浏览代码

lib/fs: Be more clear about invalid file names (ref #7010) (#7011)

Add specific errors for the failures, resulting in this rather than just
the generic "invalid filename":

[MRIW7] 08:50:50 INFO: Puller (folder default, item "NUL"): syncing: filename is invalid: name is reserved
[MRIW7] 08:50:50 INFO: Puller (folder default, item "fail."): syncing: filename is invalid: name ends with space or period
[MRIW7] 08:50:50 INFO: Puller (folder default, item "sup:yo"): syncing: filename is invalid: name contains reserved character
[MRIW7] 08:50:50 INFO: default: Failed to sync 3 items
Jakob Borg 5 年之前
父节点
当前提交
9e0b924d57
共有 4 个文件被更改,包括 22 次插入17 次删除
  1. 6 3
      lib/fs/basicfs.go
  2. 3 3
      lib/fs/filesystem.go
  3. 9 8
      lib/fs/util.go
  4. 4 3
      lib/model/folder_sendrecv.go

+ 6 - 3
lib/fs/basicfs.go

@@ -19,8 +19,11 @@ import (
 )
 )
 
 
 var (
 var (
-	ErrInvalidFilename = errors.New("filename is invalid")
-	ErrNotRelative     = errors.New("not a relative path")
+	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.)")
+	errNotRelative                        = errors.New("not a relative path")
 )
 )
 
 
 func WithJunctionsAsDirs() Option {
 func WithJunctionsAsDirs() Option {
@@ -95,7 +98,7 @@ func (f *BasicFilesystem) rooted(rel string) (string, error) {
 func rooted(rel, root string) (string, error) {
 func rooted(rel, root string) (string, error) {
 	// The root must not be empty.
 	// The root must not be empty.
 	if root == "" {
 	if root == "" {
-		return "", ErrInvalidFilename
+		return "", errInvalidFilenameEmpty
 	}
 	}
 
 
 	var err error
 	var err error

+ 3 - 3
lib/fs/filesystem.go

@@ -234,7 +234,7 @@ func Canonicalize(file string) (string, error) {
 		// The relative path may pretend to be an absolute path within
 		// The relative path may pretend to be an absolute path within
 		// the root, but the double path separator on Windows implies
 		// the root, but the double path separator on Windows implies
 		// something else and is out of spec.
 		// something else and is out of spec.
-		return "", ErrNotRelative
+		return "", errNotRelative
 	}
 	}
 
 
 	// The relative path should be clean from internal dotdots and similar
 	// The relative path should be clean from internal dotdots and similar
@@ -244,10 +244,10 @@ func Canonicalize(file string) (string, error) {
 	// It is not acceptable to attempt to traverse upwards.
 	// It is not acceptable to attempt to traverse upwards.
 	switch file {
 	switch file {
 	case "..":
 	case "..":
-		return "", ErrNotRelative
+		return "", errNotRelative
 	}
 	}
 	if strings.HasPrefix(file, ".."+pathSep) {
 	if strings.HasPrefix(file, ".."+pathSep) {
-		return "", ErrNotRelative
+		return "", errNotRelative
 	}
 	}
 
 
 	if strings.HasPrefix(file, pathSep) {
 	if strings.HasPrefix(file, pathSep) {

+ 9 - 8
lib/fs/util.go

@@ -7,7 +7,6 @@
 package fs
 package fs
 
 
 import (
 import (
-	"errors"
 	"fmt"
 	"fmt"
 	"os"
 	"os"
 	"path/filepath"
 	"path/filepath"
@@ -15,8 +14,6 @@ import (
 	"strings"
 	"strings"
 )
 )
 
 
-var errNoHome = errors.New("no home directory found - set $HOME (or the platform equivalent)")
-
 func ExpandTilde(path string) (string, error) {
 func ExpandTilde(path string) (string, error) {
 	if path == "~" {
 	if path == "~" {
 		return getHomeDir()
 		return getHomeDir()
@@ -55,7 +52,7 @@ var windowsDisallowedCharacters = string([]rune{
 	31,
 	31,
 })
 })
 
 
-func WindowsInvalidFilename(name string) bool {
+func WindowsInvalidFilename(name string) error {
 	// None of the path components should end in space or period, or be a
 	// None of the path components should end in space or period, or be a
 	// reserved name.
 	// reserved name.
 	// (https://docs.microsoft.com/en-us/windows/win32/fileio/naming-a-file)
 	// (https://docs.microsoft.com/en-us/windows/win32/fileio/naming-a-file)
@@ -66,19 +63,23 @@ func WindowsInvalidFilename(name string) bool {
 		switch part[len(part)-1] {
 		switch part[len(part)-1] {
 		case ' ', '.':
 		case ' ', '.':
 			// Names ending in space or period are not valid.
 			// Names ending in space or period are not valid.
-			return true
+			return errInvalidFilenameWindowsSpacePeriod
 		}
 		}
-		switch part {
+		switch strings.ToUpper(part) {
 		case "CON", "PRN", "AUX", "NUL",
 		case "CON", "PRN", "AUX", "NUL",
 			"COM1", "COM2", "COM3", "COM4", "COM5", "COM6", "COM7", "COM8", "COM9",
 			"COM1", "COM2", "COM3", "COM4", "COM5", "COM6", "COM7", "COM8", "COM9",
 			"LPT1", "LPT2", "LPT3", "LPT4", "LPT5", "LPT6", "LPT7", "LPT8", "LPT9":
 			"LPT1", "LPT2", "LPT3", "LPT4", "LPT5", "LPT6", "LPT7", "LPT8", "LPT9":
 			// These reserved names are not valid.
 			// These reserved names are not valid.
-			return true
+			return errInvalidFilenameWindowsReservedName
 		}
 		}
 	}
 	}
 
 
 	// The path must not contain any disallowed characters
 	// The path must not contain any disallowed characters
-	return strings.ContainsAny(name, windowsDisallowedCharacters)
+	if strings.ContainsAny(name, windowsDisallowedCharacters) {
+		return errInvalidFilenameWindowsReservedChar
+	}
+
+	return nil
 }
 }
 
 
 // IsParent compares paths purely lexicographically, meaning it returns false
 // IsParent compares paths purely lexicographically, meaning it returns false

+ 4 - 3
lib/model/folder_sendrecv.go

@@ -337,7 +337,7 @@ func (f *sendReceiveFolder) processNeeded(snap *db.Snapshot, dbUpdateChan chan<-
 			l.Debugln(f, "Handling ignored file", file)
 			l.Debugln(f, "Handling ignored file", file)
 			dbUpdateChan <- dbUpdateJob{file, dbUpdateInvalidate}
 			dbUpdateChan <- dbUpdateJob{file, dbUpdateInvalidate}
 
 
-		case runtime.GOOS == "windows" && fs.WindowsInvalidFilename(file.Name):
+		case runtime.GOOS == "windows" && fs.WindowsInvalidFilename(file.Name) != nil:
 			if file.IsDeleted() {
 			if file.IsDeleted() {
 				// Just pretend we deleted it, no reason to create an error
 				// Just pretend we deleted it, no reason to create an error
 				// about a deleted file that we can't have anyway.
 				// about a deleted file that we can't have anyway.
@@ -345,8 +345,9 @@ func (f *sendReceiveFolder) processNeeded(snap *db.Snapshot, dbUpdateChan chan<-
 				// ignored at some point.
 				// ignored at some point.
 				dbUpdateChan <- dbUpdateJob{file, dbUpdateDeleteFile}
 				dbUpdateChan <- dbUpdateJob{file, dbUpdateDeleteFile}
 			} else {
 			} else {
-				// We can't pull an invalid file.
-				f.newPullError(file.Name, fs.ErrInvalidFilename)
+				// We can't pull an invalid file. Grab the error again since
+				// we couldn't assign it directly in the case clause.
+				f.newPullError(file.Name, fs.WindowsInvalidFilename(file.Name))
 				// No reason to retry for this
 				// No reason to retry for this
 				changed--
 				changed--
 			}
 			}