changeset 1466:1a24237f5865

[gaim-migrate @ 1476] jabber got chat and handles "not acceptable" JIDs better (e.g. you can sign on as "warmenhoven" or "warmenhoven@jabber.com" or "warmenhoven@jabber.com/GAIM", and all three act the exact same). also started to rewrite get file for TOC, but only got far enough that AIM aborts the transfer and gaim handles the stopped transfer gracefully (AIM doesn't segfault; it just doesn't do what I expect it to). other doc updates. committer: Tailor Script <tailor@pidgin.im>
author Eric Warmenhoven <eric@warmenhoven.org>
date Sat, 03 Feb 2001 13:30:57 +0000
parents 163b9ee8d789
children 7f7857c5036e
files ChangeLog TODO plugins/jabber/jabber.c src/toc.c
diffstat 4 files changed, 501 insertions(+), 54 deletions(-) [+]
line wrap: on
line diff
--- a/ChangeLog	Sat Feb 03 13:16:15 2001 +0000
+++ b/ChangeLog	Sat Feb 03 13:30:57 2001 +0000
@@ -11,6 +11,9 @@
 	* Fixed hanging trees
 	* Can close windows by hitting 'Esc' (optional)
 	* Better HTML Widget
+	* Can toggle timestamps by hitting F2 (optional)
+	* Rewritten file transfer for TOC
+	* Jabber got chat
 
 version 0.11.0-pre4:
 	* ICQ upgraded to use icqlib 1.1.0
--- a/TODO	Sat Feb 03 13:16:15 2001 +0000
+++ b/TODO	Sat Feb 03 13:30:57 2001 +0000
@@ -4,7 +4,7 @@
 		(different from multiple connections)
 	Figure out some way of not displaying a million new mail messages
 	File transfer for IRC?  Maybe later?
-	Chat for Yahoo/ICQ/Jabber
+	Chat for Yahoo/ICQ
 	File transfer for Yahoo/ICQ/Oscar/Napster
 	Working file transfer for TOC
 	Other RVOUS actions for TOC/Oscar
--- a/plugins/jabber/jabber.c	Sat Feb 03 13:16:15 2001 +0000
+++ b/plugins/jabber/jabber.c	Sat Feb 03 13:30:57 2001 +0000
@@ -67,6 +67,9 @@
 #define UC_XA   0x98
 #define UC_DND  0x118
 
+#define DEFAULT_SERVER "jabber.com"
+#define DEFAULT_GROUPCHAT "conference.jabber.org"
+
 typedef struct gjconn_struct {
 	/* Core structure */
 	pool p;			/* Memory allocation pool */
@@ -111,6 +114,8 @@
 struct jabber_data {
 	gjconn jc;
 	gboolean did_import;
+	GSList *pending_chats;
+	GSList *existing_chats;
 };
 
 static char *jabber_name()
@@ -130,6 +135,20 @@
 
 #define STATE_EVT(arg) if(j->on_state) { (j->on_state)(j, (arg) ); }
 
+static char *create_valid_jid(const char *given, char *server, char *resource)
+{
+	char *valid;
+
+	if (!strchr(given, '@'))
+		valid = g_strdup_printf("%s@%s/%s", given, server, resource);
+	else if (!strchr(strchr(given, '@'), '/'))
+		valid = g_strdup_printf("%s/%s", given, resource);
+	else
+		valid = g_strdup(given);
+
+	return valid;
+}
+
 static gjconn gjab_new(char *user, char *pass, void *priv)
 {
 	pool p;
@@ -420,33 +439,103 @@
 	gjab_recv(jd->jc);
 }
 
+static struct conversation *find_chat(struct gaim_connection *gc, char *name)
+{
+	GSList *bcs = gc->buddy_chats;
+	struct conversation *b = NULL;
+	char *chat = g_strdup(normalize(name));
+
+	while (bcs) {
+		b = bcs->data;
+		if (!strcasecmp(normalize(b->name), chat))
+			break;
+		b = NULL;
+		bcs = bcs->next;
+	}
+
+	g_free(chat);
+	return b;
+}
+
 static void jabber_handlemessage(gjconn j, jpacket p)
 {
 	xmlnode y;
 	gboolean same = TRUE;
 
-	char *from = NULL, *msg = NULL;
+	char *from = NULL, *msg = NULL, *type = NULL;
+
+	type = xmlnode_get_attrib(p->x, "type");
+
+	if (!type || !strcmp(type, "normal")) {
+		from = jid_full(p->from);
+		if ((y = xmlnode_get_tag(p->x, "body"))) {
+			msg = xmlnode_get_data(y);
+		}
+
+		if (!from || !msg) {
+			return;
+		}
 
-	from = jid_full(p->from);
-	if ((y = xmlnode_get_tag(p->x, "body"))) {
-		msg = xmlnode_get_data(y);
-	}
+		if (!find_conversation(from) && jid_cmp(p->from, jid_new(j->p, GJ_GC(j)->username))) {
+			from = g_strdup_printf("%s@%s", p->from->user, p->from->server);
+			same = FALSE;
+		}
+
+		serv_got_im(GJ_GC(j), from, msg, 0);
 
-	if (!from || !msg) {
-		return;
-	}
+		if (!same)
+			g_free(from);
+	} else if (!strcmp(type, "error")) {
+		if ((y = xmlnode_get_tag(p->x, "error"))) {
+			type = xmlnode_get_attrib(y, "code");
+			msg = xmlnode_get_data(y);
+		}
 
-	if (jid_cmp(p->from, jid_new(j->p, GJ_GC(j)->username))) {
-		from = g_strdup_printf("%s@%s", p->from->user, p->from->server);
-		same = FALSE;
+		if (msg) {
+			from = g_strdup_printf("Error %s", type ? type : "");
+			do_error_dialog(msg, from);
+			g_free(from);
+		}
+	} else if (!strcmp(type, "groupchat")) {
+		struct conversation *b;
+		static int i = 0;
+		from = jid_full(p->from);
+		if ((y = xmlnode_get_tag(p->x, "body"))) {
+			msg = xmlnode_get_data(y);
+		}
+		b = find_chat(GJ_GC(j), p->from->user);
+		if (!b) {
+			jid chat = NULL;
+			struct jabber_data *jd = GJ_GC(j)->proto_data;
+			GSList *pc = jd->pending_chats;
+			while (pc) {
+				chat = jid_new(j->p, pc->data); /* whoa */
+				if (!strcasecmp(p->from->user, chat->user))
+					break;
+				pc = pc->next;
+			}
+			if (pc) {
+				serv_got_joined_chat(GJ_GC(j), i++, p->from->user);
+				b = find_chat(GJ_GC(j), p->from->user);
+				jd->existing_chats = g_slist_append(jd->existing_chats, pc->data);
+				jd->pending_chats = g_slist_remove(jd->pending_chats, pc->data);
+			} else {
+				return;
+			}
+		}
+		if (p->from->resource) {
+			if (!y)
+				add_chat_buddy(b, p->from->resource);
+			else if (msg)
+				serv_got_chat_in(GJ_GC(j), b->id, p->from->resource, 0, msg);
+		/*
+		} else if (msg) {
+			write_to_conv(b, msg, WFLAG_SYSTEM, NULL);
+		*/
+		}
+	} else {
+		debug_printf("unhandled message %s\n", type);
 	}
-
-	serv_got_im(GJ_GC(j), from, msg, 0);
-
-	if (!same)
-		g_free(from);
-
-	return;
 }
 
 static void jabber_handlepresence(gjconn j, jpacket p)
@@ -460,6 +549,7 @@
 	int state;
 	GSList *resources;
 	char *res;
+	struct conversation *cnv = NULL;
 
 	to = xmlnode_get_attrib(p->x, "to");
 	from = xmlnode_get_attrib(p->x, "from");
@@ -488,30 +578,81 @@
 
 	buddy = g_strdup_printf("%s@%s", who->user, who->server);
 
-	if (!(b = find_buddy(GJ_GC(j), buddy))) {
-		b = add_buddy(GJ_GC(j), "Buddies", buddy, buddy);
-		build_edit_tree();
-		do_export(NULL, NULL);
-	}
-	resources = b->proto_data;
-	res = who->resource;
-	while (resources) {
-		if (!strcmp(res, resources->data))
-			break;
-		resources = resources->next;
+	/* um. we're going to check if it's a chat. if it isn't, and there are pending
+	 * chats, create the chat. if there aren't pending chats, add the buddy. */
+	if ((cnv = find_chat(GJ_GC(j), who->user)) == NULL) {
+		static int i = 0x70;
+		jid chat = NULL;
+		struct jabber_data *jd = GJ_GC(j)->proto_data;
+		GSList *pc = jd->pending_chats;
+
+		while (pc) {
+			chat = jid_new(j->p, pc->data);
+			if (!jid_cmpx(who, chat, JID_USER | JID_SERVER))
+				break;
+			pc = pc->next;
+		}
+		if (pc) {
+			serv_got_joined_chat(GJ_GC(j), i++, who->user);
+			cnv = find_chat(GJ_GC(j), who->user);
+			jd->existing_chats = g_slist_append(jd->existing_chats, pc->data);
+			jd->pending_chats = g_slist_remove(jd->pending_chats, pc->data);
+		} else if (!(b = find_buddy(GJ_GC(j), buddy))) {
+			b = add_buddy(GJ_GC(j), "Buddies", buddy, buddy);
+			build_edit_tree();
+			do_export(NULL, NULL);
+		}
 	}
 
-	if (type && (strcasecmp(type, "unavailable") == 0)) {
-		g_free(resources->data);
-		b->proto_data = g_slist_remove(b->proto_data, resources->data);
-		if (!b->proto_data) {
-			serv_got_update(GJ_GC(j), buddy, 0, 0, 0, 0, 0, 0);
+	if (!cnv) {
+		resources = b->proto_data;
+		res = who->resource;
+		while (resources) {
+			if (!strcmp(res, resources->data))
+				break;
+			resources = resources->next;
+		}
+
+		if (type && (strcasecmp(type, "unavailable") == 0)) {
+			g_free(resources->data);
+			b->proto_data = g_slist_remove(b->proto_data, resources->data);
+			if (!b->proto_data) {
+				serv_got_update(GJ_GC(j), buddy, 0, 0, 0, 0, 0, 0);
+			}
+		} else {
+			if (!resources) {
+				b->proto_data = g_slist_append(b->proto_data, g_strdup(res));
+			}
+			serv_got_update(GJ_GC(j), buddy, 1, 0, 0, 0, state, 0);
 		}
 	} else {
-		if (!resources) {
-			b->proto_data = g_slist_append(b->proto_data, g_strdup(res));
+		if (who->resource) {
+			if (type && !strcmp(type, "unavailable")) {
+				struct jabber_data *jd = GJ_GC(j)->proto_data;
+				GSList *bcs = jd->existing_chats;
+				jid chat;
+				while (bcs) {
+					chat = jid_new(j->p, bcs->data);
+					if (!strcasecmp(cnv->name, chat->user))
+						break;
+					bcs = bcs->next;
+				}
+				if (!bcs) {
+					return;
+				}
+
+				if (strcasecmp(who->resource, chat->resource)) {
+					remove_chat_buddy(cnv, who->resource);
+					return;
+				}
+
+				g_free(bcs->data);
+				jd->existing_chats = g_slist_remove(jd->existing_chats, bcs->data);
+				serv_got_chat_left(GJ_GC(j), cnv->id);
+			} else {
+				add_chat_buddy(cnv, who->resource);
+			}
 		}
-		serv_got_update(GJ_GC(j), buddy, 1, 0, 0, 0, state, 0);
 	}
 
 	g_free(buddy);
@@ -702,21 +843,23 @@
 {
 	struct gaim_connection *gc = new_gaim_conn(user);
 	struct jabber_data *jd = gc->proto_data = g_new0(struct jabber_data, 1);
+	char *loginname = create_valid_jid(user->username, DEFAULT_SERVER, "GAIM");
 
 	set_login_progress(gc, 1, "Connecting");
 
-	if (!(jd->jc = gjab_new(user->username, user->password, gc))) {
+	if (!(jd->jc = gjab_new(loginname, user->password, gc))) {
+		g_free(loginname);
 		debug_printf("jabber: unable to connect (jab_new failed)\n");
 		hide_login_progress(gc, "Unable to connect");
 		signoff(gc);
 		return;
 	}
 
+	g_free(loginname);
 	gjab_state_handler(jd->jc, jabber_handlestate);
 	gjab_packet_handler(jd->jc, jabber_handlepacket);
 	gjab_start(jd->jc);
 
-
 	gc->inpa = gdk_input_add(jd->jc->fd, GDK_INPUT_READ | GDK_INPUT_EXCEPTION, jabber_callback, gc);
 
 	return;
@@ -755,6 +898,7 @@
 	}
 
 	gjab_send(((struct jabber_data *)gc->proto_data)->jc, x);
+	xmlnode_free(x);
 }
 
 static void jabber_add_buddy(struct gaim_connection *gc, char *name)
@@ -837,6 +981,104 @@
 	}
 }
 
+static void jabber_join_chat(struct gaim_connection *gc, int exch, char *name)
+{
+	xmlnode x, y;
+	char *realwho;
+	gjconn j = ((struct jabber_data *)gc->proto_data)->jc;
+	GSList *pc = ((struct jabber_data *)gc->proto_data)->pending_chats;
+
+	if (!name)
+		return;
+
+	realwho = create_valid_jid(name, DEFAULT_GROUPCHAT, j->user->user);
+
+	x = jutil_presnew(0, realwho, NULL);
+	gjab_send(j, x);
+	xmlnode_free(x);
+
+	((struct jabber_data *)gc->proto_data)->pending_chats = g_slist_append(pc, realwho);
+}
+
+static void jabber_chat_leave(struct gaim_connection *gc, int id)
+{
+	GSList *bcs = gc->buddy_chats;
+	struct conversation *b;
+	struct jabber_data *jd = gc->proto_data;
+	gjconn j = jd->jc;
+	jid chat;
+	xmlnode x;
+
+	while (bcs) {
+		b = bcs->data;
+		if (id == b->id)
+			break;
+		bcs = bcs->next;
+	}
+	if (!bcs)
+		return;
+
+	bcs = jd->existing_chats;
+	while (bcs) {
+		chat = jid_new(j->p, bcs->data);
+		if (!strcasecmp(b->name, chat->user))
+			break;
+		bcs = bcs->next;
+	}
+	if (!bcs)
+		return;
+
+	x = jutil_presnew(0, bcs->data, NULL);
+	xmlnode_put_attrib(x, "type", "unavailable");
+	gjab_send(j, x);
+	xmlnode_free(x);
+}
+
+static void jabber_chat_send(struct gaim_connection *gc, int id, char *message)
+{
+	GSList *bcs = gc->buddy_chats;
+	struct conversation *b;
+	struct jabber_data *jd = gc->proto_data;
+	gjconn j = jd->jc;
+	jid chat;
+	xmlnode x, y;
+	char *chatname;
+
+	while (bcs) {
+		b = bcs->data;
+		if (id == b->id)
+			break;
+		bcs = bcs->next;
+	}
+	if (!bcs)
+		return;
+
+	bcs = jd->existing_chats;
+	while (bcs) {
+		chat = jid_new(j->p, bcs->data);
+		if (!strcasecmp(b->name, chat->user))
+			break;
+		bcs = bcs->next;
+	}
+	if (!bcs)
+		return;
+
+	x = xmlnode_new_tag("message");
+	xmlnode_put_attrib(x, "from", bcs->data);
+	chatname = g_strdup_printf("%s@%s", chat->user, chat->server);
+	xmlnode_put_attrib(x, "to", chatname);
+	g_free(chatname);
+	xmlnode_put_attrib(x, "type", "groupchat");
+
+	if (message && strlen(message)) {
+		y = xmlnode_insert_tag(x, "body");
+		xmlnode_insert_cdata(y, message, -1);
+	}
+
+	gjab_send(((struct jabber_data *)gc->proto_data)->jc, x);
+	xmlnode_free(x);
+}
+
 static struct prpl *my_protocol = NULL;
 
 void Jabber_init(struct prpl *ret)
@@ -871,11 +1113,11 @@
 	ret->set_permit_deny = NULL;
 	ret->warn = NULL;
 	ret->accept_chat = NULL;
-	ret->join_chat = NULL;
+	ret->join_chat = jabber_join_chat;
 	ret->chat_invite = NULL;
-	ret->chat_leave = NULL;
+	ret->chat_leave = jabber_chat_leave;
 	ret->chat_whisper = NULL;
-	ret->chat_send = NULL;
+	ret->chat_send = jabber_chat_send;
 	ret->keepalive = NULL;
 
 	my_protocol = ret;
--- a/src/toc.c	Sat Feb 03 13:16:15 2001 +0000
+++ b/src/toc.c	Sat Feb 03 13:30:57 2001 +0000
@@ -35,6 +35,8 @@
 #include <stdio.h>
 #include <time.h>
 #include <sys/socket.h>
+#include <sys/types.h>
+#include <sys/stat.h>
 #include "prpl.h"
 #include "multi.h"
 #include "gaim.h"
@@ -46,7 +48,7 @@
 #include "pixmaps/dt_icon.xpm"
 #include "pixmaps/free_icon.xpm"
 
-#define REVISION "gaim:$Revision: 1469 $"
+#define REVISION "gaim:$Revision: 1476 $"
 
 #define TYPE_SIGNON    1
 #define TYPE_DATA      2
@@ -1302,14 +1304,6 @@
 	gint inpa;
 };
 
-static void toc_get_file(gpointer a, struct file_transfer *ft) {
-	char *dirname = gtk_file_selection_get_filename(GTK_FILE_SELECTION(ft->window));
-
-	if (file_is_dir(dirname, ft->window))
-		return;
-	gtk_widget_destroy(ft->window);
-}
-
 static void debug_header(struct file_transfer *ft) {
 	struct file_header *f = (struct file_header *)ft;
 	debug_printf("TOC FT HEADER:\n"
@@ -1336,7 +1330,8 @@
 			f->name);
 }
 
-static void toc_send_file_callback(gpointer data, gint source, GdkInputCondition cond) {
+static void toc_send_file_callback(gpointer data, gint source, GdkInputCondition cond)
+{
 	char buf[BUF_LONG];
 	int rt, i;
 
@@ -1444,7 +1439,8 @@
 	}
 }
 
-static void toc_send_file(gpointer a, struct file_transfer *old_ft) {
+static void toc_send_file(gpointer a, struct file_transfer *old_ft)
+{
 	struct file_transfer *ft;
 	char *dirname = gtk_file_selection_get_filename(GTK_FILE_SELECTION(old_ft->window));
 	int fd;
@@ -1489,6 +1485,212 @@
 	ft->inpa = gdk_input_add(fd, GDK_INPUT_READ | GDK_INPUT_EXCEPTION, toc_send_file_callback, ft);
 }
 
+static void toc_get_file_callback(gpointer data, gint source, GdkInputCondition cond)
+{
+	char buf[BUF_LONG];
+
+	struct file_transfer *ft = data;
+
+	if (cond & GDK_INPUT_EXCEPTION) {
+		gdk_input_remove(ft->inpa);
+		close(source);
+		g_free(ft->filename);
+		g_free(ft->user);
+		g_free(ft->ip);
+		g_free(ft->cookie);
+		if (ft->file)
+			fclose(ft->file);
+		g_free(ft);
+		return;
+	}
+
+	if (cond & GDK_INPUT_WRITE) {
+		int remain = MIN(ntohl(ft->hdr.totsize) - ft->recvsize, 1024);
+		int i;
+		for (i = 0; i < remain; i++)
+			fscanf(ft->file, "%c", &buf[i]);
+		write(source, buf, remain);
+		ft->recvsize += remain;
+		if (ft->recvsize == ntohl(ft->hdr.totsize)) {
+			gdk_input_remove(ft->inpa);
+			ft->inpa = gdk_input_add(source, GDK_INPUT_READ | GDK_INPUT_EXCEPTION,
+						 toc_get_file_callback, ft);
+		}
+		return;
+	}
+
+	if (ft->hdr.hdrtype == 0x0811) {
+		struct tm *fortime;
+		struct stat st;
+
+		read(source, ft, 8);
+		read(source, &ft->hdr.bcookie, MIN(256 - 8, ntohs(ft->hdr.hdrlen) - 8));
+		debug_header(ft);
+
+		stat(ft->filename, &st);
+		fortime = localtime(&st.st_mtime);
+		g_snprintf(buf, sizeof(buf), "%2d/%2d/%4d %2d:%2d %8ld %s\r\n",
+				fortime->tm_mon + 1, fortime->tm_mday, fortime->tm_year + 1900,
+				fortime->tm_hour + 1, fortime->tm_min + 1, (long)st.st_size,
+				g_basename(ft->filename));
+		write(source, ft, 256);
+		return;
+	}
+
+	if (ft->hdr.hdrtype == 0x0912) {
+		read(source, ft, 8);
+		read(source, &ft->hdr.bcookie, MIN(256 - 8, ntohs(ft->hdr.hdrlen) - 8));
+		debug_header(ft);
+		return;
+	}
+
+	if (ft->hdr.hdrtype == 0x0b12) {
+		read(source, ft, 8);
+		read(source, &ft->hdr.bcookie, MIN(256 - 8, ntohs(ft->hdr.hdrlen) - 8));
+		debug_header(ft);
+
+		if (ft->hdr.hdrtype != 0xc12) {
+			g_snprintf(buf, sizeof(buf), "%s decided to cancel the transfer", ft->user);
+			gdk_input_remove(ft->inpa);
+			close(source);
+			g_free(ft->filename);
+			g_free(ft->user);
+			g_free(ft->ip);
+			g_free(ft->cookie);
+			if (ft->file)
+				fclose(ft->file);
+			g_free(ft);
+			return;
+		}
+
+		ft->hdr.hdrtype = 0x0101;
+		ft->hdr.totfiles = htons(1); ft->hdr.filesleft = htons(1);
+		ft->hdr.flags = 0x20;
+		write(source, ft, 256);
+		return;
+	}
+
+	if (ft->hdr.hdrtype == 0x0101) {
+		read(source, ft, 8);
+		read(source, &ft->hdr.bcookie, MIN(256 - 8, ntohs(ft->hdr.hdrlen) - 8));
+		debug_header(ft);
+
+		gdk_input_remove(ft->inpa);
+		ft->inpa = gdk_input_add(source, GDK_INPUT_WRITE | GDK_INPUT_EXCEPTION,
+					 toc_get_file_callback, ft);
+		return;
+	}
+
+	if (ft->hdr.hdrtype == 0x0202) {
+		read(source, ft, 8);
+		read(source, &ft->hdr.bcookie, MIN(256 - 8, ntohs(ft->hdr.hdrlen) - 8));
+		debug_header(ft);
+
+		gdk_input_remove(ft->inpa);
+		close(source);
+		g_free(ft->filename);
+		g_free(ft->user);
+		g_free(ft->ip);
+		g_free(ft->cookie);
+		if (ft->file)
+			fclose(ft->file);
+		g_free(ft);
+		return;
+	}
+}
+
+static void toc_get_file(gpointer a, struct file_transfer *old_ft)
+{
+	struct file_transfer *ft;
+	struct file_header *hdr;
+	char *dirname = gtk_file_selection_get_filename(GTK_FILE_SELECTION(old_ft->window));
+	int fd;
+	struct aim_user *user;
+	char *buf;
+	struct stat st;
+
+	if (file_is_dir(dirname, old_ft->window))
+		return;
+	ft = g_new0(struct file_transfer, 1);
+	ft->filename = g_strdup(dirname);
+	ft->file = fopen(ft->filename, "r");
+	if (!ft->file) {
+		buf = g_strdup_printf("Unable to open %s for transfer!", ft->filename);
+		do_error_dialog(buf, "Error");
+		g_free(buf);
+		g_free(ft->filename);
+		g_free(ft);
+		return;
+	}
+	if (stat(dirname, &st)) {
+		buf = g_strdup_printf("Unable to examine %s!", dirname);
+		do_error_dialog(buf, "Error");
+		g_free(buf);
+		g_free(ft->filename);
+		g_free(ft);
+		return;
+	}
+	ft->cookie = g_strdup(old_ft->cookie);
+	ft->user = g_strdup(old_ft->user);
+	ft->ip = g_strdup(old_ft->ip);
+	ft->port = old_ft->port;
+	ft->gc = old_ft->gc;
+	user = ft->gc->user;
+	gtk_widget_destroy(old_ft->window);
+
+	buf = g_strdup_printf("toc_rvous_accept %s %s %s", ft->user, ft->cookie, FILE_GET_UID);
+	sflap_send(ft->gc, buf, -1, TYPE_DATA);
+	g_free(buf);
+
+	fd =
+	    proxy_connect(ft->ip, ft->port,
+			  user->proto_opt[USEROPT_SOCKSHOST],
+			  atoi(user->proto_opt[USEROPT_SOCKSPORT]),
+			  atoi(user->proto_opt[USEROPT_PROXYTYPE]));
+	if (fd < 0) {
+		do_error_dialog(_("Could not connect for transfer!"), _("Error"));
+		fclose(ft->file);
+		g_free(ft->filename);
+		g_free(ft->cookie);
+		g_free(ft->user);
+		g_free(ft->ip);
+		g_free(ft);
+		return;
+	}
+
+	hdr = (struct file_header *)ft;
+	hdr->magic[0] = 'O'; hdr->magic[1] = 'F'; hdr->magic[2] = 'T'; hdr->magic[3] = '2';
+	hdr->hdrlen = htons(256);
+	hdr->hdrtype = 0x0811;
+	buf = frombase64(ft->cookie);
+	g_snprintf(hdr->bcookie, 8, "%s", buf);
+	g_free (buf);
+	hdr->totfiles = htons(1); hdr->filesleft = htons(1);
+	hdr->totparts = htons(1); hdr->partsleft = htons(1);
+	hdr->totsize = htonl((long)st.st_size); /* combined size of all files */
+	/* size = strlen("mm/dd/yyyy hh:mm sizesize 'name'\r\n") */
+	hdr->size = htonl(28 + strlen(g_basename(ft->filename))); /* size of listing.txt */
+	hdr->modtime = htonl(st.st_mtime);
+	hdr->checksum = htonl(0x89f70000); /* uh... */
+	g_snprintf(hdr->idstring, 32, "OFT_Windows ICBMFT V1.1 32");
+	hdr->flags = 0x02;
+	hdr->lnameoffset = 0x1A;
+	hdr->lsizeoffset = 0x10;
+	g_snprintf(hdr->name, 64, "listing.txt");
+	if (write(fd, hdr, 256) < 0) {
+		do_error_dialog(_("Could not write file header!"), _("Error"));
+		fclose(ft->file);
+		g_free(ft->filename);
+		g_free(ft->cookie);
+		g_free(ft->user);
+		g_free(ft->ip);
+		g_free(ft);
+		return;
+	}
+
+	ft->inpa = gdk_input_add(fd, GDK_INPUT_READ | GDK_INPUT_EXCEPTION, toc_get_file_callback, ft);
+}
+
 static void cancel_callback(gpointer a, struct file_transfer *ft) {
 	gtk_widget_destroy(ft->window);
 	if (a == ft->window) {