diff --git a/composer.lock b/composer.lock index 2fc38a8..c575e7f 100644 --- a/composer.lock +++ b/composer.lock @@ -88,16 +88,16 @@ }, { "name": "symfony/polyfill-intl-idn", - "version": "v1.22.0", + "version": "v1.22.1", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-idn.git", - "reference": "0eb8293dbbcd6ef6bf81404c9ce7d95bcdf34f44" + "reference": "2d63434d922daf7da8dd863e7907e67ee3031483" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-idn/zipball/0eb8293dbbcd6ef6bf81404c9ce7d95bcdf34f44", - "reference": "0eb8293dbbcd6ef6bf81404c9ce7d95bcdf34f44", + "url": "https://api.github.com/repos/symfony/polyfill-intl-idn/zipball/2d63434d922daf7da8dd863e7907e67ee3031483", + "reference": "2d63434d922daf7da8dd863e7907e67ee3031483", "shasum": "" }, "require": { @@ -155,7 +155,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-idn/tree/v1.22.0" + "source": "https://github.com/symfony/polyfill-intl-idn/tree/v1.22.1" }, "funding": [ { @@ -171,20 +171,20 @@ "type": "tidelift" } ], - "time": "2021-01-07T16:49:33+00:00" + "time": "2021-01-22T09:19:47+00:00" }, { "name": "symfony/polyfill-intl-normalizer", - "version": "v1.22.0", + "version": "v1.22.1", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-normalizer.git", - "reference": "6e971c891537eb617a00bb07a43d182a6915faba" + "reference": "43a0283138253ed1d48d352ab6d0bdb3f809f248" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/6e971c891537eb617a00bb07a43d182a6915faba", - "reference": "6e971c891537eb617a00bb07a43d182a6915faba", + "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/43a0283138253ed1d48d352ab6d0bdb3f809f248", + "reference": "43a0283138253ed1d48d352ab6d0bdb3f809f248", "shasum": "" }, "require": { @@ -239,7 +239,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.22.0" + "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.22.1" }, "funding": [ { @@ -255,11 +255,11 @@ "type": "tidelift" } ], - "time": "2021-01-07T17:09:11+00:00" + "time": "2021-01-22T09:19:47+00:00" }, { "name": "symfony/polyfill-php72", - "version": "v1.22.0", + "version": "v1.22.1", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php72.git", @@ -315,7 +315,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php72/tree/v1.22.0" + "source": "https://github.com/symfony/polyfill-php72/tree/v1.22.1" }, "funding": [ { @@ -865,16 +865,16 @@ }, { "name": "felixfbecker/language-server-protocol", - "version": "v1.5.0", + "version": "1.5.1", "source": { "type": "git", "url": "https://github.com/felixfbecker/php-language-server-protocol.git", - "reference": "85e83cacd2ed573238678c6875f8f0d7ec699541" + "reference": "9d846d1f5cf101deee7a61c8ba7caa0a975cd730" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/felixfbecker/php-language-server-protocol/zipball/85e83cacd2ed573238678c6875f8f0d7ec699541", - "reference": "85e83cacd2ed573238678c6875f8f0d7ec699541", + "url": "https://api.github.com/repos/felixfbecker/php-language-server-protocol/zipball/9d846d1f5cf101deee7a61c8ba7caa0a975cd730", + "reference": "9d846d1f5cf101deee7a61c8ba7caa0a975cd730", "shasum": "" }, "require": { @@ -915,9 +915,9 @@ ], "support": { "issues": "https://github.com/felixfbecker/php-language-server-protocol/issues", - "source": "https://github.com/felixfbecker/php-language-server-protocol/tree/v1.5.0" + "source": "https://github.com/felixfbecker/php-language-server-protocol/tree/1.5.1" }, - "time": "2020-10-23T13:55:30+00:00" + "time": "2021-02-22T14:02:09+00:00" }, { "name": "guzzlehttp/guzzle", @@ -1431,16 +1431,16 @@ }, { "name": "phar-io/version", - "version": "3.0.4", + "version": "3.1.0", "source": { "type": "git", "url": "https://github.com/phar-io/version.git", - "reference": "e4782611070e50613683d2b9a57730e9a3ba5451" + "reference": "bae7c545bef187884426f042434e561ab1ddb182" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phar-io/version/zipball/e4782611070e50613683d2b9a57730e9a3ba5451", - "reference": "e4782611070e50613683d2b9a57730e9a3ba5451", + "url": "https://api.github.com/repos/phar-io/version/zipball/bae7c545bef187884426f042434e561ab1ddb182", + "reference": "bae7c545bef187884426f042434e561ab1ddb182", "shasum": "" }, "require": { @@ -1476,9 +1476,9 @@ "description": "Library for handling version information and constraints", "support": { "issues": "https://github.com/phar-io/version/issues", - "source": "https://github.com/phar-io/version/tree/3.0.4" + "source": "https://github.com/phar-io/version/tree/3.1.0" }, - "time": "2020-12-13T23:18:30+00:00" + "time": "2021-02-23T14:00:09+00:00" }, { "name": "php-coveralls/php-coveralls", @@ -3165,16 +3165,16 @@ }, { "name": "symfony/config", - "version": "v5.2.1", + "version": "v5.2.3", "source": { "type": "git", "url": "https://github.com/symfony/config.git", - "reference": "d0a82d965296083fe463d655a3644cbe49cbaa80" + "reference": "50e0e1314a3b2609d32b6a5a0d0fb5342494c4ab" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/config/zipball/d0a82d965296083fe463d655a3644cbe49cbaa80", - "reference": "d0a82d965296083fe463d655a3644cbe49cbaa80", + "url": "https://api.github.com/repos/symfony/config/zipball/50e0e1314a3b2609d32b6a5a0d0fb5342494c4ab", + "reference": "50e0e1314a3b2609d32b6a5a0d0fb5342494c4ab", "shasum": "" }, "require": { @@ -3220,10 +3220,10 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony Config Component", + "description": "Helps you find, load, combine, autofill and validate configuration values of any kind", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/config/tree/v5.2.1" + "source": "https://github.com/symfony/config/tree/v5.2.3" }, "funding": [ { @@ -3239,20 +3239,20 @@ "type": "tidelift" } ], - "time": "2020-12-09T18:54:12+00:00" + "time": "2021-01-27T10:15:41+00:00" }, { "name": "symfony/console", - "version": "v5.2.1", + "version": "v5.2.3", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "47c02526c532fb381374dab26df05e7313978976" + "reference": "89d4b176d12a2946a1ae4e34906a025b7b6b135a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/47c02526c532fb381374dab26df05e7313978976", - "reference": "47c02526c532fb381374dab26df05e7313978976", + "url": "https://api.github.com/repos/symfony/console/zipball/89d4b176d12a2946a1ae4e34906a025b7b6b135a", + "reference": "89d4b176d12a2946a1ae4e34906a025b7b6b135a", "shasum": "" }, "require": { @@ -3311,7 +3311,7 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony Console Component", + "description": "Eases the creation of beautiful and testable command line interfaces", "homepage": "https://symfony.com", "keywords": [ "cli", @@ -3320,7 +3320,7 @@ "terminal" ], "support": { - "source": "https://github.com/symfony/console/tree/v5.2.1" + "source": "https://github.com/symfony/console/tree/v5.2.3" }, "funding": [ { @@ -3336,7 +3336,7 @@ "type": "tidelift" } ], - "time": "2020-12-18T08:03:05+00:00" + "time": "2021-01-28T22:06:19+00:00" }, { "name": "symfony/deprecation-contracts", @@ -3407,16 +3407,16 @@ }, { "name": "symfony/filesystem", - "version": "v5.2.1", + "version": "v5.2.3", "source": { "type": "git", "url": "https://github.com/symfony/filesystem.git", - "reference": "fa8f8cab6b65e2d99a118e082935344c5ba8c60d" + "reference": "262d033b57c73e8b59cd6e68a45c528318b15038" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/filesystem/zipball/fa8f8cab6b65e2d99a118e082935344c5ba8c60d", - "reference": "fa8f8cab6b65e2d99a118e082935344c5ba8c60d", + "url": "https://api.github.com/repos/symfony/filesystem/zipball/262d033b57c73e8b59cd6e68a45c528318b15038", + "reference": "262d033b57c73e8b59cd6e68a45c528318b15038", "shasum": "" }, "require": { @@ -3446,10 +3446,10 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony Filesystem Component", + "description": "Provides basic utilities for the filesystem", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/filesystem/tree/v5.2.1" + "source": "https://github.com/symfony/filesystem/tree/v5.2.3" }, "funding": [ { @@ -3465,11 +3465,11 @@ "type": "tidelift" } ], - "time": "2020-11-30T17:05:38+00:00" + "time": "2021-01-27T10:01:46+00:00" }, { "name": "symfony/polyfill-ctype", - "version": "v1.22.0", + "version": "v1.22.1", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-ctype.git", @@ -3528,7 +3528,7 @@ "portable" ], "support": { - "source": "https://github.com/symfony/polyfill-ctype/tree/v1.22.0" + "source": "https://github.com/symfony/polyfill-ctype/tree/v1.22.1" }, "funding": [ { @@ -3548,16 +3548,16 @@ }, { "name": "symfony/polyfill-intl-grapheme", - "version": "v1.22.0", + "version": "v1.22.1", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-grapheme.git", - "reference": "267a9adeb8ecb8071040a740930e077cdfb987af" + "reference": "5601e09b69f26c1828b13b6bb87cb07cddba3170" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/267a9adeb8ecb8071040a740930e077cdfb987af", - "reference": "267a9adeb8ecb8071040a740930e077cdfb987af", + "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/5601e09b69f26c1828b13b6bb87cb07cddba3170", + "reference": "5601e09b69f26c1828b13b6bb87cb07cddba3170", "shasum": "" }, "require": { @@ -3609,7 +3609,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.22.0" + "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.22.1" }, "funding": [ { @@ -3625,20 +3625,20 @@ "type": "tidelift" } ], - "time": "2021-01-07T16:49:33+00:00" + "time": "2021-01-22T09:19:47+00:00" }, { "name": "symfony/polyfill-mbstring", - "version": "v1.22.0", + "version": "v1.22.1", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-mbstring.git", - "reference": "f377a3dd1fde44d37b9831d68dc8dea3ffd28e13" + "reference": "5232de97ee3b75b0360528dae24e73db49566ab1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/f377a3dd1fde44d37b9831d68dc8dea3ffd28e13", - "reference": "f377a3dd1fde44d37b9831d68dc8dea3ffd28e13", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/5232de97ee3b75b0360528dae24e73db49566ab1", + "reference": "5232de97ee3b75b0360528dae24e73db49566ab1", "shasum": "" }, "require": { @@ -3689,7 +3689,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.22.0" + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.22.1" }, "funding": [ { @@ -3705,11 +3705,11 @@ "type": "tidelift" } ], - "time": "2021-01-07T16:49:33+00:00" + "time": "2021-01-22T09:19:47+00:00" }, { "name": "symfony/polyfill-php73", - "version": "v1.22.0", + "version": "v1.22.1", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php73.git", @@ -3768,7 +3768,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php73/tree/v1.22.0" + "source": "https://github.com/symfony/polyfill-php73/tree/v1.22.1" }, "funding": [ { @@ -3788,7 +3788,7 @@ }, { "name": "symfony/polyfill-php80", - "version": "v1.22.0", + "version": "v1.22.1", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php80.git", @@ -3851,7 +3851,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php80/tree/v1.22.0" + "source": "https://github.com/symfony/polyfill-php80/tree/v1.22.1" }, "funding": [ { @@ -3950,16 +3950,16 @@ }, { "name": "symfony/stopwatch", - "version": "v5.2.1", + "version": "v5.2.3", "source": { "type": "git", "url": "https://github.com/symfony/stopwatch.git", - "reference": "2b105c0354f39a63038a1d8bf776ee92852813af" + "reference": "b12274acfab9d9850c52583d136a24398cdf1a0c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/stopwatch/zipball/2b105c0354f39a63038a1d8bf776ee92852813af", - "reference": "2b105c0354f39a63038a1d8bf776ee92852813af", + "url": "https://api.github.com/repos/symfony/stopwatch/zipball/b12274acfab9d9850c52583d136a24398cdf1a0c", + "reference": "b12274acfab9d9850c52583d136a24398cdf1a0c", "shasum": "" }, "require": { @@ -3989,10 +3989,10 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony Stopwatch Component", + "description": "Provides a way to profile code", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/stopwatch/tree/v5.2.1" + "source": "https://github.com/symfony/stopwatch/tree/v5.2.3" }, "funding": [ { @@ -4008,20 +4008,20 @@ "type": "tidelift" } ], - "time": "2020-11-01T16:14:45+00:00" + "time": "2021-01-27T10:15:41+00:00" }, { "name": "symfony/string", - "version": "v5.2.1", + "version": "v5.2.3", "source": { "type": "git", "url": "https://github.com/symfony/string.git", - "reference": "5bd67751d2e3f7d6f770c9154b8fbcb2aa05f7ed" + "reference": "c95468897f408dd0aca2ff582074423dd0455122" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/string/zipball/5bd67751d2e3f7d6f770c9154b8fbcb2aa05f7ed", - "reference": "5bd67751d2e3f7d6f770c9154b8fbcb2aa05f7ed", + "url": "https://api.github.com/repos/symfony/string/zipball/c95468897f408dd0aca2ff582074423dd0455122", + "reference": "c95468897f408dd0aca2ff582074423dd0455122", "shasum": "" }, "require": { @@ -4064,7 +4064,7 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony String component", + "description": "Provides an object-oriented API to strings and deals with bytes, UTF-8 code points and grapheme clusters in a unified way", "homepage": "https://symfony.com", "keywords": [ "grapheme", @@ -4075,7 +4075,7 @@ "utf8" ], "support": { - "source": "https://github.com/symfony/string/tree/v5.2.1" + "source": "https://github.com/symfony/string/tree/v5.2.3" }, "funding": [ { @@ -4091,20 +4091,20 @@ "type": "tidelift" } ], - "time": "2020-12-05T07:33:16+00:00" + "time": "2021-01-25T15:14:59+00:00" }, { "name": "symfony/yaml", - "version": "v5.2.1", + "version": "v5.2.3", "source": { "type": "git", "url": "https://github.com/symfony/yaml.git", - "reference": "290ea5e03b8cf9b42c783163123f54441fb06939" + "reference": "338cddc6d74929f6adf19ca5682ac4b8e109cdb0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/yaml/zipball/290ea5e03b8cf9b42c783163123f54441fb06939", - "reference": "290ea5e03b8cf9b42c783163123f54441fb06939", + "url": "https://api.github.com/repos/symfony/yaml/zipball/338cddc6d74929f6adf19ca5682ac4b8e109cdb0", + "reference": "338cddc6d74929f6adf19ca5682ac4b8e109cdb0", "shasum": "" }, "require": { @@ -4147,10 +4147,10 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony Yaml Component", + "description": "Loads and dumps YAML files", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/yaml/tree/v5.2.1" + "source": "https://github.com/symfony/yaml/tree/v5.2.3" }, "funding": [ { @@ -4166,7 +4166,7 @@ "type": "tidelift" } ], - "time": "2020-12-08T17:02:38+00:00" + "time": "2021-02-03T04:42:09+00:00" }, { "name": "theseer/tokenizer", @@ -4220,16 +4220,16 @@ }, { "name": "vimeo/psalm", - "version": "4.4.1", + "version": "4.6.2", "source": { "type": "git", "url": "https://github.com/vimeo/psalm.git", - "reference": "9fd7a7d885b3a216cff8dec9d8c21a132f275224" + "reference": "bca09d74adc704c4eaee36a3c3e9d379e290fc3b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/vimeo/psalm/zipball/9fd7a7d885b3a216cff8dec9d8c21a132f275224", - "reference": "9fd7a7d885b3a216cff8dec9d8c21a132f275224", + "url": "https://api.github.com/repos/vimeo/psalm/zipball/bca09d74adc704c4eaee36a3c3e9d379e290fc3b", + "reference": "bca09d74adc704c4eaee36a3c3e9d379e290fc3b", "shasum": "" }, "require": { @@ -4246,8 +4246,8 @@ "ext-simplexml": "*", "ext-tokenizer": "*", "felixfbecker/advanced-json-rpc": "^3.0.3", - "felixfbecker/language-server-protocol": "^1.4", - "netresearch/jsonmapper": "^1.0 || ^2.0 || ^3.0", + "felixfbecker/language-server-protocol": "^1.5", + "netresearch/jsonmapper": "^1.0 || ^2.0 || ^3.0 || ^4.0", "nikic/php-parser": "^4.10.1", "openlss/lib-array2xml": "^1.0", "php": "^7.1|^8", @@ -4263,6 +4263,7 @@ "bamarni/composer-bin-plugin": "^1.2", "brianium/paratest": "^4.0||^6.0", "ext-curl": "*", + "php-parallel-lint/php-parallel-lint": "^1.2", "phpdocumentor/reflection-docblock": "^5", "phpmyadmin/sql-parser": "5.1.0||dev-master", "phpspec/prophecy": ">=1.9.0", @@ -4318,21 +4319,21 @@ ], "support": { "issues": "https://github.com/vimeo/psalm/issues", - "source": "https://github.com/vimeo/psalm/tree/4.4.1" + "source": "https://github.com/vimeo/psalm/tree/4.6.2" }, - "time": "2021-01-14T21:44:29+00:00" + "time": "2021-02-26T02:24:18+00:00" }, { "name": "webmozart/assert", "version": "1.9.1", "source": { "type": "git", - "url": "https://github.com/webmozart/assert.git", + "url": "https://github.com/webmozarts/assert.git", "reference": "bafc69caeb4d49c39fd0779086c03a3738cbb389" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/webmozart/assert/zipball/bafc69caeb4d49c39fd0779086c03a3738cbb389", + "url": "https://api.github.com/repos/webmozarts/assert/zipball/bafc69caeb4d49c39fd0779086c03a3738cbb389", "reference": "bafc69caeb4d49c39fd0779086c03a3738cbb389", "shasum": "" }, @@ -4370,8 +4371,8 @@ "validate" ], "support": { - "issues": "https://github.com/webmozart/assert/issues", - "source": "https://github.com/webmozart/assert/tree/master" + "issues": "https://github.com/webmozarts/assert/issues", + "source": "https://github.com/webmozarts/assert/tree/1.9.1" }, "time": "2020-07-08T17:02:28+00:00" }, diff --git a/src/EmailLexer.php b/src/EmailLexer.php index fdf879f..41e9ea9 100644 --- a/src/EmailLexer.php +++ b/src/EmailLexer.php @@ -124,6 +124,7 @@ class EmailLexer extends AbstractLexer * * @var array * + * @psalm-suppress NonInvariantDocblockPropertyType * @psalm-var array{value:string, type:null|int, position:int} * @psalm-suppress NonInvariantDocblockPropertyType */ diff --git a/src/EmailParser.php b/src/EmailParser.php index 9b30c03..c78f74a 100644 --- a/src/EmailParser.php +++ b/src/EmailParser.php @@ -9,18 +9,12 @@ use Egulias\EmailValidator\Parser\DomainPart; use Egulias\EmailValidator\Result\ValidEmail; use Egulias\EmailValidator\Result\InvalidEmail; use Egulias\EmailValidator\Warning\EmailTooLong; -use Egulias\EmailValidator\Result\Reason\ExpectingATEXT; use Egulias\EmailValidator\Result\Reason\NoLocalPart; -class EmailParser +class EmailParser extends Parser { const EMAIL_MAX_LENGTH = 254; - /** - * @var array - */ - protected $warnings = []; - /** * @var string */ @@ -30,52 +24,39 @@ class EmailParser * @var string */ protected $localPart = ''; - /** - * @var EmailLexer - */ - protected $lexer; - - public function __construct(EmailLexer $lexer) - { - $this->lexer = $lexer; - } public function parse(string $str) : Result { - $this->lexer->setInput($str); - - if (!$this->hasAtToken()) { - return new InvalidEmail(new NoLocalPart(), $this->lexer->token["value"]); - } - - $localPartResult = $this->processLocalPart(); - - if ($localPartResult->isInvalid()) { - return $localPartResult; - } - - $domainPartResult = $this->processDomainPart(); - - if ($domainPartResult->isInvalid()) { - return $domainPartResult; - } - - if ($this->lexer->hasInvalidTokens()) { - return new InvalidEmail(new ExpectingATEXT("Invalid tokens found"), $this->lexer->token["value"]); - } + $result = parent::parse($str); $this->addLongEmailWarning($this->localPart, $this->domainPart); + return $result; + } + + protected function preLeftParsing(): Result + { + if (!$this->hasAtToken()) { + return new InvalidEmail(new NoLocalPart(), $this->lexer->token["value"]); + } return new ValidEmail(); } + protected function parseLeftFromAt(): Result + { + return $this->processLocalPart(); + } + + protected function parseRightFromAt(): Result + { + return $this->processDomainPart(); + } + private function processLocalPart() : Result { - $this->lexer->startRecording(); $localPartParser = new LocalPart($this->lexer); $localPartResult = $localPartParser->parse(); - $this->lexer->stopRecording(); - $this->localPart = rtrim($this->lexer->getAccumulatedValues(), '@'); + $this->localPart = $localPartParser->localPart(); $this->warnings = array_merge($localPartParser->getWarnings(), $this->warnings); return $localPartResult; @@ -83,28 +64,14 @@ class EmailParser private function processDomainPart() : Result { - $this->lexer->clearRecorded(); - $this->lexer->startRecording(); $domainPartParser = new DomainPart($this->lexer); $domainPartResult = $domainPartParser->parse(); - $this->lexer->stopRecording(); - $this->domainPart = $this->lexer->getAccumulatedValues(); + $this->domainPart = $domainPartParser->domainPart(); $this->warnings = array_merge($domainPartParser->getWarnings(), $this->warnings); return $domainPartResult; } - /** - * @return Warning\Warning[] - */ - public function getWarnings() : array - { - return $this->warnings; - } - - /** - * @return string - */ public function getDomainPart() : string { return $this->domainPart; @@ -115,25 +82,7 @@ class EmailParser return $this->localPart; } - /** - * @return bool - */ - protected function hasAtToken() : bool - { - $this->lexer->moveNext(); - $this->lexer->moveNext(); - if ($this->lexer->token['type'] === EmailLexer::S_AT) { - return false; - } - - return true; - } - - /** - * @param string $localPart - * @param string $parsedDomainPart - */ - protected function addLongEmailWarning($localPart, $parsedDomainPart) : void + private function addLongEmailWarning(string $localPart, string $parsedDomainPart) : void { if (strlen($localPart . '@' . $parsedDomainPart) > self::EMAIL_MAX_LENGTH) { $this->warnings[EmailTooLong::CODE] = new EmailTooLong(); diff --git a/src/MessageIDParser.php b/src/MessageIDParser.php new file mode 100644 index 0000000..9b029e1 --- /dev/null +++ b/src/MessageIDParser.php @@ -0,0 +1,93 @@ +addLongEmailWarning($this->idLeft, $this->idRight); + + return $result; + } + + protected function preLeftParsing(): Result + { + if (!$this->hasAtToken()) { + return new InvalidEmail(new NoLocalPart(), $this->lexer->token["value"]); + } + return new ValidEmail(); + } + + protected function parseLeftFromAt(): Result + { + return $this->processIDLeft(); + } + + protected function parseRightFromAt(): Result + { + return $this->processIDRight(); + } + + private function processIDLeft() : Result + { + $localPartParser = new IDLeftPart($this->lexer); + $localPartResult = $localPartParser->parse(); + $this->idLeft = $localPartParser->localPart(); + $this->warnings = array_merge($localPartParser->getWarnings(), $this->warnings); + + return $localPartResult; + } + + private function processIDRight() : Result + { + $domainPartParser = new IDRightPart($this->lexer); + $domainPartResult = $domainPartParser->parse(); + $this->idRight = $domainPartParser->domainPart(); + $this->warnings = array_merge($domainPartParser->getWarnings(), $this->warnings); + + return $domainPartResult; + } + + public function getLeftPart() : string + { + return $this->idLeft; + } + + public function getRightPart() : string + { + return $this->idRight; + } + + private function addLongEmailWarning(string $localPart, string $parsedDomainPart) : void + { + if (strlen($localPart . '@' . $parsedDomainPart) > self::EMAILID_MAX_LENGTH) { + $this->warnings[EmailTooLong::CODE] = new EmailTooLong(); + } + } +} \ No newline at end of file diff --git a/src/Parser.php b/src/Parser.php new file mode 100644 index 0000000..b1905f9 --- /dev/null +++ b/src/Parser.php @@ -0,0 +1,78 @@ +lexer = $lexer; + } + + public function parse(string $str) : Result + { + $this->lexer->setInput($str); + + if ($this->lexer->hasInvalidTokens()) { + return new InvalidEmail(new ExpectingATEXT("Invalid tokens found"), $this->lexer->token["value"]); + } + + $preParsingResult = $this->preLeftParsing(); + if ($preParsingResult->isInvalid()) { + return $preParsingResult; + } + + $localPartResult = $this->parseLeftFromAt(); + + if ($localPartResult->isInvalid()) { + return $localPartResult; + } + + $domainPartResult = $this->parseRightFromAt(); + + if ($domainPartResult->isInvalid()) { + return $domainPartResult; + } + + return new ValidEmail(); + } + + /** + * @return Warning\Warning[] + */ + public function getWarnings() : array + { + return $this->warnings; + } + + protected function hasAtToken() : bool + { + $this->lexer->moveNext(); + $this->lexer->moveNext(); + + return $this->lexer->token['type'] !== EmailLexer::S_AT; + } +} diff --git a/src/Parser/Comment.php b/src/Parser/Comment.php index dd7f660..ffa6128 100644 --- a/src/Parser/Comment.php +++ b/src/Parser/Comment.php @@ -11,7 +11,7 @@ use Egulias\EmailValidator\Result\Reason\UnclosedComment; use Egulias\EmailValidator\Result\Reason\UnOpenedComment; use Egulias\EmailValidator\Warning\Comment as WarningComment; -class Comment extends Parser +class Comment extends PartParser { /** * @var int diff --git a/src/Parser/DomainLiteral.php b/src/Parser/DomainLiteral.php index 7df3448..54a6fab 100644 --- a/src/Parser/DomainLiteral.php +++ b/src/Parser/DomainLiteral.php @@ -20,7 +20,7 @@ use Egulias\EmailValidator\Result\Reason\ExpectingDTEXT; use Egulias\EmailValidator\Result\Reason\UnusualElements; use Egulias\EmailValidator\Warning\DomainLiteral as WarningDomainLiteral; -class DomainLiteral extends Parser +class DomainLiteral extends PartParser { public function parse() : Result { diff --git a/src/Parser/DomainPart.php b/src/Parser/DomainPart.php index ba6deda..03f8488 100644 --- a/src/Parser/DomainPart.php +++ b/src/Parser/DomainPart.php @@ -22,7 +22,7 @@ use Egulias\EmailValidator\Parser\CommentStrategy\DomainComment; use Egulias\EmailValidator\Result\Reason\ExpectingDomainLiteralClose; use Egulias\EmailValidator\Parser\DomainLiteral as DomainLiteralParser; -class DomainPart extends Parser +class DomainPart extends PartParser { const DOMAIN_MAX_LENGTH = 253; const LABEL_MAX_LENGTH = 63; @@ -39,6 +39,9 @@ class DomainPart extends Parser public function parse() : Result { + $this->lexer->clearRecorded(); + $this->lexer->startRecording(); + $this->lexer->moveNext(); $domainChecks = $this->performDomainStartChecks(); @@ -49,20 +52,22 @@ class DomainPart extends Parser if ($this->lexer->token['type'] === EmailLexer::S_AT) { return new InvalidEmail(new ConsecutiveAt(), $this->lexer->token['value']); } - $domain = $this->doParseDomainPart(); - if ($domain->isInvalid()) { - return $domain; - } - $length = strlen($this->domainPart); + $result = $this->doParseDomainPart(); + if ($result->isInvalid()) { + return $result; + } $end = $this->checkEndOfDomain(); if ($end->isInvalid()) { return $end; } + $this->lexer->stopRecording(); + $this->domainPart = $this->lexer->getAccumulatedValues(); + + $length = strlen($this->domainPart); if ($length > self::DOMAIN_MAX_LENGTH) { - //$this->warnings[DomainTooLong::CODE] = new DomainTooLong(); return new InvalidEmail(new DomainTooLong(), $this->lexer->token['value']); } @@ -128,14 +133,6 @@ class DomainPart extends Parser return new ValidEmail(); } - /** - * @return string - */ - public function getDomainPart() - { - return $this->domainPart; - } - protected function parseComments(): Result { $commentParser = new Comment($this->lexer, new DomainComment()); @@ -229,7 +226,6 @@ class DomainPart extends Parser */ protected function parseDomainLiteral() : Result { - try { $this->lexer->find(EmailLexer::S_CLOSEBRACKET); } catch (\RuntimeException $e) { @@ -242,22 +238,8 @@ class DomainPart extends Parser return $result; } - /** - * @return InvalidEmail|ValidEmail - */ protected function checkDomainPartExceptions(array $prev, bool $hasComments) : Result { - $validDomainTokens = array( - EmailLexer::GENERIC => true, - EmailLexer::S_HYPHEN => true, - EmailLexer::S_DOT => true, - ); - - if ($hasComments) { - $validDomainTokens[EmailLexer::S_OPENPARENTHESIS] = true; - $validDomainTokens[EmailLexer::S_CLOSEPARENTHESIS] = true; - } - if ($this->lexer->token['type'] === EmailLexer::S_OPENBRACKET && $prev['type'] !== EmailLexer::S_AT) { return new InvalidEmail(new ExpectingATEXT('OPENBRACKET not after AT'), $this->lexer->token['value']); } @@ -271,6 +253,22 @@ class DomainPart extends Parser return new InvalidEmail(new ExpectingATEXT('Escaping following "ATOM"'), $this->lexer->token['value']); } + return $this->validateTokens($hasComments); + } + + protected function validateTokens(bool $hasComments) : Result + { + $validDomainTokens = array( + EmailLexer::GENERIC => true, + EmailLexer::S_HYPHEN => true, + EmailLexer::S_DOT => true, + ); + + if ($hasComments) { + $validDomainTokens[EmailLexer::S_OPENPARENTHESIS] = true; + $validDomainTokens[EmailLexer::S_CLOSEPARENTHESIS] = true; + } + if (!isset($validDomainTokens[$this->lexer->token['type']])) { return new InvalidEmail(new ExpectingATEXT('Invalid token in domain: ' . $this->lexer->token['value']), $this->lexer->token['value']); } @@ -278,7 +276,6 @@ class DomainPart extends Parser return new ValidEmail(); } - private function checkLabelLength(bool $isEndOfDomain = false) : Result { if ($this->lexer->token['type'] === EmailLexer::S_DOT || $isEndOfDomain) { @@ -307,4 +304,9 @@ class DomainPart extends Parser $this->warnings[TLD::CODE] = new TLD(); } } + + public function domainPart() : string + { + return $this->domainPart; + } } \ No newline at end of file diff --git a/src/Parser/DoubleQuote.php b/src/Parser/DoubleQuote.php index fa2d246..19c098e 100644 --- a/src/Parser/DoubleQuote.php +++ b/src/Parser/DoubleQuote.php @@ -11,7 +11,7 @@ use Egulias\EmailValidator\Result\Reason\ExpectingATEXT; use Egulias\EmailValidator\Result\Reason\UnclosedQuotedString; use Egulias\EmailValidator\Result\Result; -class DoubleQuote extends Parser +class DoubleQuote extends PartParser { public function parse() : Result { diff --git a/src/Parser/FoldingWhiteSpace.php b/src/Parser/FoldingWhiteSpace.php index 0d131fb..d32231e 100644 --- a/src/Parser/FoldingWhiteSpace.php +++ b/src/Parser/FoldingWhiteSpace.php @@ -13,7 +13,7 @@ use Egulias\EmailValidator\Result\Reason\ExpectingCTEXT; use Egulias\EmailValidator\Result\Result; use Egulias\EmailValidator\Result\ValidEmail; -class FoldingWhiteSpace extends Parser +class FoldingWhiteSpace extends PartParser { public function parse() : Result { @@ -23,7 +23,10 @@ class FoldingWhiteSpace extends Parser $previous = $this->lexer->getPrevious(); - $this->checkCRLFInFWS(); + $resultCRLF = $this->checkCRLFInFWS(); + if ($resultCRLF->isInvalid()) { + return $resultCRLF; + } if ($this->lexer->token['type'] === EmailLexer::S_CR) { return new InvalidEmail(new CRNoLF(), $this->lexer->token['value']); @@ -46,10 +49,7 @@ class FoldingWhiteSpace extends Parser return new ValidEmail(); } - /** - * @return InvalidEmail|ValidEmail|null - */ - protected function checkCRLFInFWS() + protected function checkCRLFInFWS() : Result { if ($this->lexer->token['type'] !== EmailLexer::CRLF) { return new ValidEmail(); @@ -63,12 +63,11 @@ class FoldingWhiteSpace extends Parser if (!$this->lexer->isNextTokenAny(array(EmailLexer::S_SP, EmailLexer::S_HTAB))) { return new InvalidEmail(new CRLFAtTheEnd(), $this->lexer->token['value']); } + + return new ValidEmail(); } - /** - * @return bool - */ - protected function isFWS() + protected function isFWS() : bool { if ($this->escaped()) { return false; diff --git a/src/Parser/IDLeftPart.php b/src/Parser/IDLeftPart.php new file mode 100644 index 0000000..abb4982 --- /dev/null +++ b/src/Parser/IDLeftPart.php @@ -0,0 +1,16 @@ +lexer->token['value']); + } +} \ No newline at end of file diff --git a/src/Parser/IDRightPart.php b/src/Parser/IDRightPart.php new file mode 100644 index 0000000..bcf80dd --- /dev/null +++ b/src/Parser/IDRightPart.php @@ -0,0 +1,29 @@ + 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']])) { + return new InvalidEmail(new ExpectingATEXT('Invalid token in domain: ' . $this->lexer->token['value']), $this->lexer->token['value']); + } + return new ValidEmail(); + } +} \ No newline at end of file diff --git a/src/Parser/LocalPart.php b/src/Parser/LocalPart.php index 9b0da9f..d505c61 100644 --- a/src/Parser/LocalPart.php +++ b/src/Parser/LocalPart.php @@ -13,27 +13,17 @@ use Egulias\EmailValidator\Result\Reason\ConsecutiveDot; use Egulias\EmailValidator\Result\Reason\ExpectingATEXT; use Egulias\EmailValidator\Parser\CommentStrategy\LocalComment; -class LocalPart extends Parser +class LocalPart extends PartParser { - /** - * Invalid lexer tokens for local part - * @var array + * @var string */ - private $invalidTokens = array( - EmailLexer::S_COMMA => EmailLexer::S_COMMA, - EmailLexer::S_CLOSEBRACKET => EmailLexer::S_CLOSEBRACKET, - EmailLexer::S_OPENBRACKET => EmailLexer::S_OPENBRACKET, - EmailLexer::S_GREATERTHAN => EmailLexer::S_GREATERTHAN, - EmailLexer::S_LOWERTHAN => EmailLexer::S_LOWERTHAN, - EmailLexer::S_COLON => EmailLexer::S_COLON, - EmailLexer::S_SEMICOLON => EmailLexer::S_SEMICOLON, - EmailLexer::INVALID => EmailLexer::INVALID - ); + private $localPart = ''; + public function parse() : Result { - $totalLength = 0; + $this->lexer->startRecording(); while ($this->lexer->token['type'] !== EmailLexer::S_AT && null !== $this->lexer->token['type']) { if ($this->hasDotAtStart()) { @@ -74,8 +64,9 @@ class LocalPart extends Parser return $resultEscaping; } - if (isset($this->invalidTokens[$this->lexer->token['type']])) { - return new InvalidEmail(new ExpectingATEXT('Invalid token found'), $this->lexer->token['value']); + $resultToken = $this->validateTokens(false); + if ($resultToken->isInvalid()) { + return $resultToken; } $resultFWS = $this->parseLocalFWS(); @@ -83,17 +74,41 @@ class LocalPart extends Parser return $resultFWS; } - $totalLength += strlen($this->lexer->token['value']); $this->lexer->moveNext(); } - if ($totalLength > LocalTooLong::LOCAL_PART_LENGTH) { + $this->lexer->stopRecording(); + $this->localPart = rtrim($this->lexer->getAccumulatedValues(), '@'); + if (strlen($this->localPart) > LocalTooLong::LOCAL_PART_LENGTH) { $this->warnings[LocalTooLong::CODE] = new LocalTooLong(); } return new ValidEmail(); } + protected function validateTokens(bool $hasComments) : Result + { + $invalidTokens = array( + EmailLexer::S_COMMA => EmailLexer::S_COMMA, + EmailLexer::S_CLOSEBRACKET => EmailLexer::S_CLOSEBRACKET, + EmailLexer::S_OPENBRACKET => EmailLexer::S_OPENBRACKET, + EmailLexer::S_GREATERTHAN => EmailLexer::S_GREATERTHAN, + EmailLexer::S_LOWERTHAN => EmailLexer::S_LOWERTHAN, + EmailLexer::S_COLON => EmailLexer::S_COLON, + EmailLexer::S_SEMICOLON => EmailLexer::S_SEMICOLON, + EmailLexer::INVALID => EmailLexer::INVALID + ); + if (isset($invalidTokens[$this->lexer->token['type']])) { + return new InvalidEmail(new ExpectingATEXT('Invalid token found'), $this->lexer->token['value']); + } + return new ValidEmail(); + } + + public function localPart() : string + { + return $this->localPart; + } + private function parseLocalFWS() : Result { $foldingWS = new FoldingWhiteSpace($this->lexer); @@ -118,7 +133,7 @@ class LocalPart extends Parser return $parseAgain; } - private function parseComments(): Result + protected function parseComments(): Result { $commentParser = new Comment($this->lexer, new LocalComment()); $result = $commentParser->parse(); diff --git a/src/Parser/Parser.php b/src/Parser/PartParser.php similarity index 96% rename from src/Parser/Parser.php rename to src/Parser/PartParser.php index 2ddd6cc..fd65fc5 100644 --- a/src/Parser/Parser.php +++ b/src/Parser/PartParser.php @@ -8,7 +8,7 @@ use Egulias\EmailValidator\Result\Reason\ConsecutiveDot; use Egulias\EmailValidator\Result\Result; use Egulias\EmailValidator\Result\ValidEmail; -abstract class Parser +abstract class PartParser { /** * @var array @@ -25,6 +25,8 @@ abstract class Parser $this->lexer = $lexer; } + abstract public function parse() : Result; + /** * @return \Egulias\EmailValidator\Warning\Warning[] */ @@ -33,8 +35,6 @@ abstract class Parser return $this->warnings; } - abstract public function parse() : Result; - protected function parseFWS() : Result { $foldingWS = new FoldingWhiteSpace($this->lexer); @@ -52,9 +52,6 @@ abstract class Parser return new ValidEmail(); } - /** - * @return bool - */ protected function escaped() : bool { $previous = $this->lexer->getPrevious(); diff --git a/src/Result/Reason/CommentsInIDRight.php b/src/Result/Reason/CommentsInIDRight.php new file mode 100644 index 0000000..43a7cf7 --- /dev/null +++ b/src/Result/Reason/CommentsInIDRight.php @@ -0,0 +1,16 @@ +parse($email); + $this->warnings = $parser->getWarnings(); + if ($result->isInvalid()) { + /** @psalm-suppress PropertyTypeCoercion */ + $this->error = $result; + return false; + } + } catch (\Exception $invalid) { + $this->error = new InvalidEmail(new ExceptionFound($invalid), ''); + return false; + } + + return true; + } + + public function getWarnings(): array + { + return $this->warnings; + } + + public function getError(): ?InvalidEmail + { + return $this->error; + } +} diff --git a/tests/EmailValidator/Validation/MessageIDValidationTest.php b/tests/EmailValidator/Validation/MessageIDValidationTest.php new file mode 100644 index 0000000..8aafd13 --- /dev/null +++ b/tests/EmailValidator/Validation/MessageIDValidationTest.php @@ -0,0 +1,70 @@ +assertTrue($validator->isValid($messageID, new EmailLexer())); + } + + public function validMessageIDs() : array + { + return [ + ['a@b.c+&%$.d'], + ['a.b+&%$.c@d'], + ['a@รค'], + ]; + } + + /** + * @dataProvider invalidMessageIDs + */ + public function testInvalidMessageIDs(string $messageID) + { + $validator = new MessageIDValidation(); + + $this->assertFalse($validator->isValid($messageID, new EmailLexer())); + } + + public function invalidMessageIDs() : array + { + return [ + ['example'], + ['example@with space'], + ['example@iana.'], + ['example@ia\na.'], + /** + * RFC 2822, section 3.6.4, Page 25 + * Since the msg-id has + * a similar syntax to angle-addr (identical except that comments and + * folding white space are not allowed), a good method is to put the + * domain name (or a domain literal IP address) of the host on which the + * message identifier was created on the right hand side of the "@", and + * put a combination of the current absolute date and time along with + * some other currently unique (perhaps sequential) identifier available + * on the system (for example, a process id number) on the left hand + * side. + */ + ['example(comment)@example.com'], + ["\r\nFWS@example.com"] + ]; + } + + public function testInvalidMessageIDsWithError() + { + $this->markTestIncomplete("missing error check"); + + } +}