github.com/ice-blockchain/go/src@v0.0.0-20240403114104-1564d284e521/io/fs/sub.go (about) 1 // Copyright 2020 The Go Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 package fs 6 7 import ( 8 "errors" 9 "path" 10 ) 11 12 // A SubFS is a file system with a Sub method. 13 type SubFS interface { 14 FS 15 16 // Sub returns an FS corresponding to the subtree rooted at dir. 17 Sub(dir string) (FS, error) 18 } 19 20 // Sub returns an [FS] corresponding to the subtree rooted at fsys's dir. 21 // 22 // If dir is ".", Sub returns fsys unchanged. 23 // Otherwise, if fs implements [SubFS], Sub returns fsys.Sub(dir). 24 // Otherwise, Sub returns a new [FS] implementation sub that, 25 // in effect, implements sub.Open(name) as fsys.Open(path.Join(dir, name)). 26 // The implementation also translates calls to ReadDir, ReadFile, and Glob appropriately. 27 // 28 // Note that Sub(os.DirFS("/"), "prefix") is equivalent to os.DirFS("/prefix") 29 // and that neither of them guarantees to avoid operating system 30 // accesses outside "/prefix", because the implementation of [os.DirFS] 31 // does not check for symbolic links inside "/prefix" that point to 32 // other directories. That is, [os.DirFS] is not a general substitute for a 33 // chroot-style security mechanism, and Sub does not change that fact. 34 func Sub(fsys FS, dir string) (FS, error) { 35 if !ValidPath(dir) { 36 return nil, &PathError{Op: "sub", Path: dir, Err: ErrInvalid} 37 } 38 if dir == "." { 39 return fsys, nil 40 } 41 if fsys, ok := fsys.(SubFS); ok { 42 return fsys.Sub(dir) 43 } 44 return &subFS{fsys, dir}, nil 45 } 46 47 type subFS struct { 48 fsys FS 49 dir string 50 } 51 52 // fullName maps name to the fully-qualified name dir/name. 53 func (f *subFS) fullName(op string, name string) (string, error) { 54 if !ValidPath(name) { 55 return "", &PathError{Op: op, Path: name, Err: ErrInvalid} 56 } 57 return path.Join(f.dir, name), nil 58 } 59 60 // shorten maps name, which should start with f.dir, back to the suffix after f.dir. 61 func (f *subFS) shorten(name string) (rel string, ok bool) { 62 if name == f.dir { 63 return ".", true 64 } 65 if len(name) >= len(f.dir)+2 && name[len(f.dir)] == '/' && name[:len(f.dir)] == f.dir { 66 return name[len(f.dir)+1:], true 67 } 68 return "", false 69 } 70 71 // fixErr shortens any reported names in PathErrors by stripping f.dir. 72 func (f *subFS) fixErr(err error) error { 73 if e, ok := err.(*PathError); ok { 74 if short, ok := f.shorten(e.Path); ok { 75 e.Path = short 76 } 77 } 78 return err 79 } 80 81 func (f *subFS) Open(name string) (File, error) { 82 full, err := f.fullName("open", name) 83 if err != nil { 84 return nil, err 85 } 86 file, err := f.fsys.Open(full) 87 return file, f.fixErr(err) 88 } 89 90 func (f *subFS) ReadDir(name string) ([]DirEntry, error) { 91 full, err := f.fullName("read", name) 92 if err != nil { 93 return nil, err 94 } 95 dir, err := ReadDir(f.fsys, full) 96 return dir, f.fixErr(err) 97 } 98 99 func (f *subFS) ReadFile(name string) ([]byte, error) { 100 full, err := f.fullName("read", name) 101 if err != nil { 102 return nil, err 103 } 104 data, err := ReadFile(f.fsys, full) 105 return data, f.fixErr(err) 106 } 107 108 func (f *subFS) Glob(pattern string) ([]string, error) { 109 // Check pattern is well-formed. 110 if _, err := path.Match(pattern, ""); err != nil { 111 return nil, err 112 } 113 if pattern == "." { 114 return []string{"."}, nil 115 } 116 117 full := f.dir + "/" + pattern 118 list, err := Glob(f.fsys, full) 119 for i, name := range list { 120 name, ok := f.shorten(name) 121 if !ok { 122 return nil, errors.New("invalid result from inner fsys Glob: " + name + " not in " + f.dir) // can't use fmt in this package 123 } 124 list[i] = name 125 } 126 return list, f.fixErr(err) 127 } 128 129 func (f *subFS) Sub(dir string) (FS, error) { 130 if dir == "." { 131 return f, nil 132 } 133 full, err := f.fullName("sub", dir) 134 if err != nil { 135 return nil, err 136 } 137 return &subFS{f.fsys, full}, nil 138 }