Skip to content

Commit 64ff97c

Browse files
ToxicFrogprogval
authored andcommitted
Send all enabled capabilities on CAP LIST
Notably, this fixes an issue with weechat (and possibly some other clients) where if you ever run /cap or /cap list after connection registration, the client will see the "CAP * LIST sasl" reply as downgrading the connection to drop all capabilities except sasl. This also enables the use of CAP LS after connection registration, although without post-hoc CAP REQ support this is of limited use.
1 parent 3e3e691 commit 64ff97c

File tree

2 files changed

+60
-22
lines changed

2 files changed

+60
-22
lines changed

lib/irc/handler.ex

Lines changed: 40 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,10 @@ defmodule M51.IrcConn.Handler do
105105

106106
@capabilities_ls Map.merge(@capabilities, @informative_capabilities)
107107

108+
@capability_names @capabilities
109+
|> Enum.map(fn {name, {atom, _}} -> {atom, name} end)
110+
|> Map.new()
111+
108112
@valid_batch_types ["draft/multiline"]
109113

110114
@doc """
@@ -301,6 +305,22 @@ defmodule M51.IrcConn.Handler do
301305
end
302306
end
303307

308+
defp cap_ls(is_302, send) do
309+
caps = @capabilities_ls
310+
|> Map.to_list()
311+
|> Enum.sort_by(fn {k, _v} -> k end)
312+
|> Enum.map(fn {k, {_, v}} ->
313+
cond do
314+
is_nil(v) -> k
315+
!is_302 -> k
316+
true -> k <> "=" <> v
317+
end
318+
end)
319+
|> Enum.join(" ")
320+
321+
send.(%M51.Irc.Command{source: "server.", command: "CAP", params: ["*", "LS", caps]})
322+
end
323+
304324
# Handles a connection registration command, ie. only NICK/USER/CAP/AUTHENTICATE.
305325
# Returns nil, {:nick, new_nick}, {:user, new_gecos}, {:authenticate, user_id},
306326
# :got_cap_ls, or :got_cap_end.
@@ -341,30 +361,11 @@ defmodule M51.IrcConn.Handler do
341361
nil
342362

343363
{"CAP", ["LS", "302"]} ->
344-
caps =
345-
@capabilities_ls
346-
|> Map.to_list()
347-
|> Enum.sort_by(fn {k, _v} -> k end)
348-
|> Enum.map(fn {k, {_, v}} ->
349-
case v do
350-
nil -> k
351-
_ -> k <> "=" <> v
352-
end
353-
end)
354-
|> Enum.join(" ")
355-
356-
send.(%M51.Irc.Command{source: "server.", command: "CAP", params: ["*", "LS", caps]})
364+
cap_ls(true, send)
357365
:got_cap_ls
358366

359367
{"CAP", ["LS" | _]} ->
360-
caps =
361-
@capabilities_ls
362-
|> Map.to_list()
363-
|> Enum.sort_by(fn {k, {_, _v}} -> k end)
364-
|> Enum.map(fn {k, _v} -> k end)
365-
|> Enum.join(" ")
366-
367-
send.(%M51.Irc.Command{source: "server.", command: "CAP", params: ["*", "LS", caps]})
368+
cap_ls(false, send)
368369
:got_cap_ls
369370

370371
{"CAP", ["LIST" | _]} ->
@@ -789,11 +790,28 @@ defmodule M51.IrcConn.Handler do
789790
{"USER", _} ->
790791
nil
791792

793+
{"CAP", ["LS", "302"]} ->
794+
cap_ls(true, send)
795+
796+
{"CAP", ["LS" | _]} ->
797+
cap_ls(false, send)
798+
792799
{"CAP", ["LIST" | _]} ->
793-
send.(%M51.Irc.Command{source: "server.", command: "CAP", params: ["*", "LIST", "sasl"]})
800+
caps =
801+
M51.IrcConn.State.capabilities(state)
802+
|> Enum.map(fn cap -> @capability_names[cap] end)
803+
|> Enum.filter(fn cap -> !is_nil(cap) end)
804+
|> Enum.join(" ")
805+
806+
send.(%M51.Irc.Command{
807+
source: "server.",
808+
command: "CAP",
809+
params: ["*", "LIST", caps]
810+
})
794811

795812
{"CAP", [subcommand | _]} ->
796813
# ERR_INVALIDCAPCMD
814+
# TODO: support CAP REQ to turn caps on and off post-registration.
797815
send_numeric.("410", [subcommand, "Invalid CAP subcommand"])
798816

799817
{"CAP", []} ->

test/irc/handler_test.exs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -449,6 +449,26 @@ defmodule M51.IrcConn.HandlerTest do
449449
Logger.add_backend(:console)
450450
end
451451

452+
test "post-registration CAP LS", %{handler: handler} do
453+
do_connection_registration(handler)
454+
455+
send(handler, cmd("CAP LS 302"))
456+
assert_line(@cap_ls_302)
457+
458+
send(handler, cmd("CAP LS"))
459+
assert_line(@cap_ls)
460+
end
461+
462+
test "post-registration CAP LIST", %{handler: handler} do
463+
caps_requested = ["draft/multiline", "extended-join", "message-tags", "server-time"]
464+
caps_expected = Enum.join(["batch", "labeled-response", "sasl"] ++ caps_requested, " ")
465+
466+
do_connection_registration(handler, caps_requested)
467+
468+
send(handler, cmd("CAP LIST"))
469+
assert_line(":server. CAP * LIST :" <> caps_expected <> "\r\n")
470+
end
471+
452472
test "labeled response", %{handler: handler} do
453473
do_connection_registration(handler)
454474

0 commit comments

Comments
 (0)