'Continue', 101 => 'Switching Protocols', 102 => 'Processing', // SUCCESS CODES 200 => 'OK', 201 => 'Created', 202 => 'Accepted', 203 => 'Non-Authoritative Information', 204 => 'No Content', 205 => 'Reset Content', 206 => 'Partial Content', 207 => 'Multi-status', 208 => 'Already Reported', // REDIRECTION CODES 300 => 'Multiple Choices', 301 => 'Moved Permanently', 302 => 'Found', 303 => 'See Other', 304 => 'Not Modified', 305 => 'Use Proxy', 306 => 'Switch Proxy', // Deprecated 307 => 'Temporary Redirect', // CLIENT ERROR 400 => 'Bad Request', 401 => 'Unauthorized', 402 => 'Payment Required', 403 => 'Forbidden', 404 => 'Not Found', 405 => 'Method Not Allowed', 406 => 'Not Acceptable', 407 => 'Proxy Authentication Required', 408 => 'Request Time-out', 409 => 'Conflict', 410 => 'Gone', 411 => 'Length Required', 412 => 'Precondition Failed', 413 => 'Request Entity Too Large', 414 => 'Request-URI Too Long', 415 => 'Unsupported Media Type', 416 => 'Requested range not satisfiable', 417 => 'Expectation Failed', 418 => 'I\'m a teapot', 422 => 'Unprocessable Entity', 423 => 'Locked', 424 => 'Failed Dependency', 425 => 'Unordered Collection', 426 => 'Upgrade Required', 428 => 'Precondition Required', 429 => 'Too Many Requests', 431 => 'Request Header Fields Too Large', // SERVER ERROR 500 => 'Internal Server Error', 501 => 'Not Implemented', 502 => 'Bad Gateway', 503 => 'Service Unavailable', 504 => 'Gateway Time-out', 505 => 'HTTP Version not supported', 506 => 'Variant Also Negotiates', 507 => 'Insufficient Storage', 508 => 'Loop Detected', 511 => 'Network Authentication Required', ); /** * @var int Status code */ protected $statusCode = 200; /** * @var string|null Null means it will be looked up from the $reasonPhrase list above */ protected $reasonPhrase = null; /** * Populate object from string * * @param string $string * @return self * @throws Exception\InvalidArgumentException */ public static function fromString($string) { $lines = explode("\r\n", $string); if (!is_array($lines) || count($lines) == 1) { $lines = explode("\n", $string); } $firstLine = array_shift($lines); $response = new static(); $regex = '/^HTTP\/(?P1\.[01]) (?P\d{3})(?:[ ]+(?P.*))?$/'; $matches = array(); if (!preg_match($regex, $firstLine, $matches)) { throw new Exception\InvalidArgumentException( 'A valid response status line was not found in the provided string' ); } $response->version = $matches['version']; $response->setStatusCode($matches['status']); $response->setReasonPhrase((isset($matches['reason']) ? $matches['reason'] : '')); if (count($lines) == 0) { return $response; } $isHeader = true; $headers = $content = array(); foreach ($lines as $line) { if ($isHeader && $line == '') { $isHeader = false; continue; } if ($isHeader) { if (preg_match("/[\r\n]/", $line)) { throw new Exception\RuntimeException('CRLF injection detected'); } $headers[] = $line; continue; } if (empty($content) && preg_match('/^[a-z0-9!#$%&\'*+.^_`|~-]+:$/i', $line) ) { throw new Exception\RuntimeException('CRLF injection detected'); } $content[] = $line; } if ($headers) { $response->headers = implode("\r\n", $headers); } if ($content) { $response->setContent(implode("\r\n", $content)); } return $response; } /** * @return Header\SetCookie[] */ public function getCookie() { return $this->getHeaders()->get('Set-Cookie'); } /** * Set HTTP status code and (optionally) message * * @param int $code * @throws Exception\InvalidArgumentException * @return self */ public function setStatusCode($code) { $const = get_class($this) . '::STATUS_CODE_' . $code; if (!is_numeric($code) || !defined($const)) { $code = is_scalar($code) ? $code : gettype($code); throw new Exception\InvalidArgumentException(sprintf( 'Invalid status code provided: "%s"', $code )); } return $this->saveStatusCode($code); } /** * Retrieve HTTP status code * * @return int */ public function getStatusCode() { return $this->statusCode; } /** * Set custom HTTP status code * * @param int $code * @throws Exception\InvalidArgumentException * @return self */ public function setCustomStatusCode($code) { if (!is_numeric($code)) { $code = is_scalar($code) ? $code : gettype($code); throw new Exception\InvalidArgumentException(sprintf( 'Invalid status code provided: "%s"', $code )); } return $this->saveStatusCode($code); } /** * Assign status code * * @param int $code * @return self */ protected function saveStatusCode($code) { $this->reasonPhrase = null; $this->statusCode = (int) $code; return $this; } /** * @param string $reasonPhrase * @return self */ public function setReasonPhrase($reasonPhrase) { $this->reasonPhrase = trim($reasonPhrase); return $this; } /** * Get HTTP status message * * @return string */ public function getReasonPhrase() { if (null == $this->reasonPhrase and isset($this->recommendedReasonPhrases[$this->statusCode])) { $this->reasonPhrase = $this->recommendedReasonPhrases[$this->statusCode]; } return $this->reasonPhrase; } /** * Get the body of the response * * @return string */ public function getBody() { $body = (string) $this->getContent(); $transferEncoding = $this->getHeaders()->get('Transfer-Encoding'); if (!empty($transferEncoding)) { if (strtolower($transferEncoding->getFieldValue()) == 'chunked') { $body = $this->decodeChunkedBody($body); } } $contentEncoding = $this->getHeaders()->get('Content-Encoding'); if (!empty($contentEncoding)) { $contentEncoding = $contentEncoding->getFieldValue(); if ($contentEncoding =='gzip') { $body = $this->decodeGzip($body); } elseif ($contentEncoding == 'deflate') { $body = $this->decodeDeflate($body); } } return $body; } /** * Does the status code indicate a client error? * * @return bool */ public function isClientError() { $code = $this->getStatusCode(); return ($code < 500 && $code >= 400); } /** * Is the request forbidden due to ACLs? * * @return bool */ public function isForbidden() { return (403 == $this->getStatusCode()); } /** * Is the current status "informational"? * * @return bool */ public function isInformational() { $code = $this->getStatusCode(); return ($code >= 100 && $code < 200); } /** * Does the status code indicate the resource is not found? * * @return bool */ public function isNotFound() { return (404 === $this->getStatusCode()); } /** * Do we have a normal, OK response? * * @return bool */ public function isOk() { return (200 === $this->getStatusCode()); } /** * Does the status code reflect a server error? * * @return bool */ public function isServerError() { $code = $this->getStatusCode(); return (500 <= $code && 600 > $code); } /** * Do we have a redirect? * * @return bool */ public function isRedirect() { $code = $this->getStatusCode(); return (300 <= $code && 400 > $code); } /** * Was the response successful? * * @return bool */ public function isSuccess() { $code = $this->getStatusCode(); return (200 <= $code && 300 > $code); } /** * Render the status line header * * @return string */ public function renderStatusLine() { $status = sprintf( 'HTTP/%s %d %s', $this->getVersion(), $this->getStatusCode(), $this->getReasonPhrase() ); return trim($status); } /** * Render entire response as HTTP response string * * @return string */ public function toString() { $str = $this->renderStatusLine() . "\r\n"; $str .= $this->getHeaders()->toString(); $str .= "\r\n"; $str .= $this->getContent(); return $str; } /** * Decode a "chunked" transfer-encoded body and return the decoded text * * @param string $body * @return string * @throws Exception\RuntimeException */ protected function decodeChunkedBody($body) { $decBody = ''; while (trim($body)) { if (! preg_match("/^([\da-fA-F]+)[^\r\n]*\r\n/sm", $body, $m)) { throw new Exception\RuntimeException( "Error parsing body - doesn't seem to be a chunked message" ); } $length = hexdec(trim($m[1])); $cut = strlen($m[0]); $decBody .= substr($body, $cut, $length); $body = substr($body, $cut + $length + 2); } return $decBody; } /** * Decode a gzip encoded message (when Content-encoding = gzip) * * Currently requires PHP with zlib support * * @param string $body * @return string * @throws Exception\RuntimeException */ protected function decodeGzip($body) { if (!function_exists('gzinflate')) { throw new Exception\RuntimeException( 'zlib extension is required in order to decode "gzip" encoding' ); } ErrorHandler::start(); $return = gzinflate(substr($body, 10)); $test = ErrorHandler::stop(); if ($test) { throw new Exception\RuntimeException( 'Error occurred during gzip inflation', 0, $test ); } return $return; } /** * Decode a zlib deflated message (when Content-encoding = deflate) * * Currently requires PHP with zlib support * * @param string $body * @return string * @throws Exception\RuntimeException */ protected function decodeDeflate($body) { if (!function_exists('gzuncompress')) { throw new Exception\RuntimeException( 'zlib extension is required in order to decode "deflate" encoding' ); } /** * Some servers (IIS ?) send a broken deflate response, without the * RFC-required zlib header. * * We try to detect the zlib header, and if it does not exist we * teat the body is plain DEFLATE content. * * This method was adapted from PEAR HTTP_Request2 by (c) Alexey Borzov * * @link http://framework.zend.com/issues/browse/ZF-6040 */ $zlibHeader = unpack('n', substr($body, 0, 2)); if ($zlibHeader[1] % 31 == 0) { return gzuncompress($body); } return gzinflate($body); } }