URL2.php 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943
  1. <?php
  2. /**
  3. * Net_URL2, a class representing a URL as per RFC 3986.
  4. *
  5. * PHP version 5
  6. *
  7. * LICENSE:
  8. *
  9. * Copyright (c) 2007-2009, Peytz & Co. A/S
  10. * All rights reserved.
  11. *
  12. * Redistribution and use in source and binary forms, with or without
  13. * modification, are permitted provided that the following conditions
  14. * are met:
  15. *
  16. * * Redistributions of source code must retain the above copyright
  17. * notice, this list of conditions and the following disclaimer.
  18. * * Redistributions in binary form must reproduce the above copyright
  19. * notice, this list of conditions and the following disclaimer in
  20. * the documentation and/or other materials provided with the distribution.
  21. * * Neither the name of the Net_URL2 nor the names of its contributors may
  22. * be used to endorse or promote products derived from this software
  23. * without specific prior written permission.
  24. *
  25. * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
  26. * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
  27. * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
  28. * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
  29. * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
  30. * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
  31. * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
  32. * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
  33. * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
  34. * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
  35. * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  36. *
  37. * @category Networking
  38. * @package Net_URL2
  39. * @author Christian Schmidt <schmidt@php.net>
  40. * @copyright 2007-2009 Peytz & Co. A/S
  41. * @license http://www.opensource.org/licenses/bsd-license.php New BSD License
  42. * @version CVS: $Id: URL2.php 309223 2011-03-14 14:26:32Z till $
  43. * @link http://www.rfc-editor.org/rfc/rfc3986.txt
  44. */
  45. /**
  46. * Represents a URL as per RFC 3986.
  47. *
  48. * @category Networking
  49. * @package Net_URL2
  50. * @author Christian Schmidt <schmidt@php.net>
  51. * @copyright 2007-2009 Peytz & Co. A/S
  52. * @license http://www.opensource.org/licenses/bsd-license.php New BSD License
  53. * @version Release: @package_version@
  54. * @link http://pear.php.net/package/Net_URL2
  55. */
  56. class Net_URL2
  57. {
  58. /**
  59. * Do strict parsing in resolve() (see RFC 3986, section 5.2.2). Default
  60. * is true.
  61. */
  62. const OPTION_STRICT = 'strict';
  63. /**
  64. * Represent arrays in query using PHP's [] notation. Default is true.
  65. */
  66. const OPTION_USE_BRACKETS = 'use_brackets';
  67. /**
  68. * URL-encode query variable keys. Default is true.
  69. */
  70. const OPTION_ENCODE_KEYS = 'encode_keys';
  71. /**
  72. * Query variable separators when parsing the query string. Every character
  73. * is considered a separator. Default is "&".
  74. */
  75. const OPTION_SEPARATOR_INPUT = 'input_separator';
  76. /**
  77. * Query variable separator used when generating the query string. Default
  78. * is "&".
  79. */
  80. const OPTION_SEPARATOR_OUTPUT = 'output_separator';
  81. /**
  82. * Default options corresponds to how PHP handles $_GET.
  83. */
  84. private $_options = array(
  85. self::OPTION_STRICT => true,
  86. self::OPTION_USE_BRACKETS => true,
  87. self::OPTION_ENCODE_KEYS => true,
  88. self::OPTION_SEPARATOR_INPUT => '&',
  89. self::OPTION_SEPARATOR_OUTPUT => '&',
  90. );
  91. /**
  92. * @var string|bool
  93. */
  94. private $_scheme = false;
  95. /**
  96. * @var string|bool
  97. */
  98. private $_userinfo = false;
  99. /**
  100. * @var string|bool
  101. */
  102. private $_host = false;
  103. /**
  104. * @var string|bool
  105. */
  106. private $_port = false;
  107. /**
  108. * @var string
  109. */
  110. private $_path = '';
  111. /**
  112. * @var string|bool
  113. */
  114. private $_query = false;
  115. /**
  116. * @var string|bool
  117. */
  118. private $_fragment = false;
  119. /**
  120. * Constructor.
  121. *
  122. * @param string $url an absolute or relative URL
  123. * @param array $options an array of OPTION_xxx constants
  124. *
  125. * @return $this
  126. * @uses self::parseUrl()
  127. */
  128. public function __construct($url, array $options = array())
  129. {
  130. foreach ($options as $optionName => $value) {
  131. if (array_key_exists($optionName, $this->_options)) {
  132. $this->_options[$optionName] = $value;
  133. }
  134. }
  135. $this->parseUrl($url);
  136. }
  137. /**
  138. * Magic Setter.
  139. *
  140. * This method will magically set the value of a private variable ($var)
  141. * with the value passed as the args
  142. *
  143. * @param string $var The private variable to set.
  144. * @param mixed $arg An argument of any type.
  145. * @return void
  146. */
  147. public function __set($var, $arg)
  148. {
  149. $method = 'set' . $var;
  150. if (method_exists($this, $method)) {
  151. $this->$method($arg);
  152. }
  153. }
  154. /**
  155. * Magic Getter.
  156. *
  157. * This is the magic get method to retrieve the private variable
  158. * that was set by either __set() or it's setter...
  159. *
  160. * @param string $var The property name to retrieve.
  161. * @return mixed $this->$var Either a boolean false if the
  162. * property is not set or the value
  163. * of the private property.
  164. */
  165. public function __get($var)
  166. {
  167. $method = 'get' . $var;
  168. if (method_exists($this, $method)) {
  169. return $this->$method();
  170. }
  171. return false;
  172. }
  173. /**
  174. * Returns the scheme, e.g. "http" or "urn", or false if there is no
  175. * scheme specified, i.e. if this is a relative URL.
  176. *
  177. * @return string|bool
  178. */
  179. public function getScheme()
  180. {
  181. return $this->_scheme;
  182. }
  183. /**
  184. * Sets the scheme, e.g. "http" or "urn". Specify false if there is no
  185. * scheme specified, i.e. if this is a relative URL.
  186. *
  187. * @param string|bool $scheme e.g. "http" or "urn", or false if there is no
  188. * scheme specified, i.e. if this is a relative
  189. * URL
  190. *
  191. * @return $this
  192. * @see getScheme()
  193. */
  194. public function setScheme($scheme)
  195. {
  196. $this->_scheme = $scheme;
  197. return $this;
  198. }
  199. /**
  200. * Returns the user part of the userinfo part (the part preceding the first
  201. * ":"), or false if there is no userinfo part.
  202. *
  203. * @return string|bool
  204. */
  205. public function getUser()
  206. {
  207. return $this->_userinfo !== false
  208. ? preg_replace('@:.*$@', '', $this->_userinfo)
  209. : false;
  210. }
  211. /**
  212. * Returns the password part of the userinfo part (the part after the first
  213. * ":"), or false if there is no userinfo part (i.e. the URL does not
  214. * contain "@" in front of the hostname) or the userinfo part does not
  215. * contain ":".
  216. *
  217. * @return string|bool
  218. */
  219. public function getPassword()
  220. {
  221. return $this->_userinfo !== false
  222. ? substr(strstr($this->_userinfo, ':'), 1)
  223. : false;
  224. }
  225. /**
  226. * Returns the userinfo part, or false if there is none, i.e. if the
  227. * authority part does not contain "@".
  228. *
  229. * @return string|bool
  230. */
  231. public function getUserinfo()
  232. {
  233. return $this->_userinfo;
  234. }
  235. /**
  236. * Sets the userinfo part. If two arguments are passed, they are combined
  237. * in the userinfo part as username ":" password.
  238. *
  239. * @param string|bool $userinfo userinfo or username
  240. * @param string|bool $password optional password, or false
  241. *
  242. * @return $this
  243. */
  244. public function setUserinfo($userinfo, $password = false)
  245. {
  246. $this->_userinfo = $userinfo;
  247. if ($password !== false) {
  248. $this->_userinfo .= ':' . $password;
  249. }
  250. return $this;
  251. }
  252. /**
  253. * Returns the host part, or false if there is no authority part, e.g.
  254. * relative URLs.
  255. *
  256. * @return string|bool a hostname, an IP address, or false
  257. */
  258. public function getHost()
  259. {
  260. return $this->_host;
  261. }
  262. /**
  263. * Sets the host part. Specify false if there is no authority part, e.g.
  264. * relative URLs.
  265. *
  266. * @param string|bool $host a hostname, an IP address, or false
  267. *
  268. * @return $this
  269. */
  270. public function setHost($host)
  271. {
  272. $this->_host = $host;
  273. return $this;
  274. }
  275. /**
  276. * Returns the port number, or false if there is no port number specified,
  277. * i.e. if the default port is to be used.
  278. *
  279. * @return string|bool
  280. */
  281. public function getPort()
  282. {
  283. return $this->_port;
  284. }
  285. /**
  286. * Sets the port number. Specify false if there is no port number specified,
  287. * i.e. if the default port is to be used.
  288. *
  289. * @param string|bool $port a port number, or false
  290. *
  291. * @return $this
  292. */
  293. public function setPort($port)
  294. {
  295. $this->_port = $port;
  296. return $this;
  297. }
  298. /**
  299. * Returns the authority part, i.e. [ userinfo "@" ] host [ ":" port ], or
  300. * false if there is no authority.
  301. *
  302. * @return string|bool
  303. */
  304. public function getAuthority()
  305. {
  306. if (!$this->_host) {
  307. return false;
  308. }
  309. $authority = '';
  310. if ($this->_userinfo !== false) {
  311. $authority .= $this->_userinfo . '@';
  312. }
  313. $authority .= $this->_host;
  314. if ($this->_port !== false) {
  315. $authority .= ':' . $this->_port;
  316. }
  317. return $authority;
  318. }
  319. /**
  320. * Sets the authority part, i.e. [ userinfo "@" ] host [ ":" port ]. Specify
  321. * false if there is no authority.
  322. *
  323. * @param string|false $authority a hostname or an IP addresse, possibly
  324. * with userinfo prefixed and port number
  325. * appended, e.g. "foo:bar@example.org:81".
  326. *
  327. * @return $this
  328. */
  329. public function setAuthority($authority)
  330. {
  331. $this->_userinfo = false;
  332. $this->_host = false;
  333. $this->_port = false;
  334. if (preg_match('@^(([^\@]*)\@)?([^:]+)(:(\d*))?$@', $authority, $reg)) {
  335. if ($reg[1]) {
  336. $this->_userinfo = $reg[2];
  337. }
  338. $this->_host = $reg[3];
  339. if (isset($reg[5])) {
  340. $this->_port = $reg[5];
  341. }
  342. }
  343. return $this;
  344. }
  345. /**
  346. * Returns the path part (possibly an empty string).
  347. *
  348. * @return string
  349. */
  350. public function getPath()
  351. {
  352. return $this->_path;
  353. }
  354. /**
  355. * Sets the path part (possibly an empty string).
  356. *
  357. * @param string $path a path
  358. *
  359. * @return $this
  360. */
  361. public function setPath($path)
  362. {
  363. $this->_path = $path;
  364. return $this;
  365. }
  366. /**
  367. * Returns the query string (excluding the leading "?"), or false if "?"
  368. * is not present in the URL.
  369. *
  370. * @return string|bool
  371. * @see self::getQueryVariables()
  372. */
  373. public function getQuery()
  374. {
  375. return $this->_query;
  376. }
  377. /**
  378. * Sets the query string (excluding the leading "?"). Specify false if "?"
  379. * is not present in the URL.
  380. *
  381. * @param string|bool $query a query string, e.g. "foo=1&bar=2"
  382. *
  383. * @return $this
  384. * @see self::setQueryVariables()
  385. */
  386. public function setQuery($query)
  387. {
  388. $this->_query = $query;
  389. return $this;
  390. }
  391. /**
  392. * Returns the fragment name, or false if "#" is not present in the URL.
  393. *
  394. * @return string|bool
  395. */
  396. public function getFragment()
  397. {
  398. return $this->_fragment;
  399. }
  400. /**
  401. * Sets the fragment name. Specify false if "#" is not present in the URL.
  402. *
  403. * @param string|bool $fragment a fragment excluding the leading "#", or
  404. * false
  405. *
  406. * @return $this
  407. */
  408. public function setFragment($fragment)
  409. {
  410. $this->_fragment = $fragment;
  411. return $this;
  412. }
  413. /**
  414. * Returns the query string like an array as the variables would appear in
  415. * $_GET in a PHP script. If the URL does not contain a "?", an empty array
  416. * is returned.
  417. *
  418. * @return array
  419. */
  420. public function getQueryVariables()
  421. {
  422. $pattern = '/[' .
  423. preg_quote($this->getOption(self::OPTION_SEPARATOR_INPUT), '/') .
  424. ']/';
  425. $parts = preg_split($pattern, $this->_query, -1, PREG_SPLIT_NO_EMPTY);
  426. $return = array();
  427. foreach ($parts as $part) {
  428. if (strpos($part, '=') !== false) {
  429. list($key, $value) = explode('=', $part, 2);
  430. } else {
  431. $key = $part;
  432. $value = null;
  433. }
  434. if ($this->getOption(self::OPTION_ENCODE_KEYS)) {
  435. $key = rawurldecode($key);
  436. }
  437. $value = rawurldecode($value);
  438. if ($this->getOption(self::OPTION_USE_BRACKETS) &&
  439. preg_match('#^(.*)\[([0-9a-z_-]*)\]#i', $key, $matches)) {
  440. $key = $matches[1];
  441. $idx = $matches[2];
  442. // Ensure is an array
  443. if (empty($return[$key]) || !is_array($return[$key])) {
  444. $return[$key] = array();
  445. }
  446. // Add data
  447. if ($idx === '') {
  448. $return[$key][] = $value;
  449. } else {
  450. $return[$key][$idx] = $value;
  451. }
  452. } elseif (!$this->getOption(self::OPTION_USE_BRACKETS)
  453. && !empty($return[$key])
  454. ) {
  455. $return[$key] = (array) $return[$key];
  456. $return[$key][] = $value;
  457. } else {
  458. $return[$key] = $value;
  459. }
  460. }
  461. return $return;
  462. }
  463. /**
  464. * Sets the query string to the specified variable in the query string.
  465. *
  466. * @param array $array (name => value) array
  467. *
  468. * @return $this
  469. */
  470. public function setQueryVariables(array $array)
  471. {
  472. if (!$array) {
  473. $this->_query = false;
  474. } else {
  475. $this->_query = $this->buildQuery(
  476. $array,
  477. $this->getOption(self::OPTION_SEPARATOR_OUTPUT)
  478. );
  479. }
  480. return $this;
  481. }
  482. /**
  483. * Sets the specified variable in the query string.
  484. *
  485. * @param string $name variable name
  486. * @param mixed $value variable value
  487. *
  488. * @return $this
  489. */
  490. public function setQueryVariable($name, $value)
  491. {
  492. $array = $this->getQueryVariables();
  493. $array[$name] = $value;
  494. $this->setQueryVariables($array);
  495. return $this;
  496. }
  497. /**
  498. * Removes the specifed variable from the query string.
  499. *
  500. * @param string $name a query string variable, e.g. "foo" in "?foo=1"
  501. *
  502. * @return void
  503. */
  504. public function unsetQueryVariable($name)
  505. {
  506. $array = $this->getQueryVariables();
  507. unset($array[$name]);
  508. $this->setQueryVariables($array);
  509. }
  510. /**
  511. * Returns a string representation of this URL.
  512. *
  513. * @return string
  514. */
  515. public function getURL()
  516. {
  517. // See RFC 3986, section 5.3
  518. $url = "";
  519. if ($this->_scheme !== false) {
  520. $url .= $this->_scheme . ':';
  521. }
  522. $authority = $this->getAuthority();
  523. if ($authority !== false) {
  524. $url .= '//' . $authority;
  525. }
  526. $url .= $this->_path;
  527. if ($this->_query !== false) {
  528. $url .= '?' . $this->_query;
  529. }
  530. if ($this->_fragment !== false) {
  531. $url .= '#' . $this->_fragment;
  532. }
  533. return $url;
  534. }
  535. /**
  536. * Returns a string representation of this URL.
  537. *
  538. * @return string
  539. * @see toString()
  540. */
  541. public function __toString()
  542. {
  543. return $this->getURL();
  544. }
  545. /**
  546. * Returns a normalized string representation of this URL. This is useful
  547. * for comparison of URLs.
  548. *
  549. * @return string
  550. */
  551. public function getNormalizedURL()
  552. {
  553. $url = clone $this;
  554. $url->normalize();
  555. return $url->getUrl();
  556. }
  557. /**
  558. * Returns a normalized Net_URL2 instance.
  559. *
  560. * @return Net_URL2
  561. */
  562. public function normalize()
  563. {
  564. // See RFC 3886, section 6
  565. // Schemes are case-insensitive
  566. if ($this->_scheme) {
  567. $this->_scheme = strtolower($this->_scheme);
  568. }
  569. // Hostnames are case-insensitive
  570. if ($this->_host) {
  571. $this->_host = strtolower($this->_host);
  572. }
  573. // Remove default port number for known schemes (RFC 3986, section 6.2.3)
  574. if ($this->_port &&
  575. $this->_scheme &&
  576. $this->_port == getservbyname($this->_scheme, 'tcp')) {
  577. $this->_port = false;
  578. }
  579. // Normalize case of %XX percentage-encodings (RFC 3986, section 6.2.2.1)
  580. foreach (array('_userinfo', '_host', '_path') as $part) {
  581. if ($this->$part) {
  582. $this->$part = preg_replace('/%[0-9a-f]{2}/ie',
  583. 'strtoupper("\0")',
  584. $this->$part);
  585. }
  586. }
  587. // Path segment normalization (RFC 3986, section 6.2.2.3)
  588. $this->_path = self::removeDotSegments($this->_path);
  589. // Scheme based normalization (RFC 3986, section 6.2.3)
  590. if ($this->_host && !$this->_path) {
  591. $this->_path = '/';
  592. }
  593. }
  594. /**
  595. * Returns whether this instance represents an absolute URL.
  596. *
  597. * @return bool
  598. */
  599. public function isAbsolute()
  600. {
  601. return (bool) $this->_scheme;
  602. }
  603. /**
  604. * Returns an Net_URL2 instance representing an absolute URL relative to
  605. * this URL.
  606. *
  607. * @param Net_URL2|string $reference relative URL
  608. *
  609. * @return Net_URL2
  610. */
  611. public function resolve($reference)
  612. {
  613. if (!$reference instanceof Net_URL2) {
  614. $reference = new self($reference);
  615. }
  616. if (!$this->isAbsolute()) {
  617. throw new Exception('Base-URL must be absolute');
  618. }
  619. // A non-strict parser may ignore a scheme in the reference if it is
  620. // identical to the base URI's scheme.
  621. if (!$this->getOption(self::OPTION_STRICT) && $reference->_scheme == $this->_scheme) {
  622. $reference->_scheme = false;
  623. }
  624. $target = new self('');
  625. if ($reference->_scheme !== false) {
  626. $target->_scheme = $reference->_scheme;
  627. $target->setAuthority($reference->getAuthority());
  628. $target->_path = self::removeDotSegments($reference->_path);
  629. $target->_query = $reference->_query;
  630. } else {
  631. $authority = $reference->getAuthority();
  632. if ($authority !== false) {
  633. $target->setAuthority($authority);
  634. $target->_path = self::removeDotSegments($reference->_path);
  635. $target->_query = $reference->_query;
  636. } else {
  637. if ($reference->_path == '') {
  638. $target->_path = $this->_path;
  639. if ($reference->_query !== false) {
  640. $target->_query = $reference->_query;
  641. } else {
  642. $target->_query = $this->_query;
  643. }
  644. } else {
  645. if (substr($reference->_path, 0, 1) == '/') {
  646. $target->_path = self::removeDotSegments($reference->_path);
  647. } else {
  648. // Merge paths (RFC 3986, section 5.2.3)
  649. if ($this->_host !== false && $this->_path == '') {
  650. $target->_path = '/' . $this->_path;
  651. } else {
  652. $i = strrpos($this->_path, '/');
  653. if ($i !== false) {
  654. $target->_path = substr($this->_path, 0, $i + 1);
  655. }
  656. $target->_path .= $reference->_path;
  657. }
  658. $target->_path = self::removeDotSegments($target->_path);
  659. }
  660. $target->_query = $reference->_query;
  661. }
  662. $target->setAuthority($this->getAuthority());
  663. }
  664. $target->_scheme = $this->_scheme;
  665. }
  666. $target->_fragment = $reference->_fragment;
  667. return $target;
  668. }
  669. /**
  670. * Removes dots as described in RFC 3986, section 5.2.4, e.g.
  671. * "/foo/../bar/baz" => "/bar/baz"
  672. *
  673. * @param string $path a path
  674. *
  675. * @return string a path
  676. */
  677. public static function removeDotSegments($path)
  678. {
  679. $output = '';
  680. // Make sure not to be trapped in an infinite loop due to a bug in this
  681. // method
  682. $j = 0;
  683. while ($path && $j++ < 100) {
  684. if (substr($path, 0, 2) == './') {
  685. // Step 2.A
  686. $path = substr($path, 2);
  687. } elseif (substr($path, 0, 3) == '../') {
  688. // Step 2.A
  689. $path = substr($path, 3);
  690. } elseif (substr($path, 0, 3) == '/./' || $path == '/.') {
  691. // Step 2.B
  692. $path = '/' . substr($path, 3);
  693. } elseif (substr($path, 0, 4) == '/../' || $path == '/..') {
  694. // Step 2.C
  695. $path = '/' . substr($path, 4);
  696. $i = strrpos($output, '/');
  697. $output = $i === false ? '' : substr($output, 0, $i);
  698. } elseif ($path == '.' || $path == '..') {
  699. // Step 2.D
  700. $path = '';
  701. } else {
  702. // Step 2.E
  703. $i = strpos($path, '/');
  704. if ($i === 0) {
  705. $i = strpos($path, '/', 1);
  706. }
  707. if ($i === false) {
  708. $i = strlen($path);
  709. }
  710. $output .= substr($path, 0, $i);
  711. $path = substr($path, $i);
  712. }
  713. }
  714. return $output;
  715. }
  716. /**
  717. * Percent-encodes all non-alphanumeric characters except these: _ . - ~
  718. * Similar to PHP's rawurlencode(), except that it also encodes ~ in PHP
  719. * 5.2.x and earlier.
  720. *
  721. * @param $raw the string to encode
  722. * @return string
  723. */
  724. public static function urlencode($string)
  725. {
  726. $encoded = rawurlencode($string);
  727. // This is only necessary in PHP < 5.3.
  728. $encoded = str_replace('%7E', '~', $encoded);
  729. return $encoded;
  730. }
  731. /**
  732. * Returns a Net_URL2 instance representing the canonical URL of the
  733. * currently executing PHP script.
  734. *
  735. * @return string
  736. */
  737. public static function getCanonical()
  738. {
  739. if (!isset($_SERVER['REQUEST_METHOD'])) {
  740. // ALERT - no current URL
  741. throw new Exception('Script was not called through a webserver');
  742. }
  743. // Begin with a relative URL
  744. $url = new self($_SERVER['PHP_SELF']);
  745. $url->_scheme = isset($_SERVER['HTTPS']) ? 'https' : 'http';
  746. $url->_host = $_SERVER['SERVER_NAME'];
  747. $port = $_SERVER['SERVER_PORT'];
  748. if ($url->_scheme == 'http' && $port != 80 ||
  749. $url->_scheme == 'https' && $port != 443) {
  750. $url->_port = $port;
  751. }
  752. return $url;
  753. }
  754. /**
  755. * Returns the URL used to retrieve the current request.
  756. *
  757. * @return string
  758. */
  759. public static function getRequestedURL()
  760. {
  761. return self::getRequested()->getUrl();
  762. }
  763. /**
  764. * Returns a Net_URL2 instance representing the URL used to retrieve the
  765. * current request.
  766. *
  767. * @return Net_URL2
  768. */
  769. public static function getRequested()
  770. {
  771. if (!isset($_SERVER['REQUEST_METHOD'])) {
  772. // ALERT - no current URL
  773. throw new Exception('Script was not called through a webserver');
  774. }
  775. // Begin with a relative URL
  776. $url = new self($_SERVER['REQUEST_URI']);
  777. $url->_scheme = isset($_SERVER['HTTPS']) ? 'https' : 'http';
  778. // Set host and possibly port
  779. $url->setAuthority($_SERVER['HTTP_HOST']);
  780. return $url;
  781. }
  782. /**
  783. * Returns the value of the specified option.
  784. *
  785. * @param string $optionName The name of the option to retrieve
  786. *
  787. * @return mixed
  788. */
  789. public function getOption($optionName)
  790. {
  791. return isset($this->_options[$optionName])
  792. ? $this->_options[$optionName] : false;
  793. }
  794. /**
  795. * A simple version of http_build_query in userland. The encoded string is
  796. * percentage encoded according to RFC 3986.
  797. *
  798. * @param array $data An array, which has to be converted into
  799. * QUERY_STRING. Anything is possible.
  800. * @param string $seperator See {@link self::OPTION_SEPARATOR_OUTPUT}
  801. * @param string $key For stacked values (arrays in an array).
  802. *
  803. * @return string
  804. */
  805. protected function buildQuery(array $data, $separator, $key = null)
  806. {
  807. $query = array();
  808. foreach ($data as $name => $value) {
  809. if ($this->getOption(self::OPTION_ENCODE_KEYS) === true) {
  810. $name = rawurlencode($name);
  811. }
  812. if ($key !== null) {
  813. if ($this->getOption(self::OPTION_USE_BRACKETS) === true) {
  814. $name = $key . '[' . $name . ']';
  815. } else {
  816. $name = $key;
  817. }
  818. }
  819. if (is_array($value)) {
  820. $query[] = $this->buildQuery($value, $separator, $name);
  821. } else {
  822. $query[] = $name . '=' . rawurlencode($value);
  823. }
  824. }
  825. return implode($separator, $query);
  826. }
  827. /**
  828. * This method uses a funky regex to parse the url into the designated parts.
  829. *
  830. * @param string $url
  831. *
  832. * @return void
  833. * @uses self::$_scheme, self::setAuthority(), self::$_path, self::$_query,
  834. * self::$_fragment
  835. * @see self::__construct()
  836. */
  837. protected function parseUrl($url)
  838. {
  839. // The regular expression is copied verbatim from RFC 3986, appendix B.
  840. // The expression does not validate the URL but matches any string.
  841. preg_match('!^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?!',
  842. $url,
  843. $matches);
  844. // "path" is always present (possibly as an empty string); the rest
  845. // are optional.
  846. $this->_scheme = !empty($matches[1]) ? $matches[2] : false;
  847. $this->setAuthority(!empty($matches[3]) ? $matches[4] : false);
  848. $this->_path = $matches[5];
  849. $this->_query = !empty($matches[6]) ? $matches[7] : false;
  850. $this->_fragment = !empty($matches[8]) ? $matches[9] : false;
  851. }
  852. }