/* xmpp-caps.c - XMPP Entity Capabilities support * * Copyright 2008 OpenMoko, Inc. * Authored by Daniel Willmann * * 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 #include #include #include #include #include #include #include #include G_DEFINE_TYPE(XmppCaps, xmpp_caps, G_TYPE_OBJECT); #define XMPP_CAPS_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE((o), XMPP_TYPE_CAPS, XmppCapsPrivate)) typedef struct _XmppCapsPrivate XmppCapsPrivate; struct _XmppCapsPrivate { GList *caps_cache; DiversityXmpp *xmpp; XmppDisco *disco; LmConnection *connection; LmMessageHandler *msghandle; }; struct disco_cb { XmppCaps *caps; XmppCapability *capability; }; static void xmpp_caps_finalize(GObject *obj) { GError *error = NULL; XmppCapsPrivate *priv = XMPP_CAPS_GET_PRIVATE(obj); /* Unregister message handlers */ lm_connection_unregister_message_handler(priv->connection, priv->msghandle, LM_MESSAGE_TYPE_PRESENCE); lm_message_handler_unref(priv->msghandle); priv->msghandle = NULL; lm_connection_unref(priv->connection); g_object_unref(priv->disco); g_object_unref(priv->xmpp); ((GObjectClass *) xmpp_caps_parent_class)->finalize(obj); } static void xmpp_caps_class_init(XmppCapsClass *klass) { GObjectClass *o_class = (GObjectClass *) klass; //o_class->dispose = xmpp_caps_dispose; o_class->finalize = xmpp_caps_finalize; g_type_class_add_private(klass, sizeof(XmppCapsPrivate)); } static void xmpp_caps_init(XmppCaps *caps) { XmppCapsPrivate *priv = XMPP_CAPS_GET_PRIVATE(caps); priv->caps_cache = NULL; } static LmHandlerResult xmpp_caps_handle_msg (LmMessageHandler *handler, LmConnection *connection, LmMessage *message, XmppCaps *caps); XmppCaps *xmpp_caps_new(DiversityXmpp *xmpp, XmppDisco *disco, GError **error) { XmppCaps * caps; XmppCapsPrivate *priv; XmppCapability *capability; XmppDiscoinfo *discoinfo; gchar *hash; caps = g_object_new(XMPP_TYPE_CAPS, NULL); priv = XMPP_CAPS_GET_PRIVATE(caps); priv->xmpp = xmpp; g_object_ref(priv->xmpp); priv->disco = disco; g_object_ref(priv->disco); discoinfo = xmpp_disco_get_discoinfo(priv->disco); hash = xmpp_discoinfo_get_hash(discoinfo, "sha-1"); capability = xmpp_capability_new(discoinfo, "sha-1", hash, NULL); priv->caps_cache = g_list_insert(priv->caps_cache, capability, 0); priv->connection = diversity_xmpp_get_connection(priv->xmpp); lm_connection_ref(priv->connection); /* Add message handler for incoming location events */ priv->msghandle = lm_message_handler_new(xmpp_caps_handle_msg, caps, NULL); lm_connection_register_message_handler(priv->connection, priv->msghandle, LM_MESSAGE_TYPE_PRESENCE, LM_HANDLER_PRIORITY_NORMAL); return caps; } static gint xmpp_capability_find_by_hash(XmppCapability *a, XmppCapability *b) { if (!strcmp(xmpp_capability_get_hash(a), xmpp_capability_get_hash(b))) { return strcmp(xmpp_capability_get_hash_func(a), xmpp_capability_get_hash_func(b)); } return -1; } XmppDiscoinfo *xmpp_caps_get_discoinfo(XmppCaps *caps, const gchar *hash_func, const gchar *hash) { GList *cache; XmppDiscoinfo *discoinfo; XmppCapsPrivate *priv = XMPP_CAPS_GET_PRIVATE(caps); XmppCapability *capability, *search_capability = xmpp_capability_new(NULL, NULL, NULL, NULL); xmpp_capability_set_hash(search_capability, hash_func, hash); cache = g_list_find_custom(priv->caps_cache, search_capability, xmpp_capability_find_by_hash); if (!cache) { return NULL; } capability = cache->data; discoinfo = xmpp_capability_get_discoinfo(capability); g_object_unref(search_capability); return discoinfo; } void xmpp_caps_add_c_node(XmppCaps *caps, LmMessageNode *node) { LmMessageNode *capsnode; XmppDiscoinfo *discoinfo; XmppCapsPrivate *priv = XMPP_CAPS_GET_PRIVATE(caps); guchar *hash; discoinfo = xmpp_disco_get_discoinfo(priv->disco); capsnode = lm_message_node_add_child(node, "c", NULL); lm_message_node_set_attribute(capsnode, "xmlns", XMPP_NS_CAPS); hash = xmpp_discoinfo_get_hash(discoinfo, "sha-1"); /* TODO&FIXME: Make node configurable through top level diversity class */ lm_message_node_set_attributes(capsnode, "node", "http://openmoko.org/#0.1a", "hash", "sha-1", "ver", hash, NULL); g_free(hash); } /* Receives the disco#info reply and adds the hash/disco#info pair into our cache */ static LmHandlerResult *xmpp_caps_handle_disco_reply(LmMessageHandler *handler, LmConnection *connection, LmMessage *message, struct disco_cb *cb_data) { XmppCaps *caps = cb_data->caps; XmppCapsPrivate *priv = XMPP_CAPS_GET_PRIVATE(caps); XmppCapability *capability = cb_data->capability; XmppDiscoinfo *discoinfo; LmMessageNode *temp; const gchar *from; LmMessageNode *node = lm_message_get_node(message); from = lm_message_node_get_attribute(node, "from"); if (strcmp(lm_message_node_get_attribute(node, "type"), "result") != 0) return LM_HANDLER_RESULT_REMOVE_MESSAGE; if (!(node = lm_message_node_get_child(node, "query"))) return LM_HANDLER_RESULT_REMOVE_MESSAGE; if (!strcmp(lm_message_node_get_attribute(node, "xmlns"), XMPP_NS_DISCO_INFO)) { discoinfo = xmpp_discoinfo_new_from_msg_node(node, NULL); xmpp_capability_set_discoinfo(capability, discoinfo); priv->caps_cache = g_list_insert(priv->caps_cache, capability, 0); } g_free(cb_data); return LM_HANDLER_RESULT_REMOVE_MESSAGE; } static LmHandlerResult xmpp_caps_handle_msg(LmMessageHandler *handler, LmConnection *connection, LmMessage *message, XmppCaps *caps) { XmppCapsPrivate *priv = XMPP_CAPS_GET_PRIVATE(caps); LmMessage *msg; LmMessageNode *temp; const gchar *from, *caps_node, *caps_ver, *caps_hash; gchar *msgtype; XmppDiscoinfo *discoinfo; XmppCapability *capability; LmMessageHandler *disco_handler; struct disco_cb *cb_data; LmMessageNode *node = lm_message_get_node(message); msgtype = lm_message_node_get_attribute(node, "type"); if (msgtype && !strcmp(msgtype, "error")) { return LM_HANDLER_RESULT_ALLOW_MORE_HANDLERS; } g_free(msgtype); from = lm_message_node_get_attribute(node, "from"); if (!(node = lm_message_node_get_child(node, "c"))) { return LM_HANDLER_RESULT_ALLOW_MORE_HANDLERS; } if (!strcmp(lm_message_node_get_attribute(node, "xmlns"), XMPP_NS_CAPS)) { /* Get the contents of the caps tag */ caps_node = lm_message_node_get_attribute(node, "node"); caps_ver = lm_message_node_get_attribute(node, "ver"); caps_hash = lm_message_node_get_attribute(node, "hash"); if (caps_hash == NULL) { caps_hash = strdup("sha-1"); } /* Get the disco items associated to the ver string */ discoinfo = xmpp_caps_get_discoinfo(caps, caps_hash, caps_ver); /* We don't know the hash yet - go retrieve it */ if (discoinfo == NULL) { msg = lm_message_new_with_sub_type(from, LM_MESSAGE_TYPE_IQ, LM_MESSAGE_SUB_TYPE_GET); node = lm_message_get_node(msg); temp = lm_message_node_add_child(node, "query", NULL); lm_message_node_set_attribute(temp, "xmlns", XMPP_NS_DISCO_INFO); capability = xmpp_capability_new(NULL, caps_hash, caps_ver, NULL); cb_data = g_new0(struct disco_cb, 1); cb_data->caps = caps; cb_data->capability = capability; disco_handler = lm_message_handler_new(xmpp_caps_handle_disco_reply, cb_data, NULL); lm_connection_send_with_reply(connection, msg, disco_handler, NULL); lm_message_unref(msg); lm_message_handler_unref(disco_handler); } } return LM_HANDLER_RESULT_ALLOW_MORE_HANDLERS; }