@@ -14,6 +14,7 @@ import (
14
14
coreiface "github.com/ipfs/boxo/coreiface"
15
15
coreoptions "github.com/ipfs/boxo/coreiface/options"
16
16
corepath "github.com/ipfs/boxo/coreiface/path"
17
+ files "github.com/ipfs/boxo/files"
17
18
ipath "github.com/ipfs/boxo/path"
18
19
"github.com/ipfs/boxo/path/resolver"
19
20
"github.com/ipfs/go-cid"
37
38
dirCache * ipfsDirCache
38
39
info nodeInfo
39
40
nodeTimeout time.Duration
41
+ linkLimit uint
40
42
}
41
43
ipfsSettings struct {
42
44
* IPFS
@@ -65,6 +67,7 @@ func NewIPFS(core coreiface.CoreAPI, options ...IPFSOption) (*IPFS, error) {
65
67
},
66
68
core : core ,
67
69
nodeTimeout : 1 * time .Minute ,
70
+ linkLimit : 40 , // Arbitrary.
68
71
}
69
72
settings = ipfsSettings {
70
73
IPFS : fsys ,
@@ -145,23 +148,20 @@ func WithDirectoryCacheCount(cacheCount int) IPFSOption {
145
148
}
146
149
}
147
150
148
- // WithNodeTimeout sets a timeout duration to use
149
- // when communicating with the IPFS API/node.
150
- // If <= 0, operations will not time out,
151
- // and will remain pending until the file system is closed.
152
- func WithNodeTimeout (duration time.Duration ) IPFSOption {
153
- return func (ifs * ipfsSettings ) error {
154
- ifs .nodeTimeout = duration
155
- return nil
156
- }
157
- }
158
-
159
151
func (* IPFS ) ID () filesystem.ID { return IPFSID }
160
152
161
153
func (fsys * IPFS ) setContext (ctx context.Context ) {
162
154
fsys .ctx , fsys .cancel = context .WithCancel (ctx )
163
155
}
164
156
157
+ func (fsys * IPFS ) setNodeTimeout (timeout time.Duration ) {
158
+ fsys .nodeTimeout = timeout
159
+ }
160
+
161
+ func (fsys * IPFS ) setLinkLimit (limit uint ) {
162
+ fsys .linkLimit = limit
163
+ }
164
+
165
165
func (fsys * IPFS ) setPermissions (permissions fs.FileMode ) {
166
166
fsys .info .mode = fsys .info .mode .Type () | permissions .Perm ()
167
167
}
@@ -171,20 +171,91 @@ func (fsys *IPFS) Close() error {
171
171
return nil
172
172
}
173
173
174
- func (fsys * IPFS ) Stat (name string ) (fs.FileInfo , error ) {
175
- const op = "stat"
174
+ func (fsys * IPFS ) Lstat (name string ) (fs.FileInfo , error ) {
175
+ const op = "lstat"
176
+ info , _ , err := fsys .lstat (op , name )
177
+ return info , err
178
+ }
179
+
180
+ func (fsys * IPFS ) lstat (op , name string ) (fs.FileInfo , cid.Cid , error ) {
176
181
if name == filesystem .Root {
177
- return & fsys .info , nil
182
+ return & fsys .info , cid. Cid {}, nil
178
183
}
179
184
cid , err := fsys .toCID (op , name )
180
185
if err != nil {
181
- return nil , err
186
+ return nil , cid , err
182
187
}
183
188
info , err := fsys .getInfo (name , cid )
184
189
if err != nil {
185
- return nil , fserrors .New (op , name , err , fserrors .IO )
190
+ const kind = fserrors .IO
191
+ return nil , cid , fserrors .New (op , name , err , kind )
192
+ }
193
+ return info , cid , nil
194
+ }
195
+
196
+ func (fsys * IPFS ) Stat (name string ) (fs.FileInfo , error ) {
197
+ const depth = 0
198
+ return fsys .stat (name , depth )
199
+ }
200
+
201
+ func (fsys * IPFS ) stat (name string , depth uint ) (fs.FileInfo , error ) {
202
+ const op = "stat"
203
+ info , cid , err := fsys .lstat (op , name )
204
+ if err != nil {
205
+ return nil , err
206
+ }
207
+ if isLink := info .Mode ()& fs .ModeSymlink != 0 ; ! isLink {
208
+ return info , nil
209
+ }
210
+ if depth ++ ; depth >= fsys .linkLimit {
211
+ return nil , linkLimitError (op , name , fsys .linkLimit )
212
+ }
213
+ target , err := fsys .resolveCIDSymlink (op , name , cid )
214
+ if err != nil {
215
+ return nil , err
216
+ }
217
+ return fsys .stat (target , depth )
218
+ }
219
+
220
+ func (fsys * IPFS ) Readlink (name string ) (string , error ) {
221
+ const op = "readlink"
222
+ if name == filesystem .Root {
223
+ const kind = fserrors .InvalidItem
224
+ return "" , fserrors .New (op , name , errRootLink , kind )
225
+ }
226
+ cid , err := fsys .toCID (op , name )
227
+ if err != nil {
228
+ return "" , err
186
229
}
187
- return info , nil
230
+ return fsys .resolveCIDSymlink (op , name , cid )
231
+ }
232
+
233
+ func readNodeLink (op , name string , node files.Node ) (string , error ) {
234
+ link , ok := node .(* files.Symlink )
235
+ if ! ok {
236
+ const kind = fserrors .InvalidItem
237
+ err := fmt .Errorf (
238
+ "expected node type: %T but got: %T" ,
239
+ link , node ,
240
+ )
241
+ return "" , fserrors .New (op , name , err , kind )
242
+ }
243
+ target := link .Target
244
+ if len (target ) == 0 {
245
+ const kind = fserrors .InvalidItem
246
+ return "" , fserrors .New (op , name , errEmptyLink , kind )
247
+ }
248
+ return target , nil
249
+ }
250
+
251
+ func (fsys * IPFS ) resolveCIDSymlink (op , name string , cid cid.Cid ) (string , error ) {
252
+ var (
253
+ ufs = fsys .core .Unixfs ()
254
+ ctx , cancel = fsys .nodeContext ()
255
+ )
256
+ defer cancel ()
257
+ const allowedPrefix = "/ipfs/"
258
+ return getUnixFSLink (ctx , op , name , ufs , cid , allowedPrefix )
188
259
}
189
260
190
261
func (fsys * IPFS ) toCID (op , goPath string ) (cid.Cid , error ) {
@@ -314,6 +385,11 @@ func (fsys *IPFS) resolvePath(goPath string) (cid.Cid, error) {
314
385
}
315
386
316
387
func (fsys * IPFS ) Open (name string ) (fs.File , error ) {
388
+ const depth = 0
389
+ return fsys .open (name , depth )
390
+ }
391
+
392
+ func (fsys * IPFS ) open (name string , depth uint ) (fs.File , error ) {
317
393
if name == filesystem .Root {
318
394
return emptyRoot {info : & fsys .info }, nil
319
395
}
@@ -325,23 +401,25 @@ func (fsys *IPFS) Open(name string) (fs.File, error) {
325
401
if err != nil {
326
402
return nil , err
327
403
}
328
- file , err := fsys .openCid (name , cid )
329
- if err != nil {
330
- return nil , fserrors .New (op , name , err , fserrors .IO )
331
- }
332
- return file , nil
333
- }
334
-
335
- func (fsys * IPFS ) openCid (name string , cid cid.Cid ) (fs.File , error ) {
336
404
info , err := fsys .getInfo (name , cid )
337
405
if err != nil {
338
- return nil , err
406
+ const kind = fserrors .IO
407
+ return nil , fserrors .New (op , name , err , kind )
339
408
}
340
409
switch typ := info .mode .Type (); typ {
341
410
case fs .FileMode (0 ):
342
411
return fsys .openFile (cid , info )
343
412
case fs .ModeDir :
344
413
return fsys .openDir (cid , info )
414
+ case fs .ModeSymlink :
415
+ if depth ++ ; depth >= fsys .linkLimit {
416
+ return nil , linkLimitError (op , name , fsys .linkLimit )
417
+ }
418
+ target , err := fsys .resolveCIDSymlink (op , name , cid )
419
+ if err != nil {
420
+ return nil , err
421
+ }
422
+ return fsys .open (target , depth )
345
423
default :
346
424
return nil , fmt .Errorf (
347
425
"%w got: \" %s\" want: regular file or directory" ,
@@ -521,3 +599,12 @@ func (id *ipfsDirectory) Close() error {
521
599
}
522
600
return fserrors .New (op , id .info .name , fs .ErrClosed , fserrors .InvalidItem )
523
601
}
602
+
603
+ func linkLimitError (op , name string , limit uint ) error {
604
+ const kind = fserrors .Recursion
605
+ err := fmt .Errorf (
606
+ "reached symbolic link resolution limit (%d) during operation" ,
607
+ limit ,
608
+ )
609
+ return fserrors .New (op , name , err , kind )
610
+ }
0 commit comments