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

Commit aedaaae

Browse files
committed
feat: support cidv1b32 in resolver
- feat: CID support, added resolver.cid - feat: basic support for HAMD sharded directory - not real support, we need ipfs.resolve for that - fix: return data from raw dag without resolv step Known Issues: - missing tests for CIDv1 License: MIT Signed-off-by: Marcin Rataj <[email protected]>
1 parent 4f1ace2 commit aedaaae

File tree

8 files changed

+472
-81
lines changed

8 files changed

+472
-81
lines changed

README.md

+14-4
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,10 @@
1818
1919
## Usage
2020

21-
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.
21+
22+
### Creating HTTP Response
23+
24+
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.
2225

2326
```js
2427
const { getResponse } = require('ipfs-http-response')
@@ -29,24 +32,31 @@ getResponse(ipfsNode, ipfsPath)
2932
})
3033
```
3134

32-
This module also exports the used ipfs resolver, which should be used when the response needs to be customized.
35+
### Using protocol-agnostic resolver
36+
37+
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:
3338

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

37-
resolver.multihash(ipfsNode, ipfsPath)
42+
resolver.cid(ipfsNode, ipfsPath)
3843
.then((result) => {
3944
...
4045
})
4146
```
4247

48+
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`:
49+
50+
4351
```js
4452
const { resolver } = require('ipfs-http-response')
4553

46-
resolver.directory(node, path, multihash)
54+
resolver.directory(ipfsNode, ipfsPath, cid)
4755
.then((result) => {
4856
...
4957
})
5058
```
5159

60+
`result` will be either a `string` with HTML directory listing or an array with CIDs of `index` pages present in inspected directory.
61+
5262
![ipfs-http-response usage](docs/ipfs-http-response.png "ipfs-http-response usage")

package.json

+2-2
Original file line numberDiff line numberDiff line change
@@ -46,8 +46,8 @@
4646
"aegir": "^13.1.0",
4747
"chai": "^4.1.2",
4848
"dirty-chai": "^2.0.1",
49-
"ipfs": "^0.28.2",
50-
"ipfsd-ctl": "^0.36.0"
49+
"ipfs": "^0.31.4",
50+
"ipfsd-ctl": "^0.39.1"
5151
},
5252
"contributors": [
5353
"André Cruz <[email protected]>",

src/dir-view/index.js

+7-11
Original file line numberDiff line numberDiff line change
@@ -5,21 +5,20 @@ const filesize = require('filesize')
55
const mainStyle = require('./style')
66
const pathUtil = require('../utils/path')
77

8-
function getParentDirectoryURL (originalParts) {
9-
const parts = originalParts.slice()
10-
8+
function getParentHref (path) {
9+
const parts = pathUtil.cidArray(path).slice()
1110
if (parts.length > 1) {
12-
parts.pop()
11+
// drop the last segment in a safe way that works for both paths and urls
12+
return path.replace(`/${parts.pop()}`, '')
1313
}
14-
15-
return [ '', 'ipfs' ].concat(parts).join('/')
14+
return path
1615
}
1716

1817
function buildFilesList (path, links) {
1918
const rows = links.map((link) => {
2019
let row = [
2120
`<div class="ipfs-icon ipfs-_blank">&nbsp;</div>`,
22-
`<a href="${pathUtil.joinURLParts(path, link.name)}">${link.name}</a>`,
21+
`<a href="${path}${path.endsWith('/') ? '' : '/'}${link.name}">${link.name}</a>`,
2322
filesize(link.size)
2423
]
2524

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

3433
function buildTable (path, links) {
35-
const parts = pathUtil.splitPath(path)
36-
const parentDirectoryURL = getParentDirectoryURL(parts)
37-
3834
return `
3935
<table class="table table-striped">
4036
<tbody>
@@ -43,7 +39,7 @@ function buildTable (path, links) {
4339
<div class="ipfs-icon ipfs-_blank">&nbsp;</div>
4440
</td>
4541
<td class="padding">
46-
<a href="${parentDirectoryURL}">..</a>
42+
<a href="${getParentHref(path)}">..</a>
4743
</td>
4844
<td></td>
4945
</tr>

src/index.js

+3-3
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ const response = (ipfsNode, ipfsPath) => {
2525
// switch case with true feels so wrong.
2626
switch (true) {
2727
case (errorString === 'Error: This dag node is a directory'):
28-
resolver.directory(node, path, error.fileName)
28+
resolver.directory(node, path, error.cid)
2929
.then((content) => {
3030
// dir render
3131
if (typeof content === 'string') {
@@ -59,9 +59,9 @@ const response = (ipfsNode, ipfsPath) => {
5959
resolve(Response.redirect(pathUtils.removeTrailingSlash(ipfsPath)))
6060
}
6161

62-
resolver.multihash(ipfsNode, ipfsPath)
62+
resolver.cid(ipfsNode, ipfsPath)
6363
.then((resolvedData) => {
64-
const readableStream = ipfsNode.files.catReadableStream(resolvedData.multihash)
64+
const readableStream = ipfsNode.files.catReadableStream(resolvedData.cid)
6565
const responseStream = new stream.PassThrough({ highWaterMark: 1 })
6666
readableStream.pipe(responseStream)
6767

src/resolver.js

+75-31
Original file line numberDiff line numberDiff line change
@@ -18,14 +18,21 @@ function getIndexFiles (links) {
1818
'index.htm',
1919
'index.shtml'
2020
]
21-
22-
return links.filter((link) => INDEX_HTML_FILES.indexOf(link.name) !== -1)
21+
// directory
22+
let indexes = links.filter((link) => INDEX_HTML_FILES.indexOf(link.name) !== -1)
23+
if (indexes.length) {
24+
return indexes
25+
}
26+
// hamt-sharded-directory uses a 2 char prefix
27+
return links.filter((link) => {
28+
return link.name.length > 2 && INDEX_HTML_FILES.indexOf(link.name.substring(2)) !== -1
29+
})
2330
}
2431

25-
const directory = promisify((ipfs, path, multihash, callback) => {
26-
mh.validate(mh.fromB58String(multihash))
32+
const directory = promisify((ipfs, path, cid, callback) => {
33+
cid = new CID(cid)
2734

28-
ipfs.object.get(multihash, { enc: 'base58' }, (err, dagNode) => {
35+
ipfs.object.get(cid.buffer, (err, dagNode) => {
2936
if (err) {
3037
return callback(err)
3138
}
@@ -41,17 +48,20 @@ const directory = promisify((ipfs, path, multihash, callback) => {
4148
})
4249
})
4350

44-
const multihash = promisify((ipfs, path, callback) => {
45-
const parts = pathUtil.splitPath(path)
46-
let firstMultihash = parts.shift()
51+
const cid = promisify((ipfs, path, callback) => {
52+
const parts = pathUtil.cidArray(path)
53+
let firstCid = parts.shift()
4754
let currentCid
4855

56+
// TODO: replace below with ipfs.resolve(path, {recursive: true})
57+
// (requires changes to js-ipfs/js-ipfs-api)
58+
4959
reduce(
5060
parts,
51-
firstMultihash,
61+
firstCid,
5262
(memo, item, next) => {
5363
try {
54-
currentCid = new CID(mh.fromB58String(memo))
64+
currentCid = new CID(memo)
5565
} catch (err) {
5666
return next(err)
5767
}
@@ -65,56 +75,90 @@ const multihash = promisify((ipfs, path, callback) => {
6575
}
6676

6777
const dagNode = result.value
68-
// find multihash of requested named-file in current dagNode's links
69-
let multihashOfNextFile
78+
// find multihash/cid of requested named-file in current dagNode's links
79+
let cidOfNextFile
7080
const nextFileName = item
7181

72-
for (let link of dagNode.links) {
73-
if (link.name === nextFileName) {
74-
// found multihash of requested named-file
75-
multihashOfNextFile = mh.toB58String(link.multihash)
76-
log('found multihash: ', multihashOfNextFile)
77-
break
82+
try {
83+
for (let link of dagNode.links) {
84+
if (link.name === nextFileName) {
85+
// found multihash/cid of requested named-file
86+
try {
87+
// assume a Buffer with a valid CID
88+
// (cid is allowed instead of multihash since https://github.com/ipld/js-ipld-dag-pb/pull/80)
89+
cidOfNextFile = new CID(link.multihash)
90+
} catch (err) {
91+
// fallback to multihash
92+
cidOfNextFile = new CID(mh.toB58String(link.multihash))
93+
}
94+
break
95+
}
7896
}
97+
} catch (err) {
98+
return next(err)
7999
}
80100

81-
if (!multihashOfNextFile) {
82-
return next(new Error(`no link named "${nextFileName}" under ${memo}`))
101+
if (!cidOfNextFile) {
102+
const missingLinkErr = new Error(`no link named "${nextFileName}" under ${memo}`)
103+
missingLinkErr.parentDagNode = memo
104+
missingLinkErr.missingLinkName = nextFileName
105+
return next(missingLinkErr)
83106
}
84107

85-
next(null, multihashOfNextFile)
108+
next(null, cidOfNextFile)
86109
})
87-
}, (err, result) => {
110+
}, (err, cid) => {
88111
if (err) {
89112
return callback(err)
90113
}
91114

92-
let cid
93115
try {
94-
cid = new CID(mh.fromB58String(result))
116+
cid = new CID(cid)
95117
} catch (err) {
96118
return callback(err)
97119
}
98120

121+
if (cid.codec === 'raw') {
122+
// no need for additional lookup, its raw data
123+
callback(null, { cid })
124+
}
125+
99126
ipfs.dag.get(cid, (err, dagResult) => {
100127
if (err) {
101128
return callback(err)
102129
}
103130

104-
let dagDataObj = Unixfs.unmarshal(dagResult.value.data)
105-
if (dagDataObj.type === 'directory') {
106-
let isDirErr = new Error('This dag node is a directory')
107-
// add memo (last multihash) as a fileName so it can be used by directory
108-
isDirErr.fileName = result
109-
return callback(isDirErr)
131+
try {
132+
let dagDataObj = Unixfs.unmarshal(dagResult.value.data)
133+
// There are at least two types of directories:
134+
// - "directory"
135+
// - "hamt-sharded-directory" (example: QmT5NvUtoM5nWFfrQdVrFtvGfKFmG7AHE8P34isapyhCxX)
136+
if (dagDataObj.type === 'directory' || dagDataObj.type === 'hamt-sharded-directory') {
137+
let isDirErr = new Error('This dag node is a directory')
138+
// store memo of last multihash so it can be used by directory
139+
isDirErr.cid = cid
140+
isDirErr.dagDirType = dagDataObj.type
141+
return callback(isDirErr)
142+
}
143+
} catch (err) {
144+
return callback(err)
110145
}
111146

112-
callback(null, { multihash: result })
147+
callback(null, { cid })
113148
})
114149
})
115150
})
116151

152+
const multihash = promisify((ipfs, path, callback) => {
153+
// deprecated, use 'cid' instead
154+
// (left for backward-compatibility)
155+
cid(ipfs, path)
156+
.then((result) => { callback(null, { multihash: mh.toB58String(result.cid.multihash) }) })
157+
.catch((err) => { callback(err) })
158+
})
159+
117160
module.exports = {
118161
directory: directory,
162+
cid: cid,
119163
multihash: multihash
120164
}

src/utils/path.js

+13-4
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,21 @@
11
'use strict'
22

33
/* eslint-disable no-unused-vars */
4-
function splitPath (path) {
4+
5+
// Converts path or url to an array starting at CID
6+
function cidArray (path) {
57
if (path[path.length - 1] === '/') {
68
path = path.substring(0, path.length - 1)
79
}
8-
9-
return path.substring(6).split('/')
10+
// skip /ipxs/ prefix
11+
if (path.match(/^\/ip[fn]s\//)) {
12+
path = path.substring(6)
13+
}
14+
// skip ipxs:// protocol
15+
if (path.match(/^ip[fn]s:\/\//)) {
16+
path = path.substring(7)
17+
}
18+
return path.split('/')
1019
}
1120

1221
function removeLeadingSlash (url) {
@@ -40,7 +49,7 @@ function joinURLParts (...urls) {
4049
}
4150

4251
module.exports = {
43-
splitPath,
52+
cidArray,
4453
removeLeadingSlash,
4554
removeTrailingSlash,
4655
removeSlashFromBothEnds,

0 commit comments

Comments
 (0)