protocols/ircv3/purpleircv3parser.c

changeset 42652
225762d4e206
parent 42570
a980db2607fd
equal deleted inserted replaced
42651:5ad29b5bf1c7 42652:225762d4e206
1 /*
2 * Purple - Internet Messaging Library
3 * Copyright (C) Pidgin Developers <devel@pidgin.im>
4 *
5 * Purple is the legal property of its developers, whose names are too numerous
6 * to list here. Please refer to the COPYRIGHT file distributed with this
7 * source distribution.
8 *
9 * This library is free software; you can redistribute it and/or modify
10 * it under the terms of the GNU General Public License as published by
11 * the Free Software Foundation; either version 2 of the License, or
12 * (at your option) any later version.
13 *
14 * This library is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 * GNU General Public License for more details.
18 *
19 * You should have received a copy of the GNU General Public License
20 * along with this library; if not, see <https://www.gnu.org/licenses/>.
21 */
22
23 #include "purpleircv3parser.h"
24
25 #include "purpleircv3capabilities.h"
26 #include "purpleircv3constants.h"
27 #include "purpleircv3core.h"
28 #include "purpleircv3message.h"
29 #include "purpleircv3messagehandlers.h"
30 #include "purpleircv3sasl.h"
31
32 struct _PurpleIRCv3Parser {
33 GObject parent;
34
35 GRegex *regex_message;
36 GRegex *regex_tags;
37
38 PurpleIRCv3MessageHandler fallback_handler;
39 GHashTable *handlers;
40 };
41
42 G_DEFINE_FINAL_TYPE(PurpleIRCv3Parser, purple_ircv3_parser, G_TYPE_OBJECT)
43
44 /******************************************************************************
45 * Helpers
46 *****************************************************************************/
47 static char *
48 purple_ircv3_parser_unescape_tag_value(const char *value) {
49 GString *unescaped = g_string_new("");
50 gboolean escaping = FALSE;
51
52 /* Walk the string and replace escaped values according to
53 * https://ircv3.net/specs/extensions/message-tags.html#escaping-values
54 */
55 for(int i = 0; value[i] != '\0'; i++) {
56 if(escaping) {
57 /* Set the replacement to the current character which will fall
58 * through for everything, including '\\'
59 */
60 char replacement = value[i];
61
62 if(value[i] == ':') {
63 replacement = ';';
64 } else if(value[i] == 's') {
65 replacement = ' ';
66 } else if(value[i] == 'r') {
67 replacement = '\r';
68 } else if(value[i] == 'n') {
69 replacement = '\n';
70 }
71
72 g_string_append_c(unescaped, replacement);
73 escaping = FALSE;
74 } else {
75 if(value[i] == '\\') {
76 escaping = TRUE;
77 } else {
78 g_string_append_c(unescaped, value[i]);
79 }
80 }
81 }
82
83 return g_string_free(unescaped, FALSE);
84 }
85
86 static GHashTable *
87 purple_ircv3_parser_parse_tags(PurpleIRCv3Parser *parser,
88 const gchar *tags_string, GError **error)
89 {
90 GError *local_error = NULL;
91 GHashTable *tags = NULL;
92 GMatchInfo *info = NULL;
93 gboolean matches = FALSE;
94
95 tags = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free);
96
97 /* tags_string can never be NULL, because g_match_info_fetch_named always
98 * returns a string. So if we were passed an empty string, just return the
99 * empty hash table.
100 */
101 if(*tags_string == '\0') {
102 return tags;
103 }
104
105 matches = g_regex_match_full(parser->regex_tags, tags_string, -1, 0, 0,
106 &info, &local_error);
107
108 if(local_error != NULL) {
109 g_propagate_error(error, local_error);
110
111 g_match_info_unref(info);
112
113 return tags;
114 }
115
116 if(!matches) {
117 g_set_error_literal(error, PURPLE_IRCV3_DOMAIN, 0,
118 "failed to parse tags: unknown error");
119
120 g_match_info_unref(info);
121
122 return tags;
123 }
124
125 while(g_match_info_matches(info)) {
126 char *key = NULL;
127 char *value = NULL;
128 char *unescaped = NULL;
129
130 key = g_match_info_fetch_named(info, "key");
131 value = g_match_info_fetch_named(info, "value");
132
133 unescaped = purple_ircv3_parser_unescape_tag_value(value);
134 g_free(value);
135
136 /* the hash table is created with destroy notifies for both key and
137 * value, so there's no need to free the allocated memory right now.
138 */
139 g_hash_table_insert(tags, key, unescaped);
140 g_match_info_next(info, &local_error);
141
142 if(local_error != NULL) {
143 g_propagate_error(error, local_error);
144
145 break;
146 }
147 }
148
149 g_match_info_unref(info);
150
151 return tags;
152 }
153
154 static guint
155 purple_ircv3_parser_extract_params(G_GNUC_UNUSED PurpleIRCv3Parser *parser,
156 GStrvBuilder *builder, const gchar *str)
157 {
158 gchar *ptr = NULL;
159 guint count = 0;
160
161 /* Loop through str finding each space separated string. */
162 while(str != NULL && *str != '\0') {
163 /* Look for a space. */
164 ptr = strchr(str, ' ');
165
166 /* If we found one, set it to null terminator and add the string to our
167 * builder.
168 */
169 if(ptr != NULL) {
170 *ptr = '\0';
171 g_strv_builder_add(builder, str);
172
173 /* Move str to the next character as we know there's another
174 * character which might be another null terminator.
175 */
176 str = ptr + 1;
177
178 /* And don't forget to increment the count... ah ah ah! */
179 count++;
180 } else {
181 /* Add the remaining string. */
182 g_strv_builder_add(builder, str);
183
184 /* Give the count another one, ah ah ah! */
185 count++;
186
187 /* Finally break out of the loop. */
188 break;
189 }
190 }
191
192 return count;
193 }
194
195 static GStrv
196 purple_ircv3_parser_build_params(PurpleIRCv3Parser *parser,
197 const gchar *middle, const gchar *coda,
198 const gchar *trailing, guint *n_params)
199 {
200 GStrvBuilder *builder = g_strv_builder_new();
201 GStrv result = NULL;
202
203 purple_ircv3_parser_extract_params(parser, builder, middle);
204
205 if(*coda != '\0') {
206 g_strv_builder_add(builder, trailing);
207 }
208
209 result = g_strv_builder_end(builder);
210
211 g_strv_builder_unref(builder);
212
213 if(result != NULL && n_params != NULL) {
214 *n_params = g_strv_length(result);
215 }
216
217 return result;
218 }
219
220 /******************************************************************************
221 * Handlers
222 *****************************************************************************/
223 static gboolean
224 purple_ircv3_fallback_handler(PurpleIRCv3Message *message,
225 GError **error,
226 G_GNUC_UNUSED gpointer data)
227 {
228 g_set_error(error, PURPLE_IRCV3_DOMAIN, 0, "no handler for command %s",
229 purple_ircv3_message_get_command(message));
230
231 return FALSE;
232 }
233
234 /******************************************************************************
235 * GObject Implementation
236 *****************************************************************************/
237 static void
238 purple_ircv3_parser_finalize(GObject *obj) {
239 PurpleIRCv3Parser *parser = PURPLE_IRCV3_PARSER(obj);
240
241 g_clear_pointer(&parser->regex_message, g_regex_unref);
242 g_clear_pointer(&parser->regex_tags, g_regex_unref);
243
244 g_hash_table_destroy(parser->handlers);
245
246 G_OBJECT_CLASS(purple_ircv3_parser_parent_class)->finalize(obj);
247 }
248
249 static void
250 purple_ircv3_parser_init(PurpleIRCv3Parser *parser) {
251 parser->regex_message = g_regex_new("(?:@(?<tags>[^ ]+) )?"
252 "(?::(?<source>[^ ]+) +)?"
253 "(?<command>[^ :]+)"
254 "(?: +(?<middle>(?:[^ :]+(?: +[^ :]+)*)))*"
255 "(?<coda> +:(?<trailing>.*)?)?",
256 0, 0, NULL);
257 g_assert(parser->regex_message != NULL);
258
259 parser->regex_tags = g_regex_new("(?<key>(?<client_prefix>\\+?)"
260 "(?:(?<vendor>[A-Za-z0-9-\\.]+)\\/)?"
261 "(?<key_name>[A-Za-z0-9-]+)"
262 ")"
263 "(?:=(?<value>[^\r\n;]*))?(?:;|$)",
264 0, 0, NULL);
265 g_assert(parser->regex_tags != NULL);
266
267 parser->fallback_handler = purple_ircv3_fallback_handler;
268 parser->handlers = g_hash_table_new_full(g_str_hash, g_str_equal, g_free,
269 NULL);
270 }
271
272 static void
273 purple_ircv3_parser_class_init(PurpleIRCv3ParserClass *klass) {
274 GObjectClass *obj_class = G_OBJECT_CLASS(klass);
275
276 obj_class->finalize = purple_ircv3_parser_finalize;
277 }
278
279 /******************************************************************************
280 * Public API
281 *****************************************************************************/
282 PurpleIRCv3Parser *
283 purple_ircv3_parser_new(void) {
284 return g_object_new(PURPLE_IRCV3_TYPE_PARSER, NULL);
285 }
286
287 void
288 purple_ircv3_parser_set_fallback_handler(PurpleIRCv3Parser *parser,
289 PurpleIRCv3MessageHandler handler)
290 {
291 g_return_if_fail(PURPLE_IRCV3_IS_PARSER(parser));
292
293 parser->fallback_handler = handler;
294 }
295
296 gboolean
297 purple_ircv3_parser_parse(PurpleIRCv3Parser *parser, const gchar *buffer,
298 GError **error, gpointer data)
299 {
300 PurpleIRCv3Message *message = NULL;
301 PurpleIRCv3MessageHandler handler = NULL;
302 GError *local_error = NULL;
303 GHashTable *tags = NULL;
304 GMatchInfo *info = NULL;
305 GStrv params = NULL;
306 gchar *coda = NULL;
307 gchar *command = NULL;
308 gchar *middle = NULL;
309 gchar *source = NULL;
310 gchar *tags_string = NULL;
311 gchar *trailing = NULL;
312 gboolean matches = FALSE;
313 gboolean result = FALSE;
314
315 g_return_val_if_fail(PURPLE_IRCV3_IS_PARSER(parser), FALSE);
316 g_return_val_if_fail(buffer != NULL, FALSE);
317
318 /* Check if the buffer matches our regex for messages. */
319 matches = g_regex_match(parser->regex_message, buffer, 0, &info);
320 if(!matches) {
321 g_set_error(error, PURPLE_IRCV3_DOMAIN, 0,
322 "failed to parser buffer '%s'", buffer);
323
324 g_match_info_unref(info);
325
326 return FALSE;
327 }
328
329 /* Extract the command from the buffer, so we can find the handler. */
330 command = g_match_info_fetch_named(info, "command");
331 handler = g_hash_table_lookup(parser->handlers, command);
332 if(handler == NULL) {
333 if(parser->fallback_handler == NULL) {
334 g_set_error(error, PURPLE_IRCV3_DOMAIN, 0,
335 "no handler found for command %s and no default "
336 "handler set.", command);
337
338 g_free(command);
339 g_match_info_unref(info);
340
341 return FALSE;
342 }
343
344 handler = parser->fallback_handler;
345 }
346
347 /* If we made it this far, we have our handler, so lets get the rest of the
348 * parameters and call the handler.
349 */
350 message = purple_ircv3_message_new(command);
351
352 tags_string = g_match_info_fetch_named(info, "tags");
353 tags = purple_ircv3_parser_parse_tags(parser, tags_string, &local_error);
354 g_free(tags_string);
355 if(local_error != NULL) {
356 g_propagate_error(error, local_error);
357
358 g_free(command);
359 g_clear_object(&message);
360 g_hash_table_destroy(tags);
361 g_match_info_unref(info);
362
363 return FALSE;
364 }
365 if(tags != NULL) {
366 purple_ircv3_message_set_tags(message, tags);
367 g_hash_table_unref(tags);
368 }
369
370 source = g_match_info_fetch_named(info, "source");
371 if(!purple_strempty(source)) {
372 purple_ircv3_message_set_source(message, source);
373 }
374 g_free(source);
375
376 middle = g_match_info_fetch_named(info, "middle");
377 coda = g_match_info_fetch_named(info, "coda");
378 trailing = g_match_info_fetch_named(info, "trailing");
379 params = purple_ircv3_parser_build_params(parser, middle, coda, trailing,
380 NULL);
381 if(params != NULL) {
382 purple_ircv3_message_set_params(message, params);
383 }
384
385 g_free(command);
386 g_free(middle);
387 g_free(coda);
388 g_free(trailing);
389 g_strfreev(params);
390
391 /* Call the handler. */
392 result = handler(message, error, data);
393
394 /* Cleanup the left overs. */
395 g_match_info_unref(info);
396 g_clear_object(&message);
397
398 return result;
399 }
400
401 void
402 purple_ircv3_parser_add_handler(PurpleIRCv3Parser *parser,
403 const char *command,
404 PurpleIRCv3MessageHandler handler)
405 {
406 g_return_if_fail(PURPLE_IRCV3_IS_PARSER(parser));
407 g_return_if_fail(command != NULL);
408 g_return_if_fail(handler != NULL);
409
410 g_hash_table_insert(parser->handlers, g_strdup(command), handler);
411 }
412
413 void
414 purple_ircv3_parser_add_handlers(PurpleIRCv3Parser *parser,
415 PurpleIRCv3MessageHandler handler,
416 ...)
417 {
418 va_list vargs;
419 const char *command = NULL;
420
421 g_return_if_fail(PURPLE_IRCV3_IS_PARSER(parser));
422 g_return_if_fail(handler != NULL);
423
424 va_start(vargs, handler);
425
426 while((command = va_arg(vargs, const char *)) != NULL) {
427 purple_ircv3_parser_add_handler(parser, command, handler);
428 }
429
430 va_end(vargs);
431 }
432
433 void
434 purple_ircv3_parser_add_default_handlers(PurpleIRCv3Parser *parser) {
435 g_return_if_fail(PURPLE_IRCV3_IS_PARSER(parser));
436
437 purple_ircv3_parser_set_fallback_handler(parser,
438 purple_ircv3_message_handler_fallback);
439
440 /* Core functionality. */
441 purple_ircv3_parser_add_handler(parser, PURPLE_IRCV3_MSG_CAP,
442 purple_ircv3_capabilities_message_handler);
443 purple_ircv3_parser_add_handler(parser, PURPLE_IRCV3_MSG_NOTICE,
444 purple_ircv3_message_handler_privmsg);
445 purple_ircv3_parser_add_handler(parser, PURPLE_IRCV3_MSG_PING,
446 purple_ircv3_message_handler_ping);
447 purple_ircv3_parser_add_handler(parser, PURPLE_IRCV3_MSG_PRIVMSG,
448 purple_ircv3_message_handler_privmsg);
449
450 /* Topic stuff. */
451 purple_ircv3_parser_add_handler(parser, PURPLE_IRCV3_MSG_TOPIC,
452 purple_ircv3_message_handler_topic);
453 purple_ircv3_parser_add_handler(parser, PURPLE_IRCV3_RPL_NOTOPIC,
454 purple_ircv3_message_handler_topic);
455 purple_ircv3_parser_add_handler(parser, PURPLE_IRCV3_RPL_TOPIC,
456 purple_ircv3_message_handler_topic);
457
458 /* Post Registration Greetings */
459 purple_ircv3_parser_add_handlers(parser,
460 purple_ircv3_message_handler_status_ignore_param0,
461 PURPLE_IRCV3_RPL_WELCOME,
462 PURPLE_IRCV3_RPL_YOURHOST,
463 PURPLE_IRCV3_RPL_CREATED,
464 PURPLE_IRCV3_RPL_MYINFO,
465 NULL);
466
467 /* Luser's */
468 purple_ircv3_parser_add_handlers(parser,
469 purple_ircv3_message_handler_status_ignore_param0,
470 PURPLE_IRCV3_RPL_LUSERCLIENT,
471 PURPLE_IRCV3_RPL_LUSEROP,
472 PURPLE_IRCV3_RPL_LUSERUNKNOWN,
473 PURPLE_IRCV3_RPL_LUSERCHANNELS,
474 PURPLE_IRCV3_RPL_LUSERME,
475 NULL);
476
477 /* MOTD */
478 purple_ircv3_parser_add_handlers(parser,
479 purple_ircv3_message_handler_status_ignore_param0,
480 PURPLE_IRCV3_RPL_MOTD,
481 PURPLE_IRCV3_RPL_MOTDSTART,
482 PURPLE_IRCV3_RPL_ENDOFMOTD,
483 NULL);
484
485 /* SASL stuff. */
486 purple_ircv3_parser_add_handler(parser, PURPLE_IRCV3_RPL_LOGGEDIN,
487 purple_ircv3_sasl_logged_in);
488 purple_ircv3_parser_add_handler(parser, PURPLE_IRCV3_RPL_LOGGEDOUT,
489 purple_ircv3_sasl_logged_out);
490 purple_ircv3_parser_add_handler(parser, PURPLE_IRCV3_ERR_NICKLOCKED,
491 purple_ircv3_sasl_nick_locked);
492 purple_ircv3_parser_add_handler(parser, PURPLE_IRCV3_RPL_SASLSUCCESS,
493 purple_ircv3_sasl_success);
494 purple_ircv3_parser_add_handler(parser, PURPLE_IRCV3_ERR_SASLFAIL,
495 purple_ircv3_sasl_failed);
496 purple_ircv3_parser_add_handler(parser, PURPLE_IRCV3_ERR_SASLTOOLONG,
497 purple_ircv3_sasl_message_too_long);
498 purple_ircv3_parser_add_handler(parser, PURPLE_IRCV3_ERR_SASLABORTED,
499 purple_ircv3_sasl_aborted);
500 purple_ircv3_parser_add_handler(parser, PURPLE_IRCV3_ERR_SASLALREADY,
501 purple_ircv3_sasl_already_authed);
502 purple_ircv3_parser_add_handler(parser, PURPLE_IRCV3_RPL_SASLMECHS,
503 purple_ircv3_sasl_mechanisms);
504 purple_ircv3_parser_add_handler(parser, PURPLE_IRCV3_MSG_AUTHENTICATE,
505 purple_ircv3_sasl_authenticate);
506
507 }

mercurial