Code Coverage
 
Classes and Traits
Functions and Methods
Lines
Total
0.00% covered (danger)
0.00%
0 / 1
9.09% covered (danger)
9.09%
1 / 11
CRAP
85.53% covered (warning)
85.53%
201 / 235
HttpClientTrait
0.00% covered (danger)
0.00%
0 / 1
9.09% covered (danger)
9.09%
1 / 11
239.50
85.53% covered (warning)
85.53%
201 / 235
 prepareRequest
0.00% covered (danger)
0.00%
0 / 1
51.36
81.67% covered (warning)
81.67%
49 / 60
 mergeDefaultOptions
0.00% covered (danger)
0.00%
0 / 1
21.44
90.00% covered (success)
90.00%
27 / 30
 normalizeHeaders
0.00% covered (danger)
0.00%
0 / 1
10.80
80.00% covered (warning)
80.00%
16 / 20
 normalizeBody
0.00% covered (danger)
0.00%
0 / 1
10.01
94.74% covered (success)
94.74%
18 / 19
 normalizePeerFingerprint
0.00% covered (danger)
0.00%
0 / 1
9.37
83.33% covered (warning)
83.33%
10 / 12
 jsonEncode
0.00% covered (danger)
0.00%
0 / 1
10.86
57.14% covered (warning)
57.14%
4 / 7
 resolveUrl
0.00% covered (danger)
0.00%
0 / 1
13
96.00% covered (success)
96.00%
24 / 25
 parseUrl
0.00% covered (danger)
0.00%
0 / 1
21.76
88.00% covered (warning)
88.00%
22 / 25
 removeDotSegments
100.00% covered (success)
100.00%
1 / 1
11
100.00% covered (success)
100.00%
14 / 14
 mergeQueryString
0.00% covered (danger)
0.00%
0 / 1
14.03
94.44% covered (success)
94.44%
17 / 18
 shouldBuffer
0.00% covered (danger)
0.00%
0 / 1
20
0.00% covered (danger)
0.00%
0 / 5
<?php
/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */
namespace Symfony\Component\HttpClient;
use Symfony\Component\HttpClient\Exception\InvalidArgumentException;
/**
 * Provides the common logic from writing HttpClientInterface implementations.
 *
 * All methods are static to prevent implementers from creating memory leaks via circular references.
 *
 * @author Nicolas Grekas <p@tchwork.com>
 */
trait HttpClientTrait
{
    private static $CHUNK_SIZE = 16372;
    /**
     * Validates and normalizes method, URL and options, and merges them with defaults.
     *
     * @throws InvalidArgumentException When a not-supported option is found
     */
    private static function prepareRequest(?string $method, ?string $url, array $options, array $defaultOptions = [], bool $allowExtraOptions = false): array
    {
        if (null !== $method) {
            if (\strlen($method) !== strspn($method, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ')) {
                throw new InvalidArgumentException(sprintf('Invalid HTTP method "%s", only uppercase letters are accepted.', $method));
            }
            if (!$method) {
                throw new InvalidArgumentException('The HTTP method can not be empty.');
            }
        }
        $options = self::mergeDefaultOptions($options, $defaultOptions, $allowExtraOptions);
        $buffer = $options['buffer'] ?? true;
        if ($buffer instanceof \Closure) {
            $options['buffer'] = static function (array $headers) use ($buffer) {
                if (!\is_bool($buffer = $buffer($headers))) {
                    if (!\is_array($bufferInfo = @stream_get_meta_data($buffer))) {
                        throw new \LogicException(sprintf('The closure passed as option "buffer" must return bool or stream resource, got %s.', \is_resource($buffer) ? get_resource_type($buffer).' resource' : \gettype($buffer)));
                    }
                    if (false === strpbrk($bufferInfo['mode'], 'acew+')) {
                        throw new \LogicException(sprintf('The stream returned by the closure passed as option "buffer" must be writeable, got mode "%s".', $bufferInfo['mode']));
                    }
                }
                return $buffer;
            };
        } elseif (!\is_bool($buffer)) {
            if (!\is_array($bufferInfo = @stream_get_meta_data($buffer))) {
                throw new InvalidArgumentException(sprintf('Option "buffer" must be bool, stream resource or Closure, %s given.', \is_resource($buffer) ? get_resource_type($buffer).' resource' : \gettype($buffer)));
            }
            if (false === strpbrk($bufferInfo['mode'], 'acew+')) {
                throw new InvalidArgumentException(sprintf('The stream in option "buffer" must be writeable, mode "%s" given.', $bufferInfo['mode']));
            }
        }
        if (isset($options['json'])) {
            if (isset($options['body']) && '' !== $options['body']) {
                throw new InvalidArgumentException('Define either the "json" or the "body" option, setting both is not supported.');
            }
            $options['body'] = self::jsonEncode($options['json']);
            unset($options['json']);
            if (!isset($options['normalized_headers']['content-type'])) {
                $options['normalized_headers']['content-type'] = [$options['headers'][] = 'Content-Type: application/json'];
            }
        }
        if (!isset($options['normalized_headers']['accept'])) {
            $options['normalized_headers']['accept'] = [$options['headers'][] = 'Accept: */*'];
        }
        if (isset($options['body'])) {
            $options['body'] = self::normalizeBody($options['body']);
        }
        if (isset($options['peer_fingerprint'])) {
            $options['peer_fingerprint'] = self::normalizePeerFingerprint($options['peer_fingerprint']);
        }
        // Validate on_progress
        if (!\is_callable($onProgress = $options['on_progress'] ?? 'var_dump')) {
            throw new InvalidArgumentException(sprintf('Option "on_progress" must be callable, %s given.', \is_object($onProgress) ? \get_class($onProgress) : \gettype($onProgress)));
        }
        if (\is_array($options['auth_basic'] ?? null)) {
            $count = \count($options['auth_basic']);
            if ($count <= 0 || $count > 2) {
                throw new InvalidArgumentException(sprintf('Option "auth_basic" must contain 1 or 2 elements, %s given.', $count));
            }
            $options['auth_basic'] = implode(':', $options['auth_basic']);
        }
        if (!\is_string($options['auth_basic'] ?? '')) {
            throw new InvalidArgumentException(sprintf('Option "auth_basic" must be string or an array, %s given.', \gettype($options['auth_basic'])));
        }
        if (isset($options['auth_bearer']) && (!\is_string($options['auth_bearer']) || !preg_match('{^[-._=~+/0-9a-zA-Z]++$}', $options['auth_bearer']))) {
            throw new InvalidArgumentException(sprintf('Option "auth_bearer" must be a string containing only characters from the base 64 alphabet, %s given.', \is_string($options['auth_bearer']) ? 'invalid string' : \gettype($options['auth_bearer'])));
        }
        if (isset($options['auth_basic'], $options['auth_bearer'])) {
            throw new InvalidArgumentException('Define either the "auth_basic" or the "auth_bearer" option, setting both is not supported.');
        }
        if (null !== $url) {
            // Merge auth with headers
            if (($options['auth_basic'] ?? false) && !($options['normalized_headers']['authorization'] ?? false)) {
                $options['normalized_headers']['authorization'] = [$options['headers'][] = 'Authorization: Basic '.base64_encode($options['auth_basic'])];
            }
            // Merge bearer with headers
            if (($options['auth_bearer'] ?? false) && !($options['normalized_headers']['authorization'] ?? false)) {
                $options['normalized_headers']['authorization'] = [$options['headers'][] = 'Authorization: Bearer '.$options['auth_bearer']];
            }
            unset($options['auth_basic'], $options['auth_bearer']);
            // Parse base URI
            if (\is_string($options['base_uri'])) {
                $options['base_uri'] = self::parseUrl($options['base_uri']);
            }
            // Validate and resolve URL
            $url = self::parseUrl($url, $options['query']);
            $url = self::resolveUrl($url, $options['base_uri'], $defaultOptions['query'] ?? []);
        }
        // Finalize normalization of options
        $options['http_version'] = (string) ($options['http_version'] ?? '') ?: null;
        $options['timeout'] = (float) ($options['timeout'] ?? ini_get('default_socket_timeout'));
        $options['max_duration'] = isset($options['max_duration']) ? (float) $options['max_duration'] : 0;
        return [$url, $options];
    }
    /**
     * @throws InvalidArgumentException When an invalid option is found
     */
    private static function mergeDefaultOptions(array $options, array $defaultOptions, bool $allowExtraOptions = false): array
    {