Skip to content

Commit 32ca87f

Browse files
authored
fix: unixfs dir redirect (#33)
* fix: check redirect trailing slash if path is empty * test: add interop test for unixfs directory redirect * chore: add gateway-conformance-fixtures car file * test: add verified-fetch unit test for root directory redirect
1 parent 4589c26 commit 32ca87f

File tree

4 files changed

+48
-1
lines changed

4 files changed

+48
-1
lines changed
Binary file not shown.

packages/interop/src/unixfs-dir.spec.ts

+19
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,25 @@ describe('@helia/verified-fetch - unixfs directory', () => {
2626
await verifiedFetch.stop()
2727
})
2828

29+
describe('unixfs-dir-redirect', () => {
30+
before(async () => {
31+
await loadFixtureDataCar(controller, 'gateway-conformance-fixtures.car')
32+
});
33+
34+
[
35+
'https://example.com/ipfs/bafybeifq2rzpqnqrsdupncmkmhs3ckxxjhuvdcbvydkgvch3ms24k5lo7q',
36+
'ipfs://bafybeifq2rzpqnqrsdupncmkmhs3ckxxjhuvdcbvydkgvch3ms24k5lo7q',
37+
'http://example.com/ipfs/bafybeifq2rzpqnqrsdupncmkmhs3ckxxjhuvdcbvydkgvch3ms24k5lo7q'
38+
].forEach((url: string) => {
39+
it(`request to unixfs directory with ${url} should return a 301 with a trailing slash`, async () => {
40+
const response = await verifiedFetch(url, { redirect: 'manual' })
41+
expect(response).to.be.ok()
42+
expect(response.status).to.equal(301)
43+
expect(response.headers.get('location')).to.equal(`${url}/`)
44+
})
45+
})
46+
})
47+
2948
describe('XKCD Barrel Part 1', () => {
3049
before(async () => {
3150
// This is the content of https://explore.ipld.io/#/explore/QmdmQXB2mzChmMeKY47C43LxUdg1NDJ5MWcKMKxDu7RgQm/1%20-%20Barrel%20-%20Part%201

packages/verified-fetch/src/verified-fetch.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -299,9 +299,10 @@ export class VerifiedFetch {
299299
let resolvedCID = terminalElement?.cid ?? cid
300300
if (terminalElement?.type === 'directory') {
301301
const dirCid = terminalElement.cid
302+
const redirectCheckNeeded = path === '' ? !resource.toString().endsWith('/') : !path.endsWith('/')
302303

303304
// https://specs.ipfs.tech/http-gateways/path-gateway/#use-in-directory-url-normalization
304-
if (path !== '' && !path.endsWith('/')) {
305+
if (redirectCheckNeeded) {
305306
if (options?.redirect === 'error') {
306307
this.log('could not redirect to %s/ as redirect option was set to "error"', resource)
307308
throw new TypeError('Failed to fetch')

packages/verified-fetch/test/verified-fetch.spec.ts

+27
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,33 @@ describe('@helia/verifed-fetch', () => {
160160
expect(ipfsResponse.url).to.equal(`ipfs://${res.cid}/foo`)
161161
})
162162

163+
it('should return a 301 with a trailing slash when a root directory is requested without a trailing slash', async () => {
164+
const finalRootFileContent = new Uint8Array([0x01, 0x02, 0x03])
165+
166+
const fs = unixfs(helia)
167+
const res = await last(fs.addAll([{
168+
path: '/index.html',
169+
content: finalRootFileContent
170+
}], {
171+
wrapWithDirectory: true
172+
}))
173+
174+
if (res == null) {
175+
throw new Error('Import failed')
176+
}
177+
178+
const stat = await fs.stat(res.cid)
179+
expect(stat.type).to.equal('directory')
180+
181+
const ipfsResponse = await verifiedFetch.fetch(`https://ipfs.local/ipfs/${res.cid}`, {
182+
redirect: 'manual'
183+
})
184+
expect(ipfsResponse).to.be.ok()
185+
expect(ipfsResponse.status).to.equal(301)
186+
expect(ipfsResponse.headers.get('location')).to.equal(`https://ipfs.local/ipfs/${res.cid}/`)
187+
expect(ipfsResponse.url).to.equal(`https://ipfs.local/ipfs/${res.cid}`)
188+
})
189+
163190
it('should return a 301 with a trailing slash when a gateway directory is requested without a trailing slash', async () => {
164191
const finalRootFileContent = new Uint8Array([0x01, 0x02, 0x03])
165192

0 commit comments

Comments
 (0)