Repository: restic/restic (go)

Issue:
`cat`: Parse arguments before doing remote connections
<!--

Welcome! - We kindly ask that you:

  1. Fill out the issue template below - not doing so needs a good reason.
  2. Use the forum if you have a question rather than a bug or feature request.

The forum is at: https://forum.restic.net

NOTE: Not filling out the issue template needs a good reason, as otherwise it
may take a lot longer to find the problem, not to mention it can take up a lot
more time which can otherwise be spent on development. Please also take the
time to help us debug the issue by collecting relevant information, even if
it doesn't seem to be relevant to you. Thanks!

The forum is a better place for questions about restic or general suggestions
and topics, e.g. usage or documentation questions! This issue tracker is mainly
for tracking bugs and feature requests directly relating to the development of
the software itself, rather than the project.

Thanks for understanding, and for contributing to the project!

-->


Output of `restic version`
--------------------------

```
restic 0.15.2 compiled with go1.20.3 on linux/amd64
```

How did you run restic exactly?
-------------------------------

```
restic cat locks 4e399071437898e7d419d41039d77b1aad719cdc9f5ebc0d7fd31fb979cc1b8b
```

What backend/server/service did you use to store the repository?
----------------------------------------------------------------



Expected behavior
-----------------

```
"locks" is not a correct argument, select among
[pack|blob|snapshot|index|key|masterkey|config|lock]
```

Actual behavior
---------------

```
repository a72e6e8d opened (version 1)
Fatal: invalid type
```

Steps to reproduce the behavior
-------------------------------

<!--
The more time you spend describing an easy way to reproduce the behavior (if
this is possible), the easier it is for the project developers to fix it!
-->

Do you have any idea what may have caused this?
-----------------------------------------------



Do you have an idea how to solve the issue?
-------------------------------------------



Did restic help you today? Did it make you happy in any way?
------------------------------------------------------------

<!--
Answering this question is not required, but if you have anything positive to share, please do so here!
Sometimes we get tired of reading bug reports all day and a little positive end note does wonders.
Idea by Joey Hess, https://joeyh.name/blog/entry/two_holiday_stories/
-->


Pull request:
Check for arguments before opening remote connection in cat command
<!--
Thank you very much for contributing code or documentation to restic! Please
fill out the following questions to make it easier for us to review your
changes.
-->

What does this PR change? What problem does it solve?
-----------------------------------------------------
Check for arguments to `cat` command befire opening the remote connection
<!--
Describe the changes and their purpose here, as detailed as needed.
-->
Checking the allowed arguments from an array

Was the change previously discussed in an issue or on the forum?
----------------------------------------------------------------
#4416

<!--
Link issues and relevant forum posts here.
#4416 

If this PR resolves an issue on GitHub, use "Closes #1234" so that the issue
is closed automatically when this PR is merged.
-->
Closes #4416 

Checklist
---------

<!--
You do not need to check all the boxes below all at once. Feel free to take
your time and add more commits. If you're done and ready for review, please
check the last box. Enable a checkbox by replacing [ ] with [x].
-->

- [ ] I have read the [contribution guidelines](https://github.com/restic/restic/blob/master/CONTRIBUTING.md#providing-patches).
- [ ] I have [enabled maintainer edits](https://help.github.com/en/github/collaborating-with-issues-and-pull-requests/allowing-changes-to-a-pull-request-branch-created-from-a-fork).
- [ ] I have added tests for all code changes.
- [ ] I have added documentation for relevant changes (in the manual).
- [ ] There's a new file in `changelog/unreleased/` that describes the changes for our users (see [template](https://github.com/restic/restic/blob/master/changelog/TEMPLATE)).
- [x] I have run `gofmt` on the code in all commits.
- [x] All commit messages are formatted in the same style as [the other commits in the repo](https://github.com/restic/restic/blob/master/CONTRIBUTING.md#git-commits).
- [x] I'm done! This pull request is ready for review.


Patch:
diff --git a/cmd/restic/cmd_cat.go b/cmd/restic/cmd_cat.go
index 7c4373812..97789d271 100644
--- a/cmd/restic/cmd_cat.go
+++ b/cmd/restic/cmd_cat.go
@@ -3,6 +3,7 @@ package main
 import (
 	"context"
 	"encoding/json"
+	"strings"
 
 	"github.com/spf13/cobra"
 
@@ -33,9 +34,34 @@ func init() {
 	cmdRoot.AddCommand(cmdCat)
 }
 
+func validateCatArgs(args []string) error {
+	var allowedCmds = []string{"config", "index", "snapshot", "key", "masterkey", "lock", "pack", "blob", "tree"}
+
+	if len(args) < 1 {
+		return errors.Fatal("type not specified")
+	}
+
+	validType := false
+	for _, v := range allowedCmds {
+		if v == args[0] {
+			validType = true
+			break
+		}
+	}
+	if !validType {
+		return errors.Fatalf("invalid type %q, must be one of [%s]", args[0], strings.Join(allowedCmds, "|"))
+	}
+
+	if args[0] != "masterkey" && args[0] != "config" && len(args) != 2 {
+		return errors.Fatal("ID not specified")
+	}
+
+	return nil
+}
+
 func runCat(ctx context.Context, gopts GlobalOptions, args []string) error {
-	if len(args) < 1 || (args[0] != "masterkey" && args[0] != "config" && len(args) != 2) {
-		return errors.Fatal("type or ID not specified")
+	if err := validateCatArgs(args); err != nil {
+		return err
 	}
 
 	repo, err := OpenRepository(ctx, gopts)
diff --git a/cmd/restic/cmd_restore.go b/cmd/restic/cmd_restore.go
index 9be938feb..6ef8c99db 100644
--- a/cmd/restic/cmd_restore.go
+++ b/cmd/restic/cmd_restore.go
@@ -82,9 +82,9 @@ func init() {
 
 	flags := cmdRestore.Flags()
 	flags.StringArrayVarP(&restoreOptions.Exclude, "exclude", "e", nil, "exclude a `pattern` (can be specified multiple times)")
-	flags.StringArrayVar(&restoreOptions.InsensitiveExclude, "iexclude", nil, "same as --exclude but ignores the casing of `pattern`")
+	flags.StringArrayVar(&restoreOptions.InsensitiveExclude, "iexclude", nil, "same as `--exclude` but ignores the casing of filenames")
 	flags.StringArrayVarP(&restoreOptions.Include, "include", "i", nil, "include a `pattern`, exclude everything else (can be specified multiple times)")
-	flags.StringArrayVar(&restoreOptions.InsensitiveInclude, "iinclude", nil, "same as --include but ignores the casing of `pattern`")
+	flags.StringArrayVar(&restoreOptions.InsensitiveInclude, "iinclude", nil, "same as `--include` but ignores the casing of filenames")
 	flags.StringVarP(&restoreOptions.Target, "target", "t", "", "directory to extract data to")
 
 	initSingleSnapshotFilter(flags, &restoreOptions.SnapshotFilter)


Test patch:
diff --git a/cmd/restic/cmd_cat_test.go b/cmd/restic/cmd_cat_test.go
new file mode 100644
index 000000000..8c72a16a9
--- /dev/null
+++ b/cmd/restic/cmd_cat_test.go
@@ -0,0 +1,30 @@
+package main
+
+import (
+	"strings"
+	"testing"
+
+	rtest "github.com/restic/restic/internal/test"
+)
+
+func TestCatArgsValidation(t *testing.T) {
+	for _, test := range []struct {
+		args []string
+		err  string
+	}{
+		{[]string{}, "Fatal: type not specified"},
+		{[]string{"masterkey"}, ""},
+		{[]string{"invalid"}, `Fatal: invalid type "invalid"`},
+		{[]string{"snapshot"}, "Fatal: ID not specified"},
+		{[]string{"snapshot", "12345678"}, ""},
+	} {
+		t.Run("", func(t *testing.T) {
+			err := validateCatArgs(test.args)
+			if test.err == "" {
+				rtest.Assert(t, err == nil, "unexpected error %q", err)
+			} else {
+				rtest.Assert(t, strings.Contains(err.Error(), test.err), "unexpected error expected %q to contain %q", err, test.err)
+			}
+		})
+	}
+}