XpressEngine Core  1.11.2
 All Classes Namespaces Files Functions Variables Pages
Response.php
Go to the documentation of this file.
1 <?php
24 require_once 'HTTP/Request2/Exception.php';
25 
55 {
60  protected $version;
61 
67  protected $code;
68 
74  protected $reasonPhrase;
75 
80  protected $effectiveUrl;
81 
86  protected $headers = array();
87 
92  protected $cookies = array();
93 
101  protected $lastHeader = null;
102 
107  protected $body = '';
108 
117  protected $bodyEncoded;
118 
125  protected static $phrases = array(
126 
127  // 1xx: Informational - Request received, continuing process
128  100 => 'Continue',
129  101 => 'Switching Protocols',
130 
131  // 2xx: Success - The action was successfully received, understood and
132  // accepted
133  200 => 'OK',
134  201 => 'Created',
135  202 => 'Accepted',
136  203 => 'Non-Authoritative Information',
137  204 => 'No Content',
138  205 => 'Reset Content',
139  206 => 'Partial Content',
140 
141  // 3xx: Redirection - Further action must be taken in order to complete
142  // the request
143  300 => 'Multiple Choices',
144  301 => 'Moved Permanently',
145  302 => 'Found', // 1.1
146  303 => 'See Other',
147  304 => 'Not Modified',
148  305 => 'Use Proxy',
149  307 => 'Temporary Redirect',
150 
151  // 4xx: Client Error - The request contains bad syntax or cannot be
152  // fulfilled
153  400 => 'Bad Request',
154  401 => 'Unauthorized',
155  402 => 'Payment Required',
156  403 => 'Forbidden',
157  404 => 'Not Found',
158  405 => 'Method Not Allowed',
159  406 => 'Not Acceptable',
160  407 => 'Proxy Authentication Required',
161  408 => 'Request Timeout',
162  409 => 'Conflict',
163  410 => 'Gone',
164  411 => 'Length Required',
165  412 => 'Precondition Failed',
166  413 => 'Request Entity Too Large',
167  414 => 'Request-URI Too Long',
168  415 => 'Unsupported Media Type',
169  416 => 'Requested Range Not Satisfiable',
170  417 => 'Expectation Failed',
171 
172  // 5xx: Server Error - The server failed to fulfill an apparently
173  // valid request
174  500 => 'Internal Server Error',
175  501 => 'Not Implemented',
176  502 => 'Bad Gateway',
177  503 => 'Service Unavailable',
178  504 => 'Gateway Timeout',
179  505 => 'HTTP Version Not Supported',
180  509 => 'Bandwidth Limit Exceeded',
181 
182  );
183 
194  public static function getDefaultReasonPhrase($code = null)
195  {
196  if (null === $code) {
197  return self::$phrases;
198  } else {
199  return isset(self::$phrases[$code]) ? self::$phrases[$code] : null;
200  }
201  }
202 
212  public function __construct($statusLine, $bodyEncoded = true, $effectiveUrl = null)
213  {
214  if (!preg_match('!^HTTP/(\d\.\d) (\d{3})(?: (.+))?!', $statusLine, $m)) {
216  "Malformed response: {$statusLine}",
218  );
219  }
220  $this->version = $m[1];
221  $this->code = intval($m[2]);
222  $this->reasonPhrase = !empty($m[3]) ? trim($m[3]) : self::getDefaultReasonPhrase($this->code);
223  $this->bodyEncoded = (bool)$bodyEncoded;
224  $this->effectiveUrl = (string)$effectiveUrl;
225  }
226 
237  public function parseHeaderLine($headerLine)
238  {
239  $headerLine = trim($headerLine, "\r\n");
240 
241  if ('' == $headerLine) {
242  // empty string signals the end of headers, process the received ones
243  if (!empty($this->headers['set-cookie'])) {
244  $cookies = is_array($this->headers['set-cookie'])?
245  $this->headers['set-cookie']:
246  array($this->headers['set-cookie']);
247  foreach ($cookies as $cookieString) {
248  $this->parseCookie($cookieString);
249  }
250  unset($this->headers['set-cookie']);
251  }
252  foreach (array_keys($this->headers) as $k) {
253  if (is_array($this->headers[$k])) {
254  $this->headers[$k] = implode(', ', $this->headers[$k]);
255  }
256  }
257 
258  } elseif (preg_match('!^([^\x00-\x1f\x7f-\xff()<>@,;:\\\\"/\[\]?={}\s]+):(.+)$!', $headerLine, $m)) {
259  // string of the form header-name: header value
260  $name = strtolower($m[1]);
261  $value = trim($m[2]);
262  if (empty($this->headers[$name])) {
263  $this->headers[$name] = $value;
264  } else {
265  if (!is_array($this->headers[$name])) {
266  $this->headers[$name] = array($this->headers[$name]);
267  }
268  $this->headers[$name][] = $value;
269  }
270  $this->lastHeader = $name;
271 
272  } elseif (preg_match('!^\s+(.+)$!', $headerLine, $m) && $this->lastHeader) {
273  // continuation of a previous header
274  if (!is_array($this->headers[$this->lastHeader])) {
275  $this->headers[$this->lastHeader] .= ' ' . trim($m[1]);
276  } else {
277  $key = count($this->headers[$this->lastHeader]) - 1;
278  $this->headers[$this->lastHeader][$key] .= ' ' . trim($m[1]);
279  }
280  }
281  }
282 
290  protected function parseCookie($cookieString)
291  {
292  $cookie = array(
293  'expires' => null,
294  'domain' => null,
295  'path' => null,
296  'secure' => false
297  );
298 
299  if (!strpos($cookieString, ';')) {
300  // Only a name=value pair
301  $pos = strpos($cookieString, '=');
302  $cookie['name'] = trim(substr($cookieString, 0, $pos));
303  $cookie['value'] = trim(substr($cookieString, $pos + 1));
304 
305  } else {
306  // Some optional parameters are supplied
307  $elements = explode(';', $cookieString);
308  $pos = strpos($elements[0], '=');
309  $cookie['name'] = trim(substr($elements[0], 0, $pos));
310  $cookie['value'] = trim(substr($elements[0], $pos + 1));
311 
312  for ($i = 1; $i < count($elements); $i++) {
313  if (false === strpos($elements[$i], '=')) {
314  $elName = trim($elements[$i]);
315  $elValue = null;
316  } else {
317  list ($elName, $elValue) = array_map('trim', explode('=', $elements[$i]));
318  }
319  $elName = strtolower($elName);
320  if ('secure' == $elName) {
321  $cookie['secure'] = true;
322  } elseif ('expires' == $elName) {
323  $cookie['expires'] = str_replace('"', '', $elValue);
324  } elseif ('path' == $elName || 'domain' == $elName) {
325  $cookie[$elName] = urldecode($elValue);
326  } else {
327  $cookie[$elName] = $elValue;
328  }
329  }
330  }
331  $this->cookies[] = $cookie;
332  }
333 
339  public function appendBody($bodyChunk)
340  {
341  $this->body .= $bodyChunk;
342  }
343 
352  public function getEffectiveUrl()
353  {
354  return $this->effectiveUrl;
355  }
356 
362  public function getStatus()
363  {
364  return $this->code;
365  }
366 
372  public function getReasonPhrase()
373  {
374  return $this->reasonPhrase;
375  }
376 
382  public function isRedirect()
383  {
384  return in_array($this->code, array(300, 301, 302, 303, 307))
385  && isset($this->headers['location']);
386  }
387 
397  public function getHeader($headerName = null)
398  {
399  if (null === $headerName) {
400  return $this->headers;
401  } else {
402  $headerName = strtolower($headerName);
403  return isset($this->headers[$headerName])? $this->headers[$headerName]: null;
404  }
405  }
406 
412  public function getCookies()
413  {
414  return $this->cookies;
415  }
416 
423  public function getBody()
424  {
425  if (0 == strlen($this->body) || !$this->bodyEncoded
426  || !in_array(strtolower($this->getHeader('content-encoding')), array('gzip', 'deflate'))
427  ) {
428  return $this->body;
429 
430  } else {
431  if (extension_loaded('mbstring') && (2 & ini_get('mbstring.func_overload'))) {
432  $oldEncoding = mb_internal_encoding();
433  mb_internal_encoding('8bit');
434  }
435 
436  try {
437  switch (strtolower($this->getHeader('content-encoding'))) {
438  case 'gzip':
439  $decoded = self::decodeGzip($this->body);
440  break;
441  case 'deflate':
442  $decoded = self::decodeDeflate($this->body);
443  }
444  } catch (Exception $e) {
445  }
446 
447  if (!empty($oldEncoding)) {
448  mb_internal_encoding($oldEncoding);
449  }
450  if (!empty($e)) {
451  throw $e;
452  }
453  return $decoded;
454  }
455  }
456 
462  public function getVersion()
463  {
464  return $this->version;
465  }
466 
481  public static function decodeGzip($data)
482  {
483  $length = strlen($data);
484  // If it doesn't look like gzip-encoded data, don't bother
485  if (18 > $length || strcmp(substr($data, 0, 2), "\x1f\x8b")) {
486  return $data;
487  }
488  if (!function_exists('gzinflate')) {
490  'Unable to decode body: gzip extension not available',
492  );
493  }
494  $method = ord(substr($data, 2, 1));
495  if (8 != $method) {
497  'Error parsing gzip header: unknown compression method',
499  );
500  }
501  $flags = ord(substr($data, 3, 1));
502  if ($flags & 224) {
504  'Error parsing gzip header: reserved bits are set',
506  );
507  }
508 
509  // header is 10 bytes minimum. may be longer, though.
510  $headerLength = 10;
511  // extra fields, need to skip 'em
512  if ($flags & 4) {
513  if ($length - $headerLength - 2 < 8) {
515  'Error parsing gzip header: data too short',
517  );
518  }
519  $extraLength = unpack('v', substr($data, 10, 2));
520  if ($length - $headerLength - 2 - $extraLength[1] < 8) {
522  'Error parsing gzip header: data too short',
524  );
525  }
526  $headerLength += $extraLength[1] + 2;
527  }
528  // file name, need to skip that
529  if ($flags & 8) {
530  if ($length - $headerLength - 1 < 8) {
532  'Error parsing gzip header: data too short',
534  );
535  }
536  $filenameLength = strpos(substr($data, $headerLength), chr(0));
537  if (false === $filenameLength || $length - $headerLength - $filenameLength - 1 < 8) {
539  'Error parsing gzip header: data too short',
541  );
542  }
543  $headerLength += $filenameLength + 1;
544  }
545  // comment, need to skip that also
546  if ($flags & 16) {
547  if ($length - $headerLength - 1 < 8) {
549  'Error parsing gzip header: data too short',
551  );
552  }
553  $commentLength = strpos(substr($data, $headerLength), chr(0));
554  if (false === $commentLength || $length - $headerLength - $commentLength - 1 < 8) {
556  'Error parsing gzip header: data too short',
558  );
559  }
560  $headerLength += $commentLength + 1;
561  }
562  // have a CRC for header. let's check
563  if ($flags & 2) {
564  if ($length - $headerLength - 2 < 8) {
566  'Error parsing gzip header: data too short',
568  );
569  }
570  $crcReal = 0xffff & crc32(substr($data, 0, $headerLength));
571  $crcStored = unpack('v', substr($data, $headerLength, 2));
572  if ($crcReal != $crcStored[1]) {
574  'Header CRC check failed',
576  );
577  }
578  $headerLength += 2;
579  }
580  // unpacked data CRC and size at the end of encoded data
581  $tmp = unpack('V2', substr($data, -8));
582  $dataCrc = $tmp[1];
583  $dataSize = $tmp[2];
584 
585  // finally, call the gzinflate() function
586  // don't pass $dataSize to gzinflate, see bugs #13135, #14370
587  $unpacked = gzinflate(substr($data, $headerLength, -8));
588  if (false === $unpacked) {
590  'gzinflate() call failed',
592  );
593  } elseif ($dataSize != strlen($unpacked)) {
595  'Data size check failed',
597  );
598  } elseif ((0xffffffff & $dataCrc) != (0xffffffff & crc32($unpacked))) {
599  throw new HTTP_Request2_Exception(
600  'Data CRC check failed',
602  );
603  }
604  return $unpacked;
605  }
606 
615  public static function decodeDeflate($data)
616  {
617  if (!function_exists('gzuncompress')) {
619  'Unable to decode body: gzip extension not available',
621  );
622  }
623  // RFC 2616 defines 'deflate' encoding as zlib format from RFC 1950,
624  // while many applications send raw deflate stream from RFC 1951.
625  // We should check for presence of zlib header and use gzuncompress() or
626  // gzinflate() as needed. See bug #15305
627  $header = unpack('n', substr($data, 0, 2));
628  return (0 == $header[1] % 31)? gzuncompress($data): gzinflate($data);
629  }
630 }
631 ?>
static getDefaultReasonPhrase($code=null)
Definition: Response.php:194
__construct($statusLine, $bodyEncoded=true, $effectiveUrl=null)
Definition: Response.php:212
parseCookie($cookieString)
Definition: Response.php:290
parseHeaderLine($headerLine)
Definition: Response.php:237
appendBody($bodyChunk)
Definition: Response.php:339
static decodeGzip($data)
Definition: Response.php:481
getHeader($headerName=null)
Definition: Response.php:397
static decodeDeflate($data)
Definition: Response.php:615