diff --git a/lib/fs/util.go b/lib/fs/util.go index 348124dcf..2ffd6cc22 100644 --- a/lib/fs/util.go +++ b/lib/fs/util.go @@ -47,25 +47,13 @@ func getHomeDir() (string, error) { return os.UserHomeDir() } -var ( - windowsDisallowedCharacters = string([]rune{ - '<', '>', ':', '"', '|', '?', '*', - 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, - 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, - 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, - 31, - }) - windowsDisallowedNames = []string{"CON", "PRN", "AUX", "NUL", - "COM0", "COM1", "COM2", "COM3", "COM4", "COM5", "COM6", "COM7", "COM8", "COM9", - "LPT0", "LPT1", "LPT2", "LPT3", "LPT4", "LPT5", "LPT6", "LPT7", "LPT8", "LPT9", - } -) +const windowsDisallowedCharacters = (`<>:"|?*` + + "\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f" + + "\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f") func WindowsInvalidFilename(name string) error { // None of the path components should end in space or period, or be a - // reserved name. COM0 and LPT0 are missing from the Microsoft docs, - // but Windows Explorer treats them as invalid too. - // (https://docs.microsoft.com/windows/win32/fileio/naming-a-file) + // reserved name. for _, part := range strings.Split(name, `\`) { if len(part) == 0 { continue @@ -110,7 +98,7 @@ func WindowsInvalidFilename(name string) error { func SanitizePath(path string) string { var b strings.Builder - disallowed := `<>:"'/\|?*[]{};:!@$%&^#` + windowsDisallowedCharacters + const disallowed = `'/\[]{};:!@$%&^#` + windowsDisallowedCharacters prev := ' ' for _, c := range path { if !unicode.IsPrint(c) || c == unicode.ReplacementChar || @@ -132,15 +120,27 @@ func SanitizePath(path string) string { } func windowsIsReserved(part string) bool { - upperCased := strings.ToUpper(part) - for _, disallowed := range windowsDisallowedNames { - if upperCased == disallowed { - return true - } - if strings.HasPrefix(upperCased, disallowed+".") { - // nul.txt.jpg is also disallowed - return true - } + // nul.txt.jpg is also disallowed. + dot := strings.IndexByte(part, '.') + if dot != -1 { + part = part[:dot] + } + + // Check length to skip allocating ToUpper. + if len(part) != 3 && len(part) != 4 { + return false + } + + // COM0 and LPT0 are missing from the Microsoft docs, + // but Windows Explorer treats them as invalid too. + // (https://docs.microsoft.com/windows/win32/fileio/naming-a-file) + switch strings.ToUpper(part) { + case "CON", "PRN", "AUX", "NUL", + "COM0", "COM1", "COM2", "COM3", "COM4", + "COM5", "COM6", "COM7", "COM8", "COM9", + "LPT0", "LPT1", "LPT2", "LPT3", "LPT4", + "LPT5", "LPT6", "LPT7", "LPT8", "LPT9": + return true } return false } diff --git a/lib/fs/util_test.go b/lib/fs/util_test.go index 0b934308a..a263b5ef9 100644 --- a/lib/fs/util_test.go +++ b/lib/fs/util_test.go @@ -117,3 +117,15 @@ func TestSanitizePathFuzz(t *testing.T) { } } } + +func benchmarkWindowsInvalidFilename(b *testing.B, name string) { + for i := 0; i < b.N; i++ { + WindowsInvalidFilename(name) + } +} +func BenchmarkWindowsInvalidFilenameValid(b *testing.B) { + benchmarkWindowsInvalidFilename(b, "License.txt.gz") +} +func BenchmarkWindowsInvalidFilenameNUL(b *testing.B) { + benchmarkWindowsInvalidFilename(b, "nul.txt.gz") +}