Code Coverage
 
Lines
Branches
Paths
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
25 / 25
86.49% covered (warning)
86.49%
32 / 37
11.88% covered (danger)
11.88%
12 / 101
75.00% covered (warning)
75.00%
3 / 4
CRAP
0.00% covered (danger)
0.00%
0 / 1
Uri
100.00% covered (success)
100.00%
25 / 25
86.49% covered (warning)
86.49%
32 / 37
11.88% covered (danger)
11.88%
12 / 101
100.00% covered (success)
100.00%
4 / 4
128.64
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%
8 / 8
79.17% covered (warning)
79.17%
19 / 24
5.56% covered (danger)
5.56%
5 / 90
100.00% covered (success)
100.00%
1 / 1
26.06
1<?php
2
3declare(strict_types=1);
4
5namespace Celema\Session;
6
7/** @api */
8class Uri
9{
10    public const string REMEMBERED = 'celema_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 (!str_starts_with($uri, '/') || str_starts_with($uri, '//')) {
58            return false;
59        }
60
61        $decodedUri = rawurldecode($uri);
62
63        if (preg_match('/[\x00-\x1F\x7F]/', $decodedUri) === 1) {
64            return false;
65        }
66
67        if (str_contains($decodedUri, '\\')) {
68            return false;
69        }
70
71        return !str_starts_with($decodedUri, '//');
72    }
73}