1 package squashfs 2 3 import ( 4 "io/fs" 5 "path" 6 "strings" 7 ) 8 9 type resolver struct { 10 *squashfs 11 fullPath, path string 12 cutAt int 13 redirectsRemaining int 14 } 15 16 func (s *squashfs) resolve(fpath string, resolveLast bool) (fs.FileInfo, error) { 17 if !fs.ValidPath(fpath) { 18 return nil, fs.ErrInvalid 19 } 20 21 root, err := s.getEntry(s.superblock.RootInode, "") 22 if err != nil { 23 return nil, err 24 } 25 26 const maximumRedirects = 1024 27 28 r := resolver{ 29 squashfs: s, 30 fullPath: fpath, 31 path: fpath, 32 redirectsRemaining: maximumRedirects, 33 } 34 35 return r.resolve(root, resolveLast) 36 } 37 38 func (r *resolver) resolve(root fs.FileInfo, resolveLast bool) (curr fs.FileInfo, err error) { 39 curr = root 40 41 for r.path != "" { 42 if dir, ok := curr.(dirStat); !ok { 43 return nil, fs.ErrInvalid 44 } else if name := r.splitOffNamePart(); isEmptyName(name) { 45 continue 46 } else if curr, err = r.getDirEntry(name, dir.blockIndex, dir.blockOffset, dir.fileSize); err != nil { 47 return nil, err 48 } else if r.isDone(resolveLast) { 49 break 50 } else if sym, ok := curr.(symlinkStat); !ok { 51 continue 52 } else if err := r.handleSymlink(sym); err != nil { 53 return nil, err 54 } 55 56 curr = root 57 } 58 59 return curr, nil 60 } 61 62 func (r *resolver) splitOffNamePart() string { 63 slashPos := strings.Index(r.path, "/") 64 65 var name string 66 67 if slashPos == -1 { 68 name, r.path = r.path, "" 69 } else { 70 name, r.path = r.path[:slashPos], r.path[slashPos+1:] 71 r.cutAt += slashPos + 1 72 } 73 74 return name 75 } 76 77 func (r *resolver) handleSymlink(sym symlinkStat) error { 78 r.redirectsRemaining-- 79 if r.redirectsRemaining == 0 { 80 return fs.ErrInvalid 81 } 82 83 if strings.HasPrefix(sym.targetPath, "/") { 84 r.fullPath = path.Clean(sym.targetPath)[1:] 85 } else if r.path == "" { 86 r.fullPath = path.Join(r.fullPath[:r.cutAt], sym.targetPath, r.path) 87 } else { 88 r.fullPath = path.Join(r.fullPath[:r.cutAt-len(sym.name)-1], sym.targetPath, r.path) 89 } 90 91 r.path = r.fullPath 92 r.cutAt = 0 93 94 return nil 95 } 96 97 func (r *resolver) isDone(resolveLast bool) bool { 98 return r.path == "" && !resolveLast 99 } 100 101 func isEmptyName(name string) bool { 102 return name == "" || name == "." 103 } 104