talircd/lib/irc/msg.ml

209 lines
5.7 KiB
OCaml
Raw Normal View History

2024-01-07 23:51:05 +00:00
open Types
type prefix =
| No_prefix
| Server_prefix of string
2024-01-10 01:20:39 +00:00
| User_prefix of name * userinfo option * string option
2024-01-07 23:51:05 +00:00
let pp_userinfo_opt ppf = function
| None -> ()
| Some { username; _ } -> Format.fprintf ppf "!%s" username
let pp_hostname_opt ppf = function
| None -> ()
| Some hostname -> Format.fprintf ppf "@%s" hostname
let prefix_string = function
| No_prefix -> ""
| Server_prefix s -> s
| User_prefix (nick, uinfo, host) ->
Format.asprintf "%s%a%a"
nick pp_userinfo_opt uinfo pp_hostname_opt host
let pp_prefix ppf p =
Format.fprintf ppf "%S" (prefix_string p)
2024-01-06 20:49:50 +00:00
type t = {
2024-01-07 23:51:05 +00:00
prefix : prefix;
2024-01-06 20:49:50 +00:00
command : string;
params : string list;
trailing : bool;
} [@@deriving show { with_path = false }]
let is_param_trailing p =
String.starts_with p ~prefix:":" || String.contains p ' '
2024-01-06 20:49:50 +00:00
let rec is_params_trailing = function
| [] -> false
| [p] -> is_param_trailing p
2024-01-06 20:49:50 +00:00
| _ :: tl -> is_params_trailing tl
2024-01-07 23:51:05 +00:00
let make ?(prefix = No_prefix) ?(always_trailing = false) command params =
2024-01-06 20:49:50 +00:00
let trailing = always_trailing || is_params_trailing params in
{ prefix; command; params; trailing }
let write buf t =
2024-01-07 23:51:05 +00:00
if t.prefix <> No_prefix then begin
Buffer.add_char buf ':';
Buffer.add_string buf (prefix_string t.prefix);
Buffer.add_char buf ' ';
end;
2024-01-06 20:49:50 +00:00
Buffer.add_string buf t.command;
let rec add_params = function
| [] -> ()
| [p] when t.trailing ->
2024-01-06 20:49:50 +00:00
Buffer.add_string buf " :";
Buffer.add_string buf p
| p::tl ->
2024-01-06 20:49:50 +00:00
Buffer.add_char buf ' ';
Buffer.add_string buf p;
2024-01-06 20:49:50 +00:00
add_params tl
in
add_params t.params;
Buffer.add_string buf "\r\n"
exception Empty_message of int
exception Incomplete
2024-01-07 23:51:05 +00:00
let crlf = function '\r' | '\n' | '\x00' -> true | _ -> false
2024-01-06 20:49:50 +00:00
let cl = function ':' -> true | _ -> false
let sp = function ' ' -> true | _ -> false
2024-01-07 23:51:05 +00:00
let nocrlf = function '\r' | '\n' | '\x00' -> false | _ -> true
let nospcrlf = function ' ' | '\r' | '\n' | '\x00' -> false | _ -> true
2024-01-06 20:49:50 +00:00
let startswith str f i =
if i >= String.length str then
raise_notrace Incomplete;
f str.[i]
let rec skip str f i =
if i < String.length str && f str.[i] then
skip str f (i + 1)
else
i
let span str f i =
let e = skip str f i in
e, String.sub str i (e - i)
let parse str =
let parse_prefix i =
if startswith str cl i then
let i, pfx = span str nospcrlf (i + 1) in
2024-01-07 23:51:05 +00:00
(* TODO: parse [User_prefix] *)
(* not really needed because prefixes from clients are always ignored *)
i, Server_prefix pfx
2024-01-06 20:49:50 +00:00
else
2024-01-07 23:51:05 +00:00
i, No_prefix
2024-01-06 20:49:50 +00:00
in
let parse_command i =
let i = skip str sp i in
if startswith str crlf i then
raise_notrace (Empty_message (skip str crlf i));
let i, cmd = span str nospcrlf i in
i, String.uppercase_ascii cmd
in
let rec parse_params acc i =
let i = skip str sp i in
if startswith str crlf i then
let i = skip str crlf i in
i, List.rev acc, false
else if startswith str cl i then
let i, par = span str nocrlf (i + 1) in
i, List.rev (par :: acc), true
else
let i, par = span str nospcrlf i in
parse_params (par :: acc) i
in
let parse_msg i =
let i, prefix = parse_prefix i in
let i, command = parse_command i in
let i, params, always_trailing = parse_params [] i in
2024-01-07 23:51:05 +00:00
let msg = make command params ~prefix ~always_trailing in
2024-01-06 20:49:50 +00:00
i, msg
in
let rec parse_all acc i =
match parse_msg i with
| i, msg -> parse_all (msg :: acc) i
| exception Empty_message i -> parse_all acc i
| exception Incomplete -> i, List.rev acc
in
let i, msgs = parse_all [] 0 in
let rest = if i = 0 then str else String.sub str i (String.length str - i) in
msgs, rest
;;
let%expect_test _ =
let print_msg_nl m =
let buf = Buffer.create 64 in
write buf m;
Printf.printf "%S\n" (Buffer.contents buf)
in
let print_parsed_msgs (msgs, rest) =
List.iter
(fun m -> Format.kasprintf print_string "%a\n" pp m)
msgs;
Format.printf "%S\n" rest
in
2024-01-07 23:51:05 +00:00
Format.printf "%a" pp_prefix No_prefix;
[%expect {| "" |}];
Format.printf "%a" pp_prefix (Server_prefix "localhost");
[%expect {| "localhost" |}];
Format.printf "%a" pp_prefix (User_prefix ("tali", None, None));
[%expect {| "tali" |}];
Format.printf "%a" pp_prefix (User_prefix ("tali", None, Some "elsewhere"));
[%expect {| "tali@elsewhere" |}];
let milo = { username = "milo"; realname = "Milo" } in
Format.printf "%a" pp_prefix (User_prefix ("tali", Some milo, Some "elsewhere"));
[%expect {| "tali!milo@elsewhere" |}];
2024-01-06 20:49:50 +00:00
make "NICK" ["tali"] |> print_msg_nl;
[%expect {| "NICK tali\r\n" |}];
make "NICK" [":)"] |> print_msg_nl;
[%expect {| "NICK ::)\r\n" |}];
2024-01-06 20:49:50 +00:00
make "USER" ["milo"; "0"; "*"; "milo"] ~always_trailing:true |> print_msg_nl;
[%expect {| "USER milo 0 * :milo\r\n" |}];
2024-01-07 23:51:05 +00:00
let lo = Server_prefix "localhost" in
make "001" ["tali"; "Welcome to the IRC Network"] ~prefix:lo |> print_msg_nl;
2024-01-06 20:49:50 +00:00
[%expect {| ":localhost 001 tali :Welcome to the IRC Network\r\n" |}];
2024-01-07 23:51:05 +00:00
parse ":server.com usEr tali 0 * iitalics\r\n" |> print_parsed_msgs;
2024-01-06 20:49:50 +00:00
[%expect {|
2024-01-07 23:51:05 +00:00
{ prefix = "server.com"; command = "USER";
2024-01-06 20:49:50 +00:00
params = ["tali"; "0"; "*"; "iitalics"]; trailing = false }
""
|}];
parse "PRIVMSG #lol :Hello world\r\nPRIVMSG #lol :meow\r\nPRIVM" |> print_parsed_msgs;
[%expect {|
2024-01-07 23:51:05 +00:00
{ prefix = ""; command = "PRIVMSG"; params = ["#lol"; "Hello world"];
2024-01-06 20:49:50 +00:00
trailing = true }
2024-01-07 23:51:05 +00:00
{ prefix = ""; command = "PRIVMSG"; params = ["#lol"; "meow"];
2024-01-06 20:49:50 +00:00
trailing = true }
"PRIVM"
|}];
parse " \r\n" |> print_parsed_msgs;
[%expect {| "" |}];
parse ":ignore-me \r\nFOO\r\n" |> print_parsed_msgs;
[%expect {|
2024-01-07 23:51:05 +00:00
{ prefix = ""; command = "FOO"; params = []; trailing = false }
2024-01-06 20:49:50 +00:00
""
|}];