overdue
This commit is contained in:
1346
.rclone_repo/fs/config/config.go
Executable file
1346
.rclone_repo/fs/config/config.go
Executable file
File diff suppressed because it is too large
Load Diff
10
.rclone_repo/fs/config/config_other.go
Executable file
10
.rclone_repo/fs/config/config_other.go
Executable file
@@ -0,0 +1,10 @@
|
||||
// Read, write and edit the config file
|
||||
// Non-unix specific functions.
|
||||
|
||||
// +build !darwin,!dragonfly,!freebsd,!linux,!netbsd,!openbsd,!solaris
|
||||
|
||||
package config
|
||||
|
||||
// attemptCopyGroups tries to keep the group the same, which only makes sense
|
||||
// for system with user-group-world permission model.
|
||||
func attemptCopyGroup(fromPath, toPath string) {}
|
||||
29
.rclone_repo/fs/config/config_read_password.go
Executable file
29
.rclone_repo/fs/config/config_read_password.go
Executable file
@@ -0,0 +1,29 @@
|
||||
// ReadPassword for OSes which are supported by golang.org/x/crypto/ssh/terminal
|
||||
// See https://github.com/golang/go/issues/14441 - plan9
|
||||
// https://github.com/golang/go/issues/13085 - solaris
|
||||
|
||||
// +build !solaris,!plan9
|
||||
|
||||
package config
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
|
||||
"golang.org/x/crypto/ssh/terminal"
|
||||
)
|
||||
|
||||
// ReadPassword reads a password without echoing it to the terminal.
|
||||
func ReadPassword() string {
|
||||
stdin := int(os.Stdin.Fd())
|
||||
if !terminal.IsTerminal(stdin) {
|
||||
return ReadLine()
|
||||
}
|
||||
line, err := terminal.ReadPassword(stdin)
|
||||
_, _ = fmt.Fprintln(os.Stderr)
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to read password: %v", err)
|
||||
}
|
||||
return string(line)
|
||||
}
|
||||
12
.rclone_repo/fs/config/config_read_password_unsupported.go
Executable file
12
.rclone_repo/fs/config/config_read_password_unsupported.go
Executable file
@@ -0,0 +1,12 @@
|
||||
// ReadPassword for OSes which are not supported by golang.org/x/crypto/ssh/terminal
|
||||
// See https://github.com/golang/go/issues/14441 - plan9
|
||||
// https://github.com/golang/go/issues/13085 - solaris
|
||||
|
||||
// +build solaris plan9
|
||||
|
||||
package config
|
||||
|
||||
// ReadPassword reads a password with echoing it to the terminal.
|
||||
func ReadPassword() string {
|
||||
return ReadLine()
|
||||
}
|
||||
229
.rclone_repo/fs/config/config_test.go
Executable file
229
.rclone_repo/fs/config/config_test.go
Executable file
@@ -0,0 +1,229 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/ncw/rclone/fs"
|
||||
"github.com/ncw/rclone/fs/config/obscure"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestCRUD(t *testing.T) {
|
||||
configKey = nil // reset password
|
||||
// create temp config file
|
||||
tempFile, err := ioutil.TempFile("", "crud.conf")
|
||||
assert.NoError(t, err)
|
||||
path := tempFile.Name()
|
||||
defer func() {
|
||||
err := os.Remove(path)
|
||||
assert.NoError(t, err)
|
||||
}()
|
||||
assert.NoError(t, tempFile.Close())
|
||||
|
||||
// temporarily adapt configuration
|
||||
oldOsStdout := os.Stdout
|
||||
oldConfigPath := ConfigPath
|
||||
oldConfig := fs.Config
|
||||
oldConfigFile := configFile
|
||||
oldReadLine := ReadLine
|
||||
os.Stdout = nil
|
||||
ConfigPath = path
|
||||
fs.Config = &fs.ConfigInfo{}
|
||||
configFile = nil
|
||||
defer func() {
|
||||
os.Stdout = oldOsStdout
|
||||
ConfigPath = oldConfigPath
|
||||
ReadLine = oldReadLine
|
||||
fs.Config = oldConfig
|
||||
configFile = oldConfigFile
|
||||
}()
|
||||
|
||||
LoadConfig()
|
||||
assert.Equal(t, []string{}, getConfigData().GetSectionList())
|
||||
|
||||
// Fake a remote
|
||||
fs.Register(&fs.RegInfo{Name: "config_test_remote"})
|
||||
|
||||
// add new remote
|
||||
i := 0
|
||||
ReadLine = func() string {
|
||||
answers := []string{
|
||||
"config_test_remote", // type
|
||||
"y", // looks good, save
|
||||
}
|
||||
i = i + 1
|
||||
return answers[i-1]
|
||||
}
|
||||
|
||||
NewRemote("test")
|
||||
assert.Equal(t, []string{"test"}, configFile.GetSectionList())
|
||||
|
||||
// Reload the config file to workaround this bug
|
||||
// https://github.com/Unknwon/goconfig/issues/39
|
||||
configFile, err = loadConfigFile()
|
||||
require.NoError(t, err)
|
||||
|
||||
// normal rename, test → asdf
|
||||
ReadLine = func() string { return "asdf" }
|
||||
RenameRemote("test")
|
||||
assert.Equal(t, []string{"asdf"}, configFile.GetSectionList())
|
||||
|
||||
// no-op rename, asdf → asdf
|
||||
RenameRemote("asdf")
|
||||
assert.Equal(t, []string{"asdf"}, configFile.GetSectionList())
|
||||
|
||||
// delete remote
|
||||
DeleteRemote("asdf")
|
||||
assert.Equal(t, []string{}, configFile.GetSectionList())
|
||||
}
|
||||
|
||||
// Test some error cases
|
||||
func TestReveal(t *testing.T) {
|
||||
for _, test := range []struct {
|
||||
in string
|
||||
wantErr string
|
||||
}{
|
||||
{"YmJiYmJiYmJiYmJiYmJiYp*gcEWbAw", "base64 decode failed when revealing password - is it obscured?: illegal base64 data at input byte 22"},
|
||||
{"aGVsbG8", "input too short when revealing password - is it obscured?"},
|
||||
{"", "input too short when revealing password - is it obscured?"},
|
||||
} {
|
||||
gotString, gotErr := obscure.Reveal(test.in)
|
||||
assert.Equal(t, "", gotString)
|
||||
assert.Equal(t, test.wantErr, gotErr.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func TestConfigLoad(t *testing.T) {
|
||||
oldConfigPath := ConfigPath
|
||||
ConfigPath = "./testdata/plain.conf"
|
||||
defer func() {
|
||||
ConfigPath = oldConfigPath
|
||||
}()
|
||||
configKey = nil // reset password
|
||||
c, err := loadConfigFile()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
sections := c.GetSectionList()
|
||||
var expect = []string{"RCLONE_ENCRYPT_V0", "nounc", "unc"}
|
||||
assert.Equal(t, expect, sections)
|
||||
|
||||
keys := c.GetKeyList("nounc")
|
||||
expect = []string{"type", "nounc"}
|
||||
assert.Equal(t, expect, keys)
|
||||
}
|
||||
|
||||
func TestConfigLoadEncrypted(t *testing.T) {
|
||||
var err error
|
||||
oldConfigPath := ConfigPath
|
||||
ConfigPath = "./testdata/encrypted.conf"
|
||||
defer func() {
|
||||
ConfigPath = oldConfigPath
|
||||
configKey = nil // reset password
|
||||
}()
|
||||
|
||||
// Set correct password
|
||||
err = setConfigPassword("asdf")
|
||||
require.NoError(t, err)
|
||||
c, err := loadConfigFile()
|
||||
require.NoError(t, err)
|
||||
sections := c.GetSectionList()
|
||||
var expect = []string{"nounc", "unc"}
|
||||
assert.Equal(t, expect, sections)
|
||||
|
||||
keys := c.GetKeyList("nounc")
|
||||
expect = []string{"type", "nounc"}
|
||||
assert.Equal(t, expect, keys)
|
||||
}
|
||||
|
||||
func TestConfigLoadEncryptedFailures(t *testing.T) {
|
||||
var err error
|
||||
|
||||
// This file should be too short to be decoded.
|
||||
oldConfigPath := ConfigPath
|
||||
ConfigPath = "./testdata/enc-short.conf"
|
||||
defer func() { ConfigPath = oldConfigPath }()
|
||||
_, err = loadConfigFile()
|
||||
require.Error(t, err)
|
||||
|
||||
// This file contains invalid base64 characters.
|
||||
ConfigPath = "./testdata/enc-invalid.conf"
|
||||
_, err = loadConfigFile()
|
||||
require.Error(t, err)
|
||||
|
||||
// This file contains invalid base64 characters.
|
||||
ConfigPath = "./testdata/enc-too-new.conf"
|
||||
_, err = loadConfigFile()
|
||||
require.Error(t, err)
|
||||
|
||||
// This file does not exist.
|
||||
ConfigPath = "./testdata/filenotfound.conf"
|
||||
c, err := loadConfigFile()
|
||||
assert.Equal(t, errorConfigFileNotFound, err)
|
||||
assert.Nil(t, c)
|
||||
}
|
||||
|
||||
func TestPassword(t *testing.T) {
|
||||
defer func() {
|
||||
configKey = nil // reset password
|
||||
}()
|
||||
var err error
|
||||
// Empty password should give error
|
||||
err = setConfigPassword(" \t ")
|
||||
require.Error(t, err)
|
||||
|
||||
// Test invalid utf8 sequence
|
||||
err = setConfigPassword(string([]byte{0xff, 0xfe, 0xfd}) + "abc")
|
||||
require.Error(t, err)
|
||||
|
||||
// Simple check of wrong passwords
|
||||
hashedKeyCompare(t, "mis", "match", false)
|
||||
|
||||
// Check that passwords match after unicode normalization
|
||||
hashedKeyCompare(t, "ff\u0041\u030A", "ffÅ", true)
|
||||
|
||||
// Check that passwords preserves case
|
||||
hashedKeyCompare(t, "abcdef", "ABCDEF", false)
|
||||
|
||||
}
|
||||
|
||||
func hashedKeyCompare(t *testing.T, a, b string, shouldMatch bool) {
|
||||
err := setConfigPassword(a)
|
||||
require.NoError(t, err)
|
||||
k1 := configKey
|
||||
|
||||
err = setConfigPassword(b)
|
||||
require.NoError(t, err)
|
||||
k2 := configKey
|
||||
|
||||
if shouldMatch {
|
||||
assert.Equal(t, k1, k2)
|
||||
} else {
|
||||
assert.NotEqual(t, k1, k2)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMatchProvider(t *testing.T) {
|
||||
for _, test := range []struct {
|
||||
config string
|
||||
provider string
|
||||
want bool
|
||||
}{
|
||||
{"", "", true},
|
||||
{"one", "one", true},
|
||||
{"one,two", "two", true},
|
||||
{"one,two,three", "two", true},
|
||||
{"one", "on", false},
|
||||
{"one,two,three", "tw", false},
|
||||
{"!one,two,three", "two", false},
|
||||
{"!one,two,three", "four", true},
|
||||
} {
|
||||
what := fmt.Sprintf("%q,%q", test.config, test.provider)
|
||||
got := matchProvider(test.config, test.provider)
|
||||
assert.Equal(t, test.want, got, what)
|
||||
}
|
||||
}
|
||||
37
.rclone_repo/fs/config/config_unix.go
Executable file
37
.rclone_repo/fs/config/config_unix.go
Executable file
@@ -0,0 +1,37 @@
|
||||
// Read, write and edit the config file
|
||||
// Unix specific functions.
|
||||
|
||||
// +build darwin dragonfly freebsd linux netbsd openbsd solaris
|
||||
|
||||
package config
|
||||
|
||||
import (
|
||||
"os"
|
||||
"os/user"
|
||||
"strconv"
|
||||
"syscall"
|
||||
|
||||
"github.com/ncw/rclone/fs"
|
||||
)
|
||||
|
||||
// attemptCopyGroups tries to keep the group the same. User will be the one
|
||||
// who is currently running this process.
|
||||
func attemptCopyGroup(fromPath, toPath string) {
|
||||
info, err := os.Stat(fromPath)
|
||||
if err != nil || info.Sys() == nil {
|
||||
return
|
||||
}
|
||||
if stat, ok := info.Sys().(*syscall.Stat_t); ok {
|
||||
uid := int(stat.Uid)
|
||||
// prefer self over previous owner of file, because it has a higher chance
|
||||
// of success
|
||||
if user, err := user.Current(); err == nil {
|
||||
if tmpUID, err := strconv.Atoi(user.Uid); err == nil {
|
||||
uid = tmpUID
|
||||
}
|
||||
}
|
||||
if err = os.Chown(toPath, uid, int(stat.Gid)); err != nil {
|
||||
fs.Debugf(nil, "Failed to keep previous owner of config file: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
172
.rclone_repo/fs/config/configflags/configflags.go
Executable file
172
.rclone_repo/fs/config/configflags/configflags.go
Executable file
@@ -0,0 +1,172 @@
|
||||
// Package configflags defines the flags used by rclone. It is
|
||||
// decoupled into a separate package so it can be replaced.
|
||||
package configflags
|
||||
|
||||
// Options set by command line flags
|
||||
import (
|
||||
"log"
|
||||
"net"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/ncw/rclone/fs"
|
||||
"github.com/ncw/rclone/fs/config"
|
||||
"github.com/ncw/rclone/fs/config/flags"
|
||||
"github.com/spf13/pflag"
|
||||
)
|
||||
|
||||
var (
|
||||
// these will get interpreted into fs.Config via SetFlags() below
|
||||
verbose int
|
||||
quiet bool
|
||||
dumpHeaders bool
|
||||
dumpBodies bool
|
||||
deleteBefore bool
|
||||
deleteDuring bool
|
||||
deleteAfter bool
|
||||
bindAddr string
|
||||
disableFeatures string
|
||||
noTraverse bool
|
||||
)
|
||||
|
||||
// AddFlags adds the non filing system specific flags to the command
|
||||
func AddFlags(flagSet *pflag.FlagSet) {
|
||||
// NB defaults which aren't the zero for the type should be set in fs/config.go NewConfig
|
||||
flags.CountVarP(flagSet, &verbose, "verbose", "v", "Print lots more stuff (repeat for more)")
|
||||
flags.BoolVarP(flagSet, &quiet, "quiet", "q", false, "Print as little stuff as possible")
|
||||
flags.DurationVarP(flagSet, &fs.Config.ModifyWindow, "modify-window", "", fs.Config.ModifyWindow, "Max time diff to be considered the same")
|
||||
flags.IntVarP(flagSet, &fs.Config.Checkers, "checkers", "", fs.Config.Checkers, "Number of checkers to run in parallel.")
|
||||
flags.IntVarP(flagSet, &fs.Config.Transfers, "transfers", "", fs.Config.Transfers, "Number of file transfers to run in parallel.")
|
||||
flags.StringVarP(flagSet, &config.ConfigPath, "config", "", config.ConfigPath, "Config file.")
|
||||
flags.StringVarP(flagSet, &config.CacheDir, "cache-dir", "", config.CacheDir, "Directory rclone will use for caching.")
|
||||
flags.BoolVarP(flagSet, &fs.Config.CheckSum, "checksum", "c", fs.Config.CheckSum, "Skip based on checksum & size, not mod-time & size")
|
||||
flags.BoolVarP(flagSet, &fs.Config.SizeOnly, "size-only", "", fs.Config.SizeOnly, "Skip based on size only, not mod-time or checksum")
|
||||
flags.BoolVarP(flagSet, &fs.Config.IgnoreTimes, "ignore-times", "I", fs.Config.IgnoreTimes, "Don't skip files that match size and time - transfer all files")
|
||||
flags.BoolVarP(flagSet, &fs.Config.IgnoreExisting, "ignore-existing", "", fs.Config.IgnoreExisting, "Skip all files that exist on destination")
|
||||
flags.BoolVarP(flagSet, &fs.Config.IgnoreErrors, "ignore-errors", "", fs.Config.IgnoreErrors, "delete even if there are I/O errors")
|
||||
flags.BoolVarP(flagSet, &fs.Config.DryRun, "dry-run", "n", fs.Config.DryRun, "Do a trial run with no permanent changes")
|
||||
flags.DurationVarP(flagSet, &fs.Config.ConnectTimeout, "contimeout", "", fs.Config.ConnectTimeout, "Connect timeout")
|
||||
flags.DurationVarP(flagSet, &fs.Config.Timeout, "timeout", "", fs.Config.Timeout, "IO idle timeout")
|
||||
flags.BoolVarP(flagSet, &dumpHeaders, "dump-headers", "", false, "Dump HTTP bodies - may contain sensitive info")
|
||||
flags.BoolVarP(flagSet, &dumpBodies, "dump-bodies", "", false, "Dump HTTP headers and bodies - may contain sensitive info")
|
||||
flags.BoolVarP(flagSet, &fs.Config.InsecureSkipVerify, "no-check-certificate", "", fs.Config.InsecureSkipVerify, "Do not verify the server SSL certificate. Insecure.")
|
||||
flags.BoolVarP(flagSet, &fs.Config.AskPassword, "ask-password", "", fs.Config.AskPassword, "Allow prompt for password for encrypted configuration.")
|
||||
flags.BoolVarP(flagSet, &deleteBefore, "delete-before", "", false, "When synchronizing, delete files on destination before transfering")
|
||||
flags.BoolVarP(flagSet, &deleteDuring, "delete-during", "", false, "When synchronizing, delete files during transfer")
|
||||
flags.BoolVarP(flagSet, &deleteAfter, "delete-after", "", false, "When synchronizing, delete files on destination after transfering (default)")
|
||||
flags.IntVar64P(flagSet, &fs.Config.MaxDelete, "max-delete", "", -1, "When synchronizing, limit the number of deletes")
|
||||
flags.BoolVarP(flagSet, &fs.Config.TrackRenames, "track-renames", "", fs.Config.TrackRenames, "When synchronizing, track file renames and do a server side move if possible")
|
||||
flags.IntVarP(flagSet, &fs.Config.LowLevelRetries, "low-level-retries", "", fs.Config.LowLevelRetries, "Number of low level retries to do.")
|
||||
flags.BoolVarP(flagSet, &fs.Config.UpdateOlder, "update", "u", fs.Config.UpdateOlder, "Skip files that are newer on the destination.")
|
||||
flags.BoolVarP(flagSet, &fs.Config.UseServerModTime, "use-server-modtime", "", fs.Config.UseServerModTime, "Use server modified time instead of object metadata")
|
||||
flags.BoolVarP(flagSet, &fs.Config.NoGzip, "no-gzip-encoding", "", fs.Config.NoGzip, "Don't set Accept-Encoding: gzip.")
|
||||
flags.IntVarP(flagSet, &fs.Config.MaxDepth, "max-depth", "", fs.Config.MaxDepth, "If set limits the recursion depth to this.")
|
||||
flags.BoolVarP(flagSet, &fs.Config.IgnoreSize, "ignore-size", "", false, "Ignore size when skipping use mod-time or checksum.")
|
||||
flags.BoolVarP(flagSet, &fs.Config.IgnoreChecksum, "ignore-checksum", "", fs.Config.IgnoreChecksum, "Skip post copy check of checksums.")
|
||||
flags.BoolVarP(flagSet, &noTraverse, "no-traverse", "", noTraverse, "Obsolete - does nothing.")
|
||||
flags.BoolVarP(flagSet, &fs.Config.NoUpdateModTime, "no-update-modtime", "", fs.Config.NoUpdateModTime, "Don't update destination mod-time if files identical.")
|
||||
flags.StringVarP(flagSet, &fs.Config.BackupDir, "backup-dir", "", fs.Config.BackupDir, "Make backups into hierarchy based in DIR.")
|
||||
flags.StringVarP(flagSet, &fs.Config.Suffix, "suffix", "", fs.Config.Suffix, "Suffix for use with --backup-dir.")
|
||||
flags.BoolVarP(flagSet, &fs.Config.UseListR, "fast-list", "", fs.Config.UseListR, "Use recursive list if available. Uses more memory but fewer transactions.")
|
||||
flags.Float64VarP(flagSet, &fs.Config.TPSLimit, "tpslimit", "", fs.Config.TPSLimit, "Limit HTTP transactions per second to this.")
|
||||
flags.IntVarP(flagSet, &fs.Config.TPSLimitBurst, "tpslimit-burst", "", fs.Config.TPSLimitBurst, "Max burst of transactions for --tpslimit.")
|
||||
flags.StringVarP(flagSet, &bindAddr, "bind", "", "", "Local address to bind to for outgoing connections, IPv4, IPv6 or name.")
|
||||
flags.StringVarP(flagSet, &disableFeatures, "disable", "", "", "Disable a comma separated list of features. Use help to see a list.")
|
||||
flags.StringVarP(flagSet, &fs.Config.UserAgent, "user-agent", "", fs.Config.UserAgent, "Set the user-agent to a specified string. The default is rclone/ version")
|
||||
flags.BoolVarP(flagSet, &fs.Config.Immutable, "immutable", "", fs.Config.Immutable, "Do not modify files. Fail if existing files have been modified.")
|
||||
flags.BoolVarP(flagSet, &fs.Config.AutoConfirm, "auto-confirm", "", fs.Config.AutoConfirm, "If enabled, do not request console confirmation.")
|
||||
flags.IntVarP(flagSet, &fs.Config.StatsFileNameLength, "stats-file-name-length", "", fs.Config.StatsFileNameLength, "Max file name length in stats. 0 for no limit")
|
||||
flags.FVarP(flagSet, &fs.Config.LogLevel, "log-level", "", "Log level DEBUG|INFO|NOTICE|ERROR")
|
||||
flags.FVarP(flagSet, &fs.Config.StatsLogLevel, "stats-log-level", "", "Log level to show --stats output DEBUG|INFO|NOTICE|ERROR")
|
||||
flags.FVarP(flagSet, &fs.Config.BwLimit, "bwlimit", "", "Bandwidth limit in kBytes/s, or use suffix b|k|M|G or a full timetable.")
|
||||
flags.FVarP(flagSet, &fs.Config.BufferSize, "buffer-size", "", "In memory buffer size when reading files for each --transfer.")
|
||||
flags.FVarP(flagSet, &fs.Config.StreamingUploadCutoff, "streaming-upload-cutoff", "", "Cutoff for switching to chunked upload if file size is unknown. Upload starts after reaching cutoff or when file ends.")
|
||||
flags.FVarP(flagSet, &fs.Config.Dump, "dump", "", "List of items to dump from: "+fs.DumpFlagsList)
|
||||
flags.FVarP(flagSet, &fs.Config.MaxTransfer, "max-transfer", "", "Maximum size of data to transfer.")
|
||||
flags.IntVarP(flagSet, &fs.Config.MaxBacklog, "max-backlog", "", fs.Config.MaxBacklog, "Maximum number of objects in sync or check backlog.")
|
||||
flags.BoolVarP(flagSet, &fs.Config.StatsOneLine, "stats-one-line", "", fs.Config.StatsOneLine, "Make the stats fit on one line.")
|
||||
flags.BoolVarP(flagSet, &fs.Config.Progress, "progress", "P", fs.Config.Progress, "Show progress during transfer.")
|
||||
}
|
||||
|
||||
// SetFlags converts any flags into config which weren't straight foward
|
||||
func SetFlags() {
|
||||
if verbose >= 2 {
|
||||
fs.Config.LogLevel = fs.LogLevelDebug
|
||||
} else if verbose >= 1 {
|
||||
fs.Config.LogLevel = fs.LogLevelInfo
|
||||
}
|
||||
if quiet {
|
||||
if verbose > 0 {
|
||||
log.Fatalf("Can't set -v and -q")
|
||||
}
|
||||
fs.Config.LogLevel = fs.LogLevelError
|
||||
}
|
||||
logLevelFlag := pflag.Lookup("log-level")
|
||||
if logLevelFlag != nil && logLevelFlag.Changed {
|
||||
if verbose > 0 {
|
||||
log.Fatalf("Can't set -v and --log-level")
|
||||
}
|
||||
if quiet {
|
||||
log.Fatalf("Can't set -q and --log-level")
|
||||
}
|
||||
}
|
||||
|
||||
if noTraverse {
|
||||
fs.Logf(nil, "--no-traverse is obsolete and no longer needed - please remove")
|
||||
}
|
||||
|
||||
if dumpHeaders {
|
||||
fs.Config.Dump |= fs.DumpHeaders
|
||||
fs.Logf(nil, "--dump-headers is obsolete - please use --dump headers instead")
|
||||
}
|
||||
if dumpBodies {
|
||||
fs.Config.Dump |= fs.DumpBodies
|
||||
fs.Logf(nil, "--dump-bodies is obsolete - please use --dump bodies instead")
|
||||
}
|
||||
|
||||
switch {
|
||||
case deleteBefore && (deleteDuring || deleteAfter),
|
||||
deleteDuring && deleteAfter:
|
||||
log.Fatalf(`Only one of --delete-before, --delete-during or --delete-after can be used.`)
|
||||
case deleteBefore:
|
||||
fs.Config.DeleteMode = fs.DeleteModeBefore
|
||||
case deleteDuring:
|
||||
fs.Config.DeleteMode = fs.DeleteModeDuring
|
||||
case deleteAfter:
|
||||
fs.Config.DeleteMode = fs.DeleteModeAfter
|
||||
default:
|
||||
fs.Config.DeleteMode = fs.DeleteModeDefault
|
||||
}
|
||||
|
||||
if fs.Config.IgnoreSize && fs.Config.SizeOnly {
|
||||
log.Fatalf(`Can't use --size-only and --ignore-size together.`)
|
||||
}
|
||||
|
||||
if fs.Config.Suffix != "" && fs.Config.BackupDir == "" {
|
||||
log.Fatalf(`Can only use --suffix with --backup-dir.`)
|
||||
}
|
||||
|
||||
if bindAddr != "" {
|
||||
addrs, err := net.LookupIP(bindAddr)
|
||||
if err != nil {
|
||||
log.Fatalf("--bind: Failed to parse %q as IP address: %v", bindAddr, err)
|
||||
}
|
||||
if len(addrs) != 1 {
|
||||
log.Fatalf("--bind: Expecting 1 IP address for %q but got %d", bindAddr, len(addrs))
|
||||
}
|
||||
fs.Config.BindAddr = addrs[0]
|
||||
}
|
||||
|
||||
if disableFeatures != "" {
|
||||
if disableFeatures == "help" {
|
||||
log.Fatalf("Possible backend features are: %s\n", strings.Join(new(fs.Features).List(), ", "))
|
||||
}
|
||||
fs.Config.DisableFeatures = strings.Split(disableFeatures, ",")
|
||||
}
|
||||
|
||||
// Make the config file absolute
|
||||
configPath, err := filepath.Abs(config.ConfigPath)
|
||||
if err == nil {
|
||||
config.ConfigPath = configPath
|
||||
}
|
||||
}
|
||||
86
.rclone_repo/fs/config/configmap/configmap.go
Executable file
86
.rclone_repo/fs/config/configmap/configmap.go
Executable file
@@ -0,0 +1,86 @@
|
||||
// Package configmap provides an abstraction for reading and writing config
|
||||
package configmap
|
||||
|
||||
// Getter provides an interface to get config items
|
||||
type Getter interface {
|
||||
// Get should get an item with the key passed in and return
|
||||
// the value. If the item is found then it should return true,
|
||||
// otherwise false.
|
||||
Get(key string) (value string, ok bool)
|
||||
}
|
||||
|
||||
// Setter provides an interface to set config items
|
||||
type Setter interface {
|
||||
// Set should set an item into persistent config store.
|
||||
Set(key, value string)
|
||||
}
|
||||
|
||||
// Mapper provides an interface to read and write config
|
||||
type Mapper interface {
|
||||
Getter
|
||||
Setter
|
||||
}
|
||||
|
||||
// Map provides a wrapper around multiple Setter and
|
||||
// Getter interfaces.
|
||||
type Map struct {
|
||||
setters []Setter
|
||||
getters []Getter
|
||||
}
|
||||
|
||||
// New returns an empty Map
|
||||
func New() *Map {
|
||||
return &Map{}
|
||||
}
|
||||
|
||||
// AddGetter appends a getter onto the end of the getters
|
||||
func (c *Map) AddGetter(getter Getter) *Map {
|
||||
c.getters = append(c.getters, getter)
|
||||
return c
|
||||
}
|
||||
|
||||
// AddGetters appends multiple getters onto the end of the getters
|
||||
func (c *Map) AddGetters(getters ...Getter) *Map {
|
||||
c.getters = append(c.getters, getters...)
|
||||
return c
|
||||
}
|
||||
|
||||
// AddSetter appends a setter onto the end of the setters
|
||||
func (c *Map) AddSetter(setter Setter) *Map {
|
||||
c.setters = append(c.setters, setter)
|
||||
return c
|
||||
}
|
||||
|
||||
// Get gets an item with the key passed in and return the value from
|
||||
// the first getter. If the item is found then it returns true,
|
||||
// otherwise false.
|
||||
func (c *Map) Get(key string) (value string, ok bool) {
|
||||
for _, do := range c.getters {
|
||||
value, ok = do.Get(key)
|
||||
if ok {
|
||||
return value, ok
|
||||
}
|
||||
}
|
||||
return "", false
|
||||
}
|
||||
|
||||
// Set sets an item into all the stored setters.
|
||||
func (c *Map) Set(key, value string) {
|
||||
for _, do := range c.setters {
|
||||
do.Set(key, value)
|
||||
}
|
||||
}
|
||||
|
||||
// Simple is a simple Mapper for testing
|
||||
type Simple map[string]string
|
||||
|
||||
// Get the value
|
||||
func (c Simple) Get(key string) (value string, ok bool) {
|
||||
value, ok = c[key]
|
||||
return value, ok
|
||||
}
|
||||
|
||||
// Set the value
|
||||
func (c Simple) Set(key, value string) {
|
||||
c[key] = value
|
||||
}
|
||||
91
.rclone_repo/fs/config/configmap/configmap_test.go
Executable file
91
.rclone_repo/fs/config/configmap/configmap_test.go
Executable file
@@ -0,0 +1,91 @@
|
||||
package configmap
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
var (
|
||||
_ Mapper = Simple(nil)
|
||||
_ Getter = Simple(nil)
|
||||
_ Setter = Simple(nil)
|
||||
)
|
||||
|
||||
func TestConfigMapGet(t *testing.T) {
|
||||
m := New()
|
||||
|
||||
value, found := m.Get("config1")
|
||||
assert.Equal(t, "", value)
|
||||
assert.Equal(t, false, found)
|
||||
|
||||
value, found = m.Get("config2")
|
||||
assert.Equal(t, "", value)
|
||||
assert.Equal(t, false, found)
|
||||
|
||||
m1 := Simple{
|
||||
"config1": "one",
|
||||
}
|
||||
|
||||
m.AddGetter(m1)
|
||||
|
||||
value, found = m.Get("config1")
|
||||
assert.Equal(t, "one", value)
|
||||
assert.Equal(t, true, found)
|
||||
|
||||
value, found = m.Get("config2")
|
||||
assert.Equal(t, "", value)
|
||||
assert.Equal(t, false, found)
|
||||
|
||||
m2 := Simple{
|
||||
"config1": "one2",
|
||||
"config2": "two2",
|
||||
}
|
||||
|
||||
m.AddGetter(m2)
|
||||
|
||||
value, found = m.Get("config1")
|
||||
assert.Equal(t, "one", value)
|
||||
assert.Equal(t, true, found)
|
||||
|
||||
value, found = m.Get("config2")
|
||||
assert.Equal(t, "two2", value)
|
||||
assert.Equal(t, true, found)
|
||||
|
||||
}
|
||||
|
||||
func TestConfigMapSet(t *testing.T) {
|
||||
m := New()
|
||||
|
||||
m1 := Simple{
|
||||
"config1": "one",
|
||||
}
|
||||
m2 := Simple{
|
||||
"config1": "one2",
|
||||
"config2": "two2",
|
||||
}
|
||||
|
||||
m.AddSetter(m1).AddSetter(m2)
|
||||
|
||||
m.Set("config2", "potato")
|
||||
|
||||
assert.Equal(t, Simple{
|
||||
"config1": "one",
|
||||
"config2": "potato",
|
||||
}, m1)
|
||||
assert.Equal(t, Simple{
|
||||
"config1": "one2",
|
||||
"config2": "potato",
|
||||
}, m2)
|
||||
|
||||
m.Set("config1", "beetroot")
|
||||
|
||||
assert.Equal(t, Simple{
|
||||
"config1": "beetroot",
|
||||
"config2": "potato",
|
||||
}, m1)
|
||||
assert.Equal(t, Simple{
|
||||
"config1": "beetroot",
|
||||
"config2": "potato",
|
||||
}, m2)
|
||||
}
|
||||
127
.rclone_repo/fs/config/configstruct/configstruct.go
Executable file
127
.rclone_repo/fs/config/configstruct/configstruct.go
Executable file
@@ -0,0 +1,127 @@
|
||||
// Package configstruct parses unstructured maps into structures
|
||||
package configstruct
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/ncw/rclone/fs/config/configmap"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
var matchUpper = regexp.MustCompile("([A-Z]+)")
|
||||
|
||||
// camelToSnake converts CamelCase to snake_case
|
||||
func camelToSnake(in string) string {
|
||||
out := matchUpper.ReplaceAllString(in, "_$1")
|
||||
out = strings.ToLower(out)
|
||||
out = strings.Trim(out, "_")
|
||||
return out
|
||||
}
|
||||
|
||||
// StringToInterface turns in into an interface{} the same type as def
|
||||
func StringToInterface(def interface{}, in string) (newValue interface{}, err error) {
|
||||
typ := reflect.TypeOf(def)
|
||||
switch typ.Kind() {
|
||||
case reflect.String:
|
||||
// Pass strings unmodified
|
||||
return in, nil
|
||||
}
|
||||
// Otherwise parse with Sscanln
|
||||
//
|
||||
// This means any types we use here must implement fmt.Scanner
|
||||
o := reflect.New(typ)
|
||||
n, err := fmt.Sscanln(in, o.Interface())
|
||||
if err != nil {
|
||||
return newValue, errors.Wrapf(err, "parsing %q as %T failed", in, def)
|
||||
}
|
||||
if n != 1 {
|
||||
return newValue, errors.New("no items parsed")
|
||||
}
|
||||
return o.Elem().Interface(), nil
|
||||
}
|
||||
|
||||
// Item descripts a single entry in the options structure
|
||||
type Item struct {
|
||||
Name string // snake_case
|
||||
Field string // CamelCase
|
||||
Num int // number of the field in the struct
|
||||
Value interface{}
|
||||
}
|
||||
|
||||
// Items parses the opt struct and returns a slice of Item objects.
|
||||
//
|
||||
// opt must be a pointer to a struct. The struct should have entirely
|
||||
// public fields.
|
||||
//
|
||||
// The config_name is looked up in a struct tag called "config" or if
|
||||
// not found is the field name converted from CamelCase to snake_case.
|
||||
func Items(opt interface{}) (items []Item, err error) {
|
||||
def := reflect.ValueOf(opt)
|
||||
if def.Kind() != reflect.Ptr {
|
||||
return nil, errors.New("argument must be a pointer")
|
||||
}
|
||||
def = def.Elem() // indirect the pointer
|
||||
if def.Kind() != reflect.Struct {
|
||||
return nil, errors.New("argument must be a pointer to a struct")
|
||||
}
|
||||
defType := def.Type()
|
||||
for i := 0; i < def.NumField(); i++ {
|
||||
field := defType.Field(i)
|
||||
fieldName := field.Name
|
||||
configName, ok := field.Tag.Lookup("config")
|
||||
if !ok {
|
||||
configName = camelToSnake(fieldName)
|
||||
}
|
||||
defaultItem := Item{
|
||||
Name: configName,
|
||||
Field: fieldName,
|
||||
Num: i,
|
||||
Value: def.Field(i).Interface(),
|
||||
}
|
||||
items = append(items, defaultItem)
|
||||
}
|
||||
return items, nil
|
||||
}
|
||||
|
||||
// Set interprets the field names in defaults and looks up config
|
||||
// values in the config passed in. Any values found in config will be
|
||||
// set in the opt structure.
|
||||
//
|
||||
// opt must be a pointer to a struct. The struct should have entirely
|
||||
// public fields. The field names are converted from CamelCase to
|
||||
// snake_case and looked up in the config supplied or a
|
||||
// `config:"field_name"` is looked up.
|
||||
//
|
||||
// If items are found then they are converted from string to native
|
||||
// types and set in opt.
|
||||
//
|
||||
// All the field types in the struct must implement fmt.Scanner.
|
||||
func Set(config configmap.Getter, opt interface{}) (err error) {
|
||||
defaultItems, err := Items(opt)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defStruct := reflect.ValueOf(opt).Elem()
|
||||
for _, defaultItem := range defaultItems {
|
||||
newValue := defaultItem.Value
|
||||
if configValue, ok := config.Get(defaultItem.Name); ok {
|
||||
var newNewValue interface{}
|
||||
newNewValue, err = StringToInterface(newValue, configValue)
|
||||
if err != nil {
|
||||
// Mask errors if setting an empty string as
|
||||
// it isn't valid for all types. This makes
|
||||
// empty string be the equivalent of unset.
|
||||
if configValue != "" {
|
||||
return errors.Wrapf(err, "couldn't parse config item %q = %q as %T", defaultItem.Name, configValue, defaultItem.Value)
|
||||
}
|
||||
} else {
|
||||
newValue = newNewValue
|
||||
}
|
||||
}
|
||||
defStruct.Field(defaultItem.Num).Set(reflect.ValueOf(newValue))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
116
.rclone_repo/fs/config/configstruct/configstruct_test.go
Executable file
116
.rclone_repo/fs/config/configstruct/configstruct_test.go
Executable file
@@ -0,0 +1,116 @@
|
||||
package configstruct_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/ncw/rclone/fs"
|
||||
"github.com/ncw/rclone/fs/config/configstruct"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
type conf struct {
|
||||
A string
|
||||
B string
|
||||
}
|
||||
|
||||
type conf2 struct {
|
||||
PotatoPie string `config:"spud_pie"`
|
||||
BeanStew bool
|
||||
RaisinRoll int
|
||||
SausageOnStick int64
|
||||
ForbiddenFruit uint
|
||||
CookingTime fs.Duration
|
||||
TotalWeight fs.SizeSuffix
|
||||
}
|
||||
|
||||
func TestItemsError(t *testing.T) {
|
||||
_, err := configstruct.Items(nil)
|
||||
assert.EqualError(t, err, "argument must be a pointer")
|
||||
_, err = configstruct.Items(new(int))
|
||||
assert.EqualError(t, err, "argument must be a pointer to a struct")
|
||||
}
|
||||
|
||||
func TestItems(t *testing.T) {
|
||||
in := &conf2{
|
||||
PotatoPie: "yum",
|
||||
BeanStew: true,
|
||||
RaisinRoll: 42,
|
||||
SausageOnStick: 101,
|
||||
ForbiddenFruit: 6,
|
||||
CookingTime: fs.Duration(42 * time.Second),
|
||||
TotalWeight: fs.SizeSuffix(17 << 20),
|
||||
}
|
||||
got, err := configstruct.Items(in)
|
||||
require.NoError(t, err)
|
||||
want := []configstruct.Item{
|
||||
{Name: "spud_pie", Field: "PotatoPie", Num: 0, Value: string("yum")},
|
||||
{Name: "bean_stew", Field: "BeanStew", Num: 1, Value: true},
|
||||
{Name: "raisin_roll", Field: "RaisinRoll", Num: 2, Value: int(42)},
|
||||
{Name: "sausage_on_stick", Field: "SausageOnStick", Num: 3, Value: int64(101)},
|
||||
{Name: "forbidden_fruit", Field: "ForbiddenFruit", Num: 4, Value: uint(6)},
|
||||
{Name: "cooking_time", Field: "CookingTime", Num: 5, Value: fs.Duration(42 * time.Second)},
|
||||
{Name: "total_weight", Field: "TotalWeight", Num: 6, Value: fs.SizeSuffix(17 << 20)},
|
||||
}
|
||||
assert.Equal(t, want, got)
|
||||
}
|
||||
|
||||
func TestSetBasics(t *testing.T) {
|
||||
c := &conf{A: "one", B: "two"}
|
||||
err := configstruct.Set(configMap{}, c)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, &conf{A: "one", B: "two"}, c)
|
||||
}
|
||||
|
||||
// a simple configmap.Getter for testing
|
||||
type configMap map[string]string
|
||||
|
||||
// Get the value
|
||||
func (c configMap) Get(key string) (value string, ok bool) {
|
||||
value, ok = c[key]
|
||||
return value, ok
|
||||
}
|
||||
|
||||
func TestSetMore(t *testing.T) {
|
||||
c := &conf{A: "one", B: "two"}
|
||||
m := configMap{
|
||||
"a": "ONE",
|
||||
}
|
||||
err := configstruct.Set(m, c)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, &conf{A: "ONE", B: "two"}, c)
|
||||
}
|
||||
|
||||
func TestSetFull(t *testing.T) {
|
||||
in := &conf2{
|
||||
PotatoPie: "yum",
|
||||
BeanStew: true,
|
||||
RaisinRoll: 42,
|
||||
SausageOnStick: 101,
|
||||
ForbiddenFruit: 6,
|
||||
CookingTime: fs.Duration(42 * time.Second),
|
||||
TotalWeight: fs.SizeSuffix(17 << 20),
|
||||
}
|
||||
m := configMap{
|
||||
"spud_pie": "YUM",
|
||||
"bean_stew": "FALSE",
|
||||
"raisin_roll": "43 ",
|
||||
"sausage_on_stick": " 102 ",
|
||||
"forbidden_fruit": "0x7",
|
||||
"cooking_time": "43s",
|
||||
"total_weight": "18M",
|
||||
}
|
||||
want := &conf2{
|
||||
PotatoPie: "YUM",
|
||||
BeanStew: false,
|
||||
RaisinRoll: 43,
|
||||
SausageOnStick: 102,
|
||||
ForbiddenFruit: 7,
|
||||
CookingTime: fs.Duration(43 * time.Second),
|
||||
TotalWeight: fs.SizeSuffix(18 << 20),
|
||||
}
|
||||
err := configstruct.Set(m, in)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, want, in)
|
||||
}
|
||||
60
.rclone_repo/fs/config/configstruct/internal_test.go
Executable file
60
.rclone_repo/fs/config/configstruct/internal_test.go
Executable file
@@ -0,0 +1,60 @@
|
||||
package configstruct
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestCamelToSnake(t *testing.T) {
|
||||
for _, test := range []struct {
|
||||
in string
|
||||
want string
|
||||
}{
|
||||
{"", ""},
|
||||
{"Type", "type"},
|
||||
{"AuthVersion", "auth_version"},
|
||||
{"AccessKeyID", "access_key_id"},
|
||||
} {
|
||||
got := camelToSnake(test.in)
|
||||
assert.Equal(t, test.want, got, test.in)
|
||||
}
|
||||
}
|
||||
|
||||
func TestStringToInterface(t *testing.T) {
|
||||
item := struct{ A int }{2}
|
||||
for _, test := range []struct {
|
||||
in string
|
||||
def interface{}
|
||||
want interface{}
|
||||
err string
|
||||
}{
|
||||
{"", string(""), "", ""},
|
||||
{" string ", string(""), " string ", ""},
|
||||
{"123", int(0), int(123), ""},
|
||||
{"0x123", int(0), int(0x123), ""},
|
||||
{" 0x123 ", int(0), int(0x123), ""},
|
||||
{"-123", int(0), int(-123), ""},
|
||||
{"0", false, false, ""},
|
||||
{"1", false, true, ""},
|
||||
{"FALSE", false, false, ""},
|
||||
{"true", false, true, ""},
|
||||
{"123", uint(0), uint(123), ""},
|
||||
{"123", int64(0), int64(123), ""},
|
||||
{"123x", int64(0), nil, "parsing \"123x\" as int64 failed: expected newline"},
|
||||
{"truth", false, nil, "parsing \"truth\" as bool failed: syntax error scanning boolean"},
|
||||
{"struct", item, nil, "parsing \"struct\" as struct { A int } failed: can't scan type: *struct { A int }"},
|
||||
} {
|
||||
what := fmt.Sprintf("parse %q as %T", test.in, test.def)
|
||||
got, err := StringToInterface(test.def, test.in)
|
||||
if test.err == "" {
|
||||
require.NoError(t, err, what)
|
||||
assert.Equal(t, test.want, got, what)
|
||||
} else {
|
||||
assert.Nil(t, got)
|
||||
assert.EqualError(t, err, test.err, what)
|
||||
}
|
||||
}
|
||||
}
|
||||
195
.rclone_repo/fs/config/flags/flags.go
Executable file
195
.rclone_repo/fs/config/flags/flags.go
Executable file
@@ -0,0 +1,195 @@
|
||||
// Package flags contains enahnced versions of spf13/pflag flag
|
||||
// routines which will read from the environment also.
|
||||
package flags
|
||||
|
||||
import (
|
||||
"log"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/ncw/rclone/fs"
|
||||
"github.com/spf13/pflag"
|
||||
)
|
||||
|
||||
// setDefaultFromEnv constructs a name from the flag passed in and
|
||||
// sets the default from the environment if possible.
|
||||
func setDefaultFromEnv(name string) {
|
||||
key := fs.OptionToEnv(name)
|
||||
newValue, found := os.LookupEnv(key)
|
||||
if found {
|
||||
flag := pflag.Lookup(name)
|
||||
if flag == nil {
|
||||
log.Fatalf("Couldn't find flag %q", name)
|
||||
}
|
||||
err := flag.Value.Set(newValue)
|
||||
if err != nil {
|
||||
log.Fatalf("Invalid value for environment variable %q: %v", key, err)
|
||||
}
|
||||
fs.Debugf(nil, "Set default for %q from %q to %q (%v)", name, key, newValue, flag.Value)
|
||||
flag.DefValue = newValue
|
||||
}
|
||||
}
|
||||
|
||||
// StringP defines a flag which can be overridden by an environment variable
|
||||
//
|
||||
// It is a thin wrapper around pflag.StringP
|
||||
func StringP(name, shorthand string, value string, usage string) (out *string) {
|
||||
out = pflag.StringP(name, shorthand, value, usage)
|
||||
setDefaultFromEnv(name)
|
||||
return out
|
||||
}
|
||||
|
||||
// StringVarP defines a flag which can be overridden by an environment variable
|
||||
//
|
||||
// It is a thin wrapper around pflag.StringVarP
|
||||
func StringVarP(flags *pflag.FlagSet, p *string, name, shorthand string, value string, usage string) {
|
||||
flags.StringVarP(p, name, shorthand, value, usage)
|
||||
setDefaultFromEnv(name)
|
||||
}
|
||||
|
||||
// BoolP defines a flag which can be overridden by an environment variable
|
||||
//
|
||||
// It is a thin wrapper around pflag.BoolP
|
||||
func BoolP(name, shorthand string, value bool, usage string) (out *bool) {
|
||||
out = pflag.BoolP(name, shorthand, value, usage)
|
||||
setDefaultFromEnv(name)
|
||||
return out
|
||||
}
|
||||
|
||||
// BoolVarP defines a flag which can be overridden by an environment variable
|
||||
//
|
||||
// It is a thin wrapper around pflag.BoolVarP
|
||||
func BoolVarP(flags *pflag.FlagSet, p *bool, name, shorthand string, value bool, usage string) {
|
||||
flags.BoolVarP(p, name, shorthand, value, usage)
|
||||
setDefaultFromEnv(name)
|
||||
}
|
||||
|
||||
// IntP defines a flag which can be overridden by an environment variable
|
||||
//
|
||||
// It is a thin wrapper around pflag.IntP
|
||||
func IntP(name, shorthand string, value int, usage string) (out *int) {
|
||||
out = pflag.IntP(name, shorthand, value, usage)
|
||||
setDefaultFromEnv(name)
|
||||
return out
|
||||
}
|
||||
|
||||
// Int64P defines a flag which can be overridden by an environment variable
|
||||
//
|
||||
// It is a thin wrapper around pflag.IntP
|
||||
func Int64P(name, shorthand string, value int64, usage string) (out *int64) {
|
||||
out = pflag.Int64P(name, shorthand, value, usage)
|
||||
setDefaultFromEnv(name)
|
||||
return out
|
||||
}
|
||||
|
||||
// IntVar64P defines a flag which can be overridden by an environment variable
|
||||
//
|
||||
// It is a thin wrapper around pflag.Int64VarP
|
||||
func IntVar64P(flags *pflag.FlagSet, p *int64, name, shorthand string, value int64, usage string) {
|
||||
flags.Int64VarP(p, name, shorthand, value, usage)
|
||||
setDefaultFromEnv(name)
|
||||
}
|
||||
|
||||
// IntVarP defines a flag which can be overridden by an environment variable
|
||||
//
|
||||
// It is a thin wrapper around pflag.IntVarP
|
||||
func IntVarP(flags *pflag.FlagSet, p *int, name, shorthand string, value int, usage string) {
|
||||
flags.IntVarP(p, name, shorthand, value, usage)
|
||||
setDefaultFromEnv(name)
|
||||
}
|
||||
|
||||
// Uint32VarP defines a flag which can be overridden by an environment variable
|
||||
//
|
||||
// It is a thin wrapper around pflag.Uint32VarP
|
||||
func Uint32VarP(flags *pflag.FlagSet, p *uint32, name, shorthand string, value uint32, usage string) {
|
||||
flags.Uint32VarP(p, name, shorthand, value, usage)
|
||||
setDefaultFromEnv(name)
|
||||
}
|
||||
|
||||
// Float64P defines a flag which can be overridden by an environment variable
|
||||
//
|
||||
// It is a thin wrapper around pflag.Float64P
|
||||
func Float64P(name, shorthand string, value float64, usage string) (out *float64) {
|
||||
out = pflag.Float64P(name, shorthand, value, usage)
|
||||
setDefaultFromEnv(name)
|
||||
return out
|
||||
}
|
||||
|
||||
// Float64VarP defines a flag which can be overridden by an environment variable
|
||||
//
|
||||
// It is a thin wrapper around pflag.Float64VarP
|
||||
func Float64VarP(flags *pflag.FlagSet, p *float64, name, shorthand string, value float64, usage string) {
|
||||
flags.Float64VarP(p, name, shorthand, value, usage)
|
||||
setDefaultFromEnv(name)
|
||||
}
|
||||
|
||||
// DurationP defines a flag which can be overridden by an environment variable
|
||||
//
|
||||
// It is a thin wrapper around pflag.DurationP
|
||||
func DurationP(name, shorthand string, value time.Duration, usage string) (out *time.Duration) {
|
||||
out = pflag.DurationP(name, shorthand, value, usage)
|
||||
setDefaultFromEnv(name)
|
||||
return out
|
||||
}
|
||||
|
||||
// DurationVarP defines a flag which can be overridden by an environment variable
|
||||
//
|
||||
// It is a thin wrapper around pflag.DurationVarP
|
||||
func DurationVarP(flags *pflag.FlagSet, p *time.Duration, name, shorthand string, value time.Duration, usage string) {
|
||||
flags.DurationVarP(p, name, shorthand, value, usage)
|
||||
setDefaultFromEnv(name)
|
||||
}
|
||||
|
||||
// VarP defines a flag which can be overridden by an environment variable
|
||||
//
|
||||
// It is a thin wrapper around pflag.VarP
|
||||
func VarP(value pflag.Value, name, shorthand, usage string) {
|
||||
pflag.VarP(value, name, shorthand, usage)
|
||||
setDefaultFromEnv(name)
|
||||
}
|
||||
|
||||
// FVarP defines a flag which can be overridden by an environment variable
|
||||
//
|
||||
// It is a thin wrapper around pflag.VarP
|
||||
func FVarP(flags *pflag.FlagSet, value pflag.Value, name, shorthand, usage string) {
|
||||
flags.VarP(value, name, shorthand, usage)
|
||||
setDefaultFromEnv(name)
|
||||
}
|
||||
|
||||
// StringArrayP defines a flag which can be overridden by an environment variable
|
||||
//
|
||||
// It sets one value only - command line flags can be used to set more.
|
||||
//
|
||||
// It is a thin wrapper around pflag.StringArrayP
|
||||
func StringArrayP(name, shorthand string, value []string, usage string) (out *[]string) {
|
||||
out = pflag.StringArrayP(name, shorthand, value, usage)
|
||||
setDefaultFromEnv(name)
|
||||
return out
|
||||
}
|
||||
|
||||
// StringArrayVarP defines a flag which can be overridden by an environment variable
|
||||
//
|
||||
// It sets one value only - command line flags can be used to set more.
|
||||
//
|
||||
// It is a thin wrapper around pflag.StringArrayVarP
|
||||
func StringArrayVarP(flags *pflag.FlagSet, p *[]string, name, shorthand string, value []string, usage string) {
|
||||
flags.StringArrayVarP(p, name, shorthand, value, usage)
|
||||
setDefaultFromEnv(name)
|
||||
}
|
||||
|
||||
// CountP defines a flag which can be overridden by an environment variable
|
||||
//
|
||||
// It is a thin wrapper around pflag.CountP
|
||||
func CountP(name, shorthand string, usage string) (out *int) {
|
||||
out = pflag.CountP(name, shorthand, usage)
|
||||
setDefaultFromEnv(name)
|
||||
return out
|
||||
}
|
||||
|
||||
// CountVarP defines a flag which can be overridden by an environment variable
|
||||
//
|
||||
// It is a thin wrapper around pflag.CountVarP
|
||||
func CountVarP(flags *pflag.FlagSet, p *int, name, shorthand string, usage string) {
|
||||
flags.CountVarP(p, name, shorthand, usage)
|
||||
setDefaultFromEnv(name)
|
||||
}
|
||||
94
.rclone_repo/fs/config/obscure/obscure.go
Executable file
94
.rclone_repo/fs/config/obscure/obscure.go
Executable file
@@ -0,0 +1,94 @@
|
||||
// Package obscure contains the Obscure and Reveal commands
|
||||
package obscure
|
||||
|
||||
import (
|
||||
"crypto/aes"
|
||||
"crypto/cipher"
|
||||
"crypto/rand"
|
||||
"encoding/base64"
|
||||
"io"
|
||||
"log"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// crypt internals
|
||||
var (
|
||||
cryptKey = []byte{
|
||||
0x9c, 0x93, 0x5b, 0x48, 0x73, 0x0a, 0x55, 0x4d,
|
||||
0x6b, 0xfd, 0x7c, 0x63, 0xc8, 0x86, 0xa9, 0x2b,
|
||||
0xd3, 0x90, 0x19, 0x8e, 0xb8, 0x12, 0x8a, 0xfb,
|
||||
0xf4, 0xde, 0x16, 0x2b, 0x8b, 0x95, 0xf6, 0x38,
|
||||
}
|
||||
cryptBlock cipher.Block
|
||||
cryptRand = rand.Reader
|
||||
)
|
||||
|
||||
// crypt transforms in to out using iv under AES-CTR.
|
||||
//
|
||||
// in and out may be the same buffer.
|
||||
//
|
||||
// Note encryption and decryption are the same operation
|
||||
func crypt(out, in, iv []byte) error {
|
||||
if cryptBlock == nil {
|
||||
var err error
|
||||
cryptBlock, err = aes.NewCipher(cryptKey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
stream := cipher.NewCTR(cryptBlock, iv)
|
||||
stream.XORKeyStream(out, in)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Obscure a value
|
||||
//
|
||||
// This is done by encrypting with AES-CTR
|
||||
func Obscure(x string) (string, error) {
|
||||
plaintext := []byte(x)
|
||||
ciphertext := make([]byte, aes.BlockSize+len(plaintext))
|
||||
iv := ciphertext[:aes.BlockSize]
|
||||
if _, err := io.ReadFull(cryptRand, iv); err != nil {
|
||||
return "", errors.Wrap(err, "failed to read iv")
|
||||
}
|
||||
if err := crypt(ciphertext[aes.BlockSize:], plaintext, iv); err != nil {
|
||||
return "", errors.Wrap(err, "encrypt failed")
|
||||
}
|
||||
return base64.RawURLEncoding.EncodeToString(ciphertext), nil
|
||||
}
|
||||
|
||||
// MustObscure obscures a value, exiting with a fatal error if it failed
|
||||
func MustObscure(x string) string {
|
||||
out, err := Obscure(x)
|
||||
if err != nil {
|
||||
log.Fatalf("Obscure failed: %v", err)
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// Reveal an obscured value
|
||||
func Reveal(x string) (string, error) {
|
||||
ciphertext, err := base64.RawURLEncoding.DecodeString(x)
|
||||
if err != nil {
|
||||
return "", errors.Wrap(err, "base64 decode failed when revealing password - is it obscured?")
|
||||
}
|
||||
if len(ciphertext) < aes.BlockSize {
|
||||
return "", errors.New("input too short when revealing password - is it obscured?")
|
||||
}
|
||||
buf := ciphertext[aes.BlockSize:]
|
||||
iv := ciphertext[:aes.BlockSize]
|
||||
if err := crypt(buf, buf, iv); err != nil {
|
||||
return "", errors.Wrap(err, "decrypt failed when revealing password - is it obscured?")
|
||||
}
|
||||
return string(buf), nil
|
||||
}
|
||||
|
||||
// MustReveal reveals an obscured value, exiting with a fatal error if it failed
|
||||
func MustReveal(x string) string {
|
||||
out, err := Reveal(x)
|
||||
if err != nil {
|
||||
log.Fatalf("Reveal failed: %v", err)
|
||||
}
|
||||
return out
|
||||
}
|
||||
60
.rclone_repo/fs/config/obscure/obscure_test.go
Executable file
60
.rclone_repo/fs/config/obscure/obscure_test.go
Executable file
@@ -0,0 +1,60 @@
|
||||
package obscure
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/rand"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestObscure(t *testing.T) {
|
||||
for _, test := range []struct {
|
||||
in string
|
||||
want string
|
||||
iv string
|
||||
}{
|
||||
{"", "YWFhYWFhYWFhYWFhYWFhYQ", "aaaaaaaaaaaaaaaa"},
|
||||
{"potato", "YWFhYWFhYWFhYWFhYWFhYXMaGgIlEQ", "aaaaaaaaaaaaaaaa"},
|
||||
{"potato", "YmJiYmJiYmJiYmJiYmJiYp3gcEWbAw", "bbbbbbbbbbbbbbbb"},
|
||||
} {
|
||||
cryptRand = bytes.NewBufferString(test.iv)
|
||||
got, err := Obscure(test.in)
|
||||
cryptRand = rand.Reader
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, test.want, got)
|
||||
recoveredIn, err := Reveal(got)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, test.in, recoveredIn, "not bidirectional")
|
||||
// Now the Must variants
|
||||
cryptRand = bytes.NewBufferString(test.iv)
|
||||
got = MustObscure(test.in)
|
||||
cryptRand = rand.Reader
|
||||
assert.Equal(t, test.want, got)
|
||||
recoveredIn = MustReveal(got)
|
||||
assert.Equal(t, test.in, recoveredIn, "not bidirectional")
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
func TestReveal(t *testing.T) {
|
||||
for _, test := range []struct {
|
||||
in string
|
||||
want string
|
||||
iv string
|
||||
}{
|
||||
{"YWFhYWFhYWFhYWFhYWFhYQ", "", "aaaaaaaaaaaaaaaa"},
|
||||
{"YWFhYWFhYWFhYWFhYWFhYXMaGgIlEQ", "potato", "aaaaaaaaaaaaaaaa"},
|
||||
{"YmJiYmJiYmJiYmJiYmJiYp3gcEWbAw", "potato", "bbbbbbbbbbbbbbbb"},
|
||||
} {
|
||||
cryptRand = bytes.NewBufferString(test.iv)
|
||||
got, err := Reveal(test.in)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, test.want, got)
|
||||
// Now the Must variants
|
||||
cryptRand = bytes.NewBufferString(test.iv)
|
||||
got = MustReveal(test.in)
|
||||
assert.Equal(t, test.want, got)
|
||||
|
||||
}
|
||||
}
|
||||
4
.rclone_repo/fs/config/testdata/enc-invalid.conf
vendored
Executable file
4
.rclone_repo/fs/config/testdata/enc-invalid.conf
vendored
Executable file
@@ -0,0 +1,4 @@
|
||||
# Encrypted rclone configuration File
|
||||
|
||||
RCLONE_ENCRYPT_V0:
|
||||
b5Uk6mE3cUn5Wb8xiWYnVBAxXUirAaEG1PO/GIDiO9274AOæøå+Yj790BwJA4d2y7lNkmHt4nJwIsoueFvUYmm7RDyzER8IA3XOCrjzl3OUcczZqcplk5JfBdhxMZpt1aGYWUdle1IgO/kAFne6sLD6IuxPySEb
|
||||
4
.rclone_repo/fs/config/testdata/enc-short.conf
vendored
Executable file
4
.rclone_repo/fs/config/testdata/enc-short.conf
vendored
Executable file
@@ -0,0 +1,4 @@
|
||||
# Encrypted rclone configuration File
|
||||
|
||||
RCLONE_ENCRYPT_V0:
|
||||
b5Uk6mE3cUn5Wb8xi
|
||||
4
.rclone_repo/fs/config/testdata/enc-too-new.conf
vendored
Executable file
4
.rclone_repo/fs/config/testdata/enc-too-new.conf
vendored
Executable file
@@ -0,0 +1,4 @@
|
||||
# Encrypted rclone configuration File
|
||||
|
||||
RCLONE_ENCRYPT_V1:
|
||||
b5Uk6mE3cUn5Wb8xiWYnVBAxXUirAaEG1PO/GIDiO9274AO+Yj790BwJA4d2y7lNkmHt4nJwIsoueFvUYmm7RDyzER8IA3XOCrjzl3OUcczZqcplk5JfBdhxMZpt1aGYWUdle1IgO/kAFne6sLD6IuxPySEb
|
||||
4
.rclone_repo/fs/config/testdata/encrypted.conf
vendored
Executable file
4
.rclone_repo/fs/config/testdata/encrypted.conf
vendored
Executable file
@@ -0,0 +1,4 @@
|
||||
# Encrypted rclone configuration File
|
||||
|
||||
RCLONE_ENCRYPT_V0:
|
||||
b5Uk6mE3cUn5Wb8xiWYnVBAxXUirAaEG1PO/GIDiO9274AO+Yj790BwJA4d2y7lNkmHt4nJwIsoueFvUYmm7RDyzER8IA3XOCrjzl3OUcczZqcplk5JfBdhxMZpt1aGYWUdle1IgO/kAFne6sLD6IuxPySEb
|
||||
12
.rclone_repo/fs/config/testdata/plain.conf
vendored
Executable file
12
.rclone_repo/fs/config/testdata/plain.conf
vendored
Executable file
@@ -0,0 +1,12 @@
|
||||
[RCLONE_ENCRYPT_V0]
|
||||
type = local
|
||||
nounc = true
|
||||
|
||||
[nounc]
|
||||
type = local
|
||||
nounc = true
|
||||
|
||||
|
||||
[unc]
|
||||
type = local
|
||||
nounc = false
|
||||
Reference in New Issue
Block a user