mirror of
https://github.com/php-curl-class/php-curl-class.git
synced 2026-08-25 12:39:07 +00:00
1315 lines
36 KiB
PHP
1315 lines
36 KiB
PHP
<?php
|
|
|
|
namespace Curl;
|
|
|
|
class Curl
|
|
{
|
|
const VERSION = '6.0.0';
|
|
const DEFAULT_TIMEOUT = 30;
|
|
|
|
public static $RFC2616 = array(
|
|
// RFC2616: "any CHAR except CTLs or separators".
|
|
// CHAR = <any US-ASCII character (octets 0 - 127)>
|
|
// CTL = <any US-ASCII control character
|
|
// (octets 0 - 31) and DEL (127)>
|
|
// separators = "(" | ")" | "<" | ">" | "@"
|
|
// | "," | ";" | ":" | "\" | <">
|
|
// | "/" | "[" | "]" | "?" | "="
|
|
// | "{" | "}" | SP | HT
|
|
// SP = <US-ASCII SP, space (32)>
|
|
// HT = <US-ASCII HT, horizontal-tab (9)>
|
|
// <"> = <US-ASCII double-quote mark (34)>
|
|
'!', '#', '$', '%', '&', "'", '*', '+', '-', '.', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B',
|
|
'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X',
|
|
'Y', 'Z', '^', '_', '`', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q',
|
|
'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '|', '~',
|
|
);
|
|
public static $RFC6265 = array(
|
|
// RFC6265: "US-ASCII characters excluding CTLs, whitespace DQUOTE, comma, semicolon, and backslash".
|
|
// %x21
|
|
'!',
|
|
// %x23-2B
|
|
'#', '$', '%', '&', "'", '(', ')', '*', '+',
|
|
// %x2D-3A
|
|
'-', '.', '/', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', ':',
|
|
// %x3C-5B
|
|
'<', '=', '>', '?', '@', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q',
|
|
'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '[',
|
|
// %x5D-7E
|
|
']', '^', '_', '`', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r',
|
|
's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '{', '|', '}', '~',
|
|
);
|
|
|
|
public $curl;
|
|
public $id = null;
|
|
|
|
public $error = false;
|
|
public $errorCode = 0;
|
|
public $errorMessage = null;
|
|
|
|
public $curlError = false;
|
|
public $curlErrorCode = 0;
|
|
public $curlErrorMessage = null;
|
|
|
|
public $httpError = false;
|
|
public $httpStatusCode = 0;
|
|
public $httpErrorMessage = null;
|
|
|
|
public $baseUrl = null;
|
|
public $url = null;
|
|
public $requestHeaders = null;
|
|
public $responseHeaders = null;
|
|
public $rawResponseHeaders = '';
|
|
public $responseCookies = array();
|
|
public $response = null;
|
|
public $rawResponse = null;
|
|
|
|
public $beforeSendFunction = null;
|
|
public $downloadCompleteFunction = null;
|
|
public $successFunction = null;
|
|
public $errorFunction = null;
|
|
public $completeFunction = null;
|
|
public $fileHandle = null;
|
|
|
|
private $cookies = array();
|
|
private $headers = array();
|
|
private $options = array();
|
|
|
|
private $jsonDecoder = null;
|
|
private $jsonPattern = '/^(?:application|text)\/(?:[a-z]+(?:[\.-][0-9a-z]+){0,}[\+\.]|x-)?json(?:-[a-z]+)?/i';
|
|
private $xmlDecoder = null;
|
|
private $xmlPattern = '~^(?:text/|application/(?:atom\+|rss\+)?)xml~i';
|
|
private $defaultDecoder = null;
|
|
|
|
private static $deferredProperties = array(
|
|
'effectiveUrl',
|
|
'totalTime',
|
|
);
|
|
|
|
/**
|
|
* Construct
|
|
*
|
|
* @access public
|
|
* @param $base_url
|
|
* @throws \ErrorException
|
|
*/
|
|
public function __construct($base_url = null)
|
|
{
|
|
if (!extension_loaded('curl')) {
|
|
throw new \ErrorException('cURL library is not loaded');
|
|
}
|
|
|
|
$this->curl = curl_init();
|
|
$this->id = uniqid('', true);
|
|
$this->setDefaultUserAgent();
|
|
$this->setDefaultJsonDecoder();
|
|
$this->setDefaultXmlDecoder();
|
|
$this->setDefaultTimeout();
|
|
$this->setOpt(CURLINFO_HEADER_OUT, true);
|
|
$this->setOpt(CURLOPT_HEADERFUNCTION, array($this, 'headerCallback'));
|
|
$this->setOpt(CURLOPT_RETURNTRANSFER, true);
|
|
$this->headers = new CaseInsensitiveArray();
|
|
$this->setURL($base_url);
|
|
$this->rfc2616 = array_fill_keys(self::$RFC2616, true);
|
|
$this->rfc6265 = array_fill_keys(self::$RFC6265, true);
|
|
}
|
|
|
|
/**
|
|
* Before Send
|
|
*
|
|
* @access public
|
|
* @param $callback
|
|
*/
|
|
public function beforeSend($callback)
|
|
{
|
|
$this->beforeSendFunction = $callback;
|
|
}
|
|
|
|
/**
|
|
* Build Post Data
|
|
*
|
|
* @access public
|
|
* @param $data
|
|
*
|
|
* @return array|string
|
|
*/
|
|
public function buildPostData($data)
|
|
{
|
|
$binary_data = false;
|
|
if (is_array($data)) {
|
|
// Return JSON-encoded string when the request's content-type is JSON.
|
|
if (isset($this->headers['Content-Type']) &&
|
|
preg_match($this->jsonPattern, $this->headers['Content-Type'])) {
|
|
$json_str = json_encode($data);
|
|
if (!($json_str === false)) {
|
|
$data = $json_str;
|
|
}
|
|
} else {
|
|
// Manually build a single-dimensional array from a multi-dimensional array as using curl_setopt($ch,
|
|
// CURLOPT_POSTFIELDS, $data) doesn't correctly handle multi-dimensional arrays when files are
|
|
// referenced.
|
|
if (self::is_array_multidim($data)) {
|
|
$data = self::array_flatten_multidim($data);
|
|
}
|
|
|
|
// Modify array values to ensure any referenced files are properly handled depending on the support of
|
|
// the @filename API or CURLFile usage. This also fixes the warning "curl_setopt(): The usage of the
|
|
// @filename API for file uploading is deprecated. Please use the CURLFile class instead". Ignore
|
|
// non-file values prefixed with the @ character.
|
|
foreach ($data as $key => $value) {
|
|
if (is_string($value) && strpos($value, '@') === 0 && is_file(substr($value, 1))) {
|
|
$binary_data = true;
|
|
if (class_exists('CURLFile')) {
|
|
$data[$key] = new \CURLFile(substr($value, 1));
|
|
}
|
|
} elseif ($value instanceof \CURLFile) {
|
|
$binary_data = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!$binary_data && (is_array($data) || is_object($data))) {
|
|
$data = http_build_query($data, '', '&');
|
|
}
|
|
|
|
return $data;
|
|
}
|
|
|
|
/**
|
|
* Call
|
|
*
|
|
* @access public
|
|
*/
|
|
public function call()
|
|
{
|
|
$args = func_get_args();
|
|
$function = array_shift($args);
|
|
if (is_callable($function)) {
|
|
array_unshift($args, $this);
|
|
call_user_func_array($function, $args);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Close
|
|
*
|
|
* @access public
|
|
*/
|
|
public function close()
|
|
{
|
|
if (is_resource($this->curl)) {
|
|
curl_close($this->curl);
|
|
}
|
|
$this->options = null;
|
|
$this->jsonDecoder = null;
|
|
$this->xmlDecoder = null;
|
|
$this->defaultDecoder = null;
|
|
}
|
|
|
|
/**
|
|
* Complete
|
|
*
|
|
* @access public
|
|
* @param $callback
|
|
*/
|
|
public function complete($callback)
|
|
{
|
|
$this->completeFunction = $callback;
|
|
}
|
|
|
|
/**
|
|
* Progress
|
|
*
|
|
* @access public
|
|
* @param $callback
|
|
*/
|
|
public function progress($callback)
|
|
{
|
|
$this->setOpt(CURLOPT_PROGRESSFUNCTION, $callback);
|
|
$this->setOpt(CURLOPT_NOPROGRESS, false);
|
|
}
|
|
|
|
/**
|
|
* Delete
|
|
*
|
|
* @access public
|
|
* @param $url
|
|
* @param $query_parameters
|
|
* @param $data
|
|
*
|
|
* @return string
|
|
*/
|
|
public function delete($url, $query_parameters = array(), $data = array())
|
|
{
|
|
if (is_array($url)) {
|
|
$data = $query_parameters;
|
|
$query_parameters = $url;
|
|
$url = $this->baseUrl;
|
|
}
|
|
|
|
$this->setURL($url, $query_parameters);
|
|
$this->setOpt(CURLOPT_CUSTOMREQUEST, 'DELETE');
|
|
$this->setOpt(CURLOPT_POSTFIELDS, $this->buildPostData($data));
|
|
return $this->exec();
|
|
}
|
|
|
|
/**
|
|
* Download Complete
|
|
*
|
|
* @access private
|
|
* @param $fh
|
|
*/
|
|
private function downloadComplete($fh)
|
|
{
|
|
if (!$this->error && $this->downloadCompleteFunction) {
|
|
rewind($fh);
|
|
$this->call($this->downloadCompleteFunction, $fh);
|
|
$this->downloadCompleteFunction = null;
|
|
}
|
|
|
|
if (is_resource($fh)) {
|
|
fclose($fh);
|
|
}
|
|
|
|
// Fix "PHP Notice: Use of undefined constant STDOUT" when reading the
|
|
// PHP script from stdin. Using null causes "Warning: curl_setopt():
|
|
// supplied argument is not a valid File-Handle resource".
|
|
if (!defined('STDOUT')) {
|
|
define('STDOUT', fopen('php://stdout', 'w'));
|
|
}
|
|
|
|
// Reset CURLOPT_FILE with STDOUT to avoid: "curl_exec(): CURLOPT_FILE
|
|
// resource has gone away, resetting to default".
|
|
$this->setOpt(CURLOPT_FILE, STDOUT);
|
|
|
|
// Reset CURLOPT_RETURNTRANSFER to tell cURL to return subsequent
|
|
// responses as the return value of curl_exec(). Without this,
|
|
// curl_exec() will revert to returning boolean values.
|
|
$this->setOpt(CURLOPT_RETURNTRANSFER, true);
|
|
}
|
|
|
|
/**
|
|
* Download
|
|
*
|
|
* @access public
|
|
* @param $url
|
|
* @param $mixed_filename
|
|
*
|
|
* @return boolean
|
|
*/
|
|
public function download($url, $mixed_filename)
|
|
{
|
|
if (is_callable($mixed_filename)) {
|
|
$this->downloadCompleteFunction = $mixed_filename;
|
|
$fh = tmpfile();
|
|
} else {
|
|
$filename = $mixed_filename;
|
|
$fh = fopen($filename, 'wb');
|
|
}
|
|
|
|
$this->setOpt(CURLOPT_FILE, $fh);
|
|
$this->get($url);
|
|
$this->downloadComplete($fh);
|
|
|
|
return ! $this->error;
|
|
}
|
|
|
|
/**
|
|
* Error
|
|
*
|
|
* @access public
|
|
* @param $callback
|
|
*/
|
|
public function error($callback)
|
|
{
|
|
$this->errorFunction = $callback;
|
|
}
|
|
|
|
/**
|
|
* Exec
|
|
*
|
|
* @access public
|
|
* @param $ch
|
|
*
|
|
* @return mixed Returns the value provided by parseResponse.
|
|
*/
|
|
public function exec($ch = null)
|
|
{
|
|
if ($ch === null) {
|
|
$this->responseCookies = array();
|
|
$this->call($this->beforeSendFunction);
|
|
$this->rawResponse = curl_exec($this->curl);
|
|
$this->curlErrorCode = curl_errno($this->curl);
|
|
$this->curlErrorMessage = curl_error($this->curl);
|
|
} else {
|
|
$this->rawResponse = curl_multi_getcontent($ch);
|
|
$this->curlErrorMessage = curl_error($ch);
|
|
}
|
|
$this->curlError = !($this->curlErrorCode === 0);
|
|
|
|
// Include additional error code information in error message when possible.
|
|
if ($this->curlError && function_exists('curl_strerror')) {
|
|
$this->curlErrorMessage =
|
|
curl_strerror($this->curlErrorCode) . (
|
|
empty($this->curlErrorMessage) ? '' : ': ' . $this->curlErrorMessage
|
|
);
|
|
}
|
|
|
|
$this->httpStatusCode = $this->getInfo(CURLINFO_HTTP_CODE);
|
|
$this->httpError = in_array(floor($this->httpStatusCode / 100), array(4, 5));
|
|
$this->error = $this->curlError || $this->httpError;
|
|
$this->errorCode = $this->error ? ($this->curlError ? $this->curlErrorCode : $this->httpStatusCode) : 0;
|
|
|
|
// NOTE: CURLINFO_HEADER_OUT set to true is required for requestHeaders
|
|
// to not be empty (e.g. $curl->setOpt(CURLINFO_HEADER_OUT, true);).
|
|
if ($this->getOpt(CURLINFO_HEADER_OUT) === true) {
|
|
$this->requestHeaders = $this->parseRequestHeaders($this->getInfo(CURLINFO_HEADER_OUT));
|
|
}
|
|
$this->responseHeaders = $this->parseResponseHeaders($this->rawResponseHeaders);
|
|
$this->response = $this->parseResponse($this->responseHeaders, $this->rawResponse);
|
|
|
|
$this->httpErrorMessage = '';
|
|
if ($this->error) {
|
|
if (isset($this->responseHeaders['Status-Line'])) {
|
|
$this->httpErrorMessage = $this->responseHeaders['Status-Line'];
|
|
}
|
|
}
|
|
$this->errorMessage = $this->curlError ? $this->curlErrorMessage : $this->httpErrorMessage;
|
|
|
|
if (!$this->error) {
|
|
$this->call($this->successFunction);
|
|
} else {
|
|
$this->call($this->errorFunction);
|
|
}
|
|
|
|
$this->call($this->completeFunction);
|
|
|
|
// Close open file handles and reset the curl instance.
|
|
if (!($this->fileHandle === null)) {
|
|
$this->downloadComplete($this->fileHandle);
|
|
}
|
|
|
|
return $this->response;
|
|
}
|
|
|
|
/**
|
|
* Get
|
|
*
|
|
* @access public
|
|
* @param $url
|
|
* @param $data
|
|
*
|
|
* @return mixed Returns the value provided by exec.
|
|
*/
|
|
public function get($url, $data = array())
|
|
{
|
|
if (is_array($url)) {
|
|
$data = $url;
|
|
$url = $this->baseUrl;
|
|
}
|
|
$this->setURL($url, $data);
|
|
$this->setOpt(CURLOPT_CUSTOMREQUEST, 'GET');
|
|
$this->setOpt(CURLOPT_HTTPGET, true);
|
|
return $this->exec();
|
|
}
|
|
|
|
/**
|
|
* Get Info
|
|
*
|
|
* @access public
|
|
* @param $opt
|
|
*
|
|
* @return mixed
|
|
*/
|
|
public function getInfo($opt)
|
|
{
|
|
return curl_getinfo($this->curl, $opt);
|
|
}
|
|
|
|
/**
|
|
* Get Opt
|
|
*
|
|
* @access public
|
|
* @param $option
|
|
*
|
|
* @return mixed
|
|
*/
|
|
public function getOpt($option)
|
|
{
|
|
return isset($this->options[$option]) ? $this->options[$option] : null;
|
|
}
|
|
|
|
/**
|
|
* Head
|
|
*
|
|
* @access public
|
|
* @param $url
|
|
* @param $data
|
|
*
|
|
* @return string
|
|
*/
|
|
public function head($url, $data = array())
|
|
{
|
|
if (is_array($url)) {
|
|
$data = $url;
|
|
$url = $this->baseUrl;
|
|
}
|
|
$this->setURL($url, $data);
|
|
$this->setOpt(CURLOPT_CUSTOMREQUEST, 'HEAD');
|
|
$this->setOpt(CURLOPT_NOBODY, true);
|
|
return $this->exec();
|
|
}
|
|
|
|
/**
|
|
* Header Callback
|
|
*
|
|
* @access public
|
|
* @param $ch
|
|
* @param $header
|
|
*
|
|
* @return integer
|
|
*/
|
|
public function headerCallback($ch, $header)
|
|
{
|
|
if (preg_match('/^Set-Cookie:\s*([^=]+)=([^;]+)/mi', $header, $cookie) === 1) {
|
|
$this->responseCookies[$cookie[1]] = trim($cookie[2], " \n\r\t\0\x0B");
|
|
}
|
|
$this->rawResponseHeaders .= $header;
|
|
return strlen($header);
|
|
}
|
|
|
|
/**
|
|
* Options
|
|
*
|
|
* @access public
|
|
* @param $url
|
|
* @param $data
|
|
*
|
|
* @return string
|
|
*/
|
|
public function options($url, $data = array())
|
|
{
|
|
if (is_array($url)) {
|
|
$data = $url;
|
|
$url = $this->baseUrl;
|
|
}
|
|
$this->setURL($url, $data);
|
|
$this->unsetHeader('Content-Length');
|
|
$this->setOpt(CURLOPT_CUSTOMREQUEST, 'OPTIONS');
|
|
return $this->exec();
|
|
}
|
|
|
|
/**
|
|
* Patch
|
|
*
|
|
* @access public
|
|
* @param $url
|
|
* @param $data
|
|
*
|
|
* @return string
|
|
*/
|
|
public function patch($url, $data = array())
|
|
{
|
|
if (is_array($url)) {
|
|
$data = $url;
|
|
$url = $this->baseUrl;
|
|
}
|
|
|
|
if (is_array($data) && empty($data)) {
|
|
$this->unsetHeader('Content-Length');
|
|
}
|
|
|
|
$this->setURL($url);
|
|
$this->setOpt(CURLOPT_CUSTOMREQUEST, 'PATCH');
|
|
$this->setOpt(CURLOPT_POSTFIELDS, $this->buildPostData($data));
|
|
return $this->exec();
|
|
}
|
|
|
|
/**
|
|
* Post
|
|
*
|
|
* @access public
|
|
* @param $url
|
|
* @param $data
|
|
* @param $follow_303_with_post
|
|
* If true, will cause 303 redirections to be followed using a POST request (default: false).
|
|
* Notes:
|
|
* - Redirections are only followed if the CURLOPT_FOLLOWLOCATION option is set to true.
|
|
* - According to the HTTP specs (see [1]), a 303 redirection should be followed using
|
|
* the GET method. 301 and 302 must not.
|
|
* - In order to force a 303 redirection to be performed using the same method, the
|
|
* underlying cURL object must be set in a special state (the CURLOPT_CURSTOMREQUEST
|
|
* option must be set to the method to use after the redirection). Due to a limitation
|
|
* of the cURL extension of PHP < 5.5.11 ([2], [3]) and of HHVM, it is not possible
|
|
* to reset this option. Using these PHP engines, it is therefore impossible to
|
|
* restore this behavior on an existing php-curl-class Curl object.
|
|
*
|
|
* @return string
|
|
*
|
|
* [1] https://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.3.2
|
|
* [2] https://github.com/php/php-src/pull/531
|
|
* [3] http://php.net/ChangeLog-5.php#5.5.11
|
|
*/
|
|
public function post($url, $data = array(), $follow_303_with_post = false)
|
|
{
|
|
if (is_array($url)) {
|
|
$follow_303_with_post = (bool)$data;
|
|
$data = $url;
|
|
$url = $this->baseUrl;
|
|
}
|
|
|
|
$this->setURL($url);
|
|
|
|
if ($follow_303_with_post) {
|
|
$this->setOpt(CURLOPT_CUSTOMREQUEST, 'POST');
|
|
} else {
|
|
if (isset($this->options[CURLOPT_CUSTOMREQUEST])) {
|
|
if ((version_compare(PHP_VERSION, '5.5.11') < 0) || defined('HHVM_VERSION')) {
|
|
trigger_error(
|
|
'Due to technical limitations of PHP <= 5.5.11 and HHVM, it is not possible to '
|
|
. 'perform a post-redirect-get request using a php-curl-class Curl object that '
|
|
. 'has already been used to perform other types of requests. Either use a new '
|
|
. 'php-curl-class Curl object or upgrade your PHP engine.',
|
|
E_USER_ERROR
|
|
);
|
|
} else {
|
|
$this->setOpt(CURLOPT_CUSTOMREQUEST, null);
|
|
}
|
|
}
|
|
}
|
|
|
|
$this->setOpt(CURLOPT_POST, true);
|
|
$this->setOpt(CURLOPT_POSTFIELDS, $this->buildPostData($data));
|
|
return $this->exec();
|
|
}
|
|
|
|
/**
|
|
* Put
|
|
*
|
|
* @access public
|
|
* @param $url
|
|
* @param $data
|
|
*
|
|
* @return string
|
|
*/
|
|
public function put($url, $data = array())
|
|
{
|
|
if (is_array($url)) {
|
|
$data = $url;
|
|
$url = $this->baseUrl;
|
|
}
|
|
$this->setURL($url);
|
|
$this->setOpt(CURLOPT_CUSTOMREQUEST, 'PUT');
|
|
$put_data = $this->buildPostData($data);
|
|
if (empty($this->options[CURLOPT_INFILE]) && empty($this->options[CURLOPT_INFILESIZE])) {
|
|
if (is_string($put_data)) {
|
|
$this->setHeader('Content-Length', strlen($put_data));
|
|
}
|
|
}
|
|
if (!empty($put_data)) {
|
|
$this->setOpt(CURLOPT_POSTFIELDS, $put_data);
|
|
}
|
|
return $this->exec();
|
|
}
|
|
|
|
/**
|
|
* Search
|
|
*
|
|
* @access public
|
|
* @param $url
|
|
* @param $data
|
|
*
|
|
* @return string
|
|
*/
|
|
public function search($url, $data = array())
|
|
{
|
|
if (is_array($url)) {
|
|
$data = $url;
|
|
$url = $this->baseUrl;
|
|
}
|
|
$this->setURL($url);
|
|
$this->setOpt(CURLOPT_CUSTOMREQUEST, 'SEARCH');
|
|
$put_data = $this->buildPostData($data);
|
|
if (empty($this->options[CURLOPT_INFILE]) && empty($this->options[CURLOPT_INFILESIZE])) {
|
|
if (is_string($put_data)) {
|
|
$this->setHeader('Content-Length', strlen($put_data));
|
|
}
|
|
}
|
|
if (!empty($put_data)) {
|
|
$this->setOpt(CURLOPT_POSTFIELDS, $put_data);
|
|
}
|
|
return $this->exec();
|
|
}
|
|
|
|
/**
|
|
* Set Basic Authentication
|
|
*
|
|
* @access public
|
|
* @param $username
|
|
* @param $password
|
|
*/
|
|
public function setBasicAuthentication($username, $password = '')
|
|
{
|
|
$this->setOpt(CURLOPT_HTTPAUTH, CURLAUTH_BASIC);
|
|
$this->setOpt(CURLOPT_USERPWD, $username . ':' . $password);
|
|
}
|
|
|
|
/**
|
|
* Set Digest Authentication
|
|
*
|
|
* @access public
|
|
* @param $username
|
|
* @param $password
|
|
*/
|
|
public function setDigestAuthentication($username, $password = '')
|
|
{
|
|
$this->setOpt(CURLOPT_HTTPAUTH, CURLAUTH_DIGEST);
|
|
$this->setOpt(CURLOPT_USERPWD, $username . ':' . $password);
|
|
}
|
|
|
|
/**
|
|
* Set Cookie
|
|
*
|
|
* @access public
|
|
* @param $key
|
|
* @param $value
|
|
*/
|
|
public function setCookie($key, $value)
|
|
{
|
|
$name_chars = array();
|
|
foreach (str_split($key) as $name_char) {
|
|
if (!isset($this->rfc2616[$name_char])) {
|
|
$name_chars[] = rawurlencode($name_char);
|
|
} else {
|
|
$name_chars[] = $name_char;
|
|
}
|
|
}
|
|
|
|
$value_chars = array();
|
|
foreach (str_split($value) as $value_char) {
|
|
if (!isset($this->rfc6265[$value_char])) {
|
|
$value_chars[] = rawurlencode($value_char);
|
|
} else {
|
|
$value_chars[] = $value_char;
|
|
}
|
|
}
|
|
|
|
$this->cookies[implode('', $name_chars)] = implode('', $value_chars);
|
|
$this->setOpt(CURLOPT_COOKIE, implode('; ', array_map(function ($k, $v) {
|
|
return $k . '=' . $v;
|
|
}, array_keys($this->cookies), array_values($this->cookies))));
|
|
}
|
|
|
|
/**
|
|
* Get cookie.
|
|
*
|
|
* @access public
|
|
* @param $key
|
|
*
|
|
* @return mixed
|
|
*/
|
|
public function getCookie($key)
|
|
{
|
|
return $this->getResponseCookie($key);
|
|
}
|
|
|
|
/**
|
|
* Get response cookie.
|
|
*
|
|
* @access public
|
|
* @param $key
|
|
*
|
|
* @return mixed
|
|
*/
|
|
public function getResponseCookie($key)
|
|
{
|
|
return isset($this->responseCookies[$key]) ? $this->responseCookies[$key] : null;
|
|
}
|
|
|
|
/**
|
|
* Get response cookies.
|
|
*
|
|
* @access public
|
|
*
|
|
* @return array
|
|
*/
|
|
public function getResponseCookies()
|
|
{
|
|
return $this->responseCookies;
|
|
}
|
|
|
|
/**
|
|
* Set Port
|
|
*
|
|
* @access public
|
|
* @param $port
|
|
*/
|
|
public function setPort($port)
|
|
{
|
|
$this->setOpt(CURLOPT_PORT, intval($port));
|
|
}
|
|
|
|
/**
|
|
* Set Connect Timeout
|
|
*
|
|
* @access public
|
|
* @param $seconds
|
|
*/
|
|
public function setConnectTimeout($seconds)
|
|
{
|
|
$this->setOpt(CURLOPT_CONNECTTIMEOUT, $seconds);
|
|
}
|
|
|
|
/**
|
|
* Set Cookie String
|
|
*
|
|
* @access public
|
|
* @param $string
|
|
*
|
|
* @return bool
|
|
*/
|
|
public function setCookieString($string)
|
|
{
|
|
return $this->setOpt(CURLOPT_COOKIE, $string);
|
|
}
|
|
|
|
/**
|
|
* Set Cookie File
|
|
*
|
|
* @access public
|
|
* @param $cookie_file
|
|
*/
|
|
public function setCookieFile($cookie_file)
|
|
{
|
|
$this->setOpt(CURLOPT_COOKIEFILE, $cookie_file);
|
|
}
|
|
|
|
/**
|
|
* Set Cookie Jar
|
|
*
|
|
* @access public
|
|
* @param $cookie_jar
|
|
*/
|
|
public function setCookieJar($cookie_jar)
|
|
{
|
|
$this->setOpt(CURLOPT_COOKIEJAR, $cookie_jar);
|
|
}
|
|
|
|
/**
|
|
* Set Default JSON Decoder
|
|
*
|
|
* @access public
|
|
* @param $assoc
|
|
* @param $depth
|
|
* @param $options
|
|
*/
|
|
public function setDefaultJsonDecoder()
|
|
{
|
|
$args = func_get_args();
|
|
$this->jsonDecoder = function ($response) use ($args) {
|
|
array_unshift($args, $response);
|
|
|
|
// Call json_decode() without the $options parameter in PHP
|
|
// versions less than 5.4.0 as the $options parameter was added in
|
|
// PHP version 5.4.0.
|
|
if (version_compare(PHP_VERSION, '5.4.0', '<')) {
|
|
$args = array_slice($args, 0, 3);
|
|
}
|
|
|
|
$json_obj = call_user_func_array('json_decode', $args);
|
|
if (!($json_obj === null)) {
|
|
$response = $json_obj;
|
|
}
|
|
return $response;
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Set Default XML Decoder
|
|
*
|
|
* @access public
|
|
*/
|
|
public function setDefaultXmlDecoder()
|
|
{
|
|
$this->xmlDecoder = function ($response) {
|
|
$xml_obj = @simplexml_load_string($response);
|
|
if (!($xml_obj === false)) {
|
|
$response = $xml_obj;
|
|
}
|
|
return $response;
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Set Default Decoder
|
|
*
|
|
* @access public
|
|
* @param $decoder string|callable
|
|
*/
|
|
public function setDefaultDecoder($decoder = 'json')
|
|
{
|
|
if (is_callable($decoder)) {
|
|
$this->defaultDecoder = $decoder;
|
|
} else {
|
|
if ($decoder === 'json') {
|
|
$this->defaultDecoder = $this->jsonDecoder;
|
|
} elseif ($decoder === 'xml') {
|
|
$this->defaultDecoder = $this->xmlDecoder;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Set Default Timeout
|
|
*
|
|
* @access public
|
|
*/
|
|
public function setDefaultTimeout()
|
|
{
|
|
$this->setTimeout(self::DEFAULT_TIMEOUT);
|
|
}
|
|
|
|
/**
|
|
* Set Default User Agent
|
|
*
|
|
* @access public
|
|
*/
|
|
public function setDefaultUserAgent()
|
|
{
|
|
$user_agent = 'PHP-Curl-Class/' . self::VERSION . ' (+https://github.com/php-curl-class/php-curl-class)';
|
|
$user_agent .= ' PHP/' . PHP_VERSION;
|
|
$curl_version = curl_version();
|
|
$user_agent .= ' curl/' . $curl_version['version'];
|
|
$this->setUserAgent($user_agent);
|
|
}
|
|
|
|
/**
|
|
* Set Header
|
|
*
|
|
* @access public
|
|
* @param $key
|
|
* @param $value
|
|
*/
|
|
public function setHeader($key, $value)
|
|
{
|
|
$this->headers[$key] = $value;
|
|
$headers = array();
|
|
foreach ($this->headers as $key => $value) {
|
|
$headers[] = $key . ': ' . $value;
|
|
}
|
|
$this->setOpt(CURLOPT_HTTPHEADER, $headers);
|
|
}
|
|
|
|
/**
|
|
* Set JSON Decoder
|
|
*
|
|
* @access public
|
|
* @param $function
|
|
*/
|
|
public function setJsonDecoder($function)
|
|
{
|
|
if (is_callable($function)) {
|
|
$this->jsonDecoder = $function;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Set XML Decoder
|
|
*
|
|
* @access public
|
|
* @param $function
|
|
*/
|
|
public function setXmlDecoder($function)
|
|
{
|
|
if (is_callable($function)) {
|
|
$this->xmlDecoder = $function;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Set Opt
|
|
*
|
|
* @access public
|
|
* @param $option
|
|
* @param $value
|
|
*
|
|
* @return boolean
|
|
*/
|
|
public function setOpt($option, $value)
|
|
{
|
|
$required_options = array(
|
|
CURLOPT_RETURNTRANSFER => 'CURLOPT_RETURNTRANSFER',
|
|
);
|
|
|
|
if (in_array($option, array_keys($required_options), true) && !($value === true)) {
|
|
trigger_error($required_options[$option] . ' is a required option', E_USER_WARNING);
|
|
}
|
|
|
|
$success = curl_setopt($this->curl, $option, $value);
|
|
if ($success) {
|
|
$this->options[$option] = $value;
|
|
}
|
|
return $success;
|
|
}
|
|
|
|
/**
|
|
* Set Opts
|
|
*
|
|
* @access public
|
|
* @param $options
|
|
*
|
|
* @return boolean
|
|
* Returns true if all options were successfully set. If an option could not be successfully set, false is
|
|
* immediately returned, ignoring any future options in the options array. Similar to curl_setopt_array().
|
|
*/
|
|
public function setOpts($options)
|
|
{
|
|
foreach ($options as $option => $value) {
|
|
if (!$this->setOpt($option, $value)) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Set Referer
|
|
*
|
|
* @access public
|
|
* @param $referer
|
|
*/
|
|
public function setReferer($referer)
|
|
{
|
|
$this->setReferrer($referer);
|
|
}
|
|
|
|
/**
|
|
* Set Referrer
|
|
*
|
|
* @access public
|
|
* @param $referrer
|
|
*/
|
|
public function setReferrer($referrer)
|
|
{
|
|
$this->setOpt(CURLOPT_REFERER, $referrer);
|
|
}
|
|
|
|
/**
|
|
* Set Timeout
|
|
*
|
|
* @access public
|
|
* @param $seconds
|
|
*/
|
|
public function setTimeout($seconds)
|
|
{
|
|
$this->setOpt(CURLOPT_TIMEOUT, $seconds);
|
|
}
|
|
|
|
/**
|
|
* Set Url
|
|
*
|
|
* @access public
|
|
* @param $url
|
|
* @param $data
|
|
*/
|
|
public function setURL($url, $data = array())
|
|
{
|
|
$this->baseUrl = $url;
|
|
$this->url = $this->buildURL($url, $data);
|
|
$this->setOpt(CURLOPT_URL, $this->url);
|
|
}
|
|
|
|
/**
|
|
* Set User Agent
|
|
*
|
|
* @access public
|
|
* @param $user_agent
|
|
*/
|
|
public function setUserAgent($user_agent)
|
|
{
|
|
$this->setOpt(CURLOPT_USERAGENT, $user_agent);
|
|
}
|
|
|
|
/**
|
|
* Success
|
|
*
|
|
* @access public
|
|
* @param $callback
|
|
*/
|
|
public function success($callback)
|
|
{
|
|
$this->successFunction = $callback;
|
|
}
|
|
|
|
/**
|
|
* Unset Header
|
|
*
|
|
* @access public
|
|
* @param $key
|
|
*/
|
|
public function unsetHeader($key)
|
|
{
|
|
$this->setHeader($key, '');
|
|
unset($this->headers[$key]);
|
|
}
|
|
|
|
/**
|
|
* Verbose
|
|
*
|
|
* @access public
|
|
* @param bool $on
|
|
* @param resource $output
|
|
*/
|
|
public function verbose($on = true, $output = STDERR)
|
|
{
|
|
// Turn off CURLINFO_HEADER_OUT for verbose to work. This has the side
|
|
// effect of causing Curl::requestHeaders to be empty.
|
|
if ($on) {
|
|
$this->setOpt(CURLINFO_HEADER_OUT, false);
|
|
}
|
|
$this->setOpt(CURLOPT_VERBOSE, $on);
|
|
$this->setOpt(CURLOPT_STDERR, $output);
|
|
}
|
|
|
|
/**
|
|
* Destruct
|
|
*
|
|
* @access public
|
|
*/
|
|
public function __destruct()
|
|
{
|
|
$this->close();
|
|
}
|
|
|
|
public function __get($name)
|
|
{
|
|
$return = null;
|
|
if (in_array($name, self::$deferredProperties) && is_callable(array($this, $getter = '__get_' . $name))) {
|
|
$return = $this->$name = $this->$getter();
|
|
}
|
|
return $return;
|
|
}
|
|
|
|
/**
|
|
* Get Effective Url
|
|
*
|
|
* @access private
|
|
*/
|
|
private function __get_effectiveUrl()
|
|
{
|
|
return $this->getInfo(CURLINFO_EFFECTIVE_URL);
|
|
}
|
|
|
|
/**
|
|
* Get Total Time
|
|
*
|
|
* @access private
|
|
*/
|
|
private function __get_totalTime()
|
|
{
|
|
return $this->getInfo(CURLINFO_TOTAL_TIME);
|
|
}
|
|
|
|
/**
|
|
* Build Url
|
|
*
|
|
* @access private
|
|
* @param $url
|
|
* @param $data
|
|
*
|
|
* @return string
|
|
*/
|
|
private function buildURL($url, $data = array())
|
|
{
|
|
return $url . (empty($data) ? '' : '?' . http_build_query($data, '', '&'));
|
|
}
|
|
|
|
/**
|
|
* Parse Headers
|
|
*
|
|
* @access private
|
|
* @param $raw_headers
|
|
*
|
|
* @return array
|
|
*/
|
|
private function parseHeaders($raw_headers)
|
|
{
|
|
$raw_headers = preg_split('/\r\n/', $raw_headers, null, PREG_SPLIT_NO_EMPTY);
|
|
$http_headers = new CaseInsensitiveArray();
|
|
|
|
$raw_headers_count = count($raw_headers);
|
|
for ($i = 1; $i < $raw_headers_count; $i++) {
|
|
list($key, $value) = explode(':', $raw_headers[$i], 2);
|
|
$key = trim($key);
|
|
$value = trim($value);
|
|
// Use isset() as array_key_exists() and ArrayAccess are not compatible.
|
|
if (isset($http_headers[$key])) {
|
|
$http_headers[$key] .= ',' . $value;
|
|
} else {
|
|
$http_headers[$key] = $value;
|
|
}
|
|
}
|
|
|
|
return array(isset($raw_headers['0']) ? $raw_headers['0'] : '', $http_headers);
|
|
}
|
|
|
|
/**
|
|
* Parse Request Headers
|
|
*
|
|
* @access private
|
|
* @param $raw_headers
|
|
*
|
|
* @return array
|
|
*/
|
|
private function parseRequestHeaders($raw_headers)
|
|
{
|
|
$request_headers = new CaseInsensitiveArray();
|
|
list($first_line, $headers) = $this->parseHeaders($raw_headers);
|
|
$request_headers['Request-Line'] = $first_line;
|
|
foreach ($headers as $key => $value) {
|
|
$request_headers[$key] = $value;
|
|
}
|
|
return $request_headers;
|
|
}
|
|
|
|
/**
|
|
* Parse Response
|
|
*
|
|
* @access private
|
|
* @param $response_headers
|
|
* @param $raw_response
|
|
*
|
|
* @return mixed
|
|
* Provided the content-type is determined to be json or xml:
|
|
* Returns stdClass object when the default json decoder is used and the content-type is json.
|
|
* Returns SimpleXMLElement object when the default xml decoder is used and the content-type is xml.
|
|
*/
|
|
private function parseResponse($response_headers, $raw_response)
|
|
{
|
|
$response = $raw_response;
|
|
if (isset($response_headers['Content-Type'])) {
|
|
if (preg_match($this->jsonPattern, $response_headers['Content-Type'])) {
|
|
$json_decoder = $this->jsonDecoder;
|
|
if (is_callable($json_decoder)) {
|
|
$response = $json_decoder($response);
|
|
}
|
|
} elseif (preg_match($this->xmlPattern, $response_headers['Content-Type'])) {
|
|
$xml_decoder = $this->xmlDecoder;
|
|
if (is_callable($xml_decoder)) {
|
|
$response = $xml_decoder($response);
|
|
}
|
|
} else {
|
|
$decoder = $this->defaultDecoder;
|
|
if (is_callable($decoder)) {
|
|
$response = $decoder($response);
|
|
}
|
|
}
|
|
}
|
|
|
|
return $response;
|
|
}
|
|
|
|
/**
|
|
* Parse Response Headers
|
|
*
|
|
* @access private
|
|
* @param $raw_response_headers
|
|
*
|
|
* @return array
|
|
*/
|
|
private function parseResponseHeaders($raw_response_headers)
|
|
{
|
|
$response_header_array = explode("\r\n\r\n", $raw_response_headers);
|
|
$response_header = '';
|
|
for ($i = count($response_header_array) - 1; $i >= 0; $i--) {
|
|
if (stripos($response_header_array[$i], 'HTTP/') === 0) {
|
|
$response_header = $response_header_array[$i];
|
|
break;
|
|
}
|
|
}
|
|
|
|
$response_headers = new CaseInsensitiveArray();
|
|
list($first_line, $headers) = $this->parseHeaders($response_header);
|
|
$response_headers['Status-Line'] = $first_line;
|
|
foreach ($headers as $key => $value) {
|
|
$response_headers[$key] = $value;
|
|
}
|
|
return $response_headers;
|
|
}
|
|
|
|
/**
|
|
* Is Array Assoc
|
|
*
|
|
* @access public
|
|
* @param $array
|
|
*
|
|
* @return boolean
|
|
*/
|
|
public static function is_array_assoc($array)
|
|
{
|
|
return (bool)count(array_filter(array_keys($array), 'is_string'));
|
|
}
|
|
|
|
/**
|
|
* Is Array Multidim
|
|
*
|
|
* @access public
|
|
* @param $array
|
|
*
|
|
* @return boolean
|
|
*/
|
|
public static function is_array_multidim($array)
|
|
{
|
|
if (!is_array($array)) {
|
|
return false;
|
|
}
|
|
|
|
return (bool)count(array_filter($array, 'is_array'));
|
|
}
|
|
|
|
/**
|
|
* Array Flatten Multidim
|
|
*
|
|
* @access public
|
|
* @param $array
|
|
* @param $prefix
|
|
*
|
|
* @return array
|
|
*/
|
|
public static function array_flatten_multidim($array, $prefix = false)
|
|
{
|
|
$return = array();
|
|
if (is_array($array) || is_object($array)) {
|
|
if (empty($array)) {
|
|
$return[$prefix] = '';
|
|
} else {
|
|
foreach ($array as $key => $value) {
|
|
if (is_scalar($value)) {
|
|
if ($prefix) {
|
|
$return[$prefix . '[' . $key . ']'] = $value;
|
|
} else {
|
|
$return[$key] = $value;
|
|
}
|
|
} else {
|
|
if ($value instanceof \CURLFile) {
|
|
$return[$key] = $value;
|
|
} else {
|
|
$return = array_merge(
|
|
$return,
|
|
self::array_flatten_multidim(
|
|
$value,
|
|
$prefix ? $prefix . '[' . $key . ']' : $key
|
|
)
|
|
);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
} elseif ($array === null) {
|
|
$return[$prefix] = $array;
|
|
}
|
|
return $return;
|
|
}
|
|
}
|