1package fsutil 2 3import ( 4 "bufio" 5 "fmt" 6 "io" 7 "math/rand" 8 "os" 9 "os/user" 10 "path/filepath" 11 "regexp" 12 "strings" 13 "time" 14 15 "github.com/gopasspw/gopass/pkg/debug" 16) 17 18var reCleanFilename = regexp.MustCompile(`[^\w\d@.-]`) 19 20// CleanFilename strips all possibly suspicious characters from a filename 21// WARNING: NOT suiteable for pathnames as slashes will be stripped as well! 22func CleanFilename(in string) string { 23 return strings.Trim(reCleanFilename.ReplaceAllString(in, "_"), "_ ") 24} 25 26// CleanPath resolves common aliases in a path and cleans it as much as possible 27func CleanPath(path string) string { 28 // http://stackoverflow.com/questions/17609732/expand-tilde-to-home-directory 29 // TODO: We should consider if we really want to rewrite ~ 30 if len(path) > 1 && path[:2] == "~/" { 31 usr, _ := user.Current() 32 dir := usr.HomeDir 33 if hd := os.Getenv("GOPASS_HOMEDIR"); hd != "" { 34 dir = hd 35 } 36 path = strings.Replace(path, "~/", dir+"/", 1) 37 } 38 if p, err := filepath.Abs(path); err == nil { 39 return p 40 } 41 return filepath.Clean(path) 42} 43 44// IsDir checks if a certain path exists and is a directory 45// https://stackoverflow.com/questions/10510691/how-to-check-whether-a-file-or-directory-denoted-by-a-path-exists-in-golang 46func IsDir(path string) bool { 47 fi, err := os.Stat(path) 48 if err != nil { 49 if os.IsNotExist(err) { 50 // not found 51 return false 52 } 53 debug.Log("failed to check dir %s: %s\n", path, err) 54 return false 55 } 56 57 return fi.IsDir() 58} 59 60// IsFile checks if a certain path is actually a file 61func IsFile(path string) bool { 62 fi, err := os.Stat(path) 63 if err != nil { 64 if os.IsNotExist(err) { 65 // not found 66 return false 67 } 68 debug.Log("failed to check file %s: %s\n", path, err) 69 return false 70 } 71 72 return fi.Mode().IsRegular() 73} 74 75// IsEmptyDir checks if a certain path is an empty directory 76func IsEmptyDir(path string) (bool, error) { 77 empty := true 78 if err := filepath.Walk(path, func(fp string, fi os.FileInfo, ferr error) error { 79 if ferr != nil { 80 return ferr 81 } 82 if fi.IsDir() && (fi.Name() == "." || fi.Name() == "..") { 83 return filepath.SkipDir 84 } 85 if !fi.IsDir() { 86 empty = false 87 } 88 return nil 89 }); err != nil { 90 return false, err 91 } 92 return empty, nil 93} 94 95// Shred overwrite the given file any number of times 96func Shred(path string, runs int) error { 97 rand.Seed(time.Now().UnixNano()) 98 fh, err := os.OpenFile(path, os.O_WRONLY, 0600) 99 if err != nil { 100 return fmt.Errorf("failed to open file %q: %w", path, err) 101 } 102 buf := make([]byte, 1024) 103 for i := 0; i < runs; i++ { 104 // overwrite using pseudo-random data n-1 times and 105 // use zeros in the last iteration 106 if i < runs-1 { 107 _, _ = rand.Read(buf) 108 } else { 109 buf = make([]byte, 1024) 110 } 111 if _, err := fh.Seek(0, 0); err != nil { 112 return fmt.Errorf("failed to seek to 0,0: %w", err) 113 } 114 if _, err := fh.Write(buf); err != nil { 115 if err != io.EOF { 116 return fmt.Errorf("failed to write to file: %w", err) 117 } 118 } 119 // if we fail to sync the written blocks to disk it'd be pointless 120 // do any further loops 121 if err := fh.Sync(); err != nil { 122 return fmt.Errorf("failed to sync to disk: %w", err) 123 } 124 } 125 if err := fh.Close(); err != nil { 126 return fmt.Errorf("failed to close file after writing: %w", err) 127 } 128 129 return os.Remove(path) 130} 131 132// FileContains searches the given file for the search string and returns true 133// iff it's an exact (substring) match. 134func FileContains(path, needle string) bool { 135 fh, err := os.Open(path) 136 if err != nil { 137 debug.Log("failed to open %q for reading: %s", path, err) 138 return false 139 } 140 defer fh.Close() 141 142 s := bufio.NewScanner(fh) 143 for s.Scan() { 144 if strings.Contains(s.Text(), needle) { 145 return true 146 } 147 } 148 return false 149} 150