// implementation module for cgienv class #include #include // for sprintf() prototype #include // for atoi() prototype #include #include #include #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("", 9); ScriptLink.set_rundown ( "" ); 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 ( "", 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 ( "", 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 ( "", 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 ( "", 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 ( "", 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 ( "", 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 ( "", 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; jform.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("", 9); ScriptLink.set_rundown ( "" ); 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("", 9); ScriptLink.set_rundown ( "" ); 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; } }