/*
 *  ADP (Another Data Processor) www.adp.la
 *  Copyright (C) 2010 Katsuhisa Ohfuji <katsuhisa@ohfuji.name>
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License version 2 as
 *  published by the Free Software Foundation.
 *
 *  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., 51 Franklin Street, Fifth Floor, Boston,
 *  MA 02110-1301, USA.
 */
#ifndef KZ_NET_H
#define KZ_NET_H
using namespace std;

typedef map<string,string>	ssmap;
typedef pair<string,string>	sspair;

inline void convertssmap( char **envp, ssmap &envmap)
{
	while ( *envp ) {
		char *p = strchr( *envp, '=');
		if ( p ) {
			envmap.insert( sspair( string( *envp, p), string(p+1)) );
		} else {
			envmap.insert( sspair( string(*envp), string("")) );
		}
		envp++;
	}
}

static inline void urldecode( string::iterator begin, string::iterator end, string &decode)
{
	decode.clear();
	decode.reserve(end - begin);
	for ( string::iterator i = begin; i < end; i++ ) {
		if ( *i == '+' ) {
			decode.push_back(' ');
		} else if ( *i == '%' && i + 2 < end ) {
			char buf[3];
			buf[0] = *++i;
			buf[1] = *++i;
			buf[2] = 0;
			int result = strtol( buf, 0, 16);
			decode.push_back((char)(result & 0xff));
		} else {
			decode.push_back(*i);
		}
	}
}

static inline void urlencode( string::iterator begin, string::iterator end, string &encode)
{
	encode.clear();
	encode.reserve( (size_t)((end - begin) * 3) );
	for ( string::iterator i = begin; i < end; i++ ) {
		if ( isalpha(*i) || *i == '-' || *i == '_' || *i == '.' ) {
			encode.push_back(*i);
		} else {
			char	buf[16];
			unsigned int v = (*i & 0xff);
			sprintf( buf, "%%%02X", v);
			encode += buf;
		}
	}
}



static inline void scanquery( string &querystr, ssmap &query)
{
	size_t	pos = 0;
	while ( true ) {
		size_t pos1 = querystr.find( '=', pos);
		size_t pos2 = querystr.find( '&', pos);
		if ( pos2 == string::npos ) {
			pos2 = querystr.size();
		}
		if ( pos1 == string::npos || pos1 > pos2 ) pos1 = pos2;
		string name;
		string value;
		name.clear();
		value.clear();
		urldecode( querystr.begin() + pos, querystr.begin() + pos1, name);
		if ( pos1 < pos2 ) urldecode ( querystr.begin() + pos1 + 1, querystr.begin() + pos2, value);
		query.insert( sspair( name, value));
		if ( pos2 == querystr.size() ) break;
		pos = pos2 + 1;
	}
}

#if 0
static int scanmultipart( string &boundary, ssmap &query)
{
static const char tag[] = "Content-Disposition:";
	char		buf[4096];
	string		endboundary; /* 終了バウンダリ */
	char		*p, *q;
	int			headflg;	/* 1:head 0:data */
	int			dataflg;	/* 1:text 2:file */
	char		filepath[512];
	char		*filename;
	char		*name;
	char		*data;
	int			len;
	FILE		*fp;
	int			ret;

	/* バイナリモードにする */
#ifdef _WIN32	
	_setmode( _fileno( stdin ), _O_BINARY );
#endif

	endboundary = boundary + "--";

	/* バウンダリの読み飛ばし */
	dataflg = 0;
	name = NULL;
	data = NULL;
	len  = 0;
	while ( fgets( buf, sizeof(buf), stdin) != NULL ) {
		if ( memcmp( buf, boundary, strlen(boundary)) == 0 || memcmp( buf, endboundary, strlen(endboundary)) == 0 ) {
			if ( dataflg != 0 ) {	/* 変数の割り付け処理 */
				switch ( dataflg ) {
				case 1: /* text */
					ret = setValueByTbl( name, data, vtbl, num, vlist);
					break;
				case 2: /* file */
					fclose(fp);
					ret = setValueByTbl( name, filename, vtbl, num, vlist);
					break;
				default :
					ret = -100;
				}
			}
			headflg = 1;
			if ( name ) free(name); name = NULL;
			if ( data ) free(data); data = NULL;
			len = 0;
			if ( memcmp( buf, endboundary, strlen(endboundary)) == 0 ) {
				return 0;
			} else {
				continue;
			}
		}
		if ( headflg && strcmp( buf, "\r\n") == 0 ) {
			headflg = 0;
			continue;
		}
		if ( headflg ) { /* ヘッダ部として処理 */
			if ( memcmp( buf, tag, sizeof(tag)-1) == 0 ) {
				/* ファイル名の確認 */
				if ( (p = strstr( buf,"filename")) != NULL ) {
					if ( (p = strchr( p, '"')) != NULL && 
						(q = strchr( p + 1, '"')) != NULL ) {
						memcpy( filepath, p + 1, q - p - 1);
						filepath[q - p - 1] = '\0';
						if ( (filename = strrchr( filepath, '\\')) == NULL ) {
							filename = filepath;
						} else {
							filename++;
						}
						if ( (fp = fopen( filename, "wb")) != NULL ) {
							dataflg = 2;
						} else {
							return ERR_FILE;
						}
					}
				} else {
					dataflg = 1;
				}
				/* 変数名の確認 */
				if ( (p = strstr( buf,"name")) != NULL && 
					(p = strchr( p, '"')) != NULL && 
					(q = strchr( p + 1, '"')) != NULL &&
					(name = malloc( q - p + 1)) != NULL ) {
					memcpy( name, p + 1, q - p - 1);
					name[q - p - 1] = '\0';
				} else {
					dataflg = 0;
				}
			}
		} else if ( name != NULL ) {	/* データ部として処理 */
			switch ( dataflg ) {
			case 1: /* text */
				if ( (p = realloc( data, len + strlen(buf) + 1)) == NULL ) {
					return ERR_MEM;
				}
				data = p;
				strcpy( data + len, buf);
				len += strlen(buf);
				break;
			case 2:	/* file */
				if ( fputs( buf, fp) == EOF ) {
					fclose(fp);
					return ERR_FILE;
				}
				break;
			}
		}
	}

	return ERR_ENV;
}
#endif

static inline string postread(int size)
{
	vector<char>   buff(size);
	int		sofar = 0, got;

	do {
		got = fread( &buff[0] + sofar, 1, size - sofar, stdin);
		sofar += got;
	} while (got && sofar < size);
	return string( buff.begin(), buff.end() );
}


inline bool getqueryvalue(ssmap &query)
{
	char	*ptr = 0;
	char	*method = 0;
	char	*content_type = 0;
	int		content_length = 0;
	string	querystr;
	string  boundary;

	if ( (method = getenv("REQUEST_METHOD")) == 0 ) {
		return false;
	}

	if ( strcmp( method, "GET") == 0 ) {
		if ( (ptr = getenv("QUERY_STRING")) == 0 ) {
			return false;
		}
		querystr = string(ptr);
	} else if ( strcmp(method, "POST") == 0 ) {
		if ( (content_type = getenv("CONTENT_TYPE")) == 0 ) {
			return false;
		}
		if ( (ptr = getenv("CONTENT_LENGTH")) == 0 ) {
			return false;
		}
		content_length = atoi(ptr);
		if (strcmp( content_type, "application/x-www-form-urlencoded") == 0 ) {
			querystr = postread(content_length);
		} else if ( memcmp( content_type, "multipart/form-data", 19) == 0 ) {
			if ( (ptr = strchr( content_type, '=')) == 0 ) {
				return false;
			}
			while ( *++ptr == ' ' ); /* 空白飛ばし */
			boundary = "--";
			boundary += ptr;
		} else {
			return false;
		}
	}

	if ( !querystr.empty() ) {
		scanquery(querystr, query);
//	} else if ( !boundary.empty() ) {
//		ret = scanmultipart( boundary, query);
	} else {
		return false;
	}
	return true;
}

inline bool getcookievalue( ssmap &cookies)
{
	char	*cookiep = 0;
	if ( (cookiep = getenv("HTTP_COOKIE")) == 0 ) {
		return false;
	}
	string  cookie(cookiep);
	size_t	l = cookie.size();
	size_t	s = 0;
	while ( s < l ) {
		while ( isspace(cookie[s]) && s < l ) s++;
		size_t v = cookie.find( '=', s);
		if ( v == string::npos ) {
			v = l;
		}
		size_t n = cookie.find( ';', v);
		if ( n == string::npos ) {
			n = l;
		}
		string name;
		string value;
		urldecode( cookie.begin() + s, cookie.begin() + v, name);
		urldecode( cookie.begin() + v + 1, cookie.begin() + n, value);
		cookies.insert( sspair( name, value));
		s = n + 1;
	}
	return true;
}

inline int tcp_connect(SOCKET &sofd, const char* host, int port)
{
	struct sockaddr_in	sv_addr;
	struct hostent		*shost;

    /* ソッケトの作成 */
    sofd = socket( PF_INET, SOCK_STREAM, 0);
    if ( sofd == -1 ) {
		return -1;
    }

    /* サーバのホスト名からＩＰアドレスを得る */
    shost = gethostbyname(host);
    if ( shost == NULL ) {
	    closesocket(sofd);
        return -1;
    }

    /* サーバにconnect */
    memset((char *)&sv_addr, '\0', sizeof(sv_addr));
    sv_addr.sin_family      = PF_INET;
    sv_addr.sin_port        = htons(port);
    memcpy((char *)&sv_addr.sin_addr, (char *)(shost->h_addr), shost->h_length);
    if ( connect( sofd, (struct sockaddr *)&sv_addr, sizeof(sv_addr)) < 0 ) {
	    closesocket(sofd);
        return -1;
    }

	return 0;
}

inline int tcp_send_text(SOCKET &sofd, const string& sbuf)
{
	if ( sbuf.empty() ) return 0;

	int	len;
	/* リクエストの送信 */
	len = send( sofd, sbuf.c_str(), sbuf.length(), 0);
    if ( len < 0 ) {
        return -1;
    }

	return 0;
}

inline int tcp_recv_text(SOCKET &sofd, string& rbuf)
{
	int	len;
	/* レスポンスの受信 */
	rbuf.clear();
	rbuf.reserve(204800);
	char	buf[102400];
	while ( (len = recv( sofd, buf, sizeof(buf) - 1, 0)) > 0 ) {
		buf[len] = '\0';
		rbuf += buf;
	}

	return len >= 0 ? 0 : -1;
}

inline int tcp_recv_line(SOCKET &sofd, string& rbuf)
{
	char	buf[1];
	int		len = -1;
	rbuf.clear();
	rbuf.reserve(2048);
	while ( (len = recv( sofd, buf, 1, 0)) > 0 ) {
		rbuf.push_back(buf[0]);
		if ( buf[0] == '\n' ) return 0;
	}
	return len >= 0 ? 0 : -1;
}

inline int tcp_close(SOCKET &sofd) 
{
	closesocket(sofd);
	return 0;
}

inline int tcp_send_recv_text(const char* host, int port, string& sbuf, string& rbuf)
{
	SOCKET				sofd;

	if ( tcp_connect( sofd, host, port) != 0 ) return -1;
	if ( tcp_send_text( sofd, sbuf) != 0 ) return -1;
	if ( tcp_recv_text( sofd, rbuf) != 0 ) return -1;
	if ( tcp_close( sofd) != 0 ) return -1;
	return 0;
}

inline int html_request(const char *host, int port, const char* url, const char *method, ssmap values, string& header, string& body, ssmap& cookies, bool debugflg = false)
{
	/*FORM文字列*/
	string formstr("");
	string parameter("");
	string request_body("");
	for ( ssmap::iterator i = values.begin(); i != values.end(); i++ ) {
		formstr += cformat( "%s=%s&", i->first.c_str(), i->second.c_str());
	}
	if ( values.begin() != values.end() ) {
		formstr.resize( formstr.length() - 1 );
	}
	if ( strcmp( method, "GET") == 0 ) {
		if ( strcmp( formstr.c_str(), "") != 0 ) {
			parameter = "?" + formstr;
		}
	} else {
		request_body = formstr;
	}

	/* cookies */
	string request_cookies;
	for ( ssmap::iterator i = cookies.begin(); i != cookies.end(); i++ ) {
		if ( memcmp( i->second.c_str(), url, strlen(i->second.c_str())) == 0 ) {
			request_cookies += cformat ( "Cookie: %s\r\n", i->first.c_str());
		}
	}

	/* リクエストの作成 */
	const char *request_header=
		"%s %s%s HTTP/1.0\r\n"
		"Content-Type: application/x-www-form-urlencoded\r\n"
		"Host: %s\r\n"
		"%s"	// cookie
		"Content-Length: %d\r\n\r\n%s";
	string request = cformat( request_header, method, url, parameter.c_str(), host, request_cookies.c_str(), request_body.length(), request_body.c_str());

	string obuf;
	tcp_send_recv_text(host, port, request, obuf);

	if ( debugflg == true ) {
		fputs( obuf.c_str(), stderr);
	}

	/* レスポンスの分解 */
	string::size_type pos = obuf.find( "\r\n\r\n");
	if ( pos == string::npos ) {
		return -1;
	}
	header = obuf.substr( 0, pos + 2);
	body = obuf.substr(pos + 4);
	
	/* Cookieの解釈 */
	/**** CH Expireについて考慮していない *****/
	static const boost::regex header_set_cookie("^Set-Cookie:\\s*([^;$]+);(\\s*expires=[^;$]+;)*\\s*path=([^$\\s]+)\\s*$");
	string::const_iterator start = header.begin();
	string::const_iterator end = header.end();
	boost::smatch what;
	while( boost::regex_search(start, end, what, header_set_cookie) ) {
		cookies.insert( sspair(string(what[1].first, what[1].second), string(what[3].first, what[3].second)) );
		start = what[0].second;
	}

	return 0;
}

inline int udp_send( const char *host, int port, const char *buf, size_t siz)
{
	SOCKET 				sofd;
	struct sockaddr_in	sv_addr;
	struct hostent		*shost;

    /* ソッケトの作成 */
    sofd = socket( PF_INET, SOCK_DGRAM, 0);
    if ( sofd == -1 ) {
		return -1;
    }
    int opt = 1;
	setsockopt(sofd, SOL_SOCKET, SO_BROADCAST, (char *)&opt, sizeof(opt));
	
	/* UDPパケットの送信 */

    /* 相手先のホスト名からＩＰアドレスを得る */
    shost = gethostbyname(host);
    if ( shost == NULL ) {
	    closesocket(sofd);
        return -1;
    }

    /* 相手先にUDPパケットを送信 */
    memset((char *)&sv_addr, '\0', sizeof(sv_addr));
    sv_addr.sin_family      = PF_INET;
    sv_addr.sin_port        = htons(port);
    memcpy((char *)&sv_addr.sin_addr, (char *)(shost->h_addr), shost->h_length);

	int sendlen = 0;
	size_t totallen = 0;
    while ( (sendlen = sendto( sofd, buf, siz, 0, (struct sockaddr *)&sv_addr, sizeof(sv_addr))) > 0 ) {
		totallen += sendlen;
		if ( totallen >= siz ) break;
    }
	if ( sendlen < 0 ) {
        return -1;
	}

	closesocket(sofd);
	return 0;
}

inline string base64encord( const char *src)
{
	static char	etbl[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
	string dst;

	while ( *src ) {
		unsigned long eval = 0;
		int cnt = 0;
		while ( cnt < 3 && *src ) {
			eval <<= 8;
			eval |= *src++;
			cnt++;
		}
		eval <<= 8 * (3 - cnt);
		dst.push_back( etbl[(eval >> 18) & 0x3f]);
		dst.push_back( etbl[(eval >> 12) & 0x3f]);
		if ( cnt <= 1 ) {
			dst.push_back('=');
		} else {
			dst.push_back(etbl[(eval >>  6) & 0x3f]);
		}
		if ( cnt <= 2 ) {
			dst.push_back('=');
		} else {
			dst.push_back(etbl[eval & 0x3f]);
		}
	}
	return dst;
}

inline string base64decord( const char *src )
{
	static char dtbl[] =
	"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
	"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
	"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x3e\x00\x00\x00\x3f"
	"\x34\x35\x36\x37\x38\x39\x3a\x3b\x3c\x3d\x00\x00\x00\x00\x00\x00"
	"\x00\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e"
	"\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x00\x00\x00\x00\x00"
	"\x00\x1a\x1b\x1c\x1d\x1e\x1f\x20\x21\x22\x23\x24\x25\x26\x27\x28"
	"\x29\x2a\x2b\x2c\x2d\x2e\x2f\x30\x31\x32\x33\x00\x00\x00\x00\x00"
	"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
	"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
	"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
	"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
	"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
	"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
	"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
	"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00";

	string			dst;
	unsigned int	dval = 0;
	unsigned int	oval;
	int	len = 0;
	int scnt = 0;

	while ( *src && *src != '=' ) {
		dval <<= 6;
		dval |= dtbl[*src];
		scnt += 6;
		if ( scnt >= 8 ) {
			oval = dval >> (scnt - 8);
			dval &= (1 << ((scnt - 8) + 1)) - 1;
			dst.push_back(oval);
			len++;
			scnt -= 8;
		}
		src++;
	}
	return dst;
}


inline bool smtp_check_response(SOCKET sofd, int expect)
{
	string res;
	if ( tcp_recv_line(sofd, res) < 0 ) return false;
	if ( atoi(res.c_str()) != expect ) return false;
	
	return true;
}

inline bool smtp_send_crlf(SOCKET sofd)
{
	string	crlf = "\r\n";
	if ( tcp_send_text( sofd, crlf) != 0 ) return false;
	return true;
}

inline bool smtp_send_line(SOCKET sofd, const string &cmd, const string &param)
{
	if ( tcp_send_text( sofd, cmd) != 0 ) return false;
	if ( tcp_send_text( sofd, param) != 0 ) return false;
	return smtp_send_crlf(sofd);
}

inline bool smtp_send_command(SOCKET sofd, const string &cmd, const string &param, int expect)
{
	if ( !smtp_send_line( sofd, cmd, param) ) return false;
	return smtp_check_response( sofd, expect);
}

inline bool smtp_send_header_b64encode( SOCKET sofd, const string &cmd, const string &param, const string &charset) {

	string b64param = " =?" + charset + "?B?" + base64encord(param.c_str()) + "?=";
	if ( !smtp_send_line( sofd, cmd, b64param) ) return false;
	return true;
}

inline bool smtp_send_mail_text(SOCKET sofd, const string &to, const string &cc, const string &subject, const string &text, const string &charset ) {

	if ( !to.empty() && !smtp_send_header_b64encode( sofd, "To:", to, charset) ) return false;
	if ( !cc.empty() && !smtp_send_header_b64encode( sofd, "Cc:", cc, charset) ) return false;
	if ( !subject.empty() && !smtp_send_header_b64encode( sofd, "Subject:", subject, charset ) ) return false;
	if ( !smtp_send_line( sofd, "MIME-Version:", " 1.0") ) return false;
	if ( !smtp_send_line( sofd, "Content-Type:", " text/plain; charset=" + charset ) ) return false;
	if ( !smtp_send_line( sofd, "Content-Transfer-Encoding:", " base64") ) return false;
	if ( !smtp_send_crlf( sofd) ) return false;
	if ( tcp_send_text( sofd, base64encord(text.c_str())) != 0 ) return false;
	if ( !smtp_send_crlf( sofd) ) return false;
	if ( send( sofd, ".", 1, 0) != 1 ) return false;
	if ( !smtp_send_crlf( sofd) ) return false;

	return smtp_check_response(sofd, 250);
}

inline bool sendmail( const string &smtpserver, const string &to, const string &cc, const string &bcc, const string &from, const string &subject, const string &text, const string &charset ) {
	SOCKET	sofd;
	bool	result = false;

	if ( tcp_connect( sofd, smtpserver.c_str(), 25) != 0 ) return false;

	char	hostname[256];
	if ( gethostname( hostname, sizeof(hostname)) != 0 ) return false;

	string recept_str = to;
	if ( !cc.empty() ) recept_str = recept_str + "," + cc;
	if ( !bcc.empty() ) recept_str = recept_str + "," + bcc;
	vector<string>	recpt_to;
	size_t			off = 0;
	size_t			fnd = 0;
	while ( (fnd = recept_str.find( ',', off)) != string::npos ) {
		string recept(recept_str.begin() + off, recept_str.begin() + fnd);
		recpt_to.push_back(recept);
		off = fnd + 1;
	}
	string recept( recept_str.begin() + off, recept_str.end());
	recpt_to.push_back(recept);

    if ( !smtp_check_response( sofd, 220) ) goto error_out;
    if ( !smtp_send_command( sofd, "HELO ", hostname, 250) ) goto error_out;
    if ( !smtp_send_command( sofd, "MAIL FROM:", from, 250) ) goto error_out;

	for ( vector< string >::iterator i = recpt_to.begin(); i < recpt_to.end(); i++ ) {
        if ( !smtp_send_command( sofd, "RCPT TO:", (*i).c_str(), 250) ) goto error_out;
	}
    if ( !smtp_send_command( sofd, "DATA", "", 354) ) goto error_out;
    if ( !smtp_send_mail_text( sofd, to, cc, subject, text, charset) ) goto error_out;
    if ( !smtp_send_command( sofd, "QUIT", "", 221) ) goto error_out;
    result = true;
error_out:
	tcp_close(sofd);
	return result;
}


#endif
