/** \file HttpdSocket.cpp
*/
/*
Copyright (C) 2001-2010  Anders Hedstrom (grymse@alhem.net)

This library is made available under the terms of the GNU GPL, with
the additional exemption that compiling, linking, and/or using OpenSSL 
is allowed.

If you would like to use this library in a closed-source application,
a separate license agreement is available. For information about 
the closed-source license agreement for the C++ sockets library,
please visit http://www.alhem.net/Sockets/license.html and/or
email license@alhem.net.

This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
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 General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
*/
#ifdef _MSC_VER
#pragma warning(disable:4786)
#endif
#include "HttpdCookies.h"
#include "HttpdForm.h"
#include "MemFile.h"
#include "HttpdSocket.h"
#include "Base64.h"


#ifdef SOCKETS_NAMESPACE
namespace SOCKETS_NAMESPACE {
#endif

#define TMPSIZE 10000


// statics
int HttpdSocket::m_request_count = 0;
std::string HttpdSocket::m_start = "";


HttpdSocket::HttpdSocket(ISocketHandler& h)
:HTTPSocket(h)
,m_content_length(0)
,m_file(NULL)
,m_received(0)
,m_request_id(++m_request_count)
,m_cookies(NULL)
,m_form(NULL)
{
	m_http_date = datetime2httpdate(GetDate());
	if (!m_start.size())
		m_start = m_http_date;
}


HttpdSocket::~HttpdSocket()
{
	if (m_file)
	{
		delete m_file;
	}
	if (m_cookies)
		delete m_cookies;
	if (m_form)
		delete m_form;
}


void HttpdSocket::OnFirst()
{
}


void HttpdSocket::OnHeader(const std::string& key,const std::string& value)
{
	if (!strcasecmp(key.c_str(),"content-length"))
	{
		m_content_length = atoi(value.c_str());
		m_content_length_str = value;
	}
	else
	if (!strcasecmp(key.c_str(),"cookie"))
	{
		m_http_cookie = value;
	}
	else
	if (!strcasecmp(key.c_str(),"content-type"))
	{
		m_content_type = value;
	}
	else
	if (!strcasecmp(key.c_str(),"if-modified-since"))
	{
		m_if_modified_since = value;
	}
}


void HttpdSocket::OnHeaderComplete()
{
	m_cookies = new HttpdCookies(m_http_cookie);

	if (GetMethod() == "GET")
	{
		Utility::SetEnv("QUERY_STRING", GetQueryString());
	}
	Utility::SetEnv("REQUEST_METHOD", GetMethod());
	Utility::SetEnv("HTTP_COOKIE", m_http_cookie);
	Utility::SetEnv("CONTENT_TYPE", m_content_type);
	Utility::SetEnv("CONTENT_LENGTH", m_content_length_str);
	if (GetMethod() == "POST")
	{
		m_file = new MemFile;
	}
	else
	if (GetMethod() == "GET")
	{
		m_form = new HttpdForm(GetQueryString(), GetQueryString().size() );
		AddResponseHeader("Date", datetime2httpdate(GetDate()) );
		if (GetUri() == "/image")
		{
			Send64(Utility::Logo, "image/png");
		}
		else
		{
			Exec();
		}
		Reset(); // prepare for next request
	}
	else
	{
		AddResponseHeader("Date", GetHttpDate());
		AddResponseHeader("Connection", "close");
		SetStatus("405");
		SetStatusText("Method not allowed");
		SendResponse();
	}
}


void HttpdSocket::OnData(const char *p,size_t l)
{
	if (m_file)
	{
		m_file -> fwrite(p,1,l);
	}
	m_received += l;
	if (m_received >= m_content_length && m_content_length)
	{
		// all done
		if (m_file && !m_form)
		{
			m_form = new HttpdForm(m_file, m_content_type, m_content_length);
			AddResponseHeader("Date", datetime2httpdate(GetDate()) );
			if (GetUri() == "/image")
			{
				Send64(Utility::Logo, "image/png");
			}
			else
			{
				Exec();
			}
			Reset(); // prepare for next request
		}
	}
}


void HttpdSocket::Send64(const std::string& str64, const std::string& type)
{
	Base64 bb;

	if (!strcasecmp(m_start.c_str(), m_if_modified_since.c_str()))
	{
		SetStatus("304");
		SetStatusText("Not Modified");
		SendResponse();
	}
	else
	{
		size_t len = bb.decode_length(str64);
		unsigned char *buf = new unsigned char[len];

		SetStatus("200");
		SetStatusText("OK");

		AddResponseHeader("Content-length", Utility::l2string( (long)len) );
		AddResponseHeader("Content-type", type );
		AddResponseHeader("Last-modified", m_start);
		SendResponse();

		bb.decode(str64, buf, len);
		SendBuf( (char *)buf, len);
		delete[] buf;
	}
}


std::string HttpdSocket::datetime2httpdate(const std::string& dt)
{
	struct tm tp;
	time_t t;
	const char *days[] = { "Sun","Mon","Tue","Wed","Thu","Fri","Sat" };
	const char *months[] = { "Jan","Feb","Mar","Apr","May","Jun",
	                   "Jul","Aug","Sep","Oct","Nov","Dec" };
	int i;
	char s[40];

/* 1997-12-16 09:50:40 */

	if (dt.size() == 19) 
	{
		tp.tm_year = atoi(dt.substr(0,4).c_str()) - 1900;
		i = atoi(dt.substr(5,2).c_str()) - 1;
		tp.tm_mon = i >= 0 ? i : 0;
		tp.tm_mday = atoi(dt.substr(8,2).c_str());
		tp.tm_hour = atoi(dt.substr(11,2).c_str());
		tp.tm_min = atoi(dt.substr(14,2).c_str());
		tp.tm_sec = atoi(dt.substr(17,2).c_str());
		tp.tm_wday = 0;
		tp.tm_yday = 0;
		tp.tm_isdst = 0;
		t = mktime(&tp);
		if (t == -1)
		{
			Handler().LogError(this, "datetime2httpdate", 0, "mktime() failed");
		}

		snprintf(s,sizeof(s),"%s, %02d %s %d %02d:%02d:%02d GMT",
		 days[tp.tm_wday],
		 tp.tm_mday,
		 months[tp.tm_mon],
		 tp.tm_year + 1900,
		 tp.tm_hour,tp.tm_min,tp.tm_sec);
	} 
	else
	{
		*s = 0;
	}
	return s;
}


std::string HttpdSocket::GetDate()
{
	time_t t = time(NULL);
	char slask[40]; // yyyy-mm-dd hh:mm:ss
#ifdef __CYGWIN__
	struct tm *tp = localtime(&t);
	snprintf(slask,sizeof(slask),"%d-%02d-%02d %02d:%02d:%02d",
		tp -> tm_year + 1900,
		tp -> tm_mon + 1,
		tp -> tm_mday,
		tp -> tm_hour,tp -> tm_min,tp -> tm_sec);
#else
	struct tm tp;
#if defined( _WIN32) && !defined(__CYGWIN__)
	localtime_s(&tp, &t);
#else
	localtime_r(&t, &tp);
#endif
	snprintf(slask,sizeof(slask),"%d-%02d-%02d %02d:%02d:%02d",
		tp.tm_year + 1900,
		tp.tm_mon + 1,
		tp.tm_mday,
		tp.tm_hour,tp.tm_min,tp.tm_sec);
#endif
	return slask;
}


void HttpdSocket::Reset()
{
	HTTPSocket::Reset();
	m_content_length = 0;
	if (m_file)
	{
		delete m_file;
		m_file = NULL;
	}
	m_received = 0;
	m_request_id = ++m_request_count;
	if (m_cookies)
		delete m_cookies;
	m_cookies = NULL;
	if (m_form)
		delete m_form;
	m_form = NULL;
}


const std::string& HttpdSocket::GetHttpDate() const
{
	return m_http_date; 
}


HttpdCookies *HttpdSocket::GetCookies()
{
	return m_cookies; 
}


const HttpdForm *HttpdSocket::GetForm() const
{
	return m_form; 
}



#ifdef SOCKETS_NAMESPACE
}
#endif


