fix: 收紧 GUI 安全边界

This commit is contained in:
2026-06-20 13:51:06 +08:00
parent ff73470142
commit a1b791e6ef
10 changed files with 112 additions and 67 deletions
+8 -4
View File
@@ -1,8 +1,8 @@
// views/browser.js — file/folder listing with breadcrumbs, mkdir, upload, delete, rename.
import { post, uploadFile, downloadURL } from "../rc.js?v=review-fixes-1";
import { escapeHtml, toast, formatBytes, formatTime } from "../state.js?v=review-fixes-1";
import { t } from "../i18n.js?v=review-fixes-1";
import { post, uploadFile, downloadURL } from "../rc.js?v=security-fixes-1";
import { escapeHtml, toast, formatBytes, formatTime } from "../state.js?v=security-fixes-1";
import { t } from "../i18n.js?v=security-fixes-1";
export async function renderBrowse({ remote, path }) {
const app = document.getElementById("app");
@@ -169,7 +169,7 @@ function row(fs, path, item) {
return `
<tr>
<td class="col-name">
<a href="${downloadURL(fs, path, item.Name)}" download="${escapeHtml(item.Name)}">${icon("file")} ${escapeHtml(item.Name)}</a>
<a href="${escapeAttr(downloadURL(fs, path, item.Name))}" download="${escapeAttr(item.Name)}">${icon("file")} ${escapeHtml(item.Name)}</a>
</td>
<td class="col-num col-mono">${escapeHtml(formatBytes(item.Size))}</td>
<td class="col-mono">${escapeHtml(formatTime(item.ModTime))}</td>
@@ -296,3 +296,7 @@ export function openModal(title, fields, onSubmit) {
if (first) first.focus();
});
}
function escapeAttr(s) {
return escapeHtml(s);
}
+8 -7
View File
@@ -9,9 +9,9 @@
// OAuth backends (option named "token" with IsPassword) get a banner
// and disabled submit — user must run `rclone config` in a terminal.
import { post } from "../rc.js?v=review-fixes-1";
import { escapeHtml, getState, setState, toast } from "../state.js?v=review-fixes-1";
import { t } from "../i18n.js?v=review-fixes-1";
import { post } from "../rc.js?v=security-fixes-1";
import { escapeHtml, getState, setState, toast } from "../state.js?v=security-fixes-1";
import { t } from "../i18n.js?v=security-fixes-1";
// --- Route entrypoints ---
@@ -25,7 +25,7 @@ export async function renderConfigureNew({ provider = "" }) {
document.getElementById("app").innerHTML = `
<div class="empty">
<h3>${t("error.unknown_remote")}</h3>
<p>${t("error.no_such_provider", provider)}</p>
<p>${t("error.no_such_provider", escapeHtml(provider))}</p>
<p><a href="#/configure/new">← ${t("configure.new_title")}</a></p>
</div>`;
return;
@@ -53,7 +53,7 @@ export async function renderConfigureEdit({ remote }) {
app.innerHTML = `
<div class="empty">
<h3>${t("error.unknown_remote")}</h3>
<p>${t("error.no_such_remote", remote)}</p>
<p>${t("error.no_such_remote", escapeHtml(remote))}</p>
<p><a href="#/remotes">← ${t("nav.remotes")}</a></p>
</div>`;
return;
@@ -155,6 +155,7 @@ async function renderForm({ provider, mode, remoteName = "", currentValues = {}
const requiresOAuth = provider.Options.some(
(o) => o.Name === "token" && (o.IsPassword || o.Sensitive),
);
const blocksCreate = requiresOAuth && !isEdit;
// Partition options into required, optional-basic, optional-advanced.
const required = provider.Options.filter((o) => o.Required && !o.Hide);
@@ -206,7 +207,7 @@ async function renderForm({ provider, mode, remoteName = "", currentValues = {}
</div>
<div class="toolbar">
<button type="submit" class="btn btn-primary" ${requiresOAuth ? "disabled" : ""}>
<button type="submit" class="btn btn-primary" ${blocksCreate ? "disabled" : ""}>
${isEdit ? t("configure.save") : t("configure.create")}
</button>
<a class="btn btn-secondary" href="#/remotes">${t("configure.cancel")}</a>
@@ -255,7 +256,7 @@ async function renderForm({ provider, mode, remoteName = "", currentValues = {}
form.addEventListener("submit", async (e) => {
e.preventDefault();
if (requiresOAuth) return;
if (blocksCreate) return;
const name = form.elements._remote_name.value.trim();
if (!name) return;
+4 -4
View File
@@ -1,14 +1,14 @@
// views/jobs.js — submit sync/copy/move jobs and manage recurring transfers.
import { post } from "../rc.js?v=review-fixes-1";
import { post } from "../rc.js?v=security-fixes-1";
import {
toast,
formatBytes,
formatSpeed,
formatDuration,
escapeHtml,
} from "../state.js?v=review-fixes-1";
import { t } from "../i18n.js?v=review-fixes-1";
} from "../state.js?v=security-fixes-1";
import { t } from "../i18n.js?v=security-fixes-1";
import {
createOneTimeJob,
createRecurringJob,
@@ -21,7 +21,7 @@ import {
runRecurringJobNow,
stopOneTimeJob,
stopRecurringJob,
} from "../jobs_api.js?v=review-fixes-1";
} from "../jobs_api.js?v=security-fixes-1";
let pollTimer = null;
const LOCAL_FS_VALUE = "__local__";
+3 -3
View File
@@ -1,8 +1,8 @@
// views/remotes.js — connector-tile grid of configured remotes with CRUD.
import { post } from "../rc.js?v=review-fixes-1";
import { escapeHtml, getState, setState, toast } from "../state.js?v=review-fixes-1";
import { t } from "../i18n.js?v=review-fixes-1";
import { post } from "../rc.js?v=security-fixes-1";
import { escapeHtml, getState, setState, toast } from "../state.js?v=security-fixes-1";
import { t } from "../i18n.js?v=security-fixes-1";
export async function renderRemotes() {
const app = document.getElementById("app");