Standardize all protocol header guard macros.
[pidgin-git.git] / libpurple / protocols / irc / parse.c
blob173fcac4902510b915e2c1de366dc17fd1f0b3b5
1 /**
2 * @file parse.c
4 * purple
6 * Copyright (C) 2003, Ethan Blanton <eblanton@cs.purdue.edu>
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.
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.
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., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA
23 #include "internal.h"
25 #include "accountopt.h"
26 #include "conversation.h"
27 #include "notify.h"
28 #include "debug.h"
29 #include "util.h"
30 #include "cmds.h"
31 #include "irc.h"
33 #include <stdio.h>
34 #include <stdlib.h>
35 #include <ctype.h>
37 static GSList *cmds = NULL;
39 static char *irc_send_convert(struct irc_conn *irc, const char *string);
40 static char *irc_recv_convert(struct irc_conn *irc, const char *string);
42 static void irc_parse_error_cb(struct irc_conn *irc, char *input);
44 static char *irc_mirc_colors[16] = {
45 "white", "black", "blue", "dark green", "red", "brown", "purple",
46 "orange", "yellow", "green", "teal", "cyan", "light blue",
47 "pink", "grey", "light grey" };
49 extern PurpleProtocol *_irc_protocol;
51 /*typedef void (*IRCMsgCallback)(struct irc_conn *irc, char *from, char *name, char **args);*/
52 static struct _irc_msg {
53 char *name;
54 char *format;
56 /** The required parameter count, based on values we use, not protocol
57 * specification. */
58 int req_cnt;
60 void (*cb)(struct irc_conn *irc, const char *name, const char *from, char **args);
61 } _irc_msgs[] = {
62 { "005", "n*", 2, irc_msg_features }, /* Feature list */
63 { "251", "n:", 1, irc_msg_luser }, /* Client & Server count */
64 { "255", "n:", 1, irc_msg_luser }, /* Client & Server count Mk. II */
65 { "301", "nn:", 3, irc_msg_away }, /* User is away */
66 { "303", "n:", 2, irc_msg_ison }, /* ISON reply */
67 { "311", "nnvvv:", 6, irc_msg_whois }, /* Whois user */
68 { "312", "nnv:", 4, irc_msg_whois }, /* Whois server */
69 { "313", "nn:", 2, irc_msg_whois }, /* Whois ircop */
70 { "314", "nnnvv:", 6, irc_msg_whois }, /* Whowas user */
71 { "315", "nt:", 0, irc_msg_who }, /* end of WHO channel */
72 { "317", "nnvv", 3, irc_msg_whois }, /* Whois idle */
73 { "318", "nt:", 2, irc_msg_endwhois }, /* End of WHOIS */
74 { "319", "nn:", 3, irc_msg_whois }, /* Whois channels */
75 { "320", "nn:", 2, irc_msg_whois }, /* Whois (fn ident) */
76 { "321", "*", 0, irc_msg_list }, /* Start of list */
77 { "322", "ncv:", 4, irc_msg_list }, /* List. */
78 { "323", ":", 0, irc_msg_list }, /* End of list. */
79 { "324", "ncv:", 3, irc_msg_chanmode }, /* Channel modes */
80 { "330", "nnv:", 4, irc_msg_whois }, /* Whois (fn login) */
81 { "331", "nc:", 3, irc_msg_topic }, /* No channel topic */
82 { "332", "nc:", 3, irc_msg_topic }, /* Channel topic */
83 { "333", "ncvv", 4, irc_msg_topicinfo }, /* Topic setter stuff */
84 { "352", "ncvvvnv:", 8, irc_msg_who }, /* Channel WHO */
85 { "353", "nvc:", 4, irc_msg_names }, /* Names list */
86 { "366", "nc:", 2, irc_msg_names }, /* End of names */
87 { "367", "ncnnv", 3, irc_msg_ban }, /* Ban list */
88 { "368", "nc:", 2, irc_msg_ban }, /* End of ban list */
89 { "369", "nt:", 2, irc_msg_endwhois }, /* End of WHOWAS */
90 { "372", "n:", 1, irc_msg_motd }, /* MOTD */
91 { "375", "n:", 1, irc_msg_motd }, /* Start MOTD */
92 { "376", "n:", 1, irc_msg_motd }, /* End of MOTD */
93 { "391", "nv:", 3, irc_msg_time }, /* Time reply */
94 { "401", "nt:", 2, irc_msg_nonick }, /* No such nick/chan */
95 { "403", "nc:", 2, irc_msg_nochan }, /* No such channel */
96 { "404", "nt:", 3, irc_msg_nosend }, /* Cannot send to chan */
97 { "406", "nt:", 2, irc_msg_nonick }, /* No such nick for WHOWAS */
98 { "421", "nv:", 2, irc_msg_unknown }, /* Unknown command */
99 { "422", "n:", 1, irc_msg_motd }, /* MOTD file missing */
100 { "432", "vn:", 0, irc_msg_badnick }, /* Erroneous nickname */
101 { "433", "vn:", 2, irc_msg_nickused }, /* Nickname already in use */
102 { "437", "nc:", 2, irc_msg_unavailable }, /* Nick/channel is unavailable */
103 { "438", "nn:", 3, irc_msg_nochangenick }, /* Nick may not change */
104 { "442", "nc:", 3, irc_msg_notinchan }, /* Not in channel */
105 { "473", "nc:", 2, irc_msg_inviteonly }, /* Tried to join invite-only */
106 { "474", "nc:", 2, irc_msg_banned }, /* Banned from channel */
107 { "477", "nc:", 3, irc_msg_regonly }, /* Registration Required */
108 { "478", "nct:", 3, irc_msg_banfull }, /* Banlist is full */
109 { "482", "nc:", 3, irc_msg_notop }, /* Need to be op to do that */
110 { "501", "n:", 2, irc_msg_badmode }, /* Unknown mode flag */
111 { "506", "nc:", 3, irc_msg_nosend }, /* Must identify to send */
112 { "515", "nc:", 3, irc_msg_regonly }, /* Registration required */
113 #ifdef HAVE_CYRUS_SASL
114 { "903", "*", 0, irc_msg_authok}, /* SASL auth successful */
115 { "904", "*", 0, irc_msg_authtryagain }, /* SASL auth failed, can recover*/
116 { "905", "*", 0, irc_msg_authfail }, /* SASL auth failed */
117 { "906", "*", 0, irc_msg_authfail }, /* SASL auth failed */
118 { "907", "*", 0, irc_msg_authfail }, /* SASL auth failed */
119 { "cap", "vv:", 3, irc_msg_cap }, /* SASL capable */
120 { "authenticate", ":", 1, irc_msg_authenticate }, /* SASL authenticate */
121 #endif
122 { "invite", "n:", 2, irc_msg_invite }, /* Invited */
123 { "join", ":", 1, irc_msg_join }, /* Joined a channel */
124 { "kick", "cn:", 3, irc_msg_kick }, /* KICK */
125 { "mode", "tv:", 2, irc_msg_mode }, /* MODE for channel */
126 { "nick", ":", 1, irc_msg_nick }, /* Nick change */
127 { "notice", "t:", 2, irc_msg_notice }, /* NOTICE recv */
128 { "part", "c:", 1, irc_msg_part }, /* Parted a channel */
129 { "ping", ":", 1, irc_msg_ping }, /* Received PING from server */
130 { "pong", "v:", 2, irc_msg_pong }, /* Received PONG from server */
131 { "privmsg", "t:", 2, irc_msg_privmsg }, /* Received private message */
132 { "topic", "c:", 2, irc_msg_topic }, /* TOPIC command */
133 { "quit", ":", 1, irc_msg_quit }, /* QUIT notice */
134 { "wallops", ":", 1, irc_msg_wallops }, /* WALLOPS command */
135 { NULL, NULL, 0, NULL }
138 static struct _irc_user_cmd {
139 char *name;
140 char *format;
141 IRCCmdCallback cb;
142 char *help;
143 } _irc_cmds[] = {
144 { "action", ":", irc_cmd_ctcp_action, N_("action &lt;action to perform&gt;: Perform an action.") },
145 { "authserv", ":", irc_cmd_service, N_("authserv: Send a command to authserv") },
146 { "away", ":", irc_cmd_away, N_("away [message]: Set an away message, or use no message to return from being away.") },
147 { "ctcp", "t:", irc_cmd_ctcp, N_("ctcp <nick> <msg>: sends ctcp msg to nick.") },
148 { "chanserv", ":", irc_cmd_service, N_("chanserv: Send a command to chanserv") },
149 { "deop", ":", irc_cmd_op, N_("deop &lt;nick1&gt; [nick2] ...: Remove channel operator status from someone. You must be a channel operator to do this.") },
150 { "devoice", ":", irc_cmd_op, N_("devoice &lt;nick1&gt; [nick2] ...: Remove channel voice status from someone, preventing them from speaking if the channel is moderated (+m). You must be a channel operator to do this.") },
151 { "invite", ":", irc_cmd_invite, N_("invite &lt;nick&gt; [room]: Invite someone to join you in the specified channel, or the current channel.") },
152 { "j", "cv", irc_cmd_join, N_("j &lt;room1&gt;[,room2][,...] [key1[,key2][,...]]: Enter one or more channels, optionally providing a channel key for each if needed.") },
153 { "join", "cv", irc_cmd_join, N_("join &lt;room1&gt;[,room2][,...] [key1[,key2][,...]]: Enter one or more channels, optionally providing a channel key for each if needed.") },
154 { "kick", "n:", irc_cmd_kick, N_("kick &lt;nick&gt; [message]: Remove someone from a channel. You must be a channel operator to do this.") },
155 { "list", ":", irc_cmd_list, N_("list: Display a list of chat rooms on the network. <i>Warning, some servers may disconnect you upon doing this.</i>") },
156 { "me", ":", irc_cmd_ctcp_action, N_("me &lt;action to perform&gt;: Perform an action.") },
157 { "memoserv", ":", irc_cmd_service, N_("memoserv: Send a command to memoserv") },
158 { "mode", ":", irc_cmd_mode, N_("mode &lt;+|-&gt;&lt;A-Za-z&gt; &lt;nick|channel&gt;: Set or unset a channel or user mode.") },
159 { "msg", "t:", irc_cmd_privmsg, N_("msg &lt;nick&gt; &lt;message&gt;: Send a private message to a user (as opposed to a channel).") },
160 { "names", "c", irc_cmd_names, N_("names [channel]: List the users currently in a channel.") },
161 { "nick", "n", irc_cmd_nick, N_("nick &lt;new nickname&gt;: Change your nickname.") },
162 { "nickserv", ":", irc_cmd_service, N_("nickserv: Send a command to nickserv") },
163 { "notice", "t:", irc_cmd_privmsg, N_("notice &lt;target&lt;: Send a notice to a user or channel.") },
164 { "op", ":", irc_cmd_op, N_("op &lt;nick1&gt; [nick2] ...: Grant channel operator status to someone. You must be a channel operator to do this.") },
165 { "operwall", ":", irc_cmd_wallops, N_("operwall &lt;message&gt;: If you don't know what this is, you probably can't use it.") },
166 { "operserv", ":", irc_cmd_service, N_("operserv: Send a command to operserv") },
167 { "part", "c:", irc_cmd_part, N_("part [room] [message]: Leave the current channel, or a specified channel, with an optional message.") },
168 { "ping", "n", irc_cmd_ping, N_("ping [nick]: Asks how much lag a user (or the server if no user specified) has.") },
169 { "query", "n:", irc_cmd_query, N_("query &lt;nick&gt; &lt;message&gt;: Send a private message to a user (as opposed to a channel).") },
170 { "quit", ":", irc_cmd_quit, N_("quit [message]: Disconnect from the server, with an optional message.") },
171 { "quote", "*", irc_cmd_quote, N_("quote [...]: Send a raw command to the server.") },
172 { "remove", "n:", irc_cmd_remove, N_("remove &lt;nick&gt; [message]: Remove someone from a room. You must be a channel operator to do this.") },
173 { "time", "", irc_cmd_time, N_("time: Displays the current local time at the IRC server.") },
174 { "topic", ":", irc_cmd_topic, N_("topic [new topic]: View or change the channel topic.") },
175 { "umode", ":", irc_cmd_mode, N_("umode &lt;+|-&gt;&lt;A-Za-z&gt;: Set or unset a user mode.") },
176 { "version", ":", irc_cmd_ctcp_version, N_("version [nick]: send CTCP VERSION request to a user") },
177 { "voice", ":", irc_cmd_op, N_("voice &lt;nick1&gt; [nick2] ...: Grant channel voice status to someone. You must be a channel operator to do this.") },
178 { "wallops", ":", irc_cmd_wallops, N_("wallops &lt;message&gt;: If you don't know what this is, you probably can't use it.") },
179 { "whois", "tt", irc_cmd_whois, N_("whois [server] &lt;nick&gt;: Get information on a user.") },
180 { "whowas", "t", irc_cmd_whowas, N_("whowas &lt;nick&gt;: Get information on a user that has logged off.") },
181 { NULL, NULL, NULL, NULL }
184 static PurpleCmdRet irc_parse_purple_cmd(PurpleConversation *conv, const gchar *cmd,
185 gchar **args, gchar **error, void *data)
187 PurpleConnection *gc;
188 struct irc_conn *irc;
189 struct _irc_user_cmd *cmdent;
191 gc = purple_conversation_get_connection(conv);
192 if (!gc)
193 return PURPLE_CMD_RET_FAILED;
195 irc = purple_connection_get_protocol_data(gc);
197 if ((cmdent = g_hash_table_lookup(irc->cmds, cmd)) == NULL)
198 return PURPLE_CMD_RET_FAILED;
200 (cmdent->cb)(irc, cmd, purple_conversation_get_name(conv), (const char **)args);
202 return PURPLE_CMD_RET_OK;
205 static void irc_register_command(struct _irc_user_cmd *c)
207 PurpleCmdId id;
208 PurpleCmdFlag f;
209 char args[10];
210 char *format;
211 size_t i;
213 f = PURPLE_CMD_FLAG_CHAT | PURPLE_CMD_FLAG_IM | PURPLE_CMD_FLAG_PROTOCOL_ONLY
214 | PURPLE_CMD_FLAG_ALLOW_WRONG_ARGS;
216 format = c->format;
218 for (i = 0; (i < (sizeof(args) - 1)) && *format; i++, format++)
219 switch (*format) {
220 case 'v':
221 case 'n':
222 case 'c':
223 case 't':
224 args[i] = 'w';
225 break;
226 case ':':
227 case '*':
228 args[i] = 's';
229 break;
232 args[i] = '\0';
234 id = purple_cmd_register(c->name, args, PURPLE_CMD_P_PROTOCOL, f, "prpl-irc",
235 irc_parse_purple_cmd, _(c->help), NULL);
236 cmds = g_slist_prepend(cmds, GUINT_TO_POINTER(id));
239 void irc_register_commands(void)
241 struct _irc_user_cmd *c;
243 for (c = _irc_cmds; c && c->name; c++)
244 irc_register_command(c);
247 void irc_unregister_commands(void)
249 while (cmds) {
250 PurpleCmdId id = GPOINTER_TO_UINT(cmds->data);
251 purple_cmd_unregister(id);
252 cmds = g_slist_delete_link(cmds, cmds);
256 static char *irc_send_convert(struct irc_conn *irc, const char *string)
258 char *utf8;
259 GError *err = NULL;
260 gchar **encodings;
261 const gchar *enclist;
263 enclist = purple_account_get_string(irc->account, "encoding", IRC_DEFAULT_CHARSET);
264 encodings = g_strsplit(enclist, ",", 2);
266 if (encodings[0] == NULL || !g_ascii_strcasecmp("UTF-8", encodings[0])) {
267 g_strfreev(encodings);
268 return NULL;
271 utf8 = g_convert(string, strlen(string), encodings[0], "UTF-8", NULL, NULL, &err);
272 if (err) {
273 purple_debug(PURPLE_DEBUG_ERROR, "irc", "Send conversion error: %s\n", err->message);
274 purple_debug(PURPLE_DEBUG_ERROR, "irc", "Sending as UTF-8 instead of %s\n", encodings[0]);
275 utf8 = g_strdup(string);
276 g_error_free(err);
278 g_strfreev(encodings);
280 return utf8;
283 static char *irc_recv_convert(struct irc_conn *irc, const char *string)
285 char *utf8 = NULL;
286 const gchar *charset, *enclist;
287 gchar **encodings;
288 gboolean autodetect;
289 int i;
291 autodetect = purple_account_get_bool(irc->account, "autodetect_utf8", IRC_DEFAULT_AUTODETECT);
293 if (autodetect && g_utf8_validate(string, -1, NULL)) {
294 return g_strdup(string);
297 enclist = purple_account_get_string(irc->account, "encoding", IRC_DEFAULT_CHARSET);
298 encodings = g_strsplit(enclist, ",", -1);
300 if (encodings[0] == NULL) {
301 g_strfreev(encodings);
302 return purple_utf8_salvage(string);
305 for (i = 0; encodings[i] != NULL; i++) {
306 charset = encodings[i];
307 while (*charset == ' ')
308 charset++;
310 if (!g_ascii_strcasecmp("UTF-8", charset)) {
311 if (g_utf8_validate(string, -1, NULL))
312 utf8 = g_strdup(string);
313 } else {
314 utf8 = g_convert(string, -1, "UTF-8", charset, NULL, NULL, NULL);
317 if (utf8) {
318 g_strfreev(encodings);
319 return utf8;
322 g_strfreev(encodings);
324 return purple_utf8_salvage(string);
327 /* This function is shamelessly stolen from glib--it is an old version of the
328 * private function append_escaped_text, used by g_markup_escape_text, whose
329 * behavior changed in glib 2.12. */
330 static void irc_append_escaped_text(GString *str, const char *text, gssize length)
332 const char *p = text;
333 const char *end = text + length;
334 const char *next = NULL;
336 while(p != end) {
337 next = g_utf8_next_char(p);
339 switch(*p) {
340 case '&':
341 g_string_append(str, "&amp;");
342 break;
343 case '<':
344 g_string_append(str, "&lt;");
345 break;
346 case '>':
347 g_string_append(str, "&gt;");
348 break;
349 case '\'':
350 g_string_append(str, "&apos;");
351 break;
352 case '"':
353 g_string_append(str, "&quot;");
354 break;
355 default:
356 g_string_append_len(str, p, next - p);
357 break;
360 p = next;
364 /* This function is shamelessly stolen from glib--it is an old version of the
365 * function g_markup_escape_text, whose behavior changed in glib 2.12. */
366 char *irc_escape_privmsg(const char *text, gssize length)
368 GString *str;
370 g_return_val_if_fail(text != NULL, NULL);
372 if(length < 0)
373 length = strlen(text);
375 str = g_string_sized_new(length);
377 irc_append_escaped_text(str, text, length);
379 return g_string_free(str, FALSE);
382 /* XXX tag closings are not necessarily correctly nested here! If we
383 * get a ^O or reach the end of the string and there are open
384 * tags, they are closed in a fixed order ... this means, for
385 * example, you might see <FONT COLOR="blue">some text <B>with
386 * various attributes</FONT></B> (notice that B and FONT overlap
387 * and are not cleanly nested). This is imminently fixable but
388 * I am not fixing it right now.
390 char *irc_mirc2html(const char *string)
392 const char *cur, *end;
393 char fg[3] = "\0\0", bg[3] = "\0\0";
394 int fgnum, bgnum;
395 int font = 0, bold = 0, underline = 0, italic = 0;
396 GString *decoded;
398 if (string == NULL)
399 return NULL;
401 decoded = g_string_sized_new(strlen(string));
403 cur = string;
404 do {
405 end = strpbrk(cur, "\002\003\007\017\026\037");
407 decoded = g_string_append_len(decoded, cur, (end ? (gssize)(end - cur) : (gssize)strlen(cur)));
408 cur = end ? end : cur + strlen(cur);
410 switch (*cur) {
411 case '\002':
412 cur++;
413 if (!bold) {
414 decoded = g_string_append(decoded, "<B>");
415 bold = TRUE;
416 } else {
417 decoded = g_string_append(decoded, "</B>");
418 bold = FALSE;
420 break;
421 case '\003':
422 cur++;
423 fg[0] = fg[1] = bg[0] = bg[1] = '\0';
424 if (isdigit(*cur))
425 fg[0] = *cur++;
426 if (isdigit(*cur))
427 fg[1] = *cur++;
428 if (*cur == ',') {
429 cur++;
430 if (isdigit(*cur))
431 bg[0] = *cur++;
432 if (isdigit(*cur))
433 bg[1] = *cur++;
435 if (font) {
436 decoded = g_string_append(decoded, "</FONT>");
437 font = FALSE;
440 if (fg[0]) {
441 fgnum = atoi(fg);
442 if (fgnum < 0 || fgnum > 15)
443 continue;
444 font = TRUE;
445 g_string_append_printf(decoded, "<FONT COLOR=\"%s\"", irc_mirc_colors[fgnum]);
446 if (bg[0]) {
447 bgnum = atoi(bg);
448 if (bgnum >= 0 && bgnum < 16)
449 g_string_append_printf(decoded, " BACK=\"%s\"", irc_mirc_colors[bgnum]);
451 decoded = g_string_append_c(decoded, '>');
453 break;
454 case '\011':
455 cur++;
456 if (!italic) {
457 decoded = g_string_append(decoded, "<I>");
458 italic = TRUE;
459 } else {
460 decoded = g_string_append(decoded, "</I>");
461 italic = FALSE;
463 break;
464 case '\037':
465 cur++;
466 if (!underline) {
467 decoded = g_string_append(decoded, "<U>");
468 underline = TRUE;
469 } else {
470 decoded = g_string_append(decoded, "</U>");
471 underline = FALSE;
473 break;
474 case '\007':
475 case '\026':
476 cur++;
477 break;
478 case '\017':
479 cur++;
480 /* fallthrough */
481 case '\000':
482 if (bold)
483 decoded = g_string_append(decoded, "</B>");
484 if (italic)
485 decoded = g_string_append(decoded, "</I>");
486 if (underline)
487 decoded = g_string_append(decoded, "</U>");
488 if (font)
489 decoded = g_string_append(decoded, "</FONT>");
490 bold = italic = underline = font = FALSE;
491 break;
492 default:
493 purple_debug(PURPLE_DEBUG_ERROR, "irc", "Unexpected mIRC formatting character %d\n", *cur);
495 } while (*cur);
497 return g_string_free(decoded, FALSE);
500 char *irc_mirc2txt (const char *string)
502 char *result;
503 int i, j;
505 if (string == NULL)
506 return NULL;
508 result = g_strdup (string);
510 for (i = 0, j = 0; result[i]; i++) {
511 switch (result[i]) {
512 case '\002':
513 case '\003':
514 /* Foreground color */
515 if (isdigit(result[i + 1]))
516 i++;
517 if (isdigit(result[i + 1]))
518 i++;
519 /* Optional comma and background color */
520 if (result[i + 1] == ',') {
521 i++;
522 if (isdigit(result[i + 1]))
523 i++;
524 if (isdigit(result[i + 1]))
525 i++;
527 /* Note that i still points to the last character
528 * of the color selection string. */
529 continue;
530 case '\007':
531 case '\017':
532 case '\026':
533 case '\037':
534 continue;
535 default:
536 result[j++] = result[i];
539 result[j] = '\0';
540 return result;
543 const char *irc_nick_skip_mode(struct irc_conn *irc, const char *nick)
545 static const char *default_modes = "@+%&";
546 const char *mode_chars;
548 mode_chars = irc->mode_chars ? irc->mode_chars : default_modes;
550 while (*nick && strchr(mode_chars, *nick) != NULL)
551 nick++;
553 return nick;
556 gboolean irc_ischannel(const char *string)
558 return (string[0] == '#' || string[0] == '&');
561 char *irc_parse_ctcp(struct irc_conn *irc, const char *from, const char *to, const char *msg, int notice)
563 PurpleConnection *gc;
564 const char *cur = msg + 1;
565 char *buf, *ctcp;
566 time_t timestamp;
568 /* Note that this is NOT correct w.r.t. multiple CTCPs in one
569 * message and low-level quoting ... but if you want that crap,
570 * use a real IRC client. */
572 if (msg[0] != '\001' || msg[1] == '\0' || msg[strlen(msg) - 1] != '\001')
573 return g_strdup(msg);
575 if (!strncmp(cur, "ACTION ", 7)) {
576 cur += 7;
577 buf = g_strdup_printf("/me %s", cur);
578 buf[strlen(buf) - 1] = '\0';
579 return buf;
580 } else if (!strncmp(cur, "PING ", 5)) {
581 if (notice) { /* reply */
582 gc = purple_account_get_connection(irc->account);
583 if (!gc)
584 return NULL;
585 /* TODO: Should this read in the timestamp as a double? */
586 if (sscanf(cur, "PING %lu", &timestamp) == 1) {
587 buf = g_strdup_printf(_("Reply time from %s: %lu seconds"), from, time(NULL) - timestamp);
588 purple_notify_info(gc, _("PONG"),
589 _("CTCP PING reply"), buf,
590 purple_request_cpar_from_connection(gc));
591 g_free(buf);
592 } else
593 purple_debug(PURPLE_DEBUG_ERROR, "irc", "Unable to parse PING timestamp");
594 return NULL;
595 } else {
596 buf = irc_format(irc, "vt:", "NOTICE", from, msg);
597 irc_send(irc, buf);
598 g_free(buf);
600 } else if (!strncmp(cur, "VERSION", 7) && !notice) {
601 buf = irc_format(irc, "vt:", "NOTICE", from, "\001VERSION Purple IRC\001");
602 irc_send(irc, buf);
603 g_free(buf);
604 } else if (!strncmp(cur, "DCC SEND ", 9)) {
605 irc_dccsend_recv(irc, from, msg + 10);
606 return NULL;
609 ctcp = g_strdup(msg + 1);
610 ctcp[strlen(ctcp) - 1] = '\0';
611 buf = g_strdup_printf("Received CTCP '%s' (to %s) from %s", ctcp, to, from);
612 g_free(ctcp);
613 return buf;
616 void irc_msg_table_build(struct irc_conn *irc)
618 int i;
620 if (!irc || !irc->msgs) {
621 purple_debug(PURPLE_DEBUG_ERROR, "irc", "Attempt to build a message table on a bogus structure\n");
622 return;
625 for (i = 0; _irc_msgs[i].name; i++) {
626 g_hash_table_insert(irc->msgs, (gpointer)_irc_msgs[i].name, (gpointer)&_irc_msgs[i]);
630 void irc_cmd_table_build(struct irc_conn *irc)
632 int i;
634 if (!irc || !irc->cmds) {
635 purple_debug(PURPLE_DEBUG_ERROR, "irc", "Attempt to build a command table on a bogus structure\n");
636 return;
639 for (i = 0; _irc_cmds[i].name ; i++) {
640 g_hash_table_insert(irc->cmds, (gpointer)_irc_cmds[i].name, (gpointer)&_irc_cmds[i]);
644 char *irc_format(struct irc_conn *irc, const char *format, ...)
646 GString *string = g_string_new("");
647 char *tok, *tmp;
648 const char *cur;
649 va_list ap;
651 va_start(ap, format);
652 for (cur = format; *cur; cur++) {
653 if (cur != format)
654 g_string_append_c(string, ' ');
656 tok = va_arg(ap, char *);
657 switch (*cur) {
658 case 'v':
659 g_string_append(string, tok);
660 break;
661 case ':':
662 g_string_append_c(string, ':');
663 /* no break! */
664 case 't':
665 case 'n':
666 case 'c':
667 tmp = irc_send_convert(irc, tok);
668 g_string_append(string, tmp ? tmp : tok);
669 g_free(tmp);
670 break;
671 default:
672 purple_debug(PURPLE_DEBUG_ERROR, "irc", "Invalid format character '%c'\n", *cur);
673 break;
676 va_end(ap);
677 g_string_append(string, "\r\n");
678 return (g_string_free(string, FALSE));
681 void irc_parse_msg(struct irc_conn *irc, char *input)
683 struct _irc_msg *msgent;
684 char *cur, *end, *tmp, *from, *msgname, *fmt, **args, *msg;
685 guint i;
686 PurpleConnection *gc = purple_account_get_connection(irc->account);
687 gboolean fmt_valid;
688 int args_cnt;
690 irc->recv_time = time(NULL);
693 * The data passed to irc-receiving-text is the raw protocol data.
694 * TODO: It should be passed as an array of bytes and a length
695 * instead of a null terminated string.
697 purple_signal_emit(_irc_protocol, "irc-receiving-text", gc, &input);
699 if (purple_debug_is_verbose()) {
700 char *clean = purple_utf8_salvage(input);
701 clean = g_strstrip(clean);
702 purple_debug_misc("irc", ">> %s\n", clean);
703 g_free(clean);
706 if (!strncmp(input, "PING ", 5)) {
707 msg = irc_format(irc, "vv", "PONG", input + 5);
708 irc_send(irc, msg);
709 g_free(msg);
710 return;
711 } else if (!strncmp(input, "ERROR ", 6)) {
712 if (g_utf8_validate(input, -1, NULL)) {
713 purple_connection_take_error(gc, g_error_new(
714 PURPLE_CONNECTION_ERROR,
715 PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
716 "%s\n%s", _("Disconnected."), input));
717 } else
718 purple_connection_take_error(gc, g_error_new_literal(
719 PURPLE_CONNECTION_ERROR,
720 PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
721 _("Disconnected.")));
722 return;
723 #ifdef HAVE_CYRUS_SASL
724 } else if (!strncmp(input, "AUTHENTICATE ", 13)) {
725 irc_msg_auth(irc, input + 13);
726 return;
727 #endif
730 if (input[0] != ':' || (cur = strchr(input, ' ')) == NULL) {
731 irc_parse_error_cb(irc, input);
732 return;
735 from = g_strndup(&input[1], cur - &input[1]);
736 cur++;
737 end = strchr(cur, ' ');
738 if (!end)
739 end = cur + strlen(cur);
741 tmp = g_strndup(cur, end - cur);
742 msgname = g_ascii_strdown(tmp, -1);
743 g_free(tmp);
745 if ((msgent = g_hash_table_lookup(irc->msgs, msgname)) == NULL) {
746 irc_msg_default(irc, "", from, &input);
747 g_free(msgname);
748 g_free(from);
749 return;
751 g_free(msgname);
753 fmt_valid = TRUE;
754 args = g_new0(char *, strlen(msgent->format));
755 args_cnt = 0;
756 for (cur = end, fmt = msgent->format, i = 0; fmt[i] && *cur++; i++) {
757 switch (fmt[i]) {
758 case 'v':
759 if (!(end = strchr(cur, ' '))) end = cur + strlen(cur);
760 /* This is a string of unknown encoding which we do not
761 * want to transcode, but it may or may not be valid
762 * UTF-8, so we'll salvage it. If a nick/channel/target
763 * field has inadvertently been marked verbatim, this
764 * could cause weirdness. */
765 tmp = g_strndup(cur, end - cur);
766 args[i] = purple_utf8_salvage(tmp);
767 g_free(tmp);
768 cur += end - cur;
769 break;
770 case 't':
771 case 'n':
772 case 'c':
773 if (!(end = strchr(cur, ' '))) end = cur + strlen(cur);
774 tmp = g_strndup(cur, end - cur);
775 args[i] = irc_recv_convert(irc, tmp);
776 g_free(tmp);
777 cur += end - cur;
778 break;
779 case ':':
780 if (*cur == ':') cur++;
781 args[i] = irc_recv_convert(irc, cur);
782 cur = cur + strlen(cur);
783 break;
784 case '*':
785 /* Ditto 'v' above; we're going to salvage this in case
786 * it leaks past the IRC protocol */
787 args[i] = purple_utf8_salvage(cur);
788 cur = cur + strlen(cur);
789 break;
790 default:
791 purple_debug(PURPLE_DEBUG_ERROR, "irc", "invalid message format character '%c'\n", fmt[i]);
792 fmt_valid = FALSE;
793 break;
795 if (fmt_valid)
796 args_cnt = i + 1;
798 if (G_UNLIKELY(!fmt_valid)) {
799 purple_debug_error("irc", "message format was invalid");
800 } else if (G_LIKELY(args_cnt >= msgent->req_cnt)) {
801 tmp = irc_recv_convert(irc, from);
802 (msgent->cb)(irc, msgent->name, tmp, args);
803 g_free(tmp);
804 } else {
805 purple_debug_error("irc", "args count (%d) doesn't reach "
806 "expected value of %d for the '%s' command",
807 args_cnt, msgent->req_cnt, msgent->name);
809 for (i = 0; i < strlen(msgent->format); i++) {
810 g_free(args[i]);
812 g_free(args);
813 g_free(from);
816 static void irc_parse_error_cb(struct irc_conn *irc, char *input)
818 char *clean;
819 /* This really should be escaped somehow that you can tell what
820 * the junk was -- but as it is, it can crash glib. */
821 clean = purple_utf8_salvage(input);
822 purple_debug(PURPLE_DEBUG_WARNING, "irc", "Unrecognized string: %s\n", clean);
823 g_free(clean);