1<?php 2 3/** 4 * EasyRdf 5 * 6 * LICENSE 7 * 8 * Copyright (c) 2009-2013 Nicholas J Humfrey. 9 * Copyright (c) 2005-2009 Zend Technologies USA Inc. 10 * All rights reserved. 11 * 12 * Redistribution and use in source and binary forms, with or without 13 * modification, are permitted provided that the following conditions are met: 14 * 1. Redistributions of source code must retain the above copyright 15 * notice, this list of conditions and the following disclaimer. 16 * 2. Redistributions in binary form must reproduce the above copyright notice, 17 * this list of conditions and the following disclaimer in the documentation 18 * and/or other materials provided with the distribution. 19 * 3. The name of the author 'Nicholas J Humfrey" may be used to endorse or 20 * promote products derived from this software without specific prior 21 * written permission. 22 * 23 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 24 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 25 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 26 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 27 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 28 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 29 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 30 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 31 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 32 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 33 * POSSIBILITY OF SUCH DAMAGE. 34 * 35 * @package EasyRdf 36 * @copyright Copyright (c) 2009-2013 Nicholas J Humfrey 37 * @copyright Copyright (c) 2005-2009 Zend Technologies USA Inc. 38 * @license http://www.opensource.org/licenses/bsd-license.php 39 */ 40 41/** 42 * Class that represents an HTTP 1.0 / 1.1 response message. 43 * 44 * @package EasyRdf 45 * @copyright Copyright (c) 2009-2013 Nicholas J Humfrey 46 * Copyright (c) 2005-2009 Zend Technologies USA Inc. 47 * @license http://www.opensource.org/licenses/bsd-license.php 48 */ 49class EasyRdf_Http_Response 50{ 51 52 /** 53 * The HTTP response status code 54 * 55 * @var int 56 */ 57 private $status; 58 59 /** 60 * The HTTP response code as string 61 * (e.g. 'Not Found' for 404 or 'Internal Server Error' for 500) 62 * 63 * @var string 64 */ 65 private $message; 66 67 /** 68 * The HTTP response headers array 69 * 70 * @var array 71 */ 72 private $headers = array(); 73 74 /** 75 * The HTTP response body 76 * 77 * @var string 78 */ 79 private $body; 80 81 /** 82 * Constructor. 83 * 84 * @param int $status HTTP Status code 85 * @param array $headers The HTTP response headers 86 * @param string $body The content of the response 87 * @param string $version The HTTP Version (1.0 or 1.1) 88 * @param string $message The HTTP response Message 89 * @return object EasyRdf_Http_Response 90 */ 91 public function __construct( 92 $status, 93 $headers, 94 $body = null, 95 $version = '1.1', 96 $message = null 97 ) { 98 $this->status = intval($status); 99 $this->body = $body; 100 $this->version = $version; 101 $this->message = $message; 102 103 foreach ($headers as $k => $v) { 104 $k = ucwords(strtolower($k)); 105 $this->headers[$k] = $v; 106 } 107 } 108 109 /** 110 * Check whether the response in successful 111 * 112 * @return boolean 113 */ 114 public function isSuccessful() 115 { 116 return ($this->status >= 200 && $this->status < 300); 117 } 118 119 /** 120 * Check whether the response is an error 121 * 122 * @return boolean 123 */ 124 public function isError() 125 { 126 return ($this->status >= 400 && $this->status < 600); 127 } 128 129 /** 130 * Check whether the response is a redirection 131 * 132 * @return boolean 133 */ 134 public function isRedirect() 135 { 136 return ($this->status >= 300 && $this->status < 400); 137 } 138 139 /** 140 * Get the HTTP response status code 141 * 142 * @return int 143 */ 144 public function getStatus() 145 { 146 return $this->status; 147 } 148 149 /** 150 * Return a message describing the HTTP response code 151 * (Eg. "OK", "Not Found", "Moved Permanently") 152 * 153 * @return string 154 */ 155 public function getMessage() 156 { 157 return $this->message; 158 } 159 160 /** 161 * Get the response body as string 162 * 163 * @return string 164 */ 165 public function getBody() 166 { 167 // Decode the body if it was transfer-encoded 168 switch (strtolower($this->getHeader('transfer-encoding'))) { 169 // Handle chunked body 170 case 'chunked': 171 return self::decodeChunkedBody($this->body); 172 break; 173 174 // No transfer encoding, or unknown encoding extension: 175 // return body as is 176 default: 177 return $this->body; 178 break; 179 } 180 } 181 182 /** 183 * Get the raw response body (as transfered "on wire") as string 184 * 185 * If the body is encoded (with Transfer-Encoding, not content-encoding - 186 * IE "chunked" body), gzip compressed, etc. it will not be decoded. 187 * 188 * @return string 189 */ 190 public function getRawBody() 191 { 192 return $this->body; 193 } 194 195 /** 196 * Get the HTTP version of the response 197 * 198 * @return string 199 */ 200 public function getVersion() 201 { 202 return $this->version; 203 } 204 205 /** 206 * Get the response headers 207 * 208 * @return array 209 */ 210 public function getHeaders() 211 { 212 return $this->headers; 213 } 214 215 /** 216 * Get a specific header as string, or null if it is not set 217 * 218 * @param string$header 219 * @return string|array|null 220 */ 221 public function getHeader($header) 222 { 223 $header = ucwords(strtolower($header)); 224 if (array_key_exists($header, $this->headers)) { 225 return $this->headers[$header]; 226 } else { 227 return null; 228 } 229 } 230 231 /** 232 * Get all headers as string 233 * 234 * @param boolean $statusLine Whether to return the first status line (ie "HTTP 200 OK") 235 * @param string $br Line breaks (eg. "\n", "\r\n", "<br />") 236 * @return string 237 */ 238 public function getHeadersAsString($statusLine = true, $br = "\n") 239 { 240 $str = ''; 241 242 if ($statusLine) { 243 $str = "HTTP/{$this->version} {$this->status} {$this->message}{$br}"; 244 } 245 246 // Iterate over the headers and stringify them 247 foreach ($this->headers as $name => $value) { 248 if (is_string($value)) { 249 $str .= "{$name}: {$value}{$br}"; 250 } elseif (is_array($value)) { 251 foreach ($value as $subval) { 252 $str .= "{$name}: {$subval}{$br}"; 253 } 254 } 255 } 256 257 return $str; 258 } 259 260 /** 261 * Create an EasyRdf_Http_Response object from a HTTP response string 262 * 263 * @param string $responseStr 264 * @return EasyRdf_Http_Response 265 */ 266 public static function fromString($responseStr) 267 { 268 // First, split body and headers 269 $matches = preg_split('|(?:\r?\n){2}|m', $responseStr, 2); 270 if ($matches and sizeof($matches) == 2) { 271 list ($headerLines, $body) = $matches; 272 } else { 273 throw new EasyRdf_Exception( 274 "Failed to parse HTTP response." 275 ); 276 } 277 278 // Split headers part to lines 279 $headerLines = preg_split('|[\r\n]+|m', $headerLines); 280 $status = array_shift($headerLines); 281 if (preg_match("|^HTTP/([\d\.x]+) (\d+) ([^\r\n]+)|", $status, $m)) { 282 $version = $m[1]; 283 $status = $m[2]; 284 $message = $m[3]; 285 } else { 286 throw new EasyRdf_Exception( 287 "Failed to parse HTTP response status line." 288 ); 289 } 290 291 // Process the rest of the header lines 292 $headers = array(); 293 foreach ($headerLines as $line) { 294 if (preg_match("|^([\w-]+):\s+(.+)$|", $line, $m)) { 295 $hName = ucwords(strtolower($m[1])); 296 $hValue = $m[2]; 297 298 if (isset($headers[$hName])) { 299 if (! is_array($headers[$hName])) { 300 $headers[$hName] = array($headers[$hName]); 301 } 302 $headers[$hName][] = $hValue; 303 } else { 304 $headers[$hName] = $hValue; 305 } 306 } 307 } 308 309 return new EasyRdf_Http_Response($status, $headers, $body, $version, $message); 310 } 311 312 313 /** 314 * Decode a "chunked" transfer-encoded body and return the decoded text 315 * 316 * @param string $body 317 * @return string 318 */ 319 public static function decodeChunkedBody($body) 320 { 321 $decBody = ''; 322 323 while (trim($body)) { 324 if (preg_match('/^([\da-fA-F]+)[^\r\n]*\r\n/sm', $body, $m)) { 325 $length = hexdec(trim($m[1])); 326 $cut = strlen($m[0]); 327 $decBody .= substr($body, $cut, $length); 328 $body = substr($body, $cut + $length + 2); 329 } else { 330 throw new EasyRdf_Exception( 331 "Failed to decode chunked body in HTTP response." 332 ); 333 } 334 } 335 336 return $decBody; 337 } 338 339 340 /** 341 * Get the entire response as string 342 * 343 * @param string $br Line breaks (eg. "\n", "\r\n", "<br />") 344 * @return string 345 */ 346 public function asString($br = "\n") 347 { 348 return $this->getHeadersAsString(true, $br) . $br . $this->getRawBody(); 349 } 350 351 /** 352 * Implements magic __toString() 353 * 354 * @return string 355 */ 356 public function __toString() 357 { 358 return $this->asString(); 359 } 360} 361