diff --git a/src/handlers/unixfs-dir.js b/src/handlers/unixfs-dir.js index 1cad09d..d486063 100644 --- a/src/handlers/unixfs-dir.js +++ b/src/handlers/unixfs-dir.js @@ -50,19 +50,9 @@ export async function handleUnixfsDir (request, env, ctx) { if (!entry.type.includes('directory')) throw new Error('non UnixFS directory entry') if (!unixfs) throw new Error('missing UnixFS service') - // serve index.html if directory contains one - try { - const indexPath = `${dataCid}${path}${path.endsWith('/') ? '' : '/'}index.html` - const fileEntry = await unixfs.getUnixfs(indexPath, { signal: controller?.signal }) - ctx.unixfsEntry = fileEntry - return handleUnixfsFile(request, env, ctx) - } catch (/** @type {any} */ err) { - if (err.code !== 'ERR_NOT_FOUND') throw err - } - const headers = { 'Content-Type': 'text/html', - Etag: `"DirIndex-gateway-lib@2.0.2_CID-${entry.cid}"` + Etag: `"DirIndex-gateway-lib@2.0.3_CID-${entry.cid}"` } if (request.method === 'HEAD') { @@ -72,7 +62,27 @@ export async function handleUnixfsDir (request, env, ctx) { throw new HttpError('method not allowed', { status: 405 }) } - const isSubdomain = new URL(request.url).hostname.includes('.ipfs.') + const url = new URL(request.url) + + // redirect directory missing trailing slash + if (!url.pathname.endsWith('/')) { + return new Response(null, { + status: 301, + headers: { Location: `${url.pathname}/${url.search}` } + }) + } + + // serve index.html if directory contains one + try { + const indexPath = `${dataCid}${path}${path.endsWith('/') ? '' : '/'}index.html` + const fileEntry = await unixfs.getUnixfs(indexPath, { signal: controller?.signal }) + ctx.unixfsEntry = fileEntry + return handleUnixfsFile(request, env, ctx) + } catch (/** @type {any} */ err) { + if (err.code !== 'ERR_NOT_FOUND') throw err + } + + const isSubdomain = url.hostname.includes('.ipfs.') /** @param {string} p CID path like "[/optional/path]" */ const entryPath = p => isSubdomain ? p.split('/').slice(1).join('/') : `/ipfs/${p}` diff --git a/test/handlers/unixfs-dir.spec.js b/test/handlers/unixfs-dir.spec.js index d2f0f38..11d9029 100644 --- a/test/handlers/unixfs-dir.spec.js +++ b/test/handlers/unixfs-dir.spec.js @@ -26,9 +26,34 @@ describe('UnixFS directory handler', () => { const dagula = new Dagula(blockstore) const ctx = { waitUntil, unixfs: dagula, dataCid: dirBlock.cid, path, searchParams } const env = { DEBUG: 'true' } - const req = new Request('http://localhost/ipfs/bafy') + const req = new Request('http://localhost/ipfs/bafy/') const res = await handleUnixfs(req, env, ctx) const html = await res.text() assert(html.includes('Puzzle%20People%20%231.png')) }) + + it('redirects missing trailing slash', async () => { + const waitUntil = mockWaitUntil() + const path = '/test' + const searchParams = new URLSearchParams() + const fileBlock = await encode({ value: fromString('test'), codec: raw, hasher }) + const dirData = pb.createNode(new UnixFS({ type: 'directory' }).marshal(), [{ + Name: 'Puzzle People #1.png', + Hash: fileBlock.cid + }]) + const dirBlock = await encode({ value: dirData, codec: pb, hasher }) + const rootDirData = pb.createNode(new UnixFS({ type: 'directory' }).marshal(), [{ + Name: 'test', + Hash: dirBlock.cid + }]) + const rootDirBlock = await encode({ value: rootDirData, codec: pb, hasher }) + const blockstore = mockBlockstore([rootDirBlock, dirBlock, fileBlock]) + const dagula = new Dagula(blockstore) + const ctx = { waitUntil, unixfs: dagula, dataCid: rootDirBlock.cid, path, searchParams } + const env = { DEBUG: 'true' } + const req = new Request('http://localhost/ipfs/bafy/test') + const res = await handleUnixfs(req, env, ctx) + assert.equal(res.status, 301) + assert.equal(res.headers.get('location'), '/ipfs/bafy/test/') + }) })