/**********************************************************************
 
	Copyright (C) 2003 Hirohisa MORI <joshua@nichibun.ac.jp>
 
	This program is free software; you can redistribute it 
	and/or modify it under the terms of the GLOBALBASE 
	Library General Public License (G-LGPL) as published by 

	http://www.globalbase.org/
 
	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.

**********************************************************************/


#define STREAM_LIB

#include	<fcntl.h>

#include	"machine/include.h"
#include	"pri_level.h"
#include	"lock_level.h"
#include	"memory_debug.h"
#include	"stream.h"
#include	"task.h"
#include	"utils.h"
#include	"XLoHTTP.h"
#include	"change_endian.h"
#include	"machine/err.h"
#include	"client.h"
#include	"s_buf.h"

extern SEM stream_lock;
extern SEM XLoHTTP_lock;
void
test_print(char*,char*d,int len);

void open_timeout(STREAM * con);

void XLoHTTP_task_2();
int s_close_XLoHTTP_2();
int s_write_XLoHTTP_2();
int s_read_XLoHTTP_2();
int s_flush_XLoHTTP_2();
STREAM * s_error_stream();
int s_error();
int s_get_socketip_XLoHTTP_2();

typedef struct XLoHTTP_server_info {
	struct XLoHTTP_server_info *	next;
	char *				server;
	int				ip;
	int				port;
	int				version;
} XLoHTTP_SERVER_INFO;


S_TABLE s_XLoHTTP_2_table = {
	'x',
	0,
	{0,0},
	0,
	s_error_stream,
	s_close_XLoHTTP_2,
	s_write_XLoHTTP_2,
	s_read_XLoHTTP_2,
	s_flush_XLoHTTP_2,
	s_error,
	s_error_stream,
	s_get_socketip_XLoHTTP_2
};

XLoHTTP_SERVER_INFO * 
_search_XLoHTTP_server_info_byname(char * s,int port);
XLoHTTP_SERVER_INFO * 
_search_XLoHTTP_server_info_byip(int ip,int port);
XLoHTTP_SERVER_INFO *
_insert_XLoHTTP_server_info(XLoHTTP_SERVER_INFO * si);
XLoHTTP_SERVER_INFO *
begin_XLoHTTP_server_info(char * s,int ip,int port);
void end_XLoHTTP_server_info(XLoHTTP_SERVER_INFO * si);
void output_client_header_upload(STREAM * con,char * server,int port,int length);
void output_client_header_download(STREAM * con,char * server,int port);
STREAM * proxy_new_connection(
	int * cerr,
	char * target_server,
	int target_ip,
	short target_port,
	char * proxy_server,
	int proxy_ip,
	short proxy_port);
void check_stream();



XLoHTTP_SERVER_INFO * XLoHTTP_server_info_list;


XLoHTTP_SERVER_INFO *
_search_XLoHTTP_server_info_byname(char * s,int port)
{
XLoHTTP_SERVER_INFO * si;
	for ( si = XLoHTTP_server_info_list ; si ; si = si->next ) {
		if ( si->server == 0 )
			continue;
		if ( strcmp(si->server,s) == 0 && si->port == port )
			return si;
	}
	return 0;
}

XLoHTTP_SERVER_INFO *
_search_XLoHTTP_server_info_byip(int ip,int port)
{
XLoHTTP_SERVER_INFO * si;
	for ( si = XLoHTTP_server_info_list ; si ; si = si->next ) {
		if ( si->ip == 0 )
			continue;
		if ( si->ip == ip && si->port == port )
			return si;
	}
	return 0;
}

XLoHTTP_SERVER_INFO *
_insert_XLoHTTP_server_info(XLoHTTP_SERVER_INFO * si)
{
XLoHTTP_SERVER_INFO * sip;
	sip = d_alloc(sizeof(*sip));
	*sip = *si;
	sip->server = copy_str(si->server);
	sip->next = XLoHTTP_server_info_list;
	XLoHTTP_server_info_list = sip;
	return sip;
}


XLoHTTP_SERVER_INFO *
begin_XLoHTTP_server_info(char * s,int ip,int port)
{
XLoHTTP_SERVER_INFO * ret;
XLoHTTP_SERVER_INFO d;
	lock_task(XLoHTTP_lock);
	if ( s )
		ret = _search_XLoHTTP_server_info_byname(s,port);
	if ( ret == 0 )
		ret = _search_XLoHTTP_server_info_byip(ip,port);
	if ( ret ) {
		if ( ret->version == -1 ) {
			ret->version = 0;
		}
		else for ( ; ret->version == 0 ; ) {
			sleep_task((int)ret,XLoHTTP_lock);
			lock_task(XLoHTTP_lock);
		}
	}
	else {
/*	retry: */
		d.version = 0;
		d.server = s;
		d.ip = ip;
		d.port = port;
		ret = _insert_XLoHTTP_server_info(&d);
	}
	unlock_task(XLoHTTP_lock,"task");
	return ret;
}

void
end_XLoHTTP_server_info(XLoHTTP_SERVER_INFO * si)
{
	lock_task(XLoHTTP_lock);
	wakeup_task((int)si);
	unlock_task(XLoHTTP_lock,"XLoHTTP_server_info");
}

void
output_client_header_upload(STREAM * con,char * server,int port,int length)
{

	s_printf(con,"POST http://%s:%i/cgi-bin/upload.cgi HTTP/1.0\r\n",
		server,port);
	s_printf(con,"Pragma: no-cache\r\n");
	s_printf(con,"Content-Type: application/xlohttp2\r\n");
	s_printf(con,"Host: %s:%i\r\n",server,port);
	s_printf(con,"Content-Length: %i\r\n",length);
	s_printf(con,"\r\n");
}

void
output_client_header_download(STREAM * con,char * server,int port)
{

	s_printf(con,"GET http://%s:%i/cgi-bin/download.cgi HTTP/1.0\r\n",
		server,port);
	s_printf(con,"Pragma: no-cache\r\n");
	s_printf(con,"Content-Type: application/xlohttp2\r\n");
	s_printf(con,"Host: %s:%i\r\n",server,port);
	s_printf(con,"\r\n");
}


STREAM *
proxy_new_connection(
	int * cerr,
	char * target_server,
	int target_ip,
	short target_port,
	char * proxy_server,
	int proxy_ip,
	short proxy_port)
{
	if ( proxy_server || proxy_ip ) {
		return new_connection(
			cerr,
			proxy_server,
			proxy_ip,
			proxy_port,
			0,0);
	}
	else {
		return new_connection(
			cerr,
			target_server,
			target_ip,
			target_port,
			0,0);
	}
}


STREAM *
s_connect_XLoHTTP_2(
	int * cerr,
	char * target_server,
	int target_ip,
	short target_port,
	char * proxy_server,
	int proxy_ip,
	short proxy_port,
	int flags)
{
XLoHTTP_HEADER h;
XLoHTTP_SERVER_INFO * si;
HTTP_INFO info;
STREAM * con;
S_XLoHTTP_INFO_2 * xoh_info;
STREAM * ret;
int _len,result;
	si = begin_XLoHTTP_server_info(target_server,target_ip,target_port);
retry:
ss_printf("===================VERSION %s(%i):%i -> %i\n",
target_server,target_ip,target_port,si->version);
	switch ( si->version ) {
	case 0:
		/* TESTING */
		con = proxy_new_connection(
			cerr,
			target_server,
			target_ip,
			target_port,
			proxy_server,
			proxy_ip,
			proxy_port);
		if ( con == 0 )
{ss_printf("   **** ERR 1\n");
			goto err0_unrecoverable;
}
		proxy_ip = s_get_socketip(con);
		output_client_header_upload(con,
			target_server,target_port,sizeof(h));

		h.type = XoH_T_TESTING_VER;
		h.sum = 0;
		h.seq = 0;
		h.cockie[0] = h.cockie[1] = 0;
		change_endian_XLoHTTP_HEADER(&h);
		h.sum = XoH_checksum(&h);
		if ( en_do(&_len,s_write,con,&h,sizeof(h)) <= 0 ) {
			s_close(con);
			*cerr = ESYS_CREFUSED;
ss_printf("   **** ERR 2\n");
			goto err0_unrecoverable;
		}
		new_tick((void(*)())open_timeout,-30,(int)con);
		if ( scan_HTTP(&info,con,0) < 0 ) {
			*cerr = ESYS_CTIMEOUT;
			del_tick_with_data(open_timeout,(int)con);
ss_printf("   **** ERR 3\n");
			s_close(con);
			goto err0_unrecoverable;
		}
		del_tick_with_data(open_timeout,(int)con);
		if ( info.err != 200 ) {
			free_HTTP_info(&info);
			s_close(con);
ss_printf("   **** ERR 4\n");
			*cerr = ESYS_CREFUSED;
			goto err0_unrecoverable;
		}
		free_HTTP_info(&info);
		result = en_do(&_len,s_read,con,&h,sizeof(h));
		if ( result <= 0 ) {
			s_close(con);
			*cerr = ESYS_CREFUSED;
ss_printf("   **** ERR 5\n");
			goto err0_unrecoverable;
		}
		h.sum = XoH_checksum(&h);
		change_endian_XLoHTTP_HEADER(&h);
		if ( h.sum ) {
			s_close(con);
			*cerr = ESYS_CREFUSED;
ss_printf("   **** ERR 6\n");
			goto err0_unrecoverable;
		}

		switch ( h.type ) {
		case XoH_T_ERROR:
			si->version = 1;
			break;
		case XoH_T_TESTING_VER:
			si->version = h.seq;
			break;
		default:
			si->version = 1;
		}
		s_close(con);
		end_XLoHTTP_server_info(si);
ss_printf("   **** RETRY\n");
		goto retry;
	err0_unrecoverable:
		si->version = -1;
		end_XLoHTTP_server_info(si);
		return 0;
/*	err0: */
		end_XLoHTTP_server_info(si);
	case 1:
		return	s_connect_XLoHTTP(
				cerr,
				target_server,
				target_ip,
				target_port,
				proxy_server,
				proxy_ip,
				proxy_port,
				flags);
	case 2:
		ret = d_alloc(sizeof(*ret));
		memset(ret,0,sizeof(*ret));
		ret->h.tbl = &s_XLoHTTP_2_table;
		ret->xoh2.info = xoh_info = d_alloc(sizeof(*xoh_info));
		xoh_info->flags = XoH_F2_BUFFER;

		xoh_info->target_server = copy_str(target_server);
		xoh_info->target_ip = target_ip;
		xoh_info->target_port = target_port;

		xoh_info->proxy_server = copy_str(proxy_server);
		xoh_info->proxy_ip = proxy_ip;
		xoh_info->proxy_port = proxy_port;
		xoh_info->remain_size = MAX_REMAIN_SIZE;
		init_sbuf(&xoh_info->upload_buf,XLoHTTP_lock);

		xoh_info->download_st = con = proxy_new_connection(
			cerr,
			xoh_info->target_server,
			xoh_info->target_ip,
			xoh_info->target_port,
			xoh_info->proxy_server,
			xoh_info->proxy_ip,
			xoh_info->proxy_port);
		if ( con == 0 )
			goto err2;
		xoh_info->proxy_ip = s_get_socketip(con);
		output_client_header_download(con,
				xoh_info->target_server,
				xoh_info->target_port);
		new_tick((void(*)())open_timeout,-30,(int)con);
		if ( scan_HTTP(&info,con,0) < 0 ) {
			*cerr = ESYS_CTIMEOUT;
			del_tick_with_data(open_timeout,(int)con);
			goto err2;
		}
		del_tick_with_data(open_timeout,(int)con);
		if ( info.err != 200 ) {
			free_HTTP_info(&info);
			*cerr = ESYS_CREFUSED;
			goto err2;
		}
		free_HTTP_info(&info);
		result = en_do(&_len,s_read,con,&h,sizeof(h));
		if ( result <= 0 ) {
			*cerr = ESYS_CREFUSED;
			goto err2;
		}
		h.sum = XoH_checksum(&h);
		change_endian_XLoHTTP_HEADER(&h);
		if ( h.sum ) {
			*cerr = ESYS_CREFUSED;
			goto err2;
		}
		memcpy(xoh_info->cockie,h.cockie,sizeof(h.cockie));
		create_task(XLoHTTP_task_2,(int)ret->xoh2.info,PRI_WRITE_BUF);
		
		lock_task(stream_lock);
		_s_open(ret,O_RDWR);
		unlock_task(stream_lock,"s_connect_XLoHTTP");
		return ret;
	err2:
		if ( xoh_info->proxy_server )
			d_f_ree(xoh_info->proxy_server);
		if ( xoh_info->target_server )
			d_f_ree(xoh_info->target_server);
		if ( xoh_info->download_st )
			s_close(xoh_info->download_st);
		d_f_ree(xoh_info);
		d_f_ree(ret);
		return 0;
	default:
		er_panic("open XLoHTTP ver.2\n");
	}
	return 0;
}


void
XLoHTTP_task_2(TKEY d)
{
S_XLoHTTP_INFO_2 * xoh_info;
STREAM * con;
XLoHTTP_HEADER h;
HTTP_INFO info;
int cerr;
int _len,result;
char * buf;
#define BUF_SIZE	1000
int csize;

	xoh_info = (S_XLoHTTP_INFO_2*)GET_TKEY(d);
	for ( ; ; ) {
		xoh_info->upload_st = con = proxy_new_connection(
			&cerr,
			xoh_info->target_server,
			xoh_info->target_ip,
			xoh_info->target_port,
			xoh_info->proxy_server,
			xoh_info->proxy_ip,
			xoh_info->proxy_port);
		if ( con == 0 )
			break;
		output_client_header_upload(con,
			xoh_info->target_server,
			xoh_info->target_port,
			MAX_REMAIN_SIZE+sizeof(h));
		h.type = XoH_T_UPLOAD_SESSION;
		h.sum = 0;
		h.seq = 0;
		memcpy(h.cockie,xoh_info->cockie,sizeof(xoh_info->cockie));
		change_endian_XLoHTTP_HEADER(&h);
		h.sum = XoH_checksum(&h);
		if ( en_do(&_len,s_write,con,&h,sizeof(h)) <= 0 ) {
			break;
		}

		buf = d_alloc(BUF_SIZE+1);
		lock_task(XLoHTTP_lock);
		xoh_info->remain_size = MAX_REMAIN_SIZE;
		xoh_info->upload_st = con;

		for ( ; ; ) {
			if ( xoh_info->remain_size == 0 )
				break;
			if ( xoh_info->remain_size < BUF_SIZE )
				csize = copy_out_sbuf(&xoh_info->upload_buf,buf,
						xoh_info->remain_size,SEM_NULL);
			else	csize = copy_out_sbuf(&xoh_info->upload_buf,buf,
						BUF_SIZE,SEM_NULL);
			if ( csize == 0 )
				break;
			unlock_task(XLoHTTP_lock,"task");
			if ( en_do(&_len,s_write,con,buf,csize) < 0 ) {
				s_close(con);
				goto err;
			}
			lock_task(XLoHTTP_lock);
			xoh_info->remain_size -= csize;
		}
		xoh_info->flags &= ~XoH_F2_BUFFER;
		unlock_task(XLoHTTP_lock,"task");

		if ( scan_HTTP(&info,con,0) < 0 ) {
			break;
		}
		if ( info.err != 200 ) {
			free_HTTP_info(&info);
			break;
		}
		free_HTTP_info(&info);
		result = en_do(&_len,s_read,con,&h,sizeof(h));
		if ( result <= 0 ) {
			break;
		}
		s_close(con);
		h.sum = XoH_checksum(&h);
		change_endian_XLoHTTP_HEADER(&h);
		if ( h.sum ) {
			break;
		}

		switch ( h.type ) {
		case XoH_T_ERROR:
			goto err;
		case XoH_T_UPLOAD_SESSION:
			break;
		default:
			goto err;
		}
		
		lock_task(XLoHTTP_lock);
		xoh_info->flags |= XoH_F2_BUFFER;
		unlock_task(XLoHTTP_lock,"task");
	}
err:
	lock_task(XLoHTTP_lock);
	s_safe_close(xoh_info->upload_st);
	s_safe_close(xoh_info->download_st);
	xoh_info->upload_st = 0;
	xoh_info->download_st = 0;
	xoh_info->flags |= XoH_F2_CLOSE;
	xoh_info->flags &= ~XoH_F2_BUFFER;
	unlock_task(XLoHTTP_lock,"task");
}


int s_write_XLoHTTP_2(
	STREAM * s,
	void * data,
	int len)
{
int _len;
int ret;
char * d;
	lock_task(XLoHTTP_lock);
	if ( s->xoh2.info == 0 ) {
		ret = -1;
		goto err;
	}
	if ( s->xoh2.info->flags & XoH_F2_CLOSE ) {
		ret = -1;
		goto err;
	}
	if ( s->xoh2.info->flags & XoH_F2_BUFFER ) {

test_print(">1>",data,len);

		copy_in_sbuf(&s->xoh2.info->upload_buf,data,len,SEM_NULL);
		ret = len;
	}
	else {
test_print(">2>",data,len);
		if ( s->xoh2.info->upload_st == 0 ) {
			ret = -1;
			goto err;
		}
		if ( s->xoh2.info->remain_size < len )
			_len = s->xoh2.info->remain_size;
		else	_len = len;
		s->xoh2.info->remain_size -= _len;
		unlock_task(XLoHTTP_lock,"s_write_XLoHTTP_2");
		ret = s_write(s->xoh2.info->upload_st,data,_len);
		if ( ret < 0 ) {
			s_safe_close(s->xoh2.info->upload_st);
			s_safe_close(s->xoh2.info->download_st);
			s->xoh2.info->upload_st = 0;
			s->xoh2.info->download_st = 0;
			goto err2;
		}
		lock_task(XLoHTTP_lock);
		if ( ret != _len ) {
			s->xoh2.info->remain_size += _len;
			s->xoh2.info->remain_size -= ret;
		}
		if ( ret < _len )
			goto err;
		if ( len > _len ) {
			d = (char*)data;
			copy_in_sbuf(&s->xoh2.info->upload_buf,&d[_len],len-_len,SEM_NULL);
			ret = len;
		}
	}
err:
	unlock_task(XLoHTTP_lock,"s_write_XLoHTTP");
err2:
	return ret;
}


void
test_print(char * msg,char*d,int len)
{
unsigned char dd;
	printf("%s >> ",msg);
	for ( ; len > 0 ; d ++ , len -- ) {
		dd = *d;
		if ( dd < 0x20 )
			printf("$%x$",dd);
		else if ( dd >= 0x80 )
			printf("$%x$",dd);
		else	printf("%c",dd);
	}
	printf("\n\n");
}

int
s_read_XLoHTTP_2(STREAM * s,void * data,int len)
{
int ret;
STREAM * con;
S_XLoHTTP_INFO_2 * info;
	lock_task(XLoHTTP_lock);
	if ( s->xoh2.info == 0 ) {
		ret = -1;
		goto err;
	}
	if ( s->xoh2.info->flags & XoH_F2_CLOSE ) {
		ret = -1;
		goto err;
	}
	con = s->xoh2.info->download_st;
	unlock_task(XLoHTTP_lock,"s_write_XLoHTTP");
	
	ret = s_read(con,data,len);

test_print("<<",data,len);

	lock_task(XLoHTTP_lock);
	if ( ret < 0 ) {
		info = s->xoh2.info;
		if ( info ) {
			s_safe_close(info->upload_st);
			s_safe_close(info->download_st);
			info->upload_st = 0;
			info->download_st = 0;
		}
	}
err:
	unlock_task(XLoHTTP_lock,"s_write_XLoHTTP");

	return ret;
}

int
s_flush_XLoHTTP_2(STREAM * s)
{
	return 0;
}




int
s_get_socketip_XLoHTTP_2(STREAM * s)
{
	if ( s->xoh2.info )
		return s->xoh2.info->proxy_ip;
	else	return -1;
}


int
s_close_XLoHTTP_2(STREAM * s)
{
	if ( s == 0 )
		return 0;
	if ( s->xoh2.info == 0 )
		return 0;
	__d_s_close(s->xoh2.info->upload_st,__FILE__,__LINE__);
	__d_s_close(s->xoh2.info->download_st,__FILE__,__LINE__);
	lock_task(XLoHTTP_lock);
	s->xoh2.info->upload_st = 0;
	s->xoh2.info->download_st = 0;
	for ( ; s->h.thread || (s->xoh2.info->flags & XoH_F2_CLOSE) == 0 ; ) {
		__d_s_close(s->xoh2.info->upload_st,__FILE__,__LINE__);
		__d_s_close(s->xoh2.info->download_st,__FILE__,__LINE__);
		s->xoh2.info->upload_st = 0;
		s->xoh2.info->download_st = 0;
		unlock_task(XLoHTTP_lock,"s_close_XLoHTTP");
		unlock_task(stream_lock,"s_close_XLoHTTP");
		sleep_sec(1);
		lock_task(stream_lock);
		lock_task(XLoHTTP_lock);
	}
	free_sbuf(&s->xoh2.info->upload_buf,SEM_NULL);
	d_f_ree(s->xoh2.info);
	s->xoh2.info = 0;
	unlock_task(XLoHTTP_lock,"s_close_XLoHTTP");
	return 0;
}


void
check_stream()
{
STREAM * s;
extern STREAM * stream_list;
extern S_TABLE s_XLoHTTP_table;
char * buf;
int ptr;
	lock_task(stream_lock);
	buf = d_alloc(1);
	ptr = 0;
	buf[0] = 0;
	for ( s = stream_list ; s ; s = s->h.next ) {
		if ( s->h.tbl == 0 )
			continue;
		if ( s->h.tbl == &s_XLoHTTP_2_table ) {
			if ( s->xoh2.info == 0 )
				continue;
			buf = d_re_alloc(buf,ptr+100);
			sprintf(&buf[ptr],"(V2,%s:%i)",
				s->xoh2.info->target_server,
				s->xoh2.info->target_port);
			ptr = strlen(buf);
		}
		else if ( s->h.tbl == &s_XLoHTTP_table ) {
			if ( s->xoh.info == 0 )
				continue;
			buf = d_re_alloc(buf,ptr+100);
			sprintf(&buf[ptr],"(V1,%s:%i)",
				s->xoh.info->target_server,
				s->xoh.info->target_port);
			ptr = strlen(buf);
		}
	}
	unlock_task(stream_lock,"check_stream");
	ss_printf("\n>>> %s\n\n",buf);
	d_f_ree(buf);
}


