Compare nicks by exact case everywhere (closes #128) #141
@@ -1031,14 +1031,14 @@ e.g. `+o-v alice bob`.
|
||||
| `441` | ERR_USERNOTINCHANNEL | The `+o`/`+v` nick is not in the channel |
|
||||
|
||||
**User modes:** you can query and change only your own; a nick other than your
|
||||
own, compared without regard to letter case, gets ERR_USERSDONTMATCH (502). With
|
||||
no mode string the server sends RPL_UMODEIS (221) with your modes: `+`, `+o`,
|
||||
`+w` or `+ow`. A change is a mode string, `body[0]` over the HTTP API or the
|
||||
parameter after the nick on the IRC listener, that starts with `+` or `-`: `w`
|
||||
(receive `WALLOPS`) can be set or unset, and `o` (server operator) only unset,
|
||||
since only `OPER` sets it. Any other letter, or a string that names no mode,
|
||||
gets ERR_UMODEUNKNOWNFLAG (501) and changes nothing. A change that is accepted
|
||||
is answered with 221 and the new modes; it is not sent to anyone else.
|
||||
own gets ERR_USERSDONTMATCH (502). With no mode string the server sends
|
||||
RPL_UMODEIS (221) with your modes: `+`, `+o`, `+w` or `+ow`. A change is a mode
|
||||
string, `body[0]` over the HTTP API or the parameter after the nick on the IRC
|
||||
listener, that starts with `+` or `-`: `w` (receive `WALLOPS`) can be set or
|
||||
unset, and `o` (server operator) only unset, since only `OPER` sets it. Any
|
||||
other letter, or a string that names no mode, gets ERR_UMODEUNKNOWNFLAG (501)
|
||||
and changes nothing. A change that is accepted is answered with 221 and the new
|
||||
modes; it is not sent to anyone else.
|
||||
|
||||
```json
|
||||
{ "command": "RPL_UMODEIS", "code": 221, "to": "alice", "body": ["+w"] }
|
||||
@@ -2679,8 +2679,7 @@ IRC_LISTEN_ADDR: ""
|
||||
set only over the HTTP API.
|
||||
- **User modes**: `+o` (operator, set only via `OPER`), `+w` (receives
|
||||
`WALLOPS`). `MODE` for any nick other than your own is rejected with
|
||||
`ERR_USERSDONTMATCH` (502), for both queries and changes. Nick comparison is
|
||||
case-insensitive.
|
||||
`ERR_USERSDONTMATCH` (502), for both queries and changes.
|
||||
- **KILL**: an operator's `KILL` broadcasts the victim's `QUIT` to its channel
|
||||
peers, deletes its session, and then sends the victim a `KILL` and
|
||||
`ERROR :Closing Link` before closing its socket. This applies to victims on
|
||||
@@ -2701,12 +2700,6 @@ API reaches IRC listener members as an IRC `MODE` line with its value as a
|
||||
separate parameter, e.g. `MODE #general +k secretpass` or
|
||||
`MODE #general +o bob`.
|
||||
|
||||
One gap remains: the IRC listener does not pass on a `PRIVMSG`, `NOTICE`,
|
||||
`JOIN`, `PART`, `NICK` or `QUIT` whose sender's nick matches the user's own nick
|
||||
ignoring case, so an IRC listener user `alice` never sees these from `Alice`,
|
||||
although nicks are case-sensitive. https://git.eeqj.de/sneak/neoirc/issues/128
|
||||
tracks it.
|
||||
|
||||
### Docker Usage
|
||||
|
||||
To expose the IRC port in Docker (the listener is enabled by default on
|
||||
|
||||
@@ -669,6 +669,9 @@ func TestCreateSessionDuplicate(t *testing.T) {
|
||||
if resp.StatusCode != http.StatusConflict {
|
||||
t.Fatalf("expected 409, got %d", resp.StatusCode)
|
||||
}
|
||||
|
||||
// Nicks are case-sensitive: Alice is not alice.
|
||||
tserver.createSession("Alice")
|
||||
}
|
||||
|
||||
// TestEachTestServerHasItsOwnDatabase checks that starting a
|
||||
|
||||
@@ -254,8 +254,7 @@ func (hdlr *Handlers) handleWallops(
|
||||
|
||||
// handleUserMode handles MODE for a nick: a query when the
|
||||
// body is empty, otherwise a change by the mode string in
|
||||
// the body. Only the caller's own nick, in any letter case,
|
||||
// is allowed.
|
||||
// the body. Only the caller's own nick is allowed.
|
||||
func (hdlr *Handlers) handleUserMode(
|
||||
writer http.ResponseWriter,
|
||||
request *http.Request,
|
||||
@@ -263,7 +262,7 @@ func (hdlr *Handlers) handleUserMode(
|
||||
nick, target string,
|
||||
bodyLines func() []string,
|
||||
) {
|
||||
if !strings.EqualFold(target, nick) {
|
||||
if target != nick {
|
||||
hdlr.respondIRCError(
|
||||
writer, request, clientID, sessionID,
|
||||
irc.ErrUsersDoNotMatch, nick, nil,
|
||||
|
||||
@@ -290,8 +290,10 @@ func TestUserMode(t *testing.T) {
|
||||
target, modeStr, numeric, modes string
|
||||
}{
|
||||
{nick, "+w", "221", "+w"},
|
||||
{"Alice", "", "502", ""},
|
||||
{"Alice", "-w", "502", ""},
|
||||
{nick, "", "221", "+w"},
|
||||
{strings.ToUpper(nick), "-w", "221", "+"},
|
||||
{nick, "-w", "221", "+"},
|
||||
{nick, "+z", "501", ""},
|
||||
{nick, "+o", "501", ""},
|
||||
{other, "", "502", ""},
|
||||
|
||||
@@ -710,12 +710,12 @@ func (c *Conn) applyChannelModes(
|
||||
|
||||
// handleUserMode handles MODE for a nick: a query without a
|
||||
// mode string, otherwise a change. Only the client's own
|
||||
// nick, in any letter case, is allowed.
|
||||
// nick is allowed.
|
||||
func (c *Conn) handleUserMode(
|
||||
ctx context.Context,
|
||||
msg *Message,
|
||||
) {
|
||||
if !strings.EqualFold(msg.Params[0], c.currentNick()) {
|
||||
if msg.Params[0] != c.currentNick() {
|
||||
c.sendNumeric(
|
||||
irc.ErrUsersDoNotMatch,
|
||||
"Can't change mode for other users",
|
||||
|
||||
@@ -1084,9 +1084,9 @@ func TestIntegrationWallops(t *testing.T) {
|
||||
}
|
||||
|
||||
// TestIntegrationUserMode covers MODE for a nick: changes
|
||||
// and queries of your own modes in any letter case, refusal
|
||||
// for another nick, and a rejected mode string changing
|
||||
// nothing.
|
||||
// and queries of your own modes, refusal for another nick,
|
||||
// even one that differs from yours only in letter case, and
|
||||
// a rejected mode string changing nothing.
|
||||
func TestIntegrationUserMode(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
@@ -1106,7 +1106,9 @@ func TestIntegrationUserMode(t *testing.T) {
|
||||
for _, step := range []struct {
|
||||
line, want string
|
||||
}{
|
||||
{"MODE ALICE +w", " 221 alice +w"},
|
||||
{"MODE alice +w", " 221 alice +w"},
|
||||
{"MODE Alice", notYours},
|
||||
{"MODE Alice -w", notYours},
|
||||
{"MODE bob", notYours},
|
||||
{"MODE bob -w", notYours},
|
||||
{"MODE alice xw", unknown},
|
||||
|
||||
@@ -195,7 +195,7 @@ func (c *Conn) deliverTextMessage(
|
||||
c.mu.Unlock()
|
||||
|
||||
// Don't echo our own messages back.
|
||||
if strings.EqualFold(from, nick) {
|
||||
if from == nick {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -217,7 +217,7 @@ func (c *Conn) deliverJoin(msg *db.IRCMessage) {
|
||||
|
||||
// Don't echo our own JOINs (we already sent them
|
||||
// during joinChannel).
|
||||
if strings.EqualFold(msg.From, nick) {
|
||||
if msg.From == nick {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -233,7 +233,7 @@ func (c *Conn) deliverPart(msg *db.IRCMessage, text string) {
|
||||
nick := c.nick
|
||||
c.mu.Unlock()
|
||||
|
||||
if strings.EqualFold(msg.From, nick) {
|
||||
if msg.From == nick {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -258,7 +258,7 @@ func (c *Conn) deliverNickChange(
|
||||
nick := c.nick
|
||||
c.mu.Unlock()
|
||||
|
||||
if strings.EqualFold(msg.From, nick) {
|
||||
if msg.From == nick {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -276,7 +276,7 @@ func (c *Conn) deliverQuitMsg(
|
||||
nick := c.nick
|
||||
c.mu.Unlock()
|
||||
|
||||
if strings.EqualFold(msg.From, nick) {
|
||||
if msg.From == nick {
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
@@ -185,3 +185,39 @@ func TestRelayLineBreaks(t *testing.T) {
|
||||
t.Errorf("got %q, want %q", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
// TestRelayNickDiffersOnlyInCase checks that an IRC listener
|
||||
// user alice receives a JOIN, PRIVMSG, PART, NICK and QUIT
|
||||
// from Alice in a channel they share: nicks are
|
||||
// case-sensitive, so Alice is someone else.
|
||||
func TestRelayNickDiffersOnlyInCase(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
env := newTestEnv(t)
|
||||
|
||||
alice := env.dial(t)
|
||||
alice.register("alice")
|
||||
alice.joinAndDrain("#case")
|
||||
|
||||
capitalAlice := env.dial(t)
|
||||
capitalAlice.register("Alice")
|
||||
|
||||
for _, step := range []struct {
|
||||
line, want string
|
||||
}{
|
||||
{"JOIN #case", ":Alice!Alice@* JOIN #case"},
|
||||
{"PRIVMSG #case :hi there", ":Alice!Alice@* PRIVMSG #case :hi there"},
|
||||
{"PART #case :see you", ":Alice!Alice@* PART #case :see you"},
|
||||
{"JOIN #case", ":Alice!Alice@* JOIN #case"},
|
||||
{"NICK Alicia", ":Alice!Alice@* NICK Alicia"},
|
||||
{"NICK Alice", ":Alicia!Alicia@* NICK Alice"}, // back for QUIT
|
||||
{"QUIT", ":Alice!Alice@* QUIT :Quit: Connection closed"},
|
||||
} {
|
||||
capitalAlice.send(step.line)
|
||||
|
||||
got := alice.readLine()
|
||||
if got != step.want {
|
||||
t.Errorf("%s: got %q, want %q", step.line, got, step.want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user