8 * This source file is subject to the new BSD license that is bundled
9 * with this package in the file LICENSE.txt.
10 * It is also available through the world-wide-web at this URL:
11 * http://framework.zend.com/license/new-bsd
12 * If you did not receive a copy of the license and are unable to
13 * obtain it through the world-wide-web, please send an email
14 * to license@zend.com so we can send you a copy immediately.
18 * @subpackage Response
20 * @copyright Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
21 * @license http://framework.zend.com/license/new-bsd New BSD License
25 * Zend_Http_Response represents an HTTP 1.0 / 1.1 response message. It
26 * includes easy access to all the response's different elemts, as well as some
27 * convenience methods for parsing and validating HTTP responses.
30 * @subpackage Response
31 * @copyright Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
32 * @license http://framework.zend.com/license/new-bsd New BSD License
34 class Zend_Http_Response
37 * List of all known HTTP response codes - used by responseCodeAsText() to
38 * translate numeric codes to messages.
42 protected static $messages = array(
45 101 => 'Switching Protocols',
51 203 => 'Non-Authoritative Information',
53 205 => 'Reset Content',
54 206 => 'Partial Content',
57 300 => 'Multiple Choices',
58 301 => 'Moved Permanently',
59 302 => 'Found', // 1.1
61 304 => 'Not Modified',
63 // 306 is deprecated but reserved
64 307 => 'Temporary Redirect',
68 401 => 'Unauthorized',
69 402 => 'Payment Required',
72 405 => 'Method Not Allowed',
73 406 => 'Not Acceptable',
74 407 => 'Proxy Authentication Required',
75 408 => 'Request Timeout',
78 411 => 'Length Required',
79 412 => 'Precondition Failed',
80 413 => 'Request Entity Too Large',
81 414 => 'Request-URI Too Long',
82 415 => 'Unsupported Media Type',
83 416 => 'Requested Range Not Satisfiable',
84 417 => 'Expectation Failed',
87 500 => 'Internal Server Error',
88 501 => 'Not Implemented',
90 503 => 'Service Unavailable',
91 504 => 'Gateway Timeout',
92 505 => 'HTTP Version Not Supported',
93 509 => 'Bandwidth Limit Exceeded'
97 * The HTTP version (1.0, 1.1)
104 * The HTTP response code
111 * The HTTP response code as string
112 * (e.g. 'Not Found' for 404 or 'Internal Server Error' for 500)
119 * The HTTP response headers array
123 protected $headers = array();
126 * The HTTP response body
133 * HTTP response constructor
135 * In most cases, you would use Zend_Http_Response::fromString to parse an HTTP
136 * response string and create a new Zend_Http_Response object.
138 * NOTE: The constructor no longer accepts nulls or empty values for the code and
139 * headers and will throw an exception if the passed values do not form a valid HTTP
142 * If no message is passed, the message will be guessed according to the response code.
144 * @param int $code Response code (200, 404, ...)
145 * @param array $headers Headers array
146 * @param string $body Response body
147 * @param string $version HTTP version
148 * @param string $message Response code as text
149 * @throws Zend_Http_Exception
151 public function __construct($code, array $headers, $body = null, $version = '1.1', $message = null)
153 // Make sure the response code is valid and set it
154 if (self::responseCodeAsText($code) === null) {
155 require_once 'Zend/Http/Exception.php';
156 throw new Zend_Http_Exception("{$code} is not a valid HTTP response code");
161 foreach ($headers as $name => $value) {
163 $header = explode(":", $value, 2);
164 if (count($header) != 2) {
165 require_once 'Zend/Http/Exception.php';
166 throw new Zend_Http_Exception("'{$value}' is not a valid HTTP header");
169 $name = trim($header[0]);
170 $value = trim($header[1]);
173 $this->headers[ucwords(strtolower($name))] = $value;
179 // Set the HTTP version
180 if (! preg_match('|^\d\.\d$|', $version)) {
181 require_once 'Zend/Http/Exception.php';
182 throw new Zend_Http_Exception("Invalid HTTP response version: $version");
185 $this->version = $version;
187 // If we got the response message, set it. Else, set it according to
189 if (is_string($message)) {
190 $this->message = $message;
192 $this->message = self::responseCodeAsText($code);
197 * Check whether the response is an error
201 public function isError()
203 $restype = floor($this->code / 100);
204 if ($restype == 4 || $restype == 5) {
212 * Check whether the response in successful
216 public function isSuccessful()
218 $restype = floor($this->code / 100);
219 if ($restype == 2 || $restype == 1) { // Shouldn't 3xx count as success as well ???
227 * Check whether the response is a redirection
231 public function isRedirect()
233 $restype = floor($this->code / 100);
242 * Get the response body as string
244 * This method returns the body of the HTTP response (the content), as it
245 * should be in it's readable version - that is, after decoding it (if it
246 * was decoded), deflating it (if it was gzip compressed), etc.
248 * If you want to get the raw body (as transfered on wire) use
249 * $this->getRawBody() instead.
253 public function getBody()
257 // Decode the body if it was transfer-encoded
258 switch (strtolower($this->getHeader('transfer-encoding'))) {
260 // Handle chunked body
262 $body = self::decodeChunkedBody($this->body);
265 // No transfer encoding, or unknown encoding extension:
272 // Decode any content-encoding (gzip or deflate) if needed
273 switch (strtolower($this->getHeader('content-encoding'))) {
275 // Handle gzip encoding
277 $body = self::decodeGzip($body);
280 // Handle deflate encoding
282 $body = self::decodeDeflate($body);
293 * Get the raw response body (as transfered "on wire") as string
295 * If the body is encoded (with Transfer-Encoding, not content-encoding -
296 * IE "chunked" body), gzip compressed, etc. it will not be decoded.
300 public function getRawBody()
306 * Get the HTTP version of the response
310 public function getVersion()
312 return $this->version;
316 * Get the HTTP response status code
320 public function getStatus()
326 * Return a message describing the HTTP response code
327 * (Eg. "OK", "Not Found", "Moved Permanently")
331 public function getMessage()
333 return $this->message;
337 * Get the response headers
341 public function getHeaders()
343 return $this->headers;
347 * Get a specific header as string, or null if it is not set
349 * @param string$header
350 * @return string|array|null
352 public function getHeader($header)
354 $header = ucwords(strtolower($header));
355 if (! is_string($header) || ! isset($this->headers[$header])) return null;
357 return $this->headers[$header];
361 * Get all headers as string
363 * @param boolean $status_line Whether to return the first status line (IE "HTTP 200 OK")
364 * @param string $br Line breaks (eg. "\n", "\r\n", "<br />")
367 public function getHeadersAsString($status_line = true, $br = "\n")
372 $str = "HTTP/{$this->version} {$this->code} {$this->message}{$br}";
375 // Iterate over the headers and stringify them
376 foreach ($this->headers as $name => $value)
378 if (is_string($value))
379 $str .= "{$name}: {$value}{$br}";
381 elseif (is_array($value)) {
382 foreach ($value as $subval) {
383 $str .= "{$name}: {$subval}{$br}";
392 * Get the entire response as string
394 * @param string $br Line breaks (eg. "\n", "\r\n", "<br />")
397 public function asString($br = "\n")
399 return $this->getHeadersAsString(true, $br) . $br . $this->getRawBody();
403 * Implements magic __toString()
407 public function __toString()
409 return $this->asString();
413 * A convenience function that returns a text representation of
414 * HTTP response codes. Returns 'Unknown' for unknown codes.
415 * Returns array of all codes, if $code is not specified.
417 * Conforms to HTTP/1.1 as defined in RFC 2616 (except for 'Unknown')
418 * See http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10 for reference
420 * @param int $code HTTP response code
421 * @param boolean $http11 Use HTTP version 1.1
424 public static function responseCodeAsText($code = null, $http11 = true)
426 $messages = self::$messages;
427 if (! $http11) $messages[302] = 'Moved Temporarily';
429 if ($code === null) {
431 } elseif (isset($messages[$code])) {
432 return $messages[$code];
439 * Extract the response code from a response string
441 * @param string $response_str
444 public static function extractCode($response_str)
446 preg_match("|^HTTP/[\d\.x]+ (\d+)|", $response_str, $m);
456 * Extract the HTTP message from a response
458 * @param string $response_str
461 public static function extractMessage($response_str)
463 preg_match("|^HTTP/[\d\.x]+ \d+ ([^\r\n]+)|", $response_str, $m);
473 * Extract the HTTP version from a response
475 * @param string $response_str
478 public static function extractVersion($response_str)
480 preg_match("|^HTTP/([\d\.x]+) \d+|", $response_str, $m);
490 * Extract the headers from a response string
492 * @param string $response_str
495 public static function extractHeaders($response_str)
499 // First, split body and headers
500 $parts = preg_split('|(?:\r?\n){2}|m', $response_str, 2);
501 if (! $parts[0]) return $headers;
503 // Split headers part to lines
504 $lines = explode("\n", $parts[0]);
508 foreach($lines as $line) {
509 $line = trim($line, "\r\n");
510 if ($line == "") break;
512 // Locate headers like 'Location: ...' and 'Location:...' (note the missing space)
513 if (preg_match("|^([\w-]+):\s*(.+)|", $line, $m)) {
515 $h_name = strtolower($m[1]);
518 if (isset($headers[$h_name])) {
519 if (! is_array($headers[$h_name])) {
520 $headers[$h_name] = array($headers[$h_name]);
523 $headers[$h_name][] = $h_value;
525 $headers[$h_name] = $h_value;
527 $last_header = $h_name;
528 } elseif (preg_match("|^\s+(.+)$|", $line, $m) && $last_header !== null) {
529 if (is_array($headers[$last_header])) {
530 end($headers[$last_header]);
531 $last_header_key = key($headers[$last_header]);
532 $headers[$last_header][$last_header_key] .= $m[1];
534 $headers[$last_header] .= $m[1];
543 * Extract the body from a response string
545 * @param string $response_str
548 public static function extractBody($response_str)
550 $parts = preg_split('|(?:\r?\n){2}|m', $response_str, 2);
551 if (isset($parts[1])) {
558 * Decode a "chunked" transfer-encoded body and return the decoded text
560 * @param string $body
563 public static function decodeChunkedBody($body)
567 // If mbstring overloads substr and strlen functions, we have to
568 // override it's internal encoding
569 if (function_exists('mb_internal_encoding') &&
570 ((int) ini_get('mbstring.func_overload')) & 2) {
572 $mbIntEnc = mb_internal_encoding();
573 mb_internal_encoding('ASCII');
576 while (trim($body)) {
577 if (! preg_match("/^([\da-fA-F]+)[^\r\n]*\r\n/sm", $body, $m)) {
578 require_once 'Zend/Http/Exception.php';
579 throw new Zend_Http_Exception("Error parsing body - doesn't seem to be a chunked message");
582 $length = hexdec(trim($m[1]));
583 $cut = strlen($m[0]);
584 $decBody .= substr($body, $cut, $length);
585 $body = substr($body, $cut + $length + 2);
588 if (isset($mbIntEnc)) {
589 mb_internal_encoding($mbIntEnc);
596 * Decode a gzip encoded message (when Content-encoding = gzip)
598 * Currently requires PHP with zlib support
600 * @param string $body
603 public static function decodeGzip($body)
605 if (! function_exists('gzinflate')) {
606 require_once 'Zend/Http/Exception.php';
607 throw new Zend_Http_Exception(
608 'zlib extension is required in order to decode "gzip" encoding'
612 return gzinflate(substr($body, 10));
616 * Decode a zlib deflated message (when Content-encoding = deflate)
618 * Currently requires PHP with zlib support
620 * @param string $body
623 public static function decodeDeflate($body)
625 if (! function_exists('gzuncompress')) {
626 require_once 'Zend/Http/Exception.php';
627 throw new Zend_Http_Exception(
628 'zlib extension is required in order to decode "deflate" encoding'
633 * Some servers (IIS ?) send a broken deflate response, without the
634 * RFC-required zlib header.
636 * We try to detect the zlib header, and if it does not exsit we
637 * teat the body is plain DEFLATE content.
639 * This method was adapted from PEAR HTTP_Request2 by (c) Alexey Borzov
641 * @link http://framework.zend.com/issues/browse/ZF-6040
643 $zlibHeader = unpack('n', substr($body, 0, 2));
644 if ($zlibHeader[1] % 31 == 0) {
645 return gzuncompress($body);
647 return gzinflate($body);
652 * Create a new Zend_Http_Response object from a string
654 * @param string $response_str
655 * @return Zend_Http_Response
657 public static function fromString($response_str)
659 $code = self::extractCode($response_str);
660 $headers = self::extractHeaders($response_str);
661 $body = self::extractBody($response_str);
662 $version = self::extractVersion($response_str);
663 $message = self::extractMessage($response_str);
665 return new Zend_Http_Response($code, $headers, $body, $version, $message);