comparison libgaim/protocols/jabber/message.c @ 14192:60b1bc8dbf37

[gaim-migrate @ 16863] Renamed 'core' to 'libgaim' committer: Tailor Script <tailor@pidgin.im>
author Evan Schoenberg <evan.s@dreskin.net>
date Sat, 19 Aug 2006 01:50:10 +0000
parents
children c9cc804b53bf
comparison
equal deleted inserted replaced
14191:009db0b357b5 14192:60b1bc8dbf37
1 /*
2 * gaim - Jabber Protocol Plugin
3 *
4 * Copyright (C) 2003, Nathan Walp <faceprint@faceprint.com>
5 *
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software
18 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
19 *
20 */
21 #include "internal.h"
22
23 #include "debug.h"
24 #include "notify.h"
25 #include "server.h"
26 #include "util.h"
27
28 #include "buddy.h"
29 #include "chat.h"
30 #include "message.h"
31 #include "xmlnode.h"
32
33 void jabber_message_free(JabberMessage *jm)
34 {
35 g_free(jm->from);
36 g_free(jm->to);
37 g_free(jm->id);
38 g_free(jm->subject);
39 g_free(jm->body);
40 g_free(jm->xhtml);
41 g_free(jm->password);
42 g_list_free(jm->etc);
43
44 g_free(jm);
45 }
46
47 static void handle_chat(JabberMessage *jm)
48 {
49 JabberID *jid = jabber_id_new(jm->from);
50 char *from;
51
52 JabberBuddy *jb;
53 JabberBuddyResource *jbr;
54
55 if(!jid)
56 return;
57
58 jb = jabber_buddy_find(jm->js, jm->from, TRUE);
59 jbr = jabber_buddy_find_resource(jb, jid->resource);
60
61 if(jabber_find_unnormalized_conv(jm->from, jm->js->gc->account)) {
62 from = g_strdup(jm->from);
63 } else if(jid->node) {
64 if(jid->resource) {
65 GaimConversation *conv;
66
67 from = g_strdup_printf("%s@%s", jid->node, jid->domain);
68 conv = gaim_find_conversation_with_account(GAIM_CONV_TYPE_IM, from, jm->js->gc->account);
69 if(conv) {
70 gaim_conversation_set_name(conv, jm->from);
71 }
72 g_free(from);
73 }
74 from = g_strdup(jm->from);
75 } else {
76 from = g_strdup(jid->domain);
77 }
78
79 if(!jm->xhtml && !jm->body) {
80 if(JM_STATE_COMPOSING == jm->chat_state)
81 serv_got_typing(jm->js->gc, from, 0, GAIM_TYPING);
82 else if(JM_STATE_PAUSED == jm->chat_state)
83 serv_got_typing(jm->js->gc, from, 0, GAIM_TYPED);
84 else
85 serv_got_typing_stopped(jm->js->gc, from);
86 } else {
87 if(jbr) {
88 if(JM_TS_JEP_0085 == (jm->typing_style & JM_TS_JEP_0085)) {
89 jbr->chat_states = JABBER_CHAT_STATES_SUPPORTED;
90 } else {
91 jbr->chat_states = JABBER_CHAT_STATES_UNSUPPORTED;
92 }
93
94 if(JM_TS_JEP_0022 == (jm->typing_style & JM_TS_JEP_0022)) {
95 jbr->capabilities |= JABBER_CAP_COMPOSING;
96 }
97
98 if(jbr->thread_id)
99 g_free(jbr->thread_id);
100 jbr->thread_id = g_strdup(jbr->thread_id);
101 }
102 serv_got_im(jm->js->gc, from, jm->xhtml ? jm->xhtml : jm->body, 0,
103 jm->sent);
104 }
105
106
107 g_free(from);
108 jabber_id_free(jid);
109 }
110
111 static void handle_headline(JabberMessage *jm)
112 {
113 char *title;
114 GString *body = g_string_new("");
115 GList *etc;
116
117 title = g_strdup_printf(_("Message from %s"), jm->from);
118
119 if(jm->xhtml)
120 g_string_append(body, jm->xhtml);
121 else if(jm->body)
122 g_string_append(body, jm->body);
123
124 for(etc = jm->etc; etc; etc = etc->next) {
125 xmlnode *x = etc->data;
126 const char *xmlns = xmlnode_get_namespace(x);
127 if(xmlns && !strcmp(xmlns, "jabber:x:oob")) {
128 xmlnode *url, *desc;
129 char *urltxt, *desctxt;
130
131 url = xmlnode_get_child(x, "url");
132 desc = xmlnode_get_child(x, "desc");
133
134 if(!url || !desc)
135 continue;
136
137 urltxt = xmlnode_get_data(url);
138 desctxt = xmlnode_get_data(desc);
139
140 /* I'm all about ugly hacks */
141 if(body->len && !strcmp(body->str, jm->body))
142 g_string_printf(body, "<a href='%s'>%s</a>",
143 urltxt, desctxt);
144 else
145 g_string_append_printf(body, "<br/><a href='%s'>%s</a>",
146 urltxt, desctxt);
147
148 g_free(urltxt);
149 g_free(desctxt);
150 }
151 }
152
153 gaim_notify_formatted(jm->js->gc, title, jm->subject ? jm->subject : title,
154 NULL, body->str, NULL, NULL);
155
156 g_free(title);
157 g_string_free(body, TRUE);
158 }
159
160 static void handle_groupchat(JabberMessage *jm)
161 {
162 JabberID *jid = jabber_id_new(jm->from);
163 JabberChat *chat;
164
165 if(!jid)
166 return;
167
168 chat = jabber_chat_find(jm->js, jid->node, jid->domain);
169
170 if(!chat)
171 return;
172
173 if(jm->subject) {
174 gaim_conv_chat_set_topic(GAIM_CONV_CHAT(chat->conv), jid->resource,
175 jm->subject);
176 if(!jm->xhtml && !jm->body) {
177 char *msg, *tmp, *tmp2;
178 tmp = g_markup_escape_text(jm->subject, -1);
179 tmp2 = gaim_markup_linkify(tmp);
180 if(jid->resource)
181 msg = g_strdup_printf(_("%s has set the topic to: %s"), jid->resource, tmp2);
182 else
183 msg = g_strdup_printf(_("The topic is: %s"), tmp2);
184 gaim_conv_chat_write(GAIM_CONV_CHAT(chat->conv), "", msg, GAIM_MESSAGE_SYSTEM, jm->sent);
185 g_free(tmp);
186 g_free(tmp2);
187 g_free(msg);
188 }
189 }
190
191 if(jm->xhtml || jm->body) {
192 if(jid->resource)
193 serv_got_chat_in(jm->js->gc, chat->id, jid->resource,
194 jm->delayed ? GAIM_MESSAGE_DELAYED : 0,
195 jm->xhtml ? jm->xhtml : jm->body, jm->sent);
196 else if(chat->muc)
197 gaim_conv_chat_write(GAIM_CONV_CHAT(chat->conv), "",
198 jm->xhtml ? jm->xhtml : jm->body,
199 GAIM_MESSAGE_SYSTEM, jm->sent);
200 }
201
202 jabber_id_free(jid);
203 }
204
205 static void handle_groupchat_invite(JabberMessage *jm)
206 {
207 GHashTable *components;
208 JabberID *jid = jabber_id_new(jm->to);
209
210 if(!jid)
211 return;
212
213 components = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free);
214
215 g_hash_table_replace(components, g_strdup("room"), g_strdup(jid->node));
216 g_hash_table_replace(components, g_strdup("server"), g_strdup(jid->domain));
217 g_hash_table_replace(components, g_strdup("handle"),
218 g_strdup(jm->js->user->node));
219 g_hash_table_replace(components, g_strdup("password"),
220 g_strdup(jm->password));
221
222 jabber_id_free(jid);
223 serv_got_chat_invite(jm->js->gc, jm->to, jm->from, jm->body, components);
224 }
225
226 static void handle_error(JabberMessage *jm)
227 {
228 char *buf;
229
230 if(!jm->body)
231 return;
232
233 buf = g_strdup_printf(_("Message delivery to %s failed: %s"),
234 jm->from, jm->error ? jm->error : "");
235
236 gaim_notify_formatted(jm->js->gc, _("Jabber Message Error"), _("Jabber Message Error"), buf,
237 jm->xhtml ? jm->xhtml : jm->body, NULL, NULL);
238
239 g_free(buf);
240 }
241
242 void jabber_message_parse(JabberStream *js, xmlnode *packet)
243 {
244 JabberMessage *jm;
245 const char *type;
246 xmlnode *child;
247
248 jm = g_new0(JabberMessage, 1);
249 jm->js = js;
250 jm->sent = time(NULL);
251 jm->delayed = FALSE;
252
253 type = xmlnode_get_attrib(packet, "type");
254
255 if(type) {
256 if(!strcmp(type, "normal"))
257 jm->type = JABBER_MESSAGE_NORMAL;
258 else if(!strcmp(type, "chat"))
259 jm->type = JABBER_MESSAGE_CHAT;
260 else if(!strcmp(type, "groupchat"))
261 jm->type = JABBER_MESSAGE_GROUPCHAT;
262 else if(!strcmp(type, "headline"))
263 jm->type = JABBER_MESSAGE_HEADLINE;
264 else if(!strcmp(type, "error"))
265 jm->type = JABBER_MESSAGE_ERROR;
266 else
267 jm->type = JABBER_MESSAGE_OTHER;
268 } else {
269 jm->type = JABBER_MESSAGE_NORMAL;
270 }
271
272 jm->from = g_strdup(xmlnode_get_attrib(packet, "from"));
273 jm->to = g_strdup(xmlnode_get_attrib(packet, "to"));
274 jm->id = g_strdup(xmlnode_get_attrib(packet, "id"));
275
276 for(child = packet->child; child; child = child->next) {
277 if(child->type != XMLNODE_TYPE_TAG)
278 continue;
279
280 if(!strcmp(child->name, "subject")) {
281 if(!jm->subject)
282 jm->subject = xmlnode_get_data(child);
283 } else if(!strcmp(child->name, "thread")) {
284 if(!jm->thread_id)
285 jm->thread_id = xmlnode_get_data(child);
286 } else if(!strcmp(child->name, "body")) {
287 if(!jm->body) {
288 char *msg = xmlnode_to_str(child, NULL);
289 jm->body = gaim_strdup_withhtml(msg);
290 g_free(msg);
291 }
292 } else if(!strcmp(child->name, "html")) {
293 if(!jm->xhtml && xmlnode_get_child(child, "body"))
294 jm->xhtml = xmlnode_to_str(child, NULL);
295 } else if(!strcmp(child->name, "active")) {
296 jm->chat_state = JM_STATE_ACTIVE;
297 jm->typing_style |= JM_TS_JEP_0085;
298 } else if(!strcmp(child->name, "composing")) {
299 jm->chat_state = JM_STATE_COMPOSING;
300 jm->typing_style |= JM_TS_JEP_0085;
301 } else if(!strcmp(child->name, "paused")) {
302 jm->chat_state = JM_STATE_PAUSED;
303 jm->typing_style |= JM_TS_JEP_0085;
304 } else if(!strcmp(child->name, "inactive")) {
305 jm->chat_state = JM_STATE_INACTIVE;
306 jm->typing_style |= JM_TS_JEP_0085;
307 } else if(!strcmp(child->name, "gone")) {
308 jm->chat_state = JM_STATE_GONE;
309 jm->typing_style |= JM_TS_JEP_0085;
310 } else if(!strcmp(child->name, "error")) {
311 const char *code = xmlnode_get_attrib(child, "code");
312 char *code_txt = NULL;
313 char *text = xmlnode_get_data(child);
314
315 if(code)
316 code_txt = g_strdup_printf(_(" (Code %s)"), code);
317
318 if(!jm->error)
319 jm->error = g_strdup_printf("%s%s", text ? text : "",
320 code_txt ? code_txt : "");
321
322 g_free(code_txt);
323 g_free(text);
324 } else if(!strcmp(child->name, "x")) {
325 const char *xmlns = xmlnode_get_namespace(child);
326 if(xmlns && !strcmp(xmlns, "jabber:x:event")) {
327 if(xmlnode_get_child(child, "composing")) {
328 if(jm->chat_state == JM_STATE_ACTIVE)
329 jm->chat_state = JM_STATE_COMPOSING;
330 jm->typing_style |= JM_TS_JEP_0022;
331 }
332 } else if(xmlns && !strcmp(xmlns, "jabber:x:delay")) {
333 const char *timestamp = xmlnode_get_attrib(child, "stamp");
334 jm->delayed = TRUE;
335 if(timestamp)
336 jm->sent = gaim_str_to_time(timestamp, TRUE, NULL, NULL, NULL);
337 } else if(xmlns && !strcmp(xmlns, "jabber:x:conference") &&
338 jm->type != JABBER_MESSAGE_GROUPCHAT_INVITE &&
339 jm->type != JABBER_MESSAGE_ERROR) {
340 const char *jid = xmlnode_get_attrib(child, "jid");
341 if(jid) {
342 jm->type = JABBER_MESSAGE_GROUPCHAT_INVITE;
343 g_free(jm->to);
344 jm->to = g_strdup(jid);
345 }
346 } else if(xmlns && !strcmp(xmlns,
347 "http://jabber.org/protocol/muc#user") &&
348 jm->type != JABBER_MESSAGE_ERROR) {
349 xmlnode *invite = xmlnode_get_child(child, "invite");
350 if(invite) {
351 xmlnode *reason, *password;
352 const char *jid = xmlnode_get_attrib(invite, "from");
353 g_free(jm->to);
354 jm->to = jm->from;
355 jm->from = g_strdup(jid);
356 if((reason = xmlnode_get_child(invite, "reason"))) {
357 g_free(jm->body);
358 jm->body = xmlnode_get_data(reason);
359 }
360 if((password = xmlnode_get_child(child, "password")))
361 jm->password = xmlnode_get_data(password);
362
363 jm->type = JABBER_MESSAGE_GROUPCHAT_INVITE;
364 }
365 } else {
366 jm->etc = g_list_append(jm->etc, child);
367 }
368 }
369 }
370
371 switch(jm->type) {
372 case JABBER_MESSAGE_NORMAL:
373 case JABBER_MESSAGE_CHAT:
374 handle_chat(jm);
375 break;
376 case JABBER_MESSAGE_HEADLINE:
377 handle_headline(jm);
378 break;
379 case JABBER_MESSAGE_GROUPCHAT:
380 handle_groupchat(jm);
381 break;
382 case JABBER_MESSAGE_GROUPCHAT_INVITE:
383 handle_groupchat_invite(jm);
384 break;
385 case JABBER_MESSAGE_ERROR:
386 handle_error(jm);
387 break;
388 case JABBER_MESSAGE_OTHER:
389 gaim_debug(GAIM_DEBUG_INFO, "jabber",
390 "Received message of unknown type: %s\n", type);
391 break;
392 }
393 jabber_message_free(jm);
394 }
395
396 void jabber_message_send(JabberMessage *jm)
397 {
398 xmlnode *message, *child;
399 const char *type = NULL;
400
401 message = xmlnode_new("message");
402
403 switch(jm->type) {
404 case JABBER_MESSAGE_NORMAL:
405 type = "normal";
406 break;
407 case JABBER_MESSAGE_CHAT:
408 case JABBER_MESSAGE_GROUPCHAT_INVITE:
409 type = "chat";
410 break;
411 case JABBER_MESSAGE_HEADLINE:
412 type = "headline";
413 break;
414 case JABBER_MESSAGE_GROUPCHAT:
415 type = "groupchat";
416 break;
417 case JABBER_MESSAGE_ERROR:
418 type = "error";
419 break;
420 case JABBER_MESSAGE_OTHER:
421 type = NULL;
422 break;
423 }
424
425 if(type)
426 xmlnode_set_attrib(message, "type", type);
427
428 if (jm->id)
429 xmlnode_set_attrib(message, "id", jm->id);
430
431 xmlnode_set_attrib(message, "to", jm->to);
432
433 if(jm->thread_id) {
434 child = xmlnode_new_child(message, "thread");
435 xmlnode_insert_data(child, jm->thread_id, -1);
436 }
437
438 if(JM_TS_JEP_0022 == (jm->typing_style & JM_TS_JEP_0022)) {
439 child = xmlnode_new_child(message, "x");
440 xmlnode_set_namespace(child, "jabber:x:event");
441 if(jm->chat_state == JM_STATE_COMPOSING || jm->body)
442 xmlnode_new_child(child, "composing");
443 }
444
445 if(JM_TS_JEP_0085 == (jm->typing_style & JM_TS_JEP_0085)) {
446 child = NULL;
447 switch(jm->chat_state)
448 {
449 case JM_STATE_ACTIVE:
450 child = xmlnode_new_child(message, "active");
451 break;
452 case JM_STATE_COMPOSING:
453 child = xmlnode_new_child(message, "composing");
454 break;
455 case JM_STATE_PAUSED:
456 child = xmlnode_new_child(message, "paused");
457 break;
458 case JM_STATE_INACTIVE:
459 child = xmlnode_new_child(message, "inactive");
460 break;
461 case JM_STATE_GONE:
462 child = xmlnode_new_child(message, "gone");
463 break;
464 }
465 if(child)
466 xmlnode_set_namespace(child, "http://jabber.org/protocol/chatstates");
467 }
468
469 if(jm->subject) {
470 child = xmlnode_new_child(message, "subject");
471 xmlnode_insert_data(child, jm->subject, -1);
472 }
473
474 if(jm->body) {
475 child = xmlnode_new_child(message, "body");
476 xmlnode_insert_data(child, jm->body, -1);
477 }
478
479 if(jm->xhtml) {
480 child = xmlnode_from_str(jm->xhtml, -1);
481 if(child) {
482 xmlnode_insert_child(message, child);
483 } else {
484 gaim_debug(GAIM_DEBUG_ERROR, "jabber",
485 "XHTML translation/validation failed, returning: %s\n",
486 jm->xhtml);
487 }
488 }
489
490 jabber_send(jm->js, message);
491
492 xmlnode_free(message);
493 }
494
495 int jabber_message_send_im(GaimConnection *gc, const char *who, const char *msg,
496 GaimMessageFlags flags)
497 {
498 JabberMessage *jm;
499 JabberBuddy *jb;
500 JabberBuddyResource *jbr;
501 char *buf;
502 char *xhtml;
503 char *resource;
504
505 if(!who || !msg)
506 return 0;
507
508 resource = jabber_get_resource(who);
509
510 jb = jabber_buddy_find(gc->proto_data, who, TRUE);
511 jbr = jabber_buddy_find_resource(jb, resource);
512
513 g_free(resource);
514
515 jm = g_new0(JabberMessage, 1);
516 jm->js = gc->proto_data;
517 jm->type = JABBER_MESSAGE_CHAT;
518 jm->chat_state = JM_STATE_ACTIVE;
519 jm->to = g_strdup(who);
520 jm->id = jabber_get_next_id(jm->js);
521 jm->chat_state = JM_STATE_ACTIVE;
522
523 if(jbr) {
524 if(jbr->thread_id)
525 jm->thread_id = jbr->thread_id;
526
527 if(jbr->chat_states != JABBER_CHAT_STATES_UNSUPPORTED) {
528 jm->typing_style |= JM_TS_JEP_0085;
529 /* if(JABBER_CHAT_STATES_UNKNOWN == jbr->chat_states)
530 jbr->chat_states = JABBER_CHAT_STATES_UNSUPPORTED; */
531 }
532
533 if(jbr->chat_states != JABBER_CHAT_STATES_SUPPORTED)
534 jm->typing_style |= JM_TS_JEP_0022;
535 }
536
537 buf = g_strdup_printf("<html xmlns='http://jabber.org/protocol/xhtml-im'><body xmlns='http://www.w3.org/1999/xhtml'>%s</body></html>", msg);
538
539 gaim_markup_html_to_xhtml(buf, &xhtml, &jm->body);
540 g_free(buf);
541
542 if(!jbr || jbr->capabilities & JABBER_CAP_XHTML)
543 jm->xhtml = xhtml;
544 else
545 g_free(xhtml);
546
547 jabber_message_send(jm);
548 jabber_message_free(jm);
549 return 1;
550 }
551
552 int jabber_message_send_chat(GaimConnection *gc, int id, const char *msg, GaimMessageFlags flags)
553 {
554 JabberChat *chat;
555 JabberMessage *jm;
556 JabberStream *js;
557 char *buf;
558
559 if(!msg || !gc)
560 return 0;
561
562 js = gc->proto_data;
563 chat = jabber_chat_find_by_id(js, id);
564
565 if(!chat)
566 return 0;
567
568 jm = g_new0(JabberMessage, 1);
569 jm->js = gc->proto_data;
570 jm->type = JABBER_MESSAGE_GROUPCHAT;
571 jm->to = g_strdup_printf("%s@%s", chat->room, chat->server);
572 jm->id = jabber_get_next_id(jm->js);
573
574 buf = g_strdup_printf("<html xmlns='http://jabber.org/protocol/xhtml-im'><body xmlns='http://www.w3.org/1999/xhtml'>%s</body></html>", msg);
575 gaim_markup_html_to_xhtml(buf, &jm->xhtml, &jm->body);
576 g_free(buf);
577
578 if(!chat->xhtml) {
579 g_free(jm->xhtml);
580 jm->xhtml = NULL;
581 }
582
583 jabber_message_send(jm);
584 jabber_message_free(jm);
585
586 return 1;
587 }
588
589 unsigned int jabber_send_typing(GaimConnection *gc, const char *who, GaimTypingState state)
590 {
591 JabberMessage *jm;
592 JabberBuddy *jb;
593 JabberBuddyResource *jbr;
594 char *resource = jabber_get_resource(who);
595
596 jb = jabber_buddy_find(gc->proto_data, who, TRUE);
597 jbr = jabber_buddy_find_resource(jb, resource);
598
599 g_free(resource);
600
601 if(!jbr || !((jbr->capabilities & JABBER_CAP_COMPOSING) || (jbr->chat_states != JABBER_CHAT_STATES_UNSUPPORTED)))
602 return 0;
603
604 /* TODO: figure out threading */
605 jm = g_new0(JabberMessage, 1);
606 jm->js = gc->proto_data;
607 jm->type = JABBER_MESSAGE_CHAT;
608 jm->to = g_strdup(who);
609 jm->id = jabber_get_next_id(jm->js);
610
611 if(GAIM_TYPING == state)
612 jm->chat_state = JM_STATE_COMPOSING;
613 else if(GAIM_TYPED == state)
614 jm->chat_state = JM_STATE_PAUSED;
615 else
616 jm->chat_state = JM_STATE_ACTIVE;
617
618 if(jbr->chat_states != JABBER_CHAT_STATES_UNSUPPORTED) {
619 jm->typing_style |= JM_TS_JEP_0085;
620 /* if(JABBER_CHAT_STATES_UNKNOWN == jbr->chat_states)
621 jbr->chat_states = JABBER_CHAT_STATES_UNSUPPORTED; */
622 }
623
624 if(jbr->chat_states != JABBER_CHAT_STATES_SUPPORTED)
625 jm->typing_style |= JM_TS_JEP_0022;
626
627 jabber_message_send(jm);
628 jabber_message_free(jm);
629
630 return 0;
631 }
632