Completed
Push — master ( d03bdc...cf80c7 )
by Tobias
01:50
created

MessageTrait::validateAndTrimHeader()   C

Complexity

Conditions 12
Paths 7

Size

Total Lines 31

Duplication

Lines 6
Ratio 19.35 %

Code Coverage

Tests 14
CRAP Score 12.0427

Importance

Changes 0
Metric Value
dl 6
loc 31
ccs 14
cts 15
cp 0.9333
rs 6.9666
c 0
b 0
f 0
cc 12
nc 7
nop 2
crap 12.0427

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
declare(strict_types=1);
4
5
namespace Nyholm\Psr7;
6
7
use Psr\Http\Message\StreamInterface;
8
9
/**
10
 * Trait implementing functionality common to requests and responses.
11
 *
12
 * @author Michael Dowling and contributors to guzzlehttp/psr7
13
 * @author Tobias Nyholm <[email protected]>
14
 * @author Martijn van der Ven <[email protected]>
15
 *
16
 * @internal should not be used outside of Nyholm/Psr7 as it does not fall under our BC promise.
17
 */
18
trait MessageTrait
19
{
20
    /** @var array Map of all registered headers, as original name => array of values */
21
    private $headers = [];
22
23
    /** @var array Map of lowercase header name => original name at registration */
24
    private $headerNames = [];
25
26
    /** @var string */
27
    private $protocol = '1.1';
28
29
    /** @var StreamInterface */
30
    private $stream;
31
32 5
    public function getProtocolVersion(): string
33
    {
34 5
        return $this->protocol;
35
    }
36
37 4
    public function withProtocolVersion($version): self
38
    {
39 4
        if ($this->protocol === $version) {
40 1
            return $this;
41
        }
42
43 3
        $new = clone $this;
44 3
        $new->protocol = $version;
45
46 3
        return $new;
47
    }
48
49 18
    public function getHeaders(): array
50
    {
51 18
        return $this->headers;
52
    }
53
54 82
    public function hasHeader($header): bool
55
    {
56 82
        return isset($this->headerNames[strtolower($header)]);
57
    }
58
59 27
    public function getHeader($header): array
60
    {
61 27
        $header = strtolower($header);
62 27
        if (!isset($this->headerNames[$header])) {
63 5
            return [];
64
        }
65
66 26
        $header = $this->headerNames[$header];
67
68 26
        return $this->headers[$header];
69
    }
70
71 25
    public function getHeaderLine($header): string
72
    {
73 25
        return implode(', ', $this->getHeader($header));
74
    }
75
76 19
    public function withHeader($header, $value): self
77
    {
78 19
        $value = $this->validateAndTrimHeader($header, $value);
79 5
        $normalized = strtolower($header);
80
81 5
        $new = clone $this;
82 5
        if (isset($new->headerNames[$normalized])) {
83 1
            unset($new->headers[$new->headerNames[$normalized]]);
84
        }
85 5
        $new->headerNames[$normalized] = $header;
86 5
        $new->headers[$header] = $value;
87
88 5
        return $new;
89
    }
90
91 33
    public function withAddedHeader($header, $value): self
92
    {
93 33
        if (!\is_string($header) || '' === $header) {
94 8
            throw new \InvalidArgumentException('Header name must be an RFC 7230 compatible string.');
95
        }
96
97 25
        $new = clone $this;
98 25
        $new->setHeaders([$header => $value]);
99
100 19
        return $new;
101
    }
102
103 5
    public function withoutHeader($header): self
104
    {
105 5
        $normalized = strtolower($header);
106 5
        if (!isset($this->headerNames[$normalized])) {
107 2
            return $this;
108
        }
109
110 3
        $header = $this->headerNames[$normalized];
111 3
        $new = clone $this;
112 3
        unset($new->headers[$header], $new->headerNames[$normalized]);
113
114 3
        return $new;
115
    }
116
117 9
    public function getBody(): StreamInterface
118
    {
119 9
        if (!$this->stream) {
120 3
            $this->stream = Stream::create('');
121
        }
122
123 9
        return $this->stream;
124
    }
125
126 4
    public function withBody(StreamInterface $body): self
127
    {
128 4
        if ($body === $this->stream) {
129 1
            return $this;
130
        }
131
132 3
        $new = clone $this;
133 3
        $new->stream = $body;
134
135 3
        return $new;
136
    }
137
138 142
    private function setHeaders(array $headers): void
139
    {
140 142
        foreach ($headers as $header => $value) {
141 32
            $value = $this->validateAndTrimHeader($header, $value);
142 26
            $normalized = strtolower($header);
143 26
            if (isset($this->headerNames[$normalized])) {
144 14
                $header = $this->headerNames[$normalized];
145 14
                $this->headers[$header] = array_merge($this->headers[$header], $value);
146
            } else {
147 26
                $this->headerNames[$normalized] = $header;
148 26
                $this->headers[$header] = $value;
149
            }
150
        }
151 142
    }
152
153
    /**
154
     * Make sure the header complies with RFC 7230.
155
     *
156
     * Header names must be a non-empty string consisting of token characters.
157
     *
158
     * Header values must be strings consisting of visible characters with all optional
159
     * leading and trailing whitespace stripped. This method will always strip such
160
     * optional whitespace. Note that the method does not allow folding whitespace within
161
     * the values as this was deprecated for almost all instances by the RFC.
162
     *
163
     * header-field = field-name ":" OWS field-value OWS
164
     * field-name   = 1*( "!" / "#" / "$" / "%" / "&" / "'" / "*" / "+" / "-" / "." / "^"
165
     *              / "_" / "`" / "|" / "~" / %x30-39 / ( %x41-5A / %x61-7A ) )
166
     * OWS          = *( SP / HTAB )
167
     * field-value  = *( ( %x21-7E / %x80-FF ) [ 1*( SP / HTAB ) ( %x21-7E / %x80-FF ) ] )
168
     *
169
     * @see https://tools.ietf.org/html/rfc7230#section-3.2.4
170
     */
171 48
    private function validateAndTrimHeader($header, $values): array
172
    {
173 48
        if (!\is_string($header) || 1 !== preg_match("@^[!#$%&'*+.^_`|~0-9A-Za-z-]+$@", $header)) {
174 8
            throw new \InvalidArgumentException('Header name must be an RFC 7230 compatible string.');
175
        }
176
177 40
        if (!\is_array($values)) {
178
            // This is simple, just one value.
179 33 View Code Duplication
            if ((!is_numeric($values) && !\is_string($values)) || 1 !== preg_match("@^[ \t\x21-\x7E\x80-\xFF]*$@", (string) $values)) {
1 ignored issue
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
180 8
                throw new \InvalidArgumentException('Header values must be RFC 7230 compatible strings.');
181
            }
182
183 25
            return [trim((string) $values, " \t")];
184
        }
185
186 13
        if (empty($values)) {
187 4
            throw new \InvalidArgumentException('Header values must be a string or an array of strings, empty array given.');
188
        }
189
190
        // Assert Non empty array
191 9
        $returnValues = [];
192 9
        foreach ($values as $v) {
193 9 View Code Duplication
            if ((!is_numeric($v) && !\is_string($v)) || 1 !== preg_match("@^[ \t\x21-\x7E\x80-\xFF]*$@", (string) $v)) {
1 ignored issue
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
194
                throw new \InvalidArgumentException('Header values must be RFC 7230 compatible strings.');
195
            }
196
197 9
            $returnValues[] = trim((string) $v, " \t");
198
        }
199
200 9
        return $returnValues;
201
    }
202
}
203