/* ================================================================ */
/* Copyright (c) 2003 Yee Hsu.                                      */
/*                                                                  */
/* @ cgi.cpp                                                        */
/* @ 05-28-03                                                       */
/*                                                                  */
/* class CGI implementation file -                                  */
/*      This class implements the CGI engine that will serve as     */
/*      the bridge to connect the underlying data structure to      */
/*      the web (www). All queries will be supplied by the form     */
/*      input from the HTTP request and passed into this engine     */
/*      to search for the inventory record stored in the data list. */
/*      The information is then passwd by up to the HTML generator  */
/*      for GUI display to the user.                                */
/*                                                                  */
/* Member Functions/Variables -                                     */
/*      CGI();                                                      */
/*      ~CGI();                                                     */
/*      pcgi getcgivars(void);                                      */
/*      cgiv getcgivalue(const char*);                              */
/*      char x2c(char*);                                            */
/*      void unescape_url(char*);                                   */
/*      void process_cgivars(void);                                 */
/*                                                                  */
/*      pcgi cgivars;                                               */
/* ================================================================ */

#include "invent.h"

// ====================================================================
// CGI constructor
// ====================================================================

CGI::CGI()
{
    this->cgivars = NULL;
    this->process_cgivars();
}

// ====================================================================
// CGI destructor
// ====================================================================

CGI::~CGI()
{
    /** Free anything that needs to be freed **/
    for (int i=0; cgivars[i]; i++)
    {
        free(cgivars[i]) ;
        cgivars[i] = NULL;
    }
    free(cgivars) ;
    cgivars = NULL;
}

// ====================================================================
// Identifier:  pcgi CGI::getcgivars(void)
//
// Description: Returns the CGI key-value pair...
//              cgivar[0] = key[0]      cgivar[1] = value[0]
//              cgivar[2] = key[1]      cgivar[3] = value[1]
//              cgivar[4] = key[2]      cgivar[5] = value[2]
//              ...                     ...
//              cgivar[i] = NULL
// ====================================================================

pcgi CGI::getcgivars(void) const
{
    return this->cgivars;
}

// ====================================================================
// Identifier:  cgiv CGI::getcgivalue(const char* cgivar_key)
//
// Parameters:  char* cgivar_key    - the first pair (name) of CGI
//                                    name-value pair
//
// Returns:     cgi variable base on given key
//
// Description: Searches the cgi variable table base on the given key
//              and returns the value associated to it
// ====================================================================

cgiv CGI::getcgivalue(const char* cgivar_key) const
{
    for (int i=0; cgivars[i]; i+=2)
    {
        if (strcasecmp(cgivar_key, cgivars[i]) == 0)
            return cgivars[i+1];
    }
    return NULL;
}

// ====================================================================
// Identifier:  char CGI::x2c(char *what)
//
// Parameters:  char* what      - the hexidecimal representation of
//                                the character
//
// Returns:     digit - the character represented by the hex(what)
//
// Description: Converts the 2-character hexidecimal character into the
//              character it represents and return it
// ====================================================================

char CGI::x2c(const char *what) const
{
   register char digit;

   digit = (what[0] >= 'A' ? ((what[0] & 0xdf) - 'A')+10 : (what[0] - '0'));
   digit *= 16;
   digit += (what[1] >= 'A' ? ((what[1] & 0xdf) - 'A')+10 : (what[1] - '0'));
   return(digit);
}

// ====================================================================
// Identifier:  void CGI::unescape_url(char *url)
//
// Parameters:  char* url       - the QUERY_STRING remaining part of
//                                the URL to be unescaped.
//
// Description: Reduce any %xx escape sequences to the characters
//              they represent.
// ====================================================================

void CGI::unescape_url(char *url)
{
    register int i,j;

    for(i=0,j=0; url[j]; ++i,++j)
    {
        if((url[i] = url[j]) == '%')
        {
            url[i] = x2c(&url[j+1]);
            j+= 2;
        }
    }
    url[i] = '\0';
}

// ====================================================================
// Identifier:  void CGI::process_cgivars()
//
// Post:        the member variable cgivars is created and contains
//              all the name-value paris from the CGI input
//
// Description: The CGI engine. This module looks through the
//              environment table and searches for the QUERY_STRING
//              string and parse it's structure into tokenized
//              cgivar paris. Method of input is either an
//              HTTP GET or POST request
// ====================================================================

void CGI::process_cgivars(void)
{
    register int i ;
    int content_length, paircount;
    char *request_method, *cgiinput, *nvpair, *eqpos;
    char **pairlist;

    /** Depending on the request method, read all CGI input into cgiinput. **/
    if ((request_method = getenv("REQUEST_METHOD")) == NULL) {
        /* DEVELOPMENT ONLY - perform any final task for production         */
        /* remove the next function during the final development phase      */
        /* it is only here to execute BEFORE testing on the browser         */
        epilogue();

        /* REUEST_METHOD environment variable not found, meaning this was   */
        /* executed on command prompt.                                      */
        printf("Error: please execute CGI on browser.\n");
        exit(1);
    }

    if (!strcmp(request_method, "GET") || !strcmp(request_method, "HEAD") ) {
        /* Some servers apparently don't provide QUERY_STRING if it's empty, */
        /*   so avoid strdup()'ing a NULL pointer here.                      */
        char *qs ;
        qs = getenv("QUERY_STRING") ;
        cgiinput = strdup(qs  ? qs  : "") ;
    }
    else if (!strcmp(request_method, "POST")) {
        /* strcasecmp() is not supported in Windows-- use strcmpi() instead */
        if ( strcasecmp(getenv("CONTENT_TYPE"), "application/x-www-form-urlencoded")) {
            printf("Content-Type: text/plain\n\n") ;
            printf("getcgivars(): Unsupported Content-Type.\n") ;
            exit(1) ;
        }
        if ( !(content_length = atoi(getenv("CONTENT_LENGTH"))) ) {
            printf("Content-Type: text/plain\n\n") ;
            printf("getcgivars(): No Content-Length was sent with the POST request.\n") ;
            exit(1) ;
        }
        if ( !(cgiinput= (char *) malloc(content_length+1)) ) {
            printf("Content-Type: text/plain\n\n") ;
            printf("getcgivars(): Couldn't malloc for cgiinput.\n") ;
            exit(1) ;
        }
        if (!fread(cgiinput, content_length, 1, stdin)) {
            printf("Content-Type: text/plain\n\n") ;
            printf("getcgivars(): Couldn't read CGI input from STDIN.\n") ;
            exit(1) ;
        }
        cgiinput[content_length] = '\0' ;
    }
    else {
        printf("Content-Type: text/plain\n\n") ;
        printf("getcgivars(): Unsupported REQUEST_METHOD.\n") ;
        exit(1) ;
    }

    /** Change all plusses back to spaces. **/
    for (i=0; cgiinput[i]; i++) if (cgiinput[i] == '+') cgiinput[i] = ' ' ;

    /** First, split on "&" and ";" to extract the name-value pairs into **/
    /**   pairlist.                                                      **/
    pairlist = (char **) malloc(256*sizeof(char **)) ;
    paircount = 0 ;
    nvpair = strtok(cgiinput, "&;") ;

    while (nvpair) {
        pairlist[paircount++] = strdup(nvpair) ;
        if (!(paircount%256))
            pairlist = (char **) realloc(pairlist,(paircount+256)*sizeof(char **)) ;
        nvpair = strtok(NULL, "&;") ;
    }
    pairlist[paircount] = 0 ;    /* terminate the list with NULL */

    /** Then, from the list of pairs, extract the names and values. **/
    this->cgivars = (char **) malloc((paircount*2+1)*sizeof(char **)) ;

    for (i=0; i < paircount; i++) {
        if (eqpos=strchr(pairlist[i], '=')) {
            *eqpos = '\0' ;
            unescape_url(cgivars[i*2+1] = strdup(eqpos+1)) ;
        } else {
            unescape_url(cgivars[i*2+1] = strdup("")) ;
        }
        unescape_url(cgivars[i*2] = strdup(pairlist[i])) ;
    }
    cgivars[paircount*2] = 0 ;   /* terminate the list with NULL */
    
    /** Free anything that needs to be freed. **/
    free(cgiinput) ;
    for (i=0; pairlist[i]; i++) free(pairlist[i]) ;
    free(pairlist) ;
}

// ====================================================================
// Identifier:  void CGI::ParseInputForBadData(void) const
//
// Description: This module acts as a secondary security subroutine
//              behind the firewall. It parses the input supplied
//              by the user to search for any possible hacker's
//              code. If there are any anomolies within the input,
//              the query is dropped and is considered to be
//              tampering by an intruder looking for possible
//              vulnerabilities in the system for any opportunity
//              of security breach.
//
//              If the event that the input string is considered bad,
//              the session is logged for further analysis.
// ====================================================================

void CGI::ParseInputForBadData(void) const
{
    /*  Table Symbol to filter out in cgivar input buffer           */
    /*  SYMBOL      FILTER CHARACTER        REASON                  */
    /*  ==========================================================  */
    /*  `           backquote               beg/end comannd         */
    /*  ~           tilde                   enter ambigious dir     */
    /*  !           exclamation mark        not                     */
    /*  @           at                      email                   */
    /*  #           pound                   script header           */
    /*  $           dollar                  access env variable     */
    /*  %           percent                 mod / hex               */
    /*  ^           carot                   grep / awk              */
    /*  &           ampersand               and / separator         */
    /*  [           left bracket            begin test              */
    /*  ]           right bracket           end test                */
    /*  {           left parenthesis        begin expression        */
    /*  }           right parenthesis       end expression          */
    /*  '           single quote            encapsulate string      */
    /*  :           colon                   separator               */
    /*  ;           semi-colon              next shell command      */
    /*  <           left redirection        get file                */
    /*  >           right redirection       modify file             */
    /*  ?           question mark           character wild card     */
    /*  |           pipe                    pipe I/O                */
    /*  =           equal                   assignment              */
    /*  +           plus                    add / concatenation     */
    /*  ,           comma                   separator               */
    /*  \           backslash               access back characters  */
    /*  \n          newline                 new command             */
    /*  \t          tab                     separator               */
    /*  "           double quote            encapsulate string      */
    /*  ==========================================================  */
    const char filter[] = "`~!@#$%^&*[]{}':;<>?|=+,\\\n\t\"";

    /*  ==========================================================  */
    const int  tablelen = strlen(filter);

    for (int i = 0; cgivars[i] != NULL; i++)        // loop all cgivars
    {
        for (int e = 0; e < tablelen; e++)          // loop filter table
        {
            if (strchr(cgivars[i], filter[e]) != NULL)
            {
                printf("Content-Type: text/plain\n\n");
                printf("BAD INPUT ( %c ). TRY AGAIN.\n", filter[e]);
                fflush(stdout);

                /* log intruder session                             */
                logsession();
                exit(1) ;
            }
        }
    }
}