PHP Classes

File: GetUri.php

Recommend this page to a friend!
  Classes of Grigori Kochanov   PHP server-side web browser   GetUri.php   Download  
File: GetUri.php
Role: Class source
Content type: text/plain
Description: class that works with HTTP
Class: PHP server-side web browser
Browsing the Web via PHP
Author: By
Last change:
Date: 19 years ago
Size: 33,436 bytes


Class file image Download
<?php /************************************************************** * * Package of classes GetUri can be used as an interface to communicate with * remote web-servers. * There are folowing methods defined: * GetUri(string) - class constructor, should get string with URI for initialization; * * string set_connection_type() - set connection type to keep-alive or close, * if no parameter specified, no changes is made * function returns previous setting or false on wrong parameter * * bool connect(string method) - establishes connection to the host, but if * there is already an open connection, function checks * if connection method (fopen or fsockopen) matches, if data was not * transmitted, and may leave current connection * so you can call connect() many times, but it will perform connection * only when it needs to (keep-alive is supported but not implemented) * * bool reconnect(string method) - establishes connection to the host, but if * there is already an open connection, function closes it and opens a new * one * * bool disconnect(void) - closes connection to the remote host, false on error * * string get_link_opener(void) - returns the name of the function used to open * connection with the remote server * * void uri_lookup(void) - connects to the remote host, sends HTTP "HEAD" * request, receives Response headers, follows redirects and gets the final * URI's address and headers * * bool send_request([string type [,string connection]]) - sends HTTP request * type of the request can be "GET" (default), "POST", "HEAD"; * connection can be "close" (default) or "keep-alive" * * bool get_headers(void) - read and parse Response headers * connect and send request prior to calling this method * * bool get_content(void) - read HTTP response * one should call connect() and send_request() before calling this method * * bool link_write(string data) - sends the data to the remote server * * string|false link_read([int limit]) - reads data sent by remote server * reading stops if limit (bytes) is reached * if reading method is "BYTES", default limit parameter is 512 bytes * if reading method is "STRINGS", reading stops with the end of line * * string link_read_method(string method) - define what function will be used to * read data from remote server * method can be "STRINGS" (default) or "BYTES" * note: in PHP versions prior 4.3 function fgets() ("STRINGS" method) * is not binary safe, so one should use "BYTES" to read binary data * * bool verbose([bool status]) - get and/or set the level of dubugging information * when true, all transmitted data is recorded in history field, * when false - only sent and received HTTP headers are. * * bool Request->register_get_var(string|array var, [string value]) - register * variable to be sent by GET method to the remote host * can take array of strings or "name", "value" string pair as parameters * * bool Request->register_post_var(string|array var, [string value]) - register * variable to be sent by POST method to the remote host * can take array of strings or "name", "value" string pair as parameters * * bool Request->add_custom_header(string header, [bool replace]) - register * custom header to be sent in HTTP request, * if replace is true, custom header will overwrite standart class header * with the same name, otherwise it will be appended * * string Request->get_request_string(string method[, string option]) - returns * HTTP request with custom headers and post data included * * array|false Response->find_header_by_name(string header_name) - use this * method if you want to get value of a particular header(s). Reply is a * numeric array of header values (without header name and colon ":"). * * bool Response->eof_reached(void) - returns status of connection with remote host * **** Protected methods, don't call directly **** * bool _host_socket_connect(void) - just opens a socket connection to the server * with address in $this->URI->host and writes the file pointer to * $this->link * * bool _host_file_connect(void) - opens a connection to the URI from * $this->URI->full using fopen() function */ include_once ("URI.class.php"); if (!defined("EOL"))define ("EOL", "\r\n"); // ******Settings***** //limit the number of redirects the script will go through if (!defined("REDIRECTS_LIMIT"))define ("REDIRECTS_LIMIT", 10); //limit the number of HTTP headers the script will process before signalling //an error if (!defined("HEADERS_LIMIT"))define ("HEADERS_LIMIT", 30); //class definition class GetUri{ var $link = NULL; // file pointer to the remote host opened // by fsockopen() or fopen() var $link_method = "none"; // name of function that opened connection, // can be "fsockopen", "fopen" or other, // "none" if no connection var $connection = "close"; // connection type (for socket connection) var $connections_count = 0; // counter of connections var $proxy = false; // address of the proxy to use or false // if use direct connection var $proxy_port = 3128; // port of the proxy var $uri_stream_read_method = "STRINGS"; // method to read data from URLI // "STRINGS" means using fgets(), // "BYTES" - fread() var $Request; // object for generating request to remote server var $Response; // object that contains fields with server Response var $errors; // description of errors var $history = array(); // history of transmitted data var $verbose = false; // log in history only HTTP headers, // not all transmitted data //class constructor function GetUri ($target_uri, $connection = "close"){ //init service objects $this->URI =& new URI($target_uri); $this->Request =& new Request($this); $this->set_connection_type($connection); // $this->Response =& new Response($this); $this->history['request_headers'] = array(); $this->history['response_headers'] = array(); $this->history['sent_data'] = ""; $this->history['received_data'] = ""; } // connect to the remote host, send HTTP-request and receive Response headers, // detect redirects, follow them (reconnects to the new URI) // and find the final URI with content or error function uri_lookup (){ $done = false; //starting loop for processing redirects $redirects_count = 0; while (!$done){ //count number of redirects if ($redirects_count > REDIRECTS_LIMIT){ $this->errors['common'] = "Server redirects limit reached"; break; } //re-process the URI if we are folowing a redirect if ($redirects_count){ $this->URI->process($uri); } //connect if not connected if (!$this->connect("fsockopen")){ return false; } //send request if (!$this->Request->sent_status){ $this->send_request("HEAD"); } //get headers of the URI from the web-server if (!$this->get_headers()){ return false; } //detect "Location" header if (list ($location) = $this->Response->find_header_by_name ("location")){ $uri = $location;// redirect detected ++$redirects_count; continue; } switch ($this->Response->status_class){ default: $this->errors['Response'] = $this->Response->status_code.": ".$this->Response->reason_phrase; case 2: if ($this->Response->status_code == 204){ $this->errors['Response'] = "204: No content"; }elseif (list ($content_type) = $this->Response->find_header_by_name ("Content-type")){ $this->Response->content_type = $content_type; } $done = true; break; case 3: if ($this->Response->status_code == 300){ $this->errors['Response'] = $this->Response->status_code.": ".$this->Response->reason_phrase; }else{ $this->errors['headers'] = $this->Response->status_code." (".$this->Response->reason_phrase.") returned, but no location specified"; } $done = true; break; } } if (!isset($this->errors)){ return true; }else { return false; } } // set the type of connection - keep-alive or close (default) // when no parameter specified current value is returned // any parameter value other then "keep-alive" will be treated as "close" function set_connection_type($connection = false){ $old_connection = $this->connection; if ($connection){ $connection = strtolower($connection); $this->connection = ($connection == "keep-alive")?"keep-alive":"close"; $this->Request->connection = $this->connection; } return $old_connection; } // check current connection (URI "unchanged" status and connection method) // connect to the host according to specified connection method // or keep current connection function connect($link_method){ $new_method = strtolower($link_method); switch ($new_method){ case "fopen": $method = "_host_file_connect"; break; case "fsockopen": $method = "_host_socket_connect"; break; default: $this->errors['connect'] = "Unrecognised connection method"; return false; } //connect if we are not connected yet if (!$this->is_connected()){ $is_connected = $this->$method(); }//if we are connected using different method or request have changed - reconnect elseif ($this->get_connection_method() != $new_method || $this->URI->changed){ $is_connected = $this->reconnect($new_method); } elseif($new_method == "fsockopen"){ //was there any communication using current connection? if ($this->Request->sent_status){ $is_connected = $this->reconnect("fsockopen"); }else{ //leave it connected $is_connected = true; } }else{//fopen connection, check if data was read if ($this->Response->body_reception_started){ $is_connected = $this->reconnect("fopen"); }else{ $is_connected = true; } } return $is_connected; } // break the connection if it is already established // and connect to the host accoreding to connection method function reconnect($link_method){ $link_method = strtolower ($link_method); switch ($link_method){ case "fopen": $method = "_host_file_connect"; break; case "fsockopen": $method = "_host_socket_connect"; break; default: $this->errors['reconnect'] = "Unrecognised connection method"; return false; } if ($this->is_connected()){ $this->disconnect(); } return $this->$method(); } //function opens socket connection to the remote host function _host_socket_connect (){ if ($this->link){ $this->errors['socket_connect'] = "Error opening connection: connection already exists"; return false; } //for proxy implementation (in the future) if($this->proxy){ $port = $this->proxy_port; } //connecting $host = $this->URI->host; $port = $this->URI->port; $urlp = fsockopen ($host, $port, $errnom, $errstring); //check errors if (!$urlp){ $this->errors['connect'] = "Error connecting to ".$this->URI->get_full_uri() ."\tError is: $errnom $errstring"; return false; } $this->link = $urlp; $this->link_method = "fsockopen"; ++$this->connections_count; //init objects and set flags $this->Request =& new Request($this); $this->Response =& new Response($this); $this->URI->changed = false; $this->Request->sent = false; $this->Response->headers_received = false; $this->Response->body_received = false; return true; } //connect to the URI using URL fopen wrapper function _host_file_connect (){ if ($this->link){ $this->errors['file_connect'] = "Error opening connection: connection already exists"; return false; } $uri = $this->URI->get_full_uri(); $urlp = @fopen ($uri, "r"); //open the connection if (!$urlp) { $this->errors['connect'] = "Could not connect to $url"; return false; } $this->link = $urlp; $this->link_method = "fopen"; ++$this->connections_count; $this->Request =& new Request($this); $this->Response =& new Response($this); $this->URI->changed = false; $this->Request->sent = true; $this->Response->headers_received = true; $this->Response->body_received = false; return true; } //close connection to the host function disconnect(){ if (is_resource($this->link)){ $to_return = @fclose($this->link); }else{ $to_return = false; $this->errors['connect'] = "Trying to close non-existing connection."; } $this->link = NULL; $this->link_method = "none"; return $to_return; } //returns the name of the function that opened file pointer function get_connection_method(){ return $this->link_method; } // function is_connected(){ if($this->link_method != "none"){ return true; }else{ return false; } } //write to the connection stream function link_write($data){ if (!$this->link){//are we already connected to the remote host? $this->errors['stream'] = "Error writing to server: no connection established."; return false; // otherwise return from the function with error }elseif($this->get_connection_method() == "fopen"){ //if the link is opened by fopen(), it is read-only and no writing is possible $this->errors['stream'] = "Read-only connection to remote host, can't send data."; return false; } //get length of data $length = strlen($data); //send the data $written = fwrite ($this->link, $data, $length); if ($written !== false){ if ($this->verbose){ $this->history['sent_data'] .= $data; } return true; }else{ $this->errors['stream'] = "Could not send data to remote server."; return false; } } // This function sets the method of reading data from server. // it returns the old state of $this->uri_stream_read_method // Parameter to this function can be "STRINGS", so that fgets() will be used // "BYTES", so fread() will be used // if no parameter given, the current state is returned without change // This is done because fgets() is not binary safe in PHP earlier than 4.3 // Function parameter is case-insensitive. function link_read_method($method = "CURRENT"){ $old_method = $this->uri_stream_read_method; $method = strtoupper($method); switch ($method) { case "STRINGS": $this->uri_stream_read_method = "STRINGS"; break; case "BYTES": $this->uri_stream_read_method = "BYTES"; break; case "CURRENT": break; default: $this->errors['stream'] = "Error in link_read_method(): Wrong parameter passed ($method)"; return false; } return $old_method; } //this function is the interface for processing Response as a stream function link_read($length = false){ if (!$this->link){//are we already connected to the remote host? $this->errors['stream'] = "Error reading from server: no connection."; return false; // otherwise return from the function with error } //read data with appropriate function according to reading method switch ($this->uri_stream_read_method) { //read data line by line, but not binary safe in PHP before 4.3 case "STRINGS": if ($length){ $data = fgets($this->link, $length); }else{ $data = fgets($this->link); } break; //read data by portions of fixed size (binary safe) case "BYTES": if (!$length){ $length = 512; } $data = fread($this->link, $length); break; } //log data in history log if ($this->verbose){ $this->history['received_data'] .= $data; } //detect end of file if (feof($this->link)){ $this->Response->uri_stream_eof_reached = true; }elseif ($data === false){ $this->errors['stream'] = "Error reading server Response"; $this->Response->uri_stream_eof_reached = true; } //set Response flags if (!$this->Response->headers_received && !$this->Response->headers_reception_started){ $this->Response->headers_reception_started = true; }elseif(!$this->Response->body_reception_started){ $this->Response->body_reception_started = true; } return $data; } //send the request to the remote host function send_request($request_method = "GET", $custom_request = false){ $this->request_sent = false; //are we connected to the remote host and able to send data? switch ($this->get_connection_method()){ case "none": //no connection $this->errors['request'] = "Error sending HTTP request - no connection to remote host."; return false; case "fsockopen": //OK, go on break; case "fopen"://error, connection is read-only $this->errors['request'] = "Error sending HTTP request - connection is read-only."; return false; } $request_method = strtoupper($request_method); //if one need custom request - send it if ($request_method == "CUSTOM"){ $request = $custom_request; }else{ //get request string $request = $this->Request->get_request_string($request_method, $this->connection); } //sending request if ($this->link_write($request)){ $this->Request->sent_status = true; $this->history['request_headers'][] = $request; return true; }else{ return false; } } /*this function gets the headers of the current URI * Status-Line is recorded to $this->Response->headers['status-line'] * Status Code (3 digits) - to $this->Response->status_code * class of the Status Code (1st digit if status code) - to $this->Response->status_class * reason_phrase in $this->Response->reason_phrase * rest of the headers are stored in 2-dimension array * $this->Response->headers[int][name|value] * where $this->Response->headers[1][name] contains the name of the 1st * (after the status line) header * $this->Response->headers[1][value] contains the value of the 1st header */ function get_headers(){ //check connection if ($this->get_connection_method() == "fopen"){ $this->errors['headers'] = "Error retrieving headers: connection is wrapped. Use \"fsockopen\" connection method to get HTTP headers."; return false; } //check Request and Response status if($this->Response->headers_received){ $this->errors['headers'] = "Error retrieving headers: already received"; return false; }elseif(!$this->Request->sent_status){ $this->errors['headers'] = "Error retrieving headers: request to remote server was not sent"; return false; } //read headers $header_start = true; $header_end = false; $header_eol = false; $i = $j =0; // just counters //set reading method to "STRINGS" - use fgets() $this->link_read_method("STRINGS"); //init variable to store headers $headers = array ('raw'=> ""); //process Response header in loop by lines while (!$header_end && !$this->Response->eof_reached()){ //read line from a Response $data = $this->link_read(1024); //record raw headers data $headers['raw'] .= $data; // limit the number of Response lines the script will process // in the case of buggy server Response if (++$i > HEADERS_LIMIT){ $this->errors['headers'] = "Too many headers from the server"; return false; } //parse Response headers - find status line and parse all other headers if ($data == EOL){ //empty line defines the end of HTTP header //process buggy server replies with EOLs in the start of the Response if ($header_start){ continue;// skip EOLs in the beginning of the header }else{ //not the beginning of the header, it's the end of the header $header_end = true; } }else{ //the Response is not an empty line, let's process the header if ($header_start){ //this must be a Response status-line //check if the Response was in HTTP header format and get Response status code $headers['status-line'] = $data; $regexp = "~^HTTP/\d.\d (\d{3}) (.*\S)~"; if (!preg_match ($regexp, $data, $found)){ $this->errors["headers"] = "Bad remote host Response - not an HTTP header"; return false; } $this->Response->status_code = $found[1]; $this->Response->status_class = substr($this->Response->status_code, 0, 1); $this->Response->reason_phrase = $found[2]; if ($this->Response->status_class != 1){ $header_start = false; //next lines will be a response headers } }else{ //split the header into array $regexp = "/^(\S*):\s*(.*\S)/"; if (preg_match ($regexp, $data, $found)){ $headers[++$j]['name'] = $found[1]; $headers[$j]['value'] = $found[2]; }else{ //process multiple line headers $regexp = "/^( |\t)*(.*\S)/"; if (preg_match($regexp, $data, $found)) { $headers[$j]['value'] .= " ". $found[2]; }else{ // ignore bad header continue; } } } } } $this->Response->headers = $headers; $this->history['response_headers'][] = $headers['raw']; //set flag of received headers and return if(isset($this->Response->status_code)){ $this->Response->headers_received = true; return true; }else{ return false; } } //get content of the URI function get_content(){ //check connection if(!$this->is_connected()){ $this->errors['get_uri_content'] = "Error getting reply body: no connection"; return false; } //check if response body is not received yet if ($this->Response->body_received){ $this->errors['get_uri_content'] = "Error getting reply body: already received"; return false; } //process headers if (!$this->Response->headers_received){ if (!$this->get_headers()){ return false; } } //read response body using binary-safe method $this->link_read_method("bytes"); while (!$this->Response->eof_reached()){ $data = $this->link_read(1024); if ($data === false){ return false; }else{ $this->Response->body .= $data; } } return true; } // set log datalisation level or get current status function verbose($status = "get current"){ $old_status = $this->verbose; if ($status !== "get current"){ $this->verbose = (bool) $status; } return $old_status; } //returns number of errors, occured while performing operations with the class function count_errors(){ return count ($this->errors); } //end of the class } //**************************************************************// // class represents the request sent to the remote server class Request{ var $URI; var $method = "GET"; // request type (GET, POST, HEAD, OPTIONS) var $Request_Line = ""; var $headers; // array or headers to send var $custom_headers = array(); // array or custom headers var $body = ""; // request message body var $sent_status = false; // status of the request var $post_data = ""; // data to send with POST request var $raw_sent_data = ""; // all data sent to the remote server // useng this "package" of classes function Request(&$GetUri){ $this->GetUri =& $GetUri; $this->URI =& $GetUri->URI; return true; } //use this function to prepare data to send with POST request function register_get_var($var, $var_value = ""){ //check parameters if (!$var){ $this->GetUri->errors['register_GET_var'] = "Error registering query for GET request: no variable name specified."; return false; } if (is_array($var)){ foreach ($var as $key=>$value) { if (!is_string($key) || !is_string($value)){ $this->GetUri->errors['register_GET_var'] = "Error registering query for GET request: invalid parameter type."; }else{ if ($this->URI->query){ $this->URI->query .= "&"; } $this->post_data .= rawurlencode($key). "=" .rawurlencode($value); } } }elseif (is_string($var) && is_string($var_value)){ if ($this->URI->query){ $this->URI->query .= "&"; } $this->URI->query .= urlencode($var)."=".urlencode($var_value); }else{ $this->GetUri->errors['register_GET_var'] = "Error registering query for GET request: invalid parameter type."; return false;//wrong parameter type } return true; } //use this function to prepare data to send with POST request function register_post_var($var, $var_value = ""){ //check parameters if (!$var){ $this->GetUri->errors['register_POST_var'] = "Error registering data for POST request - no variable name specified."; return false; } if (is_array($var)){ foreach ($var as $k=>$v) { if($this->post_data) { $this->post_data .= "&"; } $this->post_data .= rawurlencode($k). "=" .rawurlencode($v); } }elseif (is_string ($var)){ if ($this->post_data){ $this->post_data .= "&"; } $this->post_data .= rawurlencode($var)."=".rawurlencode($var_value); }else{ $this->GetUri->errors['register_POST_var'] = "Error registering query for POST request: invalid parameter type."; return false; } return true; } //add custom request header function add_custom_header($header, $replace = false){ static $index; if (!isset ($index)){ $index = 0; } $this->custom_headers[$index]['value'] = $header; $this->custom_headers[$index]['replace'] = $replace; } //returns the request string to send to server function get_request_string($request_method, $optional_parameter = null){ //method is case-insensitive $request_method = strtoupper($request_method); //creating HTTP request $host = $this->URI->host; $path = $this->URI->path.$this->URI->query; switch ($request_method) { case "GET": case "HEAD": case "TRACE": $this->Request_Line = "$request_method $path HTTP/1.1"; $this->headers["Host"] = "$host"; $this->headers["User-Agent"] = "PHP script for HTTP protocol"; $this->headers["Connection"] = $this->GetUri->connection; break; case "OPTIONS": if ($optional_parameter == "*"){ $this->Request_Line = "$request_method * HTTP/1.1"; }else{ $this->Request_Line = "$request_method $path HTTP/1.1"; } $this->headers["Host"] = "$host"; $this->headers["User-Agent"] = "PHP script for HTTP protocol"; $this->headers["Connection"] = $this->GetUri->connection; break; case "POST": $this->post_data = trim($this->post_data); $this->Request_Line = "$request_method $path HTTP/1.1"; $this->headers["Host"] = "$host"; $this->headers["User-Agent"] = "PHP script for HTTP protocol"; $this->headers["Connection"] = $this->GetUri->connection; $this->headers["Accept"] = "*/*"; $this->headers["Content-type"] = "application/x-www-form-urlencoded"; $this->headers["Content-length"] = strlen($this->post_data); $this->body = $this->post_data."\r\n\r\n"; break; default: $this->GetUri->errors['erquest'] = "Error sending request - wrong request method."; } //include custom headers $custom_headers_append = ""; foreach ($this->custom_headers as $custom_header){ //replace headers if custom headers are registered for replace if ($custom_header['replace']){ $colon_pos = strpos($custom_header['value'], ":"); if (!$colon_pos) { $this->URI->errors['headers'] = "Wrong custom header, skipping"; continue;// skip incorrect header } //split custom headers name and value $custom_header_name = substr($custom_header['value'], 0, $colon_pos); $custom_header_value = substr($custom_header['value'], $colon_pos+1); //search for matched headers $match = false; foreach ($this->headers as $header_name->$header_value){ if (!strcasecmp($header_name, $custom_header_name)){ //found $this->headers[$header_name] = trim($custom_header_value); $match = true; break; } } if(!$match){ $this->headers[$custom_header_name] = trim($custom_header_value); } }else{ //don't replace, just add this header $custom_headers_append .= trim($custom_header['value'])."\r\n"; } } //compile request string $http_request = $this->Request_Line."\r\n"; foreach ($this->headers as $name=>$value){ $http_request .= "$name: $value\r\n"; } //add custom headers $http_request .= $custom_headers_append; //finish headers $http_request .= "\r\n"; //add request body $http_request .= $this->body; return $http_request; } //end of the class } //**************************************************************// // class represents the reply resieved from the remote server class Response{ var $headers = array(); var $body = ""; var $headers_reception_started = false; // Response headers receiving flag var $body_reception_started = false; // Response body recieving flag [true|false] var $headers_received = false; // Response headers received flag [true|false] var $body_received = false; // Response body recieving flag [true|false] var $status_code; var $status_class; var $reason_phrase; var $content_type; var $uri_stream_eof_reached = false; function Response(&$GetUri){ $this->GetUri =& $GetUri; return true; } //get header values by header name function find_header_by_name($header_name){ if (!$this->headers_received){ $this->GetUri->errors[] = "Error looking up header: headers have not been received yet"; return false; } $Response = false; foreach ($this->headers as $header){ if (!strcasecmp($header['name'], $header_name)) $Response [] = $header['value']; } return $Response; } //get eof status of stream function eof_reached(){ if ($this->uri_stream_eof_reached || !is_resource($this->GetUri->link)) return true; else return false; } //end of the class } ?>