// implementation module for cgienv class
#include <iostream.hxx>

#include <stdio.h>			// for sprintf() prototype
#include <stdlib.h>			// for atoi() prototype
#include <string.h>
#include <errno.h>
#include <ctype.h>
#include "scriptlink.hxx"		// Network connection to server.
#include "cgienv.hxx"

const static int default_table_size = 200;
const static char *standard_vars[] = { 
   "SERVER_PROTOCOL", "SERVER_NAME", "SERVER_PORT", "REQUEST_METHOD", 
   "PATH_INFO", "PATH_TRANSLATED", "SCRIPT_NAME", "SCRIPT_PATH", "QUERY_STRING",
   "REMOTE_USER", "REMOTE_ADDR", "REMOTE_PORT", "REMOTE_HOST",
   "AUTH_TYPE", "REMOTE_IDENT", "CONTENT_TYPE", "CONTENT_LENGTH", 
    "SERVER_SOFTWARE", "" };

cgienv CGI;				// Instantiate global.
//////////////////////////////////////////////////////////////////////////////
// Constructor, just mark object as not ready.
cgienv::cgienv()
{
    this->state = 0;
    this->content = (FILE *) 0;
    this->content_length = 0;
    this->content_pending = 0;
    this->content_cache_length = 0;
}

cgienv::~cgienv()
{
    if ( this->content ) fclose ( this->content );
    this->content = (FILE *) 0;
}
////////////////////////////////////////////////////////////////////////////////
//
int cgienv::init ( int argc, char **argv, int mode )
{
    int status, i;
    //
    // Built the var symbol table.
    //
    for ( i = 0; standard_vars[i][0]; i++ ) {
	struct symdef *sym;
	sym = &this->var.entry(standard_vars[i], default_table_size);
    }
    //
    //  Assign command line args to appropriate symbols values.
    //
    this->var.define("REQUEST_METHOD", argv[1]);
    this->var.define("SCRIPT_NAME", argv[2]);
    this->var.define("SERVER_PROTOCOL", argv[3]);
 
    status = this->var_load(argc, argv);
    if ( (status&1) == 0 ) return status; 
    //
    // Load content if present.
    //
    if ( this->content_pending ) {
	status = this->content_load();
	if ( (status&1) == 0 ) return status; 
    }
    this->state = 1;
    //
    // Place link into CGI mode.
    //
    if ( mode == 0 ) {
        status = ScriptLink.write("<DNETCGI>", 9);
        ScriptLink.set_rundown ( "</DNETCGI>" );
	this->state = 2;		// flag that output started.
    }
    return status;
}
////////////////////////////////////////////////////////////////////////////////
// Query the HTTP server for connection information and hedaer lines and
// construct the related CGI variables in the var symbol table.  If
// content-length header is present, set content_pending to force load. 
//
int cgienv::var_load(int argc, char **argv)
{
    int status, i, length;
    char rsp[4096], *value, *nextv;
    struct symdef *sym;
    //
    // Ask server for version and connection information.
    //
    status = ScriptLink.query ( "<DNETID2>", rsp, sizeof(rsp)-1, length );
    if ( (status&1) == 0 ) return status;
    rsp[length] = '\0';
    //
    // THe ID2 response is:
    //     software srv-name lcl-port rem-port ip-addr [rem-user [rem-host]]
    //
    // Elements are separated by a single space.
    //
    value = strchr ( rsp, ' ' );	/* Parse out first field */
    if ( value ) *value++ = '\0';	/* terminate */
    this->var.define("SERVER_SOFTWARE",rsp);

    if ( value ) {
	nextv = strchr ( value, ' ' );
	if ( nextv ) *nextv++ = '\0';
	this->var.define("SERVER_NAME",value);
	value = nextv;
    }    
    if ( value ) {
	nextv = strchr ( value, ' ' );
	if ( nextv ) *nextv++ = '\0';
	this->var.define("SERVER_PORT",value);
	value = nextv;
    }    
    if ( value ) {
	nextv = strchr ( value, ' ' );
	if ( nextv ) *nextv++ = '\0';
	this->var.define("REMOTE_PORT",value);
	value = nextv;
    }

    if ( value ) {
	nextv = strchr ( value, ' ' );
	if ( nextv ) *nextv++ = '\0';
	char dot_format[16];
	int addr = atoi ( value );
	sprintf ( dot_format, "%d,%d,%d,%d", (addr&255),
	    ((addr>>8)&255), ((addr>>16)&255), ((addr>>24)&255) );
	this->var.define("REMOTE_ADDR",dot_format);
	value = nextv;
    }
    if ( value ) {
	nextv = strchr ( value, ' ' );
	if ( nextv ) *nextv++ = '\0';
	this->var.define("REMOTE_USER",value);
	value = nextv;
    }
    if ( value ) {
	nextv = strchr ( value, ' ' );
	if ( nextv ) *nextv++ = '\0';
	this->var.define("REMOTE_HOST",value);
	value = nextv;
    } else {
	// fallback to use REMOTE_ADDR
	struct symdef *addr;
	addr = &this->var.entry("REMOTE_ADDR");
	this->var.define("REMOTE_HOST", addr->value);
    }
    //
    //  Construct script_path variable.
    //
    ScriptLink.query ( "<DNETPATH>", rsp, sizeof(rsp)-1, length );
    if ( (status&1) == 0 ) return status;
    rsp[length] = '\0';
    this->var.define("SCRIPT_PATH",rsp);

    if ( strncmp ( rsp, argv[2], length ) == 0 ) {
	//
	// Server's path and translations agree, copy portion after first slash.
	//
	char *p;
	if ( length > 0 ) {
	    // Path info follows script name
	    for ( p = &argv[2][length]; *p && (*p != '/'); p++ );
	    this->var.define("PATH_INFO",p);
	} else {
	    // Get original URL to bypass server translation done on arg2
	    int plen;
	    ScriptLink.query ( "<DNETRQURL>", rsp, sizeof(rsp)-1, plen );
	    if ( (status&1) == 0 ) return status;
	    for ( plen=0; rsp[plen]; plen++ ) if ( rsp[plen] == '?' ) 
		{ rsp[plen] = '\0'; break; }
	    net_unescape_string ( rsp, &plen );
	    rsp[plen] = '\0';
	    this->var.define("PATH_INFO",rsp);
	    p = rsp;
	}
	//
	this->translate ( p, rsp, sizeof(rsp) );
	this->var.define("PATH_TRANSLATED", rsp);

	sym = &this->var.entry("SCRIPT_NAME",0);
	if ( strlen(sym->value) > length ) {
	    for ( p = &sym->value[length]; *p && (*p != '/' ); p++ );
	    *p = '\0';		// remove path info from script name.
	}
    }
    //
    // Check for query string, if defined, strip leading '?' included in
    // server's response.
    //
    status = ScriptLink.query ( "<DNETARG>", rsp, sizeof(rsp)-1, length );
    if ( (status&1) == 0 ) return status;
    if ( length > 0 ) {
	rsp[length] = '\0';
	this->var.define("QUERY_STRING",&rsp[1]);
    }
    //
    // Retrieve request header lines and construct or produce vars for them.
    // DNETHDR response is 1 message per header line, terminated by
    // a zero length line.
    //
    status = ScriptLink.write ( "<DNETHDR>", 9 );
    if ( (status&1) == 0 ) return status;
    do {
	char *label;
	int colon;

	status = ScriptLink.read ( rsp, sizeof(rsp), length );
	if ( (status &1) == 0 ) return status;
	/* Parse out header label */
	for ( colon = 0; colon < length; colon++ ) if ( rsp[colon]==':' ) {
	    /*
	     * Construct label.  Upcase characters and convert '-' to '_'.
	     */
	    label = new char[colon + 6];
	    
	    strcpy ( label, "HTTP_" );
	    for ( i = 0; i < colon; i++ ) {
		label[i+5] = _toupper(rsp[i]);
		if ( label[i+5] == '-' ) label[i+5] = '_';
	    }
	    label[colon+5] = '\0';
	    /*
	     * Trim leading whitespace.
	     */
	    value = rsp;
	    value[length] = '\0';
	    for ( colon++; isspace(value[colon]); colon++ );
	    /*
	     * Check for special header lines that go into pre-defined
	     * variables.
	     */
	    if ( 0 == strcmp(label,"HTTP_CONTENT_LENGTH") ) {
		this->var.define ( "CONTENT_LENGTH", &value[colon] );
		this->content_length = atoi ( &value[colon] );
		this->content_pending = this->content_length;
	    } else if ( 0 == strcmp ( label, "HTTP_CONTENT_TYPE") ) {
		this->var.define ( "CONTENT_TYPE", &value[colon] );
	    } else if ( (sym=&this->var.entry(label,1)) ) {
		/*
		 * Add new symbol or append to existing.
		 */
		if ( sym->state ) {
		    //  Existing value.
		    //
		    int old_length = strlen ( sym->value );
		    char *new_value = 
			new  char[old_length+3+strlen(&value[colon])];
		    strcpy ( new_value, sym->value );
		    strcpy ( &new_value[old_length], ", " );
		    strcpy ( &new_value[old_length+2], &value[colon] );
		    delete[] sym->value;
		    sym->value = new_value;
		} else {
		    // New instance created.
		    //
		    sym->value = new char[strlen(value)+1];
		    strcpy ( sym->value, value );
		    sym->state = 1;
		}
		colon = 0;
	    } else { // Error creating var entry
	    }
	    delete[] label;
	    break;
	}

	if ( (colon >= length) && (length > 0) ) {
	   /* Continuation header line, handle later */
	}
    } while ( length > 0 );	// continue until null line read

    return status;
}
////////////////////////////////////////////////////////////////////////////////
// Query server for request content and save content for retrieval.  Number of
// bytes to read from server determined by member content_pending.
//
int cgienv::content_load()
{
    int status, length;
    //
    // check content limit.
    //
    char *cl_limit = getenv("WWW_MAX_CGIENV_CONTENT");
    if ( cl_limit ) {
	if ( this->content_pending > atoi(cl_limit) ) {
	}
    }
    //
    // Determine whether content will fit into memory buffer or must be
    // kept in temporary file.
    //
    if ( this->content_pending < sizeof(this->content_cache) ) {
	    // All of content will fit into memory.
	    this->content_cache_length = this->content_pending;
    } else {
	//
	// Make temporary file (automatic delete on close)
	//
	this->content = fopen ( tmpnam(NULL), "w+", "fop=dlt", "mbc=64" );
    }
    if ( this->content || this->content_cache_length >= 0 ) {
	//
	// Query server for content.
	//
	int remaining, k;
	char *content_type, buffer[4096];
	/*
	 * Request the file a chunk at a time.
	 */
	for ( remaining = this->content_pending; remaining > 0;
		  remaining = remaining - length ) {
	    //
	    // Get server to read more data from client and sent it to us.
	    //
	    status = ScriptLink.write ( "<DNETINPUT>", 11 );
	    if ( (status&1) == 0 ) break;
	    //
	    // Read the sent data and output to contents file.
	    //
	    status = ScriptLink.read ( buffer, sizeof(buffer), length );
	    if ( (status&1) == 0 ) break;
	    if ( length > 0 ) {
		if ( this->content ) {
		    status = fwrite(buffer, length, 1, this->content);
		} else {
		    // Store in cache.
		    if ( length+this->content_cache_pos >
					sizeof(this->content_cache) )
			length = sizeof(this->content_cache) - 
				this->content_cache_pos;
		    memcpy ( &this->content_cache[content_cache_pos],
				buffer, length);
		    this->content_cache_pos += length;
		}
	    }

	    if ( status == 0 ) { status = vaxc$errno; break; }/* error */
	}
	if ( remaining > 0 ) {
	    fprintf ( stderr,
		"Error getting request contents: %s\n(continuing)\n",
		strerror ( EVMSERR, status ) );
	}
	/*
	 * Reset for reads.
	 */
	if ( this->content ) fseek ( this->content, 0, 0 );
	else content_cache_pos = 0;

    } else {
	fprintf(stderr,"Error opening request contents temp file:\n%s\n",
			strerror(errno,vaxc$errno) );
	status = 2160;
    }

    this->content_pending = 0;
    return status;
}
////////////////////////////////////////////////////////////////////////////////
//  Translate path.  Translation string is always nul terminated.
//
int cgienv::translate ( const char *path, char *translation, int bufsize )
{
    if ( this->state > 1 ) {
	// Not in state to ask for translations
	*translation = '\0';
	return 0;
    }
    //
    // Send dnetxlate tag followed by path to translate and read response
    // into callers buffer.
    //
    int length, status;
    status = ScriptLink.write ( "<DNETXLATEV>", 12 );
    if ( (status&1) == 1 )  status = ScriptLink.query 
		( path, translation, bufsize-1, length);
    if ( (status&1) == 0 ) length = 0;
    translation[length] = '\0';
    return status;
}
////////////////////////////////////////////////////////////////////////////////
// Parse form data and build the form symbol table.  Form data has the format
// "sym=value&sym=value&..." with spaces encoded as pluses.
//
// Return value is number of symbols defined or 0 for error.
//
int cgienv::parse_form()
{
    int status, length;
    //
    // First determine source of form data, preferred source is via POST
    // method (content), but use query argument as fallback.
    // Result fdata points to a new string containing the data.
    //
    char *value, *fdata = "";
    if ( this->content_length > 0 ) {
	//  Allocate buffer and read entire contents into it.
	fdata = new char[this->content_length+1];
	if ( !fdata ) length = 0;
	else length = this->read_content(fdata, this->content_length);
	fdata[length] = '\0';
    } else {
	//  Query string, METHOD=GET
	//
	value = this->var["QUERY_STRING"];
	if ( value ) {
	    length = strlen ( value );
	    fdata = new char[length+1];
	    strcpy ( fdata, value );
	} else length = 0;
    }
    //
    // Scan the string and parse.
    //
    if ( length > 0 ) {
	int i, j, start, finish, flen;
	//
	// Cleanup the tail so it is guaranteed to end with '&'
	//
	if ( fdata[length-1] != '&' ) fdata[length++] = '&';
	//
	start = finish = 0;
	for ( i=0; i < length; i++ ) if ( !fdata[i] || (fdata[i] == '&') ) {
	    //
	    // End of value seen.  Unescape character and look for first '='
	    //
	    flen = i - start;
	    for ( j=start; j < i; j++ ) if ( fdata[j] == '+' ) fdata[j] = ' ';

	    net_unescape_string ( &fdata[start], &flen );
	    finish = start + flen;	// length may go down, so redo finish.
	    for ( j=start; j<finish; j++ ) if ( fdata[j] == '=' ) {
		int k;
		//
		// Lookup/create table entry for form symbol.
		//
		fdata[j] = '\0';
		struct symdef *sym = &this->form.entry( &fdata[start], 50 );
		if ( !sym ) {
		    // serious error, unable to allocate new entry.
		    continue;
		}
		//
		// Define or append value.
		//
		flen = finish - j - 1;
		if ( sym->state == 0 ) {	// new value.
		    sym->value = new char[flen+1];
		    strncpy ( sym->value, &fdata[j+1], flen );
		    sym->value[flen] = '\0';
		    sym->state = 1;
		} else {			// existing value.
		}
	    } else {
		// Make field name upcase and convert hyphens to underscore

		fdata[j] = _toupper(fdata[j]);
		if ( fdata[j] == '-' ) fdata[j] = '_';
	    }
	    start = i+1;
	}
	//
	// Final cleanup.
	//
        delete[] fdata;
    }

    return 1;
}
////////////////////////////////////////////////////////////////////////////////
// Symbol table initialization
symbol_table::symbol_table()
{
    this->size = this->used = 0;
}

///////////////////////////////////////////////////////////////////////////////
// General entry lookup/addition.
//
struct symdef &symbol_table::entry 
	( const char *name, const int extend_size )
{
    int i;
    //
    // Scan for existing match, and simply return it.
    //
    for ( i = 0; i < this->used; i++ ) {
	if ( strcmp ( this->sym[i].name, name ) == 0 ) {
		return this->sym[i];
	}
    }
    //
    // No match, see if we can extend table.
    //
    if ( extend_size <= 0 ) return *((struct symdef *) 0);
    //
    // Allocate new entry.
    //
    if ( this->size == 0 ) {
	// Initial allocation
	this->sym = new struct symdef[extend_size];
	this->size = extend_size;
    } else if ( this->used >= this->size ) {
	// grow table.
    }
    //
    // Initialize
    this->used = i+1;
    this->sym[i].name = new char[strlen(name)+1];
    strcpy ( this->sym[i].name, name );
    this->sym[i].value = (char *) 0;
    this->sym[i].state = 0;

    return this->sym[i];
}

/////////////////////////////////////////////////////////////////////////////
// Public define.
int symbol_table::define(const char *symname, char *value)
{
    int status = 1;
    struct symdef &sym = this->entry(symname, default_table_size );
    if ( sym.state == 1 ) {
	delete[] sym.value;
	status = 3;
    } else sym.state = 1;

    sym.value = new char[strlen(value)+1];
    strcpy ( sym.value, value );
    return status;
}

//////////////////////////////////////////////////////////////////////////////
// array operator, looks up CGI variable and returns char pointer to value.
//
char *symbol_table::operator[] ( const char *symname )
{
    int i;
    struct symdef *sptr = this->sym;
    for ( i = 0; i < this->used; i++ ) {
	if ( strcmp ( sptr->name, symname ) == 0 ) return sptr->value;
	sptr++;
    }
    return (char *) 0;		// no match
}

//////////////////////////////////////////////////////////////////////////////
// shift operator outputs data.  Simply pass through to scriptlink.
//
cgienv &cgienv::operator<<(const char *str)
{
   if ( this->state == 1 ) {
        int status = ScriptLink.write("<DNETCGI>", 9);
        ScriptLink.set_rundown ( "</DNETCGI>" );
	this->state = 2;		// flag that output started.
    }
    ScriptLink << str;
    return *this;
}

cgienv &cgienv::operator<<(const int number )
{
   if ( this->state == 1 ) {
        int status = ScriptLink.write("<DNETCGI>", 9);
        ScriptLink.set_rundown ( "</DNETCGI>" );
	this->state = 2;		// flag that output started.
    }
    ScriptLink << number;
    return *this;
}
/////////////////////////////////////////////////////////////////////////////
// Scan function iterates returns the elements in a table over successive
// calls.
//
int symbol_table::scan ( symbol_table_scan &ctx, char *&name, char *&value )
{
    int ndx = ctx;
    if ( ndx >= this->used ) return 0;
    name = this->sym[ndx].name;
    value = this->sym[ndx].state ? this->sym[ndx].value : (char *) 0;
    ctx = ndx+1;
    return 1;
}
///////////////////////////////////////////////////////////////////////////////
// Read MIME content sent by client.  Return value is number of bytes read,
// zero for EOF, -1 for error.
//
int cgienv::read_content(char *buffer, int bufsize )
{
    if ( this->content_cache_length > 0 ) {
	// Extract from cache buffer
	int pos, i;
	pos = this->content_cache_pos;
	if ( pos >= this->content_cache_length ) return 0;	/* EOF */
	for ( i = 0; i < bufsize; i++ ) {
	    if ( pos >= this->content_cache_length ) break;
	    buffer[i] = this->content_cache[pos++];
	}
	return i;
    } else if ( this->content ) {
	// read from temporary file
	int status = fread(buffer, bufsize, 1, this->content);
	return status;
    } else {
	// Error, no content.
	return -1;
    }
}
