]> git.mxchange.org Git - quix0rs-gnu-social.git/blobdiff - lib/themeuploader.php
Merge branch 'master' of gitorious.org:social/mainline
[quix0rs-gnu-social.git] / lib / themeuploader.php
index 18ef8c4d1abe81a323854174feff94c680dbe636..a68231d844f8e60500217b1f84d9d7b609d4b529 100644 (file)
@@ -34,7 +34,7 @@ if (!defined('STATUSNET') && !defined('LACONICA')) {
 /**
  * Encapsulation of the validation-and-save process when dealing with
  * a user-uploaded StatusNet theme archive...
- * 
+ *
  * @todo extract theme metadata from css/display.css
  * @todo allow saving multiple themes
  */
@@ -47,7 +47,8 @@ class ThemeUploader
     public function __construct($filename)
     {
         if (!class_exists('ZipArchive')) {
-            throw new Exception(_("This server cannot handle theme uploads without ZIP support."));
+            // TRANS: Exception thrown when a compressed theme is uploaded while no support present in PHP configuration.
+            throw new Exception(_('This server cannot handle theme uploads without ZIP support.'));
         }
         $this->sourceFile = $filename;
     }
@@ -55,10 +56,12 @@ class ThemeUploader
     public static function fromUpload($name)
     {
         if (!isset($_FILES[$name]['error'])) {
-            throw new ServerException(_("Theme upload missing or failed."));
+            // TRANS: Server exception thrown when uploading a theme fails.
+            throw new ServerException(_('The theme file is missing or the upload failed.'));
         }
         if ($_FILES[$name]['error'] != UPLOAD_ERR_OK) {
-            throw new ServerException(_("Theme upload missing or failed."));
+            // TRANS: Server exception thrown when uploading a theme fails.
+            throw new ServerException(_('The theme file is missing or the upload failed.'));
         }
         return new ThemeUploader($_FILES[$name]['tmp_name']);
     }
@@ -88,7 +91,8 @@ class ThemeUploader
             $this->loud();
             if (!$ok) {
                 common_log(LOG_ERR, "Could not move old custom theme from $destDir to $killDir");
-                throw new ServerException(_("Failed saving theme."));
+                // TRANS: Server exception thrown when saving an uploaded theme after decompressing it fails.
+                throw new ServerException(_('Failed saving theme.'));
             }
         } else {
             $killDir = false;
@@ -99,7 +103,8 @@ class ThemeUploader
         $this->loud();
         if (!$ok) {
             common_log(LOG_ERR, "Could not move saved theme from $tmpDir to $destDir");
-            throw new ServerException(_("Failed saving theme."));
+            // TRANS: Server exception thrown when saving an uploaded theme after decompressing it fails.
+            throw new ServerException(_('Failed saving theme.'));
         }
 
         if ($killDir) {
@@ -108,7 +113,7 @@ class ThemeUploader
     }
 
     /**
-     * 
+     *
      */
     protected function traverseArchive($zip, $outdir=false)
     {
@@ -128,15 +133,24 @@ class ThemeUploader
                 continue;
             }
 
-            // Check the directory structure...
+            // Is this a safe or skippable file?
             $path = pathinfo($name);
+            if ($this->skippable($path['filename'], $path['extension'])) {
+                // Documentation and such... booooring
+                continue;
+            } else {
+                $this->validateFile($path['filename'], $path['extension']);
+            }
+
+            // Check the directory structure...
             $dirs = explode('/', $path['dirname']);
             $baseDir = array_shift($dirs);
             if ($commonBaseDir === false) {
                 $commonBaseDir = $baseDir;
             } else {
                 if ($commonBaseDir != $baseDir) {
-                    throw new ClientException(_("Invalid theme: bad directory structure."));
+                    // TRANS: Server exception thrown when an uploaded theme has an incorrect structure.
+                    throw new ClientException(_('Invalid theme: Bad directory structure.'));
                 }
             }
 
@@ -144,28 +158,23 @@ class ThemeUploader
                 $this->validateFileOrFolder($dir);
             }
 
-            // Is this a safe or skippable file?
-            if ($this->skippable($path['filename'], $path['extension'])) {
-                // Documentation and such... booooring
-                continue;
-            } else {
-                $this->validateFile($path['filename'], $path['extension']);
-            }
-
             $fullPath = $dirs;
             $fullPath[] = $path['basename'];
             $localFile = implode('/', $fullPath);
             if ($localFile == 'css/display.css') {
                 $hasMain = true;
             }
-            
+
             $size = $data['size'];
             $estSize = $blockSize * max(1, intval(ceil($size / $blockSize)));
             $totalSize += $estSize;
             if ($totalSize > $sizeLimit) {
-                $msg = sprintf(_("Uploaded theme is too large; " .
-                                 "must be less than %d bytes uncompressed."),
-                                 $sizeLimit);
+                // TRANS: Client exception thrown when an uploaded theme is larger than the limit.
+                // TRANS: %d is the number of bytes of the uncompressed theme.
+                $msg = sprintf(_m('Uploaded theme is too large; must be less than %d byte uncompressed.',
+                                  'Uploaded theme is too large; must be less than %d bytes uncompressed.',
+                                  $sizeLimit),
+                               $sizeLimit);
                 throw new ClientException($msg);
             }
 
@@ -175,46 +184,75 @@ class ThemeUploader
         }
 
         if (!$hasMain) {
-            throw new ClientException(_("Invalid theme archive: " .
-                                        "missing file css/display.css"));
+            // TRANS: Server exception thrown when an uploaded theme is incomplete.
+            throw new ClientException(_('Invalid theme archive: ' .
+                                        "Missing file css/display.css"));
         }
     }
 
+    /**
+     * @fixme Probably most unrecognized files should just be skipped...
+     */
     protected function skippable($filename, $ext)
     {
-        $skip = array('txt', 'rtf', 'doc', 'docx', 'odt');
+        $skip = array('txt', 'html', 'rtf', 'doc', 'docx', 'odt', 'xcf');
         if (strtolower($filename) == 'readme') {
             return true;
         }
         if (in_array(strtolower($ext), $skip)) {
             return true;
         }
+        if ($filename == '' || substr($filename, 0, 1) == '.') {
+            // Skip Unix-style hidden files
+            return true;
+        }
+        if ($filename == '__MACOSX') {
+            // Skip awful metadata files Mac OS X slips in for you.
+            // Thanks Apple!
+            return true;
+        }
         return false;
     }
 
     protected function validateFile($filename, $ext)
     {
         $this->validateFileOrFolder($filename);
-        $this->validateExtension($ext);
+        $this->validateExtension($filename, $ext);
         // @fixme validate content
     }
 
     protected function validateFileOrFolder($name)
     {
-        if (!preg_match('/^[a-z0-9_-]+$/i', $name)) {
+        if (!preg_match('/^[a-z0-9_\.-]+$/i', $name)) {
+            common_log(LOG_ERR, "Bad theme filename: $name");
+            // TRANS: Server exception thrown when an uploaded theme has an incorrect file or folder name.
             $msg = _("Theme contains invalid file or folder name. " .
-                     "Stick with ASCII letters, digits, underscore, and minus sign.");
+                     'Stick with ASCII letters, digits, underscore, and minus sign.');
+            throw new ClientException($msg);
+        }
+        if (preg_match('/\.(php|cgi|asp|aspx|js|vb)\w/i', $name)) {
+            common_log(LOG_ERR, "Unsafe theme filename: $name");
+            // TRANS: Server exception thrown when an uploaded theme contains files with unsafe file extensions.
+            $msg = _('Theme contains unsafe file extension names; may be unsafe.');
             throw new ClientException($msg);
         }
         return true;
     }
 
-    protected function validateExtension($ext)
+    protected function validateExtension($base, $ext)
     {
-        $allowed = array('css', 'png', 'gif', 'jpg', 'jpeg');
+        $allowed = array('css', // CSS may need validation
+                         'png', 'gif', 'jpg', 'jpeg',
+                         'svg', // SVG images/fonts may need validation
+                         'ttf', 'eot', 'woff');
         if (!in_array(strtolower($ext), $allowed)) {
-            $msg = sprintf(_("Theme contains file of type '.%s', " .
-                             "which is not allowed."),
+            if ($ext == 'ini' && $base == 'theme') {
+                // theme.ini exception
+                return true;
+            }
+            // TRANS: Server exception thrown when an uploaded theme contains a file type that is not allowed.
+            // TRANS: %s is the file type that is not allowed.
+            $msg = sprintf(_('Theme contains file of type ".%s", which is not allowed.'),
                            $ext);
             throw new ClientException($msg);
         }
@@ -227,11 +265,12 @@ class ThemeUploader
     protected function openArchive()
     {
         $zip = new ZipArchive;
-        $ok = $zip->open($this->sourceFile); 
+        $ok = $zip->open($this->sourceFile);
         if ($ok !== true) {
             common_log(LOG_ERR, "Error opening theme zip archive: " .
                                 "{$this->sourceFile} code: {$ok}");
-            throw new Exception(_("Error opening theme archive."));
+            // TRANS: Server exception thrown when an uploaded compressed theme cannot be opened.
+            throw new Exception(_('Error opening theme archive.'));
         }
         return $zip;
     }
@@ -250,11 +289,13 @@ class ThemeUploader
             $this->loud();
             if (!$ok) {
                 common_log(LOG_ERR, "Failed to mkdir $dir while uploading theme");
-                throw new ServerException(_("Failed saving theme."));
+                // TRANS: Server exception thrown when an uploaded theme cannot be saved during extraction.
+                throw new ServerException(_('Failed saving theme.'));
             }
         } else if (!is_dir($dir)) {
             common_log(LOG_ERR, "Output directory $dir not a directory while uploading theme");
-            throw new ServerException(_("Failed saving theme."));
+            // TRANS: Server exception thrown when an uploaded theme cannot be saved during extraction.
+            throw new ServerException(_('Failed saving theme.'));
         }
 
         // ZipArchive::extractTo would be easier, but won't let us alter
@@ -262,14 +303,16 @@ class ThemeUploader
         $in = $zip->getStream($from);
         if (!$in) {
             common_log(LOG_ERR, "Couldn't open archived file $from while uploading theme");
-            throw new ServerException(_("Failed saving theme."));
+            // TRANS: Server exception thrown when an uploaded theme cannot be saved during extraction.
+            throw new ServerException(_('Failed saving theme.'));
         }
         $this->quiet();
         $out = fopen($to, "wb");
         $this->loud();
         if (!$out) {
             common_log(LOG_ERR, "Couldn't open output file $to while uploading theme");
-            throw new ServerException(_("Failed saving theme."));
+            // TRANS: Server exception thrown when an uploaded theme cannot be saved during extraction.
+            throw new ServerException(_('Failed saving theme.'));
         }
         while (!feof($in)) {
             $buffer = fread($in, 65536);
@@ -307,5 +350,4 @@ class ThemeUploader
         $list->close();
         rmdir($dir);
     }
-
 }