Microsoft_Http
[ class tree: Microsoft_Http ] [ index: Microsoft_Http ] [ all elements ]

Source for file Response.php

Documentation is available at Response.php

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

Documentation generated on Wed, 18 May 2011 12:06:46 +0200 by phpDocumentor 1.4.3