#include "http_client.h"
#include "stdafx.h"

static const char *cert_data =
"-----BEGIN CERTIFICATE-----\n"\
"MIIENjCCAx6gAwIBAgIBATANBgkqhkiG9w0BAQUFADBvMQswCQYDVQQGEwJTRTEU\n"\
"MBIGA1UEChMLQWRkVHJ1c3QgQUIxJjAkBgNVBAsTHUFkZFRydXN0IEV4dGVybmFs\n"\
"IFRUUCBOZXR3b3JrMSIwIAYDVQQDExlBZGRUcnVzdCBFeHRlcm5hbCBDQSBSb290\n"\
"MB4XDTAwMDUzMDEwNDgzOFoXDTIwMDUzMDEwNDgzOFowbzELMAkGA1UEBhMCU0Ux\n"\
"FDASBgNVBAoTC0FkZFRydXN0IEFCMSYwJAYDVQQLEx1BZGRUcnVzdCBFeHRlcm5h\n"\
"bCBUVFAgTmV0d29yazEiMCAGA1UEAxMZQWRkVHJ1c3QgRXh0ZXJuYWwgQ0EgUm9v\n"\
"dDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALf3GjPm8gAELTngTlvt\n"\
"H7xsD821+iO2zt6bETOXpClMfZOfvUq8k+0DGuOPz+VtUFrWlymUWoCwSXrbLpX9\n"\
"uMq/NzgtHj6RQa1wVsfwTz/oMp50ysiQVOnGXw94nZpAPA6sYapeFI+eh6FqUNzX\n"\
"mk6vBbOmcZSccbNQYArHE504B4YCqOmoaSYYkKtMsE8jqzpPhNjfzp/haW+710LX\n"\
"a0Tkx63ubUFfclpxCDezeWWkWaCUN/cALw3CknLa0Dhy2xSoRcRdKn23tNbE7qzN\n"\
"E0S3ySvdQwAl+mG5aWpYIxG3pzOPVnVZ9c0p10a3CitlttNCbxWyuHv77+ldU9U0\n"\
"WicCAwEAAaOB3DCB2TAdBgNVHQ4EFgQUrb2YejS0Jvf6xCZU7wO94CTLVBowCwYD\n"\
"VR0PBAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wgZkGA1UdIwSBkTCBjoAUrb2YejS0\n"\
"Jvf6xCZU7wO94CTLVBqhc6RxMG8xCzAJBgNVBAYTAlNFMRQwEgYDVQQKEwtBZGRU\n"\
"cnVzdCBBQjEmMCQGA1UECxMdQWRkVHJ1c3QgRXh0ZXJuYWwgVFRQIE5ldHdvcmsx\n"\
"IjAgBgNVBAMTGUFkZFRydXN0IEV4dGVybmFsIENBIFJvb3SCAQEwDQYJKoZIhvcN\n"\
"AQEFBQADggEBALCb4IUlwtYj4g+WBpKdQZic2YR5gdkeWxQHIzZlj7DYd7usQWxH\n"\
"YINRsPkyPef89iYTx4AWpb9a/IfPeHmJIZriTAcKhjW88t5RxNKWt9x+Tu5w/Rw5\n"\
"6wwCURQtjr0W4MHfRnXnJK3s9EK0hZNwEGe6nQY1ShjTK3rMUUKhemPR5ruhxSvC\n"\
"Nr4TDea9Y355e6cJDUCrat2PisP29owaQgVR1EX1n6diIWgVIEM8med8vSTYqZEX\n"\
"c4g/VhsxOBi0cQ+azcgOno4uG+GMmIPLHzHxREzGBHNJdmAPx/i9F4BrLunMTA5a\n"\
"mnkPIAou1Z5jJh5VkpTYghdae9C8x49OhgQ=\n"\
"-----END CERTIFICATE-----\n";

static const char *APIserver = "developer.api.bitcasa.com:443";
static const char *APIHost = "developer.api.bitcasa.com";

std::random_device rd;
std::mt19937_64 gen(rd());


HttpsClient::HttpsClient()
{
	/* Set up the library */

	ERR_load_BIO_strings();
	SSL_load_error_strings();
	OpenSSL_add_all_algorithms();
	SSL_library_init();

	/* Set up the SSL context */

	ctx = SSL_CTX_new(SSLv23_client_method());
	if (ctx == NULL) {
		ERR_print_errors_fp(stderr);
		exit(1);
	}
	SSL_CTX_set_options(ctx, SSL_OP_NO_SSLv2);
	SSL_CTX_set_timeout(ctx, 180);

	/* Load the trust store */

	cbio = BIO_new_mem_buf((void*)cert_data, -1);
	cert = PEM_read_bio_X509(cbio, NULL, 0, NULL);

	X509_STORE_add_cert(ctx->cert_store, cert);

	/* Setup the connection */

	bio = BIO_new_buffer_ssl_connect(ctx);
	if (true){
		BIO *bio_logger = BIO_new_filelogger(_T("trace.log"));
		bio = BIO_push(bio_logger, bio);
	}
	struct timeval timeout;
	timeout.tv_sec = 180;
	timeout.tv_usec = 0;
	BIO_ctrl(bio, BIO_CTRL_DGRAM_SET_RECV_TIMEOUT, 0, &timeout);

	/* Set the SSL_MODE_AUTO_RETRY flag */

	BIO_get_ssl(bio, &ssl);
	SSL_set_mode(ssl, SSL_MODE_AUTO_RETRY);

	/* Create and setup the connection */

	BIO_set_conn_hostname(bio, APIserver);

	if (BIO_do_connect(bio) <= 0)
	{
		fprintf(stderr, "Error attempting to connect\n");
		ERR_print_errors_fp(stderr);
		BIO_free_all(bio);
		SSL_CTX_free(ctx);
		exit(2);
	}

	/* Check the certificate */

	if (SSL_get_verify_result(ssl) != X509_V_OK)
	{
		fprintf(stderr, "Certificate verification error: %i\n", SSL_get_verify_result(ssl));
		BIO_free_all(bio);
		SSL_CTX_free(ctx);
		exit(2);
	}

}

HttpsClient::~HttpsClient()
{
	/* Close the connection and free the context */
	SSL_SESSION_free(sess);
	BIO_free_all(cbio);
	X509_free(cert);
	BIO_free_all(bio);
	SSL_CTX_free(ctx);
}


int HttpsClient::send()
{
	/* Send the request */
	AddHeader("Host", APIHost);

	if (request.body.size() > 0) {
		AddHeader("Content-Length", dynamic_cast<std::stringstream &>(std::stringstream() << request.body.size()).str());
	}

	for (int retry = 0; retry < 5; retry++){
		BIO_puts(bio, request.line.c_str());
		for (auto it = request.header.begin(); it != request.header.end(); ++it){
			BIO_puts(bio, (it->first + ": " + it->second + "\r\n").c_str());
		}
		BIO_puts(bio, "\r\n");
		if (request.body.size() > 0) {
			BIO_write(bio, &request.body[0], (int)request.body.size());
		}
		BIO_flush(bio);

		/* Read in the response */
		response.line = ReadLine();
		//012345678901234
		//HTTP/1.1 200 OK
		if (response.line.empty()) {
			BIO_reset(bio);
			continue;
		}
		std::stringstream(response.line.substr(9, 3)) >> response.code;
		response.reason = response.line.substr(13);

		std::string head_line = ReadLine();
		while (head_line != "\r\n") {
			auto sp = head_line.find_first_of(':');
			response.header.insert(std::make_pair(head_line.substr(0, sp), head_line.substr(sp + 2, head_line.length() - 4 - sp)));
			head_line = ReadLine();
		}
		INT64 ContentLength = -1;
		auto it_length = response.header.find("Content-Length");
		std::stringstream((it_length != response.header.end())? it_length->second: "") >> ContentLength;
		if (ContentLength == 0) {
			response.body.resize(0);
			return 0;
		}
		bool tempread = true;
		if (ContentLength > 0) {
			response.body.resize(ContentLength);
			tempread = false;
		}
		size_t len = 0;
		if (tempread) {
			buffer.resize(4096);
			response.body.resize(0);
			while ((ContentLength < 0) || (len >= ContentLength)){
				int p = BIO_read(bio, &buffer[0], (int)buffer.size());
				if (p <= 0){
					break;
				}
				response.body.resize(len + p);
				memcpy(&buffer[0], &response.body[len], p);
				len += p;
			}
		}
		else {
			int p = BIO_read(bio, &response.body[0], (int)response.body.size());
			if (p <= 0) {
				response.body.resize(0);
			}
			else {
				if (p < response.body.size())
					response.body.resize(p);
			}
		}
		return 0;
	}
	return 1;
}

int HttpsClient::sendfile(std::ifstream& infile, std::string filename, void (*callback)(INT64))
{
	// get filesize
	infile.seekg(0, std::fstream::end);
	auto endPos = infile.tellg();
	infile.clear();
	infile.seekg(0, std::fstream::beg);
	auto begPos = infile.tellg();
	INT64 filesize = endPos - begPos;

	if (callback) callback(-1);

	/* Send the request */
	char bchars[] = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ ";
	std::uniform_int_distribution<int> d(0, 61);
	std::string boundary = "--------------------------------";
	for (int i = 0; i < 32; i++){
		boundary += bchars[d(gen)];
	}

	std::multimap<std::string, std::string> boundaryheader;
	boundaryheader.insert(std::make_pair("Content-Disposition", "form-data; name=\"file\"; filename=\"" + filename + "\""));
	boundaryheader.insert(std::make_pair("Content-Type", "application/octet-stream"));

	std::stringstream prebody, postbody;
	prebody << "--" << boundary << "\r\n";
	for (auto it = boundaryheader.begin(); it != boundaryheader.end(); ++it){
		prebody << (it->first + ": " + it->second + "\r\n");
	}
	prebody << "\r\n";
	postbody << "\r\n";
	postbody << "--" << boundary << "--" << "\r\n";


	AddHeader("Host", APIHost);
	INT64 contlen = prebody.str().size() + postbody.str().size() + filesize;
	AddHeader("Content-Length", dynamic_cast<std::stringstream &>(std::stringstream() << contlen).str());
	AddHeader("Expect", "100-continue");
	AddHeader("Content-Type", ("multipart/form-data; boundary=" + boundary));

	int retry = 5;
	for (; retry > 0; retry--){
		if (callback) callback(-retry);
		BIO_puts(bio, request.line.c_str());
		for (auto it = request.header.begin(); it != request.header.end(); ++it){
			BIO_puts(bio, (it->first + ": " + it->second + "\r\n").c_str());
		}
		BIO_puts(bio, "\r\n");
		BIO_flush(bio);

		/* Read in the response */
		response.line = ReadLine();
		//012345678901234
		//HTTP/1.1 200 OK
		if (response.line.empty()) {
			BIO_reset(bio);
			continue;
		}
		std::stringstream(response.line.substr(9, 3)) >> response.code;
		response.reason = response.line.substr(13);

		// 100
		if (response.code != 100){
			BIO_reset(bio);
			continue;
		}
		break;
	}
	if (retry == 0) return 1;
	response.line = ReadLine();

	// send body part
	auto str = prebody.str();
	BIO_write(bio, str.c_str(), str.size());
	std::vector<char> sendbuf;
	const INT64 bufsize = 32 * 1024 * 1024;
	while (filesize > 0)
	{
		if (callback) callback(filesize);
		if (filesize > bufsize){
			sendbuf.resize(bufsize);
		}
		else {
			sendbuf.resize(filesize);
		}
		infile.read(&sendbuf[0], sendbuf.size());
		if (BIO_write(bio, &sendbuf[0], sendbuf.size()) != sendbuf.size())
			return 1;
		filesize -= sendbuf.size();
		std::cerr << '.';
	}
	if (callback) callback(-2);
	str = postbody.str();
	BIO_write(bio, str.c_str(), str.size());
	BIO_flush(bio);
	std::cerr << "transfer finished,";

	/* Read in the response */
	response.line = ReadLine();
	//012345678901234
	//HTTP/1.1 200 OK
	if (response.line.empty()) {
		BIO_reset(bio);
		return 1;
	}
	std::stringstream(response.line.substr(9, 3)) >> response.code;
	response.reason = response.line.substr(13);

	std::string head_line = ReadLine();
	while (head_line != "\r\n") {
		auto sp = head_line.find_first_of(':');
		response.header.insert(std::make_pair(head_line.substr(0, sp), head_line.substr(sp + 2, head_line.length() - 4 - sp)));
		head_line = ReadLine();
	}
	INT64 ContentLength = -1;
	auto it_length = response.header.find("Content-Length");
	std::stringstream((it_length != response.header.end()) ? it_length->second : "") >> ContentLength;
	if (ContentLength == 0) {
		response.body.resize(0);
		return 0;
	}
	bool tempread = true;
	if (ContentLength > 0) {
		response.body.resize(ContentLength);
		tempread = false;
	}
	size_t len = 0;
	if (tempread) {
		buffer.resize(4096);
		response.body.resize(0);
		while ((ContentLength < 0) || (len >= ContentLength)){
			int p = BIO_read(bio, &buffer[0], (int)buffer.size());
			if (p <= 0) break;
			response.body.resize(len + p);
			memcpy(&buffer[0], &response.body[len], p);
			len += p;
		}
	}
	else {
		int p = BIO_read(bio, &response.body[0], (int)response.body.size());
		if (p <= 0) {
			response.body.resize(0);
		}
		else {
			if (p < response.body.size())
				response.body.resize(p);
		}
	}
	return 0;
}

std::string HttpsClient::ReadLine()
{
	buffer.resize(4096);
	int p = BIO_gets(bio, &buffer[0], (int)buffer.size());
	if (p <= 0) return std::string();
	buffer[p] = 0;
	return std::string(&buffer[0]);
}


int HttpsClient::GET(std::string URI)
{
	if (URI.find('?') != std::string::npos) {
		URI += CreateQueryURI(request.query,true);
	}
	else {
		URI += CreateQueryURI(request.query);
	}
	request.line = "GET " + URI + " HTTP/1.1\r\n";
	request.URI = URI;
	return send();
}

int HttpsClient::POST(std::string URI)
{
	request.line = "POST " + URI + " HTTP/1.1\r\n";
	request.URI = URI;
	return send();
}

int HttpsClient::POST(std::string URI, std::map<std::string, std::string> formdata)
{
	std::string body = CreateQueryURI(formdata);
	body = body.substr(1);
	URI += CreateQueryURI(request.query);
	request.line = "POST " + URI + " HTTP/1.1\r\n";
	request.URI = URI;
	request.body.resize(body.length());
	memcpy(&request.body[0], body.c_str(), body.length());
	AddHeader("Content-Type","application/x-www-form-urlencoded");

	return send();
}

int HttpsClient::POSTfile(std::string URI, std::map<std::string, std::string> formdata, std::wstring filename, void(*callback)(INT64))
{
	std::string body = CreateQueryURI(formdata);
	URI += CreateQueryURI(request.query);
	request.line = "POST " + URI + " HTTP/1.1\r\n";
	request.URI = URI;

	std::ifstream infile(filename, std::ios::in | std::ios::binary);
	if (!infile) return 1;
	auto ppos = filename.find_last_of(L"\\");
	return sendfile(infile, WideChartoUTF8((ppos == std::string::npos) ? filename : filename.substr(ppos + 1)), callback);
}


int HttpsClient::_DELETE(std::string URI)
{
	request.line = "DELETE " + URI + " HTTP/1.1\r\n";
	request.URI = URI;
	return send();
}

int HttpsClient::_DELETE(std::string URI, std::map<std::string, std::string> formdata)
{
	std::string body = CreateQueryURI(formdata);
	body = body.substr(1);
	URI += CreateQueryURI(request.query);
	request.line = "DELETE " + URI + " HTTP/1.1\r\n";
	request.URI = URI;
	request.body.resize(body.length());
	memcpy(&request.body[0], body.c_str(), body.length());
	AddHeader("Content-Type", "application/x-www-form-urlencoded");

	return send();
}

int HttpsClient::AddHeader(std::string key, std::string value)
{
	request.header.insert(std::make_pair(key, value));
	return 0;
}

std::string HttpsClient::GetResponseHeader(std::string key)
{
	auto it = response.header.find(key);
	return it->second;
}


int HttpsClient::Print()
{
	std::cout << ">>>>>request>>>>>\n";
	std::cout << request.line;
	for (auto it = request.header.begin(); it != request.header.end(); ++it){
		std::cout << (it->first + ": " + it->second + "\n");
	}
	std::cout << "\n";
	if (request.body.size() > 0) {
		std::cout.write( &request.body[0], request.body.size());
	}
	
	std::cout << "\n<<<<<<Response<<<<<<\n";
	std::cout << response.line;
	for (auto it = response.header.begin(); it != response.header.end(); ++it){
		std::cout << (it->first + ": " + it->second  + "\n");
	}
	std::cout << "\n";
	if (response.body.size() > 0) {
		std::cout.write(&response.body[0], response.body.size());
	}

	return 0;
}


int HttpsClient::Clear()
{
	request.body.clear();
	request.header.clear();
	request.query.clear();
	request.line.clear();
	response.body.clear();
	response.header.clear();
	response.line.clear();
	response.code = 0;
	response.reason.clear();
	return 0;
}


