The four source URLs in src/routes/da-admin.js are each assembled at the call site from daCtx.path and daCtx.ext. ext is html for two different reasons: the request named .html, or it named no extension and daCtx.js:110 supplied one. The call sites cannot tell those apart, so three of them guess whether to append .${ext}, and each guess is wrong for one of the two.
/ reads /.html. /page.html reads /page.html.html. Both 404, and daSourceGet answers 200 with the empty template.
Raised by @tripodsan in #242.
Proposal
Derive it once, in getDaCtx, from the filename rather than from ext:
const sourceName = filename.includes('.') ? filename : `${filename}.html`;
daCtx.sourcePath = `/${[...pathParts, sourceName].join('/')}`;
A dot in the filename means the request named its own extension. pathParts (daCtx.js:88-100) is already lowercased, index-substituted and empty-segment-filtered, so / and /folder/ resolve without a special case.
The four sites then read daCtx.sourcePath:
ext survives for the ext !== 'html' branch, which picks compose against raw proxy. sourcePath harmonizes the URL, not that classification.
Document the surviving path fields. path is the request pathname, aemPathname is what goes to *.aem.page, sourcePath is what goes to the store. The JSDoc currently says @param {pathname} pathname for a function that takes a Request.
Drop pathname, key, propsKey, filename, isFile and name. No file outside getDaCtx reads them. They are inherited from da-admin, where key is an R2 object key and isFile separates a file from a folder. This worker has neither, and isFile is constant since daCtx.js:110.
What changes
| request |
before |
after |
/ |
/.html |
/index.html |
/folder/ |
/folder/.html |
/folder/index.html |
/page.html |
/page.html.html |
/page.html |
/Page.HTML |
/Page.HTML.html |
/page.html |
/a/b.plain.html |
/a/b.plain.html.html |
/a/b.plain.html |
/a//b |
/a//b.html, da-admin answers 400 |
/a/b.html |
/page, /index, /sheet.json |
unchanged |
unchanged |
POST doubles the extension for any type today, not only .html: /sheet.json writes /sheet.json.json and /media/logo.png writes /media/logo.png.png.
POST needs a content-type gate in the same change
daSourcePost reads any body as text and re-serializes it as HTML. A 68-byte PNG posted through the worker stored 133 bytes of text/html, each non-ASCII byte replaced with U+FFFD. Today that lands on a doubled key no route serves. After the change it lands on the key GET reads, so it overwrites the asset. Gate daSourcePost on an HTML content type, or the fix turns a dead write into a destructive one.
Tests
No test asserts the outbound /source/... URL. test/routes/da-admin.test.js builds a daCtx and checks the response, never the URL handed to env.daadmin.fetch, and there is no POST test. Repoint any of the four wrongly and the suite still passes. Capture the fetched URL for /, /page.html and a non-HTML path, across GET, HEAD and POST.
Relates to #242.
The four source URLs in
src/routes/da-admin.jsare each assembled at the call site fromdaCtx.pathanddaCtx.ext.extishtmlfor two different reasons: the request named.html, or it named no extension anddaCtx.js:110supplied one. The call sites cannot tell those apart, so three of them guess whether to append.${ext}, and each guess is wrong for one of the two./reads/.html./page.htmlreads/page.html.html. Both 404, anddaSourceGetanswers 200 with the empty template.Raised by @tripodsan in #242.
Proposal
Derive it once, in
getDaCtx, from the filename rather than fromext:A dot in the filename means the request named its own extension.
pathParts(daCtx.js:88-100) is already lowercased, index-substituted and empty-segment-filtered, so/and/folder/resolve without a special case.The four sites then read
daCtx.sourcePath:daSourceGet, non-HTML proxy and HTML readdaSourceHead, replacing theadminPathternarydaSourcePostextsurvives for theext !== 'html'branch, which picks compose against raw proxy.sourcePathharmonizes the URL, not that classification.Document the surviving path fields.
pathis the request pathname,aemPathnameis what goes to*.aem.page,sourcePathis what goes to the store. The JSDoc currently says@param {pathname} pathnamefor a function that takes aRequest.Drop
pathname,key,propsKey,filename,isFileandname. No file outsidegetDaCtxreads them. They are inherited from da-admin, wherekeyis an R2 object key andisFileseparates a file from a folder. This worker has neither, andisFileis constant sincedaCtx.js:110.What changes
//.html/index.html/folder//folder/.html/folder/index.html/page.html/page.html.html/page.html/Page.HTML/Page.HTML.html/page.html/a/b.plain.html/a/b.plain.html.html/a/b.plain.html/a//b/a//b.html, da-admin answers 400/a/b.html/page,/index,/sheet.jsonPOST doubles the extension for any type today, not only
.html:/sheet.jsonwrites/sheet.json.jsonand/media/logo.pngwrites/media/logo.png.png.POST needs a content-type gate in the same change
daSourcePostreads any body as text and re-serializes it as HTML. A 68-byte PNG posted through the worker stored 133 bytes oftext/html, each non-ASCII byte replaced with U+FFFD. Today that lands on a doubled key no route serves. After the change it lands on the key GET reads, so it overwrites the asset. GatedaSourcePoston an HTML content type, or the fix turns a dead write into a destructive one.Tests
No test asserts the outbound
/source/...URL.test/routes/da-admin.test.jsbuilds adaCtxand checks the response, never the URL handed toenv.daadmin.fetch, and there is no POST test. Repoint any of the four wrongly and the suite still passes. Capture the fetched URL for/,/page.htmland a non-HTML path, across GET, HEAD and POST.Relates to #242.