From f488e4f2673af01bf868fbc71ca010b360d3f8c5 Mon Sep 17 00:00:00 2001 From: kptdobe Date: Mon, 1 Dec 2025 15:23:42 +0100 Subject: [PATCH 1/4] feat: handle bad requests --- src/handlers/unknown.js | 4 ++-- src/index.js | 10 ++++++++-- src/utils/daCtx.js | 7 +++++++ test/handlers/unknown.test.js | 3 +-- test/index.test.js | 30 ++++++++++++++++++++++++++---- test/storage/utils/list.test.js | 25 ++++++++++--------------- 6 files changed, 54 insertions(+), 25 deletions(-) diff --git a/src/handlers/unknown.js b/src/handlers/unknown.js index dbe19289..74c9eb7d 100644 --- a/src/handlers/unknown.js +++ b/src/handlers/unknown.js @@ -11,6 +11,6 @@ */ export default function unknownHandler() { - const body = JSON.stringify({ message: 'Unknown method. Please see: https://docs.da.live for more information.' }); - return { body, status: 501 }; + const body = JSON.stringify({ message: 'Bad request. Please see: https://docs.da.live for more information.' }); + return { body, status: 400 }; } diff --git a/src/index.js b/src/index.js index 20cfb66b..821b9505 100644 --- a/src/index.js +++ b/src/index.js @@ -29,7 +29,13 @@ export default { return daResp({ status: 204 }); } - const daCtx = await getDaCtx(req, env); + let daCtx; + try { + daCtx = await getDaCtx(req, env); + } catch (e) { + return daResp(unknownHandler()); + } + const { authorized, key } = daCtx; if (!authorized) { const status = daCtx.users[0].email === 'anonymous' ? 401 : 403; @@ -60,7 +66,7 @@ export default { respObj = unknownHandler(); } - if (!respObj) return daResp({ status: 404 }); + if (!respObj) return daResp(unknownHandler()); return daResp(respObj, daCtx); }, diff --git a/src/utils/daCtx.js b/src/utils/daCtx.js index 0e35887f..b4f8640d 100644 --- a/src/utils/daCtx.js +++ b/src/utils/daCtx.js @@ -59,6 +59,13 @@ export default async function getDaCtx(req, env) { const path = parts.filter((part) => part !== ''); const keyBase = path.join('/'); + const pnlc = pathname.toLocaleLowerCase(); + const validPath = `/${api}/${org}/${keyBase}`; + + if (!org || !(pnlc === validPath || pnlc === `${validPath}/`)) { + throw new Error('Invalid path'); + } + // Get the final source name daCtx.filename = path.pop() || ''; diff --git a/test/handlers/unknown.test.js b/test/handlers/unknown.test.js index cee3586d..8f916139 100644 --- a/test/handlers/unknown.test.js +++ b/test/handlers/unknown.test.js @@ -15,7 +15,6 @@ import unknownHandler from '../../src/handlers/unknown.js'; describe('unknownHandler', () => { it('should return unknown response', async () => { const result = await unknownHandler({}); - assert.strictEqual(result.status, 501); - assert.strictEqual(result.body.includes('Unknown method'), true); + assert.strictEqual(result.status, 400); }); }); diff --git a/test/index.test.js b/test/index.test.js index b86ce7a6..82ca4456 100644 --- a/test/index.test.js +++ b/test/index.test.js @@ -25,7 +25,7 @@ describe('fetch', () => { it('should return a response object for unknown', async () => { const resp = await handler.fetch({ url: 'https://www.example.com', method: 'BLAH' }, {}); - assert.strictEqual(resp.status, 501); + assert.strictEqual(resp.status, 400); }); it('should return 401 when not authorized and not logged in', async () => { @@ -50,8 +50,30 @@ describe('fetch', () => { assert.strictEqual(resp.status, 403); }); - it('return 404 for unknown get route', async () => { - const resp = await handler.fetch({ method: 'GET', url: 'http://www.example.com/' }, {}); - assert.strictEqual(resp.status, 404); +}); + +describe('invalid routes', () => { + const fetchStatus = async (path, method) => { + const resp = await handler.fetch({ method, url: `http://www.sample.com${path}` }, {}); + console.log('resp', `http://www.sample.com${path}`, resp.status); + return resp.status; + }; + + const test = async (path) => { + const methods = ['OPTIOMS', 'GET', 'POST', 'PUT', 'DELETE']; + for (const method of methods) { + const status = await fetchStatus(path, method); + assert.strictEqual(status, 400); + } + }; + + it('return 400 for invalid paths', async () => { + await test('/'); + await test('/source/owner'); + await test('/source//owner/repo/path/file.html'); + await test('/source/owner//repo/path/file.html'); + await test('/source/owner/repo//path/file.html'); + await test('/source/owner/repo/path//file.html'); + await test('/unknown/owner/repo/path/file.html'); }); }); diff --git a/test/storage/utils/list.test.js b/test/storage/utils/list.test.js index f4f3919e..daef4255 100644 --- a/test/storage/utils/list.test.js +++ b/test/storage/utils/list.test.js @@ -13,7 +13,6 @@ import assert from 'node:assert'; import sinon from 'sinon'; -import getDaCtx from '../../../src/utils/daCtx.js'; import formatList, { listCommand } from '../../../src/storage/utils/list.js'; const MOCK = { @@ -61,12 +60,8 @@ const MOCK = { ], }; -const req = new Request('https://example.com/source/adobecom'); - -const daCtx = getDaCtx(req, {}); - describe('Format object list', () => { - const list = formatList(MOCK, daCtx); + const list = formatList(MOCK); it('should return a true folder / common prefix', () => { assert.strictEqual(list[0].name, 'blog'); @@ -98,21 +93,21 @@ describe('Format object list', () => { it('should handle empty CommonPrefixes', () => { const emptyMock = { Contents: MOCK.Contents }; - const result = formatList(emptyMock, daCtx); + const result = formatList(emptyMock); assert(Array.isArray(result)); assert(result.length > 0); }); it('should handle empty Contents', () => { const emptyMock = { CommonPrefixes: MOCK.CommonPrefixes }; - const result = formatList(emptyMock, daCtx); + const result = formatList(emptyMock); assert(Array.isArray(result)); assert(result.length > 0); }); it('should handle both empty CommonPrefixes and Contents', () => { const emptyMock = {}; - const result = formatList(emptyMock, daCtx); + const result = formatList(emptyMock); assert(Array.isArray(result)); assert.strictEqual(result.length, 0); }); @@ -124,7 +119,7 @@ describe('Format object list', () => { { Prefix: 'normal-folder/' }, ], }; - const result = formatList(mockWithExtensionFolder, daCtx); + const result = formatList(mockWithExtensionFolder); const extensionFolder = result.find((item) => item.name === 'file.jpg'); assert.strictEqual(extensionFolder, undefined); const normalFolder = result.find((item) => item.name === 'normal-folder'); @@ -140,7 +135,7 @@ describe('Format object list', () => { }, ], }; - const result = formatList(mockWithComplexFile, daCtx); + const result = formatList(mockWithComplexFile); assert.strictEqual(result.length, 0); }); @@ -153,7 +148,7 @@ describe('Format object list', () => { }, ], }; - const result = formatList(mockWithHiddenFile, daCtx); + const result = formatList(mockWithHiddenFile); assert.strictEqual(result.length, 0); }); @@ -166,7 +161,7 @@ describe('Format object list', () => { }, ], }; - const result = formatList(mockWithProps, daCtx); + const result = formatList(mockWithProps); const propsItem = result.find((item) => item.name === 'test'); assert(propsItem); assert.strictEqual(propsItem.ext, undefined); @@ -183,7 +178,7 @@ describe('Format object list', () => { }, ], }; - const result = formatList(mockWithBoth, daCtx); + const result = formatList(mockWithBoth); const testItems = result.filter((item) => item.name === 'test'); assert.strictEqual(testItems.length, 1); }); @@ -196,7 +191,7 @@ describe('Format object list', () => { { Key: 'beta.html', LastModified: new Date('2025-01-01') }, ], }; - const result = formatList(mockForSorting, daCtx); + const result = formatList(mockForSorting); assert.strictEqual(result[0].name, 'alpha'); assert.strictEqual(result[1].name, 'beta'); assert.strictEqual(result[2].name, 'zebra'); From ec0040f4e2fa2f555fda91b24bd55fe8edf95487 Mon Sep 17 00:00:00 2001 From: kptdobe Date: Mon, 1 Dec 2025 15:32:19 +0100 Subject: [PATCH 2/4] chore: linting --- eslint.config.js | 1 + test/index.test.js | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/eslint.config.js b/eslint.config.js index ffa0c95c..88539f2d 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -17,6 +17,7 @@ export default defineConfig([ globalIgnores([ 'coverage', 'dist/*', + '.wrangler' ]), { languageOptions: { diff --git a/test/index.test.js b/test/index.test.js index 82ca4456..1e71f4b1 100644 --- a/test/index.test.js +++ b/test/index.test.js @@ -49,7 +49,6 @@ describe('fetch', () => { const resp = await hnd.fetch({ method: 'GET' }, {}); assert.strictEqual(resp.status, 403); }); - }); describe('invalid routes', () => { @@ -62,11 +61,12 @@ describe('invalid routes', () => { const test = async (path) => { const methods = ['OPTIOMS', 'GET', 'POST', 'PUT', 'DELETE']; for (const method of methods) { + // eslint-disable-next-line no-await-in-loop const status = await fetchStatus(path, method); assert.strictEqual(status, 400); } }; - + it('return 400 for invalid paths', async () => { await test('/'); await test('/source/owner'); From 7e59c145943a38e8871b19098052b611eff5e373 Mon Sep 17 00:00:00 2001 From: kptdobe Date: Mon, 1 Dec 2025 16:21:35 +0100 Subject: [PATCH 3/4] chore: update status --- src/handlers/unknown.js | 16 ---------------- src/index.js | 11 +++++++---- test/handlers/unknown.test.js | 20 -------------------- test/index.test.js | 31 +++++++++++++++++++------------ 4 files changed, 26 insertions(+), 52 deletions(-) delete mode 100644 src/handlers/unknown.js delete mode 100644 test/handlers/unknown.test.js diff --git a/src/handlers/unknown.js b/src/handlers/unknown.js deleted file mode 100644 index 74c9eb7d..00000000 --- a/src/handlers/unknown.js +++ /dev/null @@ -1,16 +0,0 @@ -/* - * Copyright 2025 Adobe. All rights reserved. - * This file is licensed to you under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. You may obtain a copy - * of the License at http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under - * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS - * OF ANY KIND, either express or implied. See the License for the specific language - * governing permissions and limitations under the License. - */ - -export default function unknownHandler() { - const body = JSON.stringify({ message: 'Bad request. Please see: https://docs.da.live for more information.' }); - return { body, status: 400 }; -} diff --git a/src/index.js b/src/index.js index 821b9505..ba158acb 100644 --- a/src/index.js +++ b/src/index.js @@ -16,7 +16,6 @@ import headHandler from './handlers/head.js'; import getHandler from './handlers/get.js'; import postHandler from './handlers/post.js'; import deleteHandler from './handlers/delete.js'; -import unknownHandler from './handlers/unknown.js'; export default { /** @@ -33,7 +32,11 @@ export default { try { daCtx = await getDaCtx(req, env); } catch (e) { - return daResp(unknownHandler()); + if (e.message === 'Invalid path') { + return daResp({ status: 400 }); + } + console.error('Error coumputing context', e); + return daResp({ status: 500 }); } const { authorized, key } = daCtx; @@ -63,10 +66,10 @@ export default { respObj = await deleteHandler({ req, env, daCtx }); break; default: - respObj = unknownHandler(); + respObj = { status: 405 }; } - if (!respObj) return daResp(unknownHandler()); + if (!respObj) return daResp({ status: 404 }); return daResp(respObj, daCtx); }, diff --git a/test/handlers/unknown.test.js b/test/handlers/unknown.test.js deleted file mode 100644 index 8f916139..00000000 --- a/test/handlers/unknown.test.js +++ /dev/null @@ -1,20 +0,0 @@ -/* - * Copyright 2025 Adobe. All rights reserved. - * This file is licensed to you under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. You may obtain a copy - * of the License at http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under - * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS - * OF ANY KIND, either express or implied. See the License for the specific language - * governing permissions and limitations under the License. - */ -import assert from 'node:assert'; -import unknownHandler from '../../src/handlers/unknown.js'; - -describe('unknownHandler', () => { - it('should return unknown response', async () => { - const result = await unknownHandler({}); - assert.strictEqual(result.status, 400); - }); -}); diff --git a/test/index.test.js b/test/index.test.js index 1e71f4b1..e6c5f2e2 100644 --- a/test/index.test.js +++ b/test/index.test.js @@ -54,26 +54,33 @@ describe('fetch', () => { describe('invalid routes', () => { const fetchStatus = async (path, method) => { const resp = await handler.fetch({ method, url: `http://www.sample.com${path}` }, {}); - console.log('resp', `http://www.sample.com${path}`, resp.status); return resp.status; }; - const test = async (path) => { - const methods = ['OPTIOMS', 'GET', 'POST', 'PUT', 'DELETE']; + const test = async (path, status) => { + const methods = ['GET', 'POST', 'PUT', 'DELETE']; for (const method of methods) { // eslint-disable-next-line no-await-in-loop - const status = await fetchStatus(path, method); - assert.strictEqual(status, 400); + const s = await fetchStatus(path, method); + assert.strictEqual(s, status); } }; it('return 400 for invalid paths', async () => { - await test('/'); - await test('/source/owner'); - await test('/source//owner/repo/path/file.html'); - await test('/source/owner//repo/path/file.html'); - await test('/source/owner/repo//path/file.html'); - await test('/source/owner/repo/path//file.html'); - await test('/unknown/owner/repo/path/file.html'); + await test('/', 400); + await test('/source/owner', 400); + await test('/source//owner/repo/path/file.html', 400); + await test('/source/owner//repo/path/file.html', 400); + await test('/source/owner/repo//path/file.html', 400); + await test('/source/owner/repo/path//file.html', 400); + }); + + it('return 404 for unknown paths', async () => { + await test('/unknown/owner/repo/path/file.html', 404); + }); + + it('return 405 for unknown methods', async () => { + const status = await fetchStatus('/source/owner/repo/path/file.html', 'BLAH'); + assert.strictEqual(status, 405); }); }); From f1418e6d9e6985d8a4545d83d29cef8933f2c38b Mon Sep 17 00:00:00 2001 From: kptdobe Date: Wed, 3 Dec 2025 15:15:59 +0100 Subject: [PATCH 4/4] chore: typo --- src/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/index.js b/src/index.js index ba158acb..a848d2b0 100644 --- a/src/index.js +++ b/src/index.js @@ -35,7 +35,7 @@ export default { if (e.message === 'Invalid path') { return daResp({ status: 400 }); } - console.error('Error coumputing context', e); + console.error('Error computing context', e); return daResp({ status: 500 }); }