rename accountopt.[ch] to purpleaccountoption.[ch]
[pidgin-git.git] / libpurple / protocols / jabber / buddy.c
blob8252587f9a53c19cf921aaeaee19fc468db43b59
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 "action.h"
33 #include "buddy.h"
34 #include "chat.h"
35 #include "jabber.h"
36 #include "iq.h"
37 #include "presence.h"
38 #include "useravatar.h"
39 #include "xdata.h"
40 #include "pep.h"
41 #include "adhoccommands.h"
42 #include "google/google.h"
44 typedef struct {
45 long idle_seconds;
46 } JabberBuddyInfoResource;
48 typedef struct {
49 JabberStream *js;
50 JabberBuddy *jb;
51 char *jid;
52 GSList *ids;
53 GHashTable *resources;
54 guint timeout_handle;
55 GSList *vcard_images;
56 PurpleNotifyUserInfo *user_info;
57 long last_seconds;
58 gchar *last_message;
59 } JabberBuddyInfo;
61 static void
62 jabber_buddy_resource_free(JabberBuddyResource *jbr)
64 g_return_if_fail(jbr != NULL);
66 jbr->jb->resources = g_list_remove(jbr->jb->resources, jbr);
68 while(jbr->commands) {
69 JabberAdHocCommands *cmd = jbr->commands->data;
70 g_free(cmd->jid);
71 g_free(cmd->node);
72 g_free(cmd->name);
73 g_free(cmd);
74 jbr->commands = g_list_delete_link(jbr->commands, jbr->commands);
77 while (jbr->caps.exts) {
78 g_free(jbr->caps.exts->data);
79 jbr->caps.exts = g_list_delete_link(jbr->caps.exts, jbr->caps.exts);
82 g_free(jbr->name);
83 g_free(jbr->status);
84 g_free(jbr->thread_id);
85 g_free(jbr->client.name);
86 g_free(jbr->client.version);
87 g_free(jbr->client.os);
88 g_free(jbr);
91 void jabber_buddy_free(JabberBuddy *jb)
93 g_return_if_fail(jb != NULL);
95 g_free(jb->error_msg);
96 while(jb->resources)
97 jabber_buddy_resource_free(jb->resources->data);
99 g_free(jb);
102 JabberBuddy *jabber_buddy_find(JabberStream *js, const char *name,
103 gboolean create)
105 JabberBuddy *jb;
106 char *realname;
108 if (js->buddies == NULL)
109 return NULL;
111 if(!(realname = jabber_get_bare_jid(name)))
112 return NULL;
114 jb = g_hash_table_lookup(js->buddies, realname);
116 if(!jb && create) {
117 jb = g_new0(JabberBuddy, 1);
118 g_hash_table_insert(js->buddies, realname, jb);
119 } else
120 g_free(realname);
122 return jb;
125 /* Returns -1 if a is a higher priority resource than b, or is
126 * "more available" than b. 0 if they're the same, and 1 if b is
127 * higher priority/more available than a.
129 static gint resource_compare_cb(gconstpointer a, gconstpointer b)
131 const JabberBuddyResource *jbra = a;
132 const JabberBuddyResource *jbrb = b;
133 JabberBuddyState state_a, state_b;
135 if (jbra->priority != jbrb->priority)
136 return jbra->priority > jbrb->priority ? -1 : 1;
138 /* Fold the states for easier comparison */
139 /* TODO: Differentiate online/chat and away/dnd? */
140 switch (jbra->state) {
141 case JABBER_BUDDY_STATE_ONLINE:
142 case JABBER_BUDDY_STATE_CHAT:
143 state_a = JABBER_BUDDY_STATE_ONLINE;
144 break;
145 case JABBER_BUDDY_STATE_AWAY:
146 case JABBER_BUDDY_STATE_DND:
147 state_a = JABBER_BUDDY_STATE_AWAY;
148 break;
149 case JABBER_BUDDY_STATE_XA:
150 state_a = JABBER_BUDDY_STATE_XA;
151 break;
152 case JABBER_BUDDY_STATE_UNAVAILABLE:
153 state_a = JABBER_BUDDY_STATE_UNAVAILABLE;
154 break;
155 default:
156 state_a = JABBER_BUDDY_STATE_UNKNOWN;
157 break;
160 switch (jbrb->state) {
161 case JABBER_BUDDY_STATE_ONLINE:
162 case JABBER_BUDDY_STATE_CHAT:
163 state_b = JABBER_BUDDY_STATE_ONLINE;
164 break;
165 case JABBER_BUDDY_STATE_AWAY:
166 case JABBER_BUDDY_STATE_DND:
167 state_b = JABBER_BUDDY_STATE_AWAY;
168 break;
169 case JABBER_BUDDY_STATE_XA:
170 state_b = JABBER_BUDDY_STATE_XA;
171 break;
172 case JABBER_BUDDY_STATE_UNAVAILABLE:
173 state_b = JABBER_BUDDY_STATE_UNAVAILABLE;
174 break;
175 default:
176 state_b = JABBER_BUDDY_STATE_UNKNOWN;
177 break;
180 if (state_a == state_b) {
181 if (jbra->idle == jbrb->idle)
182 return 0;
183 else if ((jbra->idle && !jbrb->idle) ||
184 (jbra->idle && jbrb->idle && jbra->idle < jbrb->idle))
185 return 1;
186 else
187 return -1;
190 if (state_a == JABBER_BUDDY_STATE_ONLINE)
191 return -1;
192 else if (state_a == JABBER_BUDDY_STATE_AWAY &&
193 (state_b == JABBER_BUDDY_STATE_XA ||
194 state_b == JABBER_BUDDY_STATE_UNAVAILABLE ||
195 state_b == JABBER_BUDDY_STATE_UNKNOWN))
196 return -1;
197 else if (state_a == JABBER_BUDDY_STATE_XA &&
198 (state_b == JABBER_BUDDY_STATE_UNAVAILABLE ||
199 state_b == JABBER_BUDDY_STATE_UNKNOWN))
200 return -1;
201 else if (state_a == JABBER_BUDDY_STATE_UNAVAILABLE &&
202 state_b == JABBER_BUDDY_STATE_UNKNOWN)
203 return -1;
205 return 1;
208 JabberBuddyResource *jabber_buddy_find_resource(JabberBuddy *jb,
209 const char *resource)
211 GList *l;
213 if (!jb)
214 return NULL;
216 if (resource == NULL)
217 return jb->resources ? jb->resources->data : NULL;
219 for (l = jb->resources; l; l = l->next)
221 JabberBuddyResource *jbr = l->data;
222 if (purple_strequal(resource, jbr->name))
223 return jbr;
226 return NULL;
229 JabberBuddyResource *jabber_buddy_track_resource(JabberBuddy *jb, const char *resource,
230 int priority, JabberBuddyState state, const char *status)
232 /* TODO: Optimization: Only reinsert if priority+state changed */
233 JabberBuddyResource *jbr = jabber_buddy_find_resource(jb, resource);
234 if (jbr) {
235 jb->resources = g_list_remove(jb->resources, jbr);
236 } else {
237 jbr = g_new0(JabberBuddyResource, 1);
238 jbr->jb = jb;
239 jbr->name = g_strdup(resource);
240 jbr->capabilities = JABBER_CAP_NONE;
241 jbr->tz_off = PURPLE_NO_TZ_OFF;
243 jbr->priority = priority;
244 jbr->state = state;
245 g_free(jbr->status);
246 jbr->status = g_strdup(status);
248 jb->resources = g_list_insert_sorted(jb->resources, jbr,
249 resource_compare_cb);
250 return jbr;
253 void jabber_buddy_remove_resource(JabberBuddy *jb, const char *resource)
255 JabberBuddyResource *jbr = jabber_buddy_find_resource(jb, resource);
257 if(!jbr)
258 return;
260 jabber_buddy_resource_free(jbr);
263 /*******
264 * This is the old vCard stuff taken from the old prpl. vCards, by definition
265 * are a temporary thing until jabber can get its act together and come up
266 * with a format for user information, hence the namespace of 'vcard-temp'
268 * Since I don't feel like putting that much work into something that's
269 * _supposed_ to go away, i'm going to just copy the kludgy old code here,
270 * and make it purdy when jabber comes up with a standards-track JEP to
271 * replace vcard-temp
272 * --Nathan
273 *******/
275 /*---------------------------------------*/
276 /* Jabber "set info" (vCard) support */
277 /*---------------------------------------*/
280 * V-Card format:
282 * <vCard prodid='' version='' xmlns=''>
283 * <FN></FN>
284 * <N>
285 * <FAMILY/>
286 * <GIVEN/>
287 * </N>
288 * <NICKNAME/>
289 * <URL/>
290 * <ADR>
291 * <STREET/>
292 * <EXTADD/>
293 * <LOCALITY/>
294 * <REGION/>
295 * <PCODE/>
296 * <COUNTRY/>
297 * </ADR>
298 * <TEL/>
299 * <EMAIL/>
300 * <ORG>
301 * <ORGNAME/>
302 * <ORGUNIT/>
303 * </ORG>
304 * <TITLE/>
305 * <ROLE/>
306 * <DESC/>
307 * <BDAY/>
308 * </vCard>
310 * See also:
312 * http://docs.jabber.org/proto/html/vcard-temp.html
313 * http://www.vcard-xml.org/dtd/vCard-XML-v2-20010520.dtd
317 * Cross-reference user-friendly V-Card entry labels to vCard XML tags
318 * and attributes.
320 * Order is (or should be) unimportant. For example: we have no way of
321 * knowing in what order real data will arrive.
323 * Format: Label, Pre-set text, "visible" flag, "editable" flag, XML tag
324 * name, XML tag's parent tag "path" (relative to vCard node).
326 * List is terminated by a NULL label pointer.
328 * Entries with no label text, but with XML tag and parent tag
329 * entries, are used by V-Card XML construction routines to
330 * "automagically" construct the appropriate XML node tree.
332 * Thoughts on future direction/expansion
334 * This is a "simple" vCard.
336 * It is possible for nodes other than the "vCard" node to have
337 * attributes. Should that prove necessary/desirable, add an
338 * "attributes" pointer to the vcard_template struct, create the
339 * necessary tag_attr structs, and add 'em to the vcard_dflt_data
340 * array.
342 * The above changes will (obviously) require changes to the vCard
343 * construction routines.
346 struct vcard_template {
347 char *label; /* label text pointer */
348 char *tag; /* tag text */
349 char *ptag; /* parent tag "path" text */
350 } const vcard_template_data[] = {
351 {N_("Full Name"), "FN", NULL},
352 {N_("Family Name"), "FAMILY", "N"},
353 {N_("Given Name"), "GIVEN", "N"},
354 {N_("Nickname"), "NICKNAME", NULL},
355 {N_("URL"), "URL", NULL},
356 {N_("Street Address"), "STREET", "ADR"},
357 {N_("Extended Address"), "EXTADD", "ADR"},
358 {N_("Locality"), "LOCALITY", "ADR"},
359 {N_("Region"), "REGION", "ADR"},
360 {N_("Postal Code"), "PCODE", "ADR"},
361 {N_("Country"), "CTRY", "ADR"},
362 {N_("Telephone"), "NUMBER", "TEL"},
363 {N_("Email"), "USERID", "EMAIL"},
364 {N_("Organization Name"), "ORGNAME", "ORG"},
365 {N_("Organization Unit"), "ORGUNIT", "ORG"},
366 {N_("Job Title"), "TITLE", NULL},
367 {N_("Role"), "ROLE", NULL},
368 {N_("Birthday"), "BDAY", NULL},
369 {N_("Description"), "DESC", NULL},
370 {"", "N", NULL},
371 {"", "ADR", NULL},
372 {"", "ORG", NULL},
373 {NULL, NULL, NULL}
377 * The "vCard" tag's attribute list...
379 struct tag_attr {
380 char *attr;
381 char *value;
382 } const vcard_tag_attr_list[] = {
383 {"prodid", "-//HandGen//NONSGML vGen v1.0//EN"},
384 {"version", "2.0", },
385 {"xmlns", "vcard-temp", },
386 {NULL, NULL},
391 * Insert a tag node into an PurpleXmlNode tree, recursively inserting parent tag
392 * nodes as necessary
394 * Returns pointer to inserted node
396 * Note to hackers: this code is designed to be re-entrant (it's recursive--it
397 * calls itself), so don't put any "static"s in here!
399 static PurpleXmlNode *insert_tag_to_parent_tag(PurpleXmlNode *start, const char *parent_tag, const char *new_tag)
401 PurpleXmlNode *x = NULL;
404 * If the parent tag wasn't specified, see if we can get it
405 * from the vCard template struct.
407 if(parent_tag == NULL) {
408 const struct vcard_template *vc_tp = vcard_template_data;
410 while(vc_tp->label != NULL) {
411 if(purple_strequal(vc_tp->tag, new_tag)) {
412 parent_tag = vc_tp->ptag;
413 break;
415 ++vc_tp;
420 * If we have a parent tag...
422 if(parent_tag != NULL ) {
424 * Try to get the parent node for a tag
426 if((x = purple_xmlnode_get_child(start, parent_tag)) == NULL) {
428 * Descend?
430 char *grand_parent = g_strdup(parent_tag);
431 char *parent;
433 if((parent = strrchr(grand_parent, '/')) != NULL) {
434 *(parent++) = '\0';
435 x = insert_tag_to_parent_tag(start, grand_parent, parent);
436 } else {
437 x = purple_xmlnode_new_child(start, grand_parent);
439 g_free(grand_parent);
440 } else {
442 * We found *something* to be the parent node.
443 * Note: may be the "root" node!
445 PurpleXmlNode *y;
446 if((y = purple_xmlnode_get_child(x, new_tag)) != NULL) {
447 return(y);
453 * insert the new tag into its parent node
455 return(purple_xmlnode_new_child((x == NULL? start : x), new_tag));
459 * Send vCard info to Jabber server
461 void jabber_set_info(PurpleConnection *gc, const char *info)
463 PurpleImage *img;
464 JabberIq *iq;
465 JabberStream *js = purple_connection_get_protocol_data(gc);
466 PurpleXmlNode *vc_node;
467 const struct tag_attr *tag_attr;
469 /* if we haven't grabbed the remote vcard yet, we can't
470 * assume that what we have here is correct */
471 if(!js->vcard_fetched) {
472 PurpleImage *image;
473 g_free(js->initial_avatar_hash);
474 image = purple_buddy_icons_find_account_icon(purple_connection_get_account(gc));
475 if (image != NULL) {
476 js->initial_avatar_hash = g_compute_checksum_for_data(
477 G_CHECKSUM_SHA1,
478 purple_image_get_data(image),
479 purple_image_get_data_size(image)
481 g_object_unref(image);
482 } else {
483 js->initial_avatar_hash = NULL;
485 return;
488 if (js->vcard_timer) {
489 g_source_remove(js->vcard_timer);
490 js->vcard_timer = 0;
493 g_free(js->avatar_hash);
494 js->avatar_hash = NULL;
497 * Send only if there's actually any *information* to send
499 vc_node = info ? purple_xmlnode_from_str(info, -1) : NULL;
501 if (vc_node && (!vc_node->name ||
502 g_ascii_strncasecmp(vc_node->name, "vCard", 5))) {
503 purple_xmlnode_free(vc_node);
504 vc_node = NULL;
507 if ((img = purple_buddy_icons_find_account_icon(purple_connection_get_account(gc)))) {
508 gconstpointer avatar_data;
509 gsize avatar_len;
510 PurpleXmlNode *photo, *binval, *type;
511 gchar *enc;
513 if(!vc_node) {
514 vc_node = purple_xmlnode_new("vCard");
515 for(tag_attr = vcard_tag_attr_list; tag_attr->attr != NULL; ++tag_attr)
516 purple_xmlnode_set_attrib(vc_node, tag_attr->attr, tag_attr->value);
519 avatar_data = purple_image_get_data(img);
520 avatar_len = purple_image_get_data_size(img);
521 /* Get rid of an old PHOTO if one exists.
522 * TODO: This may want to be modified to remove all old PHOTO
523 * children, at the moment some people have managed to get
524 * multiple PHOTO entries in their vCard. */
525 if((photo = purple_xmlnode_get_child(vc_node, "PHOTO"))) {
526 purple_xmlnode_free(photo);
528 photo = purple_xmlnode_new_child(vc_node, "PHOTO");
529 type = purple_xmlnode_new_child(photo, "TYPE");
530 purple_xmlnode_insert_data(type, "image/png", -1);
531 binval = purple_xmlnode_new_child(photo, "BINVAL");
532 enc = g_base64_encode(avatar_data, avatar_len);
534 js->avatar_hash = g_compute_checksum_for_data(G_CHECKSUM_SHA1,
535 avatar_data, avatar_len);
537 purple_xmlnode_insert_data(binval, enc, -1);
538 g_free(enc);
539 g_object_unref(img);
540 } else if (vc_node) {
541 PurpleXmlNode *photo;
542 /* TODO: Remove all PHOTO children? (see above note) */
543 if ((photo = purple_xmlnode_get_child(vc_node, "PHOTO"))) {
544 purple_xmlnode_free(photo);
548 if (vc_node != NULL) {
549 iq = jabber_iq_new(js, JABBER_IQ_SET);
550 purple_xmlnode_insert_child(iq->node, vc_node);
551 jabber_iq_send(iq);
553 /* Send presence to update vcard-temp:x:update */
554 jabber_presence_send(js, FALSE);
558 void jabber_set_buddy_icon(PurpleConnection *gc, PurpleImage *img)
560 PurpleAccount *account = purple_connection_get_account(gc);
562 /* Publish the avatar as specified in XEP-0084 */
563 jabber_avatar_set(purple_connection_get_protocol_data(gc), img);
564 /* Set the image in our vCard */
565 jabber_set_info(gc, purple_account_get_user_info(account));
567 /* TODO: Fake image to ourselves, since a number of servers do not echo
568 * back our presence to us. To do this without uselessly copying the data
569 * of the image, we need purple_buddy_icons_set_for_user_image (i.e. takes
570 * an existing icon/stored image). */
574 * This is the callback from the "ok clicked" for "set vCard"
576 * Sets the vCard with data from PurpleRequestFields.
578 static void
579 jabber_format_info(PurpleConnection *gc, PurpleRequestFields *fields)
581 PurpleXmlNode *vc_node;
582 PurpleRequestField *field;
583 const char *text;
584 char *p;
585 const struct vcard_template *vc_tp;
586 const struct tag_attr *tag_attr;
588 vc_node = purple_xmlnode_new("vCard");
590 for(tag_attr = vcard_tag_attr_list; tag_attr->attr != NULL; ++tag_attr)
591 purple_xmlnode_set_attrib(vc_node, tag_attr->attr, tag_attr->value);
593 for (vc_tp = vcard_template_data; vc_tp->label != NULL; vc_tp++) {
594 if (*vc_tp->label == '\0')
595 continue;
597 field = purple_request_fields_get_field(fields, vc_tp->tag);
598 text = purple_request_field_string_get_value(field);
601 if (text != NULL && *text != '\0') {
602 PurpleXmlNode *xp;
604 purple_debug_info("jabber", "Setting %s to '%s'\n", vc_tp->tag, text);
606 if ((xp = insert_tag_to_parent_tag(vc_node,
607 NULL, vc_tp->tag)) != NULL) {
609 purple_xmlnode_insert_data(xp, text, -1);
614 p = purple_xmlnode_to_str(vc_node, NULL);
615 purple_xmlnode_free(vc_node);
617 purple_account_set_user_info(purple_connection_get_account(gc), p);
618 purple_serv_set_info(gc, p);
620 g_free(p);
624 * This gets executed by the proto action
626 * Creates a new PurpleRequestFields struct, gets the XML-formatted user_info
627 * string (if any) into GSLists for the (multi-entry) edit dialog and
628 * calls the set_vcard dialog.
630 void jabber_setup_set_info(PurpleProtocolAction *action)
632 PurpleConnection *gc = (PurpleConnection *) action->connection;
633 PurpleRequestFields *fields;
634 PurpleRequestFieldGroup *group;
635 PurpleRequestField *field;
636 const struct vcard_template *vc_tp;
637 const char *user_info;
638 char *cdata = NULL;
639 PurpleXmlNode *x_vc_data = NULL;
641 fields = purple_request_fields_new();
642 group = purple_request_field_group_new(NULL);
643 purple_request_fields_add_group(fields, group);
646 * Get existing, XML-formatted, user info
648 if((user_info = purple_account_get_user_info(purple_connection_get_account(gc))) != NULL)
649 x_vc_data = purple_xmlnode_from_str(user_info, -1);
652 * Set up GSLists for edit with labels from "template," data from user info
654 for(vc_tp = vcard_template_data; vc_tp->label != NULL; ++vc_tp) {
655 PurpleXmlNode *data_node;
656 if((vc_tp->label)[0] == '\0')
657 continue;
659 if (x_vc_data != NULL) {
660 if(vc_tp->ptag == NULL) {
661 data_node = purple_xmlnode_get_child(x_vc_data, vc_tp->tag);
662 } else {
663 gchar *tag = g_strdup_printf("%s/%s", vc_tp->ptag, vc_tp->tag);
664 data_node = purple_xmlnode_get_child(x_vc_data, tag);
665 g_free(tag);
667 if(data_node)
668 cdata = purple_xmlnode_get_data(data_node);
671 if(purple_strequal(vc_tp->tag, "DESC")) {
672 field = purple_request_field_string_new(vc_tp->tag,
673 _(vc_tp->label), cdata,
674 TRUE);
675 } else {
676 field = purple_request_field_string_new(vc_tp->tag,
677 _(vc_tp->label), cdata,
678 FALSE);
681 g_free(cdata);
682 cdata = NULL;
684 purple_request_field_group_add_field(group, field);
687 if(x_vc_data != NULL)
688 purple_xmlnode_free(x_vc_data);
690 purple_request_fields(gc, _("Edit XMPP vCard"),
691 _("Edit XMPP vCard"),
692 _("All items below are optional. Enter only the "
693 "information with which you feel comfortable."),
694 fields,
695 _("Save"), G_CALLBACK(jabber_format_info),
696 _("Cancel"), NULL,
697 purple_request_cpar_from_connection(gc),
698 gc);
701 /*---------------------------------------*/
702 /* End Jabber "set info" (vCard) support */
703 /*---------------------------------------*/
705 /******
706 * end of that ancient crap that needs to die
707 ******/
709 static void jabber_buddy_info_destroy(JabberBuddyInfo *jbi)
711 /* Remove the timeout, which would otherwise trigger jabber_buddy_get_info_timeout() */
712 if (jbi->timeout_handle > 0)
713 g_source_remove(jbi->timeout_handle);
715 g_free(jbi->jid);
716 g_hash_table_destroy(jbi->resources);
717 g_free(jbi->last_message);
718 purple_notify_user_info_destroy(jbi->user_info);
719 g_free(jbi);
722 static void
723 add_jbr_info(JabberBuddyInfo *jbi, const char *resource,
724 JabberBuddyResource *jbr)
726 JabberBuddyInfoResource *jbir;
727 PurpleNotifyUserInfo *user_info;
729 jbir = g_hash_table_lookup(jbi->resources, resource);
730 user_info = jbi->user_info;
732 if (jbr && jbr->client.name) {
733 char *tmp =
734 g_strdup_printf("%s%s%s", jbr->client.name,
735 (jbr->client.version ? " " : ""),
736 (jbr->client.version ? jbr->client.version : ""));
737 /* TODO: Check whether it's correct to call prepend_pair_html,
738 or if we should be using prepend_pair_plaintext */
739 purple_notify_user_info_prepend_pair_html(user_info, _("Client"), tmp);
740 g_free(tmp);
742 if (jbr->client.os) {
743 /* TODO: Check whether it's correct to call prepend_pair_html,
744 or if we should be using prepend_pair_plaintext */
745 purple_notify_user_info_prepend_pair_html(user_info, _("Operating System"), jbr->client.os);
749 if (jbr && jbr->tz_off != PURPLE_NO_TZ_OFF) {
750 time_t now_t;
751 struct tm *now;
752 char *timestamp;
753 time(&now_t);
754 now_t += jbr->tz_off;
755 now = gmtime(&now_t);
757 timestamp =
758 g_strdup_printf("%s %c%02d%02d", purple_time_format(now),
759 jbr->tz_off < 0 ? '-' : '+',
760 abs(jbr->tz_off / (60*60)),
761 abs((jbr->tz_off % (60*60)) / 60));
762 purple_notify_user_info_prepend_pair_plaintext(user_info, _("Local Time"), timestamp);
763 g_free(timestamp);
766 if (jbir && jbir->idle_seconds > 0) {
767 char *idle = purple_str_seconds_to_string(jbir->idle_seconds);
768 purple_notify_user_info_prepend_pair_plaintext(user_info, _("Idle"), idle);
769 g_free(idle);
772 if (jbr) {
773 char *purdy = NULL;
774 char *tmp;
775 char priority[12];
776 const char *status_name = jabber_buddy_state_get_name(jbr->state);
778 if (jbr->status) {
779 tmp = purple_markup_escape_text(jbr->status, -1);
780 purdy = purple_strdup_withhtml(tmp);
781 g_free(tmp);
783 if (purple_strequal(status_name, purdy))
784 status_name = NULL;
787 tmp = g_strdup_printf("%s%s%s", (status_name ? status_name : ""),
788 ((status_name && purdy) ? ": " : ""),
789 (purdy ? purdy : ""));
790 purple_notify_user_info_prepend_pair_html(user_info, _("Status"), tmp);
792 g_snprintf(priority, sizeof(priority), "%d", jbr->priority);
793 purple_notify_user_info_prepend_pair_plaintext(user_info, _("Priority"), priority);
795 g_free(tmp);
796 g_free(purdy);
797 } else {
798 purple_notify_user_info_prepend_pair_plaintext(user_info, _("Status"), _("Unknown"));
802 static void jabber_buddy_info_show_if_ready(JabberBuddyInfo *jbi)
804 char *resource_name;
805 JabberBuddyResource *jbr;
806 GList *resources;
807 PurpleNotifyUserInfo *user_info;
809 /* not yet */
810 if (jbi->ids)
811 return;
813 user_info = jbi->user_info;
814 resource_name = jabber_get_resource(jbi->jid);
816 /* If we have one or more pairs from the vcard, put a section break above it */
817 if (g_queue_get_length(purple_notify_user_info_get_entries(user_info)))
818 purple_notify_user_info_prepend_section_break(user_info);
820 /* Add the information about the user's resource(s) */
821 if (resource_name) {
822 jbr = jabber_buddy_find_resource(jbi->jb, resource_name);
823 add_jbr_info(jbi, resource_name, jbr);
824 } else {
825 /* TODO: This is in priority-ascending order (lowest prio first), because
826 * everything is prepended. Is that ok? */
827 for (resources = jbi->jb->resources; resources; resources = resources->next) {
828 jbr = resources->data;
830 /* put a section break between resources, this is not needed if
831 we are at the first, because one was already added for the vcard
832 section */
833 if (resources != jbi->jb->resources)
834 purple_notify_user_info_prepend_section_break(user_info);
836 add_jbr_info(jbi, jbr->name, jbr);
838 if (jbr->name) {
839 /* TODO: Check whether it's correct to call prepend_pair_html,
840 or if we should be using prepend_pair_plaintext */
841 purple_notify_user_info_prepend_pair_html(user_info, _("Resource"), jbr->name);
846 if (!jbi->jb->resources) {
847 /* the buddy is offline */
848 gboolean is_domain = jabber_jid_is_domain(jbi->jid);
850 if (jbi->last_seconds > 0) {
851 char *last = purple_str_seconds_to_string(jbi->last_seconds);
852 gchar *message = NULL;
853 const gchar *title = NULL;
854 if (is_domain) {
855 title = _("Uptime");
856 message = last;
857 last = NULL;
858 } else {
859 title = _("Logged Off");
860 message = g_strdup_printf(_("%s ago"), last);
862 purple_notify_user_info_prepend_pair_plaintext(user_info, title, message);
863 g_free(last);
864 g_free(message);
867 if (!is_domain) {
868 gchar *status =
869 g_strdup_printf("%s%s%s", _("Offline"),
870 jbi->last_message ? ": " : "",
871 jbi->last_message ? jbi->last_message : "");
872 /* TODO: Check whether it's correct to call prepend_pair_html,
873 or if we should be using prepend_pair_plaintext */
874 purple_notify_user_info_prepend_pair_html(user_info, _("Status"), status);
875 g_free(status);
879 g_free(resource_name);
881 purple_notify_userinfo(jbi->js->gc, jbi->jid, user_info, NULL, NULL);
883 while (jbi->vcard_images) {
884 g_object_unref(jbi->vcard_images->data);
885 jbi->vcard_images = g_slist_delete_link(jbi->vcard_images, jbi->vcard_images);
888 jbi->js->pending_buddy_info_requests = g_slist_remove(jbi->js->pending_buddy_info_requests, jbi);
890 jabber_buddy_info_destroy(jbi);
893 static void jabber_buddy_info_remove_id(JabberBuddyInfo *jbi, const char *id)
895 GSList *l = jbi->ids;
896 char *comp_id;
898 if(!id)
899 return;
901 while(l) {
902 comp_id = l->data;
903 if(purple_strequal(id, comp_id)) {
904 jbi->ids = g_slist_remove(jbi->ids, comp_id);
905 g_free(comp_id);
906 return;
908 l = l->next;
912 static gboolean
913 set_own_vcard_cb(gpointer data)
915 JabberStream *js = data;
916 PurpleAccount *account = purple_connection_get_account(js->gc);
918 js->vcard_timer = 0;
920 jabber_set_info(js->gc, purple_account_get_user_info(account));
922 return FALSE;
925 static void jabber_vcard_save_mine(JabberStream *js, const char *from,
926 JabberIqType type, const char *id,
927 PurpleXmlNode *packet, gpointer data)
929 PurpleXmlNode *vcard, *photo, *binval;
930 char *txt, *vcard_hash = NULL;
931 PurpleAccount *account;
933 if (type == JABBER_IQ_ERROR) {
934 PurpleXmlNode *error;
935 purple_debug_warning("jabber", "Server returned error while retrieving vCard\n");
937 error = purple_xmlnode_get_child(packet, "error");
938 if (!error || !purple_xmlnode_get_child(error, "item-not-found"))
939 return;
942 account = purple_connection_get_account(js->gc);
944 if((vcard = purple_xmlnode_get_child(packet, "vCard")) ||
945 (vcard = purple_xmlnode_get_child_with_namespace(packet, "query", "vcard-temp")))
947 txt = purple_xmlnode_to_str(vcard, NULL);
948 purple_account_set_user_info(account, txt);
949 g_free(txt);
950 } else {
951 /* if we have no vCard, then lets not overwrite what we might have locally */
954 js->vcard_fetched = TRUE;
956 if (vcard && (photo = purple_xmlnode_get_child(vcard, "PHOTO")) &&
957 (binval = purple_xmlnode_get_child(photo, "BINVAL"))) {
958 gsize size;
959 char *bintext = purple_xmlnode_get_data(binval);
960 if (bintext) {
961 guchar *data = g_base64_decode(bintext, &size);
962 g_free(bintext);
964 if (data) {
965 vcard_hash = g_compute_checksum_for_data(
966 G_CHECKSUM_SHA1, data, size);
967 g_free(data);
972 /* Republish our vcard if the photo is different than the server's */
973 if (js->initial_avatar_hash && !purple_strequal(vcard_hash, js->initial_avatar_hash)) {
975 * Google Talk has developed the behavior that it will not accept
976 * a vcard set in the first 10 seconds (or so) of the connection;
977 * it returns an error (namespaces trimmed):
978 * <error code="500" type="wait"><internal-server-error/></error>.
980 if (js->googletalk)
981 js->vcard_timer = g_timeout_add_seconds(10, set_own_vcard_cb,
982 js);
983 else
984 jabber_set_info(js->gc, purple_account_get_user_info(account));
985 } else if (vcard_hash) {
986 /* A photo is in the vCard. Advertise its hash */
987 js->avatar_hash = vcard_hash;
988 vcard_hash = NULL;
990 /* Send presence to update vcard-temp:x:update */
991 jabber_presence_send(js, FALSE);
994 g_free(vcard_hash);
997 void jabber_vcard_fetch_mine(JabberStream *js)
999 JabberIq *iq = jabber_iq_new(js, JABBER_IQ_GET);
1001 PurpleXmlNode *vcard = purple_xmlnode_new_child(iq->node, "vCard");
1002 purple_xmlnode_set_namespace(vcard, "vcard-temp");
1003 jabber_iq_set_callback(iq, jabber_vcard_save_mine, NULL);
1005 jabber_iq_send(iq);
1008 static void jabber_vcard_parse(JabberStream *js, const char *from,
1009 JabberIqType type, const char *id,
1010 PurpleXmlNode *packet, gpointer data)
1012 char *bare_jid;
1013 char *text;
1014 char *serverside_alias = NULL;
1015 PurpleXmlNode *vcard;
1016 PurpleAccount *account;
1017 JabberBuddyInfo *jbi = data;
1018 PurpleNotifyUserInfo *user_info;
1020 g_return_if_fail(jbi != NULL);
1022 jabber_buddy_info_remove_id(jbi, id);
1024 if (type == JABBER_IQ_ERROR) {
1025 purple_debug_info("jabber", "Got error response for vCard\n");
1026 jabber_buddy_info_show_if_ready(jbi);
1027 return;
1030 user_info = jbi->user_info;
1031 account = purple_connection_get_account(js->gc);
1032 bare_jid = jabber_get_bare_jid(from ? from : purple_account_get_username(account));
1034 /* TODO: Is the query xmlns='vcard-temp' version of this still necessary? */
1035 if((vcard = purple_xmlnode_get_child(packet, "vCard")) ||
1036 (vcard = purple_xmlnode_get_child_with_namespace(packet, "query", "vcard-temp"))) {
1037 PurpleXmlNode *child;
1038 for(child = vcard->child; child; child = child->next)
1040 PurpleXmlNode *child2;
1042 if(child->type != PURPLE_XMLNODE_TYPE_TAG)
1043 continue;
1045 text = purple_xmlnode_get_data(child);
1046 if(text && purple_strequal(child->name, "FN")) {
1047 if (!serverside_alias)
1048 serverside_alias = g_strdup(text);
1050 purple_notify_user_info_add_pair_plaintext(user_info, _("Full Name"), text);
1051 } else if(purple_strequal(child->name, "N")) {
1052 for(child2 = child->child; child2; child2 = child2->next)
1054 char *text2;
1056 if(child2->type != PURPLE_XMLNODE_TYPE_TAG)
1057 continue;
1059 text2 = purple_xmlnode_get_data(child2);
1060 if(text2 && purple_strequal(child2->name, "FAMILY")) {
1061 purple_notify_user_info_add_pair_plaintext(user_info, _("Family Name"), text2);
1062 } else if(text2 && purple_strequal(child2->name, "GIVEN")) {
1063 purple_notify_user_info_add_pair_plaintext(user_info, _("Given Name"), text2);
1064 } else if(text2 && purple_strequal(child2->name, "MIDDLE")) {
1065 purple_notify_user_info_add_pair_plaintext(user_info, _("Middle Name"), text2);
1067 g_free(text2);
1069 } else if(text && purple_strequal(child->name, "NICKNAME")) {
1070 /* Prefer the Nickcname to the Full Name as the serverside alias if it's not just part of the jid.
1071 * Ignore it if it's part of the jid. */
1072 if (strstr(bare_jid, text) == NULL) {
1073 g_free(serverside_alias);
1074 serverside_alias = g_strdup(text);
1076 purple_notify_user_info_add_pair_plaintext(user_info, _("Nickname"), text);
1078 } else if(text && purple_strequal(child->name, "BDAY")) {
1079 purple_notify_user_info_add_pair_plaintext(user_info, _("Birthday"), text);
1080 } else if(purple_strequal(child->name, "ADR")) {
1081 gboolean address_line_added = FALSE;
1083 for(child2 = child->child; child2; child2 = child2->next)
1085 char *text2;
1087 if(child2->type != PURPLE_XMLNODE_TYPE_TAG)
1088 continue;
1090 text2 = purple_xmlnode_get_data(child2);
1091 if (text2 == NULL)
1092 continue;
1094 /* We do this here so that it's not added if all the child
1095 * elements are empty. */
1096 if (!address_line_added)
1098 purple_notify_user_info_add_section_header(user_info, _("Address"));
1099 address_line_added = TRUE;
1102 if(purple_strequal(child2->name, "POBOX")) {
1103 purple_notify_user_info_add_pair_plaintext(user_info, _("P.O. Box"), text2);
1104 } else if (purple_strequal(child2->name, "EXTADD") || purple_strequal(child2->name, "EXTADR")) {
1106 * EXTADD is correct, EXTADR is generated by other
1107 * clients. The next time someone reads this, remove
1108 * EXTADR.
1110 purple_notify_user_info_add_pair_plaintext(user_info, _("Extended Address"), text2);
1111 } else if(purple_strequal(child2->name, "STREET")) {
1112 purple_notify_user_info_add_pair_plaintext(user_info, _("Street Address"), text2);
1113 } else if(purple_strequal(child2->name, "LOCALITY")) {
1114 purple_notify_user_info_add_pair_plaintext(user_info, _("Locality"), text2);
1115 } else if(purple_strequal(child2->name, "REGION")) {
1116 purple_notify_user_info_add_pair_plaintext(user_info, _("Region"), text2);
1117 } else if(purple_strequal(child2->name, "PCODE")) {
1118 purple_notify_user_info_add_pair_plaintext(user_info, _("Postal Code"), text2);
1119 } else if(purple_strequal(child2->name, "CTRY")
1120 || purple_strequal(child2->name, "COUNTRY")) {
1121 purple_notify_user_info_add_pair_plaintext(user_info, _("Country"), text2);
1123 g_free(text2);
1126 if (address_line_added)
1127 purple_notify_user_info_add_section_break(user_info);
1129 } else if(purple_strequal(child->name, "TEL")) {
1130 char *number;
1131 if((child2 = purple_xmlnode_get_child(child, "NUMBER"))) {
1132 /* show what kind of number it is */
1133 number = purple_xmlnode_get_data(child2);
1134 if(number) {
1135 purple_notify_user_info_add_pair_plaintext(user_info, _("Telephone"), number);
1136 g_free(number);
1138 } else if((number = purple_xmlnode_get_data(child))) {
1139 /* lots of clients (including purple) do this, but it's
1140 * out of spec */
1141 purple_notify_user_info_add_pair_plaintext(user_info, _("Telephone"), number);
1142 g_free(number);
1144 } else if(purple_strequal(child->name, "EMAIL")) {
1145 char *userid, *escaped;
1146 if((child2 = purple_xmlnode_get_child(child, "USERID"))) {
1147 /* show what kind of email it is */
1148 userid = purple_xmlnode_get_data(child2);
1149 if(userid) {
1150 char *mailto;
1151 escaped = g_markup_escape_text(userid, -1);
1152 mailto = g_strdup_printf("<a href=\"mailto:%s\">%s</a>", escaped, escaped);
1153 purple_notify_user_info_add_pair_html(user_info, _("Email"), mailto);
1155 g_free(mailto);
1156 g_free(escaped);
1157 g_free(userid);
1159 } else if((userid = purple_xmlnode_get_data(child))) {
1160 /* lots of clients (including purple) do this, but it's
1161 * out of spec */
1162 char *mailto;
1164 escaped = g_markup_escape_text(userid, -1);
1165 mailto = g_strdup_printf("<a href=\"mailto:%s\">%s</a>", escaped, escaped);
1166 purple_notify_user_info_add_pair_html(user_info, _("Email"), mailto);
1168 g_free(mailto);
1169 g_free(escaped);
1170 g_free(userid);
1172 } else if(purple_strequal(child->name, "ORG")) {
1173 for(child2 = child->child; child2; child2 = child2->next)
1175 char *text2;
1177 if(child2->type != PURPLE_XMLNODE_TYPE_TAG)
1178 continue;
1180 text2 = purple_xmlnode_get_data(child2);
1181 if(text2 && purple_strequal(child2->name, "ORGNAME")) {
1182 purple_notify_user_info_add_pair_plaintext(user_info, _("Organization Name"), text2);
1183 } else if(text2 && purple_strequal(child2->name, "ORGUNIT")) {
1184 purple_notify_user_info_add_pair_plaintext(user_info, _("Organization Unit"), text2);
1186 g_free(text2);
1188 } else if(text && purple_strequal(child->name, "TITLE")) {
1189 purple_notify_user_info_add_pair_plaintext(user_info, _("Job Title"), text);
1190 } else if(text && purple_strequal(child->name, "ROLE")) {
1191 purple_notify_user_info_add_pair_plaintext(user_info, _("Role"), text);
1192 } else if(text && purple_strequal(child->name, "DESC")) {
1193 purple_notify_user_info_add_pair_plaintext(user_info, _("Description"), text);
1194 } else if(purple_strequal(child->name, "PHOTO") ||
1195 purple_strequal(child->name, "LOGO")) {
1196 char *bintext = NULL;
1197 PurpleXmlNode *binval;
1199 if ((binval = purple_xmlnode_get_child(child, "BINVAL")) &&
1200 (bintext = purple_xmlnode_get_data(binval))) {
1201 gsize size;
1202 guchar *data;
1203 gboolean photo = purple_strequal(child->name, "PHOTO");
1205 data = g_base64_decode(bintext, &size);
1206 if (data) {
1207 PurpleImage *img;
1208 guint img_id;
1209 char *img_text;
1210 char *hash;
1212 img = purple_image_new_from_data(data, size);
1213 img_id = purple_image_store_add(img);
1215 jbi->vcard_images = g_slist_prepend(jbi->vcard_images, img);
1216 img_text = g_strdup_printf("<img src='"
1217 PURPLE_IMAGE_STORE_PROTOCOL "%u'>", img_id);
1219 purple_notify_user_info_add_pair_html(user_info, (photo ? _("Photo") : _("Logo")), img_text);
1221 hash = g_compute_checksum_for_data(G_CHECKSUM_SHA1, data, size);
1222 purple_buddy_icons_set_for_user(account, bare_jid, data, size, hash);
1223 g_free(hash);
1224 g_free(img_text);
1226 g_free(bintext);
1229 g_free(text);
1233 if (serverside_alias) {
1234 PurpleBuddy *b;
1235 /* If we found a serverside alias, set it and tell the core */
1236 purple_serv_got_alias(js->gc, bare_jid, serverside_alias);
1237 b = purple_blist_find_buddy(account, bare_jid);
1238 if (b) {
1239 purple_blist_node_set_string((PurpleBlistNode*)b, "servernick", serverside_alias);
1242 g_free(serverside_alias);
1245 g_free(bare_jid);
1247 jabber_buddy_info_show_if_ready(jbi);
1250 static void jabber_buddy_info_resource_free(gpointer data)
1252 JabberBuddyInfoResource *jbri = data;
1253 g_free(jbri);
1256 static guint jbir_hash(gconstpointer v)
1258 if (v)
1259 return g_str_hash(v);
1260 else
1261 return 0;
1264 static gboolean jbir_equal(gconstpointer v1, gconstpointer v2)
1266 const gchar *resource_1 = v1;
1267 const gchar *resource_2 = v2;
1269 return purple_strequal(resource_1, resource_2);
1272 static void jabber_version_parse(JabberStream *js, const char *from,
1273 JabberIqType type, const char *id,
1274 PurpleXmlNode *packet, gpointer data)
1276 JabberBuddyInfo *jbi = data;
1277 PurpleXmlNode *query;
1278 char *resource_name;
1280 g_return_if_fail(jbi != NULL);
1282 jabber_buddy_info_remove_id(jbi, id);
1284 if(!from)
1285 return;
1287 resource_name = jabber_get_resource(from);
1289 if(resource_name) {
1290 if (type == JABBER_IQ_RESULT) {
1291 if((query = purple_xmlnode_get_child(packet, "query"))) {
1292 JabberBuddyResource *jbr = jabber_buddy_find_resource(jbi->jb, resource_name);
1293 if(jbr) {
1294 PurpleXmlNode *node;
1295 if((node = purple_xmlnode_get_child(query, "name"))) {
1296 jbr->client.name = purple_xmlnode_get_data(node);
1298 if((node = purple_xmlnode_get_child(query, "version"))) {
1299 jbr->client.version = purple_xmlnode_get_data(node);
1301 if((node = purple_xmlnode_get_child(query, "os"))) {
1302 jbr->client.os = purple_xmlnode_get_data(node);
1307 g_free(resource_name);
1310 jabber_buddy_info_show_if_ready(jbi);
1313 static void jabber_last_parse(JabberStream *js, const char *from,
1314 JabberIqType type, const char *id,
1315 PurpleXmlNode *packet, gpointer data)
1317 JabberBuddyInfo *jbi = data;
1318 PurpleXmlNode *query;
1319 char *resource_name;
1320 const char *seconds;
1322 g_return_if_fail(jbi != NULL);
1324 jabber_buddy_info_remove_id(jbi, id);
1326 if(!from)
1327 return;
1329 resource_name = jabber_get_resource(from);
1331 if(resource_name) {
1332 if (type == JABBER_IQ_RESULT) {
1333 if((query = purple_xmlnode_get_child(packet, "query"))) {
1334 seconds = purple_xmlnode_get_attrib(query, "seconds");
1335 if(seconds) {
1336 char *end = NULL;
1337 long sec = strtol(seconds, &end, 10);
1338 JabberBuddy *jb = NULL;
1339 char *resource = NULL;
1340 char *buddy_name = NULL;
1341 JabberBuddyResource *jbr = NULL;
1343 if(end != seconds) {
1344 JabberBuddyInfoResource *jbir = g_hash_table_lookup(jbi->resources, resource_name);
1345 if(jbir) {
1346 jbir->idle_seconds = sec;
1349 /* Update the idle time of the buddy resource, if we got it.
1350 This will correct the value when a server doesn't mark
1351 delayed presence and we got the presence when signing on */
1352 jb = jabber_buddy_find(js, from, FALSE);
1353 if (jb) {
1354 resource = jabber_get_resource(from);
1355 buddy_name = jabber_get_bare_jid(from);
1356 /* if the resource already has an idle time set, we
1357 must have gotten it originally from a presence. In
1358 this case we update it. Otherwise don't update it, to
1359 avoid setting an idle and not getting informed about
1360 the resource getting unidle */
1361 if (resource && buddy_name) {
1362 jbr = jabber_buddy_find_resource(jb, resource);
1363 if (jbr) {
1364 if (jbr->idle) {
1365 if (sec) {
1366 jbr->idle = time(NULL) - sec;
1367 } else {
1368 jbr->idle = 0;
1371 if (jbr ==
1372 jabber_buddy_find_resource(jb, NULL)) {
1373 purple_protocol_got_user_idle(purple_connection_get_account(js->gc),
1374 buddy_name, jbr->idle, jbr->idle);
1379 g_free(resource);
1380 g_free(buddy_name);
1385 g_free(resource_name);
1388 jabber_buddy_info_show_if_ready(jbi);
1391 static void jabber_last_offline_parse(JabberStream *js, const char *from,
1392 JabberIqType type, const char *id,
1393 PurpleXmlNode *packet, gpointer data)
1395 JabberBuddyInfo *jbi = data;
1396 PurpleXmlNode *query;
1397 const char *seconds;
1399 g_return_if_fail(jbi != NULL);
1401 jabber_buddy_info_remove_id(jbi, id);
1403 if (type == JABBER_IQ_RESULT) {
1404 if((query = purple_xmlnode_get_child(packet, "query"))) {
1405 seconds = purple_xmlnode_get_attrib(query, "seconds");
1406 if(seconds) {
1407 char *end = NULL;
1408 long sec = strtol(seconds, &end, 10);
1409 if(end != seconds) {
1410 jbi->last_seconds = sec;
1413 jbi->last_message = purple_xmlnode_get_data(query);
1417 jabber_buddy_info_show_if_ready(jbi);
1420 static void jabber_time_parse(JabberStream *js, const char *from,
1421 JabberIqType type, const char *id,
1422 PurpleXmlNode *packet, gpointer data)
1424 JabberBuddyInfo *jbi = data;
1425 JabberBuddyResource *jbr;
1426 char *resource_name;
1428 g_return_if_fail(jbi != NULL);
1430 jabber_buddy_info_remove_id(jbi, id);
1432 if (!from)
1433 return;
1435 resource_name = jabber_get_resource(from);
1436 jbr = resource_name ? jabber_buddy_find_resource(jbi->jb, resource_name) : NULL;
1437 g_free(resource_name);
1438 if (jbr) {
1439 if (type == JABBER_IQ_RESULT) {
1440 PurpleXmlNode *time = purple_xmlnode_get_child(packet, "time");
1441 PurpleXmlNode *tzo = time ? purple_xmlnode_get_child(time, "tzo") : NULL;
1442 char *tzo_data = tzo ? purple_xmlnode_get_data(tzo) : NULL;
1443 if (tzo_data) {
1444 char *c = tzo_data;
1445 int hours, minutes;
1446 if (tzo_data[0] == 'Z' && tzo_data[1] == '\0') {
1447 jbr->tz_off = 0;
1448 } else {
1449 gboolean offset_positive = (tzo_data[0] == '+');
1450 /* [+-]HH:MM */
1451 if (((*c == '+' || *c == '-') && (c = c + 1)) &&
1452 sscanf(c, "%02d:%02d", &hours, &minutes) == 2) {
1453 jbr->tz_off = 60*60*hours + 60*minutes;
1454 if (!offset_positive)
1455 jbr->tz_off *= -1;
1456 } else {
1457 purple_debug_info("jabber", "Ignoring malformed timezone %s",
1458 tzo_data);
1462 g_free(tzo_data);
1467 jabber_buddy_info_show_if_ready(jbi);
1470 void jabber_buddy_remove_all_pending_buddy_info_requests(JabberStream *js)
1472 if (js->pending_buddy_info_requests)
1474 JabberBuddyInfo *jbi;
1475 GSList *l = js->pending_buddy_info_requests;
1476 while (l) {
1477 jbi = l->data;
1479 g_slist_free(jbi->ids);
1480 jabber_buddy_info_destroy(jbi);
1482 l = l->next;
1485 g_slist_free(js->pending_buddy_info_requests);
1486 js->pending_buddy_info_requests = NULL;
1490 static gboolean jabber_buddy_get_info_timeout(gpointer data)
1492 JabberBuddyInfo *jbi = data;
1494 /* remove the pending callbacks */
1495 while(jbi->ids) {
1496 char *id = jbi->ids->data;
1497 jabber_iq_remove_callback_by_id(jbi->js, id);
1498 jbi->ids = g_slist_remove(jbi->ids, id);
1499 g_free(id);
1502 jbi->js->pending_buddy_info_requests = g_slist_remove(jbi->js->pending_buddy_info_requests, jbi);
1503 jbi->timeout_handle = 0;
1505 jabber_buddy_info_show_if_ready(jbi);
1507 return FALSE;
1510 static gboolean _client_is_blacklisted(JabberBuddyResource *jbr, const char *ns)
1512 /* can't be blacklisted if we don't know what you're running yet */
1513 if(!jbr->client.name)
1514 return FALSE;
1516 if(purple_strequal(ns, NS_LAST_ACTIVITY)) {
1517 if(purple_strequal(jbr->client.name, "Trillian")) {
1518 /* verified by nwalp 2007/05/09 */
1519 if(purple_strequal(jbr->client.version, "3.1.0.121") ||
1520 /* verified by nwalp 2007/09/19 */
1521 purple_strequal(jbr->client.version, "3.1.7.0")) {
1522 return TRUE;
1527 return FALSE;
1530 static void
1531 dispatch_queries_for_resource(JabberStream *js, JabberBuddyInfo *jbi,
1532 gboolean is_bare_jid, const char *jid,
1533 JabberBuddyResource *jbr)
1535 JabberIq *iq;
1536 JabberBuddyInfoResource *jbir;
1537 char *full_jid = NULL;
1538 const char *to;
1540 if (is_bare_jid && jbr->name) {
1541 full_jid = g_strdup_printf("%s/%s", jid, jbr->name);
1542 to = full_jid;
1543 } else
1544 to = jid;
1546 jbir = g_new0(JabberBuddyInfoResource, 1);
1547 g_hash_table_insert(jbi->resources, g_strdup(jbr->name), jbir);
1549 if(!jbr->client.name) {
1550 iq = jabber_iq_new_query(js, JABBER_IQ_GET, "jabber:iq:version");
1551 purple_xmlnode_set_attrib(iq->node, "to", to);
1552 jabber_iq_set_callback(iq, jabber_version_parse, jbi);
1553 jbi->ids = g_slist_prepend(jbi->ids, g_strdup(iq->id));
1554 jabber_iq_send(iq);
1557 /* this is to fix the feeling of irritation I get when trying
1558 * to get info on a friend running Trillian, which doesn't
1559 * respond (with an error or otherwise) to jabber:iq:last
1560 * requests. There are a number of Trillian users in my
1561 * office. */
1562 if(!_client_is_blacklisted(jbr, NS_LAST_ACTIVITY)) {
1563 iq = jabber_iq_new_query(js, JABBER_IQ_GET, NS_LAST_ACTIVITY);
1564 purple_xmlnode_set_attrib(iq->node, "to", to);
1565 jabber_iq_set_callback(iq, jabber_last_parse, jbi);
1566 jbi->ids = g_slist_prepend(jbi->ids, g_strdup(iq->id));
1567 jabber_iq_send(iq);
1570 if (jbr->tz_off == PURPLE_NO_TZ_OFF &&
1571 (!jbr->caps.info ||
1572 jabber_resource_has_capability(jbr, NS_ENTITY_TIME))) {
1573 PurpleXmlNode *child;
1574 iq = jabber_iq_new(js, JABBER_IQ_GET);
1575 purple_xmlnode_set_attrib(iq->node, "to", to);
1576 child = purple_xmlnode_new_child(iq->node, "time");
1577 purple_xmlnode_set_namespace(child, NS_ENTITY_TIME);
1578 jabber_iq_set_callback(iq, jabber_time_parse, jbi);
1579 jbi->ids = g_slist_prepend(jbi->ids, g_strdup(iq->id));
1580 jabber_iq_send(iq);
1583 g_free(full_jid);
1586 static void jabber_buddy_get_info_for_jid(JabberStream *js, const char *jid)
1588 JabberIq *iq;
1589 PurpleXmlNode *vcard;
1590 GList *resources;
1591 JabberBuddy *jb;
1592 JabberBuddyInfo *jbi;
1593 const char *slash;
1594 gboolean is_bare_jid;
1596 jb = jabber_buddy_find(js, jid, TRUE);
1598 /* invalid JID */
1599 if(!jb)
1600 return;
1602 slash = strchr(jid, '/');
1603 is_bare_jid = (slash == NULL);
1605 jbi = g_new0(JabberBuddyInfo, 1);
1606 jbi->jid = g_strdup(jid);
1607 jbi->js = js;
1608 jbi->jb = jb;
1609 jbi->resources = g_hash_table_new_full(jbir_hash, jbir_equal, g_free, jabber_buddy_info_resource_free);
1610 jbi->user_info = purple_notify_user_info_new();
1612 iq = jabber_iq_new(js, JABBER_IQ_GET);
1614 purple_xmlnode_set_attrib(iq->node, "to", jid);
1615 vcard = purple_xmlnode_new_child(iq->node, "vCard");
1616 purple_xmlnode_set_namespace(vcard, "vcard-temp");
1618 jabber_iq_set_callback(iq, jabber_vcard_parse, jbi);
1619 jbi->ids = g_slist_prepend(jbi->ids, g_strdup(iq->id));
1621 jabber_iq_send(iq);
1623 if (is_bare_jid) {
1624 if (jb->resources) {
1625 for(resources = jb->resources; resources; resources = resources->next) {
1626 JabberBuddyResource *jbr = resources->data;
1627 dispatch_queries_for_resource(js, jbi, is_bare_jid, jid, jbr);
1629 } else {
1630 /* user is offline, send a jabber:iq:last to find out last time online */
1631 iq = jabber_iq_new_query(js, JABBER_IQ_GET, NS_LAST_ACTIVITY);
1632 purple_xmlnode_set_attrib(iq->node, "to", jid);
1633 jabber_iq_set_callback(iq, jabber_last_offline_parse, jbi);
1634 jbi->ids = g_slist_prepend(jbi->ids, g_strdup(iq->id));
1635 jabber_iq_send(iq);
1637 } else {
1638 JabberBuddyResource *jbr = jabber_buddy_find_resource(jb, slash + 1);
1639 if (jbr)
1640 dispatch_queries_for_resource(js, jbi, is_bare_jid, jid, jbr);
1641 else
1642 purple_debug_warning("jabber", "jabber_buddy_get_info_for_jid() "
1643 "was passed JID %s, but there is no corresponding "
1644 "JabberBuddyResource!\n", jid);
1647 js->pending_buddy_info_requests = g_slist_prepend(js->pending_buddy_info_requests, jbi);
1648 jbi->timeout_handle = g_timeout_add_seconds(30, jabber_buddy_get_info_timeout, jbi);
1651 void jabber_buddy_get_info(PurpleConnection *gc, const char *who)
1653 JabberStream *js = purple_connection_get_protocol_data(gc);
1654 JabberID *jid = jabber_id_new(who);
1656 if (!jid)
1657 return;
1659 if (jid->node && jabber_chat_find(js, jid->node, jid->domain)) {
1660 /* For a conversation, include the resource (indicates the user). */
1661 jabber_buddy_get_info_for_jid(js, who);
1662 } else {
1663 char *bare_jid = jabber_get_bare_jid(who);
1664 jabber_buddy_get_info_for_jid(js, bare_jid);
1665 g_free(bare_jid);
1668 jabber_id_free(jid);
1671 static void jabber_buddy_set_invisibility(JabberStream *js, const char *who,
1672 gboolean invisible)
1674 PurplePresence *gpresence;
1675 PurpleAccount *account;
1676 PurpleStatus *status;
1677 JabberBuddy *jb = jabber_buddy_find(js, who, TRUE);
1678 PurpleXmlNode *presence;
1679 JabberBuddyState state;
1680 char *msg;
1681 int priority;
1683 account = purple_connection_get_account(js->gc);
1684 gpresence = purple_account_get_presence(account);
1685 status = purple_presence_get_active_status(gpresence);
1687 purple_status_to_jabber(status, &state, &msg, &priority);
1688 presence = jabber_presence_create_js(js, state, msg, priority);
1690 g_free(msg);
1692 purple_xmlnode_set_attrib(presence, "to", who);
1693 if(invisible) {
1694 purple_xmlnode_set_attrib(presence, "type", "invisible");
1695 jb->invisible |= JABBER_INVIS_BUDDY;
1696 } else {
1697 jb->invisible &= ~JABBER_INVIS_BUDDY;
1700 jabber_send(js, presence);
1701 purple_xmlnode_free(presence);
1704 static void jabber_buddy_make_invisible(PurpleBlistNode *node, gpointer data)
1706 PurpleBuddy *buddy;
1707 PurpleConnection *gc;
1708 JabberStream *js;
1710 g_return_if_fail(PURPLE_IS_BUDDY(node));
1712 buddy = (PurpleBuddy *) node;
1713 gc = purple_account_get_connection(purple_buddy_get_account(buddy));
1714 js = purple_connection_get_protocol_data(gc);
1716 jabber_buddy_set_invisibility(js, purple_buddy_get_name(buddy), TRUE);
1719 static void jabber_buddy_make_visible(PurpleBlistNode *node, gpointer data)
1721 PurpleBuddy *buddy;
1722 PurpleConnection *gc;
1723 JabberStream *js;
1725 g_return_if_fail(PURPLE_IS_BUDDY(node));
1727 buddy = (PurpleBuddy *) node;
1728 gc = purple_account_get_connection(purple_buddy_get_account(buddy));
1729 js = purple_connection_get_protocol_data(gc);
1731 jabber_buddy_set_invisibility(js, purple_buddy_get_name(buddy), FALSE);
1734 static void cancel_presence_notification(gpointer data)
1736 PurpleBuddy *buddy;
1737 PurpleConnection *gc;
1738 JabberStream *js;
1740 buddy = data;
1741 gc = purple_account_get_connection(purple_buddy_get_account(buddy));
1742 js = purple_connection_get_protocol_data(gc);
1744 jabber_presence_subscription_set(js, purple_buddy_get_name(buddy), "unsubscribed");
1747 static void
1748 jabber_buddy_cancel_presence_notification(PurpleBlistNode *node,
1749 gpointer data)
1751 PurpleBuddy *buddy;
1752 PurpleAccount *account;
1753 PurpleConnection *gc;
1754 const gchar *name;
1755 char *msg;
1757 g_return_if_fail(PURPLE_IS_BUDDY(node));
1759 buddy = (PurpleBuddy *) node;
1760 name = purple_buddy_get_name(buddy);
1761 account = purple_buddy_get_account(buddy);
1762 gc = purple_account_get_connection(account);
1764 msg = g_strdup_printf(_("%s will no longer be able to see your status "
1765 "updates. Do you want to continue?"), name);
1766 purple_request_yes_no(gc, NULL, _("Cancel Presence Notification"),
1767 msg, 0 /* Yes */, purple_request_cpar_from_account(account), buddy,
1768 cancel_presence_notification, NULL /* Do nothing */);
1769 g_free(msg);
1772 static void jabber_buddy_rerequest_auth(PurpleBlistNode *node, gpointer data)
1774 PurpleBuddy *buddy;
1775 PurpleConnection *gc;
1776 JabberStream *js;
1778 g_return_if_fail(PURPLE_IS_BUDDY(node));
1780 buddy = (PurpleBuddy *) node;
1781 gc = purple_account_get_connection(purple_buddy_get_account(buddy));
1782 js = purple_connection_get_protocol_data(gc);
1784 jabber_presence_subscription_set(js, purple_buddy_get_name(buddy), "subscribe");
1788 static void jabber_buddy_unsubscribe(PurpleBlistNode *node, gpointer data)
1790 PurpleBuddy *buddy;
1791 PurpleConnection *gc;
1792 JabberStream *js;
1794 g_return_if_fail(PURPLE_IS_BUDDY(node));
1796 buddy = (PurpleBuddy *) node;
1797 gc = purple_account_get_connection(purple_buddy_get_account(buddy));
1798 js = purple_connection_get_protocol_data(gc);
1800 jabber_presence_subscription_set(js, purple_buddy_get_name(buddy), "unsubscribe");
1803 static void jabber_buddy_login(PurpleBlistNode *node, gpointer data) {
1804 if(PURPLE_IS_BUDDY(node)) {
1805 /* simply create a directed presence of the current status */
1806 PurpleBuddy *buddy = (PurpleBuddy *) node;
1807 PurpleConnection *gc = purple_account_get_connection(purple_buddy_get_account(buddy));
1808 JabberStream *js = purple_connection_get_protocol_data(gc);
1809 PurpleAccount *account = purple_connection_get_account(gc);
1810 PurplePresence *gpresence = purple_account_get_presence(account);
1811 PurpleStatus *status = purple_presence_get_active_status(gpresence);
1812 PurpleXmlNode *presence;
1813 JabberBuddyState state;
1814 char *msg;
1815 int priority;
1817 purple_status_to_jabber(status, &state, &msg, &priority);
1818 presence = jabber_presence_create_js(js, state, msg, priority);
1820 g_free(msg);
1822 purple_xmlnode_set_attrib(presence, "to", purple_buddy_get_name(buddy));
1824 jabber_send(js, presence);
1825 purple_xmlnode_free(presence);
1829 static void jabber_buddy_logout(PurpleBlistNode *node, gpointer data) {
1830 if(PURPLE_IS_BUDDY(node)) {
1831 /* simply create a directed unavailable presence */
1832 PurpleBuddy *buddy = (PurpleBuddy *) node;
1833 PurpleConnection *gc = purple_account_get_connection(purple_buddy_get_account(buddy));
1834 JabberStream *js = purple_connection_get_protocol_data(gc);
1835 PurpleXmlNode *presence;
1837 presence = jabber_presence_create_js(js, JABBER_BUDDY_STATE_UNAVAILABLE, NULL, 0);
1839 purple_xmlnode_set_attrib(presence, "to", purple_buddy_get_name(buddy));
1841 jabber_send(js, presence);
1842 purple_xmlnode_free(presence);
1846 static GList *jabber_buddy_menu(PurpleBuddy *buddy)
1848 PurpleConnection *gc = purple_account_get_connection(purple_buddy_get_account(buddy));
1849 JabberStream *js = purple_connection_get_protocol_data(gc);
1850 const char *name = purple_buddy_get_name(buddy);
1851 JabberBuddy *jb = jabber_buddy_find(js, name, TRUE);
1852 GList *jbrs;
1854 GList *m = NULL;
1855 PurpleActionMenu *act;
1857 if(!jb)
1858 return m;
1860 if (js->protocol_version.major == 0 && js->protocol_version.minor == 9 &&
1861 jb != js->user_jb) {
1862 if(jb->invisible & JABBER_INVIS_BUDDY) {
1863 act = purple_action_menu_new(_("Un-hide From"),
1864 PURPLE_CALLBACK(jabber_buddy_make_visible),
1865 NULL, NULL);
1866 } else {
1867 act = purple_action_menu_new(_("Temporarily Hide From"),
1868 PURPLE_CALLBACK(jabber_buddy_make_invisible),
1869 NULL, NULL);
1871 m = g_list_append(m, act);
1874 if(jb->subscription & JABBER_SUB_FROM && jb != js->user_jb) {
1875 act = purple_action_menu_new(_("Cancel Presence Notification"),
1876 PURPLE_CALLBACK(jabber_buddy_cancel_presence_notification),
1877 NULL, NULL);
1878 m = g_list_append(m, act);
1881 if(!(jb->subscription & JABBER_SUB_TO)) {
1882 act = purple_action_menu_new(_("(Re-)Request authorization"),
1883 PURPLE_CALLBACK(jabber_buddy_rerequest_auth),
1884 NULL, NULL);
1885 m = g_list_append(m, act);
1887 } else if (jb != js->user_jb) {
1889 /* shouldn't this just happen automatically when the buddy is
1890 removed? */
1891 act = purple_action_menu_new(_("Unsubscribe"),
1892 PURPLE_CALLBACK(jabber_buddy_unsubscribe),
1893 NULL, NULL);
1894 m = g_list_append(m, act);
1897 if (js->googletalk) {
1898 act = purple_action_menu_new(_("Initiate _Chat"),
1899 PURPLE_CALLBACK(google_buddy_node_chat),
1900 NULL, NULL);
1901 m = g_list_append(m, act);
1905 * This if-condition implements parts of XEP-0100: Gateway Interaction
1907 * According to stpeter, there is no way to know if a jid on the roster is a gateway without sending a disco#info.
1908 * However, since the gateway might appear offline to us, we cannot get that information. Therefore, I just assume
1909 * that gateways on the roster can be identified by having no '@' in their jid. This is a faily safe assumption, since
1910 * people don't tend to have a server or other service there.
1912 * TODO: Use disco#info...
1914 if (strchr(name, '@') == NULL) {
1915 act = purple_action_menu_new(_("Log In"),
1916 PURPLE_CALLBACK(jabber_buddy_login),
1917 NULL, NULL);
1918 m = g_list_append(m, act);
1919 act = purple_action_menu_new(_("Log Out"),
1920 PURPLE_CALLBACK(jabber_buddy_logout),
1921 NULL, NULL);
1922 m = g_list_append(m, act);
1925 /* add all ad hoc commands to the action menu */
1926 for(jbrs = jb->resources; jbrs; jbrs = g_list_next(jbrs)) {
1927 JabberBuddyResource *jbr = jbrs->data;
1928 GList *commands;
1929 if (!jbr->commands)
1930 continue;
1931 for(commands = jbr->commands; commands; commands = g_list_next(commands)) {
1932 JabberAdHocCommands *cmd = commands->data;
1933 act = purple_action_menu_new(cmd->name, PURPLE_CALLBACK(jabber_adhoc_execute_action), cmd, NULL);
1934 m = g_list_append(m, act);
1938 return m;
1941 GList *
1942 jabber_blist_node_menu(PurpleBlistNode *node)
1944 if(PURPLE_IS_BUDDY(node)) {
1945 return jabber_buddy_menu((PurpleBuddy *) node);
1946 } else {
1947 return NULL;
1952 static void user_search_result_add_buddy_cb(PurpleConnection *gc, GList *row, void *user_data)
1954 /* XXX find out the jid */
1955 purple_blist_request_add_buddy(purple_connection_get_account(gc),
1956 g_list_nth_data(row, 0), NULL, NULL);
1959 static void user_search_result_cb(JabberStream *js, const char *from,
1960 JabberIqType type, const char *id,
1961 PurpleXmlNode *packet, gpointer data)
1963 PurpleNotifySearchResults *results;
1964 PurpleNotifySearchColumn *column;
1965 PurpleXmlNode *x, *query, *item, *field;
1967 /* XXX error checking? */
1968 if(!(query = purple_xmlnode_get_child(packet, "query")))
1969 return;
1971 results = purple_notify_searchresults_new();
1972 if((x = purple_xmlnode_get_child_with_namespace(query, "x", "jabber:x:data"))) {
1973 PurpleXmlNode *reported;
1974 GSList *column_vars = NULL;
1976 purple_debug_info("jabber", "new-skool\n");
1978 if((reported = purple_xmlnode_get_child(x, "reported"))) {
1979 PurpleXmlNode *field = purple_xmlnode_get_child(reported, "field");
1980 while(field) {
1981 const char *var = purple_xmlnode_get_attrib(field, "var");
1982 const char *label = purple_xmlnode_get_attrib(field, "label");
1983 if(var) {
1984 column = purple_notify_searchresults_column_new(label ? label : var);
1985 purple_notify_searchresults_column_add(results, column);
1986 column_vars = g_slist_append(column_vars, (char *)var);
1988 field = purple_xmlnode_get_next_twin(field);
1992 item = purple_xmlnode_get_child(x, "item");
1993 while(item) {
1994 GList *row = NULL;
1995 GSList *l;
1996 PurpleXmlNode *valuenode;
1997 const char *var;
1999 for (l = column_vars; l != NULL; l = l->next) {
2001 * Build a row containing the strings that correspond
2002 * to each column of the search results.
2004 for (field = purple_xmlnode_get_child(item, "field");
2005 field != NULL;
2006 field = purple_xmlnode_get_next_twin(field))
2008 if ((var = purple_xmlnode_get_attrib(field, "var")) &&
2009 purple_strequal(var, l->data) &&
2010 (valuenode = purple_xmlnode_get_child(field, "value")))
2012 char *value = purple_xmlnode_get_data(valuenode);
2013 row = g_list_append(row, value);
2014 break;
2017 if (field == NULL)
2018 /* No data for this column */
2019 row = g_list_append(row, NULL);
2021 purple_notify_searchresults_row_add(results, row);
2022 item = purple_xmlnode_get_next_twin(item);
2025 g_slist_free(column_vars);
2026 } else {
2027 /* old skool */
2028 purple_debug_info("jabber", "old-skool\n");
2030 column = purple_notify_searchresults_column_new(_("JID"));
2031 purple_notify_searchresults_column_add(results, column);
2032 column = purple_notify_searchresults_column_new(_("First Name"));
2033 purple_notify_searchresults_column_add(results, column);
2034 column = purple_notify_searchresults_column_new(_("Last Name"));
2035 purple_notify_searchresults_column_add(results, column);
2036 column = purple_notify_searchresults_column_new(_("Nickname"));
2037 purple_notify_searchresults_column_add(results, column);
2038 column = purple_notify_searchresults_column_new(_("Email"));
2039 purple_notify_searchresults_column_add(results, column);
2041 for(item = purple_xmlnode_get_child(query, "item"); item; item = purple_xmlnode_get_next_twin(item)) {
2042 const char *jid;
2043 PurpleXmlNode *node;
2044 GList *row = NULL;
2046 if(!(jid = purple_xmlnode_get_attrib(item, "jid")))
2047 continue;
2049 row = g_list_append(row, g_strdup(jid));
2050 node = purple_xmlnode_get_child(item, "first");
2051 row = g_list_append(row, node ? purple_xmlnode_get_data(node) : NULL);
2052 node = purple_xmlnode_get_child(item, "last");
2053 row = g_list_append(row, node ? purple_xmlnode_get_data(node) : NULL);
2054 node = purple_xmlnode_get_child(item, "nick");
2055 row = g_list_append(row, node ? purple_xmlnode_get_data(node) : NULL);
2056 node = purple_xmlnode_get_child(item, "email");
2057 row = g_list_append(row, node ? purple_xmlnode_get_data(node) : NULL);
2058 purple_debug_info("jabber", "row=%p\n", row);
2059 purple_notify_searchresults_row_add(results, row);
2063 purple_notify_searchresults_button_add(results, PURPLE_NOTIFY_BUTTON_ADD,
2064 user_search_result_add_buddy_cb);
2066 purple_notify_searchresults(js->gc, NULL, NULL, _("The following are the results of your search"), results, NULL, NULL);
2069 static void user_search_x_data_cb(JabberStream *js, PurpleXmlNode *result, gpointer data)
2071 PurpleXmlNode *query;
2072 JabberIq *iq;
2073 char *dir_server = data;
2074 const char *type;
2076 /* if they've cancelled the search, we're
2077 * just going to get an error if we send
2078 * a cancel, so skip it */
2079 type = purple_xmlnode_get_attrib(result, "type");
2080 if(purple_strequal(type, "cancel")) {
2081 g_free(dir_server);
2082 return;
2085 iq = jabber_iq_new_query(js, JABBER_IQ_SET, "jabber:iq:search");
2086 query = purple_xmlnode_get_child(iq->node, "query");
2088 purple_xmlnode_insert_child(query, result);
2090 jabber_iq_set_callback(iq, user_search_result_cb, NULL);
2091 purple_xmlnode_set_attrib(iq->node, "to", dir_server);
2092 jabber_iq_send(iq);
2093 g_free(dir_server);
2096 struct user_search_info {
2097 JabberStream *js;
2098 char *directory_server;
2101 static void user_search_cancel_cb(struct user_search_info *usi, PurpleRequestFields *fields)
2103 g_free(usi->directory_server);
2104 g_free(usi);
2107 static void user_search_cb(struct user_search_info *usi, PurpleRequestFields *fields)
2109 JabberStream *js = usi->js;
2110 JabberIq *iq;
2111 PurpleXmlNode *query;
2112 GList *groups, *flds;
2114 iq = jabber_iq_new_query(js, JABBER_IQ_SET, "jabber:iq:search");
2115 query = purple_xmlnode_get_child(iq->node, "query");
2117 for(groups = purple_request_fields_get_groups(fields); groups; groups = groups->next) {
2118 for(flds = purple_request_field_group_get_fields(groups->data);
2119 flds; flds = flds->next) {
2120 PurpleRequestField *field = flds->data;
2121 const char *id = purple_request_field_get_id(field);
2122 const char *value = purple_request_field_string_get_value(field);
2124 if(value && (purple_strequal(id, "first") || purple_strequal(id, "last") || purple_strequal(id, "nick") || purple_strequal(id, "email"))) {
2125 PurpleXmlNode *y = purple_xmlnode_new_child(query, id);
2126 purple_xmlnode_insert_data(y, value, -1);
2131 jabber_iq_set_callback(iq, user_search_result_cb, NULL);
2132 purple_xmlnode_set_attrib(iq->node, "to", usi->directory_server);
2133 jabber_iq_send(iq);
2135 g_free(usi->directory_server);
2136 g_free(usi);
2139 static void user_search_fields_result_cb(JabberStream *js, const char *from,
2140 JabberIqType type, const char *id,
2141 PurpleXmlNode *packet, gpointer data)
2143 PurpleXmlNode *query, *x;
2145 if (!from)
2146 return;
2148 if (type == JABBER_IQ_ERROR) {
2149 char *msg = jabber_parse_error(js, packet, NULL);
2151 if(!msg)
2152 msg = g_strdup(_("Unknown error"));
2154 purple_notify_error(js->gc, _("Directory Query Failed"),
2155 _("Could not query the directory server."), msg,
2156 purple_request_cpar_from_connection(js->gc));
2157 g_free(msg);
2159 return;
2163 if(!(query = purple_xmlnode_get_child(packet, "query")))
2164 return;
2166 if((x = purple_xmlnode_get_child_with_namespace(query, "x", "jabber:x:data"))) {
2167 jabber_x_data_request(js, x, user_search_x_data_cb, g_strdup(from));
2168 return;
2169 } else {
2170 struct user_search_info *usi;
2171 PurpleXmlNode *instnode;
2172 char *instructions = NULL;
2173 PurpleRequestFields *fields;
2174 PurpleRequestFieldGroup *group;
2175 PurpleRequestField *field;
2177 /* old skool */
2178 fields = purple_request_fields_new();
2179 group = purple_request_field_group_new(NULL);
2180 purple_request_fields_add_group(fields, group);
2182 if((instnode = purple_xmlnode_get_child(query, "instructions")))
2184 char *tmp = purple_xmlnode_get_data(instnode);
2186 if(tmp)
2188 /* Try to translate the message (see static message
2189 list in jabber_user_dir_comments[]) */
2190 instructions = g_strdup_printf(_("Server Instructions: %s"), _(tmp));
2191 g_free(tmp);
2195 if(!instructions)
2197 instructions = g_strdup(_("Fill in one or more fields to search "
2198 "for any matching XMPP users."));
2201 if(purple_xmlnode_get_child(query, "first")) {
2202 field = purple_request_field_string_new("first", _("First Name"),
2203 NULL, FALSE);
2204 purple_request_field_group_add_field(group, field);
2206 if(purple_xmlnode_get_child(query, "last")) {
2207 field = purple_request_field_string_new("last", _("Last Name"),
2208 NULL, FALSE);
2209 purple_request_field_group_add_field(group, field);
2211 if(purple_xmlnode_get_child(query, "nick")) {
2212 field = purple_request_field_string_new("nick", _("Nickname"),
2213 NULL, FALSE);
2214 purple_request_field_group_add_field(group, field);
2216 if(purple_xmlnode_get_child(query, "email")) {
2217 field = purple_request_field_string_new("email", _("Email Address"),
2218 NULL, FALSE);
2219 purple_request_field_group_add_field(group, field);
2222 usi = g_new0(struct user_search_info, 1);
2223 usi->js = js;
2224 usi->directory_server = g_strdup(from);
2226 purple_request_fields(js->gc, _("Search for XMPP users"),
2227 _("Search for XMPP users"), instructions, fields,
2228 _("Search"), G_CALLBACK(user_search_cb),
2229 _("Cancel"), G_CALLBACK(user_search_cancel_cb),
2230 purple_request_cpar_from_connection(js->gc),
2231 usi);
2233 g_free(instructions);
2237 void jabber_user_search(JabberStream *js, const char *directory)
2239 JabberIq *iq;
2241 /* XXX: should probably better validate the directory we're given */
2242 if(!directory || !*directory) {
2243 purple_notify_error(js->gc, _("Invalid Directory"),
2244 _("Invalid Directory"), NULL,
2245 purple_request_cpar_from_connection(js->gc));
2246 return;
2249 /* If the value provided isn't the disco#info default, persist it. Otherwise,
2250 make sure we aren't persisting an old value */
2251 if(js->user_directories && js->user_directories->data &&
2252 purple_strequal(directory, js->user_directories->data)) {
2253 purple_account_set_string(purple_connection_get_account(js->gc), "user_directory", "");
2255 else {
2256 purple_account_set_string(purple_connection_get_account(js->gc), "user_directory", directory);
2259 iq = jabber_iq_new_query(js, JABBER_IQ_GET, "jabber:iq:search");
2260 purple_xmlnode_set_attrib(iq->node, "to", directory);
2262 jabber_iq_set_callback(iq, user_search_fields_result_cb, NULL);
2264 jabber_iq_send(iq);
2267 void jabber_user_search_begin(PurpleProtocolAction *action)
2269 PurpleConnection *gc = (PurpleConnection *) action->connection;
2270 JabberStream *js = purple_connection_get_protocol_data(gc);
2271 const char *def_val = purple_account_get_string(purple_connection_get_account(js->gc), "user_directory", "");
2272 if(!*def_val && js->user_directories)
2273 def_val = js->user_directories->data;
2275 purple_request_input(gc, _("Enter a User Directory"), _("Enter a User Directory"),
2276 _("Select a user directory to search"),
2277 def_val,
2278 FALSE, FALSE, NULL,
2279 _("Search Directory"), PURPLE_CALLBACK(jabber_user_search),
2280 _("Cancel"), NULL,
2281 NULL, js);
2284 gboolean
2285 jabber_resource_know_capabilities(const JabberBuddyResource *jbr)
2287 return jbr->caps.info != NULL;
2290 gboolean
2291 jabber_resource_has_capability(const JabberBuddyResource *jbr, const gchar *cap)
2293 const GList *node = NULL;
2294 const JabberCapsNodeExts *exts;
2296 if (!jbr->caps.info) {
2297 purple_debug_info("jabber",
2298 "Unable to find caps: nothing known about buddy\n");
2299 return FALSE;
2302 node = g_list_find_custom(jbr->caps.info->features, cap, (GCompareFunc)strcmp);
2303 if (!node && jbr->caps.exts && jbr->caps.info->exts) {
2304 const GList *ext;
2305 exts = jbr->caps.info->exts;
2306 /* Walk through all the enabled caps, checking each list for the cap.
2307 * Don't check it twice, though. */
2308 for (ext = jbr->caps.exts; ext && !node; ext = ext->next) {
2309 GList *features = g_hash_table_lookup(exts->exts, ext->data);
2310 if (features)
2311 node = g_list_find_custom(features, cap, (GCompareFunc)strcmp);
2315 return (node != NULL);
2318 gboolean
2319 jabber_buddy_has_capability(const JabberBuddy *jb, const gchar *cap)
2321 JabberBuddyResource *jbr = jabber_buddy_find_resource((JabberBuddy*)jb, NULL);
2323 if (!jbr) {
2324 purple_debug_info("jabber",
2325 "Unable to find caps: buddy might be offline\n");
2326 return FALSE;
2329 return jabber_resource_has_capability(jbr, cap);
2332 const gchar *
2333 jabber_resource_get_identity_category_type(const JabberBuddyResource *jbr,
2334 const gchar *category)
2336 const GList *iter = NULL;
2338 if (jbr->caps.info) {
2339 for (iter = jbr->caps.info->identities ; iter ; iter = g_list_next(iter)) {
2340 const JabberIdentity *identity =
2341 (JabberIdentity *) iter->data;
2343 if (purple_strequal(identity->category, category)) {
2344 return identity->type;
2349 return NULL;