comparison libpurple/protocols/jabber/message.c @ 23988:305fac6af8f9

Updated to use latest spec. in XEP-0231 New namespace. Cache data globally in a running instance based on CID. Set the PNG compression level param when saving a custom smiley from GTKIMHTML.
author Marcus Lundblad <ml@update.uu.se>
date Fri, 05 Sep 2008 21:55:09 +0000
parents b2697ab66d23
children fba7c73c8f02
comparison
equal deleted inserted replaced
23987:8997acd7d143 23988:305fac6af8f9
22 22
23 #include "debug.h" 23 #include "debug.h"
24 #include "notify.h" 24 #include "notify.h"
25 #include "server.h" 25 #include "server.h"
26 #include "util.h" 26 #include "util.h"
27
28 #include "buddy.h" 27 #include "buddy.h"
29 #include "chat.h" 28 #include "chat.h"
30 #include "data.h" 29 #include "data.h"
31 #include "google.h" 30 #include "google.h"
32 #include "message.h" 31 #include "message.h"
321 typedef struct { 320 typedef struct {
322 gchar *cid; 321 gchar *cid;
323 gchar *alt; 322 gchar *alt;
324 } JabberSmileyRef; 323 } JabberSmileyRef;
325 324
326 static GList * 325
327 _jabber_message_get_refs_from_xmlnode(const xmlnode *message) 326 static void
327 jabber_message_get_refs_from_xmlnode_internal(const xmlnode *message,
328 GHashTable *table)
328 { 329 {
329 xmlnode *child; 330 xmlnode *child;
330 GList *refs = NULL; 331
331
332 for (child = xmlnode_get_child(message, "img") ; child ; 332 for (child = xmlnode_get_child(message, "img") ; child ;
333 child = xmlnode_get_next_twin(child)) { 333 child = xmlnode_get_next_twin(child)) {
334 const gchar *src = xmlnode_get_attrib(child, "src"); 334 const gchar *src = xmlnode_get_attrib(child, "src");
335 const gssize len = strlen(src); 335
336 336 if (g_str_has_prefix(src, "cid:")) {
337 if (len > 4 && g_str_has_prefix(src, "cid:")) { 337 const gchar *cid = src + 4;
338 JabberSmileyRef *ref = g_new0(JabberSmileyRef, 1); 338
339 ref->cid = g_strdup(&(src[4])); 339 /* if we haven't "fetched" this yet... */
340 ref->alt = g_strdup(xmlnode_get_attrib(child, "alt")); 340 if (!g_hash_table_lookup(table, cid)) {
341 refs = g_list_append(refs, ref); 341 /* take a copy of the cid and let the SmileyRef own it... */
342 } 342 gchar *temp_cid = g_strdup(cid);
343 } 343 JabberSmileyRef *ref = g_new0(JabberSmileyRef, 1);
344 344 const gchar *alt = xmlnode_get_attrib(child, "alt");
345 ref->cid = temp_cid;
346 /* if there is no "alt" string, use the cid... */
347 if (alt && alt[0] != '\0') {
348 ref->alt = g_strdup(xmlnode_get_attrib(child, "alt"));
349 } else {
350 ref->alt = g_strdup(cid);
351 }
352 g_hash_table_insert(table, temp_cid, ref);
353 }
354 }
355 }
356
345 for (child = message->child ; child ; child = child->next) { 357 for (child = message->child ; child ; child = child->next) {
346 refs = g_list_concat(refs, 358 jabber_message_get_refs_from_xmlnode_internal(child, table);
347 _jabber_message_get_refs_from_xmlnode(child)); 359 }
348 } 360 }
349 361
350 return refs; 362 static gboolean
363 jabber_message_get_refs_steal(gpointer key, gpointer value, gpointer user_data)
364 {
365 GList **refs = (GList **) user_data;
366 JabberSmileyRef *ref = (JabberSmileyRef *) value;
367
368 *refs = g_list_append(*refs, ref);
369
370 return TRUE;
351 } 371 }
352 372
353 static GList * 373 static GList *
354 jabber_message_get_refs_from_xmlnode(const xmlnode *message) 374 jabber_message_get_refs_from_xmlnode(const xmlnode *message)
355 { 375 {
356 GList *refs = _jabber_message_get_refs_from_xmlnode(message); 376 GList *refs = NULL;
357 GHashTable *unique_refs = g_hash_table_new(g_str_hash, g_str_equal); 377 GHashTable *unique_refs = g_hash_table_new(g_str_hash, g_str_equal);
358 GList *result = NULL; 378
359 GList *iterator = NULL; 379 jabber_message_get_refs_from_xmlnode_internal(message, unique_refs);
360 380 (void) g_hash_table_foreach_steal(unique_refs,
361 for (iterator = refs ; iterator ; iterator = g_list_next(iterator)) { 381 jabber_message_get_refs_steal, (gpointer) &refs);
362 JabberSmileyRef *ref = (JabberSmileyRef *) iterator->data;
363 if (!g_hash_table_lookup(unique_refs, ref->cid)) {
364 JabberSmileyRef *new_ref = g_new0(JabberSmileyRef, 1);
365 new_ref->cid = g_strdup(ref->cid);
366 new_ref->alt = g_strdup(ref->alt);
367 g_hash_table_insert(unique_refs, ref->cid, ref);
368 result = g_list_append(result, new_ref);
369 }
370 }
371
372 for (iterator = refs ; iterator ; iterator = g_list_next(iterator)) {
373 JabberSmileyRef *ref = (JabberSmileyRef *) iterator->data;
374 g_free(ref->cid);
375 g_free(ref->alt);
376 g_free(ref);
377 }
378
379 g_hash_table_destroy(unique_refs); 382 g_hash_table_destroy(unique_refs);
380 383 return refs;
381 return result; 384 }
382 }
383
384
385 385
386 static gchar * 386 static gchar *
387 jabber_message_xml_to_string_strip_img_smileys(xmlnode *xhtml) 387 jabber_message_xml_to_string_strip_img_smileys(xmlnode *xhtml)
388 { 388 {
389 const gchar *markup = xmlnode_to_str(xhtml, NULL); 389 const gchar *markup = xmlnode_to_str(xhtml, NULL);
402 402
403 for (; pos2 < len ; pos2++) { 403 for (; pos2 < len ; pos2++) {
404 if (g_str_has_prefix(&(markup[pos2]), "/>")) { 404 if (g_str_has_prefix(&(markup[pos2]), "/>")) {
405 pos2 += 2; 405 pos2 += 2;
406 break; 406 break;
407 } else if (g_str_has_prefix(&(markup[pos2]), "</img>")) {
408 pos2 += 5;
409 break;
407 } 410 }
408 } 411 }
409 412
410 /* note, if the above loop didn't find the end of the <img> tag, 413 /* note, if the above loop didn't find the end of the <img> tag,
411 it the parsed string will be until the end of the input string, 414 it the parsed string will be until the end of the input string,
415 img = xmlnode_from_str(&(markup[pos]), pos2 - pos); 418 img = xmlnode_from_str(&(markup[pos]), pos2 - pos);
416 src = xmlnode_get_attrib(img, "src"); 419 src = xmlnode_get_attrib(img, "src");
417 420
418 if (g_str_has_prefix(src, "cid:")) { 421 if (g_str_has_prefix(src, "cid:")) {
419 const gchar *alt = xmlnode_get_attrib(img, "alt"); 422 const gchar *alt = xmlnode_get_attrib(img, "alt");
420 gchar *escaped = g_markup_escape_text(alt, -1); 423 gchar *escaped = NULL;
421 out = g_string_append(out, escaped); 424 /* if the "alt" attribute is empty, put the cid as smiley string */
425 if (alt && alt[0] != '\0') {
426 escaped = g_markup_escape_text(alt, -1);
427 out = g_string_append(out, escaped);
428 g_free(escaped);
429 } else {
430 out = g_string_append(out, src + 4);
431 }
422 pos += pos2 - pos; 432 pos += pos2 - pos;
423 g_free(escaped);
424 } else { 433 } else {
425 out = g_string_append_c(out, markup[pos]); 434 out = g_string_append_c(out, markup[pos]);
426 pos++; 435 pos++;
427 } 436 }
428 437
436 445
437 return g_string_free(out, FALSE); 446 return g_string_free(out, FALSE);
438 } 447 }
439 448
440 static void 449 static void
441 jabber_message_add_remote_smileys_to_conv(PurpleConversation *conv, 450 jabber_message_add_remote_smileys(const xmlnode *message)
442 const xmlnode *message)
443 { 451 {
444 xmlnode *data_tag; 452 xmlnode *data_tag;
445 for (data_tag = xmlnode_get_child(message, "data") ; data_tag ; 453 for (data_tag = xmlnode_get_child_with_namespace(message, "data", XEP_0231_NAMESPACE) ;
454 data_tag ;
446 data_tag = xmlnode_get_next_twin(data_tag)) { 455 data_tag = xmlnode_get_next_twin(data_tag)) {
447 const gchar *cid = xmlnode_get_attrib(data_tag, "cid"); 456 const gchar *cid = xmlnode_get_attrib(data_tag, "cid");
448 const JabberData *data = jabber_data_find_remote_by_cid(conv, cid); 457 const JabberData *data = jabber_data_find_remote_by_cid(cid);
449 458
450 if (!data) { 459 if (!data && cid != NULL) {
451 /* we haven't cached this already, let's add it */ 460 /* we haven't cached this already, let's add it */
452 JabberData *new_data = jabber_data_create_from_xml(data_tag); 461 JabberData *new_data = jabber_data_create_from_xml(data_tag);
453 jabber_data_associate_remote_with_conv(new_data, conv); 462 jabber_data_associate_remote(new_data);
454 } 463 }
455 } 464 }
456 } 465 }
466
467 /* used in the function below to supply a conversation and shortcut for a
468 smiley */
469 typedef struct {
470 PurpleConversation *conv;
471 const gchar *alt;
472 } JabberDataRef;
457 473
458 static void 474 static void
459 jabber_message_get_data_cb(JabberStream *js, xmlnode *packet, gpointer data) 475 jabber_message_get_data_cb(JabberStream *js, xmlnode *packet, gpointer data)
460 { 476 {
461 PurpleConversation *conv = (PurpleConversation *) data; 477 JabberDataRef *ref = (JabberDataRef *) data;
478 PurpleConversation *conv = ref->conv;
479 const gchar *alt = ref->alt;
462 xmlnode *data_element = xmlnode_get_child(packet, "data"); 480 xmlnode *data_element = xmlnode_get_child(packet, "data");
463 xmlnode *item_not_found = xmlnode_get_child(packet, "item-not-found"); 481 xmlnode *item_not_found = xmlnode_get_child(packet, "item-not-found");
464 482
465 /* did we get a data element as result? */ 483 /* did we get a data element as result? */
466 if (data_element) { 484 if (data_element) {
467 JabberData *data = jabber_data_create_from_xml(data_element); 485 JabberData *data = jabber_data_create_from_xml(data_element);
468 486
469 if (data) { 487 if (data) {
470 jabber_data_associate_remote_with_conv(data, conv); 488 jabber_data_associate_remote(data);
471 purple_conv_custom_smiley_write(conv, jabber_data_get_alt(data), 489 purple_conv_custom_smiley_write(conv, alt,
472 jabber_data_get_data(data), 490 jabber_data_get_data(data),
473 jabber_data_get_size(data)); 491 jabber_data_get_size(data));
474 purple_conv_custom_smiley_close(conv, jabber_data_get_alt(data)); 492 purple_conv_custom_smiley_close(conv, alt);
475 } 493 }
476 494
477 } else if (item_not_found) { 495 } else if (item_not_found) {
478 purple_debug_info("jabber", 496 purple_debug_info("jabber",
479 "Responder didn't recognize requested data\n"); 497 "Responder didn't recognize requested data\n");
480 } else { 498 } else {
481 purple_debug_error("jabber", "Unknown response to data request\n"); 499 purple_debug_error("jabber", "Unknown response to data request\n");
482 } 500 }
501
502 g_free(ref);
483 } 503 }
484 504
485 static void 505 static void
486 jabber_message_send_data_request(JabberStream *js, PurpleConversation *conv, 506 jabber_message_send_data_request(JabberStream *js, PurpleConversation *conv,
487 const gchar *cid, const gchar *who) 507 const gchar *cid, const gchar *who,
508 const gchar *alt)
488 { 509 {
489 JabberIq *request = jabber_iq_new(js, JABBER_IQ_GET); 510 JabberIq *request = jabber_iq_new(js, JABBER_IQ_GET);
511 JabberDataRef *ref = g_new0(JabberDataRef, 1);
490 xmlnode *data_request = jabber_data_get_xml_request(cid); 512 xmlnode *data_request = jabber_data_get_xml_request(cid);
491 513
492 xmlnode_set_attrib(request->node, "to", who); 514 xmlnode_set_attrib(request->node, "to", who);
493 jabber_iq_set_callback(request, jabber_message_get_data_cb, conv); 515 ref->conv = conv;
516 ref->alt = alt;
517 jabber_iq_set_callback(request, jabber_message_get_data_cb, ref);
494 xmlnode_insert_child(request->node, data_request); 518 xmlnode_insert_child(request->node, data_request);
495 519
496 jabber_iq_send(request); 520 jabber_iq_send(request);
497 } 521 }
498 522
563 587
564 if (purple_account_get_bool(account, "custom_smileys", TRUE)) { 588 if (purple_account_get_bool(account, "custom_smileys", TRUE)) {
565 /* find a list of smileys ("cid" and "alt" text pairs) 589 /* find a list of smileys ("cid" and "alt" text pairs)
566 occuring in the message */ 590 occuring in the message */
567 smiley_refs = jabber_message_get_refs_from_xmlnode(child); 591 smiley_refs = jabber_message_get_refs_from_xmlnode(child);
568 592 purple_debug_info("jabber", "found %d smileys\n",
593 g_list_length(smiley_refs));
594
569 if (jm->type == JABBER_MESSAGE_GROUPCHAT) { 595 if (jm->type == JABBER_MESSAGE_GROUPCHAT) {
570 JabberID *jid = jabber_id_new(jm->from); 596 JabberID *jid = jabber_id_new(jm->from);
571 JabberChat *chat = NULL; 597 JabberChat *chat = NULL;
572 598
573 if (jid) { 599 if (jid) {
580 conv = 606 conv =
581 purple_find_conversation_with_account(PURPLE_CONV_TYPE_ANY, 607 purple_find_conversation_with_account(PURPLE_CONV_TYPE_ANY,
582 who, account); 608 who, account);
583 } 609 }
584 610
585 /* if the conversation doesn't exist yet we need to create it
586 now */
587 if (!conv) {
588 /* if a message of this type is initiating a conversation,
589 that must be an IM */
590 conv = purple_conversation_new(PURPLE_CONV_TYPE_IM,
591 account, who);
592 }
593
594 /* process any newly provided smileys */ 611 /* process any newly provided smileys */
595 jabber_message_add_remote_smileys_to_conv(conv, packet); 612 jabber_message_add_remote_smileys(packet);
596 } 613 }
597 614
598 /* reformat xhtml so that img tags with a "cid:" src gets 615 /* reformat xhtml so that img tags with a "cid:" src gets
599 translated to the bare text of the emoticon (the "alt" attrib) */ 616 translated to the bare text of the emoticon (the "alt" attrib) */
600 /* this is done also when custom smiley retrieval is turned off, 617 /* this is done also when custom smiley retrieval is turned off,
615 const gchar *alt = ref->alt; 632 const gchar *alt = ref->alt;
616 633
617 if (purple_conv_custom_smiley_add(conv, alt, "cid", cid, 634 if (purple_conv_custom_smiley_add(conv, alt, "cid", cid,
618 TRUE)) { 635 TRUE)) {
619 const JabberData *data = 636 const JabberData *data =
620 jabber_data_find_remote_by_cid(conv, cid); 637 jabber_data_find_remote_by_cid(cid);
621 /* if data is already known, we add write it immediatly */ 638 /* if data is already known, we add write it immediatly */
622 if (data) { 639 if (data) {
623 purple_conv_custom_smiley_write(conv, alt, 640 purple_conv_custom_smiley_write(conv, alt,
624 jabber_data_get_data(data), 641 jabber_data_get_data(data),
625 jabber_data_get_size(data)); 642 jabber_data_get_size(data));
626 purple_conv_custom_smiley_close(conv, alt); 643 purple_conv_custom_smiley_close(conv, alt);
627 } else { 644 } else {
628 /* we need to request the smiley (data) */ 645 /* we need to request the smiley (data) */
629 jabber_message_send_data_request(js, conv, cid, who); 646 jabber_message_send_data_request(js, conv, cid, who,
647 alt);
630 } 648 }
631 } 649 }
632 } 650 }
633 651
634 /* Convert all newlines to whitespace. Technically, even regular, non-XML HTML is supposed to ignore newlines, but Pidgin has, as convention 652 /* Convert all newlines to whitespace. Technically, even regular, non-XML HTML is supposed to ignore newlines, but Pidgin has, as convention
812 830
813 return found_smileys; 831 return found_smileys;
814 } 832 }
815 833
816 static gchar * 834 static gchar *
817 jabber_message_get_smileyfied_xhtml(const PurpleConversation *conv, 835 jabber_message_get_smileyfied_xhtml(const gchar *xhtml, const GList *smileys)
818 const gchar *xhtml, const GList *smileys)
819 { 836 {
820 /* create XML element for all smileys (img tags) */ 837 /* create XML element for all smileys (img tags) */
821 GString *result = g_string_new(NULL); 838 GString *result = g_string_new(NULL);
822 int pos = 0; 839 int pos = 0;
823 int length = strlen(xhtml); 840 int length = strlen(xhtml);
834 gchar *escaped = g_markup_escape_text(shortcut, len); 851 gchar *escaped = g_markup_escape_text(shortcut, len);
835 852
836 if (g_str_has_prefix(&(xhtml[pos]), escaped)) { 853 if (g_str_has_prefix(&(xhtml[pos]), escaped)) {
837 /* we found the current smiley at this position */ 854 /* we found the current smiley at this position */
838 const JabberData *data = 855 const JabberData *data =
839 jabber_data_find_local_by_alt(conv, shortcut); 856 jabber_data_find_local_by_alt(shortcut);
840 xmlnode *img = jabber_data_get_xhtml_im(data); 857 xmlnode *img = jabber_data_get_xhtml_im(data, shortcut);
841 int len; 858 int len;
842 gchar *img_text = xmlnode_to_str(img, &len); 859 gchar *img_text = xmlnode_to_str(img, &len);
843 860
844 found_smiley = TRUE; 861 found_smiley = TRUE;
845 result = g_string_append(result, img_text); 862 result = g_string_append(result, img_text);
866 jabber_conv_support_custom_smileys(const PurpleConnection *gc, 883 jabber_conv_support_custom_smileys(const PurpleConnection *gc,
867 const PurpleConversation *conv, 884 const PurpleConversation *conv,
868 const gchar *who) 885 const gchar *who)
869 { 886 {
870 JabberStream *js = (JabberStream *) gc->proto_data; 887 JabberStream *js = (JabberStream *) gc->proto_data;
871 JabberBuddy *jb = jabber_buddy_find(js, who, FALSE); 888 JabberBuddy *jb;
872 889
890 if (!js) {
891 purple_debug_error("jabber",
892 "jabber_conv_support_custom_smileys: could not find stream\n");
893 return FALSE;
894 }
895
896 jb = jabber_buddy_find(js, who, FALSE);
873 if (!jb) { 897 if (!jb) {
874 purple_debug_error("jabber", 898 purple_debug_error("jabber",
875 "jabber_conv_support_custom smileys: could not find buddy\n"); 899 "jabber_conv_support_custom smileys: could not find buddy\n");
876 return FALSE; 900 return FALSE;
877 } 901 }
902
903
878 904
879 switch (purple_conversation_get_type(conv)) { 905 switch (purple_conversation_get_type(conv)) {
880 /* for the time being, we will not support custom smileys in MUCs */ 906 /* for the time being, we will not support custom smileys in MUCs */
881 case PURPLE_CONV_TYPE_IM: 907 case PURPLE_CONV_TYPE_IM:
882 return jabber_buddy_has_capability(jb, XEP_0231_IB_IMAGE_NAMESPACE); 908 return jabber_buddy_has_capability(jb, XEP_0231_NAMESPACE);
883 break; 909 break;
884 default: 910 default:
885 return FALSE; 911 return FALSE;
886 break; 912 break;
887 } 913 }
974 if(jm->xhtml) { 1000 if(jm->xhtml) {
975 PurpleAccount *account = purple_connection_get_account(jm->js->gc); 1001 PurpleAccount *account = purple_connection_get_account(jm->js->gc);
976 PurpleConversation *conv = 1002 PurpleConversation *conv =
977 purple_find_conversation_with_account(PURPLE_CONV_TYPE_ANY, jm->to, 1003 purple_find_conversation_with_account(PURPLE_CONV_TYPE_ANY, jm->to,
978 account); 1004 account);
979 1005
980 if (jabber_conv_support_custom_smileys(jm->js->gc, conv, jm->to)) { 1006 if (jabber_conv_support_custom_smileys(jm->js->gc, conv, jm->to)) {
981 GList *found_smileys = jabber_message_xhtml_find_smileys(jm->xhtml); 1007 GList *found_smileys = jabber_message_xhtml_find_smileys(jm->xhtml);
982 1008
983 if (found_smileys) { 1009 if (found_smileys) {
984 gchar *smileyfied_xhtml = NULL; 1010 gchar *smileyfied_xhtml = NULL;
988 iterator = g_list_next(iterator)) { 1014 iterator = g_list_next(iterator)) {
989 const PurpleSmiley *smiley = 1015 const PurpleSmiley *smiley =
990 (PurpleSmiley *) iterator->data; 1016 (PurpleSmiley *) iterator->data;
991 const gchar *shortcut = purple_smiley_get_shortcut(smiley); 1017 const gchar *shortcut = purple_smiley_get_shortcut(smiley);
992 const JabberData *data = 1018 const JabberData *data =
993 jabber_data_find_local_by_alt(conv, shortcut); 1019 jabber_data_find_local_by_alt(shortcut);
994 1020
995 /* if data has not been sent before, include data */ 1021 /* the object has not been sent before */
996 if (!data) { 1022 if (!data) {
997 PurpleStoredImage *image = 1023 PurpleStoredImage *image =
998 purple_smiley_get_stored_image(smiley); 1024 purple_smiley_get_stored_image(smiley);
999 const gchar *ext = purple_imgstore_get_extension(image); 1025 const gchar *ext = purple_imgstore_get_extension(image);
1000 1026 JabberStream *js = jm->js;
1027
1001 JabberData *new_data = 1028 JabberData *new_data =
1002 jabber_data_create_from_data(purple_imgstore_get_data(image), 1029 jabber_data_create_from_data(purple_imgstore_get_data(image),
1003 purple_imgstore_get_size(image), 1030 purple_imgstore_get_size(image),
1004 jabber_message_get_mimetype_from_ext(ext), 1031 jabber_message_get_mimetype_from_ext(ext), js);
1005 shortcut); 1032 purple_debug_info("jabber",
1006 jabber_data_associate_local_with_conv(new_data, conv); 1033 "cache local smiley alt = %s, cid = %s\n",
1007 xmlnode_insert_child(message, 1034 shortcut, jabber_data_get_cid(new_data));
1008 jabber_data_get_xml_definition(new_data)); 1035 jabber_data_associate_local(new_data, shortcut);
1036 /* if the size of the data is small enough, include it */
1037 if (jabber_data_get_size(new_data) <= 1024) {
1038 xmlnode_insert_child(message,
1039 jabber_data_get_xml_definition(new_data));
1040 }
1009 } 1041 }
1010 } 1042 }
1011 1043
1012 smileyfied_xhtml = 1044 smileyfied_xhtml =
1013 jabber_message_get_smileyfied_xhtml(conv, jm->xhtml, 1045 jabber_message_get_smileyfied_xhtml(jm->xhtml, found_smileys);
1014 found_smileys);
1015 child = xmlnode_from_str(smileyfied_xhtml, -1); 1046 child = xmlnode_from_str(smileyfied_xhtml, -1);
1016 g_free(smileyfied_xhtml); 1047 g_free(smileyfied_xhtml);
1017 g_list_free(found_smileys); 1048 g_list_free(found_smileys);
1018 } else { 1049 } else {
1019 child = xmlnode_from_str(jm->xhtml, -1); 1050 child = xmlnode_from_str(jm->xhtml, -1);