123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354 |
- <?php
- if (!defined('STATUSNET') && !defined('LACONICA')) {
- exit(1);
- }
- class ThemeUploader
- {
- protected $sourceFile;
- protected $isUpload;
- private $prevErrorReporting;
- public function __construct($filename)
- {
- if (!class_exists('ZipArchive')) {
-
- throw new Exception(_('This server cannot handle theme uploads without ZIP support.'));
- }
- $this->sourceFile = $filename;
- }
- public static function fromUpload($name)
- {
- if (!isset($_FILES[$name]['error'])) {
-
- throw new ServerException(_('The theme file is missing or the upload failed.'));
- }
- if ($_FILES[$name]['error'] != UPLOAD_ERR_OK) {
-
- throw new ServerException(_('The theme file is missing or the upload failed.'));
- }
- return new ThemeUploader($_FILES[$name]['tmp_name']);
- }
-
- public function extract($destDir)
- {
- $zip = $this->openArchive();
-
-
- $this->traverseArchive($zip);
-
- $tmpDir = $destDir . '.tmp' . getmypid();
- $this->traverseArchive($zip, $tmpDir);
- $zip->close();
- if (file_exists($destDir)) {
- $killDir = $tmpDir . '.old';
- $this->quiet();
- $ok = rename($destDir, $killDir);
- $this->loud();
- if (!$ok) {
- common_log(LOG_ERR, "Could not move old custom theme from $destDir to $killDir");
-
- throw new ServerException(_('Failed saving theme.'));
- }
- } else {
- $killDir = false;
- }
- $this->quiet();
- $ok = rename($tmpDir, $destDir);
- $this->loud();
- if (!$ok) {
- common_log(LOG_ERR, "Could not move saved theme from $tmpDir to $destDir");
-
- throw new ServerException(_('Failed saving theme.'));
- }
- if ($killDir) {
- $this->recursiveRmdir($killDir);
- }
- }
-
- protected function traverseArchive($zip, $outdir=false)
- {
- $sizeLimit = 2 * 1024 * 1024;
- $blockSize = 4096;
- $totalSize = 0;
- $hasMain = false;
- $commonBaseDir = false;
- for ($i = 0; $i < $zip->numFiles; $i++) {
- $data = $zip->statIndex($i);
- $name = str_replace('\\', '/', $data['name']);
- if (substr($name, -1) == '/') {
-
- continue;
- }
-
- $path = pathinfo($name);
- if ($this->skippable($path['filename'], $path['extension'])) {
-
- continue;
- } else {
- $this->validateFile($path['filename'], $path['extension']);
- }
-
- $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.'));
- }
- }
- foreach ($dirs as $dir) {
- $this->validateFileOrFolder($dir);
- }
- $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(_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);
- }
- if ($outdir) {
- $this->extractFile($zip, $data['name'], "$outdir/$localFile");
- }
- }
- if (!$hasMain) {
-
- throw new ClientException(_('Invalid theme archive: ' .
- "Missing file css/display.css"));
- }
- }
-
- protected function skippable($filename, $ext)
- {
- $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) == '.') {
-
- return true;
- }
- if ($filename == '__MACOSX') {
-
-
- return true;
- }
- return false;
- }
- protected function validateFile($filename, $ext)
- {
- $this->validateFileOrFolder($filename);
- $this->validateExtension($filename, $ext);
-
- }
- protected function validateFileOrFolder($name)
- {
- if (!preg_match('/^[a-z0-9_\.-]+$/i', $name)) {
- common_log(LOG_ERR, "Bad theme filename: $name");
-
- $msg = _("Theme contains invalid file or folder name. " .
- '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");
-
- $msg = _('Theme contains unsafe file extension names; may be unsafe.');
- throw new ClientException($msg);
- }
- return true;
- }
- protected function validateExtension($base, $ext)
- {
- $allowed = array('css',
- 'png', 'gif', 'jpg', 'jpeg',
- 'svg',
- 'ttf', 'eot', 'woff');
- if (!in_array(strtolower($ext), $allowed)) {
- if ($ext == 'ini' && $base == 'theme') {
-
- return true;
- }
-
-
- $msg = sprintf(_('Theme contains file of type ".%s", which is not allowed.'),
- $ext);
- throw new ClientException($msg);
- }
- return true;
- }
-
- protected function openArchive()
- {
- $zip = new ZipArchive;
- $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.'));
- }
- return $zip;
- }
-
- protected function extractFile($zip, $from, $to)
- {
- $dir = dirname($to);
- if (!file_exists($dir)) {
- $this->quiet();
- $ok = mkdir($dir, 0755, true);
- $this->loud();
- if (!$ok) {
- common_log(LOG_ERR, "Failed to mkdir $dir while uploading theme");
-
- 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.'));
- }
-
-
- $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.'));
- }
- $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.'));
- }
- while (!feof($in)) {
- $buffer = fread($in, 65536);
- fwrite($out, $buffer);
- }
- fclose($in);
- fclose($out);
- }
- private function quiet()
- {
- $this->prevErrorReporting = error_reporting();
- error_reporting($this->prevErrorReporting & ~E_WARNING);
- }
- private function loud()
- {
- error_reporting($this->prevErrorReporting);
- }
- private function recursiveRmdir($dir)
- {
- $list = dir($dir);
- while (($file = $list->read()) !== false) {
- if ($file == '.' || $file == '..') {
- continue;
- }
- $full = "$dir/$file";
- if (is_dir($full)) {
- $this->recursiveRmdir($full);
- } else {
- unlink($full);
- }
- }
- $list->close();
- rmdir($dir);
- }
- }
|