diff -r c2f4c900c507 -r dc838fd61a06 includes/http.php
--- /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 @@
+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 '
';
+ echo '
' . __CLASS__ . ': Sending request
Request parameters:
';
+ echo "
Headers:
$headers
";
+ echo "
Cookies: $cookies
";
+ echo "
GET URI: " . htmlspecialchars($get) . "
";
+ echo "
POST DATA: " . htmlspecialchars($post) . "
";
+ }
+
+ // Open connection
+ $connection = fsockopen($this->host, $this->port);
+ if ( !$connection )
+ die(__CLASS__ . '::' . __METHOD__ . ': Could not make connection');
+
+ if ( $this->debug )
+ echo '
Connection opened. Writing main request to socket. Raw socket data follows.
';
+
+ // 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 '
Request written. Fetching response.
';
+
+ // 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 '
Response fetched. Closing connection. Response text follows.
';
+ echo htmlspecialchars($buffer);
+ echo '
';
+ }
+
+ 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";
+ }
+ }
+ }
+
+}
+
+?>