/***************************************************************************

    supportware
    Copyright (C) 2001-2008 by c.holzheuer
    c.holzheuer@sourceworx.org

    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.

***************************************************************************/


#include <ddwebrequest.h>
#include <ddwebfile.h>

#include <ddstring.h>
#include <stdio.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <string.h>

     
using namespace std;


/***************************************************************************

    supportware
    Copyright (C) 2001-2008 by c.holzheuer
    c.holzheuer@sourceworx.org

    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.

***************************************************************************/


const char* ddwebrequest::dd_reqtype[] = { "NONE", "GET", "POST", "HEAD" };

/**
	Default constructor.
*/

ddwebrequest::ddwebrequest()
:	ddstringrow(req_fields_size),  _protminor(0), _requesttype(0)
{
	parse_init();	
}


/**
	Destructor
*/

ddwebrequest::~ddwebrequest()
{

}


/**
	@return "true" if the cookie object named "key" exists.
*/

bool ddwebrequest::has_cookie( const ddstring& key )
{
	if( key.empty() )
		return false;
	ddwebcookiemap::iterator pos = _cookies.find( key );
	return (pos != _cookies.end() );
}



void ddwebrequest::set_raw_path( const ddstring& path )
{
	(*this)[ddwebrequest::req_raw_path] = path;
}




/**
	Dumps a ddstring representation of this object.
*/

void ddwebrequest::dump() const
{
  cout << "\nRequest:"  << endl;
	for( size_t i=0; i < ddwebrequest::req_fields_size; ++i )
		cout << i << '\t' << (*this)[i] << endl;
	cout << "\n\n"  << endl;	
}


/**
	Inits the index structure for request parameters so that
	a request can be parsed.
*/

void ddwebrequest::parse_init()
{

	// header felder
	_fieldindex["GET"]   							= ddwebrequest::req_get;
	_fieldindex["POST"]   						= ddwebrequest::req_post;
	_fieldindex["HEAD"]   						= ddwebrequest::req_head;
	
	// datenfelder, die gespeichert werden
	_fieldindex["Host"]   						= ddwebrequest::req_host;
	_fieldindex["User-Agent"]  				= ddwebrequest::req_user_agent;
	_fieldindex["Referer"]      			= ddwebrequest::req_referer;
	_fieldindex["Content-Type"] 			= ddwebrequest::req_content_type;
	_fieldindex["If-Modified-Since"]	= ddwebrequest::req_if_modified_since;	
	
	// sonderbehandlungen
 	_fieldindex["Connection"]   			= ddwebrequest::req_keep_alive;
	_fieldindex["Cookie"]      				= ddwebrequest::req_cookie;
	_fieldindex["Content-Length"] 		= ddwebrequest::req_content_length;
  

}


void ddwebrequest::clear()
{
 	ddstringrow::clear();
	resize( req_fields_size );
}


/**
	Handles parse errors.
*/

int ddwebrequest::parse_error( const ddstring& msg )
{
	_keepalive = false;
	_error->assign( msg );
	return -1;
}


/**
	http request parsen
*/

int ddwebrequest::parse( const char* data, size_t len, ddstring& error )
{
 	_error = &error;
	clear();

  ddstring raw( data, len );
  _lines.create_lines( raw );
  _cgi.clear();
 
	if( _lines.size() >= 2 )
	{
		if( parse_header() > -1 )
			return parse_fields();
		return -1;
	}
	return parse_error( "wrong number of request lines" );
}


/**
	Parses a http request header, e.g. GET /moo/blub.html HTTP/1.1
*/

int ddwebrequest::parse_header()
{

	ddstringrow tmp( _lines[0], " " );
		
	// FIX! HEAD! POST!
	if( tmp.size()<3 )
		return parse_error( "bad head: " + _lines[0] );
 
	ddmapindex::iterator pos = _fieldindex.find( tmp[0] );
	if( pos != _fieldindex.end() )
		_requesttype = (*pos).second;
	else
		return parse_error( "ddwebrequest::parse_header(): bad request:"+tmp[0] );

	// handle path parsing
	if( parse_path( tmp[1] ) < 0 ) 
		return parse_error( "ddwebrequest::parse_header(): bad path:"+tmp[1] );
		
  // HTTP/1.1.
  if( tmp[2].size() < 8 )
		return parse_error( "ddwebrequest::parse_header(): bad http version:"+tmp[2] );

	_protminor = tmp[2][7]-'0';
	
	return 1;

}


int ddwebrequest::parse_path( ddstring& fullpath )
{	
	// store orig path
	set_raw_path( fullpath );
	size_t pos = fullpath.find( '?' );
	// chop cgi
	if( pos != ddstring::npos )
	{
		if( pos < fullpath.size() )
		{
			get_cgi_path() = fullpath.substr( pos+1 );
			_cgi.create( get_cgi_path() );
		}
		fullpath = fullpath.substr( 0, pos );
	}
	_splitpath.create( fullpath.substr(1), "/" );
	get_full_path() = fullpath;
	return check_path( fullpath );
}


/**
	Parses the request-body, e.g. Accept, Keep-Alive ...
*/

int ddwebrequest::parse_fields()
{
	
	_keepalive = _upload = false;
	_cookies.clear();

	size_t xx = _lines.size(); 
  size_t i = 1;
	for( ; i < xx ; ++i )
	{
		size_t x = _lines[i].find( ":" );
		// Bugs ignorieren
		if( x == ddstring::npos )
		{     
			log_error( "wrong format: " + _lines[i] );
			continue;
		}
	  // search keys
 		ddmapindex::iterator pos = _fieldindex.find( _lines[i].substr( 0, x ) );
 		ddstring fieldcontent = _lines[i].substr( x+1 );
 		fieldcontent.trim();
 		// hack: sonderbehandlung erforderlich ?
 		if( (*pos).second < 0 )
 		{
 			switch( (*pos).second )
 			{
 				case ddwebrequest::req_content_length :
 						_contentlength = fieldcontent.as_int();
 						break;
 						
				case ddwebrequest::req_cookie :
					parse_cookies( fieldcontent );
					break;
				
				case ddwebrequest::req_keep_alive	: 					
					// ?? HTTP/1.0 kann kein keep_alive??
					if( _protminor )
						_keepalive = ( fieldcontent.to_lower().contains("keep-alive") );
 			}
 		}
 		// falsche felder ingnorieren
 		else if( pos != _fieldindex.end() )
 		{
 			(*this)[ (*pos).second ] = fieldcontent; 		
 		}
	
	} // for

  // chop port, if any.
	ddstring tmp = get_host_name();
  // host is a must!
  if( tmp.empty() )
		return parse_error( "Host part missing " );		

	get_host_port() = tmp;
  size_t c = tmp.rfind( ':' );
	if(  c != ddstring::npos )
	{
		get_host_name() = tmp.substr( 0, c );
    get_port() = tmp.substr( c+1 );
	}
  
	// set domain name
	get_domain_name() = get_host_name();
	c = get_host_name().find( '.' );
	// es gibt einen punkt ...
	if(  c != ddstring::npos )
	{
		// wenns noch einen gibt ...
		size_t dot = get_host_name().find( '.', c+1 );
		if(  dot != ddstring::npos )
			get_domain_name() = get_host_name().substr( c+1 );
	}
	
	_upload = ( get_content_type().as_lower().contains("multipart/form-data") );
	
	return 1;

}


/**
	Parses the cookies, if any. Creates the cookie map.
*/

int ddwebrequest::parse_cookies( const ddstring& rawcookies )
{
	ddstringrow tmp( rawcookies, "; " );	
	for( size_t i=0; i<tmp.size(); ++i )
	{
		const ddstring& cok = tmp[i];
		size_t c = cok.find( '=' );
		if(  c != ddstring::npos )
		{
			ddwebcookie newcookie( cok.substr( 0, c ), cok.substr( c+1 ) );
			_cookies[newcookie.get_name()] = newcookie;
		}	
	}
	return 1;
}


/**
	This killerhack function hides the cgi special characters
	"?" and "&" by escaping them as "%3F" and "%26".
	This is used to squeeze a full cgi-path (containing "&" and "?" )
	in a single cgi data field.
*/

ddstring ddwebrequest::get_raw_path_escaped()
{
	// work alreeady done?	
	if( !(*this)[ddwebrequest::req_raw_path_esc].empty() )
		return (*this)[ddwebrequest::req_raw_path_esc];
	
	// '&' Hex: 26
	// '?' Hex: 3F
	const ddstring& path = get_raw_path();
	ddstring result;
	for( size_t i=0; i<path.size(); i++ )
	{
		char c = path[i];
		if( c == '&' )
			result += "%26";		
		else if( c=='?' )
			result += "%3F";		
		else 
			result += c;
	}
	(*this)[ddwebrequest::req_raw_path_esc] = result;
	return result;
}


/**
	Parst POST-Daten.
*/

int ddwebrequest::parse_post( const char* data, size_t len )
{	
	get_post_content() = ddstring( data, len );
	// anhängen
	_cgi.create( get_post_content(), true );
	//?? HÄÄ ?? Gell das ist auch bei POST preformated mit ?x=y&y=z ??
	get_cgi_path() += get_post_content(); 
	
	return 1;
	
}



/**
	Parst die upload (multipart/formdata) blöcke
*/

int ddwebrequest::parse_upload( const char* data, size_t size )
{	
	size_t pos = get_content_type().find( "boundary=" );
	// War nix
	if( pos == ddstring::npos || pos+9 >= get_content_type().size() )
		return -1;
		
	ddstring boundary = "--" + get_content_type().substr( pos+9 );
	
	// den ersten überspringen
	size_t inc = boundary.size();
	size_t len = size - inc;
	char* beg = (char*) data + inc;
	char* nxt = 0;
	char* end = (char*) data + size;
	
	// ??
	*end = 0;
	
	while( beg < end )
	{
		nxt = (char*) ::memmem( beg, len, boundary.c_str(), inc );		
		if( !nxt )
			break;
		
		// block gefunden, ist es ein File oder ein Feld ? 
		parse_upload_block( beg, nxt-beg );
		
		// nächster block
		beg = nxt + inc;
		//len -= (nxt-beg)+inc;
		len = end - beg;
	
		
	}
	
	return 1;
	
}



void ddwebrequest::parse_upload_block( const char* block, size_t size )
{
	//Function: void * memmem (const void *haystack, size_t haystack-len,const void *needle, size_t needle-len)
	
	// name rausfinden: sollte immer da sein
	ddstring name, value;
	char* mark = (char*) ::memmem( block, size, "name=\"", 6 );
	char *end = 0;
	
	if( mark )
	{
		mark += 6;
		end = (char*) ::memchr( mark, '"', size );
		if( end )
			name = ddstring( mark, end-mark );
	}
	
	
	// Fallunterrausfinden, ob ein file hochgeladen wurde
	mark = (char*) ::memmem( block, size, "filename=\"", 10 );
	if( mark )
	{
		mark += 10;
		end = (char*) ::memchr( mark, '"', size );
		if( end )
			value = ddstring( mark, end-mark ).unescape().trim();
	}

	// den 'body' rausfinden, das ist entweder das File oder ein blödes Feld
	mark = (char*) ::memmem( block, size, "\r\n\r\n", 4 );
	if( mark )
	{
		mark += 4;			
		// ende gefunden, wenn der Filename schon belegt ist,
		// dann ist es ein File
		size_t len = size - 2 - (mark-block);
		if( value.empty() )
		{
			value = ddstring( mark, len ).unescape().trim();
		}
		else
		{
			ddwebfile file;
			file.create_media_file( mark, len, value );
		}
	}	
	
	cout << "xxxxx LOOOOOO: " << name << " : " << value << endl;
	// cgi add 
	//_cgi.strip_evil( value );
	_cgi[name] = value.trim_evil();

}


/**
	Den Request-Pfad testen, der muss mit '/' anfangen und darf kein '..' etc. 
	enthalten.
*/

int ddwebrequest::check_path( const ddstring& path )
{
	if( path.empty() || path[0] != '/' )
		return parse_error( "empty path or missing '/'" );
	
	// this hack is stolen from mathopd
	const char *p = path.c_str();
	char c;
	enum
	{
		s_normal,
		s_slash,
		s_slashdot,
		s_slashdotdot,
		s_forbidden
	} pstate;

	pstate = s_normal;
	do
	{
		c = *p++;
		switch( pstate )
		{
		case s_normal:
			
			if (c == '/')
				pstate = s_slash;
			break;
		
		case s_slash:
			
			if (c == '/')
				pstate = s_forbidden;


			else if (c == '.')
				//pstate = r->c->allow_dotfiles ? s_slashdot : s_forbidden;
				pstate = s_forbidden;
			else
				pstate = s_normal;
			break;
		
		case s_slashdot:
			
			if (c == 0 || c == '/')
				pstate = s_forbidden;
			else if (c == '.')
				pstate = s_slashdotdot;
			else
				pstate = s_normal;
			break;
		
		case s_slashdotdot:
			
			if (c == 0 || c == '/')
				pstate = s_forbidden;
			else
				pstate = s_normal;
			break;
		
		case s_forbidden:
			c = 0;
			break;

		}
	}
	while( c );
		
	if( pstate == s_forbidden )
		return parse_error( "illegal path" );
	
	return 1;

}


/***************************************************************************

    supportware
    Copyright (C) 2001-2008 by c.holzheuer
    c.holzheuer@sourceworx.org

    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.

***************************************************************************/


const char* ddwebresponse::dd_header[] =
{
	
	"HTTP/1.1 ","",
	"Server: ", DDVERSION,
	"Date: ","",
	"Set-Cookie: ","",
  "Content-Type: ","",	
  "Content-Length: ","",	
  "Last-Modified: ","",
	"Location: ","",
	  
	"Expires: ","",
	"Cache-Control: ", "",
	"Cache-Control: ", "",
  "Pragma: ", "",	
	
	0

};


const char* ddwebresponse::dd_ret_msg[] =
{
	"200 OK",
	"201 Created",
	"202 Accepted",
	"203 Non Authoritative",
	"204 No Content",
	"205 Reset Content",
	"206 Partial Content",
	"207 Multi Status",
	"300 Multiple Choices",
	"301 Moved Permanently",
	"302 Moved Temporarily",
	"303 See Other",
	"304 Not Modified",
	"305 Use Proxy",
	"307 Temporary Redirect",
	"400 Bad Request",
	"401 Unauthorized",
	"402 Payment Required",
	"403 Forbidden",
	"404 Not Found",
	"405 Method Not Allowed",
	"406 Not Acceptable",
	"407 Proxy Authentication Required",
	"408 Request Time Out",
	"409 Conflict",
	"410 Gone",
	"411 Length Required",
	"412 Precondition Failed",
	"413 Request Entity Too Large",
	"414 Request Uri Too Large",
	"415 Unsupported Media Type",
	"416 Range Not Satisfiable",
	"417 Expectation Failed",
	"422 Unprocessable Entity",
	"423 Locked",
	"424 Failed Dependency",
	"500 Internal Server Error",
	"501 Not Implemented",
	"502 Bad Gateway",
	"503 Service Unavailable",
	"504 Gateway Time Out",
	"505 Version Not Supported",
	"506 Variant Also Varies",
	"507 Insufficient Storage",
	"510 Not Extended"		
};


const char* ddwebresponse::dd_ret_msg_short[] =
{
	"200",	"201",	"202",	"203",	"204",	"205",	"206",
	"207",	"300",	"301",	"302",	"303",	"304",	"305",
	"307",	"400",	"401",	"402",	"403",	"404",	"405",
	"406",	"407",	"408",	"409",	"410",	"411",	"412",
	"413",	"414",	"415",	"416",	"417",	"422",	"423",
	"424",	"500",	"501",	"502",	"503",	"504",	"505",
	"506",	"507",	"510"		
};


/**
	Default constructor
*/

ddwebresponse::ddwebresponse()
: ddstringrow( dd_header ), _retcode( 0 ), _date( 0 )
{

}


/**
	Copy constructor
*/

ddwebresponse::ddwebresponse( const ddwebresponse& src )
: ddstringrow(), _retcode( 0 ), _date( 0 )
{
	*this = src;
}


/**
	Destructor
*/


ddwebresponse::~ddwebresponse()
{

}


/**
	Assignment operator. not done yet.
*/

ddwebresponse& ddwebresponse::operator=( const ddwebresponse& src )
{
	if( this == &src )
		return *this;
	throw runtime_error("ddwebresponse::operator= not done yet!");
	return *this;
}


/**
	@return the retcode as text.
	@param the retcode as number.
*/
		
ddstring ddwebresponse::create_retcode( size_t msg )
{  
	if( msg >= ddwebresponse::ret_code_size )
		msg = ddwebresponse::ret_500_internal_server_error;
	return ddstring( dd_ret_msg[msg] );
}


/**
	Set the retcode field.
	@param the retcode as number.
	@see get_retcode.
*/

void ddwebresponse::set_retcode( size_t msg )
{ 
	if( msg >= ddwebresponse::ret_code_size  )	
		msg = ddwebresponse::ret_500_internal_server_error;
	_retcode = msg;
	(*this)[ddwebresponse::header_retcode] = create_retcode( msg );
}


/**
	Clear all fieds except the server name.
*/

void ddwebresponse::init_data()
{
	
	_retcode = 0;
	_date = 0;
	
	(*this)[ddwebresponse::header_retcode] 			 	= "";
	(*this)[ddwebresponse::header_date] 					= ""; 
	(*this)[ddwebresponse::header_set_cookie] 		= ""; 
	(*this)[ddwebresponse::header_content_type]		= ""; 
	(*this)[ddwebresponse::header_content_length] = ""; 
	(*this)[ddwebresponse::header_last_modified]  = "";
	(*this)[ddwebresponse::header_location] 			= ""; 
	
	(*this)[ddwebresponse::header_expires] 			 	= ""; 
	(*this)[ddwebresponse::header_cache_control1] = ""; 
	(*this)[ddwebresponse::header_cache_control2] = ""; 
	(*this)[ddwebresponse::header_pragma] 			 	= ""; 
	
	// YEAH MAN ! YOU _SHOULD_ ALSO DO THIS!
	_cookie.invalidate_data();
	
}
	

/**
	Set Flags to force the browse to reload freshly
	generated pages.
*/

void ddwebresponse::set_allow_caching( bool usecache )
{
	if( usecache )
	{
		(*this)[ddwebresponse::header_expires] 				= ""; 
		(*this)[ddwebresponse::header_cache_control1] = "private"; 
		(*this)[ddwebresponse::header_cache_control2] = ""; 
		(*this)[ddwebresponse::header_pragma] 			 	= "";
		(*this)[ddwebresponse::header_cache_control1] = ""; 
	}
	else
	{
		(*this)[ddwebresponse::header_expires] 			 	= "0";//"Mon, 26 Jul 1997 05:00:00"; 
		(*this)[ddwebresponse::header_cache_control1] = "no-store, no-cache, must-revalidate, max_age=0"; 
		(*this)[ddwebresponse::header_cache_control2] = "post-check=0, pre-check=0"; 
		(*this)[ddwebresponse::header_pragma] 			 	= "no-cache"; 
	}
	/*
	header ("Pragma: no-cache");
	header ("Cache-Control: no-cache, must-revalidate, max_age=0");
	header ("Expires: 0");
	*/
	
	// weitere sachen möglich ...
	//Cache-Control: max-age=120
}


/**
	@return @see join()
*/

ddstring ddwebresponse::to_string() const
{
	return join();
}


/**
	Dump the header fields of this response into a buffer.
	@param buffer the buffer
*/

void ddwebresponse::flush_data( ddiobuffer& buffer )
{
	// reset and fill header buffer
	buffer.reset();
	for( size_t i=0; i<size()-1; i++ )
  {
		// hack: send valid cookies only
		if( ++i == ddwebresponse::header_set_cookie )
		{
			if( _cookie.is_valid() )
			{
				buffer.append( (*this)[i-1] );
				_cookie.flush_data( buffer );
				buffer.append( "\r\n", 2 );
			}
			continue;	
		}
		
		// send valid fields only 
		if( !(*this)[i].empty() )
		{
			buffer.append( (*this)[i-1] );
			buffer.append( (*this)[i] );
			buffer.append( "\r\n", 2 );			
		}
	} 
	buffer.append( "\r\n", 2 );  

}



/**
	Dumps a ddstring representation of this object.
*/

void ddwebresponse::dump( const ddstring& request ) const
{
	cout << "\nresponse for: " << request << endl;
	for( size_t i=0; i<header_fields_size-1; i+=2 )
		if( !(*this)[i+1].empty() )
			cout << (*this)[i] <<  (*this)[i+1] << endl;
	cout << "\n" << endl;

}


