Skip to content

Commit

Permalink
Replace dated and flawed reserved ip checks in Email validator
Browse files Browse the repository at this point in the history
Removes the regexes used to validate reserved/private ips in favour of using the `HostWithPublicIPv4Address` validator

Signed-off-by: George Steel <[email protected]>
  • Loading branch information
gsteel committed Jun 14, 2024
1 parent 1e24e70 commit 55d12d1
Show file tree
Hide file tree
Showing 4 changed files with 20 additions and 178 deletions.
1 change: 0 additions & 1 deletion phpunit.xml.dist
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@
<php>
<ini name="date.timezone" value="UTC"/>
<ini name="xdebug.max_nesting_level" value="3000"/>
<env name="TESTS_LAMINAS_VALIDATOR_ONLINE_ENABLED" value="true"/>
</php>
<source>
<include>
Expand Down
58 changes: 3 additions & 55 deletions src/EmailAddress.php
Original file line number Diff line number Diff line change
Expand Up @@ -280,67 +280,15 @@ public function useDomainCheck($domain = true)
}

/**
* Returns if the given host is reserved
*
* The following addresses are seen as reserved
* '0.0.0.0/8', '10.0.0.0/8', '127.0.0.0/8'
* '100.64.0.0/10'
* '172.16.0.0/12'
* '198.18.0.0/15'
* '169.254.0.0/16', '192.168.0.0/16'
* '192.0.2.0/24', '192.88.99.0/24', '198.51.100.0/24', '203.0.113.0/24'
* '224.0.0.0/4', '240.0.0.0/4'
*
* @see http://en.wikipedia.org/wiki/Reserved_IP_addresses
*
* As of RFC5753 (JAN 2010), the following blocks are no longer reserved:
* - 128.0.0.0/16
* - 191.255.0.0/16
* - 223.255.255.0/24
* @see http://tools.ietf.org/html/rfc5735#page-6
*
* As of RFC6598 (APR 2012), the following blocks are now reserved:
* - 100.64.0.0/10
* @see http://tools.ietf.org/html/rfc6598#section-7
* Returns whether the given host is a reserved IP, or a hostname that resolves to a reserved IP
*
* @param string $host
* @return bool Returns false when minimal one of the given addresses is not reserved
*/
protected function isReserved($host)
{
if (! preg_match('/^([0-9]{1,3}\.){3}[0-9]{1,3}$/', $host)) {
$host = gethostbynamel($host);
} else {
$host = [$host];
}

if (! is_array($host) || $host === []) {
return false;
}

foreach ($host as $server) {
// @codingStandardsIgnoreStart
// Search for 0.0.0.0/8, 10.0.0.0/8, 127.0.0.0/8
if (!preg_match('/^(0|10|127)(\.([0-9]|[1-9][0-9]|1([0-9][0-9])|2([0-4][0-9]|5[0-5]))){3}$/', $server) &&
// Search for 100.64.0.0/10
!preg_match('/^100\.(6[0-4]|[7-9][0-9]|1[0-1][0-9]|12[0-7])(\.([0-9]|[1-9][0-9]|1([0-9][0-9])|2([0-4][0-9]|5[0-5]))){2}$/', $server) &&
// Search for 172.16.0.0/12
!preg_match('/^172\.(1[6-9]|2[0-9]|3[0-1])(\.([0-9]|[1-9][0-9]|1([0-9][0-9])|2([0-4][0-9]|5[0-5]))){2}$/', $server) &&
// Search for 198.18.0.0/15
!preg_match('/^198\.(1[8-9])(\.([0-9]|[1-9][0-9]|1([0-9][0-9])|2([0-4][0-9]|5[0-5]))){2}$/', $server) &&
// Search for 169.254.0.0/16, 192.168.0.0/16
!preg_match('/^(169\.254|192\.168)(\.([0-9]|[1-9][0-9]|1([0-9][0-9])|2([0-4][0-9]|5[0-5]))){2}$/', $server) &&
// Search for 192.0.2.0/24, 192.88.99.0/24, 198.51.100.0/24, 203.0.113.0/24
!preg_match('/^(192\.0\.2|192\.88\.99|198\.51\.100|203\.0\.113)\.([0-9]|[1-9][0-9]|1([0-9][0-9])|2([0-4][0-9]|5[0-5]))$/', $server) &&
// Search for 224.0.0.0/4, 240.0.0.0/4
!preg_match('/^(2(2[4-9]|[3-4][0-9]|5[0-5]))(\.([0-9]|[1-9][0-9]|1([0-9][0-9])|2([0-4][0-9]|5[0-5]))){3}$/', $server)
) {
return false;
}
// @codingStandardsIgnoreEnd
}

return true;
$validator = new HostWithPublicIPv4Address();
return ! $validator->isValid($host);
}

/**
Expand Down
117 changes: 17 additions & 100 deletions test/EmailAddressTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
use Laminas\Validator\Exception\InvalidArgumentException;
use Laminas\Validator\Hostname;
use PHPUnit\Framework\Attributes\DataProvider;
use PHPUnit\Framework\Attributes\Depends;
use PHPUnit\Framework\Attributes\Group;
use PHPUnit\Framework\TestCase;

Expand All @@ -17,7 +18,6 @@
use function count;
use function current;
use function extension_loaded;
use function getenv;
use function implode;
use function next;
use function preg_replace;
Expand Down Expand Up @@ -259,8 +259,8 @@ public function testBasicValid(string $value): void
sprintf(
'%s failed validation: %s',
$value,
implode("\n", $this->validator->getMessages())
)
implode("\n", $this->validator->getMessages()),
),
);
}

Expand Down Expand Up @@ -359,8 +359,6 @@ public static function emailAddressesForMxChecks(): array
#[DataProvider('emailAddressesForMxChecks')]
public function testMXRecords(string $emailAddress, bool $expect): void
{
$this->skipIfOnlineTestsDisabled();

$validator = new EmailAddress([
'allow' => Hostname::ALLOW_DNS,
'useMxCheck' => true,
Expand Down Expand Up @@ -389,8 +387,6 @@ public function testMXRecords(string $emailAddress, bool $expect): void
*/
public function testNoMxRecordARecordFallback(): void
{
$this->skipIfOnlineTestsDisabled();

$validator = new EmailAddress([
'allow' => Hostname::ALLOW_DNS,
'useMxCheck' => true,
Expand Down Expand Up @@ -700,13 +696,8 @@ public function testIsMxSupported(): void
self::assertIsBool($validator->isMxSupported());
}

/**
* Test getMXRecord
*/
public function testGetMXRecord(): void
{
$this->skipIfOnlineTestsDisabled();

$validator = new EmailAddress(['useMxCheck' => true, 'allow' => Hostname::ALLOW_ALL]);

if (! $validator->isMxSupported()) {
Expand Down Expand Up @@ -734,7 +725,7 @@ public function testEqualsMessageTemplates(): void
EmailAddress::INVALID_LOCAL_PART,
EmailAddress::LENGTH_EXCEEDED,
],
array_keys($this->validator->getMessageTemplates())
array_keys($this->validator->getMessageTemplates()),
);
self::assertSame($this->validator->getOption('messageTemplates'), $this->validator->getMessageTemplates());
}
Expand All @@ -753,8 +744,6 @@ public function testEqualsMessageVariables(): void
#[Group('Laminas-130')]
public function testUseMxCheckBasicValid(): void
{
$this->skipIfOnlineTestsDisabled();

$validator = new EmailAddress([
'useMxCheck' => true,
'useDeepMxCheck' => true,
Expand Down Expand Up @@ -823,95 +812,23 @@ public function testUseMxRecordsBasicInvalid(): void
}
}

#[Group('Laminas-12349')]
public function testReservedIpRangeValidation(): void
{
$validator = new TestAsset\EmailValidatorWithExposedIsReserved();

// 0.0.0.0/8
self::assertTrue($validator->isReserved('0.0.0.0'));
self::assertTrue($validator->isReserved('0.255.255.255'));

// 10.0.0.0/8
self::assertTrue($validator->isReserved('10.0.0.0'));
self::assertTrue($validator->isReserved('10.255.255.255'));

// 127.0.0.0/8
self::assertTrue($validator->isReserved('127.0.0.0'));
self::assertTrue($validator->isReserved('127.255.255.255'));

// 100.64.0.0/10
self::assertTrue($validator->isReserved('100.64.0.0'));
self::assertTrue($validator->isReserved('100.127.255.255'));

// 172.16.0.0/12
self::assertTrue($validator->isReserved('172.16.0.0'));
self::assertTrue($validator->isReserved('172.31.255.255'));

// 198.18.0.0./15
self::assertTrue($validator->isReserved('198.18.0.0'));
self::assertTrue($validator->isReserved('198.19.255.255'));

// 169.254.0.0/16
self::assertTrue($validator->isReserved('169.254.0.0'));
self::assertTrue($validator->isReserved('169.254.255.255'));

// 192.168.0.0/16
self::assertTrue($validator->isReserved('192.168.0.0'));
self::assertTrue($validator->isReserved('192.168.255.25'));

// 192.0.2.0/24
self::assertTrue($validator->isReserved('192.0.2.0'));
self::assertTrue($validator->isReserved('192.0.2.255'));

// 192.88.99.0/24
self::assertTrue($validator->isReserved('192.88.99.0'));
self::assertTrue($validator->isReserved('192.88.99.255'));

// 198.51.100.0/24
self::assertTrue($validator->isReserved('198.51.100.0'));
self::assertTrue($validator->isReserved('198.51.100.255'));

// 203.0.113.0/24
self::assertTrue($validator->isReserved('203.0.113.0'));
self::assertTrue($validator->isReserved('203.0.113.255'));

// 224.0.0.0/4
self::assertTrue($validator->isReserved('224.0.0.0'));
self::assertTrue($validator->isReserved('239.255.255.255'));

// 240.0.0.0/4
self::assertTrue($validator->isReserved('240.0.0.0'));
self::assertTrue($validator->isReserved('255.255.255.254'));

// 255.255.255.255/32
self::assertTrue($validator->isReserved('255.255.55.255'));
}

#[Group('Laminas-12349')]
public function testIpRangeValidationOnRangesNoLongerMarkedAsReserved(): void
public function testRootAtLocalhostIsValid(): void
{
$validator = new TestAsset\EmailValidatorWithExposedIsReserved();

// 128.0.0.0/16
self::assertFalse($validator->isReserved('128.0.0.0'));
self::assertFalse($validator->isReserved('128.0.255.255'));

// 191.255.0.0/16
self::assertFalse($validator->isReserved('191.255.0.0'));
self::assertFalse($validator->isReserved('191.255.255.255'));

// 223.255.255.0/24
self::assertFalse($validator->isReserved('223.255.255.0'));
self::assertFalse($validator->isReserved('223.255.255.255'));
$validator = new EmailAddress([
'allow' => Hostname::ALLOW_ALL,
]);
self::assertTrue($validator->isValid('root@localhost'));
}

private function skipIfOnlineTestsDisabled(): void
#[Depends('testRootAtLocalhostIsValid')]
public function testRootAtLocalhostIsNotValidWhenDeepMxChecksAreActive(): void
{
$enabled = getenv('TESTS_LAMINAS_VALIDATOR_ONLINE_ENABLED');
if ($enabled === false || $enabled === '') {
self::markTestSkipped('Testing MX records has been disabled');
}
$validator = new EmailAddress([
'allow' => Hostname::ALLOW_ALL,
'useMxCheck' => true,
'useDeepMxCheck' => true,
]);
self::assertFalse($validator->isValid('root@localhost'));
}

public function testCanSetDomainCheckFlag(): void
Expand Down
22 changes: 0 additions & 22 deletions test/TestAsset/EmailValidatorWithExposedIsReserved.php

This file was deleted.

0 comments on commit 55d12d1

Please sign in to comment.