PageRenderTime 26ms CodeModel.GetById 23ms RepoModel.GetById 1ms app.codeStats 0ms

/library/Zend/Http/Response.php

https://github.com/beberlei/zf2
PHP | 557 lines | 362 code | 52 blank | 143 comment | 33 complexity | df712cf2b26b832074abf507c93436d7 MD5 | raw file
  1. <?php
  2. namespace Zend\Http;
  3. use Zend\Stdlib\Message,
  4. Zend\Stdlib\ResponseDescription;
  5. class Response extends Message implements ResponseDescription
  6. {
  7. /**#@+
  8. * @const int Status codes
  9. */
  10. const STATUS_CODE_CUSTOM = 0;
  11. const STATUS_CODE_100 = 100;
  12. const STATUS_CODE_101 = 101;
  13. const STATUS_CODE_200 = 200;
  14. const STATUS_CODE_201 = 201;
  15. const STATUS_CODE_202 = 202;
  16. const STATUS_CODE_203 = 203;
  17. const STATUS_CODE_204 = 204;
  18. const STATUS_CODE_205 = 205;
  19. const STATUS_CODE_206 = 206;
  20. const STATUS_CODE_300 = 300;
  21. const STATUS_CODE_301 = 301;
  22. const STATUS_CODE_302 = 302;
  23. const STATUS_CODE_303 = 303;
  24. const STATUS_CODE_304 = 304;
  25. const STATUS_CODE_305 = 305;
  26. const STATUS_CODE_306 = 306;
  27. const STATUS_CODE_307 = 307;
  28. const STATUS_CODE_400 = 400;
  29. const STATUS_CODE_401 = 401;
  30. const STATUS_CODE_402 = 402;
  31. const STATUS_CODE_403 = 403;
  32. const STATUS_CODE_404 = 404;
  33. const STATUS_CODE_405 = 405;
  34. const STATUS_CODE_406 = 406;
  35. const STATUS_CODE_407 = 407;
  36. const STATUS_CODE_408 = 408;
  37. const STATUS_CODE_409 = 409;
  38. const STATUS_CODE_410 = 410;
  39. const STATUS_CODE_411 = 411;
  40. const STATUS_CODE_412 = 412;
  41. const STATUS_CODE_413 = 413;
  42. const STATUS_CODE_414 = 414;
  43. const STATUS_CODE_415 = 415;
  44. const STATUS_CODE_416 = 416;
  45. const STATUS_CODE_417 = 417;
  46. const STATUS_CODE_418 = 418;
  47. const STATUS_CODE_428 = 428;
  48. const STATUS_CODE_429 = 429;
  49. const STATUS_CODE_431 = 431;
  50. const STATUS_CODE_500 = 500;
  51. const STATUS_CODE_501 = 501;
  52. const STATUS_CODE_502 = 502;
  53. const STATUS_CODE_503 = 503;
  54. const STATUS_CODE_504 = 504;
  55. const STATUS_CODE_505 = 505;
  56. const STATUS_CODE_511 = 511;
  57. /**#@-*/
  58. /**#@+
  59. * @const string Version constant numbers
  60. */
  61. const VERSION_11 = '1.1';
  62. const VERSION_10 = '1.0';
  63. /**#@-*/
  64. /**
  65. * @var string
  66. */
  67. protected $version = self::VERSION_11;
  68. /**
  69. * @var array Recommended Reason Phrases
  70. */
  71. protected $recommendedReasonPhrases = array(
  72. // INFORMATIONAL CODES
  73. 100 => 'Continue',
  74. 101 => 'Switching Protocols',
  75. // SUCCESS CODES
  76. 200 => 'OK',
  77. 201 => 'Created',
  78. 202 => 'Accepted',
  79. 203 => 'Non-Authoritative Information',
  80. 204 => 'No Content',
  81. 205 => 'Reset Content',
  82. 206 => 'Partial Content',
  83. // REDIRECTION CODES
  84. 300 => 'Multiple Choices',
  85. 301 => 'Moved Permanently',
  86. 302 => 'Found',
  87. 303 => 'See Other',
  88. 304 => 'Not Modified',
  89. 305 => 'Use Proxy',
  90. 306 => 'Switch Proxy', // Deprecated
  91. 307 => 'Temporary Redirect',
  92. // CLIENT ERROR
  93. 400 => 'Bad Request',
  94. 401 => 'Unauthorized',
  95. 402 => 'Payment Required',
  96. 403 => 'Forbidden',
  97. 404 => 'Not Found',
  98. 405 => 'Method Not Allowed',
  99. 406 => 'Not Acceptable',
  100. 407 => 'Proxy Authentication Required',
  101. 408 => 'Request Time-out',
  102. 409 => 'Conflict',
  103. 410 => 'Gone',
  104. 411 => 'Length Required',
  105. 412 => 'Precondition Failed',
  106. 413 => 'Request Entity Too Large',
  107. 414 => 'Request-URI Too Large',
  108. 415 => 'Unsupported Media Type',
  109. 416 => 'Requested range not satisfiable',
  110. 417 => 'Expectation Failed',
  111. 418 => 'I\'m a teapot',
  112. 428 => 'Precondition Required',
  113. 429 => 'Too Many Requests',
  114. 431 => 'Request Header Fields Too Large',
  115. // SERVER ERROR
  116. 500 => 'Internal Server Error',
  117. 501 => 'Not Implemented',
  118. 502 => 'Bad Gateway',
  119. 503 => 'Service Unavailable',
  120. 504 => 'Gateway Time-out',
  121. 505 => 'HTTP Version not supported',
  122. 511 => 'Network Authentication Required',
  123. );
  124. /**
  125. * @var int Status code
  126. */
  127. protected $statusCode = 200;
  128. /**
  129. * @var string|null Null means it will be looked up from the $reasonPhrase list above
  130. */
  131. protected $reasonPhrase = null;
  132. /**
  133. * @var Headers
  134. */
  135. protected $headers = null;
  136. /**
  137. * Populate object from string
  138. *
  139. * @param string $string
  140. * @return Response
  141. */
  142. public static function fromString($string)
  143. {
  144. $lines = preg_split('/\r\n/', $string);
  145. if (!is_array($lines) || count($lines)==1) {
  146. $lines = preg_split ('/\n/',$string);
  147. }
  148. $firstLine = array_shift($lines);
  149. $response = new static();
  150. $matches = null;
  151. if (!preg_match('/^HTTP\/(?P<version>1\.[01]) (?P<status>\d{3}) (?P<reason>.*)$/', $firstLine, $matches)) {
  152. throw new Exception\InvalidArgumentException('A valid response status line was not found in the provided string');
  153. }
  154. $response->version = $matches['version'];
  155. $response->setStatusCode($matches['status']);
  156. $response->setReasonPhrase($matches['reason']);
  157. if (count($lines) == 0) {
  158. return $response;
  159. }
  160. $isHeader = true;
  161. $headers = $content = array();
  162. while ($lines) {
  163. $nextLine = array_shift($lines);
  164. if ($nextLine == '') {
  165. $isHeader = false;
  166. continue;
  167. }
  168. if ($isHeader) {
  169. $headers[] .= $nextLine;
  170. } else {
  171. $content[] .= $nextLine;
  172. }
  173. }
  174. if ($headers) {
  175. $response->headers = implode("\r\n", $headers);
  176. }
  177. if ($content) {
  178. $response->setContent(implode("\r\n", $content));
  179. }
  180. return $response;
  181. }
  182. /**
  183. * Render the status line header
  184. *
  185. * @return string
  186. */
  187. public function renderStatusLine()
  188. {
  189. $status = sprintf(
  190. 'HTTP/%s %d %s',
  191. $this->getVersion(),
  192. $this->getStatusCode(),
  193. $this->getReasonPhrase()
  194. );
  195. return trim($status);
  196. }
  197. /**
  198. * Set response headers
  199. *
  200. * @param Headers $headers
  201. * @return Response
  202. */
  203. public function setHeaders(Headers $headers)
  204. {
  205. $this->headers = $headers;
  206. return $this;
  207. }
  208. /**
  209. * Get response headers
  210. *
  211. * @return Headers
  212. */
  213. public function headers()
  214. {
  215. if ($this->headers === null || is_string($this->headers)) {
  216. $this->headers = (is_string($this->headers)) ? Headers::fromString($this->headers) : new Headers();
  217. }
  218. return $this->headers;
  219. }
  220. /**
  221. * @return Header\SetCookie[]
  222. */
  223. public function cookie()
  224. {
  225. return $this->headers()->get('Set-Cookie');
  226. }
  227. /**
  228. * @param string $version
  229. * @return Response
  230. */
  231. public function setVersion($version)
  232. {
  233. $this->version = $version;
  234. return $this;
  235. }
  236. /**
  237. * @return string
  238. */
  239. public function getVersion()
  240. {
  241. return $this->version;
  242. }
  243. /**
  244. * Retrieve HTTP status code
  245. *
  246. * @return int
  247. */
  248. public function getStatusCode()
  249. {
  250. return $this->statusCode;
  251. }
  252. /**
  253. * @param string $reasonPhrase
  254. * @return Response
  255. */
  256. public function setReasonPhrase($reasonPhrase)
  257. {
  258. $this->reasonPhrase = trim($reasonPhrase);
  259. return $this;
  260. }
  261. /**
  262. * Get HTTP status message
  263. *
  264. * @return string
  265. */
  266. public function getReasonPhrase()
  267. {
  268. if ($this->reasonPhrase == null) {
  269. return $this->recommendedReasonPhrases[$this->statusCode];
  270. }
  271. return $this->reasonPhrase;
  272. }
  273. /**
  274. * Set HTTP status code and (optionally) message
  275. *
  276. * @param numeric $code
  277. * @return Response
  278. */
  279. public function setStatusCode($code)
  280. {
  281. $const = get_called_class() . '::STATUS_CODE_' . $code;
  282. if (!is_numeric($code) || !defined($const)) {
  283. $code = is_scalar($code) ? $code : gettype($code);
  284. throw new Exception\InvalidArgumentException(sprintf(
  285. 'Invalid status code provided: "%s"',
  286. $code
  287. ));
  288. }
  289. $this->statusCode = (int) $code;
  290. return $this;
  291. }
  292. /**
  293. * Get the body of the response
  294. *
  295. * @return string
  296. */
  297. public function getBody()
  298. {
  299. $body = (string) $this->getContent();
  300. $transferEncoding = $this->headers()->get('Transfer-Encoding');
  301. if (!empty($transferEncoding)) {
  302. if (strtolower($transferEncoding->getFieldValue()) == 'chunked') {
  303. $body = $this->decodeChunkedBody($body);
  304. }
  305. }
  306. $contentEncoding = $this->headers()->get('Content-Encoding');
  307. if (!empty($contentEncoding)) {
  308. $contentEncoding = $contentEncoding->getFieldValue();
  309. if ($contentEncoding =='gzip') {
  310. $body = $this->decodeGzip($body);
  311. } elseif ($contentEncoding == 'deflate') {
  312. $body = $this->decodeDeflate($body);
  313. }
  314. }
  315. return $body;
  316. }
  317. /**
  318. * Does the status code indicate a client error?
  319. *
  320. * @return bool
  321. */
  322. public function isClientError()
  323. {
  324. $code = $this->getStatusCode();
  325. return ($code < 500 && $code >= 400);
  326. }
  327. /**
  328. * Is the request forbidden due to ACLs?
  329. *
  330. * @return bool
  331. */
  332. public function isForbidden()
  333. {
  334. return (403 == $this->getStatusCode());
  335. }
  336. /**
  337. * Is the current status "informational"?
  338. *
  339. * @return bool
  340. */
  341. public function isInformational()
  342. {
  343. $code = $this->getStatusCode();
  344. return ($code >= 100 && $code < 200);
  345. }
  346. /**
  347. * Does the status code indicate the resource is not found?
  348. *
  349. * @return bool
  350. */
  351. public function isNotFound()
  352. {
  353. return (404 === $this->getStatusCode());
  354. }
  355. /**
  356. * Do we have a normal, OK response?
  357. *
  358. * @return bool
  359. */
  360. public function isOk()
  361. {
  362. return (200 === $this->getStatusCode());
  363. }
  364. /**
  365. * Does the status code reflect a server error?
  366. *
  367. * @return bool
  368. */
  369. public function isServerError()
  370. {
  371. $code = $this->getStatusCode();
  372. return (500 <= $code && 600 > $code);
  373. }
  374. /**
  375. * Do we have a redirect?
  376. *
  377. * @return bool
  378. */
  379. public function isRedirect()
  380. {
  381. $code = $this->getStatusCode();
  382. return (300 <= $code && 400 > $code);
  383. }
  384. /**
  385. * Was the response successful?
  386. *
  387. * @return bool
  388. */
  389. public function isSuccess()
  390. {
  391. $code = $this->getStatusCode();
  392. return (200 <= $code && 300 > $code);
  393. }
  394. /**
  395. * Render the response line string
  396. *
  397. * @return string
  398. */
  399. public function renderResponseLine()
  400. {
  401. return 'HTTP/' . $this->getVersion() . ' ' . $this->getStatusCode() . ' ' . $this->getReasonPhrase();
  402. }
  403. /**
  404. * Render entire response as HTTP response string
  405. *
  406. * @return string
  407. */
  408. public function toString()
  409. {
  410. $str = $this->renderResponseLine() . "\r\n";
  411. $str .= $this->headers()->toString();
  412. $str .= "\r\n";
  413. $str .= $this->getBody();
  414. return $str;
  415. }
  416. /**
  417. * Decode a "chunked" transfer-encoded body and return the decoded text
  418. *
  419. * @param string $body
  420. * @return string
  421. */
  422. protected function decodeChunkedBody($body)
  423. {
  424. $decBody = '';
  425. // If mbstring overloads substr and strlen functions, we have to
  426. // override it's internal encoding
  427. if (function_exists('mb_internal_encoding') &&
  428. ((int) ini_get('mbstring.func_overload')) & 2) {
  429. $mbIntEnc = mb_internal_encoding();
  430. mb_internal_encoding('ASCII');
  431. }
  432. while (trim($body)) {
  433. if (! preg_match("/^([\da-fA-F]+)[^\r\n]*\r\n/sm", $body, $m)) {
  434. throw new Exception\RuntimeException("Error parsing body - doesn't seem to be a chunked message");
  435. }
  436. $length = hexdec(trim($m[1]));
  437. $cut = strlen($m[0]);
  438. $decBody .= substr($body, $cut, $length);
  439. $body = substr($body, $cut + $length + 2);
  440. }
  441. if (isset($mbIntEnc)) {
  442. mb_internal_encoding($mbIntEnc);
  443. }
  444. return $decBody;
  445. }
  446. /**
  447. * Decode a gzip encoded message (when Content-encoding = gzip)
  448. *
  449. * Currently requires PHP with zlib support
  450. *
  451. * @param string $body
  452. * @return string
  453. */
  454. protected function decodeGzip($body)
  455. {
  456. if (!function_exists('gzinflate')) {
  457. throw new Exception\RuntimeException(
  458. 'zlib extension is required in order to decode "gzip" encoding'
  459. );
  460. }
  461. return gzinflate(substr($body, 10));
  462. }
  463. /**
  464. * Decode a zlib deflated message (when Content-encoding = deflate)
  465. *
  466. * Currently requires PHP with zlib support
  467. *
  468. * @param string $body
  469. * @return string
  470. */
  471. protected function decodeDeflate($body)
  472. {
  473. if (!function_exists('gzuncompress')) {
  474. throw new Exception\RuntimeException(
  475. 'zlib extension is required in order to decode "deflate" encoding'
  476. );
  477. }
  478. /**
  479. * Some servers (IIS ?) send a broken deflate response, without the
  480. * RFC-required zlib header.
  481. *
  482. * We try to detect the zlib header, and if it does not exsit we
  483. * teat the body is plain DEFLATE content.
  484. *
  485. * This method was adapted from PEAR HTTP_Request2 by (c) Alexey Borzov
  486. *
  487. * @link http://framework.zend.com/issues/browse/ZF-6040
  488. */
  489. $zlibHeader = unpack('n', substr($body, 0, 2));
  490. if ($zlibHeader[1] % 31 == 0) {
  491. return gzuncompress($body);
  492. } else {
  493. return gzinflate($body);
  494. }
  495. }
  496. }