Updating PEAR Net_URL2 to 2.1.2

Source: https://pear.php.net/package/Net_URL2
Release date: 2016-04-18
This commit is contained in:
Mikael Nordfeldth 2017-07-10 13:28:40 +02:00
parent a223273544
commit 711f220397
2 changed files with 437 additions and 133 deletions

View File

@ -0,0 +1,27 @@
Copyright (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.

View File

@ -38,9 +38,9 @@
* @package Net_URL2 * @package Net_URL2
* @author Christian Schmidt <schmidt@php.net> * @author Christian Schmidt <schmidt@php.net>
* @copyright 2007-2009 Peytz & Co. A/S * @copyright 2007-2009 Peytz & Co. A/S
* @license http://www.opensource.org/licenses/bsd-license.php New BSD License * @license https://spdx.org/licenses/BSD-3-Clause BSD-3-Clause
* @version CVS: $Id: URL2.php 309223 2011-03-14 14:26:32Z till $ * @version CVS: $Id$
* @link http://www.rfc-editor.org/rfc/rfc3986.txt * @link https://tools.ietf.org/html/rfc3986
*/ */
/** /**
@ -50,9 +50,9 @@
* @package Net_URL2 * @package Net_URL2
* @author Christian Schmidt <schmidt@php.net> * @author Christian Schmidt <schmidt@php.net>
* @copyright 2007-2009 Peytz & Co. A/S * @copyright 2007-2009 Peytz & Co. A/S
* @license http://www.opensource.org/licenses/bsd-license.php New BSD License * @license https://spdx.org/licenses/BSD-3-Clause BSD-3-Clause
* @version Release: @package_version@ * @version Release: 2.1.2
* @link http://pear.php.net/package/Net_URL2 * @link https://pear.php.net/package/Net_URL2
*/ */
class Net_URL2 class Net_URL2
{ {
@ -67,6 +67,12 @@ class Net_URL2
*/ */
const OPTION_USE_BRACKETS = 'use_brackets'; 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. * URL-encode query variable keys. Default is true.
*/ */
@ -90,6 +96,7 @@ class Net_URL2
private $_options = array( private $_options = array(
self::OPTION_STRICT => true, self::OPTION_STRICT => true,
self::OPTION_USE_BRACKETS => true, self::OPTION_USE_BRACKETS => true,
self::OPTION_DROP_SEQUENCE => true,
self::OPTION_ENCODE_KEYS => true, self::OPTION_ENCODE_KEYS => true,
self::OPTION_SEPARATOR_INPUT => '&', self::OPTION_SEPARATOR_INPUT => '&',
self::OPTION_SEPARATOR_OUTPUT => '&', self::OPTION_SEPARATOR_OUTPUT => '&',
@ -136,7 +143,6 @@ class Net_URL2
* @param string $url an absolute or relative URL * @param string $url an absolute or relative URL
* @param array $options an array of OPTION_xxx constants * @param array $options an array of OPTION_xxx constants
* *
* @return $this
* @uses self::parseUrl() * @uses self::parseUrl()
*/ */
public function __construct($url, array $options = array()) public function __construct($url, array $options = array())
@ -156,8 +162,9 @@ class Net_URL2
* This method will magically set the value of a private variable ($var) * This method will magically set the value of a private variable ($var)
* with the value passed as the args * with the value passed as the args
* *
* @param string $var The private variable to set. * @param string $var The private variable to set.
* @param mixed $arg An argument of any type. * @param mixed $arg An argument of any type.
*
* @return void * @return void
*/ */
public function __set($var, $arg) public function __set($var, $arg)
@ -174,10 +181,11 @@ class Net_URL2
* This is the magic get method to retrieve the private variable * This is the magic get method to retrieve the private variable
* that was set by either __set() or it's setter... * that was set by either __set() or it's setter...
* *
* @param string $var The property name to retrieve. * @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 * @return mixed $this->$var Either a boolean false if the
* of the private property. * property is not set or the value
* of the private property.
*/ */
public function __get($var) public function __get($var)
{ {
@ -193,7 +201,7 @@ class Net_URL2
* Returns the scheme, e.g. "http" or "urn", or false if there is no * Returns the scheme, e.g. "http" or "urn", or false if there is no
* scheme specified, i.e. if this is a relative URL. * scheme specified, i.e. if this is a relative URL.
* *
* @return string|bool * @return string|bool
*/ */
public function getScheme() public function getScheme()
{ {
@ -209,7 +217,7 @@ class Net_URL2
* URL * URL
* *
* @return $this * @return $this
* @see getScheme() * @see getScheme
*/ */
public function setScheme($scheme) public function setScheme($scheme)
{ {
@ -221,12 +229,12 @@ class Net_URL2
* Returns the user part of the userinfo part (the part preceding the first * Returns the user part of the userinfo part (the part preceding the first
* ":"), or false if there is no userinfo part. * ":"), or false if there is no userinfo part.
* *
* @return string|bool * @return string|bool
*/ */
public function getUser() public function getUser()
{ {
return $this->_userinfo !== false return $this->_userinfo !== false
? preg_replace('@:.*$@', '', $this->_userinfo) ? preg_replace('(:.*$)', '', $this->_userinfo)
: false; : false;
} }
@ -236,7 +244,7 @@ class Net_URL2
* contain "@" in front of the hostname) or the userinfo part does not * contain "@" in front of the hostname) or the userinfo part does not
* contain ":". * contain ":".
* *
* @return string|bool * @return string|bool
*/ */
public function getPassword() public function getPassword()
{ {
@ -249,7 +257,7 @@ class Net_URL2
* Returns the userinfo part, or false if there is none, i.e. if the * Returns the userinfo part, or false if there is none, i.e. if the
* authority part does not contain "@". * authority part does not contain "@".
* *
* @return string|bool * @return string|bool
*/ */
public function getUserinfo() public function getUserinfo()
{ {
@ -267,10 +275,15 @@ class Net_URL2
*/ */
public function setUserinfo($userinfo, $password = false) public function setUserinfo($userinfo, $password = false)
{ {
$this->_userinfo = $userinfo;
if ($password !== false) { if ($password !== false) {
$this->_userinfo .= ':' . $password; $userinfo .= ':' . $password;
} }
if ($userinfo !== false) {
$userinfo = $this->_encodeData($userinfo);
}
$this->_userinfo = $userinfo;
return $this; return $this;
} }
@ -278,7 +291,7 @@ class Net_URL2
* Returns the host part, or false if there is no authority part, e.g. * Returns the host part, or false if there is no authority part, e.g.
* relative URLs. * relative URLs.
* *
* @return string|bool a hostname, an IP address, or false * @return string|bool a hostname, an IP address, or false
*/ */
public function getHost() public function getHost()
{ {
@ -303,7 +316,7 @@ class Net_URL2
* Returns the port number, or false if there is no port number specified, * Returns the port number, or false if there is no port number specified,
* i.e. if the default port is to be used. * i.e. if the default port is to be used.
* *
* @return string|bool * @return string|bool
*/ */
public function getPort() public function getPort()
{ {
@ -332,13 +345,13 @@ class Net_URL2
*/ */
public function getAuthority() public function getAuthority()
{ {
if (!$this->_host) { if (false === $this->_host) {
return false; return false;
} }
$authority = ''; $authority = '';
if ($this->_userinfo !== false) { if (strlen($this->_userinfo)) {
$authority .= $this->_userinfo . '@'; $authority .= $this->_userinfo . '@';
} }
@ -355,7 +368,7 @@ class Net_URL2
* Sets the authority part, i.e. [ userinfo "@" ] host [ ":" port ]. Specify * Sets the authority part, i.e. [ userinfo "@" ] host [ ":" port ]. Specify
* false if there is no authority. * false if there is no authority.
* *
* @param string|false $authority a hostname or an IP addresse, possibly * @param string|bool $authority a hostname or an IP address, possibly
* with userinfo prefixed and port number * with userinfo prefixed and port number
* appended, e.g. "foo:bar@example.org:81". * appended, e.g. "foo:bar@example.org:81".
* *
@ -366,15 +379,24 @@ class Net_URL2
$this->_userinfo = false; $this->_userinfo = false;
$this->_host = false; $this->_host = false;
$this->_port = false; $this->_port = false;
if (preg_match('@^(([^\@]*)\@)?([^:]+)(:(\d*))?$@', $authority, $reg)) {
if ($reg[1]) {
$this->_userinfo = $reg[2];
}
$this->_host = $reg[3]; if ('' === $authority) {
if (isset($reg[5])) { $this->_host = $authority;
$this->_port = $reg[5]; 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; return $this;
} }
@ -407,7 +429,7 @@ class Net_URL2
* is not present in the URL. * is not present in the URL.
* *
* @return string|bool * @return string|bool
* @see self::getQueryVariables() * @see getQueryVariables
*/ */
public function getQuery() public function getQuery()
{ {
@ -421,7 +443,7 @@ class Net_URL2
* @param string|bool $query a query string, e.g. "foo=1&bar=2" * @param string|bool $query a query string, e.g. "foo=1&bar=2"
* *
* @return $this * @return $this
* @see self::setQueryVariables() * @see setQueryVariables
*/ */
public function setQuery($query) public function setQuery($query)
{ {
@ -432,7 +454,7 @@ class Net_URL2
/** /**
* Returns the fragment name, or false if "#" is not present in the URL. * Returns the fragment name, or false if "#" is not present in the URL.
* *
* @return string|bool * @return string|bool
*/ */
public function getFragment() public function getFragment()
{ {
@ -458,59 +480,167 @@ class Net_URL2
* $_GET in a PHP script. If the URL does not contain a "?", an empty array * $_GET in a PHP script. If the URL does not contain a "?", an empty array
* is returned. * is returned.
* *
* @return array * @return array
*/ */
public function getQueryVariables() public function getQueryVariables()
{ {
$pattern = '/[' . $separator = $this->getOption(self::OPTION_SEPARATOR_INPUT);
preg_quote($this->getOption(self::OPTION_SEPARATOR_INPUT), '/') . $encodeKeys = $this->getOption(self::OPTION_ENCODE_KEYS);
']/'; $useBrackets = $this->getOption(self::OPTION_USE_BRACKETS);
$parts = preg_split($pattern, $this->_query, -1, PREG_SPLIT_NO_EMPTY);
$return = array(); $return = array();
foreach ($parts as $part) { for ($part = strtok($this->_query, $separator);
if (strpos($part, '=') !== false) { strlen($part);
list($key, $value) = explode('=', $part, 2); $part = strtok($separator)
} else { ) {
$key = $part; list($key, $value) = explode('=', $part, 2) + array(1 => '');
$value = null;
}
if ($this->getOption(self::OPTION_ENCODE_KEYS)) { if ($encodeKeys) {
$key = rawurldecode($key); $key = rawurldecode($key);
} }
$value = rawurldecode($value); $value = rawurldecode($value);
if ($this->getOption(self::OPTION_USE_BRACKETS) && if ($useBrackets) {
preg_match('#^(.*)\[([0-9a-z_-]*)\]#i', $key, $matches)) { $return = $this->_queryArrayByKey($key, $value, $return);
} else {
$key = $matches[1]; if (isset($return[$key])) {
$idx = $matches[2]; $return[$key] = (array) $return[$key];
// Ensure is an array
if (empty($return[$key]) || !is_array($return[$key])) {
$return[$key] = array();
}
// Add data
if ($idx === '') {
$return[$key][] = $value; $return[$key][] = $value;
} else { } else {
$return[$key][$idx] = $value; $return[$key] = $value;
} }
} elseif (!$this->getOption(self::OPTION_USE_BRACKETS)
&& !empty($return[$key])
) {
$return[$key] = (array) $return[$key];
$return[$key][] = $value;
} else {
$return[$key] = $value;
} }
} }
return $return; 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. * Sets the query string to the specified variable in the query string.
* *
@ -548,7 +678,7 @@ class Net_URL2
} }
/** /**
* Removes the specifed variable from the query string. * Removes the specified variable from the query string.
* *
* @param string $name a query string variable, e.g. "foo" in "?foo=1" * @param string $name a query string variable, e.g. "foo" in "?foo=1"
* *
@ -564,22 +694,23 @@ class Net_URL2
/** /**
* Returns a string representation of this URL. * Returns a string representation of this URL.
* *
* @return string * @return string
*/ */
public function getURL() public function getURL()
{ {
// See RFC 3986, section 5.3 // See RFC 3986, section 5.3
$url = ""; $url = '';
if ($this->_scheme !== false) { if ($this->_scheme !== false) {
$url .= $this->_scheme . ':'; $url .= $this->_scheme . ':';
} }
$authority = $this->getAuthority(); $authority = $this->getAuthority();
if ($authority !== false) { if ($authority === false && strtolower($this->_scheme) === 'file') {
$url .= '//' . $authority; $authority = '';
} }
$url .= $this->_path;
$url .= $this->_buildAuthorityAndPath($authority, $this->_path);
if ($this->_query !== false) { if ($this->_query !== false) {
$url .= '?' . $this->_query; $url .= '?' . $this->_query;
@ -592,11 +723,31 @@ class Net_URL2
return $url; 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. * Returns a string representation of this URL.
* *
* @return string * @return string
* @see toString() * @link https://php.net/language.oop5.magic#object.tostring
*/ */
public function __toString() public function __toString()
{ {
@ -607,64 +758,112 @@ class Net_URL2
* Returns a normalized string representation of this URL. This is useful * Returns a normalized string representation of this URL. This is useful
* for comparison of URLs. * for comparison of URLs.
* *
* @return string * @return string
*/ */
public function getNormalizedURL() public function getNormalizedURL()
{ {
$url = clone $this; $url = clone $this;
$url->normalize(); $url->normalize();
return $url->getUrl(); return $url->getURL();
} }
/** /**
* Returns a normalized Net_URL2 instance. * Normalizes the URL
* *
* @return Net_URL2 * See RFC 3986, Section 6. Normalization and Comparison
*
* @link https://tools.ietf.org/html/rfc3986#section-6
*
* @return void
*/ */
public function normalize() public function normalize()
{ {
// See RFC 3886, section 6 // See RFC 3986, section 6
// Schemes are case-insensitive // Scheme is case-insensitive
if ($this->_scheme) { if ($this->_scheme) {
$this->_scheme = strtolower($this->_scheme); $this->_scheme = strtolower($this->_scheme);
} }
// Hostnames are case-insensitive // Hostname is case-insensitive
if ($this->_host) { if ($this->_host) {
$this->_host = strtolower($this->_host); $this->_host = strtolower($this->_host);
} }
// Remove default port number for known schemes (RFC 3986, section 6.2.3) // Remove default port number for known schemes (RFC 3986, section 6.2.3)
if ($this->_port && if ('' === $this->_port
$this->_scheme && || $this->_port
$this->_port == getservbyname($this->_scheme, 'tcp')) { && $this->_scheme
&& $this->_port == getservbyname($this->_scheme, 'tcp')
) {
$this->_port = false; $this->_port = false;
} }
// Normalize case of %XX percentage-encodings (RFC 3986, section 6.2.2.1) // Normalize case of %XX percentage-encodings (RFC 3986, section 6.2.2.1)
foreach (array('_userinfo', '_host', '_path') as $part) { // Normalize percentage-encoded unreserved characters (section 6.2.2.2)
if ($this->$part) { $fields = array(&$this->_userinfo, &$this->_host, &$this->_path,
$this->$part = preg_replace('/%[0-9a-f]{2}/ie', &$this->_query, &$this->_fragment);
'strtoupper("\0")', foreach ($fields as &$field) {
$this->$part); if ($field !== false) {
$field = $this->_normalize("$field");
} }
} }
unset($field);
// Path segment normalization (RFC 3986, section 6.2.2.3) // Path segment normalization (RFC 3986, section 6.2.2.3)
$this->_path = self::removeDotSegments($this->_path); $this->_path = self::removeDotSegments($this->_path);
// Scheme based normalization (RFC 3986, section 6.2.3) // Scheme based normalization (RFC 3986, section 6.2.3)
if ($this->_host && !$this->_path) { if (false !== $this->_host && '' === $this->_path) {
$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. * Returns whether this instance represents an absolute URL.
* *
* @return bool * @return bool
*/ */
public function isAbsolute() public function isAbsolute()
{ {
@ -677,20 +876,25 @@ class Net_URL2
* *
* @param Net_URL2|string $reference relative URL * @param Net_URL2|string $reference relative URL
* *
* @return Net_URL2 * @throws Exception
* @return $this
*/ */
public function resolve($reference) public function resolve($reference)
{ {
if (!$reference instanceof Net_URL2) { if (!$reference instanceof Net_URL2) {
$reference = new self($reference); $reference = new self($reference);
} }
if (!$this->isAbsolute()) { if (!$reference->_isFragmentOnly() && !$this->isAbsolute()) {
throw new Exception('Base-URL must be absolute'); 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 // A non-strict parser may ignore a scheme in the reference if it is
// identical to the base URI's scheme. // identical to the base URI's scheme.
if (!$this->getOption(self::OPTION_STRICT) && $reference->_scheme == $this->_scheme) { if (!$this->getOption(self::OPTION_STRICT)
&& $reference->_scheme == $this->_scheme
) {
$reference->_scheme = false; $reference->_scheme = false;
} }
@ -720,7 +924,7 @@ class Net_URL2
} else { } else {
// Merge paths (RFC 3986, section 5.2.3) // Merge paths (RFC 3986, section 5.2.3)
if ($this->_host !== false && $this->_path == '') { if ($this->_host !== false && $this->_path == '') {
$target->_path = '/' . $this->_path; $target->_path = '/' . $reference->_path;
} else { } else {
$i = strrpos($this->_path, '/'); $i = strrpos($this->_path, '/');
if ($i !== false) { if ($i !== false) {
@ -742,6 +946,25 @@ class Net_URL2
return $target; 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. * Removes dots as described in RFC 3986, section 5.2.4, e.g.
* "/foo/../bar/baz" => "/bar/baz" * "/foo/../bar/baz" => "/bar/baz"
@ -752,43 +975,52 @@ class Net_URL2
*/ */
public static function removeDotSegments($path) public static function removeDotSegments($path)
{ {
$path = (string) $path;
$output = ''; $output = '';
// Make sure not to be trapped in an infinite loop due to a bug in this // Make sure not to be trapped in an infinite loop due to a bug in this
// method // method
$loopLimit = 256;
$j = 0; $j = 0;
while ($path && $j++ < 100) { while ('' !== $path && $j++ < $loopLimit) {
if (substr($path, 0, 2) == './') { if (substr($path, 0, 2) === './') {
// Step 2.A // Step 2.A
$path = substr($path, 2); $path = substr($path, 2);
} elseif (substr($path, 0, 3) == '../') { } elseif (substr($path, 0, 3) === '../') {
// Step 2.A // Step 2.A
$path = substr($path, 3); $path = substr($path, 3);
} elseif (substr($path, 0, 3) == '/./' || $path == '/.') { } elseif (substr($path, 0, 3) === '/./' || $path === '/.') {
// Step 2.B // Step 2.B
$path = '/' . substr($path, 3); $path = '/' . substr($path, 3);
} elseif (substr($path, 0, 4) == '/../' || $path == '/..') { } elseif (substr($path, 0, 4) === '/../' || $path === '/..') {
// Step 2.C // Step 2.C
$path = '/' . substr($path, 4); $path = '/' . substr($path, 4);
$i = strrpos($output, '/'); $i = strrpos($output, '/');
$output = $i === false ? '' : substr($output, 0, $i); $output = $i === false ? '' : substr($output, 0, $i);
} elseif ($path == '.' || $path == '..') { } elseif ($path === '.' || $path === '..') {
// Step 2.D // Step 2.D
$path = ''; $path = '';
} else { } else {
// Step 2.E // Step 2.E
$i = strpos($path, '/'); $i = strpos($path, '/', $path[0] === '/');
if ($i === 0) {
$i = strpos($path, '/', 1);
}
if ($i === false) { if ($i === false) {
$i = strlen($path); $output .= $path;
$path = '';
break;
} }
$output .= substr($path, 0, $i); $output .= substr($path, 0, $i);
$path = substr($path, $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; return $output;
} }
@ -797,12 +1029,13 @@ class Net_URL2
* Similar to PHP's rawurlencode(), except that it also encodes ~ in PHP * Similar to PHP's rawurlencode(), except that it also encodes ~ in PHP
* 5.2.x and earlier. * 5.2.x and earlier.
* *
* @param $raw the string to encode * @param string $string string to encode
*
* @return string * @return string
*/ */
public static function urlencode($string) public static function urlencode($string)
{ {
$encoded = rawurlencode($string); $encoded = rawurlencode($string);
// This is only necessary in PHP < 5.3. // This is only necessary in PHP < 5.3.
$encoded = str_replace('%7E', '~', $encoded); $encoded = str_replace('%7E', '~', $encoded);
@ -813,7 +1046,8 @@ class Net_URL2
* Returns a Net_URL2 instance representing the canonical URL of the * Returns a Net_URL2 instance representing the canonical URL of the
* currently executing PHP script. * currently executing PHP script.
* *
* @return string * @throws Exception
* @return string
*/ */
public static function getCanonical() public static function getCanonical()
{ {
@ -827,9 +1061,9 @@ class Net_URL2
$url->_scheme = isset($_SERVER['HTTPS']) ? 'https' : 'http'; $url->_scheme = isset($_SERVER['HTTPS']) ? 'https' : 'http';
$url->_host = $_SERVER['SERVER_NAME']; $url->_host = $_SERVER['SERVER_NAME'];
$port = $_SERVER['SERVER_PORT']; $port = $_SERVER['SERVER_PORT'];
if ($url->_scheme == 'http' && $port != 80 || if ($url->_scheme == 'http' && $port != 80
$url->_scheme == 'https' && $port != 443) { || $url->_scheme == 'https' && $port != 443
) {
$url->_port = $port; $url->_port = $port;
} }
return $url; return $url;
@ -849,7 +1083,8 @@ class Net_URL2
* Returns a Net_URL2 instance representing the URL used to retrieve the * Returns a Net_URL2 instance representing the URL used to retrieve the
* current request. * current request.
* *
* @return Net_URL2 * @throws Exception
* @return $this
*/ */
public static function getRequested() public static function getRequested()
{ {
@ -871,7 +1106,7 @@ class Net_URL2
* *
* @param string $optionName The name of the option to retrieve * @param string $optionName The name of the option to retrieve
* *
* @return mixed * @return mixed
*/ */
public function getOption($optionName) public function getOption($optionName)
{ {
@ -885,7 +1120,7 @@ class Net_URL2
* *
* @param array $data An array, which has to be converted into * @param array $data An array, which has to be converted into
* QUERY_STRING. Anything is possible. * QUERY_STRING. Anything is possible.
* @param string $seperator See {@link self::OPTION_SEPARATOR_OUTPUT} * @param string $separator Separator {@link self::OPTION_SEPARATOR_OUTPUT}
* @param string $key For stacked values (arrays in an array). * @param string $key For stacked values (arrays in an array).
* *
* @return string * @return string
@ -893,12 +1128,17 @@ class Net_URL2
protected function buildQuery(array $data, $separator, $key = null) protected function buildQuery(array $data, $separator, $key = null)
{ {
$query = array(); $query = array();
$drop_names = (
$this->_options[self::OPTION_DROP_SEQUENCE] === true
&& array_keys($data) === array_keys(array_values($data))
);
foreach ($data as $name => $value) { foreach ($data as $name => $value) {
if ($this->getOption(self::OPTION_ENCODE_KEYS) === true) { if ($this->getOption(self::OPTION_ENCODE_KEYS) === true) {
$name = rawurlencode($name); $name = rawurlencode($name);
} }
if ($key !== null) { if ($key !== null) {
if ($this->getOption(self::OPTION_USE_BRACKETS) === true) { if ($this->getOption(self::OPTION_USE_BRACKETS) === true) {
$drop_names && $name = '';
$name = $key . '[' . $name . ']'; $name = $key . '[' . $name . ']';
} else { } else {
$name = $key; $name = $key;
@ -914,29 +1154,66 @@ class Net_URL2
} }
/** /**
* This method uses a funky regex to parse the url into the designated parts. * This method uses a regex to parse the url into the designated parts.
* *
* @param string $url * @param string $url URL
* *
* @return void * @return void
* @uses self::$_scheme, self::setAuthority(), self::$_path, self::$_query, * @uses self::$_scheme, self::setAuthority(), self::$_path, self::$_query,
* self::$_fragment * self::$_fragment
* @see self::__construct() * @see __construct
*/ */
protected function parseUrl($url) protected function parseUrl($url)
{ {
// The regular expression is copied verbatim from RFC 3986, appendix B. // The regular expression is copied verbatim from RFC 3986, appendix B.
// The expression does not validate the URL but matches any string. // The expression does not validate the URL but matches any string.
preg_match('!^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?!', preg_match(
$url, '(^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?)',
$matches); $url, $matches
);
// "path" is always present (possibly as an empty string); the rest // "path" is always present (possibly as an empty string); the rest
// are optional. // are optional.
$this->_scheme = !empty($matches[1]) ? $matches[2] : false; $this->_scheme = !empty($matches[1]) ? $matches[2] : false;
$this->setAuthority(!empty($matches[3]) ? $matches[4] : false); $this->setAuthority(!empty($matches[3]) ? $matches[4] : false);
$this->_path = $matches[5]; $this->_path = $this->_encodeData($matches[5]);
$this->_query = !empty($matches[6]) ? $matches[7] : false; $this->_query = !empty($matches[6])
? $this->_encodeData($matches[7])
: false
;
$this->_fragment = !empty($matches[8]) ? $matches[9] : 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]);
}
} }