| |
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 } |