🎨 Improve error handling and security for plugin data storage methods (#16717)

- Check if storageName contains path separators to prevent path injection
- saveData and removeData return complete IWebSocketData objects with code, msg, and data fields for result checking
- saveData and removeData immediately modify this.data[storageName] at function start, so even if the operation fails, subsequent use of this.data[storageName] is not affected
- All methods always resolve
This commit is contained in:
Jeffrey Chen 2025-12-29 11:01:38 +08:00 committed by GitHub
parent 3c00f5a3d1
commit c243fea81c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -259,12 +259,17 @@ export class Plugin {
}
public loadData(storageName: string) {
if (storageName.includes("/") || storageName.includes("\\")) {
console.error(`plugin ${this.name} loadData failed: storageName cannot contain path separators`);
return Promise.resolve(this.data[storageName]);
}
if (typeof this.data[storageName] === "undefined") {
this.data[storageName] = "";
}
return new Promise((resolve) => {
fetchPost("/api/file/getFile", {path: `/data/storage/petal/${this.name}/${storageName}`}, (response) => {
if (response.code !== 404) {
if (response.code !== 404 && response.code !== 403) {
this.data[storageName] = response;
}
resolve(this.data[storageName]);
@ -273,34 +278,78 @@ export class Plugin {
}
public saveData(storageName: string, data: any) {
if (storageName.includes("/") || storageName.includes("\\")) {
console.error(`plugin ${this.name} saveData failed: storageName cannot contain path separators`);
return Promise.resolve({
code: -1,
msg: "storageName cannot contain path separators",
data: null
} as IWebSocketData);
}
this.data[storageName] = data;
if (window.siyuan.config.readonly || window.siyuan.isPublish) {
return;
console.warn(`plugin ${this.name} saveData failed: Readonly mode or publish mode`);
return Promise.resolve({
code: 403,
msg: "Readonly mode or publish mode",
data: null
} as IWebSocketData);
}
return new Promise((resolve) => {
const pathString = `/data/storage/petal/${this.name}/${storageName}`;
let file: File;
if (typeof data === "object") {
file = new File([new Blob([JSON.stringify(data)], {
type: "application/json"
})], pathString.split("/").pop());
} else {
file = new File([new Blob([data])], pathString.split("/").pop());
try {
if (typeof data === "object") {
file = new File([new Blob([JSON.stringify(data)], {
type: "application/json"
})], pathString.split("/").pop());
} else {
file = new File([new Blob([data])], pathString.split("/").pop());
}
} catch (e) {
console.error(`plugin ${this.name} saveData failed:`, e);
resolve({
code: -1,
msg: e instanceof Error ? e.message : String(e),
data: null
} as IWebSocketData);
return;
}
const formData = new FormData();
formData.append("path", pathString);
formData.append("file", file);
formData.append("isDir", "false");
fetchPost("/api/file/putFile", formData, (response) => {
this.data[storageName] = data;
if (typeof response === "object" && response.code !== 0) {
console.error(`plugin ${this.name} saveData failed:`, response);
}
resolve(response);
});
});
}
public removeData(storageName: string) {
if (storageName.includes("/") || storageName.includes("\\")) {
console.error(`plugin ${this.name} removeData failed: storageName cannot contain path separators`);
return Promise.resolve({
code: -1,
msg: "storageName cannot contain path separators",
data: null
} as IWebSocketData);
}
delete this.data[storageName];
if (window.siyuan.config.readonly || window.siyuan.isPublish) {
return;
console.warn(`plugin ${this.name} removeData failed: Readonly mode or publish mode`);
return Promise.resolve({
code: 403,
msg: "Readonly mode or publish mode",
data: null
} as IWebSocketData);
}
return new Promise((resolve) => {
@ -308,7 +357,9 @@ export class Plugin {
this.data = {};
}
fetchPost("/api/file/removeFile", {path: `/data/storage/petal/${this.name}/${storageName}`}, (response) => {
delete this.data[storageName];
if (response.code !== 0 && response.code !== 404) {
console.error(`plugin ${this.name} removeData failed:`, response);
}
resolve(response);
});
});