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

Add gateway route /ipfs/* #698

Merged
merged 6 commits into from
Aug 29, 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
4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -126,12 +126,14 @@
"lodash.get": "^4.4.2",
"lodash.sortby": "^4.7.0",
"lodash.values": "^4.3.0",
"mime-types": "^2.1.13",
"mafmt": "^2.1.8",
"mkdirp": "^0.5.1",
"multiaddr": "^2.3.0",
"multihashes": "~0.4.5",
"once": "^1.4.0",
"path-exists": "^3.0.0",
"promised-for": "^1.0.0",
"peer-book": "^0.5.0",
"peer-id": "^0.9.0",
"peer-info": "^0.10.0",
Expand Down Expand Up @@ -204,4 +206,4 @@
"tcme <hi@this-connect.me>",
"ᴠɪᴄᴛᴏʀ ʙᴊᴇʟᴋʜᴏʟᴍ <victorbjelkholm@gmail.com>"
]
}
}
86 changes: 86 additions & 0 deletions src/http-api/gateway/resolver.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
'use strict'

const mh = require('multihashes')
const pf = require('promised-for')

const html = require('./utils/html')
const PathUtil = require('./utils/path')

const INDEX_HTML_FILES = [ 'index.html', 'index.htm', 'index.shtml' ]

const resolveDirectory = (ipfs, path, multihash) => {
return ipfs
.object
.get(multihash, { enc: 'base58' })
.then((DAGNode) => {
const links = DAGNode.links
const indexFiles = links.filter((link) => INDEX_HTML_FILES.indexOf(link.name) !== -1)

// found index file in links
if (indexFiles.length > 0) {
return indexFiles
}

return html.build(path, links)
})
}

const resolveMultihash = (ipfs, path) => {
const parts = PathUtil.splitPath(path)
const partsLength = parts.length

return pf(
{
multihash: parts[0],
index: 0
},
(i) => i.index < partsLength,
(i) => {
const currentIndex = i.index
const currentMultihash = i.multihash

// throws error when invalid multihash is passed
mh.validate(mh.fromB58String(currentMultihash))

return ipfs
.object
.get(currentMultihash, { enc: 'base58' })
.then((DAGNode) => {
if (currentIndex === partsLength - 1) {
// leaf node
return {
multihash: currentMultihash,
index: currentIndex + 1
}
} else {
// find multihash of requested named-file
// in current DAGNode's links
let multihashOfNextFile
const nextFileName = parts[currentIndex + 1]
const links = DAGNode.links

for (let link of links) {
if (link.name === nextFileName) {
// found multihash of requested named-file
multihashOfNextFile = mh.toB58String(link.multihash)
break
}
}

if (!multihashOfNextFile) {
throw new Error(`no link named "${nextFileName}" under ${currentMultihash}`)
}

return {
multihash: multihashOfNextFile,
index: currentIndex + 1
}
}
})
})
}

module.exports = {
resolveDirectory,
resolveMultihash
}
83 changes: 83 additions & 0 deletions src/http-api/gateway/utils/html.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
'use strict'

const filesize = require('filesize')

const HTML_PAGE_STYLE = require('./style')
const PathUtil = require('./path')

const getParentDirectoryURL = (originalParts) => {
const parts = originalParts.splice()

if (parts.length > 1) {
parts.pop()
}

return [ '', 'ipfs' ].concat(parts).join('/')
}

const buildFilesList = (path, links) => {
const rows = links.map((link) => {
let row = [
`<div class="ipfs-icon ipfs-_blank">&nbsp;</div>`,
`<a href="${PathUtil.joinURLParts(path, link.name)}">${link.name}</a>`,
filesize(link.size)
]

row = row.map((cell) => `<td>${cell}</td>`).join('')

return `<tr>${row}</tr>`
})

return rows.join('')
}

const buildTable = (path, links) => {
const parts = PathUtil.splitPath(path)
let parentDirectoryURL = getParentDirectoryURL(parts)

return `
<table class="table table-striped">
<tbody>
<tr>
<td class="narrow">
<div class="ipfs-icon ipfs-_blank">&nbsp;</div>
</td>
<td class="padding">
<a href="${parentDirectoryURL}">..</a>
</td>
<td></td>
</tr>
${buildFilesList(path, links)}
</tbody>
</table>
`
}

module.exports.build = (path, links) => {
return `
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>${path}</title>
<style>${HTML_PAGE_STYLE}</style>
</head>
<body>
<div id="header" class="row">
<div class="col-xs-2">
<div id="logo" class="ipfs-logo"></div>
</div>
</div>
<br>
<div class="col-xs-12">
<div class="panel panel-default">
<div class="panel-heading">
<strong>Index of ${path}</strong>
</div>
${buildTable(path, links)}
</div>
</div>
</body>
</html>
`
}
35 changes: 35 additions & 0 deletions src/http-api/gateway/utils/path.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
'use strict'

const splitPath = (path) => {
if (path[path.length - 1] === '/') path = path.substring(0, path.length - 1)
return path.substring(6).split('/')
}

const removeLeadingSlash = (url) => {
if (url[0] === '/') url = url.substring(1)
return url
}

const removeTrailingSlash = (url) => {
if (url.endsWith('/')) url = url.substring(0, url.length - 1)
return url
}

const removeSlashFromBothEnds = (url) => {
url = removeLeadingSlash(url)
url = removeTrailingSlash(url)
return url
}

const joinURLParts = (...urls) => {
urls = urls.filter((url) => url.length > 0)
urls = [ '' ].concat(urls.map((url) => removeSlashFromBothEnds(url)))

return urls.join('/')
}

module.exports = {
splitPath,
removeTrailingSlash,
joinURLParts
}
16 changes: 16 additions & 0 deletions src/http-api/gateway/utils/style.js

Large diffs are not rendered by default.

93 changes: 93 additions & 0 deletions src/http-api/resources/files.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@ const toPull = require('stream-to-pull-stream')
const pushable = require('pull-pushable')
const EOL = require('os').EOL
const toStream = require('pull-stream-to-stream')
const mime = require('mime-types')

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

exports = module.exports

Expand Down Expand Up @@ -213,3 +217,92 @@ exports.add = {
)
}
}

exports.gateway = {
checkHash: (request, reply) => {
if (!request.params.hash) {
return reply('Path Resolve error: path must contain at least one component').code(400).takeover()
}

return reply({
ref: `/ipfs/${request.params.hash}`
})
},
handler: (request, reply) => {
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 {
const mimeType = mime.lookup(ref)

if (!stream._read) {
stream._read = () => {}
stream._readableState = {}
}

if (mimeType) {
return reply(stream)
.header('Content-Type', mime.contentType(mimeType))
.header('X-Stream-Output', '1')
} else {
return reply(stream)
.header('X-Stream-Output', '1')
}
}
})
.catch((err) => {
if (err.toString() === 'Error: This dag node is a directory') {
return GatewayResolver
.resolveDirectory(ipfs, ref, data.multihash)
.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 {
log.error(err)
return reply(err.toString()).code(500)
}
})
}).catch((err) => {
const errorToString = err.toString()

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(errorToString).code(400)
} else {
log.error(err)
return reply(errorToString).code(500)
}
})
}
}
12 changes: 12 additions & 0 deletions src/http-api/routes/files.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ const resources = require('./../resources')

module.exports = (server) => {
const api = server.select('API')
const gateway = server.select('Gateway')

api.route({
// TODO fix method
Expand Down Expand Up @@ -41,4 +42,15 @@ module.exports = (server) => {
handler: resources.files.add.handler
}
})

gateway.route({
method: '*',
path: '/ipfs/{hash*}',
config: {
pre: [
{ method: resources.files.gateway.checkHash, assign: 'args' }
],
handler: resources.files.gateway.handler
}
})
}