Completed
Branch master (c11094)
by Charis
03:44 queued 01:31
created

Uri::createFromHeader()   F

Complexity

Conditions 16
Paths 3072

Size

Total Lines 48
Code Lines 28

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 48
c 0
b 0
f 0
rs 2.5426
cc 16
eloc 28
nc 3072
nop 0

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
namespace Resilient\Http;
4
5
use InvalidArgumentException;
6
use \Psr\Http\Message\UriInterface;
7
8
class Uri implements UriInterface
9
{
10
11
12
    /**
13
     * Uri scheme (without "://" suffix)
14
     *
15
     * @var string
16
     */
17
    protected $scheme = '';
18
19
    /**
20
     * Uri user
21
     *
22
     * @var string
23
     */
24
    protected $user = '';
25
26
    /**
27
     * Uri password
28
     *
29
     * @var string
30
     */
31
    protected $password = '';
32
33
    /**
34
     * Uri host
35
     *
36
     * @var string
37
     */
38
    protected $host = '';
39
40
    /**
41
     * Uri port number
42
     *
43
     * @var null|int
44
     */
45
    protected $port;
46
47
    /**
48
     * Uri base path
49
     *
50
     * @var string
51
     */
52
    protected $basePath = '';
53
54
    /**
55
     * Uri path
56
     *
57
     * @var string
58
     */
59
    protected $path = '';
60
61
    /**
62
     * Uri query string (without "?" prefix)
63
     *
64
     * @var string
65
     */
66
    protected $query = '';
67
68
    /**
69
     * Uri fragment string (without "#" prefix)
70
     *
71
     * @var string
72
     */
73
    protected $fragment = '';
74
75
    /**
76
     * Instance new Uri.
77
     *
78
     * @param string $scheme   Uri scheme.
79
     * @param string $host     Uri host.
80
     * @param int    $port     Uri port number.
81
     * @param string $path     Uri path.
82
     * @param string $query    Uri query string.
83
     * @param string $fragment Uri fragment.
84
     * @param string $user     Uri user.
85
     * @param string $password Uri password.
86
     */
87
    public function __construct(
88
        $scheme,
89
        $host,
90
        $port = null,
91
        $path = '/',
92
        $query = '',
93
        $fragment = '',
94
        $user = '',
95
        $password = ''
96
    ) {
97
        $this->scheme = $this->filterScheme($scheme);
98
        $this->host = $host;
99
        $this->port = $this->filterPort($port);
100
        $this->path = empty($path) ? '/' : $this->filterPath($path);
101
        $this->query = $this->filterQuery($query);
102
        $this->fragment = $this->filterQuery($fragment);
103
        $this->user = $user;
104
        $this->password = $password;
105
    }
106
107
    /**
108
     * {@inheritdoc}
109
     */
110
    public function getScheme()
111
    {
112
        return $this->scheme;
113
    }
114
115
    /**
116
     * {@inheritdoc}
117
     */
118
    public function getAuthority()
119
    {
120
        $userInfo = $this->getUserInfo();
121
        $host = $this->getHost();
122
        $port = $this->getPort();
123
124
        return ($userInfo ? $userInfo . '@' : '') . $host . ($port !== null ? ':' . $port : '');
125
    }
126
127
    /**
128
     * {@inheritdoc}
129
     */
130
    public function getUserInfo()
131
    {
132
        return $this->user . ($this->password ? ':' . $this->password : '');
133
    }
134
135
    /**
136
     * {@inheritdoc}
137
     */
138
    public function getHost()
139
    {
140
        return $this->host;
141
    }
142
143
    /**
144
     * {@inheritdoc}
145
     */
146
    public function getPort()
147
    {
148
        return $this->port !== null && !$this->hasStandardPort() ? $this->port : null;
149
    }
150
151
    /**
152
     * {@inheritdoc}
153
     */
154
    public function getPath()
155
    {
156
        return $this->path;
157
    }
158
159
    /**
160
     * {@inheritdoc}
161
     */
162
    public function getQuery()
163
    {
164
        return $this->query;
165
    }
166
167
    /**
168
     * {@inheritdoc}
169
     */
170
    public function getFragment()
171
    {
172
        return $this->fragment;
173
    }
174
175
    /**
176
     * {@inheritdoc}
177
     */
178
    public function withScheme($scheme)
179
    {
180
        $scheme = $this->filterScheme($scheme);
181
        $clone = clone $this;
182
        $clone->scheme = $scheme;
183
184
        return $clone;
185
    }
186
187
    /**
188
     * {@inheritdoc}
189
     */
190
    public function withUserInfo($user, $password = null)
191
    {
192
        $clone = clone $this;
193
        $clone->user = $user;
194
        $clone->password = $password ? $password : '';
195
196
        return $clone;
197
    }
198
199
    /**
200
     * {@inheritdoc}
201
     */
202
    public function withHost($host)
203
    {
204
        $clone = clone $this;
205
        $clone->host = $host;
206
207
        return $clone;
208
    }
209
210
    /**
211
     * {@inheritdoc}
212
     */
213
    public function withPort($port)
214
    {
215
        $port = $this->filterPort($port);
216
        $clone = clone $this;
217
        $clone->port = $port;
218
219
        return $clone;
220
    }
221
222
    /**
223
     * {@inheritdoc}
224
     */
225
    public function withPath($path)
226
    {
227
        $clone = clone $this;
228
        $clone->path = $this->filterPath($path);
229
230
        // if the path is absolute, then clear basePath
231
        if (substr($path, 0, 1) == '/') {
232
            $clone->basePath = '';
233
        }
234
235
        return $clone;
236
    }
237
238
    /**
239
     * {@inheritdoc}
240
     */
241 View Code Duplication
    public function withQuery($query)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in 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...
242
    {
243
        if (!is_string($query) && !method_exists($query, '__toString')) {
244
            throw new InvalidArgumentException('Uri query must be a string');
245
        }
246
        $query = ltrim((string)$query, '?');
247
        $clone = clone $this;
248
        $clone->query = $this->filterQuery($query);
249
250
        return $clone;
251
    }
252
253
    /**
254
     * {@inheritdoc}
255
     */
256 View Code Duplication
    public function withFragment($fragment)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in 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...
257
    {
258
        if (!is_string($fragment) && !method_exists($fragment, '__toString')) {
259
            throw new InvalidArgumentException('Uri fragment must be a string');
260
        }
261
        $fragment = ltrim((string)$fragment, '#');
262
        $clone = clone $this;
263
        $clone->fragment = $this->filterQuery($fragment);
264
265
        return $clone;
266
    }
267
268
    /**
269
     * {@inheritdoc}
270
     */
271
    public function __toString()
272
    {
273
        $scheme = $this->getScheme();
274
        $authority = $this->getAuthority();
275
        $basePath = $this->getBasePath();
276
        $path = $this->getPath();
277
        $query = $this->getQuery();
278
        $fragment = $this->getFragment();
279
280
        $path = $basePath . '/' . ltrim($path, '/');
281
282
        return ($scheme ? $scheme . ':' : '')
283
            . ($authority ? '//' . $authority : '')
284
            . $path
285
            . ($query ? '?' . $query : '')
286
            . ($fragment ? '#' . $fragment : '');
287
    }
288
289
    /*
290
        END OF UriInterface Implementation
291
    */
292
293
    /**
294
     * filter scheme given to only allow certain scheme, no file:// or ftp:// or other scheme because its http message uri interface
295
     *
296
     * @access protected
297
     * @param string $scheme
298
     * @return string $scheme
299
     * @throws InvalidArgumentException if not corret scheme is present
300
     */
301
    protected function filterScheme(string $scheme)
302
    {
303
        static $valid = [
304
            '' => true,
305
            'https' => true,
306
            'http' => true,
307
        ];
308
309
        $scheme = str_replace('://', '', strtolower($scheme));
310
        if (!isset($valid[$scheme])) {
311
            throw new InvalidArgumentException('Uri scheme must be one of: "", "https", "http"');
312
        }
313
314
        return $scheme;
315
    }
316
317
318
    /**
319
     * Filter allowable port to minimize risk
320
     *
321
     * @access protected
322
     * @param mixed $port
323
     * @return mixed $port
324
     * @throws InvalidArgumentException for incorrect port assigned
325
     */
326
    protected function filterPort($port)
327
    {
328
        if (is_null($port) || (is_integer($port) && ($port >= 1 && $port <= 65535))) {
329
            return $port;
330
        }
331
332
        throw new InvalidArgumentException('Uri port must be null or an integer between 1 and 65535 (inclusive)');
333
    }
334
335
    /**
336
     * Path allowed chars filter, no weird path on uri yes?.
337
     *
338
     * @access protected
339
     * @param string $path
340
     * @return rawurlencode of cleared path
341
     */
342
    protected function filterPath($path)
343
    {
344
        return preg_replace_callback(
345
            '/(?:[^a-zA-Z0-9_\-\.~:@&=\+\$,\/;%]+|%(?![A-Fa-f0-9]{2}))/',
346
            function ($match) {
347
                return rawurlencode($match[0]);
348
            },
349
            $path
350
        );
351
    }
352
353
    /**
354
     * replace query to clear not allowed chars
355
     *
356
     * @access protected
357
     * @param string $query
358
     * @return rawurlencode of replaced query
359
     */
360
    protected function filterQuery($query)
361
    {
362
        return preg_replace_callback(
363
            '/(?:[^a-zA-Z0-9_\-\.~!\$&\'\(\)\*\+,;=%:@\/\?]+|%(?![A-Fa-f0-9]{2}))/',
364
            function ($match) {
365
                return rawurlencode($match[0]);
366
            },
367
            $query
368
        );
369
    }
370
371
    /**
372
     * cek if current uri scheme use standard port
373
     *
374
     * @access protected
375
     * @return boolean
376
     */
377
    protected function hasStandardPort()
378
    {
379
        return ($this->scheme === 'http' && $this->port === 80) || ($this->scheme === 'https' && $this->port === 443);
380
    }
381
382
383
    /**
384
     * get BasePath property.
385
     *
386
     * @access public
387
     * @return string basePath
388
     */
389
    public function getBasePath()
390
    {
391
        return $this->basePath;
392
    }
393
394
    /**
395
     * Set BasePath Function to rewrite request.
396
     *
397
     * @access public
398
     * @param string $basePath
399
     * @return void
400
     */
401
    public function withBasePath(string $basePath)
402
    {
403
        $this->basePath = $basePath;
404
    }
405
406
407
    /**
408
     * get Base Url
409
     *
410
     * @access public
411
     * @return void
412
     */
413
    public function getBaseUrl()
414
    {
415
        $scheme = $this->getScheme();
416
        $authority = $this->getAuthority();
417
        $basePath = $this->getBasePath();
418
419
        if ($authority && substr($basePath, 0, 1) !== '/') {
420
            $basePath = $basePath . '/' . $basePath;
421
        }
422
423
        return ($scheme ? $scheme . ':' : '')
424
            . ($authority ? '//' . $authority : '')
425
            . rtrim($basePath, '/');
426
    }
427
428
    /**
429
     * Create uri Instance from header $_SERVER.
430
     *
431
     * @access public
432
     * @static
433
     * @return Resilient\Http\Uri
434
     */
435
    public static function createFromServer($serv)
0 ignored issues
show
Coding Style introduced by
createFromServer uses the super-global variable $_SERVER which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
436
    {
437
        $scheme = isset($serv['HTTPS']) ? 'https://' : 'http://';
438
        $host = empty($serv['HTTP_HOST']) ? $serv['HTTP_HOST'] : $serv['SERVER_NAME'];
439
        $port = empty($serv['SERVER_PORT']) ? $serv['SERVER_PORT'] : null;
440
441
        //Path
442
        $scriptName = parse_url($_SERVER['SCRIPT_NAME'], PHP_URL_PATH);
443
        $scriptPath = dirname($scriptName);
444
445
        $requestUri = parse_url('http://www.example.com/' . $_SERVER['REQUEST_URI'], PHP_URL_PATH);
446
447
        if (stripos($requestUri, $scriptName) === 0) {
448
            $basePath = $scriptName;
449
        } elseif ($scriptPath !== '/' && stripos($requestUri, $scriptPath) === 0) {
450
            $basePath = $scriptPath;
451
        }
452
453
        if (empty($basePath)) {
454
            $path = $requestUri;
455
            $basePath = '';
456
        } else {
457
            $path = ltrim(substr($requestUri, strlen($basePath)), '/');
458
        }
459
460
        $query = empty($serv['QUERY_STRING']) ? parse_url('http://example.com' . $serv['REQUEST_URI'], PHP_URL_QUERY) : $serv['QUERY_STRING'];
461
462
        $fragment = '';
463
464
        $user = !empty($serv['PHP_AUTH_USER']) ? $serv['PHP_AUTH_USER'] : '';
465
        $password = !empty($serv['PHP_AUTH_PW']) ? $serv['PHP_AUTH_PW'] : '';
466
467
        if (empty($user) && empty($password) && !empty($serv['HTTP_AUTHORIZATION'])) {
468
            list($user, $password) = explode(':', base64_decode(substr($serv['HTTP_AUTHORIZATION'], 6)));
469
        }
470
471
        $method = !empty($serv['REQUEST_METHOD']) ? $serv['REQUEST_METHOD'] : '';
0 ignored issues
show
Unused Code introduced by
$method is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
472
473
        $uri = new static($scheme, $host, $port, $path, $query, $fragment, $user, $password);
0 ignored issues
show
Security Bug introduced by
It seems like $path defined by $requestUri on line 454 can also be of type false; however, Resilient\Http\Uri::__construct() does only seem to accept string, did you maybe forget to handle an error condition?

This check looks for type mismatches where the missing type is false. This is usually indicative of an error condtion.

Consider the follow example

<?php

function getDate($date)
{
    if ($date !== null) {
        return new DateTime($date);
    }

    return false;
}

This function either returns a new DateTime object or false, if there was an error. This is a typical pattern in PHP programming to show that an error has occurred without raising an exception. The calling code should check for this returned false before passing on the value to another function or method that may not be able to handle a false.

Loading history...
474
475
        if ($basePath) {
476
            $uri->withBasePath($basePath);
477
        }
478
479
        return $uri;
480
    }
481
482
483
    /**
484
     * Create Uri Instance from string http://www.example.com/url/path.html
485
     *
486
     * @access public
487
     * @static
488
     * @param string $uri
489
     * @return Resilient\Http\Uri
490
     */
491
    public static function createFromString(string $uri)
492
    {
493
        $parts = parse_url($uri);
494
        $scheme = isset($parts['scheme']) ? $parts['scheme'] : '';
495
        $user = isset($parts['user']) ? $parts['user'] : '';
496
        $pass = isset($parts['pass']) ? $parts['pass'] : '';
497
        $host = isset($parts['host']) ? $parts['host'] : '';
498
        $port = isset($parts['port']) ? $parts['port'] : null;
499
        $path = isset($parts['path']) ? $parts['path'] : '';
500
        $query = isset($parts['query']) ? $parts['query'] : '';
501
        $fragment = isset($parts['fragment']) ? $parts['fragment'] : '';
502
503
        return new static($scheme, $host, $port, $path, $query, $fragment, $user, $pass);
504
    }
505
}
506