summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--bounce.h10
-rw-r--r--client.c94
-rw-r--r--pounce.18
-rw-r--r--state.c10
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 <err.h>
#include <limits.h>
#include <stdarg.h>
#include <stdbool.h>
@@ -32,6 +33,7 @@
#include <stdlib.h>
#include <string.h>
#include <sys/time.h>
+#include <sysexits.h>
#include <tls.h>
#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(&regex, Pattern, REG_EXTENDED | REG_NOSUB);
+ assert(!error);
+ }
+ compiled = true;
+ return &regex;
+}
+
+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);
}