Repository: zyedidia/micro
File: internal/util/util.go (Go)

Code before:
	if runtime.GOOS == "windows" {
		// ':' is not valid in a path name on Windows but is ok on Unix
		path = strings.ReplaceAll(path, ":", "%")
	}
	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).
// In case the length of the escaped path (plus the backup extension) exceeds
// the filename length limit, a hash of the path is returned instead.
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
	}

	if len(url)+len(".micro-backup") > 255 {
		return HashStringMd5(path)
	}

	return url
}

// GetLeadingWhitespace returns the leading whitespace of the given byte array
func GetLeadingWhitespace(b []byte) []byte {
	ws := []byte{}
	for len(b) > 0 {
		r, _, size := DecodeCharacter(b)
		if r == ' ' || r == '\t' {
			ws = append(ws, byte(r))
		} else {
			break
		}

		b = b[size:]
	}
	return ws
}

// GetTrailingWhitespace returns the trailing whitespace of the given byte array
func GetTrailingWhitespace(b []byte) []byte {
	ws := []byte{}
	for len(b) > 0 {

Review comment (security):
Did you test it? It creates the backup in the current directory, not in `~/.config/micro/backups/` (only `{hash}.path` is created in `~/.config/micro/backups/`).

The result is not just bad (the file is not recovered from the backup afterwards) but worse: the serialized buffer file is created in the current directory as well, not in `~/.config/micro/buffers/`, and it is created with the same name (i.e. the hash), i.e. it is just the same file. As as result, when opening the file next time:

```
gob: duplicate type received
You may want to remove the files in ~/.config/micro/buffers (these files
store the information for the 'saveundo' and 'savecursor' options) if
this problem persists.
This may be caused by upgrading to version 2.0, and removing the 'buffers'
directory will reset the cursor and undo history and solve the problem.

Press enter to continue
```

Fix:

```go
diff --git a/internal/util/util.go b/internal/util/util.go
index de4f66d4..e0ae62f2 100644
--- a/internal/util/util.go
+++ b/internal/util/util.go
@@ -490,7 +490,7 @@ func DetermineEscapePath(dir string, path string) (string, string) {
 
 	if len(url)+len(BackupSuffix) > 255 {
 		hash := HashStringMd5(path)
-		return hash, filepath.Join(dir, hash+".path")
+		return filepath.Join(dir, hash), filepath.Join(dir, hash+".path")
 	}
 
 	return url, ""
```

Code after:
		path = strings.ReplaceAll(path, ":", "%")
	}
	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).
// In case the length of the escaped path (plus the backup extension) exceeds
// the filename length limit, a hash of the path is returned instead. In such
// case the second return value is the name of a file the original path should
// be saved to (since the original path cannot be derived from its hash).
// Otherwise the second return value is an empty string.
func DetermineEscapePath(dir string, path string) (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, ""
	}

	if len(url)+len(BackupSuffix) > 255 {
		hash := HashStringMd5(path)
		return filepath.Join(dir, hash), filepath.Join(dir, hash+".path")
	}

	return url, ""
}

// GetLeadingWhitespace returns the leading whitespace of the given byte array
func GetLeadingWhitespace(b []byte) []byte {
	ws := []byte{}
	for len(b) > 0 {
		r, _, size := DecodeCharacter(b)
		if r == ' ' || r == '\t' {
			ws = append(ws, byte(r))
		} else {
			break
		}

		b = b[size:]
	}
	return ws
}

// GetTrailingWhitespace returns the trailing whitespace of the given byte array
func GetTrailingWhitespace(b []byte) []byte {