//-----------------------------------------------------------------------------
// Copyright © 2003 - Philip Howard - All rights reserved
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; either
// version 2.1 of the License, or (at your option) any later version.
//
// This library 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
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307	 USA
//-----------------------------------------------------------------------------
// package	libh/cgi
// 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 <stdlib.h>
#include <string.h>
#include <unistd.h>

#include "cgi_lib.h"


__FMACRO_BEGIN__
//-----------------------------------------------------------------------------
// function	cgi_form_parse_finish
//
// purpose	Finish the web page form parsing and release any resources
//		that might still be held, like the buffers for query string
//		and post content.
//
// arguments	-none-
//
// returns	(int) -1 = indicates an error occurred
//		(int)  0 = indicates no more variables available
//-----------------------------------------------------------------------------
#define cgi_form_parse_finish() cgi_form_parse((NULL),(NULL),(NULL),(NULL))

__FMACRO_END__

__PROTO_BEGIN__
//-----------------------------------------------------------------------------
// function	cgi_form_parse
//
// purpose	Get web page form variables from either query string or post
//		request method (MIME type application/x-www-form-urlencoded
//		only) or both, in a CGI conforming web server environment
//		(such as Apache).
//
//		Support for posted MIME type multipart/mixed and and uploaded
//		files (which use multipart/mixed) is NOT provided by this
//		function.
//
// usage	To get each form variable, call the cgi_form_parse() function
//		giving it pointers to where to store the following:
//			1.  where to store a pointer to the name string
//			2.  where to store the length of the name string
//			3.  where to store a pointer to the value string
//			4.  where to store the length of the value string
//		The return value indicates whether the variable is from the
//		query string (if environment variable QUERY_STRING is set
//		by the web server) or the posted content (if POST METHOD is
//		indicated in the environment variable REQUEST_METHOD and a
//		CONTENT_LENGTH is properly set by the web server).
//
//		The value returned by the function will be one of:
//			-1  indicates an error occurred
//			 0  indicates no more variables available
//			 1  indicates the variable is from the query string
//			 2  indicates the variable is from the post content
//
// arguments	1 (char * *) where to store pointer to name pointer
//		2 (size_t *) where to store pointer to name length
//		3 (char * *) where to store pointer to value pointer
//		4 (size_t *) where to store pointer to value length
//
// returns	(int) -1 = indicates an error occurred
//		(int)  0 = indicates no more variables available
//		(int)  1 = indicates the variable is from the query string
//		(int)  2 = indicates the variable is from the post content
//
// note		The use of cgi_form_map() and cgi_form_parse() in the same
//		program does not work.  if cgi_form_map() is used, it must
//		be the only caller of cgi_form_parse().
//
// note		Once a variable has been given to the caller, that variable
//		will not be given to the caller again.  Only one pass of the
//		variables is possible.  This is because the POST content
//		stream cannot be replayed and cgi_form_parse() does not keep
//		them.
//
// note		If the browser passes the same variable name multiple times,
//		which is valid for forms, then cgi_form_parse() will return
//		each of those variables with their values in the order the
//		browser sent them (usually in the same order they are in the
//		form).
//
// note		The same variable name may occur in both the query string as
//		as well as the post content.  Each will be returned separately
//		with the return value indicating which source the variable
//		came from.
//-----------------------------------------------------------------------------
int
cgi_form_parse (
    char * *	arg_name_str_ptr
    ,
    size_t *	arg_name_len_ptr
    ,
    char * *	arg_value_str_ptr
    ,
    size_t *	arg_value_len_ptr
    )
    __PROTO_END__
{

    //------------------------------------------
    // Define an ASCII indexed table with values
    // for valid cetal/hexadecimal characters.
    //------------------------------------------
#define __ -1
    static char		ascii_cetal	[256] = {
	__, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __,	// 00-0f
	__, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __,	// 10-1f
	__, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __,	// 20-2f
	 0,  1,  2,  3,  4,  5,  6,  7,  8,  9, __, __, __, __, __, __,	// 30-3f
	__, 10, 11, 12, 13, 14, 15, __, __, __, __, __, __, __, __, __,	// 40-4f
	__, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __,	// 50-5f
	__, 10, 11, 12, 13, 14, 15, __, __, __, __, __, __, __, __, __,	// 60-6f
	__, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __,	// 70-7f
	__, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __,	// 80-8f
	__, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __,	// 90-9f
	__, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __,	// a0-af
	__, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __,	// b0-bf
	__, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __,	// c0-cf
	__, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __,	// d0-df
	__, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __,	// e0-ef
	__, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __	// f0-ff
    };

    //---------------------------------------
    // Static data is kept between function calls.
    //---------------------------------------
    static int		state		= 0;
    static char *	query_data	;
    static char *	query_ptr	;
    static char *	post_data	;
    static char *	post_ptr	;

    //-------------------------------------
    // Check for first time initialization.
    //-------------------------------------
    if ( state == 0 ) {
	char *		content_length		;
	char *		query_string		;
	char *		request_method		;
	size_t		post_length		;

	//------------------------------------
	// Check for data in the query string.
	//------------------------------------
	query_data = NULL;
	if ( ( query_string = getenv( "QUERY_STRING" ) ) && * query_string ) {
	    if ( ! ( query_data = malloc( strlen( query_string ) + 1 ) ) ) return -1;
	    strcpy( query_data , query_string );
	}
	query_ptr = query_data;

	//-------------------------------
	// Check for POST request method.
	//-------------------------------
	post_data = NULL;
	if ( ! ( request_method = getenv( "REQUEST_METHOD" ) ) ) return -1;
	if ( strcmp( request_method , "POST" ) == 0 ) {

	    if ( ! ( content_length = getenv( "CONTENT_LENGTH" ) ) ) return -1;
	    if ( ( post_length = strtoul( content_length , NULL , 10 ) ) <= 0 ) return -1;
	    if ( ! ( post_data = malloc( post_length + 1 ) ) ) return -1;

	    //-------------------------------------
	    // Read in the entire post content data stream.
	    //-------------------------------------
	    {
		char *	read_ptr;
		size_t	read_len;

		read_ptr = post_data;
		read_len = post_length;
		while ( read_len ) {
		    ssize_t	num_read	;

		    if ( ( num_read = read( 0, read_ptr, read_len ) ) <= 0 ) break;
		    read_ptr += num_read;
		    read_len -= num_read;
		}
		* read_ptr = '\0';
	    }
	}
	post_ptr = post_data;

	//----------------------------------------------
	// Remember readiness for future function calls.
	//----------------------------------------------
	state = 1;
    }

    //-------------------------------------------
    // Check for a request to free all the space.
    //-------------------------------------------
    if ( ! arg_name_str_ptr ) {
	if ( state < 4 ) {
	    if ( query_data ) free( query_data );
	    if ( post_data ) free( post_data );
	}
	state = 4;
	return 0;
    }

    //-------------------------------
    // Now for the real parsing work.
    //-------------------------------
    {
	char *		name_str	;
	char *		value_str	;
	char *		parse_ptr	;
	char *		store_ptr	;
	size_t		name_len	;
	size_t		value_len	;
	int		source		;
	unsigned int	stop_ch		;
	unsigned int	this_ch		;

	//----------------------------------------------
	// If ready to parse query data, then select it.
	// If there is no query data, then skip to post.
	//----------------------------------------------
	parse_ptr = NULL;
	if ( state == 1 ) {
	    parse_ptr = query_ptr;
	    if ( ! parse_ptr ) state = 2;
	}

	//---------------------------------------------
	// If ready to parse post data, then select it.
	// If there is no post data, then skip to done.
	//---------------------------------------------
	if ( state == 2 ) {
	    parse_ptr = post_ptr;
	    if ( ! parse_ptr ) state = 3;
	}

	//-----------------------------------
	// If done, then return with nothing.
	//-----------------------------------
	if ( state > 2 ) return 0;

	//-------------------------------------------------
	// Get ready to parse for one name/value data item.
	//-------------------------------------------------
	store_ptr = parse_ptr;
	name_str = parse_ptr;
	name_len = 0;
	value_str = NULL;
	value_len = 0;
	stop_ch = '=';
	source = state;

	//------------------------------------------
	// Parse name/value one character at a time.
	//------------------------------------------
	for (;;) {
	    this_ch = * (unsigned char *) parse_ptr;

	    //---------------------------------------
	    // Check for end of name or end of value.
	    //---------------------------------------
	    if ( this_ch == stop_ch || this_ch == '&' || this_ch == '\0' ) {

		//-- Check if this is end of name.
		if ( stop_ch == '=' ) {
		    name_len = store_ptr - name_str;
		    * store_ptr = '\0';

		    //-- If a value follows, prepare to parse it and continue.
		    if ( this_ch == '=' ) {
			++ parse_ptr;
			value_str = ++ store_ptr;
			stop_ch = '&';
			continue;
		    }
		}

		//-- Else this is end of value.
		else {
		    value_len = store_ptr - value_str;
		    * store_ptr = '\0';
		}
		break;
	    }

	    //------------------------
	    // Translate '+' to blank.
	    //------------------------
	    else if ( this_ch == '+' ) {
		this_ch = ' ';
	    }

	    //----------------------------------------------------
	    // Translate '%xx' to cetal/hexadecimal encoded value.
	    //----------------------------------------------------
	    else if ( this_ch == '%' ) {
		int num;
		if ( ( num = ascii_cetal[(int)(*++parse_ptr)] ) < 0 ) continue;
		this_ch = num << 4;
		if ( ( num = ascii_cetal[(int)(*++parse_ptr)] ) < 0 ) continue;
		this_ch += num;
	    }

	    //-------------------------------------------
	    // Store (possibly converted) character back.
	    //-------------------------------------------
	    * store_ptr = this_ch;
	    ++ store_ptr;
	    ++ parse_ptr;
	}

	//-------------------------------------------
	// Put pointer back in the appropriate place.
	//-------------------------------------------
	++ parse_ptr;
	if ( state == 1 ) query_ptr	= parse_ptr;
	if ( state == 2 ) post_ptr	= parse_ptr;

	//------------------------------------------------------------
	// If this was the last name/value item, change state to next.
	//------------------------------------------------------------
	if ( this_ch == '\0' ) ++ state;

	//------------------------
	// Store back the results.
	//------------------------
	if ( arg_name_str_ptr ) * arg_name_str_ptr = name_str;
	if ( arg_name_len_ptr ) * arg_name_len_ptr = name_len;
	if ( arg_value_str_ptr ) * arg_value_str_ptr = value_str;
	if ( arg_value_len_ptr ) * arg_value_len_ptr = value_len;

	//--------------------------
	// Return with new variable.
	//--------------------------
	return source;
    }

}

