5
* @license GPL 2 (http://www.gnu.org/licenses/gpl.html)
6
* @author Andreas Goetz <cpuidle@gmx.de>
9
if(!defined('DOKU_INC')) define('DOKU_INC',realpath(dirname(__FILE__).'/../').'/');
10
require_once(DOKU_CONF.'dokuwiki.php');
12
define('HTTP_NL',"\r\n");
16
* Adds DokuWiki specific configs to the HTTP client
18
* @author Andreas Goetz <cpuidle@gmx.de>
20
class DokuHTTPClient extends HTTPClient {
25
* @author Andreas Gohr <andi@splitbrain.org>
27
function DokuHTTPClient(){
30
// call parent constructor
33
// set some values from the config
34
$this->proxy_host = $conf['proxy']['host'];
35
$this->proxy_port = $conf['proxy']['port'];
36
$this->proxy_user = $conf['proxy']['user'];
37
$this->proxy_pass = $conf['proxy']['pass'];
38
$this->proxy_ssl = $conf['proxy']['ssl'];
43
* This class implements a basic HTTP client
45
* It supports POST and GET, Proxy usage, basic authentication,
46
* handles cookies and referers. It is based upon the httpclient
47
* function from the VideoDB project.
49
* @link http://www.splitbrain.org/go/videodb
50
* @author Andreas Goetz <cpuidle@gmx.de>
51
* @author Andreas Gohr <andi@splitbrain.org>
54
//set these if you like
55
var $agent; // User agent
56
var $http; // HTTP version defaults to 1.0
57
var $timeout; // read timeout (seconds)
61
var $max_bodysize; // abort if the response body is bigger than this
62
var $header_regexp; // if set this RE must match against the headers, else abort
66
// don't set these, read on error
70
// read these after a successful request
75
// set these to do basic authentication
79
// set these if you need to use a proxy
84
var $proxy_ssl; //boolean set to true if your proxy needs SSL
89
* @author Andreas Gohr <andi@splitbrain.org>
91
function HTTPClient(){
92
$this->agent = 'Mozilla/4.0 (compatible; DokuWiki HTTP Client; '.PHP_OS.')';
94
$this->cookies = array();
96
$this->max_redirect = 3;
97
$this->redirect_count = 0;
99
$this->headers = array();
101
$this->debug = false;
102
$this->max_bodysize = 0;
103
$this->header_regexp= '';
104
if(extension_loaded('zlib')) $this->headers['Accept-encoding'] = 'gzip';
105
$this->headers['Accept'] = 'text/xml,application/xml,application/xhtml+xml,'.
106
'text/html,text/plain,image/png,image/jpeg,image/gif,*/*';
107
$this->headers['Accept-Language'] = 'en-us';
112
* Simple function to do a GET request
114
* Returns the wanted page or false on an error;
116
* @param string $url The URL to fetch
117
* @param bool $sloppy304 Return body on 304 not modified
118
* @author Andreas Gohr <andi@splitbrain.org>
120
function get($url,$sloppy304=false){
121
if(!$this->sendRequest($url)) return false;
122
if($this->status == 304 && $sloppy304) return $this->resp_body;
123
if($this->status != 200) return false;
124
return $this->resp_body;
128
* Simple function to do a POST request
130
* Returns the resulting page or false on an error;
132
* @author Andreas Gohr <andi@splitbrain.org>
134
function post($url,$data){
135
if(!$this->sendRequest($url,$data,'POST')) return false;
136
if($this->status != 200) return false;
137
return $this->resp_body;
143
* @author Andreas Goetz <cpuidle@gmx.de>
144
* @author Andreas Gohr <andi@splitbrain.org>
146
function sendRequest($url,$data=array(),$method='GET'){
150
// parse URL into bits
151
$uri = parse_url($url);
152
$server = $uri['host'];
153
$path = $uri['path'];
154
if(empty($path)) $path = '/';
155
if(!empty($uri['query'])) $path .= '?'.$uri['query'];
156
$port = $uri['port'];
157
if($uri['user']) $this->user = $uri['user'];
158
if($uri['pass']) $this->pass = $uri['pass'];
161
if($this->proxy_host){
163
$server = $this->proxy_host;
164
$port = $this->proxy_port;
165
if (empty($port)) $port = 8080;
167
$request_url = $path;
169
if (empty($port)) $port = ($uri['scheme'] == 'https') ? 443 : 80;
172
// add SSL stream prefix if needed - needs SSL support in PHP
173
if($port == 443 || $this->proxy_ssl) $server = 'ssl://'.$server;
176
$headers = $this->headers;
177
$headers['Host'] = $uri['host'];
178
$headers['User-Agent'] = $this->agent;
179
$headers['Referer'] = $this->referer;
180
$headers['Connection'] = 'Close';
181
if($method == 'POST'){
182
$post = $this->_postEncode($data);
183
$headers['Content-Type'] = 'application/x-www-form-urlencoded';
184
$headers['Content-Length'] = strlen($post);
187
$headers['Authorization'] = 'BASIC '.base64_encode($this->user.':'.$this->pass);
189
if($this->proxy_user) {
190
$headers['Proxy-Authorization'] = 'BASIC '.base64_encode($this->proxy_user.':'.$this->proxy_pass);
197
$socket = @fsockopen($server,$port,$errno, $errstr, $this->timeout);
199
$resp->status = '-100';
200
$this->error = "Could not connect to $server:$port\n$errstr ($errno)";
204
stream_set_blocking($socket,0);
207
$request = "$method $request_url HTTP/".$this->http.HTTP_NL;
208
$request .= $this->_buildHeaders($headers);
209
$request .= $this->_getCookies();
213
$this->_debug('request',$request);
216
fputs($socket, $request);
217
// read headers from socket
220
if(time()-$start > $this->timeout){
221
$this->status = -100;
222
$this->error = 'Timeout while reading headers';
226
$this->error = 'Premature End of File (socket)';
229
$r_headers .= fread($socket,1); #FIXME read full lines here?
230
}while(!preg_match('/\r\n\r\n$/',$r_headers));
232
$this->_debug('response headers',$r_headers);
234
// check if expected body size exceeds allowance
235
if($this->max_bodysize && preg_match('/\r\nContent-Length:\s*(\d+)\r\n/i',$r_headers,$match)){
236
if($match[1] > $this->max_bodysize){
237
$this->error = 'Reported content length exceeds allowed response size';
243
if (!preg_match('/^HTTP\/(\d\.\d)\s*(\d+).*?\n/', $r_headers, $m)) {
244
$this->error = 'Server returned bad answer';
247
$this->status = $m[2];
249
// handle headers and cookies
250
$this->resp_headers = $this->_parseHeaders($r_headers);
251
if(isset($this->resp_headers['set-cookie'])){
252
foreach ((array) $this->resp_headers['set-cookie'] as $c){
253
list($key, $value, $foo) = split('=', $cookie);
254
$this->cookies[$key] = $value;
258
$this->_debug('Object headers',$this->resp_headers);
260
// check server status code to follow redirect
261
if($this->status == 301 || $this->status == 302 ){
262
if (empty($this->resp_headers['location'])){
263
$this->error = 'Redirect but no Location Header found';
265
}elseif($this->redirect_count == $this->max_redirect){
266
$this->error = 'Maximum number of redirects exceeded';
269
$this->redirect_count++;
270
$this->referer = $url;
271
if (!preg_match('/^http/i', $this->resp_headers['location'])){
272
$this->resp_headers['location'] = $uri['scheme'].'://'.$uri['host'].
273
$this->resp_headers['location'];
275
// perform redirected request, always via GET (required by RFC)
276
return $this->sendRequest($this->resp_headers['location'],array(),'GET');
280
// check if headers are as expected
281
if($this->header_regexp && !preg_match($this->header_regexp,$r_headers)){
282
$this->error = 'The received headers did not match the given regexp';
286
//read body (with chunked encoding if needed)
288
if(preg_match('/transfer\-(en)?coding:\s*chunked\r\n/i',$r_header)){
293
$this->error = 'Premature End of File (socket)';
296
if(time()-$start > $this->timeout){
297
$this->status = -100;
298
$this->error = 'Timeout while reading chunk';
301
$byte = fread($socket,1);
302
$chunk_size .= $byte;
303
} while (preg_match('/[a-zA-Z0-9]/',$byte)); // read chunksize including \r
305
$byte = fread($socket,1); // readtrailing \n
306
$chunk_size = hexdec($chunk_size);
307
$this_chunk = fread($socket,$chunk_size);
308
$r_body .= $this_chunk;
309
if ($chunk_size) $byte = fread($socket,2); // read trailing \r\n
311
if($this->max_bodysize && strlen($r_body) > $this->max_bodysize){
312
$this->error = 'Allowed response size exceeded';
315
} while ($chunk_size);
317
// read entire socket
318
while (!feof($socket)) {
319
if(time()-$start > $this->timeout){
320
$this->status = -100;
321
$this->error = 'Timeout while reading response';
324
$r_body .= fread($socket,4096);
325
if($this->max_bodysize && strlen($r_body) > $this->max_bodysize){
326
$this->error = 'Allowed response size exceeded';
333
$status = socket_get_status($socket);
336
// decode gzip if needed
337
if($this->resp_headers['content-encoding'] == 'gzip'){
338
$this->resp_body = gzinflate(substr($r_body, 10));
340
$this->resp_body = $r_body;
343
$this->_debug('response body',$this->resp_body);
344
$this->redirect_count = 0;
351
* @author Andreas Gohr <andi@splitbrain.org>
353
function _debug($info,$var){
354
if(!$this->debug) return;
355
print '<b>'.$info.'</b><br />';
358
$content = htmlspecialchars(ob_get_contents());
360
print '<pre>'.$content.'</pre>';
364
* convert given header string to Header array
366
* All Keys are lowercased.
368
* @author Andreas Gohr <andi@splitbrain.org>
370
function _parseHeaders($string){
372
$lines = explode("\n",$string);
373
foreach($lines as $line){
374
list($key,$val) = explode(':',$line,2);
375
$key = strtolower(trim($key));
377
if(empty($val)) continue;
378
if(isset($headers[$key])){
379
if(is_array($headers[$key])){
380
$headers[$key][] = $val;
382
$headers[$key] = array($headers[$key],$val);
385
$headers[$key] = $val;
392
* convert given header array to header string
394
* @author Andreas Gohr <andi@splitbrain.org>
396
function _buildHeaders($headers){
398
foreach($headers as $key => $value){
399
if(empty($value)) continue;
400
$string .= $key.': '.$value.HTTP_NL;
406
* get cookies as http header string
408
* @author Andreas Goetz <cpuidle@gmx.de>
410
function _getCookies(){
411
foreach ($this->cookies as $key => $val){
412
if ($headers) $headers .= '; ';
413
$headers .= $key.'='.$val;
416
if ($headers) $headers = "Cookie: $headers".HTTP_NL;
421
* Encode data for posting
423
* @todo handle mixed encoding for file upoads
424
* @author Andreas Gohr <andi@splitbrain.org>
426
function _postEncode($data){
427
foreach($data as $key => $val){
428
if($url) $url .= '&';
429
$url .= $key.'='.urlencode($val);
435
//Setup VIM: ex: et ts=4 enc=utf-8 :