Http.php 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726
  1. <?php
  2. /**
  3. * Zend Framework
  4. *
  5. * LICENSE
  6. *
  7. * This source file is subject to the new BSD license that is bundled
  8. * with this package in the file LICENSE.txt.
  9. * It is also available through the world-wide-web at this URL:
  10. * http://framework.zend.com/license/new-bsd
  11. * If you did not receive a copy of the license and are unable to
  12. * obtain it through the world-wide-web, please send an email
  13. * to license@zend.com so we can send you a copy immediately.
  14. *
  15. * @category Zend
  16. * @package Zend_Uri
  17. * @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com)
  18. * @license http://framework.zend.com/license/new-bsd New BSD License
  19. * @version $Id$
  20. */
  21. /**
  22. * @see Zend_Uri
  23. */
  24. require_once 'Zend/Uri.php';
  25. /**
  26. * @see Zend_Validate_Hostname
  27. */
  28. require_once 'Zend/Validate/Hostname.php';
  29. /**
  30. * HTTP(S) URI handler
  31. *
  32. * @category Zend
  33. * @package Zend_Uri
  34. * @uses Zend_Uri
  35. * @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com)
  36. * @license http://framework.zend.com/license/new-bsd New BSD License
  37. */
  38. class Zend_Uri_Http extends Zend_Uri
  39. {
  40. /**
  41. * Character classes for validation regular expressions
  42. */
  43. const CHAR_ALNUM = 'A-Za-z0-9';
  44. const CHAR_MARK = '-_.!~*\'()\[\]';
  45. const CHAR_RESERVED = ';\/?:@&=+$,';
  46. const CHAR_SEGMENT = ':@&=+$,;';
  47. const CHAR_UNWISE = '{}|\\\\^`';
  48. /**
  49. * HTTP username
  50. *
  51. * @var string
  52. */
  53. protected $_username = '';
  54. /**
  55. * HTTP password
  56. *
  57. * @var string
  58. */
  59. protected $_password = '';
  60. /**
  61. * HTTP host
  62. *
  63. * @var string
  64. */
  65. protected $_host = '';
  66. /**
  67. * HTTP post
  68. *
  69. * @var string
  70. */
  71. protected $_port = '';
  72. /**
  73. * HTTP part
  74. *
  75. * @var string
  76. */
  77. protected $_path = '';
  78. /**
  79. * HTTP query
  80. *
  81. * @var string
  82. */
  83. protected $_query = '';
  84. /**
  85. * HTTP fragment
  86. *
  87. * @var string
  88. */
  89. protected $_fragment = '';
  90. /**
  91. * Regular expression grammar rules for validation; values added by constructor
  92. *
  93. * @var array
  94. */
  95. protected $_regex = array();
  96. /**
  97. * Constructor accepts a string $scheme (e.g., http, https) and a scheme-specific part of the URI
  98. * (e.g., example.com/path/to/resource?query=param#fragment)
  99. *
  100. * @param string $scheme The scheme of the URI
  101. * @param string $schemeSpecific The scheme-specific part of the URI
  102. * @throws Zend_Uri_Exception When the URI is not valid
  103. */
  104. protected function __construct($scheme, $schemeSpecific = '')
  105. {
  106. // Set the scheme
  107. $this->_scheme = $scheme;
  108. // Set up grammar rules for validation via regular expressions. These
  109. // are to be used with slash-delimited regular expression strings.
  110. // Escaped special characters (eg. '%25' for '%')
  111. $this->_regex['escaped'] = '%[[:xdigit:]]{2}';
  112. // Unreserved characters
  113. $this->_regex['unreserved'] = '[' . self::CHAR_ALNUM . self::CHAR_MARK . ']';
  114. // Segment can use escaped, unreserved or a set of additional chars
  115. $this->_regex['segment'] = '(?:' . $this->_regex['escaped'] . '|[' .
  116. self::CHAR_ALNUM . self::CHAR_MARK . self::CHAR_SEGMENT . '])*';
  117. // Path can be a series of segmets char strings seperated by '/'
  118. $this->_regex['path'] = '(?:\/(?:' . $this->_regex['segment'] . ')?)+';
  119. // URI characters can be escaped, alphanumeric, mark or reserved chars
  120. $this->_regex['uric'] = '(?:' . $this->_regex['escaped'] . '|[' .
  121. self::CHAR_ALNUM . self::CHAR_MARK . self::CHAR_RESERVED .
  122. // If unwise chars are allowed, add them to the URI chars class
  123. (self::$_config['allow_unwise'] ? self::CHAR_UNWISE : '') . '])';
  124. // If no scheme-specific part was supplied, the user intends to create
  125. // a new URI with this object. No further parsing is required.
  126. if (strlen($schemeSpecific) === 0) {
  127. return;
  128. }
  129. // Parse the scheme-specific URI parts into the instance variables.
  130. $this->_parseUri($schemeSpecific);
  131. // Validate the URI
  132. if ($this->valid() === false) {
  133. require_once 'Zend/Uri/Exception.php';
  134. throw new Zend_Uri_Exception('Invalid URI supplied');
  135. }
  136. }
  137. /**
  138. * Creates a Zend_Uri_Http from the given string
  139. *
  140. * @param string $uri String to create URI from, must start with
  141. * 'http://' or 'https://'
  142. * @throws InvalidArgumentException When the given $uri is not a string or
  143. * does not start with http:// or https://
  144. * @throws Zend_Uri_Exception When the given $uri is invalid
  145. * @return Zend_Uri_Http
  146. */
  147. public static function fromString($uri)
  148. {
  149. if (is_string($uri) === false) {
  150. require_once 'Zend/Uri/Exception.php';
  151. throw new Zend_Uri_Exception('$uri is not a string');
  152. }
  153. $uri = explode(':', $uri, 2);
  154. $scheme = strtolower($uri[0]);
  155. $schemeSpecific = isset($uri[1]) === true ? $uri[1] : '';
  156. if (in_array($scheme, array('http', 'https')) === false) {
  157. require_once 'Zend/Uri/Exception.php';
  158. throw new Zend_Uri_Exception("Invalid scheme: '$scheme'");
  159. }
  160. $schemeHandler = new Zend_Uri_Http($scheme, $schemeSpecific);
  161. return $schemeHandler;
  162. }
  163. /**
  164. * Parse the scheme-specific portion of the URI and place its parts into instance variables.
  165. *
  166. * @param string $schemeSpecific The scheme-specific portion to parse
  167. * @throws Zend_Uri_Exception When scheme-specific decoposition fails
  168. * @throws Zend_Uri_Exception When authority decomposition fails
  169. * @return void
  170. */
  171. protected function _parseUri($schemeSpecific)
  172. {
  173. // High-level decomposition parser
  174. $pattern = '~^((//)([^/?#]*))([^?#]*)(\?([^#]*))?(#(.*))?$~';
  175. $status = @preg_match($pattern, $schemeSpecific, $matches);
  176. if ($status === false) {
  177. require_once 'Zend/Uri/Exception.php';
  178. throw new Zend_Uri_Exception('Internal error: scheme-specific decomposition failed');
  179. }
  180. // Failed decomposition; no further processing needed
  181. if ($status === false) {
  182. return;
  183. }
  184. // Save URI components that need no further decomposition
  185. $this->_path = isset($matches[4]) === true ? $matches[4] : '';
  186. $this->_query = isset($matches[6]) === true ? $matches[6] : '';
  187. $this->_fragment = isset($matches[8]) === true ? $matches[8] : '';
  188. // Additional decomposition to get username, password, host, and port
  189. $combo = isset($matches[3]) === true ? $matches[3] : '';
  190. $pattern = '~^(([^:@]*)(:([^@]*))?@)?([^:]+)(:(.*))?$~';
  191. $status = @preg_match($pattern, $combo, $matches);
  192. if ($status === false) {
  193. require_once 'Zend/Uri/Exception.php';
  194. throw new Zend_Uri_Exception('Internal error: authority decomposition failed');
  195. }
  196. // Failed decomposition; no further processing needed
  197. if ($status === false) {
  198. return;
  199. }
  200. // Save remaining URI components
  201. $this->_username = isset($matches[2]) === true ? $matches[2] : '';
  202. $this->_password = isset($matches[4]) === true ? $matches[4] : '';
  203. $this->_host = isset($matches[5]) === true ? $matches[5] : '';
  204. $this->_port = isset($matches[7]) === true ? $matches[7] : '';
  205. }
  206. /**
  207. * Returns a URI based on current values of the instance variables. If any
  208. * part of the URI does not pass validation, then an exception is thrown.
  209. *
  210. * @throws Zend_Uri_Exception When one or more parts of the URI are invalid
  211. * @return string
  212. */
  213. public function getUri()
  214. {
  215. if ($this->valid() === false) {
  216. require_once 'Zend/Uri/Exception.php';
  217. throw new Zend_Uri_Exception('One or more parts of the URI are invalid');
  218. }
  219. $password = strlen($this->_password) > 0 ? ":$this->_password" : '';
  220. $auth = strlen($this->_username) > 0 ? "$this->_username$password@" : '';
  221. $port = strlen($this->_port) > 0 ? ":$this->_port" : '';
  222. $query = strlen($this->_query) > 0 ? "?$this->_query" : '';
  223. $fragment = strlen($this->_fragment) > 0 ? "#$this->_fragment" : '';
  224. return $this->_scheme
  225. . '://'
  226. . $auth
  227. . $this->_host
  228. . $port
  229. . $this->_path
  230. . $query
  231. . $fragment;
  232. }
  233. /**
  234. * Validate the current URI from the instance variables. Returns true if and only if all
  235. * parts pass validation.
  236. *
  237. * @return boolean
  238. */
  239. public function valid()
  240. {
  241. // Return true if and only if all parts of the URI have passed validation
  242. return $this->validateUsername()
  243. and $this->validatePassword()
  244. and $this->validateHost()
  245. and $this->validatePort()
  246. and $this->validatePath()
  247. and $this->validateQuery()
  248. and $this->validateFragment();
  249. }
  250. /**
  251. * Returns the username portion of the URL, or FALSE if none.
  252. *
  253. * @return string
  254. */
  255. public function getUsername()
  256. {
  257. return strlen($this->_username) > 0 ? $this->_username : false;
  258. }
  259. /**
  260. * Returns true if and only if the username passes validation. If no username is passed,
  261. * then the username contained in the instance variable is used.
  262. *
  263. * @param string $username The HTTP username
  264. * @throws Zend_Uri_Exception When username validation fails
  265. * @return boolean
  266. * @link http://www.faqs.org/rfcs/rfc2396.html
  267. */
  268. public function validateUsername($username = null)
  269. {
  270. if ($username === null) {
  271. $username = $this->_username;
  272. }
  273. // If the username is empty, then it is considered valid
  274. if (strlen($username) === 0) {
  275. return true;
  276. }
  277. // Check the username against the allowed values
  278. $status = @preg_match('/^(?:' . $this->_regex['escaped'] . '|[' .
  279. self::CHAR_ALNUM . self::CHAR_MARK . ';:&=+$,' . '])+$/', $username);
  280. if ($status === false) {
  281. require_once 'Zend/Uri/Exception.php';
  282. throw new Zend_Uri_Exception('Internal error: username validation failed');
  283. }
  284. return $status === 1;
  285. }
  286. /**
  287. * Sets the username for the current URI, and returns the old username
  288. *
  289. * @param string $username The HTTP username
  290. * @throws Zend_Uri_Exception When $username is not a valid HTTP username
  291. * @return string
  292. */
  293. public function setUsername($username)
  294. {
  295. if ($this->validateUsername($username) === false) {
  296. require_once 'Zend/Uri/Exception.php';
  297. throw new Zend_Uri_Exception("Username \"$username\" is not a valid HTTP username");
  298. }
  299. $oldUsername = $this->_username;
  300. $this->_username = $username;
  301. return $oldUsername;
  302. }
  303. /**
  304. * Returns the password portion of the URL, or FALSE if none.
  305. *
  306. * @return string
  307. */
  308. public function getPassword()
  309. {
  310. return strlen($this->_password) > 0 ? $this->_password : false;
  311. }
  312. /**
  313. * Returns true if and only if the password passes validation. If no password is passed,
  314. * then the password contained in the instance variable is used.
  315. *
  316. * @param string $password The HTTP password
  317. * @throws Zend_Uri_Exception When password validation fails
  318. * @return boolean
  319. * @link http://www.faqs.org/rfcs/rfc2396.html
  320. */
  321. public function validatePassword($password = null)
  322. {
  323. if ($password === null) {
  324. $password = $this->_password;
  325. }
  326. // If the password is empty, then it is considered valid
  327. if (strlen($password) === 0) {
  328. return true;
  329. }
  330. // If the password is nonempty, but there is no username, then it is considered invalid
  331. if (strlen($password) > 0 and strlen($this->_username) === 0) {
  332. return false;
  333. }
  334. // Check the password against the allowed values
  335. $status = @preg_match('/^(?:' . $this->_regex['escaped'] . '|[' .
  336. self::CHAR_ALNUM . self::CHAR_MARK . ';:&=+$,' . '])+$/', $password);
  337. if ($status === false) {
  338. require_once 'Zend/Uri/Exception.php';
  339. throw new Zend_Uri_Exception('Internal error: password validation failed.');
  340. }
  341. return $status == 1;
  342. }
  343. /**
  344. * Sets the password for the current URI, and returns the old password
  345. *
  346. * @param string $password The HTTP password
  347. * @throws Zend_Uri_Exception When $password is not a valid HTTP password
  348. * @return string
  349. */
  350. public function setPassword($password)
  351. {
  352. if ($this->validatePassword($password) === false) {
  353. require_once 'Zend/Uri/Exception.php';
  354. throw new Zend_Uri_Exception("Password \"$password\" is not a valid HTTP password.");
  355. }
  356. $oldPassword = $this->_password;
  357. $this->_password = $password;
  358. return $oldPassword;
  359. }
  360. /**
  361. * Returns the domain or host IP portion of the URL, or FALSE if none.
  362. *
  363. * @return string
  364. */
  365. public function getHost()
  366. {
  367. return strlen($this->_host) > 0 ? $this->_host : false;
  368. }
  369. /**
  370. * Returns true if and only if the host string passes validation. If no host is passed,
  371. * then the host contained in the instance variable is used.
  372. *
  373. * @param string $host The HTTP host
  374. * @return boolean
  375. * @uses Zend_Filter
  376. */
  377. public function validateHost($host = null)
  378. {
  379. if ($host === null) {
  380. $host = $this->_host;
  381. }
  382. // If the host is empty, then it is considered invalid
  383. if (strlen($host) === 0) {
  384. return false;
  385. }
  386. // Check the host against the allowed values; delegated to Zend_Filter.
  387. $validate = new Zend_Validate_Hostname(Zend_Validate_Hostname::ALLOW_ALL);
  388. return $validate->isValid($host);
  389. }
  390. /**
  391. * Sets the host for the current URI, and returns the old host
  392. *
  393. * @param string $host The HTTP host
  394. * @throws Zend_Uri_Exception When $host is nota valid HTTP host
  395. * @return string
  396. */
  397. public function setHost($host)
  398. {
  399. if ($this->validateHost($host) === false) {
  400. require_once 'Zend/Uri/Exception.php';
  401. throw new Zend_Uri_Exception("Host \"$host\" is not a valid HTTP host");
  402. }
  403. $oldHost = $this->_host;
  404. $this->_host = $host;
  405. return $oldHost;
  406. }
  407. /**
  408. * Returns the TCP port, or FALSE if none.
  409. *
  410. * @return string
  411. */
  412. public function getPort()
  413. {
  414. return strlen($this->_port) > 0 ? $this->_port : false;
  415. }
  416. /**
  417. * Returns true if and only if the TCP port string passes validation. If no port is passed,
  418. * then the port contained in the instance variable is used.
  419. *
  420. * @param string $port The HTTP port
  421. * @return boolean
  422. */
  423. public function validatePort($port = null)
  424. {
  425. if ($port === null) {
  426. $port = $this->_port;
  427. }
  428. // If the port is empty, then it is considered valid
  429. if (strlen($port) === 0) {
  430. return true;
  431. }
  432. // Check the port against the allowed values
  433. return ctype_digit((string) $port) and 1 <= $port and $port <= 65535;
  434. }
  435. /**
  436. * Sets the port for the current URI, and returns the old port
  437. *
  438. * @param string $port The HTTP port
  439. * @throws Zend_Uri_Exception When $port is not a valid HTTP port
  440. * @return string
  441. */
  442. public function setPort($port)
  443. {
  444. if ($this->validatePort($port) === false) {
  445. require_once 'Zend/Uri/Exception.php';
  446. throw new Zend_Uri_Exception("Port \"$port\" is not a valid HTTP port.");
  447. }
  448. $oldPort = $this->_port;
  449. $this->_port = $port;
  450. return $oldPort;
  451. }
  452. /**
  453. * Returns the path and filename portion of the URL, or FALSE if none.
  454. *
  455. * @return string
  456. */
  457. public function getPath()
  458. {
  459. return strlen($this->_path) > 0 ? $this->_path : '/';
  460. }
  461. /**
  462. * Returns true if and only if the path string passes validation. If no path is passed,
  463. * then the path contained in the instance variable is used.
  464. *
  465. * @param string $path The HTTP path
  466. * @throws Zend_Uri_Exception When path validation fails
  467. * @return boolean
  468. */
  469. public function validatePath($path = null)
  470. {
  471. if ($path === null) {
  472. $path = $this->_path;
  473. }
  474. // If the path is empty, then it is considered valid
  475. if (strlen($path) === 0) {
  476. return true;
  477. }
  478. // Determine whether the path is well-formed
  479. $pattern = '/^' . $this->_regex['path'] . '$/';
  480. $status = @preg_match($pattern, $path);
  481. if ($status === false) {
  482. require_once 'Zend/Uri/Exception.php';
  483. throw new Zend_Uri_Exception('Internal error: path validation failed');
  484. }
  485. return (boolean) $status;
  486. }
  487. /**
  488. * Sets the path for the current URI, and returns the old path
  489. *
  490. * @param string $path The HTTP path
  491. * @throws Zend_Uri_Exception When $path is not a valid HTTP path
  492. * @return string
  493. */
  494. public function setPath($path)
  495. {
  496. if ($this->validatePath($path) === false) {
  497. require_once 'Zend/Uri/Exception.php';
  498. throw new Zend_Uri_Exception("Path \"$path\" is not a valid HTTP path");
  499. }
  500. $oldPath = $this->_path;
  501. $this->_path = $path;
  502. return $oldPath;
  503. }
  504. /**
  505. * Returns the query portion of the URL (after ?), or FALSE if none.
  506. *
  507. * @return string
  508. */
  509. public function getQuery()
  510. {
  511. return strlen($this->_query) > 0 ? $this->_query : false;
  512. }
  513. /**
  514. * Returns true if and only if the query string passes validation. If no query is passed,
  515. * then the query string contained in the instance variable is used.
  516. *
  517. * @param string $query The query to validate
  518. * @throws Zend_Uri_Exception When query validation fails
  519. * @return boolean
  520. * @link http://www.faqs.org/rfcs/rfc2396.html
  521. */
  522. public function validateQuery($query = null)
  523. {
  524. if ($query === null) {
  525. $query = $this->_query;
  526. }
  527. // If query is empty, it is considered to be valid
  528. if (strlen($query) === 0) {
  529. return true;
  530. }
  531. // Determine whether the query is well-formed
  532. $pattern = '/^' . $this->_regex['uric'] . '*$/';
  533. $status = @preg_match($pattern, $query);
  534. if ($status === false) {
  535. require_once 'Zend/Uri/Exception.php';
  536. throw new Zend_Uri_Exception('Internal error: query validation failed');
  537. }
  538. return $status == 1;
  539. }
  540. /**
  541. * Set the query string for the current URI, and return the old query
  542. * string This method accepts both strings and arrays.
  543. *
  544. * @param string|array $query The query string or array
  545. * @throws Zend_Uri_Exception When $query is not a valid query string
  546. * @return string Old query string
  547. */
  548. public function setQuery($query)
  549. {
  550. $oldQuery = $this->_query;
  551. // If query is empty, set an empty string
  552. if (empty($query) === true) {
  553. $this->_query = '';
  554. return $oldQuery;
  555. }
  556. // If query is an array, make a string out of it
  557. if (is_array($query) === true) {
  558. $query = http_build_query($query, '', '&');
  559. } else {
  560. // If it is a string, make sure it is valid. If not parse and encode it
  561. $query = (string) $query;
  562. if ($this->validateQuery($query) === false) {
  563. parse_str($query, $queryArray);
  564. $query = http_build_query($queryArray, '', '&');
  565. }
  566. }
  567. // Make sure the query is valid, and set it
  568. if ($this->validateQuery($query) === false) {
  569. require_once 'Zend/Uri/Exception.php';
  570. throw new Zend_Uri_Exception("'$query' is not a valid query string");
  571. }
  572. $this->_query = $query;
  573. return $oldQuery;
  574. }
  575. /**
  576. * Returns the fragment portion of the URL (after #), or FALSE if none.
  577. *
  578. * @return string|false
  579. */
  580. public function getFragment()
  581. {
  582. return strlen($this->_fragment) > 0 ? $this->_fragment : false;
  583. }
  584. /**
  585. * Returns true if and only if the fragment passes validation. If no fragment is passed,
  586. * then the fragment contained in the instance variable is used.
  587. *
  588. * @param string $fragment Fragment of an URI
  589. * @throws Zend_Uri_Exception When fragment validation fails
  590. * @return boolean
  591. * @link http://www.faqs.org/rfcs/rfc2396.html
  592. */
  593. public function validateFragment($fragment = null)
  594. {
  595. if ($fragment === null) {
  596. $fragment = $this->_fragment;
  597. }
  598. // If fragment is empty, it is considered to be valid
  599. if (strlen($fragment) === 0) {
  600. return true;
  601. }
  602. // Determine whether the fragment is well-formed
  603. $pattern = '/^' . $this->_regex['uric'] . '*$/';
  604. $status = @preg_match($pattern, $fragment);
  605. if ($status === false) {
  606. require_once 'Zend/Uri/Exception.php';
  607. throw new Zend_Uri_Exception('Internal error: fragment validation failed');
  608. }
  609. return (boolean) $status;
  610. }
  611. /**
  612. * Sets the fragment for the current URI, and returns the old fragment
  613. *
  614. * @param string $fragment Fragment of the current URI
  615. * @throws Zend_Uri_Exception When $fragment is not a valid HTTP fragment
  616. * @return string
  617. */
  618. public function setFragment($fragment)
  619. {
  620. if ($this->validateFragment($fragment) === false) {
  621. require_once 'Zend/Uri/Exception.php';
  622. throw new Zend_Uri_Exception("Fragment \"$fragment\" is not a valid HTTP fragment");
  623. }
  624. $oldFragment = $this->_fragment;
  625. $this->_fragment = $fragment;
  626. return $oldFragment;
  627. }
  628. }