includes/http.php
changeset 328 dc838fd61a06
child 349 fdaf9070566c
equal deleted inserted replaced
327:c2f4c900c507 328:dc838fd61a06
       
     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 ?>