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

Commit 95c6cb0

Browse files
committed
fix: fix content-type by doing a fall-back using extensions
The JS IPFS gateway was only responding with the correct content-type for some mime-types, see https://github.com/sindresorhus/file-type#supported-file-types. This commit now fall-backs to detecting based on the extension as well. Note that SVGs aren't supported by the `file-type` module.
1 parent 5e80ee3 commit 95c6cb0

File tree

4 files changed

+185
-22
lines changed

4 files changed

+185
-22
lines changed

src/http/gateway/resources/gateway.js

+26-20
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,21 @@ const Stream = require('readable-stream')
1212
const { resolver } = require('ipfs-http-response')
1313
const PathUtils = require('../utils/path')
1414

15+
function detectContenType (ref, chunk) {
16+
let fileSignature
17+
18+
// try to guess the filetype based on the first bytes
19+
// note that `file-type` doesn't support svgs, thereore we assume it's a svg if ref looks like it
20+
if (!ref.endsWith('.svg')) {
21+
fileSignature = fileType(chunk)
22+
}
23+
24+
// if we were unable to, fallback to the `ref` which might contain the extension
25+
const mimeType = mime.lookup(fileSignature ? fileSignature.ext : ref)
26+
27+
return mime.contentType(mimeType)
28+
}
29+
1530
module.exports = {
1631
checkCID: (request, reply) => {
1732
if (!request.params.cid) {
@@ -97,7 +112,7 @@ module.exports = {
97112
}
98113

99114
// response.continue()
100-
let filetypeChecked = false
115+
let contentTypeDetected = false
101116
let stream2 = new Stream.PassThrough({ highWaterMark: 1 })
102117
stream2.on('error', (err) => {
103118
log.error('stream2 err: ', err)
@@ -108,29 +123,20 @@ module.exports = {
108123
pull(
109124
toPull.source(stream),
110125
pull.through((chunk) => {
111-
// Check file type. do this once.
112-
if (chunk.length > 0 && !filetypeChecked) {
113-
log('got first chunk')
114-
let fileSignature = fileType(chunk)
115-
log('file type: ', fileSignature)
116-
117-
filetypeChecked = true
118-
const mimeType = mime.lookup(fileSignature
119-
? fileSignature.ext
120-
: null)
126+
// Guess content-type (only once)
127+
if (chunk.length > 0 && !contentTypeDetected) {
128+
let contentType = detectContenType(ref, chunk)
129+
contentTypeDetected = true
121130

122131
log('ref ', ref)
123-
log('mime-type ', mimeType)
124-
125-
if (mimeType) {
126-
log('writing mimeType')
132+
log('mime-type ', contentType)
127133

128-
response
129-
.header('Content-Type', mime.contentType(mimeType))
130-
.send()
131-
} else {
132-
response.send()
134+
if (contentType) {
135+
log('writing content-type header')
136+
response.header('Content-Type', contentType)
133137
}
138+
139+
response.send()
134140
}
135141

136142
stream2.write(chunk)

test/gateway/index.js

+48-2
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,9 @@ const directoryContent = {
1919
'nested-folder/hello.txt': loadFixture('test/gateway/test-folder/nested-folder/hello.txt'),
2020
'nested-folder/ipfs.txt': loadFixture('test/gateway/test-folder/nested-folder/ipfs.txt'),
2121
'nested-folder/nested.html': loadFixture('test/gateway/test-folder/nested-folder/nested.html'),
22-
'cat-folder/cat.jpg': loadFixture('test/gateway/test-folder/cat-folder/cat.jpg')
22+
'cat-folder/cat.jpg': loadFixture('test/gateway/test-folder/cat-folder/cat.jpg'),
23+
'unsniffable-folder/hexagons-xml.svg': loadFixture('test/gateway/test-folder/unsniffable-folder/hexagons-xml.svg'),
24+
'unsniffable-folder/hexagons.svg': loadFixture('test/gateway/test-folder/unsniffable-folder/hexagons.svg')
2325
}
2426

2527
describe('HTTP Gateway', function () {
@@ -113,6 +115,22 @@ describe('HTTP Gateway', function () {
113115
expect(file.hash).to.equal(expectedMultihash)
114116
cb()
115117
})
118+
},
119+
(cb) => {
120+
const expectedMultihash = 'QmVZoGxDvKM9KExc8gaL4uTbhdNtWhzQR7ndrY7J1gWs3F'
121+
122+
let dir = [
123+
content('unsniffable-folder/hexagons-xml.svg'),
124+
content('unsniffable-folder/hexagons.svg')
125+
]
126+
127+
http.api.node.files.add(dir, (err, res) => {
128+
expect(err).to.not.exist()
129+
const file = res[res.length - 2]
130+
expect(file.path).to.equal('test-folder/unsniffable-folder')
131+
expect(file.hash).to.equal(expectedMultihash)
132+
cb()
133+
})
116134
}
117135
], done)
118136
})
@@ -166,7 +184,7 @@ describe('HTTP Gateway', function () {
166184
})
167185
})
168186

169-
it('load a non text file', (done) => {
187+
it('load a jpg file', (done) => {
170188
let kitty = 'QmW2WQi7j6c7UgJTarActp7tDNikE4B2qXtFCfLPdsgaTQ/cat.jpg'
171189

172190
gateway.inject({
@@ -184,6 +202,34 @@ describe('HTTP Gateway', function () {
184202
})
185203
})
186204

205+
it('load a svg file (unsniffable)', (done) => {
206+
let hexagons = 'QmVZoGxDvKM9KExc8gaL4uTbhdNtWhzQR7ndrY7J1gWs3F/hexagons.svg'
207+
208+
gateway.inject({
209+
method: 'GET',
210+
url: '/ipfs/' + hexagons
211+
}, (res) => {
212+
expect(res.statusCode).to.equal(200)
213+
expect(res.headers['content-type']).to.equal('image/svg+xml')
214+
215+
done()
216+
})
217+
})
218+
219+
it('load a svg file with xml leading declaration (unsniffable)', (done) => {
220+
let hexagons = 'QmVZoGxDvKM9KExc8gaL4uTbhdNtWhzQR7ndrY7J1gWs3F/hexagons-xml.svg'
221+
222+
gateway.inject({
223+
method: 'GET',
224+
url: '/ipfs/' + hexagons
225+
}, (res) => {
226+
expect(res.statusCode).to.equal(200)
227+
expect(res.headers['content-type']).to.equal('image/svg+xml')
228+
229+
done()
230+
})
231+
})
232+
187233
it('load a directory', (done) => {
188234
let dir = 'QmW2WQi7j6c7UgJTarActp7tDNikE4B2qXtFCfLPdsgaTQ/'
189235

Loading
Loading

0 commit comments

Comments
 (0)