UriDetector::setPath()   A
last analyzed

Complexity

Conditions 5
Paths 6

Size

Total Lines 14
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 7
c 1
b 0
f 0
dl 0
loc 14
rs 9.6111
cc 5
nc 6
nop 1
1
<?php
2
3
/**
4
 * UriDetector.php - Jaxon request UriDetector detector
5
 *
6
 * Detect and parse the URI of the Jaxon request being processed.
7
 *
8
 * @package jaxon-utils
9
 * @author Thierry Feuzeu <[email protected]>
10
 * @copyright 2022 Thierry Feuzeu <[email protected]>
11
 * @license https://opensource.org/licenses/BSD-3-Clause BSD 3-Clause License
12
 * @link https://github.com/jaxon-php/jaxon-core
13
 */
14
15
namespace Jaxon\Utils\Http;
16
17
use function basename;
18
use function explode;
19
use function implode;
20
use function parse_str;
21
use function parse_url;
22
use function rawurlencode;
23
use function str_replace;
24
use function strlen;
25
use function strpos;
26
use function strrpos;
27
use function strtolower;
28
use function substr;
29
30
class UriDetector
31
{
32
    /**
33
     * The URL components
34
     *
35
     * @var array
36
     */
37
    protected $aUrl;
38
39
    /**
40
     * @param array $aServerParams The server environment variables
41
     *
42
     * @return void
43
     */
44
    private function setScheme(array $aServerParams)
45
    {
46
        if(isset($this->aUrl['scheme']))
47
        {
48
            return;
49
        }
50
        if(isset($aServerParams['HTTP_SCHEME']))
51
        {
52
            $this->aUrl['scheme'] = $aServerParams['HTTP_SCHEME'];
53
            return;
54
        }
55
        if((isset($aServerParams['HTTPS']) && strtolower($aServerParams['HTTPS']) === 'on') ||
56
            (isset($aServerParams['HTTP_X_FORWARDED_SSL']) && $aServerParams['HTTP_X_FORWARDED_SSL'] === 'on') ||
57
            (isset($aServerParams['HTTP_X_FORWARDED_PROTO']) && $aServerParams['HTTP_X_FORWARDED_PROTO'] === 'https'))
58
        {
59
            $this->aUrl['scheme'] = 'https';
60
            return;
61
        }
62
        $this->aUrl['scheme'] = 'http';
63
    }
64
65
    /**
66
     * Get the URL from the $aServerParams var
67
     *
68
     * @param array $aServerParams The server environment variables
69
     * @param string $sKey The key in the $aServerParams array
70
     *
71
     * @return void
72
     */
73
    private function setHostFromServer(array $aServerParams, string $sKey)
74
    {
75
        if(isset($this->aUrl['host']) || empty($aServerParams[$sKey]))
76
        {
77
            return;
78
        }
79
        if(strpos($aServerParams[$sKey], ':') === false)
80
        {
81
            $this->aUrl['host'] = $aServerParams[$sKey];
82
            return;
83
        }
84
        list($this->aUrl['host'], $this->aUrl['port']) = explode(':', $aServerParams[$sKey]);
85
    }
86
87
    /**
88
     * @param array $aServerParams The server environment variables
89
     *
90
     * @return void
91
     * @throws UriException
92
     */
93
    private function setHost(array $aServerParams)
94
    {
95
        $this->setHostFromServer($aServerParams, 'HTTP_X_FORWARDED_HOST');
96
        $this->setHostFromServer($aServerParams, 'HTTP_HOST');
97
        $this->setHostFromServer($aServerParams, 'SERVER_NAME');
98
        if(empty($this->aUrl['host']))
99
        {
100
            throw new UriException();
101
        }
102
        if(empty($this->aUrl['port']) && isset($aServerParams['SERVER_PORT']))
103
        {
104
            $this->aUrl['port'] = $aServerParams['SERVER_PORT'];
105
        }
106
    }
107
108
    /**
109
     * @param array $aServerParams The server environment variables
110
     *
111
     * @return void
112
     */
113
    private function setPath(array $aServerParams)
114
    {
115
        if(isset($this->aUrl['path']) && strlen(basename($this->aUrl['path'])) === 0)
116
        {
117
            unset($this->aUrl['path']);
118
        }
119
        if(isset($this->aUrl['path']))
120
        {
121
            return;
122
        }
123
        $aPath = parse_url($aServerParams['PATH_INFO'] ?? ($aServerParams['PHP_SELF'] ?? ''));
124
        if(isset($aPath['path']))
125
        {
126
            $this->aUrl['path'] = $aPath['path'];
127
        }
128
    }
129
130
    /**
131
     * @return string
132
     */
133
    private function getPath(): string
134
    {
135
        return '/' . ltrim($this->aUrl['path'], '/');
136
    }
137
138
    /**
139
     * @return string
140
     */
141
    private function getUser(): string
142
    {
143
        if(empty($this->aUrl['user']))
144
        {
145
            return '';
146
        }
147
        $sUrl = $this->aUrl['user'];
148
        if(isset($this->aUrl['pass']))
149
        {
150
            $sUrl .= ':' . $this->aUrl['pass'];
151
        }
152
        return $sUrl . '@';
153
    }
154
155
    /**
156
     * @return string
157
     */
158
    private function getPort(): string
159
    {
160
        if(isset($this->aUrl['port']) &&
161
            (($this->aUrl['scheme'] === 'http' && $this->aUrl['port'] != 80) ||
162
                ($this->aUrl['scheme'] === 'https' && $this->aUrl['port'] != 443)))
163
        {
164
            return ':' . $this->aUrl['port'];
165
        }
166
        return '';
167
    }
168
169
    /**
170
     * @param array $aServerParams The server environment variables
171
     *
172
     * @return void
173
     */
174
    private function setQuery(array $aServerParams)
175
    {
176
        if(empty($this->aUrl['query']))
177
        {
178
            $this->aUrl['query'] = $aServerParams['QUERY_STRING'] ?? '';
179
        }
180
    }
181
182
    /**
183
     * @return string
184
     */
185
    private function getQuery(): string
186
    {
187
        if(empty($this->aUrl['query']))
188
        {
189
            return '';
190
        }
191
        $aQueries = explode('&', $this->aUrl['query']);
192
        foreach($aQueries as $sKey => $sQuery)
193
        {
194
            if(substr($sQuery, 0, 11) === 'jxnGenerate')
195
            {
196
                unset($aQueries[$sKey]);
197
            }
198
        }
199
        if(empty($aQueries))
200
        {
201
            return '';
202
        }
203
        return '?' . implode("&", $aQueries);
204
    }
205
206
    /**
207
     * Detect the URI of the current request
208
     *
209
     * @param array $aServerParams The server environment variables
210
     *
211
     * @return string
212
     * @throws UriException
213
     */
214
    public function detect(array $aServerParams): string
215
    {
216
        $this->aUrl = [];
217
        // Try to get the request URL
218
        if(isset($aServerParams['REQUEST_URI']))
219
        {
220
            $this->aUrl = parse_url($aServerParams['REQUEST_URI']);
221
        }
222
223
        // Fill in the empty values
224
        $this->setScheme($aServerParams);
225
        $this->setHost($aServerParams);
226
        $this->setPath($aServerParams);
227
        $this->setQuery($aServerParams);
228
229
        // Build the URL: Start with scheme, user and pass
230
        return $this->aUrl['scheme'] . '://' . $this->getUser() . $this->aUrl['host'] .
231
            $this->getPort() . str_replace(['"', "'", '<', '>'],
232
                ['%22', '%27', '%3C', '%3E'], $this->getPath() . $this->getQuery());
233
    }
234
235
    /**
236
     * @param string $sQueryPart
237
     * @param string $sKey
238
     * @param string $sValue
239
     *
240
     * @return string
241
     */
242
    private function makeQueryPart(string $sQueryPart, string $sKey, string $sValue): string
243
    {
244
        return $sValue === '' && strpos($sQueryPart, "$sKey=") === false ?
245
            rawurlencode($sKey) : rawurlencode($sKey) . '=' . rawurlencode($sValue);
246
    }
247
248
    /**
249
     * @param string $sQueryPart
250
     * @param array $aServerParams
251
     *
252
     * @return string
253
     */
254
    private function parseQueryPart(string $sQueryPart, array $aServerParams): string
255
    {
256
        $aQueryParts = [];
257
        parse_str($sQueryPart, $aQueryParts);
258
        if(empty($aQueryParts))
259
        {
260
            // Couldn't break up the query, but there's one there.
261
            // Possibly "http://url/page.html?query1234" type of query?
262
            // Try to get data from the server environment var.
263
            parse_str($aServerParams['QUERY_STRING'] ?? '', $aQueryParts);
264
        }
265
        if(($aQueryParts))
266
        {
267
            $aNewQueryParts = [];
268
            foreach($aQueryParts as $sKey => $sValue)
269
            {
270
                $aNewQueryParts[] = $this->makeQueryPart($sQueryPart, $sKey, $sValue);
271
            }
272
            return '?' . implode('&', $aNewQueryParts);
273
        }
274
        return trim($sQueryPart);
275
    }
276
277
    /**
278
     * Make the specified URL suitable for redirect
279
     *
280
     * @param string $sURL The relative or fully qualified URL
281
     * @param array $aServerParams The server environment variables
282
     *
283
     * @return string
284
     */
285
    public function redirect(string $sURL, array $aServerParams): string
286
    {
287
        // We need to parse the query part so that the values are rawurlencode()'ed.
288
        // Can't just use parse_url() cos we could be dealing with a relative URL which parse_url() can't deal with.
289
        $sURL = trim($sURL);
290
        $nQueryStart = strpos($sURL, '?', strrpos($sURL, '/'));
291
        if($nQueryStart === false)
292
        {
293
            return $sURL;
294
        }
295
        $nQueryStart++;
296
        $nQueryEnd = strpos($sURL, '#', $nQueryStart);
297
        if($nQueryEnd === false)
298
        {
299
            $nQueryEnd = strlen($sURL);
300
        }
301
        $sQueryPart = substr($sURL, $nQueryStart, $nQueryEnd - $nQueryStart);
302
        return str_replace('?' . $sQueryPart, $this->parseQueryPart($sQueryPart, $aServerParams), $sURL);
303
    }
304
}
305