[BUGFIX] Valid Content-Type header for jpg thumbnails
[Packages/TYPO3.CMS.git] / typo3 / sysext / backend / Classes / View / ThumbnailView.php
1 <?php
2 namespace TYPO3\CMS\Backend\View;
3
4 /*
5 * This file is part of the TYPO3 CMS project.
6 *
7 * It is free software; you can redistribute it and/or modify it under
8 * the terms of the GNU General Public License, either version 2
9 * of the License, or any later version.
10 *
11 * For the full copyright and license information, please read the
12 * LICENSE.txt file that was distributed with this source code.
13 *
14 * The TYPO3 project - inspiring people to share!
15 */
16
17 use TYPO3\CMS\Core\Resource\File;
18 use TYPO3\CMS\Core\Resource\ResourceFactory;
19 use TYPO3\CMS\Core\Type\Bitmask\Permission;
20 use TYPO3\CMS\Core\Utility\CommandUtility;
21 use TYPO3\CMS\Core\Utility\GeneralUtility;
22 use TYPO3\CMS\Core\Utility\MathUtility;
23
24 /**
25 * Class for generating a thumbnail from the input parameters given to the script
26 *
27 * Input GET var, &file: relative or absolute reference to an imagefile. WILL be validated against PATH_site / lockRootPath
28 * Input GET var, &size: integer-values defining size of thumbnail, format '[int]' or '[int]x[int]'
29 *
30 * Relative paths MUST BE the first two characters ONLY: eg: '../dir/file.gif', otherwise it is expect to be absolute
31 *
32 * @author Kasper Skårhøj <kasperYYYY@typo3.com>
33 * @deprecated since TYPO3 CMS 7, will be removed with TYPO3 CMS 8, use the corresponding Resource objects and Processing functionality
34 */
35 class ThumbnailView {
36
37 /**
38 * The output directory of temporary files in PATH_site
39 *
40 * @var string
41 */
42 public $outdir = 'typo3temp/';
43
44 /**
45 * @var string
46 */
47 public $output = '';
48
49 /**
50 * @var string
51 */
52 public $sizeDefault = '64x64';
53
54 /**
55 * Coming from $GLOBALS['TYPO3_CONF_VARS']['GFX']['imagefile_ext']
56 *
57 * @var string
58 */
59 public $imageList;
60
61 /**
62 * will hold the file Object
63 *
64 * @var \TYPO3\CMS\Core\Resource\File
65 */
66 public $image;
67
68 /**
69 * Holds the input size (GET: size)
70 *
71 * @var string
72 */
73 public $size;
74
75 /**
76 * Last modification time of the supplied file
77 *
78 * @var int
79 */
80 public $mTime = 0;
81
82 /**
83 * Initialize; reading parameters with GPvar and checking file path
84 *
85 * @return void
86 * @throws \InvalidArgumentException
87 * @throws \RuntimeException
88 * @throws \TYPO3\CMS\Core\Resource\Exception\FileDoesNotExistException
89 */
90 public function init() {
91 GeneralUtility::deprecationLog('The class ThumbnailView is deprecated since TYPO3 CMS 7 and will be removed with TYPO3 CMS 8, use the corresponding Resource objects and Processing functionality');
92 // Setting GPvars:
93 // Only needed for MD5 sum calculation of backwards-compatibility uploads/ files thumbnails.
94 $size = GeneralUtility::_GP('size');
95 $filePathOrCombinedFileIdentifier = rawurldecode(GeneralUtility::_GP('file'));
96 $md5sum = GeneralUtility::_GP('md5sum');
97 // Image extension list is set:
98 // valid extensions. OBS: No spaces in the list, all lowercase...
99 $this->imageList = $GLOBALS['TYPO3_CONF_VARS']['GFX']['imagefile_ext'];
100 // Check if we got a combined file identifier of the form storageUid:fileIdentifer.
101 // We need to distinguish it from absolute Windows paths by cbecking for an integer as first part.
102 $parts = GeneralUtility::trimExplode(':', $filePathOrCombinedFileIdentifier);
103 // Best case: we get a sys_file UID
104 if (!empty($filePathOrCombinedFileIdentifier) && MathUtility::canBeInterpretedAsInteger($filePathOrCombinedFileIdentifier)) {
105 /** @var File $filePathOrCombinedFileIdentifier */
106 $fileObject = ResourceFactory::getInstance()->getFileObject($filePathOrCombinedFileIdentifier);
107 } elseif (count($parts) <= 1 || !MathUtility::canBeInterpretedAsInteger($parts[0])) {
108 // @todo Historically, the input parameter could also be an absolute path. This should be supported again to stay compatible.
109 // We assume the FilePath to be a relative file path (as in backwards compatibility mode)
110 $relativeFilePath = $filePathOrCombinedFileIdentifier;
111 // The incoming relative path is relative to the typo3/ directory, but we need it relative to PATH_site. This is corrected here:
112 if (substr($relativeFilePath, 0, 3) == '../') {
113 $relativeFilePath = substr($relativeFilePath, 3);
114 } else {
115 $relativeFilePath = 'typo3/' . $relativeFilePath;
116 }
117 $relativeFilePath = ltrim($relativeFilePath, '/');
118 $mTime = 0;
119 // Checking for backpath and double slashes + the thumbnail can be made from files which are in the PATH_site OR the lockRootPath only!
120 if (GeneralUtility::isAllowedAbsPath(PATH_site . $relativeFilePath)) {
121 $mTime = filemtime(PATH_site . $relativeFilePath);
122 }
123 if (strstr($relativeFilePath, '../') !== FALSE) {
124 // Maybe this could be relaxed to not throw an error as long as the path is still within PATH_site
125 $this->errorGif('File path', 'must not contain', '"../"');
126 }
127 if ($relativeFilePath && file_exists(PATH_site . $relativeFilePath)) {
128 // Check file extension:
129 $reg = array();
130 if (preg_match('/(.*)\\.([^\\.]*$)/', $relativeFilePath, $reg)) {
131 $ext = strtolower($reg[2]);
132 $ext = $ext == 'jpeg' ? 'jpg' : $ext;
133 if (!GeneralUtility::inList($this->imageList, $ext)) {
134 $this->errorGif('Not imagefile!', $ext, basename($relativeFilePath));
135 }
136 } else {
137 $this->errorGif('Not imagefile!', 'No ext!', basename($relativeFilePath));
138 }
139 } else {
140 $this->errorGif('Input file not found.', 'not found in thumbs.php', basename($relativeFilePath));
141 }
142 // Do an MD5 check to prevent viewing of images without permission
143 $OK = FALSE;
144 if ($mTime) {
145 // Always use the absolute path for this check!
146 $check = basename($relativeFilePath) . ':' . $mTime . ':' . $GLOBALS['TYPO3_CONF_VARS']['SYS']['encryptionKey'];
147 if (GeneralUtility::shortMD5($check) === (string)$md5sum) {
148 $OK = TRUE;
149 }
150 }
151 $combinedIdentifier = '0:' . $relativeFilePath;
152 } else {
153 $combinedIdentifier = $filePathOrCombinedFileIdentifier;
154 $OK = FALSE;
155 }
156 if (empty($fileObject)) {
157 $fileObject = ResourceFactory::getInstance()->getFileObjectFromCombinedIdentifier($combinedIdentifier);
158 }
159 if (empty($OK)) {
160 $OK = $fileObject !== NULL && $fileObject->checkActionPermission('read') && $fileObject->calculateChecksum() == $md5sum;
161 }
162 if ($OK) {
163 $this->image = $fileObject;
164 $this->size = $size;
165 } else {
166 // Hide the path to the document root;
167 throw new \RuntimeException('TYPO3 Fatal Error: The requested image does not exist and/or MD5 checksum did not match. If the target file exists and its file name contains special characters, the setting of $TYPO3_CONF_VARS[SYS][systemLocale] might be wrong.', 1270853950);
168 }
169 }
170
171 /**
172 * Create the thumbnail
173 * Will exit before return if all is well.
174 *
175 * @return void
176 */
177 public function main() {
178 // Clean output buffer to ensure no extraneous output exists
179 ob_clean();
180 // If file exists, we make a thumbnail of the file.
181 if (is_object($this->image)) {
182 // Check file extension:
183 if ($this->image->getExtension() == 'ttf') {
184 // Make font preview... (will not return)
185 $this->fontGif($this->image);
186 } elseif ($this->image->getType() != File::FILETYPE_IMAGE && !GeneralUtility::inList($GLOBALS['TYPO3_CONF_VARS']['GFX']['imagefile_ext'], $this->image->getExtension())) {
187 $this->errorGif('Not imagefile!', 'No ext!', $this->image->getName());
188 }
189 // ... so we passed the extension test meaning that we are going to make a thumbnail here:
190 // default
191 if (!$this->size) {
192 $this->size = $this->sizeDefault;
193 }
194 // I added extra check, so that the size input option could not be fooled to pass other values.
195 // That means the value is exploded, evaluated to an integer and the imploded to [value]x[value].
196 // Furthermore you can specify: size=340 and it'll be translated to 340x340.
197 // explodes the input size (and if no "x" is found this will add size again so it is the same for both dimensions)
198 $sizeParts = explode('x', $this->size . 'x' . $this->size);
199 // Cleaning it up, only two parameters now.
200 $sizeParts = array(MathUtility::forceIntegerInRange($sizeParts[0], 1, 1000), MathUtility::forceIntegerInRange($sizeParts[1], 1, 1000));
201 // Imploding the cleaned size-value back to the internal variable
202 $this->size = implode('x', $sizeParts);
203 // Getting max value
204 $sizeMax = max($sizeParts);
205 // Init
206 $outpath = PATH_site . $this->outdir;
207 // Should be - ? 'png' : 'gif' - , but doesn't work (ImageMagick prob.?)
208 // René: png work for me
209 $thmMode = MathUtility::forceIntegerInRange($GLOBALS['TYPO3_CONF_VARS']['GFX']['thumbnails_png'], 0);
210 $outext = $this->image->getExtension() != 'jpg' || $thmMode & Permission::PAGE_EDIT ? ($thmMode & 1 ? 'png' : 'gif') : 'jpg';
211 $outfile = 'tmb_' . substr(md5(($this->image->getName() . $this->mtime . $this->size)), 0, 10) . '.' . $outext;
212 $this->output = $outpath . $outfile;
213 if ($GLOBALS['TYPO3_CONF_VARS']['GFX']['im']) {
214 // If thumbnail does not exist, we generate it
215 if (!file_exists($this->output)) {
216 $parameters = '-sample ' . $this->size . ' ' . CommandUtility::escapeShellArgument($this->image->getForLocalProcessing(FALSE)) . '[0] ' . CommandUtility::escapeShellArgument($this->output);
217 $cmd = GeneralUtility::imageMagickCommand('convert', $parameters);
218 \TYPO3\CMS\Core\Utility\CommandUtility::exec($cmd);
219 if (!file_exists($this->output)) {
220 $this->errorGif('No thumb', 'generated!', $this->image->getName());
221 } else {
222 GeneralUtility::fixPermissions($this->output);
223 }
224 }
225 // The thumbnail is read and output to the browser
226 if ($fd = @fopen($this->output, 'rb')) {
227 $fileModificationTime = filemtime($this->output);
228 header('Content-Type: image/' . ($outext === 'jpg' ? 'jpeg' : $outext));
229 header('Last-Modified: ' . date('r', $fileModificationTime));
230 header('ETag: ' . md5($this->output) . '-' . $fileModificationTime);
231 // Expiration time is chosen arbitrary to 1 month
232 header('Expires: ' . date('r', ($fileModificationTime + 30 * 24 * 60 * 60)));
233 fpassthru($fd);
234 fclose($fd);
235 } else {
236 $this->errorGif('Read problem!', '', $this->output);
237 }
238 } else {
239 die;
240 }
241 } else {
242 $this->errorGif('No valid', 'inputfile!', basename($this->image));
243 }
244 }
245
246 /***************************
247 *
248 * OTHER FUNCTIONS:
249 *
250 ***************************/
251 /**
252 * Creates error image based on gfx/notfound_thumb.png
253 * Requires GD lib enabled, otherwise it will exit with the three textstrings outputted as text.
254 * Outputs the image stream to browser and exits!
255 *
256 * @param string $l1 Text line 1
257 * @param string $l2 Text line 2
258 * @param string $l3 Text line 3
259 * @return void
260 */
261 public function errorGif($l1, $l2, $l3) {
262 if (!$GLOBALS['TYPO3_CONF_VARS']['GFX']['gdlib']) {
263 throw new \RuntimeException('TYPO3 Fatal Error: No gdlib. ' . $l1 . ' ' . $l2 . ' ' . $l3, 1270853954);
264 }
265 // Creates the basis for the error image
266 if ($GLOBALS['TYPO3_CONF_VARS']['GFX']['gdlib_png']) {
267 header('Content-type: image/png');
268 $im = imagecreatefrompng(PATH_typo3 . 'gfx/notfound_thumb.png');
269 } else {
270 header('Content-type: image/gif');
271 $im = imagecreatefromgif(PATH_typo3 . 'gfx/notfound_thumb.gif');
272 }
273 // Sets background color and print color.
274 $white = imageColorAllocate($im, 255, 255, 255);
275 $black = imageColorAllocate($im, 0, 0, 0);
276 // Prints the text strings with the build-in font functions of GD
277 $x = 0;
278 $font = 0;
279 if ($l1) {
280 imagefilledrectangle($im, $x, 9, 56, 16, $white);
281 imageString($im, $font, $x, 9, $l1, $black);
282 }
283 if ($l2) {
284 imagefilledrectangle($im, $x, 19, 56, 26, $white);
285 imageString($im, $font, $x, 19, $l2, $black);
286 }
287 if ($l3) {
288 imagefilledrectangle($im, $x, 29, 56, 36, $white);
289 imageString($im, $font, $x, 29, substr($l3, -14), $black);
290 }
291 // Outputting the image stream and exit
292 if ($GLOBALS['TYPO3_CONF_VARS']['GFX']['gdlib_png']) {
293 imagePng($im);
294 } else {
295 imageGif($im);
296 }
297 imagedestroy($im);
298 die;
299 }
300
301 /**
302 * Creates a font-preview thumbnail.
303 * This means a PNG/GIF file with the text "AaBbCc...." set with the font-file given as input and in various sizes to show how the font looks
304 * Requires GD lib enabled.
305 * Outputs the image stream to browser and exits!
306 *
307 * @param string $font The filepath to the font file (absolute, probably)
308 * @return void
309 */
310 public function fontGif($font) {
311 if (!$GLOBALS['TYPO3_CONF_VARS']['GFX']['gdlib']) {
312 throw new \RuntimeException('TYPO3 Fatal Error: No gdlib.', 1270853953);
313 }
314 // Create image and set background color to white.
315 $im = imageCreate(250, 76);
316 $white = imageColorAllocate($im, 255, 255, 255);
317 $col = imageColorAllocate($im, 0, 0, 0);
318 // The test string and offset in x-axis.
319 $string = 'AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZzÆæØøÅåÄäÖöÜüß';
320 $x = 13;
321 // Print (with non-ttf font) the size displayed
322 imagestring($im, 1, 0, 2, '10', $col);
323 imagestring($im, 1, 0, 15, '12', $col);
324 imagestring($im, 1, 0, 30, '14', $col);
325 imagestring($im, 1, 0, 47, '18', $col);
326 imagestring($im, 1, 0, 68, '24', $col);
327 // Print with ttf-font the test string
328 imagettftext($im, GeneralUtility::freetypeDpiComp(10), 0, $x, 8, $col, $font, $string);
329 imagettftext($im, GeneralUtility::freetypeDpiComp(12), 0, $x, 21, $col, $font, $string);
330 imagettftext($im, GeneralUtility::freetypeDpiComp(14), 0, $x, 36, $col, $font, $string);
331 imagettftext($im, GeneralUtility::freetypeDpiComp(18), 0, $x, 53, $col, $font, $string);
332 imagettftext($im, GeneralUtility::freetypeDpiComp(24), 0, $x, 74, $col, $font, $string);
333 // Output PNG or GIF based on $GLOBALS['TYPO3_CONF_VARS']['GFX']['gdlib_png']
334 if ($GLOBALS['TYPO3_CONF_VARS']['GFX']['gdlib_png']) {
335 header('Content-type: image/png');
336 imagePng($im);
337 } else {
338 header('Content-type: image/gif');
339 imageGif($im);
340 }
341 imagedestroy($im);
342 die;
343 }
344
345 }