Completed
Push — master ( a486fe...b5b395 )
by
unknown
14:37
created

ShowImageController::processRequest()   A

Complexity

Conditions 3
Paths 9

Size

Total Lines 15
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
eloc 11
nc 9
nop 1
dl 0
loc 15
rs 9.9
c 0
b 0
f 0
1
<?php
2
3
/*
4
 * This file is part of the TYPO3 CMS project.
5
 *
6
 * It is free software; you can redistribute it and/or modify it under
7
 * the terms of the GNU General Public License, either version 2
8
 * of the License, or any later version.
9
 *
10
 * For the full copyright and license information, please read the
11
 * LICENSE.txt file that was distributed with this source code.
12
 *
13
 * The TYPO3 project - inspiring people to share!
14
 */
15
16
namespace TYPO3\CMS\Frontend\Controller;
17
18
use Psr\Http\Message\ResponseInterface;
19
use Psr\Http\Message\ServerRequestInterface;
20
use TYPO3\CMS\Core\Exception;
21
use TYPO3\CMS\Core\Http\Response;
22
use TYPO3\CMS\Core\Resource\FileInterface;
23
use TYPO3\CMS\Core\Resource\ProcessedFile;
24
use TYPO3\CMS\Core\Resource\ResourceFactory;
25
use TYPO3\CMS\Core\Resource\Security\FileNameValidator;
26
use TYPO3\CMS\Core\Utility\GeneralUtility;
27
use TYPO3\CMS\Core\Utility\MathUtility;
28
29
/**
30
 * eID-Script "tx_cms_showpic"
31
 *
32
 * Shows a picture from FAL in enlarged format in a separate window.
33
 * Picture file and settings is supplied by GET-parameters:
34
 *
35
 *  - file = fileUid or Combined Identifier
36
 *  - encoded in a parameter Array (with weird format - see ContentObjectRenderer about ll. 1500)
37
 *  - width, height = usual width an height, m/c supported
38
 *  - frame
39
 *  - bodyTag
40
 *  - title
41
 *
42
 * @internal this is a concrete TYPO3 implementation and solely used for EXT:frontend and not part of TYPO3's Core API.
43
 */
44
class ShowImageController
45
{
46
    protected const ALLOWED_PARAMETER_NAMES = ['width', 'height', 'crop', 'bodyTag', 'title'];
47
48
    /**
49
     * @var \Psr\Http\Message\ServerRequestInterface
50
     */
51
    protected $request;
52
53
    /**
54
     * @var \TYPO3\CMS\Core\Resource\File
55
     */
56
    protected $file;
57
58
    /**
59
     * @var int
60
     */
61
    protected $width;
62
63
    /**
64
     * @var int
65
     */
66
    protected $height;
67
68
    /**
69
     * @var string
70
     */
71
    protected $crop;
72
73
    /**
74
     * @var int
75
     */
76
    protected $frame;
77
78
    /**
79
     * @var string
80
     */
81
    protected $bodyTag = '<body>';
82
83
    /**
84
     * @var string
85
     */
86
    protected $title = 'Image';
87
88
    /**
89
     * @var string
90
     */
91
    protected $content = <<<EOF
92
<!DOCTYPE html>
93
<html>
94
<head>
95
	<title>###TITLE###</title>
96
	<meta name="robots" content="noindex,follow" />
97
</head>
98
###BODY###
99
	###IMAGE###
100
</body>
101
</html>
102
EOF;
103
104
    /**
105
     * @var string
106
     */
107
    protected $imageTag = '<img src="###publicUrl###" alt="###alt###" title="###title###" width="###width###" height="###height###" />';
108
109
    /**
110
     * Init function, setting the input vars in the global space.
111
     *
112
     * @throws \InvalidArgumentException
113
     * @throws \TYPO3\CMS\Core\Resource\Exception\FileDoesNotExistException
114
     */
115
    public function initialize()
116
    {
117
        $fileUid = $this->request->getQueryParams()['file'] ?? null;
118
        $parametersArray = $this->request->getQueryParams()['parameters'] ?? null;
119
120
        // If no file-param or parameters are given, we must exit
121
        if (!$fileUid || !isset($parametersArray) || !is_array($parametersArray)) {
122
            throw new \InvalidArgumentException('No valid fileUid given', 1476048455);
123
        }
124
125
        // rebuild the parameter array and check if the HMAC is correct
126
        $parametersEncoded = implode('', $parametersArray);
127
128
        /* For backwards compatibility the HMAC is transported within the md5 param */
129
        $hmacParameter = $this->request->getQueryParams()['md5'] ?? null;
130
        $hmac = GeneralUtility::hmac(implode('|', [$fileUid, $parametersEncoded]));
131
        if (!is_string($hmacParameter) || !hash_equals($hmac, $hmacParameter)) {
132
            throw new \InvalidArgumentException('hash does not match', 1476048456);
133
        }
134
135
        // decode the parameters Array - `bodyTag` contains HTML if set and would lead
136
        // to a false-positive XSS-detection, that's why parameters are base64-encoded
137
        $parameters = json_decode(base64_decode($parametersEncoded), true);
138
        foreach ($parameters as $parameterName => $parameterValue) {
139
            if (in_array($parameterName, static::ALLOWED_PARAMETER_NAMES, true)) {
140
                $this->{$parameterName} = $parameterValue;
141
            }
142
        }
143
144
        if (MathUtility::canBeInterpretedAsInteger($fileUid)) {
145
            $this->file = GeneralUtility::makeInstance(ResourceFactory::class)->getFileObject((int)$fileUid);
146
        } else {
147
            $this->file = GeneralUtility::makeInstance(ResourceFactory::class)->retrieveFileOrFolderObject($fileUid);
148
        }
149
        if ($this->file !== null && !$this->isFileValid($this->file)) {
150
            throw new Exception('File processing for local storage is denied', 1594043425);
151
        }
152
153
        $this->frame = $this->request->getQueryParams()['frame'] ?? null;
154
    }
155
156
    /**
157
     * Main function which creates the image if needed and outputs the HTML code for the page displaying the image.
158
     * Accumulates the content in $this->content
159
     */
160
    public function main()
161
    {
162
        $processedImage = $this->processImage();
163
        $imageTagMarkers = [
164
            '###publicUrl###' => htmlspecialchars($processedImage->getPublicUrl()),
165
            '###alt###' => htmlspecialchars($this->file->getProperty('alternative') ?: $this->title),
166
            '###title###' => htmlspecialchars($this->file->getProperty('title') ?: $this->title),
167
            '###width###' => $processedImage->getProperty('width'),
168
            '###height###' => $processedImage->getProperty('height')
169
        ];
170
        $this->imageTag = str_replace(array_keys($imageTagMarkers), array_values($imageTagMarkers), $this->imageTag);
171
        $markerArray = [
172
            '###TITLE###' => $this->file->getProperty('title') ?: $this->title,
173
            '###IMAGE###' => $this->imageTag,
174
            '###BODY###' => $this->bodyTag
175
        ];
176
177
        $this->content = str_replace(array_keys($markerArray), array_values($markerArray), $this->content);
178
    }
179
180
    /**
181
     * Does the actual image processing
182
     *
183
     * @return \TYPO3\CMS\Core\Resource\ProcessedFile
184
     */
185
    protected function processImage()
186
    {
187
        $max = strpos($this->width . $this->height, 'm') !== false ? 'm' : '';
188
        $this->height = MathUtility::forceIntegerInRange($this->height, 0);
189
        $this->width = MathUtility::forceIntegerInRange($this->width, 0) . $max;
0 ignored issues
show
Documentation Bug introduced by
The property $width was declared of type integer, but TYPO3\CMS\Core\Utility\M...$this->width, 0) . $max is of type string. Maybe add a type cast?

This check looks for assignments to scalar types that may be of the wrong type.

To ensure the code behaves as expected, it may be a good idea to add an explicit type cast.

$answer = 42;

$correct = false;

$correct = (bool) $answer;
Loading history...
190
191
        $processingConfiguration = [
192
            'width' => $this->width,
193
            'height' => $this->height,
194
            'frame' => $this->frame,
195
            'crop' => $this->crop,
196
        ];
197
        return $this->file->process(ProcessedFile::CONTEXT_IMAGECROPSCALEMASK, $processingConfiguration);
198
    }
199
200
    /**
201
     * Fetches the content and builds a content file out of it
202
     *
203
     * @param ServerRequestInterface $request the current request object
204
     * @return ResponseInterface the modified response
205
     */
206
    public function processRequest(ServerRequestInterface $request): ResponseInterface
207
    {
208
        $this->request = $request;
209
210
        try {
211
            $this->initialize();
212
            $this->main();
213
            $response = new Response();
214
            $response->getBody()->write($this->content);
215
            return $response;
216
        } catch (\InvalidArgumentException $e) {
217
            // add a 410 "gone" if invalid parameters given
218
            return (new Response())->withStatus(410);
219
        } catch (Exception $e) {
220
            return (new Response())->withStatus(404);
221
        }
222
    }
223
224
    protected function isFileValid(FileInterface $file): bool
225
    {
226
        return $file->getStorage()->getDriverType() !== 'Local'
227
            || GeneralUtility::makeInstance(FileNameValidator::class)
228
                ->isValid(basename($file->getIdentifier()));
229
    }
230
}
231