Mercurial > pidgin.yaz
diff libpurple/protocols/jabber/buddy.c @ 15374:5fe8042783c1
Rename gtk/ and libgaim/ to pidgin/ and libpurple/
author | Sean Egan <seanegan@gmail.com> |
---|---|
date | Sat, 20 Jan 2007 02:32:10 +0000 |
parents | |
children | 0b16a4aa4e2b |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/protocols/jabber/buddy.c Sat Jan 20 02:32:10 2007 +0000 @@ -0,0 +1,1799 @@ +/* + * gaim - Jabber Protocol Plugin + * + * Copyright (C) 2003, Nathan Walp <faceprint@faceprint.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ +#include "internal.h" +#include "cipher.h" +#include "debug.h" +#include "imgstore.h" +#include "prpl.h" +#include "notify.h" +#include "request.h" +#include "util.h" +#include "xmlnode.h" + +#include "buddy.h" +#include "chat.h" +#include "jabber.h" +#include "iq.h" +#include "presence.h" +#include "xdata.h" + +typedef struct { + long idle_seconds; +} JabberBuddyInfoResource; + +typedef struct { + JabberStream *js; + JabberBuddy *jb; + char *jid; + GSList *ids; + GHashTable *resources; + int timeout_handle; + char *vcard_text; + GSList *vcard_imgids; +} JabberBuddyInfo; + +void jabber_buddy_free(JabberBuddy *jb) +{ + g_return_if_fail(jb != NULL); + + if(jb->error_msg) + g_free(jb->error_msg); + while(jb->resources) + jabber_buddy_resource_free(jb->resources->data); + + g_free(jb); +} + +JabberBuddy *jabber_buddy_find(JabberStream *js, const char *name, + gboolean create) +{ + JabberBuddy *jb; + const char *realname; + + if (js->buddies == NULL) + return NULL; + + if(!(realname = jabber_normalize(js->gc->account, name))) + return NULL; + + jb = g_hash_table_lookup(js->buddies, realname); + + if(!jb && create) { + jb = g_new0(JabberBuddy, 1); + g_hash_table_insert(js->buddies, g_strdup(realname), jb); + } + + return jb; +} + + +JabberBuddyResource *jabber_buddy_find_resource(JabberBuddy *jb, + const char *resource) +{ + JabberBuddyResource *jbr = NULL; + GList *l; + + if(!jb) + return NULL; + + for(l = jb->resources; l; l = l->next) + { + if(!jbr && !resource) { + jbr = l->data; + } else if(!resource) { + if(((JabberBuddyResource *)l->data)->priority >= jbr->priority) + jbr = l->data; + } else if(((JabberBuddyResource *)l->data)->name) { + if(!strcmp(((JabberBuddyResource *)l->data)->name, resource)) { + jbr = l->data; + break; + } + } + } + + return jbr; +} + +JabberBuddyResource *jabber_buddy_track_resource(JabberBuddy *jb, const char *resource, + int priority, JabberBuddyState state, const char *status) +{ + JabberBuddyResource *jbr = jabber_buddy_find_resource(jb, resource); + + if(!jbr) { + jbr = g_new0(JabberBuddyResource, 1); + jbr->jb = jb; + jbr->name = g_strdup(resource); + jbr->capabilities = JABBER_CAP_XHTML; + jb->resources = g_list_append(jb->resources, jbr); + } + jbr->priority = priority; + jbr->state = state; + if(jbr->status) + g_free(jbr->status); + if (status) + jbr->status = g_markup_escape_text(status, -1); + else + jbr->status = NULL; + + return jbr; +} + +void jabber_buddy_resource_free(JabberBuddyResource *jbr) +{ + g_return_if_fail(jbr != NULL); + + jbr->jb->resources = g_list_remove(jbr->jb->resources, jbr); + + g_free(jbr->name); + g_free(jbr->status); + g_free(jbr->thread_id); + g_free(jbr->client.name); + g_free(jbr->client.version); + g_free(jbr->client.os); + g_free(jbr); +} + +void jabber_buddy_remove_resource(JabberBuddy *jb, const char *resource) +{ + JabberBuddyResource *jbr = jabber_buddy_find_resource(jb, resource); + + if(!jbr) + return; + + jabber_buddy_resource_free(jbr); +} + +const char *jabber_buddy_get_status_msg(JabberBuddy *jb) +{ + JabberBuddyResource *jbr; + + if(!jb) + return NULL; + + jbr = jabber_buddy_find_resource(jb, NULL); + + if(!jbr) + return NULL; + + return jbr->status; +} + +/******* + * This is the old vCard stuff taken from the old prpl. vCards, by definition + * are a temporary thing until jabber can get its act together and come up + * with a format for user information, hence the namespace of 'vcard-temp' + * + * Since I don't feel like putting that much work into something that's + * _supposed_ to go away, i'm going to just copy the kludgy old code here, + * and make it purdy when jabber comes up with a standards-track JEP to + * replace vcard-temp + * --Nathan + *******/ + +/*---------------------------------------*/ +/* Jabber "set info" (vCard) support */ +/*---------------------------------------*/ + +/* + * V-Card format: + * + * <vCard prodid='' version='' xmlns=''> + * <FN></FN> + * <N> + * <FAMILY/> + * <GIVEN/> + * </N> + * <NICKNAME/> + * <URL/> + * <ADR> + * <STREET/> + * <EXTADD/> + * <LOCALITY/> + * <REGION/> + * <PCODE/> + * <COUNTRY/> + * </ADR> + * <TEL/> + * <EMAIL/> + * <ORG> + * <ORGNAME/> + * <ORGUNIT/> + * </ORG> + * <TITLE/> + * <ROLE/> + * <DESC/> + * <BDAY/> + * </vCard> + * + * See also: + * + * http://docs.jabber.org/proto/html/vcard-temp.html + * http://www.vcard-xml.org/dtd/vCard-XML-v2-20010520.dtd + */ + +/* + * Cross-reference user-friendly V-Card entry labels to vCard XML tags + * and attributes. + * + * Order is (or should be) unimportant. For example: we have no way of + * knowing in what order real data will arrive. + * + * Format: Label, Pre-set text, "visible" flag, "editable" flag, XML tag + * name, XML tag's parent tag "path" (relative to vCard node). + * + * List is terminated by a NULL label pointer. + * + * Entries with no label text, but with XML tag and parent tag + * entries, are used by V-Card XML construction routines to + * "automagically" construct the appropriate XML node tree. + * + * Thoughts on future direction/expansion + * + * This is a "simple" vCard. + * + * It is possible for nodes other than the "vCard" node to have + * attributes. Should that prove necessary/desirable, add an + * "attributes" pointer to the vcard_template struct, create the + * necessary tag_attr structs, and add 'em to the vcard_dflt_data + * array. + * + * The above changes will (obviously) require changes to the vCard + * construction routines. + */ + +struct vcard_template { + char *label; /* label text pointer */ + char *text; /* entry text pointer */ + int visible; /* should entry field be "visible?" */ + int editable; /* should entry field be editable? */ + char *tag; /* tag text */ + char *ptag; /* parent tag "path" text */ + char *url; /* vCard display format if URL */ +} vcard_template_data[] = { + {N_("Full Name"), NULL, TRUE, TRUE, "FN", NULL, NULL}, + {N_("Family Name"), NULL, TRUE, TRUE, "FAMILY", "N", NULL}, + {N_("Given Name"), NULL, TRUE, TRUE, "GIVEN", "N", NULL}, + {N_("Nickname"), NULL, TRUE, TRUE, "NICKNAME", NULL, NULL}, + {N_("URL"), NULL, TRUE, TRUE, "URL", NULL, "<A HREF=\"%s\">%s</A>"}, + {N_("Street Address"), NULL, TRUE, TRUE, "STREET", "ADR", NULL}, + {N_("Extended Address"), NULL, TRUE, TRUE, "EXTADD", "ADR", NULL}, + {N_("Locality"), NULL, TRUE, TRUE, "LOCALITY", "ADR", NULL}, + {N_("Region"), NULL, TRUE, TRUE, "REGION", "ADR", NULL}, + {N_("Postal Code"), NULL, TRUE, TRUE, "PCODE", "ADR", NULL}, + {N_("Country"), NULL, TRUE, TRUE, "CTRY", "ADR", NULL}, + {N_("Telephone"), NULL, TRUE, TRUE, "NUMBER", "TEL", NULL}, + {N_("E-Mail"), NULL, TRUE, TRUE, "USERID", "EMAIL", "<A HREF=\"mailto:%s\">%s</A>"}, + {N_("Organization Name"), NULL, TRUE, TRUE, "ORGNAME", "ORG", NULL}, + {N_("Organization Unit"), NULL, TRUE, TRUE, "ORGUNIT", "ORG", NULL}, + {N_("Title"), NULL, TRUE, TRUE, "TITLE", NULL, NULL}, + {N_("Role"), NULL, TRUE, TRUE, "ROLE", NULL, NULL}, + {N_("Birthday"), NULL, TRUE, TRUE, "BDAY", NULL, NULL}, + {N_("Description"), NULL, TRUE, TRUE, "DESC", NULL, NULL}, + {"", NULL, TRUE, TRUE, "N", NULL, NULL}, + {"", NULL, TRUE, TRUE, "ADR", NULL, NULL}, + {"", NULL, TRUE, TRUE, "ORG", NULL, NULL}, + {NULL, NULL, 0, 0, NULL, NULL, NULL} +}; + +/* + * The "vCard" tag's attribute list... + */ +struct tag_attr { + char *attr; + char *value; +} vcard_tag_attr_list[] = { + {"prodid", "-//HandGen//NONSGML vGen v1.0//EN"}, + {"version", "2.0", }, + {"xmlns", "vcard-temp", }, + {NULL, NULL}, +}; + + +/* + * Insert a tag node into an xmlnode tree, recursively inserting parent tag + * nodes as necessary + * + * Returns pointer to inserted node + * + * Note to hackers: this code is designed to be re-entrant (it's recursive--it + * calls itself), so don't put any "static"s in here! + */ +static xmlnode *insert_tag_to_parent_tag(xmlnode *start, const char *parent_tag, const char *new_tag) +{ + xmlnode *x = NULL; + + /* + * If the parent tag wasn't specified, see if we can get it + * from the vCard template struct. + */ + if(parent_tag == NULL) { + struct vcard_template *vc_tp = vcard_template_data; + + while(vc_tp->label != NULL) { + if(strcmp(vc_tp->tag, new_tag) == 0) { + parent_tag = vc_tp->ptag; + break; + } + ++vc_tp; + } + } + + /* + * If we have a parent tag... + */ + if(parent_tag != NULL ) { + /* + * Try to get the parent node for a tag + */ + if((x = xmlnode_get_child(start, parent_tag)) == NULL) { + /* + * Descend? + */ + char *grand_parent = g_strdup(parent_tag); + char *parent; + + if((parent = strrchr(grand_parent, '/')) != NULL) { + *(parent++) = '\0'; + x = insert_tag_to_parent_tag(start, grand_parent, parent); + } else { + x = xmlnode_new_child(start, grand_parent); + } + g_free(grand_parent); + } else { + /* + * We found *something* to be the parent node. + * Note: may be the "root" node! + */ + xmlnode *y; + if((y = xmlnode_get_child(x, new_tag)) != NULL) { + return(y); + } + } + } + + /* + * insert the new tag into its parent node + */ + return(xmlnode_new_child((x == NULL? start : x), new_tag)); +} + +/* + * Send vCard info to Jabber server + */ +void jabber_set_info(GaimConnection *gc, const char *info) +{ + JabberIq *iq; + JabberStream *js = gc->proto_data; + xmlnode *vc_node; + char *avatar_file = NULL; + struct tag_attr *tag_attr; + + g_free(js->avatar_hash); + js->avatar_hash = NULL; + + /* + * Send only if there's actually any *information* to send + */ + vc_node = info ? xmlnode_from_str(info, -1) : NULL; + avatar_file = gaim_buddy_icons_get_full_path(gaim_account_get_buddy_icon(gc->account)); + + if(!vc_node) { + vc_node = xmlnode_new("vCard"); + for(tag_attr = vcard_tag_attr_list; tag_attr->attr != NULL; ++tag_attr) + xmlnode_set_attrib(vc_node, tag_attr->attr, tag_attr->value); + } + + if (vc_node->name && + !g_ascii_strncasecmp(vc_node->name, "vCard", 5)) { + GError *error = NULL; + gchar *avatar_data_tmp; + guchar *avatar_data; + gsize avatar_len; + + if(avatar_file && g_file_get_contents(avatar_file, &avatar_data_tmp, &avatar_len, &error)) { + xmlnode *photo, *binval; + gchar *enc; + int i; + unsigned char hashval[20]; + char *p, hash[41]; + + avatar_data = (guchar *) avatar_data_tmp; + photo = xmlnode_new_child(vc_node, "PHOTO"); + binval = xmlnode_new_child(photo, "BINVAL"); + enc = gaim_base64_encode(avatar_data, avatar_len); + + gaim_cipher_digest_region("sha1", (guchar *)avatar_data, + avatar_len, sizeof(hashval), + hashval, NULL); + + p = hash; + for(i=0; i<20; i++, p+=2) + snprintf(p, 3, "%02x", hashval[i]); + js->avatar_hash = g_strdup(hash); + + xmlnode_insert_data(binval, enc, -1); + g_free(enc); + g_free(avatar_data); + } else if (error != NULL) { + g_error_free(error); + } + g_free(avatar_file); + + iq = jabber_iq_new(js, JABBER_IQ_SET); + xmlnode_insert_child(iq->node, vc_node); + jabber_iq_send(iq); + } else { + xmlnode_free(vc_node); + } +} + +void jabber_set_buddy_icon(GaimConnection *gc, const char *iconfile) +{ + GaimPresence *gpresence; + GaimStatus *status; + + jabber_set_info(gc, gaim_account_get_user_info(gc->account)); + + gpresence = gaim_account_get_presence(gc->account); + status = gaim_presence_get_active_status(gpresence); + jabber_presence_send(gc->account, status); +} + +/* + * This is the callback from the "ok clicked" for "set vCard" + * + * Formats GSList data into XML-encoded string and returns a pointer + * to said string. + * + * g_free()'ing the returned string space is the responsibility of + * the caller. + */ +static void +jabber_format_info(GaimConnection *gc, GaimRequestFields *fields) +{ + xmlnode *vc_node; + GaimRequestField *field; + const char *text; + char *p; + const struct vcard_template *vc_tp; + struct tag_attr *tag_attr; + + vc_node = xmlnode_new("vCard"); + + for(tag_attr = vcard_tag_attr_list; tag_attr->attr != NULL; ++tag_attr) + xmlnode_set_attrib(vc_node, tag_attr->attr, tag_attr->value); + + for (vc_tp = vcard_template_data; vc_tp->label != NULL; vc_tp++) { + if (*vc_tp->label == '\0') + continue; + + field = gaim_request_fields_get_field(fields, vc_tp->tag); + text = gaim_request_field_string_get_value(field); + + + if (text != NULL && *text != '\0') { + xmlnode *xp; + + gaim_debug(GAIM_DEBUG_INFO, "jabber", + "Setting %s to '%s'\n", vc_tp->tag, text); + + if ((xp = insert_tag_to_parent_tag(vc_node, + NULL, vc_tp->tag)) != NULL) { + + xmlnode_insert_data(xp, text, -1); + } + } + } + + p = xmlnode_to_str(vc_node, NULL); + xmlnode_free(vc_node); + + gaim_account_set_user_info(gaim_connection_get_account(gc), p); + serv_set_info(gc, p); + + g_free(p); +} + +/* + * This gets executed by the proto action + * + * Creates a new GaimRequestFields struct, gets the XML-formatted user_info + * string (if any) into GSLists for the (multi-entry) edit dialog and + * calls the set_vcard dialog. + */ +void jabber_setup_set_info(GaimPluginAction *action) +{ + GaimConnection *gc = (GaimConnection *) action->context; + GaimRequestFields *fields; + GaimRequestFieldGroup *group; + GaimRequestField *field; + const struct vcard_template *vc_tp; + const char *user_info; + char *cdata = NULL; + xmlnode *x_vc_data = NULL; + + fields = gaim_request_fields_new(); + group = gaim_request_field_group_new(NULL); + gaim_request_fields_add_group(fields, group); + + /* + * Get existing, XML-formatted, user info + */ + if((user_info = gaim_account_get_user_info(gc->account)) != NULL) + x_vc_data = xmlnode_from_str(user_info, -1); + + /* + * Set up GSLists for edit with labels from "template," data from user info + */ + for(vc_tp = vcard_template_data; vc_tp->label != NULL; ++vc_tp) { + xmlnode *data_node; + if((vc_tp->label)[0] == '\0') + continue; + + if (x_vc_data != NULL) { + if(vc_tp->ptag == NULL) { + data_node = xmlnode_get_child(x_vc_data, vc_tp->tag); + } else { + gchar *tag = g_strdup_printf("%s/%s", vc_tp->ptag, vc_tp->tag); + data_node = xmlnode_get_child(x_vc_data, tag); + g_free(tag); + } + if(data_node) + cdata = xmlnode_get_data(data_node); + } + + if(strcmp(vc_tp->tag, "DESC") == 0) { + field = gaim_request_field_string_new(vc_tp->tag, + _(vc_tp->label), cdata, + TRUE); + } else { + field = gaim_request_field_string_new(vc_tp->tag, + _(vc_tp->label), cdata, + FALSE); + } + + g_free(cdata); + cdata = NULL; + + gaim_request_field_group_add_field(group, field); + } + + if(x_vc_data != NULL) + xmlnode_free(x_vc_data); + + gaim_request_fields(gc, _("Edit Jabber vCard"), + _("Edit Jabber vCard"), + _("All items below are optional. Enter only the " + "information with which you feel comfortable."), + fields, + _("Save"), G_CALLBACK(jabber_format_info), + _("Cancel"), NULL, + gc); +} + +/*---------------------------------------*/ +/* End Jabber "set info" (vCard) support */ +/*---------------------------------------*/ + +/****** + * end of that ancient crap that needs to die + ******/ + +static void jabber_buddy_info_destroy(JabberBuddyInfo *jbi) +{ + /* Remove the timeout, which would otherwise trigger jabber_buddy_get_info_timeout() */ + if (jbi->timeout_handle > 0) + gaim_timeout_remove(jbi->timeout_handle); + + g_free(jbi->jid); + g_hash_table_destroy(jbi->resources); + g_free(jbi->vcard_text); + g_free(jbi); +} + +static void jabber_buddy_info_show_if_ready(JabberBuddyInfo *jbi) +{ + char *resource_name, *tmp; + JabberBuddyResource *jbr; + JabberBuddyInfoResource *jbir = NULL; + GList *resources; + GaimNotifyUserInfo *user_info; + + /* not yet */ + if(jbi->ids) + return; + + user_info = gaim_notify_user_info_new(); + resource_name = jabber_get_resource(jbi->jid); + + if(resource_name) { + jbr = jabber_buddy_find_resource(jbi->jb, resource_name); + jbir = g_hash_table_lookup(jbi->resources, resource_name); + if(jbr) { + char *purdy = NULL; + if(jbr->status) + purdy = gaim_strdup_withhtml(jbr->status); + tmp = g_strdup_printf("%s%s%s", jabber_buddy_state_get_name(jbr->state), + (purdy ? ": " : ""), + (purdy ? purdy : "")); + gaim_notify_user_info_add_pair(user_info, _("Status"), tmp); + g_free(tmp); + g_free(purdy); + } else { + gaim_notify_user_info_add_pair(user_info, _("Status"), _("Unknown")); + } + if(jbir) { + if(jbir->idle_seconds > 0) { + gaim_notify_user_info_add_pair(user_info, _("Idle"), gaim_str_seconds_to_string(jbir->idle_seconds)); + } + } + if(jbr && jbr->client.name) { + tmp = g_strdup_printf("%s%s%s", jbr->client.name, + (jbr->client.version ? " " : ""), + (jbr->client.version ? jbr->client.version : "")); + gaim_notify_user_info_add_pair(user_info, _("Client"), tmp); + g_free(tmp); + + if(jbr->client.os) { + gaim_notify_user_info_add_pair(user_info, _("Operating System"), jbr->client.os); + } + } + } else { + for(resources = jbi->jb->resources; resources; resources = resources->next) { + char *purdy = NULL; + jbr = resources->data; + if(jbr->status) + purdy = gaim_strdup_withhtml(jbr->status); + if(jbr->name) + gaim_notify_user_info_add_pair(user_info, _("Resource"), jbr->name); + tmp = g_strdup_printf("%d", jbr->priority); + gaim_notify_user_info_add_pair(user_info, _("Priority"), tmp); + g_free(tmp); + + tmp = g_strdup_printf("%s%s%s", jabber_buddy_state_get_name(jbr->state), + (purdy ? ": " : ""), + (purdy ? purdy : "")); + gaim_notify_user_info_add_pair(user_info, _("Status"), tmp); + g_free(tmp); + g_free(purdy); + + if(jbr->name) + jbir = g_hash_table_lookup(jbi->resources, jbr->name); + + if(jbir) { + if(jbir->idle_seconds > 0) { + gaim_notify_user_info_add_pair(user_info, _("Idle"), gaim_str_seconds_to_string(jbir->idle_seconds)); + } + } + if(jbr && jbr->client.name) { + tmp = g_strdup_printf("%s%s%s", jbr->client.name, + (jbr->client.version ? " " : ""), + (jbr->client.version ? jbr->client.version : "")); + gaim_notify_user_info_add_pair(user_info, + _("Client"), tmp); + g_free(tmp); + + if(jbr->client.os) { + gaim_notify_user_info_add_pair(user_info, _("Operating System"), jbr->client.os); + } + } + } + } + + g_free(resource_name); + + if (jbi->vcard_text != NULL) { + gaim_notify_user_info_add_section_break(user_info); + /* Should this have some sort of label? */ + gaim_notify_user_info_add_pair(user_info, NULL, jbi->vcard_text); + } + + gaim_notify_userinfo(jbi->js->gc, jbi->jid, user_info, NULL, NULL); + gaim_notify_user_info_destroy(user_info); + + while(jbi->vcard_imgids) { + gaim_imgstore_unref(GPOINTER_TO_INT(jbi->vcard_imgids->data)); + jbi->vcard_imgids = g_slist_delete_link(jbi->vcard_imgids, jbi->vcard_imgids); + } + + jbi->js->pending_buddy_info_requests = g_slist_remove(jbi->js->pending_buddy_info_requests, jbi); + + jabber_buddy_info_destroy(jbi); +} + +static void jabber_buddy_info_remove_id(JabberBuddyInfo *jbi, const char *id) +{ + GSList *l = jbi->ids; + + if(!id) + return; + + while(l) { + if(!strcmp(id, l->data)) { + jbi->ids = g_slist_remove(jbi->ids, l->data); + return; + } + l = l->next; + } +} + +static void jabber_vcard_parse(JabberStream *js, xmlnode *packet, gpointer data) +{ + const char *id, *from; + GString *info_text; + char *bare_jid; + char *text; + xmlnode *vcard; + GaimBuddy *b; + JabberBuddyInfo *jbi = data; + + from = xmlnode_get_attrib(packet, "from"); + id = xmlnode_get_attrib(packet, "id"); + + if(!jbi) + return; + + jabber_buddy_info_remove_id(jbi, id); + + if(!from) + return; + + if(!jabber_buddy_find(js, from, FALSE)) + return; + + /* XXX: handle the error case */ + + bare_jid = jabber_get_bare_jid(from); + + b = gaim_find_buddy(js->gc->account, bare_jid); + + info_text = g_string_new(""); + + if((vcard = xmlnode_get_child(packet, "vCard")) || + (vcard = xmlnode_get_child_with_namespace(packet, "query", "vcard-temp"))) { + xmlnode *child; + for(child = vcard->child; child; child = child->next) + { + xmlnode *child2; + + if(child->type != XMLNODE_TYPE_TAG) + continue; + + text = xmlnode_get_data(child); + if(text && !strcmp(child->name, "FN")) { + g_string_append_printf(info_text, "<b>%s:</b> %s<br/>", + _("Full Name"), text); + } else if(!strcmp(child->name, "N")) { + for(child2 = child->child; child2; child2 = child2->next) + { + char *text2; + + if(child2->type != XMLNODE_TYPE_TAG) + continue; + + text2 = xmlnode_get_data(child2); + if(text2 && !strcmp(child2->name, "FAMILY")) { + g_string_append_printf(info_text, + "<b>%s:</b> %s<br/>", + _("Family Name"), text2); + } else if(text2 && !strcmp(child2->name, "GIVEN")) { + g_string_append_printf(info_text, + "<b>%s:</b> %s<br/>", + _("Given Name"), text2); + } else if(text2 && !strcmp(child2->name, "MIDDLE")) { + g_string_append_printf(info_text, + "<b>%s:</b> %s<br/>", + _("Middle Name"), text2); + } + g_free(text2); + } + } else if(text && !strcmp(child->name, "NICKNAME")) { + serv_got_alias(js->gc, from, text); + if(b) { + gaim_blist_node_set_string((GaimBlistNode*)b, "servernick", text); + } + g_string_append_printf(info_text, "<b>%s:</b> %s<br/>", + _("Nickname"), text); + } else if(text && !strcmp(child->name, "BDAY")) { + g_string_append_printf(info_text, "<b>%s:</b> %s<br/>", + _("Birthday"), text); + } else if(!strcmp(child->name, "ADR")) { + gboolean address_line_added = FALSE; + + for(child2 = child->child; child2; child2 = child2->next) + { + char *text2; + + if(child2->type != XMLNODE_TYPE_TAG) + continue; + + text2 = xmlnode_get_data(child2); + if (text2 == NULL) + continue; + + /* We do this here so that it's not added if all the child + * elements are empty. */ + if (!address_line_added) + { + g_string_append_printf(info_text, "<b>%s:</b><br/>", + _("Address")); + address_line_added = TRUE; + } + + if(!strcmp(child2->name, "POBOX")) { + g_string_append_printf(info_text, + " <b>%s:</b> %s<br/>", + _("P.O. Box"), text2); + } else if(!strcmp(child2->name, "EXTADR")) { + g_string_append_printf(info_text, + " <b>%s:</b> %s<br/>", + _("Extended Address"), text2); + } else if(!strcmp(child2->name, "STREET")) { + g_string_append_printf(info_text, + " <b>%s:</b> %s<br/>", + _("Street Address"), text2); + } else if(!strcmp(child2->name, "LOCALITY")) { + g_string_append_printf(info_text, + " <b>%s:</b> %s<br/>", + _("Locality"), text2); + } else if(!strcmp(child2->name, "REGION")) { + g_string_append_printf(info_text, + " <b>%s:</b> %s<br/>", + _("Region"), text2); + } else if(!strcmp(child2->name, "PCODE")) { + g_string_append_printf(info_text, + " <b>%s:</b> %s<br/>", + _("Postal Code"), text2); + } else if(!strcmp(child2->name, "CTRY") + || !strcmp(child2->name, "COUNTRY")) { + g_string_append_printf(info_text, + " <b>%s:</b> %s<br/>", + _("Country"), text2); + } + g_free(text2); + } + } else if(!strcmp(child->name, "TEL")) { + char *number; + if((child2 = xmlnode_get_child(child, "NUMBER"))) { + /* show what kind of number it is */ + number = xmlnode_get_data(child2); + if(number) { + g_string_append_printf(info_text, + "<b>%s:</b> %s<br/>", _("Telephone"), number); + g_free(number); + } + } else if((number = xmlnode_get_data(child))) { + /* lots of clients (including gaim) do this, but it's + * out of spec */ + g_string_append_printf(info_text, + "<b>%s:</b> %s<br/>", _("Telephone"), number); + g_free(number); + } + } else if(!strcmp(child->name, "EMAIL")) { + char *userid; + if((child2 = xmlnode_get_child(child, "USERID"))) { + /* show what kind of email it is */ + userid = xmlnode_get_data(child2); + if(userid) { + g_string_append_printf(info_text, + "<b>%s:</b> <a href='mailto:%s'>%s</a><br/>", + _("E-Mail"), userid, userid); + g_free(userid); + } + } else if((userid = xmlnode_get_data(child))) { + /* lots of clients (including gaim) do this, but it's + * out of spec */ + g_string_append_printf(info_text, + "<b>%s:</b> <a href='mailto:%s'>%s</a><br/>", + _("E-Mail"), userid, userid); + g_free(userid); + } + } else if(!strcmp(child->name, "ORG")) { + for(child2 = child->child; child2; child2 = child2->next) + { + char *text2; + + if(child2->type != XMLNODE_TYPE_TAG) + continue; + + text2 = xmlnode_get_data(child2); + if(text2 && !strcmp(child2->name, "ORGNAME")) { + g_string_append_printf(info_text, + "<b>%s:</b> %s<br/>", + _("Organization Name"), text2); + } else if(text2 && !strcmp(child2->name, "ORGUNIT")) { + g_string_append_printf(info_text, + "<b>%s:</b> %s<br/>", + _("Organization Unit"), text2); + } + g_free(text2); + } + } else if(text && !strcmp(child->name, "TITLE")) { + g_string_append_printf(info_text, "<b>%s:</b> %s<br/>", + _("Title"), text); + } else if(text && !strcmp(child->name, "ROLE")) { + g_string_append_printf(info_text, "<b>%s:</b> %s<br/>", + _("Role"), text); + } else if(text && !strcmp(child->name, "DESC")) { + g_string_append_printf(info_text, "<b>%s:</b> %s<br/>", + _("Description"), text); + } else if(!strcmp(child->name, "PHOTO") || + !strcmp(child->name, "LOGO")) { + char *bintext = NULL; + xmlnode *binval; + + if( ((binval = xmlnode_get_child(child, "BINVAL")) && + (bintext = xmlnode_get_data(binval))) || + (bintext = xmlnode_get_data(child))) { + gsize size; + guchar *data; + int i; + unsigned char hashval[20]; + char *p, hash[41]; + gboolean photo = (strcmp(child->name, "PHOTO") == 0); + + data = gaim_base64_decode(bintext, &size); + + jbi->vcard_imgids = g_slist_prepend(jbi->vcard_imgids, GINT_TO_POINTER(gaim_imgstore_add(data, size, "logo.png"))); + g_string_append_printf(info_text, + "<b>%s:</b> <img id='%d'><br/>", + photo ? _("Photo") : _("Logo"), + GPOINTER_TO_INT(jbi->vcard_imgids->data)); + + gaim_buddy_icons_set_for_user(js->gc->account, bare_jid, + data, size); + + gaim_cipher_digest_region("sha1", (guchar *)data, size, + sizeof(hashval), hashval, NULL); + p = hash; + for(i=0; i<20; i++, p+=2) + snprintf(p, 3, "%02x", hashval[i]); + gaim_blist_node_set_string((GaimBlistNode*)b, "avatar_hash", hash); + + g_free(data); + g_free(bintext); + } + } + g_free(text); + } + } + + jbi->vcard_text = gaim_strdup_withhtml(info_text->str); + g_string_free(info_text, TRUE); + g_free(bare_jid); + + jabber_buddy_info_show_if_ready(jbi); +} + + +static void jabber_buddy_info_resource_free(gpointer data) +{ + JabberBuddyInfoResource *jbri = data; + g_free(jbri); +} + +static void jabber_version_parse(JabberStream *js, xmlnode *packet, gpointer data) +{ + JabberBuddyInfo *jbi = data; + const char *type, *id, *from; + xmlnode *query; + char *resource_name; + + g_return_if_fail(jbi != NULL); + + type = xmlnode_get_attrib(packet, "type"); + id = xmlnode_get_attrib(packet, "id"); + from = xmlnode_get_attrib(packet, "from"); + + jabber_buddy_info_remove_id(jbi, id); + + if(!from) + return; + + resource_name = jabber_get_resource(from); + + if(resource_name) { + if(type && !strcmp(type, "result")) { + if((query = xmlnode_get_child(packet, "query"))) { + JabberBuddyResource *jbr = jabber_buddy_find_resource(jbi->jb, resource_name); + if(jbr) { + xmlnode *node; + if((node = xmlnode_get_child(query, "name"))) { + jbr->client.name = xmlnode_get_data(node); + } + if((node = xmlnode_get_child(query, "version"))) { + jbr->client.version = xmlnode_get_data(node); + } + if((node = xmlnode_get_child(query, "os"))) { + jbr->client.os = xmlnode_get_data(node); + } + } + } + } + g_free(resource_name); + } + + jabber_buddy_info_show_if_ready(jbi); +} + +static void jabber_last_parse(JabberStream *js, xmlnode *packet, gpointer data) +{ + JabberBuddyInfo *jbi = data; + xmlnode *query; + char *resource_name; + const char *type, *id, *from, *seconds; + + g_return_if_fail(jbi != NULL); + + type = xmlnode_get_attrib(packet, "type"); + id = xmlnode_get_attrib(packet, "id"); + from = xmlnode_get_attrib(packet, "from"); + + jabber_buddy_info_remove_id(jbi, id); + + if(!from) + return; + + resource_name = jabber_get_resource(from); + + if(resource_name) { + if(type && !strcmp(type, "result")) { + if((query = xmlnode_get_child(packet, "query"))) { + seconds = xmlnode_get_attrib(query, "seconds"); + if(seconds) { + char *end = NULL; + long sec = strtol(seconds, &end, 10); + if(end != seconds) { + JabberBuddyInfoResource *jbir = g_hash_table_lookup(jbi->resources, resource_name); + if(jbir) { + jbir->idle_seconds = sec; + } + } + } + } + } + g_free(resource_name); + } + + jabber_buddy_info_show_if_ready(jbi); +} + +void jabber_buddy_remove_all_pending_buddy_info_requests(JabberStream *js) +{ + if (js->pending_buddy_info_requests) + { + JabberBuddyInfo *jbi; + GSList *l = js->pending_buddy_info_requests; + while (l) { + jbi = l->data; + + g_slist_free(jbi->ids); + jabber_buddy_info_destroy(jbi); + + l = l->next; + } + + g_slist_free(js->pending_buddy_info_requests); + js->pending_buddy_info_requests = NULL; + } +} + +static gboolean jabber_buddy_get_info_timeout(gpointer data) +{ + JabberBuddyInfo *jbi = data; + + /* remove the pending callbacks */ + while(jbi->ids) { + char *id = jbi->ids->data; + jabber_iq_remove_callback_by_id(jbi->js, id); + jbi->ids = g_slist_remove(jbi->ids, id); + g_free(id); + } + + jbi->js->pending_buddy_info_requests = g_slist_remove(jbi->js->pending_buddy_info_requests, jbi); + jbi->timeout_handle = 0; + + jabber_buddy_info_show_if_ready(jbi); + + return FALSE; +} + +static void jabber_buddy_get_info_for_jid(JabberStream *js, const char *jid) +{ + JabberIq *iq; + xmlnode *vcard; + GList *resources; + JabberBuddy *jb; + JabberBuddyInfo *jbi; + + jb = jabber_buddy_find(js, jid, TRUE); + + /* invalid JID */ + if(!jb) + return; + + jbi = g_new0(JabberBuddyInfo, 1); + jbi->jid = g_strdup(jid); + jbi->js = js; + jbi->jb = jb; + jbi->resources = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, jabber_buddy_info_resource_free); + + iq = jabber_iq_new(js, JABBER_IQ_GET); + + xmlnode_set_attrib(iq->node, "to", jid); + vcard = xmlnode_new_child(iq->node, "vCard"); + xmlnode_set_namespace(vcard, "vcard-temp"); + + jabber_iq_set_callback(iq, jabber_vcard_parse, jbi); + jbi->ids = g_slist_prepend(jbi->ids, g_strdup(iq->id)); + + jabber_iq_send(iq); + + for(resources = jb->resources; resources; resources = resources->next) + { + JabberBuddyResource *jbr = resources->data; + JabberBuddyInfoResource *jbir; + char *full_jid; + + if ((strchr(jid, '/') == NULL) && (jbr->name != NULL)) { + full_jid = g_strdup_printf("%s/%s", jid, jbr->name); + } else { + full_jid = g_strdup(jid); + } + + if (jbr->name != NULL) + { + jbir = g_new0(JabberBuddyInfoResource, 1); + g_hash_table_insert(jbi->resources, g_strdup(jbr->name), jbir); + } + + if(!jbr->client.name) { + iq = jabber_iq_new_query(js, JABBER_IQ_GET, "jabber:iq:version"); + xmlnode_set_attrib(iq->node, "to", full_jid); + jabber_iq_set_callback(iq, jabber_version_parse, jbi); + jbi->ids = g_slist_prepend(jbi->ids, g_strdup(iq->id)); + jabber_iq_send(iq); + } + + iq = jabber_iq_new_query(js, JABBER_IQ_GET, "jabber:iq:last"); + xmlnode_set_attrib(iq->node, "to", full_jid); + jabber_iq_set_callback(iq, jabber_last_parse, jbi); + jbi->ids = g_slist_prepend(jbi->ids, g_strdup(iq->id)); + jabber_iq_send(iq); + + g_free(full_jid); + } + + js->pending_buddy_info_requests = g_slist_prepend(js->pending_buddy_info_requests, jbi); + jbi->timeout_handle = gaim_timeout_add(30000, jabber_buddy_get_info_timeout, jbi); +} + +void jabber_buddy_get_info(GaimConnection *gc, const char *who) +{ + JabberStream *js = gc->proto_data; + char *bare_jid = jabber_get_bare_jid(who); + + if(bare_jid) { + jabber_buddy_get_info_for_jid(js, bare_jid); + g_free(bare_jid); + } +} + +void jabber_buddy_get_info_chat(GaimConnection *gc, int id, + const char *resource) +{ + JabberStream *js = gc->proto_data; + JabberChat *chat = jabber_chat_find_by_id(js, id); + char *full_jid; + + if(!chat) + return; + + full_jid = g_strdup_printf("%s@%s/%s", chat->room, chat->server, resource); + jabber_buddy_get_info_for_jid(js, full_jid); + g_free(full_jid); +} + + +static void jabber_buddy_set_invisibility(JabberStream *js, const char *who, + gboolean invisible) +{ + GaimPresence *gpresence; + GaimAccount *account; + GaimStatus *status; + JabberBuddy *jb = jabber_buddy_find(js, who, TRUE); + xmlnode *presence; + JabberBuddyState state; + char *msg; + int priority; + + account = gaim_connection_get_account(js->gc); + gpresence = gaim_account_get_presence(account); + status = gaim_presence_get_active_status(gpresence); + + gaim_status_to_jabber(status, &state, &msg, &priority); + presence = jabber_presence_create(state, msg, priority); + + g_free(msg); + + xmlnode_set_attrib(presence, "to", who); + if(invisible) { + xmlnode_set_attrib(presence, "type", "invisible"); + jb->invisible |= JABBER_INVIS_BUDDY; + } else { + jb->invisible &= ~JABBER_INVIS_BUDDY; + } + + jabber_send(js, presence); + xmlnode_free(presence); +} + +static void jabber_buddy_make_invisible(GaimBlistNode *node, gpointer data) +{ + GaimBuddy *buddy; + GaimConnection *gc; + JabberStream *js; + + g_return_if_fail(GAIM_BLIST_NODE_IS_BUDDY(node)); + + buddy = (GaimBuddy *) node; + gc = gaim_account_get_connection(buddy->account); + js = gc->proto_data; + + jabber_buddy_set_invisibility(js, buddy->name, TRUE); +} + +static void jabber_buddy_make_visible(GaimBlistNode *node, gpointer data) +{ + GaimBuddy *buddy; + GaimConnection *gc; + JabberStream *js; + + g_return_if_fail(GAIM_BLIST_NODE_IS_BUDDY(node)); + + buddy = (GaimBuddy *) node; + gc = gaim_account_get_connection(buddy->account); + js = gc->proto_data; + + jabber_buddy_set_invisibility(js, buddy->name, FALSE); +} + +static void jabber_buddy_cancel_presence_notification(GaimBlistNode *node, + gpointer data) +{ + GaimBuddy *buddy; + GaimConnection *gc; + JabberStream *js; + + g_return_if_fail(GAIM_BLIST_NODE_IS_BUDDY(node)); + + buddy = (GaimBuddy *) node; + gc = gaim_account_get_connection(buddy->account); + js = gc->proto_data; + + /* I wonder if we should prompt the user before doing this */ + jabber_presence_subscription_set(js, buddy->name, "unsubscribed"); +} + +static void jabber_buddy_rerequest_auth(GaimBlistNode *node, gpointer data) +{ + GaimBuddy *buddy; + GaimConnection *gc; + JabberStream *js; + + g_return_if_fail(GAIM_BLIST_NODE_IS_BUDDY(node)); + + buddy = (GaimBuddy *) node; + gc = gaim_account_get_connection(buddy->account); + js = gc->proto_data; + + jabber_presence_subscription_set(js, buddy->name, "subscribe"); +} + + +static void jabber_buddy_unsubscribe(GaimBlistNode *node, gpointer data) +{ + GaimBuddy *buddy; + GaimConnection *gc; + JabberStream *js; + + g_return_if_fail(GAIM_BLIST_NODE_IS_BUDDY(node)); + + buddy = (GaimBuddy *) node; + gc = gaim_account_get_connection(buddy->account); + js = gc->proto_data; + + jabber_presence_subscription_set(js, buddy->name, "unsubscribe"); +} + + +static GList *jabber_buddy_menu(GaimBuddy *buddy) +{ + GaimConnection *gc = gaim_account_get_connection(buddy->account); + JabberStream *js = gc->proto_data; + JabberBuddy *jb = jabber_buddy_find(js, buddy->name, TRUE); + + GList *m = NULL; + GaimMenuAction *act; + + if(!jb) + return m; + + /* XXX: fix the NOT ME below */ + + if(js->protocol_version == JABBER_PROTO_0_9 /* && NOT ME */) { + if(jb->invisible & JABBER_INVIS_BUDDY) { + act = gaim_menu_action_new(_("Un-hide From"), + GAIM_CALLBACK(jabber_buddy_make_visible), + NULL, NULL); + } else { + act = gaim_menu_action_new(_("Temporarily Hide From"), + GAIM_CALLBACK(jabber_buddy_make_invisible), + NULL, NULL); + } + m = g_list_append(m, act); + } + + if(jb->subscription & JABBER_SUB_FROM /* && NOT ME */) { + act = gaim_menu_action_new(_("Cancel Presence Notification"), + GAIM_CALLBACK(jabber_buddy_cancel_presence_notification), + NULL, NULL); + m = g_list_append(m, act); + } + + if(!(jb->subscription & JABBER_SUB_TO)) { + act = gaim_menu_action_new(_("(Re-)Request authorization"), + GAIM_CALLBACK(jabber_buddy_rerequest_auth), + NULL, NULL); + m = g_list_append(m, act); + + } else /* if(NOT ME) */{ + + /* shouldn't this just happen automatically when the buddy is + removed? */ + act = gaim_menu_action_new(_("Unsubscribe"), + GAIM_CALLBACK(jabber_buddy_unsubscribe), + NULL, NULL); + m = g_list_append(m, act); + } + + return m; +} + +GList * +jabber_blist_node_menu(GaimBlistNode *node) +{ + if(GAIM_BLIST_NODE_IS_BUDDY(node)) { + return jabber_buddy_menu((GaimBuddy *) node); + } else { + return NULL; + } +} + + +const char * +jabber_buddy_state_get_name(JabberBuddyState state) +{ + switch(state) { + case JABBER_BUDDY_STATE_UNKNOWN: + return _("Unknown"); + case JABBER_BUDDY_STATE_ERROR: + return _("Error"); + case JABBER_BUDDY_STATE_UNAVAILABLE: + return _("Offline"); + case JABBER_BUDDY_STATE_ONLINE: + return _("Available"); + case JABBER_BUDDY_STATE_CHAT: + return _("Chatty"); + case JABBER_BUDDY_STATE_AWAY: + return _("Away"); + case JABBER_BUDDY_STATE_XA: + return _("Extended Away"); + case JABBER_BUDDY_STATE_DND: + return _("Do Not Disturb"); + } + + return _("Unknown"); +} + +JabberBuddyState jabber_buddy_status_id_get_state(const char *id) { + if(!id) + return JABBER_BUDDY_STATE_UNKNOWN; + if(!strcmp(id, "available")) + return JABBER_BUDDY_STATE_ONLINE; + if(!strcmp(id, "freeforchat")) + return JABBER_BUDDY_STATE_CHAT; + if(!strcmp(id, "away")) + return JABBER_BUDDY_STATE_AWAY; + if(!strcmp(id, "extended_away")) + return JABBER_BUDDY_STATE_XA; + if(!strcmp(id, "dnd")) + return JABBER_BUDDY_STATE_DND; + if(!strcmp(id, "offline")) + return JABBER_BUDDY_STATE_UNAVAILABLE; + if(!strcmp(id, "error")) + return JABBER_BUDDY_STATE_ERROR; + + return JABBER_BUDDY_STATE_UNKNOWN; +} + +JabberBuddyState jabber_buddy_show_get_state(const char *id) { + if(!id) + return JABBER_BUDDY_STATE_UNKNOWN; + if(!strcmp(id, "available")) + return JABBER_BUDDY_STATE_ONLINE; + if(!strcmp(id, "chat")) + return JABBER_BUDDY_STATE_CHAT; + if(!strcmp(id, "away")) + return JABBER_BUDDY_STATE_AWAY; + if(!strcmp(id, "xa")) + return JABBER_BUDDY_STATE_XA; + if(!strcmp(id, "dnd")) + return JABBER_BUDDY_STATE_DND; + if(!strcmp(id, "offline")) + return JABBER_BUDDY_STATE_UNAVAILABLE; + if(!strcmp(id, "error")) + return JABBER_BUDDY_STATE_ERROR; + + return JABBER_BUDDY_STATE_UNKNOWN; +} + +const char *jabber_buddy_state_get_show(JabberBuddyState state) { + switch(state) { + case JABBER_BUDDY_STATE_CHAT: + return "chat"; + case JABBER_BUDDY_STATE_AWAY: + return "away"; + case JABBER_BUDDY_STATE_XA: + return "xa"; + case JABBER_BUDDY_STATE_DND: + return "dnd"; + case JABBER_BUDDY_STATE_ONLINE: + return "available"; + case JABBER_BUDDY_STATE_UNKNOWN: + case JABBER_BUDDY_STATE_ERROR: + return NULL; + case JABBER_BUDDY_STATE_UNAVAILABLE: + return "offline"; + } + return NULL; +} + +const char *jabber_buddy_state_get_status_id(JabberBuddyState state) { + switch(state) { + case JABBER_BUDDY_STATE_CHAT: + return "freeforchat"; + case JABBER_BUDDY_STATE_AWAY: + return "away"; + case JABBER_BUDDY_STATE_XA: + return "extended_away"; + case JABBER_BUDDY_STATE_DND: + return "dnd"; + case JABBER_BUDDY_STATE_ONLINE: + return "available"; + case JABBER_BUDDY_STATE_UNKNOWN: + return "available"; + case JABBER_BUDDY_STATE_ERROR: + return "error"; + case JABBER_BUDDY_STATE_UNAVAILABLE: + return "offline"; + } + return NULL; +} + +static void user_search_result_add_buddy_cb(GaimConnection *gc, GList *row, void *user_data) +{ + /* XXX find out the jid */ + gaim_blist_request_add_buddy(gaim_connection_get_account(gc), + g_list_nth_data(row, 0), NULL, NULL); +} + +static void user_search_result_cb(JabberStream *js, xmlnode *packet, gpointer data) +{ + GaimNotifySearchResults *results; + GaimNotifySearchColumn *column; + xmlnode *x, *query, *item, *field; + + /* XXX error checking? */ + if(!(query = xmlnode_get_child(packet, "query"))) + return; + + results = gaim_notify_searchresults_new(); + if((x = xmlnode_get_child_with_namespace(query, "x", "jabber:x:data"))) { + xmlnode *reported; + gaim_debug_info("jabber", "new-skool\n"); + if((reported = xmlnode_get_child(x, "reported"))) { + xmlnode *field = xmlnode_get_child(reported, "field"); + while(field) { + /* XXX keep track of this order, use it below */ + const char *var = xmlnode_get_attrib(field, "var"); + const char *label = xmlnode_get_attrib(field, "label"); + if(var) { + column = gaim_notify_searchresults_column_new(label ? label : var); + gaim_notify_searchresults_column_add(results, column); + } + field = xmlnode_get_next_twin(field); + } + } + item = xmlnode_get_child(x, "item"); + while(item) { + GList *row = NULL; + field = xmlnode_get_child(item, "field"); + while(field) { + xmlnode *valuenode = xmlnode_get_child(field, "value"); + if(valuenode) { + char *value = xmlnode_get_data(valuenode); + row = g_list_append(row, value); + } + field = xmlnode_get_next_twin(field); + } + gaim_notify_searchresults_row_add(results, row); + + item = xmlnode_get_next_twin(item); + } + } else { + /* old skool */ + gaim_debug_info("jabber", "old-skool\n"); + + column = gaim_notify_searchresults_column_new(_("JID")); + gaim_notify_searchresults_column_add(results, column); + column = gaim_notify_searchresults_column_new(_("First Name")); + gaim_notify_searchresults_column_add(results, column); + column = gaim_notify_searchresults_column_new(_("Last Name")); + gaim_notify_searchresults_column_add(results, column); + column = gaim_notify_searchresults_column_new(_("Nickname")); + gaim_notify_searchresults_column_add(results, column); + column = gaim_notify_searchresults_column_new(_("E-Mail")); + gaim_notify_searchresults_column_add(results, column); + + for(item = xmlnode_get_child(query, "item"); item; item = xmlnode_get_next_twin(item)) { + const char *jid; + xmlnode *node; + GList *row = NULL; + + if(!(jid = xmlnode_get_attrib(item, "jid"))) + continue; + + row = g_list_append(row, g_strdup(jid)); + node = xmlnode_get_child(item, "first"); + row = g_list_append(row, node ? xmlnode_get_data(node) : NULL); + node = xmlnode_get_child(item, "last"); + row = g_list_append(row, node ? xmlnode_get_data(node) : NULL); + node = xmlnode_get_child(item, "nick"); + row = g_list_append(row, node ? xmlnode_get_data(node) : NULL); + node = xmlnode_get_child(item, "email"); + row = g_list_append(row, node ? xmlnode_get_data(node) : NULL); + gaim_debug_info("jabber", "row=%d\n", row); + gaim_notify_searchresults_row_add(results, row); + } + } + + gaim_notify_searchresults_button_add(results, GAIM_NOTIFY_BUTTON_ADD, + user_search_result_add_buddy_cb); + + gaim_notify_searchresults(js->gc, NULL, NULL, _("The following are the results of your search"), results, NULL, NULL); +} + +static void user_search_x_data_cb(JabberStream *js, xmlnode *result, gpointer data) +{ + xmlnode *query; + JabberIq *iq; + char *dir_server = data; + + iq = jabber_iq_new_query(js, JABBER_IQ_SET, "jabber:iq:search"); + query = xmlnode_get_child(iq->node, "query"); + + xmlnode_insert_child(query, result); + + jabber_iq_set_callback(iq, user_search_result_cb, NULL); + xmlnode_set_attrib(iq->node, "to", dir_server); + jabber_iq_send(iq); + g_free(dir_server); +} + +struct user_search_info { + JabberStream *js; + char *directory_server; +}; + +static void user_search_cancel_cb(struct user_search_info *usi, GaimRequestFields *fields) +{ + g_free(usi->directory_server); + g_free(usi); +} + +static void user_search_cb(struct user_search_info *usi, GaimRequestFields *fields) +{ + JabberStream *js = usi->js; + JabberIq *iq; + xmlnode *query; + GList *groups, *flds; + + iq = jabber_iq_new_query(js, JABBER_IQ_SET, "jabber:iq:search"); + query = xmlnode_get_child(iq->node, "query"); + + for(groups = gaim_request_fields_get_groups(fields); groups; groups = groups->next) { + for(flds = gaim_request_field_group_get_fields(groups->data); + flds; flds = flds->next) { + GaimRequestField *field = flds->data; + const char *id = gaim_request_field_get_id(field); + const char *value = gaim_request_field_string_get_value(field); + + if(value && (!strcmp(id, "first") || !strcmp(id, "last") || !strcmp(id, "nick") || !strcmp(id, "email"))) { + xmlnode *y = xmlnode_new_child(query, id); + xmlnode_insert_data(y, value, -1); + } + } + } + + jabber_iq_set_callback(iq, user_search_result_cb, NULL); + xmlnode_set_attrib(iq->node, "to", usi->directory_server); + jabber_iq_send(iq); + + g_free(usi->directory_server); + g_free(usi); +} + +#if 0 +/* This is for gettext only -- it will see this even though there's an #if 0. */ + +/* + * An incomplete list of server generated original language search + * comments for Jabber User Directories + * + * See discussion thread "Search comment for Jabber is not translatable" + * in gaim-i18n@lists.sourceforge.net (March 2006) + */ +static const char * jabber_user_dir_comments [] = { + /* current comment from Jabber User Directory users.jabber.org */ + N_("Find a contact by entering the search criteria in the given fields. " + "Note: Each field supports wild card searches (%)"), + NULL +}; +#endif + +static void user_search_fields_result_cb(JabberStream *js, xmlnode *packet, gpointer data) +{ + xmlnode *query, *x; + const char *from, *type; + + if(!(from = xmlnode_get_attrib(packet, "from"))) + return; + + if(!(type = xmlnode_get_attrib(packet, "type")) || !strcmp(type, "error")) { + char *msg = jabber_parse_error(js, packet); + + if(!msg) + msg = g_strdup(_("Unknown error")); + + gaim_notify_error(js->gc, _("Directory Query Failed"), + _("Could not query the directory server."), msg); + g_free(msg); + + return; + } + + + if(!(query = xmlnode_get_child(packet, "query"))) + return; + + if((x = xmlnode_get_child_with_namespace(query, "x", "jabber:x:data"))) { + jabber_x_data_request(js, x, user_search_x_data_cb, g_strdup(from)); + return; + } else { + struct user_search_info *usi; + xmlnode *instnode; + char *instructions = NULL; + GaimRequestFields *fields; + GaimRequestFieldGroup *group; + GaimRequestField *field; + + /* old skool */ + fields = gaim_request_fields_new(); + group = gaim_request_field_group_new(NULL); + gaim_request_fields_add_group(fields, group); + + if((instnode = xmlnode_get_child(query, "instructions"))) + { + char *tmp = xmlnode_get_data(instnode); + + if(tmp) + { + /* Try to translate the message (see static message + list in jabber_user_dir_comments[]) */ + instructions = g_strdup_printf(_("Server Instructions: %s"), _(tmp)); + g_free(tmp); + } + } + + if(!instructions) + { + instructions = g_strdup(_("Fill in one or more fields to search " + "for any matching Jabber users.")); + } + + if(xmlnode_get_child(query, "first")) { + field = gaim_request_field_string_new("first", _("First Name"), + NULL, FALSE); + gaim_request_field_group_add_field(group, field); + } + if(xmlnode_get_child(query, "last")) { + field = gaim_request_field_string_new("last", _("Last Name"), + NULL, FALSE); + gaim_request_field_group_add_field(group, field); + } + if(xmlnode_get_child(query, "nick")) { + field = gaim_request_field_string_new("nick", _("Nickname"), + NULL, FALSE); + gaim_request_field_group_add_field(group, field); + } + if(xmlnode_get_child(query, "email")) { + field = gaim_request_field_string_new("email", _("E-Mail Address"), + NULL, FALSE); + gaim_request_field_group_add_field(group, field); + } + + usi = g_new0(struct user_search_info, 1); + usi->js = js; + usi->directory_server = g_strdup(from); + + gaim_request_fields(js->gc, _("Search for Jabber users"), + _("Search for Jabber users"), instructions, fields, + _("Search"), G_CALLBACK(user_search_cb), + _("Cancel"), G_CALLBACK(user_search_cancel_cb), usi); + + g_free(instructions); + } +} + +static void jabber_user_search_ok(JabberStream *js, const char *directory) +{ + JabberIq *iq; + + /* XXX: should probably better validate the directory we're given */ + if(!directory || !*directory) { + gaim_notify_error(js->gc, _("Invalid Directory"), _("Invalid Directory"), NULL); + return; + } + + iq = jabber_iq_new_query(js, JABBER_IQ_GET, "jabber:iq:search"); + xmlnode_set_attrib(iq->node, "to", directory); + + jabber_iq_set_callback(iq, user_search_fields_result_cb, NULL); + + jabber_iq_send(iq); +} + +void jabber_user_search_begin(GaimPluginAction *action) +{ + GaimConnection *gc = (GaimConnection *) action->context; + JabberStream *js = gc->proto_data; + + gaim_request_input(gc, _("Enter a User Directory"), _("Enter a User Directory"), + _("Select a user directory to search"), + js->user_directories ? js->user_directories->data : "users.jabber.org", + FALSE, FALSE, NULL, + _("Search Directory"), GAIM_CALLBACK(jabber_user_search_ok), + _("Cancel"), NULL, js); +} + + +