pidgin/pidgin

Remove the old Jabber/XMPP protocol plugin

2 months ago, Gary Kramlich
68cc8544b438
Parents a3b862b8dcde
Children 5b864b9b22aa
Remove the old Jabber/XMPP protocol plugin

We've already started the new version and we're not really using any of this
code. So rather than keep it around and other migrations have broken it, we're
just going to shed it now.

Testing Done:
Sewer surfed with the turtles.

Reviewed at https://reviews.imfreedom.org/r/3073/
  • +0 -1
    libpurple/meson.build
  • +0 -364
    libpurple/protocols/jabber/adhoccommands.c
  • +0 -51
    libpurple/protocols/jabber/adhoccommands.h
  • +0 -557
    libpurple/protocols/jabber/auth.c
  • +0 -64
    libpurple/protocols/jabber/auth.h
  • +0 -285
    libpurple/protocols/jabber/auth_digest_md5.c
  • +0 -41
    libpurple/protocols/jabber/auth_digest_md5.h
  • +0 -114
    libpurple/protocols/jabber/auth_plain.c
  • +0 -598
    libpurple/protocols/jabber/auth_scram.c
  • +0 -99
    libpurple/protocols/jabber/auth_scram.h
  • +0 -62
    libpurple/protocols/jabber/auth_webex.c
  • +0 -544
    libpurple/protocols/jabber/bosh.c
  • +0 -55
    libpurple/protocols/jabber/bosh.h
  • +0 -1957
    libpurple/protocols/jabber/buddy.c
  • +0 -114
    libpurple/protocols/jabber/buddy.h
  • +0 -661
    libpurple/protocols/jabber/caps.c
  • +0 -113
    libpurple/protocols/jabber/caps.h
  • +0 -1225
    libpurple/protocols/jabber/chat.c
  • +0 -96
    libpurple/protocols/jabber/chat.h
  • +0 -474
    libpurple/protocols/jabber/data.c
  • +0 -90
    libpurple/protocols/jabber/data.h
  • +0 -533
    libpurple/protocols/jabber/disco.c
  • +0 -52
    libpurple/protocols/jabber/disco.h
  • +0 -514
    libpurple/protocols/jabber/ibb.c
  • +0 -117
    libpurple/protocols/jabber/ibb.h
  • +0 -562
    libpurple/protocols/jabber/iq.c
  • +0 -120
    libpurple/protocols/jabber/iq.h
  • +0 -3312
    libpurple/protocols/jabber/jabber.c
  • +0 -324
    libpurple/protocols/jabber/jabber.h
  • +0 -477
    libpurple/protocols/jabber/jingle/content.c
  • +0 -103
    libpurple/protocols/jabber/jingle/content.h
  • +0 -499
    libpurple/protocols/jabber/jingle/iceudp.c
  • +0 -88
    libpurple/protocols/jabber/jingle/iceudp.h
  • +0 -493
    libpurple/protocols/jabber/jingle/jingle.c
  • +0 -84
    libpurple/protocols/jabber/jingle/jingle.h
  • +0 -371
    libpurple/protocols/jabber/jingle/rawudp.c
  • +0 -75
    libpurple/protocols/jabber/jingle/rawudp.h
  • +0 -933
    libpurple/protocols/jabber/jingle/rtp.c
  • +0 -63
    libpurple/protocols/jabber/jingle/rtp.h
  • +0 -685
    libpurple/protocols/jabber/jingle/session.c
  • +0 -92
    libpurple/protocols/jabber/jingle/session.h
  • +0 -144
    libpurple/protocols/jabber/jingle/transport.c
  • +0 -79
    libpurple/protocols/jabber/jingle/transport.h
  • +0 -604
    libpurple/protocols/jabber/jutil.c
  • +0 -100
    libpurple/protocols/jabber/jutil.h
  • +0 -103
    libpurple/protocols/jabber/meson.build
  • +0 -1002
    libpurple/protocols/jabber/message.c
  • +0 -79
    libpurple/protocols/jabber/message.h
  • +0 -100
    libpurple/protocols/jabber/namespaces.h
  • +0 -289
    libpurple/protocols/jabber/oob.c
  • +0 -42
    libpurple/protocols/jabber/oob.h
  • +0 -317
    libpurple/protocols/jabber/parser.c
  • +0 -34
    libpurple/protocols/jabber/parser.h
  • +0 -164
    libpurple/protocols/jabber/pep.c
  • +0 -87
    libpurple/protocols/jabber/pep.h
  • +0 -100
    libpurple/protocols/jabber/ping.c
  • +0 -38
    libpurple/protocols/jabber/ping.h
  • +0 -1248
    libpurple/protocols/jabber/presence.c
  • +0 -100
    libpurple/protocols/jabber/presence.h
  • +0 -0
    libpurple/protocols/jabber/resources/icons/16x16/apps/im-jabber.png
  • +0 -223
    libpurple/protocols/jabber/resources/icons/16x16/apps/scalable/im-jabber.svg
  • +0 -0
    libpurple/protocols/jabber/resources/icons/22x22/apps/im-jabber.png
  • +0 -251
    libpurple/protocols/jabber/resources/icons/22x22/apps/scalable/im-jabber.svg
  • +0 -0
    libpurple/protocols/jabber/resources/icons/48x48/apps/im-jabber.png
  • +0 -246
    libpurple/protocols/jabber/resources/icons/scalable/apps/im-jabber.svg
  • +0 -11
    libpurple/protocols/jabber/resources/xmpp.gresource.xml
  • +0 -521
    libpurple/protocols/jabber/roster.c
  • +0 -49
    libpurple/protocols/jabber/roster.h
  • +0 -1909
    libpurple/protocols/jabber/si.c
  • +0 -49
    libpurple/protocols/jabber/si.h
  • +0 -11
    libpurple/protocols/jabber/tests/meson.build
  • +0 -60
    libpurple/protocols/jabber/tests/test_jabber_caps.c
  • +0 -51
    libpurple/protocols/jabber/tests/test_jabber_digest_md5.c
  • +0 -290
    libpurple/protocols/jabber/tests/test_jabber_jutil.c
  • +0 -109
    libpurple/protocols/jabber/tests/test_jabber_scram.c
  • +0 -361
    libpurple/protocols/jabber/useravatar.c
  • +0 -38
    libpurple/protocols/jabber/useravatar.h
  • +0 -146
    libpurple/protocols/jabber/usernick.c
  • +0 -35
    libpurple/protocols/jabber/usernick.h
  • +0 -470
    libpurple/protocols/jabber/xdata.c
  • +0 -57
    libpurple/protocols/jabber/xdata.h
  • +0 -147
    libpurple/protocols/jabber/xmpp.c
  • +0 -47
    libpurple/protocols/jabber/xmpp.h
  • +0 -1
    libpurple/protocols/meson.build
  • +1 -2
    meson.build
  • +0 -39
    po/POTFILES.in
  • --- a/libpurple/meson.build Tue Apr 09 23:36:32 2024 -0500
    +++ b/libpurple/meson.build Wed Apr 10 00:05:21 2024 -0500
    @@ -440,7 +440,6 @@
    subdir('tests')
    subdir('plugins')
    -subdir('protocols')
    test(
    'purple_license_headers',
    --- a/libpurple/protocols/jabber/adhoccommands.c Tue Apr 09 23:36:32 2024 -0500
    +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
    @@ -1,364 +0,0 @@
    -/*
    - * 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 "adhoccommands.h"
    -#include <string.h>
    -#include "xdata.h"
    -#include "iq.h"
    -
    -static void jabber_adhoc_execute(JabberStream *js, JabberAdHocCommands *cmd);
    -
    -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
    -do_adhoc_ignoreme(G_GNUC_UNUSED JabberStream *js, ...) {
    - /* we don't have to do anything */
    -}
    -
    -typedef struct {
    - char *sessionid;
    - char *who;
    - char *node;
    - GList *actionslist;
    -} JabberAdHocActionInfo;
    -
    -static void
    -jabber_adhoc_got_buddy_list(JabberStream *js, const char *from, PurpleXmlNode *query)
    -{
    - JabberID *jid;
    - JabberBuddy *jb;
    - JabberBuddyResource *jbr = NULL;
    - PurpleXmlNode *item;
    -
    - if ((jid = jabber_id_new(from))) {
    - if (jid->resource && (jb = jabber_buddy_find(js, from, TRUE)))
    - jbr = jabber_buddy_find_resource(jb, jid->resource);
    - jabber_id_free(jid);
    - }
    -
    - if(!jbr)
    - return;
    -
    - /* since the list we just received is complete, wipe the old one */
    - g_clear_list(&jbr->commands, (GDestroyNotify)jabber_adhoc_commands_free);
    -
    - for(item = query->child; item; item = item->next) {
    - JabberAdHocCommands *cmd;
    - if(item->type != PURPLE_XMLNODE_TYPE_TAG)
    - continue;
    - if(!purple_strequal(item->name, "item"))
    - continue;
    - cmd = g_new0(JabberAdHocCommands, 1);
    -
    - cmd->jid = g_strdup(purple_xmlnode_get_attrib(item,"jid"));
    - cmd->node = g_strdup(purple_xmlnode_get_attrib(item,"node"));
    - cmd->name = g_strdup(purple_xmlnode_get_attrib(item,"name"));
    -
    - jbr->commands = g_list_append(jbr->commands,cmd);
    - }
    -}
    -
    -void
    -jabber_adhoc_disco_result_cb(JabberStream *js, const char *from,
    - JabberIqType type, G_GNUC_UNUSED const char *id,
    - PurpleXmlNode *packet, G_GNUC_UNUSED gpointer data)
    -{
    - PurpleXmlNode *query;
    - const char *node;
    -
    - if (type == JABBER_IQ_ERROR)
    - return;
    -
    - query = purple_xmlnode_get_child_with_namespace(packet, "query", NS_DISCO_ITEMS);
    - if (!query)
    - return;
    - node = purple_xmlnode_get_attrib(query, "node");
    - if (!purple_strequal(node, "http://jabber.org/protocol/commands"))
    - return;
    -
    - jabber_adhoc_got_buddy_list(js, from, query);
    -}
    -
    -static void jabber_adhoc_parse(JabberStream *js, const char *from,
    - JabberIqType type, const char *id,
    - PurpleXmlNode *packet, gpointer data);
    -
    -static void do_adhoc_action_cb(JabberStream *js, PurpleXmlNode *result, const char *actionhandle, gpointer user_data) {
    - PurpleXmlNode *command;
    - JabberAdHocActionInfo *actionInfo = user_data;
    - JabberIq *iq = jabber_iq_new(js, JABBER_IQ_SET);
    - jabber_iq_set_callback(iq, jabber_adhoc_parse, NULL);
    -
    - purple_xmlnode_set_attrib(iq->node, "to", actionInfo->who);
    - command = purple_xmlnode_new_child(iq->node,"command");
    - purple_xmlnode_set_namespace(command,"http://jabber.org/protocol/commands");
    - purple_xmlnode_set_attrib(command,"sessionid",actionInfo->sessionid);
    - purple_xmlnode_set_attrib(command,"node",actionInfo->node);
    -
    - /* cancel is handled differently on ad-hoc commands than regular forms */
    - if (purple_strequal(purple_xmlnode_get_namespace(result), "jabber:x:data") &&
    - purple_strequal(purple_xmlnode_get_attrib(result, "type"), "cancel")) {
    - purple_xmlnode_set_attrib(command,"action","cancel");
    - } else {
    - if(actionhandle)
    - purple_xmlnode_set_attrib(command,"action",actionhandle);
    - purple_xmlnode_insert_child(command,result);
    - }
    -
    - g_list_free_full(actionInfo->actionslist, g_free);
    - g_free(actionInfo->sessionid);
    - g_free(actionInfo->who);
    - g_free(actionInfo->node);
    -
    - jabber_iq_send(iq);
    -}
    -
    -static void
    -jabber_adhoc_parse(JabberStream *js, const char *from, JabberIqType type,
    - G_GNUC_UNUSED const char *id, PurpleXmlNode *packet,
    - G_GNUC_UNUSED gpointer data)
    -{
    - PurpleXmlNode *command = purple_xmlnode_get_child_with_namespace(packet, "command", "http://jabber.org/protocol/commands");
    - const char *status = purple_xmlnode_get_attrib(command,"status");
    - PurpleXmlNode *xdata = purple_xmlnode_get_child_with_namespace(command,"x","jabber:x:data");
    -
    - if (type == JABBER_IQ_ERROR) {
    - char *msg = jabber_parse_error(js, packet, NULL);
    - if(!msg)
    - msg = g_strdup(_("Unknown Error"));
    -
    - purple_notify_error(NULL, _("Ad-Hoc Command Failed"),
    - _("Ad-Hoc Command Failed"), msg,
    - purple_request_cpar_from_connection(js->gc));
    - g_free(msg);
    - return;
    - }
    -
    - if(!status)
    - return;
    -
    - if(purple_strequal(status,"completed")) {
    - /* display result */
    - PurpleXmlNode *note = purple_xmlnode_get_child(command,"note");
    -
    - if(note) {
    - char *data = purple_xmlnode_get_data(note);
    - purple_notify_info(NULL, from, data, NULL,
    - purple_request_cpar_from_connection(js->gc));
    - g_free(data);
    - }
    -
    - if(xdata)
    - jabber_x_data_request(js, xdata, (jabber_x_data_cb)do_adhoc_ignoreme, NULL);
    - return;
    - }
    - if(purple_strequal(status,"executing")) {
    - /* this command needs more steps */
    - PurpleXmlNode *actions, *action;
    - int actionindex = 0;
    - GList *actionslist = NULL;
    - JabberAdHocActionInfo *actionInfo;
    - if(!xdata)
    - return; /* shouldn't happen */
    -
    - actions = purple_xmlnode_get_child(command,"actions");
    - if(!actions) {
    - JabberXDataAction *defaultaction = g_new0(JabberXDataAction, 1);
    - defaultaction->name = g_strdup(_("execute"));
    - defaultaction->handle = g_strdup("execute");
    - actionslist = g_list_append(actionslist, defaultaction);
    - } else {
    - const char *defaultactionhandle = purple_xmlnode_get_attrib(actions, "execute");
    - int index = 0;
    - for(action = actions->child; action; action = action->next, ++index) {
    - if(action->type == PURPLE_XMLNODE_TYPE_TAG) {
    - JabberXDataAction *newaction = g_new0(JabberXDataAction, 1);
    - newaction->name = g_strdup(_(action->name));
    - newaction->handle = g_strdup(action->name);
    - actionslist = g_list_append(actionslist, newaction);
    - if(defaultactionhandle && purple_strequal(defaultactionhandle, action->name))
    - actionindex = index;
    - }
    - }
    - }
    -
    - actionInfo = g_new0(JabberAdHocActionInfo, 1);
    - actionInfo->sessionid = g_strdup(purple_xmlnode_get_attrib(command,"sessionid"));
    - actionInfo->who = g_strdup(from);
    - actionInfo->node = g_strdup(purple_xmlnode_get_attrib(command,"node"));
    - actionInfo->actionslist = actionslist;
    -
    - jabber_x_data_request_with_actions(js,xdata,actionslist,actionindex,do_adhoc_action_cb,actionInfo);
    - }
    -}
    -
    -void jabber_adhoc_execute_action(PurpleBlistNode *node, gpointer data) {
    - if (PURPLE_IS_BUDDY(node)) {
    - JabberAdHocCommands *cmd = data;
    - PurpleBuddy *buddy = (PurpleBuddy *) node;
    - PurpleAccount *account = purple_buddy_get_account(buddy);
    - PurpleConnection *gc = purple_account_get_connection(account);
    - JabberStream *js = purple_connection_get_protocol_data(gc);
    -
    - jabber_adhoc_execute(js, cmd);
    - }
    -}
    -
    -static void
    -jabber_adhoc_got_server_list(JabberStream *js, G_GNUC_UNUSED const char *from,
    - PurpleXmlNode *query)
    -{
    - PurpleXmlNode *item;
    -
    - if(!query)
    - return;
    -
    - /* clean current list (just in case there is one) */
    - g_clear_list(&js->commands, (GDestroyNotify)jabber_adhoc_commands_free);
    -
    - /* re-fill list */
    - for(item = query->child; item; item = item->next) {
    - JabberAdHocCommands *cmd;
    - if(item->type != PURPLE_XMLNODE_TYPE_TAG)
    - continue;
    - if(!purple_strequal(item->name, "item"))
    - continue;
    - cmd = g_new0(JabberAdHocCommands, 1);
    - cmd->jid = g_strdup(purple_xmlnode_get_attrib(item,"jid"));
    - cmd->node = g_strdup(purple_xmlnode_get_attrib(item,"node"));
    - cmd->name = g_strdup(purple_xmlnode_get_attrib(item,"name"));
    -
    - js->commands = g_list_append(js->commands,cmd);
    - }
    -
    - if (js->state == JABBER_STREAM_CONNECTED) {
    - PurpleProtocol *protocol = purple_connection_get_protocol(js->gc);
    -
    - purple_protocol_actions_changed(PURPLE_PROTOCOL_ACTIONS(protocol),
    - purple_connection_get_account(js->gc));
    - }
    -}
    -
    -static void
    -jabber_adhoc_server_got_list_cb(JabberStream *js, const char *from,
    - G_GNUC_UNUSED JabberIqType type,
    - G_GNUC_UNUSED const char *id,
    - PurpleXmlNode *packet,
    - G_GNUC_UNUSED gpointer data)
    -{
    - PurpleXmlNode *query = purple_xmlnode_get_child_with_namespace(packet, "query",
    - NS_DISCO_ITEMS);
    -
    - jabber_adhoc_got_server_list(js, from, query);
    -
    -}
    -
    -void jabber_adhoc_got_list(JabberStream *js, const char *from, PurpleXmlNode *query)
    -{
    - if (purple_strequal(from, js->user->domain)) {
    - jabber_adhoc_got_server_list(js, from, query);
    - } else {
    - jabber_adhoc_got_buddy_list(js, from, query);
    - }
    -}
    -
    -void jabber_adhoc_server_get_list(JabberStream *js) {
    - JabberIq *iq = jabber_iq_new_query(js, JABBER_IQ_GET, NS_DISCO_ITEMS);
    - PurpleXmlNode *query = purple_xmlnode_get_child_with_namespace(iq->node, "query",
    - NS_DISCO_ITEMS);
    -
    - purple_xmlnode_set_attrib(iq->node,"to",js->user->domain);
    - purple_xmlnode_set_attrib(query,"node","http://jabber.org/protocol/commands");
    -
    - jabber_iq_set_callback(iq,jabber_adhoc_server_got_list_cb,NULL);
    - jabber_iq_send(iq);
    -}
    -
    -static void
    -jabber_adhoc_execute(JabberStream *js, JabberAdHocCommands *cmd) {
    - JabberIq *iq = jabber_iq_new(js, JABBER_IQ_SET);
    - PurpleXmlNode *command = purple_xmlnode_new_child(iq->node,"command");
    - purple_xmlnode_set_attrib(iq->node,"to",cmd->jid);
    - purple_xmlnode_set_namespace(command,"http://jabber.org/protocol/commands");
    - purple_xmlnode_set_attrib(command,"node",cmd->node);
    - purple_xmlnode_set_attrib(command,"action","execute");
    -
    - jabber_iq_set_callback(iq,jabber_adhoc_parse,NULL);
    -
    - jabber_iq_send(iq);
    -}
    -
    -static void jabber_adhoc_server_execute(PurpleProtocolAction *action) {
    - JabberAdHocCommands *cmd = action->user_data;
    - if(cmd) {
    - PurpleConnection *gc = (PurpleConnection *) action->connection;
    - JabberStream *js = purple_connection_get_protocol_data(gc);
    -
    - jabber_adhoc_execute(js, cmd);
    - }
    -}
    -
    -void jabber_adhoc_init_server_commands(JabberStream *js, GList **m) {
    - GList *cmdlst;
    - JabberBuddy *jb;
    -
    - /* also add commands for other clients connected to the same account on another resource */
    - char *accountname = g_strdup_printf("%s@%s", js->user->node, js->user->domain);
    - if((jb = jabber_buddy_find(js, accountname, TRUE))) {
    - GList *iter;
    - for(iter = jb->resources; iter; iter = g_list_next(iter)) {
    - JabberBuddyResource *jbr = iter->data;
    - GList *riter;
    - for(riter = jbr->commands; riter; riter = g_list_next(riter)) {
    - JabberAdHocCommands *cmd = riter->data;
    - char *cmdname = g_strdup_printf("%s (%s)",cmd->name,jbr->name);
    - PurpleProtocolAction *act = purple_protocol_action_new(cmdname, jabber_adhoc_server_execute);
    - act->user_data = cmd;
    - *m = g_list_append(*m, act);
    - g_free(cmdname);
    - }
    - }
    - }
    - g_free(accountname);
    -
    - /* now add server commands */
    - for(cmdlst = js->commands; cmdlst; cmdlst = g_list_next(cmdlst)) {
    - JabberAdHocCommands *cmd = cmdlst->data;
    - PurpleProtocolAction *act = purple_protocol_action_new(cmd->name, jabber_adhoc_server_execute);
    - act->user_data = cmd;
    - *m = g_list_append(*m, act);
    - }
    -}
    --- a/libpurple/protocols/jabber/adhoccommands.h Tue Apr 09 23:36:32 2024 -0500
    +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
    @@ -1,51 +0,0 @@
    -/*
    - * 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
    - *
    - */
    -
    -#ifndef PURPLE_JABBER_ADHOCCOMMANDS_H
    -#define PURPLE_JABBER_ADHOCCOMMANDS_H
    -
    -#include "jabber.h"
    -
    -/* Implementation of XEP-0050 */
    -
    -typedef struct {
    - char *jid;
    - char *node;
    - char *name;
    -} JabberAdHocCommands;
    -
    -void jabber_adhoc_commands_free(JabberAdHocCommands *cmd);
    -
    -void jabber_adhoc_disco_result_cb(JabberStream *js, const char *from,
    - JabberIqType type, const char *id,
    - PurpleXmlNode *packet, gpointer data);
    -
    -void jabber_adhoc_execute_action(PurpleBlistNode *node, gpointer data);
    -
    -void jabber_adhoc_got_list(JabberStream *js, const char *from, PurpleXmlNode *query);
    -
    -void jabber_adhoc_server_get_list(JabberStream *js);
    -
    -void jabber_adhoc_init_server_commands(JabberStream *js, GList **m);
    -
    -#endif /* PURPLE_JABBER_ADHOCCOMMANDS_H */
    --- a/libpurple/protocols/jabber/auth.c Tue Apr 09 23:36:32 2024 -0500
    +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
    @@ -1,557 +0,0 @@
    -/*
    - * 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 "auth.h"
    -#include "disco.h"
    -#include "jabber.h"
    -#include "jutil.h"
    -#include "iq.h"
    -
    -static GSList *auth_mechs = NULL;
    -
    -static void auth_old_result_cb(JabberStream *js, const char *from,
    - JabberIqType type, const char *id,
    - PurpleXmlNode *packet, gpointer data);
    -
    -static void finish_plaintext_authentication(JabberStream *js)
    -{
    - JabberIq *iq;
    - PurpleXmlNode *query, *x;
    -
    - iq = jabber_iq_new_query(js, JABBER_IQ_SET, "jabber:iq:auth");
    - query = purple_xmlnode_get_child(iq->node, "query");
    - x = purple_xmlnode_new_child(query, "username");
    - purple_xmlnode_insert_data(x, js->user->node, -1);
    - x = purple_xmlnode_new_child(query, "resource");
    - purple_xmlnode_insert_data(x, js->user->resource, -1);
    - x = purple_xmlnode_new_child(query, "password");
    - purple_xmlnode_insert_data(x, purple_connection_get_password(js->gc), -1);
    - jabber_iq_set_callback(iq, auth_old_result_cb, NULL);
    - jabber_iq_send(iq);
    -}
    -
    -static void allow_plaintext_auth(PurpleAccount *account)
    -{
    - PurpleConnection *gc;
    - JabberStream *js;
    -
    - purple_account_set_bool(account, "auth_plain_in_clear", TRUE);
    -
    - gc = purple_account_get_connection(account);
    - js = purple_connection_get_protocol_data(gc);
    -
    - finish_plaintext_authentication(js);
    -}
    -
    -static void disallow_plaintext_auth(PurpleAccount *account)
    -{
    - purple_connection_error(purple_account_get_connection(account),
    - PURPLE_CONNECTION_ERROR_ENCRYPTION_ERROR,
    - _("Server requires plaintext authentication over an unencrypted stream"));
    -}
    -
    -static void
    -auth_old_pass_cb(PurpleConnection *gc, PurpleRequestPage *page) {
    - PurpleAccount *account;
    - JabberStream *js;
    - const char *entry;
    - gboolean remember;
    -
    - /* TODO: the password prompt dialog doesn't get disposed if the account disconnects */
    - PURPLE_ASSERT_CONNECTION_IS_VALID(gc);
    -
    - account = purple_connection_get_account(gc);
    - js = purple_connection_get_protocol_data(gc);
    -
    - entry = purple_request_page_get_string(page, "password");
    - remember = purple_request_page_get_bool(page, "remember");
    -
    - if (!entry || !*entry)
    - {
    - purple_notify_error(account, NULL,
    - _("Password is required to sign on."), NULL,
    - purple_request_cpar_from_connection(gc));
    - return;
    - }
    -
    - if(remember) {
    - PurpleCredentialManager *manager = NULL;
    -
    - purple_account_set_remember_password(account, TRUE);
    -
    - manager = purple_credential_manager_get_default();
    - purple_credential_manager_write_password_async(manager, account, entry,
    - NULL, NULL, NULL);
    - }
    -
    - /* Store the new password in our connection. */
    - purple_connection_set_password(gc, entry);
    -
    - /* Restart our connection. */
    - jabber_auth_start_old(js);
    -}
    -
    -static void
    -auth_no_pass_cb(PurpleConnection *gc, G_GNUC_UNUSED PurpleRequestPage *page) {
    - /* TODO: the password prompt dialog doesn't get disposed if the account disconnects */
    - PURPLE_ASSERT_CONNECTION_IS_VALID(gc);
    -
    - /* Disable the account as the user has cancelled connecting */
    - purple_account_set_enabled(purple_connection_get_account(gc), FALSE);
    -}
    -
    -static void
    -auth_old_read_pass_cb(GObject *source, GAsyncResult *result, gpointer data) {
    - JabberStream *js = data;
    - PurpleCredentialManager *manager = PURPLE_CREDENTIAL_MANAGER(source);
    - GError *error = NULL;
    - char *password = NULL;
    -
    - password = purple_credential_manager_read_password_finish(manager, result,
    - &error);
    - if(password == NULL || error != NULL) {
    - PurpleAccount *account = NULL;
    - const char *message = "unknown error";
    -
    - if(error != NULL) {
    - message = error->message;
    - }
    -
    - purple_debug_warning("jabber", "failed to read password from the "
    - "credential manager : %s", message);
    -
    - g_clear_error(&error);
    -
    - account = purple_connection_get_account(js->gc);
    - purple_account_request_password(account, G_CALLBACK(auth_old_pass_cb),
    - G_CALLBACK(auth_no_pass_cb),
    - js->gc);
    -
    - return;
    - }
    -
    - /* Save the password in the connection. */
    - purple_connection_set_password(js->gc, password);
    - purple_str_wipe(password);
    -
    - /* Restart the authentication process. */
    - jabber_auth_start_old(js);
    -}
    -
    -void
    -jabber_auth_start(JabberStream *js, PurpleXmlNode *packet)
    -{
    - GSList *mechanisms = NULL;
    - GSList *l;
    - PurpleXmlNode *response = NULL;
    - PurpleXmlNode *mechs, *mechnode;
    - JabberSaslState state;
    - char *msg = NULL;
    -
    - mechs = purple_xmlnode_get_child(packet, "mechanisms");
    - if(!mechs) {
    - purple_connection_error(js->gc,
    - PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
    - _("Invalid response from server"));
    - return;
    - }
    -
    - for(mechnode = purple_xmlnode_get_child(mechs, "mechanism"); mechnode;
    - mechnode = purple_xmlnode_get_next_twin(mechnode))
    - {
    - char *mech_name = purple_xmlnode_get_data(mechnode);
    -
    - if (mech_name && *mech_name)
    - mechanisms = g_slist_prepend(mechanisms, mech_name);
    - else
    - g_free(mech_name);
    -
    - }
    -
    - for (l = auth_mechs; l; l = l->next) {
    - JabberSaslMech *possible = l->data;
    -
    - /* Can we find this mechanism in the server's list? */
    - if (g_slist_find_custom(mechanisms, possible->name, (GCompareFunc)strcmp)) {
    - js->auth_mech = possible;
    - break;
    - }
    - }
    -
    - g_slist_free_full(mechanisms, g_free);
    -
    - if (js->auth_mech == NULL) {
    - /* Found no good mechanisms... */
    - purple_connection_error(js->gc,
    - PURPLE_CONNECTION_ERROR_AUTHENTICATION_IMPOSSIBLE,
    - _("Server does not use any supported authentication method"));
    - return;
    - }
    -
    - state = js->auth_mech->start(js, mechs, &response, &msg);
    - if (state == JABBER_SASL_STATE_FAIL) {
    - purple_connection_error(js->gc,
    - PURPLE_CONNECTION_ERROR_AUTHENTICATION_IMPOSSIBLE,
    - msg ? msg : _("Unknown Error"));
    - } else if (response) {
    - jabber_send(js, response);
    - purple_xmlnode_free(response);
    - }
    -
    - g_free(msg);
    -}
    -
    -static void
    -auth_old_result_cb(JabberStream *js, G_GNUC_UNUSED const char *from,
    - JabberIqType type, G_GNUC_UNUSED const char *id,
    - PurpleXmlNode *packet, G_GNUC_UNUSED gpointer data)
    -{
    - if (type == JABBER_IQ_RESULT) {
    - jabber_stream_set_state(js, JABBER_STREAM_POST_AUTH);
    - jabber_disco_items_server(js);
    - } else {
    - PurpleAccount *account;
    - PurpleConnectionError reason = PURPLE_CONNECTION_ERROR_NETWORK_ERROR;
    - char *msg = jabber_parse_error(js, packet, &reason);
    - PurpleXmlNode *error;
    - const char *err_code;
    -
    - account = purple_connection_get_account(js->gc);
    -
    - /* FIXME: Why is this not in jabber_parse_error? */
    - if((error = purple_xmlnode_get_child(packet, "error")) &&
    - (err_code = purple_xmlnode_get_attrib(error, "code")) &&
    - purple_strequal(err_code, "401")) {
    - reason = PURPLE_CONNECTION_ERROR_AUTHENTICATION_FAILED;
    - /* Clear the password if it isn't being saved */
    - if(!purple_account_get_remember_password(account)) {
    - PurpleCredentialManager *manager = NULL;
    -
    - manager = purple_credential_manager_get_default();
    -
    - purple_credential_manager_clear_password_async(manager, account,
    - NULL, NULL,
    - NULL);
    - }
    - }
    -
    - purple_connection_error(js->gc, reason, msg);
    - g_free(msg);
    - }
    -}
    -
    -static void
    -auth_old_cb(JabberStream *js, G_GNUC_UNUSED const char *from,
    - JabberIqType type, G_GNUC_UNUSED const char *id,
    - PurpleXmlNode *packet, G_GNUC_UNUSED gpointer data)
    -{
    - JabberIq *iq;
    - PurpleXmlNode *query, *x;
    - const char *pw = purple_connection_get_password(js->gc);
    -
    - if (type == JABBER_IQ_ERROR) {
    - PurpleConnectionError reason = PURPLE_CONNECTION_ERROR_NETWORK_ERROR;
    - char *msg = jabber_parse_error(js, packet, &reason);
    - purple_connection_error(js->gc, reason, msg);
    - g_free(msg);
    - } else if (type == JABBER_IQ_RESULT) {
    - query = purple_xmlnode_get_child(packet, "query");
    - if (js->stream_id && *js->stream_id &&
    - purple_xmlnode_get_child(query, "digest")) {
    - char *s, *hash;
    -
    - iq = jabber_iq_new_query(js, JABBER_IQ_SET, "jabber:iq:auth");
    - query = purple_xmlnode_get_child(iq->node, "query");
    - x = purple_xmlnode_new_child(query, "username");
    - purple_xmlnode_insert_data(x, js->user->node, -1);
    - x = purple_xmlnode_new_child(query, "resource");
    - purple_xmlnode_insert_data(x, js->user->resource, -1);
    -
    - x = purple_xmlnode_new_child(query, "digest");
    - s = g_strdup_printf("%s%s", js->stream_id, pw);
    - hash = g_compute_checksum_for_string(G_CHECKSUM_SHA1,
    - s, -1);
    - purple_xmlnode_insert_data(x, hash, -1);
    - g_free(hash);
    - g_free(s);
    - jabber_iq_set_callback(iq, auth_old_result_cb, NULL);
    - jabber_iq_send(iq);
    - } else if ((x = purple_xmlnode_get_child(query, "crammd5"))) {
    - /* For future reference, this appears to be a custom OS X extension
    - * to non-SASL authentication.
    - */
    - const char *challenge;
    - gchar *digest;
    -
    - /* Calculate the MHAC-MD5 digest */
    - challenge = purple_xmlnode_get_attrib(x, "challenge");
    - digest = g_compute_hmac_for_string(G_CHECKSUM_MD5,
    - (guchar *)pw, strlen(pw),
    - challenge, -1);
    -
    - g_return_if_fail(digest != NULL);
    -
    - /* Create the response query */
    - iq = jabber_iq_new_query(js, JABBER_IQ_SET, "jabber:iq:auth");
    - query = purple_xmlnode_get_child(iq->node, "query");
    -
    - x = purple_xmlnode_new_child(query, "username");
    - purple_xmlnode_insert_data(x, js->user->node, -1);
    - x = purple_xmlnode_new_child(query, "resource");
    - purple_xmlnode_insert_data(x, js->user->resource, -1);
    -
    - x = purple_xmlnode_new_child(query, "crammd5");
    -
    - purple_xmlnode_insert_data(x, digest, 32);
    - g_free(digest);
    -
    - jabber_iq_set_callback(iq, auth_old_result_cb, NULL);
    - jabber_iq_send(iq);
    -
    - } else if(purple_xmlnode_get_child(query, "password")) {
    - PurpleAccount *account = purple_connection_get_account(js->gc);
    - if(!jabber_stream_is_ssl(js) && !purple_account_get_bool(account,
    - "auth_plain_in_clear", FALSE))
    - {
    - PurpleContactInfo *info = PURPLE_CONTACT_INFO(account);
    - char *msg = g_strdup_printf(_("%s requires plaintext authentication over an unencrypted connection. Allow this and continue authentication?"),
    - purple_contact_info_get_username(info));
    - purple_request_yes_no(js->gc, _("Plaintext Authentication"),
    - _("Plaintext Authentication"),
    - msg,
    - 1,
    - purple_request_cpar_from_account(account),
    - account, allow_plaintext_auth,
    - disallow_plaintext_auth);
    - g_free(msg);
    - return;
    - }
    - finish_plaintext_authentication(js);
    - } else {
    - purple_connection_error(js->gc,
    - PURPLE_CONNECTION_ERROR_AUTHENTICATION_IMPOSSIBLE,
    - _("Server does not use any supported authentication method"));
    - return;
    - }
    - }
    -}
    -
    -void jabber_auth_start_old(JabberStream *js)
    -{
    - PurpleAccount *account;
    - JabberIq *iq;
    - PurpleXmlNode *query, *username;
    -
    - account = purple_connection_get_account(js->gc);
    -
    - /*
    - * We can end up here without encryption if the server doesn't support
    - * <stream:features/> and we're not using old-style SSL. If the user
    - * is requiring SSL/TLS, we need to enforce it.
    - */
    - if (!jabber_stream_is_ssl(js) &&
    - purple_strequal("require_tls",
    - purple_account_get_string(account, "connection_security", JABBER_DEFAULT_REQUIRE_TLS))) {
    - purple_connection_error(js->gc,
    - PURPLE_CONNECTION_ERROR_ENCRYPTION_ERROR,
    - _("You require encryption, but it is not available on this server."));
    - return;
    - }
    -
    - /*
    - * IQ Auth doesn't have support for resource binding, so we need to pick a
    - * default resource so it will work properly. jabberd14 throws an error and
    - * iChat server just fails silently.
    - */
    - if (!js->user->resource || *js->user->resource == '\0') {
    - g_free(js->user->resource);
    - js->user->resource = g_strdup("Home");
    - }
    -
    - /* With Cyrus SASL, passwords are optional for this protocol. So, we need to
    - * do our own password prompting here
    - */
    -
    - if (!purple_connection_get_password(js->gc)) {
    - PurpleCredentialManager *manager = NULL;
    -
    - manager = purple_credential_manager_get_default();
    -
    - purple_credential_manager_read_password_async(manager, account, NULL,
    - auth_old_read_pass_cb,
    - js);
    - return;
    - }
    - iq = jabber_iq_new_query(js, JABBER_IQ_GET, "jabber:iq:auth");
    -
    - query = purple_xmlnode_get_child(iq->node, "query");
    - username = purple_xmlnode_new_child(query, "username");
    - purple_xmlnode_insert_data(username, js->user->node, -1);
    -
    - jabber_iq_set_callback(iq, auth_old_cb, NULL);
    -
    - jabber_iq_send(iq);
    -}
    -
    -void
    -jabber_auth_handle_challenge(JabberStream *js, PurpleXmlNode *packet)
    -{
    - const char *ns = purple_xmlnode_get_namespace(packet);
    -
    - if (!purple_strequal(ns, NS_XMPP_SASL)) {
    - purple_connection_error(js->gc,
    - PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
    - _("Invalid response from server"));
    - return;
    - }
    -
    - if (js->auth_mech && js->auth_mech->handle_challenge) {
    - PurpleXmlNode *response = NULL;
    - char *msg = NULL;
    - JabberSaslState state = js->auth_mech->handle_challenge(js, packet, &response, &msg);
    - if (state == JABBER_SASL_STATE_FAIL) {
    - purple_connection_error(js->gc,
    - PURPLE_CONNECTION_ERROR_AUTHENTICATION_IMPOSSIBLE,
    - msg ? msg : _("Invalid challenge from server"));
    - } else if (response) {
    - jabber_send(js, response);
    - purple_xmlnode_free(response);
    - }
    -
    - g_free(msg);
    - } else
    - purple_debug_warning("jabber", "Received unexpected (and unhandled) <challenge/>\n");
    -}
    -
    -void jabber_auth_handle_success(JabberStream *js, PurpleXmlNode *packet)
    -{
    - const char *ns = purple_xmlnode_get_namespace(packet);
    -
    - if (!purple_strequal(ns, NS_XMPP_SASL)) {
    - purple_connection_error(js->gc,
    - PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
    - _("Invalid response from server"));
    - return;
    - }
    -
    - if (js->auth_mech && js->auth_mech->handle_success) {
    - char *msg = NULL;
    - JabberSaslState state = js->auth_mech->handle_success(js, packet, &msg);
    -
    - if (state == JABBER_SASL_STATE_FAIL) {
    - purple_connection_error(js->gc,
    - PURPLE_CONNECTION_ERROR_AUTHENTICATION_IMPOSSIBLE,
    - msg ? msg : _("Invalid response from server"));
    - return;
    - } else if (state == JABBER_SASL_STATE_CONTINUE) {
    - purple_connection_error(js->gc,
    - PURPLE_CONNECTION_ERROR_AUTHENTICATION_IMPOSSIBLE,
    - msg ? msg : _("Server thinks authentication is complete, but client does not"));
    - return;
    - }
    -
    - g_free(msg);
    - }
    -
    - /*
    - * The stream will be reinitialized later in jabber_recv_cb_ssl() or
    - * jabber_bosh_connection_send.
    - */
    - js->reinit = TRUE;
    - jabber_stream_set_state(js, JABBER_STREAM_POST_AUTH);
    -}
    -
    -void jabber_auth_handle_failure(JabberStream *js, PurpleXmlNode *packet)
    -{
    - PurpleConnectionError reason = PURPLE_CONNECTION_ERROR_NETWORK_ERROR;
    - char *msg = NULL;
    -
    - if (js->auth_mech && js->auth_mech->handle_failure) {
    - PurpleXmlNode *stanza = NULL;
    - JabberSaslState state = js->auth_mech->handle_failure(js, packet, &stanza, &msg);
    -
    - if (state != JABBER_SASL_STATE_FAIL) {
    - if (stanza) {
    - jabber_send(js, stanza);
    - purple_xmlnode_free(stanza);
    - }
    -
    - return;
    - }
    - }
    -
    - if (!msg)
    - msg = jabber_parse_error(js, packet, &reason);
    -
    - if (!msg) {
    - purple_connection_error(js->gc,
    - PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
    - _("Invalid response from server"));
    - } else {
    - purple_connection_error(js->gc, reason, msg);
    - g_free(msg);
    - }
    -}
    -
    -static gint compare_mech(gconstpointer a, gconstpointer b)
    -{
    - const JabberSaslMech *mech_a = a;
    - const JabberSaslMech *mech_b = b;
    -
    - /* higher priority comes *before* lower priority in the list */
    - if (mech_a->priority > mech_b->priority)
    - return -1;
    - else if (mech_a->priority < mech_b->priority)
    - return 1;
    - /* This really shouldn't happen */
    - return 0;
    -}
    -
    -static void
    -jabber_auth_add_mech(JabberSaslMech *mech) {
    - auth_mechs = g_slist_insert_sorted(auth_mechs, mech, compare_mech);
    -}
    -
    -void jabber_auth_init(void)
    -{
    - JabberSaslMech **tmp;
    - gint count, i;
    -
    - jabber_auth_add_mech(jabber_auth_get_plain_mech());
    - jabber_auth_add_mech(jabber_auth_get_digest_md5_mech());
    -#ifdef HAVE_WEBEX_TOKEN
    - jabber_auth_add_mech(jabber_auth_get_webex_token_mech());
    -#endif
    -
    - tmp = jabber_auth_get_scram_mechs(&count);
    - for (i = 0; i < count; ++i)
    - jabber_auth_add_mech(tmp[i]);
    -}
    -
    -void jabber_auth_uninit(void)
    -{
    - g_clear_slist(&auth_mechs, NULL);
    -}
    --- a/libpurple/protocols/jabber/auth.h Tue Apr 09 23:36:32 2024 -0500
    +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
    @@ -1,64 +0,0 @@
    -/**
    - * @file auth.h Authentication routines
    - *
    - * purple
    - *
    - * 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
    - */
    -
    -#ifndef PURPLE_JABBER_AUTH_H
    -#define PURPLE_JABBER_AUTH_H
    -
    -typedef struct _JabberSaslMech JabberSaslMech;
    -
    -#include <purple.h>
    -
    -#include "jabber.h"
    -
    -typedef enum {
    - JABBER_SASL_STATE_FAIL = -1, /* Abort, Retry, Fail? */
    - JABBER_SASL_STATE_OK = 0, /* Hooray! */
    - JABBER_SASL_STATE_CONTINUE = 1 /* More authentication required */
    -} JabberSaslState;
    -
    -struct _JabberSaslMech {
    - gint8 priority; /* Higher priority will be tried before lower priority */
    - const gchar *name;
    - JabberSaslState (*start)(JabberStream *js, PurpleXmlNode *mechanisms, PurpleXmlNode **reply, char **msg);
    - JabberSaslState (*handle_challenge)(JabberStream *js, PurpleXmlNode *packet, PurpleXmlNode **reply, char **msg);
    - JabberSaslState (*handle_success)(JabberStream *js, PurpleXmlNode *packet, char **msg);
    - JabberSaslState (*handle_failure)(JabberStream *js, PurpleXmlNode *packet, PurpleXmlNode **reply, char **msg);
    - void (*dispose)(JabberStream *js);
    -};
    -
    -void jabber_auth_start(JabberStream *js, PurpleXmlNode *packet);
    -void jabber_auth_start_old(JabberStream *js);
    -void jabber_auth_handle_challenge(JabberStream *js, PurpleXmlNode *packet);
    -void jabber_auth_handle_success(JabberStream *js, PurpleXmlNode *packet);
    -void jabber_auth_handle_failure(JabberStream *js, PurpleXmlNode *packet);
    -
    -JabberSaslMech *jabber_auth_get_plain_mech(void);
    -JabberSaslMech *jabber_auth_get_digest_md5_mech(void);
    -JabberSaslMech **jabber_auth_get_scram_mechs(gint *count);
    -JabberSaslMech *jabber_auth_get_webex_token_mech(void);
    -
    -void jabber_auth_init(void);
    -void jabber_auth_uninit(void);
    -
    -#endif /* PURPLE_JABBER_AUTH_H */
    --- a/libpurple/protocols/jabber/auth_digest_md5.c Tue Apr 09 23:36:32 2024 -0500
    +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
    @@ -1,285 +0,0 @@
    -/*
    - * 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 "auth_digest_md5.h"
    -#include "auth.h"
    -#include "jabber.h"
    -
    -static JabberSaslState
    -digest_md5_start(G_GNUC_UNUSED JabberStream *js,
    - G_GNUC_UNUSED PurpleXmlNode *packet, PurpleXmlNode **response,
    - G_GNUC_UNUSED char **error)
    -{
    - PurpleXmlNode *auth = purple_xmlnode_new("auth");
    - purple_xmlnode_set_namespace(auth, NS_XMPP_SASL);
    - purple_xmlnode_set_attrib(auth, "mechanism", "DIGEST-MD5");
    -
    - *response = auth;
    - return JABBER_SASL_STATE_CONTINUE;
    -}
    -
    -/* Parts of this algorithm are inspired by stuff in libgsasl */
    -GHashTable* jabber_auth_digest_md5_parse(const char *challenge)
    -{
    - const char *token_start, *val_start, *val_end, *cur;
    - GHashTable *ret = g_hash_table_new_full(g_str_hash, g_str_equal,
    - g_free, g_free);
    -
    - cur = challenge;
    - while(*cur != '\0') {
    - /* Find the end of the token */
    - gboolean in_quotes = FALSE;
    - char *name, *value = NULL;
    - token_start = cur;
    - while (*cur != '\0' && (in_quotes || *cur != ',')) {
    - if (*cur == '"')
    - in_quotes = !in_quotes;
    - cur++;
    - }
    -
    - /* Find start of value. */
    - val_start = strchr(token_start, '=');
    - if (val_start == NULL || val_start > cur)
    - val_start = cur;
    -
    - if (token_start != val_start) {
    - name = g_strndup(token_start, val_start - token_start);
    -
    - if (val_start != cur) {
    - val_start++;
    - while (val_start != cur && (*val_start == ' ' || *val_start == '\t'
    - || *val_start == '\r' || *val_start == '\n'
    - || *val_start == '"'))
    - val_start++;
    -
    - val_end = cur;
    - while (val_end >= val_start && (*val_end == ' ' || *val_end == ',' || *val_end == '\t'
    - || *val_end == '\r' || *val_end == '\n'
    - || *val_end == '"' || *val_end == '\0'))
    - val_end--;
    -
    - if (val_end - val_start + 1 >= 0)
    - value = g_strndup(val_start, val_end - val_start + 1);
    - }
    -
    - g_hash_table_replace(ret, name, value);
    - }
    -
    - /* Find the start of the next token, if there is one */
    - if (*cur != '\0') {
    - cur++;
    - while (*cur == ' ' || *cur == ',' || *cur == '\t'
    - || *cur == '\r' || *cur == '\n')
    - cur++;
    - }
    - }
    -
    - return ret;
    -}
    -
    -static char *
    -generate_response_value(JabberID *jid, const char *passwd, const char *nonce,
    - const char *cnonce, const char *a2, const char *realm)
    -{
    - GChecksum *hash;
    - gsize digest_len = 16;
    -
    - gchar *a1, *convnode=NULL, *convpasswd = NULL, *ha1, *ha2, *kd, *x, *z;
    -
    - if((convnode = g_convert(jid->node, -1, "iso-8859-1", "utf-8",
    - NULL, NULL, NULL)) == NULL) {
    - convnode = g_strdup(jid->node);
    - }
    - if(passwd && ((convpasswd = g_convert(passwd, -1, "iso-8859-1",
    - "utf-8", NULL, NULL, NULL)) == NULL)) {
    - convpasswd = g_strdup(passwd);
    - }
    -
    - hash = g_checksum_new(G_CHECKSUM_MD5);
    -
    - x = g_strdup_printf("%s:%s:%s", convnode, realm, convpasswd ? convpasswd : "");
    - g_checksum_update(hash, (const guchar *)x, -1);
    -
    - a1 = g_strdup_printf("xxxxxxxxxxxxxxxx:%s:%s", nonce, cnonce);
    -
    - g_checksum_get_digest(hash, (guint8 *)a1, &digest_len);
    - g_checksum_free(hash);
    -
    - ha1 = g_compute_checksum_for_string(G_CHECKSUM_MD5, a1, -1);
    - ha2 = g_compute_checksum_for_string(G_CHECKSUM_MD5, a2, -1);
    -
    - kd = g_strdup_printf("%s:%s:00000001:%s:auth:%s", ha1, nonce, cnonce, ha2);
    -
    - z = g_compute_checksum_for_string(G_CHECKSUM_MD5, kd, -1);
    -
    - g_free(convnode);
    - g_free(convpasswd);
    - g_free(x);
    - g_free(a1);
    - g_free(ha1);
    - g_free(ha2);
    - g_free(kd);
    -
    - return z;
    -}
    -
    -static JabberSaslState
    -digest_md5_handle_challenge(JabberStream *js, PurpleXmlNode *packet,
    - PurpleXmlNode **response, char **msg)
    -{
    - PurpleXmlNode *reply = NULL;
    - char *enc_in = purple_xmlnode_get_data(packet);
    - char *dec_in;
    - char *enc_out;
    - gsize size = 0;
    - GHashTable *parts;
    - JabberSaslState state = JABBER_SASL_STATE_CONTINUE;
    -
    - if (!enc_in) {
    - *msg = g_strdup(_("Invalid response from server"));
    - return JABBER_SASL_STATE_FAIL;
    - }
    -
    - dec_in = (char *)g_base64_decode(enc_in, &size);
    - purple_debug_misc("jabber", "decoded challenge (%"
    - G_GSIZE_FORMAT "): %s\n",
    - size,
    - dec_in);
    -
    - parts = jabber_auth_digest_md5_parse(dec_in);
    -
    - if (g_hash_table_lookup(parts, "rspauth")) {
    - char *rspauth = g_hash_table_lookup(parts, "rspauth");
    - char *expected_rspauth = js->auth_mech_data;
    -
    - if (rspauth && purple_strequal(rspauth, expected_rspauth)) {
    - reply = purple_xmlnode_new("response");
    - purple_xmlnode_set_namespace(reply, NS_XMPP_SASL);
    - } else {
    - *msg = g_strdup(_("Invalid challenge from server"));
    - state = JABBER_SASL_STATE_FAIL;
    - }
    - g_free(js->auth_mech_data);
    - js->auth_mech_data = NULL;
    - } else {
    - /* assemble a response, and send it */
    - /* see RFC 2831 */
    - char *realm;
    - char *nonce;
    -
    - /* Make sure the auth string contains everything that should be there.
    - This isn't everything in RFC2831, but it is what we need. */
    -
    - nonce = g_hash_table_lookup(parts, "nonce");
    -
    - /* we're actually supposed to prompt the user for a realm if
    - * the server doesn't send one, but that really complicates things,
    - * so i'm not gonna worry about it until is poses a problem to
    - * someone, or I get really bored */
    - realm = g_hash_table_lookup(parts, "realm");
    - if(!realm)
    - realm = js->user->domain;
    -
    - if (nonce == NULL || realm == NULL) {
    - *msg = g_strdup(_("Invalid challenge from server"));
    - state = JABBER_SASL_STATE_FAIL;
    - } else {
    - GString *response = g_string_new("");
    - char *a2;
    - char *auth_resp;
    - char *cnonce;
    -
    - cnonce = g_strdup_printf("%x%u%x", g_random_int(), (int)time(NULL),
    - g_random_int());
    -
    - a2 = g_strdup_printf("AUTHENTICATE:xmpp/%s", realm);
    - auth_resp = generate_response_value(js->user,
    - purple_connection_get_password(js->gc), nonce, cnonce, a2, realm);
    - g_free(a2);
    -
    - a2 = g_strdup_printf(":xmpp/%s", realm);
    - js->auth_mech_data = generate_response_value(js->user,
    - purple_connection_get_password(js->gc), nonce, cnonce, a2, realm);
    - g_free(a2);
    -
    - g_string_append_printf(response, "username=\"%s\"", js->user->node);
    - g_string_append_printf(response, ",realm=\"%s\"", realm);
    - g_string_append_printf(response, ",nonce=\"%s\"", nonce);
    - g_string_append_printf(response, ",cnonce=\"%s\"", cnonce);
    - g_string_append_printf(response, ",nc=00000001");
    - g_string_append_printf(response, ",qop=auth");
    - g_string_append_printf(response, ",digest-uri=\"xmpp/%s\"", realm);
    - g_string_append_printf(response, ",response=%s", auth_resp);
    - g_string_append_printf(response, ",charset=utf-8");
    -
    - g_free(auth_resp);
    - g_free(cnonce);
    -
    - enc_out = g_base64_encode((guchar *)response->str, response->len);
    -
    - purple_debug_misc("jabber", "decoded response (%"
    - G_GSIZE_FORMAT "): %s\n",
    - response->len, response->str);
    -
    - reply = purple_xmlnode_new("response");
    - purple_xmlnode_set_namespace(reply, NS_XMPP_SASL);
    - purple_xmlnode_insert_data(reply, enc_out, -1);
    -
    - g_free(enc_out);
    -
    - g_string_free(response, TRUE);
    - }
    - }
    -
    - g_free(enc_in);
    - g_free(dec_in);
    - g_hash_table_destroy(parts);
    -
    - *response = reply;
    - return state;
    -}
    -
    -static void
    -digest_md5_dispose(JabberStream *js)
    -{
    - g_free(js->auth_mech_data);
    - js->auth_mech_data = NULL;
    -}
    -
    -static JabberSaslMech digest_md5_mech = {
    - 10, /* priority */
    - "DIGEST-MD5", /* name */
    - digest_md5_start,
    - digest_md5_handle_challenge,
    - NULL, /* handle_success */
    - NULL, /* handle_failure */
    - digest_md5_dispose,
    -};
    -
    -JabberSaslMech *jabber_auth_get_digest_md5_mech(void)
    -{
    - return &digest_md5_mech;
    -}
    --- a/libpurple/protocols/jabber/auth_digest_md5.h Tue Apr 09 23:36:32 2024 -0500
    +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
    @@ -1,41 +0,0 @@
    -/**
    - * @file auth_digest_md5.h Implementation of SASL DIGEST-MD5 authentication
    - *
    - * purple
    - *
    - * 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
    - */
    -
    -#ifndef PURPLE_JABBER_AUTH_DIGEST_MD5_H
    -#define PURPLE_JABBER_AUTH_DIGEST_MD5_H
    -
    -#include "jabber.h"
    -
    -/*
    - * Every function in this file is ONLY exposed for tests.
    - * DO NOT USE ANYTHING HERE OR YOU WILL BE SENT TO THE PIT OF DESPAIR.
    - */
    -
    -/*
    - * Parse a DIGEST-MD5 challenge.
    - */
    -PURPLE_XMPP_EXTERN_FOR_TESTS
    -GHashTable *jabber_auth_digest_md5_parse(const char *challenge);
    -
    -#endif /* PURPLE_JABBER_AUTH_DIGEST_MD5_H */
    --- a/libpurple/protocols/jabber/auth_plain.c Tue Apr 09 23:36:32 2024 -0500
    +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
    @@ -1,114 +0,0 @@
    -/*
    - * 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 "jabber.h"
    -#include "auth.h"
    -
    -static PurpleXmlNode *finish_plaintext_authentication(JabberStream *js)
    -{
    - PurpleXmlNode *auth;
    - GString *response;
    - gchar *enc_out;
    -
    - auth = purple_xmlnode_new("auth");
    - purple_xmlnode_set_namespace(auth, NS_XMPP_SASL);
    -
    - response = g_string_new("");
    - response = g_string_append_c(response, '\0');
    - response = g_string_append(response, js->user->node);
    - response = g_string_append_c(response, '\0');
    - response = g_string_append(response,
    - purple_connection_get_password(js->gc));
    -
    - enc_out = g_base64_encode((guchar *)response->str, response->len);
    -
    - purple_xmlnode_set_attrib(auth, "mechanism", "PLAIN");
    - purple_xmlnode_insert_data(auth, enc_out, -1);
    - g_free(enc_out);
    - g_string_free(response, TRUE);
    -
    - return auth;
    -}
    -
    -static void allow_plaintext_auth(PurpleAccount *account)
    -{
    - PurpleConnection *gc = purple_account_get_connection(account);
    - JabberStream *js = purple_connection_get_protocol_data(gc);
    - PurpleXmlNode *response;
    -
    - purple_account_set_bool(account, "auth_plain_in_clear", TRUE);
    -
    - response = finish_plaintext_authentication(js);
    - jabber_send(js, response);
    - purple_xmlnode_free(response);
    -}
    -
    -static void disallow_plaintext_auth(PurpleAccount *account)
    -{
    - purple_connection_error(purple_account_get_connection(account),
    - PURPLE_CONNECTION_ERROR_ENCRYPTION_ERROR,
    - _("Server requires plaintext authentication over an unencrypted stream"));
    -}
    -
    -static JabberSaslState
    -jabber_plain_start(JabberStream *js, G_GNUC_UNUSED PurpleXmlNode *packet,
    - PurpleXmlNode **response, G_GNUC_UNUSED char **error)
    -{
    - PurpleAccount *account = purple_connection_get_account(js->gc);
    - PurpleContactInfo *info = PURPLE_CONTACT_INFO(account);
    - char *msg;
    -
    - if (jabber_stream_is_ssl(js) || purple_account_get_bool(account, "auth_plain_in_clear", FALSE)) {
    - *response = finish_plaintext_authentication(js);
    - return JABBER_SASL_STATE_OK;
    - }
    -
    - msg = g_strdup_printf(_("%s requires plaintext authentication over an unencrypted connection. Allow this and continue authentication?"),
    - purple_contact_info_get_username(info));
    - purple_request_yes_no(js->gc, _("Plaintext Authentication"),
    - _("Plaintext Authentication"),
    - msg,
    - 1,
    - purple_request_cpar_from_account(account),
    - account, allow_plaintext_auth, disallow_plaintext_auth);
    - g_free(msg);
    - return JABBER_SASL_STATE_CONTINUE;
    -}
    -
    -static JabberSaslMech plain_mech = {
    - 0, /* priority */
    - "PLAIN", /* name */
    - jabber_plain_start,
    - NULL, /* handle_challenge */
    - NULL, /* handle_success */
    - NULL, /* handle_failure */
    - NULL /* dispose */
    -};
    -
    -JabberSaslMech *jabber_auth_get_plain_mech(void)
    -{
    - return &plain_mech;
    -}
    --- a/libpurple/protocols/jabber/auth_scram.c Tue Apr 09 23:36:32 2024 -0500
    +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
    @@ -1,598 +0,0 @@
    -/*
    - * 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 "auth.h"
    -#include "auth_scram.h"
    -
    -static const JabberScramHash hashes[] = {
    - { "-SHA-1", G_CHECKSUM_SHA1 },
    -};
    -
    -static const JabberScramHash *mech_to_hash(const char *mech)
    -{
    - gsize i;
    -
    - g_return_val_if_fail(mech != NULL && *mech != '\0', NULL);
    -
    - for (i = 0; i < G_N_ELEMENTS(hashes); ++i) {
    - if (strstr(mech, hashes[i].mech_substr))
    - return &(hashes[i]);
    - }
    -
    - purple_debug_error("jabber", "Unknown SCRAM mechanism %s\n", mech);
    - g_return_val_if_reached(NULL);
    -}
    -
    -guchar *jabber_scram_hi(const JabberScramHash *hash, const GString *str,
    - GString *salt, guint iterations)
    -{
    - GHmac *hmac;
    - gsize digest_len;
    - guchar *result;
    - guint i;
    - guchar *prev, *tmp;
    -
    - g_return_val_if_fail(hash != NULL, NULL);
    - g_return_val_if_fail(str != NULL && str->len > 0, NULL);
    - g_return_val_if_fail(salt != NULL && salt->len > 0, NULL);
    - g_return_val_if_fail(iterations > 0, NULL);
    -
    - digest_len = g_checksum_type_get_length(hash->type);
    - prev = g_new0(guchar, digest_len);
    - tmp = g_new0(guchar, digest_len);
    - result = g_new0(guchar, digest_len);
    -
    - hmac = g_hmac_new(hash->type, (guchar *)str->str, str->len);
    -
    - /* Append INT(1), a four-octet encoding of the integer 1, most significant
    - * octet first. */
    - g_string_append_len(salt, "\0\0\0\1", 4);
    -
    - /* Compute U0 */
    - g_hmac_update(hmac, (guchar *)salt->str, salt->len);
    - g_hmac_get_digest(hmac, result, &digest_len);
    - g_hmac_unref(hmac);
    -
    - memcpy(prev, result, digest_len);
    -
    - /* Compute U1...Ui */
    - for (i = 1; i < iterations; ++i) {
    - guint j;
    - hmac = g_hmac_new(hash->type, (guchar *)str->str, str->len);
    - g_hmac_update(hmac, prev, digest_len);
    - g_hmac_get_digest(hmac, tmp, &digest_len);
    - g_hmac_unref(hmac);
    -
    - for (j = 0; j < digest_len; ++j)
    - result[j] ^= tmp[j];
    -
    - memcpy(prev, tmp, digest_len);
    - }
    -
    - g_free(tmp);
    - g_free(prev);
    - return result;
    -}
    -
    -/*
    - * Helper functions for doing the SCRAM calculations. The first argument
    - * is the hash algorithm. All buffers must be of the appropriate size
    - * according to the JabberScramHash.
    - *
    - * "str" is a NULL-terminated string for jabber_scram_hmac().
    - *
    - * Needless to say, these are fragile.
    - */
    -static void
    -jabber_scram_hmac(const JabberScramHash *hash, guchar *out, const guchar *key, const gchar *str)
    -{
    - GHmac *hmac;
    - gsize digest_len = g_checksum_type_get_length(hash->type);
    -
    - hmac = g_hmac_new(hash->type, key, digest_len);
    - g_hmac_update(hmac, (guchar *)str, -1);
    - g_hmac_get_digest(hmac, out, &digest_len);
    - g_hmac_unref(hmac);
    -}
    -
    -static void
    -jabber_scram_hash(const JabberScramHash *hash, guchar *out, const guchar *data)
    -{
    - GChecksum *checksum;
    - gsize digest_len = g_checksum_type_get_length(hash->type);
    -
    - checksum = g_checksum_new(hash->type);
    - g_checksum_update(checksum, data, digest_len);
    - g_checksum_get_digest(checksum, out, &digest_len);
    - g_checksum_free(checksum);
    -}
    -
    -gboolean
    -jabber_scram_calc_proofs(JabberScramData *data, GString *salt, guint iterations)
    -{
    - guint hash_len = g_checksum_type_get_length(data->hash->type);
    - guint i;
    -
    - GString *pass = g_string_new(data->password);
    -
    - guchar *salted_password;
    - guchar *client_key, *stored_key, *client_signature, *server_key;
    -
    - data->client_proof = g_string_sized_new(hash_len);
    - data->client_proof->len = hash_len;
    - data->server_signature = g_string_sized_new(hash_len);
    - data->server_signature->len = hash_len;
    -
    - salted_password = jabber_scram_hi(data->hash, pass, salt, iterations);
    -
    - memset(pass->str, 0, pass->allocated_len);
    - g_string_free(pass, TRUE);
    -
    - if (!salted_password)
    - return FALSE;
    -
    - client_key = g_new0(guchar, hash_len);
    - stored_key = g_new0(guchar, hash_len);
    - client_signature = g_new0(guchar, hash_len);
    - server_key = g_new0(guchar, hash_len);
    -
    - /* client_key = HMAC(salted_password, "Client Key") */
    - jabber_scram_hmac(data->hash, client_key, salted_password, "Client Key");
    - /* server_key = HMAC(salted_password, "Server Key") */
    - jabber_scram_hmac(data->hash, server_key, salted_password, "Server Key");
    - g_free(salted_password);
    -
    - /* stored_key = HASH(client_key) */
    - jabber_scram_hash(data->hash, stored_key, client_key);
    -
    - /* client_signature = HMAC(stored_key, auth_message) */
    - jabber_scram_hmac(data->hash, client_signature, stored_key, data->auth_message->str);
    - /* server_signature = HMAC(server_key, auth_message) */
    - jabber_scram_hmac(data->hash, (guchar *)data->server_signature->str, server_key, data->auth_message->str);
    -
    - /* client_proof = client_key XOR client_signature */
    - for (i = 0; i < hash_len; ++i)
    - data->client_proof->str[i] = client_key[i] ^ client_signature[i];
    -
    - g_free(server_key);
    - g_free(client_signature);
    - g_free(stored_key);
    - g_free(client_key);
    -
    - return TRUE;
    -}
    -
    -static gboolean
    -parse_server_step1(JabberScramData *data, const char *challenge,
    - gchar **out_nonce, GString **out_salt, guint *out_iterations)
    -{
    - char **tokens;
    - char *token, *decoded, *tmp;
    - gsize len;
    - char *nonce = NULL;
    - GString *salt = NULL;
    - guint iterations;
    -
    - tokens = g_strsplit(challenge, ",", -1);
    - if (tokens == NULL)
    - return FALSE;
    -
    - token = tokens[0];
    - if (token[0] != 'r' || token[1] != '=')
    - goto err;
    -
    - /* Ensure that the first cnonce_len bytes of the nonce are the original
    - * cnonce we sent to the server.
    - */
    - if (0 != strncmp(data->cnonce, token + 2, strlen(data->cnonce)))
    - goto err;
    -
    - nonce = g_strdup(token + 2);
    -
    - /* The Salt, base64-encoded */
    - token = tokens[1];
    - if (token[0] != 's' || token[1] != '=')
    - goto err;
    -
    - decoded = (gchar *)g_base64_decode(token + 2, &len);
    - if (!decoded || *decoded == '\0') {
    - g_free(decoded);
    - goto err;
    - }
    - salt = g_string_new_len(decoded, len);
    - g_free(decoded);
    -
    - /* The iteration count */
    - token = tokens[2];
    - if (token[0] != 'i' || token[1] != '=' || token[2] == '\0')
    - goto err;
    -
    - /* Validate the string */
    - for (tmp = token + 2; *tmp; ++tmp)
    - if (!g_ascii_isdigit(*tmp))
    - goto err;
    -
    - iterations = strtoul(token + 2, NULL, 10);
    -
    - g_strfreev(tokens);
    - *out_nonce = nonce;
    - *out_salt = salt;
    - *out_iterations = iterations;
    - return TRUE;
    -
    -err:
    - g_free(nonce);
    - if (salt)
    - g_string_free(salt, TRUE);
    - g_strfreev(tokens);
    - return FALSE;
    -}
    -
    -static gboolean
    -parse_server_step2(G_GNUC_UNUSED JabberScramData *data, const char *challenge,
    - char **out_verifier)
    -{
    - char **tokens;
    - char *token;
    -
    - tokens = g_strsplit(challenge, ",", -1);
    - if (tokens == NULL)
    - return FALSE;
    -
    - token = tokens[0];
    - if (token[0] != 'v' || token[1] != '=' || token[2] == '\0') {
    - g_strfreev(tokens);
    - return FALSE;
    - }
    -
    - *out_verifier = g_strdup(token + 2);
    - g_strfreev(tokens);
    - return TRUE;
    -}
    -
    -gboolean
    -jabber_scram_feed_parser(JabberScramData *data, gchar *in, gchar **out)
    -{
    - gboolean ret;
    -
    - g_return_val_if_fail(data != NULL, FALSE);
    -
    - g_string_append_c(data->auth_message, ',');
    - g_string_append(data->auth_message, in);
    -
    - if (data->step == 1) {
    - gchar *nonce, *proof;
    - GString *salt;
    - guint iterations;
    -
    - ret = parse_server_step1(data, in, &nonce, &salt, &iterations);
    - if (!ret)
    - return FALSE;
    -
    - g_string_append_c(data->auth_message, ',');
    -
    - /* "biws" is the base64 encoding of "n,,". I promise. */
    - g_string_append_printf(data->auth_message, "c=%s,r=%s", "biws", nonce);
    -#ifdef CHANNEL_BINDING
    -#error fix this
    -#endif
    -
    - ret = jabber_scram_calc_proofs(data, salt, iterations);
    -
    - g_string_free(salt, TRUE);
    - salt = NULL;
    - if (!ret) {
    - g_free(nonce);
    - return FALSE;
    - }
    -
    - proof = g_base64_encode((guchar *)data->client_proof->str, data->client_proof->len);
    - *out = g_strdup_printf("c=%s,r=%s,p=%s", "biws", nonce, proof);
    - g_free(nonce);
    - g_free(proof);
    - } else if (data->step == 2) {
    - gchar *server_sig, *enc_server_sig;
    - gsize len;
    -
    - ret = parse_server_step2(data, in, &enc_server_sig);
    - if (!ret)
    - return FALSE;
    -
    - server_sig = (gchar *)g_base64_decode(enc_server_sig, &len);
    - g_free(enc_server_sig);
    -
    - if (server_sig == NULL || len != data->server_signature->len) {
    - g_free(server_sig);
    - return FALSE;
    - }
    -
    - if (0 != memcmp(server_sig, data->server_signature->str, len)) {
    - g_free(server_sig);
    - return FALSE;
    - }
    - g_free(server_sig);
    -
    - *out = NULL;
    - } else {
    - purple_debug_error("jabber", "SCRAM: There is no step %d\n", data->step);
    - return FALSE;
    - }
    -
    - return TRUE;
    -}
    -
    -static gchar *escape_username(const gchar *in)
    -{
    - gchar *tmp, *tmp2;
    -
    - tmp = purple_strreplace(in, "=", "=3D");
    - tmp2 = purple_strreplace(tmp, ",", "=2C");
    - g_free(tmp);
    - return tmp2;
    -}
    -
    -static JabberSaslState
    -scram_start(JabberStream *js, G_GNUC_UNUSED PurpleXmlNode *mechanisms,
    - PurpleXmlNode **out, char **error)
    -{
    - PurpleXmlNode *reply;
    - JabberScramData *data;
    - guint64 cnonce;
    -#ifdef CHANNEL_BINDING
    - gboolean binding_supported = TRUE;
    -#endif
    - gchar *dec_out, *enc_out;
    - gchar *prepped_node, *tmp;
    - gchar *prepped_pass;
    -
    - prepped_node = jabber_saslprep(js->user->node);
    - if (!prepped_node) {
    - *error = g_strdup(_("Unable to canonicalize username"));
    - return JABBER_SASL_STATE_FAIL;
    - }
    -
    - tmp = escape_username(prepped_node);
    - g_free(prepped_node);
    - prepped_node = tmp;
    -
    - prepped_pass = jabber_saslprep(purple_connection_get_password(js->gc));
    - if (!prepped_pass) {
    - g_free(prepped_node);
    - *error = g_strdup(_("Unable to canonicalize password"));
    - return JABBER_SASL_STATE_FAIL;
    - }
    -
    - data = js->auth_mech_data = g_new0(JabberScramData, 1);
    - data->hash = mech_to_hash(js->auth_mech->name);
    - data->password = prepped_pass;
    -
    -#ifdef CHANNEL_BINDING
    - if (strstr(js->auth_mech_name, "-PLUS"))
    - data->channel_binding = TRUE;
    -#endif
    - cnonce = ((guint64)g_random_int() << 32) | g_random_int();
    - data->cnonce = g_base64_encode((guchar *)&cnonce, sizeof(cnonce));
    -
    - data->auth_message = g_string_new(NULL);
    - g_string_printf(data->auth_message, "n=%s,r=%s",
    - prepped_node, data->cnonce);
    - g_free(prepped_node);
    -
    - data->step = 1;
    -
    - reply = purple_xmlnode_new("auth");
    - purple_xmlnode_set_namespace(reply, NS_XMPP_SASL);
    - purple_xmlnode_set_attrib(reply, "mechanism", js->auth_mech->name);
    -
    - /* TODO: Channel binding */
    - dec_out = g_strdup_printf("%c,,%s", 'n', data->auth_message->str);
    - enc_out = g_base64_encode((guchar *)dec_out, strlen(dec_out));
    - purple_debug_misc("jabber", "initial SCRAM message '%s'\n", dec_out);
    -
    - purple_xmlnode_insert_data(reply, enc_out, -1);
    -
    - g_free(enc_out);
    - g_free(dec_out);
    -
    - *out = reply;
    - return JABBER_SASL_STATE_CONTINUE;
    -}
    -
    -static JabberSaslState
    -scram_handle_challenge(JabberStream *js, PurpleXmlNode *challenge, PurpleXmlNode **out, char **error)
    -{
    - JabberScramData *data = js->auth_mech_data;
    - PurpleXmlNode *reply;
    - gchar *enc_in, *dec_in = NULL;
    - gchar *enc_out = NULL, *dec_out = NULL;
    - gsize len;
    - JabberSaslState state = JABBER_SASL_STATE_FAIL;
    -
    - enc_in = purple_xmlnode_get_data(challenge);
    - if (!enc_in || *enc_in == '\0') {
    - reply = purple_xmlnode_new("abort");
    - purple_xmlnode_set_namespace(reply, NS_XMPP_SASL);
    - data->step = -1;
    - *error = g_strdup(_("Invalid challenge from server"));
    - goto out;
    - }
    -
    - dec_in = (gchar *)g_base64_decode(enc_in, &len);
    - if (!dec_in || len != strlen(dec_in)) {
    - /* Danger afoot; SCRAM shouldn't contain NUL bytes */
    - reply = purple_xmlnode_new("abort");
    - purple_xmlnode_set_namespace(reply, NS_XMPP_SASL);
    - data->step = -1;
    - *error = g_strdup(_("Malicious challenge from server"));
    - goto out;
    - }
    -
    - purple_debug_misc("jabber", "decoded challenge: %s\n", dec_in);
    -
    - if (!jabber_scram_feed_parser(data, dec_in, &dec_out)) {
    - reply = purple_xmlnode_new("abort");
    - purple_xmlnode_set_namespace(reply, NS_XMPP_SASL);
    - data->step = -1;
    - *error = g_strdup(_("Invalid challenge from server"));
    - goto out;
    - }
    -
    - data->step += 1;
    -
    - reply = purple_xmlnode_new("response");
    - purple_xmlnode_set_namespace(reply, NS_XMPP_SASL);
    -
    - purple_debug_misc("jabber", "decoded response: %s\n", dec_out ? dec_out : "(null)");
    - if (dec_out) {
    - enc_out = g_base64_encode((guchar *)dec_out, strlen(dec_out));
    - purple_xmlnode_insert_data(reply, enc_out, -1);
    - }
    -
    - state = JABBER_SASL_STATE_CONTINUE;
    -
    -out:
    - g_free(enc_in);
    - g_free(dec_in);
    - g_free(enc_out);
    - g_free(dec_out);
    -
    - *out = reply;
    - return state;
    -}
    -
    -static JabberSaslState
    -scram_handle_success(JabberStream *js, PurpleXmlNode *packet, char **error)
    -{
    - JabberScramData *data = js->auth_mech_data;
    - char *enc_in, *dec_in;
    - char *dec_out = NULL;
    - gsize len;
    -
    - enc_in = purple_xmlnode_get_data(packet);
    - if (data->step != 3 && (!enc_in || *enc_in == '\0')) {
    - *error = g_strdup(_("Invalid challenge from server"));
    - g_free(enc_in);
    - return JABBER_SASL_STATE_FAIL;
    - }
    -
    - if (data->step == 3) {
    - /*
    - * If the server took the slow approach (sending the verifier
    - * as a challenge/response pair), we get here.
    - */
    - g_free(enc_in);
    - return JABBER_SASL_STATE_OK;
    - }
    -
    - if (data->step != 2) {
    - *error = g_strdup(_("Unexpected response from server"));
    - g_free(enc_in);
    - return JABBER_SASL_STATE_FAIL;
    - }
    -
    - dec_in = (gchar *)g_base64_decode(enc_in, &len);
    - g_free(enc_in);
    - if (!dec_in || len != strlen(dec_in)) {
    - /* Danger afoot; SCRAM shouldn't contain NUL bytes */
    - g_free(dec_in);
    - *error = g_strdup(_("Malicious challenge from server"));
    - return JABBER_SASL_STATE_FAIL;
    - }
    -
    - purple_debug_misc("jabber", "decoded success: %s\n", dec_in);
    -
    - if (!jabber_scram_feed_parser(data, dec_in, &dec_out) || dec_out != NULL) {
    - g_free(dec_in);
    - g_free(dec_out);
    - *error = g_strdup(_("Invalid challenge from server"));
    - return JABBER_SASL_STATE_FAIL;
    - }
    -
    - g_free(dec_in);
    - /* Hooray */
    - return JABBER_SASL_STATE_OK;
    -}
    -
    -void jabber_scram_data_destroy(JabberScramData *data)
    -{
    - g_free(data->cnonce);
    - if (data->auth_message)
    - g_string_free(data->auth_message, TRUE);
    - if (data->client_proof)
    - g_string_free(data->client_proof, TRUE);
    - if (data->server_signature)
    - g_string_free(data->server_signature, TRUE);
    - if (data->password) {
    - memset(data->password, 0, strlen(data->password));
    - g_free(data->password);
    - }
    -
    - g_free(data);
    -}
    -
    -static void scram_dispose(JabberStream *js)
    -{
    - if (js->auth_mech_data) {
    - jabber_scram_data_destroy(js->auth_mech_data);
    - js->auth_mech_data = NULL;
    - }
    -}
    -
    -static JabberSaslMech scram_sha1_mech = {
    - 50, /* priority */
    - "SCRAM-SHA-1", /* name */
    - scram_start,
    - scram_handle_challenge,
    - scram_handle_success,
    - NULL, /* handle_failure */
    - scram_dispose
    -};
    -
    -#ifdef CHANNEL_BINDING
    -/* With channel binding */
    -static JabberSaslMech scram_sha1_plus_mech = {
    - scram_sha1_mech.priority + 1, /* priority */
    - "SCRAM-SHA-1-PLUS", /* name */
    - scram_start,
    - scram_handle_challenge,
    - scram_handle_success,
    - NULL, /* handle_failure */
    - scram_dispose
    -};
    -#endif
    -
    -JabberSaslMech **jabber_auth_get_scram_mechs(gint *count)
    -{
    - static JabberSaslMech *mechs[] = {
    - &scram_sha1_mech,
    -#ifdef CHANNEL_BINDING
    - &scram_sha1_plus_mech,
    -#endif
    - };
    -
    - *count = G_N_ELEMENTS(mechs);
    - return mechs;
    -}
    --- a/libpurple/protocols/jabber/auth_scram.h Tue Apr 09 23:36:32 2024 -0500
    +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
    @@ -1,99 +0,0 @@
    -/**
    - * @file auth_scram.h Implementation of SASL-SCRAM authentication
    - *
    - * purple
    - *
    - * 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
    - */
    -
    -#ifndef PURPLE_JABBER_AUTH_SCRAM_H
    -#define PURPLE_JABBER_AUTH_SCRAM_H
    -
    -/*
    - * Every function in this file is ONLY exposed for tests.
    - * DO NOT USE ANYTHING HERE OR YOU WILL BE SENT TO THE PIT OF DESPAIR.
    - */
    -
    -/* Per-connection state stored between messages.
    - * This is stored in js->auth_data_mech.
    - */
    -typedef struct {
    - const char *mech_substr;
    - GChecksumType type;
    -} JabberScramHash;
    -
    -typedef struct {
    - const JabberScramHash *hash;
    - char *cnonce;
    - GString *auth_message;
    -
    - GString *client_proof;
    - GString *server_signature;
    -
    - gchar *password;
    - gboolean channel_binding;
    - int step;
    -} JabberScramData;
    -
    -#include "auth.h"
    -
    -/**
    - * Implements the Hi() function as described in the SASL-SCRAM I-D.
    - *
    - * @param hash The struct corresponding to the hash function to be used.
    - * @param str The string to perform the PBKDF2 operation on.
    - * @param salt The salt.
    - * @param iterations The number of iterations to perform.
    - *
    - * @returns A newly allocated string containing the result. The string is
    - * NOT null-terminated and its length is the length of the binary
    - * output of the hash function in-use.
    - */
    -PURPLE_XMPP_EXTERN_FOR_TESTS
    -guchar *jabber_scram_hi(const JabberScramHash *hash, const GString *str,
    - GString *salt, guint iterations);
    -
    -/**
    - * Calculates the proofs as described in Section 3 of the SASL-SCRAM I-D.
    - *
    - * @param data A JabberScramData structure. hash and auth_message must be
    - * set. client_proof and server_signature will be set as a result
    - * of this function.
    - * @param salt The salt (as specified by the server)
    - * @param iterations The number of iterations to perform.
    - *
    - * @returns TRUE if the proofs were successfully calculated. FALSE otherwise.
    - */
    -PURPLE_XMPP_EXTERN_FOR_TESTS
    -gboolean jabber_scram_calc_proofs(JabberScramData *data, GString *salt,
    - guint iterations);
    -
    -/**
    - * Feed the algorithm with the data from the server.
    - */
    -PURPLE_XMPP_EXTERN_FOR_TESTS
    -gboolean jabber_scram_feed_parser(JabberScramData *data, gchar *in, gchar **out);
    -
    -/**
    - * Clean up and destroy the data struct
    - */
    -PURPLE_XMPP_EXTERN_FOR_TESTS
    -void jabber_scram_data_destroy(JabberScramData *data);
    -
    -#endif /* PURPLE_JABBER_AUTH_SCRAM_H */
    --- a/libpurple/protocols/jabber/auth_webex.c Tue Apr 09 23:36:32 2024 -0500
    +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
    @@ -1,62 +0,0 @@
    -/*
    - * 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 <purple.h>
    -
    -#include "jabber.h"
    -#include "auth.h"
    -
    -static PurpleXmlNode *finish_webex_authentication(JabberStream *js)
    -{
    - PurpleXmlNode *auth;
    -
    - auth = purple_xmlnode_new("auth");
    - purple_xmlnode_set_namespace(auth, NS_XMPP_SASL);
    -
    - purple_xmlnode_set_attrib(auth, "mechanism", "WEBEX-TOKEN");
    - purple_xmlnode_insert_data(auth, purple_connection_get_password(js->gc), -1);
    -
    - return auth;
    -}
    -
    -static JabberSaslState
    -jabber_webex_start(JabberStream *js, G_GNUC_UNUSED PurpleXmlNode *packet,
    - PurpleXmlNode **response, G_GNUC_UNUSED char **error)
    -{
    - *response = finish_webex_authentication(js);
    - return JABBER_SASL_STATE_OK;
    -}
    -
    -static JabberSaslMech webex_token_mech = {
    - 101, /* priority */
    - "WEBEX-TOKEN", /* name */
    - jabber_webex_start,
    - NULL, /* handle_challenge */
    - NULL, /* handle_success */
    - NULL, /* handle_failure */
    - NULL /* dispose */
    -};
    -
    -JabberSaslMech *jabber_auth_get_webex_token_mech(void)
    -{
    - return &webex_token_mech;
    -}
    --- a/libpurple/protocols/jabber/bosh.c Tue Apr 09 23:36:32 2024 -0500
    +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
    @@ -1,544 +0,0 @@
    -/*
    - * 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 <libsoup/soup.h>
    -
    -#include "bosh.h"
    -
    -/*
    -TODO: test, what happens, if the http server (BOSH server) doesn't support
    -keep-alive (sends connection: close).
    -*/
    -
    -#define JABBER_BOSH_SEND_DELAY 250
    -
    -#define JABBER_BOSH_TIMEOUT 10
    -
    -static gchar *jabber_bosh_useragent = NULL;
    -
    -struct _PurpleJabberBOSHConnection {
    - JabberStream *js;
    - SoupSession *payload_reqs;
    -
    - gchar *url;
    - gboolean is_ssl;
    - gboolean is_terminating;
    -
    - gchar *sid;
    - guint64 rid; /* Must be big enough to hold 2^53 - 1 */
    -
    - GString *send_buff;
    - guint send_timer;
    -};
    -
    -static SoupMessage *jabber_bosh_connection_http_request_new(
    - PurpleJabberBOSHConnection *conn, GString *data);
    -static void
    -jabber_bosh_connection_session_create(PurpleJabberBOSHConnection *conn);
    -static void
    -jabber_bosh_connection_send_now(PurpleJabberBOSHConnection *conn);
    -
    -void
    -jabber_bosh_init(void)
    -{
    - PurpleUi *ui = purple_core_get_ui();
    - const gchar *ui_name = NULL;
    - const gchar *ui_version = NULL;
    -
    - if(PURPLE_IS_UI(ui)) {
    - ui_name = purple_ui_get_name(ui);
    - ui_version = purple_ui_get_version(ui);
    - }
    -
    - if(ui_name) {
    - jabber_bosh_useragent = g_strdup_printf(
    - "%s%s%s (libpurple " VERSION ")", ui_name,
    - ui_version ? " " : "", ui_version ? ui_version : "");
    - } else {
    - jabber_bosh_useragent = g_strdup("libpurple " VERSION);
    - }
    -}
    -
    -void jabber_bosh_uninit(void)
    -{
    - g_clear_pointer(&jabber_bosh_useragent, g_free);
    -}
    -
    -PurpleJabberBOSHConnection*
    -jabber_bosh_connection_new(JabberStream *js, const gchar *url)
    -{
    - PurpleJabberBOSHConnection *conn;
    - PurpleAccount *account;
    - GProxyResolver *resolver;
    - GError *error = NULL;
    - const gchar *scheme;
    -
    - account = purple_connection_get_account(js->gc);
    - resolver = purple_proxy_get_proxy_resolver(account, &error);
    - if (resolver == NULL) {
    - purple_debug_error("jabber-bosh",
    - "Unable to get account proxy resolver: %s",
    - error->message);
    - g_error_free(error);
    - return NULL;
    - }
    -
    - scheme = g_uri_peek_scheme(url);
    - if (scheme == NULL) {
    - purple_debug_error("jabber-bosh", "Unable to parse given BOSH URL: %s",
    - url);
    - g_object_unref(resolver);
    - return NULL;
    - }
    -
    - conn = g_new0(PurpleJabberBOSHConnection, 1);
    - conn->payload_reqs = soup_session_new_with_options(
    - "proxy-resolver", resolver,
    - "timeout", JABBER_BOSH_TIMEOUT + 2,
    - "user-agent", jabber_bosh_useragent,
    - NULL);
    - conn->url = g_strdup(url);
    - conn->js = js;
    - conn->is_ssl = g_str_equal(scheme, "https");
    - conn->send_buff = g_string_new(NULL);
    -
    - /*
    - * Random 64-bit integer masked off by 2^52 - 1.
    - *
    - * This should produce a random integer in the range [0, 2^52). It's
    - * unlikely we'll send enough packets in one session to overflow the
    - * rid.
    - */
    - conn->rid = (((guint64)g_random_int() << 32) | g_random_int());
    - conn->rid &= 0xFFFFFFFFFFFFFLL;
    -
    - g_object_unref(resolver);
    -
    - jabber_bosh_connection_session_create(conn);
    -
    - return conn;
    -}
    -
    -void
    -jabber_bosh_connection_destroy(PurpleJabberBOSHConnection *conn)
    -{
    - if (conn == NULL || conn->is_terminating)
    - return;
    - conn->is_terminating = TRUE;
    -
    - if (conn->sid != NULL) {
    - purple_debug_info("jabber-bosh",
    - "Terminating a session for %p\n", conn);
    - jabber_bosh_connection_send_now(conn);
    - }
    -
    - g_clear_handle_id(&conn->send_timer, g_source_remove);
    -
    - soup_session_abort(conn->payload_reqs);
    -
    - g_clear_object(&conn->payload_reqs);
    - g_string_free(conn->send_buff, TRUE);
    - conn->send_buff = NULL;
    -
    - g_free(conn->sid);
    - conn->sid = NULL;
    - g_free(conn->url);
    - conn->url = NULL;
    -
    - g_free(conn);
    -}
    -
    -gboolean
    -jabber_bosh_connection_is_ssl(const PurpleJabberBOSHConnection *conn)
    -{
    - return conn->is_ssl;
    -}
    -
    -static PurpleXmlNode *
    -jabber_bosh_connection_parse(PurpleJabberBOSHConnection *conn,
    - SoupMessage *msg, GBytes *response_body,
    - GError *error)
    -{
    - gconstpointer body = NULL;
    - gsize length = 0;
    - PurpleXmlNode *root;
    - const gchar *type;
    -
    - g_return_val_if_fail(conn != NULL, NULL);
    - g_return_val_if_fail(msg != NULL, NULL);
    -
    - if (conn->is_terminating || purple_account_is_disconnecting(
    - purple_connection_get_account(conn->js->gc)))
    - {
    - return NULL;
    - }
    -
    - if(error != NULL ||
    - !SOUP_STATUS_IS_SUCCESSFUL(soup_message_get_status(msg))) {
    - gchar *tmp = g_strdup_printf(
    - _("Unable to connect: %s"),
    - error ? error->message : soup_message_get_reason_phrase(msg));
    - purple_connection_error(conn->js->gc,
    - PURPLE_CONNECTION_ERROR_NETWORK_ERROR, tmp);
    - g_free(tmp);
    - return NULL;
    - }
    -
    - body = g_bytes_get_data(response_body, &length);
    - root = purple_xmlnode_from_str(body, length);
    -
    - type = purple_xmlnode_get_attrib(root, "type");
    - if (purple_strequal(type, "terminate")) {
    - purple_connection_error(conn->js->gc,
    - PURPLE_CONNECTION_ERROR_OTHER_ERROR, _("The BOSH "
    - "connection manager terminated your session."));
    - purple_xmlnode_free(root);
    - return NULL;
    - }
    -
    - return root;
    -}
    -
    -static void
    -jabber_bosh_connection_recv(GObject *source, GAsyncResult *result,
    - gpointer data)
    -{
    - SoupMessage *msg = data;
    - PurpleJabberBOSHConnection *bosh_conn = NULL;
    - GBytes *response_body = NULL;
    - GError *error = NULL;
    - PurpleXmlNode *node, *child;
    -
    - response_body = soup_session_send_and_read_finish(SOUP_SESSION(source),
    - result, &error);
    - bosh_conn = g_object_get_data(G_OBJECT(msg), "bosh-connection");
    -
    - if (response_body != NULL && purple_debug_is_verbose() &&
    - purple_debug_is_unsafe())
    - {
    - const gchar *body = NULL;
    - gsize length = 0;
    -
    - body = g_bytes_get_data(response_body, &length);
    - purple_debug_misc("jabber-bosh", "received: %.*s", (int)length, body);
    - }
    -
    - node = jabber_bosh_connection_parse(bosh_conn, msg, response_body, error);
    - g_clear_pointer(&response_body, g_bytes_unref);
    - g_clear_error(&error);
    - g_clear_object(&msg);
    -
    - if (node == NULL) {
    - return;
    - }
    -
    - child = node->child;
    - while (child != NULL) {
    - /* jabber_process_packet might free child */
    - PurpleXmlNode *next = child->next;
    - const gchar *xmlns;
    -
    - if (child->type != PURPLE_XMLNODE_TYPE_TAG) {
    - child = next;
    - continue;
    - }
    -
    - /* Workaround for non-compliant servers that don't stamp
    - * the right xmlns on these packets. See #11315.
    - */
    - xmlns = purple_xmlnode_get_namespace(child);
    - if ((xmlns == NULL || purple_strequal(xmlns, NS_BOSH)) &&
    - (purple_strequal(child->name, "iq") ||
    - purple_strequal(child->name, "message") ||
    - purple_strequal(child->name, "presence")))
    - {
    - purple_xmlnode_set_namespace(child, NS_XMPP_CLIENT);
    - }
    -
    - jabber_process_packet(bosh_conn->js, &child);
    -
    - child = next;
    - }
    -
    - jabber_bosh_connection_send(bosh_conn, NULL);
    -}
    -
    -static void
    -jabber_bosh_connection_send_now(PurpleJabberBOSHConnection *conn)
    -{
    - SoupMessage *req;
    - GString *data;
    -
    - g_return_if_fail(conn != NULL);
    -
    - g_clear_handle_id(&conn->send_timer, g_source_remove);
    -
    - if (conn->sid == NULL)
    - return;
    -
    - data = g_string_new(NULL);
    -
    - /* missing parameters: route, from, ack */
    - g_string_printf(data, "<body "
    - "rid='%" G_GUINT64_FORMAT "' "
    - "sid='%s' "
    - "xmlns='" NS_BOSH "' "
    - "xmlns:xmpp='" NS_XMPP_BOSH "' ",
    - ++conn->rid, conn->sid);
    -
    - if (conn->js->reinit && !conn->is_terminating) {
    - g_string_append(data, "xmpp:restart='true'/>");
    - conn->js->reinit = FALSE;
    - } else {
    - if (conn->is_terminating)
    - g_string_append(data, "type='terminate' ");
    - g_string_append_c(data, '>');
    - g_string_append_len(data, conn->send_buff->str,
    - conn->send_buff->len);
    - g_string_append(data, "</body>");
    - g_string_set_size(conn->send_buff, 0);
    - }
    -
    - if (purple_debug_is_verbose() && purple_debug_is_unsafe())
    - purple_debug_misc("jabber-bosh", "sending: %s\n", data->str);
    -
    - req = jabber_bosh_connection_http_request_new(conn, data);
    -
    - if (conn->is_terminating) {
    - soup_session_send_async(conn->payload_reqs, req, G_PRIORITY_DEFAULT,
    - NULL, NULL, NULL);
    - g_free(conn->sid);
    - conn->sid = NULL;
    - } else {
    - g_object_set_data(G_OBJECT(req), "bosh-connection", conn);
    - soup_session_send_and_read_async(conn->payload_reqs, req,
    - G_PRIORITY_DEFAULT, NULL,
    - jabber_bosh_connection_recv, req);
    - }
    -}
    -
    -static gboolean
    -jabber_bosh_connection_send_delayed(gpointer _conn)
    -{
    - PurpleJabberBOSHConnection *conn = _conn;
    -
    - conn->send_timer = 0;
    - jabber_bosh_connection_send_now(conn);
    -
    - return FALSE;
    -}
    -
    -void
    -jabber_bosh_connection_send(PurpleJabberBOSHConnection *conn,
    - const gchar *data)
    -{
    - g_return_if_fail(conn != NULL);
    -
    - if (data)
    - g_string_append(conn->send_buff, data);
    -
    - if (conn->send_timer == 0) {
    - conn->send_timer = g_timeout_add(
    - JABBER_BOSH_SEND_DELAY,
    - jabber_bosh_connection_send_delayed, conn);
    - }
    -}
    -
    -void
    -jabber_bosh_connection_send_keepalive(PurpleJabberBOSHConnection *conn)
    -{
    - g_return_if_fail(conn != NULL);
    -
    - jabber_bosh_connection_send_now(conn);
    -}
    -
    -static gboolean
    -jabber_bosh_version_check(const gchar *version, int major_req, int minor_min)
    -{
    - const gchar *dot;
    - int major, minor = 0;
    -
    - if (version == NULL)
    - return FALSE;
    -
    - major = atoi(version);
    - dot = strchr(version, '.');
    - if (dot)
    - minor = atoi(dot + 1);
    -
    - if (major != major_req)
    - return FALSE;
    - if (minor < minor_min)
    - return FALSE;
    - return TRUE;
    -}
    -
    -static void
    -jabber_bosh_connection_session_created(GObject *source, GAsyncResult *result,
    - gpointer data)
    -{
    - SoupMessage *msg = data;
    - PurpleJabberBOSHConnection *bosh_conn = NULL;
    - GBytes *response_body = NULL;
    - GError *error = NULL;
    - PurpleXmlNode *node, *features;
    - const gchar *sid, *ver, *inactivity_str;
    - int inactivity = 0;
    -
    - response_body = soup_session_send_and_read_finish(SOUP_SESSION(source),
    - result, &error);
    - bosh_conn = g_object_get_data(G_OBJECT(msg), "bosh-connection");
    -
    - if (response_body != NULL && purple_debug_is_verbose() &&
    - purple_debug_is_unsafe())
    - {
    - const gchar *body = NULL;
    - gsize length = 0;
    -
    - body = g_bytes_get_data(response_body, &length);
    - purple_debug_misc("jabber-bosh", "received (session creation): %.*s",
    - (int)length, body);
    - }
    -
    - node = jabber_bosh_connection_parse(bosh_conn, msg, response_body, error);
    - g_clear_pointer(&response_body, g_bytes_unref);
    - g_clear_error(&error);
    - g_clear_object(&msg);
    -
    - if (node == NULL) {
    - return;
    - }
    -
    - sid = purple_xmlnode_get_attrib(node, "sid");
    - ver = purple_xmlnode_get_attrib(node, "ver");
    - inactivity_str = purple_xmlnode_get_attrib(node, "inactivity");
    - /* requests = purple_xmlnode_get_attrib(node, "requests"); */
    -
    - if (!sid) {
    - purple_connection_error(bosh_conn->js->gc,
    - PURPLE_CONNECTION_ERROR_OTHER_ERROR,
    - _("No BOSH session ID given"));
    - purple_xmlnode_free(node);
    - return;
    - }
    -
    - if (ver == NULL) {
    - purple_debug_info("jabber-bosh", "Missing version in BOSH initiation\n");
    - } else if (!jabber_bosh_version_check(ver, 1, 6)) {
    - purple_debug_error("jabber-bosh",
    - "Unsupported BOSH version: %s\n", ver);
    - purple_connection_error(bosh_conn->js->gc,
    - PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
    - _("Unsupported version of BOSH protocol"));
    - purple_xmlnode_free(node);
    - return;
    - }
    -
    - purple_debug_misc("jabber-bosh", "Session created for %p\n", bosh_conn);
    -
    - bosh_conn->sid = g_strdup(sid);
    -
    - if (inactivity_str)
    - inactivity = atoi(inactivity_str);
    - if (inactivity < 0 || inactivity > 3600) {
    - purple_debug_warning("jabber-bosh", "Ignoring invalid "
    - "inactivity value: %s\n", inactivity_str);
    - inactivity = 0;
    - }
    - if (inactivity > 0) {
    - inactivity -= 5; /* rounding */
    - if (inactivity <= 0)
    - inactivity = 1;
    - bosh_conn->js->max_inactivity = inactivity;
    - if (bosh_conn->js->inactivity_timer == 0) {
    - purple_debug_misc("jabber-bosh", "Starting inactivity "
    - "timer for %d secs (compensating for "
    - "rounding)\n", inactivity);
    - jabber_stream_restart_inactivity_timer(bosh_conn->js);
    - }
    - }
    -
    - jabber_stream_set_state(bosh_conn->js, JABBER_STREAM_AUTHENTICATING);
    -
    - /* FIXME: Depending on receiving features might break with some hosts */
    - features = purple_xmlnode_get_child(node, "features");
    - jabber_stream_features_parse(bosh_conn->js, features);
    -
    - purple_xmlnode_free(node);
    -
    - jabber_bosh_connection_send(bosh_conn, NULL);
    -}
    -
    -static void
    -jabber_bosh_connection_session_create(PurpleJabberBOSHConnection *conn)
    -{
    - SoupMessage *req;
    - GString *data;
    -
    - purple_debug_misc("jabber-bosh", "Requesting Session Create for %p\n",
    - conn);
    -
    - data = g_string_new(NULL);
    -
    - /* missing optional parameters: route, from, ack */
    - g_string_printf(data, "<body content='text/xml; charset=utf-8' "
    - "rid='%" G_GUINT64_FORMAT "' "
    - "to='%s' "
    - "xml:lang='en' "
    - "ver='1.10' "
    - "wait='%d' "
    - "hold='1' "
    - "xmlns='" NS_BOSH "' "
    - "xmpp:version='1.0' "
    - "xmlns:xmpp='urn:xmpp:xbosh' "
    - "/>",
    - ++conn->rid, conn->js->user->domain, JABBER_BOSH_TIMEOUT);
    -
    - req = jabber_bosh_connection_http_request_new(conn, data);
    - g_object_set_data(G_OBJECT(req), "bosh-connection", conn);
    - soup_session_send_and_read_async(conn->payload_reqs, req,
    - G_PRIORITY_DEFAULT, NULL,
    - jabber_bosh_connection_session_created,
    - req);
    -}
    -
    -static SoupMessage *
    -jabber_bosh_connection_http_request_new(PurpleJabberBOSHConnection *conn,
    - GString *data)
    -{
    - SoupMessage *req;
    - GBytes *body = NULL;
    -
    - jabber_stream_restart_inactivity_timer(conn->js);
    -
    - req = soup_message_new("POST", conn->url);
    - body = g_string_free_to_bytes(data);
    - soup_message_set_request_body_from_bytes(req, "text/xml; charset=utf-8",
    - body);
    - g_bytes_unref(body);
    -
    - return req;
    -}
    --- a/libpurple/protocols/jabber/bosh.h Tue Apr 09 23:36:32 2024 -0500
    +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
    @@ -1,55 +0,0 @@
    -/**
    - * @file bosh.h Bidirectional-streams over Synchronous HTTP (BOSH)
    - * (XEP-0124 and XEP-0206)
    - *
    - * purple
    - *
    - * 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
    - */
    -
    -#ifndef PURPLE_JABBER_BOSH_H
    -#define PURPLE_JABBER_BOSH_H
    -
    -typedef struct _PurpleJabberBOSHConnection PurpleJabberBOSHConnection;
    -
    -#include "jabber.h"
    -
    -void
    -jabber_bosh_init(void);
    -
    -void
    -jabber_bosh_uninit(void);
    -
    -PurpleJabberBOSHConnection *
    -jabber_bosh_connection_new(JabberStream *js, const gchar *url);
    -
    -void
    -jabber_bosh_connection_destroy(PurpleJabberBOSHConnection *conn);
    -
    -gboolean
    -jabber_bosh_connection_is_ssl(const PurpleJabberBOSHConnection *conn);
    -
    -void
    -jabber_bosh_connection_send(PurpleJabberBOSHConnection *conn,
    - const gchar *data);
    -
    -void
    -jabber_bosh_connection_send_keepalive(PurpleJabberBOSHConnection *conn);
    -
    -#endif /* PURPLE_JABBER_BOSH_H */
    --- a/libpurple/protocols/jabber/buddy.c Tue Apr 09 23:36:32 2024 -0500
    +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
    @@ -1,1957 +0,0 @@
    -/*
    - * 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;
    -
    -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_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_clear_pointer(&jbr->tz_off, g_time_zone_unref);
    - 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->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(G_GNUC_UNUSED PurpleProtocolServer *protocol_server,
    - PurpleConnection *gc, const char *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(G_GNUC_UNUSED 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, PurpleRequestPage *page) {
    - PurpleXmlNode *vc_node;
    - PurpleProtocol *protocol = NULL;
    - 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;
    -
    - text = purple_request_page_get_string(page, vc_tp->tag);
    -
    - if(!purple_strempty(text)) {
    - 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);
    - protocol = purple_connection_get_protocol(gc);
    - if(PURPLE_PROTOCOL_IMPLEMENTS(protocol, SERVER, set_info)) {
    - purple_protocol_server_set_info(PURPLE_PROTOCOL_SERVER(protocol),
    - gc, p);
    - }
    -
    - g_free(p);
    -}
    -
    -/*
    - * This gets executed as the protocol 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(G_GNUC_UNUSED GSimpleAction *action, GVariant *parameter,
    - G_GNUC_UNUSED gpointer data)
    -{
    - const char *account_id = NULL;
    - PurpleAccountManager *manager = NULL;
    - PurpleAccount *account = NULL;
    - PurpleConnection *connection = NULL;
    - PurpleRequestPage *page;
    - PurpleRequestGroup *group;
    - PurpleRequestField *field;
    - const struct vcard_template *vc_tp;
    - const char *user_info;
    - char *cdata = NULL;
    - PurpleXmlNode *x_vc_data = NULL;
    -
    - if(!g_variant_is_of_type(parameter, G_VARIANT_TYPE_STRING)) {
    - g_critical("XMPP Set Info action parameter is of incorrect type %s",
    - g_variant_get_type_string(parameter));
    - }
    -
    - account_id = g_variant_get_string(parameter, NULL);
    - manager = purple_account_manager_get_default();
    - account = purple_account_manager_find_by_id(manager, account_id);
    - connection = purple_account_get_connection(account);
    -
    - page = purple_request_page_new();
    - group = purple_request_group_new(NULL);
    - purple_request_page_add_group(page, group);
    -
    - /*
    - * Get existing, XML-formatted, user info
    - */
    - if((user_info = purple_account_get_user_info(account)) != 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_group_add_field(group, field);
    - }
    -
    - if(x_vc_data != NULL)
    - purple_xmlnode_free(x_vc_data);
    -
    - purple_request_fields(connection, _("Edit XMPP vCard"),
    - _("Edit XMPP vCard"),
    - _("All items below are optional. Enter only the "
    - "information with which you feel comfortable."),
    - page,
    - _("Save"), G_CALLBACK(jabber_format_info),
    - _("Cancel"), NULL,
    - purple_request_cpar_from_connection(connection),
    - connection);
    -
    - g_clear_object(&account);
    -}
    -
    -/*---------------------------------------*/
    -/* 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() */
    - g_clear_handle_id(&jbi->timeout_handle, g_source_remove);
    -
    - 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 != NULL) {
    - GDateTime *dt = NULL;
    - char *timestamp = NULL;
    -
    - dt = g_date_time_new_now(jbr->tz_off);
    -
    - 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 = g_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, G_GNUC_UNUSED const char *from,
    - JabberIqType type, G_GNUC_UNUSED const char *id,
    - PurpleXmlNode *packet, G_GNUC_UNUSED 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);
    -
    - if(from == NULL) {
    - from = purple_contact_info_get_username(PURPLE_CONTACT_INFO(account));
    - }
    - bare_jid = jabber_get_bare_jid(from);
    -
    - /* 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(G_GNUC_UNUSED 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(G_GNUC_UNUSED JabberStream *js,
    - G_GNUC_UNUSED 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(G_GNUC_UNUSED 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 = g_time_zone_new_offset(0);
    - } else {
    - gboolean offset_positive = (tzo_data[0] == '+');
    - /* [+-]HH:MM */
    - if (((*c == '+' || *c == '-') && (c = c + 1)) &&
    - sscanf(c, "%02d:%02d", &hours, &minutes) == 2)
    - {
    - gint32 tz_off = 60*60*hours + 60*minutes;
    - if (!offset_positive) {
    - tz_off *= -1;
    - }
    -
    - jbr->tz_off = g_time_zone_new_offset(tz_off);
    -
    - } 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_clear_slist(&js->pending_buddy_info_requests,
    - (GDestroyNotify)jabber_buddy_info_destroy);
    -}
    -
    -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 == NULL &&
    - (jbr->caps == NULL ||
    - 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(G_GNUC_UNUSED 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, G_GNUC_UNUSED 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, G_GNUC_UNUSED 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,
    - G_GNUC_UNUSED 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, G_GNUC_UNUSED 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, G_GNUC_UNUSED 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, G_GNUC_UNUSED 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, G_GNUC_UNUSED 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"),
    - G_CALLBACK(jabber_buddy_make_visible),
    - NULL, NULL);
    - } else {
    - act = purple_action_menu_new(_("Temporarily Hide From"),
    - G_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"),
    - G_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"),
    - G_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"),
    - G_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"),
    - G_CALLBACK(jabber_buddy_login),
    - NULL, NULL);
    - m = g_list_append(m, act);
    - act = purple_action_menu_new(_("Log Out"),
    - G_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, G_CALLBACK(jabber_adhoc_execute_action), cmd, NULL);
    - m = g_list_append(m, act);
    - }
    - }
    -
    - return m;
    -}
    -
    -GList *
    -jabber_blist_node_menu(G_GNUC_UNUSED PurpleProtocolClient *client,
    - PurpleBlistNode *node)
    -{
    - if(PURPLE_IS_BUDDY(node)) {
    - return jabber_buddy_menu((PurpleBuddy *) node);
    - } else {
    - return NULL;
    - }
    -}
    -
    -gboolean
    -jabber_resource_know_capabilities(const JabberBuddyResource *jbr)
    -{
    - return jbr->caps != NULL;
    -}
    -
    -gboolean
    -jabber_resource_has_capability(const JabberBuddyResource *jbr, const gchar *cap)
    -{
    - const GList *node = NULL;
    -
    - if (jbr->caps == NULL) {
    - purple_debug_info("jabber",
    - "Unable to find caps: nothing known about buddy\n");
    - return FALSE;
    - }
    -
    - node = g_list_find_custom(jbr->caps->features, cap, (GCompareFunc)strcmp);
    - return (node != NULL);
    -}
    -
    -const gchar *
    -jabber_resource_get_identity_category_type(const JabberBuddyResource *jbr,
    - const gchar *category)
    -{
    - const GList *iter = NULL;
    -
    - if (jbr->caps != NULL) {
    - for (iter = jbr->caps->identities ; iter ; iter = g_list_next(iter)) {
    - const JabberIdentity *identity =
    - (JabberIdentity *) iter->data;
    -
    - if (purple_strequal(identity->category, category)) {
    - return identity->type;
    - }
    - }
    - }
    -
    - return NULL;
    -}
    --- a/libpurple/protocols/jabber/buddy.h Tue Apr 09 23:36:32 2024 -0500
    +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
    @@ -1,114 +0,0 @@
    -/**
    - * @file buddy.h Buddy handlers
    - *
    - * purple
    - *
    - * 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
    - */
    -
    -#ifndef PURPLE_JABBER_BUDDY_H
    -#define PURPLE_JABBER_BUDDY_H
    -
    -typedef struct _JabberBuddy JabberBuddy;
    -
    -#include <purple.h>
    -
    -#include "jabber.h"
    -#include "caps.h"
    -#include "jutil.h"
    -
    -struct _JabberBuddy {
    - /**
    - * A sorted list of resources in priority descending order.
    - * This means that the first resource in the list is the
    - * "most available" (see resource_compare_cb in buddy.c for
    - * details). Don't play with this yourself, let
    - * jabber_buddy_track_resource and jabber_buddy_remove_resource do it.
    - */
    - GList *resources;
    - char *error_msg;
    - enum {
    - JABBER_INVISIBLE_NONE = 0,
    - JABBER_INVISIBLE_SERVER = 1 << 1,
    - JABBER_INVIS_BUDDY = 1 << 2
    - } invisible;
    - enum {
    - JABBER_SUB_NONE = 0,
    - JABBER_SUB_PENDING = 1 << 1,
    - JABBER_SUB_TO = 1 << 2,
    - JABBER_SUB_FROM = 1 << 3,
    - JABBER_SUB_BOTH = (JABBER_SUB_TO | JABBER_SUB_FROM),
    - JABBER_SUB_REMOVE = 1 << 4
    - } subscription;
    -};
    -
    -typedef struct {
    - JabberBuddy *jb;
    - char *name;
    - int priority;
    - JabberBuddyState state;
    - char *status;
    - time_t idle;
    - JabberCapabilities capabilities;
    - char *thread_id;
    - enum {
    - JABBER_CHAT_STATES_UNKNOWN,
    - JABBER_CHAT_STATES_UNSUPPORTED,
    - JABBER_CHAT_STATES_SUPPORTED
    - } chat_states;
    - struct {
    - char *version;
    - char *name;
    - char *os;
    - } client;
    - GTimeZone *tz_off;
    - JabberCapsClientInfo *caps;
    - GList *commands;
    - gboolean commands_fetched;
    -} JabberBuddyResource;
    -
    -void jabber_buddy_free(JabberBuddy *jb);
    -JabberBuddy *jabber_buddy_find(JabberStream *js, const char *name,
    - gboolean create);
    -JabberBuddyResource *jabber_buddy_find_resource(JabberBuddy *jb,
    - const char *resource);
    -JabberBuddyResource *jabber_buddy_track_resource(JabberBuddy *jb, const char *resource,
    - int priority, JabberBuddyState state, const char *status);
    -void jabber_buddy_remove_resource(JabberBuddy *jb, const char *resource);
    -void jabber_buddy_get_info(PurpleProtocolServer *protocol_server, PurpleConnection *gc, const char *who);
    -
    -GList *jabber_blist_node_menu(PurpleProtocolClient *client, PurpleBlistNode *node);
    -
    -void jabber_set_info(PurpleProtocolServer *protocol_server, PurpleConnection *gc, const char *info);
    -void jabber_setup_set_info(GSimpleAction *action, GVariant *parameter, gpointer data);
    -void jabber_set_buddy_icon(PurpleProtocolServer *protocol_server, PurpleConnection *gc, PurpleImage *img);
    -
    -void jabber_buddy_remove_all_pending_buddy_info_requests(JabberStream *js);
    -
    -void jabber_vcard_fetch_mine(JabberStream *js);
    -
    -gboolean jabber_resource_know_capabilities(const JabberBuddyResource *jbr);
    -gboolean jabber_resource_has_capability(const JabberBuddyResource *jbr,
    - const gchar *cap);
    -
    -const gchar *
    -jabber_resource_get_identity_category_type(const JabberBuddyResource *jbr,
    - const gchar *category);
    -
    -#endif /* PURPLE_JABBER_BUDDY_H */
    --- a/libpurple/protocols/jabber/caps.c Tue Apr 09 23:36:32 2024 -0500
    +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
    @@ -1,661 +0,0 @@
    -/*
    - * 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 "caps.h"
    -#include "iq.h"
    -#include "presence.h"
    -#include "xdata.h"
    -
    -#define JABBER_CAPS_FILENAME "xmpp-caps.xml"
    -
    -typedef struct {
    - gchar *var;
    - GList *values;
    -} JabberDataFormField;
    -
    -static GHashTable *capstable = NULL; /* JabberCapsTuple -> JabberCapsClientInfo */
    -static guint save_timer = 0;
    -
    -static guint jabber_caps_hash(gconstpointer data) {
    - const JabberCapsTuple *key = data;
    - guint nodehash = g_str_hash(key->node);
    - guint verhash = g_str_hash(key->ver);
    - guint hashhash = g_str_hash(key->hash);
    - return nodehash ^ verhash ^ hashhash;
    -}
    -
    -static gboolean jabber_caps_compare(gconstpointer v1, gconstpointer v2) {
    - const JabberCapsTuple *name1 = v1;
    - const JabberCapsTuple *name2 = v2;
    -
    - return purple_strequal(name1->node, name2->node) &&
    - purple_strequal(name1->ver, name2->ver) &&
    - purple_strequal(name1->hash, name2->hash);
    -}
    -
    -void
    -jabber_caps_client_info_destroy(JabberCapsClientInfo *info)
    -{
    - if (info == NULL)
    - return;
    -
    - g_list_free_full(info->identities, (GDestroyNotify)jabber_identity_free);
    -
    - g_list_free_full(info->features, g_free);
    -
    - g_list_free_full(info->forms, (GDestroyNotify)purple_xmlnode_free);
    -
    - g_free((char *)info->tuple.node);
    - g_free((char *)info->tuple.ver);
    - g_free((char *)info->tuple.hash);
    -
    - g_free(info);
    -}
    -
    -static void jabber_caps_store_client(gpointer key, gpointer value, gpointer user_data) {
    - const JabberCapsTuple *tuple = key;
    - const JabberCapsClientInfo *props = value;
    - PurpleXmlNode *root = user_data;
    - PurpleXmlNode *client = purple_xmlnode_new_child(root, "client");
    - GList *iter;
    -
    - purple_xmlnode_set_attrib(client, "node", tuple->node);
    - purple_xmlnode_set_attrib(client, "ver", tuple->ver);
    - purple_xmlnode_set_attrib(client, "hash", tuple->hash);
    - for(iter = props->identities; iter; iter = g_list_next(iter)) {
    - JabberIdentity *id = iter->data;
    - PurpleXmlNode *identity = purple_xmlnode_new_child(client, "identity");
    - purple_xmlnode_set_attrib(identity, "category", id->category);
    - purple_xmlnode_set_attrib(identity, "type", id->type);
    - if (id->name)
    - purple_xmlnode_set_attrib(identity, "name", id->name);
    - if (id->lang)
    - purple_xmlnode_set_attrib(identity, "lang", id->lang);
    - }
    -
    - for(iter = props->features; iter; iter = g_list_next(iter)) {
    - const char *feat = iter->data;
    - PurpleXmlNode *feature = purple_xmlnode_new_child(client, "feature");
    - purple_xmlnode_set_attrib(feature, "var", feat);
    - }
    -
    - for(iter = props->forms; iter; iter = g_list_next(iter)) {
    - /* FIXME: See #7814 */
    - PurpleXmlNode *xdata = iter->data;
    - purple_xmlnode_insert_child(client, purple_xmlnode_copy(xdata));
    - }
    -}
    -
    -static gboolean
    -do_jabber_caps_store(G_GNUC_UNUSED gpointer data)
    -{
    - char *str;
    - int length = 0;
    - PurpleXmlNode *root = purple_xmlnode_new("capabilities");
    -
    - g_hash_table_foreach(capstable, jabber_caps_store_client, root);
    - str = purple_xmlnode_to_formatted_str(root, &length);
    - purple_xmlnode_free(root);
    - purple_util_write_data_to_cache_file(JABBER_CAPS_FILENAME, str, length);
    - g_free(str);
    -
    - save_timer = 0;
    - return FALSE;
    -}
    -
    -static void
    -schedule_caps_save(void)
    -{
    - if (save_timer == 0)
    - save_timer = g_timeout_add_seconds(5, do_jabber_caps_store, NULL);
    -}
    -
    -static void
    -jabber_caps_load(void)
    -{
    - PurpleXmlNode *capsdata = purple_util_read_xml_from_cache_file(JABBER_CAPS_FILENAME, "XMPP capabilities cache");
    - PurpleXmlNode *client;
    -
    - if(!capsdata)
    - return;
    -
    - if (!purple_strequal(capsdata->name, "capabilities")) {
    - purple_xmlnode_free(capsdata);
    - return;
    - }
    -
    - for (client = capsdata->child; client; client = client->next) {
    - if (client->type != PURPLE_XMLNODE_TYPE_TAG)
    - continue;
    - if (purple_strequal(client->name, "client")) {
    - JabberCapsClientInfo *value = g_new0(JabberCapsClientInfo, 1);
    - JabberCapsTuple *key = (JabberCapsTuple*)&value->tuple;
    - PurpleXmlNode *child;
    - key->node = g_strdup(purple_xmlnode_get_attrib(client,"node"));
    - key->ver = g_strdup(purple_xmlnode_get_attrib(client,"ver"));
    - key->hash = g_strdup(purple_xmlnode_get_attrib(client,"hash"));
    -
    - for (child = client->child; child; child = child->next) {
    - if (child->type != PURPLE_XMLNODE_TYPE_TAG)
    - continue;
    - if (purple_strequal(child->name, "feature")) {
    - const char *var = purple_xmlnode_get_attrib(child, "var");
    - if(!var)
    - continue;
    - value->features = g_list_append(value->features,g_strdup(var));
    - } else if (purple_strequal(child->name, "identity")) {
    - const char *category = purple_xmlnode_get_attrib(child, "category");
    - const char *type = purple_xmlnode_get_attrib(child, "type");
    - const char *name = purple_xmlnode_get_attrib(child, "name");
    - const char *lang = purple_xmlnode_get_attrib(child, "lang");
    - JabberIdentity *id;
    -
    - if (!category || !type)
    - continue;
    -
    - id = jabber_identity_new(category, type, lang, name);
    - value->identities = g_list_append(value->identities,id);
    - } else if (purple_strequal(child->name, "x")) {
    - /* TODO: See #7814 -- this might cause problems if anyone
    - * ever actually specifies forms. In fact, for this to
    - * work properly, that bug needs to be fixed in
    - * purple_xmlnode_from_str, not the output version... */
    - value->forms = g_list_append(value->forms, purple_xmlnode_copy(child));
    - }
    - }
    -
    - g_hash_table_replace(capstable, key, value);
    - }
    - }
    - purple_xmlnode_free(capsdata);
    -}
    -
    -void jabber_caps_init(void)
    -{
    - capstable = g_hash_table_new_full(jabber_caps_hash, jabber_caps_compare, NULL, (GDestroyNotify)jabber_caps_client_info_destroy);
    - jabber_caps_load();
    -}
    -
    -void jabber_caps_uninit(void)
    -{
    - if(save_timer != 0) {
    - g_clear_handle_id(&save_timer, g_source_remove);
    - do_jabber_caps_store(NULL);
    - }
    - g_clear_pointer(&capstable, g_hash_table_destroy);
    -}
    -
    -typedef struct {
    - jabber_caps_get_info_cb cb;
    - gpointer cb_data;
    -
    - char *who;
    - char *node;
    - char *ver;
    - char *hash;
    -
    - JabberCapsClientInfo *info;
    -} jabber_caps_cbplususerdata;
    -
    -static void
    -cbplususerdata_destroy(jabber_caps_cbplususerdata *data)
    -{
    - if (data == NULL)
    - return;
    -
    - g_free(data->who);
    - g_free(data->node);
    - g_free(data->ver);
    - g_free(data->hash);
    -
    - /* If we have info here, it's already in the capstable, so don't free it */
    -
    - g_free(data);
    -}
    -
    -static void
    -jabber_caps_get_info_complete(jabber_caps_cbplususerdata *userdata)
    -{
    - if (userdata->cb) {
    - userdata->cb(userdata->info, userdata->cb_data);
    - userdata->info = NULL;
    - }
    -}
    -
    -static void
    -jabber_caps_client_iqcb(G_GNUC_UNUSED JabberStream *js,
    - G_GNUC_UNUSED const char *from, JabberIqType type,
    - G_GNUC_UNUSED const char *id, PurpleXmlNode *packet,
    - gpointer data)
    -{
    - jabber_caps_cbplususerdata *userdata = data;
    - PurpleXmlNode *query = NULL;
    - JabberCapsClientInfo *info = NULL, *value;
    - JabberCapsTuple key;
    - gchar *hash = NULL;
    - GChecksumType hash_type;
    - gboolean supported_hash = TRUE;
    -
    - query = purple_xmlnode_get_child_with_namespace(packet, "query",
    - NS_DISCO_INFO);
    - if(query == NULL || type == JABBER_IQ_ERROR) {
    - userdata->cb(NULL, userdata->cb_data);
    - cbplususerdata_destroy(userdata);
    - return;
    - }
    -
    - /* check hash */
    - info = jabber_caps_parse_client_info(query);
    -
    - if(purple_strequal(userdata->hash, "sha-1")) {
    - hash_type = G_CHECKSUM_SHA1;
    - } else if(purple_strequal(userdata->hash, "md5")) {
    - hash_type = G_CHECKSUM_MD5;
    - } else {
    - supported_hash = FALSE;
    - }
    -
    - if (supported_hash) {
    - hash = jabber_caps_calculate_hash(info, hash_type);
    - }
    -
    - if (hash == NULL || !purple_strequal(hash, userdata->ver)) {
    - purple_debug_warning("jabber",
    - "Could not validate caps info from %s. "
    - "Expected %s, got %s",
    - purple_xmlnode_get_attrib(packet, "from"),
    - userdata->ver, hash ? hash : "(null)");
    -
    - userdata->cb(NULL, userdata->cb_data);
    - jabber_caps_client_info_destroy(info);
    - cbplususerdata_destroy(userdata);
    - g_free(hash);
    - return;
    - }
    -
    - g_free(hash);
    -
    - key.node = userdata->node;
    - key.ver = userdata->ver;
    - key.hash = userdata->hash;
    -
    - /* Use the copy of this data already in the table if it exists or insert
    - * a new one if we need to */
    - if ((value = g_hash_table_lookup(capstable, &key))) {
    - jabber_caps_client_info_destroy(info);
    - info = value;
    - } else {
    - JabberCapsTuple *n_key = NULL;
    -
    - if (G_UNLIKELY(info == NULL)) {
    - g_warn_if_reached();
    - return;
    - }
    -
    - n_key = (JabberCapsTuple *)&info->tuple;
    - n_key->node = userdata->node;
    - n_key->ver = userdata->ver;
    - n_key->hash = userdata->hash;
    - userdata->node = userdata->ver = userdata->hash = NULL;
    -
    - /* The capstable gets a reference */
    - g_hash_table_insert(capstable, n_key, info);
    - schedule_caps_save();
    - }
    -
    - userdata->info = info;
    -
    - jabber_caps_get_info_complete(userdata);
    -
    - cbplususerdata_destroy(userdata);
    -}
    -
    -void
    -jabber_caps_get_info(JabberStream *js, const char *who, const char *node,
    - const char *ver, const char *hash,
    - jabber_caps_get_info_cb cb, gpointer user_data)
    -{
    - JabberCapsClientInfo *info = NULL;
    - JabberCapsTuple key;
    - jabber_caps_cbplususerdata *userdata = NULL;
    - JabberIq *iq = NULL;
    - PurpleXmlNode *query = NULL;
    - char *nodever = NULL;
    -
    - /* Using this in a read-only fashion, so the cast is OK */
    - key.node = (char *)node;
    - key.ver = (char *)ver;
    - key.hash = (char *)hash;
    -
    - info = g_hash_table_lookup(capstable, &key);
    - if (info != NULL) {
    - /* We already have all the information we care about */
    - if (cb) {
    - cb(info, user_data);
    - }
    - return;
    - }
    -
    - userdata = g_new0(jabber_caps_cbplususerdata, 1);
    - /* We start out with 0 references. Every query takes one */
    - userdata->cb = cb;
    - userdata->cb_data = user_data;
    - userdata->who = g_strdup(who);
    - userdata->node = g_strdup(node);
    - userdata->ver = g_strdup(ver);
    - userdata->hash = g_strdup(hash);
    -
    - /* If we don't have the basic information about the client, we need to
    - * fetch it. */
    - iq = jabber_iq_new_query(js, JABBER_IQ_GET, NS_DISCO_INFO);
    - query = purple_xmlnode_get_child_with_namespace(iq->node, "query",
    - NS_DISCO_INFO);
    - nodever = g_strdup_printf("%s#%s", node, ver);
    - purple_xmlnode_set_attrib(query, "node", nodever);
    - g_free(nodever);
    - purple_xmlnode_set_attrib(iq->node, "to", who);
    -
    - jabber_iq_set_callback(iq, jabber_caps_client_iqcb, userdata);
    - jabber_iq_send(iq);
    -}
    -
    -static gint
    -jabber_xdata_compare(gconstpointer a, gconstpointer b)
    -{
    - const PurpleXmlNode *aformtypefield = a;
    - const PurpleXmlNode *bformtypefield = b;
    - char *aformtype;
    - char *bformtype;
    - int result;
    -
    - aformtype = jabber_x_data_get_formtype(aformtypefield);
    - bformtype = jabber_x_data_get_formtype(bformtypefield);
    -
    - result = strcmp(aformtype, bformtype);
    - g_free(aformtype);
    - g_free(bformtype);
    - return result;
    -}
    -
    -JabberCapsClientInfo *jabber_caps_parse_client_info(PurpleXmlNode *query)
    -{
    - PurpleXmlNode *child;
    - JabberCapsClientInfo *info;
    -
    - if (!query || !purple_strequal(query->name, "query") ||
    - !purple_strequal(query->xmlns, NS_DISCO_INFO))
    - return NULL;
    -
    - info = g_new0(JabberCapsClientInfo, 1);
    -
    - for(child = query->child; child; child = child->next) {
    - if (child->type != PURPLE_XMLNODE_TYPE_TAG)
    - continue;
    - if (purple_strequal(child->name, "identity")) {
    - /* parse identity */
    - const char *category = purple_xmlnode_get_attrib(child, "category");
    - const char *type = purple_xmlnode_get_attrib(child, "type");
    - const char *name = purple_xmlnode_get_attrib(child, "name");
    - const char *lang = purple_xmlnode_get_attrib(child, "lang");
    - JabberIdentity *id;
    -
    - if (!category || !type)
    - continue;
    -
    - id = jabber_identity_new(category, type, lang, name);
    - info->identities = g_list_append(info->identities, id);
    - } else if (purple_strequal(child->name, "feature")) {
    - /* parse feature */
    - const char *var = purple_xmlnode_get_attrib(child, "var");
    - if (var)
    - info->features = g_list_prepend(info->features, g_strdup(var));
    - } else if (purple_strequal(child->name, "x")) {
    - if (purple_strequal(child->xmlns, "jabber:x:data")) {
    - /* x-data form */
    - PurpleXmlNode *dataform = purple_xmlnode_copy(child);
    - info->forms = g_list_append(info->forms, dataform);
    - }
    - }
    - }
    - return info;
    -}
    -
    -static gint jabber_caps_xdata_field_compare(gconstpointer a, gconstpointer b)
    -{
    - const JabberDataFormField *ac = a;
    - const JabberDataFormField *bc = b;
    -
    - return strcmp(ac->var, bc->var);
    -}
    -
    -static GList* jabber_caps_xdata_get_fields(const PurpleXmlNode *x)
    -{
    - GList *fields = NULL;
    - PurpleXmlNode *field;
    -
    - if (!x)
    - return NULL;
    -
    - for (field = purple_xmlnode_get_child(x, "field"); field; field = purple_xmlnode_get_next_twin(field)) {
    - PurpleXmlNode *value;
    - JabberDataFormField *xdatafield = g_new0(JabberDataFormField, 1);
    - xdatafield->var = g_strdup(purple_xmlnode_get_attrib(field, "var"));
    -
    - for (value = purple_xmlnode_get_child(field, "value"); value; value = purple_xmlnode_get_next_twin(value)) {
    - gchar *val = purple_xmlnode_get_data(value);
    - xdatafield->values = g_list_prepend(xdatafield->values, val);
    - }
    -
    - xdatafield->values = g_list_sort(xdatafield->values, (GCompareFunc)strcmp);
    - fields = g_list_prepend(fields, xdatafield);
    - }
    -
    - fields = g_list_sort(fields, jabber_caps_xdata_field_compare);
    - return fields;
    -}
    -
    -static void
    -append_escaped_string(GChecksum *hash, const gchar *str)
    -{
    - g_return_if_fail(hash != NULL);
    -
    - if (str && *str) {
    - char *tmp = g_markup_escape_text(str, -1);
    - g_checksum_update(hash, (const guchar *)tmp, -1);
    - g_free(tmp);
    - }
    -
    - g_checksum_update(hash, (const guchar *)"<", -1);
    -}
    -
    -gchar *jabber_caps_calculate_hash(JabberCapsClientInfo *info,
    - GChecksumType hash_type)
    -{
    - GChecksum *hash;
    - GList *node;
    - guint8 *checksum;
    - gsize checksum_size;
    - gchar *ret;
    -
    - if (!info)
    - return NULL;
    -
    - /* sort identities, features and x-data forms */
    - info->identities = g_list_sort(info->identities, jabber_identity_compare);
    - info->features = g_list_sort(info->features, (GCompareFunc)strcmp);
    - info->forms = g_list_sort(info->forms, jabber_xdata_compare);
    -
    - hash = g_checksum_new(hash_type);
    -
    - if (hash == NULL) {
    - return NULL;
    - }
    -
    - /* Add identities to the hash data */
    - for (node = info->identities; node; node = node->next) {
    - JabberIdentity *id = (JabberIdentity*)node->data;
    - char *category = g_markup_escape_text(id->category, -1);
    - char *type = g_markup_escape_text(id->type, -1);
    - char *lang = NULL;
    - char *name = NULL;
    - char *tmp;
    -
    - if (id->lang)
    - lang = g_markup_escape_text(id->lang, -1);
    - if (id->name)
    - name = g_markup_escape_text(id->name, -1);
    -
    - tmp = g_strconcat(category, "/", type, "/", lang ? lang : "",
    - "/", name ? name : "", "<", NULL);
    -
    - g_checksum_update(hash, (const guchar *)tmp, -1);
    -
    - g_free(tmp);
    - g_free(category);
    - g_free(type);
    - g_free(lang);
    - g_free(name);
    - }
    -
    - /* concat features to the verification string */
    - for (node = info->features; node; node = node->next) {
    - append_escaped_string(hash, node->data);
    - }
    -
    - /* concat x-data forms to the verification string */
    - for(node = info->forms; node; node = node->next) {
    - PurpleXmlNode *data = (PurpleXmlNode *)node->data;
    - gchar *formtype = jabber_x_data_get_formtype(data);
    - GList *fields = jabber_caps_xdata_get_fields(data);
    -
    - /* append FORM_TYPE's field value to the verification string */
    - append_escaped_string(hash, formtype);
    - g_free(formtype);
    -
    - while (fields) {
    - JabberDataFormField *field = (JabberDataFormField*)fields->data;
    -
    - if (!purple_strequal(field->var, "FORM_TYPE")) {
    - /* Append the "var" attribute */
    - append_escaped_string(hash, field->var);
    - /* Append <value/> elements' cdata */
    - while (field->values) {
    - append_escaped_string(hash, field->values->data);
    - g_free(field->values->data);
    - field->values = g_list_delete_link(field->values,
    - field->values);
    - }
    - } else {
    - g_list_free_full(field->values, g_free);
    - }
    -
    - g_free(field->var);
    - g_free(field);
    -
    - fields = g_list_delete_link(fields, fields);
    - }
    - }
    -
    - checksum_size = g_checksum_type_get_length(hash_type);
    - checksum = g_new(guint8, checksum_size);
    -
    - /* generate hash */
    - g_checksum_get_digest(hash, checksum, &checksum_size);
    -
    - ret = g_base64_encode(checksum, checksum_size);
    - g_free(checksum);
    - g_checksum_free(hash);
    -
    - return ret;
    -}
    -
    -void jabber_caps_calculate_own_hash(JabberStream *js) {
    - JabberCapsClientInfo info;
    - GList *iter = NULL;
    - GList *features = NULL;
    -
    - if (!jabber_identities && !jabber_features) {
    - /* This really shouldn't ever happen */
    - purple_debug_warning("jabber", "No features or identities, cannot calculate own caps hash.\n");
    - g_free(js->caps_hash);
    - js->caps_hash = NULL;
    - return;
    - }
    -
    - /* build the currently-supported list of features */
    - if (jabber_features) {
    - for (iter = jabber_features; iter; iter = iter->next) {
    - JabberFeature *feat = iter->data;
    - if(!feat->is_enabled || feat->is_enabled(js, feat->namespace)) {
    - features = g_list_append(features, feat->namespace);
    - }
    - }
    - }
    -
    - info.features = features;
    - /* TODO: This copy can go away, I think, since jabber_identities
    - * is pre-sorted, so the sort in calculate_hash should be idempotent.
    - * However, I want to test that. --darkrain
    - */
    - info.identities = g_list_copy(jabber_identities);
    - info.forms = NULL;
    -
    - g_free(js->caps_hash);
    - js->caps_hash = jabber_caps_calculate_hash(&info, G_CHECKSUM_SHA1);
    - g_list_free(info.identities);
    - g_list_free(info.features);
    -}
    -
    -const gchar* jabber_caps_get_own_hash(JabberStream *js)
    -{
    - if (!js->caps_hash)
    - jabber_caps_calculate_own_hash(js);
    -
    - return js->caps_hash;
    -}
    -
    -void
    -jabber_caps_broadcast_change(void)
    -{
    - PurpleAccountManager *manager = NULL;
    - GList *node, *accounts;
    -
    - manager = purple_account_manager_get_default();
    - accounts = purple_account_manager_get_enabled(manager);
    -
    - for (node = accounts; node; node = node->next) {
    - PurpleAccount *account = node->data;
    - const char *protocol_id = purple_account_get_protocol_id(account);
    - if (purple_strequal("prpl-jabber", protocol_id) && purple_account_is_connected(account)) {
    - PurpleConnection *gc = purple_account_get_connection(account);
    - jabber_presence_send(purple_connection_get_protocol_data(gc), TRUE);
    - }
    - }
    -
    - g_list_free(accounts);
    -}
    -
    --- a/libpurple/protocols/jabber/caps.h Tue Apr 09 23:36:32 2024 -0500
    +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
    @@ -1,113 +0,0 @@
    -/*
    - * 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
    - *
    - */
    -
    -#ifndef PURPLE_JABBER_CAPS_H
    -#define PURPLE_JABBER_CAPS_H
    -
    -typedef struct _JabberCapsClientInfo JabberCapsClientInfo;
    -
    -#include "jabber.h"
    -
    -/* Implementation of XEP-0115 - Entity Capabilities */
    -
    -typedef struct {
    - const char *node;
    - const char *ver;
    - const char *hash;
    -} JabberCapsTuple;
    -
    -struct _JabberCapsClientInfo {
    - GList *identities; /* JabberIdentity */
    - GList *features; /* char * */
    - GList *forms; /* PurpleXmlNode * */
    -
    - const JabberCapsTuple tuple;
    -};
    -
    -typedef void (*jabber_caps_get_info_cb)(JabberCapsClientInfo *info, gpointer user_data);
    -
    -void jabber_caps_init(void);
    -void jabber_caps_uninit(void);
    -
    -/**
    - * Main entity capabilities function to get the capabilities of a contact.
    - *
    - * The callback will be called synchronously if we already have the
    - * capabilities for the specified (node,ver,hash).
    - */
    -void jabber_caps_get_info(JabberStream *js, const char *who, const char *node,
    - const char *ver, const char *hash,
    - jabber_caps_get_info_cb cb,
    - gpointer user_data);
    -
    -/**
    - * Takes a JabberCapsClientInfo pointer and returns the caps hash according to
    - * XEP-0115 Version 1.5.
    - *
    - * @param info A JabberCapsClientInfo pointer.
    - * @param hash_type GChecksumType to be used. Either sha-1 or md5.
    - * @return The base64 encoded SHA-1 hash; must be freed by caller
    - */
    -PURPLE_XMPP_EXTERN_FOR_TESTS
    -gchar *jabber_caps_calculate_hash(JabberCapsClientInfo *info,
    - GChecksumType hash_type);
    -
    -/**
    - * Calculate SHA1 hash for own featureset.
    - */
    -void jabber_caps_calculate_own_hash(JabberStream *js);
    -
    -/** Get the current caps hash.
    - * @ret hash
    -**/
    -const gchar* jabber_caps_get_own_hash(JabberStream *js);
    -
    -/**
    - * Broadcast a new calculated hash using a <presence> stanza.
    - */
    -void jabber_caps_broadcast_change(void);
    -
    -/**
    - * Parse the <query/> element from an IQ stanza into a JabberCapsClientInfo
    - * struct.
    - *
    - * Exposed for tests
    - *
    - * @param query The 'query' element from an IQ reply stanza.
    - * @returns A JabberCapsClientInfo struct, or NULL on error
    - */
    -PURPLE_XMPP_EXTERN_FOR_TESTS
    -JabberCapsClientInfo *jabber_caps_parse_client_info(PurpleXmlNode *query);
    -
    -/**
    - * Release memory of a JabberCapsClientInfo struct
    - * returned by jabber_caps_parse_client_info.
    - *
    - * Exposed for tests
    - *
    - * @param info The info object to free.
    - */
    -PURPLE_XMPP_EXTERN_FOR_TESTS
    -void jabber_caps_client_info_destroy(JabberCapsClientInfo *info);
    -
    -#endif /* PURPLE_JABBER_CAPS_H */
    --- a/libpurple/protocols/jabber/chat.c Tue Apr 09 23:36:32 2024 -0500
    +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
    @@ -1,1225 +0,0 @@
    -/*
    - * 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 "chat.h"
    -#include "iq.h"
    -#include "message.h"
    -#include "presence.h"
    -#include "xdata.h"
    -#include "data.h"
    -
    -GList *
    -jabber_chat_info(G_GNUC_UNUSED PurpleProtocolChat *protocol_chat,
    - G_GNUC_UNUSED PurpleConnection *connection)
    -{
    - GList *m = NULL;
    - PurpleProtocolChatEntry *pce;
    -
    - pce = g_new0(PurpleProtocolChatEntry, 1);
    - pce->label = _("_Room");
    - pce->identifier = "room";
    - pce->required = TRUE;
    - m = g_list_append(m, pce);
    -
    - pce = g_new0(PurpleProtocolChatEntry, 1);
    - pce->label = _("_Server");
    - pce->identifier = "server";
    - pce->required = TRUE;
    - m = g_list_append(m, pce);
    -
    - pce = g_new0(PurpleProtocolChatEntry, 1);
    - pce->label = _("_Handle");
    - pce->identifier = "handle";
    - pce->required = TRUE;
    - m = g_list_append(m, pce);
    -
    - pce = g_new0(PurpleProtocolChatEntry, 1);
    - pce->label = _("_Password");
    - pce->identifier = "password";
    - pce->secret = TRUE;
    - m = g_list_append(m, pce);
    -
    - return m;
    -}
    -
    -GHashTable *jabber_chat_info_defaults(PurpleConnection *gc, const char *chat_name)
    -{
    - GHashTable *defaults;
    - JabberStream *js = purple_connection_get_protocol_data(gc);
    -
    - defaults = g_hash_table_new_full(g_str_hash, g_str_equal, NULL, g_free);
    -
    - g_hash_table_insert(defaults, "handle", g_strdup(js->user->node));
    -
    - if (js->chat_servers)
    - g_hash_table_insert(defaults, "server", g_strdup(js->chat_servers->data));
    -
    - if (chat_name != NULL) {
    - JabberID *jid = jabber_id_new(chat_name);
    - if(jid) {
    - g_hash_table_insert(defaults, "room", g_strdup(jid->node));
    - if(jid->domain)
    - g_hash_table_replace(defaults, "server", g_strdup(jid->domain));
    - if(jid->resource)
    - g_hash_table_replace(defaults, "handle", g_strdup(jid->resource));
    - jabber_id_free(jid);
    - }
    - }
    -
    - return defaults;
    -}
    -
    -JabberChat *jabber_chat_find(JabberStream *js, const char *room,
    - const char *server)
    -{
    - JabberChat *chat = NULL;
    -
    - g_return_val_if_fail(room != NULL, NULL);
    - g_return_val_if_fail(server != NULL, NULL);
    -
    - if(NULL != js->chats)
    - {
    - char *room_jid = g_strdup_printf("%s@%s", room, server);
    -
    - chat = g_hash_table_lookup(js->chats, room_jid);
    - g_free(room_jid);
    - }
    -
    - return chat;
    -}
    -
    -static gboolean
    -find_by_id_cb(G_GNUC_UNUSED gpointer key, gpointer value, gpointer user_data)
    -{
    - JabberChat *chat = value;
    -
    - return chat->id == GPOINTER_TO_INT(user_data);
    -}
    -
    -JabberChat *jabber_chat_find_by_id(JabberStream *js, int id)
    -{
    - return g_hash_table_find(js->chats, find_by_id_cb, GINT_TO_POINTER(id));
    -}
    -
    -JabberChat *jabber_chat_find_by_conv(PurpleChatConversation *conv)
    -{
    - PurpleAccount *account = purple_conversation_get_account(PURPLE_CONVERSATION(conv));
    - PurpleConnection *gc = purple_account_get_connection(account);
    - JabberStream *js;
    - int id;
    - if (!gc)
    - return NULL;
    - js = purple_connection_get_protocol_data(gc);
    - id = purple_chat_conversation_get_id(conv);
    - return jabber_chat_find_by_id(js, id);
    -}
    -
    -void jabber_chat_invite(PurpleConnection *gc, int id, const char *msg,
    - const char *name)
    -{
    - JabberStream *js = purple_connection_get_protocol_data(gc);
    - JabberChat *chat;
    - PurpleXmlNode *message, *body, *x, *invite;
    - char *room_jid;
    -
    - chat = jabber_chat_find_by_id(js, id);
    - if(!chat)
    - return;
    -
    - message = purple_xmlnode_new("message");
    -
    - room_jid = g_strdup_printf("%s@%s", chat->room, chat->server);
    -
    - if(chat->muc) {
    - purple_xmlnode_set_attrib(message, "to", room_jid);
    - x = purple_xmlnode_new_child(message, "x");
    - purple_xmlnode_set_namespace(x, "http://jabber.org/protocol/muc#user");
    - invite = purple_xmlnode_new_child(x, "invite");
    - purple_xmlnode_set_attrib(invite, "to", name);
    - if (msg) {
    - body = purple_xmlnode_new_child(invite, "reason");
    - purple_xmlnode_insert_data(body, msg, -1);
    - }
    - } else {
    - purple_xmlnode_set_attrib(message, "to", name);
    - /*
    - * Putting the reason into the body was an 'undocumented protocol,
    - * ...not part of "groupchat 1.0"'.
    - * http://xmpp.org/extensions/attic/jep-0045-1.16.html#invite
    - *
    - * Left here for compatibility.
    - */
    - if (msg) {
    - body = purple_xmlnode_new_child(message, "body");
    - purple_xmlnode_insert_data(body, msg, -1);
    - }
    -
    - x = purple_xmlnode_new_child(message, "x");
    - purple_xmlnode_set_attrib(x, "jid", room_jid);
    -
    - /* The better place for it! XEP-0249 style. */
    - if (msg)
    - purple_xmlnode_set_attrib(x, "reason", msg);
    - purple_xmlnode_set_namespace(x, "jabber:x:conference");
    - }
    -
    - jabber_send(js, message);
    - purple_xmlnode_free(message);
    - g_free(room_jid);
    -}
    -
    -void jabber_chat_member_free(JabberChatMember *jcm);
    -
    -gchar *
    -jabber_get_chat_name(G_GNUC_UNUSED PurpleProtocolChat *protocol_chat,
    - GHashTable *data)
    -{
    - char *room, *server, *chat_name = NULL;
    -
    - room = g_hash_table_lookup(data, "room");
    - server = g_hash_table_lookup(data, "server");
    -
    - if (room && server) {
    - chat_name = g_strdup_printf("%s@%s", room, server);
    - }
    - return chat_name;
    -}
    -
    -static void insert_in_hash_table(gpointer key, gpointer value, gpointer user_data)
    -{
    - GHashTable *hash_table = (GHashTable *)user_data;
    - g_hash_table_insert(hash_table, g_strdup(key), g_strdup(value));
    -}
    -
    -static JabberChat *
    -jabber_chat_new(JabberStream *js, const char *room, const char *server,
    - const char *handle, G_GNUC_UNUSED const char *password,
    - GHashTable *data)
    -{
    - JabberChat *chat;
    - char *jid;
    -
    - if (jabber_chat_find(js, room, server) != NULL)
    - return NULL;
    -
    - chat = g_new0(JabberChat, 1);
    - chat->js = js;
    - chat->joined = 0;
    -
    - chat->room = g_strdup(room);
    - chat->server = g_strdup(server);
    - chat->handle = g_strdup(handle);
    -
    - /* Copy the data hash table to chat->components */
    - chat->components = g_hash_table_new_full(g_str_hash, g_str_equal,
    - g_free, g_free);
    - if (data == NULL) {
    - g_hash_table_insert(chat->components, g_strdup("handle"), g_strdup(handle));
    - g_hash_table_insert(chat->components, g_strdup("room"), g_strdup(room));
    - g_hash_table_insert(chat->components, g_strdup("server"), g_strdup(server));
    - /* g_hash_table_insert(chat->components, g_strdup("password"), g_strdup(server)); */
    - } else {
    - g_hash_table_foreach(data, insert_in_hash_table, chat->components);
    - }
    -
    - chat->members = g_hash_table_new_full(g_str_hash, g_str_equal, NULL,
    - (GDestroyNotify)jabber_chat_member_free);
    -
    - jid = g_strdup_printf("%s@%s", room, server);
    - g_hash_table_insert(js->chats, jid, chat);
    -
    - return chat;
    -}
    -
    -/*
    - * jabber_join_chat:
    - * @room: The room to join. This MUST be normalized already.
    - * @server: The server the room is on. This MUST be normalized already.
    - * @password: (nullable): The password (if required) to join the room.
    - * @data: (nullable): The chat hash table. If NULL, it will be generated for
    - * current core<>protocol API interface.
    - *
    - * In-protocol function for joining a chat room. Doesn't require sticking goop
    - * into a hash table.
    - */
    -static JabberChat *
    -jabber_join_chat(JabberStream *js, const char *room, const char *server,
    - const char *handle, const char *password, GHashTable *data)
    -{
    - JabberChat *chat;
    -
    - PurpleConnection *gc;
    - PurpleAccount *account;
    - PurpleStatus *status;
    -
    - PurpleXmlNode *presence, *x;
    - JabberBuddyState state;
    - char *msg;
    - int priority;
    -
    - char *jid;
    -
    - chat = jabber_chat_new(js, room, server, handle, password, data);
    - if (chat == NULL)
    - return NULL;
    -
    - gc = js->gc;
    - account = purple_connection_get_account(gc);
    - status = purple_account_get_active_status(account);
    - purple_status_to_jabber(status, &state, &msg, &priority);
    -
    - presence = jabber_presence_create_js(js, state, msg, priority);
    - g_free(msg);
    -
    - jid = g_strdup_printf("%s@%s/%s", room, server, handle);
    - purple_xmlnode_set_attrib(presence, "to", jid);
    - g_free(jid);
    -
    - x = purple_xmlnode_new_child(presence, "x");
    - purple_xmlnode_set_namespace(x, "http://jabber.org/protocol/muc");
    -
    - if (password && *password) {
    - PurpleXmlNode *p = purple_xmlnode_new_child(x, "password");
    - purple_xmlnode_insert_data(p, password, -1);
    - }
    -
    - jabber_send(js, presence);
    - purple_xmlnode_free(presence);
    -
    - return chat;
    -}
    -
    -void jabber_chat_join(PurpleConnection *gc, GHashTable *data)
    -{
    - char *room, *server, *handle, *passwd;
    - JabberID *jid;
    - JabberStream *js = purple_connection_get_protocol_data(gc);
    - char *tmp;
    -
    - room = g_hash_table_lookup(data, "room");
    - server = g_hash_table_lookup(data, "server");
    - handle = g_hash_table_lookup(data, "handle");
    - passwd = g_hash_table_lookup(data, "password");
    -
    - if(!room || !server)
    - return;
    -
    - if(!handle)
    - handle = js->user->node;
    -
    - if(!jabber_nodeprep_validate(room)) {
    - char *buf = g_strdup_printf(_("%s is not a valid room name"), room);
    - purple_notify_error(gc, _("Invalid Room Name"), _("Invalid Room Name"),
    - buf, purple_request_cpar_from_connection(gc));
    - purple_serv_got_join_chat_failed(gc, data);
    - g_free(buf);
    - return;
    - } else if(!jabber_domain_validate(server)) {
    - char *buf = g_strdup_printf(_("%s is not a valid server name"), server);
    - purple_notify_error(gc, _("Invalid Server Name"),
    - _("Invalid Server Name"), buf,
    - purple_request_cpar_from_connection(gc));
    - purple_serv_got_join_chat_failed(gc, data);
    - g_free(buf);
    - return;
    - } else if(!jabber_resourceprep_validate(handle)) {
    - char *buf = g_strdup_printf(_("%s is not a valid room handle"), handle);
    - purple_notify_error(gc, _("Invalid Room Handle"),
    - _("Invalid Room Handle"), buf,
    - purple_request_cpar_from_connection(gc));
    - purple_serv_got_join_chat_failed(gc, data);
    - g_free(buf);
    - return;
    - }
    -
    - /* Normalize the room and server parameters */
    - tmp = g_strdup_printf("%s@%s", room, server);
    - jid = jabber_id_new(tmp);
    - g_free(tmp);
    -
    - if (jid == NULL) {
    - /* TODO: Error message */
    -
    - g_return_if_reached();
    - }
    -
    - /*
    - * Now that we've done all that nice core-interface stuff, let's join
    - * this room!
    - */
    - jabber_join_chat(js, jid->node, jid->domain, handle, passwd, data);
    - jabber_id_free(jid);
    -}
    -
    -void
    -jabber_chat_leave(G_GNUC_UNUSED PurpleProtocolChat *protocol_chat,
    - PurpleConnection *gc, gint id)
    -{
    - JabberStream *js = purple_connection_get_protocol_data(gc);
    - JabberChat *chat = jabber_chat_find_by_id(js, id);
    -
    - if(!chat)
    - return;
    -
    - jabber_chat_part(chat, NULL);
    -
    - chat->left = TRUE;
    -}
    -
    -void jabber_chat_destroy(JabberChat *chat)
    -{
    - JabberStream *js = chat->js;
    - char *room_jid = g_strdup_printf("%s@%s", chat->room, chat->server);
    -
    - g_hash_table_remove(js->chats, room_jid);
    - g_free(room_jid);
    -}
    -
    -void jabber_chat_free(JabberChat *chat)
    -{
    - if(chat->config_dialog_handle)
    - purple_request_close(chat->config_dialog_type, chat->config_dialog_handle);
    -
    - g_free(chat->room);
    - g_free(chat->server);
    - g_free(chat->handle);
    - g_hash_table_destroy(chat->members);
    - g_hash_table_destroy(chat->components);
    -
    - g_clear_pointer(&chat->joined, g_date_time_unref);
    -
    - g_free(chat);
    -}
    -
    -gboolean jabber_chat_find_buddy(PurpleChatConversation *conv, const char *name)
    -{
    - return purple_chat_conversation_has_user(conv, name);
    -}
    -
    -gchar *
    -jabber_chat_user_real_name(G_GNUC_UNUSED PurpleProtocolChat *protocol_chat,
    - PurpleConnection *gc, gint id, const gchar *who)
    -{
    - JabberStream *js = purple_connection_get_protocol_data(gc);
    - JabberChat *chat;
    - JabberChatMember *jcm;
    -
    - chat = jabber_chat_find_by_id(js, id);
    -
    - if(!chat)
    - return NULL;
    -
    - jcm = g_hash_table_lookup(chat->members, who);
    - if (jcm != NULL && jcm->jid)
    - return g_strdup(jcm->jid);
    -
    -
    - return g_strdup_printf("%s@%s/%s", chat->room, chat->server, who);
    -}
    -
    -static void jabber_chat_room_configure_x_data_cb(JabberStream *js, PurpleXmlNode *result, gpointer data)
    -{
    - JabberChat *chat = data;
    - PurpleXmlNode *query;
    - JabberIq *iq;
    - char *to = g_strdup_printf("%s@%s", chat->room, chat->server);
    -
    - iq = jabber_iq_new_query(js, JABBER_IQ_SET, "http://jabber.org/protocol/muc#owner");
    - purple_xmlnode_set_attrib(iq->node, "to", to);
    - g_free(to);
    -
    - query = purple_xmlnode_get_child(iq->node, "query");
    -
    - purple_xmlnode_insert_child(query, result);
    -
    - jabber_iq_send(iq);
    -}
    -
    -static void
    -jabber_chat_room_configure_cb(JabberStream *js, const char *from,
    - JabberIqType type, G_GNUC_UNUSED const char *id,
    - PurpleXmlNode *packet,
    - G_GNUC_UNUSED gpointer data)
    -{
    - PurpleXmlNode *query, *x;
    - char *msg;
    - JabberChat *chat;
    - JabberID *jid;
    -
    - if (!from)
    - return;
    -
    - if (type == JABBER_IQ_RESULT) {
    - jid = jabber_id_new(from);
    -
    - if(!jid)
    - return;
    -
    - chat = jabber_chat_find(js, jid->node, jid->domain);
    - jabber_id_free(jid);
    -
    - if(!chat)
    - return;
    -
    - if(!(query = purple_xmlnode_get_child(packet, "query")))
    - return;
    -
    - for(x = purple_xmlnode_get_child(query, "x"); x; x = purple_xmlnode_get_next_twin(x)) {
    - const char *xmlns;
    - if(!(xmlns = purple_xmlnode_get_namespace(x)))
    - continue;
    -
    - if(purple_strequal(xmlns, "jabber:x:data")) {
    - chat->config_dialog_type = PURPLE_REQUEST_FIELDS;
    - chat->config_dialog_handle = jabber_x_data_request(js, x, jabber_chat_room_configure_x_data_cb, chat);
    - return;
    - }
    - }
    - } else if (type == JABBER_IQ_ERROR) {
    - char *msg = jabber_parse_error(js, packet, NULL);
    -
    - purple_notify_error(js->gc, _("Configuration error"),
    - _("Configuration error"), msg,
    - purple_request_cpar_from_connection(js->gc));
    -
    - g_free(msg);
    - return;
    - }
    -
    - msg = g_strdup_printf("Unable to configure room %s", from);
    -
    - purple_notify_info(js->gc, _("Unable to configure"),
    - _("Unable to configure"), msg,
    - purple_request_cpar_from_connection(js->gc));
    - g_free(msg);
    -
    -}
    -
    -void jabber_chat_request_room_configure(JabberChat *chat) {
    - JabberIq *iq;
    - char *room_jid;
    -
    - if(!chat)
    - return;
    -
    - chat->config_dialog_handle = NULL;
    -
    - if(!chat->muc) {
    - purple_notify_error(chat->js->gc, _("Room Configuration Error"),
    - _("Room Configuration Error"),
    - _("This room is not capable of being configured"),
    - purple_request_cpar_from_connection(chat->js->gc));
    - return;
    - }
    -
    - iq = jabber_iq_new_query(chat->js, JABBER_IQ_GET,
    - "http://jabber.org/protocol/muc#owner");
    - room_jid = g_strdup_printf("%s@%s", chat->room, chat->server);
    -
    - purple_xmlnode_set_attrib(iq->node, "to", room_jid);
    -
    - jabber_iq_set_callback(iq, jabber_chat_room_configure_cb, NULL);
    -
    - jabber_iq_send(iq);
    -
    - g_free(room_jid);
    -}
    -
    -void jabber_chat_create_instant_room(JabberChat *chat) {
    - JabberIq *iq;
    - PurpleXmlNode *query, *x;
    - char *room_jid;
    -
    - if(!chat)
    - return;
    -
    - chat->config_dialog_handle = NULL;
    -
    - iq = jabber_iq_new_query(chat->js, JABBER_IQ_SET,
    - "http://jabber.org/protocol/muc#owner");
    - query = purple_xmlnode_get_child(iq->node, "query");
    - x = purple_xmlnode_new_child(query, "x");
    - room_jid = g_strdup_printf("%s@%s", chat->room, chat->server);
    -
    - purple_xmlnode_set_attrib(iq->node, "to", room_jid);
    - purple_xmlnode_set_namespace(x, "jabber:x:data");
    - purple_xmlnode_set_attrib(x, "type", "submit");
    -
    - jabber_iq_send(iq);
    -
    - g_free(room_jid);
    -}
    -
    -static void
    -jabber_chat_register_x_data_result_cb(JabberStream *js,
    - G_GNUC_UNUSED const char *from,
    - JabberIqType type,
    - G_GNUC_UNUSED const char *id,
    - PurpleXmlNode *packet,
    - G_GNUC_UNUSED gpointer data)
    -{
    - if (type == JABBER_IQ_ERROR) {
    - char *msg = jabber_parse_error(js, packet, NULL);
    -
    - purple_notify_error(js->gc, _("Registration error"),
    - _("Registration error"), msg,
    - purple_request_cpar_from_connection(js->gc));
    -
    - g_free(msg);
    - return;
    - }
    -}
    -
    -static void jabber_chat_register_x_data_cb(JabberStream *js, PurpleXmlNode *result, gpointer data)
    -{
    - JabberChat *chat = data;
    - PurpleXmlNode *query;
    - JabberIq *iq;
    - char *to = g_strdup_printf("%s@%s", chat->room, chat->server);
    -
    - iq = jabber_iq_new_query(js, JABBER_IQ_SET, "jabber:iq:register");
    - purple_xmlnode_set_attrib(iq->node, "to", to);
    - g_free(to);
    -
    - query = purple_xmlnode_get_child(iq->node, "query");
    -
    - purple_xmlnode_insert_child(query, result);
    -
    - jabber_iq_set_callback(iq, jabber_chat_register_x_data_result_cb, NULL);
    -
    - jabber_iq_send(iq);
    -}
    -
    -static void
    -jabber_chat_register_cb(JabberStream *js, const char *from, JabberIqType type,
    - G_GNUC_UNUSED const char *id, PurpleXmlNode *packet,
    - G_GNUC_UNUSED gpointer data)
    -{
    - PurpleXmlNode *query, *x;
    - char *msg;
    - JabberChat *chat;
    - JabberID *jid;
    -
    - if (!from)
    - return;
    -
    - if (type == JABBER_IQ_RESULT) {
    - jid = jabber_id_new(from);
    -
    - if(!jid)
    - return;
    -
    - chat = jabber_chat_find(js, jid->node, jid->domain);
    - jabber_id_free(jid);
    -
    - if(!chat)
    - return;
    -
    - if(!(query = purple_xmlnode_get_child(packet, "query")))
    - return;
    -
    - for(x = purple_xmlnode_get_child(query, "x"); x; x = purple_xmlnode_get_next_twin(x)) {
    - const char *xmlns;
    -
    - if(!(xmlns = purple_xmlnode_get_namespace(x)))
    - continue;
    -
    - if(purple_strequal(xmlns, "jabber:x:data")) {
    - jabber_x_data_request(js, x, jabber_chat_register_x_data_cb, chat);
    - return;
    - }
    - }
    - } else if (type == JABBER_IQ_ERROR) {
    - char *msg = jabber_parse_error(js, packet, NULL);
    -
    - purple_notify_error(js->gc, _("Registration error"),
    - _("Registration error"), msg,
    - purple_request_cpar_from_connection(js->gc));
    -
    - g_free(msg);
    - return;
    - }
    -
    - msg = g_strdup_printf("Unable to configure room %s", from);
    -
    - purple_notify_info(js->gc, _("Unable to configure"), _("Unable to "
    - "configure"), msg, purple_request_cpar_from_connection(js->gc));
    - g_free(msg);
    -
    -}
    -
    -void jabber_chat_register(JabberChat *chat)
    -{
    - JabberIq *iq;
    - char *room_jid;
    -
    - if(!chat)
    - return;
    -
    - room_jid = g_strdup_printf("%s@%s", chat->room, chat->server);
    -
    - iq = jabber_iq_new_query(chat->js, JABBER_IQ_GET, "jabber:iq:register");
    - purple_xmlnode_set_attrib(iq->node, "to", room_jid);
    - g_free(room_jid);
    -
    - jabber_iq_set_callback(iq, jabber_chat_register_cb, NULL);
    -
    - jabber_iq_send(iq);
    -}
    -
    -/* merge this with the function below when we get everyone on the same page wrt /commands */
    -void jabber_chat_change_topic(JabberChat *chat, const char *topic)
    -{
    - JabberMessage *jm;
    -
    - jm = g_new0(JabberMessage, 1);
    - jm->js = chat->js;
    - jm->type = JABBER_MESSAGE_GROUPCHAT;
    - jm->to = g_strdup_printf("%s@%s", chat->room, chat->server);
    -
    - if (topic && *topic)
    - jm->subject = g_strdup(topic);
    - else
    - jm->subject = g_strdup("");
    -
    - jabber_message_send(jm);
    - jabber_message_free(jm);
    -}
    -
    -void
    -jabber_chat_set_topic(G_GNUC_UNUSED PurpleProtocolChat *protocol_chat,
    - PurpleConnection *gc, gint id, const gchar *topic)
    -{
    - JabberStream *js = purple_connection_get_protocol_data(gc);
    - JabberChat *chat = jabber_chat_find_by_id(js, id);
    -
    - if(!chat)
    - return;
    -
    - jabber_chat_change_topic(chat, topic);
    -}
    -
    -
    -gboolean jabber_chat_change_nick(JabberChat *chat, const char *nick)
    -{
    - PurpleXmlNode *presence;
    - char *full_jid;
    - PurpleAccount *account;
    - PurpleStatus *status;
    - JabberBuddyState state;
    - char *msg;
    - int priority;
    -
    - if(!chat->muc) {
    - purple_conversation_write_system_message(
    - PURPLE_CONVERSATION(chat->conv),
    - _("Nick changing not supported in non-MUC chatrooms"), 0);
    - return FALSE;
    - }
    -
    - account = purple_connection_get_account(chat->js->gc);
    - status = purple_account_get_active_status(account);
    -
    - purple_status_to_jabber(status, &state, &msg, &priority);
    -
    - presence = jabber_presence_create_js(chat->js, state, msg, priority);
    - full_jid = g_strdup_printf("%s@%s/%s", chat->room, chat->server, nick);
    - purple_xmlnode_set_attrib(presence, "to", full_jid);
    - g_free(full_jid);
    - g_free(msg);
    -
    - jabber_send(chat->js, presence);
    - purple_xmlnode_free(presence);
    -
    - return TRUE;
    -}
    -
    -void jabber_chat_part(JabberChat *chat, const char *msg)
    -{
    - char *room_jid;
    - PurpleXmlNode *presence;
    -
    - room_jid = g_strdup_printf("%s@%s/%s", chat->room, chat->server,
    - chat->handle);
    - presence = purple_xmlnode_new("presence");
    - purple_xmlnode_set_attrib(presence, "to", room_jid);
    - purple_xmlnode_set_attrib(presence, "type", "unavailable");
    - if(msg) {
    - PurpleXmlNode *status = purple_xmlnode_new_child(presence, "status");
    - purple_xmlnode_insert_data(status, msg, -1);
    - }
    - jabber_send(chat->js, presence);
    -
    - purple_xmlnode_free(presence);
    - g_free(room_jid);
    -}
    -
    -static void
    -roomlist_disco_result_cb(JabberStream *js, G_GNUC_UNUSED const char *from,
    - JabberIqType type, G_GNUC_UNUSED const char *id,
    - PurpleXmlNode *packet, G_GNUC_UNUSED gpointer data)
    -{
    - PurpleXmlNode *query;
    - PurpleXmlNode *item;
    -
    - if(!js->roomlist)
    - return;
    -
    - if (type == JABBER_IQ_ERROR) {
    - char *err = jabber_parse_error(js, packet, NULL);
    - purple_notify_error(js->gc, _("Error"),
    - _("Error retrieving room list"), err,
    - purple_request_cpar_from_connection(js->gc));
    - purple_roomlist_set_in_progress(js->roomlist, FALSE);
    - g_object_unref(js->roomlist);
    - js->roomlist = NULL;
    - g_free(err);
    - return;
    - }
    -
    - if(!(query = purple_xmlnode_get_child(packet, "query"))) {
    - char *err = jabber_parse_error(js, packet, NULL);
    - purple_notify_error(js->gc, _("Error"),
    - _("Error retrieving room list"), err,
    - purple_request_cpar_from_connection(js->gc));
    - purple_roomlist_set_in_progress(js->roomlist, FALSE);
    - g_object_unref(js->roomlist);
    - js->roomlist = NULL;
    - g_free(err);
    - return;
    - }
    -
    - for(item = purple_xmlnode_get_child(query, "item"); item;
    - item = purple_xmlnode_get_next_twin(item)) {
    - const char *name;
    - PurpleRoomlistRoom *room;
    - JabberID *jid;
    -
    - if(!(jid = jabber_id_new(purple_xmlnode_get_attrib(item, "jid"))))
    - continue;
    - name = purple_xmlnode_get_attrib(item, "name");
    -
    -
    - room = purple_roomlist_room_new(jid->node, name);
    - purple_roomlist_room_add_field(room, "room", g_strdup(jid->node));
    - purple_roomlist_room_add_field(room, "server", g_strdup(jid->domain));
    - purple_roomlist_room_add(js->roomlist, room);
    - g_object_unref(room);
    -
    - jabber_id_free(jid);
    - }
    - purple_roomlist_set_in_progress(js->roomlist, FALSE);
    - g_object_unref(js->roomlist);
    - js->roomlist = NULL;
    -}
    -
    -static void
    -roomlist_cancel_cb(JabberStream *js, G_GNUC_UNUSED const char *server) {
    - if(js->roomlist) {
    - purple_roomlist_set_in_progress(js->roomlist, FALSE);
    - g_object_unref(js->roomlist);
    - js->roomlist = NULL;
    - }
    -}
    -
    -static void roomlist_ok_cb(JabberStream *js, const char *server)
    -{
    - JabberIq *iq;
    -
    - if(!js->roomlist)
    - return;
    -
    - if(!server || !*server) {
    - purple_notify_error(js->gc, _("Invalid Server"),
    - _("Invalid Server"), NULL,
    - purple_request_cpar_from_connection(js->gc));
    - purple_roomlist_set_in_progress(js->roomlist, FALSE);
    - return;
    - }
    -
    - purple_roomlist_set_in_progress(js->roomlist, TRUE);
    -
    - iq = jabber_iq_new_query(js, JABBER_IQ_GET, NS_DISCO_ITEMS);
    -
    - purple_xmlnode_set_attrib(iq->node, "to", server);
    -
    - jabber_iq_set_callback(iq, roomlist_disco_result_cb, NULL);
    -
    - jabber_iq_send(iq);
    -}
    -
    -PurpleRoomlist *
    -jabber_roomlist_get_list(G_GNUC_UNUSED PurpleProtocolRoomlist *protocol_roomlist,
    - PurpleConnection *gc)
    -{
    - JabberStream *js = purple_connection_get_protocol_data(gc);
    -
    - if(js->roomlist)
    - g_object_unref(js->roomlist);
    -
    - js->roomlist = purple_roomlist_new(purple_connection_get_account(js->gc));
    -
    - purple_request_input(gc, _("Enter a Conference Server"), _("Enter a Conference Server"),
    - _("Select a conference server to query"),
    - js->chat_servers ? js->chat_servers->data : NULL,
    - FALSE, FALSE, NULL,
    - _("Find Rooms"), G_CALLBACK(roomlist_ok_cb),
    - _("Cancel"), G_CALLBACK(roomlist_cancel_cb),
    - purple_request_cpar_from_connection(gc),
    - js);
    -
    - return js->roomlist;
    -}
    -
    -void
    -jabber_roomlist_cancel(G_GNUC_UNUSED PurpleProtocolRoomlist *protocol_roomlist,
    - PurpleRoomlist *list)
    -{
    - PurpleAccount *account;
    - PurpleConnection *gc;
    - JabberStream *js;
    -
    - account = purple_roomlist_get_account(list);
    - gc = purple_account_get_connection(account);
    - js = purple_connection_get_protocol_data(gc);
    -
    - purple_roomlist_set_in_progress(list, FALSE);
    -
    - if (js->roomlist == list) {
    - js->roomlist = NULL;
    - g_object_unref(list);
    - }
    -}
    -
    -char *
    -jabber_roomlist_room_serialize(G_GNUC_UNUSED PurpleProtocolRoomlist *protocol_roomlist,
    - PurpleRoomlistRoom *room)
    -{
    - const gchar *room_name = NULL, *server = NULL;
    -
    - room_name = purple_roomlist_room_get_field(room, "room");
    - server = purple_roomlist_room_get_field(room, "server");
    -
    - return g_strdup_printf("%s@%s", room_name, server);
    -}
    -
    -void jabber_chat_member_free(JabberChatMember *jcm)
    -{
    - g_free(jcm->handle);
    - g_free(jcm->jid);
    - g_free(jcm);
    -}
    -
    -void
    -jabber_chat_track_handle(JabberChat *chat, const char *handle, const char *jid,
    - G_GNUC_UNUSED const char *affiliation,
    - G_GNUC_UNUSED const char *role)
    -{
    - JabberChatMember *jcm = g_new0(JabberChatMember, 1);
    -
    - jcm->handle = g_strdup(handle);
    - jcm->jid = g_strdup(jid);
    -
    - g_hash_table_replace(chat->members, jcm->handle, jcm);
    -
    - /* XXX: keep track of role and affiliation */
    -}
    -
    -void jabber_chat_remove_handle(JabberChat *chat, const char *handle)
    -{
    - g_hash_table_remove(chat->members, handle);
    -}
    -
    -gboolean jabber_chat_ban_user(JabberChat *chat, const char *who, const char *why)
    -{
    - JabberChatMember *jcm;
    - const char *jid;
    - char *to;
    - JabberIq *iq;
    - PurpleXmlNode *query, *item, *reason;
    -
    - jcm = g_hash_table_lookup(chat->members, who);
    - if (jcm && jcm->jid)
    - jid = jcm->jid;
    - else if (strchr(who, '@') != NULL)
    - jid = who;
    - else
    - return FALSE;
    -
    - iq = jabber_iq_new_query(chat->js, JABBER_IQ_SET,
    - "http://jabber.org/protocol/muc#admin");
    -
    - to = g_strdup_printf("%s@%s", chat->room, chat->server);
    - purple_xmlnode_set_attrib(iq->node, "to", to);
    - g_free(to);
    -
    - query = purple_xmlnode_get_child(iq->node, "query");
    - item = purple_xmlnode_new_child(query, "item");
    - purple_xmlnode_set_attrib(item, "jid", jid);
    - purple_xmlnode_set_attrib(item, "affiliation", "outcast");
    - if(why) {
    - reason = purple_xmlnode_new_child(item, "reason");
    - purple_xmlnode_insert_data(reason, why, -1);
    - }
    -
    - jabber_iq_send(iq);
    -
    - return TRUE;
    -}
    -
    -gboolean jabber_chat_affiliate_user(JabberChat *chat, const char *who, const char *affiliation)
    -{
    - JabberChatMember *jcm;
    - const char *jid;
    - char *to;
    - JabberIq *iq;
    - PurpleXmlNode *query, *item;
    -
    - jcm = g_hash_table_lookup(chat->members, who);
    - if (jcm && jcm->jid)
    - jid = jcm->jid;
    - else if (strchr(who, '@') != NULL)
    - jid = who;
    - else
    - return FALSE;
    -
    - iq = jabber_iq_new_query(chat->js, JABBER_IQ_SET,
    - "http://jabber.org/protocol/muc#admin");
    -
    - to = g_strdup_printf("%s@%s", chat->room, chat->server);
    - purple_xmlnode_set_attrib(iq->node, "to", to);
    - g_free(to);
    -
    - query = purple_xmlnode_get_child(iq->node, "query");
    - item = purple_xmlnode_new_child(query, "item");
    - purple_xmlnode_set_attrib(item, "jid", jid);
    - purple_xmlnode_set_attrib(item, "affiliation", affiliation);
    -
    - jabber_iq_send(iq);
    -
    - return TRUE;
    -}
    -
    -static void
    -jabber_chat_affiliation_list_cb(JabberStream *js,
    - G_GNUC_UNUSED const char *from,
    - JabberIqType type,
    - G_GNUC_UNUSED const char *id,
    - PurpleXmlNode *packet, gpointer data)
    -{
    - JabberChat *chat;
    - PurpleXmlNode *query, *item;
    - int chat_id = GPOINTER_TO_INT(data);
    - GString *buf;
    -
    - if(!(chat = jabber_chat_find_by_id(js, chat_id)))
    - return;
    -
    - if (type == JABBER_IQ_ERROR)
    - return;
    -
    - if(!(query = purple_xmlnode_get_child(packet, "query")))
    - return;
    -
    - buf = g_string_new(_("Affiliations:"));
    -
    - item = purple_xmlnode_get_child(query, "item");
    - if (item) {
    - for( ; item; item = purple_xmlnode_get_next_twin(item)) {
    - const char *jid = purple_xmlnode_get_attrib(item, "jid");
    - const char *affiliation = purple_xmlnode_get_attrib(item, "affiliation");
    - if (jid && affiliation)
    - g_string_append_printf(buf, "\n%s %s", jid, affiliation);
    - }
    - } else {
    - buf = g_string_append_c(buf, '\n');
    - buf = g_string_append_len(buf, _("No users found"), -1);
    - }
    -
    - purple_conversation_write_system_message(PURPLE_CONVERSATION(chat->conv),
    - buf->str, PURPLE_MESSAGE_NO_LOG);
    -
    - g_string_free(buf, TRUE);
    -}
    -
    -gboolean jabber_chat_affiliation_list(JabberChat *chat, const char *affiliation)
    -{
    - JabberIq *iq;
    - char *room_jid;
    - PurpleXmlNode *query, *item;
    -
    - iq = jabber_iq_new_query(chat->js, JABBER_IQ_GET,
    - "http://jabber.org/protocol/muc#admin");
    -
    - room_jid = g_strdup_printf("%s@%s", chat->room, chat->server);
    - purple_xmlnode_set_attrib(iq->node, "to", room_jid);
    -
    - query = purple_xmlnode_get_child(iq->node, "query");
    - item = purple_xmlnode_new_child(query, "item");
    - purple_xmlnode_set_attrib(item, "affiliation", affiliation);
    -
    - jabber_iq_set_callback(iq, jabber_chat_affiliation_list_cb, GINT_TO_POINTER(chat->id));
    - jabber_iq_send(iq);
    -
    - return TRUE;
    -}
    -
    -gboolean jabber_chat_role_user(JabberChat *chat, const char *who,
    - const char *role, const char *why)
    -{
    - char *to;
    - JabberIq *iq;
    - PurpleXmlNode *query, *item;
    - JabberChatMember *jcm;
    -
    - jcm = g_hash_table_lookup(chat->members, who);
    -
    - if (!jcm || !jcm->handle)
    - return FALSE;
    -
    - iq = jabber_iq_new_query(chat->js, JABBER_IQ_SET,
    - "http://jabber.org/protocol/muc#admin");
    -
    - to = g_strdup_printf("%s@%s", chat->room, chat->server);
    - purple_xmlnode_set_attrib(iq->node, "to", to);
    - g_free(to);
    -
    - query = purple_xmlnode_get_child(iq->node, "query");
    - item = purple_xmlnode_new_child(query, "item");
    - purple_xmlnode_set_attrib(item, "nick", jcm->handle);
    - purple_xmlnode_set_attrib(item, "role", role);
    - if (why) {
    - PurpleXmlNode *reason = purple_xmlnode_new_child(item, "reason");
    - purple_xmlnode_insert_data(reason, why, -1);
    - }
    -
    - jabber_iq_send(iq);
    -
    - return TRUE;
    -}
    -
    -static void
    -jabber_chat_role_list_cb(JabberStream *js, G_GNUC_UNUSED const char *from,
    - JabberIqType type, G_GNUC_UNUSED const char *id,
    - PurpleXmlNode *packet, gpointer data)
    -{
    - JabberChat *chat;
    - PurpleXmlNode *query, *item;
    - int chat_id = GPOINTER_TO_INT(data);
    - GString *buf;
    -
    - if(!(chat = jabber_chat_find_by_id(js, chat_id)))
    - return;
    -
    - if (type == JABBER_IQ_ERROR)
    - return;
    -
    - if(!(query = purple_xmlnode_get_child(packet, "query")))
    - return;
    -
    - buf = g_string_new(_("Roles:"));
    -
    - item = purple_xmlnode_get_child(query, "item");
    - if (item) {
    - for( ; item; item = purple_xmlnode_get_next_twin(item)) {
    - const char *jid = purple_xmlnode_get_attrib(item, "jid");
    - const char *role = purple_xmlnode_get_attrib(item, "role");
    - if (jid && role)
    - g_string_append_printf(buf, "\n%s %s", jid, role);
    - }
    - } else {
    - buf = g_string_append_c(buf, '\n');
    - buf = g_string_append_len(buf, _("No users found"), -1);
    - }
    -
    - purple_conversation_write_system_message(PURPLE_CONVERSATION(chat->conv),
    - buf->str, PURPLE_MESSAGE_NO_LOG);
    -
    - g_string_free(buf, TRUE);
    -}
    -
    -gboolean jabber_chat_role_list(JabberChat *chat, const char *role)
    -{
    - JabberIq *iq;
    - char *room_jid;
    - PurpleXmlNode *query, *item;
    -
    - iq = jabber_iq_new_query(chat->js, JABBER_IQ_GET,
    - "http://jabber.org/protocol/muc#admin");
    -
    - room_jid = g_strdup_printf("%s@%s", chat->room, chat->server);
    - purple_xmlnode_set_attrib(iq->node, "to", room_jid);
    -
    - query = purple_xmlnode_get_child(iq->node, "query");
    - item = purple_xmlnode_new_child(query, "item");
    - purple_xmlnode_set_attrib(item, "role", role);
    -
    - jabber_iq_set_callback(iq, jabber_chat_role_list_cb, GINT_TO_POINTER(chat->id));
    - jabber_iq_send(iq);
    -
    - return TRUE;
    -}
    -
    -static void
    -jabber_chat_disco_traffic_cb(JabberStream *js, G_GNUC_UNUSED const char *from,
    - G_GNUC_UNUSED JabberIqType type,
    - G_GNUC_UNUSED const char *id,
    - G_GNUC_UNUSED PurpleXmlNode *packet,
    - gpointer data)
    -{
    - JabberChat *chat;
    - int chat_id = GPOINTER_TO_INT(data);
    -
    - if(!(chat = jabber_chat_find_by_id(js, chat_id)))
    - return;
    -
    - /* defaults, in case the conference server doesn't
    - * support this request */
    - chat->xhtml = TRUE;
    -}
    -
    -void jabber_chat_disco_traffic(JabberChat *chat)
    -{
    - JabberIq *iq;
    - PurpleXmlNode *query;
    - char *room_jid;
    -
    - room_jid = g_strdup_printf("%s@%s", chat->room, chat->server);
    -
    - iq = jabber_iq_new_query(chat->js, JABBER_IQ_GET, NS_DISCO_INFO);
    -
    - purple_xmlnode_set_attrib(iq->node, "to", room_jid);
    -
    - query = purple_xmlnode_get_child(iq->node, "query");
    -
    - purple_xmlnode_set_attrib(query, "node", "http://jabber.org/protocol/muc#traffic");
    -
    - jabber_iq_set_callback(iq, jabber_chat_disco_traffic_cb, GINT_TO_POINTER(chat->id));
    -
    - jabber_iq_send(iq);
    -
    - g_free(room_jid);
    -}
    --- a/libpurple/protocols/jabber/chat.h Tue Apr 09 23:36:32 2024 -0500
    +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
    @@ -1,96 +0,0 @@
    -/**
    - * @file chat.h Chat stuff
    - *
    - * purple
    - *
    - * 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
    - */
    -
    -#ifndef PURPLE_JABBER_CHAT_H
    -#define PURPLE_JABBER_CHAT_H
    -
    -#include <purple.h>
    -
    -#include "jabber.h"
    -
    -typedef struct {
    - char *handle;
    - char *jid;
    -} JabberChatMember;
    -
    -
    -typedef struct {
    - JabberStream *js;
    - char *room;
    - char *server;
    - char *handle;
    - GHashTable *components;
    - int id;
    - PurpleChatConversation *conv;
    - gboolean muc;
    - gboolean xhtml;
    - PurpleRequestType config_dialog_type;
    - void *config_dialog_handle;
    - GHashTable *members;
    - gboolean left;
    - GDateTime *joined;
    -} JabberChat;
    -
    -GList *jabber_chat_info(PurpleProtocolChat *protocol_chat, PurpleConnection *connection);
    -GHashTable *jabber_chat_info_defaults(PurpleConnection *gc, const char *chat_name);
    -char *jabber_get_chat_name(PurpleProtocolChat *protocol_chat, GHashTable *data);
    -
    -void jabber_chat_join(PurpleConnection *gc, GHashTable *data);
    -JabberChat *jabber_chat_find(JabberStream *js, const char *room,
    - const char *server);
    -JabberChat *jabber_chat_find_by_id(JabberStream *js, int id);
    -JabberChat *jabber_chat_find_by_conv(PurpleChatConversation *conv);
    -void jabber_chat_destroy(JabberChat *chat);
    -void jabber_chat_free(JabberChat *chat);
    -gboolean jabber_chat_find_buddy(PurpleChatConversation *conv, const char *name);
    -void jabber_chat_invite(PurpleConnection *gc, int id, const char *message,
    - const char *name);
    -void jabber_chat_leave(PurpleProtocolChat *protocol_chat, PurpleConnection *gc, int id);
    -char *jabber_chat_user_real_name(PurpleProtocolChat *protocol_chat, PurpleConnection *gc, int id, const char *who);
    -void jabber_chat_request_room_configure(JabberChat *chat);
    -void jabber_chat_create_instant_room(JabberChat *chat);
    -void jabber_chat_register(JabberChat *chat);
    -void jabber_chat_change_topic(JabberChat *chat, const char *topic);
    -void jabber_chat_set_topic(PurpleProtocolChat *protocol_chat, PurpleConnection *gc, int id, const char *topic);
    -gboolean jabber_chat_change_nick(JabberChat *chat, const char *nick);
    -void jabber_chat_part(JabberChat *chat, const char *msg);
    -void jabber_chat_track_handle(JabberChat *chat, const char *handle,
    - const char *jid, const char *affiliation, const char *role);
    -void jabber_chat_remove_handle(JabberChat *chat, const char *handle);
    -gboolean jabber_chat_ban_user(JabberChat *chat, const char *who,
    - const char *why);
    -gboolean jabber_chat_affiliate_user(JabberChat *chat, const char *who,
    - const char *affiliation);
    -gboolean jabber_chat_affiliation_list(JabberChat *chat, const char *affiliation);
    -gboolean jabber_chat_role_user(JabberChat *chat, const char *who,
    - const char *role, const char *why);
    -gboolean jabber_chat_role_list(JabberChat *chat, const char *role);
    -
    -PurpleRoomlist *jabber_roomlist_get_list(PurpleProtocolRoomlist *protocol_roomlist, PurpleConnection *gc);
    -void jabber_roomlist_cancel(PurpleProtocolRoomlist *protocol_roomlist, PurpleRoomlist *list);
    -char *jabber_roomlist_room_serialize(PurpleProtocolRoomlist *protocol_roomlist, PurpleRoomlistRoom *room);
    -
    -void jabber_chat_disco_traffic(JabberChat *chat);
    -
    -#endif /* PURPLE_JABBER_CHAT_H */
    --- a/libpurple/protocols/jabber/data.c Tue Apr 09 23:36:32 2024 -0500
    +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
    @@ -1,474 +0,0 @@
    -/*
    - * purple - Handling of XEP-0231: Bits of Binary.
    - *
    - * 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 <stdlib.h>
    -#include <glib.h>
    -#include <string.h>
    -
    -#include <purple.h>
    -#include "libpurple/glibcompat.h"
    -
    -#include "data.h"
    -#include "iq.h"
    -
    -static GHashTable *local_data_by_alt = NULL;
    -static GHashTable *local_data_by_cid = NULL;
    -static GHashTable *remote_data_by_cid = NULL;
    -
    -JabberData *
    -jabber_data_create_from_data(gconstpointer rawdata, gsize size,
    - const char *type, gboolean ephemeral,
    - G_GNUC_UNUSED JabberStream *js)
    -{
    - JabberData *data;
    - gchar *checksum;
    -
    - g_return_val_if_fail(rawdata != NULL, NULL);
    - g_return_val_if_fail(size > 0, NULL);
    - g_return_val_if_fail(type != NULL, NULL);
    -
    - checksum = g_compute_checksum_for_data(G_CHECKSUM_SHA1, rawdata, size);
    -
    - data = g_new0(JabberData, 1);
    - data->cid = g_strdup_printf("sha1+%s@bob.xmpp.org", checksum);
    - data->type = g_strdup(type);
    - data->size = size;
    - data->ephemeral = ephemeral;
    - data->data = g_memdup2(rawdata, size);
    -
    - g_free(checksum);
    - return data;
    -}
    -
    -static void
    -jabber_data_delete(gpointer cbdata)
    -{
    - JabberData *data = cbdata;
    -
    - g_free(data->cid);
    - g_free(data->type);
    - g_free(data->data);
    - g_free(data);
    -}
    -
    -
    -JabberData *
    -jabber_data_create_from_xml(PurpleXmlNode *tag)
    -{
    - JabberData *data;
    - gchar *raw_data = NULL;
    - const gchar *cid, *type;
    -
    - g_return_val_if_fail(tag != NULL, NULL);
    -
    - /* check if this is a "data" tag */
    - if (!purple_strequal(tag->name, "data")) {
    - purple_debug_error("jabber", "Invalid data element\n");
    - return NULL;
    - }
    -
    - cid = purple_xmlnode_get_attrib(tag, "cid");
    - type = purple_xmlnode_get_attrib(tag, "type");
    -
    - if (!cid || !type) {
    - purple_debug_error("jabber", "cid or type missing\n");
    - return NULL;
    - }
    -
    - raw_data = purple_xmlnode_get_data(tag);
    -
    - if (raw_data == NULL || *raw_data == '\0') {
    - purple_debug_error("jabber", "data element was empty");
    - g_free(raw_data);
    - return NULL;
    - }
    -
    - data = g_new0(JabberData, 1);
    - data->data = g_base64_decode(raw_data, &data->size);
    - g_free(raw_data);
    -
    - if (data->data == NULL) {
    - purple_debug_error("jabber", "Malformed base64 data\n");
    - g_free(data);
    - return NULL;
    - }
    -
    - data->cid = g_strdup(cid);
    - data->type = g_strdup(type);
    -
    - return data;
    -}
    -
    -void
    -jabber_data_destroy(JabberData *data)
    -{
    - g_return_if_fail(data != NULL);
    -
    - jabber_data_delete(data);
    -}
    -
    -const char *
    -jabber_data_get_cid(const JabberData *data)
    -{
    - g_return_val_if_fail(data != NULL, NULL);
    -
    - return data->cid;
    -}
    -
    -
    -const char *
    -jabber_data_get_type(const JabberData *data)
    -{
    - g_return_val_if_fail(data != NULL, NULL);
    -
    - return data->type;
    -}
    -
    -gsize
    -jabber_data_get_size(const JabberData *data)
    -{
    - g_return_val_if_fail(data != NULL, 0);
    -
    - return data->size;
    -}
    -
    -gpointer
    -jabber_data_get_data(const JabberData *data)