Skip to content
This repository has been archived by the owner on Sep 28, 2021. It is now read-only.

Add support for CIDv1 and Base32 #9

Merged
merged 6 commits into from
Sep 28, 2018
Merged
Show file tree
Hide file tree
Changes from 3 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
18 changes: 14 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,10 @@

## Usage

This project consists on creating a HTTP response from an IPFS Hash. This response can be a file, a directory list view or the entry point of a web page.

### Creating HTTP Response

This project creates a HTTP response for an IPFS Path. This response can be a file, a HTML with directory listing or the entry point of a web page.

```js
const { getResponse } = require('ipfs-http-response')
Expand All @@ -29,24 +32,31 @@ getResponse(ipfsNode, ipfsPath)
})
```

This module also exports the used ipfs resolver, which should be used when the response needs to be customized.
### Using protocol-agnostic resolver

This module also exports the used ipfs `resolver`, which should be used when the response needs to be customized or non-HTTP transport is used:

```js
const { resolver } = require('ipfs-http-response')

resolver.multihash(ipfsNode, ipfsPath)
resolver.cid(ipfsNode, ipfsPath)
.then((result) => {
...
})
```

If `ipfsPath` points at a directory, `resolver.cid` will throw Error `This dag node is a directory` with a `cid` attribute that can be passed to `resolver.directory`:


```js
const { resolver } = require('ipfs-http-response')

resolver.directory(node, path, multihash)
resolver.directory(ipfsNode, ipfsPath, cid)
.then((result) => {
...
})
```

`result` will be either a `string` with HTML directory listing or an array with CIDs of `index` pages present in inspected directory.

![ipfs-http-response usage](docs/ipfs-http-response.png "ipfs-http-response usage")
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,8 @@
"aegir": "^13.1.0",
"chai": "^4.1.2",
"dirty-chai": "^2.0.1",
"ipfs": "^0.28.2",
"ipfsd-ctl": "^0.36.0"
"ipfs": "^0.32.2",
"ipfsd-ctl": "^0.39.1"
},
"contributors": [
"André Cruz <andremiguelcruz@msn.com>",
Expand Down
18 changes: 7 additions & 11 deletions src/dir-view/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,21 +5,20 @@ const filesize = require('filesize')
const mainStyle = require('./style')
const pathUtil = require('../utils/path')

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

function getParentHref (path) {
const parts = pathUtil.cidArray(path).slice()
if (parts.length > 1) {
parts.pop()
// drop the last segment in a safe way that works for both paths and urls
return path.replace(`/${parts.pop()}`, '')
}

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

function 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>`,
`<a href="${path}${path.endsWith('/') ? '' : '/'}${link.name}">${link.name}</a>`,
filesize(link.size)
]

Expand All @@ -32,9 +31,6 @@ function buildFilesList (path, links) {
}

function buildTable (path, links) {
const parts = pathUtil.splitPath(path)
const parentDirectoryURL = getParentDirectoryURL(parts)

return `
<table class="table table-striped">
<tbody>
Expand All @@ -43,7 +39,7 @@ function buildTable (path, links) {
<div class="ipfs-icon ipfs-_blank">&nbsp;</div>
</td>
<td class="padding">
<a href="${parentDirectoryURL}">..</a>
<a href="${getParentHref(path)}">..</a>
</td>
<td></td>
</tr>
Expand Down
7 changes: 4 additions & 3 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ const resolver = require('./resolver')
const pathUtils = require('./utils/path')
const detectContentType = require('./utils/content-type')

// TODO: pass path and add Etag and X-Ipfs-Path + tests
const header = (status = 200, statusText = 'OK', headers = {}) => ({
status,
statusText,
Expand All @@ -25,7 +26,7 @@ const response = (ipfsNode, ipfsPath) => {
// switch case with true feels so wrong.
switch (true) {
case (errorString === 'Error: This dag node is a directory'):
resolver.directory(node, path, error.fileName)
resolver.directory(node, path, error.cid)
.then((content) => {
// dir render
if (typeof content === 'string') {
Expand Down Expand Up @@ -59,9 +60,9 @@ const response = (ipfsNode, ipfsPath) => {
resolve(Response.redirect(pathUtils.removeTrailingSlash(ipfsPath)))
}

resolver.multihash(ipfsNode, ipfsPath)
resolver.cid(ipfsNode, ipfsPath)
.then((resolvedData) => {
const readableStream = ipfsNode.files.catReadableStream(resolvedData.multihash)
const readableStream = ipfsNode.files.catReadableStream(resolvedData.cid)
const responseStream = new stream.PassThrough({ highWaterMark: 1 })
readableStream.pipe(responseStream)

Expand Down
106 changes: 75 additions & 31 deletions src/resolver.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,21 @@ function getIndexFiles (links) {
'index.htm',
'index.shtml'
]

return links.filter((link) => INDEX_HTML_FILES.indexOf(link.name) !== -1)
// directory
let indexes = links.filter((link) => INDEX_HTML_FILES.indexOf(link.name) !== -1)
if (indexes.length) {
return indexes
}
// hamt-sharded-directory uses a 2 char prefix
return links.filter((link) => {
return link.name.length > 2 && INDEX_HTML_FILES.indexOf(link.name.substring(2)) !== -1
})
}

const directory = promisify((ipfs, path, multihash, callback) => {
mh.validate(mh.fromB58String(multihash))
const directory = promisify((ipfs, path, cid, callback) => {
cid = new CID(cid)

ipfs.object.get(multihash, { enc: 'base58' }, (err, dagNode) => {
ipfs.object.get(cid.buffer, (err, dagNode) => {
if (err) {
return callback(err)
}
Expand All @@ -41,17 +48,20 @@ const directory = promisify((ipfs, path, multihash, callback) => {
})
})

const multihash = promisify((ipfs, path, callback) => {
const parts = pathUtil.splitPath(path)
let firstMultihash = parts.shift()
const cid = promisify((ipfs, path, callback) => {
const parts = pathUtil.cidArray(path)
let firstCid = parts.shift()
let currentCid

// TODO: replace below with ipfs.resolve(path, {recursive: true})
// (requires changes to js-ipfs/js-ipfs-api)

reduce(
parts,
firstMultihash,
firstCid,
(memo, item, next) => {
try {
currentCid = new CID(mh.fromB58String(memo))
currentCid = new CID(memo)
} catch (err) {
return next(err)
}
Expand All @@ -65,56 +75,90 @@ const multihash = promisify((ipfs, path, callback) => {
}

const dagNode = result.value
// find multihash of requested named-file in current dagNode's links
let multihashOfNextFile
// find multihash/cid of requested named-file in current dagNode's links
let cidOfNextFile
const nextFileName = item

for (let link of dagNode.links) {
if (link.name === nextFileName) {
// found multihash of requested named-file
multihashOfNextFile = mh.toB58String(link.multihash)
log('found multihash: ', multihashOfNextFile)
break
try {
for (let link of dagNode.links) {
if (link.name === nextFileName) {
// found multihash/cid of requested named-file
try {
// assume a Buffer with a valid CID
// (cid is allowed instead of multihash since https://github.com/ipld/js-ipld-dag-pb/pull/80)
cidOfNextFile = new CID(link.multihash)
} catch (err) {
// fallback to multihash
cidOfNextFile = new CID(mh.toB58String(link.multihash))
}
break
}
}
} catch (err) {
return next(err)
}

if (!multihashOfNextFile) {
return next(new Error(`no link named "${nextFileName}" under ${memo}`))
if (!cidOfNextFile) {
const missingLinkErr = new Error(`no link named "${nextFileName}" under ${memo}`)
missingLinkErr.parentDagNode = memo
missingLinkErr.missingLinkName = nextFileName
return next(missingLinkErr)
}

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

let cid
try {
cid = new CID(mh.fromB58String(result))
cid = new CID(cid)
} catch (err) {
return callback(err)
}

if (cid.codec === 'raw') {
// no need for additional lookup, its raw data
callback(null, { cid })
}

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 directory
isDirErr.fileName = result
return callback(isDirErr)
try {
let dagDataObj = Unixfs.unmarshal(dagResult.value.data)
// There are at least two types of directories:
// - "directory"
// - "hamt-sharded-directory" (example: QmT5NvUtoM5nWFfrQdVrFtvGfKFmG7AHE8P34isapyhCxX)
if (dagDataObj.type === 'directory' || dagDataObj.type === 'hamt-sharded-directory') {
let isDirErr = new Error('This dag node is a directory')
// store memo of last multihash so it can be used by directory
isDirErr.cid = isDirErr.fileName = cid
Copy link
Member Author

Choose a reason for hiding this comment

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

Backward compatibility: js-ipfs uses fileName field, so we are keeping it here for now.
When ipfs-http-response is released with these changes, will PR js-ipfs to switch from fileName to cid

isDirErr.dagDirType = dagDataObj.type
return callback(isDirErr)
}
} catch (err) {
return callback(err)
}

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

const multihash = promisify((ipfs, path, callback) => {
// deprecated, use 'cid' instead
// (left for backward-compatibility)
cid(ipfs, path)
.then((result) => { callback(null, { multihash: mh.toB58String(result.cid.multihash) }) })
.catch((err) => { callback(err) })
})

module.exports = {
directory: directory,
cid: cid,
multihash: multihash
}
17 changes: 13 additions & 4 deletions src/utils/path.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,21 @@
'use strict'

/* eslint-disable no-unused-vars */
function splitPath (path) {

// Converts path or url to an array starting at CID
function cidArray (path) {
if (path[path.length - 1] === '/') {
path = path.substring(0, path.length - 1)
}

return path.substring(6).split('/')
// skip /ipxs/ prefix
if (path.match(/^\/ip[fn]s\//)) {
path = path.substring(6)
}
// skip ipxs:// protocol
if (path.match(/^ip[fn]s:\/\//)) {
path = path.substring(7)
}
return path.split('/')
}

function removeLeadingSlash (url) {
Expand Down Expand Up @@ -40,7 +49,7 @@ function joinURLParts (...urls) {
}

module.exports = {
splitPath,
cidArray,
removeLeadingSlash,
removeTrailingSlash,
removeSlashFromBothEnds,
Expand Down
Loading