comparison libpurple/protocols/jabber/buddy.c @ 26537:d6a863df7884

propagate from branch 'im.pidgin.pidgin' (head 079a5ca1aa110ee9f6661eba31e5ea3f0b5a07e7) to branch 'im.pidgin.cpw.darkrain42.xmpp.avatars' (head 120c877c682a4ab854a6b27743504a054b3cef23)
author Paul Aurich <paul@darkrain42.org>
date Sat, 11 Apr 2009 04:34:47 +0000
parents b87843de7c6a d6a31b0ad109
children 09e9b7e50df9
comparison
equal deleted inserted replaced
26536:d7bd1431b989 26537:d6a863df7884
30 #include "buddy.h" 30 #include "buddy.h"
31 #include "chat.h" 31 #include "chat.h"
32 #include "jabber.h" 32 #include "jabber.h"
33 #include "iq.h" 33 #include "iq.h"
34 #include "presence.h" 34 #include "presence.h"
35 #include "useravatar.h"
35 #include "xdata.h" 36 #include "xdata.h"
36 #include "pep.h" 37 #include "pep.h"
37 #include "adhoccommands.h" 38 #include "adhoccommands.h"
38
39 #define MAX_HTTP_BUDDYICON_BYTES (200 * 1024)
40 39
41 typedef struct { 40 typedef struct {
42 long idle_seconds; 41 long idle_seconds;
43 } JabberBuddyInfoResource; 42 } JabberBuddyInfoResource;
44 43
480 xmlnode_free(photo); 479 xmlnode_free(photo);
481 } 480 }
482 } 481 }
483 482
484 if (vc_node != NULL) { 483 if (vc_node != NULL) {
484 PurpleAccount *account = purple_connection_get_account(js->gc);
485
485 iq = jabber_iq_new(js, JABBER_IQ_SET); 486 iq = jabber_iq_new(js, JABBER_IQ_SET);
486 xmlnode_insert_child(iq->node, vc_node); 487 xmlnode_insert_child(iq->node, vc_node);
487 jabber_iq_send(iq); 488 jabber_iq_send(iq);
489
490 /* Send presence to update vcard-temp:x:update */
491 jabber_presence_send(account, purple_account_get_active_status(account));
488 } 492 }
489 } 493 }
490 494
491 void jabber_set_buddy_icon(PurpleConnection *gc, PurpleStoredImage *img) 495 void jabber_set_buddy_icon(PurpleConnection *gc, PurpleStoredImage *img)
492 { 496 {
493 PurplePresence *gpresence; 497 PurpleAccount *account = purple_connection_get_account(gc);
494 PurpleStatus *status; 498 jabber_avatar_set(gc->proto_data, img, NULL);
495
496 if(((JabberStream*)purple_connection_get_protocol_data(gc))->pep) {
497 /* XEP-0084: User Avatars */
498 if(img) {
499 /*
500 * TODO: This is pretty gross. The Jabber PRPL really shouldn't
501 * do voodoo to try to determine the image type, height
502 * and width.
503 */
504 /* A PNG header, including the IHDR, but nothing else */
505 const struct {
506 guchar signature[8]; /* must be hex 89 50 4E 47 0D 0A 1A 0A */
507 struct {
508 guint32 length; /* must be 0x0d */
509 guchar type[4]; /* must be 'I' 'H' 'D' 'R' */
510 guint32 width;
511 guint32 height;
512 guchar bitdepth;
513 guchar colortype;
514 guchar compression;
515 guchar filter;
516 guchar interlace;
517 } ihdr;
518 } *png = purple_imgstore_get_data(img); /* ATTN: this is in network byte order! */
519
520 /* check if the data is a valid png file (well, at least to some extend) */
521 if(png->signature[0] == 0x89 &&
522 png->signature[1] == 0x50 &&
523 png->signature[2] == 0x4e &&
524 png->signature[3] == 0x47 &&
525 png->signature[4] == 0x0d &&
526 png->signature[5] == 0x0a &&
527 png->signature[6] == 0x1a &&
528 png->signature[7] == 0x0a &&
529 ntohl(png->ihdr.length) == 0x0d &&
530 png->ihdr.type[0] == 'I' &&
531 png->ihdr.type[1] == 'H' &&
532 png->ihdr.type[2] == 'D' &&
533 png->ihdr.type[3] == 'R') {
534 /* parse PNG header to get the size of the image (yes, this is required) */
535 guint32 width = ntohl(png->ihdr.width);
536 guint32 height = ntohl(png->ihdr.height);
537 xmlnode *publish, *item, *data, *metadata, *info;
538 char *lengthstring, *widthstring, *heightstring;
539
540 /* compute the sha1 hash */
541 char *hash = jabber_calculate_data_sha1sum(purple_imgstore_get_data(img), purple_imgstore_get_size(img));
542 char *base64avatar;
543
544 publish = xmlnode_new("publish");
545 xmlnode_set_attrib(publish,"node",AVATARNAMESPACEDATA);
546
547 item = xmlnode_new_child(publish, "item");
548 xmlnode_set_attrib(item, "id", hash);
549
550 data = xmlnode_new_child(item, "data");
551 xmlnode_set_namespace(data,AVATARNAMESPACEDATA);
552
553 base64avatar = purple_base64_encode(purple_imgstore_get_data(img), purple_imgstore_get_size(img));
554 xmlnode_insert_data(data,base64avatar,-1);
555 g_free(base64avatar);
556
557 /* publish the avatar itself */
558 jabber_pep_publish((JabberStream*)purple_connection_get_protocol_data(gc), publish);
559
560 /* next step: publish the metadata */
561 publish = xmlnode_new("publish");
562 xmlnode_set_attrib(publish,"node",AVATARNAMESPACEMETA);
563
564 item = xmlnode_new_child(publish, "item");
565 xmlnode_set_attrib(item, "id", hash);
566
567 metadata = xmlnode_new_child(item, "metadata");
568 xmlnode_set_namespace(metadata,AVATARNAMESPACEMETA);
569
570 info = xmlnode_new_child(metadata, "info");
571 xmlnode_set_attrib(info, "id", hash);
572 xmlnode_set_attrib(info, "type", "image/png");
573 lengthstring = g_strdup_printf("%u", (unsigned)purple_imgstore_get_size(img));
574 xmlnode_set_attrib(info, "bytes", lengthstring);
575 g_free(lengthstring);
576 widthstring = g_strdup_printf("%u", width);
577 xmlnode_set_attrib(info, "width", widthstring);
578 g_free(widthstring);
579 heightstring = g_strdup_printf("%u", height);
580 xmlnode_set_attrib(info, "height", heightstring);
581 g_free(heightstring);
582
583 /* publish the metadata */
584 jabber_pep_publish((JabberStream*)purple_connection_get_protocol_data(gc), publish);
585
586 g_free(hash);
587 } else {
588 purple_debug_error("jabber", "jabber_set_buddy_icon received non-png data");
589 }
590 } else {
591 /* remove the metadata */
592 xmlnode *metadata, *item;
593 xmlnode *publish = xmlnode_new("publish");
594 xmlnode_set_attrib(publish,"node",AVATARNAMESPACEMETA);
595
596 item = xmlnode_new_child(publish, "item");
597
598 metadata = xmlnode_new_child(item, "metadata");
599 xmlnode_set_namespace(metadata,AVATARNAMESPACEMETA);
600
601 xmlnode_new_child(metadata, "stop");
602
603 /* publish the metadata */
604 jabber_pep_publish((JabberStream*)gc->proto_data, publish);
605 }
606 }
607
608 /* vCard avatars do not have an image type requirement so update our 499 /* vCard avatars do not have an image type requirement so update our
609 * vCard avatar regardless of image type for those poor older clients 500 * vCard avatar regardless of image type for those poor older clients
610 */ 501 */
611 jabber_set_info(gc, purple_account_get_user_info(gc->account)); 502 jabber_set_info(gc, purple_account_get_user_info(account));
612 503
613 gpresence = purple_account_get_presence(gc->account); 504 /* TODO: Fake image to ourselves, since a number of servers do not echo
614 status = purple_presence_get_active_status(gpresence); 505 * back our presence to us. To do this without uselessly copying the data
615 jabber_presence_send(gc->account, status); 506 * of the image, we need purple_buddy_icons_set_for_user_image (i.e. takes
507 * an existing icon/stored image). */
616 } 508 }
617 509
618 /* 510 /*
619 * This is the callback from the "ok clicked" for "set vCard" 511 * This is the callback from the "ok clicked" for "set vCard"
620 * 512 *
1185 1077
1186 static void jabber_vcard_save_mine(JabberStream *js, const char *from, 1078 static void jabber_vcard_save_mine(JabberStream *js, const char *from,
1187 JabberIqType type, const char *id, 1079 JabberIqType type, const char *id,
1188 xmlnode *packet, gpointer data) 1080 xmlnode *packet, gpointer data)
1189 { 1081 {
1190 xmlnode *vcard; 1082 xmlnode *vcard, *photo, *binval;
1191 char *txt; 1083 char *txt, *vcard_hash = NULL;
1192 PurpleStoredImage *img;
1193 1084
1194 if (type == JABBER_IQ_ERROR) { 1085 if (type == JABBER_IQ_ERROR) {
1195 purple_debug_warning("jabber", "Server returned error while retrieving vCard"); 1086 purple_debug_warning("jabber", "Server returned error while retrieving vCard");
1196 return; 1087 return;
1197 } 1088 }
1207 /* if we have no vCard, then lets not overwrite what we might have locally */ 1098 /* if we have no vCard, then lets not overwrite what we might have locally */
1208 } 1099 }
1209 1100
1210 js->vcard_fetched = TRUE; 1101 js->vcard_fetched = TRUE;
1211 1102
1212 if(NULL != (img = purple_buddy_icons_find_account_icon(js->gc->account))) { 1103 if (vcard && (photo = xmlnode_get_child(vcard, "PHOTO")) &&
1213 jabber_set_buddy_icon(js->gc, img); 1104 (binval = xmlnode_get_child(photo, "BINVAL"))) {
1214 purple_imgstore_unref(img); 1105 gsize size;
1215 } 1106 char *bintext = xmlnode_get_data(binval);
1107 guchar *data = purple_base64_decode(bintext, &size);
1108 g_free(bintext);
1109
1110 if (data) {
1111 vcard_hash = jabber_calculate_data_sha1sum(data, size);
1112 g_free(data);
1113 }
1114 }
1115
1116 /* Republish our vcard if the photo is different than the server's */
1117 if ((!vcard_hash && js->initial_avatar_hash) ||
1118 (vcard_hash && (!js->initial_avatar_hash || strcmp(vcard_hash, js->initial_avatar_hash)))) {
1119 PurpleAccount *account = purple_connection_get_account(js->gc);
1120 jabber_set_info(js->gc, purple_account_get_user_info(account));
1121 } else if (js->initial_avatar_hash) {
1122 /* Our photo is in the vcard, so advertise vcard-temp updates */
1123 js->avatar_hash = g_strdup(js->initial_avatar_hash);
1124 }
1125
1126 g_free(vcard_hash);
1216 } 1127 }
1217 1128
1218 void jabber_vcard_fetch_mine(JabberStream *js) 1129 void jabber_vcard_fetch_mine(JabberStream *js)
1219 { 1130 {
1220 JabberIq *iq = jabber_iq_new(js, JABBER_IQ_GET); 1131 JabberIq *iq = jabber_iq_new(js, JABBER_IQ_GET);
1456 } 1367 }
1457 1368
1458 g_free(bare_jid); 1369 g_free(bare_jid);
1459 1370
1460 jabber_buddy_info_show_if_ready(jbi); 1371 jabber_buddy_info_show_if_ready(jbi);
1461 }
1462
1463 typedef struct _JabberBuddyAvatarUpdateURLInfo {
1464 JabberStream *js;
1465 char *from;
1466 char *id;
1467 } JabberBuddyAvatarUpdateURLInfo;
1468
1469 static void do_buddy_avatar_update_fromurl(PurpleUtilFetchUrlData *url_data, gpointer user_data, const gchar *url_text, gsize len, const gchar *error_message) {
1470 JabberBuddyAvatarUpdateURLInfo *info = user_data;
1471 if(!url_text) {
1472 purple_debug(PURPLE_DEBUG_ERROR, "jabber",
1473 "do_buddy_avatar_update_fromurl got error \"%s\"", error_message);
1474 return;
1475 }
1476
1477 purple_buddy_icons_set_for_user(purple_connection_get_account(info->js->gc), info->from, (void*)url_text, len, info->id);
1478 g_free(info->from);
1479 g_free(info->id);
1480 g_free(info);
1481 }
1482
1483 static void do_buddy_avatar_update_data(JabberStream *js, const char *from, xmlnode *items) {
1484 xmlnode *item, *data;
1485 const char *checksum;
1486 char *b64data;
1487 void *img;
1488 size_t size;
1489 if(!items)
1490 return;
1491
1492 item = xmlnode_get_child(items, "item");
1493 if(!item)
1494 return;
1495
1496 data = xmlnode_get_child_with_namespace(item,"data",AVATARNAMESPACEDATA);
1497 if(!data)
1498 return;
1499
1500 checksum = xmlnode_get_attrib(item,"id");
1501 if(!checksum)
1502 return;
1503
1504 b64data = xmlnode_get_data(data);
1505 if(!b64data)
1506 return;
1507
1508 img = purple_base64_decode(b64data, &size);
1509 if(!img) {
1510 g_free(b64data);
1511 return;
1512 }
1513
1514 purple_buddy_icons_set_for_user(purple_connection_get_account(js->gc), from, img, size, checksum);
1515 g_free(b64data);
1516 }
1517
1518 void jabber_buddy_avatar_update_metadata(JabberStream *js, const char *from, xmlnode *items) {
1519 PurpleBuddy *buddy = purple_find_buddy(purple_connection_get_account(js->gc), from);
1520 const char *checksum;
1521 xmlnode *item, *metadata;
1522 if(!buddy)
1523 return;
1524
1525 checksum = purple_buddy_icons_get_checksum_for_user(buddy);
1526 item = xmlnode_get_child(items,"item");
1527 metadata = xmlnode_get_child_with_namespace(item, "metadata", AVATARNAMESPACEMETA);
1528 if(!metadata)
1529 return;
1530 /* check if we have received a stop */
1531 if(xmlnode_get_child(metadata, "stop")) {
1532 purple_buddy_icons_set_for_user(purple_connection_get_account(js->gc), from, NULL, 0, NULL);
1533 } else {
1534 xmlnode *info, *goodinfo = NULL;
1535 gboolean has_children = FALSE;
1536
1537 /* iterate over all info nodes to get one we can use */
1538 for(info = metadata->child; info; info = info->next) {
1539 if(info->type == XMLNODE_TYPE_TAG)
1540 has_children = TRUE;
1541 if(info->type == XMLNODE_TYPE_TAG && !strcmp(info->name,"info")) {
1542 const char *type = xmlnode_get_attrib(info,"type");
1543 const char *id = xmlnode_get_attrib(info,"id");
1544
1545 if(checksum && id && !strcmp(id, checksum)) {
1546 /* we already have that avatar, so we don't have to do anything */
1547 goodinfo = NULL;
1548 break;
1549 }
1550 /* We'll only pick the png one for now. It's a very nice image format anyways. */
1551 if(type && id && !goodinfo && !strcmp(type, "image/png"))
1552 goodinfo = info;
1553 }
1554 }
1555 if(has_children == FALSE) {
1556 purple_buddy_icons_set_for_user(purple_connection_get_account(js->gc), from, NULL, 0, NULL);
1557 } else if(goodinfo) {
1558 const char *url = xmlnode_get_attrib(goodinfo, "url");
1559 const char *id = xmlnode_get_attrib(goodinfo,"id");
1560
1561 /* the avatar might either be stored in a pep node, or on a HTTP/HTTPS URL */
1562 if(!url)
1563 jabber_pep_request_item(js, from, AVATARNAMESPACEDATA, id, do_buddy_avatar_update_data);
1564 else {
1565 PurpleUtilFetchUrlData *url_data;
1566 JabberBuddyAvatarUpdateURLInfo *info = g_new0(JabberBuddyAvatarUpdateURLInfo, 1);
1567 info->js = js;
1568
1569 url_data = purple_util_fetch_url_len(url, TRUE, NULL, TRUE,
1570 MAX_HTTP_BUDDYICON_BYTES,
1571 do_buddy_avatar_update_fromurl, info);
1572 if (url_data) {
1573 info->from = g_strdup(from);
1574 info->id = g_strdup(id);
1575 js->url_datas = g_slist_prepend(js->url_datas, url_data);
1576 } else
1577 g_free(info);
1578
1579 }
1580 }
1581 }
1582 } 1372 }
1583 1373
1584 static void jabber_buddy_info_resource_free(gpointer data) 1374 static void jabber_buddy_info_resource_free(gpointer data)
1585 { 1375 {
1586 JabberBuddyInfoResource *jbri = data; 1376 JabberBuddyInfoResource *jbri = data;