Fix charset of the 'filename' field within the Content-disposition header of FileReader
authorTim Düsterhus <duesterhus@woltlab.com>
Fri, 8 May 2020 12:55:59 +0000 (14:55 +0200)
committerTim Düsterhus <duesterhus@woltlab.com>
Fri, 8 May 2020 13:09:02 +0000 (15:09 +0200)
The regular 'filename' field must not contain non-ASCII characters.
Specifically UTF-8 characters are reported to be improperly interpreted as
ISO-8859 in Safari.

Fix by following the guidelines outlined in RFC 5987.

wcfsetup/install/files/lib/util/FileReader.class.php

index f1212e199b11c9c0065c7655de1cd3728d14feed..72b436d362d0eb12896ff0e2a528bb9d5a319764 100644 (file)
@@ -151,8 +151,16 @@ class FileReader {
                        // file type
                        $this->addHeader('Content-Type', $this->options['mimeType']);
                        
+                       $filename = $this->sanitizeFilename($this->options['filename']);
+                       $asciiFilename = $this->sanitizeFilename($this->getAsciiFilename($filename));
+                       
                        // file name
-                       $this->addHeader('Content-disposition', ($this->options['showInline'] ? 'inline' : 'attachment').'; filename="'.$this->options['filename'].'"');
+                       $this->addHeader(
+                               'Content-disposition',
+                               ($this->options['showInline'] ? 'inline' : 'attachment').'; '.
+                                       'filename="'.rawurlencode($asciiFilename).'"; '.
+                                       "filename*=UTF-8''".rawurlencode($filename)
+                       );
                        
                        // range
                        if ($this->startByte > 0 || $this->endByte < $this->options['filesize'] - 1) {
@@ -179,6 +187,35 @@ class FileReader {
                }
        }
        
+       /**
+        * Returns an ASCII filename for the given filename.
+        * 
+        * @param       string  $filename
+        * @return      string
+        */
+       protected function getAsciiFilename($filename) {
+               // Attempt to use the intl extension if possible, this will result
+               // in more readable filenames for Umlauts, because they will be converted
+               // into the base character instead of an underscore.
+               if (function_exists('transliterator_transliterate')) {
+                       return transliterator_transliterate('Latin-ASCII', $filename);
+               }
+               else {
+                       return preg_replace('/[^\x20-\x7E]/', '_', $filename);
+               }
+       }
+       
+       /**
+        * Sanitizes the given filename, removing special characters that will
+        * cause issues on Windows.
+        * 
+        * @param       string  $filename
+        * @return      string
+        */
+       protected function sanitizeFilename($filename) {
+               return str_replace(['<', '>', ':', '"', '/', '\\', '|', '?', '*'], '_', $filename);
+       }
+       
        /**
         * Sends the headers of the file to the client.
         */