PageRenderTime 51ms CodeModel.GetById 22ms RepoModel.GetById 1ms app.codeStats 0ms

/wp-content/plugins/w3-total-cache/lib/Microsoft/Http/Response.php

https://bitbucket.org/mrmustarde/manhattan-beach
PHP | 664 lines | 361 code | 70 blank | 233 comment | 45 complexity | 8d8b86a25bf2c8b1333d00265b1dc6b0 MD5 | raw file
Possible License(s): BSD-3-Clause
  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 Microsoft
  16. * @package Microsoft_Http
  17. * @subpackage Response
  18. * @version $Id: Response.php 35835 2009-12-17 09:40:36Z unknown $
  19. * @copyright Copyright (c) 2005-2009 Zend Technologies USA Inc. (http://www.zend.com)
  20. * @license http://framework.zend.com/license/new-bsd New BSD License
  21. */
  22. /**
  23. * Microsoft_Http_Response represents an HTTP 1.0 / 1.1 response message. It
  24. * includes easy access to all the response's different elemts, as well as some
  25. * convenience methods for parsing and validating HTTP responses.
  26. *
  27. * @package Microsoft_Http
  28. * @subpackage Response
  29. * @copyright Copyright (c) 2005-2009 Zend Technologies USA Inc. (http://www.zend.com)
  30. * @license http://framework.zend.com/license/new-bsd New BSD License
  31. */
  32. class Microsoft_Http_Response
  33. {
  34. /**
  35. * List of all known HTTP response codes - used by responseCodeAsText() to
  36. * translate numeric codes to messages.
  37. *
  38. * @var array
  39. */
  40. protected static $messages = array(
  41. // Informational 1xx
  42. 100 => 'Continue',
  43. 101 => 'Switching Protocols',
  44. // Success 2xx
  45. 200 => 'OK',
  46. 201 => 'Created',
  47. 202 => 'Accepted',
  48. 203 => 'Non-Authoritative Information',
  49. 204 => 'No Content',
  50. 205 => 'Reset Content',
  51. 206 => 'Partial Content',
  52. // Redirection 3xx
  53. 300 => 'Multiple Choices',
  54. 301 => 'Moved Permanently',
  55. 302 => 'Found', // 1.1
  56. 303 => 'See Other',
  57. 304 => 'Not Modified',
  58. 305 => 'Use Proxy',
  59. // 306 is deprecated but reserved
  60. 307 => 'Temporary Redirect',
  61. // Client Error 4xx
  62. 400 => 'Bad Request',
  63. 401 => 'Unauthorized',
  64. 402 => 'Payment Required',
  65. 403 => 'Forbidden',
  66. 404 => 'Not Found',
  67. 405 => 'Method Not Allowed',
  68. 406 => 'Not Acceptable',
  69. 407 => 'Proxy Authentication Required',
  70. 408 => 'Request Timeout',
  71. 409 => 'Conflict',
  72. 410 => 'Gone',
  73. 411 => 'Length Required',
  74. 412 => 'Precondition Failed',
  75. 413 => 'Request Entity Too Large',
  76. 414 => 'Request-URI Too Long',
  77. 415 => 'Unsupported Media Type',
  78. 416 => 'Requested Range Not Satisfiable',
  79. 417 => 'Expectation Failed',
  80. // Server Error 5xx
  81. 500 => 'Internal Server Error',
  82. 501 => 'Not Implemented',
  83. 502 => 'Bad Gateway',
  84. 503 => 'Service Unavailable',
  85. 504 => 'Gateway Timeout',
  86. 505 => 'HTTP Version Not Supported',
  87. 509 => 'Bandwidth Limit Exceeded'
  88. );
  89. /**
  90. * The HTTP version (1.0, 1.1)
  91. *
  92. * @var string
  93. */
  94. protected $version;
  95. /**
  96. * The HTTP response code
  97. *
  98. * @var int
  99. */
  100. protected $code;
  101. /**
  102. * The HTTP response code as string
  103. * (e.g. 'Not Found' for 404 or 'Internal Server Error' for 500)
  104. *
  105. * @var string
  106. */
  107. protected $message;
  108. /**
  109. * The HTTP response headers array
  110. *
  111. * @var array
  112. */
  113. protected $headers = array();
  114. /**
  115. * The HTTP response body
  116. *
  117. * @var string
  118. */
  119. protected $body;
  120. /**
  121. * HTTP response constructor
  122. *
  123. * In most cases, you would use Microsoft_Http_Response::fromString to parse an HTTP
  124. * response string and create a new Microsoft_Http_Response object.
  125. *
  126. * NOTE: The constructor no longer accepts nulls or empty values for the code and
  127. * headers and will throw an exception if the passed values do not form a valid HTTP
  128. * responses.
  129. *
  130. * If no message is passed, the message will be guessed according to the response code.
  131. *
  132. * @param int $code Response code (200, 404, ...)
  133. * @param array $headers Headers array
  134. * @param string $body Response body
  135. * @param string $version HTTP version
  136. * @param string $message Response code as text
  137. * @throws Microsoft_Http_Exception
  138. */
  139. public function __construct($code, $headers, $body = null, $version = '1.1', $message = null)
  140. {
  141. // Make sure the response code is valid and set it
  142. if (self::responseCodeAsText($code) === null) {
  143. require_once 'Microsoft/Http/Exception.php';
  144. throw new Microsoft_Http_Exception("{$code} is not a valid HTTP response code");
  145. }
  146. $this->code = $code;
  147. // Make sure we got valid headers and set them
  148. if (! is_array($headers)) {
  149. require_once 'Microsoft/Http/Exception.php';
  150. throw new Microsoft_Http_Exception('No valid headers were passed');
  151. }
  152. foreach ($headers as $name => $value) {
  153. if (is_int($name))
  154. list($name, $value) = explode(": ", $value, 1);
  155. $this->headers[ucwords(strtolower($name))] = $value;
  156. }
  157. // Set the body
  158. $this->body = $body;
  159. // Set the HTTP version
  160. if (! preg_match('|^\d\.\d$|', $version)) {
  161. require_once 'Microsoft/Http/Exception.php';
  162. throw new Microsoft_Http_Exception("Invalid HTTP response version: $version");
  163. }
  164. $this->version = $version;
  165. // If we got the response message, set it. Else, set it according to
  166. // the response code
  167. if (is_string($message)) {
  168. $this->message = $message;
  169. } else {
  170. $this->message = self::responseCodeAsText($code);
  171. }
  172. }
  173. /**
  174. * Check whether the response is an error
  175. *
  176. * @return boolean
  177. */
  178. public function isError()
  179. {
  180. $restype = floor($this->code / 100);
  181. if ($restype == 4 || $restype == 5) {
  182. return true;
  183. }
  184. return false;
  185. }
  186. /**
  187. * Check whether the response in successful
  188. *
  189. * @return boolean
  190. */
  191. public function isSuccessful()
  192. {
  193. $restype = floor($this->code / 100);
  194. if ($restype == 2 || $restype == 1) { // Shouldn't 3xx count as success as well ???
  195. return true;
  196. }
  197. return false;
  198. }
  199. /**
  200. * Check whether the response is a redirection
  201. *
  202. * @return boolean
  203. */
  204. public function isRedirect()
  205. {
  206. $restype = floor($this->code / 100);
  207. if ($restype == 3) {
  208. return true;
  209. }
  210. return false;
  211. }
  212. /**
  213. * Get the response body as string
  214. *
  215. * This method returns the body of the HTTP response (the content), as it
  216. * should be in it's readable version - that is, after decoding it (if it
  217. * was decoded), deflating it (if it was gzip compressed), etc.
  218. *
  219. * If you want to get the raw body (as transfered on wire) use
  220. * $this->getRawBody() instead.
  221. *
  222. * @return string
  223. */
  224. public function getBody()
  225. {
  226. $body = '';
  227. // Decode the body if it was transfer-encoded
  228. switch (strtolower($this->getHeader('transfer-encoding'))) {
  229. // Handle chunked body
  230. case 'chunked':
  231. $body = self::decodeChunkedBody($this->body);
  232. break;
  233. // No transfer encoding, or unknown encoding extension:
  234. // return body as is
  235. default:
  236. $body = $this->body;
  237. break;
  238. }
  239. // Decode any content-encoding (gzip or deflate) if needed
  240. switch (strtolower($this->getHeader('content-encoding'))) {
  241. // Handle gzip encoding
  242. case 'gzip':
  243. $body = self::decodeGzip($body);
  244. break;
  245. // Handle deflate encoding
  246. case 'deflate':
  247. $body = self::decodeDeflate($body);
  248. break;
  249. default:
  250. break;
  251. }
  252. return $body;
  253. }
  254. /**
  255. * Get the raw response body (as transfered "on wire") as string
  256. *
  257. * If the body is encoded (with Transfer-Encoding, not content-encoding -
  258. * IE "chunked" body), gzip compressed, etc. it will not be decoded.
  259. *
  260. * @return string
  261. */
  262. public function getRawBody()
  263. {
  264. return $this->body;
  265. }
  266. /**
  267. * Get the HTTP version of the response
  268. *
  269. * @return string
  270. */
  271. public function getVersion()
  272. {
  273. return $this->version;
  274. }
  275. /**
  276. * Get the HTTP response status code
  277. *
  278. * @return int
  279. */
  280. public function getStatus()
  281. {
  282. return $this->code;
  283. }
  284. /**
  285. * Return a message describing the HTTP response code
  286. * (Eg. "OK", "Not Found", "Moved Permanently")
  287. *
  288. * @return string
  289. */
  290. public function getMessage()
  291. {
  292. return $this->message;
  293. }
  294. /**
  295. * Get the response headers
  296. *
  297. * @return array
  298. */
  299. public function getHeaders()
  300. {
  301. return $this->headers;
  302. }
  303. /**
  304. * Get a specific header as string, or null if it is not set
  305. *
  306. * @param string$header
  307. * @return string|array|null
  308. */
  309. public function getHeader($header)
  310. {
  311. $header = ucwords(strtolower($header));
  312. if (! is_string($header) || ! isset($this->headers[$header])) return null;
  313. return $this->headers[$header];
  314. }
  315. /**
  316. * Get all headers as string
  317. *
  318. * @param boolean $status_line Whether to return the first status line (IE "HTTP 200 OK")
  319. * @param string $br Line breaks (eg. "\n", "\r\n", "<br />")
  320. * @return string
  321. */
  322. public function getHeadersAsString($status_line = true, $br = "\n")
  323. {
  324. $str = '';
  325. if ($status_line) {
  326. $str = "HTTP/{$this->version} {$this->code} {$this->message}{$br}";
  327. }
  328. // Iterate over the headers and stringify them
  329. foreach ($this->headers as $name => $value)
  330. {
  331. if (is_string($value))
  332. $str .= "{$name}: {$value}{$br}";
  333. elseif (is_array($value)) {
  334. foreach ($value as $subval) {
  335. $str .= "{$name}: {$subval}{$br}";
  336. }
  337. }
  338. }
  339. return $str;
  340. }
  341. /**
  342. * Get the entire response as string
  343. *
  344. * @param string $br Line breaks (eg. "\n", "\r\n", "<br />")
  345. * @return string
  346. */
  347. public function asString($br = "\n")
  348. {
  349. return $this->getHeadersAsString(true, $br) . $br . $this->getRawBody();
  350. }
  351. /**
  352. * Implements magic __toString()
  353. *
  354. * @return string
  355. */
  356. public function __toString()
  357. {
  358. return $this->asString();
  359. }
  360. /**
  361. * A convenience function that returns a text representation of
  362. * HTTP response codes. Returns 'Unknown' for unknown codes.
  363. * Returns array of all codes, if $code is not specified.
  364. *
  365. * Conforms to HTTP/1.1 as defined in RFC 2616 (except for 'Unknown')
  366. * See http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10 for reference
  367. *
  368. * @param int $code HTTP response code
  369. * @param boolean $http11 Use HTTP version 1.1
  370. * @return string
  371. */
  372. public static function responseCodeAsText($code = null, $http11 = true)
  373. {
  374. $messages = self::$messages;
  375. if (! $http11) $messages[302] = 'Moved Temporarily';
  376. if ($code === null) {
  377. return $messages;
  378. } elseif (isset($messages[$code])) {
  379. return $messages[$code];
  380. } else {
  381. return 'Unknown';
  382. }
  383. }
  384. /**
  385. * Extract the response code from a response string
  386. *
  387. * @param string $response_str
  388. * @return int
  389. */
  390. public static function extractCode($response_str)
  391. {
  392. preg_match("|^HTTP/[\d\.x]+ (\d+)|", $response_str, $m);
  393. if (isset($m[1])) {
  394. return (int) $m[1];
  395. } else {
  396. return false;
  397. }
  398. }
  399. /**
  400. * Extract the HTTP message from a response
  401. *
  402. * @param string $response_str
  403. * @return string
  404. */
  405. public static function extractMessage($response_str)
  406. {
  407. preg_match("|^HTTP/[\d\.x]+ \d+ ([^\r\n]+)|", $response_str, $m);
  408. if (isset($m[1])) {
  409. return $m[1];
  410. } else {
  411. return false;
  412. }
  413. }
  414. /**
  415. * Extract the HTTP version from a response
  416. *
  417. * @param string $response_str
  418. * @return string
  419. */
  420. public static function extractVersion($response_str)
  421. {
  422. preg_match("|^HTTP/([\d\.x]+) \d+|", $response_str, $m);
  423. if (isset($m[1])) {
  424. return $m[1];
  425. } else {
  426. return false;
  427. }
  428. }
  429. /**
  430. * Extract the headers from a response string
  431. *
  432. * @param string $response_str
  433. * @return array
  434. */
  435. public static function extractHeaders($response_str)
  436. {
  437. $headers = array();
  438. // First, split body and headers
  439. $parts = preg_split('|(?:\r?\n){2}|m', $response_str, 2);
  440. if (! $parts[0]) return $headers;
  441. // Split headers part to lines
  442. $lines = explode("\n", $parts[0]);
  443. unset($parts);
  444. $last_header = null;
  445. foreach($lines as $line) {
  446. $line = trim($line, "\r\n");
  447. if ($line == "") break;
  448. if (preg_match("|^([\w-]+):\s+(.+)|", $line, $m)) {
  449. unset($last_header);
  450. $h_name = strtolower($m[1]);
  451. $h_value = $m[2];
  452. if (isset($headers[$h_name])) {
  453. if (! is_array($headers[$h_name])) {
  454. $headers[$h_name] = array($headers[$h_name]);
  455. }
  456. $headers[$h_name][] = $h_value;
  457. } else {
  458. $headers[$h_name] = $h_value;
  459. }
  460. $last_header = $h_name;
  461. } elseif (preg_match("|^\s+(.+)$|", $line, $m) && $last_header !== null) {
  462. if (is_array($headers[$last_header])) {
  463. end($headers[$last_header]);
  464. $last_header_key = key($headers[$last_header]);
  465. $headers[$last_header][$last_header_key] .= $m[1];
  466. } else {
  467. $headers[$last_header] .= $m[1];
  468. }
  469. }
  470. }
  471. return $headers;
  472. }
  473. /**
  474. * Extract the body from a response string
  475. *
  476. * @param string $response_str
  477. * @return string
  478. */
  479. public static function extractBody($response_str)
  480. {
  481. $parts = preg_split('|(?:\r?\n){2}|m', $response_str, 2);
  482. if (isset($parts[1])) {
  483. return $parts[1];
  484. }
  485. return '';
  486. }
  487. /**
  488. * Decode a "chunked" transfer-encoded body and return the decoded text
  489. *
  490. * @param string $body
  491. * @return string
  492. */
  493. public static function decodeChunkedBody($body)
  494. {
  495. $decBody = '';
  496. // If mbstring overloads substr and strlen functions, we have to
  497. // override it's internal encoding
  498. if (function_exists('mb_internal_encoding') &&
  499. ((int) ini_get('mbstring.func_overload')) & 2) {
  500. $mbIntEnc = mb_internal_encoding();
  501. mb_internal_encoding('ASCII');
  502. }
  503. while (trim($body)) {
  504. if (! preg_match("/^([\da-fA-F]+)[^\r\n]*\r\n/sm", $body, $m)) {
  505. require_once 'Microsoft/Http/Exception.php';
  506. throw new Microsoft_Http_Exception("Error parsing body - doesn't seem to be a chunked message");
  507. }
  508. $length = hexdec(trim($m[1]));
  509. $cut = strlen($m[0]);
  510. $decBody .= substr($body, $cut, $length);
  511. $body = substr($body, $cut + $length + 2);
  512. }
  513. if (isset($mbIntEnc)) {
  514. mb_internal_encoding($mbIntEnc);
  515. }
  516. return $decBody;
  517. }
  518. /**
  519. * Decode a gzip encoded message (when Content-encoding = gzip)
  520. *
  521. * Currently requires PHP with zlib support
  522. *
  523. * @param string $body
  524. * @return string
  525. */
  526. public static function decodeGzip($body)
  527. {
  528. if (! function_exists('gzinflate')) {
  529. require_once 'Microsoft/Http/Exception.php';
  530. throw new Microsoft_Http_Exception(
  531. 'zlib extension is required in order to decode "gzip" encoding'
  532. );
  533. }
  534. return gzinflate(substr($body, 10));
  535. }
  536. /**
  537. * Decode a zlib deflated message (when Content-encoding = deflate)
  538. *
  539. * Currently requires PHP with zlib support
  540. *
  541. * @param string $body
  542. * @return string
  543. */
  544. public static function decodeDeflate($body)
  545. {
  546. if (! function_exists('gzuncompress')) {
  547. require_once 'Microsoft/Http/Exception.php';
  548. throw new Microsoft_Http_Exception(
  549. 'zlib extension is required in order to decode "deflate" encoding'
  550. );
  551. }
  552. /**
  553. * Some servers (IIS ?) send a broken deflate response, without the
  554. * RFC-required zlib header.
  555. *
  556. * We try to detect the zlib header, and if it does not exsit we
  557. * teat the body is plain DEFLATE content.
  558. *
  559. * This method was adapted from PEAR HTTP_Request2 by (c) Alexey Borzov
  560. *
  561. * @link http://framework.zend.com/issues/browse/ZF-6040
  562. */
  563. $zlibHeader = unpack('n', substr($body, 0, 2));
  564. if ($zlibHeader[1] % 31 == 0) {
  565. return gzuncompress($body);
  566. } else {
  567. return gzinflate($body);
  568. }
  569. }
  570. /**
  571. * Create a new Microsoft_Http_Response object from a string
  572. *
  573. * @param string $response_str
  574. * @return Microsoft_Http_Response
  575. */
  576. public static function fromString($response_str)
  577. {
  578. $code = self::extractCode($response_str);
  579. $headers = self::extractHeaders($response_str);
  580. $body = self::extractBody($response_str);
  581. $version = self::extractVersion($response_str);
  582. $message = self::extractMessage($response_str);
  583. return new Microsoft_Http_Response($code, $headers, $body, $version, $message);
  584. }
  585. }