580 lines
14 KiB
PHP
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,
|
|
];
|
|
}
|
|
}
|
|
|
|
}
|