1<?php 2if (!defined('DOKU_INC')) 3 define('DOKU_INC', realpath(dirname(__FILE__) . '/../../../') . '/'); 4 5require_once (DOKU_INC . 'inc/HTTPClient.php'); 6 7define('HTTP_NL', "\r\n"); 8 9/** 10 * Modifies sendRequest. If max_bodysize_limit is set to true, the size of 11 * the retrieved body is limited to the value set in max_bodysize. 12 * 13 * Also, modifies get and post to allow response codes in the 200 range. 14 * 15 * @author Gina Haeussge <osd@foosel.net> 16 */ 17class LinkbackHTTPClient extends DokuHTTPClient { 18 19 var $max_bodysize_limit = false; 20 21 function __construct() { 22 parent::__construct(); 23 } 24 25 /** 26 * Simple function to do a GET request 27 * 28 * Returns the wanted page or false on an error; 29 * 30 * @param string $url The URL to fetch 31 * @param bool $sloppy304 Return body on 304 not modified 32 * @author Andreas Gohr <andi@splitbrain.org> 33 */ 34 function get($url,$sloppy304=false){ 35 if(!$this->sendRequest($url)) return false; 36 if($this->status == 304 && $sloppy304) return $this->resp_body; 37 if($this->status < 200 || $this->status > 206) return false; 38 return $this->resp_body; 39 } 40 41 /** 42 * Simple function to do a POST request 43 * 44 * Returns the resulting page or false on an error; 45 * 46 * @author Andreas Gohr <andi@splitbrain.org> 47 */ 48 function post($url,$data){ 49 if(!$this->sendRequest($url,$data,'POST')) return false; 50 if($this->status < 200 || $this->status > 206) return false; 51 return $this->resp_body; 52 } 53 54 /** 55 * Do an HTTP request 56 * 57 * @author Andreas Goetz <cpuidle@gmx.de> 58 * @author Andreas Gohr <andi@splitbrain.org> 59 */ 60 function sendRequest($url, $data = array (), $method = 'GET') { 61 $this->error = ''; 62 $this->status = 0; 63 64 // parse URL into bits 65 $uri = parse_url($url); 66 $server = $uri['host']; 67 $path = $uri['path']; 68 if (empty ($path)) 69 $path = '/'; 70 if (!empty ($uri['query'])) 71 $path .= '?' . $uri['query']; 72 $port = $uri['port']; 73 if ($uri['user']) 74 $this->user = $uri['user']; 75 if ($uri['pass']) 76 $this->pass = $uri['pass']; 77 78 // proxy setup 79 if ($this->proxy_host) { 80 $request_url = $url; 81 $server = $this->proxy_host; 82 $port = $this->proxy_port; 83 if (empty ($port)) 84 $port = 8080; 85 } else { 86 $request_url = $path; 87 $server = $server; 88 if (empty ($port)) 89 $port = ($uri['scheme'] == 'https') ? 443 : 80; 90 } 91 92 // add SSL stream prefix if needed - needs SSL support in PHP 93 if ($port == 443 || $this->proxy_ssl) 94 $server = 'ssl://' . $server; 95 96 // prepare headers 97 $headers = $this->headers; 98 $headers['Host'] = $uri['host']; 99 $headers['User-Agent'] = $this->agent; 100 $headers['Referer'] = $this->referer; 101 $headers['Connection'] = 'Close'; 102 if ($method == 'POST') { 103 $post = $this->_postEncode($data); 104 $headers['Content-Type'] = 'application/x-www-form-urlencoded'; 105 $headers['Content-Length'] = strlen($post); 106 } 107 if ($this->user) { 108 $headers['Authorization'] = 'BASIC ' . base64_encode($this->user . ':' . $this->pass); 109 } 110 if ($this->proxy_user) { 111 $headers['Proxy-Authorization'] = 'BASIC ' . base64_encode($this->proxy_user . ':' . $this->proxy_pass); 112 } 113 114 // stop time 115 $start = time(); 116 117 // open socket 118 $socket = @ fsockopen($server, $port, $errno, $errstr, $this->timeout); 119 if (!$socket) { 120 $resp->status = '-100'; 121 $this->error = "Could not connect to $server:$port\n$errstr ($errno)"; 122 return false; 123 } 124 //set non blocking 125 stream_set_blocking($socket, 0); 126 127 // build request 128 $request = "$method $request_url HTTP/" . $this->http . HTTP_NL; 129 $request .= $this->_buildHeaders($headers); 130 $request .= $this->_getCookies(); 131 $request .= HTTP_NL; 132 $request .= $post; 133 134 $this->_debug('request', $request); 135 136 // send request 137 fputs($socket, $request); 138 // read headers from socket 139 $r_headers = ''; 140 do { 141 if (time() - $start > $this->timeout) { 142 $this->status = -100; 143 $this->error = 'Timeout while reading headers'; 144 return false; 145 } 146 if (feof($socket)) { 147 $this->error = 'Premature End of File (socket)'; 148 return false; 149 } 150 $r_headers .= fread($socket, 1); #FIXME read full lines here? 151 } while (!preg_match('/\r\n\r\n$/', $r_headers)); 152 153 $this->_debug('response headers', $r_headers); 154 155 // check if expected body size exceeds allowance 156 if ($this->max_bodysize && preg_match('/\r\nContent-Length:\s*(\d+)\r\n/i', $r_headers, $match)) { 157 if ($match[1] > $this->max_bodysize) { 158 $this->error = 'Reported content length exceeds allowed response size'; 159 if (!$this->max_bodysize_limit) 160 return false; 161 } 162 } 163 164 // get Status 165 if (!preg_match('/^HTTP\/(\d\.\d)\s*(\d+).*?\n/', $r_headers, $m)) { 166 $this->error = 'Server returned bad answer'; 167 return false; 168 } 169 $this->status = $m[2]; 170 171 // handle headers and cookies 172 $this->resp_headers = $this->_parseHeaders($r_headers); 173 if (isset ($this->resp_headers['set-cookie'])) { 174 foreach ((array) $this->resp_headers['set-cookie'] as $c) { 175 list ($key, $value, $foo) = explode('=', $cookie); 176 $this->cookies[$key] = $value; 177 } 178 } 179 180 $this->_debug('Object headers', $this->resp_headers); 181 182 // check server status code to follow redirect 183 if ($this->status == 301 || $this->status == 302) { 184 if (empty ($this->resp_headers['location'])) { 185 $this->error = 'Redirect but no Location Header found'; 186 return false; 187 } 188 elseif ($this->redirect_count == $this->max_redirect) { 189 $this->error = 'Maximum number of redirects exceeded'; 190 return false; 191 } else { 192 $this->redirect_count++; 193 $this->referer = $url; 194 if (!preg_match('/^http/i', $this->resp_headers['location'])) { 195 $this->resp_headers['location'] = $uri['scheme'] . '://' . $uri['host'] . 196 $this->resp_headers['location']; 197 } 198 // perform redirected request, always via GET (required by RFC) 199 return $this->sendRequest($this->resp_headers['location'], array (), 'GET'); 200 } 201 } 202 203 // check if headers are as expected 204 if ($this->header_regexp && !preg_match($this->header_regexp, $r_headers)) { 205 $this->error = 'The received headers did not match the given regexp'; 206 return false; 207 } 208 209 //read body (with chunked encoding if needed) 210 $r_body = ''; 211 if (preg_match('/transfer\-(en)?coding:\s*chunked\r\n/i', $r_header)) { 212 do { 213 unset ($chunk_size); 214 do { 215 if (feof($socket)) { 216 $this->error = 'Premature End of File (socket)'; 217 return false; 218 } 219 if (time() - $start > $this->timeout) { 220 $this->status = -100; 221 $this->error = 'Timeout while reading chunk'; 222 return false; 223 } 224 $byte = fread($socket, 1); 225 $chunk_size .= $byte; 226 } while (preg_match('/[a-zA-Z0-9]/', $byte)); // read chunksize including \r 227 228 $byte = fread($socket, 1); // readtrailing \n 229 $chunk_size = hexdec($chunk_size); 230 $this_chunk = fread($socket, $chunk_size); 231 $r_body .= $this_chunk; 232 if ($chunk_size) 233 $byte = fread($socket, 2); // read trailing \r\n 234 235 if ($this->max_bodysize && strlen($r_body) > $this->max_bodysize) { 236 $this->error = 'Allowed response size exceeded'; 237 if ($this->max_bodysize_limit) 238 break; 239 else 240 return false; 241 } 242 } 243 while ($chunk_size); 244 } else { 245 // read entire socket 246 while (!feof($socket)) { 247 if (time() - $start > $this->timeout) { 248 $this->status = -100; 249 $this->error = 'Timeout while reading response'; 250 return false; 251 } 252 $r_body .= fread($socket, 4096); 253 if ($this->max_bodysize && strlen($r_body) > $this->max_bodysize) { 254 $this->error = 'Allowed response size exceeded'; 255 if ($this->max_bodysize_limit) 256 break; 257 else 258 return false; 259 } 260 } 261 } 262 263 // close socket 264 $status = socket_get_status($socket); 265 fclose($socket); 266 267 // decode gzip if needed 268 if ($this->resp_headers['content-encoding'] == 'gzip') { 269 $this->resp_body = gzinflate(substr($r_body, 10)); 270 } else { 271 $this->resp_body = $r_body; 272 } 273 274 $this->_debug('response body', $this->resp_body); 275 $this->redirect_count = 0; 276 return true; 277 } 278 279} 280