1 | <?php
|
---|
2 | /**
|
---|
3 | * HTTP Client
|
---|
4 | *
|
---|
5 | * @license GPL 2 (http://www.gnu.org/licenses/gpl.html)
|
---|
6 | * @author Andreas Goetz <[email protected]>
|
---|
7 | */
|
---|
8 |
|
---|
9 |
|
---|
10 | define('HTTP_NL',"\r\n");
|
---|
11 |
|
---|
12 |
|
---|
13 | /**
|
---|
14 | * Adds DokuWiki specific configs to the HTTP client
|
---|
15 | *
|
---|
16 | * @author Andreas Goetz <[email protected]>
|
---|
17 | */
|
---|
18 | class DokuHTTPClient extends HTTPClient {
|
---|
19 |
|
---|
20 | /**
|
---|
21 | * Constructor.
|
---|
22 | *
|
---|
23 | * @author Andreas Gohr <[email protected]>
|
---|
24 | */
|
---|
25 | function DokuHTTPClient(){
|
---|
26 | global $conf;
|
---|
27 |
|
---|
28 | // call parent constructor
|
---|
29 | $this->HTTPClient();
|
---|
30 |
|
---|
31 | // set some values from the config
|
---|
32 | $this->proxy_host = $conf['proxy']['host'];
|
---|
33 | $this->proxy_port = $conf['proxy']['port'];
|
---|
34 | $this->proxy_user = $conf['proxy']['user'];
|
---|
35 | $this->proxy_pass = conf_decodeString($conf['proxy']['pass']);
|
---|
36 | $this->proxy_ssl = $conf['proxy']['ssl'];
|
---|
37 | $this->proxy_except = $conf['proxy']['except'];
|
---|
38 | }
|
---|
39 |
|
---|
40 |
|
---|
41 | /**
|
---|
42 | * Wraps an event around the parent function
|
---|
43 | *
|
---|
44 | * @triggers HTTPCLIENT_REQUEST_SEND
|
---|
45 | * @author Andreas Gohr <[email protected]>
|
---|
46 | */
|
---|
47 | function sendRequest($url,$data='',$method='GET'){
|
---|
48 | $httpdata = array('url' => $url,
|
---|
49 | 'data' => $data,
|
---|
50 | 'method' => $method);
|
---|
51 | $evt = new Doku_Event('HTTPCLIENT_REQUEST_SEND',$httpdata);
|
---|
52 | if($evt->advise_before()){
|
---|
53 | $url = $httpdata['url'];
|
---|
54 | $data = $httpdata['data'];
|
---|
55 | $method = $httpdata['method'];
|
---|
56 | }
|
---|
57 | $evt->advise_after();
|
---|
58 | unset($evt);
|
---|
59 | return parent::sendRequest($url,$data,$method);
|
---|
60 | }
|
---|
61 |
|
---|
62 | }
|
---|
63 |
|
---|
64 | /**
|
---|
65 | * This class implements a basic HTTP client
|
---|
66 | *
|
---|
67 | * It supports POST and GET, Proxy usage, basic authentication,
|
---|
68 | * handles cookies and referers. It is based upon the httpclient
|
---|
69 | * function from the VideoDB project.
|
---|
70 | *
|
---|
71 | * @link http://www.splitbrain.org/go/videodb
|
---|
72 | * @author Andreas Goetz <[email protected]>
|
---|
73 | * @author Andreas Gohr <[email protected]>
|
---|
74 | * @author Tobias Sarnowski <[email protected]>
|
---|
75 | */
|
---|
76 | class HTTPClient {
|
---|
77 | //set these if you like
|
---|
78 | var $agent; // User agent
|
---|
79 | var $http; // HTTP version defaults to 1.0
|
---|
80 | var $timeout; // read timeout (seconds)
|
---|
81 | var $cookies;
|
---|
82 | var $referer;
|
---|
83 | var $max_redirect;
|
---|
84 | var $max_bodysize;
|
---|
85 | var $max_bodysize_abort = true; // if set, abort if the response body is bigger than max_bodysize
|
---|
86 | var $header_regexp; // if set this RE must match against the headers, else abort
|
---|
87 | var $headers;
|
---|
88 | var $debug;
|
---|
89 | var $start = 0; // for timings
|
---|
90 | var $keep_alive = true; // keep alive rocks
|
---|
91 |
|
---|
92 | // don't set these, read on error
|
---|
93 | var $error;
|
---|
94 | var $redirect_count;
|
---|
95 |
|
---|
96 | // read these after a successful request
|
---|
97 | var $status;
|
---|
98 | var $resp_body;
|
---|
99 | var $resp_headers;
|
---|
100 |
|
---|
101 | // set these to do basic authentication
|
---|
102 | var $user;
|
---|
103 | var $pass;
|
---|
104 |
|
---|
105 | // set these if you need to use a proxy
|
---|
106 | var $proxy_host;
|
---|
107 | var $proxy_port;
|
---|
108 | var $proxy_user;
|
---|
109 | var $proxy_pass;
|
---|
110 | var $proxy_ssl; //boolean set to true if your proxy needs SSL
|
---|
111 | var $proxy_except; // regexp of URLs to exclude from proxy
|
---|
112 |
|
---|
113 | // list of kept alive connections
|
---|
114 | static $connections = array();
|
---|
115 |
|
---|
116 | // what we use as boundary on multipart/form-data posts
|
---|
117 | var $boundary = '---DokuWikiHTTPClient--4523452351';
|
---|
118 |
|
---|
119 | /**
|
---|
120 | * Constructor.
|
---|
121 | *
|
---|
122 | * @author Andreas Gohr <[email protected]>
|
---|
123 | */
|
---|
124 | function HTTPClient(){
|
---|
125 | $this->agent = 'Mozilla/4.0 (compatible; DokuWiki HTTP Client; '.PHP_OS.')';
|
---|
126 | $this->timeout = 15;
|
---|
127 | $this->cookies = array();
|
---|
128 | $this->referer = '';
|
---|
129 | $this->max_redirect = 3;
|
---|
130 | $this->redirect_count = 0;
|
---|
131 | $this->status = 0;
|
---|
132 | $this->headers = array();
|
---|
133 | $this->http = '1.0';
|
---|
134 | $this->debug = false;
|
---|
135 | $this->max_bodysize = 0;
|
---|
136 | $this->header_regexp= '';
|
---|
137 | if(extension_loaded('zlib')) $this->headers['Accept-encoding'] = 'gzip';
|
---|
138 | $this->headers['Accept'] = 'text/xml,application/xml,application/xhtml+xml,'.
|
---|
139 | 'text/html,text/plain,image/png,image/jpeg,image/gif,*/*';
|
---|
140 | $this->headers['Accept-Language'] = 'en-us';
|
---|
141 | }
|
---|
142 |
|
---|
143 |
|
---|
144 | /**
|
---|
145 | * Simple function to do a GET request
|
---|
146 | *
|
---|
147 | * Returns the wanted page or false on an error;
|
---|
148 | *
|
---|
149 | * @param string $url The URL to fetch
|
---|
150 | * @param bool $sloppy304 Return body on 304 not modified
|
---|
151 | * @author Andreas Gohr <[email protected]>
|
---|
152 | */
|
---|
153 | function get($url,$sloppy304=false){
|
---|
154 | if(!$this->sendRequest($url)) return false;
|
---|
155 | if($this->status == 304 && $sloppy304) return $this->resp_body;
|
---|
156 | if($this->status < 200 || $this->status > 206) return false;
|
---|
157 | return $this->resp_body;
|
---|
158 | }
|
---|
159 |
|
---|
160 | /**
|
---|
161 | * Simple function to do a GET request with given parameters
|
---|
162 | *
|
---|
163 | * Returns the wanted page or false on an error.
|
---|
164 | *
|
---|
165 | * This is a convenience wrapper around get(). The given parameters
|
---|
166 | * will be correctly encoded and added to the given base URL.
|
---|
167 | *
|
---|
168 | * @param string $url The URL to fetch
|
---|
169 | * @param array $data Associative array of parameters
|
---|
170 | * @param bool $sloppy304 Return body on 304 not modified
|
---|
171 | * @author Andreas Gohr <[email protected]>
|
---|
172 | */
|
---|
173 | function dget($url,$data,$sloppy304=false){
|
---|
174 | if(strpos($url,'?')){
|
---|
175 | $url .= '&';
|
---|
176 | }else{
|
---|
177 | $url .= '?';
|
---|
178 | }
|
---|
179 | $url .= $this->_postEncode($data);
|
---|
180 | return $this->get($url,$sloppy304);
|
---|
181 | }
|
---|
182 |
|
---|
183 | /**
|
---|
184 | * Simple function to do a POST request
|
---|
185 | *
|
---|
186 | * Returns the resulting page or false on an error;
|
---|
187 | *
|
---|
188 | * @author Andreas Gohr <[email protected]>
|
---|
189 | */
|
---|
190 | function post($url,$data){
|
---|
191 | if(!$this->sendRequest($url,$data,'POST')) return false;
|
---|
192 | if($this->status < 200 || $this->status > 206) return false;
|
---|
193 | return $this->resp_body;
|
---|
194 | }
|
---|
195 |
|
---|
196 | /**
|
---|
197 | * Send an HTTP request
|
---|
198 | *
|
---|
199 | * This method handles the whole HTTP communication. It respects set proxy settings,
|
---|
200 | * builds the request headers, follows redirects and parses the response.
|
---|
201 | *
|
---|
202 | * Post data should be passed as associative array. When passed as string it will be
|
---|
203 | * sent as is. You will need to setup your own Content-Type header then.
|
---|
204 | *
|
---|
205 | * @param string $url - the complete URL
|
---|
206 | * @param mixed $data - the post data either as array or raw data
|
---|
207 | * @param string $method - HTTP Method usually GET or POST.
|
---|
208 | * @return bool - true on success
|
---|
209 | * @author Andreas Goetz <[email protected]>
|
---|
210 | * @author Andreas Gohr <[email protected]>
|
---|
211 | */
|
---|
212 | function sendRequest($url,$data='',$method='GET'){
|
---|
213 | $this->start = $this->_time();
|
---|
214 | $this->error = '';
|
---|
215 | $this->status = 0;
|
---|
216 |
|
---|
217 | // don't accept gzip if truncated bodies might occur
|
---|
218 | if($this->max_bodysize &&
|
---|
219 | !$this->max_bodysize_abort &&
|
---|
220 | $this->headers['Accept-encoding'] == 'gzip'){
|
---|
221 | unset($this->headers['Accept-encoding']);
|
---|
222 | }
|
---|
223 |
|
---|
224 | // parse URL into bits
|
---|
225 | $uri = parse_url($url);
|
---|
226 | $server = $uri['host'];
|
---|
227 | $path = $uri['path'];
|
---|
228 | if(empty($path)) $path = '/';
|
---|
229 | if(!empty($uri['query'])) $path .= '?'.$uri['query'];
|
---|
230 | if(isset($uri['port']) && !empty($uri['port'])) $port = $uri['port'];
|
---|
231 | if(isset($uri['user'])) $this->user = $uri['user'];
|
---|
232 | if(isset($uri['pass'])) $this->pass = $uri['pass'];
|
---|
233 |
|
---|
234 | // proxy setup
|
---|
235 | if($this->proxy_host && (!$this->proxy_except || !preg_match('/'.$this->proxy_except.'/i',$url)) ){
|
---|
236 | $request_url = $url;
|
---|
237 | $server = $this->proxy_host;
|
---|
238 | $port = $this->proxy_port;
|
---|
239 | if (empty($port)) $port = 8080;
|
---|
240 | }else{
|
---|
241 | $request_url = $path;
|
---|
242 | $server = $server;
|
---|
243 | if (!isset($port)) $port = ($uri['scheme'] == 'https') ? 443 : 80;
|
---|
244 | }
|
---|
245 |
|
---|
246 | // add SSL stream prefix if needed - needs SSL support in PHP
|
---|
247 | if($port == 443 || $this->proxy_ssl) $server = 'ssl://'.$server;
|
---|
248 |
|
---|
249 | // prepare headers
|
---|
250 | $headers = $this->headers;
|
---|
251 | $headers['Host'] = $uri['host'];
|
---|
252 | if($uri['port']) $headers['Host'].= ':'.$uri['port'];
|
---|
253 | $headers['User-Agent'] = $this->agent;
|
---|
254 | $headers['Referer'] = $this->referer;
|
---|
255 | if ($this->keep_alive) {
|
---|
256 | $headers['Connection'] = 'Keep-Alive';
|
---|
257 | } else {
|
---|
258 | $headers['Connection'] = 'Close';
|
---|
259 | }
|
---|
260 | if($method == 'POST'){
|
---|
261 | if(is_array($data)){
|
---|
262 | if($headers['Content-Type'] == 'multipart/form-data'){
|
---|
263 | $headers['Content-Type'] = 'multipart/form-data; boundary='.$this->boundary;
|
---|
264 | $data = $this->_postMultipartEncode($data);
|
---|
265 | }else{
|
---|
266 | $headers['Content-Type'] = 'application/x-www-form-urlencoded';
|
---|
267 | $data = $this->_postEncode($data);
|
---|
268 | }
|
---|
269 | }
|
---|
270 | $headers['Content-Length'] = strlen($data);
|
---|
271 | $rmethod = 'POST';
|
---|
272 | }elseif($method == 'GET'){
|
---|
273 | $data = ''; //no data allowed on GET requests
|
---|
274 | }
|
---|
275 | if($this->user) {
|
---|
276 | $headers['Authorization'] = 'Basic '.base64_encode($this->user.':'.$this->pass);
|
---|
277 | }
|
---|
278 | if($this->proxy_user) {
|
---|
279 | $headers['Proxy-Authorization'] = 'Basic '.base64_encode($this->proxy_user.':'.$this->proxy_pass);
|
---|
280 | }
|
---|
281 |
|
---|
282 | // stop time
|
---|
283 | $start = time();
|
---|
284 |
|
---|
285 | // already connected?
|
---|
286 | $connectionId = $this->_uniqueConnectionId($server,$port);
|
---|
287 | $this->_debug('connection pool', $this->connections);
|
---|
288 | $socket = null;
|
---|
289 | if (isset($this->connections[$connectionId])) {
|
---|
290 | $this->_debug('reusing connection', $connectionId);
|
---|
291 | $socket = $this->connections[$connectionId];
|
---|
292 | }
|
---|
293 | if (is_null($socket) || feof($socket)) {
|
---|
294 | $this->_debug('opening connection', $connectionId);
|
---|
295 | // open socket
|
---|
296 | $socket = @fsockopen($server,$port,$errno, $errstr, $this->timeout);
|
---|
297 | if (!$socket){
|
---|
298 | $this->status = -100;
|
---|
299 | $this->error = "Could not connect to $server:$port\n$errstr ($errno)";
|
---|
300 | return false;
|
---|
301 | }
|
---|
302 |
|
---|
303 | // keep alive?
|
---|
304 | if ($this->keep_alive) {
|
---|
305 | $this->connections[$connectionId] = $socket;
|
---|
306 | } else {
|
---|
307 | unset($this->connections[$connectionId]);
|
---|
308 | }
|
---|
309 | }
|
---|
310 |
|
---|
311 | //set blocking
|
---|
312 | stream_set_blocking($socket,1);
|
---|
313 |
|
---|
314 | // build request
|
---|
315 | $request = "$method $request_url HTTP/".$this->http.HTTP_NL;
|
---|
316 | $request .= $this->_buildHeaders($headers);
|
---|
317 | $request .= $this->_getCookies();
|
---|
318 | $request .= HTTP_NL;
|
---|
319 | $request .= $data;
|
---|
320 |
|
---|
321 | $this->_debug('request',$request);
|
---|
322 |
|
---|
323 | // select parameters
|
---|
324 | $sel_r = null;
|
---|
325 | $sel_w = array($socket);
|
---|
326 | $sel_e = null;
|
---|
327 |
|
---|
328 | // send request
|
---|
329 | $towrite = strlen($request);
|
---|
330 | $written = 0;
|
---|
331 | while($written < $towrite){
|
---|
332 | // check timeout
|
---|
333 | if(time()-$start > $this->timeout){
|
---|
334 | $this->status = -100;
|
---|
335 | $this->error = sprintf('Timeout while sending request (%.3fs)',$this->_time() - $this->start);
|
---|
336 | unset($this->connections[$connectionId]);
|
---|
337 | return false;
|
---|
338 | }
|
---|
339 |
|
---|
340 | // wait for stream ready or timeout (1sec)
|
---|
341 | if(stream_select($sel_r,$sel_w,$sel_e,1) === false) continue;
|
---|
342 |
|
---|
343 | // write to stream
|
---|
344 | $ret = fwrite($socket, substr($request,$written,4096));
|
---|
345 | if($ret === false){
|
---|
346 | $this->status = -100;
|
---|
347 | $this->error = 'Failed writing to socket';
|
---|
348 | unset($this->connections[$connectionId]);
|
---|
349 | return false;
|
---|
350 | }
|
---|
351 | $written += $ret;
|
---|
352 | }
|
---|
353 |
|
---|
354 | // continue non-blocking
|
---|
355 | stream_set_blocking($socket,0);
|
---|
356 |
|
---|
357 | // read headers from socket
|
---|
358 | $r_headers = '';
|
---|
359 | do{
|
---|
360 | if(time()-$start > $this->timeout){
|
---|
361 | $this->status = -100;
|
---|
362 | $this->error = sprintf('Timeout while reading headers (%.3fs)',$this->_time() - $this->start);
|
---|
363 | unset($this->connections[$connectionId]);
|
---|
364 | return false;
|
---|
365 | }
|
---|
366 | if(feof($socket)){
|
---|
367 | $this->error = 'Premature End of File (socket)';
|
---|
368 | unset($this->connections[$connectionId]);
|
---|
369 | return false;
|
---|
370 | }
|
---|
371 | $r_headers .= fgets($socket,1024);
|
---|
372 | }while(!preg_match('/\r?\n\r?\n$/',$r_headers));
|
---|
373 |
|
---|
374 | $this->_debug('response headers',$r_headers);
|
---|
375 |
|
---|
376 | // check if expected body size exceeds allowance
|
---|
377 | if($this->max_bodysize && preg_match('/\r?\nContent-Length:\s*(\d+)\r?\n/i',$r_headers,$match)){
|
---|
378 | if($match[1] > $this->max_bodysize){
|
---|
379 | $this->error = 'Reported content length exceeds allowed response size';
|
---|
380 | if ($this->max_bodysize_abort)
|
---|
381 | unset($this->connections[$connectionId]);
|
---|
382 | return false;
|
---|
383 | }
|
---|
384 | }
|
---|
385 |
|
---|
386 | // get Status
|
---|
387 | if (!preg_match('/^HTTP\/(\d\.\d)\s*(\d+).*?\n/', $r_headers, $m)) {
|
---|
388 | $this->error = 'Server returned bad answer';
|
---|
389 | unset($this->connections[$connectionId]);
|
---|
390 | return false;
|
---|
391 | }
|
---|
392 | $this->status = $m[2];
|
---|
393 |
|
---|
394 | // handle headers and cookies
|
---|
395 | $this->resp_headers = $this->_parseHeaders($r_headers);
|
---|
396 | if(isset($this->resp_headers['set-cookie'])){
|
---|
397 | foreach ((array) $this->resp_headers['set-cookie'] as $cookie){
|
---|
398 | list($cookie) = explode(';',$cookie,2);
|
---|
399 | list($key,$val) = explode('=',$cookie,2);
|
---|
400 | $key = trim($key);
|
---|
401 | if($val == 'deleted'){
|
---|
402 | if(isset($this->cookies[$key])){
|
---|
403 | unset($this->cookies[$key]);
|
---|
404 | }
|
---|
405 | }elseif($key){
|
---|
406 | $this->cookies[$key] = $val;
|
---|
407 | }
|
---|
408 | }
|
---|
409 | }
|
---|
410 |
|
---|
411 | $this->_debug('Object headers',$this->resp_headers);
|
---|
412 |
|
---|
413 | // check server status code to follow redirect
|
---|
414 | if($this->status == 301 || $this->status == 302 ){
|
---|
415 | // close the connection because we don't handle content retrieval here
|
---|
416 | // that's the easiest way to clean up the connection
|
---|
417 | fclose($socket);
|
---|
418 | unset($this->connections[$connectionId]);
|
---|
419 |
|
---|
420 | if (empty($this->resp_headers['location'])){
|
---|
421 | $this->error = 'Redirect but no Location Header found';
|
---|
422 | return false;
|
---|
423 | }elseif($this->redirect_count == $this->max_redirect){
|
---|
424 | $this->error = 'Maximum number of redirects exceeded';
|
---|
425 | return false;
|
---|
426 | }else{
|
---|
427 | $this->redirect_count++;
|
---|
428 | $this->referer = $url;
|
---|
429 | // handle non-RFC-compliant relative redirects
|
---|
430 | if (!preg_match('/^http/i', $this->resp_headers['location'])){
|
---|
431 | if($this->resp_headers['location'][0] != '/'){
|
---|
432 | $this->resp_headers['location'] = $uri['scheme'].'://'.$uri['host'].':'.$uri['port'].
|
---|
433 | dirname($uri['path']).'/'.$this->resp_headers['location'];
|
---|
434 | }else{
|
---|
435 | $this->resp_headers['location'] = $uri['scheme'].'://'.$uri['host'].':'.$uri['port'].
|
---|
436 | $this->resp_headers['location'];
|
---|
437 | }
|
---|
438 | }
|
---|
439 | // perform redirected request, always via GET (required by RFC)
|
---|
440 | return $this->sendRequest($this->resp_headers['location'],array(),'GET');
|
---|
441 | }
|
---|
442 | }
|
---|
443 |
|
---|
444 | // check if headers are as expected
|
---|
445 | if($this->header_regexp && !preg_match($this->header_regexp,$r_headers)){
|
---|
446 | $this->error = 'The received headers did not match the given regexp';
|
---|
447 | unset($this->connections[$connectionId]);
|
---|
448 | return false;
|
---|
449 | }
|
---|
450 |
|
---|
451 | //read body (with chunked encoding if needed)
|
---|
452 | $r_body = '';
|
---|
453 | if(preg_match('/transfer\-(en)?coding:\s*chunked\r\n/i',$r_headers)){
|
---|
454 | do {
|
---|
455 | unset($chunk_size);
|
---|
456 | do {
|
---|
457 | if(feof($socket)){
|
---|
458 | $this->error = 'Premature End of File (socket)';
|
---|
459 | unset($this->connections[$connectionId]);
|
---|
460 | return false;
|
---|
461 | }
|
---|
462 | if(time()-$start > $this->timeout){
|
---|
463 | $this->status = -100;
|
---|
464 | $this->error = sprintf('Timeout while reading chunk (%.3fs)',$this->_time() - $this->start);
|
---|
465 | unset($this->connections[$connectionId]);
|
---|
466 | return false;
|
---|
467 | }
|
---|
468 | $byte = fread($socket,1);
|
---|
469 | $chunk_size .= $byte;
|
---|
470 | } while (preg_match('/[a-zA-Z0-9]/',$byte)); // read chunksize including \r
|
---|
471 |
|
---|
472 | $byte = fread($socket,1); // readtrailing \n
|
---|
473 | $chunk_size = hexdec($chunk_size);
|
---|
474 | if ($chunk_size) {
|
---|
475 | $this_chunk = fread($socket,$chunk_size);
|
---|
476 | $r_body .= $this_chunk;
|
---|
477 | $byte = fread($socket,2); // read trailing \r\n
|
---|
478 | }
|
---|
479 |
|
---|
480 | if($this->max_bodysize && strlen($r_body) > $this->max_bodysize){
|
---|
481 | $this->error = 'Allowed response size exceeded';
|
---|
482 | if ($this->max_bodysize_abort){
|
---|
483 | unset($this->connections[$connectionId]);
|
---|
484 | return false;
|
---|
485 | } else {
|
---|
486 | break;
|
---|
487 | }
|
---|
488 | }
|
---|
489 | } while ($chunk_size);
|
---|
490 | }else{
|
---|
491 | // read entire socket
|
---|
492 | while (!feof($socket)) {
|
---|
493 | if(time()-$start > $this->timeout){
|
---|
494 | $this->status = -100;
|
---|
495 | $this->error = sprintf('Timeout while reading response (%.3fs)',$this->_time() - $this->start);
|
---|
496 | unset($this->connections[$connectionId]);
|
---|
497 | return false;
|
---|
498 | }
|
---|
499 | $r_body .= fread($socket,4096);
|
---|
500 | $r_size = strlen($r_body);
|
---|
501 | if($this->max_bodysize && $r_size > $this->max_bodysize){
|
---|
502 | $this->error = 'Allowed response size exceeded';
|
---|
503 | if ($this->max_bodysize_abort) {
|
---|
504 | unset($this->connections[$connectionId]);
|
---|
505 | return false;
|
---|
506 | } else {
|
---|
507 | break;
|
---|
508 | }
|
---|
509 | }
|
---|
510 | if(isset($this->resp_headers['content-length']) &&
|
---|
511 | !isset($this->resp_headers['transfer-encoding']) &&
|
---|
512 | $this->resp_headers['content-length'] == $r_size){
|
---|
513 | // we read the content-length, finish here
|
---|
514 | break;
|
---|
515 | }
|
---|
516 | }
|
---|
517 | }
|
---|
518 |
|
---|
519 | if (!$this->keep_alive ||
|
---|
520 | (isset($this->resp_headers['connection']) && $this->resp_headers['connection'] == 'Close')) {
|
---|
521 | // close socket
|
---|
522 | $status = socket_get_status($socket);
|
---|
523 | fclose($socket);
|
---|
524 | unset($this->connections[$connectionId]);
|
---|
525 | }
|
---|
526 |
|
---|
527 | // decode gzip if needed
|
---|
528 | if(isset($this->resp_headers['content-encoding']) &&
|
---|
529 | $this->resp_headers['content-encoding'] == 'gzip' &&
|
---|
530 | strlen($r_body) > 10 && substr($r_body,0,3)=="\x1f\x8b\x08"){
|
---|
531 | $this->resp_body = @gzinflate(substr($r_body, 10));
|
---|
532 | if($this->resp_body === false){
|
---|
533 | $this->error = 'Failed to decompress gzip encoded content';
|
---|
534 | $this->resp_body = $r_body;
|
---|
535 | }
|
---|
536 | }else{
|
---|
537 | $this->resp_body = $r_body;
|
---|
538 | }
|
---|
539 |
|
---|
540 | $this->_debug('response body',$this->resp_body);
|
---|
541 | $this->redirect_count = 0;
|
---|
542 | return true;
|
---|
543 | }
|
---|
544 |
|
---|
545 | /**
|
---|
546 | * print debug info
|
---|
547 | *
|
---|
548 | * @author Andreas Gohr <[email protected]>
|
---|
549 | */
|
---|
550 | function _debug($info,$var=null){
|
---|
551 | if(!$this->debug) return;
|
---|
552 | print '<b>'.$info.'</b> '.($this->_time() - $this->start).'s<br />';
|
---|
553 | if(!is_null($var)){
|
---|
554 | ob_start();
|
---|
555 | print_r($var);
|
---|
556 | $content = htmlspecialchars(ob_get_contents());
|
---|
557 | ob_end_clean();
|
---|
558 | print '<pre>'.$content.'</pre>';
|
---|
559 | }
|
---|
560 | }
|
---|
561 |
|
---|
562 | /**
|
---|
563 | * Return current timestamp in microsecond resolution
|
---|
564 | */
|
---|
565 | function _time(){
|
---|
566 | list($usec, $sec) = explode(" ", microtime());
|
---|
567 | return ((float)$usec + (float)$sec);
|
---|
568 | }
|
---|
569 |
|
---|
570 | /**
|
---|
571 | * convert given header string to Header array
|
---|
572 | *
|
---|
573 | * All Keys are lowercased.
|
---|
574 | *
|
---|
575 | * @author Andreas Gohr <[email protected]>
|
---|
576 | */
|
---|
577 | function _parseHeaders($string){
|
---|
578 | $headers = array();
|
---|
579 | if (!preg_match_all('/^\s*([\w-]+)\s*:\s*([\S \t]+)\s*$/m', $string,
|
---|
580 | $matches, PREG_SET_ORDER)) {
|
---|
581 | return $headers;
|
---|
582 | }
|
---|
583 | foreach($matches as $match){
|
---|
584 | list(, $key, $val) = $match;
|
---|
585 | $key = strtolower($key);
|
---|
586 | if(isset($headers[$key])){
|
---|
587 | if(is_array($headers[$key])){
|
---|
588 | $headers[$key][] = $val;
|
---|
589 | }else{
|
---|
590 | $headers[$key] = array($headers[$key],$val);
|
---|
591 | }
|
---|
592 | }else{
|
---|
593 | $headers[$key] = $val;
|
---|
594 | }
|
---|
595 | }
|
---|
596 | return $headers;
|
---|
597 | }
|
---|
598 |
|
---|
599 | /**
|
---|
600 | * convert given header array to header string
|
---|
601 | *
|
---|
602 | * @author Andreas Gohr <[email protected]>
|
---|
603 | */
|
---|
604 | function _buildHeaders($headers){
|
---|
605 | $string = '';
|
---|
606 | foreach($headers as $key => $value){
|
---|
607 | if(empty($value)) continue;
|
---|
608 | $string .= $key.': '.$value.HTTP_NL;
|
---|
609 | }
|
---|
610 | return $string;
|
---|
611 | }
|
---|
612 |
|
---|
613 | /**
|
---|
614 | * get cookies as http header string
|
---|
615 | *
|
---|
616 | * @author Andreas Goetz <[email protected]>
|
---|
617 | */
|
---|
618 | function _getCookies(){
|
---|
619 | $headers = '';
|
---|
620 | foreach ($this->cookies as $key => $val){
|
---|
621 | $headers .= "$key=$val; ";
|
---|
622 | }
|
---|
623 | $headers = substr($headers, 0, -2);
|
---|
624 | if ($headers !== '') $headers = "Cookie: $headers".HTTP_NL;
|
---|
625 | return $headers;
|
---|
626 | }
|
---|
627 |
|
---|
628 | /**
|
---|
629 | * Encode data for posting
|
---|
630 | *
|
---|
631 | * @author Andreas Gohr <[email protected]>
|
---|
632 | */
|
---|
633 | function _postEncode($data){
|
---|
634 | $url = '';
|
---|
635 | foreach($data as $key => $val){
|
---|
636 | if($url) $url .= '&';
|
---|
637 | $url .= urlencode($key).'='.urlencode($val);
|
---|
638 | }
|
---|
639 | return $url;
|
---|
640 | }
|
---|
641 |
|
---|
642 | /**
|
---|
643 | * Encode data for posting using multipart encoding
|
---|
644 | *
|
---|
645 | * @fixme use of urlencode might be wrong here
|
---|
646 | * @author Andreas Gohr <[email protected]>
|
---|
647 | */
|
---|
648 | function _postMultipartEncode($data){
|
---|
649 | $boundary = '--'.$this->boundary;
|
---|
650 | $out = '';
|
---|
651 | foreach($data as $key => $val){
|
---|
652 | $out .= $boundary.HTTP_NL;
|
---|
653 | if(!is_array($val)){
|
---|
654 | $out .= 'Content-Disposition: form-data; name="'.urlencode($key).'"'.HTTP_NL;
|
---|
655 | $out .= HTTP_NL; // end of headers
|
---|
656 | $out .= $val;
|
---|
657 | $out .= HTTP_NL;
|
---|
658 | }else{
|
---|
659 | $out .= 'Content-Disposition: form-data; name="'.urlencode($key).'"';
|
---|
660 | if($val['filename']) $out .= '; filename="'.urlencode($val['filename']).'"';
|
---|
661 | $out .= HTTP_NL;
|
---|
662 | if($val['mimetype']) $out .= 'Content-Type: '.$val['mimetype'].HTTP_NL;
|
---|
663 | $out .= HTTP_NL; // end of headers
|
---|
664 | $out .= $val['body'];
|
---|
665 | $out .= HTTP_NL;
|
---|
666 | }
|
---|
667 | }
|
---|
668 | $out .= "$boundary--".HTTP_NL;
|
---|
669 | return $out;
|
---|
670 | }
|
---|
671 |
|
---|
672 | /**
|
---|
673 | * Generates a unique identifier for a connection.
|
---|
674 | *
|
---|
675 | * @return string unique identifier
|
---|
676 | */
|
---|
677 | function _uniqueConnectionId($server, $port) {
|
---|
678 | return "$server:$port";
|
---|
679 | }
|
---|
680 | }
|
---|
681 |
|
---|
682 | //Setup VIM: ex: et ts=4 :
|
---|