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

Commit

Permalink
refactor(gateway): return implicit index.html (#2217)
Browse files Browse the repository at this point in the history
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]>
  • Loading branch information
lidel authored and Alan Shaw committed Jul 5, 2019
1 parent 43ac305 commit 8519886
Show file tree
Hide file tree
Showing 2 changed files with 41 additions and 14 deletions.
25 changes: 16 additions & 9 deletions src/http/gateway/resources/gateway.js
Original file line number Diff line number Diff line change
Expand Up @@ -56,10 +56,11 @@ module.exports = {
// so we convert /ipns/ to /ipfs/ before passing it to the resolver ¯\_(ツ)_/¯
// This could be removed if a solution proposed in
// https://github.com/ipfs/js-ipfs-http-response/issues/22 lands upstream
const ipfsPath = decodeURI(path.startsWith('/ipns/')
let ipfsPath = decodeURI(path.startsWith('/ipns/')
? await ipfs.name.resolve(path, { recursive: true })
: path)

let directory = false
let data
try {
data = await resolver.cid(ipfs, ipfsPath)
Expand All @@ -70,22 +71,23 @@ module.exports = {
// switch case with true feels so wrong.
switch (true) {
case (errorToString === 'Error: This dag node is a directory'):
directory = true
data = await resolver.directory(ipfs, ipfsPath, err.cid)

if (typeof data === 'string') {
// no index file found
if (!path.endsWith('/')) {
// for a directory, if URL doesn't end with a /
// append / and redirect permanent to that URL
// add trailing slash for directory listings
return h.redirect(`${path}/`).permanent(true)
}
// send directory listing
return h.response(data)
}

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

if (path.endsWith('/')) {
if (!directory && path.endsWith('/')) {
// remove trailing slash for files
return h.redirect(PathUtils.removeTrailingSlash(path)).permanent(true)
}
if (directory && !path.endsWith('/')) {
// add trailing slash for directories with implicit index.html
return h.redirect(`${path}/`).permanent(true)
}

// Support If-None-Match & Etag (Conditional Requests from RFC7232)
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/ETag
Expand Down Expand Up @@ -153,7 +159,7 @@ module.exports = {
log.error(err)
return reject(err)
}
resolve({ peekedStream, contentType: detectContentType(path, streamHead) })
resolve({ peekedStream, contentType: detectContentType(ipfsPath, streamHead) })
})
})

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

log('path ', path)
log('HTTP path ', path)
log('IPFS path ', ipfsPath)
log('content-type ', contentType)

if (contentType) {
Expand Down
30 changes: 25 additions & 5 deletions test/gateway/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -519,20 +519,40 @@ describe('HTTP Gateway', function () {
expect(res.headers['x-ipfs-path']).to.equal(undefined)
})

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

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

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

it('load a directory with index.html', async () => {
const dir = 'QmbQD7EMEL1zeebwBsWEfA3ndgSS6F7S6iTuwuqasPgVRi/' // note '/' at the end

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

// confirm payload is index.html
expect(res.statusCode).to.equal(200)
expect(res.headers['content-type']).to.equal('text/html; charset=utf-8')
expect(res.headers['x-ipfs-path']).to.equal('/ipfs/' + dir)
expect(res.headers['cache-control']).to.equal('public, max-age=29030400, immutable')
expect(res.headers['last-modified']).to.equal('Thu, 01 Jan 1970 00:00:01 GMT')
expect(res.headers['content-length']).to.equal(res.rawPayload.length)
expect(res.headers.etag).to.equal('"Qma6665X5k3zti8nKy7gmXK2BndNDSkgmANpV6k3FUjUeg"')
expect(res.headers.suborigin).to.equal('ipfs000bafybeigccfheqv7upr4k64bkg5b5wiwelunyn2l2rbirmm43m34lcpuqqe')
expect(res.rawPayload).to.deep.equal(directoryContent['index.html'])
})

it('test(gateway): load from URI-encoded path', async () => {
// non-ascii characters will be URI-encoded by the browser
const utf8path = '/ipfs/QmaRdtkDark8TgXPdDczwBneadyF44JvFGbrKLTkmTUhHk/cat-with-óąśśł-and-أعظم._.jpg'
Expand Down

0 comments on commit 8519886

Please sign in to comment.