|
1 <?php |
|
2 |
|
3 /* |
|
4 * Enano - an open-source CMS capable of wiki functions, Drupal-like sidebar blocks, and everything in between |
|
5 * Version 1.0.3 (Dyrad) |
|
6 * Copyright (C) 2006-2007 Dan Fuhry |
|
7 * class_http.php - Pure PHP HTTP client library |
|
8 * |
|
9 * This program is Free Software; you can redistribute and/or modify it under the terms of the GNU General Public License |
|
10 * as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. |
|
11 * |
|
12 * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied |
|
13 * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for details. |
|
14 */ |
|
15 |
|
16 // |
|
17 // HTTP status codes |
|
18 // |
|
19 |
|
20 // Informational |
|
21 define('HTTP_CONTINUE', 100); |
|
22 define('HTTP_SWITCHING_PROTOCOLS', 101); |
|
23 define('HTTP_PROCESSING', 102); |
|
24 |
|
25 // Success |
|
26 define('HTTP_OK', 200); |
|
27 define('HTTP_CREATED', 201); |
|
28 define('HTTP_ACCEPTED', 202); |
|
29 define('HTTP_NON_AUTHORITATIVE', 203); |
|
30 define('HTTP_NO_CONTENT', 204); |
|
31 define('HTTP_RESET_CONTENT', 205); |
|
32 define('HTTP_PARTIAL_CONTENT', 206); |
|
33 define('HTTP_MULTI_STATUS', 207); |
|
34 |
|
35 // Redirection |
|
36 define('HTTP_MULTIPLE_CHOICES', 300); |
|
37 define('HTTP_MOVED_PERMANENTLY', 301); |
|
38 define('HTTP_FOUND', 302); |
|
39 define('HTTP_SEE_OTHER', 303); |
|
40 define('HTTP_NOT_MODIFIED', 304); |
|
41 define('HTTP_USE_PROXY', 305); |
|
42 define('HTTP_SWITCH_PROXY', 306); |
|
43 define('HTTP_TEMPORARY_REDIRECT', 307); |
|
44 |
|
45 // Client Error |
|
46 define('HTTP_BAD_REQUEST', 400); |
|
47 define('HTTP_UNAUTHORIZED', 401); |
|
48 define('HTTP_PAYMENT_REQUIRED', 402); |
|
49 define('HTTP_FORBIDDEN', 403); |
|
50 define('HTTP_NOT_FOUND', 404); |
|
51 define('HTTP_METHOD_NOT_ALLOWED', 405); |
|
52 define('HTTP_NOT_ACCEPTABLE', 406); |
|
53 define('HTTP_PROXY_AUTHENTICATION_REQUIRED', 407); |
|
54 define('HTTP_REQUEST_TIMEOUT', 408); |
|
55 define('HTTP_CONFLICT', 409); |
|
56 define('HTTP_GONE', 410); |
|
57 define('HTTP_LENGTH_REQUIRED', 411); |
|
58 define('HTTP_PRECONDITION_FAILED', 412); |
|
59 define('HTTP_REQUEST_ENTITY_TOO_LARGE', 413); |
|
60 define('HTTP_REQUEST_URI_TOO_LONG', 414); |
|
61 define('HTTP_UNSUPPORTED_MEDIA_TYPE', 415); |
|
62 define('HTTP_REQUESTED_RANGE_NOT_SATISFIABLE', 416); |
|
63 define('HTTP_EXPECTATION_FAILED', 417); |
|
64 define('HTTP_UNPROCESSABLE_ENTITY', 422); |
|
65 define('HTTP_LOCKED', 423); |
|
66 define('HTTP_FAILED_DEPENDENCY', 424); |
|
67 define('HTTP_UNORDERED_COLLECTION', 425); |
|
68 define('HTTP_UPGRADE_REQUIRED', 426); |
|
69 define('HTTP_RETRY_WITH', 449); |
|
70 |
|
71 // Server error |
|
72 define('HTTP_INTERNAL_SERVER_ERROR', 500); |
|
73 define('HTTP_NOT_IMPLEMENTED', 501); |
|
74 define('HTTP_BAD_GATEWAY', 502); |
|
75 define('HTTP_SERVICE_TEMPORARILY_UNAVAILABLE', 503); |
|
76 define('HTTP_GATEWAY_TIMEOUT', 504); |
|
77 define('HTTP_HTTP_VERSION_NOT_SUPPORTED', 505); |
|
78 define('HTTP_VARIANT_ALSO_NEGOTIATES', 506); |
|
79 define('HTTP_INSUFFICIENT_STORAGE', 507); |
|
80 define('HTTP_BANDWIDTH_LIMIT_EXCEEDED', 509); |
|
81 define('HTTP_NOT_EXTENDED', 510); |
|
82 |
|
83 /** |
|
84 * Class for making HTTP requests. This can do GET and POST, and when used properly it consumes under a meg of memory, even with huge files. |
|
85 * @package Enano |
|
86 * @subpackage Backend functions |
|
87 * @copyright 2007 Dan Fuhry |
|
88 */ |
|
89 |
|
90 class Request_HTTP |
|
91 { |
|
92 |
|
93 /** |
|
94 * Switch to enable or disable debugging. You want this off on production sites. |
|
95 * @var bool |
|
96 */ |
|
97 |
|
98 var $debug = false; |
|
99 |
|
100 /** |
|
101 * The host the request will be sent to. |
|
102 * @var string |
|
103 */ |
|
104 |
|
105 var $host = ''; |
|
106 |
|
107 /** |
|
108 * The TCP port our connection is (will be) on. |
|
109 * @var int |
|
110 */ |
|
111 |
|
112 var $port = 80; |
|
113 |
|
114 /** |
|
115 * The request method. Can be GET or POST, defaults to GET. |
|
116 * @var string |
|
117 */ |
|
118 |
|
119 var $method = 'GET'; |
|
120 |
|
121 /** |
|
122 * The URI to the remote script. |
|
123 * @var string |
|
124 */ |
|
125 |
|
126 var $uri = ''; |
|
127 |
|
128 /** |
|
129 * The parameters to be sent on GET. |
|
130 * @var array (associative) |
|
131 */ |
|
132 |
|
133 var $parms_get = array(); |
|
134 |
|
135 /** |
|
136 * The parameters to be sent on POST. Ignored if $this->method == GET. |
|
137 * @var array (associative) |
|
138 */ |
|
139 |
|
140 var $parms_post = array(); |
|
141 |
|
142 /** |
|
143 * The list of cookies that will be sent. |
|
144 * @var array (associative) |
|
145 */ |
|
146 |
|
147 var $cookies_out = array(); |
|
148 |
|
149 /** |
|
150 * Additional request headers. |
|
151 * @var array (associative) |
|
152 */ |
|
153 |
|
154 var $headers = array(); |
|
155 |
|
156 /** |
|
157 * Cached response. |
|
158 * @var string, or bool:false if the request hasn't been sent yet |
|
159 */ |
|
160 |
|
161 var $response = false; |
|
162 |
|
163 /** |
|
164 * Cached response code |
|
165 * @var int set to -1 if request hasn't been sent yet |
|
166 */ |
|
167 |
|
168 var $response_code = -1; |
|
169 |
|
170 /** |
|
171 * Cached response code string |
|
172 * @var string or bool:false if the request hasn't been sent yet |
|
173 */ |
|
174 |
|
175 var $response_string = false; |
|
176 |
|
177 /** |
|
178 * Resource for the socket. False if a connection currently isn't going. |
|
179 * @var resource |
|
180 */ |
|
181 |
|
182 var $socket = false; |
|
183 |
|
184 /** |
|
185 * The state of our request. 0 means it hasn't been made yet. 1 means the socket is open, 2 means the socket is open and the request has been written, 3 means the headers have been fetched, and 4 means the request is completed. |
|
186 * @var int |
|
187 */ |
|
188 |
|
189 var $state = 0; |
|
190 |
|
191 /** |
|
192 * Constructor. |
|
193 * @param string Hostname to send to |
|
194 * @param string URI (/index.php) |
|
195 * @param string Request method - GET or POST. |
|
196 * @param int Optional. The port to open the request on. Defaults to 80. |
|
197 */ |
|
198 |
|
199 function Request_HTTP($host, $uri, $method = 'GET', $port = 80) |
|
200 { |
|
201 if ( !preg_match('/^(([a-z0-9-]+\.)*?)([a-z0-9-]+)$/', $host) ) |
|
202 die(__CLASS__ . ': Invalid hostname'); |
|
203 $this->host = $host; |
|
204 $this->uri = $uri; |
|
205 if ( is_int($port) && $port >= 1 && $port <= 65535 ) |
|
206 $this->port = $port; |
|
207 else |
|
208 die(__CLASS__ . ': Invalid port'); |
|
209 $method = strtoupper($method); |
|
210 if ( $method == 'GET' || $method == 'POST' ) |
|
211 $this->method = $method; |
|
212 else |
|
213 die(__CLASS__ . ': Invalid request method'); |
|
214 |
|
215 $newline = "\r\n"; |
|
216 $php_ver = PHP_VERSION; |
|
217 $this->add_header('User-Agent', "PHP/$php_ver (Server: {$_SERVER['SERVER_SOFTWARE']}; automated bot request)"); |
|
218 } |
|
219 |
|
220 /** |
|
221 * Sets one or more cookies to be sent to the server. |
|
222 * @param string or array If a string, the cookie name. If an array, associative array in the form of cookiename => cookievalue |
|
223 * @param string or bool If a string, the cookie value. If boolean, defaults to false, param 1 should be an array, and this should not be passed. |
|
224 */ |
|
225 |
|
226 function add_cookie($cookiename, $cookievalue = false) |
|
227 { |
|
228 if ( is_array($cookiename) && !$cookievalue ) |
|
229 { |
|
230 foreach ( $cookiename as $name => $value ) |
|
231 { |
|
232 $this->cookies_out[$name] = $value; |
|
233 } |
|
234 } |
|
235 else if ( is_string($cookiename) && is_string($cookievalue) ) |
|
236 { |
|
237 $this->cookies_out[$cookiename] = $cookievalue; |
|
238 } |
|
239 else |
|
240 { |
|
241 die(__CLASS__ . '::' . __METHOD__ . ': Invalid argument(s)'); |
|
242 } |
|
243 } |
|
244 |
|
245 /** |
|
246 * Sets one or more request header values. |
|
247 * @param string or array If a string, the header name. If an array, associative array in the form of headername => headervalue |
|
248 * @param string or bool If a string, the header value. If boolean, defaults to false, param 1 should be an array, and this should not be passed. |
|
249 */ |
|
250 |
|
251 function add_header($headername, $headervalue = false) |
|
252 { |
|
253 if ( is_array($headername) && !$headervalue ) |
|
254 { |
|
255 foreach ( $headername as $name => $value ) |
|
256 { |
|
257 $this->headers[$name] = $value; |
|
258 } |
|
259 } |
|
260 else if ( is_string($headername) && is_string($headervalue) ) |
|
261 { |
|
262 $this->headers[$headername] = $headervalue; |
|
263 } |
|
264 else |
|
265 { |
|
266 die(__CLASS__ . '::' . __METHOD__ . ': Invalid argument(s)'); |
|
267 } |
|
268 } |
|
269 |
|
270 /** |
|
271 * Adds one or more values to be passed on GET. |
|
272 * @param string or array If a string, the parameter name. If an array, associative array in the form of parametername => parametervalue |
|
273 * @param string or bool If a string, the parameter value. If boolean, defaults to false, param 1 should be an array, and this should not be passed. |
|
274 */ |
|
275 |
|
276 function add_get($getname, $getvalue = false) |
|
277 { |
|
278 if ( is_array($getname) && !$getvalue ) |
|
279 { |
|
280 foreach ( $getname as $name => $value ) |
|
281 { |
|
282 $this->parms_get[$name] = $value; |
|
283 } |
|
284 } |
|
285 else if ( is_string($getname) && is_string($getvalue) ) |
|
286 { |
|
287 $this->parms_get[$getname] = $getvalue; |
|
288 } |
|
289 else |
|
290 { |
|
291 die(__CLASS__ . '::' . __METHOD__ . ': Invalid argument(s)'); |
|
292 } |
|
293 } |
|
294 |
|
295 /** |
|
296 * Adds one or more values to be passed on POST. |
|
297 * @param string or array If a string, the header name. If an array, associative array in the form of headername => headervalue |
|
298 * @param string or bool If a string, the header value. If boolean, defaults to false, param 1 should be an array, and this should not be passed. |
|
299 */ |
|
300 |
|
301 function add_post($postname, $postvalue = false) |
|
302 { |
|
303 if ( is_array($postname) && !$postvalue ) |
|
304 { |
|
305 foreach ( $postname as $name => $value ) |
|
306 { |
|
307 $this->parms_post[$name] = $value; |
|
308 } |
|
309 } |
|
310 else if ( is_string($postname) && is_string($postvalue) ) |
|
311 { |
|
312 $this->parms_post[$postname] = $postvalue; |
|
313 } |
|
314 else |
|
315 { |
|
316 die(__CLASS__ . '::' . __METHOD__ . ': Invalid argument(s)'); |
|
317 } |
|
318 } |
|
319 |
|
320 /** |
|
321 * Internal function to open up the socket. |
|
322 * @access private |
|
323 */ |
|
324 |
|
325 function _sock_open(&$connection) |
|
326 { |
|
327 if ( $this->debug ) |
|
328 { |
|
329 echo '<hr /><div style="white-space: nowrap;">'; |
|
330 echo '<p><b>' . __CLASS__ . ': Sending request</b></p><p>Request parameters:</p>'; |
|
331 echo "<p><b>Headers:</b></p><pre>$headers</pre>"; |
|
332 echo "<p><b>Cookies:</b> $cookies</p>"; |
|
333 echo "<p><b>GET URI:</b> " . htmlspecialchars($get) . "</p>"; |
|
334 echo "<p><b>POST DATA:</b> " . htmlspecialchars($post) . "</p>"; |
|
335 } |
|
336 |
|
337 // Open connection |
|
338 $connection = fsockopen($this->host, $this->port); |
|
339 if ( !$connection ) |
|
340 die(__CLASS__ . '::' . __METHOD__ . ': Could not make connection'); |
|
341 |
|
342 if ( $this->debug ) |
|
343 echo '<p>Connection opened. Writing main request to socket. Raw socket data follows.</p><pre>'; |
|
344 |
|
345 // 1 = socket open |
|
346 $this->state = 1; |
|
347 } |
|
348 |
|
349 /** |
|
350 * Internal function to actually write the request into the socket. |
|
351 * @access private |
|
352 */ |
|
353 |
|
354 function _write_request(&$connection, &$headers, &$cookies, &$get, &$post) |
|
355 { |
|
356 $newline = "\r\n"; |
|
357 |
|
358 $this->_fputs($connection, "{$this->method} {$this->uri}{$get} HTTP/1.1{$newline}"); |
|
359 $this->_fputs($connection, "Host: {$this->host}{$newline}"); |
|
360 $this->_fputs($connection, $headers); |
|
361 $this->_fputs($connection, $cookies); |
|
362 |
|
363 if ( $this->method == 'POST' ) |
|
364 { |
|
365 // POST-specific parameters |
|
366 $post_length = strlen($post); |
|
367 $this->_fputs($connection, "Content-type: application/x-www-form-urlencoded{$newline}"); |
|
368 $this->_fputs($connection, "Content-length: {$post_length}{$newline}"); |
|
369 } |
|
370 |
|
371 $this->_fputs($connection, "Connection: close{$newline}"); |
|
372 $this->_fputs($connection, "{$newline}"); |
|
373 |
|
374 if ( $this->method == 'POST' ) |
|
375 { |
|
376 $this->_fputs($connection, $post); |
|
377 } |
|
378 |
|
379 if ( $this->debug ) |
|
380 echo '</pre><p>Request written. Fetching response.</p>'; |
|
381 |
|
382 // 2 = request written |
|
383 $this->state = 2; |
|
384 } |
|
385 |
|
386 /** |
|
387 * Wrap up and close the socket. Nothing more than a call to fsockclose() except in debug mode. |
|
388 * @access private |
|
389 */ |
|
390 |
|
391 function sock_close(&$connection) |
|
392 { |
|
393 if ( $this->debug ) |
|
394 { |
|
395 echo '<p>Response fetched. Closing connection. Response text follows.</p><pre>'; |
|
396 echo htmlspecialchars($buffer); |
|
397 echo '</pre></div><hr />'; |
|
398 } |
|
399 |
|
400 fclose($connection); |
|
401 } |
|
402 |
|
403 /** |
|
404 * Internal function to grab the response code and status string |
|
405 * @access string |
|
406 */ |
|
407 |
|
408 function _parse_response_code($buffer) |
|
409 { |
|
410 // Retrieve response code and status |
|
411 $pos_newline = strpos($buffer, "\n"); |
|
412 $pos_carriage_return = strpos($buffer, "\r"); |
|
413 $pos_end_first_line = ( $pos_carriage_return < $pos_newline && $pos_carriage_return > 0 ) ? $pos_carriage_return : $pos_newline; |
|
414 |
|
415 // First line is in format of: |
|
416 // HTTP/1.1 ### Blah blah blah(\r?)\n |
|
417 $response_code = substr($buffer, 9, 3); |
|
418 $response_string = substr($buffer, 13, ( $pos_end_first_line - 13 ) ); |
|
419 $this->response_code = intval($response_code); |
|
420 $this->response_string = $response_string; |
|
421 } |
|
422 |
|
423 /** |
|
424 * Internal function to send the request. |
|
425 * @access private |
|
426 */ |
|
427 |
|
428 function _send_request() |
|
429 { |
|
430 $this->concat_headers($headers, $cookies, $get, $post); |
|
431 |
|
432 if ( $this->state < 1 ) |
|
433 { |
|
434 $this->_sock_open($this->socket); |
|
435 } |
|
436 if ( $this->state < 2 ) |
|
437 { |
|
438 $this->_write_request($this->socket, $headers, $cookies, $get, $post); |
|
439 } |
|
440 if ( $this->state == 2 ) |
|
441 { |
|
442 $buffer = $this->_read_until_newlines($this->socket); |
|
443 $this->state = 3; |
|
444 $this->_parse_response_code($buffer); |
|
445 $this->response = $buffer; |
|
446 } |
|
447 if ( $this->state == 3 ) |
|
448 { |
|
449 // Determine transfer encoding |
|
450 $is_chunked = preg_match("/Transfer-Encoding: (chunked)\r?\n/", $this->response); |
|
451 |
|
452 $buffer = ''; |
|
453 while ( !feof($this->socket) ) |
|
454 { |
|
455 $part = fgets($this->socket, 1024); |
|
456 if ( $is_chunked && preg_match("/^([a-f0-9]+)\x0D\x0A$/", $part, $match) ) |
|
457 { |
|
458 $chunklen = hexdec($match[1]); |
|
459 $part = ( $chunklen > 0 ) ? fread($this->socket, $chunklen) : ''; |
|
460 // remove the last newline from $part |
|
461 $part = preg_replace("/\r?\n\$/m", "", $part); |
|
462 } |
|
463 $buffer .= $part; |
|
464 } |
|
465 $this->response .= $buffer; |
|
466 } |
|
467 $this->state = 4; |
|
468 |
|
469 $this->sock_close($this->socket); |
|
470 $this->socket = false; |
|
471 } |
|
472 |
|
473 /** |
|
474 * Internal function to send the request but only fetch the headers. Leaves a connection open for a finish-up function. |
|
475 * @access private |
|
476 */ |
|
477 |
|
478 function _send_request_headers_only() |
|
479 { |
|
480 $this->concat_headers($headers, $cookies, $get, $post); |
|
481 |
|
482 if ( $this->state < 1 ) |
|
483 { |
|
484 $this->_sock_open($this->socket); |
|
485 } |
|
486 if ( $this->state < 2 ) |
|
487 { |
|
488 $this->_write_request($this->socket, $headers, $cookies, $get, $post); |
|
489 } |
|
490 if ( $this->state == 2 ) |
|
491 { |
|
492 $buffer = $this->_read_until_newlines($this->socket); |
|
493 $this->state = 3; |
|
494 $this->_parse_response_code($buffer); |
|
495 $this->response = $buffer; |
|
496 } |
|
497 } |
|
498 |
|
499 /** |
|
500 * Internal function to read from a socket until two consecutive newlines are hit. |
|
501 * @access private |
|
502 */ |
|
503 |
|
504 function _read_until_newlines($sock) |
|
505 { |
|
506 $prev_char = ''; |
|
507 $prev1_char = ''; |
|
508 $prev2_char = ''; |
|
509 $buf = ''; |
|
510 while ( !feof($sock) ) |
|
511 { |
|
512 $chr = fread($sock, 1); |
|
513 $buf .= $chr; |
|
514 if ( ( $chr == "\n" && $prev_char == "\n" ) || |
|
515 ( $chr == "\n" && $prev_char == "\r" && $prev1_char == "\n" && $prev2_char == "\r" ) ) |
|
516 { |
|
517 return $buf; |
|
518 } |
|
519 $prev2_char = $prev1_char; |
|
520 $prev1_char = $prev_char; |
|
521 $prev_char = $chr; |
|
522 } |
|
523 return $buf; |
|
524 } |
|
525 |
|
526 /** |
|
527 * Returns the response text. If the request hasn't been sent, it will be sent here. |
|
528 * @return string |
|
529 */ |
|
530 |
|
531 function get_response() |
|
532 { |
|
533 if ( $this->state == 4 ) |
|
534 return $this->response; |
|
535 $this->_send_request(); |
|
536 return $this->response; |
|
537 } |
|
538 |
|
539 /** |
|
540 * Writes the response body to a file. This is good for conserving memory when downloading large files. If the file already exists it will be overwritten. |
|
541 * @param string File to write to |
|
542 * @param int Chunk size in KB to read from the socket. Optional and should only be needed in circumstances when extreme memory conservation is needed. Defaults to 768. |
|
543 * @param int Maximum file size. Defaults to 0, which means no limit. |
|
544 * @return bool True on success, false on failure |
|
545 */ |
|
546 |
|
547 function write_response_to_file($file, $chunklen = 768, $max_file_size = 0) |
|
548 { |
|
549 if ( !is_writeable( dirname($file) ) || !file_exists( dirname($file) ) ) |
|
550 { |
|
551 return false; |
|
552 } |
|
553 $handle = @fopen($file, 'w'); |
|
554 if ( !$handle ) |
|
555 return false; |
|
556 $chunklen = intval($chunklen); |
|
557 if ( $chunklen < 1 ) |
|
558 return false; |
|
559 if ( $this->state == 4 ) |
|
560 { |
|
561 // we already have the response, so cheat |
|
562 $response = $this->get_response_body(); |
|
563 fwrite($handle, $response); |
|
564 } |
|
565 else |
|
566 { |
|
567 // read data from the socket, write it immediately, and unset to free memory |
|
568 $headers = $this->get_response_headers(); |
|
569 $transferred_bytes = 0; |
|
570 $bandwidth_exceeded = false; |
|
571 // if transfer-encoding is chunked, read using chunk sizes the server specifies |
|
572 $is_chunked = preg_match("/Transfer-Encoding: (chunked)\r?\n/", $this->response); |
|
573 if ( $is_chunked ) |
|
574 { |
|
575 $buffer = ''; |
|
576 while ( !feof($this->socket) ) |
|
577 { |
|
578 $part = fgets($this->socket, ( 1024 * $chunklen )); |
|
579 // Theoretically if the encoding is really chunked then this should always match. |
|
580 if ( $is_chunked && preg_match("/^([a-f0-9]+)\x0D\x0A$/", $part, $match) ) |
|
581 { |
|
582 $chunk_length = hexdec($match[1]); |
|
583 $part = ( $chunk_length > 0 ) ? fread($this->socket, $chunk_length) : ''; |
|
584 // remove the last newline from $part |
|
585 $part = preg_replace("/\r?\n\$/m", "", $part); |
|
586 } |
|
587 |
|
588 $transferred_bytes += strlen($part); |
|
589 if ( $max_file_size && $transferred_bytes > $max_file_size ) |
|
590 { |
|
591 // truncate output to $max_file_size bytes |
|
592 $partlen = $max_file_size - ( $transferred_bytes - strlen($part) ); |
|
593 $part = substr($part, 0, $partlen); |
|
594 $bandwidth_exceeded = true; |
|
595 } |
|
596 fwrite($handle, $part); |
|
597 if ( $bandwidth_exceeded ) |
|
598 { |
|
599 break; |
|
600 } |
|
601 } |
|
602 } |
|
603 else |
|
604 { |
|
605 $first_chunk = fread($this->socket, ( 1024 * $chunklen )); |
|
606 fwrite($handle, $first_chunk); |
|
607 while ( !feof($this->socket) ) |
|
608 { |
|
609 $chunk = fread($this->socket, ( 1024 * $chunklen )); |
|
610 |
|
611 $transferred_bytes += strlen($chunk); |
|
612 if ( $max_file_size && $transferred_bytes > $max_file_size ) |
|
613 { |
|
614 // truncate output to $max_file_size bytes |
|
615 $partlen = $max_file_size - ( $transferred_bytes - strlen($chunk) ); |
|
616 $chunk = substr($chunk, 0, $partlen); |
|
617 $bandwidth_exceeded = true; |
|
618 } |
|
619 |
|
620 fwrite($handle, $chunk); |
|
621 unset($chunk); |
|
622 |
|
623 if ( $bandwidth_exceeded ) |
|
624 { |
|
625 break; |
|
626 } |
|
627 } |
|
628 } |
|
629 } |
|
630 fclose($handle); |
|
631 // close socket and reset state, since we haven't cached the response |
|
632 $this->sock_close($this->socket); |
|
633 $this->state = 0; |
|
634 return ($bandwidth_exceeded) ? false : true; |
|
635 } |
|
636 |
|
637 /** |
|
638 * Returns only the response headers. |
|
639 * @return string |
|
640 */ |
|
641 |
|
642 function get_response_headers() |
|
643 { |
|
644 if ( $this->state == 3 ) |
|
645 { |
|
646 return $this->response; |
|
647 } |
|
648 else if ( $this->state == 4 ) |
|
649 { |
|
650 $pos_end = strpos($this->response, "\r\n\r\n"); |
|
651 $data = substr($this->response, 0, $pos_start); |
|
652 return $data; |
|
653 } |
|
654 else |
|
655 { |
|
656 $this->_send_request_headers_only(); |
|
657 return $this->response; |
|
658 } |
|
659 } |
|
660 |
|
661 /** |
|
662 * Returns only the response headers, as an associative array. |
|
663 * @return array |
|
664 */ |
|
665 |
|
666 function get_response_headers_array() |
|
667 { |
|
668 $data = $this->get_response_headers(); |
|
669 preg_match_all("/(^|\n)([A-z0-9_-]+?): (.+?)(\r|\n|\$)/", $data, $matches); |
|
670 $headers = array(); |
|
671 for ( $i = 0; $i < count($matches[0]); $i++ ) |
|
672 { |
|
673 $headers[ $matches[2][$i] ] = $matches[3][$i]; |
|
674 } |
|
675 return $headers; |
|
676 } |
|
677 |
|
678 /** |
|
679 * Returns only the body (not the headers) of the response. If the request hasn't been sent, it will be sent here. |
|
680 * @return string |
|
681 */ |
|
682 |
|
683 function get_response_body() |
|
684 { |
|
685 $data = $this->get_response(); |
|
686 $pos_start = strpos($data, "\r\n\r\n") + 4; |
|
687 $data = substr($data, $pos_start); |
|
688 return $data; |
|
689 } |
|
690 |
|
691 /** |
|
692 * Returns all cookies requested to be set by the server as an associative array. If the request hasn't been sent, it will be sent here. |
|
693 * @return array |
|
694 */ |
|
695 |
|
696 function get_cookies() |
|
697 { |
|
698 $data = $this->get_response(); |
|
699 $data = str_replace("\r\n", "\n", $data); |
|
700 $pos = strpos($data, "\n\n"); |
|
701 $headers = substr($data, 0, $pos); |
|
702 preg_match_all("/Set-Cookie: ([a-z0-9_]+)=([^;]+);( expires=([^;]+);)?( path=(.*?))?\n/", $headers, $cookiematch); |
|
703 if ( count($cookiematch[0]) < 1 ) |
|
704 return array(); |
|
705 $cookies = array(); |
|
706 foreach ( $cookiematch[0] as $i => $cookie ) |
|
707 { |
|
708 $cookies[$cookiematch[1][$i]] = $cookiematch[2][$i]; |
|
709 } |
|
710 return $cookies; |
|
711 } |
|
712 |
|
713 /** |
|
714 * Internal method to write data to a socket with debugging possibility. |
|
715 * @access private |
|
716 */ |
|
717 |
|
718 function _fputs($socket, $data) |
|
719 { |
|
720 if ( $this->debug ) |
|
721 echo htmlspecialchars($data); |
|
722 return fputs($socket, $data); |
|
723 } |
|
724 |
|
725 /** |
|
726 * Internal function to stringify cookies, headers, get, and post. |
|
727 * @access private |
|
728 */ |
|
729 |
|
730 function concat_headers(&$headers, &$cookies, &$get, &$post) |
|
731 { |
|
732 $headers = ''; |
|
733 $cookies = ''; |
|
734 foreach ( $this->headers as $name => $value ) |
|
735 { |
|
736 $value = str_replace('\\n', '\\\\n', $value); |
|
737 $value = str_replace("\n", '\\n', $value); |
|
738 $headers .= "$name: $value\r\n"; |
|
739 } |
|
740 unset($value); |
|
741 if ( count($this->cookies_out) > 0 ) |
|
742 { |
|
743 $i = 0; |
|
744 $cookie_header = 'Cookie: '; |
|
745 foreach ( $this->cookies_out as $name => $value ) |
|
746 { |
|
747 $i++; |
|
748 if ( $i > 1 ) |
|
749 $cookie_header .= '; '; |
|
750 $value = str_replace(';', rawurlencode(';'), $value); |
|
751 $value = str_replace('\\n', '\\\\n', $value); |
|
752 $value = str_replace("\n", '\\n', $value); |
|
753 $cookie_header .= "$name=$value"; |
|
754 } |
|
755 $cookie_header .= "\r\n"; |
|
756 $cookies = $cookie_header; |
|
757 unset($value, $cookie_header); |
|
758 } |
|
759 if ( count($this->parms_get) > 0 ) |
|
760 { |
|
761 $get = '?'; |
|
762 $i = 0; |
|
763 foreach ( $this->parms_get as $name => $value ) |
|
764 { |
|
765 $i++; |
|
766 if ( $i > 1 ) |
|
767 $get .= '&'; |
|
768 $value = urlencode($value); |
|
769 if ( !empty($value) ) |
|
770 $get .= "$name=$value"; |
|
771 else |
|
772 $get .= "$name"; |
|
773 } |
|
774 } |
|
775 if ( count($this->parms_post) > 0 ) |
|
776 { |
|
777 $post = ''; |
|
778 $i = 0; |
|
779 foreach ( $this->parms_post as $name => $value ) |
|
780 { |
|
781 $i++; |
|
782 if ( $i > 1 ) |
|
783 $post .= '&'; |
|
784 $value = urlencode($value); |
|
785 $post .= "$name=$value"; |
|
786 } |
|
787 } |
|
788 } |
|
789 |
|
790 } |
|
791 |
|
792 ?> |