Fix CLI token set command

This commit is contained in:
2026-09-13 10:34:28 +02:00
parent 594d5d0150
commit 3d8d1e5a66
3 changed files with 67 additions and 5 deletions
+44
View File
@@ -112,6 +112,50 @@ func TestFormatDurationSpelling(t *testing.T) {
} }
} }
// A word the parser does not expect has to be refused, not ignored: the flag
// package stops at the first non-flag argument, so a command that accepted one
// would silently drop every option after it - including the one naming the
// file it writes.
func TestParseRefusesStrayArguments(t *testing.T) {
newSet := func() (*Set, *string, *bool) {
var listen string
var flagged bool
s := NewSet("test", "UNCENSORED_SEND_TEST_")
s.String(&listen, "listen", "l", "default", "ADDR", "listen address")
s.Bool(&flagged, "vanity", "", false, "a boolean")
return s, &listen, &flagged
}
for _, args := range [][]string{
{"--vanity", "false"}, // the value a boolean never takes
{"stray"}, // a word on its own
{"--vanity", "false", "-l", "here"}, // options after the stray word
} {
s, listen, _ := newSet()
err := s.Parse(args)
if err == nil {
t.Errorf("Parse(%q) was accepted; listen = %q", args, *listen)
continue
}
if !strings.Contains(err.Error(), "unexpected argument") {
t.Errorf("Parse(%q) failed with %v, which does not name the problem", args, err)
}
}
// The forms that are actually correct still parse.
for _, args := range [][]string{
{"--vanity=false"},
{"--vanity", "-l", "here"},
{"-l", "here"},
{},
} {
s, _, _ := newSet()
if err := s.Parse(args); err != nil {
t.Errorf("Parse(%q) = %v, want it accepted", args, err)
}
}
}
// The convention is: one hyphen for a letter, two for a word. It is enforced // The convention is: one hyphen for a letter, two for a word. It is enforced
// here because the stdlib flag package treats both forms as the same. // here because the stdlib flag package treats both forms as the same.
func TestHyphenConvention(t *testing.T) { func TestHyphenConvention(t *testing.T) {
+13 -2
View File
@@ -42,7 +42,6 @@ func NewSet(name, envPrefix string) *Set {
return s return s
} }
func (s *Set) Args() []string { return s.fs.Args() }
func (s *Set) SetOutput(w io.Writer) { s.fs.SetOutput(w) } func (s *Set) SetOutput(w io.Writer) { s.fs.SetOutput(w) }
func (s *Set) register(sp *spec) { s.specs = append(s.specs, sp) } func (s *Set) register(sp *spec) { s.specs = append(s.specs, sp) }
func (s *Set) note(long, short string) { func (s *Set) note(long, short string) {
@@ -208,7 +207,19 @@ func (s *Set) Parse(args []string) error {
if err := s.checkConvention(args); err != nil { if err := s.checkConvention(args); err != nil {
return err return err
} }
return s.fs.Parse(args) if err := s.fs.Parse(args); err != nil {
return err
}
// A stray word is never harmless: the flag package stops parsing at the
// first non-flag argument, so every option after it is dropped in silence.
// Writing a boolean as "--vanity false" is the way this bites - it sets
// the flag to true and then discards the --data that says which file to
// write, leaving a cheerful message about the opposite of what was meant.
if extra := s.fs.Args(); len(extra) > 0 {
return fmt.Errorf("unexpected argument %q: an option's value attaches with %q, as in --name=value, and any positional argument comes first",
extra[0], "=")
}
return nil
} }
// Changed reports whether an option was given on the command line. // Changed reports whether an option was given on the command line.
+10 -3
View File
@@ -28,7 +28,14 @@ Usage:
A token grants its own size and lifetime limits. Any limit left unset is A token grants its own size and lifetime limits. Any limit left unset is
inherited from the running server's defaults, so a token with no options inherited from the running server's defaults, so a token with no options
behaves exactly like the anonymous tier but may claim vanity names. behaves exactly like the anonymous tier.
"set" changes only what you name on the command line; anything you leave out
keeps its current value. Give a limit an empty value to go back to inheriting
the server's default, and turn a flag off with "=false":
uncensored-send token set friend --max-size= inherit the default again
uncensored-send token set friend --vanity=false revoke custom names
Options: Options:
` `
@@ -144,8 +151,8 @@ func (o *tokenOptions) register() *config.Set {
fs.String(&o.defExpiry, "default-expiry", "", "", "DURATION", "lifetime applied when this token does not ask for one") fs.String(&o.defExpiry, "default-expiry", "", "", "DURATION", "lifetime applied when this token does not ask for one")
fs.String(&o.chosen, "token", "t", "", "VALUE", fs.String(&o.chosen, "token", "t", "", "VALUE",
"use this token instead of a generated one; \"-\" reads it from standard input") "use this token instead of a generated one; \"-\" reads it from standard input")
fs.Bool(&o.vanity, "vanity", "", false, "allow this token to claim custom names") fs.Bool(&o.vanity, "vanity", "", false, "allow this token to claim custom names; --vanity=false revokes it")
fs.Bool(&o.admin, "admin", "", false, "allow this token to delete anyone's files") fs.Bool(&o.admin, "admin", "", false, "allow this token to delete anyone's files; --admin=false revokes it")
return fs return fs
} }