Passed
Push — master ( 8e4875...07bfbc )
by
unknown
12:19
created

ReferrerEnforcer::handle()   C

Complexity

Conditions 12
Paths 4

Size

Total Lines 51
Code Lines 33

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 33
dl 0
loc 51
rs 6.9666
c 0
b 0
f 0
cc 12
nc 4
nop 1

How to fix   Long Method    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
declare(strict_types=1);
4
5
/*
6
 * This file is part of the TYPO3 CMS project.
7
 *
8
 * It is free software; you can redistribute it and/or modify it under
9
 * the terms of the GNU General Public License, either version 2
10
 * of the License, or any later version.
11
 *
12
 * For the full copyright and license information, please read the
13
 * LICENSE.txt file that was distributed with this source code.
14
 *
15
 * The TYPO3 project - inspiring people to share!
16
 */
17
18
namespace TYPO3\CMS\Core\Http\Security;
19
20
use Psr\Http\Message\ResponseInterface;
21
use Psr\Http\Message\ServerRequestInterface;
22
use TYPO3\CMS\Core\Http\HtmlResponse;
23
use TYPO3\CMS\Core\Http\NormalizedParams;
24
use TYPO3\CMS\Core\Utility\GeneralUtility;
25
use TYPO3\CMS\Core\Utility\PathUtility;
26
27
/**
28
 * @internal
29
 */
30
class ReferrerEnforcer
31
{
32
    private const TYPE_REFERRER_EMPTY = 1;
33
    private const TYPE_REFERRER_SAME_SITE = 2;
34
    private const TYPE_REFERRER_SAME_ORIGIN = 4;
35
36
    /**
37
     * @var ServerRequestInterface
38
     */
39
    protected $request;
40
41
    /**
42
     * @var string
43
     */
44
    protected $requestHost;
45
46
    /**
47
     * @var string
48
     */
49
    protected $requestDir;
50
51
    public function __construct(ServerRequestInterface $request)
52
    {
53
        $this->request = $request;
54
        $this->requestHost = rtrim($this->resolveRequestHost($request), '/') . '/';
55
        $this->requestDir = $this->resolveRequestDir($request);
56
    }
57
58
    public function handle(array $options = null): ?ResponseInterface
59
    {
60
        $referrerType = $this->resolveReferrerType();
61
        // valid referrer, no more actions required
62
        if ($referrerType & self::TYPE_REFERRER_SAME_ORIGIN) {
63
            return null;
64
        }
65
        $flags = $options['flags'] ?? [];
66
        $expiration = $options['expiration'] ?? 5;
67
        // referrer is missing and route requested to refresh
68
        // (created HTML refresh to enforce having referrer)
69
        if (($this->request->getQueryParams()['referrer-refresh'] ?? 0) <= time()
70
            && (
71
                in_array('refresh-always', $flags, true)
72
                || ($referrerType & self::TYPE_REFERRER_EMPTY && in_array('refresh-empty', $flags, true))
73
                || ($referrerType & self::TYPE_REFERRER_SAME_SITE && in_array('refresh-same-site', $flags, true))
74
            )
75
        ) {
76
            $refreshParameter = 'referrer-refresh=' . (time() + $expiration);
77
            $refreshUri = $this->request->getUri();
78
            $query = $refreshUri->getQuery();
79
            $refreshUri = $refreshUri->withQuery(
80
                $query !== '' ? $query . '&' . $refreshParameter : $refreshParameter
81
            );
82
            $scriptUri = $this->resolveAbsoluteWebPath(
83
                'EXT:core/Resources/Public/JavaScript/ReferrerRefresh.js'
84
            );
85
            // simulating navigate event by clicking anchor link
86
            // since meta-refresh won't change `document.referrer` in e.g. Firefox
87
            return new HtmlResponse(sprintf(
88
                '<html>'
89
                . '<head><link rel="icon" href="data:image/svg+xml,"></head>'
90
                . '<body><a href="%1$s" id="referrer-refresh">&nbsp;</a>'
91
                . '<script src="%2$s"></script></body>'
92
                . '</html>',
93
                htmlspecialchars((string)$refreshUri),
94
                htmlspecialchars($scriptUri)
95
            ));
96
        }
97
        $subject = $options['subject'] ?? '';
98
        if ($referrerType & self::TYPE_REFERRER_EMPTY) {
99
            // still empty referrer or invalid referrer, deny route invocation
100
            throw new MissingReferrerException(
101
                sprintf('Missing referrer%s', $subject !== '' ? ' for ' . $subject : ''),
102
                1588095935
103
            );
104
        }
105
        // referrer is given, but does not match current base URL
106
        throw new InvalidReferrerException(
107
            sprintf('Invalid referrer%s', $subject !== '' ? ' for ' . $subject : ''),
108
            1588095936
109
        );
110
    }
111
112
    protected function resolveAbsoluteWebPath(string $target): string
113
    {
114
        return PathUtility::getAbsoluteWebPath(
115
            GeneralUtility::getFileAbsFileName($target)
116
        );
117
    }
118
119
    protected function resolveReferrerType(): int
120
    {
121
        $referrer = $this->request->getServerParams()['HTTP_REFERER'] ?? '';
122
        if ($referrer === '') {
123
            return self::TYPE_REFERRER_EMPTY;
124
        }
125
        if (strpos($referrer, $this->requestDir) === 0) {
126
            // same-origin implies same-site
127
            return self::TYPE_REFERRER_SAME_ORIGIN | self::TYPE_REFERRER_SAME_SITE;
128
        }
129
        if (strpos($referrer, $this->requestHost) === 0) {
130
            return self::TYPE_REFERRER_SAME_SITE;
131
        }
132
        return 0;
133
    }
134
135
    protected function resolveRequestHost(ServerRequestInterface $request): string
136
    {
137
        $normalizedParams = $request->getAttribute('normalizedParams');
138
        if ($normalizedParams instanceof NormalizedParams) {
139
            return $normalizedParams->getRequestHost();
140
        }
141
        return GeneralUtility::getIndpEnv('TYPO3_REQUEST_HOST');
142
    }
143
144
    protected function resolveRequestDir(ServerRequestInterface $request): string
145
    {
146
        $normalizedParams = $request->getAttribute('normalizedParams');
147
        if ($normalizedParams instanceof NormalizedParams) {
148
            return $normalizedParams->getRequestDir();
149
        }
150
        return GeneralUtility::getIndpEnv('TYPO3_REQUEST_DIR');
151
    }
152
}
153