Spaces:
Sleeping
Sleeping
| // +-----------------------------------------------------------------------+ | |
| // | Copyright (c) 2007-2008, Christian Schmidt, Peytz & Co. A/S | | |
| // | All rights reserved. | | |
| // | | | |
| // | Redistribution and use in source and binary forms, with or without | | |
| // | modification, are permitted provided that the following conditions | | |
| // | are met: | | |
| // | | | |
| // | o Redistributions of source code must retain the above copyright | | |
| // | notice, this list of conditions and the following disclaimer. | | |
| // | o 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.| | |
| // | o The names of the authors may not 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 | | |
| // | OWNER 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. | | |
| // | | | |
| // +-----------------------------------------------------------------------+ | |
| // | Author: Christian Schmidt <schmidt at php dot net> | | |
| // +-----------------------------------------------------------------------+ | |
| // | |
| // $Id: URL2.php,v 1.10 2008/04/26 21:57:08 schmidt Exp $ | |
| // | |
| // Net_URL2 Class (PHP5 Only) | |
| // This code is released under the BSD License - http://www.opensource.org/licenses/bsd-license.php | |
| /** | |
| * @license BSD License | |
| */ | |
| 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'; | |
| /** | |
| * 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 specified by the | |
| * arg_separator.input php.ini setting (this defaults to "&"). | |
| */ | |
| const OPTION_SEPARATOR_INPUT = 'input_separator'; | |
| /** | |
| * Query variable separator used when generating the query string. Default | |
| * is specified by the arg_separator.output php.ini setting (this defaults | |
| * to "&"). | |
| */ | |
| 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_ENCODE_KEYS => true, | |
| self::OPTION_SEPARATOR_INPUT => 'x&', | |
| self::OPTION_SEPARATOR_OUTPUT => 'x&', | |
| ); | |
| /** | |
| * @var string|bool | |
| */ | |
| private $scheme = false; | |
| /** | |
| * @var string|bool | |
| */ | |
| private $userinfo = false; | |
| /** | |
| * @var string|bool | |
| */ | |
| private $host = false; | |
| /** | |
| * @var int|bool | |
| */ | |
| private $port = false; | |
| /** | |
| * @var string | |
| */ | |
| private $path = ''; | |
| /** | |
| * @var string|bool | |
| */ | |
| private $query = false; | |
| /** | |
| * @var string|bool | |
| */ | |
| private $fragment = false; | |
| /** | |
| * @param string $url an absolute or relative URL | |
| * @param array $options | |
| */ | |
| public function __construct($url, $options = null) | |
| { | |
| $this->setOption(self::OPTION_SEPARATOR_INPUT, | |
| ini_get('arg_separator.input')); | |
| $this->setOption(self::OPTION_SEPARATOR_OUTPUT, | |
| ini_get('arg_separator.output')); | |
| if (is_array($options)) { | |
| foreach ($options as $optionName => $value) { | |
| $this->setOption($optionName); | |
| } | |
| } | |
| if (preg_match('@^([a-z][a-z0-9.+-]*):@i', $url, $reg)) { | |
| $this->scheme = $reg[1]; | |
| $url = substr($url, strlen($reg[0])); | |
| } | |
| if (preg_match('@^//([^/#?]+)@', $url, $reg)) { | |
| $this->setAuthority($reg[1]); | |
| $url = substr($url, strlen($reg[0])); | |
| } | |
| $i = strcspn($url, '?#'); | |
| $this->path = substr($url, 0, $i); | |
| $url = substr($url, $i); | |
| if (preg_match('@^\?([^#]*)@', $url, $reg)) { | |
| $this->query = $reg[1]; | |
| $url = substr($url, strlen($reg[0])); | |
| } | |
| if ($url) { | |
| $this->fragment = substr($url, 1); | |
| } | |
| } | |
| /** | |
| * 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; | |
| } | |
| /** | |
| * @param string|bool $scheme | |
| * | |
| * @return void | |
| * @see getScheme() | |
| */ | |
| public function setScheme($scheme) | |
| { | |
| $this->scheme = $scheme; | |
| } | |
| /** | |
| * 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 | |
| * | |
| * @return void | |
| */ | |
| public function setUserinfo($userinfo, $password = false) | |
| { | |
| $this->userinfo = $userinfo; | |
| if ($password !== false) { | |
| $this->userinfo .= ':' . $password; | |
| } | |
| } | |
| /** | |
| * Returns the host part, or false if there is no authority part, e.g. | |
| * relative URLs. | |
| * | |
| * @return string|bool | |
| */ | |
| public function getHost() | |
| { | |
| return $this->host; | |
| } | |
| /** | |
| * @param string|bool $host | |
| * | |
| * @return void | |
| */ | |
| public function setHost($host) | |
| { | |
| $this->host = $host; | |
| } | |
| /** | |
| * Returns the port number, or false if there is no port number specified, | |
| * i.e. if the default port is to be used. | |
| * | |
| * @return int|bool | |
| */ | |
| public function getPort() | |
| { | |
| return $this->port; | |
| } | |
| /** | |
| * @param int|bool $port | |
| * | |
| * @return void | |
| */ | |
| public function setPort($port) | |
| { | |
| $this->port = intval($port); | |
| } | |
| /** | |
| * Returns the authority part, i.e. [ userinfo "@" ] host [ ":" port ], or | |
| * false if there is no authority none. | |
| * | |
| * @return string|bool | |
| */ | |
| public function getAuthority() | |
| { | |
| if (!$this->host) { | |
| return false; | |
| } | |
| $authority = ''; | |
| if ($this->userinfo !== false) { | |
| $authority .= $this->userinfo . '@'; | |
| } | |
| $authority .= $this->host; | |
| if ($this->port !== false) { | |
| $authority .= ':' . $this->port; | |
| } | |
| return $authority; | |
| } | |
| /** | |
| * @param string|false $authority | |
| * | |
| * @return void | |
| */ | |
| public function setAuthority($authority) | |
| { | |
| $this->user = false; | |
| $this->pass = false; | |
| $this->host = false; | |
| $this->port = false; | |
| if (preg_match('@^(([^\@]+)\@)?([^:]+)(:(\d*))?$@', $authority, $reg)) { | |
| if ($reg[1]) { | |
| $this->userinfo = $reg[2]; | |
| } | |
| $this->host = $reg[3]; | |
| if (isset($reg[5])) { | |
| $this->port = intval($reg[5]); | |
| } | |
| } | |
| } | |
| /** | |
| * Returns the path part (possibly an empty string). | |
| * | |
| * @return string | |
| */ | |
| public function getPath() | |
| { | |
| return $this->path; | |
| } | |
| /** | |
| * @param string $path | |
| * | |
| * @return void | |
| */ | |
| public function setPath($path) | |
| { | |
| $this->path = $path; | |
| } | |
| /** | |
| * Returns the query string (excluding the leading "?"), or false if "?" | |
| * isn't present in the URL. | |
| * | |
| * @return string|bool | |
| * @see self::getQueryVariables() | |
| */ | |
| public function getQuery() | |
| { | |
| return $this->query; | |
| } | |
| /** | |
| * @param string|bool $query | |
| * | |
| * @return void | |
| * @see self::setQueryVariables() | |
| */ | |
| public function setQuery($query) | |
| { | |
| $this->query = $query; | |
| } | |
| /** | |
| * Returns the fragment name, or false if "#" isn't present in the URL. | |
| * | |
| * @return string|bool | |
| */ | |
| public function getFragment() | |
| { | |
| return $this->fragment; | |
| } | |
| /** | |
| * @param string|bool $fragment | |
| * | |
| * @return void | |
| */ | |
| public function setFragment($fragment) | |
| { | |
| $this->fragment = $fragment; | |
| } | |
| /** | |
| * Returns the query string like an array as the variables would appear in | |
| * $_GET in a PHP script. | |
| * | |
| * @return array | |
| */ | |
| public function getQueryVariables() | |
| { | |
| $pattern = '/[' . | |
| preg_quote($this->getOption(self::OPTION_SEPARATOR_INPUT), '/') . | |
| ']/'; | |
| $parts = preg_split($pattern, $this->query, -1, PREG_SPLIT_NO_EMPTY); | |
| $return = array(); | |
| foreach ($parts as $part) { | |
| if (strpos($part, '=') !== false) { | |
| list($key, $value) = explode('=', $part, 2); | |
| } else { | |
| $key = $part; | |
| $value = null; | |
| } | |
| if ($this->getOption(self::OPTION_ENCODE_KEYS)) { | |
| $key = rawurldecode($key); | |
| } | |
| $value = rawurldecode($value); | |
| if ($this->getOption(self::OPTION_USE_BRACKETS) && | |
| preg_match('#^(.*)\[([0-9a-z_-]*)\]#i', $key, $matches)) { | |
| $key = $matches[1]; | |
| $idx = $matches[2]; | |
| // Ensure is an array | |
| if (empty($return[$key]) || !is_array($return[$key])) { | |
| $return[$key] = array(); | |
| } | |
| // Add data | |
| if ($idx === '') { | |
| $return[$key][] = $value; | |
| } else { | |
| $return[$key][$idx] = $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; | |
| } | |
| /** | |
| * @param array $array (name => value) array | |
| * | |
| * @return void | |
| */ | |
| public function setQueryVariables(array $array) | |
| { | |
| if (!$array) { | |
| $this->query = false; | |
| } else { | |
| foreach ($array as $name => $value) { | |
| if ($this->getOption(self::OPTION_ENCODE_KEYS)) { | |
| $name = rawurlencode($name); | |
| } | |
| if (is_array($value)) { | |
| foreach ($value as $k => $v) { | |
| $parts[] = $this->getOption(self::OPTION_USE_BRACKETS) | |
| ? sprintf('%s[%s]=%s', $name, $k, $v) | |
| : ($name . '=' . $v); | |
| } | |
| } elseif (!is_null($value)) { | |
| $parts[] = $name . '=' . $value; | |
| } else { | |
| $parts[] = $name; | |
| } | |
| } | |
| $this->query = implode($this->getOption(self::OPTION_SEPARATOR_OUTPUT), | |
| $parts); | |
| } | |
| } | |
| /** | |
| * @param string $name | |
| * @param mixed $value | |
| * | |
| * @return array | |
| */ | |
| public function setQueryVariable($name, $value) | |
| { | |
| $array = $this->getQueryVariables(); | |
| $array[$name] = $value; | |
| $this->setQueryVariables($array); | |
| } | |
| /** | |
| * @param string $name | |
| * | |
| * @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) { | |
| $url .= '//' . $authority; | |
| } | |
| $url .= $this->path; | |
| if ($this->query !== false) { | |
| $url .= '?' . $this->query; | |
| } | |
| if ($this->fragment !== false) { | |
| $url .= '#' . $this->fragment; | |
| } | |
| return $url; | |
| } | |
| /** | |
| * 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(); | |
| } | |
| /** | |
| * Returns a normalized Net_URL2 instance. | |
| * | |
| * @return Net_URL2 | |
| */ | |
| public function normalize() | |
| { | |
| // See RFC 3886, section 6 | |
| // Schemes are case-insensitive | |
| if ($this->scheme) { | |
| $this->scheme = strtolower($this->scheme); | |
| } | |
| // Hostnames are 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->scheme && | |
| $this->port == getservbyname($this->scheme, 'tcp')) { | |
| $this->port = false; | |
| } | |
| // Normalize case of %XX percentage-encodings (RFC 3986, section 6.2.2.1) | |
| foreach (array('userinfo', 'host', 'path') as $part) { | |
| if ($this->$part) { | |
| $this->$part = preg_replace('/%[0-9a-f]{2}/ie', 'strtoupper("\0")', $this->$part); | |
| } | |
| } | |
| // 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 ($this->host && !$this->path) { | |
| $this->path = '/'; | |
| } | |
| } | |
| /** | |
| * 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 | |
| * | |
| * @return Net_URL2 | |
| */ | |
| public function resolve($reference) | |
| { | |
| if (is_string($reference)) { | |
| $reference = new self($reference); | |
| } | |
| if (!$this->isAbsolute()) { | |
| throw new Exception('Base-URL must be absolute'); | |
| } | |
| // 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 = '/' . $this->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; | |
| } | |
| /** | |
| * 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 | |
| */ | |
| private static function removeDotSegments($path) | |
| { | |
| $output = ''; | |
| // Make sure not to be trapped in an infinite loop due to a bug in this | |
| // method | |
| $j = 0; | |
| while ($path && $j++ < 100) { | |
| // Step A | |
| if (substr($path, 0, 2) == './') { | |
| $path = substr($path, 2); | |
| } elseif (substr($path, 0, 3) == '../') { | |
| $path = substr($path, 3); | |
| // Step B | |
| } elseif (substr($path, 0, 3) == '/./' || $path == '/.') { | |
| $path = '/' . substr($path, 3); | |
| // Step C | |
| } elseif (substr($path, 0, 4) == '/../' || $path == '/..') { | |
| $path = '/' . substr($path, 4); | |
| $i = strrpos($output, '/'); | |
| $output = $i === false ? '' : substr($output, 0, $i); | |
| // Step D | |
| } elseif ($path == '.' || $path == '..') { | |
| $path = ''; | |
| // Step E | |
| } else { | |
| $i = strpos($path, '/'); | |
| if ($i === 0) { | |
| $i = strpos($path, '/', 1); | |
| } | |
| if ($i === false) { | |
| $i = strlen($path); | |
| } | |
| $output .= substr($path, 0, $i); | |
| $path = substr($path, $i); | |
| } | |
| } | |
| return $output; | |
| } | |
| /** | |
| * Returns a Net_URL2 instance representing the canonical URL of the | |
| * currently executing PHP script. | |
| * | |
| * @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 = intval($_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. | |
| * | |
| * @return Net_URL2 | |
| */ | |
| 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; | |
| } | |
| /** | |
| * Sets the specified option. | |
| * | |
| * @param string $optionName a self::OPTION_ constant | |
| * @param mixed $value option value | |
| * | |
| * @return void | |
| * @see self::OPTION_STRICT | |
| * @see self::OPTION_USE_BRACKETS | |
| * @see self::OPTION_ENCODE_KEYS | |
| */ | |
| function setOption($optionName, $value) | |
| { | |
| if (!array_key_exists($optionName, $this->options)) { | |
| return false; | |
| } | |
| $this->options[$optionName] = $value; | |
| } | |
| /** | |
| * Returns the value of the specified option. | |
| * | |
| * @param string $optionName The name of the option to retrieve | |
| * | |
| * @return mixed | |
| */ | |
| function getOption($optionName) | |
| { | |
| return isset($this->options[$optionName]) | |
| ? $this->options[$optionName] : false; | |
| } | |
| } | |