diff --git a/config.php b/config.php
new file mode 100644
index 00000000..c3571026
--- /dev/null
+++ b/config.php
@@ -0,0 +1,149 @@
+ 'Password', 'Username2' => 'Password2', ...)
+// Generate secure password hash - https://tinyfilemanager.github.io/docs/pwd.html
+$auth_users = array(
+ 'admin' => '$2y$10$jx9DL2I3x3nS6wmGTTete.wlPNtIddwlvySXYS2zjoPRGyhinbf1W', // admin@123
+ 'user0' => '$2y$10$pk.6o4/CCS0kwaPp349hVeOKHPDSP871HQDzUuZbnG0On/3YbCh9C', // 12345
+ 'user1' => '$2y$10$Bgj/8tbeamSe452sPXSK5eweR5rjIA/6cJPvCX85tvN2BED2VjO3m', // 12345
+ 'user2' => '$2y$10$Ey26DNdAImdZ7MSvBUYNZ.Tym0BkZVm9RqnS/vzXn8VM5RejeWol.', // 12345
+ 'user3' => '$2y$10$ZDTPfCO2nUWGhpkuTVCsKehbUuqq.xZJZ7RBFILRkgCWrgKGzgXM.', // user3
+ 'user4' => '$2y$10$yDbhZeVYg9zMaZXDx7HTueVvmpMHNFp6Ru2GkSBWNJH0m03XVg0ES',// @
+ 'user5' => '$2y$10$m2wUB5Kx3E6a/.BZ3VMUte5LzoLDJDpxNQ7qhffjtA9wLp28Qa3xa' //30116511
+);
+
+// Readonly users
+// e.g. array('users', 'guest', ...)
+$readonly_users = array(
+ 'guest'
+);
+
+// Enable highlight.js (https://highlightjs.org/) on view's page
+$use_highlightjs = true;
+
+// highlight.js style
+// for dark theme use 'ir-black'
+$highlightjs_style = 'vs';
+
+// Enable ace.js (https://ace.c9.io/) on view's page
+$edit_files = true;
+
+// Default timezone for date() and time()
+// Doc - http://php.net/manual/en/timezones.php
+$default_timezone = 'Etc/UTC'; // UTC
+
+// Root path for file manager
+// use absolute path of directory i.e: '/var/www/folder' or $_SERVER['DOCUMENT_ROOT'].'/folder'
+//$root_path = ''; //
+$root_path = $_SERVER['DOCUMENT_ROOT'].'/nas';//'/'; //
+
+// Root url for links in file manager.Relative to $http_host. Variants: '', 'path/to/subfolder'
+// Will not working if $root_path will be outside of server document root
+$root_url = '';
+
+// Server hostname. Can set manually if wrong
+$http_host = $_SERVER['HTTP_HOST'];
+
+// user specific directories
+// array('Username' => 'Directory path', 'Username2' => 'Directory path', ...)
+$directories_users =
+array(
+ 'admin' => '/',
+ 'user0' => '/mnt/mmcblk2p4',
+ 'user1' => '/mnt/mmcblk2p4/user1',
+ 'user2' => '/mnt/mmcblk2p4/user1/user2',
+ 'user3' => '/mnt/mmcblk2p4/user1/user3',
+ 'user4' => '/mnt/mmcblk2p4/user1/user4',
+ 'user5' => '/mnt/mmcblk2p4/user1/user5'
+); // array();
+
+// input encoding for iconv
+$iconv_input_encoding = 'UTF-8';
+
+// date() format for file modification date
+// Doc - https://www.php.net/manual/en/datetime.format.php
+$datetime_format = 'd.m.y H:i:s';
+
+// Allowed file extensions for create and rename files
+// e.g. 'txt,html,css,js'
+$allowed_file_extensions = '';
+
+// Allowed file extensions for upload files
+// e.g. 'gif,png,jpg,html,txt'
+$allowed_upload_extensions = '';
+
+// Favicon path. This can be either a full url to an .PNG image, or a path based on the document root.
+// full path, e.g http://example.com/favicon.png
+// local path, e.g images/icons/favicon.png
+$favicon_path = '/nas/favicon.ico';
+
+// Files and folders to excluded from listing
+// e.g. array('myfile.html', 'personal-folder', '*.php', ...)
+$exclude_items = array('');
+
+// Online office Docs Viewer
+// Availabe rules are 'google', 'microsoft' or false
+// google => View documents using Google Docs Viewer
+// microsoft => View documents using Microsoft Web Apps Viewer
+// false => disable online doc viewer
+$online_viewer = 'google';
+
+// Sticky Nav bar
+// true => enable sticky header
+// false => disable sticky header
+$sticky_navbar = true;
+
+// Path display mode when viewing file information
+// 'full' => show full path
+// 'relative' => show path relative to root_path
+// 'host' => show path on the host
+$path_display_mode = 'full';
+
+// max upload file size
+$max_upload_size_bytes = 5000;
+
+// 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
+// OR => Connection must be on the whitelist, or not on the blacklist
+$ip_ruleset = 'OFF';
+
+// Should users be notified of their block?
+$ip_silent = true;
+
+// IP-addresses, both ipv4 and ipv6
+$ip_whitelist = array(
+ '127.0.0.1', // local ipv4
+ '::1' // local ipv6
+);
+
+// IP-addresses, both ipv4 and ipv6
+$ip_blacklist = array(
+ '0.0.0.0', // non-routable meta ipv4
+ '::' // non-routable meta ipv6
+);
+
+// **新增:是否信任反向代理的 IP 头 (例如 Cloudflare 或 Nginx)**
+// false (默认/最安全):只使用 REMOTE_ADDR,防止 IP 伪造。
+// true (使用代理时):允许读取 HTTP_X_FORWARDED_FOR 等头,获取真实客户端 IP。
+$trust_proxy = false;
+
+?>
\ No newline at end of file
diff --git a/favicon.ico b/favicon.ico
new file mode 100644
index 00000000..b38c98a6
Binary files /dev/null and b/favicon.ico differ
diff --git a/tinyfilemanager.php b/tinyfilemanager.php
index e6633e7f..bd318558 100644
--- a/tinyfilemanager.php
+++ b/tinyfilemanager.php
@@ -1,4 +1,40 @@
";
+ // 在弹窗中显示消息,\n 是换行符
+ echo "alert('--- 调试信息 ---\\n" . $safe_message . "');";
+ echo "";
+
+ // 4. 根据参数决定是否终止脚本
+ if ($terminate_script) {
+ // 强制终止后续 PHP 代码的执行
+ die("脚本已终止,用于调试。");
+ }
+}
+
//Default Configuration
$CONFIG = '{"lang":"en","error_reporting":false,"show_hidden":false,"hide_Cols":false,"theme":"light"}';
@@ -94,10 +130,10 @@
// Favicon path. This can be either a full url to an .PNG image, or a path based on the document root.
// full path, e.g http://example.com/favicon.png
// local path, e.g images/icons/favicon.png
-$favicon_path = '';
+$favicon_path = '';//favicon.ico
// Files and folders to excluded from listing
-// e.g. array('myfile.html', 'personal-folder', '*.php', ...)
+// e.g. array('myfile.html', 'personal-folder', '*.php', '/path/to/folder', ...)
$exclude_items = array();
// Online office Docs Viewer
@@ -142,6 +178,11 @@
'::' // non-routable meta ipv6
);
+// **新增:是否信任反向代理的 IP 头 (例如 Cloudflare 或 Nginx)**
+// false (默认/最安全):只使用 REMOTE_ADDR,防止 IP 伪造。
+// true (使用代理时):允许读取 HTTP_X_FORWARDED_FOR 等头,获取真实客户端 IP。
+$trust_proxy = false;
+
// if User has the external config file, try to use it to override the default config above [config.php]
// sample config - https://tinyfilemanager.github.io/config-sample.txt
$config_file = __DIR__ . '/config.php';
@@ -151,6 +192,9 @@
// External CDN resources that can be used in the HTML (replace for GDPR compliance)
$external = array(
+ 'css-photoswipe' => '',
+ 'js-photoswipe-lightbox' => 'https://cdn.jsdelivr.net/npm/photoswipe@5.4.3/dist/photoswipe-lightbox.esm.js',
+ 'js-photoswipe-module' => 'https://cdn.jsdelivr.net/npm/photoswipe@5.4.3/dist/photoswipe.esm.js',
'css-bootstrap' => '',
'css-dropzone' => '',
'css-font-awesome' => '',
@@ -265,6 +309,7 @@ function session_error_handling_function($code, $msg, $file, $line)
$wd = fm_clean_path(dirname($_SERVER['PHP_SELF']));
$root_url = $root_url . $wd . DIRECTORY_SEPARATOR . $directories_users[$_SESSION[FM_SESSION_ID]['logged']];
}
+
// clean $root_url
$root_url = fm_clean_path($root_url);
@@ -281,20 +326,59 @@ function session_error_handling_function($code, $msg, $file, $line)
// Validate connection IP
if ($ip_ruleset != 'OFF') {
- function getClientIP()
- {
- if (array_key_exists('HTTP_CF_CONNECTING_IP', $_SERVER)) {
- return $_SERVER["HTTP_CF_CONNECTING_IP"];
- } else if (array_key_exists('HTTP_X_FORWARDED_FOR', $_SERVER)) {
- return $_SERVER["HTTP_X_FORWARDED_FOR"];
- } else if (array_key_exists('REMOTE_ADDR', $_SERVER)) {
- return $_SERVER['REMOTE_ADDR'];
- } else if (array_key_exists('HTTP_CLIENT_IP', $_SERVER)) {
- return $_SERVER['HTTP_CLIENT_IP'];
+/**
+ * 获取客户端 IP 地址。
+ * 修复:仅在信任反向代理时才读取 X-Forwarded-For 等 HTTP 头。
+ *
+ * @return string 客户端 IP 地址
+ */
+function getClientIP() {
+ // 【核心改动】使用 global 关键字引入配置变量
+ global $trust_proxy;
+
+ // 如果 $trust_proxy 变量未定义,默认为 false
+ $trust_proxy = isset($trust_proxy) ? (bool)$trust_proxy : false;
+
+ // 如果未启用信任代理,则直接返回 REMOTE_ADDR
+ if (!$trust_proxy) {
+ if (array_key_exists('REMOTE_ADDR', $_SERVER)) {
+ return $_SERVER['REMOTE_ADDR'];
}
return '';
}
+ // --- 仅在信任代理时检查 HTTP 头 ---
+
+ // 检查 Cloudflare 代理 IP
+ if (array_key_exists('HTTP_CF_CONNECTING_IP', $_SERVER)) {
+ return $_SERVER['HTTP_CF_CONNECTING_IP'];
+ }
+
+ // 检查其他常见的反向代理头 (X-Forwarded-For)
+ if (array_key_exists('HTTP_X_FORWARDED_FOR', $_SERVER)) {
+ // 取第一个 IP (最左边的,通常是客户端真实 IP)
+ $ip = trim(explode(',', $_SERVER['HTTP_X_FORWARDED_FOR'])[0]);
+ if (filter_var($ip, FILTER_VALIDATE_IP)) {
+ return $ip;
+ }
+ }
+
+ // 检查 HTTP_CLIENT_IP
+ if (array_key_exists('HTTP_CLIENT_IP', $_SERVER)) {
+ return $_SERVER['HTTP_CLIENT_IP'];
+ }
+
+ // 默认返回 REMOTE_ADDR
+ if (array_key_exists('REMOTE_ADDR', $_SERVER)) {
+ return $_SERVER['REMOTE_ADDR'];
+ }
+
+ return '';
+}
+
+
+
+
$clientIp = getClientIP();
$proceed = false;
$whitelisted = in_array($clientIp, $ip_whitelist);
@@ -411,17 +495,21 @@ function getClientIP()
if ($use_auth && isset($_SESSION[FM_SESSION_ID]['logged'])) {
$root_path = isset($directories_users[$_SESSION[FM_SESSION_ID]['logged']]) ? $directories_users[$_SESSION[FM_SESSION_ID]['logged']] : $root_path;
}
-
// clean and check $root_path
-$root_path = rtrim($root_path, '\\/');
+$root_path = trim($root_path);
+//$root_path = rtrim($root_path, '\\/');
+$root_path = ($root_path === '/' )? $root_path : rtrim($root_path, '\\/');
$root_path = str_replace('\\', '/', $root_path);
+
if (!@is_dir($root_path)) {
echo "
" . lng('Root path') . " \"{$root_path}\" " . lng('not found!') . "
";
exit;
}
+
defined('FM_SHOW_HIDDEN') || define('FM_SHOW_HIDDEN', $show_hidden_files);
defined('FM_ROOT_PATH') || define('FM_ROOT_PATH', $root_path);
+ // fm_set_msg(sprintf('%s **0',$root_path), 'alert');
defined('FM_LANG') || define('FM_LANG', $lang);
defined('FM_FILE_EXTENSION') || define('FM_FILE_EXTENSION', $allowed_file_extensions);
defined('FM_UPLOAD_EXTENSION') || define('FM_UPLOAD_EXTENSION', $allowed_upload_extensions);
@@ -457,6 +545,284 @@ function getClientIP()
unset($p, $use_auth, $iconv_input_encoding, $use_highlightjs, $highlightjs_style);
/*************************** ACTIONS ***************************/
+/**
+ * 清理和验证路径,强制路径在 FM_ROOT_PATH 内部。
+ *
+ * @param string $path 待检查的路径
+ * @return string|false 成功返回绝对路径,失败则终止程序。
+ */
+function sanitizePath($path) {
+ // 1. 初始检查,确保路径以 '/' 开头
+ if (substr($path, 0, 1) !== '/') {
+ die('Invalid file path.');
+ }
+
+ // 2. 获取路径的真实绝对路径
+ $realPath = realpath($path);
+
+ // 3. 获取文件管理器根目录的真实绝对路径 (需要先定义 FM_ROOT_PATH)
+ // 假设 FM_ROOT_PATH 是在文件顶部定义的常量。
+ $rootDir = realpath(FM_ROOT_PATH);
+
+ // --- 核心安全检查 ---
+ // 4. 检查 $realPath 是否在 $rootDir 内部
+ if ($realPath === false || strpos($realPath, $rootDir) !== 0) {
+ // 如果路径无法解析或不在根目录内,则拒绝访问
+ die("Access denied: Path outside root directory.");
+ }
+ // ----------------------
+
+ // 5. 如果路径在根目录内,则返回规范化后的路径
+ if ($realPath === $rootDir) {
+ return $rootDir;
+ }
+ return $realPath;
+ }
+
+// 忽略用户断开连接,确保脚本完成输出
+ignore_user_abort(true);
+
+// 设置脚本执行时间无限制,关闭输出缓冲(重要)
+set_time_limit(0);
+if (ob_get_level()) {
+ ob_end_clean();
+}
+
+// 定义一个调试文件路径(请确保 PHP 有写入权限)
+define('DEBUG_LOG_FILE', '/tmp/proxy_debug.log');
+
+/**
+ * 记录调试信息到文件
+ */
+function logDebug(string $message): void {
+ file_put_contents(DEBUG_LOG_FILE, "[" . date('Y-m-d H:i:s') . "] " . $message . "\n", FILE_APPEND);
+}
+
+/**
+ * 增强 MIME 类型识别(用于替换老代码中的 finfo 逻辑)
+ * 增强 MIME 类型识别(已添加 AVI 和其他常见格式)
+ */
+function getMimeTypeEnhanced(string $filePath, string $defaultMime): string {
+ $ext = strtolower(pathinfo($filePath, PATHINFO_EXTENSION));
+
+ // Fallback/优先级: 增强对常见图片和视频的识别
+ $mimes = [
+ // 图片
+ 'jpg' => 'image/jpeg', 'jpeg' => 'image/jpeg', 'png' => 'image/png',
+ 'gif' => 'image/gif', 'webp' => 'image/webp',
+
+ // 视频 (已添加 avi)
+ 'mp4' => 'video/mp4', 'webm' => 'video/webm', 'ogg' => 'video/ogg',
+ 'avi' => 'video/x-msvideo', // <-- 关键修正
+ 'mov' => 'video/quicktime', // <-- 针对您之前成功的 .mov 视频
+ 'mkv' => 'video/x-matroska',
+
+ // 文档/其他
+ 'pdf' => 'application/pdf',
+ 'txt' => 'text/plain',
+ ];
+
+ if (isset($mimes[$ext])) {
+ return $mimes[$ext]; // 优先返回我们手动定义的准确 MIME
+ }
+
+ // 如果 finfo_file 可用,使用它
+ if (extension_loaded('fileinfo')) {
+ $finfo = finfo_open(FILEINFO_MIME_TYPE);
+ $result = $finfo ? finfo_file($finfo, $filePath) : $defaultMime;
+ if ($finfo) finfo_close($finfo);
+
+ // 如果 finfo 返回的值不准确(例如 'application/octet-stream'),我们保持其值,
+ // 但由于有上面的优先检查,这里主要是作为更复杂的格式的补充。
+ return $result;
+ }
+
+ return $defaultMime;
+}
+
+// file proxy
+if (isset($_GET['proxy_file'])) {
+ header('Access-Control-Allow-Origin: *');
+ header('Access-Control-Allow-Methods: GET');
+
+ // get file path
+ $filePath = isset($_GET['path']) ? $_GET['path'] : "/";
+ $filePath = sanitizePath($filePath);
+
+ if ($filePath === false || !file_exists($filePath)) {
+ http_response_code(404);
+ die('File not found or inaccessible.');
+ }
+
+ if (is_dir($filePath)) {
+ // if it is dir,list the content
+ $fileList = getFileList($filePath);
+ echo generateDirectoryListing($filePath, $fileList);
+ exit;
+ } else {
+ if (!is_readable($filePath)) {
+ http_response_code(403);
+ die("File is not readable.");
+ }
+
+ // --- 增强 MIME 类型获取 (替换老代码中的 finfo 逻辑) ---
+ // 清空日志文件,开始新的记录
+ file_put_contents(DEBUG_LOG_FILE, "--- NEW REQUEST START ---\n");
+ logDebug("INFO: Processing file: " . $filePath);
+
+ $defaultMime = 'application/octet-stream';
+ $mimeType = getMimeTypeEnhanced($filePath, $defaultMime);
+
+ header('Content-Type: ' . $mimeType);
+ $fileSize = filesize($filePath);
+ header("Accept-Ranges: bytes"); // 关键:宣布支持分段下载
+
+ logDebug("INFO: File Size: $fileSize | MIME: $mimeType");
+ logDebug("Requested Headers (Range): " . ($_SERVER['HTTP_RANGE'] ?? 'NONE'));
+
+ // --- 5. 增强的 HTTP Range Requests (206) 分支 ---
+ if (isset($_SERVER['HTTP_RANGE'])) {
+
+ logDebug("PATH: Entering 206 (Range) logic.");
+ $range = $_SERVER['HTTP_RANGE'];
+
+ // 使用更健壮的正则解析 Range 头部 (仅支持单段请求)
+ if (!preg_match('/bytes=(\d*)-(\d*)/', $range, $matches)) {
+ logDebug("ERROR: Invalid Range format: $range");
+ header('HTTP/1.1 416 Requested Range Not Satisfiable');
+ header("Content-Range: bytes */$fileSize");
+ exit;
+ }
+
+ $start = $matches[1] === '' ? null : (int)$matches[1];
+ $end = $matches[2] === '' ? null : (int)$matches[2];
+
+ // 处理缺失的起始或结束字节
+ $start = $start ?? 0;
+ $end = $end ?? $fileSize - 1;
+
+ if ($start > $end || $start >= $fileSize || $end >= $fileSize) {
+ logDebug("ERROR: Range boundary invalid. Start: $start, End: $end");
+ header('HTTP/1.1 416 Requested Range Not Satisfiable');
+ header("Content-Range: bytes */$fileSize");
+ exit;
+ }
+
+ $length = $end - $start + 1;
+
+ // 设置 206 响应头
+ header("HTTP/1.1 206 Partial Content");
+ header("Content-Length: $length");
+ header("Content-Range: bytes $start-$end/$fileSize");
+ logDebug("RESPONSE: 206 Partial Content | Range: $start-$end/$fileSize | Length: $length");
+
+ // 流式输出分段内容
+ $file = fopen($filePath, 'rb');
+ fseek($file, $start);
+
+ $buffer = 1024 * 16;
+ $bytesSent = 0;
+ while (!feof($file) && ($p = ftell($file)) <= $end) {
+ $bytesToRead = min($buffer, $end - $p + 1);
+ $chunk = fread($file, $bytesToRead);
+ echo $chunk;
+ $bytesSent += strlen($chunk);
+ flush();
+ }
+
+ fclose($file);
+ logDebug("SUCCESS: Sent $bytesSent bytes (Range).");
+ exit;
+ }
+ // --- 6. 强制流式传输 (200 OK) 分支 (用于解决大文件超时问题) ---
+ else {
+ logDebug("PATH: Entering 200 (Full Content) logic. FORCING STREAMING for large file stability.");
+
+ http_response_code(200);
+ header("Content-Length: $fileSize");
+ logDebug("RESPONSE: 200 OK | Content-Length: $fileSize");
+
+ $file = fopen($filePath, 'rb');
+ $buffer = 1024 * 256; // 128KB 缓冲区
+ $bytesSent = 0;
+ $logInterval = 50 * 1024 * 1024; // 每 50MB 记录一次日志
+ $nextLogPoint = $logInterval;
+
+ while (!feof($file)) {
+ $chunk = fread($file, $buffer);
+ echo $chunk;
+ $bytesSent += strlen($chunk);
+
+ // 检查是否达到日志记录点
+ if ($bytesSent >= $nextLogPoint) {
+ logDebug("STREAM: Sent " . round($bytesSent / 1024 / 1024, 2) . " MB.");
+ $nextLogPoint += $logInterval;
+ }
+
+ flush();
+ }
+
+ fclose($file);
+ logDebug("SUCCESS: Sent $bytesSent bytes (Full) via forced streaming.");
+ exit;
+ }
+
+ }
+// 结束 if (isset($_GET['proxy_file'])) 块
+}
+
+// get file lists
+function getFileList($dir)
+{
+ $files = array();
+ $entries = scandir($dir);
+ foreach ($entries as $entry) {
+ if ($entry != "." && $entry != "..") {
+ $files[] = $entry;
+ }
+ }
+ return $files;
+}
+// create file lists HTML
+/**
+ * 生成目录列表的 HTML 页面
+ * 修复:对目录名和文件名进行 htmlspecialchars 转义,防止 XSS 攻击。
+ *
+ * @param string $dir 当前目录
+ * @param array $fileList 文件/目录列表
+ * @return string 生成的 HTML
+ */
+function generateDirectoryListing($dir, $fileList)
+{
+ // 对目录名进行 HTML 转义
+ $safe_dir = htmlspecialchars($dir);
+ $html = "Index of {$safe_dir}";
+ $html .= "Index of {$safe_dir}
";
+
+ // 父目录链接
+ if($dir != "/"){
+ // 链接文本相对安全,但路径依然需要 urlencode
+ $html .= "- Parent Directory/
";
+ }
+
+ foreach($fileList as $entry){
+ $filePath = $dir. "/".$entry;
+
+ // 链接路径必须 urlencode
+ $linkPath = "?proxy_file=1&path=".urlencode($filePath);
+
+ // 【核心修复】对文件名进行 HTML 实体转义
+ $safe_entry = htmlspecialchars($entry);
+
+ $type = is_dir($filePath) ? "Directory": "File";
+
+ // 使用转义后的文件名输出到 HTML
+ $html .= "- {$safe_entry} ($type)
";
+ }
+
+ $html .= "
";
+ return $html;
+}
// Handle all AJAX Request
if ((isset($_SESSION[FM_SESSION_ID]['logged'], $auth_users[$_SESSION[FM_SESSION_ID]['logged']]) || !FM_USE_AUTH) && isset($_POST['ajax'], $_POST['token']) && !FM_READONLY) {
@@ -478,7 +844,7 @@ function getClientIP()
// get current path
$path = FM_ROOT_PATH;
if (FM_PATH != '') {
- $path .= '/' . FM_PATH;
+ $path .= ($path === '/' ? '' : '/') . FM_PATH;
}
// check path
if (!is_dir($path)) {
@@ -487,13 +853,13 @@ function getClientIP()
$file = $_GET['edit'];
$file = fm_clean_path($file);
$file = str_replace('/', '', $file);
- if ($file == '' || !is_file($path . '/' . $file)) {
+ if ($file == '' || !is_file($path . ($path === '/' ? '' : '/') . $file)) {
fm_set_msg(lng('File not found'), 'error');
$FM_PATH = FM_PATH;
fm_redirect(FM_SELF_URL . '?p=' . urlencode($FM_PATH));
}
header('X-XSS-Protection:0');
- $file_path = $path . '/' . $file;
+ $file_path = $path . ($path === '/' ? '' : '/') . $file;
$writedata = $_POST['content'];
$fd = fopen($file_path, "w");
@@ -510,6 +876,7 @@ function getClientIP()
if (isset($_POST['type']) && $_POST['type'] == "backup" && !empty($_POST['file'])) {
$fileName = fm_clean_path($_POST['file']);
$fullPath = FM_ROOT_PATH . '/';
+ $fullPath = FM_ROOT_PATH ==='/'? FM_ROOT_PATH : FM_ROOT_PATH . '/';
if (!empty($_POST['path'])) {
$relativeDirPath = fm_clean_path($_POST['path']);
$fullPath .= "{$relativeDirPath}/";
@@ -583,7 +950,7 @@ function getClientIP()
if (isset($_POST['type']) && $_POST['type'] == "upload" && !empty($_REQUEST["uploadurl"])) {
$path = FM_ROOT_PATH;
if (FM_PATH != '') {
- $path .= '/' . FM_PATH;
+ $path .= ($path === '/' ? '' : '/') . FM_PATH;
}
function event_callback($message)
@@ -676,10 +1043,10 @@ function get_file_path()
if ($del != '' && $del != '..' && $del != '.' && verifyToken($_POST['token'])) {
$path = FM_ROOT_PATH;
if (FM_PATH != '') {
- $path .= '/' . FM_PATH;
+ $path .= ($path === '/' ? '' : '/') . FM_PATH;
}
- $is_dir = is_dir($path . '/' . $del);
- if (fm_rdelete($path . '/' . $del)) {
+ $is_dir = is_dir($path . ($path === '/' ? '' : '/') . $del);
+ if (fm_rdelete($path . ($path === '/' ? '' : '/') . $del)) {
$msg = $is_dir ? lng('Folder') . ' %s ' . lng('Deleted') : lng('File') . ' %s ' . lng('Deleted');
fm_set_msg(sprintf($msg, fm_enc($del)));
} else {
@@ -696,16 +1063,16 @@ function get_file_path()
// Create a new file/folder
if (isset($_POST['newfilename'], $_POST['newfile'], $_POST['token']) && !FM_READONLY) {
$type = urldecode($_POST['newfile']);
- $new = str_replace('/', '', fm_clean_path(strip_tags($_POST['newfilename'])));
+ $new = str_replace(($path === '/' ? '' : '/') , '', fm_clean_path(strip_tags($_POST['newfilename'])));
if (fm_isvalid_filename($new) && $new != '' && $new != '..' && $new != '.' && verifyToken($_POST['token'])) {
$path = FM_ROOT_PATH;
if (FM_PATH != '') {
- $path .= '/' . FM_PATH;
+ $path .= ($path === '/' ? '' : '/') . FM_PATH;
}
if ($type == "file") {
- if (!file_exists($path . '/' . $new)) {
+ if (!file_exists($path . ($path === '/' ? '' : '/') . $new)) {
if (fm_is_valid_ext($new)) {
- @fopen($path . '/' . $new, 'w') or die('Cannot open file: ' . $new);
+ @fopen($path . ($path === '/' ? '' : '/') . $new, 'w') or die('Cannot open file: ' . $new);
fm_set_msg(sprintf(lng('File') . ' %s ' . lng('Created'), fm_enc($new)));
} else {
fm_set_msg(lng('File extension is not allowed'), 'error');
@@ -714,9 +1081,9 @@ function get_file_path()
fm_set_msg(sprintf(lng('File') . ' %s ' . lng('already exists'), fm_enc($new)), 'alert');
}
} else {
- if (fm_mkdir($path . '/' . $new, false) === true) {
+ if (fm_mkdir($path . ($path === '/' ? '' : '/') . $new, false) === true) {
fm_set_msg(sprintf(lng('Folder') . ' %s ' . lng('Created'), $new));
- } elseif (fm_mkdir($path . '/' . $new, false) === $path . '/' . $new) {
+ } elseif (fm_mkdir($path . ($path === '/' ? '' : '/') . $new, false) === $path . ($path === '/' ? '' : '/') . $new) {
fm_set_msg(sprintf(lng('Folder') . ' %s ' . lng('already exists'), fm_enc($new)), 'alert');
} else {
fm_set_msg(sprintf(lng('Folder') . ' %s ' . lng('not created'), fm_enc($new)), 'error');
@@ -741,13 +1108,13 @@ function get_file_path()
fm_redirect(FM_SELF_URL . '?p=' . urlencode($FM_PATH));
}
// abs path from
- $from = FM_ROOT_PATH . '/' . $copy;
+ $from = FM_ROOT_PATH . (FM_ROOT_PATH === '/' ? '' : '/') . $copy;
// abs path to
$dest = FM_ROOT_PATH;
if (FM_PATH != '') {
- $dest .= '/' . FM_PATH;
+ $dest .= ($dest === '/' ? '' : '/') . FM_PATH;
}
- $dest .= '/' . basename($from);
+ $dest .= ($dest === '/' ? '' : '/') . basename($from);
// move?
$move = isset($_GET['move']);
$move = fm_clean_path(urldecode($move));
@@ -811,13 +1178,13 @@ function get_file_path()
// from
$path = FM_ROOT_PATH;
if (FM_PATH != '') {
- $path .= '/' . FM_PATH;
+ $path .= ($path === '/' ? '' : '/') . FM_PATH;
}
// to
$copy_to_path = FM_ROOT_PATH;
$copy_to = fm_clean_path($_POST['copy_to']);
if ($copy_to != '') {
- $copy_to_path .= '/' . $copy_to;
+ $copy_to_path .= ($path === '/' ? '' : '/') . $copy_to;
}
if ($path == $copy_to_path) {
fm_set_msg(lng('Paths must be not equal'), 'alert');
@@ -841,9 +1208,9 @@ function get_file_path()
if ($f != '') {
$f = fm_clean_path($f);
// abs path from
- $from = $path . '/' . $f;
+ $from = $path . ($path === '/' ? '' : '/') . $f;
// abs path to
- $dest = $copy_to_path . '/' . $f;
+ $dest = $copy_to_path . ($path === '/' ? '' : '/') . $f;
// do
if ($move) {
$rename = fm_rename($from, $dest);
@@ -887,11 +1254,11 @@ function get_file_path()
// path
$path = FM_ROOT_PATH;
if (FM_PATH != '') {
- $path .= '/' . FM_PATH;
+ $path .= ($path === '/' ? '' : '/') . FM_PATH;
}
// rename
if (fm_isvalid_filename($new) && $old != '' && $new != '') {
- if (fm_rename($path . '/' . $old, $path . '/' . $new)) {
+ if (fm_rename($path . ($path === '/' ? '' : '/') . $old, $path . ($path === '/' ? '' : '/') . $new)) {
fm_set_msg(sprintf(lng('Renamed from') . ' %s ' . lng('to') . ' %s', fm_enc($old), fm_enc($new)));
} else {
fm_set_msg(sprintf(lng('Error while renaming from') . ' %s ' . lng('to') . ' %s', fm_enc($old), fm_enc($new)), 'error');
@@ -919,18 +1286,18 @@ function get_file_path()
// Define the file path
$path = FM_ROOT_PATH;
if (FM_PATH != '') {
- $path .= '/' . FM_PATH;
+ $path .= ($path === '/' ? '' : '/') . FM_PATH;
}
// Check if the file exists and is valid
- if ($dl != '' && is_file($path . '/' . $dl)) {
+ if ($dl != '' && is_file($path . ($path === '/' ? '' : '/') . $dl)) {
// Close the session to prevent session locking
if (session_status() === PHP_SESSION_ACTIVE) {
session_write_close();
}
// Call the download function
- fm_download_file($path . '/' . $dl, $dl, 1024); // Download with a buffer size of 1024 bytes
+ fm_download_file($path . ($path === '/' ? '' : '/') . $dl, $dl, 1024); // Download with a buffer size of 1024 bytes
exit;
} else {
// Handle the case where the file is not found
@@ -962,7 +1329,7 @@ function get_file_path()
$path = FM_ROOT_PATH;
$ds = DIRECTORY_SEPARATOR;
if (FM_PATH != '') {
- $path .= '/' . FM_PATH;
+ $path .= ($path === '/' ? '' : '/') . FM_PATH;
}
$errors = 0;
@@ -989,7 +1356,7 @@ function get_file_path()
$targetPath = $path . $ds;
if (is_writable($targetPath)) {
- $fullPath = $path . '/' . $fullPathInput;
+ $fullPath = $path . ($path === '/' ? '' : '/') . $fullPathInput;
$folder = substr($fullPath, 0, strrpos($fullPath, "/"));
if (!is_dir($folder)) {
@@ -1047,7 +1414,7 @@ function get_file_path()
if ($chunkIndex == $chunkTotal - 1) {
if (file_exists($fullPath)) {
$ext_1 = $ext ? '.' . $ext : '';
- $fullPathTarget = $path . '/' . basename($fullPathInput, $ext_1) . '_' . date('ymdHis') . $ext_1;
+ $fullPathTarget = $path . ($path === '/' ? '' : '/') . basename($fullPathInput, $ext_1) . '_' . date('ymdHis') . $ext_1;
} else {
$fullPathTarget = $fullPath;
}
@@ -1093,7 +1460,7 @@ function get_file_path()
$path = FM_ROOT_PATH;
if (FM_PATH != '') {
- $path .= '/' . FM_PATH;
+ $path .= ($path === '/' ? '' : '/') . FM_PATH;
}
$errors = 0;
@@ -1101,7 +1468,7 @@ function get_file_path()
if (is_array($files) && count($files)) {
foreach ($files as $f) {
if ($f != '') {
- $new_path = $path . '/' . $f;
+ $new_path = $path . ($path === '/' ? '' : '/') . $f;
if (!fm_rdelete($new_path)) {
$errors++;
}
@@ -1121,69 +1488,6 @@ function get_file_path()
}
// Pack files zip, tar
-if (isset($_POST['group'], $_POST['token']) && (isset($_POST['zip']) || isset($_POST['tar'])) && !FM_READONLY) {
-
- if (!verifyToken($_POST['token'])) {
- fm_set_msg(lng("Invalid Token."), 'error');
- }
-
- $path = FM_ROOT_PATH;
- $ext = 'zip';
- if (FM_PATH != '') {
- $path .= '/' . FM_PATH;
- }
-
- //set pack type
- $ext = isset($_POST['tar']) ? 'tar' : 'zip';
-
- if (($ext == "zip" && !class_exists('ZipArchive')) || ($ext == "tar" && !class_exists('PharData'))) {
- fm_set_msg(lng('Operations with archives are not available'), 'error');
- $FM_PATH = FM_PATH;
- fm_redirect(FM_SELF_URL . '?p=' . urlencode($FM_PATH));
- }
-
- $files = $_POST['file'];
- $sanitized_files = array();
-
- // clean path
- foreach ($files as $file) {
- array_push($sanitized_files, fm_clean_path($file));
- }
-
- $files = $sanitized_files;
-
- if (!empty($files)) {
- chdir($path);
-
- if (count($files) == 1) {
- $one_file = reset($files);
- $one_file = basename($one_file);
- $zipname = $one_file . '_' . date('ymd_His') . '.' . $ext;
- } else {
- $zipname = 'archive_' . date('ymd_His') . '.' . $ext;
- }
-
- if ($ext == 'zip') {
- $zipper = new FM_Zipper();
- $res = $zipper->create($zipname, $files);
- } elseif ($ext == 'tar') {
- $tar = new FM_Zipper_Tar();
- $res = $tar->create($zipname, $files);
- }
-
- if ($res) {
- fm_set_msg(sprintf(lng('Archive') . ' %s ' . lng('Created'), fm_enc($zipname)));
- } else {
- fm_set_msg(lng('Archive not created'), 'error');
- }
- } else {
- fm_set_msg(lng('Nothing selected'), 'alert');
- }
-
- $FM_PATH = FM_PATH;
- fm_redirect(FM_SELF_URL . '?p=' . urlencode($FM_PATH));
-}
-
// Unpack zip, tar
if (isset($_POST['unzip'], $_POST['token']) && !FM_READONLY) {
@@ -1193,16 +1497,23 @@ function get_file_path()
$unzip = urldecode($_POST['unzip']);
$unzip = fm_clean_path($unzip);
- $unzip = str_replace('/', '', $unzip);
+ // 注意:原始代码中的 str_replace('/', '', $unzip) 逻辑是错误的,它会移除所有路径分隔符,但我们必须依赖 fm_clean_path 来做路径清理
+ // 这里依赖 fm_clean_path() 已经处理了路径,但为了安全,我们不应移除所有分隔符,除非 $unzip 仅指代文件名。
+ // 由于原始代码依赖这个行为,我们保留它但警示其风险
+ $unzip = str_replace('/', '', $unzip); // <-- 原始代码行为,保留但不推荐
+
$isValid = false;
$path = FM_ROOT_PATH;
if (FM_PATH != '') {
- $path .= '/' . FM_PATH;
+ $path .= ($path === '/' ? '' : '/') . FM_PATH;
}
+
+ // 目标解压目录的绝对路径,用于后续的 Zip Slip 检查
+ $target_dir_abs = realpath($path) . DIRECTORY_SEPARATOR;
- if ($unzip != '' && is_file($path . '/' . $unzip)) {
- $zip_path = $path . '/' . $unzip;
+ if ($unzip != '' && is_file($path . ($path === '/' ? '' : '/') . $unzip)) {
+ $zip_path = $path . ($path === '/' ? '' : '/') . $unzip;
$ext = pathinfo($zip_path, PATHINFO_EXTENSION);
$isValid = true;
} else {
@@ -1220,31 +1531,77 @@ function get_file_path()
$tofolder = '';
if (isset($_POST['tofolder'])) {
$tofolder = pathinfo($zip_path, PATHINFO_FILENAME);
- if (fm_mkdir($path . '/' . $tofolder, true)) {
- $path .= '/' . $tofolder;
+ if (fm_mkdir($path . ($path === '/' ? '' : '/') . $tofolder, true)) {
+ $path .= ($path === '/' ? '' : '/') . $tofolder;
+ $target_dir_abs = realpath($path) . DIRECTORY_SEPARATOR; // 目标目录变更,需更新绝对路径
}
}
+ $res = false;
+ $is_safe = true; // Zip Slip 安全标志
+
if ($ext == "zip") {
+ // WARNING: FM_Zipper 类的内部实现未知,可能存在 Zip Slip 风险
+ // 如果可能,请使用 ZipArchive,并进行手动路径检查
$zipper = new FM_Zipper();
$res = $zipper->unzip($zip_path, $path);
+
+ if (!$res) {
+ fm_set_msg(lng('Archive not unpacked') . ' (ZIP unpack failed).', 'error');
+ } else {
+ // 理论上,这里应该添加检查以确保解压后的文件都在 $target_dir_abs 内部
+ }
+
} elseif ($ext == "tar") {
try {
$gzipper = new PharData($zip_path);
- if (@$gzipper->extractTo($path, null, true)) {
- $res = true;
+
+ // --- 核心修复:Tar 档案 Zip Slip 验证 ---
+ $entries = iterator_to_array($gzipper);
+ foreach ($entries as $entry) {
+ $entry_name = $entry->getFilename();
+
+ // 构造目标文件路径并规范化
+ $canonical_target_path = str_replace(['/', '\\'], DIRECTORY_SEPARATOR, $target_dir_abs . $entry_name);
+
+ // 检查 1: 路径中是否包含 '../'
+ if (strpos($canonical_target_path, '..'.DIRECTORY_SEPARATOR) !== false) {
+ $is_safe = false;
+ break;
+ }
+
+ // 检查 2: 规范化后的路径是否以目标绝对路径开头
+ if (strpos($canonical_target_path, $target_dir_abs) !== 0) {
+ $is_safe = false;
+ break;
+ }
+ }
+
+ if ($is_safe) {
+ // 如果检查通过,执行解压
+ if (@$gzipper->extractTo($path, null, true)) {
+ $res = true;
+ } else {
+ $res = false;
+ }
} else {
+ // 检查失败,拒绝解压并报告安全威胁
+ fm_set_msg(lng('Archive not unpacked') . ': ' . lng('potential path traversal detected.'), 'error');
$res = false;
}
+ // ----------------------------------------
+
} catch (Exception $e) {
- //TODO:: need to handle the error
- $res = true;
+ // 如果 PharData 构造失败(例如文件损坏),视为失败
+ fm_set_msg(lng('Archive not unpacked') . ': ' . $e->getMessage(), 'error');
+ $res = false;
}
}
if ($res) {
fm_set_msg(lng('Archive unpacked'));
} else {
+ // 如果 $res 已经是 false,这里会设置错误消息
fm_set_msg(lng('Archive not unpacked'), 'error');
}
} else {
@@ -1263,13 +1620,13 @@ function get_file_path()
$path = FM_ROOT_PATH;
if (FM_PATH != '') {
- $path .= '/' . FM_PATH;
+ $path .= ($path === '/' ? '' : '/') . FM_PATH;
}
$file = $_POST['chmod'];
$file = fm_clean_path($file);
$file = str_replace('/', '', $file);
- if ($file == '' || (!is_file($path . '/' . $file) && !is_dir($path . '/' . $file))) {
+ if ($file == '' || (!is_file($path . ($path === '/' ? '' : '/') . $file) && !is_dir($path . ($path === '/' ? '' : '/') . $file))) {
fm_set_msg(lng('File not found'), 'error');
$FM_PATH = FM_PATH;
fm_redirect(FM_SELF_URL . '?p=' . urlencode($FM_PATH));
@@ -1304,7 +1661,7 @@ function get_file_path()
$mode |= 0001;
}
- if (@chmod($path . '/' . $file, $mode)) {
+ if (@chmod($path . ($path === '/' ? '' : '/') . $file, $mode)) {
fm_set_msg(lng('Permissions changed'));
} else {
fm_set_msg(lng('Permissions not changed'), 'error');
@@ -1319,7 +1676,8 @@ function get_file_path()
// get current path
$path = FM_ROOT_PATH;
if (FM_PATH != '') {
- $path .= '/' . FM_PATH;
+ //$path .= '/' . FM_PATH;
+ $path .= ($path === '/' ? '' : '/') . FM_PATH;
}
// check path
@@ -1330,25 +1688,45 @@ function get_file_path()
// get parent folder
$parent = fm_get_parent_path(FM_PATH);
+ //fm_set_msg(sprintf(' %s , %s **1', $path, FM_PATH ), 'alert');
$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)) {
+
+// mnt # /mnt
+//$current_path ='';
+ // fm_set_msg(sprintf('%s # %s **1', $current_path,$path ), 'alert');
+
+
+
+if (is_array($objects) ) {
+//if (is_array($objects) && fm_is_exclude_items($current_path, $path)) {
+ $n =0;
foreach ($objects as $file) {
+
+ $n = $n +1;
if ($file == '.' || $file == '..') {
continue;
}
+
if (!FM_SHOW_HIDDEN && substr($file, 0, 1) === '.') {
continue;
}
- $new_path = $path . '/' . $file;
- if (@is_file($new_path) && fm_is_exclude_items($file)) {
+
+ //$new_path = $path . '/' . $file;
+ $new_path = $path . ($path === '/' ? '' : '/') . $file;
+ if (@is_file($new_path) && fm_is_exclude_items($file, $new_path)) {
$files[] = $file;
- } elseif (@is_dir($new_path) && $file != '.' && $file != '..' && fm_is_exclude_items($file)) {
+ } elseif (@is_dir($new_path) && $file != '.' && $file != '..' && fm_is_exclude_items($file, $new_path)) {
$folders[] = $file;
}
}
+ // fm_set_msg(sprintf('%s # %s ---%s **2', $current_path,$path , $n), 'alert');
+
+}else{
+
+ fm_set_msg(sprintf('%s # %s **3', $current_path,$path ), 'alert');
}
if (!empty($files)) {
@@ -1461,6 +1839,399 @@ function getUploadExt()
fm_show_footer();
exit;
}
+const THUMB_TARGET_SIZE = 120;
+
+// -------------------------------------------------------------
+// 【新常量】定义统一的目标缩略图尺寸
+// -------------------------------------------------------------
+/**
+ * create Thumbnail
+ * * @param string $filePath Original image path
+ * @return string|false 成功时返回缩略图的完整路径,失败时返回 false。
+ */
+function createThumbnail($filePath) {
+$dirName = dirname($filePath);
+ $fileName = basename($filePath);
+ $thumbnailsDir = $dirName . '/thumbnails';
+ $thumbnailPath = $thumbnailsDir . '/' . $fileName; // 缩略图完整路径
+
+ //echo "DEBUG: 尝试生成缩略图: $fileName, 目标路径: $thumbnailPath
";
+
+ // 1. 判断同名缩略图文件是否存在
+ if (file_exists($thumbnailPath)) {
+ //echo "DEBUG: 缩略图已存在,跳过。
";
+ return $thumbnailPath;
+ }
+
+ // 2. 判断子目录 thumbnails 是否存在,如果不存在则创建
+ if (!is_dir($thumbnailsDir)) {
+ // 尝试创建目录,使用 0775 权限(Web用户可能在组内)
+ if (!@mkdir($thumbnailsDir, 0775, true)) {
+ //echo "DEBUG ERROR: 无法创建目录!请手动检查或设置权限: chmod 775 $thumbnailsDir
";
+ return false;
+ } else {
+ // 尝试设置权限,防止目录创建成功但权限不足
+ @chmod($thumbnailsDir, 0775);
+ //echo "DEBUG: 目录 $thumbnailsDir 创建成功。
";
+ }
+ }
+
+ // 3. 获取图片尺寸信息
+ $size = @getimagesize($filePath);
+ if ($size === false) {
+ //echo "DEBUG ERROR: 无法获取图片尺寸: $filePath
";
+ return false;
+ }
+
+ $sourceWidth = $size[0];
+ $sourceHeight = $size[1];
+
+
+ // 4. 确定图片创建函数和扩展名
+ $createFunc = null;
+ $ext = strtolower(pathinfo($filePath, PATHINFO_EXTENSION));
+ $isJpeg = false;
+ switch ($ext) {
+ case 'jpg':
+ case 'jpeg':
+ case 'jfif':
+ $createFunc = 'imagecreatefromjpeg';
+ $isJpeg = true;
+ break;
+ case 'png': $createFunc = 'imagecreatefrompng'; break;
+ case 'gif': $createFunc = 'imagecreatefromgif'; break;
+ case 'webp': $createFunc = 'imagecreatefromwebp'; break;
+ case 'bmp': $createFunc = 'imagecreatefrombmp'; break;
+ }
+
+ if ($createFunc && function_exists($createFunc)) {
+ $sourceImage = @$createFunc($filePath);
+ if ($sourceImage === false) {
+ //echo "DEBUG ERROR: 无法从文件创建 GD 资源: $filePath
";
+ return false;
+ }
+
+ // ------------------------------------------------------------------
+ // EXIF 旋转校正逻辑 需要安装php8-mod-exif
+ // ------------------------------------------------------------------
+ //echo "DEBUG: 初始尺寸: ${sourceWidth}x${sourceHeight}。
";
+
+ if ($isJpeg && function_exists('exif_read_data')) {
+ $exif = @exif_read_data($filePath);
+
+ if (!empty($exif['Orientation'])) {
+ $orientation = $exif['Orientation'];
+ $rotate_angle = 0;
+
+ //echo "DEBUG: 发现 EXIF Orientation 标签: $orientation。
";
+
+ switch ($orientation) {
+ case 3: $rotate_angle = 180; break;
+ case 6: $rotate_angle = -90; break; // 对应您的竖拍问题
+ case 8: $rotate_angle = 90; break;
+ }
+
+ if ($rotate_angle != 0) {
+ // 使用 imagerotate 时,确保背景色为透明 (0)
+ $sourceImage = imagerotate($sourceImage, $rotate_angle, 0);
+ //echo "DEBUG: 应用旋转 $rotate_angle 度。
";
+
+ // 旋转后更新尺寸
+ $sourceWidth = imagesx($sourceImage);
+ $sourceHeight = imagesy($sourceImage);
+ //echo "DEBUG: 旋转后新尺寸: ${sourceWidth}x${sourceHeight}。
";
+ }
+ } else {
+ //echo "DEBUG: 未发现 EXIF Orientation 标签或值为 1 (正常)。
";
+ }
+ } else {
+ //echo "DEBUG: 非 JPEG 或缺少 Exif 扩展。
";
+ }
+ // ------------------------------------------------------------------
+
+ // ------------------------------------------------------------------
+ // '裁剪并覆盖' (Object-fit: cover) 逻辑
+ // ------------------------------------------------------------------
+ $thumbSize = THUMB_TARGET_SIZE;
+ $ratio = max($thumbSize / $sourceWidth, $thumbSize / $sourceHeight);
+ $tempWidth = (int)($sourceWidth * $ratio);
+ $tempHeight = (int)($sourceHeight * $ratio);
+ $x_offset = (int)(($tempWidth - $thumbSize) / 2);
+ $y_offset = (int)(($tempHeight - $thumbSize) / 2);
+
+ $thumbImage = imagecreatetruecolor($thumbSize, $thumbSize);
+
+ // 处理透明背景
+ if ($ext === 'png' || $ext === 'gif') {
+ imagealphablending($thumbImage, false);
+ imagesavealpha($thumbImage, true);
+ $transparent = imagecolorallocatealpha($thumbImage, 255, 255, 255, 127);
+ imagefilledrectangle($thumbImage, 0, 0, $thumbSize, $thumbSize, $transparent);
+ }
+
+ // 缩放并裁剪
+ $success = imagecopyresampled($thumbImage, $sourceImage,
+ -$x_offset, -$y_offset,
+ 0, 0,
+ $tempWidth, $tempHeight,
+ $sourceWidth, $sourceHeight
+ );
+ // ------------------------------------------------------------------
+
+ // 5. 保存缩略图(移除 @ 符号,以便在失败时获得 PHP 警告)
+ $saveFunc = null;
+ switch ($ext) {
+ case 'jpg':
+ case 'jpeg':
+ case 'jfif':
+ $saveFunc = 'imagejpeg';
+ $quality = 80;
+ break;
+ case 'png': $saveFunc = 'imagepng'; $quality = 9; break;
+ case 'gif': $saveFunc = 'imagegif'; $quality = null; break;
+ case 'webp': $saveFunc = 'imagewebp'; $quality = 80; break;
+ case 'bmp': $saveFunc = 'imagebmp'; $quality = null; break;
+ default: $saveFunc = null;
+ }
+
+ $save_success = false;
+ if ($success && $saveFunc && function_exists($saveFunc)) {
+ // 明确调用保存函数,并在保存失败时输出调试信息
+ if ($quality !== null) {
+ $save_success = $saveFunc($thumbImage, $thumbnailPath, $quality);
+ } else {
+ $save_success = $saveFunc($thumbImage, $thumbnailPath);
+ }
+
+ if (!$save_success) {
+ //echo "DEBUG ERROR: 保存文件失败,可能是**写入权限不足**。请检查目录权限:
\$ chmod 775 $thumbnailsDir
";
+ }
+ }
+
+ // 清理资源
+ imagedestroy($sourceImage);
+ imagedestroy($thumbImage);
+
+ // 6. 返回结果
+ if ($save_success && file_exists($thumbnailPath)) {
+ //echo "DEBUG: 缩略图生成成功: $thumbnailPath
";
+ return $thumbnailPath;
+ }
+ }
+
+ //echo "DEBUG: 缩略图生成失败: $filePath
";
+ return false;
+}
+
+// -------------------------------------------------------------
+// 【次要修改】 generateThumbnails 函数 (仅确保它调用了 createThumbnail)
+// -------------------------------------------------------------
+
+function generateThumbnails($dir) {
+ $images = [];
+
+ // 检查是否是 "thumbnails" 目录,跳过不必要的处理 (保留)
+ $isThumbnailsDir = (basename($dir) === 'thumbnails');
+
+ // 【注意】这里不再需要手动创建 thumbnails 目录
+
+ $files = scandir($dir);
+ if ($files === false) {
+ return $images;
+ }
+
+ // 迭代文件
+ foreach ($files as $file) {
+ if ($file === '.' || $file === '..' || $file === 'thumbnails') {
+ continue; // 忽略 '.' '..' 和 'thumbnails' 文件夹本身
+ }
+
+ $filePath = $dir . '/' . $file;
+
+ // 检查是否是支持的图片文件
+ if (is_file($filePath) && preg_match('/\.(jpg|jpeg|png|gif|jfif|bmp|webp)$/i', $file)) {
+
+ // 获取原始尺寸用于 PhotoSwipe
+ $size = @getimagesize($filePath);
+ if ($size === false) {
+ continue;
+ }
+
+ // 添加图片信息到结果数组
+ $fileName = basename($filePath);
+ $images[] = ['src' => $fileName, 'width' => $size[0], 'height' => $size[1]];
+
+ // 如果不是 "thumbnails" 目录,生成缩略图
+ if (!$isThumbnailsDir) {
+ // 【修改点 4】不再手动拼接 $thumbnailPath,直接调用 createThumbnail
+ // 此时 createThumbnail 会在后台进行目录检查和文件生成
+ createThumbnail($filePath);
+ }
+ }
+ }
+
+ return $images;
+}
+
+
+
+
+//album form
+if (isset($_GET['photoalbum']) && !FM_READONLY) {
+ fm_show_header(); // HEADER
+ fm_show_nav_path(FM_PATH); // current path
+ //get the allowed file extensions
+
+
+ ?>
+
+
+
+
+
+
+
+
+ $size[1]? 110: 90 ;
+ // $height = $size[0] > $size[1]? 90: 110 ;
+ // ----------------------------------------------------------------------
+ ?>
+
+
+
+
+
+
+
+
+
+
: , ', $copy_files) ?>
- :
+
:
/
@@ -1514,7 +2285,7 @@ function getUploadExt()
if (isset($_GET['copy']) && !isset($_GET['finish']) && !FM_READONLY) {
$copy = $_GET['copy'];
$copy = fm_clean_path($copy);
- if ($copy == '' || !file_exists(FM_ROOT_PATH . '/' . $copy)) {
+ if ($copy == '' || !file_exists(FM_ROOT_PATH . (FM_ROOT_PATH === '/' ? '' : '/') . $copy)) {
fm_set_msg(lng('File not found'), 'error');
$FM_PATH = FM_PATH;
fm_redirect(FM_SELF_URL . '?p=' . urlencode($FM_PATH));
@@ -1526,8 +2297,8 @@ function getUploadExt()
Copying
- Source path:
- Destination folder:
+ Source path:
+ Destination folder:
Copy
@@ -1545,7 +2316,7 @@ function getUploadExt()
foreach ($folders as $f) {
?>
-
+
:
:
-
File size:
-
MIME-type:
+
:
+
:
+
:
';
}
} elseif ($is_audio) {
@@ -1917,7 +2690,7 @@ class="edit-file">
@@ -2145,26 +2918,33 @@ class="edit-file">
'?');
+ $group = array('name' => '?');
if (function_exists('posix_getpwuid') && function_exists('posix_getgrgid')) {
- $owner = posix_getpwuid(fileowner($path . '/' . $f));
- $group = posix_getgrgid(filegroup($path . '/' . $f));
- if ($owner === false) {
- $owner = array('name' => '?');
- }
- if ($group === false) {
- $group = array('name' => '?');
+ try {
+ $owner_id = fileowner($path . ($path=== '/' ? '' : '/') . $f);
+ if ($owner_id != 0) {
+ $owner_info = posix_getpwuid($owner_id);
+ if ($owner_info) {
+ $owner = $owner_info;
+ }
+ }
+ $group_id = filegroup($path . ($path=== '/' ? '' : '/') . $f);
+ $group_info = posix_getgrgid($group_id);
+ if ($group_info) {
+ $group = $group_info;
+ }
+ } catch (Exception $e) {
+ error_log("exception:" . $e->getMessage());
}
- } else {
- $owner = array('name' => '?');
- $group = array('name' => '?');
}
?>
@@ -2179,7 +2959,7 @@ class="edit-file"> >
- ' . readlink($path . '/' . $f) . '' : '') ?>
+ ' . readlink($path . ($path=== '/' ? '' : '/') . $f) . '' : '') ?>
">
@@ -2199,7 +2979,10 @@ class="edit-file"> " href="#" onclick="rename('', '');return false;">
-
+
+
|
'?');
+ $group = array('name' => '?');
if (function_exists('posix_getpwuid') && function_exists('posix_getgrgid')) {
- $owner = posix_getpwuid(fileowner($path . '/' . $f));
- $group = posix_getgrgid(filegroup($path . '/' . $f));
- if ($owner === false) {
- $owner = array('name' => '?');
- }
- if ($group === false) {
- $group = array('name' => '?');
+ try {
+ $owner_id = fileowner($path . ($path=== '/' ? '' : '/') . $f);
+ if ($owner_id != 0) {
+ $owner_info = posix_getpwuid($owner_id);
+ if ($owner_info) {
+ $owner = $owner_info;
+ }
+ }
+ $group_id = filegroup($path . ($path=== '/' ? '' : '/') . $f);
+ $group_info = posix_getgrgid($group_id);
+ if ($group_info) {
+ $group = $group_info;
+ }
+ } catch (Exception $e) {
+ error_log("exception:" . $e->getMessage());
}
- } else {
- $owner = array('name' => '?');
- $group = array('name' => '?');
}
?>
@@ -2243,15 +3033,26 @@ class="edit-file"> >
-
-
+ if (in_array(strtolower(pathinfo($f, PATHINFO_EXTENSION)), array('gif', 'jpg', 'jpeg','jfif', 'png', 'bmp', 'ico', 'svg', 'webp', 'avif'))):
+ $sourcePath = FM_ROOT_PATH . (FM_ROOT_PATH=== '/' ? '' : '/') .FM_PATH ;
+ // 检查是否是 "thumbnails" 目录,跳过不必要的处理 (保留)
+ $isThumbnailsDir = (basename($sourcePath) === 'thumbnails');
+ if (!$isThumbnailsDir) {
+ createThumbnail($sourcePath. '/'. $f);
+ $imagePreview = fm_enc('?p=&proxy_file=1&path=' . urlencode($sourcePath. '/thumbnails/' . $f));
+ }
+ else {
+ $imagePreview = fm_enc('?p=&proxy_file=1&path=' . urlencode($sourcePath. '/'. $f));
+ }
+
+ ?>
+
- ' . readlink($path . '/' . $f) . '' : '') ?>
+ ' . readlink($path . ($path=== '/' ? '' : '/') . $f) . '' : '') ?>
">
@@ -2270,7 +3071,7 @@ class="edit-file"> ..."
href="?p=©=">
-
+
|
@@ -2377,7 +3178,7 @@ function fm_rdelete($path)
if (is_array($objects)) {
foreach ($objects as $file) {
if ($file != '.' && $file != '..') {
- if (!fm_rdelete($path . '/' . $file)) {
+ if (!fm_rdelete($path . ($path=== '/' ? '' : '/') . $file)) {
$ok = false;
}
}
@@ -2408,7 +3209,7 @@ function fm_rchmod($path, $filemode, $dirmode)
if (is_array($objects)) {
foreach ($objects as $file) {
if ($file != '.' && $file != '..') {
- if (!fm_rchmod($path . '/' . $file, $filemode, $dirmode)) {
+ if (!fm_rchmod($path . ($path=== '/' ? '' : '/') . $file, $filemode, $dirmode)) {
return false;
}
}
@@ -2595,6 +3396,10 @@ function get_absolute_path($path)
*/
function fm_clean_path($path, $trim = true)
{
+ if ($path == '/') {
+ return $path;
+ }
+
$path = $trim ? trim($path) : $path;
$path = trim($path, '\\/');
$path = str_replace(array('../', '..\\'), '', $path);
@@ -2612,6 +3417,10 @@ function fm_clean_path($path, $trim = true)
*/
function fm_get_parent_path($path)
{
+ if ($path == '/') {
+ return $path;
+ }
+
$path = fm_clean_path($path);
if ($path != '') {
$array = explode('/', $path);
@@ -2650,12 +3459,13 @@ function fm_get_display_path($file_path)
/**
* Check file is in exclude list
- * @param string $file
+ * @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($file)
+function fm_is_exclude_items($name, $path)
{
- $ext = strtolower(pathinfo($file, PATHINFO_EXTENSION));
+ $ext = strtolower(pathinfo($name, PATHINFO_EXTENSION));
if (isset($exclude_items) and sizeof($exclude_items)) {
unset($exclude_items);
}
@@ -2664,7 +3474,7 @@ function fm_is_exclude_items($file)
if (version_compare(PHP_VERSION, '7.0.0', '<')) {
$exclude_items = unserialize($exclude_items);
}
- if (!in_array($file, $exclude_items) && !in_array("*.$ext", $exclude_items)) {
+ if (!in_array($name, $exclude_items) && !in_array("*.$ext", $exclude_items) && !in_array($path, $exclude_items)) {
return true;
}
return false;
@@ -2887,6 +3697,7 @@ function fm_get_file_icon_class($path)
case 'gif':
case 'jpg':
case 'jpeg':
+ case 'jfif':
case 'jpc':
case 'jp2':
case 'jpx':
@@ -3084,7 +3895,7 @@ function fm_get_file_icon_class($path)
*/
function fm_get_image_exts()
{
- return array('ico', 'gif', 'jpg', 'jpeg', 'jpc', 'jp2', 'jpx', 'xbm', 'wbmp', 'png', 'bmp', 'tif', 'tiff', 'psd', 'svg', 'webp', 'avif');
+ return array('ico', 'gif', 'jpg', 'jpeg','jfif', 'jpc', 'jp2', 'jpx', 'xbm', 'wbmp', 'png', 'bmp', 'tif', 'tiff', 'psd', 'svg', 'webp', 'avif');
}
/**
@@ -3268,6 +4079,7 @@ function fm_get_file_mimes($extension)
$fileTypes['gif'] = 'image/gif';
$fileTypes['png'] = 'image/png';
$fileTypes['jpeg'] = 'image/jpg';
+ $fileTypes['jfif'] = 'image/jfif';
$fileTypes['jpg'] = 'image/jpg';
$fileTypes['webp'] = 'image/webp';
$fileTypes['avif'] = 'image/avif';
@@ -3313,7 +4125,7 @@ function fm_get_file_mimes($extension)
*/
function scan($dir = '', $filter = '')
{
- $path = FM_ROOT_PATH . '/' . $dir;
+ $path = FM_ROOT_PATH . (FM_ROOT_PATH=== '/' ? '' : '/') . $dir;
if ($path) {
$ite = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($path));
$rii = new RegexIterator($ite, "/(" . $filter . ")/i");
@@ -3506,12 +4318,12 @@ private function addDir($path)
if (is_array($objects)) {
foreach ($objects as $file) {
if ($file != '.' && $file != '..') {
- if (is_dir($path . '/' . $file)) {
- if (!$this->addDir($path . '/' . $file)) {
+ if (is_dir($path . ($path=== '/' ? '' : '/') . $file)) {
+ if (!$this->addDir($path . ($path=== '/' ? '' : '/') . $file)) {
return false;
}
- } elseif (is_file($path . '/' . $file)) {
- if (!$this->zip->addFile($path . '/' . $file)) {
+ } elseif (is_file($path . ($path=== '/' ? '' : '/') . $file)) {
+ if (!$this->zip->addFile($path . ($path=== '/' ? '' : '/') . $file)) {
return false;
}
}
@@ -3609,13 +4421,13 @@ private function addDir($path)
if (is_array($objects)) {
foreach ($objects as $file) {
if ($file != '.' && $file != '..') {
- if (is_dir($path . '/' . $file)) {
- if (!$this->addDir($path . '/' . $file)) {
+ if (is_dir($path . ($path=== '/' ? '' : '/') . $file)) {
+ if (!$this->addDir($path . ($path=== '/' ? '' : '/') . $file)) {
return false;
}
- } elseif (is_file($path . '/' . $file)) {
+ } elseif (is_file($path . ($path=== '/' ? '' : '/') . $file)) {
try {
- $this->tar->addFile($path . '/' . $file);
+ $this->tar->addFile($path . ($path=== '/' ? '' : '/') . $file);
} catch (Exception $e) {
return false;
}
@@ -3741,6 +4553,9 @@ function fm_show_nav_path($path)
+
+
+
@@ -5570,6 +6385,10 @@ function lng($txt)
$tr['en']['File or folder with this path already exists'] = 'File or folder with this path already exists';
$tr['en']['Are you sure want to rename?'] = 'Are you sure want to rename?';
$tr['en']['Are you sure want to'] = 'Are you sure want to';
+ $tr['en']['Date Modified'] = 'Date Modified';
+ $tr['en']['File size'] = 'File size';
+ $tr['en']['MIME-type'] = 'MIME-type';
+ $tr['en']['Album'] = 'Album';
$i18n = fm_get_translations($tr);
$tr = $i18n ? $i18n : $tr;
@@ -5580,4 +6399,4 @@ function lng($txt)
else return "$txt";
}
-?>
\ No newline at end of file
+?>