summaryrefslogtreecommitdiff
path: root/vendor/guzzle/guzzle/src/Guzzle/Http/Curl/CurlHandle.php
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/guzzle/guzzle/src/Guzzle/Http/Curl/CurlHandle.php')
-rw-r--r--vendor/guzzle/guzzle/src/Guzzle/Http/Curl/CurlHandle.php464
1 files changed, 464 insertions, 0 deletions
diff --git a/vendor/guzzle/guzzle/src/Guzzle/Http/Curl/CurlHandle.php b/vendor/guzzle/guzzle/src/Guzzle/Http/Curl/CurlHandle.php
new file mode 100644
index 0000000..efba5d1
--- /dev/null
+++ b/vendor/guzzle/guzzle/src/Guzzle/Http/Curl/CurlHandle.php
@@ -0,0 +1,464 @@
+<?php
+
+namespace Guzzle\Http\Curl;
+
+use Guzzle\Common\Exception\InvalidArgumentException;
+use Guzzle\Common\Exception\RuntimeException;
+use Guzzle\Common\Collection;
+use Guzzle\Http\Message\EntityEnclosingRequest;
+use Guzzle\Http\Message\RequestInterface;
+use Guzzle\Parser\ParserRegistry;
+use Guzzle\Http\Url;
+
+/**
+ * Immutable wrapper for a cURL handle
+ */
+class CurlHandle
+{
+ const BODY_AS_STRING = 'body_as_string';
+ const PROGRESS = 'progress';
+ const DEBUG = 'debug';
+
+ /** @var Collection Curl options */
+ protected $options;
+
+ /** @var resource Curl resource handle */
+ protected $handle;
+
+ /** @var int CURLE_* error */
+ protected $errorNo = CURLE_OK;
+
+ /**
+ * Factory method to create a new curl handle based on an HTTP request.
+ *
+ * There are some helpful options you can set to enable specific behavior:
+ * - debug: Set to true to enable cURL debug functionality to track the actual headers sent over the wire.
+ * - progress: Set to true to enable progress function callbacks.
+ *
+ * @param RequestInterface $request Request
+ *
+ * @return CurlHandle
+ * @throws RuntimeException
+ */
+ public static function factory(RequestInterface $request)
+ {
+ $requestCurlOptions = $request->getCurlOptions();
+ $mediator = new RequestMediator($request, $requestCurlOptions->get('emit_io'));
+ $tempContentLength = null;
+ $method = $request->getMethod();
+ $bodyAsString = $requestCurlOptions->get(self::BODY_AS_STRING);
+
+ // Prepare url
+ $url = (string)$request->getUrl();
+ if(($pos = strpos($url, '#')) !== false ){
+ // strip fragment from url
+ $url = substr($url, 0, $pos);
+ }
+
+ // Array of default cURL options.
+ $curlOptions = array(
+ CURLOPT_URL => $url,
+ CURLOPT_CONNECTTIMEOUT => 150,
+ CURLOPT_RETURNTRANSFER => false,
+ CURLOPT_HEADER => false,
+ CURLOPT_PORT => $request->getPort(),
+ CURLOPT_HTTPHEADER => array(),
+ CURLOPT_WRITEFUNCTION => array($mediator, 'writeResponseBody'),
+ CURLOPT_HEADERFUNCTION => array($mediator, 'receiveResponseHeader'),
+ CURLOPT_HTTP_VERSION => $request->getProtocolVersion() === '1.0'
+ ? CURL_HTTP_VERSION_1_0 : CURL_HTTP_VERSION_1_1,
+ // Verifies the authenticity of the peer's certificate
+ CURLOPT_SSL_VERIFYPEER => 1,
+ // Certificate must indicate that the server is the server to which you meant to connect
+ CURLOPT_SSL_VERIFYHOST => 2
+ );
+
+ if (defined('CURLOPT_PROTOCOLS')) {
+ // Allow only HTTP and HTTPS protocols
+ $curlOptions[CURLOPT_PROTOCOLS] = CURLPROTO_HTTP | CURLPROTO_HTTPS;
+ }
+
+ // Add CURLOPT_ENCODING if Accept-Encoding header is provided
+ if ($acceptEncodingHeader = $request->getHeader('Accept-Encoding')) {
+ $curlOptions[CURLOPT_ENCODING] = (string) $acceptEncodingHeader;
+ // Let cURL set the Accept-Encoding header, prevents duplicate values
+ $request->removeHeader('Accept-Encoding');
+ }
+
+ // Enable curl debug information if the 'debug' param was set
+ if ($requestCurlOptions->get('debug')) {
+ $curlOptions[CURLOPT_STDERR] = fopen('php://temp', 'r+');
+ // @codeCoverageIgnoreStart
+ if (false === $curlOptions[CURLOPT_STDERR]) {
+ throw new RuntimeException('Unable to create a stream for CURLOPT_STDERR');
+ }
+ // @codeCoverageIgnoreEnd
+ $curlOptions[CURLOPT_VERBOSE] = true;
+ }
+
+ // Specify settings according to the HTTP method
+ if ($method == 'GET') {
+ $curlOptions[CURLOPT_HTTPGET] = true;
+ } elseif ($method == 'HEAD') {
+ $curlOptions[CURLOPT_NOBODY] = true;
+ // HEAD requests do not use a write function
+ unset($curlOptions[CURLOPT_WRITEFUNCTION]);
+ } elseif (!($request instanceof EntityEnclosingRequest)) {
+ $curlOptions[CURLOPT_CUSTOMREQUEST] = $method;
+ } else {
+
+ $curlOptions[CURLOPT_CUSTOMREQUEST] = $method;
+
+ // Handle sending raw bodies in a request
+ if ($request->getBody()) {
+ // You can send the body as a string using curl's CURLOPT_POSTFIELDS
+ if ($bodyAsString) {
+ $curlOptions[CURLOPT_POSTFIELDS] = (string) $request->getBody();
+ // Allow curl to add the Content-Length for us to account for the times when
+ // POST redirects are followed by GET requests
+ if ($tempContentLength = $request->getHeader('Content-Length')) {
+ $tempContentLength = (int) (string) $tempContentLength;
+ }
+ // Remove the curl generated Content-Type header if none was set manually
+ if (!$request->hasHeader('Content-Type')) {
+ $curlOptions[CURLOPT_HTTPHEADER][] = 'Content-Type:';
+ }
+ } else {
+ $curlOptions[CURLOPT_UPLOAD] = true;
+ // Let cURL handle setting the Content-Length header
+ if ($tempContentLength = $request->getHeader('Content-Length')) {
+ $tempContentLength = (int) (string) $tempContentLength;
+ $curlOptions[CURLOPT_INFILESIZE] = $tempContentLength;
+ }
+ // Add a callback for curl to read data to send with the request only if a body was specified
+ $curlOptions[CURLOPT_READFUNCTION] = array($mediator, 'readRequestBody');
+ // Attempt to seek to the start of the stream
+ $request->getBody()->seek(0);
+ }
+
+ } else {
+
+ // Special handling for POST specific fields and files
+ $postFields = false;
+ if (count($request->getPostFiles())) {
+ $postFields = $request->getPostFields()->useUrlEncoding(false)->urlEncode();
+ foreach ($request->getPostFiles() as $key => $data) {
+ $prefixKeys = count($data) > 1;
+ foreach ($data as $index => $file) {
+ // Allow multiple files in the same key
+ $fieldKey = $prefixKeys ? "{$key}[{$index}]" : $key;
+ $postFields[$fieldKey] = $file->getCurlValue();
+ }
+ }
+ } elseif (count($request->getPostFields())) {
+ $postFields = (string) $request->getPostFields()->useUrlEncoding(true);
+ }
+
+ if ($postFields !== false) {
+ if ($method == 'POST') {
+ unset($curlOptions[CURLOPT_CUSTOMREQUEST]);
+ $curlOptions[CURLOPT_POST] = true;
+ }
+ $curlOptions[CURLOPT_POSTFIELDS] = $postFields;
+ $request->removeHeader('Content-Length');
+ }
+ }
+
+ // If the Expect header is not present, prevent curl from adding it
+ if (!$request->hasHeader('Expect')) {
+ $curlOptions[CURLOPT_HTTPHEADER][] = 'Expect:';
+ }
+ }
+
+ // If a Content-Length header was specified but we want to allow curl to set one for us
+ if (null !== $tempContentLength) {
+ $request->removeHeader('Content-Length');
+ }
+
+ // Set custom cURL options
+ foreach ($requestCurlOptions->toArray() as $key => $value) {
+ if (is_numeric($key)) {
+ $curlOptions[$key] = $value;
+ }
+ }
+
+ // Do not set an Accept header by default
+ if (!isset($curlOptions[CURLOPT_ENCODING])) {
+ $curlOptions[CURLOPT_HTTPHEADER][] = 'Accept:';
+ }
+
+ // Add any custom headers to the request. Empty headers will cause curl to not send the header at all.
+ foreach ($request->getHeaderLines() as $line) {
+ $curlOptions[CURLOPT_HTTPHEADER][] = $line;
+ }
+
+ // Add the content-length header back if it was temporarily removed
+ if (null !== $tempContentLength) {
+ $request->setHeader('Content-Length', $tempContentLength);
+ }
+
+ // Apply the options to a new cURL handle.
+ $handle = curl_init();
+
+ // Enable the progress function if the 'progress' param was set
+ if ($requestCurlOptions->get('progress')) {
+ // Wrap the function in a function that provides the curl handle to the mediator's progress function
+ // Using this rather than injecting the handle into the mediator prevents a circular reference
+ $curlOptions[CURLOPT_PROGRESSFUNCTION] = function () use ($mediator, $handle) {
+ $args = func_get_args();
+ $args[] = $handle;
+
+ // PHP 5.5 pushed the handle onto the start of the args
+ if (is_resource($args[0])) {
+ array_shift($args);
+ }
+
+ call_user_func_array(array($mediator, 'progress'), $args);
+ };
+ $curlOptions[CURLOPT_NOPROGRESS] = false;
+ }
+
+ curl_setopt_array($handle, $curlOptions);
+
+ return new static($handle, $curlOptions);
+ }
+
+ /**
+ * Construct a new CurlHandle object that wraps a cURL handle
+ *
+ * @param resource $handle Configured cURL handle resource
+ * @param Collection|array $options Curl options to use with the handle
+ *
+ * @throws InvalidArgumentException
+ */
+ public function __construct($handle, $options)
+ {
+ if (!is_resource($handle)) {
+ throw new InvalidArgumentException('Invalid handle provided');
+ }
+ if (is_array($options)) {
+ $this->options = new Collection($options);
+ } elseif ($options instanceof Collection) {
+ $this->options = $options;
+ } else {
+ throw new InvalidArgumentException('Expected array or Collection');
+ }
+ $this->handle = $handle;
+ }
+
+ /**
+ * Destructor
+ */
+ public function __destruct()
+ {
+ $this->close();
+ }
+
+ /**
+ * Close the curl handle
+ */
+ public function close()
+ {
+ if (is_resource($this->handle)) {
+ curl_close($this->handle);
+ }
+ $this->handle = null;
+ }
+
+ /**
+ * Check if the handle is available and still OK
+ *
+ * @return bool
+ */
+ public function isAvailable()
+ {
+ return is_resource($this->handle);
+ }
+
+ /**
+ * Get the last error that occurred on the cURL handle
+ *
+ * @return string
+ */
+ public function getError()
+ {
+ return $this->isAvailable() ? curl_error($this->handle) : '';
+ }
+
+ /**
+ * Get the last error number that occurred on the cURL handle
+ *
+ * @return int
+ */
+ public function getErrorNo()
+ {
+ if ($this->errorNo) {
+ return $this->errorNo;
+ }
+
+ return $this->isAvailable() ? curl_errno($this->handle) : CURLE_OK;
+ }
+
+ /**
+ * Set the curl error number
+ *
+ * @param int $error Error number to set
+ *
+ * @return CurlHandle
+ */
+ public function setErrorNo($error)
+ {
+ $this->errorNo = $error;
+
+ return $this;
+ }
+
+ /**
+ * Get cURL curl_getinfo data
+ *
+ * @param int $option Option to retrieve. Pass null to retrieve all data as an array.
+ *
+ * @return array|mixed
+ */
+ public function getInfo($option = null)
+ {
+ if (!is_resource($this->handle)) {
+ return null;
+ }
+
+ if (null !== $option) {
+ return curl_getinfo($this->handle, $option) ?: null;
+ }
+
+ return curl_getinfo($this->handle) ?: array();
+ }
+
+ /**
+ * Get the stderr output
+ *
+ * @param bool $asResource Set to TRUE to get an fopen resource
+ *
+ * @return string|resource|null
+ */
+ public function getStderr($asResource = false)
+ {
+ $stderr = $this->getOptions()->get(CURLOPT_STDERR);
+ if (!$stderr) {
+ return null;
+ }
+
+ if ($asResource) {
+ return $stderr;
+ }
+
+ fseek($stderr, 0);
+ $e = stream_get_contents($stderr);
+ fseek($stderr, 0, SEEK_END);
+
+ return $e;
+ }
+
+ /**
+ * Get the URL that this handle is connecting to
+ *
+ * @return Url
+ */
+ public function getUrl()
+ {
+ return Url::factory($this->options->get(CURLOPT_URL));
+ }
+
+ /**
+ * Get the wrapped curl handle
+ *
+ * @return resource|null Returns the cURL handle or null if it was closed
+ */
+ public function getHandle()
+ {
+ return $this->isAvailable() ? $this->handle : null;
+ }
+
+ /**
+ * Get the cURL setopt options of the handle. Changing values in the return object will have no effect on the curl
+ * handle after it is created.
+ *
+ * @return Collection
+ */
+ public function getOptions()
+ {
+ return $this->options;
+ }
+
+ /**
+ * Update a request based on the log messages of the CurlHandle
+ *
+ * @param RequestInterface $request Request to update
+ */
+ public function updateRequestFromTransfer(RequestInterface $request)
+ {
+ if (!$request->getResponse()) {
+ return;
+ }
+
+ // Update the transfer stats of the response
+ $request->getResponse()->setInfo($this->getInfo());
+
+ if (!$log = $this->getStderr(true)) {
+ return;
+ }
+
+ // Parse the cURL stderr output for outgoing requests
+ $headers = '';
+ fseek($log, 0);
+ while (($line = fgets($log)) !== false) {
+ if ($line && $line[0] == '>') {
+ $headers = substr(trim($line), 2) . "\r\n";
+ while (($line = fgets($log)) !== false) {
+ if ($line[0] == '*' || $line[0] == '<') {
+ break;
+ } else {
+ $headers .= trim($line) . "\r\n";
+ }
+ }
+ }
+ }
+
+ // Add request headers to the request exactly as they were sent
+ if ($headers) {
+ $parsed = ParserRegistry::getInstance()->getParser('message')->parseRequest($headers);
+ if (!empty($parsed['headers'])) {
+ $request->setHeaders(array());
+ foreach ($parsed['headers'] as $name => $value) {
+ $request->setHeader($name, $value);
+ }
+ }
+ if (!empty($parsed['version'])) {
+ $request->setProtocolVersion($parsed['version']);
+ }
+ }
+ }
+
+ /**
+ * Parse the config and replace curl.* configurators into the constant based values so it can be used elsewhere
+ *
+ * @param array|Collection $config The configuration we want to parse
+ *
+ * @return array
+ */
+ public static function parseCurlConfig($config)
+ {
+ $curlOptions = array();
+ foreach ($config as $key => $value) {
+ if (is_string($key) && defined($key)) {
+ // Convert constants represented as string to constant int values
+ $key = constant($key);
+ }
+ if (is_string($value) && defined($value)) {
+ $value = constant($value);
+ }
+ $curlOptions[$key] = $value;
+ }
+
+ return $curlOptions;
+ }
+}