SHINDIG-1056 by lipeng, BasicRemoteContentTest doesn't depend on static private key...
[shindig.git] / php / external / Zend / Http / Response.php
blob589b941e28037309933f1fec197acc5f1994a1cf
1 <?php
3 /**
4 * Zend Framework
6 * LICENSE
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.
16 * @category Zend
17 * @package Zend_Http
18 * @subpackage Response
19 * @version $Id: Response.php 8064 2008-02-16 10:58:39Z thomas $
20 * @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com)
21 * @license http://framework.zend.com/license/new-bsd New BSD License
24 /**
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.
29 * @package Zend_Http
30 * @subpackage Response
31 * @copyright Copyright (c) 2005-2008 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 {
35 /**
36 * List of all known HTTP response codes - used by responseCodeAsText() to
37 * translate numeric codes to messages.
39 * @var array
41 protected static $messages = array(// Informational 1xx
42 100 => 'Continue', 101 => 'Switching Protocols',
44 // Success 2xx
45 200 => 'OK', 201 => 'Created', 202 => 'Accepted', 203 => 'Non-Authoritative Information',
46 204 => 'No Content', 205 => 'Reset Content', 206 => 'Partial Content',
48 // Redirection 3xx
49 300 => 'Multiple Choices', 301 => 'Moved Permanently',
50 302 => 'Found', // 1.1
51 303 => 'See Other', 304 => 'Not Modified', 305 => 'Use Proxy',
52 // 306 is deprecated but reserved
53 307 => 'Temporary Redirect',
55 // Client Error 4xx
56 400 => 'Bad Request', 401 => 'Unauthorized',
57 402 => 'Payment Required', 403 => 'Forbidden', 404 => 'Not Found',
58 405 => 'Method Not Allowed', 406 => 'Not Acceptable',
59 407 => 'Proxy Authentication Required', 408 => 'Request Timeout', 409 => 'Conflict',
60 410 => 'Gone', 411 => 'Length Required', 412 => 'Precondition Failed',
61 413 => 'Request Entity Too Large', 414 => 'Request-URI Too Long',
62 415 => 'Unsupported Media Type', 416 => 'Requested Range Not Satisfiable',
63 417 => 'Expectation Failed',
65 // Server Error 5xx
66 500 => 'Internal Server Error', 501 => 'Not Implemented',
67 502 => 'Bad Gateway', 503 => 'Service Unavailable', 504 => 'Gateway Timeout',
68 505 => 'HTTP Version Not Supported', 509 => 'Bandwidth Limit Exceeded');
70 /**
71 * The HTTP version (1.0, 1.1)
73 * @var string
75 protected $version;
77 /**
78 * The HTTP response code
80 * @var int
82 protected $code;
84 /**
85 * The HTTP response code as string
86 * (e.g. 'Not Found' for 404 or 'Internal Server Error' for 500)
88 * @var string
90 protected $message;
92 /**
93 * The HTTP response headers array
95 * @var array
97 protected $headers = array();
99 /**
100 * The HTTP response body
102 * @var string
104 protected $body;
107 * HTTP response constructor
109 * In most cases, you would use Zend_Http_Response::fromString to parse an HTTP
110 * response string and create a new Zend_Http_Response object.
112 * NOTE: The constructor no longer accepts nulls or empty values for the code and
113 * headers and will throw an exception if the passed values do not form a valid HTTP
114 * responses.
116 * If no message is passed, the message will be guessed according to the response code.
118 * @param int $code Response code (200, 404, ...)
119 * @param array $headers Headers array
120 * @param string $body Response body
121 * @param string $version HTTP version
122 * @param string $message Response code as text
123 * @throws Zend_Http_Exception
125 public function __construct($code, $headers, $body = null, $version = '1.1', $message = null) {
126 // Make sure the response code is valid and set it
127 if (self::responseCodeAsText($code) === null) {
128 require_once 'external/Zend/Http/Exception.php';
129 throw new Zend_Http_Exception("{$code} is not a valid HTTP response code");
132 $this->code = $code;
134 // Make sure we got valid headers and set them
135 if (! is_array($headers)) {
136 require_once 'external/Zend/Http/Exception.php';
137 throw new Zend_Http_Exception('No valid headers were passed');
140 foreach ($headers as $name => $value) {
141 if (is_int($name)) list($name, $value) = explode(": ", $value, 1);
143 $this->headers[ucwords(strtolower($name))] = $value;
146 // Set the body
147 $this->body = $body;
149 // Set the HTTP version
150 if (! preg_match('|^\d\.\d$|', $version)) {
151 require_once 'external/Zend/Http/Exception.php';
152 throw new Zend_Http_Exception("Invalid HTTP response version: $version");
155 $this->version = $version;
157 // If we got the response message, set it. Else, set it according to
158 // the response code
159 if (is_string($message)) {
160 $this->message = $message;
161 } else {
162 $this->message = self::responseCodeAsText($code);
167 * Check whether the response is an error
169 * @return boolean
171 public function isError() {
172 $restype = floor($this->code / 100);
173 if ($restype == 4 || $restype == 5) {
174 return true;
177 return false;
181 * Check whether the response in successful
183 * @return boolean
185 public function isSuccessful() {
186 $restype = floor($this->code / 100);
187 if ($restype == 2 || $restype == 1) { // Shouldn't 3xx count as success as well ???
188 return true;
191 return false;
195 * Check whether the response is a redirection
197 * @return boolean
199 public function isRedirect() {
200 $restype = floor($this->code / 100);
201 if ($restype == 3) {
202 return true;
205 return false;
209 * Get the response body as string
211 * This method returns the body of the HTTP response (the content), as it
212 * should be in it's readable version - that is, after decoding it (if it
213 * was decoded), deflating it (if it was gzip compressed), etc.
215 * If you want to get the raw body (as transfered on wire) use
216 * $this->getRawBody() instead.
218 * @return string
220 public function getBody() {
221 $body = '';
223 // Decode the body if it was transfer-encoded
224 switch ($this->getHeader('transfer-encoding')) {
226 // Handle chunked body
227 case 'chunked':
228 $body = self::decodeChunkedBody($this->body);
229 break;
231 // No transfer encoding, or unknown encoding extension:
232 // return body as is
233 default:
234 $body = $this->body;
235 break;
238 // Decode any content-encoding (gzip or deflate) if needed
239 switch (strtolower($this->getHeader('content-encoding'))) {
241 // Handle gzip encoding
242 case 'gzip':
243 $body = self::decodeGzip($body);
244 break;
246 // Handle deflate encoding
247 case 'deflate':
248 $body = self::decodeDeflate($body);
249 break;
251 default:
252 break;
255 return $body;
259 * Get the raw response body (as transfered "on wire") as string
261 * If the body is encoded (with Transfer-Encoding, not content-encoding -
262 * IE "chunked" body), gzip compressed, etc. it will not be decoded.
264 * @return string
266 public function getRawBody() {
267 return $this->body;
271 * Get the HTTP version of the response
273 * @return string
275 public function getVersion() {
276 return $this->version;
280 * Get the HTTP response status code
282 * @return int
284 public function getStatus() {
285 return $this->code;
289 * Return a message describing the HTTP response code
290 * (Eg. "OK", "Not Found", "Moved Permanently")
292 * @return string
294 public function getMessage() {
295 return $this->message;
299 * Get the response headers
301 * @return array
303 public function getHeaders() {
304 return $this->headers;
308 * Get a specific header as string, or null if it is not set
310 * @param string$header
311 * @return string|array|null
313 public function getHeader($header) {
314 $header = ucwords(strtolower($header));
315 if (! is_string($header) || ! isset($this->headers[$header])) return null;
317 return $this->headers[$header];
321 * Get all headers as string
323 * @param boolean $status_line Whether to return the first status line (IE "HTTP 200 OK")
324 * @param string $br Line breaks (eg. "\n", "\r\n", "<br />")
325 * @return string
327 public function getHeadersAsString($status_line = true, $br = "\n") {
328 $str = '';
330 if ($status_line) {
331 $str = "HTTP/{$this->version} {$this->code} {$this->message}{$br}";
334 // Iterate over the headers and stringify them
335 foreach ($this->headers as $name => $value) {
336 if (is_string($value)) $str .= "{$name}: {$value}{$br}";
338 elseif (is_array($value)) {
339 foreach ($value as $subval) {
340 $str .= "{$name}: {$subval}{$br}";
345 return $str;
349 * Get the entire response as string
351 * @param string $br Line breaks (eg. "\n", "\r\n", "<br />")
352 * @return string
354 public function asString($br = "\n") {
355 return $this->getHeadersAsString(true, $br) . $br . $this->getBody();
359 * A convenience function that returns a text representation of
360 * HTTP response codes. Returns 'Unknown' for unknown codes.
361 * Returns array of all codes, if $code is not specified.
363 * Conforms to HTTP/1.1 as defined in RFC 2616 (except for 'Unknown')
364 * See http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10 for reference
366 * @param int $code HTTP response code
367 * @param boolean $http11 Use HTTP version 1.1
368 * @return string
370 public static function responseCodeAsText($code = null, $http11 = true) {
371 $messages = self::$messages;
372 if (! $http11) $messages[302] = 'Moved Temporarily';
374 if ($code === null) {
375 return $messages;
376 } elseif (isset($messages[$code])) {
377 return $messages[$code];
378 } else {
379 return 'Unknown';
384 * Extract the response code from a response string
386 * @param string $response_str
387 * @return int
389 public static function extractCode($response_str) {
390 preg_match("|^HTTP/[\d\.x]+ (\d+)|", $response_str, $m);
392 if (isset($m[1])) {
393 return (int)$m[1];
394 } else {
395 return false;
400 * Extract the HTTP message from a response
402 * @param string $response_str
403 * @return string
405 public static function extractMessage($response_str) {
406 preg_match("|^HTTP/[\d\.x]+ \d+ ([^\r\n]+)|", $response_str, $m);
408 if (isset($m[1])) {
409 return $m[1];
410 } else {
411 return false;
416 * Extract the HTTP version from a response
418 * @param string $response_str
419 * @return string
421 public static function extractVersion($response_str) {
422 preg_match("|^HTTP/([\d\.x]+) \d+|", $response_str, $m);
424 if (isset($m[1])) {
425 return $m[1];
426 } else {
427 return false;
432 * Extract the headers from a response string
434 * @param string $response_str
435 * @return array
437 public static function extractHeaders($response_str) {
438 $headers = array();
440 // First, split body and headers
441 $parts = preg_split('|(?:\r?\n){2}|m', $response_str, 2);
442 if (! $parts[0]) return $headers;
444 // Split headers part to lines
445 $lines = explode("\n", $parts[0]);
446 unset($parts);
447 $last_header = null;
449 foreach ($lines as $line) {
450 $line = trim($line, "\r\n");
451 if ($line == "") break;
453 if (preg_match("|^([\w-]+):\s+(.+)|", $line, $m)) {
454 unset($last_header);
455 $h_name = strtolower($m[1]);
456 $h_value = $m[2];
458 if (isset($headers[$h_name])) {
459 if (! is_array($headers[$h_name])) {
460 $headers[$h_name] = array($headers[$h_name]);
463 $headers[$h_name][] = $h_value;
464 } else {
465 $headers[$h_name] = $h_value;
467 $last_header = $h_name;
468 } elseif (preg_match("|^\s+(.+)$|", $line, $m) && $last_header !== null) {
469 if (is_array($headers[$last_header])) {
470 end($headers[$last_header]);
471 $last_header_key = key($headers[$last_header]);
472 $headers[$last_header][$last_header_key] .= $m[1];
473 } else {
474 $headers[$last_header] .= $m[1];
479 return $headers;
483 * Extract the body from a response string
485 * @param string $response_str
486 * @return string
488 public static function extractBody($response_str) {
489 $parts = preg_split('|(?:\r?\n){2}|m', $response_str, 2);
490 if (isset($parts[1])) {
491 return $parts[1];
492 } else {
493 return '';
498 * Decode a "chunked" transfer-encoded body and return the decoded text
500 * @param string $body
501 * @return string
503 public static function decodeChunkedBody($body) {
504 $decBody = '';
506 while (trim($body)) {
507 if (! preg_match("/^([\da-fA-F]+)[^\r\n]*\r\n/sm", $body, $m)) {
508 require_once 'external/Zend/Http/Exception.php';
509 throw new Zend_Http_Exception("Error parsing body - doesn't seem to be a chunked message");
512 $length = hexdec(trim($m[1]));
513 $cut = strlen($m[0]);
515 $decBody .= substr($body, $cut, $length);
516 $body = substr($body, $cut + $length + 2);
519 return $decBody;
523 * Decode a gzip encoded message (when Content-encoding = gzip)
525 * Currently requires PHP with zlib support
527 * @param string $body
528 * @return string
530 public static function decodeGzip($body) {
531 if (! function_exists('gzinflate')) {
532 require_once 'external/Zend/Http/Exception.php';
533 throw new Zend_Http_Exception('Unable to decode gzipped response ' . 'body: perhaps the zlib extension is not loaded?');
536 return gzinflate(substr($body, 10));
540 * Decode a zlib deflated message (when Content-encoding = deflate)
542 * Currently requires PHP with zlib support
544 * @param string $body
545 * @return string
547 public static function decodeDeflate($body) {
548 if (! function_exists('gzuncompress')) {
549 require_once 'external/Zend/Http/Exception.php';
550 throw new Zend_Http_Exception('Unable to decode deflated response ' . 'body: perhaps the zlib extension is not loaded?');
553 return gzuncompress($body);
557 * Create a new Zend_Http_Response object from a string
559 * @param string $response_str
560 * @return Zend_Http_Response
562 public static function fromString($response_str) {
563 $code = self::extractCode($response_str);
564 $headers = self::extractHeaders($response_str);
565 $body = self::extractBody($response_str);
566 $version = self::extractVersion($response_str);
567 $message = self::extractMessage($response_str);
569 return new Zend_Http_Response($code, $headers, $body, $version, $message);