From e0db5707b69b70668b2775e694b90fdd8235762f Mon Sep 17 00:00:00 2001 From: Kijin Sung Date: Sat, 17 Dec 2016 19:32:47 +0900 Subject: [PATCH 01/14] Remove unnecessary parameters from upload request --- modules/editor/skins/ckeditor/file_upload.html | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/modules/editor/skins/ckeditor/file_upload.html b/modules/editor/skins/ckeditor/file_upload.html index 68f445da9..d15304289 100644 --- a/modules/editor/skins/ckeditor/file_upload.html +++ b/modules/editor/skins/ckeditor/file_upload.html @@ -56,7 +56,8 @@ // uploader var setting = { maxFileSize: {$file_config->allowed_filesize}, - limitMultiFileUploadSize: {$file_config->allowed_filesize} + maxChunkSize: 0, + singleFileUploads: true }; $container = $('#xefu-container-{$editor_sequence}'); $container.data('instance',$container.xeUploader(setting)); From f313e693c47c70dbc647c3cc296f0c1bf5096f9d Mon Sep 17 00:00:00 2001 From: Kijin Sung Date: Sat, 17 Dec 2016 19:33:05 +0900 Subject: [PATCH 02/14] Remove potentially troublesome symlink js --- common/js/plugins/jquery.fileupload/js/main.js | 5 +---- common/js/plugins/jquery.fileupload/js/main.min.js | 2 +- 2 files changed, 2 insertions(+), 5 deletions(-) mode change 120000 => 100644 common/js/plugins/jquery.fileupload/js/main.min.js diff --git a/common/js/plugins/jquery.fileupload/js/main.js b/common/js/plugins/jquery.fileupload/js/main.js index 042cbe45e..8105596d8 100644 --- a/common/js/plugins/jquery.fileupload/js/main.js +++ b/common/js/plugins/jquery.fileupload/js/main.js @@ -64,10 +64,7 @@ if(location.protocol == 'https:') { window.enforce_ssl = true; } var settings = { - url: request_uri - .setQuery('module', 'file') - .setQuery('act', 'procFileUpload') - .setQuery('mid', window.current_mid), + url: request_uri, formData: { "editor_sequence": data.editorSequence, "upload_target_srl" : data.uploadTargetSrl, diff --git a/common/js/plugins/jquery.fileupload/js/main.min.js b/common/js/plugins/jquery.fileupload/js/main.min.js deleted file mode 120000 index 82df3461d..000000000 --- a/common/js/plugins/jquery.fileupload/js/main.min.js +++ /dev/null @@ -1 +0,0 @@ -main.js \ No newline at end of file diff --git a/common/js/plugins/jquery.fileupload/js/main.min.js b/common/js/plugins/jquery.fileupload/js/main.min.js new file mode 100644 index 000000000..52fd46a34 --- /dev/null +++ b/common/js/plugins/jquery.fileupload/js/main.min.js @@ -0,0 +1 @@ +This file is not used in Rhymix. From d34ef5901f6fb2899668d8759145c58549b0489a Mon Sep 17 00:00:00 2001 From: Kijin Sung Date: Sat, 17 Dec 2016 20:49:10 +0900 Subject: [PATCH 03/14] Improve FileHandler::returnBytes() to handle a wider range of sizes --- classes/file/FileHandler.class.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/classes/file/FileHandler.class.php b/classes/file/FileHandler.class.php index a1bdb9695..bd39a47a7 100644 --- a/classes/file/FileHandler.class.php +++ b/classes/file/FileHandler.class.php @@ -426,11 +426,14 @@ class FileHandler */ public static function returnBytes($val) { + $val = preg_replace('/[^0-9\.PTGMK]/', '', $val); $unit = strtoupper(substr($val, -1)); $val = (float)$val; switch ($unit) { + case 'P': $val *= 1024; + case 'T': $val *= 1024; case 'G': $val *= 1024; case 'M': $val *= 1024; case 'K': $val *= 1024; From ef27f8c0c414acee5c158d7fa80fc82984865fbc Mon Sep 17 00:00:00 2001 From: Kijin Sung Date: Sat, 17 Dec 2016 20:49:57 +0900 Subject: [PATCH 04/14] Add chunk size setting to editor/uploader --- modules/editor/editor.model.php | 2 ++ modules/editor/skins/ckeditor/file_upload.html | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/modules/editor/editor.model.php b/modules/editor/editor.model.php index c350d9e55..84f5b87c5 100644 --- a/modules/editor/editor.model.php +++ b/modules/editor/editor.model.php @@ -305,6 +305,8 @@ class editorModel extends editor $file_config = $oFileModel->getUploadConfig(); $file_config->allowed_attach_size = $file_config->allowed_attach_size*1024*1024; $file_config->allowed_filesize = $file_config->allowed_filesize*1024*1024; + $file_config->allowed_chunk_size = min(FileHandler::returnBytes(ini_get('upload_max_filesize')), FileHandler::returnBytes(ini_get('post_max_size')) * 0.95, 100 * 1024 * 1024); + $file_config->allowed_chunk_size = floor($file_config->allowed_chunk_size / 131072) * 131072; Context::set('file_config',$file_config); // Configure upload status such as file size diff --git a/modules/editor/skins/ckeditor/file_upload.html b/modules/editor/skins/ckeditor/file_upload.html index d15304289..918b6b69d 100644 --- a/modules/editor/skins/ckeditor/file_upload.html +++ b/modules/editor/skins/ckeditor/file_upload.html @@ -56,7 +56,7 @@ // uploader var setting = { maxFileSize: {$file_config->allowed_filesize}, - maxChunkSize: 0, + maxChunkSize: {$file_config->allowed_chunk_size}, singleFileUploads: true }; $container = $('#xefu-container-{$editor_sequence}'); From e2895912e5d8e8fc77cc365df123d894ff8b7eb8 Mon Sep 17 00:00:00 2001 From: Kijin Sung Date: Sat, 17 Dec 2016 20:50:43 +0900 Subject: [PATCH 05/14] Implement chunked file uploads on the server side --- modules/file/file.controller.php | 97 ++++++++++++++++++++++++++++---- 1 file changed, 87 insertions(+), 10 deletions(-) diff --git a/modules/file/file.controller.php b/modules/file/file.controller.php index 2719cfc7a..bd2be70a9 100644 --- a/modules/file/file.controller.php +++ b/modules/file/file.controller.php @@ -29,21 +29,98 @@ class fileController extends file $file_info = $_FILES['Filedata']; // An error appears if not a normally uploaded file - if(!is_uploaded_file($file_info['tmp_name'])) exit(); + if(!$file_info || !is_uploaded_file($file_info['tmp_name'])) exit(); // Basic variables setting $oFileModel = getModel('file'); $editor_sequence = Context::get('editor_sequence'); - $upload_target_srl = intval(Context::get('uploadTargetSrl')); - if(!$upload_target_srl) $upload_target_srl = intval(Context::get('upload_target_srl')); $module_srl = $this->module_srl; + // Exit a session if there is neither upload permission nor information - if(!$_SESSION['upload_info'][$editor_sequence]->enabled) exit(); - // Extract from session information if upload_target_srl is not specified - if(!$upload_target_srl) $upload_target_srl = $_SESSION['upload_info'][$editor_sequence]->upload_target_srl; - // Create if upload_target_srl is not defined in the session information - if(!$upload_target_srl) $_SESSION['upload_info'][$editor_sequence]->upload_target_srl = $upload_target_srl = getNextSequence(); - + if(!$_SESSION['upload_info'][$editor_sequence]->enabled) + { + return new Object(-1, 'msg_not_permitted'); + } + + // Get upload_target_srl + $upload_target_srl = intval(Context::get('uploadTargetSrl')) ?: intval(Context::get('upload_target_srl')); + if (!$upload_target_srl) + { + $upload_target_srl = $_SESSION['upload_info'][$editor_sequence]->upload_target_srl; + } + if (!$upload_target_srl) + { + $upload_target_srl = getNextSequence(); + $_SESSION['upload_info'][$editor_sequence]->upload_target_srl = $upload_target_srl; + } + + // Handle chunking + if (preg_match('!^bytes (\d+)-(\d+)/(\d+)$!', $_SERVER['HTTP_CONTENT_RANGE'], $matches)) + { + // Check basic sanity + $chunk_start = $matches[1]; + $chunk_size = ($matches[2] - $matches[1]) + 1; + $total_size = $matches[3]; + if ($chunk_start < 0 || $chunk_size < 0 || $total_size < 0 || $chunk_start + $chunk_size > $total_size || $chunk_size != $file_info['size']) + { + return new Object(-1, 'msg_upload_invalid_chunk'); + } + + // Check existing chunks + $temp_key = hash_hmac('sha1', sprintf('%d:%d:%d:%s', $editor_sequence, $upload_target_srl, $module_srl, $file_info['name']), config('crypto.authentication_key')); + $temp_filename = RX_BASEDIR . 'files/attach/chunks/' . $temp_key; + if ($chunk_start == 0 && Rhymix\Framework\Storage::isFile($temp_filename)) + { + Rhymix\Framework\Storage::delete($temp_filename); + return new Object(-1, 'msg_upload_invalid_chunk'); + } + if ($chunk_start != 0 && (!Rhymix\Framework\Storage::isFile($temp_filename) || Rhymix\Framework\Storage::getSize($temp_filename) != $chunk_start)) + { + Rhymix\Framework\Storage::delete($temp_filename); + return new Object(-1, 'msg_upload_invalid_chunk'); + } + + // Check size limit + $is_admin = (Context::get('logged_info')->is_admin === 'Y'); + if (!$is_admin) + { + $module_config = getModel('file')->getFileConfig($module_srl); + $allowed_attach_size = $module_config->allowed_attach_size * 1024 * 1024; + $allowed_filesize = $module_config->allowed_filesize * 1024 * 1024; + if ($total_size > $allowed_filesize) + { + return new Object(-1, 'msg_exceeds_limit_size'); + } + $output = executeQuery('file.getAttachedFileSize', (object)array('upload_target_srl' => $upload_target_srl)); + if (intval($output->data->attached_size) + $total_size > $allowed_attach_size) + { + return new Object(-1, 'msg_exceeds_limit_size'); + } + } + + // Append to chunk + $fp = fopen($file_info['tmp_name'], 'r'); + $success = Rhymix\Framework\Storage::write($temp_filename, $fp, 'a'); + if ($success && Rhymix\Framework\Storage::getSize($temp_filename) == $chunk_start + $chunk_size) + { + if ($chunk_start + $chunk_size == $total_size) + { + $file_info['tmp_name'] = $temp_filename; + $file_info['size'] = Rhymix\Framework\Storage::getSize($temp_filename); + } + else + { + return new Object(); + } + } + else + { + Rhymix\Framework\Storage::delete($temp_filename); + return new Object(-1, 'msg_upload_chunk_append_failed'); + } + } + + // Save the file $output = $this->insertFile($file_info, $module_srl, $upload_target_srl); Context::setResponseMethod('JSON'); @@ -752,7 +829,7 @@ class fileController extends file } // Move the file - if($manual_insert) + if($manual_insert || starts_with(RX_BASEDIR . 'files/attach/chunks/', $file_info['tmp_name'])) { @copy($file_info['tmp_name'], $filename); if(!file_exists($filename)) From 193fab949d4d601dccecbfa9bbed86b0e9e4b509 Mon Sep 17 00:00:00 2001 From: Kijin Sung Date: Sat, 17 Dec 2016 20:51:10 +0900 Subject: [PATCH 06/14] Eliminate php.ini restrictions on all file sizes --- modules/file/file.admin.controller.php | 4 +++- modules/file/file.model.php | 4 ++++ modules/file/tpl/file_module_config.html | 5 ++--- 3 files changed, 9 insertions(+), 4 deletions(-) diff --git a/modules/file/file.admin.controller.php b/modules/file/file.admin.controller.php index 212800d70..31fc03b7e 100644 --- a/modules/file/file.admin.controller.php +++ b/modules/file/file.admin.controller.php @@ -134,6 +134,7 @@ class fileAdminController extends file else $file_config->download_grant = $download_grant; //관리자가 허용한 첨부파일의 사이즈가 php.ini의 값보다 큰지 확인하기 - by ovclas + /* $userFileAllowSize = FileHandler::returnbytes($file_config->allowed_filesize.'M'); $userAttachAllowSize = FileHandler::returnbytes($file_config->allowed_attach_size.'M'); $iniPostMaxSize = FileHandler::returnbytes(ini_get('post_max_size')); @@ -142,7 +143,8 @@ class fileAdminController extends file if($userFileAllowSize > $iniMinSzie || $userAttachAllowSize > $iniMinSzie) return new Object(-1, 'input size over than config in php.ini'); - + */ + $oModuleController = getController('module'); for($i=0;$iallow_outlink) $config->allow_outlink = 'Y'; if(!$config->download_grant) $config->download_grant = array(); + /* $size = ini_get('upload_max_filesize'); $unit = strtolower($size[strlen($size) - 1]); $size = (float)$size; @@ -189,6 +190,7 @@ class fileModel extends file { $config->allowed_attach_size = $size; } + */ return $config; } @@ -286,11 +288,13 @@ class fileModel extends file if($logged_info->is_admin == 'Y') { + /* $iniPostMaxSize = FileHandler::returnbytes(ini_get('post_max_size')); $iniUploadMaxSize = FileHandler::returnbytes(ini_get('upload_max_filesize')); $size = min($iniPostMaxSize, $iniUploadMaxSize) / 1048576; $file_config->allowed_attach_size = $size; $file_config->allowed_filesize = $size; + */ $file_config->allowed_filetypes = '*.*'; } return $file_config; diff --git a/modules/file/tpl/file_module_config.html b/modules/file/tpl/file_module_config.html index 2454a464f..8d80eb05a 100644 --- a/modules/file/tpl/file_module_config.html +++ b/modules/file/tpl/file_module_config.html @@ -10,15 +10,14 @@
- MB + MB

{$lang->about_allowed_filesize}

- MB - / {ini_get('upload_max_filesize')} + MB

{$lang->about_allowed_attach_size}

From 13b14dc7f588973efbc5dba5e916e8a353ffb8a0 Mon Sep 17 00:00:00 2001 From: Kijin Sung Date: Sat, 17 Dec 2016 21:37:30 +0900 Subject: [PATCH 07/14] Improve debugging and error handling for chunked file uploads --- .../js/plugins/jquery.fileupload/js/main.js | 77 +++++++++++++++---- modules/editor/editor.model.php | 11 ++- modules/file/file.controller.php | 35 +++++++-- modules/file/lang/en.php | 1 + modules/file/lang/ko.php | 1 + 5 files changed, 103 insertions(+), 22 deletions(-) diff --git a/common/js/plugins/jquery.fileupload/js/main.js b/common/js/plugins/jquery.fileupload/js/main.js index 8105596d8..1939ea164 100644 --- a/common/js/plugins/jquery.fileupload/js/main.js +++ b/common/js/plugins/jquery.fileupload/js/main.js @@ -62,21 +62,24 @@ var currentEnforce_ssl = window.enforce_ssl; if(location.protocol == 'https:') { window.enforce_ssl = true; } + + var chunkStatus = true; + var defaultFormData = { + "editor_sequence": data.editorSequence, + "upload_target_srl" : data.uploadTargetSrl, + "mid" : window.current_mid, + "act": 'procFileUpload' + }; var settings = { url: request_uri, - formData: { - "editor_sequence": data.editorSequence, - "upload_target_srl" : data.uploadTargetSrl, - "mid" : window.current_mid, - "act": 'procFileUpload' - }, + formData: defaultFormData, dropZone: $container, add: function(e, d) { var dfd = jQuery.Deferred(); $.each(d.files, function(index, file) { - if(data.settings.maxFileSize <= file.size) { + if(data.settings.maxFileSize > 0 && data.settings.maxFileSize < file.size) { dfd.reject(); alert(window.xe.msg_exceeds_limit_size); return false; @@ -88,15 +91,51 @@ d.submit(); }); }, + submit: function(e, data) { + data.formData = defaultFormData; + data.formData.nonce = "T" + new Date().getTime() + "." + Math.random(); + chunkStatus = true; + }, + chunksend: function(e, data) { + if (!chunkStatus) { + return false; + } + }, + chunkdone: function(e, res) { + if (res.result) { + if (res.result.error != 0) { + if (res.result.message) { + alert(res.result.message); + } else { + alert(window.xe.msg_file_upload_error + " (Type 1)"); + } + return chunkStatus = false; + } + } else { + alert(window.xe.msg_file_upload_error + " (Type 2)"); + return chunkStatus = false; + } + }, + chunkfail: function(e, data) { + if (chunkStatus) { + alert(window.xe.msg_file_upload_error + " (Type 3)" + "\n" + data.errorThrown + "\n" + data.textStatus); + return chunkStatus = false; + } + }, done: function(e, res) { var result = res.response().result; var temp_code = ''; - - if(!result) return; - - if(!jQuery.isPlainObject(result)) result = jQuery.parseJSON(result); - - if(!result) return; + if (!result) { + alert(window.xe.msg_file_upload_error + " (Type 4)"); + return false; + } + if (!jQuery.isPlainObject(result)) { + result = jQuery.parseJSON(result); + } + if (!result) { + alert(window.xe.msg_file_upload_error + " (Type 5)"); + return false; + } if(result.error == 0) { if(/\.(jpe?g|png|gif)$/i.test(result.source_filename)) { @@ -105,8 +144,18 @@ } _getCkeInstance(settings.formData.editor_sequence).insertHtml(temp_code, "unfiltered_html"); - } else { + } else if (result.message) { alert(result.message); + return false; + } else { + alert(window.xe.msg_file_upload_error + " (Type 6)"); + return false; + } + }, + fail: function(e, data) { + if (chunkStatus) { + alert(window.xe.msg_file_upload_error + " (Type 7)" + "\n" + data.errorThrown + "\n" + data.textStatus); + return false; } }, stop: function() { diff --git a/modules/editor/editor.model.php b/modules/editor/editor.model.php index 84f5b87c5..8f35c773a 100644 --- a/modules/editor/editor.model.php +++ b/modules/editor/editor.model.php @@ -305,8 +305,15 @@ class editorModel extends editor $file_config = $oFileModel->getUploadConfig(); $file_config->allowed_attach_size = $file_config->allowed_attach_size*1024*1024; $file_config->allowed_filesize = $file_config->allowed_filesize*1024*1024; - $file_config->allowed_chunk_size = min(FileHandler::returnBytes(ini_get('upload_max_filesize')), FileHandler::returnBytes(ini_get('post_max_size')) * 0.95, 100 * 1024 * 1024); - $file_config->allowed_chunk_size = floor($file_config->allowed_chunk_size / 131072) * 131072; + $file_config->allowed_chunk_size = min(FileHandler::returnBytes(ini_get('upload_max_filesize')), FileHandler::returnBytes(ini_get('post_max_size')) * 0.95, 64 * 1024 * 1024); + if ($file_config->allowed_chunk_size > 4 * 1048576) + { + $file_config->allowed_chunk_size = floor($file_config->allowed_chunk_size / 1048576) * 1048576; + } + else + { + $file_config->allowed_chunk_size = floor($file_config->allowed_chunk_size / 65536) * 65536; + } Context::set('file_config',$file_config); // Configure upload status such as file size diff --git a/modules/file/file.controller.php b/modules/file/file.controller.php index bd2be70a9..3f5be2051 100644 --- a/modules/file/file.controller.php +++ b/modules/file/file.controller.php @@ -67,7 +67,8 @@ class fileController extends file } // Check existing chunks - $temp_key = hash_hmac('sha1', sprintf('%d:%d:%d:%s', $editor_sequence, $upload_target_srl, $module_srl, $file_info['name']), config('crypto.authentication_key')); + $nonce = Context::get('nonce'); + $temp_key = hash_hmac('sha1', sprintf('%d:%d:%d:%s:%s', $editor_sequence, $upload_target_srl, $module_srl, $file_info['name'], $nonce), config('crypto.authentication_key')); $temp_filename = RX_BASEDIR . 'files/attach/chunks/' . $temp_key; if ($chunk_start == 0 && Rhymix\Framework\Storage::isFile($temp_filename)) { @@ -79,6 +80,11 @@ class fileController extends file Rhymix\Framework\Storage::delete($temp_filename); return new Object(-1, 'msg_upload_invalid_chunk'); } + if ($chunk_start >= 2 * 1024 * 1024) + { + Rhymix\Framework\Storage::delete($temp_filename); + return new Object(-1, 'msg_upload_invalid_chunk'); + } // Check size limit $is_admin = (Context::get('logged_info')->is_admin === 'Y'); @@ -116,7 +122,7 @@ class fileController extends file else { Rhymix\Framework\Storage::delete($temp_filename); - return new Object(-1, 'msg_upload_chunk_append_failed'); + return new Object(-1, 'msg_upload_invalid_chunk'); } } @@ -829,23 +835,40 @@ class fileController extends file } // Move the file - if($manual_insert || starts_with(RX_BASEDIR . 'files/attach/chunks/', $file_info['tmp_name'])) + $filename = $path . Rhymix\Framework\Security::getRandom(32, 'hex') . '.' . $ext; + if($manual_insert) { @copy($file_info['tmp_name'], $filename); if(!file_exists($filename)) { - $filename = $path . Rhymix\Framework\Security::getRandom(32, 'hex') . '.' . $ext; @copy($file_info['tmp_name'], $filename); + if(!file_exists($filename)) + { + return new Object(-1,'msg_file_upload_error'); + } + } + } + elseif(starts_with(RX_BASEDIR . 'files/attach/chunks/', $file_info['tmp_name'])) + { + if (!Rhymix\Framework\Storage::move($file_info['tmp_name'], $filename)) + { + if (!Rhymix\Framework\Storage::move($file_info['tmp_name'], $filename)) + { + return new Object(-1,'msg_file_upload_error'); + } } } else { if(!@move_uploaded_file($file_info['tmp_name'], $filename)) { - $filename = $path . Rhymix\Framework\Security::getRandom(32, 'hex') . '.' . $ext; - if(!@move_uploaded_file($file_info['tmp_name'], $filename)) return new Object(-1,'msg_file_upload_error'); + if(!@move_uploaded_file($file_info['tmp_name'], $filename)) + { + return new Object(-1,'msg_file_upload_error'); + } } } + // Get member information $oMemberModel = getModel('member'); $member_srl = $oMemberModel->getLoggedMemberSrl(); diff --git a/modules/file/lang/en.php b/modules/file/lang/en.php index b9321ddea..56f452543 100644 --- a/modules/file/lang/en.php +++ b/modules/file/lang/en.php @@ -44,6 +44,7 @@ $lang->file_search_target_list['isvalid'] = 'Status'; $lang->msg_not_allowed_outlink = 'It is not allowed to download files from sites other than this.'; $lang->msg_not_permitted_create = 'Failed to create a file or directory.'; $lang->msg_file_upload_error = 'An error has occurred during uploading.'; +$lang->msg_upload_invalid_chunk = 'An error has occurred during chunked uploading.'; $lang->no_files = 'No Files'; $lang->file_manager = 'Manage selected files'; $lang->selected_file = 'Selected files'; diff --git a/modules/file/lang/ko.php b/modules/file/lang/ko.php index c115b4d7c..541f392b1 100644 --- a/modules/file/lang/ko.php +++ b/modules/file/lang/ko.php @@ -45,6 +45,7 @@ $lang->file_search_target_list['isvalid'] = '상태'; $lang->msg_not_allowed_outlink = '외부링크에서 다운로드할 수 없습니다.'; $lang->msg_not_permitted_create = '파일 또는 디렉터리를 생성할 수 없습니다.'; $lang->msg_file_upload_error = '파일 업로드 중 에러가 발생하였습니다.'; +$lang->msg_upload_invalid_chunk = '분할 업로드 처리 중 오류가 발생하였습니다.'; $lang->no_files = '파일이 없습니다.'; $lang->file_manager = '선택한 파일 관리'; $lang->selected_file = '선택한 파일'; From d69007e200c6f6fb5886a99b029baa0dfaea4ec0 Mon Sep 17 00:00:00 2001 From: Kijin Sung Date: Sat, 17 Dec 2016 21:54:00 +0900 Subject: [PATCH 08/14] Improve debugging --- .../js/plugins/jquery.fileupload/js/main.js | 8 ++-- modules/file/file.controller.php | 38 +++++++++++-------- 2 files changed, 27 insertions(+), 19 deletions(-) diff --git a/common/js/plugins/jquery.fileupload/js/main.js b/common/js/plugins/jquery.fileupload/js/main.js index 1939ea164..029cf3474 100644 --- a/common/js/plugins/jquery.fileupload/js/main.js +++ b/common/js/plugins/jquery.fileupload/js/main.js @@ -118,7 +118,7 @@ }, chunkfail: function(e, data) { if (chunkStatus) { - alert(window.xe.msg_file_upload_error + " (Type 3)" + "\n" + data.errorThrown + "\n" + data.textStatus); + alert(window.xe.msg_file_upload_error + " (Type 3)" + "
\n" + data.errorThrown + "
\n" + data.textStatus); return chunkStatus = false; } }, @@ -133,7 +133,7 @@ result = jQuery.parseJSON(result); } if (!result) { - alert(window.xe.msg_file_upload_error + " (Type 5)"); + alert(window.xe.msg_file_upload_error + " (Type 5)" + "
\n" + res.response().result); return false; } @@ -148,13 +148,13 @@ alert(result.message); return false; } else { - alert(window.xe.msg_file_upload_error + " (Type 6)"); + alert(window.xe.msg_file_upload_error + " (Type 6)" + "
\n" + res.response().result); return false; } }, fail: function(e, data) { if (chunkStatus) { - alert(window.xe.msg_file_upload_error + " (Type 7)" + "\n" + data.errorThrown + "\n" + data.textStatus); + alert(window.xe.msg_file_upload_error + " (Type 7)" + "
\n" + data.errorThrown + "
\n" + data.textStatus); return false; } }, diff --git a/modules/file/file.controller.php b/modules/file/file.controller.php index 3f5be2051..468869178 100644 --- a/modules/file/file.controller.php +++ b/modules/file/file.controller.php @@ -58,13 +58,15 @@ class fileController extends file if (preg_match('!^bytes (\d+)-(\d+)/(\d+)$!', $_SERVER['HTTP_CONTENT_RANGE'], $matches)) { // Check basic sanity - $chunk_start = $matches[1]; + $chunk_start = intval($matches[1]); $chunk_size = ($matches[2] - $matches[1]) + 1; - $total_size = $matches[3]; + $total_size = intval($matches[3]); if ($chunk_start < 0 || $chunk_size < 0 || $total_size < 0 || $chunk_start + $chunk_size > $total_size || $chunk_size != $file_info['size']) { return new Object(-1, 'msg_upload_invalid_chunk'); } + $this->add('chunk_current_size', $chunk_size); + $this->add('chunk_uploaded_size', $chunk_start); // Check existing chunks $nonce = Context::get('nonce'); @@ -73,16 +75,13 @@ class fileController extends file if ($chunk_start == 0 && Rhymix\Framework\Storage::isFile($temp_filename)) { Rhymix\Framework\Storage::delete($temp_filename); + $this->add('chunk_status', 11); return new Object(-1, 'msg_upload_invalid_chunk'); } if ($chunk_start != 0 && (!Rhymix\Framework\Storage::isFile($temp_filename) || Rhymix\Framework\Storage::getSize($temp_filename) != $chunk_start)) { Rhymix\Framework\Storage::delete($temp_filename); - return new Object(-1, 'msg_upload_invalid_chunk'); - } - if ($chunk_start >= 2 * 1024 * 1024) - { - Rhymix\Framework\Storage::delete($temp_filename); + $this->add('chunk_status', 12); return new Object(-1, 'msg_upload_invalid_chunk'); } @@ -95,11 +94,13 @@ class fileController extends file $allowed_filesize = $module_config->allowed_filesize * 1024 * 1024; if ($total_size > $allowed_filesize) { + $this->add('chunk_status', 21); return new Object(-1, 'msg_exceeds_limit_size'); } $output = executeQuery('file.getAttachedFileSize', (object)array('upload_target_srl' => $upload_target_srl)); if (intval($output->data->attached_size) + $total_size > $allowed_attach_size) { + $this->add('chunk_status', 22); return new Object(-1, 'msg_exceeds_limit_size'); } } @@ -109,6 +110,8 @@ class fileController extends file $success = Rhymix\Framework\Storage::write($temp_filename, $fp, 'a'); if ($success && Rhymix\Framework\Storage::getSize($temp_filename) == $chunk_start + $chunk_size) { + $this->add('chunk_status', 0); + $this->add('chunk_uploaded_size', $chunk_start + $chunk_size); if ($chunk_start + $chunk_size == $total_size) { $file_info['tmp_name'] = $temp_filename; @@ -122,9 +125,14 @@ class fileController extends file else { Rhymix\Framework\Storage::delete($temp_filename); + $this->add('chunk_status', 40); return new Object(-1, 'msg_upload_invalid_chunk'); } } + else + { + $this->add('chunk_status', -1); + } // Save the file $output = $this->insertFile($file_info, $module_srl, $upload_target_srl); @@ -805,26 +813,27 @@ class fileController extends file // Set upload path by checking if the attachement is an image or other kinds of file if(preg_match("/\.(jpe?g|gif|png|wm[va]|mpe?g|avi|swf|flv|mp[1-4]|as[fx]|wav|midi?|moo?v|qt|r[am]{1,2}|m4v)$/i", $file_info['name'])) { - $path = sprintf("./files/attach/images/%s/%s", $module_srl,getNumberingPath($upload_target_srl,3)); + $path = RX_BASEDIR . sprintf("files/attach/images/%s/%s", $module_srl,getNumberingPath($upload_target_srl,3)); // special character to '_' // change to random file name. because window php bug. window php is not recognize unicode character file name - by cherryfilter $ext = substr(strrchr($file_info['name'],'.'),1); //$_filename = preg_replace('/[#$&*?+%"\']/', '_', $file_info['name']); - $_filename = Rhymix\Framework\Security::getRandom(32, 'hex') . '.' . $ext; - $filename = $path.$_filename; - $idx = 1; + $filename = $path . Rhymix\Framework\Security::getRandom(32, 'hex') . '.' . $ext; while(file_exists($filename)) { - $filename = $path.preg_replace('/\.([a-z0-9]+)$/i','_'.$idx.'.$1',$_filename); - $idx++; + $filename = $path . Rhymix\Framework\Security::getRandom(32, 'hex') . '.' . $ext; } $direct_download = 'Y'; } else { - $path = sprintf("./files/attach/binaries/%s/%s", $module_srl, getNumberingPath($upload_target_srl,3)); + $path = RX_BASEDIR . sprintf("files/attach/binaries/%s/%s", $module_srl, getNumberingPath($upload_target_srl,3)); $filename = $path . Rhymix\Framework\Security::getRandom(32, 'hex'); + while(file_exists($filename)) + { + $filename = $path . Rhymix\Framework\Security::getRandom(32, 'hex'); + } $direct_download = 'N'; } @@ -835,7 +844,6 @@ class fileController extends file } // Move the file - $filename = $path . Rhymix\Framework\Security::getRandom(32, 'hex') . '.' . $ext; if($manual_insert) { @copy($file_info['tmp_name'], $filename); From 7bf5d78206c95ecfa477e49148de170f43ee4f21 Mon Sep 17 00:00:00 2001 From: Kijin Sung Date: Sat, 17 Dec 2016 22:09:02 +0900 Subject: [PATCH 09/14] Improve documentation and error messages --- modules/editor/editor.model.php | 4 ++++ modules/editor/skins/ckeditor/file_upload.html | 2 +- modules/file/file.admin.controller.php | 7 +++++++ modules/file/lang/en.php | 6 ++++-- modules/file/lang/ko.php | 6 ++++-- modules/file/tpl/file_module_config.html | 6 +++--- 6 files changed, 23 insertions(+), 8 deletions(-) diff --git a/modules/editor/editor.model.php b/modules/editor/editor.model.php index 8f35c773a..2db3dad80 100644 --- a/modules/editor/editor.model.php +++ b/modules/editor/editor.model.php @@ -305,6 +305,10 @@ class editorModel extends editor $file_config = $oFileModel->getUploadConfig(); $file_config->allowed_attach_size = $file_config->allowed_attach_size*1024*1024; $file_config->allowed_filesize = $file_config->allowed_filesize*1024*1024; + if (PHP_INT_SIZE < 8) + { + $file_config->allowed_filesize = min($file_config->allowed_filesize, 2147483647); + } $file_config->allowed_chunk_size = min(FileHandler::returnBytes(ini_get('upload_max_filesize')), FileHandler::returnBytes(ini_get('post_max_size')) * 0.95, 64 * 1024 * 1024); if ($file_config->allowed_chunk_size > 4 * 1048576) { diff --git a/modules/editor/skins/ckeditor/file_upload.html b/modules/editor/skins/ckeditor/file_upload.html index 918b6b69d..9adadffce 100644 --- a/modules/editor/skins/ckeditor/file_upload.html +++ b/modules/editor/skins/ckeditor/file_upload.html @@ -55,7 +55,7 @@ jQuery(function($){ // uploader var setting = { - maxFileSize: {$file_config->allowed_filesize}, + maxFileSize: {$logged_info->is_admin === 'Y' ? 0 : $file_config->allowed_filesize}, maxChunkSize: {$file_config->allowed_chunk_size}, singleFileUploads: true }; diff --git a/modules/file/file.admin.controller.php b/modules/file/file.admin.controller.php index 31fc03b7e..942f19492 100644 --- a/modules/file/file.admin.controller.php +++ b/modules/file/file.admin.controller.php @@ -144,6 +144,13 @@ class fileAdminController extends file if($userFileAllowSize > $iniMinSzie || $userAttachAllowSize > $iniMinSzie) return new Object(-1, 'input size over than config in php.ini'); */ + if (PHP_INT_SIZE < 8) + { + if ($file_config->allowed_filesize > 2047 || $file_config->allowed_attach_size > 2047) + { + return new Object(-1, 'msg_32bit_max_2047mb'); + } + } $oModuleController = getController('module'); for($i=0;$ienable_download_group = 'Downloadable Groups'; $lang->about_allow_outlink = 'You can block external links according to referers(except media files like *.wmv and *.mp3).'; $lang->about_allow_outlink_format = 'These formats will always be allowed. Please use comma(,) for multiple input. eg)hwp,doc,zip,pdf'; $lang->about_allow_outlink_site = 'These websites will alyways be allowed. Please use new line for multiple input. ex)https://www.rhymix.org/'; -$lang->about_allowed_filesize = 'You can assign file size limit for each file.(Exclude administrators)'; -$lang->about_allowed_attach_size = 'You can assign file size limit for each document.(Exclude administrators)'; +$lang->about_allowed_filesize = 'You can limit the size of each attached file. Administrators are exempt.'; +$lang->about_allowed_attach_size = 'You can limit the total size of all attached files in one document. Administrators are exempt.'; +$lang->about_allowed_size_limits = 'The file size will be limited to the value set in php.ini (%sB) in IE9 and below and older Android browsers.'; $lang->about_allowed_filetypes = 'To allow an extension, use "*.[extention]". To allow multiple extensions, use ";" between each extension. ex) *.* or *.jpg;*.gif; '; $lang->cmd_delete_checked_file = 'Delete Selected Item(s)'; $lang->cmd_move_to_document = 'Move to Document'; @@ -45,6 +46,7 @@ $lang->msg_not_allowed_outlink = 'It is not allowed to download files from sites $lang->msg_not_permitted_create = 'Failed to create a file or directory.'; $lang->msg_file_upload_error = 'An error has occurred during uploading.'; $lang->msg_upload_invalid_chunk = 'An error has occurred during chunked uploading.'; +$lang->msg_32bit_max_2047mb = 'On 32-bit servers, the file size limit cannot exceed 2047MB.'; $lang->no_files = 'No Files'; $lang->file_manager = 'Manage selected files'; $lang->selected_file = 'Selected files'; diff --git a/modules/file/lang/ko.php b/modules/file/lang/ko.php index 541f392b1..b8a0fb24b 100644 --- a/modules/file/lang/ko.php +++ b/modules/file/lang/ko.php @@ -18,8 +18,9 @@ $lang->enable_download_group = '다운로드 가능 그룹'; $lang->about_allow_outlink = '리퍼러에 따라 파일 외부 링크를 차단할 수 있습니다.(*.wmv, *.mp3등 미디어 파일 제외)'; $lang->about_allow_outlink_format = '파일 외부 링크 설정에 상관없이 허용하는 파일 확장자입니다. 여러 개 입력 시에 쉼표(,)을 이용해서 구분해주세요. 예)hwp,doc,zip,pdf'; $lang->about_allow_outlink_site = '파일 외부 링크 설정에 상관없이 허용하는 사이트 주소입니다. 여러 개 입력 시에 줄을 바꿔서 구분해주세요. 예)https://www.rhymix.org/'; -$lang->about_allowed_filesize = '하나의 파일에 대해 최고 용량을 지정할 수 있습니다.(관리자는 제외)'; -$lang->about_allowed_attach_size = '하나의 문서에 첨부할 수 있는 최고 용량을 지정할 수 있습니다.(관리자는 제외)'; +$lang->about_allowed_filesize = '각 파일의 용량을 제한할 수 있습니다. 관리자에게는 적용되지 않습니다.'; +$lang->about_allowed_attach_size = '하나의 문서에 첨부할 수 있는 최대 용량을 제한할 수 있습니다. 관리자에게는 적용되지 않습니다.'; +$lang->about_allowed_size_limits = 'IE9 이하, 구버전 안드로이드 등에서는 php.ini에서 지정한 %sB로 제한됩니다.'; $lang->about_allowed_filetypes = '"*.확장자"로 지정할 수 있고 ";" 으로 여러 개 지정이 가능합니다. 예) *.* or *.jpg;*.gif;'; $lang->cmd_delete_checked_file = '선택항목 삭제'; $lang->cmd_move_to_document = '문서로 이동'; @@ -46,6 +47,7 @@ $lang->msg_not_allowed_outlink = '외부링크에서 다운로드할 수 없습 $lang->msg_not_permitted_create = '파일 또는 디렉터리를 생성할 수 없습니다.'; $lang->msg_file_upload_error = '파일 업로드 중 에러가 발생하였습니다.'; $lang->msg_upload_invalid_chunk = '분할 업로드 처리 중 오류가 발생하였습니다.'; +$lang->msg_32bit_max_2047mb = '32비트 서버에서는 파일 크기 제한이 2047MB를 초과할 수 없습니다.'; $lang->no_files = '파일이 없습니다.'; $lang->file_manager = '선택한 파일 관리'; $lang->selected_file = '선택한 파일'; diff --git a/modules/file/tpl/file_module_config.html b/modules/file/tpl/file_module_config.html index 8d80eb05a..64fb9e2c8 100644 --- a/modules/file/tpl/file_module_config.html +++ b/modules/file/tpl/file_module_config.html @@ -11,21 +11,21 @@
MB -

{$lang->about_allowed_filesize}

+

{$lang->about_allowed_filesize}
{sprintf($lang->about_allowed_size_limits, ini_get('upload_max_filesize'))}

MB -

{$lang->about_allowed_attach_size}

+

{$lang->about_allowed_attach_size}
{sprintf($lang->about_allowed_size_limits, ini_get('upload_max_filesize'))}

-

{$lang->about_allowed_filetypes}

+

{$lang->about_allowed_filetypes}

From 4f15ae24ea5980c14d3757cd6cc594cb46a8c61e Mon Sep 17 00:00:00 2001 From: Kijin Sung Date: Sat, 17 Dec 2016 22:30:51 +0900 Subject: [PATCH 10/14] Display GB and TB sizes correctly in FileHandler::filesize() --- classes/file/FileHandler.class.php | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/classes/file/FileHandler.class.php b/classes/file/FileHandler.class.php index bd39a47a7..5595def2b 100644 --- a/classes/file/FileHandler.class.php +++ b/classes/file/FileHandler.class.php @@ -256,12 +256,22 @@ class FileHandler return $size . 'Bytes'; } - if($size >= 1024 && $size < 1024 * 1024) + if($size >= 1024 && $size < (1024 * 1024)) { return sprintf("%0.1fKB", $size / 1024); } - return sprintf("%0.2fMB", $size / (1024 * 1024)); + if($size >= (1024 * 1024) && $size < (1024 * 1024 * 1024)) + { + return sprintf("%0.2fMB", $size / (1024 * 1024)); + } + + if($size >= (1024 * 1024 * 1024) && $size < (1024 * 1024 * 1024 * 1024)) + { + return sprintf("%0.2fGB", $size / (1024 * 1024 * 1024)); + } + + return sprintf("%0.2fTB", $size / (1024 * 1024 * 1024 * 1024)); } /** From b8096f6a945e8fd78a6ea9205574a44ff8825eb8 Mon Sep 17 00:00:00 2001 From: Kijin Sung Date: Sat, 17 Dec 2016 22:45:49 +0900 Subject: [PATCH 11/14] Disable chunked uploads in IE < 10, Android, Opera --- modules/editor/editor.model.php | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/modules/editor/editor.model.php b/modules/editor/editor.model.php index 2db3dad80..20c3fab5b 100644 --- a/modules/editor/editor.model.php +++ b/modules/editor/editor.model.php @@ -300,8 +300,8 @@ class editorModel extends editor $files_count = 0; if($allow_fileupload) { + // Get file upload limits $oFileModel = getModel('file'); - // Get upload configuration to set on SWFUploader $file_config = $oFileModel->getUploadConfig(); $file_config->allowed_attach_size = $file_config->allowed_attach_size*1024*1024; $file_config->allowed_filesize = $file_config->allowed_filesize*1024*1024; @@ -318,6 +318,14 @@ class editorModel extends editor { $file_config->allowed_chunk_size = floor($file_config->allowed_chunk_size / 65536) * 65536; } + + // Do not allow chunked uploads in IE < 10, Android browser, and Opera + $browser = Rhymix\Framework\UA::getBrowserInfo(); + if (($browser->browser === 'IE' && version_compare($browser->version, '10', '<')) || $browser->browser === 'Android' || $browser->browser === 'Opera') + { + $file_config->allowed_filesize = min(FileHandler::returnBytes(ini_get('upload_max_filesize')), FileHandler::returnBytes(ini_get('post_max_size'))); + $file_config->allowed_chunk_size = 0; + } Context::set('file_config',$file_config); // Configure upload status such as file size From 9d7b731b4cb3f5a138ed8333d9bb06278ed9ffbf Mon Sep 17 00:00:00 2001 From: Kijin Sung Date: Sat, 17 Dec 2016 22:50:14 +0900 Subject: [PATCH 12/14] Delete file before calling 'after' trigger --- modules/file/file.controller.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/modules/file/file.controller.php b/modules/file/file.controller.php index 468869178..b48d80047 100644 --- a/modules/file/file.controller.php +++ b/modules/file/file.controller.php @@ -985,11 +985,11 @@ class fileController extends file $output = executeQuery('file.deleteFile', $args); if(!$output->toBool()) return $output; - // Call a trigger (after) - ModuleHandler::triggerCall('file.deleteFile', 'after', $trigger_obj); - // If successfully deleted, remove the file FileHandler::removeFile($uploaded_filename); + + // Call a trigger (after) + ModuleHandler::triggerCall('file.deleteFile', 'after', $trigger_obj); } $oDocumentController->updateUploaedCount($documentSrlList); From 73d4ea842f9ee1654fbf39dec03dc27f2a6097dc Mon Sep 17 00:00:00 2001 From: Kijin Sung Date: Sat, 17 Dec 2016 23:06:59 +0900 Subject: [PATCH 13/14] Display upload progress more accurately, and don't hide the progress bar until server processing is complete --- common/js/plugins/jquery.fileupload/js/main.js | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/common/js/plugins/jquery.fileupload/js/main.js b/common/js/plugins/jquery.fileupload/js/main.js index 029cf3474..e296223e4 100644 --- a/common/js/plugins/jquery.fileupload/js/main.js +++ b/common/js/plugins/jquery.fileupload/js/main.js @@ -123,6 +123,10 @@ } }, done: function(e, res) { + data.settings.progressbarGraph.width('100%'); + data.settings.progressPercent.text('100%'); + data.settings.progressbar.delay(1000).slideUp(); + data.settings.progressStatus.delay(1000).slideUp(); var result = res.response().result; var temp_code = ''; if (!result) { @@ -153,6 +157,8 @@ } }, fail: function(e, data) { + data.settings.progressbar.delay(1000).slideUp(); + data.settings.progressStatus.delay(1000).slideUp(); if (chunkStatus) { alert(window.xe.msg_file_upload_error + " (Type 7)" + "
\n" + data.errorThrown + "
\n" + data.textStatus); return false; @@ -167,14 +173,9 @@ data.settings.progressbar.show(); }, progressall: function (e, d) { - var progress = parseInt(d.loaded / d.total * 100, 10); + var progress = Math.round(d.loaded / d.total * 999) / 10; data.settings.progressbarGraph.width(progress+'%'); data.settings.progressPercent.text(progress+'%'); - - if(progress >= 100) { - data.settings.progressbar.delay(3000).slideUp(); - data.settings.progressStatus.delay(3000).slideUp(); - } } }; window.enforce_ssl = currentEnforce_ssl; From d981b649c186b8781582b364bec7fafeae1d5399 Mon Sep 17 00:00:00 2001 From: Kijin Sung Date: Sat, 17 Dec 2016 23:44:18 +0900 Subject: [PATCH 14/14] Enforce php.ini limits when not using CKEditor + jQuery File Upload --- common/js/plugins/jquery.fileupload/js/main.js | 1 + modules/editor/editor.model.php | 7 +++++++ modules/file/file.model.php | 11 ++++++++++- 3 files changed, 18 insertions(+), 1 deletion(-) diff --git a/common/js/plugins/jquery.fileupload/js/main.js b/common/js/plugins/jquery.fileupload/js/main.js index e296223e4..21b6e38dd 100644 --- a/common/js/plugins/jquery.fileupload/js/main.js +++ b/common/js/plugins/jquery.fileupload/js/main.js @@ -343,6 +343,7 @@ var obj = {}; obj.mid = window.current_mid; obj.editor_sequence = data.editorSequence; + obj.allow_chunks = 'Y'; $.exec_json('file.getFileList', obj, function(res){ data.uploadTargetSrl = res.upload_target_srl; diff --git a/modules/editor/editor.model.php b/modules/editor/editor.model.php index 20c3fab5b..7fccc8440 100644 --- a/modules/editor/editor.model.php +++ b/modules/editor/editor.model.php @@ -326,6 +326,13 @@ class editorModel extends editor $file_config->allowed_filesize = min(FileHandler::returnBytes(ini_get('upload_max_filesize')), FileHandler::returnBytes(ini_get('post_max_size'))); $file_config->allowed_chunk_size = 0; } + + // Do not allow chunked uploads in XpressEditor. + if (starts_with($option->skin, 'xpresseditor')) + { + $file_config->allowed_filesize = min(FileHandler::returnBytes(ini_get('upload_max_filesize')), FileHandler::returnBytes(ini_get('post_max_size'))); + $file_config->allowed_chunk_size = 0; + } Context::set('file_config',$file_config); // Configure upload status such as file size diff --git a/modules/file/file.model.php b/modules/file/file.model.php index 44358e671..3b452b4d1 100644 --- a/modules/file/file.model.php +++ b/modules/file/file.model.php @@ -309,6 +309,15 @@ class fileModel extends file function getUploadStatus($attached_size = 0) { $file_config = $this->getUploadConfig(); + if (Context::get('allow_chunks') === 'Y') + { + $allowed_filesize = $file_config->allowed_filesize * 1024 * 1024; + } + else + { + $allowed_filesize = min(FileHandler::returnBytes(ini_get('upload_max_filesize')), FileHandler::returnBytes(ini_get('post_max_size'))); + } + // Display upload status $upload_status = sprintf( '%s : %s/ %s
%s : %s (%s : %s)', @@ -316,7 +325,7 @@ class fileModel extends file FileHandler::filesize($attached_size), FileHandler::filesize($file_config->allowed_attach_size*1024*1024), lang('allowed_filesize'), - FileHandler::filesize($file_config->allowed_filesize*1024*1024), + FileHandler::filesize($allowed_filesize), lang('allowed_filetypes'), $file_config->allowed_filetypes );