//-----------------------------------------------------------------------------
// Copyright © 2004 - Philip Howard - All rights reserved
//
// 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.
//-----------------------------------------------------------------------------
// package	libh/cgi
// purpose	Demonstrate the LIBH CGI functions in a sample CGI program
//		which can also be used as a CGI based tool.
// homepage	http://libh.slashusr.org/
//-----------------------------------------------------------------------------
// author	Philip Howard
// email	libh at ipal dot org
// homepage	http://phil.ipal.org/
//-----------------------------------------------------------------------------
// This file is best viewed using a fixed spaced font such as Courier
// and in a display at least 120 columns wide.
//-----------------------------------------------------------------------------
#include <errno.h>
#include <grp.h>
#include <pwd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <unistd.h>

#include <libh/cgi.h>
#include <libh/http.h>
#include <libh/io.h>
#include <libh/map.h>
#include <libh/string.h>
#include <libh/time.h>

//-----------------------------------------------------------------------------
// program	submit (cgi)
//
// purpose	As a demonstration of using LIBH/cgi functions, this
//		program processes and stores web form submissions.
//-----------------------------------------------------------------------------

#define DATA_BUFFER_SIZE	65536


//-----------------------------------------------------------------------------
// main_cgi
//-----------------------------------------------------------------------------
int
main (
    int		argc
    ,
    char * *	argv
    )
{
    MAP			var_map			;

    FILE *		html_file		;

    split_t		this_split		;

    char * *		var_ptr			;
    char * *		var_split		;

    char *		data_end		;
    char *		data_ptr		;
    char *		var_name		;
    char *		var_value		;

    const char *	argument_name		;
    const char *	http_user_agent		;
    const char *	remote_addr		;
    const char *	remote_port		;
    const char *	request_method		;
    const char *	script_filename		;
    const char *	script_name		;
    const char *	server_name		;
    const char *	short_argument_name	;
    const char *	short_script_filename	;
    const char *	short_script_name	;

    size_t		data_len		;
    size_t		var_name_len		;
    size_t		var_value_len		;

    int			data_fd			;
    int			var_lines		;

    char		current_directory	[PATH_MAX+1];
    char		data_directory		[PATH_MAX+1];
    char		data_name		[PATH_MAX+1];

    char		data_buffer		[DATA_BUFFER_SIZE];


    //---------------------------------------------------------------
    // Get the name of the script from the arguments and environment.
    //---------------------------------------------------------------
    argument_name = argv[0];
    short_argument_name = str_tail( argument_name, '/', 1 );

    //--------------------------------------------
    // Check to see if we are running in NPH mode.
    // If so, set HTTP full mode.
    //--------------------------------------------
    if ( short_argument_name[0] == 'n' &&
	 short_argument_name[1] == 'p' &&
	 short_argument_name[2] == 'h' &&
	 short_argument_name[3] == '-' ) http_set_full( 1 );

    //-----------------------------------------------
    // Make sure we are running in a CGI environment.
    //-----------------------------------------------
    request_method = getenv( "REQUEST_METHOD" );
    if ( ! request_method ) {
	http_error_return( 1, HTTP_RESPONSE_500, "Internal server error ", "1" );
    }

    //----------------------------------------
    // Make sure this is a GET or POST method.
    //----------------------------------------
    if ( strcmp( request_method , "GET" ) != 0 && strcmp( request_method , "POST" ) != 0 ) {
	http_error_return( 1, HTTP_RESPONSE_500, "Internal server error ", "2" );
    }

    //--------------------------------------------------
    // Verify SCRIPT_NAME is consistent with argument 0.
    //--------------------------------------------------
    if ( ( script_name = getenv( "SCRIPT_NAME" ) ) ) {
	short_script_name = str_tail( script_name, '/', 1 );
	if ( strcmp( short_script_name, short_argument_name ) != 0 ) {
	    http_error_return( 1, HTTP_RESPONSE_500, "Internal server error ", "3" );
	}
    }

    //------------------------------------------------------
    // Verify SCRIPT_FILENAME is consistent with argument 0.
    //------------------------------------------------------
    if ( ( script_filename = getenv( "SCRIPT_FILENAME" ) ) ) {
	short_script_filename = str_tail( script_filename, '/', 1 );
	if ( strcmp( short_script_filename, short_argument_name ) != 0 ) {
	    http_error_return( 1, HTTP_RESPONSE_500, "Internal server error ", "4" );
	}
    }

    //---------------------------------------------------------------------------
    // Create a mapping to collect submitted data for response page substitution.
    //---------------------------------------------------------------------------
    if ( ! ( var_map = map_str_new() ) ) {
	http_error_return( 1, HTTP_RESPONSE_500, "Internal server error ", "5" );
    }

    //------------------------------------
    // Set up submission meta information.
    //------------------------------------
    data_end = ( data_ptr = data_buffer ) + 72;
    while ( data_ptr < data_end ) * data_ptr ++ = '-';
    * data_ptr ++ = '\n';
    * data_ptr = 0;

    etime_to_str_app( data_buffer, sizeof (data_buffer), "*time = %Y-%m-%d %H:%M:%S.%U\n", current_etime() );

    str_app_str( data_buffer, sizeof (data_buffer), "*request_method = " );
    str_app_str( data_buffer, sizeof (data_buffer), request_method );
    str_app_ch( data_buffer, sizeof (data_buffer), '\n' );

    if ( ( remote_addr = getenv( "REMOTE_ADDR" ) ) ) {
	str_app_str( data_buffer, sizeof (data_buffer), "*remote_addr = " );
	str_app_str( data_buffer, sizeof (data_buffer), remote_addr );
	str_app_ch( data_buffer, sizeof (data_buffer), '\n' );
    }

    if ( ( remote_port = getenv( "REMOTE_PORT" ) ) ) {
	str_app_str( data_buffer, sizeof (data_buffer), "*remote_port = " );
	str_app_str( data_buffer, sizeof (data_buffer), remote_port );
	str_app_ch( data_buffer, sizeof (data_buffer), '\n' );
    }

    if ( ( http_user_agent = getenv( "HTTP_USER_AGENT" ) ) ) {
	str_app_str( data_buffer, sizeof (data_buffer), "*http_user_agent = \"" );
	str_app_str( data_buffer, sizeof (data_buffer), http_user_agent );
	str_app_str( data_buffer, sizeof (data_buffer), "\"\n" );
    }

    //------------------------
    // Collect submitted data.
    //------------------------
    split_init( this_split );
    split_sep_crlf( this_split );
    while ( cgi_form_parse( & var_name, & var_name_len, & var_value, & var_value_len ) > 0 ) {

	//-- Make sure there is some data for this variable.
	if ( var_name_len == 0 ) continue;
	if ( var_value_len == 0 ) continue;

	//-- Variable names must beging with an alphabetic character.
	if ( ! isalpha( * var_name ) ) continue;

	//-- Clean up submitted name and value.
	{
	    char *p, *q;
	    q = p = var_name;
	    while ( * p ) {
		if ( * p >= 32 ) * q ++ = * p;
		++ p;
	    }
	    * q = 0;

	    q = p = var_value;
	    while ( * p ) {
		if ( * p >= 32 || * p == '\n' ) * q ++ = * p;
		++ p;
	    }
	    * q = 0;
	}

	//-- Split value by lines.
	split_data_str( this_split, var_value );
	var_lines = split_to_common( this_split, & var_split );
	if ( var_lines == 0 ) continue;

	//-- Store this data in the map for later substituting.
	if ( map_str_insert( var_map, var_name ) == 0 ) {
	    map_ca_store( var_map, var_value, var_value_len );
	}

	//-- If one line, handle as a simple name = value.
	if ( var_lines == 1 ) {
	    str_app_str( data_buffer, sizeof (data_buffer), var_name );
	    str_app_str( data_buffer, sizeof (data_buffer), " = " );
	    str_app_str( data_buffer, sizeof (data_buffer), var_value );
	    str_app_ch( data_buffer, sizeof (data_buffer), '\n' );
	    continue;
	}

	//-- If multiple lines, handle as a block of text.
	str_app_str( data_buffer, sizeof (data_buffer), var_name );
	str_app_str( data_buffer, sizeof (data_buffer), " : " );
	snprintf( data_buffer + strlen( data_buffer ),
		  sizeof (data_buffer) - strlen( data_buffer ),
		  "%d lines\n", var_lines );

	//-- Format each line.
	var_ptr = var_split;
	while ( * var_ptr ) {

	    //-- If last line is empty, skip it.
	    if ( -- var_lines == 0 ) {
		if ( ! * * var_ptr ) break;
	    }

	    //-- Record the line with a tab.
	    str_app_ch( data_buffer, sizeof (data_buffer), '\t' );
	    str_app_str( data_buffer, sizeof (data_buffer), * var_ptr );
	    str_app_ch( data_buffer, sizeof (data_buffer), '\n' );

	    ++ var_ptr;
	}

	//-- Release the buffer memory.
	free( var_split );
    }
    split_end( this_split );

    //------------------------------------------------------
    // If too much data is submitted, give an error message.
    //------------------------------------------------------
    if ( strlen( data_buffer ) > ( sizeof (data_buffer) - 1 ) ) {
	http_error_return( 1, HTTP_RESPONSE_500, "Internal server error ", "6" );
    }

    //----------------------------------------
    // Get the data name from the script name.
    //----------------------------------------
    data_len = str_select_cpy( data_name, sizeof (data_name), argument_name, "^1c>)", '.' );
    if ( strlen( data_name ) < data_len ) {
	http_error_return( 1, HTTP_RESPONSE_500, "Internal server error ", "7" );
    }
    data_end = data_name + data_len;

    //------------------------------------------
    // Make sure the current directory is known.
    //------------------------------------------
    if ( ! getcwd( current_directory, sizeof (current_directory) ) ) {
	http_error_return( 1, HTTP_RESPONSE_500, "Internal server error ", "8" );
    }

    //-------------------------------------------
    // If this is a "cgi-bin" or "cgi" directory,
    // change to the "data" directory peer to it.
    //-------------------------------------------
    {
	char *p;
	p = str_tail( current_directory, '/', 1 );
	if ( strcmp( p, "cgi-bin" ) == 0 || strcmp( p, "cgi" ) == 0 ) {
	    str_select_cpy( data_directory, sizeof (data_directory), "$1c<)", current_directory, '/' );
	    str_app_str( data_directory, sizeof (data_directory), "data" );
	    if ( chdir( data_directory ) != 0 ) {
		http_error_return( 1, HTTP_RESPONSE_500, "Internal server error ", "9" );
	    }
	    if ( ! getcwd( current_directory, sizeof (current_directory) ) ) {
		http_error_return( 1, HTTP_RESPONSE_500, "Internal server error ", "10" );
	    }
	    if ( strcmp( current_directory, data_directory ) != 0 ) {
		http_error_return( 1, HTTP_RESPONSE_500, "Internal server error ", "11" );
	    }
	}
    }

    //----------------------------------------------------------------------------
    // Look for where to store the data.
    //----------------------------------------------------------------------------
    // If there is a directory with the data name, then open a unique file inside.
    // If there is a file with the data name plus ".dat" extension, open it.
    //----------------------------------------------------------------------------
    data_ptr = data_buffer;
    data_fd = -1; // older gcc compilers get confused without this.
    if ( chdir( data_name ) == 0 ) {
	int n;
	for ( n = 0; n < 16; ++ n ) {
	    if ( ( data_fd = open_unique( "submit-%Y-%m-%d-%H%M%S-%U.dat", 0, 644 ) ) >= 0 ) break;
	}
	++ data_ptr;
	if ( data_fd < 0 ) {
	    http_error_return( 1, HTTP_RESPONSE_500, "Internal server error ", "12" );
	}
    } else {
	data_fd = open_join( O_WRONLY | O_APPEND, 0644, data_name, ".dat" );
	str_app_ch( data_buffer, sizeof (data_buffer), '\n' );
	if ( data_fd < 0 ) {
	    http_error_return( 1, HTTP_RESPONSE_500, "Internal server error ", "13" );
	}
    }

    //---------------------------------------------------------
    // Write submitted data all at once, sync it, and close it.
    //---------------------------------------------------------
    write( data_fd, data_buffer, strlen( data_buffer ) );
    fsync( data_fd );
    close( data_fd );

    //--------------------------------------------------------
    // If there is a symlink pointing to a URL, do a redirect.
    //--------------------------------------------------------
    if ( readlink_join( data_buffer, sizeof (data_buffer), data_name, ".url" ) == 0 &&
	 memcmp( data_buffer, "http://", 7 ) == 0 ) {
	http_redirect( "%s", data_buffer );
	return 0;
    }

    //--------------------------------
    // Output HTTP for a dynamic page.
    //--------------------------------
    http_no_cache();
    http_header( 0, "Content-Type", "text/html", NULL );
    http_commit_put();

    //----------------------------------
    // Open response document to output.
    //----------------------------------
    html_file = fopen_join( "r", data_name, ".html" );

    //-------------------------------------------------------
    // If the document is not present, output a default page.
    //-------------------------------------------------------
    if ( ! html_file ) {
	fputs( "<html>"
	       "<head><title>Thank you</title></head>"
	       "<body bgcolor=white>"
	       "<h1>Thank you for your input</h1>",
	       stdout );
	if ( ( server_name = getenv( "SERVER_NAME" ) ) ) {
	    printf( "<h3><a href=\"http://%s/\">http://%s/</a></h3>", server_name, server_name );
	}
	fputs( "</body>"
	       "</html>\n",
	       stdout );
	return 0;
    }

    //-----------------------------------------------------
    // Read response document while substituting variables.
    //-----------------------------------------------------
    for (;;) {
	int ch;
	ch = fgetc( html_file );
	if ( ch == EOF ) break;
	if ( ch == '$' ) {
	    ch = fgetc( html_file );
	    if ( ch == EOF ) break;
	    if ( ch == '{' ) {

		//-- Collect variable name.
		data_end = ( data_ptr = data_name ) + sizeof (data_name) - 1;
		for (;;) {
		    ch = fgetc( html_file );
		    if ( ch == EOF || ch == '}' ) break;
		    * data_ptr ++ = ch;
		}
		if ( ch == EOF ) break;
		* data_ptr = 0;

		//-- Look up variable in map and output its value.
		if ( data_ptr > data_name && map_str_find_exact( var_map, data_name ) == MAP_DATA_CA ) {
		    fputs( map_ca_fetch_ptr( var_map ), stdout );
		}

	    } else {
		fputc( '$', stdout );
	    }
	} else {
	    fputc( ch, stdout );
	}
    }

    return 0;
}

