Mercurial > pidgin.yaz
annotate src/protocols/irc/parse.c @ 7971:6fca0d9cc98b
[gaim-migrate @ 8648]
this particular work of art is topic changing support for jabber, and
support for setting the topic by just changing the text in the chat window.
hopefully someone less lazy than I will implement the right function for
IRC, and any other chats that do topics.
committer: Tailor Script <tailor@pidgin.im>
author | Nathan Walp <nwalp@pidgin.im> |
---|---|
date | Fri, 02 Jan 2004 06:16:44 +0000 |
parents | 828856b7fe30 |
children | ad5c8f01882b |
rev | line source |
---|---|
6333 | 1 /** |
2 * @file parse.c | |
3 * | |
4 * gaim | |
5 * | |
6 * Copyright (C) 2003, Ethan Blanton <eblanton@cs.purdue.edu> | |
7 * | |
8 * This program is free software; you can redistribute it and/or modify | |
9 * it under the terms of the GNU General Public License as published by | |
10 * the Free Software Foundation; either version 2 of the License, or | |
11 * (at your option) any later version. | |
12 * | |
13 * This program is distributed in the hope that it will be useful, | |
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
16 * GNU General Public License for more details. | |
17 * | |
18 * You should have received a copy of the GNU General Public License | |
19 * along with this program; if not, write to the Free Software | |
20 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA | |
21 */ | |
22 | |
23 #include "internal.h" | |
24 | |
25 #include "accountopt.h" | |
26 #include "conversation.h" | |
27 #include "notify.h" | |
28 #include "debug.h" | |
29 #include "irc.h" | |
30 | |
31 #include <stdio.h> | |
32 #include <stdlib.h> | |
33 #include <ctype.h> | |
34 | |
35 static char *irc_send_convert(struct irc_conn *irc, const char *string); | |
36 static char *irc_recv_convert(struct irc_conn *irc, const char *string); | |
37 | |
38 char *irc_mirc2html(const char *string); | |
39 | |
40 static void irc_parse_error_cb(struct irc_conn *irc, char *input); | |
41 | |
42 static char *irc_mirc_colors[16] = { | |
43 "white", "black", "blue", "dark green", "red", "brown", "purple", | |
44 "orange", "yellow", "green", "teal", "cyan", "light blue", | |
45 "pink", "grey", "light grey" }; | |
46 | |
47 /*typedef void (*IRCMsgCallback)(struct irc_conn *irc, char *from, char *name, char **args);*/ | |
48 static struct _irc_msg { | |
49 char *name; | |
50 char *format; | |
51 void (*cb)(struct irc_conn *irc, const char *name, const char *from, char **args); | |
52 } _irc_msgs[] = { | |
53 { "301", "nn:", irc_msg_away }, /* User is away */ | |
54 { "303", "n:", irc_msg_ison }, /* ISON reply */ | |
55 { "311", "nnvvv:", irc_msg_whois }, /* Whois user */ | |
56 { "312", "nnv:", irc_msg_whois }, /* Whois server */ | |
57 { "313", "nn:", irc_msg_whois }, /* Whois ircop */ | |
58 { "317", "nnvv", irc_msg_whois }, /* Whois idle */ | |
59 { "318", "nt:", irc_msg_endwhois }, /* End of WHOIS */ | |
60 { "319", "nn:", irc_msg_whois }, /* Whois channels */ | |
61 { "320", "nn:", irc_msg_whois }, /* Whois (fn ident) */ | |
62 { "324", "ncv:", irc_msg_chanmode }, /* Channel modes */ | |
63 { "331", "nc:", irc_msg_topic }, /* No channel topic */ | |
64 { "332", "nc:", irc_msg_topic }, /* Channel topic */ | |
65 { "333", "*", irc_msg_ignore }, /* Topic setter stuff */ | |
66 { "353", "nvc:", irc_msg_names }, /* Names list */ | |
67 { "366", "nc:", irc_msg_names }, /* End of names */ | |
68 { "372", "n:", irc_msg_motd }, /* MOTD */ | |
69 { "375", "n:", irc_msg_motd }, /* Start MOTD */ | |
70 { "376", "n:", irc_msg_endmotd }, /* End of MOTD */ | |
71 { "401", "nt:", irc_msg_nonick }, /* No such nick/chan */ | |
7877 | 72 { "403", "nc:", irc_msg_nochan }, /* No such channel */ |
6333 | 73 { "404", "nt:", irc_msg_nosend }, /* Cannot send to chan */ |
74 { "421", "nv:", irc_msg_unknown }, /* Unknown command */ | |
6350 | 75 { "422", "nv:", irc_msg_endmotd }, /* No MOTD available */ |
6333 | 76 { "433", "vn:", irc_msg_nickused }, /* Nickname already in use */ |
6718 | 77 { "438", "nn:", irc_msg_nochangenick }, /* Nick may not change */ |
6333 | 78 { "442", "nc:", irc_msg_notinchan }, /* Not in channel */ |
79 { "473", "nc:", irc_msg_inviteonly }, /* Tried to join invite-only */ | |
80 { "474", "nc:", irc_msg_banned }, /* Banned from channel */ | |
81 { "482", "nc:", irc_msg_notop }, /* Need to be op to do that */ | |
82 { "501", "n:", irc_msg_badmode }, /* Unknown mode flag */ | |
6714 | 83 { "515", "nc:", irc_msg_regonly }, /* Registration required */ |
6333 | 84 { "invite", "n:", irc_msg_invite }, /* Invited */ |
85 { "join", ":", irc_msg_join }, /* Joined a channel */ | |
86 { "kick", "cn:", irc_msg_kick }, /* KICK */ | |
87 { "mode", "tv:", irc_msg_mode }, /* MODE for channel */ | |
88 { "nick", ":", irc_msg_nick }, /* Nick change */ | |
89 { "notice", "t:", irc_msg_notice }, /* NOTICE recv */ | |
90 { "part", "c:", irc_msg_part }, /* Parted a channel */ | |
91 { "ping", ":", irc_msg_ping }, /* Received PING from server */ | |
92 { "pong", "v:", irc_msg_pong }, /* Received PONG from server */ | |
93 { "privmsg", "t:", irc_msg_privmsg }, /* Received private message */ | |
94 { "topic", "c:", irc_msg_topic }, /* TOPIC command */ | |
95 { "quit", ":", irc_msg_quit }, /* QUIT notice */ | |
96 { "wallops", ":", irc_msg_wallops }, /* WALLOPS command */ | |
97 { NULL, NULL, NULL } | |
98 }; | |
99 | |
100 static struct _irc_user_cmd { | |
101 char *name; | |
102 char *format; | |
103 IRCCmdCallback cb; | |
104 } _irc_cmds[] = { | |
105 { "away", ":", irc_cmd_away }, | |
106 { "deop", ":", irc_cmd_op }, | |
107 { "devoice", ":", irc_cmd_op }, | |
6415
e3be6b9744b7
[gaim-migrate @ 6922]
Christian Hammond <chipx86@chipx86.com>
parents:
6350
diff
changeset
|
108 { "help", "v", irc_cmd_help }, |
6333 | 109 { "invite", ":", irc_cmd_invite }, |
110 { "j", "cv", irc_cmd_join }, | |
111 { "join", "cv", irc_cmd_join }, | |
112 { "kick", "n:", irc_cmd_kick }, | |
113 { "me", ":", irc_cmd_ctcp_action }, | |
114 { "mode", ":", irc_cmd_mode }, | |
115 { "msg", "t:", irc_cmd_privmsg }, | |
116 { "names", "c", irc_cmd_names }, | |
117 { "nick", "n", irc_cmd_nick }, | |
118 { "op", ":", irc_cmd_op }, | |
119 { "operwall", ":", irc_cmd_wallops }, | |
120 { "part", "c:", irc_cmd_part }, | |
121 { "ping", "n", irc_cmd_ping }, | |
122 { "query", "n:", irc_cmd_query }, | |
123 { "quit", ":", irc_cmd_quit }, | |
124 { "quote", "*", irc_cmd_quote }, | |
125 { "remove", "n:", irc_cmd_remove }, | |
126 { "topic", ":", irc_cmd_topic }, | |
127 { "umode", ":", irc_cmd_mode }, | |
128 { "voice", ":", irc_cmd_op }, | |
129 { "wallops", ":", irc_cmd_wallops }, | |
130 { "whois", "n", irc_cmd_whois }, | |
7631 | 131 { NULL, NULL, NULL } |
6333 | 132 }; |
133 | |
134 static char *irc_send_convert(struct irc_conn *irc, const char *string) | |
135 { | |
136 char *utf8; | |
137 GError *err = NULL; | |
138 | |
139 utf8 = g_convert(string, strlen(string), | |
140 gaim_account_get_string(irc->account, "encoding", IRC_DEFAULT_CHARSET), | |
141 "UTF-8", NULL, NULL, &err); | |
142 if (err) { | |
143 gaim_debug(GAIM_DEBUG_ERROR, "irc", "send conversion error: %s\n", err->message); | |
144 gaim_debug(GAIM_DEBUG_ERROR, "irc", "Sending raw, which probably isn't right\n"); | |
145 utf8 = g_strdup(string); | |
146 } | |
147 | |
148 return utf8; | |
149 } | |
150 | |
151 static char *irc_recv_convert(struct irc_conn *irc, const char *string) | |
152 { | |
153 char *utf8; | |
154 GError *err = NULL; | |
155 | |
156 utf8 = g_convert(string, strlen(string), "UTF-8", | |
157 gaim_account_get_string(irc->account, "encoding", IRC_DEFAULT_CHARSET), | |
158 NULL, NULL, &err); | |
159 if (err) { | |
160 gaim_debug(GAIM_DEBUG_ERROR, "irc", "recv conversion error: %s\n", err->message); | |
161 utf8 = g_strdup(_("(There was an error converting this message. Check the 'Encoding' option in the Account Editor)")); | |
162 } | |
163 | |
164 return utf8; | |
165 } | |
166 | |
167 /* XXX tag closings are not necessarily correctly nested here! If we | |
168 * get a ^O or reach the end of the string and there are open | |
169 * tags, they are closed in a fixed order ... this means, for | |
170 * example, you might see <FONT COLOR="blue">some text <B>with | |
171 * various attributes</FONT></B> (notice that B and FONT overlap | |
172 * and are not cleanly nested). This is imminently fixable but | |
173 * I am not fixing it right now. | |
174 */ | |
175 char *irc_mirc2html(const char *string) | |
176 { | |
177 const char *cur, *end; | |
178 char fg[3] = "\0\0", bg[3] = "\0\0"; | |
179 int fgnum, bgnum; | |
6754 | 180 int font = 0, bold = 0, underline = 0; |
6333 | 181 GString *decoded = g_string_sized_new(strlen(string)); |
182 | |
183 cur = string; | |
184 do { | |
6754 | 185 end = strpbrk(cur, "\002\003\007\017\026\037"); |
6333 | 186 |
187 decoded = g_string_append_len(decoded, cur, end ? end - cur : strlen(cur)); | |
188 cur = end ? end : cur + strlen(cur); | |
189 | |
190 switch (*cur) { | |
191 case '\002': | |
192 cur++; | |
193 if (!bold) { | |
194 decoded = g_string_append(decoded, "<B>"); | |
195 bold = TRUE; | |
196 } else { | |
197 decoded = g_string_append(decoded, "</B>"); | |
198 bold = FALSE; | |
199 } | |
200 break; | |
201 case '\003': | |
202 cur++; | |
203 fg[0] = fg[1] = bg[0] = bg[1] = '\0'; | |
204 if (isdigit(*cur)) | |
205 fg[0] = *cur++; | |
206 if (isdigit(*cur)) | |
207 fg[1] = *cur++; | |
208 if (*cur == ',') { | |
209 cur++; | |
210 if (isdigit(*cur)) | |
211 bg[0] = *cur++; | |
212 if (isdigit(*cur)) | |
213 bg[1] = *cur++; | |
214 } | |
215 if (font) { | |
216 decoded = g_string_append(decoded, "</FONT>"); | |
217 font = FALSE; | |
218 } | |
219 | |
220 if (fg[0]) { | |
221 fgnum = atoi(fg); | |
222 if (fgnum < 0 || fgnum > 15) | |
223 continue; | |
224 font = TRUE; | |
225 g_string_append_printf(decoded, "<FONT COLOR=\"%s\"", irc_mirc_colors[fgnum]); | |
226 if (bg[0]) { | |
227 bgnum = atoi(bg); | |
228 if (bgnum >= 0 && bgnum < 16) | |
229 g_string_append_printf(decoded, " BACK=\"%s\"", irc_mirc_colors[bgnum]); | |
230 } | |
231 decoded = g_string_append_c(decoded, '>'); | |
232 } | |
233 break; | |
6754 | 234 case '\037': |
235 cur++; | |
236 if (!underline) { | |
237 decoded = g_string_append(decoded, "<U>"); | |
238 underline = TRUE; | |
239 } else { | |
240 decoded = g_string_append(decoded, "</U>"); | |
241 underline = TRUE; | |
242 } | |
243 break; | |
6333 | 244 case '\007': |
245 case '\026': | |
246 cur++; | |
247 break; | |
248 case '\017': | |
249 cur++; | |
250 /* fallthrough */ | |
251 case '\000': | |
252 if (bold) | |
6754 | 253 decoded = g_string_append(decoded, "</B>"); |
254 if (underline) | |
255 decoded = g_string_append(decoded, "</U>"); | |
6333 | 256 if (font) |
257 decoded = g_string_append(decoded, "</FONT>"); | |
258 break; | |
259 default: | |
260 gaim_debug(GAIM_DEBUG_ERROR, "irc", "Unexpected mIRC formatting character %d\n", *cur); | |
261 } | |
262 } while (*cur); | |
263 | |
264 return g_string_free(decoded, FALSE); | |
265 } | |
266 | |
267 char *irc_parse_ctcp(struct irc_conn *irc, const char *from, const char *to, const char *msg, int notice) | |
268 { | |
269 GaimConnection *gc; | |
270 const char *cur = msg + 1; | |
271 char *buf, *ctcp; | |
272 time_t timestamp; | |
273 | |
6754 | 274 /* Note that this is NOT correct w.r.t. multiple CTCPs in one |
275 * message and low-level quoting ... but if you want that crap, | |
276 * use a real IRC client. */ | |
277 | |
6333 | 278 if (msg[0] != '\001' || msg[strlen(msg) - 1] != '\001') |
279 return g_strdup(msg); | |
280 | |
281 if (!strncmp(cur, "ACTION ", 7)) { | |
282 cur += 7; | |
283 buf = g_strdup_printf("/me %s", cur); | |
284 buf[strlen(buf) - 1] = '\0'; | |
285 return buf; | |
286 } else if (!strncmp(cur, "PING ", 5)) { | |
287 if (notice) { /* reply */ | |
288 sscanf(cur, "PING %lu", ×tamp); | |
289 gc = gaim_account_get_connection(irc->account); | |
290 if (!gc) | |
291 return NULL; | |
6350 | 292 buf = g_strdup_printf(_("Reply time from %s: %lu seconds"), from, time(NULL) - timestamp); |
6333 | 293 gaim_notify_info(gc, _("PONG"), _("CTCP PING reply"), buf); |
294 g_free(buf); | |
295 return NULL; | |
296 } else { | |
297 buf = irc_format(irc, "vt:", "NOTICE", from, msg); | |
298 irc_send(irc, buf); | |
299 g_free(buf); | |
300 gc = gaim_account_get_connection(irc->account); | |
301 } | |
302 } else if (!strncmp(cur, "VERSION", 7) && !notice) { | |
303 buf = irc_format(irc, "vt:", "NOTICE", from, "\001VERSION Gaim IRC\001"); | |
304 irc_send(irc, buf); | |
305 g_free(buf); | |
306 } | |
307 | |
308 ctcp = g_strdup(msg + 1); | |
309 ctcp[strlen(ctcp) - 1] = '\0'; | |
310 buf = g_strdup_printf("Received CTCP '%s' (to %s) from %s", ctcp, to, from); | |
311 g_free(ctcp); | |
312 return buf; | |
313 } | |
314 | |
315 void irc_msg_table_build(struct irc_conn *irc) | |
316 { | |
317 int i; | |
318 | |
319 if (!irc || !irc->msgs) { | |
320 gaim_debug(GAIM_DEBUG_ERROR, "irc", "Attempt to build a message table on a bogus structure\n"); | |
321 return; | |
322 } | |
323 | |
324 for (i = 0; _irc_msgs[i].name; i++) { | |
325 g_hash_table_insert(irc->msgs, (gpointer)_irc_msgs[i].name, (gpointer)&_irc_msgs[i]); | |
326 } | |
327 } | |
328 | |
329 void irc_cmd_table_build(struct irc_conn *irc) | |
330 { | |
331 int i; | |
332 | |
333 if (!irc || !irc->cmds) { | |
334 gaim_debug(GAIM_DEBUG_ERROR, "irc", "Attempt to build a command table on a bogus structure\n"); | |
335 return; | |
336 } | |
337 | |
338 for (i = 0; _irc_cmds[i].name ; i++) { | |
339 g_hash_table_insert(irc->cmds, (gpointer)_irc_cmds[i].name, (gpointer)&_irc_cmds[i]); | |
340 } | |
341 } | |
342 | |
343 char *irc_format(struct irc_conn *irc, const char *format, ...) | |
344 { | |
345 GString *string = g_string_new(""); | |
346 char *tok, *tmp; | |
347 const char *cur; | |
348 va_list ap; | |
349 | |
350 va_start(ap, format); | |
351 for (cur = format; *cur; cur++) { | |
352 if (cur != format) | |
353 g_string_append_c(string, ' '); | |
354 | |
355 tok = va_arg(ap, char *); | |
356 switch (*cur) { | |
357 case 'v': | |
358 g_string_append(string, tok); | |
359 break; | |
360 case ':': | |
361 g_string_append_c(string, ':'); | |
362 /* no break! */ | |
363 case 't': | |
364 case 'n': | |
365 case 'c': | |
366 tmp = irc_send_convert(irc, tok); | |
367 g_string_append(string, tmp); | |
368 g_free(tmp); | |
369 break; | |
370 default: | |
371 gaim_debug(GAIM_DEBUG_ERROR, "irc", "Invalid format character '%c'\n", *cur); | |
372 break; | |
373 } | |
374 } | |
375 va_end(ap); | |
376 g_string_append(string, "\r\n"); | |
377 return (g_string_free(string, FALSE)); | |
378 } | |
379 | |
380 void irc_parse_msg(struct irc_conn *irc, char *input) | |
381 { | |
382 struct _irc_msg *msgent; | |
383 char *cur, *end, *tmp, *from, *msgname, *fmt, **args, *msg; | |
7631 | 384 guint i; |
6333 | 385 |
386 if (!strncmp(input, "PING ", 5)) { | |
387 msg = irc_format(irc, "vv", "PONG", input + 5); | |
388 irc_send(irc, msg); | |
389 g_free(msg); | |
390 return; | |
391 } else if (!strncmp(input, "ERROR ", 6)) { | |
392 gaim_connection_error(gaim_account_get_connection(irc->account), _("Disconnected")); | |
393 return; | |
394 } | |
395 | |
396 if (input[0] != ':' || (cur = strchr(input, ' ')) == NULL) { | |
397 irc_parse_error_cb(irc, input); | |
398 return; | |
399 } | |
400 | |
401 from = g_strndup(&input[1], cur - &input[1]); | |
402 cur++; | |
403 end = strchr(cur, ' '); | |
404 if (!end) | |
405 end = cur + strlen(cur); | |
406 | |
407 tmp = g_strndup(cur, end - cur); | |
408 msgname = g_ascii_strdown(tmp, -1); | |
409 g_free(tmp); | |
410 | |
411 if ((msgent = g_hash_table_lookup(irc->msgs, msgname)) == NULL) { | |
412 irc_msg_default(irc, "", from, &input); | |
413 g_free(msgname); | |
414 g_free(from); | |
415 return; | |
416 } | |
417 g_free(msgname); | |
418 | |
419 args = g_new0(char *, strlen(msgent->format)); | |
420 for (cur = end, fmt = msgent->format, i = 0; fmt[i] && *cur++; i++) { | |
421 switch (fmt[i]) { | |
422 case 'v': | |
423 if (!(end = strchr(cur, ' '))) end = cur + strlen(cur); | |
424 args[i] = g_strndup(cur, end - cur); | |
425 cur += end - cur; | |
426 break; | |
427 case 't': | |
428 case 'n': | |
429 case 'c': | |
430 if (!(end = strchr(cur, ' '))) end = cur + strlen(cur); | |
431 tmp = g_strndup(cur, end - cur); | |
432 args[i] = irc_recv_convert(irc, tmp); | |
433 g_free(tmp); | |
434 cur += end - cur; | |
435 break; | |
436 case ':': | |
437 if (*cur == ':') cur++; | |
438 args[i] = irc_recv_convert(irc, cur); | |
439 cur = cur + strlen(cur); | |
440 break; | |
441 case '*': | |
442 args[i] = g_strdup(cur); | |
443 cur = cur + strlen(cur); | |
444 break; | |
445 default: | |
446 gaim_debug(GAIM_DEBUG_ERROR, "irc", "invalid message format character '%c'\n", fmt[i]); | |
447 break; | |
448 } | |
449 } | |
6970 | 450 tmp = irc_recv_convert(irc, from); |
451 (msgent->cb)(irc, msgent->name, tmp, args); | |
452 g_free(tmp); | |
6333 | 453 for (i = 0; i < strlen(msgent->format); i++) { |
454 g_free(args[i]); | |
455 } | |
456 g_free(args); | |
457 g_free(from); | |
458 } | |
459 | |
460 int irc_parse_cmd(struct irc_conn *irc, const char *target, const char *cmdstr) | |
461 { | |
462 const char *cur, *end, *fmt; | |
463 char *tmp, *cmd, **args; | |
464 struct _irc_user_cmd *cmdent; | |
7631 | 465 guint i; |
466 int ret; | |
6333 | 467 |
468 cur = cmdstr; | |
469 end = strchr(cmdstr, ' '); | |
470 if (!end) | |
471 end = cur + strlen(cur); | |
472 | |
473 tmp = g_strndup(cur, end - cur); | |
474 cmd = g_utf8_strdown(tmp, -1); | |
475 g_free(tmp); | |
476 | |
477 if ((cmdent = g_hash_table_lookup(irc->cmds, cmd)) == NULL) { | |
478 ret = irc_cmd_default(irc, cmd, target, &cmdstr); | |
479 g_free(cmd); | |
480 return ret; | |
481 } | |
482 | |
483 args = g_new0(char *, strlen(cmdent->format)); | |
484 for (cur = end, fmt = cmdent->format, i = 0; fmt[i] && *cur++; i++) { | |
485 switch (fmt[i]) { | |
486 case 'v': | |
487 if (!(end = strchr(cur, ' '))) end = cur + strlen(cur); | |
488 args[i] = g_strndup(cur, end - cur); | |
489 cur += end - cur; | |
490 break; | |
491 case 't': | |
492 case 'n': | |
493 case 'c': | |
494 if (!(end = strchr(cur, ' '))) end = cur + strlen(cur); | |
495 args[i] = g_strndup(cur, end - cur); | |
496 cur += end - cur; | |
497 break; | |
498 case ':': | |
499 case '*': | |
500 args[i] = g_strdup(cur); | |
501 cur = cur + strlen(cur); | |
502 break; | |
503 default: | |
504 gaim_debug(GAIM_DEBUG_ERROR, "irc", "invalid command format character '%c'\n", fmt[i]); | |
505 break; | |
506 } | |
507 } | |
508 ret = (cmdent->cb)(irc, cmd, target, (const char **)args); | |
509 for (i = 0; i < strlen(cmdent->format); i++) | |
510 g_free(args[i]); | |
511 g_free(args); | |
512 | |
513 g_free(cmd); | |
514 return ret; | |
515 } | |
516 | |
517 static void irc_parse_error_cb(struct irc_conn *irc, char *input) | |
518 { | |
519 gaim_debug(GAIM_DEBUG_WARNING, "irc", "Unrecognized string: %s\n", input); | |
520 } |