323 lines
11 KiB
PHP
Raw Normal View History

<?php
/**
* This file is part of the BeSimpleSoapClient.
*
* (c) Christian Kerl <christian-kerl@web.de>
* (c) Francis Besset <francis.besset@gmail.com>
* (c) Andreas Schamberger <mail@andreass.net>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*
* @link https://github.com/BeSimple/BeSimpleSoapClient
*/
namespace BeSimple\SoapClient;
/**
* cURL wrapper class for doing HTTP requests that uses the soap class options.
*
* @author Andreas Schamberger
*/
class Curl
{
/**
* HTTP User Agent.
*
* @var string
*/
const USER_AGENT = 'PHP-SOAP/\BeSimple\SoapClient';
/**
* Curl resource.
*
* @var resource
*/
private $ch;
/**
* Maximum number of location headers to follow.
*
* @var int
*/
private $followLocationMaxRedirects;
/**
* Request response data.
*
* @var string
*/
private $response;
/**
* Constructor.
*
* @param array $options
* @param int $followLocationMaxRedirects
*/
public function __construct( array $options, $followLocationMaxRedirects = 10 )
{
// set the default HTTP user agent
if ( !isset( $options['user_agent'] ) )
{
$options['user_agent'] = self::USER_AGENT;
}
$this->followLocationMaxRedirects = $followLocationMaxRedirects;
// make http request
$this->ch = curl_init();
$curlOptions = array(
CURLOPT_ENCODING => '',
CURLOPT_SSL_VERIFYPEER => false,
CURLOPT_FAILONERROR => false,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_HEADER => true,
CURLOPT_USERAGENT => $options['user_agent'],
CURLINFO_HEADER_OUT => true,
);
curl_setopt_array( $this->ch, $curlOptions );
if ( isset( $options['compression'] ) && !( $options['compression'] & SOAP_COMPRESSION_ACCEPT ) )
{
curl_setopt( $this->ch, CURLOPT_ENCODING, 'identity' );
}
if ( isset( $options['connection_timeout'] ) )
{
curl_setopt( $this->ch, CURLOPT_CONNECTTIMEOUT, $options['connection_timeout'] );
}
if ( isset( $options['proxy_host'] ) )
{
$port = isset( $options['proxy_port'] ) ? $options['proxy_port'] : 8080;
curl_setopt( $this->ch, CURLOPT_PROXY, $options['proxy_host'] . ':' . $port );
}
if ( isset( $options['proxy_user'] ) )
{
curl_setopt( $this->ch, CURLOPT_PROXYUSERPWD, $options['proxy_user'] . ':' . $options['proxy_password'] );
}
if ( isset( $options['login'] ) )
{
curl_setopt( $this->ch, CURLOPT_HTTPAUTH, CURLAUTH_ANY );
curl_setopt( $this->ch, CURLOPT_USERPWD, $options['login'].':'.$options['password'] );
}
if ( isset( $options['local_cert'] ) )
{
curl_setopt( $this->ch, CURLOPT_SSLCERT, $options['local_cert'] );
curl_setopt( $this->ch, CURLOPT_SSLCERTPASSWD, $options['passphrase'] );
}
}
/**
* Destructor.
*/
public function __destruct()
{
curl_close( $this->ch );
}
/**
* Execute HTTP request.
* Returns true if request was successfull.
*
* @param string $location
* @param string $request
* @param array $requestHeaders
* @return bool
*/
public function exec( $location, $request = null, $requestHeaders = array() )
{
curl_setopt( $this->ch, CURLOPT_URL, $location);
if ( !is_null( $request ) )
{
curl_setopt( $this->ch, CURLOPT_POST, true );
curl_setopt( $this->ch, CURLOPT_POSTFIELDS, $request );
}
if ( count( $requestHeaders ) > 0 )
{
curl_setopt( $this->ch, CURLOPT_HTTPHEADER, $requestHeaders );
}
$this->response = $this->execManualRedirect( $this->followLocationMaxRedirects );
return ( $this->response === false ) ? false : true;
}
/**
* Custom curl_exec wrapper that allows to follow redirects when specific
* http response code is set. SOAP only allows 307.
*
* @param resource $ch
* @param int $maxRedirects
* @param int $redirects
* @return mixed
*/
private function execManualRedirect( $redirects = 0 )
{
if ( $redirects > $this->followLocationMaxRedirects )
{
// TODO Redirection limit reached, aborting
return false;
}
curl_setopt( $this->ch, CURLOPT_HEADER, true );
curl_setopt( $this->ch, CURLOPT_RETURNTRANSFER, true );
$response = curl_exec( $this->ch );
$httpResponseCode = curl_getinfo( $this->ch, CURLINFO_HTTP_CODE );
if ( $httpResponseCode == 307 )
{
$headerSize = curl_getinfo( $this->ch, CURLINFO_HEADER_SIZE );
$header = substr( $response, 0, $headerSize );
$matches = array();
preg_match( '/Location:(.*?)\n/', $header, $matches );
$url = trim( array_pop( $matches ) );
// @parse_url to suppress E_WARNING for invalid urls
if ( ( $url = @parse_url( $url ) ) !== false )
{
$lastUrl = parse_url( curl_getinfo( $this->ch, CURLINFO_EFFECTIVE_URL ) );
if ( !isset( $url['scheme'] ) )
{
$url['scheme'] = $lastUrl['scheme'];
}
if ( !isset( $url['host'] ) )
{
$url['host'] = $lastUrl['host'];
}
if ( !isset( $url['path'] ) )
{
$url['path'] = $lastUrl['path'];
}
$newUrl = $url['scheme'] . '://' . $url['host'] . $url['path'] . ( $url['query'] ? '?' . $url['query'] : '' );
curl_setopt( $this->ch, CURLOPT_URL, $newUrl );
return $this->execManualRedirect( $redirects++ );
}
}
return $response;
}
/**
* Error code mapping from cURL error codes to PHP ext/soap error messages
* (where applicable)
*
* http://curl.haxx.se/libcurl/c/libcurl-errors.html
*
* @var array(int=>string)
*/
protected function getErrorCodeMapping()
{
return array(
1 => 'Unknown protocol. Only http and https are allowed.', //CURLE_UNSUPPORTED_PROTOCOL
3 => 'Unable to parse URL', //CURLE_URL_MALFORMAT
5 => 'Could not connect to host', //CURLE_COULDNT_RESOLVE_PROXY
6 => 'Could not connect to host', //CURLE_COULDNT_RESOLVE_HOST
7 => 'Could not connect to host', //CURLE_COULDNT_CONNECT
9 => 'Could not connect to host', //CURLE_REMOTE_ACCESS_DENIED
28 => 'Failed Sending HTTP SOAP request', //CURLE_OPERATION_TIMEDOUT
35 => 'Could not connect to host', //CURLE_SSL_CONNECT_ERROR
41 => 'Can\'t uncompress compressed response', //CURLE_FUNCTION_NOT_FOUND
51 => 'Could not connect to host', //CURLE_PEER_FAILED_VERIFICATION
52 => 'Error Fetching http body, No Content-Length, connection closed or chunked data', //CURLE_GOT_NOTHING
53 => 'SSL support is not available in this build', //CURLE_SSL_ENGINE_NOTFOUND
54 => 'SSL support is not available in this build', //CURLE_SSL_ENGINE_SETFAILED
55 => 'Failed Sending HTTP SOAP request', //CURLE_SEND_ERROR
56 => 'Error Fetching http body, No Content-Length, connection closed or chunked data', //CURLE_RECV_ERROR
58 => 'Could not connect to host', //CURLE_SSL_CERTPROBLEM
59 => 'Could not connect to host', //CURLE_SSL_CIPHER
60 => 'Could not connect to host', //CURLE_SSL_CACERT
61 => 'Unknown Content-Encoding', //CURLE_BAD_CONTENT_ENCODING
65 => 'Failed Sending HTTP SOAP request', //CURLE_SEND_FAIL_REWIND
66 => 'SSL support is not available in this build', //CURLE_SSL_ENGINE_INITFAILED
67 => 'Could not connect to host', //CURLE_LOGIN_DENIED
77 => 'Could not connect to host', //CURLE_SSL_CACERT_BADFILE
80 => 'Error Fetching http body, No Content-Length, connection closed or chunked data', //CURLE_SSL_SHUTDOWN_FAILED
);
}
/**
* Gets the curl error message.
*
* @return string
*/
public function getErrorMessage()
{
$errorCodeMapping = $this->getErrorCodeMapping();
$errorNumber = curl_errno( $this->ch );
if ( isset( $errorCodeMapping[$errorNumber] ) )
{
return $errorCodeMapping[$errorNumber];
}
return curl_error( $this->ch );
}
/**
* Gets the request headers as a string.
*
* @return string
*/
public function getRequestHeaders()
{
return curl_getinfo( $this->ch, CURLINFO_HEADER_OUT );
}
/**
* Gets the whole response (including headers) as a string.
*
* @return string
*/
public function getResponse()
{
return $this->response;
}
/**
* Gets the response body as a string.
*
* @return string
*/
public function getResponseBody()
{
$headerSize = curl_getinfo( $this->ch, CURLINFO_HEADER_SIZE );
return substr( $this->response, $headerSize );
}
/**
* Gets the response content type.
*
* @return string
*/
public function getResponseContentType()
{
return curl_getinfo( $this->ch, CURLINFO_CONTENT_TYPE );
}
/**
* Gets the response headers as a string.
*
* @return string
*/
public function getResponseHeaders()
{
$headerSize = curl_getinfo( $this->ch, CURLINFO_HEADER_SIZE );
return substr( $this->response, 0, $headerSize );
}
/**
* Gets the response http status code.
*
* @return string
*/
public function getResponseStatusCode()
{
return curl_getinfo( $this->ch, CURLINFO_HTTP_CODE );
}
/**
* Gets the response http status message.
*
* @return string
*/
public function getResponseStatusMessage()
{
preg_match( '/HTTP\/(1\.[0-1]+) ([0-9]{3}) (.*)/', $this->response, $matches );
return trim( array_pop( $matches ) );
}
}