Ajde_Http_Request::getRefferer()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 2
nc 1
nop 0
dl 0
loc 4
rs 10
c 0
b 0
f 0
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) {
0 ignored issues
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...
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);
0 ignored issues
show
Documentation introduced by
$exception is of type object<Ajde_Core_Exception_Security>, but the function expects a object<Throwable>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
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) {
0 ignored issues
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...
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);
0 ignored issues
show
Documentation introduced by
$exception is of type object<Ajde_Core_Exception_Security>, but the function expects a object<Throwable>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
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);
0 ignored issues
show
Bug introduced by
Are you sure the assignment to $route is correct as issetor($_GET['_route'], false) (which targets issetor()) seems to always return null.

This check looks for function or method calls that always return null and whose return value is assigned to a variable.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
$object = $a->getObject();

The method getObject() can return nothing but null, so it makes no sense to assign that value to a variable.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
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;
0 ignored issues
show
Unused Code introduced by
break; does not seem to be reachable.

This check looks for unreachable code. It uses sophisticated control flow analysis techniques to find statements which will never be executed.

Unreachable code is most often the result of return, die or exit statements that have been added for debug purposes.

function fx() {
    try {
        doSomething();
        return true;
    }
    catch (\Exception $e) {
        return false;
    }

    return false;
}

In the above example, the last return false will never be executed, because a return statement has already been met in every possible execution path.

Loading history...
250
                case self::TYPE_INTEGER:
251
                    return (int) $data[$key];
252
                    break;
0 ignored issues
show
Unused Code introduced by
break is not strictly necessary here and could be removed.

The break statement is not necessary if it is preceded for example by a return statement:

switch ($x) {
    case 1:
        return 'foo';
        break; // This break is not necessary and can be left off.
}

If you would like to keep this construct to be consistent with other case statements, you can safely mark this issue as a false-positive.

Loading history...
253
                case self::TYPE_FLOAT:
254
                    return (float) $data[$key];
255
                    break;
0 ignored issues
show
Unused Code introduced by
break is not strictly necessary here and could be removed.

The break statement is not necessary if it is preceded for example by a return statement:

switch ($x) {
    case 1:
        return 'foo';
        break; // This break is not necessary and can be left off.
}

If you would like to keep this construct to be consistent with other case statements, you can safely mark this issue as a false-positive.

Loading history...
256
                case self::TYPE_RAW:
257
                    return $data[$key];
258
                    break;
0 ignored issues
show
Unused Code introduced by
break is not strictly necessary here and could be removed.

The break statement is not necessary if it is preceded for example by a return statement:

switch ($x) {
    case 1:
        return 'foo';
        break; // This break is not necessary and can be left off.
}

If you would like to keep this construct to be consistent with other case statements, you can safely mark this issue as a false-positive.

Loading history...
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