3 This snippet implements HTTP queries, and allows for most request
4 methods, content types and encodings. It is useful for contacting
5 scripts made to serve HTML forms.
6 - does neither depend upon wget or curl, nor any other extension
7 - you can add ->$params (form variables) on the fly, it's a hash
8 - if the initial URL contains a query string, the vars will be
10 - set the ->$enc very carefully, because many CGI apps and HTTP
11 servers can't deal with it (else "gzip" and "deflate" are nice)
12 - there are abbreviations for the content ->$type values (namely
13 "form" , "url" and "php")
14 - user:password@ pairs may be included in the initially given URL
15 - headers always get normalized to "Studly-Caps"
16 - won't support keep-alive connections
17 - for PUT and other methods, the ->$params var may just hold the
19 - files can be added to the ->params array as hash with specially
20 named fields: "content"/"data", and "filename"/"name" , "type"
21 - you can add authentication information using the standard notation
22 "http://user:passw@www.example.com/..." for ->$url and ->$proxy
24 A response object will have a ->$content field, ->$headers[] and
25 ->len, ->type attributes as well. You could also ->decode() the
26 body, if it is app/vnd.php.serialized or app/x-www-form-urlencoded.
28 Public Domain (use freely, transform into any other license, like
29 LGPL, BSD, MPL, ...; but if you change this into GPL please be so
30 kind and leave your users a hint where to find the free version).
38 var $proto = "HTTP/1.1";
40 var $params = array(); // URL/form post vars, or single request body str
41 var $headers = array();
42 var $cookies = array();
43 var $type = "url"; // content-type, abbrv. for x-www-form-...
44 var $enc = false; // "gzip" or "deflate"
45 var $error="", $io_err=0, $io_err_s="";
46 var $active_client = 1; // enables redirect-following
48 var $proxy = false; // set to "http://host:NN/"
53 function http_request($method="GET", $url="", $params=NULL) {
54 $this->headers["User-Agent"] = "http_query/17.2 {$GLOBALS[ewiki_config][ua]}";
55 $this->headers["Accept"] = "text/html, application/xml;q=0.9, text/xml;q=0.7, xml/*;q=0.6, text/plain;q=0.5, text/*;q=0.1, image/png;q=0.8, image/*;q=0.4, */*+xml;q=0.3; application/x-msword;q=0.001, */*;q=0.075";
56 $this->headers["Accept-Language"] = "en, eo, es;q=0.2, fr;q=0.1, nl;q=0.1, de;q=0.1";
57 $this->headers["Accept-Charset"] = "iso-8859-1, utf-8";
58 $this->headers["Accept-Feature"] = "textonly, tables, !tcpa, !javascript, !activex, !graphic";
59 $this->headers["Accept-Encoding"] = "deflate, gzip, compress, x-gzip, x-bzip2";
60 //$this->headers["Referer"] = '$google';
61 $this->headers["TE"] = "identity, chunked, binary, base64";
62 $this->headers["Connection"] = "close";
63 //$this->headers["Content-Type"] = & $this->type;
65 $this->params = $params;
67 if (strpos($method, "://")) {
68 $url = $method; # glue for incompat PEAR::Http_Request
71 $this->method($method);
76 #-- sets request method
77 function method($str = "GET") {
82 function setcookie($str="name=value", $add="") {
83 $this->cookies[strtok($str,"=")] = strtok("\000").$add;
87 #-- deciphers URL into server+path and query string
88 function setURL($url) {
89 if ($this->method == "GET") {
90 $this->url = strtok($url, "?");
91 if ($uu = strtok("\000")) {
92 $this->setQueryString($uu);
101 #-- decodes a query strings vars into the $params hash
102 function setQueryString($qs) {
103 $qs = ltrim($qs, "?");
104 parse_str($qs, $this->params);
108 #-- returns params as querystring for GET requests
109 function getQueryString() {
111 if (function_exists("http_build_query")) {
112 $qs = http_build_query($this->params);
115 foreach ($this->params as $n=>$v) {
116 $qs .= "&" . urlencode($n) . "=" . urlencode($v);
118 $qs = substr($qs, 1);
124 #-- transforms $params into request body
125 function pack(&$path) {
126 $m = strtoupper($this->method);
129 if (($m == "GET") || ($m == "HEAD")) {
131 $path .= (strpos($path, "?") ? "&" : "?") . $this->getQueryString();
135 elseif (($m == "POST") && is_array($this->params)) {
137 #-- known encoding types
138 $type = $this->type($this->type, 0);
139 if ($type == "url") {
140 $BODY = $this->getQueryString($prep="");
142 elseif ($type == "php") {
143 $BODY = serialize($this->params);
145 elseif ($type == "form") {
146 // boundary doesn't need checking, unique enough
147 $bnd = "snip-".dechex(time())."-".md5(serialize($this->params))
148 . "-".dechex(rand())."-snap";
150 foreach ($this->params as $i=>$v) {
154 ($ct = $v["ct"].$v["type"].$v["content-type"]) || ($ct = "application/octet-stream");
155 $inj = ' filename="' . urlencode($v["name"].$v["file"].$v["filename"]) . '"';
156 $v = $v["data"].$v["content"].$v["body"];
158 $BODY .= "--$bnd\015\012"
159 . "Content-Disposition: form-data; name=\"".urlencode($i)."\"$inj\015\012"
160 . "Content-Type: $ct\015\012"
161 . "Content-Length: " . strlen($v) . "\015\012"
162 . "\015\012$v\015\012";
164 $BODY .= "--$bnd--\015\012";
165 $ct = $this->type("form") . "; boundary=$bnd";
169 $this->error = "unsupported POST encoding";
171 $BODY = & $this->params;
174 $this->headers["Content-Type"] = isset($ct) ? $ct : $this->type($type, 1);
177 #-- PUT, POST, PUSH, P*
178 elseif ($m[0] == "P") {
179 $BODY = & $this->$params;
182 #-- ERROR (but don't complain)
184 $this->error = "unsupported request method '{$this->method}'";
186 $BODY = & $this->params;
193 #-- converts content-type strings from/to shortened nick
194 function type($str, $long=1) {
196 "form" => "multipart/form-data",
197 "url" => "application/x-www-form-urlencoded",
198 "php" => "application/vnd.php.serialized",
200 $trans["multi"] = &$trans["form"];
205 $new = array_search($str, $trans);
207 return( $new ? $new : $str );
211 #-- initiate the configured HTTP request ------------------------------
212 function go($force=0, $asis=0) {
215 $url = $this->prepare_url();
216 if (!$url && !$force) { return; }
217 $BODY = $this->body($url);
218 if (($BODY===false) && !$force) { return; }
219 $HEAD = $this->head($url);
222 if (!$this->connect($url)) {
226 #-- send request data
227 fwrite($this->socket, $HEAD);
228 fwrite($this->socket, $BODY);
232 #-- read response, end connection
233 while (!feof($this->socket) && (strlen($DATA) <= 1<<22)) {
234 $DATA .= fread($this->socket, 32<<10);
236 fclose($this->socket);
237 unset($this->socket);
239 #-- for raw http pings
245 $r = new http_response();
246 $r->from($DATA); // should auto-unset $DATA
249 if ($this->active_client) {
250 $this->auto_actions($r);
258 function start($a=0, $b=0) {
259 return $this->go($a, $b);
263 #-- creates socket connection
264 function connect(&$url) {
265 if ((isset($this->socket) and !feof($this->socket))
266 or ($this->socket = fsockopen($url["host"], $url["port"], $this->io_err, $this->io_err_s, $this->timeout))) {
267 socket_set_blocking($this->socket, true);
268 socket_set_timeout($this->socket, $this->timeout, 555);
272 $this->error = "no socket/connection";
278 #-- separate URL into pieces, prepare special headers
279 function prepare_url() {
280 $this->setURL($this->url);
282 $url = parse_url($this->url);
283 if (strtolower($url["scheme"]) != "http") {
284 $this->error = "unsupported protocol/scheme";
287 if (!$url["host"]) { return; }
288 if (!$url["port"]) { $url["port"] = 80; }
289 if (!$url["path"]) { $url["path"] = "/"; }
290 if ($url["query"]) { $url["path"] .= "?" . $url["query"]; }
294 $url = parse_url($this->proxy);
295 $url["path"] = $this->url;
297 $this->headers["Proxy-Connection"] = $this->headers["Connection"];
301 if ($url["user"] || $url["pass"]) {
302 $this->headers[$proxy."Authorization"] = "Basic " . base64_encode("$url[user]:$url[pass]");
309 #-- generates request body (if any), must be called before ->head()
310 function body(&$url) {
312 #-- encoding of variable $params as request body (according to reqmethod)
313 $BODY = $this->pack($url["path"]);
314 if ($BODY === false) {
317 elseif ($len = strlen($BODY)) {
318 $this->headers["Content-Length"] = $len;
320 $enc_funcs = array("gzip"=>"gzencode", "deflate"=>"gzinflate", "bzip2"=>"bzcompress", "x-bzip2"=>"bzcompress", "compress"=>"gzcompress");
321 if ((strlen($BODY) >= 1024) && ($f = $enc_funcs[$this->enc]) && function_exists($f)) {
323 $this->headers["Content-Encoding"] = $this->enc;
324 $this->headers["Content-Length"] = strlen($BODY);
330 #-- generates request head part
331 function head(&$url) {
333 #-- inject cookie header (if any)
334 if ($this->cookies) {
336 foreach ($this->cookies as $i=>$v) {
337 $c .= "; " . urlencode($i) . "=" . urlencode($v);
339 $this->headers["Cookie"] = substr($c, 2);
340 $this->headers["Cookie2"] = '$Version="1"';
345 $HEAD = "{$this->method} {$url[path]} {$this->proto}$CRLF";
346 $HEAD .= "Host: {$url[host]}$CRLF";
347 foreach ($this->headers as $h=>$v) {
348 $HEAD .= trim($h) . ": " . strtr(trim($v), "\n", " ") . $CRLF;
354 #-- perform some things automatically (redirects)
355 function auto_actions(&$r) {
359 "failure" => "204,300,304,305,306",
360 "clean_::POST" => "300,301,302,303,307",
361 "clean_::PUT" => "300,301,302,303,307",
362 "clean_::GET" => "300", // $params:=undef
363 "GET_::POST" => "303",
364 "GET_::PUT" => "303", // downgrade $method:=GET
368 if (strstr($this->behaviour_table["failure"], $r->status)) {
373 if (($pri_url=$r->headers["Location"]) || ($pri_url=$r->headers["Uri"])) {
375 if ((($this->redirects--) >= 0) && ($r->status >= 300) && ($r->status < 400)) {
376 $m = strtoupper($this->method);
377 if (strstr($this->behaviour_table["clean_::$m"], $r->status)) {
378 unset($this->params);
380 if (strstr($this->behaviour_table["GET_::$m"], $r->status)) {
381 $this->method("GET");
383 $this->setURL($pri_url);
389 #-- aliases for compatiblity to PEAR::HTTP_Request
390 function sendRequest() {
393 function setBasicAuth($user, $pw) {
394 $this->url = preg_replace("#//(.+?@)?#", "//$user@$pw", $this->url);
396 function setMethod($m) {
399 function setProxy($host, $port=8080, $user="", $pw="") {
400 $auth = ($pw ? "$user:$pw@" : ($user ? "$user@" : ""));
401 $this->proxy = "http://$auth$server:$port";
403 function addHeader($h, $v) {
404 $this->headers[$h] = $v;
406 function getResponseStatus() {
407 $this->headers[$h] = $v;
410 class http_query extends http_request {
411 /* this is just an alias */
417 #-- every query result will be encoded in such an object --------------------
418 class http_response {
421 var $status_str = "";
422 var $headers_str = "";
423 var $headers = array();
425 var $type = "message/x-raw";
429 function http_response() {
433 #-- fill object from given HTTP response BLOB
434 function from(&$SRC) {
435 $this->breakHeaders($SRC); // split data into body + headers
437 $this->decodeHeaders(); // normalize header names
439 $this->decodeTransferEncodings(); // chunked
440 $this->decodeContentEncodings(); // gzip, deflate
441 $this->len = strlen($this->content);
445 #-- separates headers block from response body part
446 function breakHeaders(&$DATA) {
447 $l = strpos($DATA, "\012\015\012"); $skip = 3;
448 $r = strpos($DATA, "\012\012");
449 if ($r && ($r<$l)) { $l = $r; $skip = 2; }
450 if (!$l) { $l = strlen($DATA); }
451 $this->headers_str = rtrim(substr($DATA, 0, $l), "\015");
452 $this->content = substr($DATA, $l + $skip);
453 $this->body = & $this->content;
454 $this->data = & $this->content; // aliases
455 $this->ct = & $this->type;
459 #-- splits up the $headers_str into an array and normalizes header names
460 function decodeHeaders() {
462 #-- normalize linebreaks
463 $str = & $this->headers_str;
464 // $str = str_replace("\n ", " ", $str);
465 $str = str_replace("\r", "", $str);
468 $nl = strpos($str, "\n") + 1;
469 $this->proto = strtok(substr($str, 0, $nl), " ");
470 $this->status = (int) strtok(" ");
471 $this->status_str = strtok("\000\r\n");
472 if ($this->status == 100) {
473 $this->full_duplex = 1;
476 #-- go through lines, split name:value pairs
477 foreach (explode("\n", substr($str, $nl)) as $line) {
479 $i = trim(strtok($line, ":"));
480 $v = trim(strtok("\000"));
482 #-- normalize name look&feel
483 $i = strtr(ucwords(strtolower(strtr($i, "-", " "))), " ", "-");
485 #-- add to, if key exists
486 if (!empty($this->headers[$i])) {
487 $this->headers[$i] .= ", ".$v;
490 $this->headers[$i] = $v;
497 #-- extract interesting values
498 function headerMeta() {
499 $this->len = strlen($this->content);
500 $this->type = trim(strtok(strtolower($this->headers["Content-Type"]), ";"));
504 #-- strip any content transformation
505 function decodeTransferEncodings() {
506 $enc = trim(strtok(strtolower($this->headers["Transfer-Encoding"]), ",;"));
510 $this->decodeChunkedEncoding();
513 $this->content = base64_decode($this->content);
514 $this->len = strlen($this->content);
516 case "identity": case "binary":
517 case "7bit": case "8bit":
520 trigger_error("http_response::decodeTransferEncodings: unkown TE of '$enc'\n", E_WARNING);
526 #-- scripts on HTTP/1.1 servers may send fragmented response
527 function decodeChunkedEncoding() {
529 $data = ""; # decoded data
530 $p = 0; # current string position
532 while ($p < strlen($this->content)) {
535 $n = strtok(substr($this->content, $p, 20), "\n");
539 $n = 0 + (int) (trim($n));
545 $data .= substr($this->content, $p, $n);
549 $this->content = $data;
551 $this->len = strlen($this->content);
555 #-- uncompress response body
556 function decodeContentEncodings() {
557 $enc = trim(strtok(strtolower($this->headers["Content-Encoding"]), ";,"));
558 $dat = &$this->content;
559 if ($enc == "deflate") {
560 $dat = gzinflate($dat);
562 elseif (($enc == "gzip") || ($enc == "x-gzip")) {
563 if (function_exists("gzdecode")) {
564 $dat = gzdecode($dat);
567 $dat = gzinflate(substr($dat, 10, strlen($dat)-18));
570 elseif ($enc == "compress") {
571 $dat = gzuncompress($dat);
573 elseif (($enc == "x-bzip2") || ($enc == "bzip2")) {
574 if (function_exists("bzdecompress")) {
575 $dat = bzdecompress($dat);
577 else trigger_error("http_response::decodeContentEncoding: bzip2 decoding isn't supported with this PHP interpreter version", E_WARNING);
579 $this->len = strlen($this->content);
583 #-- can handle special content-types (multipart, serialized, form-data)
585 $t = http_request::type($this->type, 0);
587 return(unserialize($this->content));
589 elseif ($t == "url") {
590 parse_str($this->content, $r);
593 elseif ($t == "form") {
594 // oh, not yet exactly
598 #-- aliases for compatiblity to PEAR::HTTP_Request
599 function getResponseBody() {
600 return $this->content;
602 function getResponseStatus() {
603 return $this->status;
605 function getResponseCode() {
606 return $this->status;
608 function getResponseHeader($i=NULL) {
610 return $this->headers;
613 foreach ($this->headers as $h=>$v) {
614 if (strtolower($h)==$i) {