diff --git a/examples/get_relative.php b/examples/get_relative.php new file mode 100644 index 0000000..d21d6b6 --- /dev/null +++ b/examples/get_relative.php @@ -0,0 +1,20 @@ +get('test', array( + 'key' => 'value', +)); +assert('https://www.example.com/api/test?key=value' === $curl->url); +assert($curl->url === $curl->effectiveUrl); + +// https://www.example.com/root?key=value +$response = $curl->get('/root', array( + 'key' => 'value', +)); +assert('https://www.example.com/root?key=value' === $curl->url); +assert($curl->url === $curl->effectiveUrl); diff --git a/src/Curl/Curl.php b/src/Curl/Curl.php index b582a3e..0252fec 100644 --- a/src/Curl/Curl.php +++ b/src/Curl/Curl.php @@ -25,7 +25,6 @@ class Curl public $httpStatusCode = 0; public $httpErrorMessage = null; - public $baseUrl = null; public $url = null; public $requestHeaders = null; public $responseHeaders = null; @@ -59,7 +58,7 @@ class Curl private $defaultDecoder = null; public static $RFC2616 = array( - // RFC2616: "any CHAR except CTLs or separators". + // RFC 2616: "any CHAR except CTLs or separators". // CHAR = // CTL = @@ -76,7 +75,7 @@ class Curl 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '|', '~', ); public static $RFC6265 = array( - // RFC6265: "US-ASCII characters excluding CTLs, whitespace DQUOTE, comma, semicolon, and backslash". + // RFC 6265: "US-ASCII characters excluding CTLs, whitespace DQUOTE, comma, semicolon, and backslash". // %x21 '!', // %x23-2B @@ -261,7 +260,7 @@ class Curl if (is_array($url)) { $data = $query_parameters; $query_parameters = $url; - $url = $this->baseUrl; + $url = (string)$this->url; } $this->setUrl($url, $query_parameters); @@ -390,6 +389,10 @@ class Curl } $this->errorMessage = $this->curlError ? $this->curlErrorMessage : $this->httpErrorMessage; + // Reset select deferred properties so that they may be recalculated. + unset($this->effectiveUrl); + unset($this->totalTime); + // Allow multicurl to attempt retry as needed. if ($this->isChildOfMultiCurl) { return; @@ -433,7 +436,7 @@ class Curl { if (is_array($url)) { $data = $url; - $url = $this->baseUrl; + $url = (string)$this->url; } $this->setUrl($url, $data); $this->setOpt(CURLOPT_CUSTOMREQUEST, 'GET'); @@ -487,7 +490,7 @@ class Curl { if (is_array($url)) { $data = $url; - $url = $this->baseUrl; + $url = (string)$this->url; } $this->setUrl($url, $data); $this->setOpt(CURLOPT_CUSTOMREQUEST, 'HEAD'); @@ -508,7 +511,7 @@ class Curl { if (is_array($url)) { $data = $url; - $url = $this->baseUrl; + $url = (string)$this->url; } $this->setUrl($url, $data); $this->removeHeader('Content-Length'); @@ -529,7 +532,7 @@ class Curl { if (is_array($url)) { $data = $url; - $url = $this->baseUrl; + $url = (string)$this->url; } if (is_array($data) && empty($data)) { @@ -572,7 +575,7 @@ class Curl if (is_array($url)) { $follow_303_with_post = (bool)$data; $data = $url; - $url = $this->baseUrl; + $url = (string)$this->url; } $this->setUrl($url); @@ -613,7 +616,7 @@ class Curl { if (is_array($url)) { $data = $url; - $url = $this->baseUrl; + $url = (string)$this->url; } $this->setUrl($url); $this->setOpt(CURLOPT_CUSTOMREQUEST, 'PUT'); @@ -642,7 +645,7 @@ class Curl { if (is_array($url)) { $data = $url; - $url = $this->baseUrl; + $url = (string)$this->url; } $this->setUrl($url); $this->setOpt(CURLOPT_CUSTOMREQUEST, 'SEARCH'); @@ -1071,8 +1074,14 @@ class Curl */ public function setUrl($url, $mixed_data = '') { - $this->baseUrl = $url; - $this->url = $this->buildUrl($url, $mixed_data); + $built_url = $this->buildUrl($url, $mixed_data); + + if ($this->url === null) { + $this->url = (string)new Url($built_url); + } else { + $this->url = (string)new Url($this->url, $built_url); + } + $this->setOpt(CURLOPT_URL, $this->url); } diff --git a/src/Curl/StrUtil.php b/src/Curl/StrUtil.php new file mode 100644 index 0000000..b32cfaf --- /dev/null +++ b/src/Curl/StrUtil.php @@ -0,0 +1,20 @@ +baseUrl = $base_url; + $this->relativeUrl = $relative_url; + } + + public function __toString() + { + return $this->absolutizeUrl(); + } + + /** + * Remove dot segments. + * + * Interpret and remove the special "." and ".." path segments from a referenced path. + */ + public static function removeDotSegments($input) + { + // 1. The input buffer is initialized with the now-appended path + // components and the output buffer is initialized to the empty + // string. + $output = ''; + + // 2. While the input buffer is not empty, loop as follows: + while (!empty($input)) { + + // A. If the input buffer begins with a prefix of "../" or "./", + // then remove that prefix from the input buffer; otherwise, + if (StrUtil::startsWith($input, '../')) { + $input = substr($input, 3); + } elseif (StrUtil::startsWith($input, './')) { + $input = substr($input, 2); + + // B. if the input buffer begins with a prefix of "/./" or "/.", + // where "." is a complete path segment, then replace that + // prefix with "/" in the input buffer; otherwise, + } elseif (StrUtil::startsWith($input, '/./')) { + $input = substr($input, 2); + } elseif ($input === '/.') { + $input = '/'; + + // C. if the input buffer begins with a prefix of "/../" or "/..", + // where ".." is a complete path segment, then replace that + // prefix with "/" in the input buffer and remove the last + // segment and its preceding "/" (if any) from the output + // buffer; otherwise, + } elseif (StrUtil::startsWith($input, '/../')) { + $input = substr($input, 3); + $output = substr_replace($output, '', mb_strrpos($output, '/')); + } elseif ($input === '/..') { + $input = '/'; + $output = substr_replace($output, '', mb_strrpos($output, '/')); + + // D. if the input buffer consists only of "." or "..", then remove + // that from the input buffer; otherwise, + } elseif ($input === '.' || $input === '..') { + $input = ''; + + // E. move the first path segment in the input buffer to the end of + // the output buffer, including the initial "/" character (if + // any) and any subsequent characters up to, but not including, + // the next "/" character or the end of the input buffer. + } elseif (!(($pos = mb_strpos($input, '/', 1)) === false)) { + $output .= substr($input, 0, $pos); + $input = substr_replace($input, '', 0, $pos); + } else { + $output .= $input; + $input = ''; + } + } + + // 3. Finally, the output buffer is returned as the result of + // remove_dot_segments. + return $output . $input; + } + + /** + * Absolutize url. + * + * Combine the base and relative url into an absolute url. + */ + private function absolutizeUrl() + { + $b = $this->parseUrl($this->baseUrl); + + if (!($this->relativeUrl === null)) { + $r = $this->parseUrl($this->relativeUrl); + + // Copy relative parts to base url. + if (isset($r['scheme'])) { + $b['scheme'] = $r['scheme']; + } + if (isset($r['host'])) { + $b['host'] = $r['host']; + } + if (isset($r['port'])) { + $b['port'] = $r['port']; + } + if (isset($r['user'])) { + $b['user'] = $r['user']; + } + if (isset($r['pass'])) { + $b['pass'] = $r['pass']; + } + + if (!isset($r['path']) || $r['path'] === '') { + $r['path'] = '/'; + } + // Merge relative url with base when relative url's path doesn't start with a slash. + if (!(StrUtil::startsWith($r['path'], '/'))) { + $base = mb_strrchr($b['path'], '/', true); + if ($base === false) { + $base = ''; + } + $r['path'] = $base . '/' . $r['path']; + } + $b['path'] = $r['path']; + $b['path'] = $this->removeDotSegments($b['path']); + + if (isset($r['query'])) { + $b['query'] = $r['query']; + } + if (isset($r['fragment'])) { + $b['fragment'] = $r['fragment']; + } + } + + if (!isset($b['path'])) { + $b['path'] = '/'; + } + + $absolutized_url = $this->unparseUrl($b); + return $absolutized_url; + } + + /** + * Parse url. + * + * Parse url into components of a URI as specified by RFC 3986. + */ + private function parseUrl($url) + { + return parse_url($url); + } + + /** + * Unparse url. + * + * Combine url components into a url. + */ + private function unparseUrl($parsed_url) { + $scheme = isset($parsed_url['scheme']) ? $parsed_url['scheme'] . '://' : ''; + $host = isset($parsed_url['host']) ? $parsed_url['host'] : ''; + $port = isset($parsed_url['port']) ? ':' . $parsed_url['port'] : ''; + $user = isset($parsed_url['user']) ? $parsed_url['user'] : ''; + $pass = isset($parsed_url['pass']) ? ':' . $parsed_url['pass'] : ''; + $pass = ($user || $pass) ? $pass . '@' : ''; + $path = isset($parsed_url['path']) ? $parsed_url['path'] : ''; + $query = isset($parsed_url['query']) ? '?' . $parsed_url['query'] : ''; + $fragment = isset($parsed_url['fragment']) ? '#' . $parsed_url['fragment'] : ''; + $unparsed_url = $scheme . $user . $pass . $host . $port . $path . $query . $fragment; + return $unparsed_url; + } +} diff --git a/tests/PHPCurlClass/PHPCurlClassTest.php b/tests/PHPCurlClass/PHPCurlClassTest.php index 700d5ff..61b7cb4 100644 --- a/tests/PHPCurlClass/PHPCurlClassTest.php +++ b/tests/PHPCurlClass/PHPCurlClassTest.php @@ -2,8 +2,8 @@ namespace CurlTest; -use \Curl\Curl; use \Curl\CaseInsensitiveArray; +use \Curl\Curl; use \Helper\Test; class CurlTest extends \PHPUnit\Framework\TestCase @@ -105,43 +105,36 @@ class CurlTest extends \PHPUnit\Framework\TestCase // curl -v --get --request GET "http://127.0.0.1:8000/" --data "foo=bar" $test = new Test(); $test->server('server', 'GET', $data); - $this->assertEquals(Test::TEST_URL, $test->curl->baseUrl); $this->assertEquals(Test::TEST_URL . '?' . http_build_query($data), $test->curl->url); // curl -v --request POST "http://127.0.0.1:8000/" --data "foo=bar" $test = new Test(); $test->server('server', 'POST', $data); - $this->assertEquals(Test::TEST_URL, $test->curl->baseUrl); $this->assertEquals(Test::TEST_URL, $test->curl->url); // curl -v --request PUT "http://127.0.0.1:8000/" --data "foo=bar" $test = new Test(); $test->server('server', 'PUT', $data); - $this->assertEquals(Test::TEST_URL, $test->curl->baseUrl); $this->assertEquals(Test::TEST_URL, $test->curl->url); // curl -v --request PATCH "http://127.0.0.1:8000/" --data "foo=bar" $test = new Test(); $test->server('server', 'PATCH', $data); - $this->assertEquals(Test::TEST_URL, $test->curl->baseUrl); $this->assertEquals(Test::TEST_URL, $test->curl->url); // curl -v --request DELETE "http://127.0.0.1:8000/?foo=bar" $test = new Test(); $test->server('server', 'DELETE', $data); - $this->assertEquals(Test::TEST_URL, $test->curl->baseUrl); $this->assertEquals(Test::TEST_URL . '?' . http_build_query($data), $test->curl->url); // curl -v --get --request HEAD --head "http://127.0.0.1:8000/" --data "foo=bar" $test = new Test(); $test->server('server', 'HEAD', $data); - $this->assertEquals(Test::TEST_URL, $test->curl->baseUrl); $this->assertEquals(Test::TEST_URL . '?' . http_build_query($data), $test->curl->url); // curl -v --get --request OPTIONS "http://127.0.0.1:8000/" --data "foo=bar" $test = new Test(); $test->server('server', 'OPTIONS', $data); - $this->assertEquals(Test::TEST_URL, $test->curl->baseUrl); $this->assertEquals(Test::TEST_URL . '?' . http_build_query($data), $test->curl->url); } @@ -152,49 +145,41 @@ class CurlTest extends \PHPUnit\Framework\TestCase $curl = new Curl(Test::TEST_URL); $curl->setHeader('X-DEBUG-TEST', 'delete_with_body'); $curl->delete($data, array('wibble' => 'wubble')); - $this->assertEquals(Test::TEST_URL, $curl->baseUrl); $this->assertEquals('{"get":{"key":"value"},"delete":{"wibble":"wubble"}}', $curl->rawResponse); $curl = new Curl(Test::TEST_URL); $curl->setHeader('X-DEBUG-TEST', 'get'); $curl->delete($data); - $this->assertEquals(Test::TEST_URL, $curl->baseUrl); $this->assertEquals('key=value', $curl->response); $curl = new Curl(Test::TEST_URL); $curl->setHeader('X-DEBUG-TEST', 'get'); $curl->get($data); - $this->assertEquals(Test::TEST_URL, $curl->baseUrl); $this->assertEquals('key=value', $curl->response); $curl = new Curl(Test::TEST_URL); $curl->setHeader('X-DEBUG-TEST', 'get'); $curl->head($data); - $this->assertEquals(Test::TEST_URL, $curl->baseUrl); $this->assertEquals('HEAD /?key=value HTTP/1.1', $curl->requestHeaders['Request-Line']); $curl = new Curl(Test::TEST_URL); $curl->setHeader('X-DEBUG-TEST', 'get'); $curl->options($data); - $this->assertEquals(Test::TEST_URL, $curl->baseUrl); $this->assertEquals('key=value', $curl->response); $curl = new Curl(Test::TEST_URL); $curl->setHeader('X-DEBUG-TEST', 'request_method'); $curl->patch($data); - $this->assertEquals(Test::TEST_URL, $curl->baseUrl); $this->assertEquals('PATCH', $curl->response); $curl = new Curl(Test::TEST_URL); $curl->setHeader('X-DEBUG-TEST', 'post'); $curl->post($data); - $this->assertEquals(Test::TEST_URL, $curl->baseUrl); $this->assertEquals('key=value', $curl->response); $curl = new Curl(Test::TEST_URL); $curl->setHeader('X-DEBUG-TEST', 'put'); $curl->put($data); - $this->assertEquals(Test::TEST_URL, $curl->baseUrl); $this->assertEquals('key=value', $curl->response); } @@ -255,6 +240,12 @@ class CurlTest extends \PHPUnit\Framework\TestCase $test->curl->setOpt(CURLOPT_FOLLOWLOCATION, true); $test->server('redirect', 'GET'); $this->assertEquals(Test::TEST_URL . '?redirect', $test->curl->effectiveUrl); + + $test = new Test(); + $test->server('get', 'GET'); + $this->assertEquals(Test::TEST_URL, $test->curl->effectiveUrl); + $test->server('get', 'GET', array('a' => '1', 'b' => '2')); + $this->assertEquals(Test::TEST_URL . '?a=1&b=2', $test->curl->effectiveUrl); } public function testPostRequestMethod() @@ -3398,4 +3389,96 @@ class CurlTest extends \PHPUnit\Framework\TestCase $this->assertEquals($expect_retries, $test->curl->retries); } } + + public function testRelativeUrl() + { + $curl = new Curl(Test::TEST_URL . 'path/'); + $this->assertEquals('http://127.0.0.1:8000/path/', (string)$curl->url); + + $curl->get('test', array( + 'a' => '1', + 'b' => '2', + )); + $this->assertEquals('http://127.0.0.1:8000/path/test?a=1&b=2', (string)$curl->url); + + $curl->get('/root', array( + 'c' => '3', + 'd' => '4', + )); + $this->assertEquals('http://127.0.0.1:8000/root?c=3&d=4', (string)$curl->url); + + $tests = array( + array( + 'args' => array( + 'http://www.example.com/', + '/foo', + ), + 'expected' => 'http://www.example.com/foo', + ), + array( + 'args' => array( + 'http://www.example.com/', + '/foo/', + ), + 'expected' => 'http://www.example.com/foo/', + ), + array( + 'args' => array( + 'http://www.example.com/', + '/dir/page.html', + ), + 'expected' => 'http://www.example.com/dir/page.html', + ), + array( + 'args' => array( + 'http://www.example.com/dir1/page2.html', + '/dir/page.html', + ), + 'expected' => 'http://www.example.com/dir/page.html', + ), + array( + 'args' => array( + 'http://www.example.com/dir1/page2.html', + 'dir/page.html', + ), + 'expected' => 'http://www.example.com/dir1/dir/page.html', + ), + array( + 'args' => array( + 'http://www.example.com/dir1/dir3/page.html', + '../dir/page.html', + ), + 'expected' => 'http://www.example.com/dir1/dir/page.html', + ), + ); + foreach ($tests as $test) { + $curl = new Curl($test['args']['0']); + $curl->setUrl($test['args']['1']); + $this->assertEquals($test['expected'], $curl->getOpt(CURLOPT_URL)); + + $curl = new Curl($test['args']['0']); + $curl->setUrl($test['args']['1'], array('a' => '1', 'b' => '2')); + $this->assertEquals($test['expected'] . '?a=1&b=2', $curl->getOpt(CURLOPT_URL)); + + $curl = new Curl(); + $curl->setUrl($test['args']['0']); + $curl->setUrl($test['args']['1']); + $this->assertEquals($test['expected'], $curl->getOpt(CURLOPT_URL)); + + $curl = new Curl(); + $curl->setUrl($test['args']['0'], array('a' => '1', 'b' => '2')); + $curl->setUrl($test['args']['1']); + $this->assertEquals($test['expected'] . '?a=1&b=2', $curl->getOpt(CURLOPT_URL)); + + $curl = new Curl(); + $curl->setUrl($test['args']['0']); + $curl->setUrl($test['args']['1'], array('a' => '1', 'b' => '2')); + $this->assertEquals($test['expected'] . '?a=1&b=2', $curl->getOpt(CURLOPT_URL)); + + $curl = new Curl(); + $curl->setUrl($test['args']['0'], array('a' => '1', 'b' => '2')); + $curl->setUrl($test['args']['1'], array('c' => '3', 'd' => '4')); + $this->assertEquals($test['expected'] . '?c=3&d=4', $curl->getOpt(CURLOPT_URL)); + } + } } diff --git a/tests/PHPCurlClass/PHPMultiCurlClassTest.php b/tests/PHPCurlClass/PHPMultiCurlClassTest.php index 1f248c9..2a64286 100644 --- a/tests/PHPCurlClass/PHPMultiCurlClassTest.php +++ b/tests/PHPCurlClass/PHPMultiCurlClassTest.php @@ -2290,7 +2290,6 @@ class MultiCurlTest extends \PHPUnit\Framework\TestCase $multi_curl = new MultiCurl(Test::TEST_URL); $multi_curl->setHeader('X-DEBUG-TEST', 'delete_with_body'); $multi_curl->addDelete($data, array('wibble' => 'wubble'))->complete(function ($instance) { - \PHPUnit\Framework\Assert::assertEquals(Test::TEST_URL, $instance->baseUrl); \PHPUnit\Framework\Assert::assertEquals( '{"get":{"key":"value"},"delete":{"wibble":"wubble"}}', $instance->rawResponse @@ -2301,7 +2300,6 @@ class MultiCurlTest extends \PHPUnit\Framework\TestCase $multi_curl = new MultiCurl(Test::TEST_URL); $multi_curl->setHeader('X-DEBUG-TEST', 'get'); $multi_curl->addDelete($data)->complete(function ($instance) { - \PHPUnit\Framework\Assert::assertEquals(Test::TEST_URL, $instance->baseUrl); \PHPUnit\Framework\Assert::assertEquals('key=value', $instance->response); }); $multi_curl->start(); @@ -2309,7 +2307,6 @@ class MultiCurlTest extends \PHPUnit\Framework\TestCase $multi_curl = new MultiCurl(Test::TEST_URL); $multi_curl->setHeader('X-DEBUG-TEST', 'get'); $multi_curl->addGet($data)->complete(function ($instance) { - \PHPUnit\Framework\Assert::assertEquals(Test::TEST_URL, $instance->baseUrl); \PHPUnit\Framework\Assert::assertEquals('key=value', $instance->response); }); $multi_curl->start(); @@ -2317,7 +2314,6 @@ class MultiCurlTest extends \PHPUnit\Framework\TestCase $multi_curl = new MultiCurl(Test::TEST_URL); $multi_curl->setHeader('X-DEBUG-TEST', 'get'); $multi_curl->addHead($data)->complete(function ($instance) { - \PHPUnit\Framework\Assert::assertEquals(Test::TEST_URL, $instance->baseUrl); \PHPUnit\Framework\Assert::assertEquals( 'HEAD /?key=value HTTP/1.1', $instance->requestHeaders['Request-Line'] @@ -2328,7 +2324,6 @@ class MultiCurlTest extends \PHPUnit\Framework\TestCase $multi_curl = new MultiCurl(Test::TEST_URL); $multi_curl->setHeader('X-DEBUG-TEST', 'get'); $multi_curl->addOptions($data)->complete(function ($instance) { - \PHPUnit\Framework\Assert::assertEquals(Test::TEST_URL, $instance->baseUrl); \PHPUnit\Framework\Assert::assertEquals('key=value', $instance->response); }); $multi_curl->start(); @@ -2336,7 +2331,6 @@ class MultiCurlTest extends \PHPUnit\Framework\TestCase $multi_curl = new MultiCurl(Test::TEST_URL); $multi_curl->setHeader('X-DEBUG-TEST', 'request_method'); $multi_curl->addPatch($data)->complete(function ($instance) { - \PHPUnit\Framework\Assert::assertEquals(Test::TEST_URL, $instance->baseUrl); \PHPUnit\Framework\Assert::assertEquals('PATCH', $instance->response); }); $multi_curl->start(); @@ -2344,7 +2338,6 @@ class MultiCurlTest extends \PHPUnit\Framework\TestCase $multi_curl = new MultiCurl(Test::TEST_URL); $multi_curl->setHeader('X-DEBUG-TEST', 'post'); $multi_curl->addPost($data)->complete(function ($instance) { - \PHPUnit\Framework\Assert::assertEquals(Test::TEST_URL, $instance->baseUrl); \PHPUnit\Framework\Assert::assertEquals('key=value', $instance->response); }); $multi_curl->start(); @@ -2352,7 +2345,6 @@ class MultiCurlTest extends \PHPUnit\Framework\TestCase $multi_curl = new MultiCurl(Test::TEST_URL); $multi_curl->setHeader('X-DEBUG-TEST', 'put'); $multi_curl->addPut($data)->complete(function ($instance) { - \PHPUnit\Framework\Assert::assertEquals(Test::TEST_URL, $instance->baseUrl); \PHPUnit\Framework\Assert::assertEquals('key=value', $instance->response); }); $multi_curl->start(); diff --git a/tests/PHPCurlClass/UrlTest.php b/tests/PHPCurlClass/UrlTest.php new file mode 100644 index 0000000..bd5dacd --- /dev/null +++ b/tests/PHPCurlClass/UrlTest.php @@ -0,0 +1,111 @@ + array( + 'http://www.example.com/', + '/foo', + ), + 'expected' => 'http://www.example.com/foo', + ), + array( + 'args' => array( + 'http://www.example.com/', + '/foo/', + ), + 'expected' => 'http://www.example.com/foo/', + ), + array( + 'args' => array( + 'http://www.example.com/', + '/dir/page.html', + ), + 'expected' => 'http://www.example.com/dir/page.html', + ), + array( + 'args' => array( + 'http://www.example.com/dir1/page2.html', + '/dir/page.html', + ), + 'expected' => 'http://www.example.com/dir/page.html', + ), + array( + 'args' => array( + 'http://www.example.com/dir1/page2.html', + 'dir/page.html', + ), + 'expected' => 'http://www.example.com/dir1/dir/page.html', + ), + array( + 'args' => array( + 'http://www.example.com/dir1/dir3/page.html', + '../dir/page.html', + ), + 'expected' => 'http://www.example.com/dir1/dir/page.html', + ), + ); + foreach ($tests as $test) { + $reflector = new \ReflectionClass('\Curl\Url'); + $url = $reflector->newInstanceArgs($test['args']); + $actual_url = (string)$url; + $this->assertEquals($test['expected'], $actual_url); + } + } + + public function testUrlInstances() + { + $a = new Url('https://developer.mozilla.org', '/'); + $this->assertEquals('https://developer.mozilla.org/', $a); + + $b = new Url('https://developer.mozilla.org'); + $this->assertEquals('https://developer.mozilla.org/', $b); + + $c = new Url($b, 'en-US/docs'); + $this->assertEquals('https://developer.mozilla.org/en-US/docs', $c); + + $d = new Url($b, '/en-US/docs'); + $this->assertEquals('https://developer.mozilla.org/en-US/docs', $d); + + $f = new Url($d, '/en-US/docs'); + $this->assertEquals('https://developer.mozilla.org/en-US/docs', $f); + + $g = new Url('https://developer.mozilla.org/fr-FR/toto', '/en-US/docs'); + $this->assertEquals('https://developer.mozilla.org/en-US/docs', $g); + + $h = new Url($a, '/en-US/docs'); + $this->assertEquals('https://developer.mozilla.org/en-US/docs', $h); + + $k = new Url('https://developers.mozilla.com', 'http://www.example.com'); + $this->assertEquals('http://www.example.com/', $k); + + $l = new Url($b, 'http://www.example.com'); + $this->assertEquals('http://www.example.com/', $l); + } + + public function testRemoveDotSegments() + { + // TODO: Add tests using "Normal Examples" and "Abnormal Examples" from RFC 3986. + $tests = array( + array( + 'path' => '/a/b/c/./../../g', + 'expected' => '/a/g', + ), + array( + 'path' => 'mid/content=5/../6', + 'expected' => 'mid/6', + ), + ); + foreach ($tests as $test) { + $actual_path = Url::removeDotSegments($test['path']); + $this->assertEquals($test['expected'], $actual_path); + } + } +}