Вход Регистрация
Файл: monst/Xsolla/Guzzle/Http/Curl/CurlHandle.php
Строк: 534
<?php

namespace GuzzleHttpCurl;

use 
GuzzleCommonExceptionInvalidArgumentException;
use 
GuzzleCommonExceptionRuntimeException;
use 
GuzzleCommonCollection;
use 
GuzzleHttpMessageEntityEnclosingRequest;
use 
GuzzleHttpMessageRequestInterface;
use 
GuzzleParserParserRegistry;
use 
GuzzleHttpUrl;

/**
 * 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($url0$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($stderr0);
        
$e stream_get_contents($stderr);
        
fseek($stderr0SEEK_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($log0);
        while ((
$line fgets($log)) !== false) {
            if (
$line && $line[0] == '>') {
                
$headers substr(trim($line), 2) . "rn";
                while ((
$line fgets($log)) !== false) {
                    if (
$line[0] == '*' || $line[0] == '<') {
                        break;
                    } else {
                        
$headers .= trim($line) . "rn";
                    }
                }
            }
        }

        
// 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;
    }
}
Онлайн: 2
Реклама