Skip to content

Commit

Permalink
Added support for identifying and working with Gmail addresses using …
Browse files Browse the repository at this point in the history
…the "plus trick" to create unique addresses
  • Loading branch information
stymiee committed Feb 2, 2022
1 parent dd780e7 commit 61b14e8
Show file tree
Hide file tree
Showing 7 changed files with 225 additions and 83 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
VERSION 1.1.0 - 2022-02-02
-----------------------------------------------------------------------------------------------------------------------
Added support for identifying and working with Gmail addresses using the "plus trick" to create unique addresses


VERSION 1.0.2 - 2022-01-24
-----------------------------------------------------------------------------------------------------------------------
Issue #2: Error state not clearing between validations
Expand Down
179 changes: 102 additions & 77 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ The PHP Email Validator will validate an email address for all or some of the fo
- is not a disposable email address (optional)
- is not a free email account (optional)
- is not a banned email domain (optional)
- flag Gmail accounts that use the "plus trick" and return a sanitized email address

The Email Validator is configurable, so you have full control over how much validation will occur.

Expand All @@ -27,13 +28,13 @@ Simply add a dependency on `stymiee/email-validator` to your project's `composer
[Composer](https://getcomposer.org/) to manage the dependencies of your project.

Here is a minimal example of a `composer.json` file that just defines a dependency on PHP Simple Encryption:

{
"require": {
"stymiee/email-validator": "^1"
}
```json
{
"require": {
"stymiee/email-validator": "^1"
}

}
```
## Functional Description

The Email Validator library builds upon PHP's built in `filter_var($emailAddress, FILTER_VALIDATE_EMAIL);` by adding a
Expand Down Expand Up @@ -73,6 +74,15 @@ domains (i.e. public email providers like Gmail or Yahoo mail), you can block th
to `true` in the configuration (see below) and providing an array of banned domains. Examples are provided in the
`examples` directory which demonstrate how to do this.

### Flag Gmail Addresses Using The "Plus Trick"

Gmail offers the ability to create unique email addresses within a Google account by adding a `+` character and unique
identifier after the username portion of the email address. If not explicitly checked for a user can create an unlimited
amount of unique email addresses that all belong to the same account.

A special check can be performed when a Gmail account is used and a sanitized email address (e.g. one without the "plus
trick") can be obtained and then checked for uniqueness in your system.

### Configuration

To configure the Email Validator you can pass an array with the follow parameters/values:
Expand Down Expand Up @@ -116,81 +126,96 @@ An array of domains that are suspected disposable email address providers.
An array of domains that are free email address providers.

**Example**

$config = [
'checkMxRecords' => true,
'checkBannedListedEmail' => true,
'checkDisposableEmail' => true,
'checkFreeEmail' => true,
'bannedList' => $bannedDomainList,
'disposableList' => $customDisposableEmailList,
'freeList' => $customFreeEmailList,
];
$emailValidator = new EmailValidator($config);

```php
$config = [
'checkMxRecords' => true,
'checkBannedListedEmail' => true,
'checkDisposableEmail' => true,
'checkFreeEmail' => true,
'bannedList' => $bannedDomainList,
'disposableList' => $customDisposableEmailList,
'freeList' => $customFreeEmailList,
];
$emailValidator = new EmailValidator($config);
````
### Example

<?php
use EmailValidator\EmailValidator;

require('../vendor/autoload.php');

$customDisposableEmailList = [
'example.com',
];
$customFreeEmailList = [
'example2.com',
];
$bannedDomainList = [
'domain.com',
];
$testEmailAddresses = [
'[email protected]',
'[email protected]',
'[email protected]',
'[email protected]',
'[email protected]',
'[email protected]',
'[email protected]',
'[email protected]',
'[email protected]',
'[email protected]',
'[email protected]',
'[email protected]',
];

$config = [
'checkMxRecords' => true,
'checkBannedListedEmail' => true,
'checkDisposableEmail' => true,
'checkFreeEmail' => true,
'bannedList' => $bannedDomainList,
'disposableList' => $customDisposableEmailList,
'freeList' => $customFreeEmailList,
];
$emailValidator = new EmailValidator($config);

foreach($testEmailAddresses as $emailAddress) {
$emailIsValid = $emailValidator->validate($emailAddress);
echo ($emailIsValid) ? 'Email is valid' : $emailValidator->getErrorReason();
echo PHP_EOL;
```php
<?php

namespace EmailValidator;

require('../vendor/autoload.php');

$customDisposableEmailList = [
'example.com',
];

$bannedDomainList = [
'domain.com',
];

$customFreeEmailList = [
'example2.com',
];

$testEmailAddresses = [
'[email protected]',
'[email protected]',
'[email protected]',
'[email protected]',
'[email protected]',
'[email protected]',
'[email protected]',
'[email protected]',
'[email protected]',
'[email protected]',
'[email protected]',
'[email protected]',
'[email protected]',
'[email protected]',
];

$config = [
'checkMxRecords' => true,
'checkBannedListedEmail' => true,
'checkDisposableEmail' => true,
'checkFreeEmail' => true,
'bannedList' => $bannedDomainList,
'disposableList' => $customDisposableEmailList,
'freeList' => $customFreeEmailList,
];
$emailValidator = new EmailValidator($config);

foreach ($testEmailAddresses as $emailAddress) {
$emailIsValid = $emailValidator->validate($emailAddress);
echo ($emailIsValid) ? 'Email is valid' : $emailValidator->getErrorReason();
if ($emailValidator->isGmailWithPlusChar()) {
printf(
' (Sanitized address: %s)',
$emailValidator->getGmailAddressWithoutPlus()
);
}
echo PHP_EOL;
}
```

**Output**

Email is valid
Domain is used by free email providers
Domain is used by free email providers
Domain is used by free email providers
Domain is used by free email providers
Domain is banned
Domain is used by disposable email providers
Domain is used by free email providers
Domain is used by disposable email providers
Domain does not accept email
Domain is used by disposable email providers
Domain is used by disposable email providers

```
Domain is banned
Email is valid
Domain is used by free email providers
Domain is used by free email providers
Domain is used by free email providers
Domain is used by free email providers
Domain is banned
Domain does not accept email
Domain is used by disposable email providers
Domain is used by free email providers
Domain is used by disposable email providers
Domain does not accept email
Domain is used by disposable email providers
Domain is used by free email providers (Sanitized address: [email protected])
```
## Notes

The email address is checked against a list of known disposable email address providers which are aggregated from
Expand Down
8 changes: 8 additions & 0 deletions examples/fullValidation.php
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
'[email protected]',
'[email protected]',
'[email protected]',
'[email protected]',
];

$config = [
Expand All @@ -46,5 +47,12 @@
foreach ($testEmailAddresses as $emailAddress) {
$emailIsValid = $emailValidator->validate($emailAddress);
echo ($emailIsValid) ? 'Email is valid' : $emailValidator->getErrorReason();
if ($emailValidator->isGmailWithPlusChar()) {
printf(
' (%s is a Gmail account and contains a plus character. Sanitized address: %s)',
$emailAddress,
$emailValidator->getGmailAddressWithoutPlus()
);
}
echo PHP_EOL;
}
38 changes: 38 additions & 0 deletions src/EmailValidator/EmailAddress.php
Original file line number Diff line number Diff line change
Expand Up @@ -35,4 +35,42 @@ public function getEmailAddress(): string
{
return $this->email;
}

/**
* Returns the username of the email address.
*
* @since 1.1.0
* @return string|null
*/
private function getUsername(): ?string
{
return explode('@', $this->email)[0] ?? null;
}

/**
* Determines if a gmail account is using the "plus trick".
*
* @since 1.1.0
* @return bool
*/
public function isGmailWithPlusChar(): bool
{
$result = false;
if ($this->getDomain() === 'gmail.com') {
$result = strpos($this->getUsername(), '+') !== false;
}

return $result;
}

/**
* Returns a gmail address with the "plus trick" portion of the email address.
*
* @since 1.1.0
* @return string
*/
public function getGmailAddressWithoutPlus(): string
{
return preg_replace('/^(.+?)(\+.+?)(@.+?)/', '$1$3', $this->getEmailAddress());
}
}
44 changes: 38 additions & 6 deletions src/EmailValidator/EmailValidator.php
Original file line number Diff line number Diff line change
Expand Up @@ -54,8 +54,16 @@ class EmailValidator
*/
private $reason;

/**
* @var EmailAddress
* @since 1.1.0
*/
private $emailAddress;

public function __construct(array $config = [])
{
$this->reason = self::NO_ERROR;

$policy = new Policy($config);

$this->mxValidator = new MxValidator($policy);
Expand All @@ -75,17 +83,17 @@ public function validate(string $email): bool
{
$this->resetErrorCode();

$emailAddress = new EmailAddress($email);
$this->emailAddress = new EmailAddress($email);

if (!$this->basicValidator->validate($emailAddress)) {
if (!$this->basicValidator->validate($this->emailAddress)) {
$this->reason = self::FAIL_BASIC;
} elseif (!$this->mxValidator->validate($emailAddress)) {
} elseif (!$this->mxValidator->validate($this->emailAddress)) {
$this->reason = self::FAIL_MX_RECORD;
} elseif (!$this->bannedListValidator->validate($emailAddress)) {
} elseif (!$this->bannedListValidator->validate($this->emailAddress)) {
$this->reason = self::FAIL_BANNED_DOMAIN;
} elseif (!$this->disposableEmailValidator->validate($emailAddress)) {
} elseif (!$this->disposableEmailValidator->validate($this->emailAddress)) {
$this->reason = self::FAIL_DISPOSABLE_DOMAIN;
} elseif (!$this->freeEmailValidator->validate($emailAddress)) {
} elseif (!$this->freeEmailValidator->validate($this->emailAddress)) {
$this->reason = self::FAIL_FREE_PROVIDER;
}

Expand Down Expand Up @@ -146,4 +154,28 @@ private function resetErrorCode(): void
{
$this->reason = self::NO_ERROR;
}

/**
* Determines if a gmail account is using the "plus trick".
*
* @codeCoverageIgnore
* @since 1.1.0
* @return bool
*/
public function isGmailWithPlusChar(): bool
{
return $this->emailAddress->isGmailWithPlusChar();
}

/**
* Returns a gmail address with the "plus trick" portion of the email address.
*
* @codeCoverageIgnore
* @since 1.1.0
* @return string
*/
public function getGmailAddressWithoutPlus(): string
{
return $this->emailAddress->getGmailAddressWithoutPlus();
}
}
27 changes: 27 additions & 0 deletions tests/EmailValidator/EmailAddressTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -52,4 +52,31 @@ public function testGetEmailAddress(string $emailAddress): void
$email = new EmailAddress($emailAddress);
self::assertEquals($email->getEmailAddress(), $emailAddress);
}

public function emailWithPlusDataProvider(): array
{
return [
['[email protected]', false],
['[email protected]', true],
['[email protected]', false],
['[email protected]', false],
];
}

/**
* @dataProvider emailWithPlusDataProvider
* @param string $emailAddress
* @param bool $hasPlus
*/
public function testIsGmailWithPlusChar(string $emailAddress, bool $hasPlus): void
{
$email = new EmailAddress($emailAddress);
self::assertEquals($hasPlus, $email->isGmailWithPlusChar());
}

public function testGetGmailAddressWithoutPlus(): void
{
$email = new EmailAddress('[email protected]');
self::assertEquals('[email protected]', $email->getGmailAddressWithoutPlus());
}
}
Loading

0 comments on commit 61b14e8

Please sign in to comment.