/******************************************************************** Copyright (C) 2000 Bassoukos Tassos 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., 675 Mass Ave, Cambridge, MA 02139, USA. *********************************************************************/ #include #include "hotline.h" #include "network.h" #include "protocol.h" #include "files.h" #include "filetransfer.h" #include "filethread.h" #include "guiprefs.h" #include "smalltrans.h" typedef struct { char HTXF[4]; guint32 xferid; guint32 uplen; guint32 pad; } FTHandshake; #define FTHandshake_sizeof 16 typedef struct { char name[4]; guint32 pad[2]; guint32 size; } FTHeader; #define FTHeader_sizeof 16 typedef struct { char FILP[4]; guint16 version; guint16 pad1; guint32 pad2[3]; guint32 count; } FTInit; #define FTInit_sizeof 24 typedef struct { char amac[4],type[4],creator[4]; guint32 pad[10]; guint32 time_created[2],time_modified[2]; } FTInfo; #define FTInfo_sizeof (17*4) #define ft_get_speed(ft, timer) (float)ft->session_bytes/(float)g_timer_elapsed(timer,NULL) /* Called by ft_transfer_bytes to enforce the speed limit */ static int ft_speed_shaping(FileTransfer *ft, GTimer *t) { int speed_limit = 0; int new_speed; new_speed = ft_get_speed(ft, t); if(!ft->is_upload) speed_limit = downloads_max_speed * 1024; else speed_limit = uploads_max_speed * 1024; if(speed_limit) { while(new_speed > speed_limit) { usleep(5e8/speed_limit); new_speed = ft_get_speed(ft, t); } } return new_speed; } static void ft_format_for_task(Task *task,FileTransfer *ft){ char speedbuf[32],buf[32],sizec[32]; int seconds,minutes,hours,days; if(ft->task_format==NULL || ft->task==NULL) return; if(ft->speed > 1024) sprintf(speedbuf, "%.1f%s", (float)ft->speed/1024.0, "kb"); else sprintf(speedbuf, "%ib", ft->speed); seconds=(int)(((float)(ft->total_bytes-ft->current_bytes))/ft->speed); minutes=seconds/60;seconds%=60; hours=minutes/60;minutes%=60; days=hours/24;hours%=24; if(days>0) sprintf(buf,_("%dd%d:%02d:%02ds"),days,hours,minutes,seconds); else if(hours>0) sprintf(buf,_("%d:%02d:%02ds"),hours,minutes,seconds); else if(minutes>0) sprintf(buf,_("%d:%02ds"),minutes,seconds); else sprintf(buf,_("%d seconds"),seconds); file_format_size(sizec,ft->current_bytes,FALSE); task_set_printf(ft->task,(float)ft->current_bytes/(float)ft->total_bytes, ft->task_format,sizec,speedbuf,buf); } /* Called by either ft_upload_thread or ft_download_thread to actually transfer the data */ static gboolean ft_transfer_bytes(FileTransfer *ft, FILE *in, FILE *out, int size){ int j,i,blocksize=2*1024; char buf[1024*32]; /* This will break if we use ft_transfer_bytes for the resource forks */ if(ft->task!=NULL) gtk_signal_connect(GTK_OBJECT(ft->task),"updated",GTK_SIGNAL_FUNC(ft_format_for_task),ft); ft->timer = g_timer_new(); g_timer_start(ft->timer); file_format_size(buf,ft->total_bytes,TRUE); ft->task_format=g_strdup_printf(_("%s %s (%%s of %s) (%%s/s) (ETA %%s)..."), ft->is_upload ? _("Uploading") : _("Receiving"), ft->name, buf); while(size>0){ j=fread(buf,1,size>blocksize?blocksize:size,in); if(j==0){ show_error_errno(ft->c,NULL,_("While reading data: ")); return FALSE; } size-=j; ft->session_bytes+=j; ft->current_bytes+=j; if(ft->is_upload==FALSE){ if(download_convert_text==TRUE && ft->is_text==TRUE) for(i=0;iis_text==TRUE) for(i=0;ic,NULL,_("While writing data")); return FALSE; } ft->speed=ft_speed_shaping(ft, ft->timer); blocksize=ft->speed>>1; if(blocksize<1024) blocksize=1024; else if(blocksize>1024*32) blocksize=1024*32; if(ft->task!=NULL) task_async_request_update(ft->task); } return TRUE; } /* Called when resuming a download. Gets RESUME_OVERLAP bytes and compare them with the end of the local file to be sure we don't miss or add anything. */ static gboolean ft_check_bytes(FileTransfer *ft, FILE *in, FILE *out) { char buf[RESUME_OVERLAP], fbuf[RESUME_OVERLAP]; if(!ft->current_bytes) return TRUE; if(ft->task) task_async_set_printf(ft->task,-1,_("Matching overlap")); if(fread(buf,1,RESUME_OVERLAP,in) != RESUME_OVERLAP){ show_error_errno(ft->c,NULL,_("While reading data")); return FALSE; } ft->session_bytes+=RESUME_OVERLAP; ft->current_bytes+=RESUME_OVERLAP; if(download_convert_text==TRUE && ft->is_text==TRUE) { int i; for(i=0;ic,NULL,_("While checking data")); return FALSE; } if(memcmp(fbuf, buf, RESUME_OVERLAP)) { int i, ok = 0; printf("Could not match overlap, trying with offset\n(EXPERIMENTAL, please report result)\n"); for(i = 1; i < RESUME_OVERLAP/4; i++) { if(!memcmp(fbuf, buf+i, RESUME_OVERLAP-i)) { ok = 1; break; } } if(ok) fseek(out, i, SEEK_CUR); else { for(i = 1; i < RESUME_OVERLAP/4; i++) { if(!memcmp(fbuf+i, buf, RESUME_OVERLAP-i)) { ok = 1; break; } } if(ok) fseek(out, -i, SEEK_CUR); else { show_error(ft->c, NULL, _("Could not match overlap while trying to resume, giving up\n")); return FALSE; } } fseek(out, -RESUME_OVERLAP, SEEK_CUR); if(fwrite(fbuf, 1, RESUME_OVERLAP, out) != RESUME_OVERLAP) { show_error_errno(ft->c,NULL,_("While checking data")); return FALSE; } } return TRUE; } /* Opens a socket to the server */ static gboolean ft_make_connection(FileTransfer *ft, int upload_size, int xferid){ NetAddr na; FTHandshake header; if(isIP(&na,ft->c->bookmark->host)==FALSE){ if(resolveHost(&na,ft->c->bookmark->host)==FALSE){ show_error_errno(ft->c,NULL,_("No IP for name %s:"),ft->c->bookmark->host); return FALSE; } } ft->fd=getSocketTo(&na,ft->c->bookmark->port+1); if(ft->fd==NULL){ show_error_errno(ft->c,NULL,_("Could not connect to %s:"),C_NAME(ft->c)); return FALSE; } strncpy(header.HTXF,"HTXF",4); header.xferid=GINT32_TO_BE(xferid); header.uplen=GINT32_TO_BE(upload_size); header.pad=GINT32_TO_BE(0); fwrite(&header,sizeof(header),1,ft->fd); fflush(ft->fd); if(ferror(ft->fd)!=0){ show_error_errno(ft->c,NULL,_("Error handshaking with %s:"),C_NAME(ft->c)); return FALSE; } return TRUE; } /* Handle the negociation and the various info chunks */ void ft_upload_thread(gpointer data){ FileTransfer *ft=(FileTransfer *)data; FTHeader fthd; FTInit ftinit; FTInfo ftinfo; guint16 tmp16; int name_len=strlen(ft->name); struct stat buf; int tot_size; if(ft->server_queue_no!=0) ft_update_queue(ft); fseek(ft->localfile,ft->current_bytes,SEEK_SET); fstat(fileno(ft->localfile),&buf); ft->total_bytes=buf.st_size; tot_size=FTInit_sizeof+FTInfo_sizeof+2+2+name_len+2+ 3*FTHeader_sizeof+buf.st_size-ft->current_bytes; if(ft_make_connection(ft,tot_size,ft->xferid)==FALSE) return; memset(&ftinit,0,sizeof(ftinit)); memcpy(&ftinit.FILP[0],"FILP",4); ftinit.version=GINT16_TO_BE(1); ftinit.count=GINT32_TO_BE(3); memset(&fthd,0,sizeof(fthd)); memcpy(&fthd.name[0],"INFO",4); fthd.size=GINT32_TO_BE(sizeof(ftinfo)+2+2+name_len+2); memset(&ftinfo,0,sizeof(ftinfo)); memcpy(&ftinfo.amac[0],"AMAC",4); if(ft->name[name_len-4]=='.' && ft->name[name_len-3]=='t' && ft->name[name_len-2]=='x' && ft->name[name_len-1]=='t') ft->is_text=TRUE; memcpy(&ftinfo.type[0],ft->is_text==TRUE?"TEXT":"BINA",4); memcpy(&ftinfo.creator[0],"UNIX",4); fwrite(&ftinit,FTInit_sizeof,1,ft->fd); fwrite(&fthd,FTHeader_sizeof,1,ft->fd); fwrite(&ftinfo,FTInfo_sizeof,1,ft->fd); tmp16=GINT16_TO_BE(0); fwrite(&tmp16,sizeof(tmp16),1,ft->fd); tmp16=GINT16_TO_BE(name_len); fwrite(&tmp16,sizeof(tmp16),1,ft->fd); fwrite(ft->name,name_len,1,ft->fd); tmp16=GINT16_TO_BE(0); fwrite(&tmp16,sizeof(tmp16),1,ft->fd); memcpy(&fthd.name[0],"DATA",4); fthd.size=GINT32_TO_BE(buf.st_size-ft->current_bytes); fwrite(&fthd,FTHeader_sizeof,1,ft->fd); ft_transfer_bytes(ft,ft->localfile,ft->fd,buf.st_size-ft->current_bytes); memcpy(&fthd.name[0],"MACR",4); fthd.size=GINT32_TO_BE(0); fwrite(&fthd,FTHeader_sizeof,1,ft->fd); { struct linger { int l_onoff; int l_linger; } l; l.l_onoff=1; l.l_linger=30; setsockopt(fileno(ft->fd),SOL_SOCKET,SO_LINGER,&l,sizeof(l)); } fflush(ft->fd); fclose(ft->fd); ft->fd = NULL; } /* Same as above for downloads */ void ft_download_thread(gpointer data){ FileTransfer *ft=(FileTransfer *)data; FTInit ftinit; int i,have_info=FALSE; if(ft->server_queue_no!=0) ft_update_queue(ft); if(ft_make_connection(ft,0,ft->xferid)==FALSE) return; fread(&ftinit,FTInit_sizeof,1,ft->fd); if(memcmp(&ftinit.FILP,"FILP",4)!=0 || GINT16_FROM_BE(ftinit.version)!=1){ show_error(ft->c,NULL,_("Bad response from server while downloading")); return; } for(i=GINT32_FROM_BE(ftinit.count);i>0;i--){ FTHeader fthd; fread(&fthd,FTHeader_sizeof,1,ft->fd); fthd.size=GINT32_FROM_BE(fthd.size); if(memcmp(&fthd.name,"INFO",4)==0){ FTInfo *ftinfo; ftinfo=malloc(fthd.size); fread(ftinfo,fthd.size,1,ft->fd); /* if(memcmp(&ftinfo.amac,"AMAC",4)!=0){ show_error("Bad response from server while downloading"); return; } */ have_info=TRUE; if(memcmp(&ftinfo->type,"TEXT",4)==0) ft->is_text=TRUE; free(ftinfo); } else if(memcmp(&fthd.name,"DATA",4)==0){ if(have_info==FALSE) printf(_("Warning: DATA before INFO!\n")); ft->total_bytes=ft->current_bytes+fthd.size; ft->session_bytes=0; if(ft->current_bytes && !ft_check_bytes(ft, ft->fd, ft->localfile)) return; if(ft_transfer_bytes(ft,ft->fd,ft->localfile, fthd.size - ft->session_bytes)==FALSE) return; } else { char buf[1024]; int j; if(memcmp(&fthd.name,"MACR",4)!=0) printf(_("Hmmm.... unknown data packet %s (%d long)??\n"), &fthd.name[0],fthd.size); while(fthd.size>0){ if(!(j=fread(buf,1,fthd.size>1024?1024:fthd.size,ft->fd))){ show_error_errno(ft->c,NULL,_("While reading data")); return; } fthd.size-=j; } } } }