2013-07-17 01:54:44 +02:00
|
|
|
<?php
|
|
|
|
|
|
|
|
namespace PicoFeed\Clients;
|
|
|
|
|
|
|
|
use \PicoFeed\Logging;
|
2014-05-20 20:20:27 +02:00
|
|
|
use \PicoFeed\Client;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* cURL HTTP client
|
|
|
|
*
|
|
|
|
* @author Frederic Guillot
|
|
|
|
* @package client
|
|
|
|
*/
|
|
|
|
class Curl extends Client
|
2013-07-17 01:54:44 +02:00
|
|
|
{
|
2014-05-20 20:20:27 +02:00
|
|
|
/**
|
|
|
|
* HTTP response body
|
|
|
|
*
|
|
|
|
* @access private
|
|
|
|
* @var string
|
|
|
|
*/
|
2013-07-17 01:54:44 +02:00
|
|
|
private $body = '';
|
2014-05-20 20:20:27 +02:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Body size
|
|
|
|
*
|
|
|
|
* @access private
|
|
|
|
* @var integer
|
|
|
|
*/
|
2013-07-17 01:54:44 +02:00
|
|
|
private $body_length = 0;
|
2014-05-20 20:20:27 +02:00
|
|
|
|
|
|
|
/**
|
|
|
|
* HTTP response headers
|
|
|
|
*
|
|
|
|
* @access private
|
|
|
|
* @var array
|
|
|
|
*/
|
2013-07-17 01:54:44 +02:00
|
|
|
private $headers = array();
|
|
|
|
|
2014-05-20 20:20:27 +02:00
|
|
|
/**
|
|
|
|
* Counter on the number of header received
|
|
|
|
*
|
|
|
|
* @access private
|
|
|
|
* @var integer
|
|
|
|
*/
|
|
|
|
private $headers_counter = 0;
|
2013-07-17 01:54:44 +02:00
|
|
|
|
2014-05-20 20:20:27 +02:00
|
|
|
/**
|
|
|
|
* cURL callback to read the HTTP body
|
|
|
|
*
|
|
|
|
* If the function return -1, curl stop to read the HTTP response
|
|
|
|
*
|
|
|
|
* @access public
|
|
|
|
* @param resource $ch cURL handler
|
|
|
|
* @param string $buffer Chunk of data
|
|
|
|
* @return integer Length of the buffer
|
|
|
|
*/
|
2013-07-17 01:54:44 +02:00
|
|
|
public function readBody($ch, $buffer)
|
|
|
|
{
|
|
|
|
$length = strlen($buffer);
|
|
|
|
$this->body_length += $length;
|
|
|
|
|
2014-05-20 20:20:27 +02:00
|
|
|
if ($this->body_length > $this->max_body_size) {
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
2013-07-17 01:54:44 +02:00
|
|
|
$this->body .= $buffer;
|
|
|
|
|
|
|
|
return $length;
|
|
|
|
}
|
|
|
|
|
2014-05-20 20:20:27 +02:00
|
|
|
/**
|
|
|
|
* cURL callback to read HTTP headers
|
|
|
|
*
|
|
|
|
* @access public
|
|
|
|
* @param resource $ch cURL handler
|
|
|
|
* @param string $buffer Header line
|
|
|
|
* @return integer Length of the buffer
|
|
|
|
*/
|
2013-07-17 01:54:44 +02:00
|
|
|
public function readHeaders($ch, $buffer)
|
|
|
|
{
|
|
|
|
$length = strlen($buffer);
|
|
|
|
|
|
|
|
if ($buffer === "\r\n") {
|
|
|
|
$this->headers_counter++;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
|
|
|
|
if (! isset($this->headers[$this->headers_counter])) {
|
|
|
|
$this->headers[$this->headers_counter] = '';
|
|
|
|
}
|
|
|
|
|
|
|
|
$this->headers[$this->headers_counter] .= $buffer;
|
|
|
|
}
|
|
|
|
|
|
|
|
return $length;
|
|
|
|
}
|
|
|
|
|
2014-05-20 20:20:27 +02:00
|
|
|
/**
|
2014-10-19 20:42:31 +02:00
|
|
|
* Prepare HTTP headers
|
2014-05-20 20:20:27 +02:00
|
|
|
*
|
2014-10-19 20:42:31 +02:00
|
|
|
* @access private
|
|
|
|
* @return array
|
2014-05-20 20:20:27 +02:00
|
|
|
*/
|
2014-10-19 20:42:31 +02:00
|
|
|
private function prepareHeaders()
|
2013-07-17 01:54:44 +02:00
|
|
|
{
|
2014-10-19 20:42:31 +02:00
|
|
|
$headers = array(
|
|
|
|
'Connection: close',
|
|
|
|
'User-Agent: '.$this->user_agent,
|
|
|
|
);
|
2013-07-17 01:54:44 +02:00
|
|
|
|
2014-10-19 20:42:31 +02:00
|
|
|
if ($this->etag) {
|
|
|
|
$headers[] = 'If-None-Match: '.$this->etag;
|
|
|
|
}
|
2013-07-17 01:54:44 +02:00
|
|
|
|
2014-10-19 20:42:31 +02:00
|
|
|
if ($this->last_modified) {
|
|
|
|
$headers[] = 'If-Modified-Since: '.$this->last_modified;
|
|
|
|
}
|
2013-07-17 01:54:44 +02:00
|
|
|
|
2014-10-19 20:42:31 +02:00
|
|
|
return $headers;
|
|
|
|
}
|
2013-09-24 01:22:13 +02:00
|
|
|
|
2014-10-19 20:42:31 +02:00
|
|
|
/**
|
|
|
|
* Prepare curl proxy context
|
|
|
|
*
|
|
|
|
* @access private
|
|
|
|
* @return resource
|
|
|
|
*/
|
|
|
|
private function prepareProxyContext($ch)
|
|
|
|
{
|
2014-05-20 20:20:27 +02:00
|
|
|
if ($this->proxy_hostname) {
|
2013-09-24 01:22:13 +02:00
|
|
|
|
2014-05-20 20:20:27 +02:00
|
|
|
Logging::setMessage(get_called_class().' Proxy: '.$this->proxy_hostname.':'.$this->proxy_port);
|
|
|
|
|
|
|
|
curl_setopt($ch, CURLOPT_PROXYPORT, $this->proxy_port);
|
2013-09-24 01:22:13 +02:00
|
|
|
curl_setopt($ch, CURLOPT_PROXYTYPE, 'HTTP');
|
2014-05-20 20:20:27 +02:00
|
|
|
curl_setopt($ch, CURLOPT_PROXY, $this->proxy_hostname);
|
2013-09-24 01:22:13 +02:00
|
|
|
|
2014-05-20 20:20:27 +02:00
|
|
|
if ($this->proxy_username) {
|
|
|
|
Logging::setMessage(get_called_class().' Proxy credentials: Yes');
|
|
|
|
curl_setopt($ch, CURLOPT_PROXYUSERPWD, $this->proxy_username.':'.$this->proxy_password);
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
Logging::setMessage(get_called_class().' Proxy credentials: No');
|
2013-09-24 01:22:13 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-10-19 20:42:31 +02:00
|
|
|
return $ch;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Prepare curl context
|
|
|
|
*
|
|
|
|
* @access private
|
|
|
|
* @return resource
|
|
|
|
*/
|
|
|
|
private function prepareContext()
|
|
|
|
{
|
|
|
|
$ch = curl_init();
|
|
|
|
|
|
|
|
curl_setopt($ch, CURLOPT_URL, $this->url);
|
|
|
|
curl_setopt($ch, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);
|
|
|
|
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, $this->timeout);
|
|
|
|
curl_setopt($ch, CURLOPT_TIMEOUT, $this->timeout);
|
|
|
|
curl_setopt($ch, CURLOPT_HTTPHEADER, $this->prepareHeaders());
|
|
|
|
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, ini_get('open_basedir') === '');
|
|
|
|
curl_setopt($ch, CURLOPT_MAXREDIRS, $this->max_redirects);
|
|
|
|
curl_setopt($ch, CURLOPT_ENCODING, '');
|
|
|
|
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); // For auto-signed certificates...
|
|
|
|
curl_setopt($ch, CURLOPT_WRITEFUNCTION, array($this, 'readBody'));
|
|
|
|
curl_setopt($ch, CURLOPT_HEADERFUNCTION, array($this, 'readHeaders'));
|
|
|
|
curl_setopt($ch, CURLOPT_COOKIEJAR, 'php://memory');
|
|
|
|
curl_setopt($ch, CURLOPT_COOKIEFILE, 'php://memory');
|
|
|
|
|
|
|
|
$ch = $this->prepareProxyContext($ch);
|
|
|
|
|
|
|
|
return $ch;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Execute curl context
|
|
|
|
*
|
|
|
|
* @access private
|
|
|
|
* @return resource
|
|
|
|
*/
|
|
|
|
private function executeContext()
|
|
|
|
{
|
|
|
|
$ch = $this->prepareContext();
|
2013-07-17 01:54:44 +02:00
|
|
|
curl_exec($ch);
|
|
|
|
|
2014-05-20 20:20:27 +02:00
|
|
|
Logging::setMessage(get_called_class().' cURL total time: '.curl_getinfo($ch, CURLINFO_TOTAL_TIME));
|
|
|
|
Logging::setMessage(get_called_class().' cURL dns lookup time: '.curl_getinfo($ch, CURLINFO_NAMELOOKUP_TIME));
|
|
|
|
Logging::setMessage(get_called_class().' cURL connect time: '.curl_getinfo($ch, CURLINFO_CONNECT_TIME));
|
|
|
|
Logging::setMessage(get_called_class().' cURL speed download: '.curl_getinfo($ch, CURLINFO_SPEED_DOWNLOAD));
|
|
|
|
Logging::setMessage(get_called_class().' cURL effective url: '.curl_getinfo($ch, CURLINFO_EFFECTIVE_URL));
|
2013-07-17 01:54:44 +02:00
|
|
|
|
|
|
|
if (curl_errno($ch)) {
|
2014-05-20 20:20:27 +02:00
|
|
|
Logging::setMessage(get_called_class().' cURL error: '.curl_error($ch));
|
2013-07-17 01:54:44 +02:00
|
|
|
curl_close($ch);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
curl_close($ch);
|
|
|
|
|
2014-10-19 20:42:31 +02:00
|
|
|
return true;
|
|
|
|
}
|
2013-08-15 03:18:23 +02:00
|
|
|
|
2014-10-19 20:42:31 +02:00
|
|
|
/**
|
|
|
|
* Do the HTTP request
|
|
|
|
*
|
|
|
|
* @access public
|
|
|
|
* @param bool $follow_location Flag used when there is an open_basedir restriction
|
|
|
|
* @return array HTTP response ['body' => ..., 'status' => ..., 'headers' => ...]
|
|
|
|
*/
|
|
|
|
public function doRequest($follow_location = true)
|
|
|
|
{
|
|
|
|
if (! $this->executeContext()) {
|
|
|
|
return false;
|
|
|
|
}
|
2013-08-15 03:18:23 +02:00
|
|
|
|
2014-10-19 20:42:31 +02:00
|
|
|
list($status, $headers) = $this->parseHeaders(explode("\r\n", $this->headers[$this->headers_counter - 1]));
|
2013-08-15 03:18:23 +02:00
|
|
|
|
2014-10-19 20:42:31 +02:00
|
|
|
// When resticted with open_basedir
|
|
|
|
if ($this->needToHandleRedirection($follow_location, $status)) {
|
|
|
|
return $this->handleRedirection($headers['Location']);
|
2013-08-15 03:18:23 +02:00
|
|
|
}
|
|
|
|
|
2013-07-17 01:54:44 +02:00
|
|
|
return array(
|
|
|
|
'status' => $status,
|
|
|
|
'body' => $this->body,
|
|
|
|
'headers' => $headers
|
|
|
|
);
|
|
|
|
}
|
2014-10-19 20:42:31 +02:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Check if the redirection have to be handled manually
|
|
|
|
*
|
|
|
|
* @access private
|
|
|
|
* @param boolean $follow_location Flag
|
|
|
|
* @param integer $status HTTP status code
|
|
|
|
* @return boolean
|
|
|
|
*/
|
|
|
|
private function needToHandleRedirection($follow_location, $status)
|
|
|
|
{
|
|
|
|
return $follow_location && ini_get('open_basedir') !== '' && ($status == 301 || $status == 302);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Handle manually redirections when there is an open base dir restriction
|
|
|
|
*
|
|
|
|
* @access private
|
|
|
|
* @param string $location Redirected URL
|
|
|
|
* @return boolean|array
|
|
|
|
*/
|
|
|
|
private function handleRedirection($location)
|
|
|
|
{
|
|
|
|
$nb_redirects = 0;
|
|
|
|
$this->url = $location;
|
|
|
|
$this->body = '';
|
|
|
|
$this->body_length = 0;
|
|
|
|
$this->headers = array();
|
|
|
|
$this->headers_counter = 0;
|
|
|
|
|
|
|
|
while (true) {
|
|
|
|
|
|
|
|
$nb_redirects++;
|
|
|
|
|
|
|
|
if ($nb_redirects >= $this->max_redirects) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
$result = $this->doRequest(false);
|
|
|
|
|
|
|
|
if ($result['status'] == 301 || $result['status'] == 302) {
|
|
|
|
$this->url = $result['headers']['Location'];
|
|
|
|
$this->body = '';
|
|
|
|
$this->body_length = 0;
|
|
|
|
$this->headers = array();
|
|
|
|
$this->headers_counter = 0;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
return $result;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
2014-05-20 20:20:27 +02:00
|
|
|
}
|