diff --git a/internal/mailbox/bot.go b/internal/mailbox/bot.go index 8391c95..7f4e83a 100644 --- a/internal/mailbox/bot.go +++ b/internal/mailbox/bot.go @@ -137,7 +137,11 @@ func RunBot(ctx context.Context, c *Client, args []string, in io.Reader, out io. } on := true if len(args) > 3 { - on = !strings.EqualFold(args[3], "off") && args[3] != "false" && args[3] != "0" + var ok bool + on, ok = parseBotBool(args[3]) + if !ok { + return fail(fmt.Errorf("invalid %s value %q; use on/off", args[0], args[3])) + } } if strings.ToLower(args[0]) == "flag" { err = c.Flag(ctx, mailbox, uid, on) @@ -175,3 +179,14 @@ func mailboxUID(args []string) (string, uint32, error) { } return args[1], uint32(uid), nil } + +func parseBotBool(value string) (bool, bool) { + switch strings.ToLower(strings.TrimSpace(value)) { + case "on", "true", "1": + return true, true + case "off", "false", "0": + return false, true + default: + return false, false + } +} diff --git a/internal/mailbox/mailbox_test.go b/internal/mailbox/mailbox_test.go index 45d595b..c5bdd72 100644 --- a/internal/mailbox/mailbox_test.go +++ b/internal/mailbox/mailbox_test.go @@ -230,3 +230,30 @@ func TestRunBotListRejectsInvalidLimit(t *testing.T) { } } } + +func TestRunBotFlagSeenRejectInvalidToggle(t *testing.T) { + tr := seeded() + c := paidClient(tr) + + for _, args := range [][]string{ + {"flag", Inbox, "1", "maybe"}, + {"seen", Inbox, "1", "maybe"}, + } { + var out bytes.Buffer + err := RunBot(context.Background(), c, args, strings.NewReader(""), &out) + if err == nil { + t.Fatalf("RunBot(%v) expected error", args) + } + if !strings.Contains(out.String(), "use on/off") { + t.Fatalf("RunBot(%v) output = %q, want on/off error", args, out.String()) + } + } + + msg, ok, err := tr.ReadMessage(context.Background(), Inbox, 1) + if err != nil || !ok { + t.Fatalf("ReadMessage: ok=%v err=%v", ok, err) + } + if msg.Flagged || msg.Seen { + t.Fatalf("invalid flag/seen toggles should not mutate message: %+v", msg.MessageSummary) + } +}