// Package crypt provides wrappers for Fs and Object which implement encryptionpackage crypt import ( "fmt" "io" "path" "strconv" "strings" "time" "github.com/ncw/rclone/fs" "github.com/ncw/rclone/fs/config" "github.com/ncw/rclone/fs/config/flags" "github.com/ncw/rclone/fs/config/obscure" "github.com/ncw/rclone/fs/hash" "github.com/pkg/errors") // Globalsvar ( // Flags cryptShowMapping = flags.BoolP("crypt-show-mapping", "", false, "For all files listed show how the names encrypt.")) // Register with Fsfunc init() { fs.Register(&fs.RegInfo{ Name: "crypt", Description: "Encrypt/Decrypt a remote", NewFs: NewFs, Options: []fs.Option{{ Name: "remote", Help: "Remote to encrypt/decrypt.\nNormally should contain a ':' and a path, eg \"myremote:path/to/dir\",\n\"myremote:bucket\" or maybe \"myremote:\" (not recommended).", }, { Name: "filename_encryption", Help: "How to encrypt the filenames.", Examples: []fs.OptionExample{ { Value: "off", Help: "Don't encrypt the file names. Adds a \".bin\" extension only.", }, { Value: "standard", Help: "Encrypt the filenames see the docs for the details.", }, { Value: "obfuscate", Help: "Very simple filename obfuscation.", }, }, }, { Name: "directory_name_encryption", Help: "Option to either encrypt directory names or leave them intact.", 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, }, { Name: "password2", Help: "Password or pass phrase for salt. Optional but recommended.\nShould be different to the previous password.", IsPassword: true, Optional: true, }}, })} // NewCipher constructs a Cipher for the given config namefunc NewCipher(name string) (Cipher, error) { mode, err := NewNameEncryptionMode(config.FileGet(name, "filename_encryption", "standard")) if err != nil { return nil, err } dirNameEncrypt, err := strconv.ParseBool(config.FileGet(name, "directory_name_encryption", "true")) if err != nil { return nil, err } password := config.FileGet(name, "password", "") if password == "" { return nil, errors.New("password not set in config file") } password, err = obscure.Reveal(password) if err != nil { return nil, errors.Wrap(err, "failed to decrypt password") } salt := config.FileGet(name, "password2", "") if salt != "" { salt, err = obscure.Reveal(salt) if err != nil { return nil, errors.Wrap(err, "failed to decrypt password2") } } cipher, err := newCipher(mode, password, salt, dirNameEncrypt) if err != nil { return nil, errors.Wrap(err, "failed to make cipher") } return cipher, nil} // NewFs contstructs an Fs from the path, container:pathfunc NewFs(name, rpath string) (fs.Fs, error) { cipher, err := NewCipher(name) if err != nil { return nil, err } remote := config.FileGet(name, "remote") if strings.HasPrefix(remote, name+":") { return nil, errors.New("can't point crypt remote at itself - check the value of the remote setting") } // Look for a file first remotePath := path.Join(remote, cipher.EncryptFileName(rpath)) wrappedFs, err := fs.NewFs(remotePath) // if that didn't produce a file, look for a directory if err != fs.ErrorIsFile { remotePath = path.Join(remote, cipher.EncryptDirName(rpath)) wrappedFs, err = fs.NewFs(remotePath) } if err != fs.ErrorIsFile && err != nil { return nil, errors.Wrapf(err, "failed to make remote %q to wrap", remotePath) } f := &Fs{ Fs: wrappedFs, name: name, root: rpath, cipher: cipher, } // the features here are ones we could support, and they are // ANDed with the ones from wrappedFs f.features = (&fs.Features{ CaseInsensitive: cipher.NameEncryptionMode() == NameEncryptionOff, DuplicateFiles: true, ReadMimeType: false, // MimeTypes not supported with crypt WriteMimeType: false, BucketBased: true, CanHaveEmptyDirectories: true, }).Fill(f).Mask(wrappedFs).WrapsFs(f, wrappedFs) doChangeNotify := wrappedFs.Features().ChangeNotify if doChangeNotify != nil { f.features.ChangeNotify = func(notifyFunc func(string, fs.EntryType), pollInterval time.Duration) chan bool { wrappedNotifyFunc := func(path string, entryType fs.EntryType) { decrypted, err := f.DecryptFileName(path) if err != nil { fs.Logf(f, "ChangeNotify was unable to decrypt %q: %s", path, err) return } notifyFunc(decrypted, entryType) } return doChangeNotify(wrappedNotifyFunc, pollInterval) } } return f, err} // Fs represents a wrapped fs.Fstype Fs struct { fs.Fs name string root string 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) { remote := obj.Remote() decryptedRemote, err := f.cipher.DecryptFileName(remote) if err != nil { fs.Debugf(remote, "Skipping undecryptable file name: %v", err) return } if *cryptShowMapping { fs.Logf(decryptedRemote, "Encrypts to %q", remote) } *entries = append(*entries, f.newObject(obj))} // Encrypt an directory file name to entries.func (f *Fs) addDir(entries *fs.DirEntries, dir fs.Directory) { remote := dir.Remote() decryptedRemote, err := f.cipher.DecryptDirName(remote) if err != nil { fs.Debugf(remote, "Skipping undecryptable dir name: %v", err) return } if *cryptShowMapping { fs.Logf(decryptedRemote, "Encrypts to %q", remote) } *entries = append(*entries, f.newDir(dir))} // Encrypt some directory entries. This alters entries returning it as newEntries.func (f *Fs) encryptEntries(entries fs.DirEntries) (newEntries fs.DirEntries, err error) { newEntries = entries[:0] // in place filter for _, entry := range entries { switch x := entry.(type) { case fs.Object: f.add(&newEntries, x) case fs.Directory: f.addDir(&newEntries, x) default: return nil, errors.Errorf("Unknown object type %T", entry) } } 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(dir string) (entries fs.DirEntries, err error) { entries, err = f.Fs.List(f.cipher.EncryptDirName(dir)) if err != nil { return nil, err } return f.encryptEntries(entries)} // 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(dir string, callback fs.ListRCallback) (err error) { return f.Fs.Features().ListR(f.cipher.EncryptDirName(dir), func(entries fs.DirEntries) error { newEntries, err := f.encryptEntries(entries) if err != nil { return err } return callback(newEntries) })} // NewObject finds the Object at remote.func (f *Fs) NewObject(remote string) (fs.Object, error) { o, err := f.Fs.NewObject(f.cipher.EncryptFileName(remote)) if err != nil { return nil, err } return f.newObject(o), nil} type putFn func(in io.Reader, src fs.ObjectInfo, options ...fs.OpenOption) (fs.Object, error) // put implements Put or PutStreamfunc (f *Fs) put(in io.Reader, src fs.ObjectInfo, options []fs.OpenOption, put putFn) (fs.Object, error) { // Encrypt the data into wrappedIn wrappedIn, err := f.cipher.EncryptData(in) if err != nil { return nil, err } // Find a hash the destination supports to compute a hash of // the encrypted data ht := f.Fs.Hashes().GetOne() var hasher *hash.MultiHasher if ht != hash.None { hasher, err = hash.NewMultiHasherTypes(hash.NewHashSet(ht)) if err != nil { return nil, err } wrappedIn = io.TeeReader(wrappedIn, hasher) } // Transfer the data o, err := put(wrappedIn, f.newObjectInfo(src), options...) if err != nil { return nil, err } // Check the hashes of the encrypted data if we were comparing them if ht != hash.None && hasher != nil { srcHash := hasher.Sums()[ht] var dstHash string dstHash, err = o.Hash(ht) if err != nil { return nil, errors.Wrap(err, "failed to read destination hash") } if srcHash != "" && dstHash != "" && srcHash != dstHash { // remove object err = o.Remove() if err != nil { fs.Errorf(o, "Failed to remove corrupted object: %v", err) } return nil, errors.Errorf("corrupted on transfer: %v crypted hash differ %q vs %q", ht, srcHash, dstHash) } } return f.newObject(o), nil} // Put in to the remote path with the modTime given of the given size//// May create the object even if it returns an error - if so// will return the object and the error, otherwise will return// nil and the errorfunc (f *Fs) Put(in io.Reader, src fs.ObjectInfo, options ...fs.OpenOption) (fs.Object, error) { return f.put(in, src, options, f.Fs.Put)} // PutStream uploads to the remote path with the modTime given of indeterminate sizefunc (f *Fs) PutStream(in io.Reader, src fs.ObjectInfo, options ...fs.OpenOption) (fs.Object, error) { return f.put(in, src, options, f.Fs.Features().PutStream)} // Hashes returns the supported hash sets.func (f *Fs) Hashes() hash.Set { return hash.Set(hash.None)} // Mkdir makes the directory (container, bucket)//// Shouldn't return an error if it already existsfunc (f *Fs) Mkdir(dir string) error { return f.Fs.Mkdir(f.cipher.EncryptDirName(dir))} // Rmdir removes the directory (container, bucket) if empty//// Return an error if it doesn't exist or isn't emptyfunc (f *Fs) Rmdir(dir string) error { return f.Fs.Rmdir(f.cipher.EncryptDirName(dir))} // Purge all files in the root and the root directory//// Implement this if you have a way of deleting all the files// quicker than just running Remove() on the result of List()//// Return an error if it doesn't existfunc (f *Fs) Purge() error { do := f.Fs.Features().Purge if do == nil { return fs.ErrorCantPurge } return do()} // Copy src to this remote using server side copy operations.//// This is stored with the remote path given//// It returns the destination Object and a possible error//// Will only be called if src.Fs().Name() == f.Name()//// If it isn't possible then return fs.ErrorCantCopyfunc (f *Fs) Copy(src fs.Object, remote string) (fs.Object, error) { do := f.Fs.Features().Copy if do == nil { return nil, fs.ErrorCantCopy } o, ok := src.(*Object) if !ok { return nil, fs.ErrorCantCopy } oResult, err := do(o.Object, f.cipher.EncryptFileName(remote)) if err != nil { return nil, err } return f.newObject(oResult), nil} // Move src to this remote using server side move operations.//// This is stored with the remote path given//// It returns the destination Object and a possible error//// Will only be called if src.Fs().Name() == f.Name()//// If it isn't possible then return fs.ErrorCantMovefunc (f *Fs) Move(src fs.Object, remote string) (fs.Object, error) { do := f.Fs.Features().Move if do == nil { return nil, fs.ErrorCantMove } o, ok := src.(*Object) if !ok { return nil, fs.ErrorCantMove } oResult, err := do(o.Object, f.cipher.EncryptFileName(remote)) if err != nil { return nil, err } return f.newObject(oResult), nil} // DirMove moves src, srcRemote to this remote at dstRemote// using server side move operations.//// Will only be called if src.Fs().Name() == f.Name()//// If it isn't possible then return fs.ErrorCantDirMove//// If destination exists then return fs.ErrorDirExistsfunc (f *Fs) DirMove(src fs.Fs, srcRemote, dstRemote string) error { do := f.Fs.Features().DirMove if do == nil { return fs.ErrorCantDirMove } srcFs, ok := src.(*Fs) if !ok { fs.Debugf(srcFs, "Can't move directory - not same remote type") return fs.ErrorCantDirMove } return do(srcFs.Fs, f.cipher.EncryptDirName(srcRemote), f.cipher.EncryptDirName(dstRemote))} // PutUnchecked uploads the object//// This will create a duplicate if we upload a new file without// checking to see if there is one already - use Put() for that.func (f *Fs) PutUnchecked(in io.Reader, src fs.ObjectInfo, options ...fs.OpenOption) (fs.Object, error) { do := f.Fs.Features().PutUnchecked if do == nil { return nil, errors.New("can't PutUnchecked") } wrappedIn, err := f.cipher.EncryptData(in) if err != nil { return nil, err } o, err := do(wrappedIn, f.newObjectInfo(src)) if err != nil { return nil, err } return f.newObject(o), nil} // CleanUp the trash in the Fs//// Implement this if you have a way of emptying the trash or// otherwise cleaning up old versions of files.func (f *Fs) CleanUp() error { do := f.Fs.Features().CleanUp if do == nil { return errors.New("can't CleanUp") } return do()} // About gets quota information from the Fsfunc (f *Fs) About() (*fs.Usage, error) { do := f.Fs.Features().About if do == nil { return nil, errors.New("About not supported") } return do()} // UnWrap returns the Fs that this Fs is wrappingfunc (f *Fs) UnWrap() fs.Fs { return f.Fs}Lines 1–500 of 748. Load more
// Package crypt provides wrappers for Fs and Object which implement encryption package crypt import ( "fmt" "io" "path" "strconv" "strings" "time" "github.com/ncw/rclone/fs" "github.com/ncw/rclone/fs/config" "github.com/ncw/rclone/fs/config/flags" "github.com/ncw/rclone/fs/config/obscure" "github.com/ncw/rclone/fs/hash" "github.com/pkg/errors" ) // Globals var ( // Flags cryptShowMapping = flags.BoolP("crypt-show-mapping", "", false, "For all files listed show how the names encrypt.") ) // Register with Fs func init() { fs.Register(&fs.RegInfo{ Name: "crypt", Description: "Encrypt/Decrypt a remote", NewFs: NewFs, Options: []fs.Option{{ Name: "remote", Help: "Remote to encrypt/decrypt.\nNormally should contain a ':' and a path, eg \"myremote:path/to/dir\",\n\"myremote:bucket\" or maybe \"myremote:\" (not recommended).", }, { Name: "filename_encryption", Help: "How to encrypt the filenames.", Examples: []fs.OptionExample{ { Value: "off", Help: "Don't encrypt the file names. Adds a \".bin\" extension only.", }, { Value: "standard", Help: "Encrypt the filenames see the docs for the details.", }, { Value: "obfuscate", Help: "Very simple filename obfuscation.", }, }, }, { Name: "directory_name_encryption", Help: "Option to either encrypt directory names or leave them intact.", 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, }, { Name: "password2", Help: "Password or pass phrase for salt. Optional but recommended.\nShould be different to the previous password.", IsPassword: true, Optional: true, }}, }) } // NewCipher constructs a Cipher for the given config name func NewCipher(name string) (Cipher, error) { mode, err := NewNameEncryptionMode(config.FileGet(name, "filename_encryption", "standard")) if err != nil { return nil, err } dirNameEncrypt, err := strconv.ParseBool(config.FileGet(name, "directory_name_encryption", "true")) if err != nil { return nil, err } password := config.FileGet(name, "password", "") if password == "" { return nil, errors.New("password not set in config file") } password, err = obscure.Reveal(password) if err != nil { return nil, errors.Wrap(err, "failed to decrypt password") } salt := config.FileGet(name, "password2", "") if salt != "" { salt, err = obscure.Reveal(salt) if err != nil { return nil, errors.Wrap(err, "failed to decrypt password2") } } cipher, err := newCipher(mode, password, salt, dirNameEncrypt) if err != nil { return nil, errors.Wrap(err, "failed to make cipher") } return cipher, nil } // NewFs contstructs an Fs from the path, container:path func NewFs(name, rpath string) (fs.Fs, error) { cipher, err := NewCipher(name) if err != nil { return nil, err } remote := config.FileGet(name, "remote") if strings.HasPrefix(remote, name+":") { return nil, errors.New("can't point crypt remote at itself - check the value of the remote setting") } // Look for a file first remotePath := path.Join(remote, cipher.EncryptFileName(rpath)) wrappedFs, err := fs.NewFs(remotePath) // if that didn't produce a file, look for a directory if err != fs.ErrorIsFile { remotePath = path.Join(remote, cipher.EncryptDirName(rpath)) wrappedFs, err = fs.NewFs(remotePath) } if err != fs.ErrorIsFile && err != nil { return nil, errors.Wrapf(err, "failed to make remote %q to wrap", remotePath) } f := &Fs{ Fs: wrappedFs, name: name, root: rpath, cipher: cipher, } // the features here are ones we could support, and they are // ANDed with the ones from wrappedFs f.features = (&fs.Features{ CaseInsensitive: cipher.NameEncryptionMode() == NameEncryptionOff, DuplicateFiles: true, ReadMimeType: false, // MimeTypes not supported with crypt WriteMimeType: false, BucketBased: true, CanHaveEmptyDirectories: true, }).Fill(f).Mask(wrappedFs).WrapsFs(f, wrappedFs) doChangeNotify := wrappedFs.Features().ChangeNotify if doChangeNotify != nil { f.features.ChangeNotify = func(notifyFunc func(string, fs.EntryType), pollInterval time.Duration) chan bool { wrappedNotifyFunc := func(path string, entryType fs.EntryType) { decrypted, err := f.DecryptFileName(path) if err != nil { fs.Logf(f, "ChangeNotify was unable to decrypt %q: %s", path, err) return } notifyFunc(decrypted, entryType) } return doChangeNotify(wrappedNotifyFunc, pollInterval) } } return f, err } // Fs represents a wrapped fs.Fs type Fs struct { fs.Fs name string root string 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) { remote := obj.Remote() decryptedRemote, err := f.cipher.DecryptFileName(remote) if err != nil { fs.Debugf(remote, "Skipping undecryptable file name: %v", err) return } if *cryptShowMapping { fs.Logf(decryptedRemote, "Encrypts to %q", remote) } *entries = append(*entries, f.newObject(obj)) } // Encrypt an directory file name to entries. func (f *Fs) addDir(entries *fs.DirEntries, dir fs.Directory) { remote := dir.Remote() decryptedRemote, err := f.cipher.DecryptDirName(remote) if err != nil { fs.Debugf(remote, "Skipping undecryptable dir name: %v", err) return } if *cryptShowMapping { fs.Logf(decryptedRemote, "Encrypts to %q", remote) } *entries = append(*entries, f.newDir(dir)) } // Encrypt some directory entries. This alters entries returning it as newEntries. func (f *Fs) encryptEntries(entries fs.DirEntries) (newEntries fs.DirEntries, err error) { newEntries = entries[:0] // in place filter for _, entry := range entries { switch x := entry.(type) { case fs.Object: f.add(&newEntries, x) case fs.Directory: f.addDir(&newEntries, x) default: return nil, errors.Errorf("Unknown object type %T", entry) } } 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(dir string) (entries fs.DirEntries, err error) { entries, err = f.Fs.List(f.cipher.EncryptDirName(dir)) if err != nil { return nil, err } return f.encryptEntries(entries) } // 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(dir string, callback fs.ListRCallback) (err error) { return f.Fs.Features().ListR(f.cipher.EncryptDirName(dir), func(entries fs.DirEntries) error { newEntries, err := f.encryptEntries(entries) if err != nil { return err } return callback(newEntries) }) } // NewObject finds the Object at remote. func (f *Fs) NewObject(remote string) (fs.Object, error) { o, err := f.Fs.NewObject(f.cipher.EncryptFileName(remote)) if err != nil { return nil, err } return f.newObject(o), nil } type putFn func(in io.Reader, src fs.ObjectInfo, options ...fs.OpenOption) (fs.Object, error) // put implements Put or PutStream func (f *Fs) put(in io.Reader, src fs.ObjectInfo, options []fs.OpenOption, put putFn) (fs.Object, error) { // Encrypt the data into wrappedIn wrappedIn, err := f.cipher.EncryptData(in) if err != nil { return nil, err } // Find a hash the destination supports to compute a hash of // the encrypted data ht := f.Fs.Hashes().GetOne() var hasher *hash.MultiHasher if ht != hash.None { hasher, err = hash.NewMultiHasherTypes(hash.NewHashSet(ht)) if err != nil { return nil, err } wrappedIn = io.TeeReader(wrappedIn, hasher) } // Transfer the data o, err := put(wrappedIn, f.newObjectInfo(src), options...) if err != nil { return nil, err } // Check the hashes of the encrypted data if we were comparing them if ht != hash.None && hasher != nil { srcHash := hasher.Sums()[ht] var dstHash string dstHash, err = o.Hash(ht) if err != nil { return nil, errors.Wrap(err, "failed to read destination hash") } if srcHash != "" && dstHash != "" && srcHash != dstHash { // remove object err = o.Remove() if err != nil { fs.Errorf(o, "Failed to remove corrupted object: %v", err) } return nil, errors.Errorf("corrupted on transfer: %v crypted hash differ %q vs %q", ht, srcHash, dstHash) } } return f.newObject(o), nil } // Put in to the remote path with the modTime given of the given size // // May create the object even if it returns an error - if so // will return the object and the error, otherwise will return // nil and the error func (f *Fs) Put(in io.Reader, src fs.ObjectInfo, options ...fs.OpenOption) (fs.Object, error) { return f.put(in, src, options, f.Fs.Put) } // PutStream uploads to the remote path with the modTime given of indeterminate size func (f *Fs) PutStream(in io.Reader, src fs.ObjectInfo, options ...fs.OpenOption) (fs.Object, error) { return f.put(in, src, options, f.Fs.Features().PutStream) } // Hashes returns the supported hash sets. func (f *Fs) Hashes() hash.Set { return hash.Set(hash.None) } // Mkdir makes the directory (container, bucket) // // Shouldn't return an error if it already exists func (f *Fs) Mkdir(dir string) error { return f.Fs.Mkdir(f.cipher.EncryptDirName(dir)) } // Rmdir removes the directory (container, bucket) if empty // // Return an error if it doesn't exist or isn't empty func (f *Fs) Rmdir(dir string) error { return f.Fs.Rmdir(f.cipher.EncryptDirName(dir)) } // Purge all files in the root and the root directory // // Implement this if you have a way of deleting all the files // quicker than just running Remove() on the result of List() // // Return an error if it doesn't exist func (f *Fs) Purge() error { do := f.Fs.Features().Purge if do == nil { return fs.ErrorCantPurge } return do() } // Copy src to this remote using server side copy operations. // // This is stored with the remote path given // // It returns the destination Object and a possible error // // Will only be called if src.Fs().Name() == f.Name() // // If it isn't possible then return fs.ErrorCantCopy func (f *Fs) Copy(src fs.Object, remote string) (fs.Object, error) { do := f.Fs.Features().Copy if do == nil { return nil, fs.ErrorCantCopy } o, ok := src.(*Object) if !ok { return nil, fs.ErrorCantCopy } oResult, err := do(o.Object, f.cipher.EncryptFileName(remote)) if err != nil { return nil, err } return f.newObject(oResult), nil } // Move src to this remote using server side move operations. // // This is stored with the remote path given // // It returns the destination Object and a possible error // // Will only be called if src.Fs().Name() == f.Name() // // If it isn't possible then return fs.ErrorCantMove func (f *Fs) Move(src fs.Object, remote string) (fs.Object, error) { do := f.Fs.Features().Move if do == nil { return nil, fs.ErrorCantMove } o, ok := src.(*Object) if !ok { return nil, fs.ErrorCantMove } oResult, err := do(o.Object, f.cipher.EncryptFileName(remote)) if err != nil { return nil, err } return f.newObject(oResult), nil } // DirMove moves src, srcRemote to this remote at dstRemote // using server side move operations. // // Will only be called if src.Fs().Name() == f.Name() // // If it isn't possible then return fs.ErrorCantDirMove // // If destination exists then return fs.ErrorDirExists func (f *Fs) DirMove(src fs.Fs, srcRemote, dstRemote string) error { do := f.Fs.Features().DirMove if do == nil { return fs.ErrorCantDirMove } srcFs, ok := src.(*Fs) if !ok { fs.Debugf(srcFs, "Can't move directory - not same remote type") return fs.ErrorCantDirMove } return do(srcFs.Fs, f.cipher.EncryptDirName(srcRemote), f.cipher.EncryptDirName(dstRemote)) } // PutUnchecked uploads the object // // This will create a duplicate if we upload a new file without // checking to see if there is one already - use Put() for that. func (f *Fs) PutUnchecked(in io.Reader, src fs.ObjectInfo, options ...fs.OpenOption) (fs.Object, error) { do := f.Fs.Features().PutUnchecked if do == nil { return nil, errors.New("can't PutUnchecked") } wrappedIn, err := f.cipher.EncryptData(in) if err != nil { return nil, err } o, err := do(wrappedIn, f.newObjectInfo(src)) if err != nil { return nil, err } return f.newObject(o), nil } // CleanUp the trash in the Fs // // Implement this if you have a way of emptying the trash or // otherwise cleaning up old versions of files. func (f *Fs) CleanUp() error { do := f.Fs.Features().CleanUp if do == nil { return errors.New("can't CleanUp") } return do() } // About gets quota information from the Fs func (f *Fs) About() (*fs.Usage, error) { do := f.Fs.Features().About if do == nil { return nil, errors.New("About not supported") } return do() } // UnWrap returns the Fs that this Fs is wrapping func (f *Fs) UnWrap() fs.Fs { return f.Fs }