| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265 | <?phpdeclare(strict_types=1);namespace GuzzleHttp\Psr7;use Psr\Http\Message\MessageInterface;use Psr\Http\Message\StreamInterface;/** * Trait implementing functionality common to requests and responses. */trait MessageTrait{    /** @var string[][] Map of all registered headers, as original name => array of values */    private $headers = [];    /** @var string[] Map of lowercase header name => original name at registration */    private $headerNames = [];    /** @var string */    private $protocol = '1.1';    /** @var StreamInterface|null */    private $stream;    public function getProtocolVersion(): string    {        return $this->protocol;    }    public function withProtocolVersion($version): MessageInterface    {        if ($this->protocol === $version) {            return $this;        }        $new = clone $this;        $new->protocol = $version;        return $new;    }    public function getHeaders(): array    {        return $this->headers;    }    public function hasHeader($header): bool    {        return isset($this->headerNames[strtolower($header)]);    }    public function getHeader($header): array    {        $header = strtolower($header);        if (!isset($this->headerNames[$header])) {            return [];        }        $header = $this->headerNames[$header];        return $this->headers[$header];    }    public function getHeaderLine($header): string    {        return implode(', ', $this->getHeader($header));    }    public function withHeader($header, $value): MessageInterface    {        $this->assertHeader($header);        $value = $this->normalizeHeaderValue($value);        $normalized = strtolower($header);        $new = clone $this;        if (isset($new->headerNames[$normalized])) {            unset($new->headers[$new->headerNames[$normalized]]);        }        $new->headerNames[$normalized] = $header;        $new->headers[$header] = $value;        return $new;    }    public function withAddedHeader($header, $value): MessageInterface    {        $this->assertHeader($header);        $value = $this->normalizeHeaderValue($value);        $normalized = strtolower($header);        $new = clone $this;        if (isset($new->headerNames[$normalized])) {            $header = $this->headerNames[$normalized];            $new->headers[$header] = array_merge($this->headers[$header], $value);        } else {            $new->headerNames[$normalized] = $header;            $new->headers[$header] = $value;        }        return $new;    }    public function withoutHeader($header): MessageInterface    {        $normalized = strtolower($header);        if (!isset($this->headerNames[$normalized])) {            return $this;        }        $header = $this->headerNames[$normalized];        $new = clone $this;        unset($new->headers[$header], $new->headerNames[$normalized]);        return $new;    }    public function getBody(): StreamInterface    {        if (!$this->stream) {            $this->stream = Utils::streamFor('');        }        return $this->stream;    }    public function withBody(StreamInterface $body): MessageInterface    {        if ($body === $this->stream) {            return $this;        }        $new = clone $this;        $new->stream = $body;        return $new;    }    /**     * @param (string|string[])[] $headers     */    private function setHeaders(array $headers): void    {        $this->headerNames = $this->headers = [];        foreach ($headers as $header => $value) {            // Numeric array keys are converted to int by PHP.            $header = (string) $header;            $this->assertHeader($header);            $value = $this->normalizeHeaderValue($value);            $normalized = strtolower($header);            if (isset($this->headerNames[$normalized])) {                $header = $this->headerNames[$normalized];                $this->headers[$header] = array_merge($this->headers[$header], $value);            } else {                $this->headerNames[$normalized] = $header;                $this->headers[$header] = $value;            }        }    }    /**     * @param mixed $value     *     * @return string[]     */    private function normalizeHeaderValue($value): array    {        if (!is_array($value)) {            return $this->trimAndValidateHeaderValues([$value]);        }        if (count($value) === 0) {            throw new \InvalidArgumentException('Header value can not be an empty array.');        }        return $this->trimAndValidateHeaderValues($value);    }    /**     * Trims whitespace from the header values.     *     * Spaces and tabs ought to be excluded by parsers when extracting the field value from a header field.     *     * header-field = field-name ":" OWS field-value OWS     * OWS          = *( SP / HTAB )     *     * @param mixed[] $values Header values     *     * @return string[] Trimmed header values     *     * @see https://datatracker.ietf.org/doc/html/rfc7230#section-3.2.4     */    private function trimAndValidateHeaderValues(array $values): array    {        return array_map(function ($value) {            if (!is_scalar($value) && null !== $value) {                throw new \InvalidArgumentException(sprintf(                    'Header value must be scalar or null but %s provided.',                    is_object($value) ? get_class($value) : gettype($value)                ));            }            $trimmed = trim((string) $value, " \t");            $this->assertValue($trimmed);            return $trimmed;        }, array_values($values));    }    /**     * @see https://datatracker.ietf.org/doc/html/rfc7230#section-3.2     *     * @param mixed $header     */    private function assertHeader($header): void    {        if (!is_string($header)) {            throw new \InvalidArgumentException(sprintf(                'Header name must be a string but %s provided.',                is_object($header) ? get_class($header) : gettype($header)            ));        }        if (!preg_match('/^[a-zA-Z0-9\'`#$%&*+.^_|~!-]+$/D', $header)) {            throw new \InvalidArgumentException(                sprintf('"%s" is not valid header name.', $header)            );        }    }    /**     * @see https://datatracker.ietf.org/doc/html/rfc7230#section-3.2     *     * field-value    = *( field-content / obs-fold )     * field-content  = field-vchar [ 1*( SP / HTAB ) field-vchar ]     * field-vchar    = VCHAR / obs-text     * VCHAR          = %x21-7E     * obs-text       = %x80-FF     * obs-fold       = CRLF 1*( SP / HTAB )     */    private function assertValue(string $value): void    {        // The regular expression intentionally does not support the obs-fold production, because as        // per RFC 7230#3.2.4:        //        // A sender MUST NOT generate a message that includes        // line folding (i.e., that has any field-value that contains a match to        // the obs-fold rule) unless the message is intended for packaging        // within the message/http media type.        //        // Clients must not send a request with line folding and a server sending folded headers is        // likely very rare. Line folding is a fairly obscure feature of HTTP/1.1 and thus not accepting        // folding is not likely to break any legitimate use case.        if (!preg_match('/^[\x20\x09\x21-\x7E\x80-\xFF]*$/D', $value)) {            throw new \InvalidArgumentException(                sprintf('"%s" is not valid header value.', $value)            );        }    }}
 |