diff options
author | dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> | 2021-08-19 10:25:13 +0300 |
---|---|---|
committer | Carl Schwan <carl@carlschwan.eu> | 2021-08-25 16:40:07 +0300 |
commit | 56dadbd34ec5a2cc44fb9c1b8f10e58620827426 (patch) | |
tree | 8d69e1db03f0585df35d0363b851add8800e93ca /egulias | |
parent | 3359f61c34b5d64ae25eeab6609b3a45ad426c2b (diff) |
Bump egulias/email-validator from 2.1.25 to 3.1.1
Bumps [egulias/email-validator](https://github.com/egulias/EmailValidator) from 2.1.25 to 3.1.1.
- [Release notes](https://github.com/egulias/EmailValidator/releases)
- [Changelog](https://github.com/egulias/EmailValidator/blob/3.x/CHANGELOG.md)
- [Commits](https://github.com/egulias/EmailValidator/compare/2.1.25...3.1.1)
---
updated-dependencies:
- dependency-name: egulias/email-validator
dependency-type: direct:production
update-type: version-update:semver-major
...
Signed-off-by: dependabot[bot] <support@github.com>
Signed-off-by: Carl Schwan <carl@carlschwan.eu>
Diffstat (limited to 'egulias')
99 files changed, 6795 insertions, 1207 deletions
diff --git a/egulias/email-validator/CONTRIBUTING.md b/egulias/email-validator/CONTRIBUTING.md new file mode 100644 index 00000000..7b79e108 --- /dev/null +++ b/egulias/email-validator/CONTRIBUTING.md @@ -0,0 +1,153 @@ +# Contributing + +When contributing to this repository make sure to follow the Pull request process below. +Reduce to the minimum 3rd party dependencies. + +Please note we have a [code of conduct](#Code of Conduct), please follow it in all your interactions with the project. + +## Pull Request Process + +When doing a PR to v2 remember that you also have to do the PR port to v3, or tests confirming the bug is not reproducible. + +1. Supported version is v3. If you are fixing a bug in v2, please port to v3 +2. Use the title as a brief description of the changes +3. Describe the changes you are proposing + 1. If adding an extra validation state the benefits of adding it and the problem is solving + 2. Document in the readme, by adding it to the list +4. Provide appropiate tests for the code you are submitting: aim to keep the existing coverage percentage. +5. Add your Twitter handle (if you have) so we can thank you there. + +## License +By contributing, you agree that your contributions will be licensed under its MIT License. + +## Code of Conduct + +### Our Pledge + +We as members, contributors, and leaders pledge to make participation in our +community a harassment-free experience for everyone, regardless of age, body +size, visible or invisible disability, ethnicity, sex characteristics, gender +identity and expression, level of experience, education, socio-economic status, +nationality, personal appearance, race, religion, or sexual identity +and orientation. + +We pledge to act and interact in ways that contribute to an open, welcoming, +diverse, inclusive, and healthy community. + +### Our Standards + +Examples of behavior that contributes to a positive environment for our +community include: + +* Demonstrating empathy and kindness toward other people +* Being respectful of differing opinions, viewpoints, and experiences +* Giving and gracefully accepting constructive feedback +* Accepting responsibility and apologizing to those affected by our mistakes, + and learning from the experience +* Focusing on what is best not just for us as individuals, but for the + overall community + +Examples of unacceptable behavior include: + +* The use of sexualized language or imagery, and sexual attention or + advances of any kind +* Trolling, insulting or derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or email + address, without their explicit permission +* Other conduct which could reasonably be considered inappropriate in a + professional setting + +### Enforcement Responsibilities + +Community leaders are responsible for clarifying and enforcing our standards of +acceptable behavior and will take appropriate and fair corrective action in +response to any behavior that they deem inappropriate, threatening, offensive, +or harmful. + +Community leaders have the right and responsibility to remove, edit, or reject +comments, commits, code, wiki edits, issues, and other contributions that are +not aligned to this Code of Conduct, and will communicate reasons for moderation +decisions when appropriate. + +### Scope + +This Code of Conduct applies within all community spaces, and also applies when +an individual is officially representing the community in public spaces. +Examples of representing our community include using an official e-mail address, +posting via an official social media account, or acting as an appointed +representative at an online or offline event. + +### Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported to the community leaders responsible for enforcement at <emailvalidatorrfc.ccreport@gmail.com>. +All complaints will be reviewed and investigated promptly and fairly. + +All community leaders are obligated to respect the privacy and security of the +reporter of any incident. + +#### Enforcement Guidelines + +Community leaders will follow these Community Impact Guidelines in determining +the consequences for any action they deem in violation of this Code of Conduct: + +#### 1. Correction + +**Community Impact**: Use of inappropriate language or other behavior deemed +unprofessional or unwelcome in the community. + +**Consequence**: A private, written warning from community leaders, providing +clarity around the nature of the violation and an explanation of why the +behavior was inappropriate. A public apology may be requested. + +#### 2. Warning + +**Community Impact**: A violation through a single incident or series +of actions. + +**Consequence**: A warning with consequences for continued behavior. No +interaction with the people involved, including unsolicited interaction with +those enforcing the Code of Conduct, for a specified period of time. This +includes avoiding interactions in community spaces as well as external channels +like social media. Violating these terms may lead to a temporary or +permanent ban. + +#### 3. Temporary Ban + +**Community Impact**: A serious violation of community standards, including +sustained inappropriate behavior. + +**Consequence**: A temporary ban from any sort of interaction or public +communication with the community for a specified period of time. No public or +private interaction with the people involved, including unsolicited interaction +with those enforcing the Code of Conduct, is allowed during this period. +Violating these terms may lead to a permanent ban. + +#### 4. Permanent Ban + +**Community Impact**: Demonstrating a pattern of violation of community +standards, including sustained inappropriate behavior, harassment of an +individual, or aggression toward or disparagement of classes of individuals. + +**Consequence**: A permanent ban from any sort of public interaction within +the community. + +### Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], +version 2.0, available at +[https://www.contributor-covenant.org/version/2/0/code_of_conduct.html][v2.0]. + +Community Impact Guidelines were inspired by +[Mozilla's code of conduct enforcement ladder][Mozilla CoC]. + +For answers to common questions about this code of conduct, see the FAQ at +[https://www.contributor-covenant.org/faq][FAQ]. Translations are available +at [https://www.contributor-covenant.org/translations][translations]. + +[homepage]: https://www.contributor-covenant.org +[v2.0]: https://www.contributor-covenant.org/version/2/0/code_of_conduct.html +[Mozilla CoC]: https://github.com/mozilla/diversity +[FAQ]: https://www.contributor-covenant.org/faq +[translations]: https://www.contributor-covenant.org/translations diff --git a/egulias/email-validator/LICENSE b/egulias/email-validator/LICENSE index c34d2c19..1f0f2678 100644 --- a/egulias/email-validator/LICENSE +++ b/egulias/email-validator/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2013-2016 Eduardo Gulias Davis +Copyright (c) 2013-2021 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 diff --git a/egulias/email-validator/composer.json b/egulias/email-validator/composer.json index a275696a..d598d1b6 100644 --- a/egulias/email-validator/composer.json +++ b/egulias/email-validator/composer.json @@ -9,18 +9,18 @@ ], "extra": { "branch-alias": { - "dev-master": "2.1.x-dev" + "dev-master": "3.0.x-dev" } }, "require": { - "php": ">=5.5", - "doctrine/lexer": "^1.0.1", - "symfony/polyfill-intl-idn": "^1.10" + "php": ">=7.2", + "doctrine/lexer": "^1.2", + "symfony/polyfill-intl-idn": "^1.15" }, "require-dev": { - "dominicsayers/isemail": "^3.0.7", - "phpunit/phpunit": "^4.8.36|^7.5.15", - "satooshi/php-coveralls": "^1.0.1" + "php-coveralls/php-coveralls": "^2.2", + "phpunit/phpunit": "^8.5.8|^9.3.3", + "vimeo/psalm": "^4" }, "suggest": { "ext-intl": "PHP Internationalization Libraries are required to use the SpoofChecking validation" diff --git a/egulias/email-validator/composer.lock b/egulias/email-validator/composer.lock new file mode 100644 index 00000000..c575e7fc --- /dev/null +++ b/egulias/email-validator/composer.lock @@ -0,0 +1,4440 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", + "This file is @generated automatically" + ], + "content-hash": "a77d36b64bc1213fecf4d4f92d759c3b", + "packages": [ + { + "name": "doctrine/lexer", + "version": "1.2.1", + "source": { + "type": "git", + "url": "https://github.com/doctrine/lexer.git", + "reference": "e864bbf5904cb8f5bb334f99209b48018522f042" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/lexer/zipball/e864bbf5904cb8f5bb334f99209b48018522f042", + "reference": "e864bbf5904cb8f5bb334f99209b48018522f042", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "require-dev": { + "doctrine/coding-standard": "^6.0", + "phpstan/phpstan": "^0.11.8", + "phpunit/phpunit": "^8.2" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.2.x-dev" + } + }, + "autoload": { + "psr-4": { + "Doctrine\\Common\\Lexer\\": "lib/Doctrine/Common/Lexer" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, + { + "name": "Roman Borschel", + "email": "roman@code-factory.org" + }, + { + "name": "Johannes Schmitt", + "email": "schmittjoh@gmail.com" + } + ], + "description": "PHP Doctrine Lexer parser library that can be used in Top-Down, Recursive Descent Parsers.", + "homepage": "https://www.doctrine-project.org/projects/lexer.html", + "keywords": [ + "annotations", + "docblock", + "lexer", + "parser", + "php" + ], + "support": { + "issues": "https://github.com/doctrine/lexer/issues", + "source": "https://github.com/doctrine/lexer/tree/1.2.1" + }, + "funding": [ + { + "url": "https://www.doctrine-project.org/sponsorship.html", + "type": "custom" + }, + { + "url": "https://www.patreon.com/phpdoctrine", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/doctrine%2Flexer", + "type": "tidelift" + } + ], + "time": "2020-05-25T17:44:05+00:00" + }, + { + "name": "symfony/polyfill-intl-idn", + "version": "v1.22.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-intl-idn.git", + "reference": "2d63434d922daf7da8dd863e7907e67ee3031483" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-intl-idn/zipball/2d63434d922daf7da8dd863e7907e67ee3031483", + "reference": "2d63434d922daf7da8dd863e7907e67ee3031483", + "shasum": "" + }, + "require": { + "php": ">=7.1", + "symfony/polyfill-intl-normalizer": "^1.10", + "symfony/polyfill-php72": "^1.10" + }, + "suggest": { + "ext-intl": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.22-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Intl\\Idn\\": "" + }, + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Laurent Bassin", + "email": "laurent@bassin.info" + }, + { + "name": "Trevor Rowbotham", + "email": "trevor.rowbotham@pm.me" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for intl's idn_to_ascii and idn_to_utf8 functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "idn", + "intl", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-intl-idn/tree/v1.22.1" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2021-01-22T09:19:47+00:00" + }, + { + "name": "symfony/polyfill-intl-normalizer", + "version": "v1.22.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-intl-normalizer.git", + "reference": "43a0283138253ed1d48d352ab6d0bdb3f809f248" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/43a0283138253ed1d48d352ab6d0bdb3f809f248", + "reference": "43a0283138253ed1d48d352ab6d0bdb3f809f248", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "suggest": { + "ext-intl": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.22-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Intl\\Normalizer\\": "" + }, + "files": [ + "bootstrap.php" + ], + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for intl's Normalizer class and related functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "intl", + "normalizer", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.22.1" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2021-01-22T09:19:47+00:00" + }, + { + "name": "symfony/polyfill-php72", + "version": "v1.22.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php72.git", + "reference": "cc6e6f9b39fe8075b3dabfbaf5b5f645ae1340c9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php72/zipball/cc6e6f9b39fe8075b3dabfbaf5b5f645ae1340c9", + "reference": "cc6e6f9b39fe8075b3dabfbaf5b5f645ae1340c9", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.22-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Php72\\": "" + }, + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 7.2+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php72/tree/v1.22.1" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2021-01-07T16:49:33+00:00" + } + ], + "packages-dev": [ + { + "name": "amphp/amp", + "version": "v2.5.2", + "source": { + "type": "git", + "url": "https://github.com/amphp/amp.git", + "reference": "efca2b32a7580087adb8aabbff6be1dc1bb924a9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/amphp/amp/zipball/efca2b32a7580087adb8aabbff6be1dc1bb924a9", + "reference": "efca2b32a7580087adb8aabbff6be1dc1bb924a9", + "shasum": "" + }, + "require": { + "php": ">=7" + }, + "require-dev": { + "amphp/php-cs-fixer-config": "dev-master", + "amphp/phpunit-util": "^1", + "ext-json": "*", + "jetbrains/phpstorm-stubs": "^2019.3", + "phpunit/phpunit": "^6.0.9 | ^7", + "psalm/phar": "^3.11@dev", + "react/promise": "^2" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.x-dev" + } + }, + "autoload": { + "psr-4": { + "Amp\\": "lib" + }, + "files": [ + "lib/functions.php", + "lib/Internal/functions.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Daniel Lowrey", + "email": "rdlowrey@php.net" + }, + { + "name": "Aaron Piotrowski", + "email": "aaron@trowski.com" + }, + { + "name": "Bob Weinand", + "email": "bobwei9@hotmail.com" + }, + { + "name": "Niklas Keller", + "email": "me@kelunik.com" + } + ], + "description": "A non-blocking concurrency framework for PHP applications.", + "homepage": "http://amphp.org/amp", + "keywords": [ + "async", + "asynchronous", + "awaitable", + "concurrency", + "event", + "event-loop", + "future", + "non-blocking", + "promise" + ], + "support": { + "irc": "irc://irc.freenode.org/amphp", + "issues": "https://github.com/amphp/amp/issues", + "source": "https://github.com/amphp/amp/tree/v2.5.2" + }, + "funding": [ + { + "url": "https://github.com/amphp", + "type": "github" + } + ], + "time": "2021-01-10T17:06:37+00:00" + }, + { + "name": "amphp/byte-stream", + "version": "v1.8.0", + "source": { + "type": "git", + "url": "https://github.com/amphp/byte-stream.git", + "reference": "f0c20cf598a958ba2aa8c6e5a71c697d652c7088" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/amphp/byte-stream/zipball/f0c20cf598a958ba2aa8c6e5a71c697d652c7088", + "reference": "f0c20cf598a958ba2aa8c6e5a71c697d652c7088", + "shasum": "" + }, + "require": { + "amphp/amp": "^2", + "php": ">=7.1" + }, + "require-dev": { + "amphp/php-cs-fixer-config": "dev-master", + "amphp/phpunit-util": "^1.4", + "friendsofphp/php-cs-fixer": "^2.3", + "jetbrains/phpstorm-stubs": "^2019.3", + "phpunit/phpunit": "^6 || ^7 || ^8", + "psalm/phar": "^3.11.4" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Amp\\ByteStream\\": "lib" + }, + "files": [ + "lib/functions.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Aaron Piotrowski", + "email": "aaron@trowski.com" + }, + { + "name": "Niklas Keller", + "email": "me@kelunik.com" + } + ], + "description": "A stream abstraction to make working with non-blocking I/O simple.", + "homepage": "http://amphp.org/byte-stream", + "keywords": [ + "amp", + "amphp", + "async", + "io", + "non-blocking", + "stream" + ], + "support": { + "irc": "irc://irc.freenode.org/amphp", + "issues": "https://github.com/amphp/byte-stream/issues", + "source": "https://github.com/amphp/byte-stream/tree/master" + }, + "time": "2020-06-29T18:35:05+00:00" + }, + { + "name": "composer/package-versions-deprecated", + "version": "1.11.99.1", + "source": { + "type": "git", + "url": "https://github.com/composer/package-versions-deprecated.git", + "reference": "7413f0b55a051e89485c5cb9f765fe24bb02a7b6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/package-versions-deprecated/zipball/7413f0b55a051e89485c5cb9f765fe24bb02a7b6", + "reference": "7413f0b55a051e89485c5cb9f765fe24bb02a7b6", + "shasum": "" + }, + "require": { + "composer-plugin-api": "^1.1.0 || ^2.0", + "php": "^7 || ^8" + }, + "replace": { + "ocramius/package-versions": "1.11.99" + }, + "require-dev": { + "composer/composer": "^1.9.3 || ^2.0@dev", + "ext-zip": "^1.13", + "phpunit/phpunit": "^6.5 || ^7" + }, + "type": "composer-plugin", + "extra": { + "class": "PackageVersions\\Installer", + "branch-alias": { + "dev-master": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "PackageVersions\\": "src/PackageVersions" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Marco Pivetta", + "email": "ocramius@gmail.com" + }, + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be" + } + ], + "description": "Composer plugin that provides efficient querying for installed package versions (no runtime IO)", + "support": { + "issues": "https://github.com/composer/package-versions-deprecated/issues", + "source": "https://github.com/composer/package-versions-deprecated/tree/1.11.99.1" + }, + "funding": [ + { + "url": "https://packagist.com", + "type": "custom" + }, + { + "url": "https://github.com/composer", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/composer/composer", + "type": "tidelift" + } + ], + "time": "2020-11-11T10:22:58+00:00" + }, + { + "name": "composer/semver", + "version": "3.2.4", + "source": { + "type": "git", + "url": "https://github.com/composer/semver.git", + "reference": "a02fdf930a3c1c3ed3a49b5f63859c0c20e10464" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/semver/zipball/a02fdf930a3c1c3ed3a49b5f63859c0c20e10464", + "reference": "a02fdf930a3c1c3ed3a49b5f63859c0c20e10464", + "shasum": "" + }, + "require": { + "php": "^5.3.2 || ^7.0 || ^8.0" + }, + "require-dev": { + "phpstan/phpstan": "^0.12.54", + "symfony/phpunit-bridge": "^4.2 || ^5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Composer\\Semver\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nils Adermann", + "email": "naderman@naderman.de", + "homepage": "http://www.naderman.de" + }, + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "http://seld.be" + }, + { + "name": "Rob Bast", + "email": "rob.bast@gmail.com", + "homepage": "http://robbast.nl" + } + ], + "description": "Semver library that offers utilities, version constraint parsing and validation.", + "keywords": [ + "semantic", + "semver", + "validation", + "versioning" + ], + "support": { + "irc": "irc://irc.freenode.org/composer", + "issues": "https://github.com/composer/semver/issues", + "source": "https://github.com/composer/semver/tree/3.2.4" + }, + "funding": [ + { + "url": "https://packagist.com", + "type": "custom" + }, + { + "url": "https://github.com/composer", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/composer/composer", + "type": "tidelift" + } + ], + "time": "2020-11-13T08:59:24+00:00" + }, + { + "name": "composer/xdebug-handler", + "version": "1.4.5", + "source": { + "type": "git", + "url": "https://github.com/composer/xdebug-handler.git", + "reference": "f28d44c286812c714741478d968104c5e604a1d4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/xdebug-handler/zipball/f28d44c286812c714741478d968104c5e604a1d4", + "reference": "f28d44c286812c714741478d968104c5e604a1d4", + "shasum": "" + }, + "require": { + "php": "^5.3.2 || ^7.0 || ^8.0", + "psr/log": "^1.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.35 || ^5.7 || 6.5 - 8" + }, + "type": "library", + "autoload": { + "psr-4": { + "Composer\\XdebugHandler\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "John Stevenson", + "email": "john-stevenson@blueyonder.co.uk" + } + ], + "description": "Restarts a process without Xdebug.", + "keywords": [ + "Xdebug", + "performance" + ], + "support": { + "irc": "irc://irc.freenode.org/composer", + "issues": "https://github.com/composer/xdebug-handler/issues", + "source": "https://github.com/composer/xdebug-handler/tree/1.4.5" + }, + "funding": [ + { + "url": "https://packagist.com", + "type": "custom" + }, + { + "url": "https://github.com/composer", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/composer/composer", + "type": "tidelift" + } + ], + "time": "2020-11-13T08:04:11+00:00" + }, + { + "name": "dnoegel/php-xdg-base-dir", + "version": "v0.1.1", + "source": { + "type": "git", + "url": "https://github.com/dnoegel/php-xdg-base-dir.git", + "reference": "8f8a6e48c5ecb0f991c2fdcf5f154a47d85f9ffd" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/dnoegel/php-xdg-base-dir/zipball/8f8a6e48c5ecb0f991c2fdcf5f154a47d85f9ffd", + "reference": "8f8a6e48c5ecb0f991c2fdcf5f154a47d85f9ffd", + "shasum": "" + }, + "require": { + "php": ">=5.3.2" + }, + "require-dev": { + "phpunit/phpunit": "~7.0|~6.0|~5.0|~4.8.35" + }, + "type": "library", + "autoload": { + "psr-4": { + "XdgBaseDir\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "implementation of xdg base directory specification for php", + "support": { + "issues": "https://github.com/dnoegel/php-xdg-base-dir/issues", + "source": "https://github.com/dnoegel/php-xdg-base-dir/tree/v0.1.1" + }, + "time": "2019-12-04T15:06:13+00:00" + }, + { + "name": "doctrine/instantiator", + "version": "1.4.0", + "source": { + "type": "git", + "url": "https://github.com/doctrine/instantiator.git", + "reference": "d56bf6102915de5702778fe20f2de3b2fe570b5b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/instantiator/zipball/d56bf6102915de5702778fe20f2de3b2fe570b5b", + "reference": "d56bf6102915de5702778fe20f2de3b2fe570b5b", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0" + }, + "require-dev": { + "doctrine/coding-standard": "^8.0", + "ext-pdo": "*", + "ext-phar": "*", + "phpbench/phpbench": "^0.13 || 1.0.0-alpha2", + "phpstan/phpstan": "^0.12", + "phpstan/phpstan-phpunit": "^0.12", + "phpunit/phpunit": "^7.0 || ^8.0 || ^9.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Doctrine\\Instantiator\\": "src/Doctrine/Instantiator/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Marco Pivetta", + "email": "ocramius@gmail.com", + "homepage": "https://ocramius.github.io/" + } + ], + "description": "A small, lightweight utility to instantiate objects in PHP without invoking their constructors", + "homepage": "https://www.doctrine-project.org/projects/instantiator.html", + "keywords": [ + "constructor", + "instantiate" + ], + "support": { + "issues": "https://github.com/doctrine/instantiator/issues", + "source": "https://github.com/doctrine/instantiator/tree/1.4.0" + }, + "funding": [ + { + "url": "https://www.doctrine-project.org/sponsorship.html", + "type": "custom" + }, + { + "url": "https://www.patreon.com/phpdoctrine", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/doctrine%2Finstantiator", + "type": "tidelift" + } + ], + "time": "2020-11-10T18:47:58+00:00" + }, + { + "name": "felixfbecker/advanced-json-rpc", + "version": "v3.2.0", + "source": { + "type": "git", + "url": "https://github.com/felixfbecker/php-advanced-json-rpc.git", + "reference": "06f0b06043c7438959dbdeed8bb3f699a19be22e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/felixfbecker/php-advanced-json-rpc/zipball/06f0b06043c7438959dbdeed8bb3f699a19be22e", + "reference": "06f0b06043c7438959dbdeed8bb3f699a19be22e", + "shasum": "" + }, + "require": { + "netresearch/jsonmapper": "^1.0 || ^2.0", + "php": "^7.1 || ^8.0", + "phpdocumentor/reflection-docblock": "^4.3.4 || ^5.0.0" + }, + "require-dev": { + "phpunit/phpunit": "^7.0 || ^8.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "AdvancedJsonRpc\\": "lib/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "ISC" + ], + "authors": [ + { + "name": "Felix Becker", + "email": "felix.b@outlook.com" + } + ], + "description": "A more advanced JSONRPC implementation", + "support": { + "issues": "https://github.com/felixfbecker/php-advanced-json-rpc/issues", + "source": "https://github.com/felixfbecker/php-advanced-json-rpc/tree/v3.2.0" + }, + "time": "2021-01-10T17:48:47+00:00" + }, + { + "name": "felixfbecker/language-server-protocol", + "version": "1.5.1", + "source": { + "type": "git", + "url": "https://github.com/felixfbecker/php-language-server-protocol.git", + "reference": "9d846d1f5cf101deee7a61c8ba7caa0a975cd730" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/felixfbecker/php-language-server-protocol/zipball/9d846d1f5cf101deee7a61c8ba7caa0a975cd730", + "reference": "9d846d1f5cf101deee7a61c8ba7caa0a975cd730", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "require-dev": { + "phpstan/phpstan": "*", + "squizlabs/php_codesniffer": "^3.1", + "vimeo/psalm": "^4.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "LanguageServerProtocol\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "ISC" + ], + "authors": [ + { + "name": "Felix Becker", + "email": "felix.b@outlook.com" + } + ], + "description": "PHP classes for the Language Server Protocol", + "keywords": [ + "language", + "microsoft", + "php", + "server" + ], + "support": { + "issues": "https://github.com/felixfbecker/php-language-server-protocol/issues", + "source": "https://github.com/felixfbecker/php-language-server-protocol/tree/1.5.1" + }, + "time": "2021-02-22T14:02:09+00:00" + }, + { + "name": "guzzlehttp/guzzle", + "version": "7.2.0", + "source": { + "type": "git", + "url": "https://github.com/guzzle/guzzle.git", + "reference": "0aa74dfb41ae110835923ef10a9d803a22d50e79" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/guzzle/zipball/0aa74dfb41ae110835923ef10a9d803a22d50e79", + "reference": "0aa74dfb41ae110835923ef10a9d803a22d50e79", + "shasum": "" + }, + "require": { + "ext-json": "*", + "guzzlehttp/promises": "^1.4", + "guzzlehttp/psr7": "^1.7", + "php": "^7.2.5 || ^8.0", + "psr/http-client": "^1.0" + }, + "provide": { + "psr/http-client-implementation": "1.0" + }, + "require-dev": { + "ext-curl": "*", + "php-http/client-integration-tests": "^3.0", + "phpunit/phpunit": "^8.5.5 || ^9.3.5", + "psr/log": "^1.1" + }, + "suggest": { + "ext-curl": "Required for CURL handler support", + "ext-intl": "Required for Internationalized Domain Name (IDN) support", + "psr/log": "Required for using the Log middleware" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "7.1-dev" + } + }, + "autoload": { + "psr-4": { + "GuzzleHttp\\": "src/" + }, + "files": [ + "src/functions_include.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + }, + { + "name": "Márk Sági-Kazár", + "email": "mark.sagikazar@gmail.com", + "homepage": "https://sagikazarmark.hu" + } + ], + "description": "Guzzle is a PHP HTTP client library", + "homepage": "http://guzzlephp.org/", + "keywords": [ + "client", + "curl", + "framework", + "http", + "http client", + "psr-18", + "psr-7", + "rest", + "web service" + ], + "support": { + "issues": "https://github.com/guzzle/guzzle/issues", + "source": "https://github.com/guzzle/guzzle/tree/7.2.0" + }, + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://github.com/Nyholm", + "type": "github" + }, + { + "url": "https://github.com/alexeyshockov", + "type": "github" + }, + { + "url": "https://github.com/gmponos", + "type": "github" + } + ], + "time": "2020-10-10T11:47:56+00:00" + }, + { + "name": "guzzlehttp/promises", + "version": "1.4.0", + "source": { + "type": "git", + "url": "https://github.com/guzzle/promises.git", + "reference": "60d379c243457e073cff02bc323a2a86cb355631" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/promises/zipball/60d379c243457e073cff02bc323a2a86cb355631", + "reference": "60d379c243457e073cff02bc323a2a86cb355631", + "shasum": "" + }, + "require": { + "php": ">=5.5" + }, + "require-dev": { + "symfony/phpunit-bridge": "^4.4 || ^5.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.4-dev" + } + }, + "autoload": { + "psr-4": { + "GuzzleHttp\\Promise\\": "src/" + }, + "files": [ + "src/functions_include.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + } + ], + "description": "Guzzle promises library", + "keywords": [ + "promise" + ], + "support": { + "issues": "https://github.com/guzzle/promises/issues", + "source": "https://github.com/guzzle/promises/tree/1.4.0" + }, + "time": "2020-09-30T07:37:28+00:00" + }, + { + "name": "guzzlehttp/psr7", + "version": "1.7.0", + "source": { + "type": "git", + "url": "https://github.com/guzzle/psr7.git", + "reference": "53330f47520498c0ae1f61f7e2c90f55690c06a3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/psr7/zipball/53330f47520498c0ae1f61f7e2c90f55690c06a3", + "reference": "53330f47520498c0ae1f61f7e2c90f55690c06a3", + "shasum": "" + }, + "require": { + "php": ">=5.4.0", + "psr/http-message": "~1.0", + "ralouphie/getallheaders": "^2.0.5 || ^3.0.0" + }, + "provide": { + "psr/http-message-implementation": "1.0" + }, + "require-dev": { + "ext-zlib": "*", + "phpunit/phpunit": "~4.8.36 || ^5.7.27 || ^6.5.14 || ^7.5.20 || ^8.5.8 || ^9.3.10" + }, + "suggest": { + "laminas/laminas-httphandlerrunner": "Emit PSR-7 responses" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.7-dev" + } + }, + "autoload": { + "psr-4": { + "GuzzleHttp\\Psr7\\": "src/" + }, + "files": [ + "src/functions_include.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + }, + { + "name": "Tobias Schultze", + "homepage": "https://github.com/Tobion" + } + ], + "description": "PSR-7 message implementation that also provides common utility methods", + "keywords": [ + "http", + "message", + "psr-7", + "request", + "response", + "stream", + "uri", + "url" + ], + "support": { + "issues": "https://github.com/guzzle/psr7/issues", + "source": "https://github.com/guzzle/psr7/tree/1.7.0" + }, + "time": "2020-09-30T07:37:11+00:00" + }, + { + "name": "myclabs/deep-copy", + "version": "1.10.2", + "source": { + "type": "git", + "url": "https://github.com/myclabs/DeepCopy.git", + "reference": "776f831124e9c62e1a2c601ecc52e776d8bb7220" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/776f831124e9c62e1a2c601ecc52e776d8bb7220", + "reference": "776f831124e9c62e1a2c601ecc52e776d8bb7220", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0" + }, + "replace": { + "myclabs/deep-copy": "self.version" + }, + "require-dev": { + "doctrine/collections": "^1.0", + "doctrine/common": "^2.6", + "phpunit/phpunit": "^7.1" + }, + "type": "library", + "autoload": { + "psr-4": { + "DeepCopy\\": "src/DeepCopy/" + }, + "files": [ + "src/DeepCopy/deep_copy.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Create deep copies (clones) of your objects", + "keywords": [ + "clone", + "copy", + "duplicate", + "object", + "object graph" + ], + "support": { + "issues": "https://github.com/myclabs/DeepCopy/issues", + "source": "https://github.com/myclabs/DeepCopy/tree/1.10.2" + }, + "funding": [ + { + "url": "https://tidelift.com/funding/github/packagist/myclabs/deep-copy", + "type": "tidelift" + } + ], + "time": "2020-11-13T09:40:50+00:00" + }, + { + "name": "netresearch/jsonmapper", + "version": "v2.1.0", + "source": { + "type": "git", + "url": "https://github.com/cweiske/jsonmapper.git", + "reference": "e0f1e33a71587aca81be5cffbb9746510e1fe04e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/cweiske/jsonmapper/zipball/e0f1e33a71587aca81be5cffbb9746510e1fe04e", + "reference": "e0f1e33a71587aca81be5cffbb9746510e1fe04e", + "shasum": "" + }, + "require": { + "ext-json": "*", + "ext-pcre": "*", + "ext-reflection": "*", + "ext-spl": "*", + "php": ">=5.6" + }, + "require-dev": { + "phpunit/phpunit": "~4.8.35 || ~5.7 || ~6.4 || ~7.0", + "squizlabs/php_codesniffer": "~3.5" + }, + "type": "library", + "autoload": { + "psr-0": { + "JsonMapper": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "OSL-3.0" + ], + "authors": [ + { + "name": "Christian Weiske", + "email": "cweiske@cweiske.de", + "homepage": "http://github.com/cweiske/jsonmapper/", + "role": "Developer" + } + ], + "description": "Map nested JSON structures onto PHP classes", + "support": { + "email": "cweiske@cweiske.de", + "issues": "https://github.com/cweiske/jsonmapper/issues", + "source": "https://github.com/cweiske/jsonmapper/tree/master" + }, + "time": "2020-04-16T18:48:43+00:00" + }, + { + "name": "nikic/php-parser", + "version": "v4.10.4", + "source": { + "type": "git", + "url": "https://github.com/nikic/PHP-Parser.git", + "reference": "c6d052fc58cb876152f89f532b95a8d7907e7f0e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/c6d052fc58cb876152f89f532b95a8d7907e7f0e", + "reference": "c6d052fc58cb876152f89f532b95a8d7907e7f0e", + "shasum": "" + }, + "require": { + "ext-tokenizer": "*", + "php": ">=7.0" + }, + "require-dev": { + "ircmaxell/php-yacc": "^0.0.7", + "phpunit/phpunit": "^6.5 || ^7.0 || ^8.0 || ^9.0" + }, + "bin": [ + "bin/php-parse" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.9-dev" + } + }, + "autoload": { + "psr-4": { + "PhpParser\\": "lib/PhpParser" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Nikita Popov" + } + ], + "description": "A PHP parser written in PHP", + "keywords": [ + "parser", + "php" + ], + "support": { + "issues": "https://github.com/nikic/PHP-Parser/issues", + "source": "https://github.com/nikic/PHP-Parser/tree/v4.10.4" + }, + "time": "2020-12-20T10:01:03+00:00" + }, + { + "name": "openlss/lib-array2xml", + "version": "1.0.0", + "source": { + "type": "git", + "url": "https://github.com/nullivex/lib-array2xml.git", + "reference": "a91f18a8dfc69ffabe5f9b068bc39bb202c81d90" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nullivex/lib-array2xml/zipball/a91f18a8dfc69ffabe5f9b068bc39bb202c81d90", + "reference": "a91f18a8dfc69ffabe5f9b068bc39bb202c81d90", + "shasum": "" + }, + "require": { + "php": ">=5.3.2" + }, + "type": "library", + "autoload": { + "psr-0": { + "LSS": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "Bryan Tong", + "email": "bryan@nullivex.com", + "homepage": "https://www.nullivex.com" + }, + { + "name": "Tony Butler", + "email": "spudz76@gmail.com", + "homepage": "https://www.nullivex.com" + } + ], + "description": "Array2XML conversion library credit to lalit.org", + "homepage": "https://www.nullivex.com", + "keywords": [ + "array", + "array conversion", + "xml", + "xml conversion" + ], + "support": { + "issues": "https://github.com/nullivex/lib-array2xml/issues", + "source": "https://github.com/nullivex/lib-array2xml/tree/master" + }, + "time": "2019-03-29T20:06:56+00:00" + }, + { + "name": "phar-io/manifest", + "version": "2.0.1", + "source": { + "type": "git", + "url": "https://github.com/phar-io/manifest.git", + "reference": "85265efd3af7ba3ca4b2a2c34dbfc5788dd29133" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phar-io/manifest/zipball/85265efd3af7ba3ca4b2a2c34dbfc5788dd29133", + "reference": "85265efd3af7ba3ca4b2a2c34dbfc5788dd29133", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-phar": "*", + "ext-xmlwriter": "*", + "phar-io/version": "^3.0.1", + "php": "^7.2 || ^8.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" + }, + { + "name": "Sebastian Heuer", + "email": "sebastian@phpeople.de", + "role": "Developer" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "Developer" + } + ], + "description": "Component for reading phar.io manifest information from a PHP Archive (PHAR)", + "support": { + "issues": "https://github.com/phar-io/manifest/issues", + "source": "https://github.com/phar-io/manifest/tree/master" + }, + "time": "2020-06-27T14:33:11+00:00" + }, + { + "name": "phar-io/version", + "version": "3.1.0", + "source": { + "type": "git", + "url": "https://github.com/phar-io/version.git", + "reference": "bae7c545bef187884426f042434e561ab1ddb182" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phar-io/version/zipball/bae7c545bef187884426f042434e561ab1ddb182", + "reference": "bae7c545bef187884426f042434e561ab1ddb182", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" + }, + { + "name": "Sebastian Heuer", + "email": "sebastian@phpeople.de", + "role": "Developer" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "Developer" + } + ], + "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.1.0" + }, + "time": "2021-02-23T14:00:09+00:00" + }, + { + "name": "php-coveralls/php-coveralls", + "version": "v2.4.3", + "source": { + "type": "git", + "url": "https://github.com/php-coveralls/php-coveralls.git", + "reference": "909381bd40a17ae6e9076051f0d73293c1c091af" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-coveralls/php-coveralls/zipball/909381bd40a17ae6e9076051f0d73293c1c091af", + "reference": "909381bd40a17ae6e9076051f0d73293c1c091af", + "shasum": "" + }, + "require": { + "ext-json": "*", + "ext-simplexml": "*", + "guzzlehttp/guzzle": "^6.0 || ^7.0", + "php": "^5.5 || ^7.0 || ^8.0", + "psr/log": "^1.0", + "symfony/config": "^2.1 || ^3.0 || ^4.0 || ^5.0", + "symfony/console": "^2.1 || ^3.0 || ^4.0 || ^5.0", + "symfony/stopwatch": "^2.0 || ^3.0 || ^4.0 || ^5.0", + "symfony/yaml": "^2.0.5 || ^3.0 || ^4.0 || ^5.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.35 || ^5.4.3 || ^6.0 || ^7.0 || ^8.0 || ^9.0", + "sanmai/phpunit-legacy-adapter": "^6.1 || ^8.0" + }, + "suggest": { + "symfony/http-kernel": "Allows Symfony integration" + }, + "bin": [ + "bin/php-coveralls" + ], + "type": "library", + "autoload": { + "psr-4": { + "PhpCoveralls\\": "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", + "role": "Original creator" + }, + { + "name": "Takashi Matsuo", + "email": "tmatsuo@google.com" + }, + { + "name": "Google Inc" + }, + { + "name": "Dariusz Ruminski", + "email": "dariusz.ruminski@gmail.com", + "homepage": "https://github.com/keradus" + }, + { + "name": "Contributors", + "homepage": "https://github.com/php-coveralls/php-coveralls/graphs/contributors" + } + ], + "description": "PHP client library for Coveralls API", + "homepage": "https://github.com/php-coveralls/php-coveralls", + "keywords": [ + "ci", + "coverage", + "github", + "test" + ], + "support": { + "issues": "https://github.com/php-coveralls/php-coveralls/issues", + "source": "https://github.com/php-coveralls/php-coveralls/tree/v2.4.3" + }, + "time": "2020-12-24T09:17:03+00:00" + }, + { + "name": "phpdocumentor/reflection-common", + "version": "2.2.0", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/ReflectionCommon.git", + "reference": "1d01c49d4ed62f25aa84a747ad35d5a16924662b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionCommon/zipball/1d01c49d4ed62f25aa84a747ad35d5a16924662b", + "reference": "1d01c49d4ed62f25aa84a747ad35d5a16924662b", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-2.x": "2.x-dev" + } + }, + "autoload": { + "psr-4": { + "phpDocumentor\\Reflection\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jaap van Otterdijk", + "email": "opensource@ijaap.nl" + } + ], + "description": "Common reflection classes used by phpdocumentor to reflect the code structure", + "homepage": "http://www.phpdoc.org", + "keywords": [ + "FQSEN", + "phpDocumentor", + "phpdoc", + "reflection", + "static analysis" + ], + "support": { + "issues": "https://github.com/phpDocumentor/ReflectionCommon/issues", + "source": "https://github.com/phpDocumentor/ReflectionCommon/tree/2.x" + }, + "time": "2020-06-27T09:03:43+00:00" + }, + { + "name": "phpdocumentor/reflection-docblock", + "version": "5.2.2", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git", + "reference": "069a785b2141f5bcf49f3e353548dc1cce6df556" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/069a785b2141f5bcf49f3e353548dc1cce6df556", + "reference": "069a785b2141f5bcf49f3e353548dc1cce6df556", + "shasum": "" + }, + "require": { + "ext-filter": "*", + "php": "^7.2 || ^8.0", + "phpdocumentor/reflection-common": "^2.2", + "phpdocumentor/type-resolver": "^1.3", + "webmozart/assert": "^1.9.1" + }, + "require-dev": { + "mockery/mockery": "~1.3.2" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.x-dev" + } + }, + "autoload": { + "psr-4": { + "phpDocumentor\\Reflection\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mike van Riel", + "email": "me@mikevanriel.com" + }, + { + "name": "Jaap van Otterdijk", + "email": "account@ijaap.nl" + } + ], + "description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.", + "support": { + "issues": "https://github.com/phpDocumentor/ReflectionDocBlock/issues", + "source": "https://github.com/phpDocumentor/ReflectionDocBlock/tree/master" + }, + "time": "2020-09-03T19:13:55+00:00" + }, + { + "name": "phpdocumentor/type-resolver", + "version": "1.4.0", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/TypeResolver.git", + "reference": "6a467b8989322d92aa1c8bf2bebcc6e5c2ba55c0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/6a467b8989322d92aa1c8bf2bebcc6e5c2ba55c0", + "reference": "6a467b8989322d92aa1c8bf2bebcc6e5c2ba55c0", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0", + "phpdocumentor/reflection-common": "^2.0" + }, + "require-dev": { + "ext-tokenizer": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-1.x": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "phpDocumentor\\Reflection\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mike van Riel", + "email": "me@mikevanriel.com" + } + ], + "description": "A PSR-5 based resolver of Class names, Types and Structural Element Names", + "support": { + "issues": "https://github.com/phpDocumentor/TypeResolver/issues", + "source": "https://github.com/phpDocumentor/TypeResolver/tree/1.4.0" + }, + "time": "2020-09-17T18:55:26+00:00" + }, + { + "name": "phpspec/prophecy", + "version": "1.12.2", + "source": { + "type": "git", + "url": "https://github.com/phpspec/prophecy.git", + "reference": "245710e971a030f42e08f4912863805570f23d39" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpspec/prophecy/zipball/245710e971a030f42e08f4912863805570f23d39", + "reference": "245710e971a030f42e08f4912863805570f23d39", + "shasum": "" + }, + "require": { + "doctrine/instantiator": "^1.2", + "php": "^7.2 || ~8.0, <8.1", + "phpdocumentor/reflection-docblock": "^5.2", + "sebastian/comparator": "^3.0 || ^4.0", + "sebastian/recursion-context": "^3.0 || ^4.0" + }, + "require-dev": { + "phpspec/phpspec": "^6.0", + "phpunit/phpunit": "^8.0 || ^9.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.11.x-dev" + } + }, + "autoload": { + "psr-4": { + "Prophecy\\": "src/Prophecy" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Konstantin Kudryashov", + "email": "ever.zet@gmail.com", + "homepage": "http://everzet.com" + }, + { + "name": "Marcello Duarte", + "email": "marcello.duarte@gmail.com" + } + ], + "description": "Highly opinionated mocking framework for PHP 5.3+", + "homepage": "https://github.com/phpspec/prophecy", + "keywords": [ + "Double", + "Dummy", + "fake", + "mock", + "spy", + "stub" + ], + "support": { + "issues": "https://github.com/phpspec/prophecy/issues", + "source": "https://github.com/phpspec/prophecy/tree/1.12.2" + }, + "time": "2020-12-19T10:15:11+00:00" + }, + { + "name": "phpunit/php-code-coverage", + "version": "7.0.14", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-code-coverage.git", + "reference": "bb7c9a210c72e4709cdde67f8b7362f672f2225c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/bb7c9a210c72e4709cdde67f8b7362f672f2225c", + "reference": "bb7c9a210c72e4709cdde67f8b7362f672f2225c", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-xmlwriter": "*", + "php": ">=7.2", + "phpunit/php-file-iterator": "^2.0.2", + "phpunit/php-text-template": "^1.2.1", + "phpunit/php-token-stream": "^3.1.1 || ^4.0", + "sebastian/code-unit-reverse-lookup": "^1.0.1", + "sebastian/environment": "^4.2.2", + "sebastian/version": "^2.0.1", + "theseer/tokenizer": "^1.1.3" + }, + "require-dev": { + "phpunit/phpunit": "^8.2.2" + }, + "suggest": { + "ext-xdebug": "^2.7.2" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "7.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.", + "homepage": "https://github.com/sebastianbergmann/php-code-coverage", + "keywords": [ + "coverage", + "testing", + "xunit" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", + "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/7.0.14" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-12-02T13:39:03+00:00" + }, + { + "name": "phpunit/php-file-iterator", + "version": "2.0.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-file-iterator.git", + "reference": "4b49fb70f067272b659ef0174ff9ca40fdaa6357" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/4b49fb70f067272b659ef0174ff9ca40fdaa6357", + "reference": "4b49fb70f067272b659ef0174ff9ca40fdaa6357", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "require-dev": { + "phpunit/phpunit": "^8.5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "FilterIterator implementation that filters files based on a list of suffixes.", + "homepage": "https://github.com/sebastianbergmann/php-file-iterator/", + "keywords": [ + "filesystem", + "iterator" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-file-iterator/issues", + "source": "https://github.com/sebastianbergmann/php-file-iterator/tree/2.0.3" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-11-30T08:25:21+00:00" + }, + { + "name": "phpunit/php-text-template", + "version": "1.2.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-text-template.git", + "reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/31f8b717e51d9a2afca6c9f046f5d69fc27c8686", + "reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Simple template engine.", + "homepage": "https://github.com/sebastianbergmann/php-text-template/", + "keywords": [ + "template" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-text-template/issues", + "source": "https://github.com/sebastianbergmann/php-text-template/tree/1.2.1" + }, + "time": "2015-06-21T13:50:34+00:00" + }, + { + "name": "phpunit/php-timer", + "version": "2.1.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-timer.git", + "reference": "2454ae1765516d20c4ffe103d85a58a9a3bd5662" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/2454ae1765516d20c4ffe103d85a58a9a3bd5662", + "reference": "2454ae1765516d20c4ffe103d85a58a9a3bd5662", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "require-dev": { + "phpunit/phpunit": "^8.5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.1-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Utility class for timing", + "homepage": "https://github.com/sebastianbergmann/php-timer/", + "keywords": [ + "timer" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-timer/issues", + "source": "https://github.com/sebastianbergmann/php-timer/tree/2.1.3" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-11-30T08:20:02+00:00" + }, + { + "name": "phpunit/php-token-stream", + "version": "3.1.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-token-stream.git", + "reference": "472b687829041c24b25f475e14c2f38a09edf1c2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/472b687829041c24b25f475e14c2f38a09edf1c2", + "reference": "472b687829041c24b25f475e14c2f38a09edf1c2", + "shasum": "" + }, + "require": { + "ext-tokenizer": "*", + "php": ">=7.1" + }, + "require-dev": { + "phpunit/phpunit": "^7.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.1-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Wrapper around PHP's tokenizer extension.", + "homepage": "https://github.com/sebastianbergmann/php-token-stream/", + "keywords": [ + "tokenizer" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-token-stream/issues", + "source": "https://github.com/sebastianbergmann/php-token-stream/tree/3.1.2" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "abandoned": true, + "time": "2020-11-30T08:38:46+00:00" + }, + { + "name": "phpunit/phpunit", + "version": "8.5.14", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/phpunit.git", + "reference": "c25f79895d27b6ecd5abfa63de1606b786a461a3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/c25f79895d27b6ecd5abfa63de1606b786a461a3", + "reference": "c25f79895d27b6ecd5abfa63de1606b786a461a3", + "shasum": "" + }, + "require": { + "doctrine/instantiator": "^1.3.1", + "ext-dom": "*", + "ext-json": "*", + "ext-libxml": "*", + "ext-mbstring": "*", + "ext-xml": "*", + "ext-xmlwriter": "*", + "myclabs/deep-copy": "^1.10.0", + "phar-io/manifest": "^2.0.1", + "phar-io/version": "^3.0.2", + "php": ">=7.2", + "phpspec/prophecy": "^1.10.3", + "phpunit/php-code-coverage": "^7.0.12", + "phpunit/php-file-iterator": "^2.0.2", + "phpunit/php-text-template": "^1.2.1", + "phpunit/php-timer": "^2.1.2", + "sebastian/comparator": "^3.0.2", + "sebastian/diff": "^3.0.2", + "sebastian/environment": "^4.2.3", + "sebastian/exporter": "^3.1.2", + "sebastian/global-state": "^3.0.0", + "sebastian/object-enumerator": "^3.0.3", + "sebastian/resource-operations": "^2.0.1", + "sebastian/type": "^1.1.3", + "sebastian/version": "^2.0.1" + }, + "require-dev": { + "ext-pdo": "*" + }, + "suggest": { + "ext-soap": "*", + "ext-xdebug": "*", + "phpunit/php-invoker": "^2.0.0" + }, + "bin": [ + "phpunit" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "8.5-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "The PHP Unit Testing framework.", + "homepage": "https://phpunit.de/", + "keywords": [ + "phpunit", + "testing", + "xunit" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/phpunit/issues", + "source": "https://github.com/sebastianbergmann/phpunit/tree/8.5.14" + }, + "funding": [ + { + "url": "https://phpunit.de/donate.html", + "type": "custom" + }, + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2021-01-17T07:37:30+00:00" + }, + { + "name": "psr/container", + "version": "1.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/container.git", + "reference": "b7ce3b176482dbbc1245ebf52b181af44c2cf55f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/container/zipball/b7ce3b176482dbbc1245ebf52b181af44c2cf55f", + "reference": "b7ce3b176482dbbc1245ebf52b181af44c2cf55f", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Container\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common Container Interface (PHP FIG PSR-11)", + "homepage": "https://github.com/php-fig/container", + "keywords": [ + "PSR-11", + "container", + "container-interface", + "container-interop", + "psr" + ], + "support": { + "issues": "https://github.com/php-fig/container/issues", + "source": "https://github.com/php-fig/container/tree/master" + }, + "time": "2017-02-14T16:28:37+00:00" + }, + { + "name": "psr/http-client", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-client.git", + "reference": "2dfb5f6c5eff0e91e20e913f8c5452ed95b86621" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-client/zipball/2dfb5f6c5eff0e91e20e913f8c5452ed95b86621", + "reference": "2dfb5f6c5eff0e91e20e913f8c5452ed95b86621", + "shasum": "" + }, + "require": { + "php": "^7.0 || ^8.0", + "psr/http-message": "^1.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Client\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP clients", + "homepage": "https://github.com/php-fig/http-client", + "keywords": [ + "http", + "http-client", + "psr", + "psr-18" + ], + "support": { + "source": "https://github.com/php-fig/http-client/tree/master" + }, + "time": "2020-06-29T06:28:15+00:00" + }, + { + "name": "psr/http-message", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-message.git", + "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-message/zipball/f6561bf28d520154e4b0ec72be95418abe6d9363", + "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP messages", + "homepage": "https://github.com/php-fig/http-message", + "keywords": [ + "http", + "http-message", + "psr", + "psr-7", + "request", + "response" + ], + "support": { + "source": "https://github.com/php-fig/http-message/tree/master" + }, + "time": "2016-08-06T14:39:51+00:00" + }, + { + "name": "psr/log", + "version": "1.1.3", + "source": { + "type": "git", + "url": "https://github.com/php-fig/log.git", + "reference": "0f73288fd15629204f9d42b7055f72dacbe811fc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/log/zipball/0f73288fd15629204f9d42b7055f72dacbe811fc", + "reference": "0f73288fd15629204f9d42b7055f72dacbe811fc", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Log\\": "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", + "homepage": "https://github.com/php-fig/log", + "keywords": [ + "log", + "psr", + "psr-3" + ], + "support": { + "source": "https://github.com/php-fig/log/tree/1.1.3" + }, + "time": "2020-03-23T09:12:05+00:00" + }, + { + "name": "ralouphie/getallheaders", + "version": "3.0.3", + "source": { + "type": "git", + "url": "https://github.com/ralouphie/getallheaders.git", + "reference": "120b605dfeb996808c31b6477290a714d356e822" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/ralouphie/getallheaders/zipball/120b605dfeb996808c31b6477290a714d356e822", + "reference": "120b605dfeb996808c31b6477290a714d356e822", + "shasum": "" + }, + "require": { + "php": ">=5.6" + }, + "require-dev": { + "php-coveralls/php-coveralls": "^2.1", + "phpunit/phpunit": "^5 || ^6.5" + }, + "type": "library", + "autoload": { + "files": [ + "src/getallheaders.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ralph Khattar", + "email": "ralph.khattar@gmail.com" + } + ], + "description": "A polyfill for getallheaders.", + "support": { + "issues": "https://github.com/ralouphie/getallheaders/issues", + "source": "https://github.com/ralouphie/getallheaders/tree/develop" + }, + "time": "2019-03-08T08:55:37+00:00" + }, + { + "name": "sebastian/code-unit-reverse-lookup", + "version": "1.0.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/code-unit-reverse-lookup.git", + "reference": "1de8cd5c010cb153fcd68b8d0f64606f523f7619" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/1de8cd5c010cb153fcd68b8d0f64606f523f7619", + "reference": "1de8cd5c010cb153fcd68b8d0f64606f523f7619", + "shasum": "" + }, + "require": { + "php": ">=5.6" + }, + "require-dev": { + "phpunit/phpunit": "^8.5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Looks up which function or method a line of code belongs to", + "homepage": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/", + "support": { + "issues": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/issues", + "source": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/tree/1.0.2" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-11-30T08:15:22+00:00" + }, + { + "name": "sebastian/comparator", + "version": "3.0.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/comparator.git", + "reference": "1071dfcef776a57013124ff35e1fc41ccd294758" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/1071dfcef776a57013124ff35e1fc41ccd294758", + "reference": "1071dfcef776a57013124ff35e1fc41ccd294758", + "shasum": "" + }, + "require": { + "php": ">=7.1", + "sebastian/diff": "^3.0", + "sebastian/exporter": "^3.1" + }, + "require-dev": { + "phpunit/phpunit": "^8.5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Volker Dusch", + "email": "github@wallbash.com" + }, + { + "name": "Bernhard Schussek", + "email": "bschussek@2bepublished.at" + } + ], + "description": "Provides the functionality to compare PHP values for equality", + "homepage": "https://github.com/sebastianbergmann/comparator", + "keywords": [ + "comparator", + "compare", + "equality" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/comparator/issues", + "source": "https://github.com/sebastianbergmann/comparator/tree/3.0.3" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-11-30T08:04:30+00:00" + }, + { + "name": "sebastian/diff", + "version": "3.0.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/diff.git", + "reference": "14f72dd46eaf2f2293cbe79c93cc0bc43161a211" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/14f72dd46eaf2f2293cbe79c93cc0bc43161a211", + "reference": "14f72dd46eaf2f2293cbe79c93cc0bc43161a211", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "require-dev": { + "phpunit/phpunit": "^7.5 || ^8.0", + "symfony/process": "^2 || ^3.3 || ^4" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Kore Nordmann", + "email": "mail@kore-nordmann.de" + } + ], + "description": "Diff implementation", + "homepage": "https://github.com/sebastianbergmann/diff", + "keywords": [ + "diff", + "udiff", + "unidiff", + "unified diff" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/diff/issues", + "source": "https://github.com/sebastianbergmann/diff/tree/3.0.3" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-11-30T07:59:04+00:00" + }, + { + "name": "sebastian/environment", + "version": "4.2.4", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/environment.git", + "reference": "d47bbbad83711771f167c72d4e3f25f7fcc1f8b0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/d47bbbad83711771f167c72d4e3f25f7fcc1f8b0", + "reference": "d47bbbad83711771f167c72d4e3f25f7fcc1f8b0", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "require-dev": { + "phpunit/phpunit": "^7.5" + }, + "suggest": { + "ext-posix": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.2-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Provides functionality to handle HHVM/PHP environments", + "homepage": "http://www.github.com/sebastianbergmann/environment", + "keywords": [ + "Xdebug", + "environment", + "hhvm" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/environment/issues", + "source": "https://github.com/sebastianbergmann/environment/tree/4.2.4" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-11-30T07:53:42+00:00" + }, + { + "name": "sebastian/exporter", + "version": "3.1.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/exporter.git", + "reference": "6b853149eab67d4da22291d36f5b0631c0fd856e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/6b853149eab67d4da22291d36f5b0631c0fd856e", + "reference": "6b853149eab67d4da22291d36f5b0631c0fd856e", + "shasum": "" + }, + "require": { + "php": ">=7.0", + "sebastian/recursion-context": "^3.0" + }, + "require-dev": { + "ext-mbstring": "*", + "phpunit/phpunit": "^6.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.1.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Volker Dusch", + "email": "github@wallbash.com" + }, + { + "name": "Adam Harvey", + "email": "aharvey@php.net" + }, + { + "name": "Bernhard Schussek", + "email": "bschussek@gmail.com" + } + ], + "description": "Provides the functionality to export PHP variables for visualization", + "homepage": "http://www.github.com/sebastianbergmann/exporter", + "keywords": [ + "export", + "exporter" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/exporter/issues", + "source": "https://github.com/sebastianbergmann/exporter/tree/3.1.3" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-11-30T07:47:53+00:00" + }, + { + "name": "sebastian/global-state", + "version": "3.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/global-state.git", + "reference": "474fb9edb7ab891665d3bfc6317f42a0a150454b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/474fb9edb7ab891665d3bfc6317f42a0a150454b", + "reference": "474fb9edb7ab891665d3bfc6317f42a0a150454b", + "shasum": "" + }, + "require": { + "php": ">=7.2", + "sebastian/object-reflector": "^1.1.1", + "sebastian/recursion-context": "^3.0" + }, + "require-dev": { + "ext-dom": "*", + "phpunit/phpunit": "^8.0" + }, + "suggest": { + "ext-uopz": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Snapshotting of global state", + "homepage": "http://www.github.com/sebastianbergmann/global-state", + "keywords": [ + "global state" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/global-state/issues", + "source": "https://github.com/sebastianbergmann/global-state/tree/3.0.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-11-30T07:43:24+00:00" + }, + { + "name": "sebastian/object-enumerator", + "version": "3.0.4", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/object-enumerator.git", + "reference": "e67f6d32ebd0c749cf9d1dbd9f226c727043cdf2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/e67f6d32ebd0c749cf9d1dbd9f226c727043cdf2", + "reference": "e67f6d32ebd0c749cf9d1dbd9f226c727043cdf2", + "shasum": "" + }, + "require": { + "php": ">=7.0", + "sebastian/object-reflector": "^1.1.1", + "sebastian/recursion-context": "^3.0" + }, + "require-dev": { + "phpunit/phpunit": "^6.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Traverses array structures and object graphs to enumerate all referenced objects", + "homepage": "https://github.com/sebastianbergmann/object-enumerator/", + "support": { + "issues": "https://github.com/sebastianbergmann/object-enumerator/issues", + "source": "https://github.com/sebastianbergmann/object-enumerator/tree/3.0.4" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-11-30T07:40:27+00:00" + }, + { + "name": "sebastian/object-reflector", + "version": "1.1.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/object-reflector.git", + "reference": "9b8772b9cbd456ab45d4a598d2dd1a1bced6363d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/9b8772b9cbd456ab45d4a598d2dd1a1bced6363d", + "reference": "9b8772b9cbd456ab45d4a598d2dd1a1bced6363d", + "shasum": "" + }, + "require": { + "php": ">=7.0" + }, + "require-dev": { + "phpunit/phpunit": "^6.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.1-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Allows reflection of object attributes, including inherited and non-public ones", + "homepage": "https://github.com/sebastianbergmann/object-reflector/", + "support": { + "issues": "https://github.com/sebastianbergmann/object-reflector/issues", + "source": "https://github.com/sebastianbergmann/object-reflector/tree/1.1.2" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-11-30T07:37:18+00:00" + }, + { + "name": "sebastian/recursion-context", + "version": "3.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/recursion-context.git", + "reference": "367dcba38d6e1977be014dc4b22f47a484dac7fb" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/367dcba38d6e1977be014dc4b22f47a484dac7fb", + "reference": "367dcba38d6e1977be014dc4b22f47a484dac7fb", + "shasum": "" + }, + "require": { + "php": ">=7.0" + }, + "require-dev": { + "phpunit/phpunit": "^6.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Adam Harvey", + "email": "aharvey@php.net" + } + ], + "description": "Provides functionality to recursively process PHP variables", + "homepage": "http://www.github.com/sebastianbergmann/recursion-context", + "support": { + "issues": "https://github.com/sebastianbergmann/recursion-context/issues", + "source": "https://github.com/sebastianbergmann/recursion-context/tree/3.0.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-11-30T07:34:24+00:00" + }, + { + "name": "sebastian/resource-operations", + "version": "2.0.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/resource-operations.git", + "reference": "31d35ca87926450c44eae7e2611d45a7a65ea8b3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/resource-operations/zipball/31d35ca87926450c44eae7e2611d45a7a65ea8b3", + "reference": "31d35ca87926450c44eae7e2611d45a7a65ea8b3", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Provides a list of PHP built-in functions that operate on resources", + "homepage": "https://www.github.com/sebastianbergmann/resource-operations", + "support": { + "issues": "https://github.com/sebastianbergmann/resource-operations/issues", + "source": "https://github.com/sebastianbergmann/resource-operations/tree/2.0.2" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-11-30T07:30:19+00:00" + }, + { + "name": "sebastian/type", + "version": "1.1.4", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/type.git", + "reference": "0150cfbc4495ed2df3872fb31b26781e4e077eb4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/0150cfbc4495ed2df3872fb31b26781e4e077eb4", + "reference": "0150cfbc4495ed2df3872fb31b26781e4e077eb4", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "require-dev": { + "phpunit/phpunit": "^8.2" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.1-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Collection of value objects that represent the types of the PHP type system", + "homepage": "https://github.com/sebastianbergmann/type", + "support": { + "issues": "https://github.com/sebastianbergmann/type/issues", + "source": "https://github.com/sebastianbergmann/type/tree/1.1.4" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-11-30T07:25:11+00:00" + }, + { + "name": "sebastian/version", + "version": "2.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/version.git", + "reference": "99732be0ddb3361e16ad77b68ba41efc8e979019" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/99732be0ddb3361e16ad77b68ba41efc8e979019", + "reference": "99732be0ddb3361e16ad77b68ba41efc8e979019", + "shasum": "" + }, + "require": { + "php": ">=5.6" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library that helps with managing the version number of Git-hosted PHP projects", + "homepage": "https://github.com/sebastianbergmann/version", + "support": { + "issues": "https://github.com/sebastianbergmann/version/issues", + "source": "https://github.com/sebastianbergmann/version/tree/master" + }, + "time": "2016-10-03T07:35:21+00:00" + }, + { + "name": "symfony/config", + "version": "v5.2.3", + "source": { + "type": "git", + "url": "https://github.com/symfony/config.git", + "reference": "50e0e1314a3b2609d32b6a5a0d0fb5342494c4ab" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/config/zipball/50e0e1314a3b2609d32b6a5a0d0fb5342494c4ab", + "reference": "50e0e1314a3b2609d32b6a5a0d0fb5342494c4ab", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "symfony/deprecation-contracts": "^2.1", + "symfony/filesystem": "^4.4|^5.0", + "symfony/polyfill-ctype": "~1.8", + "symfony/polyfill-php80": "^1.15" + }, + "conflict": { + "symfony/finder": "<4.4" + }, + "require-dev": { + "symfony/event-dispatcher": "^4.4|^5.0", + "symfony/finder": "^4.4|^5.0", + "symfony/messenger": "^4.4|^5.0", + "symfony/service-contracts": "^1.1|^2", + "symfony/yaml": "^4.4|^5.0" + }, + "suggest": { + "symfony/yaml": "To use the yaml reference dumper" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Config\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "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.3" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2021-01-27T10:15:41+00:00" + }, + { + "name": "symfony/console", + "version": "v5.2.3", + "source": { + "type": "git", + "url": "https://github.com/symfony/console.git", + "reference": "89d4b176d12a2946a1ae4e34906a025b7b6b135a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/console/zipball/89d4b176d12a2946a1ae4e34906a025b7b6b135a", + "reference": "89d4b176d12a2946a1ae4e34906a025b7b6b135a", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "symfony/polyfill-mbstring": "~1.0", + "symfony/polyfill-php73": "^1.8", + "symfony/polyfill-php80": "^1.15", + "symfony/service-contracts": "^1.1|^2", + "symfony/string": "^5.1" + }, + "conflict": { + "symfony/dependency-injection": "<4.4", + "symfony/dotenv": "<5.1", + "symfony/event-dispatcher": "<4.4", + "symfony/lock": "<4.4", + "symfony/process": "<4.4" + }, + "provide": { + "psr/log-implementation": "1.0" + }, + "require-dev": { + "psr/log": "~1.0", + "symfony/config": "^4.4|^5.0", + "symfony/dependency-injection": "^4.4|^5.0", + "symfony/event-dispatcher": "^4.4|^5.0", + "symfony/lock": "^4.4|^5.0", + "symfony/process": "^4.4|^5.0", + "symfony/var-dumper": "^4.4|^5.0" + }, + "suggest": { + "psr/log": "For using the console logger", + "symfony/event-dispatcher": "", + "symfony/lock": "", + "symfony/process": "" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Console\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Eases the creation of beautiful and testable command line interfaces", + "homepage": "https://symfony.com", + "keywords": [ + "cli", + "command line", + "console", + "terminal" + ], + "support": { + "source": "https://github.com/symfony/console/tree/v5.2.3" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2021-01-28T22:06:19+00:00" + }, + { + "name": "symfony/deprecation-contracts", + "version": "v2.2.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/deprecation-contracts.git", + "reference": "5fa56b4074d1ae755beb55617ddafe6f5d78f665" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/5fa56b4074d1ae755beb55617ddafe6f5d78f665", + "reference": "5fa56b4074d1ae755beb55617ddafe6f5d78f665", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.2-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" + } + }, + "autoload": { + "files": [ + "function.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "A generic function and convention to trigger deprecation notices", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/deprecation-contracts/tree/master" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2020-09-07T11:33:47+00:00" + }, + { + "name": "symfony/filesystem", + "version": "v5.2.3", + "source": { + "type": "git", + "url": "https://github.com/symfony/filesystem.git", + "reference": "262d033b57c73e8b59cd6e68a45c528318b15038" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/filesystem/zipball/262d033b57c73e8b59cd6e68a45c528318b15038", + "reference": "262d033b57c73e8b59cd6e68a45c528318b15038", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "symfony/polyfill-ctype": "~1.8" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Filesystem\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides basic utilities for the filesystem", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/filesystem/tree/v5.2.3" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2021-01-27T10:01:46+00:00" + }, + { + "name": "symfony/polyfill-ctype", + "version": "v1.22.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-ctype.git", + "reference": "c6c942b1ac76c82448322025e084cadc56048b4e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/c6c942b1ac76c82448322025e084cadc56048b4e", + "reference": "c6c942b1ac76c82448322025e084cadc56048b4e", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "suggest": { + "ext-ctype": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.22-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Ctype\\": "" + }, + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Gert de Pagter", + "email": "BackEndTea@gmail.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for ctype functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "ctype", + "polyfill", + "portable" + ], + "support": { + "source": "https://github.com/symfony/polyfill-ctype/tree/v1.22.1" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2021-01-07T16:49:33+00:00" + }, + { + "name": "symfony/polyfill-intl-grapheme", + "version": "v1.22.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-intl-grapheme.git", + "reference": "5601e09b69f26c1828b13b6bb87cb07cddba3170" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/5601e09b69f26c1828b13b6bb87cb07cddba3170", + "reference": "5601e09b69f26c1828b13b6bb87cb07cddba3170", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "suggest": { + "ext-intl": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.22-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Intl\\Grapheme\\": "" + }, + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for intl's grapheme_* functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "grapheme", + "intl", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.22.1" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2021-01-22T09:19:47+00:00" + }, + { + "name": "symfony/polyfill-mbstring", + "version": "v1.22.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "5232de97ee3b75b0360528dae24e73db49566ab1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/5232de97ee3b75b0360528dae24e73db49566ab1", + "reference": "5232de97ee3b75b0360528dae24e73db49566ab1", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "suggest": { + "ext-mbstring": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.22-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Mbstring\\": "" + }, + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for the Mbstring extension", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "mbstring", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.22.1" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2021-01-22T09:19:47+00:00" + }, + { + "name": "symfony/polyfill-php73", + "version": "v1.22.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php73.git", + "reference": "a678b42e92f86eca04b7fa4c0f6f19d097fb69e2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php73/zipball/a678b42e92f86eca04b7fa4c0f6f19d097fb69e2", + "reference": "a678b42e92f86eca04b7fa4c0f6f19d097fb69e2", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.22-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Php73\\": "" + }, + "files": [ + "bootstrap.php" + ], + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 7.3+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php73/tree/v1.22.1" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2021-01-07T16:49:33+00:00" + }, + { + "name": "symfony/polyfill-php80", + "version": "v1.22.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php80.git", + "reference": "dc3063ba22c2a1fd2f45ed856374d79114998f91" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/dc3063ba22c2a1fd2f45ed856374d79114998f91", + "reference": "dc3063ba22c2a1fd2f45ed856374d79114998f91", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.22-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Php80\\": "" + }, + "files": [ + "bootstrap.php" + ], + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ion Bazan", + "email": "ion.bazan@gmail.com" + }, + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 8.0+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php80/tree/v1.22.1" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2021-01-07T16:49:33+00:00" + }, + { + "name": "symfony/service-contracts", + "version": "v2.2.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/service-contracts.git", + "reference": "d15da7ba4957ffb8f1747218be9e1a121fd298a1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/service-contracts/zipball/d15da7ba4957ffb8f1747218be9e1a121fd298a1", + "reference": "d15da7ba4957ffb8f1747218be9e1a121fd298a1", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "psr/container": "^1.0" + }, + "suggest": { + "symfony/service-implementation": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.2-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Contracts\\Service\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Generic abstractions related to writing services", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "support": { + "source": "https://github.com/symfony/service-contracts/tree/master" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2020-09-07T11:33:47+00:00" + }, + { + "name": "symfony/stopwatch", + "version": "v5.2.3", + "source": { + "type": "git", + "url": "https://github.com/symfony/stopwatch.git", + "reference": "b12274acfab9d9850c52583d136a24398cdf1a0c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/stopwatch/zipball/b12274acfab9d9850c52583d136a24398cdf1a0c", + "reference": "b12274acfab9d9850c52583d136a24398cdf1a0c", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "symfony/service-contracts": "^1.0|^2" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Stopwatch\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides a way to profile code", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/stopwatch/tree/v5.2.3" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2021-01-27T10:15:41+00:00" + }, + { + "name": "symfony/string", + "version": "v5.2.3", + "source": { + "type": "git", + "url": "https://github.com/symfony/string.git", + "reference": "c95468897f408dd0aca2ff582074423dd0455122" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/string/zipball/c95468897f408dd0aca2ff582074423dd0455122", + "reference": "c95468897f408dd0aca2ff582074423dd0455122", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "symfony/polyfill-ctype": "~1.8", + "symfony/polyfill-intl-grapheme": "~1.0", + "symfony/polyfill-intl-normalizer": "~1.0", + "symfony/polyfill-mbstring": "~1.0", + "symfony/polyfill-php80": "~1.15" + }, + "require-dev": { + "symfony/error-handler": "^4.4|^5.0", + "symfony/http-client": "^4.4|^5.0", + "symfony/translation-contracts": "^1.1|^2", + "symfony/var-exporter": "^4.4|^5.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\String\\": "" + }, + "files": [ + "Resources/functions.php" + ], + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "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", + "i18n", + "string", + "unicode", + "utf-8", + "utf8" + ], + "support": { + "source": "https://github.com/symfony/string/tree/v5.2.3" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2021-01-25T15:14:59+00:00" + }, + { + "name": "symfony/yaml", + "version": "v5.2.3", + "source": { + "type": "git", + "url": "https://github.com/symfony/yaml.git", + "reference": "338cddc6d74929f6adf19ca5682ac4b8e109cdb0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/yaml/zipball/338cddc6d74929f6adf19ca5682ac4b8e109cdb0", + "reference": "338cddc6d74929f6adf19ca5682ac4b8e109cdb0", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "symfony/deprecation-contracts": "^2.1", + "symfony/polyfill-ctype": "~1.8" + }, + "conflict": { + "symfony/console": "<4.4" + }, + "require-dev": { + "symfony/console": "^4.4|^5.0" + }, + "suggest": { + "symfony/console": "For validating YAML files using the lint command" + }, + "bin": [ + "Resources/bin/yaml-lint" + ], + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Yaml\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Loads and dumps YAML files", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/yaml/tree/v5.2.3" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2021-02-03T04:42:09+00:00" + }, + { + "name": "theseer/tokenizer", + "version": "1.2.0", + "source": { + "type": "git", + "url": "https://github.com/theseer/tokenizer.git", + "reference": "75a63c33a8577608444246075ea0af0d052e452a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/theseer/tokenizer/zipball/75a63c33a8577608444246075ea0af0d052e452a", + "reference": "75a63c33a8577608444246075ea0af0d052e452a", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-tokenizer": "*", + "ext-xmlwriter": "*", + "php": "^7.2 || ^8.0" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" + } + ], + "description": "A small library for converting tokenized PHP source code into XML and potentially other formats", + "support": { + "issues": "https://github.com/theseer/tokenizer/issues", + "source": "https://github.com/theseer/tokenizer/tree/master" + }, + "funding": [ + { + "url": "https://github.com/theseer", + "type": "github" + } + ], + "time": "2020-07-12T23:59:07+00:00" + }, + { + "name": "vimeo/psalm", + "version": "4.6.2", + "source": { + "type": "git", + "url": "https://github.com/vimeo/psalm.git", + "reference": "bca09d74adc704c4eaee36a3c3e9d379e290fc3b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/vimeo/psalm/zipball/bca09d74adc704c4eaee36a3c3e9d379e290fc3b", + "reference": "bca09d74adc704c4eaee36a3c3e9d379e290fc3b", + "shasum": "" + }, + "require": { + "amphp/amp": "^2.1", + "amphp/byte-stream": "^1.5", + "composer/package-versions-deprecated": "^1.8.0", + "composer/semver": "^1.4 || ^2.0 || ^3.0", + "composer/xdebug-handler": "^1.1", + "dnoegel/php-xdg-base-dir": "^0.1.1", + "ext-dom": "*", + "ext-json": "*", + "ext-libxml": "*", + "ext-mbstring": "*", + "ext-simplexml": "*", + "ext-tokenizer": "*", + "felixfbecker/advanced-json-rpc": "^3.0.3", + "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", + "sebastian/diff": "^3.0 || ^4.0", + "symfony/console": "^3.4.17 || ^4.1.6 || ^5.0", + "webmozart/path-util": "^2.3" + }, + "provide": { + "psalm/psalm": "self.version" + }, + "require-dev": { + "amphp/amp": "^2.4.2", + "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", + "phpunit/phpunit": "^9.0", + "psalm/plugin-phpunit": "^0.13", + "slevomat/coding-standard": "^6.3.11", + "squizlabs/php_codesniffer": "^3.5", + "symfony/process": "^4.3", + "weirdan/prophecy-shim": "^1.0 || ^2.0" + }, + "suggest": { + "ext-igbinary": "^2.0.5" + }, + "bin": [ + "psalm", + "psalm-language-server", + "psalm-plugin", + "psalm-refactor", + "psalter" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.x-dev", + "dev-3.x": "3.x-dev", + "dev-2.x": "2.x-dev", + "dev-1.x": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psalm\\": "src/Psalm/" + }, + "files": [ + "src/functions.php", + "src/spl_object_id.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Matthew Brown" + } + ], + "description": "A static analysis tool for finding errors in PHP applications", + "keywords": [ + "code", + "inspection", + "php" + ], + "support": { + "issues": "https://github.com/vimeo/psalm/issues", + "source": "https://github.com/vimeo/psalm/tree/4.6.2" + }, + "time": "2021-02-26T02:24:18+00:00" + }, + { + "name": "webmozart/assert", + "version": "1.9.1", + "source": { + "type": "git", + "url": "https://github.com/webmozarts/assert.git", + "reference": "bafc69caeb4d49c39fd0779086c03a3738cbb389" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/webmozarts/assert/zipball/bafc69caeb4d49c39fd0779086c03a3738cbb389", + "reference": "bafc69caeb4d49c39fd0779086c03a3738cbb389", + "shasum": "" + }, + "require": { + "php": "^5.3.3 || ^7.0 || ^8.0", + "symfony/polyfill-ctype": "^1.8" + }, + "conflict": { + "phpstan/phpstan": "<0.12.20", + "vimeo/psalm": "<3.9.1" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.36 || ^7.5.13" + }, + "type": "library", + "autoload": { + "psr-4": { + "Webmozart\\Assert\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Bernhard Schussek", + "email": "bschussek@gmail.com" + } + ], + "description": "Assertions to validate method input/output with nice error messages.", + "keywords": [ + "assert", + "check", + "validate" + ], + "support": { + "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" + }, + { + "name": "webmozart/path-util", + "version": "2.3.0", + "source": { + "type": "git", + "url": "https://github.com/webmozart/path-util.git", + "reference": "d939f7edc24c9a1bb9c0dee5cb05d8e859490725" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/webmozart/path-util/zipball/d939f7edc24c9a1bb9c0dee5cb05d8e859490725", + "reference": "d939f7edc24c9a1bb9c0dee5cb05d8e859490725", + "shasum": "" + }, + "require": { + "php": ">=5.3.3", + "webmozart/assert": "~1.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.6", + "sebastian/version": "^1.0.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.3-dev" + } + }, + "autoload": { + "psr-4": { + "Webmozart\\PathUtil\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Bernhard Schussek", + "email": "bschussek@gmail.com" + } + ], + "description": "A robust cross-platform utility for normalizing, comparing and modifying file paths.", + "support": { + "issues": "https://github.com/webmozart/path-util/issues", + "source": "https://github.com/webmozart/path-util/tree/2.3.0" + }, + "time": "2015-12-17T08:42:14+00:00" + } + ], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": [], + "prefer-stable": false, + "prefer-lowest": false, + "platform": { + "php": ">=7.2" + }, + "platform-dev": [], + "plugin-api-version": "2.0.0" +} diff --git a/egulias/email-validator/src/EmailLexer.php b/egulias/email-validator/src/EmailLexer.php index 59dcd587..41e9ea94 100644 --- a/egulias/email-validator/src/EmailLexer.php +++ b/egulias/email-validator/src/EmailLexer.php @@ -7,37 +7,52 @@ use Doctrine\Common\Lexer\AbstractLexer; class EmailLexer extends AbstractLexer { //ASCII values - const C_DEL = 127; + const S_EMPTY = null; const C_NUL = 0; - const S_AT = 64; - const S_BACKSLASH = 92; - const S_DOT = 46; + const S_HTAB = 9; + const S_LF = 10; + const S_CR = 13; + const S_SP = 32; + const EXCLAMATION = 33; const S_DQUOTE = 34; + const NUMBER_SIGN = 35; + const DOLLAR = 36; + const PERCENTAGE = 37; + const AMPERSAND = 38; const S_SQUOTE = 39; + const S_OPENPARENTHESIS = 40; + const S_CLOSEPARENTHESIS = 41; + const ASTERISK = 42; + const S_PLUS = 43; + const S_COMMA = 44; + const S_HYPHEN = 45; + const S_DOT = 46; + const S_SLASH = 47; + const S_COLON = 58; + const S_SEMICOLON = 59; + const S_LOWERTHAN = 60; + const S_EQUAL = 61; + const S_GREATERTHAN = 62; + const QUESTIONMARK = 63; + const S_AT = 64; + const S_OPENBRACKET = 91; + const S_BACKSLASH = 92; + const S_CLOSEBRACKET = 93; + const CARET = 94; + const S_UNDERSCORE = 95; const S_BACKTICK = 96; - const S_OPENPARENTHESIS = 49; - const S_CLOSEPARENTHESIS = 261; - const S_OPENBRACKET = 262; - const S_CLOSEBRACKET = 263; - const S_HYPHEN = 264; - const S_COLON = 265; - const S_DOUBLECOLON = 266; - const S_SP = 267; - const S_HTAB = 268; - const S_CR = 269; - const S_LF = 270; - const S_IPV6TAG = 271; - const S_LOWERTHAN = 272; - const S_GREATERTHAN = 273; - const S_COMMA = 274; - const S_SEMICOLON = 275; - const S_OPENQBRACKET = 276; - const S_CLOSEQBRACKET = 277; - const S_SLASH = 278; - const S_EMPTY = null; + const S_OPENCURLYBRACES = 123; + const S_PIPE = 124; + const S_CLOSECURLYBRACES = 125; + const S_TILDE = 126; + const C_DEL = 127; + const INVERT_QUESTIONMARK= 168; + const INVERT_EXCLAMATION = 173; const GENERIC = 300; - const CRLF = 301; + const S_IPV6TAG = 301; const INVALID = 302; + const CRLF = 1310; + const S_DOUBLECOLON = 5858; const ASCII_INVALID_FROM = 127; const ASCII_INVALID_TO = 199; @@ -47,6 +62,8 @@ class EmailLexer extends AbstractLexer * @var array */ protected $charValue = array( + '{' => self::S_OPENCURLYBRACES, + '}' => self::S_CLOSECURLYBRACES, '(' => self::S_OPENPARENTHESIS, ')' => self::S_CLOSEPARENTHESIS, '<' => self::S_LOWERTHAN, @@ -71,10 +88,23 @@ class EmailLexer extends AbstractLexer "\n" => self::S_LF, "\r\n" => self::CRLF, 'IPv6' => self::S_IPV6TAG, - '{' => self::S_OPENQBRACKET, - '}' => self::S_CLOSEQBRACKET, '' => self::S_EMPTY, '\0' => self::C_NUL, + '*' => self::ASTERISK, + '!' => self::EXCLAMATION, + '&' => self::AMPERSAND, + '^' => self::CARET, + '$' => self::DOLLAR, + '%' => self::PERCENTAGE, + '~' => self::S_TILDE, + '|' => self::S_PIPE, + '_' => self::S_UNDERSCORE, + '=' => self::S_EQUAL, + '+' => self::S_PLUS, + '¿' => self::INVERT_QUESTIONMARK, + '?' => self::QUESTIONMARK, + '#' => self::NUMBER_SIGN, + '¡' => self::INVERT_EXCLAMATION, ); /** @@ -94,7 +124,9 @@ class EmailLexer extends AbstractLexer * * @var array * + * @psalm-suppress NonInvariantDocblockPropertyType * @psalm-var array{value:string, type:null|int, position:int} + * @psalm-suppress NonInvariantDocblockPropertyType */ public $token; @@ -114,6 +146,16 @@ class EmailLexer extends AbstractLexer 'position' => 0, ]; + /** + * @var string + */ + private $accumulator = ''; + + /** + * @var bool + */ + private $hasToRecord = false; + public function __construct() { $this->previous = $this->token = self::$nullToken; @@ -173,10 +215,18 @@ class EmailLexer extends AbstractLexer */ public function moveNext() { + if ($this->hasToRecord && $this->previous === self::$nullToken) { + $this->accumulator .= $this->token['value']; + } + $this->previous = $this->token; $hasNext = parent::moveNext(); $this->token = $this->token ?: self::$nullToken; + if ($this->hasToRecord) { + $this->accumulator .= $this->token['value']; + } + return $hasNext; } @@ -188,7 +238,7 @@ class EmailLexer extends AbstractLexer protected function getCatchablePatterns() { return array( - '[a-zA-Z_]+[46]?', //ASCII and domain literal + '[a-zA-Z]+[46]?', //ASCII and domain literal '[^\x00-\x7F]', //UTF-8 '[0-9]+', '\r\n', @@ -205,7 +255,9 @@ class EmailLexer extends AbstractLexer */ protected function getNonCatchablePatterns() { - return array('[\xA0-\xff]+'); + return [ + '[\xA0-\xff]+', + ]; } /** @@ -217,28 +269,38 @@ class EmailLexer extends AbstractLexer */ protected function getType(&$value) { - if ($this->isNullType($value)) { - return self::C_NUL; + $encoded = $value; + + if (mb_detect_encoding($value, 'auto', true) !== 'UTF-8') { + $encoded = utf8_encode($value); + } + + if ($this->isValid($encoded)) { + return $this->charValue[$encoded]; } - if ($this->isValid($value)) { - return $this->charValue[$value]; + if ($this->isNullType($encoded)) { + return self::C_NUL; } - if ($this->isUTF8Invalid($value)) { + if ($this->isInvalidChar($encoded)) { $this->hasInvalidTokens = true; return self::INVALID; } + return self::GENERIC; } - /** - * @param string $value - * - * @return bool - */ - protected function isValid($value) + protected function isInvalidChar(string $value) : bool + { + if(preg_match("/[^\p{S}\p{C}\p{Cc}]+/iu", $value) ) { + return false; + } + return true; + } + + protected function isValid(string $value) : bool { if (isset($this->charValue[$value])) { return true; @@ -260,11 +322,7 @@ class EmailLexer extends AbstractLexer return false; } - /** - * @param string $value - * @return bool - */ - protected function isUTF8Invalid($value) + protected function isUTF8Invalid(string $value) : bool { if (preg_match('/\p{Cc}+/u', $value)) { return true; @@ -280,4 +338,24 @@ class EmailLexer extends AbstractLexer { return 'iu'; } + + public function getAccumulatedValues() : string + { + return $this->accumulator; + } + + public function startRecording() : void + { + $this->hasToRecord = true; + } + + public function stopRecording() : void + { + $this->hasToRecord = false; + } + + public function clearRecorded() : void + { + $this->accumulator = ''; + } } diff --git a/egulias/email-validator/src/EmailParser.php b/egulias/email-validator/src/EmailParser.php index 6b7bad66..c78f74a9 100644 --- a/egulias/email-validator/src/EmailParser.php +++ b/egulias/email-validator/src/EmailParser.php @@ -2,27 +2,20 @@ namespace Egulias\EmailValidator; -use Egulias\EmailValidator\Exception\ExpectingATEXT; -use Egulias\EmailValidator\Exception\NoLocalPart; -use Egulias\EmailValidator\Parser\DomainPart; +use Egulias\EmailValidator\EmailLexer; +use Egulias\EmailValidator\Result\Result; use Egulias\EmailValidator\Parser\LocalPart; +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\NoLocalPart; -/** - * EmailParser - * - * @author Eduardo Gulias Davis <me@egulias.com> - */ -class EmailParser +class EmailParser extends Parser { const EMAIL_MAX_LENGTH = 254; /** - * @var array - */ - protected $warnings = []; - - /** * @var string */ protected $domainPart = ''; @@ -31,104 +24,65 @@ class EmailParser * @var string */ protected $localPart = ''; - /** - * @var EmailLexer - */ - protected $lexer; - /** - * @var LocalPart - */ - protected $localPartParser; + public function parse(string $str) : Result + { + $result = parent::parse($str); - /** - * @var DomainPart - */ - protected $domainPartParser; + $this->addLongEmailWarning($this->localPart, $this->domainPart); - public function __construct(EmailLexer $lexer) - { - $this->lexer = $lexer; - $this->localPartParser = new LocalPart($this->lexer); - $this->domainPartParser = new DomainPart($this->lexer); + return $result; } - - /** - * @param string $str - * @return array - */ - public function parse($str) + + protected function preLeftParsing(): Result { - $this->lexer->setInput($str); - if (!$this->hasAtToken()) { - throw new NoLocalPart(); - } - - - $this->localPartParser->parse($str); - $this->domainPartParser->parse($str); - - $this->setParts($str); - - if ($this->lexer->hasInvalidTokens()) { - throw new ExpectingATEXT(); + return new InvalidEmail(new NoLocalPart(), $this->lexer->token["value"]); } + return new ValidEmail(); + } - return array('local' => $this->localPart, 'domain' => $this->domainPart); + protected function parseLeftFromAt(): Result + { + return $this->processLocalPart(); } - /** - * @return Warning\Warning[] - */ - public function getWarnings() + protected function parseRightFromAt(): Result { - $localPartWarnings = $this->localPartParser->getWarnings(); - $domainPartWarnings = $this->domainPartParser->getWarnings(); - $this->warnings = array_merge($localPartWarnings, $domainPartWarnings); + return $this->processDomainPart(); + } - $this->addLongEmailWarning($this->localPart, $this->domainPart); + private function processLocalPart() : Result + { + $localPartParser = new LocalPart($this->lexer); + $localPartResult = $localPartParser->parse(); + $this->localPart = $localPartParser->localPart(); + $this->warnings = array_merge($localPartParser->getWarnings(), $this->warnings); - return $this->warnings; + return $localPartResult; } - /** - * @return string - */ - public function getParsedDomainPart() + private function processDomainPart() : Result { - return $this->domainPart; + $domainPartParser = new DomainPart($this->lexer); + $domainPartResult = $domainPartParser->parse(); + $this->domainPart = $domainPartParser->domainPart(); + $this->warnings = array_merge($domainPartParser->getWarnings(), $this->warnings); + + return $domainPartResult; } - /** - * @param string $email - */ - protected function setParts($email) + public function getDomainPart() : string { - $parts = explode('@', $email); - $this->domainPart = $this->domainPartParser->getDomainPart(); - $this->localPart = $parts[0]; + return $this->domainPart; } - /** - * @return bool - */ - protected function hasAtToken() + public function getLocalPart() : string { - $this->lexer->moveNext(); - $this->lexer->moveNext(); - if ($this->lexer->token['type'] === EmailLexer::S_AT) { - return false; - } - - return true; + return $this->localPart; } - /** - * @param string $localPart - * @param string $parsedDomainPart - */ - protected function addLongEmailWarning($localPart, $parsedDomainPart) + 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/egulias/email-validator/src/EmailValidator.php b/egulias/email-validator/src/EmailValidator.php index a30f21dc..5a2e5c82 100644 --- a/egulias/email-validator/src/EmailValidator.php +++ b/egulias/email-validator/src/EmailValidator.php @@ -2,7 +2,7 @@ namespace Egulias\EmailValidator; -use Egulias\EmailValidator\Exception\InvalidEmail; +use Egulias\EmailValidator\Result\InvalidEmail; use Egulias\EmailValidator\Validation\EmailValidation; class EmailValidator @@ -15,12 +15,12 @@ class EmailValidator /** * @var Warning\Warning[] */ - protected $warnings = []; + private $warnings = []; /** - * @var InvalidEmail|null + * @var ?InvalidEmail */ - protected $error; + private $error; public function __construct() { @@ -32,7 +32,7 @@ class EmailValidator * @param EmailValidation $emailValidation * @return bool */ - public function isValid($email, EmailValidation $emailValidation) + public function isValid(string $email, EmailValidation $emailValidation) { $isValid = $emailValidation->isValid($email, $this->lexer); $this->warnings = $emailValidation->getWarnings(); diff --git a/egulias/email-validator/src/Exception/AtextAfterCFWS.php b/egulias/email-validator/src/Exception/AtextAfterCFWS.php deleted file mode 100644 index 97f41a2c..00000000 --- a/egulias/email-validator/src/Exception/AtextAfterCFWS.php +++ /dev/null @@ -1,9 +0,0 @@ -<?php - -namespace Egulias\EmailValidator\Exception; - -class AtextAfterCFWS extends InvalidEmail -{ - const CODE = 133; - const REASON = "ATEXT found after CFWS"; -} diff --git a/egulias/email-validator/src/Exception/CRLFAtTheEnd.php b/egulias/email-validator/src/Exception/CRLFAtTheEnd.php deleted file mode 100644 index ec23bc71..00000000 --- a/egulias/email-validator/src/Exception/CRLFAtTheEnd.php +++ /dev/null @@ -1,9 +0,0 @@ -<?php - -namespace Egulias\EmailValidator\Exception; - -class CRLFAtTheEnd extends InvalidEmail -{ - const CODE = 149; - const REASON = "CRLF at the end"; -} diff --git a/egulias/email-validator/src/Exception/CRLFX2.php b/egulias/email-validator/src/Exception/CRLFX2.php deleted file mode 100644 index 6bd377ee..00000000 --- a/egulias/email-validator/src/Exception/CRLFX2.php +++ /dev/null @@ -1,9 +0,0 @@ -<?php - -namespace Egulias\EmailValidator\Exception; - -class CRLFX2 extends InvalidEmail -{ - const CODE = 148; - const REASON = "Folding whitespace CR LF found twice"; -} diff --git a/egulias/email-validator/src/Exception/CRNoLF.php b/egulias/email-validator/src/Exception/CRNoLF.php deleted file mode 100644 index 9c9f7394..00000000 --- a/egulias/email-validator/src/Exception/CRNoLF.php +++ /dev/null @@ -1,9 +0,0 @@ -<?php - -namespace Egulias\EmailValidator\Exception; - -class CRNoLF extends InvalidEmail -{ - const CODE = 150; - const REASON = "Missing LF after CR"; -} diff --git a/egulias/email-validator/src/Exception/CharNotAllowed.php b/egulias/email-validator/src/Exception/CharNotAllowed.php deleted file mode 100644 index ea20ce59..00000000 --- a/egulias/email-validator/src/Exception/CharNotAllowed.php +++ /dev/null @@ -1,9 +0,0 @@ -<?php - -namespace Egulias\EmailValidator\Exception; - -class CharNotAllowed extends InvalidEmail -{ - const CODE = 201; - const REASON = "Non allowed character in domain"; -} diff --git a/egulias/email-validator/src/Exception/CommaInDomain.php b/egulias/email-validator/src/Exception/CommaInDomain.php deleted file mode 100644 index e9245d96..00000000 --- a/egulias/email-validator/src/Exception/CommaInDomain.php +++ /dev/null @@ -1,9 +0,0 @@ -<?php - -namespace Egulias\EmailValidator\Exception; - -class CommaInDomain extends InvalidEmail -{ - const CODE = 200; - const REASON = "Comma ',' is not allowed in domain part"; -} diff --git a/egulias/email-validator/src/Exception/ConsecutiveAt.php b/egulias/email-validator/src/Exception/ConsecutiveAt.php deleted file mode 100644 index 165ff57a..00000000 --- a/egulias/email-validator/src/Exception/ConsecutiveAt.php +++ /dev/null @@ -1,9 +0,0 @@ -<?php - -namespace Egulias\EmailValidator\Exception; - -class ConsecutiveAt extends InvalidEmail -{ - const CODE = 128; - const REASON = "Consecutive AT"; -} diff --git a/egulias/email-validator/src/Exception/ConsecutiveDot.php b/egulias/email-validator/src/Exception/ConsecutiveDot.php deleted file mode 100644 index 949af3b5..00000000 --- a/egulias/email-validator/src/Exception/ConsecutiveDot.php +++ /dev/null @@ -1,9 +0,0 @@ -<?php - -namespace Egulias\EmailValidator\Exception; - -class ConsecutiveDot extends InvalidEmail -{ - const CODE = 132; - const REASON = "Consecutive DOT"; -} diff --git a/egulias/email-validator/src/Exception/DomainAcceptsNoMail.php b/egulias/email-validator/src/Exception/DomainAcceptsNoMail.php deleted file mode 100644 index 40a99705..00000000 --- a/egulias/email-validator/src/Exception/DomainAcceptsNoMail.php +++ /dev/null @@ -1,9 +0,0 @@ -<?php - -namespace Egulias\EmailValidator\Exception; - -class DomainAcceptsNoMail extends InvalidEmail -{ - const CODE = 154; - const REASON = 'Domain accepts no mail (Null MX, RFC7505)'; -}
\ No newline at end of file diff --git a/egulias/email-validator/src/Exception/DomainHyphened.php b/egulias/email-validator/src/Exception/DomainHyphened.php deleted file mode 100644 index 6f586860..00000000 --- a/egulias/email-validator/src/Exception/DomainHyphened.php +++ /dev/null @@ -1,9 +0,0 @@ -<?php - -namespace Egulias\EmailValidator\Exception; - -class DomainHyphened extends InvalidEmail -{ - const CODE = 144; - const REASON = "Hyphen found in domain"; -} diff --git a/egulias/email-validator/src/Exception/DotAtEnd.php b/egulias/email-validator/src/Exception/DotAtEnd.php deleted file mode 100644 index 05ade77d..00000000 --- a/egulias/email-validator/src/Exception/DotAtEnd.php +++ /dev/null @@ -1,9 +0,0 @@ -<?php - -namespace Egulias\EmailValidator\Exception; - -class DotAtEnd extends InvalidEmail -{ - const CODE = 142; - const REASON = "Dot at the end"; -} diff --git a/egulias/email-validator/src/Exception/DotAtStart.php b/egulias/email-validator/src/Exception/DotAtStart.php deleted file mode 100644 index 7772df7f..00000000 --- a/egulias/email-validator/src/Exception/DotAtStart.php +++ /dev/null @@ -1,9 +0,0 @@ -<?php - -namespace Egulias\EmailValidator\Exception; - -class DotAtStart extends InvalidEmail -{ - const CODE = 141; - const REASON = "Found DOT at start"; -} diff --git a/egulias/email-validator/src/Exception/ExpectingAT.php b/egulias/email-validator/src/Exception/ExpectingAT.php deleted file mode 100644 index 36d633c1..00000000 --- a/egulias/email-validator/src/Exception/ExpectingAT.php +++ /dev/null @@ -1,9 +0,0 @@ -<?php - -namespace Egulias\EmailValidator\Exception; - -class ExpectingAT extends InvalidEmail -{ - const CODE = 202; - const REASON = "Expecting AT '@' "; -} diff --git a/egulias/email-validator/src/Exception/ExpectingATEXT.php b/egulias/email-validator/src/Exception/ExpectingATEXT.php deleted file mode 100644 index 095d9db7..00000000 --- a/egulias/email-validator/src/Exception/ExpectingATEXT.php +++ /dev/null @@ -1,9 +0,0 @@ -<?php - -namespace Egulias\EmailValidator\Exception; - -class ExpectingATEXT extends InvalidEmail -{ - const CODE = 137; - const REASON = "Expecting ATEXT"; -} diff --git a/egulias/email-validator/src/Exception/ExpectingCTEXT.php b/egulias/email-validator/src/Exception/ExpectingCTEXT.php deleted file mode 100644 index 63b870a4..00000000 --- a/egulias/email-validator/src/Exception/ExpectingCTEXT.php +++ /dev/null @@ -1,9 +0,0 @@ -<?php - -namespace Egulias\EmailValidator\Exception; - -class ExpectingCTEXT extends InvalidEmail -{ - const CODE = 139; - const REASON = "Expecting CTEXT"; -} diff --git a/egulias/email-validator/src/Exception/ExpectingDTEXT.php b/egulias/email-validator/src/Exception/ExpectingDTEXT.php deleted file mode 100644 index 6a5bb9bf..00000000 --- a/egulias/email-validator/src/Exception/ExpectingDTEXT.php +++ /dev/null @@ -1,9 +0,0 @@ -<?php - -namespace Egulias\EmailValidator\Exception; - -class ExpectingDTEXT extends InvalidEmail -{ - const CODE = 129; - const REASON = "Expected DTEXT"; -} diff --git a/egulias/email-validator/src/Exception/ExpectingDomainLiteralClose.php b/egulias/email-validator/src/Exception/ExpectingDomainLiteralClose.php deleted file mode 100644 index 81aad427..00000000 --- a/egulias/email-validator/src/Exception/ExpectingDomainLiteralClose.php +++ /dev/null @@ -1,9 +0,0 @@ -<?php - -namespace Egulias\EmailValidator\Exception; - -class ExpectingDomainLiteralClose extends InvalidEmail -{ - const CODE = 137; - const REASON = "Closing bracket ']' for domain literal not found"; -} diff --git a/egulias/email-validator/src/Exception/ExpectingQPair.php b/egulias/email-validator/src/Exception/ExpectingQPair.php deleted file mode 100644 index a738eeb6..00000000 --- a/egulias/email-validator/src/Exception/ExpectingQPair.php +++ /dev/null @@ -1,9 +0,0 @@ -<?php - -namespace Egulias\EmailValidator\Exception; - -class ExpectingQPair extends InvalidEmail -{ - const CODE = 136; - const REASON = "Expecting QPAIR"; -} diff --git a/egulias/email-validator/src/Exception/InvalidEmail.php b/egulias/email-validator/src/Exception/InvalidEmail.php deleted file mode 100644 index 1c0218e9..00000000 --- a/egulias/email-validator/src/Exception/InvalidEmail.php +++ /dev/null @@ -1,14 +0,0 @@ -<?php - -namespace Egulias\EmailValidator\Exception; - -abstract class InvalidEmail extends \InvalidArgumentException -{ - const REASON = "Invalid email"; - const CODE = 0; - - public function __construct() - { - parent::__construct(static::REASON, static::CODE); - } -} diff --git a/egulias/email-validator/src/Exception/LocalOrReservedDomain.php b/egulias/email-validator/src/Exception/LocalOrReservedDomain.php deleted file mode 100644 index 695b05a4..00000000 --- a/egulias/email-validator/src/Exception/LocalOrReservedDomain.php +++ /dev/null @@ -1,9 +0,0 @@ -<?php - -namespace Egulias\EmailValidator\Exception; - -class LocalOrReservedDomain extends InvalidEmail -{ - const CODE = 153; - const REASON = 'Local, mDNS or reserved domain (RFC2606, RFC6762)'; -}
\ No newline at end of file diff --git a/egulias/email-validator/src/Exception/NoDNSRecord.php b/egulias/email-validator/src/Exception/NoDNSRecord.php deleted file mode 100644 index 0aa5fa78..00000000 --- a/egulias/email-validator/src/Exception/NoDNSRecord.php +++ /dev/null @@ -1,9 +0,0 @@ -<?php - -namespace Egulias\EmailValidator\Exception; - -class NoDNSRecord extends InvalidEmail -{ - const CODE = 5; - const REASON = 'No MX or A DSN record was found for this email'; -} diff --git a/egulias/email-validator/src/Exception/NoDomainPart.php b/egulias/email-validator/src/Exception/NoDomainPart.php deleted file mode 100644 index 05a2604c..00000000 --- a/egulias/email-validator/src/Exception/NoDomainPart.php +++ /dev/null @@ -1,9 +0,0 @@ -<?php - -namespace Egulias\EmailValidator\Exception; - -class NoDomainPart extends InvalidEmail -{ - const CODE = 131; - const REASON = "No Domain part"; -} diff --git a/egulias/email-validator/src/Exception/NoLocalPart.php b/egulias/email-validator/src/Exception/NoLocalPart.php deleted file mode 100644 index 07c14b84..00000000 --- a/egulias/email-validator/src/Exception/NoLocalPart.php +++ /dev/null @@ -1,9 +0,0 @@ -<?php - -namespace Egulias\EmailValidator\Exception; - -class NoLocalPart extends InvalidEmail -{ - const CODE = 130; - const REASON = "No local part"; -} diff --git a/egulias/email-validator/src/Exception/UnclosedComment.php b/egulias/email-validator/src/Exception/UnclosedComment.php deleted file mode 100644 index 86b2b096..00000000 --- a/egulias/email-validator/src/Exception/UnclosedComment.php +++ /dev/null @@ -1,9 +0,0 @@ -<?php - -namespace Egulias\EmailValidator\Exception; - -class UnclosedComment extends InvalidEmail -{ - const CODE = 146; - const REASON = "No closing comment token found"; -} diff --git a/egulias/email-validator/src/Exception/UnclosedQuotedString.php b/egulias/email-validator/src/Exception/UnclosedQuotedString.php deleted file mode 100644 index 730a39dd..00000000 --- a/egulias/email-validator/src/Exception/UnclosedQuotedString.php +++ /dev/null @@ -1,9 +0,0 @@ -<?php - -namespace Egulias\EmailValidator\Exception; - -class UnclosedQuotedString extends InvalidEmail -{ - const CODE = 145; - const REASON = "Unclosed quoted string"; -} diff --git a/egulias/email-validator/src/Exception/UnopenedComment.php b/egulias/email-validator/src/Exception/UnopenedComment.php deleted file mode 100644 index cff12d92..00000000 --- a/egulias/email-validator/src/Exception/UnopenedComment.php +++ /dev/null @@ -1,9 +0,0 @@ -<?php - -namespace Egulias\EmailValidator\Exception; - -class UnopenedComment extends InvalidEmail -{ - const CODE = 152; - const REASON = "No opening comment token found"; -} diff --git a/egulias/email-validator/src/MessageIDParser.php b/egulias/email-validator/src/MessageIDParser.php new file mode 100644 index 00000000..9b029e14 --- /dev/null +++ b/egulias/email-validator/src/MessageIDParser.php @@ -0,0 +1,93 @@ +<?php + +namespace Egulias\EmailValidator; + +use Egulias\EmailValidator\Parser; +use Egulias\EmailValidator\EmailLexer; +use Egulias\EmailValidator\Result\Result; +use Egulias\EmailValidator\Parser\IDLeftPart; +use Egulias\EmailValidator\Parser\IDRightPart; +use Egulias\EmailValidator\Result\ValidEmail; +use Egulias\EmailValidator\Result\InvalidEmail; +use Egulias\EmailValidator\Warning\EmailTooLong; +use Egulias\EmailValidator\Result\Reason\NoLocalPart; + +class MessageIDParser extends Parser +{ + + const EMAILID_MAX_LENGTH = 254; + + /** + * @var string + */ + protected $idLeft = ''; + + /** + * @var string + */ + protected $idRight = ''; + + public function parse(string $str) : Result + { + $result = parent::parse($str); + + $this->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/egulias/email-validator/src/Parser.php b/egulias/email-validator/src/Parser.php new file mode 100644 index 00000000..b1905f9a --- /dev/null +++ b/egulias/email-validator/src/Parser.php @@ -0,0 +1,78 @@ +<?php + +namespace Egulias\EmailValidator; + +use Egulias\EmailValidator\Result\Result; +use Egulias\EmailValidator\Result\ValidEmail; +use Egulias\EmailValidator\Result\InvalidEmail; +use Egulias\EmailValidator\Result\Reason\ExpectingATEXT; + +abstract class Parser +{ + /** + * @var Warning\Warning[] + */ + protected $warnings = []; + + /** + * @var EmailLexer + */ + protected $lexer; + + /** + * id-left "@" id-right + */ + abstract protected function parseRightFromAt() : Result; + abstract protected function parseLeftFromAt() : Result; + abstract protected function preLeftParsing() : Result; + + + public function __construct(EmailLexer $lexer) + { + $this->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/egulias/email-validator/src/Parser/Comment.php b/egulias/email-validator/src/Parser/Comment.php new file mode 100644 index 00000000..ffa61281 --- /dev/null +++ b/egulias/email-validator/src/Parser/Comment.php @@ -0,0 +1,103 @@ +<?php + +namespace Egulias\EmailValidator\Parser; + +use Egulias\EmailValidator\EmailLexer; +use Egulias\EmailValidator\Result\Result; +use Egulias\EmailValidator\Warning\QuotedPart; +use Egulias\EmailValidator\Result\InvalidEmail; +use Egulias\EmailValidator\Parser\CommentStrategy\CommentStrategy; +use Egulias\EmailValidator\Result\Reason\UnclosedComment; +use Egulias\EmailValidator\Result\Reason\UnOpenedComment; +use Egulias\EmailValidator\Warning\Comment as WarningComment; + +class Comment extends PartParser +{ + /** + * @var int + */ + private $openedParenthesis = 0; + + /** + * @var CommentStrategy + */ + private $commentStrategy; + + public function __construct(EmailLexer $lexer, CommentStrategy $commentStrategy) + { + $this->lexer = $lexer; + $this->commentStrategy = $commentStrategy; + } + + public function parse() : Result + { + if ($this->lexer->token['type'] === EmailLexer::S_OPENPARENTHESIS) { + $this->openedParenthesis++; + if($this->noClosingParenthesis()) { + return new InvalidEmail(new UnclosedComment(), $this->lexer->token['value']); + } + } + + if ($this->lexer->token['type'] === EmailLexer::S_CLOSEPARENTHESIS) { + return new InvalidEmail(new UnOpenedComment(), $this->lexer->token['value']); + } + + $this->warnings[WarningComment::CODE] = new WarningComment(); + + $moreTokens = true; + while ($this->commentStrategy->exitCondition($this->lexer, $this->openedParenthesis) && $moreTokens){ + + if ($this->lexer->isNextToken(EmailLexer::S_OPENPARENTHESIS)) { + $this->openedParenthesis++; + } + $this->warnEscaping(); + if($this->lexer->isNextToken(EmailLexer::S_CLOSEPARENTHESIS)) { + $this->openedParenthesis--; + } + $moreTokens = $this->lexer->moveNext(); + } + + if($this->openedParenthesis >= 1) { + return new InvalidEmail(new UnclosedComment(), $this->lexer->token['value']); + } else if ($this->openedParenthesis < 0) { + return new InvalidEmail(new UnOpenedComment(), $this->lexer->token['value']); + } + + $finalValidations = $this->commentStrategy->endOfLoopValidations($this->lexer); + + $this->warnings = array_merge($this->warnings, $this->commentStrategy->getWarnings()); + + return $finalValidations; + } + + + /** + * @return bool + */ + private function warnEscaping() : bool + { + //Backslash found + if ($this->lexer->token['type'] !== EmailLexer::S_BACKSLASH) { + return false; + } + + if (!$this->lexer->isNextTokenAny(array(EmailLexer::S_SP, EmailLexer::S_HTAB, EmailLexer::C_DEL))) { + return false; + } + + $this->warnings[QuotedPart::CODE] = + new QuotedPart($this->lexer->getPrevious()['type'], $this->lexer->token['type']); + return true; + + } + + private function noClosingParenthesis() : bool + { + try { + $this->lexer->find(EmailLexer::S_CLOSEPARENTHESIS); + return false; + } catch (\RuntimeException $e) { + return true; + } + } +}
\ No newline at end of file diff --git a/egulias/email-validator/src/Parser/CommentStrategy/CommentStrategy.php b/egulias/email-validator/src/Parser/CommentStrategy/CommentStrategy.php new file mode 100644 index 00000000..c388efd6 --- /dev/null +++ b/egulias/email-validator/src/Parser/CommentStrategy/CommentStrategy.php @@ -0,0 +1,18 @@ +<?php + +namespace Egulias\EmailValidator\Parser\CommentStrategy; + +use Egulias\EmailValidator\EmailLexer; +use Egulias\EmailValidator\Result\Result; + +interface CommentStrategy +{ + /** + * Return "true" to continue, "false" to exit + */ + public function exitCondition(EmailLexer $lexer, int $openedParenthesis) : bool; + + public function endOfLoopValidations(EmailLexer $lexer) : Result; + + public function getWarnings() : array; +}
\ No newline at end of file diff --git a/egulias/email-validator/src/Parser/CommentStrategy/DomainComment.php b/egulias/email-validator/src/Parser/CommentStrategy/DomainComment.php new file mode 100644 index 00000000..b34ce29a --- /dev/null +++ b/egulias/email-validator/src/Parser/CommentStrategy/DomainComment.php @@ -0,0 +1,37 @@ +<?php + +namespace Egulias\EmailValidator\Parser\CommentStrategy; + +use Egulias\EmailValidator\EmailLexer; +use Egulias\EmailValidator\Result\Result; +use Egulias\EmailValidator\Result\ValidEmail; +use Egulias\EmailValidator\Result\InvalidEmail; +use Egulias\EmailValidator\Result\Reason\ExpectingATEXT; + +class DomainComment implements CommentStrategy +{ + public function exitCondition(EmailLexer $lexer, int $openedParenthesis) : bool + { + if (($openedParenthesis === 0 && $lexer->isNextToken(EmailLexer::S_DOT))){ // || !$internalLexer->moveNext()) { + return false; + } + + return true; + } + + public function endOfLoopValidations(EmailLexer $lexer) : Result + { + //test for end of string + if (!$lexer->isNextToken(EmailLexer::S_DOT)) { + return new InvalidEmail(new ExpectingATEXT('DOT not found near CLOSEPARENTHESIS'), $lexer->token['value']); + } + //add warning + //Address is valid within the message but cannot be used unmodified for the envelope + return new ValidEmail(); + } + + public function getWarnings(): array + { + return []; + } +}
\ No newline at end of file diff --git a/egulias/email-validator/src/Parser/CommentStrategy/LocalComment.php b/egulias/email-validator/src/Parser/CommentStrategy/LocalComment.php new file mode 100644 index 00000000..73bc7b2b --- /dev/null +++ b/egulias/email-validator/src/Parser/CommentStrategy/LocalComment.php @@ -0,0 +1,37 @@ +<?php + +namespace Egulias\EmailValidator\Parser\CommentStrategy; + +use Egulias\EmailValidator\EmailLexer; +use Egulias\EmailValidator\Result\Result; +use Egulias\EmailValidator\Result\ValidEmail; +use Egulias\EmailValidator\Warning\CFWSNearAt; +use Egulias\EmailValidator\Result\InvalidEmail; +use Egulias\EmailValidator\Result\Reason\ExpectingATEXT; + +class LocalComment implements CommentStrategy +{ + /** + * @var array + */ + private $warnings = []; + + public function exitCondition(EmailLexer $lexer, int $openedParenthesis) : bool + { + return !$lexer->isNextToken(EmailLexer::S_AT); + } + + public function endOfLoopValidations(EmailLexer $lexer) : Result + { + if (!$lexer->isNextToken(EmailLexer::S_AT)) { + return new InvalidEmail(new ExpectingATEXT('ATEX is not expected after closing comments'), $lexer->token['value']); + } + $this->warnings[CFWSNearAt::CODE] = new CFWSNearAt(); + return new ValidEmail(); + } + + public function getWarnings(): array + { + return $this->warnings; + } +}
\ No newline at end of file diff --git a/egulias/email-validator/src/Parser/DomainLiteral.php b/egulias/email-validator/src/Parser/DomainLiteral.php new file mode 100644 index 00000000..54a6fab9 --- /dev/null +++ b/egulias/email-validator/src/Parser/DomainLiteral.php @@ -0,0 +1,212 @@ +<?php +namespace Egulias\EmailValidator\Parser; + +use Egulias\EmailValidator\EmailLexer; +use Egulias\EmailValidator\Result\Result; +use Egulias\EmailValidator\Result\ValidEmail; +use Egulias\EmailValidator\Result\InvalidEmail; +use Egulias\EmailValidator\Warning\CFWSWithFWS; +use Egulias\EmailValidator\Warning\IPV6BadChar; +use Egulias\EmailValidator\Result\Reason\CRNoLF; +use Egulias\EmailValidator\Warning\IPV6ColonEnd; +use Egulias\EmailValidator\Warning\IPV6MaxGroups; +use Egulias\EmailValidator\Warning\ObsoleteDTEXT; +use Egulias\EmailValidator\Warning\AddressLiteral; +use Egulias\EmailValidator\Warning\IPV6ColonStart; +use Egulias\EmailValidator\Warning\IPV6Deprecated; +use Egulias\EmailValidator\Warning\IPV6GroupCount; +use Egulias\EmailValidator\Warning\IPV6DoubleColon; +use Egulias\EmailValidator\Result\Reason\ExpectingDTEXT; +use Egulias\EmailValidator\Result\Reason\UnusualElements; +use Egulias\EmailValidator\Warning\DomainLiteral as WarningDomainLiteral; + +class DomainLiteral extends PartParser +{ + public function parse() : Result + { + $this->addTagWarnings(); + + $IPv6TAG = false; + $addressLiteral = ''; + + do { + if ($this->lexer->token['type'] === EmailLexer::C_NUL) { + return new InvalidEmail(new ExpectingDTEXT(), $this->lexer->token['value']); + } + + $this->addObsoleteWarnings(); + + if ($this->lexer->isNextTokenAny(array(EmailLexer::S_OPENBRACKET, EmailLexer::S_OPENBRACKET))) { + return new InvalidEmail(new ExpectingDTEXT(), $this->lexer->token['value']); + } + + if ($this->lexer->isNextTokenAny( + array(EmailLexer::S_HTAB, EmailLexer::S_SP, $this->lexer->token['type'] === EmailLexer::CRLF) + )) { + $this->warnings[CFWSWithFWS::CODE] = new CFWSWithFWS(); + $this->parseFWS(); + } + + if ($this->lexer->isNextToken(EmailLexer::S_CR)) { + return new InvalidEmail(new CRNoLF(), $this->lexer->token['value']); + } + + if ($this->lexer->token['type'] === EmailLexer::S_BACKSLASH) { + return new InvalidEmail(new UnusualElements($this->lexer->token['value']), $this->lexer->token['value']); + } + if ($this->lexer->token['type'] === EmailLexer::S_IPV6TAG) { + $IPv6TAG = true; + } + + if ($this->lexer->token['type'] === EmailLexer::S_CLOSEBRACKET) { + break; + } + + $addressLiteral .= $this->lexer->token['value']; + + } while ($this->lexer->moveNext()); + + + //Encapsulate + $addressLiteral = str_replace('[', '', $addressLiteral); + $isAddressLiteralIPv4 = $this->checkIPV4Tag($addressLiteral); + + if (!$isAddressLiteralIPv4) { + return new ValidEmail(); + } else { + $addressLiteral = $this->convertIPv4ToIPv6($addressLiteral); + } + + if (!$IPv6TAG) { + $this->warnings[WarningDomainLiteral::CODE] = new WarningDomainLiteral(); + return new ValidEmail(); + } + + $this->warnings[AddressLiteral::CODE] = new AddressLiteral(); + + $this->checkIPV6Tag($addressLiteral); + + return new ValidEmail(); + } + + /** + * @param string $addressLiteral + * @param int $maxGroups + */ + public function checkIPV6Tag($addressLiteral, $maxGroups = 8) : void + { + $prev = $this->lexer->getPrevious(); + if ($prev['type'] === EmailLexer::S_COLON) { + $this->warnings[IPV6ColonEnd::CODE] = new IPV6ColonEnd(); + } + + $IPv6 = substr($addressLiteral, 5); + //Daniel Marschall's new IPv6 testing strategy + $matchesIP = explode(':', $IPv6); + $groupCount = count($matchesIP); + $colons = strpos($IPv6, '::'); + + if (count(preg_grep('/^[0-9A-Fa-f]{0,4}$/', $matchesIP, PREG_GREP_INVERT)) !== 0) { + $this->warnings[IPV6BadChar::CODE] = new IPV6BadChar(); + } + + if ($colons === false) { + // We need exactly the right number of groups + if ($groupCount !== $maxGroups) { + $this->warnings[IPV6GroupCount::CODE] = new IPV6GroupCount(); + } + return; + } + + if ($colons !== strrpos($IPv6, '::')) { + $this->warnings[IPV6DoubleColon::CODE] = new IPV6DoubleColon(); + return; + } + + if ($colons === 0 || $colons === (strlen($IPv6) - 2)) { + // RFC 4291 allows :: at the start or end of an address + //with 7 other groups in addition + ++$maxGroups; + } + + if ($groupCount > $maxGroups) { + $this->warnings[IPV6MaxGroups::CODE] = new IPV6MaxGroups(); + } elseif ($groupCount === $maxGroups) { + $this->warnings[IPV6Deprecated::CODE] = new IPV6Deprecated(); + } + } + + public function convertIPv4ToIPv6(string $addressLiteralIPv4) : string + { + $matchesIP = array(); + $IPv4Match = 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]?)$/', + $addressLiteralIPv4, + $matchesIP); + + // Extract IPv4 part from the end of the address-literal (if there is one) + if ($IPv4Match > 0) { + $index = (int) strrpos($addressLiteralIPv4, $matchesIP[0]); + //There's a match but it is at the start + if ($index > 0) { + // Convert IPv4 part to IPv6 format for further testing + return substr($addressLiteralIPv4, 0, $index) . '0:0'; + } + } + + return $addressLiteralIPv4; + } + + /** + * @param string $addressLiteral + * + * @return bool + */ + protected function checkIPV4Tag($addressLiteral) : bool + { + $matchesIP = array(); + $IPv4Match = 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); + + // Extract IPv4 part from the end of the address-literal (if there is one) + + if ($IPv4Match > 0) { + $index = strrpos($addressLiteral, $matchesIP[0]); + //There's a match but it is at the start + if ($index === 0) { + $this->warnings[AddressLiteral::CODE] = new AddressLiteral(); + return false; + } + } + + return true; + } + + private function addObsoleteWarnings() : void + { + if ($this->lexer->token['type'] === EmailLexer::INVALID || + $this->lexer->token['type'] === EmailLexer::C_DEL || + $this->lexer->token['type'] === EmailLexer::S_LF || + $this->lexer->token['type'] === EmailLexer::S_BACKSLASH + ) { + $this->warnings[ObsoleteDTEXT::CODE] = new ObsoleteDTEXT(); + } + } + + private function addTagWarnings() : void + { + if ($this->lexer->isNextToken(EmailLexer::S_COLON)) { + $this->warnings[IPV6ColonStart::CODE] = new IPV6ColonStart(); + } + if ($this->lexer->isNextToken(EmailLexer::S_IPV6TAG)) { + $lexer = clone $this->lexer; + $lexer->moveNext(); + if ($lexer->isNextToken(EmailLexer::S_DOUBLECOLON)) { + $this->warnings[IPV6ColonStart::CODE] = new IPV6ColonStart(); + } + } + } + +}
\ No newline at end of file diff --git a/egulias/email-validator/src/Parser/DomainPart.php b/egulias/email-validator/src/Parser/DomainPart.php index 4dadba8a..4ca54f2d 100644 --- a/egulias/email-validator/src/Parser/DomainPart.php +++ b/egulias/email-validator/src/Parser/DomainPart.php @@ -3,38 +3,28 @@ namespace Egulias\EmailValidator\Parser; use Egulias\EmailValidator\EmailLexer; -use Egulias\EmailValidator\Exception\CharNotAllowed; -use Egulias\EmailValidator\Exception\CommaInDomain; -use Egulias\EmailValidator\Exception\ConsecutiveAt; -use Egulias\EmailValidator\Exception\CRLFAtTheEnd; -use Egulias\EmailValidator\Exception\CRNoLF; -use Egulias\EmailValidator\Exception\DomainHyphened; -use Egulias\EmailValidator\Exception\DotAtEnd; -use Egulias\EmailValidator\Exception\DotAtStart; -use Egulias\EmailValidator\Exception\ExpectingATEXT; -use Egulias\EmailValidator\Exception\ExpectingDomainLiteralClose; -use Egulias\EmailValidator\Exception\ExpectingDTEXT; -use Egulias\EmailValidator\Exception\NoDomainPart; -use Egulias\EmailValidator\Exception\UnopenedComment; -use Egulias\EmailValidator\Warning\AddressLiteral; -use Egulias\EmailValidator\Warning\CFWSWithFWS; -use Egulias\EmailValidator\Warning\DeprecatedComment; -use Egulias\EmailValidator\Warning\DomainLiteral; -use Egulias\EmailValidator\Warning\DomainTooLong; -use Egulias\EmailValidator\Warning\IPV6BadChar; -use Egulias\EmailValidator\Warning\IPV6ColonEnd; -use Egulias\EmailValidator\Warning\IPV6ColonStart; -use Egulias\EmailValidator\Warning\IPV6Deprecated; -use Egulias\EmailValidator\Warning\IPV6DoubleColon; -use Egulias\EmailValidator\Warning\IPV6GroupCount; -use Egulias\EmailValidator\Warning\IPV6MaxGroups; -use Egulias\EmailValidator\Warning\LabelTooLong; -use Egulias\EmailValidator\Warning\ObsoleteDTEXT; use Egulias\EmailValidator\Warning\TLD; - -class DomainPart extends Parser +use Egulias\EmailValidator\Result\Result; +use Egulias\EmailValidator\Result\ValidEmail; +use Egulias\EmailValidator\Result\InvalidEmail; +use Egulias\EmailValidator\Result\Reason\DotAtEnd; +use Egulias\EmailValidator\Result\Reason\DotAtStart; +use Egulias\EmailValidator\Warning\DeprecatedComment; +use Egulias\EmailValidator\Result\Reason\CRLFAtTheEnd; +use Egulias\EmailValidator\Result\Reason\LabelTooLong; +use Egulias\EmailValidator\Result\Reason\NoDomainPart; +use Egulias\EmailValidator\Result\Reason\ConsecutiveAt; +use Egulias\EmailValidator\Result\Reason\DomainTooLong; +use Egulias\EmailValidator\Result\Reason\CharNotAllowed; +use Egulias\EmailValidator\Result\Reason\DomainHyphened; +use Egulias\EmailValidator\Result\Reason\ExpectingATEXT; +use Egulias\EmailValidator\Parser\CommentStrategy\DomainComment; +use Egulias\EmailValidator\Result\Reason\ExpectingDomainLiteralClose; +use Egulias\EmailValidator\Parser\DomainLiteral as DomainLiteralParser; + +class DomainPart extends PartParser { - const DOMAIN_MAX_LENGTH = 254; + const DOMAIN_MAX_LENGTH = 253; const LABEL_MAX_LENGTH = 63; /** @@ -42,402 +32,281 @@ class DomainPart extends Parser */ protected $domainPart = ''; - public function parse($domainPart) + /** + * @var string + */ + protected $label = ''; + + public function parse() : Result { + $this->lexer->clearRecorded(); + $this->lexer->startRecording(); + $this->lexer->moveNext(); - $this->performDomainStartChecks(); + $domainChecks = $this->performDomainStartChecks(); + if ($domainChecks->isInvalid()) { + return $domainChecks; + } + + if ($this->lexer->token['type'] === EmailLexer::S_AT) { + return new InvalidEmail(new ConsecutiveAt(), $this->lexer->token['value']); + } - $domain = $this->doParseDomainPart(); + $result = $this->doParseDomainPart(); + if ($result->isInvalid()) { + return $result; + } - $prev = $this->lexer->getPrevious(); - $length = strlen($domain); + $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) { + return new InvalidEmail(new DomainTooLong(), $this->lexer->token['value']); + } + + return new ValidEmail(); + } + private function checkEndOfDomain() : Result + { + $prev = $this->lexer->getPrevious(); if ($prev['type'] === EmailLexer::S_DOT) { - throw new DotAtEnd(); + return new InvalidEmail(new DotAtEnd(), $this->lexer->token['value']); } if ($prev['type'] === EmailLexer::S_HYPHEN) { - throw new DomainHyphened(); + return new InvalidEmail(new DomainHyphened('Hypen found at the end of the domain'), $prev['value']); } - if ($length > self::DOMAIN_MAX_LENGTH) { - $this->warnings[DomainTooLong::CODE] = new DomainTooLong(); - } - if ($prev['type'] === EmailLexer::S_CR) { - throw new CRLFAtTheEnd(); + + if ($this->lexer->token['type'] === EmailLexer::S_SP) { + return new InvalidEmail(new CRLFAtTheEnd(), $prev['value']); } - $this->domainPart = $domain; + return new ValidEmail(); + } - private function performDomainStartChecks() + private function performDomainStartChecks() : Result { - $this->checkInvalidTokensAfterAT(); - $this->checkEmptyDomain(); + $invalidTokens = $this->checkInvalidTokensAfterAT(); + if ($invalidTokens->isInvalid()) { + return $invalidTokens; + } + + $missingDomain = $this->checkEmptyDomain(); + if ($missingDomain->isInvalid()) { + return $missingDomain; + } if ($this->lexer->token['type'] === EmailLexer::S_OPENPARENTHESIS) { $this->warnings[DeprecatedComment::CODE] = new DeprecatedComment(); - $this->parseDomainComments(); } + return new ValidEmail(); } - private function checkEmptyDomain() + private function checkEmptyDomain() : Result { $thereIsNoDomain = $this->lexer->token['type'] === EmailLexer::S_EMPTY || ($this->lexer->token['type'] === EmailLexer::S_SP && !$this->lexer->isNextToken(EmailLexer::GENERIC)); if ($thereIsNoDomain) { - throw new NoDomainPart(); + return new InvalidEmail(new NoDomainPart(), $this->lexer->token['value']); } + + return new ValidEmail(); } - private function checkInvalidTokensAfterAT() + private function checkInvalidTokensAfterAT() : Result { if ($this->lexer->token['type'] === EmailLexer::S_DOT) { - throw new DotAtStart(); + return new InvalidEmail(new DotAtStart(), $this->lexer->token['value']); } if ($this->lexer->token['type'] === EmailLexer::S_HYPHEN) { - throw new DomainHyphened(); + return new InvalidEmail(new DomainHyphened('After AT'), $this->lexer->token['value']); } + return new ValidEmail(); } - /** - * @return string - */ - public function getDomainPart() + protected function parseComments(): Result { - return $this->domainPart; - } + $commentParser = new Comment($this->lexer, new DomainComment()); + $result = $commentParser->parse(); + $this->warnings = array_merge($this->warnings, $commentParser->getWarnings()); - /** - * @param string $addressLiteral - * @param int $maxGroups - */ - public function checkIPV6Tag($addressLiteral, $maxGroups = 8) - { - $prev = $this->lexer->getPrevious(); - if ($prev['type'] === EmailLexer::S_COLON) { - $this->warnings[IPV6ColonEnd::CODE] = new IPV6ColonEnd(); - } - - $IPv6 = substr($addressLiteral, 5); - //Daniel Marschall's new IPv6 testing strategy - $matchesIP = explode(':', $IPv6); - $groupCount = count($matchesIP); - $colons = strpos($IPv6, '::'); - - if (count(preg_grep('/^[0-9A-Fa-f]{0,4}$/', $matchesIP, PREG_GREP_INVERT)) !== 0) { - $this->warnings[IPV6BadChar::CODE] = new IPV6BadChar(); - } - - if ($colons === false) { - // We need exactly the right number of groups - if ($groupCount !== $maxGroups) { - $this->warnings[IPV6GroupCount::CODE] = new IPV6GroupCount(); - } - return; - } - - if ($colons !== strrpos($IPv6, '::')) { - $this->warnings[IPV6DoubleColon::CODE] = new IPV6DoubleColon(); - return; - } - - if ($colons === 0 || $colons === (strlen($IPv6) - 2)) { - // RFC 4291 allows :: at the start or end of an address - //with 7 other groups in addition - ++$maxGroups; - } - - if ($groupCount > $maxGroups) { - $this->warnings[IPV6MaxGroups::CODE] = new IPV6MaxGroups(); - } elseif ($groupCount === $maxGroups) { - $this->warnings[IPV6Deprecated::CODE] = new IPV6Deprecated(); - } + return $result; } - /** - * @return string - */ - protected function doParseDomainPart() + protected function doParseDomainPart() : Result { + $tldMissing = true; + $hasComments = false; $domain = ''; - $label = ''; - $openedParenthesis = 0; do { $prev = $this->lexer->getPrevious(); - $this->checkNotAllowedChars($this->lexer->token); + $notAllowedChars = $this->checkNotAllowedChars($this->lexer->token); + if ($notAllowedChars->isInvalid()) { + return $notAllowedChars; + } + + if ($this->lexer->token['type'] === EmailLexer::S_OPENPARENTHESIS || + $this->lexer->token['type'] === EmailLexer::S_CLOSEPARENTHESIS ) { + $hasComments = true; + $commentsResult = $this->parseComments(); - if ($this->lexer->token['type'] === EmailLexer::S_OPENPARENTHESIS) { - $this->parseComments(); - $openedParenthesis += $this->getOpenedParenthesis(); - $this->lexer->moveNext(); - $tmpPrev = $this->lexer->getPrevious(); - if ($tmpPrev['type'] === EmailLexer::S_CLOSEPARENTHESIS) { - $openedParenthesis--; + //Invalid comment parsing + if($commentsResult->isInvalid()) { + return $commentsResult; } } - if ($this->lexer->token['type'] === EmailLexer::S_CLOSEPARENTHESIS) { - if ($openedParenthesis === 0) { - throw new UnopenedComment(); - } else { - $openedParenthesis--; - } + + $dotsResult = $this->checkConsecutiveDots(); + if ($dotsResult->isInvalid()) { + return $dotsResult; } - $this->checkConsecutiveDots(); - $this->checkDomainPartExceptions($prev); + if ($this->lexer->token['type'] === EmailLexer::S_OPENBRACKET) { + $literalResult = $this->parseDomainLiteral(); - if ($this->hasBrackets()) { - $this->parseDomainLiteral(); + $this->addTLDWarnings($tldMissing); + return $literalResult; } - if ($this->lexer->token['type'] === EmailLexer::S_DOT) { - $this->checkLabelLength($label); - $label = ''; - } else { - $label .= $this->lexer->token['value']; - } + $labelCheck = $this->checkLabelLength(); + if ($labelCheck->isInvalid()) { + return $labelCheck; + } - if ($this->isFWS()) { - $this->parseFWS(); + $FwsResult = $this->parseFWS(); + if($FwsResult->isInvalid()) { + return $FwsResult; } $domain .= $this->lexer->token['value']; - $this->lexer->moveNext(); - if ($this->lexer->token['type'] === EmailLexer::S_SP) { - throw new CharNotAllowed(); + + if ($this->lexer->token['type'] === EmailLexer::S_DOT && $this->lexer->isNextToken(EmailLexer::GENERIC)) { + $tldMissing = false; } + + $exceptionsResult = $this->checkDomainPartExceptions($prev, $hasComments); + if ($exceptionsResult->isInvalid()) { + return $exceptionsResult; + } + $this->lexer->moveNext(); + } while (null !== $this->lexer->token['type']); - $this->checkLabelLength($label); + $labelCheck = $this->checkLabelLength(true); + if ($labelCheck->isInvalid()) { + return $labelCheck; + } + $this->addTLDWarnings($tldMissing); - return $domain; + $this->domainPart = $domain; + return new ValidEmail(); } - private function checkNotAllowedChars(array $token) + private function checkNotAllowedChars(array $token) : Result { $notAllowed = [EmailLexer::S_BACKSLASH => true, EmailLexer::S_SLASH=> true]; if (isset($notAllowed[$token['type']])) { - throw new CharNotAllowed(); + return new InvalidEmail(new CharNotAllowed(), $token['value']); } + return new ValidEmail(); } /** - * @return string|false + * @return Result */ - protected function parseDomainLiteral() + protected function parseDomainLiteral() : Result { - if ($this->lexer->isNextToken(EmailLexer::S_COLON)) { - $this->warnings[IPV6ColonStart::CODE] = new IPV6ColonStart(); - } - if ($this->lexer->isNextToken(EmailLexer::S_IPV6TAG)) { - $lexer = clone $this->lexer; - $lexer->moveNext(); - if ($lexer->isNextToken(EmailLexer::S_DOUBLECOLON)) { - $this->warnings[IPV6ColonStart::CODE] = new IPV6ColonStart(); - } + try { + $this->lexer->find(EmailLexer::S_CLOSEBRACKET); + } catch (\RuntimeException $e) { + return new InvalidEmail(new ExpectingDomainLiteralClose(), $this->lexer->token['value']); } - return $this->doParseDomainLiteral(); + $domainLiteralParser = new DomainLiteralParser($this->lexer); + $result = $domainLiteralParser->parse(); + $this->warnings = array_merge($this->warnings, $domainLiteralParser->getWarnings()); + return $result; } - /** - * @return string|false - */ - protected function doParseDomainLiteral() + protected function checkDomainPartExceptions(array $prev, bool $hasComments) : Result { - $IPv6TAG = false; - $addressLiteral = ''; - do { - if ($this->lexer->token['type'] === EmailLexer::C_NUL) { - throw new ExpectingDTEXT(); - } - - if ($this->lexer->token['type'] === EmailLexer::INVALID || - $this->lexer->token['type'] === EmailLexer::C_DEL || - $this->lexer->token['type'] === EmailLexer::S_LF - ) { - $this->warnings[ObsoleteDTEXT::CODE] = new ObsoleteDTEXT(); - } - - if ($this->lexer->isNextTokenAny(array(EmailLexer::S_OPENQBRACKET, EmailLexer::S_OPENBRACKET))) { - throw new ExpectingDTEXT(); - } - - if ($this->lexer->isNextTokenAny( - array(EmailLexer::S_HTAB, EmailLexer::S_SP, $this->lexer->token['type'] === EmailLexer::CRLF) - )) { - $this->warnings[CFWSWithFWS::CODE] = new CFWSWithFWS(); - $this->parseFWS(); - } - - if ($this->lexer->isNextToken(EmailLexer::S_CR)) { - throw new CRNoLF(); - } - - if ($this->lexer->token['type'] === EmailLexer::S_BACKSLASH) { - $this->warnings[ObsoleteDTEXT::CODE] = new ObsoleteDTEXT(); - $addressLiteral .= $this->lexer->token['value']; - $this->lexer->moveNext(); - $this->validateQuotedPair(); - } - if ($this->lexer->token['type'] === EmailLexer::S_IPV6TAG) { - $IPv6TAG = true; - } - if ($this->lexer->token['type'] === EmailLexer::S_CLOSEQBRACKET) { - break; - } - - $addressLiteral .= $this->lexer->token['value']; - - } while ($this->lexer->moveNext()); - - $addressLiteral = str_replace('[', '', $addressLiteral); - $addressLiteral = $this->checkIPV4Tag($addressLiteral); - - if (false === $addressLiteral) { - return $addressLiteral; + if ($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']); } - if (!$IPv6TAG) { - $this->warnings[DomainLiteral::CODE] = new DomainLiteral(); - return $addressLiteral; + if ($this->lexer->token['type'] === EmailLexer::S_HYPHEN && $this->lexer->isNextToken(EmailLexer::S_DOT)) { + return new InvalidEmail(new DomainHyphened('Hypen found near DOT'), $this->lexer->token['value']); } - $this->warnings[AddressLiteral::CODE] = new AddressLiteral(); - - $this->checkIPV6Tag($addressLiteral); - - return $addressLiteral; - } - - /** - * @param string $addressLiteral - * - * @return string|false - */ - protected function checkIPV4Tag($addressLiteral) - { - $matchesIP = array(); - - // Extract IPv4 part from the end of the address-literal (if there is one) - if (preg_match( - '/\\b(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/', - $addressLiteral, - $matchesIP - ) > 0 - ) { - $index = strrpos($addressLiteral, $matchesIP[0]); - if ($index === 0) { - $this->warnings[AddressLiteral::CODE] = new AddressLiteral(); - return false; - } - // Convert IPv4 part to IPv6 format for further testing - $addressLiteral = substr($addressLiteral, 0, (int) $index) . '0:0'; + if ($this->lexer->token['type'] === EmailLexer::S_BACKSLASH + && $this->lexer->isNextToken(EmailLexer::GENERIC)) { + return new InvalidEmail(new ExpectingATEXT('Escaping following "ATOM"'), $this->lexer->token['value']); } - return $addressLiteral; + return $this->validateTokens($hasComments); } - protected function checkDomainPartExceptions(array $prev) + protected function validateTokens(bool $hasComments) : Result { - $invalidDomainTokens = array( - EmailLexer::S_DQUOTE => true, - EmailLexer::S_SQUOTE => true, - EmailLexer::S_BACKTICK => true, - EmailLexer::S_SEMICOLON => true, - EmailLexer::S_GREATERTHAN => true, - EmailLexer::S_LOWERTHAN => true, + $validDomainTokens = array( + EmailLexer::GENERIC => true, + EmailLexer::S_HYPHEN => true, + EmailLexer::S_DOT => true, ); - if (isset($invalidDomainTokens[$this->lexer->token['type']])) { - throw new ExpectingATEXT(); - } - - if ($this->lexer->token['type'] === EmailLexer::S_COMMA) { - throw new CommaInDomain(); + if ($hasComments) { + $validDomainTokens[EmailLexer::S_OPENPARENTHESIS] = true; + $validDomainTokens[EmailLexer::S_CLOSEPARENTHESIS] = true; } - if ($this->lexer->token['type'] === EmailLexer::S_AT) { - throw new ConsecutiveAt(); - } - - if ($this->lexer->token['type'] === EmailLexer::S_OPENQBRACKET && $prev['type'] !== EmailLexer::S_AT) { - throw new ExpectingATEXT(); + if (!isset($validDomainTokens[$this->lexer->token['type']])) { + return new InvalidEmail(new ExpectingATEXT('Invalid token in domain: ' . $this->lexer->token['value']), $this->lexer->token['value']); } - if ($this->lexer->token['type'] === EmailLexer::S_HYPHEN && $this->lexer->isNextToken(EmailLexer::S_DOT)) { - throw new DomainHyphened(); - } - - if ($this->lexer->token['type'] === EmailLexer::S_BACKSLASH - && $this->lexer->isNextToken(EmailLexer::GENERIC)) { - throw new ExpectingATEXT(); - } + return new ValidEmail(); } - /** - * @return bool - */ - protected function hasBrackets() + private function checkLabelLength(bool $isEndOfDomain = false) : Result { - if ($this->lexer->token['type'] !== EmailLexer::S_OPENBRACKET) { - return false; - } - - try { - $this->lexer->find(EmailLexer::S_CLOSEBRACKET); - } catch (\RuntimeException $e) { - throw new ExpectingDomainLiteralClose(); + if ($this->lexer->token['type'] === EmailLexer::S_DOT || $isEndOfDomain) { + if ($this->isLabelTooLong($this->label)) { + return new InvalidEmail(new LabelTooLong(), $this->lexer->token['value']); + } + $this->label = ''; } - - return true; + $this->label .= $this->lexer->token['value']; + return new ValidEmail(); } - /** - * @param string $label - */ - protected function checkLabelLength($label) - { - if ($this->isLabelTooLong($label)) { - $this->warnings[LabelTooLong::CODE] = new LabelTooLong(); - } - } - /** - * @param string $label - * @return bool - */ - private function isLabelTooLong($label) + private function isLabelTooLong(string $label) : bool { if (preg_match('/[^\x00-\x7F]/', $label)) { - idn_to_ascii($label, IDNA_DEFAULT, INTL_IDNA_VARIANT_UTS46, $idnaInfo); - + idn_to_ascii(utf8_decode($label), IDNA_DEFAULT, INTL_IDNA_VARIANT_UTS46, $idnaInfo); return (bool) ($idnaInfo['errors'] & IDNA_ERROR_LABEL_TOO_LONG); } - return strlen($label) > self::LABEL_MAX_LENGTH; } - protected function parseDomainComments() + private function addTLDWarnings(bool $isTLDMissing) : void { - $this->isUnclosedComment(); - while (!$this->lexer->isNextToken(EmailLexer::S_CLOSEPARENTHESIS)) { - $this->warnEscaping(); - $this->lexer->moveNext(); - } - - $this->lexer->moveNext(); - if ($this->lexer->isNextToken(EmailLexer::S_DOT)) { - throw new ExpectingATEXT(); + if ($isTLDMissing) { + $this->warnings[TLD::CODE] = new TLD(); } } - protected function addTLDWarnings() + public function domainPart() : string { - if ($this->warnings[DomainLiteral::CODE]) { - $this->warnings[TLD::CODE] = new TLD(); - } + return $this->domainPart; } -} +}
\ No newline at end of file diff --git a/egulias/email-validator/src/Parser/DoubleQuote.php b/egulias/email-validator/src/Parser/DoubleQuote.php new file mode 100644 index 00000000..19c098e8 --- /dev/null +++ b/egulias/email-validator/src/Parser/DoubleQuote.php @@ -0,0 +1,87 @@ +<?php +namespace Egulias\EmailValidator\Parser; + +use Egulias\EmailValidator\EmailLexer; +use Egulias\EmailValidator\Parser\Parser; +use Egulias\EmailValidator\Result\ValidEmail; +use Egulias\EmailValidator\Result\InvalidEmail; +use Egulias\EmailValidator\Warning\CFWSWithFWS; +use Egulias\EmailValidator\Warning\QuotedString; +use Egulias\EmailValidator\Result\Reason\ExpectingATEXT; +use Egulias\EmailValidator\Result\Reason\UnclosedQuotedString; +use Egulias\EmailValidator\Result\Result; + +class DoubleQuote extends PartParser +{ + public function parse() : Result + { + + $validQuotedString = $this->checkDQUOTE(); + if($validQuotedString->isInvalid()) return $validQuotedString; + + $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 && null !== $this->lexer->token['type']) { + if (isset($special[$this->lexer->token['type']]) && $setSpecialsWarning) { + $this->warnings[CFWSWithFWS::CODE] = new CFWSWithFWS(); + $setSpecialsWarning = false; + } + if ($this->lexer->token['type'] === EmailLexer::S_BACKSLASH && $this->lexer->isNextToken(EmailLexer::S_DQUOTE)) { + $this->lexer->moveNext(); + } + + $this->lexer->moveNext(); + + if (!$this->escaped() && isset($invalid[$this->lexer->token['type']])) { + return new InvalidEmail(new ExpectingATEXT("Expecting ATEXT between DQUOTE"), $this->lexer->token['value']); + } + } + + $prev = $this->lexer->getPrevious(); + + if ($prev['type'] === EmailLexer::S_BACKSLASH) { + $validQuotedString = $this->checkDQUOTE(); + if($validQuotedString->isInvalid()) return $validQuotedString; + } + + if (!$this->lexer->isNextToken(EmailLexer::S_AT) && $prev['type'] !== EmailLexer::S_BACKSLASH) { + return new InvalidEmail(new ExpectingATEXT("Expecting ATEXT between DQUOTE"), $this->lexer->token['value']); + } + + return new ValidEmail(); + } + + protected function checkDQUOTE() : Result + { + $previous = $this->lexer->getPrevious(); + + if ($this->lexer->isNextToken(EmailLexer::GENERIC) && $previous['type'] === EmailLexer::GENERIC) { + $description = 'https://tools.ietf.org/html/rfc5322#section-3.2.4 - quoted string should be a unit'; + return new InvalidEmail(new ExpectingATEXT($description), $this->lexer->token['value']); + } + + try { + $this->lexer->find(EmailLexer::S_DQUOTE); + } catch (\Exception $e) { + return new InvalidEmail(new UnclosedQuotedString(), $this->lexer->token['value']); + } + $this->warnings[QuotedString::CODE] = new QuotedString($previous['value'], $this->lexer->token['value']); + + return new ValidEmail(); + } + +}
\ No newline at end of file diff --git a/egulias/email-validator/src/Parser/FoldingWhiteSpace.php b/egulias/email-validator/src/Parser/FoldingWhiteSpace.php new file mode 100644 index 00000000..d32231e7 --- /dev/null +++ b/egulias/email-validator/src/Parser/FoldingWhiteSpace.php @@ -0,0 +1,82 @@ +<?php +namespace Egulias\EmailValidator\Parser; + +use Egulias\EmailValidator\EmailLexer; +use Egulias\EmailValidator\Warning\CFWSNearAt; +use Egulias\EmailValidator\Result\InvalidEmail; +use Egulias\EmailValidator\Warning\CFWSWithFWS; +use Egulias\EmailValidator\Result\Reason\CRNoLF; +use Egulias\EmailValidator\Result\Reason\AtextAfterCFWS; +use Egulias\EmailValidator\Result\Reason\CRLFAtTheEnd; +use Egulias\EmailValidator\Result\Reason\CRLFX2; +use Egulias\EmailValidator\Result\Reason\ExpectingCTEXT; +use Egulias\EmailValidator\Result\Result; +use Egulias\EmailValidator\Result\ValidEmail; + +class FoldingWhiteSpace extends PartParser +{ + public function parse() : Result + { + if (!$this->isFWS()) { + return new ValidEmail(); + } + + $previous = $this->lexer->getPrevious(); + + $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']); + } + + if ($this->lexer->isNextToken(EmailLexer::GENERIC) && $previous['type'] !== EmailLexer::S_AT) { + return new InvalidEmail(new AtextAfterCFWS(), $this->lexer->token['value']); + } + + if ($this->lexer->token['type'] === EmailLexer::S_LF || $this->lexer->token['type'] === EmailLexer::C_NUL) { + return new InvalidEmail(new ExpectingCTEXT(), $this->lexer->token['value']); + } + + if ($this->lexer->isNextToken(EmailLexer::S_AT) || $previous['type'] === EmailLexer::S_AT) { + $this->warnings[CFWSNearAt::CODE] = new CFWSNearAt(); + } else { + $this->warnings[CFWSWithFWS::CODE] = new CFWSWithFWS(); + } + + return new ValidEmail(); + } + + protected function checkCRLFInFWS() : Result + { + if ($this->lexer->token['type'] !== EmailLexer::CRLF) { + return new ValidEmail(); + } + + if (!$this->lexer->isNextTokenAny(array(EmailLexer::S_SP, EmailLexer::S_HTAB))) { + return new InvalidEmail(new CRLFX2(), $this->lexer->token['value']); + } + + //this has no coverage. Condition is repeated from above one + if (!$this->lexer->isNextTokenAny(array(EmailLexer::S_SP, EmailLexer::S_HTAB))) { + return new InvalidEmail(new CRLFAtTheEnd(), $this->lexer->token['value']); + } + + return new ValidEmail(); + } + + protected function isFWS() : bool + { + if ($this->escaped()) { + return false; + } + + return $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; + } +}
\ No newline at end of file diff --git a/egulias/email-validator/src/Parser/IDLeftPart.php b/egulias/email-validator/src/Parser/IDLeftPart.php new file mode 100644 index 00000000..abb4982b --- /dev/null +++ b/egulias/email-validator/src/Parser/IDLeftPart.php @@ -0,0 +1,16 @@ +<?php + +namespace Egulias\EmailValidator\Parser; + +use Egulias\EmailValidator\Result\Result; +use Egulias\EmailValidator\Parser\LocalPart; +use Egulias\EmailValidator\Result\InvalidEmail; +use Egulias\EmailValidator\Result\Reason\CommentsInIDRight; + +class IDLeftPart extends LocalPart +{ + protected function parseComments(): Result + { + return new InvalidEmail(new CommentsInIDRight(), $this->lexer->token['value']); + } +}
\ No newline at end of file diff --git a/egulias/email-validator/src/Parser/IDRightPart.php b/egulias/email-validator/src/Parser/IDRightPart.php new file mode 100644 index 00000000..bcf80dd0 --- /dev/null +++ b/egulias/email-validator/src/Parser/IDRightPart.php @@ -0,0 +1,29 @@ +<?php + +namespace Egulias\EmailValidator\Parser; + +use Egulias\EmailValidator\EmailLexer; +use Egulias\EmailValidator\Result\Result; +use Egulias\EmailValidator\Result\ValidEmail; +use Egulias\EmailValidator\Result\InvalidEmail; +use Egulias\EmailValidator\Result\Reason\ExpectingATEXT; + +class IDRightPart extends DomainPart +{ + protected function validateTokens(bool $hasComments) : Result + { + $invalidDomainTokens = array( + EmailLexer::S_DQUOTE => true, + EmailLexer::S_SQUOTE => true, + EmailLexer::S_BACKTICK => true, + EmailLexer::S_SEMICOLON => true, + EmailLexer::S_GREATERTHAN => true, + EmailLexer::S_LOWERTHAN => true, + ); + + if (isset($invalidDomainTokens[$this->lexer->token['type']])) { + 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/egulias/email-validator/src/Parser/LocalPart.php b/egulias/email-validator/src/Parser/LocalPart.php index 3c21f34a..d505c618 100644 --- a/egulias/email-validator/src/Parser/LocalPart.php +++ b/egulias/email-validator/src/Parser/LocalPart.php @@ -2,144 +2,163 @@ namespace Egulias\EmailValidator\Parser; -use Egulias\EmailValidator\Exception\DotAtEnd; -use Egulias\EmailValidator\Exception\DotAtStart; use Egulias\EmailValidator\EmailLexer; -use Egulias\EmailValidator\Exception\ExpectingAT; -use Egulias\EmailValidator\Exception\ExpectingATEXT; -use Egulias\EmailValidator\Exception\UnclosedQuotedString; -use Egulias\EmailValidator\Exception\UnopenedComment; -use Egulias\EmailValidator\Warning\CFWSWithFWS; +use Egulias\EmailValidator\Result\Result; +use Egulias\EmailValidator\Result\ValidEmail; +use Egulias\EmailValidator\Result\InvalidEmail; use Egulias\EmailValidator\Warning\LocalTooLong; +use Egulias\EmailValidator\Result\Reason\DotAtEnd; +use Egulias\EmailValidator\Result\Reason\DotAtStart; +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 { - public function parse($localPart) + /** + * @var string + */ + private $localPart = ''; + + + public function parse() : Result { - $parseDQuote = true; - $closingQuote = false; - $openedParenthesis = 0; - $totalLength = 0; + $this->lexer->startRecording(); while ($this->lexer->token['type'] !== EmailLexer::S_AT && null !== $this->lexer->token['type']) { - if ($this->lexer->token['type'] === EmailLexer::S_DOT && null === $this->lexer->getPrevious()['type']) { - throw new DotAtStart(); + if ($this->hasDotAtStart()) { + return new InvalidEmail(new DotAtStart(), $this->lexer->token['value']); } - $closingQuote = $this->checkDQUOTE($closingQuote); - if ($closingQuote && $parseDQuote) { - $parseDQuote = $this->parseDoubleQuote(); - } + if ($this->lexer->token['type'] === EmailLexer::S_DQUOTE) { + $dquoteParsingResult = $this->parseDoubleQuote(); - if ($this->lexer->token['type'] === EmailLexer::S_OPENPARENTHESIS) { - $this->parseComments(); - $openedParenthesis += $this->getOpenedParenthesis(); + //Invalid double quote parsing + if($dquoteParsingResult->isInvalid()) { + return $dquoteParsingResult; + } } - if ($this->lexer->token['type'] === EmailLexer::S_CLOSEPARENTHESIS) { - if ($openedParenthesis === 0) { - throw new UnopenedComment(); - } + if ($this->lexer->token['type'] === EmailLexer::S_OPENPARENTHESIS || + $this->lexer->token['type'] === EmailLexer::S_CLOSEPARENTHESIS ) { + $commentsResult = $this->parseComments(); - $openedParenthesis--; + //Invalid comment parsing + if($commentsResult->isInvalid()) { + return $commentsResult; + } } - $this->checkConsecutiveDots(); + if ($this->lexer->token['type'] === EmailLexer::S_DOT && $this->lexer->isNextToken(EmailLexer::S_DOT)) { + return new InvalidEmail(new ConsecutiveDot(), $this->lexer->token['value']); + } if ($this->lexer->token['type'] === EmailLexer::S_DOT && $this->lexer->isNextToken(EmailLexer::S_AT) ) { - throw new DotAtEnd(); + return new InvalidEmail(new DotAtEnd(), $this->lexer->token['value']); } - $this->warnEscaping(); - $this->isInvalidToken($this->lexer->token, $closingQuote); + $resultEscaping = $this->validateEscaping(); + if ($resultEscaping->isInvalid()) { + return $resultEscaping; + } + + $resultToken = $this->validateTokens(false); + if ($resultToken->isInvalid()) { + return $resultToken; + } - if ($this->isFWS()) { - $this->parseFWS(); + $resultFWS = $this->parseLocalFWS(); + if($resultFWS->isInvalid()) { + 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(); } - /** - * @return bool - */ - protected function parseDoubleQuote() + protected function validateTokens(bool $hasComments) : Result { - $parseAgain = true; - $special = array( - EmailLexer::S_CR => true, - EmailLexer::S_HTAB => true, - EmailLexer::S_LF => true + $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 ); - - $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 && null !== $this->lexer->token['type']) { - $parseAgain = false; - if (isset($special[$this->lexer->token['type']]) && $setSpecialsWarning) { - $this->warnings[CFWSWithFWS::CODE] = new CFWSWithFWS(); - $setSpecialsWarning = false; - } - if ($this->lexer->token['type'] === EmailLexer::S_BACKSLASH && $this->lexer->isNextToken(EmailLexer::S_DQUOTE)) { - $this->lexer->moveNext(); - } - - $this->lexer->moveNext(); - - if (!$this->escaped() && isset($invalid[$this->lexer->token['type']])) { - throw new ExpectingATEXT(); - } + if (isset($invalidTokens[$this->lexer->token['type']])) { + return new InvalidEmail(new ExpectingATEXT('Invalid token found'), $this->lexer->token['value']); } + return new ValidEmail(); + } - $prev = $this->lexer->getPrevious(); + public function localPart() : string + { + return $this->localPart; + } - if ($prev['type'] === EmailLexer::S_BACKSLASH) { - if (!$this->checkDQUOTE(false)) { - throw new UnclosedQuotedString(); - } + private function parseLocalFWS() : Result + { + $foldingWS = new FoldingWhiteSpace($this->lexer); + $resultFWS = $foldingWS->parse(); + if ($resultFWS->isValid()) { + $this->warnings = array_merge($this->warnings, $foldingWS->getWarnings()); } + return $resultFWS; + } - if (!$this->lexer->isNextToken(EmailLexer::S_AT) && $prev['type'] !== EmailLexer::S_BACKSLASH) { - throw new ExpectingAT(); - } + private function hasDotAtStart() : bool + { + return $this->lexer->token['type'] === EmailLexer::S_DOT && null === $this->lexer->getPrevious()['type']; + } + + private function parseDoubleQuote() : Result + { + $dquoteParser = new DoubleQuote($this->lexer); + $parseAgain = $dquoteParser->parse(); + $this->warnings = array_merge($this->warnings, $dquoteParser->getWarnings()); return $parseAgain; } - /** - * @param bool $closingQuote - */ - protected function isInvalidToken(array $token, $closingQuote) + protected function parseComments(): Result { - $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 - ); + $commentParser = new Comment($this->lexer, new LocalComment()); + $result = $commentParser->parse(); + $this->warnings = array_merge($this->warnings, $commentParser->getWarnings()); + if($result->isInvalid()) { + return $result; + } + return $result; + } - if (in_array($token['type'], $forbidden) && !$closingQuote) { - throw new ExpectingATEXT(); + private function validateEscaping() : Result + { + //Backslash found + if ($this->lexer->token['type'] !== EmailLexer::S_BACKSLASH) { + return new ValidEmail(); } + + if ($this->lexer->isNextToken(EmailLexer::GENERIC)) { + return new InvalidEmail(new ExpectingATEXT('Found ATOM after escaping'), $this->lexer->token['value']); + } + + if (!$this->lexer->isNextTokenAny(array(EmailLexer::S_SP, EmailLexer::S_HTAB, EmailLexer::C_DEL))) { + return new ValidEmail(); + } + + return new ValidEmail(); } -} +}
\ No newline at end of file diff --git a/egulias/email-validator/src/Parser/Parser.php b/egulias/email-validator/src/Parser/Parser.php deleted file mode 100644 index ccdc9388..00000000 --- a/egulias/email-validator/src/Parser/Parser.php +++ /dev/null @@ -1,249 +0,0 @@ -<?php - -namespace Egulias\EmailValidator\Parser; - -use Egulias\EmailValidator\EmailLexer; -use Egulias\EmailValidator\Exception\AtextAfterCFWS; -use Egulias\EmailValidator\Exception\ConsecutiveDot; -use Egulias\EmailValidator\Exception\CRLFAtTheEnd; -use Egulias\EmailValidator\Exception\CRLFX2; -use Egulias\EmailValidator\Exception\CRNoLF; -use Egulias\EmailValidator\Exception\ExpectingQPair; -use Egulias\EmailValidator\Exception\ExpectingATEXT; -use Egulias\EmailValidator\Exception\ExpectingCTEXT; -use Egulias\EmailValidator\Exception\UnclosedComment; -use Egulias\EmailValidator\Exception\UnclosedQuotedString; -use Egulias\EmailValidator\Warning\CFWSNearAt; -use Egulias\EmailValidator\Warning\CFWSWithFWS; -use Egulias\EmailValidator\Warning\Comment; -use Egulias\EmailValidator\Warning\QuotedPart; -use Egulias\EmailValidator\Warning\QuotedString; - -abstract class Parser -{ - /** - * @var array - */ - protected $warnings = []; - - /** - * @var EmailLexer - */ - protected $lexer; - - /** - * @var int - */ - protected $openedParenthesis = 0; - - public function __construct(EmailLexer $lexer) - { - $this->lexer = $lexer; - } - - /** - * @return \Egulias\EmailValidator\Warning\Warning[] - */ - public function getWarnings() - { - return $this->warnings; - } - - /** - * @param string $str - */ - abstract public function parse($str); - - /** @return int */ - public function getOpenedParenthesis() - { - return $this->openedParenthesis; - } - - /** - * validateQuotedPair - */ - protected function validateQuotedPair() - { - if (!($this->lexer->token['type'] === EmailLexer::INVALID - || $this->lexer->token['type'] === EmailLexer::C_DEL)) { - throw new ExpectingQPair(); - } - - $this->warnings[QuotedPart::CODE] = - new QuotedPart($this->lexer->getPrevious()['type'], $this->lexer->token['type']); - } - - protected function parseComments() - { - $this->openedParenthesis = 1; - $this->isUnclosedComment(); - $this->warnings[Comment::CODE] = new Comment(); - while (!$this->lexer->isNextToken(EmailLexer::S_CLOSEPARENTHESIS)) { - if ($this->lexer->isNextToken(EmailLexer::S_OPENPARENTHESIS)) { - $this->openedParenthesis++; - } - $this->warnEscaping(); - $this->lexer->moveNext(); - } - - $this->lexer->moveNext(); - if ($this->lexer->isNextTokenAny(array(EmailLexer::GENERIC, EmailLexer::S_EMPTY))) { - throw new ExpectingATEXT(); - } - - if ($this->lexer->isNextToken(EmailLexer::S_AT)) { - $this->warnings[CFWSNearAt::CODE] = new CFWSNearAt(); - } - } - - /** - * @return bool - */ - protected function isUnclosedComment() - { - try { - $this->lexer->find(EmailLexer::S_CLOSEPARENTHESIS); - return true; - } catch (\RuntimeException $e) { - throw new UnclosedComment(); - } - } - - protected function parseFWS() - { - $previous = $this->lexer->getPrevious(); - - $this->checkCRLFInFWS(); - - if ($this->lexer->token['type'] === EmailLexer::S_CR) { - throw new CRNoLF(); - } - - if ($this->lexer->isNextToken(EmailLexer::GENERIC) && $previous['type'] !== EmailLexer::S_AT) { - throw new AtextAfterCFWS(); - } - - if ($this->lexer->token['type'] === EmailLexer::S_LF || $this->lexer->token['type'] === EmailLexer::C_NUL) { - throw new ExpectingCTEXT(); - } - - if ($this->lexer->isNextToken(EmailLexer::S_AT) || $previous['type'] === EmailLexer::S_AT) { - $this->warnings[CFWSNearAt::CODE] = new CFWSNearAt(); - } else { - $this->warnings[CFWSWithFWS::CODE] = new CFWSWithFWS(); - } - } - - protected function checkConsecutiveDots() - { - if ($this->lexer->token['type'] === EmailLexer::S_DOT && $this->lexer->isNextToken(EmailLexer::S_DOT)) { - throw new ConsecutiveDot(); - } - } - - /** - * @return bool - */ - 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; - } - - /** - * @return bool - */ - protected function escaped() - { - $previous = $this->lexer->getPrevious(); - - if ($previous && $previous['type'] === EmailLexer::S_BACKSLASH - && - $this->lexer->token['type'] !== EmailLexer::GENERIC - ) { - return true; - } - - return false; - } - - /** - * @return bool - */ - protected function warnEscaping() - { - if ($this->lexer->token['type'] !== EmailLexer::S_BACKSLASH) { - return false; - } - - if ($this->lexer->isNextToken(EmailLexer::GENERIC)) { - throw new ExpectingATEXT(); - } - - if (!$this->lexer->isNextTokenAny(array(EmailLexer::S_SP, EmailLexer::S_HTAB, EmailLexer::C_DEL))) { - return false; - } - - $this->warnings[QuotedPart::CODE] = - new QuotedPart($this->lexer->getPrevious()['type'], $this->lexer->token['type']); - return true; - - } - - /** - * @param bool $hasClosingQuote - * - * @return bool - */ - 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 ExpectingATEXT(); - } - - try { - $this->lexer->find(EmailLexer::S_DQUOTE); - $hasClosingQuote = true; - } catch (\Exception $e) { - throw new UnclosedQuotedString(); - } - $this->warnings[QuotedString::CODE] = new QuotedString($previous['value'], $this->lexer->token['value']); - - return $hasClosingQuote; - } - - protected function checkCRLFInFWS() - { - if ($this->lexer->token['type'] !== EmailLexer::CRLF) { - return; - } - - if (!$this->lexer->isNextTokenAny(array(EmailLexer::S_SP, EmailLexer::S_HTAB))) { - throw new CRLFX2(); - } - - if (!$this->lexer->isNextTokenAny(array(EmailLexer::S_SP, EmailLexer::S_HTAB))) { - throw new CRLFAtTheEnd(); - } - } -} diff --git a/egulias/email-validator/src/Parser/PartParser.php b/egulias/email-validator/src/Parser/PartParser.php new file mode 100644 index 00000000..fd65fc56 --- /dev/null +++ b/egulias/email-validator/src/Parser/PartParser.php @@ -0,0 +1,63 @@ +<?php + +namespace Egulias\EmailValidator\Parser; + +use Egulias\EmailValidator\EmailLexer; +use Egulias\EmailValidator\Result\InvalidEmail; +use Egulias\EmailValidator\Result\Reason\ConsecutiveDot; +use Egulias\EmailValidator\Result\Result; +use Egulias\EmailValidator\Result\ValidEmail; + +abstract class PartParser +{ + /** + * @var array + */ + protected $warnings = []; + + /** + * @var EmailLexer + */ + protected $lexer; + + public function __construct(EmailLexer $lexer) + { + $this->lexer = $lexer; + } + + abstract public function parse() : Result; + + /** + * @return \Egulias\EmailValidator\Warning\Warning[] + */ + public function getWarnings() + { + return $this->warnings; + } + + protected function parseFWS() : Result + { + $foldingWS = new FoldingWhiteSpace($this->lexer); + $resultFWS = $foldingWS->parse(); + $this->warnings = array_merge($this->warnings, $foldingWS->getWarnings()); + return $resultFWS; + } + + protected function checkConsecutiveDots() : Result + { + if ($this->lexer->token['type'] === EmailLexer::S_DOT && $this->lexer->isNextToken(EmailLexer::S_DOT)) { + return new InvalidEmail(new ConsecutiveDot(), $this->lexer->token['value']); + } + + return new ValidEmail(); + } + + protected function escaped() : bool + { + $previous = $this->lexer->getPrevious(); + + return $previous && $previous['type'] === EmailLexer::S_BACKSLASH + && + $this->lexer->token['type'] !== EmailLexer::GENERIC; + } +}
\ No newline at end of file diff --git a/egulias/email-validator/src/Result/InvalidEmail.php b/egulias/email-validator/src/Result/InvalidEmail.php new file mode 100644 index 00000000..3d85e154 --- /dev/null +++ b/egulias/email-validator/src/Result/InvalidEmail.php @@ -0,0 +1,46 @@ +<?php + +namespace Egulias\EmailValidator\Result; + +use Egulias\EmailValidator\Result\Reason\Reason; + +class InvalidEmail implements Result +{ + private $token; + /** + * @var Reason + */ + protected $reason; + + public function __construct(Reason $reason, string $token) + { + $this->token = $token; + $this->reason = $reason; + } + + public function isValid(): bool + { + return false; + } + + public function isInvalid(): bool + { + return true; + } + + public function description(): string + { + return $this->reason->description() . " in char " . $this->token; + } + + public function code(): int + { + return $this->reason->code(); + } + + public function reason() : Reason + { + return $this->reason; + } + +}
\ No newline at end of file diff --git a/egulias/email-validator/src/Result/MultipleErrors.php b/egulias/email-validator/src/Result/MultipleErrors.php new file mode 100644 index 00000000..24bf5ff7 --- /dev/null +++ b/egulias/email-validator/src/Result/MultipleErrors.php @@ -0,0 +1,54 @@ +<?php + +namespace Egulias\EmailValidator\Result; + +use Egulias\EmailValidator\Result\InvalidEmail; +use Egulias\EmailValidator\Result\Reason\Reason; + +/** + * @psalm-suppress PropertyNotSetInConstructor + */ +class MultipleErrors extends InvalidEmail +{ + /** + * @var Reason[] + */ + private $reasons = []; + + public function __construct() + { + } + + public function addReason(Reason $reason) : void + { + $this->reasons[$reason->code()] = $reason; + } + + /** + * @return Reason[] + */ + public function getReasons() : array + { + return $this->reasons; + } + + public function reason() : Reason + { + return $this->reasons[0]; + } + + public function description() : string + { + $description = ''; + foreach($this->reasons as $reason) { + $description .= $reason->description() . PHP_EOL; + } + + return $description; + } + + public function code() : int + { + return 0; + } +} diff --git a/egulias/email-validator/src/Result/Reason/AtextAfterCFWS.php b/egulias/email-validator/src/Result/Reason/AtextAfterCFWS.php new file mode 100644 index 00000000..76015a2d --- /dev/null +++ b/egulias/email-validator/src/Result/Reason/AtextAfterCFWS.php @@ -0,0 +1,16 @@ +<?php + +namespace Egulias\EmailValidator\Result\Reason; + +class AtextAfterCFWS implements Reason +{ + public function code() : int + { + return 133; + } + + public function description() : string + { + return 'ATEXT found after CFWS'; + } +}
\ No newline at end of file diff --git a/egulias/email-validator/src/Result/Reason/CRLFAtTheEnd.php b/egulias/email-validator/src/Result/Reason/CRLFAtTheEnd.php new file mode 100644 index 00000000..a0b66e71 --- /dev/null +++ b/egulias/email-validator/src/Result/Reason/CRLFAtTheEnd.php @@ -0,0 +1,19 @@ +<?php + +namespace Egulias\EmailValidator\Result\Reason; + +class CRLFAtTheEnd implements Reason +{ + const CODE = 149; + const REASON = "CRLF at the end"; + + public function code() : int + { + return 149; + } + + public function description() : string + { + return 'CRLF at the end'; + } +} diff --git a/egulias/email-validator/src/Result/Reason/CRLFX2.php b/egulias/email-validator/src/Result/Reason/CRLFX2.php new file mode 100644 index 00000000..61235649 --- /dev/null +++ b/egulias/email-validator/src/Result/Reason/CRLFX2.php @@ -0,0 +1,16 @@ +<?php + +namespace Egulias\EmailValidator\Result\Reason; + +class CRLFX2 implements Reason +{ + public function code() : int + { + return 148; + } + + public function description() : string + { + return 'CR LF tokens found twice'; + } +} diff --git a/egulias/email-validator/src/Result/Reason/CRNoLF.php b/egulias/email-validator/src/Result/Reason/CRNoLF.php new file mode 100644 index 00000000..e315ff0c --- /dev/null +++ b/egulias/email-validator/src/Result/Reason/CRNoLF.php @@ -0,0 +1,16 @@ +<?php + +namespace Egulias\EmailValidator\Result\Reason; + +class CRNoLF implements Reason +{ + public function code() : int + { + return 150; + } + + public function description() : string + { + return 'Missing LF after CR'; + } +} diff --git a/egulias/email-validator/src/Result/Reason/CharNotAllowed.php b/egulias/email-validator/src/Result/Reason/CharNotAllowed.php new file mode 100644 index 00000000..45999b36 --- /dev/null +++ b/egulias/email-validator/src/Result/Reason/CharNotAllowed.php @@ -0,0 +1,16 @@ +<?php + +namespace Egulias\EmailValidator\Result\Reason; + +class CharNotAllowed implements Reason +{ + public function code() : int + { + return 1; + } + + public function description() : string + { + return "Character not allowed"; + } +}
\ No newline at end of file diff --git a/egulias/email-validator/src/Result/Reason/CommaInDomain.php b/egulias/email-validator/src/Result/Reason/CommaInDomain.php new file mode 100644 index 00000000..93d3b561 --- /dev/null +++ b/egulias/email-validator/src/Result/Reason/CommaInDomain.php @@ -0,0 +1,16 @@ +<?php + +namespace Egulias\EmailValidator\Result\Reason; + +class CommaInDomain implements Reason +{ + public function code() : int + { + return 200; + } + + public function description() : string + { + return "Comma ',' is not allowed in domain part"; + } +}
\ No newline at end of file diff --git a/egulias/email-validator/src/Result/Reason/CommentsInIDRight.php b/egulias/email-validator/src/Result/Reason/CommentsInIDRight.php new file mode 100644 index 00000000..43a7cf7b --- /dev/null +++ b/egulias/email-validator/src/Result/Reason/CommentsInIDRight.php @@ -0,0 +1,16 @@ +<?php + +namespace Egulias\EmailValidator\Result\Reason; + +class CommentsInIDRight implements Reason +{ + public function code() : int + { + return 400; + } + + public function description() : string + { + return 'Comments are not allowed in IDRight for message-id'; + } +}
\ No newline at end of file diff --git a/egulias/email-validator/src/Result/Reason/ConsecutiveAt.php b/egulias/email-validator/src/Result/Reason/ConsecutiveAt.php new file mode 100644 index 00000000..5128dc9e --- /dev/null +++ b/egulias/email-validator/src/Result/Reason/ConsecutiveAt.php @@ -0,0 +1,17 @@ +<?php + +namespace Egulias\EmailValidator\Result\Reason; + +class ConsecutiveAt implements Reason +{ + public function code() : int + { + return 128; + } + + public function description() : string + { + return '@ found after another @'; + } + +} diff --git a/egulias/email-validator/src/Result/Reason/ConsecutiveDot.php b/egulias/email-validator/src/Result/Reason/ConsecutiveDot.php new file mode 100644 index 00000000..e7b65657 --- /dev/null +++ b/egulias/email-validator/src/Result/Reason/ConsecutiveDot.php @@ -0,0 +1,16 @@ +<?php + +namespace Egulias\EmailValidator\Result\Reason; + +class ConsecutiveDot implements Reason +{ + public function code() : int + { + return 132; + } + + public function description() : string + { + return 'Concecutive DOT found'; + } +} diff --git a/egulias/email-validator/src/Result/Reason/DetailedReason.php b/egulias/email-validator/src/Result/Reason/DetailedReason.php new file mode 100644 index 00000000..17519873 --- /dev/null +++ b/egulias/email-validator/src/Result/Reason/DetailedReason.php @@ -0,0 +1,13 @@ +<?php + +namespace Egulias\EmailValidator\Result\Reason; + +abstract class DetailedReason implements Reason +{ + protected $detailedDescription; + + public function __construct(string $details) + { + $this->detailedDescription = $details; + } +}
\ No newline at end of file diff --git a/egulias/email-validator/src/Result/Reason/DomainAcceptsNoMail.php b/egulias/email-validator/src/Result/Reason/DomainAcceptsNoMail.php new file mode 100644 index 00000000..55f44bba --- /dev/null +++ b/egulias/email-validator/src/Result/Reason/DomainAcceptsNoMail.php @@ -0,0 +1,16 @@ +<?php + +namespace Egulias\EmailValidator\Result\Reason; + +class DomainAcceptsNoMail implements Reason +{ + public function code() : int + { + return 154; + } + + public function description() : string + { + return 'Domain accepts no mail (Null MX, RFC7505)'; + } +}
\ No newline at end of file diff --git a/egulias/email-validator/src/Result/Reason/DomainHyphened.php b/egulias/email-validator/src/Result/Reason/DomainHyphened.php new file mode 100644 index 00000000..2944eb8e --- /dev/null +++ b/egulias/email-validator/src/Result/Reason/DomainHyphened.php @@ -0,0 +1,16 @@ +<?php + +namespace Egulias\EmailValidator\Result\Reason; + +class DomainHyphened extends DetailedReason +{ + public function code() : int + { + return 144; + } + + public function description() : string + { + return 'S_HYPHEN found in domain'; + } +} diff --git a/egulias/email-validator/src/Result/Reason/DomainTooLong.php b/egulias/email-validator/src/Result/Reason/DomainTooLong.php new file mode 100644 index 00000000..fa172132 --- /dev/null +++ b/egulias/email-validator/src/Result/Reason/DomainTooLong.php @@ -0,0 +1,16 @@ +<?php + +namespace Egulias\EmailValidator\Result\Reason; + +class DomainTooLong implements Reason +{ + public function code() : int + { + return 244; + } + + public function description() : string + { + return 'Domain is longer than 253 characters'; + } +} diff --git a/egulias/email-validator/src/Result/Reason/DotAtEnd.php b/egulias/email-validator/src/Result/Reason/DotAtEnd.php new file mode 100644 index 00000000..6dfe6055 --- /dev/null +++ b/egulias/email-validator/src/Result/Reason/DotAtEnd.php @@ -0,0 +1,16 @@ +<?php + +namespace Egulias\EmailValidator\Result\Reason; + +class DotAtEnd implements Reason +{ + public function code() : int + { + return 142; + } + + public function description() : string + { + return 'Dot at the end'; + } +} diff --git a/egulias/email-validator/src/Result/Reason/DotAtStart.php b/egulias/email-validator/src/Result/Reason/DotAtStart.php new file mode 100644 index 00000000..b564f1b5 --- /dev/null +++ b/egulias/email-validator/src/Result/Reason/DotAtStart.php @@ -0,0 +1,16 @@ +<?php + +namespace Egulias\EmailValidator\Result\Reason; + +class DotAtStart implements Reason +{ + public function code() : int + { + return 141; + } + + public function description() : string + { + return "Starts with a DOT"; + } +} diff --git a/egulias/email-validator/src/Result/Reason/ExceptionFound.php b/egulias/email-validator/src/Result/Reason/ExceptionFound.php new file mode 100644 index 00000000..8b1135d0 --- /dev/null +++ b/egulias/email-validator/src/Result/Reason/ExceptionFound.php @@ -0,0 +1,26 @@ +<?php + +namespace Egulias\EmailValidator\Result\Reason; + +class ExceptionFound implements Reason +{ + /** + * @var \Exception + */ + private $exception; + + public function __construct(\Exception $exception) + { + $this->exception = $exception; + + } + public function code() : int + { + return 999; + } + + public function description() : string + { + return $this->exception->getMessage(); + } +}
\ No newline at end of file diff --git a/egulias/email-validator/src/Result/Reason/ExpectingATEXT.php b/egulias/email-validator/src/Result/Reason/ExpectingATEXT.php new file mode 100644 index 00000000..07ea8d23 --- /dev/null +++ b/egulias/email-validator/src/Result/Reason/ExpectingATEXT.php @@ -0,0 +1,16 @@ +<?php + +namespace Egulias\EmailValidator\Result\Reason; + +class ExpectingATEXT extends DetailedReason +{ + public function code() : int + { + return 137; + } + + public function description() : string + { + return "Expecting ATEXT (Printable US-ASCII). Extended: " . $this->detailedDescription; + } +} diff --git a/egulias/email-validator/src/Result/Reason/ExpectingCTEXT.php b/egulias/email-validator/src/Result/Reason/ExpectingCTEXT.php new file mode 100644 index 00000000..64f5f7c3 --- /dev/null +++ b/egulias/email-validator/src/Result/Reason/ExpectingCTEXT.php @@ -0,0 +1,16 @@ +<?php + +namespace Egulias\EmailValidator\Result\Reason; + +class ExpectingCTEXT implements Reason +{ + public function code() : int + { + return 139; + } + + public function description() : string + { + return 'Expecting CTEXT'; + } +} diff --git a/egulias/email-validator/src/Result/Reason/ExpectingDTEXT.php b/egulias/email-validator/src/Result/Reason/ExpectingDTEXT.php new file mode 100644 index 00000000..e47c251b --- /dev/null +++ b/egulias/email-validator/src/Result/Reason/ExpectingDTEXT.php @@ -0,0 +1,16 @@ +<?php + +namespace Egulias\EmailValidator\Result\Reason; + +class ExpectingDTEXT implements Reason +{ + public function code() : int + { + return 129; + } + + public function description() : string + { + return 'Expecting DTEXT'; + } +} diff --git a/egulias/email-validator/src/Result/Reason/ExpectingDomainLiteralClose.php b/egulias/email-validator/src/Result/Reason/ExpectingDomainLiteralClose.php new file mode 100644 index 00000000..7deffcaf --- /dev/null +++ b/egulias/email-validator/src/Result/Reason/ExpectingDomainLiteralClose.php @@ -0,0 +1,16 @@ +<?php + +namespace Egulias\EmailValidator\Result\Reason; + +class ExpectingDomainLiteralClose implements Reason +{ + public function code() : int + { + return 137; + } + + public function description() : string + { + return "Closing bracket ']' for domain literal not found"; + } +}
\ No newline at end of file diff --git a/egulias/email-validator/src/Result/Reason/LabelTooLong.php b/egulias/email-validator/src/Result/Reason/LabelTooLong.php new file mode 100644 index 00000000..e181ef92 --- /dev/null +++ b/egulias/email-validator/src/Result/Reason/LabelTooLong.php @@ -0,0 +1,16 @@ +<?php + +namespace Egulias\EmailValidator\Result\Reason; + +class LabelTooLong implements Reason +{ + public function code() : int + { + return 245; + } + + public function description() : string + { + return 'Domain "label" is longer than 63 characters'; + } +} diff --git a/egulias/email-validator/src/Result/Reason/LocalOrReservedDomain.php b/egulias/email-validator/src/Result/Reason/LocalOrReservedDomain.php new file mode 100644 index 00000000..bc7c5d5a --- /dev/null +++ b/egulias/email-validator/src/Result/Reason/LocalOrReservedDomain.php @@ -0,0 +1,16 @@ +<?php + +namespace Egulias\EmailValidator\Result\Reason; + +class LocalOrReservedDomain implements Reason +{ + public function code() : int + { + return 153; + } + + public function description() : string + { + return 'Local, mDNS or reserved domain (RFC2606, RFC6762)'; + } +}
\ No newline at end of file diff --git a/egulias/email-validator/src/Result/Reason/NoDNSRecord.php b/egulias/email-validator/src/Result/Reason/NoDNSRecord.php new file mode 100644 index 00000000..e217d02c --- /dev/null +++ b/egulias/email-validator/src/Result/Reason/NoDNSRecord.php @@ -0,0 +1,16 @@ +<?php + +namespace Egulias\EmailValidator\Result\Reason; + +class NoDNSRecord implements Reason +{ + public function code() : int + { + return 5; + } + + public function description() : string + { + return 'No MX or A DSN record was found for this email'; + } +}
\ No newline at end of file diff --git a/egulias/email-validator/src/Result/Reason/NoDomainPart.php b/egulias/email-validator/src/Result/Reason/NoDomainPart.php new file mode 100644 index 00000000..bbbb04be --- /dev/null +++ b/egulias/email-validator/src/Result/Reason/NoDomainPart.php @@ -0,0 +1,16 @@ +<?php + +namespace Egulias\EmailValidator\Result\Reason; + +class NoDomainPart implements Reason +{ + public function code() : int + { + return 131; + } + + public function description() : string + { + return 'No domain part found'; + } +} diff --git a/egulias/email-validator/src/Result/Reason/NoLocalPart.php b/egulias/email-validator/src/Result/Reason/NoLocalPart.php new file mode 100644 index 00000000..984c0619 --- /dev/null +++ b/egulias/email-validator/src/Result/Reason/NoLocalPart.php @@ -0,0 +1,16 @@ +<?php + +namespace Egulias\EmailValidator\Result\Reason; + +class NoLocalPart implements Reason +{ + public function code() : int + { + return 130; + } + + public function description() : string + { + return "No local part"; + } +} diff --git a/egulias/email-validator/src/Result/Reason/RFCWarnings.php b/egulias/email-validator/src/Result/Reason/RFCWarnings.php new file mode 100644 index 00000000..e6ff29a3 --- /dev/null +++ b/egulias/email-validator/src/Result/Reason/RFCWarnings.php @@ -0,0 +1,16 @@ +<?php + +namespace Egulias\EmailValidator\Result\Reason; + +class RFCWarnings implements Reason +{ + public function code() : int + { + return 997; + } + + public function description() : string + { + return 'Warnings found after validating'; + } +} diff --git a/egulias/email-validator/src/Result/Reason/Reason.php b/egulias/email-validator/src/Result/Reason/Reason.php new file mode 100644 index 00000000..e6810b93 --- /dev/null +++ b/egulias/email-validator/src/Result/Reason/Reason.php @@ -0,0 +1,16 @@ +<?php + +namespace Egulias\EmailValidator\Result\Reason; + +interface Reason +{ + /** + * Code for user land to act upon; + */ + public function code() : int; + + /** + * Short description of the result, human readable. + */ + public function description() : string; +}
\ No newline at end of file diff --git a/egulias/email-validator/src/Result/Reason/SpoofEmail.php b/egulias/email-validator/src/Result/Reason/SpoofEmail.php new file mode 100644 index 00000000..da669cc8 --- /dev/null +++ b/egulias/email-validator/src/Result/Reason/SpoofEmail.php @@ -0,0 +1,17 @@ +<?php + +namespace Egulias\EmailValidator\Result\Reason; + +class SpoofEmail implements Reason +{ + public function code() : int + { + return 298; + } + + public function description() : string + { + return 'The email contains mixed UTF8 chars that makes it suspicious'; + } + +} diff --git a/egulias/email-validator/src/Result/Reason/UnOpenedComment.php b/egulias/email-validator/src/Result/Reason/UnOpenedComment.php new file mode 100644 index 00000000..cc7915ca --- /dev/null +++ b/egulias/email-validator/src/Result/Reason/UnOpenedComment.php @@ -0,0 +1,16 @@ +<?php + +namespace Egulias\EmailValidator\Result\Reason; + +class UnOpenedComment implements Reason +{ + public function code() : int + { + return 152; + } + + public function description(): string + { + return 'Missing openning comment parentheses - https://tools.ietf.org/html/rfc5322#section-3.2.2'; + } +}
\ No newline at end of file diff --git a/egulias/email-validator/src/Result/Reason/UnableToGetDNSRecord.php b/egulias/email-validator/src/Result/Reason/UnableToGetDNSRecord.php new file mode 100644 index 00000000..f178b1a0 --- /dev/null +++ b/egulias/email-validator/src/Result/Reason/UnableToGetDNSRecord.php @@ -0,0 +1,19 @@ +<?php + +namespace Egulias\EmailValidator\Result\Reason; + +/** + * Used on SERVFAIL, TIMEOUT or other runtime and network errors + */ +class UnableToGetDNSRecord extends NoDNSRecord +{ + public function code() : int + { + return 3; + } + + public function description() : string + { + return 'Unable to get DNS records for the host'; + } +} diff --git a/egulias/email-validator/src/Result/Reason/UnclosedComment.php b/egulias/email-validator/src/Result/Reason/UnclosedComment.php new file mode 100644 index 00000000..4ac41a0b --- /dev/null +++ b/egulias/email-validator/src/Result/Reason/UnclosedComment.php @@ -0,0 +1,16 @@ +<?php + +namespace Egulias\EmailValidator\Result\Reason; + +class UnclosedComment implements Reason +{ + public function code() : int + { + return 146; + } + + public function description(): string + { + return 'No closing comment token found'; + } +} diff --git a/egulias/email-validator/src/Result/Reason/UnclosedQuotedString.php b/egulias/email-validator/src/Result/Reason/UnclosedQuotedString.php new file mode 100644 index 00000000..f42c4225 --- /dev/null +++ b/egulias/email-validator/src/Result/Reason/UnclosedQuotedString.php @@ -0,0 +1,16 @@ +<?php + +namespace Egulias\EmailValidator\Result\Reason; + +class UnclosedQuotedString implements Reason +{ + public function code() : int + { + return 145; + } + + public function description() : string + { + return "Unclosed quoted string"; + } +} diff --git a/egulias/email-validator/src/Result/Reason/UnusualElements.php b/egulias/email-validator/src/Result/Reason/UnusualElements.php new file mode 100644 index 00000000..03873dc0 --- /dev/null +++ b/egulias/email-validator/src/Result/Reason/UnusualElements.php @@ -0,0 +1,26 @@ +<?php + +namespace Egulias\EmailValidator\Result\Reason; + +class UnusualElements implements Reason +{ + /** + * @var string $element + */ + private $element = ''; + + public function __construct(string $element) + { + $this->element = $element; + } + + public function code() : int + { + return 201; + } + + public function description() : string + { + return 'Unusual element found, wourld render invalid in majority of cases. Element found: ' . $this->element; + } +}
\ No newline at end of file diff --git a/egulias/email-validator/src/Result/Result.php b/egulias/email-validator/src/Result/Result.php new file mode 100644 index 00000000..1e16bcca --- /dev/null +++ b/egulias/email-validator/src/Result/Result.php @@ -0,0 +1,27 @@ +<?php + +namespace Egulias\EmailValidator\Result; + +interface Result +{ + /** + * Is validation result valid? + */ + public function isValid() : bool; + + /** + * Is validation result invalid? + * Usually the inverse of isValid() + */ + public function isInvalid() : bool; + + /** + * Short description of the result, human readable. + */ + public function description() : string; + + /** + * Code for user land to act upon. + */ + public function code() : int; +}
\ No newline at end of file diff --git a/egulias/email-validator/src/Result/SpoofEmail.php b/egulias/email-validator/src/Result/SpoofEmail.php new file mode 100644 index 00000000..9f010de1 --- /dev/null +++ b/egulias/email-validator/src/Result/SpoofEmail.php @@ -0,0 +1,14 @@ +<?php +namespace Egulias\EmailValidator\Result; + +use Egulias\EmailValidator\Result\InvalidEmail; +use Egulias\EmailValidator\Result\Reason\SpoofEmail as ReasonSpoofEmail; + +class SpoofEmail extends InvalidEmail +{ + public function __construct() + { + $this->reason = new ReasonSpoofEmail(); + parent::__construct($this->reason, ''); + } +}
\ No newline at end of file diff --git a/egulias/email-validator/src/Result/ValidEmail.php b/egulias/email-validator/src/Result/ValidEmail.php new file mode 100644 index 00000000..4f3693a9 --- /dev/null +++ b/egulias/email-validator/src/Result/ValidEmail.php @@ -0,0 +1,27 @@ +<?php + +namespace Egulias\EmailValidator\Result; + +class ValidEmail implements Result +{ + public function isValid(): bool + { + return true; + } + + public function isInvalid(): bool + { + return false; + } + + public function description(): string + { + return "Valid email"; + } + + public function code(): int + { + return 0; + } + +}
\ No newline at end of file diff --git a/egulias/email-validator/src/Validation/DNSCheckValidation.php b/egulias/email-validator/src/Validation/DNSCheckValidation.php index 491082a5..a9357278 100644 --- a/egulias/email-validator/src/Validation/DNSCheckValidation.php +++ b/egulias/email-validator/src/Validation/DNSCheckValidation.php @@ -3,15 +3,21 @@ namespace Egulias\EmailValidator\Validation; use Egulias\EmailValidator\EmailLexer; -use Egulias\EmailValidator\Exception\InvalidEmail; -use Egulias\EmailValidator\Exception\LocalOrReservedDomain; -use Egulias\EmailValidator\Exception\DomainAcceptsNoMail; +use Egulias\EmailValidator\Result\InvalidEmail; +use Egulias\EmailValidator\Result\Reason\DomainAcceptsNoMail; +use Egulias\EmailValidator\Result\Reason\LocalOrReservedDomain; +use Egulias\EmailValidator\Result\Reason\NoDNSRecord as ReasonNoDNSRecord; +use Egulias\EmailValidator\Result\Reason\UnableToGetDNSRecord; use Egulias\EmailValidator\Warning\NoDNSMXRecord; -use Egulias\EmailValidator\Exception\NoDNSRecord; class DNSCheckValidation implements EmailValidation { /** + * @var int + */ + protected const DNS_RECORD_TYPES_TO_CHECK = DNS_MX + DNS_A + DNS_AAAA; + + /** * @var array */ private $warnings = []; @@ -34,7 +40,7 @@ class DNSCheckValidation implements EmailValidation } } - public function isValid($email, EmailLexer $emailLexer) + public function isValid(string $email, EmailLexer $emailLexer) : bool { // use the input to check DNS if we cannot extract something similar to a domain $host = $email; @@ -73,19 +79,19 @@ class DNSCheckValidation implements EmailValidation // Exclude reserved top level DNS names if ($isLocalDomain || $isReservedTopLevel) { - $this->error = new LocalOrReservedDomain(); + $this->error = new InvalidEmail(new LocalOrReservedDomain(), $host); return false; } return $this->checkDns($host); } - public function getError() + public function getError() : ?InvalidEmail { return $this->error; } - public function getWarnings() + public function getWarnings() : array { return $this->warnings; } @@ -112,31 +118,43 @@ class DNSCheckValidation implements EmailValidation * * @return bool True on success. */ - private function validateDnsRecords($host) + private function validateDnsRecords($host) : bool { - // Get all MX, A and AAAA DNS records for host - // Using @ as workaround to fix https://bugs.php.net/bug.php?id=73149 - $dnsRecords = @dns_get_record($host, DNS_MX + DNS_A + DNS_AAAA); + // A workaround to fix https://bugs.php.net/bug.php?id=73149 + /** @psalm-suppress InvalidArgument */ + set_error_handler( + static function (int $errorLevel, string $errorMessage): ?bool { + throw new \RuntimeException("Unable to get DNS record for the host: $errorMessage"); + } + ); + + try { + // Get all MX, A and AAAA DNS records for host + $dnsRecords = dns_get_record($host, static::DNS_RECORD_TYPES_TO_CHECK); + } catch (\RuntimeException $exception) { + $this->error = new InvalidEmail(new UnableToGetDNSRecord(), ''); + return false; + } finally { + restore_error_handler(); + } // No MX, A or AAAA DNS records - if (empty($dnsRecords)) { - $this->error = new NoDNSRecord(); + if ($dnsRecords === [] || $dnsRecords === false) { + $this->error = new InvalidEmail(new ReasonNoDNSRecord(), ''); return false; } // For each DNS record foreach ($dnsRecords as $dnsRecord) { if (!$this->validateMXRecord($dnsRecord)) { + // No MX records (fallback to A or AAAA records) + if (empty($this->mxRecords)) { + $this->warnings[NoDNSMXRecord::CODE] = new NoDNSMXRecord(); + } return false; } } - - // No MX records (fallback to A or AAAA records) - if (empty($this->mxRecords)) { - $this->warnings[NoDNSMXRecord::CODE] = new NoDNSMXRecord(); - } - return true; } @@ -147,7 +165,7 @@ class DNSCheckValidation implements EmailValidation * * @return bool True if valid. */ - private function validateMxRecord($dnsRecord) + private function validateMxRecord($dnsRecord) : bool { if ($dnsRecord['type'] !== 'MX') { return true; @@ -155,7 +173,7 @@ class DNSCheckValidation implements EmailValidation // "Null MX" record indicates the domain accepts no mail (https://tools.ietf.org/html/rfc7505) if (empty($dnsRecord['target']) || $dnsRecord['target'] === '.') { - $this->error = new DomainAcceptsNoMail(); + $this->error = new InvalidEmail(new DomainAcceptsNoMail(), ""); return false; } @@ -163,4 +181,4 @@ class DNSCheckValidation implements EmailValidation return true; } -} +}
\ No newline at end of file diff --git a/egulias/email-validator/src/Validation/EmailValidation.php b/egulias/email-validator/src/Validation/EmailValidation.php index d5a015be..1bcc0a70 100644 --- a/egulias/email-validator/src/Validation/EmailValidation.php +++ b/egulias/email-validator/src/Validation/EmailValidation.php @@ -3,7 +3,7 @@ namespace Egulias\EmailValidator\Validation; use Egulias\EmailValidator\EmailLexer; -use Egulias\EmailValidator\Exception\InvalidEmail; +use Egulias\EmailValidator\Result\InvalidEmail; use Egulias\EmailValidator\Warning\Warning; interface EmailValidation @@ -16,19 +16,19 @@ interface EmailValidation * * @return bool */ - public function isValid($email, EmailLexer $emailLexer); + public function isValid(string $email, EmailLexer $emailLexer) : bool; /** * Returns the validation error. * * @return InvalidEmail|null */ - public function getError(); + public function getError() : ?InvalidEmail; /** * Returns the validation warnings. * * @return Warning[] */ - public function getWarnings(); + public function getWarnings() : array; } diff --git a/egulias/email-validator/src/Validation/Error/RFCWarnings.php b/egulias/email-validator/src/Validation/Error/RFCWarnings.php deleted file mode 100644 index 7f2256d6..00000000 --- a/egulias/email-validator/src/Validation/Error/RFCWarnings.php +++ /dev/null @@ -1,11 +0,0 @@ -<?php - -namespace Egulias\EmailValidator\Validation\Error; - -use Egulias\EmailValidator\Exception\InvalidEmail; - -class RFCWarnings extends InvalidEmail -{ - const CODE = 997; - const REASON = 'Warnings were found.'; -} diff --git a/egulias/email-validator/src/Validation/Error/SpoofEmail.php b/egulias/email-validator/src/Validation/Error/SpoofEmail.php deleted file mode 100644 index 8c92cb5a..00000000 --- a/egulias/email-validator/src/Validation/Error/SpoofEmail.php +++ /dev/null @@ -1,11 +0,0 @@ -<?php - -namespace Egulias\EmailValidator\Validation\Error; - -use Egulias\EmailValidator\Exception\InvalidEmail; - -class SpoofEmail extends InvalidEmail -{ - const CODE = 998; - const REASON = "The email contains mixed UTF8 chars that makes it suspicious"; -} diff --git a/egulias/email-validator/src/Validation/SpoofCheckValidation.php b/egulias/email-validator/src/Validation/Extra/SpoofCheckValidation.php index e10bfabd..4972dbce 100644 --- a/egulias/email-validator/src/Validation/SpoofCheckValidation.php +++ b/egulias/email-validator/src/Validation/Extra/SpoofCheckValidation.php @@ -1,11 +1,12 @@ <?php -namespace Egulias\EmailValidator\Validation; +namespace Egulias\EmailValidator\Validation\Extra; -use Egulias\EmailValidator\EmailLexer; -use Egulias\EmailValidator\Exception\InvalidEmail; -use Egulias\EmailValidator\Validation\Error\SpoofEmail; use \Spoofchecker; +use Egulias\EmailValidator\EmailLexer; +use Egulias\EmailValidator\Result\SpoofEmail; +use Egulias\EmailValidator\Result\InvalidEmail; +use Egulias\EmailValidator\Validation\EmailValidation; class SpoofCheckValidation implements EmailValidation { @@ -24,7 +25,7 @@ class SpoofCheckValidation implements EmailValidation /** * @psalm-suppress InvalidArgument */ - public function isValid($email, EmailLexer $emailLexer) + public function isValid(string $email, EmailLexer $emailLexer) : bool { $checker = new Spoofchecker(); $checker->setChecks(Spoofchecker::SINGLE_SCRIPT); @@ -37,14 +38,14 @@ class SpoofCheckValidation implements EmailValidation } /** - * @return InvalidEmail|null + * @return InvalidEmail */ - public function getError() + public function getError() : ?InvalidEmail { return $this->error; } - public function getWarnings() + public function getWarnings() : array { return []; } diff --git a/egulias/email-validator/src/Validation/MessageIDValidation.php b/egulias/email-validator/src/Validation/MessageIDValidation.php new file mode 100644 index 00000000..0e020433 --- /dev/null +++ b/egulias/email-validator/src/Validation/MessageIDValidation.php @@ -0,0 +1,51 @@ +<?php + +namespace Egulias\EmailValidator\Validation; + +use Egulias\EmailValidator\EmailLexer; +use Egulias\EmailValidator\MessageIDParser; +use Egulias\EmailValidator\Result\InvalidEmail; +use Egulias\EmailValidator\Result\Reason\ExceptionFound; + +class MessageIDValidation implements EmailValidation +{ + + /** + * @var array + */ + private $warnings = []; + + /** + * @var ?InvalidEmail + */ + private $error; + + public function isValid(string $email, EmailLexer $emailLexer): bool + { + $parser = new MessageIDParser($emailLexer); + try { + $result = $parser->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/egulias/email-validator/src/Validation/MultipleErrors.php b/egulias/email-validator/src/Validation/MultipleErrors.php deleted file mode 100644 index 3be59732..00000000 --- a/egulias/email-validator/src/Validation/MultipleErrors.php +++ /dev/null @@ -1,32 +0,0 @@ -<?php - -namespace Egulias\EmailValidator\Validation; - -use Egulias\EmailValidator\Exception\InvalidEmail; - -class MultipleErrors extends InvalidEmail -{ - const CODE = 999; - const REASON = "Accumulated errors for multiple validations"; - /** - * @var InvalidEmail[] - */ - private $errors = []; - - /** - * @param InvalidEmail[] $errors - */ - public function __construct(array $errors) - { - $this->errors = $errors; - parent::__construct(); - } - - /** - * @return InvalidEmail[] - */ - public function getErrors() - { - return $this->errors; - } -} diff --git a/egulias/email-validator/src/Validation/MultipleValidationWithAnd.php b/egulias/email-validator/src/Validation/MultipleValidationWithAnd.php index feb22402..6debf22f 100644 --- a/egulias/email-validator/src/Validation/MultipleValidationWithAnd.php +++ b/egulias/email-validator/src/Validation/MultipleValidationWithAnd.php @@ -3,13 +3,15 @@ namespace Egulias\EmailValidator\Validation; use Egulias\EmailValidator\EmailLexer; +use Egulias\EmailValidator\Result\InvalidEmail; use Egulias\EmailValidator\Validation\Exception\EmptyValidationList; +use Egulias\EmailValidator\Result\MultipleErrors; class MultipleValidationWithAnd implements EmailValidation { /** - * If one of validations gets failure skips all succeeding validation. - * This means MultipleErrors will only contain a single error which first found. + * If one of validations fails, the remaining validations will be skept. + * This means MultipleErrors will only contain a single error, the first found. */ const STOP_ON_ERROR = 0; @@ -56,60 +58,51 @@ class MultipleValidationWithAnd implements EmailValidation /** * {@inheritdoc} */ - public function isValid($email, EmailLexer $emailLexer) + public function isValid(string $email, EmailLexer $emailLexer) : bool { $result = true; - $errors = []; foreach ($this->validations as $validation) { $emailLexer->reset(); $validationResult = $validation->isValid($email, $emailLexer); $result = $result && $validationResult; $this->warnings = array_merge($this->warnings, $validation->getWarnings()); - $errors = $this->addNewError($validation->getError(), $errors); + if (!$validationResult) { + $this->processError($validation); + } if ($this->shouldStop($result)) { break; } } - if (!empty($errors)) { - $this->error = new MultipleErrors($errors); - } - return $result; } - /** - * @param \Egulias\EmailValidator\Exception\InvalidEmail|null $possibleError - * @param \Egulias\EmailValidator\Exception\InvalidEmail[] $errors - * - * @return \Egulias\EmailValidator\Exception\InvalidEmail[] - */ - private function addNewError($possibleError, array $errors) + private function initErrorStorage() : void { - if (null !== $possibleError) { - $errors[] = $possibleError; + if (null === $this->error) { + $this->error = new MultipleErrors(); } + } - return $errors; + private function processError(EmailValidation $validation) : void + { + if (null !== $validation->getError()) { + $this->initErrorStorage(); + /** @psalm-suppress PossiblyNullReference */ + $this->error->addReason($validation->getError()->reason()); + } } - /** - * @param bool $result - * - * @return bool - */ - private function shouldStop($result) + private function shouldStop(bool $result) : bool { return !$result && $this->mode === self::STOP_ON_ERROR; } /** * Returns the validation errors. - * - * @return MultipleErrors|null */ - public function getError() + public function getError() : ?InvalidEmail { return $this->error; } @@ -117,7 +110,7 @@ class MultipleValidationWithAnd implements EmailValidation /** * {@inheritdoc} */ - public function getWarnings() + public function getWarnings() : array { return $this->warnings; } diff --git a/egulias/email-validator/src/Validation/NoRFCWarningsValidation.php b/egulias/email-validator/src/Validation/NoRFCWarningsValidation.php index 6b31e544..06885ed7 100644 --- a/egulias/email-validator/src/Validation/NoRFCWarningsValidation.php +++ b/egulias/email-validator/src/Validation/NoRFCWarningsValidation.php @@ -3,8 +3,8 @@ namespace Egulias\EmailValidator\Validation; use Egulias\EmailValidator\EmailLexer; -use Egulias\EmailValidator\Exception\InvalidEmail; -use Egulias\EmailValidator\Validation\Error\RFCWarnings; +use Egulias\EmailValidator\Result\InvalidEmail; +use Egulias\EmailValidator\Result\Reason\RFCWarnings; class NoRFCWarningsValidation extends RFCValidation { @@ -16,7 +16,7 @@ class NoRFCWarningsValidation extends RFCValidation /** * {@inheritdoc} */ - public function isValid($email, EmailLexer $emailLexer) + public function isValid(string $email, EmailLexer $emailLexer) : bool { if (!parent::isValid($email, $emailLexer)) { return false; @@ -26,7 +26,7 @@ class NoRFCWarningsValidation extends RFCValidation return true; } - $this->error = new RFCWarnings(); + $this->error = new InvalidEmail(new RFCWarnings(), ''); return false; } @@ -34,7 +34,7 @@ class NoRFCWarningsValidation extends RFCValidation /** * {@inheritdoc} */ - public function getError() + public function getError() : ?InvalidEmail { return $this->error ?: parent::getError(); } diff --git a/egulias/email-validator/src/Validation/RFCValidation.php b/egulias/email-validator/src/Validation/RFCValidation.php index 8781e0b6..e2c27bac 100644 --- a/egulias/email-validator/src/Validation/RFCValidation.php +++ b/egulias/email-validator/src/Validation/RFCValidation.php @@ -4,7 +4,8 @@ namespace Egulias\EmailValidator\Validation; use Egulias\EmailValidator\EmailLexer; use Egulias\EmailValidator\EmailParser; -use Egulias\EmailValidator\Exception\InvalidEmail; +use Egulias\EmailValidator\Result\InvalidEmail; +use Egulias\EmailValidator\Result\Reason\ExceptionFound; class RFCValidation implements EmailValidation { @@ -19,30 +20,35 @@ class RFCValidation implements EmailValidation private $warnings = []; /** - * @var InvalidEmail|null + * @var ?InvalidEmail */ private $error; - public function isValid($email, EmailLexer $emailLexer) + public function isValid(string $email, EmailLexer $emailLexer) : bool { $this->parser = new EmailParser($emailLexer); try { - $this->parser->parse((string)$email); - } catch (InvalidEmail $invalid) { - $this->error = $invalid; + $result = $this->parser->parse($email); + $this->warnings = $this->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; } - $this->warnings = $this->parser->getWarnings(); return true; } - public function getError() + public function getError() : ?InvalidEmail { return $this->error; } - public function getWarnings() + public function getWarnings() : array { return $this->warnings; } diff --git a/egulias/email-validator/src/Warning/DomainTooLong.php b/egulias/email-validator/src/Warning/DomainTooLong.php deleted file mode 100644 index 61ff17a7..00000000 --- a/egulias/email-validator/src/Warning/DomainTooLong.php +++ /dev/null @@ -1,14 +0,0 @@ -<?php - -namespace Egulias\EmailValidator\Warning; - -class DomainTooLong extends Warning -{ - const CODE = 255; - - public function __construct() - { - $this->message = 'Domain is too long, exceeds 255 chars'; - $this->rfcNumber = 5322; - } -} diff --git a/egulias/email-validator/src/Warning/LabelTooLong.php b/egulias/email-validator/src/Warning/LabelTooLong.php deleted file mode 100644 index daf07f40..00000000 --- a/egulias/email-validator/src/Warning/LabelTooLong.php +++ /dev/null @@ -1,14 +0,0 @@ -<?php - -namespace Egulias\EmailValidator\Warning; - -class LabelTooLong extends Warning -{ - const CODE = 63; - - public function __construct() - { - $this->message = 'Label too long'; - $this->rfcNumber = 5322; - } -} diff --git a/egulias/email-validator/src/Warning/Warning.php b/egulias/email-validator/src/Warning/Warning.php index a2ee7b0d..bce7e7a5 100644 --- a/egulias/email-validator/src/Warning/Warning.php +++ b/egulias/email-validator/src/Warning/Warning.php @@ -29,7 +29,7 @@ abstract class Warning */ public function code() { - return static::CODE; + return self::CODE; } /** |