‪TYPO3CMS  ‪main
ShowImageController.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;
22 use Symfony\Component\DependencyInjection\Attribute\Autoconfigure;
35 
51 #[Autoconfigure(public: true)]
53 {
54  protected const ‪ALLOWED_PARAMETER_NAMES = ['width', 'height', 'crop', 'bodyTag', 'title'];
55 
59  protected ‪$request;
60 
64  protected ‪$file;
65 
69  protected ‪$width;
70 
74  protected ‪$height;
75 
79  protected ‪$crop;
80 
84  protected ‪$frame;
85 
89  protected ‪$bodyTag = '<body>';
90 
94  protected ‪$title = 'Image';
95 
99  protected ‪$content = <<<EOF
100 <!DOCTYPE html>
101 <html>
102 <head>
103  <title>###TITLE###</title>
104  <meta name="robots" content="noindex,follow" />
105 </head>
106 ###BODY###
107  ###IMAGE###
108 </body>
109 </html>
110 EOF;
111 
112  public function ‪__construct(
113  protected readonly ‪Features $features
114  ) {}
115 
122  public function ‪initialize()
123  {
124  $fileUid = $this->request->getQueryParams()['file'] ?? null;
125  $parametersArray = $this->request->getQueryParams()['parameters'] ?? null;
126 
127  // If no file-param or parameters are given, we must exit
128  if (!$fileUid || !isset($parametersArray) || !is_array($parametersArray)) {
129  throw new \InvalidArgumentException('No valid fileUid given', 1476048455);
130  }
131 
132  // rebuild the parameter array and check if the HMAC is correct
133  $parametersEncoded = implode('', $parametersArray);
134 
135  /* For backwards compatibility the HMAC is transported within the md5 param */
136  $hmacParameter = $this->request->getQueryParams()['md5'] ?? null;
137  $hashService = GeneralUtility::makeInstance(HashService::class);
138  $hmac = $hashService->hmac(implode('|', [$fileUid, $parametersEncoded]), 'tx_cms_showpic');
139  if (!is_string($hmacParameter) || !hash_equals($hmac, $hmacParameter)) {
140  throw new \InvalidArgumentException('hash does not match', 1476048456);
141  }
142 
143  // decode the parameters Array - `bodyTag` contains HTML if set and would lead
144  // to a false-positive XSS-detection, that's why parameters are base64-encoded
145  $parameters = json_decode(base64_decode($parametersEncoded), true) ?? [];
146  foreach ($parameters as $parameterName => $parameterValue) {
147  if (in_array($parameterName, static::ALLOWED_PARAMETER_NAMES, true)) {
148  $this->{$parameterName} = $parameterValue;
149  }
150  }
151 
153  $this->file = GeneralUtility::makeInstance(ResourceFactory::class)->getFileObject((int)$fileUid);
154  } else {
155  $this->file = GeneralUtility::makeInstance(ResourceFactory::class)->retrieveFileOrFolderObject($fileUid);
156  }
157  if (!($this->file instanceof FileInterface && $this->‪isFileValid($this->file))) {
158  throw new Exception('File processing for local storage is denied', 1594043425);
159  }
160 
161  if ($this->features->isFeatureEnabled('security.frontend.allowInsecureFrameOptionInShowImageController')) {
162  $frameValue = $this->request->getQueryParams()['frame'] ?? null;
163  if ($frameValue !== null && ‪MathUtility::canBeInterpretedAsInteger($frameValue)) {
164  $this->frame = (int)$frameValue;
165  }
166  }
167  }
168 
173  public function ‪main()
174  {
175  $processedImage = $this->‪processImage();
176  $imageAttributes = [
177  'src' => $processedImage->getPublicUrl() ?? '',
178  'alt' => $this->file->getProperty('alternative') ?: ‪$this->title,
179  'title' => $this->file->getProperty('title') ?: ‪$this->title,
180  'width' => (string)$processedImage->getProperty('width'),
181  'height' => (string)$processedImage->getProperty('height'),
182  ];
183 
184  $markerArray = [
185  '###TITLE###' => htmlspecialchars($this->file->getProperty('title') ?: $this->title),
186  '###IMAGE###' => sprintf('<img %s>', GeneralUtility::implodeAttributes($imageAttributes, true)),
187  '###BODY###' => ‪$this->bodyTag,
188  ];
189 
190  $this->content = str_replace(array_keys($markerArray), array_values($markerArray), $this->content);
191  }
192 
198  protected function ‪processImage()
199  {
200  $max = str_contains($this->width . $this->height, 'm') ? 'm' : '';
201  $this->height = ‪MathUtility::forceIntegerInRange($this->height, 0);
202  $this->width = ‪MathUtility::forceIntegerInRange((int)$this->width, 0) . $max;
203 
204  $processingConfiguration = [
205  'width' => ‪$this->width,
206  'height' => ‪$this->height,
207  'frame' => ‪$this->frame,
208  'crop' => ‪$this->crop,
209  ];
210  return $this->file->process(‪ProcessedFile::CONTEXT_IMAGECROPSCALEMASK, $processingConfiguration);
211  }
212 
219  public function ‪processRequest(ServerRequestInterface ‪$request): ResponseInterface
220  {
221  $this->request = ‪$request;
222 
223  try {
224  $this->‪initialize();
225  $this->‪main();
226  $response = new ‪Response();
227  $response->getBody()->write($this->content);
228  return $response;
229  } catch (\InvalidArgumentException $e) {
230  // add a 410 "gone" if invalid parameters given
231  return (new ‪Response())->withStatus(410);
232  } catch (‪Exception $e) {
233  return (new Response())->withStatus(404);
234  }
235  }
236 
237  protected function ‪isFileValid(FileInterface ‪$file): bool
238  {
239  return ‪$file->‪getStorage()->getDriverType() !== 'Local'
240  || GeneralUtility::makeInstance(FileNameValidator::class)
241  ->isValid(basename(‪$file->‪getIdentifier()));
242  }
243 }
‪TYPO3\CMS\Frontend\Controller\ShowImageController\$frame
‪int null $frame
Definition: ShowImageController.php:78
‪TYPO3\CMS\Frontend\Controller\ShowImageController\$bodyTag
‪string $bodyTag
Definition: ShowImageController.php:82
‪TYPO3\CMS\Core\Resource\FileInterface
Definition: FileInterface.php:26
‪TYPO3\CMS\Core\Exception
Definition: Exception.php:21
‪TYPO3\CMS\Core\Resource\Security\FileNameValidator
Definition: FileNameValidator.php:25
‪TYPO3\CMS\Frontend\Controller\ShowImageController\ALLOWED_PARAMETER_NAMES
‪const ALLOWED_PARAMETER_NAMES
Definition: ShowImageController.php:54
‪TYPO3\CMS\Core\Resource\ProcessedFile\CONTEXT_IMAGECROPSCALEMASK
‪const CONTEXT_IMAGECROPSCALEMASK
Definition: ProcessedFile.php:61
‪TYPO3\CMS\Frontend\Controller\ShowImageController\__construct
‪__construct(protected readonly Features $features)
Definition: ShowImageController.php:103
‪TYPO3\CMS\Core\Exception
‪TYPO3\CMS\Frontend\Controller\ShowImageController\processImage
‪TYPO3 CMS Core Resource ProcessedFile processImage()
Definition: ShowImageController.php:189
‪TYPO3\CMS\Frontend\Controller\ShowImageController\isFileValid
‪isFileValid(FileInterface $file)
Definition: ShowImageController.php:228
‪TYPO3\CMS\Frontend\Controller\ShowImageController\processRequest
‪ResponseInterface processRequest(ServerRequestInterface $request)
Definition: ShowImageController.php:210
‪TYPO3\CMS\Frontend\Controller\ShowImageController\initialize
‪initialize()
Definition: ShowImageController.php:113
‪TYPO3\CMS\Frontend\Controller\ShowImageController\$file
‪File Folder null $file
Definition: ShowImageController.php:62
‪TYPO3\CMS\Core\Utility\MathUtility\canBeInterpretedAsInteger
‪static bool canBeInterpretedAsInteger(mixed $var)
Definition: MathUtility.php:74
‪TYPO3\CMS\Frontend\Controller\ShowImageController\$crop
‪string $crop
Definition: ShowImageController.php:74
‪TYPO3\CMS\Core\Http\Response
Definition: Response.php:32
‪TYPO3\CMS\Frontend\Controller\ShowImageController\$title
‪string $title
Definition: ShowImageController.php:86
‪TYPO3\CMS\Frontend\Controller\ShowImageController\$request
‪Psr Http Message ServerRequestInterface $request
Definition: ShowImageController.php:58
‪TYPO3\CMS\Frontend\Exception
Definition: Exception.php:23
‪TYPO3\CMS\Frontend\Controller\ShowImageController\$content
‪string $content
Definition: ShowImageController.php:90
‪TYPO3\CMS\Core\Resource\AbstractFile\getStorage
‪int< 0, getSize():int { if( $this->deleted) { throw new \RuntimeException( 'File has been deleted.', 1329821480);} if(empty( $this->properties[ 'size'])) { $fileInfo=$this-> getStorage() -> getFileInfoByIdentifier($this->getIdentifier(), ['size'])
‪TYPO3\CMS\Core\Resource\Folder
Definition: Folder.php:38
‪TYPO3\CMS\Core\Resource\ResourceFactory
Definition: ResourceFactory.php:42
‪TYPO3\CMS\Core\Resource\File
Definition: File.php:26
‪TYPO3\CMS\Core\Configuration\Features
Definition: Features.php:56
‪TYPO3\CMS\Frontend\Controller\ShowImageController\main
‪main()
Definition: ShowImageController.php:164
‪TYPO3\CMS\Frontend\Controller\ShowImageController\$height
‪int $height
Definition: ShowImageController.php:70
‪TYPO3\CMS\Core\Resource\ProcessedFile
Definition: ProcessedFile.php:47
‪TYPO3\CMS\Core\Utility\MathUtility
Definition: MathUtility.php:24
‪TYPO3\CMS\Frontend\Controller\ShowImageController\$width
‪string $width
Definition: ShowImageController.php:66
‪TYPO3\CMS\Frontend\Controller
Definition: ErrorController.php:18
‪TYPO3\CMS\Core\Utility\MathUtility\forceIntegerInRange
‪static int forceIntegerInRange(mixed $theInt, int $min, int $max=2000000000, int $defaultValue=0)
Definition: MathUtility.php:34
‪TYPO3\CMS\Core\Utility\GeneralUtility
Definition: GeneralUtility.php:52
‪TYPO3\CMS\Core\Crypto\HashService
Definition: HashService.php:27
‪TYPO3\CMS\Core\Resource\AbstractFile\getIdentifier
‪getIdentifier()
Definition: AbstractFile.php:144
‪TYPO3\CMS\Frontend\Controller\ShowImageController
Definition: ShowImageController.php:53