/*	WebDownloader for X-Window
 *	Copyright (C) 1999-2002 Koshelev Maxim
 *	This Program is free but not GPL!!! You can't modify it
 *	without agreement with author. You can't distribute modified 
 *	program but you can distribute unmodified program.
 *
 *	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.
 */

#include "ftpd.h"
#include "ftp.h"
#include "client.h"
#include "liststr.h"
#include "var.h"
#include "locstr.h"
#include "ntlocale.h"
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <strings.h>
#include <time.h>
#include "signal.h"

using namespace d4x;

//****************************************/
void ftp_extract_link(char *src,char *dst) {
	char *delim=" -> ";
	if (src) {
		char *tmp=strstr(src,delim);
		if (tmp) {
			strcpy(dst,tmp+strlen(delim));
		} else dst[0]=0;
	};
};

int ftp_type_from_str(char *data) {
	if (*data=='d')
		return T_DIR;
	if (*data=='l')
		return T_LINK;
	if (*data=='c' || *data=='b')
		return T_DEVICE;
	return T_FILE;
};

int ftp_permisions_from_str(char *a) {
	if (a==NULL || strlen(a)<10) return(S_IRUSR|S_IWUSR);
	int perm=0;
	if (a[1]=='r') perm|=S_IRUSR;
	if (a[2]=='w') perm|=S_IWUSR;
	if (a[3]=='x' || a[3]=='s' || a[3]=='S') perm|=S_IXUSR ;
	if (a[4]=='r') perm|=S_IRGRP;
	if (a[5]=='w') perm|=S_IWGRP;
	if (a[6]=='x' || a[6]=='s' || a[6]=='S') perm|=S_IXGRP;
	if (a[7]=='r') perm|=S_IROTH;
	if (a[8]=='w') perm|=S_IWOTH;
	if (a[9]=='x') perm|=S_IXOTH;
	return perm;
};

time_t ftp_date_from_dos(char *src){
	struct tm *date=new tm;
	time_t NOW=time(NULL);
	localtime_r(&NOW,date);
	date->tm_sec=0;
	date->tm_isdst=-1;
	char *tmp=src;
	/* day-month-year min:hour[aa]*/
	sscanf_int(tmp,&(date->tm_mday));
	tmp+=3;
	sscanf_int(tmp,&(date->tm_mon));
	tmp+=3;
	int year=date->tm_year;
	sscanf_int(tmp,&(year));
	if (year+100<=date->tm_year)
		date->tm_year=year+100;
	else
		date->tm_year=year;
	if (date->tm_mon>12){
		year=date->tm_mon;
		date->tm_mon=date->tm_mday;
		date->tm_mday=year;
	};
	date->tm_mon-=1;
	/* parsing minutes and hours */
	tmp=index(src,' ');
	if (tmp){
		while (*tmp && *tmp==' ') tmp+=1;
		sscanf_int(tmp,&(date->tm_hour));
		tmp+=3;
		sscanf_int(tmp,&(date->tm_min));
		tmp+=2;
		if (*tmp=='p' || *tmp=='P')
			date->tm_hour+=12;
	};
	NOW=mktime(date);
	delete(date);
	return(NOW);
};

time_t ftp_date_from_str(char *src) {
	char *data=new char[strlen(src)+1];
	char *tmp;
	int month,day;

	tmp=extract_string(src,data,5);
	if (is_string(data)){
		tmp=extract_string(tmp,data);
		if (!is_string(data)){
			tmp=extract_string(src,data,4);
		};
	};

	time_t NOW=time(NULL);
	struct tm *date=new tm;
	localtime_r(&NOW,date);
	date->tm_sec=0;
	date->tm_min=0;
	date->tm_hour=0;
	date->tm_isdst=-1;
	month=date->tm_mon;
	day=date->tm_mday;
	if (tmp && *tmp) {
		tmp=extract_string(tmp,data);
		date->tm_mon=convert_month(data);
	};
	if (tmp && *tmp) {
		tmp=extract_string(tmp,data);
		sscanf(data,"%i",&(date->tm_mday));
	};
	if (tmp && *tmp) {
		extract_string(tmp,data);
		if (index(data,':')) {
			/* very ugly way to skip first zero */
			char *tmpdata=data;
			sscanf_int(tmpdata,&(date->tm_hour));
			tmpdata+=3;
			sscanf_int(tmpdata,&(date->tm_min));
			if (month<date->tm_mon ||
			    (month==date->tm_mon && day<date->tm_mday))
				date->tm_year-=1;
		} else {
			sscanf(data,"%i",&(date->tm_year));
			date->tm_year-=1900;
		};
	};	
	NOW=mktime(date);
	delete date;
	delete[] data;
	return NOW;
};

/* parsing string of LIST -la command
   char *src - string for parsing
   tFileInfo *dst - where put result
   int flag - need put strings (name and name of link) to result or not
 */
void ftp_cut_string_list(char *src,tFileInfo *dst,int flag) {
	if (src==NULL || dst==NULL) return;
	del_crlf(src);
	int srclen=strlen(src)+1;
	char *str1=new char[srclen];
	char *name=new char[srclen];
	*str1=0;
	*name=0;
	int par1;
	extract_string(src,str1,5);
	if (strlen(str1)){
// unix style listing
		extract_string(src,name,1);
		char *rsrc;
		if (is_string(name))
			rsrc=src;
		else{
			rsrc=skip_strings(src,2);
			extract_string(rsrc,str1,5);
		};
		char *tmp;
		if (!is_string(str1)){
			tmp=skip_strings(rsrc,8);
			sscanf(rsrc,"%s %u %s %s %lli %s %u %s %s",
	       		str1,&par1,str1,str1,&dst->size,str1,&par1,str1,name);
		}else{
			tmp=skip_strings(rsrc,7);
			sscanf(rsrc,"%s %u %s %lli %s %u %s %s",
	       		str1,&par1,str1,&dst->size,str1,&par1,str1,name);
		};
		dst->type=ftp_type_from_str(rsrc);
		if (dst->type!=T_DEVICE) {
			dst->perm=ftp_permisions_from_str(rsrc);
			dst->date=ftp_date_from_str(rsrc);
		};
		if (flag) {
			if (tmp) dst->name.set(tmp);
			else dst->name.set(name);
		};
		if (dst->type==T_LINK) {
			ftp_extract_link(rsrc,name);
			dst->body.set(name);
			if (flag) {
				tmp=strstr(dst->name.get()," -> ");
				if (tmp) *tmp=0;
			};
		}else dst->body.set(NULL);
	}else{
// dos style listing

		char *new_src=extract_string(src,str1);
		new_src=extract_string(new_src,str1);
		new_src=extract_string(new_src,str1);
		if (new_src && *new_src){
			dst->date=ftp_date_from_dos(src);
			if (strstr(str1,"DIR")){
				dst->type=T_DIR;
				dst->perm=0775;
			}else{
				sscanf(str1,"%lli",&(dst->size));
				dst->type=T_FILE;
				dst->perm=0664;
			};
			
			if (flag && new_src && *new_src) {
				while (*new_src==' ') new_src+=1;
				dst->name.set(new_src);
				dst->body.set(NULL);
			};
		};
	};
	delete[] str1;
	delete[] name;
};
//****************************************/
void tFtpDownload::print_error(int error_code){
	switch(error_code){
	case ERROR_DATA_CONNECT:
		LOG->log(LOG_ERROR,_("Can't establish data connection"));
		break;
	case ERROR_CWD:
		LOG->log(LOG_ERROR,_("Can't change directory"));
		break;
	case ERROR_TOO_MANY_USERS:
		LOG->log(LOG_WARNING,_("Server refused login, perhaps there are too many users of your class"));
		break;
	default:
		tDownloader::print_error(error_code);
		break;
	};
};

int tFtpDownload::change_dir() {
	int rvalue=0;
	if (DONT_CWD || CWDFlag) return 0;
	/*
	if (!equal(ADDR.username.get(),DEFAULT_USER)){
		if ((rvalue=FTP->change_dir("/"))){
			print_error(ERROR_CWD);
			return(rvalue);
		};
	};
	if ((rvalue=FTP->change_dir(ADDR.path.get()))){
		print_error(ERROR_CWD);
		return(rvalue);
	};
	*/
	if ((rvalue=FTP->change_dir(ADDR.path.c_str()))){
		print_error(ERROR_CWD);
		return(rvalue);
	};
		
	CWDFlag=1;
	return RVALUE_OK;
};

//****************************************/
tFtpDownload::tFtpDownload():tDownloader(){
	FTP=NULL;
	DIR=list=NULL;
	RetrNum=0;
	DONT_CWD=0;
};

tFtpDownload::tFtpDownload(tWriterLoger *log):tDownloader(log){
	FTP=NULL;
	DIR=list=NULL;
	RetrNum=0;
	DONT_CWD=0;
};

void tFtpDownload::dont_cwd(){
	DONT_CWD=1;
};

int tFtpDownload::reconnect() {
	int success=1;
	Status=D_QUERYING;
	while (success) {
		if (FTP->get_status()!=STATUS_TIMEOUT && FTP->get_status()!=0) {
			print_error(ERROR_TOO_MANY_USERS);
		};
		FTP->done();
		if (config.number_of_attempts &&
		    RetrNum>=config.number_of_attempts) {
			print_error(ERROR_ATTEMPT_LIMIT);
			return -1;
		};
		RetrNum++;
		print_error(ERROR_ATTEMPT);
		tDownloader::reconnect();
		if (RetrNum>1) {
			LOG->log(LOG_OK,_("Sleeping"));
			d4x::USR1Off2On sig_unlocker_;
			sleep(config.time_for_sleep+1);
		};
		if (FTP->reinit()==0){
			success=0;
			if (config.proxy.ftp_user.get() && config.proxy.ftp_pass.get()){
				FTP->registr(config.proxy.ftp_user.get(), config.proxy.ftp_pass.get());
				success=FTP->connect();
			};
			if (success==0){
				FTP->registr(ADDR.user,ADDR.pass);
				success=FTP->connect();
			};
		};
	};
	CWDFlag=0;
	return 0;
};

void tFtpDownload::init_download(const std::string &path,const std::string &file) {
	ADDR.file=file;
	if (path[0]!='~' && path[0]!='/'){
		ADDR.path="/";
		ADDR.path/=path;
	}else
		ADDR.path=path;
};

int tFtpDownload::init(const d4x::URL &hostinfo,tCfg *cfg,SocketPtr s) {
	FTP=new tFtpClient(cfg);
	RetrNum=0;
	ADDR=hostinfo;
	if (ADDR.user.empty())
		ADDR.user=DEFAULT_USER;
	if (ADDR.pass.empty())
		ADDR.pass=CFG.ANONYMOUS_PASS;
	DIR=NULL;
	list=NULL;

	config.copy_ints(cfg);
	if (cfg->split){
		config.retry=0;
		config.rollback=0;
	};
	config.copy_proxy(cfg);

	if (config.proxy.type==0 && config.proxy.ftp_host.get()
	    && config.proxy.ftp_host.get()[0] && config.proxy.ftp_port) {
		FTP->init(config.proxy.ftp_host.get(),LOG,config.proxy.ftp_port,config.timeout);
		char port[MAX_LEN];
		port[0]=0;
		if (ADDR.port!=get_port_by_proto(D_PROTO_FTP))
			sprintf(port,":%i",ADDR.port);
		ADDR.user=ADDR.user+"@"+ADDR.host+port;
	} else
		FTP->init(ADDR.host,LOG,ADDR.port,config.timeout);
	FTP->set_passive(config.passive);
	FTP->set_retry(config.retry);
	FTP->set_dont_set_quit(config.dont_send_quit);
	if (!s){
		while(reconnect()==0){
			if (cfg->split==0 || FTP->force_reget()==0)
				return(0);
		};
		return(-1);
	};
	FTP->import_ctrl_socket(s);
	CWDFlag=0;
	RetrNum=1;
	tDownloader::reconnect();
	return(0);
};

tStringList *tFtpDownload::dir() {
	return DIR;
};

int tFtpDownload::ftp_get_size_no_sdc(tStringList *l){
	if (ADDR.mask){
		return(FTP->get_size(NULL,l));
	};
	if (DONT_CWD){
		TMP_FILEPATH=std::string("/")+ADDR.path;
		TMP_FILEPATH/=ADDR.file;
		return(FTP->get_size(TMP_FILEPATH.c_str(),l));
	};
	return(FTP->get_size(ADDR.file.c_str(),l));
};

int tFtpDownload::ftp_get_size(tStringList *l){
	if (FTP->stand_data_connection()){
		print_error(ERROR_DATA_CONNECT);
		return(-1);
	};
	return(ftp_get_size_no_sdc(l));
};

int tFtpDownload::is_dir(){
	if (FTP->change_dir(ADDR.file.c_str())==0)
		return(1);
	return(0);
};

fsize_t tFtpDownload::ls_answer_short(){
	tString *last=list->last();
	if (!last) {
		LOG->log(LOG_WARNING,_("Empty answer. Trying to change directory to determine file type."));
		D_FILE.perm=S_IRUSR|S_IWUSR;
		/* try to CWD */
		if (is_dir()){
			D_FILE.type=T_DIR;
			ADDR.path/=ADDR.file;
			ADDR.file.clear();
		}else
			D_FILE.type=T_FILE;
		return 0;
	};
	D_FILE.type=T_FILE;
	D_FILE.size=0;
	if (equal_first(last->body,"total")) {
		D_FILE.type=T_DIR;
		D_FILE.size=1;
		if (DIR) delete (DIR);
		DIR=list;
		list=NULL;
		D_FILE.perm=S_IRUSR|S_IWUSR|S_IXUSR;
		return 0;
	};
	ftp_cut_string_list(last->body,&D_FILE,1);
	LOG->log_printf(LOG_OK,_("Length is %ll"),D_FILE.size);
	return D_FILE.size;
};

fsize_t tFtpDownload::ls_answer_long(){
	tString *last=list->last();
	while(last){
		if (strstr(last->body,ADDR.file.c_str()))
			break;
		last=(tString*)(last->next);
	};
	if (is_dir() || ADDR.mask){
		LOG->log(LOG_WARNING,_("This is a directory!"));
		D_FILE.size=1;
		if (DIR) delete DIR;
		DIR=list;
		list=NULL;
		D_FILE.type=T_DIR;
		D_FILE.perm=S_IRUSR|S_IWUSR;
		return 1;
	};
	if (last==NULL){
		LOG->log(LOG_WARNING,_("No such file or directory!"));
		return(-1);
	};
	ftp_cut_string_list(last->body,&D_FILE,0);
	LOG->log_printf(LOG_OK,_("Length is %ll"),D_FILE.size);
	D_FILE.type=T_FILE;
	return(D_FILE.size);
};

fsize_t tFtpDownload::get_size_only() {
	return(get_size());
};

fsize_t tFtpDownload::get_size() {
	if (!list) {
		list=new tStringList;
		list->init(0);
	} else list->done();
	while (1) {
		if (!change_dir()) {
			int a=ftp_get_size(list);
			if (a==0 && list->count()<=2){
				fsize_t sz=ls_answer_short();
				if ((D_FILE.name.get() && ADDR.file==D_FILE.name.get()) ||
				    FTP->METHOD_TO_LIST==1)
					return(sz);
				FTP->METHOD_TO_LIST=1;
				list->done();
				a=ftp_get_size(list);
				if (a==0 && list->count()<=2)
					return(ls_answer_short());
			};
			if (a==0 && list->count()>2) {
				return(ls_answer_long());
			};
			LOG->log(LOG_WARNING,_("Couldn't get size :(("));
		};
		if (FTP->get_status()==STATUS_CMD_ERR ||
		    FTP->get_status()==STATUS_UNSPEC_ERR){
			D_FILE.type=T_FILE;
			break; //an error occured
		};
		if (reconnect())
			break;
	};
	return -1;
};

int tFtpDownload::download_dir() {
	int ind=0;
	LOG->log(LOG_OK,_("Loading directory..."));
	if (!DIR) {
		DIR=new tStringList;
		DIR->init(0);

		while(1) {
			if (!change_dir()) {
				if (!FTP->stand_data_connection()) {
					if (!FTP->test_reget()) {
						DIR->done();
					};
					Status=D_DOWNLOAD;
					ind=ftp_get_size_no_sdc(DIR);
					if (ind==0) {
						LOG->log(LOG_OK,_("Listing was loaded"));
						return 0;
					};
					LOG->log(LOG_WARNING,_("Listing not loaded completelly"));
				} else {
					print_error(ERROR_DATA_CONNECT);
				};
/*
			} else {
				int s=FTP->get_status();
				if (s!=STATUS_TIMEOUT && (s!=STATUS_CMD_ERR || s!=STATUS_UNSPEC_ERR))
					return -1;
*/
			};
			if (reconnect()) {
				return -1;
			};
		};
	};
	return 0;
};

int tFtpDownload::download(fsize_t len) {
	int rvalue=0;
#ifdef DEBUG_ALL
	LOG->log_printf(LOG_OK,"tFtpDownload::download(%ll)",len);
#endif //DEBUG ALL
	switch (D_FILE.type){
	case T_DIR: {
		rvalue=download_dir();
		break;
	};
	case T_LINK: {
		LOG->log(LOG_OK,_("Link was loaded :))"));
		rvalue=0;
		break;
	};
	default:{
		if (LOADED && remote_file_changed()){
			print_error(ERROR_FILE_UPDATED);
			if (config.retry==0) return(-1);
			LOADED=0;
			LOG->shift(0);
			LOG->truncate();
		};
		fsize_t length_to_load=len>0?LOADED+len:0;
		fsize_t ind=0;
		while(1) {
			if (!change_dir()) {
				if (!FTP->stand_data_connection()) {
					StartSize=rollback();
					Status=D_DOWNLOAD;
					fsize_t to_load=len>0?length_to_load-LOADED:0;
					if (DONT_CWD){
						TMP_FILEPATH=std::string("/")+ADDR.path;
						TMP_FILEPATH/=ADDR.file;
						ind=FTP->get_file_from(TMP_FILEPATH.c_str(),LOADED,to_load);
					}else
						ind=FTP->get_file_from(ADDR.file.c_str(),LOADED,to_load);
#ifdef DEBUG_ALL
					LOG->log_printf(LOG_OK,"return to tFtpDownload::download with ind=%ll",ind);
#endif //DEBUG ALL
					if (!FTP->test_reget()){
						if (config.retry==0) break;
						StartSize=LOADED=0;
					};
					if (ind>0) {
						LOADED+=ind;
//						LOG->log_printf(LOG_OK,_("%ll bytes loaded."),ind);
						LOG->log_printf(LOG_OK,"%ll bytes loaded.(LOADED=%ll,len=%ll,FTP->get_status()=%i)",
								ind,LOADED,len,FTP->get_status());
					};
					if (!FTP->get_status()) {
						rvalue=0;
						break;
					};
				} else {
					print_error(ERROR_DATA_CONNECT);
				};
/*			} else {
				int s=FTP->get_status();
				if (s!=STATUS_TIMEOUT && (s!=STATUS_CMD_ERR || s!=STATUS_UNSPEC_ERR)){
					rvalue=-1;
					break;
				};
*/
			};
			if (reconnect()) {
				rvalue=-1;
				break;
			};
		};
	};
	};
#ifdef DEBUG_ALL
	LOG->log_printf(LOG_OK,"exit from tFtpDownload::download with %i",rvalue);
#endif //DEBUG ALL
	return rvalue;
};

fsize_t tFtpDownload::get_readed() {
	if (D_FILE.type==T_FILE) return (FTP->get_readed());
	if (DIR) return DIR->size();
	if (list) return list->size();
	return 0;
};

fsize_t tFtpDownload::another_way_get_size() {
	return FTP->another_way_get_size();
};

int tFtpDownload::get_child_status() {
	return(FTP->get_status());
};

fsize_t tFtpDownload::get_start_size() {
	if (FTP && !FTP->test_reget())
		return 0;
	return(StartSize);
};

char *tFtpDownload::get_new_url() {
	return(copy_string(D_FILE.body.get()));
};

int tFtpDownload::reget() {
	if (FTP) return FTP->test_reget();
	return(1);
};

void tFtpDownload::done() {
	if (FTP) FTP->done();
};

SocketPtr tFtpDownload::export_ctrl_socket(){
	SocketPtr rval;
	if (FTP) return(FTP->export_ctrl_socket());
	return rval;
};

tFtpDownload::~tFtpDownload() {
	if (FTP) delete(FTP);
	if (DIR) delete(DIR);
	if (list) delete(list);
};


syntax highlighted by Code2HTML, v. 0.9.1