package.xml0000664000175000017500000003151612705260505011365 0ustar motmot Net_URL2 pear.php.net Net_URL Class for parsing and handling URL. Provides parsing of URLs into their constituent parts (scheme, host, path etc.), URL generation, and resolving of relative URLs. Tom Klingenberg tkli tkli@php.net yes David Coallier davidc davidc@php.net no Christian Schmidt schmidt schmidt@php.net no 2016-04-18 2.2.1 2.1.0 stable stable BSD-3-Clause * Fix: Correct earlier regex delimiter * Fix: Travis missing hhvm-nightly * Fix: Travis PHP 5.2 * Fix: Correct case of method call name 5.1.4 1.4.0b1 2016-04-19 2.2.1 2.1.0 stable stable BSD-3-Clause * Fix: Correct earlier regex delimiter * Fix: Travis missing hhvm-nightly * Fix: Travis PHP 5.2 * Fix: Correct case of method call name 2015-04-18 2.2.0 2.1.0 stable stable BSD-3-Clause * Changed composer autoloader to classmap, fixes include path pollution 2014-12-27 2.1.1 2.1.0 stable stable BSD-3-Clause * Fixed #20473: Normalize query and fragment broken 2014-10-21 2.1.0 2.1.0 stable stable BSD-3-Clause * New: OPTION_DROP_SEQUENCE 2014-10-21 2.0.12 2.0.0 stable stable BSD-3-Clause * Removed: OPTION_DROP_SEQUENCE 2014-10-18 2.0.11 2.0.0 stable stable BSD-3-Clause * New: OPTION_DROP_SEQUENCE 2014-10-09 2.0.10 2.0.0 stable stable BSD-3-Clause * Imp: composer for pear * Fix: Documentation problem 2014-10-08 2.0.9 2.0.0 stable stable BSD-3-Clause * Fixed #20418: Incorrect normalization of URI with missing authority * Upd: Test for RFC 3986 Section 1.1.2 Examples * Upd: Travis CI - PHP 5.6 added 2014-10-07 2.0.8 2.0.0 stable stable BSD-3-Clause * Fixed #20420: Inconsistent setAuthority and getAuthority * Fixed #20423: URI with IPv6 or IPvFuture not parsed * Imp: Test for RFC 3986 Section 1.1.2 Examples 2014-09-07 2.0.7 2.0.0 stable stable BSD-3-Clause * Fixed #20385: Incorrect normalization of userinfo * Fixed #20399: Setting userinfo to FALSE not transparent 2014-07-21 2.0.6 2.0.0 stable stable BSD-3-Clause * Fixed #20304: file:// URI gets crippled 2014-01-01 2.0.5 2.0.0 stable stable BSD-3-Clause * Fixed #17036: Brackets broken for query variables * Fixed diverse coding style violations and misc. minor issues * Increased code coverage 2013-12-31 2.0.4 2.0.0 stable stable BSD-3-Clause * Fixed #20161: URLs with "0" as host fail to normalize with empty path * A flaw in NetURL2::removeDotSegments() introduced in 2.0.1 has been fixed * New: NetURL2::removeDotSegments() emits a warning on loop limit reach * Url-segment maximum count raised from 100 to 256 (loop limit) 2013-12-30 2.0.3 2.0.0 stable stable BSD-3-Clause * Fixed #20156: setAuthority() flaw with "0" as host * Fixed #20157: normalize() flaw with "0" as path * Fixed #20158: Fragment-only references are not resolved to non-absolute base URI * Fixed #20159: Authority not terminated by slash * Fixed diverse coding style violations and misc. minor issues * Increased code coverage * Added support for Scrutinizer CI 2013-12-27 2.0.2 2.0.0 stable stable BSD-3-Clause * Fixed #19684: Redirects containing spaces do not work * Fixed diverse coding style violations and misc. minor issues * Improved source package distribution * Added support for Travis CI 2013-12-24 2.0.1 2.0.0 stable stable BSD-3-Clause * Fixed Bug #20013: getNormalizedURL() adds leading "@" chars in the Authority * Fixed Bug #20016: Wrong data in 6d4f4dd "Package.xml preparation." * Fixed Bug #19176: resolve() does not merge the path if the base path is empty * Fixed Bug #19315: removeDotSegments("0") not working 2011-10-20 2.0.0 2.0.0 stable stable BSD-3-Clause * Fixed the version of the release. Follow the convention for Package2. * Fixed Bug #18917: URL2.php moved to ./Net/Net (davidc) 1.0.0 1.0.0 stable stable 2011-10-20 BSD-3-Clause * Fixed Bug #17036: Problem with parsed query string * Fixed Bug #17087: setOption() function is gone * Fixed #17166: Fluent Interface * Fixed #17167: Refactor __construct * Fixed Bug #18267: setQueryVariables() fails to encode array values * Fixed Bug #14399: Fixed multiple bugs in Net_URL2 (Missing setOption value, unused properties, etc.) 0.3.1 0.3.0 beta beta 2011-02-22 BSD-3-Clause * BC break: Removed setOption() to avoid undefined behaviour (bug #16674) * Fixed Bug #16854: Invalid package.xml making it impossible to install with Pyrus * Fixed Bug #16651: Port may be an empty string * Fixed Bug #16653: Don't make OPTION_SEPARATOR_(IN|OUT)PUT default to arg_separator.(in|out)put 0.3.0 0.3.0 beta beta 2009-09-05 BSD-3-Clause * Fixed #14399 (Errors in URL parsing (items #1 and #3)) * Fixed #14735 (Encode query string values) * Fixed #15546 (Add adding __toString()) * Fixed #15367 (Use RFC 3986-compliant version of rawurlencode() in PHP < 5.2) * Fixed #14289 (Add __get() and __set()) 0.2.0 0.2.0 beta beta 2008-06-18 BSD-3-Clause * Major rewrite to comply with RFC3986 (bug #11574). * Much better support for resolving relative URLs. * WARNING: Method and property names has changed to reflect the terminology used in the RFC - THIS RELEASE IS NOT BACKWARDS COMPATIBLE WITH VERSION 0.1.0. 0.1.0 0.1.0 beta beta 2007-05-08 BSD-3-Clause Convert to PHP5 only. PHP4 users should continue with version 1.0.15 Net_URL2-2.2.1/docs/example.php0000775000175000017500000000255312705260505014336 0ustar motmot * * @category Networking * @package Net_URL2 * @author Some Pear Developers * @license https://spdx.org/licenses/BSD-3-Clause BSD-3-Clause * @link https://tools.ietf.org/html/rfc3986 */ /** * This example will decode the url given and display its * constituent parts. */ error_reporting(E_ALL | E_STRICT); require 'Net/URL2.php'; $url = new Net_URL2( 'https://example.com/pls/portal30/PORTAL30.wwpob_page.changetabs?' .'p_back_url=http%3A%2F%2Fexample.com%2Fservlet%2Fpage%3F_pageid%3D360' .'%2C366%2C368%2C382%26_dad%3Dportal30%26_schema%3DPORTAL30&foo=bar' ); ?>
Protocol...: protocol; ?>

Username...: user; ?>

Password...: pass; ?>

Server.....: host; ?>

Port.......: port; ?>

File/path..: path; ?>

Querystring: querystring); ?>

Anchor.....: anchor;?>

Full URL...: getUrl(); ?>


Resolve path (.././/foo/bar/joe/./././../jabba): resolve('.././/foo/bar/joe/./././../jabba'); ?>
Net_URL2-2.2.1/docs/6470.php0000775000175000017500000000232212705260505013275 0ustar motmot * * @category Networking * @package Net_URL2 * @author Some Pear Developers * @license https://spdx.org/licenses/BSD-3-Clause BSD-3-Clause * @link https://tools.ietf.org/html/rfc3986 */ /** * This example will decode the url given and display its * constituent parts. */ error_reporting(E_ALL | E_STRICT); require_once 'Net/URL2.php'; $url = new Net_URL2('https://www.example.com/foo/bar/index.php?foo=bar'); ?>
Protocol...: protocol; ?>

Username...: user; ?>

Password...: pass; ?>

Server.....: host; ?>

Port.......: port; ?>

File/path..: path; ?>

Querystring: querystring); ?>

Anchor.....: anchor;?>

Full URL...: getUrl(); ?>

Resolve path (/.././/foo/bar/joe/./././../jabba): resolve('/.././/foo/bar/joe/./././../jabba'); ?>
Net_URL2-2.2.1/docs/BSD-3-CLAUSE-Heyes0000664000175000017500000000273312705260505014707 0ustar motmotCopyright (c) 2002-2003, Richard Heyes All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1) Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2) Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 3) Neither the name of the Richard Heyes nor the names of his contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. Net_URL2-2.2.1/tests/Net/URL2Test.php0000664000175000017500000010564512705260505015232 0ustar motmot * @license https://spdx.org/licenses/BSD-3-Clause BSD-3-Clause * @link https://tools.ietf.org/html/rfc3986 */ /** * Test class for Net_URL2. * * @category Networking * @package Net_URL2 * @author Some Pear Developers * @license https://spdx.org/licenses/BSD-3-Clause BSD-3-Clause * @version Release: 2.2.1 * @link https://pear.php.net/package/Net_URL2 */ class Net_URL2Test extends PHPUnit_Framework_TestCase { /** * Tests setting a zero-length string and false as authority * Also: Regression test for Bug #20420 * * @covers Net_URL2::setAuthority * @return void * @link https://pear.php.net/bugs/bug.php?id=20420 */ public function testSetEmptyAuthority() { $url = new Net_URL2('http://www.example.com/'); $url->setAuthority(''); $this->assertSame('', $url->getAuthority()); $this->assertSame('', $url->getHost()); $this->assertSame(false, $url->getPort()); $this->assertSame(false, $url->getUserinfo()); $this->assertSame(false, $url->getUser()); $url->setAuthority(false); $this->assertSame(false, $url->getAuthority()); } /** * Tests setting an empty userinfo part * Also: Regression test for Bug #20013 and Bug #20399 * * @covers Net_URL2::setUserinfo * @covers Net_URL2::getUserinfo * @covers Net_URL2::getURL * @return void * @link https://pear.php.net/bugs/bug.php?id=20013 * @link https://pear.php.net/bugs/bug.php?id=20399 */ public function testSetEmptyUserinfo() { $url = new Net_URL2('http://@www.example.com/'); $this->assertSame('http://www.example.com/', $url->getURL()); $url = new Net_URL2('http://www.example.com/'); $this->assertSame('http://www.example.com/', $url->getURL()); $url->setUserinfo(''); $this->assertSame('http://www.example.com/', $url->getURL()); $this->assertSame('', $url->getUserinfo()); $url->setUserinfo(false); $this->assertSame('http://www.example.com/', $url->getURL()); $this->assertFalse($url->getUserinfo()); } /** * Tests an URL with no userinfo and normalization * * Also: Regression test for Bug #20385 * * @covers Net_URL2::getUserinfo * @covers Net_URL2::normalize * @covers Net_URL2::getNormalizedURL * @return void * @link https://pear.php.net/bugs/bug.php?id=20385 */ public function testNoUserinfoAndNormalize() { $testUrl = 'http://www.example.com/'; $url = new Net_URL2($testUrl); $this->assertFalse($url->getUserinfo()); $url->normalize(); $this->assertFalse($url->getUserinfo()); $this->assertEquals($testUrl, $url->getURL()); $this->assertEquals($testUrl, $url->getNormalizedURL()); } /** * Tests setQueryVariable(). * * @return void */ public function testSetQueryVariable() { $url = new Net_URL2('http://www.example.com/'); $url->setQueryVariable('pear', 'fun'); $this->assertEquals($url->getURL(), 'http://www.example.com/?pear=fun'); } /** * Tests setQueryVariables(). * * @return void */ public function testSetQueryVariables() { $url = new Net_URL2('http://www.example.com/'); $url->setQueryVariables(array('pear' => 'fun')); $this->assertEquals('http://www.example.com/?pear=fun', $url->getURL()); $url->setQueryVariables(array('pear' => 'fun for sure')); $this->assertEquals( 'http://www.example.com/?pear=fun%20for%20sure', $url->getURL() ); } /** * Tests unsetQueryVariable() * * @return void */ public function testUnsetQueryVariable() { $url = new Net_URL2( 'http://www.example.com/?name=david&pear=fun&fish=slippery' ); $removes = array( 'pear' => 'http://www.example.com/?name=david&fish=slippery', 'name' => 'http://www.example.com/?fish=slippery', 'fish' => 'http://www.example.com/', ); foreach ($removes as $name => $expected) { $url->unsetQueryVariable($name); $this->assertEquals($expected, $url); } } /** * Tests setQuery(). * * @return void */ public function testSetQuery() { $url = new Net_URL2('http://www.example.com/'); $url->setQuery('flapdoodle&dilly%20all%20day'); $this->assertEquals( $url->getURL(), 'http://www.example.com/?flapdoodle&dilly%20all%20day' ); } /** * Tests getQuery(). * * @return void */ public function testGetQuery() { $url = new Net_URL2('http://www.example.com/?foo'); $this->assertEquals($url->getQuery(), 'foo'); $url = new Net_URL2('http://www.example.com/?pear=fun&fruit=fruity'); $this->assertEquals($url->getQuery(), 'pear=fun&fruit=fruity'); } /** * Tests setScheme(). * * @return void */ public function testSetScheme() { $url = new Net_URL2('http://www.example.com/'); $url->setScheme('ftp'); $this->assertEquals($url->getURL(), 'ftp://www.example.com/'); $url->setScheme('gopher'); $this->assertEquals($url->getURL(), 'gopher://www.example.com/'); } /** * Tests setting the fragment. * * @return void */ public function testSetFragment() { $url = new Net_URL2('http://www.example.com/'); $url->setFragment('pear'); $this->assertEquals('http://www.example.com/#pear', $url->getURL()); } /** * A dataProvider for paths that are solved to a base URI. * * @see testResolveUrls * @return array */ public function provideResolveUrls() { return array( array( // Examples from RFC 3986, section 5.4. // relative base-URI, (URL => absolute URL), [(options)] 'http://a/b/c/d;p?q', array( 'g:h' => 'g:h', 'g' => 'http://a/b/c/g', './g' => 'http://a/b/c/g', 'g/' => 'http://a/b/c/g/', '/g' => 'http://a/g', '//g' => 'http://g', '?y' => 'http://a/b/c/d;p?y', 'g?y' => 'http://a/b/c/g?y', '#s' => 'http://a/b/c/d;p?q#s', 'g#s' => 'http://a/b/c/g#s', 'g?y#s' => 'http://a/b/c/g?y#s', ';x' => 'http://a/b/c/;x', 'g;x' => 'http://a/b/c/g;x', 'g;x?y#s' => 'http://a/b/c/g;x?y#s', '' => 'http://a/b/c/d;p?q', '.' => 'http://a/b/c/', './' => 'http://a/b/c/', '..' => 'http://a/b/', '../' => 'http://a/b/', '../g' => 'http://a/b/g', '../..' => 'http://a/', '../../' => 'http://a/', '../../g' => 'http://a/g', '../../../g' => 'http://a/g', '../../../../g' => 'http://a/g', '/./g' => 'http://a/g', '/../g' => 'http://a/g', 'g.' => 'http://a/b/c/g.', '.g' => 'http://a/b/c/.g', 'g..' => 'http://a/b/c/g..', '..g' => 'http://a/b/c/..g', './../g' => 'http://a/b/g', './g/.' => 'http://a/b/c/g/', 'g/./h' => 'http://a/b/c/g/h', 'g/../h' => 'http://a/b/c/h', 'g;x=1/./y' => 'http://a/b/c/g;x=1/y', 'g;x=1/../y' => 'http://a/b/c/y', 'g?y/./x' => 'http://a/b/c/g?y/./x', 'g?y/../x' => 'http://a/b/c/g?y/../x', 'g#s/./x' => 'http://a/b/c/g#s/./x', 'g#s/../x' => 'http://a/b/c/g#s/../x', 'http:g' => 'http:g', ), ), array( 'http://a/b/c/d;p?q', array('http:g' => 'http://a/b/c/g'), array('::OPTION_STRICT' => false) ), ); } /** * Test the resolve() function to resolve URLs to each other. * * @param string $baseURL base-URI * @param array $relativeAbsolutePairs url-pairs, relative => resolved * @param array $options Net_URL2 options * * @dataProvider provideResolveUrls * @covers Net_URL2::resolve * @return void */ public function testResolveUrls($baseURL, array $relativeAbsolutePairs, array $options = array() ) { $options = $this->_translateOptionData($options); $base = new Net_URL2($baseURL, $options); $count = count($relativeAbsolutePairs); $this->assertGreaterThan(0, $count, 'relative-absolute-pairs data is empty'); foreach ($relativeAbsolutePairs as $relativeURL => $absoluteURL) { $this->assertSame($absoluteURL, (string) $base->resolve($relativeURL)); } } /** * Helper method to turn options with strings as the constant names * (to allow to externalize the fixtures) into a concrete options * array that uses the values from the Net_URL2 class constants. * * @param array $options options * * @return array */ private function _translateOptionData(array $options) { // translate string option-names to class constant starting with a colon. foreach ($options as $name => $value) { if ($name[0] === ':') { unset($options[$name]); $options[constant("Net_URL2$name")] = $value; } } return $options; } /** * Test the resolve() function throwing an exception with invalid data. * * @covers Net_URL2::resolve * @return void */ public function testResolveException() { // resolving a relative to a relative URL throws an exception $base = new Net_URL2('news.html?category=arts'); $this->addToAssertionCount(1); try { $base->resolve('../arts.html#section-2.4'); } catch (Exception $e) { $expected = 'Base-URL must be absolute if reference is not fragment-onl'; $this->assertStringStartsWith($expected, $e->getMessage()); return; } $this->fail('Expected exception not thrown.'); } /** * Assert that there is a last error message and it contains needle. * * @param string $needle needle * * @return void */ private function _assertLastErrorContains($needle) { $error = error_get_last(); $this->assertArrayHasKey('message', $error, 'there was an error previously'); $pos = strpos($error['message'], $needle); $this->assertTrue( false !== $pos, sprintf( 'Last error message "%s" contains "%s"', $error['message'], $needle ) ); } /** * Test UrlEncoding * * @return void * @link https://pear.php.net/bugs/bug.php?id=18267 */ public function testUrlEncoding() { $options = array(Net_URL2::OPTION_DROP_SEQUENCE => false); $url = new Net_URL2('http://localhost/bug.php', $options); $url->setQueryVariables( array( 'indexed' => array( 'first value', 'second value', array('foo', 'bar'), ) ) ); $this->assertEquals( 'http://localhost/bug.php?indexed[0]=first%20value&indexed[1]' . '=second%20value&indexed[2][0]=foo&indexed[2][1]=bar', strval($url) ); } /** * A test to verify that keys in QUERY_STRING are encoded by default. * * @return void * @see Net_URL2::OPTION_ENCODE_KEYS * @see Net_URL2::buildQuery() */ public function testEncodeKeys() { $url = new Net_URL2('http://example.org'); $url->setQueryVariables(array('helgi rulez' => 'till too')); $this->assertEquals( 'http://example.org?helgi%20rulez=till%20too', strval($url) ); } /** * A test to verify that keys in QUERY_STRING are not encoded when we supply * 'false' via {@link Net_URL2::__construct()}. * * @return void * @see Net_URL2::OPTION_ENCODE_KEYS * @see Net_URL2::buildQuery() */ public function testDontEncodeKeys() { $url = new Net_URL2( 'http://example.org', array(Net_URL2::OPTION_ENCODE_KEYS => false) ); $url->setQueryVariables(array('till rulez' => 'helgi too')); $this->assertEquals( 'http://example.org?till rulez=helgi%20too', strval($url) ); } /** * Brackets for array query variables * * Also text to not encode zero based integer sequence into brackets * * @return void * * @link https://pear.php.net/bugs/bug.php?id=20427 */ public function testUseBrackets() { $url = new Net_URL2('http://example.org/'); $url->setQueryVariables(array('foo' => array('bar', 'baz'))); $expected = 'http://example.org/?foo[]=bar&foo[]=baz'; $this->assertEquals($expected, $url->getURL()); $options = array(Net_URL2::OPTION_DROP_SEQUENCE => false); $url = new Net_URL2('http://example.org/', $options); $url->setQueryVariables(array('foo' => array('bar', 'foobar'))); $expected = 'http://example.org/?foo[0]=bar&foo[1]=foobar'; $this->assertEquals($expected, $url->getURL()); } /** * Do not use brackets for query variables passed as array * * @return void */ public function testDontUseBrackets() { $url = new Net_URL2( 'http://example.org/', array(Net_URL2::OPTION_USE_BRACKETS => false) ); $url->setQueryVariables(array('foo' => array('bar', 'foobar'))); $this->assertEquals( 'http://example.org/?foo=bar&foo=foobar', strval($url) ); } /** * A dataProvider for example URIs from RFC 3986 Section 1.1.2 * * @return array * @link http://tools.ietf.org/html/rfc3986#section-1.1.2 * @see testExampleUri */ public function provideExampleUri() { return array( array('ftp://ftp.is.co.za/rfc/rfc1808.txt'), array('http://www.ietf.org/rfc/rfc2396.txt'), array('ldap://[2001:db8::7]/c=GB?objectClass?one'), array('mailto:John.Doe@example.com'), array('news:comp.infosystems.www.servers.unix'), array('tel:+1-816-555-1212'), array('telnet://192.0.2.16:80/'), array('urn:oasis:names:specification:docbook:dtd:xml:4.1.2'), ); } /** * test that Net_URL2 works with the example URIs from RFC 3986 Section 1.1.2 * * @param string $uri example URI * * @return void * @dataProvider provideExampleUri * @link http://tools.ietf.org/html/rfc3986#section-1.1.2 * @see testComponentRecompositionAndNormalization */ public function testExampleUri($uri) { $url = new Net_URL2($uri); $this->assertSame($uri, $url->__toString()); $url->normalize(); $this->assertSame($uri, $url->__toString()); } /** * A dataProvider for pairs of paths with dot segments and * their form when removed. * * @see testRemoveDotSegments * @return array */ public function providePath() { // The numbers behind are in reference to sections // in RFC 3986 5.2.4. Remove Dot Segments return array( array('../', ''), // 2. A. array('./', ''), // 2. A. array('/./', '/'), // 2. B. array('/.', '/'), // 2. B. array('/../', '/'), // 2. C. array('/..', '/'), // 2. C. array('..', ''), // 2. D. array('.', ''), // 2. D. array('a', 'a'), // 2. E. array('/a', '/a'), // 2. E. array('/a/b/c/./../../g', '/a/g'), // 3. array('mid/content=5/../6', 'mid/6'), // 3. array('../foo/bar.php', 'foo/bar.php'), array('/foo/../bar/boo.php', '/bar/boo.php'), array('/boo/..//foo//bar.php', '//foo//bar.php'), array('/./foo/././bar.php', '/foo/bar.php'), array('./.', ''), ); } /** * Test removal of dot segments * * @param string $path Path * @param string $assertion Assertion * * @dataProvider providePath * @covers Net_URL2::removeDotSegments * @return void */ public function testRemoveDotSegments($path, $assertion) { $this->assertEquals($assertion, Net_URL2::removeDotSegments($path)); } /** * Test removeDotSegments() loop limit warning * * @covers Net_URL2::removeDotSegments * @return void */ public function testRemoveDotSegmentsLoopLimit() { $loopLimit = 256; $segments = str_repeat('a/', $loopLimit); @Net_URL2::removeDotSegments($segments . 'b/'); $this->_assertLastErrorContains(sprintf(' loop limit %d ', $loopLimit + 1)); $this->_assertLastErrorContains(" (left: '/b/')"); } /** * A dataProvider for query strings and their array representation * * @see testGetQueryVariables * @return array */ public function provideQueryStrings() { // If the second (expected) value is set or not null, parse_str() differs. // Notes on PHP differences with each entry/block return array( // Net_URL2::getQueryVariables() non-bracket mode array('test=1&t%65st=%41&extra=', array('test' => array('1', 'A'), 'extra' => ''), array('::OPTION_USE_BRACKETS' => false)), array(''), array('='), array('key'), array('key='), array('=value'), array('k=v'), // no space as var-name in PHP (array()): array(' ', array(' ' => '' )), array(' =v', array(' ' => 'v')), array('key=value'), // PHP replaces ".", " " and "[" in name replaced by "_": array('key.=value' , array('key.' => 'value')), array('key =value' , array('key ' => 'value')), array('key[=value' , array('key[' => 'value')), array("key\0=value", array("key\0" => 'value')), array('key]=value'), array('key[]=value'), array('[]=value'), array(']=value'), array(']]=value'), // PHP drops variables that are an open bracket only array('[=value', array('[' => 'value')), // PHP drops spaces in brackets: array('key[ ]=value', array('key' => array(' ' => 'value'))), // PHP replaces space " " in name by "_" array('key []=1' , array('key ' => array('1' ))) , // PHP does not support "\0" in var-names: array("key[\0]=value" , array('key' => array("\0" => 'value' ))), array("key[a\0]=value" , array('key' => array("a\0" => 'value' ))), array("key[a\0b]=value" , array('key' => array("a\0b" => 'value'))), array('var[]=1&var[0][]=2'), array('key[] []=1'), array('key[] [] []=1'), array('key[] [] []'), array('key[] [] []=[] []'), array('[] [] []=[] []'), ); } /** * Test parsing of query variables * * @param string $query string * @param mixed $expected null to test against parse_str() behavior * @param array $options Net_URL2 options * * @dataProvider provideQueryStrings * @covers Net_URL2::getQueryVariables * @covers Net_URL2::_queryArrayByKey * @covers Net_URL2::_queryArrayByBrackets * @covers Net_URL2::_queryKeyBracketOffset * @return void */ public function testGetQueryVariables($query, $expected = null, array $options = array() ) { $options = $this->_translateOptionData($options); $url = new Net_URL2('', $options); if ($expected === null) { // parse_str() is in PHP before copy on write, therefore // it uses pass-by-reference for $expected to return // the array parse_str($query, $expected); } // Xdebug: If breakpoints are ignored, see Xdebug Issue 0000924 $url->setQuery($query); $actual = $url->getQueryVariables(); // Do two assertions, because the first one shows a more nice diff in case // it fails and the second one is actually strict which is what has to be // tested. $this->assertEquals($expected, $actual); $this->assertSame($expected, $actual); } /** * data provider of host and port * * @return array * @see testHostAndPort */ public function provideHostAndPort() { return array( array('[::1]', '[::1]', false), array('[::1]:', '[::1]', false), array('[::1]:128', '[::1]', '128'), array('127.0.0.1', '127.0.0.1', false), array('127.0.0.1:', '127.0.0.1', false), array('127.0.0.1:128', '127.0.0.1', '128'), array('localhost', 'localhost', false), array('localhost:', 'localhost', false), array('localhost:128', 'localhost', '128'), ); } /** * test that an authority containing host and port maps to expected host and port * * This is also a regression test to test that using ip-literals works along- * side ipv4 and reg-name hosts incl. port numbers * * It was reported as Bug #20423 on 2014-10-06 18:25 UTC that * http://[::1]// URI drops the host * * @param string $authority string * @param string $expectedHost string * @param string|bool $expectedPort string or FALSE * * @return void * @dataProvider provideHostAndPort * @covers Net_URL2::setAuthority() * @link https://pear.php.net/bugs/bug.php?id=20423 * @link http://tools.ietf.org/html/rfc3986#section-3.2.2 * @link http://tools.ietf.org/html/rfc3986#section-3.2 * @link http://tools.ietf.org/html/rfc3986#section-3.2.3 */ public function testHostAndPort($authority, $expectedHost, $expectedPort) { $uri = "http://{$authority}"; $url = new Net_URL2($uri); $this->assertSame($expectedHost, $url->getHost()); $this->assertSame($expectedPort, $url->getPort()); } /** * This is a regression test to test that Net_URL2::getQueryVariables() does * not have a problem with nested array values in form of stacked brackets and * was reported as Bug #17036 on 2010-01-26 15:48 UTC that there would be * a problem with parsed query string. * * @link https://pear.php.net/bugs/bug.php?id=17036 * @covers Net_URL2::getQueryVariables * @return void */ public function test17036() { $queryString = 'start=10&test[0][first][1.1][20]=coucou'; $url = new Net_URL2('?' . $queryString); $vars = $url->getQueryVariables(); $expected = array(); $expected['start'] = '10'; $expected['test'][0]['first']['1.1'][20] = 'coucou'; $this->assertEquals($expected, $vars); // give nice diff in case of failuer $this->assertSame($expected, $vars); // strictly assert the premise } /** * This is a regression test to test that resolve() does * merge the path if the base path is empty as the opposite * was reported as Bug #19176 on 2011-12-31 02:07 UTC * * @return void */ public function test19176() { $foo = new Net_URL2('http://www.example.com'); $test = $foo->resolve('test.html')->getURL(); $this->assertEquals('http://www.example.com/test.html', $test); } /** * This is a regression test that removeDotSegments('0') is * working as it was reported as not-working in Bug #19315 * on 2012-03-04 04:18 UTC. * * @return void */ public function test19315() { $actual = Net_URL2::removeDotSegments('0'); $this->assertSame('0', $actual); $nonStringObject = (object)array(); try { Net_URL2::removeDotSegments($nonStringObject); } catch (PHPUnit_Framework_Error $error) { $this->addToAssertionCount(1); } if (!isset($error)) { $this->fail('Failed to verify that error was given.'); } unset($error); } /** * This is a regression test to test that recovering from * a wrongly encoded URL is possible. * * It was requested as Request #19684 on 2011-12-31 02:07 UTC * that redirects containing spaces should work. * * @return void */ public function test19684() { // Location: URL obtained Thu, 25 Apr 2013 20:51:31 GMT $urlWithSpace = 'http://www.sigmaaldrich.com/catalog/search?interface=CAS N' . 'o.&term=108-88-3&lang=en®ion=US&mode=match+partialmax&N=0+2200030' . '48+219853269+219853286'; $urlCorrect = 'http://www.sigmaaldrich.com/catalog/search?interface=CAS%20N' . 'o.&term=108-88-3&lang=en®ion=US&mode=match+partialmax&N=0+2200030' . '48+219853269+219853286'; $url = new Net_URL2($urlWithSpace); $this->assertTrue($url->isAbsolute()); $urlPart = parse_url($urlCorrect, PHP_URL_PATH); $this->assertSame($urlPart, $url->getPath()); $urlPart = parse_url($urlCorrect, PHP_URL_QUERY); $this->assertSame($urlPart, $url->getQuery()); $this->assertSame($urlCorrect, (string)$url); $input = 'http://example.com/get + + to my nose/'; $expected = 'http://example.com/get%20+%20+%20to%20my%20nose/'; $actual = new Net_URL2($input); $this->assertEquals($expected, $actual); $actual->normalize(); } /** * data provider of list of equivalent URLs. * * @see testNormalize * @see testConstructSelf * @return array */ public function provideEquivalentUrlLists() { return array( // String equivalence: array('http://example.com/', 'http://example.com/'), // Originally first dataset: array('http://www.example.com/%9a', 'http://www.example.com/%9A'), // Example from RFC 3986 6.2.2.: array('example://a/b/c/%7Bfoo%7D', 'eXAMPLE://a/./b/../b/%63/%7bfoo%7d'), // Example from RFC 3986 6.2.2.1.: array('HTTP://www.EXAMPLE.com/', 'http://www.example.com/'), // Example from RFC 3986 6.2.3.: array( 'http://example.com', 'http://example.com/', 'http://example.com:/', 'http://example.com:80/' ), // Bug #20161: URLs with "0" as host fail to normalize with empty path array('http://0/', 'http://0'), // Bug #20473: Normalize query and fragment broken array('foo:///?%66%6f%6f#%62%61%72', 'foo:///?foo#bar'), ); } /** * This is a coverage test to invoke the normalize() * method. * * @return void * * @dataProvider provideEquivalentUrlLists */ public function testNormalize() { $urls = func_get_args(); $this->assertGreaterThanOrEqual(2, count($urls)); $last = null; foreach ($urls as $index => $url) { $url = new Net_Url2($url); $url->normalize(); if ($index) { $this->assertSame((string)$last, (string)$url); } $last = $url; } } /** * This is a coverage test to invoke __get and __set * * @covers Net_URL2::__get * @covers Net_URL2::__set * @return void */ public function testMagicSetGet() { $url = new Net_URL2(''); $property = 'authority'; $url->$property = $value = 'value'; $this->assertEquals($value, $url->$property); $property = 'unsetProperty'; $url->$property = $value; $this->assertEquals(false, $url->$property); } /** * data provider of uri and normal URIs * * @return array * @see testComponentRecompositionAndNormalization */ public function provideComposedAndNormalized() { return array( array(''), array('http:g'), array('user@host'), array('mailto:user@host'), ); } /** * Tests Net_URL2 RFC 3986 5.3. Component Recomposition in the light * of normalization * * This is also a regression test to test that a missing authority works well * with normalization * * It was reported as Bug #20418 on 2014-10-02 22:10 UTC that there is an * Incorrect normalization of URI with missing authority * * @param string $uri URI * * @return void * @covers Net_URL2::getUrl() * @covers Net_URL2::normalize() * @dataProvider provideComposedAndNormalized * @link https://pear.php.net/bugs/bug.php?id=20418 * @see testExampleUri */ public function testComponentRecompositionAndNormalization($uri) { $url = new Net_URL2($uri); $this->assertSame($uri, $url->getURL()); $url->normalize(); $this->assertSame($uri, $url->getURL()); } /** * Tests Net_URL2 ctors URL parameter works with objects implementing * __toString(). * * @dataProvider provideEquivalentUrlLists * @coversNothing * @return void */ public function testConstructSelf() { $urls = func_get_args(); foreach ($urls as $url) { $urlA = new Net_URL2($url); $urlB = new Net_URL2($urlA); $this->assertSame((string)$urlA, (string)$urlB); } } /** * This is a feature test to see that the userinfo's data is getting * encoded as outlined in #19684. * * @covers Net_URL2::setAuthority * @covers Net_URL2::setUserinfo * @return void */ public function testEncodeDataUserinfoAuthority() { $url = new Net_URL2('http://john doe:secret@example.com/'); $this->assertSame('http://john%20doe:secret@example.com/', (string)$url); $url->setUserinfo('john doe'); $this->assertSame('http://john%20doe@example.com/', (string)$url); $url->setUserinfo('john doe', 'pa wd'); $this->assertSame('http://john%20doe:pa%20wd@example.com/', (string)$url); } /** * This is a regression test to test that using the * host-name "0" does work with getAuthority() * * It was reported as Bug #20156 on 2013-12-27 22:56 UTC * that setAuthority() with "0" as host would not work * * @covers Net_URL2::setAuthority * @covers Net_URL2::getAuthority * @covers Net_URL2::setHost * @return void */ public function test20156() { $url = new Net_URL2('http://user:pass@example.com:127/'); $host = '0'; $url->setHost($host); $this->assertSame('user:pass@0:127', $url->getAuthority()); $url->setHost(false); $this->assertSame(false, $url->getAuthority()); $url->setAuthority($host); $this->assertSame($host, $url->getAuthority()); } /** * This is a regression test to test that setting "0" as path * does not break normalize(). * * It was reported as Bug #20157 on 2013-12-27 23:42 UTC that * normalize() with "0" as path would not work. * * @covers Net_URL2::normalize * @return void */ public function test20157() { $subject = 'http://example.com'; $url = new Net_URL2($subject); $url->setPath('0'); $url->normalize(); $this->assertSame("$subject/0", (string)$url); } /** * This is a regression test to ensure that fragment-only references can be * resolved to a non-absolute Base-URI. * * It was reported as Bug #20158 2013-12-28 14:49 UTC that fragment-only * references would not be resolved to non-absolute base URI * * @covers Net_URL2::resolve * @covers Net_URL2::_isFragmentOnly * @return void */ public function test20158() { $base = new Net_URL2('myfile.html'); $resolved = $base->resolve('#world'); $this->assertSame('myfile.html#world', (string)$resolved); } /** * This is a regression test to ensure that authority and path are properly * combined when the path does not start with a slash which is the separator * character between authority and path. * * It was reported as Bug #20159 2013-12-28 17:18 UTC that authority * would not be terminated by slash * * @covers Net_URL2::getUrl * @return void */ public function test20159() { $url = new Net_URL2('index.html'); $url->setHost('example.com'); $this->assertSame('//example.com/index.html', (string)$url); } /** * This is a regression test to test that using the file:// URI scheme with * an empty (default) hostname has the empty authority preserved when the * full URL is build. * * It was reported as Bug #20304 on 2014-06-21 00:06 UTC * that file:// URI are crippled. * * Tests with a default authority for the "file" URI scheme * * @covers Net_URL2::getURL * @return void * @link https://pear.php.net/bugs/bug.php?id=20304 */ public function test20304() { $file = 'file:///path/to/file'; $url = new Net_URL2($file); $this->assertSame($file, (string) $url); $file = 'file://localhost/path/to/file'; $url = new Net_URL2($file); $this->assertSame($file, (string) $url); $file = 'file://user@/path/to/file'; $url = new Net_URL2($file); $this->assertSame($file, (string) $url); $file = 'FILE:///path/to/file'; $url = new Net_URL2($file); $this->assertSame($file, (string) $url); } } Net_URL2-2.2.1/tests/AllTests.php0000664000175000017500000000235212705260505014642 0ustar motmot * @license https://spdx.org/licenses/BSD-3-Clause BSD-3-Clause * @link https://tools.ietf.org/html/rfc3986 */ require_once 'PHPUnit/Autoload.php'; chdir(dirname(__FILE__) . '/../'); require_once 'Net/URL2Test.php'; require_once 'Net/URL2.php'; /** * Test class for Net_URL2. * * @category Networking * @package Net_URL2 * @author Some Pear Developers * @license https://spdx.org/licenses/BSD-3-Clause BSD-3-Clause * @version Release: @package_version@ * @link https://pear.php.net/package/Net_URL2 */ class Net_URL2_AllTests { /** * main() * * @return void */ public static function main() { PHPUnit_TextUI_TestRunner::run(self::suite()); } /** * suite() * * @return PHPUnit_Framework_TestSuite */ public static function suite() { $suite = new PHPUnit_Framework_TestSuite('Net_URL2 tests'); /** Add testsuites, if there is. */ $suite->addTestSuite('Net_URL2Test'); return $suite; } } Net_URL2_AllTests::main(); Net_URL2-2.2.1/Net/URL2.php0000775000175000017500000010454112705260505013225 0ustar motmot * @copyright 2007-2009 Peytz & Co. A/S * @license https://spdx.org/licenses/BSD-3-Clause BSD-3-Clause * @version CVS: $Id$ * @link https://tools.ietf.org/html/rfc3986 */ /** * Represents a URL as per RFC 3986. * * @category Networking * @package Net_URL2 * @author Christian Schmidt * @copyright 2007-2009 Peytz & Co. A/S * @license https://spdx.org/licenses/BSD-3-Clause BSD-3-Clause * @version Release: 2.2.1 * @link https://pear.php.net/package/Net_URL2 */ class Net_URL2 { /** * Do strict parsing in resolve() (see RFC 3986, section 5.2.2). Default * is true. */ const OPTION_STRICT = 'strict'; /** * Represent arrays in query using PHP's [] notation. Default is true. */ const OPTION_USE_BRACKETS = 'use_brackets'; /** * Drop zero-based integer sequences in query using PHP's [] notation. Default * is true. */ const OPTION_DROP_SEQUENCE = 'drop_sequence'; /** * URL-encode query variable keys. Default is true. */ const OPTION_ENCODE_KEYS = 'encode_keys'; /** * Query variable separators when parsing the query string. Every character * is considered a separator. Default is "&". */ const OPTION_SEPARATOR_INPUT = 'input_separator'; /** * Query variable separator used when generating the query string. Default * is "&". */ const OPTION_SEPARATOR_OUTPUT = 'output_separator'; /** * Default options corresponds to how PHP handles $_GET. */ private $_options = array( self::OPTION_STRICT => true, self::OPTION_USE_BRACKETS => true, self::OPTION_DROP_SEQUENCE => true, self::OPTION_ENCODE_KEYS => true, self::OPTION_SEPARATOR_INPUT => '&', self::OPTION_SEPARATOR_OUTPUT => '&', ); /** * @var string|bool */ private $_scheme = false; /** * @var string|bool */ private $_userinfo = false; /** * @var string|bool */ private $_host = false; /** * @var string|bool */ private $_port = false; /** * @var string */ private $_path = ''; /** * @var string|bool */ private $_query = false; /** * @var string|bool */ private $_fragment = false; /** * Constructor. * * @param string $url an absolute or relative URL * @param array $options an array of OPTION_xxx constants * * @uses self::parseUrl() */ public function __construct($url, array $options = array()) { foreach ($options as $optionName => $value) { if (array_key_exists($optionName, $this->_options)) { $this->_options[$optionName] = $value; } } $this->parseUrl($url); } /** * Magic Setter. * * This method will magically set the value of a private variable ($var) * with the value passed as the args * * @param string $var The private variable to set. * @param mixed $arg An argument of any type. * * @return void */ public function __set($var, $arg) { $method = 'set' . $var; if (method_exists($this, $method)) { $this->$method($arg); } } /** * Magic Getter. * * This is the magic get method to retrieve the private variable * that was set by either __set() or it's setter... * * @param string $var The property name to retrieve. * * @return mixed $this->$var Either a boolean false if the * property is not set or the value * of the private property. */ public function __get($var) { $method = 'get' . $var; if (method_exists($this, $method)) { return $this->$method(); } return false; } /** * Returns the scheme, e.g. "http" or "urn", or false if there is no * scheme specified, i.e. if this is a relative URL. * * @return string|bool */ public function getScheme() { return $this->_scheme; } /** * Sets the scheme, e.g. "http" or "urn". Specify false if there is no * scheme specified, i.e. if this is a relative URL. * * @param string|bool $scheme e.g. "http" or "urn", or false if there is no * scheme specified, i.e. if this is a relative * URL * * @return $this * @see getScheme */ public function setScheme($scheme) { $this->_scheme = $scheme; return $this; } /** * Returns the user part of the userinfo part (the part preceding the first * ":"), or false if there is no userinfo part. * * @return string|bool */ public function getUser() { return $this->_userinfo !== false ? preg_replace('(:.*$)', '', $this->_userinfo) : false; } /** * Returns the password part of the userinfo part (the part after the first * ":"), or false if there is no userinfo part (i.e. the URL does not * contain "@" in front of the hostname) or the userinfo part does not * contain ":". * * @return string|bool */ public function getPassword() { return $this->_userinfo !== false ? substr(strstr($this->_userinfo, ':'), 1) : false; } /** * Returns the userinfo part, or false if there is none, i.e. if the * authority part does not contain "@". * * @return string|bool */ public function getUserinfo() { return $this->_userinfo; } /** * Sets the userinfo part. If two arguments are passed, they are combined * in the userinfo part as username ":" password. * * @param string|bool $userinfo userinfo or username * @param string|bool $password optional password, or false * * @return $this */ public function setUserinfo($userinfo, $password = false) { if ($password !== false) { $userinfo .= ':' . $password; } if ($userinfo !== false) { $userinfo = $this->_encodeData($userinfo); } $this->_userinfo = $userinfo; return $this; } /** * Returns the host part, or false if there is no authority part, e.g. * relative URLs. * * @return string|bool a hostname, an IP address, or false */ public function getHost() { return $this->_host; } /** * Sets the host part. Specify false if there is no authority part, e.g. * relative URLs. * * @param string|bool $host a hostname, an IP address, or false * * @return $this */ public function setHost($host) { $this->_host = $host; return $this; } /** * Returns the port number, or false if there is no port number specified, * i.e. if the default port is to be used. * * @return string|bool */ public function getPort() { return $this->_port; } /** * Sets the port number. Specify false if there is no port number specified, * i.e. if the default port is to be used. * * @param string|bool $port a port number, or false * * @return $this */ public function setPort($port) { $this->_port = $port; return $this; } /** * Returns the authority part, i.e. [ userinfo "@" ] host [ ":" port ], or * false if there is no authority. * * @return string|bool */ public function getAuthority() { if (false === $this->_host) { return false; } $authority = ''; if (strlen($this->_userinfo)) { $authority .= $this->_userinfo . '@'; } $authority .= $this->_host; if ($this->_port !== false) { $authority .= ':' . $this->_port; } return $authority; } /** * Sets the authority part, i.e. [ userinfo "@" ] host [ ":" port ]. Specify * false if there is no authority. * * @param string|bool $authority a hostname or an IP address, possibly * with userinfo prefixed and port number * appended, e.g. "foo:bar@example.org:81". * * @return $this */ public function setAuthority($authority) { $this->_userinfo = false; $this->_host = false; $this->_port = false; if ('' === $authority) { $this->_host = $authority; return $this; } if (!preg_match('(^(([^@]*)@)?(.+?)(:(\d*))?$)', $authority, $matches)) { return $this; } if ($matches[1]) { $this->_userinfo = $this->_encodeData($matches[2]); } $this->_host = $matches[3]; if (isset($matches[5]) && strlen($matches[5])) { $this->_port = $matches[5]; } return $this; } /** * Returns the path part (possibly an empty string). * * @return string */ public function getPath() { return $this->_path; } /** * Sets the path part (possibly an empty string). * * @param string $path a path * * @return $this */ public function setPath($path) { $this->_path = $path; return $this; } /** * Returns the query string (excluding the leading "?"), or false if "?" * is not present in the URL. * * @return string|bool * @see getQueryVariables */ public function getQuery() { return $this->_query; } /** * Sets the query string (excluding the leading "?"). Specify false if "?" * is not present in the URL. * * @param string|bool $query a query string, e.g. "foo=1&bar=2" * * @return $this * @see setQueryVariables */ public function setQuery($query) { $this->_query = $query; return $this; } /** * Returns the fragment name, or false if "#" is not present in the URL. * * @return string|bool */ public function getFragment() { return $this->_fragment; } /** * Sets the fragment name. Specify false if "#" is not present in the URL. * * @param string|bool $fragment a fragment excluding the leading "#", or * false * * @return $this */ public function setFragment($fragment) { $this->_fragment = $fragment; return $this; } /** * Returns the query string like an array as the variables would appear in * $_GET in a PHP script. If the URL does not contain a "?", an empty array * is returned. * * @return array */ public function getQueryVariables() { $separator = $this->getOption(self::OPTION_SEPARATOR_INPUT); $encodeKeys = $this->getOption(self::OPTION_ENCODE_KEYS); $useBrackets = $this->getOption(self::OPTION_USE_BRACKETS); $return = array(); for ($part = strtok($this->_query, $separator); strlen($part); $part = strtok($separator) ) { list($key, $value) = explode('=', $part, 2) + array(1 => ''); if ($encodeKeys) { $key = rawurldecode($key); } $value = rawurldecode($value); if ($useBrackets) { $return = $this->_queryArrayByKey($key, $value, $return); } else { if (isset($return[$key])) { $return[$key] = (array) $return[$key]; $return[$key][] = $value; } else { $return[$key] = $value; } } } return $return; } /** * Parse a single query key=value pair into an existing php array * * @param string $key query-key * @param string $value query-value * @param array $array of existing query variables (if any) * * @return mixed */ private function _queryArrayByKey($key, $value, array $array = array()) { if (!strlen($key)) { return $array; } $offset = $this->_queryKeyBracketOffset($key); if ($offset === false) { $name = $key; } else { $name = substr($key, 0, $offset); } if (!strlen($name)) { return $array; } if (!$offset) { // named value $array[$name] = $value; } else { // array $brackets = substr($key, $offset); if (!isset($array[$name])) { $array[$name] = null; } $array[$name] = $this->_queryArrayByBrackets( $brackets, $value, $array[$name] ); } return $array; } /** * Parse a key-buffer to place value in array * * @param string $buffer to consume all keys from * @param string $value to be set/add * @param array $array to traverse and set/add value in * * @throws Exception * @return array */ private function _queryArrayByBrackets($buffer, $value, array $array = null) { $entry = &$array; for ($iteration = 0; strlen($buffer); $iteration++) { $open = $this->_queryKeyBracketOffset($buffer); if ($open !== 0) { // Opening bracket [ must exist at offset 0, if not, there is // no bracket to parse and the value dropped. // if this happens in the first iteration, this is flawed, see // as well the second exception below. if ($iteration) { break; } // @codeCoverageIgnoreStart throw new Exception( 'Net_URL2 Internal Error: '. __METHOD__ .'(): ' . 'Opening bracket [ must exist at offset 0' ); // @codeCoverageIgnoreEnd } $close = strpos($buffer, ']', 1); if (!$close) { // this error condition should never be reached as this is a // private method and bracket pairs are checked beforehand. // See as well the first exception for the opening bracket. // @codeCoverageIgnoreStart throw new Exception( 'Net_URL2 Internal Error: '. __METHOD__ .'(): ' . 'Closing bracket ] must exist, not found' ); // @codeCoverageIgnoreEnd } $index = substr($buffer, 1, $close - 1); if (strlen($index)) { $entry = &$entry[$index]; } else { if (!is_array($entry)) { $entry = array(); } $entry[] = &$new; $entry = &$new; unset($new); } $buffer = substr($buffer, $close + 1); } $entry = $value; return $array; } /** * Query-key has brackets ("...[]") * * @param string $key query-key * * @return bool|int offset of opening bracket, false if no brackets */ private function _queryKeyBracketOffset($key) { if (false !== $open = strpos($key, '[') and false === strpos($key, ']', $open + 1) ) { $open = false; } return $open; } /** * Sets the query string to the specified variable in the query string. * * @param array $array (name => value) array * * @return $this */ public function setQueryVariables(array $array) { if (!$array) { $this->_query = false; } else { $this->_query = $this->buildQuery( $array, $this->getOption(self::OPTION_SEPARATOR_OUTPUT) ); } return $this; } /** * Sets the specified variable in the query string. * * @param string $name variable name * @param mixed $value variable value * * @return $this */ public function setQueryVariable($name, $value) { $array = $this->getQueryVariables(); $array[$name] = $value; $this->setQueryVariables($array); return $this; } /** * Removes the specified variable from the query string. * * @param string $name a query string variable, e.g. "foo" in "?foo=1" * * @return void */ public function unsetQueryVariable($name) { $array = $this->getQueryVariables(); unset($array[$name]); $this->setQueryVariables($array); } /** * Returns a string representation of this URL. * * @return string */ public function getURL() { // See RFC 3986, section 5.3 $url = ''; if ($this->_scheme !== false) { $url .= $this->_scheme . ':'; } $authority = $this->getAuthority(); if ($authority === false && strtolower($this->_scheme) === 'file') { $authority = ''; } $url .= $this->_buildAuthorityAndPath($authority, $this->_path); if ($this->_query !== false) { $url .= '?' . $this->_query; } if ($this->_fragment !== false) { $url .= '#' . $this->_fragment; } return $url; } /** * Put authority and path together, wrapping authority * into proper separators/terminators. * * @param string|bool $authority authority * @param string $path path * * @return string */ private function _buildAuthorityAndPath($authority, $path) { if ($authority === false) { return $path; } $terminator = ($path !== '' && $path[0] !== '/') ? '/' : ''; return '//' . $authority . $terminator . $path; } /** * Returns a string representation of this URL. * * @return string * @link https://php.net/language.oop5.magic#object.tostring */ public function __toString() { return $this->getURL(); } /** * Returns a normalized string representation of this URL. This is useful * for comparison of URLs. * * @return string */ public function getNormalizedURL() { $url = clone $this; $url->normalize(); return $url->getURL(); } /** * Normalizes the URL * * See RFC 3986, Section 6. Normalization and Comparison * * @link https://tools.ietf.org/html/rfc3986#section-6 * * @return void */ public function normalize() { // See RFC 3986, section 6 // Scheme is case-insensitive if ($this->_scheme) { $this->_scheme = strtolower($this->_scheme); } // Hostname is case-insensitive if ($this->_host) { $this->_host = strtolower($this->_host); } // Remove default port number for known schemes (RFC 3986, section 6.2.3) if ('' === $this->_port || $this->_port && $this->_scheme && $this->_port == getservbyname($this->_scheme, 'tcp') ) { $this->_port = false; } // Normalize case of %XX percentage-encodings (RFC 3986, section 6.2.2.1) // Normalize percentage-encoded unreserved characters (section 6.2.2.2) $fields = array(&$this->_userinfo, &$this->_host, &$this->_path, &$this->_query, &$this->_fragment); foreach ($fields as &$field) { if ($field !== false) { $field = $this->_normalize("$field"); } } unset($field); // Path segment normalization (RFC 3986, section 6.2.2.3) $this->_path = self::removeDotSegments($this->_path); // Scheme based normalization (RFC 3986, section 6.2.3) if (false !== $this->_host && '' === $this->_path) { $this->_path = '/'; } // path should start with '/' if there is authority (section 3.3.) if (strlen($this->getAuthority()) && strlen($this->_path) && $this->_path[0] !== '/' ) { $this->_path = '/' . $this->_path; } } /** * Normalize case of %XX percentage-encodings (RFC 3986, section 6.2.2.1) * Normalize percentage-encoded unreserved characters (section 6.2.2.2) * * @param string|array $mixed string or array of strings to normalize * * @return string|array * @see normalize * @see _normalizeCallback() */ private function _normalize($mixed) { return preg_replace_callback( '((?:%[0-9a-fA-Z]{2})+)', array($this, '_normalizeCallback'), $mixed ); } /** * Callback for _normalize() of %XX percentage-encodings * * @param array $matches as by preg_replace_callback * * @return string * @see normalize * @see _normalize * @SuppressWarnings(PHPMD.UnusedPrivateMethod) */ private function _normalizeCallback($matches) { return self::urlencode(urldecode($matches[0])); } /** * Returns whether this instance represents an absolute URL. * * @return bool */ public function isAbsolute() { return (bool) $this->_scheme; } /** * Returns an Net_URL2 instance representing an absolute URL relative to * this URL. * * @param Net_URL2|string $reference relative URL * * @throws Exception * @return $this */ public function resolve($reference) { if (!$reference instanceof Net_URL2) { $reference = new self($reference); } if (!$reference->_isFragmentOnly() && !$this->isAbsolute()) { throw new Exception( 'Base-URL must be absolute if reference is not fragment-only' ); } // A non-strict parser may ignore a scheme in the reference if it is // identical to the base URI's scheme. if (!$this->getOption(self::OPTION_STRICT) && $reference->_scheme == $this->_scheme ) { $reference->_scheme = false; } $target = new self(''); if ($reference->_scheme !== false) { $target->_scheme = $reference->_scheme; $target->setAuthority($reference->getAuthority()); $target->_path = self::removeDotSegments($reference->_path); $target->_query = $reference->_query; } else { $authority = $reference->getAuthority(); if ($authority !== false) { $target->setAuthority($authority); $target->_path = self::removeDotSegments($reference->_path); $target->_query = $reference->_query; } else { if ($reference->_path == '') { $target->_path = $this->_path; if ($reference->_query !== false) { $target->_query = $reference->_query; } else { $target->_query = $this->_query; } } else { if (substr($reference->_path, 0, 1) == '/') { $target->_path = self::removeDotSegments($reference->_path); } else { // Merge paths (RFC 3986, section 5.2.3) if ($this->_host !== false && $this->_path == '') { $target->_path = '/' . $reference->_path; } else { $i = strrpos($this->_path, '/'); if ($i !== false) { $target->_path = substr($this->_path, 0, $i + 1); } $target->_path .= $reference->_path; } $target->_path = self::removeDotSegments($target->_path); } $target->_query = $reference->_query; } $target->setAuthority($this->getAuthority()); } $target->_scheme = $this->_scheme; } $target->_fragment = $reference->_fragment; return $target; } /** * URL is fragment-only * * @SuppressWarnings(PHPMD.UnusedPrivateMethod) * @return bool */ private function _isFragmentOnly() { return ( $this->_fragment !== false && $this->_query === false && $this->_path === '' && $this->_port === false && $this->_host === false && $this->_userinfo === false && $this->_scheme === false ); } /** * Removes dots as described in RFC 3986, section 5.2.4, e.g. * "/foo/../bar/baz" => "/bar/baz" * * @param string $path a path * * @return string a path */ public static function removeDotSegments($path) { $path = (string) $path; $output = ''; // Make sure not to be trapped in an infinite loop due to a bug in this // method $loopLimit = 256; $j = 0; while ('' !== $path && $j++ < $loopLimit) { if (substr($path, 0, 2) === './') { // Step 2.A $path = substr($path, 2); } elseif (substr($path, 0, 3) === '../') { // Step 2.A $path = substr($path, 3); } elseif (substr($path, 0, 3) === '/./' || $path === '/.') { // Step 2.B $path = '/' . substr($path, 3); } elseif (substr($path, 0, 4) === '/../' || $path === '/..') { // Step 2.C $path = '/' . substr($path, 4); $i = strrpos($output, '/'); $output = $i === false ? '' : substr($output, 0, $i); } elseif ($path === '.' || $path === '..') { // Step 2.D $path = ''; } else { // Step 2.E $i = strpos($path, '/', $path[0] === '/'); if ($i === false) { $output .= $path; $path = ''; break; } $output .= substr($path, 0, $i); $path = substr($path, $i); } } if ($path !== '') { $message = sprintf( 'Unable to remove dot segments; hit loop limit %d (left: %s)', $j, var_export($path, true) ); trigger_error($message, E_USER_WARNING); } return $output; } /** * Percent-encodes all non-alphanumeric characters except these: _ . - ~ * Similar to PHP's rawurlencode(), except that it also encodes ~ in PHP * 5.2.x and earlier. * * @param string $string string to encode * * @return string */ public static function urlencode($string) { $encoded = rawurlencode($string); // This is only necessary in PHP < 5.3. $encoded = str_replace('%7E', '~', $encoded); return $encoded; } /** * Returns a Net_URL2 instance representing the canonical URL of the * currently executing PHP script. * * @throws Exception * @return string */ public static function getCanonical() { if (!isset($_SERVER['REQUEST_METHOD'])) { // ALERT - no current URL throw new Exception('Script was not called through a webserver'); } // Begin with a relative URL $url = new self($_SERVER['PHP_SELF']); $url->_scheme = isset($_SERVER['HTTPS']) ? 'https' : 'http'; $url->_host = $_SERVER['SERVER_NAME']; $port = $_SERVER['SERVER_PORT']; if ($url->_scheme == 'http' && $port != 80 || $url->_scheme == 'https' && $port != 443 ) { $url->_port = $port; } return $url; } /** * Returns the URL used to retrieve the current request. * * @return string */ public static function getRequestedURL() { return self::getRequested()->getUrl(); } /** * Returns a Net_URL2 instance representing the URL used to retrieve the * current request. * * @throws Exception * @return $this */ public static function getRequested() { if (!isset($_SERVER['REQUEST_METHOD'])) { // ALERT - no current URL throw new Exception('Script was not called through a webserver'); } // Begin with a relative URL $url = new self($_SERVER['REQUEST_URI']); $url->_scheme = isset($_SERVER['HTTPS']) ? 'https' : 'http'; // Set host and possibly port $url->setAuthority($_SERVER['HTTP_HOST']); return $url; } /** * Returns the value of the specified option. * * @param string $optionName The name of the option to retrieve * * @return mixed */ public function getOption($optionName) { return isset($this->_options[$optionName]) ? $this->_options[$optionName] : false; } /** * A simple version of http_build_query in userland. The encoded string is * percentage encoded according to RFC 3986. * * @param array $data An array, which has to be converted into * QUERY_STRING. Anything is possible. * @param string $separator Separator {@link self::OPTION_SEPARATOR_OUTPUT} * @param string $key For stacked values (arrays in an array). * * @return string */ protected function buildQuery(array $data, $separator, $key = null) { $query = array(); $drop_names = ( $this->_options[self::OPTION_DROP_SEQUENCE] === true && array_keys($data) === array_keys(array_values($data)) ); foreach ($data as $name => $value) { if ($this->getOption(self::OPTION_ENCODE_KEYS) === true) { $name = rawurlencode($name); } if ($key !== null) { if ($this->getOption(self::OPTION_USE_BRACKETS) === true) { $drop_names && $name = ''; $name = $key . '[' . $name . ']'; } else { $name = $key; } } if (is_array($value)) { $query[] = $this->buildQuery($value, $separator, $name); } else { $query[] = $name . '=' . rawurlencode($value); } } return implode($separator, $query); } /** * This method uses a regex to parse the url into the designated parts. * * @param string $url URL * * @return void * @uses self::$_scheme, self::setAuthority(), self::$_path, self::$_query, * self::$_fragment * @see __construct */ protected function parseUrl($url) { // The regular expression is copied verbatim from RFC 3986, appendix B. // The expression does not validate the URL but matches any string. preg_match( '(^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?)', $url, $matches ); // "path" is always present (possibly as an empty string); the rest // are optional. $this->_scheme = !empty($matches[1]) ? $matches[2] : false; $this->setAuthority(!empty($matches[3]) ? $matches[4] : false); $this->_path = $this->_encodeData($matches[5]); $this->_query = !empty($matches[6]) ? $this->_encodeData($matches[7]) : false ; $this->_fragment = !empty($matches[8]) ? $matches[9] : false; } /** * Encode characters that might have been forgotten to encode when passing * in an URL. Applied onto Userinfo, Path and Query. * * @param string $url URL * * @return string * @see parseUrl * @see setAuthority * @link https://pear.php.net/bugs/bug.php?id=20425 */ private function _encodeData($url) { return preg_replace_callback( '([\x-\x20\x22\x3C\x3E\x7F-\xFF]+)', array($this, '_encodeCallback'), $url ); } /** * callback for encoding character data * * @param array $matches Matches * * @return string * @see _encodeData * @SuppressWarnings(PHPMD.UnusedPrivateMethod) */ private function _encodeCallback(array $matches) { return rawurlencode($matches[0]); } }