Mercurial > pidgin
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; |