git-svn-id: https://svn.fournier38.fr/svn/ProgSVN/trunk@5317 bf3deb0d-5f1a-0410-827f-c0cc1f45334c
1070 lines
31 KiB
PHP
1070 lines
31 KiB
PHP
<?php
|
|
/** DomFramework
|
|
* @package domframework
|
|
* @author Dominique Fournier <dominique@fournier38.fr>
|
|
*/
|
|
|
|
/** This programe allow to get a HTTP page from a site, and examine the content.
|
|
* It will store the Cookies, allow to do the redirects, follow links and
|
|
* get form / input and send the values.
|
|
*/
|
|
class Httpclient
|
|
{
|
|
//////////////////////////
|
|
//// PROPERTIES ////
|
|
//////////////////////////
|
|
// {{{
|
|
/** The debug depth. 0: Nothing is displayed, 1: Only URL are displayed,
|
|
* 2: headers only, 3: all the content
|
|
*/
|
|
private $debug = null;
|
|
|
|
/** The URL to use
|
|
*/
|
|
private $url = "";
|
|
|
|
/** The cookies
|
|
*/
|
|
private $cookies = array ();
|
|
|
|
/** Store the session cookies when analyzing the answer of the server
|
|
*/
|
|
private $cookiesSession = true;
|
|
|
|
/** The headersReceived
|
|
*/
|
|
private $headersReceived = array ();
|
|
|
|
/** The headersSent to the server
|
|
*/
|
|
private $headersSent = array ();
|
|
|
|
/** The Method used to communicate : GET, POST, HEAD, PUT, DELETE
|
|
*/
|
|
private $method = "GET";
|
|
|
|
/** The TCP port used for connection
|
|
*/
|
|
private $port = null;
|
|
|
|
/** The TCPClient object
|
|
*/
|
|
private $tcpclient = null;
|
|
|
|
/** The SSL Options parameters (if set)
|
|
*/
|
|
private $ssloptions = array ();
|
|
|
|
/** The maximum maxsize allowed
|
|
*/
|
|
private $maxsize;
|
|
|
|
/** The returned HTTP code from the server
|
|
*/
|
|
private $httpCode = null;
|
|
|
|
/** The body size staying to read
|
|
*/
|
|
private $bodySize = null;
|
|
|
|
/** The content method to get the content : chunked or Content-Length
|
|
*/
|
|
private $contentMethod = false;
|
|
|
|
/** Follow X redirects before abort
|
|
*/
|
|
private $redirectMaxCount = 10;
|
|
|
|
/** The actual number of redirect
|
|
*/
|
|
private $redirectCount = 0;
|
|
|
|
/** The form data to send
|
|
* Will be of type array ("field" => "value")
|
|
* If value is like "@/tmp/file", use the /tmp/file as content
|
|
*/
|
|
private $formData = array ();
|
|
|
|
/** The raw data to send
|
|
*/
|
|
private $rawData = "";
|
|
|
|
/** The timeout in second before expiring the connection
|
|
*/
|
|
private $timeout = 30;
|
|
|
|
/** Store the user agent. If it is empty, it will not be sent to the server
|
|
*/
|
|
private $useragent = "HTTPClient";
|
|
|
|
/** The last valid page known. Used as referer
|
|
*/
|
|
private $referer = "";
|
|
|
|
/** The accept type of data wanted by the client
|
|
*/
|
|
private $accept = "text/html,application/xhtml+xml,application/xml;q=0.9,".
|
|
"*/*;q=0.8";
|
|
|
|
/** The accept language wanted by the client
|
|
*/
|
|
private $acceptLanguage = "en-us,en;q=0.5";
|
|
|
|
/** The accept encoding wanted by the client
|
|
*/
|
|
private $acceptEncoding = "gzip, deflate";
|
|
|
|
// }}}
|
|
|
|
/** The constructor
|
|
*/
|
|
public function __construct ()
|
|
// {{{
|
|
{
|
|
$maxsize = str_replace (array ('G', 'M', 'K'),
|
|
array ('000000000', '000000', '000'),
|
|
ini_get ('memory_limit'));
|
|
$maxsize = intval ($maxsize / 2);
|
|
// If no maxsize limit, set to 8G
|
|
if ($maxsize === 0)
|
|
$maxsize = 8000000000;
|
|
$this->maxsize = $maxsize;
|
|
$this->headersReset ();
|
|
}
|
|
// }}}
|
|
|
|
/////////////////////////////////
|
|
//// GETTERS / SETTERS ////
|
|
/////////////////////////////////
|
|
/** Set / Get the url
|
|
* @param string|null $url Set / Get the url
|
|
*/
|
|
public function url ($url = null)
|
|
// {{{
|
|
{
|
|
if ($url === null)
|
|
return $this->url;
|
|
if (! is_string ($url))
|
|
throw new \Exception ("Invalid url to set : not a string", 406);
|
|
$this->url = $url;
|
|
return $this;
|
|
}
|
|
// }}}
|
|
|
|
/** Set / Get the cookies stored
|
|
* @param array|null $cookies Set / Get the cookies
|
|
*/
|
|
public function cookies ($cookies = null)
|
|
// {{{
|
|
{
|
|
if ($cookies === null)
|
|
return $this->cookies;
|
|
if (! is_array ($cookies))
|
|
throw new \Exception ("Invalid cookies to set : not an array", 406);
|
|
$this->cookies = $cookies;
|
|
return $this;
|
|
}
|
|
// }}}
|
|
|
|
/** Set / Get the method
|
|
* @param string|null $method Set / Get the method
|
|
*/
|
|
public function method ($method = null)
|
|
// {{{
|
|
{
|
|
if ($method === null)
|
|
return $this->method;
|
|
if (! is_string ($method))
|
|
throw new \Exception ("Invalid method to set : not an string", 406);
|
|
$method = strtoupper ($method);
|
|
if (! in_array ($method, array ("GET", "POST", "PUT", "DELETE", "HEAD")))
|
|
throw new \Exception ("Invalid method to set : not in list", 406);
|
|
$this->method = $method;
|
|
return $this;
|
|
}
|
|
// }}}
|
|
|
|
/** Get the headersReceived after the page was get
|
|
*/
|
|
public function headersReceived ()
|
|
// {{{
|
|
{
|
|
return $this->headersReceived;
|
|
}
|
|
// }}}
|
|
|
|
/** Get the headers sent to the server after the page was get
|
|
*/
|
|
public function headersSent ()
|
|
// {{{
|
|
{
|
|
return $this->headersSent;
|
|
}
|
|
// }}}
|
|
|
|
/** Set the headers to initial value
|
|
*/
|
|
public function headersReset ()
|
|
// {{{
|
|
{
|
|
$this->headersSent = array ();
|
|
return $this;
|
|
}
|
|
// }}}
|
|
|
|
/** Add a new header to be sent to the server
|
|
* @param string $header The header to add/update
|
|
* @param string $value The value to save
|
|
*/
|
|
public function headerAdd ($header, $value)
|
|
// {{{
|
|
{
|
|
$this->headersSent[$header] = $value;
|
|
return $this;
|
|
}
|
|
// }}}
|
|
|
|
/** Get the port used for connection
|
|
*/
|
|
public function port ()
|
|
// {{{
|
|
{
|
|
return $this->port;
|
|
}
|
|
// }}}
|
|
|
|
/** Set / Get the maximum maxsize allowed
|
|
* @param integer|null $maxsize The maxsize in bytes
|
|
*/
|
|
public function maxsize ($maxsize = null)
|
|
// {{{
|
|
{
|
|
if ($maxsize === null)
|
|
return $this->maxsize;
|
|
$this->maxsize = intval ($maxsize);
|
|
return $this;
|
|
}
|
|
// }}}
|
|
|
|
/** Get the HTTP Return code from connection
|
|
*/
|
|
public function httpCode ()
|
|
// {{{
|
|
{
|
|
return $this->httpCode;
|
|
}
|
|
// }}}
|
|
|
|
/** Set / Get the debug mode
|
|
* 0: Nothing is displayed, 1: Only URL are displayed,
|
|
* 2: headers only send and received, 3: all the content, but without sent
|
|
* files, 4: all the content
|
|
* @param boolean|null $debug The debug value to set or get
|
|
*/
|
|
public function debug ($debug = null)
|
|
// {{{
|
|
{
|
|
if ($debug === null)
|
|
return $this->debug;
|
|
$this->debug = intval ($debug);
|
|
return $this;
|
|
}
|
|
// }}}
|
|
|
|
/** Set / Get the form Data
|
|
* Will be of type array ("field" => "value")
|
|
* If value is like "@/tmp/file", use the /tmp/file as content
|
|
* @param array|null $formData The data to send to the server
|
|
*/
|
|
public function formData ($formData = null)
|
|
// {{{
|
|
{
|
|
if ($formData === null)
|
|
return $this->formData;
|
|
if (! is_array ($formData))
|
|
throw new \Exception ("Invalid form data provided : not an array", 406);
|
|
$this->formData = $formData;
|
|
$this->rawData = "";
|
|
return $this;
|
|
}
|
|
// }}}
|
|
|
|
/** Set / Get the content Data
|
|
* The data is in raw format and will not be modified.
|
|
* Overwrite the eventually previous form data and rawData
|
|
* @param string|null $rawData The data to use
|
|
*/
|
|
public function rawData ($rawData = null)
|
|
// {{{
|
|
{
|
|
if ($rawData === null)
|
|
return $this->rawData;
|
|
if (! is_string ($rawData))
|
|
throw new \Exception ("Invalid raw data provided : not a string", 406);
|
|
$this->rawData = $rawData;
|
|
$this->formData = array ();
|
|
return $this;
|
|
}
|
|
// }}}
|
|
|
|
/** Get / Set the Store of session cookies when analyzing the answer of the
|
|
* server
|
|
* @param boolean|null $cookiesSession Allow to store the session cookies
|
|
*/
|
|
public function cookiesSession ($cookiesSession = null)
|
|
// {{{
|
|
{
|
|
if ($cookiesSession === null)
|
|
return $this->cookiesSession;
|
|
$this->cookiesSession = !! $cookiesSession;
|
|
return $this;
|
|
}
|
|
// }}}
|
|
|
|
/** Get / Set the maximum number of redirect to follow before aborting
|
|
* @param integer|null $redirectMaxCount The maximum number of redirect
|
|
* before exception
|
|
*/
|
|
public function redirectMaxCount ($redirectMaxCount = null)
|
|
// {{{
|
|
{
|
|
if ($redirectMaxCount === null)
|
|
return $this->redirectMaxCount;
|
|
$this->redirectMaxCount = intval ($redirectMaxCount);
|
|
return $this;
|
|
}
|
|
// }}}
|
|
|
|
/** Get / Set the actual number of redirect
|
|
* @param integer|null $redirectCount The actual number of redirect
|
|
*/
|
|
public function redirectCount ($redirectCount = null)
|
|
// {{{
|
|
{
|
|
if ($redirectCount === null)
|
|
return $this->redirectCount;
|
|
$this->redirectCount = intval ($redirectCount);
|
|
return $this;
|
|
}
|
|
// }}}
|
|
|
|
/** Get / Set the timeout in second before expiring the connection
|
|
* 30s by default
|
|
* @param integer|null $timeout The timeout value
|
|
*/
|
|
public function timeout ($timeout = null)
|
|
// {{{
|
|
{
|
|
if ($timeout === null)
|
|
return $this->timeout;
|
|
$this->timeout = intval ($timeout);
|
|
return $this;
|
|
}
|
|
// }}}
|
|
|
|
/** Get / Set the useragent sent to the server. If it is empty, it will not be
|
|
* sent
|
|
* @param string|null $useragent The user agent to use
|
|
*/
|
|
public function useragent ($useragent = null)
|
|
// {{{
|
|
{
|
|
if ($useragent === null)
|
|
return $this->useragent;
|
|
$this->useragent = $useragent;
|
|
return $this;
|
|
}
|
|
// }}}
|
|
|
|
/** Get/Set the referer page
|
|
* @param string|null $referer The new referer that will be used on next
|
|
* request
|
|
*/
|
|
public function referer ($referer = null)
|
|
// {{{
|
|
{
|
|
if ($referer === null)
|
|
return $this->referer;
|
|
$this->referer = $referer;
|
|
return $this;
|
|
}
|
|
// }}}
|
|
|
|
/** Get/Set the accept type of page wanted by the client
|
|
* @param string|null $accept The accept types with weight
|
|
*/
|
|
public function accept ($accept = null)
|
|
// {{{
|
|
{
|
|
if ($accept === null)
|
|
return $this->accept;
|
|
$this->accept = $accept;
|
|
return $this;
|
|
}
|
|
// }}}
|
|
|
|
/** Get/Set the accept Language wanted by the client
|
|
* @param string|null $acceptLanguage The languages with weight
|
|
*/
|
|
public function acceptLanguage ($acceptLanguage = null)
|
|
// {{{
|
|
{
|
|
if ($acceptLanguage === null)
|
|
return $this->acceptLanguage;
|
|
$this->acceptLanguage = $acceptLanguage;
|
|
return $this;
|
|
}
|
|
// }}}
|
|
|
|
/** Get/Set the accept Encoding wanted by the client
|
|
* @param string|null $acceptEncoding The encoding requested
|
|
*/
|
|
public function acceptEncoding ($acceptEncoding = null)
|
|
// {{{
|
|
{
|
|
if ($acceptEncoding === null)
|
|
return $this->acceptEncoding;
|
|
$this->acceptEncoding = $acceptEncoding;
|
|
return $this;
|
|
}
|
|
// }}}
|
|
|
|
/** Set the authentication in Basic type
|
|
* @param string $login The login to use
|
|
* @param string $password The password to use
|
|
*/
|
|
public function authBasic ($login, $password)
|
|
// {{{
|
|
{
|
|
$this->headersSent["Authorization"] = "Basic ".
|
|
base64_encode ("$login:$password");
|
|
return $this;
|
|
}
|
|
// }}}
|
|
|
|
/** Get/Set the ssl options
|
|
* @param array|null $ssloptions The SSL Options to use
|
|
* @return $this
|
|
*/
|
|
public function ssloptions ($ssloptions = null)
|
|
// {{{
|
|
{
|
|
if ($ssloptions === null)
|
|
return $this->ssloptions;
|
|
$this->ssloptions = $ssloptions;
|
|
return $this;
|
|
}
|
|
// }}}
|
|
|
|
//////////////////////////////////
|
|
//// THE ACTIVE METHODS ////
|
|
//////////////////////////////////
|
|
/** Get the page
|
|
* Will fill the headersReceived, cookies and port properties.
|
|
* This will fill all the RAM if the page is too big. For big files, use
|
|
* $httpclient->url ($url)
|
|
* ->connect () ;
|
|
* while ($content = $httpclient->read ()) {}
|
|
* $httpclient->disconnect ();
|
|
* If no maxsize limit is set, limit the download to 8G
|
|
* @param string $url The URL to get
|
|
* @param array|null $ssloptions The SSL options (stream_context_set_option)
|
|
* @return the page body
|
|
*/
|
|
public function getPage ($url, $ssloptions = null)
|
|
// {{{
|
|
{
|
|
$this->method ("GET");
|
|
$this->url ($url);
|
|
$this->connect ($ssloptions);
|
|
return $this->getContent ();
|
|
}
|
|
// }}}
|
|
|
|
/** Init the connection to URL
|
|
* Will fill the headersReceived, cookies and port properties.
|
|
* @param array|null $ssloptions The SSL options (stream_context_set_option)
|
|
* @return $this
|
|
*/
|
|
public function connect ($ssloptions = null)
|
|
// {{{
|
|
{
|
|
$this->log (2, "## URL Start $this->method $this->url");
|
|
if ($ssloptions !== null)
|
|
$this->ssloptions = $ssloptions;
|
|
$this->headersReceived = array ();
|
|
$this->bodySize = null;
|
|
$this->httpCode = null;
|
|
if ($this->url === "")
|
|
throw new \Exception ("No URL set to connect", 406);
|
|
// Manage the URL (and the parameters in GET method)
|
|
// {{{
|
|
$parseURL = parse_url ($this->url);
|
|
if (! key_exists ("scheme", $parseURL))
|
|
throw new \Exception ("Scheme must be set to http or https", 406);
|
|
if ($parseURL["scheme"] !== "http" &&
|
|
$parseURL["scheme"] !== "https")
|
|
throw new \Exception ("Scheme must be http or https only", 406);
|
|
if (key_exists ("port", $parseURL))
|
|
$this->port = $parseURL["port"];
|
|
elseif (key_exists ("scheme", $parseURL))
|
|
{
|
|
if ($parseURL["scheme"] === "http")
|
|
$this->port = 80;
|
|
elseif ($parseURL["scheme"] === "https")
|
|
$this->port = 443;
|
|
}
|
|
if (! key_exists ("path", $parseURL))
|
|
$path = "/";
|
|
else
|
|
$path = $parseURL["path"];
|
|
if (key_exists ("query", $parseURL))
|
|
$path .= "?".$parseURL["query"];
|
|
|
|
if ($this->method === "GET" && ! empty ($this->formData))
|
|
{
|
|
// In GET method, the form data are added to the path
|
|
if (! key_exists ("query", $parseURL))
|
|
$path .= "?";
|
|
else
|
|
$path .= "&";
|
|
$i = 0;
|
|
foreach ($this->formData as $key => $val)
|
|
{
|
|
if ($i > 0)
|
|
$path .= "&";
|
|
$path .= rawurlencode ($key)."=";
|
|
if (isset ($val{0}) && $val{0} === "@")
|
|
{
|
|
$file = substr ($val, 1);
|
|
if (! file_exists ($file))
|
|
throw new \Exception ("Data file '$file' doesn't exists", 406);
|
|
$val = file_get_contents ($file);
|
|
}
|
|
$path .= rawurlencode ($val);
|
|
$i ++;
|
|
}
|
|
}
|
|
|
|
if (key_exists ("fragment", $parseURL))
|
|
$path .= "#".$parseURL["fragment"];
|
|
if (! key_exists ("host", $parseURL))
|
|
throw new \Exception ("No host provided to URL", 406);
|
|
// }}}
|
|
|
|
// Prepare the headers to be sent
|
|
// {{{
|
|
unset ($this->headersSent[0]);
|
|
array_unshift ($this->headersSent,
|
|
"$this->method $path HTTP/1.1");
|
|
$this->headersSent["Host"] = $parseURL["host"];
|
|
if ($this->acceptEncoding !== "")
|
|
$this->headersSent["Accept-Encoding"] = $this->acceptEncoding;
|
|
if ($this->acceptLanguage !== "")
|
|
$this->headersSent["Accept-Language"] = $this->acceptLanguage;
|
|
if ($this->accept != "")
|
|
$this->headersSent["Accept"] = $this->accept;
|
|
if ($this->useragent !== "")
|
|
$this->headersSent["User-Agent"] = $this->useragent;
|
|
if ($this->referer !== "")
|
|
$this->headersSent["Referer"] = $this->referer;
|
|
$this->headersSent["Connection"] = "keep-alive";
|
|
$cookies = $this->cookieToSend ($this->url);
|
|
if (! empty ($cookies))
|
|
{
|
|
$this->headersSent["Cookie"] = implode ("; ", $cookies);
|
|
}
|
|
if ($this->method !== "GET" && ! empty ($this->formData))
|
|
{
|
|
$this->headersSent["Content-Type"] = "application/x-www-form-urlencoded";
|
|
$len = 0;
|
|
foreach ($this->formData as $key => $val)
|
|
{
|
|
if ($len > 0)
|
|
$len++; // Add the &
|
|
$len += strlen (rawurlencode ($key)) + 1;
|
|
if (isset ($val{0}) && $val{0} === "@")
|
|
{
|
|
$file = substr ($val, 1);
|
|
if (! file_exists ($file))
|
|
throw new \Exception ("Data file '$file' doesn't exists", 406);
|
|
// TODO : Do a loop of 1MB for big files instead of loading the mem
|
|
$len += strlen (rawurlencode (file_get_contents ($file)));
|
|
}
|
|
else
|
|
$len += strlen (rawurlencode ($val));
|
|
}
|
|
$this->headersSent["Content-Length"] = $len;
|
|
}
|
|
if ($this->method !== "GET" && $this->rawData !== "")
|
|
{
|
|
if (! key_exists ("Content-Type", $this->headersSent))
|
|
$this->headersSent["Content-Type"] =
|
|
"application/x-www-form-urlencoded";
|
|
$this->headersSent["Content-Length"] = strlen ($this->rawData);
|
|
}
|
|
|
|
$this->log (2, "Headers Send :");
|
|
$this->log (2, $this->headersSent);
|
|
// }}}
|
|
|
|
// Send the request to the server
|
|
// {{{
|
|
if ($this->tcpclient === null)
|
|
{
|
|
$this->tcpclient = new tcpclient ($parseURL["host"], $this->port);
|
|
$this->tcpclient->timeout ($this->timeout);
|
|
$this->tcpclient->connect ();
|
|
if ($parseURL["scheme"] === "https")
|
|
$this->tcpclient->cryptoEnable (true, null, $this->ssloptions);
|
|
}
|
|
$this->tcpclient->readMode ("text");
|
|
$this->tcpclient->send ($this->headersSent[0]."\r\n");
|
|
foreach ($this->headersSent as $header => $value)
|
|
{
|
|
if ($header === 0)
|
|
continue;
|
|
$this->tcpclient->send ("$header: $value\r\n");
|
|
}
|
|
$this->tcpclient->send ("\r\n");
|
|
// }}}
|
|
|
|
// Send the POST data form if exists
|
|
// {{{
|
|
if ($this->method !== "GET" && ! empty ($this->formData))
|
|
{
|
|
$i = 0;
|
|
$this->log (3, "Body Send : \n");
|
|
foreach ($this->formData as $key => $val)
|
|
{
|
|
if ($i > 0)
|
|
{
|
|
$this->tcpclient->send ("&");
|
|
$this->log (3, "&");
|
|
}
|
|
$this->tcpclient->send (rawurlencode ($key)."=");
|
|
$this->log (3, rawurlencode ($key)."=");
|
|
if (isset ($val{0}) && $val{0} === "@")
|
|
{
|
|
$file = substr ($val, 1);
|
|
if (! file_exists ($file))
|
|
throw new \Exception ("Data file '$file' doesn't exists", 406);
|
|
// TODO : Do a loop of 1MB for big files instead of loading the mem
|
|
$val = file_get_contents ($file);
|
|
$this->tcpclient->send (rawurlencode ($val));
|
|
$this->log (4, rawurlencode ($val));
|
|
}
|
|
else
|
|
{
|
|
$this->tcpclient->send (rawurlencode ($val));
|
|
$this->log (3, rawurlencode ($val));
|
|
}
|
|
$i ++;
|
|
}
|
|
$this->log (3, "\n");
|
|
}
|
|
elseif ($this->method !== "GET" && $this->rawData !== "")
|
|
{
|
|
$this->tcpclient->send ($this->rawData);
|
|
$this->log (3, $this->rawData."\n");
|
|
}
|
|
// }}}
|
|
|
|
// Get the result header from the server
|
|
// {{{
|
|
$headers = array ();
|
|
while (($header = $this->tcpclient->read (4095)) !== "")
|
|
{
|
|
@list ($key, $val) = explode (":", $header, 2);
|
|
if ($val === null)
|
|
$headers[] = $header;
|
|
else
|
|
{
|
|
if (key_exists ($key, $headers))
|
|
{
|
|
if (! (is_array ($headers[$key])))
|
|
$headers[$key] = array ($headers[$key]);
|
|
$headers[$key][] = trim ($val);
|
|
}
|
|
else
|
|
$headers[$key] = trim ($val);
|
|
}
|
|
}
|
|
if (! key_exists (0, $headers))
|
|
throw new \Exception ("No HTTP code available from server", 500);
|
|
$this->headersReceived = $headers;
|
|
$returnCode = $headers[0];
|
|
preg_match_all ("#^HTTP/(?P<HTTPVersion>\d.\d) (?P<HTTPCode>\d+) ".
|
|
"(?P<HTTPString>.+)$#i", $returnCode, $matches);
|
|
if (isset ($matches["HTTPCode"][0]))
|
|
$this->httpCode = intval ($matches["HTTPCode"][0]);
|
|
|
|
// Add the received cookies to property
|
|
if (isset ($headers["Set-Cookie"]))
|
|
{
|
|
if (! is_array ($headers["Set-Cookie"]))
|
|
$cookies = array ($headers["Set-Cookie"]);
|
|
else
|
|
$cookies = $headers["Set-Cookie"];
|
|
foreach ($cookies as $cookie)
|
|
{
|
|
// The invalid cookies are silently dropped
|
|
$this->cookieAdd ($parseURL["host"], $cookie);
|
|
}
|
|
}
|
|
$this->log (2, "Headers Received :");
|
|
$this->log (2, $this->headersReceived);
|
|
|
|
$this->contentMethod = false;
|
|
if (key_exists ("Transfer-Encoding", $headers) &&
|
|
$headers["Transfer-Encoding"] === "chunked")
|
|
{
|
|
$this->contentMethod = "chunked";
|
|
$this->bodySize = 0;
|
|
}
|
|
elseif (key_exists ("Content-Length", $headers))
|
|
{
|
|
$this->contentMethod = "Content-Length";
|
|
$this->bodySize = $headers["Content-Length"];
|
|
}
|
|
elseif (key_exists ("Connection", $headers) &&
|
|
$headers["Connection"] === "close")
|
|
{
|
|
// Connection closed by server. Nothing to get
|
|
}
|
|
elseif ($this->httpCode !== 204 && $this->httpCode !== 301 &&
|
|
$this->httpCode !== 302)
|
|
{
|
|
throw new \Exception ("No transfert content provided", 500);
|
|
}
|
|
if ($this->contentMethod === "chunked")
|
|
$this->log (1, "URL $this->method $this->url $this->httpCode Chunked");
|
|
elseif (is_numeric ($this->bodySize))
|
|
$this->log (1, "URL $this->method $this->url $this->httpCode ".
|
|
$this->bodySize);
|
|
else
|
|
$this->log (1, "URL $this->method $this->url $this->httpCode 0");
|
|
// }}}
|
|
return $this;
|
|
}
|
|
// }}}
|
|
|
|
/** Get the content from the server and put it in memory
|
|
*/
|
|
public function getContent ()
|
|
// {{{
|
|
{
|
|
$url = $this->url;
|
|
$content = "";
|
|
while ($tmp = $this->read (1000000))
|
|
{
|
|
$content .= $tmp;
|
|
if (strlen ($content) > $this->maxsize)
|
|
throw new \Exception ("File to get exceeded maxsize", 500);
|
|
}
|
|
if (key_exists ("Content-Encoding", $this->headersReceived))
|
|
{
|
|
if ($this->headersReceived["Content-Encoding"] === "gzip" &&
|
|
function_exists ("gzdecode"))
|
|
$content = gzdecode ($content);
|
|
}
|
|
$this->disconnect ();
|
|
$this->headersReset ();
|
|
$this->formData = "";
|
|
$this->rawData = "";
|
|
if ($this->httpCode === 301 || $this->httpCode === 302 ||
|
|
key_exists ("Location", $this->headersReceived))
|
|
{
|
|
if (! key_exists ("Location", $this->headersReceived))
|
|
throw new \Exception ("Redirect without location provided", 406);
|
|
$this->redirectCount++;
|
|
if ($this->redirectCount > $this->redirectMaxCount)
|
|
throw new \Exception ("Redirect exceed maximum limit", 406);
|
|
// echo "REDIRECT TO ".$this->headersReceived["Location"]."\n";
|
|
$location = $this->headersReceived["Location"];
|
|
$parseURLInit = parse_url ($url);
|
|
$parseURLLocation = parse_url ($location);
|
|
if ($parseURLLocation["path"]{0} !== "/" &&
|
|
key_exists ("path", $parseURLInit))
|
|
$location = dirname ($parseURLInit["path"]).$location;
|
|
if (! key_exists ("port", $parseURLLocation) &&
|
|
key_exists ("port", $parseURLInit))
|
|
$location = ":".$parseURLInit["port"].$location;
|
|
if (! key_exists ("host", $parseURLLocation))
|
|
$location = $parseURLInit["host"].$location;
|
|
if (! key_exists ("scheme", $parseURLLocation))
|
|
$location = $parseURLInit["scheme"]."://".$location;
|
|
$this->log (1, "REDIRECT $this->httpCode to $location");
|
|
$this->log (2, " content=$content\n");
|
|
$content = $this->getPage ($location, $this->ssloptions);
|
|
}
|
|
$this->referer = $url;
|
|
$this->redirectCount = 0;
|
|
return $content;
|
|
}
|
|
// }}}
|
|
|
|
/** Read max MAXSIZE bytes
|
|
* Return "" if all the file is received
|
|
* 3 methods are supported : Chunked mode, Content-Length defined and all
|
|
* until the connection will be closed by the server
|
|
* @param integer $maxsize The maxsize to get in this read
|
|
*/
|
|
public function read ($maxsize = 4096)
|
|
// {{{
|
|
{
|
|
if ($this->tcpclient === null)
|
|
throw new \Exception ("HTTPClient : can not read non connected URL", 406);
|
|
$this->tcpclient->timeout ($this->timeout);
|
|
$content = "";
|
|
if ($this->contentMethod === "chunked" && $this->bodySize === 0)
|
|
{
|
|
// Get the body chunk size
|
|
$this->tcpclient->readMode ("text");
|
|
$size = trim ($this->tcpclient->read ());
|
|
$this->bodySize = hexdec ($size);
|
|
}
|
|
if ($this->bodySize === 0)
|
|
return "";
|
|
if ($this->bodySize === null)
|
|
$toBeRead = $maxsize;
|
|
else
|
|
$toBeRead = $this->bodySize;
|
|
if ($toBeRead > $maxsize)
|
|
$toBeRead = $maxsize;
|
|
$this->tcpclient->readMode ("binary");
|
|
if ($toBeRead > 0)
|
|
$content = $this->tcpclient->read ($toBeRead);
|
|
// In close mode, the bodySize is not set and should not be updated
|
|
if ($this->bodySize !== null)
|
|
$this->bodySize = $this->bodySize - strlen ($content);
|
|
if ($this->contentMethod === "chunked" && $this->bodySize === 0)
|
|
{
|
|
// Get the Carriage return before the next chunk size
|
|
$this->tcpclient->readMode ("text");
|
|
$cr = trim ($this->tcpclient->read ());
|
|
}
|
|
$this->log (3, $content);
|
|
return $content;
|
|
}
|
|
// }}}
|
|
|
|
/** Disconnect the connection
|
|
*/
|
|
public function disconnect ()
|
|
// {{{
|
|
{
|
|
$this->tcpclient = null;
|
|
}
|
|
// }}}
|
|
|
|
/** Display the log message
|
|
* @param integer $priority The minimal priority to display the message
|
|
* @param mixed $message The message to display
|
|
*/
|
|
public function log ($priority, $message)
|
|
{
|
|
if ($this->debug < $priority)
|
|
return;
|
|
if (is_array ($message))
|
|
print_r ($message);
|
|
elseif (is_bool ($message))
|
|
{
|
|
if ($message) echo "TRUE\n"; else echo "FALSE\n";
|
|
}
|
|
else
|
|
{
|
|
echo "$message";
|
|
if ($priority < 3)
|
|
echo "\n";
|
|
}
|
|
}
|
|
|
|
/** Return the base URL of the site
|
|
* @return the URL
|
|
*/
|
|
public function baseURL ()
|
|
// {{{
|
|
{
|
|
if ($this->url === "")
|
|
throw new \Exception ("Can not get baseURL of empty url", 500);
|
|
$parseURL = parse_url ($this->url);
|
|
$scheme = isset ($parseURL['scheme']) ? $parseURL['scheme'] . '://' : '';
|
|
$host = isset ($parseURL['host']) ? $parseURL['host'] : '';
|
|
$port = isset ($parseURL['port']) ? ':' . $parseURL['port'] : '';
|
|
$user = isset ($parseURL['user']) ? $parseURL['user'] : '';
|
|
$pass = isset ($parseURL['pass']) ? ':' . $parseURL['pass'] : '';
|
|
$pass = ($user || $pass) ? "$pass@" : '';
|
|
return "$scheme$user$pass$host$port";
|
|
}
|
|
// }}}
|
|
|
|
//////////////////////////////////
|
|
//// COOKIES MANAGEMENT ////
|
|
//////////////////////////////////
|
|
/** The cookies are stored in Netscape cookies.txt file :
|
|
The layout of Netscape's cookies.txt file is such that each line contains
|
|
one name-value pair. An example cookies.txt file may have an entry that
|
|
looks like this:
|
|
|
|
.netscape.com TRUE / FALSE 946684799 NETSCAPE_ID 100103
|
|
|
|
Each line represents a single piece of stored information. A tab is inserted
|
|
between each of the fields.
|
|
|
|
From left-to-right, here is what each field represents:
|
|
domain : The domain that created AND that can read the variable.
|
|
flag : A TRUE/FALSE value indicating if all machines within a given
|
|
domain can access the variable. This value is set automatically
|
|
by the browser, depending on the value you set for domain.
|
|
path : The path within the domain that the variable is valid for.
|
|
secure : A TRUE/FALSE value indicating if a secure connection with the
|
|
domain is needed to access the variable.
|
|
expiration : The UNIX time that the variable will expire on. UNIX time is
|
|
defined as the number of seconds since Jan 1, 1970 00:00:00
|
|
GMT.
|
|
name : The name of the variable.
|
|
|
|
value : The value of the variable.
|
|
*/
|
|
|
|
/** Add a cookie in the store
|
|
* If the cookie already exists, the old one is replaced by the new value
|
|
* @param string $domain The domain to use
|
|
* @param string $cookie The cookie content to store
|
|
*/
|
|
public function cookieAdd ($domain, $cookie)
|
|
// {{{
|
|
{
|
|
// echo "COOKIE = $cookie\n";
|
|
$content = explode (";", $cookie);
|
|
$flag = "FALSE";
|
|
$path = "/";
|
|
$secure = "FALSE";
|
|
$expiration = 0;
|
|
$name = "";
|
|
$value = "";
|
|
foreach ($content as $part)
|
|
{
|
|
@list ($key, $val) = explode ("=", $part, 2);
|
|
$key = trim ($key);
|
|
if (strtolower ($key) === "path") $path = $val;
|
|
elseif (strtolower ($key) === "domain")
|
|
{
|
|
// Check if $domain is compatible with $key before storing the cookie
|
|
if (substr ($domain, -1 * strlen ($val)) === $val)
|
|
{
|
|
$domain = $val;
|
|
$flag = "TRUE";
|
|
}
|
|
else
|
|
{
|
|
return "Invalid domain provided";
|
|
}
|
|
}
|
|
elseif (strtolower ($key) === "expires")
|
|
{
|
|
try
|
|
{
|
|
$date = new \DateTime ($val);
|
|
$expiration = $date->getTimestamp();
|
|
}
|
|
catch (\Exception $e)
|
|
{
|
|
return "Invalid expires date provided";
|
|
}
|
|
}
|
|
elseif ($val !== null && $name === "")
|
|
{
|
|
// Only the first value will be stored as cookie (name, val) pair
|
|
// echo "KEY=$key => $val\n";
|
|
$name = $key;
|
|
$value = $val;
|
|
}
|
|
elseif ($val !== null)
|
|
{
|
|
// echo "Not managed key=>val $key=>$val\n";
|
|
}
|
|
else
|
|
{
|
|
// No value provided : no test
|
|
}
|
|
}
|
|
|
|
$cookieLine = "$domain\t$flag\t$path\t$secure\t$expiration\t$name\t$value";
|
|
if (strlen ($cookieLine) > 4096)
|
|
return "Cookie value too long";
|
|
if ($expiration === 0 && $this->cookiesSession === false)
|
|
{
|
|
// echo "Do not store Session cookies\n";
|
|
return;
|
|
}
|
|
|
|
$found = false;
|
|
foreach ($this->cookies as $key => $storedCookie)
|
|
{
|
|
$storedCookie = explode ("\t", $storedCookie);
|
|
if (! key_exists (0, $storedCookie) ||
|
|
! key_exists (5, $storedCookie))
|
|
continue;
|
|
if ($storedCookie[0] !== $domain || $storedCookie[5] !== $name)
|
|
continue;
|
|
if ($expiration > 0 && $expiration < time ())
|
|
{
|
|
//echo "Remove the already set cookie for $domain $name : expired\n";
|
|
unset ($this->cookies[$key]);
|
|
$found = true;
|
|
}
|
|
else
|
|
{
|
|
//echo "Update the already set cookie for $domain $name\n";
|
|
$this->cookies[$key] = $cookieLine;
|
|
$found = true;
|
|
}
|
|
}
|
|
if ($found === false)
|
|
{
|
|
//echo "Append the new cookie for $domain $name\n";
|
|
$this->cookies[] = $cookieLine;
|
|
}
|
|
}
|
|
// }}}
|
|
|
|
/** Check if some stored cookies must be send to the server, because they are
|
|
* in the same domain.
|
|
* @param string $url The URL requested
|
|
* @return array the cookies to add to the headers send to the server
|
|
*/
|
|
public function cookieToSend ($url)
|
|
// {{{
|
|
{
|
|
$parseURL = parse_url ($this->url);
|
|
if ($parseURL === false)
|
|
return array ();
|
|
if (! key_exists ("host", $parseURL))
|
|
return array ();
|
|
if (! key_exists ("path", $parseURL))
|
|
$parseURL["path"] = "/";
|
|
if ($parseURL["path"]{0} !== "/")
|
|
$parseURL["path"] = "/".$parseURL["path"];
|
|
$res = array ();
|
|
foreach ($this->cookies as $storedCookie)
|
|
{
|
|
$storedCookie = explode ("\t", $storedCookie);
|
|
if (! key_exists (0, $storedCookie) ||
|
|
! key_exists (2, $storedCookie) ||
|
|
! key_exists (5, $storedCookie) ||
|
|
! key_exists (6, $storedCookie))
|
|
continue;
|
|
if ($storedCookie[0] !== substr ($parseURL["host"],
|
|
-1 * strlen ($storedCookie[0])) ||
|
|
$storedCookie[2] !== substr ($parseURL["path"], 0,
|
|
strlen ($storedCookie[2])))
|
|
continue;
|
|
$res[] = $storedCookie[5]."=".$storedCookie[6];
|
|
}
|
|
return $res;
|
|
}
|
|
// }}}
|
|
}
|