‪TYPO3CMS  ‪main
ReferrerEnforcer.php
Go to the documentation of this file.
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 
19 
20 use Psr\Http\Message\ResponseInterface;
21 use Psr\Http\Message\ServerRequestInterface;
27 
32 {
33  private const ‪TYPE_REFERRER_EMPTY = 1;
34  private const ‪TYPE_REFERRER_SAME_SITE = 2;
35  private const ‪TYPE_REFERRER_SAME_ORIGIN = 4;
36 
40  protected ‪$request;
41 
45  protected ‪$requestHost;
46 
50  protected ‪$requestDir;
51 
52  public function ‪__construct(ServerRequestInterface ‪$request)
53  {
54  $this->request = ‪$request;
55  $this->requestHost = rtrim($this->‪resolveRequestHost($request), '/') . '/';
56  $this->requestDir = $this->‪resolveRequestDir($request);
57  }
58 
59  public function ‪handle(array $options = null): ?ResponseInterface
60  {
61  $referrerType = $this->‪resolveReferrerType();
62  // valid referrer, no more actions required
63  if ($referrerType & self::TYPE_REFERRER_SAME_ORIGIN) {
64  return null;
65  }
66  $flags = $options['flags'] ?? [];
67  $expiration = $options['expiration'] ?? 5;
68  $nonce = $this->request->getAttribute('nonce');
69  // referrer is missing and route requested to refresh
70  // (created HTML refresh to enforce having referrer)
71  if (($this->request->getQueryParams()['referrer-refresh'] ?? 0) <= time()
72  && (
73  in_array('refresh-always', $flags, true)
74  || ($referrerType & self::TYPE_REFERRER_EMPTY && in_array('refresh-empty', $flags, true))
75  || ($referrerType & self::TYPE_REFERRER_SAME_SITE && in_array('refresh-same-site', $flags, true))
76  )
77  ) {
78  $refreshParameter = 'referrer-refresh=' . (time() + $expiration);
79  $refreshUri = $this->request->getUri();
80  $query = $refreshUri->getQuery();
81  $refreshUri = $refreshUri->withQuery(
82  $query !== '' ? $query . '&' . $refreshParameter : $refreshParameter
83  );
84  $scriptUri = $this->‪resolveAbsoluteWebPath(
85  'EXT:core/Resources/Public/JavaScript/referrer-refresh.js'
86  );
87  $attributes = ['src' => $scriptUri];
88  if ($nonce instanceof ConsumableNonce) {
89  $attributes['nonce'] = $nonce->consume();
90  }
91  // simulating navigate event by clicking anchor link
92  // since meta-refresh won't change `document.referrer` in e.g. Firefox
93  return new HtmlResponse(sprintf(
94  '<html>'
95  . '<head><link rel="icon" href="data:image/svg+xml,"></head>'
96  . '<body><a href="%s" id="referrer-refresh">&nbsp;</a>'
97  . '<script %s></script></body>'
98  . '</html>',
99  htmlspecialchars((string)$refreshUri),
100  GeneralUtility::implodeAttributes($attributes, true)
101  ));
102  }
103  $subject = $options['subject'] ?? '';
104  if ($referrerType & self::TYPE_REFERRER_EMPTY) {
105  // still empty referrer or invalid referrer, deny route invocation
106  throw new MissingReferrerException(
107  sprintf('Missing referrer%s', $subject !== '' ? ' for ' . $subject : ''),
108  1588095935
109  );
110  }
111  // referrer is given, but does not match current base URL
112  throw new InvalidReferrerException(
113  sprintf('Invalid referrer%s', $subject !== '' ? ' for ' . $subject : ''),
114  1588095936
115  );
116  }
117 
118  protected function ‪resolveAbsoluteWebPath(string $target): string
119  {
121  }
122 
123  protected function ‪resolveReferrerType(): int
124  {
125  $referrer = $this->request->getServerParams()['HTTP_REFERER'] ?? '';
126  if ($referrer === '') {
128  }
129  if (str_starts_with($referrer, $this->requestDir)) {
130  // same-origin implies same-site
131  return self::TYPE_REFERRER_SAME_ORIGIN | ‪self::TYPE_REFERRER_SAME_SITE;
132  }
133  if (str_starts_with($referrer, $this->requestHost)) {
135  }
136  return 0;
137  }
138 
139  protected function ‪resolveRequestHost(ServerRequestInterface ‪$request): string
140  {
141  $normalizedParams = ‪$request->getAttribute('normalizedParams');
142  if ($normalizedParams instanceof ‪NormalizedParams) {
143  return $normalizedParams->getRequestHost();
144  }
145  return GeneralUtility::getIndpEnv('TYPO3_REQUEST_HOST');
146  }
147 
148  protected function ‪resolveRequestDir(ServerRequestInterface ‪$request): string
149  {
150  $normalizedParams = ‪$request->getAttribute('normalizedParams');
151  if ($normalizedParams instanceof ‪NormalizedParams) {
152  return $normalizedParams->getRequestDir();
153  }
154  return GeneralUtility::getIndpEnv('TYPO3_REQUEST_DIR');
155  }
156 }
‪TYPO3\CMS\Core\Http\Security\ReferrerEnforcer\resolveReferrerType
‪resolveReferrerType()
Definition: ReferrerEnforcer.php:120
‪TYPO3\CMS\Core\Http\Security\ReferrerEnforcer
Definition: ReferrerEnforcer.php:32
‪TYPO3\CMS\Core\Utility\PathUtility
Definition: PathUtility.php:27
‪TYPO3\CMS\Core\Http\Security\ReferrerEnforcer\__construct
‪__construct(ServerRequestInterface $request)
Definition: ReferrerEnforcer.php:49
‪TYPO3\CMS\Core\Security\ContentSecurityPolicy\ConsumableNonce
Definition: ConsumableNonce.php:24
‪TYPO3\CMS\Core\Http\Security
Definition: InvalidReferrerException.php:16
‪TYPO3\CMS\Core\Http\Security\MissingReferrerException
Definition: MissingReferrerException.php:23
‪TYPO3\CMS\Core\Http\Security\ReferrerEnforcer\$request
‪ServerRequestInterface $request
Definition: ReferrerEnforcer.php:39
‪TYPO3\CMS\Core\Http\Security\ReferrerEnforcer\resolveRequestDir
‪resolveRequestDir(ServerRequestInterface $request)
Definition: ReferrerEnforcer.php:145
‪TYPO3\CMS\Core\Http\Security\ReferrerEnforcer\TYPE_REFERRER_SAME_SITE
‪const TYPE_REFERRER_SAME_SITE
Definition: ReferrerEnforcer.php:34
‪TYPO3\CMS\Core\Utility\PathUtility\getPublicResourceWebPath
‪static getPublicResourceWebPath(string $resourcePath, bool $prefixWithSitePath=true)
Definition: PathUtility.php:97
‪TYPO3\CMS\Core\Http\Security\ReferrerEnforcer\handle
‪handle(array $options=null)
Definition: ReferrerEnforcer.php:56
‪TYPO3\CMS\Core\Http\Security\ReferrerEnforcer\$requestDir
‪string $requestDir
Definition: ReferrerEnforcer.php:47
‪TYPO3\CMS\Core\Http\Security\ReferrerEnforcer\TYPE_REFERRER_EMPTY
‪const TYPE_REFERRER_EMPTY
Definition: ReferrerEnforcer.php:33
‪TYPO3\CMS\Core\Http\Security\ReferrerEnforcer\resolveRequestHost
‪resolveRequestHost(ServerRequestInterface $request)
Definition: ReferrerEnforcer.php:136
‪TYPO3\CMS\Core\Utility\GeneralUtility
Definition: GeneralUtility.php:52
‪TYPO3\CMS\Core\Http\Security\InvalidReferrerException
Definition: InvalidReferrerException.php:23
‪TYPO3\CMS\Core\Http\Security\ReferrerEnforcer\TYPE_REFERRER_SAME_ORIGIN
‪const TYPE_REFERRER_SAME_ORIGIN
Definition: ReferrerEnforcer.php:35
‪TYPO3\CMS\Core\Http\Security\ReferrerEnforcer\$requestHost
‪string $requestHost
Definition: ReferrerEnforcer.php:43
‪TYPO3\CMS\Core\Http\NormalizedParams
Definition: NormalizedParams.php:38
‪TYPO3\CMS\Core\Http\Security\ReferrerEnforcer\resolveAbsoluteWebPath
‪resolveAbsoluteWebPath(string $target)
Definition: ReferrerEnforcer.php:115
‪TYPO3\CMS\Core\Http\HtmlResponse
Definition: HtmlResponse.php:28