mirror of
https://github.com/getpaseo/paseo.git
synced 2026-10-14 13:22:37 +00:00
fix(app): enforce the session download size limit during the read
The token size is sampled before the file is read. Pass the download byte budget through the actual read so a changed file cannot bypass the in-memory limit. Co-authored-by: Michael Vandesompel <mv@droggol.be> Co-authored-by: YASICJUNWOO <joonoo3@naver.com> Co-authored-by: JrDw0 <JrDw0@users.noreply.github.com>
This commit is contained in:
co-authored by
Michael Vandesompel
YASICJUNWOO
JrDw0
parent
8f85e85f35
commit
629aea2608
@@ -254,7 +254,7 @@ export function useFileExplorerActions(params: { serverId: string } & FileExplor
|
||||
);
|
||||
|
||||
const requestFileBytes = useCallback(
|
||||
async (path: string) => {
|
||||
async (path: string, maxBytes: number) => {
|
||||
if (!normalizedWorkspaceRoot) {
|
||||
throw new Error(t("workspace.fileExplorer.states.unavailable"));
|
||||
}
|
||||
@@ -263,7 +263,7 @@ export function useFileExplorerActions(params: { serverId: string } & FileExplor
|
||||
}
|
||||
// No request timeout: a large file over a slow relay legitimately takes longer than the
|
||||
// 60 s default, and a dead connection still rejects on disconnect.
|
||||
return client.readFile(normalizedWorkspaceRoot, path, undefined, undefined, 0);
|
||||
return client.readFile(normalizedWorkspaceRoot, path, undefined, maxBytes, 0);
|
||||
},
|
||||
[client, normalizedWorkspaceRoot, t],
|
||||
);
|
||||
|
||||
@@ -142,6 +142,29 @@ describe("download store transport selection", () => {
|
||||
]);
|
||||
});
|
||||
|
||||
it("bounds the session read even when the token reports a smaller file", async () => {
|
||||
const readRequests: Array<{ path: string; maxBytes: number }> = [];
|
||||
const saver = createFileSaver();
|
||||
await useDownloadStore.getState().startDownload({
|
||||
serverId: "srv_remote",
|
||||
scopeId: "workspace-1",
|
||||
fileName: FILE_NAME,
|
||||
path: FILE_PATH,
|
||||
daemonProfile: profileWith([relayConnection]),
|
||||
activeConnectionId: relayConnection.id,
|
||||
requestFileDownloadToken: createTokenRequest(1).request,
|
||||
readFile: async (path: string, maxBytes: number) => {
|
||||
readRequests.push({ path, maxBytes });
|
||||
return { bytes: FILE_BYTES, size: FILE_BYTES.byteLength };
|
||||
},
|
||||
saveFile: saver.save,
|
||||
});
|
||||
expect(readRequests).toEqual([{ path: FILE_PATH, maxBytes: MAX_SESSION_DOWNLOAD_BYTES }]);
|
||||
expect(saver.savedFiles).toEqual([
|
||||
{ bytes: FILE_BYTES, fileName: FILE_NAME, mimeType: MIME_TYPE },
|
||||
]);
|
||||
});
|
||||
|
||||
it("ignores a directTcp entry that is not the active connection", async () => {
|
||||
const reader = createFileReader(async () => ({
|
||||
bytes: FILE_BYTES,
|
||||
|
||||
@@ -57,7 +57,7 @@ interface DownloadState {
|
||||
error: string | null;
|
||||
}>;
|
||||
/** Reads the whole file over the session socket; used when no direct HTTP endpoint is active. */
|
||||
readFile: (path: string) => Promise<{ bytes: Uint8Array; size: number }>;
|
||||
readFile: (path: string, maxBytes: number) => Promise<{ bytes: Uint8Array; size: number }>;
|
||||
saveFile?: typeof saveDownloadedBytes;
|
||||
}) => Promise<void>;
|
||||
|
||||
@@ -121,7 +121,7 @@ export const useDownloadStore = create<DownloadState>()((set, get) => ({
|
||||
i18n.t("composer.errors.fileTooLarge", { fileName: resolvedFileName, size: "64 MB" }),
|
||||
);
|
||||
}
|
||||
const { bytes, size } = await readFile(path);
|
||||
const { bytes, size } = await readFile(path, MAX_SESSION_DOWNLOAD_BYTES);
|
||||
// A daemon without a binary channel answers `encoding: "none"` for a binary file: no
|
||||
// content, real size. Never save that as a complete download.
|
||||
if (bytes.byteLength !== size) {
|
||||
|
||||
Reference in New Issue
Block a user