Skip to content
This repository was archived by the owner on Feb 12, 2024. It is now read-only.

Commit 8519886

Browse files
lidelAlan Shaw
authored and
Alan Shaw
committed
refactor(gateway): return implicit index.html (#2217)
BREAKING CHANGE: Gateway now implicitly responds with the contents of `/index.html` when accessing a directory `/` instead of redirecting to `/index.html`. This changes current logic (redirect to index.html) to match what go-ipfs does (return index.html without changing URL) We also ensure directory URLs always end with '/' License: MIT Signed-off-by: Marcin Rataj <[email protected]>
1 parent 43ac305 commit 8519886

File tree

2 files changed

+41
-14
lines changed

2 files changed

+41
-14
lines changed

src/http/gateway/resources/gateway.js

+16-9
Original file line numberDiff line numberDiff line change
@@ -56,10 +56,11 @@ module.exports = {
5656
// so we convert /ipns/ to /ipfs/ before passing it to the resolver ¯\_(ツ)_/¯
5757
// This could be removed if a solution proposed in
5858
// https://github.com./ipfs/js-ipfs-http-response/issues/22 lands upstream
59-
const ipfsPath = decodeURI(path.startsWith('/ipns/')
59+
let ipfsPath = decodeURI(path.startsWith('/ipns/')
6060
? await ipfs.name.resolve(path, { recursive: true })
6161
: path)
6262

63+
let directory = false
6364
let data
6465
try {
6566
data = await resolver.cid(ipfs, ipfsPath)
@@ -70,22 +71,23 @@ module.exports = {
7071
// switch case with true feels so wrong.
7172
switch (true) {
7273
case (errorToString === 'Error: This dag node is a directory'):
74+
directory = true
7375
data = await resolver.directory(ipfs, ipfsPath, err.cid)
7476

7577
if (typeof data === 'string') {
7678
// no index file found
7779
if (!path.endsWith('/')) {
78-
// for a directory, if URL doesn't end with a /
79-
// append / and redirect permanent to that URL
80+
// add trailing slash for directory listings
8081
return h.redirect(`${path}/`).permanent(true)
8182
}
8283
// send directory listing
8384
return h.response(data)
8485
}
8586

86-
// found index file
87-
// redirect to URL/<found-index-file>
88-
return h.redirect(PathUtils.joinURLParts(path, data[0].Name))
87+
// found index file: return <ipfsPath>/<found-index-file>
88+
ipfsPath = PathUtils.joinURLParts(ipfsPath, data[0].Name)
89+
data = await resolver.cid(ipfs, ipfsPath)
90+
break
8991
case (errorToString.startsWith('Error: no link named')):
9092
throw Boom.boomify(err, { statusCode: 404 })
9193
case (errorToString.startsWith('Error: multihash length inconsistent')):
@@ -97,10 +99,14 @@ module.exports = {
9799
}
98100
}
99101

100-
if (path.endsWith('/')) {
102+
if (!directory && path.endsWith('/')) {
101103
// remove trailing slash for files
102104
return h.redirect(PathUtils.removeTrailingSlash(path)).permanent(true)
103105
}
106+
if (directory && !path.endsWith('/')) {
107+
// add trailing slash for directories with implicit index.html
108+
return h.redirect(`${path}/`).permanent(true)
109+
}
104110

105111
// Support If-None-Match & Etag (Conditional Requests from RFC7232)
106112
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/ETag
@@ -153,7 +159,7 @@ module.exports = {
153159
log.error(err)
154160
return reject(err)
155161
}
156-
resolve({ peekedStream, contentType: detectContentType(path, streamHead) })
162+
resolve({ peekedStream, contentType: detectContentType(ipfsPath, streamHead) })
157163
})
158164
})
159165

@@ -170,7 +176,8 @@ module.exports = {
170176
res.header('Cache-Control', 'public, max-age=29030400, immutable')
171177
}
172178

173-
log('path ', path)
179+
log('HTTP path ', path)
180+
log('IPFS path ', ipfsPath)
174181
log('content-type ', contentType)
175182

176183
if (contentType) {

test/gateway/index.js

+25-5
Original file line numberDiff line numberDiff line change
@@ -519,20 +519,40 @@ describe('HTTP Gateway', function () {
519519
expect(res.headers['x-ipfs-path']).to.equal(undefined)
520520
})
521521

522-
// TODO: check if interop for this exists and if not, match behavior of go-ipfs
523-
it('redirect to webpage index.html', async () => {
524-
const dir = 'QmbQD7EMEL1zeebwBsWEfA3ndgSS6F7S6iTuwuqasPgVRi/'
522+
it('redirect to a directory with index.html', async () => {
523+
const dir = 'QmbQD7EMEL1zeebwBsWEfA3ndgSS6F7S6iTuwuqasPgVRi' // note lack of '/' at the end
525524

526525
const res = await gateway.inject({
527526
method: 'GET',
528527
url: '/ipfs/' + dir
529528
})
530529

531-
expect(res.statusCode).to.equal(302)
532-
expect(res.headers.location).to.equal('/ipfs/QmbQD7EMEL1zeebwBsWEfA3ndgSS6F7S6iTuwuqasPgVRi/index.html')
530+
// we expect redirect to the same path but with '/' at the end
531+
expect(res.statusCode).to.equal(301)
532+
expect(res.headers.location).to.equal(`/ipfs/${dir}/`)
533533
expect(res.headers['x-ipfs-path']).to.equal(undefined)
534534
})
535535

536+
it('load a directory with index.html', async () => {
537+
const dir = 'QmbQD7EMEL1zeebwBsWEfA3ndgSS6F7S6iTuwuqasPgVRi/' // note '/' at the end
538+
539+
const res = await gateway.inject({
540+
method: 'GET',
541+
url: '/ipfs/' + dir
542+
})
543+
544+
// confirm payload is index.html
545+
expect(res.statusCode).to.equal(200)
546+
expect(res.headers['content-type']).to.equal('text/html; charset=utf-8')
547+
expect(res.headers['x-ipfs-path']).to.equal('/ipfs/' + dir)
548+
expect(res.headers['cache-control']).to.equal('public, max-age=29030400, immutable')
549+
expect(res.headers['last-modified']).to.equal('Thu, 01 Jan 1970 00:00:01 GMT')
550+
expect(res.headers['content-length']).to.equal(res.rawPayload.length)
551+
expect(res.headers.etag).to.equal('"Qma6665X5k3zti8nKy7gmXK2BndNDSkgmANpV6k3FUjUeg"')
552+
expect(res.headers.suborigin).to.equal('ipfs000bafybeigccfheqv7upr4k64bkg5b5wiwelunyn2l2rbirmm43m34lcpuqqe')
553+
expect(res.rawPayload).to.deep.equal(directoryContent['index.html'])
554+
})
555+
536556
it('test(gateway): load from URI-encoded path', async () => {
537557
// non-ascii characters will be URI-encoded by the browser
538558
const utf8path = '/ipfs/QmaRdtkDark8TgXPdDczwBneadyF44JvFGbrKLTkmTUhHk/cat-with-óąśśł-and-أعظم._.jpg'

0 commit comments

Comments
 (0)