#include "stdafx.h"

//#pragma warning(push, 1)
//#include <boost/filesystem/fstream.hpp>
//#pragma warning(disable: 4701)
//#include <boost/lexical_cast.hpp>
//#pragma warning(pop)
//#include <boost/filesystem/operations.hpp>

//#include <winsock2.h>
//#include <windows.h>
//#include <process.h>

//#include <boost/regex.hpp>

//#include "socket.h"
//#include "main.h"
//#include "board.h"
//#include "key.h"
//#include "debug.h"
#include "zlib/zlib.h"
#include "fasthash/MakeSHA1.h"

using namespace std;
using boost::lexical_cast;

const int BUF_LEN = 8192;
const unsigned short DEF_PROXY_PORT = 8000;

//#define STRFOUND(s)		((s)!=string::npos)

extern HANDLE			proxy_listen_thread_handle;
//extern Board			*board;
//extern KeyDB			*mykey;
//extern unsigned short	proxy_port;
//extern unsigned short	p2p_port;
//extern string			trip_name;
//extern int				proxy_accept_all;

void __cdecl proxy_thread(void *data);
bool gzip_uncompress(const vector<char>& src, vector<char>& dest);

int hex2int(const char *s)
{
	int val=0;

	while ((*s >= '0' && *s <= '9')
			|| (*s >= 'a' && *s <= 'f')
			|| (*s >= 'A' && *s <= 'F')) {

		val <<= 4;
		if (*s >= '0' && *s <= '9') val += (*s & 0x0f);
		else val += ((*s & 0x0f) + 9);
		s++;
	}
	return val;
}

//**************************************************************************
//*
//* proxy_listen_thread
//*	ブラウザからの接続を待つスレッド
//*
//**************************************************************************
unsigned int __stdcall proxy_listen_thread(void * /*data*/)
{
	unsigned long inaddr = 0x7f000001;
	//if (proxy_accept_all)
	//	inaddr = INADDR_ANY;

	unsigned short proxy_port;
	try {
		proxy_port = lexical_cast<unsigned short>(
			getIniString("o2on", "proxy_port", "").c_str());
	} catch (boost::bad_lexical_cast&) {
		proxy_port = DEF_PROXY_PORT;
	}

	TCPSocket listening_socket;
	if (!listening_socket.listen(proxy_port, inaddr)) {
		MessageBox(NULL, "PROXYポートのオープンに失敗しました\n設定を変更して再起動してください",
			"o2on", MB_OK);
		return 0;
	}

	//dprint("[PROXY] listen start (port:%d)\n", proxy_port);

	while (do_thread) {
		if (!listening_socket.readable(3, 0))
			continue;

		SOCKET new_socket = listening_socket.accept();

		if (new_socket == INVALID_SOCKET)
			continue;

		//dprint("[PROXY] incoming new client (s:%d addr:%d.%d.%d.%d)\n",
		//	new_socket,
		//	static_cast<unsigned char>( sin.sin_addr.s_addr		&0x000000ff),
		//	static_cast<unsigned char>((sin.sin_addr.s_addr >>	8)&0x000000ff),
		//	static_cast<unsigned char>((sin.sin_addr.s_addr >> 16)&0x000000ff),
		//	static_cast<unsigned char>((sin.sin_addr.s_addr >> 24)&0x000000ff));

		// ↓はいらないはず
		//if (!proxy_accept_all && sin.sin_addr.S_un.S_addr != 0x0100007f) {
		//	// ローカル以外からの接続を弾く
		//	dprint("[PROXY]: FORCE CLOSE CONNECTION\n");
		//	shutdown(new_socket, 1);
		//	closesocket(new_socket);
		//	continue;
		//}

		// proxyスレッドを起動
		SOCKET *client_socket = new SOCKET(new_socket);	// proxy_threadのなかでdeleteされる
		_beginthread(proxy_thread, 0, client_socket);
	}

	return 0;
}

//**************************************************************************
//*
//* proxy_thread
//*
//*
//**************************************************************************
void __cdecl proxy_thread(void *data)
{
	SOCKET *new_socket = static_cast<SOCKET*>(data);
	TCPSocket client_socket(*new_socket);
	delete new_socket;

	dprint("***** thread START *****\n");

	//*****************************************************
	// クライアントからHTTPリクエスト文を受信
	//*****************************************************
	int		pos1, pos2;
	int		body_start_pos = 0;
	int		read_size = 0;
	int		content_length = 0;
	bool	head_flag = true;
	char	buf[BUF_LEN];
	string	httpstr;

	for (;;) {
		if (!client_socket.readable(3, 0))
			return;

		int len = client_socket.recv(buf, sizeof buf);
		if (len < 0)
			return;

		httpstr.append(buf, len);
		read_size += len;

		if (head_flag) {
			//==========================
			//リクエストヘッダの読み込み
			//==========================
			body_start_pos = httpstr.find("\r\n\r\n");
			if (!strfound(body_start_pos))
				continue;

			body_start_pos += 4;
			pos1 = httpstr.find("Content-Length: ");
			if (strfound(pos1)) {
				pos1 += 16;
				pos2 = httpstr.find("\r", pos1);
				if (strfound(pos2)) {
					try {
						content_length = lexical_cast<int>(httpstr.substr(pos1, pos2-pos1));
					}
					catch (boost::bad_lexical_cast &) {
						content_length = 0;
					}
					head_flag = false;
				} else {
					content_length = 0;
					break;
				}
			} else {
				//BODYがないので読み込み終了
				content_length = 0;
				break;
			}
		}
		//==========================
		//リクエストBODYの読み込み
		//==========================
		if (read_size - body_start_pos >= content_length) {
			break;
		}
	}

	// Connection: close を付加
	httpstr.insert(httpstr.size() - 2 - content_length, "Connection: close\r\n");

	if (httpstr.empty())
		return;

	dprint("*********\n");
	dprint("*REQUEST*\n");
	dprint("*********\n");
	dprint("%s\n", httpstr.c_str());

	//*****************************************************
	// リクエストの1行目からホスト名などを取得
	//*****************************************************
	//メソッドを取得
	pos1 = httpstr.find(" ");
	if (pos1 == string::npos) return;
	string request_method = httpstr.substr(0, pos1);
	pos1++;

	//URIを取得
	pos2 = httpstr.find(" ", pos1);
	if (pos2 == string::npos) return;

	string uri = httpstr.substr(pos1, pos2-pos1);
	int uri_start = pos1;
	int uri_end = pos2;

	//URIからホスト名を抽出
	pos1 = uri.find("//");
	if (pos1 == string::npos) return;
	pos1 += 2;

	pos2 = uri.find("/", pos1);
	if (pos2 == string::npos) return;

	string host = uri.substr(pos1, pos2-pos1);

	//URIからパス名を抽出
	string path = uri.substr(pos2);

	//ポートが指定されているか調べる
	pos1 = host.find(":");
	int port = 0;
	if (pos1 != string::npos) {
		string port_str = host.substr(pos1+1);
		port = atoi(port_str.c_str());
		host.erase(pos1);
	}

	dprint("URI:%s\nHOST:%s\nPORT:%d\nPATH:[%s]\n", uri.c_str(), host.c_str(), port, path.c_str());

	bool datfile = true;
	boost::smatch results;
	{
		static const boost::regex regex("http://(.+?)(?:\\:\\d+)?/(?:[^/]+/)*([^/]+?)/dat/(\\d{9,10})\\.dat");
		if (!boost::regex_search(uri, results, regex))
			datfile = false;
	}

	//*****************************************************
	// ホストに接続しリクエストを投げる
	//*****************************************************

	// サーバに接続
	if (port == 0)
		port = 80;

	// ソケット生成
	TCPSocket	server_socket;
	if (!server_socket.connect(host.c_str(), static_cast<unsigned short>(port))) {
		dprint("connect に失敗しました。\n");
		return;
	}

	// HTTP プロトコル生成 & サーバに送信
	httpstr.replace(uri_start, uri_end - uri_start, path);
	int ret = server_socket.send(httpstr.data(), httpstr.size());

	dprint("[[[%s]]]\n",httpstr.c_str());
	dprint("send ret:%d\n", ret);

	//*****************************************************
	// ホストからコンテンツを受信してクライアントに投げる
	//*****************************************************
	httpstr.erase();
	bool reading = true;
	bool gzip_flag = false;
	bool chunked_flag = false;
	int current_chunk_size = -1;
	int readed_chunk_size = 0;
	string lastModified, range, resStatus;
	read_size = 0;
	content_length = 0;
	head_flag = true;
	vector<char> body;

	while (reading) {
		// 5分でタイムアウト
		if (!server_socket.readable(300, 0))
			break;

		int len = server_socket.recv(buf, sizeof buf);
		if (len <= 0)
			break;

		client_socket.send(buf, len);
		read_size += len;

		if (head_flag) {
			//======================
			//レスポンスヘッダの処理
			//======================
			httpstr += buf;
			body.insert(body.end(), &buf[0], &buf[len]);

			body_start_pos = httpstr.find("\r\n\r\n");
			if (!strfound(body_start_pos))
				continue;

			body_start_pos += 4;
			dprint("*****************\n");
			dprint("*RESPONSE HEADER*\n");
			dprint("*****************\n");
			dprint("%s", httpstr.substr(0,body_start_pos).c_str());

			//Transfer-Encodingを特定
			pos1 = httpstr.find("Transfer-Encoding: ");
			if (strfound(pos1)) {
				pos1 += 19;
				pos2 = httpstr.find("\r", pos1);
				if (strfound(pos2)
					&& httpstr.substr(pos1,pos2-pos1) == "chunked") {
					chunked_flag = true;
					//current_chunk_size = -1;
					//readed_chunk_size = 0;
				}
			}
			//Content-Encodingを特定
			pos1 = httpstr.find("Content-Encoding: ");
			if (strfound(pos1)) {
				pos1 += 18;
				pos2 = httpstr.find("\r", pos1);
				if (strfound(pos2)
					&& httpstr.substr(pos1,pos2-pos1) == "gzip") {
					gzip_flag = true;
				}
			}
			//Last-Modifiedを特定
			pos1 = httpstr.find("Last-Modified: ");
			if (strfound(pos1)) {
				pos1 += 15;
				pos2 = httpstr.find("\r", pos1);
				if (strfound(pos2)) {
					lastModified.assign(httpstr, pos1, pos2 - pos1);
				}
			}
			//Content-Range: bytes を特定
			pos1 = httpstr.find("Content-Range: bytes ");
			if (strfound(pos1)) {
				pos1 += 21;
				pos2 = httpstr.find("\r", pos1);
				if (strfound(pos2)) {
					range.assign(httpstr, pos1, pos2 - pos1);
				}
			}
			//Response Statusを特定
			pos1 = httpstr.find("HTTP/1.1 ");
			if (strfound(pos1)) {
				pos1 += 9;
				pos2 = httpstr.find("\r", pos1);
				if (strfound(pos2)) {
					resStatus.assign(httpstr, pos1, pos2 - pos1);
				}
			}
			//Content-Lengthを特定
			pos1 = httpstr.find("Content-Length: ");
			if (strfound(pos1)) {
				pos1 += 16;
				pos2 = httpstr.find("\r", pos1);
				if (strfound(pos2)) {
					try {
						content_length = lexical_cast<int>(httpstr.substr(pos1, pos2-pos1));
					}
					catch (boost::bad_lexical_cast &) {
						content_length = 0;
					}
				} else {
					content_length = 0;
					break;
				}
			} else if (!chunked_flag) {
				//BODYがないので読み込み終了
				content_length = 0;
				break;
			}

			head_flag = false;
			httpstr.erase(body_start_pos);
			body.erase(body.begin(), body.begin() + body_start_pos);
		} else if (datfile || chunked_flag) {
			// datfileかchunkedで送られてきた時はメモリに読み込む
			body.insert(body.end(), &buf[0], &buf[len]);
		}

		//====================
		//レスポンスBODYの処理
		//====================
		if (chunked_flag) {
			//chunked
			for (;;) {
				if (current_chunk_size == -1) {
					body.push_back('\0');
					char *p = strstr(&body[readed_chunk_size], "\r\n");

					if (p) {
						current_chunk_size = hex2int(&body[readed_chunk_size]);
						dprint("current_chunk_size:%d\n", current_chunk_size);

						body.erase(body.begin() + readed_chunk_size,
							body.begin() + readed_chunk_size + (p - &body[readed_chunk_size]) + 2);
						if (current_chunk_size == 0) {
							//body.erase(body.begin());
							reading = false; //chunksize=0なので終了
							break;
						}
					}
					body.pop_back();
				}
				if (current_chunk_size != -1
					&& (body.size() - readed_chunk_size)
						>= static_cast<unsigned>(current_chunk_size) + 2) {

					readed_chunk_size += current_chunk_size;
					current_chunk_size = -1;
					// chunk末尾のCRFLを削る
					body.erase(body.begin() + readed_chunk_size,
						body.begin() + readed_chunk_size + 2);
					continue;
				} else {
					break; //chunkループ終了
				}
			}

		} else if (read_size - body_start_pos >= content_length) {
			//not chunked
			break;
		}
	}

	if (!datfile) // datファイルでない場合はここでreturn
		return;

	//server_socket.closeSocket();
	//client_socket.closeSocket();

	if (content_length) {
		dprint("***************\n");
		dprint("*RESPONSE BODY*\n");
		dprint("***************\n");
		if (gzip_flag) dprint("(gzip size %d)\n", body.size());
		//else dprint("%s", &body[0]);
	}

	//*****************************************************
	// 受信した情報をキーDBに入れる
	//*****************************************************
	if (request_method == "GET") {
		if (resStatus.empty() || resStatus[0] != '2') {
			// status codeが200系以外ならキャッシュを更新しない
			return;
		}

		string server(results.str(1)), board(results.str(2)), thread(results.str(3));
		size_t pos = server.find("2ch.net");
		if (strfound(pos)) {
			server.erase(server.begin(), server.begin() + pos);
		}

		string keyname(server + "/" + board + "/" + thread);

		CMakeSHA1 sha1;
		sha1.ComputeHash(keyname.c_str(), keyname.size());
		char keyhash[40 + 1];
		sha1.GetHashString(keyhash, TRUE);

		if (gzip_flag) {
			vector<char> ungzip;
			if (!gzip_uncompress(body, ungzip)) {
				MessageBox(NULL, "UngzipError", "o2on", MB_OK);
				return;
			}
			body.assign(ungzip.begin(), ungzip.end());
		}

		namespace fs = boost::filesystem;
		fs::path cachefile(fs::path(DATDIR) / keyhash);

		if (range != "") {
			// キャッシュを読み込む
			fs::ifstream ifs(cachefile, ios::binary);
			if (!ifs.fail()) {
				char buffer[1024];
				// とりあえずヘッダ部を読み飛ばしておく
				while (!ifs.eof()) {
					ifs.getline(buffer, sizeof buffer);
					if (buffer[0] == '\0') {
						break;
					}
				}
				vector<char> cachedbody((istreambuf_iterator<char>(ifs)),
					istreambuf_iterator<char>());

				// range が正しくなかったらキャッシュを破棄
				try {
					size_t start = lexical_cast<size_t>(range.substr(0, range.find("-")));
					size_t bodysize = lexical_cast<size_t>(range.substr(range.find("/") + 1));

					if (cachedbody.size() < start) {
						throw exception();
					} else if (cachedbody.size() > start) {
						cachedbody.erase(cachedbody.begin() + start, cachedbody.end());
					}

					body.reserve(body.size() + cachedbody.size());
					body.insert(body.begin(), cachedbody.begin(), cachedbody.end());

					if (body.size() != bodysize) {
						throw exception();
					}
				} catch (exception&) {
					ifs.close();
					try {
						fs::remove(cachefile);
					} catch (exception&) {}

					return;
				}
			} else {
				// 途中から読み込んだときキャッシュがない場合は無視
				return;
			}
		}

		sha1.ComputeHash(&body[0], body.size());
		char bodysha1[40 + 1];
		sha1.GetHashString(bodysha1, TRUE);

		// キャッシュ書き出し
		fs::ofstream ofs(cachefile, ios::binary);
		if (!ofs.fail()) {
			ofs << "Debug: Content-Range: bytes " << range << "\n";
			ofs << "Debug: keyinfo: " << keyname << "\n";
			ofs << "Cache-Version: 0.02\n"
				<< "Hash-SHA1: " << bodysha1 << "\n"
				<< "Last-Modified: " << lastModified << "\n"
				<< "Content-Length: " << body.size() << "\n\n";
			copy(body.begin(), body.end(), ostreambuf_iterator<char>(ofs));
		}


		// なんとなくキー追加
		//Key		key;
		//strncpy(key.core.site, server.c_str(), 31);
		//key.core.site[31] = '\0';

		//strcpy(key.core.category, "ｶﾃｺﾞﾘ('A`)ﾏﾝﾄﾞｸｾ");

		//strncpy(key.core.board, board.c_str(), 31);
		//key.core.board[31] = '\0';

		//try {
		//	key.core.surekey = lexical_cast<time_t>(thread);
		//} catch (boost::bad_lexical_cast&) {
		//	key.core.surekey = 0;
		//}
		//key.core.node.ip = 0;
		//key.core.node.port = p2p_port;

		//key.res_num = 1000;
		//key.last_modified = time(NULL);

		//key.ver_major = 0;
		//key.ver_minor = 1;

		//mykey->insert(key);
	}

	return;
}

//**************************************************************************
//*
//* gzip_uncompress
//*
//*
//**************************************************************************
#define ASCII_FLAG		0x01 /* bit 0 set: file probably ascii text */
#define HEAD_CRC		0x02 /* bit 1 set: header CRC present */
#define EXTRA_FIELD		0x04 /* bit 2 set: extra field present */
#define ORIG_NAME		0x08 /* bit 3 set: original file name present */
#define COMMENT			0x10 /* bit 4 set: file comment present */
#define RESERVED		0xE0 /* bits 5..7: reserved */

bool gzip_uncompress(const vector<char>& src, vector<char>& dest)
{
	const static char gz_magic[2] = {'\x1f', '\x8b'};

	if (src[0] != gz_magic[0] || src[1] != gz_magic[1]) { // not gzip
		return false;
	}

	char flags = src[3];
	if (src[2] != Z_DEFLATED || flags & RESERVED) { // not deflate
		return false;
	}

	int start = 10;
	if (flags & EXTRA_FIELD) { // skip the extra field
		int len = src[start++];
		len += src[start++] << 8;
		start += len;
	}
	if (flags & ORIG_NAME) { // skip the original file name
		while (src[start++] != '\0');
	}
	if (flags & COMMENT) { // skip the .gz file comment
		while (src[start++] != '\0');
	}
	if (flags & HEAD_CRC) { // skip the header crc
		start += 2;
	}

	vector<char> srctemp(src.begin() + start, src.end());

	z_stream z;
	z.zalloc = Z_NULL;
	z.zfree = Z_NULL;
	z.opaque = Z_NULL;
	z.next_in = Z_NULL;
	z.avail_in = 0;
	if (inflateInit2(&z, -MAX_WBITS) != Z_OK) {
		return false;
	}

	z.next_in = reinterpret_cast<Bytef*>(const_cast<char*>(&srctemp[0]));
	z.avail_in = srctemp.size();
	char buf[8 * 1024];
	z.next_out = reinterpret_cast<Bytef*>(buf);
	z.avail_out = sizeof buf;

	dest.clear();

	int status;
	do {
		status = inflate(&z, Z_NO_FLUSH);
		if (status == Z_OK || status == Z_STREAM_END) {
			dest.insert(dest.end(), &buf[0], &buf[sizeof buf - z.avail_out]);
			z.next_out = reinterpret_cast<Bytef*>(buf);
			z.avail_out = sizeof buf;
		}
	} while (status == Z_OK);

	inflateEnd(&z);
	return status == Z_STREAM_END;
}
