diff --git a/src/handlers/unknown.js b/src/handlers/unknown.js deleted file mode 100644 index dbe19289..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: 'Unknown method. Please see: https://docs.da.live for more information.' }); - return { body, status: 501 }; -} diff --git a/src/index.js b/src/index.js index 20cfb66b..a848d2b0 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 { /** @@ -29,7 +28,17 @@ export default { return daResp({ status: 204 }); } - const daCtx = await getDaCtx(req, env); + let daCtx; + try { + daCtx = await getDaCtx(req, env); + } catch (e) { + if (e.message === 'Invalid path') { + return daResp({ status: 400 }); + } + console.error('Error computing context', e); + return daResp({ status: 500 }); + } + const { authorized, key } = daCtx; if (!authorized) { const status = daCtx.users[0].email === 'anonymous' ? 401 : 403; @@ -57,7 +66,7 @@ export default { respObj = await deleteHandler({ req, env, daCtx }); break; default: - respObj = unknownHandler(); + respObj = { status: 405 }; } if (!respObj) return daResp({ status: 404 }); 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 deleted file mode 100644 index cee3586d..00000000 --- a/test/handlers/unknown.test.js +++ /dev/null @@ -1,21 +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, 501); - assert.strictEqual(result.body.includes('Unknown method'), true); - }); -}); diff --git a/test/index.test.js b/test/index.test.js index b86ce7a6..e6c5f2e2 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 () => { @@ -49,9 +49,38 @@ describe('fetch', () => { const resp = await hnd.fetch({ method: 'GET' }, {}); assert.strictEqual(resp.status, 403); }); +}); + +describe('invalid routes', () => { + const fetchStatus = async (path, method) => { + const resp = await handler.fetch({ method, url: `http://www.sample.com${path}` }, {}); + return resp.status; + }; + + 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 s = await fetchStatus(path, method); + assert.strictEqual(s, status); + } + }; + + it('return 400 for invalid paths', async () => { + 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 404 for unknown get route', async () => { - const resp = await handler.fetch({ method: 'GET', url: 'http://www.example.com/' }, {}); - assert.strictEqual(resp.status, 404); + it('return 405 for unknown methods', async () => { + const status = await fetchStatus('/source/owner/repo/path/file.html', 'BLAH'); + assert.strictEqual(status, 405); }); }); 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');