This project does not seem to handle request data directly as such no vulnerable execution paths were found.
include
, or for example
via PHP's auto-loading mechanism.
These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more
1 | <?php |
||
2 | /** |
||
3 | * @author Todd Burry <[email protected]> |
||
4 | * @copyright 2009-2014 Vanilla Forums Inc. |
||
5 | * @license MIT |
||
6 | */ |
||
7 | |||
8 | namespace Garden; |
||
9 | |||
10 | use JsonSerializable; |
||
11 | |||
12 | /** |
||
13 | * A class that contains the information in an http request. |
||
14 | */ |
||
15 | class Request implements JsonSerializable { |
||
16 | |||
17 | /// Constants /// |
||
18 | const METHOD_HEAD = 'HEAD'; |
||
19 | const METHOD_GET = 'GET'; |
||
20 | const METHOD_POST = 'POST'; |
||
21 | const METHOD_PUT = 'PUT'; |
||
22 | const METHOD_PATCH = 'PATCH'; |
||
23 | const METHOD_DELETE = 'DELETE'; |
||
24 | const METHOD_OPTIONS = 'OPTIONS'; |
||
25 | |||
26 | /// Properties /// |
||
27 | |||
28 | /** |
||
29 | * |
||
30 | * @var array The data in this request. |
||
31 | */ |
||
32 | protected $env; |
||
33 | |||
34 | /** |
||
35 | * @var Request The currently dispatched request. |
||
36 | */ |
||
37 | protected static $current; |
||
38 | |||
39 | /** |
||
40 | * @var array The default environment for constructed requests. |
||
41 | */ |
||
42 | protected static $defaultEnv; |
||
43 | |||
44 | protected static $knownExtensions = [ |
||
45 | '.html' => 'text/html', |
||
46 | '.json' => 'application/json', |
||
47 | '.txt' => 'text/plain', |
||
48 | '.xml' => 'application/xml' |
||
49 | ]; |
||
50 | |||
51 | /** |
||
52 | * @var array The global environment for the request. |
||
53 | */ |
||
54 | protected static $globalEnv; |
||
55 | |||
56 | /** |
||
57 | * Special-case HTTP headers that are otherwise unidentifiable as HTTP headers. |
||
58 | * Typically, HTTP headers in the $_SERVER array will be prefixed with |
||
59 | * `HTTP_` or `X_`. These are not so we list them here for later reference. |
||
60 | * |
||
61 | * @var array |
||
62 | */ |
||
63 | protected static $specialHeaders = array( |
||
64 | 'CONTENT_TYPE', |
||
65 | 'CONTENT_LENGTH', |
||
66 | 'PHP_AUTH_USER', |
||
67 | 'PHP_AUTH_PW', |
||
68 | 'PHP_AUTH_DIGEST', |
||
69 | 'AUTH_TYPE' |
||
70 | ); |
||
71 | |||
72 | /// Methods /// |
||
73 | |||
74 | /** |
||
75 | * Initialize a new instance of the {@link Request} class. |
||
76 | * |
||
77 | * @param string $url The url of the request or blank to use the current environment. |
||
78 | * @param string $method The request method. |
||
79 | * @param mixed $data The request data. This is the query string for GET requests or the body for other requests. |
||
80 | */ |
||
81 | 79 | public function __construct($url = '', $method = '', $data = null) { |
|
82 | 79 | if ($url) { |
|
83 | 70 | $this->env = (array)static::defaultEnvironment(); |
|
84 | // Instantiate the request from the url. |
||
85 | 70 | $this->setUrl($url); |
|
86 | 70 | if ($method) { |
|
87 | 56 | $this->setMethod($method); |
|
88 | 56 | } |
|
89 | 70 | if (is_array($data)) { |
|
90 | 2 | $this->setData($data); |
|
91 | 2 | } |
|
92 | 70 | } else { |
|
93 | // Instantiate the request from the global environment. |
||
94 | 9 | $this->env = static::globalEnvironment(); |
|
0 ignored issues
–
show
|
|||
95 | 9 | if ($method) { |
|
96 | $this->setMethod($method); |
||
97 | } |
||
98 | 9 | if (is_array($data)) { |
|
99 | $this->setData($data); |
||
100 | } |
||
101 | } |
||
102 | |||
103 | 79 | static::overrideEnvironment($this->env); |
|
104 | 79 | } |
|
105 | |||
106 | /** |
||
107 | * Convert a request to a string. |
||
108 | * |
||
109 | * @return string Returns the url of the request. |
||
110 | */ |
||
111 | 1 | public function __toString() { |
|
112 | 1 | return $this->getUrl(); |
|
113 | } |
||
114 | |||
115 | /** |
||
116 | * Gets or sets the current request. |
||
117 | * |
||
118 | * @param Request $request Pass a request object to set the current request. |
||
119 | * @return Request Returns the current request if {@link Request} is null or the previous request otherwise. |
||
120 | */ |
||
121 | 44 | public static function current(Request $request = null) { |
|
122 | 44 | if ($request !== null) { |
|
123 | 44 | $bak = self::$current; |
|
124 | 44 | self::$current = $request; |
|
125 | 44 | return $bak; |
|
126 | } |
||
127 | 1 | return self::$current; |
|
128 | } |
||
129 | |||
130 | /** |
||
131 | * Gets or updates the default environment. |
||
132 | * |
||
133 | * @param string|array|null $key Specifies a specific key in the environment array. |
||
134 | * If you pass an array for this parameter then you can set the default environment. |
||
135 | * @param bool $merge Whether or not to merge the new value. |
||
136 | * @return array|mixed Returns the value at {@link $key} or the entire environment array. |
||
137 | * @throws \InvalidArgumentException Throws an exception when {@link $key} is not valid. |
||
138 | */ |
||
139 | 71 | public static function defaultEnvironment($key = null, $merge = false) { |
|
140 | 71 | if (self::$defaultEnv === null) { |
|
141 | 1 | self::$defaultEnv = array( |
|
142 | 1 | 'REQUEST_METHOD' => 'GET', |
|
143 | 1 | 'X_REWRITE' => true, |
|
144 | 1 | 'SCRIPT_NAME' => '', |
|
145 | 1 | 'PATH_INFO' => '/', |
|
146 | 1 | 'EXT' => '', |
|
147 | 1 | 'QUERY' => [], |
|
148 | 1 | 'SERVER_NAME' => 'localhost', |
|
149 | 1 | 'SERVER_PORT' => 80, |
|
150 | 1 | 'HTTP_ACCEPT' => 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8', |
|
151 | 1 | 'HTTP_ACCEPT_LANGUAGE' => 'en-US,en;q=0.8', |
|
152 | 1 | 'HTTP_ACCEPT_CHARSET' => 'ISO-8859-1,utf-8;q=0.7,*;q=0.3', |
|
153 | 1 | 'HTTP_USER_AGENT' => 'Garden/0.1 (Howdy stranger)', |
|
154 | 1 | 'REMOTE_ADDR' => '127.0.0.1', |
|
155 | 1 | 'URL_SCHEME' => 'http', |
|
156 | 1 | 'INPUT' => [], |
|
157 | 1 | ); |
|
158 | 1 | } |
|
159 | |||
160 | 71 | if ($key === null) { |
|
161 | 71 | return self::$defaultEnv; |
|
162 | 45 | } elseif (is_array($key)) { |
|
163 | 44 | if ($merge) { |
|
164 | 44 | self::$defaultEnv = array_merge(self::$defaultEnv, $key); |
|
165 | 44 | } else { |
|
166 | self::$defaultEnv = $key; |
||
167 | } |
||
168 | 44 | return self::$defaultEnv; |
|
169 | 1 | } elseif (is_string($key)) { |
|
170 | 1 | return val($key, self::$defaultEnv); |
|
171 | } else { |
||
172 | throw new \InvalidArgumentException("Argument #1 for Request::globalEnvironment() is invalid.", 422); |
||
173 | } |
||
174 | } |
||
175 | |||
176 | /** |
||
177 | * Parse request information from the current environment. |
||
178 | * |
||
179 | * The environment contains keys based on the Rack protocol (see http://rack.rubyforge.org/doc/SPEC.html). |
||
180 | * |
||
181 | * @param mixed $key The environment variable to look at. |
||
182 | * - null: Return the entire environment. |
||
183 | * - true: Force a re-parse of the environment and return the entire environment. |
||
184 | * - string: One of the environment variables. |
||
185 | * @return array|string Returns the global environment or the value at {@link $key}. |
||
186 | */ |
||
187 | 9 | public static function globalEnvironment($key = null) { |
|
188 | // Check to parse the environment. |
||
189 | 9 | if ($key === true || !isset(self::$globalEnv)) { |
|
190 | 1 | self::$globalEnv = static::parseServerVariables(); |
|
191 | 1 | } |
|
192 | |||
193 | 9 | if ($key) { |
|
194 | return val($key, self::$globalEnv); |
||
195 | } |
||
196 | 9 | return self::$globalEnv; |
|
197 | } |
||
198 | |||
199 | /** |
||
200 | * Parse the various server variables to build the global environment. |
||
201 | * |
||
202 | * @return array Returns an array suitable to be used as the {@see Request::$globalEnv}. |
||
203 | * @see Request::globalEnvironment(). |
||
204 | */ |
||
205 | 1 | protected static function parseServerVariables() { |
|
206 | 1 | $env = static::defaultEnvironment(); |
|
207 | |||
208 | // REQUEST_METHOD. |
||
209 | 1 | $env['REQUEST_METHOD'] = val('REQUEST_METHOD', $_SERVER) ?: 'CONSOLE'; |
|
210 | |||
211 | // SCRIPT_NAME: This is the root directory of the application. |
||
212 | 1 | $script_name = rtrim_substr($_SERVER['SCRIPT_NAME'], 'index.php'); |
|
213 | 1 | $env['SCRIPT_NAME'] = rtrim($script_name, '/'); |
|
214 | |||
215 | // PATH_INFO. |
||
216 | 1 | $path = isset($_SERVER['PATH_INFO']) ? $_SERVER['PATH_INFO'] : ''; |
|
217 | |||
218 | // Strip the extension from the path. |
||
219 | 1 | list($path, $ext) = static::splitPathExt($path); |
|
220 | 1 | $env['PATH_INFO'] = '/' . ltrim($path, '/'); |
|
221 | 1 | $env['EXT'] = $ext; |
|
222 | |||
223 | // QUERY. |
||
224 | 1 | $get = $_GET; |
|
225 | 1 | $env['QUERY'] = $get; |
|
226 | |||
227 | // SERVER_NAME. |
||
228 | 1 | $host = array_select( |
|
229 | 1 | ['HTTP_X_FORWARDED_HOST', 'HTTP_HOST', 'SERVER_NAME'], |
|
230 | $_SERVER |
||
231 | 1 | ); |
|
232 | 1 | list($host) = explode(':', $host, 2); |
|
233 | 1 | $env['SERVER_NAME'] = $host; |
|
234 | |||
235 | // HTTP_* headers. |
||
236 | 1 | $env = array_replace($env, static::extractHeaders($_SERVER)); |
|
237 | |||
238 | // URL_SCHEME. |
||
239 | 1 | $url_scheme = 'http'; |
|
240 | // Web server-originated SSL. |
||
241 | 1 | if (isset($_SERVER['HTTPS']) && strtolower($_SERVER['HTTPS']) == 'on') { |
|
242 | $url_scheme = 'https'; |
||
243 | } |
||
244 | 1 | $url_scheme = array_select([ |
|
245 | 1 | 'HTTP_X_ORIGINALLY_FORWARDED_PROTO', // varnish modifies the scheme |
|
246 | 'HTTP_X_FORWARDED_PROTO' // load balancer-originated (and terminated) ssl |
||
247 | 1 | ], $_SERVER, $url_scheme); |
|
248 | 1 | $env['URL_SCHEME'] = $url_scheme; |
|
249 | |||
250 | // SERVER_PORT. |
||
251 | 1 | $server_port = (int)val('SERVER_PORT', $_SERVER, $url_scheme === 'https' ? 443 :80); |
|
252 | 1 | $env['SERVER_PORT'] = $server_port; |
|
253 | |||
254 | // INPUT: The entire input. |
||
255 | // Input stream (readable one time only; not available for multipart/form-data requests) |
||
256 | 1 | switch (val('CONTENT_TYPE', $env)) { |
|
257 | 1 | case 'application/json': |
|
258 | $input_raw = @file_get_contents('php://input'); |
||
259 | $input = @json_decode($input_raw, true); |
||
260 | break; |
||
261 | 1 | default: |
|
262 | 1 | $input = $_POST; |
|
263 | 1 | $input_raw = null; |
|
264 | 1 | break; |
|
265 | 1 | } |
|
266 | 1 | $env['INPUT'] = $input; |
|
267 | 1 | $env['INPUT_RAW'] = $input_raw; |
|
268 | |||
269 | // IP Address. |
||
270 | // Load balancers set a different ip address. |
||
271 | 1 | $ip = array_select( |
|
272 | 1 | ['HTTP_X_ORIGINALLY_FORWARDED_FOR', 'HTTP_X_FORWARDED_FOR', 'REMOTE_ADDR'], |
|
273 | 1 | $_SERVER, |
|
274 | '127.0.0.1' |
||
275 | 1 | ); |
|
276 | 1 | $env['REMOTE_ADDR'] = force_ipv4($ip); |
|
277 | 1 | return $env; |
|
278 | } |
||
279 | |||
280 | /** |
||
281 | * Check for specific environment overrides. |
||
282 | * |
||
283 | * @param array &$env The environment to override. |
||
284 | */ |
||
285 | 79 | protected static function overrideEnvironment(&$env) { |
|
286 | 79 | $get =& $env['QUERY']; |
|
287 | |||
288 | // Check to override the method. |
||
289 | 79 | if (isset($get['x-method'])) { |
|
290 | 7 | $method = strtoupper($get['x-method']); |
|
291 | |||
292 | 7 | $getMethods = array(self::METHOD_GET, self::METHOD_HEAD, self::METHOD_OPTIONS); |
|
293 | |||
294 | // Don't allow get style methods to be overridden to post style methods. |
||
295 | 7 | if (!in_array($env['REQUEST_METHOD'], $getMethods) || in_array($method, $getMethods)) { |
|
296 | 7 | static::replaceEnv($env, 'REQUEST_METHOD', $method); |
|
297 | 7 | } else { |
|
298 | 4 | $env['X_METHOD_BLOCKED'] = true; |
|
299 | } |
||
300 | 7 | unset($get['x-method']); |
|
301 | 7 | } |
|
302 | |||
303 | // Force the path and extension to lowercase. |
||
304 | 79 | $path = strtolower($env['PATH_INFO']); |
|
305 | 79 | if ($path !== $env['PATH_INFO']) { |
|
306 | 1 | static::replaceEnv($env, 'PATH_INFO', $path); |
|
307 | 1 | } |
|
308 | |||
309 | 79 | $ext = strtolower($env['EXT']); |
|
310 | 79 | if ($ext !== $env['EXT']) { |
|
311 | static::replaceEnv($env, 'EXT', $ext); |
||
312 | } |
||
313 | |||
314 | // Check to override the accepts header. |
||
315 | 79 | if (isset(self::$knownExtensions[$ext]) && $env['HTTP_ACCEPT'] !== 'application/internal') { |
|
316 | 7 | static::replaceEnv($env, 'HTTP_ACCEPT', self::$knownExtensions[$ext]); |
|
317 | 7 | } |
|
318 | 79 | } |
|
319 | |||
320 | /** |
||
321 | * Get a value from the environment or the entire environment. |
||
322 | * |
||
323 | * @param string|null $key The key to get or null to get the entire environment. |
||
324 | * @param mixed $default The default value if {@link $key} is not found. |
||
325 | * @return mixed|array Returns the value at {@link $key}, {$link $default} or the entire environment array. |
||
326 | * @see Request::setEnv() |
||
327 | */ |
||
328 | 51 | public function getEnv($key = null, $default = null) { |
|
329 | 51 | if ($key === null) { |
|
330 | return $this->env; |
||
331 | } |
||
332 | 51 | return val(strtoupper($key), $this->env, $default); |
|
333 | } |
||
334 | |||
335 | /** |
||
336 | * Set a value from the environment or the entire environment. |
||
337 | * |
||
338 | * @param string|array $key The key to set or an array to set the entire environment. |
||
339 | * @param mixed $value The value to set. |
||
340 | * @return Request Returns $this for fluent calls. |
||
341 | * @throws \InvalidArgumentException Throws an exception when {@link $key} is invalid. |
||
342 | * @see Request::getEnv() |
||
343 | */ |
||
344 | 1 | public function setEnv($key, $value = null) { |
|
345 | 1 | if (is_string($key)) { |
|
346 | $this->env[strtoupper($key)] = $value; |
||
347 | 1 | } elseif (is_array($key)) { |
|
348 | 1 | $this->env = $key; |
|
349 | 1 | } else { |
|
350 | throw new \InvalidArgumentException("Argument 1 must be either a string or array.", 422); |
||
351 | } |
||
352 | 1 | return $this; |
|
353 | } |
||
354 | |||
355 | /** |
||
356 | * Replace an environment variable with another one and back up the old one in a *_RAW key. |
||
357 | * |
||
358 | * @param array &$env The environment array. |
||
359 | * @param string $key The environment key. |
||
360 | * @param mixed $value The new environment value. |
||
361 | * @return mixed Returns the old value or null if there was no old value. |
||
362 | */ |
||
363 | 15 | public static function replaceEnv(&$env, $key, $value) { |
|
364 | 15 | $key = strtoupper($key); |
|
365 | |||
366 | 15 | $result = null; |
|
367 | 15 | if (isset($env[$key])) { |
|
368 | 15 | $result = $env[$key]; |
|
369 | 15 | $env[$key.'_RAW'] = $result; |
|
370 | 15 | } |
|
371 | 15 | $env[$key] = $value; |
|
372 | 15 | return $result; |
|
373 | } |
||
374 | |||
375 | /** |
||
376 | * Restore an environment variable that was replaced with {@link Request::replaceEnv()}. |
||
377 | * |
||
378 | * @param array &$env The environment array. |
||
379 | * @param string $key The environment key. |
||
380 | * @return mixed Returns the current environment value. |
||
381 | */ |
||
382 | public static function restoreEnv(&$env, $key) { |
||
383 | $key = strtoupper($key); |
||
384 | |||
385 | if (isset($env[$key.'_RAW'])) { |
||
386 | $env[$key] = $env[$key.'_RAW']; |
||
387 | unset($env[$key.'_RAW']); |
||
388 | return $env[$key]; |
||
389 | } elseif (isset($env[$key])) { |
||
390 | return $env[$key]; |
||
391 | } |
||
392 | return null; |
||
393 | } |
||
394 | |||
395 | /** |
||
396 | * Extract the headers from an array such as $_SERVER or the request's own $env. |
||
397 | * |
||
398 | * @param array $arr The array to extract. |
||
399 | * @return array The extracted headers. |
||
400 | */ |
||
401 | 1 | public static function extractHeaders($arr) { |
|
402 | 1 | $result = []; |
|
403 | |||
404 | 1 | foreach ($arr as $key => $value) { |
|
405 | 1 | $key = strtoupper($key); |
|
406 | 1 | if (strpos($key, 'X_') === 0 || strpos($key, 'HTTP_') === 0 || in_array($key, static::$specialHeaders)) { |
|
407 | if ($key === 'HTTP_CONTENT_TYPE' || $key === 'HTTP_CONTENT_LENGTH') { |
||
408 | continue; |
||
409 | } |
||
410 | $result[$key] = $value; |
||
411 | } |
||
412 | 1 | } |
|
413 | 1 | return $result; |
|
414 | } |
||
415 | |||
416 | /** |
||
417 | * Retrieves all message headers. |
||
418 | * |
||
419 | * @return array Returns an associative array of the message's headers. |
||
420 | * Each key represents a header name, and each value is an array of strings. |
||
421 | */ |
||
422 | public function getHeaders() { |
||
423 | $result = []; |
||
424 | |||
425 | foreach ($this->env as $key => $value) { |
||
426 | if (stripos($key, 'HTTP_') === 0 && !str_ends($key, '_RAW')) { |
||
427 | $headerKey = static::normalizeHeaderName(substr($key, 5)); |
||
428 | $result[$headerKey][] = $value; |
||
429 | } |
||
430 | } |
||
431 | return $result; |
||
432 | } |
||
433 | |||
434 | /** |
||
435 | * Get the hostname of the request. |
||
436 | * |
||
437 | * @return string Returns the host. |
||
438 | */ |
||
439 | 4 | public function getHost() { |
|
440 | 4 | return (string)$this->env['SERVER_NAME']; |
|
441 | } |
||
442 | |||
443 | /** |
||
444 | * Set the hostname of the request. |
||
445 | * |
||
446 | * @param string $host The hostname. |
||
447 | * @return Request Returns $this for fluent calls. |
||
448 | */ |
||
449 | 18 | public function setHost($host) { |
|
450 | 18 | $this->env['SERVER_NAME'] = $host; |
|
451 | 18 | return $this; |
|
452 | } |
||
453 | |||
454 | /** |
||
455 | * Get the host and port, but only if the port is not the standard port for the request scheme. |
||
456 | * |
||
457 | * @return string Returns the host and port or just the host if this is the standard port. |
||
458 | * @see Request::getHost() |
||
459 | * @see Request::getPort() |
||
460 | */ |
||
461 | 3 | public function getHostAndPort() { |
|
462 | 3 | $host = $this->getHost(); |
|
463 | 3 | $port = $this->getPort(); |
|
464 | |||
465 | // Only append the port if it is non-standard. |
||
466 | 3 | if (($port == 80 && $this->getScheme() === 'http') || ($port == 443 && $this->getScheme() === 'https')) { |
|
467 | 1 | $port = ''; |
|
468 | 1 | } else { |
|
469 | 2 | $port = ':'.$port; |
|
470 | } |
||
471 | 3 | return $host.$port; |
|
472 | } |
||
473 | |||
474 | /** |
||
475 | * Get the ip address of the request. |
||
476 | * |
||
477 | * @return string Returns the current ip address. |
||
478 | */ |
||
479 | 1 | public function getIP() { |
|
480 | 1 | return (string)$this->env['REMOTE_ADDR']; |
|
481 | } |
||
482 | |||
483 | /** |
||
484 | * Set the ip address of the request. |
||
485 | * |
||
486 | * @param string $ip The new ip address. |
||
487 | * @return Request Returns $this for fluent calls. |
||
488 | */ |
||
489 | 1 | public function setIP($ip) { |
|
490 | 1 | $this->env['REMOTE_ADDR'] = $ip; |
|
491 | 1 | return $this; |
|
492 | } |
||
493 | |||
494 | /** |
||
495 | * Gets whether or not this is a DELETE request. |
||
496 | * |
||
497 | * @return bool Returns true if this is a DELETE request, false otherwise. |
||
498 | */ |
||
499 | 1 | public function isDelete() { |
|
500 | 1 | return $this->getMethod() === self::METHOD_DELETE; |
|
501 | } |
||
502 | |||
503 | /** |
||
504 | * Gets whether or not this is a GET request. |
||
505 | * |
||
506 | * @return bool Returns true if this is a GET request, false otherwise. |
||
507 | */ |
||
508 | 1 | public function isGet() { |
|
509 | 1 | return $this->getMethod() === self::METHOD_GET; |
|
510 | } |
||
511 | |||
512 | /** |
||
513 | * Gets whether or not this is a HEAD request. |
||
514 | * |
||
515 | * @return bool Returns true if this is a HEAD request, false otherwise. |
||
516 | */ |
||
517 | 1 | public function isHead() { |
|
518 | 1 | return $this->getMethod() === self::METHOD_HEAD; |
|
519 | } |
||
520 | |||
521 | /** |
||
522 | * Gets whether or not this is an OPTIONS request. |
||
523 | * |
||
524 | * @return bool Returns true if this is an OPTIONS request, false otherwise. |
||
525 | */ |
||
526 | 1 | public function isOptions() { |
|
527 | 1 | return $this->getMethod() === self::METHOD_OPTIONS; |
|
528 | } |
||
529 | |||
530 | /** |
||
531 | * Gets whether or not this is a PATCH request. |
||
532 | * |
||
533 | * @return bool Returns true if this is a PATCH request, false otherwise. |
||
534 | */ |
||
535 | 1 | public function isPatch() { |
|
536 | 1 | return $this->getMethod() === self::METHOD_PATCH; |
|
537 | } |
||
538 | |||
539 | /** |
||
540 | * Gets whether or not this is a POST request. |
||
541 | * |
||
542 | * @return bool Returns true if this is a POST request, false otherwise. |
||
543 | */ |
||
544 | 1 | public function isPost() { |
|
545 | 1 | return $this->getMethod() === self::METHOD_POST; |
|
546 | } |
||
547 | |||
548 | /** |
||
549 | * Gets whether or not this is a PUT request. |
||
550 | * |
||
551 | * @return bool Returns true if this is a PUT request, false otherwise. |
||
552 | */ |
||
553 | 1 | public function isPut() { |
|
554 | 1 | return $this->getMethod() === self::METHOD_PUT; |
|
555 | } |
||
556 | |||
557 | /** |
||
558 | * Get the http method of the request. |
||
559 | * |
||
560 | * @return string Returns the http method of the request. |
||
561 | */ |
||
562 | 61 | public function getMethod() { |
|
563 | 61 | return (string)$this->env['REQUEST_METHOD']; |
|
564 | } |
||
565 | |||
566 | /** |
||
567 | * Set the http method of the request. |
||
568 | * |
||
569 | * @param string $method The new request method. |
||
570 | * @return Request Returns $this for fluent calls. |
||
571 | */ |
||
572 | 57 | public function setMethod($method) { |
|
573 | 57 | $this->env['REQUEST_METHOD'] = strtoupper($method); |
|
574 | 57 | return $this; |
|
575 | } |
||
576 | |||
577 | /** |
||
578 | * Gets the request path. |
||
579 | * |
||
580 | * Note that this returns the path without the file extension. |
||
581 | * If you want the full path use {@link Request::getFullPath()}. |
||
582 | * |
||
583 | * @return string Returns the request path. |
||
584 | */ |
||
585 | 48 | public function getPath() { |
|
586 | 48 | return (string)$this->env['PATH_INFO']; |
|
587 | } |
||
588 | |||
589 | /** |
||
590 | * Sets the request path. |
||
591 | * |
||
592 | * @param string $path The path to set. |
||
593 | * @return Request Returns $this for fluent calls. |
||
594 | */ |
||
595 | 1 | public function setPath($path) { |
|
596 | 1 | $this->env['PATH_INFO'] = (string)$path; |
|
597 | 1 | return $this; |
|
598 | } |
||
599 | |||
600 | /** |
||
601 | * Get the file extension of the request. |
||
602 | * |
||
603 | * @return string Returns the file extension. |
||
604 | */ |
||
605 | 2 | public function getExt() { |
|
606 | 2 | return (string)$this->env['EXT']; |
|
607 | } |
||
608 | |||
609 | /** |
||
610 | * Sets the file extension of the request. |
||
611 | * |
||
612 | * @param string $ext The file extension to set. |
||
613 | * @return Request Returns $this for fluent calls. |
||
614 | */ |
||
615 | 2 | public function setExt($ext) { |
|
616 | 2 | if ($ext) { |
|
617 | 1 | $this->env['EXT'] = '.'.ltrim($ext, '.'); |
|
618 | 1 | } else { |
|
619 | 1 | $this->env['EXT'] = ''; |
|
620 | } |
||
621 | 2 | return $this; |
|
622 | } |
||
623 | |||
624 | /** |
||
625 | * Get the path and file extenstion. |
||
626 | * |
||
627 | * @return string Returns the path and file extension. |
||
628 | * @see Request::setPathExt() |
||
629 | */ |
||
630 | 5 | public function getPathExt() { |
|
631 | 5 | return $this->env['PATH_INFO'].$this->env['EXT']; |
|
632 | } |
||
633 | |||
634 | /** |
||
635 | * Set the path with file extension. |
||
636 | * |
||
637 | * @param string $path The path to set. |
||
638 | * @return Request Returns $this for fluent calls. |
||
639 | * @see Request::getPathExt() |
||
640 | */ |
||
641 | 65 | public function setPathExt($path) { |
|
642 | // Strip the extension from the path. |
||
643 | 65 | if (substr($path, -1) !== '/' && ($pos = strrpos($path, '.')) !== false) { |
|
644 | 9 | $ext = substr($path, $pos); |
|
645 | 9 | $path = substr($path, 0, $pos); |
|
646 | 9 | $this->env['EXT'] = $ext; |
|
647 | 9 | } else { |
|
648 | 58 | $this->env['EXT'] = ''; |
|
649 | } |
||
650 | 65 | $this->env['PATH_INFO'] = $path; |
|
651 | 65 | return $this; |
|
652 | } |
||
653 | |||
654 | /** |
||
655 | * Get the full path of the request. |
||
656 | * |
||
657 | * The full path consists of the root + path + extension. |
||
658 | * |
||
659 | * @return string Returns the full path. |
||
660 | */ |
||
661 | 3 | public function getFullPath() { |
|
662 | 3 | return $this->getRoot().$this->getPathExt(); |
|
663 | } |
||
664 | |||
665 | /** |
||
666 | * Set the full path of the request. |
||
667 | * |
||
668 | * The full path consists of the root + path + extension. |
||
669 | * This method examins the current root to see if the root can still be used or should be removed. |
||
670 | * Special care must be taken when calling this method to make sure you don't remove the root unintentionally. |
||
671 | * |
||
672 | * @param string $fullPath The full path to set. |
||
673 | * @return Request Returns $this for fluent calls. |
||
674 | */ |
||
675 | 64 | public function setFullPath($fullPath) { |
|
676 | 64 | $fullPath = '/'.ltrim($fullPath, '/'); |
|
677 | |||
678 | // Try stripping the root out of the path first. |
||
679 | 64 | $root = (string)$this->getRoot(); |
|
680 | |||
681 | 64 | if ($root && |
|
682 | 64 | strpos($fullPath, $root) === 0 && |
|
683 | 64 | (strlen($fullPath) === strlen($root) || substr($fullPath, strlen($root), 1) === '/')) { |
|
684 | |||
685 | 1 | $this->setPathExt(substr($fullPath, strlen($root))); |
|
686 | 1 | } else { |
|
687 | 64 | $this->setRoot(''); |
|
688 | 64 | $this->setPathExt($fullPath); |
|
689 | } |
||
690 | |||
691 | 64 | return $this; |
|
692 | } |
||
693 | |||
694 | /** |
||
695 | * Normalize a header field name to follow the general HTTP header `Capital-Dash-Separated` convention. |
||
696 | * |
||
697 | * @param string $name The header name to normalize. |
||
698 | * @return string Returns the normalized header name. |
||
699 | */ |
||
700 | public static function normalizeHeaderName($name) { |
||
701 | $result = str_replace(' ', '-', ucwords(str_replace(['-', '_'], ' ', strtolower($name)))); |
||
702 | return $result; |
||
703 | } |
||
704 | |||
705 | /** |
||
706 | * Gets the port. |
||
707 | * |
||
708 | * @return int Returns the port. |
||
709 | */ |
||
710 | 3 | public function getPort() { |
|
711 | 3 | return (int)$this->env['SERVER_PORT']; |
|
712 | } |
||
713 | |||
714 | /** |
||
715 | * Sets the port. |
||
716 | * |
||
717 | * Setting the port to 80 or 443 will also set the scheme to http or https respectively. |
||
718 | * |
||
719 | * @param int $port The port to set. |
||
720 | * @return Request Returns $this for fluent calls. |
||
721 | */ |
||
722 | 17 | public function setPort($port) { |
|
723 | 17 | $this->env['SERVER_PORT'] = $port; |
|
724 | |||
725 | // Override the scheme for standard ports. |
||
726 | 17 | if ($port === 80) { |
|
727 | 15 | $this->setScheme('http'); |
|
728 | 17 | } elseif ($port === 443) { |
|
729 | 1 | $this->setScheme('https'); |
|
730 | 1 | } |
|
731 | |||
732 | 17 | return $this; |
|
733 | } |
||
734 | |||
735 | /** |
||
736 | * Get an item from the query string array. |
||
737 | * |
||
738 | * @param string|null $key Either a string key or null to get the entire array. |
||
739 | * @param mixed|null $default The default to return if {@link $key} is not found. |
||
740 | * @return string|array|null Returns the query string value or the query string itself. |
||
741 | * @see Request::setQuery() |
||
742 | */ |
||
743 | 47 | public function getQuery($key = null, $default = null) { |
|
744 | 47 | if ($key === null) { |
|
745 | 39 | return $this->env['QUERY']; |
|
746 | } |
||
747 | 10 | return isset($this->env['QUERY'][$key]) ? $this->env['QUERY'][$key] : $default; |
|
748 | } |
||
749 | |||
750 | /** |
||
751 | * Set an item from the query string array. |
||
752 | * |
||
753 | * @param string|array $key Either a string key or an array to set the entire query string. |
||
754 | * @param mixed|null $value The value to set. |
||
755 | * @return Request Returns $this for fluent call. |
||
756 | * @throws \InvalidArgumentException Throws an exception when {@link $key is invalid}. |
||
757 | * @see Request::getQuery() |
||
758 | */ |
||
759 | 14 | View Code Duplication | public function setQuery($key, $value = null) { |
760 | 14 | if (is_string($key)) { |
|
761 | $this->env['QUERY'][$key] = $value; |
||
762 | 14 | } elseif (is_array($key)) { |
|
763 | 13 | $this->env['QUERY'] = $key; |
|
764 | 13 | } else { |
|
765 | 1 | throw new \InvalidArgumentException("Argument 1 must be a string or array.", 422); |
|
766 | } |
||
767 | 13 | return $this; |
|
768 | } |
||
769 | |||
770 | /** |
||
771 | * Get an item from the input array. |
||
772 | * |
||
773 | * @param string|null $key Either a string key or null to get the entire array. |
||
774 | * @param mixed|null $default The default to return if {@link $key} is not found. |
||
775 | * @return string|array|null Returns the query string value or the input array itself. |
||
776 | */ |
||
777 | 4 | public function getInput($key = null, $default = null) { |
|
778 | 4 | if ($key === null) { |
|
779 | 4 | return $this->env['INPUT']; |
|
780 | } |
||
781 | 1 | return isset($this->env['INPUT'][$key]) ? $this->env['INPUT'][$key] : $default; |
|
782 | } |
||
783 | |||
784 | /** |
||
785 | * Set an item from the input array. |
||
786 | * |
||
787 | * @param string|array $key Either a string key or an array to set the entire input. |
||
788 | * @param mixed|null $value The value to set. |
||
789 | * @return Request Returns $this for fluent call. |
||
790 | * @throws \InvalidArgumentException Throws an exception when {@link $key is invalid}. |
||
791 | */ |
||
792 | 4 | View Code Duplication | public function setInput($key, $value = null) { |
793 | 4 | if (is_string($key)) { |
|
794 | $this->env['INPUT'][$key] = $value; |
||
795 | 4 | } elseif (is_array($key)) { |
|
796 | 3 | $this->env['INPUT'] = $key; |
|
797 | 3 | } else { |
|
798 | 1 | throw new \InvalidArgumentException("Argument 1 must be a string or array.", 422); |
|
799 | } |
||
800 | 3 | return $this; |
|
801 | } |
||
802 | |||
803 | /** |
||
804 | * Gets the query on input depending on the http method. |
||
805 | * |
||
806 | * @param string|null $key Either a string key or null to get the entire array. |
||
807 | * @param mixed|null $default The default to return if {@link $key} is not found. |
||
808 | * @return mixed|array Returns the value at {@link $key} or the entire array. |
||
809 | * @see Request::setData() |
||
810 | * @see Request::getInput() |
||
811 | * @see Request::getQuery() |
||
812 | * @see Request::hasInput() |
||
813 | */ |
||
814 | 1 | public function getData($key = null, $default = null) { |
|
815 | 1 | if ($this->hasInput()) { |
|
816 | 1 | return $this->getInput($key, $default); |
|
817 | } else { |
||
818 | 1 | return $this->getQuery($key, $default); |
|
819 | } |
||
820 | } |
||
821 | |||
822 | /** |
||
823 | * Sets the query on input depending on the http method. |
||
824 | * |
||
825 | * @param string|array $key Either a string key or an array to set the entire data. |
||
826 | * @param mixed|null $value The value to set. |
||
827 | * @return Request Returns $this for fluent call. |
||
828 | * @see Request::getData() |
||
829 | * @see Request::setInput() |
||
830 | * @see Request::setQuery() |
||
831 | * @see Request::hasInput() |
||
832 | */ |
||
833 | 2 | public function setData($key, $value = null) { |
|
834 | 2 | if ($this->hasInput()) { |
|
835 | 2 | $this->setInput($key, $value); |
|
836 | 2 | } else { |
|
837 | 1 | $this->setQuery($key, $value); |
|
838 | } |
||
839 | 2 | return $this; |
|
840 | } |
||
841 | |||
842 | /** |
||
843 | * Returns true if an http method has input (a post body). |
||
844 | * |
||
845 | * @param string $method The http method to test. |
||
846 | * @return bool Returns true if the http method has input, false otherwise. |
||
847 | */ |
||
848 | 2 | public function hasInput($method = '') { |
|
849 | 2 | if (!$method) { |
|
850 | 2 | $method = $this->getMethod(); |
|
851 | 2 | } |
|
852 | |||
853 | 2 | switch (strtoupper($method)) { |
|
854 | 2 | case self::METHOD_GET: |
|
855 | 2 | case self::METHOD_DELETE: |
|
856 | 2 | case self::METHOD_HEAD: |
|
857 | 2 | case self::METHOD_OPTIONS: |
|
858 | 1 | return false; |
|
859 | 2 | } |
|
860 | 2 | return true; |
|
861 | } |
||
862 | |||
863 | /** |
||
864 | * Get the root directory (SCRIPT_NAME) of the request. |
||
865 | * |
||
866 | * @return Returns the root directory of the request as a string. |
||
867 | * @see Request::setRoot() |
||
868 | */ |
||
869 | 65 | public function getRoot() { |
|
870 | 65 | return (string)$this->env['SCRIPT_NAME']; |
|
871 | } |
||
872 | |||
873 | /** |
||
874 | * Set the root directory (SCRIPT_NAME) of the request. |
||
875 | * |
||
876 | * This method will modify the set root to include a leading slash if it does not have one. |
||
877 | * A root of just "/" will be coerced to an empty string. |
||
878 | * |
||
879 | * @param string $root The new root directory. |
||
880 | * @return Request Returns $this for fluent calls. |
||
881 | * @see Request::getRoot() |
||
882 | */ |
||
883 | 65 | public function setRoot($root) { |
|
884 | 65 | $value = trim($root, '/'); |
|
885 | 65 | if ($value) { |
|
886 | 4 | $value = '/'.$value; |
|
887 | 4 | } |
|
888 | 65 | $this->env['SCRIPT_NAME'] = $value; |
|
889 | 65 | return $this; |
|
890 | } |
||
891 | |||
892 | /** |
||
893 | * Get the request scheme. |
||
894 | * |
||
895 | * The request scheme is usually http or https. |
||
896 | * |
||
897 | * @return string Retuns the scheme. |
||
898 | * @see Request::setScheme() |
||
899 | */ |
||
900 | 18 | public function getScheme() { |
|
901 | 18 | return (string)$this->env['URL_SCHEME']; |
|
902 | } |
||
903 | |||
904 | /** |
||
905 | * Set the request scheme. |
||
906 | * |
||
907 | * The request scheme is usually http or https. |
||
908 | * |
||
909 | * @param string $scheme The new scheme to set. |
||
910 | * @return Request Returns $this for fluent calls. |
||
911 | * @see Request::getScheme() |
||
912 | */ |
||
913 | 18 | public function setScheme($scheme) { |
|
914 | 18 | $this->env['URL_SCHEME'] = $scheme; |
|
915 | 18 | return $this; |
|
916 | } |
||
917 | |||
918 | /** |
||
919 | * Get the full url of the request. |
||
920 | * |
||
921 | * @return string Returns the full url of the request. |
||
922 | * @see Request::setUrl() |
||
923 | */ |
||
924 | 2 | public function getUrl() { |
|
925 | 2 | $query = $this->getQuery(); |
|
926 | return |
||
927 | 2 | $this->getScheme(). |
|
928 | 2 | '://'. |
|
929 | 2 | $this->getHostAndPort(). |
|
930 | 2 | $this->getRoot(). |
|
931 | 2 | $this->getPath(). |
|
932 | 2 | (!empty($query) ? '?'.http_build_query($query) : ''); |
|
933 | } |
||
934 | |||
935 | /** |
||
936 | * Set the full url of the request. |
||
937 | * |
||
938 | * @param string $url The new url. |
||
939 | * @return Request $this Returns $this for fluent calls. |
||
940 | * @see Request::getUrl() |
||
941 | */ |
||
942 | 70 | public function setUrl($url) { |
|
943 | // Parse the url and set the individual components. |
||
944 | 70 | $url_parts = parse_url($url); |
|
945 | |||
946 | 70 | if (isset($url_parts['scheme'])) { |
|
947 | 17 | $this->setScheme($url_parts['scheme']); |
|
948 | 17 | } |
|
949 | |||
950 | 70 | if (isset($url_parts['host'])) { |
|
951 | 17 | $this->setHost($url_parts['host']); |
|
952 | 17 | } |
|
953 | |||
954 | 70 | if (isset($url_parts['port'])) { |
|
955 | 2 | $this->setPort($url_parts['port']); |
|
956 | 70 | } elseif (isset($url_parts['scheme'])) { |
|
957 | 15 | $this->setPort($this->getScheme() === 'https' ? 443 : 80); |
|
958 | 15 | } |
|
959 | |||
960 | 70 | if (isset($url_parts['path'])) { |
|
961 | 63 | $this->setFullPath($url_parts['path']); |
|
962 | 63 | } |
|
963 | |||
964 | 70 | if (isset($url_parts['query'])) { |
|
965 | 11 | parse_str($url_parts['query'], $query); |
|
966 | 11 | if (is_array($query)) { |
|
967 | 11 | $this->setQuery($query); |
|
968 | 11 | } |
|
969 | 11 | } |
|
970 | 70 | return $this; |
|
971 | } |
||
972 | |||
973 | /** |
||
974 | * Construct a url on the current site. |
||
975 | * |
||
976 | * @param string $path The path of the url. |
||
977 | * @param mixed $domain Whether or not to include the domain. This can be one of the following. |
||
978 | * - false: The domain will not be included. |
||
979 | * - true: The domain will be included. |
||
980 | * - http: Force http. |
||
981 | * - https: Force https. |
||
982 | * - //: A schemeless domain will be included. |
||
983 | * - /: Just the path will be returned. |
||
984 | * @return string Returns the url. |
||
985 | */ |
||
986 | 1 | public function makeUrl($path, $domain = false) { |
|
987 | 1 | if (!$path) { |
|
988 | $path = $this->getPath(); |
||
989 | } |
||
990 | |||
991 | // Check for a specific scheme. |
||
992 | 1 | $scheme = $this->getScheme(); |
|
993 | 1 | if ($domain === 'http' || $domain === 'https') { |
|
994 | 1 | $scheme = $domain; |
|
995 | 1 | $domain = true; |
|
996 | 1 | } |
|
997 | |||
998 | 1 | if ($domain === true) { |
|
999 | 1 | $prefix = $scheme.'://'.$this->getHostAndPort().$this->getRoot(); |
|
1000 | 1 | } elseif ($domain === false) { |
|
1001 | 1 | $prefix = $this->getRoot(); |
|
1002 | 1 | } elseif ($domain === '//') { |
|
1003 | 1 | $prefix = '//'.$this->getHostAndPort().$this->getRoot(); |
|
1004 | 1 | } else { |
|
1005 | 1 | $prefix = ''; |
|
1006 | } |
||
1007 | |||
1008 | 1 | return $prefix.'/'.ltrim($path, '/'); |
|
1009 | } |
||
1010 | |||
1011 | /** |
||
1012 | * Split the file extension off a path. |
||
1013 | * |
||
1014 | * @param string $path The path to split. |
||
1015 | * @return array Returns an array in the form `['path', 'ext']`. |
||
1016 | */ |
||
1017 | 1 | protected static function splitPathExt($path) { |
|
1018 | 1 | if (substr($path, -1) !== '/' && ($pos = strrpos($path, '.')) !== false) { |
|
1019 | $ext = substr($path, $pos); |
||
1020 | $path = substr($path, 0, $pos); |
||
1021 | return [$path, $ext]; |
||
1022 | } else { |
||
1023 | 1 | return [$path, '']; |
|
1024 | } |
||
1025 | } |
||
1026 | |||
1027 | /** |
||
1028 | * Specify data which should be serialized to JSON. |
||
1029 | * |
||
1030 | * @link http://php.net/manual/en/jsonserializable.jsonserialize.php |
||
1031 | * @return mixed data which can be serialized by <b>json_encode</b>, |
||
1032 | * which is a value of any type other than a resource. |
||
1033 | */ |
||
1034 | 1 | public function jsonSerialize() { |
|
1035 | 1 | return $this->env; |
|
1036 | } |
||
1037 | } |
||
1038 |
Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.
For example, imagine you have a variable
$accountId
that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to theid
property of an instance of theAccount
class. This class holds a proper account, so the id value must no longer be false.Either this assignment is in error or a type check should be added for that assignment.