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