aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJakob Borg <jakob@kastelo.net>2023-07-07 13:00:40 +0200
committerGitHub <noreply@github.com>2023-07-07 11:00:40 +0000
commitc44de2cd583cf36b4e4f0f984c1e524c14984138 (patch)
treea2dd3a487bc6ab0b824fe2ffdfb305f51f16669f
parent6ff5ed6d232b8e6093c199cd83de0025641447bc (diff)
downloadsyncthing-c44de2cd583cf36b4e4f0f984c1e524c14984138.tar.gz
syncthing-c44de2cd583cf36b4e4f0f984c1e524c14984138.zip
lib/fs: Clarify errors for Windows filenames (fixes #8968) (#8969)
With this change, error messages include the offending characters or name parts. Examples: nul.txt: name is invalid, contains Windows reserved name: "nul" foo>bar.txt: name is invalid, contains Windows reserved character: ">" foo \bar.txt: name is invalid, must not end in space or period on Windows
-rw-r--r--lib/fs/basicfs.go4
-rw-r--r--lib/fs/util.go18
-rw-r--r--lib/fs/util_test.go6
3 files changed, 16 insertions, 12 deletions
diff --git a/lib/fs/basicfs.go b/lib/fs/basicfs.go
index 51e598ed2..58afc0e27 100644
--- a/lib/fs/basicfs.go
+++ b/lib/fs/basicfs.go
@@ -22,8 +22,8 @@ import (
var (
errInvalidFilenameEmpty = errors.New("name is invalid, must not be empty")
errInvalidFilenameWindowsSpacePeriod = errors.New("name is invalid, must not end in space or period on Windows")
- errInvalidFilenameWindowsReservedName = errors.New("name is invalid, contains Windows reserved name (NUL, COM1, etc.)")
- errInvalidFilenameWindowsReservedChar = errors.New("name is invalid, contains Windows reserved character (?, *, etc.)")
+ errInvalidFilenameWindowsReservedName = errors.New("name is invalid, contains Windows reserved name")
+ errInvalidFilenameWindowsReservedChar = errors.New("name is invalid, contains Windows reserved character")
)
type OptionJunctionsAsDirs struct{}
diff --git a/lib/fs/util.go b/lib/fs/util.go
index 9e977cb37..41ee39300 100644
--- a/lib/fs/util.go
+++ b/lib/fs/util.go
@@ -54,8 +54,8 @@ const windowsDisallowedCharacters = (`<>:"|?*` +
func WindowsInvalidFilename(name string) error {
// The path must not contain any disallowed characters.
- if strings.ContainsAny(name, windowsDisallowedCharacters) {
- return errInvalidFilenameWindowsReservedChar
+ if idx := strings.IndexAny(name, windowsDisallowedCharacters); idx != -1 {
+ return fmt.Errorf("%w: %q", errInvalidFilenameWindowsReservedChar, name[idx:idx+1])
}
// None of the path components should end in space or period, or be a
@@ -72,8 +72,8 @@ func WindowsInvalidFilename(name string) error {
// Names ending in space or period are not valid.
return errInvalidFilenameWindowsSpacePeriod
}
- if windowsIsReserved(part) {
- return errInvalidFilenameWindowsReservedName
+ if reserved := windowsReservedNamePart(part); reserved != "" {
+ return fmt.Errorf("%w: %q", errInvalidFilenameWindowsReservedName, reserved)
}
}
@@ -117,13 +117,13 @@ func SanitizePath(path string) string {
}
path = strings.TrimSpace(b.String())
- if windowsIsReserved(path) {
+ if reserved := windowsReservedNamePart(path); reserved != "" {
path = "-" + path
}
return path
}
-func windowsIsReserved(part string) bool {
+func windowsReservedNamePart(part string) string {
// nul.txt.jpg is also disallowed.
dot := strings.IndexByte(part, '.')
if dot != -1 {
@@ -132,7 +132,7 @@ func windowsIsReserved(part string) bool {
// Check length to skip allocating ToUpper.
if len(part) != 3 && len(part) != 4 {
- return false
+ return ""
}
// COM0 and LPT0 are missing from the Microsoft docs,
@@ -144,9 +144,9 @@ func windowsIsReserved(part string) bool {
"COM5", "COM6", "COM7", "COM8", "COM9",
"LPT0", "LPT1", "LPT2", "LPT3", "LPT4",
"LPT5", "LPT6", "LPT7", "LPT8", "LPT9":
- return true
+ return part
}
- return false
+ return ""
}
// IsParent compares paths purely lexicographically, meaning it returns false
diff --git a/lib/fs/util_test.go b/lib/fs/util_test.go
index 1727044d2..0be64b0f4 100644
--- a/lib/fs/util_test.go
+++ b/lib/fs/util_test.go
@@ -7,6 +7,7 @@
package fs
import (
+ "errors"
"math/rand"
"testing"
"unicode"
@@ -69,9 +70,10 @@ func TestWindowsInvalidFilename(t *testing.T) {
for _, tc := range cases {
err := WindowsInvalidFilename(tc.name)
- if err != tc.err {
+ if !errors.Is(err, tc.err) {
t.Errorf("For %q, got %v, expected %v", tc.name, err, tc.err)
}
+ t.Logf("%s: %v", tc.name, err)
}
}
@@ -124,9 +126,11 @@ func benchmarkWindowsInvalidFilename(b *testing.B, name string) {
WindowsInvalidFilename(name)
}
}
+
func BenchmarkWindowsInvalidFilenameValid(b *testing.B) {
benchmarkWindowsInvalidFilename(b, "License.txt.gz")
}
+
func BenchmarkWindowsInvalidFilenameNUL(b *testing.B) {
benchmarkWindowsInvalidFilename(b, "nul.txt.gz")
}