includes/http.php
changeset 328 dc838fd61a06
child 349 fdaf9070566c
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/includes/http.php	Thu Dec 20 22:23:07 2007 -0500
@@ -0,0 +1,792 @@
+<?php
+
+/*
+ * Enano - an open-source CMS capable of wiki functions, Drupal-like sidebar blocks, and everything in between
+ * Version 1.0.3 (Dyrad)
+ * Copyright (C) 2006-2007 Dan Fuhry
+ * class_http.php - Pure PHP HTTP client library
+ *
+ * This program is Free Software; you can redistribute and/or modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
+ * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for details.
+ */
+
+//
+// HTTP status codes
+//
+
+// Informational
+define('HTTP_CONTINUE', 100);
+define('HTTP_SWITCHING_PROTOCOLS', 101);
+define('HTTP_PROCESSING', 102);
+
+// Success
+define('HTTP_OK', 200);
+define('HTTP_CREATED', 201);
+define('HTTP_ACCEPTED', 202);
+define('HTTP_NON_AUTHORITATIVE', 203);
+define('HTTP_NO_CONTENT', 204);
+define('HTTP_RESET_CONTENT', 205);
+define('HTTP_PARTIAL_CONTENT', 206);
+define('HTTP_MULTI_STATUS', 207);
+
+// Redirection
+define('HTTP_MULTIPLE_CHOICES', 300);
+define('HTTP_MOVED_PERMANENTLY', 301);
+define('HTTP_FOUND', 302);
+define('HTTP_SEE_OTHER', 303);
+define('HTTP_NOT_MODIFIED', 304);
+define('HTTP_USE_PROXY', 305);
+define('HTTP_SWITCH_PROXY', 306);
+define('HTTP_TEMPORARY_REDIRECT', 307);
+
+// Client Error
+define('HTTP_BAD_REQUEST', 400);
+define('HTTP_UNAUTHORIZED', 401);
+define('HTTP_PAYMENT_REQUIRED', 402);
+define('HTTP_FORBIDDEN', 403);
+define('HTTP_NOT_FOUND', 404);
+define('HTTP_METHOD_NOT_ALLOWED', 405);
+define('HTTP_NOT_ACCEPTABLE', 406);
+define('HTTP_PROXY_AUTHENTICATION_REQUIRED', 407);
+define('HTTP_REQUEST_TIMEOUT', 408);
+define('HTTP_CONFLICT', 409);
+define('HTTP_GONE', 410);
+define('HTTP_LENGTH_REQUIRED', 411);
+define('HTTP_PRECONDITION_FAILED', 412);
+define('HTTP_REQUEST_ENTITY_TOO_LARGE', 413);
+define('HTTP_REQUEST_URI_TOO_LONG', 414);
+define('HTTP_UNSUPPORTED_MEDIA_TYPE', 415);
+define('HTTP_REQUESTED_RANGE_NOT_SATISFIABLE', 416);
+define('HTTP_EXPECTATION_FAILED', 417);
+define('HTTP_UNPROCESSABLE_ENTITY', 422);
+define('HTTP_LOCKED', 423);
+define('HTTP_FAILED_DEPENDENCY', 424);
+define('HTTP_UNORDERED_COLLECTION', 425);
+define('HTTP_UPGRADE_REQUIRED', 426);
+define('HTTP_RETRY_WITH', 449);
+
+// Server error
+define('HTTP_INTERNAL_SERVER_ERROR', 500);
+define('HTTP_NOT_IMPLEMENTED', 501);
+define('HTTP_BAD_GATEWAY', 502);
+define('HTTP_SERVICE_TEMPORARILY_UNAVAILABLE', 503);
+define('HTTP_GATEWAY_TIMEOUT', 504);
+define('HTTP_HTTP_VERSION_NOT_SUPPORTED', 505);
+define('HTTP_VARIANT_ALSO_NEGOTIATES', 506);
+define('HTTP_INSUFFICIENT_STORAGE', 507);
+define('HTTP_BANDWIDTH_LIMIT_EXCEEDED', 509);
+define('HTTP_NOT_EXTENDED', 510);
+
+/**
+ * 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.
+ * @package Enano
+ * @subpackage Backend functions
+ * @copyright 2007 Dan Fuhry
+ */
+
+class Request_HTTP
+{
+  
+  /**
+   * Switch to enable or disable debugging. You want this off on production sites.
+   * @var bool
+   */
+  
+  var $debug = false;
+  
+  /**
+   * The host the request will be sent to.
+   * @var string
+   */
+  
+  var $host = '';
+  
+  /**
+   * The TCP port our connection is (will be) on.
+   * @var int
+   */
+  
+  var $port = 80;
+  
+  /**
+   * The request method. Can be GET or POST, defaults to GET.
+   * @var string
+   */
+  
+  var $method = 'GET';
+  
+  /**
+   * The URI to the remote script.
+   * @var string
+   */
+  
+  var $uri = '';
+  
+  /**
+   * The parameters to be sent on GET.
+   * @var array (associative)
+   */
+  
+  var $parms_get = array();
+  
+  /**
+   * The parameters to be sent on POST. Ignored if $this->method == GET.
+   * @var array (associative)
+   */
+  
+  var $parms_post = array();
+  
+  /**
+   * The list of cookies that will be sent.
+   * @var array (associative)
+   */
+  
+  var $cookies_out = array();
+  
+  /**
+   * Additional request headers.
+   * @var array (associative)
+   */
+  
+  var $headers = array();
+  
+  /**
+   * Cached response.
+   * @var string, or bool:false if the request hasn't been sent yet
+   */
+  
+  var $response = false;
+  
+  /**
+   * Cached response code
+   * @var int set to -1 if request hasn't been sent yet
+   */
+  
+  var $response_code = -1;
+  
+  /**
+   * Cached response code string
+   * @var string or bool:false if the request hasn't been sent yet
+   */
+  
+  var $response_string = false;
+  
+  /**
+   * Resource for the socket. False if a connection currently isn't going.
+   * @var resource
+   */
+  
+  var $socket = false;
+  
+  /**
+   * 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.
+   * @var int
+   */
+  
+  var $state = 0;
+  
+  /**
+   * Constructor.
+   * @param string Hostname to send to
+   * @param string URI (/index.php)
+   * @param string Request method - GET or POST.
+   * @param int Optional. The port to open the request on. Defaults to 80.
+   */
+  
+  function Request_HTTP($host, $uri, $method = 'GET', $port = 80)
+  {
+    if ( !preg_match('/^(([a-z0-9-]+\.)*?)([a-z0-9-]+)$/', $host) )
+      die(__CLASS__ . ': Invalid hostname');
+    $this->host = $host;
+    $this->uri = $uri;
+    if ( is_int($port) && $port >= 1 && $port <= 65535 )
+      $this->port = $port;
+    else
+      die(__CLASS__ . ': Invalid port');
+    $method = strtoupper($method);
+    if ( $method == 'GET' || $method == 'POST' )
+      $this->method = $method;
+    else
+      die(__CLASS__ . ': Invalid request method');
+      
+    $newline = "\r\n";
+    $php_ver = PHP_VERSION;
+    $this->add_header('User-Agent', "PHP/$php_ver (Server: {$_SERVER['SERVER_SOFTWARE']}; automated bot request)");
+  }
+  
+  /**
+   * Sets one or more cookies to be sent to the server.
+   * @param string or array If a string, the cookie name. If an array, associative array in the form of cookiename => cookievalue
+   * @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.
+   */
+  
+  function add_cookie($cookiename, $cookievalue = false)
+  {
+    if ( is_array($cookiename) && !$cookievalue )
+    {
+      foreach ( $cookiename as $name => $value )
+      {
+        $this->cookies_out[$name] = $value;
+      }
+    }
+    else if ( is_string($cookiename) && is_string($cookievalue) )
+    {
+      $this->cookies_out[$cookiename] = $cookievalue;
+    }
+    else
+    {
+      die(__CLASS__ . '::' . __METHOD__ . ': Invalid argument(s)');
+    }
+  }
+  
+  /**
+   * Sets one or more request header values.
+   * @param string or array If a string, the header name. If an array, associative array in the form of headername => headervalue
+   * @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.
+   */
+  
+  function add_header($headername, $headervalue = false)
+  {
+    if ( is_array($headername) && !$headervalue )
+    {
+      foreach ( $headername as $name => $value )
+      {
+        $this->headers[$name] = $value;
+      }
+    }
+    else if ( is_string($headername) && is_string($headervalue) )
+    {
+      $this->headers[$headername] = $headervalue;
+    }
+    else
+    {
+      die(__CLASS__ . '::' . __METHOD__ . ': Invalid argument(s)');
+    }
+  }
+  
+  /**
+   * Adds one or more values to be passed on GET.
+   * @param string or array If a string, the parameter name. If an array, associative array in the form of parametername => parametervalue
+   * @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.
+   */
+  
+  function add_get($getname, $getvalue = false)
+  {
+    if ( is_array($getname) && !$getvalue )
+    {
+      foreach ( $getname as $name => $value )
+      {
+        $this->parms_get[$name] = $value;
+      }
+    }
+    else if ( is_string($getname) && is_string($getvalue) )
+    {
+      $this->parms_get[$getname] = $getvalue;
+    }
+    else
+    {
+      die(__CLASS__ . '::' . __METHOD__ . ': Invalid argument(s)');
+    }
+  }
+  
+  /**
+   * Adds one or more values to be passed on POST.
+   * @param string or array If a string, the header name. If an array, associative array in the form of headername => headervalue
+   * @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.
+   */
+  
+  function add_post($postname, $postvalue = false)
+  {
+    if ( is_array($postname) && !$postvalue )
+    {
+      foreach ( $postname as $name => $value )
+      {
+        $this->parms_post[$name] = $value;
+      }
+    }
+    else if ( is_string($postname) && is_string($postvalue) )
+    {
+      $this->parms_post[$postname] = $postvalue;
+    }
+    else
+    {
+      die(__CLASS__ . '::' . __METHOD__ . ': Invalid argument(s)');
+    }
+  }
+  
+  /**
+   * Internal function to open up the socket.
+   * @access private
+   */
+  
+  function _sock_open(&$connection)
+  {
+    if ( $this->debug )
+    {
+      echo '<hr /><div style="white-space: nowrap;">';
+      echo '<p><b>' . __CLASS__ . ': Sending request</b></p><p>Request parameters:</p>';
+      echo "<p><b>Headers:</b></p><pre>$headers</pre>";
+      echo "<p><b>Cookies:</b> $cookies</p>";
+      echo "<p><b>GET URI:</b> " . htmlspecialchars($get) . "</p>";
+      echo "<p><b>POST DATA:</b> " . htmlspecialchars($post) . "</p>";
+    }
+    
+    // Open connection
+    $connection = fsockopen($this->host, $this->port);
+    if ( !$connection )
+      die(__CLASS__ . '::' . __METHOD__ . ': Could not make connection');
+    
+    if ( $this->debug )
+      echo '<p>Connection opened. Writing main request to socket. Raw socket data follows.</p><pre>';
+    
+    // 1 = socket open
+    $this->state = 1;
+  }
+  
+  /**
+   * Internal function to actually write the request into the socket.
+   * @access private
+   */
+  
+  function _write_request(&$connection, &$headers, &$cookies, &$get, &$post)
+  {
+    $newline = "\r\n";
+    
+    $this->_fputs($connection, "{$this->method} {$this->uri}{$get} HTTP/1.1{$newline}");
+    $this->_fputs($connection, "Host: {$this->host}{$newline}");
+    $this->_fputs($connection, $headers);
+    $this->_fputs($connection, $cookies);
+    
+    if ( $this->method == 'POST' )
+    {
+      // POST-specific parameters
+      $post_length = strlen($post);
+      $this->_fputs($connection, "Content-type: application/x-www-form-urlencoded{$newline}");
+      $this->_fputs($connection, "Content-length: {$post_length}{$newline}");
+    }
+    
+    $this->_fputs($connection, "Connection: close{$newline}");
+    $this->_fputs($connection, "{$newline}");
+    
+    if ( $this->method == 'POST' )
+    {
+      $this->_fputs($connection, $post);
+    }
+    
+    if ( $this->debug )
+      echo '</pre><p>Request written. Fetching response.</p>';
+    
+    // 2 = request written
+    $this->state = 2;
+  }
+  
+  /**
+   * Wrap up and close the socket. Nothing more than a call to fsockclose() except in debug mode.
+   * @access private
+   */
+  
+  function sock_close(&$connection)
+  {
+    if ( $this->debug )
+    {
+      echo '<p>Response fetched. Closing connection. Response text follows.</p><pre>';
+      echo htmlspecialchars($buffer);
+      echo '</pre></div><hr />';
+    }
+    
+    fclose($connection);
+  }
+  
+  /**
+   * Internal function to grab the response code and status string
+   * @access string
+   */
+  
+  function _parse_response_code($buffer)
+  {
+    // Retrieve response code and status
+    $pos_newline = strpos($buffer, "\n");
+    $pos_carriage_return = strpos($buffer, "\r");
+    $pos_end_first_line = ( $pos_carriage_return < $pos_newline && $pos_carriage_return > 0 ) ? $pos_carriage_return : $pos_newline;
+    
+    // First line is in format of:
+    // HTTP/1.1 ### Blah blah blah(\r?)\n
+    $response_code = substr($buffer, 9, 3);
+    $response_string = substr($buffer, 13, ( $pos_end_first_line - 13 ) );
+    $this->response_code = intval($response_code);
+    $this->response_string = $response_string;
+  }
+  
+  /**
+   * Internal function to send the request.
+   * @access private
+   */
+  
+  function _send_request()
+  {
+    $this->concat_headers($headers, $cookies, $get, $post);
+    
+    if ( $this->state < 1 )
+    {
+      $this->_sock_open($this->socket);
+    }
+    if ( $this->state < 2 )
+    {
+      $this->_write_request($this->socket, $headers, $cookies, $get, $post);
+    }
+    if ( $this->state == 2 )
+    {
+      $buffer = $this->_read_until_newlines($this->socket);
+      $this->state = 3;
+      $this->_parse_response_code($buffer);
+      $this->response = $buffer;
+    }
+    if ( $this->state == 3 )
+    {
+      // Determine transfer encoding
+      $is_chunked = preg_match("/Transfer-Encoding: (chunked)\r?\n/", $this->response);
+      
+      $buffer = '';
+      while ( !feof($this->socket) )
+      {
+        $part = fgets($this->socket, 1024);
+        if ( $is_chunked && preg_match("/^([a-f0-9]+)\x0D\x0A$/", $part, $match) )
+        {
+          $chunklen = hexdec($match[1]);
+          $part = ( $chunklen > 0 ) ? fread($this->socket, $chunklen) : '';
+          // remove the last newline from $part
+          $part = preg_replace("/\r?\n\$/m", "", $part);
+        }
+        $buffer .= $part;
+      }
+      $this->response .= $buffer;
+    }
+    $this->state = 4;
+    
+    $this->sock_close($this->socket);
+    $this->socket = false;
+  }
+  
+  /**
+   * Internal function to send the request but only fetch the headers. Leaves a connection open for a finish-up function.
+   * @access private
+   */
+  
+  function _send_request_headers_only()
+  {
+    $this->concat_headers($headers, $cookies, $get, $post);
+    
+    if ( $this->state < 1 )
+    {
+      $this->_sock_open($this->socket);
+    }
+    if ( $this->state < 2 )
+    {
+      $this->_write_request($this->socket, $headers, $cookies, $get, $post);
+    }
+    if ( $this->state == 2 )
+    {
+      $buffer = $this->_read_until_newlines($this->socket);
+      $this->state = 3;
+      $this->_parse_response_code($buffer);
+      $this->response = $buffer;
+    }
+  }
+  
+  /**
+   * Internal function to read from a socket until two consecutive newlines are hit.
+   * @access private
+   */
+  
+  function _read_until_newlines($sock)
+  {
+    $prev_char = '';
+    $prev1_char = '';
+    $prev2_char = '';
+    $buf = '';
+    while ( !feof($sock) )
+    {
+      $chr = fread($sock, 1);
+      $buf .= $chr;
+      if ( ( $chr == "\n" && $prev_char == "\n" ) ||
+           ( $chr == "\n" && $prev_char == "\r" && $prev1_char == "\n" && $prev2_char == "\r" ) )
+      {
+        return $buf;
+      }
+      $prev2_char = $prev1_char;
+      $prev1_char = $prev_char;
+      $prev_char = $chr;
+    }
+    return $buf;
+  }
+  
+  /**
+   * Returns the response text. If the request hasn't been sent, it will be sent here.
+   * @return string
+   */
+  
+  function get_response()
+  {
+    if ( $this->state == 4 )
+      return $this->response;
+    $this->_send_request();
+    return $this->response;
+  }
+  
+  /**
+   * 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.
+   * @param string File to write to
+   * @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.
+   * @param int Maximum file size. Defaults to 0, which means no limit.
+   * @return bool True on success, false on failure
+   */
+  
+  function write_response_to_file($file, $chunklen = 768, $max_file_size = 0)
+  {
+    if ( !is_writeable( dirname($file) ) || !file_exists( dirname($file) ) )
+    {
+      return false;
+    }
+    $handle = @fopen($file, 'w');
+    if ( !$handle )
+      return false;
+    $chunklen = intval($chunklen);
+    if ( $chunklen < 1 )
+      return false;
+    if ( $this->state == 4 )
+    {
+      // we already have the response, so cheat
+      $response = $this->get_response_body();
+      fwrite($handle, $response);
+    }
+    else
+    {
+      // read data from the socket, write it immediately, and unset to free memory
+      $headers = $this->get_response_headers();
+      $transferred_bytes = 0;
+      $bandwidth_exceeded = false;
+      // if transfer-encoding is chunked, read using chunk sizes the server specifies
+      $is_chunked = preg_match("/Transfer-Encoding: (chunked)\r?\n/", $this->response);
+      if ( $is_chunked )
+      {
+        $buffer = '';
+        while ( !feof($this->socket) )
+        {
+          $part = fgets($this->socket, ( 1024 * $chunklen ));
+          // Theoretically if the encoding is really chunked then this should always match.
+          if ( $is_chunked && preg_match("/^([a-f0-9]+)\x0D\x0A$/", $part, $match) )
+          {
+            $chunk_length = hexdec($match[1]);
+            $part = ( $chunk_length > 0 ) ? fread($this->socket, $chunk_length) : '';
+            // remove the last newline from $part
+            $part = preg_replace("/\r?\n\$/m", "", $part);
+          }
+          
+          $transferred_bytes += strlen($part);
+          if ( $max_file_size && $transferred_bytes > $max_file_size )
+          {
+            // truncate output to $max_file_size bytes
+            $partlen = $max_file_size - ( $transferred_bytes - strlen($part) );
+            $part = substr($part, 0, $partlen);
+            $bandwidth_exceeded = true;
+          }
+          fwrite($handle, $part);
+          if ( $bandwidth_exceeded )
+          {
+            break;
+          }
+        }
+      }
+      else
+      {
+        $first_chunk = fread($this->socket, ( 1024 * $chunklen ));
+        fwrite($handle, $first_chunk);
+        while ( !feof($this->socket) )
+        {
+          $chunk = fread($this->socket, ( 1024 * $chunklen ));
+          
+          $transferred_bytes += strlen($chunk);
+          if ( $max_file_size && $transferred_bytes > $max_file_size )
+          {
+            // truncate output to $max_file_size bytes
+            $partlen = $max_file_size - ( $transferred_bytes - strlen($chunk) );
+            $chunk = substr($chunk, 0, $partlen);
+            $bandwidth_exceeded = true;
+          }
+          
+          fwrite($handle, $chunk);
+          unset($chunk);
+          
+          if ( $bandwidth_exceeded )
+          {
+            break;
+          }
+        }
+      }
+    }
+    fclose($handle);
+    // close socket and reset state, since we haven't cached the response
+    $this->sock_close($this->socket);
+    $this->state = 0;
+    return ($bandwidth_exceeded) ? false : true;
+  }
+  
+  /**
+   * Returns only the response headers.
+   * @return string
+   */
+  
+  function get_response_headers()
+  {
+    if ( $this->state == 3 )
+    {
+      return $this->response;
+    }
+    else if ( $this->state == 4 )
+    {
+      $pos_end = strpos($this->response, "\r\n\r\n");
+      $data = substr($this->response, 0, $pos_start);
+      return $data;
+    }
+    else
+    {
+      $this->_send_request_headers_only();
+      return $this->response;
+    }
+  }
+  
+  /**
+   * Returns only the response headers, as an associative array.
+   * @return array
+   */
+  
+  function get_response_headers_array()
+  {
+    $data = $this->get_response_headers();
+    preg_match_all("/(^|\n)([A-z0-9_-]+?): (.+?)(\r|\n|\$)/", $data, $matches);
+    $headers = array();
+    for ( $i = 0; $i < count($matches[0]); $i++ )
+    {
+      $headers[ $matches[2][$i] ] = $matches[3][$i];
+    }
+    return $headers;
+  }
+  
+  /**
+   * Returns only the body (not the headers) of the response. If the request hasn't been sent, it will be sent here.
+   * @return string
+   */
+  
+  function get_response_body()
+  {
+    $data = $this->get_response();
+    $pos_start = strpos($data, "\r\n\r\n") + 4;
+    $data = substr($data, $pos_start);
+    return $data;
+  }
+  
+  /**
+   * 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.
+   * @return array
+   */
+  
+  function get_cookies()
+  {
+    $data = $this->get_response();
+    $data = str_replace("\r\n", "\n", $data);
+    $pos = strpos($data, "\n\n");
+    $headers = substr($data, 0, $pos);
+    preg_match_all("/Set-Cookie: ([a-z0-9_]+)=([^;]+);( expires=([^;]+);)?( path=(.*?))?\n/", $headers, $cookiematch);
+    if ( count($cookiematch[0]) < 1 )
+      return array();
+    $cookies = array();
+    foreach ( $cookiematch[0] as $i => $cookie )
+    {
+      $cookies[$cookiematch[1][$i]] = $cookiematch[2][$i];
+    }
+    return $cookies;
+  }
+  
+  /**
+   * Internal method to write data to a socket with debugging possibility.
+   * @access private
+   */
+  
+  function _fputs($socket, $data)
+  {
+    if ( $this->debug )
+      echo htmlspecialchars($data);
+    return fputs($socket, $data);
+  }
+  
+  /**
+   * Internal function to stringify cookies, headers, get, and post.
+   * @access private
+   */
+  
+  function concat_headers(&$headers, &$cookies, &$get, &$post)
+  {
+    $headers = '';
+    $cookies = '';
+    foreach ( $this->headers as $name => $value )
+    {
+      $value = str_replace('\\n', '\\\\n', $value);
+      $value = str_replace("\n", '\\n', $value);
+      $headers .= "$name: $value\r\n";
+    }
+    unset($value);
+    if ( count($this->cookies_out) > 0 )
+    {
+      $i = 0;
+      $cookie_header = 'Cookie: ';
+      foreach ( $this->cookies_out as $name => $value )
+      {
+        $i++;
+        if ( $i > 1 )
+          $cookie_header .= '; ';
+        $value = str_replace(';', rawurlencode(';'), $value);
+        $value = str_replace('\\n', '\\\\n', $value);
+        $value = str_replace("\n", '\\n', $value);
+        $cookie_header .= "$name=$value";
+      }
+      $cookie_header .= "\r\n";
+      $cookies = $cookie_header;
+      unset($value, $cookie_header);
+    }
+    if ( count($this->parms_get) > 0 )
+    {
+      $get = '?';
+      $i = 0;
+      foreach ( $this->parms_get as $name => $value )
+      {
+        $i++;
+        if ( $i > 1 )
+          $get .= '&';
+        $value = urlencode($value);
+        if ( !empty($value) )
+          $get .= "$name=$value";
+        else
+          $get .= "$name";
+      }
+    }
+    if ( count($this->parms_post) > 0 )
+    {
+      $post = '';
+      $i = 0;
+      foreach ( $this->parms_post as $name => $value )
+      {
+        $i++;
+        if ( $i > 1 )
+          $post .= '&';
+        $value = urlencode($value);
+        $post .= "$name=$value";
+      }
+    }
+  }
+  
+}
+
+?>