*\r
* LICENSE:\r
*\r
- * Copyright (c) 2008, 2009, Alexey Borzov <avb@php.net>\r
+ * Copyright (c) 2008-2011, Alexey Borzov <avb@php.net>\r
* All rights reserved.\r
*\r
* Redistribution and use in source and binary forms, with or without\r
* @package HTTP_Request2\r
* @author Alexey Borzov <avb@php.net>\r
* @license http://opensource.org/licenses/bsd-license.php New BSD License\r
- * @version CVS: $Id: Response.php 287948 2009-09-01 17:12:18Z avb $\r
+ * @version SVN: $Id: Response.php 309921 2011-04-03 16:43:02Z avb $\r
* @link http://pear.php.net/package/HTTP_Request2\r
*/\r
\r
/**\r
* Exception class for HTTP_Request2 package\r
- */ \r
+ */\r
require_once 'HTTP/Request2/Exception.php';\r
\r
/**\r
* $headerLine = read_header_line();\r
* $response->parseHeaderLine($headerLine);\r
* } while ($headerLine != '');\r
- * \r
+ *\r
* while ($chunk = read_body()) {\r
* $response->appendBody($chunk);\r
* }\r
- * \r
+ *\r
* var_dump($response->getHeader(), $response->getCookies(), $response->getBody());\r
* </code>\r
*\r
* @category HTTP\r
* @package HTTP_Request2\r
* @author Alexey Borzov <avb@php.net>\r
- * @version Release: 0.4.1\r
+ * @version Release: 2.0.0RC1\r
* @link http://tools.ietf.org/html/rfc2616#section-6\r
*/\r
class HTTP_Request2_Response\r
*/\r
protected $reasonPhrase;\r
\r
+ /**\r
+ * Effective URL (may be different from original request URL in case of redirects)\r
+ * @var string\r
+ */\r
+ protected $effectiveUrl;\r
+\r
/**\r
* Associative array of response headers\r
* @var array\r
305 => 'Use Proxy',\r
307 => 'Temporary Redirect',\r
\r
- // 4xx: Client Error - The request contains bad syntax or cannot be \r
+ // 4xx: Client Error - The request contains bad syntax or cannot be\r
// fulfilled\r
400 => 'Bad Request',\r
401 => 'Unauthorized',\r
/**\r
* Constructor, parses the response status line\r
*\r
- * @param string Response status line (e.g. "HTTP/1.1 200 OK")\r
- * @param bool Whether body is still encoded by Content-Encoding\r
- * @throws HTTP_Request2_Exception if status line is invalid according to spec\r
+ * @param string Response status line (e.g. "HTTP/1.1 200 OK")\r
+ * @param bool Whether body is still encoded by Content-Encoding\r
+ * @param string Effective URL of the response\r
+ * @throws HTTP_Request2_MessageException if status line is invalid according to spec\r
*/\r
- public function __construct($statusLine, $bodyEncoded = true)\r
+ public function __construct($statusLine, $bodyEncoded = true, $effectiveUrl = null)\r
{\r
if (!preg_match('!^HTTP/(\d\.\d) (\d{3})(?: (.+))?!', $statusLine, $m)) {\r
- throw new HTTP_Request2_Exception("Malformed response: {$statusLine}");\r
+ throw new HTTP_Request2_MessageException(\r
+ "Malformed response: {$statusLine}",\r
+ HTTP_Request2_Exception::MALFORMED_RESPONSE\r
+ );\r
}\r
$this->version = $m[1];\r
$this->code = intval($m[2]);\r
} elseif (!empty(self::$phrases[$this->code])) {\r
$this->reasonPhrase = self::$phrases[$this->code];\r
}\r
- $this->bodyEncoded = (bool)$bodyEncoded;\r
+ $this->bodyEncoded = (bool)$bodyEncoded;\r
+ $this->effectiveUrl = (string)$effectiveUrl;\r
}\r
\r
/**\r
* Parses the line from HTTP response filling $headers array\r
*\r
- * The method should be called after reading the line from socket or receiving \r
+ * The method should be called after reading the line from socket or receiving\r
* it into cURL callback. Passing an empty string here indicates the end of\r
* response headers and triggers additional processing, so be sure to pass an\r
* empty string in the end.\r
}\r
$this->lastHeader = $name;\r
\r
- // string \r
+ // continuation of a previous header\r
} elseif (preg_match('!^\s+(.+)$!', $headerLine, $m) && $this->lastHeader) {\r
if (!is_array($this->headers[$this->lastHeader])) {\r
$this->headers[$this->lastHeader] .= ' ' . trim($m[1]);\r
$this->headers[$this->lastHeader][$key] .= ' ' . trim($m[1]);\r
}\r
}\r
- } \r
+ }\r
\r
/**\r
* Parses a Set-Cookie header to fill $cookies array\r
*\r
* @param string value of Set-Cookie header\r
- * @link http://cgi.netscape.com/newsref/std/cookie_spec.html\r
+ * @link http://web.archive.org/web/20080331104521/http://cgi.netscape.com/newsref/std/cookie_spec.html\r
*/\r
protected function parseCookie($cookieString)\r
{\r
$this->body .= $bodyChunk;\r
}\r
\r
+ /**\r
+ * Returns the effective URL of the response\r
+ *\r
+ * This may be different from the request URL if redirects were followed.\r
+ *\r
+ * @return string\r
+ * @link http://pear.php.net/bugs/bug.php?id=18412\r
+ */\r
+ public function getEffectiveUrl()\r
+ {\r
+ return $this->effectiveUrl;\r
+ }\r
+\r
/**\r
* Returns the status code\r
- * @return integer \r
+ * @return integer\r
*/\r
public function getStatus()\r
{\r
return $this->reasonPhrase;\r
}\r
\r
+ /**\r
+ * Whether response is a redirect that can be automatically handled by HTTP_Request2\r
+ * @return bool\r
+ */\r
+ public function isRedirect()\r
+ {\r
+ return in_array($this->code, array(300, 301, 302, 303, 307))\r
+ && isset($this->headers['location']);\r
+ }\r
+\r
/**\r
* Returns either the named header or all response headers\r
*\r
*/\r
public function getBody()\r
{\r
- if (!$this->bodyEncoded ||\r
+ if (0 == strlen($this->body) || !$this->bodyEncoded ||\r
!in_array(strtolower($this->getHeader('content-encoding')), array('gzip', 'deflate'))\r
) {\r
return $this->body;\r
* Get the HTTP version of the response\r
*\r
* @return string\r
- */ \r
+ */\r
public function getVersion()\r
{\r
return $this->version;\r
*\r
* @param string gzip-encoded data\r
* @return string decoded data\r
- * @throws HTTP_Request2_Exception\r
+ * @throws HTTP_Request2_LogicException\r
+ * @throws HTTP_Request2_MessageException\r
* @link http://tools.ietf.org/html/rfc1952\r
*/\r
public static function decodeGzip($data)\r
return $data;\r
}\r
if (!function_exists('gzinflate')) {\r
- throw new HTTP_Request2_Exception('Unable to decode body: gzip extension not available');\r
+ throw new HTTP_Request2_LogicException(\r
+ 'Unable to decode body: gzip extension not available',\r
+ HTTP_Request2_Exception::MISCONFIGURATION\r
+ );\r
}\r
$method = ord(substr($data, 2, 1));\r
if (8 != $method) {\r
- throw new HTTP_Request2_Exception('Error parsing gzip header: unknown compression method');\r
+ throw new HTTP_Request2_MessageException(\r
+ 'Error parsing gzip header: unknown compression method',\r
+ HTTP_Request2_Exception::DECODE_ERROR\r
+ );\r
}\r
$flags = ord(substr($data, 3, 1));\r
if ($flags & 224) {\r
- throw new HTTP_Request2_Exception('Error parsing gzip header: reserved bits are set');\r
+ throw new HTTP_Request2_MessageException(\r
+ 'Error parsing gzip header: reserved bits are set',\r
+ HTTP_Request2_Exception::DECODE_ERROR\r
+ );\r
}\r
\r
// header is 10 bytes minimum. may be longer, though.\r
// extra fields, need to skip 'em\r
if ($flags & 4) {\r
if ($length - $headerLength - 2 < 8) {\r
- throw new HTTP_Request2_Exception('Error parsing gzip header: data too short');\r
+ throw new HTTP_Request2_MessageException(\r
+ 'Error parsing gzip header: data too short',\r
+ HTTP_Request2_Exception::DECODE_ERROR\r
+ );\r
}\r
$extraLength = unpack('v', substr($data, 10, 2));\r
if ($length - $headerLength - 2 - $extraLength[1] < 8) {\r
- throw new HTTP_Request2_Exception('Error parsing gzip header: data too short');\r
+ throw new HTTP_Request2_MessageException(\r
+ 'Error parsing gzip header: data too short',\r
+ HTTP_Request2_Exception::DECODE_ERROR\r
+ );\r
}\r
$headerLength += $extraLength[1] + 2;\r
}\r
// file name, need to skip that\r
if ($flags & 8) {\r
if ($length - $headerLength - 1 < 8) {\r
- throw new HTTP_Request2_Exception('Error parsing gzip header: data too short');\r
+ throw new HTTP_Request2_MessageException(\r
+ 'Error parsing gzip header: data too short',\r
+ HTTP_Request2_Exception::DECODE_ERROR\r
+ );\r
}\r
$filenameLength = strpos(substr($data, $headerLength), chr(0));\r
if (false === $filenameLength || $length - $headerLength - $filenameLength - 1 < 8) {\r
- throw new HTTP_Request2_Exception('Error parsing gzip header: data too short');\r
+ throw new HTTP_Request2_MessageException(\r
+ 'Error parsing gzip header: data too short',\r
+ HTTP_Request2_Exception::DECODE_ERROR\r
+ );\r
}\r
$headerLength += $filenameLength + 1;\r
}\r
// comment, need to skip that also\r
if ($flags & 16) {\r
if ($length - $headerLength - 1 < 8) {\r
- throw new HTTP_Request2_Exception('Error parsing gzip header: data too short');\r
+ throw new HTTP_Request2_MessageException(\r
+ 'Error parsing gzip header: data too short',\r
+ HTTP_Request2_Exception::DECODE_ERROR\r
+ );\r
}\r
$commentLength = strpos(substr($data, $headerLength), chr(0));\r
if (false === $commentLength || $length - $headerLength - $commentLength - 1 < 8) {\r
- throw new HTTP_Request2_Exception('Error parsing gzip header: data too short');\r
+ throw new HTTP_Request2_MessageException(\r
+ 'Error parsing gzip header: data too short',\r
+ HTTP_Request2_Exception::DECODE_ERROR\r
+ );\r
}\r
$headerLength += $commentLength + 1;\r
}\r
// have a CRC for header. let's check\r
if ($flags & 2) {\r
if ($length - $headerLength - 2 < 8) {\r
- throw new HTTP_Request2_Exception('Error parsing gzip header: data too short');\r
+ throw new HTTP_Request2_MessageException(\r
+ 'Error parsing gzip header: data too short',\r
+ HTTP_Request2_Exception::DECODE_ERROR\r
+ );\r
}\r
$crcReal = 0xffff & crc32(substr($data, 0, $headerLength));\r
$crcStored = unpack('v', substr($data, $headerLength, 2));\r
if ($crcReal != $crcStored[1]) {\r
- throw new HTTP_Request2_Exception('Header CRC check failed');\r
+ throw new HTTP_Request2_MessageException(\r
+ 'Header CRC check failed',\r
+ HTTP_Request2_Exception::DECODE_ERROR\r
+ );\r
}\r
$headerLength += 2;\r
}\r
// don't pass $dataSize to gzinflate, see bugs #13135, #14370\r
$unpacked = gzinflate(substr($data, $headerLength, -8));\r
if (false === $unpacked) {\r
- throw new HTTP_Request2_Exception('gzinflate() call failed');\r
+ throw new HTTP_Request2_MessageException(\r
+ 'gzinflate() call failed',\r
+ HTTP_Request2_Exception::DECODE_ERROR\r
+ );\r
} elseif ($dataSize != strlen($unpacked)) {\r
- throw new HTTP_Request2_Exception('Data size check failed');\r
+ throw new HTTP_Request2_MessageException(\r
+ 'Data size check failed',\r
+ HTTP_Request2_Exception::DECODE_ERROR\r
+ );\r
} elseif ((0xffffffff & $dataCrc) != (0xffffffff & crc32($unpacked))) {\r
- throw new HTTP_Request2_Exception('Data CRC check failed');\r
+ throw new HTTP_Request2_Exception(\r
+ 'Data CRC check failed',\r
+ HTTP_Request2_Exception::DECODE_ERROR\r
+ );\r
}\r
return $unpacked;\r
}\r
*\r
* @param string deflate-encoded data\r
* @return string decoded data\r
- * @throws HTTP_Request2_Exception\r
+ * @throws HTTP_Request2_LogicException\r
*/\r
public static function decodeDeflate($data)\r
{\r
if (!function_exists('gzuncompress')) {\r
- throw new HTTP_Request2_Exception('Unable to decode body: gzip extension not available');\r
+ throw new HTTP_Request2_LogicException(\r
+ 'Unable to decode body: gzip extension not available',\r
+ HTTP_Request2_Exception::MISCONFIGURATION\r
+ );\r
}\r
// RFC 2616 defines 'deflate' encoding as zlib format from RFC 1950,\r
// while many applications send raw deflate stream from RFC 1951.\r