Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions cmd/commands/init_templates_source.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,13 @@ var templateSource clonetemplate.Source = func(ctx context.Context) (clonetempla
// coverage under any of them counts (multi-key support, as plugin install).
func templateLicenseKeys() []string {
keys := license.CollectLicenseKeys()
// CollectLicenseKeys reads only the primary key file; key.2 to key.10 are
// stored keys too, and a covered second key must not read as E110.
for _, k := range license.GetAllStoredKeys() {
if k = strings.TrimSpace(k); k != "" && !slices.Contains(keys, k) {
keys = append(keys, k)
}
}
if k := strings.TrimSpace(license.CollectLicenseKey()); k != "" {
keys = append([]string{k}, slices.DeleteFunc(keys, func(x string) bool { return x == k })...)
}
Expand Down
19 changes: 19 additions & 0 deletions cmd/commands/init_templates_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -254,6 +254,25 @@ func TestTemplateLicenseKeysOwnerFirst(t *testing.T) {
}
}

// TestTemplateLicenseKeysStoredNumbered: stored key.2 to key.10 files count as
// configured keys, after the primary key and each once (D-1170).
func TestTemplateLicenseKeysStoredNumbered(t *testing.T) {
compattest.Set(t, true)
isolateSource(t, func(http.ResponseWriter, *http.Request) {})
dir := filepath.Join(os.Getenv("HOME"), ".nself", "license")
if err := os.MkdirAll(dir, 0o700); err != nil {
t.Fatal(err)
}
for name, key := range map[string]string{"key": "fake-key-one", "key.2": "fake-key-two\n", "key.10": "fake-key-ten", "key.3": "fake-key-one"} {
if err := os.WriteFile(filepath.Join(dir, name), []byte(key), 0o600); err != nil {
t.Fatal(err)
}
}
if got := strings.Join(templateLicenseKeys(), ","); got != "fake-key-one,fake-key-two,fake-key-ten" {
t.Errorf("keys = %q, want the primary then key.2 and key.10, each once", got)
}
}

// TestTemplateLicenseKeysOwnerKeyTrimmed: a trailing newline on the owner key
// must not make the licence server read as unreachable.
func TestTemplateLicenseKeysOwnerKeyTrimmed(t *testing.T) {
Expand Down
13 changes: 12 additions & 1 deletion internal/backup/stream_dest_compat.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (
"io"
"os"
"path/filepath"
"regexp"
"strings"
"sync"

Expand All @@ -29,13 +30,19 @@ var (
bareNoticed = map[string]bool{}
)

// inlineOptionRe is the `,key=` shape of an inline rclone option
// ("myr,pass=x"). A relative value carrying it is a malformed remote, not a
// directory, even when no ":" follows.
var inlineOptionRe = regexp.MustCompile(`,[A-Za-z0-9_]+=`)

// isBarePath reports whether rclone would have read uri as a local path: not
// empty, not option-shaped, and no ":" before the first "/" (so no scheme, no
// name:path remote and no :backend connection string). A value starting with
// "/" is always a path (no rclone remote starts with "/"), commas included.
// A value with padding whitespace or a control character, and a relative
// value with an inline-credential shape (a "," before a ":", e.g.
// "myr,pass=ab/cd:p"), are left to the parser, which refuses them without
// "myr,pass=ab/cd:p", or a ",key=" option with no ":" at all, "myr,pass=ab/cd"; "./" and "../" always mean a path),
// are left to the parser, which refuses them without
// echoing them (D-0467).
func isBarePath(uri string) bool {
if uri == "" || strings.HasPrefix(uri, "-") || uri != strings.TrimSpace(uri) {
Expand All @@ -50,6 +57,10 @@ func isBarePath(uri string) bool {
if strings.Contains(uri, ":") && destinations.HasInlineCredentials(uri) {
return false
}
explicitPath := strings.HasPrefix(uri, "./") || strings.HasPrefix(uri, "../")
if !explicitPath && inlineOptionRe.MatchString(uri) {
return false
}
head, _, _ := strings.Cut(uri, "/")
return !strings.Contains(head, ":")
}
Expand Down
2 changes: 2 additions & 0 deletions internal/backup/stream_dest_compat_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,8 @@ func TestSettleBarePath(t *testing.T) {
{"old,2026", "path:///srv/proj/old,2026", true},
{"myr,pass=fake/secret-not-real:p", "myr,pass=fake/secret-not-real:p", false},
{"s3,secret_access_key=fake/secret-not-real:b", "s3,secret_access_key=fake/secret-not-real:b", false},
{"myr,pass=fake/secret-not-real", "myr,pass=fake/secret-not-real", false},
{"./myr,pass=fake-secret-not-real", "path:///srv/proj/myr,pass=fake-secret-not-real", true},
{"-x", "-x", false},
{" /mnt/backups", " /mnt/backups", false},
{"/mnt/a\nb", "/mnt/a\nb", false},
Expand Down
Loading