1<?php 2 3declare(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 18namespace TYPO3\CMS\Core\Imaging; 19 20use TYPO3\CMS\Core\Exception; 21use TYPO3\CMS\Core\Type\File\FileInfo; 22use TYPO3\CMS\Core\Utility\CommandUtility; 23use TYPO3\CMS\Core\Utility\GeneralUtility; 24 25/** 26 * Value object for file to be used for ImageMagick/GraphicsMagick invocation when 27 * being used as input file (implies and requires that file exists for some evaluations). 28 */ 29class ImageMagickFile 30{ 31 /** 32 * Path to input file to be processed 33 * 34 * @var string 35 */ 36 protected $filePath; 37 38 /** 39 * Frame to be used (of multi-page document, e.g. PDF) 40 * 41 * @var int|null 42 */ 43 protected $frame; 44 45 /** 46 * Whether file actually exists 47 * 48 * @var bool 49 */ 50 protected $fileExists; 51 52 /** 53 * File extension as given in $filePath (e.g. 'file.png' -> 'png') 54 * 55 * @var string 56 */ 57 protected $fileExtension; 58 59 /** 60 * Resolved mime-type of file 61 * 62 * @var string 63 */ 64 protected $mimeType; 65 66 /** 67 * Resolved extension for mime-type (e.g. 'image/png' -> 'png') 68 * (might be empty if not defined in magic.mime database) 69 * 70 * @var string[] 71 * @see FileInfo::getMimeExtensions() 72 */ 73 protected $mimeExtensions = []; 74 75 /** 76 * Result to be used for ImageMagick/GraphicsMagick invocation containing 77 * combination of resolved format prefix, $filePath and frame escaped to be 78 * used as CLI argument (e.g. "'png:file.png'") 79 * 80 * @var string 81 */ 82 protected $asArgument; 83 84 /** 85 * File extensions that directly can be used (and are considered to be safe). 86 * 87 * @var string[] 88 */ 89 protected $allowedExtensions = ['png', 'jpg', 'jpeg', 'gif', 'webp', 'tif', 'tiff', 'bmp', 'pcx', 'tga', 'ico']; 90 91 /** 92 * File extensions that never shall be used. 93 * 94 * @var string[] 95 */ 96 protected $deniedExtensions = ['epi', 'eps', 'eps2', 'eps3', 'epsf', 'epsi', 'ept', 'ept2', 'ept3', 'msl', 'ps', 'ps2', 'ps3']; 97 98 /** 99 * File mime-types that have to be matching. Adding custom mime-types is possible using 100 * $GLOBALS['TYPO3_CONF_VARS']['SYS']['FileInfo']['fileExtensionToMimeType'] 101 * 102 * @var string[] 103 * @see FileInfo::getMimeExtensions() 104 */ 105 protected $mimeTypeExtensionMap = [ 106 'image/png' => 'png', 107 'image/jpeg' => 'jpg', 108 'image/gif' => 'gif', 109 'image/heic' => 'heic', 110 'image/heif' => 'heif', 111 'image/webp' => 'webp', 112 'image/svg' => 'svg', 113 'image/svg+xml' => 'svg', 114 'image/tiff' => 'tif', 115 'application/pdf' => 'pdf', 116 ]; 117 118 /** 119 * @param string $filePath 120 * @param int|null $frame 121 * @return ImageMagickFile 122 */ 123 public static function fromFilePath(string $filePath, int $frame = null): self 124 { 125 return GeneralUtility::makeInstance( 126 static::class, 127 $filePath, 128 $frame 129 ); 130 } 131 132 /** 133 * @param string $filePath 134 * @param int|null $frame 135 * @throws Exception 136 */ 137 public function __construct(string $filePath, int $frame = null) 138 { 139 $this->frame = $frame; 140 $this->fileExists = file_exists($filePath); 141 $this->filePath = $filePath; 142 $this->fileExtension = pathinfo($filePath, PATHINFO_EXTENSION); 143 144 if ($this->fileExists) { 145 $fileInfo = $this->getFileInfo($filePath); 146 $this->mimeType = $fileInfo->getMimeType(); 147 $this->mimeExtensions = $fileInfo->getMimeExtensions(); 148 } 149 150 $this->asArgument = $this->escape( 151 $this->resolvePrefix() . $this->filePath 152 . ($this->frame !== null ? '[' . $this->frame . ']' : '') 153 ); 154 } 155 156 /** 157 * @return string 158 */ 159 public function __toString(): string 160 { 161 return $this->asArgument; 162 } 163 164 /** 165 * Resolves according ImageMagic/GraphicsMagic format (e.g. 'png:', 'jpg:', ...). 166 * + in case mime-type could be resolved and is configured, it takes precedence 167 * + otherwise resolved mime-type extension of mime.magick database is used if available 168 * (includes custom settings with $GLOBALS['TYPO3_CONF_VARS']['SYS']['FileInfo']['fileExtensionToMimeType']) 169 * + otherwise "safe" and allowed file extension is used (jpg, png, gif, webp, tif, ...) 170 * + potentially malicious script formats (eps, ps, ...) are not allowed 171 * 172 * @return string 173 * @throws Exception 174 */ 175 protected function resolvePrefix(): string 176 { 177 $prefixExtension = null; 178 $fileExtension = strtolower($this->fileExtension); 179 if ($this->mimeType !== null && !empty($this->mimeTypeExtensionMap[$this->mimeType])) { 180 $prefixExtension = $this->mimeTypeExtensionMap[$this->mimeType]; 181 } elseif (!empty($this->mimeExtensions) && strpos((string)$this->mimeType, 'image/') === 0) { 182 $prefixExtension = $this->mimeExtensions[0]; 183 } elseif ($this->isInAllowedExtensions($fileExtension)) { 184 $prefixExtension = $fileExtension; 185 } 186 if ($prefixExtension !== null && !in_array(strtolower($prefixExtension), $this->deniedExtensions, true)) { 187 return $prefixExtension . ':'; 188 } 189 throw new Exception( 190 sprintf( 191 'Unsupported file %s (%s)', 192 basename($this->filePath), 193 $this->mimeType ?? 'unknown' 194 ), 195 1550060977 196 ); 197 } 198 199 /** 200 * @param string $value 201 * @return string 202 */ 203 protected function escape(string $value): string 204 { 205 return CommandUtility::escapeShellArgument($value); 206 } 207 208 /** 209 * @param string $extension 210 * @return bool 211 */ 212 protected function isInAllowedExtensions(string $extension): bool 213 { 214 return in_array($extension, $this->allowedExtensions, true); 215 } 216 217 /** 218 * @param string $filePath 219 * @return FileInfo 220 */ 221 protected function getFileInfo(string $filePath): FileInfo 222 { 223 return GeneralUtility::makeInstance(FileInfo::class, $filePath); 224 } 225} 226