4 Version 0.9, 6th April 2003 - Simon Willison ( http://simon.incutio.com/ )
5 Manual: http://scripts.incutio.com/httpclient/
7 Copyright © 2003 Incutio Limited
8 License: http://www.opensource.org/licenses/artistic-license.php
10 File upload and xmlrpc support by Reini Urban for PhpWiki, 2006-12-28 18:12:47
22 var $cookies = array();
24 var $accept = 'text/xml,application/xml,application/xhtml+xml,text/html,text/plain,image/png,image/jpeg,image/gif,*/*';
25 var $accept_encoding = 'gzip';
26 var $accept_language = 'en-us';
27 var $user_agent = 'Incutio HttpClient v1.0';
28 var $boundary = "xYzZY"; // FIXME: check if this string doesn't occur in the data
32 var $persist_cookies = true; // If true, received cookies are placed in the $this->cookies array ready for the next request
33 // Note: This currently ignores the cookie path (and time) completely. Time is not important,
34 // but path could possibly lead to security problems.
35 var $persist_referers = true; // For each request, sends path of last request as referer
37 var $handle_redirects = true; // Auaomtically redirect if Location or URI header is found
38 var $max_redirects = 5;
39 var $headers_only = false; // If true, stops receiving once headers have been read.
40 // Basic authorization variables
45 var $headers = array();
49 var $redirect_count = 0;
50 var $cookie_host = '';
52 function HttpClient($host = 'localhost', $port = 80)
58 function get($path, $data = false)
61 $this->method = 'GET';
63 $this->path .= '?' . $this->buildQueryString($data);
65 return $this->doRequest();
68 function post($path, $data)
71 $this->method = 'POST';
72 $this->postdata = $this->buildQueryString($data);
73 return $this->doRequest();
76 function postfile($path, $filename)
79 $this->method = 'POST';
80 $boundary = $this->boundary; //"httpclient_boundary";
81 $headers[] = "Content-Type: multipart/form-data; boundary=\"$boundary\"";
82 $basename = basename($filename);
85 . "Content-Disposition: form-data; filename=\"$basename\"\r\n"
86 . "Content-Type: application/octet-stream\r\n\r\n";
87 $this->postdata .= join("", file($filename));
88 $this->postdata .= "\r\n\r\n--$boundary--\r\n";
89 return $this->doRequest();
92 function buildQueryString($data)
95 if (is_array($data)) {
96 // Change data in to postable data
97 foreach ($data as $key => $val) {
99 foreach ($val as $val2) {
100 $querystring .= urlencode($key) . '=' . urlencode($val2) . '&';
103 $querystring .= urlencode($key) . '=' . urlencode($val) . '&';
106 $querystring = substr($querystring, 0, -1); // Eliminate unnecessary &
108 $querystring = $data;
115 // Performs the actual HTTP request, returning true or false depending on outcome
116 // Ensure that the PHP timeout is longer than the socket timeout
117 longer_timeout($this->timeout);
118 if (!$fp = @fsockopen($this->host, $this->port, $errno, $errstr, $this->timeout)) {
122 $this->errormsg = 'Socket creation failed (-3)';
124 $this->errormsg = 'DNS lookup failure (-4)';
126 $this->errormsg = 'Connection refused or timed out (-5)';
128 $this->errormsg = 'Connection failed (' . $errno . ')';
129 $this->errormsg .= ' ' . $errstr;
130 $this->debug($this->errormsg);
134 socket_set_timeout($fp, $this->timeout);
135 if ($this->method == 'POST' and preg_match("/\<methodCall\>/", $this->postdata))
136 $request = $this->buildRequest("text/xml"); //xmlrpc
137 else if ($this->method == 'POST' and strstr("\r\nContent-Disposition: form-data; filename=",
141 $boundary = $this->boundary;
142 $request = $this->buildRequest("multipart/form-data; boundary=\"$boundary\"");
144 $request = $this->buildRequest();
145 $this->debug('Request', $request);
146 fwrite($fp, $request);
147 // Reset all the variables that should not persist between requests
148 $this->headers = array();
150 $this->errormsg = '';
151 // Set a couple of flags
154 // Now start reading back the response
156 $line = fgets($fp, 4096);
158 // Deal with first line of returned data
160 if ($line === false) {
161 $this->errormsg = "Empty " . $this->method . " response";
164 if (!preg_match('/HTTP\/(\\d\\.\\d)\\s*(\\d+)\\s*(.*)/', $line, $m)) {
165 $this->errormsg = "Status code line invalid: " . htmlentities($line);
166 $this->debug($this->errormsg);
169 $http_version = $m[1]; // not used
170 $this->status = $m[2];
171 $status_string = $m[3]; // not used
172 $this->debug(trim($line));
176 if (trim($line) == '') {
178 $this->debug('Received Headers', $this->headers);
179 if ($this->headers_only) {
180 break; // Skip the rest of the input
184 if (!preg_match('/([^:]+):\\s*(.*)/', $line, $m)) {
185 // Skip to the next header
188 $key = strtolower(trim($m[1]));
190 // Deal with the possibility of multiple headers of same name
191 if (isset($this->headers[$key])) {
192 if (is_array($this->headers[$key])) {
193 $this->headers[$key][] = $val;
195 $this->headers[$key] = array($this->headers[$key], $val);
198 $this->headers[$key] = $val;
202 // We're not in the headers, so append the line to the contents
203 $this->content .= $line;
206 // If data is compressed, uncompress it
207 if (isset($this->headers['content-encoding']) && $this->headers['content-encoding'] == 'gzip') {
208 $this->debug('Content is gzip encoded, unzipping it');
209 $this->content = substr($this->content, 10); // See http://www.php.net/manual/en/function.gzencode.php
210 $this->content = gzinflate($this->content);
212 // If $persist_cookies, deal with any cookies
213 if ($this->persist_cookies && isset($this->headers['set-cookie']) && $this->host == $this->cookie_host) {
214 $cookies = $this->headers['set-cookie'];
215 if (!is_array($cookies)) {
216 $cookies = array($cookies);
218 foreach ($cookies as $cookie) {
219 if (preg_match('/([^=]+)=([^;]+);/', $cookie, $m)) {
220 $this->cookies[$m[1]] = $m[2];
223 // Record domain of cookies for security reasons
224 $this->cookie_host = $this->host;
226 if ($this->status == '401') {
227 $this->errormsg = '401 ' . $status_string;
230 // If $persist_referers, set the referer ready for the next request
231 if (isset($this->persist_referers)) {
232 $this->debug('Persisting referer: ' . $this->getRequestURL());
233 $this->referer = $this->getRequestURL();
235 // Finally, if handle_redirects and a redirect is sent, do that
236 if (isset($this->handle_redirects)) {
237 if (++$this->redirect_count >= $this->max_redirects) {
238 $this->errormsg = 'Number of redirects exceeded maximum (' . $this->max_redirects . ')';
239 $this->debug($this->errormsg);
240 $this->redirect_count = 0;
243 $location = isset($this->headers['location']) ? $this->headers['location'] : '';
244 $uri = isset($this->headers['uri']) ? $this->headers['uri'] : '';
245 if ($location || $uri) {
246 $url = parse_url($location . $uri);
247 if ($this->method == 'POST')
248 return $this->doRequest();
250 // This will FAIL if redirect is to a different site
251 return $this->get($url['path']);
257 function buildRequest($ContentType = 'application/x-www-form-urlencoded')
260 // Using 1.1 leads to all manner of problems, such as "chunked" encoding
261 $headers[] = "{$this->method} {$this->path} HTTP/1.0";
262 $headers[] = "Host: {$this->host}";
263 $headers[] = "User-Agent: {$this->user_agent}";
264 $headers[] = "Accept: {$this->accept}";
265 if ($this->use_gzip) {
266 $headers[] = "Accept-encoding: {$this->accept_encoding}";
268 $headers[] = "Accept-language: {$this->accept_language}";
269 if (!empty($this->referer)) {
270 $headers[] = "Referer: {$this->referer}";
273 if (!empty($this->cookies)) {
274 $cookie = 'Cookie: ';
275 foreach ($this->cookies as $key => $value) {
276 $cookie .= "$key=$value; ";
278 $headers[] = $cookie;
280 // Basic authentication
281 if (!empty($this->username) && !empty($this->password)) {
282 $headers[] = 'Authorization: BASIC ' . base64_encode($this->username . ':' . $this->password);
284 // If this is a POST, set the content type and length
285 if ($this->postdata) {
286 $headers[] = 'Content-Type: ' . $ContentType;
287 $headers[] = 'Content-Length: ' . strlen($this->postdata);
289 $request = implode("\r\n", $headers) . "\r\n\r\n" . $this->postdata;
295 return $this->status;
298 function getContent()
300 return $this->content;
303 function getHeaders()
305 return $this->headers;
308 function getHeader($header)
310 $header = strtolower($header);
311 if (isset($this->headers[$header])) {
312 return $this->headers[$header];
320 return $this->errormsg;
323 function getCookies()
325 return $this->cookies;
328 function getRequestURL()
330 $url = 'http://' . $this->host;
331 if ($this->port != 80) {
332 $url = 'https://' . $this->host;
333 $url .= ':' . $this->port;
340 function setUserAgent($string)
342 $this->user_agent = $string;
345 function setAuthorization($username, $password)
347 $this->username = $username;
348 $this->password = $password;
351 function setCookies($array)
353 $this->cookies = $array;
356 // Option setting methods
357 function useGzip($boolean)
359 $this->use_gzip = $boolean;
362 function setPersistCookies($boolean)
364 $this->persist_cookies = $boolean;
367 function setPersistReferers($boolean)
369 $this->persist_referers = $boolean;
372 function setHandleRedirects($boolean)
374 $this->handle_redirects = $boolean;
377 function setMaxRedirects($num)
379 $this->max_redirects = $num;
382 function setHeadersOnly($boolean)
384 $this->headers_only = $boolean;
387 function setDebug($boolean)
389 $this->debug = $boolean;
392 // "Quick" static methods
393 function quickGet($url)
395 $bits = parse_url($url);
396 $host = $bits['host'];
397 $port = isset($bits['port']) ? $bits['port'] : 80;
398 $path = isset($bits['path']) ? $bits['path'] : '/';
399 if (isset($bits['query'])) {
400 $path .= '?' . $bits['query'];
402 $client = new HttpClient($host, $port);
403 if (!$client->get($path)) {
406 return $client->getContent();
410 function quickPost($url, $data)
412 $bits = parse_url($url);
413 $host = $bits['host'];
414 $port = isset($bits['port']) ? $bits['port'] : 80;
415 $path = isset($bits['path']) ? $bits['path'] : '/';
416 $client = new HttpClient($host, $port);
417 if (!$client->post($path, $data)) {
420 return $client->getContent();
424 function debug($msg, $object = false)
427 print '<div style="border: 1px solid red; padding: 0.5em; margin: 0.5em;"><strong>HttpClient Debug:</strong> ' . $msg;
431 $content = htmlentities(ob_get_contents());
433 print '<pre>' . $content . '</pre>';
444 // c-hanging-comment-ender-p: nil
445 // indent-tabs-mode: nil