diff --git a/backend/fichier/api.go b/backend/fichier/api.go index b111e37f7..6a5872d0f 100644 --- a/backend/fichier/api.go +++ b/backend/fichier/api.go @@ -107,6 +107,10 @@ func (f *Fs) listFiles(ctx context.Context, directoryID int) (filesList *FilesLi if err != nil { return nil, errors.Wrap(err, "couldn't list files") } + for i := range filesList.Items { + item := &filesList.Items[i] + item.Filename = enc.ToStandardName(item.Filename) + } return filesList, nil } @@ -131,6 +135,11 @@ func (f *Fs) listFolders(ctx context.Context, directoryID int) (foldersList *Fol if err != nil { return nil, errors.Wrap(err, "couldn't list folders") } + foldersList.Name = enc.ToStandardName(foldersList.Name) + for i := range foldersList.SubFolders { + folder := &foldersList.SubFolders[i] + folder.Name = enc.ToStandardName(folder.Name) + } // fs.Debugf(f, "Got FoldersList for id `%s`", directoryID) @@ -166,7 +175,6 @@ func (f *Fs) listDir(ctx context.Context, dir string) (entries fs.DirEntries, er entries = make([]fs.DirEntry, len(files.Items)+len(folders.SubFolders)) for i, item := range files.Items { - item.Filename = restoreReservedChars(item.Filename) entries[i] = f.newObjectFromFile(ctx, dir, item) } @@ -176,7 +184,6 @@ func (f *Fs) listDir(ctx context.Context, dir string) (entries fs.DirEntries, er return nil, err } - folder.Name = restoreReservedChars(folder.Name) fullPath := getRemote(dir, folder.Name) folderID := strconv.Itoa(folder.ID) @@ -206,7 +213,7 @@ func getRemote(dir, fileName string) string { } func (f *Fs) makeFolder(ctx context.Context, leaf string, folderID int) (response *MakeFolderResponse, err error) { - name := replaceReservedChars(leaf) + name := enc.FromStandardName(leaf) // fs.Debugf(f, "Creating folder `%s` in id `%s`", name, directoryID) request := MakeFolderRequest{ @@ -316,7 +323,7 @@ func (f *Fs) getUploadNode(ctx context.Context) (response *GetUploadNodeResponse func (f *Fs) uploadFile(ctx context.Context, in io.Reader, size int64, fileName, folderID, uploadID, node string) (response *http.Response, err error) { // fs.Debugf(f, "Uploading File `%s`", fileName) - fileName = replaceReservedChars(fileName) + fileName = enc.FromStandardName(fileName) if len(uploadID) > 10 || !isAlphaNumeric(uploadID) { return nil, errors.New("Invalid UploadID") diff --git a/backend/fichier/fichier.go b/backend/fichier/fichier.go index 2802d79d4..b07b63810 100644 --- a/backend/fichier/fichier.go +++ b/backend/fichier/fichier.go @@ -13,6 +13,7 @@ import ( "github.com/rclone/rclone/fs" "github.com/rclone/rclone/fs/config/configmap" "github.com/rclone/rclone/fs/config/configstruct" + "github.com/rclone/rclone/fs/encodings" "github.com/rclone/rclone/fs/fshttp" "github.com/rclone/rclone/fs/hash" "github.com/rclone/rclone/lib/dircache" @@ -28,6 +29,8 @@ const ( decayConstant = 2 // bigger for slower decay, exponential ) +const enc = encodings.Fichier + func init() { fs.Register(&fs.RegInfo{ Name: "fichier", @@ -142,7 +145,7 @@ func (f *Fs) Features() *fs.Features { // On Windows avoid single character remote names as they can be mixed // up with drive letters. func NewFs(name string, rootleaf string, config configmap.Mapper) (fs.Fs, error) { - root := replaceReservedChars(rootleaf) + root := enc.FromStandardPath(rootleaf) opt := new(Options) err := configstruct.Set(config, opt) if err != nil { diff --git a/backend/fichier/replace.go b/backend/fichier/replace.go deleted file mode 100644 index e5db38af8..000000000 --- a/backend/fichier/replace.go +++ /dev/null @@ -1,71 +0,0 @@ -/* -Translate file names for 1fichier - -1Fichier reserved characters - -The following characters are 1Fichier reserved characters, and can't -be used in 1Fichier folder and file names. - -*/ - -package fichier - -import ( - "regexp" - "strings" -) - -// charMap holds replacements for characters -// -// 1Fichier 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 LESS-THAN SIGN - '>': '>', // FULLWIDTH GREATER-THAN SIGN - '"': '"', // FULLWIDTH QUOTATION MARK - not on the list but seems to be reserved - '\'': ''', // FULLWIDTH APOSTROPHE - '$': '$', // FULLWIDTH DOLLAR SIGN - '`': '`', // FULLWIDTH GRAVE ACCENT - ' ': '␠', // SYMBOL FOR SPACE - } - invCharMap map[rune]rune - fixStartingWithSpace = 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 { - // file names can't start with space either - in = fixStartingWithSpace.ReplaceAllString(in, "$1"+string(charMap[' '])) - // Replace reserved characters - return strings.Map(func(c rune) rune { - if replacement, ok := charMap[c]; ok && 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/fichier/replace_test.go b/backend/fichier/replace_test.go deleted file mode 100644 index 40a5eebef..000000000 --- a/backend/fichier/replace_test.go +++ /dev/null @@ -1,24 +0,0 @@ -package fichier - -import "testing" - -func TestReplace(t *testing.T) { - for _, test := range []struct { - in string - out string - }{ - {"", ""}, - {"abc 123", "abc 123"}, - {"\"'<>/\\$`", `"'<>/\$``}, - {" leading space", "␠leading space"}, - } { - 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/docs/content/fichier.md b/docs/content/fichier.md index 786f74303..1229a6582 100644 --- a/docs/content/fichier.md +++ b/docs/content/fichier.md @@ -87,11 +87,31 @@ normal file system). Duplicated files cause problems with the syncing and you will see messages in the log about duplicates. -### Forbidden characters ### +#### Restricted filename characters -1Fichier does not support the characters ``\ < > " ' ` $`` and spaces at the beginning of folder names. -`rclone` automatically escapes these to a unicode equivalent. The exception is `/`, -which cannot be escaped and will therefore lead to errors. +In addition to the [default restricted characters set](/overview/#restricted-characters) +the following characters are also replaced: + +| Character | Value | Replacement | +| --------- |:-----:|:-----------:| +| \ | 0x5C | \ | +| < | 0x3C | < | +| > | 0x3E | > | +| " | 0x22 | " | +| $ | 0x24 | $ | +| ` | 0x60 | ` | +| ' | 0x27 | ' | + +File names can also not start or end with the following characters. +These only get replaced if they are first or last character in the +name: + +| Character | Value | Replacement | +| --------- |:-----:|:-----------:| +| SP | 0x20 | ␠ | + +Invalid UTF-8 bytes will also be [replaced](/overview/#invalid-utf8), +as they can't be used in JSON strings. ### Standard Options diff --git a/fs/encodings/encodings.go b/fs/encodings/encodings.go index 673c53f09..ac6211747 100644 --- a/fs/encodings/encodings.go +++ b/fs/encodings/encodings.go @@ -229,6 +229,31 @@ const Pcloud = encoder.MultiEncoder( encoder.EncodeBackSlash | encoder.EncodeInvalidUtf8) +// Fichier is the encoding used by the fichier backend +// +// Characters that need escaping +// +// '\\': '\', // FULLWIDTH REVERSE SOLIDUS +// '<': '<', // FULLWIDTH LESS-THAN SIGN +// '>': '>', // FULLWIDTH GREATER-THAN SIGN +// '"': '"', // FULLWIDTH QUOTATION MARK - not on the list but seems to be reserved +// '\'': ''', // FULLWIDTH APOSTROPHE +// '$': '$', // FULLWIDTH DOLLAR SIGN +// '`': '`', // FULLWIDTH GRAVE ACCENT +// +// Leading space and trailing space +const Fichier = encoder.MultiEncoder( + uint(Display) | + encoder.EncodeBackSlash | + encoder.EncodeSingleQuote | + encoder.EncodeBackQuote | + encoder.EncodeDoubleQuote | + encoder.EncodeLtGt | + encoder.EncodeDollar | + encoder.EncodeLeftSpace | + encoder.EncodeRightSpace | + encoder.EncodeInvalidUtf8) + // FTP is the encoding used by the ftp backend // // The FTP protocal can't handle trailing spaces (for instance @@ -298,6 +323,8 @@ func ByName(name string) encoder.Encoder { case "dropbox": return Dropbox //case "ftp": + case "ficher": + return Fichier case "googlecloudstorage": return GoogleCloudStorage //case "http": diff --git a/fs/encodings/encodings_noencode.go b/fs/encodings/encodings_noencode.go index aed762326..b283d8232 100644 --- a/fs/encodings/encodings_noencode.go +++ b/fs/encodings/encodings_noencode.go @@ -15,11 +15,13 @@ const ( LocalUnix = Base LocalWindows = Base AmazonCloudDrive = Base + AzureBlob = Base B2 = Base Box = Base Drive = Base Dropbox = Base FTP = Base + Fichier = Base GoogleCloudStorage = Base JottaCloud = Base Koofr = Base @@ -27,7 +29,9 @@ const ( OneDrive = Base OpenDrive = Base Pcloud = Base + QingStor = Base S3 = Base + Swift = Base ) // ByName returns the encoder for a give backend name or nil