web/lib/Zend/Http/Response.php
changeset 64 162c1de6545a
parent 19 1c2f13fd785c
child 68 ecaf28ffe26e
equal deleted inserted replaced
63:5b37998e522e 64:162c1de6545a
       
     1 <?php
       
     2 
       
     3 /**
       
     4  * Zend Framework
       
     5  *
       
     6  * LICENSE
       
     7  *
       
     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.
       
    15  *
       
    16  * @category   Zend
       
    17  * @package    Zend_Http
       
    18  * @subpackage Response
       
    19  * @version    $Id: Response.php 22810 2010-08-08 10:29:09Z shahar $
       
    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
       
    22  */
       
    23 
       
    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.
       
    28  *
       
    29  * @package    Zend_Http
       
    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
       
    33  */
       
    34 class Zend_Http_Response
       
    35 {
       
    36     /**
       
    37      * List of all known HTTP response codes - used by responseCodeAsText() to
       
    38      * translate numeric codes to messages.
       
    39      *
       
    40      * @var array
       
    41      */
       
    42     protected static $messages = array(
       
    43         // Informational 1xx
       
    44         100 => 'Continue',
       
    45         101 => 'Switching Protocols',
       
    46 
       
    47         // Success 2xx
       
    48         200 => 'OK',
       
    49         201 => 'Created',
       
    50         202 => 'Accepted',
       
    51         203 => 'Non-Authoritative Information',
       
    52         204 => 'No Content',
       
    53         205 => 'Reset Content',
       
    54         206 => 'Partial Content',
       
    55 
       
    56         // Redirection 3xx
       
    57         300 => 'Multiple Choices',
       
    58         301 => 'Moved Permanently',
       
    59         302 => 'Found',  // 1.1
       
    60         303 => 'See Other',
       
    61         304 => 'Not Modified',
       
    62         305 => 'Use Proxy',
       
    63         // 306 is deprecated but reserved
       
    64         307 => 'Temporary Redirect',
       
    65 
       
    66         // Client Error 4xx
       
    67         400 => 'Bad Request',
       
    68         401 => 'Unauthorized',
       
    69         402 => 'Payment Required',
       
    70         403 => 'Forbidden',
       
    71         404 => 'Not Found',
       
    72         405 => 'Method Not Allowed',
       
    73         406 => 'Not Acceptable',
       
    74         407 => 'Proxy Authentication Required',
       
    75         408 => 'Request Timeout',
       
    76         409 => 'Conflict',
       
    77         410 => 'Gone',
       
    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',
       
    85 
       
    86         // Server Error 5xx
       
    87         500 => 'Internal Server Error',
       
    88         501 => 'Not Implemented',
       
    89         502 => 'Bad Gateway',
       
    90         503 => 'Service Unavailable',
       
    91         504 => 'Gateway Timeout',
       
    92         505 => 'HTTP Version Not Supported',
       
    93         509 => 'Bandwidth Limit Exceeded'
       
    94     );
       
    95 
       
    96     /**
       
    97      * The HTTP version (1.0, 1.1)
       
    98      *
       
    99      * @var string
       
   100      */
       
   101     protected $version;
       
   102 
       
   103     /**
       
   104      * The HTTP response code
       
   105      *
       
   106      * @var int
       
   107      */
       
   108     protected $code;
       
   109 
       
   110     /**
       
   111      * The HTTP response code as string
       
   112      * (e.g. 'Not Found' for 404 or 'Internal Server Error' for 500)
       
   113      *
       
   114      * @var string
       
   115      */
       
   116     protected $message;
       
   117 
       
   118     /**
       
   119      * The HTTP response headers array
       
   120      *
       
   121      * @var array
       
   122      */
       
   123     protected $headers = array();
       
   124 
       
   125     /**
       
   126      * The HTTP response body
       
   127      *
       
   128      * @var string
       
   129      */
       
   130     protected $body;
       
   131 
       
   132     /**
       
   133      * HTTP response constructor
       
   134      *
       
   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.
       
   137      *
       
   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
       
   140      * responses.
       
   141      *
       
   142      * If no message is passed, the message will be guessed according to the response code.
       
   143      *
       
   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
       
   150      */
       
   151     public function __construct($code, array $headers, $body = null, $version = '1.1', $message = null)
       
   152     {
       
   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");
       
   157         }
       
   158 
       
   159         $this->code = $code;
       
   160 
       
   161         foreach ($headers as $name => $value) {
       
   162             if (is_int($name)) {
       
   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");
       
   167                 }
       
   168                 
       
   169                 $name  = trim($header[0]);
       
   170                 $value = trim($header[1]);
       
   171             }
       
   172 
       
   173             $this->headers[ucwords(strtolower($name))] = $value;
       
   174         }
       
   175 
       
   176         // Set the body
       
   177         $this->body = $body;
       
   178 
       
   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");
       
   183         }
       
   184 
       
   185         $this->version = $version;
       
   186 
       
   187         // If we got the response message, set it. Else, set it according to
       
   188         // the response code
       
   189         if (is_string($message)) {
       
   190             $this->message = $message;
       
   191         } else {
       
   192             $this->message = self::responseCodeAsText($code);
       
   193         }
       
   194     }
       
   195 
       
   196     /**
       
   197      * Check whether the response is an error
       
   198      *
       
   199      * @return boolean
       
   200      */
       
   201     public function isError()
       
   202     {
       
   203         $restype = floor($this->code / 100);
       
   204         if ($restype == 4 || $restype == 5) {
       
   205             return true;
       
   206         }
       
   207 
       
   208         return false;
       
   209     }
       
   210 
       
   211     /**
       
   212      * Check whether the response in successful
       
   213      *
       
   214      * @return boolean
       
   215      */
       
   216     public function isSuccessful()
       
   217     {
       
   218         $restype = floor($this->code / 100);
       
   219         if ($restype == 2 || $restype == 1) { // Shouldn't 3xx count as success as well ???
       
   220             return true;
       
   221         }
       
   222 
       
   223         return false;
       
   224     }
       
   225 
       
   226     /**
       
   227      * Check whether the response is a redirection
       
   228      *
       
   229      * @return boolean
       
   230      */
       
   231     public function isRedirect()
       
   232     {
       
   233         $restype = floor($this->code / 100);
       
   234         if ($restype == 3) {
       
   235             return true;
       
   236         }
       
   237 
       
   238         return false;
       
   239     }
       
   240 
       
   241     /**
       
   242      * Get the response body as string
       
   243      *
       
   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.
       
   247      *
       
   248      * If you want to get the raw body (as transfered on wire) use
       
   249      * $this->getRawBody() instead.
       
   250      *
       
   251      * @return string
       
   252      */
       
   253     public function getBody()
       
   254     {
       
   255         $body = '';
       
   256 
       
   257         // Decode the body if it was transfer-encoded
       
   258         switch (strtolower($this->getHeader('transfer-encoding'))) {
       
   259 
       
   260             // Handle chunked body
       
   261             case 'chunked':
       
   262                 $body = self::decodeChunkedBody($this->body);
       
   263                 break;
       
   264 
       
   265             // No transfer encoding, or unknown encoding extension:
       
   266             // return body as is
       
   267             default:
       
   268                 $body = $this->body;
       
   269                 break;
       
   270         }
       
   271 
       
   272         // Decode any content-encoding (gzip or deflate) if needed
       
   273         switch (strtolower($this->getHeader('content-encoding'))) {
       
   274 
       
   275             // Handle gzip encoding
       
   276             case 'gzip':
       
   277                 $body = self::decodeGzip($body);
       
   278                 break;
       
   279 
       
   280             // Handle deflate encoding
       
   281             case 'deflate':
       
   282                 $body = self::decodeDeflate($body);
       
   283                 break;
       
   284 
       
   285             default:
       
   286                 break;
       
   287         }
       
   288 
       
   289         return $body;
       
   290     }
       
   291 
       
   292     /**
       
   293      * Get the raw response body (as transfered "on wire") as string
       
   294      *
       
   295      * If the body is encoded (with Transfer-Encoding, not content-encoding -
       
   296      * IE "chunked" body), gzip compressed, etc. it will not be decoded.
       
   297      *
       
   298      * @return string
       
   299      */
       
   300     public function getRawBody()
       
   301     {
       
   302         return $this->body;
       
   303     }
       
   304 
       
   305     /**
       
   306      * Get the HTTP version of the response
       
   307      *
       
   308      * @return string
       
   309      */
       
   310     public function getVersion()
       
   311     {
       
   312         return $this->version;
       
   313     }
       
   314 
       
   315     /**
       
   316      * Get the HTTP response status code
       
   317      *
       
   318      * @return int
       
   319      */
       
   320     public function getStatus()
       
   321     {
       
   322         return $this->code;
       
   323     }
       
   324 
       
   325     /**
       
   326      * Return a message describing the HTTP response code
       
   327      * (Eg. "OK", "Not Found", "Moved Permanently")
       
   328      *
       
   329      * @return string
       
   330      */
       
   331     public function getMessage()
       
   332     {
       
   333         return $this->message;
       
   334     }
       
   335 
       
   336     /**
       
   337      * Get the response headers
       
   338      *
       
   339      * @return array
       
   340      */
       
   341     public function getHeaders()
       
   342     {
       
   343         return $this->headers;
       
   344     }
       
   345 
       
   346     /**
       
   347      * Get a specific header as string, or null if it is not set
       
   348      *
       
   349      * @param string$header
       
   350      * @return string|array|null
       
   351      */
       
   352     public function getHeader($header)
       
   353     {
       
   354         $header = ucwords(strtolower($header));
       
   355         if (! is_string($header) || ! isset($this->headers[$header])) return null;
       
   356 
       
   357         return $this->headers[$header];
       
   358     }
       
   359 
       
   360     /**
       
   361      * Get all headers as string
       
   362      *
       
   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 />")
       
   365      * @return string
       
   366      */
       
   367     public function getHeadersAsString($status_line = true, $br = "\n")
       
   368     {
       
   369         $str = '';
       
   370 
       
   371         if ($status_line) {
       
   372             $str = "HTTP/{$this->version} {$this->code} {$this->message}{$br}";
       
   373         }
       
   374 
       
   375         // Iterate over the headers and stringify them
       
   376         foreach ($this->headers as $name => $value)
       
   377         {
       
   378             if (is_string($value))
       
   379                 $str .= "{$name}: {$value}{$br}";
       
   380 
       
   381             elseif (is_array($value)) {
       
   382                 foreach ($value as $subval) {
       
   383                     $str .= "{$name}: {$subval}{$br}";
       
   384                 }
       
   385             }
       
   386         }
       
   387 
       
   388         return $str;
       
   389     }
       
   390 
       
   391     /**
       
   392      * Get the entire response as string
       
   393      *
       
   394      * @param string $br Line breaks (eg. "\n", "\r\n", "<br />")
       
   395      * @return string
       
   396      */
       
   397     public function asString($br = "\n")
       
   398     {
       
   399         return $this->getHeadersAsString(true, $br) . $br . $this->getRawBody();
       
   400     }
       
   401 
       
   402     /**
       
   403      * Implements magic __toString()
       
   404      *
       
   405      * @return string
       
   406      */
       
   407     public function __toString()
       
   408     {
       
   409         return $this->asString();
       
   410     }
       
   411 
       
   412     /**
       
   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.
       
   416      *
       
   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
       
   419      *
       
   420      * @param int $code HTTP response code
       
   421      * @param boolean $http11 Use HTTP version 1.1
       
   422      * @return string
       
   423      */
       
   424     public static function responseCodeAsText($code = null, $http11 = true)
       
   425     {
       
   426         $messages = self::$messages;
       
   427         if (! $http11) $messages[302] = 'Moved Temporarily';
       
   428 
       
   429         if ($code === null) {
       
   430             return $messages;
       
   431         } elseif (isset($messages[$code])) {
       
   432             return $messages[$code];
       
   433         } else {
       
   434             return 'Unknown';
       
   435         }
       
   436     }
       
   437 
       
   438     /**
       
   439      * Extract the response code from a response string
       
   440      *
       
   441      * @param string $response_str
       
   442      * @return int
       
   443      */
       
   444     public static function extractCode($response_str)
       
   445     {
       
   446         preg_match("|^HTTP/[\d\.x]+ (\d+)|", $response_str, $m);
       
   447 
       
   448         if (isset($m[1])) {
       
   449             return (int) $m[1];
       
   450         } else {
       
   451             return false;
       
   452         }
       
   453     }
       
   454 
       
   455     /**
       
   456      * Extract the HTTP message from a response
       
   457      *
       
   458      * @param string $response_str
       
   459      * @return string
       
   460      */
       
   461     public static function extractMessage($response_str)
       
   462     {
       
   463         preg_match("|^HTTP/[\d\.x]+ \d+ ([^\r\n]+)|", $response_str, $m);
       
   464 
       
   465         if (isset($m[1])) {
       
   466             return $m[1];
       
   467         } else {
       
   468             return false;
       
   469         }
       
   470     }
       
   471 
       
   472     /**
       
   473      * Extract the HTTP version from a response
       
   474      *
       
   475      * @param string $response_str
       
   476      * @return string
       
   477      */
       
   478     public static function extractVersion($response_str)
       
   479     {
       
   480         preg_match("|^HTTP/([\d\.x]+) \d+|", $response_str, $m);
       
   481 
       
   482         if (isset($m[1])) {
       
   483             return $m[1];
       
   484         } else {
       
   485             return false;
       
   486         }
       
   487     }
       
   488 
       
   489     /**
       
   490      * Extract the headers from a response string
       
   491      *
       
   492      * @param   string $response_str
       
   493      * @return  array
       
   494      */
       
   495     public static function extractHeaders($response_str)
       
   496     {
       
   497         $headers = array();
       
   498 
       
   499         // First, split body and headers
       
   500         $parts = preg_split('|(?:\r?\n){2}|m', $response_str, 2);
       
   501         if (! $parts[0]) return $headers;
       
   502 
       
   503         // Split headers part to lines
       
   504         $lines = explode("\n", $parts[0]);
       
   505         unset($parts);
       
   506         $last_header = null;
       
   507 
       
   508         foreach($lines as $line) {
       
   509             $line = trim($line, "\r\n");
       
   510             if ($line == "") break;
       
   511 
       
   512             // Locate headers like 'Location: ...' and 'Location:...' (note the missing space)
       
   513             if (preg_match("|^([\w-]+):\s*(.+)|", $line, $m)) {
       
   514                 unset($last_header);
       
   515                 $h_name = strtolower($m[1]);
       
   516                 $h_value = $m[2];
       
   517 
       
   518                 if (isset($headers[$h_name])) {
       
   519                     if (! is_array($headers[$h_name])) {
       
   520                         $headers[$h_name] = array($headers[$h_name]);
       
   521                     }
       
   522 
       
   523                     $headers[$h_name][] = $h_value;
       
   524                 } else {
       
   525                     $headers[$h_name] = $h_value;
       
   526                 }
       
   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];
       
   533                 } else {
       
   534                     $headers[$last_header] .= $m[1];
       
   535                 }
       
   536             }
       
   537         }
       
   538 
       
   539         return $headers;
       
   540     }
       
   541 
       
   542     /**
       
   543      * Extract the body from a response string
       
   544      *
       
   545      * @param string $response_str
       
   546      * @return string
       
   547      */
       
   548     public static function extractBody($response_str)
       
   549     {
       
   550         $parts = preg_split('|(?:\r?\n){2}|m', $response_str, 2);
       
   551         if (isset($parts[1])) {
       
   552             return $parts[1];
       
   553         }
       
   554         return '';
       
   555     }
       
   556 
       
   557     /**
       
   558      * Decode a "chunked" transfer-encoded body and return the decoded text
       
   559      *
       
   560      * @param string $body
       
   561      * @return string
       
   562      */
       
   563     public static function decodeChunkedBody($body)
       
   564     {
       
   565         $decBody = '';
       
   566 
       
   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) {
       
   571 
       
   572             $mbIntEnc = mb_internal_encoding();
       
   573             mb_internal_encoding('ASCII');
       
   574         }
       
   575 
       
   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");
       
   580             }
       
   581 
       
   582             $length = hexdec(trim($m[1]));
       
   583             $cut = strlen($m[0]);
       
   584             $decBody .= substr($body, $cut, $length);
       
   585             $body = substr($body, $cut + $length + 2);
       
   586         }
       
   587 
       
   588         if (isset($mbIntEnc)) {
       
   589             mb_internal_encoding($mbIntEnc);
       
   590         }
       
   591 
       
   592         return $decBody;
       
   593     }
       
   594 
       
   595     /**
       
   596      * Decode a gzip encoded message (when Content-encoding = gzip)
       
   597      *
       
   598      * Currently requires PHP with zlib support
       
   599      *
       
   600      * @param string $body
       
   601      * @return string
       
   602      */
       
   603     public static function decodeGzip($body)
       
   604     {
       
   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'
       
   609             );
       
   610         }
       
   611 
       
   612         return gzinflate(substr($body, 10));
       
   613     }
       
   614 
       
   615     /**
       
   616      * Decode a zlib deflated message (when Content-encoding = deflate)
       
   617      *
       
   618      * Currently requires PHP with zlib support
       
   619      *
       
   620      * @param string $body
       
   621      * @return string
       
   622      */
       
   623     public static function decodeDeflate($body)
       
   624     {
       
   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'
       
   629             );
       
   630         }
       
   631 
       
   632         /**
       
   633          * Some servers (IIS ?) send a broken deflate response, without the
       
   634          * RFC-required zlib header.
       
   635          *
       
   636          * We try to detect the zlib header, and if it does not exsit we
       
   637          * teat the body is plain DEFLATE content.
       
   638          *
       
   639          * This method was adapted from PEAR HTTP_Request2 by (c) Alexey Borzov
       
   640          *
       
   641          * @link http://framework.zend.com/issues/browse/ZF-6040
       
   642          */
       
   643         $zlibHeader = unpack('n', substr($body, 0, 2));
       
   644         if ($zlibHeader[1] % 31 == 0) {
       
   645             return gzuncompress($body);
       
   646         } else {
       
   647             return gzinflate($body);
       
   648         }
       
   649     }
       
   650 
       
   651     /**
       
   652      * Create a new Zend_Http_Response object from a string
       
   653      *
       
   654      * @param string $response_str
       
   655      * @return Zend_Http_Response
       
   656      */
       
   657     public static function fromString($response_str)
       
   658     {
       
   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);
       
   664 
       
   665         return new Zend_Http_Response($code, $headers, $body, $version, $message);
       
   666     }
       
   667 }