From 6eea773a68ab6db6a65c32c21e840e44206b45d0 Mon Sep 17 00:00:00 2001 From: Mark Ivanowich <4605655+MarkIvanowich@users.noreply.github.com> Date: Sat, 19 Apr 2025 13:38:23 -0500 Subject: [PATCH 1/6] Upload Existing File Configuration Added `$upload_name_conflict_handling` option for handling upload of files that already exist. --- tinyfilemanager.php | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/tinyfilemanager.php b/tinyfilemanager.php index d1848d9..10b9878 100644 --- a/tinyfilemanager.php +++ b/tinyfilemanager.php @@ -121,6 +121,12 @@ $max_upload_size_bytes = 5000000000; // size 5,000,000,000 bytes (~5GB) // eg. decrease to 1MB if nginx reports problem 413 entity too large $upload_chunk_size_bytes = 2000000; // chunk size 2,000,000 bytes (~2MB) +// Handle file uploads where the file exists +// NEW => newly uploaded file is renamed with a timestamp appended +// OLD => old file will be renamed with a timestamp appended +// REPLACE => old file will be deleted +$upload_name_conflict_handling = 'OLD'; + // Possible rules are 'OFF', 'AND' or 'OR' // OFF => Don't check connection IP, defaults to OFF // AND => Connection must be on the whitelist, and not on the blacklist @@ -1045,11 +1051,21 @@ if (!empty($_FILES) && !FM_READONLY) { } if ($chunkIndex == $chunkTotal - 1) { + $fullPathTarget = $fullPath; if (file_exists($fullPath)) { $ext_1 = $ext ? '.' . $ext : ''; - $fullPathTarget = $path . '/' . basename($fullPathInput, $ext_1) . '_' . date('ymdHis') . $ext_1; - } else { - $fullPathTarget = $fullPath; + $datedPath = $path . '/' . basename($fullPathInput, $ext_1) . '_' . date('ymdHis') . $ext_1; + switch($upload_name_conflict_handling) + { + case 'OLD': + rename($fullPath,$datedPath); + break; + case 'REPLACE': + if( fm_rdelete($fullPath) ) break; + case 'NEW': + default: + $fullPathTarget = $datedPath; + } } rename("{$fullPath}.part", $fullPathTarget); } From 0150471ef9f53e1c2338676c1ebc75d3a8c7f57b Mon Sep 17 00:00:00 2001 From: Mark Ivanowich <4605655+MarkIvanowich@users.noreply.github.com> Date: Sat, 19 Apr 2025 14:33:53 -0500 Subject: [PATCH 2/6] Security for File Upload Conflicts Additional checks to ensure an uploaded file is not on the `exclude_items` list. --- tinyfilemanager.php | 26 +++++++++++++++----------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/tinyfilemanager.php b/tinyfilemanager.php index 10b9878..fc2cdbd 100644 --- a/tinyfilemanager.php +++ b/tinyfilemanager.php @@ -1055,19 +1055,23 @@ if (!empty($_FILES) && !FM_READONLY) { if (file_exists($fullPath)) { $ext_1 = $ext ? '.' . $ext : ''; $datedPath = $path . '/' . basename($fullPathInput, $ext_1) . '_' . date('ymdHis') . $ext_1; - switch($upload_name_conflict_handling) - { - case 'OLD': - rename($fullPath,$datedPath); - break; - case 'REPLACE': - if( fm_rdelete($fullPath) ) break; - case 'NEW': - default: - $fullPathTarget = $datedPath; + if(fm_is_exclude_items($fullPath)){ + $fullPathTarget = $datedPath; // excluded items should not be replaced or renamed + }else{ + switch($upload_name_conflict_handling) + { + case 'OLD': + fm_rename($fullPath,$datedPath); + break; + case 'REPLACE': + if(fm_rdelete($fullPath)) break; + case 'NEW': + default: + $fullPathTarget = $datedPath; + } } } - rename("{$fullPath}.part", $fullPathTarget); + fm_rename("{$fullPath}.part", $fullPathTarget); } } else if (move_uploaded_file($tmp_name, $fullPath)) { // Be sure that the file has been uploaded From ce1d720f4ef0336d0d865671a4eadf1ecaf735d7 Mon Sep 17 00:00:00 2001 From: Mark Date: Sun, 27 Apr 2025 13:23:01 -0500 Subject: [PATCH 3/6] Refactor fm_is_exclude_items to fm_is_excluded for clearer name and operation --- tinyfilemanager.php | 29 +++++++++++++---------------- 1 file changed, 13 insertions(+), 16 deletions(-) diff --git a/tinyfilemanager.php b/tinyfilemanager.php index fc2cdbd..27c868a 100644 --- a/tinyfilemanager.php +++ b/tinyfilemanager.php @@ -1354,7 +1354,7 @@ $objects = is_readable($path) ? scandir($path) : array(); $folders = array(); $files = array(); $current_path = array_slice(explode("/", $path), -1)[0]; -if (is_array($objects) && fm_is_exclude_items($current_path, $path)) { +if (is_array($objects) && !fm_is_excluded($current_path, $path)) { foreach ($objects as $file) { if ($file == '.' || $file == '..') { continue; @@ -1363,9 +1363,9 @@ if (is_array($objects) && fm_is_exclude_items($current_path, $path)) { continue; } $new_path = $path . '/' . $file; - if (@is_file($new_path) && fm_is_exclude_items($file, $new_path)) { + if (@is_file($new_path) && !fm_is_excluded($file, $new_path)) { $files[] = $file; - } elseif (@is_dir($new_path) && $file != '.' && $file != '..' && fm_is_exclude_items($file, $new_path)) { + } elseif (@is_dir($new_path) && $file != '.' && $file != '..' && !fm_is_excluded($file, $new_path)) { $folders[] = $file; } } @@ -1732,7 +1732,7 @@ if (isset($_GET['view'])) { $file = $_GET['view']; $file = fm_clean_path($file, false); $file = str_replace('/', '', $file); - if ($file == '' || !is_file($path . '/' . $file) || !fm_is_exclude_items($file, $path . '/' . $file)) { + if ($file == '' || !is_file($path . '/' . $file) || fm_is_excluded($file, $path . '/' . $file)) { fm_set_msg(lng('File not found'), 'error'); $FM_PATH = FM_PATH; fm_redirect(FM_SELF_URL . '?p=' . urlencode($FM_PATH)); @@ -1938,7 +1938,7 @@ if (isset($_GET['edit']) && !FM_READONLY) { $file = $_GET['edit']; $file = fm_clean_path($file, false); $file = str_replace('/', '', $file); - if ($file == '' || !is_file($path . '/' . $file) || !fm_is_exclude_items($file, $path . '/' . $file)) { + if ($file == '' || !is_file($path . '/' . $file) || fm_is_excluded($file, $path . '/' . $file)) { fm_set_msg(lng('File not found'), 'error'); $FM_PATH = FM_PATH; fm_redirect(FM_SELF_URL . '?p=' . urlencode($FM_PATH)); @@ -2684,26 +2684,23 @@ function fm_get_display_path($file_path) } /** - * Check file is in exclude list + * Check if the file, extension, or path is an excluded item * @param string $name The name of the file/folder * @param string $path The full path of the file/folder * @return bool */ -function fm_is_exclude_items($name, $path) +function fm_is_excluded($name, $path) { $ext = strtolower(pathinfo($name, PATHINFO_EXTENSION)); - if (isset($exclude_items) and sizeof($exclude_items)) { - unset($exclude_items); + $excluded_items = FM_EXCLUDE_ITEMS; // set by above config or environment + if (version_compare(PHP_VERSION, '7.0.0', '<')) { + $excluded_items = unserialize($excluded_items); // constants cant hold arrays before PHP 7 } - $exclude_items = FM_EXCLUDE_ITEMS; - if (version_compare(PHP_VERSION, '7.0.0', '<')) { - $exclude_items = unserialize($exclude_items); + if (in_array($name, $excluded_items) || in_array("*.$ext", $excluded_items) || in_array($path, $excluded_items)) { + return true; // item is in exclude_items } - if (!in_array($name, $exclude_items) && !in_array("*.$ext", $exclude_items) && !in_array($path, $exclude_items)) { - return true; - } - return false; + return false; // item is safe } /** From 4210dae7e55c3afc432be87d1b6c093caea91de5 Mon Sep 17 00:00:00 2001 From: Mark Date: Sun, 27 Apr 2025 13:43:28 -0500 Subject: [PATCH 4/6] Add upload conflict prompts --- tinyfilemanager.php | 215 ++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 196 insertions(+), 19 deletions(-) diff --git a/tinyfilemanager.php b/tinyfilemanager.php index 27c868a..057c8de 100644 --- a/tinyfilemanager.php +++ b/tinyfilemanager.php @@ -122,10 +122,11 @@ $max_upload_size_bytes = 5000000000; // size 5,000,000,000 bytes (~5GB) $upload_chunk_size_bytes = 2000000; // chunk size 2,000,000 bytes (~2MB) // Handle file uploads where the file exists +// PROMPT => ask the user to resolve specific conflicts, or leave the temporary file // NEW => newly uploaded file is renamed with a timestamp appended // OLD => old file will be renamed with a timestamp appended // REPLACE => old file will be deleted -$upload_name_conflict_handling = 'OLD'; +$upload_name_conflict_handling = 'PROMPT'; // Possible rules are 'OFF', 'AND' or 'OR' // OFF => Don't check connection IP, defaults to OFF @@ -673,6 +674,46 @@ if ((isset($_SESSION[FM_SESSION_ID]['logged'], $auth_users[$_SESSION[FM_SESSION_ event_callback(array("fail" => $err)); } } + + // Upload Conflict Resolution + if (isset($_POST['type']) && $_POST['type'] == 'conflict') { + $path = $_POST['path']; + $filename = $_POST['filename']; + $resolution = $_POST['resoultion']; + + + $fullPath = $path . '/' . $filename; + $tempPath = "{$fullPath}.part"; + + if (!file_exists($tempPath)) { + echo json_encode(['status' => 'error', 'info' => "Temporary file not found. $tempPath"]); + exit; + } + + $ext = pathinfo($filename, PATHINFO_FILENAME) != '' ? strtolower(pathinfo($filename, PATHINFO_EXTENSION)) : ''; + $fullPathTarget = $fullPath; + $ext_1 = $ext ? '.' . $ext : ''; + $datedPath = $path . '/' . basename($filename, $ext_1) . '_' . date('ymdHis') . $ext_1; + + if (fm_is_excluded($filename, $path)) { + $fullPathTarget = $datedPath; + } else switch ($_POST['resolution']) { + case 'OLD': + fm_rename($fullPath, $datedPath); + break; + case 'REPLACE': + if (fm_rdelete($fullPath)) break; + case 'NEW': + default: + $fullPathTarget = $datedPath; + } + fm_rename($tempPath, $fullPathTarget); + + echo json_encode(['status' => 'resolved', + 'info' => "RESOLVED WITH: $path , $filename, $resolution"]); + exit; + } + exit(); } @@ -1051,24 +1092,30 @@ if (!empty($_FILES) && !FM_READONLY) { } if ($chunkIndex == $chunkTotal - 1) { - $fullPathTarget = $fullPath; + $fullPathTarget = $fullPath; + $ext_1 = $ext ? '.' . $ext : ''; + $datedPath = $path . '/' . basename($fullPathInput, $ext_1) . '_' . date('ymdHis') . $ext_1; if (file_exists($fullPath)) { - $ext_1 = $ext ? '.' . $ext : ''; - $datedPath = $path . '/' . basename($fullPathInput, $ext_1) . '_' . date('ymdHis') . $ext_1; - if(fm_is_exclude_items($fullPath)){ - $fullPathTarget = $datedPath; // excluded items should not be replaced or renamed - }else{ - switch($upload_name_conflict_handling) - { - case 'OLD': - fm_rename($fullPath,$datedPath); - break; - case 'REPLACE': - if(fm_rdelete($fullPath)) break; - case 'NEW': - default: - $fullPathTarget = $datedPath; - } + if (fm_is_excluded($fullPathInput, $path)) { + $fullPathTarget = $datedPath; // excluded items should not be replaced, renamed, or prompted + } else switch ($upload_name_conflict_handling) { + case 'PROMPT': + $response = array( + 'status' => 'conflict', + 'info' => "file upload successful with conflict", + 'filename' => $fullPathInput, + 'path' => $path + ); + echo json_encode($response); + exit(); + case 'OLD': + fm_rename($fullPath,$datedPath); + break; + case 'REPLACE': + if (fm_rdelete($fullPath)) break; + case 'NEW': + default: + $fullPathTarget = $datedPath; } } fm_rename("{$fullPath}.part", $fullPathTarget); @@ -1441,6 +1488,36 @@ if (isset($_GET['upload']) && !FM_READONLY) { + +