Mercurial > pidgin.yaz
diff libpurple/protocols/jabber/disco.c @ 27052:413006df9828
propagate from branch 'im.pidgin.pidgin' (head 13ac492a493b4d31c8b29905174b43a533304300)
to branch 'im.pidgin.cpw.darkrain42.xmpp.disco' (head e73974597bbcc75504a88eac45d6893c182d058e)
author | Paul Aurich <paul@darkrain42.org> |
---|---|
date | Wed, 29 Apr 2009 23:20:51 +0000 |
parents | 3912f55a1633 464dbfad4474 |
children | 58ff88dba33a |
line wrap: on
line diff
--- a/libpurple/protocols/jabber/disco.c Wed Apr 29 17:50:14 2009 +0000 +++ b/libpurple/protocols/jabber/disco.c Wed Apr 29 23:20:51 2009 +0000 @@ -1,5 +1,5 @@ /* - * purple - Jabber Protocol Plugin + * purple - Jabber Service Discovery * * Copyright (C) 2003, Nathan Walp <faceprint@faceprint.com> * @@ -40,11 +40,28 @@ JabberDiscoInfoCallback *callback; }; +struct _jabber_disco_items_cb_data { + gpointer data; + JabberDiscoItemsCallback *callback; +}; + #define SUPPORT_FEATURE(x) { \ feature = xmlnode_new_child(query, "feature"); \ xmlnode_set_attrib(feature, "var", x); \ } +struct jabber_disco_list_data { + JabberStream *js; /* TODO: Needed? */ + PurpleDiscoList *list; + char *server; + int fetch_count; +}; + +struct jabber_disco_service_data { + char *jid; + char *node; +}; + static void jabber_disco_bytestream_server_cb(JabberStream *js, const char *from, JabberIqType type, const char *id, @@ -160,11 +177,11 @@ #endif } else { xmlnode *error, *inf; - + /* XXX: gross hack, implement jabber_iq_set_type or something */ xmlnode_set_attrib(iq->node, "type", "error"); iq->type = JABBER_IQ_ERROR; - + error = xmlnode_new_child(query, "error"); xmlnode_set_attrib(error, "code", "404"); xmlnode_set_attrib(error, "type", "cancel"); @@ -173,7 +190,37 @@ } g_free(node_uri); jabber_iq_send(iq); - } else if(type == JABBER_IQ_RESULT) { + } else if (type == JABBER_IQ_SET) { + /* wtf? seriously. wtf‽ */ + JabberIq *iq = jabber_iq_new(js, JABBER_IQ_ERROR); + xmlnode *error, *bad_request; + + /* Free the <query/> */ + xmlnode_free(xmlnode_get_child(iq->node, "query")); + /* Add an error */ + error = xmlnode_new_child(iq->node, "error"); + xmlnode_set_attrib(error, "type", "modify"); + bad_request = xmlnode_new_child(error, "bad-request"); + xmlnode_set_namespace(bad_request, "urn:ietf:params:xml:ns:xmpp-stanzas"); + + jabber_iq_set_id(iq, id); + xmlnode_set_attrib(iq->node, "to", from); + + jabber_iq_send(iq); + } +} + +static void jabber_disco_info_cb(JabberStream *js, const char *from, + JabberIqType type, const char *id, + xmlnode *packet, gpointer data) +{ + struct _jabber_disco_info_cb_data *jdicd = data; + xmlnode *query; + + query = xmlnode_get_child_with_namespace(packet, "query", + "http://jabber.org/protocol/disco#info"); + + if (type == JABBER_IQ_RESULT && query) { xmlnode *child; JabberID *jid; JabberBuddy *jb; @@ -190,7 +237,7 @@ if(jbr) capabilities = jbr->capabilities; - for(child = in_query->child; child; child = child->next) { + for(child = query->child; child; child = child->next) { if(child->type != XMLNODE_TYPE_TAG) continue; @@ -242,6 +289,8 @@ capabilities |= JABBER_CAP_IQ_REGISTER; else if(!strcmp(var, "urn:xmpp:ping")) capabilities |= JABBER_CAP_PING; + else if(!strcmp(var, "http://jabber.org/protocol/disco#items")) + capabilities |= JABBER_CAP_ITEMS; else if(!strcmp(var, "http://jabber.org/protocol/commands")) { capabilities |= JABBER_CAP_ADHOC; } @@ -257,19 +306,12 @@ if(jbr) jbr->capabilities = capabilities; - if((jdicd = g_hash_table_lookup(js->disco_callbacks, from))) { - jdicd->callback(js, from, capabilities, jdicd->data); - g_hash_table_remove(js->disco_callbacks, from); - } - } else if(type == JABBER_IQ_ERROR) { + jdicd->callback(js, from, capabilities, jdicd->data); + } else { /* type == JABBER_IQ_ERROR or query == NULL */ JabberID *jid; JabberBuddy *jb; JabberBuddyResource *jbr = NULL; JabberCapabilities capabilities = JABBER_CAP_NONE; - struct _jabber_disco_info_cb_data *jdicd; - - if(!(jdicd = g_hash_table_lookup(js->disco_callbacks, from))) - return; if((jid = jabber_id_new(from))) { if(jid->resource && (jb = jabber_buddy_find(js, from, TRUE))) @@ -281,7 +323,6 @@ capabilities = jbr->capabilities; jdicd->callback(js, from, capabilities, jdicd->data); - g_hash_table_remove(js->disco_callbacks, from); } } @@ -371,6 +412,12 @@ } +struct _disco_data { + struct jabber_disco_list_data *list_data; + PurpleDiscoService *parent; + char *node; +}; + static void jabber_disco_server_info_result_cb(JabberStream *js, const char *from, JabberIqType type, const char *id, @@ -526,10 +573,541 @@ jdicd->data = data; jdicd->callback = callback; - g_hash_table_insert(js->disco_callbacks, g_strdup(who), jdicd); - iq = jabber_iq_new_query(js, JABBER_IQ_GET, "http://jabber.org/protocol/disco#info"); xmlnode_set_attrib(iq->node, "to", who); + jabber_iq_set_callback(iq, jabber_disco_info_cb, jdicd); jabber_iq_send(iq); } + +static void +jabber_disco_list_data_destroy(struct jabber_disco_list_data *data) +{ + g_free(data->server); + g_free(data); +} + +static void +disco_proto_data_destroy_cb(PurpleDiscoList *list) +{ + struct jabber_disco_list_data *data; + + data = purple_disco_list_get_protocol_data(list); + jabber_disco_list_data_destroy(data); +} + +static PurpleDiscoServiceType +jabber_disco_category_from_string(const gchar *str) +{ + if (!g_ascii_strcasecmp(str, "gateway")) + return PURPLE_DISCO_SERVICE_TYPE_GATEWAY; + else if (!g_ascii_strcasecmp(str, "directory")) + return PURPLE_DISCO_SERVICE_TYPE_DIRECTORY; + else if (!g_ascii_strcasecmp(str, "conference")) + return PURPLE_DISCO_SERVICE_TYPE_CHAT; + + return PURPLE_DISCO_SERVICE_TYPE_OTHER; +} + +static const struct { + const char *from; + const char *to; +} disco_type_mappings[] = { + { "gadu-gadu", "gg" }, + { "sametime", "meanwhile" }, + { "myspaceim", "myspace" }, + { "xmpp", "jabber" }, + { NULL, NULL } +}; + +static const gchar * +jabber_disco_type_from_string(const gchar *str) +{ + int i = 0; + + g_return_val_if_fail(str != NULL, ""); + + for ( ; disco_type_mappings[i].from; ++i) { + if (!strcasecmp(str, disco_type_mappings[i].from)) + return disco_type_mappings[i].to; + } + + /* fallback to the string itself */ + return str; +} + +static void +jabber_disco_service_info_cb(JabberStream *js, const char *from, + JabberIqType type, const char *id, + xmlnode *packet, gpointer data); + +static void +jabber_disco_service_items_cb(JabberStream *js, const char *who, const char *node, + GSList *items, gpointer data) +{ + GSList *l; + struct _disco_data *disco_data; + struct jabber_disco_list_data *list_data; + PurpleDiscoList *list; + PurpleDiscoService *parent; + PurpleDiscoServiceType parent_type; + const char *parent_node; + + disco_data = data; + list_data = disco_data->list_data; + list = list_data->list; + + --list_data->fetch_count; + + if (list_data->list == NULL) { + if (list_data->fetch_count == 0) + jabber_disco_list_data_destroy(list_data); + + return; + } + + if (items == NULL) { + if (list_data->fetch_count == 0) + purple_disco_list_set_in_progress(list, FALSE); + + purple_disco_list_unref(list); + return; + } + + parent = disco_data->parent; + parent_type = purple_disco_service_get_type(parent); + parent_node = disco_data->node; + + for (l = items; l; l = l->next) { + JabberDiscoItem *item = l->data; + + if (parent_type & PURPLE_DISCO_SERVICE_TYPE_CHAT) { + /* A chat server's items are chats. I promise. */ + PurpleDiscoService *service; + struct jabber_disco_service_data *service_data; + JabberID *jid = jabber_id_new(item->jid); + + if (jid) { + service_data = g_new0(struct jabber_disco_service_data, 1); + service_data->jid = g_strdup(item->jid); + + service = purple_disco_list_service_new(PURPLE_DISCO_SERVICE_TYPE_CHAT, + jid->node, item->name, PURPLE_DISCO_ADD, service_data); + + purple_disco_list_service_add(list, service, parent); + + jabber_id_free(jid); + } + } else { + JabberIq *iq; + struct _disco_data *req_data; + char *full_node; + + if (parent_node && !item->node) + continue; + + if (parent_node) + full_node = g_strconcat(parent_node, "/", item->node, NULL); + else + full_node = g_strdup(item->node); + + req_data = g_new0(struct _disco_data, 1); + req_data->list_data = list_data; + req_data->parent = parent; + req_data->node = full_node; + + ++list_data->fetch_count; + purple_disco_list_ref(list); + + iq = jabber_iq_new_query(js, JABBER_IQ_GET, "http://jabber.org/protocol/disco#info"); + xmlnode_set_attrib(iq->node, "to", item->jid); + if (full_node) + xmlnode_set_attrib(xmlnode_get_child(iq->node, "query"), + "node", full_node); + jabber_iq_set_callback(iq, jabber_disco_service_info_cb, req_data); + + jabber_iq_send(iq); + } + } + + g_slist_foreach(items, (GFunc)jabber_disco_item_free, NULL); + g_slist_free(items); + + if (list_data->fetch_count == 0) + purple_disco_list_set_in_progress(list, FALSE); + + purple_disco_list_unref(list); + + g_free(disco_data->node); + g_free(disco_data); +} + +static void +jabber_disco_service_info_cb(JabberStream *js, const char *from, + JabberIqType type, const char *id, + xmlnode *packet, gpointer data) +{ + struct _disco_data *disco_data; + struct jabber_disco_list_data *list_data; + xmlnode *query, *identity, *child; + const char *anode; + char *aname, *node; + + PurpleDiscoList *list; + PurpleDiscoService *parent, *service; + PurpleDiscoServiceType service_type; + PurpleDiscoServiceFlags flags; + struct jabber_disco_service_data *service_data; + + disco_data = data; + list_data = disco_data->list_data; + list = list_data->list; + parent = disco_data->parent; + + node = disco_data->node; + disco_data->node = NULL; + g_free(disco_data); + + --list_data->fetch_count; + + if (!purple_disco_list_get_in_progress(list)) { + purple_disco_list_unref(list); + return; + } + + if (!from || type == JABBER_IQ_ERROR + || (!(query = xmlnode_get_child(packet, "query"))) + || (!(identity = xmlnode_get_child(query, "identity")))) { + if (list_data->fetch_count == 0) + purple_disco_list_set_in_progress(list, FALSE); + + purple_disco_list_unref(list); + return; + } + + service_type = jabber_disco_category_from_string( + xmlnode_get_attrib(identity, "category")); + + /* Default to allowing things to be add-able */ + flags = PURPLE_DISCO_ADD; + + for (child = xmlnode_get_child(query, "feature"); child; + child = xmlnode_get_next_twin(child)) { + const char *var; + + if (!(var = xmlnode_get_attrib(child, "var"))) + continue; + + if (g_str_equal(var, "jabber:iq:register")) + flags |= PURPLE_DISCO_REGISTER; + + if (g_str_equal(var, "http://jabber.org/protocol/disco#items")) + flags |= PURPLE_DISCO_BROWSE; + + if (g_str_equal(var, "http://jabber.org/protocol/muc")) { + flags |= PURPLE_DISCO_BROWSE; + service_type = PURPLE_DISCO_SERVICE_TYPE_CHAT; + } + } + + if ((anode = xmlnode_get_attrib(query, "node"))) + aname = g_strconcat(from, anode, NULL); + else + aname = g_strdup(from); + + service_data = g_new0(struct jabber_disco_service_data, 1); + service_data->jid = g_strdup(from); + if (anode) + service_data->node = g_strdup(anode); + + service = purple_disco_list_service_new(service_type, aname, + xmlnode_get_attrib(identity, "name"), flags, service_data); + g_free(aname); + + if (service_type == PURPLE_DISCO_SERVICE_TYPE_GATEWAY) + purple_disco_service_set_gateway_type(service, + jabber_disco_type_from_string(xmlnode_get_attrib(identity, + "type"))); + + purple_disco_list_service_add(list, service, parent); + + if (list_data->fetch_count == 0) + purple_disco_list_set_in_progress(list, FALSE); + + purple_disco_list_unref(list); + + g_free(node); +} + +static void +jabber_disco_server_items_cb(JabberStream *js, const char *from, + JabberIqType type, const char *id, + xmlnode *packet, gpointer data) +{ + struct jabber_disco_list_data *list_data; + xmlnode *query, *child; + gboolean has_items = FALSE; + + list_data = data; + --list_data->fetch_count; + + if (!from || type == JABBER_IQ_ERROR || + !purple_disco_list_get_in_progress(list_data->list)) { + purple_disco_list_unref(list_data->list); + return; + } + + query = xmlnode_get_child(packet, "query"); + + for(child = xmlnode_get_child(query, "item"); child; + child = xmlnode_get_next_twin(child)) { + JabberIq *iq; + const char *jid; + struct _disco_data *disco_data; + + if(!(jid = xmlnode_get_attrib(child, "jid"))) + continue; + + disco_data = g_new0(struct _disco_data, 1); + disco_data->list_data = list_data; + + has_items = TRUE; + ++list_data->fetch_count; + purple_disco_list_ref(list_data->list); + iq = jabber_iq_new_query(js, JABBER_IQ_GET, "http://jabber.org/protocol/disco#info"); + xmlnode_set_attrib(iq->node, "to", jid); + jabber_iq_set_callback(iq, jabber_disco_service_info_cb, disco_data); + + jabber_iq_send(iq); + } + + if (!has_items) + purple_disco_list_set_in_progress(list_data->list, FALSE); + + purple_disco_list_unref(list_data->list); +} + +static void +jabber_disco_server_info_cb(JabberStream *js, const char *who, JabberCapabilities caps, gpointer data) +{ + struct jabber_disco_list_data *list_data; + + list_data = data; + --list_data->fetch_count; + + if (!list_data->list) { + purple_disco_list_unref(list_data->list); + + if (list_data->fetch_count == 0) + jabber_disco_list_data_destroy(list_data); + + return; + } + + if (caps & JABBER_CAP_ITEMS) { + JabberIq *iq = jabber_iq_new_query(js, JABBER_IQ_GET, "http://jabber.org/protocol/disco#items"); + xmlnode_set_attrib(iq->node, "to", who); + jabber_iq_set_callback(iq, jabber_disco_server_items_cb, list_data); + + ++list_data->fetch_count; + purple_disco_list_ref(list_data->list); + + jabber_iq_send(iq); + } else { + purple_notify_error(NULL, _("Error"), _("Server doesn't support service discovery"), NULL); + purple_disco_list_set_in_progress(list_data->list, FALSE); + } + + purple_disco_list_unref(list_data->list); +} + +static void discolist_cancel_cb(struct jabber_disco_list_data *list_data, const char *server) +{ + purple_disco_list_set_in_progress(list_data->list, FALSE); + purple_disco_list_unref(list_data->list); +} + +static void discolist_ok_cb(struct jabber_disco_list_data *list_data, const char *server) +{ + JabberStream *js; + + js = list_data->js; + + if (!server || !*server) { + purple_notify_error(js->gc, _("Invalid Server"), _("Invalid Server"), NULL); + + purple_disco_list_set_in_progress(list_data->list, FALSE); + purple_disco_list_unref(list_data->list); + return; + } + + list_data->server = g_strdup(server); + if (js->last_disco_server) + g_free(js->last_disco_server); + js->last_disco_server = g_strdup(server); + + purple_disco_list_set_in_progress(list_data->list, TRUE); + + ++list_data->fetch_count; + jabber_disco_info_do(js, list_data->server, jabber_disco_server_info_cb, list_data); +} + +static void +jabber_disco_service_close(PurpleDiscoService *service) +{ + struct jabber_disco_service_data *data; + + data = purple_disco_service_get_protocol_data(service); + g_free(data->jid); + g_free(data->node); + g_free(data); +} + +#if 0 +static void +jabber_disco_cancel(PurpleDiscoList *list) +{ + /* This space intentionally letft blank */ +} +#endif + +static void +jabber_disco_list_expand(PurpleDiscoList *list, PurpleDiscoService *service) +{ + PurpleAccount *account; + PurpleConnection *pc; + JabberStream *js; + struct jabber_disco_list_data *list_data; + struct jabber_disco_service_data *service_data; + struct _disco_data *disco_data; + + account = purple_disco_list_get_account(list); + pc = purple_account_get_connection(account); + js = purple_connection_get_protocol_data(pc); + + list_data = purple_disco_list_get_protocol_data(list); + + ++list_data->fetch_count; + purple_disco_list_ref(list); + disco_data = g_new0(struct _disco_data, 1); + disco_data->list_data = list_data; + disco_data->parent = service; + + service_data = purple_disco_service_get_protocol_data(service); + + jabber_disco_items_do(js, service_data->jid, service_data->node, + jabber_disco_service_items_cb, disco_data); +} + +static void +jabber_disco_service_register(PurpleConnection *gc, PurpleDiscoService *service) +{ + JabberStream *js = purple_connection_get_protocol_data(gc); + + jabber_register_gateway(js, purple_disco_service_get_name(service)); +} + + +PurpleDiscoList * +jabber_disco_get_list(PurpleConnection *gc) +{ + PurpleAccount *account; + PurpleDiscoList *list; + JabberStream *js; + struct jabber_disco_list_data *disco_list_data; + + account = purple_connection_get_account(gc); + js = purple_connection_get_protocol_data(gc); + + /* We start with a ref */ + list = purple_disco_list_new(account); + + disco_list_data = g_new0(struct jabber_disco_list_data, 1); + disco_list_data->list = list; + disco_list_data->js = js; + purple_disco_list_set_protocol_data(list, disco_list_data, + disco_proto_data_destroy_cb); + purple_disco_list_set_service_close_func(list, jabber_disco_service_close); +#if 0 + purple_disco_list_set_cancel_func(list, jabber_disco_cancel); +#endif + purple_disco_list_set_expand_func(list, jabber_disco_list_expand); + purple_disco_list_set_register_func(list, jabber_disco_service_register); + + purple_request_input(gc, _("Server name request"), _("Enter an XMPP Server"), + _("Select an XMPP server to query"), + js->last_disco_server ? js->last_disco_server : js->user->domain, + FALSE, FALSE, NULL, + _("Find Services"), PURPLE_CALLBACK(discolist_ok_cb), + _("Cancel"), PURPLE_CALLBACK(discolist_cancel_cb), + account, NULL, NULL, disco_list_data); + + return list; +} + +static void +jabber_disco_items_cb(JabberStream *js, const char *from, JabberIqType type, + const char *id, xmlnode *packet, gpointer data) +{ + struct _jabber_disco_items_cb_data *jdicd; + xmlnode *query, *child; + const char *node = NULL; + GSList *items = NULL; + + jdicd = data; + + query = xmlnode_get_child(packet, "query"); + + if (query) + node = xmlnode_get_attrib(query, "node"); + + if (!from || !query || type == JABBER_IQ_ERROR) { + jdicd->callback(js, from, node, NULL, jdicd->data); + g_free(jdicd); + return; + } + + for (child = xmlnode_get_child(query, "item"); child; + child = xmlnode_get_next_twin(child)) { + JabberDiscoItem *item; + + item = g_new0(JabberDiscoItem, 1); + item->jid = g_strdup(xmlnode_get_attrib(child, "jid")); + item->node = g_strdup(xmlnode_get_attrib(child, "node")); + item->name = g_strdup(xmlnode_get_attrib(child, "name")); + + items = g_slist_prepend(items, item); + } + + items = g_slist_reverse(items); + jdicd->callback(js, from, node, items, jdicd->data); + g_free(jdicd); +} + +void jabber_disco_items_do(JabberStream *js, const char *who, const char *node, + JabberDiscoItemsCallback *callback, gpointer data) +{ + struct _jabber_disco_items_cb_data *jdicd; + JabberIq *iq; + + jdicd = g_new0(struct _jabber_disco_items_cb_data, 1); + jdicd->data = data; + jdicd->callback = callback; + + iq = jabber_iq_new_query(js, JABBER_IQ_GET, "http://jabber.org/protocol/disco#items"); + if (node) + xmlnode_set_attrib(xmlnode_get_child(iq->node, "query"), "node", node); + + xmlnode_set_attrib(iq->node, "to", who); + + jabber_iq_set_callback(iq, jabber_disco_items_cb, jdicd); + jabber_iq_send(iq); +} + +void jabber_disco_item_free(JabberDiscoItem *item) +{ + g_free((char *)item->jid); + g_free((char *)item->node); + g_free((char *)item->name); + g_free(item); +}