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

Commit 976e955

Browse files
committed
chore: update pr after review
1 parent 6111234 commit 976e955

File tree

4 files changed

+525
-198
lines changed

4 files changed

+525
-198
lines changed

src/http/api/resources/dag.js

Lines changed: 130 additions & 86 deletions
Original file line numberDiff line numberDiff line change
@@ -3,58 +3,89 @@
33
const promisify = require('promisify-es6')
44
const CID = require('cids')
55
const multipart = require('ipfs-multipart')
6+
const mh = require('multihashes')
67
const Joi = require('joi')
78
const multibase = require('multibase')
89
const Boom = require('boom')
910
const debug = require('debug')
11+
const {
12+
cidToString
13+
} = require('../../../utils/cid')
1014
const log = debug('ipfs:http-api:dag')
1115
log.error = debug('ipfs:http-api:dag:error')
1216

1317
// common pre request handler that parses the args and returns `key` which is assigned to `request.pre.args`
14-
exports.parseKey = (request, h) => {
15-
if (!request.query.arg) {
16-
throw Boom.badRequest("Argument 'key' is required")
17-
}
18+
exports.parseKey = (argument = 'Argument', name = 'key', quote = "'") => {
19+
return (request) => {
20+
if (!request.query.arg) {
21+
// for compatibility with go error messages
22+
throw Boom.badRequest(`${argument} ${quote}${name}${quote} is required`)
23+
}
1824

19-
let key = request.query.arg.trim()
20-
let path
25+
let key = request.query.arg.trim()
26+
let path
2127

22-
if (key.startsWith('/ipfs')) {
23-
key = key.substring(5)
24-
}
28+
if (key.startsWith('/ipfs')) {
29+
key = key.substring(5)
30+
}
2531

26-
const parts = key.split('/')
32+
const parts = key.split('/')
33+
34+
if (parts.length > 1) {
35+
key = parts.shift()
36+
path = `${parts.join('/')}`
37+
}
38+
39+
if (path && path.endsWith('/')) {
40+
path = path.substring(0, path.length - 1)
41+
}
2742

28-
if (parts.length > 1) {
29-
key = parts.shift()
30-
path = `${parts.join('/')}`
43+
try {
44+
return {
45+
[name]: new CID(key),
46+
path
47+
}
48+
} catch (err) {
49+
log.error(err)
50+
throw Boom.badRequest("invalid 'ipfs ref' path")
51+
}
3152
}
53+
}
3254

33-
if (path.endsWith('/')) {
34-
path = path.substring(0, path.length - 1)
55+
const encodeBufferKeys = (obj, encoding) => {
56+
if (!obj) {
57+
return obj
3558
}
3659

37-
try {
38-
return {
39-
key: new CID(key),
40-
path
41-
}
42-
} catch (err) {
43-
log.error(err)
44-
throw Boom.badRequest("invalid 'ipfs ref' path")
60+
if (Buffer.isBuffer(obj)) {
61+
return obj.toString(encoding)
4562
}
63+
64+
Object.keys(obj).forEach(key => {
65+
if (Buffer.isBuffer(obj)) {
66+
obj[key] = obj[key].toString(encoding)
67+
68+
return
69+
}
70+
71+
if (typeof obj[key] === 'object') {
72+
obj[key] = encodeBufferKeys(obj[key], encoding)
73+
}
74+
})
75+
76+
return obj
4677
}
4778

4879
exports.get = {
4980
validate: {
5081
query: Joi.object().keys({
51-
'data-encoding': Joi.string().valid(['text', 'base64']).default('base64'),
82+
'data-encoding': Joi.string().valid(['text', 'base64', 'hex']).default('text'),
5283
'cid-base': Joi.string().valid(multibase.names)
5384
}).unknown()
5485
},
5586

5687
// uses common parseKey method that returns a `key`
57-
parseArgs: exports.parseKey,
88+
parseArgs: exports.parseKey(),
5889

5990
// main route handler which is called after the above `parseArgs`, but only if the args were valid
6091
async handler (request, h) {
@@ -64,22 +95,24 @@ exports.get = {
6495
} = request.pre.args
6596
const { ipfs } = request.server.app
6697

98+
let dataEncoding = request.query['data-encoding']
99+
100+
if (dataEncoding === 'text') {
101+
dataEncoding = 'utf8'
102+
}
103+
67104
let result
68105

69106
try {
70107
result = await ipfs.dag.get(key, path)
71108
} catch (err) {
72-
throw Boom.boomify(err, { message: 'Failed to get dag node' })
109+
throw Boom.badRequest(err)
73110
}
74111

75-
if (key.codec === 'dag-pb' && result.value) {
76-
if (typeof result.value.toJSON === 'function') {
77-
result.value = result.value.toJSON()
78-
}
79-
80-
if (Buffer.isBuffer(result.value.data)) {
81-
result.value.data = result.value.data.toString(request.query.dataencoding)
82-
}
112+
try {
113+
result.value = encodeBufferKeys(result.value, dataEncoding)
114+
} catch (err) {
115+
throw Boom.boomify(err)
83116
}
84117

85118
return h.response(result.value)
@@ -89,11 +122,10 @@ exports.get = {
89122
exports.put = {
90123
validate: {
91124
query: Joi.object().keys({
92-
// TODO validate format, & hash
93-
format: Joi.string(),
94-
'input-enc': Joi.string().valid('dag-cbor', 'dag-pb', 'raw'),
125+
format: Joi.string().default('cbor'),
126+
'input-enc': Joi.string().default('json'),
95127
pin: Joi.boolean(),
96-
hash: Joi.string(),
128+
hash: Joi.string().valid(mh.names).default('sha2-256'),
97129
'cid-base': Joi.string().valid(multibase.names).default('base58btc')
98130
}).unknown()
99131
},
@@ -102,78 +134,89 @@ exports.put = {
102134
// which is assigned to `request.pre.args`
103135
async parseArgs (request, h) {
104136
if (!request.payload) {
105-
throw Boom.badRequest("File argument 'data' is required")
137+
throw Boom.badRequest("File argument 'object data' is required")
106138
}
107139

108-
const enc = request.query.inputenc
140+
const enc = request.query['input-enc']
141+
142+
if (!request.headers['content-type']) {
143+
throw Boom.badRequest("File argument 'object data' is required")
144+
}
109145

110146
const fileStream = await new Promise((resolve, reject) => {
111147
multipart.reqParser(request.payload)
112148
.on('file', (name, stream) => resolve(stream))
113-
.on('end', () => reject(Boom.badRequest("File argument 'data' is required")))
149+
.on('end', () => reject(Boom.badRequest("File argument 'object data' is required")))
114150
})
115151

116152
let data = await new Promise((resolve, reject) => {
117153
fileStream
118154
.on('data', data => resolve(data))
119-
.on('end', () => reject(Boom.badRequest("File argument 'data' is required")))
155+
.on('end', () => reject(Boom.badRequest("File argument 'object data' is required")))
120156
})
121157

122-
if (enc === 'json') {
158+
let format = request.query.format
159+
160+
if (format === 'cbor') {
161+
format = 'dag-cbor'
162+
}
163+
164+
let node
165+
166+
if (format === 'raw') {
167+
node = data
168+
} else if (enc === 'json') {
123169
try {
124-
data = JSON.parse(data.toString())
170+
node = JSON.parse(data.toString())
125171
} catch (err) {
126172
throw Boom.badRequest('Failed to parse the JSON: ' + err)
127173
}
128-
}
174+
} else {
175+
const { ipfs } = request.server.app
176+
const codec = ipfs._ipld.resolvers[format]
129177

130-
try {
131-
return {
132-
buffer: data
178+
if (!codec) {
179+
throw Boom.badRequest(`Missing IPLD format "${request.query.format}"`)
133180
}
134-
} catch (err) {
135-
throw Boom.badRequest('Failed to create DAG node: ' + err)
181+
182+
const deserialize = promisify(codec.util.deserialize)
183+
184+
node = await deserialize(data)
185+
}
186+
187+
return {
188+
node,
189+
format,
190+
hashAlg: request.query.hash
136191
}
137192
},
138193

139194
// main route handler which is called after the above `parseArgs`, but only if the args were valid
140195
async handler (request, h) {
141196
const { ipfs } = request.server.app
142-
const { buffer } = request.pre.args
197+
const { node, format, hashAlg } = request.pre.args
143198

144199
let cid
145200

146-
return new Promise((resolve, reject) => {
147-
const format = ipfs._ipld.resolvers[request.query.format]
148-
149-
if (!format) {
150-
return reject(Boom.badRequest(`Missing IPLD format "${request.query.format}"`))
151-
}
152-
153-
format.util.deserialize(buffer, async (err, node) => {
154-
if (err) {
155-
return reject(err)
156-
}
157-
158-
try {
159-
cid = await ipfs.dag.put(node, {
160-
format: request.query.format,
161-
hashAlg: request.query.hash
162-
})
163-
} catch (err) {
164-
throw Boom.boomify(err, { message: 'Failed to put node' })
165-
}
201+
try {
202+
cid = await ipfs.dag.put(node, {
203+
format: format,
204+
hashAlg: hashAlg
205+
})
206+
} catch (err) {
207+
throw Boom.boomify(err, { message: 'Failed to put node' })
208+
}
166209

167-
if (request.query.pin) {
168-
await ipfs.pin.add(cid)
169-
}
210+
if (request.query.pin) {
211+
await ipfs.pin.add(cid)
212+
}
170213

171-
resolve(h.response({
172-
Cid: {
173-
'/': cid.toBaseEncodedString(request.query.cidbase)
174-
}
175-
}))
176-
})
214+
return h.response({
215+
Cid: {
216+
'/': cidToString(cid, {
217+
base: request.query['cid-base']
218+
})
219+
}
177220
})
178221
}
179222
}
@@ -186,18 +229,17 @@ exports.resolve = {
186229
},
187230

188231
// uses common parseKey method that returns a `key`
189-
parseArgs: exports.parseKey,
232+
parseArgs: exports.parseKey('argument', 'ref', '"'),
190233

191234
// main route handler which is called after the above `parseArgs`, but only if the args were valid
192235
async handler (request, h) {
193-
let { key, path } = request.pre.args
194-
const cidBase = request.query['cid-base']
236+
let { ref, path } = request.pre.args
195237
const { ipfs } = request.server.app
196238

197239
// to be consistent with go we need to return the CID to the last node we've traversed
198240
// along with the path inside that node as the remainder path
199241
try {
200-
let lastCid = key
242+
let lastCid = ref
201243
let lastRemainderPath = path
202244

203245
while (true) {
@@ -226,9 +268,11 @@ exports.resolve = {
226268

227269
return h.response({
228270
Cid: {
229-
'/': lastCid.toBaseEncodedString(cidBase)
271+
'/': cidToString(lastCid, {
272+
base: request.query['cid-base']
273+
})
230274
},
231-
RemPath: lastRemainderPath
275+
RemPath: lastRemainderPath || ''
232276
})
233277
} catch (err) {
234278
throw Boom.boomify(err)

0 commit comments

Comments
 (0)