Add NormEmail

This commit is contained in:
Visman 2019-12-25 19:10:00 +07:00
parent f04dc6f280
commit 2b5a996578
11 changed files with 464 additions and 20 deletions

View file

@ -4,6 +4,7 @@
"homepage": "https://github.com/forkbb",
"type": "project",
"license": "MIT",
"minimum-stability": "dev",
"authors": [
{
"name": "Visman",
@ -27,6 +28,7 @@
"ext-gd": "*",
"ext-mbstring": "*",
"artoodetoo/dirk": "dev-visman",
"miovisman/parserus": "dev-master"
"miovisman/parserus": "dev-master",
"miovisman/normemail": "dev-master"
}
}

60
composer.lock generated
View file

@ -1,11 +1,10 @@
{
"_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#composer-lock-the-lock-file",
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"hash": "533b558f4bef9f6e04f96421dd776ab4",
"content-hash": "6646dc09855f04f546d6d92253e01a30",
"content-hash": "dd233d78a2c422cc3c940a860acb6b41",
"packages": [
{
"name": "artoodetoo/dirk",
@ -58,7 +57,53 @@
"support": {
"source": "https://github.com/MioVisman/dirk/tree/visman"
},
"time": "2019-11-28 13:14:33"
"time": "2019-11-28T13:14:33+00:00"
},
{
"name": "miovisman/normemail",
"version": "dev-master",
"source": {
"type": "git",
"url": "https://github.com/MioVisman/NormEmail.git",
"reference": "c582b1359adf1ee2efa38809e8827ed21138a9ac"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/MioVisman/NormEmail/zipball/c582b1359adf1ee2efa38809e8827ed21138a9ac",
"reference": "c582b1359adf1ee2efa38809e8827ed21138a9ac",
"shasum": ""
},
"require": {
"ext-intl": "*",
"ext-mbstring": "*",
"php": ">=5.6.0"
},
"type": "library",
"autoload": {
"psr-4": {
"MioVisman\\NormEmail\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Visman",
"email": "mio.visman@yandex.ru",
"homepage": "https://github.com/MioVisman"
}
],
"description": "Normalization of email for bans, for request cache to SFS or for checking the uniqueness of new users.",
"homepage": "https://github.com/MioVisman/NormEmail",
"keywords": [
"ban",
"canonical email",
"email",
"normalization"
],
"time": "2019-12-24T15:42:43+00:00"
},
{
"name": "miovisman/parserus",
@ -100,15 +145,16 @@
"bbcode",
"parser"
],
"time": "2019-10-11 12:06:27"
"time": "2019-10-11T12:06:27+00:00"
}
],
"packages-dev": [],
"aliases": [],
"minimum-stability": "stable",
"minimum-stability": "dev",
"stability-flags": {
"artoodetoo/dirk": 20,
"miovisman/parserus": 20
"miovisman/parserus": 20,
"miovisman/normemail": 20
},
"prefer-stable": false,
"prefer-lowest": false,

View file

@ -55,6 +55,7 @@ class ClassLoader
private $classMap = array();
private $classMapAuthoritative = false;
private $missingClasses = array();
private $apcuPrefix;
public function getPrefixes()
{
@ -271,6 +272,26 @@ class ClassLoader
return $this->classMapAuthoritative;
}
/**
* APCu prefix to use to cache found/not-found classes, if the extension is enabled.
*
* @param string|null $apcuPrefix
*/
public function setApcuPrefix($apcuPrefix)
{
$this->apcuPrefix = function_exists('apcu_fetch') && filter_var(ini_get('apc.enabled'), FILTER_VALIDATE_BOOLEAN) ? $apcuPrefix : null;
}
/**
* The APCu prefix in use, or null if APCu caching is not enabled.
*
* @return string|null
*/
public function getApcuPrefix()
{
return $this->apcuPrefix;
}
/**
* Registers this instance as an autoloader.
*
@ -313,11 +334,6 @@ class ClassLoader
*/
public function findFile($class)
{
// work around for PHP 5.3.0 - 5.3.2 https://bugs.php.net/50731
if ('\\' == $class[0]) {
$class = substr($class, 1);
}
// class map lookup
if (isset($this->classMap[$class])) {
return $this->classMap[$class];
@ -325,6 +341,12 @@ class ClassLoader
if ($this->classMapAuthoritative || isset($this->missingClasses[$class])) {
return false;
}
if (null !== $this->apcuPrefix) {
$file = apcu_fetch($this->apcuPrefix.$class, $hit);
if ($hit) {
return $file;
}
}
$file = $this->findFileWithExtension($class, '.php');
@ -333,6 +355,10 @@ class ClassLoader
$file = $this->findFileWithExtension($class, '.hh');
}
if (null !== $this->apcuPrefix) {
apcu_add($this->apcuPrefix.$class, $file);
}
if (false === $file) {
// Remember that this class does not exist.
$this->missingClasses[$class] = true;
@ -348,10 +374,14 @@ class ClassLoader
$first = $class[0];
if (isset($this->prefixLengthsPsr4[$first])) {
foreach ($this->prefixLengthsPsr4[$first] as $prefix => $length) {
if (0 === strpos($class, $prefix)) {
foreach ($this->prefixDirsPsr4[$prefix] as $dir) {
if (file_exists($file = $dir . DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $length))) {
$subPath = $class;
while (false !== $lastPos = strrpos($subPath, '\\')) {
$subPath = substr($subPath, 0, $lastPos);
$search = $subPath . '\\';
if (isset($this->prefixDirsPsr4[$search])) {
$pathEnd = DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $lastPos + 1);
foreach ($this->prefixDirsPsr4[$search] as $dir) {
if (file_exists($file = $dir . $pathEnd)) {
return $file;
}
}

View file

@ -1,5 +1,5 @@
Copyright (c) 2016 Nils Adermann, Jordi Boggiano
Copyright (c) Nils Adermann, Jordi Boggiano
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

View file

@ -7,5 +7,6 @@ $baseDir = dirname($vendorDir);
return array(
'R2\\Templating\\' => array($vendorDir . '/artoodetoo/dirk/src'),
'MioVisman\\NormEmail\\' => array($vendorDir . '/miovisman/normemail/src'),
'ForkBB\\' => array($baseDir . '/app'),
);

View file

@ -11,6 +11,10 @@ class ComposerStaticInit90ad93c7251d4f60daa9e545879c49e7
array (
'R2\\Templating\\' => 14,
),
'M' =>
array (
'MioVisman\\NormEmail\\' => 20,
),
'F' =>
array (
'ForkBB\\' => 7,
@ -22,6 +26,10 @@ class ComposerStaticInit90ad93c7251d4f60daa9e545879c49e7
array (
0 => __DIR__ . '/..' . '/artoodetoo/dirk/src',
),
'MioVisman\\NormEmail\\' =>
array (
0 => __DIR__ . '/..' . '/miovisman/normemail/src',
),
'ForkBB\\' =>
array (
0 => __DIR__ . '/../..' . '/app',

View file

@ -20,7 +20,7 @@
"require-dev": {
"phpunit/phpunit": "4.0.*"
},
"time": "2019-11-28 13:14:33",
"time": "2019-11-28T13:14:33+00:00",
"type": "library",
"installation-source": "dist",
"autoload": {
@ -54,6 +54,54 @@
"source": "https://github.com/MioVisman/dirk/tree/visman"
}
},
{
"name": "miovisman/normemail",
"version": "dev-master",
"version_normalized": "9999999-dev",
"source": {
"type": "git",
"url": "https://github.com/MioVisman/NormEmail.git",
"reference": "c582b1359adf1ee2efa38809e8827ed21138a9ac"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/MioVisman/NormEmail/zipball/c582b1359adf1ee2efa38809e8827ed21138a9ac",
"reference": "c582b1359adf1ee2efa38809e8827ed21138a9ac",
"shasum": ""
},
"require": {
"ext-intl": "*",
"ext-mbstring": "*",
"php": ">=5.6.0"
},
"time": "2019-12-24T15:42:43+00:00",
"type": "library",
"installation-source": "dist",
"autoload": {
"psr-4": {
"MioVisman\\NormEmail\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Visman",
"email": "mio.visman@yandex.ru",
"homepage": "https://github.com/MioVisman"
}
],
"description": "Normalization of email for bans, for request cache to SFS or for checking the uniqueness of new users.",
"homepage": "https://github.com/MioVisman/NormEmail",
"keywords": [
"ban",
"canonical email",
"email",
"normalization"
]
},
{
"name": "miovisman/parserus",
"version": "dev-master",
@ -72,7 +120,7 @@
"require": {
"php": ">=5.4.0"
},
"time": "2019-10-11 12:06:27",
"time": "2019-10-11T12:06:27+00:00",
"type": "library",
"installation-source": "dist",
"autoload": {

21
vendor/miovisman/normemail/LICENSE vendored Normal file
View file

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2019 Visman
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

62
vendor/miovisman/normemail/README.md vendored Normal file
View file

@ -0,0 +1,62 @@
# NormEmail
[![MIT licensed](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
## Installation
```
composer require miovisman/normemail
```
## normalize() method
``` php
$nEmail = new MioVisman\NormEmail\NormEmail();
$email = $nEmail->normalize($email);
```
* ### Method does not validate email address
* ### Do not use normalized email to send emails
* Use a normalized email to check the ban or uniqueness of the email of a new user. Check on the normalized emails ;)
* The domain is lowercase (and in Punycode)
* The local part is lowercase unless otherwise specified
* The local part after the "+" is truncated (for Yahoo domains - after the "-")
```
// some string
ExampLe => example
exaMple.COM => example.com
.example.com => .example.com
@example.com => example.com
"example.com => "example.com
"USER+++NAME@EXAMpLE.com => "USER+++NAME@example.com
googlemail.com => gmail.com
pm.me => protonmail.com
yandex.tj => yandex.ru
ya.ru => yandex.ru
// Unicode
ПОЛЬЗОВАТЕЛЬ@домен.РУ => пользователь@xn--d1acufc.xn--p1ag
пользователь+тег@домен.ру => пользователь@xn--d1acufc.xn--p1ag
// Gmail
User.namE+tag@gmail.com => username@gmail.com
u.sern.ame+tag+tag+tag@googlemail.com => username@gmail.com
// Protonmail
u_s.e-rname+tag@pm.me => username@protonmail.com
user-name@protonmail.ch => username@protonmail.com
// Yahoo (.com, .ae, .at, ...)
username-tag@yahoo.com => username@yahoo.com
user+name-tag@yahoo.fr => user+name@yahoo.fr
// Yandex (13 domains)
user.name+tag@яндекс.рф => user-name@yandex.ru
user-name@yandex.com => user-name@yandex.ru
username@ya.ru => username@yandex.ru
```
## License
This project is under MIT license. Please see the [license file](LICENSE) for details.

View file

@ -0,0 +1,25 @@
{
"name": "miovisman/normemail",
"description": "Normalization of email for bans, for request cache to SFS or for checking the uniqueness of new users.",
"keywords": ["email", "normalization", "canonical email", "ban"],
"homepage": "https://github.com/MioVisman/NormEmail",
"type": "library",
"license": "MIT",
"authors": [
{
"name": "Visman",
"email": "mio.visman@yandex.ru",
"homepage": "https://github.com/MioVisman"
}
],
"require": {
"php": ">=5.6.0",
"ext-mbstring": "*",
"ext-intl" : "*"
},
"autoload": {
"psr-4": {
"MioVisman\\NormEmail\\": "src/"
}
}
}

View file

@ -0,0 +1,201 @@
<?php
/**
* @copyright Copyright (c) 2019 Visman. All rights reserved.
* @author Visman <mio.visman@yandex.ru>
* @link https://github.com/MioVisman/NormEmail
* @license https://opensource.org/licenses/MIT The MIT License (MIT)
*/
namespace MioVisman\NormEmail;
class NormEmail
{
const NO_RULE = 0;
const DOT = 1; // google
const DUM = 2; // protonmail
const DTOH = 4; // yandex
const HYPHEN = 8; // yahoo
const DONT_PLUS = 16; // someone?
const SENSITIVE = 32;
protected $rules = [
// Google https://support.google.com/mail/answer/7436150
'gmail.com' => self::DOT,
'googlemail.com' => 'gmail.com',
// Protonmail https://protonmail.com/support/knowledge-base/addresses-and-aliases/#comment-7913
// https://protonmail.com/support/knowledge-base/pm-me-addresses/
'protonmail.com' => self::DUM,
'protonmail.ch' => 'protonmail.com',
'pm.me' => 'protonmail.com',
// Yahoo https://help.yahoo.com/kb/SLN28338.html
// https://help.yahoo.com/kb/SLN2153.html
'yahoo.com' => self::HYPHEN,
'yahoo.ae' => self::HYPHEN,
'yahoo.com.ar' => self::HYPHEN,
'yahoo.at' => self::HYPHEN,
'yahoo.com.au' => self::HYPHEN,
'yahoo.be' => self::HYPHEN,
'yahoo.com.br' => self::HYPHEN,
'yahoo.ca' => self::HYPHEN,
'yahoo.ch' => self::HYPHEN,
'yahoo.com.co' => self::HYPHEN,
'yahoo.cz' => self::HYPHEN,
'yahoo.de' => self::HYPHEN,
'yahoo.dk' => self::HYPHEN,
'yahoo.es' => self::HYPHEN,
'yahoo.fi' => self::HYPHEN,
'yahoo.fr' => self::HYPHEN,
'yahoo.gr' => self::HYPHEN,
'yahoo.com.hk' => self::HYPHEN,
'yahoo.com.hr' => self::HYPHEN,
'yahoo.hu' => self::HYPHEN,
'yahoo.co.id' => self::HYPHEN,
'yahoo.ie' => self::HYPHEN,
'yahoo.co.il' => self::HYPHEN,
'yahoo.in' => self::HYPHEN,
'yahoo.co.in' => self::HYPHEN,
'yahoo.it' => self::HYPHEN,
'yahoo.co.jp' => self::HYPHEN,
'yahoo.com.my' => self::HYPHEN,
'yahoo.com.mx' => self::HYPHEN,
'yahoo.nl' => self::HYPHEN,
'yahoo.no' => self::HYPHEN,
'yahoo.co.nz' => self::HYPHEN,
'yahoo.com.ph' => self::HYPHEN,
'yahoo.pl' => self::HYPHEN,
'yahoo.pt' => self::HYPHEN,
'yahoo.ro' => self::HYPHEN,
'yahoo.ru' => self::HYPHEN,
'yahoo.se' => self::HYPHEN,
'yahoo.com.sg' => self::HYPHEN,
'yahoo.co.th' => self::HYPHEN,
'yahoo.com.tr' => self::HYPHEN,
'yahoo.com.tw' => self::HYPHEN,
'yahoo.co.uk' => self::HYPHEN,
'yahoo.com.vn' => self::HYPHEN,
'yahoo.co.za' => self::HYPHEN,
// Yandex https://habr.com/ru/company/yandex/blog/56866/
'yandex.ru' => self::DTOH,
'ya.ru' => 'yandex.ru',
# 'yandex.asia' => 'yandex.ru', // no MX
'yandex.az' => 'yandex.ru',
'yandex.by' => 'yandex.ru',
'yandex.com' => 'yandex.ru',
# 'yandex.de' => 'yandex.ru', // no MX
# 'yandex.dk' => 'yandex.ru', // empty
# 'yandex.do' => 'yandex.ru', // empty
'yandex.ee' => 'yandex.ru',
# 'yandex.es' => 'yandex.ru', // empty
# 'yandex.eu' => 'yandex.ru', // empty
# 'yandex.ie' => 'yandex.ru', // empty
# 'yandex.in' => 'yandex.ru', // empty
# 'yandex.it' => 'yandex.ru', // no MX
'yandex.lt' => 'yandex.ru',
# 'yandex.lu' => 'yandex.ru', // empty
'yandex.lv' => 'yandex.ru',
'yandex.md' => 'yandex.ru',
# 'yandex.mobi' => 'yandex.ru', // no MX
# 'yandex.mx' => 'yandex.ru', // empty
# 'yandex.net' => 'yandex.ru', // is not synonymous with ru? need self::DTOH?
# 'yandex.no' => 'yandex.ru', // empty
# 'yandex.nu' => 'yandex.ru', // no MX
# 'yandex.org' => 'yandex.ru', // no MX
# 'yandex.pl' => 'yandex.ru', // empty
# 'yandex.pt' => 'yandex.ru', // no MX
# 'yandex.qa' => 'yandex.ru', // empty
# 'yandex.ro' => 'yandex.ru', // empty
# 'yandex.rs' => 'yandex.ru', // empty
# 'yandex.net.ru' => 'yandex.ru', // no MX
# 'yandex.com.ru' => 'yandex.ru', // no MX
# 'yandex.sk' => 'yandex.ru', // empty
# 'yandex.so' => 'yandex.ru', // empty
'yandex.tj' => 'yandex.ru',
'yandex.tm' => 'yandex.ru',
'yandex.ua' => 'yandex.ru',
# 'yandex.com.ua' => 'yandex.ru', // is not synonymous with ru? need self::DTOH?
'yandex.uz' => 'yandex.ru',
'xn--d1acpjx3f.xn--p1ai' => 'yandex.ru', // яндекс.рф
// Fastmail https://www.fastmail.com/help/receive/addressing.html
// https://www.fastmail.com/about/ourdomains/
// TODO: Make subdomain translation to the local part of the address
];
public function normalize($email)
{
if (false === ($pos = \strrpos($email, '@'))) {
$domain = $email;
$local = '';
} else {
$domain = \substr($email, $pos + 1);
$local = \substr($email, 0, $pos);
}
$domain = \mb_strtolower($domain, 'UTF-8');
// TODO: Process the dot at the beginning of the domain for the ban of the domains array (ban by the domain name without the local part)
if ('[' !== $domain[0] && \preg_match('%[\x80-\xFF]%', $domain)) {
$parts = \explode('.', $domain);
foreach ($parts as &$part) {
$ascii = \idn_to_ascii($part, \IDNA_DEFAULT, \INTL_IDNA_VARIANT_UTS46);
if (false !== $ascii) {
$part = $ascii;
}
}
unset($part);
$domain = \implode('.', $parts);
}
do {
$rule = self::NO_RULE;
if (isset($this->rules[$domain])) {
$rule = $this->rules[$domain];
if (\is_string($rule)) {
$domain = $rule;
continue;
}
}
break;
} while (true);
if (isset($local[0]) && '"' !== $local[0]) {
if ($rule & self::HYPHEN) {
$symbol = '-';
} elseif ($rule & self::DONT_PLUS) {
$symbol = false;
} else {
$symbol = '+';
}
if (false !== $symbol) {
$pos = \strpos($local, $symbol);
if (false !== $pos) {
$local = \substr($local, 0, $pos);
}
}
if ($rule & self::DOT) {
$local = \str_replace('.', '', $local);
}
if ($rule & self::DUM) {
$local = \str_replace(['.', '_', '-'], '', $local);
}
if ($rule & self::DTOH) {
$local = \str_replace('.', '-', $local);
}
if (! ($rule & self::SENSITIVE)) {
$local = \mb_strtolower($local, 'UTF-8');
}
}
if ('' == $local) {
return $domain; // '@myhost.com' --> 'myhost.com'
} else {
return $local . '@' . $domain;
}
}
}