annotate vendor/egulias/email-validator/EmailValidator/Parser/DomainPart.php @ 5:12f9dff5fda9 tip

Update to Drupal core 8.7.1
author Chris Cannam
date Thu, 09 May 2019 15:34:47 +0100
parents
children
rev   line source
Chris@5 1 <?php
Chris@5 2
Chris@5 3 namespace Egulias\EmailValidator\Parser;
Chris@5 4
Chris@5 5 use Egulias\EmailValidator\EmailLexer;
Chris@5 6 use Egulias\EmailValidator\Exception\CharNotAllowed;
Chris@5 7 use Egulias\EmailValidator\Exception\CommaInDomain;
Chris@5 8 use Egulias\EmailValidator\Exception\ConsecutiveAt;
Chris@5 9 use Egulias\EmailValidator\Exception\CRLFAtTheEnd;
Chris@5 10 use Egulias\EmailValidator\Exception\CRNoLF;
Chris@5 11 use Egulias\EmailValidator\Exception\DomainHyphened;
Chris@5 12 use Egulias\EmailValidator\Exception\DotAtEnd;
Chris@5 13 use Egulias\EmailValidator\Exception\DotAtStart;
Chris@5 14 use Egulias\EmailValidator\Exception\ExpectingATEXT;
Chris@5 15 use Egulias\EmailValidator\Exception\ExpectingDomainLiteralClose;
Chris@5 16 use Egulias\EmailValidator\Exception\ExpectingDTEXT;
Chris@5 17 use Egulias\EmailValidator\Exception\NoDomainPart;
Chris@5 18 use Egulias\EmailValidator\Exception\UnopenedComment;
Chris@5 19 use Egulias\EmailValidator\Warning\AddressLiteral;
Chris@5 20 use Egulias\EmailValidator\Warning\CFWSWithFWS;
Chris@5 21 use Egulias\EmailValidator\Warning\DeprecatedComment;
Chris@5 22 use Egulias\EmailValidator\Warning\DomainLiteral;
Chris@5 23 use Egulias\EmailValidator\Warning\DomainTooLong;
Chris@5 24 use Egulias\EmailValidator\Warning\IPV6BadChar;
Chris@5 25 use Egulias\EmailValidator\Warning\IPV6ColonEnd;
Chris@5 26 use Egulias\EmailValidator\Warning\IPV6ColonStart;
Chris@5 27 use Egulias\EmailValidator\Warning\IPV6Deprecated;
Chris@5 28 use Egulias\EmailValidator\Warning\IPV6DoubleColon;
Chris@5 29 use Egulias\EmailValidator\Warning\IPV6GroupCount;
Chris@5 30 use Egulias\EmailValidator\Warning\IPV6MaxGroups;
Chris@5 31 use Egulias\EmailValidator\Warning\LabelTooLong;
Chris@5 32 use Egulias\EmailValidator\Warning\ObsoleteDTEXT;
Chris@5 33 use Egulias\EmailValidator\Warning\TLD;
Chris@5 34
Chris@5 35 class DomainPart extends Parser
Chris@5 36 {
Chris@5 37 const DOMAIN_MAX_LENGTH = 254;
Chris@5 38 protected $domainPart = '';
Chris@5 39
Chris@5 40 public function parse($domainPart)
Chris@5 41 {
Chris@5 42 $this->lexer->moveNext();
Chris@5 43
Chris@5 44 if ($this->lexer->token['type'] === EmailLexer::S_DOT) {
Chris@5 45 throw new DotAtStart();
Chris@5 46 }
Chris@5 47
Chris@5 48 if ($this->lexer->token['type'] === EmailLexer::S_EMPTY) {
Chris@5 49 throw new NoDomainPart();
Chris@5 50 }
Chris@5 51 if ($this->lexer->token['type'] === EmailLexer::S_HYPHEN) {
Chris@5 52 throw new DomainHyphened();
Chris@5 53 }
Chris@5 54
Chris@5 55 if ($this->lexer->token['type'] === EmailLexer::S_OPENPARENTHESIS) {
Chris@5 56 $this->warnings[DeprecatedComment::CODE] = new DeprecatedComment();
Chris@5 57 $this->parseDomainComments();
Chris@5 58 }
Chris@5 59
Chris@5 60 $domain = $this->doParseDomainPart();
Chris@5 61
Chris@5 62 $prev = $this->lexer->getPrevious();
Chris@5 63 $length = strlen($domain);
Chris@5 64
Chris@5 65 if ($prev['type'] === EmailLexer::S_DOT) {
Chris@5 66 throw new DotAtEnd();
Chris@5 67 }
Chris@5 68 if ($prev['type'] === EmailLexer::S_HYPHEN) {
Chris@5 69 throw new DomainHyphened();
Chris@5 70 }
Chris@5 71 if ($length > self::DOMAIN_MAX_LENGTH) {
Chris@5 72 $this->warnings[DomainTooLong::CODE] = new DomainTooLong();
Chris@5 73 }
Chris@5 74 if ($prev['type'] === EmailLexer::S_CR) {
Chris@5 75 throw new CRLFAtTheEnd();
Chris@5 76 }
Chris@5 77 $this->domainPart = $domain;
Chris@5 78 }
Chris@5 79
Chris@5 80 public function getDomainPart()
Chris@5 81 {
Chris@5 82 return $this->domainPart;
Chris@5 83 }
Chris@5 84
Chris@5 85 public function checkIPV6Tag($addressLiteral, $maxGroups = 8)
Chris@5 86 {
Chris@5 87 $prev = $this->lexer->getPrevious();
Chris@5 88 if ($prev['type'] === EmailLexer::S_COLON) {
Chris@5 89 $this->warnings[IPV6ColonEnd::CODE] = new IPV6ColonEnd();
Chris@5 90 }
Chris@5 91
Chris@5 92 $IPv6 = substr($addressLiteral, 5);
Chris@5 93 //Daniel Marschall's new IPv6 testing strategy
Chris@5 94 $matchesIP = explode(':', $IPv6);
Chris@5 95 $groupCount = count($matchesIP);
Chris@5 96 $colons = strpos($IPv6, '::');
Chris@5 97
Chris@5 98 if (count(preg_grep('/^[0-9A-Fa-f]{0,4}$/', $matchesIP, PREG_GREP_INVERT)) !== 0) {
Chris@5 99 $this->warnings[IPV6BadChar::CODE] = new IPV6BadChar();
Chris@5 100 }
Chris@5 101
Chris@5 102 if ($colons === false) {
Chris@5 103 // We need exactly the right number of groups
Chris@5 104 if ($groupCount !== $maxGroups) {
Chris@5 105 $this->warnings[IPV6GroupCount::CODE] = new IPV6GroupCount();
Chris@5 106 }
Chris@5 107 return;
Chris@5 108 }
Chris@5 109
Chris@5 110 if ($colons !== strrpos($IPv6, '::')) {
Chris@5 111 $this->warnings[IPV6DoubleColon::CODE] = new IPV6DoubleColon();
Chris@5 112 return;
Chris@5 113 }
Chris@5 114
Chris@5 115 if ($colons === 0 || $colons === (strlen($IPv6) - 2)) {
Chris@5 116 // RFC 4291 allows :: at the start or end of an address
Chris@5 117 //with 7 other groups in addition
Chris@5 118 ++$maxGroups;
Chris@5 119 }
Chris@5 120
Chris@5 121 if ($groupCount > $maxGroups) {
Chris@5 122 $this->warnings[IPV6MaxGroups::CODE] = new IPV6MaxGroups();
Chris@5 123 } elseif ($groupCount === $maxGroups) {
Chris@5 124 $this->warnings[IPV6Deprecated::CODE] = new IPV6Deprecated();
Chris@5 125 }
Chris@5 126 }
Chris@5 127
Chris@5 128 protected function doParseDomainPart()
Chris@5 129 {
Chris@5 130 $domain = '';
Chris@5 131 $openedParenthesis = 0;
Chris@5 132 do {
Chris@5 133 $prev = $this->lexer->getPrevious();
Chris@5 134
Chris@5 135 $this->checkNotAllowedChars($this->lexer->token);
Chris@5 136
Chris@5 137 if ($this->lexer->token['type'] === EmailLexer::S_OPENPARENTHESIS) {
Chris@5 138 $this->parseComments();
Chris@5 139 $openedParenthesis += $this->getOpenedParenthesis();
Chris@5 140 $this->lexer->moveNext();
Chris@5 141 $tmpPrev = $this->lexer->getPrevious();
Chris@5 142 if ($tmpPrev['type'] === EmailLexer::S_CLOSEPARENTHESIS) {
Chris@5 143 $openedParenthesis--;
Chris@5 144 }
Chris@5 145 }
Chris@5 146 if ($this->lexer->token['type'] === EmailLexer::S_CLOSEPARENTHESIS) {
Chris@5 147 if ($openedParenthesis === 0) {
Chris@5 148 throw new UnopenedComment();
Chris@5 149 } else {
Chris@5 150 $openedParenthesis--;
Chris@5 151 }
Chris@5 152 }
Chris@5 153
Chris@5 154 $this->checkConsecutiveDots();
Chris@5 155 $this->checkDomainPartExceptions($prev);
Chris@5 156
Chris@5 157 if ($this->hasBrackets()) {
Chris@5 158 $this->parseDomainLiteral();
Chris@5 159 }
Chris@5 160
Chris@5 161 $this->checkLabelLength($prev);
Chris@5 162
Chris@5 163 if ($this->isFWS()) {
Chris@5 164 $this->parseFWS();
Chris@5 165 }
Chris@5 166
Chris@5 167 $domain .= $this->lexer->token['value'];
Chris@5 168 $this->lexer->moveNext();
Chris@5 169 } while ($this->lexer->token);
Chris@5 170
Chris@5 171 return $domain;
Chris@5 172 }
Chris@5 173
Chris@5 174 private function checkNotAllowedChars($token)
Chris@5 175 {
Chris@5 176 $notAllowed = [EmailLexer::S_BACKSLASH => true, EmailLexer::S_SLASH=> true];
Chris@5 177 if (isset($notAllowed[$token['type']])) {
Chris@5 178 throw new CharNotAllowed();
Chris@5 179 }
Chris@5 180 }
Chris@5 181
Chris@5 182 protected function parseDomainLiteral()
Chris@5 183 {
Chris@5 184 if ($this->lexer->isNextToken(EmailLexer::S_COLON)) {
Chris@5 185 $this->warnings[IPV6ColonStart::CODE] = new IPV6ColonStart();
Chris@5 186 }
Chris@5 187 if ($this->lexer->isNextToken(EmailLexer::S_IPV6TAG)) {
Chris@5 188 $lexer = clone $this->lexer;
Chris@5 189 $lexer->moveNext();
Chris@5 190 if ($lexer->isNextToken(EmailLexer::S_DOUBLECOLON)) {
Chris@5 191 $this->warnings[IPV6ColonStart::CODE] = new IPV6ColonStart();
Chris@5 192 }
Chris@5 193 }
Chris@5 194
Chris@5 195 return $this->doParseDomainLiteral();
Chris@5 196 }
Chris@5 197
Chris@5 198 protected function doParseDomainLiteral()
Chris@5 199 {
Chris@5 200 $IPv6TAG = false;
Chris@5 201 $addressLiteral = '';
Chris@5 202 do {
Chris@5 203 if ($this->lexer->token['type'] === EmailLexer::C_NUL) {
Chris@5 204 throw new ExpectingDTEXT();
Chris@5 205 }
Chris@5 206
Chris@5 207 if ($this->lexer->token['type'] === EmailLexer::INVALID ||
Chris@5 208 $this->lexer->token['type'] === EmailLexer::C_DEL ||
Chris@5 209 $this->lexer->token['type'] === EmailLexer::S_LF
Chris@5 210 ) {
Chris@5 211 $this->warnings[ObsoleteDTEXT::CODE] = new ObsoleteDTEXT();
Chris@5 212 }
Chris@5 213
Chris@5 214 if ($this->lexer->isNextTokenAny(array(EmailLexer::S_OPENQBRACKET, EmailLexer::S_OPENBRACKET))) {
Chris@5 215 throw new ExpectingDTEXT();
Chris@5 216 }
Chris@5 217
Chris@5 218 if ($this->lexer->isNextTokenAny(
Chris@5 219 array(EmailLexer::S_HTAB, EmailLexer::S_SP, $this->lexer->token['type'] === EmailLexer::CRLF)
Chris@5 220 )) {
Chris@5 221 $this->warnings[CFWSWithFWS::CODE] = new CFWSWithFWS();
Chris@5 222 $this->parseFWS();
Chris@5 223 }
Chris@5 224
Chris@5 225 if ($this->lexer->isNextToken(EmailLexer::S_CR)) {
Chris@5 226 throw new CRNoLF();
Chris@5 227 }
Chris@5 228
Chris@5 229 if ($this->lexer->token['type'] === EmailLexer::S_BACKSLASH) {
Chris@5 230 $this->warnings[ObsoleteDTEXT::CODE] = new ObsoleteDTEXT();
Chris@5 231 $addressLiteral .= $this->lexer->token['value'];
Chris@5 232 $this->lexer->moveNext();
Chris@5 233 $this->validateQuotedPair();
Chris@5 234 }
Chris@5 235 if ($this->lexer->token['type'] === EmailLexer::S_IPV6TAG) {
Chris@5 236 $IPv6TAG = true;
Chris@5 237 }
Chris@5 238 if ($this->lexer->token['type'] === EmailLexer::S_CLOSEQBRACKET) {
Chris@5 239 break;
Chris@5 240 }
Chris@5 241
Chris@5 242 $addressLiteral .= $this->lexer->token['value'];
Chris@5 243
Chris@5 244 } while ($this->lexer->moveNext());
Chris@5 245
Chris@5 246 $addressLiteral = str_replace('[', '', $addressLiteral);
Chris@5 247 $addressLiteral = $this->checkIPV4Tag($addressLiteral);
Chris@5 248
Chris@5 249 if (false === $addressLiteral) {
Chris@5 250 return $addressLiteral;
Chris@5 251 }
Chris@5 252
Chris@5 253 if (!$IPv6TAG) {
Chris@5 254 $this->warnings[DomainLiteral::CODE] = new DomainLiteral();
Chris@5 255 return $addressLiteral;
Chris@5 256 }
Chris@5 257
Chris@5 258 $this->warnings[AddressLiteral::CODE] = new AddressLiteral();
Chris@5 259
Chris@5 260 $this->checkIPV6Tag($addressLiteral);
Chris@5 261
Chris@5 262 return $addressLiteral;
Chris@5 263 }
Chris@5 264
Chris@5 265 protected function checkIPV4Tag($addressLiteral)
Chris@5 266 {
Chris@5 267 $matchesIP = array();
Chris@5 268
Chris@5 269 // Extract IPv4 part from the end of the address-literal (if there is one)
Chris@5 270 if (preg_match(
Chris@5 271 '/\\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@5 272 $addressLiteral,
Chris@5 273 $matchesIP
Chris@5 274 ) > 0
Chris@5 275 ) {
Chris@5 276 $index = strrpos($addressLiteral, $matchesIP[0]);
Chris@5 277 if ($index === 0) {
Chris@5 278 $this->warnings[AddressLiteral::CODE] = new AddressLiteral();
Chris@5 279 return false;
Chris@5 280 }
Chris@5 281 // Convert IPv4 part to IPv6 format for further testing
Chris@5 282 $addressLiteral = substr($addressLiteral, 0, $index) . '0:0';
Chris@5 283 }
Chris@5 284
Chris@5 285 return $addressLiteral;
Chris@5 286 }
Chris@5 287
Chris@5 288 protected function checkDomainPartExceptions($prev)
Chris@5 289 {
Chris@5 290 $invalidDomainTokens = array(
Chris@5 291 EmailLexer::S_DQUOTE => true,
Chris@5 292 EmailLexer::S_SEMICOLON => true,
Chris@5 293 EmailLexer::S_GREATERTHAN => true,
Chris@5 294 EmailLexer::S_LOWERTHAN => true,
Chris@5 295 );
Chris@5 296
Chris@5 297 if (isset($invalidDomainTokens[$this->lexer->token['type']])) {
Chris@5 298 throw new ExpectingATEXT();
Chris@5 299 }
Chris@5 300
Chris@5 301 if ($this->lexer->token['type'] === EmailLexer::S_COMMA) {
Chris@5 302 throw new CommaInDomain();
Chris@5 303 }
Chris@5 304
Chris@5 305 if ($this->lexer->token['type'] === EmailLexer::S_AT) {
Chris@5 306 throw new ConsecutiveAt();
Chris@5 307 }
Chris@5 308
Chris@5 309 if ($this->lexer->token['type'] === EmailLexer::S_OPENQBRACKET && $prev['type'] !== EmailLexer::S_AT) {
Chris@5 310 throw new ExpectingATEXT();
Chris@5 311 }
Chris@5 312
Chris@5 313 if ($this->lexer->token['type'] === EmailLexer::S_HYPHEN && $this->lexer->isNextToken(EmailLexer::S_DOT)) {
Chris@5 314 throw new DomainHyphened();
Chris@5 315 }
Chris@5 316
Chris@5 317 if ($this->lexer->token['type'] === EmailLexer::S_BACKSLASH
Chris@5 318 && $this->lexer->isNextToken(EmailLexer::GENERIC)) {
Chris@5 319 throw new ExpectingATEXT();
Chris@5 320 }
Chris@5 321 }
Chris@5 322
Chris@5 323 protected function hasBrackets()
Chris@5 324 {
Chris@5 325 if ($this->lexer->token['type'] !== EmailLexer::S_OPENBRACKET) {
Chris@5 326 return false;
Chris@5 327 }
Chris@5 328
Chris@5 329 try {
Chris@5 330 $this->lexer->find(EmailLexer::S_CLOSEBRACKET);
Chris@5 331 } catch (\RuntimeException $e) {
Chris@5 332 throw new ExpectingDomainLiteralClose();
Chris@5 333 }
Chris@5 334
Chris@5 335 return true;
Chris@5 336 }
Chris@5 337
Chris@5 338 protected function checkLabelLength($prev)
Chris@5 339 {
Chris@5 340 if ($this->lexer->token['type'] === EmailLexer::S_DOT &&
Chris@5 341 $prev['type'] === EmailLexer::GENERIC &&
Chris@5 342 strlen($prev['value']) > 63
Chris@5 343 ) {
Chris@5 344 $this->warnings[LabelTooLong::CODE] = new LabelTooLong();
Chris@5 345 }
Chris@5 346 }
Chris@5 347
Chris@5 348 protected function parseDomainComments()
Chris@5 349 {
Chris@5 350 $this->isUnclosedComment();
Chris@5 351 while (!$this->lexer->isNextToken(EmailLexer::S_CLOSEPARENTHESIS)) {
Chris@5 352 $this->warnEscaping();
Chris@5 353 $this->lexer->moveNext();
Chris@5 354 }
Chris@5 355
Chris@5 356 $this->lexer->moveNext();
Chris@5 357 if ($this->lexer->isNextToken(EmailLexer::S_DOT)) {
Chris@5 358 throw new ExpectingATEXT();
Chris@5 359 }
Chris@5 360 }
Chris@5 361
Chris@5 362 protected function addTLDWarnings()
Chris@5 363 {
Chris@5 364 if ($this->warnings[DomainLiteral::CODE]) {
Chris@5 365 $this->warnings[TLD::CODE] = new TLD();
Chris@5 366 }
Chris@5 367 }
Chris@5 368 }