1 | <?php |
||
2 | /** |
||
3 | * This file is part of the Shieldon package. |
||
4 | * |
||
5 | * (c) Terry L. <[email protected]> |
||
6 | * |
||
7 | * For the full copyright and license information, please view the LICENSE |
||
8 | * file that was distributed with this source code. |
||
9 | */ |
||
10 | |||
11 | declare(strict_types=1); |
||
12 | |||
13 | namespace Shieldon\Psr7; |
||
14 | |||
15 | use Psr\Http\Message\ResponseInterface; |
||
16 | use Shieldon\Psr7\Message; |
||
17 | use InvalidArgumentException; |
||
18 | |||
19 | use function gettype; |
||
20 | use function is_integer; |
||
21 | use function is_string; |
||
22 | use function sprintf; |
||
23 | use function str_replace; |
||
24 | use function strpos; |
||
25 | |||
26 | /* |
||
27 | * Representation of an outgoing, server-side response. |
||
28 | */ |
||
29 | class Response extends Message implements ResponseInterface |
||
30 | { |
||
31 | /** |
||
32 | * HTTP status number. |
||
33 | * |
||
34 | * @var int |
||
35 | */ |
||
36 | protected $status; |
||
37 | |||
38 | /** |
||
39 | * HTTP status reason phrase. |
||
40 | * |
||
41 | * @var string |
||
42 | */ |
||
43 | protected $reasonPhrase; |
||
44 | |||
45 | /** |
||
46 | * HTTP status codes. |
||
47 | * |
||
48 | * @see https://www.iana.org/assignments/http-status-codes/http-status-codes.xhtml |
||
49 | * |
||
50 | * @var array |
||
51 | */ |
||
52 | protected static $statusCode = [ |
||
53 | |||
54 | // 1xx: Informational |
||
55 | // Request received, continuing process. |
||
56 | 100 => 'Continue', |
||
57 | 101 => 'Switching Protocols', |
||
58 | 102 => 'Processing', |
||
59 | |||
60 | // 2xx: Success |
||
61 | // The action was successfully received, understood, and accepted. |
||
62 | 200 => 'OK', |
||
63 | 201 => 'Created', |
||
64 | 202 => 'Accepted', |
||
65 | 203 => 'Non-Authoritative Information', |
||
66 | 204 => 'No Content', |
||
67 | 205 => 'Reset Content', |
||
68 | 206 => 'Partial Content', |
||
69 | 207 => 'Multi-status', |
||
70 | 208 => 'Already Reported', |
||
71 | |||
72 | // 3xx: Redirection |
||
73 | // Further action must be taken in order to complete the request. |
||
74 | 300 => 'Multiple Choices', |
||
75 | 301 => 'Moved Permanently', |
||
76 | 302 => 'Found', |
||
77 | 303 => 'See Other', |
||
78 | 304 => 'Not Modified', |
||
79 | 305 => 'Use Proxy', |
||
80 | 306 => 'Switch Proxy', |
||
81 | 307 => 'Temporary Redirect', |
||
82 | 308 => 'Permanent Redirect', |
||
83 | // => '309-399 Unassigned.' |
||
84 | |||
85 | // 4xx: Client Error |
||
86 | // The request contains bad syntax or cannot be fulfilled. |
||
87 | 400 => 'Bad Request', |
||
88 | 401 => 'Unauthorized', |
||
89 | 402 => 'Payment Required', |
||
90 | 403 => 'Forbidden', |
||
91 | 404 => 'Not Found', |
||
92 | 405 => 'Method Not Allowed', |
||
93 | 406 => 'Not Acceptable', |
||
94 | 407 => 'Proxy Authentication Required', |
||
95 | 408 => 'Request Time-out', |
||
96 | 409 => 'Conflict', |
||
97 | 410 => 'Gone', |
||
98 | 411 => 'Length Required', |
||
99 | 412 => 'Precondition Failed', |
||
100 | 413 => 'Request Entity Too Large', |
||
101 | 414 => 'Request-URI Too Large', |
||
102 | 415 => 'Unsupported Media Type', |
||
103 | 416 => 'Requested range not satisfiable', |
||
104 | 417 => 'Expectation Failed', |
||
105 | // => '418-412: Unassigned' |
||
106 | 422 => 'Unprocessable Entity', |
||
107 | 423 => 'Locked', |
||
108 | 424 => 'Failed Dependency', |
||
109 | 425 => 'Unordered Collection', |
||
110 | 426 => 'Upgrade Required', |
||
111 | 428 => 'Precondition Required', |
||
112 | 429 => 'Too Many Requests', |
||
113 | 431 => 'Request Header Fields Too Large', |
||
114 | // => '432-450: Unassigned.' |
||
115 | 451 => 'Unavailable For Legal Reasons', |
||
116 | // => '452-499: Unassigned.' |
||
117 | |||
118 | // 5xx: Server Error |
||
119 | // The server failed to fulfill an apparently valid request. |
||
120 | 500 => 'Internal Server Error', |
||
121 | 501 => 'Not Implemented', |
||
122 | 502 => 'Bad Gateway', |
||
123 | 503 => 'Service Unavailable', |
||
124 | 504 => 'Gateway Time-out', |
||
125 | 505 => 'HTTP Version not supported', |
||
126 | 506 => 'Variant Also Negotiates', |
||
127 | 507 => 'Insufficient Storage', |
||
128 | 508 => 'Loop Detected', |
||
129 | 510 => 'Not Extended', |
||
130 | 511 => 'Network Authentication Required', |
||
131 | // => '512-599 Unassigned.' |
||
132 | ]; |
||
133 | |||
134 | /** |
||
135 | * Response Constructor. |
||
136 | * |
||
137 | * @param int $status Response HTTP status code. |
||
138 | * @param array $headers Response headers. |
||
139 | * @param StreamInterface|string $body Response body. |
||
0 ignored issues
–
show
|
|||
140 | * @param string $version Response protocol version. |
||
141 | * @param string $reason Reaspnse HTTP reason phrase. |
||
142 | */ |
||
143 | 10 | public function __construct( |
|
144 | int $status = 200 , |
||
145 | array $headers = [] , |
||
146 | $body = '' , |
||
147 | string $version = '1.1', |
||
148 | string $reason = 'OK' |
||
149 | ) { |
||
150 | 10 | $this->assertStatus($status); |
|
151 | 9 | $this->assertReasonPhrase($reason); |
|
152 | 9 | $this->assertProtocolVersion($version); |
|
153 | |||
154 | 9 | $this->setHeaders($headers); |
|
155 | 9 | $this->setBody($body); |
|
156 | |||
157 | 9 | $this->status = $status; |
|
158 | 9 | $this->protocolVersion = $version; |
|
159 | 9 | $this->reasonPhrase = $reason; |
|
160 | } |
||
161 | |||
162 | /** |
||
163 | * {@inheritdoc} |
||
164 | */ |
||
165 | 4 | public function getStatusCode(): int |
|
166 | { |
||
167 | 4 | return $this->status; |
|
168 | } |
||
169 | |||
170 | /** |
||
171 | * {@inheritdoc} |
||
172 | */ |
||
173 | 6 | public function withStatus($code, $reasonPhrase = ''): ResponseInterface |
|
174 | { |
||
175 | 6 | $this->assertStatus($code); |
|
176 | 5 | $this->assertReasonPhrase($reasonPhrase); |
|
177 | |||
178 | 3 | if ($reasonPhrase === '' && isset(self::$statusCode[$code])) { |
|
179 | 1 | $reasonPhrase = self::$statusCode[$code]; |
|
180 | } |
||
181 | |||
182 | 3 | $clone = clone $this; |
|
183 | 3 | $clone->status = $code; |
|
184 | 3 | $clone->reasonPhrase = $reasonPhrase; |
|
185 | |||
186 | 3 | return $clone; |
|
187 | } |
||
188 | |||
189 | /** |
||
190 | * {@inheritdoc} |
||
191 | */ |
||
192 | 1 | public function getReasonPhrase(): string |
|
193 | { |
||
194 | 1 | return $this->reasonPhrase; |
|
195 | } |
||
196 | |||
197 | /* |
||
198 | |-------------------------------------------------------------------------- |
||
199 | | Non PSR-7 Methods. |
||
200 | |-------------------------------------------------------------------------- |
||
201 | */ |
||
202 | |||
203 | /** |
||
204 | * Throw exception when the HTTP status code is not valid. |
||
205 | * |
||
206 | * @param int $code HTTP status code. |
||
207 | * |
||
208 | * @return void |
||
209 | * |
||
210 | * @throws InvalidArgumentException |
||
211 | */ |
||
212 | 10 | protected function assertStatus($code) |
|
213 | { |
||
214 | 10 | if (!is_integer($code)) { |
|
0 ignored issues
–
show
|
|||
215 | 1 | throw new InvalidArgumentException( |
|
216 | 1 | sprintf( |
|
217 | 1 | 'Status code should be an integer value, but "%s" provided.', |
|
218 | 1 | gettype($code) |
|
219 | 1 | ) |
|
220 | 1 | ); |
|
221 | } |
||
222 | |||
223 | 10 | if (!($code > 100 && $code < 599)) { |
|
224 | 1 | throw new InvalidArgumentException( |
|
225 | 1 | sprintf( |
|
226 | 1 | 'Status code should be in a range of 100-599, but "%s" provided.', |
|
227 | 1 | $code |
|
228 | 1 | ) |
|
229 | 1 | ); |
|
230 | } |
||
231 | } |
||
232 | |||
233 | /** |
||
234 | * Throw exception when the HTTP reason phrase is not valid. |
||
235 | * |
||
236 | * @param string $reasonPhrase |
||
237 | * |
||
238 | * @return void |
||
239 | * |
||
240 | * @throws InvalidArgumentException |
||
241 | */ |
||
242 | 9 | protected function assertReasonPhrase($reasonPhrase) |
|
243 | { |
||
244 | 9 | if ($reasonPhrase === '') { |
|
245 | 1 | return; |
|
246 | } |
||
247 | |||
248 | 9 | if (!is_string($reasonPhrase)) { |
|
0 ignored issues
–
show
|
|||
249 | 1 | throw new InvalidArgumentException( |
|
250 | 1 | sprintf( |
|
251 | 1 | 'Reason phrase must be a string, but "%s" provided.', |
|
252 | 1 | gettype($reasonPhrase) |
|
253 | 1 | ) |
|
254 | 1 | ); |
|
255 | } |
||
256 | |||
257 | // Special characters, such as "line breaks", "tab" and others... |
||
258 | 9 | $escapeCharacters = [ |
|
259 | 9 | '\f', '\r', '\n', '\t', '\v', '\0', '[\b]', '\s', '\S', '\w', '\W', '\d', '\D', '\b', '\B', '\cX', '\xhh', '\uhhhh' |
|
260 | 9 | ]; |
|
261 | |||
262 | 9 | $filteredPhrase = str_replace($escapeCharacters, '', $reasonPhrase); |
|
263 | |||
264 | 9 | if ($reasonPhrase !== $filteredPhrase) { |
|
265 | 1 | foreach ($escapeCharacters as $escape) { |
|
266 | 1 | if (strpos($reasonPhrase, $escape) !== false) { |
|
267 | 1 | throw new InvalidArgumentException( |
|
268 | 1 | sprintf( |
|
269 | 1 | 'Reason phrase contains "%s" that is considered as a prohibited character.', |
|
270 | 1 | $escape |
|
271 | 1 | ) |
|
272 | 1 | ); |
|
273 | } |
||
274 | } |
||
275 | } |
||
276 | } |
||
277 | } |
||
278 |
The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g.
excluded_paths: ["lib/*"]
, you can move it to the dependency path list as follows:For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths