~ubuntu-branches/ubuntu/karmic/dokuwiki/karmic

« back to all changes in this revision

Viewing changes to inc/HTTPClient.php

  • Committer: Bazaar Package Importer
  • Author(s): Mohammed Adnène Trojette
  • Date: 2007-03-29 19:44:52 UTC
  • mfrom: (2.1.6 feisty)
  • Revision ID: james.westby@ubuntu.com-20070329194452-8r2w798oo21ago6l
Tags: 0.0.20061106-6
* High-urgency upload for fixing RC bug.
* Make fr.po's translation of "global" consistent. (Closes: #416509)
* Remove /etc/apache*/conf.d/ on purge. (Closes: #387974)

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
<?php
 
2
/**
 
3
 * HTTP Client
 
4
 *
 
5
 * @license    GPL 2 (http://www.gnu.org/licenses/gpl.html)
 
6
 * @author     Andreas Goetz <cpuidle@gmx.de>
 
7
 */
 
8
 
 
9
if(!defined('DOKU_INC')) define('DOKU_INC',realpath(dirname(__FILE__).'/../').'/');
 
10
require_once(DOKU_CONF.'dokuwiki.php');
 
11
 
 
12
define('HTTP_NL',"\r\n");
 
13
 
 
14
 
 
15
/**
 
16
 * Adds DokuWiki specific configs to the HTTP client
 
17
 *
 
18
 * @author Andreas Goetz <cpuidle@gmx.de>
 
19
 */
 
20
class DokuHTTPClient extends HTTPClient {
 
21
 
 
22
    /**
 
23
     * Constructor.
 
24
     *
 
25
     * @author Andreas Gohr <andi@splitbrain.org>
 
26
     */
 
27
    function DokuHTTPClient(){
 
28
        global $conf;
 
29
 
 
30
        // call parent constructor
 
31
        $this->HTTPClient();
 
32
 
 
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'];
 
39
    }
 
40
}
 
41
 
 
42
/**
 
43
 * This class implements a basic HTTP client
 
44
 *
 
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.
 
48
 *
 
49
 * @link   http://www.splitbrain.org/go/videodb
 
50
 * @author Andreas Goetz <cpuidle@gmx.de>
 
51
 * @author Andreas Gohr <andi@splitbrain.org>
 
52
 */
 
53
class HTTPClient {
 
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)
 
58
    var $cookies;
 
59
    var $referer;
 
60
    var $max_redirect;
 
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
 
63
    var $headers;
 
64
    var $debug;
 
65
 
 
66
    // don't set these, read on error
 
67
    var $error;
 
68
    var $redirect_count;
 
69
 
 
70
    // read these after a successful request
 
71
    var $resp_status;
 
72
    var $resp_body;
 
73
    var $resp_headers;
 
74
 
 
75
    // set these to do basic authentication
 
76
    var $user;
 
77
    var $pass;
 
78
 
 
79
    // set these if you need to use a proxy
 
80
    var $proxy_host;
 
81
    var $proxy_port;
 
82
    var $proxy_user;
 
83
    var $proxy_pass;
 
84
    var $proxy_ssl; //boolean set to true if your proxy needs SSL
 
85
 
 
86
    /**
 
87
     * Constructor.
 
88
     *
 
89
     * @author Andreas Gohr <andi@splitbrain.org>
 
90
     */
 
91
    function HTTPClient(){
 
92
        $this->agent        = 'Mozilla/4.0 (compatible; DokuWiki HTTP Client; '.PHP_OS.')';
 
93
        $this->timeout      = 15;
 
94
        $this->cookies      = array();
 
95
        $this->referer      = '';
 
96
        $this->max_redirect = 3;
 
97
        $this->redirect_count = 0;
 
98
        $this->status       = 0;
 
99
        $this->headers      = array();
 
100
        $this->http         = '1.0';
 
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';
 
108
    }
 
109
 
 
110
 
 
111
    /**
 
112
     * Simple function to do a GET request
 
113
     *
 
114
     * Returns the wanted page or false on an error;
 
115
     *
 
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>
 
119
     */
 
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;
 
125
    }
 
126
 
 
127
    /**
 
128
     * Simple function to do a POST request
 
129
     *
 
130
     * Returns the resulting page or false on an error;
 
131
     *
 
132
     * @author Andreas Gohr <andi@splitbrain.org>
 
133
     */
 
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;
 
138
    }
 
139
 
 
140
    /**
 
141
     * Do an HTTP request
 
142
     *
 
143
     * @author Andreas Goetz <cpuidle@gmx.de>
 
144
     * @author Andreas Gohr <andi@splitbrain.org>
 
145
     */
 
146
    function sendRequest($url,$data=array(),$method='GET'){
 
147
        $this->error = '';
 
148
        $this->status = 0;
 
149
 
 
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'];
 
159
 
 
160
        // proxy setup
 
161
        if($this->proxy_host){
 
162
            $request_url = $url;
 
163
            $server      = $this->proxy_host;
 
164
            $port        = $this->proxy_port;
 
165
            if (empty($port)) $port = 8080;
 
166
        }else{
 
167
            $request_url = $path;
 
168
            $server      = $server;
 
169
            if (empty($port)) $port = ($uri['scheme'] == 'https') ? 443 : 80;
 
170
        }
 
171
 
 
172
        // add SSL stream prefix if needed - needs SSL support in PHP
 
173
        if($port == 443 || $this->proxy_ssl) $server = 'ssl://'.$server;
 
174
 
 
175
        // prepare headers
 
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);
 
185
        }
 
186
        if($this->user) {
 
187
            $headers['Authorization'] = 'BASIC '.base64_encode($this->user.':'.$this->pass);
 
188
        }
 
189
        if($this->proxy_user) {
 
190
            $headers['Proxy-Authorization'] = 'BASIC '.base64_encode($this->proxy_user.':'.$this->proxy_pass);
 
191
        }
 
192
 
 
193
        // stop time
 
194
        $start = time();
 
195
 
 
196
        // open socket
 
197
        $socket = @fsockopen($server,$port,$errno, $errstr, $this->timeout);
 
198
        if (!$socket){
 
199
            $resp->status = '-100';
 
200
            $this->error = "Could not connect to $server:$port\n$errstr ($errno)";
 
201
            return false;
 
202
        }
 
203
        //set non blocking
 
204
        stream_set_blocking($socket,0);
 
205
 
 
206
        // build request
 
207
        $request  = "$method $request_url HTTP/".$this->http.HTTP_NL;
 
208
        $request .= $this->_buildHeaders($headers);
 
209
        $request .= $this->_getCookies();
 
210
        $request .= HTTP_NL;
 
211
        $request .= $post;
 
212
 
 
213
        $this->_debug('request',$request);
 
214
 
 
215
        // send request
 
216
        fputs($socket, $request);
 
217
        // read headers from socket
 
218
        $r_headers = '';
 
219
        do{
 
220
            if(time()-$start > $this->timeout){
 
221
                $this->status = -100;
 
222
                $this->error = 'Timeout while reading headers';
 
223
                return false;
 
224
            }
 
225
            if(feof($socket)){
 
226
                $this->error = 'Premature End of File (socket)';
 
227
                return false;
 
228
            }
 
229
            $r_headers .= fread($socket,1); #FIXME read full lines here?
 
230
        }while(!preg_match('/\r\n\r\n$/',$r_headers));
 
231
 
 
232
        $this->_debug('response headers',$r_headers);
 
233
 
 
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';
 
238
                return false;
 
239
            }
 
240
        }
 
241
 
 
242
        // get Status
 
243
        if (!preg_match('/^HTTP\/(\d\.\d)\s*(\d+).*?\n/', $r_headers, $m)) {
 
244
            $this->error = 'Server returned bad answer';
 
245
            return false;
 
246
        }
 
247
        $this->status = $m[2];
 
248
 
 
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;
 
255
            }
 
256
        }
 
257
 
 
258
        $this->_debug('Object headers',$this->resp_headers);
 
259
 
 
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';
 
264
                return false;
 
265
            }elseif($this->redirect_count == $this->max_redirect){
 
266
                $this->error = 'Maximum number of redirects exceeded';
 
267
                return false;
 
268
            }else{
 
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'];
 
274
                }
 
275
                // perform redirected request, always via GET (required by RFC)
 
276
                return $this->sendRequest($this->resp_headers['location'],array(),'GET');
 
277
            }
 
278
        }
 
279
 
 
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';
 
283
            return false;
 
284
        }
 
285
 
 
286
        //read body (with chunked encoding if needed)
 
287
        $r_body    = '';
 
288
        if(preg_match('/transfer\-(en)?coding:\s*chunked\r\n/i',$r_header)){
 
289
            do {
 
290
                unset($chunk_size);
 
291
                do {
 
292
                    if(feof($socket)){
 
293
                        $this->error = 'Premature End of File (socket)';
 
294
                        return false;
 
295
                    }
 
296
                    if(time()-$start > $this->timeout){
 
297
                        $this->status = -100;
 
298
                        $this->error = 'Timeout while reading chunk';
 
299
                        return false;
 
300
                    }
 
301
                    $byte = fread($socket,1);
 
302
                    $chunk_size .= $byte;
 
303
                } while (preg_match('/[a-zA-Z0-9]/',$byte)); // read chunksize including \r
 
304
 
 
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
 
310
 
 
311
                if($this->max_bodysize && strlen($r_body) > $this->max_bodysize){
 
312
                    $this->error = 'Allowed response size exceeded';
 
313
                    return false;
 
314
                }
 
315
            } while ($chunk_size);
 
316
        }else{
 
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';
 
322
                    return false;
 
323
                }
 
324
                $r_body .= fread($socket,4096);
 
325
                if($this->max_bodysize && strlen($r_body) > $this->max_bodysize){
 
326
                    $this->error = 'Allowed response size exceeded';
 
327
                    return false;
 
328
                }
 
329
            }
 
330
        }
 
331
 
 
332
        // close socket
 
333
        $status = socket_get_status($socket);
 
334
        fclose($socket);
 
335
 
 
336
        // decode gzip if needed
 
337
        if($this->resp_headers['content-encoding'] == 'gzip'){
 
338
            $this->resp_body = gzinflate(substr($r_body, 10));
 
339
        }else{
 
340
            $this->resp_body = $r_body;
 
341
        }
 
342
 
 
343
        $this->_debug('response body',$this->resp_body);
 
344
        $this->redirect_count = 0;
 
345
        return true;
 
346
    }
 
347
 
 
348
    /**
 
349
     * print debug info
 
350
     *
 
351
     * @author Andreas Gohr <andi@splitbrain.org>
 
352
     */
 
353
    function _debug($info,$var){
 
354
        if(!$this->debug) return;
 
355
        print '<b>'.$info.'</b><br />';
 
356
        ob_start();
 
357
        print_r($var);
 
358
        $content = htmlspecialchars(ob_get_contents());
 
359
        ob_end_clean();
 
360
        print '<pre>'.$content.'</pre>';
 
361
    }
 
362
 
 
363
    /**
 
364
     * convert given header string to Header array
 
365
     *
 
366
     * All Keys are lowercased.
 
367
     *
 
368
     * @author Andreas Gohr <andi@splitbrain.org>
 
369
     */
 
370
    function _parseHeaders($string){
 
371
        $headers = array();
 
372
        $lines = explode("\n",$string);
 
373
        foreach($lines as $line){
 
374
            list($key,$val) = explode(':',$line,2);
 
375
            $key = strtolower(trim($key));
 
376
            $val = trim($val);
 
377
            if(empty($val)) continue;
 
378
            if(isset($headers[$key])){
 
379
                if(is_array($headers[$key])){
 
380
                    $headers[$key][] = $val;
 
381
                }else{
 
382
                    $headers[$key] = array($headers[$key],$val);
 
383
                }
 
384
            }else{
 
385
                $headers[$key] = $val;
 
386
            }
 
387
        }
 
388
        return $headers;
 
389
    }
 
390
 
 
391
    /**
 
392
     * convert given header array to header string
 
393
     *
 
394
     * @author Andreas Gohr <andi@splitbrain.org>
 
395
     */
 
396
    function _buildHeaders($headers){
 
397
        $string = '';
 
398
        foreach($headers as $key => $value){
 
399
            if(empty($value)) continue;
 
400
            $string .= $key.': '.$value.HTTP_NL;
 
401
        }
 
402
        return $string;
 
403
    }
 
404
 
 
405
    /**
 
406
     * get cookies as http header string
 
407
     *
 
408
     * @author Andreas Goetz <cpuidle@gmx.de>
 
409
     */
 
410
    function _getCookies(){
 
411
        foreach ($this->cookies as $key => $val){
 
412
            if ($headers) $headers .= '; ';
 
413
            $headers .= $key.'='.$val;
 
414
        }
 
415
 
 
416
        if ($headers) $headers = "Cookie: $headers".HTTP_NL;
 
417
        return $headers;
 
418
    }
 
419
 
 
420
    /**
 
421
     * Encode data for posting
 
422
     *
 
423
     * @todo handle mixed encoding for file upoads
 
424
     * @author Andreas Gohr <andi@splitbrain.org>
 
425
     */
 
426
    function _postEncode($data){
 
427
        foreach($data as $key => $val){
 
428
            if($url) $url .= '&';
 
429
            $url .= $key.'='.urlencode($val);
 
430
        }
 
431
        return $url;
 
432
    }
 
433
}
 
434
 
 
435
//Setup VIM: ex: et ts=4 enc=utf-8 :