Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
77.53% covered (warning)
77.53%
69 / 89
66.67% covered (warning)
66.67%
6 / 9
CRAP
0.00% covered (danger)
0.00%
0 / 1
HttpUtils
77.53% covered (warning)
77.53%
69 / 89
66.67% covered (warning)
66.67%
6 / 9
41.91
0.00% covered (danger)
0.00%
0 / 1
 getBotRegexes
100.00% covered (success)
100.00%
13 / 13
100.00% covered (success)
100.00%
1 / 1
1
 isBot
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
3
 getEInkRegexes
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
1
 isEInk
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
3
 countRequest
85.71% covered (warning)
85.71%
12 / 14
0.00% covered (danger)
0.00%
0 / 1
5.07
 stripParams
0.00% covered (danger)
0.00%
0 / 12
0.00% covered (danger)
0.00%
0 / 1
42
 dieWithHttpError
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
1
 redirect
100.00% covered (success)
100.00%
15 / 15
100.00% covered (success)
100.00%
1 / 1
1
 validateGetParams
64.71% covered (warning)
64.71%
11 / 17
0.00% covered (danger)
0.00%
0 / 1
7.58
 sendHttpResponseCode
n/a
0 / 0
n/a
0 / 0
1
 sendHeader
n/a
0 / 0
n/a
0 / 0
1
 sendHttpBody
n/a
0 / 0
n/a
0 / 0
1
 exitExecution
n/a
0 / 0
n/a
0 / 0
1
1<?php
2
3namespace Olz\Utils;
4
5use Olz\Components\Error\OlzErrorPage\OlzErrorPage;
6use Olz\Components\Page\OlzFooter\OlzFooter;
7use Olz\Components\Page\OlzHeaderWithoutRouting\OlzHeaderWithoutRouting;
8use Olz\Entity\Counter;
9use PHPStan\PhpDocParser\Ast\Type\TypeNode;
10use PhpTypeScriptApi\PhpStan\PhpStanUtils;
11use PhpTypeScriptApi\PhpStan\ValidateVisitor;
12use Symfony\Component\HttpFoundation\Request;
13
14class HttpUtils {
15    use WithUtilsTrait;
16
17    /** @return array<string> */
18    public function getBotRegexes(): array {
19        return [
20            '/bingbot/i',
21            '/googlebot/i',
22            '/google/i',
23            '/facebookexternalhit/i',
24            '/applebot/i',
25            '/yandexbot/i',
26            '/ecosia/i',
27            '/phpservermon/i',
28            '/OlzSystemTest\//i',
29            '/bot\//i',
30            '/crawler\//i',
31        ];
32    }
33
34    public function isBot(string $user_agent): bool {
35        foreach ($this->getBotRegexes() as $regex) {
36            if (preg_match($regex, $user_agent)) {
37                return true;
38            }
39        }
40        return false;
41    }
42
43    /** @return array<string> */
44    public function getEInkRegexes(): array {
45        return [
46            '/kindle\//i',
47            '/pocketbook\//i',
48        ];
49    }
50
51    public function isEInk(string $user_agent): bool {
52        foreach ($this->getEInkRegexes() as $regex) {
53            if (preg_match($regex, $user_agent)) {
54                return true;
55            }
56        }
57        return false;
58    }
59
60    /** @param array<string> $get_params */
61    public function countRequest(Request $request, array $get_params = []): void {
62        $user_agent = $this->server()['HTTP_USER_AGENT'] ?? '';
63        if ($this->isBot($user_agent)) {
64            $this->log()->debug("Counter: user agent is bot: {$user_agent}");
65            return;
66        }
67        $path = "{$request->getBasePath()}{$request->getPathInfo()}";
68        $query = [];
69        foreach ($get_params as $key) {
70            $value = $request->query->get($key);
71            if ($value !== null) {
72                $query[] = "{$key}={$value}";
73            }
74        }
75        $pretty_query = empty($query) ? '' : '?'.implode('&', $query);
76        $counter_repo = $this->entityManager()->getRepository(Counter::class);
77        $counter_repo->record("{$path}{$pretty_query}");
78        $this->log()->debug("Counter: Counted {$path}{$pretty_query} (user agent: {$user_agent})");
79    }
80
81    /** @param array<string> $get_params */
82    public function stripParams(Request $request, array $get_params = []): void {
83        $should_strip = false;
84        $query = [];
85        foreach ($request->query->all() as $key => $value) {
86            if (in_array($key, $get_params)) {
87                $should_strip = true;
88            } elseif (is_string($value)) {
89                $query[] = "{$key}={$value}";
90            }
91        }
92        if (!$should_strip) {
93            return;
94        }
95        $path = "{$request->getBasePath()}{$request->getPathInfo()}";
96        $pretty_query = empty($query) ? '' : '?'.implode('&', $query);
97        $this->redirect("{$path}{$pretty_query}", 308);
98    }
99
100    public function dieWithHttpError(int $http_status_code): void {
101        $this->sendHttpResponseCode($http_status_code);
102
103        $out = OlzErrorPage::render([
104            'http_status_code' => $http_status_code,
105        ]);
106
107        $this->sendHttpBody($out);
108        $this->exitExecution();
109    }
110
111    public function redirect(string $redirect_url, int $http_status_code = 301): void {
112        $this->sendHeader("Location: {$redirect_url}");
113        $this->sendHttpResponseCode($http_status_code);
114
115        $out = "";
116        $out .= OlzHeaderWithoutRouting::render([
117            'title' => "Weiterleitung...",
118        ]);
119
120        $enc_redirect_url = json_encode($redirect_url);
121        $out .= <<<ZZZZZZZZZZ
122            <div class='content-full'>
123                <h2>Automatische Weiterleitung...</h2>
124                <p>Falls die automatische Weiterleitung nicht funktionieren sollte, kannst du auch diesen Link anklicken:</p>
125                <p><b><a href='{$redirect_url}' class='linkint' id='redirect-link'>{$redirect_url}</a></b></p>
126                <script type='text/javascript'>
127                    window.setTimeout(function () {
128                        window.location.href = {$enc_redirect_url};
129                    }, 1000);
130                </script>
131            </div>
132            ZZZZZZZZZZ;
133
134        $out .= OlzFooter::render();
135        $this->sendHttpBody($out);
136        $this->exitExecution();
137    }
138
139    /**
140     * @template T of array
141     *
142     * @param class-string<HttpParams<T>>             $params_class
143     * @param ?array<string, ?(string|array<string>)> $get_params
144     * @param array{just_log?: bool}                  $options
145     *
146     * @return T
147     */
148    public function validateGetParams(string $params_class, ?array $get_params = null, array $options = []): array {
149        if ($get_params === null) {
150            $get_params = $this->getParams();
151        }
152        $utils = new PhpStanUtils();
153        $generics = $utils->getSuperGenerics($params_class, HttpParams::class);
154        $type = $generics[0] ?? null;
155        if (!$type) {
156            $this->dieWithHttpError(400);
157            throw new \Exception('should already have failed');
158        }
159        $resolved_type = $utils->resolveType($type, $params_class);
160        if (!$resolved_type instanceof TypeNode) {
161            $this->dieWithHttpError(400);
162            throw new \Exception('should already have failed');
163        }
164        $result = ValidateVisitor::validateDeserialize($utils, $get_params, $resolved_type, []);
165        if (!$result->isValid() && ($options['just_log'] ?? false) === false) {
166            $this->dieWithHttpError(400);
167            throw new \Exception('should already have failed');
168        }
169        return $result->getValue();
170    }
171
172    // @codeCoverageIgnoreStart
173    // Reason: Mock functions for tests.
174
175    protected function sendHttpResponseCode(int $http_response_code): void {
176        http_response_code($http_response_code);
177    }
178
179    protected function sendHeader(string $http_header_line): void {
180        header($http_header_line);
181    }
182
183    protected function sendHttpBody(string $http_body): void {
184        echo $http_body;
185    }
186
187    protected function exitExecution(): void {
188        exit('');
189    }
190
191    // @codeCoverageIgnoreEnd
192}