From 8519886100f9ce365055d548f936459be8707fe5 Mon Sep 17 00:00:00 2001 From: Marcin Rataj Date: Fri, 5 Jul 2019 14:49:05 +0200 Subject: [PATCH] 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 --- src/http/gateway/resources/gateway.js | 25 ++++++++++++++-------- test/gateway/index.js | 30 ++++++++++++++++++++++----- 2 files changed, 41 insertions(+), 14 deletions(-) diff --git a/src/http/gateway/resources/gateway.js b/src/http/gateway/resources/gateway.js index 6f2a42b6c2..3cc943a737 100644 --- a/src/http/gateway/resources/gateway.js +++ b/src/http/gateway/resources/gateway.js @@ -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) @@ -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/ - return h.redirect(PathUtils.joinURLParts(path, data[0].Name)) + // found index file: return / + 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')): @@ -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 @@ -153,7 +159,7 @@ module.exports = { log.error(err) return reject(err) } - resolve({ peekedStream, contentType: detectContentType(path, streamHead) }) + resolve({ peekedStream, contentType: detectContentType(ipfsPath, streamHead) }) }) }) @@ -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) { diff --git a/test/gateway/index.js b/test/gateway/index.js index 9cab5b6d7d..651cee1578 100644 --- a/test/gateway/index.js +++ b/test/gateway/index.js @@ -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'