///////////////////////////////////////////////////////////////////////////////
//                                                                             
//  Copyright (C) 2008-2012  Artyom Beilis (Tonkikh) <artyomtnk@yahoo.com>     
//                                                                             
//  See accompanying file COPYING.TXT file for licensing details.
//
///////////////////////////////////////////////////////////////////////////////
#define CPPCMS_SOURCE
#include <cppcms/url_dispatcher.h>
#include <cppcms/http_context.h>
#include <cppcms/http_request.h>
#include <cppcms/http_response.h>
#include <cppcms/forwarder.h>
#include "http_protocol.h"
#include <cppcms/service.h>
#include "service_impl.h"
#include "cached_settings.h"
#include <cppcms/json.h>
#include "cgi_api.h"
#include "multipart_parser.h"
#include <cppcms/util.h>
#include <scgi_header.h>
#include <stdlib.h>
#include <cppcms/config.h>
#ifdef CPPCMS_USE_EXTERNAL_BOOST
#   include <boost/bind.hpp>
#else // Internal Boost
#   include <cppcms_boost/bind.hpp>
    namespace boost = cppcms_boost;
#endif

#include <booster/log.h>
#include <booster/aio/endpoint.h>
#include <booster/aio/aio_category.h>
#include <booster/aio/socket.h>
#include <booster/aio/buffer.h>


namespace cppcms { namespace impl { namespace cgi {

	//
	// Special forwarder from generic CGI to SCGI
	//
	struct connection::cgi_forwarder : public booster::enable_shared_from_this<connection::cgi_forwarder> {
	public:
		cgi_forwarder(booster::shared_ptr<connection> c,std::string ip,int port) :
			conn_(c),
			scgi_(c->get_io_service()),
			ep_(ip,port)
		{
			booster::aio::endpoint ep(ip,port);
			booster::system::error_code e;
			scgi_.open(ep.family(),e);
			if(e) {	return;	}
		}
		void async_run()
		{
			scgi_.async_connect(ep_,boost::bind(&cgi_forwarder::on_connected,shared_from_this(),_1));
		}
	private:
		void on_connected(booster::system::error_code const &e)
		{
			if(e) return;
			header_ = make_scgi_header(conn_->getenv(),0);
			scgi_.async_write(
				booster::aio::buffer(header_),
				boost::bind(&cgi_forwarder::on_header_sent,shared_from_this(),_1,_2));
		}
		void on_header_sent(booster::system::error_code const &e,size_t n)
		{
			if(e || n!=header_.size())
				return;
			header_.clear();
			std::string slen = conn_->getenv("CONTENT_LENGTH");
			content_length_ = slen.empty() ? 0LL : atoll(slen.c_str());
			if(content_length_ > 0) {
				post_.resize( content_length_ > 8192 ? 8192 : content_length_,0);
				write_post();
			}
			else {
				response_.resize(8192);
				read_response();
			}
		}
		void write_post()
		{
			if(content_length_ > 0) {
				if(content_length_ <  (long long)(post_.size())) {
					post_.resize(content_length_);
				}
				conn_->async_read_some(&post_.front(),post_.size(),
					boost::bind(&cgi_forwarder::on_post_data_read,shared_from_this(),_1,_2));
			}
			else {
				response_.swap(post_);
				response_.resize(8192);
				read_response();
			}
		}
		void on_post_data_read(booster::system::error_code const &e,size_t len)
		{
			if(e)  { cleanup(); return; }
			conn_->on_async_read_complete();
			scgi_.async_write(
				booster::aio::buffer(&post_.front(),len),
				boost::bind(&cgi_forwarder::on_post_data_written,shared_from_this(),_1,_2));
		}
		void on_post_data_written(booster::system::error_code const &e,size_t len)
		{
			if(e) { return; }
			content_length_ -= len;
			write_post();
		}
		
		void read_response() 
		{
			conn_->async_read_eof(boost::bind(&cgi_forwarder::cleanup,shared_from_this()));
			scgi_.async_read_some(booster::aio::buffer(response_),
						boost::bind(&cgi_forwarder::on_response_read,shared_from_this(),_1,_2));
		}
		void on_response_read(booster::system::error_code const &e,size_t len)
		{
			if(e) {
				conn_->async_write_eof(boost::bind(&cgi_forwarder::cleanup,shared_from_this()));
				return;
			}
			else {
				conn_->async_write(&response_.front(),len,boost::bind(&cgi_forwarder::on_response_written,shared_from_this(),_1,_2));
			}
		}
		void on_response_written(booster::system::error_code const &e,size_t /*len*/)
		{
			if(e) { cleanup(); return; }
			scgi_.async_read_some(booster::aio::buffer(response_),
				boost::bind(&cgi_forwarder::on_response_read,shared_from_this(),_1,_2));
		}

		void cleanup()
		{
			booster::system::error_code e;
			scgi_.shutdown(booster::aio::stream_socket::shut_rdwr,e);
			scgi_.close(e);
		}

		booster::shared_ptr<connection> conn_;
		booster::aio::stream_socket scgi_;
		booster::aio::endpoint ep_;
		long long int content_length_;
		std::string header_;
		std::vector<char> post_;
		std::vector<char> response_;

	};







connection::connection(cppcms::service &srv) :
	service_(&srv),
	request_in_progress_(true)
{
}

connection::~connection()
{
}


cppcms::service &connection::service()
{
	return *service_;
}
booster::shared_ptr<connection> connection::self()
{
	return shared_from_this();
}

void connection::async_prepare_request(	http::context *context,
					booster::callback<void(bool)> const &h)
{
	async_read_headers(boost::bind(&connection::on_headers_read,self(),_1,context,h));
}

void connection::on_headers_read(booster::system::error_code const &e,http::context *context,ehandler const &h)
{
	if(e)  {
		set_error(h,e.message());
		return;
	}
	forwarder::address_type addr = service().forwarder().check_forwading_rules(
		cgetenv("HTTP_HOST"),
		cgetenv("SCRIPT_NAME"),
		cgetenv("PATH_INFO"));
	
	if(addr.second != 0 && !addr.first.empty()) {
		booster::shared_ptr<cgi_forwarder> f(new cgi_forwarder(self(),addr.first,addr.second));
		f->async_run();
		h(true);
		return;
	}
	context->request().prepare();
	load_content(e,context,h);
}

void connection::aync_wait_for_close_by_peer(booster::callback<void()> const &on_eof)
{
	async_read_eof(boost::bind(&connection::handle_eof,self(),on_eof));
}

void connection::handle_eof(callback const &on_eof)
{
	if(request_in_progress_) {
		on_eof();
	}
}

void connection::set_error(ehandler const &h,std::string s)
{
	error_=s;
	h(true);
}

void connection::handle_http_error(int code,http::context *context,ehandler const &h)
{
	async_chunk_.clear();
	async_chunk_.reserve(256);
	std::string status;
	status.reserve(128);
	status += char('0' +  code/100);
	status += char('0' +  code/10 % 10);
	status += char('0' +  code % 10);
	status += ' ';
	status += http::response::status_to_string(code);
	if(context->service().cached_settings().service.generate_http_headers) {
		async_chunk_ += "HTTP/1.0 ";
		async_chunk_ += status;
		async_chunk_ += "\r\n"
				"Connection: close\r\n"
				"Content-Type: text/html\r\n"
				"\r\n";
	}
	else {
		async_chunk_ += "Content-Type: text/html\r\n"
				"Status: ";
		async_chunk_ += status;
		async_chunk_ += "\r\n"
				"\r\n";
	}


	async_chunk_ += 	
		"<html>\r\n"
		"<body>\r\n"
		"<h1>";
	async_chunk_ += status;
	async_chunk_ += "</h1>\r\n"
		"</body>\r\n"
		"</html>\r\n";
	async_write(async_chunk_.c_str(),async_chunk_.size(),
		boost::bind(
			&connection::handle_http_error_eof,
			self(),
			_1,
			_2,
			code,
			h));
}

void connection::handle_http_error_eof(
	booster::system::error_code const &e,
	size_t /*n*/,
	int code,
	ehandler const &h)
{
	if(e)  {
		set_error(h,e.message());
		return;
	}
	async_write_eof(boost::bind(&connection::handle_http_error_done,self(),_1,code,h));
}

void connection::handle_http_error_done(booster::system::error_code const &e,int code,ehandler const &h)
{
	if(e) {
		set_error(h,e.message());
		return;
	}
	set_error(h,http::response::status_to_string(code));
}

void connection::load_content(booster::system::error_code const &e,http::context *context,ehandler const &h)
{
	if(e)  {
		set_error(h,e.message());
		return;
	}

	http::content_type content_type = context->request().content_type_parsed();
	char const *s_content_length=cgetenv("CONTENT_LENGTH");

	long long content_length = *s_content_length == 0 ? 0 : atoll(s_content_length);

	if(content_length < 0)  {
		handle_http_error(400,context,h);
		return;
	}
	
	if(content_length > 0) {
		if(content_type.media_type()=="multipart/form-data") {
			// 64 MB
			long long allowed=service().cached_settings().security.multipart_form_data_limit*1024;
			if(content_length > allowed) { 
				BOOSTER_NOTICE("cppcms") << "multipart/form-data size too big " << content_length << 
					" REMOTE_ADDR = `" << getenv("REMOTE_ADDR") << "' REMOTE_HOST=`" << getenv("REMOTE_HOST") << "'";
				handle_http_error(413,context,h);
				return;
			}
			multipart_parser_.reset(new multipart_parser(
				service().cached_settings().security.uploads_path,
				service().cached_settings().security.file_in_memory_limit));
			read_size_ = content_length;
			if(!multipart_parser_->set_content_type(content_type)) {
				BOOSTER_NOTICE("cppcms") << "Invalid multipart/form-data request" << content_length << 
					" REMOTE_ADDR = `" << getenv("REMOTE_ADDR") << "' REMOTE_HOST=`" << getenv("REMOTE_HOST") << "'";
				handle_http_error(400,context,h);
				return;
			}
			content_.clear();
			content_.resize(8192);
			async_read_some(&content_.front(),content_.size(),
				boost::bind(&connection::on_some_multipart_read,
					self(),
					_1,
					_2,
					context,
					h));
		}
		else {
			long long allowed=service().cached_settings().security.content_length_limit*1024;
			if(content_length > allowed) {
				BOOSTER_NOTICE("cppcms") << "POST data size too big " << content_length << 
					" REMOTE_ADDR = `" << getenv("REMOTE_ADDR") << "' REMOTE_HOST=`" << getenv("REMOTE_HOST") << "'";
				handle_http_error(413,context,h);
				return;
			}
			content_.clear();
			content_.resize(content_length,0);
			async_read(	&content_.front(),
					content_.size(),
					boost::bind(&connection::on_post_data_loaded,self(),_1,context,h));
		}
	}
	else  {
		on_post_data_loaded(booster::system::error_code(),context,h);
	}
}

void connection::on_some_multipart_read(booster::system::error_code const &e,size_t n,http::context *context,ehandler const &h)
{
	if(e) { set_error(h,e.message()); return; }
	read_size_-=n;
	if(read_size_ < 0) { handle_http_error(400,context,h); return ;}
	multipart_parser::parsing_result_type r = multipart_parser_->consume(&content_.front(),n);
	if(r == multipart_parser::eof) {
		if(read_size_ != 0)  {
			handle_http_error(400,context,h);
			return;
		}
		content_.clear();
		multipart_parser::files_type files = multipart_parser_->get_files();
		long long allowed=service().cached_settings().security.content_length_limit*1024;
		for(unsigned i=0;i<files.size();i++) {
			if(files[i]->mime().empty() && files[i]->size() > allowed) {
				BOOSTER_NOTICE("cppcms") << "multipart/form-data non-file entry size too big " << 
						files[i]->size() 
						<< " REMOTE_ADDR = `" << getenv("REMOTE_ADDR") 
						<< "' REMOTE_HOST=`" << getenv("REMOTE_HOST") << "'";
				handle_http_error(413,context,h);
				return;
			}
		}
		context->request().set_post_data(files);
		multipart_parser_.reset();
		h(false);
		return;
	}
	else if (r==multipart_parser::parsing_error) {
		handle_http_error(400,context,h);
		return;
	}
	else if(r==multipart_parser::no_room_left) {
		handle_http_error(413,context,h);
		return;
	}
	else if(read_size_ == 0) {
		handle_http_error(400,context,h);
		return;
	}
	else {
		async_read_some(&content_.front(),content_.size(),
			boost::bind(&connection::on_some_multipart_read,
				self(),
				_1,
				_2,
				context,
				h));
	}
}


void connection::on_post_data_loaded(booster::system::error_code const &e,http::context *context,ehandler const &h)
{
	if(e) { set_error(h,e.message()); return; }
	context->request().set_post_data(content_);
	on_async_read_complete();
	h(false);
}

bool connection::is_reuseable()
{
	return error_.empty() && keep_alive();
}

std::string connection::last_error()
{
	return error_;
}

void connection::async_write_response(	http::response &response,
					bool complete_response,
					ehandler const &h)
{
	async_chunk_=response.get_async_chunk();
	if(!async_chunk_.empty()) {
		async_write(	async_chunk_.data(),
				async_chunk_.size(),
				boost::bind(	&connection::on_async_write_written,
						self(),
						_1,
						complete_response,
						h));
		return;
	}
	if(complete_response) {
		on_async_write_written(booster::system::error_code(),complete_response,h);
		return;
	}
	service().impl().get_io_service().post(boost::bind(h,false));
}

void connection::on_async_write_written(booster::system::error_code const &e,bool complete_response,ehandler const &h)
{
	if(e) {	
		BOOSTER_WARNING("cppcms") << "Writing response failed:" << e.message();
		service().impl().get_io_service().post(boost::bind(h,true));
		return;
	}
	if(complete_response) {
		async_write_eof(boost::bind(&connection::on_eof_written,self(),_1,h));
		request_in_progress_=false;
		return;
	}
	service().impl().get_io_service().post(boost::bind(h,false));
}
void connection::async_complete_response(ehandler const &h)
{
	async_write_eof(boost::bind(&connection::on_eof_written,self(),_1,h));
	request_in_progress_=false;
}

void connection::complete_response()
{
	write_eof();
}

void connection::on_eof_written(booster::system::error_code const &e,ehandler const &h)
{
	if(e) { set_error(h,e.message()); return; }
	h(false);
}


struct connection::reader {
	reader(connection *C,io_handler const &H,size_t S,char *P) : h(H), s(S), p(P),conn(C)
	{
		done=0;
	}
	io_handler h;
	size_t s;
	size_t done;
	char *p;
	connection *conn;
	void operator() (booster::system::error_code const &e=booster::system::error_code(),size_t read = 0)
	{
		if(e) {
			h(e,done+read);
			return;
		}
		s-=read;
		p+=read;
		done+=read;
		if(s==0)
			h(booster::system::error_code(),done);
		else
			conn->async_read_some(p,s,*this);
	}
};
struct connection::writer {
	writer(connection *C,io_handler const &H,size_t S,char const *P) : h(H), s(S), p(P),conn(C)
	{
		done=0;
	}
	io_handler h;
	size_t s;
	size_t done;
	char const *p;
	connection *conn;
	void operator() (booster::system::error_code const &e=booster::system::error_code(),size_t wr = 0)
	{
		if(e) {
			h(e,done+wr);
			return;
		}
		s-=wr;
		p+=wr;
		done+=wr;
		if(s==0)
			h(booster::system::error_code(),done);
		else
			conn->async_write_some(p,s,*this);
	}
};

void connection::async_read(void *p,size_t s,io_handler const &h)
{
	reader r(this,h,s,(char*)p);
	r();
}

void connection::async_write(void const *p,size_t s,io_handler const &h)
{
	writer w(this,h,s,(char const *)p);
	w();
}

size_t connection::write(void const *data,size_t n,booster::system::error_code &e)
{
	char const *p=reinterpret_cast<char const *>(data);
	size_t wr=0;
	while(n > 0) {
		size_t d=write_some(p,n,e);
		if(d == 0)
			return wr;
		p+=d;
		wr+=d;
		n-=d;
		if(e)
			return wr;
	}
	return wr;
}


} // cgi
} // impl
} // cppcms
