wiki.techinc.nl/tests/phpunit/unit/includes/utils/UrlUtilsProviders.php
Tim Starling e17b872a7f UrlUtilsProviders::provideExpand: use cartesian product
Change-Id: Ia4d5d516a29dc212df6da9a0d6f72ac55a3bff29
2022-05-16 14:23:11 +10:00

580 lines
14 KiB
PHP

<?php
use MediaWiki\Utils\UrlUtils;
/**
* Shared data providers for UrlUtilsTest and some GlobalFunctions tests
*/
class UrlUtilsProviders {
private const DEFAULT_PROTOS = [
'http' => PROTO_HTTP,
'https' => PROTO_HTTPS,
'protocol-relative' => PROTO_RELATIVE,
'current' => PROTO_CURRENT,
'fallback' => PROTO_FALLBACK,
'canonical' => PROTO_CANONICAL,
'internal' => PROTO_INTERNAL,
];
public static function provideExpandException(): Generator {
foreach ( self::DEFAULT_PROTOS as $protoDesc => $defaultProto ) {
$options = [
UrlUtils::SERVER => null,
UrlUtils::CANONICAL_SERVER => 'http://example.com',
UrlUtils::INTERNAL_SERVER => 'http://example.com',
];
if ( $defaultProto === PROTO_CANONICAL ) {
$options[UrlUtils::CANONICAL_SERVER] = null;
} elseif ( $defaultProto === PROTO_INTERNAL ) {
$options[UrlUtils::INTERNAL_SERVER] = null;
}
yield "defaultProto $protoDesc with appropriate options unset" => [
$options, $defaultProto, BadMethodCallException::class,
'Cannot call expand() if the appropriate SERVER/CANONICAL_SERVER/INTERNAL_SERVER' .
' option was not passed to the constructor',
];
}
yield 'protocol-relative server with port and custom HTTPS port' => [
[ UrlUtils::SERVER => '//example.com:123', UrlUtils::HTTPS_PORT => 456 ],
PROTO_HTTPS,
Exception::class,
'A protocol-relative server may not contain a port number',
];
}
public static function provideExpand() {
$modes = [ 'http', 'https' ];
$servers = [
'http://example.com',
'https://example.com',
'//example.com',
];
$defaultProtos = [
'http' => PROTO_HTTP,
'https' => PROTO_HTTPS,
'protocol-relative' => PROTO_RELATIVE,
'fallback' => PROTO_FALLBACK,
'canonical' => PROTO_CANONICAL,
];
foreach ( ArrayUtils::cartesianProduct( $modes, $servers, $modes, array_keys( $defaultProtos ) )
as [ $fallbackProto, $server, $canServerMode, $protoDesc ]
) {
$defaultProto = $defaultProtos[$protoDesc];
$canServer = "$canServerMode://example2.com";
$options = [
UrlUtils::SERVER => $server,
UrlUtils::CANONICAL_SERVER => $canServer,
UrlUtils::HTTPS_PORT => 443,
UrlUtils::FALLBACK_PROTOCOL => $fallbackProto,
];
$case = "fallback: $fallbackProto, default: $protoDesc, server: $server, canonical: $canServer";
yield "No-op fully-qualified http URL ($case)" => [
'http://example.com',
$options, $defaultProto,
'http://example.com',
];
yield "No-op fully-qualified https URL ($case)" => [
'https://example.com',
$options, $defaultProto,
'https://example.com',
];
yield "No-op rootless path-only URL ($case" => [
"wiki/FooBar",
$options, $defaultProto,
'wiki/FooBar',
];
// Determine expected protocol
if ( $protoDesc === 'protocol-relative' ) {
$p = '';
} elseif ( $protoDesc === 'fallback' ) {
$p = "$fallbackProto:";
} elseif ( $protoDesc === 'canonical' ) {
$p = "$canServerMode:";
} else {
$p = $protoDesc . ':';
}
yield "Expand protocol-relative URL ($case)" => [
'//wikipedia.org',
$options, $defaultProto,
"$p//wikipedia.org",
];
// Determine expected server name
if ( $protoDesc === 'canonical' ) {
$srv = $canServer;
} elseif ( $server === '//example.com' ) {
$srv = $p . $server;
} else {
$srv = $server;
}
yield "Expand path that starts with slash ($case)" => [
'/wiki/FooBar',
$options, $defaultProto,
"$srv/wiki/FooBar",
];
}
// Non-standard https port
$optionsRel111 = [
UrlUtils::SERVER => '//wiki.example.com',
UrlUtils::CANONICAL_SERVER => 'http://wiki.example.com',
UrlUtils::HTTPS_PORT => 111,
UrlUtils::FALLBACK_PROTOCOL => 'https',
];
yield "No-op foreign URL, ignore custom port config" => [
'https://foreign.example.com/foo',
$optionsRel111, PROTO_HTTPS,
'https://foreign.example.com/foo',
];
yield "No-op foreign URL, preserve existing port" => [
'https://foreign.example.com:222/foo',
$optionsRel111, PROTO_HTTPS,
'https://foreign.example.com:222/foo',
];
yield "Expand path with custom HTTPS port" => [
'/foo',
$optionsRel111, PROTO_HTTPS,
'https://wiki.example.com:111/foo',
];
// Silly corner cases
yield 'CanonicalServer with no protocol' => [
'/',
[
UrlUtils::CANONICAL_SERVER => '//dont.do.this',
UrlUtils::FALLBACK_PROTOCOL => 'https',
],
PROTO_CANONICAL,
'http://dont.do.this/',
];
yield 'InternalServer with no protocol' => [
'/',
[
UrlUtils::INTERNAL_SERVER => '//dont.do.this',
UrlUtils::FALLBACK_PROTOCOL => 'https',
],
PROTO_INTERNAL,
'http://dont.do.this/',
];
// XXX Find something that this will actually return null for
// XXX Add dot-removing tests
}
public static function provideGetServer(): Generator {
foreach ( self::provideExpand() as $desc => [ $input, $options, $defaultProto, $expected ]
) {
if ( $input !== '/wiki/FooBar' ) {
continue;
}
$desc = str_replace( 'Expand path that starts with slash (', '', $desc );
$desc = str_replace( ')', '', $desc );
$expected = str_replace( '/wiki/FooBar', '', $expected );
yield $desc => [ $options, $defaultProto, $expected ];
}
}
public static function provideAssemble(): Generator {
$schemes = [
'' => [],
'//' => [
'delimiter' => '//',
],
'http://' => [
'scheme' => 'http',
'delimiter' => '://',
],
];
$hosts = [
'' => [],
'example.com' => [
'host' => 'example.com',
],
'example.com:123' => [
'host' => 'example.com',
'port' => 123,
],
'id@example.com' => [
'user' => 'id',
'host' => 'example.com',
],
'id@example.com:123' => [
'user' => 'id',
'host' => 'example.com',
'port' => 123,
],
'id:key@example.com' => [
'user' => 'id',
'pass' => 'key',
'host' => 'example.com',
],
'id:key@example.com:123' => [
'user' => 'id',
'pass' => 'key',
'host' => 'example.com',
'port' => 123,
],
];
foreach ( $schemes as $scheme => $schemeParts ) {
foreach ( $hosts as $host => $hostParts ) {
foreach ( [ '', '/', '/0', '/path' ] as $path ) {
foreach ( [ '', '0', 'query' ] as $query ) {
foreach ( [ '', '0', 'fragment' ] as $fragment ) {
$parts = array_merge(
$schemeParts,
$hostParts
);
$url = $scheme .
$host .
$path;
if ( $path !== '' ) {
$parts['path'] = $path;
}
if ( $query !== '' ) {
$parts['query'] = $query;
$url .= '?' . $query;
}
if ( $fragment !== '' ) {
$parts['fragment'] = $fragment;
$url .= '#' . $fragment;
}
yield [ $parts, $url ];
}
}
}
}
}
yield [
[
'scheme' => 'http',
'delimiter' => '://',
'user' => 'id',
'pass' => 'key',
'host' => 'example.org',
'port' => 321,
'path' => '/over/there',
'query' => 'name=ferret&foo=bar',
'fragment' => 'nose',
],
'http://id:key@example.org:321/over/there?name=ferret&foo=bar#nose',
];
// Account for parse_url() on PHP >= 8 returning an empty query field for URLs ending with
// '?' such as "http://url.with.empty.query/foo?" (T268852)
yield [
[
'scheme' => 'http',
'delimiter' => '://',
'host' => 'url.with.empty.query',
'path' => '/foo',
'query' => '',
],
'http://url.with.empty.query/foo',
];
}
public static function provideRemoveDotSegments(): Generator {
yield [ '/a/b/c/./../../g', '/a/g' ];
yield [ 'mid/content=5/../6', 'mid/6' ];
yield [ '/a//../b', '/a/b' ];
yield [ '/.../a', '/.../a' ];
yield [ '.../a', '.../a' ];
yield [ '', '' ];
yield [ '/', '/' ];
yield [ '//', '//' ];
yield [ '.', '' ];
yield [ '..', '' ];
yield [ '...', '...' ];
yield [ '/.', '/' ];
yield [ '/..', '/' ];
yield [ './', '' ];
yield [ '../', '' ];
yield [ './a', 'a' ];
yield [ '../a', 'a' ];
yield [ '../../a', 'a' ];
yield [ '.././a', 'a' ];
yield [ './../a', 'a' ];
yield [ '././a', 'a' ];
yield [ '../../', '' ];
yield [ '.././', '' ];
yield [ './../', '' ];
yield [ '././', '' ];
yield [ '../..', '' ];
yield [ '../.', '' ];
yield [ './..', '' ];
yield [ './.', '' ];
yield [ '/../../a', '/a' ];
yield [ '/.././a', '/a' ];
yield [ '/./../a', '/a' ];
yield [ '/././a', '/a' ];
yield [ '/../../', '/' ];
yield [ '/.././', '/' ];
yield [ '/./../', '/' ];
yield [ '/././', '/' ];
yield [ '/../..', '/' ];
yield [ '/../.', '/' ];
yield [ '/./..', '/' ];
yield [ '/./.', '/' ];
yield [ 'b/../../a', '/a' ];
yield [ 'b/.././a', '/a' ];
yield [ 'b/./../a', '/a' ];
yield [ 'b/././a', 'b/a' ];
yield [ 'b/../../', '/' ];
yield [ 'b/.././', '/' ];
yield [ 'b/./../', '/' ];
yield [ 'b/././', 'b/' ];
yield [ 'b/../..', '/' ];
yield [ 'b/../.', '/' ];
yield [ 'b/./..', '/' ];
yield [ 'b/./.', 'b/' ];
yield [ '/b/../../a', '/a' ];
yield [ '/b/.././a', '/a' ];
yield [ '/b/./../a', '/a' ];
yield [ '/b/././a', '/b/a' ];
yield [ '/b/../../', '/' ];
yield [ '/b/.././', '/' ];
yield [ '/b/./../', '/' ];
yield [ '/b/././', '/b/' ];
yield [ '/b/../..', '/' ];
yield [ '/b/../.', '/' ];
yield [ '/b/./..', '/' ];
yield [ '/b/./.', '/b/' ];
}
public static function provideValidProtocols(): Generator {
foreach ( [ 'validProtocols', 'validAbsoluteProtocols' ] as $method ) {
yield "$method with string for UrlProtocols" =>
[ $method, 'some|string', 'some|string' ];
yield "$method simple case" => [ $method, [ 'foo', 'bar' ], 'foo|bar' ];
yield "$method reserved characters" => [ $method,
[ '^si|lly', 'in/valid', 'p[ro*t?o.c+o(ls$' ],
'\^si\|lly|in\/valid|p\[ro\*t\?o\.c\+o\(ls\$' ];
}
yield 'validProtocols with relative' =>
[ 'validProtocols', [ 'a', '//', 'b' ], 'a|\/\/|b' ];
yield 'validAbsoluteProtocols with relative' =>
[ 'validAbsoluteProtocols', [ 'a', '//', 'b' ], 'a|b' ];
}
public static function provideParse(): Generator {
yield [
'//example.org',
[
'scheme' => '',
'delimiter' => '//',
'host' => 'example.org',
]
];
yield [
'http://example.org',
[
'scheme' => 'http',
'delimiter' => '://',
'host' => 'example.org',
]
];
yield [
'https://example.org',
[
'scheme' => 'https',
'delimiter' => '://',
'host' => 'example.org',
]
];
yield [
'http://id:key@example.org:123/path?foo=bar#baz',
[
'scheme' => 'http',
'delimiter' => '://',
'user' => 'id',
'pass' => 'key',
'host' => 'example.org',
'port' => 123,
'path' => '/path',
'query' => 'foo=bar',
'fragment' => 'baz',
]
];
yield [
'file://example.org/etc/php.ini',
[
'scheme' => 'file',
'delimiter' => '://',
'host' => 'example.org',
'path' => '/etc/php.ini',
]
];
yield [
'file:///etc/php.ini',
[
'scheme' => 'file',
'delimiter' => '://',
'host' => '',
'path' => '/etc/php.ini',
]
];
yield [
'file:///c:/',
[
'scheme' => 'file',
'delimiter' => '://',
'host' => '',
'path' => '/c:/',
]
];
yield [
'mailto:id@example.org',
[
'scheme' => 'mailto',
'delimiter' => ':',
'host' => 'id@example.org',
'path' => '',
]
];
yield [
'mailto:id@example.org?subject=Foo',
[
'scheme' => 'mailto',
'delimiter' => ':',
'host' => 'id@example.org',
'path' => '',
'query' => 'subject=Foo',
]
];
yield [
'mailto:?subject=Foo',
[
'scheme' => 'mailto',
'delimiter' => ':',
'host' => '',
'path' => '',
'query' => 'subject=Foo',
]
];
yield [
'invalid://test/',
null
];
// T212067
yield [
'//evil.com?example.org/foo/bar',
[
'scheme' => '',
'delimiter' => '//',
'host' => 'evil.com',
'query' => 'example.org/foo/bar',
]
];
yield [
'//evil.com?example.org/foo/bar?baz#quux',
[
'scheme' => '',
'delimiter' => '//',
'host' => 'evil.com',
'query' => 'example.org/foo/bar?baz',
'fragment' => 'quux',
]
];
yield [
'//evil.com?example.org?baz#quux',
[
'scheme' => '',
'delimiter' => '//',
'host' => 'evil.com',
'query' => 'example.org?baz',
'fragment' => 'quux',
]
];
yield [
'//evil.com?example.org#quux',
[
'scheme' => '',
'delimiter' => '//',
'host' => 'evil.com',
'query' => 'example.org',
'fragment' => 'quux',
]
];
yield [
'%0Ahttp://example.com',
null,
];
yield [
'http:///test.com',
null,
];
// T294559
yield [
'//xy.wikimedia.org/wiki/Foo:1234',
[
'scheme' => '',
'delimiter' => '//',
'host' => 'xy.wikimedia.org',
'path' => '/wiki/Foo:1234'
]
];
yield [
'//xy.wikimedia.org:8888/wiki/Foo:1234',
[
'scheme' => '',
'delimiter' => '//',
'host' => 'xy.wikimedia.org',
'path' => '/wiki/Foo:1234',
'port' => 8888,
]
];
}
public static function provideMatchesDomainList(): Generator {
$protocols = [ 'HTTP' => 'http:', 'HTTPS' => 'https:', 'protocol-relative' => '' ];
foreach ( $protocols as $pDesc => $p ) {
yield "No matches for empty domains array, $pDesc URL" => [
"$p//www.example.com",
[],
false,
];
yield "Exact match in domains array, $pDesc URL" => [
"$p//www.example.com",
[ 'www.example.com' ],
true,
];
yield "Match without subdomain in domains array, $pDesc URL" => [
"$p//www.example.com",
[ 'example.com' ],
true,
];
yield "Exact match with other domains in array, $pDesc URL" => [
"$p//www.example2.com",
[ 'www.example.com', 'www.example2.com', 'www.example3.com' ],
true,
];
yield "Match without subdomain with other domains in array, $pDesc URL" => [
"$p//www.example2.com",
[ 'example.com', 'example2.com', 'example3,com' ],
true,
];
yield "Domain not in array, $pDesc URL" => [
"$p//www.example4.com",
[ 'example.com', 'example2.com', 'example3,com' ],
false,
];
yield "Non-matching substring of domain, $pDesc URL" => [
"$p//nds-nl.wikipedia.org",
[ 'nl.wikipedia.org' ],
false,
];
}
}
}