github.com/anishathalye/periscope@v0.3.5/internal/periscope/file.go (about) 1 package periscope 2 3 import ( 4 "github.com/anishathalye/periscope/internal/herror" 5 6 "fmt" 7 "io" 8 "os" 9 "path/filepath" 10 "strings" 11 12 "golang.org/x/crypto/blake2b" 13 ) 14 15 const initialChunkSize = 4 * 1024 16 const readChunkSize = 1024 * 1024 17 const ShortHashSize = 8 18 const HashSize = blake2b.Size256 19 20 func hashToArray(hash []byte) [HashSize]byte { 21 var res [HashSize]byte 22 copy(res[:], hash) 23 return res 24 } 25 26 func shortHashToArray(hash []byte) [ShortHashSize]byte { 27 var res [ShortHashSize]byte 28 copy(res[:], hash) 29 return res 30 } 31 32 func (ps *Periscope) hashPartial(path string, key []byte) ([]byte, error) { 33 buf := make([]byte, initialChunkSize) 34 h, err := blake2b.New256(key) 35 if err != nil { 36 return nil, err 37 } 38 f, err := ps.fs.Open(path) 39 if err != nil { 40 return nil, err 41 } 42 defer f.Close() 43 44 n, err := f.Read(buf) 45 if err != nil && err != io.EOF { 46 return nil, err 47 } 48 h.Write(buf[:n]) 49 return h.Sum(nil)[:ShortHashSize], nil 50 } 51 52 // a simpler hashFile that hashes the full file 53 // purposefully avoiding code reuse with the above 54 func (ps *Periscope) hashFile(path string) ([]byte, error) { 55 f, err := ps.fs.Open(path) 56 if err != nil { 57 return nil, err 58 } 59 defer f.Close() 60 h, err := blake2b.New256(nil) 61 if err != nil { 62 return nil, err 63 } 64 buf := make([]byte, readChunkSize) 65 if _, err := io.CopyBuffer(h, f, buf); err != nil { 66 return nil, err 67 } 68 return h.Sum(nil), nil 69 } 70 71 func relPath(absDirectory, absPath string) string { 72 relPath, err := filepath.Rel(absDirectory, absPath) 73 if err != nil || len(relPath) > len(absPath) { 74 return absPath 75 } 76 return relPath 77 } 78 79 func relFrom(relDirectory, absPath string) string { 80 absDirectory, err := filepath.Abs(relDirectory) 81 if err != nil { 82 return absPath 83 } 84 relPath, err := filepath.Rel(absDirectory, absPath) 85 if err != nil { 86 return absPath 87 } 88 path := filepath.Join(relDirectory, relPath) 89 if len(path) <= len(absPath) { 90 return path 91 } 92 return absPath 93 } 94 95 func (ps *Periscope) checkSymlinks(cleanAbsPath string) (hasSymlinks bool, realAbsPath string, err error) { 96 if !ps.realFs { 97 // filepath.EvalSymlinks() won't be a sensible thing to do when 98 // testing with afero's in-memory filesystem 99 return false, cleanAbsPath, nil 100 } 101 resolved, err := filepath.EvalSymlinks(cleanAbsPath) 102 if err != nil { 103 return false, "", err 104 } 105 return (resolved != cleanAbsPath), resolved, nil 106 } 107 108 func (ps *Periscope) checkFile(path string, mustBeRegularFile, mustBeDirectory bool, action string, quiet, fatal bool) (realAbsPath string, info os.FileInfo, herr herror.Interface) { 109 checkFileError := func(format string, a ...interface{}) herror.Interface { 110 if !fatal { 111 if !quiet { 112 fmt.Fprintf(ps.errStream, format, a...) 113 fmt.Fprintf(ps.errStream, "\n") 114 } 115 return herror.Silent() 116 } 117 return herror.UserF(nil, format, a...) 118 } 119 // check that it exists 120 info, err := ps.fs.Stat(path) 121 if err != nil { 122 if os.IsNotExist(err) { 123 return "", nil, checkFileError("cannot %s '%s': no such file or directory", action, path) 124 } 125 if os.IsPermission(err) { 126 return "", nil, checkFileError("cannot %s '%s': permission denied", action, path) 127 } 128 // what else can go wrong here? one example is too many levels of symbolic links 129 return "", nil, checkFileError("cannot %s '%s': %s", action, path, err.Error()) 130 } 131 // get an absolute path 132 absPath, err := filepath.Abs(path) 133 if err != nil { 134 // when can this happen? 135 return "", nil, herror.Internal(err, "") 136 } 137 // check whether there are any symbolic links involved 138 hasLinks, resolved, err := ps.checkSymlinks(absPath) 139 if err != nil { 140 // when can this happen? we've already checked that the file or 141 // directory exists 142 return "", nil, herror.Internal(err, "") 143 } 144 if hasLinks { 145 return "", nil, checkFileError("cannot %s '%s': path has symbolic links (use '%s' instead)", action, path, resolved) 146 } 147 // at this point, absPath == resolved 148 // check whether it's a regular file or directory 149 if !info.Mode().IsRegular() && !info.Mode().IsDir() { 150 return "", nil, checkFileError("cannot %s '%s': not a regular file or directory", action, path) 151 } 152 // check whether it's a regular file, if required 153 if mustBeRegularFile && !info.Mode().IsRegular() { 154 return "", nil, checkFileError("cannot %s '%s': not a regular file", action, path) 155 } 156 // check whether it's a directory, if required 157 if mustBeDirectory && !info.Mode().IsDir() { 158 return "", nil, checkFileError("cannot %s '%s': not a directory", action, path) 159 } 160 // all okay 161 return absPath, info, nil 162 } 163 164 func containedInAny(path string, dirs []string) bool { 165 for _, dir := range dirs { 166 if dir[len(dir)-1] != os.PathSeparator { 167 dir = dir + string(os.PathSeparator) 168 } 169 if strings.HasPrefix(path, dir) { 170 return true 171 } 172 } 173 return false 174 }