PageRenderTime 45ms CodeModel.GetById 16ms RepoModel.GetById 0ms app.codeStats 0ms

/lib/Cake/Network/Http/HttpSocketResponse.php

https://gitlab.com/fouzia23chowdhury/cakephpCRUD
PHP | 447 lines | 230 code | 49 blank | 168 comment | 37 complexity | a609601638134156f3c88e68c2c7d18f MD5 | raw file
  1. <?php
  2. /**
  3. * HTTP Response from HttpSocket.
  4. *
  5. * CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
  6. * Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
  7. *
  8. * Licensed under The MIT License
  9. * For full copyright and license information, please see the LICENSE.txt
  10. * Redistributions of files must retain the above copyright notice.
  11. *
  12. * @copyright Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
  13. * @link http://cakephp.org CakePHP(tm) Project
  14. * @since CakePHP(tm) v 2.0.0
  15. * @license http://www.opensource.org/licenses/mit-license.php MIT License
  16. */
  17. /**
  18. * HTTP Response from HttpSocket.
  19. *
  20. * @package Cake.Network.Http
  21. */
  22. class HttpSocketResponse implements ArrayAccess {
  23. /**
  24. * Body content
  25. *
  26. * @var string
  27. */
  28. public $body = '';
  29. /**
  30. * Headers
  31. *
  32. * @var array
  33. */
  34. public $headers = array();
  35. /**
  36. * Cookies
  37. *
  38. * @var array
  39. */
  40. public $cookies = array();
  41. /**
  42. * HTTP version
  43. *
  44. * @var string
  45. */
  46. public $httpVersion = 'HTTP/1.1';
  47. /**
  48. * Response code
  49. *
  50. * @var int
  51. */
  52. public $code = 0;
  53. /**
  54. * Reason phrase
  55. *
  56. * @var string
  57. */
  58. public $reasonPhrase = '';
  59. /**
  60. * Pure raw content
  61. *
  62. * @var string
  63. */
  64. public $raw = '';
  65. /**
  66. * Context data in the response.
  67. * Contains SSL certificates for example.
  68. *
  69. * @var array
  70. */
  71. public $context = array();
  72. /**
  73. * Constructor
  74. *
  75. * @param string $message Message to parse.
  76. */
  77. public function __construct($message = null) {
  78. if ($message !== null) {
  79. $this->parseResponse($message);
  80. }
  81. }
  82. /**
  83. * Body content
  84. *
  85. * @return string
  86. */
  87. public function body() {
  88. return (string)$this->body;
  89. }
  90. /**
  91. * Get header in case insensitive
  92. *
  93. * @param string $name Header name.
  94. * @param array $headers Headers to format.
  95. * @return mixed String if header exists or null
  96. */
  97. public function getHeader($name, $headers = null) {
  98. if (!is_array($headers)) {
  99. $headers =& $this->headers;
  100. }
  101. if (isset($headers[$name])) {
  102. return $headers[$name];
  103. }
  104. foreach ($headers as $key => $value) {
  105. if (strcasecmp($key, $name) === 0) {
  106. return $value;
  107. }
  108. }
  109. return null;
  110. }
  111. /**
  112. * If return is 200 (OK)
  113. *
  114. * @return bool
  115. */
  116. public function isOk() {
  117. return in_array($this->code, array(200, 201, 202, 203, 204, 205, 206));
  118. }
  119. /**
  120. * If return is a valid 3xx (Redirection)
  121. *
  122. * @return bool
  123. */
  124. public function isRedirect() {
  125. return in_array($this->code, array(301, 302, 303, 307)) && $this->getHeader('Location') !== null;
  126. }
  127. /**
  128. * Parses the given message and breaks it down in parts.
  129. *
  130. * @param string $message Message to parse
  131. * @return void
  132. * @throws SocketException
  133. */
  134. public function parseResponse($message) {
  135. if (!is_string($message)) {
  136. throw new SocketException(__d('cake_dev', 'Invalid response.'));
  137. }
  138. if (!preg_match("/^(.+\r\n)(.*)(?<=\r\n)\r\n/Us", $message, $match)) {
  139. throw new SocketException(__d('cake_dev', 'Invalid HTTP response.'));
  140. }
  141. list(, $statusLine, $header) = $match;
  142. $this->raw = $message;
  143. $this->body = (string)substr($message, strlen($match[0]));
  144. if (preg_match("/(.+) ([0-9]{3})(?:\s+(\w.+))?\s*\r\n/DU", $statusLine, $match)) {
  145. $this->httpVersion = $match[1];
  146. $this->code = $match[2];
  147. if (isset($match[3])) {
  148. $this->reasonPhrase = $match[3];
  149. }
  150. }
  151. $this->headers = $this->_parseHeader($header);
  152. $transferEncoding = $this->getHeader('Transfer-Encoding');
  153. $decoded = $this->_decodeBody($this->body, $transferEncoding);
  154. $this->body = $decoded['body'];
  155. if (!empty($decoded['header'])) {
  156. $this->headers = $this->_parseHeader($this->_buildHeader($this->headers) . $this->_buildHeader($decoded['header']));
  157. }
  158. if (!empty($this->headers)) {
  159. $this->cookies = $this->parseCookies($this->headers);
  160. }
  161. }
  162. /**
  163. * Generic function to decode a $body with a given $encoding. Returns either an array with the keys
  164. * 'body' and 'header' or false on failure.
  165. *
  166. * @param string $body A string containing the body to decode.
  167. * @param string|bool $encoding Can be false in case no encoding is being used, or a string representing the encoding.
  168. * @return mixed Array of response headers and body or false.
  169. */
  170. protected function _decodeBody($body, $encoding = 'chunked') {
  171. if (!is_string($body)) {
  172. return false;
  173. }
  174. if (empty($encoding)) {
  175. return array('body' => $body, 'header' => false);
  176. }
  177. $decodeMethod = '_decode' . Inflector::camelize(str_replace('-', '_', $encoding)) . 'Body';
  178. if (!is_callable(array(&$this, $decodeMethod))) {
  179. return array('body' => $body, 'header' => false);
  180. }
  181. return $this->{$decodeMethod}($body);
  182. }
  183. /**
  184. * Decodes a chunked message $body and returns either an array with the keys 'body' and 'header' or false as
  185. * a result.
  186. *
  187. * @param string $body A string containing the chunked body to decode.
  188. * @return mixed Array of response headers and body or false.
  189. * @throws SocketException
  190. */
  191. protected function _decodeChunkedBody($body) {
  192. if (!is_string($body)) {
  193. return false;
  194. }
  195. $decodedBody = null;
  196. $chunkLength = null;
  197. while ($chunkLength !== 0) {
  198. if (!preg_match('/^([0-9a-f]+)[ ]*(?:;(.+)=(.+))?(?:\r\n|\n)/iU', $body, $match)) {
  199. // Handle remaining invalid data as one big chunk.
  200. preg_match('/^(.*?)\r\n/', $body, $invalidMatch);
  201. $length = isset($invalidMatch[1]) ? strlen($invalidMatch[1]) : 0;
  202. $match = array(
  203. 0 => '',
  204. 1 => dechex($length)
  205. );
  206. }
  207. $chunkSize = 0;
  208. $hexLength = 0;
  209. if (isset($match[0])) {
  210. $chunkSize = $match[0];
  211. }
  212. if (isset($match[1])) {
  213. $hexLength = $match[1];
  214. }
  215. $chunkLength = hexdec($hexLength);
  216. $body = substr($body, strlen($chunkSize));
  217. $decodedBody .= substr($body, 0, $chunkLength);
  218. if ($chunkLength) {
  219. $body = substr($body, $chunkLength + strlen("\r\n"));
  220. }
  221. }
  222. $entityHeader = false;
  223. if (!empty($body)) {
  224. $entityHeader = $this->_parseHeader($body);
  225. }
  226. return array('body' => $decodedBody, 'header' => $entityHeader);
  227. }
  228. /**
  229. * Parses an array based header.
  230. *
  231. * @param array $header Header as an indexed array (field => value)
  232. * @return array Parsed header
  233. */
  234. protected function _parseHeader($header) {
  235. if (is_array($header)) {
  236. return $header;
  237. } elseif (!is_string($header)) {
  238. return false;
  239. }
  240. preg_match_all("/(.+):(.+)(?:(?<![\t ])\r\n|\$)/Uis", $header, $matches, PREG_SET_ORDER);
  241. $header = array();
  242. foreach ($matches as $match) {
  243. list(, $field, $value) = $match;
  244. $value = trim($value);
  245. $value = preg_replace("/[\t ]\r\n/", "\r\n", $value);
  246. $field = $this->_unescapeToken($field);
  247. if (!isset($header[$field])) {
  248. $header[$field] = $value;
  249. } else {
  250. $header[$field] = array_merge((array)$header[$field], (array)$value);
  251. }
  252. }
  253. return $header;
  254. }
  255. /**
  256. * Parses cookies in response headers.
  257. *
  258. * @param array $header Header array containing one ore more 'Set-Cookie' headers.
  259. * @return mixed Either false on no cookies, or an array of cookies received.
  260. */
  261. public function parseCookies($header) {
  262. $cookieHeader = $this->getHeader('Set-Cookie', $header);
  263. if (!$cookieHeader) {
  264. return false;
  265. }
  266. $cookies = array();
  267. foreach ((array)$cookieHeader as $cookie) {
  268. if (strpos($cookie, '";"') !== false) {
  269. $cookie = str_replace('";"', "{__cookie_replace__}", $cookie);
  270. $parts = str_replace("{__cookie_replace__}", '";"', explode(';', $cookie));
  271. } else {
  272. $parts = preg_split('/\;[ \t]*/', $cookie);
  273. }
  274. list($name, $value) = explode('=', array_shift($parts), 2);
  275. $cookies[$name] = compact('value');
  276. foreach ($parts as $part) {
  277. if (strpos($part, '=') !== false) {
  278. list($key, $value) = explode('=', $part);
  279. } else {
  280. $key = $part;
  281. $value = true;
  282. }
  283. $key = strtolower($key);
  284. if (!isset($cookies[$name][$key])) {
  285. $cookies[$name][$key] = $value;
  286. }
  287. }
  288. }
  289. return $cookies;
  290. }
  291. /**
  292. * Unescapes a given $token according to RFC 2616 (HTTP 1.1 specs)
  293. *
  294. * @param string $token Token to unescape.
  295. * @param array $chars Characters to unescape.
  296. * @return string Unescaped token
  297. */
  298. protected function _unescapeToken($token, $chars = null) {
  299. $regex = '/"([' . implode('', $this->_tokenEscapeChars(true, $chars)) . '])"/';
  300. $token = preg_replace($regex, '\\1', $token);
  301. return $token;
  302. }
  303. /**
  304. * Gets escape chars according to RFC 2616 (HTTP 1.1 specs).
  305. *
  306. * @param bool $hex True to get them as HEX values, false otherwise.
  307. * @param array $chars Characters to uescape.
  308. * @return array Escape chars
  309. */
  310. protected function _tokenEscapeChars($hex = true, $chars = null) {
  311. if (!empty($chars)) {
  312. $escape = $chars;
  313. } else {
  314. $escape = array('"', "(", ")", "<", ">", "@", ",", ";", ":", "\\", "/", "[", "]", "?", "=", "{", "}", " ");
  315. for ($i = 0; $i <= 31; $i++) {
  316. $escape[] = chr($i);
  317. }
  318. $escape[] = chr(127);
  319. }
  320. if (!$hex) {
  321. return $escape;
  322. }
  323. foreach ($escape as $key => $char) {
  324. $escape[$key] = '\\x' . str_pad(dechex(ord($char)), 2, '0', STR_PAD_LEFT);
  325. }
  326. return $escape;
  327. }
  328. /**
  329. * ArrayAccess - Offset Exists
  330. *
  331. * @param string $offset Offset to check.
  332. * @return bool
  333. */
  334. public function offsetExists($offset) {
  335. return in_array($offset, array('raw', 'status', 'header', 'body', 'cookies'));
  336. }
  337. /**
  338. * ArrayAccess - Offset Get
  339. *
  340. * @param string $offset Offset to get.
  341. * @return mixed
  342. */
  343. public function offsetGet($offset) {
  344. switch ($offset) {
  345. case 'raw':
  346. $firstLineLength = strpos($this->raw, "\r\n") + 2;
  347. if ($this->raw[$firstLineLength] === "\r") {
  348. $header = null;
  349. } else {
  350. $header = substr($this->raw, $firstLineLength, strpos($this->raw, "\r\n\r\n") - $firstLineLength) . "\r\n";
  351. }
  352. return array(
  353. 'status-line' => $this->httpVersion . ' ' . $this->code . ' ' . $this->reasonPhrase . "\r\n",
  354. 'header' => $header,
  355. 'body' => $this->body,
  356. 'response' => $this->raw
  357. );
  358. case 'status':
  359. return array(
  360. 'http-version' => $this->httpVersion,
  361. 'code' => $this->code,
  362. 'reason-phrase' => $this->reasonPhrase
  363. );
  364. case 'header':
  365. return $this->headers;
  366. case 'body':
  367. return $this->body;
  368. case 'cookies':
  369. return $this->cookies;
  370. }
  371. return null;
  372. }
  373. /**
  374. * ArrayAccess - Offset Set
  375. *
  376. * @param string $offset Offset to set.
  377. * @param mixed $value Value.
  378. * @return void
  379. */
  380. public function offsetSet($offset, $value) {
  381. }
  382. /**
  383. * ArrayAccess - Offset Unset
  384. *
  385. * @param string $offset Offset to unset.
  386. * @return void
  387. */
  388. public function offsetUnset($offset) {
  389. }
  390. /**
  391. * Instance as string
  392. *
  393. * @return string
  394. */
  395. public function __toString() {
  396. return $this->body();
  397. }
  398. }