Refuse a PRIVMSG or NOTICE whose body or meta has the wrong type (closes #245)
check / check (push) Canceled after 0s
check / check (push) Canceled after 0s
Over the HTTP API, a PRIVMSG or NOTICE body was checked only by decoding it into a list of strings. JSON null decodes into an empty string there, and meta was not checked at all, so a null line, or a meta that is not an object, reached every recipient. handlePrivmsg now answers 400 invalid request body, before anything else, when body is present and is not an array of strings, or meta is present and is not an object. A missing or empty body still gets ERR_NOTEXTTOSEND. README.md says so. Model: opus-5-5
This commit is contained in:
@@ -8,10 +8,12 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"maps"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"net/url"
|
||||
"os"
|
||||
"reflect"
|
||||
"slices"
|
||||
"strconv"
|
||||
"strings"
|
||||
@@ -1056,6 +1058,132 @@ func TestMessageMissingTo(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// TestMessageInvalidBodyOrMeta checks that a PRIVMSG or
|
||||
// NOTICE, to a channel or a nick, whose body is not an array
|
||||
// of strings or whose meta is not an object gets 400 and
|
||||
// reaches nobody.
|
||||
func TestMessageInvalidBodyOrMeta(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
cases := []struct {
|
||||
name string
|
||||
fields map[string]any
|
||||
}{
|
||||
{"null line", map[string]any{bodyKey: []any{"hi", nil}}},
|
||||
{"number line", map[string]any{bodyKey: []any{"hi", 1}}},
|
||||
{"object line", map[string]any{bodyKey: []any{map[string]any{}}}},
|
||||
{"null body", map[string]any{bodyKey: nil}},
|
||||
{"object body", map[string]any{bodyKey: map[string]any{}}},
|
||||
{"null meta", map[string]any{bodyKey: []string{"hi"}, metaKey: nil}},
|
||||
{"array meta", map[string]any{bodyKey: []string{"hi"}, metaKey: []string{}}},
|
||||
{"string meta", map[string]any{bodyKey: []string{"hi"}, metaKey: "hi"}},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
const channel = "#badmsg"
|
||||
|
||||
tserver := newTestServer(t)
|
||||
aliceToken := tserver.createSession("alice_badmsg")
|
||||
bobToken := tserver.createSession("bob_badmsg")
|
||||
|
||||
tserver.sendCommand(aliceToken, map[string]any{
|
||||
commandKey: joinCmd, toKey: channel,
|
||||
})
|
||||
tserver.sendCommand(bobToken, map[string]any{
|
||||
commandKey: joinCmd, toKey: channel,
|
||||
})
|
||||
|
||||
_, bobLastID := tserver.pollMessages(bobToken, 0)
|
||||
|
||||
for _, command := range []string{privmsgCmd, irc.CmdNotice} {
|
||||
for _, target := range []string{channel, "bob_badmsg"} {
|
||||
request := map[string]any{
|
||||
commandKey: command, toKey: target,
|
||||
}
|
||||
maps.Copy(request, tc.fields)
|
||||
|
||||
status, result := tserver.sendCommand(
|
||||
aliceToken, request,
|
||||
)
|
||||
if status != http.StatusBadRequest ||
|
||||
result["error"] != "invalid request body" {
|
||||
t.Errorf(
|
||||
"%s to %s: expected 400 invalid "+
|
||||
"request body, got %d: %v",
|
||||
command, target, status, result,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
msgs, _ := tserver.pollMessages(bobToken, bobLastID)
|
||||
if len(msgs) != 0 {
|
||||
t.Fatalf("expected no messages, bob got %v", msgs)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestMessageRelaysBodyAndMeta checks that a PRIVMSG or
|
||||
// NOTICE, to a channel or a nick, reaches its recipient with
|
||||
// body and meta as sent.
|
||||
func TestMessageRelaysBodyAndMeta(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
const channel = "#relaymsg"
|
||||
|
||||
tserver := newTestServer(t)
|
||||
aliceToken := tserver.createSession("alice_relaymsg")
|
||||
bobToken := tserver.createSession("bob_relaymsg")
|
||||
|
||||
tserver.sendCommand(aliceToken, map[string]any{
|
||||
commandKey: joinCmd, toKey: channel,
|
||||
})
|
||||
tserver.sendCommand(bobToken, map[string]any{
|
||||
commandKey: joinCmd, toKey: channel,
|
||||
})
|
||||
|
||||
_, bobLastID := tserver.pollMessages(bobToken, 0)
|
||||
|
||||
body := []any{"line one", ""}
|
||||
meta := map[string]any{"sig": "abc", "alg": "ed25519"}
|
||||
|
||||
for _, command := range []string{privmsgCmd, irc.CmdNotice} {
|
||||
for _, target := range []string{channel, "bob_relaymsg"} {
|
||||
status, result := tserver.sendCommand(
|
||||
aliceToken,
|
||||
map[string]any{
|
||||
commandKey: command, toKey: target,
|
||||
bodyKey: body, metaKey: meta,
|
||||
},
|
||||
)
|
||||
if status != http.StatusOK {
|
||||
t.Fatalf(
|
||||
"%s to %s: expected 200, got %d: %v",
|
||||
command, target, status, result,
|
||||
)
|
||||
}
|
||||
|
||||
var msgs []map[string]any
|
||||
|
||||
msgs, bobLastID = tserver.pollMessages(
|
||||
bobToken, bobLastID,
|
||||
)
|
||||
if len(msgs) != 1 ||
|
||||
msgs[0][commandKey] != command ||
|
||||
!reflect.DeepEqual(msgs[0][bodyKey], body) ||
|
||||
!reflect.DeepEqual(msgs[0][metaKey], meta) {
|
||||
t.Fatalf(
|
||||
"%s to %s: bob got %v", command, target, msgs,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestNonMemberCannotSend(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
|
||||
Reference in New Issue
Block a user