diff mcabber/mcabber/xmpp_iqrequest.c @ 1668:41c26b7d2890

Install mcabber headers * Change mcabber headers naming scheme * Move 'src/' -> 'mcabber/' * Add missing include <mcabber/config.h>'s * Create and install clean config.h version in 'include/' * Move "dirty" config.h version to 'mcabber/' * Add $(top_srcdir) to compiler include path * Update modules HOWTO
author Myhailo Danylenko <isbear@ukrpost.net>
date Mon, 18 Jan 2010 15:36:19 +0200
parents mcabber/src/xmpp_iqrequest.c@351427ef0b4b
children b09f82f61745
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mcabber/mcabber/xmpp_iqrequest.c	Mon Jan 18 15:36:19 2010 +0200
@@ -0,0 +1,636 @@
+/*
+ * xmpp_iqrequest.c -- Jabber IQ request handling
+ *
+ * Copyright (C) 2008-2009 Frank Zschockelt <mcabber@freakysoft.de>
+ * Copyright (C) 2005-2009 Mikael Berthe <mikael@lilotux.net>
+ *
+ * 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 <string.h>
+#include <stdlib.h>
+
+#include "xmpp_helper.h"
+#include "xmpp_iq.h"
+#include "screen.h"
+#include "utils.h"
+#include "settings.h"
+#include "hooks.h"
+#include "hbuf.h"
+
+extern LmMessageNode *bookmarks;
+extern LmMessageNode *rosternotes;
+
+static LmHandlerResult cb_roster(LmMessageHandler *h, LmConnection *c,
+                                 LmMessage *m, gpointer user_data);
+static LmHandlerResult cb_version(LmMessageHandler *h, LmConnection *c,
+                                  LmMessage *m, gpointer user_data);
+static LmHandlerResult cb_time(LmMessageHandler *h, LmConnection *c,
+                               LmMessage *m, gpointer user_data);
+static LmHandlerResult cb_last(LmMessageHandler *h, LmConnection *c,
+                               LmMessage *m, gpointer user_data);
+static LmHandlerResult cb_vcard(LmMessageHandler *h, LmConnection *c,
+                               LmMessage *m, gpointer user_data);
+
+static struct IqRequestHandlers
+{
+  const gchar *xmlns;
+  const gchar *querytag;
+  LmHandleMessageFunction handler;
+} iq_request_handlers[] = {
+  {NS_ROSTER, "query", &cb_roster},
+  {NS_VERSION,"query", &cb_version},
+  {NS_TIME,   "query", &cb_time},
+  {NS_LAST,   "query", &cb_last},
+  {NS_VCARD,  "vCard", &cb_vcard},
+  {NULL, NULL, NULL}
+};
+
+// Enum for vCard attributes
+enum vcard_attr {
+  vcard_home    = 1<<0,
+  vcard_work    = 1<<1,
+  vcard_postal  = 1<<2,
+  vcard_voice   = 1<<3,
+  vcard_fax     = 1<<4,
+  vcard_cell    = 1<<5,
+  vcard_inet    = 1<<6,
+  vcard_pref    = 1<<7,
+};
+
+// xmlns has to be a namespace from iq_request_handlers[].xmlns
+void xmpp_iq_request(const char *fulljid, const char *xmlns)
+{
+  LmMessage *iq;
+  LmMessageNode *query;
+  LmMessageHandler *handler;
+  int i;
+
+  iq = lm_message_new_with_sub_type(fulljid, LM_MESSAGE_TYPE_IQ,
+                                    LM_MESSAGE_SUB_TYPE_GET);
+  for (i = 0; strcmp(iq_request_handlers[i].xmlns, xmlns) != 0 ; ++i)
+       ;
+  query = lm_message_node_add_child(iq->node,
+                                    iq_request_handlers[i].querytag,
+                                    NULL);
+  lm_message_node_set_attribute(query, "xmlns", xmlns);
+  handler = lm_message_handler_new(iq_request_handlers[i].handler,
+                                   NULL, FALSE);
+  lm_connection_send_with_reply(lconnection, iq, handler, NULL);
+  lm_message_handler_unref(handler);
+  lm_message_unref(iq);
+}
+
+//  This callback is reached when mcabber receives the first roster update
+// after the connection.
+static LmHandlerResult cb_roster(LmMessageHandler *h, LmConnection *c,
+                                 LmMessage *m, gpointer user_data)
+{
+  LmMessageNode *x;
+  const char *ns;
+
+  // Only execute the hook if the roster has been successfully retrieved
+  if (lm_message_get_sub_type(m) != LM_MESSAGE_SUB_TYPE_RESULT)
+    return LM_HANDLER_RESULT_ALLOW_MORE_HANDLERS;
+
+  x = lm_message_node_find_child(m->node, "query");
+  if (!x)
+    return LM_HANDLER_RESULT_ALLOW_MORE_HANDLERS;
+
+  ns = lm_message_node_get_attribute(x, "xmlns");
+  if (ns && !strcmp(ns, NS_ROSTER))
+    handle_iq_roster(NULL, c, m, user_data);
+
+  // Post-login stuff
+  hook_execute_internal("hook-post-connect");
+
+  return LM_HANDLER_RESULT_REMOVE_MESSAGE;
+}
+
+static LmHandlerResult cb_version(LmMessageHandler *h, LmConnection *c,
+                                  LmMessage *m, gpointer user_data)
+{
+  LmMessageNode *ansqry;
+  const char *p, *bjid;
+  char *tmp;
+  char *buf;
+
+  ansqry = lm_message_node_get_child(m->node, "query");
+  if (!ansqry) {
+    scr_LogPrint(LPRINT_LOGNORM, "Invalid IQ:version result!");
+    return LM_HANDLER_RESULT_ALLOW_MORE_HANDLERS;
+  }
+
+  // Display IQ result sender...
+  p = lm_message_get_from(m);
+  if (!p) {
+    scr_LogPrint(LPRINT_LOGNORM, "Invalid IQ:version result (no sender name).");
+    return LM_HANDLER_RESULT_ALLOW_MORE_HANDLERS;
+  }
+  bjid = p;
+
+  buf = g_strdup_printf("Received IQ:version result from <%s>", bjid);
+  scr_LogPrint(LPRINT_LOGNORM, "%s", buf);
+
+  // bjid should now really be the "bare JID", let's strip the resource
+  tmp = strchr(bjid, JID_RESOURCE_SEPARATOR);
+  if (tmp) *tmp = '\0';
+
+  scr_WriteIncomingMessage(bjid, buf, 0, HBB_PREFIX_INFO, 0);
+  g_free(buf);
+
+  // Get result data...
+  p = lm_message_node_get_child_value(ansqry, "name");
+  if (p) {
+    buf = g_strdup_printf("Name:    %s", p);
+    scr_WriteIncomingMessage(bjid, buf,
+                             0, HBB_PREFIX_INFO | HBB_PREFIX_CONT, 0);
+    g_free(buf);
+  }
+  p = lm_message_node_get_child_value(ansqry, "version");
+  if (p) {
+    buf = g_strdup_printf("Version: %s", p);
+    scr_WriteIncomingMessage(bjid, buf,
+                             0, HBB_PREFIX_INFO | HBB_PREFIX_CONT, 0);
+    g_free(buf);
+  }
+  p = lm_message_node_get_child_value(ansqry, "os");
+  if (p) {
+    buf = g_strdup_printf("OS:      %s", p);
+    scr_WriteIncomingMessage(bjid, buf,
+                             0, HBB_PREFIX_INFO | HBB_PREFIX_CONT, 0);
+    g_free(buf);
+  }
+  return LM_HANDLER_RESULT_REMOVE_MESSAGE;
+}
+
+static LmHandlerResult cb_time(LmMessageHandler *h, LmConnection *c,
+                               LmMessage *m, gpointer user_data)
+{
+  LmMessageNode *ansqry;
+  const char *p, *bjid;
+  char *tmp;
+  char *buf;
+
+  ansqry = lm_message_node_get_child(m->node, "query");
+  if (!ansqry) {
+    scr_LogPrint(LPRINT_LOGNORM, "Invalid IQ:time result!");
+    return LM_HANDLER_RESULT_ALLOW_MORE_HANDLERS;
+  }
+  // Display IQ result sender...
+  p = lm_message_get_from(m);
+  if (!p) {
+    scr_LogPrint(LPRINT_LOGNORM, "Invalid IQ:time result (no sender name).");
+    return LM_HANDLER_RESULT_ALLOW_MORE_HANDLERS;
+  }
+  bjid = p;
+
+  buf = g_strdup_printf("Received IQ:time result from <%s>", bjid);
+  scr_LogPrint(LPRINT_LOGNORM, "%s", buf);
+
+  // bjid should now really be the "bare JID", let's strip the resource
+  tmp = strchr(bjid, JID_RESOURCE_SEPARATOR);
+  if (tmp) *tmp = '\0';
+
+  scr_WriteIncomingMessage(bjid, buf, 0, HBB_PREFIX_INFO, 0);
+  g_free(buf);
+
+  // Get result data...
+  p = lm_message_node_get_child_value(ansqry, "utc");
+  if (p) {
+    buf = g_strdup_printf("UTC:  %s", p);
+    scr_WriteIncomingMessage(bjid, buf,
+                             0, HBB_PREFIX_INFO | HBB_PREFIX_CONT, 0);
+    g_free(buf);
+  }
+  p = lm_message_node_get_child_value(ansqry, "tz");
+  if (p) {
+    buf = g_strdup_printf("TZ:   %s", p);
+    scr_WriteIncomingMessage(bjid, buf,
+                             0, HBB_PREFIX_INFO | HBB_PREFIX_CONT, 0);
+    g_free(buf);
+  }
+  p = lm_message_node_get_child_value(ansqry, "display");
+  if (p) {
+    buf = g_strdup_printf("Time: %s", p);
+    scr_WriteIncomingMessage(bjid, buf,
+                             0, HBB_PREFIX_INFO | HBB_PREFIX_CONT, 0);
+    g_free(buf);
+  }
+  return LM_HANDLER_RESULT_REMOVE_MESSAGE;
+}
+
+static LmHandlerResult cb_last(LmMessageHandler *h, LmConnection *c,
+                               LmMessage *m, gpointer user_data)
+{
+  LmMessageNode *ansqry;
+  const char *p, *bjid;
+  char *buf, *tmp;
+
+  ansqry = lm_message_node_get_child(m->node, "query");
+  if (!ansqry) {
+    scr_LogPrint(LPRINT_LOGNORM, "Invalid IQ:last result!");
+    return LM_HANDLER_RESULT_ALLOW_MORE_HANDLERS;
+  }
+  // Display IQ result sender...
+  p = lm_message_get_from(m);
+  if (!p) {
+    scr_LogPrint(LPRINT_LOGNORM, "Invalid IQ:last result (no sender name).");
+    return LM_HANDLER_RESULT_ALLOW_MORE_HANDLERS;
+  }
+  bjid = p;
+
+  buf = g_strdup_printf("Received IQ:last result from <%s>", bjid);
+  scr_LogPrint(LPRINT_LOGNORM, "%s", buf);
+
+  // bjid should now really be the "bare JID", let's strip the resource
+  tmp = strchr(bjid, JID_RESOURCE_SEPARATOR);
+  if (tmp) *tmp = '\0';
+
+  scr_WriteIncomingMessage(bjid, buf, 0, HBB_PREFIX_INFO, 0);
+  g_free(buf);
+
+  // Get result data...
+  p = lm_message_node_get_attribute(ansqry, "seconds");
+  if (p) {
+    long int s;
+    GString *sbuf;
+    sbuf = g_string_new("Idle time: ");
+    s = atol(p);
+    // Days
+    if (s > 86400L) {
+      g_string_append_printf(sbuf, "%ldd ", s/86400L);
+      s %= 86400L;
+    }
+    // hh:mm:ss
+    g_string_append_printf(sbuf, "%02ld:", s/3600L);
+    s %= 3600L;
+    g_string_append_printf(sbuf, "%02ld:%02ld", s/60L, s%60L);
+    scr_WriteIncomingMessage(bjid, sbuf->str,
+                             0, HBB_PREFIX_INFO | HBB_PREFIX_CONT, 0);
+    g_string_free(sbuf, TRUE);
+  } else {
+    scr_WriteIncomingMessage(bjid, "No idle time reported.",
+                             0, HBB_PREFIX_INFO | HBB_PREFIX_CONT, 0);
+  }
+  p = lm_message_node_get_value(ansqry);
+  if (p) {
+    buf = g_strdup_printf("Status message: %s", p);
+    scr_WriteIncomingMessage(bjid, buf, 0, HBB_PREFIX_INFO, 0);
+    g_free(buf);
+  }
+  return LM_HANDLER_RESULT_REMOVE_MESSAGE;
+}
+
+static void display_vcard_item(const char *bjid, const char *label,
+                               enum vcard_attr vcard_attrib, const char *text)
+{
+  char *buf;
+
+  if (!text || !bjid || !label)
+    return;
+
+  buf = g_strdup_printf("%s: %s%s%s%s%s%s%s%s%s%s", label,
+                        (vcard_attrib & vcard_home ? "[home]" : ""),
+                        (vcard_attrib & vcard_work ? "[work]" : ""),
+                        (vcard_attrib & vcard_postal ? "[postal]" : ""),
+                        (vcard_attrib & vcard_voice ? "[voice]" : ""),
+                        (vcard_attrib & vcard_fax  ? "[fax]"  : ""),
+                        (vcard_attrib & vcard_cell ? "[cell]" : ""),
+                        (vcard_attrib & vcard_inet ? "[inet]" : ""),
+                        (vcard_attrib & vcard_pref ? "[pref]" : ""),
+                        (vcard_attrib ? " " : ""),
+                        text);
+  scr_WriteIncomingMessage(bjid, buf, 0, HBB_PREFIX_INFO | HBB_PREFIX_CONT, 0);
+  g_free(buf);
+}
+
+static void handle_vcard_node(const char *barejid, LmMessageNode *vcardnode)
+{
+  LmMessageNode *x;
+  const char *p;
+
+  for (x = vcardnode->children ; x; x = x->next) {
+    const char *data;
+    enum vcard_attr vcard_attrib = 0;
+
+    p = x->name;
+    data = lm_message_node_get_value(x);
+    if (!p || !data)
+      continue;
+
+    if (!strcmp(p, "FN"))
+      display_vcard_item(barejid, "Name", vcard_attrib, data);
+    else if (!strcmp(p, "NICKNAME"))
+      display_vcard_item(barejid, "Nickname", vcard_attrib, data);
+    else if (!strcmp(p, "URL"))
+      display_vcard_item(barejid, "URL", vcard_attrib, data);
+    else if (!strcmp(p, "BDAY"))
+      display_vcard_item(barejid, "Birthday", vcard_attrib, data);
+    else if (!strcmp(p, "TZ"))
+      display_vcard_item(barejid, "Timezone", vcard_attrib, data);
+    else if (!strcmp(p, "TITLE"))
+      display_vcard_item(barejid, "Title", vcard_attrib, data);
+    else if (!strcmp(p, "ROLE"))
+      display_vcard_item(barejid, "Role", vcard_attrib, data);
+    else if (!strcmp(p, "DESC"))
+      display_vcard_item(barejid, "Comment", vcard_attrib, data);
+    else if (!strcmp(p, "N")) {
+      data = lm_message_node_get_child_value(x, "FAMILY");
+      display_vcard_item(barejid, "Family Name", vcard_attrib, data);
+      data = lm_message_node_get_child_value(x, "GIVEN");
+      display_vcard_item(barejid, "Given Name", vcard_attrib, data);
+      data = lm_message_node_get_child_value(x, "MIDDLE");
+      display_vcard_item(barejid, "Middle Name", vcard_attrib, data);
+    } else if (!strcmp(p, "ORG")) {
+      data = lm_message_node_get_child_value(x, "ORGNAME");
+      display_vcard_item(barejid, "Organisation name", vcard_attrib, data);
+      data = lm_message_node_get_child_value(x, "ORGUNIT");
+      display_vcard_item(barejid, "Organisation unit", vcard_attrib, data);
+    } else {
+      // The HOME, WORK and PREF attributes are common to the remaining fields
+      // (ADR, TEL & EMAIL)
+      if (lm_message_node_get_child(x, "HOME"))
+        vcard_attrib |= vcard_home;
+      if (lm_message_node_get_child(x, "WORK"))
+        vcard_attrib |= vcard_work;
+      if (lm_message_node_get_child(x, "PREF"))
+        vcard_attrib |= vcard_pref;
+      if (!strcmp(p, "ADR")) {          // Address
+        if (lm_message_node_get_child(x, "POSTAL"))
+          vcard_attrib |= vcard_postal;
+        data = lm_message_node_get_child_value(x, "EXTADD");
+        display_vcard_item(barejid, "Addr (ext)", vcard_attrib, data);
+        data = lm_message_node_get_child_value(x, "STREET");
+        display_vcard_item(barejid, "Street", vcard_attrib, data);
+        data = lm_message_node_get_child_value(x, "LOCALITY");
+        display_vcard_item(barejid, "Locality", vcard_attrib, data);
+        data = lm_message_node_get_child_value(x, "REGION");
+        display_vcard_item(barejid, "Region", vcard_attrib, data);
+        data = lm_message_node_get_child_value(x, "PCODE");
+        display_vcard_item(barejid, "Postal code", vcard_attrib, data);
+        data = lm_message_node_get_child_value(x, "CTRY");
+        display_vcard_item(barejid, "Country", vcard_attrib, data);
+      } else if (!strcmp(p, "TEL")) {   // Telephone
+        data = lm_message_node_get_child_value(x, "NUMBER");
+        if (data) {
+          if (lm_message_node_get_child(x, "VOICE"))
+            vcard_attrib |= vcard_voice;
+          if (lm_message_node_get_child(x, "FAX"))
+            vcard_attrib |= vcard_fax;
+          if (lm_message_node_get_child(x, "CELL"))
+            vcard_attrib |= vcard_cell;
+          display_vcard_item(barejid, "Phone", vcard_attrib, data);
+        }
+      } else if (!strcmp(p, "EMAIL")) { // Email
+        if (lm_message_node_get_child(x, "INTERNET"))
+          vcard_attrib |= vcard_inet;
+        data = lm_message_node_get_child_value(x, "USERID");
+        display_vcard_item(barejid, "Email", vcard_attrib, data);
+      }
+    }
+  }
+}
+
+static LmHandlerResult cb_vcard(LmMessageHandler *h, LmConnection *c,
+                               LmMessage *m, gpointer user_data)
+{
+  LmMessageNode *ansqry;
+  const char *p, *bjid;
+  char *buf, *tmp;
+
+  // Display IQ result sender...
+  p = lm_message_get_from(m);
+  if (!p) {
+    scr_LogPrint(LPRINT_LOGNORM, "Invalid IQ:vCard result (no sender name).");
+    return LM_HANDLER_RESULT_ALLOW_MORE_HANDLERS;
+  }
+  bjid = p;
+
+  buf = g_strdup_printf("Received IQ:vCard result from <%s>", bjid);
+  scr_LogPrint(LPRINT_LOGNORM, "%s", buf);
+
+  // Get the vCard node
+  ansqry = lm_message_node_get_child(m->node, "vCard");
+  if (!ansqry) {
+    scr_LogPrint(LPRINT_LOGNORM, "Empty IQ:vCard result!");
+    g_free(buf);
+    return LM_HANDLER_RESULT_REMOVE_MESSAGE;
+  }
+
+  // bjid should really be the "bare JID", let's strip the resource
+  tmp = strchr(bjid, JID_RESOURCE_SEPARATOR);
+  if (tmp) *tmp = '\0';
+
+  scr_WriteIncomingMessage(bjid, buf, 0, HBB_PREFIX_INFO, 0);
+  g_free(buf);
+
+  // Get result data...
+  handle_vcard_node(bjid, ansqry);
+  return LM_HANDLER_RESULT_REMOVE_MESSAGE;
+}
+
+static void storage_bookmarks_parse_conference(LmMessageNode *node)
+{
+  const char *fjid, *name, *autojoin;
+  const char *pstatus, *awhois;
+  char *bjid;
+  GSList *room_elt;
+
+  fjid = lm_message_node_get_attribute(node, "jid");
+  if (!fjid)
+    return;
+  name = lm_message_node_get_attribute(node, "name");
+  autojoin = lm_message_node_get_attribute(node, "autojoin");
+  awhois = lm_message_node_get_attribute(node, "autowhois");
+  pstatus = lm_message_node_get_child_value(node, "print_status");
+
+  bjid = jidtodisp(fjid); // Bare jid
+
+  // Make sure this is a room (it can be a conversion user->room)
+  room_elt = roster_find(bjid, jidsearch, 0);
+  if (!room_elt) {
+    room_elt = roster_add_user(bjid, name, NULL, ROSTER_TYPE_ROOM,
+                               sub_none, -1);
+  } else {
+    buddy_settype(room_elt->data, ROSTER_TYPE_ROOM);
+    /*
+    // If the name is available, should we use it?
+    // I don't think so, it would be confusing because this item is already
+    // in the roster.
+    if (name)
+      buddy_setname(room_elt->data, name);
+    */
+  }
+
+  // Set the print_status and auto_whois values
+  if (pstatus) {
+    enum room_printstatus i;
+    for (i = status_none; i <= status_all; i++)
+      if (!strcasecmp(pstatus, strprintstatus[i]))
+        break;
+    if (i <= status_all)
+      buddy_setprintstatus(room_elt->data, i);
+  }
+  if (awhois) {
+    enum room_autowhois i = autowhois_default;
+    if (!strcmp(awhois, "1"))
+      i = autowhois_on;
+    else if (!strcmp(awhois, "0"))
+      i = autowhois_off;
+    if (i != autowhois_default)
+      buddy_setautowhois(room_elt->data, i);
+  }
+
+  // Is autojoin set?
+  // If it is, we'll look up for more information (nick? password?) and
+  // try to join the room.
+  if (autojoin && !strcmp(autojoin, "1")) {
+    const char *nick, *passwd;
+    char *tmpnick = NULL;
+    nick = lm_message_node_get_child_value(node, "nick");
+    passwd = lm_message_node_get_child_value(node, "password");
+    if (!nick || !*nick)
+      nick = tmpnick = default_muc_nickname(NULL);
+    // Let's join now
+    scr_LogPrint(LPRINT_LOGNORM, "Auto-join bookmark <%s>", bjid);
+    xmpp_room_join(bjid, nick, passwd);
+    g_free(tmpnick);
+  }
+  g_free(bjid);
+}
+
+static LmHandlerResult cb_storage_bookmarks(LmMessageHandler *h,
+                                            LmConnection *c,
+                                            LmMessage *m, gpointer user_data)
+{
+  LmMessageNode *x, *ansqry;
+  char *p;
+
+  if (lm_message_get_sub_type(m) == LM_MESSAGE_SUB_TYPE_ERROR) {
+    // No server support, or no bookmarks?
+    p = m->node->children->name;
+    if (p && !strcmp(p, "item-not-found")) {
+      // item-no-found means the server has Private Storage, but it's
+      // currently empty.
+      if (bookmarks)
+        lm_message_node_unref(bookmarks);
+      bookmarks = lm_message_node_new("storage", "storage:bookmarks");
+      // We return 0 so that the IQ error message be
+      // not displayed, as it isn't a real error.
+      return LM_HANDLER_RESULT_REMOVE_MESSAGE;
+    }
+    return LM_HANDLER_RESULT_ALLOW_MORE_HANDLERS; // Unhandled error
+  }
+
+  ansqry = lm_message_node_get_child(m->node, "query");
+  ansqry = lm_message_node_get_child(ansqry, "storage");
+  if (!ansqry) {
+    scr_LogPrint(LPRINT_LOG, "Invalid IQ:private result! (storage:bookmarks)");
+    return 0;
+  }
+
+  // Walk through the storage tags
+  for (x = ansqry->children ; x; x = x->next) {
+    // If the current node is a conference item, parse it and update the roster
+    if (x->name && !strcmp(x->name, "conference"))
+      storage_bookmarks_parse_conference(x);
+  }
+  // "Copy" the bookmarks node
+  if (bookmarks)
+    lm_message_node_unref(bookmarks);
+  lm_message_node_deep_ref(ansqry);
+  bookmarks = ansqry;
+  return 0;
+}
+
+
+static LmHandlerResult cb_storage_rosternotes(LmMessageHandler *h,
+                                              LmConnection *c,
+                                              LmMessage *m, gpointer user_data)
+{
+  LmMessageNode *ansqry;
+
+  if (lm_message_get_sub_type(m) == LM_MESSAGE_SUB_TYPE_ERROR) {
+    const char *p;
+    // No server support, or no roster notes?
+    p = m->node->children->name;
+    if (p && !strcmp(p, "item-not-found")) {
+      // item-no-found means the server has Private Storage, but it's
+      // currently empty.
+      if (rosternotes)
+        lm_message_node_unref(rosternotes);
+      rosternotes = lm_message_node_new("storage", "storage:rosternotes");
+      // We return 0 so that the IQ error message be
+      // not displayed, as it isn't a real error.
+      return LM_HANDLER_RESULT_REMOVE_MESSAGE;
+    }
+    return LM_HANDLER_RESULT_ALLOW_MORE_HANDLERS; // Unhandled error
+  }
+
+  ansqry = lm_message_node_get_child(m->node, "query");
+  ansqry = lm_message_node_get_child(ansqry, "storage");
+  if (!ansqry) {
+    scr_LogPrint(LPRINT_LOG, "Invalid IQ:private result! "
+                 "(storage:rosternotes)");
+    return LM_HANDLER_RESULT_REMOVE_MESSAGE;
+  }
+  // Copy the rosternotes node
+  if (rosternotes)
+    lm_message_node_unref(rosternotes);
+  lm_message_node_deep_ref(ansqry);
+  rosternotes = ansqry;
+  return 0;
+}
+
+
+static struct IqRequestStorageHandlers
+{
+  const gchar *storagens;
+  LmHandleMessageFunction handler;
+} iq_request_storage_handlers[] = {
+  {"storage:rosternotes", &cb_storage_rosternotes},
+  {"storage:bookmarks", &cb_storage_bookmarks},
+  {NULL, NULL}
+};
+
+void xmpp_request_storage(const gchar *storage)
+{
+  LmMessage *iq;
+  LmMessageNode *query;
+  LmMessageHandler *handler;
+  int i;
+
+  iq = lm_message_new_with_sub_type(NULL, LM_MESSAGE_TYPE_IQ,
+                                    LM_MESSAGE_SUB_TYPE_GET);
+  query = lm_message_node_add_child(iq->node, "query", NULL);
+  lm_message_node_set_attribute(query, "xmlns", NS_PRIVATE);
+  lm_message_node_set_attribute(lm_message_node_add_child
+                                (query, "storage", NULL),
+                                "xmlns", storage);
+
+  for (i = 0;
+       strcmp(iq_request_storage_handlers[i].storagens, storage) != 0;
+       ++i) ;
+
+  handler = lm_message_handler_new(iq_request_storage_handlers[i].handler,
+                                   NULL, FALSE);
+  lm_connection_send_with_reply(lconnection, iq, handler, NULL);
+  lm_message_handler_unref(handler);
+  lm_message_unref(iq);
+}
+
+/* vim: set expandtab cindent cinoptions=>2\:2(0:  For Vim users... */