mirror of
https://github.com/kmc7468/arkvault.git
synced 2026-11-04 08:10:28 +09:00
상위 디렉터리 및 카테고리의 소유권을 검증하지 않던 문제 등 버그 수정 및 Vitest 기반 테스트 추가
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
67989f4741
commit
ed732be999
31 files changed
+2057
-42
No files matched your search
@@ -1,8 +1,8 @@
|
||||
import { DECRYPTED_FILE_URL_PREFIX, CHUNK_SIZE, ENCRYPTED_CHUNK_SIZE } from "../constants";
|
||||
import { decryptChunk, getEncryptedRange, getDecryptedSize } from "../modules/crypto";
|
||||
import { parseRangeHeader, getContentRangeHeader } from "../modules/http";
|
||||
import { getFileMetadata } from "../modules/metadata";
|
||||
import { getFile } from "../modules/opfs";
|
||||
import { fileMetadataStore } from "../stores";
|
||||
import type { FileMetadata } from "../types";
|
||||
|
||||
const createResponse = (
|
||||
@@ -131,15 +131,15 @@ const streamFromServer = async (
|
||||
);
|
||||
};
|
||||
|
||||
const decryptFileHandler = async (request: Request) => {
|
||||
const decryptFileHandler = async (request: Request, clientId: string) => {
|
||||
const url = new URL(request.url);
|
||||
const fileId = parseInt(url.pathname.slice(DECRYPTED_FILE_URL_PREFIX.length), 10);
|
||||
if (isNaN(fileId)) {
|
||||
throw new Response("Invalid file id", { status: 400 });
|
||||
return new Response("Invalid file id", { status: 400 });
|
||||
}
|
||||
|
||||
const downloadFilename = url.searchParams.get("download") ?? undefined;
|
||||
const metadata = fileMetadataStore.get(fileId);
|
||||
const metadata = await getFileMetadata(fileId, clientId);
|
||||
const range = parseRangeHeader(request.headers.get("Range"));
|
||||
const cache = await getFile(`/cache/${fileId}`);
|
||||
if (cache) {
|
||||
|
||||
@@ -24,7 +24,7 @@ self.addEventListener("message", (event) => {
|
||||
self.addEventListener("fetch", (event) => {
|
||||
const url = new URL(event.request.url);
|
||||
if (url.pathname.startsWith(DECRYPTED_FILE_URL_PREFIX)) {
|
||||
event.respondWith(decryptFile(event.request));
|
||||
event.respondWith(decryptFile(event.request, event.clientId));
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
import { self } from "$app/service-worker";
|
||||
import { fileMetadataStore } from "../stores";
|
||||
import type { FileMetadata, ServiceWorkerResponse } from "../types";
|
||||
|
||||
const METADATA_REQUEST_TIMEOUT_MS = 3000;
|
||||
|
||||
const requestFromClient = (client: Client, fileId: number) => {
|
||||
return new Promise<FileMetadata | null>((resolve) => {
|
||||
const channel = new MessageChannel();
|
||||
const timeout = setTimeout(() => {
|
||||
channel.port1.close();
|
||||
resolve(null);
|
||||
}, METADATA_REQUEST_TIMEOUT_MS);
|
||||
|
||||
channel.port1.onmessage = (event: MessageEvent<FileMetadata | null>) => {
|
||||
clearTimeout(timeout);
|
||||
channel.port1.close();
|
||||
resolve(event.data);
|
||||
};
|
||||
client.postMessage(
|
||||
{ type: "decryption-metadata-request", fileId } satisfies ServiceWorkerResponse,
|
||||
[channel.port2],
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
export const getFileMetadata = async (fileId: number, clientId: string) => {
|
||||
const cached = fileMetadataStore.get(fileId);
|
||||
if (cached) return cached;
|
||||
|
||||
// The worker might have been restarted since the client prepared the decryption
|
||||
const requestingClient = clientId ? await self.clients.get(clientId) : undefined;
|
||||
const windowClients = await self.clients.matchAll({ type: "window" });
|
||||
const candidates = [
|
||||
...(requestingClient ? [requestingClient] : []),
|
||||
...windowClients.filter(({ id }) => id !== requestingClient?.id),
|
||||
];
|
||||
|
||||
for (const client of candidates) {
|
||||
const metadata = await requestFromClient(client, fileId);
|
||||
if (metadata) {
|
||||
fileMetadataStore.set(fileId, metadata);
|
||||
return metadata;
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -1,3 +1,25 @@
|
||||
import type { FileMetadata } from "./types";
|
||||
|
||||
export const fileMetadataStore = new Map<number, FileMetadata>();
|
||||
const MAX_FILE_METADATA_ENTRIES = 32;
|
||||
|
||||
const fileMetadata = new Map<number, FileMetadata>();
|
||||
|
||||
export const fileMetadataStore = {
|
||||
get(fileId: number) {
|
||||
const metadata = fileMetadata.get(fileId);
|
||||
if (metadata) {
|
||||
// Move to the end to keep the most recently used entries
|
||||
fileMetadata.delete(fileId);
|
||||
fileMetadata.set(fileId, metadata);
|
||||
}
|
||||
return metadata;
|
||||
},
|
||||
set(fileId: number, metadata: FileMetadata) {
|
||||
fileMetadata.delete(fileId);
|
||||
fileMetadata.set(fileId, metadata);
|
||||
|
||||
while (fileMetadata.size > MAX_FILE_METADATA_ENTRIES) {
|
||||
fileMetadata.delete(fileMetadata.keys().next().value!);
|
||||
}
|
||||
},
|
||||
};
|
||||
Reference in new issue
Block a user