// Package crypt provides wrappers for Fs and Object which implement encryptionpackage crypt import ( "context" "errors" "fmt" "io" "path" "strings" "time" "github.com/rclone/rclone/fs" "github.com/rclone/rclone/fs/accounting" "github.com/rclone/rclone/fs/cache" "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/fspath" "github.com/rclone/rclone/fs/hash" "github.com/rclone/rclone/fs/list") // Globals// Register with Fsfunc init() { fs.Register(&fs.RegInfo{ Name: "crypt", Description: "Encrypt/Decrypt a remote", NewFs: NewFs, CommandHelp: commandHelp, MetadataInfo: &fs.MetadataInfo{ Help: `Any metadata supported by the underlying remote is read and written.`, }, Options: []fs.Option{{ Name: "remote", Help: "Remote to encrypt/decrypt.\n\nNormally should contain a ':' and a path, e.g. \"myremote:path/to/dir\",\n\"myremote:bucket\" or maybe \"myremote:\" (not recommended).", Required: true, }, { Name: "filename_encryption", Help: "How to encrypt the filenames.", Default: "standard", Examples: []fs.OptionExample{ { Value: "standard", Help: "Encrypt the filenames.\nSee the docs for the details.", }, { Value: "obfuscate", Help: "Very simple filename obfuscation.", }, { Value: "off", Help: "Don't encrypt the file names.\nAdds a \".bin\", or \"suffix\" extension only.", }, }, }, { Name: "directory_name_encryption", Help: `Option to either encrypt directory names or leave them intact. NB If filename_encryption is "off" then this option will do nothing.`, Default: true, Examples: []fs.OptionExample{ { Value: "true", Help: "Encrypt directory names.", }, { Value: "false", Help: "Don't encrypt directory names, leave them intact.", }, }, }, { Name: "password", Help: "Password or pass phrase for encryption.", IsPassword: true, Required: true, }, { Name: "password2", Help: "Password or pass phrase for salt.\n\nOptional but recommended.\nShould be different to the previous password.", IsPassword: true, }, { Name: "server_side_across_configs", Default: false, Help: `Deprecated: use --server-side-across-configs instead. Allow server-side operations (e.g. copy) to work across different crypt configs. Normally this option is not what you want, but if you have two cryptspointing to the same backend you can use it. This can be used, for example, to change file name encryption typewithout re-uploading all the data. Just make two crypt backendspointing to two different directories with the single changedparameter and use rclone move to move the files between the cryptremotes.`, Advanced: true, }, { Name: "show_mapping", Help: `For all files listed show how the names encrypt. If this flag is set then for each file that the remote is asked tolist, it will log (at level INFO) a line stating the decrypted filename and the encrypted file name. This is so you can work out which encrypted names are which decryptednames just in case you need to do something with the encrypted filenames, or for debugging purposes.`, Default: false, Hide: fs.OptionHideConfigurator, Advanced: true, }, { Name: "no_data_encryption", Help: "Option to either encrypt file data or leave it unencrypted.", Default: false, Advanced: true, Examples: []fs.OptionExample{ { Value: "true", Help: "Don't encrypt file data, leave it unencrypted.", }, { Value: "false", Help: "Encrypt file data.", }, }, }, { Name: "pass_bad_blocks", Help: `If set this will pass bad blocks through as all 0. This should not be set in normal operation, it should only be set iftrying to recover an encrypted file with errors and it is desired torecover as much of the file as possible.`, Default: false, Advanced: true, }, { Name: "strict_names", Help: `If set, this will raise an error when crypt comes across a filename that can't be decrypted. (By default, rclone will just log a NOTICE and continue as normal.)This can happen if encrypted and unencrypted files are stored in the samedirectory (which is not recommended.) It may also indicate a more seriousproblem that should be investigated.`, Default: false, Advanced: true, }, { Name: "filename_encoding", Help: `How to encode the encrypted filename to text string. This option could help with shortening the encrypted filename. The suitable option would depend on the way your remote count the filenamelength and if it's case sensitive.`, Default: "base32", Examples: []fs.OptionExample{ { Value: "base32", Help: "Encode using base32. Suitable for all remote.", }, { Value: "base64", Help: "Encode using base64. Suitable for case sensitive remote.", }, { Value: "base32768", Help: "Encode using base32768. Suitable if your remote counts UTF-16 or\nUnicode codepoint instead of UTF-8 byte length. (Eg. Onedrive, Dropbox)", }, }, Advanced: true, }, { Name: "suffix", Help: `If this is set it will override the default suffix of ".bin". Setting suffix to "none" will result in an empty suffix. This may be useful when the path length is critical.`, Default: ".bin", Advanced: true, }}, })} // newCipherForConfig constructs a Cipher for the given config namefunc newCipherForConfig(opt *Options) (*Cipher, error) { mode, err := NewNameEncryptionMode(opt.FilenameEncryption) if err != nil { return nil, err } if opt.Password == "" { return nil, errors.New("password not set in config file") } password, err := obscure.Reveal(opt.Password) if err != nil { return nil, fmt.Errorf("failed to decrypt password: %w", err) } var salt string if opt.Password2 != "" { salt, err = obscure.Reveal(opt.Password2) if err != nil { return nil, fmt.Errorf("failed to decrypt password2: %w", err) } } enc, err := NewNameEncoding(opt.FilenameEncoding) if err != nil { return nil, err } cipher, err := newCipher(mode, password, salt, opt.DirectoryNameEncryption, enc) if err != nil { return nil, fmt.Errorf("failed to make cipher: %w", err) } cipher.setEncryptedSuffix(opt.Suffix) cipher.setPassBadBlocks(opt.PassBadBlocks) return cipher, nil} // NewCipher constructs a Cipher for the given configfunc NewCipher(m configmap.Mapper) (*Cipher, error) { // Parse config into Options struct opt := new(Options) err := configstruct.Set(m, opt) if err != nil { return nil, err } return newCipherForConfig(opt)} // NewFs constructs an Fs from the path, container:pathfunc NewFs(ctx context.Context, name, rpath string, m configmap.Mapper) (fs.Fs, error) { // Parse config into Options struct opt := new(Options) err := configstruct.Set(m, opt) if err != nil { return nil, err } cipher, err := newCipherForConfig(opt) if err != nil { return nil, err } remote := opt.Remote if strings.HasPrefix(remote, name+":") { return nil, errors.New("can't point crypt remote at itself - check the value of the remote setting") } // Make sure to remove trailing . referring to the current dir if path.Base(rpath) == "." { rpath = strings.TrimSuffix(rpath, ".") } // Look for a file first var wrappedFs fs.Fs if rpath == "" { wrappedFs, err = cache.Get(ctx, remote) } else { remotePath := fspath.JoinRootPath(remote, cipher.EncryptFileName(rpath)) wrappedFs, err = cache.Get(ctx, remotePath) // if that didn't produce a file, look for a directory if err != fs.ErrorIsFile { remotePath = fspath.JoinRootPath(remote, cipher.EncryptDirName(rpath)) wrappedFs, err = cache.Get(ctx, remotePath) } } if err != fs.ErrorIsFile && err != nil { return nil, fmt.Errorf("failed to make remote %q to wrap: %w", remote, err) } f := &Fs{ Fs: wrappedFs, name: name, root: rpath, opt: *opt, cipher: cipher, } cache.PinUntilFinalized(f.Fs, f) // Correct root if definitely pointing to a file if err == fs.ErrorIsFile { f.root = path.Dir(f.root) if f.root == "." || f.root == "/" { f.root = "" } } // the features here are ones we could support, and they are // ANDed with the ones from wrappedFs f.features = (&fs.Features{ CaseInsensitive: !cipher.dirNameEncrypt || cipher.NameEncryptionMode() == NameEncryptionOff, DuplicateFiles: true, ReadMimeType: false, // MimeTypes not supported with crypt WriteMimeType: false, BucketBased: true, CanHaveEmptyDirectories: true, SetTier: true, GetTier: true, ServerSideAcrossConfigs: opt.ServerSideAcrossConfigs, ReadMetadata: true, WriteMetadata: true, UserMetadata: true, ReadDirMetadata: true, WriteDirMetadata: true, WriteDirSetModTime: true, UserDirMetadata: true, DirModTimeUpdatesOnWrite: true, PartialUploads: true, }).Fill(ctx, f).Mask(ctx, wrappedFs).WrapsFs(f, wrappedFs) // Enable ListP always f.features.ListP = f.ListP return f, err} // Options defines the configuration for this backendtype Options struct { Remote string `config:"remote"` FilenameEncryption string `config:"filename_encryption"` DirectoryNameEncryption bool `config:"directory_name_encryption"` NoDataEncryption bool `config:"no_data_encryption"` Password string `config:"password"` Password2 string `config:"password2"` ServerSideAcrossConfigs bool `config:"server_side_across_configs"` ShowMapping bool `config:"show_mapping"` PassBadBlocks bool `config:"pass_bad_blocks"` FilenameEncoding string `config:"filename_encoding"` Suffix string `config:"suffix"` StrictNames bool `config:"strict_names"`} // Fs represents a wrapped fs.Fstype Fs struct { fs.Fs wrapper fs.Fs name string root string opt Options features *fs.Features // optional features cipher *Cipher} // Name of the remote (as passed into NewFs)func (f *Fs) Name() string { return f.name} // Root of the remote (as passed into NewFs)func (f *Fs) Root() string { return f.root} // Features returns the optional features of this Fsfunc (f *Fs) Features() *fs.Features { return f.features} // String returns a description of the FSfunc (f *Fs) String() string { return fmt.Sprintf("Encrypted drive '%s:%s'", f.name, f.root)} // Encrypt an object file name to entries.func (f *Fs) add(entries *fs.DirEntries, obj fs.Object) error { remote := obj.Remote() decryptedRemote, err := f.cipher.DecryptFileName(remote) if err != nil { if f.opt.StrictNames { return fmt.Errorf("%s: undecryptable file name detected: %v", remote, err) } fs.Logf(remote, "Skipping undecryptable file name: %v", err) return nil } if f.opt.ShowMapping { fs.Logf(decryptedRemote, "Encrypts to %q", remote) } *entries = append(*entries, f.newObject(obj)) return nil} // Encrypt a directory file name to entries.func (f *Fs) addDir(ctx context.Context, entries *fs.DirEntries, dir fs.Directory) error { remote := dir.Remote() decryptedRemote, err := f.cipher.DecryptDirName(remote) if err != nil { if f.opt.StrictNames { return fmt.Errorf("%s: undecryptable dir name detected: %v", remote, err) } fs.Logf(remote, "Skipping undecryptable dir name: %v", err) return nil } if f.opt.ShowMapping { fs.Logf(decryptedRemote, "Encrypts to %q", remote) } *entries = append(*entries, f.newDir(ctx, dir)) return nil} // Encrypt some directory entries. This alters entries returning it as newEntries.func (f *Fs) encryptEntries(ctx context.Context, entries fs.DirEntries) (newEntries fs.DirEntries, err error) { newEntries = entries[:0] // in place filter errors := 0 var firsterr error for _, entry := range entries { switch x := entry.(type) { case fs.Object: err = f.add(&newEntries, x) case fs.Directory: err = f.addDir(ctx, &newEntries, x) default: return nil, fmt.Errorf("unknown object type %T", entry) } if err != nil { errors++ if firsterr == nil { firsterr = err } } } if firsterr != nil { return nil, fmt.Errorf("there were %v undecryptable name errors. first error: %v", errors, firsterr) } return newEntries, nil} // List the objects and directories in dir into entries. The// entries can be returned in any order but should be for a// complete directory.//// dir should be "" to list the root, and should not have// trailing slashes.//// This should return ErrDirNotFound if the directory isn't// found.func (f *Fs) List(ctx context.Context, dir string) (entries fs.DirEntries, err error) { return list.WithListP(ctx, dir, f)} // ListP lists the objects and directories of the Fs starting// from dir non recursively into out.//// dir should be "" to start from the root, and should not// have trailing slashes.//// This should return ErrDirNotFound if the directory isn't// found.//// It should call callback for each tranche of entries read.// These need not be returned in any particular order. If// callback returns an error then the listing will stop// immediately.func (f *Fs) ListP(ctx context.Context, dir string, callback fs.ListRCallback) error { wrappedCallback := func(entries fs.DirEntries) error { entries, err := f.encryptEntries(ctx, entries) if err != nil { return err } return callback(entries) } listP := f.Fs.Features().ListP encryptedDir := f.cipher.EncryptDirName(dir) if listP == nil { entries, err := f.Fs.List(ctx, encryptedDir) if err != nil { return err } return wrappedCallback(entries) } return listP(ctx, encryptedDir, wrappedCallback)} // ListR lists the objects and directories of the Fs starting// from dir recursively into out.//// dir should be "" to start from the root, and should not// have trailing slashes.//// This should return ErrDirNotFound if the directory isn't// found.//// It should call callback for each tranche of entries read.// These need not be returned in any particular order. If// callback returns an error then the listing will stop// immediately.//// Don't implement this unless you have a more efficient way// of listing recursively that doing a directory traversal.func (f *Fs) ListR(ctx context.Context, dir string, callback fs.ListRCallback) (err error) { return f.Fs.Features().ListR(ctx, f.cipher.EncryptDirName(dir), func(entries fs.DirEntries) error { newEntries, err := f.encryptEntries(ctx, entries) if err != nil { return err } return callback(newEntries) })} // NewObject finds the Object at remote.func (f *Fs) NewObject(ctx context.Context, remote string) (fs.Object, error) { o, err := f.Fs.NewObject(ctx, f.cipher.EncryptFileName(remote)) if err != nil { return nil, err } return f.newObject(o), nil} type putFn func(ctx context.Context, in io.Reader, src fs.ObjectInfo, options ...fs.OpenOption) (fs.Object, error) // put implements Put or PutStreamfunc (f *Fs) put(ctx context.Context, in io.Reader, src fs.ObjectInfo, options []fs.OpenOption, put putFn) (fs.Object, error) { ci := fs.GetConfig(ctx) if f.opt.NoDataEncryption {Lines 1–500 of 1337. Load more
// Package crypt provides wrappers for Fs and Object which implement encryption package crypt import ( "context" "errors" "fmt" "io" "path" "strings" "time" "github.com/rclone/rclone/fs" "github.com/rclone/rclone/fs/accounting" "github.com/rclone/rclone/fs/cache" "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/fspath" "github.com/rclone/rclone/fs/hash" "github.com/rclone/rclone/fs/list" ) // Globals // Register with Fs func init() { fs.Register(&fs.RegInfo{ Name: "crypt", Description: "Encrypt/Decrypt a remote", NewFs: NewFs, CommandHelp: commandHelp, MetadataInfo: &fs.MetadataInfo{ Help: `Any metadata supported by the underlying remote is read and written.`, }, Options: []fs.Option{{ Name: "remote", Help: "Remote to encrypt/decrypt.\n\nNormally should contain a ':' and a path, e.g. \"myremote:path/to/dir\",\n\"myremote:bucket\" or maybe \"myremote:\" (not recommended).", Required: true, }, { Name: "filename_encryption", Help: "How to encrypt the filenames.", Default: "standard", Examples: []fs.OptionExample{ { Value: "standard", Help: "Encrypt the filenames.\nSee the docs for the details.", }, { Value: "obfuscate", Help: "Very simple filename obfuscation.", }, { Value: "off", Help: "Don't encrypt the file names.\nAdds a \".bin\", or \"suffix\" extension only.", }, }, }, { Name: "directory_name_encryption", Help: `Option to either encrypt directory names or leave them intact. NB If filename_encryption is "off" then this option will do nothing.`, Default: true, Examples: []fs.OptionExample{ { Value: "true", Help: "Encrypt directory names.", }, { Value: "false", Help: "Don't encrypt directory names, leave them intact.", }, }, }, { Name: "password", Help: "Password or pass phrase for encryption.", IsPassword: true, Required: true, }, { Name: "password2", Help: "Password or pass phrase for salt.\n\nOptional but recommended.\nShould be different to the previous password.", IsPassword: true, }, { Name: "server_side_across_configs", Default: false, Help: `Deprecated: use --server-side-across-configs instead. Allow server-side operations (e.g. copy) to work across different crypt configs. Normally this option is not what you want, but if you have two crypts pointing to the same backend you can use it. This can be used, for example, to change file name encryption type without re-uploading all the data. Just make two crypt backends pointing to two different directories with the single changed parameter and use rclone move to move the files between the crypt remotes.`, Advanced: true, }, { Name: "show_mapping", Help: `For all files listed show how the names encrypt. If this flag is set then for each file that the remote is asked to list, it will log (at level INFO) a line stating the decrypted file name and the encrypted file name. This is so you can work out which encrypted names are which decrypted names just in case you need to do something with the encrypted file names, or for debugging purposes.`, Default: false, Hide: fs.OptionHideConfigurator, Advanced: true, }, { Name: "no_data_encryption", Help: "Option to either encrypt file data or leave it unencrypted.", Default: false, Advanced: true, Examples: []fs.OptionExample{ { Value: "true", Help: "Don't encrypt file data, leave it unencrypted.", }, { Value: "false", Help: "Encrypt file data.", }, }, }, { Name: "pass_bad_blocks", Help: `If set this will pass bad blocks through as all 0. This should not be set in normal operation, it should only be set if trying to recover an encrypted file with errors and it is desired to recover as much of the file as possible.`, Default: false, Advanced: true, }, { Name: "strict_names", Help: `If set, this will raise an error when crypt comes across a filename that can't be decrypted. (By default, rclone will just log a NOTICE and continue as normal.) This can happen if encrypted and unencrypted files are stored in the same directory (which is not recommended.) It may also indicate a more serious problem that should be investigated.`, Default: false, Advanced: true, }, { Name: "filename_encoding", Help: `How to encode the encrypted filename to text string. This option could help with shortening the encrypted filename. The suitable option would depend on the way your remote count the filename length and if it's case sensitive.`, Default: "base32", Examples: []fs.OptionExample{ { Value: "base32", Help: "Encode using base32. Suitable for all remote.", }, { Value: "base64", Help: "Encode using base64. Suitable for case sensitive remote.", }, { Value: "base32768", Help: "Encode using base32768. Suitable if your remote counts UTF-16 or\nUnicode codepoint instead of UTF-8 byte length. (Eg. Onedrive, Dropbox)", }, }, Advanced: true, }, { Name: "suffix", Help: `If this is set it will override the default suffix of ".bin". Setting suffix to "none" will result in an empty suffix. This may be useful when the path length is critical.`, Default: ".bin", Advanced: true, }}, }) } // newCipherForConfig constructs a Cipher for the given config name func newCipherForConfig(opt *Options) (*Cipher, error) { mode, err := NewNameEncryptionMode(opt.FilenameEncryption) if err != nil { return nil, err } if opt.Password == "" { return nil, errors.New("password not set in config file") } password, err := obscure.Reveal(opt.Password) if err != nil { return nil, fmt.Errorf("failed to decrypt password: %w", err) } var salt string if opt.Password2 != "" { salt, err = obscure.Reveal(opt.Password2) if err != nil { return nil, fmt.Errorf("failed to decrypt password2: %w", err) } } enc, err := NewNameEncoding(opt.FilenameEncoding) if err != nil { return nil, err } cipher, err := newCipher(mode, password, salt, opt.DirectoryNameEncryption, enc) if err != nil { return nil, fmt.Errorf("failed to make cipher: %w", err) } cipher.setEncryptedSuffix(opt.Suffix) cipher.setPassBadBlocks(opt.PassBadBlocks) return cipher, nil } // NewCipher constructs a Cipher for the given config func NewCipher(m configmap.Mapper) (*Cipher, error) { // Parse config into Options struct opt := new(Options) err := configstruct.Set(m, opt) if err != nil { return nil, err } return newCipherForConfig(opt) } // NewFs constructs an Fs from the path, container:path func NewFs(ctx context.Context, name, rpath string, m configmap.Mapper) (fs.Fs, error) { // Parse config into Options struct opt := new(Options) err := configstruct.Set(m, opt) if err != nil { return nil, err } cipher, err := newCipherForConfig(opt) if err != nil { return nil, err } remote := opt.Remote if strings.HasPrefix(remote, name+":") { return nil, errors.New("can't point crypt remote at itself - check the value of the remote setting") } // Make sure to remove trailing . referring to the current dir if path.Base(rpath) == "." { rpath = strings.TrimSuffix(rpath, ".") } // Look for a file first var wrappedFs fs.Fs if rpath == "" { wrappedFs, err = cache.Get(ctx, remote) } else { remotePath := fspath.JoinRootPath(remote, cipher.EncryptFileName(rpath)) wrappedFs, err = cache.Get(ctx, remotePath) // if that didn't produce a file, look for a directory if err != fs.ErrorIsFile { remotePath = fspath.JoinRootPath(remote, cipher.EncryptDirName(rpath)) wrappedFs, err = cache.Get(ctx, remotePath) } } if err != fs.ErrorIsFile && err != nil { return nil, fmt.Errorf("failed to make remote %q to wrap: %w", remote, err) } f := &Fs{ Fs: wrappedFs, name: name, root: rpath, opt: *opt, cipher: cipher, } cache.PinUntilFinalized(f.Fs, f) // Correct root if definitely pointing to a file if err == fs.ErrorIsFile { f.root = path.Dir(f.root) if f.root == "." || f.root == "/" { f.root = "" } } // the features here are ones we could support, and they are // ANDed with the ones from wrappedFs f.features = (&fs.Features{ CaseInsensitive: !cipher.dirNameEncrypt || cipher.NameEncryptionMode() == NameEncryptionOff, DuplicateFiles: true, ReadMimeType: false, // MimeTypes not supported with crypt WriteMimeType: false, BucketBased: true, CanHaveEmptyDirectories: true, SetTier: true, GetTier: true, ServerSideAcrossConfigs: opt.ServerSideAcrossConfigs, ReadMetadata: true, WriteMetadata: true, UserMetadata: true, ReadDirMetadata: true, WriteDirMetadata: true, WriteDirSetModTime: true, UserDirMetadata: true, DirModTimeUpdatesOnWrite: true, PartialUploads: true, }).Fill(ctx, f).Mask(ctx, wrappedFs).WrapsFs(f, wrappedFs) // Enable ListP always f.features.ListP = f.ListP return f, err } // Options defines the configuration for this backend type Options struct { Remote string `config:"remote"` FilenameEncryption string `config:"filename_encryption"` DirectoryNameEncryption bool `config:"directory_name_encryption"` NoDataEncryption bool `config:"no_data_encryption"` Password string `config:"password"` Password2 string `config:"password2"` ServerSideAcrossConfigs bool `config:"server_side_across_configs"` ShowMapping bool `config:"show_mapping"` PassBadBlocks bool `config:"pass_bad_blocks"` FilenameEncoding string `config:"filename_encoding"` Suffix string `config:"suffix"` StrictNames bool `config:"strict_names"` } // Fs represents a wrapped fs.Fs type Fs struct { fs.Fs wrapper fs.Fs name string root string opt Options features *fs.Features // optional features cipher *Cipher } // Name of the remote (as passed into NewFs) func (f *Fs) Name() string { return f.name } // Root of the remote (as passed into NewFs) func (f *Fs) Root() string { return f.root } // Features returns the optional features of this Fs func (f *Fs) Features() *fs.Features { return f.features } // String returns a description of the FS func (f *Fs) String() string { return fmt.Sprintf("Encrypted drive '%s:%s'", f.name, f.root) } // Encrypt an object file name to entries. func (f *Fs) add(entries *fs.DirEntries, obj fs.Object) error { remote := obj.Remote() decryptedRemote, err := f.cipher.DecryptFileName(remote) if err != nil { if f.opt.StrictNames { return fmt.Errorf("%s: undecryptable file name detected: %v", remote, err) } fs.Logf(remote, "Skipping undecryptable file name: %v", err) return nil } if f.opt.ShowMapping { fs.Logf(decryptedRemote, "Encrypts to %q", remote) } *entries = append(*entries, f.newObject(obj)) return nil } // Encrypt a directory file name to entries. func (f *Fs) addDir(ctx context.Context, entries *fs.DirEntries, dir fs.Directory) error { remote := dir.Remote() decryptedRemote, err := f.cipher.DecryptDirName(remote) if err != nil { if f.opt.StrictNames { return fmt.Errorf("%s: undecryptable dir name detected: %v", remote, err) } fs.Logf(remote, "Skipping undecryptable dir name: %v", err) return nil } if f.opt.ShowMapping { fs.Logf(decryptedRemote, "Encrypts to %q", remote) } *entries = append(*entries, f.newDir(ctx, dir)) return nil } // Encrypt some directory entries. This alters entries returning it as newEntries. func (f *Fs) encryptEntries(ctx context.Context, entries fs.DirEntries) (newEntries fs.DirEntries, err error) { newEntries = entries[:0] // in place filter errors := 0 var firsterr error for _, entry := range entries { switch x := entry.(type) { case fs.Object: err = f.add(&newEntries, x) case fs.Directory: err = f.addDir(ctx, &newEntries, x) default: return nil, fmt.Errorf("unknown object type %T", entry) } if err != nil { errors++ if firsterr == nil { firsterr = err } } } if firsterr != nil { return nil, fmt.Errorf("there were %v undecryptable name errors. first error: %v", errors, firsterr) } return newEntries, nil } // List the objects and directories in dir into entries. The // entries can be returned in any order but should be for a // complete directory. // // dir should be "" to list the root, and should not have // trailing slashes. // // This should return ErrDirNotFound if the directory isn't // found. func (f *Fs) List(ctx context.Context, dir string) (entries fs.DirEntries, err error) { return list.WithListP(ctx, dir, f) } // ListP lists the objects and directories of the Fs starting // from dir non recursively into out. // // dir should be "" to start from the root, and should not // have trailing slashes. // // This should return ErrDirNotFound if the directory isn't // found. // // It should call callback for each tranche of entries read. // These need not be returned in any particular order. If // callback returns an error then the listing will stop // immediately. func (f *Fs) ListP(ctx context.Context, dir string, callback fs.ListRCallback) error { wrappedCallback := func(entries fs.DirEntries) error { entries, err := f.encryptEntries(ctx, entries) if err != nil { return err } return callback(entries) } listP := f.Fs.Features().ListP encryptedDir := f.cipher.EncryptDirName(dir) if listP == nil { entries, err := f.Fs.List(ctx, encryptedDir) if err != nil { return err } return wrappedCallback(entries) } return listP(ctx, encryptedDir, wrappedCallback) } // ListR lists the objects and directories of the Fs starting // from dir recursively into out. // // dir should be "" to start from the root, and should not // have trailing slashes. // // This should return ErrDirNotFound if the directory isn't // found. // // It should call callback for each tranche of entries read. // These need not be returned in any particular order. If // callback returns an error then the listing will stop // immediately. // // Don't implement this unless you have a more efficient way // of listing recursively that doing a directory traversal. func (f *Fs) ListR(ctx context.Context, dir string, callback fs.ListRCallback) (err error) { return f.Fs.Features().ListR(ctx, f.cipher.EncryptDirName(dir), func(entries fs.DirEntries) error { newEntries, err := f.encryptEntries(ctx, entries) if err != nil { return err } return callback(newEntries) }) } // NewObject finds the Object at remote. func (f *Fs) NewObject(ctx context.Context, remote string) (fs.Object, error) { o, err := f.Fs.NewObject(ctx, f.cipher.EncryptFileName(remote)) if err != nil { return nil, err } return f.newObject(o), nil } type putFn func(ctx context.Context, in io.Reader, src fs.ObjectInfo, options ...fs.OpenOption) (fs.Object, error) // put implements Put or PutStream func (f *Fs) put(ctx context.Context, in io.Reader, src fs.ObjectInfo, options []fs.OpenOption, put putFn) (fs.Object, error) { ci := fs.GetConfig(ctx) if f.opt.NoDataEncryption {