Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

XEP-0084: User Avatar (fetching only) #4

Merged
merged 5 commits into from
Dec 31, 2020
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/capabilities.c
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,8 @@ static const Feature self_advertised_features[] =
#endif

{ FEATURE_FIXED, NS_DISCO_INFO },
{ FEATURE_FIXED, NS_AVATAR_METADATA },
{ FEATURE_FIXED, NS_AVATAR_METADATA "+notify" },
{ FEATURE_FIXED, NS_CHAT_STATES },
{ FEATURE_FIXED, NS_NICK },
{ FEATURE_FIXED, NS_NICK "+notify" },
Expand Down
270 changes: 259 additions & 11 deletions src/conn-avatars.c
Original file line number Diff line number Diff line change
Expand Up @@ -33,11 +33,19 @@
#include "namespaces.h"
#include "vcard-manager.h"
#include "util.h"
#include "request-pipeline.h"

#define DEBUG_FLAG GABBLE_DEBUG_CONNECTION

#include "debug.h"

typedef struct {
TpHandle handle;
} pep_request_ctx;

static pep_request_ctx *
pep_avatar_request_data (GabbleConnection *conn, TpHandle handle, gchar *id);

/* If the SHA1 has changed, this function will copy it to self_presence,
* emit a signal and push it to the server. */
static gboolean
Expand Down Expand Up @@ -711,17 +719,27 @@ gabble_connection_request_avatars (TpSvcConnectionInterfaceAvatars *iface,
if (NULL == g_hash_table_lookup (self->avatar_requests,
GUINT_TO_POINTER (contact)))
{
RequestAvatarsContext *ctx = g_slice_new (RequestAvatarsContext);

ctx->conn = self;
ctx->iface = iface;
ctx->handle = contact;

g_hash_table_insert (self->avatar_requests,
GUINT_TO_POINTER (contact), ctx);

gabble_vcard_manager_request (self->vcard_manager,
contact, 0, request_avatars_cb, ctx, NULL);
gchar *id;
if (id = g_hash_table_lookup (self->pep_avatar_hashes, GINT_TO_POINTER(contact)))
rufferson marked this conversation as resolved.
Show resolved Hide resolved
{
pep_request_ctx *ctx = pep_avatar_request_data (self, contact, id);
g_hash_table_insert (self->avatar_requests,
GUINT_TO_POINTER (contact), ctx);
}
else
{
RequestAvatarsContext *ctx = g_slice_new (RequestAvatarsContext);

ctx->conn = self;
ctx->iface = iface;
ctx->handle = contact;

g_hash_table_insert (self->avatar_requests,
GUINT_TO_POINTER (contact), ctx);

gabble_vcard_manager_request (self->vcard_manager,
contact, 0, request_avatars_cb, ctx, NULL);
}
}
}
}
Expand Down Expand Up @@ -912,9 +930,12 @@ conn_avatars_fill_contact_attributes (GObject *obj,
if (NULL != presence)
{
GValue *val = tp_g_value_slice_new (G_TYPE_STRING);
gchar *id;

if (NULL != presence->avatar_sha1)
g_value_set_string (val, presence->avatar_sha1);
else if (id = g_hash_table_lookup (self->pep_avatar_hashes, GINT_TO_POINTER(handle)))
rufferson marked this conversation as resolved.
Show resolved Hide resolved
g_value_set_string (val, id);
else
g_value_set_string (val, "");

Expand All @@ -924,6 +945,227 @@ conn_avatars_fill_contact_attributes (GObject *obj,
}
}

static void
pep_avatar_request_data_cb (
GabbleConnection *conn,
WockyStanza *msg,
gpointer user_data,
GError *error)
{
pep_request_ctx *ctx = user_data;
TpHandle handle = ctx->handle;

WockyNode *pubsub_node, *items_node, *item_node, *data_node;

const gchar *binval_value;
gchar *sha1;
// we only request avatars of type png, so this is fixed for now
const gchar *mime_type = "image/png";
gchar *bindata;
gsize outlen;
GArray *arr;

g_slice_free (pep_request_ctx, ctx);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this makes hash contain dangling pointer for several cycles, let's better reorder these calls perhaps - remove then free. assert will abort the execution whatsoever.


g_assert (g_hash_table_lookup (conn->avatar_requests,
GUINT_TO_POINTER (handle)));

g_hash_table_remove (conn->avatar_requests,
GUINT_TO_POINTER (handle));

pubsub_node = wocky_node_get_child_ns (
wocky_stanza_get_top_node (msg), "pubsub", NS_PUBSUB);
if (pubsub_node == NULL)
{
pubsub_node = wocky_node_get_child_ns (
wocky_stanza_get_top_node (msg), "pubsub", NS_PUBSUB "#event");

if (pubsub_node == NULL)
{
STANZA_DEBUG (msg, "PEP reply with no <pubsub>, ignoring");
return;
}
else
{
STANZA_DEBUG (msg, "PEP reply from buggy server with #event "
"on <pubsub> namespace");
}
}

items_node = wocky_node_get_child (pubsub_node, "items");
if (items_node == NULL)
{
STANZA_DEBUG (msg, "No items in PEP reply");
return;
}

item_node = wocky_node_get_child (items_node, "item");
if (item_node == NULL)
{
STANZA_DEBUG (msg, "No item in PEP reply");
return;
}

data_node = wocky_node_get_child (item_node, "data");
if (data_node == NULL)
{
STANZA_DEBUG (msg, "No data in PEP reply");
return;
}

binval_value = data_node->content;

if (NULL == binval_value)
{
g_set_error (&error, TP_ERROR, TP_ERROR_NOT_AVAILABLE,
"contact avatar is missing binval content");
return;
}

bindata = (gchar *) g_base64_decode (binval_value, &outlen);

if (bindata == NULL)
{
g_set_error (&error, TP_ERROR, TP_ERROR_NOT_AVAILABLE,
"failed to decode avatar from base64");
return;
}

sha1 = sha1_hex (bindata, outlen);
arr = g_array_new (FALSE, FALSE, sizeof (gchar));
g_array_append_vals (arr, bindata, outlen);
tp_svc_connection_interface_avatars_emit_avatar_retrieved (conn, handle,
sha1, arr, mime_type);

g_array_unref (arr);
g_free (bindata);

DEBUG ("retrieved avatar from %d with size=%zd, sha1='%s'", handle, outlen, sha1);

// is this really needed?
if (sha1)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this shouldn't be needed, if contact wants to maintain interop they will send the hash explicitly or by xep-0398. if some clients implementations rely on this information - they should rather not.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

but is needed if we don't maintain known tokens via pep. also there's no verification of whether this resulting sha1 matches to the pep id (which is also sha1). On the other hand we're not retrieving specific node id so we don't really know what have we fetched :)

{
GabblePresence *presence = gabble_presence_cache_get (conn->presence_cache, handle);
if (presence)
{
DEBUG ("presence found");

g_free (presence->avatar_sha1);
presence->avatar_sha1 = g_strdup (sha1);
}
else
DEBUG ("presence not found");
}

g_free (sha1);
}

static void
pep_avatar_metadata_node_changed (WockyPepService *pep,
WockyBareContact *contact,
WockyStanza *stanza,
WockyNode *item,
GabbleConnection *conn)
{
TpBaseConnection *base = (TpBaseConnection *) conn;
TpHandleRepoIface *contact_repo = tp_base_connection_get_handles (
(TpBaseConnection *) conn, TP_HANDLE_TYPE_CONTACT);
TpHandle handle;
WockyNode *metadata, *info;
WockyNodeIter iter;
const gchar *jid;
const gchar *sha1;

jid = wocky_bare_contact_get_jid (contact);

handle = tp_handle_ensure (contact_repo, jid, NULL, NULL);
if (handle == 0)
{
DEBUG ("Invalid from: %s", jid);
return;
}

if (NULL == item)
{
STANZA_DEBUG (stanza, "PEP event without item node, ignoring");
return;
}

metadata = wocky_node_get_child_ns (item, "metadata", NS_AVATAR_METADATA);
if (NULL == metadata)
{
STANZA_DEBUG (stanza, "PEP item without metadata node, ignoring");
return;
}

gchar *type;
info = NULL;
wocky_node_iter_init (&iter, metadata, "info", NULL);
while (wocky_node_iter_next (&iter, &info))
{
gchar *url = wocky_node_get_attribute (info, "url");
type = wocky_node_get_attribute (info, "type");
rufferson marked this conversation as resolved.
Show resolved Hide resolved
//Found one of type png which is not an url node
if ((type) && (g_strcmp0(type, "image/png") == 0) && (!url))
{
break;
}
}

if (!info)
{
STANZA_DEBUG (stanza, "avatar metadata without compatible type, ignoring");
return;
}

sha1 = wocky_node_get_attribute (info, "id");

if (handle == tp_base_connection_get_self_handle (base))
update_own_avatar_sha1 (conn, sha1, NULL);
else
tp_svc_connection_interface_avatars_emit_avatar_updated (conn, handle, sha1);

DEBUG ("got pep avatar metadata update of '%s', sha1 = %s", jid, sha1);

g_hash_table_insert (conn->pep_avatar_hashes, GINT_TO_POINTER(handle), g_strdup (sha1));
rufferson marked this conversation as resolved.
Show resolved Hide resolved
}

static pep_request_ctx *
pep_avatar_request_data (GabbleConnection *conn, TpHandle handle, gchar *id)
{
TpBaseConnection *base = (TpBaseConnection *) conn;
pep_request_ctx *ctx;
WockyStanza *msg;

const gchar *jid = NULL;

TpHandleRepoIface *contact_repo = tp_base_connection_get_handles (
base, TP_HANDLE_TYPE_CONTACT);

jid = tp_handle_inspect (contact_repo, handle);

ctx = g_slice_new0 (pep_request_ctx);
ctx->handle = handle;

msg = wocky_stanza_build (WOCKY_STANZA_TYPE_IQ, WOCKY_STANZA_SUB_TYPE_GET,
NULL, jid,
'(', "pubsub",
':', NS_PUBSUB,
'(', "items",
'@', "node", NS_AVATAR_DATA,
rufferson marked this conversation as resolved.
Show resolved Hide resolved
'(', "item",
'@', "id", id,
')',
')',
')',
NULL);

gabble_request_pipeline_enqueue (conn->req_pipeline,
msg, 0, pep_avatar_request_data_cb, ctx);
g_object_unref (msg);

return ctx;
}

void
conn_avatars_init (GabbleConnection *conn)
Expand All @@ -938,6 +1180,12 @@ conn_avatars_init (GabbleConnection *conn)
tp_contacts_mixin_add_contact_attributes_iface (G_OBJECT (conn),
TP_IFACE_CONNECTION_INTERFACE_AVATARS,
conn_avatars_fill_contact_attributes);

conn->pep_avatar = wocky_pep_service_new (NS_AVATAR_METADATA, TRUE);
conn->pep_avatar_hashes = g_hash_table_new (g_direct_hash, g_direct_equal);
rufferson marked this conversation as resolved.
Show resolved Hide resolved

g_signal_connect (conn->pep_avatar, "changed",
G_CALLBACK (pep_avatar_metadata_node_changed), conn);
}


Expand Down
2 changes: 2 additions & 0 deletions src/connection.c
Original file line number Diff line number Diff line change
Expand Up @@ -1386,6 +1386,7 @@ gabble_connection_dispose (GObject *object)
tp_clear_object (&self->pep_olpc_activities);
tp_clear_object (&self->pep_olpc_current_act);
tp_clear_object (&self->pep_olpc_act_props);
tp_clear_object (&self->pep_avatar);
rufferson marked this conversation as resolved.
Show resolved Hide resolved

conn_sidecars_dispose (self);

Expand Down Expand Up @@ -2036,6 +2037,7 @@ connector_connected (GabbleConnection *self,
wocky_pep_service_start (self->pep_olpc_activities, self->session);
wocky_pep_service_start (self->pep_olpc_current_act, self->session);
wocky_pep_service_start (self->pep_olpc_act_props, self->session);
wocky_pep_service_start (self->pep_avatar, self->session);

/* Don't use wocky_session_start as we don't want to start all the
* components (Roster, presence-manager, etc) for now */
Expand Down
4 changes: 4 additions & 0 deletions src/connection.h
Original file line number Diff line number Diff line change
Expand Up @@ -240,6 +240,10 @@ struct _GabbleConnection {
WockyPepService *pep_olpc_activities;
WockyPepService *pep_olpc_current_act;
WockyPepService *pep_olpc_act_props;
WockyPepService *pep_avatar;

/* list to recognize pep avatars */
GHashTable *pep_avatar_hashes;

/* Sidecars */
/* gchar *interface → GabbleSidecar */
Expand Down
2 changes: 2 additions & 0 deletions src/namespaces.h
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@
#define NS_SM2 "urn:xmpp:sm:2"
#define NS_SM3 "urn:xmpp:sm:3"
#define NS_AMP "http://jabber.org/protocol/amp"
#define NS_AVATAR_DATA "urn:xmpp:avatar:data"
#define NS_AVATAR_METADATA "urn:xmpp:avatar:metadata"
#define NS_BYTESTREAMS "http://jabber.org/protocol/bytestreams"
#define NS_CHAT_STATES "http://jabber.org/protocol/chatstates"
#define NS_CHAT_MARKERS "urn:xmpp:chat-markers:0"
Expand Down