diff --git a/internal/buffer/backup.go b/internal/buffer/backup.go index 8d6997f0..dfbc698c 100644 --- a/internal/buffer/backup.go +++ b/internal/buffer/backup.go @@ -79,7 +79,7 @@ func (b *Buffer) Backup() error { os.Mkdir(backupdir, os.ModePerm) } - name := filepath.Join(backupdir, util.EscapePath(b.AbsPath)) + name := util.DetermineEscapePath(backupdir, b.AbsPath) err = overwriteFile(name, encoding.Nop, func(file io.Writer) (e error) { b.Lock() @@ -123,7 +123,7 @@ func (b *Buffer) RemoveBackup() { if !b.Settings["backup"].(bool) || b.Settings["permbackup"].(bool) || b.Path == "" || b.Type != BTDefault { return } - f := filepath.Join(config.ConfigDir, "backups", util.EscapePath(b.AbsPath)) + f := util.DetermineEscapePath(filepath.Join(config.ConfigDir, "backups"), b.AbsPath) os.Remove(f) } @@ -131,13 +131,13 @@ func (b *Buffer) RemoveBackup() { // Returns true if a backup was applied func (b *Buffer) ApplyBackup(fsize int64) (bool, bool) { if b.Settings["backup"].(bool) && !b.Settings["permbackup"].(bool) && len(b.Path) > 0 && b.Type == BTDefault { - backupfile := filepath.Join(config.ConfigDir, "backups", util.EscapePath(b.AbsPath)) + backupfile := util.DetermineEscapePath(filepath.Join(config.ConfigDir, "backups"), b.AbsPath) if info, err := os.Stat(backupfile); err == nil { backup, err := os.Open(backupfile) if err == nil { defer backup.Close() t := info.ModTime() - msg := fmt.Sprintf(backupMsg, t.Format("Mon Jan _2 at 15:04, 2006"), util.EscapePath(b.AbsPath)) + msg := fmt.Sprintf(backupMsg, t.Format("Mon Jan _2 at 15:04, 2006"), backupfile) choice := screen.TermPrompt(msg, []string{"r", "i", "a", "recover", "ignore", "abort"}, true) if choice%3 == 0 { diff --git a/internal/buffer/serialize.go b/internal/buffer/serialize.go index e72311da..06906f76 100644 --- a/internal/buffer/serialize.go +++ b/internal/buffer/serialize.go @@ -31,7 +31,7 @@ func (b *Buffer) Serialize() error { return nil } - name := filepath.Join(config.ConfigDir, "buffers", util.EscapePath(b.AbsPath)) + name := util.DetermineEscapePath(filepath.Join(config.ConfigDir, "buffers"), b.AbsPath) return overwriteFile(name, encoding.Nop, func(file io.Writer) error { err := gob.NewEncoder(file).Encode(SerializedBuffer{ @@ -50,7 +50,7 @@ func (b *Buffer) Unserialize() error { if b.Path == "" { return nil } - file, err := os.Open(filepath.Join(config.ConfigDir, "buffers", util.EscapePath(b.AbsPath))) + file, err := os.Open(util.DetermineEscapePath(filepath.Join(config.ConfigDir, "buffers"), b.AbsPath)) if err == nil { defer file.Close() var buffer SerializedBuffer diff --git a/internal/util/util.go b/internal/util/util.go index bcfeca07..6f8f6325 100644 --- a/internal/util/util.go +++ b/internal/util/util.go @@ -7,6 +7,7 @@ import ( "fmt" "io" "net/http" + "net/url" "os" "os/user" "path/filepath" @@ -408,8 +409,13 @@ func GetModTime(path string) (time.Time, error) { return info.ModTime(), nil } -// EscapePath replaces every path separator in a given path with a % -func EscapePath(path string) string { +// EscapePathUrl encodes the path in URL query form +func EscapePathUrl(path string) string { + return url.QueryEscape(filepath.ToSlash(path)) +} + +// EscapePathLegacy replaces every path separator in a given path with a % +func EscapePathLegacy(path string) string { path = filepath.ToSlash(path) if runtime.GOOS == "windows" { // ':' is not valid in a path name on Windows but is ok on Unix @@ -418,6 +424,24 @@ func EscapePath(path string) string { return strings.ReplaceAll(path, "/", "%") } +// DetermineEscapePath escapes a path, determining whether it should be escaped +// using URL encoding (preferred, since it encodes unambiguously) or +// legacy encoding with '%' (for backward compatibility, if the legacy-escaped +// path exists in the given directory). +func DetermineEscapePath(dir string, path string) string { + url := filepath.Join(dir, EscapePathUrl(path)) + if _, err := os.Stat(url); err == nil { + return url + } + + legacy := filepath.Join(dir, EscapePathLegacy(path)) + if _, err := os.Stat(legacy); err == nil { + return legacy + } + + return url +} + // GetLeadingWhitespace returns the leading whitespace of the given byte array func GetLeadingWhitespace(b []byte) []byte { ws := []byte{}