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