<?php
/**
* RequestProcessor using CURL.
*/
class CURLRequestProcessor extends RequestProcessor
{
/**
* Temporary buffer for headers.
*/
private $headers = '';
public function __construct()
{
if ( ini_get( 'safe_mode' ) || ini_get( 'open_basedir' ) ) {
$this->can_followlocation = false;
}
if ( !defined( 'FILE_CACHE_LOCATION' ) ) {
define( 'FILE_CACHE_LOCATION', HABARI_PATH . '/user/cache/' );
}
}
public function execute( $method, $url, $headers, $body, $config )
{
$merged_headers = array();
foreach ( $headers as $k => $v ) {
$merged_headers[] = $k . ': ' . $v;
}
$ch = curl_init();
$options = array(
CURLOPT_URL => $url, // The URL
CURLOPT_HEADERFUNCTION => array( &$this, '_headerfunction' ), // The header of the response.
CURLOPT_MAXREDIRS => $config['max_redirects'], // Maximum number of redirections to follow.
CURLOPT_CONNECTTIMEOUT => $config['connect_timeout'],
CURLOPT_TIMEOUT => $config['timeout'],
CURLOPT_SSL_VERIFYPEER => $config['ssl']['verify_peer'],
CURLOPT_SSL_VERIFYHOST => $config['ssl']['verify_host'],
CURLOPT_BUFFERSIZE => $config['buffer_size'],
CURLOPT_HTTPHEADER => $merged_headers, // headers to send
CURLOPT_FAILONERROR => true, // if the status code is >= 400, fail
CURLOPT_ENCODING => '', // let curl decide which compression methods to use
);
if ( $this->can_followlocation && $config['follow_redirects'] ) {
$options[CURLOPT_FOLLOWLOCATION] = true; // Follow 302's and the like.
}
if ( $method === 'POST' ) {
$options[CURLOPT_POST] = true; // POST mode.
$options[CURLOPT_POSTFIELDS] = $body;
}
else {
$options[CURLOPT_CRLF] = true; // Convert UNIX newlines to \r\n
}
// set proxy, if needed
$urlbits = InputFilter::parse_url( $url );
if ( $config['proxy']['server'] && ! in_array( $urlbits['host'], $config['proxy']['exceptions'] ) ) {
$options[CURLOPT_PROXY] = $config['proxy']['server'] . ':' . $config['proxy']['port']; // Validation of the existence of the port should take place in the Options form
if ( $config['proxy']['username'] ) {
$options[CURLOPT_PROXYUSERPWD] = $config['proxy']['username'] . ':' . $config['proxy']['password'];
switch ( $config['proxy']['auth_type'] ) {
case 'basic':
$options[CURLOPT_PROXYAUTH] = CURLAUTH_BASIC;
break;
case 'digest':
$options[CURLOPT_PROXYAUTH] = CURLAUTH_DIGEST;
break;
}
}
// if it's a socks proxy, we have to tell curl that
if ( $config['proxy']['type'] == 'socks' ) {
$options[CURLOPT_PROXYTYPE] = CURLPROXY_SOCKS5;
}
}
curl_setopt_array( $ch, $options );
/**
* @todo Possibly find a way to generate a temp file without needing the user
* to set write permissions on cache directory
*
* @todo Fallback to using the the old way if the cache directory isn't writable
*
* @todo How about trying to use the system-defined temp directory? We could at least try, even if safe_mode or something breaks it. - chrismeller
*/
$tmp = tempnam( FILE_CACHE_LOCATION, 'RR' );
if ( ! $tmp ) {
throw new Exception( _t( 'CURL Error. Unable to create temporary file name.' ) );
}
$fh = @fopen( $tmp, 'w+b' );
if ( ! $fh ) {
throw new Exception( _t( 'CURL Error. Unable to open temporary file.' ) );
}
curl_setopt( $ch, CURLOPT_FILE, $fh );
$success = curl_exec( $ch );
if ( $success ) {
rewind( $fh );
$body = stream_get_contents( $fh );
}
fclose( $fh );
unset( $fh );
if ( isset( $tmp ) && file_exists( $tmp ) ) {
unlink( $tmp );
}
if ( curl_errno( $ch ) !== 0 ) {
// get the number and error before we close the handle so we don't error out
$errno = curl_errno( $ch );
$error = curl_error( $ch );
// before we throw an exception, just to be nice
curl_close( $ch );
switch ( $errno ) {
case CURLE_OPERATION_TIMEOUTED:
// the request timed out
throw new RemoteRequest_Timeout( $error, $errno );
break;
default:
throw new Exception( _t( 'CURL Error %1$d: %2$s', array( $errno, $error ) ) );
break;
}
}
curl_close( $ch );
$this->response_headers = $this->headers; // really redundant now, since we could write directly to response_headers, but makes it more clear where they came from
$this->response_body = $body;
$this->executed = true;
return true;
}
/**
* cURL will hand each header received by the *response* to this method, so we use it
* to conveniently capture them for storing in case the user wants them.
*
* @param $ch resource The cURL handle from curl_init() that is executing.
* @param $str string The header received from the response. Should always be a single header at a time.
*
* @return int The length of the header. Used by cURL to report the header_size returned by the curl_getinfo() method.
*/
public function _headerfunction( $ch, $str )
{
$header = trim( $str );
// don't save blank lines we might be handed - there's usually one after the headers
if ( $header != '' ) {
// break the header up into field and value
$pieces = explode( ': ', $header, 2 );
if ( count( $pieces ) > 1 ) {
// if the header was a key: value format, store it keyed in the array
$this->headers[ $pieces[0] ] = $pieces[1];
}
else {
// some headers (like the HTTP version in use) aren't keyed, so just store it keyed as itself
$this->headers[ $pieces[0] ] = $pieces[0];
}
}
return strlen( $str );
}
public function get_response_body()
{
if ( ! $this->executed ) {
throw new Exception( _t( 'Unable to get response body. Request did not yet execute.' ) );
}
return $this->response_body;
}
public function get_response_headers()
{
if ( ! $this->executed ) {
throw new Exception( _t( 'Unable to get response headers. Request did not yet execute.' ) );
}
return $this->response_headers;
}
}
?>