Compare commits

...

72 Commits

Author SHA1 Message Date
Eduardo Gulias Davis af9417f765 #40 - require latest lexer 2015-01-04 23:42:32 +01:00
Eduardo Gulias Davis ab0bcfb424 Merge pull request #44 from egulias/kbond-semicolon-fail
From #43 - Kbond semicolon fail
2015-01-04 17:36:06 +01:00
Eduardo Gulias Davis 833b3e7924 #41 - Semicolon in domain 2015-01-04 17:28:58 +01:00
Eduardo Gulias Davis fec1609458 Update tests for DNSCheck 2015-01-04 17:24:36 +01:00
Eduardo Gulias Davis add559b99c Add PhpStorm logo
Thanks for supporting!
2015-01-04 17:03:09 +01:00
Kevin Bond 19e390d863 added failing test for email with semi-colon in domain 2014-12-18 11:00:21 -05:00
Eduardo Gulias Davis 5320c26b38 Merge pull request #39 from shakaran/patch-1
Rename Ohter.md to Other.md
2014-11-30 19:03:44 +01:00
Ángel Guzmán Maeso 3b6ab3bcd5 Rename Ohter.md to Other.md
Fix small filename typo.
2014-11-30 02:17:28 +01:00
Eduardo Gulias Davis 7a64ea18af Merge pull request #38 from egulias/improved-utf8
#30 - Improved utf8
2014-11-29 10:09:14 +01:00
Eduardo Gulias Davis 9103f4f99b #30 - Improved control for UTF8 chars 2014-11-29 10:05:19 +01:00
Eduardo Gulias Davis 3f623e9006 Merge pull request #37 from egulias/multiple_spaces
#36 - Multiple spaces in local part
2014-11-17 01:23:05 +01:00
Eduardo Gulias Davis 833eb65135 #30 - [WIP] - improve utf-8 control 2014-11-17 01:18:11 +01:00
Eduardo Gulias Davis e61481fefb #36 - Multiple spaces in local part 2014-11-16 19:15:26 +01:00
Eduardo Gulias Davis 518f80a0ff Merge pull request #35 from craue/fix-strict-check
make strict check work again
2014-11-06 09:59:44 +01:00
Christian Raue 2ec7babda6 fixed strict check 2014-11-05 01:09:55 +01:00
Christian Raue 05367768d6 improved tests for strict checking 2014-11-04 14:01:13 +01:00
Eduardo Gulias Davis 5c3a79217c Merge pull request #33 from egulias/32_strict_and_dns_bug
#32 - Fixed usage of DNS check and strict parameter
2014-11-03 00:13:57 +01:00
Eduardo Gulias Davis 749b423775 #32 - Fixed usage of DNS check and strict parameter 2014-11-03 00:05:58 +01:00
Eduardo Gulias Davis 49494c3189 Merge pull request #31 from egulias/18-discrep-isemail
#18 Differences with isemail
2014-10-26 23:13:52 +01:00
Eduardo Gulias Davis 8b62522238 Scrutinizer patch 2014-10-26 23:02:09 +01:00
Eduardo Gulias Davis 82fb2d6ce1 Remove under development tests emails 2014-10-26 22:55:42 +01:00
Eduardo Gulias Davis 2933b7bbd1 Reset state 2014-10-26 22:54:30 +01:00
Eduardo Gulias Davis f5c48f62d3 Remove commens 2014-10-26 22:54:21 +01:00
Eduardo Gulias Davis 43650e8566 Merge pull request #29 from rdohms/patch-1
Update README.md
2014-10-22 14:39:14 +02:00
Rafael Dohms 3d94cc6487 Update README.md
Updated composer installation instructions.
It now follows the recommendation of using the "require" command and letting Composer auto-define the latest version.
2014-10-22 14:29:38 +02:00
Eduardo Gulias Davis 65c8c3d7bb Merge pull request #28 from DotZecker/patch-1
Fix namespace
2014-10-21 23:52:23 +02:00
Rafael Antonio 57f8ae443b Fix namespace 2014-10-21 09:28:45 +02:00
Eduardo Gulias Davis 18df863858 #18 - [WIP] - Almost done with erros, dobule backslask left and utf8 chars 2014-10-14 00:48:32 +02:00
Eduardo Gulias Davis 953cbdbbcf Merge pull request #26 from stof/patch-1
Fixed the SensioLabsInsight project id
2014-09-17 14:47:23 +02:00
Christophe Coevoet 41cb75ec00 Fixed the SensioLabsInsight project id
Closes #23
2014-09-17 14:12:41 +02:00
Eduardo Gulias Davis 8927160d8d Merge pull request #25 from thewilkybarkid/constraint
Improve suggested constraint
2014-09-17 11:02:18 +02:00
thewilkybarkid 887605ad1b Fix typo 2014-09-17 09:06:33 +01:00
thewilkybarkid 43e1825cd8 Improve suggested constraint 2014-09-17 09:06:22 +01:00
Eduardo Gulias Davis 2f44d7b1e1 #23 - SensioLabsInsight errors 2014-09-16 23:20:11 +02:00
Eduardo Gulias Davis 355cb7e604 Merge pull request #22 from stof/patch-1
Fixed the branch alias for master
2014-09-16 23:00:05 +02:00
Eduardo Gulias Davis d6fcd2d7ca Merge pull request #24 from mhor/patch-1
Typo
2014-09-16 22:58:54 +02:00
Maxime Horcholle 9be8eb420a typo 2014-09-16 19:07:39 +02:00
Christophe Coevoet 20066b1c1e Fixed the branch alias for master 2014-09-16 17:45:41 +02:00
Eduardo Gulias Davis 39b451bb2b #19 and #10 - Improvement of QuotedPart parsing 2014-09-02 00:35:48 +02:00
Eduardo Gulias Davis 65afaf3f2b Change DNS validation to default to true instead of false by @brianfreytag 2014-08-22 01:08:16 +02:00
Eduardo Gulias Davis 82f8514d89 COMMA is not allowed in domain part 2014-08-22 01:05:58 +02:00
Eduardo Gulias Davis 440bc61237 Merge pull request #17 from brianfreytag/remove_dns_requirement
Change DNS validation to default to true instead of false
2014-08-21 23:47:24 +02:00
Brian Freytag a96f0822e0 Change DNS validation to default to true instead of false
There shouldn't be a dependency on the checkDNS() method in order to
validate strict = true. We should only be concerned about the DNS if the
library is specifically told to check DNS, so we default the DNS
variable to true and then set it to false if checkDNS is set to true and
checkDNS() returns false.
2014-08-20 15:05:30 -04:00
Eduardo Gulias Davis bb530a1930 Removed class comment 2014-07-06 19:21:29 +02:00
Eduardo Gulias Davis e0691bc67d Removed not needed test 2014-07-06 19:03:12 +02:00
Eduardo Gulias Davis 07a21c63d5 #2 - Scrutinizer badge 2014-07-03 13:10:41 +02:00
Eduardo Gulias Davis 46383ef57a [WIP] - ordering classes 2014-07-03 12:19:54 +02:00
Eduardo Gulias Davis 2312070f3a [WIP] - parse comments improvement 2014-07-03 01:00:20 +02:00
Eduardo Gulias Davis f7402dd8dc [WIP] - Comments bug and comments improvemente 2014-07-03 00:55:00 +02:00
Eduardo Gulias Davis 679128b980 Uncommented test 2014-07-02 18:53:46 +02:00
Eduardo Gulias Davis 6306334f79 [WIP] - test passing for the new structure 2014-07-02 18:17:56 +02:00
Eduardo Gulias Davis 8381eb3399 [WIP] - Chopping things down to make them more readable 2014-06-17 12:03:30 +02:00
Eduardo Gulias Davis 045ebd2f8e Refactor of domain exceptions 2014-05-08 18:26:53 +02:00
Eduardo Gulias Davis 86a022cd66 Refactor of domain part exceptions 2014-05-08 18:13:17 +02:00
Eduardo Gulias Davis 1866e51905 Reducing complexity for domainLiteral 2014-05-08 18:02:14 +02:00
Eduardo Gulias Davis a80300a3ba Reducing complexity for domainPart 2014-05-08 17:53:51 +02:00
Eduardo Gulias Davis 978b27b10a Reducing complexity 2014-05-08 17:47:44 +02:00
Eduardo Gulias Davis f679da922a Refactor to methods for IPv4 and 6 tags 2014-05-08 17:33:52 +02:00
Eduardo Gulias Davis 4b62335424 Refactoring domainPart 2014-05-08 16:23:54 +02:00
Eduardo Gulias Davis 46050f9bc8 Extracted method for CRLF in FWS 2014-05-08 13:30:27 +02:00
Eduardo Gulias Davis c3f2d33935 Extracted method 2014-05-08 13:16:08 +02:00
Eduardo Gulias Davis 73b80f1383 Small if refactor 2014-05-08 12:51:25 +02:00
Eduardo Gulias Davis 77ea3babe3 Improvements derived from scrutinizer-ci 2014-05-08 12:23:10 +02:00
Eduardo Gulias Davis 3d12808268 Improving code quality 2014-05-04 18:53:31 +02:00
Eduardo Gulias Davis c4354acc0e Adding new PHP versions on travis, cleaning old comments to a new document 2014-05-04 18:31:29 +02:00
Eduardo Gulias Davis 0964bfd101 Coverage badge 2014-04-25 11:54:11 +02:00
Eduardo Gulias Davis 8207889eaf Deps update 2014-04-25 11:44:20 +02:00
Eduardo Gulias Davis 0d75ea740d Travis small fix 2014-04-25 11:35:57 +02:00
Eduardo Gulias Davis 7bc2ec3ce0 #1 - Coveralls integration 2014-03-24 13:25:47 +01:00
Eduardo Gulias Davis f01e700ffe Using coverall.io with travis-ci 2014-03-07 12:15:00 +01:00
Eduardo Gulias Davis d3bf0dbe4b Using coverall.io 2014-03-07 12:14:03 +01:00
Eduardo Gulias Davis 35038cd507 Update LICENSE 2014-02-13 11:51:06 +01:00
16 changed files with 1538 additions and 614 deletions
+3
View File
@@ -0,0 +1,3 @@
service_name: travis-ci
src_dir: .
coverage_clover: build/logs/clover*.xml
+14 -3
View File
@@ -2,8 +2,19 @@ language: php
php:
- 5.3
- 5.4
- 5.5
- 5.6
- hhvm
before_script:
- wget http://getcomposer.org/composer.phar
- php composer.phar install
script: phpunit
- wget http://getcomposer.org/composer.phar
- php composer.phar install --dev --no-interaction
script:
- mkdir -p build/logs
- phpunit --coverage-clover build/logs/clover.xml
after_script:
- php vendor/bin/coveralls
+1 -1
View File
@@ -1,4 +1,4 @@
Copyright (c) 2013-2013 Eduardo Gulias Davis
Copyright (c) 2013 Eduardo Gulias Davis
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
+13 -5
View File
@@ -1,8 +1,16 @@
EmailValidator [![Build Status](https://travis-ci.org/egulias/EmailValidator.png?branch=master)](https://travis-ci.org/egulias/EmailValidator) [![SensioLabsInsight](https://insight.sensiolabs.com/projects/b18d473e-bd5a-4d88-a7b2-7aeaee0ebd7b/small.png)](https://insight.sensiolabs.com/projects/b18d473e-bd5a-4d88-a7b2-7aeaee0ebd7b)
#EmailValidator#
[![Build Status](https://travis-ci.org/egulias/EmailValidator.png?branch=master)](https://travis-ci.org/egulias/EmailValidator) [![Coverage Status](https://coveralls.io/repos/egulias/EmailValidator/badge.png?branch=master)](https://coveralls.io/r/egulias/EmailValidator?branch=master) [![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/egulias/EmailValidator/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/egulias/EmailValidator/?branch=master) [![SensioLabsInsight](https://insight.sensiolabs.com/projects/22ba6692-9c02-42e5-a65d-1c5696bfffc6/small.png)](https://insight.sensiolabs.com/projects/22ba6692-9c02-42e5-a65d-1c5696bfffc6)
=============================
With the help of
![Powered by PhpStorm](https://www.jetbrains.com/phpstorm/documentation/docs/logo_phpstorm.png)
##Installation##
Install via composer. Add to your current compooser.json ```require``` key: ```"egulias/email-validator":"1.0.x-dev" ```
Run the command below to install via Composer
```shell
composer require egulias/email-validator
```
##Usage##
@@ -24,7 +32,7 @@ More advanced example (returns detailed diagnostic error codes):
```php
<?php
use egulias\EmailValidator\EmailValidator;
use Egulias\EmailValidator\EmailValidator;
$validator = new EmailValidator;
$email = 'dominic@sayers.cc';
@@ -46,6 +54,6 @@ As this is a port from another library and work, here are other people related t
* Josepf Bielawski [@stloyd](http://github.com/stloyd): For its first re-work of Dominic's lib
* Dominic Sayers [@dominicsayers](http://github.com/dominicsayers): The original isemail function
##Licence##
Released under the MIT Licence attached with this code.
##License##
Released under the MIT License attached with this code.
+4 -1
View File
@@ -10,13 +10,16 @@
],
"extra": {
"branch-alias": {
"dev-master": "1.0.x-dev"
"dev-master": "1.3.x-dev"
}
},
"require": {
"php": ">= 5.3.3",
"doctrine/lexer": "~1.0"
},
"require-dev" : {
"satooshi/php-coveralls": "dev-master"
},
"autoload": {
"psr-0": {
"Egulias\\": "src/"
Generated
+461 -68
View File
@@ -1,28 +1,34 @@
{
"_readme": [
"This file locks the dependencies of your project to a known state",
"Read more about it at http://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file"
"Read more about it at http://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
"This file is @generated automatically"
],
"hash": "ec97acf9cb298074065a27ad27d42b64",
"hash": "9e9dff0cc08c7292600453e681201e13",
"packages": [
{
"name": "doctrine/lexer",
"version": "v1.0",
"version": "v1.0.1",
"source": {
"type": "git",
"url": "https://github.com/doctrine/lexer.git",
"reference": "2f708a85bb3aab5d99dab8be435abd73e0b18acb"
"reference": "83893c552fd2045dd78aef794c31e694c37c0b8c"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/doctrine/lexer/zipball/2f708a85bb3aab5d99dab8be435abd73e0b18acb",
"reference": "2f708a85bb3aab5d99dab8be435abd73e0b18acb",
"url": "https://api.github.com/repos/doctrine/lexer/zipball/83893c552fd2045dd78aef794c31e694c37c0b8c",
"reference": "83893c552fd2045dd78aef794c31e694c37c0b8c",
"shasum": ""
},
"require": {
"php": ">=5.3.2"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.0.x-dev"
}
},
"autoload": {
"psr-0": {
"Doctrine\\Common\\Lexer\\": "lib/"
@@ -33,20 +39,17 @@
"MIT"
],
"authors": [
{
"name": "Guilherme Blanco",
"email": "guilhermeblanco@gmail.com",
"homepage": "http://www.instaclick.com"
},
{
"name": "Roman Borschel",
"email": "roman@code-factory.org"
},
{
"name": "Guilherme Blanco",
"email": "guilhermeblanco@gmail.com"
},
{
"name": "Johannes Schmitt",
"email": "schmittjoh@gmail.com",
"homepage": "http://jmsyst.com",
"role": "Developer of wrapped JMSSerializerBundle"
"email": "schmittjoh@gmail.com"
}
],
"description": "Base library for a lexer that can be used in Top-Down, Recursive Descent Parsers.",
@@ -55,43 +58,300 @@
"lexer",
"parser"
],
"time": "2013-01-12 18:59:04"
},
"time": "2014-09-09 13:34:57"
}
],
"packages-dev": [
{
"name": "symfony/translation",
"version": "v2.3.4",
"target-dir": "Symfony/Component/Translation",
"name": "guzzle/guzzle",
"version": "v3.9.2",
"source": {
"type": "git",
"url": "https://github.com/symfony/Translation.git",
"reference": "65f888291f0896ad492f9abc6dc05c998373aded"
"url": "https://github.com/guzzle/guzzle3.git",
"reference": "54991459675c1a2924122afbb0e5609ade581155"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/Translation/zipball/65f888291f0896ad492f9abc6dc05c998373aded",
"reference": "65f888291f0896ad492f9abc6dc05c998373aded",
"url": "https://api.github.com/repos/guzzle/guzzle3/zipball/54991459675c1a2924122afbb0e5609ade581155",
"reference": "54991459675c1a2924122afbb0e5609ade581155",
"shasum": ""
},
"require": {
"ext-curl": "*",
"php": ">=5.3.3",
"symfony/event-dispatcher": "~2.1"
},
"replace": {
"guzzle/batch": "self.version",
"guzzle/cache": "self.version",
"guzzle/common": "self.version",
"guzzle/http": "self.version",
"guzzle/inflection": "self.version",
"guzzle/iterator": "self.version",
"guzzle/log": "self.version",
"guzzle/parser": "self.version",
"guzzle/plugin": "self.version",
"guzzle/plugin-async": "self.version",
"guzzle/plugin-backoff": "self.version",
"guzzle/plugin-cache": "self.version",
"guzzle/plugin-cookie": "self.version",
"guzzle/plugin-curlauth": "self.version",
"guzzle/plugin-error-response": "self.version",
"guzzle/plugin-history": "self.version",
"guzzle/plugin-log": "self.version",
"guzzle/plugin-md5": "self.version",
"guzzle/plugin-mock": "self.version",
"guzzle/plugin-oauth": "self.version",
"guzzle/service": "self.version",
"guzzle/stream": "self.version"
},
"require-dev": {
"doctrine/cache": "~1.3",
"monolog/monolog": "~1.0",
"phpunit/phpunit": "3.7.*",
"psr/log": "~1.0",
"symfony/class-loader": "~2.1",
"zendframework/zend-cache": "2.*,<2.3",
"zendframework/zend-log": "2.*,<2.3"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "3.9-dev"
}
},
"autoload": {
"psr-0": {
"Guzzle": "src/",
"Guzzle\\Tests": "tests/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Michael Dowling",
"email": "mtdowling@gmail.com",
"homepage": "https://github.com/mtdowling"
},
{
"name": "Guzzle Community",
"homepage": "https://github.com/guzzle/guzzle/contributors"
}
],
"description": "Guzzle is a PHP HTTP client library and framework for building RESTful web service clients",
"homepage": "http://guzzlephp.org/",
"keywords": [
"client",
"curl",
"framework",
"http",
"http client",
"rest",
"web service"
],
"time": "2014-08-11 04:32:36"
},
{
"name": "psr/log",
"version": "1.0.0",
"source": {
"type": "git",
"url": "https://github.com/php-fig/log.git",
"reference": "fe0936ee26643249e916849d48e3a51d5f5e278b"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/php-fig/log/zipball/fe0936ee26643249e916849d48e3a51d5f5e278b",
"reference": "fe0936ee26643249e916849d48e3a51d5f5e278b",
"shasum": ""
},
"type": "library",
"autoload": {
"psr-0": {
"Psr\\Log\\": ""
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "PHP-FIG",
"homepage": "http://www.php-fig.org/"
}
],
"description": "Common interface for logging libraries",
"keywords": [
"log",
"psr",
"psr-3"
],
"time": "2012-12-21 11:40:51"
},
{
"name": "satooshi/php-coveralls",
"version": "dev-master",
"source": {
"type": "git",
"url": "https://github.com/satooshi/php-coveralls.git",
"reference": "2fbf803803d179ab1082807308a67bbd5a760c70"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/satooshi/php-coveralls/zipball/2fbf803803d179ab1082807308a67bbd5a760c70",
"reference": "2fbf803803d179ab1082807308a67bbd5a760c70",
"shasum": ""
},
"require": {
"ext-json": "*",
"ext-simplexml": "*",
"guzzle/guzzle": ">=2.7",
"php": ">=5.3",
"psr/log": "1.0.0",
"symfony/config": ">=2.0",
"symfony/console": ">=2.0",
"symfony/stopwatch": ">=2.2",
"symfony/yaml": ">=2.0"
},
"require-dev": {
"apigen/apigen": "2.8.*@stable",
"pdepend/pdepend": "dev-master as 2.0.0",
"phpmd/phpmd": "dev-master",
"phpunit/php-invoker": ">=1.1.0,<1.2.0",
"phpunit/phpunit": "3.7.*@stable",
"sebastian/finder-facade": "dev-master",
"sebastian/phpcpd": "1.4.*@stable",
"squizlabs/php_codesniffer": "1.4.*@stable",
"theseer/fdomdocument": "dev-master"
},
"suggest": {
"symfony/http-kernel": "Allows Symfony integration"
},
"bin": [
"composer/bin/coveralls"
],
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "0.7-dev"
}
},
"autoload": {
"psr-0": {
"Satooshi\\Component": "src/",
"Satooshi\\Bundle": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Kitamura Satoshi",
"email": "with.no.parachute@gmail.com",
"homepage": "https://www.facebook.com/satooshi.jp"
}
],
"description": "PHP client library for Coveralls API",
"homepage": "https://github.com/satooshi/php-coveralls",
"keywords": [
"ci",
"coverage",
"github",
"test"
],
"time": "2014-11-11 15:35:34"
},
{
"name": "symfony/config",
"version": "v2.6.1",
"target-dir": "Symfony/Component/Config",
"source": {
"type": "git",
"url": "https://github.com/symfony/Config.git",
"reference": "84c0c150c1520995f09ea9e47e817068b353cb0f"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/Config/zipball/84c0c150c1520995f09ea9e47e817068b353cb0f",
"reference": "84c0c150c1520995f09ea9e47e817068b353cb0f",
"shasum": ""
},
"require": {
"php": ">=5.3.3",
"symfony/filesystem": "~2.3"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "2.6-dev"
}
},
"autoload": {
"psr-0": {
"Symfony\\Component\\Config\\": ""
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Symfony Community",
"homepage": "http://symfony.com/contributors"
},
{
"name": "Fabien Potencier",
"email": "fabien@symfony.com"
}
],
"description": "Symfony Config Component",
"homepage": "http://symfony.com",
"time": "2014-12-02 20:19:20"
},
{
"name": "symfony/console",
"version": "v2.6.1",
"target-dir": "Symfony/Component/Console",
"source": {
"type": "git",
"url": "https://github.com/symfony/Console.git",
"reference": "ef825fd9f809d275926547c9e57cbf14968793e8"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/Console/zipball/ef825fd9f809d275926547c9e57cbf14968793e8",
"reference": "ef825fd9f809d275926547c9e57cbf14968793e8",
"shasum": ""
},
"require": {
"php": ">=5.3.3"
},
"require-dev": {
"symfony/config": "~2.0",
"symfony/yaml": "~2.2"
"psr/log": "~1.0",
"symfony/event-dispatcher": "~2.1",
"symfony/process": "~2.1"
},
"suggest": {
"symfony/config": "",
"symfony/yaml": ""
"psr/log": "For using the console logger",
"symfony/event-dispatcher": "",
"symfony/process": ""
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "2.3-dev"
"dev-master": "2.6-dev"
}
},
"autoload": {
"psr-0": {
"Symfony\\Component\\Translation\\": ""
"Symfony\\Component\\Console\\": ""
}
},
"notification-url": "https://packagist.org/downloads/",
@@ -99,60 +359,57 @@
"MIT"
],
"authors": [
{
"name": "Fabien Potencier",
"email": "fabien@symfony.com"
},
{
"name": "Symfony Community",
"homepage": "http://symfony.com/contributors"
},
{
"name": "Fabien Potencier",
"email": "fabien@symfony.com"
}
],
"description": "Symfony Translation Component",
"description": "Symfony Console Component",
"homepage": "http://symfony.com",
"time": "2013-08-26 05:49:51"
"time": "2014-12-02 20:19:20"
},
{
"name": "symfony/validator",
"version": "dev-master",
"target-dir": "Symfony/Component/Validator",
"name": "symfony/event-dispatcher",
"version": "v2.6.1",
"target-dir": "Symfony/Component/EventDispatcher",
"source": {
"type": "git",
"url": "https://github.com/symfony/Validator.git",
"reference": "d98ac2aac2cccb7032b08cd79eeb579ed35c9a66"
"url": "https://github.com/symfony/EventDispatcher.git",
"reference": "720fe9bca893df7ad1b4546649473b5afddf0216"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/Validator/zipball/d98ac2aac2cccb7032b08cd79eeb579ed35c9a66",
"reference": "d98ac2aac2cccb7032b08cd79eeb579ed35c9a66",
"url": "https://api.github.com/repos/symfony/EventDispatcher/zipball/720fe9bca893df7ad1b4546649473b5afddf0216",
"reference": "720fe9bca893df7ad1b4546649473b5afddf0216",
"shasum": ""
},
"require": {
"php": ">=5.3.3",
"symfony/translation": "~2.0"
"php": ">=5.3.3"
},
"require-dev": {
"symfony/config": "~2.2",
"symfony/http-foundation": "~2.1",
"symfony/intl": "~2.3",
"symfony/yaml": "~2.0"
"psr/log": "~1.0",
"symfony/config": "~2.0",
"symfony/dependency-injection": "~2.6",
"symfony/expression-language": "~2.6",
"symfony/stopwatch": "~2.2"
},
"suggest": {
"doctrine/common": "",
"symfony/config": "",
"symfony/http-foundation": "",
"symfony/intl": "",
"symfony/yaml": ""
"symfony/dependency-injection": "",
"symfony/http-kernel": ""
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "2.4-dev"
"dev-master": "2.6-dev"
}
},
"autoload": {
"psr-0": {
"Symfony\\Component\\Validator\\": ""
"Symfony\\Component\\EventDispatcher\\": ""
}
},
"notification-url": "https://packagist.org/downloads/",
@@ -160,34 +417,170 @@
"MIT"
],
"authors": [
{
"name": "Symfony Community",
"homepage": "http://symfony.com/contributors"
},
{
"name": "Fabien Potencier",
"email": "fabien@symfony.com"
},
}
],
"description": "Symfony EventDispatcher Component",
"homepage": "http://symfony.com",
"time": "2014-12-02 20:19:20"
},
{
"name": "symfony/filesystem",
"version": "v2.6.1",
"target-dir": "Symfony/Component/Filesystem",
"source": {
"type": "git",
"url": "https://github.com/symfony/Filesystem.git",
"reference": "ff6efc95256cb33031933729e68b01d720b5436b"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/Filesystem/zipball/ff6efc95256cb33031933729e68b01d720b5436b",
"reference": "ff6efc95256cb33031933729e68b01d720b5436b",
"shasum": ""
},
"require": {
"php": ">=5.3.3"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "2.6-dev"
}
},
"autoload": {
"psr-0": {
"Symfony\\Component\\Filesystem\\": ""
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Symfony Community",
"homepage": "http://symfony.com/contributors"
},
{
"name": "Fabien Potencier",
"email": "fabien@symfony.com"
}
],
"description": "Symfony Validator Component",
"description": "Symfony Filesystem Component",
"homepage": "http://symfony.com",
"time": "2013-08-19 20:44:22"
"time": "2014-12-02 20:19:20"
},
{
"name": "symfony/stopwatch",
"version": "v2.6.1",
"target-dir": "Symfony/Component/Stopwatch",
"source": {
"type": "git",
"url": "https://github.com/symfony/Stopwatch.git",
"reference": "261abd360cfb6ac65ea93ffd82073e2011d034fc"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/Stopwatch/zipball/261abd360cfb6ac65ea93ffd82073e2011d034fc",
"reference": "261abd360cfb6ac65ea93ffd82073e2011d034fc",
"shasum": ""
},
"require": {
"php": ">=5.3.3"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "2.6-dev"
}
},
"autoload": {
"psr-0": {
"Symfony\\Component\\Stopwatch\\": ""
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Symfony Community",
"homepage": "http://symfony.com/contributors"
},
{
"name": "Fabien Potencier",
"email": "fabien@symfony.com"
}
],
"description": "Symfony Stopwatch Component",
"homepage": "http://symfony.com",
"time": "2014-12-02 20:19:20"
},
{
"name": "symfony/yaml",
"version": "v2.6.1",
"target-dir": "Symfony/Component/Yaml",
"source": {
"type": "git",
"url": "https://github.com/symfony/Yaml.git",
"reference": "3346fc090a3eb6b53d408db2903b241af51dcb20"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/Yaml/zipball/3346fc090a3eb6b53d408db2903b241af51dcb20",
"reference": "3346fc090a3eb6b53d408db2903b241af51dcb20",
"shasum": ""
},
"require": {
"php": ">=5.3.3"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "2.6-dev"
}
},
"autoload": {
"psr-0": {
"Symfony\\Component\\Yaml\\": ""
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Symfony Community",
"homepage": "http://symfony.com/contributors"
},
{
"name": "Fabien Potencier",
"email": "fabien@symfony.com"
}
],
"description": "Symfony Yaml Component",
"homepage": "http://symfony.com",
"time": "2014-12-02 20:19:20"
}
],
"packages-dev": [
],
"aliases": [
],
"aliases": [],
"minimum-stability": "stable",
"stability-flags": {
"symfony/validator": 20
"satooshi/php-coveralls": 20
},
"prefer-stable": false,
"prefer-lowest": false,
"platform": {
"php": ">= 5.3.3"
},
"platform-dev": [
]
"platform-dev": []
}
+69
View File
@@ -0,0 +1,69 @@
Email length
------------
http://tools.ietf.org/html/rfc5321#section-4.1.2
Forward-path = Path
Path = "<" [ A-d-l ":" ] Mailbox ">"
http://tools.ietf.org/html/rfc5321#section-4.5.3.1.3
http://tools.ietf.org/html/rfc1035#section-2.3.4
DNS
---
http://tools.ietf.org/html/rfc5321#section-2.3.5
Names that can
be resolved to MX RRs or address (i.e., A or AAAA) RRs (as discussed
in Section 5) are permitted, as are CNAME RRs whose targets can be
resolved, in turn, to MX or address RRs.
http://tools.ietf.org/html/rfc5321#section-5.1
The lookup first attempts to locate an MX record associated with the
name. If a CNAME record is found, the resulting name is processed as
if it were the initial name. ... If an empty list of MXs is returned,
the address is treated as if it was associated with an implicit MX
RR, with a preference of 0, pointing to that host.
is_email() author's note: We will regard the existence of a CNAME to be
sufficient evidence of the domain's existence. For performance reasons
we will not repeat the DNS lookup for the CNAME's target, but we will
raise a warning because we didn't immediately find an MX record.
Check for TLD addresses
-----------------------
TLD addresses are specifically allowed in RFC 5321 but they are
unusual to say the least. We will allocate a separate
status to these addresses on the basis that they are more likely
to be typos than genuine addresses (unless we've already
established that the domain does have an MX record)
http://tools.ietf.org/html/rfc5321#section-2.3.5
In the case
of a top-level domain used by itself in an email address, a single
string is used without any dots. This makes the requirement,
described in more detail below, that only fully-qualified domain
names appear in SMTP transactions on the public Internet,
particularly important where top-level domains are involved.
TLD format
----------
The format of TLDs has changed a number of times. The standards
used by IANA have been largely ignored by ICANN, leading to
confusion over the standards being followed. These are not defined
anywhere, except as a general component of a DNS host name (a label).
However, this could potentially lead to 123.123.123.123 being a
valid DNS name (rather than an IP address) and thereby creating
an ambiguity. The most authoritative statement on TLD formats that
the author can find is in a (rejected!) erratum to RFC 1123
submitted by John Klensin, the author of RFC 5321:
http://www.rfc-editor.org/errata_search.php?rfc=1123&eid=1353
However, a valid host name can never have the dotted-decimal
form #.#.#.#, since this change does not permit the highest-level
component label to start with a digit even if it is not all-numeric.
Comments
--------
Comments at the start of the domain are deprecated in the text
Comments at the start of a subdomain are obs-domain
(http://tools.ietf.org/html/rfc5322#section-3.4.1)
+97 -17
View File
@@ -31,10 +31,13 @@ class EmailLexer extends AbstractLexer
const S_SEMICOLON = 275;
const S_OPENQBRACKET = 276;
const S_CLOSEQBRACKET = 277;
const S_SLASH = 278;
const S_EMPTY = null;
const GENERIC = 300;
const CRLF = 301;
const INVALID = 302;
const ASCII_INVALID_FROM = 127;
const ASCII_INVALID_TO = 199;
/**
* US-ASCII visible characters not valid for atext (@link http://tools.ietf.org/html/rfc5322#section-3.2.3)
@@ -52,6 +55,7 @@ class EmailLexer extends AbstractLexer
';' => self::S_SEMICOLON,
'@' => self::S_AT,
'\\' => self::S_BACKSLASH,
'/' => self::S_SLASH,
',' => self::S_COMMA,
'.' => self::S_DOT,
'"' => self::S_DQUOTE,
@@ -67,17 +71,31 @@ class EmailLexer extends AbstractLexer
'>' => self::S_GREATERTHAN,
'{' => self::S_OPENQBRACKET,
'}' => self::S_CLOSEQBRACKET,
'' => self::S_EMPTY
'' => self::S_EMPTY,
'\0' => self::C_NUL,
);
protected $invalidASCII = array(226 => 1,);
protected $hasInvalidTokens = false;
protected $previous;
public function reset()
{
$this->hasInvalidTokens = false;
parent::reset();
}
public function hasInvalidTokens()
{
return $this->hasInvalidTokens;
}
/**
* find
*
* @param mixed $type
*
* @throws UnexpectedValueException
* @param $type
* @throws \UnexpectedValueException
* @return boolean
*/
public function find($type)
{
@@ -87,7 +105,7 @@ class EmailLexer extends AbstractLexer
if (!$search->lookahead) {
throw new \UnexpectedValueException($type . ' not found');
}
return true;
}
/**
@@ -103,7 +121,7 @@ class EmailLexer extends AbstractLexer
/**
* moveNext
*
* @return mixed
* @return boolean
*/
public function moveNext()
{
@@ -115,29 +133,28 @@ class EmailLexer extends AbstractLexer
/**
* Lexical catchable patterns.
*
* @return array
* @return string[]
*/
protected function getCatchablePatterns()
{
return array(
'[a-zA-Z_]+[4,6]?',
'[a-zA-Z_]+[46]?',
'[0-9]+',
'\r\n',
'::',
'\s+',
'[\x1-\x1F]+',
'.'
'\s+?',
'.',
);
}
/**
* Lexical non-catchable patterns.
*
* @return array
* @return string[]
*/
protected function getNonCatchablePatterns()
{
return array('[\x7f-\xff]+');
return array('[\xA0-\xff]+');
}
/**
@@ -149,14 +166,77 @@ class EmailLexer extends AbstractLexer
*/
protected function getType(&$value)
{
if (isset($this->charValue[$value])) {
if ($this->isNullType($value)) {
return self::C_NUL;
}
if ($this->isValid($value)) {
return $this->charValue[$value];
}
if (preg_match('/[\x1-\x1F]+/', $value)) {
if ($this->isUTF8Invalid($value)) {
$this->hasInvalidTokens = true;
return self::INVALID;
}
if ($this->isASCIIInvalid($value)) {
$this->hasInvalidTokens = true;
return self::INVALID;
}
return self::GENERIC;
}
protected function isValid($value)
{
if (isset($this->charValue[$value])) {
return true;
}
return false;
}
/**
* @param $value
* @return bool
*/
protected function isNullType($value)
{
if ($value === "\0") {
return true;
}
return false;
}
/**
* @param $value
* @return bool
*/
protected function isASCIIInvalid($value)
{
if (isset($this->invalidASCII[ord($value)])) {
return true;
}
return false;
}
/**
* @param $value
* @return bool
*/
protected function isUTF8Invalid($value)
{
if (preg_match('/\p{Cc}+/u', $value)) {
return true;
}
return false;
}
protected function getModifiers()
{
return 'iu';
}
}
+44 -405
View File
@@ -2,6 +2,9 @@
namespace Egulias\EmailValidator;
use Egulias\EmailValidator\Parser\DomainPart;
use Egulias\EmailValidator\Parser\LocalPart;
/**
* EmailParser
*
@@ -9,55 +12,55 @@ namespace Egulias\EmailValidator;
*/
class EmailParser
{
const EMAIL_MAX_LENGTH = 254;
protected $warnings = array();
protected $domainPart = '';
protected $localPart = '';
protected $lexer;
protected $localPartParser;
protected $domainPartParser;
public function __construct(EmailLexer $lexer)
{
$this->lexer = $lexer;
$this->localPartParser = new LocalPart($this->lexer);
$this->domainPartParser = new DomainPart($this->lexer);
}
/**
* parse
*
* @param string $str
*
* @param $str
* @return array
*/
public function parse($str)
{
$this->lexer->setInput($str);
$this->lexer->moveNext();
$this->lexer->moveNext();
if ($this->lexer->token['type'] === EmailLexer::S_AT) {
if (!$this->hasAtToken()) {
throw new \InvalidArgumentException('ERR_NOLOCALPART');
}
$this->parseLocalPart();
$this->parseDomainPart();
$parts = explode('@', $str);
if (strlen($parts[0] . '@' . $this->domainPart) > 254) {
// http://tools.ietf.org/html/rfc5321#section-4.1.2
// Forward-path = Path
//
// Path = "<" [ A-d-l ":" ] Mailbox ">"
//
// http://tools.ietf.org/html/rfc5321#section-4.5.3.1.3
// http://tools.ietf.org/html/rfc1035#section-2.3.4
$this->warnings[] = EmailValidator::RFC5322_TOOLONG;
$this->localPartParser->parse($str);
$this->domainPartParser->parse($str);
$this->setParts($str);
if ($this->lexer->hasInvalidTokens()) {
throw new \InvalidArgumentException('ERR_INVALID_ATEXT');
}
return array('local' => $parts[0], 'domain' => $this->domainPart);
return array('local' => $this->localPart, 'domain' => $this->domainPart);
}
/**
* getWarnings
*
* @return array
*/
public function getWarnings()
{
$localPartWarnings = $this->localPartParser->getWarnings();
$domainPartWarnings = $this->domainPartParser->getWarnings();
$this->warnings = array_merge($localPartWarnings, $domainPartWarnings);
$this->addLongEmailWarning($this->localPart, $this->domainPart);
return $this->warnings;
}
@@ -66,396 +69,32 @@ class EmailParser
return $this->domainPart;
}
/**
* parseDomainPart
*
*/
private function parseDomainPart()
protected function setParts($email)
{
$domain = '';
$parts = explode('@', $email);
$this->domainPart = $this->domainPartParser->getDomainPart();
$this->localPart = $parts[0];
}
protected function hasAtToken()
{
$this->lexer->moveNext();
$this->lexer->moveNext();
if ($this->lexer->token['type'] === EmailLexer::S_AT) {
throw new \InvalidArgumentException('ERR_CONSECUTIVEATS');
return false;
}
if ($this->lexer->token['type'] === EmailLexer::S_DOT) {
throw new \InvalidArgumentException('ERR_DOT_START');
}
if ($this->lexer->token['type'] === EmailLexer::S_EMPTY) {
throw new \InvalidArgumentException('ERR_NODOMAIN');
}
// Comments at the start of the domain are deprecated in the text
// Comments at the start of a subdomain are obs-domain
// (http://tools.ietf.org/html/rfc5322#section-3.4.1)
if ($this->lexer->token['type'] === EmailLexer::S_OPENPARENTHESIS) {
$this->warnings[] = EmailValidator::DEPREC_COMMENT;
$this->parseComments();
}
do {
$prev = $this->lexer->getPrevious();
if ($this->lexer->token['type'] === EmailLexer::S_AT) {
throw new \InvalidArgumentException('ERR_CONSECUTIVEATS');
}
if ($this->lexer->token['type'] === EmailLexer::S_OPENQBRACKET && $prev['type'] !== EmailLexer::S_AT) {
throw new \InvalidArgumentException('ERR_EXPECTING_ATEXT');
}
if ($this->lexer->token['type'] === EmailLexer::S_OPENPARENTHESIS) {
$this->parseComments();
$this->lexer->moveNext();
}
if ($this->lexer->token['type'] === EmailLexer::S_DOT && $this->lexer->isNextToken(EmailLexer::S_DOT)) {
throw new \InvalidArgumentException('ERR_CONSECUTIVEDOTS');
}
if ($this->lexer->token['type'] === EmailLexer::S_HYPHEN && $this->lexer->isNextToken(EmailLexer::S_DOT)) {
throw new \InvalidArgumentException('ERR_DOMAINHYPHENEND');
}
if ($this->lexer->token['type'] === EmailLexer::S_OPENQBRACKET) {
try {
$this->lexer->find(EmailLexer::S_CLOSEQBRACKET);
} catch (\RuntimeException $e) {
throw new \InvalidArgumentException('ERR_EXPECTING_DOMLIT_CLOSE');
}
$this->parseDomainLiteral();
}
if ($this->lexer->token['type'] === EmailLexer::S_OPENBRACKET) {
try {
$this->lexer->find(EmailLexer::S_CLOSEBRACKET);
} catch (\RuntimeException $e) {
throw new \InvalidArgumentException('ERR_EXPECTING_DOMLIT_CLOSE');
}
$this->parseDomainLiteral();
}
if ($this->lexer->token['type'] === EmailLexer::S_BACKSLASH
&& $this->lexer->isNextToken(EmailLexer::GENERIC)) {
throw new \InvalidArgumentException('ERR_EXPECTING_ATEXT');
}
if ($this->lexer->token['type'] === EmailLexer::S_DOT &&
$prev['type'] === EmailLexer::GENERIC &&
strlen($prev['value']) > 63
) {
$this->warnings[] = EmailValidator::RFC5322_LABEL_TOOLONG;
}
if ($this->lexer->token['type'] === EmailLexer::S_SP ||
$this->lexer->token['type'] === EmailLexer::S_HTAB ||
$this->lexer->token['type'] === EmailLexer::S_CR ||
$this->lexer->token['type'] === EmailLexer::S_LF ||
$this->lexer->token['type'] === EmailLexer::CRLF
) {
$this->parseFWS();
}
$domain .= $this->lexer->token['value'];
$this->lexer->moveNext();
} while ($this->lexer->token);
$prev = $this->lexer->getPrevious();
$length = strlen($prev['value']);
if ($prev['type'] === EmailLexer::S_DOT) {
throw new \InvalidArgumentException('ERR_DOT_END');
}
if ($prev['type'] === EmailLexer::S_HYPHEN) {
throw new \InvalidArgumentException('ERR_DOMAINHYPHENEND');
}
if ($length > 254) {
$this->warnings[] = EmailValidator::RFC5322_DOMAIN_TOOLONG;
}
if ($prev['type'] === EmailLexer::S_CR) {
throw new \InvalidArgumentException('ERR_FWS_CRLF_END');
}
$this->domainPart = $domain;
}
private function parseDomainLiteral()
{
$IPv6TAG = false;
$addressLiteral = '';
if ($this->lexer->isNextToken(EmailLexer::S_COLON)) {
// Address starts with a single colon
$this->warnings[] = EmailValidator::RFC5322_IPV6_COLONSTRT;
}
if ($this->lexer->isNextToken(EmailLexer::S_IPV6TAG)) {
try {
$lexer = clone $this->lexer;
$lexer->moveNext();
if ($lexer->isNextToken(EmailLexer::S_DOUBLECOLON)) {
$this->warnings[] = EmailValidator::RFC5322_IPV6_COLONSTRT;
}
} catch (\Exception $e) {
}
}
do {
if ($this->lexer->token['type'] === EmailLexer::C_NUL) {
throw new \InvalidArgumentException('ERR_EXPECTING_DTEXT');
} elseif ($this->lexer->token['type'] === EmailLexer::INVALID ||
$this->lexer->token['type'] === EmailLexer::C_DEL ||
$this->lexer->token['type'] === EmailLexer::S_LF
) {
$this->warnings[] = EmailValidator::RFC5322_DOMLIT_OBSDTEXT;
}
if ($this->lexer->isNextTokenAny(array(EmailLexer::S_OPENQBRACKET, EmailLexer::S_OPENBRACKET))) {
throw new \InvalidArgumentException('ERR_EXPECTING_DTEXT');
}
if ($this->lexer->isNextTokenAny(array(EmailLexer::S_HTAB, EmailLexer::S_SP))) {
$this->warnings[] = EmailValidator::CFWS_FWS;
$this->parseFWS();
}
if ($this->lexer->isNextToken(EmailLexer::S_CR)) {
throw new \InvalidArgumentException("ERR_CR_NO_LF");
}
if ($this->lexer->token['type'] === EmailLexer::S_BACKSLASH) {
$this->warnings[] = EmailValidator::RFC5322_DOMLIT_OBSDTEXT;
$addressLiteral .= $this->lexer->token['value'];
$this->lexer->moveNext();
$this->validateQuotedPair();
}
if ($this->lexer->token['type'] === EmailLexer::S_CLOSEQBRACKET) {
break;
}
if ($this->lexer->token['type'] === EmailLexer::S_SP ||
$this->lexer->token['type'] === EmailLexer::S_HTAB ||
$this->lexer->token['type'] === EmailLexer::CRLF
) {
$this->parseFWS();
}
if ($this->lexer->token['type'] === EmailLexer::S_IPV6TAG) {
$IPv6TAG = true;
}
$addressLiteral .= $this->lexer->token['value'];
} while ($this->lexer->moveNext());
// Revision 2.7: Daniel Marschall's new IPv6 testing strategy
$prev = $this->lexer->getPrevious();
if ($prev['type'] === EmailLexer::S_COLON) {
// Address ends with a single colon
$this->warnings[] = EmailValidator::RFC5322_IPV6_COLONEND;
}
$addressLiteral = str_replace('[', '', $addressLiteral);
$maxGroups = 8;
$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[] = EmailValidator::RFC5321_ADDRESSLITERAL;
return;
}
// Convert IPv4 part to IPv6 format for further testing
$addressLiteral = substr($addressLiteral, 0, $index) . '0:0';
}
if (!$IPv6TAG) {
$this->warnings[] = EmailValidator::RFC5322_DOMAINLITERAL;
return;
}
$this->warnings[] = EmailValidator::RFC5321_ADDRESSLITERAL;
$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) {
// Check for unmatched characters
$this->warnings[] = EmailValidator::RFC5322_IPV6_BADCHAR;
}
if ($colons === false) {
// We need exactly the right number of groups
if ($groupCount !== $maxGroups) {
$this->warnings[] = EmailValidator::RFC5322_IPV6_GRPCOUNT;
}
} else {
if ($colons !== strrpos($IPv6, '::')) {
$this->warnings[] = EmailValidator::RFC5322_IPV6_2X2XCOLON;
} else {
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[] = EmailValidator::RFC5322_IPV6_MAXGRPS;
} elseif ($groupCount === $maxGroups) {
// Eliding a single "::"
$this->warnings[] = EmailValidator::RFC5321_IPV6DEPRECATED;
}
}
}
return $addressLiteral;
return true;
}
/**
* validateQuotedPair
* @param string $localPart
* @param string $parsedDomainPart
*/
private function validateQuotedPair()
protected function addLongEmailWarning($localPart, $parsedDomainPart)
{
if (!($this->lexer->token['type'] === EmailLexer::INVALID
|| $this->lexer->token['type'] === EmailLexer::C_DEL)) {
throw new \InvalidArgumentException('ERR_EXPECTING_QPAIR');
}
// SP & HTAB are allowed
$this->warnings[] = EmailValidator::DEPREC_QP;
}
private function parseLocalPart()
{
$closingQuote = false;
while ($this->lexer->token['type'] !== EmailLexer::S_AT && $this->lexer->token) {
$previous = $this->lexer->getPrevious();
if ($this->lexer->token['type'] === EmailLexer::S_DOT && !$this->lexer->getPrevious()) {
throw new \InvalidArgumentException('ERR_DOT_START');
}
if ($this->lexer->token['type'] === EmailLexer::S_DQUOTE) {
if (!$closingQuote) {
if ($this->lexer->isNextToken(EmailLexer::GENERIC) && $previous['type'] === EmailLexer::GENERIC) {
throw new \InvalidArgumentException('ERR_EXPECTING_ATEXT');
}
$this->warnings[] = EmailValidator::RFC5321_QUOTEDSTRING;
try {
$this->lexer->find(EmailLexer::S_DQUOTE);
$closingQuote = true;
} catch (\Exception $e) {
throw new \InvalidArgumentException('ERR_UNCLOSEDQUOTEDSTR');
}
}
}
if ($this->lexer->token['type'] === EmailLexer::S_OPENPARENTHESIS) {
$this->parseComments();
}
if ($this->lexer->token['type'] === EmailLexer::S_DOT &&
$this->lexer->isNextToken(EmailLexer::S_DOT)) {
throw new \InvalidArgumentException('ERR_CONSECUTIVEDOTS');
}
if ($this->lexer->token['type'] === EmailLexer::S_DOT && $this->lexer->isNextToken(EmailLexer::S_AT)) {
throw new \InvalidArgumentException('ERR_DOT_END');
}
if ($this->lexer->token['type'] === EmailLexer::S_BACKSLASH) {
if ($this->lexer->isNextTokenAny(array(EmailLexer::S_SP, EmailLexer::S_HTAB, EmailLexer::C_DEL))) {
$this->warnings[] = EmailValidator::DEPREC_QP;
}
if ($this->lexer->isNextToken(EmailLexer::GENERIC)) {
throw new \InvalidArgumentException('ERR_EXPECTING_ATEXT');
}
}
if ($this->lexer->isNextTokenAny(
array(
EmailLexer::INVALID, EmailLexer::S_LOWERTHAN, EmailLexer::S_GREATERTHAN
)
)
) {
throw new \InvalidArgumentException('ERR_EXPECTING_ATEXT');
}
if ($this->lexer->token['type'] === EmailLexer::S_SP ||
$this->lexer->token['type'] === EmailLexer::S_HTAB ||
$this->lexer->token['type'] === EmailLexer::S_CR ||
$this->lexer->token['type'] === EmailLexer::S_LF ||
$this->lexer->token['type'] === EmailLexer::CRLF
) {
$this->parseFWS();
}
$this->lexer->moveNext();
}
$prev = $this->lexer->getPrevious();
if (strlen($prev['value']) > EmailValidator::RFC5322_LOCAL_TOOLONG) {
$this->warnings[] = EmailValidator::RFC5322_LOCAL_TOOLONG;
}
}
/**
* parseComments
*
* @return string the the comment
*/
private function parseComments()
{
$this->warnings[] = EmailValidator::CFWS_COMMENT;
while (!$this->lexer->isNextToken(EmailLexer::S_CLOSEPARENTHESIS)) {
try {
$this->lexer->find(EmailLexer::S_CLOSEPARENTHESIS);
} catch (\RuntimeException $e) {
throw new \InvalidArgumentException('ERR_UNCLOSEDCOMMENT');
}
//scaping in a comment
if ($this->lexer->token['type'] === EmailLexer::S_BACKSLASH) {
if ($this->lexer->isNextTokenAny(array(EmailLexer::S_SP, EmailLexer::S_HTAB, EmailLexer::C_DEL))) {
$this->warnings[] = EmailValidator::DEPREC_QP;
}
}
$this->lexer->moveNext();
}
$this->lexer->moveNext();
if ($this->lexer->isNextToken(EmailLexer::GENERIC) || EmailLexer::S_EMPTY === $this->lexer->peek()) {
throw new \InvalidArgumentException('ERR_EXPECTING_ATEXT');
}
if ($this->lexer->isNextToken(EmailLexer::S_AT)) {
$this->warnings[] = EmailValidator::DEPREC_CFWS_NEAR_AT;
}
}
/**
* parseFWS
*
* @throw InvalidArgumentException
*/
private function parseFWS()
{
$previous = $this->lexer->getPrevious();
if ($this->lexer->token['type'] === EmailLexer::CRLF && $this->lexer->isNextToken(EmailLexer::CRLF)) {
throw new \InvalidArgumentException("ERR_FWS_CRLF_X2");
}
if ($this->lexer->token['type'] === EmailLexer::S_CR) {
throw new \InvalidArgumentException("ERR_CR_NO_LF");
}
if (!$this->lexer->isNextTokenAny(array(EmailLexer::S_SP, EmailLexer::S_HTAB)) &&
$this->lexer->token['type'] === EmailLexer::CRLF ) {
throw new \InvalidArgumentException("ERR_FWS_CRLF_END");
}
if ($this->lexer->isNextToken(EmailLexer::GENERIC) && $previous['type'] !== EmailLexer::S_AT) {
throw new \InvalidArgumentException("ERR_ATEXT_AFTER_CFWS");
}
if ($this->lexer->token['type'] === EmailLexer::S_LF || $this->lexer->token['type'] === EmailLexer::C_NUL) {
throw new \InvalidArgumentException('ERR_EXPECTING_CTEXT');
}
if ($this->lexer->isNextToken(EmailLexer::S_AT) || $previous['type'] === EmailLexer::S_AT) {
$this->warnings[] = EmailValidator::DEPREC_CFWS_NEAR_AT;
} else {
$this->warnings[] = EmailValidator::CFWS_FWS;
if (strlen($localPart . '@' . $parsedDomainPart) > self::EMAIL_MAX_LENGTH) {
$this->warnings[] = EmailValidator::RFC5322_TOOLONG;
}
}
}
+16 -89
View File
@@ -9,7 +9,6 @@ namespace Egulias\EmailValidator;
*/
class EmailValidator
{
// Address is invalid for any purpose
const ERR_CONSECUTIVEATS = 128;
const ERR_EXPECTING_DTEXT = 129;
const ERR_NOLOCALPART = 130;
@@ -34,7 +33,6 @@ class EmailValidator
const ERR_FWS_CRLF_END = 149;
const ERR_CR_NO_LF = 150;
const ERR_DEPREC_REACHED = 151;
const RFC5321_TLD = 9;
const RFC5321_TLDNUMERIC = 10;
const RFC5321_QUOTEDSTRING = 11;
@@ -69,7 +67,6 @@ class EmailValidator
protected $warnings = array();
protected $error;
protected $threshold = 255;
protected $emailParts = array();
public function __construct()
{
@@ -80,7 +77,6 @@ class EmailValidator
{
try {
$this->parser->parse((string)$email);
$this->emailParts = explode('@', $email);
$this->warnings = $this->parser->getWarnings();
} catch (\Exception $e) {
$rClass = new \ReflectionClass($this);
@@ -88,21 +84,21 @@ class EmailValidator
return false;
}
$dns = true;
if ($checkDNS) {
$dns = $this->checkDNS();
}
if ($this->hasWarnings() && ((int) max($this->warnings) > $this->threshold)) {
$this->error = self::ERR_DEPREC_REACHED;
return false;
}
return ($strict) ? (!$this->hasWarnings() && $dns) : true;
return !$strict || (!$this->hasWarnings() && $dns);
}
/**
* hasWarnings
*
* @return boolean
*/
public function hasWarnings()
@@ -111,8 +107,6 @@ class EmailValidator
}
/**
* getWarnings
*
* @return array
*/
public function getWarnings()
@@ -121,8 +115,6 @@ class EmailValidator
}
/**
* getError
*
* @return string
*/
public function getError()
@@ -131,8 +123,6 @@ class EmailValidator
}
/**
* setThreshols
*
* @param int $threshold
*
* @return EmailValidator
@@ -145,8 +135,6 @@ class EmailValidator
}
/**
* getThreshold
*
* @return int
*/
public function getThreshold()
@@ -156,87 +144,26 @@ class EmailValidator
protected function checkDNS()
{
$checked = false;
if (!function_exists('dns_get_record') && (
in_array(self::DNSWARN_NO_RECORD, $this->warnings) &&
in_array(self::DNSWARN_NO_MX_RECORD, $this->warnings)
)) {
return $checked;
}
// http://tools.ietf.org/html/rfc5321#section-2.3.5
// Names that can
// be resolved to MX RRs or address (i.e., A or AAAA) RRs (as discussed
// in Section 5) are permitted, as are CNAME RRs whose targets can be
// resolved, in turn, to MX or address RRs.
//
// http://tools.ietf.org/html/rfc5321#section-5.1
// The lookup first attempts to locate an MX record associated with the
// name. If a CNAME record is found, the resulting name is processed as
// if it were the initial name. ... If an empty list of MXs is returned,
// the address is treated as if it was associated with an implicit MX
// RR, with a preference of 0, pointing to that host.
//
// is_email() author's note: We will regard the existence of a CNAME to be
// sufficient evidence of the domain's existence. For performance reasons
// we will not repeat the DNS lookup for the CNAME's target, but we will
// raise a warning because we didn't immediately find an MX record.
$checked = true;
$result = checkdnsrr(trim($this->parser->getParsedDomainPart()), 'MX');
$checked = true;
if (!$result) {
// Domain can't be found in DNS
$this->warnings[] = self::DNSWARN_NO_RECORD;
$checked = false;
}
// Check for TLD addresses
// -----------------------
// TLD addresses are specifically allowed in RFC 5321 but they are
// unusual to say the least. We will allocate a separate
// status to these addresses on the basis that they are more likely
// to be typos than genuine addresses (unless we've already
// established that the domain does have an MX record)
//
// http://tools.ietf.org/html/rfc5321#section-2.3.5
// In the case
// of a top-level domain used by itself in an email address, a single
// string is used without any dots. This makes the requirement,
// described in more detail below, that only fully-qualified domain
// names appear in SMTP transactions on the public Internet,
// particularly important where top-level domains are involved.
//
// TLD format
// ----------
// The format of TLDs has changed a number of times. The standards
// used by IANA have been largely ignored by ICANN, leading to
// confusion over the standards being followed. These are not defined
// anywhere, except as a general component of a DNS host name (a label).
// However, this could potentially lead to 123.123.123.123 being a
// valid DNS name (rather than an IP address) and thereby creating
// an ambiguity. The most authoritative statement on TLD formats that
// the author can find is in a (rejected!) erratum to RFC 1123
// submitted by John Klensin, the author of RFC 5321:
//
// http://www.rfc-editor.org/errata_search.php?rfc=1123&eid=1353
// However, a valid host name can never have the dotted-decimal
// form #.#.#.#, since this change does not permit the highest-level
// component label to start with a digit even if it is not all-numeric.
if (!$checked && (!in_array(self::DNSWARN_NO_RECORD, $this->warnings) &&
!in_array(self::DNSWARN_NO_MX_RECORD, $this->warnings))
) {
if (in_array($this->warnings, RFC5322_DOMAINLITERAL)) {
$this->warnings[] = self::RFC5321_TLD;
}
//if (isset($this->atomList[self::COMPONENT_DOMAIN][$this->elementCount][0]) &&
// is_numeric($this->atomList[self::COMPONENT_DOMAIN][$this->elementCount][0])
//) {
// $this->warnings[] = self::RFC5321_TLDNUMERIC;
//}
$this->addTLDWarnings();
}
return $checked;
}
protected function addTLDWarnings()
{
if (!in_array(self::DNSWARN_NO_RECORD, $this->warnings) &&
!in_array(self::DNSWARN_NO_MX_RECORD, $this->warnings) &&
in_array(self::RFC5322_DOMAINLITERAL, $this->warnings)
) {
$this->warnings[] = self::RFC5321_TLD;
}
}
}
@@ -0,0 +1,312 @@
<?php
namespace Egulias\EmailValidator\Parser;
use Egulias\EmailValidator\EmailLexer;
use Egulias\EmailValidator\Parser\Parser;
use Egulias\EmailValidator\EmailValidator;
class DomainPart extends Parser
{
const DOMAIN_MAX_LENGTH = 254;
protected $domainPart = '';
public function parse($domainPart)
{
$this->lexer->moveNext();
if ($this->lexer->token['type'] === EmailLexer::S_DOT) {
throw new \InvalidArgumentException('ERR_DOT_START');
}
if ($this->lexer->token['type'] === EmailLexer::S_EMPTY) {
throw new \InvalidArgumentException('ERR_NODOMAIN');
}
if ($this->lexer->token['type'] === EmailLexer::S_HYPHEN) {
throw new \InvalidArgumentException('ERR_DOMAINHYPHENEND');
}
if ($this->lexer->token['type'] === EmailLexer::S_OPENPARENTHESIS) {
$this->warnings[] = EmailValidator::DEPREC_COMMENT;
$this->parseDomainComments();
}
$domain = $this->doParseDomainPart();
$prev = $this->lexer->getPrevious();
$length = strlen($domain);
if ($prev['type'] === EmailLexer::S_DOT) {
throw new \InvalidArgumentException('ERR_DOT_END');
}
if ($prev['type'] === EmailLexer::S_HYPHEN) {
throw new \InvalidArgumentException('ERR_DOMAINHYPHENEND');
}
if ($length > self::DOMAIN_MAX_LENGTH) {
$this->warnings[] = EmailValidator::RFC5322_DOMAIN_TOOLONG;
}
if ($prev['type'] === EmailLexer::S_CR) {
throw new \InvalidArgumentException('ERR_FWS_CRLF_END');
}
$this->domainPart = $domain;
}
public function getDomainPart()
{
return $this->domainPart;
}
public function checkIPV6Tag($addressLiteral, $maxGroups = 8)
{
$prev = $this->lexer->getPrevious();
if ($prev['type'] === EmailLexer::S_COLON) {
$this->warnings[] = EmailValidator::RFC5322_IPV6_COLONEND;
}
$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[] = EmailValidator::RFC5322_IPV6_BADCHAR;
}
if ($colons === false) {
// We need exactly the right number of groups
if ($groupCount !== $maxGroups) {
$this->warnings[] = EmailValidator::RFC5322_IPV6_GRPCOUNT;
}
return;
}
if ($colons !== strrpos($IPv6, '::')) {
$this->warnings[] = EmailValidator::RFC5322_IPV6_2X2XCOLON;
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[] = EmailValidator::RFC5322_IPV6_MAXGRPS;
} elseif ($groupCount === $maxGroups) {
$this->warnings[] = EmailValidator::RFC5321_IPV6DEPRECATED;
}
}
protected function doParseDomainPart()
{
$domain = '';
do {
if ($this->lexer->token['type'] === EmailLexer::S_SEMICOLON) {
throw new \InvalidArgumentException('ERR_EXPECTING_ATEXT');
}
$prev = $this->lexer->getPrevious();
if ($this->lexer->token['type'] === EmailLexer::S_SLASH) {
throw new \InvalidArgumentException('ERR_DOMAIN_CHAR_NOT_ALLOWED');
}
if ($this->lexer->token['type'] === EmailLexer::S_OPENPARENTHESIS) {
$this->parseComments();
$this->lexer->moveNext();
}
$this->checkConsecutiveDots();
$this->checkDomainPartExceptions($prev);
if ($this->hasBrackets()) {
$this->parseDomainLiteral();
}
$this->checkLabelLength($prev);
if ($this->isFWS()) {
$this->parseFWS();
}
$domain .= $this->lexer->token['value'];
$this->lexer->moveNext();
} while ($this->lexer->token);
return $domain;
}
protected function parseDomainLiteral()
{
if ($this->lexer->isNextToken(EmailLexer::S_COLON)) {
$this->warnings[] = EmailValidator::RFC5322_IPV6_COLONSTRT;
}
if ($this->lexer->isNextToken(EmailLexer::S_IPV6TAG)) {
$lexer = clone $this->lexer;
$lexer->moveNext();
if ($lexer->isNextToken(EmailLexer::S_DOUBLECOLON)) {
$this->warnings[] = EmailValidator::RFC5322_IPV6_COLONSTRT;
}
}
return $this->doParseDomainLiteral();
}
protected function doParseDomainLiteral()
{
$IPv6TAG = false;
$addressLiteral = '';
do {
if ($this->lexer->token['type'] === EmailLexer::C_NUL) {
throw new \InvalidArgumentException('ERR_EXPECTING_DTEXT');
}
if ($this->lexer->token['type'] === EmailLexer::INVALID ||
$this->lexer->token['type'] === EmailLexer::C_DEL ||
$this->lexer->token['type'] === EmailLexer::S_LF
) {
$this->warnings[] = EmailValidator::RFC5322_DOMLIT_OBSDTEXT;
}
if ($this->lexer->isNextTokenAny(array(EmailLexer::S_OPENQBRACKET, EmailLexer::S_OPENBRACKET))) {
throw new \InvalidArgumentException('ERR_EXPECTING_DTEXT');
}
if ($this->lexer->isNextTokenAny(
array(EmailLexer::S_HTAB, EmailLexer::S_SP, $this->lexer->token['type'] === EmailLexer::CRLF)
)) {
$this->warnings[] = EmailValidator::CFWS_FWS;
$this->parseFWS();
}
if ($this->lexer->isNextToken(EmailLexer::S_CR)) {
throw new \InvalidArgumentException("ERR_CR_NO_LF");
}
if ($this->lexer->token['type'] === EmailLexer::S_BACKSLASH) {
$this->warnings[] = EmailValidator::RFC5322_DOMLIT_OBSDTEXT;
$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[] = EmailValidator::RFC5322_DOMAINLITERAL;
return $addressLiteral;
}
$this->warnings[] = EmailValidator::RFC5321_ADDRESSLITERAL;
$this->checkIPV6Tag($addressLiteral);
return $addressLiteral;
}
/**
* @param string $addressLiteral
*/
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[] = EmailValidator::RFC5321_ADDRESSLITERAL;
return false;
}
// Convert IPv4 part to IPv6 format for further testing
$addressLiteral = substr($addressLiteral, 0, $index) . '0:0';
}
return $addressLiteral;
}
protected function checkDomainPartExceptions($prev)
{
if ($this->lexer->token['type'] === EmailLexer::S_COMMA) {
throw new \InvalidArgumentException('ERR_COMMA_IN_DOMAIN');
}
if ($this->lexer->token['type'] === EmailLexer::S_AT) {
throw new \InvalidArgumentException('ERR_CONSECUTIVEATS');
}
if ($this->lexer->token['type'] === EmailLexer::S_OPENQBRACKET && $prev['type'] !== EmailLexer::S_AT) {
throw new \InvalidArgumentException('ERR_EXPECTING_ATEXT');
}
if ($this->lexer->token['type'] === EmailLexer::S_HYPHEN && $this->lexer->isNextToken(EmailLexer::S_DOT)) {
throw new \InvalidArgumentException('ERR_DOMAINHYPHENEND');
}
if ($this->lexer->token['type'] === EmailLexer::S_BACKSLASH
&& $this->lexer->isNextToken(EmailLexer::GENERIC)) {
throw new \InvalidArgumentException('ERR_EXPECTING_ATEXT');
}
}
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 \InvalidArgumentException('ERR_EXPECTING_DOMLIT_CLOSE');
}
return true;
}
protected function checkLabelLength($prev)
{
if ($this->lexer->token['type'] === EmailLexer::S_DOT &&
$prev['type'] === EmailLexer::GENERIC &&
strlen($prev['value']) > 63
) {
$this->warnings[] = EmailValidator::RFC5322_LABEL_TOOLONG;
}
}
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 \InvalidArgumentException('ERR_EXPECTING_ATEXT');
}
}
}
@@ -0,0 +1,121 @@
<?php
namespace Egulias\EmailValidator\Parser;
use Egulias\EmailValidator\EmailLexer;
use Egulias\EmailValidator\EmailValidator;
use \InvalidArgumentException;
class LocalPart extends Parser
{
public function parse($localPart)
{
$parseDQuote = true;
$closingQuote = false;
while ($this->lexer->token['type'] !== EmailLexer::S_AT && $this->lexer->token) {
if ($this->lexer->token['type'] === EmailLexer::S_DOT && !$this->lexer->getPrevious()) {
throw new \InvalidArgumentException('ERR_DOT_START');
}
$closingQuote = $this->checkDQUOTE($closingQuote);
if ($closingQuote && $parseDQuote) {
$parseDQuote = $this->parseDoubleQuote();
}
if ($this->lexer->token['type'] === EmailLexer::S_OPENPARENTHESIS) {
$this->parseComments();
}
$this->checkConsecutiveDots();
if (
$this->lexer->token['type'] === EmailLexer::S_DOT &&
$this->lexer->isNextToken(EmailLexer::S_AT)
) {
throw new \InvalidArgumentException('ERR_DOT_END');
}
$this->warnEscaping();
$this->isInvalidToken($this->lexer->token, $closingQuote);
if ($this->isFWS()) {
$this->parseFWS();
}
$this->lexer->moveNext();
}
$prev = $this->lexer->getPrevious();
if (strlen($prev['value']) > EmailValidator::RFC5322_LOCAL_TOOLONG) {
$this->warnings[] = EmailValidator::RFC5322_LOCAL_TOOLONG;
}
}
protected function parseDoubleQuote()
{
$parseAgain = true;
$special = array(
EmailLexer::S_CR => true,
EmailLexer::S_HTAB => true,
EmailLexer::S_LF => true
);
$invalid = array(
EmailLexer::C_NUL => true,
EmailLexer::S_HTAB => true,
EmailLexer::S_CR => true,
EmailLexer::S_LF => true
);
$setSpecialsWarning = true;
$this->lexer->moveNext();
while ($this->lexer->token['type'] !== EmailLexer::S_DQUOTE && $this->lexer->token) {
$parseAgain = false;
if (isset($special[$this->lexer->token['type']]) && $setSpecialsWarning) {
$this->warnings[] = EmailValidator::CFWS_FWS;
$setSpecialsWarning = false;
}
$this->lexer->moveNext();
if (!$this->escaped() && isset($invalid[$this->lexer->token['type']])) {
throw new InvalidArgumentException("ERR_EXPECTED_ATEXT");
}
}
$prev = $this->lexer->getPrevious();
if ($prev['type'] === EmailLexer::S_BACKSLASH) {
if (!$this->checkDQUOTE(false)) {
throw new \InvalidArgumentException("ERR_UNCLOSED_DQUOTE");
}
}
if (!$this->lexer->isNextToken(EmailLexer::S_AT) && $prev['type'] !== EmailLexer::S_BACKSLASH) {
throw new \InvalidArgumentException("ERR_EXPECED_AT");
}
return $parseAgain;
}
protected function isInvalidToken($token, $closingQuote)
{
$forbidden = array(
EmailLexer::S_COMMA,
EmailLexer::S_CLOSEBRACKET,
EmailLexer::S_OPENBRACKET,
EmailLexer::S_GREATERTHAN,
EmailLexer::S_LOWERTHAN,
EmailLexer::S_COLON,
EmailLexer::S_SEMICOLON,
EmailLexer::INVALID
);
if (in_array($token['type'], $forbidden) && !$closingQuote) {
throw new \InvalidArgumentException('ERR_EXPECTING_ATEXT');
}
}
}
@@ -0,0 +1,190 @@
<?php
namespace Egulias\EmailValidator\Parser;
use Egulias\EmailValidator\EmailLexer;
use Egulias\EmailValidator\EmailValidator;
abstract class Parser
{
protected $warnings = array();
protected $lexer;
public function __construct(EmailLexer $lexer)
{
$this->lexer = $lexer;
}
public function getWarnings()
{
return $this->warnings;
}
abstract function parse($str);
/**
* validateQuotedPair
*/
protected function validateQuotedPair()
{
if (!($this->lexer->token['type'] === EmailLexer::INVALID
|| $this->lexer->token['type'] === EmailLexer::C_DEL)) {
throw new \InvalidArgumentException('ERR_EXPECTING_QPAIR');
}
$this->warnings[] = EmailValidator::DEPREC_QP;
}
/**
* @return string the the comment
* @throws \InvalidArgumentException
*/
protected function parseComments()
{
$this->isUnclosedComment();
$this->warnings[] = EmailValidator::CFWS_COMMENT;
while (!$this->lexer->isNextToken(EmailLexer::S_CLOSEPARENTHESIS)) {
$this->warnEscaping();
$this->lexer->moveNext();
}
$this->lexer->moveNext();
if ($this->lexer->isNextTokenAny(array(EmailLexer::GENERIC, EmailLexer::S_EMPTY))) {
throw new \InvalidArgumentException('ERR_EXPECTING_ATEXT');
}
if ($this->lexer->isNextToken(EmailLexer::S_AT)) {
$this->warnings[] = EmailValidator::DEPREC_CFWS_NEAR_AT;
}
}
protected function isUnclosedComment()
{
try {
$this->lexer->find(EmailLexer::S_CLOSEPARENTHESIS);
return true;
} catch (\RuntimeException $e) {
throw new \InvalidArgumentException('ERR_UNCLOSEDCOMMENT');
}
}
protected function parseFWS()
{
$previous = $this->lexer->getPrevious();
$this->checkCRLFInFWS();
if ($this->lexer->token['type'] === EmailLexer::S_CR) {
throw new \InvalidArgumentException("ERR_CR_NO_LF");
}
if ($this->lexer->isNextToken(EmailLexer::GENERIC) && $previous['type'] !== EmailLexer::S_AT) {
throw new \InvalidArgumentException("ERR_ATEXT_AFTER_CFWS");
}
if ($this->lexer->token['type'] === EmailLexer::S_LF || $this->lexer->token['type'] === EmailLexer::C_NUL) {
throw new \InvalidArgumentException('ERR_EXPECTING_CTEXT');
}
if ($this->lexer->isNextToken(EmailLexer::S_AT) || $previous['type'] === EmailLexer::S_AT) {
$this->warnings[] = EmailValidator::DEPREC_CFWS_NEAR_AT;
} else {
$this->warnings[] = EmailValidator::CFWS_FWS;
}
}
protected function checkConsecutiveDots()
{
if ($this->lexer->token['type'] === EmailLexer::S_DOT && $this->lexer->isNextToken(EmailLexer::S_DOT)) {
throw new \InvalidArgumentException('ERR_CONSECUTIVEDOTS');
}
}
protected function isFWS()
{
if ($this->escaped()) {
return false;
}
if ($this->lexer->token['type'] === EmailLexer::S_SP ||
$this->lexer->token['type'] === EmailLexer::S_HTAB ||
$this->lexer->token['type'] === EmailLexer::S_CR ||
$this->lexer->token['type'] === EmailLexer::S_LF ||
$this->lexer->token['type'] === EmailLexer::CRLF
) {
return true;
}
return false;
}
protected function escaped()
{
$previous = $this->lexer->getPrevious();
if ($previous['type'] === EmailLexer::S_BACKSLASH
&&
$this->lexer->token['type'] !== EmailLexer::GENERIC
) {
return true;
}
return false;
}
protected function warnEscaping()
{
if ($this->lexer->token['type'] !== EmailLexer::S_BACKSLASH) {
return false;
}
if ($this->lexer->isNextToken(EmailLexer::GENERIC)) {
throw new \InvalidArgumentException('ERR_EXPECTING_ATEXT');
}
if (!$this->lexer->isNextTokenAny(array(EmailLexer::S_SP, EmailLexer::S_HTAB, EmailLexer::C_DEL))) {
return false;
}
$this->warnings[] = EmailValidator::DEPREC_QP;
return true;
}
protected function checkDQUOTE($hasClosingQuote)
{
if ($this->lexer->token['type'] !== EmailLexer::S_DQUOTE) {
return $hasClosingQuote;
}
if ($hasClosingQuote) {
return $hasClosingQuote;
}
$previous = $this->lexer->getPrevious();
if ($this->lexer->isNextToken(EmailLexer::GENERIC) && $previous['type'] === EmailLexer::GENERIC) {
throw new \InvalidArgumentException('ERR_EXPECTING_ATEXT');
}
$this->warnings[] = EmailValidator::RFC5321_QUOTEDSTRING;
try {
$this->lexer->find(EmailLexer::S_DQUOTE);
$hasClosingQuote = true;
} catch (\Exception $e) {
throw new \InvalidArgumentException('ERR_UNCLOSEDQUOTEDSTR');
}
return $hasClosingQuote;
}
protected function checkCRLFInFWS()
{
if ($this->lexer->token['type'] !== EmailLexer::CRLF) {
return;
}
if ($this->lexer->isNextToken(EmailLexer::CRLF)) {
throw new \InvalidArgumentException("ERR_FWS_CRLF_X2");
}
if (!$this->lexer->isNextTokenAny(array(EmailLexer::S_SP, EmailLexer::S_HTAB))) {
throw new \InvalidArgumentException("ERR_FWS_CRLF_END");
}
}
}
@@ -26,6 +26,73 @@ class EmailLexerTests extends \PHPUnit_Framework_TestCase
$this->assertEquals($token, $lexer->token['type']);
}
public function testLexerParsesMultipleSpaces()
{
$lexer = new EmailLexer();
$lexer->setInput(' ');
$lexer->moveNext();
$lexer->moveNext();
$this->assertEquals(EmailLexer::S_SP, $lexer->token['type']);
$lexer->moveNext();
$this->assertEquals(EmailLexer::S_SP, $lexer->token['type']);
}
/**
* @dataProvider invalidUTF8CharsProvider
*/
public function testLexerParsesInvalidUTF8($char)
{
$lexer = new EmailLexer();
$lexer->setInput($char);
$lexer->moveNext();
$lexer->moveNext();
$this->assertEquals(EmailLexer::INVALID, $lexer->token['type']);
}
public function invalidUTF8CharsProvider()
{
$chars = array();
for ($i = 0; $i < 0x100; ++$i) {
$c = $this->utf8Chr($i);
if (preg_match('/(?=\p{Cc})(?=[^\t\n\n\r])/u', $c) && !preg_match('/\x{0000}/u', $c)) {
$chars[] = array($c);
}
}
return $chars;
}
protected function utf8Chr($code_point)
{
if ($code_point < 0 || 0x10FFFF < $code_point || (0xD800 <= $code_point && $code_point <= 0xDFFF)) {
return '';
}
if ($code_point < 0x80) {
$hex[0] = $code_point;
$ret = chr($hex[0]);
} elseif ($code_point < 0x800) {
$hex[0] = 0x1C0 | $code_point >> 6;
$hex[1] = 0x80 | $code_point & 0x3F;
$ret = chr($hex[0]).chr($hex[1]);
} elseif ($code_point < 0x10000) {
$hex[0] = 0xE0 | $code_point >> 12;
$hex[1] = 0x80 | $code_point >> 6 & 0x3F;
$hex[2] = 0x80 | $code_point & 0x3F;
$ret = chr($hex[0]).chr($hex[1]).chr($hex[2]);
} else {
$hex[0] = 0xF0 | $code_point >> 18;
$hex[1] = 0x80 | $code_point >> 12 & 0x3F;
$hex[2] = 0x80 | $code_point >> 6 & 0x3F;
$hex[3] = 0x80 | $code_point & 0x3F;
$ret = chr($hex[0]).chr($hex[1]).chr($hex[2]).chr($hex[3]);
}
return $ret;
}
public function testLexerForTab()
{
$lexer = new EmailLexer();
@@ -36,6 +103,23 @@ class EmailLexerTests extends \PHPUnit_Framework_TestCase
$this->assertEquals(EmailLexer::S_HTAB, $lexer->token['type']);
}
public function testLexerSearchToken()
{
$lexer = new EmailLexer();
$lexer->setInput("foo\tbar");
$lexer->moveNext();
$this->assertTrue($lexer->find(EmailLexer::S_HTAB));
}
public function testLexerHasInvalidTokens()
{
$lexer = new EmailLexer();
$lexer->setInput(chr(226));
$lexer->moveNext();
$lexer->moveNext();
$this->assertTrue($lexer->hasInvalidTokens());
}
public function getTokens()
{
return array(
@@ -53,6 +137,7 @@ class EmailLexerTests extends \PHPUnit_Framework_TestCase
array("\"", EmailLexer::S_DQUOTE),
array("-", EmailLexer::S_HYPHEN),
array("\\", EmailLexer::S_BACKSLASH),
array("/", EmailLexer::S_SLASH),
array("(", EmailLexer::S_OPENPARENTHESIS),
array(")", EmailLexer::S_CLOSEPARENTHESIS),
array('<', EmailLexer::S_LOWERTHAN),
@@ -66,7 +151,9 @@ class EmailLexerTests extends \PHPUnit_Framework_TestCase
array('{', EmailLexer::S_OPENQBRACKET),
array('}', EmailLexer::S_CLOSEQBRACKET),
array('', EmailLexer::S_EMPTY),
array(chr(31), EmailLexer::INVALID)
array(chr(31), EmailLexer::INVALID),
array(chr(226), EmailLexer::INVALID),
array(chr(0), EmailLexer::C_NUL)
);
}
}
@@ -1,15 +0,0 @@
<?php
namespace Egulias\Tests\EmailValidator;
use Egulias\EmailValidator\EmailParser;
class EmailParserTests extends \PHPUnit_Framework_TestCase
{
public function testParserExtendsLib()
{
$mock = $this->getMock('egulias\EmailValidator\EmailLexer');
$parser = new EmailParser($mock);
$this->markTestIncomplete();
}
}
@@ -26,6 +26,14 @@ class EmailValidatorTest extends \PHPUnit_Framework_TestCase
$this->assertTrue($this->validator->isValid($email));
}
public function testInvalidUTF8Email()
{
$validator = new EmailValidator;
$email = "\x80\x81\x82@\x83\x84\x85.\x86\x87\x88";
$this->assertFalse($validator->isValid($email));
}
public function getValidEmails()
{
return array(
@@ -34,18 +42,26 @@ class EmailValidatorTest extends \PHPUnit_Framework_TestCase
array('fabien_potencier@example.fr'),
array('example@localhost'),
array('fab\'ien@symfony.com'),
array('fab\ ien@symfony.com'),
array('example((example))@fakedfake.co.uk'),
array('example@faked(fake).co.uk'),
array('fabien+@symfony.com'),
array('инфо@письмо.рф'),
array('"username"@example.com'),
array('"user,name"@example.com'),
array('"user name"@example.com'),
array('"user@name"@example.com'),
array('"\a"@iana.org'),
array('"test\ test"@iana.org'),
array('""@iana.org'),
array('"\""@iana.org'),
);
}
/**
* @dataProvider getInvalidEmails
*/
public function testAInvalidEmails($email)
public function testInvalidEmails($email)
{
$this->assertFalse($this->validator->isValid($email));
}
@@ -53,9 +69,12 @@ class EmailValidatorTest extends \PHPUnit_Framework_TestCase
public function getInvalidEmails()
{
return array(
array('user name@example.com'),
array('user name@example.com'),
array('example.@example.co.uk'),
array('example@example@example.co.uk'),
array('(fabien_potencier@example.fr)'),
array('(test_exampel@example.fr)'),
array('example(example)example@example.co.uk'),
array('.example@localhost'),
array('ex\ample@localhost'),
@@ -63,6 +82,33 @@ class EmailValidatorTest extends \PHPUnit_Framework_TestCase
array('example@localhost.'),
array('user name@example.com'),
array('username@ example . com'),
array('example@(fake).com'),
array('example@(fake.com'),
array('username@example,com'),
array('usern,ame@example.com'),
array('user[na]me@example.com'),
array('"""@iana.org'),
array('"\"@iana.org'),
array('"test"test@iana.org'),
array('"test""test"@iana.org'),
array('"test"."test"@iana.org'),
array('"test".test@iana.org'),
array('"test"' . chr(0) . '@iana.org'),
array('"test\"@iana.org'),
array(chr(226) . '@iana.org'),
array('test@' . chr(226) . '.org'),
array('\r\ntest@iana.org'),
array('\r\n test@iana.org'),
array('\r\n \r\ntest@iana.org'),
array('\r\n \r\ntest@iana.org'),
array('\r\n \r\n test@iana.org'),
array('test@iana.org \r\n'),
array('test@iana.org \r\n '),
array('test@iana.org \r\n \r\n'),
array('test@iana.org \r\n\r\n'),
array('test@iana.org \r\n\r\n '),
array('test@iana/icann.org'),
array('test@foo;bar.com'),
);
}
@@ -102,30 +148,58 @@ class EmailValidatorTest extends \PHPUnit_Framework_TestCase
array(EmailValidator::ERR_CR_NO_LF, "example@exa\rmple.co.uk"),
array(EmailValidator::ERR_CR_NO_LF, "example@[\r]"),
array(EmailValidator::ERR_CR_NO_LF, "exam\rple@example.co.uk"),
array(EmailValidator::ERR_CR_NO_LF, "\"\r\"@localhost"),
);
}
/**
* @dataProvider getInvalidEmailsWithWarnings
*/
public function testInvalidEmailsWithWarningsCheck($warnings, $email)
public function testValidEmailsWithWarningsCheck($warnings, $email)
{
$this->assertTrue($this->validator->isValid($email, true));
$this->assertEquals($warnings, $this->validator->getWarnings());
}
/**
* @dataProvider getInvalidEmailsWithWarnings
*/
public function testInvalidEmailsWithDnsCheckAndStrictMode($warnings, $email)
{
$this->assertFalse($this->validator->isValid($email, true, true));
$this->assertEquals($warnings, $this->validator->getWarnings());
}
public function getInvalidEmailsWithWarnings()
{
return array(
array(array( EmailValidator::DEPREC_CFWS_NEAR_AT,), 'example @example.co.uk'),
array(array( EmailValidator::DEPREC_CFWS_NEAR_AT,), 'example@ example.co.uk'),
array(array( EmailValidator::CFWS_COMMENT,), 'example@example(examplecomment).co.uk'),
array(
array(
EmailValidator::DEPREC_CFWS_NEAR_AT,
EmailValidator::DNSWARN_NO_RECORD
),
'example @example.co.uk'
),
array(
array(
EmailValidator::DEPREC_CFWS_NEAR_AT,
EmailValidator::DNSWARN_NO_RECORD
),
'example@ example.co.uk'
),
array(
array(
EmailValidator::CFWS_COMMENT,
EmailValidator::DNSWARN_NO_RECORD
),
'example@example(examplecomment).co.uk'
),
array(
array(
EmailValidator::CFWS_COMMENT,
EmailValidator::DEPREC_CFWS_NEAR_AT,
EmailValidator::DNSWARN_NO_RECORD,
),
'example(examplecomment)@example.co.uk'
),
@@ -133,9 +207,18 @@ class EmailValidatorTest extends \PHPUnit_Framework_TestCase
array(
EmailValidator::RFC5321_QUOTEDSTRING,
EmailValidator::CFWS_FWS,
EmailValidator::DNSWARN_NO_RECORD,
),
"\"\t\"@example.co.uk"
),
array(
array(
EmailValidator::RFC5321_QUOTEDSTRING,
EmailValidator::CFWS_FWS,
EmailValidator::DNSWARN_NO_RECORD
),
"\"\r\"@example.co.uk"
),
array(
array(
EmailValidator::RFC5321_ADDRESSLITERAL,
@@ -215,8 +298,8 @@ class EmailValidatorTest extends \PHPUnit_Framework_TestCase
),
array(
array(
EmailValidator::RFC5322_IPV6_COLONEND,
EmailValidator::RFC5321_ADDRESSLITERAL,
EmailValidator::RFC5322_IPV6_COLONEND,
EmailValidator::DNSWARN_NO_RECORD,
),
'example@[IPv6:2001:0db8:85a3:0000:0000:8a2e:0370:]'
@@ -224,12 +307,14 @@ class EmailValidatorTest extends \PHPUnit_Framework_TestCase
array(
array(
EmailValidator::RFC5321_QUOTEDSTRING,
EmailValidator::DNSWARN_NO_RECORD
),
'"example"@example.co.uk'
),
array(
array(
EmailValidator::RFC5322_LOCAL_TOOLONG,
EmailValidator::DNSWARN_NO_RECORD
),
'too_long_localpart_too_long_localpart_too_long_localpart_too_long_localpart@example.co.uk'
),
@@ -260,6 +345,17 @@ class EmailValidatorTest extends \PHPUnit_Framework_TestCase
'parttoolonglocalparttoolonglocalparttoolonglocalparttoolonglocalparttoolonglocalparttoolonglocalpart'.
'toolonglocalparttoolonglocalparttoolonglocalparttoolonglocalpar'
),
array(
array(
EmailValidator::DNSWARN_NO_RECORD,
),
'test@test'
),
);
}
public function testInvalidEmailsWithStrict()
{
$this->assertFalse($this->validator->isValid('"test"@test', false, true));
}
}