From a6c37817668f5b0e1d2cce3ee949470a0a440bfe Mon Sep 17 00:00:00 2001 From: June McEnroe Date: Sun, 16 Jul 2023 21:23:33 -0400 Subject: Implement draft/read-marker --- bounce.h | 10 +++++++ client.c | 94 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++- pounce.1 | 8 ++++++ state.c | 10 +++---- 4 files changed, 115 insertions(+), 7 deletions(-) diff --git a/bounce.h b/bounce.h index 8df85fc..e670ff3 100644 --- a/bounce.h +++ b/bounce.h @@ -25,6 +25,7 @@ * covered work. */ +#include #include #include #include @@ -32,6 +33,7 @@ #include #include #include +#include #include #ifndef OPENSSL_BIN @@ -57,6 +59,12 @@ static inline char *seprintf(char *ptr, char *end, const char *fmt, ...) { return ptr + n; } +static inline void set(char **field, const char *value) { + if (*field) free(*field); + *field = strdup(value); + if (!*field) err(EX_OSERR, "strdup"); +} + enum { MessageCap = 8191 + 512 }; enum { ParamCap = 15 }; @@ -92,6 +100,7 @@ static inline struct Message parse(char *line) { X("causal.agency/consumer", CapConsumer) \ X("causal.agency/passive", CapPassive) \ X("chghost", CapChghost) \ + X("draft/read-marker", CapReadMarker) \ X("echo-message", CapEchoMessage) \ X("extended-join", CapExtendedJoin) \ X("extended-monitor", CapExtendedMonitor) \ @@ -238,6 +247,7 @@ void clientSend(struct Client *client, const char *ptr, size_t len); void clientFormat(struct Client *client, const char *format, ...) __attribute__((format(printf, 2, 3))); void clientConsume(struct Client *client); +void clientGetMarker(struct Client *client, const char *target); extern bool stateNoNames; extern enum Cap stateCaps; diff --git a/client.c b/client.c index 3827b87..2227a4c 100644 --- a/client.c +++ b/client.c @@ -42,7 +42,13 @@ #include "bounce.h" -enum Cap clientCaps = CapServerTime | CapConsumer | CapPassive | CapSTS; +enum Cap clientCaps = 0 + | CapConsumer + | CapPassive + | CapReadMarker + | CapSTS + | CapServerTime; + char *clientOrigin; char *clientPass; char *clientAway; @@ -380,6 +386,91 @@ static void handlePalaver(struct Client *client, struct Message *msg) { clientProduce(client, buf); } +struct Marker { + char *target; + char *timestamp; +}; + +static struct { + struct Marker *ptr; + size_t cap, len; +} markers; + +void clientGetMarker(struct Client *client, const char *target) { + for (size_t i = 0; i < markers.len; ++i) { + struct Marker marker = markers.ptr[i]; + if (strcasecmp(marker.target, target)) continue; + clientFormat( + client, ":%s MARKREAD %s timestamp=%s\r\n", + clientOrigin, target, marker.timestamp + ); + return; + } + clientFormat(client, ":%s MARKREAD %s *\r\n", clientOrigin, target); +} + +static void clientSetMarker( + struct Client *client, const char *target, const char *timestamp +) { + struct Marker *marker = NULL; + for (size_t i = 0; i < markers.len; ++i) { + marker = &markers.ptr[i]; + if (strcasecmp(marker->target, target)) continue; + if (strcmp(timestamp, marker->timestamp) > 0) { + set(&marker->timestamp, timestamp); + } + goto reply; + } + if (markers.len == markers.cap) { + markers.cap = (markers.cap ? markers.cap * 2 : 8); + markers.ptr = realloc(markers.ptr, sizeof(*markers.ptr) * markers.cap); + if (!markers.ptr) err(EX_OSERR, "realloc"); + } + marker = &markers.ptr[markers.len++]; + *marker = (struct Marker) {0}; + set(&marker->target, target); + set(&marker->timestamp, timestamp); +reply: + clientFormat( + client, ":%s MARKREAD %s timestamp=%s\r\n", + clientOrigin, target, marker->timestamp + ); +} + +static regex_t *TimestampRegex(void) { + static const char *Pattern = { +#define R2D "[0-9]{2}" + "^timestamp=[0-9]{4,}-" R2D "-" R2D + "T" R2D ":" R2D ":" R2D "[.][0-9]{3}Z$" +#undef R2D + }; + static bool compiled; + static regex_t regex; + if (!compiled) { + int error = regcomp(®ex, Pattern, REG_EXTENDED | REG_NOSUB); + assert(!error); + } + compiled = true; + return ®ex; +} + +static void handleMarkRead(struct Client *client, struct Message *msg) { + if (!msg->params[0]) { + clientFormat( + client, "FAIL MARKREAD NEED_MORE_PARAMS :Missing parameters\r\n" + ); + } else if (!msg->params[1]) { + clientGetMarker(client, msg->params[0]); + } else if (regexec(TimestampRegex(), msg->params[1], 0, NULL, 0)) { + clientFormat( + client, "FAIL MARKREAD INVALID_PARAMS %s :Invalid parameters\r\n", + msg->params[1] + ); + } else { + clientSetMarker(client, msg->params[0], &msg->params[1][10]); + } +} + static void handlePong(struct Client *client, struct Message *msg) { (void)client; (void)msg; @@ -399,6 +490,7 @@ static const struct { { true, false, "CAP", handleCap }, { true, false, "PALAVER", handlePalaver }, { true, false, "PONG", handlePong }, + { true, true, "MARKREAD", handleMarkRead }, { true, true, "NOTICE", handlePrivmsg }, { true, true, "PRIVMSG", handlePrivmsg }, { true, true, "QUIT", handleQuit }, diff --git a/pounce.1 b/pounce.1 index 79517a3..e4919d2 100644 --- a/pounce.1 +++ b/pounce.1 @@ -826,6 +826,14 @@ can be adjusted with .Re .It .Rs +.%A Simon Ser +.%A delthas +.%T Read marker +.%I IRCv3 Working Group +.%U https://ircv3.net/specs/extensions/read-marker +.Re +.It +.Rs .%A K. Zeilenga, Ed. .%T The PLAIN Simple Authentication and Security Layer (SASL) Mechanism .%I IETF diff --git a/state.c b/state.c index 924bb8f..a28b3ba 100644 --- a/state.c +++ b/state.c @@ -53,12 +53,6 @@ static void require(const struct Message *msg, bool origin, size_t len) { } } -static void set(char **field, const char *value) { - if (*field) free(*field); - *field = strdup(value); - if (!*field) err(EX_OSERR, "strdup"); -} - // Maximum size of one AUTHENTICATE message. enum { AuthLen = 299 }; static char plainBase64[BASE64_SIZE(AuthLen)]; @@ -90,6 +84,7 @@ static const enum Cap DontReq = 0 | CapConsumer | CapPalaverApp | CapPassive + | CapReadMarker | CapSASL | CapSTS | CapUnsupported; @@ -475,6 +470,9 @@ void stateSync(struct Client *client) { clientOrigin, self.nick, chan->name, chan->topic ); } + if (client->caps & CapReadMarker) { + clientGetMarker(client, chan->name); + } if (stateNoNames) continue; serverEnqueue("NAMES %s\r\n", chan->name); } -- cgit 1.4.1