lexer->moveNext(); $this->performDomainStartChecks(); $domain = $this->doParseDomainPart(); $prev = $this->lexer->getPrevious(); $length = strlen($domain); if ($prev['type'] === EmailLexer::S_DOT) { throw new DotAtEnd(); } if ($prev['type'] === EmailLexer::S_HYPHEN) { throw new DomainHyphened(); } if ($length > self::DOMAIN_MAX_LENGTH) { $this->warnings[DomainTooLong::CODE] = new DomainTooLong(); } if ($prev['type'] === EmailLexer::S_CR) { throw new CRLFAtTheEnd(); } $this->domainPart = $domain; } private function performDomainStartChecks() { $this->checkInvalidTokensAfterAT(); $this->checkEmptyDomain(); if ($this->lexer->token['type'] === EmailLexer::S_OPENPARENTHESIS) { $this->warnings[DeprecatedComment::CODE] = new DeprecatedComment(); $this->parseDomainComments(); } } private function checkEmptyDomain() { $thereIsNoDomain = $this->lexer->token['type'] === EmailLexer::S_EMPTY || ($this->lexer->token['type'] === EmailLexer::S_SP && !$this->lexer->isNextToken(EmailLexer::GENERIC)); if ($thereIsNoDomain) { throw new NoDomainPart(); } } private function checkInvalidTokensAfterAT() { if ($this->lexer->token['type'] === EmailLexer::S_DOT) { throw new DotAtStart(); } if ($this->lexer->token['type'] === EmailLexer::S_HYPHEN) { throw new DomainHyphened(); } } /** * @return string */ public function getDomainPart() { return $this->domainPart; } /** * @param string $addressLiteral * @param int $maxGroups */ public function checkIPV6Tag($addressLiteral, $maxGroups = 8) { $prev = $this->lexer->getPrevious(); if ($prev['type'] === EmailLexer::S_COLON) { $this->warnings[IPV6ColonEnd::CODE] = new IPV6ColonEnd(); } $IPv6 = substr($addressLiteral, 5); //Daniel Marschall's new IPv6 testing strategy $matchesIP = explode(':', $IPv6); $groupCount = count($matchesIP); $colons = strpos($IPv6, '::'); if (count(preg_grep('/^[0-9A-Fa-f]{0,4}$/', $matchesIP, PREG_GREP_INVERT)) !== 0) { $this->warnings[IPV6BadChar::CODE] = new IPV6BadChar(); } if ($colons === false) { // We need exactly the right number of groups if ($groupCount !== $maxGroups) { $this->warnings[IPV6GroupCount::CODE] = new IPV6GroupCount(); } return; } if ($colons !== strrpos($IPv6, '::')) { $this->warnings[IPV6DoubleColon::CODE] = new IPV6DoubleColon(); return; } if ($colons === 0 || $colons === (strlen($IPv6) - 2)) { // RFC 4291 allows :: at the start or end of an address //with 7 other groups in addition ++$maxGroups; } if ($groupCount > $maxGroups) { $this->warnings[IPV6MaxGroups::CODE] = new IPV6MaxGroups(); } elseif ($groupCount === $maxGroups) { $this->warnings[IPV6Deprecated::CODE] = new IPV6Deprecated(); } } /** * @return string */ protected function doParseDomainPart() { $domain = ''; $label = ''; $openedParenthesis = 0; do { $prev = $this->lexer->getPrevious(); $this->checkNotAllowedChars($this->lexer->token); if ($this->lexer->token['type'] === EmailLexer::S_OPENPARENTHESIS) { $this->parseComments(); $openedParenthesis += $this->getOpenedParenthesis(); $this->lexer->moveNext(); $tmpPrev = $this->lexer->getPrevious(); if ($tmpPrev['type'] === EmailLexer::S_CLOSEPARENTHESIS) { $openedParenthesis--; } } if ($this->lexer->token['type'] === EmailLexer::S_CLOSEPARENTHESIS) { if ($openedParenthesis === 0) { throw new UnopenedComment(); } else { $openedParenthesis--; } } $this->checkConsecutiveDots(); $this->checkDomainPartExceptions($prev); if ($this->hasBrackets()) { $this->parseDomainLiteral(); } if ($this->lexer->token['type'] === EmailLexer::S_DOT) { $this->checkLabelLength($label); $label = ''; } else { $label .= $this->lexer->token['value']; } if ($this->isFWS()) { $this->parseFWS(); } $domain .= $this->lexer->token['value']; $this->lexer->moveNext(); if ($this->lexer->token['type'] === EmailLexer::S_SP) { throw new CharNotAllowed(); } } while (null !== $this->lexer->token['type']); $this->checkLabelLength($label); return $domain; } private function checkNotAllowedChars(array $token) { $notAllowed = [EmailLexer::S_BACKSLASH => true, EmailLexer::S_SLASH=> true]; if (isset($notAllowed[$token['type']])) { throw new CharNotAllowed(); } } /** * @return string|false */ protected function parseDomainLiteral() { if ($this->lexer->isNextToken(EmailLexer::S_COLON)) { $this->warnings[IPV6ColonStart::CODE] = new IPV6ColonStart(); } if ($this->lexer->isNextToken(EmailLexer::S_IPV6TAG)) { $lexer = clone $this->lexer; $lexer->moveNext(); if ($lexer->isNextToken(EmailLexer::S_DOUBLECOLON)) { $this->warnings[IPV6ColonStart::CODE] = new IPV6ColonStart(); } } return $this->doParseDomainLiteral(); } /** * @return string|false */ protected function doParseDomainLiteral() { $IPv6TAG = false; $addressLiteral = ''; do { if ($this->lexer->token['type'] === EmailLexer::C_NUL) { throw new ExpectingDTEXT(); } if ($this->lexer->token['type'] === EmailLexer::INVALID || $this->lexer->token['type'] === EmailLexer::C_DEL || $this->lexer->token['type'] === EmailLexer::S_LF ) { $this->warnings[ObsoleteDTEXT::CODE] = new ObsoleteDTEXT(); } if ($this->lexer->isNextTokenAny(array(EmailLexer::S_OPENQBRACKET, EmailLexer::S_OPENBRACKET))) { throw new ExpectingDTEXT(); } if ($this->lexer->isNextTokenAny( array(EmailLexer::S_HTAB, EmailLexer::S_SP, $this->lexer->token['type'] === EmailLexer::CRLF) )) { $this->warnings[CFWSWithFWS::CODE] = new CFWSWithFWS(); $this->parseFWS(); } if ($this->lexer->isNextToken(EmailLexer::S_CR)) { throw new CRNoLF(); } if ($this->lexer->token['type'] === EmailLexer::S_BACKSLASH) { $this->warnings[ObsoleteDTEXT::CODE] = new ObsoleteDTEXT(); $addressLiteral .= $this->lexer->token['value']; $this->lexer->moveNext(); $this->validateQuotedPair(); } if ($this->lexer->token['type'] === EmailLexer::S_IPV6TAG) { $IPv6TAG = true; } if ($this->lexer->token['type'] === EmailLexer::S_CLOSEQBRACKET) { break; } $addressLiteral .= $this->lexer->token['value']; } while ($this->lexer->moveNext()); $addressLiteral = str_replace('[', '', $addressLiteral); $addressLiteral = $this->checkIPV4Tag($addressLiteral); if (false === $addressLiteral) { return $addressLiteral; } if (!$IPv6TAG) { $this->warnings[DomainLiteral::CODE] = new DomainLiteral(); return $addressLiteral; } $this->warnings[AddressLiteral::CODE] = new AddressLiteral(); $this->checkIPV6Tag($addressLiteral); return $addressLiteral; } /** * @param string $addressLiteral * * @return string|false */ protected function checkIPV4Tag($addressLiteral) { $matchesIP = array(); // Extract IPv4 part from the end of the address-literal (if there is one) if (preg_match( '/\\b(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/', $addressLiteral, $matchesIP ) > 0 ) { $index = strrpos($addressLiteral, $matchesIP[0]); if ($index === 0) { $this->warnings[AddressLiteral::CODE] = new AddressLiteral(); return false; } // Convert IPv4 part to IPv6 format for further testing $addressLiteral = substr($addressLiteral, 0, (int) $index) . '0:0'; } return $addressLiteral; } protected function checkDomainPartExceptions(array $prev) { $invalidDomainTokens = array( EmailLexer::S_DQUOTE => true, EmailLexer::S_SQUOTE => true, EmailLexer::S_BACKTICK => true, EmailLexer::S_SEMICOLON => true, EmailLexer::S_GREATERTHAN => true, EmailLexer::S_LOWERTHAN => true, ); if (isset($invalidDomainTokens[$this->lexer->token['type']])) { throw new ExpectingATEXT(); } if ($this->lexer->token['type'] === EmailLexer::S_COMMA) { throw new CommaInDomain(); } if ($this->lexer->token['type'] === EmailLexer::S_AT) { throw new ConsecutiveAt(); } if ($this->lexer->token['type'] === EmailLexer::S_OPENQBRACKET && $prev['type'] !== EmailLexer::S_AT) { throw new ExpectingATEXT(); } if ($this->lexer->token['type'] === EmailLexer::S_HYPHEN && $this->lexer->isNextToken(EmailLexer::S_DOT)) { throw new DomainHyphened(); } if ($this->lexer->token['type'] === EmailLexer::S_BACKSLASH && $this->lexer->isNextToken(EmailLexer::GENERIC)) { throw new ExpectingATEXT(); } } /** * @return bool */ protected function hasBrackets() { if ($this->lexer->token['type'] !== EmailLexer::S_OPENBRACKET) { return false; } try { $this->lexer->find(EmailLexer::S_CLOSEBRACKET); } catch (\RuntimeException $e) { throw new ExpectingDomainLiteralClose(); } return true; } /** * @param string $label */ protected function checkLabelLength($label) { if ($this->isLabelTooLong($label)) { $this->warnings[LabelTooLong::CODE] = new LabelTooLong(); } } /** * @param string $label * @return bool */ private function isLabelTooLong($label) { if (preg_match('/[^\x00-\x7F]/', $label)) { idn_to_ascii($label, IDNA_DEFAULT, INTL_IDNA_VARIANT_UTS46, $idnaInfo); return (bool) ($idnaInfo['errors'] & IDNA_ERROR_LABEL_TOO_LONG); } return strlen($label) > self::LABEL_MAX_LENGTH; } protected function parseDomainComments() { $this->isUnclosedComment(); while (!$this->lexer->isNextToken(EmailLexer::S_CLOSEPARENTHESIS)) { $this->warnEscaping(); $this->lexer->moveNext(); } $this->lexer->moveNext(); if ($this->lexer->isNextToken(EmailLexer::S_DOT)) { throw new ExpectingATEXT(); } } protected function addTLDWarnings() { if ($this->warnings[DomainLiteral::CODE]) { $this->warnings[TLD::CODE] = new TLD(); } } }