libpurple/protocols/ircv3/purpleircv3parser.c

changeset 41774
170078e728c0
child 41796
ebe4ff278b02
equal deleted inserted replaced
41773:c4acd02fdf73 41774:170078e728c0
1 /*
2 * Purple - Internet Messaging Library
3 * Copyright (C) Pidgin Developers <devel@pidgin.im>
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, see <https://www.gnu.org/licenses/>.
17 */
18
19 #include "purpleircv3parser.h"
20
21 #include "purpleircv3core.h"
22
23 struct _PurpleIRCv3Parser {
24 GObject parent;
25
26 GRegex *regex_message;
27 GRegex *regex_tags;
28
29 PurpleIRCv3MessageHandler fallback_handler;
30 GHashTable *handlers;
31 };
32
33 G_DEFINE_TYPE(PurpleIRCv3Parser, purple_ircv3_parser, G_TYPE_OBJECT)
34
35 /******************************************************************************
36 * Helpers
37 *****************************************************************************/
38 static GHashTable *
39 purple_ircv3_parser_parse_tags(PurpleIRCv3Parser *parser,
40 const gchar *tags_string, GError **error)
41 {
42 GError *local_error = NULL;
43 GHashTable *tags = NULL;
44 GMatchInfo *info = NULL;
45 gboolean matches = FALSE;
46
47 tags = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free);
48
49 /* tags_string can never be NULL, because g_match_info_fetch_named always
50 * returns a string. So if we were passed an empty string, just return the
51 * empty hash table.
52 */
53 if(*tags_string == '\0') {
54 return tags;
55 }
56
57 matches = g_regex_match_full(parser->regex_tags, tags_string, -1, 0, 0,
58 &info, &local_error);
59
60 if(local_error != NULL) {
61 g_propagate_error(error, local_error);
62
63 g_match_info_unref(info);
64
65 return tags;
66 }
67
68 if(!matches) {
69 g_set_error_literal(error, PURPLE_IRCV3_DOMAIN, 0,
70 "failed to parse tags: unknown error");
71
72 g_match_info_unref(info);
73
74 return tags;
75 }
76
77 while(g_match_info_matches(info)) {
78 gchar *key = NULL;
79 gchar *value = NULL;
80
81 key = g_match_info_fetch_named(info, "key");
82 value = g_match_info_fetch_named(info, "value");
83
84 /* the hash table is created with destroy notifies for both key and
85 * value, so there's no need to free the allocated memory right now.
86 */
87 g_hash_table_insert(tags, key, value);
88 g_match_info_next(info, &local_error);
89
90 if(local_error != NULL) {
91 g_propagate_error(error, local_error);
92
93 break;
94 }
95 }
96
97 g_match_info_unref(info);
98
99 return tags;
100 }
101
102 static guint
103 purple_ircv3_parser_extract_params(PurpleIRCv3Parser *parser,
104 GStrvBuilder *builder, const gchar *str)
105 {
106 gchar *ptr = NULL;
107 guint count = 0;
108
109 /* Loop through str finding each space separated string. */
110 while(str != NULL && *str != '\0') {
111 /* Look for a space. */
112 ptr = strchr(str, ' ');
113
114 /* If we found one, set it to null terminator and add the string to our
115 * builder.
116 */
117 if(ptr != NULL) {
118 *ptr = '\0';
119 g_strv_builder_add(builder, str);
120
121 /* Move str to the next character as we know there's another
122 * character which might be another null terminator.
123 */
124 str = ptr + 1;
125
126 /* And don't forget to increment the count... ah ah ah! */
127 count++;
128 } else {
129 /* Add the remaining string. */
130 g_strv_builder_add(builder, str);
131
132 /* Give the count another one, ah ah ah! */
133 count++;
134
135 /* Finally break out of the loop. */
136 break;
137 }
138 }
139
140 return count;
141 }
142
143 static GStrv
144 purple_ircv3_parser_build_params(PurpleIRCv3Parser *parser,
145 const gchar *middle, const gchar *coda,
146 const gchar *trailing, guint *n_params)
147 {
148 GStrvBuilder *builder = g_strv_builder_new();
149 GStrv result = NULL;
150 guint count = 0;
151
152 *n_params = 0;
153
154 count = purple_ircv3_parser_extract_params(parser, builder, middle);
155 *n_params = *n_params + count;
156
157 if(*coda != '\0') {
158 g_strv_builder_add(builder, trailing);
159 *n_params = *n_params + 1;
160 }
161
162 result = g_strv_builder_end(builder);
163
164 g_strv_builder_unref(builder);
165
166 return result;
167 }
168
169 /******************************************************************************
170 * Handlers
171 *****************************************************************************/
172 static gboolean
173 purple_ircv3_fallback_handler(G_GNUC_UNUSED GHashTable *tags,
174 G_GNUC_UNUSED const gchar *source,
175 G_GNUC_UNUSED const gchar *command,
176 G_GNUC_UNUSED guint n_params,
177 G_GNUC_UNUSED GStrv params,
178 GError **error,
179 G_GNUC_UNUSED gpointer data)
180 {
181 g_set_error(error, PURPLE_IRCV3_DOMAIN, 0, "no handler for command %s",
182 command);
183
184 return FALSE;
185 }
186
187 /******************************************************************************
188 * GObject Implementation
189 *****************************************************************************/
190 static void
191 purple_ircv3_parser_finalize(GObject *obj) {
192 PurpleIRCv3Parser *parser = PURPLE_IRCV3_PARSER(obj);
193
194 g_clear_pointer(&parser->regex_message, g_regex_unref);
195 g_clear_pointer(&parser->regex_tags, g_regex_unref);
196
197 g_hash_table_destroy(parser->handlers);
198
199 G_OBJECT_CLASS(purple_ircv3_parser_parent_class)->finalize(obj);
200 }
201
202 static void
203 purple_ircv3_parser_init(PurpleIRCv3Parser *parser) {
204 parser->regex_message = g_regex_new("(?:@(?<tags>[^ ]+) )?"
205 "(?::(?<source>[^ ]+) +)?"
206 "(?<command>[^ :]+)"
207 "(?: +(?<middle>(?:[^ :]+(?: +[^ :]+)*)))*"
208 "(?<coda> +:(?<trailing>.*)?)?",
209 0, 0, NULL);
210 g_assert(parser->regex_message != NULL);
211
212 parser->regex_tags = g_regex_new("(?:(?<key>[A-Za-z0-9-\\/]+)"
213 "(?:=(?<value>[^\\r\\n;]*))?(?:;|$))",
214 0, 0, NULL);
215 g_assert(parser->regex_tags != NULL);
216
217 parser->fallback_handler = purple_ircv3_fallback_handler;
218 parser->handlers = g_hash_table_new_full(g_str_hash, g_str_equal, g_free,
219 NULL);
220 }
221
222 static void
223 purple_ircv3_parser_class_init(PurpleIRCv3ParserClass *klass) {
224 GObjectClass *obj_class = G_OBJECT_CLASS(klass);
225
226 obj_class->finalize = purple_ircv3_parser_finalize;
227 }
228
229 /******************************************************************************
230 * Public API
231 *****************************************************************************/
232 PurpleIRCv3Parser *
233 purple_ircv3_parser_new(void) {
234 return g_object_new(PURPLE_IRCV3_TYPE_PARSER, NULL);
235 }
236
237 void
238 purple_ircv3_parser_set_fallback_handler(PurpleIRCv3Parser *parser,
239 PurpleIRCv3MessageHandler handler)
240 {
241 g_return_if_fail(PURPLE_IRCV3_IS_PARSER(parser));
242
243 parser->fallback_handler = handler;
244 }
245
246 gboolean
247 purple_ircv3_parser_parse(PurpleIRCv3Parser *parser, const gchar *buffer,
248 GError **error, gpointer data)
249 {
250 PurpleIRCv3MessageHandler handler = NULL;
251 GError *local_error = NULL;
252 GHashTable *tags = NULL;
253 GMatchInfo *info = NULL;
254 GStrv params = NULL;
255 gchar *coda = NULL;
256 gchar *command = NULL;
257 gchar *middle = NULL;
258 gchar *source = NULL;
259 gchar *tags_string = NULL;
260 gchar *trailing = NULL;
261 gboolean matches = FALSE;
262 gboolean result = FALSE;
263 guint n_params = 0;
264
265 g_return_val_if_fail(PURPLE_IRCV3_IS_PARSER(parser), FALSE);
266 g_return_val_if_fail(buffer != NULL, FALSE);
267
268 /* Check if the buffer matches our regex for messages. */
269 matches = g_regex_match(parser->regex_message, buffer, 0, &info);
270 if(!matches) {
271 g_set_error(error, PURPLE_IRCV3_DOMAIN, 0,
272 "failed to parser buffer '%s'", buffer);
273
274 g_match_info_unref(info);
275
276 return FALSE;
277 }
278
279 /* Extract the command from the buffer, so we can find the handler. */
280 command = g_match_info_fetch_named(info, "command");
281 handler = g_hash_table_lookup(parser->handlers, command);
282 if(handler == NULL) {
283 if(parser->fallback_handler == NULL) {
284 g_set_error(error, PURPLE_IRCV3_DOMAIN, 0,
285 "no handler found for command %s and no default "
286 "handler set.", command);
287
288 g_free(command);
289 g_match_info_unref(info);
290
291 return FALSE;
292 }
293
294 handler = parser->fallback_handler;
295 }
296
297 /* If we made it this far, we have our handler, so lets get the rest of the
298 * parameters and call the handler.
299 */
300 tags_string = g_match_info_fetch_named(info, "tags");
301 tags = purple_ircv3_parser_parse_tags(parser, tags_string, &local_error);
302 g_free(tags_string);
303 if(local_error != NULL) {
304 g_propagate_error(error, local_error);
305
306 g_free(command);
307 g_hash_table_destroy(tags);
308 g_match_info_unref(info);
309
310 return FALSE;
311 }
312
313 source = g_match_info_fetch_named(info, "source");
314 middle = g_match_info_fetch_named(info, "middle");
315 coda = g_match_info_fetch_named(info, "coda");
316 trailing = g_match_info_fetch_named(info, "trailing");
317
318 params = purple_ircv3_parser_build_params(parser, middle, coda, trailing,
319 &n_params);
320
321 /* Call the handler. */
322 result = handler(tags, source, command, n_params, params, error, data);
323
324 /* Cleanup everything. */
325 g_free(source);
326 g_free(command);
327 g_free(middle);
328 g_free(coda);
329 g_free(trailing);
330 g_strfreev(params);
331 g_hash_table_destroy(tags);
332 g_match_info_unref(info);
333
334 return result;
335 }

mercurial