<commit_before><?php
/**
 * PSR-7 compatible cURL based HTTP client
 *
 * @license http://opensource.org/licenses/MIT MIT
 */
namespace Http\Curl;

use Http\Client\Exception\RequestException;
use Http\Client\HttpClient;
use Http\Message\MessageFactory;
use Http\Message\MessageFactoryAwareTemplate;
use Http\Message\StreamFactory;
use Http\Message\StreamFactoryAwareTemplate;
use InvalidArgumentException;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseInterface;
use RuntimeException;
use UnexpectedValueException;

/**
 * Use php cURL extension to perform HTTP requests
 *
 * @author Kemist <<EMAIL>>
 * @author <NAME> <<EMAIL>>
 * @author <NAME> <<EMAIL>>
 *
 * @api
 * @since  1.00
 */
class CurlHttpClient implements HttpClient
{
    use MessageFactoryAwareTemplate;
    use StreamFactoryAwareTemplate;

    /**
     * cURL handle configuration TODO change description
     *
     * @var array
     *
     * @since 1.00
     */
    protected $options;

    /**
     * Constructor
     *
     * Available options (see also {@link getDefaultOptions}):
     *
     * - connection_timeout : int —  connection timeout in seconds
     * - ssl_verify_peer : bool — verify peer when using SSL
     * - timeout : int —  overall timeout in seconds
     *
     * @param MessageFactory $messageFactory HTTP Message factory
     * @param StreamFactory  $streamFactory  HTTP Stream factory
     * @param array          $options        Client options
     *
     * @since 1.00
     */
    public function __construct(
        MessageFactory $messageFactory,
        StreamFactory $streamFactory,
        array $options = []
    ) {
        $this->setMessageFactory($messageFactory);
        $this->setStreamFactory($streamFactory);
        $this->options = array_merge(
            $this->getDefaultOptions(),
            array_intersect_key(
                $options,
                $this->getDefaultOptions()
            )
        );
    }

    /**
     * Return available options and there default values
     *
     * @return array
     *
     * @since 3.02
     */
    public function getDefaultOptions()
    {
        return [
            'curl_options' => [],
            'connection_timeout' => 3,
            'ssl_verify_peer' => true,
            'timeout' => 10
        ];
    }

    /**
     * Sends a PSR-7 request.
     *
     * @param RequestInterface $request
     *
     * @return ResponseInterface
     *
     * @throws UnexpectedValueException if unsupported HTTP version requested
     * @throws InvalidArgumentException
     * @throws RequestException
     *
     * @since 1.00
     */
    public function sendRequest(RequestInterface $request)
    {
        $options = $this->createCurlOptions($request);

        try {
            $this->request($options, $raw, $info);
        } catch (RuntimeException $e) {
            throw new RequestException($e->getMessage(), $request, $e);
        }

        $response = $this->getMessageFactory()->createResponse();

        $headerSize = $info['header_size'];
        $rawHeaders = substr($raw, 0, $headerSize);

        // Parse headers
        $allHeaders = explode("\r\n\r\n", $rawHeaders);
        $lastHeaders = trim(array_pop($allHeaders));
        while (count($allHeaders) > 0 && '' === $lastHeaders) {
            $lastHeaders = trim(array_pop($allHeaders));
        }
        $headerLines = explode("\r\n", $lastHeaders);
        foreach ($headerLines as $header) {
            $header = trim($header);
            if ('' === $header) {
                continue;
            }
            // Status line
            if (substr(strtolower($header), 0, 5) === 'http/') {
                $parts = explode(' ', $header, 3);
                $response = $response
                    ->withStatus($parts[1])
                    ->withProtocolVersion(substr($parts[0], 5));
                continue;
            }
            // Extract header
            $parts = explode(':', $header, 2);
            $headerName = trim(urldecode($parts[0]));
            $headerValue = trim(urldecode($parts[1]));
            $response = $this->addHeaderToResponse($response, $headerName, $headerValue);
        }

        $content = (string) substr($raw, $headerSize);
        $stream = $this->getStreamFactory()->createStream($content);
        /** @var ResponseInterface $response */
        $response = $response->withBody($stream);

        return $response;
    }

    /**
     * Perform request via cURL
     *
     * @param array  $options cURL options
     * @param string $raw     raw response
     * @param array  $info    cURL response info
     *
     * @throws RuntimeException on cURL error
     *
     * @since 3.00
     */
    protected function request($options, &$raw, &$info)
    {
        $ch = curl_init();
        try {
            curl_setopt_array($ch, $options);
            $raw = curl_exec($ch);

            if (curl_errno($ch) > 0) {
                throw new RuntimeException(
                    sprintf('Curl error: (%d) %s', curl_errno($ch), curl_error($ch))
                );
            }
            $info = curl_getinfo($ch);
        } finally {
            curl_close($ch);
        }
    }

    /**
     * Adds a header to the response object
     *
     * @param ResponseInterface $response
     * @param string            $name
     * @param string            $value
     *
     * @return ResponseInterface
     *
     * @since 3.02
     */
    protected function addHeaderToResponse($response, $name, $value)
    {
        if ($response->hasHeader($name)) {
            $response = $response->withAddedHeader($name, $value);
        } else {
            $response = $response->withHeader($name, $value);
        }
        return $response;
    }

    /**
     * Generates cURL options
     *
     * @param RequestInterface $request
     *
     * @throws UnexpectedValueException if unsupported HTTP version requested
     *
     * @return array
     */
    private function createCurlOptions(RequestInterface $request)<commit_msg>Fix PHPDoc.<commit_after><?php
/**
 * PSR-7 compatible cURL based HTTP client
 *
 * @license http://opensource.org/licenses/MIT MIT
 */
namespace Http\Curl;

use Http\Client\Exception\RequestException;
use Http\Client\HttpClient;
use Http\Message\MessageFactory;
use Http\Message\MessageFactoryAwareTemplate;
use Http\Message\StreamFactory;
use Http\Message\StreamFactoryAwareTemplate;
use InvalidArgumentException;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseInterface;
use RuntimeException;
use UnexpectedValueException;

/**
 * Use php cURL extension to perform HTTP requests
 *
 * @author Kemist <<EMAIL>>
 * @author <NAME> <<EMAIL>>
 * @author <NAME> <<EMAIL>>
 *
 * @api
 * @since  1.00
 */
class CurlHttpClient implements HttpClient
{
    use MessageFactoryAwareTemplate;
    use StreamFactoryAwareTemplate;

    /**
     * cURL handle configuration TODO change description
     *
     * @var array
     *
     * @since 1.00
     */
    protected $options;

    /**
     * Constructor
     *
     * Available options (see also {@link getDefaultOptions}):
     *
     * - connection_timeout : int —  connection timeout in seconds
     * - ssl_verify_peer : bool — verify peer when using SSL
     * - timeout : int —  overall timeout in seconds
     *
     * @param MessageFactory $messageFactory HTTP Message factory
     * @param StreamFactory  $streamFactory  HTTP Stream factory
     * @param array          $options        Client options
     *
     * @since 1.00
     */
    public function __construct(
        MessageFactory $messageFactory,
        StreamFactory $streamFactory,
        array $options = []
    ) {
        $this->setMessageFactory($messageFactory);
        $this->setStreamFactory($streamFactory);
        $this->options = array_merge(
            $this->getDefaultOptions(),
            array_intersect_key(
                $options,
                $this->getDefaultOptions()
            )
        );
    }

    /**
     * Return available options and there default values
     *
     * @return array
     *
     * @since 1.00
     */
    public function getDefaultOptions()
    {
        return [
            'curl_options' => [],
            'connection_timeout' => 3,
            'ssl_verify_peer' => true,
            'timeout' => 10
        ];
    }

    /**
     * Sends a PSR-7 request.
     *
     * @param RequestInterface $request
     *
     * @return ResponseInterface
     *
     * @throws UnexpectedValueException if unsupported HTTP version requested
     * @throws InvalidArgumentException
     * @throws RequestException
     *
     * @since 1.00
     */
    public function sendRequest(RequestInterface $request)
    {
        $options = $this->createCurlOptions($request);

        try {
            $this->request($options, $raw, $info);
        } catch (RuntimeException $e) {
            throw new RequestException($e->getMessage(), $request, $e);
        }

        $response = $this->getMessageFactory()->createResponse();

        $headerSize = $info['header_size'];
        $rawHeaders = substr($raw, 0, $headerSize);

        // Parse headers
        $allHeaders = explode("\r\n\r\n", $rawHeaders);
        $lastHeaders = trim(array_pop($allHeaders));
        while (count($allHeaders) > 0 && '' === $lastHeaders) {
            $lastHeaders = trim(array_pop($allHeaders));
        }
        $headerLines = explode("\r\n", $lastHeaders);
        foreach ($headerLines as $header) {
            $header = trim($header);
            if ('' === $header) {
                continue;
            }
            // Status line
            if (substr(strtolower($header), 0, 5) === 'http/') {
                $parts = explode(' ', $header, 3);
                $response = $response
                    ->withStatus($parts[1])
                    ->withProtocolVersion(substr($parts[0], 5));
                continue;
            }
            // Extract header
            $parts = explode(':', $header, 2);
            $headerName = trim(urldecode($parts[0]));
            $headerValue = trim(urldecode($parts[1]));
            $response = $this->addHeaderToResponse($response, $headerName, $headerValue);
        }

        $content = (string) substr($raw, $headerSize);
        $stream = $this->getStreamFactory()->createStream($content);
        /** @var ResponseInterface $response */
        $response = $response->withBody($stream);

        return $response;
    }

    /**
     * Perform request via cURL
     *
     * @param array  $options cURL options
     * @param string $raw     raw response
     * @param array  $info    cURL response info
     *
     * @throws RuntimeException on cURL error
     *
     * @since 1.00
     */
    protected function request($options, &$raw, &$info)
    {
        $ch = curl_init();
        try {
            curl_setopt_array($ch, $options);
            $raw = curl_exec($ch);

            if (curl_errno($ch) > 0) {
                throw new RuntimeException(
                    sprintf('Curl error: (%d) %s', curl_errno($ch), curl_error($ch))
                );
            }
            $info = curl_getinfo($ch);
        } finally {
            curl_close($ch);
        }
    }

    /**
     * Adds a header to the response object
     *
     * @param ResponseInterface $response
     * @param string            $name
     * @param string            $value
     *
     * @return ResponseInterface
     *
     * @since 1.00
     */
    protected function addHeaderToResponse($response, $name, $value)
    {
        if ($response->hasHeader($name)) {
            $response = $response->withAddedHeader($name, $value);
        } else {
            $response = $response->withHeader($name, $value);
        }
        return $response;
    }

    /**
     * Generates cURL options
     *
     * @param RequestInterface $request
     *
     * @throws UnexpectedValueException if unsupported HTTP version requested
     *
     * @return array
     */
    private function createCurlOptions(RequestInterface $request)
{'fraction_non_alphanumeric': 0.12160596026490066, 'fraction_numerical': 0.006291390728476821, 'mean_word_length': 2.01497379585725, 'pattern_counts': {'":': 0, '<': 21, '<?xml version=': 0, '>': 69, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '4001278', 'n_tokens_mistral': 3356, 'n_tokens_neox': 3250, 'n_words': 776}