Rev 80 | Rev 120 | Go to most recent revision | Blame | Compare with Previous | Last modification | View Log | RSS feed
<?php// +-----------------------------------------------------------------------+// | 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: Url.php 85 2009-08-27 08:19:18Z jpm $//// 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 Url{/*** 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 arrayif (empty($return[$key]) || !is_array($return[$key])) {$return[$key] = array();}// Add dataif ($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-insensitiveif ($this->scheme) {$this->scheme = strtolower($this->scheme);}// Hostnames are case-insensitiveif ($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 Aif (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 URLthrow 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 URLthrow 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;}public function __toString() {return $this->getURL();}}