pidgin/pidgin

1896a80ff8e3
Route GLib debug logging directly to the Finch debug window

Instead of flowing through purple debug, this merges some bits of the existing GLib log handler, and the purple debug printer.

Testing Done:
Open the Debug window an see some `GLib-*` outputs.

Reviewed at https://reviews.imfreedom.org/r/1057/
/*
* purple - Jabber Protocol Plugin
*
* Purple is the legal property of its developers, whose names are too numerous
* to list here. Please refer to the COPYRIGHT file distributed with this
* source distribution.
*
* 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., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA
*
*/
#include <glib/gi18n-lib.h>
#include <purple.h>
#include "glibcompat.h"
#include "buddy.h"
#include "chat.h"
#include "jabber.h"
#include "iq.h"
#include "presence.h"
#include "useravatar.h"
#include "xdata.h"
#include "pep.h"
#include "adhoccommands.h"
typedef struct {
long idle_seconds;
} JabberBuddyInfoResource;
typedef struct {
JabberStream *js;
JabberBuddy *jb;
char *jid;
GSList *ids;
GHashTable *resources;
guint timeout_handle;
GSList *vcard_images;
PurpleNotifyUserInfo *user_info;
long last_seconds;
gchar *last_message;
} JabberBuddyInfo;
void
jabber_adhoc_commands_free(JabberAdHocCommands *cmd)
{
g_return_if_fail(cmd != NULL);
g_free(cmd->jid);
g_free(cmd->node);
g_free(cmd->name);
g_free(cmd);
}
static void
jabber_buddy_resource_free(JabberBuddyResource *jbr)
{
g_return_if_fail(jbr != NULL);
g_list_free_full(jbr->commands, (GDestroyNotify)jabber_adhoc_commands_free);
g_list_free_full(jbr->caps.exts, g_free);
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_free(JabberBuddy *jb)
{
g_return_if_fail(jb != NULL);
g_free(jb->error_msg);
g_list_free_full(jb->resources, (GDestroyNotify)jabber_buddy_resource_free);
g_free(jb);
}
JabberBuddy *jabber_buddy_find(JabberStream *js, const char *name,
gboolean create)
{
JabberBuddy *jb;
char *realname;
if (js->buddies == NULL)
return NULL;
if(!(realname = jabber_get_bare_jid(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, realname, jb);
} else
g_free(realname);
return jb;
}
/* Returns -1 if a is a higher priority resource than b, or is
* "more available" than b. 0 if they're the same, and 1 if b is
* higher priority/more available than a.
*/
static gint resource_compare_cb(gconstpointer a, gconstpointer b)
{
const JabberBuddyResource *jbra = a;
const JabberBuddyResource *jbrb = b;
JabberBuddyState state_a, state_b;
if (jbra->priority != jbrb->priority)
return jbra->priority > jbrb->priority ? -1 : 1;
/* Fold the states for easier comparison */
/* TODO: Differentiate online/chat and away/dnd? */
switch (jbra->state) {
case JABBER_BUDDY_STATE_ONLINE:
case JABBER_BUDDY_STATE_CHAT:
state_a = JABBER_BUDDY_STATE_ONLINE;
break;
case JABBER_BUDDY_STATE_AWAY:
case JABBER_BUDDY_STATE_DND:
state_a = JABBER_BUDDY_STATE_AWAY;
break;
case JABBER_BUDDY_STATE_XA:
state_a = JABBER_BUDDY_STATE_XA;
break;
case JABBER_BUDDY_STATE_UNAVAILABLE:
state_a = JABBER_BUDDY_STATE_UNAVAILABLE;
break;
default:
state_a = JABBER_BUDDY_STATE_UNKNOWN;
break;
}
switch (jbrb->state) {
case JABBER_BUDDY_STATE_ONLINE:
case JABBER_BUDDY_STATE_CHAT:
state_b = JABBER_BUDDY_STATE_ONLINE;
break;
case JABBER_BUDDY_STATE_AWAY:
case JABBER_BUDDY_STATE_DND:
state_b = JABBER_BUDDY_STATE_AWAY;
break;
case JABBER_BUDDY_STATE_XA:
state_b = JABBER_BUDDY_STATE_XA;
break;
case JABBER_BUDDY_STATE_UNAVAILABLE:
state_b = JABBER_BUDDY_STATE_UNAVAILABLE;
break;
default:
state_b = JABBER_BUDDY_STATE_UNKNOWN;
break;
}
if (state_a == state_b) {
if (jbra->idle == jbrb->idle)
return 0;
else if ((jbra->idle && !jbrb->idle) ||
(jbra->idle && jbrb->idle && jbra->idle < jbrb->idle))
return 1;
else
return -1;
}
if (state_a == JABBER_BUDDY_STATE_ONLINE)
return -1;
else if (state_a == JABBER_BUDDY_STATE_AWAY &&
(state_b == JABBER_BUDDY_STATE_XA ||
state_b == JABBER_BUDDY_STATE_UNAVAILABLE ||
state_b == JABBER_BUDDY_STATE_UNKNOWN))
return -1;
else if (state_a == JABBER_BUDDY_STATE_XA &&
(state_b == JABBER_BUDDY_STATE_UNAVAILABLE ||
state_b == JABBER_BUDDY_STATE_UNKNOWN))
return -1;
else if (state_a == JABBER_BUDDY_STATE_UNAVAILABLE &&
state_b == JABBER_BUDDY_STATE_UNKNOWN)
return -1;
return 1;
}
JabberBuddyResource *jabber_buddy_find_resource(JabberBuddy *jb,
const char *resource)
{
GList *l;
if (!jb)
return NULL;
if (resource == NULL)
return jb->resources ? jb->resources->data : NULL;
for (l = jb->resources; l; l = l->next)
{
JabberBuddyResource *jbr = l->data;
if (purple_strequal(resource, jbr->name))
return jbr;
}
return NULL;
}
JabberBuddyResource *jabber_buddy_track_resource(JabberBuddy *jb, const char *resource,
int priority, JabberBuddyState state, const char *status)
{
/* TODO: Optimization: Only reinsert if priority+state changed */
JabberBuddyResource *jbr = jabber_buddy_find_resource(jb, resource);
if (jbr) {
jb->resources = g_list_remove(jb->resources, jbr);
} else {
jbr = g_new0(JabberBuddyResource, 1);
jbr->jb = jb;
jbr->name = g_strdup(resource);
jbr->capabilities = JABBER_CAP_NONE;
jbr->tz_off = PURPLE_NO_TZ_OFF;
}
jbr->priority = priority;
jbr->state = state;
g_free(jbr->status);
jbr->status = g_strdup(status);
jb->resources = g_list_insert_sorted(jb->resources, jbr,
resource_compare_cb);
return jbr;
}
void jabber_buddy_remove_resource(JabberBuddy *jb, const char *resource)
{
JabberBuddyResource *jbr = jabber_buddy_find_resource(jb, resource);
if(!jbr)
return;
jbr->jb->resources = g_list_remove(jbr->jb->resources, jbr);
jabber_buddy_resource_free(jbr);
}
/*******
* 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 *tag; /* tag text */
char *ptag; /* parent tag "path" text */
} const vcard_template_data[] = {
{N_("Full Name"), "FN", NULL},
{N_("Family Name"), "FAMILY", "N"},
{N_("Given Name"), "GIVEN", "N"},
{N_("Nickname"), "NICKNAME", NULL},
{N_("URL"), "URL", NULL},
{N_("Street Address"), "STREET", "ADR"},
{N_("Extended Address"), "EXTADD", "ADR"},
{N_("Locality"), "LOCALITY", "ADR"},
{N_("Region"), "REGION", "ADR"},
{N_("Postal Code"), "PCODE", "ADR"},
{N_("Country"), "CTRY", "ADR"},
{N_("Telephone"), "NUMBER", "TEL"},
{N_("Email"), "USERID", "EMAIL"},
{N_("Organization Name"), "ORGNAME", "ORG"},
{N_("Organization Unit"), "ORGUNIT", "ORG"},
{N_("Job Title"), "TITLE", NULL},
{N_("Role"), "ROLE", NULL},
{N_("Birthday"), "BDAY", NULL},
{N_("Description"), "DESC", NULL},
{"", "N", NULL},
{"", "ADR", NULL},
{"", "ORG", NULL},
{NULL, NULL, NULL}
};
/*
* The "vCard" tag's attribute list...
*/
struct tag_attr {
char *attr;
char *value;
} const 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 PurpleXmlNode 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 PurpleXmlNode *insert_tag_to_parent_tag(PurpleXmlNode *start, const char *parent_tag, const char *new_tag)
{
PurpleXmlNode *x = NULL;
/*
* If the parent tag wasn't specified, see if we can get it
* from the vCard template struct.
*/
if(parent_tag == NULL) {
const struct vcard_template *vc_tp = vcard_template_data;
while(vc_tp->label != NULL) {
if(purple_strequal(vc_tp->tag, new_tag)) {
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 = purple_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 = purple_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!
*/
PurpleXmlNode *y;
if((y = purple_xmlnode_get_child(x, new_tag)) != NULL) {
return(y);
}
}
}
/*
* insert the new tag into its parent node
*/
return(purple_xmlnode_new_child((x == NULL? start : x), new_tag));
}
/*
* Send vCard info to Jabber server
*/
void
jabber_set_info(PurpleProtocolServer *protocol_server, PurpleConnection *gc,
const gchar *info)
{
PurpleImage *img;
JabberIq *iq;
JabberStream *js = purple_connection_get_protocol_data(gc);
PurpleXmlNode *vc_node;
const struct tag_attr *tag_attr;
/* if we haven't grabbed the remote vcard yet, we can't
* assume that what we have here is correct */
if(!js->vcard_fetched) {
PurpleImage *image;
g_free(js->initial_avatar_hash);
image = purple_buddy_icons_find_account_icon(purple_connection_get_account(gc));
if (image != NULL) {
js->initial_avatar_hash = g_compute_checksum_for_data(
G_CHECKSUM_SHA1,
purple_image_get_data(image),
purple_image_get_data_size(image)
);
g_object_unref(image);
} else {
js->initial_avatar_hash = NULL;
}
return;
}
g_free(js->avatar_hash);
js->avatar_hash = NULL;
/*
* Send only if there's actually any *information* to send
*/
vc_node = info ? purple_xmlnode_from_str(info, -1) : NULL;
if (vc_node && (!vc_node->name ||
g_ascii_strncasecmp(vc_node->name, "vCard", 5))) {
purple_xmlnode_free(vc_node);
vc_node = NULL;
}
if ((img = purple_buddy_icons_find_account_icon(purple_connection_get_account(gc)))) {
gconstpointer avatar_data;
gsize avatar_len;
PurpleXmlNode *photo, *binval, *type;
gchar *enc;
if(!vc_node) {
vc_node = purple_xmlnode_new("vCard");
for(tag_attr = vcard_tag_attr_list; tag_attr->attr != NULL; ++tag_attr)
purple_xmlnode_set_attrib(vc_node, tag_attr->attr, tag_attr->value);
}
avatar_data = purple_image_get_data(img);
avatar_len = purple_image_get_data_size(img);
/* Get rid of an old PHOTO if one exists.
* TODO: This may want to be modified to remove all old PHOTO
* children, at the moment some people have managed to get
* multiple PHOTO entries in their vCard. */
if((photo = purple_xmlnode_get_child(vc_node, "PHOTO"))) {
purple_xmlnode_free(photo);
}
photo = purple_xmlnode_new_child(vc_node, "PHOTO");
type = purple_xmlnode_new_child(photo, "TYPE");
purple_xmlnode_insert_data(type, "image/png", -1);
binval = purple_xmlnode_new_child(photo, "BINVAL");
enc = g_base64_encode(avatar_data, avatar_len);
js->avatar_hash = g_compute_checksum_for_data(G_CHECKSUM_SHA1,
avatar_data, avatar_len);
purple_xmlnode_insert_data(binval, enc, -1);
g_free(enc);
g_object_unref(img);
} else if (vc_node) {
PurpleXmlNode *photo;
/* TODO: Remove all PHOTO children? (see above note) */
if ((photo = purple_xmlnode_get_child(vc_node, "PHOTO"))) {
purple_xmlnode_free(photo);
}
}
if (vc_node != NULL) {
iq = jabber_iq_new(js, JABBER_IQ_SET);
purple_xmlnode_insert_child(iq->node, vc_node);
jabber_iq_send(iq);
/* Send presence to update vcard-temp:x:update */
jabber_presence_send(js, FALSE);
}
}
void
jabber_set_buddy_icon(PurpleProtocolServer *protocol_server,
PurpleConnection *gc, PurpleImage *img)
{
PurpleAccount *account = purple_connection_get_account(gc);
/* Publish the avatar as specified in XEP-0084 */
jabber_avatar_set(purple_connection_get_protocol_data(gc), img);
/* Set the image in our vCard */
jabber_set_info(NULL, gc, purple_account_get_user_info(account));
/* TODO: Fake image to ourselves, since a number of servers do not echo
* back our presence to us. To do this without uselessly copying the data
* of the image, we need purple_buddy_icons_set_for_user_image (i.e. takes
* an existing icon/stored image). */
}
/*
* This is the callback from the "ok clicked" for "set vCard"
*
* Sets the vCard with data from PurpleRequestFields.
*/
static void
jabber_format_info(PurpleConnection *gc, PurpleRequestFields *fields)
{
PurpleXmlNode *vc_node;
PurpleRequestField *field;
const char *text;
char *p;
const struct vcard_template *vc_tp;
const struct tag_attr *tag_attr;
vc_node = purple_xmlnode_new("vCard");
for(tag_attr = vcard_tag_attr_list; tag_attr->attr != NULL; ++tag_attr)
purple_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 = purple_request_fields_get_field(fields, vc_tp->tag);
text = purple_request_field_string_get_value(field);
if (text != NULL && *text != '\0') {
PurpleXmlNode *xp;
purple_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) {
purple_xmlnode_insert_data(xp, text, -1);
}
}
}
p = purple_xmlnode_to_str(vc_node, NULL);
purple_xmlnode_free(vc_node);
purple_account_set_user_info(purple_connection_get_account(gc), p);
purple_serv_set_info(gc, p);
g_free(p);
}
/*
* This gets executed by the proto action
*
* Creates a new PurpleRequestFields 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(PurpleProtocolAction *action)
{
PurpleConnection *gc = (PurpleConnection *) action->connection;
PurpleRequestFields *fields;
PurpleRequestFieldGroup *group;
PurpleRequestField *field;
const struct vcard_template *vc_tp;
const char *user_info;
char *cdata = NULL;
PurpleXmlNode *x_vc_data = NULL;
fields = purple_request_fields_new();
group = purple_request_field_group_new(NULL);
purple_request_fields_add_group(fields, group);
/*
* Get existing, XML-formatted, user info
*/
if((user_info = purple_account_get_user_info(purple_connection_get_account(gc))) != NULL)
x_vc_data = purple_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) {
PurpleXmlNode *data_node;
if((vc_tp->label)[0] == '\0')
continue;
if (x_vc_data != NULL) {
if(vc_tp->ptag == NULL) {
data_node = purple_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 = purple_xmlnode_get_child(x_vc_data, tag);
g_free(tag);
}
if(data_node)
cdata = purple_xmlnode_get_data(data_node);
}
if(purple_strequal(vc_tp->tag, "DESC")) {
field = purple_request_field_string_new(vc_tp->tag,
_(vc_tp->label), cdata,
TRUE);
} else {
field = purple_request_field_string_new(vc_tp->tag,
_(vc_tp->label), cdata,
FALSE);
}
g_free(cdata);
cdata = NULL;
purple_request_field_group_add_field(group, field);
}
if(x_vc_data != NULL)
purple_xmlnode_free(x_vc_data);
purple_request_fields(gc, _("Edit XMPP vCard"),
_("Edit XMPP vCard"),
_("All items below are optional. Enter only the "
"information with which you feel comfortable."),
fields,
_("Save"), G_CALLBACK(jabber_format_info),
_("Cancel"), NULL,
purple_request_cpar_from_connection(gc),
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)
g_source_remove(jbi->timeout_handle);
g_slist_free(jbi->ids);
g_free(jbi->jid);
g_hash_table_destroy(jbi->resources);
g_free(jbi->last_message);
purple_notify_user_info_destroy(jbi->user_info);
g_free(jbi);
}
static void
add_jbr_info(JabberBuddyInfo *jbi, const char *resource,
JabberBuddyResource *jbr)
{
JabberBuddyInfoResource *jbir;
PurpleNotifyUserInfo *user_info;
jbir = g_hash_table_lookup(jbi->resources, resource);
user_info = jbi->user_info;
if (jbr && jbr->client.name) {
char *tmp =
g_strdup_printf("%s%s%s", jbr->client.name,
(jbr->client.version ? " " : ""),
(jbr->client.version ? jbr->client.version : ""));
/* TODO: Check whether it's correct to call prepend_pair_html,
or if we should be using prepend_pair_plaintext */
purple_notify_user_info_prepend_pair_html(user_info, _("Client"), tmp);
g_free(tmp);
if (jbr->client.os) {
/* TODO: Check whether it's correct to call prepend_pair_html,
or if we should be using prepend_pair_plaintext */
purple_notify_user_info_prepend_pair_html(user_info, _("Operating System"), jbr->client.os);
}
}
if (jbr && jbr->tz_off != PURPLE_NO_TZ_OFF) {
GDateTime *dt = NULL;
GTimeZone *tz = NULL;
char *timestamp = NULL;
tz = g_time_zone_new_offset(jbr->tz_off);
dt = g_date_time_new_now(tz);
g_time_zone_unref(tz);
timestamp = g_date_time_format(dt, "%X %:z");
g_date_time_unref(dt);
purple_notify_user_info_prepend_pair_plaintext(user_info, _("Local Time"), timestamp);
g_free(timestamp);
}
if (jbir && jbir->idle_seconds > 0) {
char *idle = purple_str_seconds_to_string(jbir->idle_seconds);
purple_notify_user_info_prepend_pair_plaintext(user_info, _("Idle"), idle);
g_free(idle);
}
if (jbr) {
char *purdy = NULL;
char *tmp;
char priority[12];
const char *status_name = jabber_buddy_state_get_name(jbr->state);
if (jbr->status) {
tmp = purple_markup_escape_text(jbr->status, -1);
purdy = purple_strdup_withhtml(tmp);
g_free(tmp);
if (purple_strequal(status_name, purdy))
status_name = NULL;
}
tmp = g_strdup_printf("%s%s%s", (status_name ? status_name : ""),
((status_name && purdy) ? ": " : ""),
(purdy ? purdy : ""));
purple_notify_user_info_prepend_pair_html(user_info, _("Status"), tmp);
g_snprintf(priority, sizeof(priority), "%d", jbr->priority);
purple_notify_user_info_prepend_pair_plaintext(user_info, _("Priority"), priority);
g_free(tmp);
g_free(purdy);
} else {
purple_notify_user_info_prepend_pair_plaintext(user_info, _("Status"), _("Unknown"));
}
}
static void jabber_buddy_info_show_if_ready(JabberBuddyInfo *jbi)
{
char *resource_name;
JabberBuddyResource *jbr;
GList *resources;
PurpleNotifyUserInfo *user_info;
/* not yet */
if (jbi->ids)
return;
user_info = jbi->user_info;
resource_name = jabber_get_resource(jbi->jid);
/* If we have one or more pairs from the vcard, put a section break above it */
if (g_queue_get_length(purple_notify_user_info_get_entries(user_info)))
purple_notify_user_info_prepend_section_break(user_info);
/* Add the information about the user's resource(s) */
if (resource_name) {
jbr = jabber_buddy_find_resource(jbi->jb, resource_name);
add_jbr_info(jbi, resource_name, jbr);
} else {
/* TODO: This is in priority-ascending order (lowest prio first), because
* everything is prepended. Is that ok? */
for (resources = jbi->jb->resources; resources; resources = resources->next) {
jbr = resources->data;
/* put a section break between resources, this is not needed if
we are at the first, because one was already added for the vcard
section */
if (resources != jbi->jb->resources)
purple_notify_user_info_prepend_section_break(user_info);
add_jbr_info(jbi, jbr->name, jbr);
if (jbr->name) {
/* TODO: Check whether it's correct to call prepend_pair_html,
or if we should be using prepend_pair_plaintext */
purple_notify_user_info_prepend_pair_html(user_info, _("Resource"), jbr->name);
}
}
}
if (!jbi->jb->resources) {
/* the buddy is offline */
gboolean is_domain = jabber_jid_is_domain(jbi->jid);
if (jbi->last_seconds > 0) {
char *last = purple_str_seconds_to_string(jbi->last_seconds);
gchar *message = NULL;
const gchar *title = NULL;
if (is_domain) {
title = _("Uptime");
message = last;
last = NULL;
} else {
title = _("Logged Off");
message = g_strdup_printf(_("%s ago"), last);
}
purple_notify_user_info_prepend_pair_plaintext(user_info, title, message);
g_free(last);
g_free(message);
}
if (!is_domain) {
gchar *status =
g_strdup_printf("%s%s%s", _("Offline"),
jbi->last_message ? ": " : "",
jbi->last_message ? jbi->last_message : "");
/* TODO: Check whether it's correct to call prepend_pair_html,
or if we should be using prepend_pair_plaintext */
purple_notify_user_info_prepend_pair_html(user_info, _("Status"), status);
g_free(status);
}
}
g_free(resource_name);
purple_notify_userinfo(jbi->js->gc, jbi->jid, user_info, NULL, NULL);
g_slist_free_full(jbi->vcard_images, g_object_unref);
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;
char *comp_id;
if(!id)
return;
l = g_slist_find_custom(jbi->ids, id, (GCompareFunc)g_strcmp0);
if(l) {
comp_id = l->data;
jbi->ids = g_slist_delete_link(jbi->ids, l);
g_free(comp_id);
}
}
static void jabber_vcard_save_mine(JabberStream *js, const char *from,
JabberIqType type, const char *id,
PurpleXmlNode *packet, gpointer data)
{
PurpleXmlNode *vcard, *photo, *binval;
char *txt, *vcard_hash = NULL;
PurpleAccount *account;
if (type == JABBER_IQ_ERROR) {
PurpleXmlNode *error;
purple_debug_warning("jabber", "Server returned error while retrieving vCard\n");
error = purple_xmlnode_get_child(packet, "error");
if (!error || !purple_xmlnode_get_child(error, "item-not-found"))
return;
}
account = purple_connection_get_account(js->gc);
if((vcard = purple_xmlnode_get_child(packet, "vCard")) ||
(vcard = purple_xmlnode_get_child_with_namespace(packet, "query", "vcard-temp")))
{
txt = purple_xmlnode_to_str(vcard, NULL);
purple_account_set_user_info(account, txt);
g_free(txt);
} else {
/* if we have no vCard, then lets not overwrite what we might have locally */
}
js->vcard_fetched = TRUE;
if (vcard && (photo = purple_xmlnode_get_child(vcard, "PHOTO")) &&
(binval = purple_xmlnode_get_child(photo, "BINVAL"))) {
gsize size;
char *bintext = purple_xmlnode_get_data(binval);
if (bintext) {
guchar *data = g_base64_decode(bintext, &size);
g_free(bintext);
if (data) {
vcard_hash = g_compute_checksum_for_data(
G_CHECKSUM_SHA1, data, size);
g_free(data);
}
}
}
/* Republish our vcard if the photo is different than the server's */
if (js->initial_avatar_hash && !purple_strequal(vcard_hash, js->initial_avatar_hash)) {
jabber_set_info(NULL, js->gc, purple_account_get_user_info(account));
} else if (vcard_hash) {
/* A photo is in the vCard. Advertise its hash */
js->avatar_hash = vcard_hash;
vcard_hash = NULL;
/* Send presence to update vcard-temp:x:update */
jabber_presence_send(js, FALSE);
}
g_free(vcard_hash);
}
void jabber_vcard_fetch_mine(JabberStream *js)
{
JabberIq *iq = jabber_iq_new(js, JABBER_IQ_GET);
PurpleXmlNode *vcard = purple_xmlnode_new_child(iq->node, "vCard");
purple_xmlnode_set_namespace(vcard, "vcard-temp");
jabber_iq_set_callback(iq, jabber_vcard_save_mine, NULL);
jabber_iq_send(iq);
}
static void jabber_vcard_parse(JabberStream *js, const char *from,
JabberIqType type, const char *id,
PurpleXmlNode *packet, gpointer data)
{
char *bare_jid;
char *text;
char *serverside_alias = NULL;
PurpleXmlNode *vcard;
PurpleAccount *account;
JabberBuddyInfo *jbi = data;
PurpleNotifyUserInfo *user_info;
g_return_if_fail(jbi != NULL);
jabber_buddy_info_remove_id(jbi, id);
if (type == JABBER_IQ_ERROR) {
purple_debug_info("jabber", "Got error response for vCard\n");
jabber_buddy_info_show_if_ready(jbi);
return;
}
user_info = jbi->user_info;
account = purple_connection_get_account(js->gc);
bare_jid = jabber_get_bare_jid(from ? from : purple_account_get_username(account));
/* TODO: Is the query xmlns='vcard-temp' version of this still necessary? */
if((vcard = purple_xmlnode_get_child(packet, "vCard")) ||
(vcard = purple_xmlnode_get_child_with_namespace(packet, "query", "vcard-temp"))) {
PurpleXmlNode *child;
for(child = vcard->child; child; child = child->next)
{
PurpleXmlNode *child2;
if(child->type != PURPLE_XMLNODE_TYPE_TAG)
continue;
text = purple_xmlnode_get_data(child);
if(text && purple_strequal(child->name, "FN")) {
if (!serverside_alias)
serverside_alias = g_strdup(text);
purple_notify_user_info_add_pair_plaintext(user_info, _("Full Name"), text);
} else if(purple_strequal(child->name, "N")) {
for(child2 = child->child; child2; child2 = child2->next)
{
char *text2;
if(child2->type != PURPLE_XMLNODE_TYPE_TAG)
continue;
text2 = purple_xmlnode_get_data(child2);
if(text2 && purple_strequal(child2->name, "FAMILY")) {
purple_notify_user_info_add_pair_plaintext(user_info, _("Family Name"), text2);
} else if(text2 && purple_strequal(child2->name, "GIVEN")) {
purple_notify_user_info_add_pair_plaintext(user_info, _("Given Name"), text2);
} else if(text2 && purple_strequal(child2->name, "MIDDLE")) {
purple_notify_user_info_add_pair_plaintext(user_info, _("Middle Name"), text2);
}
g_free(text2);
}
} else if(text && purple_strequal(child->name, "NICKNAME")) {
/* Prefer the Nickcname to the Full Name as the serverside alias if it's not just part of the jid.
* Ignore it if it's part of the jid. */
if (strstr(bare_jid, text) == NULL) {
g_free(serverside_alias);
serverside_alias = g_strdup(text);
purple_notify_user_info_add_pair_plaintext(user_info, _("Nickname"), text);
}
} else if(text && purple_strequal(child->name, "BDAY")) {
purple_notify_user_info_add_pair_plaintext(user_info, _("Birthday"), text);
} else if(purple_strequal(child->name, "ADR")) {
gboolean address_line_added = FALSE;
for(child2 = child->child; child2; child2 = child2->next)
{
char *text2;
if(child2->type != PURPLE_XMLNODE_TYPE_TAG)
continue;
text2 = purple_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)
{
purple_notify_user_info_add_section_header(user_info, _("Address"));
address_line_added = TRUE;
}
if(purple_strequal(child2->name, "POBOX")) {
purple_notify_user_info_add_pair_plaintext(user_info, _("P.O. Box"), text2);
} else if (purple_strequal(child2->name, "EXTADD") || purple_strequal(child2->name, "EXTADR")) {
/*
* EXTADD is correct, EXTADR is generated by other
* clients. The next time someone reads this, remove
* EXTADR.
*/
purple_notify_user_info_add_pair_plaintext(user_info, _("Extended Address"), text2);
} else if(purple_strequal(child2->name, "STREET")) {
purple_notify_user_info_add_pair_plaintext(user_info, _("Street Address"), text2);
} else if(purple_strequal(child2->name, "LOCALITY")) {
purple_notify_user_info_add_pair_plaintext(user_info, _("Locality"), text2);
} else if(purple_strequal(child2->name, "REGION")) {
purple_notify_user_info_add_pair_plaintext(user_info, _("Region"), text2);
} else if(purple_strequal(child2->name, "PCODE")) {
purple_notify_user_info_add_pair_plaintext(user_info, _("Postal Code"), text2);
} else if(purple_strequal(child2->name, "CTRY")
|| purple_strequal(child2->name, "COUNTRY")) {
purple_notify_user_info_add_pair_plaintext(user_info, _("Country"), text2);
}
g_free(text2);
}
if (address_line_added)
purple_notify_user_info_add_section_break(user_info);
} else if(purple_strequal(child->name, "TEL")) {
char *number;
if((child2 = purple_xmlnode_get_child(child, "NUMBER"))) {
/* show what kind of number it is */
number = purple_xmlnode_get_data(child2);
if(number) {
purple_notify_user_info_add_pair_plaintext(user_info, _("Telephone"), number);
g_free(number);
}
} else if((number = purple_xmlnode_get_data(child))) {
/* lots of clients (including purple) do this, but it's
* out of spec */
purple_notify_user_info_add_pair_plaintext(user_info, _("Telephone"), number);
g_free(number);
}
} else if(purple_strequal(child->name, "EMAIL")) {
char *userid, *escaped;
if((child2 = purple_xmlnode_get_child(child, "USERID"))) {
/* show what kind of email it is */
userid = purple_xmlnode_get_data(child2);
if(userid) {
char *mailto;
escaped = g_markup_escape_text(userid, -1);
mailto = g_strdup_printf("<a href=\"mailto:%s\">%s</a>", escaped, escaped);
purple_notify_user_info_add_pair_html(user_info, _("Email"), mailto);
g_free(mailto);
g_free(escaped);
g_free(userid);
}
} else if((userid = purple_xmlnode_get_data(child))) {
/* lots of clients (including purple) do this, but it's
* out of spec */
char *mailto;
escaped = g_markup_escape_text(userid, -1);
mailto = g_strdup_printf("<a href=\"mailto:%s\">%s</a>", escaped, escaped);
purple_notify_user_info_add_pair_html(user_info, _("Email"), mailto);
g_free(mailto);
g_free(escaped);
g_free(userid);
}
} else if(purple_strequal(child->name, "ORG")) {
for(child2 = child->child; child2; child2 = child2->next)
{
char *text2;
if(child2->type != PURPLE_XMLNODE_TYPE_TAG)
continue;
text2 = purple_xmlnode_get_data(child2);
if(text2 && purple_strequal(child2->name, "ORGNAME")) {
purple_notify_user_info_add_pair_plaintext(user_info, _("Organization Name"), text2);
} else if(text2 && purple_strequal(child2->name, "ORGUNIT")) {
purple_notify_user_info_add_pair_plaintext(user_info, _("Organization Unit"), text2);
}
g_free(text2);
}
} else if(text && purple_strequal(child->name, "TITLE")) {
purple_notify_user_info_add_pair_plaintext(user_info, _("Job Title"), text);
} else if(text && purple_strequal(child->name, "ROLE")) {
purple_notify_user_info_add_pair_plaintext(user_info, _("Role"), text);
} else if(text && purple_strequal(child->name, "DESC")) {
purple_notify_user_info_add_pair_plaintext(user_info, _("Description"), text);
} else if(purple_strequal(child->name, "PHOTO") ||
purple_strequal(child->name, "LOGO")) {
char *bintext = NULL;
PurpleXmlNode *binval;
if ((binval = purple_xmlnode_get_child(child, "BINVAL")) &&
(bintext = purple_xmlnode_get_data(binval))) {
gsize size;
guchar *data;
gboolean photo = purple_strequal(child->name, "PHOTO");
data = g_base64_decode(bintext, &size);
if (data) {
PurpleImage *img;
guint img_id;
char *img_text;
char *hash;
img = purple_image_new_from_data(data, size);
img_id = purple_image_store_add(img);
jbi->vcard_images = g_slist_prepend(jbi->vcard_images, img);
img_text = g_strdup_printf("<img src='"
PURPLE_IMAGE_STORE_PROTOCOL "%u'>", img_id);
purple_notify_user_info_add_pair_html(user_info, (photo ? _("Photo") : _("Logo")), img_text);
hash = g_compute_checksum_for_data(G_CHECKSUM_SHA1, data, size);
purple_buddy_icons_set_for_user(account, bare_jid, data, size, hash);
g_free(hash);
g_free(img_text);
}
g_free(bintext);
}
}
g_free(text);
}
}
if (serverside_alias) {
PurpleBuddy *b;
/* If we found a serverside alias, set it and tell the core */
purple_serv_got_alias(js->gc, bare_jid, serverside_alias);
b = purple_blist_find_buddy(account, bare_jid);
if (b) {
purple_blist_node_set_string((PurpleBlistNode*)b, "servernick", serverside_alias);
}
g_free(serverside_alias);
}
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 guint jbir_hash(gconstpointer v)
{
if (v)
return g_str_hash(v);
else
return 0;
}
static gboolean jbir_equal(gconstpointer v1, gconstpointer v2)
{
const gchar *resource_1 = v1;
const gchar *resource_2 = v2;
return purple_strequal(resource_1, resource_2);
}
static void jabber_version_parse(JabberStream *js, const char *from,
JabberIqType type, const char *id,
PurpleXmlNode *packet, gpointer data)
{
JabberBuddyInfo *jbi = data;
PurpleXmlNode *query;
char *resource_name;
g_return_if_fail(jbi != NULL);
jabber_buddy_info_remove_id(jbi, id);
if(!from)
return;
resource_name = jabber_get_resource(from);
if(resource_name) {
if (type == JABBER_IQ_RESULT) {
if((query = purple_xmlnode_get_child(packet, "query"))) {
JabberBuddyResource *jbr = jabber_buddy_find_resource(jbi->jb, resource_name);
if(jbr) {
PurpleXmlNode *node;
if((node = purple_xmlnode_get_child(query, "name"))) {
jbr->client.name = purple_xmlnode_get_data(node);
}
if((node = purple_xmlnode_get_child(query, "version"))) {
jbr->client.version = purple_xmlnode_get_data(node);
}
if((node = purple_xmlnode_get_child(query, "os"))) {
jbr->client.os = purple_xmlnode_get_data(node);
}
}
}
}
g_free(resource_name);
}
jabber_buddy_info_show_if_ready(jbi);
}
static void jabber_last_parse(JabberStream *js, const char *from,
JabberIqType type, const char *id,
PurpleXmlNode *packet, gpointer data)
{
JabberBuddyInfo *jbi = data;
PurpleXmlNode *query;
char *resource_name;
const char *seconds;
g_return_if_fail(jbi != NULL);
jabber_buddy_info_remove_id(jbi, id);
if(!from)
return;
resource_name = jabber_get_resource(from);
if(resource_name) {
if (type == JABBER_IQ_RESULT) {
if((query = purple_xmlnode_get_child(packet, "query"))) {
seconds = purple_xmlnode_get_attrib(query, "seconds");
if(seconds) {
char *end = NULL;
long sec = strtol(seconds, &end, 10);
JabberBuddy *jb = NULL;
char *resource = NULL;
char *buddy_name = NULL;
JabberBuddyResource *jbr = NULL;
if(end != seconds) {
JabberBuddyInfoResource *jbir = g_hash_table_lookup(jbi->resources, resource_name);
if(jbir) {
jbir->idle_seconds = sec;
}
}
/* Update the idle time of the buddy resource, if we got it.
This will correct the value when a server doesn't mark
delayed presence and we got the presence when signing on */
jb = jabber_buddy_find(js, from, FALSE);
if (jb) {
resource = jabber_get_resource(from);
buddy_name = jabber_get_bare_jid(from);
/* if the resource already has an idle time set, we
must have gotten it originally from a presence. In
this case we update it. Otherwise don't update it, to
avoid setting an idle and not getting informed about
the resource getting unidle */
if (resource && buddy_name) {
jbr = jabber_buddy_find_resource(jb, resource);
if (jbr) {
if (jbr->idle) {
if (sec) {
jbr->idle = time(NULL) - sec;
} else {
jbr->idle = 0;
}
if (jbr ==
jabber_buddy_find_resource(jb, NULL)) {
purple_protocol_got_user_idle(purple_connection_get_account(js->gc),
buddy_name, jbr->idle, jbr->idle);
}
}
}
}
g_free(resource);
g_free(buddy_name);
}
}
}
}
g_free(resource_name);
}
jabber_buddy_info_show_if_ready(jbi);
}
static void jabber_last_offline_parse(JabberStream *js, const char *from,
JabberIqType type, const char *id,
PurpleXmlNode *packet, gpointer data)
{
JabberBuddyInfo *jbi = data;
PurpleXmlNode *query;
const char *seconds;
g_return_if_fail(jbi != NULL);
jabber_buddy_info_remove_id(jbi, id);
if (type == JABBER_IQ_RESULT) {
if((query = purple_xmlnode_get_child(packet, "query"))) {
seconds = purple_xmlnode_get_attrib(query, "seconds");
if(seconds) {
char *end = NULL;
long sec = strtol(seconds, &end, 10);
if(end != seconds) {
jbi->last_seconds = sec;
}
}
jbi->last_message = purple_xmlnode_get_data(query);
}
}
jabber_buddy_info_show_if_ready(jbi);
}
static void jabber_time_parse(JabberStream *js, const char *from,
JabberIqType type, const char *id,
PurpleXmlNode *packet, gpointer data)
{
JabberBuddyInfo *jbi = data;
JabberBuddyResource *jbr;
char *resource_name;
g_return_if_fail(jbi != NULL);
jabber_buddy_info_remove_id(jbi, id);
if (!from)
return;
resource_name = jabber_get_resource(from);
jbr = resource_name ? jabber_buddy_find_resource(jbi->jb, resource_name) : NULL;
g_free(resource_name);
if (jbr) {
if (type == JABBER_IQ_RESULT) {
PurpleXmlNode *time = purple_xmlnode_get_child(packet, "time");
PurpleXmlNode *tzo = time ? purple_xmlnode_get_child(time, "tzo") : NULL;
char *tzo_data = tzo ? purple_xmlnode_get_data(tzo) : NULL;
if (tzo_data) {
char *c = tzo_data;
int hours, minutes;
if (tzo_data[0] == 'Z' && tzo_data[1] == '\0') {
jbr->tz_off = 0;
} else {
gboolean offset_positive = (tzo_data[0] == '+');
/* [+-]HH:MM */
if (((*c == '+' || *c == '-') && (c = c + 1)) &&
sscanf(c, "%02d:%02d", &hours, &minutes) == 2) {
jbr->tz_off = 60*60*hours + 60*minutes;
if (!offset_positive)
jbr->tz_off *= -1;
} else {
purple_debug_info("jabber", "Ignoring malformed timezone %s",
tzo_data);
}
}
g_free(tzo_data);
}
}
}
jabber_buddy_info_show_if_ready(jbi);
}
void jabber_buddy_remove_all_pending_buddy_info_requests(JabberStream *js)
{
g_slist_free_full(js->pending_buddy_info_requests, (GDestroyNotify)jabber_buddy_info_destroy);
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_delete_link(jbi->ids, jbi->ids);
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 gboolean _client_is_blacklisted(JabberBuddyResource *jbr, const char *ns)
{
/* can't be blacklisted if we don't know what you're running yet */
if(!jbr->client.name)
return FALSE;
if(purple_strequal(ns, NS_LAST_ACTIVITY)) {
if(purple_strequal(jbr->client.name, "Trillian")) {
/* verified by nwalp 2007/05/09 */
if(purple_strequal(jbr->client.version, "3.1.0.121") ||
/* verified by nwalp 2007/09/19 */
purple_strequal(jbr->client.version, "3.1.7.0")) {
return TRUE;
}
}
}
return FALSE;
}
static void
dispatch_queries_for_resource(JabberStream *js, JabberBuddyInfo *jbi,
gboolean is_bare_jid, const char *jid,
JabberBuddyResource *jbr)
{
JabberIq *iq;
JabberBuddyInfoResource *jbir;
char *full_jid = NULL;
const char *to;
if (is_bare_jid && jbr->name) {
full_jid = g_strdup_printf("%s/%s", jid, jbr->name);
to = full_jid;
} else
to = jid;
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");
purple_xmlnode_set_attrib(iq->node, "to", to);
jabber_iq_set_callback(iq, jabber_version_parse, jbi);
jbi->ids = g_slist_prepend(jbi->ids, g_strdup(iq->id));
jabber_iq_send(iq);
}
/* this is to fix the feeling of irritation I get when trying
* to get info on a friend running Trillian, which doesn't
* respond (with an error or otherwise) to jabber:iq:last
* requests. There are a number of Trillian users in my
* office. */
if(!_client_is_blacklisted(jbr, NS_LAST_ACTIVITY)) {
iq = jabber_iq_new_query(js, JABBER_IQ_GET, NS_LAST_ACTIVITY);
purple_xmlnode_set_attrib(iq->node, "to", to);
jabber_iq_set_callback(iq, jabber_last_parse, jbi);
jbi->ids = g_slist_prepend(jbi->ids, g_strdup(iq->id));
jabber_iq_send(iq);
}
if (jbr->tz_off == PURPLE_NO_TZ_OFF &&
(!jbr->caps.info ||
jabber_resource_has_capability(jbr, NS_ENTITY_TIME))) {
PurpleXmlNode *child;
iq = jabber_iq_new(js, JABBER_IQ_GET);
purple_xmlnode_set_attrib(iq->node, "to", to);
child = purple_xmlnode_new_child(iq->node, "time");
purple_xmlnode_set_namespace(child, NS_ENTITY_TIME);
jabber_iq_set_callback(iq, jabber_time_parse, jbi);
jbi->ids = g_slist_prepend(jbi->ids, g_strdup(iq->id));
jabber_iq_send(iq);
}
g_free(full_jid);
}
static void jabber_buddy_get_info_for_jid(JabberStream *js, const char *jid)
{
JabberIq *iq;
PurpleXmlNode *vcard;
GList *resources;
JabberBuddy *jb;
JabberBuddyInfo *jbi;
const char *slash;
gboolean is_bare_jid;
jb = jabber_buddy_find(js, jid, TRUE);
/* invalid JID */
if(!jb)
return;
slash = strchr(jid, '/');
is_bare_jid = (slash == NULL);
jbi = g_new0(JabberBuddyInfo, 1);
jbi->jid = g_strdup(jid);
jbi->js = js;
jbi->jb = jb;
jbi->resources = g_hash_table_new_full(jbir_hash, jbir_equal, g_free, jabber_buddy_info_resource_free);
jbi->user_info = purple_notify_user_info_new();
iq = jabber_iq_new(js, JABBER_IQ_GET);
purple_xmlnode_set_attrib(iq->node, "to", jid);
vcard = purple_xmlnode_new_child(iq->node, "vCard");
purple_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);
if (is_bare_jid) {
if (jb->resources) {
for(resources = jb->resources; resources; resources = resources->next) {
JabberBuddyResource *jbr = resources->data;
dispatch_queries_for_resource(js, jbi, is_bare_jid, jid, jbr);
}
} else {
/* user is offline, send a jabber:iq:last to find out last time online */
iq = jabber_iq_new_query(js, JABBER_IQ_GET, NS_LAST_ACTIVITY);
purple_xmlnode_set_attrib(iq->node, "to", jid);
jabber_iq_set_callback(iq, jabber_last_offline_parse, jbi);
jbi->ids = g_slist_prepend(jbi->ids, g_strdup(iq->id));
jabber_iq_send(iq);
}
} else {
JabberBuddyResource *jbr = jabber_buddy_find_resource(jb, slash + 1);
if (jbr)
dispatch_queries_for_resource(js, jbi, is_bare_jid, jid, jbr);
else
purple_debug_warning("jabber", "jabber_buddy_get_info_for_jid() "
"was passed JID %s, but there is no corresponding "
"JabberBuddyResource!\n", jid);
}
js->pending_buddy_info_requests = g_slist_prepend(js->pending_buddy_info_requests, jbi);
jbi->timeout_handle = g_timeout_add_seconds(30, jabber_buddy_get_info_timeout, jbi);
}
void
jabber_buddy_get_info(PurpleProtocolServer *protocol_server,
PurpleConnection *gc, const char *who)
{
JabberStream *js = purple_connection_get_protocol_data(gc);
JabberID *jid = jabber_id_new(who);
if (!jid)
return;
if (jid->node && jabber_chat_find(js, jid->node, jid->domain)) {
/* For a conversation, include the resource (indicates the user). */
jabber_buddy_get_info_for_jid(js, who);
} else {
char *bare_jid = jabber_get_bare_jid(who);
jabber_buddy_get_info_for_jid(js, bare_jid);
g_free(bare_jid);
}
jabber_id_free(jid);
}
static void jabber_buddy_set_invisibility(JabberStream *js, const char *who,
gboolean invisible)
{
PurplePresence *gpresence;
PurpleAccount *account;
PurpleStatus *status;
JabberBuddy *jb = jabber_buddy_find(js, who, TRUE);
PurpleXmlNode *presence;
JabberBuddyState state;
char *msg;
int priority;
account = purple_connection_get_account(js->gc);
gpresence = purple_account_get_presence(account);
status = purple_presence_get_active_status(gpresence);
purple_status_to_jabber(status, &state, &msg, &priority);
presence = jabber_presence_create_js(js, state, msg, priority);
g_free(msg);
purple_xmlnode_set_attrib(presence, "to", who);
if(invisible) {
purple_xmlnode_set_attrib(presence, "type", "invisible");
jb->invisible |= JABBER_INVIS_BUDDY;
} else {
jb->invisible &= ~JABBER_INVIS_BUDDY;
}
jabber_send(js, presence);
purple_xmlnode_free(presence);
}
static void jabber_buddy_make_invisible(PurpleBlistNode *node, gpointer data)
{
PurpleBuddy *buddy;
PurpleConnection *gc;
JabberStream *js;
g_return_if_fail(PURPLE_IS_BUDDY(node));
buddy = (PurpleBuddy *) node;
gc = purple_account_get_connection(purple_buddy_get_account(buddy));
js = purple_connection_get_protocol_data(gc);
jabber_buddy_set_invisibility(js, purple_buddy_get_name(buddy), TRUE);
}
static void jabber_buddy_make_visible(PurpleBlistNode *node, gpointer data)
{
PurpleBuddy *buddy;
PurpleConnection *gc;
JabberStream *js;
g_return_if_fail(PURPLE_IS_BUDDY(node));
buddy = (PurpleBuddy *) node;
gc = purple_account_get_connection(purple_buddy_get_account(buddy));
js = purple_connection_get_protocol_data(gc);
jabber_buddy_set_invisibility(js, purple_buddy_get_name(buddy), FALSE);
}
static void cancel_presence_notification(gpointer data)
{
PurpleBuddy *buddy;
PurpleConnection *gc;
JabberStream *js;
buddy = data;
gc = purple_account_get_connection(purple_buddy_get_account(buddy));
js = purple_connection_get_protocol_data(gc);
jabber_presence_subscription_set(js, purple_buddy_get_name(buddy), "unsubscribed");
}
static void
jabber_buddy_cancel_presence_notification(PurpleBlistNode *node,
gpointer data)
{
PurpleBuddy *buddy;
PurpleAccount *account;
PurpleConnection *gc;
const gchar *name;
char *msg;
g_return_if_fail(PURPLE_IS_BUDDY(node));
buddy = (PurpleBuddy *) node;
name = purple_buddy_get_name(buddy);
account = purple_buddy_get_account(buddy);
gc = purple_account_get_connection(account);
msg = g_strdup_printf(_("%s will no longer be able to see your status "
"updates. Do you want to continue?"), name);
purple_request_yes_no(gc, NULL, _("Cancel Presence Notification"),
msg, 0 /* Yes */, purple_request_cpar_from_account(account), buddy,
cancel_presence_notification, NULL /* Do nothing */);
g_free(msg);
}
static void jabber_buddy_rerequest_auth(PurpleBlistNode *node, gpointer data)
{
PurpleBuddy *buddy;
PurpleConnection *gc;
JabberStream *js;
g_return_if_fail(PURPLE_IS_BUDDY(node));
buddy = (PurpleBuddy *) node;
gc = purple_account_get_connection(purple_buddy_get_account(buddy));
js = purple_connection_get_protocol_data(gc);
jabber_presence_subscription_set(js, purple_buddy_get_name(buddy), "subscribe");
}
static void jabber_buddy_unsubscribe(PurpleBlistNode *node, gpointer data)
{
PurpleBuddy *buddy;
PurpleConnection *gc;
JabberStream *js;
g_return_if_fail(PURPLE_IS_BUDDY(node));
buddy = (PurpleBuddy *) node;
gc = purple_account_get_connection(purple_buddy_get_account(buddy));
js = purple_connection_get_protocol_data(gc);
jabber_presence_subscription_set(js, purple_buddy_get_name(buddy), "unsubscribe");
}
static void jabber_buddy_login(PurpleBlistNode *node, gpointer data) {
if(PURPLE_IS_BUDDY(node)) {
/* simply create a directed presence of the current status */
PurpleBuddy *buddy = (PurpleBuddy *) node;
PurpleConnection *gc = purple_account_get_connection(purple_buddy_get_account(buddy));
JabberStream *js = purple_connection_get_protocol_data(gc);
PurpleAccount *account = purple_connection_get_account(gc);
PurplePresence *gpresence = purple_account_get_presence(account);
PurpleStatus *status = purple_presence_get_active_status(gpresence);
PurpleXmlNode *presence;
JabberBuddyState state;
char *msg;
int priority;
purple_status_to_jabber(status, &state, &msg, &priority);
presence = jabber_presence_create_js(js, state, msg, priority);
g_free(msg);
purple_xmlnode_set_attrib(presence, "to", purple_buddy_get_name(buddy));
jabber_send(js, presence);
purple_xmlnode_free(presence);
}
}
static void jabber_buddy_logout(PurpleBlistNode *node, gpointer data) {
if(PURPLE_IS_BUDDY(node)) {
/* simply create a directed unavailable presence */
PurpleBuddy *buddy = (PurpleBuddy *) node;
PurpleConnection *gc = purple_account_get_connection(purple_buddy_get_account(buddy));
JabberStream *js = purple_connection_get_protocol_data(gc);
PurpleXmlNode *presence;
presence = jabber_presence_create_js(js, JABBER_BUDDY_STATE_UNAVAILABLE, NULL, 0);
purple_xmlnode_set_attrib(presence, "to", purple_buddy_get_name(buddy));
jabber_send(js, presence);
purple_xmlnode_free(presence);
}
}
static GList *jabber_buddy_menu(PurpleBuddy *buddy)
{
PurpleConnection *gc = purple_account_get_connection(purple_buddy_get_account(buddy));
JabberStream *js = purple_connection_get_protocol_data(gc);
const char *name = purple_buddy_get_name(buddy);
JabberBuddy *jb = jabber_buddy_find(js, name, TRUE);
GList *jbrs;
GList *m = NULL;
PurpleActionMenu *act;
if(!jb)
return m;
if (js->protocol_version.major == 0 && js->protocol_version.minor == 9 &&
jb != js->user_jb) {
if(jb->invisible & JABBER_INVIS_BUDDY) {
act = purple_action_menu_new(_("Un-hide From"),
PURPLE_CALLBACK(jabber_buddy_make_visible),
NULL, NULL);
} else {
act = purple_action_menu_new(_("Temporarily Hide From"),
PURPLE_CALLBACK(jabber_buddy_make_invisible),
NULL, NULL);
}
m = g_list_append(m, act);
}
if(jb->subscription & JABBER_SUB_FROM && jb != js->user_jb) {
act = purple_action_menu_new(_("Cancel Presence Notification"),
PURPLE_CALLBACK(jabber_buddy_cancel_presence_notification),
NULL, NULL);
m = g_list_append(m, act);
}
if(!(jb->subscription & JABBER_SUB_TO)) {
act = purple_action_menu_new(_("(Re-)Request authorization"),
PURPLE_CALLBACK(jabber_buddy_rerequest_auth),
NULL, NULL);
m = g_list_append(m, act);
} else if (jb != js->user_jb) {
/* shouldn't this just happen automatically when the buddy is
removed? */
act = purple_action_menu_new(_("Unsubscribe"),
PURPLE_CALLBACK(jabber_buddy_unsubscribe),
NULL, NULL);
m = g_list_append(m, act);
}
/*
* This if-condition implements parts of XEP-0100: Gateway Interaction
*
* According to stpeter, there is no way to know if a jid on the roster is a gateway without sending a disco#info.
* However, since the gateway might appear offline to us, we cannot get that information. Therefore, I just assume
* that gateways on the roster can be identified by having no '@' in their jid. This is a faily safe assumption, since
* people don't tend to have a server or other service there.
*
* TODO: Use disco#info...
*/
if (strchr(name, '@') == NULL) {
act = purple_action_menu_new(_("Log In"),
PURPLE_CALLBACK(jabber_buddy_login),
NULL, NULL);
m = g_list_append(m, act);
act = purple_action_menu_new(_("Log Out"),
PURPLE_CALLBACK(jabber_buddy_logout),
NULL, NULL);
m = g_list_append(m, act);
}
/* add all ad hoc commands to the action menu */
for(jbrs = jb->resources; jbrs; jbrs = g_list_next(jbrs)) {
JabberBuddyResource *jbr = jbrs->data;
GList *commands;
if (!jbr->commands)
continue;
for(commands = jbr->commands; commands; commands = g_list_next(commands)) {
JabberAdHocCommands *cmd = commands->data;
act = purple_action_menu_new(cmd->name, PURPLE_CALLBACK(jabber_adhoc_execute_action), cmd, NULL);
m = g_list_append(m, act);
}
}
return m;
}
GList *
jabber_blist_node_menu(PurpleProtocolClient *client, PurpleBlistNode *node)
{
if(PURPLE_IS_BUDDY(node)) {
return jabber_buddy_menu((PurpleBuddy *) node);
} else {
return NULL;
}
}
static void user_search_result_add_buddy_cb(PurpleConnection *gc, GList *row, void *user_data)
{
/* XXX find out the jid */
purple_blist_request_add_buddy(purple_connection_get_account(gc),
g_list_nth_data(row, 0), NULL, NULL);
}
static void user_search_result_cb(JabberStream *js, const char *from,
JabberIqType type, const char *id,
PurpleXmlNode *packet, gpointer data)
{
PurpleNotifySearchResults *results;
PurpleNotifySearchColumn *column;
PurpleXmlNode *x, *query, *item, *field;
/* XXX error checking? */
if(!(query = purple_xmlnode_get_child(packet, "query")))
return;
results = purple_notify_searchresults_new();
if((x = purple_xmlnode_get_child_with_namespace(query, "x", "jabber:x:data"))) {
PurpleXmlNode *reported;
GSList *column_vars = NULL;
purple_debug_info("jabber", "new-skool\n");
if((reported = purple_xmlnode_get_child(x, "reported"))) {
PurpleXmlNode *field = purple_xmlnode_get_child(reported, "field");
while(field) {
const char *var = purple_xmlnode_get_attrib(field, "var");
const char *label = purple_xmlnode_get_attrib(field, "label");
if(var) {
column = purple_notify_searchresults_column_new(label ? label : var);
purple_notify_searchresults_column_add(results, column);
column_vars = g_slist_append(column_vars, (char *)var);
}
field = purple_xmlnode_get_next_twin(field);
}
}
item = purple_xmlnode_get_child(x, "item");
while(item) {
GList *row = NULL;
GSList *l;
PurpleXmlNode *valuenode;
const char *var;
for (l = column_vars; l != NULL; l = l->next) {
/*
* Build a row containing the strings that correspond
* to each column of the search results.
*/
for (field = purple_xmlnode_get_child(item, "field");
field != NULL;
field = purple_xmlnode_get_next_twin(field))
{
if ((var = purple_xmlnode_get_attrib(field, "var")) &&
purple_strequal(var, l->data) &&
(valuenode = purple_xmlnode_get_child(field, "value")))
{
char *value = purple_xmlnode_get_data(valuenode);
row = g_list_append(row, value);
break;
}
}
if (field == NULL)
/* No data for this column */
row = g_list_append(row, NULL);
}
purple_notify_searchresults_row_add(results, row);
item = purple_xmlnode_get_next_twin(item);
}
g_slist_free(column_vars);
} else {
/* old skool */
purple_debug_info("jabber", "old-skool\n");
column = purple_notify_searchresults_column_new(_("JID"));
purple_notify_searchresults_column_add(results, column);
column = purple_notify_searchresults_column_new(_("First Name"));
purple_notify_searchresults_column_add(results, column);
column = purple_notify_searchresults_column_new(_("Last Name"));
purple_notify_searchresults_column_add(results, column);
column = purple_notify_searchresults_column_new(_("Nickname"));
purple_notify_searchresults_column_add(results, column);
column = purple_notify_searchresults_column_new(_("Email"));
purple_notify_searchresults_column_add(results, column);
for(item = purple_xmlnode_get_child(query, "item"); item; item = purple_xmlnode_get_next_twin(item)) {
const char *jid;
PurpleXmlNode *node;
GList *row = NULL;
if(!(jid = purple_xmlnode_get_attrib(item, "jid")))
continue;
row = g_list_append(row, g_strdup(jid));
node = purple_xmlnode_get_child(item, "first");
row = g_list_append(row, node ? purple_xmlnode_get_data(node) : NULL);
node = purple_xmlnode_get_child(item, "last");
row = g_list_append(row, node ? purple_xmlnode_get_data(node) : NULL);
node = purple_xmlnode_get_child(item, "nick");
row = g_list_append(row, node ? purple_xmlnode_get_data(node) : NULL);
node = purple_xmlnode_get_child(item, "email");
row = g_list_append(row, node ? purple_xmlnode_get_data(node) : NULL);
purple_debug_info("jabber", "row=%p\n", row);
purple_notify_searchresults_row_add(results, row);
}
}
purple_notify_searchresults_button_add(results, PURPLE_NOTIFY_BUTTON_ADD,
user_search_result_add_buddy_cb);
purple_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, PurpleXmlNode *result, gpointer data)
{
PurpleXmlNode *query;
JabberIq *iq;
char *dir_server = data;
const char *type;
/* if they've cancelled the search, we're
* just going to get an error if we send
* a cancel, so skip it */
type = purple_xmlnode_get_attrib(result, "type");
if(purple_strequal(type, "cancel")) {
g_free(dir_server);
return;
}
iq = jabber_iq_new_query(js, JABBER_IQ_SET, "jabber:iq:search");
query = purple_xmlnode_get_child(iq->node, "query");
purple_xmlnode_insert_child(query, result);
jabber_iq_set_callback(iq, user_search_result_cb, NULL);
purple_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, PurpleRequestFields *fields)
{
g_free(usi->directory_server);
g_free(usi);
}
static void user_search_cb(struct user_search_info *usi, PurpleRequestFields *fields)
{
JabberStream *js = usi->js;
JabberIq *iq;
PurpleXmlNode *query;
GList *groups, *flds;
iq = jabber_iq_new_query(js, JABBER_IQ_SET, "jabber:iq:search");
query = purple_xmlnode_get_child(iq->node, "query");
for(groups = purple_request_fields_get_groups(fields); groups; groups = groups->next) {
for(flds = purple_request_field_group_get_fields(groups->data);
flds; flds = flds->next) {
PurpleRequestField *field = flds->data;
const char *id = purple_request_field_get_id(field);
const char *value = purple_request_field_string_get_value(field);
if(value && (purple_strequal(id, "first") || purple_strequal(id, "last") || purple_strequal(id, "nick") || purple_strequal(id, "email"))) {
PurpleXmlNode *y = purple_xmlnode_new_child(query, id);
purple_xmlnode_insert_data(y, value, -1);
}
}
}
jabber_iq_set_callback(iq, user_search_result_cb, NULL);
purple_xmlnode_set_attrib(iq->node, "to", usi->directory_server);
jabber_iq_send(iq);
g_free(usi->directory_server);
g_free(usi);
}
static void user_search_fields_result_cb(JabberStream *js, const char *from,
JabberIqType type, const char *id,
PurpleXmlNode *packet, gpointer data)
{
PurpleXmlNode *query, *x;
if (!from)
return;
if (type == JABBER_IQ_ERROR) {
char *msg = jabber_parse_error(js, packet, NULL);
if(!msg)
msg = g_strdup(_("Unknown error"));
purple_notify_error(js->gc, _("Directory Query Failed"),
_("Could not query the directory server."), msg,
purple_request_cpar_from_connection(js->gc));
g_free(msg);
return;
}
if(!(query = purple_xmlnode_get_child(packet, "query")))
return;
if((x = purple_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;
PurpleXmlNode *instnode;
char *instructions = NULL;
PurpleRequestFields *fields;
PurpleRequestFieldGroup *group;
PurpleRequestField *field;
/* old skool */
fields = purple_request_fields_new();
group = purple_request_field_group_new(NULL);
purple_request_fields_add_group(fields, group);
if((instnode = purple_xmlnode_get_child(query, "instructions")))
{
char *tmp = purple_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 XMPP users."));
}
if(purple_xmlnode_get_child(query, "first")) {
field = purple_request_field_string_new("first", _("First Name"),
NULL, FALSE);
purple_request_field_group_add_field(group, field);
}
if(purple_xmlnode_get_child(query, "last")) {
field = purple_request_field_string_new("last", _("Last Name"),
NULL, FALSE);
purple_request_field_group_add_field(group, field);
}
if(purple_xmlnode_get_child(query, "nick")) {
field = purple_request_field_string_new("nick", _("Nickname"),
NULL, FALSE);
purple_request_field_group_add_field(group, field);
}
if(purple_xmlnode_get_child(query, "email")) {
field = purple_request_field_string_new("email", _("Email Address"),
NULL, FALSE);
purple_request_field_group_add_field(group, field);
}
usi = g_new0(struct user_search_info, 1);
usi->js = js;
usi->directory_server = g_strdup(from);
purple_request_fields(js->gc, _("Search for XMPP users"),
_("Search for XMPP users"), instructions, fields,
_("Search"), G_CALLBACK(user_search_cb),
_("Cancel"), G_CALLBACK(user_search_cancel_cb),
purple_request_cpar_from_connection(js->gc),
usi);
g_free(instructions);
}
}
void jabber_user_search(JabberStream *js, const char *directory)
{
JabberIq *iq;
/* XXX: should probably better validate the directory we're given */
if(!directory || !*directory) {
purple_notify_error(js->gc, _("Invalid Directory"),
_("Invalid Directory"), NULL,
purple_request_cpar_from_connection(js->gc));
return;
}
/* If the value provided isn't the disco#info default, persist it. Otherwise,
make sure we aren't persisting an old value */
if(js->user_directories && js->user_directories->data &&
purple_strequal(directory, js->user_directories->data)) {
purple_account_set_string(purple_connection_get_account(js->gc), "user_directory", "");
}
else {
purple_account_set_string(purple_connection_get_account(js->gc), "user_directory", directory);
}
iq = jabber_iq_new_query(js, JABBER_IQ_GET, "jabber:iq:search");
purple_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(PurpleProtocolAction *action)
{
PurpleConnection *gc = (PurpleConnection *) action->connection;
JabberStream *js = purple_connection_get_protocol_data(gc);
const char *def_val = purple_account_get_string(purple_connection_get_account(js->gc), "user_directory", "");
if(!*def_val && js->user_directories)
def_val = js->user_directories->data;
purple_request_input(gc, _("Enter a User Directory"), _("Enter a User Directory"),
_("Select a user directory to search"),
def_val,
FALSE, FALSE, NULL,
_("Search Directory"), PURPLE_CALLBACK(jabber_user_search),
_("Cancel"), NULL,
NULL, js);
}
gboolean
jabber_resource_know_capabilities(const JabberBuddyResource *jbr)
{
return jbr->caps.info != NULL;
}
gboolean
jabber_resource_has_capability(const JabberBuddyResource *jbr, const gchar *cap)
{
const GList *node = NULL;
const JabberCapsNodeExts *exts;
if (!jbr->caps.info) {
purple_debug_info("jabber",
"Unable to find caps: nothing known about buddy\n");
return FALSE;
}
node = g_list_find_custom(jbr->caps.info->features, cap, (GCompareFunc)strcmp);
if (!node && jbr->caps.exts && jbr->caps.info->exts) {
const GList *ext;
exts = jbr->caps.info->exts;
/* Walk through all the enabled caps, checking each list for the cap.
* Don't check it twice, though. */
for (ext = jbr->caps.exts; ext && !node; ext = ext->next) {
GList *features = g_hash_table_lookup(exts->exts, ext->data);
if (features)
node = g_list_find_custom(features, cap, (GCompareFunc)strcmp);
}
}
return (node != NULL);
}
gboolean
jabber_buddy_has_capability(const JabberBuddy *jb, const gchar *cap)
{
JabberBuddyResource *jbr = jabber_buddy_find_resource((JabberBuddy*)jb, NULL);
if (!jbr) {
purple_debug_info("jabber",
"Unable to find caps: buddy might be offline\n");
return FALSE;
}
return jabber_resource_has_capability(jbr, cap);
}
const gchar *
jabber_resource_get_identity_category_type(const JabberBuddyResource *jbr,
const gchar *category)
{
const GList *iter = NULL;
if (jbr->caps.info) {
for (iter = jbr->caps.info->identities ; iter ; iter = g_list_next(iter)) {
const JabberIdentity *identity =
(JabberIdentity *) iter->data;
if (purple_strequal(identity->category, category)) {
return identity->type;
}
}
}
return NULL;
}