diff --git a/backend/local/local.go b/backend/local/local.go index 80f44c76e..e292e689a 100644 --- a/backend/local/local.go +++ b/backend/local/local.go @@ -1568,32 +1568,47 @@ func (o *Object) SetMetadata(ctx context.Context, metadata fs.Metadata) error { } func cleanRootPath(s string, noUNC bool, enc encoder.MultiEncoder) string { - if runtime.GOOS != "windows" || !strings.HasPrefix(s, "\\") { - if !filepath.IsAbs(s) { - s2, err := filepath.Abs(s) - if err == nil { - s = s2 - } - } else { - s = filepath.Clean(s) - } - } + var vol string if runtime.GOOS == "windows" { - s = filepath.ToSlash(s) - vol := filepath.VolumeName(s) + vol = filepath.VolumeName(s) if vol == `\\?` && len(s) >= 6 { // `\\?\C:` vol = s[:6] } - s = vol + enc.FromStandardPath(s[len(vol):]) - s = filepath.FromSlash(s) - if !noUNC { - // Convert to UNC - s = file.UNCPath(s) - } - return s + s = s[len(vol):] + } + // Don't use FromStandardPath. Make sure Dot (`.`, `..`) as name will not be reencoded + // Take care of the case Standard: ././‛. (the first dot means current directory) + if enc != encoder.Standard { + s = filepath.ToSlash(s) + parts := strings.Split(s, "/") + encoded := make([]string, len(parts)) + changed := false + for i, p := range parts { + if (p == ".") || (p == "..") { + encoded[i] = p + continue + } + part := enc.FromStandardName(p) + changed = changed || part != p + encoded[i] = part + } + if changed { + s = strings.Join(encoded, "/") + } + s = filepath.FromSlash(s) + } + if runtime.GOOS == "windows" { + s = vol + s + } + s2, err := filepath.Abs(s) + if err == nil { + s = s2 + } + if !noUNC { + // Convert to UNC. It does nothing on non windows platforms. + s = file.UNCPath(s) } - s = enc.FromStandardPath(s) return s }