diff --git a/src/download.js b/src/download.js index 41f0da2..a7d6aea 100644 --- a/src/download.js +++ b/src/download.js @@ -1,27 +1,39 @@ +import { getHashString } from "./utils"; + export async function downloadDispatcher(request, env, ctx) { const url = new URL(request.url); const uri = url.pathname; const bucket = env.EXP_DATA; + const signatureBase = env.SIGNATURE_BASE; const adminSecret = env.ADMIN_SECRET; - if (request.headers.get('x-access-token') !== adminSecret) - return new Response(null, { status: 401, statusText: 'Unauthorized' }); + const key = uri.slice(10); // '/download/'.length + const signature = url.searchParams.get('signature'); + const timestamp = url.searchParams.get('timestamp'); + + if (!signature && request.headers.get('x-access-token') !== adminSecret) + return new Response(null, { status: 403, statusText: 'Forbidden' }); + else { + const hash = await getHashString(`${key}${timestamp}${signatureBase}`, 'SHA-1'); + if (signature !== hash) + return new Response(null, { status: 403, statusText: 'Forbidden' }); + } + try { - const key = uri.slice(10); // '/download/'.length const object = await bucket.get(key); if (object === null) return new Response(null, { status: 404, statusText: 'Not Found' }); const { body, etag, httpMetadata, customMetadata } = object; return new Response(body, { headers: { - etag: etag, + etag, 'content-type': httpMetadata.contentType, 'content-length': body.length, 'x-metadata': JSON.stringify(customMetadata), }}); - } catch (e) { return new Response(null, { status: 400 }); } + } catch (e) { return new Response(null, { status: 500 }); } } export default downloadDispatcher; \ No newline at end of file diff --git a/src/list.js b/src/list.js index 41792c4..8bf009d 100644 --- a/src/list.js +++ b/src/list.js @@ -1,17 +1,29 @@ +import { getHashString, adaptiveFilesize } from "./utils"; + export async function listDispatcher(request, env, ctx) { + const url = new URL(request.url); + const headers = request.headers; + const bucket = env.EXP_DATA; + const signatureBase = env.SIGNATURE_BASE; const adminSecret = env.ADMIN_SECRET; const badRequest = new Response(null, { status: 400 }); - if (request.headers.get('x-access-token') !== adminSecret) + const token = url.searchParams.get('accessToken') || headers.get('x-access-token'); + if (token !== adminSecret) return new Response(null, { status: 401, statusText: 'Unauthorized' }); + + const resType = url.searchParams.get('resType') || headers.get('x-res-type') || 'html'; + if (!['json', 'html'].includes(resType)) + return badRequest; + try { - const type = request.headers.get('x-list-type') || 'prefix'; - let pattern = request.headers.get('x-pattern') || ''; + const type = url.searchParams.get('type') || headers.get('x-type') || 'prefix'; + let pattern = url.searchParams.get('pattern') || headers.get('x-pattern') || ''; let objects = []; let truncated = true; - let cursor = ""; + let cursor; switch (type) { case 'prefix': while (truncated) { @@ -37,12 +49,47 @@ export async function listDispatcher(request, env, ctx) { return badRequest; } - const res = JSON.stringify(objects, ['key', 'etag', 'size', 'uploaded']); - return new Response(res, { status: 200 }); - } catch (e) { - console.error(e); - return badRequest; - } + const timestamp = Date.now(); + const baseUrl = `${url.protocol}//${url.hostname}:${url.port}`; + const res = await Promise.all(objects.map(async object => { + const { key, etag, size, uploaded } = object; + const hash = await getHashString(`${key}${timestamp}${signatureBase}`, 'SHA-1'); + return { key, etag, size, uploaded, + presignedUrl: `${baseUrl}/download/${key}?signature=${hash}×tamp=${timestamp}` }; + })); + console.log(objects[0]); + if (resType === 'json') + return new Response(JSON.stringify(res), { status: 200 }); + else if (resType === 'html') { + // Generate a table of links with URLs, sizes, and upload dates + const body = res.map(obj => ` + + ${obj.key} + ${adaptiveFilesize(obj.size)} + ${obj.uploaded.toISOString()} + + `).join(''); + return new Response(` + + + Object List + + + + + + + + + + ${body} +
KeySizeUploaded
+ + + `, { status: 200, headers: { 'content-type': 'text/html' } }); + } + + } catch (e) { console.log(e); return badRequest; } } export default listDispatcher; \ No newline at end of file diff --git a/src/utils.js b/src/utils.js index 9dee904..3d37b9d 100644 --- a/src/utils.js +++ b/src/utils.js @@ -4,4 +4,10 @@ export async function getHashString(string, method = 'SHA-256') { const hash = await crypto.subtle.digest(method, data); const hashArray = Array.from(new Uint8Array(hash)); return hashArray.map(b => b.toString(16).padStart(2, '0')).join(''); +} + +export function adaptiveFilesize(bytes, digits = 2) { + if (bytes < 1024) return `${bytes} B`; + else if (bytes < 1048576) return `${(bytes / 1024).toFixed(digits)} KB`; + else return `${(bytes / 1048576).toFixed(digits)} MB`; } \ No newline at end of file