1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | 280 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 | 289 | 290 | 291 | 292 | 293 | 294 | 295 | 296 | 297 | 298 | 299 | 300 | 301 | 302 | 303 | 304 | 305 | 306 | 307 | 308 | 309 | 310 | 311 | 312 | 313 | 314 | 315 | 316 | 317 | 318 | 319 | 320 | 321 | 322 | 323 | 324 | 325 | 326 | 327 | 328 | 329 | 330 | 331 | 332 | 333 | 334 | 335 | 336 | 337 | 338 | 339 | 340 | 341 | 342 | 343 | 344 | 345 | 346 | 347 | 348 | 349 | 350 | 351 | 352 | 353 | 354 | 355 | 356 | 357 | 358 | 359 | 360 | 361 | 362 | 363 | 364 | 365 | 366 | 367 | 368 | 369 | 370 | 371 | 372 | 373 | 374 | 375 |
|
<?php
/**
* restWrapper - HTTP stream wrapper, "drop in replacement"
*
* All methods prefixed with stream_ are PHP magic stream methods.
*
* Returns the response content no matter the HTTP response status.
* Supports HTTP/1.1 chunked transfer encodings.
*
* Planned features:
* - HTTP 30x Redirects
* - Write support (i.e. POST/PUT files)
*
* @author Hannes Magnusson <bjori@redpill-linpro.com>
*/
class restWrapper {
// Identical to $http_response_header
public static $lastHeaders = array();
// Must be public so PHP can set it..
public $context;
private $default_options = array();
private $eof = false;
private $contentlen = 0;
// The stream resource
private $fp = null;
// HTTP/1.1 chunked
private $chunked = false;
private $buffer = "";
private $buflen = 0;
public function __construct()
{
$this->default_options = array(
"method" => "GET",
"header" => "", // TODO: Use ini_get();
"user_agent" => "RPL/Wrapper", // TODO: Use ini_get();
"content" => "",
"proxy" => "",
"request_fulluri" => false,
"max_redirects" => 20,
"protocol_version" => "1.1",
"timeout" => 5,
"ignore_errors" => false,
);
}
private function resetProps()
{
// Prevent cross stream contamination
$this->eof = false;
$this->contentlen = 0;
$this->fp = null;
$this->chunked = false;
$this->buffer = "";
$this->bufflen = 0;
}
public function parseHeaders(array $headers)
{
unset($headers[0]);
foreach($headers as $line)
{
list($key, $value) = explode(":", $line, 2);
$value = trim($value);
switch(strtolower($key))
{
case "content-length":
$this->contentlen = $value;
break;
case "transfer-encoding":
$this->chunked = $value == "chunked";
break;
}
}
}
protected function write($buf)
{
$buf .= "\r\n";
//echo $buf;
$len = fwrite($this->fp, $buf);
$buflen = strlen($buf);
if ($len != $buflen)
{
throw new UnexpectedValueException("Cannot write buffer, $len vs $buflen");
}
return true;
}
public function stream_open($path, $mode, $options, $opened_path)
{
$this->resetProps();
$parsed = parse_url($path);
if (!isset($parsed["path"]))
{
$parsed["path"] = "/";
}
if (isset($parsed["query"]))
{
$parsed["query"] = "?" . $parsed["query"];
}
else
{
$parsed["query"] = "";
}
if (!isset($parsed["port"]))
{
$parsed["port"] = $parsed["scheme"] == "https" ? 443 : 80;
}
if (strpbrk($mode, "awx+"))
{
throw new UnexpectedValueException("I don't support writing at the moment");
}
$options = stream_context_get_options($this->context);
if (isset($options["http"]))
{
$options = array_merge($this->default_options, $options["http"]);
}
else
{
$options = $this->default_options;
}
$socket = sprintf("%s://%s:%d", $parsed["scheme"] == "https" ? "ssl" : "tcp", $parsed["host"], $parsed["port"]);
$this->fp = $fp = stream_socket_client($socket, $errno, $errstr, $options["timeout"], STREAM_CLIENT_CONNECT, $this->context);
if (!is_resource($fp))
{
throw new UnexpectedValueException("Connection failed");
}
$this->sendHeaders($options, $parsed);
$headers = $this->streamReadHeaders();
$this->parseHeaders($headers);
return true;
}
protected function sendHeaders($ctx, $url)
{
$buf = sprintf("%s %s%s HTTP/%.1f\r\n", $ctx["method"], $url["path"], $url["query"], $ctx["protocol_version"]);
$buf .= sprintf("Host: %s\r\n", $url["host"]);
// Some webservers require Content-Lenght to be sent before
// Content-Type - which could be part of the user specified header..
// see PHP bug#44603
if ($ctx["content"])
{
if (stripos($ctx["header"], "content-length") === false)
{
$buf .= sprintf("Content-Length: %d\r\n", strlen($ctx["content"]));
}
}
if ($ctx["header"])
{
$buf .= rtrim($ctx["header"]) . "\r\n";
}
// If we have content we _MUST_ send Content-Type headers.. but they
// _SHOULD_ be sent by the user via the "header" key
if ($ctx["content"])
{
if (stripos($buf, "content-type") === false)
{
$buf .= "Content-Type: application/x-www-form-urlencoded\r\n";
trigger_error("Content-type not specified assuming application/x-www-form-urlencoded", E_USER_NOTICE);
}
}
if (isset($url["user"], $url["pass"]))
{
$base = base64_encode($url["user"] . ":" . $url["pass"]);
$buf .= "Authorization: Basic $base\r\n";
}
// TODO: Allow "persistent" connections
$buf .= sprintf("Connection: close\r\n");
if ($ctx["content"])
{
$buf .= "\r\n" . $ctx["content"];
}
return $this->write($buf);
}
public function stream_read($max)
{
// For performance reasons we check right away
if ($this->chunked)
{
//var_dump("Reading chunked");
return $this->streamReadChunked($max);
}
if ($this->contentlen)
{
//var_dump("Reading {$this->contentlen}, but only $max");
static $left = null;
if ($left === null)
{
$left = $this->contentlen;
}
if ($left === 0)
{
// We've read all the content of this response. Reset $left for
// the next request so we get the new contentlen
$left = null;
return null;
}
// If we are asked to read more then the actual content length we
// have to do minor magic
if ($max >= $left)
{
$content = fread($this->fp, $left);
$tmplen = strlen($content);
// If we managed to read everything left of the request
if ($tmplen >= $left)
{
// We tell PHP that. NOTE: We cannot prepare for the next
// request right away as PHP will call us again to make sure
// we have no more data
$left = 0;
$this->eof = true;
}
else
{
// If however the data on the stream was less then we
// requested then we'll have to go again later
$left -= $tmplen;
}
return $content;
}
// We cannot be sure we actually read $max bytes, we have to check
// it explicitly
$content = fread($this->fp, $max);
$left -= strlen($content);
return $content;
}
// We have no idea how much data is on the stream, we'll just have to
// read and read until the stream closes, or PHP doesn't want more
$content = "";
do {
$content .= $line = fread($this->fp, $max);
$max -= strlen($line);
if (feof($this->fp))
{
$this->eof = true;
break;
}
} while($max > 1);
return $content;
}
public function streamReadChunked($max)
{
//echo "\n";
//var_dump($max);
$content = $this->buffer;
$contentlen = $this->buflen;
// If we have enough data in the buffer already we should return it rather
// then keep increasing it
if ($contentlen > $max)
{
$this->buffer = substr($content, $max);
$this->buflen = $contentlen - $max;
$retval = substr($content, 0, $max);
//var_dump("Returning " . strlen($retval) . " I have left: " . strlen($this->buffer));
return $retval;
}
$this->buffer = "";
$this->buflen = 0;
do {
$chunksize = fgets($this->fp);
$chunksize = rtrim($chunksize);
if (!ctype_xdigit($chunksize))
{
return $content;
}
$bufsize = $chunksize = hexdec($chunksize);
//var_dump($bufsize);
if ($bufsize === 0)
{
$this->eof = true;
return $content;
}
do {
$content .= $line = fread($this->fp, $chunksize);
$linelen = strlen($line);
$chunksize -= $linelen;
$contentlen += $linelen;
$this->eof = $eof = feof($this->fp);
} while(!$eof && $chunksize > 0);
// Read the newline marker
if (!$eof)
{
fgets($this->fp);
}
} while(!$eof && $contentlen < $max);
if ($contentlen > $max)
{
$this->buffer = substr($content, $max);
$this->buflen = $contentlen - $max;
$retval = substr($content, 0, $max);
//var_dump("Returning " . strlen($retval) . " I have left: " . strlen($this->buffer));
return $retval;
}
return $content;
}
public function streamReadHeaders()
{
global $http_response_header; // PHP built-in variable
$http_response_header = array();
do {
$line = fgets($this->fp);
if ($line === "\r\n")
{
break;
}
// The new line marker is not supposed to be in the response array
$http_response_header[] = rtrim($line);
} while(true);
// Just so we don't have to do scope magic for $http_response_header
self::$lastHeaders = $http_response_header;
return $http_response_header;
}
public function stream_eof()
{
return !$this->buffer && ($this->eof || feof($this->fp));
}
public function stream_flush()
{
// TODO
// Only relevant when writing, not reading
}
public function stream_stat()
{
// This will never be implemented as none of these values are relevant
return array();
}
public function stream_close()
{
return fclose($this->fp);
}
}
?>
|