Use file extensions other than `.bin` for safe types
authorAlexander Ebert <ebert@woltlab.com>
Mon, 15 Apr 2024 10:46:33 +0000 (12:46 +0200)
committerAlexander Ebert <ebert@woltlab.com>
Sat, 8 Jun 2024 10:19:38 +0000 (12:19 +0200)
wcfsetup/install/files/lib/data/file/File.class.php
wcfsetup/install/files/lib/data/file/FileEditor.class.php
wcfsetup/setup/db/install.sql

index 79b9b820130cff2d77a758b539fbb59cb075faa2..bd27d90bbe3dc1ae592ece756f4eef9d5a009dd9 100644 (file)
@@ -20,6 +20,7 @@ use wcf\util\StringUtil;
  * @property-read string $filename
  * @property-read int $fileSize
  * @property-read string $fileHash
+ * @property-read string $fileExtension
  * @property-read string $typeName
  * @property-read string $mimeType
  * @property-read int|null $width
@@ -27,15 +28,43 @@ use wcf\util\StringUtil;
  */
 class File extends DatabaseObject
 {
+    /**
+     * List of common file extensions that are always safe to be served directly
+     * by the webserver.
+     *
+     * @var array<string, string>
+     */
+    public const SAFE_FILE_EXTENSIONS = [
+        'avif' => 'image/avif',
+        'bz2' => 'application/x-bzip2',
+        'gif' => 'image/gif',
+        'gz' => 'application/gzip',
+        'jpg' => 'image/jpeg',
+        'jpeg' => 'image/jpeg',
+        'mp3' => 'audio/mpeg',
+        'mp4' => 'video/mp4',
+        'pdf' => 'application/pdf',
+        'png' => 'image/png',
+        'rar' => 'application/vnd.rar',
+        'svg' => 'image/svg+xml',
+        'tar' => 'application/x-tar',
+        'tiff' => 'image/tiff',
+        'txt' => 'text/plain',
+        'webm' => 'video/webm',
+        'webp' => 'image/webp',
+        'zip' => 'application/zip',
+    ];
+
     /** @var array<string, FileThumbnail> */
     private array $thumbnails = [];
 
     public function getSourceFilename(): string
     {
         return \sprintf(
-            '%d-%s.bin',
+            '%d-%s.%s',
             $this->fileID,
             $this->fileHash,
+            $this->fileExtension,
         );
     }
 
@@ -110,7 +139,7 @@ class File extends DatabaseObject
             ];
         }
 
-        // TODO: Icon and preview url is missing.
+        // TODO: Icon and preview url are missing.
         return \sprintf(
             <<<'EOT'
                 <woltlab-core-file
@@ -126,4 +155,30 @@ class File extends DatabaseObject
             StringUtil::encodeHTML(\json_encode($thumbnails)),
         );
     }
+
+    /**
+     * Returns the file extension that is always safe for the delivery by the
+     * webserver. If the file extension cannot be detected or is not among the
+     * list of allowed file extension then 'bin' is returned.
+     */
+    public static function getSafeFileExtension(string $mimeType, string $filename): string
+    {
+        $fileExtension = \array_search($mimeType, self::SAFE_FILE_EXTENSIONS, true);
+        if (\is_string($fileExtension)) {
+            return $fileExtension;
+        }
+
+        if (\str_contains($filename, '.')) {
+            $fileExtension = \mb_substr(
+                $filename,
+                \mb_strrpos($filename, '.') + 1
+            );
+
+            if (isset(self::SAFE_FILE_EXTENSIONS[$fileExtension])) {
+                return $fileExtension;
+            }
+        }
+
+        return 'bin';
+    }
 }
index 73d5e2f86f298b4ad897023c97087911f3abc07e..8696efdc2e1d497c8660b3b99de66ac56283397d 100644 (file)
@@ -44,6 +44,7 @@ class FileEditor extends DatabaseObjectEditor
             'filename' => $fileTemporary->filename,
             'fileSize' => $fileTemporary->fileSize,
             'fileHash' => $fileTemporary->fileHash,
+            'fileExtension' => File::getSafeFileExtension($mimeType, $fileTemporary->filename),
             'typeName' => $fileTemporary->typeName,
             'mimeType' => $mimeType,
             'width' => $width,
index f3fbba6b9f9723656150f6b76f5ca2215b53d3e6..c76e56a9ba3bcb83e389b6eed2bc3196254fe7b3 100644 (file)
@@ -604,6 +604,7 @@ CREATE TABLE wcf1_file (
        filename VARCHAR(255) NOT NULL,
        fileSize BIGINT NOT NULL,
        fileHash CHAR(64) NOT NULL,
+       fileExtension VARCHAR(10) NOT NULL,
        typeName VARCHAR(255) NOT NULL,
        mimeType VARCHAR(255) NOT NULL,
        width INT,