Code Coverage
 
Lines
Branches
Paths
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
35 / 35
88.46% covered (warning)
88.46%
46 / 52
1.04% covered (danger)
1.04%
14 / 1350
75.00% covered (warning)
75.00%
3 / 4
CRAP
0.00% covered (danger)
0.00%
0 / 1
Uri
100.00% covered (success)
100.00%
35 / 35
88.46% covered (warning)
88.46%
46 / 52
1.04% covered (danger)
1.04%
14 / 1350
100.00% covered (success)
100.00%
4 / 4
368.88
100.00% covered (success)
100.00%
1 / 1
 __construct
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 remember
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 pull
100.00% covered (success)
100.00%
12 / 12
100.00% covered (success)
100.00%
11 / 11
55.56% covered (warning)
55.56%
5 / 9
100.00% covered (success)
100.00%
1 / 1
9.16
 isLocal
100.00% covered (success)
100.00%
18 / 18
84.62% covered (warning)
84.62%
33 / 39
0.52% covered (danger)
0.52%
7 / 1339
100.00% covered (success)
100.00%
1 / 1
130.11
1<?php
2
3declare(strict_types=1);
4
5namespace Celemas\Session;
6
7/** @api */
8class Uri
9{
10    public const string REMEMBERED = 'celemas_remembered_uri';
11
12    /** @param non-empty-string $key */
13    public function __construct(
14        private readonly Session $session,
15        private readonly string $key = self::REMEMBERED,
16    ) {}
17
18    public function remember(
19        string $uri,
20        int $expires = 3600,
21    ): void {
22        $this->session->set($this->key, [
23            'uri' => $uri,
24            'expires' => time() + $expires,
25        ]);
26    }
27
28    public function pull(string $default = '/'): string
29    {
30        /** @var mixed $rememberedUri */
31        $rememberedUri = $this->session->pull($this->key, null);
32
33        if (!is_array($rememberedUri)) {
34            return $default;
35        }
36
37        $uri = $rememberedUri['uri'] ?? null;
38        $expires = $rememberedUri['expires'] ?? null;
39
40        if (!is_string($uri) || !is_int($expires)) {
41            return $default;
42        }
43
44        if ($expires <= time()) {
45            return $default;
46        }
47
48        if (!self::isLocal($uri)) {
49            return $default;
50        }
51
52        return $uri;
53    }
54
55    private static function isLocal(string $uri): bool
56    {
57        if ($uri === '') {
58            return false;
59        }
60
61        if (!str_starts_with($uri, '/') || str_starts_with($uri, '//')) {
62            return false;
63        }
64
65        $decodedUri = rawurldecode($uri);
66
67        if (preg_match('/[\x00-\x1F\x7F]/', $decodedUri) === 1) {
68            return false;
69        }
70
71        if (str_contains($decodedUri, '\\')) {
72            return false;
73        }
74
75        if (!str_starts_with($decodedUri, '/') || str_starts_with($decodedUri, '//')) {
76            return false;
77        }
78
79        $parts = parse_url($uri);
80
81        return (
82            is_array($parts)
83            && !array_key_exists('scheme', $parts)
84            && !array_key_exists('host', $parts)
85            && array_key_exists('path', $parts)
86        );
87    }
88}