/*****************************************************************************
 * yahoofxfer.c
 *
 * 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.
 *
 * Copyright (C) 2006 Stefan Sikora
 *****************************************************************************/
 
#ifdef HAVE_CONFIG_H
#  include <config.h>
#endif

#include <netdb.h>
#include <arpa/inet.h>
#include <errno.h>
#include <glib-object.h>

#include "gyach.h"
#include "main.h"
#include "yahoochat.h"
#include "util.h"
#include "yahoofxfer.h"
#include "sounds.h"
#include "friends.h"
#include "profname.h"
#include "conference.h"
#include "users.h"
#include "packet_handler.h"
#include "interface.h"
#include "images.h"

struct fxfile {
	char *fullfilename;
	char *filename;
	char *filekey;
	int   file_length;
	int   bytes_read;
	char *server;
	char *who;
	char *token;
	GtkWidget *meter;
	GtkWidget *progress_bar;
  
	int   thread_started;
	int   cancel_transfer;
	int   close_meter_when_done;
};

int fxfr_close_meter_when_done;

GList *send_files = NULL;
GList *receive_files = NULL;


/* not a correct urlencode, just to please yahoo's server */
const char *url_encode(const char *str)
{
	static char bufencode[3072];
	guint i, j = 0;

	g_return_val_if_fail(str != NULL, NULL);

	for (i = 0; i < strlen(str); i++) {
		if (str[i] != 0x02)
			bufencode[j++] = str[i];
		else {
			sprintf(bufencode + j, "%%%02x", (unsigned char)str[i]);
			j += 3;
		}
		if (j>3040) {break;} /* buffer overflow protection */ 
	}

	bufencode[j] = '\0';

	return bufencode;
}

struct fxfile *create_fxfile_info(char *who, char *filename, char *filekey, int file_length)
{
	struct fxfile *file_info;

	file_info=malloc(sizeof(struct fxfile));
	file_info->who=strdup(who);
	file_info->filekey= filekey  ? strdup(filekey)  : NULL;
	file_info->filename=filename ? strdup(filename) : NULL;
	file_info->file_length=file_length;
	file_info->fullfilename=NULL;
	file_info->server=NULL;
	file_info->token=NULL;
	file_info->meter=NULL;
	file_info->progress_bar=NULL;
	file_info->thread_started=0;
	file_info->cancel_transfer=0;
	file_info->close_meter_when_done = fxfr_close_meter_when_done;
	return(file_info);
}

void free_fxfile_info(GList** list, struct fxfile *file_info) {
	if( list) {
		*list = g_list_remove(*list, file_info);
	}

	if (ymsg_sess->debug_packets) {
		fprintf(packetdump_fp, "free_fxfile_info: %s is %d deep\n", list == &receive_files ? "RECEIVE" : "SENT", g_list_length(*list));
	}

	if (file_info->meter) {
		g_object_steal_data(G_OBJECT(file_info->meter), "file_info");
	}

	if (file_info->fullfilename) {free(file_info->fullfilename); file_info->fullfilename=NULL;}
	if (file_info->filename)     {free(file_info->filename);     file_info->filename=NULL;}
	if (file_info->filekey)      {free(file_info->filekey);      file_info->filekey=NULL;}
	if (file_info->server)       {free(file_info->server);       file_info->server=NULL;}
	if (file_info->who)          {free(file_info->who);          file_info->who=NULL;}
	if (file_info->token)        {free(file_info->token);        file_info->token=NULL;}
	free(file_info);
}


struct fxfile *yahoo_find_file_info(GList **file_list, char *who, char *server, char *filename, char *filekey) {
	struct fxfile *file_info;
	GList *l_listp;

	if (ymsg_sess->debug_packets) {
		fprintf(packetdump_fp, "yahoo_find_file_info: %s is %d deep\n", file_list == &receive_files ? "RECEIVE" : "SENT", g_list_length(*file_list));
	}

	file_info = 0;
	for (l_listp=*file_list; l_listp; l_listp=g_list_next(l_listp)) {
		file_info=(struct fxfile *)(l_listp->data);
		if (strcmp(filekey,  file_info->filekey))  continue;
		if (strcmp(who,      file_info->who))      continue;
		return file_info;
	}

	/* hmmm. this should'nt happen.
	 * If here, then we didn't find our file_info!
	 * Well... log it!
	 */

	if (ymsg_sess->debug_packets) {
		fprintf(packetdump_fp, "Could not find file_info for who:'%s', filename: '%s', filekey: '%s'\n", 
			who,
			filename,
			filekey);
	}

	if (capture_fp) {
		fprintf(capture_fp, "Could not find file_info for who:'%s', filename: '%s', filekey: '%s'\n", 
			who,
			filename,
			filekey);
	}

	return 0;
}


/*
 * -----------------------------------------------------------------------------------------
 */

GtkWidget *find_pms_window(char *who) {
	GList *pm_lpackptr;
	GtkWidget *parent = NULL;

	if ((pm_lpackptr = find_pm_session(who)) != NULL) {
		parent=((PM_SESSION *)(pm_lpackptr->data))->pm_notebook->window;
	}
	return parent;
}

/*
 * -----------------------------------------------------------------------------------------
 */

/*
 * progress_meter callback support support
 *
 * [] remember settings clicked
 *
 */
void on_meter_remember_clicked(GtkToggleButton *button, gpointer user_data) {
	GtkWidget *tmp_widget;  

	tmp_widget = g_object_get_data(G_OBJECT(button), "mywindow");
	if (tmp_widget) {
		fxfr_close_meter_when_done = gtk_toggle_button_get_active(button);
		write_config();
	}
}


/*
 * progress_meter callback support support
 *
 * [] close meter when done clicked
 *
 */
void on_meter_close_meter_clicked(GtkToggleButton *button, gpointer user_data) {
	GtkWidget *tmp_widget;  
	struct fxfile *file_info = user_data;

	tmp_widget = g_object_get_data(G_OBJECT(button), "mywindow");
	if (tmp_widget) {
		file_info = g_object_get_data(G_OBJECT(tmp_widget), "file_info");
		if (file_info) {
			file_info->close_meter_when_done = gtk_toggle_button_get_active(button);
		}
	}
}


/*
 * progress_meter callback support support
 *
 * [close] button clicked
 *
 */
void on_meter_close_clicked(GtkWidget *button, gpointer user_data) {
	GtkWidget *tmp_widget;
	struct fxfile *file_info;

	tmp_widget = g_object_get_data(G_OBJECT(button), "mywindow");
	if (tmp_widget) {
		file_info = g_object_get_data(G_OBJECT(tmp_widget), "file_info");
		if (file_info) {
			file_info->progress_bar= NULL;
			file_info->meter=NULL;
		}
		gtk_widget_destroy(tmp_widget);
	}		
}

/*
 * progress_meter callback support support
 *
 * [cancel] button clicked
 *
 */
void on_meter_cancel_clicked(GtkWidget *button, gpointer user_data) {
	GtkWidget *tmp_widget;
	struct fxfile *file_info;

	tmp_widget = g_object_get_data(G_OBJECT(button), "mywindow");
	if (tmp_widget) {
		file_info = g_object_get_data(G_OBJECT(tmp_widget), "file_info");
		if (file_info) {
			file_info->progress_bar= NULL;
			file_info->meter=NULL;
			file_info->cancel_transfer = 1;
			ymsg_fxfer_cancel(ymsg_sess, file_info->who, file_info->filekey);
		}

		gtk_widget_destroy(tmp_widget);
	}		
}

/*
 * progress_meter callback support support
 *
 * call back to destroy window when [x] kill window icon is clicked!
 * Almost the same as [cancel], except that the callback is slightly
 * different, and expects a true/false return.
 * False to propogate the event further.
 */
gboolean on_meter_kill(GtkWidget *widget, GdkEvent *event, gpointer user_data)
{
	struct fxfile *file_info;

	file_info = g_object_get_data(G_OBJECT(widget), "file_info");
	if (file_info) {
		file_info->progress_bar= NULL;
		file_info->meter=NULL;
	}

	gtk_widget_destroy(widget);
	return(TRUE);
}

void create_meter(GtkWidget *parent, struct fxfile *file_info, char *title_pre) {
	GtkWidget *dwindow;
	GtkWidget *dvbox;
	GtkWidget *dlabel;
	GtkWidget *dhbox;
	GtkWidget *close_button;
	GtkWidget *cancel_button;
	GtkWidget *close_meter;
	GtkWidget *remember_settings;
	char       buffer[1024];

	if (capture_fp) {	
		fprintf(capture_fp,"\n[%s] Creating Progress Meter. Filename: '%s'\n", gyach_timestamp(), file_info->filename);
		fflush(capture_fp);
	}

	snprintf(buffer, sizeof(buffer)-1, "%s %s", _utf(title_pre), file_info->filename);

	dwindow = gtk_window_new(GTK_WINDOW_TOPLEVEL);
	gtk_window_set_wmclass(GTK_WINDOW(dwindow), "gyachEDialog", "GYachI"); 
	gtk_window_set_title(GTK_WINDOW(dwindow), buffer);
	gtk_window_set_policy(GTK_WINDOW(dwindow), TRUE, TRUE, FALSE);
	gtk_window_set_position(GTK_WINDOW(dwindow), GTK_WIN_POS_CENTER_ON_PARENT);
	gtk_window_set_transient_for(GTK_WINDOW(dwindow), GTK_WINDOW(parent));
	gtk_window_set_destroy_with_parent(GTK_WINDOW(dwindow), TRUE);
	gtk_window_set_resizable(GTK_WINDOW(dwindow), FALSE);
	file_info->meter=dwindow;

	dvbox = gtk_vbox_new(FALSE, 2);
	gtk_container_add(GTK_CONTAINER(dwindow), dvbox); 
	gtk_container_set_border_width(GTK_CONTAINER(dvbox), 6);  

	dlabel=gtk_label_new("");
	snprintf(buffer, sizeof(buffer)-1, "%s: %s",
		 strcmp(title_pre, "Sending") ? _("Receiving file from") : _("Sending file to"),
		 file_info->who);
	gtk_label_set_text(GTK_LABEL(dlabel), buffer);
	gtk_label_set_line_wrap(GTK_LABEL(dlabel),1);
  	gtk_box_pack_start(GTK_BOX(dvbox), dlabel, TRUE, TRUE, 4);

	dlabel=gtk_label_new("");
	snprintf(buffer, sizeof(buffer)-1, "%s: %s (%d %s)",
		 _("Filename"),
		 file_info->fullfilename,
		 file_info->file_length/(1024*1024) > 0 ? file_info->file_length/(1024*1024) : file_info->file_length/1024,
		 file_info->file_length/(1024*1024) > 0 ? "Mb" : "Kb");
	gtk_label_set_text(GTK_LABEL(dlabel), buffer);
	gtk_label_set_line_wrap(GTK_LABEL(dlabel),1);
  	gtk_box_pack_start(GTK_BOX(dvbox), dlabel, TRUE, TRUE, 4);

	file_info->progress_bar=gtk_progress_bar_new();
  	gtk_box_pack_start(GTK_BOX(dvbox), file_info->progress_bar, TRUE, TRUE, 4);

	close_meter=gtk_check_button_new_with_label(_("Close when done"));
  	gtk_box_pack_start(GTK_BOX(dvbox), close_meter, TRUE, TRUE, 4);
	gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(close_meter), file_info->close_meter_when_done);

	remember_settings=gtk_check_button_new_with_label(_("Remember this setting"));
  	gtk_box_pack_start(GTK_BOX(dvbox), remember_settings, TRUE, TRUE, 4);
	gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(remember_settings), 0);

	dhbox = gtk_hbox_new(FALSE, 2);
	gtk_box_pack_start(GTK_BOX(dvbox), dhbox, FALSE, FALSE, 2);
	gtk_container_set_border_width(GTK_CONTAINER(dhbox), 6);

	close_button=get_pixmapped_button(_("Close"), GTK_STOCK_YES);
	set_tooltip(close_button,_("Close the meter box"));
	gtk_box_pack_start(GTK_BOX(dhbox), close_button, FALSE, FALSE,2);

	gtk_box_pack_start(GTK_BOX(dhbox), gtk_label_new(" "), TRUE, TRUE,2);

	cancel_button=get_pixmapped_button(_("Cancel"), GTK_STOCK_CANCEL);
	set_tooltip(cancel_button,_("Cancel Transfer"));
	gtk_box_pack_start(GTK_BOX(dhbox), cancel_button, FALSE, FALSE,2);

	g_object_set_data(G_OBJECT(dwindow),           "file_info", file_info);
	g_object_set_data(G_OBJECT(close_button)    ,  "mywindow",  dwindow);
	g_object_set_data(G_OBJECT(cancel_button),     "mywindow",  dwindow);
	g_object_set_data(G_OBJECT(close_meter),       "mywindow",  dwindow);
	g_object_set_data(G_OBJECT(remember_settings), "mywindow",  dwindow);

	g_signal_connect(G_OBJECT(close_button), "clicked",
                      G_CALLBACK(on_meter_close_clicked), NULL);

	g_signal_connect(G_OBJECT(cancel_button), "clicked",
                      G_CALLBACK(on_meter_cancel_clicked), NULL);

	g_signal_connect(G_OBJECT(dwindow), "delete_event",
                      G_CALLBACK(on_meter_kill), NULL);

	g_signal_connect(G_OBJECT(close_meter), "toggled",
                      G_CALLBACK(on_meter_close_meter_clicked), NULL);

	g_signal_connect(G_OBJECT(remember_settings), "toggled",
                      G_CALLBACK(on_meter_remember_clicked), close_meter);

	gtk_widget_show_all(dwindow);
	gtk_label_set_selectable(GTK_LABEL(dlabel), 1);
	gtk_widget_grab_focus(remember_settings);
}


void update_meter(struct fxfile *file_info, gdouble percent)
{
	if (file_info->progress_bar) {
		gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(file_info->progress_bar), percent);
	}
}

/*
 * -----------------------------------------------------------------------------------------
 */

/*
 * yahoo_fxfer_offer_msg dialog support
 *
 * [ok] button clicked
 *
 */
void on_fxfer_accept(GtkWidget *button, gpointer user_data) {
	GtkWidget *tmp_widget;
	struct fxfile *file_info;

	/* get embedded data */
	file_info = g_object_get_data(G_OBJECT(button), "file_info");
	if (file_info) {
		if (file_info->cancel_transfer) {
			/* Yes, this can happen.
			 * remote user cancelled before we confirmed!
			 */
			free_fxfile_info(&receive_files, file_info);
		}
		else {
			ymsg_fxfer_accept(ymsg_sess, file_info->who, file_info->filekey);
		}
	}
	tmp_widget = g_object_get_data(G_OBJECT(button), "mywindow");
	if (tmp_widget) gtk_widget_destroy(tmp_widget);
}

/*
 * yahoo_fxfer_offer_msg dialog support
 *
 * [cancel] button clicked
 *
 */
void on_fxfer_reject(GtkWidget *button, gpointer user_data) {
	GtkWidget *tmp_widget;
	struct fxfile *file_info;

	/* get embedded data */
	file_info = g_object_get_data(G_OBJECT(button), "file_info");
	if (file_info) {
		ymsg_fxfer_reject(ymsg_sess, file_info->who, file_info->filekey);
		free_fxfile_info(&receive_files, file_info);
	}
	tmp_widget = g_object_get_data(G_OBJECT(button), "mywindow");
	if (tmp_widget) gtk_widget_destroy(tmp_widget);
}

/*
 * yahoo_fxfer_offer_msg dialog support
 *
 * call back to destroy window when [x] kill window icon is clicked!
 * Almost the same as [cancel], except that the callback is slightly
 * different, and expects a true/false return.
 * False to propogate the event further.
 */
gboolean on_fxfer_destroy(GtkWidget *widget, GdkEvent *event, gpointer user_data)
{
	on_fxfer_reject(user_data, NULL);
	return(FALSE);
}

/* handles incoming file-offers */
void yahoo_fxfer_offer_msg(char *who, char *filename, char *filekey, int filelength)
{
	char buff[512];
	GtkWidget *okbutton=NULL;
	GtkWidget *cbutton=NULL;
	int accept_pm;
	struct fxfile *file_info;
	GtkWidget *parent;
	GtkWidget *window;

	/* get parent window */
	parent = find_pms_window(who);

	accept_pm = get_pm_perms(who);
	if ( ! accept_pm) {					
		send_rejection_message(0);
		return;
	}

	if (allow_no_sent_files) {
		send_rejection_message(0);
		return;
	}

	snprintf(buff, sizeof(buff)-1, _("The Yahoo user <b>%s</b> is sending you the file:\n<tt>%s</tt> (%d %s)\n\nDo you want to accept?"),
		 who,
		 filename,
		 filelength/(1024*1024) > 0 ? filelength/(1024*1024) : filelength/1024,
		 filelength/(1024*1024) > 0 ? "Mb" : "Kb");

	okbutton = show_confirm_dialog_config_p(parent, buff, _("Yes"), _("No"), 0);
	if (!okbutton) {
		ymsg_fxfer_reject(ymsg_sess, who, filekey); 
		return;
	}

	file_info=create_fxfile_info(who, filename, filekey, filelength);
	receive_files = g_list_append(receive_files, file_info);

	g_object_set_data(G_OBJECT(okbutton), "file_info", file_info);
	g_signal_handlers_block_by_func(G_OBJECT(okbutton), on_close_ok_dialog, NULL);
	g_signal_connect(G_OBJECT(okbutton), "clicked", G_CALLBACK(on_fxfer_accept), NULL);

	cbutton = g_object_get_data(G_OBJECT(okbutton), "cancel");
	if (cbutton) {
		g_signal_connect(G_OBJECT(cbutton), "clicked", G_CALLBACK(on_fxfer_reject), NULL);
		g_object_set_data(G_OBJECT(cbutton), "file_info", file_info);
	}

	window = g_object_get_data(G_OBJECT(okbutton), "mywindow");
	g_signal_handlers_block_by_func(G_OBJECT(window), on_close_ok_dialogw, NULL);
	g_signal_connect(G_OBJECT(window), "delete_event", G_CALLBACK(on_fxfer_destroy), okbutton);

	play_sound_event(SOUND_EVENT_OTHER);
}

/*
 * -----------------------------------------------------------------------------------------
 */

/* writes information about filetransfer to chatwindow+pm-window */
void yahoo_fxfer_filemessage(char* who, char* filename, char* url){
	char buf[2048];

	snprintf(buf, 256, "  %s%s** %s: ",YAHOO_STYLE_BOLDON, YAHOO_COLOR_PURPLE, _("User"));
	strcat(buf, "'");
	strncat(buf, who, 80);
	strcat(buf, "' ( ");
	strncat(buf, (char *)get_screenname_alias(who), 80);
	strcat(buf," ) ");
	strncat(buf, _("sent a file that you saved as: "), 90);

	strncat(buf, _utf(filename), 1024); // avoid buffer overflow
	strcat(buf, " **\n");

	strcat(buf, YAHOO_STYLE_BOLDOFF);
	strcat(buf, "\n");
	
	if (enter_leave_timestamp) {append_timestamp(chat_window, NULL);}
	append_char_pixmap_text((const char**)pixmap_pm_file , NULL);	
	append_to_textbox(chat_window, NULL, buf);
	append_to_open_pms(who, buf, 1);
	
	play_sound_event(SOUND_EVENT_OTHER);
} /* yahoo_fxfer_filemessage */


/* for now just notify about p2p-file waiting */
void yahoo_fxfer_getfile_p2p(char *who, char *url, char *filename) {
	yahoo_fxfer_filemessage(who, filename, url);
}

/*
 * -----------------------------------------------------------------------------------------
 */

void *yahoo_fxfer_getfile_thread(void *arg) {
	int sock = -1;
	struct sockaddr_in sa;
	int readct;
	int bytes_read;
	char buffer[513];
	char fetchurl[1024];
	char url[2048];
	FILE *f;
	int err;
	char *ptr;
	int  count;
	int  file_length;
	struct fxfile *file_info = arg;
	GtkWidget *parent; /* either pm of "who", or main chat window */
	float percent;

	file_info->bytes_read=0;
	file_info->thread_started=1;
	/* get parent window */
	gdk_threads_enter();
	// * FIXME * get parent before thread starts.
	// * FIXME * ? maybe put it in the fxfile struct ?
	parent = find_pms_window(file_info->who);
	if (!parent) parent=chat_window;
	gtk_window_present(GTK_WINDOW(parent));
	gdk_flush();
	gdk_threads_leave();

	// ack of token
	ymsg_fxfer_relayaccept(ymsg_sess, file_info->who, file_info->filename, file_info->filekey, file_info->token);
	snprintf(fetchurl, 2046, "/relay?token=%s&sender=%s&recver=%s",
		 url_encode(file_info->token),
		 file_info->who,
		 select_profile_name(YMSG_NOTIFY, 0));

	memset(&sa, 0, sizeof(sa));
	sa.sin_addr.s_addr = inet_addr(file_info->server);
	sa.sin_family = AF_INET;
	sa.sin_port = htons(80);
	
	if ((sock=socket(AF_INET, SOCK_STREAM, 6)) == -1) {
		gdk_threads_enter();
		show_ok_dialog_p(parent, _("Socket error!"));
		free_fxfile_info(&receive_files, file_info);
		gdk_flush();
		gdk_threads_leave();
		return(0);
	}

	if (connect(sock, (struct sockaddr*)&sa, sizeof(sa)) == -1) {
		gdk_threads_enter();
		show_ok_dialog_p(parent, _("Could not connect to relay server"));
		free_fxfile_info(&receive_files, file_info);
		gdk_flush();
		gdk_threads_leave();
		close(sock);
		return(0);
	}

	// send the "HEAD-command"
	snprintf(url, 1022, "HEAD %s HTTP/1.1\r\n"
						"Accept: */*\r\n"
						"Cookie: %s ;B=6n6djhp203aio&b=3&s=np\r\n"
						"User-Agent: Mozilla/4.0 (compatible; MSIE 5.5)\r\n"
						"Host: %s\r\n"
						"Content-Length: 0\r\n"
						"Cache-Control: no-cache\r\n"
						"\r\n", fetchurl, ymsg_sess->cookie, file_info->server);
	write(sock, url, strlen(url));

	// now receive the answer...
	bytes_read = read(sock, (char *)&buffer, (sizeof(buffer)-1));
	close(sock);
	
	if (ymsg_sess->debug_packets) {
		int i;
		fprintf(packetdump_fp, "received from relay server: size: %d\n", bytes_read);
		for (i=0; i<bytes_read; i++) {
			fputc(buffer[i], packetdump_fp);
		}
		fprintf(packetdump_fp, "\n");
	}

	// new socket for the GET	
	memset(&sa, 0, sizeof(sa));
	sa.sin_addr.s_addr = inet_addr(file_info->server);
	sa.sin_family = AF_INET;
	sa.sin_port = htons(80);
	
	if ((sock=socket(AF_INET, SOCK_STREAM, 6)) == -1) {
		gdk_threads_enter();
		show_ok_dialog_p(parent, _("Socket error!"));
		free_fxfile_info(&receive_files, file_info);
		gdk_flush();
		gdk_threads_leave();
		return(0);
	}

	if (connect(sock, (struct sockaddr*)&sa, sizeof(sa)) == -1) {
		gdk_threads_enter();
		show_ok_dialog_p(parent, _("Could not connect to relay server"));
		free_fxfile_info(&receive_files, file_info);
		gdk_flush();
		gdk_threads_leave();
		close(sock);
		return(0);
	}
	memset(&buffer, 0, sizeof(buffer));

	// send the "GET-command"
	snprintf(url, 1022, "GET %s HTTP/1.1\r\n"
						"Cookie: %s ;B=6n6djhp203aio&b=3&s=np\r\n"
						"User-Agent: Mozilla/4.0 (compatible; MSIE 5.5)\r\n"
						"Host: %s\r\n"
						"Connection: Keep-Alive\r\n"
						"\r\n", fetchurl, ymsg_sess->cookie, file_info->server);
	write(sock, url, strlen(url));

	// get the http-header
	/* The header will look like this:
	 *     HTTP/1.0 200\r\n
	 *     Server: YHttpServer\r\n
	 *     Connection: close\r\n
	 *     Content-Type: text/html\r\n
	 *     Content-Length: 33032\r\n
	 *     \r\n
	 *
	 * The actual content MIGHT start on the next byte, *OR* the received packet
	 * might be at the end of the buffer, and then the data begins at the start
	 * of the next packet. I'm guessing that in the earlier days, this packet
	 * would END with the Content-Length: ###\r\n\r\n, and recently, sometime
	 * 2005/2006, yahoo started to just send the packet header right infront of
	 * the file IN THE SAME PACKET... That's mostly what I'm seeing these days...
	 */

	buffer[sizeof(buffer)-1]=0;
	readct = read(sock, (char *)&buffer, sizeof(buffer)-1);
	if (ymsg_sess->debug_packets) {
		int i;
		fprintf(packetdump_fp, "received from relay server: size: %d\n", readct);
		for (i=0; i<readct; i++) {
			fputc(buffer[i], packetdump_fp);
		}
		fprintf(packetdump_fp, "\n");
	}

	// now get the file
	if (!(f = fopen(file_info->fullfilename, "wb"))) {
		snprintf(url, 500, "%s:\n%s", _("File could not be opened for output"), file_info->fullfilename);
		gdk_threads_enter();
		show_ok_dialog_p(parent, url);
		free_fxfile_info(&receive_files, file_info);
		gdk_flush();
		gdk_threads_leave();
		close(sock);
		return(0);
	}

	/* parse the header. Look for
	 * Content-Length: -- if we see, then extract the filesize.
	 * \r\n\r\n -- if we see, then ignore up to the \r\n\r\n, and everything
	 * else in the packet is file content.
	 */
	file_info->bytes_read = 0;
	ptr=buffer;
	while (ptr && strncmp(ptr, "\r\n\r\n", 4)) {
		if (strncmp(ptr, "\r\n", 2) == 0) ptr += 2;
		if (strncasecmp(ptr, "Content-Length: ", 16) == 0) {
			ptr+=16;
			file_length=atoi(ptr);
			if (file_length != file_info->file_length) {
				if (capture_fp) {
					fprintf(capture_fp, "yahoo file_length (%d) does not match http header file_length (%d)\n",
						file_info->file_length, file_length);
					file_info->file_length=file_length;
				}
			}
		}
		ptr=strstr(ptr, "\r\n");
	}

	/* create the progress meter box */
	gdk_threads_enter();
	create_meter(parent, file_info, "Receiving");
	gdk_flush();
	gdk_threads_leave();

	/* at here, ptr is either a null pointer, or is sitting on \r\n\r\n */
	if (ptr) {
		count=ptr-buffer+4;
		if (count < readct) {
			fwrite(ptr+4, 1, readct-count, f);
			file_info->bytes_read = readct-count;
			percent=(float)file_info->bytes_read/(float)file_info->file_length;
			gdk_threads_enter();
			update_meter(file_info, percent);
			gdk_flush();
			gdk_threads_leave();
		}
		if (ymsg_sess->debug_packets) {
			fprintf(packetdump_fp, "part of file is in initial packet: %d bytes\n", file_info->bytes_read);
		}
	}

	while (!file_info->cancel_transfer && file_info->file_length ? file_info->bytes_read < file_info->file_length : 1) {
		readct = read(sock, (char *)&buffer, sizeof(buffer)-1);
		if (readct < 0) {
			if (ymsg_sess->debug_packets) {
				err=errno;
				fprintf(packetdump_fp, "error reading file from server: %d, %s\n", err, strerror(err));
			}
			file_info->cancel_transfer=1;
			break;
		}
		if (readct == 0) {
			if (ymsg_sess->debug_packets) {
				fprintf(packetdump_fp, "read from server, size 0\n");
			}
			break;
		}
		file_info->bytes_read += readct;
		fwrite(buffer, 1, readct, f);
		percent=(float)file_info->bytes_read/(float)file_info->file_length;
		gdk_threads_enter();
		update_meter(file_info, percent);
		gdk_flush();
		gdk_threads_leave();
	}

	fclose(f);
	close(sock);

	gdk_threads_enter();
	if (file_info->close_meter_when_done) {
		if (file_info->meter) {
			gtk_widget_destroy(file_info->meter);
			file_info->meter=NULL;
			file_info->progress_bar=NULL;
		}
	}


	if (file_info->bytes_read == file_info->file_length) {
		snprintf(url, 500, "%s\n\n%s %s", _("File received successfully."), _("File saved as:"), file_info->fullfilename);
		show_ok_dialog_p(parent, url );
	}
	else {
	  snprintf(url, 500, "%s %d, %s %d %s\n%s\n\n%s %s",
		   _("Expected"),
		   file_info->file_length,
		   _("bytes, but recieved"),
		   file_info->bytes_read,
		   _("bytes."),
		   _("The transfer may be incomplete due to a network error, or the sending party may have prematurely cancelled the transfer."),
		   _("File saved as:"),
		   file_info->fullfilename);
		show_ok_dialog_p(parent, url );
	}

	yahoo_fxfer_filemessage(file_info->who, file_info->fullfilename, NULL);
	free_fxfile_info(&receive_files, file_info);
	gdk_flush();
	gdk_threads_leave();
	return(0);
}

/*
 * -----------------------------------------------------------------------------------------
 */

/* accepts a file from relay-server */
void on_start_transfer_clicked(GtkButton *button, gpointer user_data) {
	GtkWidget *filesel = user_data;
	struct fxfile *file_info;

	/* get embedded data */
	file_info = g_object_get_data(G_OBJECT(filesel), "file_info");

	if (file_info->cancel_transfer) {
		/* Yes, this can happen.
		 * remote user cancelled before we selected the filename
		 */

		/* destroy the file selection dialog */
		gtk_widget_destroy(filesel);
		free_fxfile_info(&receive_files, file_info);
		return;
	}

	/* construct result filename */
	if (g_file_test(gtk_file_selection_get_filename(GTK_FILE_SELECTION(filesel)), G_FILE_TEST_IS_DIR)) {
		file_info->fullfilename = malloc(strlen(gtk_file_selection_get_filename(GTK_FILE_SELECTION(filesel))) + strlen(file_info->filename) + 2);
		strcpy(file_info->fullfilename, gtk_file_selection_get_filename(GTK_FILE_SELECTION(filesel)));
		strcat(file_info->fullfilename, "/");
		strcat(file_info->fullfilename, file_info->filename);
	}
	else {
		file_info->fullfilename = strdup(gtk_file_selection_get_filename(GTK_FILE_SELECTION(filesel)));
		free(file_info->filename);
		file_info->filename=g_path_get_basename(file_info->fullfilename);
	}

	/* FIXME * Need to check to see if file exists, and if so, get a confirmation of overwrite */

	if (ymsg_sess->debug_packets) {
		fprintf(packetdump_fp, "Full path of saved file: %s\n", file_info->fullfilename);
	  
	}

	/* destroy the file selection dialog */
	gtk_widget_destroy(filesel);

	/* fire off a thread for this file download.
	 * Each download gets a separate thread
	 */
	g_thread_create(yahoo_fxfer_getfile_thread, file_info, FALSE, NULL);
}

/* call back to destroy window when [cancel] button is called! */
void  on_cancel_transfer_clicked(GtkButton *button, gpointer user_data)
{
	GtkWidget *filesel = user_data;
	struct fxfile *file_info;
	int cancel_transfer;

	/* get embedded data */
	file_info = g_object_get_data(G_OBJECT(filesel), "file_info");

	gtk_widget_destroy(filesel);

	cancel_transfer = file_info->cancel_transfer;
	if (!cancel_transfer) {
		/* send a cancel packet */
		ymsg_fxfer_cancel(ymsg_sess, file_info->who, file_info->filekey);
	}

	free_fxfile_info(&receive_files, file_info);

	if (cancel_transfer) {
		/* Yes, this can happen.
		 * remote user cancelled before we selected the filename
		 */
		return;
	}

	return;
}

/* call back to destroy window when [x] kill window icon is clicked!
 * Almost the same as [cancel], except that the callback is slightly
 * different, and expects a true/false return.
 * False to propogate the event further.
 */
gboolean on_file_selector_window_destroy_event(GtkWidget *widget, GdkEvent *event, gpointer user_data)
{
	on_cancel_transfer_clicked(user_data, widget);
	return FALSE;
}
 
/* Creates file selector for user to enter path for saving the file
 * This is the entry path, from packet_handler 
 */
void yahoo_fxfer_show_file_selection(char *who, char *server, char *filename, char *filekey, char *token) {
	GtkWidget *file_selector;
	GtkWidget *parent; /* either pm of "who", or main chat window */
	struct fxfile *file_info;

	file_info=yahoo_find_file_info(&receive_files, who, server, filename, filekey);
	if (!file_info) {
		/* We never found the file_info in the list.
		 * This should NEVER happen...
		 */
		return;
	}

	parent = find_pms_window(file_info->who);
	if (parent) gtk_window_present(GTK_WINDOW(parent));

	file_info->token=strdup(token);
	file_info->server=strdup(server);

	/* Create the selector */
	file_selector = gtk_file_selection_new("Select directory to save the file");
	GTK_WINDOW(file_selector)->type = GTK_WINDOW_TOPLEVEL;
	if (parent) {
		gtk_window_set_position(GTK_WINDOW(file_selector), GTK_WIN_POS_CENTER_ON_PARENT);
		gtk_window_set_transient_for(GTK_WINDOW(file_selector), GTK_WINDOW(parent));
		gtk_window_set_destroy_with_parent(GTK_WINDOW(file_selector), TRUE);
		gtk_window_present(GTK_WINDOW(parent));
	}
	else {
		gtk_window_set_position(GTK_WINDOW(file_selector), GTK_WIN_POS_CENTER);
	}

	gtk_file_selection_set_select_multiple(GTK_FILE_SELECTION(file_selector), FALSE);
	gtk_file_selection_set_filename(GTK_FILE_SELECTION(file_selector), filename);

	g_signal_connect(GTK_FILE_SELECTION(file_selector)->ok_button,
			 "clicked",
			 G_CALLBACK(on_start_transfer_clicked),
			 file_selector);
	g_signal_connect(GTK_FILE_SELECTION(file_selector)->cancel_button,
			 "clicked",
			 G_CALLBACK(on_cancel_transfer_clicked),
			 file_selector);
	g_signal_connect(G_OBJECT(file_selector), "delete_event",
			 G_CALLBACK(on_file_selector_window_destroy_event),
			 GTK_FILE_SELECTION(file_selector)->cancel_button);
    
	/* Save the file info. We will need it for the actual transfer */
	g_object_set_data(G_OBJECT(file_selector), "file_info", file_info);

	/* Display that dialog */  
	gtk_widget_show(file_selector);
	gtk_widget_grab_focus(file_selector);
	gtk_editable_set_position(GTK_EDITABLE(GTK_FILE_SELECTION(file_selector)->selection_entry), -1);

	return;
} /* yahoo_fxfer_show_file_selection */

void yahoo_fxfer_getfile_cancel(char *who, char *filekey, char *token) {
	struct fxfile *file_info;
	GtkWidget *parent;

	file_info=yahoo_find_file_info(&receive_files, who, 0, 0, filekey);
	if (!file_info) {
		/* We never found the file_info in the list.
		 * This should NEVER happen...
		 */
		return;
	}
	file_info->cancel_transfer = 1;
	parent=find_pms_window(who);
	if (!parent) parent=chat_window;
	gtk_window_present(GTK_WINDOW(parent));
	show_ok_dialog_p(parent, _("The remote user has cancelled the transfer"));
	//potential timing issues. Allow the file_info to leak.
	//if (!file_info->thread_started) free_fxfile_info(&receive_files, file_info);
	ymsg_fxfer_cancel(ymsg_sess, who, filekey);
}

/*
 * -----------------------------------------------------------------------------------------
 */

void *yahoo_fxfer_sendfile_thread(void *arg) {
	char url[2048];
	FILE *f;
	char buffer[512];
	int sock = -1;
	struct sockaddr_in sa;
	int numsnd;
	int bytesleft;
	int bytesread;
	struct fxfile *file_info = arg;
	GtkWidget *parent; /* either pm of "who", or main chat window */
	float percent;

	file_info->thread_started=1;
	/* get parent window */
	gdk_threads_enter();
	// * FIXME * get parent before thread starts.
	// * FIXME * ? maybe put it in the fxfile struct ?
	parent = find_pms_window(file_info->who);
	if (!parent) parent=chat_window;
	gtk_window_present(GTK_WINDOW(parent));
	gdk_flush();
	gdk_threads_leave();

	memset(&sa, 0, sizeof(sa));
	sa.sin_addr.s_addr = inet_addr(file_info->server);
	sa.sin_family = AF_INET;
	sa.sin_port = htons(80);
	
	if ((sock=socket(AF_INET, SOCK_STREAM, 6)) == -1) {
		gdk_threads_enter();
		show_ok_dialog_p(parent, _("Socket error!"));
		free_fxfile_info(&send_files, file_info);
		gdk_flush();
		gdk_threads_leave();
		return(0);
	}

	if (connect(sock, (struct sockaddr*)&sa, sizeof(sa)) == -1) {
		gdk_threads_enter();
		show_ok_dialog_p(parent, _("Could not connect to relay server"));
		free_fxfile_info(&send_files, file_info);
		gdk_flush();
		gdk_threads_leave();
		close(sock);
		return(0);
	}

	// send the "POST-command"
	snprintf(url, 1022, "POST /relay?token=%s&sender=%s&recver=%s HTTP/1.1\r\n"
			     "Referer: memyselfandi\r\n"
			     "Cookie: %s ;B=6n6djhp203aio&b=3&s=np\r\n"
			     "User-Agent: Mozilla/4.0 (compatible; MSIE 5.5)\r\n"
			     "Host: %s\r\n"
			     "Content-Length: %i\r\n"
			     "Cache-Control: no-cache\r\n"
			     "\r\n",
		  url_encode(file_info->token),
		  select_profile_name(YMSG_NOTIFY, 0),
		  file_info->who,
		  ymsg_sess->cookie,
		  file_info->server,
		  file_info->file_length);
	write(sock, url, strlen(url));

	// now the data...
	if (!(f=fopen(file_info->fullfilename, "rb"))) {
		snprintf(url, 500, "%s:\n%s", _("File could not be opened"), file_info->fullfilename);
		gdk_threads_enter();
		show_ok_dialog_p(parent, url);
		free_fxfile_info(&send_files, file_info);
		gdk_flush();
		gdk_threads_leave();
		close(sock);
		return(0);
	}

	/* create the progress meter box */
	gdk_threads_enter();
	create_meter(parent, file_info, "Sending");
	gdk_flush();
	gdk_threads_leave();

	bytesleft = file_info->file_length;
	while (!file_info->cancel_transfer && bytesleft) {
		bytesread = fread(&buffer, 1, sizeof(buffer), f);
		numsnd = write(sock, buffer, bytesread);
		if (numsnd < 0) {
			gdk_threads_enter();
			show_ok_dialog_p(parent, _("The remote user has cancelled the transfer"));
			gdk_flush();
			gdk_threads_leave();
			file_info->cancel_transfer = 1;
			break;
		}
		bytesleft -= numsnd;
		percent = 1.0 - (float)bytesleft/(float)file_info->file_length;
		gdk_threads_enter();
		update_meter(file_info, percent);
		gdk_flush();
		gdk_threads_leave();
	}
	
	fclose(f);
	close(sock);

	if (file_info->close_meter_when_done) {
		if (file_info->meter) {
			gdk_threads_enter();
			gtk_widget_destroy(file_info->meter);
			gdk_flush();
			gdk_threads_leave();
			file_info->meter=NULL;
			file_info->progress_bar=NULL;
		}
	}

	if (file_info->cancel_transfer == 0) {
		gdk_threads_enter();
		snprintf(url, 500, "%s\n%s", _("File sent successfully"), file_info->fullfilename);
		show_ok_dialog_p(parent, url );
		gdk_flush();
		gdk_threads_leave();
	}

	gdk_threads_enter();
	free_fxfile_info(&send_files, file_info);
	gdk_threads_leave();
	return(0);
}

/*
 * -----------------------------------------------------------------------------------------
 */

/* send a file to relay-server */
/* handles our own file transfer
 * This routine gets invoked if the remote user decided to cancel the transfer.
 */
void yahoo_fxfer_sendfile_relay_cancel(char *who, char *filekey) {
	struct fxfile *file_info;
	GtkWidget *parent;

	file_info=yahoo_find_file_info(&send_files, who, 0, 0, filekey);
	if (!file_info) {
		/* We never found the file_info in the list.
		 * This should NEVER happen...
		 */
		return;
	}
	file_info->cancel_transfer = 1;
	parent=find_pms_window(who);
	if (!parent) parent=chat_window;
	gtk_window_present(GTK_WINDOW(parent));
	show_ok_dialog_p(parent, _("The remote user has cancelled the transfer"));
	ymsg_fxfer_cancel(ymsg_sess, who, filekey);
}


/* send a file to relay-server */
/* handles our own file transfer
 * This is the 3rd step.
 */
void yahoo_fxfer_sendfile_relay(char *who, char *filekey, char *token) {
	struct fxfile *file_info;
		    
	file_info=yahoo_find_file_info(&send_files, who, 0, 0, filekey);
	if (!file_info) {
		/* We never found the file_info in the list.
		 * This should NEVER happen...
		 */
		return;
	}

	file_info->token=strdup(token);

	/* fire off a thread for this file download.
	 * Each download gets a separate thread
	 */
	g_thread_create(yahoo_fxfer_sendfile_thread, file_info, FALSE, NULL);
}


/* handles declining of file-offering */
/* Remote user has declined our file send */
void yahoo_fxfer_decline_msg(char *who, char *filekey) {
	char url[1024];
	GtkWidget *parent;
	struct fxfile *file_info;

	file_info=yahoo_find_file_info(&send_files, who, 0, 0, filekey);
	if (file_info) {
		free_fxfile_info(&send_files, file_info);
	}

	/* get parent window */
	parent = find_pms_window(who);
	if (parent) gtk_window_present(GTK_WINDOW(parent));

	snprintf(url, 1024, _("%s has declined the file"), who);
	show_ok_dialog_p(parent, url);
}


/* preparing to send a file via relay-server */
/* Most Linux-System are firewalled and Apache listening on Port 80 */
/* So we would want to use Relay-Server when possible */
/* handles our own file transfer
 * This is the 2nd step.
 */
void yahoo_fxfer_prepsendfile(char *who, char *filekey) {
	struct hostent *hinfo;
	struct fxfile *file_info;
	
	file_info=yahoo_find_file_info(&send_files, who, 0, 0, filekey);
	if (!file_info) {
		/* We never found the file_info in the list.
		 * This should NEVER happen...
		 */
		return;
	}

	// get the relay-server
	hinfo = gethostbyname(YAHOO_RELAYSERVER);
	file_info->server=malloc(16);
	strncpy(file_info->server, (char *)inet_ntoa(*((struct in_addr *)hinfo->h_addr)), 15);
	ymsg_fxfer_relaynotice(ymsg_sess,
			       file_info->who,
			       file_info->filename,
			       file_info->filekey,
			       file_info->server);
}


/* generates a "random" filekey */
char *yahoo_fxfer_genfilekey() {
	static char possible[] = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_$";
	char *rndcookie=(char *)malloc(25);
	int i;

	for(i=0; i<22; i++) {
		rndcookie[i] = possible[(rand() & 0x3f)];
		if (rndcookie[i] == rndcookie[i-1] && rndcookie[i] == 0x24) i--;  // avoid $$ in the first 22 bytes
	}
	rndcookie[22] = 0x24;
	rndcookie[23] = 0x24;
	rndcookie[24] = 0x00;
	return(rndcookie);
}


/* handles our own file transfer
 * This is the 1st step of the transfer.
 */
void yahoo_start_fxfer(char *who, char *filename) {
	FILE *f;
	struct fxfile *file_info;
	char url[1024];
	struct stat sbuf;
	GtkWidget *parent;

	/* get parent window */
	parent = find_pms_window(who);
	if (parent) gtk_window_present(GTK_WINDOW(parent));

	if (stat(filename, &sbuf)) {
		snprintf(url, 500, "%s:\n%s", _("File does not exist"), filename);
		show_ok_dialog_p(parent, url);
		return;
	}

	if (!(f=fopen(filename, "rb"))) {
		snprintf(url, 500, "%s:\n%s", _("File could not be opened"), filename);
		show_ok_dialog_p(parent, url);
		return;
	}
	fclose(f);
	
	/* Limit to files 250kb and under */
	/*	
	 * if (((int)sbuf.st_size) > 250000) {
	 *	snprintf(url, 500, "%s:\n%s\n\n%s: 250kb", _("File is too large"), filename, _("Maximum Allowed File Size"));
	 *	show_ok_dialog(url);
	 *	return;
	 * }
	 */

	file_info=create_fxfile_info(who, 0, 0, sbuf.st_size);
	file_info->fullfilename=strdup(filename);
	file_info->filename=g_path_get_basename(filename);
	file_info->filekey=yahoo_fxfer_genfilekey();
	
	send_files = g_list_append(send_files, file_info);
	ymsg_fxfer_offer(ymsg_sess, who, file_info->filename, file_info->filekey, file_info->file_length);
} /* yahoo_start_fxfer */

