summaryrefslogtreecommitdiff
path: root/libpurple/protocols/jabber
diff options
context:
space:
mode:
Diffstat (limited to 'libpurple/protocols/jabber')
-rw-r--r--libpurple/protocols/jabber/.todo47
-rw-r--r--libpurple/protocols/jabber/JEPS27
-rw-r--r--libpurple/protocols/jabber/Makefile.am67
-rw-r--r--libpurple/protocols/jabber/Makefile.mingw96
-rw-r--r--libpurple/protocols/jabber/auth.c800
-rw-r--r--libpurple/protocols/jabber/auth.h35
-rw-r--r--libpurple/protocols/jabber/buddy.c1799
-rw-r--r--libpurple/protocols/jabber/buddy.h106
-rw-r--r--libpurple/protocols/jabber/chat.c1013
-rw-r--r--libpurple/protocols/jabber/chat.h95
-rw-r--r--libpurple/protocols/jabber/disco.c384
-rw-r--r--libpurple/protocols/jabber/disco.h38
-rw-r--r--libpurple/protocols/jabber/google.c363
-rw-r--r--libpurple/protocols/jabber/google.h40
-rw-r--r--libpurple/protocols/jabber/iq.c336
-rw-r--r--libpurple/protocols/jabber/iq.h70
-rw-r--r--libpurple/protocols/jabber/jabber.c2022
-rw-r--r--libpurple/protocols/jabber/jabber.h165
-rw-r--r--libpurple/protocols/jabber/jutil.c235
-rw-r--r--libpurple/protocols/jabber/jutil.h50
-rw-r--r--libpurple/protocols/jabber/message.c632
-rw-r--r--libpurple/protocols/jabber/message.h77
-rw-r--r--libpurple/protocols/jabber/oob.c242
-rw-r--r--libpurple/protocols/jabber/oob.h27
-rw-r--r--libpurple/protocols/jabber/parser.c195
-rw-r--r--libpurple/protocols/jabber/parser.h30
-rw-r--r--libpurple/protocols/jabber/presence.c617
-rw-r--r--libpurple/protocols/jabber/presence.h37
-rw-r--r--libpurple/protocols/jabber/roster.c409
-rw-r--r--libpurple/protocols/jabber/roster.h42
-rw-r--r--libpurple/protocols/jabber/si.c974
-rw-r--r--libpurple/protocols/jabber/si.h34
-rw-r--r--libpurple/protocols/jabber/win32/posix.uname.c135
-rw-r--r--libpurple/protocols/jabber/win32/utsname.h23
-rw-r--r--libpurple/protocols/jabber/xdata.c348
-rw-r--r--libpurple/protocols/jabber/xdata.h31
36 files changed, 11641 insertions, 0 deletions
diff --git a/libpurple/protocols/jabber/.todo b/libpurple/protocols/jabber/.todo
new file mode 100644
index 0000000000..5f659a3e0f
--- /dev/null
+++ b/libpurple/protocols/jabber/.todo
@@ -0,0 +1,47 @@
+<todo version="0.1.19">
+ <note priority="verylow" time="1036043981" done="1089237837">
+ *sigh* file transfer (do we really need/want this?)
+ <comment>
+ faceprint did this
+ </comment>
+ </note>
+ <note priority="high" time="1036043427">
+ problem seeing buddies with long blist?
+ </note>
+ <note priority="medium" time="1036044198">
+ Browsing
+ </note>
+ <note priority="medium" time="1036044416">
+ Server Admin operations (messages, etc.)
+ </note>
+ <note priority="medium" time="1036044448">
+ Add option for user info to be published or not in JUD.
+ </note>
+ <note priority="medium" time="1036044571">
+ Show self on buddylist
+ <comment>
+ is this done?
+ </comment>
+ </note>
+ <note priority="medium" time="1036044583">
+ Delete server account.
+ </note>
+ <note priority="medium" time="1036045649">
+ Permit/Deny buddy support.
+ </note>
+ <note priority="medium" time="1036046413">
+ a populate roster from local blist. most useful if you want to migrate a blist from one account to another, also useful if something freaky happens and the server blist is lost.
+ </note>
+ <note priority="medium" time="1037892911">
+ info
+ <note priority="medium" time="1037893000">
+ formatted. enhancement-request so that the birthday field in the setinfo form would split up into relevant fields allowing for a strict syntax (like year--month--day or so, perhaps even dropdown menus)
+ </note>
+ <note priority="low" time="1037890968">
+ have set info pre-fill values from the server when no local vcard exists. this will help people migrating to gaim
+ </note>
+ </note>
+ <note priority="verylow" time="1036044192">
+ Jabber Transports (having them show up on the buddy list should be fairly easy; having an appropriate right-click menu for them should also be somewhat easy. Providing a UI for adding transports should be rather difficult.)
+ </note>
+</todo>
diff --git a/libpurple/protocols/jabber/JEPS b/libpurple/protocols/jabber/JEPS
new file mode 100644
index 0000000000..cd67248431
--- /dev/null
+++ b/libpurple/protocols/jabber/JEPS
@@ -0,0 +1,27 @@
+0045: IN PROGRESS
+ Multi-User Chat
+0047: IN PROGRESS
+ In-Band Bytestreams
+0060: NEED
+ Pub-Sub
+0071: AWAITING FINAL SPEC
+ XHTML-IM
+0073: NEED
+ Basic IM Protocol Suite
+0080: NEED (Do we?)
+ Geographic Location Information
+0084: NEED
+ User Avatars in Jabber
+0085: NEED
+ Chat State Notifications
+0089: WATCH
+ Generic Alerts
+0093: NEED
+ Roster Item Exchange
+0100: NEED
+ Gateway Interaction (Transports)
+0115: WATCH
+ Client Capabilities
+0117: NEED
+ Intermediate IM Protocol Suite
+
diff --git a/libpurple/protocols/jabber/Makefile.am b/libpurple/protocols/jabber/Makefile.am
new file mode 100644
index 0000000000..5c6a15a7c7
--- /dev/null
+++ b/libpurple/protocols/jabber/Makefile.am
@@ -0,0 +1,67 @@
+EXTRA_DIST = \
+ Makefile.mingw \
+ win32/posix.uname.c \
+ win32/utsname.h
+
+pkgdir = $(libdir)/gaim
+
+JABBERSOURCES = auth.c \
+ auth.h \
+ buddy.c \
+ buddy.h \
+ chat.c \
+ chat.h \
+ disco.c \
+ disco.h \
+ google.c \
+ google.h \
+ iq.c \
+ iq.h \
+ jabber.c \
+ jabber.h \
+ jutil.c \
+ jutil.h \
+ message.c \
+ message.h \
+ oob.c \
+ oob.h \
+ parser.c \
+ parser.h \
+ presence.c \
+ presence.h \
+ roster.c \
+ roster.h \
+ si.c \
+ si.h \
+ xdata.c \
+ xdata.h
+
+AM_CFLAGS = $(st)
+
+libjabber_la_LDFLAGS = -module -avoid-version
+
+if STATIC_JABBER
+
+st = -DGAIM_STATIC_PRPL
+noinst_LIBRARIES = libjabber.a
+pkg_LTLIBRARIES =
+
+libjabber_a_SOURCES = $(JABBERSOURCES)
+libjabber_a_CFLAGS = $(AM_CFLAGS)
+
+else
+
+st =
+pkg_LTLIBRARIES = libjabber.la
+noinst_LIBRARIES =
+
+libjabber_la_SOURCES = $(JABBERSOURCES)
+libjabber_la_LIBADD = $(GLIB_LIBS) $(SASL_LIBS) $(LIBXML_LIBS)
+
+endif
+
+AM_CPPFLAGS = \
+ -I$(top_srcdir)/libpurple \
+ $(DEBUG_CFLAGS) \
+ $(GLIB_CFLAGS) \
+ $(LIBXML_CFLAGS)
diff --git a/libpurple/protocols/jabber/Makefile.mingw b/libpurple/protocols/jabber/Makefile.mingw
new file mode 100644
index 0000000000..1a59a8eaf2
--- /dev/null
+++ b/libpurple/protocols/jabber/Makefile.mingw
@@ -0,0 +1,96 @@
+#
+# Makefile.mingw
+#
+# Description: Makefile for win32 (mingw) version of libjabber
+#
+
+GAIM_TOP := ../../..
+include $(GAIM_TOP)/libgaim/win32/global.mak
+
+TARGET = libjabber
+TYPE = PLUGIN
+
+# Static or Plugin...
+ifeq ($(TYPE),STATIC)
+ DEFINES += -DSTATIC
+ DLL_INSTALL_DIR = $(GAIM_INSTALL_DIR)
+else
+ifeq ($(TYPE),PLUGIN)
+ DLL_INSTALL_DIR = $(GAIM_INSTALL_PLUGINS_DIR)
+endif
+endif
+
+##
+## INCLUDE PATHS
+##
+INCLUDE_PATHS += -I. \
+ -I./win32 \
+ -I$(GTK_TOP)/include \
+ -I$(GTK_TOP)/include/glib-2.0 \
+ -I$(GTK_TOP)/lib/glib-2.0/include \
+ -I$(LIBXML2_TOP)/include \
+ -I$(GAIM_LIB_TOP) \
+ -I$(GAIM_LIB_TOP)/win32 \
+ -I$(GAIM_TOP)
+
+LIB_PATHS = -L$(GTK_TOP)/lib \
+ -L$(LIBXML2_TOP)/lib \
+ -L$(GAIM_LIB_TOP)
+
+##
+## SOURCES, OBJECTS
+##
+C_SRC = auth.c \
+ buddy.c \
+ chat.c \
+ disco.c \
+ google.c \
+ iq.c \
+ jabber.c \
+ jutil.c \
+ message.c \
+ oob.c \
+ parser.c \
+ presence.c \
+ roster.c \
+ si.c \
+ xdata.c \
+ win32/posix.uname.c
+
+OBJECTS = $(C_SRC:%.c=%.o)
+
+##
+## LIBRARIES
+##
+LIBS = \
+ -lglib-2.0 \
+ -lxml2 \
+ -lws2_32 \
+ -lintl \
+ -lgaim
+
+include $(GAIM_COMMON_RULES)
+
+##
+## TARGET DEFINITIONS
+##
+.PHONY: all install clean
+
+all: $(TARGET).dll
+
+install: all $(DLL_INSTALL_DIR)
+ cp $(TARGET).dll $(DLL_INSTALL_DIR)
+
+$(OBJECTS): $(GAIM_CONFIG_H)
+
+$(TARGET).dll: $(GAIM_LIBGAIM_DLL).a $(OBJECTS)
+ $(CC) -shared $(OBJECTS) $(LIB_PATHS) $(LIBS) $(DLL_LD_FLAGS) -o $(TARGET).dll
+
+##
+## CLEAN RULES
+##
+clean:
+ rm -f $(OBJECTS)
+ rm -f $(TARGET).dll
+
+include $(GAIM_COMMON_TARGETS)
diff --git a/libpurple/protocols/jabber/auth.c b/libpurple/protocols/jabber/auth.c
new file mode 100644
index 0000000000..9f2718463e
--- /dev/null
+++ b/libpurple/protocols/jabber/auth.c
@@ -0,0 +1,800 @@
+/*
+ * gaim - Jabber Protocol Plugin
+ *
+ * Copyright (C) 2003, Nathan Walp <faceprint@faceprint.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ */
+#include "internal.h"
+
+#include "jutil.h"
+#include "auth.h"
+#include "xmlnode.h"
+#include "jabber.h"
+#include "iq.h"
+
+#include "debug.h"
+#include "util.h"
+#include "cipher.h"
+#include "sslconn.h"
+#include "request.h"
+
+static void auth_old_result_cb(JabberStream *js, xmlnode *packet,
+ gpointer data);
+
+gboolean
+jabber_process_starttls(JabberStream *js, xmlnode *packet)
+{
+ xmlnode *starttls;
+
+ if((starttls = xmlnode_get_child(packet, "starttls"))) {
+ if(gaim_ssl_is_supported()) {
+ jabber_send_raw(js,
+ "<starttls xmlns='urn:ietf:params:xml:ns:xmpp-tls'/>", -1);
+ return TRUE;
+ } else if(xmlnode_get_child(starttls, "required")) {
+ gaim_connection_error(js->gc, _("Server requires TLS/SSL for login. No TLS/SSL support found."));
+ return TRUE;
+ }
+ }
+
+ return FALSE;
+}
+
+static void finish_plaintext_authentication(JabberStream *js)
+{
+ if(js->auth_type == JABBER_AUTH_PLAIN) {
+ xmlnode *auth;
+ GString *response;
+ gchar *enc_out;
+
+ auth = xmlnode_new("auth");
+ xmlnode_set_namespace(auth, "urn:ietf:params:xml:ns:xmpp-sasl");
+
+ xmlnode_set_attrib(auth, "xmlns:ga", "http://www.google.com/talk/protocol/auth");
+ xmlnode_set_attrib(auth, "ga:client-uses-full-bind-result", "true");
+
+ response = g_string_new("");
+ response = g_string_append_len(response, "\0", 1);
+ response = g_string_append(response, js->user->node);
+ response = g_string_append_len(response, "\0", 1);
+ response = g_string_append(response,
+ gaim_connection_get_password(js->gc));
+
+ enc_out = gaim_base64_encode((guchar *)response->str, response->len);
+
+ xmlnode_set_attrib(auth, "mechanism", "PLAIN");
+ xmlnode_insert_data(auth, enc_out, -1);
+ g_free(enc_out);
+ g_string_free(response, TRUE);
+
+ jabber_send(js, auth);
+ xmlnode_free(auth);
+ } else if(js->auth_type == JABBER_AUTH_IQ_AUTH) {
+ JabberIq *iq;
+ xmlnode *query, *x;
+
+ iq = jabber_iq_new_query(js, JABBER_IQ_SET, "jabber:iq:auth");
+ query = xmlnode_get_child(iq->node, "query");
+ x = xmlnode_new_child(query, "username");
+ xmlnode_insert_data(x, js->user->node, -1);
+ x = xmlnode_new_child(query, "resource");
+ xmlnode_insert_data(x, js->user->resource, -1);
+ x = xmlnode_new_child(query, "password");
+ xmlnode_insert_data(x, gaim_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(GaimAccount *account)
+{
+ gaim_account_set_bool(account, "auth_plain_in_clear", TRUE);
+
+ finish_plaintext_authentication(account->gc->proto_data);
+}
+
+static void disallow_plaintext_auth(GaimAccount *account)
+{
+ gaim_connection_error(account->gc, _("Server requires plaintext authentication over an unencrypted stream"));
+}
+
+#ifdef HAVE_CYRUS_SASL
+
+static void jabber_auth_start_cyrus(JabberStream *);
+
+/* Callbacks for Cyrus SASL */
+
+static int jabber_sasl_cb_realm(void *ctx, int id, const char **avail, const char **result)
+{
+ JabberStream *js = (JabberStream *)ctx;
+
+ if (id != SASL_CB_GETREALM || !result) return SASL_BADPARAM;
+
+ *result = js->user->domain;
+
+ return SASL_OK;
+}
+
+static int jabber_sasl_cb_simple(void *ctx, int id, const char **res, unsigned *len)
+{
+ JabberStream *js = (JabberStream *)ctx;
+
+ switch(id) {
+ case SASL_CB_AUTHNAME:
+ *res = js->user->node;
+ break;
+ case SASL_CB_USER:
+ *res = "";
+ break;
+ default:
+ return SASL_BADPARAM;
+ }
+ if (len) *len = strlen((char *)*res);
+ return SASL_OK;
+}
+
+static int jabber_sasl_cb_secret(sasl_conn_t *conn, void *ctx, int id, sasl_secret_t **secret)
+{
+ JabberStream *js = (JabberStream *)ctx;
+ const char *pw = gaim_account_get_password(js->gc->account);
+ size_t len;
+ static sasl_secret_t *x = NULL;
+
+ if (!conn || !secret || id != SASL_CB_PASS)
+ return SASL_BADPARAM;
+
+ len = strlen(pw);
+ x = (sasl_secret_t *) realloc(x, sizeof(sasl_secret_t) + len);
+
+ if (!x)
+ return SASL_NOMEM;
+
+ x->len = len;
+ strcpy((char*)x->data, pw);
+
+ *secret = x;
+ return SASL_OK;
+}
+
+static void allow_cyrus_plaintext_auth(GaimAccount *account)
+{
+ gaim_account_set_bool(account, "auth_plain_in_clear", TRUE);
+
+ jabber_auth_start_cyrus(account->gc->proto_data);
+}
+
+static void jabber_auth_start_cyrus(JabberStream *js)
+{
+ const char *clientout = NULL, *mech = NULL;
+ char *enc_out;
+ unsigned coutlen = 0;
+ xmlnode *auth;
+ sasl_security_properties_t secprops;
+ gboolean again;
+ gboolean plaintext = TRUE;
+
+ /* Set up security properties and options */
+ secprops.min_ssf = 0;
+ secprops.security_flags = SASL_SEC_NOANONYMOUS;
+
+ if (!js->gsc) {
+ secprops.max_ssf = -1;
+ secprops.maxbufsize = 4096;
+ plaintext = gaim_account_get_bool(js->gc->account, "auth_plain_in_clear", FALSE);
+ if (!plaintext)
+ secprops.security_flags |= SASL_SEC_NOPLAINTEXT;
+ } else {
+ secprops.max_ssf = 0;
+ secprops.maxbufsize = 0;
+ plaintext = TRUE;
+ }
+ secprops.property_names = 0;
+ secprops.property_values = 0;
+
+ do {
+ again = FALSE;
+ /* Use the user's domain for compatibility with the old
+ * DIGESTMD5 code. Note that this may cause problems where
+ * the user's domain doesn't match the FQDN of the jabber
+ * service
+ */
+
+ js->sasl_state = sasl_client_new("xmpp", js->user->domain, NULL, NULL, js->sasl_cb, 0, &js->sasl);
+ if (js->sasl_state==SASL_OK) {
+ sasl_setprop(js->sasl, SASL_SEC_PROPS, &secprops);
+ gaim_debug_info("sasl", "Mechs found: %s\n", js->sasl_mechs->str);
+ js->sasl_state = sasl_client_start(js->sasl, js->sasl_mechs->str, NULL, &clientout, &coutlen, &mech);
+ }
+ switch (js->sasl_state) {
+ /* Success */
+ case SASL_OK:
+ case SASL_CONTINUE:
+ break;
+ case SASL_NOMECH:
+ /* No mechanisms do what we want. See if we can add
+ * plaintext ones to the list. */
+
+ if (!gaim_account_get_password(js->gc->account)) {
+ gaim_connection_error(js->gc, _("Server couldn't authenticate you without a password"));
+ return;
+ } else if (!plaintext) {
+ gaim_request_yes_no(js->gc, _("Plaintext Authentication"),
+ _("Plaintext Authentication"),
+ _("This server requires plaintext authentication over an unencrypted connection. Allow this and continue authentication?"),
+ 2, js->gc->account,
+ allow_cyrus_plaintext_auth,
+ disallow_plaintext_auth);
+ return;
+ } else {
+ gaim_connection_error(js->gc, _("Server does not use any supported authentication method"));
+ return;
+ }
+ /* not reached */
+ break;
+
+ /* Fatal errors. Give up and go home */
+ case SASL_BADPARAM:
+ case SASL_NOMEM:
+ break;
+
+ /* For everything else, fail the mechanism and try again */
+ default:
+ gaim_debug_info("sasl", "sasl_state is %d, failing the mech and trying again\n", js->sasl_state);
+
+ /*
+ * DAA: is this right?
+ * The manpage says that "mech" will contain the chosen mechanism on success.
+ * Presumably, if we get here that isn't the case and we shouldn't try again?
+ * I suspect that this never happens.
+ */
+ if (mech && strlen(mech) > 0) {
+ char *pos;
+ if ((pos = strstr(js->sasl_mechs->str, mech))) {
+ g_string_erase(js->sasl_mechs, pos-js->sasl_mechs->str, strlen(mech));
+ }
+ again = TRUE;
+ }
+
+ sasl_dispose(&js->sasl);
+ }
+ } while (again);
+
+ if (js->sasl_state == SASL_CONTINUE || js->sasl_state == SASL_OK) {
+ auth = xmlnode_new("auth");
+ xmlnode_set_namespace(auth, "urn:ietf:params:xml:ns:xmpp-sasl");
+ xmlnode_set_attrib(auth, "mechanism", mech);
+ if (clientout) {
+ if (coutlen == 0) {
+ xmlnode_insert_data(auth, "=", -1);
+ } else {
+ enc_out = gaim_base64_encode((unsigned char*)clientout, coutlen);
+ xmlnode_insert_data(auth, enc_out, -1);
+ g_free(enc_out);
+ }
+ }
+ jabber_send(js, auth);
+ xmlnode_free(auth);
+ } else {
+ gaim_connection_error(js->gc, "SASL authentication failed\n");
+ }
+}
+
+static int
+jabber_sasl_cb_log(void *context, int level, const char *message)
+{
+ if(level <= SASL_LOG_TRACE)
+ gaim_debug_info("sasl", "%s\n", message);
+
+ return SASL_OK;
+}
+
+#endif
+
+void
+jabber_auth_start(JabberStream *js, xmlnode *packet)
+{
+#ifdef HAVE_CYRUS_SASL
+ int id;
+#else
+ gboolean digest_md5 = FALSE, plain=FALSE;
+#endif
+
+ xmlnode *mechs, *mechnode;
+
+
+ if(js->registration) {
+ jabber_register_start(js);
+ return;
+ }
+
+ mechs = xmlnode_get_child(packet, "mechanisms");
+
+ if(!mechs) {
+ gaim_connection_error(js->gc, _("Invalid response from server."));
+ return;
+ }
+
+#ifdef HAVE_CYRUS_SASL
+ js->sasl_mechs = g_string_new("");
+#endif
+
+ for(mechnode = xmlnode_get_child(mechs, "mechanism"); mechnode;
+ mechnode = xmlnode_get_next_twin(mechnode))
+ {
+ char *mech_name = xmlnode_get_data(mechnode);
+#ifdef HAVE_CYRUS_SASL
+ g_string_append(js->sasl_mechs, mech_name);
+ g_string_append_c(js->sasl_mechs, ' ');
+#else
+ if(mech_name && !strcmp(mech_name, "DIGEST-MD5"))
+ digest_md5 = TRUE;
+ else if(mech_name && !strcmp(mech_name, "PLAIN"))
+ plain = TRUE;
+#endif
+ g_free(mech_name);
+ }
+
+#ifdef HAVE_CYRUS_SASL
+ js->auth_type = JABBER_AUTH_CYRUS;
+
+ /* Set up our callbacks structure */
+ js->sasl_cb = g_new0(sasl_callback_t,6);
+
+ id = 0;
+ js->sasl_cb[id].id = SASL_CB_GETREALM;
+ js->sasl_cb[id].proc = jabber_sasl_cb_realm;
+ js->sasl_cb[id].context = (void *)js;
+ id++;
+
+ js->sasl_cb[id].id = SASL_CB_AUTHNAME;
+ js->sasl_cb[id].proc = jabber_sasl_cb_simple;
+ js->sasl_cb[id].context = (void *)js;
+ id++;
+
+ js->sasl_cb[id].id = SASL_CB_USER;
+ js->sasl_cb[id].proc = jabber_sasl_cb_simple;
+ js->sasl_cb[id].context = (void *)js;
+ id++;
+
+ if (gaim_account_get_password(js->gc->account)) {
+ js->sasl_cb[id].id = SASL_CB_PASS;
+ js->sasl_cb[id].proc = jabber_sasl_cb_secret;
+ js->sasl_cb[id].context = (void *)js;
+ id++;
+ }
+
+ js->sasl_cb[id].id = SASL_CB_LOG;
+ js->sasl_cb[id].proc = jabber_sasl_cb_log;
+ js->sasl_cb[id].context = (void*)js;
+ id++;
+
+ js->sasl_cb[id].id = SASL_CB_LIST_END;
+
+ jabber_auth_start_cyrus(js);
+#else
+
+ if(digest_md5) {
+ xmlnode *auth;
+
+ js->auth_type = JABBER_AUTH_DIGEST_MD5;
+ auth = xmlnode_new("auth");
+ xmlnode_set_namespace(auth, "urn:ietf:params:xml:ns:xmpp-sasl");
+ xmlnode_set_attrib(auth, "mechanism", "DIGEST-MD5");
+
+ jabber_send(js, auth);
+ xmlnode_free(auth);
+ } else if(plain) {
+ js->auth_type = JABBER_AUTH_PLAIN;
+
+ if(js->gsc == NULL && !gaim_account_get_bool(js->gc->account, "auth_plain_in_clear", FALSE)) {
+ gaim_request_yes_no(js->gc, _("Plaintext Authentication"),
+ _("Plaintext Authentication"),
+ _("This server requires plaintext authentication over an unencrypted connection. Allow this and continue authentication?"),
+ 2, js->gc->account, allow_plaintext_auth,
+ disallow_plaintext_auth);
+ return;
+ }
+ finish_plaintext_authentication(js);
+ } else {
+ gaim_connection_error(js->gc,
+ _("Server does not use any supported authentication method"));
+ }
+#endif
+}
+
+static void auth_old_result_cb(JabberStream *js, xmlnode *packet, gpointer data)
+{
+ const char *type = xmlnode_get_attrib(packet, "type");
+
+ if(type && !strcmp(type, "result")) {
+ jabber_stream_set_state(js, JABBER_STREAM_CONNECTED);
+ } else {
+ char *msg = jabber_parse_error(js, packet);
+ xmlnode *error;
+ const char *err_code;
+
+ if((error = xmlnode_get_child(packet, "error")) &&
+ (err_code = xmlnode_get_attrib(error, "code")) &&
+ !strcmp(err_code, "401")) {
+ js->gc->wants_to_die = TRUE;
+ }
+
+ gaim_connection_error(js->gc, msg);
+ g_free(msg);
+ }
+}
+
+static void auth_old_cb(JabberStream *js, xmlnode *packet, gpointer data)
+{
+ JabberIq *iq;
+ xmlnode *query, *x;
+ const char *type = xmlnode_get_attrib(packet, "type");
+ const char *pw = gaim_connection_get_password(js->gc);
+
+ if(!type) {
+ gaim_connection_error(js->gc, _("Invalid response from server."));
+ return;
+ } else if(!strcmp(type, "error")) {
+ char *msg = jabber_parse_error(js, packet);
+ gaim_connection_error(js->gc, msg);
+ g_free(msg);
+ } else if(!strcmp(type, "result")) {
+ query = xmlnode_get_child(packet, "query");
+ if(js->stream_id && xmlnode_get_child(query, "digest")) {
+ unsigned char hashval[20];
+ char *s, h[41], *p;
+ int i;
+
+ iq = jabber_iq_new_query(js, JABBER_IQ_SET, "jabber:iq:auth");
+ query = xmlnode_get_child(iq->node, "query");
+ x = xmlnode_new_child(query, "username");
+ xmlnode_insert_data(x, js->user->node, -1);
+ x = xmlnode_new_child(query, "resource");
+ xmlnode_insert_data(x, js->user->resource, -1);
+
+ x = xmlnode_new_child(query, "digest");
+ s = g_strdup_printf("%s%s", js->stream_id, pw);
+
+ gaim_cipher_digest_region("sha1", (guchar *)s, strlen(s),
+ sizeof(hashval), hashval, NULL);
+
+ p = h;
+ for(i=0; i<20; i++, p+=2)
+ snprintf(p, 3, "%02x", hashval[i]);
+ xmlnode_insert_data(x, h, -1);
+ g_free(s);
+ jabber_iq_set_callback(iq, auth_old_result_cb, NULL);
+ jabber_iq_send(iq);
+
+ } else if(xmlnode_get_child(query, "password")) {
+ if(js->gsc == NULL && !gaim_account_get_bool(js->gc->account,
+ "auth_plain_in_clear", FALSE)) {
+ gaim_request_yes_no(js->gc, _("Plaintext Authentication"),
+ _("Plaintext Authentication"),
+ _("This server requires plaintext authentication over an unencrypted connection. Allow this and continue authentication?"),
+ 2, js->gc->account, allow_plaintext_auth,
+ disallow_plaintext_auth);
+ return;
+ }
+ finish_plaintext_authentication(js);
+ } else {
+ gaim_connection_error(js->gc,
+ _("Server does not use any supported authentication method"));
+ return;
+ }
+ }
+}
+
+void jabber_auth_start_old(JabberStream *js)
+{
+ JabberIq *iq;
+ xmlnode *query, *username;
+
+ iq = jabber_iq_new_query(js, JABBER_IQ_GET, "jabber:iq:auth");
+
+ query = xmlnode_get_child(iq->node, "query");
+ username = xmlnode_new_child(query, "username");
+ xmlnode_insert_data(username, js->user->node, -1);
+
+ jabber_iq_set_callback(iq, auth_old_cb, NULL);
+
+ jabber_iq_send(iq);
+}
+
+static GHashTable* parse_challenge(const char *challenge)
+{
+ GHashTable *ret = g_hash_table_new_full(g_str_hash, g_str_equal,
+ g_free, g_free);
+ char **pairs;
+ int i;
+
+ pairs = g_strsplit(challenge, ",", -1);
+
+ for(i=0; pairs[i]; i++) {
+ char **keyval = g_strsplit(pairs[i], "=", 2);
+ if(keyval[0] && keyval[1]) {
+ if(keyval[1][0] == '"' && keyval[1][strlen(keyval[1])-1] == '"')
+ g_hash_table_replace(ret, g_strdup(keyval[0]), g_strndup(keyval[1]+1, strlen(keyval[1])-2));
+ else
+ g_hash_table_replace(ret, g_strdup(keyval[0]), g_strdup(keyval[1]));
+ }
+ g_strfreev(keyval);
+ }
+
+ g_strfreev(pairs);
+
+ return ret;
+}
+
+static char *
+generate_response_value(JabberID *jid, const char *passwd, const char *nonce,
+ const char *cnonce, const char *a2, const char *realm)
+{
+ GaimCipher *cipher;
+ GaimCipherContext *context;
+ guchar result[16];
+ size_t a1len;
+
+ gchar *a1, *convnode=NULL, *convpasswd = NULL, *ha1, *ha2, *kd, *x, *z;
+
+ if((convnode = g_convert(jid->node, strlen(jid->node), "iso-8859-1", "utf-8",
+ NULL, NULL, NULL)) == NULL) {
+ convnode = g_strdup(jid->node);
+ }
+ if(passwd && ((convpasswd = g_convert(passwd, strlen(passwd), "iso-8859-1",
+ "utf-8", NULL, NULL, NULL)) == NULL)) {
+ convpasswd = g_strdup(passwd);
+ }
+
+ cipher = gaim_ciphers_find_cipher("md5");
+ context = gaim_cipher_context_new(cipher, NULL);
+
+ x = g_strdup_printf("%s:%s:%s", convnode, realm, convpasswd ? convpasswd : "");
+ gaim_cipher_context_append(context, (const guchar *)x, strlen(x));
+ gaim_cipher_context_digest(context, sizeof(result), result, NULL);
+
+ a1 = g_strdup_printf("xxxxxxxxxxxxxxxx:%s:%s", nonce, cnonce);
+ a1len = strlen(a1);
+ g_memmove(a1, result, 16);
+
+ gaim_cipher_context_reset(context, NULL);
+ gaim_cipher_context_append(context, (const guchar *)a1, a1len);
+ gaim_cipher_context_digest(context, sizeof(result), result, NULL);
+
+ ha1 = gaim_base16_encode(result, 16);
+
+ gaim_cipher_context_reset(context, NULL);
+ gaim_cipher_context_append(context, (const guchar *)a2, strlen(a2));
+ gaim_cipher_context_digest(context, sizeof(result), result, NULL);
+
+ ha2 = gaim_base16_encode(result, 16);
+
+ kd = g_strdup_printf("%s:%s:00000001:%s:auth:%s", ha1, nonce, cnonce, ha2);
+
+ gaim_cipher_context_reset(context, NULL);
+ gaim_cipher_context_append(context, (const guchar *)kd, strlen(kd));
+ gaim_cipher_context_digest(context, sizeof(result), result, NULL);
+ gaim_cipher_context_destroy(context);
+
+ z = gaim_base16_encode(result, 16);
+
+ g_free(convnode);
+ g_free(convpasswd);
+ g_free(x);
+ g_free(a1);
+ g_free(ha1);
+ g_free(ha2);
+ g_free(kd);
+
+ return z;
+}
+
+void
+jabber_auth_handle_challenge(JabberStream *js, xmlnode *packet)
+{
+
+ if(js->auth_type == JABBER_AUTH_DIGEST_MD5) {
+ char *enc_in = xmlnode_get_data(packet);
+ char *dec_in;
+ char *enc_out;
+ GHashTable *parts;
+
+ if(!enc_in) {
+ gaim_connection_error(js->gc, _("Invalid response from server."));
+ return;
+ }
+
+ dec_in = (char *)gaim_base64_decode(enc_in, NULL);
+ gaim_debug(GAIM_DEBUG_MISC, "jabber", "decoded challenge (%d): %s\n",
+ strlen(dec_in), dec_in);
+
+ parts = parse_challenge(dec_in);
+
+
+ if (g_hash_table_lookup(parts, "rspauth")) {
+ char *rspauth = g_hash_table_lookup(parts, "rspauth");
+
+
+ if(rspauth && js->expected_rspauth &&
+ !strcmp(rspauth, js->expected_rspauth)) {
+ jabber_send_raw(js,
+ "<response xmlns='urn:ietf:params:xml:ns:xmpp-sasl' />",
+ -1);
+ } else {
+ gaim_connection_error(js->gc, _("Invalid challenge from server"));
+ }
+ g_free(js->expected_rspauth);
+ } else {
+ /* assemble a response, and send it */
+ /* see RFC 2831 */
+ GString *response = g_string_new("");
+ char *a2;
+ char *auth_resp;
+ char *buf;
+ char *cnonce;
+ char *realm;
+ char *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;
+
+ cnonce = g_strdup_printf("%x%u%x", g_random_int(), (int)time(NULL),
+ g_random_int());
+ nonce = g_hash_table_lookup(parts, "nonce");
+
+
+ a2 = g_strdup_printf("AUTHENTICATE:xmpp/%s", realm);
+ auth_resp = generate_response_value(js->user,
+ gaim_connection_get_password(js->gc), nonce, cnonce, a2, realm);
+ g_free(a2);
+
+ a2 = g_strdup_printf(":xmpp/%s", realm);
+ js->expected_rspauth = generate_response_value(js->user,
+ gaim_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 = gaim_base64_encode((guchar *)response->str, response->len);
+
+ gaim_debug(GAIM_DEBUG_MISC, "jabber", "decoded response (%d): %s\n", response->len, response->str);
+
+ buf = g_strdup_printf("<response xmlns='urn:ietf:params:xml:ns:xmpp-sasl'>%s</response>", enc_out);
+
+ jabber_send_raw(js, buf, -1);
+
+ g_free(buf);
+
+ g_free(enc_out);
+
+ g_string_free(response, TRUE);
+ }
+
+ g_free(enc_in);
+ g_free(dec_in);
+ g_hash_table_destroy(parts);
+ }
+#ifdef HAVE_CYRUS_SASL
+ else if (js->auth_type == JABBER_AUTH_CYRUS) {
+ char *enc_in = xmlnode_get_data(packet);
+ unsigned char *dec_in;
+ char *enc_out;
+ const char *c_out;
+ unsigned int clen;
+ gsize declen;
+ xmlnode *response;
+
+ dec_in = gaim_base64_decode(enc_in, &declen);
+
+ js->sasl_state = sasl_client_step(js->sasl, (char*)dec_in, declen,
+ NULL, &c_out, &clen);
+ g_free(enc_in);
+ g_free(dec_in);
+ if (js->sasl_state != SASL_CONTINUE && js->sasl_state != SASL_OK) {
+ gaim_debug_error("jabber", "Error is %d : %s\n",js->sasl_state,sasl_errdetail(js->sasl));
+ gaim_connection_error(js->gc, _("SASL error"));
+ return;
+ } else {
+ response = xmlnode_new("response");
+ xmlnode_set_namespace(response, "urn:ietf:params:xml:ns:xmpp-sasl");
+ if (c_out) {
+ enc_out = gaim_base64_encode((unsigned char*)c_out, clen);
+ xmlnode_insert_data(response, enc_out, -1);
+ g_free(enc_out);
+ }
+ jabber_send(js, response);
+ xmlnode_free(response);
+ }
+ }
+#endif
+}
+
+void jabber_auth_handle_success(JabberStream *js, xmlnode *packet)
+{
+ const char *ns = xmlnode_get_namespace(packet);
+#ifdef HAVE_CYRUS_SASL
+ const int *x;
+#endif
+
+ if(!ns || strcmp(ns, "urn:ietf:params:xml:ns:xmpp-sasl")) {
+ gaim_connection_error(js->gc, _("Invalid response from server."));
+ return;
+ }
+
+#ifdef HAVE_CYRUS_SASL
+ /* The SASL docs say that if the client hasn't returned OK yet, we
+ * should try one more round against it
+ */
+ if (js->sasl_state != SASL_OK) {
+ char *enc_in = xmlnode_get_data(packet);
+ unsigned char *dec_in = NULL;
+ const char *c_out;
+ unsigned int clen;
+ gsize declen = 0;
+
+ if(enc_in != NULL)
+ dec_in = gaim_base64_decode(enc_in, &declen);
+
+ js->sasl_state = sasl_client_step(js->sasl, (char*)dec_in, declen, NULL, &c_out, &clen);
+
+ g_free(enc_in);
+ g_free(dec_in);
+
+ if (js->sasl_state != SASL_OK) {
+ /* This should never happen! */
+ gaim_connection_error(js->gc, _("Invalid response from server."));
+ }
+ }
+ /* If we've negotiated a security layer, we need to enable it */
+ sasl_getprop(js->sasl, SASL_SSF, &x);
+ if (*x > 0) {
+ sasl_getprop(js->sasl, SASL_MAXOUTBUF, &x);
+ js->sasl_maxbuf = *x;
+ }
+#endif
+
+ jabber_stream_set_state(js, JABBER_STREAM_REINITIALIZING);
+}
+
+void jabber_auth_handle_failure(JabberStream *js, xmlnode *packet)
+{
+ char *msg = jabber_parse_error(js, packet);
+
+ if(!msg) {
+ gaim_connection_error(js->gc, _("Invalid response from server."));
+ } else {
+ gaim_connection_error(js->gc, msg);
+ g_free(msg);
+ }
+}
diff --git a/libpurple/protocols/jabber/auth.h b/libpurple/protocols/jabber/auth.h
new file mode 100644
index 0000000000..5daf9ae6de
--- /dev/null
+++ b/libpurple/protocols/jabber/auth.h
@@ -0,0 +1,35 @@
+/**
+ * @file auth.h Authentication routines
+ *
+ * gaim
+ *
+ * Copyright (C) 2003 Nathan Walp <faceprint@faceprint.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+#ifndef _GAIM_JABBER_AUTH_H_
+#define _GAIM_JABBER_AUTH_H_
+
+#include "jabber.h"
+#include "xmlnode.h"
+
+gboolean jabber_process_starttls(JabberStream *js, xmlnode *packet);
+void jabber_auth_start(JabberStream *js, xmlnode *packet);
+void jabber_auth_start_old(JabberStream *js);
+void jabber_auth_handle_challenge(JabberStream *js, xmlnode *packet);
+void jabber_auth_handle_success(JabberStream *js, xmlnode *packet);
+void jabber_auth_handle_failure(JabberStream *js, xmlnode *packet);
+
+#endif /* _GAIM_JABBER_AUTH_H_ */
diff --git a/libpurple/protocols/jabber/buddy.c b/libpurple/protocols/jabber/buddy.c
new file mode 100644
index 0000000000..d8a5c1fa17
--- /dev/null
+++ b/libpurple/protocols/jabber/buddy.c
@@ -0,0 +1,1799 @@
+/*
+ * gaim - Jabber Protocol Plugin
+ *
+ * Copyright (C) 2003, Nathan Walp <faceprint@faceprint.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ */
+#include "internal.h"
+#include "cipher.h"
+#include "debug.h"
+#include "imgstore.h"
+#include "prpl.h"
+#include "notify.h"
+#include "request.h"
+#include "util.h"
+#include "xmlnode.h"
+
+#include "buddy.h"
+#include "chat.h"
+#include "jabber.h"
+#include "iq.h"
+#include "presence.h"
+#include "xdata.h"
+
+typedef struct {
+ long idle_seconds;
+} JabberBuddyInfoResource;
+
+typedef struct {
+ JabberStream *js;
+ JabberBuddy *jb;
+ char *jid;
+ GSList *ids;
+ GHashTable *resources;
+ int timeout_handle;
+ char *vcard_text;
+ GSList *vcard_imgids;
+} JabberBuddyInfo;
+
+void jabber_buddy_free(JabberBuddy *jb)
+{
+ g_return_if_fail(jb != NULL);
+
+ if(jb->error_msg)
+ g_free(jb->error_msg);
+ while(jb->resources)
+ jabber_buddy_resource_free(jb->resources->data);
+
+ g_free(jb);
+}
+
+JabberBuddy *jabber_buddy_find(JabberStream *js, const char *name,
+ gboolean create)
+{
+ JabberBuddy *jb;
+ const char *realname;
+
+ if (js->buddies == NULL)
+ return NULL;
+
+ if(!(realname = jabber_normalize(js->gc->account, 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, g_strdup(realname), jb);
+ }
+
+ return jb;
+}
+
+
+JabberBuddyResource *jabber_buddy_find_resource(JabberBuddy *jb,
+ const char *resource)
+{
+ JabberBuddyResource *jbr = NULL;
+ GList *l;
+
+ if(!jb)
+ return NULL;
+
+ for(l = jb->resources; l; l = l->next)
+ {
+ if(!jbr && !resource) {
+ jbr = l->data;
+ } else if(!resource) {
+ if(((JabberBuddyResource *)l->data)->priority >= jbr->priority)
+ jbr = l->data;
+ } else if(((JabberBuddyResource *)l->data)->name) {
+ if(!strcmp(((JabberBuddyResource *)l->data)->name, resource)) {
+ jbr = l->data;
+ break;
+ }
+ }
+ }
+
+ return jbr;
+}
+
+JabberBuddyResource *jabber_buddy_track_resource(JabberBuddy *jb, const char *resource,
+ int priority, JabberBuddyState state, const char *status)
+{
+ JabberBuddyResource *jbr = jabber_buddy_find_resource(jb, resource);
+
+ if(!jbr) {
+ jbr = g_new0(JabberBuddyResource, 1);
+ jbr->jb = jb;
+ jbr->name = g_strdup(resource);
+ jbr->capabilities = JABBER_CAP_XHTML;
+ jb->resources = g_list_append(jb->resources, jbr);
+ }
+ jbr->priority = priority;
+ jbr->state = state;
+ if(jbr->status)
+ g_free(jbr->status);
+ if (status)
+ jbr->status = g_markup_escape_text(status, -1);
+ else
+ jbr->status = NULL;
+
+ return jbr;
+}
+
+void jabber_buddy_resource_free(JabberBuddyResource *jbr)
+{
+ g_return_if_fail(jbr != NULL);
+
+ jbr->jb->resources = g_list_remove(jbr->jb->resources, jbr);
+
+ 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_free(jbr);
+}
+
+void jabber_buddy_remove_resource(JabberBuddy *jb, const char *resource)
+{
+ JabberBuddyResource *jbr = jabber_buddy_find_resource(jb, resource);
+
+ if(!jbr)
+ return;
+
+ jabber_buddy_resource_free(jbr);
+}
+
+const char *jabber_buddy_get_status_msg(JabberBuddy *jb)
+{
+ JabberBuddyResource *jbr;
+
+ if(!jb)
+ return NULL;
+
+ jbr = jabber_buddy_find_resource(jb, NULL);
+
+ if(!jbr)
+ return NULL;
+
+ return jbr->status;
+}
+
+/*******
+ * 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 *text; /* entry text pointer */
+ int visible; /* should entry field be "visible?" */
+ int editable; /* should entry field be editable? */
+ char *tag; /* tag text */
+ char *ptag; /* parent tag "path" text */
+ char *url; /* vCard display format if URL */
+} vcard_template_data[] = {
+ {N_("Full Name"), NULL, TRUE, TRUE, "FN", NULL, NULL},
+ {N_("Family Name"), NULL, TRUE, TRUE, "FAMILY", "N", NULL},
+ {N_("Given Name"), NULL, TRUE, TRUE, "GIVEN", "N", NULL},
+ {N_("Nickname"), NULL, TRUE, TRUE, "NICKNAME", NULL, NULL},
+ {N_("URL"), NULL, TRUE, TRUE, "URL", NULL, "<A HREF=\"%s\">%s</A>"},
+ {N_("Street Address"), NULL, TRUE, TRUE, "STREET", "ADR", NULL},
+ {N_("Extended Address"), NULL, TRUE, TRUE, "EXTADD", "ADR", NULL},
+ {N_("Locality"), NULL, TRUE, TRUE, "LOCALITY", "ADR", NULL},
+ {N_("Region"), NULL, TRUE, TRUE, "REGION", "ADR", NULL},
+ {N_("Postal Code"), NULL, TRUE, TRUE, "PCODE", "ADR", NULL},
+ {N_("Country"), NULL, TRUE, TRUE, "CTRY", "ADR", NULL},
+ {N_("Telephone"), NULL, TRUE, TRUE, "NUMBER", "TEL", NULL},
+ {N_("E-Mail"), NULL, TRUE, TRUE, "USERID", "EMAIL", "<A HREF=\"mailto:%s\">%s</A>"},
+ {N_("Organization Name"), NULL, TRUE, TRUE, "ORGNAME", "ORG", NULL},
+ {N_("Organization Unit"), NULL, TRUE, TRUE, "ORGUNIT", "ORG", NULL},
+ {N_("Title"), NULL, TRUE, TRUE, "TITLE", NULL, NULL},
+ {N_("Role"), NULL, TRUE, TRUE, "ROLE", NULL, NULL},
+ {N_("Birthday"), NULL, TRUE, TRUE, "BDAY", NULL, NULL},
+ {N_("Description"), NULL, TRUE, TRUE, "DESC", NULL, NULL},
+ {"", NULL, TRUE, TRUE, "N", NULL, NULL},
+ {"", NULL, TRUE, TRUE, "ADR", NULL, NULL},
+ {"", NULL, TRUE, TRUE, "ORG", NULL, NULL},
+ {NULL, NULL, 0, 0, NULL, NULL, NULL}
+};
+
+/*
+ * The "vCard" tag's attribute list...
+ */
+struct tag_attr {
+ char *attr;
+ char *value;
+} 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 xmlnode 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 xmlnode *insert_tag_to_parent_tag(xmlnode *start, const char *parent_tag, const char *new_tag)
+{
+ xmlnode *x = NULL;
+
+ /*
+ * If the parent tag wasn't specified, see if we can get it
+ * from the vCard template struct.
+ */
+ if(parent_tag == NULL) {
+ struct vcard_template *vc_tp = vcard_template_data;
+
+ while(vc_tp->label != NULL) {
+ if(strcmp(vc_tp->tag, new_tag) == 0) {
+ 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 = 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 = 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!
+ */
+ xmlnode *y;
+ if((y = xmlnode_get_child(x, new_tag)) != NULL) {
+ return(y);
+ }
+ }
+ }
+
+ /*
+ * insert the new tag into its parent node
+ */
+ return(xmlnode_new_child((x == NULL? start : x), new_tag));
+}
+
+/*
+ * Send vCard info to Jabber server
+ */
+void jabber_set_info(GaimConnection *gc, const char *info)
+{
+ JabberIq *iq;
+ JabberStream *js = gc->proto_data;
+ xmlnode *vc_node;
+ char *avatar_file = NULL;
+ struct tag_attr *tag_attr;
+
+ g_free(js->avatar_hash);
+ js->avatar_hash = NULL;
+
+ /*
+ * Send only if there's actually any *information* to send
+ */
+ vc_node = info ? xmlnode_from_str(info, -1) : NULL;
+ avatar_file = gaim_buddy_icons_get_full_path(gaim_account_get_buddy_icon(gc->account));
+
+ if(!vc_node) {
+ vc_node = xmlnode_new("vCard");
+ for(tag_attr = vcard_tag_attr_list; tag_attr->attr != NULL; ++tag_attr)
+ xmlnode_set_attrib(vc_node, tag_attr->attr, tag_attr->value);
+ }
+
+ if (vc_node->name &&
+ !g_ascii_strncasecmp(vc_node->name, "vCard", 5)) {
+ GError *error = NULL;
+ gchar *avatar_data_tmp;
+ guchar *avatar_data;
+ gsize avatar_len;
+
+ if(avatar_file && g_file_get_contents(avatar_file, &avatar_data_tmp, &avatar_len, &error)) {
+ xmlnode *photo, *binval;
+ gchar *enc;
+ int i;
+ unsigned char hashval[20];
+ char *p, hash[41];
+
+ avatar_data = (guchar *) avatar_data_tmp;
+ photo = xmlnode_new_child(vc_node, "PHOTO");
+ binval = xmlnode_new_child(photo, "BINVAL");
+ enc = gaim_base64_encode(avatar_data, avatar_len);
+
+ gaim_cipher_digest_region("sha1", (guchar *)avatar_data,
+ avatar_len, sizeof(hashval),
+ hashval, NULL);
+
+ p = hash;
+ for(i=0; i<20; i++, p+=2)
+ snprintf(p, 3, "%02x", hashval[i]);
+ js->avatar_hash = g_strdup(hash);
+
+ xmlnode_insert_data(binval, enc, -1);
+ g_free(enc);
+ g_free(avatar_data);
+ } else if (error != NULL) {
+ g_error_free(error);
+ }
+ g_free(avatar_file);
+
+ iq = jabber_iq_new(js, JABBER_IQ_SET);
+ xmlnode_insert_child(iq->node, vc_node);
+ jabber_iq_send(iq);
+ } else {
+ xmlnode_free(vc_node);
+ }
+}
+
+void jabber_set_buddy_icon(GaimConnection *gc, const char *iconfile)
+{
+ GaimPresence *gpresence;
+ GaimStatus *status;
+
+ jabber_set_info(gc, gaim_account_get_user_info(gc->account));
+
+ gpresence = gaim_account_get_presence(gc->account);
+ status = gaim_presence_get_active_status(gpresence);
+ jabber_presence_send(gc->account, status);
+}
+
+/*
+ * This is the callback from the "ok clicked" for "set vCard"
+ *
+ * Formats GSList data into XML-encoded string and returns a pointer
+ * to said string.
+ *
+ * g_free()'ing the returned string space is the responsibility of
+ * the caller.
+ */
+static void
+jabber_format_info(GaimConnection *gc, GaimRequestFields *fields)
+{
+ xmlnode *vc_node;
+ GaimRequestField *field;
+ const char *text;
+ char *p;
+ const struct vcard_template *vc_tp;
+ struct tag_attr *tag_attr;
+
+ vc_node = xmlnode_new("vCard");
+
+ for(tag_attr = vcard_tag_attr_list; tag_attr->attr != NULL; ++tag_attr)
+ 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;
+
+ field = gaim_request_fields_get_field(fields, vc_tp->tag);
+ text = gaim_request_field_string_get_value(field);
+
+
+ if (text != NULL && *text != '\0') {
+ xmlnode *xp;
+
+ gaim_debug(GAIM_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) {
+
+ xmlnode_insert_data(xp, text, -1);
+ }
+ }
+ }
+
+ p = xmlnode_to_str(vc_node, NULL);
+ xmlnode_free(vc_node);
+
+ gaim_account_set_user_info(gaim_connection_get_account(gc), p);
+ serv_set_info(gc, p);
+
+ g_free(p);
+}
+
+/*
+ * This gets executed by the proto action
+ *
+ * Creates a new GaimRequestFields 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(GaimPluginAction *action)
+{
+ GaimConnection *gc = (GaimConnection *) action->context;
+ GaimRequestFields *fields;
+ GaimRequestFieldGroup *group;
+ GaimRequestField *field;
+ const struct vcard_template *vc_tp;
+ const char *user_info;
+ char *cdata = NULL;
+ xmlnode *x_vc_data = NULL;
+
+ fields = gaim_request_fields_new();
+ group = gaim_request_field_group_new(NULL);
+ gaim_request_fields_add_group(fields, group);
+
+ /*
+ * Get existing, XML-formatted, user info
+ */
+ if((user_info = gaim_account_get_user_info(gc->account)) != NULL)
+ x_vc_data = 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) {
+ xmlnode *data_node;
+ if((vc_tp->label)[0] == '\0')
+ continue;
+
+ if (x_vc_data != NULL) {
+ if(vc_tp->ptag == NULL) {
+ data_node = 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 = xmlnode_get_child(x_vc_data, tag);
+ g_free(tag);
+ }
+ if(data_node)
+ cdata = xmlnode_get_data(data_node);
+ }
+
+ if(strcmp(vc_tp->tag, "DESC") == 0) {
+ field = gaim_request_field_string_new(vc_tp->tag,
+ _(vc_tp->label), cdata,
+ TRUE);
+ } else {
+ field = gaim_request_field_string_new(vc_tp->tag,
+ _(vc_tp->label), cdata,
+ FALSE);
+ }
+
+ g_free(cdata);
+ cdata = NULL;
+
+ gaim_request_field_group_add_field(group, field);
+ }
+
+ if(x_vc_data != NULL)
+ xmlnode_free(x_vc_data);
+
+ gaim_request_fields(gc, _("Edit Jabber vCard"),
+ _("Edit Jabber vCard"),
+ _("All items below are optional. Enter only the "
+ "information with which you feel comfortable."),
+ fields,
+ _("Save"), G_CALLBACK(jabber_format_info),
+ _("Cancel"), NULL,
+ gc);
+}
+
+/*---------------------------------------*/
+/* 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() */
+ if (jbi->timeout_handle > 0)
+ gaim_timeout_remove(jbi->timeout_handle);
+
+ g_free(jbi->jid);
+ g_hash_table_destroy(jbi->resources);
+ g_free(jbi->vcard_text);
+ g_free(jbi);
+}
+
+static void jabber_buddy_info_show_if_ready(JabberBuddyInfo *jbi)
+{
+ char *resource_name, *tmp;
+ JabberBuddyResource *jbr;
+ JabberBuddyInfoResource *jbir = NULL;
+ GList *resources;
+ GaimNotifyUserInfo *user_info;
+
+ /* not yet */
+ if(jbi->ids)
+ return;
+
+ user_info = gaim_notify_user_info_new();
+ resource_name = jabber_get_resource(jbi->jid);
+
+ if(resource_name) {
+ jbr = jabber_buddy_find_resource(jbi->jb, resource_name);
+ jbir = g_hash_table_lookup(jbi->resources, resource_name);
+ if(jbr) {
+ char *purdy = NULL;
+ if(jbr->status)
+ purdy = gaim_strdup_withhtml(jbr->status);
+ tmp = g_strdup_printf("%s%s%s", jabber_buddy_state_get_name(jbr->state),
+ (purdy ? ": " : ""),
+ (purdy ? purdy : ""));
+ gaim_notify_user_info_add_pair(user_info, _("Status"), tmp);
+ g_free(tmp);
+ g_free(purdy);
+ } else {
+ gaim_notify_user_info_add_pair(user_info, _("Status"), _("Unknown"));
+ }
+ if(jbir) {
+ if(jbir->idle_seconds > 0) {
+ gaim_notify_user_info_add_pair(user_info, _("Idle"), gaim_str_seconds_to_string(jbir->idle_seconds));
+ }
+ }
+ if(jbr && jbr->client.name) {
+ tmp = g_strdup_printf("%s%s%s", jbr->client.name,
+ (jbr->client.version ? " " : ""),
+ (jbr->client.version ? jbr->client.version : ""));
+ gaim_notify_user_info_add_pair(user_info, _("Client"), tmp);
+ g_free(tmp);
+
+ if(jbr->client.os) {
+ gaim_notify_user_info_add_pair(user_info, _("Operating System"), jbr->client.os);
+ }
+ }
+ } else {
+ for(resources = jbi->jb->resources; resources; resources = resources->next) {
+ char *purdy = NULL;
+ jbr = resources->data;
+ if(jbr->status)
+ purdy = gaim_strdup_withhtml(jbr->status);
+ if(jbr->name)
+ gaim_notify_user_info_add_pair(user_info, _("Resource"), jbr->name);
+ tmp = g_strdup_printf("%d", jbr->priority);
+ gaim_notify_user_info_add_pair(user_info, _("Priority"), tmp);
+ g_free(tmp);
+
+ tmp = g_strdup_printf("%s%s%s", jabber_buddy_state_get_name(jbr->state),
+ (purdy ? ": " : ""),
+ (purdy ? purdy : ""));
+ gaim_notify_user_info_add_pair(user_info, _("Status"), tmp);
+ g_free(tmp);
+ g_free(purdy);
+
+ if(jbr->name)
+ jbir = g_hash_table_lookup(jbi->resources, jbr->name);
+
+ if(jbir) {
+ if(jbir->idle_seconds > 0) {
+ gaim_notify_user_info_add_pair(user_info, _("Idle"), gaim_str_seconds_to_string(jbir->idle_seconds));
+ }
+ }
+ if(jbr && jbr->client.name) {
+ tmp = g_strdup_printf("%s%s%s", jbr->client.name,
+ (jbr->client.version ? " " : ""),
+ (jbr->client.version ? jbr->client.version : ""));
+ gaim_notify_user_info_add_pair(user_info,
+ _("Client"), tmp);
+ g_free(tmp);
+
+ if(jbr->client.os) {
+ gaim_notify_user_info_add_pair(user_info, _("Operating System"), jbr->client.os);
+ }
+ }
+ }
+ }
+
+ g_free(resource_name);
+
+ if (jbi->vcard_text != NULL) {
+ gaim_notify_user_info_add_section_break(user_info);
+ /* Should this have some sort of label? */
+ gaim_notify_user_info_add_pair(user_info, NULL, jbi->vcard_text);
+ }
+
+ gaim_notify_userinfo(jbi->js->gc, jbi->jid, user_info, NULL, NULL);
+ gaim_notify_user_info_destroy(user_info);
+
+ while(jbi->vcard_imgids) {
+ gaim_imgstore_unref(GPOINTER_TO_INT(jbi->vcard_imgids->data));
+ jbi->vcard_imgids = g_slist_delete_link(jbi->vcard_imgids, jbi->vcard_imgids);
+ }
+
+ 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 = jbi->ids;
+
+ if(!id)
+ return;
+
+ while(l) {
+ if(!strcmp(id, l->data)) {
+ jbi->ids = g_slist_remove(jbi->ids, l->data);
+ return;
+ }
+ l = l->next;
+ }
+}
+
+static void jabber_vcard_parse(JabberStream *js, xmlnode *packet, gpointer data)
+{
+ const char *id, *from;
+ GString *info_text;
+ char *bare_jid;
+ char *text;
+ xmlnode *vcard;
+ GaimBuddy *b;
+ JabberBuddyInfo *jbi = data;
+
+ from = xmlnode_get_attrib(packet, "from");
+ id = xmlnode_get_attrib(packet, "id");
+
+ if(!jbi)
+ return;
+
+ jabber_buddy_info_remove_id(jbi, id);
+
+ if(!from)
+ return;
+
+ if(!jabber_buddy_find(js, from, FALSE))
+ return;
+
+ /* XXX: handle the error case */
+
+ bare_jid = jabber_get_bare_jid(from);
+
+ b = gaim_find_buddy(js->gc->account, bare_jid);
+
+ info_text = g_string_new("");
+
+ if((vcard = xmlnode_get_child(packet, "vCard")) ||
+ (vcard = xmlnode_get_child_with_namespace(packet, "query", "vcard-temp"))) {
+ xmlnode *child;
+ for(child = vcard->child; child; child = child->next)
+ {
+ xmlnode *child2;
+
+ if(child->type != XMLNODE_TYPE_TAG)
+ continue;
+
+ text = xmlnode_get_data(child);
+ if(text && !strcmp(child->name, "FN")) {
+ g_string_append_printf(info_text, "<b>%s:</b> %s<br/>",
+ _("Full Name"), text);
+ } else if(!strcmp(child->name, "N")) {
+ for(child2 = child->child; child2; child2 = child2->next)
+ {
+ char *text2;
+
+ if(child2->type != XMLNODE_TYPE_TAG)
+ continue;
+
+ text2 = xmlnode_get_data(child2);
+ if(text2 && !strcmp(child2->name, "FAMILY")) {
+ g_string_append_printf(info_text,
+ "<b>%s:</b> %s<br/>",
+ _("Family Name"), text2);
+ } else if(text2 && !strcmp(child2->name, "GIVEN")) {
+ g_string_append_printf(info_text,
+ "<b>%s:</b> %s<br/>",
+ _("Given Name"), text2);
+ } else if(text2 && !strcmp(child2->name, "MIDDLE")) {
+ g_string_append_printf(info_text,
+ "<b>%s:</b> %s<br/>",
+ _("Middle Name"), text2);
+ }
+ g_free(text2);
+ }
+ } else if(text && !strcmp(child->name, "NICKNAME")) {
+ serv_got_alias(js->gc, from, text);
+ if(b) {
+ gaim_blist_node_set_string((GaimBlistNode*)b, "servernick", text);
+ }
+ g_string_append_printf(info_text, "<b>%s:</b> %s<br/>",
+ _("Nickname"), text);
+ } else if(text && !strcmp(child->name, "BDAY")) {
+ g_string_append_printf(info_text, "<b>%s:</b> %s<br/>",
+ _("Birthday"), text);
+ } else if(!strcmp(child->name, "ADR")) {
+ gboolean address_line_added = FALSE;
+
+ for(child2 = child->child; child2; child2 = child2->next)
+ {
+ char *text2;
+
+ if(child2->type != XMLNODE_TYPE_TAG)
+ continue;
+
+ text2 = 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)
+ {
+ g_string_append_printf(info_text, "<b>%s:</b><br/>",
+ _("Address"));
+ address_line_added = TRUE;
+ }
+
+ if(!strcmp(child2->name, "POBOX")) {
+ g_string_append_printf(info_text,
+ "&nbsp;<b>%s:</b> %s<br/>",
+ _("P.O. Box"), text2);
+ } else if(!strcmp(child2->name, "EXTADR")) {
+ g_string_append_printf(info_text,
+ "&nbsp;<b>%s:</b> %s<br/>",
+ _("Extended Address"), text2);
+ } else if(!strcmp(child2->name, "STREET")) {
+ g_string_append_printf(info_text,
+ "&nbsp;<b>%s:</b> %s<br/>",
+ _("Street Address"), text2);
+ } else if(!strcmp(child2->name, "LOCALITY")) {
+ g_string_append_printf(info_text,
+ "&nbsp;<b>%s:</b> %s<br/>",
+ _("Locality"), text2);
+ } else if(!strcmp(child2->name, "REGION")) {
+ g_string_append_printf(info_text,
+ "&nbsp;<b>%s:</b> %s<br/>",
+ _("Region"), text2);
+ } else if(!strcmp(child2->name, "PCODE")) {
+ g_string_append_printf(info_text,
+ "&nbsp;<b>%s:</b> %s<br/>",
+ _("Postal Code"), text2);
+ } else if(!strcmp(child2->name, "CTRY")
+ || !strcmp(child2->name, "COUNTRY")) {
+ g_string_append_printf(info_text,
+ "&nbsp;<b>%s:</b> %s<br/>",
+ _("Country"), text2);
+ }
+ g_free(text2);
+ }
+ } else if(!strcmp(child->name, "TEL")) {
+ char *number;
+ if((child2 = xmlnode_get_child(child, "NUMBER"))) {
+ /* show what kind of number it is */
+ number = xmlnode_get_data(child2);
+ if(number) {
+ g_string_append_printf(info_text,
+ "<b>%s:</b> %s<br/>", _("Telephone"), number);
+ g_free(number);
+ }
+ } else if((number = xmlnode_get_data(child))) {
+ /* lots of clients (including gaim) do this, but it's
+ * out of spec */
+ g_string_append_printf(info_text,
+ "<b>%s:</b> %s<br/>", _("Telephone"), number);
+ g_free(number);
+ }
+ } else if(!strcmp(child->name, "EMAIL")) {
+ char *userid;
+ if((child2 = xmlnode_get_child(child, "USERID"))) {
+ /* show what kind of email it is */
+ userid = xmlnode_get_data(child2);
+ if(userid) {
+ g_string_append_printf(info_text,
+ "<b>%s:</b> <a href='mailto:%s'>%s</a><br/>",
+ _("E-Mail"), userid, userid);
+ g_free(userid);
+ }
+ } else if((userid = xmlnode_get_data(child))) {
+ /* lots of clients (including gaim) do this, but it's
+ * out of spec */
+ g_string_append_printf(info_text,
+ "<b>%s:</b> <a href='mailto:%s'>%s</a><br/>",
+ _("E-Mail"), userid, userid);
+ g_free(userid);
+ }
+ } else if(!strcmp(child->name, "ORG")) {
+ for(child2 = child->child; child2; child2 = child2->next)
+ {
+ char *text2;
+
+ if(child2->type != XMLNODE_TYPE_TAG)
+ continue;
+
+ text2 = xmlnode_get_data(child2);
+ if(text2 && !strcmp(child2->name, "ORGNAME")) {
+ g_string_append_printf(info_text,
+ "<b>%s:</b> %s<br/>",
+ _("Organization Name"), text2);
+ } else if(text2 && !strcmp(child2->name, "ORGUNIT")) {
+ g_string_append_printf(info_text,
+ "<b>%s:</b> %s<br/>",
+ _("Organization Unit"), text2);
+ }
+ g_free(text2);
+ }
+ } else if(text && !strcmp(child->name, "TITLE")) {
+ g_string_append_printf(info_text, "<b>%s:</b> %s<br/>",
+ _("Title"), text);
+ } else if(text && !strcmp(child->name, "ROLE")) {
+ g_string_append_printf(info_text, "<b>%s:</b> %s<br/>",
+ _("Role"), text);
+ } else if(text && !strcmp(child->name, "DESC")) {
+ g_string_append_printf(info_text, "<b>%s:</b> %s<br/>",
+ _("Description"), text);
+ } else if(!strcmp(child->name, "PHOTO") ||
+ !strcmp(child->name, "LOGO")) {
+ char *bintext = NULL;
+ xmlnode *binval;
+
+ if( ((binval = xmlnode_get_child(child, "BINVAL")) &&
+ (bintext = xmlnode_get_data(binval))) ||
+ (bintext = xmlnode_get_data(child))) {
+ gsize size;
+ guchar *data;
+ int i;
+ unsigned char hashval[20];
+ char *p, hash[41];
+ gboolean photo = (strcmp(child->name, "PHOTO") == 0);
+
+ data = gaim_base64_decode(bintext, &size);
+
+ jbi->vcard_imgids = g_slist_prepend(jbi->vcard_imgids, GINT_TO_POINTER(gaim_imgstore_add(data, size, "logo.png")));
+ g_string_append_printf(info_text,
+ "<b>%s:</b> <img id='%d'><br/>",
+ photo ? _("Photo") : _("Logo"),
+ GPOINTER_TO_INT(jbi->vcard_imgids->data));
+
+ gaim_buddy_icons_set_for_user(js->gc->account, bare_jid,
+ data, size);
+
+ gaim_cipher_digest_region("sha1", (guchar *)data, size,
+ sizeof(hashval), hashval, NULL);
+ p = hash;
+ for(i=0; i<20; i++, p+=2)
+ snprintf(p, 3, "%02x", hashval[i]);
+ gaim_blist_node_set_string((GaimBlistNode*)b, "avatar_hash", hash);
+
+ g_free(data);
+ g_free(bintext);
+ }
+ }
+ g_free(text);
+ }
+ }
+
+ jbi->vcard_text = gaim_strdup_withhtml(info_text->str);
+ g_string_free(info_text, TRUE);
+ 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 void jabber_version_parse(JabberStream *js, xmlnode *packet, gpointer data)
+{
+ JabberBuddyInfo *jbi = data;
+ const char *type, *id, *from;
+ xmlnode *query;
+ char *resource_name;
+
+ g_return_if_fail(jbi != NULL);
+
+ type = xmlnode_get_attrib(packet, "type");
+ id = xmlnode_get_attrib(packet, "id");
+ from = xmlnode_get_attrib(packet, "from");
+
+ jabber_buddy_info_remove_id(jbi, id);
+
+ if(!from)
+ return;
+
+ resource_name = jabber_get_resource(from);
+
+ if(resource_name) {
+ if(type && !strcmp(type, "result")) {
+ if((query = xmlnode_get_child(packet, "query"))) {
+ JabberBuddyResource *jbr = jabber_buddy_find_resource(jbi->jb, resource_name);
+ if(jbr) {
+ xmlnode *node;
+ if((node = xmlnode_get_child(query, "name"))) {
+ jbr->client.name = xmlnode_get_data(node);
+ }
+ if((node = xmlnode_get_child(query, "version"))) {
+ jbr->client.version = xmlnode_get_data(node);
+ }
+ if((node = xmlnode_get_child(query, "os"))) {
+ jbr->client.os = xmlnode_get_data(node);
+ }
+ }
+ }
+ }
+ g_free(resource_name);
+ }
+
+ jabber_buddy_info_show_if_ready(jbi);
+}
+
+static void jabber_last_parse(JabberStream *js, xmlnode *packet, gpointer data)
+{
+ JabberBuddyInfo *jbi = data;
+ xmlnode *query;
+ char *resource_name;
+ const char *type, *id, *from, *seconds;
+
+ g_return_if_fail(jbi != NULL);
+
+ type = xmlnode_get_attrib(packet, "type");
+ id = xmlnode_get_attrib(packet, "id");
+ from = xmlnode_get_attrib(packet, "from");
+
+ jabber_buddy_info_remove_id(jbi, id);
+
+ if(!from)
+ return;
+
+ resource_name = jabber_get_resource(from);
+
+ if(resource_name) {
+ if(type && !strcmp(type, "result")) {
+ if((query = xmlnode_get_child(packet, "query"))) {
+ seconds = xmlnode_get_attrib(query, "seconds");
+ if(seconds) {
+ char *end = NULL;
+ long sec = strtol(seconds, &end, 10);
+ if(end != seconds) {
+ JabberBuddyInfoResource *jbir = g_hash_table_lookup(jbi->resources, resource_name);
+ if(jbir) {
+ jbir->idle_seconds = sec;
+ }
+ }
+ }
+ }
+ }
+ g_free(resource_name);
+ }
+
+ jabber_buddy_info_show_if_ready(jbi);
+}
+
+void jabber_buddy_remove_all_pending_buddy_info_requests(JabberStream *js)
+{
+ if (js->pending_buddy_info_requests)
+ {
+ JabberBuddyInfo *jbi;
+ GSList *l = js->pending_buddy_info_requests;
+ while (l) {
+ jbi = l->data;
+
+ g_slist_free(jbi->ids);
+ jabber_buddy_info_destroy(jbi);
+
+ l = l->next;
+ }
+
+ g_slist_free(js->pending_buddy_info_requests);
+ js->pending_buddy_info_requests = NULL;
+ }
+}
+
+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_remove(jbi->ids, id);
+ 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 void jabber_buddy_get_info_for_jid(JabberStream *js, const char *jid)
+{
+ JabberIq *iq;
+ xmlnode *vcard;
+ GList *resources;
+ JabberBuddy *jb;
+ JabberBuddyInfo *jbi;
+
+ jb = jabber_buddy_find(js, jid, TRUE);
+
+ /* invalid JID */
+ if(!jb)
+ return;
+
+ jbi = g_new0(JabberBuddyInfo, 1);
+ jbi->jid = g_strdup(jid);
+ jbi->js = js;
+ jbi->jb = jb;
+ jbi->resources = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, jabber_buddy_info_resource_free);
+
+ iq = jabber_iq_new(js, JABBER_IQ_GET);
+
+ xmlnode_set_attrib(iq->node, "to", jid);
+ vcard = xmlnode_new_child(iq->node, "vCard");
+ 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);
+
+ for(resources = jb->resources; resources; resources = resources->next)
+ {
+ JabberBuddyResource *jbr = resources->data;
+ JabberBuddyInfoResource *jbir;
+ char *full_jid;
+
+ if ((strchr(jid, '/') == NULL) && (jbr->name != NULL)) {
+ full_jid = g_strdup_printf("%s/%s", jid, jbr->name);
+ } else {
+ full_jid = g_strdup(jid);
+ }
+
+ if (jbr->name != NULL)
+ {
+ 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");
+ xmlnode_set_attrib(iq->node, "to", full_jid);
+ jabber_iq_set_callback(iq, jabber_version_parse, jbi);
+ jbi->ids = g_slist_prepend(jbi->ids, g_strdup(iq->id));
+ jabber_iq_send(iq);
+ }
+
+ iq = jabber_iq_new_query(js, JABBER_IQ_GET, "jabber:iq:last");
+ xmlnode_set_attrib(iq->node, "to", full_jid);
+ jabber_iq_set_callback(iq, jabber_last_parse, jbi);
+ jbi->ids = g_slist_prepend(jbi->ids, g_strdup(iq->id));
+ jabber_iq_send(iq);
+
+ g_free(full_jid);
+ }
+
+ js->pending_buddy_info_requests = g_slist_prepend(js->pending_buddy_info_requests, jbi);
+ jbi->timeout_handle = gaim_timeout_add(30000, jabber_buddy_get_info_timeout, jbi);
+}
+
+void jabber_buddy_get_info(GaimConnection *gc, const char *who)
+{
+ JabberStream *js = gc->proto_data;
+ char *bare_jid = jabber_get_bare_jid(who);
+
+ if(bare_jid) {
+ jabber_buddy_get_info_for_jid(js, bare_jid);
+ g_free(bare_jid);
+ }
+}
+
+void jabber_buddy_get_info_chat(GaimConnection *gc, int id,
+ const char *resource)
+{
+ JabberStream *js = gc->proto_data;
+ JabberChat *chat = jabber_chat_find_by_id(js, id);
+ char *full_jid;
+
+ if(!chat)
+ return;
+
+ full_jid = g_strdup_printf("%s@%s/%s", chat->room, chat->server, resource);
+ jabber_buddy_get_info_for_jid(js, full_jid);
+ g_free(full_jid);
+}
+
+
+static void jabber_buddy_set_invisibility(JabberStream *js, const char *who,
+ gboolean invisible)
+{
+ GaimPresence *gpresence;
+ GaimAccount *account;
+ GaimStatus *status;
+ JabberBuddy *jb = jabber_buddy_find(js, who, TRUE);
+ xmlnode *presence;
+ JabberBuddyState state;
+ char *msg;
+ int priority;
+
+ account = gaim_connection_get_account(js->gc);
+ gpresence = gaim_account_get_presence(account);
+ status = gaim_presence_get_active_status(gpresence);
+
+ gaim_status_to_jabber(status, &state, &msg, &priority);
+ presence = jabber_presence_create(state, msg, priority);
+
+ g_free(msg);
+
+ xmlnode_set_attrib(presence, "to", who);
+ if(invisible) {
+ xmlnode_set_attrib(presence, "type", "invisible");
+ jb->invisible |= JABBER_INVIS_BUDDY;
+ } else {
+ jb->invisible &= ~JABBER_INVIS_BUDDY;
+ }
+
+ jabber_send(js, presence);
+ xmlnode_free(presence);
+}
+
+static void jabber_buddy_make_invisible(GaimBlistNode *node, gpointer data)
+{
+ GaimBuddy *buddy;
+ GaimConnection *gc;
+ JabberStream *js;
+
+ g_return_if_fail(GAIM_BLIST_NODE_IS_BUDDY(node));
+
+ buddy = (GaimBuddy *) node;
+ gc = gaim_account_get_connection(buddy->account);
+ js = gc->proto_data;
+
+ jabber_buddy_set_invisibility(js, buddy->name, TRUE);
+}
+
+static void jabber_buddy_make_visible(GaimBlistNode *node, gpointer data)
+{
+ GaimBuddy *buddy;
+ GaimConnection *gc;
+ JabberStream *js;
+
+ g_return_if_fail(GAIM_BLIST_NODE_IS_BUDDY(node));
+
+ buddy = (GaimBuddy *) node;
+ gc = gaim_account_get_connection(buddy->account);
+ js = gc->proto_data;
+
+ jabber_buddy_set_invisibility(js, buddy->name, FALSE);
+}
+
+static void jabber_buddy_cancel_presence_notification(GaimBlistNode *node,
+ gpointer data)
+{
+ GaimBuddy *buddy;
+ GaimConnection *gc;
+ JabberStream *js;
+
+ g_return_if_fail(GAIM_BLIST_NODE_IS_BUDDY(node));
+
+ buddy = (GaimBuddy *) node;
+ gc = gaim_account_get_connection(buddy->account);
+ js = gc->proto_data;
+
+ /* I wonder if we should prompt the user before doing this */
+ jabber_presence_subscription_set(js, buddy->name, "unsubscribed");
+}
+
+static void jabber_buddy_rerequest_auth(GaimBlistNode *node, gpointer data)
+{
+ GaimBuddy *buddy;
+ GaimConnection *gc;
+ JabberStream *js;
+
+ g_return_if_fail(GAIM_BLIST_NODE_IS_BUDDY(node));
+
+ buddy = (GaimBuddy *) node;
+ gc = gaim_account_get_connection(buddy->account);
+ js = gc->proto_data;
+
+ jabber_presence_subscription_set(js, buddy->name, "subscribe");
+}
+
+
+static void jabber_buddy_unsubscribe(GaimBlistNode *node, gpointer data)
+{
+ GaimBuddy *buddy;
+ GaimConnection *gc;
+ JabberStream *js;
+
+ g_return_if_fail(GAIM_BLIST_NODE_IS_BUDDY(node));
+
+ buddy = (GaimBuddy *) node;
+ gc = gaim_account_get_connection(buddy->account);
+ js = gc->proto_data;
+
+ jabber_presence_subscription_set(js, buddy->name, "unsubscribe");
+}
+
+
+static GList *jabber_buddy_menu(GaimBuddy *buddy)
+{
+ GaimConnection *gc = gaim_account_get_connection(buddy->account);
+ JabberStream *js = gc->proto_data;
+ JabberBuddy *jb = jabber_buddy_find(js, buddy->name, TRUE);
+
+ GList *m = NULL;
+ GaimMenuAction *act;
+
+ if(!jb)
+ return m;
+
+ /* XXX: fix the NOT ME below */
+
+ if(js->protocol_version == JABBER_PROTO_0_9 /* && NOT ME */) {
+ if(jb->invisible & JABBER_INVIS_BUDDY) {
+ act = gaim_menu_action_new(_("Un-hide From"),
+ GAIM_CALLBACK(jabber_buddy_make_visible),
+ NULL, NULL);
+ } else {
+ act = gaim_menu_action_new(_("Temporarily Hide From"),
+ GAIM_CALLBACK(jabber_buddy_make_invisible),
+ NULL, NULL);
+ }
+ m = g_list_append(m, act);
+ }
+
+ if(jb->subscription & JABBER_SUB_FROM /* && NOT ME */) {
+ act = gaim_menu_action_new(_("Cancel Presence Notification"),
+ GAIM_CALLBACK(jabber_buddy_cancel_presence_notification),
+ NULL, NULL);
+ m = g_list_append(m, act);
+ }
+
+ if(!(jb->subscription & JABBER_SUB_TO)) {
+ act = gaim_menu_action_new(_("(Re-)Request authorization"),
+ GAIM_CALLBACK(jabber_buddy_rerequest_auth),
+ NULL, NULL);
+ m = g_list_append(m, act);
+
+ } else /* if(NOT ME) */{
+
+ /* shouldn't this just happen automatically when the buddy is
+ removed? */
+ act = gaim_menu_action_new(_("Unsubscribe"),
+ GAIM_CALLBACK(jabber_buddy_unsubscribe),
+ NULL, NULL);
+ m = g_list_append(m, act);
+ }
+
+ return m;
+}
+
+GList *
+jabber_blist_node_menu(GaimBlistNode *node)
+{
+ if(GAIM_BLIST_NODE_IS_BUDDY(node)) {
+ return jabber_buddy_menu((GaimBuddy *) node);
+ } else {
+ return NULL;
+ }
+}
+
+
+const char *
+jabber_buddy_state_get_name(JabberBuddyState state)
+{
+ switch(state) {
+ case JABBER_BUDDY_STATE_UNKNOWN:
+ return _("Unknown");
+ case JABBER_BUDDY_STATE_ERROR:
+ return _("Error");
+ case JABBER_BUDDY_STATE_UNAVAILABLE:
+ return _("Offline");
+ case JABBER_BUDDY_STATE_ONLINE:
+ return _("Available");
+ case JABBER_BUDDY_STATE_CHAT:
+ return _("Chatty");
+ case JABBER_BUDDY_STATE_AWAY:
+ return _("Away");
+ case JABBER_BUDDY_STATE_XA:
+ return _("Extended Away");
+ case JABBER_BUDDY_STATE_DND:
+ return _("Do Not Disturb");
+ }
+
+ return _("Unknown");
+}
+
+JabberBuddyState jabber_buddy_status_id_get_state(const char *id) {
+ if(!id)
+ return JABBER_BUDDY_STATE_UNKNOWN;
+ if(!strcmp(id, "available"))
+ return JABBER_BUDDY_STATE_ONLINE;
+ if(!strcmp(id, "freeforchat"))
+ return JABBER_BUDDY_STATE_CHAT;
+ if(!strcmp(id, "away"))
+ return JABBER_BUDDY_STATE_AWAY;
+ if(!strcmp(id, "extended_away"))
+ return JABBER_BUDDY_STATE_XA;
+ if(!strcmp(id, "dnd"))
+ return JABBER_BUDDY_STATE_DND;
+ if(!strcmp(id, "offline"))
+ return JABBER_BUDDY_STATE_UNAVAILABLE;
+ if(!strcmp(id, "error"))
+ return JABBER_BUDDY_STATE_ERROR;
+
+ return JABBER_BUDDY_STATE_UNKNOWN;
+}
+
+JabberBuddyState jabber_buddy_show_get_state(const char *id) {
+ if(!id)
+ return JABBER_BUDDY_STATE_UNKNOWN;
+ if(!strcmp(id, "available"))
+ return JABBER_BUDDY_STATE_ONLINE;
+ if(!strcmp(id, "chat"))
+ return JABBER_BUDDY_STATE_CHAT;
+ if(!strcmp(id, "away"))
+ return JABBER_BUDDY_STATE_AWAY;
+ if(!strcmp(id, "xa"))
+ return JABBER_BUDDY_STATE_XA;
+ if(!strcmp(id, "dnd"))
+ return JABBER_BUDDY_STATE_DND;
+ if(!strcmp(id, "offline"))
+ return JABBER_BUDDY_STATE_UNAVAILABLE;
+ if(!strcmp(id, "error"))
+ return JABBER_BUDDY_STATE_ERROR;
+
+ return JABBER_BUDDY_STATE_UNKNOWN;
+}
+
+const char *jabber_buddy_state_get_show(JabberBuddyState state) {
+ switch(state) {
+ case JABBER_BUDDY_STATE_CHAT:
+ return "chat";
+ case JABBER_BUDDY_STATE_AWAY:
+ return "away";
+ case JABBER_BUDDY_STATE_XA:
+ return "xa";
+ case JABBER_BUDDY_STATE_DND:
+ return "dnd";
+ case JABBER_BUDDY_STATE_ONLINE:
+ return "available";
+ case JABBER_BUDDY_STATE_UNKNOWN:
+ case JABBER_BUDDY_STATE_ERROR:
+ return NULL;
+ case JABBER_BUDDY_STATE_UNAVAILABLE:
+ return "offline";
+ }
+ return NULL;
+}
+
+const char *jabber_buddy_state_get_status_id(JabberBuddyState state) {
+ switch(state) {
+ case JABBER_BUDDY_STATE_CHAT:
+ return "freeforchat";
+ case JABBER_BUDDY_STATE_AWAY:
+ return "away";
+ case JABBER_BUDDY_STATE_XA:
+ return "extended_away";
+ case JABBER_BUDDY_STATE_DND:
+ return "dnd";
+ case JABBER_BUDDY_STATE_ONLINE:
+ return "available";
+ case JABBER_BUDDY_STATE_UNKNOWN:
+ return "available";
+ case JABBER_BUDDY_STATE_ERROR:
+ return "error";
+ case JABBER_BUDDY_STATE_UNAVAILABLE:
+ return "offline";
+ }
+ return NULL;
+}
+
+static void user_search_result_add_buddy_cb(GaimConnection *gc, GList *row, void *user_data)
+{
+ /* XXX find out the jid */
+ gaim_blist_request_add_buddy(gaim_connection_get_account(gc),
+ g_list_nth_data(row, 0), NULL, NULL);
+}
+
+static void user_search_result_cb(JabberStream *js, xmlnode *packet, gpointer data)
+{
+ GaimNotifySearchResults *results;
+ GaimNotifySearchColumn *column;
+ xmlnode *x, *query, *item, *field;
+
+ /* XXX error checking? */
+ if(!(query = xmlnode_get_child(packet, "query")))
+ return;
+
+ results = gaim_notify_searchresults_new();
+ if((x = xmlnode_get_child_with_namespace(query, "x", "jabber:x:data"))) {
+ xmlnode *reported;
+ gaim_debug_info("jabber", "new-skool\n");
+ if((reported = xmlnode_get_child(x, "reported"))) {
+ xmlnode *field = xmlnode_get_child(reported, "field");
+ while(field) {
+ /* XXX keep track of this order, use it below */
+ const char *var = xmlnode_get_attrib(field, "var");
+ const char *label = xmlnode_get_attrib(field, "label");
+ if(var) {
+ column = gaim_notify_searchresults_column_new(label ? label : var);
+ gaim_notify_searchresults_column_add(results, column);
+ }
+ field = xmlnode_get_next_twin(field);
+ }
+ }
+ item = xmlnode_get_child(x, "item");
+ while(item) {
+ GList *row = NULL;
+ field = xmlnode_get_child(item, "field");
+ while(field) {
+ xmlnode *valuenode = xmlnode_get_child(field, "value");
+ if(valuenode) {
+ char *value = xmlnode_get_data(valuenode);
+ row = g_list_append(row, value);
+ }
+ field = xmlnode_get_next_twin(field);
+ }
+ gaim_notify_searchresults_row_add(results, row);
+
+ item = xmlnode_get_next_twin(item);
+ }
+ } else {
+ /* old skool */
+ gaim_debug_info("jabber", "old-skool\n");
+
+ column = gaim_notify_searchresults_column_new(_("JID"));
+ gaim_notify_searchresults_column_add(results, column);
+ column = gaim_notify_searchresults_column_new(_("First Name"));
+ gaim_notify_searchresults_column_add(results, column);
+ column = gaim_notify_searchresults_column_new(_("Last Name"));
+ gaim_notify_searchresults_column_add(results, column);
+ column = gaim_notify_searchresults_column_new(_("Nickname"));
+ gaim_notify_searchresults_column_add(results, column);
+ column = gaim_notify_searchresults_column_new(_("E-Mail"));
+ gaim_notify_searchresults_column_add(results, column);
+
+ for(item = xmlnode_get_child(query, "item"); item; item = xmlnode_get_next_twin(item)) {
+ const char *jid;
+ xmlnode *node;
+ GList *row = NULL;
+
+ if(!(jid = xmlnode_get_attrib(item, "jid")))
+ continue;
+
+ row = g_list_append(row, g_strdup(jid));
+ node = xmlnode_get_child(item, "first");
+ row = g_list_append(row, node ? xmlnode_get_data(node) : NULL);
+ node = xmlnode_get_child(item, "last");
+ row = g_list_append(row, node ? xmlnode_get_data(node) : NULL);
+ node = xmlnode_get_child(item, "nick");
+ row = g_list_append(row, node ? xmlnode_get_data(node) : NULL);
+ node = xmlnode_get_child(item, "email");
+ row = g_list_append(row, node ? xmlnode_get_data(node) : NULL);
+ gaim_debug_info("jabber", "row=%d\n", row);
+ gaim_notify_searchresults_row_add(results, row);
+ }
+ }
+
+ gaim_notify_searchresults_button_add(results, GAIM_NOTIFY_BUTTON_ADD,
+ user_search_result_add_buddy_cb);
+
+ gaim_notify_searchresults(js->gc, NULL, NULL, _("The following are the results of your search"), results, NULL, NULL);
+}
+
+static void user_search_x_data_cb(JabberStream *js, xmlnode *result, gpointer data)
+{
+ xmlnode *query;
+ JabberIq *iq;
+ char *dir_server = data;
+
+ iq = jabber_iq_new_query(js, JABBER_IQ_SET, "jabber:iq:search");
+ query = xmlnode_get_child(iq->node, "query");
+
+ xmlnode_insert_child(query, result);
+
+ jabber_iq_set_callback(iq, user_search_result_cb, NULL);
+ xmlnode_set_attrib(iq->node, "to", dir_server);
+ jabber_iq_send(iq);
+ g_free(dir_server);
+}
+
+struct user_search_info {
+ JabberStream *js;
+ char *directory_server;
+};
+
+static void user_search_cancel_cb(struct user_search_info *usi, GaimRequestFields *fields)
+{
+ g_free(usi->directory_server);
+ g_free(usi);
+}
+
+static void user_search_cb(struct user_search_info *usi, GaimRequestFields *fields)
+{
+ JabberStream *js = usi->js;
+ JabberIq *iq;
+ xmlnode *query;
+ GList *groups, *flds;
+
+ iq = jabber_iq_new_query(js, JABBER_IQ_SET, "jabber:iq:search");
+ query = xmlnode_get_child(iq->node, "query");
+
+ for(groups = gaim_request_fields_get_groups(fields); groups; groups = groups->next) {
+ for(flds = gaim_request_field_group_get_fields(groups->data);
+ flds; flds = flds->next) {
+ GaimRequestField *field = flds->data;
+ const char *id = gaim_request_field_get_id(field);
+ const char *value = gaim_request_field_string_get_value(field);
+
+ if(value && (!strcmp(id, "first") || !strcmp(id, "last") || !strcmp(id, "nick") || !strcmp(id, "email"))) {
+ xmlnode *y = xmlnode_new_child(query, id);
+ xmlnode_insert_data(y, value, -1);
+ }
+ }
+ }
+
+ jabber_iq_set_callback(iq, user_search_result_cb, NULL);
+ xmlnode_set_attrib(iq->node, "to", usi->directory_server);
+ jabber_iq_send(iq);
+
+ g_free(usi->directory_server);
+ g_free(usi);
+}
+
+#if 0
+/* This is for gettext only -- it will see this even though there's an #if 0. */
+
+/*
+ * An incomplete list of server generated original language search
+ * comments for Jabber User Directories
+ *
+ * See discussion thread "Search comment for Jabber is not translatable"
+ * in gaim-i18n@lists.sourceforge.net (March 2006)
+ */
+static const char * jabber_user_dir_comments [] = {
+ /* current comment from Jabber User Directory users.jabber.org */
+ N_("Find a contact by entering the search criteria in the given fields. "
+ "Note: Each field supports wild card searches (%)"),
+ NULL
+};
+#endif
+
+static void user_search_fields_result_cb(JabberStream *js, xmlnode *packet, gpointer data)
+{
+ xmlnode *query, *x;
+ const char *from, *type;
+
+ if(!(from = xmlnode_get_attrib(packet, "from")))
+ return;
+
+ if(!(type = xmlnode_get_attrib(packet, "type")) || !strcmp(type, "error")) {
+ char *msg = jabber_parse_error(js, packet);
+
+ if(!msg)
+ msg = g_strdup(_("Unknown error"));
+
+ gaim_notify_error(js->gc, _("Directory Query Failed"),
+ _("Could not query the directory server."), msg);
+ g_free(msg);
+
+ return;
+ }
+
+
+ if(!(query = xmlnode_get_child(packet, "query")))
+ return;
+
+ if((x = xmlnode_get_child_with_namespace(query, "x", "jabber:x:data"))) {
+ jabber_x_data_request(js, x, user_search_x_data_cb, g_strdup(from));
+ return;
+ } else {
+ struct user_search_info *usi;
+ xmlnode *instnode;
+ char *instructions = NULL;
+ GaimRequestFields *fields;
+ GaimRequestFieldGroup *group;
+ GaimRequestField *field;
+
+ /* old skool */
+ fields = gaim_request_fields_new();
+ group = gaim_request_field_group_new(NULL);
+ gaim_request_fields_add_group(fields, group);
+
+ if((instnode = xmlnode_get_child(query, "instructions")))
+ {
+ char *tmp = xmlnode_get_data(instnode);
+
+ if(tmp)
+ {
+ /* Try to translate the message (see static message
+ list in jabber_user_dir_comments[]) */
+ instructions = g_strdup_printf(_("Server Instructions: %s"), _(tmp));
+ g_free(tmp);
+ }
+ }
+
+ if(!instructions)
+ {
+ instructions = g_strdup(_("Fill in one or more fields to search "
+ "for any matching Jabber users."));
+ }
+
+ if(xmlnode_get_child(query, "first")) {
+ field = gaim_request_field_string_new("first", _("First Name"),
+ NULL, FALSE);
+ gaim_request_field_group_add_field(group, field);
+ }
+ if(xmlnode_get_child(query, "last")) {
+ field = gaim_request_field_string_new("last", _("Last Name"),
+ NULL, FALSE);
+ gaim_request_field_group_add_field(group, field);
+ }
+ if(xmlnode_get_child(query, "nick")) {
+ field = gaim_request_field_string_new("nick", _("Nickname"),
+ NULL, FALSE);
+ gaim_request_field_group_add_field(group, field);
+ }
+ if(xmlnode_get_child(query, "email")) {
+ field = gaim_request_field_string_new("email", _("E-Mail Address"),
+ NULL, FALSE);
+ gaim_request_field_group_add_field(group, field);
+ }
+
+ usi = g_new0(struct user_search_info, 1);
+ usi->js = js;
+ usi->directory_server = g_strdup(from);
+
+ gaim_request_fields(js->gc, _("Search for Jabber users"),
+ _("Search for Jabber users"), instructions, fields,
+ _("Search"), G_CALLBACK(user_search_cb),
+ _("Cancel"), G_CALLBACK(user_search_cancel_cb), usi);
+
+ g_free(instructions);
+ }
+}
+
+static void jabber_user_search_ok(JabberStream *js, const char *directory)
+{
+ JabberIq *iq;
+
+ /* XXX: should probably better validate the directory we're given */
+ if(!directory || !*directory) {
+ gaim_notify_error(js->gc, _("Invalid Directory"), _("Invalid Directory"), NULL);
+ return;
+ }
+
+ iq = jabber_iq_new_query(js, JABBER_IQ_GET, "jabber:iq:search");
+ xmlnode_set_attrib(iq->node, "to", directory);
+
+ jabber_iq_set_callback(iq, user_search_fields_result_cb, NULL);
+
+ jabber_iq_send(iq);
+}
+
+void jabber_user_search_begin(GaimPluginAction *action)
+{
+ GaimConnection *gc = (GaimConnection *) action->context;
+ JabberStream *js = gc->proto_data;
+
+ gaim_request_input(gc, _("Enter a User Directory"), _("Enter a User Directory"),
+ _("Select a user directory to search"),
+ js->user_directories ? js->user_directories->data : "users.jabber.org",
+ FALSE, FALSE, NULL,
+ _("Search Directory"), GAIM_CALLBACK(jabber_user_search_ok),
+ _("Cancel"), NULL, js);
+}
+
+
+
diff --git a/libpurple/protocols/jabber/buddy.h b/libpurple/protocols/jabber/buddy.h
new file mode 100644
index 0000000000..db2b628aca
--- /dev/null
+++ b/libpurple/protocols/jabber/buddy.h
@@ -0,0 +1,106 @@
+/**
+ * @file buddy.h Buddy handlers
+ *
+ * gaim
+ *
+ * Copyright (C) 2003 Nathan Walp <faceprint@faceprint.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+#ifndef _GAIM_JABBER_BUDDY_H_
+#define _GAIM_JABBER_BUDDY_H_
+
+#include "jabber.h"
+
+typedef enum {
+ JABBER_BUDDY_STATE_UNKNOWN = -2,
+ JABBER_BUDDY_STATE_ERROR = -1,
+ JABBER_BUDDY_STATE_UNAVAILABLE = 0,
+ JABBER_BUDDY_STATE_ONLINE,
+ JABBER_BUDDY_STATE_CHAT,
+ JABBER_BUDDY_STATE_AWAY,
+ JABBER_BUDDY_STATE_XA,
+ JABBER_BUDDY_STATE_DND
+} JabberBuddyState;
+
+typedef struct _JabberBuddy {
+ 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;
+} JabberBuddy;
+
+typedef struct _JabberBuddyResource {
+ JabberBuddy *jb;
+ char *name;
+ int priority;
+ JabberBuddyState state;
+ char *status;
+ 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;
+} 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_resource_free(JabberBuddyResource *jbr);
+void jabber_buddy_remove_resource(JabberBuddy *jb, const char *resource);
+const char *jabber_buddy_get_status_msg(JabberBuddy *jb);
+void jabber_buddy_get_info(GaimConnection *gc, const char *who);
+void jabber_buddy_get_info_chat(GaimConnection *gc, int id,
+ const char *resource);
+
+GList *jabber_blist_node_menu(GaimBlistNode *node);
+
+void jabber_set_info(GaimConnection *gc, const char *info);
+void jabber_setup_set_info(GaimPluginAction *action);
+void jabber_set_buddy_icon(GaimConnection *gc, const char *iconfile);
+
+const char *jabber_buddy_state_get_name(JabberBuddyState state);
+const char *jabber_buddy_state_get_status_id(JabberBuddyState state);
+const char *jabber_buddy_state_get_show(JabberBuddyState state);
+JabberBuddyState jabber_buddy_status_id_get_state(const char *id);
+JabberBuddyState jabber_buddy_show_get_state(const char *id);
+
+void jabber_user_search_begin(GaimPluginAction *);
+
+void jabber_buddy_remove_all_pending_buddy_info_requests(JabberStream *js);
+
+#endif /* _GAIM_JABBER_BUDDY_H_ */
diff --git a/libpurple/protocols/jabber/chat.c b/libpurple/protocols/jabber/chat.c
new file mode 100644
index 0000000000..7346a15aa3
--- /dev/null
+++ b/libpurple/protocols/jabber/chat.c
@@ -0,0 +1,1013 @@
+/*
+ * gaim - Jabber Protocol Plugin
+ *
+ * Copyright (C) 2003, Nathan Walp <faceprint@faceprint.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ */
+#include "internal.h"
+#include "debug.h"
+#include "prpl.h" /* for proto_chat_entry */
+#include "notify.h"
+#include "request.h"
+#include "roomlist.h"
+#include "util.h"
+
+#include "chat.h"
+#include "iq.h"
+#include "message.h"
+#include "presence.h"
+#include "xdata.h"
+
+GList *jabber_chat_info(GaimConnection *gc)
+{
+ GList *m = NULL;
+ struct proto_chat_entry *pce;
+
+ pce = g_new0(struct proto_chat_entry, 1);
+ pce->label = _("_Room:");
+ pce->identifier = "room";
+ pce->required = TRUE;
+ m = g_list_append(m, pce);
+
+ pce = g_new0(struct proto_chat_entry, 1);
+ pce->label = _("_Server:");
+ pce->identifier = "server";
+ pce->required = TRUE;
+ m = g_list_append(m, pce);
+
+ pce = g_new0(struct proto_chat_entry, 1);
+ pce->label = _("_Handle:");
+ pce->identifier = "handle";
+ pce->required = TRUE;
+ m = g_list_append(m, pce);
+
+ pce = g_new0(struct proto_chat_entry, 1);
+ pce->label = _("_Password:");
+ pce->identifier = "password";
+ pce->secret = TRUE;
+ m = g_list_append(m, pce);
+
+ return m;
+}
+
+GHashTable *jabber_chat_info_defaults(GaimConnection *gc, const char *chat_name)
+{
+ GHashTable *defaults;
+ JabberStream *js = gc->proto_data;
+
+ 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));
+ else
+ g_hash_table_insert(defaults, "server", g_strdup("conference.jabber.org"));
+
+ 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;
+ char *room_jid;
+
+ if(NULL != js->chats)
+ {
+ room_jid = g_strdup_printf("%s@%s", room, server);
+
+ chat = g_hash_table_lookup(js->chats, jabber_normalize(NULL, room_jid));
+ g_free(room_jid);
+ }
+
+ return chat;
+}
+
+struct _find_by_id_data {
+ int id;
+ JabberChat *chat;
+};
+
+static void find_by_id_foreach_cb(gpointer key, gpointer value, gpointer user_data)
+{
+ JabberChat *chat = value;
+ struct _find_by_id_data *fbid = user_data;
+
+ if(chat->id == fbid->id)
+ fbid->chat = chat;
+}
+
+JabberChat *jabber_chat_find_by_id(JabberStream *js, int id)
+{
+ JabberChat *chat;
+ struct _find_by_id_data *fbid = g_new0(struct _find_by_id_data, 1);
+ fbid->id = id;
+ g_hash_table_foreach(js->chats, find_by_id_foreach_cb, fbid);
+ chat = fbid->chat;
+ g_free(fbid);
+ return chat;
+}
+
+JabberChat *jabber_chat_find_by_conv(GaimConversation *conv)
+{
+ GaimAccount *account = gaim_conversation_get_account(conv);
+ GaimConnection *gc = gaim_account_get_connection(account);
+ JabberStream *js = gc->proto_data;
+ int id = gaim_conv_chat_get_id(GAIM_CONV_CHAT(conv));
+
+ return jabber_chat_find_by_id(js, id);
+}
+
+void jabber_chat_invite(GaimConnection *gc, int id, const char *msg,
+ const char *name)
+{
+ JabberStream *js = gc->proto_data;
+ JabberChat *chat;
+ xmlnode *message, *body, *x, *invite;
+ char *room_jid;
+
+ chat = jabber_chat_find_by_id(js, id);
+ if(!chat)
+ return;
+
+ message = xmlnode_new("message");
+
+ room_jid = g_strdup_printf("%s@%s", chat->room, chat->server);
+
+ if(chat->muc) {
+ xmlnode_set_attrib(message, "to", room_jid);
+ x = xmlnode_new_child(message, "x");
+ xmlnode_set_namespace(x, "http://jabber.org/protocol/muc#user");
+ invite = xmlnode_new_child(x, "invite");
+ xmlnode_set_attrib(invite, "to", name);
+ body = xmlnode_new_child(invite, "reason");
+ xmlnode_insert_data(body, msg, -1);
+ } else {
+ xmlnode_set_attrib(message, "to", name);
+ body = xmlnode_new_child(message, "body");
+ xmlnode_insert_data(body, msg, -1);
+ x = xmlnode_new_child(message, "x");
+ xmlnode_set_attrib(x, "jid", room_jid);
+ xmlnode_set_namespace(x, "jabber:x:conference");
+ }
+
+ jabber_send(js, message);
+ xmlnode_free(message);
+ g_free(room_jid);
+}
+
+void jabber_chat_member_free(JabberChatMember *jcm);
+
+char *jabber_get_chat_name(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;
+}
+
+void jabber_chat_join(GaimConnection *gc, GHashTable *data)
+{
+ JabberChat *chat;
+ char *room, *server, *handle, *passwd;
+ xmlnode *presence, *x;
+ char *tmp, *room_jid, *full_jid;
+ JabberStream *js = gc->proto_data;
+ GaimPresence *gpresence;
+ GaimStatus *status;
+ JabberBuddyState state;
+ char *msg;
+ int priority;
+
+ 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);
+ gaim_notify_error(gc, _("Invalid Room Name"), _("Invalid Room Name"),
+ buf);
+ g_free(buf);
+ return;
+ } else if(!jabber_nameprep_validate(server)) {
+ char *buf = g_strdup_printf(_("%s is not a valid server name"), server);
+ gaim_notify_error(gc, _("Invalid Server Name"),
+ _("Invalid Server Name"), buf);
+ g_free(buf);
+ return;
+ } else if(!jabber_resourceprep_validate(handle)) {
+ char *buf = g_strdup_printf(_("%s is not a valid room handle"), handle);
+ gaim_notify_error(gc, _("Invalid Room Handle"),
+ _("Invalid Room Handle"), buf);
+ }
+
+ if(jabber_chat_find(js, room, server))
+ return;
+
+ tmp = g_strdup_printf("%s@%s", room, server);
+ room_jid = g_strdup(jabber_normalize(NULL, tmp));
+ g_free(tmp);
+
+ chat = g_new0(JabberChat, 1);
+ chat->js = gc->proto_data;
+
+ chat->room = g_strdup(room);
+ chat->server = g_strdup(server);
+ chat->handle = g_strdup(handle);
+
+ chat->members = g_hash_table_new_full(g_str_hash, g_str_equal, NULL,
+ (GDestroyNotify)jabber_chat_member_free);
+
+ g_hash_table_insert(js->chats, room_jid, chat);
+
+ gpresence = gaim_account_get_presence(gc->account);
+ status = gaim_presence_get_active_status(gpresence);
+
+ gaim_status_to_jabber(status, &state, &msg, &priority);
+
+ presence = jabber_presence_create(state, msg, priority);
+ full_jid = g_strdup_printf("%s/%s", room_jid, handle);
+ xmlnode_set_attrib(presence, "to", full_jid);
+ g_free(full_jid);
+ g_free(msg);
+
+ x = xmlnode_new_child(presence, "x");
+ xmlnode_set_namespace(x, "http://jabber.org/protocol/muc");
+
+ if(passwd && *passwd) {
+ xmlnode *password = xmlnode_new_child(x, "password");
+ xmlnode_insert_data(password, passwd, -1);
+ }
+
+ jabber_send(js, presence);
+ xmlnode_free(presence);
+}
+
+void jabber_chat_leave(GaimConnection *gc, int id)
+{
+ JabberStream *js = gc->proto_data;
+ JabberChat *chat = jabber_chat_find_by_id(js, id);
+
+
+ if(!chat)
+ return;
+
+ jabber_chat_part(chat, NULL);
+
+ chat->conv = NULL;
+}
+
+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, jabber_normalize(NULL, room_jid));
+ g_free(room_jid);
+}
+
+void jabber_chat_free(JabberChat *chat)
+{
+ if(chat->config_dialog_handle)
+ gaim_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_free(chat);
+}
+
+gboolean jabber_chat_find_buddy(GaimConversation *conv, const char *name)
+{
+ return gaim_conv_chat_find_user(GAIM_CONV_CHAT(conv), name);
+}
+
+char *jabber_chat_buddy_real_name(GaimConnection *gc, int id, const char *who)
+{
+ JabberStream *js = gc->proto_data;
+ JabberChat *chat;
+
+ chat = jabber_chat_find_by_id(js, id);
+
+ if(!chat)
+ return NULL;
+
+ return g_strdup_printf("%s@%s/%s", chat->room, chat->server, who);
+}
+
+static void jabber_chat_room_configure_x_data_cb(JabberStream *js, xmlnode *result, gpointer data)
+{
+ JabberChat *chat = data;
+ xmlnode *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");
+ xmlnode_set_attrib(iq->node, "to", to);
+ g_free(to);
+
+ query = xmlnode_get_child(iq->node, "query");
+
+ xmlnode_insert_child(query, result);
+
+ jabber_iq_send(iq);
+}
+
+static void jabber_chat_room_configure_cb(JabberStream *js, xmlnode *packet, gpointer data)
+{
+ xmlnode *query, *x;
+ const char *type = xmlnode_get_attrib(packet, "type");
+ const char *from = xmlnode_get_attrib(packet, "from");
+ char *msg;
+ JabberChat *chat;
+ JabberID *jid;
+
+ if(!type || !from)
+ return;
+
+
+
+ if(!strcmp(type, "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 = xmlnode_get_child(packet, "query")))
+ return;
+
+ for(x = xmlnode_get_child(query, "x"); x; x = xmlnode_get_next_twin(x)) {
+ const char *xmlns;
+ if(!(xmlns = xmlnode_get_namespace(x)))
+ continue;
+
+ if(!strcmp(xmlns, "jabber:x:data")) {
+ chat->config_dialog_type = GAIM_REQUEST_FIELDS;
+ chat->config_dialog_handle = jabber_x_data_request(js, x, jabber_chat_room_configure_x_data_cb, chat);
+ return;
+ }
+ }
+ } else if(!strcmp(type, "error")) {
+ char *msg = jabber_parse_error(js, packet);
+
+ gaim_notify_error(js->gc, _("Configuration error"), _("Configuration error"), msg);
+
+ if(msg)
+ g_free(msg);
+ return;
+ }
+
+ msg = g_strdup_printf("Unable to configure room %s", from);
+
+ gaim_notify_info(js->gc, _("Unable to configure"), _("Unable to configure"), msg);
+ 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) {
+ gaim_notify_error(chat->js->gc, _("Room Configuration Error"), _("Room Configuration Error"),
+ _("This room is not capable of being configured"));
+ 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);
+
+ 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;
+ xmlnode *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 = xmlnode_get_child(iq->node, "query");
+ x = xmlnode_new_child(query, "x");
+ room_jid = g_strdup_printf("%s@%s", chat->room, chat->server);
+
+ xmlnode_set_attrib(iq->node, "to", room_jid);
+ xmlnode_set_namespace(x, "jabber:x:data");
+ 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, xmlnode *packet, gpointer data)
+{
+ const char *type = xmlnode_get_attrib(packet, "type");
+
+ if(type && !strcmp(type, "error")) {
+ char *msg = jabber_parse_error(js, packet);
+
+ gaim_notify_error(js->gc, _("Registration error"), _("Registration error"), msg);
+
+ if(msg)
+ g_free(msg);
+ return;
+ }
+}
+
+static void jabber_chat_register_x_data_cb(JabberStream *js, xmlnode *result, gpointer data)
+{
+ JabberChat *chat = data;
+ xmlnode *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");
+ xmlnode_set_attrib(iq->node, "to", to);
+ g_free(to);
+
+ query = xmlnode_get_child(iq->node, "query");
+
+ 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, xmlnode *packet, gpointer data)
+{
+ xmlnode *query, *x;
+ const char *type = xmlnode_get_attrib(packet, "type");
+ const char *from = xmlnode_get_attrib(packet, "from");
+ char *msg;
+ JabberChat *chat;
+ JabberID *jid;
+
+ if(!type || !from)
+ return;
+
+ if(!strcmp(type, "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 = xmlnode_get_child(packet, "query")))
+ return;
+
+ for(x = xmlnode_get_child(query, "x"); x; x = xmlnode_get_next_twin(x)) {
+ const char *xmlns;
+
+ if(!(xmlns = xmlnode_get_namespace(x)))
+ continue;
+
+ if(!strcmp(xmlns, "jabber:x:data")) {
+ jabber_x_data_request(js, x, jabber_chat_register_x_data_cb, chat);
+ return;
+ }
+ }
+ } else if(!strcmp(type, "error")) {
+ char *msg = jabber_parse_error(js, packet);
+
+ gaim_notify_error(js->gc, _("Registration error"), _("Registration error"), msg);
+
+ if(msg)
+ g_free(msg);
+ return;
+ }
+
+ msg = g_strdup_printf("Unable to configure room %s", from);
+
+ gaim_notify_info(js->gc, _("Unable to configure"), _("Unable to configure"), msg);
+ 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");
+ 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)
+{
+ if(topic && *topic) {
+ JabberMessage *jm;
+ jm = g_new0(JabberMessage, 1);
+ jm->js = chat->js;
+ jm->type = JABBER_MESSAGE_GROUPCHAT;
+ jm->subject = gaim_markup_strip_html(topic);
+ jm->to = g_strdup_printf("%s@%s", chat->room, chat->server);
+ jabber_message_send(jm);
+ jabber_message_free(jm);
+ } else {
+ const char *cur = gaim_conv_chat_get_topic(GAIM_CONV_CHAT(chat->conv));
+ char *buf, *tmp, *tmp2;
+
+ if(cur) {
+ tmp = g_markup_escape_text(cur, -1);
+ tmp2 = gaim_markup_linkify(tmp);
+ buf = g_strdup_printf(_("current topic is: %s"), tmp2);
+ g_free(tmp);
+ g_free(tmp2);
+ } else
+ buf = g_strdup(_("No topic is set"));
+ gaim_conv_chat_write(GAIM_CONV_CHAT(chat->conv), "", buf,
+ GAIM_MESSAGE_SYSTEM | GAIM_MESSAGE_NO_LOG, time(NULL));
+ g_free(buf);
+ }
+
+}
+
+void jabber_chat_set_topic(GaimConnection *gc, int id, const char *topic)
+{
+ JabberStream *js = gc->proto_data;
+ JabberChat *chat = jabber_chat_find_by_id(js, id);
+
+ if(!chat)
+ return;
+
+ jabber_chat_change_topic(chat, topic);
+}
+
+
+void jabber_chat_change_nick(JabberChat *chat, const char *nick)
+{
+ xmlnode *presence;
+ char *full_jid;
+ GaimPresence *gpresence;
+ GaimStatus *status;
+ JabberBuddyState state;
+ char *msg;
+ int priority;
+
+ if(!chat->muc) {
+ gaim_conv_chat_write(GAIM_CONV_CHAT(chat->conv), "",
+ _("Nick changing not supported in non-MUC chatrooms"),
+ GAIM_MESSAGE_SYSTEM, time(NULL));
+ return;
+ }
+
+ gpresence = gaim_account_get_presence(chat->js->gc->account);
+ status = gaim_presence_get_active_status(gpresence);
+
+ gaim_status_to_jabber(status, &state, &msg, &priority);
+
+ presence = jabber_presence_create(state, msg, priority);
+ full_jid = g_strdup_printf("%s@%s/%s", chat->room, chat->server, nick);
+ xmlnode_set_attrib(presence, "to", full_jid);
+ g_free(full_jid);
+ g_free(msg);
+
+ jabber_send(chat->js, presence);
+ xmlnode_free(presence);
+}
+
+void jabber_chat_part(JabberChat *chat, const char *msg)
+{
+ char *room_jid;
+ xmlnode *presence;
+
+ room_jid = g_strdup_printf("%s@%s/%s", chat->room, chat->server,
+ chat->handle);
+ presence = xmlnode_new("presence");
+ xmlnode_set_attrib(presence, "to", room_jid);
+ xmlnode_set_attrib(presence, "type", "unavailable");
+ if(msg) {
+ xmlnode *status = xmlnode_new_child(presence, "status");
+ xmlnode_insert_data(status, msg, -1);
+ }
+ jabber_send(chat->js, presence);
+ xmlnode_free(presence);
+ g_free(room_jid);
+}
+
+static void roomlist_disco_result_cb(JabberStream *js, xmlnode *packet, gpointer data)
+{
+ xmlnode *query;
+ xmlnode *item;
+ const char *type;
+
+ if(!js->roomlist)
+ return;
+
+ if(!(type = xmlnode_get_attrib(packet, "type")) || strcmp(type, "result")) {
+ char *err = jabber_parse_error(js,packet);
+ gaim_notify_error(js->gc, _("Error"),
+ _("Error retrieving room list"), err);
+ gaim_roomlist_set_in_progress(js->roomlist, FALSE);
+ gaim_roomlist_unref(js->roomlist);
+ js->roomlist = NULL;
+ g_free(err);
+ return;
+ }
+
+ if(!(query = xmlnode_get_child(packet, "query"))) {
+ char *err = jabber_parse_error(js, packet);
+ gaim_notify_error(js->gc, _("Error"),
+ _("Error retrieving room list"), err);
+ gaim_roomlist_set_in_progress(js->roomlist, FALSE);
+ gaim_roomlist_unref(js->roomlist);
+ js->roomlist = NULL;
+ g_free(err);
+ return;
+ }
+
+ for(item = xmlnode_get_child(query, "item"); item;
+ item = xmlnode_get_next_twin(item)) {
+ const char *name;
+ GaimRoomlistRoom *room;
+ JabberID *jid;
+
+ if(!(jid = jabber_id_new(xmlnode_get_attrib(item, "jid"))))
+ continue;
+ name = xmlnode_get_attrib(item, "name");
+
+
+ room = gaim_roomlist_room_new(GAIM_ROOMLIST_ROOMTYPE_ROOM, jid->node, NULL);
+ gaim_roomlist_room_add_field(js->roomlist, room, jid->node);
+ gaim_roomlist_room_add_field(js->roomlist, room, jid->domain);
+ gaim_roomlist_room_add_field(js->roomlist, room, name ? name : "");
+ gaim_roomlist_room_add(js->roomlist, room);
+
+ jabber_id_free(jid);
+ }
+ gaim_roomlist_set_in_progress(js->roomlist, FALSE);
+ gaim_roomlist_unref(js->roomlist);
+ js->roomlist = NULL;
+}
+
+static void roomlist_cancel_cb(JabberStream *js, const char *server) {
+ if(js->roomlist) {
+ gaim_roomlist_set_in_progress(js->roomlist, FALSE);
+ gaim_roomlist_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) {
+ gaim_notify_error(js->gc, _("Invalid Server"), _("Invalid Server"), NULL);
+ return;
+ }
+
+ gaim_roomlist_set_in_progress(js->roomlist, TRUE);
+
+ iq = jabber_iq_new_query(js, JABBER_IQ_GET, "http://jabber.org/protocol/disco#items");
+
+ xmlnode_set_attrib(iq->node, "to", server);
+
+ jabber_iq_set_callback(iq, roomlist_disco_result_cb, NULL);
+
+ jabber_iq_send(iq);
+}
+
+char *jabber_roomlist_room_serialize(GaimRoomlistRoom *room)
+{
+
+ return g_strdup_printf("%s@%s", (char*)room->fields->data, (char*)room->fields->next->data);
+}
+
+GaimRoomlist *jabber_roomlist_get_list(GaimConnection *gc)
+{
+ JabberStream *js = gc->proto_data;
+ GList *fields = NULL;
+ GaimRoomlistField *f;
+
+ if(js->roomlist)
+ gaim_roomlist_unref(js->roomlist);
+
+ js->roomlist = gaim_roomlist_new(gaim_connection_get_account(js->gc));
+
+ f = gaim_roomlist_field_new(GAIM_ROOMLIST_FIELD_STRING, "", "room", TRUE);
+ fields = g_list_append(fields, f);
+
+ f = gaim_roomlist_field_new(GAIM_ROOMLIST_FIELD_STRING, "", "server", TRUE);
+ fields = g_list_append(fields, f);
+
+ f = gaim_roomlist_field_new(GAIM_ROOMLIST_FIELD_STRING, _("Description"), "description", FALSE);
+ fields = g_list_append(fields, f);
+
+ gaim_roomlist_set_fields(js->roomlist, fields);
+
+
+ gaim_request_input(gc, _("Enter a Conference Server"), _("Enter a Conference Server"),
+ _("Select a conference server to query"),
+ js->chat_servers ? js->chat_servers->data : "conference.jabber.org",
+ FALSE, FALSE, NULL,
+ _("Find Rooms"), GAIM_CALLBACK(roomlist_ok_cb),
+ _("Cancel"), GAIM_CALLBACK(roomlist_cancel_cb), js);
+
+ return js->roomlist;
+}
+
+void jabber_roomlist_cancel(GaimRoomlist *list)
+{
+ GaimConnection *gc;
+ JabberStream *js;
+
+ gc = gaim_account_get_connection(list->account);
+ js = gc->proto_data;
+
+ gaim_roomlist_set_in_progress(list, FALSE);
+
+ if (js->roomlist == list) {
+ js->roomlist = NULL;
+ gaim_roomlist_unref(list);
+ }
+}
+
+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, const char *affiliation, 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)
+{
+ JabberIq *iq;
+ JabberChatMember *jcm = g_hash_table_lookup(chat->members, who);
+ char *to;
+ xmlnode *query, *item, *reason;
+
+ if(!jcm || !jcm->jid)
+ 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);
+ xmlnode_set_attrib(iq->node, "to", to);
+ g_free(to);
+
+ query = xmlnode_get_child(iq->node, "query");
+ item = xmlnode_new_child(query, "item");
+ xmlnode_set_attrib(item, "jid", jcm->jid);
+ xmlnode_set_attrib(item, "affiliation", "outcast");
+ if(why) {
+ reason = xmlnode_new_child(item, "reason");
+ 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)
+{
+ char *to;
+ JabberIq *iq;
+ xmlnode *query, *item;
+ JabberChatMember *jcm;
+
+ jcm = g_hash_table_lookup(chat->members, who);
+
+ if (!jcm || !jcm->jid)
+ 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);
+ xmlnode_set_attrib(iq->node, "to", to);
+ g_free(to);
+
+ query = xmlnode_get_child(iq->node, "query");
+ item = xmlnode_new_child(query, "item");
+ xmlnode_set_attrib(item, "jid", jcm->jid);
+ xmlnode_set_attrib(item, "affiliation", affiliation);
+
+ jabber_iq_send(iq);
+
+ return TRUE;
+}
+
+gboolean jabber_chat_role_user(JabberChat *chat, const char *who, const char *role)
+{
+ char *to;
+ JabberIq *iq;
+ xmlnode *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);
+ xmlnode_set_attrib(iq->node, "to", to);
+ g_free(to);
+
+ query = xmlnode_get_child(iq->node, "query");
+ item = xmlnode_new_child(query, "item");
+ xmlnode_set_attrib(item, "nick", jcm->handle);
+ xmlnode_set_attrib(item, "role", role);
+
+ jabber_iq_send(iq);
+
+ return TRUE;
+}
+
+gboolean jabber_chat_kick_user(JabberChat *chat, const char *who, const char *why)
+{
+ JabberIq *iq;
+ JabberChatMember *jcm = g_hash_table_lookup(chat->members, who);
+ char *to;
+ xmlnode *query, *item, *reason;
+
+ if(!jcm || !jcm->jid)
+ 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);
+ xmlnode_set_attrib(iq->node, "to", to);
+ g_free(to);
+
+ query = xmlnode_get_child(iq->node, "query");
+ item = xmlnode_new_child(query, "item");
+ xmlnode_set_attrib(item, "jid", jcm->jid);
+ xmlnode_set_attrib(item, "role", "none");
+ if(why) {
+ reason = xmlnode_new_child(item, "reason");
+ xmlnode_insert_data(reason, why, -1);
+ }
+
+ jabber_iq_send(iq);
+
+ return TRUE;
+}
+
+static void jabber_chat_disco_traffic_cb(JabberStream *js, xmlnode *packet, gpointer data)
+{
+ JabberChat *chat;
+ xmlnode *query;
+ int id = GPOINTER_TO_INT(data);
+
+ if(!(chat = jabber_chat_find_by_id(js, id)))
+ return;
+
+ /* defaults, in case the conference server doesn't
+ * support this request */
+ chat->xhtml = TRUE;
+
+ if(xmlnode_get_child(packet, "error")) {
+ return;
+ }
+
+ if(!(query = xmlnode_get_child(packet, "query")))
+ return;
+
+ /* disabling this until more MUC servers support
+ * announcing this
+ chat->xhtml = FALSE;
+
+ for(x = xmlnode_get_child(query, "feature"); x; x = xmlnode_get_next_twin(x)) {
+ const char *var = xmlnode_get_attrib(x, "var");
+
+ if(var && !strcmp(var, "http://jabber.org/protocol/xhtml-im")) {
+ chat->xhtml = TRUE;
+ }
+ }
+ */
+}
+
+void jabber_chat_disco_traffic(JabberChat *chat)
+{
+ JabberIq *iq;
+ xmlnode *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,
+ "http://jabber.org/protocol/disco#info");
+
+ xmlnode_set_attrib(iq->node, "to", room_jid);
+
+ query = xmlnode_get_child(iq->node, "query");
+
+ 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);
+}
+
+
+
diff --git a/libpurple/protocols/jabber/chat.h b/libpurple/protocols/jabber/chat.h
new file mode 100644
index 0000000000..150aa1918f
--- /dev/null
+++ b/libpurple/protocols/jabber/chat.h
@@ -0,0 +1,95 @@
+/**
+ * @file chat.h Chat stuff
+ *
+ * gaim
+ *
+ * Copyright (C) 2003 Nathan Walp <faceprint@faceprint.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+#ifndef _GAIM_JABBER_CHAT_H_
+#define _GAIM_JABBER_CHAT_H_
+
+#include "internal.h"
+#include "connection.h"
+#include "conversation.h"
+#include "request.h"
+#include "roomlist.h"
+
+#include "jabber.h"
+
+typedef struct _JabberChatMember {
+ char *handle;
+ char *jid;
+} JabberChatMember;
+
+
+typedef struct _JabberChat {
+ JabberStream *js;
+ char *room;
+ char *server;
+ char *handle;
+ int id;
+ GaimConversation *conv;
+ gboolean muc;
+ gboolean xhtml;
+ GaimRequestType config_dialog_type;
+ void *config_dialog_handle;
+ GHashTable *members;
+} JabberChat;
+
+GList *jabber_chat_info(GaimConnection *gc);
+GHashTable *jabber_chat_info_defaults(GaimConnection *gc, const char *chat_name);
+char *jabber_get_chat_name(GHashTable *data);
+void jabber_chat_join(GaimConnection *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(GaimConversation *conv);
+void jabber_chat_destroy(JabberChat *chat);
+void jabber_chat_free(JabberChat *chat);
+gboolean jabber_chat_find_buddy(GaimConversation *conv, const char *name);
+void jabber_chat_invite(GaimConnection *gc, int id, const char *message,
+ const char *name);
+void jabber_chat_leave(GaimConnection *gc, int id);
+char *jabber_chat_buddy_real_name(GaimConnection *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(GaimConnection *gc, int id, const char *topic);
+void 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_role_user(JabberChat *chat, const char *who,
+ const char *role);
+gboolean jabber_chat_kick_user(JabberChat *chat, const char *who,
+ const char *why);
+
+GaimRoomlist *jabber_roomlist_get_list(GaimConnection *gc);
+void jabber_roomlist_cancel(GaimRoomlist *list);
+
+void jabber_chat_disco_traffic(JabberChat *chat);
+
+char *jabber_roomlist_room_serialize(GaimRoomlistRoom *room);
+
+
+#endif /* _GAIM_JABBER_CHAT_H_ */
diff --git a/libpurple/protocols/jabber/disco.c b/libpurple/protocols/jabber/disco.c
new file mode 100644
index 0000000000..905a6d6791
--- /dev/null
+++ b/libpurple/protocols/jabber/disco.c
@@ -0,0 +1,384 @@
+/*
+ * gaim - Jabber Protocol Plugin
+ *
+ * Copyright (C) 2003, Nathan Walp <faceprint@faceprint.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ */
+
+#include "internal.h"
+#include "prefs.h"
+#include "debug.h"
+
+#include "buddy.h"
+#include "google.h"
+#include "iq.h"
+#include "disco.h"
+#include "jabber.h"
+#include "presence.h"
+#include "roster.h"
+
+struct _jabber_disco_info_cb_data {
+ gpointer data;
+ JabberDiscoInfoCallback *callback;
+};
+
+#define SUPPORT_FEATURE(x) \
+ feature = xmlnode_new_child(query, "feature"); \
+ xmlnode_set_attrib(feature, "var", x);
+
+
+void jabber_disco_info_parse(JabberStream *js, xmlnode *packet) {
+ const char *from = xmlnode_get_attrib(packet, "from");
+ const char *type = xmlnode_get_attrib(packet, "type");
+
+ if(!from || !type)
+ return;
+
+ if(!strcmp(type, "get")) {
+ xmlnode *query, *identity, *feature;
+ JabberIq *iq;
+
+ xmlnode *in_query;
+ const char *node = NULL;
+
+ if((in_query = xmlnode_get_child(packet, "query"))) {
+ node = xmlnode_get_attrib(in_query, "node");
+ }
+
+
+ iq = jabber_iq_new_query(js, JABBER_IQ_RESULT,
+ "http://jabber.org/protocol/disco#info");
+
+ jabber_iq_set_id(iq, xmlnode_get_attrib(packet, "id"));
+
+ xmlnode_set_attrib(iq->node, "to", from);
+ query = xmlnode_get_child(iq->node, "query");
+
+ if(node)
+ xmlnode_set_attrib(query, "node", node);
+
+ if(!node || !strcmp(node, CAPS0115_NODE "#" VERSION)) {
+
+ identity = xmlnode_new_child(query, "identity");
+ xmlnode_set_attrib(identity, "category", "client");
+ xmlnode_set_attrib(identity, "type", "pc"); /* XXX: bot, console,
+ * handheld, pc, phone,
+ * web */
+ xmlnode_set_attrib(identity, "name", PACKAGE);
+
+ SUPPORT_FEATURE("jabber:iq:last")
+ SUPPORT_FEATURE("jabber:iq:oob")
+ SUPPORT_FEATURE("jabber:iq:time")
+ SUPPORT_FEATURE("jabber:iq:version")
+ SUPPORT_FEATURE("jabber:x:conference")
+ SUPPORT_FEATURE("http://jabber.org/protocol/bytestreams")
+ SUPPORT_FEATURE("http://jabber.org/protocol/disco#info")
+ SUPPORT_FEATURE("http://jabber.org/protocol/disco#items")
+#if 0
+ SUPPORT_FEATURE("http://jabber.org/protocol/ibb")
+#endif
+ SUPPORT_FEATURE("http://jabber.org/protocol/muc")
+ SUPPORT_FEATURE("http://jabber.org/protocol/muc#user")
+ SUPPORT_FEATURE("http://jabber.org/protocol/si")
+ SUPPORT_FEATURE("http://jabber.org/protocol/si/profile/file-transfer")
+ SUPPORT_FEATURE("http://jabber.org/protocol/xhtml-im")
+ } else {
+ xmlnode *error, *inf;
+
+ /* XXX: gross hack, implement jabber_iq_set_type or something */
+ xmlnode_set_attrib(iq->node, "type", "error");
+ iq->type = JABBER_IQ_ERROR;
+
+ error = xmlnode_new_child(query, "error");
+ xmlnode_set_attrib(error, "code", "404");
+ xmlnode_set_attrib(error, "type", "cancel");
+ inf = xmlnode_new_child(error, "item-not-found");
+ xmlnode_set_namespace(inf, "urn:ietf:params:xml:ns:xmpp-stanzas");
+ }
+
+ jabber_iq_send(iq);
+ } else if(!strcmp(type, "result")) {
+ xmlnode *query = xmlnode_get_child(packet, "query");
+ xmlnode *child;
+ JabberID *jid;
+ JabberBuddy *jb;
+ JabberBuddyResource *jbr = NULL;
+ JabberCapabilities capabilities = JABBER_CAP_NONE;
+ struct _jabber_disco_info_cb_data *jdicd;
+
+ 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)
+ capabilities = jbr->capabilities;
+
+ for(child = query->child; child; child = child->next) {
+ if(child->type != XMLNODE_TYPE_TAG)
+ continue;
+
+ if(!strcmp(child->name, "identity")) {
+ const char *category = xmlnode_get_attrib(child, "category");
+ const char *type = xmlnode_get_attrib(child, "type");
+ if(!category || !type)
+ continue;
+
+ if(!strcmp(category, "conference") && !strcmp(type, "text")) {
+ /* we found a groupchat or MUC server, add it to the list */
+ /* XXX: actually check for protocol/muc or gc-1.0 support */
+ js->chat_servers = g_list_append(js->chat_servers, g_strdup(from));
+ } else if(!strcmp(category, "directory") && !strcmp(type, "user")) {
+ /* we found a JUD */
+ js->user_directories = g_list_append(js->user_directories, g_strdup(from));
+ }
+
+ } else if(!strcmp(child->name, "feature")) {
+ const char *var = xmlnode_get_attrib(child, "var");
+ if(!var)
+ continue;
+
+ if(!strcmp(var, "http://jabber.org/protocol/si"))
+ capabilities |= JABBER_CAP_SI;
+ else if(!strcmp(var, "http://jabber.org/protocol/si/profile/file-transfer"))
+ capabilities |= JABBER_CAP_SI_FILE_XFER;
+ else if(!strcmp(var, "http://jabber.org/protocol/bytestreams"))
+ capabilities |= JABBER_CAP_BYTESTREAMS;
+ else if(!strcmp(var, "jabber:iq:search"))
+ capabilities |= JABBER_CAP_IQ_SEARCH;
+ else if(!strcmp(var, "jabber:iq:register"))
+ capabilities |= JABBER_CAP_IQ_REGISTER;
+ }
+ }
+
+ capabilities |= JABBER_CAP_RETRIEVED;
+
+ if(jbr)
+ jbr->capabilities = capabilities;
+
+ if((jdicd = g_hash_table_lookup(js->disco_callbacks, from))) {
+ jdicd->callback(js, from, capabilities, jdicd->data);
+ g_hash_table_remove(js->disco_callbacks, from);
+ }
+ } else if(!strcmp(type, "error")) {
+ JabberID *jid;
+ JabberBuddy *jb;
+ JabberBuddyResource *jbr = NULL;
+ JabberCapabilities capabilities = JABBER_CAP_NONE;
+ struct _jabber_disco_info_cb_data *jdicd;
+
+ if(!(jdicd = g_hash_table_lookup(js->disco_callbacks, from)))
+ return;
+
+ if((jid = jabber_id_new(from))) {
+ if(jid->resource && (jb = jabber_buddy_find(js, from, TRUE)))
+ jbr = jabber_buddy_find_resource(jb, jid->resource);
+ jabber_id_free(jid);
+ }
+
+ if(jbr)
+ capabilities = jbr->capabilities;
+
+ jdicd->callback(js, from, capabilities, jdicd->data);
+ g_hash_table_remove(js->disco_callbacks, from);
+ }
+}
+
+void jabber_disco_items_parse(JabberStream *js, xmlnode *packet) {
+ const char *from = xmlnode_get_attrib(packet, "from");
+ const char *type = xmlnode_get_attrib(packet, "type");
+
+ if(type && !strcmp(type, "get")) {
+ JabberIq *iq = jabber_iq_new_query(js, JABBER_IQ_RESULT,
+ "http://jabber.org/protocol/disco#items");
+
+ jabber_iq_set_id(iq, xmlnode_get_attrib(packet, "id"));
+
+ xmlnode_set_attrib(iq->node, "to", from);
+ jabber_iq_send(iq);
+ }
+}
+
+static void
+jabber_disco_finish_server_info_result_cb(JabberStream *js)
+{
+ GaimPresence *gpresence;
+ GaimStatus *status;
+
+ if (!(js->server_caps & JABBER_CAP_GOOGLE_ROSTER)) {
+ /* If the server supports JABBER_CAP_GOOGLE_ROSTER; we will have already requested it */
+ jabber_roster_request(js);
+ }
+
+ /* Send initial presence; this will trigger receipt of presence for contacts on the roster */
+ gpresence = gaim_account_get_presence(js->gc->account);
+ status = gaim_presence_get_active_status(gpresence);
+ jabber_presence_send(js->gc->account, status);
+}
+
+static void
+jabber_disco_server_info_result_cb(JabberStream *js, xmlnode *packet, gpointer data)
+{
+ xmlnode *query, *child;
+ const char *from = xmlnode_get_attrib(packet, "from");
+ const char *type = xmlnode_get_attrib(packet, "type");
+
+ if((!from || !type) ||
+ (strcmp(from, js->user->domain))) {
+ jabber_disco_finish_server_info_result_cb(js);
+ return;
+ }
+
+ if(strcmp(type, "result")) {
+ /* A common way to get here is for the server not to support xmlns http://jabber.org/protocol/disco#info */
+ jabber_disco_finish_server_info_result_cb(js);
+ return;
+ }
+
+ query = xmlnode_get_child(packet, "query");
+
+ if (!query) {
+ jabber_disco_finish_server_info_result_cb(js);
+ return;
+ }
+
+ for (child = xmlnode_get_child(query, "category"); child;
+ child = xmlnode_get_next_twin(child)) {
+ const char *category, *type, *name;
+ category = xmlnode_get_attrib(child, "category");
+ if (!category || strcmp(category, "server"))
+ continue;
+ type = xmlnode_get_attrib(child, "type");
+ if (!type || strcmp(type, "im"))
+ continue;
+
+ name = xmlnode_get_attrib(child, "name");
+ if (!name)
+ continue;
+
+ g_free(js->server_name);
+ js->server_name = g_strdup(name);
+ if (!strcmp(name, "Google Talk"))
+ js->googletalk = TRUE;
+ }
+
+ for (child = xmlnode_get_child(query, "feature"); child;
+ child = xmlnode_get_next_twin(child)) {
+ const char *var;
+ var = xmlnode_get_attrib(child, "var");
+ if (!var)
+ continue;
+
+ if (!strcmp("google:mail:notify", var)) {
+ js->server_caps |= JABBER_CAP_GMAIL_NOTIFY;
+ jabber_gmail_init(js);
+ } else if (!strcmp("google:roster", var)) {
+ js->server_caps |= JABBER_CAP_GOOGLE_ROSTER;
+ jabber_google_roster_init(js);
+ }
+ }
+
+ jabber_disco_finish_server_info_result_cb(js);
+}
+
+static void
+jabber_disco_server_items_result_cb(JabberStream *js, xmlnode *packet, gpointer data)
+{
+ xmlnode *query, *child;
+ const char *from = xmlnode_get_attrib(packet, "from");
+ const char *type = xmlnode_get_attrib(packet, "type");
+
+ if(!from || !type)
+ return;
+
+ if(strcmp(from, js->user->domain))
+ return;
+
+ if(strcmp(type, "result"))
+ return;
+
+ while(js->chat_servers) {
+ g_free(js->chat_servers->data);
+ js->chat_servers = g_list_delete_link(js->chat_servers, js->chat_servers);
+ }
+
+ query = xmlnode_get_child(packet, "query");
+
+ for(child = xmlnode_get_child(query, "item"); child;
+ child = xmlnode_get_next_twin(child)) {
+ JabberIq *iq;
+ const char *jid;
+
+ if(!(jid = xmlnode_get_attrib(child, "jid")))
+ continue;
+
+ iq = jabber_iq_new_query(js, JABBER_IQ_GET, "http://jabber.org/protocol/disco#info");
+ xmlnode_set_attrib(iq->node, "to", jid);
+ jabber_iq_send(iq);
+ }
+}
+
+void jabber_disco_items_server(JabberStream *js)
+{
+ JabberIq *iq = jabber_iq_new_query(js, JABBER_IQ_GET,
+ "http://jabber.org/protocol/disco#items");
+
+ xmlnode_set_attrib(iq->node, "to", js->user->domain);
+
+ jabber_iq_set_callback(iq, jabber_disco_server_items_result_cb, NULL);
+ jabber_iq_send(iq);
+
+ iq = jabber_iq_new_query(js, JABBER_IQ_GET,
+ "http://jabber.org/protocol/disco#info");
+ xmlnode_set_attrib(iq->node, "to", js->user->domain);
+ jabber_iq_set_callback(iq, jabber_disco_server_info_result_cb, NULL);
+ jabber_iq_send(iq);
+}
+
+void jabber_disco_info_do(JabberStream *js, const char *who, JabberDiscoInfoCallback *callback, gpointer data)
+{
+ JabberID *jid;
+ JabberBuddy *jb;
+ JabberBuddyResource *jbr = NULL;
+ struct _jabber_disco_info_cb_data *jdicd;
+ JabberIq *iq;
+
+ if((jid = jabber_id_new(who))) {
+ if(jid->resource && (jb = jabber_buddy_find(js, who, TRUE)))
+ jbr = jabber_buddy_find_resource(jb, jid->resource);
+ jabber_id_free(jid);
+ }
+
+ if(jbr && jbr->capabilities & JABBER_CAP_RETRIEVED) {
+ callback(js, who, jbr->capabilities, data);
+ return;
+ }
+
+ jdicd = g_new0(struct _jabber_disco_info_cb_data, 1);
+ jdicd->data = data;
+ jdicd->callback = callback;
+
+ g_hash_table_insert(js->disco_callbacks, g_strdup(who), jdicd);
+
+ iq = jabber_iq_new_query(js, JABBER_IQ_GET, "http://jabber.org/protocol/disco#info");
+ xmlnode_set_attrib(iq->node, "to", who);
+
+ jabber_iq_send(iq);
+}
+
+
diff --git a/libpurple/protocols/jabber/disco.h b/libpurple/protocols/jabber/disco.h
new file mode 100644
index 0000000000..550c591498
--- /dev/null
+++ b/libpurple/protocols/jabber/disco.h
@@ -0,0 +1,38 @@
+/**
+ * @file iq.h JabberID handlers
+ *
+ * gaim
+ *
+ * Copyright (C) 2003 Nathan Walp <faceprint@faceprint.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+#ifndef _GAIM_JABBER_DISCO_H_
+#define _GAIM_JABBER_DISCO_H_
+
+#include "jabber.h"
+
+typedef void (JabberDiscoInfoCallback)(JabberStream *js, const char *who,
+ JabberCapabilities capabilities, gpointer data);
+
+void jabber_disco_info_parse(JabberStream *js, xmlnode *packet);
+void jabber_disco_items_parse(JabberStream *js, xmlnode *packet);
+
+void jabber_disco_items_server(JabberStream *js);
+
+void jabber_disco_info_do(JabberStream *js, const char *who,
+ JabberDiscoInfoCallback *callback, gpointer data);
+
+#endif /* _GAIM_JABBER_DISCO_H_ */
diff --git a/libpurple/protocols/jabber/google.c b/libpurple/protocols/jabber/google.c
new file mode 100644
index 0000000000..8dd6211a77
--- /dev/null
+++ b/libpurple/protocols/jabber/google.c
@@ -0,0 +1,363 @@
+
+/**
+ * Gaim 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#include "internal.h"
+#include "debug.h"
+#include "privacy.h"
+
+#include "buddy.h"
+#include "google.h"
+#include "jabber.h"
+#include "presence.h"
+#include "iq.h"
+
+static void
+jabber_gmail_parse(JabberStream *js, xmlnode *packet, gpointer nul)
+{
+ const char *type = xmlnode_get_attrib(packet, "type");
+ xmlnode *child;
+ xmlnode *message, *sender_node, *subject_node;
+ const char *from, *to, *subject, *url, *tid;
+ const char *in_str;
+ char *to_name;
+ int i, count = 1, returned_count;
+
+ const char **tos, **froms, **subjects, **urls;
+
+ if (strcmp(type, "result"))
+ return;
+
+ child = xmlnode_get_child(packet, "mailbox");
+ if (!child)
+ return;
+
+ in_str = xmlnode_get_attrib(child, "total-matched");
+ if (in_str && *in_str)
+ count = atoi(in_str);
+
+ if (count == 0)
+ return;
+
+ message = xmlnode_get_child(child, "mail-thread-info");
+
+ /* Loop once to see how many messages were returned so we can allocate arrays
+ * accordingly */
+ if (!message)
+ return;
+ for (returned_count = 0; message; returned_count++, message=xmlnode_get_next_twin(message));
+
+ froms = g_new0(const char* , returned_count);
+ tos = g_new0(const char* , returned_count);
+ subjects = g_new0(const char* , returned_count);
+ urls = g_new0(const char* , returned_count);
+
+ to = xmlnode_get_attrib(packet, "to");
+ to_name = jabber_get_bare_jid(to);
+ url = xmlnode_get_attrib(child, "url");
+ if (!url || !*url)
+ url = "http://www.gmail.com";
+
+ message= xmlnode_get_child(child, "mail-thread-info");
+ for (i=0; message; message = xmlnode_get_next_twin(message), i++) {
+ subject_node = xmlnode_get_child(message, "subject");
+ sender_node = xmlnode_get_child(message, "senders");
+ sender_node = xmlnode_get_child(sender_node, "sender");
+
+ while (sender_node && (!xmlnode_get_attrib(sender_node, "unread") ||
+ !strcmp(xmlnode_get_attrib(sender_node, "unread"),"0")))
+ sender_node = xmlnode_get_next_twin(sender_node);
+
+ if (!sender_node) {
+ i--;
+ continue;
+ }
+
+ from = xmlnode_get_attrib(sender_node, "name");
+ if (!from || !*from)
+ from = xmlnode_get_attrib(sender_node, "address");
+ subject = xmlnode_get_data(subject_node);
+ /*
+ * url = xmlnode_get_attrib(message, "url");
+ */
+ tos[i] = (to_name != NULL ? to_name : "");
+ froms[i] = (from != NULL ? from : "");
+ subjects[i] = (subject != NULL ? subject : "");
+ urls[i] = (url != NULL ? url : "");
+
+ tid = xmlnode_get_attrib(message, "tid");
+ if (tid &&
+ (js->gmail_last_tid == NULL || strcmp(tid, js->gmail_last_tid) > 0)) {
+ g_free(js->gmail_last_tid);
+ js->gmail_last_tid = g_strdup(tid);
+ }
+ }
+
+ if (i>0)
+ gaim_notify_emails(js->gc, count, count == returned_count, subjects, froms, tos,
+ urls, NULL, NULL);
+
+ g_free(to_name);
+ g_free(tos);
+ g_free(froms);
+ g_free(subjects);
+ g_free(urls);
+
+ in_str = xmlnode_get_attrib(child, "result-time");
+ if (in_str && *in_str) {
+ g_free(js->gmail_last_time);
+ js->gmail_last_time = g_strdup(in_str);
+ }
+}
+
+void
+jabber_gmail_poke(JabberStream *js, xmlnode *packet)
+{
+ const char *type;
+ xmlnode *query;
+ JabberIq *iq;
+
+ /* bail if the user isn't interested */
+ if (!gaim_account_get_check_mail(js->gc->account))
+ return;
+
+ type = xmlnode_get_attrib(packet, "type");
+
+
+ /* Is this an initial incoming mail notification? If so, send a request for more info */
+ if (strcmp(type, "set") || !xmlnode_get_child(packet, "new-mail"))
+ return;
+
+ gaim_debug(GAIM_DEBUG_MISC, "jabber",
+ "Got new mail notification. Sending request for more info\n");
+
+ iq = jabber_iq_new_query(js, JABBER_IQ_GET, "google:mail:notify");
+ jabber_iq_set_callback(iq, jabber_gmail_parse, NULL);
+ query = xmlnode_get_child(iq->node, "query");
+
+ if (js->gmail_last_time)
+ xmlnode_set_attrib(query, "newer-than-time", js->gmail_last_time);
+ if (js->gmail_last_tid)
+ xmlnode_set_attrib(query, "newer-than-tid", js->gmail_last_tid);
+
+ jabber_iq_send(iq);
+ return;
+}
+
+void jabber_gmail_init(JabberStream *js) {
+ JabberIq *iq;
+
+ if (!gaim_account_get_check_mail(js->gc->account))
+ return;
+
+ iq = jabber_iq_new_query(js, JABBER_IQ_GET, "google:mail:notify");
+ jabber_iq_set_callback(iq, jabber_gmail_parse, NULL);
+ jabber_iq_send(iq);
+}
+
+void jabber_google_roster_init(JabberStream *js)
+{
+ JabberIq *iq;
+ xmlnode *query;
+
+ iq = jabber_iq_new_query(js, JABBER_IQ_GET, "jabber:iq:roster");
+ query = xmlnode_get_child(iq->node, "query");
+
+ xmlnode_set_attrib(query, "xmlns:gr", "google:roster");
+ xmlnode_set_attrib(query, "gr:ext", "2");
+
+ jabber_iq_send(iq);
+}
+
+void jabber_google_roster_outgoing(JabberStream *js, xmlnode *query, xmlnode *item)
+{
+ GaimAccount *account = gaim_connection_get_account(js->gc);
+ GSList *list = account->deny;
+ const char *jid = xmlnode_get_attrib(item, "jid");
+ char *jid_norm = g_strdup(jabber_normalize(account, jid));
+
+ while (list) {
+ if (!strcmp(jid_norm, (char*)list->data)) {
+ xmlnode_set_attrib(query, "xmlns:gr", "google:roster");
+ xmlnode_set_attrib(item, "gr:t", "B");
+ xmlnode_set_attrib(query, "xmlns:gr", "google:roster");
+ xmlnode_set_attrib(query, "gr:ext", "2");
+ return;
+ }
+ list = list->next;
+ }
+
+}
+
+void jabber_google_roster_incoming(JabberStream *js, xmlnode *item)
+{
+ GaimAccount *account = gaim_connection_get_account(js->gc);
+ GSList *list = account->deny;
+ const char *jid = xmlnode_get_attrib(item, "jid");
+ gboolean on_block_list = FALSE;
+
+ char *jid_norm = g_strdup(jabber_normalize(account, jid));
+
+ const char *grt = xmlnode_get_attrib_with_namespace(item, "t", "google:roster");
+
+ while (list) {
+ if (!strcmp(jid_norm, (char*)list->data)) {
+ on_block_list = TRUE;
+ break;
+ }
+ list = list->next;
+ }
+
+ if (!on_block_list && (grt && (*grt == 'B' || *grt == 'b'))) {
+ gaim_debug_info("jabber", "Blocking %s\n", jid_norm);
+ gaim_privacy_deny_add(account, jid_norm, TRUE);
+ } else if (on_block_list && (!grt || (*grt != 'B' && *grt != 'b' ))){
+ gaim_debug_info("jabber", "Unblocking %s\n", jid_norm);
+ gaim_privacy_deny_remove(account, jid_norm, TRUE);
+ }
+}
+
+void jabber_google_roster_add_deny(GaimConnection *gc, const char *who)
+{
+ JabberStream *js;
+ GSList *buddies;
+ JabberIq *iq;
+ xmlnode *query;
+ xmlnode *item;
+ xmlnode *group;
+ GaimBuddy *b;
+ JabberBuddy *jb;
+
+ js = (JabberStream*)(gc->proto_data);
+
+ if (!js || !js->server_caps & JABBER_CAP_GOOGLE_ROSTER)
+ return;
+
+ jb = jabber_buddy_find(js, who, TRUE);
+
+ buddies = gaim_find_buddies(js->gc->account, who);
+ if(!buddies)
+ return;
+
+ b = buddies->data;
+
+ iq = jabber_iq_new_query(js, JABBER_IQ_SET, "jabber:iq:roster");
+
+ query = xmlnode_get_child(iq->node, "query");
+ item = xmlnode_new_child(query, "item");
+
+ while(buddies) {
+ GaimGroup *g;
+
+ b = buddies->data;
+ g = gaim_buddy_get_group(b);
+
+ group = xmlnode_new_child(item, "group");
+ xmlnode_insert_data(group, g->name, -1);
+
+ buddies = buddies->next;
+ }
+
+ iq = jabber_iq_new_query(js, JABBER_IQ_SET, "jabber:iq:roster");
+
+ query = xmlnode_get_child(iq->node, "query");
+ item = xmlnode_new_child(query, "item");
+
+ xmlnode_set_attrib(item, "jid", who);
+ xmlnode_set_attrib(item, "name", b->alias ? b->alias : "");
+ xmlnode_set_attrib(item, "gr:t", "B");
+ xmlnode_set_attrib(query, "xmlns:gr", "google:roster");
+ xmlnode_set_attrib(query, "gr:ext", "2");
+
+ jabber_iq_send(iq);
+
+ /* Synthesize a sign-off */
+ if (jb) {
+ JabberBuddyResource *jbr;
+ GList *l = jb->resources;
+ while (l) {
+ jbr = l->data;
+ if (jbr && jbr->name)
+ {
+ gaim_debug(GAIM_DEBUG_MISC, "jabber", "Removing resource %s\n", jbr->name);
+ jabber_buddy_remove_resource(jb, jbr->name);
+ }
+ l = l->next;
+ }
+ }
+ gaim_prpl_got_user_status(gaim_connection_get_account(gc), who, "offline", NULL);
+}
+
+void jabber_google_roster_rem_deny(GaimConnection *gc, const char *who)
+{
+ JabberStream *js;
+ GSList *buddies;
+ JabberIq *iq;
+ xmlnode *query;
+ xmlnode *item;
+ xmlnode *group;
+ GaimBuddy *b;
+
+ g_return_if_fail(gc != NULL);
+ g_return_if_fail(who != NULL);
+
+ js = (JabberStream*)(gc->proto_data);
+
+ if (!js || !js->server_caps & JABBER_CAP_GOOGLE_ROSTER)
+ return;
+
+ buddies = gaim_find_buddies(js->gc->account, who);
+ if(!buddies)
+ return;
+
+ b = buddies->data;
+
+ iq = jabber_iq_new_query(js, JABBER_IQ_SET, "jabber:iq:roster");
+
+ query = xmlnode_get_child(iq->node, "query");
+ item = xmlnode_new_child(query, "item");
+
+ while(buddies) {
+ GaimGroup *g;
+
+ b = buddies->data;
+ g = gaim_buddy_get_group(b);
+
+ group = xmlnode_new_child(item, "group");
+ xmlnode_insert_data(group, g->name, -1);
+
+ buddies = buddies->next;
+ }
+
+ iq = jabber_iq_new_query(js, JABBER_IQ_SET, "jabber:iq:roster");
+
+ query = xmlnode_get_child(iq->node, "query");
+ item = xmlnode_new_child(query, "item");
+
+ xmlnode_set_attrib(item, "jid", who);
+ xmlnode_set_attrib(item, "name", b->alias ? b->alias : "");
+ xmlnode_set_attrib(query, "xmlns:gr", "google:roster");
+ xmlnode_set_attrib(query, "gr:ext", "2");
+
+ jabber_iq_send(iq);
+
+ /* See if he's online */
+ jabber_presence_subscription_set(js, who, "probe");
+}
diff --git a/libpurple/protocols/jabber/google.h b/libpurple/protocols/jabber/google.h
new file mode 100644
index 0000000000..ac342450bc
--- /dev/null
+++ b/libpurple/protocols/jabber/google.h
@@ -0,0 +1,40 @@
+/**
+ * Gaim 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#ifndef _GAIM_GOOGLE_H_
+#define _GAIM_GOOGLE_H_
+
+/* This is a place for Google Talk-specific XMPP extensions to live
+ * such that they don't intermingle with code for the XMPP RFCs and XEPs :) */
+
+#include "jabber.h"
+
+void jabber_gmail_init(JabberStream *js);
+void jabber_gmail_poke(JabberStream *js, xmlnode *node);
+
+void jabber_google_roster_init(JabberStream *js);
+void jabber_google_roster_outgoing(JabberStream *js, xmlnode *query, xmlnode *item);
+void jabber_google_roster_incoming(JabberStream *js, xmlnode *item);
+void jabber_google_roster_add_deny(GaimConnection *gc, const char *who);
+void jabber_google_roster_rem_deny(GaimConnection *gc, const char *who);
+
+
+
+#endif /* _GAIM_GOOGLE_H_ */
diff --git a/libpurple/protocols/jabber/iq.c b/libpurple/protocols/jabber/iq.c
new file mode 100644
index 0000000000..ce46831b41
--- /dev/null
+++ b/libpurple/protocols/jabber/iq.c
@@ -0,0 +1,336 @@
+/*
+ * gaim - Jabber Protocol Plugin
+ *
+ * Copyright (C) 2003, Nathan Walp <faceprint@faceprint.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ */
+#include "internal.h"
+#include "debug.h"
+#include "prefs.h"
+#include "util.h"
+
+#include "buddy.h"
+#include "disco.h"
+#include "google.h"
+#include "iq.h"
+#include "oob.h"
+#include "roster.h"
+#include "si.h"
+
+#ifdef _WIN32
+#include "utsname.h"
+#endif
+
+GHashTable *iq_handlers = NULL;
+
+
+JabberIq *jabber_iq_new(JabberStream *js, JabberIqType type)
+{
+ JabberIq *iq;
+
+ iq = g_new0(JabberIq, 1);
+
+ iq->type = type;
+
+ iq->node = xmlnode_new("iq");
+ switch(iq->type) {
+ case JABBER_IQ_SET:
+ xmlnode_set_attrib(iq->node, "type", "set");
+ break;
+ case JABBER_IQ_GET:
+ xmlnode_set_attrib(iq->node, "type", "get");
+ break;
+ case JABBER_IQ_ERROR:
+ xmlnode_set_attrib(iq->node, "type", "error");
+ break;
+ case JABBER_IQ_RESULT:
+ xmlnode_set_attrib(iq->node, "type", "result");
+ break;
+ case JABBER_IQ_NONE:
+ /* this shouldn't ever happen */
+ break;
+ }
+
+ iq->js = js;
+
+ if(type == JABBER_IQ_GET || type == JABBER_IQ_SET) {
+ iq->id = jabber_get_next_id(js);
+ xmlnode_set_attrib(iq->node, "id", iq->id);
+ }
+
+ return iq;
+}
+
+JabberIq *jabber_iq_new_query(JabberStream *js, JabberIqType type,
+ const char *xmlns)
+{
+ JabberIq *iq = jabber_iq_new(js, type);
+ xmlnode *query;
+
+ query = xmlnode_new_child(iq->node, "query");
+ xmlnode_set_namespace(query, xmlns);
+
+ return iq;
+}
+
+typedef struct _JabberCallbackData {
+ JabberIqCallback *callback;
+ gpointer data;
+} JabberCallbackData;
+
+void
+jabber_iq_set_callback(JabberIq *iq, JabberIqCallback *callback, gpointer data)
+{
+ iq->callback = callback;
+ iq->callback_data = data;
+}
+
+void jabber_iq_set_id(JabberIq *iq, const char *id)
+{
+ if(iq->id)
+ g_free(iq->id);
+
+ if(id) {
+ xmlnode_set_attrib(iq->node, "id", id);
+ iq->id = g_strdup(id);
+ } else {
+ xmlnode_remove_attrib(iq->node, "id");
+ iq->id = NULL;
+ }
+}
+
+void jabber_iq_send(JabberIq *iq)
+{
+ JabberCallbackData *jcd;
+ g_return_if_fail(iq != NULL);
+
+ jabber_send(iq->js, iq->node);
+
+ if(iq->id && iq->callback) {
+ jcd = g_new0(JabberCallbackData, 1);
+ jcd->callback = iq->callback;
+ jcd->data = iq->callback_data;
+ g_hash_table_insert(iq->js->iq_callbacks, g_strdup(iq->id), jcd);
+ }
+
+ jabber_iq_free(iq);
+}
+
+void jabber_iq_free(JabberIq *iq)
+{
+ g_return_if_fail(iq != NULL);
+
+ g_free(iq->id);
+ xmlnode_free(iq->node);
+ g_free(iq);
+}
+
+static void jabber_iq_last_parse(JabberStream *js, xmlnode *packet)
+{
+ JabberIq *iq;
+ const char *type;
+ const char *from;
+ const char *id;
+ xmlnode *query;
+ char *idle_time;
+
+ type = xmlnode_get_attrib(packet, "type");
+ from = xmlnode_get_attrib(packet, "from");
+ id = xmlnode_get_attrib(packet, "id");
+
+ if(type && !strcmp(type, "get")) {
+ iq = jabber_iq_new_query(js, JABBER_IQ_RESULT, "jabber:iq:last");
+ jabber_iq_set_id(iq, id);
+ xmlnode_set_attrib(iq->node, "to", from);
+
+ query = xmlnode_get_child(iq->node, "query");
+
+ idle_time = g_strdup_printf("%ld", js->idle ? time(NULL) - js->idle : 0);
+ xmlnode_set_attrib(query, "seconds", idle_time);
+ g_free(idle_time);
+
+ jabber_iq_send(iq);
+ }
+}
+
+static void jabber_iq_time_parse(JabberStream *js, xmlnode *packet)
+{
+ const char *type, *from, *id;
+ JabberIq *iq;
+ xmlnode *query;
+ time_t now_t;
+ struct tm *now;
+
+ time(&now_t);
+ now = localtime(&now_t);
+
+ type = xmlnode_get_attrib(packet, "type");
+ from = xmlnode_get_attrib(packet, "from");
+ id = xmlnode_get_attrib(packet, "id");
+
+ if(type && !strcmp(type, "get")) {
+ const char *date;
+
+ iq = jabber_iq_new_query(js, JABBER_IQ_RESULT, "jabber:iq:time");
+ jabber_iq_set_id(iq, id);
+ xmlnode_set_attrib(iq->node, "to", from);
+
+ query = xmlnode_get_child(iq->node, "query");
+
+ date = gaim_utf8_strftime("%Y%m%dT%T", now);
+ xmlnode_insert_data(xmlnode_new_child(query, "utc"), date, -1);
+
+ date = gaim_utf8_strftime("%Z", now);
+ xmlnode_insert_data(xmlnode_new_child(query, "tz"), date, -1);
+
+ date = gaim_utf8_strftime("%d %b %Y %T", now);
+ xmlnode_insert_data(xmlnode_new_child(query, "display"), date, -1);
+
+ jabber_iq_send(iq);
+ }
+}
+
+static void jabber_iq_version_parse(JabberStream *js, xmlnode *packet)
+{
+ JabberIq *iq;
+ const char *type, *from, *id;
+ xmlnode *query;
+ char *os = NULL;
+
+ type = xmlnode_get_attrib(packet, "type");
+
+ if(type && !strcmp(type, "get")) {
+
+ if(!gaim_prefs_get_bool("/plugins/prpl/jabber/hide_os")) {
+ struct utsname osinfo;
+
+ uname(&osinfo);
+ os = g_strdup_printf("%s %s %s", osinfo.sysname, osinfo.release,
+ osinfo.machine);
+ }
+
+ from = xmlnode_get_attrib(packet, "from");
+ id = xmlnode_get_attrib(packet, "id");
+
+ iq = jabber_iq_new_query(js, JABBER_IQ_RESULT, "jabber:iq:version");
+ xmlnode_set_attrib(iq->node, "to", from);
+ jabber_iq_set_id(iq, id);
+
+ query = xmlnode_get_child(iq->node, "query");
+
+ xmlnode_insert_data(xmlnode_new_child(query, "name"), PACKAGE, -1);
+ xmlnode_insert_data(xmlnode_new_child(query, "version"), VERSION, -1);
+ if(os) {
+ xmlnode_insert_data(xmlnode_new_child(query, "os"), os, -1);
+ g_free(os);
+ }
+
+ jabber_iq_send(iq);
+ }
+}
+
+void jabber_iq_remove_callback_by_id(JabberStream *js, const char *id)
+{
+ g_hash_table_remove(js->iq_callbacks, id);
+}
+
+void jabber_iq_parse(JabberStream *js, xmlnode *packet)
+{
+ JabberCallbackData *jcd;
+ xmlnode *query, *error, *x;
+ const char *xmlns;
+ const char *type, *id, *from;
+ JabberIqHandler *jih;
+
+ query = xmlnode_get_child(packet, "query");
+ type = xmlnode_get_attrib(packet, "type");
+ from = xmlnode_get_attrib(packet, "from");
+ id = xmlnode_get_attrib(packet, "id");
+
+ /* First, lets see if a special callback got registered */
+
+ if(type && (!strcmp(type, "result") || !strcmp(type, "error"))) {
+ if(id && *id && (jcd = g_hash_table_lookup(js->iq_callbacks, id))) {
+ jcd->callback(js, packet, jcd->data);
+ jabber_iq_remove_callback_by_id(js, id);
+ return;
+ }
+ }
+
+ /* Apparently not, so lets see if we have a pre-defined handler */
+
+ if(type && query && (xmlns = xmlnode_get_namespace(query))) {
+ if((jih = g_hash_table_lookup(iq_handlers, xmlns))) {
+ jih(js, packet);
+ return;
+ }
+ }
+
+ if(xmlnode_get_child_with_namespace(packet, "si", "http://jabber.org/protocol/si")) {
+ jabber_si_parse(js, packet);
+ return;
+ }
+
+ if(xmlnode_get_child_with_namespace(packet, "new-mail", "google:mail:notify")) {
+ jabber_gmail_poke(js, packet);
+ return;
+ }
+
+ /* If we get here, send the default error reply mandated by XMPP-CORE */
+ if(type && (!strcmp(type, "set") || !strcmp(type, "get"))) {
+ JabberIq *iq = jabber_iq_new(js, JABBER_IQ_ERROR);
+
+ xmlnode_free(iq->node);
+ iq->node = xmlnode_copy(packet);
+ xmlnode_set_attrib(iq->node, "to", from);
+ xmlnode_remove_attrib(iq->node, "from");
+ xmlnode_set_attrib(iq->node, "type", "error");
+ error = xmlnode_new_child(iq->node, "error");
+ xmlnode_set_attrib(error, "type", "cancel");
+ xmlnode_set_attrib(error, "code", "501");
+ x = xmlnode_new_child(error, "feature-not-implemented");
+ xmlnode_set_namespace(x, "urn:ietf:params:xml:ns:xmpp-stanzas");
+
+ jabber_iq_send(iq);
+ }
+}
+
+void jabber_iq_register_handler(const char *xmlns, JabberIqHandler handlerfunc)
+{
+ g_hash_table_replace(iq_handlers, g_strdup(xmlns), handlerfunc);
+}
+
+void jabber_iq_init(void)
+{
+ iq_handlers = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL);
+
+ jabber_iq_register_handler("jabber:iq:roster", jabber_roster_parse);
+ jabber_iq_register_handler("jabber:iq:oob", jabber_oob_parse);
+ jabber_iq_register_handler("http://jabber.org/protocol/bytestreams", jabber_bytestreams_parse);
+ jabber_iq_register_handler("jabber:iq:last", jabber_iq_last_parse);
+ jabber_iq_register_handler("jabber:iq:time", jabber_iq_time_parse);
+ jabber_iq_register_handler("jabber:iq:version", jabber_iq_version_parse);
+ jabber_iq_register_handler("http://jabber.org/protocol/disco#info", jabber_disco_info_parse);
+ jabber_iq_register_handler("http://jabber.org/protocol/disco#items", jabber_disco_items_parse);
+ jabber_iq_register_handler("jabber:iq:register", jabber_register_parse);
+}
+
+void jabber_iq_uninit(void)
+{
+ g_hash_table_destroy(iq_handlers);
+}
+
diff --git a/libpurple/protocols/jabber/iq.h b/libpurple/protocols/jabber/iq.h
new file mode 100644
index 0000000000..acde9ed681
--- /dev/null
+++ b/libpurple/protocols/jabber/iq.h
@@ -0,0 +1,70 @@
+/**
+ * @file iq.h JabberID handlers
+ *
+ * gaim
+ *
+ * Copyright (C) 2003 Nathan Walp <faceprint@faceprint.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+#ifndef _GAIM_JABBER_IQ_H_
+#define _GAIM_JABBER_IQ_H_
+
+#include "jabber.h"
+
+typedef struct _JabberIq JabberIq;
+
+typedef enum {
+ JABBER_IQ_SET,
+ JABBER_IQ_GET,
+ JABBER_IQ_RESULT,
+ JABBER_IQ_ERROR,
+ JABBER_IQ_NONE
+} JabberIqType;
+
+typedef void (JabberIqHandler)(JabberStream *js, xmlnode *packet);
+
+typedef void (JabberIqCallback)(JabberStream *js, xmlnode *packet, gpointer data);
+
+struct _JabberIq {
+ JabberIqType type;
+ char *id;
+ xmlnode *node;
+
+ JabberIqCallback *callback;
+ gpointer callback_data;
+
+ JabberStream *js;
+};
+
+JabberIq *jabber_iq_new(JabberStream *js, JabberIqType type);
+JabberIq *jabber_iq_new_query(JabberStream *js, JabberIqType type,
+ const char *xmlns);
+
+void jabber_iq_parse(JabberStream *js, xmlnode *packet);
+
+void jabber_iq_remove_callback_by_id(JabberStream *js, const char *id);
+void jabber_iq_set_callback(JabberIq *iq, JabberIqCallback *cb, gpointer data);
+void jabber_iq_set_id(JabberIq *iq, const char *id);
+
+void jabber_iq_send(JabberIq *iq);
+void jabber_iq_free(JabberIq *iq);
+
+void jabber_iq_init(void);
+void jabber_iq_uninit(void);
+
+void jabber_iq_register_handler(const char *xmlns, JabberIqHandler *func);
+
+#endif /* _GAIM_JABBER_IQ_H_ */
diff --git a/libpurple/protocols/jabber/jabber.c b/libpurple/protocols/jabber/jabber.c
new file mode 100644
index 0000000000..7c6ca5343f
--- /dev/null
+++ b/libpurple/protocols/jabber/jabber.c
@@ -0,0 +1,2022 @@
+/*
+ * gaim - Jabber Protocol Plugin
+ *
+ * Copyright (C) 2003, Nathan Walp <faceprint@faceprint.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ */
+#include "internal.h"
+
+#include "account.h"
+#include "accountopt.h"
+#include "blist.h"
+#include "cmds.h"
+#include "connection.h"
+#include "debug.h"
+#include "dnssrv.h"
+#include "message.h"
+#include "notify.h"
+#include "pluginpref.h"
+#include "proxy.h"
+#include "prpl.h"
+#include "request.h"
+#include "server.h"
+#include "util.h"
+#include "version.h"
+
+#include "auth.h"
+#include "buddy.h"
+#include "chat.h"
+#include "disco.h"
+#include "google.h"
+#include "iq.h"
+#include "jutil.h"
+#include "message.h"
+#include "parser.h"
+#include "presence.h"
+#include "jabber.h"
+#include "roster.h"
+#include "si.h"
+#include "xdata.h"
+
+#define JABBER_CONNECT_STEPS (js->gsc ? 8 : 5)
+
+static GaimPlugin *my_protocol = NULL;
+
+static void jabber_stream_init(JabberStream *js)
+{
+ char *open_stream;
+
+ open_stream = g_strdup_printf("<stream:stream to='%s' "
+ "xmlns='jabber:client' "
+ "xmlns:stream='http://etherx.jabber.org/streams' "
+ "version='1.0'>",
+ js->user->domain);
+ /* setup the parser fresh for each stream */
+ jabber_parser_setup(js);
+ jabber_send_raw(js, open_stream, -1);
+ js->reinit = FALSE;
+ g_free(open_stream);
+}
+
+static void
+jabber_session_initialized_cb(JabberStream *js, xmlnode *packet, gpointer data)
+{
+ const char *type = xmlnode_get_attrib(packet, "type");
+ if(type && !strcmp(type, "result")) {
+ jabber_stream_set_state(js, JABBER_STREAM_CONNECTED);
+ } else {
+ gaim_connection_error(js->gc, _("Error initializing session"));
+ }
+}
+
+static void jabber_session_init(JabberStream *js)
+{
+ JabberIq *iq = jabber_iq_new(js, JABBER_IQ_SET);
+ xmlnode *session;
+
+ jabber_iq_set_callback(iq, jabber_session_initialized_cb, NULL);
+
+ session = xmlnode_new_child(iq->node, "session");
+ xmlnode_set_namespace(session, "urn:ietf:params:xml:ns:xmpp-session");
+
+ jabber_iq_send(iq);
+}
+
+static void jabber_bind_result_cb(JabberStream *js, xmlnode *packet,
+ gpointer data)
+{
+ const char *type = xmlnode_get_attrib(packet, "type");
+ xmlnode *bind;
+
+ if(type && !strcmp(type, "result") &&
+ (bind = xmlnode_get_child_with_namespace(packet, "bind", "urn:ietf:params:xml:ns:xmpp-bind"))) {
+ xmlnode *jid;
+ char *full_jid;
+ if((jid = xmlnode_get_child(bind, "jid")) && (full_jid = xmlnode_get_data(jid))) {
+ JabberBuddy *my_jb = NULL;
+ jabber_id_free(js->user);
+ if(!(js->user = jabber_id_new(full_jid))) {
+ gaim_connection_error(js->gc, _("Invalid response from server."));
+ }
+ if((my_jb = jabber_buddy_find(js, full_jid, TRUE)))
+ my_jb->subscription |= JABBER_SUB_BOTH;
+ g_free(full_jid);
+ }
+ } else {
+ char *msg = jabber_parse_error(js, packet);
+ gaim_connection_error(js->gc, msg);
+ g_free(msg);
+ }
+
+ jabber_session_init(js);
+}
+
+static void jabber_stream_features_parse(JabberStream *js, xmlnode *packet)
+{
+ if(xmlnode_get_child(packet, "starttls")) {
+ if(jabber_process_starttls(js, packet))
+ return;
+ }
+
+ if(js->registration) {
+ jabber_register_start(js);
+ } else if(xmlnode_get_child(packet, "mechanisms")) {
+ jabber_auth_start(js, packet);
+ } else if(xmlnode_get_child(packet, "bind")) {
+ xmlnode *bind, *resource;
+ JabberIq *iq = jabber_iq_new(js, JABBER_IQ_SET);
+ bind = xmlnode_new_child(iq->node, "bind");
+ xmlnode_set_namespace(bind, "urn:ietf:params:xml:ns:xmpp-bind");
+ resource = xmlnode_new_child(bind, "resource");
+ xmlnode_insert_data(resource, js->user->resource, -1);
+
+ jabber_iq_set_callback(iq, jabber_bind_result_cb, NULL);
+
+ jabber_iq_send(iq);
+ } else /* if(xmlnode_get_child_with_namespace(packet, "auth")) */ {
+ /* If we get an empty stream:features packet, or we explicitly get
+ * an auth feature with namespace http://jabber.org/features/iq-auth
+ * we should revert back to iq:auth authentication, even though we're
+ * connecting to an XMPP server. */
+ js->auth_type = JABBER_AUTH_IQ_AUTH;
+ jabber_stream_set_state(js, JABBER_STREAM_AUTHENTICATING);
+ }
+}
+
+static void jabber_stream_handle_error(JabberStream *js, xmlnode *packet)
+{
+ char *msg = jabber_parse_error(js, packet);
+
+ gaim_connection_error(js->gc, msg);
+ g_free(msg);
+}
+
+static void tls_init(JabberStream *js);
+
+void jabber_process_packet(JabberStream *js, xmlnode *packet)
+{
+ gaim_signal_emit(my_protocol, "jabber-receiving-xmlnode", js->gc, &packet);
+
+ /* if the signal leaves us with a null packet, we're done */
+ if(NULL == packet)
+ return;
+
+ if(!strcmp(packet->name, "iq")) {
+ jabber_iq_parse(js, packet);
+ } else if(!strcmp(packet->name, "presence")) {
+ jabber_presence_parse(js, packet);
+ } else if(!strcmp(packet->name, "message")) {
+ jabber_message_parse(js, packet);
+ } else if(!strcmp(packet->name, "stream:features")) {
+ jabber_stream_features_parse(js, packet);
+ } else if (!strcmp(packet->name, "features") &&
+ !strcmp(xmlnode_get_namespace(packet), "http://etherx.jabber.org/streams")) {
+ jabber_stream_features_parse(js, packet);
+ } else if(!strcmp(packet->name, "stream:error")) {
+ jabber_stream_handle_error(js, packet);
+ } else if (!strcmp(packet->name, "error") &&
+ !strcmp(xmlnode_get_namespace(packet), "http://etherx.jabber.org/streams")) {
+ jabber_stream_handle_error(js, packet);
+ } else if(!strcmp(packet->name, "challenge")) {
+ if(js->state == JABBER_STREAM_AUTHENTICATING)
+ jabber_auth_handle_challenge(js, packet);
+ } else if(!strcmp(packet->name, "success")) {
+ if(js->state == JABBER_STREAM_AUTHENTICATING)
+ jabber_auth_handle_success(js, packet);
+ } else if(!strcmp(packet->name, "failure")) {
+ if(js->state == JABBER_STREAM_AUTHENTICATING)
+ jabber_auth_handle_failure(js, packet);
+ } else if(!strcmp(packet->name, "proceed")) {
+ if(js->state == JABBER_STREAM_AUTHENTICATING && !js->gsc)
+ tls_init(js);
+ } else {
+ gaim_debug(GAIM_DEBUG_WARNING, "jabber", "Unknown packet: %s\n",
+ packet->name);
+ }
+}
+
+static int jabber_do_send(JabberStream *js, const char *data, int len)
+{
+ int ret;
+
+ if (js->gsc)
+ ret = gaim_ssl_write(js->gsc, data, len);
+ else
+ ret = write(js->fd, data, len);
+
+ return ret;
+}
+
+static void jabber_send_cb(gpointer data, gint source, GaimInputCondition cond)
+{
+ JabberStream *js = data;
+ int ret, writelen;
+ writelen = gaim_circ_buffer_get_max_read(js->write_buffer);
+
+ if (writelen == 0) {
+ gaim_input_remove(js->writeh);
+ js->writeh = 0;
+ return;
+ }
+
+ ret = jabber_do_send(js, js->write_buffer->outptr, writelen);
+
+ if (ret < 0 && errno == EAGAIN)
+ return;
+ else if (ret <= 0) {
+ gaim_connection_error(js->gc, _("Write error"));
+ return;
+ }
+
+ gaim_circ_buffer_mark_read(js->write_buffer, ret);
+}
+
+void jabber_send_raw(JabberStream *js, const char *data, int len)
+{
+ int ret;
+
+ /* because printing a tab to debug every minute gets old */
+ if(strcmp(data, "\t"))
+ gaim_debug(GAIM_DEBUG_MISC, "jabber", "Sending%s: %s\n",
+ js->gsc ? " (ssl)" : "", data);
+
+ /* If we've got a security layer, we need to encode the data,
+ * splitting it on the maximum buffer length negotiated */
+
+ gaim_signal_emit(my_protocol, "jabber-sending-text", js->gc, &data);
+ if (data == NULL)
+ return;
+
+#ifdef HAVE_CYRUS_SASL
+ if (js->sasl_maxbuf>0) {
+ int pos;
+
+ if (!js->gsc && js->fd<0)
+ return;
+ pos = 0;
+ if (len == -1)
+ len = strlen(data);
+ while (pos < len) {
+ int towrite;
+ const char *out;
+ unsigned olen;
+
+ if ((len - pos) < js->sasl_maxbuf)
+ towrite = len - pos;
+ else
+ towrite = js->sasl_maxbuf;
+
+ sasl_encode(js->sasl, &data[pos], towrite, &out, &olen);
+ pos += towrite;
+
+ if (js->writeh > 0)
+ ret = jabber_do_send(js, out, olen);
+ else {
+ ret = -1;
+ errno = EAGAIN;
+ }
+
+ if (ret < 0 && errno != EAGAIN)
+ gaim_connection_error(js->gc, _("Write error"));
+ else if (ret < olen) {
+ if (ret < 0)
+ ret = 0;
+ if (js->writeh == 0)
+ js->writeh = gaim_input_add(
+ js->gsc ? js->gsc->fd : js->fd,
+ GAIM_INPUT_WRITE,
+ jabber_send_cb, js);
+ gaim_circ_buffer_append(js->write_buffer,
+ out + ret, olen - ret);
+ }
+ }
+ return;
+ }
+#endif
+
+ if (len == -1)
+ len = strlen(data);
+
+ if (js->writeh == 0)
+ ret = jabber_do_send(js, data, len);
+ else {
+ ret = -1;
+ errno = EAGAIN;
+ }
+
+ if (ret < 0 && errno != EAGAIN)
+ gaim_connection_error(js->gc, _("Write error"));
+ else if (ret < len) {
+ if (ret < 0)
+ ret = 0;
+ if (js->writeh == 0)
+ js->writeh = gaim_input_add(
+ js->gsc ? js->gsc->fd : js->fd,
+ GAIM_INPUT_WRITE, jabber_send_cb, js);
+ gaim_circ_buffer_append(js->write_buffer,
+ data + ret, len - ret);
+ }
+ return;
+}
+
+static int jabber_prpl_send_raw(GaimConnection *gc, const char *buf, int len)
+{
+ JabberStream *js = (JabberStream*)gc->proto_data;
+ jabber_send_raw(js, buf, len);
+ return len;
+}
+
+void jabber_send(JabberStream *js, xmlnode *packet)
+{
+ char *txt;
+ int len;
+
+ gaim_signal_emit(my_protocol, "jabber-sending-xmlnode", js->gc, &packet);
+
+ /* if we get NULL back, we're done processing */
+ if(NULL == packet)
+ return;
+
+ txt = xmlnode_to_str(packet, &len);
+ jabber_send_raw(js, txt, len);
+ g_free(txt);
+}
+
+static void jabber_keepalive(GaimConnection *gc)
+{
+ jabber_send_raw(gc->proto_data, "\t", -1);
+}
+
+static void
+jabber_recv_cb_ssl(gpointer data, GaimSslConnection *gsc,
+ GaimInputCondition cond)
+{
+ GaimConnection *gc = data;
+ JabberStream *js = gc->proto_data;
+ int len;
+ static char buf[4096];
+
+ /* TODO: It should be possible to make this check unnecessary */
+ if(!GAIM_CONNECTION_IS_VALID(gc)) {
+ gaim_ssl_close(gsc);
+ return;
+ }
+
+ while((len = gaim_ssl_read(gsc, buf, sizeof(buf) - 1)) > 0) {
+ buf[len] = '\0';
+ gaim_debug(GAIM_DEBUG_INFO, "jabber", "Recv (ssl)(%d): %s\n", len, buf);
+ jabber_parser_process(js, buf, len);
+ if(js->reinit)
+ jabber_stream_init(js);
+ }
+
+ if(errno == EAGAIN)
+ return;
+ else
+ gaim_connection_error(gc, _("Read Error"));
+}
+
+static void
+jabber_recv_cb(gpointer data, gint source, GaimInputCondition condition)
+{
+ GaimConnection *gc = data;
+ JabberStream *js = gc->proto_data;
+ int len;
+ static char buf[4096];
+
+ if(!GAIM_CONNECTION_IS_VALID(gc))
+ return;
+
+ if((len = read(js->fd, buf, sizeof(buf) - 1)) > 0) {
+#ifdef HAVE_CYRUS_SASL
+ if (js->sasl_maxbuf>0) {
+ const char *out;
+ unsigned int olen;
+ sasl_decode(js->sasl, buf, len, &out, &olen);
+ if (olen>0) {
+ gaim_debug(GAIM_DEBUG_INFO, "jabber", "RecvSASL (%u): %s\n", olen, out);
+ jabber_parser_process(js,out,olen);
+ if(js->reinit)
+ jabber_stream_init(js);
+ }
+ return;
+ }
+#endif
+ buf[len] = '\0';
+ gaim_debug(GAIM_DEBUG_INFO, "jabber", "Recv (%d): %s\n", len, buf);
+ jabber_parser_process(js, buf, len);
+ if(js->reinit)
+ jabber_stream_init(js);
+ } else if(errno == EAGAIN) {
+ return;
+ } else {
+ gaim_connection_error(gc, _("Read Error"));
+ }
+}
+
+static void
+jabber_login_callback_ssl(gpointer data, GaimSslConnection *gsc,
+ GaimInputCondition cond)
+{
+ GaimConnection *gc = data;
+ JabberStream *js;
+
+ /* TODO: It should be possible to make this check unnecessary */
+ if(!GAIM_CONNECTION_IS_VALID(gc)) {
+ gaim_ssl_close(gsc);
+ return;
+ }
+
+ js = gc->proto_data;
+
+ if(js->state == JABBER_STREAM_CONNECTING)
+ jabber_send_raw(js, "<?xml version='1.0' ?>", -1);
+ jabber_stream_set_state(js, JABBER_STREAM_INITIALIZING);
+ gaim_ssl_input_add(gsc, jabber_recv_cb_ssl, gc);
+}
+
+
+static void
+jabber_login_callback(gpointer data, gint source, const gchar *error)
+{
+ GaimConnection *gc = data;
+ JabberStream *js = gc->proto_data;
+
+ if (source < 0) {
+ gaim_connection_error(gc, _("Couldn't connect to host"));
+ return;
+ }
+
+ js->fd = source;
+
+ if(js->state == JABBER_STREAM_CONNECTING)
+ jabber_send_raw(js, "<?xml version='1.0' ?>", -1);
+
+ jabber_stream_set_state(js, JABBER_STREAM_INITIALIZING);
+ gc->inpa = gaim_input_add(js->fd, GAIM_INPUT_READ, jabber_recv_cb, gc);
+}
+
+static void
+jabber_ssl_connect_failure(GaimSslConnection *gsc, GaimSslErrorType error,
+ gpointer data)
+{
+ GaimConnection *gc = data;
+ JabberStream *js = gc->proto_data;
+
+ js->gsc = NULL;
+
+ switch(error) {
+ case GAIM_SSL_CONNECT_FAILED:
+ gaim_connection_error(gc, _("Connection Failed"));
+ break;
+ case GAIM_SSL_HANDSHAKE_FAILED:
+ gaim_connection_error(gc, _("SSL Handshake Failed"));
+ break;
+ }
+}
+
+static void tls_init(JabberStream *js)
+{
+ gaim_input_remove(js->gc->inpa);
+ js->gc->inpa = 0;
+ js->gsc = gaim_ssl_connect_fd(js->gc->account, js->fd,
+ jabber_login_callback_ssl, jabber_ssl_connect_failure, js->gc);
+}
+
+static void jabber_login_connect(JabberStream *js, const char *server, int port)
+{
+ if (gaim_proxy_connect(js->gc, js->gc->account, server,
+ port, jabber_login_callback, js->gc) == NULL)
+ gaim_connection_error(js->gc, _("Unable to create socket"));
+}
+
+static void srv_resolved_cb(GaimSrvResponse *resp, int results, gpointer data)
+{
+ JabberStream *js;
+
+ js = data;
+ js->srv_query_data = NULL;
+
+ if(results) {
+ jabber_login_connect(js, resp->hostname, resp->port);
+ g_free(resp);
+ } else {
+ jabber_login_connect(js, js->user->domain,
+ gaim_account_get_int(js->gc->account, "port", 5222));
+ }
+}
+
+
+
+static void
+jabber_login(GaimAccount *account)
+{
+ GaimConnection *gc = gaim_account_get_connection(account);
+ const char *connect_server = gaim_account_get_string(account,
+ "connect_server", "");
+ JabberStream *js;
+ JabberBuddy *my_jb = NULL;
+
+ gc->flags |= GAIM_CONNECTION_HTML;
+ js = gc->proto_data = g_new0(JabberStream, 1);
+ js->gc = gc;
+ js->fd = -1;
+ js->iq_callbacks = g_hash_table_new_full(g_str_hash, g_str_equal,
+ g_free, g_free);
+ js->disco_callbacks = g_hash_table_new_full(g_str_hash, g_str_equal,
+ g_free, g_free);
+ js->buddies = g_hash_table_new_full(g_str_hash, g_str_equal,
+ g_free, (GDestroyNotify)jabber_buddy_free);
+ js->chats = g_hash_table_new_full(g_str_hash, g_str_equal,
+ g_free, (GDestroyNotify)jabber_chat_free);
+ js->chat_servers = g_list_append(NULL, g_strdup("conference.jabber.org"));
+ js->user = jabber_id_new(gaim_account_get_username(account));
+ js->next_id = g_random_int();
+ js->write_buffer = gaim_circ_buffer_new(512);
+
+ if(!js->user) {
+ gaim_connection_error(gc, _("Invalid Jabber ID"));
+ return;
+ }
+
+ if(!js->user->resource) {
+ char *me;
+ js->user->resource = g_strdup("Home");
+ if(!js->user->node) {
+ js->user->node = js->user->domain;
+ js->user->domain = g_strdup("jabber.org");
+ }
+ me = g_strdup_printf("%s@%s/%s", js->user->node, js->user->domain,
+ js->user->resource);
+ gaim_account_set_username(account, me);
+ g_free(me);
+ }
+
+ if((my_jb = jabber_buddy_find(js, gaim_account_get_username(account), TRUE)))
+ my_jb->subscription |= JABBER_SUB_BOTH;
+
+ jabber_stream_set_state(js, JABBER_STREAM_CONNECTING);
+
+ /* if they've got old-ssl mode going, we probably want to ignore SRV lookups */
+ if(gaim_account_get_bool(js->gc->account, "old_ssl", FALSE)) {
+ if(gaim_ssl_is_supported()) {
+ js->gsc = gaim_ssl_connect(js->gc->account,
+ connect_server[0] ? connect_server : js->user->domain,
+ gaim_account_get_int(account, "port", 5223), jabber_login_callback_ssl,
+ jabber_ssl_connect_failure, js->gc);
+ } else {
+ gaim_connection_error(js->gc, _("SSL support unavailable"));
+ }
+ }
+
+ /* no old-ssl, so if they've specified a connect server, we'll use that, otherwise we'll
+ * invoke the magic of SRV lookups, to figure out host and port */
+ if(!js->gsc) {
+ if(connect_server[0]) {
+ jabber_login_connect(js, connect_server, gaim_account_get_int(account, "port", 5222));
+ } else {
+ js->srv_query_data = gaim_srv_resolve("xmpp-client",
+ "tcp", js->user->domain, srv_resolved_cb, js);
+ }
+ }
+}
+
+
+static gboolean
+conn_close_cb(gpointer data)
+{
+ JabberStream *js = data;
+ GaimAccount *account = gaim_connection_get_account(js->gc);
+
+ gaim_account_disconnect(account);
+
+ return FALSE;
+}
+
+static void
+jabber_connection_schedule_close(JabberStream *js)
+{
+ gaim_timeout_add(0, conn_close_cb, js);
+}
+
+static void
+jabber_registration_result_cb(JabberStream *js, xmlnode *packet, gpointer data)
+{
+ const char *type = xmlnode_get_attrib(packet, "type");
+ char *buf;
+
+ if(!strcmp(type, "result")) {
+ buf = g_strdup_printf(_("Registration of %s@%s successful"),
+ js->user->node, js->user->domain);
+ gaim_notify_info(NULL, _("Registration Successful"),
+ _("Registration Successful"), buf);
+ g_free(buf);
+ } else {
+ char *msg = jabber_parse_error(js, packet);
+
+ if(!msg)
+ msg = g_strdup(_("Unknown Error"));
+
+ gaim_notify_error(NULL, _("Registration Failed"),
+ _("Registration Failed"), msg);
+ g_free(msg);
+ }
+ jabber_connection_schedule_close(js);
+}
+
+static void
+jabber_register_cb(JabberStream *js, GaimRequestFields *fields)
+{
+ GList *groups, *flds;
+ xmlnode *query, *y;
+ JabberIq *iq;
+ char *username;
+
+ iq = jabber_iq_new_query(js, JABBER_IQ_SET, "jabber:iq:register");
+ query = xmlnode_get_child(iq->node, "query");
+
+ for(groups = gaim_request_fields_get_groups(fields); groups;
+ groups = groups->next) {
+ for(flds = gaim_request_field_group_get_fields(groups->data);
+ flds; flds = flds->next) {
+ GaimRequestField *field = flds->data;
+ const char *id = gaim_request_field_get_id(field);
+ const char *value = gaim_request_field_string_get_value(field);
+
+ if(!strcmp(id, "username")) {
+ y = xmlnode_new_child(query, "username");
+ } else if(!strcmp(id, "password")) {
+ y = xmlnode_new_child(query, "password");
+ } else if(!strcmp(id, "name")) {
+ y = xmlnode_new_child(query, "name");
+ } else if(!strcmp(id, "email")) {
+ y = xmlnode_new_child(query, "email");
+ } else if(!strcmp(id, "nick")) {
+ y = xmlnode_new_child(query, "nick");
+ } else if(!strcmp(id, "first")) {
+ y = xmlnode_new_child(query, "first");
+ } else if(!strcmp(id, "last")) {
+ y = xmlnode_new_child(query, "last");
+ } else if(!strcmp(id, "address")) {
+ y = xmlnode_new_child(query, "address");
+ } else if(!strcmp(id, "city")) {
+ y = xmlnode_new_child(query, "city");
+ } else if(!strcmp(id, "state")) {
+ y = xmlnode_new_child(query, "state");
+ } else if(!strcmp(id, "zip")) {
+ y = xmlnode_new_child(query, "zip");
+ } else if(!strcmp(id, "phone")) {
+ y = xmlnode_new_child(query, "phone");
+ } else if(!strcmp(id, "url")) {
+ y = xmlnode_new_child(query, "url");
+ } else if(!strcmp(id, "date")) {
+ y = xmlnode_new_child(query, "date");
+ } else {
+ continue;
+ }
+ xmlnode_insert_data(y, value, -1);
+ if(!strcmp(id, "username")) {
+ if(js->user->node)
+ g_free(js->user->node);
+ js->user->node = g_strdup(value);
+ }
+ }
+ }
+
+ username = g_strdup_printf("%s@%s/%s", js->user->node, js->user->domain,
+ js->user->resource);
+ gaim_account_set_username(js->gc->account, username);
+ g_free(username);
+
+ jabber_iq_set_callback(iq, jabber_registration_result_cb, NULL);
+
+ jabber_iq_send(iq);
+
+}
+
+static void
+jabber_register_cancel_cb(JabberStream *js, GaimRequestFields *fields)
+{
+ jabber_connection_schedule_close(js);
+}
+
+static void jabber_register_x_data_cb(JabberStream *js, xmlnode *result, gpointer data)
+{
+ xmlnode *query;
+ JabberIq *iq;
+
+ iq = jabber_iq_new_query(js, JABBER_IQ_SET, "jabber:iq:register");
+ query = xmlnode_get_child(iq->node, "query");
+
+ xmlnode_insert_child(query, result);
+
+ jabber_iq_set_callback(iq, jabber_registration_result_cb, NULL);
+ jabber_iq_send(iq);
+}
+
+void jabber_register_parse(JabberStream *js, xmlnode *packet)
+{
+ const char *type;
+ if(!(type = xmlnode_get_attrib(packet, "type")) || strcmp(type, "result"))
+ return;
+
+ if(js->registration) {
+ GaimRequestFields *fields;
+ GaimRequestFieldGroup *group;
+ GaimRequestField *field;
+ xmlnode *query, *x, *y;
+ char *instructions;
+
+ /* get rid of the login thingy */
+ gaim_connection_set_state(js->gc, GAIM_CONNECTED);
+
+ query = xmlnode_get_child(packet, "query");
+
+ if(xmlnode_get_child(query, "registered")) {
+ gaim_notify_error(NULL, _("Already Registered"),
+ _("Already Registered"), NULL);
+ jabber_connection_schedule_close(js);
+ return;
+ }
+
+ if((x = xmlnode_get_child_with_namespace(packet, "x",
+ "jabber:x:data"))) {
+ jabber_x_data_request(js, x, jabber_register_x_data_cb, NULL);
+ return;
+ } else if((x = xmlnode_get_child_with_namespace(packet, "x",
+ "jabber:x:oob"))) {
+ xmlnode *url;
+
+ if((url = xmlnode_get_child(x, "url"))) {
+ char *href;
+ if((href = xmlnode_get_data(url))) {
+ gaim_notify_uri(NULL, href);
+ g_free(href);
+ js->gc->wants_to_die = TRUE;
+ jabber_connection_schedule_close(js);
+ return;
+ }
+ }
+ }
+
+ /* as a last resort, use the old jabber:iq:register syntax */
+
+ fields = gaim_request_fields_new();
+ group = gaim_request_field_group_new(NULL);
+ gaim_request_fields_add_group(fields, group);
+
+ field = gaim_request_field_string_new("username", _("Username"),
+ js->user->node, FALSE);
+ gaim_request_field_group_add_field(group, field);
+
+ field = gaim_request_field_string_new("password", _("Password"),
+ gaim_connection_get_password(js->gc), FALSE);
+ gaim_request_field_string_set_masked(field, TRUE);
+ gaim_request_field_group_add_field(group, field);
+
+ if(xmlnode_get_child(query, "name")) {
+ field = gaim_request_field_string_new("name", _("Name"),
+ gaim_account_get_alias(js->gc->account), FALSE);
+ gaim_request_field_group_add_field(group, field);
+ }
+ if(xmlnode_get_child(query, "email")) {
+ field = gaim_request_field_string_new("email", _("E-mail"),
+ NULL, FALSE);
+ gaim_request_field_group_add_field(group, field);
+ }
+ if(xmlnode_get_child(query, "nick")) {
+ field = gaim_request_field_string_new("nick", _("Nickname"),
+ NULL, FALSE);
+ gaim_request_field_group_add_field(group, field);
+ }
+ if(xmlnode_get_child(query, "first")) {
+ field = gaim_request_field_string_new("first", _("First name"),
+ NULL, FALSE);
+ gaim_request_field_group_add_field(group, field);
+ }
+ if(xmlnode_get_child(query, "last")) {
+ field = gaim_request_field_string_new("last", _("Last name"),
+ NULL, FALSE);
+ gaim_request_field_group_add_field(group, field);
+ }
+ if(xmlnode_get_child(query, "address")) {
+ field = gaim_request_field_string_new("address", _("Address"),
+ NULL, FALSE);
+ gaim_request_field_group_add_field(group, field);
+ }
+ if(xmlnode_get_child(query, "city")) {
+ field = gaim_request_field_string_new("city", _("City"),
+ NULL, FALSE);
+ gaim_request_field_group_add_field(group, field);
+ }
+ if(xmlnode_get_child(query, "state")) {
+ field = gaim_request_field_string_new("state", _("State"),
+ NULL, FALSE);
+ gaim_request_field_group_add_field(group, field);
+ }
+ if(xmlnode_get_child(query, "zip")) {
+ field = gaim_request_field_string_new("zip", _("Postal code"),
+ NULL, FALSE);
+ gaim_request_field_group_add_field(group, field);
+ }
+ if(xmlnode_get_child(query, "phone")) {
+ field = gaim_request_field_string_new("phone", _("Phone"),
+ NULL, FALSE);
+ gaim_request_field_group_add_field(group, field);
+ }
+ if(xmlnode_get_child(query, "url")) {
+ field = gaim_request_field_string_new("url", _("URL"),
+ NULL, FALSE);
+ gaim_request_field_group_add_field(group, field);
+ }
+ if(xmlnode_get_child(query, "date")) {
+ field = gaim_request_field_string_new("date", _("Date"),
+ NULL, FALSE);
+ gaim_request_field_group_add_field(group, field);
+ }
+
+ if((y = xmlnode_get_child(query, "instructions")))
+ instructions = xmlnode_get_data(y);
+ else
+ instructions = g_strdup(_("Please fill out the information below "
+ "to register your new account."));
+
+ gaim_request_fields(js->gc, _("Register New Jabber Account"),
+ _("Register New Jabber Account"), instructions, fields,
+ _("Register"), G_CALLBACK(jabber_register_cb),
+ _("Cancel"), G_CALLBACK(jabber_register_cancel_cb), js);
+
+ g_free(instructions);
+ }
+}
+
+void jabber_register_start(JabberStream *js)
+{
+ JabberIq *iq;
+
+ iq = jabber_iq_new_query(js, JABBER_IQ_GET, "jabber:iq:register");
+ jabber_iq_send(iq);
+}
+
+static void jabber_register_account(GaimAccount *account)
+{
+ GaimConnection *gc = gaim_account_get_connection(account);
+ JabberStream *js;
+ JabberBuddy *my_jb = NULL;
+ const char *connect_server = gaim_account_get_string(account,
+ "connect_server", "");
+ const char *server;
+
+ js = gc->proto_data = g_new0(JabberStream, 1);
+ js->gc = gc;
+ js->registration = TRUE;
+ js->iq_callbacks = g_hash_table_new_full(g_str_hash, g_str_equal,
+ g_free, g_free);
+ js->disco_callbacks = g_hash_table_new_full(g_str_hash, g_str_equal,
+ g_free, g_free);
+ js->user = jabber_id_new(gaim_account_get_username(account));
+ js->next_id = g_random_int();
+
+ if(!js->user) {
+ gaim_connection_error(gc, _("Invalid Jabber ID"));
+ return;
+ }
+
+ js->write_buffer = gaim_circ_buffer_new(512);
+
+ if(!js->user->resource) {
+ char *me;
+ js->user->resource = g_strdup("Home");
+ if(!js->user->node) {
+ js->user->node = js->user->domain;
+ js->user->domain = g_strdup("jabber.org");
+ }
+ me = g_strdup_printf("%s@%s/%s", js->user->node, js->user->domain,
+ js->user->resource);
+ gaim_account_set_username(account, me);
+ g_free(me);
+ }
+
+ if((my_jb = jabber_buddy_find(js, gaim_account_get_username(account), TRUE)))
+ my_jb->subscription |= JABBER_SUB_BOTH;
+
+ server = connect_server[0] ? connect_server : js->user->domain;
+
+ jabber_stream_set_state(js, JABBER_STREAM_CONNECTING);
+
+ if(gaim_account_get_bool(account, "old_ssl", FALSE)) {
+ if(gaim_ssl_is_supported()) {
+ js->gsc = gaim_ssl_connect(account, server,
+ gaim_account_get_int(account, "port", 5222),
+ jabber_login_callback_ssl, jabber_ssl_connect_failure, gc);
+ } else {
+ gaim_connection_error(gc, _("SSL support unavailable"));
+ }
+ }
+
+ if(!js->gsc) {
+ if (connect_server[0]) {
+ jabber_login_connect(js, server,
+ gaim_account_get_int(account,
+ "port", 5222));
+ } else {
+ js->srv_query_data = gaim_srv_resolve("xmpp-client",
+ "tcp",
+ js->user->domain,
+ srv_resolved_cb,
+ js);
+ }
+ }
+}
+
+static void jabber_close(GaimConnection *gc)
+{
+ JabberStream *js = gc->proto_data;
+
+ /* Don't perform any actions on the ssl connection
+ * if we were forcibly disconnected because it will crash
+ * on some SSL backends.
+ */
+ if (!gc->disconnect_timeout)
+ jabber_send_raw(js, "</stream:stream>", -1);
+
+ if (js->srv_query_data)
+ gaim_srv_cancel(js->srv_query_data);
+
+ if(js->gsc) {
+#ifdef HAVE_OPENSSL
+ if (!gc->disconnect_timeout)
+#endif
+ gaim_ssl_close(js->gsc);
+ } else if (js->fd > 0) {
+ if(js->gc->inpa)
+ gaim_input_remove(js->gc->inpa);
+ close(js->fd);
+ }
+
+ jabber_buddy_remove_all_pending_buddy_info_requests(js);
+
+ if(js->iq_callbacks)
+ g_hash_table_destroy(js->iq_callbacks);
+ if(js->disco_callbacks)
+ g_hash_table_destroy(js->disco_callbacks);
+ if(js->buddies)
+ g_hash_table_destroy(js->buddies);
+ if(js->chats)
+ g_hash_table_destroy(js->chats);
+ while(js->chat_servers) {
+ g_free(js->chat_servers->data);
+ js->chat_servers = g_list_delete_link(js->chat_servers, js->chat_servers);
+ }
+ while(js->user_directories) {
+ g_free(js->user_directories->data);
+ js->user_directories = g_list_delete_link(js->user_directories, js->user_directories);
+ }
+ if(js->stream_id)
+ g_free(js->stream_id);
+ if(js->user)
+ jabber_id_free(js->user);
+ if(js->avatar_hash)
+ g_free(js->avatar_hash);
+ gaim_circ_buffer_destroy(js->write_buffer);
+ if(js->writeh)
+ gaim_input_remove(js->writeh);
+#ifdef HAVE_CYRUS_SASL
+ if(js->sasl)
+ sasl_dispose(&js->sasl);
+ if(js->sasl_mechs)
+ g_string_free(js->sasl_mechs, TRUE);
+ if(js->sasl_cb)
+ g_free(js->sasl_cb);
+#endif
+ g_free(js->server_name);
+ g_free(js->gmail_last_time);
+ g_free(js->gmail_last_tid);
+ g_free(js);
+
+ gc->proto_data = NULL;
+}
+
+void jabber_stream_set_state(JabberStream *js, JabberStreamState state)
+{
+ js->state = state;
+ switch(state) {
+ case JABBER_STREAM_OFFLINE:
+ break;
+ case JABBER_STREAM_CONNECTING:
+ gaim_connection_update_progress(js->gc, _("Connecting"), 1,
+ JABBER_CONNECT_STEPS);
+ break;
+ case JABBER_STREAM_INITIALIZING:
+ gaim_connection_update_progress(js->gc, _("Initializing Stream"),
+ js->gsc ? 5 : 2, JABBER_CONNECT_STEPS);
+ jabber_stream_init(js);
+ break;
+ case JABBER_STREAM_AUTHENTICATING:
+ gaim_connection_update_progress(js->gc, _("Authenticating"),
+ js->gsc ? 6 : 3, JABBER_CONNECT_STEPS);
+ if(js->protocol_version == JABBER_PROTO_0_9 && js->registration) {
+ jabber_register_start(js);
+ } else if(js->auth_type == JABBER_AUTH_IQ_AUTH) {
+ jabber_auth_start_old(js);
+ }
+ break;
+ case JABBER_STREAM_REINITIALIZING:
+ gaim_connection_update_progress(js->gc, _("Re-initializing Stream"),
+ (js->gsc ? 7 : 4), JABBER_CONNECT_STEPS);
+
+ /* The stream will be reinitialized later, in jabber_recv_cb_ssl() */
+ js->reinit = TRUE;
+
+ break;
+ case JABBER_STREAM_CONNECTED:
+ gaim_connection_set_state(js->gc, GAIM_CONNECTED);
+ jabber_disco_items_server(js);
+ break;
+ }
+}
+
+char *jabber_get_next_id(JabberStream *js)
+{
+ return g_strdup_printf("gaim%x", js->next_id++);
+}
+
+
+static void jabber_idle_set(GaimConnection *gc, int idle)
+{
+ JabberStream *js = gc->proto_data;
+
+ js->idle = idle ? time(NULL) - idle : idle;
+}
+
+static const char *jabber_list_icon(GaimAccount *a, GaimBuddy *b)
+{
+ return "jabber";
+}
+
+static void jabber_list_emblems(GaimBuddy *b, const char **se, const char **sw,
+ const char **nw, const char **ne)
+{
+ JabberStream *js;
+ JabberBuddy *jb = NULL;
+
+ if(!b->account->gc)
+ return;
+ js = b->account->gc->proto_data;
+ if(js)
+ jb = jabber_buddy_find(js, b->name, FALSE);
+
+ if(!GAIM_BUDDY_IS_ONLINE(b)) {
+ if(jb && jb->error_msg)
+ *nw = "error";
+
+ if(jb && (jb->subscription & JABBER_SUB_PENDING ||
+ !(jb->subscription & JABBER_SUB_TO)))
+ *se = "notauthorized";
+ else
+ *se = "offline";
+ } else {
+ GaimStatusType *status_type = gaim_status_get_type(gaim_presence_get_active_status(gaim_buddy_get_presence(b)));
+ GaimStatusPrimitive primitive = gaim_status_type_get_primitive(status_type);
+
+ if(primitive > GAIM_STATUS_AVAILABLE) {
+ *se = gaim_status_type_get_id(status_type);
+ }
+ }
+}
+
+static char *jabber_status_text(GaimBuddy *b)
+{
+ JabberBuddy *jb = jabber_buddy_find(b->account->gc->proto_data, b->name,
+ FALSE);
+ char *ret = NULL;
+
+ if(jb && !GAIM_BUDDY_IS_ONLINE(b) && (jb->subscription & JABBER_SUB_PENDING || !(jb->subscription & JABBER_SUB_TO))) {
+ ret = g_strdup(_("Not Authorized"));
+ } else if(jb && !GAIM_BUDDY_IS_ONLINE(b) && jb->error_msg) {
+ ret = g_strdup(jb->error_msg);
+ } else {
+ char *stripped;
+
+ if(!(stripped = gaim_markup_strip_html(jabber_buddy_get_status_msg(jb)))) {
+ GaimStatus *status = gaim_presence_get_active_status(gaim_buddy_get_presence(b));
+
+ if(!gaim_status_is_available(status))
+ stripped = g_strdup(gaim_status_get_name(status));
+ }
+
+ if(stripped) {
+ ret = g_markup_escape_text(stripped, -1);
+ g_free(stripped);
+ }
+ }
+
+ return ret;
+}
+
+static void jabber_tooltip_text(GaimBuddy *b, GaimNotifyUserInfo *user_info, gboolean full)
+{
+ JabberBuddy *jb;
+
+ g_return_if_fail(b != NULL);
+ g_return_if_fail(b->account != NULL);
+ g_return_if_fail(b->account->gc != NULL);
+ g_return_if_fail(b->account->gc->proto_data != NULL);
+
+ jb = jabber_buddy_find(b->account->gc->proto_data, b->name,
+ FALSE);
+
+ if(jb) {
+ JabberBuddyResource *jbr = NULL;
+ const char *sub;
+ GList *l;
+
+ if (full) {
+ if(jb->subscription & JABBER_SUB_FROM) {
+ if(jb->subscription & JABBER_SUB_TO)
+ sub = _("Both");
+ else if(jb->subscription & JABBER_SUB_PENDING)
+ sub = _("From (To pending)");
+ else
+ sub = _("From");
+ } else {
+ if(jb->subscription & JABBER_SUB_TO)
+ sub = _("To");
+ else if(jb->subscription & JABBER_SUB_PENDING)
+ sub = _("None (To pending)");
+ else
+ sub = _("None");
+ }
+
+ gaim_notify_user_info_add_pair(user_info, _("Subscription"), sub);
+ }
+
+ for(l=jb->resources; l; l = l->next) {
+ char *text = NULL;
+ char *res = NULL;
+ char *label, *value;
+ const char *state;
+
+ jbr = l->data;
+
+ if(jbr->status) {
+ char *tmp;
+ text = gaim_strreplace(jbr->status, "\n", "<br />\n");
+ tmp = gaim_markup_strip_html(text);
+ g_free(text);
+ text = g_markup_escape_text(tmp, -1);
+ g_free(tmp);
+ }
+
+ if(jbr->name)
+ res = g_strdup_printf(" (%s)", jbr->name);
+
+ state = jabber_buddy_state_get_name(jbr->state);
+ if (text != NULL && !gaim_utf8_strcasecmp(state, text)) {
+ g_free(text);
+ text = NULL;
+ }
+
+ label = g_strdup_printf("%s%s",
+ _("Status"), (res ? res : ""));
+ value = g_strdup_printf("%s%s%s",
+ state,
+ (text ? ": " : ""),
+ (text ? text : ""));
+
+ gaim_notify_user_info_add_pair(user_info, label, value);
+
+ g_free(label);
+ g_free(value);
+ g_free(text);
+ g_free(res);
+ }
+
+ if(!GAIM_BUDDY_IS_ONLINE(b) && jb->error_msg) {
+ gaim_notify_user_info_add_pair(user_info, _("Error"), jb->error_msg);
+ }
+ }
+}
+
+static GList *jabber_status_types(GaimAccount *account)
+{
+ GaimStatusType *type;
+ GList *types = NULL;
+ GaimValue *priority_value;
+
+ priority_value = gaim_value_new(GAIM_TYPE_INT);
+ gaim_value_set_int(priority_value, 1);
+ type = gaim_status_type_new_with_attrs(GAIM_STATUS_AVAILABLE,
+ jabber_buddy_state_get_status_id(JABBER_BUDDY_STATE_ONLINE),
+ NULL, TRUE, TRUE, FALSE,
+ "priority", _("Priority"), priority_value,
+ "message", _("Message"), gaim_value_new(GAIM_TYPE_STRING),
+ NULL);
+ types = g_list_append(types, type);
+
+ priority_value = gaim_value_new(GAIM_TYPE_INT);
+ gaim_value_set_int(priority_value, 1);
+ type = gaim_status_type_new_with_attrs(GAIM_STATUS_AVAILABLE,
+ jabber_buddy_state_get_status_id(JABBER_BUDDY_STATE_CHAT),
+ _("Chatty"), TRUE, TRUE, FALSE,
+ "priority", _("Priority"), priority_value,
+ "message", _("Message"), gaim_value_new(GAIM_TYPE_STRING),
+ NULL);
+ types = g_list_append(types, type);
+
+ priority_value = gaim_value_new(GAIM_TYPE_INT);
+ gaim_value_set_int(priority_value, 0);
+ type = gaim_status_type_new_with_attrs(GAIM_STATUS_AWAY,
+ jabber_buddy_state_get_status_id(JABBER_BUDDY_STATE_AWAY),
+ NULL, TRUE, TRUE, FALSE,
+ "priority", _("Priority"), priority_value,
+ "message", _("Message"), gaim_value_new(GAIM_TYPE_STRING),
+ NULL);
+ types = g_list_append(types, type);
+
+ priority_value = gaim_value_new(GAIM_TYPE_INT);
+ gaim_value_set_int(priority_value, 0);
+ type = gaim_status_type_new_with_attrs(GAIM_STATUS_EXTENDED_AWAY,
+ jabber_buddy_state_get_status_id(JABBER_BUDDY_STATE_XA),
+ NULL, TRUE, TRUE, FALSE,
+ "priority", _("Priority"), priority_value,
+ "message", _("Message"), gaim_value_new(GAIM_TYPE_STRING),
+ NULL);
+ types = g_list_append(types, type);
+
+ priority_value = gaim_value_new(GAIM_TYPE_INT);
+ gaim_value_set_int(priority_value, 0);
+ type = gaim_status_type_new_with_attrs(GAIM_STATUS_UNAVAILABLE,
+ jabber_buddy_state_get_status_id(JABBER_BUDDY_STATE_DND),
+ _("Do Not Disturb"), TRUE, TRUE, FALSE,
+ "priority", _("Priority"), priority_value,
+ "message", _("Message"), gaim_value_new(GAIM_TYPE_STRING),
+ NULL);
+ types = g_list_append(types, type);
+
+ /*
+ if(js->protocol_version == JABBER_PROTO_0_9)
+ m = g_list_append(m, _("Invisible"));
+ */
+
+ type = gaim_status_type_new_with_attrs(GAIM_STATUS_OFFLINE,
+ jabber_buddy_state_get_status_id(JABBER_BUDDY_STATE_UNAVAILABLE),
+ NULL, FALSE, TRUE, FALSE,
+ "message", _("Message"), gaim_value_new(GAIM_TYPE_STRING),
+ NULL);
+ types = g_list_append(types, type);
+
+ return types;
+}
+
+static void
+jabber_password_change_result_cb(JabberStream *js, xmlnode *packet,
+ gpointer data)
+{
+ const char *type;
+
+ type = xmlnode_get_attrib(packet, "type");
+
+ if(type && !strcmp(type, "result")) {
+ gaim_notify_info(js->gc, _("Password Changed"), _("Password Changed"),
+ _("Your password has been changed."));
+ } else {
+ char *msg = jabber_parse_error(js, packet);
+
+ gaim_notify_error(js->gc, _("Error changing password"),
+ _("Error changing password"), msg);
+ g_free(msg);
+ }
+}
+
+static void jabber_password_change_cb(JabberStream *js,
+ GaimRequestFields *fields)
+{
+ const char *p1, *p2;
+ JabberIq *iq;
+ xmlnode *query, *y;
+
+ p1 = gaim_request_fields_get_string(fields, "password1");
+ p2 = gaim_request_fields_get_string(fields, "password2");
+
+ if(strcmp(p1, p2)) {
+ gaim_notify_error(js->gc, NULL, _("New passwords do not match."), NULL);
+ return;
+ }
+
+ iq = jabber_iq_new_query(js, JABBER_IQ_SET, "jabber:iq:register");
+
+ xmlnode_set_attrib(iq->node, "to", js->user->domain);
+
+ query = xmlnode_get_child(iq->node, "query");
+
+ y = xmlnode_new_child(query, "username");
+ xmlnode_insert_data(y, js->user->node, -1);
+ y = xmlnode_new_child(query, "password");
+ xmlnode_insert_data(y, p1, -1);
+
+ jabber_iq_set_callback(iq, jabber_password_change_result_cb, NULL);
+
+ jabber_iq_send(iq);
+
+ gaim_account_set_password(js->gc->account, p1);
+}
+
+static void jabber_password_change(GaimPluginAction *action)
+{
+
+ GaimConnection *gc = (GaimConnection *) action->context;
+ JabberStream *js = gc->proto_data;
+ GaimRequestFields *fields;
+ GaimRequestFieldGroup *group;
+ GaimRequestField *field;
+
+ fields = gaim_request_fields_new();
+ group = gaim_request_field_group_new(NULL);
+ gaim_request_fields_add_group(fields, group);
+
+ field = gaim_request_field_string_new("password1", _("Password"),
+ "", FALSE);
+ gaim_request_field_string_set_masked(field, TRUE);
+ gaim_request_field_group_add_field(group, field);
+
+ field = gaim_request_field_string_new("password2", _("Password (again)"),
+ "", FALSE);
+ gaim_request_field_string_set_masked(field, TRUE);
+ gaim_request_field_group_add_field(group, field);
+
+ gaim_request_fields(js->gc, _("Change Jabber Password"),
+ _("Change Jabber Password"), _("Please enter your new password"),
+ fields, _("OK"), G_CALLBACK(jabber_password_change_cb),
+ _("Cancel"), NULL, js);
+}
+
+static GList *jabber_actions(GaimPlugin *plugin, gpointer context)
+{
+ GList *m = NULL;
+ GaimPluginAction *act;
+
+ act = gaim_plugin_action_new(_("Set User Info..."),
+ jabber_setup_set_info);
+ m = g_list_append(m, act);
+
+ /* if (js->protocol_options & CHANGE_PASSWORD) { */
+ act = gaim_plugin_action_new(_("Change Password..."),
+ jabber_password_change);
+ m = g_list_append(m, act);
+ /* } */
+
+ act = gaim_plugin_action_new(_("Search for Users..."),
+ jabber_user_search_begin);
+ m = g_list_append(m, act);
+
+ return m;
+}
+
+static GaimChat *jabber_find_blist_chat(GaimAccount *account, const char *name)
+{
+ GaimBlistNode *gnode, *cnode;
+ JabberID *jid;
+
+ if(!(jid = jabber_id_new(name)))
+ return NULL;
+
+ for(gnode = gaim_get_blist()->root; gnode; gnode = gnode->next) {
+ for(cnode = gnode->child; cnode; cnode = cnode->next) {
+ GaimChat *chat = (GaimChat*)cnode;
+ const char *room, *server;
+ if(!GAIM_BLIST_NODE_IS_CHAT(cnode))
+ continue;
+
+ if(chat->account != account)
+ continue;
+
+ if(!(room = g_hash_table_lookup(chat->components, "room")))
+ continue;
+ if(!(server = g_hash_table_lookup(chat->components, "server")))
+ continue;
+
+ if(jid->node && jid->domain &&
+ !g_utf8_collate(room, jid->node) && !g_utf8_collate(server, jid->domain)) {
+ jabber_id_free(jid);
+ return chat;
+ }
+ }
+ }
+ jabber_id_free(jid);
+ return NULL;
+}
+
+static void jabber_convo_closed(GaimConnection *gc, const char *who)
+{
+ JabberStream *js = gc->proto_data;
+ JabberID *jid;
+ JabberBuddy *jb;
+ JabberBuddyResource *jbr;
+
+ if(!(jid = jabber_id_new(who)))
+ return;
+
+ if((jb = jabber_buddy_find(js, who, TRUE)) &&
+ (jbr = jabber_buddy_find_resource(jb, jid->resource))) {
+ if(jbr->thread_id) {
+ g_free(jbr->thread_id);
+ jbr->thread_id = NULL;
+ }
+ }
+
+ jabber_id_free(jid);
+}
+
+
+char *jabber_parse_error(JabberStream *js, xmlnode *packet)
+{
+ xmlnode *error;
+ const char *code = NULL, *text = NULL;
+ const char *xmlns = xmlnode_get_namespace(packet);
+ char *cdata = NULL;
+
+ if((error = xmlnode_get_child(packet, "error"))) {
+ cdata = xmlnode_get_data(error);
+ code = xmlnode_get_attrib(error, "code");
+
+ /* Stanza errors */
+ if(xmlnode_get_child(error, "bad-request")) {
+ text = _("Bad Request");
+ } else if(xmlnode_get_child(error, "conflict")) {
+ text = _("Conflict");
+ } else if(xmlnode_get_child(error, "feature-not-implemented")) {
+ text = _("Feature Not Implemented");
+ } else if(xmlnode_get_child(error, "forbidden")) {
+ text = _("Forbidden");
+ } else if(xmlnode_get_child(error, "gone")) {
+ text = _("Gone");
+ } else if(xmlnode_get_child(error, "internal-server-error")) {
+ text = _("Internal Server Error");
+ } else if(xmlnode_get_child(error, "item-not-found")) {
+ text = _("Item Not Found");
+ } else if(xmlnode_get_child(error, "jid-malformed")) {
+ text = _("Malformed Jabber ID");
+ } else if(xmlnode_get_child(error, "not-acceptable")) {
+ text = _("Not Acceptable");
+ } else if(xmlnode_get_child(error, "not-allowed")) {
+ text = _("Not Allowed");
+ } else if(xmlnode_get_child(error, "not-authorized")) {
+ text = _("Not Authorized");
+ } else if(xmlnode_get_child(error, "payment-required")) {
+ text = _("Payment Required");
+ } else if(xmlnode_get_child(error, "recipient-unavailable")) {
+ text = _("Recipient Unavailable");
+ } else if(xmlnode_get_child(error, "redirect")) {
+ /* XXX */
+ } else if(xmlnode_get_child(error, "registration-required")) {
+ text = _("Registration Required");
+ } else if(xmlnode_get_child(error, "remote-server-not-found")) {
+ text = _("Remote Server Not Found");
+ } else if(xmlnode_get_child(error, "remote-server-timeout")) {
+ text = _("Remote Server Timeout");
+ } else if(xmlnode_get_child(error, "resource-constraint")) {
+ text = _("Server Overloaded");
+ } else if(xmlnode_get_child(error, "service-unavailable")) {
+ text = _("Service Unavailable");
+ } else if(xmlnode_get_child(error, "subscription-required")) {
+ text = _("Subscription Required");
+ } else if(xmlnode_get_child(error, "unexpected-request")) {
+ text = _("Unexpected Request");
+ } else if(xmlnode_get_child(error, "undefined-condition")) {
+ text = _("Unknown Error");
+ }
+ } else if(xmlns && !strcmp(xmlns, "urn:ietf:params:xml:ns:xmpp-sasl")) {
+ if(xmlnode_get_child(packet, "aborted")) {
+ js->gc->wants_to_die = TRUE;
+ text = _("Authorization Aborted");
+ } else if(xmlnode_get_child(packet, "incorrect-encoding")) {
+ text = _("Incorrect encoding in authorization");
+ } else if(xmlnode_get_child(packet, "invalid-authzid")) {
+ js->gc->wants_to_die = TRUE;
+ text = _("Invalid authzid");
+ } else if(xmlnode_get_child(packet, "invalid-mechanism")) {
+ js->gc->wants_to_die = TRUE;
+ text = _("Invalid Authorization Mechanism");
+ } else if(xmlnode_get_child(packet, "mechanism-too-weak")) {
+ js->gc->wants_to_die = TRUE;
+ text = _("Authorization mechanism too weak");
+ } else if(xmlnode_get_child(packet, "not-authorized")) {
+ js->gc->wants_to_die = TRUE;
+ text = _("Not Authorized");
+ } else if(xmlnode_get_child(packet, "temporary-auth-failure")) {
+ text = _("Temporary Authentication Failure");
+ } else {
+ js->gc->wants_to_die = TRUE;
+ text = _("Authentication Failure");
+ }
+ } else if(!strcmp(packet->name, "stream:error")) {
+ if(xmlnode_get_child(packet, "bad-format")) {
+ text = _("Bad Format");
+ } else if(xmlnode_get_child(packet, "bad-namespace-prefix")) {
+ text = _("Bad Namespace Prefix");
+ } else if(xmlnode_get_child(packet, "conflict")) {
+ js->gc->wants_to_die = TRUE;
+ text = _("Resource Conflict");
+ } else if(xmlnode_get_child(packet, "connection-timeout")) {
+ text = _("Connection Timeout");
+ } else if(xmlnode_get_child(packet, "host-gone")) {
+ text = _("Host Gone");
+ } else if(xmlnode_get_child(packet, "host-unknown")) {
+ text = _("Host Unknown");
+ } else if(xmlnode_get_child(packet, "improper-addressing")) {
+ text = _("Improper Addressing");
+ } else if(xmlnode_get_child(packet, "internal-server-error")) {
+ text = _("Internal Server Error");
+ } else if(xmlnode_get_child(packet, "invalid-id")) {
+ text = _("Invalid ID");
+ } else if(xmlnode_get_child(packet, "invalid-namespace")) {
+ text = _("Invalid Namespace");
+ } else if(xmlnode_get_child(packet, "invalid-xml")) {
+ text = _("Invalid XML");
+ } else if(xmlnode_get_child(packet, "nonmatching-hosts")) {
+ text = _("Non-matching Hosts");
+ } else if(xmlnode_get_child(packet, "not-authorized")) {
+ text = _("Not Authorized");
+ } else if(xmlnode_get_child(packet, "policy-violation")) {
+ text = _("Policy Violation");
+ } else if(xmlnode_get_child(packet, "remote-connection-failed")) {
+ text = _("Remote Connection Failed");
+ } else if(xmlnode_get_child(packet, "resource-constraint")) {
+ text = _("Resource Constraint");
+ } else if(xmlnode_get_child(packet, "restricted-xml")) {
+ text = _("Restricted XML");
+ } else if(xmlnode_get_child(packet, "see-other-host")) {
+ text = _("See Other Host");
+ } else if(xmlnode_get_child(packet, "system-shutdown")) {
+ text = _("System Shutdown");
+ } else if(xmlnode_get_child(packet, "undefined-condition")) {
+ text = _("Undefined Condition");
+ } else if(xmlnode_get_child(packet, "unsupported-encoding")) {
+ text = _("Unsupported Encoding");
+ } else if(xmlnode_get_child(packet, "unsupported-stanza-type")) {
+ text = _("Unsupported Stanza Type");
+ } else if(xmlnode_get_child(packet, "unsupported-version")) {
+ text = _("Unsupported Version");
+ } else if(xmlnode_get_child(packet, "xml-not-well-formed")) {
+ text = _("XML Not Well Formed");
+ } else {
+ text = _("Stream Error");
+ }
+ }
+
+ if(text || cdata) {
+ char *ret = g_strdup_printf("%s%s%s", code ? code : "",
+ code ? ": " : "", text ? text : cdata);
+ g_free(cdata);
+ return ret;
+ } else {
+ return NULL;
+ }
+}
+
+static GaimCmdRet jabber_cmd_chat_config(GaimConversation *conv,
+ const char *cmd, char **args, char **error, void *data)
+{
+ JabberChat *chat = jabber_chat_find_by_conv(conv);
+ jabber_chat_request_room_configure(chat);
+ return GAIM_CMD_RET_OK;
+}
+
+static GaimCmdRet jabber_cmd_chat_register(GaimConversation *conv,
+ const char *cmd, char **args, char **error, void *data)
+{
+ JabberChat *chat = jabber_chat_find_by_conv(conv);
+ jabber_chat_register(chat);
+ return GAIM_CMD_RET_OK;
+}
+
+static GaimCmdRet jabber_cmd_chat_topic(GaimConversation *conv,
+ const char *cmd, char **args, char **error, void *data)
+{
+ JabberChat *chat = jabber_chat_find_by_conv(conv);
+ jabber_chat_change_topic(chat, args ? args[0] : NULL);
+ return GAIM_CMD_RET_OK;
+}
+
+static GaimCmdRet jabber_cmd_chat_nick(GaimConversation *conv,
+ const char *cmd, char **args, char **error, void *data)
+{
+ JabberChat *chat = jabber_chat_find_by_conv(conv);
+
+ if(!args || !args[0])
+ return GAIM_CMD_RET_FAILED;
+
+ jabber_chat_change_nick(chat, args[0]);
+ return GAIM_CMD_RET_OK;
+}
+
+static GaimCmdRet jabber_cmd_chat_part(GaimConversation *conv,
+ const char *cmd, char **args, char **error, void *data)
+{
+ JabberChat *chat = jabber_chat_find_by_conv(conv);
+ jabber_chat_part(chat, args ? args[0] : NULL);
+ return GAIM_CMD_RET_OK;
+}
+
+static GaimCmdRet jabber_cmd_chat_ban(GaimConversation *conv,
+ const char *cmd, char **args, char **error, void *data)
+{
+ JabberChat *chat = jabber_chat_find_by_conv(conv);
+
+ if(!args || !args[0])
+ return GAIM_CMD_RET_FAILED;
+
+ if(!jabber_chat_ban_user(chat, args[0], args[1])) {
+ *error = g_strdup_printf(_("Unable to ban user %s"), args[0]);
+ return GAIM_CMD_RET_FAILED;
+ }
+
+ return GAIM_CMD_RET_OK;
+}
+
+static GaimCmdRet jabber_cmd_chat_affiliate(GaimConversation *conv,
+ const char *cmd, char **args, char **error, void *data)
+{
+ JabberChat *chat = jabber_chat_find_by_conv(conv);
+
+ if (!args || !args[0] || !args[1])
+ return GAIM_CMD_RET_FAILED;
+
+ if (strcmp(args[1], "owner") != 0 &&
+ strcmp(args[1], "admin") != 0 &&
+ strcmp(args[1], "member") != 0 &&
+ strcmp(args[1], "outcast") != 0 &&
+ strcmp(args[1], "none") != 0) {
+ *error = g_strdup_printf(_("Unknown affiliation: \"%s\""), args[1]);
+ return GAIM_CMD_RET_FAILED;
+ }
+
+ if (!jabber_chat_affiliate_user(chat, args[0], args[1])) {
+ *error = g_strdup_printf(_("Unable to affiliate user %s as \"%s\""), args[0], args[1]);
+ return GAIM_CMD_RET_FAILED;
+ }
+
+ return GAIM_CMD_RET_OK;
+}
+
+static GaimCmdRet jabber_cmd_chat_role(GaimConversation *conv,
+ const char *cmd, char **args, char **error, void *data)
+{
+ JabberChat *chat;
+
+ if (!args || !args[0] || !args[1])
+ return GAIM_CMD_RET_FAILED;
+
+ if (strcmp(args[1], "moderator") != 0 &&
+ strcmp(args[1], "participant") != 0 &&
+ strcmp(args[1], "visitor") != 0 &&
+ strcmp(args[1], "none") != 0) {
+ *error = g_strdup_printf(_("Unknown role: \"%s\""), args[1]);
+ return GAIM_CMD_RET_FAILED;
+ }
+
+ chat = jabber_chat_find_by_conv(conv);
+
+ if (!jabber_chat_role_user(chat, args[0], args[1])) {
+ *error = g_strdup_printf(_("Unable to set role \"%s\" for user: %s"),
+ args[1], args[0]);
+ return GAIM_CMD_RET_FAILED;
+ }
+
+ return GAIM_CMD_RET_OK;
+}
+
+static GaimCmdRet jabber_cmd_chat_invite(GaimConversation *conv,
+ const char *cmd, char **args, char **error, void *data)
+{
+ if(!args || !args[0])
+ return GAIM_CMD_RET_FAILED;
+
+ jabber_chat_invite(gaim_conversation_get_gc(conv),
+ gaim_conv_chat_get_id(GAIM_CONV_CHAT(conv)), args[1] ? args[1] : "",
+ args[0]);
+
+ return GAIM_CMD_RET_OK;
+}
+
+static GaimCmdRet jabber_cmd_chat_join(GaimConversation *conv,
+ const char *cmd, char **args, char **error, void *data)
+{
+ JabberChat *chat = jabber_chat_find_by_conv(conv);
+ GHashTable *components;
+
+ if(!args || !args[0])
+ return GAIM_CMD_RET_FAILED;
+
+ components = g_hash_table_new_full(g_str_hash, g_str_equal, NULL, NULL);
+
+ g_hash_table_replace(components, "room", args[0]);
+ g_hash_table_replace(components, "server", chat->server);
+ g_hash_table_replace(components, "handle", chat->handle);
+ if(args[1])
+ g_hash_table_replace(components, "password", args[1]);
+
+ jabber_chat_join(gaim_conversation_get_gc(conv), components);
+
+ g_hash_table_destroy(components);
+ return GAIM_CMD_RET_OK;
+}
+
+static GaimCmdRet jabber_cmd_chat_kick(GaimConversation *conv,
+ const char *cmd, char **args, char **error, void *data)
+{
+ JabberChat *chat = jabber_chat_find_by_conv(conv);
+
+ if(!args || !args[0])
+ return GAIM_CMD_RET_FAILED;
+
+ if(!jabber_chat_kick_user(chat, args[0], args[1])) {
+ *error = g_strdup_printf(_("Unable to kick user %s"), args[0]);
+ return GAIM_CMD_RET_FAILED;
+ }
+
+ return GAIM_CMD_RET_OK;
+}
+
+static GaimCmdRet jabber_cmd_chat_msg(GaimConversation *conv,
+ const char *cmd, char **args, char **error, void *data)
+{
+ JabberChat *chat = jabber_chat_find_by_conv(conv);
+ char *who;
+
+ who = g_strdup_printf("%s@%s/%s", chat->room, chat->server, args[0]);
+
+ jabber_message_send_im(gaim_conversation_get_gc(conv), who, args[1], 0);
+
+ g_free(who);
+ return GAIM_CMD_RET_OK;
+}
+
+static gboolean jabber_offline_message(const GaimBuddy *buddy)
+{
+ return TRUE;
+}
+
+static void jabber_register_commands(void)
+{
+ gaim_cmd_register("config", "", GAIM_CMD_P_PRPL,
+ GAIM_CMD_FLAG_CHAT | GAIM_CMD_FLAG_PRPL_ONLY,
+ "prpl-jabber", jabber_cmd_chat_config,
+ _("config: Configure a chat room."), NULL);
+ gaim_cmd_register("configure", "", GAIM_CMD_P_PRPL,
+ GAIM_CMD_FLAG_CHAT | GAIM_CMD_FLAG_PRPL_ONLY,
+ "prpl-jabber", jabber_cmd_chat_config,
+ _("configure: Configure a chat room."), NULL);
+ gaim_cmd_register("nick", "s", GAIM_CMD_P_PRPL,
+ GAIM_CMD_FLAG_CHAT | GAIM_CMD_FLAG_PRPL_ONLY,
+ "prpl-jabber", jabber_cmd_chat_nick,
+ _("nick &lt;new nickname&gt;: Change your nickname."),
+ NULL);
+ gaim_cmd_register("part", "s", GAIM_CMD_P_PRPL,
+ GAIM_CMD_FLAG_CHAT | GAIM_CMD_FLAG_PRPL_ONLY |
+ GAIM_CMD_FLAG_ALLOW_WRONG_ARGS, "prpl-jabber",
+ jabber_cmd_chat_part, _("part [room]: Leave the room."),
+ NULL);
+ gaim_cmd_register("register", "", GAIM_CMD_P_PRPL,
+ GAIM_CMD_FLAG_CHAT | GAIM_CMD_FLAG_PRPL_ONLY,
+ "prpl-jabber", jabber_cmd_chat_register,
+ _("register: Register with a chat room."), NULL);
+ /* XXX: there needs to be a core /topic cmd, methinks */
+ gaim_cmd_register("topic", "s", GAIM_CMD_P_PRPL,
+ GAIM_CMD_FLAG_CHAT | GAIM_CMD_FLAG_PRPL_ONLY |
+ GAIM_CMD_FLAG_ALLOW_WRONG_ARGS, "prpl-jabber",
+ jabber_cmd_chat_topic,
+ _("topic [new topic]: View or change the topic."),
+ NULL);
+ gaim_cmd_register("ban", "ws", GAIM_CMD_P_PRPL,
+ GAIM_CMD_FLAG_CHAT | GAIM_CMD_FLAG_PRPL_ONLY |
+ GAIM_CMD_FLAG_ALLOW_WRONG_ARGS, "prpl-jabber",
+ jabber_cmd_chat_ban,
+ _("ban &lt;user&gt; [room]: Ban a user from the room."),
+ NULL);
+ gaim_cmd_register("affiliate", "ws", GAIM_CMD_P_PRPL,
+ GAIM_CMD_FLAG_CHAT | GAIM_CMD_FLAG_PRPL_ONLY |
+ GAIM_CMD_FLAG_ALLOW_WRONG_ARGS, "prpl-jabber",
+ jabber_cmd_chat_affiliate,
+ _("affiliate &lt;user&gt; &lt;owner|admin|member|outcast|none&gt;: Set a user's affiliation with the room."),
+ NULL);
+ gaim_cmd_register("role", "ws", GAIM_CMD_P_PRPL,
+ GAIM_CMD_FLAG_CHAT | GAIM_CMD_FLAG_PRPL_ONLY |
+ GAIM_CMD_FLAG_ALLOW_WRONG_ARGS, "prpl-jabber",
+ jabber_cmd_chat_role,
+ _("role &lt;user&gt; &lt;moderator|participant|visitor|none&gt;: Set a user's role in the room."),
+ NULL);
+ gaim_cmd_register("invite", "ws", GAIM_CMD_P_PRPL,
+ GAIM_CMD_FLAG_CHAT | GAIM_CMD_FLAG_PRPL_ONLY |
+ GAIM_CMD_FLAG_ALLOW_WRONG_ARGS, "prpl-jabber",
+ jabber_cmd_chat_invite,
+ _("invite &lt;user&gt; [message]: Invite a user to the room."),
+ NULL);
+ gaim_cmd_register("join", "ws", GAIM_CMD_P_PRPL,
+ GAIM_CMD_FLAG_CHAT | GAIM_CMD_FLAG_PRPL_ONLY |
+ GAIM_CMD_FLAG_ALLOW_WRONG_ARGS, "prpl-jabber",
+ jabber_cmd_chat_join,
+ _("join: &lt;room&gt; [server]: Join a chat on this server."),
+ NULL);
+ gaim_cmd_register("kick", "ws", GAIM_CMD_P_PRPL,
+ GAIM_CMD_FLAG_CHAT | GAIM_CMD_FLAG_PRPL_ONLY |
+ GAIM_CMD_FLAG_ALLOW_WRONG_ARGS, "prpl-jabber",
+ jabber_cmd_chat_kick,
+ _("kick &lt;user&gt; [room]: Kick a user from the room."),
+ NULL);
+ gaim_cmd_register("msg", "ws", GAIM_CMD_P_PRPL,
+ GAIM_CMD_FLAG_CHAT | GAIM_CMD_FLAG_PRPL_ONLY,
+ "prpl-jabber", jabber_cmd_chat_msg,
+ _("msg &lt;user&gt; &lt;message&gt;: Send a private message to another user."),
+ NULL);
+}
+
+static GaimPluginProtocolInfo prpl_info =
+{
+ OPT_PROTO_CHAT_TOPIC | OPT_PROTO_UNIQUE_CHATNAME | OPT_PROTO_MAIL_CHECK,
+ NULL, /* user_splits */
+ NULL, /* protocol_options */
+ {"png,gif,jpeg", 32, 32, 96, 96, 8191, GAIM_ICON_SCALE_SEND | GAIM_ICON_SCALE_DISPLAY}, /* icon_spec */
+ jabber_list_icon, /* list_icon */
+ jabber_list_emblems, /* list_emblems */
+ jabber_status_text, /* status_text */
+ jabber_tooltip_text, /* tooltip_text */
+ jabber_status_types, /* status_types */
+ jabber_blist_node_menu, /* blist_node_menu */
+ jabber_chat_info, /* chat_info */
+ jabber_chat_info_defaults, /* chat_info_defaults */
+ jabber_login, /* login */
+ jabber_close, /* close */
+ jabber_message_send_im, /* send_im */
+ jabber_set_info, /* set_info */
+ jabber_send_typing, /* send_typing */
+ jabber_buddy_get_info, /* get_info */
+ jabber_presence_send, /* set_away */
+ jabber_idle_set, /* set_idle */
+ NULL, /* change_passwd */
+ jabber_roster_add_buddy, /* add_buddy */
+ NULL, /* add_buddies */
+ jabber_roster_remove_buddy, /* remove_buddy */
+ NULL, /* remove_buddies */
+ NULL, /* add_permit */
+ jabber_google_roster_add_deny, /* add_deny */
+ NULL, /* rem_permit */
+ jabber_google_roster_rem_deny, /* rem_deny */
+ NULL, /* set_permit_deny */
+ jabber_chat_join, /* join_chat */
+ NULL, /* reject_chat */
+ jabber_get_chat_name, /* get_chat_name */
+ jabber_chat_invite, /* chat_invite */
+ jabber_chat_leave, /* chat_leave */
+ NULL, /* chat_whisper */
+ jabber_message_send_chat, /* chat_send */
+ jabber_keepalive, /* keepalive */
+ jabber_register_account, /* register_user */
+ jabber_buddy_get_info_chat, /* get_cb_info */
+ NULL, /* get_cb_away */
+ jabber_roster_alias_change, /* alias_buddy */
+ jabber_roster_group_change, /* group_buddy */
+ jabber_roster_group_rename, /* rename_group */
+ NULL, /* buddy_free */
+ jabber_convo_closed, /* convo_closed */
+ jabber_normalize, /* normalize */
+ jabber_set_buddy_icon, /* set_buddy_icon */
+ NULL, /* remove_group */
+ jabber_chat_buddy_real_name, /* get_cb_real_name */
+ jabber_chat_set_topic, /* set_chat_topic */
+ jabber_find_blist_chat, /* find_blist_chat */
+ jabber_roomlist_get_list, /* roomlist_get_list */
+ jabber_roomlist_cancel, /* roomlist_cancel */
+ NULL, /* roomlist_expand_category */
+ NULL, /* can_receive_file */
+ jabber_si_xfer_send, /* send_file */
+ jabber_si_new_xfer, /* new_xfer */
+ jabber_offline_message, /* offline_message */
+ NULL, /* whiteboard_prpl_ops */
+ jabber_prpl_send_raw, /* send_raw */
+ jabber_roomlist_room_serialize, /* roomlist_room_serialize */
+};
+
+static gboolean load_plugin(GaimPlugin *plugin)
+{
+ gaim_signal_register(plugin, "jabber-receiving-xmlnode",
+ gaim_marshal_VOID__POINTER_POINTER, NULL, 2,
+ gaim_value_new(GAIM_TYPE_SUBTYPE, GAIM_SUBTYPE_CONNECTION),
+ gaim_value_new_outgoing(GAIM_TYPE_SUBTYPE, GAIM_SUBTYPE_XMLNODE));
+
+ gaim_signal_register(plugin, "jabber-sending-xmlnode",
+ gaim_marshal_VOID__POINTER_POINTER, NULL, 2,
+ gaim_value_new(GAIM_TYPE_SUBTYPE, GAIM_SUBTYPE_CONNECTION),
+ gaim_value_new_outgoing(GAIM_TYPE_SUBTYPE, GAIM_SUBTYPE_XMLNODE));
+
+ gaim_signal_register(plugin, "jabber-sending-text",
+ gaim_marshal_VOID__POINTER_POINTER, NULL, 2,
+ gaim_value_new(GAIM_TYPE_SUBTYPE, GAIM_SUBTYPE_CONNECTION),
+ gaim_value_new_outgoing(GAIM_TYPE_STRING));
+
+
+ return TRUE;
+}
+
+static gboolean unload_plugin(GaimPlugin *plugin)
+{
+ gaim_signal_unregister(plugin, "jabber-receiving-xmlnode");
+
+ gaim_signal_unregister(plugin, "jabber-sending-xmlnode");
+
+ gaim_signal_unregister(plugin, "jabber-sending-text");
+
+ return TRUE;
+}
+
+static GaimPluginInfo info =
+{
+ GAIM_PLUGIN_MAGIC,
+ GAIM_MAJOR_VERSION,
+ GAIM_MINOR_VERSION,
+ GAIM_PLUGIN_PROTOCOL, /**< type */
+ NULL, /**< ui_requirement */
+ 0, /**< flags */
+ NULL, /**< dependencies */
+ GAIM_PRIORITY_DEFAULT, /**< priority */
+
+ "prpl-jabber", /**< id */
+ "Jabber", /**< name */
+ VERSION, /**< version */
+ /** summary */
+ N_("Jabber Protocol Plugin"),
+ /** description */
+ N_("Jabber Protocol Plugin"),
+ NULL, /**< author */
+ GAIM_WEBSITE, /**< homepage */
+
+ load_plugin, /**< load */
+ unload_plugin, /**< unload */
+ NULL, /**< destroy */
+
+ NULL, /**< ui_info */
+ &prpl_info, /**< extra_info */
+ NULL, /**< prefs_info */
+ jabber_actions
+};
+
+static void
+init_plugin(GaimPlugin *plugin)
+{
+ GaimAccountUserSplit *split;
+ GaimAccountOption *option;
+
+ split = gaim_account_user_split_new(_("Server"), "jabber.org", '@');
+ prpl_info.user_splits = g_list_append(prpl_info.user_splits, split);
+
+ split = gaim_account_user_split_new(_("Resource"), "Home", '/');
+ prpl_info.user_splits = g_list_append(prpl_info.user_splits, split);
+
+ option = gaim_account_option_bool_new(_("Force old (port 5223) SSL"), "old_ssl", FALSE);
+ prpl_info.protocol_options = g_list_append(prpl_info.protocol_options,
+ option);
+
+ option = gaim_account_option_bool_new(
+ _("Allow plaintext auth over unencrypted streams"),
+ "auth_plain_in_clear", FALSE);
+ prpl_info.protocol_options = g_list_append(prpl_info.protocol_options,
+ option);
+
+ option = gaim_account_option_int_new(_("Connect port"), "port", 5222);
+ prpl_info.protocol_options = g_list_append(prpl_info.protocol_options,
+ option);
+
+ option = gaim_account_option_string_new(_("Connect server"),
+ "connect_server", NULL);
+ prpl_info.protocol_options = g_list_append(prpl_info.protocol_options,
+ option);
+
+ my_protocol = plugin;
+
+ gaim_prefs_remove("/plugins/prpl/jabber");
+
+ /* XXX - If any other plugin wants SASL this won't be good ... */
+#ifdef HAVE_CYRUS_SASL
+ sasl_client_init(NULL);
+#endif
+ jabber_register_commands();
+
+ jabber_iq_init();
+}
+
+GAIM_INIT_PLUGIN(jabber, init_plugin, info);
diff --git a/libpurple/protocols/jabber/jabber.h b/libpurple/protocols/jabber/jabber.h
new file mode 100644
index 0000000000..412a2a4de5
--- /dev/null
+++ b/libpurple/protocols/jabber/jabber.h
@@ -0,0 +1,165 @@
+/**
+ * @file jabber.h
+ *
+ * gaim
+ *
+ * Copyright (C) 2003 Nathan Walp <faceprint@faceprint.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+#ifndef _GAIM_JABBER_H_
+#define _GAIM_JABBER_H_
+
+#include <libxml/parser.h>
+#include <glib.h>
+#include "circbuffer.h"
+#include "connection.h"
+#include "dnssrv.h"
+#include "roomlist.h"
+#include "sslconn.h"
+
+#include "jutil.h"
+#include "xmlnode.h"
+
+#ifdef HAVE_CYRUS_SASL
+#include <sasl/sasl.h>
+#endif
+
+#define CAPS0115_NODE "http://gaim.sf.net/caps"
+
+typedef enum {
+ JABBER_CAP_NONE = 0,
+ JABBER_CAP_XHTML = 1 << 0,
+ JABBER_CAP_COMPOSING = 1 << 1,
+ JABBER_CAP_SI = 1 << 2,
+ JABBER_CAP_SI_FILE_XFER = 1 << 3,
+ JABBER_CAP_BYTESTREAMS = 1 << 4,
+ JABBER_CAP_IBB = 1 << 5,
+ JABBER_CAP_CHAT_STATES = 1 << 6,
+ JABBER_CAP_IQ_SEARCH = 1 << 7,
+ JABBER_CAP_IQ_REGISTER = 1 << 8,
+
+ /* Google Talk extensions:
+ * http://code.google.com/apis/talk/jep_extensions/extensions.html
+ */
+ JABBER_CAP_GMAIL_NOTIFY = 1 << 9,
+ JABBER_CAP_GOOGLE_ROSTER = 1 << 10,
+
+ JABBER_CAP_RETRIEVED = 1 << 31
+} JabberCapabilities;
+
+typedef enum {
+ JABBER_STREAM_OFFLINE,
+ JABBER_STREAM_CONNECTING,
+ JABBER_STREAM_INITIALIZING,
+ JABBER_STREAM_AUTHENTICATING,
+ JABBER_STREAM_REINITIALIZING,
+ JABBER_STREAM_CONNECTED
+} JabberStreamState;
+
+typedef struct _JabberStream
+{
+ int fd;
+
+ GaimSrvQueryData *srv_query_data;
+
+ xmlParserCtxt *context;
+ xmlnode *current;
+
+ enum {
+ JABBER_PROTO_0_9,
+ JABBER_PROTO_1_0
+ } protocol_version;
+ enum {
+ JABBER_AUTH_UNKNOWN,
+ JABBER_AUTH_DIGEST_MD5,
+ JABBER_AUTH_PLAIN,
+ JABBER_AUTH_IQ_AUTH,
+ JABBER_AUTH_CYRUS
+ } auth_type;
+ char *stream_id;
+ JabberStreamState state;
+
+ /* SASL authentication */
+ char *expected_rspauth;
+
+ GHashTable *buddies;
+ gboolean roster_parsed;
+
+ GHashTable *chats;
+ GList *chat_servers;
+ GaimRoomlist *roomlist;
+ GList *user_directories;
+
+ GHashTable *iq_callbacks;
+ GHashTable *disco_callbacks;
+ int next_id;
+
+
+ GList *oob_file_transfers;
+ GList *file_transfers;
+
+ time_t idle;
+
+ JabberID *user;
+ GaimConnection *gc;
+ GaimSslConnection *gsc;
+
+ gboolean registration;
+
+ char *avatar_hash;
+ GSList *pending_avatar_requests;
+
+ GSList *pending_buddy_info_requests;
+
+ GaimCircBuffer *write_buffer;
+ guint writeh;
+
+ gboolean reinit;
+
+ JabberCapabilities server_caps;
+ gboolean googletalk;
+ char *server_name;
+
+ char *gmail_last_time;
+ char *gmail_last_tid;
+
+ /* OK, this stays at the end of the struct, so plugins can depend
+ * on the rest of the stuff being in the right place
+ */
+#ifdef HAVE_CYRUS_SASL
+ sasl_conn_t *sasl;
+ sasl_callback_t *sasl_cb;
+ int sasl_state;
+ int sasl_maxbuf;
+ GString *sasl_mechs;
+#endif
+
+} JabberStream;
+
+void jabber_process_packet(JabberStream *js, xmlnode *packet);
+void jabber_send(JabberStream *js, xmlnode *data);
+void jabber_send_raw(JabberStream *js, const char *data, int len);
+
+void jabber_stream_set_state(JabberStream *js, JabberStreamState state);
+
+void jabber_register_parse(JabberStream *js, xmlnode *packet);
+void jabber_register_start(JabberStream *js);
+
+char *jabber_get_next_id(JabberStream *js);
+
+char *jabber_parse_error(JabberStream *js, xmlnode *packet);
+
+#endif /* _GAIM_JABBER_H_ */
diff --git a/libpurple/protocols/jabber/jutil.c b/libpurple/protocols/jabber/jutil.c
new file mode 100644
index 0000000000..025f6ba1d4
--- /dev/null
+++ b/libpurple/protocols/jabber/jutil.c
@@ -0,0 +1,235 @@
+/*
+ * gaim - Jabber Protocol Plugin
+ *
+ * Copyright (C) 2003, Nathan Walp <faceprint@faceprint.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ */
+#include "internal.h"
+#include "server.h"
+#include "util.h"
+
+#include "chat.h"
+#include "presence.h"
+#include "jutil.h"
+
+gboolean jabber_nodeprep_validate(const char *str)
+{
+ const char *c;
+
+ if(!str)
+ return TRUE;
+
+ if(strlen(str) > 1023)
+ return FALSE;
+
+ c = str;
+ while(c && *c) {
+ gunichar ch = g_utf8_get_char(c);
+ if(ch == '\"' || ch == '&' || ch == '\'' || ch == '/' || ch == ':' ||
+ ch == '<' || ch == '>' || ch == '@' || !g_unichar_isgraph(ch)) {
+ return FALSE;
+ }
+ c = g_utf8_next_char(c);
+ }
+
+ return TRUE;
+}
+
+gboolean jabber_nameprep_validate(const char *str)
+{
+ const char *c;
+
+ if(!str)
+ return TRUE;
+
+ if(strlen(str) > 1023)
+ return FALSE;
+
+ c = str;
+ while(c && *c) {
+ gunichar ch = g_utf8_get_char(c);
+ if(!g_unichar_isgraph(ch))
+ return FALSE;
+
+ c = g_utf8_next_char(c);
+ }
+
+
+ return TRUE;
+}
+
+gboolean jabber_resourceprep_validate(const char *str)
+{
+ const char *c;
+
+ if(!str)
+ return TRUE;
+
+ if(strlen(str) > 1023)
+ return FALSE;
+
+ c = str;
+ while(c && *c) {
+ gunichar ch = g_utf8_get_char(c);
+ if(!g_unichar_isgraph(ch) && ch != ' ')
+ return FALSE;
+
+ c = g_utf8_next_char(c);
+ }
+
+ return TRUE;
+}
+
+
+JabberID*
+jabber_id_new(const char *str)
+{
+ char *at;
+ char *slash;
+ JabberID *jid;
+
+ if(!str || !g_utf8_validate(str, -1, NULL))
+ return NULL;
+
+ jid = g_new0(JabberID, 1);
+
+ at = g_utf8_strchr(str, -1, '@');
+ slash = g_utf8_strchr(str, -1, '/');
+
+ if(at) {
+ jid->node = g_utf8_normalize(str, at-str, G_NORMALIZE_NFKC);
+ if(slash) {
+ jid->domain = g_utf8_normalize(at+1, slash-(at+1), G_NORMALIZE_NFKC);
+ jid->resource = g_utf8_normalize(slash+1, -1, G_NORMALIZE_NFKC);
+ } else {
+ jid->domain = g_utf8_normalize(at+1, -1, G_NORMALIZE_NFKC);
+ }
+ } else {
+ if(slash) {
+ jid->domain = g_utf8_normalize(str, slash-str, G_NORMALIZE_NFKC);
+ jid->resource = g_utf8_normalize(slash+1, -1, G_NORMALIZE_NFKC);
+ } else {
+ jid->domain = g_utf8_normalize(str, -1, G_NORMALIZE_NFKC);
+ }
+ }
+
+
+ if(!jabber_nodeprep_validate(jid->node) ||
+ !jabber_nameprep_validate(jid->domain) ||
+ !jabber_resourceprep_validate(jid->resource)) {
+ jabber_id_free(jid);
+ return NULL;
+ }
+
+ return jid;
+}
+
+void
+jabber_id_free(JabberID *jid)
+{
+ if(jid) {
+ if(jid->node)
+ g_free(jid->node);
+ if(jid->domain)
+ g_free(jid->domain);
+ if(jid->resource)
+ g_free(jid->resource);
+ g_free(jid);
+ }
+}
+
+
+char *jabber_get_resource(const char *in)
+{
+ JabberID *jid = jabber_id_new(in);
+ char *out;
+
+ if(!jid)
+ return NULL;
+
+ out = g_strdup(jid->resource);
+ jabber_id_free(jid);
+
+ return out;
+}
+
+char *jabber_get_bare_jid(const char *in)
+{
+ JabberID *jid = jabber_id_new(in);
+ char *out;
+
+ if(!jid)
+ return NULL;
+
+ out = g_strdup_printf("%s%s%s", jid->node ? jid->node : "",
+ jid->node ? "@" : "", jid->domain);
+ jabber_id_free(jid);
+
+ return out;
+}
+
+const char *jabber_normalize(const GaimAccount *account, const char *in)
+{
+ GaimConnection *gc = account ? account->gc : NULL;
+ JabberStream *js = gc ? gc->proto_data : NULL;
+ static char buf[3072]; /* maximum legal length of a jabber jid */
+ JabberID *jid;
+ char *node, *domain;
+
+ jid = jabber_id_new(in);
+
+ if(!jid)
+ return NULL;
+
+ node = jid->node ? g_utf8_strdown(jid->node, -1) : NULL;
+ domain = g_utf8_strdown(jid->domain, -1);
+
+
+ if(js && node && jid->resource &&
+ jabber_chat_find(js, node, domain))
+ g_snprintf(buf, sizeof(buf), "%s@%s/%s", node, domain,
+ jid->resource);
+ else
+ g_snprintf(buf, sizeof(buf), "%s%s%s", node ? node : "",
+ node ? "@" : "", domain);
+
+ jabber_id_free(jid);
+ g_free(node);
+ g_free(domain);
+
+ return buf;
+}
+
+GaimConversation *
+jabber_find_unnormalized_conv(const char *name, GaimAccount *account)
+{
+ GaimConversation *c = NULL;
+ GList *cnv;
+
+ g_return_val_if_fail(name != NULL, NULL);
+
+ for(cnv = gaim_get_conversations(); cnv; cnv = cnv->next) {
+ c = (GaimConversation*)cnv->data;
+ if(gaim_conversation_get_type(c) == GAIM_CONV_TYPE_IM &&
+ !gaim_utf8_strcasecmp(name, gaim_conversation_get_name(c)) &&
+ account == gaim_conversation_get_account(c))
+ return c;
+ }
+
+ return NULL;
+}
+
diff --git a/libpurple/protocols/jabber/jutil.h b/libpurple/protocols/jabber/jutil.h
new file mode 100644
index 0000000000..24570ab043
--- /dev/null
+++ b/libpurple/protocols/jabber/jutil.h
@@ -0,0 +1,50 @@
+/**
+ * @file jutil.h utility functions
+ *
+ * gaim
+ *
+ * Copyright (C) 2003 Nathan Walp <faceprint@faceprint.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+#ifndef _GAIM_JABBER_JUTIL_H_
+#define _GAIM_JABBER_JUTIL_H_
+
+#include "account.h"
+#include "conversation.h"
+#include "xmlnode.h"
+
+
+typedef struct _JabberID {
+ char *node;
+ char *domain;
+ char *resource;
+} JabberID;
+
+JabberID* jabber_id_new(const char *str);
+void jabber_id_free(JabberID *jid);
+
+char *jabber_get_resource(const char *jid);
+char *jabber_get_bare_jid(const char *jid);
+
+const char *jabber_normalize(const GaimAccount *account, const char *in);
+
+gboolean jabber_nodeprep_validate(const char *);
+gboolean jabber_nameprep_validate(const char *);
+gboolean jabber_resourceprep_validate(const char *);
+
+GaimConversation *jabber_find_unnormalized_conv(const char *name, GaimAccount *account);
+
+#endif /* _GAIM_JABBER_JUTIL_H_ */
diff --git a/libpurple/protocols/jabber/message.c b/libpurple/protocols/jabber/message.c
new file mode 100644
index 0000000000..49ee35a0a9
--- /dev/null
+++ b/libpurple/protocols/jabber/message.c
@@ -0,0 +1,632 @@
+/*
+ * gaim - Jabber Protocol Plugin
+ *
+ * Copyright (C) 2003, Nathan Walp <faceprint@faceprint.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ */
+#include "internal.h"
+
+#include "debug.h"
+#include "notify.h"
+#include "server.h"
+#include "util.h"
+
+#include "buddy.h"
+#include "chat.h"
+#include "message.h"
+#include "xmlnode.h"
+
+void jabber_message_free(JabberMessage *jm)
+{
+ g_free(jm->from);
+ g_free(jm->to);
+ g_free(jm->id);
+ g_free(jm->subject);
+ g_free(jm->body);
+ g_free(jm->xhtml);
+ g_free(jm->password);
+ g_list_free(jm->etc);
+
+ g_free(jm);
+}
+
+static void handle_chat(JabberMessage *jm)
+{
+ JabberID *jid = jabber_id_new(jm->from);
+ char *from;
+
+ JabberBuddy *jb;
+ JabberBuddyResource *jbr;
+
+ if(!jid)
+ return;
+
+ jb = jabber_buddy_find(jm->js, jm->from, TRUE);
+ jbr = jabber_buddy_find_resource(jb, jid->resource);
+
+ if(jabber_find_unnormalized_conv(jm->from, jm->js->gc->account)) {
+ from = g_strdup(jm->from);
+ } else if(jid->node) {
+ if(jid->resource) {
+ GaimConversation *conv;
+
+ from = g_strdup_printf("%s@%s", jid->node, jid->domain);
+ conv = gaim_find_conversation_with_account(GAIM_CONV_TYPE_IM, from, jm->js->gc->account);
+ if(conv) {
+ gaim_conversation_set_name(conv, jm->from);
+ }
+ g_free(from);
+ }
+ from = g_strdup(jm->from);
+ } else {
+ from = g_strdup(jid->domain);
+ }
+
+ if(!jm->xhtml && !jm->body) {
+ if(JM_STATE_COMPOSING == jm->chat_state)
+ serv_got_typing(jm->js->gc, from, 0, GAIM_TYPING);
+ else if(JM_STATE_PAUSED == jm->chat_state)
+ serv_got_typing(jm->js->gc, from, 0, GAIM_TYPED);
+ else
+ serv_got_typing_stopped(jm->js->gc, from);
+ } else {
+ if(jbr) {
+ if(JM_TS_JEP_0085 == (jm->typing_style & JM_TS_JEP_0085)) {
+ jbr->chat_states = JABBER_CHAT_STATES_SUPPORTED;
+ } else {
+ jbr->chat_states = JABBER_CHAT_STATES_UNSUPPORTED;
+ }
+
+ if(JM_TS_JEP_0022 == (jm->typing_style & JM_TS_JEP_0022)) {
+ jbr->capabilities |= JABBER_CAP_COMPOSING;
+ }
+
+ if(jbr->thread_id)
+ g_free(jbr->thread_id);
+ jbr->thread_id = g_strdup(jbr->thread_id);
+ }
+ serv_got_im(jm->js->gc, from, jm->xhtml ? jm->xhtml : jm->body, 0,
+ jm->sent);
+ }
+
+
+ g_free(from);
+ jabber_id_free(jid);
+}
+
+static void handle_headline(JabberMessage *jm)
+{
+ char *title;
+ GString *body = g_string_new("");
+ GList *etc;
+
+ title = g_strdup_printf(_("Message from %s"), jm->from);
+
+ if(jm->xhtml)
+ g_string_append(body, jm->xhtml);
+ else if(jm->body)
+ g_string_append(body, jm->body);
+
+ for(etc = jm->etc; etc; etc = etc->next) {
+ xmlnode *x = etc->data;
+ const char *xmlns = xmlnode_get_namespace(x);
+ if(xmlns && !strcmp(xmlns, "jabber:x:oob")) {
+ xmlnode *url, *desc;
+ char *urltxt, *desctxt;
+
+ url = xmlnode_get_child(x, "url");
+ desc = xmlnode_get_child(x, "desc");
+
+ if(!url || !desc)
+ continue;
+
+ urltxt = xmlnode_get_data(url);
+ desctxt = xmlnode_get_data(desc);
+
+ /* I'm all about ugly hacks */
+ if(body->len && jm->body && !strcmp(body->str, jm->body))
+ g_string_printf(body, "<a href='%s'>%s</a>",
+ urltxt, desctxt);
+ else
+ g_string_append_printf(body, "<br/><a href='%s'>%s</a>",
+ urltxt, desctxt);
+
+ g_free(urltxt);
+ g_free(desctxt);
+ }
+ }
+
+ gaim_notify_formatted(jm->js->gc, title, jm->subject ? jm->subject : title,
+ NULL, body->str, NULL, NULL);
+
+ g_free(title);
+ g_string_free(body, TRUE);
+}
+
+static void handle_groupchat(JabberMessage *jm)
+{
+ JabberID *jid = jabber_id_new(jm->from);
+ JabberChat *chat;
+
+ if(!jid)
+ return;
+
+ chat = jabber_chat_find(jm->js, jid->node, jid->domain);
+
+ if(!chat)
+ return;
+
+ if(jm->subject) {
+ gaim_conv_chat_set_topic(GAIM_CONV_CHAT(chat->conv), jid->resource,
+ jm->subject);
+ if(!jm->xhtml && !jm->body) {
+ char *msg, *tmp, *tmp2;
+ tmp = g_markup_escape_text(jm->subject, -1);
+ tmp2 = gaim_markup_linkify(tmp);
+ if(jid->resource)
+ msg = g_strdup_printf(_("%s has set the topic to: %s"), jid->resource, tmp2);
+ else
+ msg = g_strdup_printf(_("The topic is: %s"), tmp2);
+ gaim_conv_chat_write(GAIM_CONV_CHAT(chat->conv), "", msg, GAIM_MESSAGE_SYSTEM, jm->sent);
+ g_free(tmp);
+ g_free(tmp2);
+ g_free(msg);
+ }
+ }
+
+ if(jm->xhtml || jm->body) {
+ if(jid->resource)
+ serv_got_chat_in(jm->js->gc, chat->id, jid->resource,
+ jm->delayed ? GAIM_MESSAGE_DELAYED : 0,
+ jm->xhtml ? jm->xhtml : jm->body, jm->sent);
+ else if(chat->muc)
+ gaim_conv_chat_write(GAIM_CONV_CHAT(chat->conv), "",
+ jm->xhtml ? jm->xhtml : jm->body,
+ GAIM_MESSAGE_SYSTEM, jm->sent);
+ }
+
+ jabber_id_free(jid);
+}
+
+static void handle_groupchat_invite(JabberMessage *jm)
+{
+ GHashTable *components;
+ JabberID *jid = jabber_id_new(jm->to);
+
+ if(!jid)
+ return;
+
+ components = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free);
+
+ g_hash_table_replace(components, g_strdup("room"), g_strdup(jid->node));
+ g_hash_table_replace(components, g_strdup("server"), g_strdup(jid->domain));
+ g_hash_table_replace(components, g_strdup("handle"),
+ g_strdup(jm->js->user->node));
+ g_hash_table_replace(components, g_strdup("password"),
+ g_strdup(jm->password));
+
+ jabber_id_free(jid);
+ serv_got_chat_invite(jm->js->gc, jm->to, jm->from, jm->body, components);
+}
+
+static void handle_error(JabberMessage *jm)
+{
+ char *buf;
+
+ if(!jm->body)
+ return;
+
+ buf = g_strdup_printf(_("Message delivery to %s failed: %s"),
+ jm->from, jm->error ? jm->error : "");
+
+ gaim_notify_formatted(jm->js->gc, _("Jabber Message Error"), _("Jabber Message Error"), buf,
+ jm->xhtml ? jm->xhtml : jm->body, NULL, NULL);
+
+ g_free(buf);
+}
+
+void jabber_message_parse(JabberStream *js, xmlnode *packet)
+{
+ JabberMessage *jm;
+ const char *type;
+ xmlnode *child;
+
+ jm = g_new0(JabberMessage, 1);
+ jm->js = js;
+ jm->sent = time(NULL);
+ jm->delayed = FALSE;
+
+ type = xmlnode_get_attrib(packet, "type");
+
+ if(type) {
+ if(!strcmp(type, "normal"))
+ jm->type = JABBER_MESSAGE_NORMAL;
+ else if(!strcmp(type, "chat"))
+ jm->type = JABBER_MESSAGE_CHAT;
+ else if(!strcmp(type, "groupchat"))
+ jm->type = JABBER_MESSAGE_GROUPCHAT;
+ else if(!strcmp(type, "headline"))
+ jm->type = JABBER_MESSAGE_HEADLINE;
+ else if(!strcmp(type, "error"))
+ jm->type = JABBER_MESSAGE_ERROR;
+ else
+ jm->type = JABBER_MESSAGE_OTHER;
+ } else {
+ jm->type = JABBER_MESSAGE_NORMAL;
+ }
+
+ jm->from = g_strdup(xmlnode_get_attrib(packet, "from"));
+ jm->to = g_strdup(xmlnode_get_attrib(packet, "to"));
+ jm->id = g_strdup(xmlnode_get_attrib(packet, "id"));
+
+ for(child = packet->child; child; child = child->next) {
+ if(child->type != XMLNODE_TYPE_TAG)
+ continue;
+
+ if(!strcmp(child->name, "subject")) {
+ if(!jm->subject)
+ jm->subject = xmlnode_get_data(child);
+ } else if(!strcmp(child->name, "thread")) {
+ if(!jm->thread_id)
+ jm->thread_id = xmlnode_get_data(child);
+ } else if(!strcmp(child->name, "body")) {
+ if(!jm->body) {
+ char *msg = xmlnode_to_str(child, NULL);
+ jm->body = gaim_strdup_withhtml(msg);
+ g_free(msg);
+ }
+ } else if(!strcmp(child->name, "html")) {
+ if(!jm->xhtml && xmlnode_get_child(child, "body"))
+ jm->xhtml = xmlnode_to_str(child, NULL);
+ } else if(!strcmp(child->name, "active")) {
+ jm->chat_state = JM_STATE_ACTIVE;
+ jm->typing_style |= JM_TS_JEP_0085;
+ } else if(!strcmp(child->name, "composing")) {
+ jm->chat_state = JM_STATE_COMPOSING;
+ jm->typing_style |= JM_TS_JEP_0085;
+ } else if(!strcmp(child->name, "paused")) {
+ jm->chat_state = JM_STATE_PAUSED;
+ jm->typing_style |= JM_TS_JEP_0085;
+ } else if(!strcmp(child->name, "inactive")) {
+ jm->chat_state = JM_STATE_INACTIVE;
+ jm->typing_style |= JM_TS_JEP_0085;
+ } else if(!strcmp(child->name, "gone")) {
+ jm->chat_state = JM_STATE_GONE;
+ jm->typing_style |= JM_TS_JEP_0085;
+ } else if(!strcmp(child->name, "error")) {
+ const char *code = xmlnode_get_attrib(child, "code");
+ char *code_txt = NULL;
+ char *text = xmlnode_get_data(child);
+
+ if(code)
+ code_txt = g_strdup_printf(_(" (Code %s)"), code);
+
+ if(!jm->error)
+ jm->error = g_strdup_printf("%s%s", text ? text : "",
+ code_txt ? code_txt : "");
+
+ g_free(code_txt);
+ g_free(text);
+ } else if(!strcmp(child->name, "x")) {
+ const char *xmlns = xmlnode_get_namespace(child);
+ if(xmlns && !strcmp(xmlns, "jabber:x:event")) {
+ if(xmlnode_get_child(child, "composing")) {
+ if(jm->chat_state == JM_STATE_ACTIVE)
+ jm->chat_state = JM_STATE_COMPOSING;
+ jm->typing_style |= JM_TS_JEP_0022;
+ }
+ } else if(xmlns && !strcmp(xmlns, "jabber:x:delay")) {
+ const char *timestamp = xmlnode_get_attrib(child, "stamp");
+ jm->delayed = TRUE;
+ if(timestamp)
+ jm->sent = gaim_str_to_time(timestamp, TRUE, NULL, NULL, NULL);
+ } else if(xmlns && !strcmp(xmlns, "jabber:x:conference") &&
+ jm->type != JABBER_MESSAGE_GROUPCHAT_INVITE &&
+ jm->type != JABBER_MESSAGE_ERROR) {
+ const char *jid = xmlnode_get_attrib(child, "jid");
+ if(jid) {
+ jm->type = JABBER_MESSAGE_GROUPCHAT_INVITE;
+ g_free(jm->to);
+ jm->to = g_strdup(jid);
+ }
+ } else if(xmlns && !strcmp(xmlns,
+ "http://jabber.org/protocol/muc#user") &&
+ jm->type != JABBER_MESSAGE_ERROR) {
+ xmlnode *invite = xmlnode_get_child(child, "invite");
+ if(invite) {
+ xmlnode *reason, *password;
+ const char *jid = xmlnode_get_attrib(invite, "from");
+ g_free(jm->to);
+ jm->to = jm->from;
+ jm->from = g_strdup(jid);
+ if((reason = xmlnode_get_child(invite, "reason"))) {
+ g_free(jm->body);
+ jm->body = xmlnode_get_data(reason);
+ }
+ if((password = xmlnode_get_child(child, "password")))
+ jm->password = xmlnode_get_data(password);
+
+ jm->type = JABBER_MESSAGE_GROUPCHAT_INVITE;
+ }
+ } else {
+ jm->etc = g_list_append(jm->etc, child);
+ }
+ }
+ }
+
+ switch(jm->type) {
+ case JABBER_MESSAGE_NORMAL:
+ case JABBER_MESSAGE_CHAT:
+ handle_chat(jm);
+ break;
+ case JABBER_MESSAGE_HEADLINE:
+ handle_headline(jm);
+ break;
+ case JABBER_MESSAGE_GROUPCHAT:
+ handle_groupchat(jm);
+ break;
+ case JABBER_MESSAGE_GROUPCHAT_INVITE:
+ handle_groupchat_invite(jm);
+ break;
+ case JABBER_MESSAGE_ERROR:
+ handle_error(jm);
+ break;
+ case JABBER_MESSAGE_OTHER:
+ gaim_debug(GAIM_DEBUG_INFO, "jabber",
+ "Received message of unknown type: %s\n", type);
+ break;
+ }
+ jabber_message_free(jm);
+}
+
+void jabber_message_send(JabberMessage *jm)
+{
+ xmlnode *message, *child;
+ const char *type = NULL;
+
+ message = xmlnode_new("message");
+
+ switch(jm->type) {
+ case JABBER_MESSAGE_NORMAL:
+ type = "normal";
+ break;
+ case JABBER_MESSAGE_CHAT:
+ case JABBER_MESSAGE_GROUPCHAT_INVITE:
+ type = "chat";
+ break;
+ case JABBER_MESSAGE_HEADLINE:
+ type = "headline";
+ break;
+ case JABBER_MESSAGE_GROUPCHAT:
+ type = "groupchat";
+ break;
+ case JABBER_MESSAGE_ERROR:
+ type = "error";
+ break;
+ case JABBER_MESSAGE_OTHER:
+ type = NULL;
+ break;
+ }
+
+ if(type)
+ xmlnode_set_attrib(message, "type", type);
+
+ if (jm->id)
+ xmlnode_set_attrib(message, "id", jm->id);
+
+ xmlnode_set_attrib(message, "to", jm->to);
+
+ if(jm->thread_id) {
+ child = xmlnode_new_child(message, "thread");
+ xmlnode_insert_data(child, jm->thread_id, -1);
+ }
+
+ if(JM_TS_JEP_0022 == (jm->typing_style & JM_TS_JEP_0022)) {
+ child = xmlnode_new_child(message, "x");
+ xmlnode_set_namespace(child, "jabber:x:event");
+ if(jm->chat_state == JM_STATE_COMPOSING || jm->body)
+ xmlnode_new_child(child, "composing");
+ }
+
+ if(JM_TS_JEP_0085 == (jm->typing_style & JM_TS_JEP_0085)) {
+ child = NULL;
+ switch(jm->chat_state)
+ {
+ case JM_STATE_ACTIVE:
+ child = xmlnode_new_child(message, "active");
+ break;
+ case JM_STATE_COMPOSING:
+ child = xmlnode_new_child(message, "composing");
+ break;
+ case JM_STATE_PAUSED:
+ child = xmlnode_new_child(message, "paused");
+ break;
+ case JM_STATE_INACTIVE:
+ child = xmlnode_new_child(message, "inactive");
+ break;
+ case JM_STATE_GONE:
+ child = xmlnode_new_child(message, "gone");
+ break;
+ }
+ if(child)
+ xmlnode_set_namespace(child, "http://jabber.org/protocol/chatstates");
+ }
+
+ if(jm->subject) {
+ child = xmlnode_new_child(message, "subject");
+ xmlnode_insert_data(child, jm->subject, -1);
+ }
+
+ if(jm->body) {
+ child = xmlnode_new_child(message, "body");
+ xmlnode_insert_data(child, jm->body, -1);
+ }
+
+ if(jm->xhtml) {
+ child = xmlnode_from_str(jm->xhtml, -1);
+ if(child) {
+ xmlnode_insert_child(message, child);
+ } else {
+ gaim_debug(GAIM_DEBUG_ERROR, "jabber",
+ "XHTML translation/validation failed, returning: %s\n",
+ jm->xhtml);
+ }
+ }
+
+ jabber_send(jm->js, message);
+
+ xmlnode_free(message);
+}
+
+int jabber_message_send_im(GaimConnection *gc, const char *who, const char *msg,
+ GaimMessageFlags flags)
+{
+ JabberMessage *jm;
+ JabberBuddy *jb;
+ JabberBuddyResource *jbr;
+ char *buf;
+ char *xhtml;
+ char *resource;
+
+ if(!who || !msg)
+ return 0;
+
+ resource = jabber_get_resource(who);
+
+ jb = jabber_buddy_find(gc->proto_data, who, TRUE);
+ jbr = jabber_buddy_find_resource(jb, resource);
+
+ g_free(resource);
+
+ jm = g_new0(JabberMessage, 1);
+ jm->js = gc->proto_data;
+ jm->type = JABBER_MESSAGE_CHAT;
+ jm->chat_state = JM_STATE_ACTIVE;
+ jm->to = g_strdup(who);
+ jm->id = jabber_get_next_id(jm->js);
+ jm->chat_state = JM_STATE_ACTIVE;
+
+ if(jbr) {
+ if(jbr->thread_id)
+ jm->thread_id = jbr->thread_id;
+
+ if(jbr->chat_states != JABBER_CHAT_STATES_UNSUPPORTED) {
+ jm->typing_style |= JM_TS_JEP_0085;
+ /* if(JABBER_CHAT_STATES_UNKNOWN == jbr->chat_states)
+ jbr->chat_states = JABBER_CHAT_STATES_UNSUPPORTED; */
+ }
+
+ if(jbr->chat_states != JABBER_CHAT_STATES_SUPPORTED)
+ jm->typing_style |= JM_TS_JEP_0022;
+ }
+
+ buf = g_strdup_printf("<html xmlns='http://jabber.org/protocol/xhtml-im'><body xmlns='http://www.w3.org/1999/xhtml'>%s</body></html>", msg);
+
+ gaim_markup_html_to_xhtml(buf, &xhtml, &jm->body);
+ g_free(buf);
+
+ if(!jbr || jbr->capabilities & JABBER_CAP_XHTML)
+ jm->xhtml = xhtml;
+ else
+ g_free(xhtml);
+
+ jabber_message_send(jm);
+ jabber_message_free(jm);
+ return 1;
+}
+
+int jabber_message_send_chat(GaimConnection *gc, int id, const char *msg, GaimMessageFlags flags)
+{
+ JabberChat *chat;
+ JabberMessage *jm;
+ JabberStream *js;
+ char *buf;
+
+ if(!msg || !gc)
+ return 0;
+
+ js = gc->proto_data;
+ chat = jabber_chat_find_by_id(js, id);
+
+ if(!chat)
+ return 0;
+
+ jm = g_new0(JabberMessage, 1);
+ jm->js = gc->proto_data;
+ jm->type = JABBER_MESSAGE_GROUPCHAT;
+ jm->to = g_strdup_printf("%s@%s", chat->room, chat->server);
+ jm->id = jabber_get_next_id(jm->js);
+
+ buf = g_strdup_printf("<html xmlns='http://jabber.org/protocol/xhtml-im'><body xmlns='http://www.w3.org/1999/xhtml'>%s</body></html>", msg);
+ gaim_markup_html_to_xhtml(buf, &jm->xhtml, &jm->body);
+ g_free(buf);
+
+ if(!chat->xhtml) {
+ g_free(jm->xhtml);
+ jm->xhtml = NULL;
+ }
+
+ jabber_message_send(jm);
+ jabber_message_free(jm);
+
+ return 1;
+}
+
+unsigned int jabber_send_typing(GaimConnection *gc, const char *who, GaimTypingState state)
+{
+ JabberMessage *jm;
+ JabberBuddy *jb;
+ JabberBuddyResource *jbr;
+ char *resource = jabber_get_resource(who);
+
+ jb = jabber_buddy_find(gc->proto_data, who, TRUE);
+ jbr = jabber_buddy_find_resource(jb, resource);
+
+ g_free(resource);
+
+ if(!jbr || !((jbr->capabilities & JABBER_CAP_COMPOSING) || (jbr->chat_states != JABBER_CHAT_STATES_UNSUPPORTED)))
+ return 0;
+
+ /* TODO: figure out threading */
+ jm = g_new0(JabberMessage, 1);
+ jm->js = gc->proto_data;
+ jm->type = JABBER_MESSAGE_CHAT;
+ jm->to = g_strdup(who);
+ jm->id = jabber_get_next_id(jm->js);
+
+ if(GAIM_TYPING == state)
+ jm->chat_state = JM_STATE_COMPOSING;
+ else if(GAIM_TYPED == state)
+ jm->chat_state = JM_STATE_PAUSED;
+ else
+ jm->chat_state = JM_STATE_ACTIVE;
+
+ if(jbr->chat_states != JABBER_CHAT_STATES_UNSUPPORTED) {
+ jm->typing_style |= JM_TS_JEP_0085;
+ /* if(JABBER_CHAT_STATES_UNKNOWN == jbr->chat_states)
+ jbr->chat_states = JABBER_CHAT_STATES_UNSUPPORTED; */
+ }
+
+ if(jbr->chat_states != JABBER_CHAT_STATES_SUPPORTED)
+ jm->typing_style |= JM_TS_JEP_0022;
+
+ jabber_message_send(jm);
+ jabber_message_free(jm);
+
+ return 0;
+}
+
diff --git a/libpurple/protocols/jabber/message.h b/libpurple/protocols/jabber/message.h
new file mode 100644
index 0000000000..2755d55d3f
--- /dev/null
+++ b/libpurple/protocols/jabber/message.h
@@ -0,0 +1,77 @@
+/**
+ * @file message.h Message handlers
+ *
+ * gaim
+ *
+ * Copyright (C) 2003 Nathan Walp <faceprint@faceprint.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+#ifndef _GAIM_JABBER_MESSAGE_H_
+#define _GAIM_JABBER_MESSAGE_H_
+
+#include "jabber.h"
+#include "xmlnode.h"
+
+typedef struct _JabberMessage {
+ JabberStream *js;
+ enum {
+ JABBER_MESSAGE_NORMAL,
+ JABBER_MESSAGE_CHAT,
+ JABBER_MESSAGE_GROUPCHAT,
+ JABBER_MESSAGE_HEADLINE,
+ JABBER_MESSAGE_ERROR,
+ JABBER_MESSAGE_GROUPCHAT_INVITE,
+ JABBER_MESSAGE_OTHER
+ } type;
+ time_t sent;
+ gboolean delayed;
+ char *id;
+ char *from;
+ char *to;
+ char *subject;
+ char *body;
+ char *xhtml;
+ char *password;
+ char *error;
+ char *thread_id;
+ enum {
+ JM_TS_NONE = 0,
+ JM_TS_JEP_0022 = 0x1,
+ JM_TS_JEP_0085 = 0x2
+ } typing_style;
+ enum {
+ JM_STATE_ACTIVE,
+ JM_STATE_COMPOSING,
+ JM_STATE_PAUSED,
+ JM_STATE_INACTIVE,
+ JM_STATE_GONE
+ } chat_state;
+ GList *etc;
+} JabberMessage;
+
+void jabber_message_free(JabberMessage *jm);
+
+void jabber_message_send(JabberMessage *jm);
+
+void jabber_message_parse(JabberStream *js, xmlnode *packet);
+int jabber_message_send_im(GaimConnection *gc, const char *who, const char *msg,
+ GaimMessageFlags flags);
+int jabber_message_send_chat(GaimConnection *gc, int id, const char *message, GaimMessageFlags flags);
+
+unsigned int jabber_send_typing(GaimConnection *gc, const char *who, GaimTypingState state);
+
+
+#endif /* _GAIM_JABBER_MESSAGE_H_ */
diff --git a/libpurple/protocols/jabber/oob.c b/libpurple/protocols/jabber/oob.c
new file mode 100644
index 0000000000..2295de9d02
--- /dev/null
+++ b/libpurple/protocols/jabber/oob.c
@@ -0,0 +1,242 @@
+/*
+ * gaim - Jabber Protocol Plugin
+ *
+ * Copyright (C) 2003, Nathan Walp <faceprint@faceprint.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ */
+#include "internal.h"
+#include "debug.h"
+#include "ft.h"
+#include "util.h"
+
+#include "jabber.h"
+#include "iq.h"
+#include "oob.h"
+
+typedef struct _JabberOOBXfer {
+ char *address;
+ int port;
+ char *page;
+
+ GString *headers;
+
+ char *iq_id;
+
+ JabberStream *js;
+
+ gchar *write_buffer;
+ gsize written_len;
+ guint writeh;
+
+} JabberOOBXfer;
+
+static void jabber_oob_xfer_init(GaimXfer *xfer)
+{
+ JabberOOBXfer *jox = xfer->data;
+ gaim_xfer_start(xfer, -1, jox->address, jox->port);
+}
+
+static void jabber_oob_xfer_free(GaimXfer *xfer)
+{
+ JabberOOBXfer *jox = xfer->data;
+ jox->js->oob_file_transfers = g_list_remove(jox->js->oob_file_transfers,
+ xfer);
+
+ g_string_free(jox->headers, TRUE);
+ g_free(jox->address);
+ g_free(jox->page);
+ g_free(jox->iq_id);
+ g_free(jox->write_buffer);
+ if(jox->writeh)
+ gaim_input_remove(jox->writeh);
+ g_free(jox);
+
+ xfer->data = NULL;
+}
+
+static void jabber_oob_xfer_end(GaimXfer *xfer)
+{
+ JabberOOBXfer *jox = xfer->data;
+ JabberIq *iq;
+
+ iq = jabber_iq_new(jox->js, JABBER_IQ_RESULT);
+ xmlnode_set_attrib(iq->node, "to", xfer->who);
+ jabber_iq_set_id(iq, jox->iq_id);
+
+ jabber_iq_send(iq);
+
+ jabber_oob_xfer_free(xfer);
+}
+
+static void jabber_oob_xfer_request_send(gpointer data, gint source, GaimInputCondition cond) {
+ GaimXfer *xfer = data;
+ JabberOOBXfer *jox = xfer->data;
+ int len, total_len = strlen(jox->write_buffer);
+
+ len = write(xfer->fd, jox->write_buffer + jox->written_len,
+ total_len - jox->written_len);
+
+ if(len < 0 && errno == EAGAIN)
+ return;
+ else if(len < 0) {
+ gaim_debug(GAIM_DEBUG_ERROR, "jabber", "Write error on oob xfer!\n");
+ gaim_input_remove(jox->writeh);
+ gaim_xfer_cancel_local(xfer);
+ }
+ jox->written_len += len;
+
+ if(jox->written_len == total_len) {
+ gaim_input_remove(jox->writeh);
+ g_free(jox->write_buffer);
+ jox->write_buffer = NULL;
+ }
+}
+
+static void jabber_oob_xfer_start(GaimXfer *xfer)
+{
+ JabberOOBXfer *jox = xfer->data;
+
+ if(jox->write_buffer == NULL) {
+ jox->write_buffer = g_strdup_printf(
+ "GET /%s HTTP/1.1\r\nHost: %s\r\n\r\n",
+ jox->page, jox->address);
+ jox->written_len = 0;
+ }
+
+ jox->writeh = gaim_input_add(xfer->fd, GAIM_INPUT_WRITE,
+ jabber_oob_xfer_request_send, xfer);
+
+ jabber_oob_xfer_request_send(xfer, xfer->fd, GAIM_INPUT_WRITE);
+}
+
+static gssize jabber_oob_xfer_read(guchar **buffer, GaimXfer *xfer) {
+ JabberOOBXfer *jox = xfer->data;
+ char test[2048];
+ char *tmp, *lenstr;
+ int len;
+
+ if((len = read(xfer->fd, test, sizeof(test))) > 0) {
+ jox->headers = g_string_append_len(jox->headers, test, len);
+ if((tmp = strstr(jox->headers->str, "\r\n\r\n"))) {
+ *tmp = '\0';
+ lenstr = strstr(jox->headers->str, "Content-Length: ");
+ if(lenstr) {
+ int size;
+ sscanf(lenstr, "Content-Length: %d", &size);
+ gaim_xfer_set_size(xfer, size);
+ }
+ gaim_xfer_set_read_fnc(xfer, NULL);
+
+ tmp += 4;
+
+ *buffer = (unsigned char*) g_strdup(tmp);
+ return strlen(tmp);
+ }
+ return 0;
+ } else if (errno != EAGAIN) {
+ gaim_debug(GAIM_DEBUG_ERROR, "jabber", "Read error on oob xfer!\n");
+ gaim_xfer_cancel_local(xfer);
+ }
+
+ return 0;
+}
+
+static void jabber_oob_xfer_recv_error(GaimXfer *xfer, const char *code) {
+ JabberOOBXfer *jox = xfer->data;
+ JabberIq *iq;
+ xmlnode *y, *z;
+
+ iq = jabber_iq_new(jox->js, JABBER_IQ_ERROR);
+ xmlnode_set_attrib(iq->node, "to", xfer->who);
+ jabber_iq_set_id(iq, jox->iq_id);
+ y = xmlnode_new_child(iq->node, "error");
+ xmlnode_set_attrib(y, "code", code);
+ if(!strcmp(code, "406")) {
+ z = xmlnode_new_child(y, "not-acceptable");
+ xmlnode_set_attrib(y, "type", "modify");
+ xmlnode_set_namespace(z, "urn:ietf:params:xml:ns:xmpp-stanzas");
+ } else if(!strcmp(code, "404")) {
+ z = xmlnode_new_child(y, "not-found");
+ xmlnode_set_attrib(y, "type", "cancel");
+ xmlnode_set_namespace(z, "urn:ietf:params:xml:ns:xmpp-stanzas");
+ }
+ jabber_iq_send(iq);
+
+ jabber_oob_xfer_free(xfer);
+}
+
+static void jabber_oob_xfer_recv_denied(GaimXfer *xfer) {
+ jabber_oob_xfer_recv_error(xfer, "406");
+}
+
+static void jabber_oob_xfer_recv_canceled(GaimXfer *xfer) {
+ jabber_oob_xfer_recv_error(xfer, "404");
+}
+
+void jabber_oob_parse(JabberStream *js, xmlnode *packet) {
+ JabberOOBXfer *jox;
+ GaimXfer *xfer;
+ char *filename;
+ char *url;
+ const char *type;
+ xmlnode *querynode, *urlnode;
+
+ if(!(type = xmlnode_get_attrib(packet, "type")) || strcmp(type, "set"))
+ return;
+
+ if(!(querynode = xmlnode_get_child(packet, "query")))
+ return;
+
+ if(!(urlnode = xmlnode_get_child(querynode, "url")))
+ return;
+
+ url = xmlnode_get_data(urlnode);
+
+ jox = g_new0(JabberOOBXfer, 1);
+ gaim_url_parse(url, &jox->address, &jox->port, &jox->page, NULL, NULL);
+ g_free(url);
+ jox->js = js;
+ jox->headers = g_string_new("");
+ jox->iq_id = g_strdup(xmlnode_get_attrib(packet, "id"));
+
+ xfer = gaim_xfer_new(js->gc->account, GAIM_XFER_RECEIVE,
+ xmlnode_get_attrib(packet, "from"));
+ if (xfer)
+ {
+ xfer->data = jox;
+
+ if(!(filename = g_strdup(g_strrstr(jox->page, "/"))))
+ filename = g_strdup(jox->page);
+
+ gaim_xfer_set_filename(xfer, filename);
+
+ g_free(filename);
+
+ gaim_xfer_set_init_fnc(xfer, jabber_oob_xfer_init);
+ gaim_xfer_set_end_fnc(xfer, jabber_oob_xfer_end);
+ gaim_xfer_set_request_denied_fnc(xfer, jabber_oob_xfer_recv_denied);
+ gaim_xfer_set_cancel_recv_fnc(xfer, jabber_oob_xfer_recv_canceled);
+ gaim_xfer_set_read_fnc(xfer, jabber_oob_xfer_read);
+ gaim_xfer_set_start_fnc(xfer, jabber_oob_xfer_start);
+
+ js->oob_file_transfers = g_list_append(js->oob_file_transfers, xfer);
+
+ gaim_xfer_request(xfer);
+ }
+}
+
+
diff --git a/libpurple/protocols/jabber/oob.h b/libpurple/protocols/jabber/oob.h
new file mode 100644
index 0000000000..bd5d3e0761
--- /dev/null
+++ b/libpurple/protocols/jabber/oob.h
@@ -0,0 +1,27 @@
+/**
+ * @file jutil.h utility functions
+ *
+ * gaim
+ *
+ * Copyright (C) 2003 Nathan Walp <faceprint@faceprint.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+#ifndef _GAIM_JABBER_OOB_H_
+#define _GAIM_JABBER_OOB_H_
+
+void jabber_oob_parse(JabberStream *js, xmlnode *packet);
+
+#endif /* _GAIM_JABBER_OOB_H_ */
diff --git a/libpurple/protocols/jabber/parser.c b/libpurple/protocols/jabber/parser.c
new file mode 100644
index 0000000000..7ba794a4a5
--- /dev/null
+++ b/libpurple/protocols/jabber/parser.c
@@ -0,0 +1,195 @@
+/*
+ * gaim - Jabber XML parser stuff
+ *
+ * Copyright (C) 2003, Nathan Walp <faceprint@faceprint.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ */
+#include "internal.h"
+
+#include <libxml/parser.h>
+
+#include "connection.h"
+#include "debug.h"
+#include "jabber.h"
+#include "parser.h"
+#include "util.h"
+#include "xmlnode.h"
+
+static void
+jabber_parser_element_start_libxml(void *user_data,
+ const xmlChar *element_name, const xmlChar *prefix, const xmlChar *namespace,
+ int nb_namespaces, const xmlChar **namespaces,
+ int nb_attributes, int nb_defaulted, const xmlChar **attributes)
+{
+ JabberStream *js = user_data;
+ xmlnode *node;
+ int i;
+
+ if(!element_name) {
+ return;
+ } else if(!xmlStrcmp(element_name, (xmlChar*) "stream")) {
+ js->protocol_version = JABBER_PROTO_0_9;
+ for(i=0; i < nb_attributes * 5; i += 5) {
+ int attrib_len = attributes[i+4] - attributes[i+3];
+ char *attrib = g_malloc(attrib_len + 1);
+ memcpy(attrib, attributes[i+3], attrib_len);
+ attrib[attrib_len] = '\0';
+
+ if(!xmlStrcmp(attributes[i], (xmlChar*) "version")
+ && !strcmp(attrib, "1.0")) {
+ js->protocol_version = JABBER_PROTO_1_0;
+ g_free(attrib);
+ } else if(!xmlStrcmp(attributes[i], (xmlChar*) "id")) {
+ if(js->stream_id)
+ g_free(js->stream_id);
+ js->stream_id = attrib;
+ }
+ }
+ if(js->protocol_version == JABBER_PROTO_0_9)
+ js->auth_type = JABBER_AUTH_IQ_AUTH;
+
+ if(js->state == JABBER_STREAM_INITIALIZING)
+ jabber_stream_set_state(js, JABBER_STREAM_AUTHENTICATING);
+ } else {
+
+ if(js->current)
+ node = xmlnode_new_child(js->current, (const char*) element_name);
+ else
+ node = xmlnode_new((const char*) element_name);
+ xmlnode_set_namespace(node, (const char*) namespace);
+
+ for(i=0; i < nb_attributes * 5; i+=5) {
+ char *txt;
+ int attrib_len = attributes[i+4] - attributes[i+3];
+ char *attrib = g_malloc(attrib_len + 1);
+ char *attrib_ns = NULL;
+
+ if (attributes[i+2]) {
+ attrib_ns = g_strdup(attributes[i+2]);;
+ }
+
+ memcpy(attrib, attributes[i+3], attrib_len);
+ attrib[attrib_len] = '\0';
+
+ txt = attrib;
+ attrib = gaim_unescape_html(txt);
+ g_free(txt);
+ xmlnode_set_attrib_with_namespace(node, (const char*) attributes[i], attrib_ns, attrib);
+ g_free(attrib);
+ g_free(attrib_ns);
+ }
+
+ js->current = node;
+ }
+}
+
+static void
+jabber_parser_element_end_libxml(void *user_data, const xmlChar *element_name,
+ const xmlChar *prefix, const xmlChar *namespace)
+{
+ JabberStream *js = user_data;
+
+ if(!js->current)
+ return;
+
+ if(js->current->parent) {
+ if(!xmlStrcmp((xmlChar*) js->current->name, element_name))
+ js->current = js->current->parent;
+ } else {
+ xmlnode *packet = js->current;
+ js->current = NULL;
+ jabber_process_packet(js, packet);
+ xmlnode_free(packet);
+ }
+}
+
+static void
+jabber_parser_element_text_libxml(void *user_data, const xmlChar *text, int text_len)
+{
+ JabberStream *js = user_data;
+
+ if(!js->current)
+ return;
+
+ if(!text || !text_len)
+ return;
+
+ xmlnode_insert_data(js->current, (const char*) text, text_len);
+}
+
+static xmlSAXHandler jabber_parser_libxml = {
+ .internalSubset = NULL,
+ .isStandalone = NULL,
+ .hasInternalSubset = NULL,
+ .hasExternalSubset = NULL,
+ .resolveEntity = NULL,
+ .getEntity = NULL,
+ .entityDecl = NULL,
+ .notationDecl = NULL,
+ .attributeDecl = NULL,
+ .elementDecl = NULL,
+ .unparsedEntityDecl = NULL,
+ .setDocumentLocator = NULL,
+ .startDocument = NULL,
+ .endDocument = NULL,
+ .startElement = NULL,
+ .endElement = NULL,
+ .reference = NULL,
+ .characters = jabber_parser_element_text_libxml,
+ .ignorableWhitespace = NULL,
+ .processingInstruction = NULL,
+ .comment = NULL,
+ .warning = NULL,
+ .error = NULL,
+ .fatalError = NULL,
+ .getParameterEntity = NULL,
+ .cdataBlock = NULL,
+ .externalSubset = NULL,
+ .initialized = XML_SAX2_MAGIC,
+ ._private = NULL,
+ .startElementNs = jabber_parser_element_start_libxml,
+ .endElementNs = jabber_parser_element_end_libxml,
+ .serror = NULL
+};
+
+void
+jabber_parser_setup(JabberStream *js)
+{
+ /* This seems backwards, but it makes sense. The libxml code creates
+ * the parser context when you try to use it (this way, it can figure
+ * out the encoding at creation time. So, setting up the parser is
+ * just a matter of destroying any current parser. */
+ if (js->context) {
+ xmlParseChunk(js->context, NULL,0,1);
+ xmlFreeParserCtxt(js->context);
+ js->context = NULL;
+ }
+}
+
+
+void jabber_parser_process(JabberStream *js, const char *buf, int len)
+{
+ if (js->context == NULL) {
+ /* libxml inconsistently starts parsing on creating the
+ * parser, so do a ParseChunk right afterwards to force it. */
+ js->context = xmlCreatePushParserCtxt(&jabber_parser_libxml, js, buf, len, NULL);
+ xmlParseChunk(js->context, "", 0, 0);
+ } else if (xmlParseChunk(js->context, buf, len, 0) < 0) {
+ gaim_connection_error(js->gc, _("XML Parse error"));
+ }
+}
+
diff --git a/libpurple/protocols/jabber/parser.h b/libpurple/protocols/jabber/parser.h
new file mode 100644
index 0000000000..29aa1eed0c
--- /dev/null
+++ b/libpurple/protocols/jabber/parser.h
@@ -0,0 +1,30 @@
+/**
+ * @file parser.h XML parser functions
+ *
+ * gaim
+ *
+ * Copyright (C) 2003 Nathan Walp <faceprint@faceprint.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+#ifndef _GAIM_JABBER_PARSER_H_
+#define _GAIM_JABBER_PARSER_H_
+
+#include "jabber.h"
+
+void jabber_parser_setup(JabberStream *js);
+void jabber_parser_process(JabberStream *js, const char *buf, int len);
+
+#endif /* _GAIM_JABBER_PARSER_H_ */
diff --git a/libpurple/protocols/jabber/presence.c b/libpurple/protocols/jabber/presence.c
new file mode 100644
index 0000000000..9ac70dfc57
--- /dev/null
+++ b/libpurple/protocols/jabber/presence.c
@@ -0,0 +1,617 @@
+/*
+ * gaim - Jabber Protocol Plugin
+ *
+ * Copyright (C) 2003, Nathan Walp <faceprint@faceprint.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ */
+#include "internal.h"
+
+#include "cipher.h"
+#include "debug.h"
+#include "notify.h"
+#include "request.h"
+#include "server.h"
+#include "status.h"
+#include "util.h"
+
+#include "buddy.h"
+#include "chat.h"
+#include "presence.h"
+#include "iq.h"
+#include "jutil.h"
+#include "xmlnode.h"
+
+
+static void chats_send_presence_foreach(gpointer key, gpointer val,
+ gpointer user_data)
+{
+ JabberChat *chat = val;
+ xmlnode *presence = user_data;
+ char *chat_full_jid;
+
+ if(!chat->conv)
+ return;
+
+ chat_full_jid = g_strdup_printf("%s@%s/%s", chat->room, chat->server,
+ chat->handle);
+
+ xmlnode_set_attrib(presence, "to", chat_full_jid);
+ jabber_send(chat->js, presence);
+ g_free(chat_full_jid);
+}
+
+void jabber_presence_fake_to_self(JabberStream *js, const GaimStatus *gstatus) {
+ char *my_base_jid;
+
+ if(!js->user)
+ return;
+
+ my_base_jid = g_strdup_printf("%s@%s", js->user->node, js->user->domain);
+ if(gaim_find_buddy(js->gc->account, my_base_jid)) {
+ JabberBuddy *jb;
+ JabberBuddyResource *jbr;
+ if((jb = jabber_buddy_find(js, my_base_jid, TRUE))) {
+ JabberBuddyState state;
+ char *msg;
+ int priority;
+
+ gaim_status_to_jabber(gstatus, &state, &msg, &priority);
+
+ if (state == JABBER_BUDDY_STATE_UNAVAILABLE || state == JABBER_BUDDY_STATE_UNKNOWN) {
+ jabber_buddy_remove_resource(jb, js->user->resource);
+ } else {
+ jabber_buddy_track_resource(jb, js->user->resource, priority, state, msg);
+ }
+ if((jbr = jabber_buddy_find_resource(jb, NULL))) {
+ gaim_prpl_got_user_status(js->gc->account, my_base_jid, jabber_buddy_state_get_status_id(jbr->state), "priority", jbr->priority, jbr->status ? "message" : NULL, jbr->status, NULL);
+ } else {
+ gaim_prpl_got_user_status(js->gc->account, my_base_jid, "offline", msg ? "message" : NULL, msg, NULL);
+ }
+
+ g_free(msg);
+ }
+ }
+ g_free(my_base_jid);
+}
+
+
+void jabber_presence_send(GaimAccount *account, GaimStatus *status)
+{
+ GaimConnection *gc = NULL;
+ JabberStream *js = NULL;
+ gboolean disconnected;
+ int primitive;
+ xmlnode *presence, *x, *photo;
+ char *stripped = NULL;
+ JabberBuddyState state;
+ int priority;
+
+ if(!gaim_status_is_active(status))
+ return;
+
+ disconnected = gaim_account_is_disconnected(account);
+ primitive = gaim_status_type_get_primitive(gaim_status_get_type(status));
+
+ if(disconnected)
+ return;
+
+ gc = gaim_account_get_connection(account);
+ js = gc->proto_data;
+
+ gaim_status_to_jabber(status, &state, &stripped, &priority);
+
+
+ presence = jabber_presence_create(state, stripped, priority);
+ g_free(stripped);
+
+ if(js->avatar_hash) {
+ x = xmlnode_new_child(presence, "x");
+ xmlnode_set_namespace(x, "vcard-temp:x:update");
+ photo = xmlnode_new_child(x, "photo");
+ xmlnode_insert_data(photo, js->avatar_hash, -1);
+ }
+
+ jabber_send(js, presence);
+
+ g_hash_table_foreach(js->chats, chats_send_presence_foreach, presence);
+ xmlnode_free(presence);
+
+ jabber_presence_fake_to_self(js, status);
+}
+
+xmlnode *jabber_presence_create(JabberBuddyState state, const char *msg, int priority)
+{
+ xmlnode *show, *status, *presence, *pri, *c;
+ const char *show_string = NULL;
+
+ presence = xmlnode_new("presence");
+
+ if(state == JABBER_BUDDY_STATE_UNAVAILABLE)
+ xmlnode_set_attrib(presence, "type", "unavailable");
+ else if(state != JABBER_BUDDY_STATE_ONLINE &&
+ state != JABBER_BUDDY_STATE_UNKNOWN &&
+ state != JABBER_BUDDY_STATE_ERROR)
+ show_string = jabber_buddy_state_get_show(state);
+
+ if(show_string) {
+ show = xmlnode_new_child(presence, "show");
+ xmlnode_insert_data(show, show_string, -1);
+ }
+
+ if(msg) {
+ status = xmlnode_new_child(presence, "status");
+ xmlnode_insert_data(status, msg, -1);
+ }
+
+ if(priority) {
+ char *pstr = g_strdup_printf("%d", priority);
+ pri = xmlnode_new_child(presence, "priority");
+ xmlnode_insert_data(pri, pstr, -1);
+ g_free(pstr);
+ }
+
+ /* JEP-0115 */
+ c = xmlnode_new_child(presence, "c");
+ xmlnode_set_namespace(c, "http://jabber.org/protocol/caps");
+ xmlnode_set_attrib(c, "node", CAPS0115_NODE);
+ xmlnode_set_attrib(c, "ver", VERSION);
+
+ return presence;
+}
+
+struct _jabber_add_permit {
+ GaimConnection *gc;
+ JabberStream *js;
+ char *who;
+};
+
+static void authorize_add_cb(struct _jabber_add_permit *jap)
+{
+ jabber_presence_subscription_set(jap->gc->proto_data, jap->who,
+ "subscribed");
+ g_free(jap->who);
+ g_free(jap);
+}
+
+static void deny_add_cb(struct _jabber_add_permit *jap)
+{
+ jabber_presence_subscription_set(jap->gc->proto_data, jap->who,
+ "unsubscribed");
+
+ g_free(jap->who);
+ g_free(jap);
+}
+
+static void jabber_vcard_parse_avatar(JabberStream *js, xmlnode *packet, gpointer blah)
+{
+ JabberBuddy *jb = NULL;
+ GaimBuddy *b = NULL;
+ xmlnode *vcard, *photo, *binval;
+ char *text;
+ guchar *data;
+ gsize size;
+ const char *from = xmlnode_get_attrib(packet, "from");
+
+ if(!from)
+ return;
+
+ jb = jabber_buddy_find(js, from, TRUE);
+
+ js->pending_avatar_requests = g_slist_remove(js->pending_avatar_requests, jb);
+
+ if((vcard = xmlnode_get_child(packet, "vCard")) ||
+ (vcard = xmlnode_get_child_with_namespace(packet, "query", "vcard-temp"))) {
+ if((photo = xmlnode_get_child(vcard, "PHOTO")) &&
+ (( (binval = xmlnode_get_child(photo, "BINVAL")) &&
+ (text = xmlnode_get_data(binval))) ||
+ (text = xmlnode_get_data(photo)))) {
+ data = gaim_base64_decode(text, &size);
+
+ gaim_buddy_icons_set_for_user(js->gc->account, from, data, size);
+ if((b = gaim_find_buddy(js->gc->account, from))) {
+ unsigned char hashval[20];
+ char hash[41], *p;
+ int i;
+
+ gaim_cipher_digest_region("sha1", data, size,
+ sizeof(hashval), hashval, NULL);
+ p = hash;
+ for(i=0; i<20; i++, p+=2)
+ snprintf(p, 3, "%02x", hashval[i]);
+ gaim_blist_node_set_string((GaimBlistNode*)b, "avatar_hash", hash);
+ }
+ g_free(data);
+ g_free(text);
+ }
+ }
+}
+
+void jabber_presence_parse(JabberStream *js, xmlnode *packet)
+{
+ const char *from = xmlnode_get_attrib(packet, "from");
+ const char *type = xmlnode_get_attrib(packet, "type");
+ const char *real_jid = NULL;
+ const char *affiliation = NULL;
+ const char *role = NULL;
+ char *status = NULL;
+ int priority = 0;
+ JabberID *jid;
+ JabberChat *chat;
+ JabberBuddy *jb;
+ JabberBuddyResource *jbr = NULL, *found_jbr = NULL;
+ GaimConvChatBuddyFlags flags = GAIM_CBFLAGS_NONE;
+ gboolean delayed = FALSE;
+ GaimBuddy *b = NULL;
+ char *buddy_name;
+ JabberBuddyState state = JABBER_BUDDY_STATE_UNKNOWN;
+ xmlnode *y;
+ gboolean muc = FALSE;
+ char *avatar_hash = NULL;
+
+ if(!(jb = jabber_buddy_find(js, from, TRUE)))
+ return;
+
+ if(!(jid = jabber_id_new(from)))
+ return;
+
+ if(jb->error_msg) {
+ g_free(jb->error_msg);
+ jb->error_msg = NULL;
+ }
+
+ if(type && !strcmp(type, "error")) {
+ char *msg = jabber_parse_error(js, packet);
+
+ state = JABBER_BUDDY_STATE_ERROR;
+ jb->error_msg = msg ? msg : g_strdup(_("Unknown Error in presence"));
+ } else if(type && !strcmp(type, "subscribe")) {
+ struct _jabber_add_permit *jap = g_new0(struct _jabber_add_permit, 1);
+ gboolean onlist = FALSE;
+ GaimBuddy *buddy = gaim_find_buddy(gaim_connection_get_account(js->gc), from);
+ JabberBuddy *jb = NULL;
+
+ if (buddy) {
+ jb = jabber_buddy_find(js, from, TRUE);
+ if ((jb->subscription & JABBER_SUB_TO) == 0)
+ onlist = TRUE;
+ }
+
+ jap->gc = js->gc;
+ jap->who = g_strdup(from);
+ jap->js = js;
+
+ gaim_account_request_authorization(gaim_connection_get_account(js->gc), from, NULL, NULL, NULL, onlist,
+ G_CALLBACK(authorize_add_cb), G_CALLBACK(deny_add_cb), jap);
+ jabber_id_free(jid);
+ return;
+ } else if(type && !strcmp(type, "subscribed")) {
+ /* we've been allowed to see their presence, but we don't care */
+ jabber_id_free(jid);
+ return;
+ } else if(type && !strcmp(type, "unsubscribe")) {
+ /* XXX I'm not sure this is the right way to handle this, it
+ * might be better to add "unsubscribe" to the presence status
+ * if lower down, but I'm not sure. */
+ /* they are unsubscribing from our presence, we don't care */
+ /* Well, maybe just a little, we might want/need to start
+ * acknowledging this (and the others) at some point. */
+ jabber_id_free(jid);
+ return;
+ } else {
+ if((y = xmlnode_get_child(packet, "show"))) {
+ char *show = xmlnode_get_data(y);
+ state = jabber_buddy_show_get_state(show);
+ g_free(show);
+ } else {
+ state = JABBER_BUDDY_STATE_ONLINE;
+ }
+ }
+
+
+ for(y = packet->child; y; y = y->next) {
+ if(y->type != XMLNODE_TYPE_TAG)
+ continue;
+
+ if(!strcmp(y->name, "status")) {
+ g_free(status);
+ status = xmlnode_get_data(y);
+ } else if(!strcmp(y->name, "priority")) {
+ char *p = xmlnode_get_data(y);
+ if(p) {
+ priority = atoi(p);
+ g_free(p);
+ }
+ } else if(!strcmp(y->name, "x")) {
+ const char *xmlns = xmlnode_get_namespace(y);
+ if(xmlns && !strcmp(xmlns, "jabber:x:delay")) {
+ /* XXX: compare the time. jabber:x:delay can happen on presence packets that aren't really and truly delayed */
+ delayed = TRUE;
+ } else if(xmlns && !strcmp(xmlns, "http://jabber.org/protocol/muc#user")) {
+ xmlnode *z;
+
+ muc = TRUE;
+ if((z = xmlnode_get_child(y, "status"))) {
+ const char *code = xmlnode_get_attrib(z, "code");
+ if(code && !strcmp(code, "201")) {
+ if((chat = jabber_chat_find(js, jid->node, jid->domain))) {
+ chat->config_dialog_type = GAIM_REQUEST_ACTION;
+ chat->config_dialog_handle =
+ gaim_request_action(js->gc,
+ _("Create New Room"),
+ _("Create New Room"),
+ _("You are creating a new room. Would"
+ " you like to configure it, or"
+ " accept the default settings?"),
+ 1, chat, 2, _("_Configure Room"),
+ G_CALLBACK(jabber_chat_request_room_configure),
+ _("_Accept Defaults"),
+ G_CALLBACK(jabber_chat_create_instant_room));
+ }
+ }
+ }
+ if((z = xmlnode_get_child(y, "item"))) {
+ real_jid = xmlnode_get_attrib(z, "jid");
+ affiliation = xmlnode_get_attrib(z, "affiliation");
+ role = xmlnode_get_attrib(z, "role");
+ if(affiliation != NULL && !strcmp(affiliation, "owner"))
+ flags |= GAIM_CBFLAGS_FOUNDER;
+ if (role != NULL) {
+ if (!strcmp(role, "moderator"))
+ flags |= GAIM_CBFLAGS_OP;
+ else if (!strcmp(role, "participant"))
+ flags |= GAIM_CBFLAGS_VOICE;
+ }
+ }
+ } else if(xmlns && !strcmp(xmlns, "vcard-temp:x:update")) {
+ xmlnode *photo = xmlnode_get_child(y, "photo");
+ if(photo) {
+ if(avatar_hash)
+ g_free(avatar_hash);
+ avatar_hash = xmlnode_get_data(photo);
+ }
+ }
+ }
+ }
+
+
+ if(jid->node && (chat = jabber_chat_find(js, jid->node, jid->domain))) {
+ static int i = 1;
+ char *room_jid = g_strdup_printf("%s@%s", jid->node, jid->domain);
+
+ if(state == JABBER_BUDDY_STATE_ERROR) {
+ char *title, *msg = jabber_parse_error(js, packet);
+
+ if(chat->conv) {
+ title = g_strdup_printf(_("Error in chat %s"), from);
+ serv_got_chat_left(js->gc, chat->id);
+ } else {
+ title = g_strdup_printf(_("Error joining chat %s"), from);
+ }
+ gaim_notify_error(js->gc, title, title, msg);
+ g_free(title);
+ g_free(msg);
+
+ jabber_chat_destroy(chat);
+ jabber_id_free(jid);
+ g_free(status);
+ g_free(room_jid);
+ if(avatar_hash)
+ g_free(avatar_hash);
+ return;
+ }
+
+
+ if(type && !strcmp(type, "unavailable")) {
+ gboolean nick_change = FALSE;
+
+ /* If we haven't joined the chat yet, we don't care that someone
+ * left, or it was us leaving after we closed the chat */
+ if(!chat->conv) {
+ if(jid->resource && chat->handle && !strcmp(jid->resource, chat->handle))
+ jabber_chat_destroy(chat);
+ jabber_id_free(jid);
+ g_free(status);
+ g_free(room_jid);
+ if(avatar_hash)
+ g_free(avatar_hash);
+ return;
+ }
+
+ jabber_buddy_remove_resource(jb, jid->resource);
+ if(chat->muc) {
+ xmlnode *x;
+ for(x = xmlnode_get_child(packet, "x"); x; x = xmlnode_get_next_twin(x)) {
+ const char *xmlns, *nick, *code;
+ xmlnode *stat, *item;
+ if(!(xmlns = xmlnode_get_namespace(x)) ||
+ strcmp(xmlns, "http://jabber.org/protocol/muc#user"))
+ continue;
+ if(!(stat = xmlnode_get_child(x, "status")))
+ continue;
+ if(!(code = xmlnode_get_attrib(stat, "code")))
+ continue;
+ if(!strcmp(code, "301")) {
+ /* XXX: we got banned */
+ } else if(!strcmp(code, "303")) {
+ if(!(item = xmlnode_get_child(x, "item")))
+ continue;
+ if(!(nick = xmlnode_get_attrib(item, "nick")))
+ continue;
+ nick_change = TRUE;
+ if(!strcmp(jid->resource, chat->handle)) {
+ g_free(chat->handle);
+ chat->handle = g_strdup(nick);
+ }
+ gaim_conv_chat_rename_user(GAIM_CONV_CHAT(chat->conv), jid->resource, nick);
+ jabber_chat_remove_handle(chat, jid->resource);
+ break;
+ } else if(!strcmp(code, "307")) {
+ /* XXX: we got kicked */
+ } else if(!strcmp(code, "321")) {
+ /* XXX: removed due to an affiliation change */
+ } else if(!strcmp(code, "322")) {
+ /* XXX: removed because room is now members-only */
+ } else if(!strcmp(code, "332")) {
+ /* XXX: removed due to system shutdown */
+ }
+ }
+ }
+ if(!nick_change) {
+ if(!g_utf8_collate(jid->resource, chat->handle)) {
+ serv_got_chat_left(js->gc, chat->id);
+ jabber_chat_destroy(chat);
+ } else {
+ gaim_conv_chat_remove_user(GAIM_CONV_CHAT(chat->conv), jid->resource,
+ status);
+ jabber_chat_remove_handle(chat, jid->resource);
+ }
+ }
+ } else {
+ if(!chat->conv) {
+ chat->id = i++;
+ chat->muc = muc;
+ chat->conv = serv_got_joined_chat(js->gc, chat->id, room_jid);
+ gaim_conv_chat_set_nick(GAIM_CONV_CHAT(chat->conv), chat->handle);
+
+ jabber_chat_disco_traffic(chat);
+ }
+
+ jabber_buddy_track_resource(jb, jid->resource, priority, state,
+ status);
+
+ jabber_chat_track_handle(chat, jid->resource, real_jid, affiliation, role);
+
+ if(!jabber_chat_find_buddy(chat->conv, jid->resource))
+ gaim_conv_chat_add_user(GAIM_CONV_CHAT(chat->conv), jid->resource,
+ real_jid, flags, !delayed);
+ else
+ gaim_conv_chat_user_set_flags(GAIM_CONV_CHAT(chat->conv), jid->resource,
+ flags);
+ }
+ g_free(room_jid);
+ } else {
+ buddy_name = g_strdup_printf("%s%s%s", jid->node ? jid->node : "",
+ jid->node ? "@" : "", jid->domain);
+ if((b = gaim_find_buddy(js->gc->account, buddy_name)) == NULL) {
+ gaim_debug_warning("jabber", "Got presence for unknown buddy %s on account %s (%x)",
+ buddy_name, gaim_account_get_username(js->gc->account), js->gc->account);
+ jabber_id_free(jid);
+ if(avatar_hash)
+ g_free(avatar_hash);
+ g_free(buddy_name);
+ g_free(status);
+ return;
+ }
+
+ if(avatar_hash) {
+ const char *avatar_hash2 = gaim_blist_node_get_string((GaimBlistNode*)b, "avatar_hash");
+ if(!avatar_hash2 || strcmp(avatar_hash, avatar_hash2)) {
+ JabberIq *iq;
+ xmlnode *vcard;
+
+ /* XXX this is a crappy way of trying to prevent
+ * someone from spamming us with presence packets
+ * and causing us to DoS ourselves...what we really
+ * need is a queue system that can throttle itself,
+ * but i'm too tired to write that right now */
+ if(!g_slist_find(js->pending_avatar_requests, jb)) {
+
+ js->pending_avatar_requests = g_slist_prepend(js->pending_avatar_requests, jb);
+
+ iq = jabber_iq_new(js, JABBER_IQ_GET);
+ xmlnode_set_attrib(iq->node, "to", buddy_name);
+ vcard = xmlnode_new_child(iq->node, "vCard");
+ xmlnode_set_namespace(vcard, "vcard-temp");
+
+ jabber_iq_set_callback(iq, jabber_vcard_parse_avatar, NULL);
+ jabber_iq_send(iq);
+ }
+ }
+ }
+
+ if(state == JABBER_BUDDY_STATE_ERROR ||
+ (type && (!strcmp(type, "unavailable") ||
+ !strcmp(type, "unsubscribed")))) {
+ GaimConversation *conv;
+
+ jabber_buddy_remove_resource(jb, jid->resource);
+ if((conv = jabber_find_unnormalized_conv(from, js->gc->account)))
+ gaim_conversation_set_name(conv, buddy_name);
+
+ } else {
+ jbr = jabber_buddy_track_resource(jb, jid->resource, priority,
+ state, status);
+ }
+
+ if((found_jbr = jabber_buddy_find_resource(jb, NULL))) {
+ if(!jbr || jbr == found_jbr) {
+ gaim_prpl_got_user_status(js->gc->account, buddy_name, jabber_buddy_state_get_status_id(state), "priority", found_jbr->priority, found_jbr->status ? "message" : NULL, found_jbr->status, NULL);
+ }
+ } else {
+ gaim_prpl_got_user_status(js->gc->account, buddy_name, "offline", status ? "message" : NULL, status, NULL);
+ }
+ g_free(buddy_name);
+ }
+ g_free(status);
+ jabber_id_free(jid);
+ if(avatar_hash)
+ g_free(avatar_hash);
+}
+
+void jabber_presence_subscription_set(JabberStream *js, const char *who, const char *type)
+{
+ xmlnode *presence = xmlnode_new("presence");
+
+ xmlnode_set_attrib(presence, "to", who);
+ xmlnode_set_attrib(presence, "type", type);
+
+ jabber_send(js, presence);
+ xmlnode_free(presence);
+}
+
+void gaim_status_to_jabber(const GaimStatus *status, JabberBuddyState *state, char **msg, int *priority)
+{
+ const char *status_id = NULL;
+ const char *formatted_msg = NULL;
+
+ if(state) *state = JABBER_BUDDY_STATE_UNKNOWN;
+ if(msg) *msg = NULL;
+ if(priority) *priority = 0;
+
+ if(!status) {
+ if(state) *state = JABBER_BUDDY_STATE_UNAVAILABLE;
+ } else {
+ if(state) {
+ status_id = gaim_status_get_id(status);
+ *state = jabber_buddy_status_id_get_state(status_id);
+ }
+
+ if(msg) {
+ formatted_msg = gaim_status_get_attr_string(status, "message");
+
+ /* if the message is blank, then there really isn't a message */
+ if(formatted_msg && !*formatted_msg)
+ formatted_msg = NULL;
+
+ if(formatted_msg)
+ gaim_markup_html_to_xhtml(formatted_msg, NULL, msg);
+ }
+
+ if(priority)
+ *priority = gaim_status_get_attr_int(status, "priority");
+ }
+}
diff --git a/libpurple/protocols/jabber/presence.h b/libpurple/protocols/jabber/presence.h
new file mode 100644
index 0000000000..3c65f3d823
--- /dev/null
+++ b/libpurple/protocols/jabber/presence.h
@@ -0,0 +1,37 @@
+/**
+ * @file presence.h Presence
+ *
+ * gaim
+ *
+ * Copyright (C) 2003 Nathan Walp <faceprint@faceprint.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+#ifndef _GAIM_JABBER_PRESENCE_H_
+#define _GAIM_JABBER_PRESENCE_H_
+
+#include "buddy.h"
+#include "jabber.h"
+#include "xmlnode.h"
+
+void jabber_presence_send(GaimAccount *account, GaimStatus *status);
+xmlnode *jabber_presence_create(JabberBuddyState state, const char *msg, int priority);
+void jabber_presence_parse(JabberStream *js, xmlnode *packet);
+void jabber_presence_subscription_set(JabberStream *js, const char *who,
+ const char *type);
+void jabber_presence_fake_to_self(JabberStream *js, const GaimStatus *status);
+void gaim_status_to_jabber(const GaimStatus *status, JabberBuddyState *state, char **msg, int *priority);
+
+#endif /* _GAIM_JABBER_PRESENCE_H_ */
diff --git a/libpurple/protocols/jabber/roster.c b/libpurple/protocols/jabber/roster.c
new file mode 100644
index 0000000000..d551466030
--- /dev/null
+++ b/libpurple/protocols/jabber/roster.c
@@ -0,0 +1,409 @@
+/*
+ * gaim - Jabber Protocol Plugin
+ *
+ * Copyright (C) 2003, Nathan Walp <faceprint@faceprint.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ */
+#include "internal.h"
+#include "debug.h"
+#include "server.h"
+#include "util.h"
+
+#include "buddy.h"
+#include "google.h"
+#include "presence.h"
+#include "roster.h"
+#include "iq.h"
+
+#include <string.h>
+
+
+void jabber_roster_request(JabberStream *js)
+{
+ JabberIq *iq;
+
+ iq = jabber_iq_new_query(js, JABBER_IQ_GET, "jabber:iq:roster");
+
+ jabber_iq_send(iq);
+}
+
+static void remove_gaim_buddies(JabberStream *js, const char *jid)
+{
+ GSList *buddies, *l;
+
+ buddies = gaim_find_buddies(js->gc->account, jid);
+
+ for(l = buddies; l; l = l->next)
+ gaim_blist_remove_buddy(l->data);
+
+ g_slist_free(buddies);
+}
+
+static void add_gaim_buddies_in_groups(JabberStream *js, const char *jid,
+ const char *alias, GSList *groups)
+{
+ GSList *buddies, *g2, *l;
+ gchar *my_bare_jid;
+
+ buddies = gaim_find_buddies(js->gc->account, jid);
+
+ g2 = groups;
+
+ if(!groups) {
+ if(!buddies)
+ g2 = g_slist_append(g2, g_strdup(_("Buddies")));
+ else
+ return;
+ }
+
+ my_bare_jid = g_strdup_printf("%s@%s", js->user->node, js->user->domain);
+
+ while(buddies) {
+ GaimBuddy *b = buddies->data;
+ GaimGroup *g = gaim_buddy_get_group(b);
+
+ buddies = g_slist_remove(buddies, b);
+
+ if((l = g_slist_find_custom(g2, g->name, (GCompareFunc)strcmp))) {
+ const char *servernick;
+
+ if((servernick = gaim_blist_node_get_string((GaimBlistNode*)b, "servernick")))
+ serv_got_alias(js->gc, jid, servernick);
+
+ if(alias && (!b->alias || strcmp(b->alias, alias)))
+ gaim_blist_alias_buddy(b, alias);
+ g_free(l->data);
+ g2 = g_slist_delete_link(g2, l);
+ } else {
+ gaim_blist_remove_buddy(b);
+ }
+ }
+
+ while(g2) {
+ GaimBuddy *b = gaim_buddy_new(js->gc->account, jid, alias);
+ GaimGroup *g = gaim_find_group(g2->data);
+
+ if(!g) {
+ g = gaim_group_new(g2->data);
+ gaim_blist_add_group(g, NULL);
+ }
+
+ gaim_blist_add_buddy(b, NULL, g, NULL);
+ gaim_blist_alias_buddy(b, alias);
+
+ /* If we just learned about ourself, then fake our status,
+ * because we won't be receiving a normal presence message
+ * about ourself. */
+ if(!strcmp(b->name, my_bare_jid)) {
+ GaimPresence *gpresence;
+ GaimStatus *status;
+
+ gpresence = gaim_account_get_presence(js->gc->account);
+ status = gaim_presence_get_active_status(gpresence);
+ jabber_presence_fake_to_self(js, status);
+ }
+
+ g_free(g2->data);
+ g2 = g_slist_delete_link(g2, g2);
+ }
+
+ g_free(my_bare_jid);
+ g_slist_free(buddies);
+}
+
+void jabber_roster_parse(JabberStream *js, xmlnode *packet)
+{
+ xmlnode *query, *item, *group;
+ const char *from = xmlnode_get_attrib(packet, "from");
+
+ if(from) {
+ char *from_norm;
+ gboolean invalid;
+
+ from_norm = g_strdup(jabber_normalize(js->gc->account, from));
+
+ if(!from_norm)
+ return;
+
+ invalid = g_utf8_collate(from_norm,
+ jabber_normalize(js->gc->account,
+ gaim_account_get_username(js->gc->account)));
+
+ g_free(from_norm);
+
+ if(invalid)
+ return;
+ }
+
+ query = xmlnode_get_child(packet, "query");
+ if(!query)
+ return;
+
+ js->roster_parsed = TRUE;
+
+ for(item = xmlnode_get_child(query, "item"); item; item = xmlnode_get_next_twin(item))
+ {
+ const char *jid, *name, *subscription, *ask;
+ JabberBuddy *jb;
+
+ subscription = xmlnode_get_attrib(item, "subscription");
+ jid = xmlnode_get_attrib(item, "jid");
+ name = xmlnode_get_attrib(item, "name");
+ ask = xmlnode_get_attrib(item, "ask");
+
+ if(!jid)
+ continue;
+
+ if(!(jb = jabber_buddy_find(js, jid, TRUE)))
+ continue;
+
+ if(subscription) {
+ gint me = -1;
+ char *jid_norm;
+ const char *username;
+
+ jid_norm = g_strdup(jabber_normalize(js->gc->account, jid));
+ username = gaim_account_get_username(js->gc->account);
+ me = g_utf8_collate(jid_norm,
+ jabber_normalize(js->gc->account,
+ username));
+
+ if(me == 0)
+ jb->subscription = JABBER_SUB_BOTH;
+ else if(!strcmp(subscription, "none"))
+ jb->subscription = JABBER_SUB_NONE;
+ else if(!strcmp(subscription, "to"))
+ jb->subscription = JABBER_SUB_TO;
+ else if(!strcmp(subscription, "from"))
+ jb->subscription = JABBER_SUB_FROM;
+ else if(!strcmp(subscription, "both"))
+ jb->subscription = JABBER_SUB_BOTH;
+ else if(!strcmp(subscription, "remove"))
+ jb->subscription = JABBER_SUB_REMOVE;
+ /* XXX: if subscription is now "from" or "none" we need to
+ * fake a signoff, since we won't get any presence from them
+ * anymore */
+ /* YYY: I was going to use this, but I'm not sure it's necessary
+ * anymore, but it's here in case it is. */
+ /*
+ if ((jb->subscription & JABBER_SUB_FROM) ||
+ (jb->subscription & JABBER_SUB_NONE)) {
+ gaim_prpl_got_user_status(js->gc->account, jid, "offline", NULL);
+ }
+ */
+ }
+
+ if(ask && !strcmp(ask, "subscribe"))
+ jb->subscription |= JABBER_SUB_PENDING;
+ else
+ jb->subscription &= ~JABBER_SUB_PENDING;
+
+ if(jb->subscription == JABBER_SUB_REMOVE) {
+ remove_gaim_buddies(js, jid);
+ } else {
+ GSList *groups = NULL;
+ for(group = xmlnode_get_child(item, "group"); group; group = xmlnode_get_next_twin(group)) {
+ char *group_name;
+
+ if(!(group_name = xmlnode_get_data(group)))
+ group_name = g_strdup("");
+
+ if (g_slist_find_custom(groups, group_name, (GCompareFunc)gaim_utf8_strcasecmp) == NULL)
+ groups = g_slist_append(groups, group_name);
+ }
+ if (js->server_caps & JABBER_CAP_GOOGLE_ROSTER)
+ jabber_google_roster_incoming(js, item);
+ add_gaim_buddies_in_groups(js, jid, name, groups);
+ }
+ }
+}
+
+static void jabber_roster_update(JabberStream *js, const char *name,
+ GSList *grps)
+{
+ GaimBuddy *b;
+ GaimGroup *g;
+ GSList *groups = NULL, *l;
+ JabberIq *iq;
+ xmlnode *query, *item, *group;
+
+ if(grps) {
+ groups = grps;
+ } else {
+ GSList *buddies = gaim_find_buddies(js->gc->account, name);
+ if(!buddies)
+ return;
+ while(buddies) {
+ b = buddies->data;
+ g = gaim_buddy_get_group(b);
+ groups = g_slist_append(groups, g->name);
+ buddies = g_slist_remove(buddies, b);
+ }
+ }
+
+ if(!(b = gaim_find_buddy(js->gc->account, name)))
+ return;
+
+ iq = jabber_iq_new_query(js, JABBER_IQ_SET, "jabber:iq:roster");
+
+ query = xmlnode_get_child(iq->node, "query");
+ item = xmlnode_new_child(query, "item");
+
+ xmlnode_set_attrib(item, "jid", name);
+
+ xmlnode_set_attrib(item, "name", b->alias ? b->alias : "");
+
+ for(l = groups; l; l = l->next) {
+ group = xmlnode_new_child(item, "group");
+ xmlnode_insert_data(group, l->data, -1);
+ }
+
+ if(!grps)
+ g_slist_free(groups);
+
+ if (js->server_caps & JABBER_CAP_GOOGLE_ROSTER) {
+ jabber_google_roster_outgoing(js, query, item);
+ xmlnode_set_attrib(query, "xmlns:gr", "google:roster");
+ xmlnode_set_attrib(query, "gr:ext", "2");
+ }
+ jabber_iq_send(iq);
+}
+
+void jabber_roster_add_buddy(GaimConnection *gc, GaimBuddy *buddy,
+ GaimGroup *group)
+{
+ JabberStream *js = gc->proto_data;
+ char *who;
+ GSList *groups = NULL;
+ JabberBuddy *jb;
+ JabberBuddyResource *jbr;
+ char *my_bare_jid;
+
+ if(!js->roster_parsed)
+ return;
+
+ if(!(who = jabber_get_bare_jid(buddy->name)))
+ return;
+
+ jb = jabber_buddy_find(js, buddy->name, FALSE);
+
+ if(!jb || !(jb->subscription & JABBER_SUB_TO)) {
+ groups = g_slist_append(groups, group->name);
+ }
+
+ jabber_roster_update(js, who, groups);
+
+ my_bare_jid = g_strdup_printf("%s@%s", js->user->node, js->user->domain);
+ if(!strcmp(who, my_bare_jid)) {
+ GaimPresence *gpresence;
+ GaimStatus *status;
+
+ gpresence = gaim_account_get_presence(js->gc->account);
+ status = gaim_presence_get_active_status(gpresence);
+ jabber_presence_fake_to_self(js, status);
+ } else if(!jb || !(jb->subscription & JABBER_SUB_TO)) {
+ jabber_presence_subscription_set(js, who, "subscribe");
+ } else if((jbr =jabber_buddy_find_resource(jb, NULL))) {
+ gaim_prpl_got_user_status(gc->account, who,
+ jabber_buddy_state_get_status_id(jbr->state),
+ "priority", jbr->priority, jbr->status ? "message" : NULL, jbr->status, NULL);
+ }
+
+ g_free(my_bare_jid);
+ g_free(who);
+}
+
+void jabber_roster_alias_change(GaimConnection *gc, const char *name, const char *alias)
+{
+ GaimBuddy *b = gaim_find_buddy(gc->account, name);
+
+ if(b != NULL) {
+ gaim_blist_alias_buddy(b, alias);
+
+ jabber_roster_update(gc->proto_data, name, NULL);
+ }
+}
+
+void jabber_roster_group_change(GaimConnection *gc, const char *name,
+ const char *old_group, const char *new_group)
+{
+ GSList *buddies, *groups = NULL;
+ GaimBuddy *b;
+ GaimGroup *g;
+
+ if(!old_group || !new_group || !strcmp(old_group, new_group))
+ return;
+
+ buddies = gaim_find_buddies(gc->account, name);
+ while(buddies) {
+ b = buddies->data;
+ g = gaim_buddy_get_group(b);
+ if(!strcmp(g->name, old_group))
+ groups = g_slist_append(groups, (char*)new_group); /* ick */
+ else
+ groups = g_slist_append(groups, g->name);
+ buddies = g_slist_remove(buddies, b);
+ }
+ jabber_roster_update(gc->proto_data, name, groups);
+ g_slist_free(groups);
+}
+
+void jabber_roster_group_rename(GaimConnection *gc, const char *old_name,
+ GaimGroup *group, GList *moved_buddies)
+{
+ GList *l;
+ for(l = moved_buddies; l; l = l->next) {
+ GaimBuddy *buddy = l->data;
+ jabber_roster_group_change(gc, buddy->name, old_name, group->name);
+ }
+}
+
+void jabber_roster_remove_buddy(GaimConnection *gc, GaimBuddy *buddy,
+ GaimGroup *group) {
+ GSList *buddies = gaim_find_buddies(gc->account, buddy->name);
+ GSList *groups = NULL;
+
+ buddies = g_slist_remove(buddies, buddy);
+ if(g_slist_length(buddies)) {
+ GaimBuddy *tmpbuddy;
+ GaimGroup *tmpgroup;
+
+ while(buddies) {
+ tmpbuddy = buddies->data;
+ tmpgroup = gaim_buddy_get_group(tmpbuddy);
+ groups = g_slist_append(groups, tmpgroup->name);
+ buddies = g_slist_remove(buddies, tmpbuddy);
+ }
+
+ jabber_roster_update(gc->proto_data, buddy->name, groups);
+ } else {
+ JabberIq *iq = jabber_iq_new_query(gc->proto_data, JABBER_IQ_SET,
+ "jabber:iq:roster");
+ xmlnode *query = xmlnode_get_child(iq->node, "query");
+ xmlnode *item = xmlnode_new_child(query, "item");
+
+ xmlnode_set_attrib(item, "jid", buddy->name);
+ xmlnode_set_attrib(item, "subscription", "remove");
+
+ jabber_iq_send(iq);
+ }
+
+ if(buddies)
+ g_slist_free(buddies);
+ if(groups)
+ g_slist_free(groups);
+}
diff --git a/libpurple/protocols/jabber/roster.h b/libpurple/protocols/jabber/roster.h
new file mode 100644
index 0000000000..798effa6c7
--- /dev/null
+++ b/libpurple/protocols/jabber/roster.h
@@ -0,0 +1,42 @@
+/**
+ * @file roster.h Roster manipulation
+ *
+ * gaim
+ *
+ * Copyright (C) 2003 Nathan Walp <faceprint@faceprint.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+#ifndef _GAIM_JABBER_ROSTER_H_
+#define _GAIM_JABBER_ROSTER_H_
+
+#include "jabber.h"
+
+void jabber_roster_request(JabberStream *js);
+
+void jabber_roster_parse(JabberStream *js, xmlnode *packet);
+
+void jabber_roster_add_buddy(GaimConnection *gc, GaimBuddy *buddy,
+ GaimGroup *group);
+void jabber_roster_alias_change(GaimConnection *gc, const char *name,
+ const char *alias);
+void jabber_roster_group_change(GaimConnection *gc, const char *name,
+ const char *old_group, const char *new_group);
+void jabber_roster_group_rename(GaimConnection *gc, const char *old_name,
+ GaimGroup *group, GList *moved_buddies);
+void jabber_roster_remove_buddy(GaimConnection *gc, GaimBuddy *buddy,
+ GaimGroup *group);
+
+#endif /* _GAIM_JABBER_ROSTER_H_ */
diff --git a/libpurple/protocols/jabber/si.c b/libpurple/protocols/jabber/si.c
new file mode 100644
index 0000000000..637e48802e
--- /dev/null
+++ b/libpurple/protocols/jabber/si.c
@@ -0,0 +1,974 @@
+/*
+ * gaim - Jabber Protocol Plugin
+ *
+ * Copyright (C) 2003, Nathan Walp <faceprint@faceprint.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ *
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ */
+
+#include "blist.h"
+
+#include "internal.h"
+#include "cipher.h"
+#include "debug.h"
+#include "ft.h"
+#include "network.h"
+#include "notify.h"
+
+#include "buddy.h"
+#include "disco.h"
+#include "jabber.h"
+#include "iq.h"
+#include "si.h"
+
+#include "si.h"
+
+struct bytestreams_streamhost {
+ char *jid;
+ char *host;
+ int port;
+};
+
+typedef struct _JabberSIXfer {
+ JabberStream *js;
+
+ GaimProxyConnectData *connect_data;
+ GaimNetworkListenData *listen_data;
+
+ gboolean accepted;
+
+ char *stream_id;
+ char *iq_id;
+
+ enum {
+ STREAM_METHOD_UNKNOWN = 0,
+ STREAM_METHOD_BYTESTREAMS = 2 << 1,
+ STREAM_METHOD_IBB = 2 << 2,
+ STREAM_METHOD_UNSUPPORTED = 2 << 31
+ } stream_method;
+
+ GList *streamhosts;
+ GaimProxyInfo *gpi;
+
+ char *rxqueue;
+ size_t rxlen;
+ gsize rxmaxlen;
+} JabberSIXfer;
+
+static GaimXfer*
+jabber_si_xfer_find(JabberStream *js, const char *sid, const char *from)
+{
+ GList *xfers;
+
+ if(!sid || !from)
+ return NULL;
+
+ for(xfers = js->file_transfers; xfers; xfers = xfers->next) {
+ GaimXfer *xfer = xfers->data;
+ JabberSIXfer *jsx = xfer->data;
+ if(jsx->stream_id && xfer->who &&
+ !strcmp(jsx->stream_id, sid) && !strcmp(xfer->who, from))
+ return xfer;
+ }
+
+ return NULL;
+}
+
+
+static void jabber_si_bytestreams_attempt_connect(GaimXfer *xfer);
+
+static void
+jabber_si_bytestreams_connect_cb(gpointer data, gint source, const gchar *error_message)
+{
+ GaimXfer *xfer = data;
+ JabberSIXfer *jsx = xfer->data;
+ JabberIq *iq;
+ xmlnode *query, *su;
+ struct bytestreams_streamhost *streamhost = jsx->streamhosts->data;
+
+ gaim_proxy_info_destroy(jsx->gpi);
+ jsx->connect_data = NULL;
+
+ if(source < 0) {
+ jsx->streamhosts = g_list_remove(jsx->streamhosts, streamhost);
+ g_free(streamhost->jid);
+ g_free(streamhost->host);
+ g_free(streamhost);
+ jabber_si_bytestreams_attempt_connect(xfer);
+ return;
+ }
+
+ iq = jabber_iq_new_query(jsx->js, JABBER_IQ_RESULT, "http://jabber.org/protocol/bytestreams");
+ xmlnode_set_attrib(iq->node, "to", xfer->who);
+ jabber_iq_set_id(iq, jsx->iq_id);
+ query = xmlnode_get_child(iq->node, "query");
+ su = xmlnode_new_child(query, "streamhost-used");
+ xmlnode_set_attrib(su, "jid", streamhost->jid);
+
+ jabber_iq_send(iq);
+
+ gaim_xfer_start(xfer, source, NULL, -1);
+}
+
+static void jabber_si_bytestreams_attempt_connect(GaimXfer *xfer)
+{
+ JabberSIXfer *jsx = xfer->data;
+ struct bytestreams_streamhost *streamhost;
+ char *dstaddr, *p;
+ int i;
+ unsigned char hashval[20];
+ JabberID *dstjid;
+
+ if(!jsx->streamhosts) {
+ JabberIq *iq = jabber_iq_new(jsx->js, JABBER_IQ_ERROR);
+ xmlnode *error, *inf;
+
+ if(jsx->iq_id)
+ jabber_iq_set_id(iq, jsx->iq_id);
+
+ xmlnode_set_attrib(iq->node, "to", xfer->who);
+ error = xmlnode_new_child(iq->node, "error");
+ xmlnode_set_attrib(error, "code", "404");
+ xmlnode_set_attrib(error, "type", "cancel");
+ inf = xmlnode_new_child(error, "item-not-found");
+ xmlnode_set_namespace(inf, "urn:ietf:params:xml:ns:xmpp-stanzas");
+
+ jabber_iq_send(iq);
+
+ gaim_xfer_cancel_local(xfer);
+
+ return;
+ }
+
+ streamhost = jsx->streamhosts->data;
+
+ dstjid = jabber_id_new(xfer->who);
+
+ if(dstjid != NULL) {
+ jsx->gpi = gaim_proxy_info_new();
+ gaim_proxy_info_set_type(jsx->gpi, GAIM_PROXY_SOCKS5);
+ gaim_proxy_info_set_host(jsx->gpi, streamhost->host);
+ gaim_proxy_info_set_port(jsx->gpi, streamhost->port);
+
+
+
+ dstaddr = g_strdup_printf("%s%s@%s/%s%s@%s/%s", jsx->stream_id, dstjid->node, dstjid->domain, dstjid->resource, jsx->js->user->node,
+ jsx->js->user->domain, jsx->js->user->resource);
+
+ gaim_cipher_digest_region("sha1", (guchar *)dstaddr, strlen(dstaddr),
+ sizeof(hashval), hashval, NULL);
+ g_free(dstaddr);
+ dstaddr = g_malloc(41);
+ p = dstaddr;
+ for(i=0; i<20; i++, p+=2)
+ snprintf(p, 3, "%02x", hashval[i]);
+
+ jsx->connect_data = gaim_proxy_connect_socks5(NULL, jsx->gpi,
+ dstaddr, 0,
+ jabber_si_bytestreams_connect_cb, xfer);
+ g_free(dstaddr);
+
+ jabber_id_free(dstjid);
+ }
+
+ if (jsx->connect_data == NULL)
+ {
+ jsx->streamhosts = g_list_remove(jsx->streamhosts, streamhost);
+ g_free(streamhost->jid);
+ g_free(streamhost->host);
+ g_free(streamhost);
+ jabber_si_bytestreams_attempt_connect(xfer);
+ }
+}
+
+void jabber_bytestreams_parse(JabberStream *js, xmlnode *packet)
+{
+ GaimXfer *xfer;
+ JabberSIXfer *jsx;
+ xmlnode *query, *streamhost;
+ const char *sid, *from, *type;
+
+ if(!(type = xmlnode_get_attrib(packet, "type")) || strcmp(type, "set"))
+ return;
+
+ if(!(from = xmlnode_get_attrib(packet, "from")))
+ return;
+
+ if(!(query = xmlnode_get_child(packet, "query")))
+ return;
+
+ if(!(sid = xmlnode_get_attrib(query, "sid")))
+ return;
+
+ if(!(xfer = jabber_si_xfer_find(js, sid, from)))
+ return;
+
+ jsx = xfer->data;
+
+ if(!jsx->accepted)
+ return;
+
+ if(jsx->iq_id)
+ g_free(jsx->iq_id);
+ jsx->iq_id = g_strdup(xmlnode_get_attrib(packet, "id"));
+
+ for(streamhost = xmlnode_get_child(query, "streamhost"); streamhost;
+ streamhost = xmlnode_get_next_twin(streamhost)) {
+ const char *jid, *host, *port;
+ int portnum;
+
+ if((jid = xmlnode_get_attrib(streamhost, "jid")) &&
+ (host = xmlnode_get_attrib(streamhost, "host")) &&
+ (port = xmlnode_get_attrib(streamhost, "port")) &&
+ (portnum = atoi(port))) {
+ struct bytestreams_streamhost *sh = g_new0(struct bytestreams_streamhost, 1);
+ sh->jid = g_strdup(jid);
+ sh->host = g_strdup(host);
+ sh->port = portnum;
+ jsx->streamhosts = g_list_append(jsx->streamhosts, sh);
+ }
+ }
+
+ jabber_si_bytestreams_attempt_connect(xfer);
+}
+
+
+static void
+jabber_si_xfer_bytestreams_send_read_again_resp_cb(gpointer data, gint source,
+ GaimInputCondition cond)
+{
+ GaimXfer *xfer = data;
+ JabberSIXfer *jsx = xfer->data;
+ int len;
+
+ len = write(source, jsx->rxqueue + jsx->rxlen, jsx->rxmaxlen - jsx->rxlen);
+ if (len < 0 && errno == EAGAIN)
+ return;
+ else if (len < 0) {
+ gaim_input_remove(xfer->watcher);
+ xfer->watcher = 0;
+ g_free(jsx->rxqueue);
+ jsx->rxqueue = NULL;
+ close(source);
+ gaim_xfer_cancel_remote(xfer);
+ return;
+ }
+ jsx->rxlen += len;
+
+ if (jsx->rxlen < jsx->rxmaxlen)
+ return;
+
+ gaim_input_remove(xfer->watcher);
+ xfer->watcher = 0;
+ g_free(jsx->rxqueue);
+ jsx->rxqueue = NULL;
+
+ gaim_xfer_start(xfer, source, NULL, -1);
+}
+
+static void
+jabber_si_xfer_bytestreams_send_read_again_cb(gpointer data, gint source,
+ GaimInputCondition cond)
+{
+ GaimXfer *xfer = data;
+ JabberSIXfer *jsx = xfer->data;
+ int i;
+ char buffer[256];
+ int len;
+ char *dstaddr, *p;
+ unsigned char hashval[20];
+ const char *host;
+
+ gaim_debug_info("jabber", "in jabber_si_xfer_bytestreams_send_read_again_cb\n");
+
+ if(jsx->rxlen < 5) {
+ gaim_debug_info("jabber", "reading the first 5 bytes\n");
+ len = read(source, buffer, 5 - jsx->rxlen);
+ if(len < 0 && errno == EAGAIN)
+ return;
+ else if(len <= 0) {
+ gaim_input_remove(xfer->watcher);
+ xfer->watcher = 0;
+ close(source);
+ gaim_xfer_cancel_remote(xfer);
+ return;
+ }
+ jsx->rxqueue = g_realloc(jsx->rxqueue, len + jsx->rxlen);
+ memcpy(jsx->rxqueue + jsx->rxlen, buffer, len);
+ jsx->rxlen += len;
+ return;
+ } else if(jsx->rxqueue[0] != 0x05 || jsx->rxqueue[1] != 0x01 ||
+ jsx->rxqueue[3] != 0x03) {
+ gaim_debug_info("jabber", "invalid socks5 stuff\n");
+ gaim_input_remove(xfer->watcher);
+ xfer->watcher = 0;
+ close(source);
+ gaim_xfer_cancel_remote(xfer);
+ return;
+ } else if(jsx->rxlen - 5 < jsx->rxqueue[4] + 2) {
+ gaim_debug_info("jabber", "reading umpteen more bytes\n");
+ len = read(source, buffer, jsx->rxqueue[4] + 5 + 2 - jsx->rxlen);
+ if(len < 0 && errno == EAGAIN)
+ return;
+ else if(len <= 0) {
+ gaim_input_remove(xfer->watcher);
+ xfer->watcher = 0;
+ close(source);
+ gaim_xfer_cancel_remote(xfer);
+ return;
+ }
+ jsx->rxqueue = g_realloc(jsx->rxqueue, len + jsx->rxlen);
+ memcpy(jsx->rxqueue + jsx->rxlen, buffer, len);
+ jsx->rxlen += len;
+ }
+
+ if(jsx->rxlen - 5 < jsx->rxqueue[4] + 2)
+ return;
+
+ gaim_input_remove(xfer->watcher);
+ xfer->watcher = 0;
+
+ dstaddr = g_strdup_printf("%s%s@%s/%s%s", jsx->stream_id,
+ jsx->js->user->node, jsx->js->user->domain,
+ jsx->js->user->resource, xfer->who);
+
+ gaim_cipher_digest_region("sha1", (guchar *)dstaddr, strlen(dstaddr),
+ sizeof(hashval), hashval, NULL);
+ g_free(dstaddr);
+ dstaddr = g_malloc(41);
+ p = dstaddr;
+ for(i=0; i<20; i++, p+=2)
+ snprintf(p, 3, "%02x", hashval[i]);
+
+ if(jsx->rxqueue[4] != 40 || strncmp(dstaddr, jsx->rxqueue+5, 40) ||
+ jsx->rxqueue[45] != 0x00 || jsx->rxqueue[46] != 0x00) {
+ gaim_debug_error("jabber", "someone connected with the wrong info!\n");
+ close(source);
+ gaim_xfer_cancel_remote(xfer);
+ return;
+ }
+
+ g_free(jsx->rxqueue);
+ host = gaim_network_get_my_ip(jsx->js->fd);
+
+ jsx->rxmaxlen = 5 + strlen(host) + 2;
+ jsx->rxqueue = g_malloc(jsx->rxmaxlen);
+ jsx->rxlen = 0;
+
+ jsx->rxqueue[0] = 0x05;
+ jsx->rxqueue[1] = 0x00;
+ jsx->rxqueue[2] = 0x00;
+ jsx->rxqueue[3] = 0x03;
+ jsx->rxqueue[4] = strlen(host);
+ memcpy(jsx->rxqueue + 5, host, strlen(host));
+ jsx->rxqueue[5+strlen(host)] = 0x00;
+ jsx->rxqueue[6+strlen(host)] = 0x00;
+
+ xfer->watcher = gaim_input_add(source, GAIM_INPUT_WRITE,
+ jabber_si_xfer_bytestreams_send_read_again_resp_cb, xfer);
+ jabber_si_xfer_bytestreams_send_read_again_resp_cb(xfer, source,
+ GAIM_INPUT_WRITE);
+}
+
+static void
+jabber_si_xfer_bytestreams_send_read_response_cb(gpointer data, gint source,
+ GaimInputCondition cond)
+{
+ GaimXfer *xfer = data;
+ JabberSIXfer *jsx = xfer->data;
+ int len;
+
+ len = write(source, jsx->rxqueue + jsx->rxlen, jsx->rxmaxlen - jsx->rxlen);
+ if (len < 0 && errno == EAGAIN)
+ return;
+ else if (len < 0) {
+ gaim_input_remove(xfer->watcher);
+ xfer->watcher = 0;
+ g_free(jsx->rxqueue);
+ jsx->rxqueue = NULL;
+ close(source);
+ gaim_xfer_cancel_remote(xfer);
+ return;
+ }
+ jsx->rxlen += len;
+
+ if (jsx->rxlen < jsx->rxmaxlen)
+ return;
+
+ gaim_input_remove(xfer->watcher);
+ xfer->watcher = 0;
+
+ if (jsx->rxqueue[1] == 0x00) {
+ xfer->watcher = gaim_input_add(source, GAIM_INPUT_READ,
+ jabber_si_xfer_bytestreams_send_read_again_cb, xfer);
+ g_free(jsx->rxqueue);
+ jsx->rxqueue = NULL;
+ } else {
+ close(source);
+ gaim_xfer_cancel_remote(xfer);
+ }
+}
+
+static void
+jabber_si_xfer_bytestreams_send_read_cb(gpointer data, gint source,
+ GaimInputCondition cond)
+{
+ GaimXfer *xfer = data;
+ JabberSIXfer *jsx = xfer->data;
+ int i;
+ int len;
+ char buffer[256];
+
+ gaim_debug_info("jabber", "in jabber_si_xfer_bytestreams_send_read_cb\n");
+
+ xfer->fd = source;
+
+ if(jsx->rxlen < 2) {
+ gaim_debug_info("jabber", "reading those first two bytes\n");
+ len = read(source, buffer, 2 - jsx->rxlen);
+ if(len < 0 && errno == EAGAIN)
+ return;
+ else if(len <= 0) {
+ gaim_input_remove(xfer->watcher);
+ xfer->watcher = 0;
+ close(source);
+ gaim_xfer_cancel_remote(xfer);
+ return;
+ }
+ jsx->rxqueue = g_realloc(jsx->rxqueue, len + jsx->rxlen);
+ memcpy(jsx->rxqueue + jsx->rxlen, buffer, len);
+ jsx->rxlen += len;
+ return;
+ } else if(jsx->rxlen - 2 < jsx->rxqueue[1]) {
+ gaim_debug_info("jabber", "reading the next umpteen bytes\n");
+ len = read(source, buffer, jsx->rxqueue[1] + 2 - jsx->rxlen);
+ if(len < 0 && errno == EAGAIN)
+ return;
+ else if(len <= 0) {
+ gaim_input_remove(xfer->watcher);
+ xfer->watcher = 0;
+ close(source);
+ gaim_xfer_cancel_remote(xfer);
+ return;
+ }
+ jsx->rxqueue = g_realloc(jsx->rxqueue, len + jsx->rxlen);
+ memcpy(jsx->rxqueue + jsx->rxlen, buffer, len);
+ jsx->rxlen += len;
+ }
+
+ if(jsx->rxlen -2 < jsx->rxqueue[1])
+ return;
+
+ gaim_input_remove(xfer->watcher);
+ xfer->watcher = 0;
+
+ gaim_debug_info("jabber", "checking to make sure we're socks FIVE\n");
+
+ if(jsx->rxqueue[0] != 0x05) {
+ close(source);
+ gaim_xfer_cancel_remote(xfer);
+ return;
+ }
+
+ gaim_debug_info("jabber", "going to test %hhu different methods\n", jsx->rxqueue[1]);
+
+ for(i=0; i<jsx->rxqueue[1]; i++) {
+
+ gaim_debug_info("jabber", "testing %hhu\n", jsx->rxqueue[i+2]);
+ if(jsx->rxqueue[i+2] == 0x00) {
+ g_free(jsx->rxqueue);
+ jsx->rxlen = 0;
+ jsx->rxmaxlen = 2;
+ jsx->rxqueue = g_malloc(jsx->rxmaxlen);
+ jsx->rxqueue[0] = 0x05;
+ jsx->rxqueue[1] = 0x00;
+ xfer->watcher = gaim_input_add(source, GAIM_INPUT_WRITE,
+ jabber_si_xfer_bytestreams_send_read_response_cb,
+ xfer);
+ jabber_si_xfer_bytestreams_send_read_response_cb(xfer,
+ source, GAIM_INPUT_WRITE);
+ jsx->rxqueue = NULL;
+ jsx->rxlen = 0;
+ return;
+ }
+ }
+
+ g_free(jsx->rxqueue);
+ jsx->rxlen = 0;
+ jsx->rxmaxlen = 2;
+ jsx->rxqueue = g_malloc(jsx->rxmaxlen);
+ jsx->rxqueue[0] = 0x05;
+ jsx->rxqueue[1] = 0xFF;
+ xfer->watcher = gaim_input_add(source, GAIM_INPUT_WRITE,
+ jabber_si_xfer_bytestreams_send_read_response_cb, xfer);
+ jabber_si_xfer_bytestreams_send_read_response_cb(xfer,
+ source, GAIM_INPUT_WRITE);
+}
+
+static void
+jabber_si_xfer_bytestreams_send_connected_cb(gpointer data, gint source,
+ GaimInputCondition cond)
+{
+ GaimXfer *xfer = data;
+ int acceptfd;
+
+ gaim_debug_info("jabber", "in jabber_si_xfer_bytestreams_send_connected_cb\n");
+
+ acceptfd = accept(source, NULL, 0);
+ if(acceptfd == -1 && (errno == EAGAIN || errno == EWOULDBLOCK))
+ return;
+ else if(acceptfd == -1) {
+ gaim_debug_warning("jabber", "accept: %s\n", strerror(errno));
+ return;
+ }
+
+ gaim_input_remove(xfer->watcher);
+ close(source);
+
+ xfer->watcher = gaim_input_add(acceptfd, GAIM_INPUT_READ,
+ jabber_si_xfer_bytestreams_send_read_cb, xfer);
+}
+
+static void
+jabber_si_xfer_bytestreams_listen_cb(int sock, gpointer data)
+{
+ GaimXfer *xfer = data;
+ JabberSIXfer *jsx;
+ JabberIq *iq;
+ xmlnode *query, *streamhost;
+ char *jid, *port;
+
+ jsx = xfer->data;
+ jsx->listen_data = NULL;
+
+ if (gaim_xfer_get_status(xfer) == GAIM_XFER_STATUS_CANCEL_LOCAL) {
+ gaim_xfer_unref(xfer);
+ return;
+ }
+
+ gaim_xfer_unref(xfer);
+
+ if (sock < 0) {
+ gaim_xfer_cancel_local(xfer);
+ return;
+ }
+
+ iq = jabber_iq_new_query(jsx->js, JABBER_IQ_SET,
+ "http://jabber.org/protocol/bytestreams");
+ xmlnode_set_attrib(iq->node, "to", xfer->who);
+ query = xmlnode_get_child(iq->node, "query");
+
+ xmlnode_set_attrib(query, "sid", jsx->stream_id);
+
+ streamhost = xmlnode_new_child(query, "streamhost");
+ jid = g_strdup_printf("%s@%s/%s", jsx->js->user->node,
+ jsx->js->user->domain, jsx->js->user->resource);
+ xmlnode_set_attrib(streamhost, "jid", jid);
+ g_free(jid);
+
+ /* XXX: shouldn't we use the public IP or something? here */
+ xmlnode_set_attrib(streamhost, "host",
+ gaim_network_get_my_ip(jsx->js->fd));
+ xfer->local_port = gaim_network_get_port_from_fd(sock);
+ port = g_strdup_printf("%hu", xfer->local_port);
+ xmlnode_set_attrib(streamhost, "port", port);
+ g_free(port);
+
+ xfer->watcher = gaim_input_add(sock, GAIM_INPUT_READ,
+ jabber_si_xfer_bytestreams_send_connected_cb, xfer);
+
+ /* XXX: insert proxies here */
+
+ /* XXX: callback to find out which streamhost they used, or see if they
+ * screwed it up */
+ jabber_iq_send(iq);
+
+}
+
+static void
+jabber_si_xfer_bytestreams_send_init(GaimXfer *xfer)
+{
+ JabberSIXfer *jsx;
+
+ gaim_xfer_ref(xfer);
+
+ jsx = xfer->data;
+ jsx->listen_data = gaim_network_listen_range(0, 0, SOCK_STREAM,
+ jabber_si_xfer_bytestreams_listen_cb, xfer);
+ if (jsx->listen_data == NULL) {
+ gaim_xfer_unref(xfer);
+ /* XXX: couldn't open a port, we're fscked */
+ gaim_xfer_cancel_local(xfer);
+ return;
+ }
+
+}
+
+static void jabber_si_xfer_send_method_cb(JabberStream *js, xmlnode *packet,
+ gpointer data)
+{
+ GaimXfer *xfer = data;
+ xmlnode *si, *feature, *x, *field, *value;
+
+ if(!(si = xmlnode_get_child_with_namespace(packet, "si", "http://jabber.org/protocol/si"))) {
+ gaim_xfer_cancel_remote(xfer);
+ return;
+ }
+
+ if(!(feature = xmlnode_get_child_with_namespace(si, "feature", "http://jabber.org/protocol/feature-neg"))) {
+ gaim_xfer_cancel_remote(xfer);
+ return;
+ }
+
+ if(!(x = xmlnode_get_child_with_namespace(feature, "x", "jabber:x:data"))) {
+ gaim_xfer_cancel_remote(xfer);
+ return;
+ }
+
+ for(field = xmlnode_get_child(x, "field"); field; field = xmlnode_get_next_twin(field)) {
+ const char *var = xmlnode_get_attrib(field, "var");
+
+ if(var && !strcmp(var, "stream-method")) {
+ if((value = xmlnode_get_child(field, "value"))) {
+ char *val = xmlnode_get_data(value);
+ if(val && !strcmp(val, "http://jabber.org/protocol/bytestreams")) {
+ jabber_si_xfer_bytestreams_send_init(xfer);
+ g_free(val);
+ return;
+ }
+ g_free(val);
+ }
+ }
+ }
+ gaim_xfer_cancel_remote(xfer);
+}
+
+static void jabber_si_xfer_send_request(GaimXfer *xfer)
+{
+ JabberSIXfer *jsx = xfer->data;
+ JabberIq *iq;
+ xmlnode *si, *file, *feature, *x, *field, *option, *value;
+ char buf[32];
+
+ xfer->filename = g_path_get_basename(xfer->local_filename);
+
+ iq = jabber_iq_new(jsx->js, JABBER_IQ_SET);
+ xmlnode_set_attrib(iq->node, "to", xfer->who);
+ si = xmlnode_new_child(iq->node, "si");
+ xmlnode_set_namespace(si, "http://jabber.org/protocol/si");
+ jsx->stream_id = jabber_get_next_id(jsx->js);
+ xmlnode_set_attrib(si, "id", jsx->stream_id);
+ xmlnode_set_attrib(si, "profile",
+ "http://jabber.org/protocol/si/profile/file-transfer");
+
+ file = xmlnode_new_child(si, "file");
+ xmlnode_set_namespace(file,
+ "http://jabber.org/protocol/si/profile/file-transfer");
+ xmlnode_set_attrib(file, "name", xfer->filename);
+ g_snprintf(buf, sizeof(buf), "%" G_GSIZE_FORMAT, xfer->size);
+ xmlnode_set_attrib(file, "size", buf);
+ /* maybe later we'll do hash and date attribs */
+
+ feature = xmlnode_new_child(si, "feature");
+ xmlnode_set_namespace(feature,
+ "http://jabber.org/protocol/feature-neg");
+ x = xmlnode_new_child(feature, "x");
+ xmlnode_set_namespace(x, "jabber:x:data");
+ xmlnode_set_attrib(x, "type", "form");
+ field = xmlnode_new_child(x, "field");
+ xmlnode_set_attrib(field, "var", "stream-method");
+ xmlnode_set_attrib(field, "type", "list-single");
+ option = xmlnode_new_child(field, "option");
+ value = xmlnode_new_child(option, "value");
+ xmlnode_insert_data(value, "http://jabber.org/protocol/bytestreams",
+ -1);
+ /*
+ option = xmlnode_new_child(field, "option");
+ value = xmlnode_new_child(option, "value");
+ xmlnode_insert_data(value, "http://jabber.org/protocol/ibb", -1);
+ */
+
+ jabber_iq_set_callback(iq, jabber_si_xfer_send_method_cb, xfer);
+
+ jabber_iq_send(iq);
+}
+
+static void jabber_si_xfer_free(GaimXfer *xfer)
+{
+ JabberSIXfer *jsx = xfer->data;
+ JabberStream *js = jsx->js;
+
+ js->file_transfers = g_list_remove(js->file_transfers, xfer);
+
+ if (jsx->connect_data != NULL)
+ gaim_proxy_connect_cancel(jsx->connect_data);
+ if (jsx->listen_data != NULL)
+ gaim_network_listen_cancel(jsx->listen_data);
+
+ g_free(jsx->stream_id);
+ g_free(jsx->iq_id);
+ /* XXX: free other stuff */
+ g_free(jsx->rxqueue);
+ g_free(jsx);
+ xfer->data = NULL;
+}
+
+static void jabber_si_xfer_cancel_send(GaimXfer *xfer)
+{
+ jabber_si_xfer_free(xfer);
+ gaim_debug(GAIM_DEBUG_INFO, "jabber", "in jabber_si_xfer_cancel_send\n");
+}
+
+
+static void jabber_si_xfer_request_denied(GaimXfer *xfer)
+{
+ jabber_si_xfer_free(xfer);
+ gaim_debug(GAIM_DEBUG_INFO, "jabber", "in jabber_si_xfer_request_denied\n");
+}
+
+
+static void jabber_si_xfer_cancel_recv(GaimXfer *xfer)
+{
+ jabber_si_xfer_free(xfer);
+ gaim_debug(GAIM_DEBUG_INFO, "jabber", "in jabber_si_xfer_cancel_recv\n");
+}
+
+
+static void jabber_si_xfer_end(GaimXfer *xfer)
+{
+ jabber_si_xfer_free(xfer);
+}
+
+
+static void jabber_si_xfer_send_disco_cb(JabberStream *js, const char *who,
+ JabberCapabilities capabilities, gpointer data)
+{
+ GaimXfer *xfer = data;
+
+ if(capabilities & JABBER_CAP_SI_FILE_XFER) {
+ jabber_si_xfer_send_request(xfer);
+ } else {
+ char *msg = g_strdup_printf(_("Unable to send file to %s, user does not support file transfers"), who);
+ gaim_notify_error(js->gc, _("File Send Failed"),
+ _("File Send Failed"), msg);
+ g_free(msg);
+ }
+}
+
+static void jabber_si_xfer_init(GaimXfer *xfer)
+{
+ JabberSIXfer *jsx = xfer->data;
+ JabberIq *iq;
+ if(gaim_xfer_get_type(xfer) == GAIM_XFER_SEND) {
+ JabberBuddy *jb;
+ JabberBuddyResource *jbr = NULL;
+
+ jb = jabber_buddy_find(jsx->js, xfer->who, TRUE);
+ /* XXX */
+ if(!jb)
+ return;
+
+ /* XXX: for now, send to the first resource available */
+ if(g_list_length(jb->resources) >= 1) {
+ char **who_v = g_strsplit(xfer->who, "/", 2);
+ char *who;
+
+ jbr = jabber_buddy_find_resource(jb, NULL);
+ who = g_strdup_printf("%s/%s", who_v[0], jbr->name);
+ g_strfreev(who_v);
+ g_free(xfer->who);
+ xfer->who = who;
+ jabber_disco_info_do(jsx->js, who,
+ jabber_si_xfer_send_disco_cb, xfer);
+ } else {
+ return; /* XXX: ick */
+ }
+ } else {
+ xmlnode *si, *feature, *x, *field, *value;
+
+ iq = jabber_iq_new(jsx->js, JABBER_IQ_RESULT);
+ xmlnode_set_attrib(iq->node, "to", xfer->who);
+ if(jsx->iq_id)
+ jabber_iq_set_id(iq, jsx->iq_id);
+
+ jsx->accepted = TRUE;
+
+ si = xmlnode_new_child(iq->node, "si");
+ xmlnode_set_namespace(si, "http://jabber.org/protocol/si");
+
+ feature = xmlnode_new_child(si, "feature");
+ xmlnode_set_namespace(feature, "http://jabber.org/protocol/feature-neg");
+
+ x = xmlnode_new_child(feature, "x");
+ xmlnode_set_namespace(x, "jabber:x:data");
+ xmlnode_set_attrib(x, "type", "submit");
+
+ field = xmlnode_new_child(x, "field");
+ xmlnode_set_attrib(field, "var", "stream-method");
+
+ value = xmlnode_new_child(field, "value");
+ if(jsx->stream_method & STREAM_METHOD_BYTESTREAMS)
+ xmlnode_insert_data(value, "http://jabber.org/protocol/bytestreams", -1);
+ /*
+ else if(jsx->stream_method & STREAM_METHOD_IBB)
+ xmlnode_insert_data(value, "http://jabber.org/protocol/ibb", -1);
+ */
+
+ jabber_iq_send(iq);
+ }
+}
+
+GaimXfer *jabber_si_new_xfer(GaimConnection *gc, const char *who)
+{
+ JabberStream *js;
+
+ GaimXfer *xfer;
+ JabberSIXfer *jsx;
+
+ js = gc->proto_data;
+
+ xfer = gaim_xfer_new(gc->account, GAIM_XFER_SEND, who);
+ if (xfer)
+ {
+ xfer->data = jsx = g_new0(JabberSIXfer, 1);
+ jsx->js = js;
+
+ gaim_xfer_set_init_fnc(xfer, jabber_si_xfer_init);
+ gaim_xfer_set_cancel_send_fnc(xfer, jabber_si_xfer_cancel_send);
+ gaim_xfer_set_end_fnc(xfer, jabber_si_xfer_end);
+
+ js->file_transfers = g_list_append(js->file_transfers, xfer);
+ }
+
+ return xfer;
+}
+
+void jabber_si_xfer_send(GaimConnection *gc, const char *who, const char *file)
+{
+ JabberStream *js;
+
+ GaimXfer *xfer;
+
+ js = gc->proto_data;
+
+ if(!gaim_find_buddy(gc->account, who) || !jabber_buddy_find(js, who, FALSE))
+ return;
+
+ xfer = jabber_si_new_xfer(gc, who);
+
+ if (file)
+ gaim_xfer_request_accepted(xfer, file);
+ else
+ gaim_xfer_request(xfer);
+}
+
+void jabber_si_parse(JabberStream *js, xmlnode *packet)
+{
+ JabberSIXfer *jsx;
+ GaimXfer *xfer;
+ xmlnode *si, *file, *feature, *x, *field, *option, *value;
+ const char *stream_id, *filename, *filesize_c, *profile, *from;
+ size_t filesize = 0;
+
+ if(!(si = xmlnode_get_child(packet, "si")))
+ return;
+
+ if(!(profile = xmlnode_get_attrib(si, "profile")) ||
+ strcmp(profile, "http://jabber.org/protocol/si/profile/file-transfer"))
+ return;
+
+ if(!(stream_id = xmlnode_get_attrib(si, "id")))
+ return;
+
+ if(!(file = xmlnode_get_child(si, "file")))
+ return;
+
+ if(!(filename = xmlnode_get_attrib(file, "name")))
+ return;
+
+ if((filesize_c = xmlnode_get_attrib(file, "size")))
+ filesize = atoi(filesize_c);
+
+ if(!(feature = xmlnode_get_child(si, "feature")))
+ return;
+
+ if(!(x = xmlnode_get_child_with_namespace(feature, "x", "jabber:x:data")))
+ return;
+
+ if(!(from = xmlnode_get_attrib(packet, "from")))
+ return;
+
+ /* if they've already sent us this file transfer with the same damn id
+ * then we're gonna ignore it, until I think of something better to do
+ * with it */
+ if((xfer = jabber_si_xfer_find(js, stream_id, from)))
+ return;
+
+ jsx = g_new0(JabberSIXfer, 1);
+
+ for(field = xmlnode_get_child(x, "field"); field; field = xmlnode_get_next_twin(field)) {
+ const char *var = xmlnode_get_attrib(field, "var");
+ if(var && !strcmp(var, "stream-method")) {
+ for(option = xmlnode_get_child(field, "option"); option;
+ option = xmlnode_get_next_twin(option)) {
+ if((value = xmlnode_get_child(option, "value"))) {
+ char *val;
+ if((val = xmlnode_get_data(value))) {
+ if(!strcmp(val, "http://jabber.org/protocol/bytestreams")) {
+ jsx->stream_method |= STREAM_METHOD_BYTESTREAMS;
+ /*
+ } else if(!strcmp(val, "http://jabber.org/protocol/ibb")) {
+ jsx->stream_method |= STREAM_METHOD_IBB;
+ */
+ }
+ g_free(val);
+ }
+ }
+ }
+ }
+ }
+
+ if(jsx->stream_method == STREAM_METHOD_UNKNOWN) {
+ g_free(jsx);
+ return;
+ }
+
+ jsx->js = js;
+ jsx->stream_id = g_strdup(stream_id);
+ jsx->iq_id = g_strdup(xmlnode_get_attrib(packet, "id"));
+
+ xfer = gaim_xfer_new(js->gc->account, GAIM_XFER_RECEIVE, from);
+ if (xfer)
+ {
+ xfer->data = jsx;
+
+ gaim_xfer_set_filename(xfer, filename);
+ if(filesize > 0)
+ gaim_xfer_set_size(xfer, filesize);
+
+ gaim_xfer_set_init_fnc(xfer, jabber_si_xfer_init);
+ gaim_xfer_set_request_denied_fnc(xfer, jabber_si_xfer_request_denied);
+ gaim_xfer_set_cancel_recv_fnc(xfer, jabber_si_xfer_cancel_recv);
+ gaim_xfer_set_end_fnc(xfer, jabber_si_xfer_end);
+
+ js->file_transfers = g_list_append(js->file_transfers, xfer);
+
+ gaim_xfer_request(xfer);
+ }
+}
+
+
diff --git a/libpurple/protocols/jabber/si.h b/libpurple/protocols/jabber/si.h
new file mode 100644
index 0000000000..724a4dfe84
--- /dev/null
+++ b/libpurple/protocols/jabber/si.h
@@ -0,0 +1,34 @@
+/**
+ * @file jutil.h utility functions
+ *
+ * gaim
+ *
+ * Copyright (C) 2003 Nathan Walp <faceprint@faceprint.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+#ifndef _GAIM_JABBER_SI_H_
+#define _GAIM_JABBER_SI_H_
+
+#include "ft.h"
+
+#include "jabber.h"
+
+void jabber_bytestreams_parse(JabberStream *js, xmlnode *packet);
+void jabber_si_parse(JabberStream *js, xmlnode *packet);
+GaimXfer *jabber_si_new_xfer(GaimConnection *gc, const char *who);
+void jabber_si_xfer_send(GaimConnection *gc, const char *who, const char *file);
+
+#endif /* _GAIM_JABBER_SI_H_ */
diff --git a/libpurple/protocols/jabber/win32/posix.uname.c b/libpurple/protocols/jabber/win32/posix.uname.c
new file mode 100644
index 0000000000..a5ecb128ed
--- /dev/null
+++ b/libpurple/protocols/jabber/win32/posix.uname.c
@@ -0,0 +1,135 @@
+/*
+ posix.uname.c - version 1.1
+ Copyright (C) 1999, 2000
+ Earnie Boyd and assigns
+
+ Fills the utsname structure with the appropriate values.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published
+ by the Free Software Foundation; either version 2.1, 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 PARTICUALR PURPOSE. See the
+ GNU Lesser General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software Foundation,
+ Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+/*
+ Send bug reports to Earnie Boyd <earnie_boyd@yahoo.com>
+ */
+
+#include "utsname.h"
+#include <string.h>
+#include <stdio.h>
+
+/* ANONYMOUS unions and structs are used from the windows header definitions.
+ These need to be defined for them to work correctly with gcc2.95.2-mingw. */
+/*#define _ANONYMOUS_STRUCT*/
+/*#define _ANONYMOUS_UNION*/
+#include <windows.h>
+#include <_mingw.h>
+
+int
+uname( struct utsname *uts )
+{
+ DWORD sLength;
+ OSVERSIONINFO OS_version;
+ SYSTEM_INFO System_Info;
+
+/* XXX Should these be in the global runtime */
+ enum WinOS {Win95, Win98, WinNT, unknown};
+ int MingwOS;
+
+ memset( uts, 0, sizeof ( *uts ) );
+ OS_version.dwOSVersionInfoSize = sizeof( OSVERSIONINFO );
+
+ GetVersionEx ( &OS_version );
+ GetSystemInfo ( &System_Info );
+
+ strcpy( uts->sysname, "MINGW_" );
+ switch( OS_version.dwPlatformId )
+ {
+ case VER_PLATFORM_WIN32_NT:
+ strcat( uts->sysname, "WinNT" );
+ MingwOS = WinNT;
+ break;
+ case VER_PLATFORM_WIN32_WINDOWS:
+ switch ( OS_version.dwMinorVersion )
+ {
+ case 0:
+ strcat( uts->sysname, "Win95" );
+ MingwOS = Win95;
+ break;
+ case 10:
+ strcat( uts->sysname, "Win98" );
+ MingwOS = Win98;
+ break;
+ default:
+ strcat( uts->sysname, "Win??" );
+ MingwOS = unknown;
+ break;
+ }
+ break;
+ default:
+ strcat( uts->sysname, "Win??" );
+ MingwOS = unknown;
+ break;
+ }
+
+ sprintf( uts->version, "%i", __MINGW32_MAJOR_VERSION );
+ sprintf( uts->release, "%i", __MINGW32_MINOR_VERSION );
+
+ switch( System_Info.wProcessorArchitecture )
+ {
+ case PROCESSOR_ARCHITECTURE_PPC:
+ strcpy( uts->machine, "ppc" );
+ break;
+ case PROCESSOR_ARCHITECTURE_ALPHA:
+ strcpy( uts->machine, "alpha" );
+ break;
+ case PROCESSOR_ARCHITECTURE_MIPS:
+ strcpy( uts->machine, "mips" );
+ break;
+ case PROCESSOR_ARCHITECTURE_INTEL:
+ /* dwProcessorType is only valid in Win95 and Win98
+ wProcessorLevel is only valid in WinNT */
+ switch( MingwOS )
+ {
+ case Win95:
+ case Win98:
+ switch( System_Info.dwProcessorType )
+ {
+ case PROCESSOR_INTEL_386:
+ case PROCESSOR_INTEL_486:
+ case PROCESSOR_INTEL_PENTIUM:
+ sprintf( uts->machine, "i%ld", System_Info.dwProcessorType );
+ break;
+ default:
+ strcpy( uts->machine, "i386" );
+ break;
+ }
+ break;
+ case WinNT:
+ sprintf( uts->machine, "i%d86", System_Info.wProcessorLevel );
+ break;
+ default:
+ strcpy( uts->machine, "unknown" );
+ break;
+ }
+ break;
+ default:
+ strcpy( uts->machine, "unknown" );
+ break;
+ }
+
+ sLength = sizeof ( uts->nodename ) - 1;
+ GetComputerNameA( uts->nodename, &sLength );
+ return 1;
+}
+
diff --git a/libpurple/protocols/jabber/win32/utsname.h b/libpurple/protocols/jabber/win32/utsname.h
new file mode 100644
index 0000000000..bbfa9a6d0c
--- /dev/null
+++ b/libpurple/protocols/jabber/win32/utsname.h
@@ -0,0 +1,23 @@
+#ifndef _SYS_UTSNAME_H
+#define _SYS_UTSNAME_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+struct utsname
+{
+ char sysname[20];
+ char nodename[20];
+ char release[20];
+ char version[20];
+ char machine[20];
+};
+
+int uname (struct utsname *);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/libpurple/protocols/jabber/xdata.c b/libpurple/protocols/jabber/xdata.c
new file mode 100644
index 0000000000..0a8ff52252
--- /dev/null
+++ b/libpurple/protocols/jabber/xdata.c
@@ -0,0 +1,348 @@
+/*
+ * gaim - Jabber Protocol Plugin
+ *
+ * Copyright (C) 2003, Nathan Walp <faceprint@faceprint.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ */
+#include "internal.h"
+#include "request.h"
+#include "server.h"
+
+#include "xdata.h"
+
+typedef enum {
+ JABBER_X_DATA_IGNORE = 0,
+ JABBER_X_DATA_TEXT_SINGLE,
+ JABBER_X_DATA_TEXT_MULTI,
+ JABBER_X_DATA_LIST_SINGLE,
+ JABBER_X_DATA_LIST_MULTI,
+ JABBER_X_DATA_BOOLEAN,
+ JABBER_X_DATA_JID_SINGLE
+} jabber_x_data_field_type;
+
+struct jabber_x_data_data {
+ GHashTable *fields;
+ GSList *values;
+ jabber_x_data_cb cb;
+ gpointer user_data;
+ JabberStream *js;
+};
+
+static void jabber_x_data_ok_cb(struct jabber_x_data_data *data, GaimRequestFields *fields) {
+ xmlnode *result = xmlnode_new("x");
+ jabber_x_data_cb cb = data->cb;
+ gpointer user_data = data->user_data;
+ JabberStream *js = data->js;
+ GList *groups, *flds;
+
+ xmlnode_set_namespace(result, "jabber:x:data");
+ xmlnode_set_attrib(result, "type", "submit");
+
+ for(groups = gaim_request_fields_get_groups(fields); groups; groups = groups->next) {
+ for(flds = gaim_request_field_group_get_fields(groups->data); flds; flds = flds->next) {
+ xmlnode *fieldnode, *valuenode;
+ GaimRequestField *field = flds->data;
+ const char *id = gaim_request_field_get_id(field);
+ jabber_x_data_field_type type = GPOINTER_TO_INT(g_hash_table_lookup(data->fields, id));
+
+ switch(type) {
+ case JABBER_X_DATA_TEXT_SINGLE:
+ case JABBER_X_DATA_JID_SINGLE:
+ {
+ const char *value = gaim_request_field_string_get_value(field);
+ fieldnode = xmlnode_new_child(result, "field");
+ xmlnode_set_attrib(fieldnode, "var", id);
+ valuenode = xmlnode_new_child(fieldnode, "value");
+ if(value)
+ xmlnode_insert_data(valuenode, value, -1);
+ break;
+ }
+ case JABBER_X_DATA_TEXT_MULTI:
+ {
+ char **pieces, **p;
+ const char *value = gaim_request_field_string_get_value(field);
+ fieldnode = xmlnode_new_child(result, "field");
+ xmlnode_set_attrib(fieldnode, "var", id);
+
+ pieces = g_strsplit(value, "\n", -1);
+ for(p = pieces; *p != NULL; p++) {
+ valuenode = xmlnode_new_child(fieldnode, "value");
+ xmlnode_insert_data(valuenode, *p, -1);
+ }
+ g_strfreev(pieces);
+ }
+ break;
+ case JABBER_X_DATA_LIST_SINGLE:
+ case JABBER_X_DATA_LIST_MULTI:
+ {
+ const GList *selected = gaim_request_field_list_get_selected(field);
+ char *value;
+ fieldnode = xmlnode_new_child(result, "field");
+ xmlnode_set_attrib(fieldnode, "var", id);
+
+ while(selected) {
+ value = gaim_request_field_list_get_data(field, selected->data);
+ valuenode = xmlnode_new_child(fieldnode, "value");
+ if(value)
+ xmlnode_insert_data(valuenode, value, -1);
+ selected = selected->next;
+ }
+ }
+ break;
+ case JABBER_X_DATA_BOOLEAN:
+ fieldnode = xmlnode_new_child(result, "field");
+ xmlnode_set_attrib(fieldnode, "var", id);
+ valuenode = xmlnode_new_child(fieldnode, "value");
+ if(gaim_request_field_bool_get_value(field))
+ xmlnode_insert_data(valuenode, "1", -1);
+ else
+ xmlnode_insert_data(valuenode, "0", -1);
+ break;
+ case JABBER_X_DATA_IGNORE:
+ break;
+ }
+ }
+ }
+
+ g_hash_table_destroy(data->fields);
+ while(data->values) {
+ g_free(data->values->data);
+ data->values = g_slist_delete_link(data->values, data->values);
+ }
+ g_free(data);
+
+ cb(js, result, user_data);
+}
+
+static void jabber_x_data_cancel_cb(struct jabber_x_data_data *data, GaimRequestFields *fields) {
+ xmlnode *result = xmlnode_new("x");
+ jabber_x_data_cb cb = data->cb;
+ gpointer user_data = data->user_data;
+ JabberStream *js = data->js;
+ g_hash_table_destroy(data->fields);
+ while(data->values) {
+ g_free(data->values->data);
+ data->values = g_slist_delete_link(data->values, data->values);
+ }
+ g_free(data);
+
+ xmlnode_set_namespace(result, "jabber:x:data");
+ xmlnode_set_attrib(result, "type", "cancel");
+
+ cb(js, result, user_data);
+}
+
+void *jabber_x_data_request(JabberStream *js, xmlnode *packet, jabber_x_data_cb cb, gpointer user_data)
+{
+ void *handle;
+ xmlnode *fn, *x;
+ GaimRequestFields *fields;
+ GaimRequestFieldGroup *group;
+ GaimRequestField *field;
+
+ char *title = NULL;
+ char *instructions = NULL;
+
+ struct jabber_x_data_data *data = g_new0(struct jabber_x_data_data, 1);
+
+ data->fields = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL);
+ data->user_data = user_data;
+ data->cb = cb;
+ data->js = js;
+
+ fields = gaim_request_fields_new();
+ group = gaim_request_field_group_new(NULL);
+ gaim_request_fields_add_group(fields, group);
+
+ for(fn = xmlnode_get_child(packet, "field"); fn; fn = xmlnode_get_next_twin(fn)) {
+ xmlnode *valuenode;
+ const char *type = xmlnode_get_attrib(fn, "type");
+ const char *label = xmlnode_get_attrib(fn, "label");
+ const char *var = xmlnode_get_attrib(fn, "var");
+ char *value = NULL;
+
+ if(!type)
+ continue;
+
+ if(!var && strcmp(type, "fixed"))
+ continue;
+ if(!label)
+ label = var;
+
+ if((valuenode = xmlnode_get_child(fn, "value")))
+ value = xmlnode_get_data(valuenode);
+
+
+ /* XXX: handle <required/> */
+
+ if(!strcmp(type, "text-private")) {
+ if((valuenode = xmlnode_get_child(fn, "value")))
+ value = xmlnode_get_data(valuenode);
+
+ field = gaim_request_field_string_new(var, label,
+ value ? value : "", FALSE);
+ gaim_request_field_string_set_masked(field, TRUE);
+ gaim_request_field_group_add_field(group, field);
+
+ g_hash_table_replace(data->fields, g_strdup(var), GINT_TO_POINTER(JABBER_X_DATA_TEXT_SINGLE));
+
+ if(value)
+ g_free(value);
+ } else if(!strcmp(type, "text-multi") || !strcmp(type, "jid-multi")) {
+ GString *str = g_string_new("");
+
+ for(valuenode = xmlnode_get_child(fn, "value"); valuenode;
+ valuenode = xmlnode_get_next_twin(valuenode)) {
+
+ if(!(value = xmlnode_get_data(valuenode)))
+ continue;
+
+ g_string_append_printf(str, "%s\n", value);
+ g_free(value);
+ }
+
+ field = gaim_request_field_string_new(var, label,
+ str->str, TRUE);
+ gaim_request_field_group_add_field(group, field);
+
+ g_hash_table_replace(data->fields, g_strdup(var), GINT_TO_POINTER(JABBER_X_DATA_TEXT_MULTI));
+
+ g_string_free(str, TRUE);
+ } else if(!strcmp(type, "list-single") || !strcmp(type, "list-multi")) {
+ xmlnode *optnode;
+ GList *selected = NULL;
+
+ field = gaim_request_field_list_new(var, label);
+
+ if(!strcmp(type, "list-multi")) {
+ gaim_request_field_list_set_multi_select(field, TRUE);
+ g_hash_table_replace(data->fields, g_strdup(var),
+ GINT_TO_POINTER(JABBER_X_DATA_LIST_MULTI));
+ } else {
+ g_hash_table_replace(data->fields, g_strdup(var),
+ GINT_TO_POINTER(JABBER_X_DATA_LIST_SINGLE));
+ }
+
+ for(valuenode = xmlnode_get_child(fn, "value"); valuenode;
+ valuenode = xmlnode_get_next_twin(valuenode)) {
+ selected = g_list_prepend(selected, xmlnode_get_data(valuenode));
+ }
+
+ for(optnode = xmlnode_get_child(fn, "option"); optnode;
+ optnode = xmlnode_get_next_twin(optnode)) {
+ const char *lbl;
+
+ if(!(valuenode = xmlnode_get_child(optnode, "value")))
+ continue;
+
+ if(!(value = xmlnode_get_data(valuenode)))
+ continue;
+
+ if(!(lbl = xmlnode_get_attrib(optnode, "label")))
+ label = value;
+
+ data->values = g_slist_prepend(data->values, value);
+
+ gaim_request_field_list_add(field, lbl, value);
+ if(g_list_find_custom(selected, value, (GCompareFunc)strcmp))
+ gaim_request_field_list_add_selected(field, lbl);
+ }
+ gaim_request_field_group_add_field(group, field);
+
+ while(selected) {
+ g_free(selected->data);
+ selected = g_list_delete_link(selected, selected);
+ }
+
+ } else if(!strcmp(type, "boolean")) {
+ gboolean def = FALSE;
+
+ if((valuenode = xmlnode_get_child(fn, "value")))
+ value = xmlnode_get_data(valuenode);
+
+ if(value && (!g_ascii_strcasecmp(value, "yes") ||
+ !g_ascii_strcasecmp(value, "true") || !g_ascii_strcasecmp(value, "1")))
+ def = TRUE;
+
+ field = gaim_request_field_bool_new(var, label, def);
+ gaim_request_field_group_add_field(group, field);
+
+ g_hash_table_replace(data->fields, g_strdup(var), GINT_TO_POINTER(JABBER_X_DATA_BOOLEAN));
+
+ if(value)
+ g_free(value);
+ } else if(!strcmp(type, "fixed") && value) {
+ if((valuenode = xmlnode_get_child(fn, "value")))
+ value = xmlnode_get_data(valuenode);
+
+ field = gaim_request_field_label_new("", value);
+ gaim_request_field_group_add_field(group, field);
+
+ if(value)
+ g_free(value);
+ } else if(!strcmp(type, "hidden")) {
+ if((valuenode = xmlnode_get_child(fn, "value")))
+ value = xmlnode_get_data(valuenode);
+
+ field = gaim_request_field_string_new(var, "", value ? value : "",
+ FALSE);
+ gaim_request_field_set_visible(field, FALSE);
+ gaim_request_field_group_add_field(group, field);
+
+ g_hash_table_replace(data->fields, g_strdup(var), GINT_TO_POINTER(JABBER_X_DATA_TEXT_SINGLE));
+
+ if(value)
+ g_free(value);
+ } else { /* text-single, jid-single, and the default */
+ if((valuenode = xmlnode_get_child(fn, "value")))
+ value = xmlnode_get_data(valuenode);
+
+ field = gaim_request_field_string_new(var, label,
+ value ? value : "", FALSE);
+ gaim_request_field_group_add_field(group, field);
+
+ if(!strcmp(type, "jid-single")) {
+ gaim_request_field_set_type_hint(field, "screenname");
+ g_hash_table_replace(data->fields, g_strdup(var), GINT_TO_POINTER(JABBER_X_DATA_JID_SINGLE));
+ } else {
+ g_hash_table_replace(data->fields, g_strdup(var), GINT_TO_POINTER(JABBER_X_DATA_TEXT_SINGLE));
+ }
+
+ if(value)
+ g_free(value);
+ }
+ }
+
+ if((x = xmlnode_get_child(packet, "title")))
+ title = xmlnode_get_data(x);
+
+ if((x = xmlnode_get_child(packet, "instructions")))
+ instructions = xmlnode_get_data(x);
+
+ handle = gaim_request_fields(js->gc, title, title, instructions, fields,
+ _("OK"), G_CALLBACK(jabber_x_data_ok_cb),
+ _("Cancel"), G_CALLBACK(jabber_x_data_cancel_cb), data);
+
+ if(title)
+ g_free(title);
+ if(instructions)
+ g_free(instructions);
+
+ return handle;
+}
+
+
diff --git a/libpurple/protocols/jabber/xdata.h b/libpurple/protocols/jabber/xdata.h
new file mode 100644
index 0000000000..09a1fdbae3
--- /dev/null
+++ b/libpurple/protocols/jabber/xdata.h
@@ -0,0 +1,31 @@
+/**
+ * @file xdata.h utility functions
+ *
+ * gaim
+ *
+ * Copyright (C) 2003 Nathan Walp <faceprint@faceprint.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+#ifndef _GAIM_JABBER_XDATA_H_
+#define _GAIM_JABBER_XDATA_H_
+
+#include "jabber.h"
+#include "xmlnode.h"
+
+typedef void (*jabber_x_data_cb)(JabberStream *js, xmlnode *result, gpointer user_data);
+void *jabber_x_data_request(JabberStream *js, xmlnode *packet, jabber_x_data_cb cb, gpointer user_data);
+
+#endif /* _GAIM_JABBER_XDATA_H_ */