Migrate certificates, icons, logs to XDG dirs
[pidgin-git.git] / libpurple / protocols / jabber / buddy.c
blob8b4b47c0c468e20f5e4ffa114f5bc217d35b2de6
1 /*
2 * purple - Jabber Protocol Plugin
4 * Purple is the legal property of its developers, whose names are too numerous
5 * to list here. Please refer to the COPYRIGHT file distributed with this
6 * source distribution.
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"
24 #include "debug.h"
25 #include "image-store.h"
26 #include "protocol.h"
27 #include "notify.h"
28 #include "request.h"
29 #include "util.h"
30 #include "xmlnode.h"
32 #include "buddy.h"
33 #include "chat.h"
34 #include "jabber.h"
35 #include "iq.h"
36 #include "presence.h"
37 #include "useravatar.h"
38 #include "xdata.h"
39 #include "pep.h"
40 #include "adhoccommands.h"
41 #include "google/google.h"
43 typedef struct {
44 long idle_seconds;
45 } JabberBuddyInfoResource;
47 typedef struct {
48 JabberStream *js;
49 JabberBuddy *jb;
50 char *jid;
51 GSList *ids;
52 GHashTable *resources;
53 guint timeout_handle;
54 GSList *vcard_images;
55 PurpleNotifyUserInfo *user_info;
56 long last_seconds;
57 gchar *last_message;
58 } JabberBuddyInfo;
60 static void
61 jabber_buddy_resource_free(JabberBuddyResource *jbr)
63 g_return_if_fail(jbr != NULL);
65 jbr->jb->resources = g_list_remove(jbr->jb->resources, jbr);
67 while(jbr->commands) {
68 JabberAdHocCommands *cmd = jbr->commands->data;
69 g_free(cmd->jid);
70 g_free(cmd->node);
71 g_free(cmd->name);
72 g_free(cmd);
73 jbr->commands = g_list_delete_link(jbr->commands, jbr->commands);
76 while (jbr->caps.exts) {
77 g_free(jbr->caps.exts->data);
78 jbr->caps.exts = g_list_delete_link(jbr->caps.exts, jbr->caps.exts);
81 g_free(jbr->name);
82 g_free(jbr->status);
83 g_free(jbr->thread_id);
84 g_free(jbr->client.name);
85 g_free(jbr->client.version);
86 g_free(jbr->client.os);
87 g_free(jbr);
90 void jabber_buddy_free(JabberBuddy *jb)
92 g_return_if_fail(jb != NULL);
94 g_free(jb->error_msg);
95 while(jb->resources)
96 jabber_buddy_resource_free(jb->resources->data);
98 g_free(jb);
101 JabberBuddy *jabber_buddy_find(JabberStream *js, const char *name,
102 gboolean create)
104 JabberBuddy *jb;
105 char *realname;
107 if (js->buddies == NULL)
108 return NULL;
110 if(!(realname = jabber_get_bare_jid(name)))
111 return NULL;
113 jb = g_hash_table_lookup(js->buddies, realname);
115 if(!jb && create) {
116 jb = g_new0(JabberBuddy, 1);
117 g_hash_table_insert(js->buddies, realname, jb);
118 } else
119 g_free(realname);
121 return jb;
124 /* Returns -1 if a is a higher priority resource than b, or is
125 * "more available" than b. 0 if they're the same, and 1 if b is
126 * higher priority/more available than a.
128 static gint resource_compare_cb(gconstpointer a, gconstpointer b)
130 const JabberBuddyResource *jbra = a;
131 const JabberBuddyResource *jbrb = b;
132 JabberBuddyState state_a, state_b;
134 if (jbra->priority != jbrb->priority)
135 return jbra->priority > jbrb->priority ? -1 : 1;
137 /* Fold the states for easier comparison */
138 /* TODO: Differentiate online/chat and away/dnd? */
139 switch (jbra->state) {
140 case JABBER_BUDDY_STATE_ONLINE:
141 case JABBER_BUDDY_STATE_CHAT:
142 state_a = JABBER_BUDDY_STATE_ONLINE;
143 break;
144 case JABBER_BUDDY_STATE_AWAY:
145 case JABBER_BUDDY_STATE_DND:
146 state_a = JABBER_BUDDY_STATE_AWAY;
147 break;
148 case JABBER_BUDDY_STATE_XA:
149 state_a = JABBER_BUDDY_STATE_XA;
150 break;
151 case JABBER_BUDDY_STATE_UNAVAILABLE:
152 state_a = JABBER_BUDDY_STATE_UNAVAILABLE;
153 break;
154 default:
155 state_a = JABBER_BUDDY_STATE_UNKNOWN;
156 break;
159 switch (jbrb->state) {
160 case JABBER_BUDDY_STATE_ONLINE:
161 case JABBER_BUDDY_STATE_CHAT:
162 state_b = JABBER_BUDDY_STATE_ONLINE;
163 break;
164 case JABBER_BUDDY_STATE_AWAY:
165 case JABBER_BUDDY_STATE_DND:
166 state_b = JABBER_BUDDY_STATE_AWAY;
167 break;
168 case JABBER_BUDDY_STATE_XA:
169 state_b = JABBER_BUDDY_STATE_XA;
170 break;
171 case JABBER_BUDDY_STATE_UNAVAILABLE:
172 state_b = JABBER_BUDDY_STATE_UNAVAILABLE;
173 break;
174 default:
175 state_b = JABBER_BUDDY_STATE_UNKNOWN;
176 break;
179 if (state_a == state_b) {
180 if (jbra->idle == jbrb->idle)
181 return 0;
182 else if ((jbra->idle && !jbrb->idle) ||
183 (jbra->idle && jbrb->idle && jbra->idle < jbrb->idle))
184 return 1;
185 else
186 return -1;
189 if (state_a == JABBER_BUDDY_STATE_ONLINE)
190 return -1;
191 else if (state_a == JABBER_BUDDY_STATE_AWAY &&
192 (state_b == JABBER_BUDDY_STATE_XA ||
193 state_b == JABBER_BUDDY_STATE_UNAVAILABLE ||
194 state_b == JABBER_BUDDY_STATE_UNKNOWN))
195 return -1;
196 else if (state_a == JABBER_BUDDY_STATE_XA &&
197 (state_b == JABBER_BUDDY_STATE_UNAVAILABLE ||
198 state_b == JABBER_BUDDY_STATE_UNKNOWN))
199 return -1;
200 else if (state_a == JABBER_BUDDY_STATE_UNAVAILABLE &&
201 state_b == JABBER_BUDDY_STATE_UNKNOWN)
202 return -1;
204 return 1;
207 JabberBuddyResource *jabber_buddy_find_resource(JabberBuddy *jb,
208 const char *resource)
210 GList *l;
212 if (!jb)
213 return NULL;
215 if (resource == NULL)
216 return jb->resources ? jb->resources->data : NULL;
218 for (l = jb->resources; l; l = l->next)
220 JabberBuddyResource *jbr = l->data;
221 if (jbr->name && g_str_equal(resource, jbr->name))
222 return jbr;
225 return NULL;
228 JabberBuddyResource *jabber_buddy_track_resource(JabberBuddy *jb, const char *resource,
229 int priority, JabberBuddyState state, const char *status)
231 /* TODO: Optimization: Only reinsert if priority+state changed */
232 JabberBuddyResource *jbr = jabber_buddy_find_resource(jb, resource);
233 if (jbr) {
234 jb->resources = g_list_remove(jb->resources, jbr);
235 } else {
236 jbr = g_new0(JabberBuddyResource, 1);
237 jbr->jb = jb;
238 jbr->name = g_strdup(resource);
239 jbr->capabilities = JABBER_CAP_NONE;
240 jbr->tz_off = PURPLE_NO_TZ_OFF;
242 jbr->priority = priority;
243 jbr->state = state;
244 g_free(jbr->status);
245 jbr->status = g_strdup(status);
247 jb->resources = g_list_insert_sorted(jb->resources, jbr,
248 resource_compare_cb);
249 return jbr;
252 void jabber_buddy_remove_resource(JabberBuddy *jb, const char *resource)
254 JabberBuddyResource *jbr = jabber_buddy_find_resource(jb, resource);
256 if(!jbr)
257 return;
259 jabber_buddy_resource_free(jbr);
262 /*******
263 * This is the old vCard stuff taken from the old prpl. vCards, by definition
264 * are a temporary thing until jabber can get its act together and come up
265 * with a format for user information, hence the namespace of 'vcard-temp'
267 * Since I don't feel like putting that much work into something that's
268 * _supposed_ to go away, i'm going to just copy the kludgy old code here,
269 * and make it purdy when jabber comes up with a standards-track JEP to
270 * replace vcard-temp
271 * --Nathan
272 *******/
274 /*---------------------------------------*/
275 /* Jabber "set info" (vCard) support */
276 /*---------------------------------------*/
279 * V-Card format:
281 * <vCard prodid='' version='' xmlns=''>
282 * <FN></FN>
283 * <N>
284 * <FAMILY/>
285 * <GIVEN/>
286 * </N>
287 * <NICKNAME/>
288 * <URL/>
289 * <ADR>
290 * <STREET/>
291 * <EXTADD/>
292 * <LOCALITY/>
293 * <REGION/>
294 * <PCODE/>
295 * <COUNTRY/>
296 * </ADR>
297 * <TEL/>
298 * <EMAIL/>
299 * <ORG>
300 * <ORGNAME/>
301 * <ORGUNIT/>
302 * </ORG>
303 * <TITLE/>
304 * <ROLE/>
305 * <DESC/>
306 * <BDAY/>
307 * </vCard>
309 * See also:
311 * http://docs.jabber.org/proto/html/vcard-temp.html
312 * http://www.vcard-xml.org/dtd/vCard-XML-v2-20010520.dtd
316 * Cross-reference user-friendly V-Card entry labels to vCard XML tags
317 * and attributes.
319 * Order is (or should be) unimportant. For example: we have no way of
320 * knowing in what order real data will arrive.
322 * Format: Label, Pre-set text, "visible" flag, "editable" flag, XML tag
323 * name, XML tag's parent tag "path" (relative to vCard node).
325 * List is terminated by a NULL label pointer.
327 * Entries with no label text, but with XML tag and parent tag
328 * entries, are used by V-Card XML construction routines to
329 * "automagically" construct the appropriate XML node tree.
331 * Thoughts on future direction/expansion
333 * This is a "simple" vCard.
335 * It is possible for nodes other than the "vCard" node to have
336 * attributes. Should that prove necessary/desirable, add an
337 * "attributes" pointer to the vcard_template struct, create the
338 * necessary tag_attr structs, and add 'em to the vcard_dflt_data
339 * array.
341 * The above changes will (obviously) require changes to the vCard
342 * construction routines.
345 struct vcard_template {
346 char *label; /* label text pointer */
347 char *tag; /* tag text */
348 char *ptag; /* parent tag "path" text */
349 } const vcard_template_data[] = {
350 {N_("Full Name"), "FN", NULL},
351 {N_("Family Name"), "FAMILY", "N"},
352 {N_("Given Name"), "GIVEN", "N"},
353 {N_("Nickname"), "NICKNAME", NULL},
354 {N_("URL"), "URL", NULL},
355 {N_("Street Address"), "STREET", "ADR"},
356 {N_("Extended Address"), "EXTADD", "ADR"},
357 {N_("Locality"), "LOCALITY", "ADR"},
358 {N_("Region"), "REGION", "ADR"},
359 {N_("Postal Code"), "PCODE", "ADR"},
360 {N_("Country"), "CTRY", "ADR"},
361 {N_("Telephone"), "NUMBER", "TEL"},
362 {N_("Email"), "USERID", "EMAIL"},
363 {N_("Organization Name"), "ORGNAME", "ORG"},
364 {N_("Organization Unit"), "ORGUNIT", "ORG"},
365 {N_("Job Title"), "TITLE", NULL},
366 {N_("Role"), "ROLE", NULL},
367 {N_("Birthday"), "BDAY", NULL},
368 {N_("Description"), "DESC", NULL},
369 {"", "N", NULL},
370 {"", "ADR", NULL},
371 {"", "ORG", NULL},
372 {NULL, NULL, NULL}
376 * The "vCard" tag's attribute list...
378 struct tag_attr {
379 char *attr;
380 char *value;
381 } const vcard_tag_attr_list[] = {
382 {"prodid", "-//HandGen//NONSGML vGen v1.0//EN"},
383 {"version", "2.0", },
384 {"xmlns", "vcard-temp", },
385 {NULL, NULL},
390 * Insert a tag node into an PurpleXmlNode tree, recursively inserting parent tag
391 * nodes as necessary
393 * Returns pointer to inserted node
395 * Note to hackers: this code is designed to be re-entrant (it's recursive--it
396 * calls itself), so don't put any "static"s in here!
398 static PurpleXmlNode *insert_tag_to_parent_tag(PurpleXmlNode *start, const char *parent_tag, const char *new_tag)
400 PurpleXmlNode *x = NULL;
403 * If the parent tag wasn't specified, see if we can get it
404 * from the vCard template struct.
406 if(parent_tag == NULL) {
407 const struct vcard_template *vc_tp = vcard_template_data;
409 while(vc_tp->label != NULL) {
410 if(strcmp(vc_tp->tag, new_tag) == 0) {
411 parent_tag = vc_tp->ptag;
412 break;
414 ++vc_tp;
419 * If we have a parent tag...
421 if(parent_tag != NULL ) {
423 * Try to get the parent node for a tag
425 if((x = purple_xmlnode_get_child(start, parent_tag)) == NULL) {
427 * Descend?
429 char *grand_parent = g_strdup(parent_tag);
430 char *parent;
432 if((parent = strrchr(grand_parent, '/')) != NULL) {
433 *(parent++) = '\0';
434 x = insert_tag_to_parent_tag(start, grand_parent, parent);
435 } else {
436 x = purple_xmlnode_new_child(start, grand_parent);
438 g_free(grand_parent);
439 } else {
441 * We found *something* to be the parent node.
442 * Note: may be the "root" node!
444 PurpleXmlNode *y;
445 if((y = purple_xmlnode_get_child(x, new_tag)) != NULL) {
446 return(y);
452 * insert the new tag into its parent node
454 return(purple_xmlnode_new_child((x == NULL? start : x), new_tag));
458 * Send vCard info to Jabber server
460 void jabber_set_info(PurpleConnection *gc, const char *info)
462 PurpleImage *img;
463 JabberIq *iq;
464 JabberStream *js = purple_connection_get_protocol_data(gc);
465 PurpleXmlNode *vc_node;
466 const struct tag_attr *tag_attr;
468 /* if we haven't grabbed the remote vcard yet, we can't
469 * assume that what we have here is correct */
470 if(!js->vcard_fetched) {
471 PurpleImage *image;
472 g_free(js->initial_avatar_hash);
473 image = purple_buddy_icons_find_account_icon(purple_connection_get_account(gc));
474 if (image != NULL) {
475 js->initial_avatar_hash = jabber_calculate_data_hash(
476 purple_image_get_data(image),
477 purple_image_get_size(image), "sha1");
478 g_object_unref(image);
479 } else {
480 js->initial_avatar_hash = NULL;
482 return;
485 if (js->vcard_timer) {
486 purple_timeout_remove(js->vcard_timer);
487 js->vcard_timer = 0;
490 g_free(js->avatar_hash);
491 js->avatar_hash = NULL;
494 * Send only if there's actually any *information* to send
496 vc_node = info ? purple_xmlnode_from_str(info, -1) : NULL;
498 if (vc_node && (!vc_node->name ||
499 g_ascii_strncasecmp(vc_node->name, "vCard", 5))) {
500 purple_xmlnode_free(vc_node);
501 vc_node = NULL;
504 if ((img = purple_buddy_icons_find_account_icon(purple_connection_get_account(gc)))) {
505 gconstpointer avatar_data;
506 gsize avatar_len;
507 PurpleXmlNode *photo, *binval, *type;
508 gchar *enc;
510 if(!vc_node) {
511 vc_node = purple_xmlnode_new("vCard");
512 for(tag_attr = vcard_tag_attr_list; tag_attr->attr != NULL; ++tag_attr)
513 purple_xmlnode_set_attrib(vc_node, tag_attr->attr, tag_attr->value);
516 avatar_data = purple_image_get_data(img);
517 avatar_len = purple_image_get_size(img);
518 /* Get rid of an old PHOTO if one exists.
519 * TODO: This may want to be modified to remove all old PHOTO
520 * children, at the moment some people have managed to get
521 * multiple PHOTO entries in their vCard. */
522 if((photo = purple_xmlnode_get_child(vc_node, "PHOTO"))) {
523 purple_xmlnode_free(photo);
525 photo = purple_xmlnode_new_child(vc_node, "PHOTO");
526 type = purple_xmlnode_new_child(photo, "TYPE");
527 purple_xmlnode_insert_data(type, "image/png", -1);
528 binval = purple_xmlnode_new_child(photo, "BINVAL");
529 enc = purple_base64_encode(avatar_data, avatar_len);
531 js->avatar_hash =
532 jabber_calculate_data_hash(avatar_data, avatar_len, "sha1");
534 purple_xmlnode_insert_data(binval, enc, -1);
535 g_free(enc);
536 g_object_unref(img);
537 } else if (vc_node) {
538 PurpleXmlNode *photo;
539 /* TODO: Remove all PHOTO children? (see above note) */
540 if ((photo = purple_xmlnode_get_child(vc_node, "PHOTO"))) {
541 purple_xmlnode_free(photo);
545 if (vc_node != NULL) {
546 iq = jabber_iq_new(js, JABBER_IQ_SET);
547 purple_xmlnode_insert_child(iq->node, vc_node);
548 jabber_iq_send(iq);
550 /* Send presence to update vcard-temp:x:update */
551 jabber_presence_send(js, FALSE);
555 void jabber_set_buddy_icon(PurpleConnection *gc, PurpleImage *img)
557 PurpleAccount *account = purple_connection_get_account(gc);
559 /* Publish the avatar as specified in XEP-0084 */
560 jabber_avatar_set(purple_connection_get_protocol_data(gc), img);
561 /* Set the image in our vCard */
562 jabber_set_info(gc, purple_account_get_user_info(account));
564 /* TODO: Fake image to ourselves, since a number of servers do not echo
565 * back our presence to us. To do this without uselessly copying the data
566 * of the image, we need purple_buddy_icons_set_for_user_image (i.e. takes
567 * an existing icon/stored image). */
571 * This is the callback from the "ok clicked" for "set vCard"
573 * Sets the vCard with data from PurpleRequestFields.
575 static void
576 jabber_format_info(PurpleConnection *gc, PurpleRequestFields *fields)
578 PurpleXmlNode *vc_node;
579 PurpleRequestField *field;
580 const char *text;
581 char *p;
582 const struct vcard_template *vc_tp;
583 const struct tag_attr *tag_attr;
585 vc_node = purple_xmlnode_new("vCard");
587 for(tag_attr = vcard_tag_attr_list; tag_attr->attr != NULL; ++tag_attr)
588 purple_xmlnode_set_attrib(vc_node, tag_attr->attr, tag_attr->value);
590 for (vc_tp = vcard_template_data; vc_tp->label != NULL; vc_tp++) {
591 if (*vc_tp->label == '\0')
592 continue;
594 field = purple_request_fields_get_field(fields, vc_tp->tag);
595 text = purple_request_field_string_get_value(field);
598 if (text != NULL && *text != '\0') {
599 PurpleXmlNode *xp;
601 purple_debug_info("jabber", "Setting %s to '%s'\n", vc_tp->tag, text);
603 if ((xp = insert_tag_to_parent_tag(vc_node,
604 NULL, vc_tp->tag)) != NULL) {
606 purple_xmlnode_insert_data(xp, text, -1);
611 p = purple_xmlnode_to_str(vc_node, NULL);
612 purple_xmlnode_free(vc_node);
614 purple_account_set_user_info(purple_connection_get_account(gc), p);
615 purple_serv_set_info(gc, p);
617 g_free(p);
621 * This gets executed by the proto action
623 * Creates a new PurpleRequestFields struct, gets the XML-formatted user_info
624 * string (if any) into GSLists for the (multi-entry) edit dialog and
625 * calls the set_vcard dialog.
627 void jabber_setup_set_info(PurpleProtocolAction *action)
629 PurpleConnection *gc = (PurpleConnection *) action->connection;
630 PurpleRequestFields *fields;
631 PurpleRequestFieldGroup *group;
632 PurpleRequestField *field;
633 const struct vcard_template *vc_tp;
634 const char *user_info;
635 char *cdata = NULL;
636 PurpleXmlNode *x_vc_data = NULL;
638 fields = purple_request_fields_new();
639 group = purple_request_field_group_new(NULL);
640 purple_request_fields_add_group(fields, group);
643 * Get existing, XML-formatted, user info
645 if((user_info = purple_account_get_user_info(purple_connection_get_account(gc))) != NULL)
646 x_vc_data = purple_xmlnode_from_str(user_info, -1);
649 * Set up GSLists for edit with labels from "template," data from user info
651 for(vc_tp = vcard_template_data; vc_tp->label != NULL; ++vc_tp) {
652 PurpleXmlNode *data_node;
653 if((vc_tp->label)[0] == '\0')
654 continue;
656 if (x_vc_data != NULL) {
657 if(vc_tp->ptag == NULL) {
658 data_node = purple_xmlnode_get_child(x_vc_data, vc_tp->tag);
659 } else {
660 gchar *tag = g_strdup_printf("%s/%s", vc_tp->ptag, vc_tp->tag);
661 data_node = purple_xmlnode_get_child(x_vc_data, tag);
662 g_free(tag);
664 if(data_node)
665 cdata = purple_xmlnode_get_data(data_node);
668 if(strcmp(vc_tp->tag, "DESC") == 0) {
669 field = purple_request_field_string_new(vc_tp->tag,
670 _(vc_tp->label), cdata,
671 TRUE);
672 } else {
673 field = purple_request_field_string_new(vc_tp->tag,
674 _(vc_tp->label), cdata,
675 FALSE);
678 g_free(cdata);
679 cdata = NULL;
681 purple_request_field_group_add_field(group, field);
684 if(x_vc_data != NULL)
685 purple_xmlnode_free(x_vc_data);
687 purple_request_fields(gc, _("Edit XMPP vCard"),
688 _("Edit XMPP vCard"),
689 _("All items below are optional. Enter only the "
690 "information with which you feel comfortable."),
691 fields,
692 _("Save"), G_CALLBACK(jabber_format_info),
693 _("Cancel"), NULL,
694 purple_request_cpar_from_connection(gc),
695 gc);
698 /*---------------------------------------*/
699 /* End Jabber "set info" (vCard) support */
700 /*---------------------------------------*/
702 /******
703 * end of that ancient crap that needs to die
704 ******/
706 static void jabber_buddy_info_destroy(JabberBuddyInfo *jbi)
708 /* Remove the timeout, which would otherwise trigger jabber_buddy_get_info_timeout() */
709 if (jbi->timeout_handle > 0)
710 purple_timeout_remove(jbi->timeout_handle);
712 g_free(jbi->jid);
713 g_hash_table_destroy(jbi->resources);
714 g_free(jbi->last_message);
715 purple_notify_user_info_destroy(jbi->user_info);
716 g_free(jbi);
719 static void
720 add_jbr_info(JabberBuddyInfo *jbi, const char *resource,
721 JabberBuddyResource *jbr)
723 JabberBuddyInfoResource *jbir;
724 PurpleNotifyUserInfo *user_info;
726 jbir = g_hash_table_lookup(jbi->resources, resource);
727 user_info = jbi->user_info;
729 if (jbr && jbr->client.name) {
730 char *tmp =
731 g_strdup_printf("%s%s%s", jbr->client.name,
732 (jbr->client.version ? " " : ""),
733 (jbr->client.version ? jbr->client.version : ""));
734 /* TODO: Check whether it's correct to call prepend_pair_html,
735 or if we should be using prepend_pair_plaintext */
736 purple_notify_user_info_prepend_pair_html(user_info, _("Client"), tmp);
737 g_free(tmp);
739 if (jbr->client.os) {
740 /* TODO: Check whether it's correct to call prepend_pair_html,
741 or if we should be using prepend_pair_plaintext */
742 purple_notify_user_info_prepend_pair_html(user_info, _("Operating System"), jbr->client.os);
746 if (jbr && jbr->tz_off != PURPLE_NO_TZ_OFF) {
747 time_t now_t;
748 struct tm *now;
749 char *timestamp;
750 time(&now_t);
751 now_t += jbr->tz_off;
752 now = gmtime(&now_t);
754 timestamp =
755 g_strdup_printf("%s %c%02d%02d", purple_time_format(now),
756 jbr->tz_off < 0 ? '-' : '+',
757 abs(jbr->tz_off / (60*60)),
758 abs((jbr->tz_off % (60*60)) / 60));
759 purple_notify_user_info_prepend_pair_plaintext(user_info, _("Local Time"), timestamp);
760 g_free(timestamp);
763 if (jbir && jbir->idle_seconds > 0) {
764 char *idle = purple_str_seconds_to_string(jbir->idle_seconds);
765 purple_notify_user_info_prepend_pair_plaintext(user_info, _("Idle"), idle);
766 g_free(idle);
769 if (jbr) {
770 char *purdy = NULL;
771 char *tmp;
772 char priority[12];
773 const char *status_name = jabber_buddy_state_get_name(jbr->state);
775 if (jbr->status) {
776 tmp = purple_markup_escape_text(jbr->status, -1);
777 purdy = purple_strdup_withhtml(tmp);
778 g_free(tmp);
780 if (purple_strequal(status_name, purdy))
781 status_name = NULL;
784 tmp = g_strdup_printf("%s%s%s", (status_name ? status_name : ""),
785 ((status_name && purdy) ? ": " : ""),
786 (purdy ? purdy : ""));
787 purple_notify_user_info_prepend_pair_html(user_info, _("Status"), tmp);
789 g_snprintf(priority, sizeof(priority), "%d", jbr->priority);
790 purple_notify_user_info_prepend_pair_plaintext(user_info, _("Priority"), priority);
792 g_free(tmp);
793 g_free(purdy);
794 } else {
795 purple_notify_user_info_prepend_pair_plaintext(user_info, _("Status"), _("Unknown"));
799 static void jabber_buddy_info_show_if_ready(JabberBuddyInfo *jbi)
801 char *resource_name;
802 JabberBuddyResource *jbr;
803 GList *resources;
804 PurpleNotifyUserInfo *user_info;
806 /* not yet */
807 if (jbi->ids)
808 return;
810 user_info = jbi->user_info;
811 resource_name = jabber_get_resource(jbi->jid);
813 /* If we have one or more pairs from the vcard, put a section break above it */
814 if (g_queue_get_length(purple_notify_user_info_get_entries(user_info)))
815 purple_notify_user_info_prepend_section_break(user_info);
817 /* Add the information about the user's resource(s) */
818 if (resource_name) {
819 jbr = jabber_buddy_find_resource(jbi->jb, resource_name);
820 add_jbr_info(jbi, resource_name, jbr);
821 } else {
822 /* TODO: This is in priority-ascending order (lowest prio first), because
823 * everything is prepended. Is that ok? */
824 for (resources = jbi->jb->resources; resources; resources = resources->next) {
825 jbr = resources->data;
827 /* put a section break between resources, this is not needed if
828 we are at the first, because one was already added for the vcard
829 section */
830 if (resources != jbi->jb->resources)
831 purple_notify_user_info_prepend_section_break(user_info);
833 add_jbr_info(jbi, jbr->name, jbr);
835 if (jbr->name) {
836 /* TODO: Check whether it's correct to call prepend_pair_html,
837 or if we should be using prepend_pair_plaintext */
838 purple_notify_user_info_prepend_pair_html(user_info, _("Resource"), jbr->name);
843 if (!jbi->jb->resources) {
844 /* the buddy is offline */
845 gboolean is_domain = jabber_jid_is_domain(jbi->jid);
847 if (jbi->last_seconds > 0) {
848 char *last = purple_str_seconds_to_string(jbi->last_seconds);
849 gchar *message = NULL;
850 const gchar *title = NULL;
851 if (is_domain) {
852 title = _("Uptime");
853 message = last;
854 last = NULL;
855 } else {
856 title = _("Logged Off");
857 message = g_strdup_printf(_("%s ago"), last);
859 purple_notify_user_info_prepend_pair_plaintext(user_info, title, message);
860 g_free(last);
861 g_free(message);
864 if (!is_domain) {
865 gchar *status =
866 g_strdup_printf("%s%s%s", _("Offline"),
867 jbi->last_message ? ": " : "",
868 jbi->last_message ? jbi->last_message : "");
869 /* TODO: Check whether it's correct to call prepend_pair_html,
870 or if we should be using prepend_pair_plaintext */
871 purple_notify_user_info_prepend_pair_html(user_info, _("Status"), status);
872 g_free(status);
876 g_free(resource_name);
878 purple_notify_userinfo(jbi->js->gc, jbi->jid, user_info, NULL, NULL);
880 while (jbi->vcard_images) {
881 g_object_unref(jbi->vcard_images->data);
882 jbi->vcard_images = g_slist_delete_link(jbi->vcard_images, jbi->vcard_images);
885 jbi->js->pending_buddy_info_requests = g_slist_remove(jbi->js->pending_buddy_info_requests, jbi);
887 jabber_buddy_info_destroy(jbi);
890 static void jabber_buddy_info_remove_id(JabberBuddyInfo *jbi, const char *id)
892 GSList *l = jbi->ids;
893 char *comp_id;
895 if(!id)
896 return;
898 while(l) {
899 comp_id = l->data;
900 if(!strcmp(id, comp_id)) {
901 jbi->ids = g_slist_remove(jbi->ids, comp_id);
902 g_free(comp_id);
903 return;
905 l = l->next;
909 static gboolean
910 set_own_vcard_cb(gpointer data)
912 JabberStream *js = data;
913 PurpleAccount *account = purple_connection_get_account(js->gc);
915 js->vcard_timer = 0;
917 jabber_set_info(js->gc, purple_account_get_user_info(account));
919 return FALSE;
922 static void jabber_vcard_save_mine(JabberStream *js, const char *from,
923 JabberIqType type, const char *id,
924 PurpleXmlNode *packet, gpointer data)
926 PurpleXmlNode *vcard, *photo, *binval;
927 char *txt, *vcard_hash = NULL;
928 PurpleAccount *account;
930 if (type == JABBER_IQ_ERROR) {
931 PurpleXmlNode *error;
932 purple_debug_warning("jabber", "Server returned error while retrieving vCard\n");
934 error = purple_xmlnode_get_child(packet, "error");
935 if (!error || !purple_xmlnode_get_child(error, "item-not-found"))
936 return;
939 account = purple_connection_get_account(js->gc);
941 if((vcard = purple_xmlnode_get_child(packet, "vCard")) ||
942 (vcard = purple_xmlnode_get_child_with_namespace(packet, "query", "vcard-temp")))
944 txt = purple_xmlnode_to_str(vcard, NULL);
945 purple_account_set_user_info(account, txt);
946 g_free(txt);
947 } else {
948 /* if we have no vCard, then lets not overwrite what we might have locally */
951 js->vcard_fetched = TRUE;
953 if (vcard && (photo = purple_xmlnode_get_child(vcard, "PHOTO")) &&
954 (binval = purple_xmlnode_get_child(photo, "BINVAL"))) {
955 gsize size;
956 char *bintext = purple_xmlnode_get_data(binval);
957 if (bintext) {
958 guchar *data = purple_base64_decode(bintext, &size);
959 g_free(bintext);
961 if (data) {
962 vcard_hash = jabber_calculate_data_hash(data, size, "sha1");
963 g_free(data);
968 /* Republish our vcard if the photo is different than the server's */
969 if (js->initial_avatar_hash && !purple_strequal(vcard_hash, js->initial_avatar_hash)) {
971 * Google Talk has developed the behavior that it will not accept
972 * a vcard set in the first 10 seconds (or so) of the connection;
973 * it returns an error (namespaces trimmed):
974 * <error code="500" type="wait"><internal-server-error/></error>.
976 if (js->googletalk)
977 js->vcard_timer = purple_timeout_add_seconds(10, set_own_vcard_cb,
978 js);
979 else
980 jabber_set_info(js->gc, purple_account_get_user_info(account));
981 } else if (vcard_hash) {
982 /* A photo is in the vCard. Advertise its hash */
983 js->avatar_hash = vcard_hash;
984 vcard_hash = NULL;
986 /* Send presence to update vcard-temp:x:update */
987 jabber_presence_send(js, FALSE);
990 g_free(vcard_hash);
993 void jabber_vcard_fetch_mine(JabberStream *js)
995 JabberIq *iq = jabber_iq_new(js, JABBER_IQ_GET);
997 PurpleXmlNode *vcard = purple_xmlnode_new_child(iq->node, "vCard");
998 purple_xmlnode_set_namespace(vcard, "vcard-temp");
999 jabber_iq_set_callback(iq, jabber_vcard_save_mine, NULL);
1001 jabber_iq_send(iq);
1004 static void jabber_vcard_parse(JabberStream *js, const char *from,
1005 JabberIqType type, const char *id,
1006 PurpleXmlNode *packet, gpointer data)
1008 char *bare_jid;
1009 char *text;
1010 char *serverside_alias = NULL;
1011 PurpleXmlNode *vcard;
1012 PurpleAccount *account;
1013 JabberBuddyInfo *jbi = data;
1014 PurpleNotifyUserInfo *user_info;
1016 g_return_if_fail(jbi != NULL);
1018 jabber_buddy_info_remove_id(jbi, id);
1020 if (type == JABBER_IQ_ERROR) {
1021 purple_debug_info("jabber", "Got error response for vCard\n");
1022 jabber_buddy_info_show_if_ready(jbi);
1023 return;
1026 user_info = jbi->user_info;
1027 account = purple_connection_get_account(js->gc);
1028 bare_jid = jabber_get_bare_jid(from ? from : purple_account_get_username(account));
1030 /* TODO: Is the query xmlns='vcard-temp' version of this still necessary? */
1031 if((vcard = purple_xmlnode_get_child(packet, "vCard")) ||
1032 (vcard = purple_xmlnode_get_child_with_namespace(packet, "query", "vcard-temp"))) {
1033 PurpleXmlNode *child;
1034 for(child = vcard->child; child; child = child->next)
1036 PurpleXmlNode *child2;
1038 if(child->type != PURPLE_XMLNODE_TYPE_TAG)
1039 continue;
1041 text = purple_xmlnode_get_data(child);
1042 if(text && !strcmp(child->name, "FN")) {
1043 if (!serverside_alias)
1044 serverside_alias = g_strdup(text);
1046 purple_notify_user_info_add_pair_plaintext(user_info, _("Full Name"), text);
1047 } else if(!strcmp(child->name, "N")) {
1048 for(child2 = child->child; child2; child2 = child2->next)
1050 char *text2;
1052 if(child2->type != PURPLE_XMLNODE_TYPE_TAG)
1053 continue;
1055 text2 = purple_xmlnode_get_data(child2);
1056 if(text2 && !strcmp(child2->name, "FAMILY")) {
1057 purple_notify_user_info_add_pair_plaintext(user_info, _("Family Name"), text2);
1058 } else if(text2 && !strcmp(child2->name, "GIVEN")) {
1059 purple_notify_user_info_add_pair_plaintext(user_info, _("Given Name"), text2);
1060 } else if(text2 && !strcmp(child2->name, "MIDDLE")) {
1061 purple_notify_user_info_add_pair_plaintext(user_info, _("Middle Name"), text2);
1063 g_free(text2);
1065 } else if(text && !strcmp(child->name, "NICKNAME")) {
1066 /* Prefer the Nickcname to the Full Name as the serverside alias if it's not just part of the jid.
1067 * Ignore it if it's part of the jid. */
1068 if (strstr(bare_jid, text) == NULL) {
1069 g_free(serverside_alias);
1070 serverside_alias = g_strdup(text);
1072 purple_notify_user_info_add_pair_plaintext(user_info, _("Nickname"), text);
1074 } else if(text && !strcmp(child->name, "BDAY")) {
1075 purple_notify_user_info_add_pair_plaintext(user_info, _("Birthday"), text);
1076 } else if(!strcmp(child->name, "ADR")) {
1077 gboolean address_line_added = FALSE;
1079 for(child2 = child->child; child2; child2 = child2->next)
1081 char *text2;
1083 if(child2->type != PURPLE_XMLNODE_TYPE_TAG)
1084 continue;
1086 text2 = purple_xmlnode_get_data(child2);
1087 if (text2 == NULL)
1088 continue;
1090 /* We do this here so that it's not added if all the child
1091 * elements are empty. */
1092 if (!address_line_added)
1094 purple_notify_user_info_add_section_header(user_info, _("Address"));
1095 address_line_added = TRUE;
1098 if(!strcmp(child2->name, "POBOX")) {
1099 purple_notify_user_info_add_pair_plaintext(user_info, _("P.O. Box"), text2);
1100 } else if (g_str_equal(child2->name, "EXTADD") || g_str_equal(child2->name, "EXTADR")) {
1102 * EXTADD is correct, EXTADR is generated by other
1103 * clients. The next time someone reads this, remove
1104 * EXTADR.
1106 purple_notify_user_info_add_pair_plaintext(user_info, _("Extended Address"), text2);
1107 } else if(!strcmp(child2->name, "STREET")) {
1108 purple_notify_user_info_add_pair_plaintext(user_info, _("Street Address"), text2);
1109 } else if(!strcmp(child2->name, "LOCALITY")) {
1110 purple_notify_user_info_add_pair_plaintext(user_info, _("Locality"), text2);
1111 } else if(!strcmp(child2->name, "REGION")) {
1112 purple_notify_user_info_add_pair_plaintext(user_info, _("Region"), text2);
1113 } else if(!strcmp(child2->name, "PCODE")) {
1114 purple_notify_user_info_add_pair_plaintext(user_info, _("Postal Code"), text2);
1115 } else if(!strcmp(child2->name, "CTRY")
1116 || !strcmp(child2->name, "COUNTRY")) {
1117 purple_notify_user_info_add_pair_plaintext(user_info, _("Country"), text2);
1119 g_free(text2);
1122 if (address_line_added)
1123 purple_notify_user_info_add_section_break(user_info);
1125 } else if(!strcmp(child->name, "TEL")) {
1126 char *number;
1127 if((child2 = purple_xmlnode_get_child(child, "NUMBER"))) {
1128 /* show what kind of number it is */
1129 number = purple_xmlnode_get_data(child2);
1130 if(number) {
1131 purple_notify_user_info_add_pair_plaintext(user_info, _("Telephone"), number);
1132 g_free(number);
1134 } else if((number = purple_xmlnode_get_data(child))) {
1135 /* lots of clients (including purple) do this, but it's
1136 * out of spec */
1137 purple_notify_user_info_add_pair_plaintext(user_info, _("Telephone"), number);
1138 g_free(number);
1140 } else if(!strcmp(child->name, "EMAIL")) {
1141 char *userid, *escaped;
1142 if((child2 = purple_xmlnode_get_child(child, "USERID"))) {
1143 /* show what kind of email it is */
1144 userid = purple_xmlnode_get_data(child2);
1145 if(userid) {
1146 char *mailto;
1147 escaped = g_markup_escape_text(userid, -1);
1148 mailto = g_strdup_printf("<a href=\"mailto:%s\">%s</a>", escaped, escaped);
1149 purple_notify_user_info_add_pair_html(user_info, _("Email"), mailto);
1151 g_free(mailto);
1152 g_free(escaped);
1153 g_free(userid);
1155 } else if((userid = purple_xmlnode_get_data(child))) {
1156 /* lots of clients (including purple) do this, but it's
1157 * out of spec */
1158 char *mailto;
1160 escaped = g_markup_escape_text(userid, -1);
1161 mailto = g_strdup_printf("<a href=\"mailto:%s\">%s</a>", escaped, escaped);
1162 purple_notify_user_info_add_pair_html(user_info, _("Email"), mailto);
1164 g_free(mailto);
1165 g_free(escaped);
1166 g_free(userid);
1168 } else if(!strcmp(child->name, "ORG")) {
1169 for(child2 = child->child; child2; child2 = child2->next)
1171 char *text2;
1173 if(child2->type != PURPLE_XMLNODE_TYPE_TAG)
1174 continue;
1176 text2 = purple_xmlnode_get_data(child2);
1177 if(text2 && !strcmp(child2->name, "ORGNAME")) {
1178 purple_notify_user_info_add_pair_plaintext(user_info, _("Organization Name"), text2);
1179 } else if(text2 && !strcmp(child2->name, "ORGUNIT")) {
1180 purple_notify_user_info_add_pair_plaintext(user_info, _("Organization Unit"), text2);
1182 g_free(text2);
1184 } else if(text && !strcmp(child->name, "TITLE")) {
1185 purple_notify_user_info_add_pair_plaintext(user_info, _("Job Title"), text);
1186 } else if(text && !strcmp(child->name, "ROLE")) {
1187 purple_notify_user_info_add_pair_plaintext(user_info, _("Role"), text);
1188 } else if(text && !strcmp(child->name, "DESC")) {
1189 purple_notify_user_info_add_pair_plaintext(user_info, _("Description"), text);
1190 } else if(!strcmp(child->name, "PHOTO") ||
1191 !strcmp(child->name, "LOGO")) {
1192 char *bintext = NULL;
1193 PurpleXmlNode *binval;
1195 if ((binval = purple_xmlnode_get_child(child, "BINVAL")) &&
1196 (bintext = purple_xmlnode_get_data(binval))) {
1197 gsize size;
1198 guchar *data;
1199 gboolean photo = (strcmp(child->name, "PHOTO") == 0);
1201 data = purple_base64_decode(bintext, &size);
1202 if (data) {
1203 PurpleImage *img;
1204 guint img_id;
1205 char *img_text;
1206 char *hash;
1208 img = purple_image_new_from_data(g_memdup(data, size), size);
1209 img_id = purple_image_store_add(img);
1211 jbi->vcard_images = g_slist_prepend(jbi->vcard_images, img);
1212 img_text = g_strdup_printf("<img src='"
1213 PURPLE_IMAGE_STORE_PROTOCOL "%u'>", img_id);
1215 purple_notify_user_info_add_pair_html(user_info, (photo ? _("Photo") : _("Logo")), img_text);
1217 hash = jabber_calculate_data_hash(data, size, "sha1");
1218 purple_buddy_icons_set_for_user(account, bare_jid, data, size, hash);
1219 g_free(hash);
1220 g_free(img_text);
1222 g_free(bintext);
1225 g_free(text);
1229 if (serverside_alias) {
1230 PurpleBuddy *b;
1231 /* If we found a serverside alias, set it and tell the core */
1232 purple_serv_got_alias(js->gc, bare_jid, serverside_alias);
1233 b = purple_blist_find_buddy(account, bare_jid);
1234 if (b) {
1235 purple_blist_node_set_string((PurpleBlistNode*)b, "servernick", serverside_alias);
1238 g_free(serverside_alias);
1241 g_free(bare_jid);
1243 jabber_buddy_info_show_if_ready(jbi);
1246 static void jabber_buddy_info_resource_free(gpointer data)
1248 JabberBuddyInfoResource *jbri = data;
1249 g_free(jbri);
1252 static guint jbir_hash(gconstpointer v)
1254 if (v)
1255 return g_str_hash(v);
1256 else
1257 return 0;
1260 static gboolean jbir_equal(gconstpointer v1, gconstpointer v2)
1262 const gchar *resource_1 = v1;
1263 const gchar *resource_2 = v2;
1265 return purple_strequal(resource_1, resource_2);
1268 static void jabber_version_parse(JabberStream *js, const char *from,
1269 JabberIqType type, const char *id,
1270 PurpleXmlNode *packet, gpointer data)
1272 JabberBuddyInfo *jbi = data;
1273 PurpleXmlNode *query;
1274 char *resource_name;
1276 g_return_if_fail(jbi != NULL);
1278 jabber_buddy_info_remove_id(jbi, id);
1280 if(!from)
1281 return;
1283 resource_name = jabber_get_resource(from);
1285 if(resource_name) {
1286 if (type == JABBER_IQ_RESULT) {
1287 if((query = purple_xmlnode_get_child(packet, "query"))) {
1288 JabberBuddyResource *jbr = jabber_buddy_find_resource(jbi->jb, resource_name);
1289 if(jbr) {
1290 PurpleXmlNode *node;
1291 if((node = purple_xmlnode_get_child(query, "name"))) {
1292 jbr->client.name = purple_xmlnode_get_data(node);
1294 if((node = purple_xmlnode_get_child(query, "version"))) {
1295 jbr->client.version = purple_xmlnode_get_data(node);
1297 if((node = purple_xmlnode_get_child(query, "os"))) {
1298 jbr->client.os = purple_xmlnode_get_data(node);
1303 g_free(resource_name);
1306 jabber_buddy_info_show_if_ready(jbi);
1309 static void jabber_last_parse(JabberStream *js, const char *from,
1310 JabberIqType type, const char *id,
1311 PurpleXmlNode *packet, gpointer data)
1313 JabberBuddyInfo *jbi = data;
1314 PurpleXmlNode *query;
1315 char *resource_name;
1316 const char *seconds;
1318 g_return_if_fail(jbi != NULL);
1320 jabber_buddy_info_remove_id(jbi, id);
1322 if(!from)
1323 return;
1325 resource_name = jabber_get_resource(from);
1327 if(resource_name) {
1328 if (type == JABBER_IQ_RESULT) {
1329 if((query = purple_xmlnode_get_child(packet, "query"))) {
1330 seconds = purple_xmlnode_get_attrib(query, "seconds");
1331 if(seconds) {
1332 char *end = NULL;
1333 long sec = strtol(seconds, &end, 10);
1334 JabberBuddy *jb = NULL;
1335 char *resource = NULL;
1336 char *buddy_name = NULL;
1337 JabberBuddyResource *jbr = NULL;
1339 if(end != seconds) {
1340 JabberBuddyInfoResource *jbir = g_hash_table_lookup(jbi->resources, resource_name);
1341 if(jbir) {
1342 jbir->idle_seconds = sec;
1345 /* Update the idle time of the buddy resource, if we got it.
1346 This will correct the value when a server doesn't mark
1347 delayed presence and we got the presence when signing on */
1348 jb = jabber_buddy_find(js, from, FALSE);
1349 if (jb) {
1350 resource = jabber_get_resource(from);
1351 buddy_name = jabber_get_bare_jid(from);
1352 /* if the resource already has an idle time set, we
1353 must have gotten it originally from a presence. In
1354 this case we update it. Otherwise don't update it, to
1355 avoid setting an idle and not getting informed about
1356 the resource getting unidle */
1357 if (resource && buddy_name) {
1358 jbr = jabber_buddy_find_resource(jb, resource);
1359 if (jbr) {
1360 if (jbr->idle) {
1361 if (sec) {
1362 jbr->idle = time(NULL) - sec;
1363 } else {
1364 jbr->idle = 0;
1367 if (jbr ==
1368 jabber_buddy_find_resource(jb, NULL)) {
1369 purple_protocol_got_user_idle(purple_connection_get_account(js->gc),
1370 buddy_name, jbr->idle, jbr->idle);
1375 g_free(resource);
1376 g_free(buddy_name);
1381 g_free(resource_name);
1384 jabber_buddy_info_show_if_ready(jbi);
1387 static void jabber_last_offline_parse(JabberStream *js, const char *from,
1388 JabberIqType type, const char *id,
1389 PurpleXmlNode *packet, gpointer data)
1391 JabberBuddyInfo *jbi = data;
1392 PurpleXmlNode *query;
1393 const char *seconds;
1395 g_return_if_fail(jbi != NULL);
1397 jabber_buddy_info_remove_id(jbi, id);
1399 if (type == JABBER_IQ_RESULT) {
1400 if((query = purple_xmlnode_get_child(packet, "query"))) {
1401 seconds = purple_xmlnode_get_attrib(query, "seconds");
1402 if(seconds) {
1403 char *end = NULL;
1404 long sec = strtol(seconds, &end, 10);
1405 if(end != seconds) {
1406 jbi->last_seconds = sec;
1409 jbi->last_message = purple_xmlnode_get_data(query);
1413 jabber_buddy_info_show_if_ready(jbi);
1416 static void jabber_time_parse(JabberStream *js, const char *from,
1417 JabberIqType type, const char *id,
1418 PurpleXmlNode *packet, gpointer data)
1420 JabberBuddyInfo *jbi = data;
1421 JabberBuddyResource *jbr;
1422 char *resource_name;
1424 g_return_if_fail(jbi != NULL);
1426 jabber_buddy_info_remove_id(jbi, id);
1428 if (!from)
1429 return;
1431 resource_name = jabber_get_resource(from);
1432 jbr = resource_name ? jabber_buddy_find_resource(jbi->jb, resource_name) : NULL;
1433 g_free(resource_name);
1434 if (jbr) {
1435 if (type == JABBER_IQ_RESULT) {
1436 PurpleXmlNode *time = purple_xmlnode_get_child(packet, "time");
1437 PurpleXmlNode *tzo = time ? purple_xmlnode_get_child(time, "tzo") : NULL;
1438 char *tzo_data = tzo ? purple_xmlnode_get_data(tzo) : NULL;
1439 if (tzo_data) {
1440 char *c = tzo_data;
1441 int hours, minutes;
1442 if (tzo_data[0] == 'Z' && tzo_data[1] == '\0') {
1443 jbr->tz_off = 0;
1444 } else {
1445 gboolean offset_positive = (tzo_data[0] == '+');
1446 /* [+-]HH:MM */
1447 if (((*c == '+' || *c == '-') && (c = c + 1)) &&
1448 sscanf(c, "%02d:%02d", &hours, &minutes) == 2) {
1449 jbr->tz_off = 60*60*hours + 60*minutes;
1450 if (!offset_positive)
1451 jbr->tz_off *= -1;
1452 } else {
1453 purple_debug_info("jabber", "Ignoring malformed timezone %s",
1454 tzo_data);
1458 g_free(tzo_data);
1463 jabber_buddy_info_show_if_ready(jbi);
1466 void jabber_buddy_remove_all_pending_buddy_info_requests(JabberStream *js)
1468 if (js->pending_buddy_info_requests)
1470 JabberBuddyInfo *jbi;
1471 GSList *l = js->pending_buddy_info_requests;
1472 while (l) {
1473 jbi = l->data;
1475 g_slist_free(jbi->ids);
1476 jabber_buddy_info_destroy(jbi);
1478 l = l->next;
1481 g_slist_free(js->pending_buddy_info_requests);
1482 js->pending_buddy_info_requests = NULL;
1486 static gboolean jabber_buddy_get_info_timeout(gpointer data)
1488 JabberBuddyInfo *jbi = data;
1490 /* remove the pending callbacks */
1491 while(jbi->ids) {
1492 char *id = jbi->ids->data;
1493 jabber_iq_remove_callback_by_id(jbi->js, id);
1494 jbi->ids = g_slist_remove(jbi->ids, id);
1495 g_free(id);
1498 jbi->js->pending_buddy_info_requests = g_slist_remove(jbi->js->pending_buddy_info_requests, jbi);
1499 jbi->timeout_handle = 0;
1501 jabber_buddy_info_show_if_ready(jbi);
1503 return FALSE;
1506 static gboolean _client_is_blacklisted(JabberBuddyResource *jbr, const char *ns)
1508 /* can't be blacklisted if we don't know what you're running yet */
1509 if(!jbr->client.name)
1510 return FALSE;
1512 if(!strcmp(ns, NS_LAST_ACTIVITY)) {
1513 if(!strcmp(jbr->client.name, "Trillian")) {
1514 /* verified by nwalp 2007/05/09 */
1515 if(!strcmp(jbr->client.version, "3.1.0.121") ||
1516 /* verified by nwalp 2007/09/19 */
1517 !strcmp(jbr->client.version, "3.1.7.0")) {
1518 return TRUE;
1523 return FALSE;
1526 static void
1527 dispatch_queries_for_resource(JabberStream *js, JabberBuddyInfo *jbi,
1528 gboolean is_bare_jid, const char *jid,
1529 JabberBuddyResource *jbr)
1531 JabberIq *iq;
1532 JabberBuddyInfoResource *jbir;
1533 char *full_jid = NULL;
1534 const char *to;
1536 if (is_bare_jid && jbr->name) {
1537 full_jid = g_strdup_printf("%s/%s", jid, jbr->name);
1538 to = full_jid;
1539 } else
1540 to = jid;
1542 jbir = g_new0(JabberBuddyInfoResource, 1);
1543 g_hash_table_insert(jbi->resources, g_strdup(jbr->name), jbir);
1545 if(!jbr->client.name) {
1546 iq = jabber_iq_new_query(js, JABBER_IQ_GET, "jabber:iq:version");
1547 purple_xmlnode_set_attrib(iq->node, "to", to);
1548 jabber_iq_set_callback(iq, jabber_version_parse, jbi);
1549 jbi->ids = g_slist_prepend(jbi->ids, g_strdup(iq->id));
1550 jabber_iq_send(iq);
1553 /* this is to fix the feeling of irritation I get when trying
1554 * to get info on a friend running Trillian, which doesn't
1555 * respond (with an error or otherwise) to jabber:iq:last
1556 * requests. There are a number of Trillian users in my
1557 * office. */
1558 if(!_client_is_blacklisted(jbr, NS_LAST_ACTIVITY)) {
1559 iq = jabber_iq_new_query(js, JABBER_IQ_GET, NS_LAST_ACTIVITY);
1560 purple_xmlnode_set_attrib(iq->node, "to", to);
1561 jabber_iq_set_callback(iq, jabber_last_parse, jbi);
1562 jbi->ids = g_slist_prepend(jbi->ids, g_strdup(iq->id));
1563 jabber_iq_send(iq);
1566 if (jbr->tz_off == PURPLE_NO_TZ_OFF &&
1567 (!jbr->caps.info ||
1568 jabber_resource_has_capability(jbr, NS_ENTITY_TIME))) {
1569 PurpleXmlNode *child;
1570 iq = jabber_iq_new(js, JABBER_IQ_GET);
1571 purple_xmlnode_set_attrib(iq->node, "to", to);
1572 child = purple_xmlnode_new_child(iq->node, "time");
1573 purple_xmlnode_set_namespace(child, NS_ENTITY_TIME);
1574 jabber_iq_set_callback(iq, jabber_time_parse, jbi);
1575 jbi->ids = g_slist_prepend(jbi->ids, g_strdup(iq->id));
1576 jabber_iq_send(iq);
1579 g_free(full_jid);
1582 static void jabber_buddy_get_info_for_jid(JabberStream *js, const char *jid)
1584 JabberIq *iq;
1585 PurpleXmlNode *vcard;
1586 GList *resources;
1587 JabberBuddy *jb;
1588 JabberBuddyInfo *jbi;
1589 const char *slash;
1590 gboolean is_bare_jid;
1592 jb = jabber_buddy_find(js, jid, TRUE);
1594 /* invalid JID */
1595 if(!jb)
1596 return;
1598 slash = strchr(jid, '/');
1599 is_bare_jid = (slash == NULL);
1601 jbi = g_new0(JabberBuddyInfo, 1);
1602 jbi->jid = g_strdup(jid);
1603 jbi->js = js;
1604 jbi->jb = jb;
1605 jbi->resources = g_hash_table_new_full(jbir_hash, jbir_equal, g_free, jabber_buddy_info_resource_free);
1606 jbi->user_info = purple_notify_user_info_new();
1608 iq = jabber_iq_new(js, JABBER_IQ_GET);
1610 purple_xmlnode_set_attrib(iq->node, "to", jid);
1611 vcard = purple_xmlnode_new_child(iq->node, "vCard");
1612 purple_xmlnode_set_namespace(vcard, "vcard-temp");
1614 jabber_iq_set_callback(iq, jabber_vcard_parse, jbi);
1615 jbi->ids = g_slist_prepend(jbi->ids, g_strdup(iq->id));
1617 jabber_iq_send(iq);
1619 if (is_bare_jid) {
1620 if (jb->resources) {
1621 for(resources = jb->resources; resources; resources = resources->next) {
1622 JabberBuddyResource *jbr = resources->data;
1623 dispatch_queries_for_resource(js, jbi, is_bare_jid, jid, jbr);
1625 } else {
1626 /* user is offline, send a jabber:iq:last to find out last time online */
1627 iq = jabber_iq_new_query(js, JABBER_IQ_GET, NS_LAST_ACTIVITY);
1628 purple_xmlnode_set_attrib(iq->node, "to", jid);
1629 jabber_iq_set_callback(iq, jabber_last_offline_parse, jbi);
1630 jbi->ids = g_slist_prepend(jbi->ids, g_strdup(iq->id));
1631 jabber_iq_send(iq);
1633 } else {
1634 JabberBuddyResource *jbr = jabber_buddy_find_resource(jb, slash + 1);
1635 if (jbr)
1636 dispatch_queries_for_resource(js, jbi, is_bare_jid, jid, jbr);
1637 else
1638 purple_debug_warning("jabber", "jabber_buddy_get_info_for_jid() "
1639 "was passed JID %s, but there is no corresponding "
1640 "JabberBuddyResource!\n", jid);
1643 js->pending_buddy_info_requests = g_slist_prepend(js->pending_buddy_info_requests, jbi);
1644 jbi->timeout_handle = purple_timeout_add_seconds(30, jabber_buddy_get_info_timeout, jbi);
1647 void jabber_buddy_get_info(PurpleConnection *gc, const char *who)
1649 JabberStream *js = purple_connection_get_protocol_data(gc);
1650 JabberID *jid = jabber_id_new(who);
1652 if (!jid)
1653 return;
1655 if (jid->node && jabber_chat_find(js, jid->node, jid->domain)) {
1656 /* For a conversation, include the resource (indicates the user). */
1657 jabber_buddy_get_info_for_jid(js, who);
1658 } else {
1659 char *bare_jid = jabber_get_bare_jid(who);
1660 jabber_buddy_get_info_for_jid(js, bare_jid);
1661 g_free(bare_jid);
1664 jabber_id_free(jid);
1667 static void jabber_buddy_set_invisibility(JabberStream *js, const char *who,
1668 gboolean invisible)
1670 PurplePresence *gpresence;
1671 PurpleAccount *account;
1672 PurpleStatus *status;
1673 JabberBuddy *jb = jabber_buddy_find(js, who, TRUE);
1674 PurpleXmlNode *presence;
1675 JabberBuddyState state;
1676 char *msg;
1677 int priority;
1679 account = purple_connection_get_account(js->gc);
1680 gpresence = purple_account_get_presence(account);
1681 status = purple_presence_get_active_status(gpresence);
1683 purple_status_to_jabber(status, &state, &msg, &priority);
1684 presence = jabber_presence_create_js(js, state, msg, priority);
1686 g_free(msg);
1688 purple_xmlnode_set_attrib(presence, "to", who);
1689 if(invisible) {
1690 purple_xmlnode_set_attrib(presence, "type", "invisible");
1691 jb->invisible |= JABBER_INVIS_BUDDY;
1692 } else {
1693 jb->invisible &= ~JABBER_INVIS_BUDDY;
1696 jabber_send(js, presence);
1697 purple_xmlnode_free(presence);
1700 static void jabber_buddy_make_invisible(PurpleBlistNode *node, gpointer data)
1702 PurpleBuddy *buddy;
1703 PurpleConnection *gc;
1704 JabberStream *js;
1706 g_return_if_fail(PURPLE_IS_BUDDY(node));
1708 buddy = (PurpleBuddy *) node;
1709 gc = purple_account_get_connection(purple_buddy_get_account(buddy));
1710 js = purple_connection_get_protocol_data(gc);
1712 jabber_buddy_set_invisibility(js, purple_buddy_get_name(buddy), TRUE);
1715 static void jabber_buddy_make_visible(PurpleBlistNode *node, gpointer data)
1717 PurpleBuddy *buddy;
1718 PurpleConnection *gc;
1719 JabberStream *js;
1721 g_return_if_fail(PURPLE_IS_BUDDY(node));
1723 buddy = (PurpleBuddy *) node;
1724 gc = purple_account_get_connection(purple_buddy_get_account(buddy));
1725 js = purple_connection_get_protocol_data(gc);
1727 jabber_buddy_set_invisibility(js, purple_buddy_get_name(buddy), FALSE);
1730 static void cancel_presence_notification(gpointer data)
1732 PurpleBuddy *buddy;
1733 PurpleConnection *gc;
1734 JabberStream *js;
1736 buddy = data;
1737 gc = purple_account_get_connection(purple_buddy_get_account(buddy));
1738 js = purple_connection_get_protocol_data(gc);
1740 jabber_presence_subscription_set(js, purple_buddy_get_name(buddy), "unsubscribed");
1743 static void
1744 jabber_buddy_cancel_presence_notification(PurpleBlistNode *node,
1745 gpointer data)
1747 PurpleBuddy *buddy;
1748 PurpleAccount *account;
1749 PurpleConnection *gc;
1750 const gchar *name;
1751 char *msg;
1753 g_return_if_fail(PURPLE_IS_BUDDY(node));
1755 buddy = (PurpleBuddy *) node;
1756 name = purple_buddy_get_name(buddy);
1757 account = purple_buddy_get_account(buddy);
1758 gc = purple_account_get_connection(account);
1760 msg = g_strdup_printf(_("%s will no longer be able to see your status "
1761 "updates. Do you want to continue?"), name);
1762 purple_request_yes_no(gc, NULL, _("Cancel Presence Notification"),
1763 msg, 0 /* Yes */, purple_request_cpar_from_account(account), buddy,
1764 cancel_presence_notification, NULL /* Do nothing */);
1765 g_free(msg);
1768 static void jabber_buddy_rerequest_auth(PurpleBlistNode *node, gpointer data)
1770 PurpleBuddy *buddy;
1771 PurpleConnection *gc;
1772 JabberStream *js;
1774 g_return_if_fail(PURPLE_IS_BUDDY(node));
1776 buddy = (PurpleBuddy *) node;
1777 gc = purple_account_get_connection(purple_buddy_get_account(buddy));
1778 js = purple_connection_get_protocol_data(gc);
1780 jabber_presence_subscription_set(js, purple_buddy_get_name(buddy), "subscribe");
1784 static void jabber_buddy_unsubscribe(PurpleBlistNode *node, gpointer data)
1786 PurpleBuddy *buddy;
1787 PurpleConnection *gc;
1788 JabberStream *js;
1790 g_return_if_fail(PURPLE_IS_BUDDY(node));
1792 buddy = (PurpleBuddy *) node;
1793 gc = purple_account_get_connection(purple_buddy_get_account(buddy));
1794 js = purple_connection_get_protocol_data(gc);
1796 jabber_presence_subscription_set(js, purple_buddy_get_name(buddy), "unsubscribe");
1799 static void jabber_buddy_login(PurpleBlistNode *node, gpointer data) {
1800 if(PURPLE_IS_BUDDY(node)) {
1801 /* simply create a directed presence of the current status */
1802 PurpleBuddy *buddy = (PurpleBuddy *) node;
1803 PurpleConnection *gc = purple_account_get_connection(purple_buddy_get_account(buddy));
1804 JabberStream *js = purple_connection_get_protocol_data(gc);
1805 PurpleAccount *account = purple_connection_get_account(gc);
1806 PurplePresence *gpresence = purple_account_get_presence(account);
1807 PurpleStatus *status = purple_presence_get_active_status(gpresence);
1808 PurpleXmlNode *presence;
1809 JabberBuddyState state;
1810 char *msg;
1811 int priority;
1813 purple_status_to_jabber(status, &state, &msg, &priority);
1814 presence = jabber_presence_create_js(js, state, msg, priority);
1816 g_free(msg);
1818 purple_xmlnode_set_attrib(presence, "to", purple_buddy_get_name(buddy));
1820 jabber_send(js, presence);
1821 purple_xmlnode_free(presence);
1825 static void jabber_buddy_logout(PurpleBlistNode *node, gpointer data) {
1826 if(PURPLE_IS_BUDDY(node)) {
1827 /* simply create a directed unavailable presence */
1828 PurpleBuddy *buddy = (PurpleBuddy *) node;
1829 PurpleConnection *gc = purple_account_get_connection(purple_buddy_get_account(buddy));
1830 JabberStream *js = purple_connection_get_protocol_data(gc);
1831 PurpleXmlNode *presence;
1833 presence = jabber_presence_create_js(js, JABBER_BUDDY_STATE_UNAVAILABLE, NULL, 0);
1835 purple_xmlnode_set_attrib(presence, "to", purple_buddy_get_name(buddy));
1837 jabber_send(js, presence);
1838 purple_xmlnode_free(presence);
1842 static GList *jabber_buddy_menu(PurpleBuddy *buddy)
1844 PurpleConnection *gc = purple_account_get_connection(purple_buddy_get_account(buddy));
1845 JabberStream *js = purple_connection_get_protocol_data(gc);
1846 const char *name = purple_buddy_get_name(buddy);
1847 JabberBuddy *jb = jabber_buddy_find(js, name, TRUE);
1848 GList *jbrs;
1850 GList *m = NULL;
1851 PurpleMenuAction *act;
1853 if(!jb)
1854 return m;
1856 if (js->protocol_version.major == 0 && js->protocol_version.minor == 9 &&
1857 jb != js->user_jb) {
1858 if(jb->invisible & JABBER_INVIS_BUDDY) {
1859 act = purple_menu_action_new(_("Un-hide From"),
1860 PURPLE_CALLBACK(jabber_buddy_make_visible),
1861 NULL, NULL);
1862 } else {
1863 act = purple_menu_action_new(_("Temporarily Hide From"),
1864 PURPLE_CALLBACK(jabber_buddy_make_invisible),
1865 NULL, NULL);
1867 m = g_list_append(m, act);
1870 if(jb->subscription & JABBER_SUB_FROM && jb != js->user_jb) {
1871 act = purple_menu_action_new(_("Cancel Presence Notification"),
1872 PURPLE_CALLBACK(jabber_buddy_cancel_presence_notification),
1873 NULL, NULL);
1874 m = g_list_append(m, act);
1877 if(!(jb->subscription & JABBER_SUB_TO)) {
1878 act = purple_menu_action_new(_("(Re-)Request authorization"),
1879 PURPLE_CALLBACK(jabber_buddy_rerequest_auth),
1880 NULL, NULL);
1881 m = g_list_append(m, act);
1883 } else if (jb != js->user_jb) {
1885 /* shouldn't this just happen automatically when the buddy is
1886 removed? */
1887 act = purple_menu_action_new(_("Unsubscribe"),
1888 PURPLE_CALLBACK(jabber_buddy_unsubscribe),
1889 NULL, NULL);
1890 m = g_list_append(m, act);
1893 if (js->googletalk) {
1894 act = purple_menu_action_new(_("Initiate _Chat"),
1895 PURPLE_CALLBACK(google_buddy_node_chat),
1896 NULL, NULL);
1897 m = g_list_append(m, act);
1901 * This if-condition implements parts of XEP-0100: Gateway Interaction
1903 * According to stpeter, there is no way to know if a jid on the roster is a gateway without sending a disco#info.
1904 * However, since the gateway might appear offline to us, we cannot get that information. Therefore, I just assume
1905 * that gateways on the roster can be identified by having no '@' in their jid. This is a faily safe assumption, since
1906 * people don't tend to have a server or other service there.
1908 * TODO: Use disco#info...
1910 if (strchr(name, '@') == NULL) {
1911 act = purple_menu_action_new(_("Log In"),
1912 PURPLE_CALLBACK(jabber_buddy_login),
1913 NULL, NULL);
1914 m = g_list_append(m, act);
1915 act = purple_menu_action_new(_("Log Out"),
1916 PURPLE_CALLBACK(jabber_buddy_logout),
1917 NULL, NULL);
1918 m = g_list_append(m, act);
1921 /* add all ad hoc commands to the action menu */
1922 for(jbrs = jb->resources; jbrs; jbrs = g_list_next(jbrs)) {
1923 JabberBuddyResource *jbr = jbrs->data;
1924 GList *commands;
1925 if (!jbr->commands)
1926 continue;
1927 for(commands = jbr->commands; commands; commands = g_list_next(commands)) {
1928 JabberAdHocCommands *cmd = commands->data;
1929 act = purple_menu_action_new(cmd->name, PURPLE_CALLBACK(jabber_adhoc_execute_action), cmd, NULL);
1930 m = g_list_append(m, act);
1934 return m;
1937 GList *
1938 jabber_blist_node_menu(PurpleBlistNode *node)
1940 if(PURPLE_IS_BUDDY(node)) {
1941 return jabber_buddy_menu((PurpleBuddy *) node);
1942 } else {
1943 return NULL;
1948 static void user_search_result_add_buddy_cb(PurpleConnection *gc, GList *row, void *user_data)
1950 /* XXX find out the jid */
1951 purple_blist_request_add_buddy(purple_connection_get_account(gc),
1952 g_list_nth_data(row, 0), NULL, NULL);
1955 static void user_search_result_cb(JabberStream *js, const char *from,
1956 JabberIqType type, const char *id,
1957 PurpleXmlNode *packet, gpointer data)
1959 PurpleNotifySearchResults *results;
1960 PurpleNotifySearchColumn *column;
1961 PurpleXmlNode *x, *query, *item, *field;
1963 /* XXX error checking? */
1964 if(!(query = purple_xmlnode_get_child(packet, "query")))
1965 return;
1967 results = purple_notify_searchresults_new();
1968 if((x = purple_xmlnode_get_child_with_namespace(query, "x", "jabber:x:data"))) {
1969 PurpleXmlNode *reported;
1970 GSList *column_vars = NULL;
1972 purple_debug_info("jabber", "new-skool\n");
1974 if((reported = purple_xmlnode_get_child(x, "reported"))) {
1975 PurpleXmlNode *field = purple_xmlnode_get_child(reported, "field");
1976 while(field) {
1977 const char *var = purple_xmlnode_get_attrib(field, "var");
1978 const char *label = purple_xmlnode_get_attrib(field, "label");
1979 if(var) {
1980 column = purple_notify_searchresults_column_new(label ? label : var);
1981 purple_notify_searchresults_column_add(results, column);
1982 column_vars = g_slist_append(column_vars, (char *)var);
1984 field = purple_xmlnode_get_next_twin(field);
1988 item = purple_xmlnode_get_child(x, "item");
1989 while(item) {
1990 GList *row = NULL;
1991 GSList *l;
1992 PurpleXmlNode *valuenode;
1993 const char *var;
1995 for (l = column_vars; l != NULL; l = l->next) {
1997 * Build a row containing the strings that correspond
1998 * to each column of the search results.
2000 for (field = purple_xmlnode_get_child(item, "field");
2001 field != NULL;
2002 field = purple_xmlnode_get_next_twin(field))
2004 if ((var = purple_xmlnode_get_attrib(field, "var")) &&
2005 !strcmp(var, l->data) &&
2006 (valuenode = purple_xmlnode_get_child(field, "value")))
2008 char *value = purple_xmlnode_get_data(valuenode);
2009 row = g_list_append(row, value);
2010 break;
2013 if (field == NULL)
2014 /* No data for this column */
2015 row = g_list_append(row, NULL);
2017 purple_notify_searchresults_row_add(results, row);
2018 item = purple_xmlnode_get_next_twin(item);
2021 g_slist_free(column_vars);
2022 } else {
2023 /* old skool */
2024 purple_debug_info("jabber", "old-skool\n");
2026 column = purple_notify_searchresults_column_new(_("JID"));
2027 purple_notify_searchresults_column_add(results, column);
2028 column = purple_notify_searchresults_column_new(_("First Name"));
2029 purple_notify_searchresults_column_add(results, column);
2030 column = purple_notify_searchresults_column_new(_("Last Name"));
2031 purple_notify_searchresults_column_add(results, column);
2032 column = purple_notify_searchresults_column_new(_("Nickname"));
2033 purple_notify_searchresults_column_add(results, column);
2034 column = purple_notify_searchresults_column_new(_("Email"));
2035 purple_notify_searchresults_column_add(results, column);
2037 for(item = purple_xmlnode_get_child(query, "item"); item; item = purple_xmlnode_get_next_twin(item)) {
2038 const char *jid;
2039 PurpleXmlNode *node;
2040 GList *row = NULL;
2042 if(!(jid = purple_xmlnode_get_attrib(item, "jid")))
2043 continue;
2045 row = g_list_append(row, g_strdup(jid));
2046 node = purple_xmlnode_get_child(item, "first");
2047 row = g_list_append(row, node ? purple_xmlnode_get_data(node) : NULL);
2048 node = purple_xmlnode_get_child(item, "last");
2049 row = g_list_append(row, node ? purple_xmlnode_get_data(node) : NULL);
2050 node = purple_xmlnode_get_child(item, "nick");
2051 row = g_list_append(row, node ? purple_xmlnode_get_data(node) : NULL);
2052 node = purple_xmlnode_get_child(item, "email");
2053 row = g_list_append(row, node ? purple_xmlnode_get_data(node) : NULL);
2054 purple_debug_info("jabber", "row=%p\n", row);
2055 purple_notify_searchresults_row_add(results, row);
2059 purple_notify_searchresults_button_add(results, PURPLE_NOTIFY_BUTTON_ADD,
2060 user_search_result_add_buddy_cb);
2062 purple_notify_searchresults(js->gc, NULL, NULL, _("The following are the results of your search"), results, NULL, NULL);
2065 static void user_search_x_data_cb(JabberStream *js, PurpleXmlNode *result, gpointer data)
2067 PurpleXmlNode *query;
2068 JabberIq *iq;
2069 char *dir_server = data;
2070 const char *type;
2072 /* if they've cancelled the search, we're
2073 * just going to get an error if we send
2074 * a cancel, so skip it */
2075 type = purple_xmlnode_get_attrib(result, "type");
2076 if(type && !strcmp(type, "cancel")) {
2077 g_free(dir_server);
2078 return;
2081 iq = jabber_iq_new_query(js, JABBER_IQ_SET, "jabber:iq:search");
2082 query = purple_xmlnode_get_child(iq->node, "query");
2084 purple_xmlnode_insert_child(query, result);
2086 jabber_iq_set_callback(iq, user_search_result_cb, NULL);
2087 purple_xmlnode_set_attrib(iq->node, "to", dir_server);
2088 jabber_iq_send(iq);
2089 g_free(dir_server);
2092 struct user_search_info {
2093 JabberStream *js;
2094 char *directory_server;
2097 static void user_search_cancel_cb(struct user_search_info *usi, PurpleRequestFields *fields)
2099 g_free(usi->directory_server);
2100 g_free(usi);
2103 static void user_search_cb(struct user_search_info *usi, PurpleRequestFields *fields)
2105 JabberStream *js = usi->js;
2106 JabberIq *iq;
2107 PurpleXmlNode *query;
2108 GList *groups, *flds;
2110 iq = jabber_iq_new_query(js, JABBER_IQ_SET, "jabber:iq:search");
2111 query = purple_xmlnode_get_child(iq->node, "query");
2113 for(groups = purple_request_fields_get_groups(fields); groups; groups = groups->next) {
2114 for(flds = purple_request_field_group_get_fields(groups->data);
2115 flds; flds = flds->next) {
2116 PurpleRequestField *field = flds->data;
2117 const char *id = purple_request_field_get_id(field);
2118 const char *value = purple_request_field_string_get_value(field);
2120 if(value && (!strcmp(id, "first") || !strcmp(id, "last") || !strcmp(id, "nick") || !strcmp(id, "email"))) {
2121 PurpleXmlNode *y = purple_xmlnode_new_child(query, id);
2122 purple_xmlnode_insert_data(y, value, -1);
2127 jabber_iq_set_callback(iq, user_search_result_cb, NULL);
2128 purple_xmlnode_set_attrib(iq->node, "to", usi->directory_server);
2129 jabber_iq_send(iq);
2131 g_free(usi->directory_server);
2132 g_free(usi);
2135 #if 0
2136 /* This is for gettext only -- it will see this even though there's an #if 0. */
2139 * An incomplete list of server generated original language search
2140 * comments for Jabber User Directories
2142 * See discussion thread "Search comment for Jabber is not translatable"
2143 * in purple-i18n@lists.sourceforge.net (March 2006)
2145 static const char * jabber_user_dir_comments [] = {
2146 /* current comment from Jabber User Directory users.jabber.org */
2147 N_("Find a contact by entering the search criteria in the given fields. "
2148 "Note: Each field supports wild card searches (%)"),
2149 NULL
2151 #endif
2153 static void user_search_fields_result_cb(JabberStream *js, const char *from,
2154 JabberIqType type, const char *id,
2155 PurpleXmlNode *packet, gpointer data)
2157 PurpleXmlNode *query, *x;
2159 if (!from)
2160 return;
2162 if (type == JABBER_IQ_ERROR) {
2163 char *msg = jabber_parse_error(js, packet, NULL);
2165 if(!msg)
2166 msg = g_strdup(_("Unknown error"));
2168 purple_notify_error(js->gc, _("Directory Query Failed"),
2169 _("Could not query the directory server."), msg,
2170 purple_request_cpar_from_connection(js->gc));
2171 g_free(msg);
2173 return;
2177 if(!(query = purple_xmlnode_get_child(packet, "query")))
2178 return;
2180 if((x = purple_xmlnode_get_child_with_namespace(query, "x", "jabber:x:data"))) {
2181 jabber_x_data_request(js, x, user_search_x_data_cb, g_strdup(from));
2182 return;
2183 } else {
2184 struct user_search_info *usi;
2185 PurpleXmlNode *instnode;
2186 char *instructions = NULL;
2187 PurpleRequestFields *fields;
2188 PurpleRequestFieldGroup *group;
2189 PurpleRequestField *field;
2191 /* old skool */
2192 fields = purple_request_fields_new();
2193 group = purple_request_field_group_new(NULL);
2194 purple_request_fields_add_group(fields, group);
2196 if((instnode = purple_xmlnode_get_child(query, "instructions")))
2198 char *tmp = purple_xmlnode_get_data(instnode);
2200 if(tmp)
2202 /* Try to translate the message (see static message
2203 list in jabber_user_dir_comments[]) */
2204 instructions = g_strdup_printf(_("Server Instructions: %s"), _(tmp));
2205 g_free(tmp);
2209 if(!instructions)
2211 instructions = g_strdup(_("Fill in one or more fields to search "
2212 "for any matching XMPP users."));
2215 if(purple_xmlnode_get_child(query, "first")) {
2216 field = purple_request_field_string_new("first", _("First Name"),
2217 NULL, FALSE);
2218 purple_request_field_group_add_field(group, field);
2220 if(purple_xmlnode_get_child(query, "last")) {
2221 field = purple_request_field_string_new("last", _("Last Name"),
2222 NULL, FALSE);
2223 purple_request_field_group_add_field(group, field);
2225 if(purple_xmlnode_get_child(query, "nick")) {
2226 field = purple_request_field_string_new("nick", _("Nickname"),
2227 NULL, FALSE);
2228 purple_request_field_group_add_field(group, field);
2230 if(purple_xmlnode_get_child(query, "email")) {
2231 field = purple_request_field_string_new("email", _("Email Address"),
2232 NULL, FALSE);
2233 purple_request_field_group_add_field(group, field);
2236 usi = g_new0(struct user_search_info, 1);
2237 usi->js = js;
2238 usi->directory_server = g_strdup(from);
2240 purple_request_fields(js->gc, _("Search for XMPP users"),
2241 _("Search for XMPP users"), instructions, fields,
2242 _("Search"), G_CALLBACK(user_search_cb),
2243 _("Cancel"), G_CALLBACK(user_search_cancel_cb),
2244 purple_request_cpar_from_connection(js->gc),
2245 usi);
2247 g_free(instructions);
2251 void jabber_user_search(JabberStream *js, const char *directory)
2253 JabberIq *iq;
2255 /* XXX: should probably better validate the directory we're given */
2256 if(!directory || !*directory) {
2257 purple_notify_error(js->gc, _("Invalid Directory"),
2258 _("Invalid Directory"), NULL,
2259 purple_request_cpar_from_connection(js->gc));
2260 return;
2263 /* If the value provided isn't the disco#info default, persist it. Otherwise,
2264 make sure we aren't persisting an old value */
2265 if(js->user_directories && js->user_directories->data &&
2266 !strcmp(directory, js->user_directories->data)) {
2267 purple_account_set_string(purple_connection_get_account(js->gc), "user_directory", "");
2269 else {
2270 purple_account_set_string(purple_connection_get_account(js->gc), "user_directory", directory);
2273 iq = jabber_iq_new_query(js, JABBER_IQ_GET, "jabber:iq:search");
2274 purple_xmlnode_set_attrib(iq->node, "to", directory);
2276 jabber_iq_set_callback(iq, user_search_fields_result_cb, NULL);
2278 jabber_iq_send(iq);
2281 void jabber_user_search_begin(PurpleProtocolAction *action)
2283 PurpleConnection *gc = (PurpleConnection *) action->connection;
2284 JabberStream *js = purple_connection_get_protocol_data(gc);
2285 const char *def_val = purple_account_get_string(purple_connection_get_account(js->gc), "user_directory", "");
2286 if(!*def_val && js->user_directories)
2287 def_val = js->user_directories->data;
2289 purple_request_input(gc, _("Enter a User Directory"), _("Enter a User Directory"),
2290 _("Select a user directory to search"),
2291 def_val,
2292 FALSE, FALSE, NULL,
2293 _("Search Directory"), PURPLE_CALLBACK(jabber_user_search),
2294 _("Cancel"), NULL,
2295 NULL, js);
2298 gboolean
2299 jabber_resource_know_capabilities(const JabberBuddyResource *jbr)
2301 return jbr->caps.info != NULL;
2304 gboolean
2305 jabber_resource_has_capability(const JabberBuddyResource *jbr, const gchar *cap)
2307 const GList *node = NULL;
2308 const JabberCapsNodeExts *exts;
2310 if (!jbr->caps.info) {
2311 purple_debug_info("jabber",
2312 "Unable to find caps: nothing known about buddy\n");
2313 return FALSE;
2316 node = g_list_find_custom(jbr->caps.info->features, cap, (GCompareFunc)strcmp);
2317 if (!node && jbr->caps.exts && jbr->caps.info->exts) {
2318 const GList *ext;
2319 exts = jbr->caps.info->exts;
2320 /* Walk through all the enabled caps, checking each list for the cap.
2321 * Don't check it twice, though. */
2322 for (ext = jbr->caps.exts; ext && !node; ext = ext->next) {
2323 GList *features = g_hash_table_lookup(exts->exts, ext->data);
2324 if (features)
2325 node = g_list_find_custom(features, cap, (GCompareFunc)strcmp);
2329 return (node != NULL);
2332 gboolean
2333 jabber_buddy_has_capability(const JabberBuddy *jb, const gchar *cap)
2335 JabberBuddyResource *jbr = jabber_buddy_find_resource((JabberBuddy*)jb, NULL);
2337 if (!jbr) {
2338 purple_debug_info("jabber",
2339 "Unable to find caps: buddy might be offline\n");
2340 return FALSE;
2343 return jabber_resource_has_capability(jbr, cap);
2346 const gchar *
2347 jabber_resource_get_identity_category_type(const JabberBuddyResource *jbr,
2348 const gchar *category)
2350 const GList *iter = NULL;
2352 if (jbr->caps.info) {
2353 for (iter = jbr->caps.info->identities ; iter ; iter = g_list_next(iter)) {
2354 const JabberIdentity *identity =
2355 (JabberIdentity *) iter->data;
2357 if (strcmp(identity->category, category) == 0) {
2358 return identity->type;
2363 return NULL;