@@ -4,15 +4,29 @@ import (
4
4
"bytes"
5
5
"context"
6
6
"fmt"
7
+ bsfetcher "github.com/ipfs/go-fetcher/impl/blockservice"
8
+ format "github.com/ipfs/go-ipld-format"
9
+ "github.com/ipfs/go-libipfs/blocks"
10
+ "github.com/ipfs/go-libipfs/files"
11
+ "github.com/ipfs/go-libipfs/gateway"
12
+ "github.com/ipfs/go-merkledag"
13
+ "github.com/ipfs/go-path"
14
+ "github.com/ipfs/go-path/resolver"
7
15
unixfile "github.com/ipfs/go-unixfs/file"
16
+ "github.com/ipfs/go-unixfsnode"
8
17
"github.com/ipld/go-car"
9
18
"github.com/ipld/go-car/util"
19
+ dagpb "github.com/ipld/go-codec-dagpb"
20
+ "github.com/ipld/go-ipld-prime"
21
+ "github.com/ipld/go-ipld-prime/node/basicnode"
22
+ "github.com/ipld/go-ipld-prime/schema"
10
23
"io"
11
24
"net/http"
12
- "os "
25
+ "net/url "
13
26
"runtime/debug"
14
27
"strconv"
15
28
"strings"
29
+ "sync"
16
30
"time"
17
31
18
32
"github.com/prometheus/client_golang/prometheus"
@@ -76,7 +90,7 @@ func makeGatewayCARHandler(bsrv blockservice.BlockService, port int) (*http.Serv
76
90
if formatParam := r .URL .Query ().Get ("format" ); formatParam != "" {
77
91
isCar = formatParam == "car"
78
92
if ! isCar {
79
- http .Error (w , "only raw format supported" , http .StatusBadRequest )
93
+ http .Error (w , "only car format supported" , http .StatusBadRequest )
80
94
return
81
95
}
82
96
} else {
@@ -104,7 +118,7 @@ func makeGatewayCARHandler(bsrv blockservice.BlockService, port int) (*http.Serv
104
118
return
105
119
}
106
120
107
- carStream , err := simpleSelectorToCar (contentPath )
121
+ carStream , err := simpleSelectorToCar (ctx , bsrv , contentPath . String (), r . URL . Query () )
108
122
if err != nil {
109
123
http .Error (w , "only the ipfs names is supported" , http .StatusBadRequest )
110
124
return
@@ -147,8 +161,8 @@ func makeGatewayCARHandler(bsrv blockservice.BlockService, port int) (*http.Serv
147
161
}, nil
148
162
}
149
163
150
- func simpleSelectorToCar (ipfsPath ipath. Path ) (io.ReadCloser , error ) {
151
- pathSegs := strings .Split (ipfsPath . String () , "/" )
164
+ func simpleSelectorToCar (ctx context. Context , bsrv blockservice. BlockService , p string , params url. Values ) (io.ReadCloser , error ) {
165
+ pathSegs := strings .Split (p , "/" )
152
166
if len (pathSegs ) < 3 || ! (pathSegs [0 ] == "" && pathSegs [1 ] == "ipfs" ) {
153
167
return nil , fmt .Errorf ("invalid path" )
154
168
}
@@ -159,6 +173,11 @@ func simpleSelectorToCar(ipfsPath ipath.Path) (io.ReadCloser, error) {
159
173
return nil , err
160
174
}
161
175
176
+ ipfspath , err := path .ParsePath (p )
177
+ if err != nil {
178
+ return nil , err
179
+ }
180
+
162
181
r , w := io .Pipe ()
163
182
// Setup header for the output car
164
183
err = car .WriteHeader (& car.CarHeader {
@@ -169,20 +188,230 @@ func simpleSelectorToCar(ipfsPath ipath.Path) (io.ReadCloser, error) {
169
188
return nil , fmt .Errorf ("writing car header: %w" , err )
170
189
}
171
190
191
+ rangeStr , hasRange := params .Get ("bytes" ), params .Has ("bytes" )
192
+ depthStr , hasDepth := params .Get ("depth" ), params .Has ("depth" )
193
+
194
+ if hasDepth && ! (depthStr == "0" || depthStr == "1" || depthStr == "all" ) {
195
+ return nil , fmt .Errorf ("depth type: %s not supported" , depthStr )
196
+ }
197
+ var getRange * gateway.GetRange
198
+ if hasRange {
199
+ getRange , err = rangeStrToGetRange (rangeStr )
200
+ if err != nil {
201
+ return nil , err
202
+ }
203
+ }
204
+
172
205
go func () {
173
206
defer w .Close ()
174
- remainingPath := pathSegs [1 :]
175
- unixfile .NewUnixfsFile ()
207
+ blockGetter := merkledag .NewDAGService (bsrv ).Session (ctx )
208
+ blockGetter = & nodeGetterToCarExporer {
209
+ ng : blockGetter ,
210
+ w : w ,
211
+ mhSet : make (map [string ]struct {}),
212
+ }
213
+ dsrv := merkledag .NewReadOnlyDagService (blockGetter )
214
+
215
+ // Setup the UnixFS resolver.
216
+ fetcherConfig := bsfetcher .NewFetcherConfig (bsrv )
217
+ fetcherConfig .PrototypeChooser = dagpb .AddSupportToChooser (func (lnk ipld.Link , lnkCtx ipld.LinkContext ) (ipld.NodePrototype , error ) {
218
+ if tlnkNd , ok := lnkCtx .LinkNode .(schema.TypedLinkNode ); ok {
219
+ return tlnkNd .LinkTargetNodePrototype (), nil
220
+ }
221
+ return basicnode .Prototype .Any , nil
222
+ })
223
+ fetcher := fetcherConfig .WithReifier (unixfsnode .Reify )
224
+ r := resolver .NewBasicResolver (fetcher )
225
+
226
+ lastCid , remainder , err := r .ResolveToLastNode (ctx , ipfspath )
227
+ if err != nil {
228
+ goLog .Error (err )
229
+ return
230
+ }
231
+
232
+ if hasDepth && depthStr == "0" {
233
+ return
234
+ }
235
+
236
+ lastCidNode , err := dsrv .Get (ctx , lastCid )
237
+ if err != nil {
238
+ goLog .Error (err )
239
+ return
240
+ }
176
241
177
- err = util . LdWrite ( os . Stdout , block . Cid (). Bytes (), block . RawData ()) // write to the output car
242
+ ufsNode , err := unixfile . NewUnixfsFile ( ctx , dsrv , lastCidNode )
178
243
if err != nil {
179
- return fmt .Errorf ("writing to output car: %w" , err )
244
+ // It's not UnixFS
245
+
246
+ // If it's all fetch the graph recursively
247
+ if depthStr == "all" {
248
+ if err := merkledag .FetchGraph (ctx , lastCid , dsrv ); err != nil {
249
+ goLog .Error (err )
250
+ }
251
+ return
252
+ }
253
+
254
+ //if not then either this is an error (which we can't report) or this is the last block for us to return
255
+ return
256
+ }
257
+ if f , ok := ufsNode .(files.File ); ok {
258
+ if len (remainder ) > 0 {
259
+ // this is an error, so we're done
260
+ return
261
+ }
262
+ if hasRange {
263
+ // TODO: testing + check off by one errors
264
+ var numToRead int64
265
+ if * getRange .To < 0 {
266
+ size , err := f .Seek (0 , io .SeekEnd )
267
+ if err != nil {
268
+ return
269
+ }
270
+ numToRead = (size - * getRange .To ) - int64 (getRange .From )
271
+ } else {
272
+ numToRead = int64 (getRange .From ) - * getRange .To
273
+ }
274
+
275
+ if _ , err := f .Seek (int64 (getRange .From ), io .SeekStart ); err != nil {
276
+ return
277
+ }
278
+ _ , _ = io .CopyN (io .Discard , f , numToRead )
279
+ return
280
+ }
281
+ } else if d , ok := ufsNode .(files.Directory ); ok {
282
+ if depthStr == "1" {
283
+ for d .Entries ().Next () {
284
+ }
285
+ return
286
+ }
287
+ if depthStr == "all" {
288
+ // TODO: being lazy here
289
+ w , err := files .NewTarWriter (io .Discard )
290
+ if err != nil {
291
+ goLog .Error (fmt .Errorf ("could not create tar write %w" , err ))
292
+ return
293
+ }
294
+ if err := w .WriteFile (d , "tmp" ); err != nil {
295
+ goLog .Error (err )
296
+ return
297
+ }
298
+ return
299
+ }
300
+ } else {
301
+ return
180
302
}
181
303
}()
182
- _ = rootCid
183
304
return r , nil
184
305
}
185
306
307
+ type nodeGetterToCarExporer struct {
308
+ ng format.NodeGetter
309
+ w io.Writer
310
+
311
+ lk sync.RWMutex
312
+ mhSet map [string ]struct {}
313
+ }
314
+
315
+ func (n * nodeGetterToCarExporer ) Get (ctx context.Context , c cid.Cid ) (format.Node , error ) {
316
+ nd , err := n .ng .Get (ctx , c )
317
+ if err != nil {
318
+ return nil , err
319
+ }
320
+
321
+ if err := n .trySendBlock (nd ); err != nil {
322
+ return nil , err
323
+ }
324
+
325
+ return nd , nil
326
+ }
327
+
328
+ func (n * nodeGetterToCarExporer ) GetMany (ctx context.Context , cids []cid.Cid ) <- chan * format.NodeOption {
329
+ ndCh := n .ng .GetMany (ctx , cids )
330
+ outCh := make (chan * format.NodeOption )
331
+ go func () {
332
+ defer close (outCh )
333
+ for nd := range ndCh {
334
+ if nd .Err == nil {
335
+ if err := n .trySendBlock (nd .Node ); err != nil {
336
+ select {
337
+ case outCh <- & format.NodeOption {Err : err }:
338
+ case <- ctx .Done ():
339
+ }
340
+ return
341
+ }
342
+ select {
343
+ case outCh <- nd :
344
+ case <- ctx .Done ():
345
+ }
346
+ }
347
+ }
348
+ }()
349
+ return outCh
350
+ }
351
+
352
+ func (n * nodeGetterToCarExporer ) trySendBlock (block blocks.Block ) error {
353
+ h := string (block .Cid ().Hash ())
354
+ n .lk .RLock ()
355
+ _ , found := n .mhSet [h ]
356
+ n .lk .RUnlock ()
357
+ if ! found {
358
+ doSend := false
359
+ n .lk .Lock ()
360
+ _ , found := n .mhSet [h ]
361
+ if ! found {
362
+ doSend = true
363
+ n .mhSet [h ] = struct {}{}
364
+ }
365
+ n .lk .Unlock ()
366
+ if doSend {
367
+ err := util .LdWrite (n .w , block .Cid ().Bytes (), block .RawData ()) // write to the output car
368
+ if err != nil {
369
+ return fmt .Errorf ("writing to output car: %w" , err )
370
+ }
371
+ }
372
+ }
373
+ return nil
374
+ }
375
+
376
+ var _ format.NodeGetter = (* nodeGetterToCarExporer )(nil )
377
+
378
+ func rangeStrToGetRange (rangeStr string ) (* gateway.GetRange , error ) {
379
+ rangeElems := strings .Split (rangeStr , ":" )
380
+ if len (rangeElems ) > 2 {
381
+ return nil , fmt .Errorf ("invalid range" )
382
+ }
383
+ first , err := strconv .ParseUint (rangeElems [0 ], 10 , 64 )
384
+ if err != nil {
385
+ return nil , err
386
+ }
387
+
388
+ if rangeElems [1 ] == "*" {
389
+ return & gateway.GetRange {
390
+ From : first ,
391
+ To : nil ,
392
+ }, nil
393
+ }
394
+
395
+ second , err := strconv .ParseInt (rangeElems [1 ], 10 , 64 )
396
+ if err != nil {
397
+ return nil , err
398
+ }
399
+
400
+ if second < 0 {
401
+ // TODO: fix, might also require a fix in boxo/gateway
402
+ return nil , fmt .Errorf ("unsupported" )
403
+ }
404
+
405
+ if uint64 (second ) < first {
406
+ return nil , fmt .Errorf ("invalid range" )
407
+ }
408
+
409
+ return & gateway.GetRange {
410
+ From : first ,
411
+ To : & second ,
412
+ }, nil
413
+ }
414
+
186
415
func makeGatewayBlockHandler (bsrv blockservice.BlockService , port int ) (* http.Server , error ) {
187
416
mux := http .NewServeMux ()
188
417
mux .HandleFunc ("/ipfs/" , func (w http.ResponseWriter , r * http.Request ) {
0 commit comments