1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
class Ajde_Http_Request extends Ajde_Object_Standard |
4
|
|
|
{ |
5
|
|
|
const TYPE_STRING = 1; |
6
|
|
|
const TYPE_HTML = 2; |
7
|
|
|
const TYPE_INTEGER = 3; |
8
|
|
|
const TYPE_FLOAT = 4; |
9
|
|
|
const TYPE_RAW = 5; |
10
|
|
|
|
11
|
|
|
const FORM_MIN_TIME = 0; // minimum time to have a post form returned (seconds) |
12
|
|
|
const FORM_MAX_TIME = 3600; // timeout of post forms (seconds) |
13
|
|
|
|
14
|
|
|
/** |
15
|
|
|
* @var Ajde_Core_Route |
16
|
|
|
*/ |
17
|
|
|
protected $_route = null; |
18
|
|
|
protected $_postData = []; |
19
|
|
|
|
20
|
|
|
/** |
21
|
|
|
* @throws Ajde_Core_Exception_Security |
22
|
|
|
* |
23
|
|
|
* @return Ajde_Http_Request |
24
|
|
|
*/ |
25
|
|
|
public static function fromGlobal() |
26
|
|
|
{ |
27
|
|
|
$instance = new self(); |
28
|
|
|
$post = self::globalPost(); |
29
|
|
|
if (!empty($post) && self::requirePostToken() && !self::_isWhitelisted()) { |
30
|
|
|
|
31
|
|
|
// Measures against CSRF attacks |
32
|
|
|
$session = new Ajde_Session('AC.Form'); |
33
|
|
|
if (!isset($post['_token']) || !$session->has('formTime')) { |
34
|
|
|
$exception = new Ajde_Core_Exception_Security('No form token received or no form time set, bailing out to prevent CSRF attack'); |
35
|
|
View Code Duplication |
if (config('app.debug') === true) { |
|
|
|
|
36
|
|
|
Ajde_Http_Response::setResponseType(Ajde_Http_Response::RESPONSE_TYPE_FORBIDDEN); |
37
|
|
|
throw $exception; |
38
|
|
|
} else { |
39
|
|
|
// Prevent inf. loops |
40
|
|
|
unset($_POST); |
41
|
|
|
unset($_REQUEST); |
42
|
|
|
// Rewrite |
43
|
|
|
Ajde_Exception_Log::logException($exception); |
|
|
|
|
44
|
|
|
Ajde_Http_Response::dieOnCode(Ajde_Http_Response::RESPONSE_TYPE_FORBIDDEN); |
45
|
|
|
} |
46
|
|
|
} |
47
|
|
|
|
48
|
|
|
$formToken = $post['_token']; |
49
|
|
|
if (!self::verifyFormToken($formToken) || !self::verifyFormTime()) { |
50
|
|
|
// TODO: |
51
|
|
|
if (!self::verifyFormToken($formToken)) { |
52
|
|
|
$exception = new Ajde_Core_Exception_Security('No matching form token (got '.self::_getHashFromSession($formToken).', expected '.self::_tokenHash($formToken).'), bailing out to prevent CSRF attack'); |
53
|
|
|
} else { |
54
|
|
|
$exception = new Ajde_Core_Exception_Security('Form token timed out, bailing out to prevent CSRF attack'); |
55
|
|
|
} |
56
|
|
View Code Duplication |
if (config('app.debug') === true) { |
|
|
|
|
57
|
|
|
Ajde_Http_Response::setResponseType(Ajde_Http_Response::RESPONSE_TYPE_FORBIDDEN); |
58
|
|
|
throw $exception; |
59
|
|
|
} else { |
60
|
|
|
// Prevent inf. loops |
61
|
|
|
unset($_POST); |
62
|
|
|
unset($_REQUEST); |
63
|
|
|
// Rewrite |
64
|
|
|
Ajde_Exception_Log::logException($exception); |
|
|
|
|
65
|
|
|
Ajde_Http_Response::dieOnCode(Ajde_Http_Response::RESPONSE_TYPE_FORBIDDEN); |
66
|
|
|
} |
67
|
|
|
} |
68
|
|
|
} |
69
|
|
|
|
70
|
|
|
// Security measure, protect $_POST |
71
|
|
|
$global = self::globalGet(); |
72
|
|
|
foreach ($global as $key => $value) { |
73
|
|
|
$instance->set($key, $value); |
74
|
|
|
} |
75
|
|
|
|
76
|
|
|
$instance->_postData = self::globalPost(); |
77
|
|
|
if (!empty($instance->_postData)) { |
78
|
|
|
Ajde_Cache::getInstance()->disable(); |
79
|
|
|
} |
80
|
|
|
|
81
|
|
|
return $instance; |
82
|
|
|
} |
83
|
|
|
|
84
|
|
|
public static function getRefferer() |
85
|
|
|
{ |
86
|
|
|
return @$_SERVER['HTTP_REFERER']; |
87
|
|
|
} |
88
|
|
|
|
89
|
|
|
public static function globalGet() |
90
|
|
|
{ |
91
|
|
|
return isset($_GET) ? $_GET : (isset($_REQUEST) ? $_REQUEST : []); |
92
|
|
|
} |
93
|
|
|
|
94
|
|
|
public static function globalPost() |
95
|
|
|
{ |
96
|
|
|
return isset($_POST) ? $_POST : (isset($_REQUEST) ? $_REQUEST : []); |
97
|
|
|
} |
98
|
|
|
|
99
|
|
|
// From http://stackoverflow.com/a/10372836/938297 |
100
|
|
|
public static function getRealIp() |
101
|
|
|
{ |
102
|
|
|
if (!empty($_SERVER['HTTP_CLIENT_IP'])) { |
103
|
|
|
return $_SERVER['HTTP_CLIENT_IP']; |
104
|
|
|
} else { |
105
|
|
|
if (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) { |
106
|
|
|
return $_SERVER['HTTP_X_FORWARDED_FOR']; |
107
|
|
|
} else { |
108
|
|
|
return $_SERVER['REMOTE_ADDR']; |
109
|
|
|
} |
110
|
|
|
} |
111
|
|
|
} |
112
|
|
|
|
113
|
|
|
/** |
114
|
|
|
* Security. |
115
|
|
|
*/ |
116
|
|
|
private static function autoEscapeString() |
117
|
|
|
{ |
118
|
|
|
return config('security.autoEscapeString') == true; |
119
|
|
|
} |
120
|
|
|
|
121
|
|
|
private static function autoCleanHtml() |
122
|
|
|
{ |
123
|
|
|
return config('security.autoCleanHtml') == true; |
124
|
|
|
} |
125
|
|
|
|
126
|
|
|
private static function requirePostToken() |
127
|
|
|
{ |
128
|
|
|
return config('security.csrf.requirePostToken') == true; |
129
|
|
|
} |
130
|
|
|
|
131
|
|
|
/** |
132
|
|
|
* CSRF prevention token. |
133
|
|
|
*/ |
134
|
|
|
public static function getFormToken() |
135
|
|
|
{ |
136
|
|
|
static $token; |
137
|
|
|
if (!isset($token)) { |
138
|
|
|
Ajde_Cache::getInstance()->disable(); |
139
|
|
|
$token = md5(uniqid(rand(), true)); |
140
|
|
|
$session = new Ajde_Session('AC.Form'); |
141
|
|
|
$tokenDictionary = self::_getTokenDictionary($session); |
142
|
|
|
$tokenDictionary[$token] = self::_tokenHash($token); |
143
|
|
|
$session->set('formTokens', $tokenDictionary); |
144
|
|
|
} |
145
|
|
|
self::markFormTime(); |
146
|
|
|
|
147
|
|
|
return $token; |
148
|
|
|
} |
149
|
|
|
|
150
|
|
|
public static function verifyFormToken($requestToken) |
151
|
|
|
{ |
152
|
|
|
return self::_tokenHash($requestToken) === self::_getHashFromSession($requestToken); |
153
|
|
|
} |
154
|
|
|
|
155
|
|
|
private static function _tokenHash($token) |
156
|
|
|
{ |
157
|
|
|
return md5($token.$_SERVER['REMOTE_ADDR'].$_SERVER['HTTP_USER_AGENT'].config('security.secret')); |
158
|
|
|
} |
159
|
|
|
|
160
|
|
|
private static function _isWhitelisted() |
161
|
|
|
{ |
162
|
|
|
$route = issetor($_GET['_route'], false); |
|
|
|
|
163
|
|
|
foreach (config('security.csrf.postWhitelistRoutes') as $whitelist) { |
164
|
|
|
if (stripos($route, $whitelist) === 0) { |
165
|
|
|
return true; |
166
|
|
|
} |
167
|
|
|
} |
168
|
|
|
|
169
|
|
|
return false; |
170
|
|
|
} |
171
|
|
|
|
172
|
|
|
private static function _getTokenDictionary(&$session = null) |
173
|
|
|
{ |
174
|
|
|
if (!isset($session)) { |
175
|
|
|
$session = new Ajde_Session('AC.Form'); |
176
|
|
|
} |
177
|
|
|
$tokenDictionary = ($session->has('formTokens') ? $session->get('formTokens') : []); |
178
|
|
|
if (!is_array($tokenDictionary)) { |
179
|
|
|
$tokenDictionary = []; |
180
|
|
|
} |
181
|
|
|
|
182
|
|
|
return $tokenDictionary; |
183
|
|
|
} |
184
|
|
|
|
185
|
|
|
private static function _getHashFromSession($token) |
186
|
|
|
{ |
187
|
|
|
$tokenDictionary = self::_getTokenDictionary(); |
188
|
|
|
|
189
|
|
|
return issetor($tokenDictionary[$token], ''); |
190
|
|
|
} |
191
|
|
|
|
192
|
|
|
public static function markFormTime() |
193
|
|
|
{ |
194
|
|
|
$time = time(); |
195
|
|
|
$session = new Ajde_Session('AC.Form'); |
196
|
|
|
$session->set('formTime', $time); |
197
|
|
|
|
198
|
|
|
return $time; |
199
|
|
|
} |
200
|
|
|
|
201
|
|
|
public static function verifyFormTime() |
202
|
|
|
{ |
203
|
|
|
$session = new Ajde_Session('AC.Form'); |
204
|
|
|
$sessionTime = $session->get('formTime'); |
205
|
|
|
if ((time() - $sessionTime) < self::FORM_MIN_TIME || |
206
|
|
|
(time() - $sessionTime) > self::FORM_MAX_TIME |
207
|
|
|
) { |
208
|
|
|
return false; |
209
|
|
|
} else { |
210
|
|
|
return true; |
211
|
|
|
} |
212
|
|
|
} |
213
|
|
|
|
214
|
|
|
public static function isAjax() |
215
|
|
|
{ |
216
|
|
|
return !empty($_SERVER['HTTP_X_REQUESTED_WITH']) && strtolower($_SERVER['HTTP_X_REQUESTED_WITH']) == 'xmlhttprequest'; |
217
|
|
|
} |
218
|
|
|
|
219
|
|
|
/** |
220
|
|
|
* @return string Lowercase request method |
221
|
|
|
*/ |
222
|
|
|
public static function method() |
223
|
|
|
{ |
224
|
|
|
return strtolower($_SERVER['REQUEST_METHOD']); |
225
|
|
|
} |
226
|
|
|
|
227
|
|
|
/** |
228
|
|
|
* Helpers. |
229
|
|
|
*/ |
230
|
|
|
public function get($key) |
231
|
|
|
{ |
232
|
|
|
return $this->getParam($key); |
233
|
|
|
} |
234
|
|
|
|
235
|
|
|
public function getParam($key, $default = null, $type = self::TYPE_STRING, $post = false) |
236
|
|
|
{ |
237
|
|
|
$data = $this->_data; |
238
|
|
|
if ($post === true) { |
239
|
|
|
$data = $this->getPostData(); |
240
|
|
|
} |
241
|
|
|
if (isset($data[$key])) { |
242
|
|
|
switch ($type) { |
243
|
|
|
case self::TYPE_HTML: |
244
|
|
|
if ($this->autoCleanHtml() === true) { |
245
|
|
|
return Ajde_Component_String::clean($data[$key]); |
246
|
|
|
} else { |
247
|
|
|
return $data[$key]; |
248
|
|
|
} |
249
|
|
|
break; |
|
|
|
|
250
|
|
|
case self::TYPE_INTEGER: |
251
|
|
|
return (int) $data[$key]; |
252
|
|
|
break; |
|
|
|
|
253
|
|
|
case self::TYPE_FLOAT: |
254
|
|
|
return (float) $data[$key]; |
255
|
|
|
break; |
|
|
|
|
256
|
|
|
case self::TYPE_RAW: |
257
|
|
|
return $data[$key]; |
258
|
|
|
break; |
|
|
|
|
259
|
|
|
case self::TYPE_STRING: |
260
|
|
|
default: |
261
|
|
|
if ($this->autoEscapeString() === true) { |
262
|
|
|
if (is_array($data[$key])) { |
263
|
|
|
array_walk($data[$key], ['Ajde_Component_String', 'escape']); |
264
|
|
|
|
265
|
|
|
return $data[$key]; |
266
|
|
|
} else { |
267
|
|
|
return Ajde_Component_String::escape($data[$key]); |
268
|
|
|
} |
269
|
|
|
} else { |
270
|
|
|
return $data[$key]; |
271
|
|
|
} |
272
|
|
|
} |
273
|
|
|
} else { |
274
|
|
|
if (isset($default)) { |
275
|
|
|
return $default; |
276
|
|
|
} else { |
277
|
|
|
// TODO: |
278
|
|
|
throw new Ajde_Exception("Parameter '$key' not present in request and no default value given"); |
279
|
|
|
} |
280
|
|
|
} |
281
|
|
|
} |
282
|
|
|
|
283
|
|
|
public function getStr($key, $default) |
284
|
|
|
{ |
285
|
|
|
return $this->getString($key, $default); |
286
|
|
|
} |
287
|
|
|
|
288
|
|
|
public function getInt($key, $default) |
289
|
|
|
{ |
290
|
|
|
return $this->getInteger($key, $default); |
291
|
|
|
} |
292
|
|
|
|
293
|
|
|
public function getString($key, $default = null) |
294
|
|
|
{ |
295
|
|
|
return $this->getParam($key, $default, self::TYPE_STRING); |
296
|
|
|
} |
297
|
|
|
|
298
|
|
|
public function getHtml($key, $default = null) |
299
|
|
|
{ |
300
|
|
|
return $this->getParam($key, $default, self::TYPE_HTML); |
301
|
|
|
} |
302
|
|
|
|
303
|
|
|
public function getInteger($key, $default = null) |
304
|
|
|
{ |
305
|
|
|
return $this->getParam($key, $default, self::TYPE_INTEGER); |
306
|
|
|
} |
307
|
|
|
|
308
|
|
|
public function getFloat($key, $default = null) |
309
|
|
|
{ |
310
|
|
|
return $this->getParam($key, $default, self::TYPE_FLOAT); |
311
|
|
|
} |
312
|
|
|
|
313
|
|
|
public function getRaw($key, $default = null) |
314
|
|
|
{ |
315
|
|
|
return $this->getParam($key, $default, self::TYPE_RAW); |
316
|
|
|
} |
317
|
|
|
|
318
|
|
|
/** |
319
|
|
|
* FORM. |
320
|
|
|
*/ |
321
|
|
|
public function getCheckbox($key, $post = true) |
322
|
|
|
{ |
323
|
|
|
if ($this->getParam($key, false, self::TYPE_RAW, $post) === 'on') { |
324
|
|
|
return true; |
325
|
|
|
} else { |
326
|
|
|
return false; |
327
|
|
|
} |
328
|
|
|
} |
329
|
|
|
|
330
|
|
|
/** |
331
|
|
|
* POST. |
332
|
|
|
*/ |
333
|
|
|
public function getPostData() |
334
|
|
|
{ |
335
|
|
|
return $this->_postData; |
336
|
|
|
} |
337
|
|
|
|
338
|
|
|
public function getPostParam($key, $default = null, $type = self::TYPE_STRING) |
339
|
|
|
{ |
340
|
|
|
return $this->getParam($key, $default, $type, true); |
341
|
|
|
} |
342
|
|
|
|
343
|
|
|
public function getPostRaw($key, $default = null) |
344
|
|
|
{ |
345
|
|
|
return $this->getParam($key, $default, self::TYPE_RAW, true); |
346
|
|
|
} |
347
|
|
|
|
348
|
|
|
public function hasPostParam($key) |
349
|
|
|
{ |
350
|
|
|
return array_key_exists($key, $this->_postData); |
351
|
|
|
} |
352
|
|
|
|
353
|
|
|
/** |
354
|
|
|
* @return Ajde_Core_Route |
355
|
|
|
*/ |
356
|
|
|
public function getRoute() |
357
|
|
|
{ |
358
|
|
|
if (!isset($this->_route)) { |
359
|
|
|
$route = $this->extractRoute(); |
360
|
|
|
$this->_route = new Ajde_Core_Route($route); |
361
|
|
|
foreach ($this->_route->values() as $part => $value) { |
362
|
|
|
if (!$this->hasNotEmpty($part)) { |
363
|
|
|
$this->set($part, $value); |
364
|
|
|
} |
365
|
|
|
} |
366
|
|
|
} |
367
|
|
|
|
368
|
|
|
return $this->_route; |
369
|
|
|
} |
370
|
|
|
|
371
|
|
|
private function extractRoute() |
372
|
|
|
{ |
373
|
|
|
// Strip query string |
374
|
|
|
$URIComponents = explode('?', $_SERVER['REQUEST_URI']); |
375
|
|
|
$requestURI = reset($URIComponents); |
376
|
|
|
|
377
|
|
|
// Route is the part after our base path |
378
|
|
|
$baseURI = str_replace('index.php', '', $_SERVER['PHP_SELF']); |
379
|
|
|
|
380
|
|
|
// Strip public from request and base |
381
|
|
|
$requestURI = str_replace(PUBLIC_DIR, '', $requestURI); |
382
|
|
|
$baseURI = str_replace(PUBLIC_DIR, '', $baseURI); |
383
|
|
|
|
384
|
|
|
// TODO: potential bug when baseuri is something like /node (now all requests with /node/node will return '') |
385
|
|
|
return $baseURI !== '/' ? str_replace($baseURI, '', $requestURI) : trim($requestURI, '/'); |
386
|
|
|
} |
387
|
|
|
|
388
|
|
|
public function initRoute() |
389
|
|
|
{ |
390
|
|
|
$route = $this->getRoute(); |
391
|
|
|
|
392
|
|
|
return $route; |
393
|
|
|
} |
394
|
|
|
|
395
|
|
|
public static function getClientIP() |
396
|
|
|
{ |
397
|
|
|
if (array_key_exists('HTTP_X_FORWARDED_FOR', $_SERVER)) { |
398
|
|
|
return $_SERVER['HTTP_X_FORWARDED_FOR']; |
399
|
|
|
} else { |
400
|
|
|
if (array_key_exists('REMOTE_ADDR', $_SERVER)) { |
401
|
|
|
return $_SERVER['REMOTE_ADDR']; |
402
|
|
|
} else { |
403
|
|
|
if (array_key_exists('HTTP_CLIENT_IP', $_SERVER)) { |
404
|
|
|
return $_SERVER['HTTP_CLIENT_IP']; |
405
|
|
|
} |
406
|
|
|
} |
407
|
|
|
} |
408
|
|
|
|
409
|
|
|
return ''; |
410
|
|
|
} |
411
|
|
|
} |
412
|
|
|
|
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.