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

required changes for the gateway #1003

Merged
merged 1 commit into from
Sep 8, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/http/gateway/dir-view/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ const mainStyle = require('./style')
const pathUtil = require('../utils/path')

function getParentDirectoryURL (originalParts) {
const parts = originalParts.splice()
const parts = originalParts.slice()

if (parts.length > 1) {
parts.pop()
Expand Down
68 changes: 33 additions & 35 deletions src/http/gateway/resolver.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

const mh = require('multihashes')
const promisify = require('promisify-es6')
const eachOfSeries = require('async/eachOfSeries')
const reduce = require('async/reduce')
const CID = require('cids')
const Unixfs = require('ipfs-unixfs')
const debug = require('debug')
Expand All @@ -22,11 +22,7 @@ function getIndexFiles (links) {
return links.filter((link) => INDEX_HTML_FILES.indexOf(link.name) !== -1)
}

function noop () {}

const resolveDirectory = promisify((ipfs, path, multihash, callback) => {
callback = callback || noop

mh.validate(mh.fromB58String(multihash))

ipfs.object.get(multihash, { enc: 'base58' }, (err, dagNode) => {
Expand All @@ -43,43 +39,28 @@ const resolveDirectory = promisify((ipfs, path, multihash, callback) => {
})

const resolveMultihash = promisify((ipfs, path, callback) => {
callback = callback || noop

const parts = pathUtil.splitPath(path)
const partsLength = parts.length

let currentMultihash = parts[0]
let firstMultihash = parts.shift()
let currentCid
eachOfSeries(parts, (multihash, currentIndex, next) => {
// throws error when invalid CID is passed

reduce(parts, firstMultihash, (memo, item, next) => {
try {
currentCid = new CID(mh.fromB58String(currentMultihash))
currentCid = new CID(mh.fromB58String(memo))
} catch (err) {
return next(err)
}

log('currentMultihash: ', currentMultihash)
log('currentIndex: ', currentIndex, '/', partsLength)
log('memo: ', memo)
log('item: ', item)

ipfs.dag.get(currentCid, (err, result) => {
if (err) { return next(err) }
let dagNode = result.value

if (currentIndex === partsLength - 1) {
let dagDataObj = Unixfs.unmarshal(dagNode.data)
if (dagDataObj.type === 'directory') {
let isDirErr = new Error('This dag node is a directory')
// add currentMultihash as a fileName so it can be used by resolveDirectory
isDirErr.fileName = currentMultihash
return next(isDirErr)
}

return next()
}

let dagNode = result.value
// find multihash of requested named-file in current dagNode's links
let multihashOfNextFile
const nextFileName = parts[currentIndex + 1]
let nextFileName = item

const links = dagNode.links

for (let link of links) {
Expand All @@ -92,17 +73,34 @@ const resolveMultihash = promisify((ipfs, path, callback) => {
}

if (!multihashOfNextFile) {
log.error(`no link named "${nextFileName}" under ${currentMultihash}`)
return next(new Error(`no link named "${nextFileName}" under ${currentMultihash}`))
return next(new Error(`no link named "${nextFileName}" under ${memo}`))
}

currentMultihash = multihashOfNextFile
next()
next(null, multihashOfNextFile)
})
}, (err) => {
}, (err, result) => {
if (err) { return callback(err) }

callback(null, { multihash: currentMultihash })
let cid
try {
cid = new CID(mh.fromB58String(result))
} catch (err) {
return callback(err)
}

ipfs.dag.get(cid, (err, dagResult) => {
if (err) return callback(err)

let dagDataObj = Unixfs.unmarshal(dagResult.value.data)
if (dagDataObj.type === 'directory') {
let isDirErr = new Error('This dag node is a directory')
// add memo (last multihash) as a fileName so it can be used by resolveDirectory
isDirErr.fileName = result
return callback(isDirErr)
}

callback(null, { multihash: result })
})
})
})

Expand Down
209 changes: 106 additions & 103 deletions src/http/gateway/resources/gateway.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ const fileType = require('file-type')
const mime = require('mime-types')
const Stream = require('readable-stream')

const GatewayResolver = require('../resolver')
const gatewayResolver = require('../resolver')
const PathUtils = require('../utils/path')

module.exports = {
Expand All @@ -29,113 +29,116 @@ module.exports = {
const ref = request.pre.args.ref
const ipfs = request.server.app.ipfs

return GatewayResolver
.resolveMultihash(ipfs, ref)
.then((data) => {
ipfs
.files
.cat(data.multihash)
.then((stream) => {
if (ref.endsWith('/')) {
// remove trailing slash for files
return reply
.redirect(PathUtils.removeTrailingSlash(ref))
.permanent(true)
} else {
if (!stream._read) {
stream._read = () => {}
stream._readableState = {}
}
// response.continue()
let filetypeChecked = false
let stream2 = new Stream.PassThrough({highWaterMark: 1})
let response = reply(stream2).hold()
function handleGatewayResolverError (err) {
if (err) {
log.error('err: ', err.toString(), ' fileName: ', err.fileName)

pull(
toPull.source(stream),
pull.drain((chunk) => {
// Check file type. do this once.
if (chunk.length > 0 && !filetypeChecked) {
log('got first chunk')
let fileSignature = fileType(chunk)
log('file type: ', fileSignature)
const errorToString = err.toString()
// switch case with true feels so wrong.
switch (true) {
case (errorToString === 'Error: This dag node is a directory'):
gatewayResolver.resolveDirectory(ipfs, ref, err.fileName, (err, data) => {
if (err) {
log.error(err)
return reply(err.toString()).code(500)
}
if (typeof data === 'string') {
// no index file found
if (!ref.endsWith('/')) {
// for a directory, if URL doesn't end with a /
// append / and redirect permanent to that URL
return reply.redirect(`${ref}/`).permanent(true)
} else {
// send directory listing
return reply(data)
}
} else {
// found index file
// redirect to URL/<found-index-file>
return reply.redirect(PathUtils.joinURLParts(ref, data[0].name))
}
})
break
case (errorToString.startsWith('Error: no link named')):
return reply(errorToString).code(404)
case (errorToString.startsWith('Error: multihash length inconsistent')):
case (errorToString.startsWith('Error: Non-base58 character')):
return reply({Message: errorToString, code: 0}).code(400)
default:
log.error(err)
return reply({Message: errorToString, code: 0}).code(500)
}
}
}

return gatewayResolver.resolveMultihash(ipfs, ref, (err, data) => {
if (err) {
return handleGatewayResolverError(err)
}
ipfs.files.cat(data.multihash, (err, stream) => {
if (err) {
log.error(err)
return reply(err.toString()).code(500)
}

filetypeChecked = true
const mimeType = mime.lookup((fileSignature) ? fileSignature.ext : null)
log('ref ', ref)
log('mime-type ', mimeType)
if (ref.endsWith('/')) {
// remove trailing slash for files
return reply
.redirect(PathUtils.removeTrailingSlash(ref))
.permanent(true)
} else {
if (!stream._read) {
stream._read = () => {}
stream._readableState = {}
}
// response.continue()
let filetypeChecked = false
let stream2 = new Stream.PassThrough({highWaterMark: 1})
let response = reply(stream2).hold()

if (mimeType) {
log('writing mimeType')
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)

response
.header('Content-Type', mime.contentType(mimeType))
.header('Access-Control-Allow-Headers', 'X-Stream-Output, X-Chunked-Ouput')
.header('Access-Control-Allow-Methods', 'GET')
.header('Access-Control-Allow-Origin', '*')
.header('Access-Control-Expose-Headers', 'X-Stream-Output, X-Chunked-Ouput')
.send()
} else {
response
.header('Access-Control-Allow-Headers', 'X-Stream-Output, X-Chunked-Ouput')
.header('Access-Control-Allow-Methods', 'GET')
.header('Access-Control-Allow-Origin', '*')
.header('Access-Control-Expose-Headers', 'X-Stream-Output, X-Chunked-Ouput')
.send()
}
}
filetypeChecked = true
const mimeType = mime.lookup((fileSignature) ? fileSignature.ext : null)
log('ref ', ref)
log('mime-type ', mimeType)

stream2.write(chunk)
}, (err) => {
if (err) throw err
log('stream ended.')
stream2.end()
})
)
}
})
.catch((err) => {
if (err) {
log.error(err)
return reply(err.toString()).code(500)
}
})
}).catch((err) => {
log('err: ', err.toString(), ' fileName: ', err.fileName)
if (mimeType) {
log('writing mimeType')

const errorToString = err.toString()
if (errorToString === 'Error: This dag node is a directory') {
return GatewayResolver
.resolveDirectory(ipfs, ref, err.fileName)
.then((data) => {
if (typeof data === 'string') {
// no index file found
if (!ref.endsWith('/')) {
// for a directory, if URL doesn't end with a /
// append / and redirect permanent to that URL
return reply.redirect(`${ref}/`).permanent(true)
} else {
// send directory listing
return reply(data)
}
} else {
// found index file
// redirect to URL/<found-index-file>
return reply.redirect(PathUtils.joinURLParts(ref, data[0].name))
}
}).catch((err) => {
log.error(err)
return reply(err.toString()).code(500)
})
} else if (errorToString.startsWith('Error: no link named')) {
return reply(errorToString).code(404)
} else if (errorToString.startsWith('Error: multihash length inconsistent') ||
errorToString.startsWith('Error: Non-base58 character')) {
return reply({Message: errorToString, code: 0}).code(400)
} else {
log.error(err)
return reply({Message: errorToString, code: 0}).code(500)
}
})
response
.header('Content-Type', mime.contentType(mimeType))
.header('Access-Control-Allow-Headers', 'X-Stream-Output, X-Chunked-Ouput')
.header('Access-Control-Allow-Methods', 'GET')
.header('Access-Control-Allow-Origin', '*')
.header('Access-Control-Expose-Headers', 'X-Stream-Output, X-Chunked-Ouput')
.send()
} else {
response
.header('Access-Control-Allow-Headers', 'X-Stream-Output, X-Chunked-Ouput')
.header('Access-Control-Allow-Methods', 'GET')
.header('Access-Control-Allow-Origin', '*')
.header('Access-Control-Expose-Headers', 'X-Stream-Output, X-Chunked-Ouput')
.send()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some of these addrs are not required. I'll fix it in the main PR.

}
}

stream2.write(chunk)
}),
pull.onEnd(() => {
log('stream ended.')
stream2.end()
})
)
}
})
})
}
}
3 changes: 0 additions & 3 deletions test/gateway/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,9 +38,6 @@ describe('HTTP Gateway', () => {
(cb) => http.api.start(true, cb),
(cb) => {
gateway = http.api.server.select('Gateway')
cb()
},
(cb) => {
const content = (name) => ({
path: `test-folder/${name}`,
content: directoryContent[name]
Expand Down