Skip to content
Open
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
11 changes: 10 additions & 1 deletion internal/api/backresthandler.go
Original file line number Diff line number Diff line change
Expand Up @@ -990,7 +990,16 @@ func (s *BackrestHandler) GeneratePairingToken(ctx context.Context, req *connect
func sanitizeRepoFlags(repo *v1.Repo) {
for i, flag := range repo.Flags {
if strings.HasPrefix(flag, "--option=sftp.args=") {
repo.Flags[i] = strings.ReplaceAll(flag, "-i @", "-i ")
// Remove the "-i @" workaround since we now handle quoting properly
flag = strings.ReplaceAll(flag, "-i @", "-i ")
// Strip double quotes from sftp.args values that cause restic's
// pflag parser to fail with "bare \" in non-quoted-field".
// The UI wraps paths in double quotes (e.g. -i "/path/to/key"),
// but restic's --option flag parser cannot handle them.
// Paths passed as sftp.args don't need quoting since they are
// passed directly to sftp as individual arguments.
flag = strings.ReplaceAll(flag, "\"", "")
repo.Flags[i] = flag
}
}
}
49 changes: 49 additions & 0 deletions internal/api/backresthandler_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1123,3 +1123,52 @@ func getOperations(t *testing.T, log *oplog.OpLog) []*v1.Operation {
}
return operations
}

func TestSanitizeRepoFlags(t *testing.T) {
tests := []struct {
name string
flags []string
want []string
}{
{
name: "strip double quotes from sftp.args",
flags: []string{`--option=sftp.args='-oBatchMode=yes -i "/root/.ssh/id_ed25519" -p 23 -oUserKnownHostsFile="/root/.ssh/known_hosts"'`},
want: []string{`--option=sftp.args='-oBatchMode=yes -i /root/.ssh/id_ed25519 -p 23 -oUserKnownHostsFile=/root/.ssh/known_hosts'`},
},
{
name: "strip double quotes with -i @ workaround",
flags: []string{`--option=sftp.args='-oBatchMode=yes -i @/root/.ssh/id_ed25519"'`},
want: []string{`--option=sftp.args='-oBatchMode=yes -i /root/.ssh/id_ed25519'`},
},
{
name: "no sftp.args flags unchanged",
flags: []string{`--option=some.other=value`},
want: []string{`--option=some.other=value`},
},
{
name: "sftp.args without double quotes unchanged",
flags: []string{`--option=sftp.args=-oBatchMode=yes`},
want: []string{`--option=sftp.args=-oBatchMode=yes`},
},
{
name: "mixed flags only sftp.args modified",
flags: []string{`--option=other=val`, `--option=sftp.args='-i "/path/key"'`},
want: []string{`--option=other=val`, `--option=sftp.args=-i /path/key`},
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
repo := &v1.Repo{Flags: tt.flags}
sanitizeRepoFlags(repo)
if len(repo.Flags) != len(tt.want) {
t.Fatalf("got %d flags, want %d", len(repo.Flags), len(tt.want))
}
for i, got := range repo.Flags {
if got != tt.want[i] {
t.Errorf("flags[%d] = %q, want %q", i, got, tt.want[i])
}
}
})
}
}