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