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

    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 <ddwebconnection.h>
#include <ddwebserver.h>
#include <ddiourl.h>
#include <string.h>


/**
	Standard constructor
*/

ddwebconnection::ddwebconnection( ddwebserver* parent, int connectionid )
: _server( parent ), _lastaction( 0 )
{
	_errortemplate.create( DDHINTERRPAGE, "|" );
	// id speichern
	_connectionid = ddstrutil::from_int( connectionid );
}


/**
	Destructor
*/

ddwebconnection::~ddwebconnection()
{
	connection_handle_close();
}


/**
	Accept a connection from the outside.
*/

int ddwebconnection::connection_handle_accept( int masterfd )
{	
	int result = _multibuffer.accept( masterfd );
	if( result <= 0 )
		return result;
	
	set_state_flags( con_reading );
	// update timers
	_lastaction = ::time(0);
		
	return 1;
	
}	
	

/**
	Einsprung der state-engine: verzweigt in den zuständigen unterstatus.
	Wird von ddwebserver aufgerufen, wenn sich nach poll() was getan hat
*/

int ddwebconnection::connection_handle_state()
{
 	
	// update timers
	_lastaction = ::time(0);
	
	switch( _flags )
	{
		case con_reading :
			return connection_handle_read();
		
		case con_reading_post :
			return connection_handle_read_post();
			
 		case con_writing :
			return connection_handle_write(); 	
	
	}
	
	return 0;

}


/**
	result == 0  means: EAGAIN
	result >= 1  means: read ok 
	result == -1 means: readerro 	
*/

int ddwebconnection::connection_handle_read()
{
	int	result = _multibuffer.receive();
	// EAGAIN: weitermachen
	if( result == 0 )
		return 0;
	// schiefgegangen?
	if( result < 0 )
		return connection_handle_close();
		
	// lesen hat geklappt, nachschauen, ob schon ein 
	// request draus wird.
	
	return connection_handle_test_buffer();
	
}



int ddwebconnection::connection_handle_test_buffer()
{
 	int len = _multibuffer.max_output();
	// gibbet schon was ?
	if( !len )
		return 0;
	
	// request fertig ?
	char* bufout = _multibuffer.output();
	char* crlf   = ::strstr( bufout, "\r\n\r\n" );
	// weiterlesen
	if( !crlf )
		return 0;
	
	
	// Multi?
	/*
	ddstring raw( bufout, len );
	raw = "FULL:\n " + raw;
	cout << raw << endl;
	*/
	
	// es gibt einen fertigen request	
	ddstring errortxt;
	int bytes = crlf-bufout;
	
	int result = _request.parse( bufout, bytes, errortxt );
	
	// ob ok oder nicht, den buffer shiften
	// +4 wg. 2*crlf
	_multibuffer.move_output( bytes+4 );
	//return handle_error( "moooooo!", ddwebresponse::ret_400_bad_request );		
	// fehler mit meldung machen
	if( result < 0 )
		return connection_handle_error( errortxt, ddwebresponse::ret_400_bad_request );			
	
	// Ist es ein Post ?
	if( _request.get_request_type() != ddwebrequest::req_post ) 
		return connection_handle_request();
	
	_contentlength = _request.get_content_length();
	// Zu fett?	
	
	
	if( _contentlength > DDMAXUPLOADSIZE	)
	//if( _contentlength > 25000	)		
		return connection_handle_error( "Upload zu gross, bitte maximal 2 MB hochladen.", ddwebresponse::ret_507_insufficient_storage );					 
		
	set_state_flags( con_reading_post );	  	
	
	return connection_handle_test_post();
}


/**
	Prüfen, ob der POST-Datenblock schon komplett
	geladen wurde. Falls ja, muss eine Falluntscheidung 
	zwischen 'normalen' Formdaten und einem File-Upload
	getroffen werden. Das Flag 'is_upload' wird schon
	vorher in @see '_request.parse' gesetzt.
*/

int ddwebconnection::connection_handle_test_post()
{
	// post schon komplett?
	if( _multibuffer.max_output() != _contentlength )	
		return 0;
	
	int result = 0;
	// FALLUNTERSCHEIDUNG: uploads müssen anders behandelt werden
	if( _request.is_upload() )
		result = _request.parse_upload( _multibuffer.output(), _multibuffer.max_output() );
	else
		result = _request.parse_post( _multibuffer.output(), _multibuffer.max_output() );
	
	if( result < 0 )
		return connection_handle_error( "handle_test_post() FAILED!", ddwebresponse::ret_400_bad_request );	
	
	//??
	_multibuffer.move_output( _contentlength );
	return connection_handle_request();
		
}


int ddwebconnection::connection_handle_read_post()
{
	// weiterlesen schiefgegangen oder EAGAIN?
	if( _multibuffer.receive() < 0 )
		return connection_handle_close();
	// weitermachen
	return connection_handle_test_post();

}


int ddwebconnection::connection_handle_request()
{
	_errorcode    = 0;
	_errormessage = "(nüscht)";

	// wir tun was: sperren
	set_state_flags( con_locked );	  
	
	// den debug timer starten
	_timer.start();
	_firstpath = get_request().get_raw_path().as_lower();
	
	// den request bearbeiten, ergebnis:
	// - jetzt sind wir entweder locked und warten auf ein pagelet(was uns von aussen weckt)
	// - oder wir servieren ein statisches file
	// - oder es ist was schiefgegangen. (domain not found, halted etc)

	int writemode = _server->webserver_handle_request( *this );
	//log_debug( "YX " + _connectionid + " Time: " +  ddstrutil::from_double( _timer.elapsed() ) + " Request: " + _firstpath + " mode: " + ddstrutil::from_int( writemode ) );

	switch( writemode )
	{
				
		case ddwebconnection::write_wait :
	
			// warten auf ein pagelet: wir tun nix
			return 0;
			
		case ddwebconnection::write_sendfile :
			
			// wir sind kein dynamischer request, also
			// wird ein file veschickt
			
			return connection_handle_static_response();
		
		default: //writemode<0
	
			// wenn hier ein Fehler kommt, sind message & code 
			// schon von aussen gesetzt worden.
			return connection_handle_error( _errormessage, _errorcode );
			
	}
	
	return 0;
	
}


/**
	Ein statisches file veschicken, wird nur intern aufgerufen
	und schreibt sofort los.
*/

int ddwebconnection::connection_handle_static_response()
{
	// wir sind kein dynamischer request, also
	// wird ein file veschickt
			
	// rausfinden, ob das file überhaupt existiert
	ddiourl& url = _request.get_local_url();
	size_t length = _multibuffer.set_path( url );
	if( !length )
		return connection_handle_error( "sendfile: can't open: " + url.get_full_path(), ddwebresponse::ret_404_not_found );	
			
	// erstmal defaults für sendfile mode setzen
	int writemode = ddwebconnection::write_sendfile;
	int retcode   = ddwebresponse::ret_200_ok;

	// das file ist vorhanden & offen, jetzt nachschauen, ob es überhaupt
	// gebraucht wird
	if( url.get_last_modified_str() == _request.get_if_modified_since()  )
	{
		_multibuffer.set_file_size( 0 );
		retcode = ddwebresponse::ret_304_not_modified;
		writemode = ddwebconnection::write_not_modified;
	}	
	
	_multibuffer.set_write_mode( writemode );
			
	// nach wiedereinsprung: update timers
	_lastaction = ::time(0);	
	_response.set_date( _lastaction );
  
  // prepare header buffer
  _response.set_retcode( retcode );  
	_response.get_last_modified() = url.get_last_modified_str();
	_response.get_content_type() = _server->get_mime_type( url.get_file_type() ); 
	_response.set_content_length( length );  	
	// ... und in den buffer mit det janze
	_response.flush_data( _multibuffer.get_head_buffer() );
	
	// log speichern, bevor die connection resettet wird.
	create_log_entry();
	
	// wir sind fertig und können schreiben
	set_state_flags( con_writing );
	
	// und ab dafür
	return connection_handle_write();
			
}


/**
	handle_response: neue version: In den schreibmodus gehen, wird sowohl von 'innen' als 
	auch von 'aussen' aufgerufen, bei aufruf von aussen darf allerdings nicht gleich 
	losgeschrieben werden, da es sonst parallelzugriffe geben kann.
*/

int ddwebconnection::connection_handle_response( int retcode, const ddstring& type, bool writenow )
{
	// nach wiedereinsprung: update timers
	_lastaction = ::time(0);
	_response.set_date( _lastaction );
	// hier immer über den buffer	
	_multibuffer.set_write_mode( ddwebconnection::write_buffer );
  
  _response.get_content_type() = type;
  _response.set_retcode( retcode );
	_response.set_content_length( _multibuffer.get_body_buffer().max_output() );  	
	_response.flush_data( _multibuffer.get_head_buffer() );
		
	// debugging ausgabe
	ddstring req = 	_request.get_request_type_name() +  ": " + _request.get_raw_path();
	_response.dump( req );
		
	// log speichern, bevor die connection resettet wird.
	create_log_entry();
	
	// wir sind fertig und können schreiben
	set_state_flags( con_writing );
	
	// Wenn das hier aus einem anderen thread-context aufgerufen wird,
	// dann wird ein und derselbe buffer parallel geschrieben.
	// daher die Falluntscheidung.
	
	return writenow ? connection_handle_write() : 1;
}


int ddwebconnection::connection_handle_write()
{

	
	int result = _multibuffer.send();
	// EAGAIN: weitermachen
	if( result == 0 )
		return 0;
	
	// schiefgegangen?
	if( result < 0 )
		return connection_handle_close();
	
	// und zum nächsten state durchfallen lassen: reinit
	// response aufräumen
	_response.init_data();
 	
 	// wir möchten nicht weitermachen ...
	if( !get_request().is_keep_alive() )
		return connection_handle_close();
	
	// wir möchten doch weitermachen ...
  set_state_flags( con_reading );
	
	_multibuffer.restore_buffers();
	
	/*
	// buffer restaurieren: Achtung! da können noch weitere requests drin sein
	// Also: Wenn der buf nicht leer ist, mit erstmal mit dem
	// request-parser weitermachen
	
	_pending = _multibuffer.restore_buffers()  ? false : true;
	*/
	
	return 1;
	
}


/**
	"normaler" fehler, bad request etc. mit fehlermeldung
	Achtung! Die Connection wird hier _nicht_ geschlossen !?
	@param hint die Fehlermeldung
	@param errorcode der Fehlercode
	@param writenow: unterscheidung, ob direkt gesendet werden darf (interner aufruf) oder nicht (externer aufruf)
	@see handle_response()
*/

int ddwebconnection::connection_handle_error( const ddstring& hint, int errorcode, bool writenow )
{
	
 	_errortemplate[1] = _response.create_retcode_short( errorcode );
	_errortemplate[3] = _response.create_retcode( errorcode );
 	_errortemplate[5] = hint;	
 	_errortemplate[7] = _response.create_retcode( errorcode );
	_errortemplate[9] = hint;
	_errortemplate[11] = "Request: " + _request.get_raw_path();
	_errortemplate[13] = DDVERSION;
	
	log_error( "site error: " + hint );
	
	// Meldung in den buffer malen...
	get_buffer().append( _errortemplate.join() );
	_response.set_retcode( errorcode );
	
	// input buffer resetten
	//_multibuffer.reset();
	
	// und ab
	return connection_handle_response( errorcode, "text/html", writenow );

}


/**
	"Böser" Fehler, Verbindungsabbruch
*/

int ddwebconnection::connection_handle_close()
{
	// verbindung abbauen & buffer resetten
 	_multibuffer.close();
	// response aufräumen
	_response.init_data();
	// clear all flags
	set_state_flags( con_free );
	
	return -1;
}


/**
	Einen Logfile-Eintrag erzeugen
*/

void ddwebconnection::create_log_entry()
{
	
	_logentry = get_host_ip();
	
	_logentry += _response.get_date().to_string( " - - [%d/%b/%Y:%T %z] \"" );	
	_logentry += _request.get_request_type_name() +  " ";
	_logentry += _request.get_raw_path() + "\" "; 	
	_logentry += _response.get_retcode_short() +  " ";
	_logentry += _response.get_content_length() + " \"";
 	if( _request.get_referer().empty() )
		_logentry += "-\" \"";
	else
		_logentry += _request.get_referer()  + "\" \"";
	_logentry += _request.get_user_agent() +"\"";

}


/**
	Debug-String erzeugen
*/

ddstring ddwebconnection::to_string() const
{
	ddstring result = " connection: flags: " + ddstrutil::from_int( _flags );
	result += " IP: " + get_host_ip() + ": "; 
		  
	if ( is_free() ) 
		result += " free ";
	if ( is_active() )
		result += "active ";
	if ( is_locked() ) 
		result += " locked ";
	if ( is_writing() )
		result += " writing ";
	
	result += _request.get_raw_path();
	
	return result;
						
} 




