3
3
const promisify = require ( 'promisify-es6' )
4
4
const CID = require ( 'cids' )
5
5
const multipart = require ( 'ipfs-multipart' )
6
+ const mh = require ( 'multihashes' )
6
7
const Joi = require ( 'joi' )
7
8
const multibase = require ( 'multibase' )
8
9
const Boom = require ( 'boom' )
9
10
const debug = require ( 'debug' )
11
+ const {
12
+ cidToString
13
+ } = require ( '../../../utils/cid' )
10
14
const log = debug ( 'ipfs:http-api:dag' )
11
15
log . error = debug ( 'ipfs:http-api:dag:error' )
12
16
13
17
// common pre request handler that parses the args and returns `key` which is assigned to `request.pre.args`
14
- exports . parseKey = ( request , h ) => {
18
+ exports . parseKey = ( request ) => {
15
19
if ( ! request . query . arg ) {
16
20
throw Boom . badRequest ( "Argument 'key' is required" )
17
21
}
@@ -30,7 +34,7 @@ exports.parseKey = (request, h) => {
30
34
path = `${ parts . join ( '/' ) } `
31
35
}
32
36
33
- if ( path . endsWith ( '/' ) ) {
37
+ if ( path && path . endsWith ( '/' ) ) {
34
38
path = path . substring ( 0 , path . length - 1 )
35
39
}
36
40
@@ -45,10 +49,34 @@ exports.parseKey = (request, h) => {
45
49
}
46
50
}
47
51
52
+ const encodeBufferKeys = ( obj , encoding ) => {
53
+ if ( ! obj ) {
54
+ return obj
55
+ }
56
+
57
+ if ( Buffer . isBuffer ( obj ) ) {
58
+ return obj . toString ( encoding )
59
+ }
60
+
61
+ Object . keys ( obj ) . forEach ( key => {
62
+ if ( Buffer . isBuffer ( obj ) ) {
63
+ obj [ key ] = obj [ key ] . toString ( encoding )
64
+
65
+ return
66
+ }
67
+
68
+ if ( typeof obj [ key ] === 'object' ) {
69
+ obj [ key ] = encodeBufferKeys ( obj [ key ] , encoding )
70
+ }
71
+ } )
72
+
73
+ return obj
74
+ }
75
+
48
76
exports . get = {
49
77
validate : {
50
78
query : Joi . object ( ) . keys ( {
51
- 'data-encoding' : Joi . string ( ) . valid ( [ 'text' , 'base64' ] ) . default ( 'base64 ' ) ,
79
+ 'data-encoding' : Joi . string ( ) . valid ( [ 'text' , 'base64' , 'hex' ] ) . default ( 'text ' ) ,
52
80
'cid-base' : Joi . string ( ) . valid ( multibase . names )
53
81
} ) . unknown ( )
54
82
} ,
@@ -64,22 +92,24 @@ exports.get = {
64
92
} = request . pre . args
65
93
const { ipfs } = request . server . app
66
94
95
+ let dataEncoding = request . query [ 'data-encoding' ]
96
+
97
+ if ( dataEncoding === 'text' ) {
98
+ dataEncoding = 'utf8'
99
+ }
100
+
67
101
let result
68
102
69
103
try {
70
104
result = await ipfs . dag . get ( key , path )
71
105
} catch ( err ) {
72
- throw Boom . boomify ( err , { message : 'Failed to get dag node' } )
106
+ throw Boom . badRequest ( err )
73
107
}
74
108
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
- }
109
+ try {
110
+ result . value = encodeBufferKeys ( result . value , dataEncoding )
111
+ } catch ( err ) {
112
+ throw Boom . boomify ( err )
83
113
}
84
114
85
115
return h . response ( result . value )
@@ -89,11 +119,10 @@ exports.get = {
89
119
exports . put = {
90
120
validate : {
91
121
query : Joi . object ( ) . keys ( {
92
- // TODO validate format, & hash
93
- format : Joi . string ( ) ,
94
- 'input-enc' : Joi . string ( ) . valid ( 'dag-cbor' , 'dag-pb' , 'raw' ) ,
122
+ format : Joi . string ( ) . default ( 'cbor' ) ,
123
+ 'input-enc' : Joi . string ( ) . default ( 'json' ) ,
95
124
pin : Joi . boolean ( ) ,
96
- hash : Joi . string ( ) ,
125
+ hash : Joi . string ( ) . valid ( mh . names ) . default ( 'sha2-256' ) ,
97
126
'cid-base' : Joi . string ( ) . valid ( multibase . names ) . default ( 'base58btc' )
98
127
} ) . unknown ( )
99
128
} ,
@@ -102,78 +131,89 @@ exports.put = {
102
131
// which is assigned to `request.pre.args`
103
132
async parseArgs ( request , h ) {
104
133
if ( ! request . payload ) {
105
- throw Boom . badRequest ( "File argument 'data' is required" )
134
+ throw Boom . badRequest ( "File argument 'object data' is required" )
106
135
}
107
136
108
- const enc = request . query . inputenc
137
+ const enc = request . query [ 'input-enc' ]
138
+
139
+ if ( ! request . headers [ 'content-type' ] ) {
140
+ throw Boom . badRequest ( "File argument 'object data' is required" )
141
+ }
109
142
110
143
const fileStream = await new Promise ( ( resolve , reject ) => {
111
144
multipart . reqParser ( request . payload )
112
145
. on ( 'file' , ( name , stream ) => resolve ( stream ) )
113
- . on ( 'end' , ( ) => reject ( Boom . badRequest ( "File argument 'data' is required" ) ) )
146
+ . on ( 'end' , ( ) => reject ( Boom . badRequest ( "File argument 'object data' is required" ) ) )
114
147
} )
115
148
116
149
let data = await new Promise ( ( resolve , reject ) => {
117
150
fileStream
118
151
. on ( 'data' , data => resolve ( data ) )
119
- . on ( 'end' , ( ) => reject ( Boom . badRequest ( "File argument 'data' is required" ) ) )
152
+ . on ( 'end' , ( ) => reject ( Boom . badRequest ( "File argument 'object data' is required" ) ) )
120
153
} )
121
154
122
- if ( enc === 'json' ) {
155
+ let format = request . query . format
156
+
157
+ if ( format === 'cbor' ) {
158
+ format = 'dag-cbor'
159
+ }
160
+
161
+ let node
162
+
163
+ if ( format === 'raw' ) {
164
+ node = data
165
+ } else if ( enc === 'json' ) {
123
166
try {
124
- data = JSON . parse ( data . toString ( ) )
167
+ node = JSON . parse ( data . toString ( ) )
125
168
} catch ( err ) {
126
169
throw Boom . badRequest ( 'Failed to parse the JSON: ' + err )
127
170
}
128
- }
171
+ } else {
172
+ const { ipfs } = request . server . app
173
+ const codec = ipfs . _ipld . resolvers [ format ]
129
174
130
- try {
131
- return {
132
- buffer : data
175
+ if ( ! codec ) {
176
+ throw Boom . badRequest ( `Missing IPLD format "${ request . query . format } "` )
133
177
}
134
- } catch ( err ) {
135
- throw Boom . badRequest ( 'Failed to create DAG node: ' + err )
178
+
179
+ const deserialize = promisify ( codec . util . deserialize )
180
+
181
+ node = await deserialize ( data )
182
+ }
183
+
184
+ return {
185
+ node,
186
+ format,
187
+ hashAlg : request . query . hash
136
188
}
137
189
} ,
138
190
139
191
// main route handler which is called after the above `parseArgs`, but only if the args were valid
140
192
async handler ( request , h ) {
141
193
const { ipfs } = request . server . app
142
- const { buffer } = request . pre . args
194
+ const { node , format , hashAlg } = request . pre . args
143
195
144
196
let cid
145
197
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
- }
198
+ try {
199
+ cid = await ipfs . dag . put ( node , {
200
+ format : format ,
201
+ hashAlg : hashAlg
202
+ } )
203
+ } catch ( err ) {
204
+ throw Boom . boomify ( err , { message : 'Failed to put node' } )
205
+ }
166
206
167
- if ( request . query . pin ) {
168
- await ipfs . pin . add ( cid )
169
- }
207
+ if ( request . query . pin ) {
208
+ await ipfs . pin . add ( cid )
209
+ }
170
210
171
- resolve ( h . response ( {
172
- Cid : {
173
- '/' : cid . toBaseEncodedString ( request . query . cidbase )
174
- }
175
- } ) )
176
- } )
211
+ return h . response ( {
212
+ Cid : {
213
+ '/' : cidToString ( cid , {
214
+ base : request . query [ 'cid-base' ]
215
+ } )
216
+ }
177
217
} )
178
218
}
179
219
}
@@ -191,7 +231,6 @@ exports.resolve = {
191
231
// main route handler which is called after the above `parseArgs`, but only if the args were valid
192
232
async handler ( request , h ) {
193
233
let { key, path } = request . pre . args
194
- const cidBase = request . query [ 'cid-base' ]
195
234
const { ipfs } = request . server . app
196
235
197
236
// to be consistent with go we need to return the CID to the last node we've traversed
@@ -226,9 +265,11 @@ exports.resolve = {
226
265
227
266
return h . response ( {
228
267
Cid : {
229
- '/' : lastCid . toBaseEncodedString ( cidBase )
268
+ '/' : cidToString ( lastCid , {
269
+ base : request . query [ 'cid-base' ]
270
+ } )
230
271
} ,
231
- RemPath : lastRemainderPath
272
+ RemPath : lastRemainderPath || ''
232
273
} )
233
274
} catch ( err ) {
234
275
throw Boom . boomify ( err )
0 commit comments