Issues (92)

src/Request/Http.php (3 issues)

1
<?php
2
3
namespace Nip\Http\Request;
4
5
/**
6
 * Nip Framework
7
 *
8
 * @category   Nip
9
 * @copyright  2009 Nip Framework
10
 * @license    http://www.opensource.org/licenses/mit-license.php The MIT License
11
 * @version    SVN: $Id: Http.php 135 2009-05-27 16:48:23Z victor.stanciu $
12
 */
13
14
class Http
15
{
16
    protected $request;
17
18
    /**
19
     * @var string
20
     */
21
    protected $pathInfo;
22
23
    /**
24
     * @var string
25
     */
26
    protected $requestUri;
27
    /**
28
     * @var string
29
     */
30
    protected $baseUrl;
31
32
    /**
33
     * Normalizes a query string.
34
     *
35
     * It builds a normalized query string, where keys/value pairs are alphabetized,
36
     * have consistent escaping and unneeded delimiters are removed.
37
     *
38
     * @param string $qs Query string
39
     *
40
     * @return string A normalized query string for the Request
41
     */
42 1
    public static function normalizeQueryString($qs)
43
    {
44 1
        if ('' == $qs) {
45
            return '';
46
        }
47 1
        $parts = [];
48 1
        $order = [];
49 1
        foreach (explode('&', $qs) as $param) {
50 1
            if ('' === $param || '=' === $param[0]) {
51
                // Ignore useless delimiters, e.g. "x=y&".
52
                // Also ignore pairs with empty key, even if there was a value, e.g. "=value",
53
                // as such nameless values cannot be retrieved anyway.
54
                // PHP also does not include them when building _GET.
55
                continue;
56
            }
57 1
            $keyValuePair = explode('=', $param, 2);
58
            // GET parameters, that are submitted from a HTML form, encode spaces as "+" by default
59
            // (as defined in enctype application/x-www-form-urlencoded).
60
            // PHP also converts "+" to spaces when filling the global _GET or when using the function parse_str.
61
            // This is why we use urldecode and then normalize to
62
            // RFC 3986 with rawurlencode.
63 1
            $parts[] = isset($keyValuePair[1]) ?
64 1
                rawurlencode(urldecode($keyValuePair[0])) . '=' . rawurlencode(urldecode($keyValuePair[1])) : rawurlencode(urldecode($keyValuePair[0]));
65 1
            $order[] = urldecode($keyValuePair[0]);
66
        }
67 1
        array_multisort($order, SORT_ASC, $parts);
0 ignored issues
show
Nip\Http\Request\SORT_ASC cannot be passed to array_multisort() as the parameter $rest expects a reference. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

67
        array_multisort($order, /** @scrutinizer ignore-type */ SORT_ASC, $parts);
Loading history...
68
69 1
        return implode('&', $parts);
70
    }
71
72
    /**
73
     * Generates a normalized URI (URL) for the Request.
74
     *
75
     * @return string A normalized URI (URL) for the Request
76
     *
77
     * @see getQueryString()
78
     */
79 1
    public function getUri()
80
    {
81 1
        if (null !== $qs = $this->getQueryString()) {
82 1
            $qs = '?' . $qs;
83
        }
84 1
        return $this->getSchemeAndHttpHost() . $this->getBaseUrl() . $this->getRequest()->getPathInfo() . $qs;
85
    }
86
87
    /**
88
     * Generates the normalized query string for the Request.
89
     *
90
     * It builds a normalized query string, where keys/value pairs are alphabetized
91
     * and have consistent escaping.
92
     *
93
     * @return string|null A normalized query string for the Request
94
     */
95 1
    public function getQueryString()
96
    {
97 1
        $qs = static::normalizeQueryString($this->getRequest()->server->get('QUERY_STRING'));
98
99 1
        return '' === $qs ? null : $qs;
100
    }
101
102
    /**
103
     * @return mixed
104
     */
105 8
    public function getRequest()
106
    {
107 8
        return $this->request;
108
    }
109
110
    /*
111
     * The following methods are derived from code of the Zend Framework (1.10dev - 2010-01-24)
112
     *
113
     * Code subject to the new BSD license (http://framework.zend.com/license/new-bsd).
114
     *
115
     * Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
116
     */
117
118
    /**
119
     * @param mixed $request
120
     */
121 8
    public function setRequest($request)
122
    {
123 8
        $this->request = $request;
124 8
    }
125
126
    /**
127
     * Gets the scheme and HTTP host.
128
     *
129
     * If the URL was called with basic authentication, the user
130
     * and the password are not added to the generated string.
131
     *
132
     * @return string The scheme and HTTP host
133
     */
134 8
    public function getSchemeAndHttpHost()
135
    {
136 8
        return $this->getScheme() . '://' . $this->getHttpHost();
137
    }
138
139
    /**
140
     * Gets the request's scheme.
141
     *
142
     * @return string
143
     */
144 8
    public function getScheme()
145
    {
146 8
        return $this->isSecure() ? 'https' : 'http';
147
    }
148
149
    /**
150
     * @return bool
151
     */
152 8
    public function isSecure()
153
    {
154 8
        $https = $this->getRequest()->server->get('HTTPS');
155
156 8
        return !empty($https) && 'off' !== strtolower($https);
157
    }
158
159
    /**
160
     * Returns the HTTP host being requested.
161
     *
162
     * The port name will be appended to the host if it's non-standard.
163
     *
164
     * @return string
165
     */
166 8
    public function getHttpHost()
167
    {
168 8
        $scheme = $this->getScheme();
169 8
        $port = $this->getPort();
170 8
        if (('http' == $scheme && $port == 80) || ('https' == $scheme && $port == 443)) {
171 8
            return $this->getHost();
172
        }
173
174
        return $this->getHost() . ':' . $port;
175
    }
176
177
    /**
178
     * @return mixed
179
     */
180 8
    public function getPort()
181
    {
182 8
        return $this->getRequest()->server->get('SERVER_PORT');
183
    }
184
185
    /**
186
     * Returns the host name.
187
     *
188
     * This method can read the client host name from the "X-Forwarded-Host" header
189
     * when trusted proxies were set via "setTrustedProxies()".
190
     *
191
     * The "X-Forwarded-Host" header must contain the client host name.
192
     *
193
     * If your reverse proxy uses a different header name than "X-Forwarded-Host",
194
     * configure it via "setTrustedHeaderName()" with the "client-host" key.
195
     *
196
     * @return string
197
     *
198
     * @throws \UnexpectedValueException when the host name is invalid
199
     */
200 8
    public function getHost()
201
    {
202 8
        if (!$host = $this->getRequest()->headers->get('HOST')) {
203
            if (!$host = $this->getRequest()->server->get('SERVER_NAME')) {
204
                $host = $this->getRequest()->server->get('SERVER_ADDR', '');
205
            }
206
        }
207
        // trim and remove port number from host
208
        // host is lowercase as per RFC 952/2181
209 8
        $host = strtolower(preg_replace('/:\d+$/', '', trim($host)));
210
211
        // as the host can come from the user (HTTP_HOST and depending on the configuration,
212
        // SERVER_NAME too can come from the user)
213
        // check that it does not contain forbidden characters (see RFC 952 and RFC 2181)
214
        // use preg_replace() instead of preg_match() to prevent DoS attacks with long host names
215 8
        if ($host && '' !== preg_replace('/(?:^\[)?[a-zA-Z0-9-:\]_]+\.?/', '', $host)) {
216
            throw new \UnexpectedValueException(sprintf('Invalid Host "%s"', $host));
217
        }
218
219 8
        return $host;
220
    }
221
222
    /**
223
     * Returns the root URL from which this request is executed.
224
     *
225
     * The base URL never ends with a /.
226
     *
227
     * This is similar to getBasePath(), except that it also includes the
228
     * script filename (e.g. index.php) if one exists.
229
     *
230
     * @return string The raw URL (i.e. not urldecoded)
231
     */
232 8
    public function getBaseUrl()
233
    {
234 8
        if (null === $this->baseUrl) {
235 8
            $this->baseUrl = $this->prepareBaseUrl();
236
        }
237 8
        return $this->baseUrl;
238
    }
239
240
    /**
241
     * Returns the requested URI (path and query string).
242
     *
243
     * @return string The raw URI (i.e. not URI decoded)
244
     */
245 8
    public function getRequestUri()
246
    {
247 8
        if (null === $this->requestUri) {
248 8
            $this->requestUri = $this->prepareRequestUri();
249
        }
250 8
        return $this->requestUri;
251
    }
252
253
    /**
254
     * @return bool|mixed
255
     */
256
    public function getSubdomain()
257
    {
258
        $name = $this->getServerName();
259
        if ($name) {
260
            if (substr_count($name, '.') > 1) {
261
                $parts = explode('.', $name);
262
                return reset($parts);
263
            }
264
        }
265
266
        return false;
267
    }
268
269
    /**
270
     * @return mixed
271
     */
272
    public function getServerName()
273
    {
274
        return $this->getRequest()->server->get('SERVER_NAME');
275
    }
276
277
    /**
278
     * @return bool|mixed|string
279
     */
280
    public function getRootDomain()
281
    {
282
        $name = $this->getServerName();
283
        if ($name) {
284
            if (substr_count($name, '.') > 1) {
285
                $parts = explode('.', $name);
286
                array_shift($parts);
287
                return implode('.', $parts);
288
            }
289
            return $name;
290
        }
291
292
        return false;
293
    }
294
295
    /*
296
     * Returns the prefix as encoded in the string when the string starts with
297
     * the given prefix, false otherwise.
298
     *
299
     * @param string $string The urlencoded string
300
     * @param string $prefix The prefix not encoded
301
     *
302
     * @return string|false The prefix as it is encoded in $string, or false
303
     */
304
305
    /**
306
     * @return bool
307
     */
308
    public function isConsole()
309
    {
310
        if (php_sapi_name() === 'cli') {
311
            return true;
312
        }
313
        if (php_sapi_name() === 'cgi-fcgi') {
314
            return true;
315
        }
316
317
        return false;
318
    }
319
320
    /**
321
     * Prepares the base URL.
322
     *
323
     * @return string
324
     */
325 8
    protected function prepareBaseUrl()
326
    {
327 8
        $filename = basename($this->getRequest()->server->get('SCRIPT_FILENAME'));
328 8
        if (basename($this->getRequest()->server->get('SCRIPT_NAME')) === $filename) {
329 8
            $baseUrl = $this->getRequest()->server->get('SCRIPT_NAME');
330
        } elseif (basename($this->getRequest()->server->get('PHP_SELF')) === $filename) {
331
            $baseUrl = $this->getRequest()->server->get('PHP_SELF');
332
        } elseif (basename($this->getRequest()->server->get('ORIG_SCRIPT_NAME')) === $filename) {
333
            $baseUrl = $this->getRequest()->server->get('ORIG_SCRIPT_NAME'); // 1and1 shared hosting compatibility
334
        } else {
335
            // Backtrack up the script_filename to find the portion matching
336
            // php_self
337
            $path = $this->getRequest()->server->get('PHP_SELF', '');
338
            $file = $this->getRequest()->server->get('SCRIPT_FILENAME', '');
339
            $segs = explode('/', trim($file, '/'));
340
            $segs = array_reverse($segs);
341
            $index = 0;
342
            $last = count($segs);
343
            $baseUrl = '';
344
            do {
345
                $seg = $segs[$index];
346
                $baseUrl = '/' . $seg . $baseUrl;
347
                ++$index;
348
            } while ($last > $index && (false !== $pos = strpos($path, $baseUrl)) && 0 != $pos);
349
        }
350
        // Does the baseUrl have anything in common with the request_uri?
351 8
        $requestUri = $this->getRequestUri();
352 8
        if ($baseUrl && false !== $prefix = $this->getUrlencodedPrefix($requestUri, $baseUrl)) {
353
            // full $baseUrl matches
354 2
            return $prefix;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $prefix returns the type true which is incompatible with the documented return type string.
Loading history...
355
        }
356 6
        if ($baseUrl && false !== $prefix = $this->getUrlencodedPrefix($requestUri,
357 6
                rtrim(dirname($baseUrl), '/' . DIRECTORY_SEPARATOR) . '/')
358
        ) {
359
            // directory portion of $baseUrl matches
360 5
            return rtrim($prefix, '/' . DIRECTORY_SEPARATOR);
0 ignored issues
show
$prefix of type true is incompatible with the type string expected by parameter $string of rtrim(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

360
            return rtrim(/** @scrutinizer ignore-type */ $prefix, '/' . DIRECTORY_SEPARATOR);
Loading history...
361
        }
362 1
        $truncatedRequestUri = $requestUri;
363 1
        if (false !== $pos = strpos($requestUri, '?')) {
364 1
            $truncatedRequestUri = substr($requestUri, 0, $pos);
365
        }
366 1
        $basename = basename($baseUrl);
367 1
        if (empty($basename) || !strpos(rawurldecode($truncatedRequestUri), $basename)) {
368
            // no match whatsoever; set it blank
369 1
            return '';
370
        }
371
        // If using mod_rewrite or ISAPI_Rewrite strip the script filename
372
        // out of baseUrl. $pos !== 0 makes sure it is not matching a value
373
        // from PATH_INFO or QUERY_STRING
374
        if (strlen($requestUri) >= strlen($baseUrl) && (false !== $pos = strpos($requestUri, $baseUrl)) && $pos !== 0) {
375
            $baseUrl = substr($requestUri, 0, $pos + strlen($baseUrl));
376
        }
377
        return rtrim($baseUrl, '/' . DIRECTORY_SEPARATOR);
378
    }
379
380
    /**
381
     * @return string
382
     */
383 8
    protected function prepareRequestUri()
384
    {
385 8
        $requestUri = '';
386 8
        if ($this->getRequest()->headers->has('X_ORIGINAL_URL')) {
387
            // IIS with Microsoft Rewrite Module
388
            $requestUri = $this->getRequest()->headers->get('X_ORIGINAL_URL');
389
            $this->getRequest()->headers->remove('X_ORIGINAL_URL');
390
            $this->getRequest()->server->remove('HTTP_X_ORIGINAL_URL');
391
            $this->getRequest()->server->remove('UNENCODED_URL');
392
            $this->getRequest()->server->remove('IIS_WasUrlRewritten');
393 8
        } elseif ($this->getRequest()->headers->has('X_REWRITE_URL')) {
394
            // IIS with ISAPI_Rewrite
395
            $requestUri = $this->getRequest()->headers->get('X_REWRITE_URL');
396
            $this->getRequest()->headers->remove('X_REWRITE_URL');
397 8
        } elseif ($this->getRequest()->server->get('IIS_WasUrlRewritten') == '1' && $this->getRequest()->server->get('UNENCODED_URL') != '') {
398
            // IIS7 with URL Rewrite: make sure we get the unencoded URL (double slash problem)
399
            $requestUri = $this->getRequest()->server->get('UNENCODED_URL');
400
            $this->getRequest()->server->remove('UNENCODED_URL');
401
            $this->getRequest()->server->remove('IIS_WasUrlRewritten');
402 8
        } elseif ($this->getRequest()->server->has('REQUEST_URI')) {
403 8
            $requestUri = $this->getRequest()->server->get('REQUEST_URI');
404
            // HTTP proxy reqs setup request URI with scheme and host [and port] + the URL path, only use URL path
405 8
            $schemeAndHttpHost = $this->getSchemeAndHttpHost();
406 8
            if (strpos($requestUri, $schemeAndHttpHost) === 0) {
407 8
                $requestUri = substr($requestUri, strlen($schemeAndHttpHost));
408
            }
409
        } elseif ($this->getRequest()->server->has('ORIG_PATH_INFO')) {
410
            // IIS 5.0, PHP as CGI
411
            $requestUri = $this->getRequest()->server->get('ORIG_PATH_INFO');
412
            if ('' != $this->getRequest()->server->get('QUERY_STRING')) {
413
                $requestUri .= '?' . $this->getRequest()->server->get('QUERY_STRING');
414
            }
415
            $this->getRequest()->server->remove('ORIG_PATH_INFO');
416
        }
417
        // normalize the request URI to ease creating sub-requests from this request
418 8
        $this->getRequest()->server->set('REQUEST_URI', $requestUri);
419 8
        return $requestUri;
420
    }
421
422
    /**
423
     * @param $string
424
     * @param $prefix
425
     * @return bool
426
     */
427 7
    private function getUrlencodedPrefix($string, $prefix)
428
    {
429 7
        if (0 !== strpos(rawurldecode($string), $prefix)) {
430 5
            return false;
431
        }
432 7
        $len = strlen($prefix);
433 7
        if (preg_match(sprintf('#^(%%[[:xdigit:]]{2}|.){%d}#', $len), $string, $match)) {
434 7
            return $match[0];
435
        }
436
437
        return false;
438
    }
439
}
440