diff --git a/backend/sharefile/replace.go b/backend/sharefile/replace.go deleted file mode 100644 index 3f8f4b8eb..000000000 --- a/backend/sharefile/replace.go +++ /dev/null @@ -1,76 +0,0 @@ -/* -Translate file names for sharefile -*/ - -package sharefile - -import ( - "regexp" - "strings" -) - -// charMap holds replacements for characters -// -// Sharefile has a restricted set of characters compared to other -// cloud storage systems, so we to map these to the FULLWIDTH unicode -// equivalents -// -// http://unicode-search.net/unicode-namesearch.pl?term=SOLIDUS -var ( - charMap = map[rune]rune{ - '\\': '\', // FULLWIDTH REVERSE SOLIDUS - '*': '*', // FULLWIDTH ASTERISK - '<': '<', // FULLWIDTH LESS-THAN SIGN - '>': '>', // FULLWIDTH GREATER-THAN SIGN - '?': '?', // FULLWIDTH QUESTION MARK - ':': ':', // FULLWIDTH COLON - '|': '|', // FULLWIDTH VERTICAL LINE - '"': '"', // FULLWIDTH QUOTATION MARK - '.': '.', // FULLWIDTH FULL STOP - ' ': '␠', // SYMBOL FOR SPACE - } - invCharMap map[rune]rune - fixStartingWithPeriod = regexp.MustCompile(`(/|^)\.`) - fixEndingWithPeriod = regexp.MustCompile(`\.(/|$)`) - fixStartingWithSpace = regexp.MustCompile(`(/|^) `) - fixEndingWithSpace = regexp.MustCompile(` (/|$)`) -) - -func init() { - // Create inverse charMap - invCharMap = make(map[rune]rune, len(charMap)) - for k, v := range charMap { - invCharMap[v] = k - } -} - -// replaceReservedChars takes a path and substitutes any reserved -// characters in it -func replaceReservedChars(in string) string { - // Names can't start with a period '.' - in = fixStartingWithPeriod.ReplaceAllString(in, "$1"+string(charMap['.'])) - // Names can't end with a period '.' - in = fixEndingWithPeriod.ReplaceAllString(in, string(charMap['.'])+"$1") - // Names can't start with space - in = fixStartingWithSpace.ReplaceAllString(in, "$1"+string(charMap[' '])) - // Names can't end with space - in = fixEndingWithSpace.ReplaceAllString(in, string(charMap[' '])+"$1") - // Replace reserved characters - return strings.Map(func(c rune) rune { - if replacement, ok := charMap[c]; ok && c != '.' && c != '~' && c != ' ' { - return replacement - } - return c - }, in) -} - -// restoreReservedChars takes a path and undoes any substitutions -// made by replaceReservedChars -func restoreReservedChars(in string) string { - return strings.Map(func(c rune) rune { - if replacement, ok := invCharMap[c]; ok { - return replacement - } - return c - }, in) -} diff --git a/backend/sharefile/replace_test.go b/backend/sharefile/replace_test.go deleted file mode 100644 index 9450d69a9..000000000 --- a/backend/sharefile/replace_test.go +++ /dev/null @@ -1,31 +0,0 @@ -package sharefile - -import "testing" - -func TestReplace(t *testing.T) { - for _, test := range []struct { - in string - out string - }{ - {"", ""}, - {"abc 123", "abc 123"}, - {`\*<>?:|#%".~`, `\*<>?:|#%".~`}, - {`\*<>?:|#%".~/\*<>?:|#%".~`, `\*<>?:|#%".~/\*<>?:|#%".~`}, - {" leading space", "␠leading space"}, - {"trailing space ", "trailing space␠"}, - {".leading dot", ".leading dot"}, - {"trailing dot.", "trailing dot."}, - {" leading space/ leading space/ leading space", "␠leading space/␠leading space/␠leading space"}, - {"trailing dot./trailing dot./trailing dot.", "trailing dot./trailing dot./trailing dot."}, - {".leading dot/..leading dot/.leading dot", ".leading dot/..leading dot/.leading dot"}, - } { - got := replaceReservedChars(test.in) - if got != test.out { - t.Errorf("replaceReservedChars(%q) want %q got %q", test.in, test.out, got) - } - got2 := restoreReservedChars(got) - if got2 != test.in { - t.Errorf("restoreReservedChars(%q) want %q got %q", got, test.in, got2) - } - } -} diff --git a/backend/sharefile/sharefile.go b/backend/sharefile/sharefile.go index 0b179eb00..09308146f 100644 --- a/backend/sharefile/sharefile.go +++ b/backend/sharefile/sharefile.go @@ -90,6 +90,7 @@ import ( "github.com/rclone/rclone/fs/config/configmap" "github.com/rclone/rclone/fs/config/configstruct" "github.com/rclone/rclone/fs/config/obscure" + "github.com/rclone/rclone/fs/encodings" "github.com/rclone/rclone/fs/fserrors" "github.com/rclone/rclone/fs/hash" "github.com/rclone/rclone/lib/dircache" @@ -100,6 +101,8 @@ import ( "golang.org/x/oauth2" ) +const enc = encodings.Sharefile + const ( rcloneClientID = "djQUPlHTUM9EvayYBWuKC5IrVIoQde46" rcloneEncryptedClientSecret = "v7572bKhUindQL3yDnUAebmgP-QxiwT38JLxVPolcZBl6SSs329MtFzH73x7BeELmMVZtneUPvALSopUZ6VkhQ" @@ -298,7 +301,7 @@ func (f *Fs) readMetaDataForIDPath(ctx context.Context, id, path string, directo } if path != "" { opts.Path += "/ByPath" - opts.Parameters.Set("path", "/"+replaceReservedChars(path)) + opts.Parameters.Set("path", "/"+enc.FromStandardPath(path)) } var item api.Item var resp *http.Response @@ -592,7 +595,7 @@ func (f *Fs) FindLeaf(ctx context.Context, pathID, leaf string) (pathIDOut strin // CreateDir makes a directory with pathID as parent and name leaf func (f *Fs) CreateDir(ctx context.Context, pathID, leaf string) (newID string, err error) { var resp *http.Response - leaf = replaceReservedChars(leaf) + leaf = enc.FromStandardName(leaf) var req = api.Item{ Name: leaf, FileName: leaf, @@ -661,7 +664,7 @@ func (f *Fs) listAll(ctx context.Context, dirID string, directoriesOnly bool, fi fs.Debugf(f, "Ignoring %q - unknown type %q", item.Name, item.Type) continue } - item.Name = restoreReservedChars(item.Name) + item.Name = enc.ToStandardName(item.Name) if fn(item) { found = true break @@ -870,7 +873,7 @@ func (f *Fs) updateItem(ctx context.Context, id, leaf, directoryID string, modTi "overwrite": {"false"}, }, } - leaf = replaceReservedChars(leaf) + leaf = enc.FromStandardName(leaf) // FIXME this appears to be a bug in the API // // If you set the modified time via PATCH then the server @@ -1116,7 +1119,7 @@ func (f *Fs) Copy(ctx context.Context, src fs.Object, remote string) (dst fs.Obj if err != nil { return nil, err } - srcLeaf = replaceReservedChars(srcLeaf) + srcLeaf = enc.FromStandardName(srcLeaf) _ = srcParentID // Create temporary object @@ -1124,7 +1127,7 @@ func (f *Fs) Copy(ctx context.Context, src fs.Object, remote string) (dst fs.Obj if err != nil { return nil, err } - dstLeaf = replaceReservedChars(dstLeaf) + dstLeaf = enc.FromStandardName(dstLeaf) sameName := strings.ToLower(srcLeaf) == strings.ToLower(dstLeaf) if sameName && srcParentID == dstParentID { @@ -1387,7 +1390,7 @@ func (o *Object) Update(ctx context.Context, in io.Reader, src fs.ObjectInfo, op if err != nil { return err } - leaf = replaceReservedChars(leaf) + leaf = enc.FromStandardName(leaf) var req = api.UploadRequest{ Method: "standard", Raw: true, diff --git a/fs/encodings/encodings.go b/fs/encodings/encodings.go index ac6211747..d00ac02a1 100644 --- a/fs/encodings/encodings.go +++ b/fs/encodings/encodings.go @@ -302,6 +302,18 @@ const QingStor = encoder.MultiEncoder( encoder.EncodeCtl | encoder.EncodeSlash) +// Sharefile is the encoding used by the sharefile backend +const Sharefile = encoder.MultiEncoder( + uint(Base) | + encoder.EncodeWin | // :?"*<>| + encoder.EncodeBackSlash | // \ + encoder.EncodeCtl | + encoder.EncodeRightSpace | + encoder.EncodeRightPeriod | + encoder.EncodeLeftSpace | + encoder.EncodeLeftPeriod | + encoder.EncodeInvalidUtf8) + // ByName returns the encoder for a give backend name or nil func ByName(name string) encoder.Encoder { switch strings.ToLower(name) { @@ -353,6 +365,8 @@ func ByName(name string) encoder.Encoder { return QingStor case "s3": return S3 + case "sharefile": + return Sharefile //case "sftp": case "swift": return Swift @@ -392,5 +406,6 @@ func Names() []string { "onedrive", "opendrive", "pcloud", + "sharefile", } }