// GoTunnel File Manager Plugin v2.0.0 // 提供完整的 Web 文件管理界面 function metadata() { return { name: "file-manager", version: "2.0.0", description: "Web 文件管理器,提供远程文件浏览、上传、下载和管理功能", author: "GoTunnel Official", type: "app", run_at: "client" }; } var rootPath = "."; function start() { rootPath = config("root_path") || "."; log("File Manager started, root: " + rootPath); } function stop() { log("File Manager stopped"); } function handleConn(conn) { http.serve(conn, handleRequest); } // ============================================================================ // 请求路由 // ============================================================================ function handleRequest(req) { var path = req.path; var method = req.method; // 静态页面 if (path === "/" || path === "/index.html") { return { status: 200, contentType: "text/html", body: getIndexHTML() }; } // API 路由 if (path === "/api/list" && method === "GET") { return apiList(req); } if (path === "/api/stat" && method === "GET") { return apiStat(req); } if (path === "/api/read" && method === "POST") { return apiRead(req); } if (path === "/api/write" && method === "POST") { return apiWrite(req); } if (path === "/api/mkdir" && method === "POST") { return apiMkdir(req); } if (path === "/api/delete" && method === "POST") { return apiDelete(req); } if (path === "/api/rename" && method === "POST") { return apiRename(req); } if (path === "/api/download" && method === "GET") { return apiDownload(req); } return jsonError(404, "Not Found"); } // ============================================================================ // API 处理函数 // ============================================================================ function apiList(req) { var dir = getQueryParam(req.path, "path") || "/"; var fullPath = resolvePath(dir); if (!fullPath) { return jsonError(403, "Access denied"); } var result = fs.readDir(fullPath); if (result.error) { return jsonError(500, result.error); } var entries = []; for (var i = 0; i < result.entries.length; i++) { var e = result.entries[i]; entries.push({ name: e.name, isDir: e.isDir, size: e.size || 0 }); } // 排序:文件夹在前,然后按名称 entries.sort(function (a, b) { if (a.isDir !== b.isDir) return a.isDir ? -1 : 1; return a.name.localeCompare(b.name); }); return jsonSuccess({ path: dir, entries: entries }); } function apiStat(req) { var path = getQueryParam(req.path, "path") || "/"; var fullPath = resolvePath(path); if (!fullPath) { return jsonError(403, "Access denied"); } var result = fs.stat(fullPath); if (result.error) { return jsonError(404, result.error); } return jsonSuccess({ name: result.name, size: result.size, isDir: result.isDir, modTime: result.modTime }); } function apiRead(req) { var body = parseBody(req.body); var path = body.path; if (!path) { return jsonError(400, "path required"); } var fullPath = resolvePath(path); if (!fullPath) { return jsonError(403, "Access denied"); } var result = fs.readFile(fullPath); if (result.error) { return jsonError(500, result.error); } return jsonSuccess({ content: result.data }); } function apiWrite(req) { var body = parseBody(req.body); var path = body.path; var content = body.content; if (!path) { return jsonError(400, "path required"); } var fullPath = resolvePath(path); if (!fullPath) { return jsonError(403, "Access denied"); } var result = fs.writeFile(fullPath, content || ""); if (!result.ok) { return jsonError(500, result.error); } return jsonSuccess({ message: "File saved" }); } function apiMkdir(req) { var body = parseBody(req.body); var path = body.path; if (!path) { return jsonError(400, "path required"); } var fullPath = resolvePath(path); if (!fullPath) { return jsonError(403, "Access denied"); } var result = fs.mkdir(fullPath); if (!result.ok) { return jsonError(500, result.error); } return jsonSuccess({ message: "Directory created" }); } function apiDelete(req) { var body = parseBody(req.body); var path = body.path; if (!path) { return jsonError(400, "path required"); } var fullPath = resolvePath(path); if (!fullPath) { return jsonError(403, "Access denied"); } var result = fs.remove(fullPath); if (!result.ok) { return jsonError(500, result.error); } return jsonSuccess({ message: "Deleted" }); } function apiRename(req) { var body = parseBody(req.body); var oldPath = body.path; var newPath = body.newPath; if (!oldPath || !newPath) { return jsonError(400, "path and newPath required"); } var fullOldPath = resolvePath(oldPath); var fullNewPath = resolvePath(newPath); if (!fullOldPath || !fullNewPath) { return jsonError(403, "Access denied"); } // 读取旧文件 var stat = fs.stat(fullOldPath); if (stat.error) { return jsonError(404, stat.error); } if (stat.isDir) { return jsonError(400, "Cannot rename directories"); } var content = fs.readFile(fullOldPath); if (content.error) { return jsonError(500, content.error); } // 写入新位置 var writeResult = fs.writeFile(fullNewPath, content.data); if (!writeResult.ok) { return jsonError(500, writeResult.error); } // 删除旧文件 fs.remove(fullOldPath); return jsonSuccess({ message: "Renamed" }); } function apiDownload(req) { var path = getQueryParam(req.path, "path"); if (!path) { return jsonError(400, "path required"); } var fullPath = resolvePath(path); if (!fullPath) { return jsonError(403, "Access denied"); } var result = fs.readFile(fullPath); if (result.error) { return jsonError(404, result.error); } var filename = path.split("/").pop() || "download"; return { status: 200, contentType: "application/octet-stream", body: result.data }; } // ============================================================================ // 工具函数 // ============================================================================ function resolvePath(path) { // 规范化路径 path = path || "/"; // 移除开头的斜杠 while (path.charAt(0) === "/") { path = path.substring(1); } // 检查路径穿越攻击 if (path.indexOf("..") !== -1) { return null; } // 拼接根路径 if (path === "") { return rootPath; } return rootPath + "/" + path; } function getQueryParam(path, key) { var idx = path.indexOf("?"); if (idx === -1) return ""; var query = path.substring(idx + 1); var pairs = query.split("&"); for (var i = 0; i < pairs.length; i++) { var pair = pairs[i].split("="); if (pair[0] === key) { return decodeURIComponent(pair[1] || ""); } } return ""; } function parseBody(body) { try { return JSON.parse(body || "{}"); } catch (e) { return {}; } } function jsonSuccess(data) { return { status: 200, contentType: "application/json", body: http.json({ success: true, data: data }) }; } function jsonError(status, message) { return { status: status, contentType: "application/json", body: http.json({ success: false, error: message }) }; } // ============================================================================ // Web UI - HTML/CSS/JavaScript // ============================================================================ function getIndexHTML() { return `