// PluginHTTP.cpp
// ``````````````

// Includes some base64 routines by René Nyffenegger under the following license.

/* 
   base64.cpp and base64.h

   Copyright (C) 2004-2008 René Nyffenegger

   This source code is provided 'as-is', without any express or implied
   warranty. In no event will the author be held liable for any damages
   arising from the use of this software.

   Permission is granted to anyone to use this software for any purpose,
   including commercial applications, and to alter it and redistribute it
   freely, subject to the following restrictions:

   1. The origin of this source code must not be misrepresented; you must not
      claim that you wrote the original source code. If you use this source code
      in a product, an acknowledgment in the product documentation would be
      appreciated but is not required.

   2. Altered source versions must be plainly marked as such, and must not be
      misrepresented as being the original source code.

   3. This notice may not be removed or altered from any source distribution.

   René Nyffenegger rene.nyffenegger@adp-gmbh.ch

*/


#pragma once
#define UNICODE
#include <string>
#include "../common/PluginAPI.h"
#include "../common/mSock.h"
#include "../common/mUtil.h"
using namespace std;


/////////////////////////////////////////////////////////////////
// Helpers
/////////////////////////////////////////////////////////////////


// Returns true if it has been more than 10 seconds since the last call to timeout(true)
bool timeout(bool set) {
	static int tick = 0;
	if (set) {
		tick = getUpTicks();
		return false;
	} else {
		return (getUpTicks() - tick > (1000*10) /* 10 seconds */);
	}
}
bool timeout() {return timeout(false);}


// Escapes non-alphanumeric characters for HTTP transmission.
void httpEscape(string& sString) {
	char* src = (char*) sString.c_str();
	int destlen = (sString.length() * 3) + 2;
	char* destBuff = new char[destlen];	
	memset(destBuff, 0, destlen);

	char* dest = destBuff;
	while(*src != 0) {
		if ( (*src >= 'a' && *src <= 'z') ||
			 (*src >= 'A' && *src <= 'Z') ||
			 (*src >= '0' && *src <= '9') )
		{
			*dest = *src;
			dest++;
		} else {
			dest[0] = '%';
			char eBuff[8] = {0};
			sprintf_s(eBuff, "%2X", *src);
			dest[1] = eBuff[0];
			dest[2] = eBuff[1];
			dest += 3;
		}
		src++;
	}

	sString.assign(destBuff);
	delete[] destBuff;
}


// Extract domain and port from URL.
// Slightly naive - doesn't account for // after :, passwords, etc.
std::string URL2Domain(const char* URL) {
	string sUrl(URL), domain;

	int ss = sUrl.find("://");
	ss = (ss == sUrl.npos) ? 0 : ss + 3;
	int p =  sUrl.find(':', ss);
	int s =  sUrl.find('/', ss);	
	
	if (p == sUrl.npos || p > s) {
		domain = sUrl.substr(ss, s - ss);
	} else {
		domain = sUrl.substr(ss, p - ss);
	}

	return domain;
}

int URL2Port(const char* URL) {
	string sUrl(URL), port;

	int ss = sUrl.find("://");
	ss = (ss == sUrl.npos) ? 0 : ss + 3;
	int p =  sUrl.find(':', ss);
	int s =  sUrl.find('/', ss);	
	
	if (p == sUrl.npos || p > s) {
		port = "80";
	} else {
		port   = sUrl.substr(p+1, s - p - 1);
	}

	int iport = atoi(port.c_str());
	return iport ? iport : 80;
}

std::string URL2Path(const char* URL) {
	string sUrl(URL), path;

	int ss = sUrl.find("://");
	ss = (ss == sUrl.npos) ? 0 : ss + 3;
	ss = sUrl.find("/", ss);
	if (ss == sUrl.npos) ss = 0;

	string retval = sUrl.substr(ss);
	return retval;
}

std::string itos(int i) {
	char buff[16] = {0};
	sprintf(buff, "%d", i);
	return buff;
}

/////////////////////////////////////////////////////////////////
// Base64 Routines (contributed)
/////////////////////////////////////////////////////////////////


static const std::string base64_chars = 
	"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";

static inline bool is_base64(unsigned char c) {
	return (isalnum(c) || (c == '+') || (c == '/'));
}

std::string base64_encode(unsigned char const* bytes_to_encode, unsigned int in_len) {
  std::string ret;
  int i = 0;
  int j = 0;
  unsigned char char_array_3[3];
  unsigned char char_array_4[4];

  while (in_len--) {
    char_array_3[i++] = *(bytes_to_encode++);
    if (i == 3) {
      char_array_4[0] = (char_array_3[0] & 0xfc) >> 2;
      char_array_4[1] = ((char_array_3[0] & 0x03) << 4) + ((char_array_3[1] & 0xf0) >> 4);
      char_array_4[2] = ((char_array_3[1] & 0x0f) << 2) + ((char_array_3[2] & 0xc0) >> 6);
      char_array_4[3] = char_array_3[2] & 0x3f;

      for(i = 0; (i <4) ; i++)
        ret += base64_chars[char_array_4[i]];
      i = 0;
    }
  }

  if (i)
  {
    for(j = i; j < 3; j++)
      char_array_3[j] = '\0';

    char_array_4[0] = (char_array_3[0] & 0xfc) >> 2;
    char_array_4[1] = ((char_array_3[0] & 0x03) << 4) + ((char_array_3[1] & 0xf0) >> 4);
    char_array_4[2] = ((char_array_3[1] & 0x0f) << 2) + ((char_array_3[2] & 0xc0) >> 6);
    char_array_4[3] = char_array_3[2] & 0x3f;

    for (j = 0; (j < i + 1); j++)
      ret += base64_chars[char_array_4[j]];

    while((i++ < 3)) ret += '=';

  }

  return ret;
}


/////////////////////////////////////////////////////////////////
// Raw HTTP functions
/////////////////////////////////////////////////////////////////


// Sends sBody to the address sHostname, and waits for a result.
std::string httpQuery(string& sHostname, int port, string& sBody) {
	
	// Connect to address
	mSock* sock = new mSock(sHostname.c_str(), port);
	sock->put(sBody.c_str(), sBody.length());

	// Wait for response
	std::string sResponse = "";	
	timeout(true);
	do {
		sock->doEvents();
		sResponse += sock->get();
		releaseTimeslice();
	} while (sock->isValid() && ! timeout());

	delete sock;
	return sResponse;
}

// Return result of a (blocking) HTTP/1.1 GET on
//   http://sUser:sPass@sHostname:iPort/sPath (including headers)
bool httpGet(
	string& sResponse, string& sHostname, const char* sPath, int iPort,
	string& sUser, string& sPass, bool include_headers
) {

	// Construct query
	string sQuery = format(
		"GET %s HTTP/1.1\r\nHost: %s:%d\r\nConnection: close",
		sPath, sHostname.c_str(), iPort
	);
	if (sUser.length() && sPass.length()) {
		sQuery.append("\r\nAuthorization: Basic ");
		string sUserAndPass = sUser + ":" + sPass;
		sQuery.append(
			base64_encode(
				(const unsigned char*)sUserAndPass.c_str(), sUserAndPass.length()
			)
		);
	}
	sQuery.append("\r\n\r\n");	

	// Connect to address and wait for the returned token
	sResponse = httpQuery(sHostname, iPort, sQuery);
	/*
	mSock* sock = new mSock(sHostname.c_str(), iPort);
	sock->put(sQuery.c_str());

	// Wait for the returned token
	sResponse = "";	
	timeout(true);
	do {
		sock->doEvents();
		sResponse += sock->get();
		releaseTimeslice();
	} while (sock->isValid() && ! timeout());
	*/
	// Strip headers if necessary
	if (sResponse.length() && ! include_headers) {
		size_t p1 = sResponse.find("\r\n\r\n");
		size_t p2 = sResponse.find("\n\n");
		if (p1 < p2 && p1 != sResponse.npos) {
			sResponse = sResponse.substr(p1 + 4);
		} else if (p2 < p1 && p1 != sResponse.npos) {
			sResponse = sResponse.substr(p1 + 2);
		} else {
			// No headers? Or other invalid response
		}

	}

	// Tidy up
	//delete sock;
	return (sResponse.length() > 0);
}

// Return result of a (blocking) HTTP/1.1 GET
std::string httpGet(const char* URL) {

	string sResponse, blank;
	bool result = httpGet(
		sResponse, URL2Domain(URL), URL2Path(URL).c_str(), URL2Port(URL),
		blank, blank, false
	);

	// Ignore result for now
	return sResponse;
}

// Return result of a (blocking) HTTP/1.1 POST
std::string httpPost(const char* URL, const char* body) {
	string domain = URL2Domain(URL);
	string path   = URL2Path(URL);
	int    port   = URL2Port(URL);

	string query = \
		"POST " + path + " HTTP/1.1\r\n" + \
		"Host: " + domain + "\r\n" + \
		"Accept: */*\r\n" + \
		"Connection: close\r\n" + \
		"Content-Type: application/x-www-form-urlencoded\r\n" + \
		"Content-Length: " + itos(strlen(body)) + "\r\n" + \
		"\r\n" + \
		body
	;

	std::string sResponse = httpQuery(domain, port, query);

	// Strip headers

	if (sResponse.length()) {
		size_t p = sResponse.find("\r\n\r\n");
		if (p != sResponse.npos) sResponse = sResponse.substr(p + 4);
	}
	return sResponse;
}

/////////////////////////////////////////////////////////////////
// Plugin wrappers
/////////////////////////////////////////////////////////////////


void js_httpPost(CScriptVar *v, void*) {
	std::string response = httpPost(
		v->getParameter("url")->getString().c_str(),
		v->getParameter("data")->getString().c_str()
		);
	v->getReturnVar()->setString(response);
}

void js_httpGet(CScriptVar* v, void*) {
	std::string response = httpGet(
		v->getParameter("url")->getString().c_str()
	);
	v->getReturnVar()->setString(response);
}

void js_httpGetAuth(CScriptVar* v, void*) {
	
	string
		sResponse,
		domain	= v->getParameter("domain")->getString(),
		user	= v->getParameter("user")->getString(),
		pass	= v->getParameter("pass")->getString()
	;

	httpGet(
		sResponse, domain, v->getParameter("url")->getString().c_str(),
		v->getParameter("port")->getInt(),
		user,
		pass,
		false
	);

	v->getReturnVar()->setString(sResponse);
}

void js_httpEscape(CScriptVar* v, void*) {
	std::string text = v->getParameter("text")->getString();
	httpEscape(text);
	v->getReturnVar()->setString(text);
}

void plugin_onLoad(CTinyJS* js) {
	js->addNative("function http_get(url)", js_httpGet, 0);
	js->addNative("function http_getAuth(domain, port, url, user, pass)", js_httpGetAuth, 0);
	js->addNative("function http_escape(text)", js_httpEscape, 0);
	js->addNative("function http_post(url, data)", js_httpPost, 0);
}

void plugin_onTick(CTinyJS* js) {
	// Nothing to do
}

void plugin_onExit(CTinyJS* js) {
	// Nothing to do
}