diff --git a/src/http/gateway/resources/gateway.js b/src/http/gateway/resources/gateway.js index c3a866c359..856bf4a8d9 100644 --- a/src/http/gateway/resources/gateway.js +++ b/src/http/gateway/resources/gateway.js @@ -12,6 +12,21 @@ const Stream = require('readable-stream') const { resolver } = require('ipfs-http-response') const PathUtils = require('../utils/path') +function detectContentType (ref, chunk) { + let fileSignature + + // try to guess the filetype based on the first bytes + // note that `file-type` doesn't support svgs, therefore we assume it's a svg if ref looks like it + if (!ref.endsWith('.svg')) { + fileSignature = fileType(chunk) + } + + // if we were unable to, fallback to the `ref` which might contain the extension + const mimeType = mime.lookup(fileSignature ? fileSignature.ext : ref) + + return mime.contentType(mimeType) +} + module.exports = { checkCID: (request, reply) => { if (!request.params.cid) { @@ -97,7 +112,7 @@ module.exports = { } // response.continue() - let filetypeChecked = false + let contentTypeDetected = false let stream2 = new Stream.PassThrough({ highWaterMark: 1 }) stream2.on('error', (err) => { log.error('stream2 err: ', err) @@ -108,29 +123,20 @@ module.exports = { pull( toPull.source(stream), pull.through((chunk) => { - // Check file type. do this once. - if (chunk.length > 0 && !filetypeChecked) { - log('got first chunk') - let fileSignature = fileType(chunk) - log('file type: ', fileSignature) - - filetypeChecked = true - const mimeType = mime.lookup(fileSignature - ? fileSignature.ext - : null) + // Guess content-type (only once) + if (chunk.length > 0 && !contentTypeDetected) { + let contentType = detectContentType(ref, chunk) + contentTypeDetected = true log('ref ', ref) - log('mime-type ', mimeType) - - if (mimeType) { - log('writing mimeType') + log('mime-type ', contentType) - response - .header('Content-Type', mime.contentType(mimeType)) - .send() - } else { - response.send() + if (contentType) { + log('writing content-type header') + response.header('Content-Type', contentType) } + + response.send() } stream2.write(chunk) diff --git a/test/gateway/index.js b/test/gateway/index.js index 84ce39914a..1c319671e5 100644 --- a/test/gateway/index.js +++ b/test/gateway/index.js @@ -19,7 +19,9 @@ const directoryContent = { 'nested-folder/hello.txt': loadFixture('test/gateway/test-folder/nested-folder/hello.txt'), 'nested-folder/ipfs.txt': loadFixture('test/gateway/test-folder/nested-folder/ipfs.txt'), 'nested-folder/nested.html': loadFixture('test/gateway/test-folder/nested-folder/nested.html'), - 'cat-folder/cat.jpg': loadFixture('test/gateway/test-folder/cat-folder/cat.jpg') + 'cat-folder/cat.jpg': loadFixture('test/gateway/test-folder/cat-folder/cat.jpg'), + 'unsniffable-folder/hexagons-xml.svg': loadFixture('test/gateway/test-folder/unsniffable-folder/hexagons-xml.svg'), + 'unsniffable-folder/hexagons.svg': loadFixture('test/gateway/test-folder/unsniffable-folder/hexagons.svg') } describe('HTTP Gateway', function () { @@ -113,6 +115,22 @@ describe('HTTP Gateway', function () { expect(file.hash).to.equal(expectedMultihash) cb() }) + }, + (cb) => { + const expectedMultihash = 'QmVZoGxDvKM9KExc8gaL4uTbhdNtWhzQR7ndrY7J1gWs3F' + + let dir = [ + content('unsniffable-folder/hexagons-xml.svg'), + content('unsniffable-folder/hexagons.svg') + ] + + http.api.node.files.add(dir, (err, res) => { + expect(err).to.not.exist() + const file = res[res.length - 2] + expect(file.path).to.equal('test-folder/unsniffable-folder') + expect(file.hash).to.equal(expectedMultihash) + cb() + }) } ], done) }) @@ -166,7 +184,7 @@ describe('HTTP Gateway', function () { }) }) - it('load a non text file', (done) => { + it('load a jpg file', (done) => { let kitty = 'QmW2WQi7j6c7UgJTarActp7tDNikE4B2qXtFCfLPdsgaTQ/cat.jpg' gateway.inject({ @@ -184,6 +202,34 @@ describe('HTTP Gateway', function () { }) }) + it('load a svg file (unsniffable)', (done) => { + let hexagons = 'QmVZoGxDvKM9KExc8gaL4uTbhdNtWhzQR7ndrY7J1gWs3F/hexagons.svg' + + gateway.inject({ + method: 'GET', + url: '/ipfs/' + hexagons + }, (res) => { + expect(res.statusCode).to.equal(200) + expect(res.headers['content-type']).to.equal('image/svg+xml') + + done() + }) + }) + + it('load a svg file with xml leading declaration (unsniffable)', (done) => { + let hexagons = 'QmVZoGxDvKM9KExc8gaL4uTbhdNtWhzQR7ndrY7J1gWs3F/hexagons-xml.svg' + + gateway.inject({ + method: 'GET', + url: '/ipfs/' + hexagons + }, (res) => { + expect(res.statusCode).to.equal(200) + expect(res.headers['content-type']).to.equal('image/svg+xml') + + done() + }) + }) + it('load a directory', (done) => { let dir = 'QmW2WQi7j6c7UgJTarActp7tDNikE4B2qXtFCfLPdsgaTQ/' diff --git a/test/gateway/test-folder/unsniffable-folder/hexagons-xml.svg b/test/gateway/test-folder/unsniffable-folder/hexagons-xml.svg new file mode 100644 index 0000000000..fe6b79dfd2 --- /dev/null +++ b/test/gateway/test-folder/unsniffable-folder/hexagons-xml.svg @@ -0,0 +1,56 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/test/gateway/test-folder/unsniffable-folder/hexagons.svg b/test/gateway/test-folder/unsniffable-folder/hexagons.svg new file mode 100644 index 0000000000..557d927b39 --- /dev/null +++ b/test/gateway/test-folder/unsniffable-folder/hexagons.svg @@ -0,0 +1,55 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +