1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
/** |
4
|
|
|
* |
5
|
|
|
* This file is part of the Apix Project. |
6
|
|
|
* |
7
|
|
|
* (c) Franck Cassedanne <franck at ouarz.net> |
8
|
|
|
* |
9
|
|
|
* @license http://opensource.org/licenses/BSD-3-Clause New BSD License |
10
|
|
|
* |
11
|
|
|
*/ |
12
|
|
|
|
13
|
|
|
namespace Apix; |
14
|
|
|
|
15
|
|
|
class Request |
16
|
|
|
{ |
17
|
|
|
const METHOD_OVERRIDE = '_method'; |
18
|
|
|
|
19
|
|
|
/** |
20
|
|
|
* Holds the URI string |
21
|
|
|
* @var string |
22
|
|
|
*/ |
23
|
|
|
protected $uri = null; |
24
|
|
|
|
25
|
|
|
/** |
26
|
|
|
* The HTTP response headers array |
27
|
|
|
* @var array |
28
|
|
|
*/ |
29
|
|
|
protected $headers = array(); |
30
|
|
|
|
31
|
|
|
/** |
32
|
|
|
* Hold the request body (raw) data |
33
|
|
|
* @var string |
34
|
|
|
*/ |
35
|
|
|
protected $body = null; |
36
|
|
|
|
37
|
|
|
/** |
38
|
|
|
* Holds the HTTP method |
39
|
|
|
* @var string |
40
|
|
|
*/ |
41
|
|
|
protected $method = null; |
42
|
|
|
|
43
|
|
|
/** |
44
|
|
|
* Body stream scheme |
45
|
|
|
* @var string |
46
|
|
|
*/ |
47
|
|
|
protected $bodyStream = 'php://input'; |
48
|
|
|
|
49
|
|
|
/** |
50
|
|
|
* Constructor, at instanciation sets the minimum request properties |
51
|
|
|
*/ |
52
|
|
|
public function __construct() |
53
|
|
|
{ |
54
|
|
|
$this->setHeaders(); |
55
|
|
|
$this->setParams(); |
56
|
|
|
$this->setBody(); |
57
|
|
|
} |
58
|
|
|
|
59
|
|
|
/** |
60
|
|
|
* Sets and parse the provided URI, or if missing guess from the enviroment |
61
|
|
|
* |
62
|
|
|
* @param string|false $uri If false, extract one from $_SERVER variables |
63
|
|
|
*/ |
64
|
|
|
public function setUri($uri=false) |
65
|
|
|
{ |
66
|
|
|
$uri = false === $uri ? $this->getRequestedUri() : $uri; |
67
|
|
|
$uri = parse_url($uri, PHP_URL_PATH); |
68
|
|
|
|
69
|
|
|
// remove a trailing slash |
70
|
|
|
if ( $uri != '/' && substr($uri, -1) == '/' ) { |
71
|
|
|
$uri = substr($uri, 0, -1); |
72
|
|
|
} |
73
|
|
|
|
74
|
|
|
$this->uri = $uri; |
|
|
|
|
75
|
|
|
} |
76
|
|
|
|
77
|
|
|
/** |
78
|
|
|
* Gets the current URI, if undefined guess and set one up. |
79
|
|
|
* |
80
|
|
|
* @return string |
81
|
|
|
*/ |
82
|
|
|
public function getUri() |
83
|
|
|
{ |
84
|
|
|
if (null === $this->uri) { |
85
|
|
|
$this->setUri(); |
86
|
|
|
} |
87
|
|
|
|
88
|
|
|
return $this->uri; |
89
|
|
|
} |
90
|
|
|
|
91
|
|
|
/** |
92
|
|
|
* Gets the requested URI (from client, spoofable). |
93
|
|
|
* |
94
|
|
|
* @return string |
95
|
|
|
*/ |
96
|
|
|
public function getRequestedUri() |
|
|
|
|
97
|
|
|
{ |
98
|
|
|
if (isset($_SERVER['HTTP_X_REWRITE_URL'])) { |
99
|
|
|
$uri = $_SERVER['HTTP_X_REWRITE_URL']; |
100
|
|
|
} elseif (isset($_SERVER['IIS_WasUrlRewritten']) |
101
|
|
|
&& $_SERVER['IIS_WasUrlRewritten'] == '1' |
102
|
|
|
&& isset($_SERVER['UNENCODED_URL']) |
103
|
|
|
&& $_SERVER['UNENCODED_URL'] != '' |
104
|
|
|
) { |
105
|
|
|
$uri = $_SERVER['UNENCODED_URL']; |
106
|
|
|
} elseif (isset($_SERVER['REQUEST_URI'])) { |
107
|
|
|
$uri = $_SERVER['REQUEST_URI']; |
108
|
|
|
} elseif (isset($_SERVER['PATH_INFO'])) { |
109
|
|
|
$uri = $_SERVER['PATH_INFO']; |
110
|
|
|
} elseif (isset($_SERVER['ORIG_PATH_INFO'])) { |
111
|
|
|
$uri = $_SERVER['ORIG_PATH_INFO']; |
112
|
|
|
} |
113
|
|
|
|
114
|
|
|
return isset($uri) ? $uri : '/'; |
115
|
|
|
} |
116
|
|
|
|
117
|
|
|
/** |
118
|
|
|
* Gets the requested Scheme (from server). |
119
|
|
|
* |
120
|
|
|
* @return string |
121
|
|
|
*/ |
122
|
|
|
public function getRequestedScheme() |
|
|
|
|
123
|
|
|
{ |
124
|
|
|
return isset($_SERVER['HTTPS']) ? 'https' : 'http'; |
125
|
|
|
} |
126
|
|
|
|
127
|
|
|
/** |
128
|
|
|
* Gets the requested host (from client, spoofable). |
129
|
|
|
* |
130
|
|
|
* @return string |
131
|
|
|
*/ |
132
|
|
|
public function getRequestedHost() |
|
|
|
|
133
|
|
|
{ |
134
|
|
|
$s = $_SERVER; |
135
|
|
|
$host = ''; |
136
|
|
|
if ( !empty($s['HTTP_X_FORWARDED_HOST']) ) { |
137
|
|
|
$host = $s['HTTP_X_FORWARDED_HOST']; |
138
|
|
|
// handles multiple forwarders. |
139
|
|
|
if (strpos($host, ',') !== false) { |
140
|
|
|
$host = trim(array_pop( explode(',', $host) )); |
|
|
|
|
141
|
|
|
} |
142
|
|
|
} elseif ( !empty($s['HTTP_HOST']) ) { |
143
|
|
|
$host = $s['HTTP_HOST']; |
144
|
|
|
} elseif ( !empty($s['SERVER_NAME']) ) { |
145
|
|
|
$host = $s['SERVER_NAME']; |
146
|
|
|
$host .= !empty($s['SERVER_PORT']) |
147
|
|
|
? ':' . $s['SERVER_PORT'] |
148
|
|
|
: ''; |
149
|
|
|
} |
150
|
|
|
|
151
|
|
|
return rtrim($host, '/'); |
152
|
|
|
} |
153
|
|
|
|
154
|
|
|
/** |
155
|
|
|
* Gets the request Url. |
156
|
|
|
* |
157
|
|
|
* @return string |
158
|
|
|
*/ |
159
|
|
|
public function getRequestedUrl() |
160
|
|
|
{ |
161
|
|
|
// TODO here |
162
|
|
|
echo $this->getRequestedHost(); |
163
|
|
|
if($host = $this->getRequestedHost() ) { |
164
|
|
|
$host = $this->getRequestedScheme() . '://' . $host; |
165
|
|
|
} |
166
|
|
|
return $host . $this->getRequestedUri(); |
167
|
|
|
|
168
|
|
|
// return 'http' . (isset($_SERVER['HTTPS']) ? 's' : '') . '://' |
|
|
|
|
169
|
|
|
// . $this->getRequestedHost() |
|
|
|
|
170
|
|
|
// . $this->getRequestedUri(); |
|
|
|
|
171
|
|
|
} |
172
|
|
|
|
173
|
|
|
/** |
174
|
|
|
* Sets a parameter by name |
175
|
|
|
* |
176
|
|
|
* @param string $key The key |
177
|
|
|
* @param mixed $value The value |
178
|
|
|
*/ |
179
|
|
|
public function setParam($key, $value) |
180
|
|
|
{ |
181
|
|
|
$this->params[$key] = $value; |
|
|
|
|
182
|
|
|
} |
183
|
|
|
|
184
|
|
|
/** |
185
|
|
|
* Gets a specified param |
186
|
|
|
* |
187
|
|
|
* @param string $key |
188
|
|
|
* @param boolean $raw Set to true to get the raw URL encoded value |
189
|
|
|
* @param string $filter POSIX character classes e.g. alnum, alpha |
190
|
|
|
* @return mixed |
191
|
|
|
*/ |
192
|
|
|
public function getParam($key, $raw=false, $filter=null) |
193
|
|
|
{ |
194
|
|
|
if (isset($this->params[$key])) { |
195
|
|
|
|
196
|
|
|
$param = $raw===false ? rawurldecode($this->params[$key]) |
197
|
|
|
: $this->params[$key]; |
198
|
|
|
|
199
|
|
|
if (null !== $filter) { |
200
|
|
|
return preg_replace('/[^[:' . $filter . ':]]/', '', $param); |
201
|
|
|
} |
202
|
|
|
|
203
|
|
|
return $param; |
204
|
|
|
} |
205
|
|
|
} |
206
|
|
|
|
207
|
|
|
/** |
208
|
|
|
* Sets all parameters |
209
|
|
|
* |
210
|
|
|
* @param array|null $params |
211
|
|
|
*/ |
212
|
|
|
public function setParams(array $params = null) |
|
|
|
|
213
|
|
|
{ |
214
|
|
|
$this->params = null === $params ? $_REQUEST : $params; |
215
|
|
|
} |
216
|
|
|
|
217
|
|
|
/** |
218
|
|
|
* Returns all the request parameters |
219
|
|
|
* |
220
|
|
|
* @return array |
221
|
|
|
*/ |
222
|
|
|
public function getParams() |
223
|
|
|
{ |
224
|
|
|
return $this->params; |
225
|
|
|
} |
226
|
|
|
|
227
|
|
|
/** |
228
|
|
|
* Sets the request method either using: |
229
|
|
|
* - the passed method value, |
230
|
|
|
* - or from an override value: |
231
|
|
|
* - X-HTTP-Method-Override, |
232
|
|
|
* - a GET param override, |
233
|
|
|
* - server env or use the default value. |
234
|
|
|
* |
235
|
|
|
* @param string $method |
236
|
|
|
* @param string $default |
237
|
|
|
*/ |
238
|
|
|
public function setMethod($method = null, $default = 'GET') |
|
|
|
|
239
|
|
|
{ |
240
|
|
|
if (null === $method) { |
241
|
|
|
if ($this->hasHeader('X-HTTP-Method-Override')) { |
242
|
|
|
$method = $this->getHeader('X-HTTP-Method-Override'); |
243
|
|
|
} elseif ($this->getParam(self::METHOD_OVERRIDE)) { |
244
|
|
|
$method = $this->getParam(self::METHOD_OVERRIDE); |
245
|
|
|
} else { |
246
|
|
|
$method = isset($_SERVER['REQUEST_METHOD']) |
247
|
|
|
? $_SERVER['REQUEST_METHOD'] |
248
|
|
|
: $default; |
249
|
|
|
} |
250
|
|
|
} |
251
|
|
|
$this->method = strtoupper($method); |
252
|
|
|
} |
253
|
|
|
|
254
|
|
|
/** |
255
|
|
|
* Returns the current requet method |
256
|
|
|
* |
257
|
|
|
* @param string |
258
|
|
|
*/ |
259
|
|
|
public function getMethod() |
260
|
|
|
{ |
261
|
|
|
if (null === $this->method) { |
262
|
|
|
$this->setMethod(); |
263
|
|
|
} |
264
|
|
|
|
265
|
|
|
return $this->method; |
266
|
|
|
} |
267
|
|
|
|
268
|
|
|
/** |
269
|
|
|
* Sets a request header by name |
270
|
|
|
* |
271
|
|
|
* @param string $name |
272
|
|
|
* @param string $value |
273
|
|
|
*/ |
274
|
|
|
public function setHeader($name, $value) |
275
|
|
|
{ |
276
|
|
|
$this->headers[strtoupper($name)] = $value; |
277
|
|
|
} |
278
|
|
|
|
279
|
|
|
/** |
280
|
|
|
* Checks if specified header exists |
281
|
|
|
* |
282
|
|
|
* @param string $name |
283
|
|
|
* @return boolean |
284
|
|
|
*/ |
285
|
|
|
public function hasHeader($name) |
286
|
|
|
{ |
287
|
|
|
return isset($this->headers[strtoupper($name)]); |
288
|
|
|
} |
289
|
|
|
|
290
|
|
|
/** |
291
|
|
|
* Returns the specified header |
292
|
|
|
* |
293
|
|
|
* @param string $name |
294
|
|
|
* @return string |
295
|
|
|
*/ |
296
|
|
|
public function getHeader($name) |
297
|
|
|
{ |
298
|
|
|
$name = strtoupper($name); |
299
|
|
|
if (isset($this->headers[$name])) { |
300
|
|
|
return $this->headers[$name]; |
301
|
|
|
} |
302
|
|
|
} |
303
|
|
|
|
304
|
|
|
/** |
305
|
|
|
* Populates the headers properties |
306
|
|
|
* Will use the provided associative array or extract things from $_SERVER |
307
|
|
|
* |
308
|
|
|
* @param array $headers The value |
309
|
|
|
*/ |
310
|
|
|
public function setHeaders(array $headers = null) |
|
|
|
|
311
|
|
|
{ |
312
|
|
|
if (null === $headers) { |
313
|
|
|
#$headers = http_get_request_headers(); |
|
|
|
|
314
|
|
|
$headers = $_SERVER; |
315
|
|
|
} |
316
|
|
|
$this->headers = $headers; |
317
|
|
|
// $this->headers = array_change_key_case($headers); |
|
|
|
|
318
|
|
|
} |
319
|
|
|
|
320
|
|
|
/** |
321
|
|
|
* Returns all the headers |
322
|
|
|
* |
323
|
|
|
* @return array |
324
|
|
|
*/ |
325
|
|
|
public function getHeaders() |
326
|
|
|
{ |
327
|
|
|
return $this->headers; |
328
|
|
|
} |
329
|
|
|
|
330
|
|
|
/** |
331
|
|
|
* Returns the client's IP address |
332
|
|
|
* |
333
|
|
|
* @return string |
334
|
|
|
*/ |
335
|
|
|
public function getIp() |
336
|
|
|
{ |
337
|
|
|
$ip = $this->getHeader('HTTP_CLIENT_IP'); |
338
|
|
|
if (empty($ip)) { |
339
|
|
|
$ip = $this->getHeader('HTTP_X_FORWARDED_FOR'); |
340
|
|
|
} |
341
|
|
|
|
342
|
|
|
return empty($ip) ? $this->getHeader('REMOTE_ADDR') : $ip; |
343
|
|
|
} |
344
|
|
|
|
345
|
|
|
/** |
346
|
|
|
* Sets the body stream property |
347
|
|
|
* |
348
|
|
|
* @param string $string |
349
|
|
|
*/ |
350
|
|
|
public function setBodyStream($string) |
351
|
|
|
{ |
352
|
|
|
$this->bodyStream = $string; |
353
|
|
|
} |
354
|
|
|
|
355
|
|
|
/** |
356
|
|
|
* Sets the body using the provided string or retrieve it from a PHP stream |
357
|
|
|
* |
358
|
|
|
* @param string $body |
359
|
|
|
*/ |
360
|
|
|
public function setBody($body = null) |
361
|
|
|
{ |
362
|
|
|
$this->body = null === $body |
363
|
|
|
? file_get_contents($this->bodyStream) |
364
|
|
|
: $body; |
365
|
|
|
} |
366
|
|
|
|
367
|
|
|
/** |
368
|
|
|
* Checks wether the body contains data |
369
|
|
|
* |
370
|
|
|
* @return boolean |
371
|
|
|
*/ |
372
|
|
|
public function hasBody() |
373
|
|
|
{ |
374
|
|
|
return !empty($this->body); |
375
|
|
|
} |
376
|
|
|
|
377
|
|
|
/** |
378
|
|
|
* Returns the raw (undecoded) body data |
379
|
|
|
* |
380
|
|
|
* @return string |
381
|
|
|
*/ |
382
|
|
|
public function getRawBody() |
383
|
|
|
{ |
384
|
|
|
return $this->body; |
385
|
|
|
#return = http_get_request_body(); |
|
|
|
|
386
|
|
|
#return file_get_contents($this->bodyStream); |
|
|
|
|
387
|
|
|
} |
388
|
|
|
|
389
|
|
|
/** |
390
|
|
|
* Returns the (decoded) body data of a request |
391
|
|
|
* |
392
|
|
|
* @param boolean $cache Wether to cache the body after decoding. |
393
|
|
|
* @return string |
394
|
|
|
* @throws \BadFunctionCallException |
395
|
|
|
*/ |
396
|
|
|
public function getBody($cache=true) |
397
|
|
|
{ |
398
|
|
|
static $body = null; |
399
|
|
|
if ($cache && null !== $body) { |
400
|
|
|
return $body; |
401
|
|
|
} |
402
|
|
|
|
403
|
|
|
// Decode any content-encoding (gzip or deflate) if needed |
404
|
|
|
switch (strtolower($this->getHeader('content-encoding'))) { |
405
|
|
|
// Handle gzip encoding |
406
|
|
|
case 'gzip': |
407
|
|
|
$body = $this->gzDecode($this->body); |
408
|
|
|
break; |
409
|
|
|
|
410
|
|
|
// Handle deflate encoding |
411
|
|
|
case 'deflate': |
412
|
|
|
$body = $this->gzInflate($this->body); |
413
|
|
|
break; |
414
|
|
|
|
415
|
|
|
default: |
416
|
|
|
return $this->body; |
417
|
|
|
} |
418
|
|
|
|
419
|
|
|
return $body; |
420
|
|
|
} |
421
|
|
|
|
422
|
|
|
/** |
423
|
|
|
* Handles gzip decoding |
424
|
|
|
* |
425
|
|
|
* @param boolean $cache Wether to cache the body after decoding. |
|
|
|
|
426
|
|
|
* @return string |
427
|
|
|
* @throws \BadFunctionCallException |
428
|
|
|
* @codeCoverageIgnore |
429
|
|
|
*/ |
430
|
|
|
public function gzDecode($data) |
431
|
|
|
{ |
432
|
|
|
return function_exists('gzdecode') |
433
|
|
|
? gzdecode($data) |
434
|
|
|
: file_get_contents( |
435
|
|
|
'compress.zlib://data:;base64,' |
436
|
|
|
. base64_encode($data) |
437
|
|
|
); |
438
|
|
|
} |
439
|
|
|
|
440
|
|
|
/** |
441
|
|
|
* Handles inflating a deflated string |
442
|
|
|
* |
443
|
|
|
* @param string $data |
444
|
|
|
* @return string |
445
|
|
|
* @throws \BadFunctionCallException |
446
|
|
|
* @codeCoverageIgnore |
447
|
|
|
*/ |
448
|
|
|
public function gzInflate($data) |
449
|
|
|
{ |
450
|
|
|
|
451
|
|
|
if (! function_exists('gzinflate')) { |
452
|
|
|
throw new \BadFunctionCallException( |
453
|
|
|
'zlib extension is required to deflate this' |
454
|
|
|
); |
455
|
|
|
} |
456
|
|
|
|
457
|
|
|
return gzinflate($data); |
458
|
|
|
} |
459
|
|
|
|
460
|
|
|
} |
461
|
|
|
|
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.