/*************************************************************************
*    UrBackup - Client/Server backup system
*    Copyright (C) 2011-2016 Martin Raiber
*
*    This program is free software: you can redistribute it and/or modify
*    it under the terms of the GNU Affero General Public License as published by
*    the Free Software Foundation, either version 3 of the License, or
*    (at your option) any later version.
*
*    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 Affero General Public License for more details.
*
*    You should have received a copy of the GNU Affero General Public License
*    along with this program.  If not, see <http://www.gnu.org/licenses/>.
**************************************************************************/

#include "../vld.h"
#include <stdlib.h>
#include "HTTPClient.h"
#include "../Interface/Pipe.h"
#include "../Interface/Thread.h"
#include "../Interface/ThreadPool.h"
#include "../Interface/Server.h"
#include "../Interface/File.h"
#include "../stringtools.h"
#include "HTTPService.h"
#include "HTTPFile.h"
#include "HTTPAction.h"
#include "HTTPProxy.h"
#include "HTTPSocket.h"

extern CHTTPService* http_service;

const int HTTP_STATE_COMMAND=0;
const int HTTP_STATE_HEADER=1;
const int HTTP_STATE_READY=2;
const int HTTP_STATE_CONTENT=3;
const int HTTP_STATE_WAIT_FOR_THREAD=4;
const int HTTP_STATE_KEEPALIVE=5;
const int HTTP_STATE_DONE=6;
const int HTTP_STATE_WEBSOCKET = 7;

const int HTTP_MAX_KEEPALIVE=15000;

IMutex *CHTTPClient::share_mutex=nullptr;
std::map<std::string, SShareProxy> CHTTPClient::shared_connections;
extern std::vector<std::string> allowed_urls;

void CHTTPClient::Init(THREAD_ID pTID, IPipe *pPipe, const std::string& pEndpoint)
{
	pipe=pPipe;
	do_quit=false;
	http_g_state=HTTP_STATE_COMMAND;
	http_state=0;
	request_num=0;
	request_ticket=ILLEGAL_THREADPOOL_TICKET;
	fileupload=false;
	endpoint=pEndpoint;
}

void CHTTPClient::init_mutex(void)
{
	share_mutex=Server->createMutex();
}

void CHTTPClient::destroy_mutex(void)
{
	Server->destroy(share_mutex);
}

bool CHTTPClient::wantReceive(void)
{
	return http_g_state != HTTP_STATE_WEBSOCKET;
}

bool CHTTPClient::closeSocket(void)
{
	return http_g_state != HTTP_STATE_WEBSOCKET;
}

void CHTTPClient::ReceivePackets(IRunOtherCallback* run_other)
{
	std::string data;
	size_t rc=pipe->Read(&data);
	if( rc>0 )
	{
		for(size_t i=0;i<rc;++i)
		{
			char ch=data[i];
			switch(http_g_state)
			{
			case HTTP_STATE_KEEPALIVE:
				reset();
				http_g_state=HTTP_STATE_COMMAND;
				//fallthrough
			case HTTP_STATE_COMMAND:
				processCommand(ch);
				break;
			case HTTP_STATE_HEADER:
				processHeader(ch);
				break;
			case HTTP_STATE_CONTENT:
				processContent(ch);
				break;
			}

			if(http_g_state==HTTP_STATE_READY )
			{
				if(	processRequest() )
				{
					if (http_g_state != HTTP_STATE_WEBSOCKET)
						http_g_state = HTTP_STATE_WAIT_FOR_THREAD;
					
				}
				else
					do_quit=true;

				break;
			}

		}
	}
	else
	{
		do_quit=true;
	}
}

bool CHTTPClient::Run(IRunOtherCallback* run_other)
{
	if (http_g_state == HTTP_STATE_WEBSOCKET)
	{
		return false;
	}

	if( http_g_state==HTTP_STATE_WAIT_FOR_THREAD )
	{
		if( !Server->getThreadPool()->isRunning(request_ticket) )
		{
			//Server->Log("Connection: "+http_params["CONNECTION"], LL_DEBUG);
			/*if( strlower(http_params["CONNECTION"])=="close" )
			{
				http_g_state=HTTP_STATE_DONE;
			}
			else
			{
				http_g_state=HTTP_STATE_KEEPALIVE;
				http_keepalive_start=Server->getTimeMS();
				//Server->Log("Keep-alive: "+http_params["KEEP-ALIVE"], LL_DEBUG);
				str_map::iterator iter=http_params.find("KEEP-ALIVE");
				if(iter==http_params.end())
				{
					http_keepalive_count=HTTP_MAX_KEEPALIVE;
				}
				else
				{
					http_keepalive_count=std::min(atoi(iter->second.c_str()), HTTP_MAX_KEEPALIVE );
				}
			}*/
			http_g_state=HTTP_STATE_DONE;
			if(fileupload)
			{
				Server->clearPostFiles(tid);
			}
			delete request_handler;
			request_handler=nullptr;

			if(!http_service->getProxyServer().empty() && http_service->getShareProxyConnections()==1)
			{
				IScopedLock lock(share_mutex);
				std::map<std::string, SShareProxy>::iterator it=shared_connections.find(http_query);
				if(it!=shared_connections.end())
				{
					Server->destroy(it->second.notify_pipe);
					Server->destroy(it->second.timeout_pipe);
					delete it->second.proxy;
					shared_connections.erase(it);
				}
			}
		}
	}

	if( do_quit==true || http_g_state==HTTP_STATE_DONE)
	{
		if(http_service->getProxyServer().empty() || http_service->getShareProxyConnections()==0)
		{
		    WaitForRemove();
		}
		else
		{
			Server->wait(10);
			if(Server->getThreadPool()->isRunning(request_ticket) )
			{
				IScopedLock lock(share_mutex);
				std::map<std::string, SShareProxy>::iterator it=shared_connections.find(http_query);
				if(it!=shared_connections.end())
				{
					std::vector<IPipe*> timeoutpipes;
					size_t rc;
					do
					{
						IPipe *t;
						rc=it->second.timeout_pipe->Read((char*)&t, sizeof(IPipe*),0);
						if(rc>0)
							timeoutpipes.push_back(t);
					}
					while(rc>0);

					bool found=false;
					for(size_t i=0;i<timeoutpipes.size();++i)
					{
						if(timeoutpipes[i]==pipe)
						{
							found=true;
						}
						else
						{
							it->second.timeout_pipe->Write((char*)&timeoutpipes[i], sizeof(IPipe*));
						}
					}

					if(found)
					{
						return false;
					}
					else
					{
						return true;
					}
				}
				else
				{
					return false;
				}
			}
			else
			{
				return false;
			}
		}
		return false;
	}

	if( http_g_state==HTTP_STATE_KEEPALIVE )
	{
		if( Server->getTimeMS()-http_keepalive_start>=http_keepalive_count )
		{
			return false;
		}
	}

	return true;
}

void CHTTPClient::processCommand(char ch)
{
	switch(http_state)
	{
	case 0:
		if( ch!=' ' )
			http_state=1;
		else
			break;
	case 1:
		if( ch==' ' && !http_method.empty() )
			http_state=2;
		else if( ch!=' ' )
			http_method+=(char)toupper(ch);
		break;
	case 2:
		if( ch!=' ' && ch!='\r' && ch!='\n' )
		{
			tmp+=ch;
			break;
		}
		else if( (ch==' ' || ch=='\r' || ch=='\n' ) && !tmp.empty() )
		{
			if( tmp=="HTTP/1.0" )
				http_version=10;
			else if( tmp=="HTTP/1.1" )
				http_version=11;
			else
			{
				if( tmp.size()>http_query.size() )
					http_query=tmp;
			}
			tmp.clear();

			if( ch=='\r' || ch=='\n'  )
				http_state=4;
			else
				break;
		}
	case 3:
		if( ch=='\n' )
			http_state=4;
		else if( ch=='\r' )
			break;
	case 4:
		http_g_state=HTTP_STATE_HEADER;
		http_state=0;
		tmp.clear();
	}
}

void CHTTPClient::processHeader(char ch)
{
	switch(http_state)
	{
	case 0:
		if( ch=='\r' || ch=='\n' )
			http_state=1;
		else 
		{
			if( ch!=':' )
				http_header_key+=(char)toupper(ch);
			else
			{
				http_state=3;
			}
			break;
		}
	case 1:
		if( ch=='\n' )
			http_state=2;
		else
			break;
	case 2:
		if( http_method=="POST")
		{
			str_map::iterator iter=http_params.find("CONTENT-LENGTH");
			if( iter!=http_params.end() )
			{
				http_remaining_content=atoi(iter->second.c_str() );
				if( http_remaining_content>0 )
				{
					http_g_state=HTTP_STATE_CONTENT;
					break;
				}
			}
		}
		http_g_state=HTTP_STATE_READY;
		break;

	case 3:
		if( ch!=' ')
			http_state=4;
		else
			break;
	case 4:
		if( ch!='\r' && ch!='\n' )
		{
			tmp+=ch;
			break;
		}
		else
		{
			http_params.insert(std::pair<std::string, std::string>(http_header_key, tmp) );
			tmp.clear();
			http_header_key.clear();
			http_state=5;
		}
	case 5:
		if( ch=='\n' )
			http_state=0;
		else
			break;
	}
}

void CHTTPClient::processContent(char ch)
{
	if( http_remaining_content>0 )
	{
		http_content+=ch;
		--http_remaining_content;
	}
	if( http_remaining_content<=0 )
	{
		http_g_state=HTTP_STATE_READY;
		
		str_map::iterator iter=http_params.find("CONTENT-TYPE");
		if(iter!=http_params.end())
		{
			std::string ct=iter->second;
			strlower(ct);
			if(ct.find("multipart/form-data")!=std::string::npos)
			{
				std::string boundary=getafter("boundary=", iter->second);
				ParseMultipartData(http_content, boundary);
			}
		}
	}
}

std::vector<std::string> CHTTPClient::parseHTTPPath(std::string pPath)
{
	std::string tmp;
	std::vector<std::string> ret;
	for(size_t i=0;i<pPath.size();++i)
	{
		char ch=pPath[i];
		if( ch!='/' )
		{
			tmp+=ch;
		}
		else
		{
			if( tmp!="" )
			{
				ret.push_back(tmp);
				tmp.clear();
			}
		}
	}
	if( tmp!="" )
	{
		ret.push_back(tmp);
	}
	return ret;
}

void CHTTPClient::parseAction(std::string pQuery, std::string &pAction, std::string &pContext)
{
	std::string tmp;
	int cs=0;
	char ch=0;
	char lch;
	for(size_t i=0;i<pQuery.size();++i)
	{
		lch=ch;
		ch=pQuery[i];
		
		if( ch=='=' && lch=='a' && pAction.empty() )
			cs=1;
		else if( ch=='=' && lch=='c' && pContext.empty())
			cs=2;
		else if( cs==1 )
		{
			if( ch=='&' )
				cs=0;
			else
				pAction+=ch;
		}
		else if(cs==2 )
		{
			if( ch=='&' )
				cs=0;
			else
				pContext+=ch;
		}
	}
}


bool CHTTPClient::processRequest(void)
{
	//Server->Log("Parsing done... starting handling request_num: "+convert(request_num)+" "+convert(Server->getTimeMS()), LL_INFO);
	++request_num;
	if(!allowed_urls.empty())
	{
		bool found=false;
		for(size_t i=0;i<allowed_urls.size();++i)
		{	
			if(allowed_urls[i]==http_query)
			{
				found=true;
				break;
			}
		}
		if(found==false)
		{
			Server->Log("URL not allowed");
			pipe->Write("HTTP/1.1 403 Not Allowed\r\nContent-Type: text/html\r\nContent-Length: 46\r\n\r\nSorry. You're not allowed to access this file.");
			request_handler=nullptr;
			request_ticket=ILLEGAL_THREADPOOL_TICKET;
			return false;			
		}
	}
	std::vector<std::string> path=parseHTTPPath(http_query);

	if(!http_service->getProxyServer().empty())
	{
		if(http_service->getShareProxyConnections()==1)
		{
			IScopedLock lock(share_mutex);

			std::map<std::string, SShareProxy>::iterator it=shared_connections.find(http_query);
			if(it!=shared_connections.end())
			{
				it->second.notify_pipe->Write((char*)&pipe, sizeof(IPipe*) );
				request_ticket=it->second.proxy_ticket;
				request_handler=nullptr;
				return true;
			}

			SShareProxy sp;
			sp.notify_pipe=Server->createMemoryPipe();
			sp.timeout_pipe=Server->createMemoryPipe();
			sp.proxy=new CHTTPProxy(http_method, http_query, http_version, http_content, http_params, pipe, sp.notify_pipe, sp.timeout_pipe);
			sp.proxy_ticket=Server->getThreadPool()->execute(sp.proxy);
			request_ticket=sp.proxy_ticket;
			request_handler=nullptr;

			shared_connections[http_query]=sp;
		}
		else
		{
			CHTTPProxy *proxy_handler=new CHTTPProxy(http_method, http_query, http_version, http_content, http_params, pipe, nullptr, nullptr);
			request_ticket=Server->getThreadPool()->execute(proxy_handler);
			request_handler=proxy_handler;
		}
		return true;
	}

	if( path.size()>0 || http_query=="/" )
	{
		std::string *pl;
		if(path.size()>0)
			pl=&path[path.size()-1];
		else
			pl=&http_query;

		std::string name;
		std::string context;
		size_t pstart;
		str_map::iterator upgrade_param = http_params.find("UPGRADE");
		if (upgrade_param != http_params.end()
			&& upgrade_param->second == "websocket")
		{
			std::string name = getuntil("?", *pl);
			std::string gparams = getafter("?", *pl);
			CHTTPSocket* socket_handler = new CHTTPSocket(name, gparams, http_params, pipe, endpoint);
			request_ticket = Server->getThreadPool()->execute(socket_handler, "http websocket");
			request_handler = socket_handler;
			http_g_state = HTTP_STATE_WEBSOCKET;
			return true;
		}
		else if( pl->size()>1 && (*pl)[0]=='x' && (*pl)[1]=='?' )
		{
			parseAction(*pl, name, context);
		}
		else if(pl->find(".")==std::string::npos && path.size()<=2 && (pstart=pl->find("?"))!=std::string::npos)
		{
			name=pl->substr(0, pstart);
			
			if( path.size()>1 )
				context=path[0];
		}
		else
		{
			std::string rp;
			for(size_t i=0;i<path.size();++i)
			{
				if( i+1>=path.size() )
				{
					size_t li=path[i].find_last_of('?');
					if(li!=std::string::npos)
					{
						path[i]=path[i].substr(0,li);
					}
				}
				if( path[i]!=".." && path[i]!="." )
					rp+="/"+path[i];
			}
#ifdef _WIN32
			rp = greplace("\\", "_", rp);
#endif
			CHTTPFile *file_handler=new CHTTPFile(http_service->getRoot()+rp, pipe);
			request_ticket=Server->getThreadPool()->execute(file_handler, "http file request");
			request_handler=file_handler;
			return true;
		}

		std::string gparams=getafter("?", *pl);

		pl=nullptr;
		http_params["REMOTE_ADDR"]=endpoint;
		CHTTPAction *action_handler=new CHTTPAction(name,context,gparams, http_content, http_params, pipe);
		request_ticket=Server->getThreadPool()->execute(action_handler, "http dynamic request");
		request_handler=action_handler;
		return true;
	}
	else
		return false;
}

void CHTTPClient::reset(void)
{
	http_params.clear();
	http_method.clear();
	http_query.clear();
	http_content.clear();
	http_state=0;
	tmp.clear();
	http_header_key.clear();
	request_ticket=ILLEGAL_THREADPOOL_TICKET;
	fileupload=false;
}

void CHTTPClient::WaitForRemove(void)
{
	if(request_ticket!=ILLEGAL_THREADPOOL_TICKET)
	{
		Server->getThreadPool()->waitFor(request_ticket);
	}
}

void CHTTPClient::ParseMultipartData(const std::string &data, const std::string &boundary)
{
	std::string rboundary="--"+boundary;
	int state=0;
	std::string key;
	std::string value;
	std::string filename;
	std::string name;
	std::string contenttype;
	size_t start;
	POSTFILE_KEY pfilekey=Server->getPostFileKey();
	http_params["POSTFILEKEY"]=convert(pfilekey);
	for(size_t i=0;i<data.size();++i)
	{
	    switch(state)
	    {
		case 0:
		    if(next(data,i,rboundary))
		    {
				i+=rboundary.size()+1;
				state=2;
		    }
		    break;
		case 1:
		    if(data[i]=='\n' || data[i]=='\r' )
			{
				if(data[i]=='\n')
				{
					state=4;
					rboundary+="--";
					start=i+1;
				}
				else
					break;
			}
		    else
				state=1;
		case 2:
			if(data[i]!=':')
				key+=toupper(data[i]);
		    else
				state=3;
		    break;
		case 3:
		    if(data[i]!='\n' && data[i]!='\r' )
				value+=data[i];
		    else if(data[i]=='\n')
			{
				if(key=="CONTENT-DISPOSITION")
				{
					name=getbetween("name=\"","\"", value);
					filename=getbetween("filename=\"","\"", value);
				}
				else if(key=="CONTENT-TYPE" )
				{
					contenttype=value;
				}
				value.clear();
				key.clear();
				state=1;
			}
		    break;
		case 4:
			if(next(data,i,rboundary)==true)
			{
                IFile *memfile=Server->openTemporaryFile();
				memfile->Write(data.substr(start,i-start-2) );
				memfile->Seek(0);
				Server->addPostFile(pfilekey, name, SPostfile(memfile, filename, contenttype) );
				fileupload=true;
				state=0;
				rboundary.erase(rboundary.size()-2,2);
				i+=rboundary.size()+2;
				state=0;
			}
	    }
	}
}

