CVE-2023-4652: Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting') (CWE-79)
value: Cross-site Scripting (XSS) - Stored in GitHub repository instantsoft/icms2 prior to 2.16.1-git.
CWE description: The product does not neutralize or incorrectly neutralizes user-controllable input before it is placed in output that is used as a web page that is served to other users.
CVSS v3 base score: 5.4
Published: 2023-08-31T01:15Z
Repository: https://github.com/instantsoft/icms2
Language: PHP
Files changed: system/controllers/admin/actions/install.php, system/core/uploader.php, system/core/uploadfile.php
Commit date: 2023-08-11 19:02:19 +0300
Commit message: Fixed upload XSS with wrong extension
Diff: diff --git a/system/controllers/admin/actions/install.php b/system/controllers/admin/actions/install.php
index 1ec52a95b..f49f5157b 100755
--- a/system/controllers/admin/actions/install.php
+++ b/system/controllers/admin/actions/install.php
@@ -283,17 +283,18 @@ class actionAdminInstall extends cmsAction {
 
         }
 
         files_clear_directory(cmsConfig::get('upload_path') . $this->installer_upload_path);
 
-        $result = $this->cms_uploader->upload($this->upload_name, $this->upload_exts, 0, $this->installer_upload_path);
+        $result = $this->cms_uploader->setAllowedMime([
+            'application/zip'
+        ])->upload($this->upload_name, $this->upload_exts, 0, $this->installer_upload_path);
 
         if (!$result['success']){
             cmsUser::addSessionMessage($result['error'], 'error');
             return false;
         }
 
         return $result['name'];
-
     }
 
 }
diff --git a/system/core/uploader.php b/system/core/uploader.php
index f9d1e0d05..bad651838 100755
--- a/system/core/uploader.php
+++ b/system/core/uploader.php
@@ -3,276 +3,320 @@
 class cmsUploader {
 
     private $allow_remote = false;
     private $file_name = '';
     private $user_id = 0;
-    private $site_cfg = array();
+    private $site_cfg = null;
 
-    private $last_error = false;
-    private $upload_errors = array();
+    private $last_error = null;
+    private $upload_errors = [];
 
     private $allowed_mime = false;
     private $allowed_mime_ext = [];
+    private $mime_types = [];
 
-    public function __construct() {
-        $this->upload_errors = array(
+    public function __construct($user_id = null) {
+
+        $this->mime_types = (new cmsConfigs('mimetypes.php'))->getAll();
+
+        $this->upload_errors = [
             UPLOAD_ERR_OK         => LANG_UPLOAD_ERR_OK,
             UPLOAD_ERR_INI_SIZE   => sprintf(LANG_UPLOAD_ERR_INI_SIZE, $this->getMaxUploadSize()),
             UPLOAD_ERR_FORM_SIZE  => LANG_UPLOAD_ERR_FORM_SIZE,
             UPLOAD_ERR_PARTIAL    => LANG_UPLOAD_ERR_PARTIAL,
             UPLOAD_ERR_NO_FILE    => LANG_UPLOAD_ERR_NO_FILE,
             UPLOAD_ERR_NO_TMP_DIR => LANG_UPLOAD_ERR_NO_TMP_DIR,
             UPLOAD_ERR_CANT_WRITE => LANG_UPLOAD_ERR_CANT_WRITE,
             UPLOAD_ERR_EXTENSION  => LANG_UPLOAD_ERR_EXTENSION
-        );
-        $this->user_id = cmsUser::getInstance()->id;
+        ];
+
+        $this->user_id  = $user_id ?? cmsUser::getInstance()->id;
         $this->site_cfg = cmsConfig::getInstance();
     }
 
+    /**
+     * Устанавливает разрешённые типы файлов
+     *
+     * @param array $types
+     * @return $this
+     */
     public function setAllowedMime($types) {
 
         $this->allowed_mime = $types;
 
-        $mime_types = (new cmsConfigs('mimetypes.php'))->getAll();
-
         foreach ($this->allowed_mime as $mime) {
-            if(isset($mime_types[$mime])){
-                $this->allowed_mime_ext[] = $mime_types[$mime];
+            if (isset($this->mime_types[$mime])) {
+                $this->allowed_mime_ext[] = $this->mime_types[$mime];
             }
         }
 
         return $this;
     }
 
+    /**
+     * Устанавливает разрешённые типы файлов по их расширению
+     *
+     * @param array|string $allowed_ext
+     * @return $this
+     */
+    private function setAllowedMimeByExt($allowed_ext) {
+
+        // Если установлено ранее, то ничего не делаем
+        if ($this->allowed_mime) {
+            return $this;
+        }
+
+        $this->allowed_mime = [];
+
+        if (!is_array($allowed_ext)) {
+            $allowed_ext = explode(',', (string) $allowed_ext);
+        }
+
+        foreach ($allowed_ext as $aext) {
+
+            $aext = mb_strtolower(trim(trim((string) $aext, '., ')));
+
+            if (empty($aext)) {
+                continue;
+            }
+
+            $mime_key = array_search($aext, $this->mime_types, true);
+
+            if(!$mime_key){
+                continue;
+            }
+
+            $this->allowed_mime[] = $mime_key;
+
+            $this->allowed_mime_ext[] = $aext;
+        }
+
+    }
+
+    /**
+     * Устанавливает имя файла
+     *
+     * @param string $name
+     * @return $this
+     */
     public function setFileName($name) {
-        $this->file_name = mb_substr(trim($name), 0, 64); return $this;
+
+        $this->file_name = mb_substr(trim($name), 0, 64);
+
+        return $this;
     }
 
+    /**
+     * Устанавливает id пользователя
+     *
+     * @param int $id
+     * @return $this
+     */
     public function setUserId($id) {
-        $this->user_id = $id; return $this;
+
+        $this->user_id = $id;
+
+        return $this;
     }
 
+    /**
+     * Возвращает последнюю ошибку
+     *
+     * @return ?string
+     */
     public function getLastError() {
         return $this->last_error;
     }
 
-//============================================================================//
     /**
      * Возвращает строку с максимальным размером загружаемых файлов,
      * установленным в php.ini
+     *
      * @return string
      */
-    public function getMaxUploadSize(){
+    public function getMaxUploadSize() {
 
         // вычисляем по тому, что меньше, т.к. если post_max_size меньше upload_max_filesize,
         // то максимум можно будет загрузить post_max_size
         $max_size = min(files_convert_bytes(@ini_get('upload_max_filesize')), files_convert_bytes(@ini_get('post_max_size')));
 
         return files_format_bytes($max_size);
-
     }
 
-    public function isUploaded($name){
+    /**
+     * Проверяет, загружен ли файл наличием его в $_FILES
+     *
+     * @param string $name Имя в массиве $_FILES
+     * @return boolean
+     */
+    public function isUploaded($name) {
 
-        if (!isset($_FILES[$name])) { return false; }
+        if (!isset($_FILES[$name])) {
+            return false;
+        }
 
         if (empty($_FILES[$name]['size'])) {
 
-            if(isset($_FILES[$name]['error'])){
-                if(isset($this->upload_errors[$_FILES[$name]['error']]) && $this->upload_errors[$_FILES[$name]['error']] !== UPLOAD_ERR_OK){
+            if (isset($_FILES[$name]['error'])) {
+                if (isset($this->upload_errors[$_FILES[$name]['error']]) && $this->upload_errors[$_FILES[$name]['error']] !== UPLOAD_ERR_OK) {
                     $this->last_error = $this->upload_errors[$_FILES[$name]['error']];
                 }
             }
 
             return false;
-
         }
 
         return true;
-
     }
 
-    public function isUploadedXHR($name){
+    /**
+     * Проверяет, загружен ли файл через XHR
+     *
+     * @param string $name Имя в массиве $_GET
+     * @return boolean
+     */
+    public function isUploadedXHR($name) {
         return !empty($_GET['qqfile']);
     }
 
-    public function isUploadedFromLink($name){
+    /**
+     * Проверяет, надо ли загрузить файл по ссылке
+     *
+     * @param string $name Имя в массиве $_POST
+     * @return boolean
+     */
+    public function isUploadedFromLink($name) {
         return $this->allow_remote && !empty($_POST[$name]);
     }
 
+    /**
+     * Разрешает загрузку по ссылке
+     *
+     * @return $this
+     */
     public function enableRemoteUpload() {
-        $this->allow_remote = true; return $this;
-    }
-    public function disableRemoteUpload() {
-        $this->allow_remote = false; return $this;
-    }
-
-    private function getFileName($path, $file_ext, $file_name = false) {
-
-        if(!$file_name){
-            if($this->file_name){
-                $file_name = str_replace('.'.$file_ext, '', files_sanitize_name($this->file_name.'.'.$file_ext));
-            } else {
-                $file_name = substr(md5(microtime(true)), 0, 8);
-            }
-        }
-
-        if (file_exists($path.$file_name.'.'.$file_ext)) {
-            return $this->getFileName($path, $file_ext, $file_name.'_'.md5(microtime(true)));
-        }
 
-        return $file_name.'.'.$file_ext;
+        $this->allow_remote = true;
 
+        return $this;
     }
 
-//============================================================================//
-//============================================================================//
-
     /**
-     * Этот метод устаревший, используйте класс cmsImages
+     * Запрещает загрузку по ссылке
+     *
+     * @return $this
      */
-    public function resizeImage($source_file, $size){
-
-        $dest_dir  = $this->getUploadDestinationDirectory();
-        $dest_ext  = pathinfo($source_file, PATHINFO_EXTENSION);
-        $dest_name = $this->getFileName($dest_dir, $dest_ext);
-
-        $dest_file = $dest_dir . $dest_name;
-
-        if (!isset($size['height'])) { $size['height'] = 0; }
-        if (!isset($size['quality'])) { $size['quality'] = 90; }
-
-        if (img_resize($source_file, $dest_file, $size['width'], $size['height'], $size['is_square'], $size['quality'])) {
-
-            return str_replace($this->site_cfg->upload_path, '', $dest_file);
-
-        }
+    public function disableRemoteUpload() {
 
-        return false;
+        $this->allow_remote = false;
 
+        return $this;
     }
 
-    private function checkExt($ext, $allowed_ext) {
-
-        if($allowed_ext === false){
-            return true;
-        }
-
-        if(empty($ext)){ return false; }
-
-        if(!is_array($allowed_ext)){
-            $allowed_ext = explode(',', (string)$allowed_ext);
-        }
-
-        $allowed = array();
+    /**
+     * Возвращает имя файла с расширением
+     * проверяя наличии одноимённого
+     *
+     * @param string $path Путь к директории хранения файла
+     * @param string $file_ext Расширение файла
+     * @param ?string $file_name Имя файла
+     * @return string
+     */
+    private function getFileName($path, $file_ext, $file_name = null) {
 
-        foreach($allowed_ext as $aext){
-            $aext = mb_strtolower(trim(trim((string)$aext, '., ')));
-            if(empty($aext)){
-                continue;
+        if (!$file_name) {
+            if ($this->file_name) {
+                $file_name = str_replace('.' . $file_ext, '', files_sanitize_name($this->file_name . '.' . $file_ext));
+            } else {
+                $file_name = substr(md5(microtime(true)), 0, 8);
             }
-            $allowed[] = $aext;
         }
 
-        return in_array(mb_strtolower($ext), $allowed, true);
-
-    }
-
-    private function isMimeTypeAllowed($file_path) {
-
-        $finfo = finfo_open(FILEINFO_MIME_TYPE);
-
-        $file_mime = finfo_file($finfo, $file_path);
-
-        if($file_mime === false){ return false; }
-
-        return in_array($file_mime, $this->allowed_mime);
+        if (file_exists($path . $file_name . '.' . $file_ext)) {
+            return $this->getFileName($path, $file_ext, $file_name . '_' . md5(microtime(true)));
+        }
 
+        return $file_name . '.' . $file_ext;
     }
 
-//============================================================================//
-//============================================================================//
-
     /**
      * Загружает файл на сервер
-     * @param string $post_filename Название поля с файлом в массиве $_FILES
+     *
+     * @param string $filename Название поля с файлом
      * @param string $allowed_ext Список допустимых расширений (через запятую)
      * @param string $allowed_size Максимальный размер файла (в байтах)
-     * @param string $destination Папка назначения (внутри пути upload)
+     * @param string $destination Директория назначения (внутри пути upload)
      * @return array
      */
-    public function upload($post_filename, $allowed_ext = false, $allowed_size = 0, $destination = false){
+    public function upload($filename, $allowed_ext = false, $allowed_size = 0, $destination = false) {
 
-        if ($this->isUploadedFromLink($post_filename)){
-            return $this->uploadFromLink($post_filename, $allowed_ext, $allowed_size, $destination);
+        // Если переданы расширения
+        if ($allowed_ext) {
+            $this->setAllowedMimeByExt($allowed_ext);
         }
 
-        if ($this->isUploadedXHR($post_filename)){
-            return $this->uploadXHR($post_filename, $allowed_ext, $allowed_size, $destination);
+        if ($this->isUploadedFromLink($filename)) {
+            return $this->uploadFromLink($filename, $allowed_size, $destination);
         }
 
-        if ($this->isUploaded($post_filename)){
-            return $this->uploadForm($post_filename, $allowed_ext, $allowed_size, $destination);
+        if ($this->isUploadedXHR($filename)) {
+            return $this->uploadXHR($filename, $allowed_size, $destination);
+        }
+
+        if ($this->isUploaded($filename)) {
+            return $this->uploadForm($filename, $allowed_size, $destination);
         }
 
         $last_error = $this->getLastError();
 
-        return array(
+        return [
             'success' => false,
             'error'   => ($last_error ? $last_error : LANG_UPLOAD_ERR_NO_FILE)
-        );
-
+        ];
     }
 
 //============================================================================//
 //============================================================================//
 
     /**
      * Загружает файл на сервер переданный через input типа file
-     * @param string $post_filename Название поля с файлом в массиве $_FILES
-     * @param string $allowed_ext Список допустимых расширений (через запятую)
-     * @param string $allowed_size Максимальный размер файла (в байтах)
-     * @param string $destination Папка назначения (внутри пути upload)
+     *
+     * @param string $filename Название поля с файлом в массиве $_FILES
+     * @param int $allowed_size Максимальный размер файла (в байтах)
+     * @param string $destination Директория назначения (внутри пути upload)
      * @return array
      */
-    public function uploadForm($post_filename, $allowed_ext = false, $allowed_size = 0, $destination = false){
+    public function uploadForm($filename, $allowed_size = 0, $destination = false) {
 
-        $source     = $_FILES[$post_filename]['tmp_name'];
-        $error_code = $_FILES[$post_filename]['error'];
-        $dest_size  = (int)$_FILES[$post_filename]['size'];
-        $dest_name  = files_sanitize_name($_FILES[$post_filename]['name']);
-        $dest_ext   = pathinfo($dest_name, PATHINFO_EXTENSION);
+        $source     = $_FILES[$filename]['tmp_name'];
+        $error_code = $_FILES[$filename]['error'];
+        $dest_size  = (int) $_FILES[$filename]['size'];
+        $dest_name  = files_sanitize_name($_FILES[$filename]['name']);
 
-        if(!$this->checkExt($dest_ext, $allowed_ext)){
-            return array(
-                'error'   => LANG_UPLOAD_ERR_MIME,
+        $file = new cmsUploadfile($source, $this->allowed_mime);
+
+        if (!$file->isAllowed()) {
+            return [
+                'error'   => LANG_UPLOAD_ERR_MIME . '. ' . sprintf(LANG_PARSER_FILE_EXTS_FIELD_HINT, implode(', ', $this->allowed_mime_ext)),
                 'success' => false,
                 'name'    => $dest_name
-            );
-        }
-
-        if($this->allowed_mime !== false){
-            if(!$this->isMimeTypeAllowed($source)){
-                return array(
-                    'error'   => LANG_UPLOAD_ERR_MIME.'. '.sprintf(LANG_PARSER_FILE_EXTS_FIELD_HINT, implode(', ', $this->allowed_mime_ext)),
-                    'success' => false,
-                    'name'    => $dest_name
-                );
-            }
+            ];
         }
 
-        if ($allowed_size){
-            if ($dest_size > $allowed_size){
-                return array(
+        if ($allowed_size) {
+            if ($dest_size > $allowed_size) {
+                return [
                     'error'   => sprintf(LANG_UPLOAD_ERR_INI_SIZE, files_format_bytes($allowed_size)),
                     'success' => false,
                     'name'    => $dest_name
-                );
+                ];
             }
         }
 
-        if (!$destination){
+        $dest_ext = $file->getExt();
+
+        if (!$destination) {
             $destination = $this->getUploadDestinationDirectory();
         } else {
             $destination = $this->site_cfg->upload_path . $destination . '/';
         }
 
@@ -281,302 +325,225 @@ class cmsUploader {
         }
 
         $destination .= $this->getFileName($destination, $dest_ext);
 
         return $this->moveUploadedFile($source, $destination, $error_code, $dest_name, $dest_size);
-
     }
 
-//============================================================================//
-//============================================================================//
-
-    public function uploadFromLink($post_filename, $allowed_ext = false, $allowed_size = 0, $destination = false) {
+    /**
+     * Загружает файл по ссылке
+     *
+     * @param string $post_filename Название поля с файлом в массиве $_POST
+     * @param int $allowed_size Максимальный размер файла (в байтах)
+     * @param string $destination Директория назначения (внутри пути upload)
+     * @return array
+     */
+    public function uploadFromLink($post_filename, $allowed_size = 0, $destination = false) {
 
         $link = $file_name = trim($_POST[$post_filename]);
 
         // проверяем редирект и имя файла
-        if (function_exists('curl_init')){
-            $curl = curl_init();
-            curl_setopt($curl, CURLOPT_URL, $link);
-            curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
-            curl_setopt($curl, CURLOPT_HEADER, true);
-            curl_setopt($curl, CURLOPT_NOBODY, true);
-            curl_setopt($curl, CURLOPT_TIMEOUT, 5);
-            $headers = curl_exec($curl);
-            curl_close($curl);
-            $matches = array();
-            if(preg_match("/(?:Location:|URI:)([^\n]+)*/is", $headers, $matches)){
-                $url = trim($matches[1]);
-                if(strpos($url, 'http') !== 0){
-                    $url_data = parse_url($link);
-                    $link = $url_data['scheme'].'://'.$url_data['host'].$url;
-                } else {
-                    $link = $url;
-                }
-                $_POST[$post_filename] = $link;
-                return $this->uploadFromLink($post_filename, $allowed_ext, $allowed_size, $destination);
-            }
-            if(preg_match('#filename="([^"]+)#uis', $headers, $matches)){
-                $file_name = trim($matches[1]);
+        $curl = curl_init();
+        curl_setopt($curl, CURLOPT_URL, $link);
+        curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
+        curl_setopt($curl, CURLOPT_HEADER, true);
+        curl_setopt($curl, CURLOPT_NOBODY, true);
+        curl_setopt($curl, CURLOPT_TIMEOUT, 5);
+        $headers = curl_exec($curl);
+        curl_close($curl);
+
+        $matches = [];
+        if (preg_match("/(?:Location:|URI:)([^\n]+)*/is", $headers, $matches)) {
+
+            $url = trim($matches[1]);
+
+            if (strpos($url, 'http') !== 0) {
+                $url_data = parse_url($link);
+                $link     = $url_data['scheme'] . '://' . $url_data['host'] . $url;
+            } else {
+                $link = $url;
             }
-        }
 
-        $dest_ext  = strtolower(pathinfo(parse_url($file_name, PHP_URL_PATH), PATHINFO_EXTENSION));
-        $dest_name = files_sanitize_name($file_name);
+            $_POST[$post_filename] = $link;
 
-        if(!$this->checkExt($dest_ext, $allowed_ext)){
-            return array(
-                'error'   => LANG_UPLOAD_ERR_MIME,
-                'success' => false,
-                'name'    => $dest_name
-            );
+            return $this->uploadFromLink($post_filename, $allowed_size, $destination);
+        }
+
+        if (preg_match('#filename="([^"]+)#uis', $headers, $matches)) {
+            $file_name = trim($matches[1]);
         }
 
+        $dest_name = files_sanitize_name($file_name);
+
         $file_bin = file_get_contents_from_url($link);
 
-        if(!$file_bin){
-            return array(
+        if (!$file_bin) {
+            return [
                 'success' => false,
-                'error'   => LANG_UPLOAD_ERR_PARTIAL,
+                'error'   => LANG_UPLOAD_ERR_NO_FILE,
                 'name'    => $dest_name,
                 'path'    => ''
-            );
+            ];
         }
 
-        $image_size = strlen($file_bin);
-
-        if ($allowed_size){
-            if ($image_size > $allowed_size){
-                return array(
-                    'error'   => sprintf(LANG_UPLOAD_ERR_INI_SIZE, files_format_bytes($allowed_size)),
-                    'success' => false,
-                    'name'    => $dest_name
-                );
-            }
-        }
-
-        if (!$destination){
-            $destination = $this->getUploadDestinationDirectory();
-        } else {
-            $destination = $this->site_cfg->upload_path . $destination.'/';
-        }
+        return $this->saveFileFromString($file_bin, $allowed_size, $destination, $dest_name);
+    }
 
-        $destination .= $this->getFileName($destination, $dest_ext);
+    /**
+     * Сохраняет файл из php://input в целевую директорию  и отслеживает ошибки
+     *
+     * @param string $filename Название поля с файлом в массиве $_GET
+     * @param int $allowed_size Максимальный размер файла (в байтах)
+     * @param string $destination Директория назначения (внутри пути upload)
+     * @return array
+     */
+    public function uploadXHR($filename, $allowed_size = 0, $destination = false) {
 
-		$f = fopen($destination, 'w+');
-		fwrite($f, $file_bin);
-        fclose($f);
+        $dest_name = files_sanitize_name($_GET['qqfile']);
 
+        $file_bin = file_get_contents('php://input');
 
-        if($this->allowed_mime !== false){
-            if(!$this->isMimeTypeAllowed($destination)){
-                @unlink($destination);
-                return array(
-                    'error'   => LANG_UPLOAD_ERR_MIME.'. '.sprintf(LANG_PARSER_FILE_EXTS_FIELD_HINT, implode(', ', $this->allowed_mime_ext)),
-                    'success' => false,
-                    'name'    => $dest_name
-                );
-            }
+        if (!$file_bin) {
+            return [
+                'success' => false,
+                'error'   => LANG_UPLOAD_ERR_NO_FILE,
+                'name'    => $dest_name,
+                'path'    => ''
+            ];
         }
 
-        return array(
-            'success' => true,
-            'path'    => $destination,
-            'url'     => str_replace($this->site_cfg->upload_path, '', $destination),
-            'name'    => basename($destination),
-            'size'    => $image_size
-        );
-
+        return $this->saveFileFromString($file_bin, $allowed_size, $destination, $dest_name);
     }
 
     /**
-     * Загружает файл на сервер переданный через XHR
-     * @param string $post_filename Название поля с файлом в массиве $_GET
-     * @param string $allowed_ext Список допустимых расширений (через запятую)
-     * @param string $allowed_size Максимальный размер файла (в байтах)
-     * @param string $destination Папка назначения (внутри пути upload)
+     * Сохраняет файл из строки данных
+     *
+     * @param string $file_bin
+     * @param int $allowed_size Максимальный размер файла (в байтах)
+     * @param string $destination Директория назначения (внутри пути upload)
+     * @param string $dest_name Имя файла
      * @return array
      */
-    public function uploadXHR($post_filename, $allowed_ext = false, $allowed_size = 0, $destination = false){
+    private function saveFileFromString($file_bin, $allowed_size, $destination, $dest_name) {
 
-        $dest_name = files_sanitize_name($_GET['qqfile'], false);
-        $dest_ext  = pathinfo($dest_name, PATHINFO_EXTENSION);
+        $file = new cmsUploadfile($file_bin, $this->allowed_mime);
 
-        if(!$this->checkExt($dest_ext, $allowed_ext)){
-            return array(
-                'error'   => LANG_UPLOAD_ERR_MIME,
+        if (!$file->isAllowed()) {
+            return [
+                'error'   => LANG_UPLOAD_ERR_MIME . '. ' . sprintf(LANG_PARSER_FILE_EXTS_FIELD_HINT, implode(', ', $this->allowed_mime_ext)),
                 'success' => false,
                 'name'    => $dest_name
-            );
+            ];
         }
 
-        if ($allowed_size){
-            if ($this->getXHRFileSize() > $allowed_size){
-                return array(
+        $dest_ext = $file->getExt();
+
+        $file_size = strlen($file_bin);
+
+        if ($allowed_size) {
+            if ($file_size > $allowed_size) {
+                return [
                     'error'   => sprintf(LANG_UPLOAD_ERR_INI_SIZE, files_format_bytes($allowed_size)),
                     'success' => false,
                     'name'    => $dest_name
-                );
+                ];
             }
         }
 
-        if (!$destination){
+        if (!$destination) {
             $destination = $this->getUploadDestinationDirectory();
         } else {
             $destination = $this->site_cfg->upload_path . $destination . '/';
         }
 
         $destination .= $this->getFileName($destination, $dest_ext);
 
-        return $this->saveXHRFile($destination, $dest_name);
-
-    }
-
-    public function getXHRFileSize(){
-        if (isset($_SERVER["CONTENT_LENGTH"])){
-            return (int)$_SERVER["CONTENT_LENGTH"];
-        } else {
-            return false;
-        }
-    }
-
-//============================================================================//
-//============================================================================//
-
-    public function saveXHRFile($destination, $orig_name=''){
-
-        $target = @fopen($destination, 'wb');
-        $input  = @fopen("php://input", 'rb');
-
-        if (!$target){
-            return array(
+        if (!is_writable(dirname($destination))) {
+            return [
                 'success' => false,
                 'error'   => LANG_UPLOAD_ERR_CANT_WRITE,
-                'name'    => $orig_name,
-                'path'    => ''
-            );
-        }
-        if (!$input){
-            return array(
-                'success' => false,
-                'error'   => LANG_UPLOAD_ERR_NO_FILE,
-                'name'    => $orig_name,
+                'name'    => $dest_name,
                 'path'    => ''
-            );
+            ];
         }
 
-        while ($buff = fread($input, 4096)) {
-            fwrite($target, $buff);
-        }
-
-        @fclose($target);
-        @fclose($input);
-
-        $real_size = filesize($destination);
-
-        if (!$real_size || $real_size != $this->getXHRFileSize()){
-            @unlink($destination);
-            return array(
+        if(file_put_contents($destination, $file_bin) === false){
+            return [
                 'success' => false,
-                'error'   => LANG_UPLOAD_ERR_PARTIAL,
-                'name'    => $orig_name,
+                'error'   => LANG_UPLOAD_ERR_CANT_WRITE,
+                'name'    => $dest_name,
                 'path'    => ''
-            );
+            ];
         }
 
-        if($this->allowed_mime !== false){
-            if(!$this->isMimeTypeAllowed($destination)){
-                @unlink($destination);
-                return array(
-                    'error'   => LANG_UPLOAD_ERR_MIME.'. '.sprintf(LANG_PARSER_FILE_EXTS_FIELD_HINT, implode(', ', $this->allowed_mime_ext)),
-                    'success' => false,
-                    'name'    => $orig_name,
-                    'path'    => ''
-                );
-            }
-        }
-
-        return array(
+        return [
             'success' => true,
             'path'    => $destination,
             'url'     => str_replace($this->site_cfg->upload_path, '', $destination),
-            'name'    => $orig_name,
-            'size'    => $real_size
-        );
-
+            'name'    => basename($destination),
+            'size'    => $file_size
+        ];
     }
 
-//============================================================================//
     /**
-     * Копирует файл из временной папки в целевую и отслеживает ошибки
+     * Копирует файл из временной директории в целевую и отслеживает ошибки
+     *
      * @param string $source
      * @param string $destination
      * @param int $errorCode
-     * @return bool
+     * @return array
      */
-    private function moveUploadedFile($source, $destination, $errorCode, $orig_name='', $orig_size=0){
+    private function moveUploadedFile($source, $destination, $errorCode, $orig_name = '', $orig_size = 0) {
 
-        if($errorCode !== UPLOAD_ERR_OK && isset($this->upload_errors[$errorCode])){
+        if ($errorCode !== UPLOAD_ERR_OK && isset($this->upload_errors[$errorCode])) {
 
-            return array(
+            return [
                 'success' => false,
                 'error'   => $this->upload_errors[$errorCode],
                 'name'    => $orig_name,
                 'path'    => ''
-            );
-
+            ];
         }
 
         $upload_dir = dirname($destination);
-        if (!is_writable($upload_dir)){	@chmod($upload_dir, 0777); }
+        if (!is_writable($upload_dir)) {
+            @chmod($upload_dir, 0777);
+        }
+
+        if (!is_writable($upload_dir)) {
 
-        if (!is_writable($upload_dir)){
-            return array(
+            return [
                 'success' => false,
                 'error'   => LANG_UPLOAD_ERR_CANT_WRITE,
                 'name'    => $orig_name,
                 'path'    => ''
-            );
+            ];
         }
 
-        return array(
+        return [
             'success' => @move_uploaded_file($source, $destination),
             'path'    => $destination,
             'url'     => str_replace($this->site_cfg->upload_path, '', $destination),
             'name'    => basename($destination),
             'size'    => $orig_size,
             'error'   => $this->upload_errors[$errorCode]
-        );
-
+        ];
     }
 
     /**
      * Удаляет файл
      * @param string $file_path
      * @return boolean
      */
-    public function remove($file_path){
-        return @unlink($file_path);
+    public function remove($file_path) {
+        return files_delete_file($file_path, 2);
     }
 
     /**
      * Создаёт дерево директорий для загрузки файла
      * @return string
      */
-    public function getUploadDestinationDirectory(){
+    public function getUploadDestinationDirectory() {
         return files_get_upload_dir($this->user_id);
     }
 
-    /**
-     * Проверяет файл, является ли он изображением
-     * @param string $src
-     * @return boolean
-     */
-    public function isImage($src){
-
-        $size = getimagesize($src);
-
-        return $size !== false;
-
-    }
-
 }
diff --git a/system/core/uploadfile.php b/system/core/uploadfile.php
new file mode 100755
index 000000000..8bbab3a93
--- /dev/null
+++ b/system/core/uploadfile.php
@@ -0,0 +1,57 @@
+<?php
+
+class cmsUploadfile {
+
+    private $file_mime;
+    private $mime_types;
+    private $allowed_mime;
+
+    public function __construct($file_path, $allowed_mime = null) {
+
+        $this->allowed_mime = $allowed_mime;
+
+        $this->mime_types = (new cmsConfigs('mimetypes.php'))->getAll();
+
+        $finfo = finfo_open(FILEINFO_MIME_TYPE);
+
+        if(strpos($file_path, DIRECTORY_SEPARATOR) === 0){
+
+            $this->file_mime = finfo_file($finfo, $file_path);
+
+        } else {
+
+            $this->file_mime = finfo_buffer($finfo, $file_path);
+        }
+
+        finfo_close($finfo);
+    }
+
+    public function getMime() {
+        return $this->file_mime;
+    }
+
+    public function getExt() {
+
+        if ($this->file_mime && isset($this->mime_types[$this->file_mime])) {
+
+            return $this->mime_types[$this->file_mime];
+        }
+
+        return 'bin';
+    }
+
+    public function isAllowed() {
+
+        // Пускаем любые, если не указано
+        if ($this->allowed_mime === null) {
+            return true;
+        }
+
+        if ($this->file_mime === false) {
+            return false;
+        }
+
+        return in_array($this->file_mime, $this->allowed_mime, true);
+    }
+
+}

Vulnerable code:         $result = $this->cms_uploader->upload($this->upload_name, $this->upload_exts, 0, $this->installer_upload_path);

    private $site_cfg = array();
    private $last_error = false;
    private $upload_errors = array();
    public function __construct() {
        $this->upload_errors = array(
        );
        $this->user_id = cmsUser::getInstance()->id;
        $mime_types = (new cmsConfigs('mimetypes.php'))->getAll();

            if(isset($mime_types[$mime])){
                $this->allowed_mime_ext[] = $mime_types[$mime];
        $this->file_name = mb_substr(trim($name), 0, 64); return $this;
        $this->user_id = $id; return $this;
//============================================================================//
    public function getMaxUploadSize(){

    public function isUploaded($name){
        if (!isset($_FILES[$name])) { return false; }
            if(isset($_FILES[$name]['error'])){
                if(isset($this->upload_errors[$_FILES[$name]['error']]) && $this->upload_errors[$_FILES[$name]['error']] !== UPLOAD_ERR_OK){


    public function isUploadedXHR($name){
    public function isUploadedFromLink($name){
        $this->allow_remote = true; return $this;
    }
    public function disableRemoteUpload() {
        $this->allow_remote = false; return $this;
    }

    private function getFileName($path, $file_ext, $file_name = false) {

        if(!$file_name){
            if($this->file_name){
                $file_name = str_replace('.'.$file_ext, '', files_sanitize_name($this->file_name.'.'.$file_ext));
            } else {
                $file_name = substr(md5(microtime(true)), 0, 8);
            }
        }

        if (file_exists($path.$file_name.'.'.$file_ext)) {
            return $this->getFileName($path, $file_ext, $file_name.'_'.md5(microtime(true)));
        }
        return $file_name.'.'.$file_ext;
//============================================================================//
//============================================================================//

     * Этот метод устаревший, используйте класс cmsImages
    public function resizeImage($source_file, $size){

        $dest_dir  = $this->getUploadDestinationDirectory();
        $dest_ext  = pathinfo($source_file, PATHINFO_EXTENSION);
        $dest_name = $this->getFileName($dest_dir, $dest_ext);

        $dest_file = $dest_dir . $dest_name;

        if (!isset($size['height'])) { $size['height'] = 0; }
        if (!isset($size['quality'])) { $size['quality'] = 90; }

        if (img_resize($source_file, $dest_file, $size['width'], $size['height'], $size['is_square'], $size['quality'])) {

            return str_replace($this->site_cfg->upload_path, '', $dest_file);

        }
        return false;
    private function checkExt($ext, $allowed_ext) {

        if($allowed_ext === false){
            return true;
        }

        if(empty($ext)){ return false; }

        if(!is_array($allowed_ext)){
            $allowed_ext = explode(',', (string)$allowed_ext);
        }

        $allowed = array();
        foreach($allowed_ext as $aext){
            $aext = mb_strtolower(trim(trim((string)$aext, '., ')));
            if(empty($aext)){
                continue;
            $allowed[] = $aext;
        return in_array(mb_strtolower($ext), $allowed, true);

    }

    private function isMimeTypeAllowed($file_path) {

        $finfo = finfo_open(FILEINFO_MIME_TYPE);

        $file_mime = finfo_file($finfo, $file_path);

        if($file_mime === false){ return false; }

        return in_array($file_mime, $this->allowed_mime);
//============================================================================//
//============================================================================//

     * @param string $post_filename Название поля с файлом в массиве $_FILES
     * @param string $destination Папка назначения (внутри пути upload)
    public function upload($post_filename, $allowed_ext = false, $allowed_size = 0, $destination = false){
        if ($this->isUploadedFromLink($post_filename)){
            return $this->uploadFromLink($post_filename, $allowed_ext, $allowed_size, $destination);
        if ($this->isUploadedXHR($post_filename)){
            return $this->uploadXHR($post_filename, $allowed_ext, $allowed_size, $destination);
        if ($this->isUploaded($post_filename)){
            return $this->uploadForm($post_filename, $allowed_ext, $allowed_size, $destination);
        return array(
        );

     * @param string $post_filename Название поля с файлом в массиве $_FILES
     * @param string $allowed_ext Список допустимых расширений (через запятую)
     * @param string $allowed_size Максимальный размер файла (в байтах)
     * @param string $destination Папка назначения (внутри пути upload)
    public function uploadForm($post_filename, $allowed_ext = false, $allowed_size = 0, $destination = false){
        $source     = $_FILES[$post_filename]['tmp_name'];
        $error_code = $_FILES[$post_filename]['error'];
        $dest_size  = (int)$_FILES[$post_filename]['size'];
        $dest_name  = files_sanitize_name($_FILES[$post_filename]['name']);
        $dest_ext   = pathinfo($dest_name, PATHINFO_EXTENSION);
        if(!$this->checkExt($dest_ext, $allowed_ext)){
            return array(
                'error'   => LANG_UPLOAD_ERR_MIME,
            );
        }

        if($this->allowed_mime !== false){
            if(!$this->isMimeTypeAllowed($source)){
                return array(
                    'error'   => LANG_UPLOAD_ERR_MIME.'. '.sprintf(LANG_PARSER_FILE_EXTS_FIELD_HINT, implode(', ', $this->allowed_mime_ext)),
                    'success' => false,
                    'name'    => $dest_name
                );
            }
        if ($allowed_size){
            if ($dest_size > $allowed_size){
                return array(
                );
        if (!$destination){

//============================================================================//
//============================================================================//

    public function uploadFromLink($post_filename, $allowed_ext = false, $allowed_size = 0, $destination = false) {
        if (function_exists('curl_init')){
            $curl = curl_init();
            curl_setopt($curl, CURLOPT_URL, $link);
            curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
            curl_setopt($curl, CURLOPT_HEADER, true);
            curl_setopt($curl, CURLOPT_NOBODY, true);
            curl_setopt($curl, CURLOPT_TIMEOUT, 5);
            $headers = curl_exec($curl);
            curl_close($curl);
            $matches = array();
            if(preg_match("/(?:Location:|URI:)([^\n]+)*/is", $headers, $matches)){
                $url = trim($matches[1]);
                if(strpos($url, 'http') !== 0){
                    $url_data = parse_url($link);
                    $link = $url_data['scheme'].'://'.$url_data['host'].$url;
                } else {
                    $link = $url;
                }
                $_POST[$post_filename] = $link;
                return $this->uploadFromLink($post_filename, $allowed_ext, $allowed_size, $destination);
            }
            if(preg_match('#filename="([^"]+)#uis', $headers, $matches)){
                $file_name = trim($matches[1]);
        }
        $dest_ext  = strtolower(pathinfo(parse_url($file_name, PHP_URL_PATH), PATHINFO_EXTENSION));
        $dest_name = files_sanitize_name($file_name);
        if(!$this->checkExt($dest_ext, $allowed_ext)){
            return array(
                'error'   => LANG_UPLOAD_ERR_MIME,
                'success' => false,
                'name'    => $dest_name
            );
        if(!$file_bin){
            return array(
                'error'   => LANG_UPLOAD_ERR_PARTIAL,
            );
        $image_size = strlen($file_bin);

        if ($allowed_size){
            if ($image_size > $allowed_size){
                return array(
                    'error'   => sprintf(LANG_UPLOAD_ERR_INI_SIZE, files_format_bytes($allowed_size)),
                    'success' => false,
                    'name'    => $dest_name
                );
            }
        }

        if (!$destination){
            $destination = $this->getUploadDestinationDirectory();
        } else {
            $destination = $this->site_cfg->upload_path . $destination.'/';
        }
        $destination .= $this->getFileName($destination, $dest_ext);
		$f = fopen($destination, 'w+');
		fwrite($f, $file_bin);
        fclose($f);
        if($this->allowed_mime !== false){
            if(!$this->isMimeTypeAllowed($destination)){
                @unlink($destination);
                return array(
                    'error'   => LANG_UPLOAD_ERR_MIME.'. '.sprintf(LANG_PARSER_FILE_EXTS_FIELD_HINT, implode(', ', $this->allowed_mime_ext)),
                    'success' => false,
                    'name'    => $dest_name
                );
            }
        return array(
            'success' => true,
            'path'    => $destination,
            'url'     => str_replace($this->site_cfg->upload_path, '', $destination),
            'name'    => basename($destination),
            'size'    => $image_size
        );

     * Загружает файл на сервер переданный через XHR
     * @param string $post_filename Название поля с файлом в массиве $_GET
     * @param string $allowed_ext Список допустимых расширений (через запятую)
     * @param string $allowed_size Максимальный размер файла (в байтах)
     * @param string $destination Папка назначения (внутри пути upload)
    public function uploadXHR($post_filename, $allowed_ext = false, $allowed_size = 0, $destination = false){
        $dest_name = files_sanitize_name($_GET['qqfile'], false);
        $dest_ext  = pathinfo($dest_name, PATHINFO_EXTENSION);
        if(!$this->checkExt($dest_ext, $allowed_ext)){
            return array(
                'error'   => LANG_UPLOAD_ERR_MIME,
            );
        if ($allowed_size){
            if ($this->getXHRFileSize() > $allowed_size){
                return array(
                );
        if (!$destination){
        return $this->saveXHRFile($destination, $dest_name);

    }

    public function getXHRFileSize(){
        if (isset($_SERVER["CONTENT_LENGTH"])){
            return (int)$_SERVER["CONTENT_LENGTH"];
        } else {
            return false;
        }
    }

//============================================================================//
//============================================================================//

    public function saveXHRFile($destination, $orig_name=''){

        $target = @fopen($destination, 'wb');
        $input  = @fopen("php://input", 'rb');

        if (!$target){
            return array(
                'name'    => $orig_name,
                'path'    => ''
            );
        }
        if (!$input){
            return array(
                'success' => false,
                'error'   => LANG_UPLOAD_ERR_NO_FILE,
                'name'    => $orig_name,
            );
        while ($buff = fread($input, 4096)) {
            fwrite($target, $buff);
        }

        @fclose($target);
        @fclose($input);

        $real_size = filesize($destination);

        if (!$real_size || $real_size != $this->getXHRFileSize()){
            @unlink($destination);
            return array(
                'error'   => LANG_UPLOAD_ERR_PARTIAL,
                'name'    => $orig_name,
            );
        if($this->allowed_mime !== false){
            if(!$this->isMimeTypeAllowed($destination)){
                @unlink($destination);
                return array(
                    'error'   => LANG_UPLOAD_ERR_MIME.'. '.sprintf(LANG_PARSER_FILE_EXTS_FIELD_HINT, implode(', ', $this->allowed_mime_ext)),
                    'success' => false,
                    'name'    => $orig_name,
                    'path'    => ''
                );
            }
        }

        return array(
            'name'    => $orig_name,
            'size'    => $real_size
        );

//============================================================================//
     * Копирует файл из временной папки в целевую и отслеживает ошибки
     * @return bool
    private function moveUploadedFile($source, $destination, $errorCode, $orig_name='', $orig_size=0){
        if($errorCode !== UPLOAD_ERR_OK && isset($this->upload_errors[$errorCode])){
            return array(
            );

        if (!is_writable($upload_dir)){	@chmod($upload_dir, 0777); }
        if (!is_writable($upload_dir)){
            return array(
            );
        return array(
        );

    public function remove($file_path){
        return @unlink($file_path);
    public function getUploadDestinationDirectory(){
    /**
     * Проверяет файл, является ли он изображением
     * @param string $src
     * @return boolean
     */
    public function isImage($src){

        $size = getimagesize($src);

        return $size !== false;

    }

Fixed code:         $result = $this->cms_uploader->setAllowedMime([
            'application/zip'
        ])->upload($this->upload_name, $this->upload_exts, 0, $this->installer_upload_path);
    private $site_cfg = null;
    private $last_error = null;
    private $upload_errors = [];
    private $mime_types = [];
    public function __construct($user_id = null) {

        $this->mime_types = (new cmsConfigs('mimetypes.php'))->getAll();

        $this->upload_errors = [
        ];

        $this->user_id  = $user_id ?? cmsUser::getInstance()->id;
    /**
     * Устанавливает разрешённые типы файлов
     *
     * @param array $types
     * @return $this
     */
            if (isset($this->mime_types[$mime])) {
                $this->allowed_mime_ext[] = $this->mime_types[$mime];
    /**
     * Устанавливает разрешённые типы файлов по их расширению
     *
     * @param array|string $allowed_ext
     * @return $this
     */
    private function setAllowedMimeByExt($allowed_ext) {

        // Если установлено ранее, то ничего не делаем
        if ($this->allowed_mime) {
            return $this;
        }

        $this->allowed_mime = [];

        if (!is_array($allowed_ext)) {
            $allowed_ext = explode(',', (string) $allowed_ext);
        }

        foreach ($allowed_ext as $aext) {

            $aext = mb_strtolower(trim(trim((string) $aext, '., ')));

            if (empty($aext)) {
                continue;
            }

            $mime_key = array_search($aext, $this->mime_types, true);

            if(!$mime_key){
                continue;
            }

            $this->allowed_mime[] = $mime_key;

            $this->allowed_mime_ext[] = $aext;
        }

    }

    /**
     * Устанавливает имя файла
     *
     * @param string $name
     * @return $this
     */

        $this->file_name = mb_substr(trim($name), 0, 64);

        return $this;
    /**
     * Устанавливает id пользователя
     *
     * @param int $id
     * @return $this
     */

        $this->user_id = $id;

        return $this;
    /**
     * Возвращает последнюю ошибку
     *
     * @return ?string
     */
     *
    public function getMaxUploadSize() {
    /**
     * Проверяет, загружен ли файл наличием его в $_FILES
     *
     * @param string $name Имя в массиве $_FILES
     * @return boolean
     */
    public function isUploaded($name) {
        if (!isset($_FILES[$name])) {
            return false;
        }
            if (isset($_FILES[$name]['error'])) {
                if (isset($this->upload_errors[$_FILES[$name]['error']]) && $this->upload_errors[$_FILES[$name]['error']] !== UPLOAD_ERR_OK) {
    /**
     * Проверяет, загружен ли файл через XHR
     *
     * @param string $name Имя в массиве $_GET
     * @return boolean
     */
    public function isUploadedXHR($name) {
    /**
     * Проверяет, надо ли загрузить файл по ссылке
     *
     * @param string $name Имя в массиве $_POST
     * @return boolean
     */
    public function isUploadedFromLink($name) {
    /**
     * Разрешает загрузку по ссылке
     *
     * @return $this
     */
        $this->allow_remote = true;
        return $this;
     * Запрещает загрузку по ссылке
     *
     * @return $this
    public function disableRemoteUpload() {
        $this->allow_remote = false;
        return $this;
    /**
     * Возвращает имя файла с расширением
     * проверяя наличии одноимённого
     *
     * @param string $path Путь к директории хранения файла
     * @param string $file_ext Расширение файла
     * @param ?string $file_name Имя файла
     * @return string
     */
    private function getFileName($path, $file_ext, $file_name = null) {
        if (!$file_name) {
            if ($this->file_name) {
                $file_name = str_replace('.' . $file_ext, '', files_sanitize_name($this->file_name . '.' . $file_ext));
            } else {
                $file_name = substr(md5(microtime(true)), 0, 8);
        if (file_exists($path . $file_name . '.' . $file_ext)) {
            return $this->getFileName($path, $file_ext, $file_name . '_' . md5(microtime(true)));
        }
        return $file_name . '.' . $file_ext;
     *
     * @param string $filename Название поля с файлом
     * @param string $destination Директория назначения (внутри пути upload)
    public function upload($filename, $allowed_ext = false, $allowed_size = 0, $destination = false) {
        // Если переданы расширения
        if ($allowed_ext) {
            $this->setAllowedMimeByExt($allowed_ext);
        if ($this->isUploadedFromLink($filename)) {
            return $this->uploadFromLink($filename, $allowed_size, $destination);
        if ($this->isUploadedXHR($filename)) {
            return $this->uploadXHR($filename, $allowed_size, $destination);
        }

        if ($this->isUploaded($filename)) {
            return $this->uploadForm($filename, $allowed_size, $destination);
        return [
        ];
     *
     * @param string $filename Название поля с файлом в массиве $_FILES
     * @param int $allowed_size Максимальный размер файла (в байтах)
     * @param string $destination Директория назначения (внутри пути upload)
    public function uploadForm($filename, $allowed_size = 0, $destination = false) {
        $source     = $_FILES[$filename]['tmp_name'];
        $error_code = $_FILES[$filename]['error'];
        $dest_size  = (int) $_FILES[$filename]['size'];
        $dest_name  = files_sanitize_name($_FILES[$filename]['name']);
        $file = new cmsUploadfile($source, $this->allowed_mime);

        if (!$file->isAllowed()) {
            return [
                'error'   => LANG_UPLOAD_ERR_MIME . '. ' . sprintf(LANG_PARSER_FILE_EXTS_FIELD_HINT, implode(', ', $this->allowed_mime_ext)),
            ];
        if ($allowed_size) {
            if ($dest_size > $allowed_size) {
                return [
                ];
        $dest_ext = $file->getExt();

        if (!$destination) {
    /**
     * Загружает файл по ссылке
     *
     * @param string $post_filename Название поля с файлом в массиве $_POST
     * @param int $allowed_size Максимальный размер файла (в байтах)
     * @param string $destination Директория назначения (внутри пути upload)
     * @return array
     */
    public function uploadFromLink($post_filename, $allowed_size = 0, $destination = false) {
        $curl = curl_init();
        curl_setopt($curl, CURLOPT_URL, $link);
        curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($curl, CURLOPT_HEADER, true);
        curl_setopt($curl, CURLOPT_NOBODY, true);
        curl_setopt($curl, CURLOPT_TIMEOUT, 5);
        $headers = curl_exec($curl);
        curl_close($curl);

        $matches = [];
        if (preg_match("/(?:Location:|URI:)([^\n]+)*/is", $headers, $matches)) {

            $url = trim($matches[1]);

            if (strpos($url, 'http') !== 0) {
                $url_data = parse_url($link);
                $link     = $url_data['scheme'] . '://' . $url_data['host'] . $url;
            } else {
                $link = $url;
            $_POST[$post_filename] = $link;
            return $this->uploadFromLink($post_filename, $allowed_size, $destination);
        }

        if (preg_match('#filename="([^"]+)#uis', $headers, $matches)) {
            $file_name = trim($matches[1]);
        $dest_name = files_sanitize_name($file_name);

        if (!$file_bin) {
            return [
                'error'   => LANG_UPLOAD_ERR_NO_FILE,
            ];
        return $this->saveFileFromString($file_bin, $allowed_size, $destination, $dest_name);
    }
    /**
     * Сохраняет файл из php://input в целевую директорию  и отслеживает ошибки
     *
     * @param string $filename Название поля с файлом в массиве $_GET
     * @param int $allowed_size Максимальный размер файла (в байтах)
     * @param string $destination Директория назначения (внутри пути upload)
     * @return array
     */
    public function uploadXHR($filename, $allowed_size = 0, $destination = false) {
        $dest_name = files_sanitize_name($_GET['qqfile']);
        $file_bin = file_get_contents('php://input');
        if (!$file_bin) {
            return [
                'success' => false,
                'error'   => LANG_UPLOAD_ERR_NO_FILE,
                'name'    => $dest_name,
                'path'    => ''
            ];
        return $this->saveFileFromString($file_bin, $allowed_size, $destination, $dest_name);
     * Сохраняет файл из строки данных
     *
     * @param string $file_bin
     * @param int $allowed_size Максимальный размер файла (в байтах)
     * @param string $destination Директория назначения (внутри пути upload)
     * @param string $dest_name Имя файла
    private function saveFileFromString($file_bin, $allowed_size, $destination, $dest_name) {
        $file = new cmsUploadfile($file_bin, $this->allowed_mime);
        if (!$file->isAllowed()) {
            return [
                'error'   => LANG_UPLOAD_ERR_MIME . '. ' . sprintf(LANG_PARSER_FILE_EXTS_FIELD_HINT, implode(', ', $this->allowed_mime_ext)),
            ];
        $dest_ext = $file->getExt();

        $file_size = strlen($file_bin);

        if ($allowed_size) {
            if ($file_size > $allowed_size) {
                return [
                ];
        if (!$destination) {
        if (!is_writable(dirname($destination))) {
            return [
                'name'    => $dest_name,
            ];
        if(file_put_contents($destination, $file_bin) === false){
            return [
                'error'   => LANG_UPLOAD_ERR_CANT_WRITE,
                'name'    => $dest_name,
            ];
        return [
            'name'    => basename($destination),
            'size'    => $file_size
        ];
     * Копирует файл из временной директории в целевую и отслеживает ошибки
     *
     * @return array
    private function moveUploadedFile($source, $destination, $errorCode, $orig_name = '', $orig_size = 0) {
        if ($errorCode !== UPLOAD_ERR_OK && isset($this->upload_errors[$errorCode])) {
            return [
            ];
        if (!is_writable($upload_dir)) {
            @chmod($upload_dir, 0777);
        }

        if (!is_writable($upload_dir)) {
            return [
            ];
        return [
        ];
    public function remove($file_path) {
        return files_delete_file($file_path, 2);
    public function getUploadDestinationDirectory() {
<?php

class cmsUploadfile {

    private $file_mime;
    private $mime_types;
    private $allowed_mime;

    public function __construct($file_path, $allowed_mime = null) {

        $this->allowed_mime = $allowed_mime;

        $this->mime_types = (new cmsConfigs('mimetypes.php'))->getAll();

        $finfo = finfo_open(FILEINFO_MIME_TYPE);

        if(strpos($file_path, DIRECTORY_SEPARATOR) === 0){

            $this->file_mime = finfo_file($finfo, $file_path);

        } else {

            $this->file_mime = finfo_buffer($finfo, $file_path);
        }

        finfo_close($finfo);
    }

    public function getMime() {
        return $this->file_mime;
    }

    public function getExt() {

        if ($this->file_mime && isset($this->mime_types[$this->file_mime])) {

            return $this->mime_types[$this->file_mime];
        }

        return 'bin';
    }

    public function isAllowed() {

        // Пускаем любые, если не указано
        if ($this->allowed_mime === null) {
            return true;
        }

        if ($this->file_mime === false) {
            return false;
        }

        return in_array($this->file_mime, $this->allowed_mime, true);
    }

}