Skip to content

Commit

Permalink
Merge pull request #12 from simplesamlphp/feature/rfc-compliant-uri
Browse files Browse the repository at this point in the history
Use phpleague/uri to properly test valid URI/URL/URN's
  • Loading branch information
tvdijen authored Jul 22, 2024
2 parents ea66bce + e50d5cd commit f6904dd
Show file tree
Hide file tree
Showing 4 changed files with 42 additions and 25 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/php.yml
Original file line number Diff line number Diff line change
Expand Up @@ -212,7 +212,7 @@ jobs:
uses: shivammathur/setup-php@v2
with:
php-version: ${{ matrix.php-versions }}
extensions: ctype, date, filter, pcre, spl
extensions: ctype, date, filter, intl, pcre, spl
tools: composer
ini-values: error_reporting=E_ALL
coverage: none
Expand Down
6 changes: 4 additions & 2 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,11 @@
"ext-pcre": "*",
"ext-spl": "*",

"league/uri-interfaces": "^7.4",
"webmozart/assert": "^1.11"
},
"require-dev": {
"simplesamlphp/simplesamlphp-test-framework": "^1.5.5"
"simplesamlphp/simplesamlphp-test-framework": "^1.7"
},
"autoload": {
"psr-4": {
Expand All @@ -44,7 +45,8 @@
"allow-plugins": {
"composer/package-versions-deprecated": true,
"dealerdirect/phpcodesniffer-composer-installer": true,
"simplesamlphp/composer-module-installer": false
"simplesamlphp/composer-module-installer": false,
"phpstan/extension-installer": true
}
}
}
47 changes: 28 additions & 19 deletions src/CustomAssertionTrait.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@

namespace SimpleSAML\Assert;

use DateTimeImmutable; // Requires ext-date
use DateTimeInterface; // Requires ext-date
use InvalidArgumentException;
use League\Uri\Exceptions\SyntaxError;
use League\Uri\UriString;

use function array_map;
use function base64_decode;
Expand Down Expand Up @@ -37,15 +37,6 @@ trait CustomAssertionTrait
/** @var string */
private static string $base64_regex = '/^(?:[a-z0-9+\/]{4})*(?:[a-z0-9+\/]{2}==|[a-z0-9+\/]{3}=)?$/i';

/** @var string */
private static string $uri_same_document_regex = '#^(?:\#([A-Za-z][A-Za-z0-9+\-.]*:(?:\/\/(?:(?:[A-Za-z0-9\-._~!$&\'()*+,;=:]|%[0-9A-Fa-f]{2})*@)?(?:\[(?:(?:(?:(?:[0-9A-Fa-f]{1,4}:){6}|::(?:[0-9A-Fa-f]{1,4}:){5}|(?:[0-9A-Fa-f]{1,4})?::(?:[0-9A-Fa-f]{1,4}:){4}|(?:(?:[0-9A-Fa-f]{1,4}:){0,1}[0-9A-Fa-f]{1,4})?::(?:[0-9A-Fa-f]{1,4}:){3}|(?:(?:[0-9A-Fa-f]{1,4}:){0,2}[0-9A-Fa-f]{1,4})?::(?:[0-9A-Fa-f]{1,4}:){2}|(?:(?:[0-9A-Fa-f]{1,4}:){0,3}[0-9A-Fa-f]{1,4})?::[0-9A-Fa-f]{1,4}:|(?:(?:[0-9A-Fa-f]{1,4}:){0,4}[0-9A-Fa-f]{1,4})?::)(?:[0-9A-Fa-f]{1,4}:[0-9A-Fa-f]{1,4}|(?:(?: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]?))|(?:(?:[0-9A-Fa-f]{1,4}:){0,5}[0-9A-Fa-f]{1,4})?::[0-9A-Fa-f]{1,4}|(?:(?:[0-9A-Fa-f]{1,4}:){0,6}[0-9A-Fa-f]{1,4})?::)|[Vv][0-9A-Fa-f]+\.[A-Za-z0-9\-._~!$&\'()*+,;=:]+)\]|(?:(?: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]?)|(?:[A-Za-z0-9\-._~!$&\'()*+,;=]|%[0-9A-Fa-f]{2})*)(?::[0-9]*)?(?:\/(?:[A-Za-z0-9\-._~!$&\'()*+,;=:@]|%[0-9A-Fa-f]{2})*)*|\/(?:(?:[A-Za-z0-9\-._~!$&\'()*+,;=:@]|%[0-9A-Fa-f]{2})+(?:\/(?:[A-Za-z0-9\-._~!$&\'()*+,;=:@]|%[0-9A-Fa-f]{2})*)*)?|(?:[A-Za-z0-9\-._~!$&\'()*+,;=:@]|%[0-9A-Fa-f]{2})+(?:\/(?:[A-Za-z0-9\-._~!$&\'()*+,;=:@]|%[0-9A-Fa-f]{2})*)*|)(?:\?(?:[A-Za-z0-9\-._~!$&\'()*+,;=:@/?]|%[0-9A-Fa-f]{2})*)?(?:\#(?:[A-Za-z0-9\-._~!$&\'()*+,;=:@/?]|%[0-9A-Fa-f]{2})*)?|(?:\/\/(?:(?:[A-Za-z0-9\-._~!$&\'()*+,;=:]|%[0-9A-Fa-f]{2})*@)?(?:\[(?:(?:(?:(?:[0-9A-Fa-f]{1,4}:){6}|::(?:[0-9A-Fa-f]{1,4}:){5}|(?:[0-9A-Fa-f]{1,4})?::(?:[0-9A-Fa-f]{1,4}:){4}|(?:(?:[0-9A-Fa-f]{1,4}:){0,1}[0-9A-Fa-f]{1,4})?::(?:[0-9A-Fa-f]{1,4}:){3}|(?:(?:[0-9A-Fa-f]{1,4}:){0,2}[0-9A-Fa-f]{1,4})?::(?:[0-9A-Fa-f]{1,4}:){2}|(?:(?:[0-9A-Fa-f]{1,4}:){0,3}[0-9A-Fa-f]{1,4})?::[0-9A-Fa-f]{1,4}:|(?:(?:[0-9A-Fa-f]{1,4}:){0,4}[0-9A-Fa-f]{1,4})?::)(?:[0-9A-Fa-f]{1,4}:[0-9A-Fa-f]{1,4}|(?:(?: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]?))|(?:(?:[0-9A-Fa-f]{1,4}:){0,5}[0-9A-Fa-f]{1,4})?::[0-9A-Fa-f]{1,4}|(?:(?:[0-9A-Fa-f]{1,4}:){0,6}[0-9A-Fa-f]{1,4})?::)|[Vv][0-9A-Fa-f]+\.[A-Za-z0-9\-._~!$&\'()*+,;=:]+)\]|(?:(?: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]?)|(?:[A-Za-z0-9\-._~!$&\'()*+,;=]|%[0-9A-Fa-f]{2})*)(?::[0-9]*)?(?:\/(?:[A-Za-z0-9\-._~!$&\'()*+,;=:@]|%[0-9A-Fa-f]{2})*)*|\/(?:(?:[A-Za-z0-9\-._~!$&\'()*+,;=:@]|%[0-9A-Fa-f]{2})+(?:\/(?:[A-Za-z0-9\-._~!$&\'()*+,;=:@]|%[0-9A-Fa-f]{2})*)*)?|(?:[A-Za-z0-9\-._~!$&\'()*+,;=@]|%[0-9A-Fa-f]{2})+(?:\/(?:[A-Za-z0-9\-._~!$&\'()*+,;=:@]|%[0-9A-Fa-f]{2})*)*|)(?:\?(?:[A-Za-z0-9\-._~!$&\'()*+,;=:@/?]|%[0-9A-Fa-f]{2})*)?(?:\#(?:[A-Za-z0-9\-._~!$&\'()*+,;=:@/?]|%[0-9A-Fa-f]{2})*)?))$#';
/** @var string */
private static string $urn_regex = '/\A(?i:urn:(?!urn:)(?<nid>[a-z0-9][a-z0-9-]{1,31}):(?<nss>(?:[-a-z0-9()+,.:=@;$_!*\'&~\/]|%[0-9a-f]{2})+)(?:\?\+(?<rcomponent>.*?))?(?:\?=(?<qcomponent>.*?))?(?:#(?<fcomponent>.*?))?)\z/';

/** @var string */
private static string $uri_regex = '#[A-Za-z][A-Za-z0-9+\-.]*:(?:\/\/(?:(?:[A-Za-z0-9\-._~!$&\'()*+,;=:]|%[0-9A-Fa-f]{2})*@)?(?:\[(?:(?:(?:(?:[0-9A-Fa-f]{1,4}:){6}|::(?:[0-9A-Fa-f]{1,4}:){5}|(?:[0-9A-Fa-f]{1,4})?::(?:[0-9A-Fa-f]{1,4}:){4}|(?:(?:[0-9A-Fa-f]{1,4}:){0,1}[0-9A-Fa-f]{1,4})?::(?:[0-9A-Fa-f]{1,4}:){3}|(?:(?:[0-9A-Fa-f]{1,4}:){0,2}[0-9A-Fa-f]{1,4})?::(?:[0-9A-Fa-f]{1,4}:){2}|(?:(?:[0-9A-Fa-f]{1,4}:){0,3}[0-9A-Fa-f]{1,4})?::[0-9A-Fa-f]{1,4}:|(?:(?:[0-9A-Fa-f]{1,4}:){0,4}[0-9A-Fa-f]{1,4})?::)(?:[0-9A-Fa-f]{1,4}:[0-9A-Fa-f]{1,4}|(?:(?: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]?))|(?:(?:[0-9A-Fa-f]{1,4}:){0,5}[0-9A-Fa-f]{1,4})?::[0-9A-Fa-f]{1,4}|(?:(?:[0-9A-Fa-f]{1,4}:){0,6}[0-9A-Fa-f]{1,4})?::)|[Vv][0-9A-Fa-f]+\.[A-Za-z0-9\-._~!$&\'()*+,;=:]+)\]|(?:(?: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]?)|(?:[A-Za-z0-9\-._~!$&\'()*+,;=]|%[0-9A-Fa-f]{2})*)(?::[0-9]*)?(?:\/(?:[A-Za-z0-9\-._~!$&\'()*+,;=:@]|%[0-9A-Fa-f]{2})*)*|\/(?:(?:[A-Za-z0-9\-._~!$&\'()*+,;=:@]|%[0-9A-Fa-f]{2})+(?:\/(?:[A-Za-z0-9\-._~!$&\'()*+,;=:@]|%[0-9A-Fa-f]{2})*)*)?|(?:[A-Za-z0-9\-._~!$&\'()*+,;=:@]|%[0-9A-Fa-f]{2})+(?:\/(?:[A-Za-z0-9\-._~!$&\'()*+,;=:@]|%[0-9A-Fa-f]{2})*)*|)(?:\?(?:[A-Za-z0-9\-._~!$&\'()*+,;=:@/?]|%[0-9A-Fa-f]{2})*)?(?:\#(?:[A-Za-z0-9\-._~!$&\'()*+,;=:@/?]|%[0-9A-Fa-f]{2})*)?#';

/** @var string */
private static string $hostname_regex = '/^(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9])\.)*([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9\-]*[A-Za-z0-9])$/';

Expand Down Expand Up @@ -168,7 +159,19 @@ private static function notInArray($value, array $values, string $message = ''):
*/
private static function validURN(string $value, string $message = ''): void
{
if (filter_var($value, FILTER_VALIDATE_REGEXP, ['options' => ['regexp' => self::$urn_regex]]) === false) {
try {
$uri = UriString::parse($value);
} catch (SyntaxError $e) {
throw new InvalidArgumentException(sprintf(
$message ?: '\'%s\' is not a valid RFC3986 compliant URI',
$value,
));
}

if (
$uri['scheme'] !== 'urn'
|| (($uri['scheme'] !== null) && $uri['path'] !== substr($value, strlen($uri['scheme']) + 1))
) {
throw new InvalidArgumentException(sprintf(
$message ?: '\'%s\' is not a valid RFC8141 compliant URN',
$value,
Expand All @@ -183,7 +186,16 @@ private static function validURN(string $value, string $message = ''): void
*/
private static function validURL(string $value, string $message = ''): void
{
if (filter_var($value, FILTER_VALIDATE_URL) === false) {
try {
$uri = UriString::parse($value);
} catch (SyntaxError $e) {
throw new InvalidArgumentException(sprintf(
$message ?: '\'%s\' is not a valid RFC3986 compliant URI',
$value,
));
}

if ($uri['scheme'] !== 'http' && $uri['scheme'] !== 'https') {
throw new InvalidArgumentException(sprintf(
$message ?: '\'%s\' is not a valid RFC2396 compliant URL',
$value,
Expand All @@ -198,12 +210,9 @@ private static function validURL(string $value, string $message = ''): void
*/
private static function validURI(string $value, string $message = ''): void
{
if (
filter_var($value, FILTER_VALIDATE_REGEXP, ['options' => ['regexp' => self::$uri_regex]]) === false &&
// We're very lenient here to accept DNS hostnames without a scheme
filter_var($value, FILTER_VALIDATE_REGEXP, ['options' => ['regexp' => self::$hostname_regex]]) === false &&
filter_var($value, FILTER_VALIDATE_REGEXP, ['options' => ['regexp' => self::$uri_same_document_regex]]) === false
) {
try {
UriString::parse($value);
} catch (SyntaxError $e) {
throw new InvalidArgumentException(sprintf(
$message ?: '\'%s\' is not a valid RFC3986 compliant URI',
$value,
Expand Down
12 changes: 9 additions & 3 deletions tests/Assert/URITest.php
Original file line number Diff line number Diff line change
Expand Up @@ -75,8 +75,10 @@ public static function provideURI(): array
'urn' => [true, 'urn:x-simplesamlphp:phpunit'],
'same-doc' => [true, '#_53d830ab1be17291a546c95c7f1cdf8d3d23c959e6'],
'url' => [true, 'https://www.simplesamlphp.org'],
'bogus' => [false, 'stupid value'],
'invalid_char' => [false, 'https://a⒈com'],
'intl' => [true, 'https://niño.com'],
'spn' => [true, 'spn:a4cf592f-a64c-46ff-a788-b260f474525b'],
'typos' => [true, 'https//www.uni.l/en/'],
];
}

Expand All @@ -90,8 +92,10 @@ public static function provideURL(): array
'url' => [true, 'https://www.simplesamlphp.org'],
'same-doc' => [false, '#_53d830ab1be17291a546c95c7f1cdf8d3d23c959e6'],
'urn' => [false, 'urn:x-simplesamlphp:phpunit'],
'bogus' => [false, 'stupid value'],
'invalid_char' => [false, 'https://a⒈com'],
'intl' => [true, 'https://niño.com'],
'spn' => [false, 'spn:a4cf592f-a64c-46ff-a788-b260f474525b'],
'typos' => [false, 'https//www.uni.l/en/'],
];
}

Expand All @@ -105,8 +109,10 @@ public static function provideURN(): array
'urn' => [true, 'urn:x-simplesamlphp:phpunit'],
'url' => [false, 'https://www.simplesamlphp.org'],
'same-doc' => [false, '#_53d830ab1be17291a546c95c7f1cdf8d3d23c959e6'],
'bogus' => [false, 'stupid value'],
'invalid_char' => [false, 'https://a⒈com'],
'intl' => [false, 'https://niño.com'],
'spn' => [false, 'spn:a4cf592f-a64c-46ff-a788-b260f474525b'],
'typos' => [false, 'https//www.uni.l/en/'],
];
}
}

0 comments on commit f6904dd

Please sign in to comment.