/*===========================================================================
 *
 * File:	DL_CGI.CPP
 * Author:	Dave Humphrey (uesp@m0use.net)
 * Created On:	Wednesday, May 09, 2001
 *
 * Implements various CGI related routines.
 *
 *=========================================================================*/

	/* Include Files */
#include "dl_cgi.h"
#include "dl_math.h"
#include "dl_str.h"
#include "listfile.h"
#include <stdarg.h>


/*===========================================================================
 *
 * Begin Local Variables
 *
 *=========================================================================*/
  DEFINE_FILE();
/*===========================================================================
 *		End of Local Variables
 *=========================================================================*/


/*===========================================================================
 *
 * Function - cgireferer_t cgiCheckReferers (pValidReferersFile);
 *
 * Checks the current Referers environment variable with the valid referers
 * listed in the given list file.  Returns:
 *	CGI_REFERER_OK    : The current referer is valid
 *	CGI_REFERER_BAD   : An invalid referer address
 *	CGI_REFERER_ERROR : An error occured while trying to validate the referer
 * A valid referer is one which matches all, or a part of, a referer in
 * in the valid list file.  For example,  "http://www.yahoo.com" would be
 * a valid referer if the list file contains "http://www.yahoo.com", "yahoo",
 * "www.yahoo.com", etc...  Another example, "somesite.net" would not be
 * a valid referer if the list file contained "http://somesite.net", 
 * "www.somesite.net", or "somesite.net/~user".  The valid referers list
 * file should be created carefully to allow the proper referers access
 * to the script.  The comparisons are case insensitive.
 *
 *=========================================================================*/
cgireferer_t cgiCheckReferers (const char* pValidReferersFile) {
  DEFINE_FUNCTION("cgiCheckReferers()");
  CListFile ReferersList;
  boolean   Result;
  char*     pReferer;

	/* Attempt to get the current referer address */
  pReferer = getenv(HTTP_REFERER_ENV);
  if (pReferer == NULL || strlen(pReferer) == 0) return (CGI_REFERER_ERROR);

	/* Attempt to open referer list file */
  Result = ReferersList.Open(pValidReferersFile);
  if (!Result) return (CGI_REFERER_ERROR);

	/* Check current referer with valid referers in list file */
  while (ReferersList.IsValidLine()) {
    if (stristr(pReferer, ReferersList.GetCurrentLine()) != NULL) return (CGI_REFERER_OK);
    ReferersList.ReadNextLine();
   }
  
  return (CGI_REFERER_BAD);
 }
/*===========================================================================
 *		End of Function cgiCheckReferers()
 *=========================================================================*/


/*===========================================================================
 *
 * Function - boolean cgiErrorOutputFunc (Format, pOutputFile, pUserData);
 *
 * The custom user output function for error pages.  Accepts the format
 * information and outputs the appropiate data to the give file stream.
 * Returns FALSE on any error.
 *	%e or %E - Output last error message
 *	%m of %M - Output the user message.
 * The user data is of the type erroroutput_userdata_t.
 *
 *=========================================================================*/
boolean cgiErrorOutputFunc (const COutputFormat& Format, FILE* pOutputFile, void* pUserData) {
  DEFINE_FUNCTION("cgiErrorOutputFunc()");
  int Result;

  switch (Format.Type) {
    case 'E':
    case 'e': 		/* Output the last error message */
      Result = fprintf (pOutputFile, "%s", ErrorHandler.GetLastErrorMsg());
      break; 
    case 'M':
    case 'm': {		/* Output the user message */
      erroroutput_userdata_t* pData = (erroroutput_userdata_t *)pUserData;

      if (pData == NULL)
        Result = fprintf (pOutputFile, "No user message given!");
      else 
        Result = vfprintf(pOutputFile, pData->pString, pData->Args);

      break; }
    default:		/* Output just the type character */
      Result = fprintf (pOutputFile, "%c", Format.Type);
      break;
   }

  if (Result < 0) return (FALSE);
  return (TRUE);
 }
/*===========================================================================
 *		End of Function cgiErrorOutputFunc()
 *=========================================================================*/


/*===========================================================================
 *
 * Function - void cgiOutputError (pErrFilename, pString, ...);
 *
 * Outputs an error page using the the given printf() style input as a 
 * user message.  First it attempts to use the given HTML error filename,
 * outputting it using the output function cgiErrorOutputFunc().  If
 * the file is not found, the function cgiOutputErrorPage() is used
 * to display a simple error page.
 *
 *=========================================================================*/
void cgiOutputError (const char* pErrFilename, const char* pString, ...) {
  DEFINE_FUNCTION("cgiOutputError()");
  CGenOutput ErrorOutput(cgiErrorOutputFunc);
  boolean    Result;

	/* Ensure valid input */
  ASSERT(pString != NULL);

	/* Ensure the HTML header has been output */
  cgiOutputHTMLHeader();

	/* Attempt to read the output error file */
  Result = ErrorOutput.ReadFile(pErrFilename);
  
  if (Result) {
    erroroutput_userdata_t UserData;
    
		/* Initialize the user data structure */
    va_start(UserData.Args, pString);
    UserData.pString = pString;

		/* Output the HTML error file to stdout */
    Result = ErrorOutput.OutputFile(stdout, (void *)&UserData);
    va_end(UserData.Args);
   }

	/* Failed to load/output the output error file */
  if (!Result) {
    va_list Args;

		/* Output simple error HTML page to stdout */
    va_start(Args, pString);
    cgiOutputErrorPage(pString, Args);
    va_end(Args);
   }

 }
/*===========================================================================
 *		End of Function cgiOutputError()
 *=========================================================================*/


/*===========================================================================
 *
 * Function - void cgiOutputErrorPageV (pString, Args);
 *
 * Outputs a simple error page using a variable argument list message.
 *
 *=========================================================================*/
void cgiOutputErrorPageV (const char* pString, va_list Args) {
  DEFINE_FUNCTION("cgiOutputErrorPageV(char*, va_list)");
  
	/* Ensure HTML header has been output */
  cgiOutputHTMLHeader();
  printf ("<HTML><HEAD><TITLE>CGI Error!</TITLE></HEAD><BODY>\n");
  printf ("<P><CENTER><H2>CGI Error!</H2></CENTER><HR NOSHADE>\n");
  printf ("An error has occured in a CGI script, causing it to abort. ");
  printf ("This probably wasn't supposed to happen.  ");
  
	/* Output the user message, if any */
  if (pString != NULL && Args != NULL) {
    printf ("The reported error message is:\n");
    printf ("<P><FONT COLOR='#ff3333'><B><DL><DD>");
    vprintf(pString, Args);
    printf ("</DL></FONT></B><P>\n");
   }

	/* Output the last error in incident list */  
  printf ("The last recorded error incident was:\n<P><FONT COLOR='#ff3333'><B><DL><DD>");
  printf ("Message: %s<BR>", ErrorHandler.GetLastErrorMsg());
  printf ("Error: %s<BR>", ErrorHandler.GetLastErrorDBMsg());
    
  printf ("</DL></FONT></B><P>\n");
  printf ("For more information, please return to the previous web pages and contact ");
  printf ("its author(s).<HR NOSHADE>\n");
  printf ("<ADDRESS><I><SMALL><FONT COLOR='#666666'>");
  printf ("Module %s written by %s, <A HREF='mailto:%s'>%s</A>, May 2001", ThisFile, DL_BASE_AUTHOR, DL_BASE_EMAIL, DL_BASE_EMAIL);
  printf ("</FONT></SMALL></I></ADDRESS>");
  printf ("</BODY></HTML>\n");
  fflush (stdout);
 }
/*===========================================================================
 *		End of Function cgiOutputErrorPageV()
 *=========================================================================*/


/*===========================================================================
 *
 * Function - void cgiOutputErrorPage (pString, ...);
 *
 * Outputs a simple error page to stdout with the given printf() formatted
 * error message.  Ensures the HTML header has been output.  
 *
 *=========================================================================*/
void cgiOutputErrorPage (const char* pString, ...) {
  DEFINE_FUNCTION("cgiOutputErrorPage(char*, ...)");
  va_list	  Args;
  
  if (pString != NULL) {
    va_start(Args, pString);
    cgiOutputErrorPageV(pString, Args);
    va_end(Args);
   }
  else
    cgiOutputErrorPageV(pString, NULL);
  
 }
/*===========================================================================
 *		End of Function cgiOutputErrorPage()
 *=========================================================================*/


/*===========================================================================
 *
 * Function - boolean cgiOutputHTML (pFilename, UserFunction, pUserData);
 *
 * Attempts to output the given HTML file to stdout, parsing out any
 * '%' variables, giving them to the supplied user function.  Returns FALSE
 * on any error outputting the file.  ASSERTs if given invalid input.
 * Does not output the HTML header.
 *
 *=========================================================================*/
boolean cgiOutputHTML (const char* pFilename, PUSER_OUTPUT_FUNC UserFunction, void* pUserData) {
  DEFINE_FUNCTION("cgiOutputHTML()");
  CGenOutput OutputFile(UserFunction);
  boolean    Result;

	/* Ensure valid inputs */
  ASSERT(pFilename != NULL && UserFunction != NULL);

	/* Attempt to input the file and output HTML to stdout */
  Result = OutputFile.ReadFile(pFilename);
  if (Result) Result = OutputFile.OutputFile(stdout, pUserData);
  return (Result);
 }
/*===========================================================================
 *		End of Function cgiOutputHTML()
 *=========================================================================*/


/*===========================================================================
 *
 * Function - void cgiOutputHTMLHeader (void);
 *
 * Outputs the HTML header to stdout, indicating an HTML document is to 
 * be following.  Can only be output once per application run.
 *
 *=========================================================================*/
void cgiOutputHTMLHeader (void) {
  //DEFINE_FUNCTION("cgiOutputHTMLHeader()");
  static boolean HaveOutputHeader = FALSE;

	/* Ensure we only output the header once */
  if (!HaveOutputHeader) {
    printf ("Content-type: text/html\n\n");
    fflush (stdout);
    HaveOutputHeader = TRUE;
   }
 }
/*===========================================================================
 *		End of Function cgiOutputHTMLHeader()
 *=========================================================================*/


/*===========================================================================
 *
 * Function - void cgiOutputRedirect (pWebSite);
 *
 * Outputs a redirect request to stdout.  ASSERTs if given an invalid
 * string pointer.  Does not work if the HTML header has already been
 * output.
 *
 *=========================================================================*/
void cgiOutputRedirect (const char* pWebSite) {
  DEFINE_FUNCTION("cgiOutputRedirect()");

	/* Ensure valid input */
  ASSERT(pWebSite != NULL);
  printf ("Location: %s\n\n", pWebSite);
  fflush(stdout);
 }
/*===========================================================================
 *		End of Function cgiOutputRedirect()
 *=========================================================================*/


/*===========================================================================
 *
 * Function - boolean cgiParseFormHexCode (OutputChar, ppString);
 *
 * Attempts to parse a '%##' hexcode from the given position in a form 
 * output string.  Returns FALSE on any error.  ASSERTs if given
 * invalid input.
 *
 *=========================================================================*/
boolean cgiParseFormHexCode (char& OutputChar, char** ppString) {
  DEFINE_FUNCTION("cgiParseFormHexCode()");

	/* Ensure valid input */
  ASSERT(ppString != NULL && *ppString != NULL);
  if (**ppString != '%') return (FALSE);

  (*ppString)++;
  if (**ppString == NULL_CHAR) return (FALSE);
  OutputChar = (char)(16*HexCharToInt(**ppString));

  (*ppString)++;
  if (**ppString == NULL_CHAR) return (FALSE);
  OutputChar += (char)(HexCharToInt(**ppString));

  return (TRUE);
 }
/*===========================================================================
 *		End of Function cgiParseFormHexCode()
 *=========================================================================*/


/*===========================================================================
 *
 * Function - boolean cgiUnWebFormOutput (pString);
 *
 * Converts a string output from a HTML form into a regular ASCII string.
 * Returns FALSE on any error and sets the appropiate code with ErrorHandler.
 * The function converts the following:
 *	- Converts all '+' characters to spaces
 *	- Converts % hex codes into ASCII characters.  The hex codes have
 *	  the format '%##' where ## is some hex number (0-255).
 * ASSERTs if it received invalid input.  The string is converted in
 * place and may end up shorted than when it started.
 *
 *=========================================================================*/
boolean cgiUnWebFormOutput (char* pString) {
  DEFINE_FUNCTION("cgiUnWebFormOutput()");
  char*   pParse = pString;
  boolean Result;

	/* Ensure valid input */
  ASSERT(pString  != NULL);

	/* Parse the entire input string */
  while (*pString != NULL_CHAR) {

    switch (*pString) {
      case '%':		/* Check for a hex code character */
        Result = cgiParseFormHexCode(*pParse, &pString);

        if (!Result) {
          ErrorHandler.AddError(ERR_BADINPUT, "Error parsing form output hex code (stopped at character %d, '%c')!", (int)*pString,  *pString);
          return (FALSE);
         }

        break;
      case '+':		/* Convert the space character */
        *pParse = ' ';
	break;
      default:		/* Convert a regular character */
        *pParse = *pString;
	break;
     }

    pParse++;
    pString++;
   }

	/* Terminate the new string */
  *pParse = NULL_CHAR;
  return (TRUE);
 }
/*===========================================================================
 *		End of Function cgiUnWebFormOutput()
 *=========================================================================*/


/*===========================================================================
 *
 * Begin Module Test Routines
 *
 * Functions to test all routines in this module available only in DEBUG
 * builds.
 *
 *=========================================================================*/
#if defined(_DEBUG)

	/* Structure used for testing the cgiOutputHTML() function */
typedef struct {
  char* pPointer;
  int   Number;
  long  LongNumber;
  float FloatNumber;
 } CTestType;

/*===========================================================================
 *
 * Function - boolean Test_UserFunction (Format, pOutputFile, pUserData);
 *
 * Test user function for parsing '%' variables from an output file.
 * pUserData is of type CTestType. 
 *
 *=========================================================================*/
boolean Test_UserFunction (const COutputFormat& Format, FILE* pOutputFile, void* pUserData) {
  DEFINE_FUNCTION("Test_UserFunction()");
  CTestType* pTestData = (CTestType *) pUserData;
  int Result;

  switch (Format.Type) {
    case 'I':
      Result = fprintf(pOutputFile, "***I***");
      break;
    case 'i':
      Result = fprintf(pOutputFile, "%d", pTestData->Number);
      break;
    case 's':
    case 'S':
      Result = fprintf(pOutputFile, "%s", pTestData->pPointer);
      break;
    case 'l':
    case 'L':
      Result = fprintf(pOutputFile, "%ld", pTestData->LongNumber);
      break;
    case 'f':
    case 'F':
      Result = fprintf(pOutputFile, "%f", pTestData->FloatNumber);
      break;
    case 'E':
    case 'e':
      ErrorHandler.AddError(ERR_BADINPUT, "Test_UserFunction() - Testing returning FALSE");
      return (FALSE);
    default:
      Result = fprintf(pOutputFile, "%c", Format.Type);
      break;
   }

  if (Result < 0) {
    ErrorHandler.AddError(ERR_SYSTEM, errno, "Test_UserFunction() - Error writing to file stream!");
    return (FALSE);
   }

  return (TRUE);
 }
/*===========================================================================
 *		End of Function Test_UserFunction()
 *=========================================================================*/


/*===========================================================================
 *
 * Function - void Test_cgiCheckReferers (void);
 *
 * Tests the cgiCheckReferers() function.  Assumes that a test referers
 * file has been created.
 *	1. Tests with typical valid referers
 *	2. Test with some invalid referers
 *
 *=========================================================================*/
void Test_cgiCheckReferers (void) {
  DEFINE_FUNCTION("Test_cgiCheckReferers()");

  SystemLog.Printf (stdout, "=============== Testing cgiCheckReferers() =================");

	/* Test several valid referers */
  putenv("HTTP_REFERER=http://www.m0use.net");
  ASSERT(cgiCheckReferers("referers.lst") == CGI_REFERER_OK);
  putenv("HTTP_REFERER=http://m0use.net/~uesp/somescript");
  ASSERT(cgiCheckReferers("referers.lst") == CGI_REFERER_OK);
  putenv("HTTP_REFERER=http://130.15.193.126/~uesp/somescript");
  ASSERT(cgiCheckReferers("referers.lst") == CGI_REFERER_OK);

	/* Test several invalid referers */
  putenv("HTTP_REFERER=m0use.net");
  ASSERT(cgiCheckReferers("referers.lst") == CGI_REFERER_BAD);
  putenv("HTTP_REFERER=");
  ASSERT(cgiCheckReferers("referers.lst") == CGI_REFERER_ERROR);
  putenv("HTTP_REFERER=http://");
  ASSERT(cgiCheckReferers("referers.lst") == CGI_REFERER_BAD);
  ASSERT(cgiCheckReferers("nofile.lst") == CGI_REFERER_ERROR);
 }
/*===========================================================================
 *		End of Function Test_cgiCheckReferers()
 *=========================================================================*/


/*===========================================================================
 *
 * Function - void Test_cgiOutputHTML (void);
 *
 * Tests the cgiOutputHTML() function.
 *	1. Outputs a sample HTML file with a variety of '%' codes
 *	2. Tests outputting an invalid filename
 *	3. Test outputting a HTML containing known errors
 *
 *=========================================================================*/
void Test_cgiOutputHTML (void) {
  DEFINE_FUNCTION("Test_cgiOutputHTML()");
  CTestType TestData;
  char      TestString[101] = "***This is a test string!***";

  SystemLog.Printf (stdout, "=============== Testing cgiOutputHTML() =================");
  TestData.FloatNumber = (float)9.12345678e27;
  TestData.LongNumber = 987654321;
  TestData.Number = 1234567890;
  TestData.pPointer = TestString;

	/* Test outputting a valid HTML file */
  ASSERT(cgiOutputHTML("testhtml1.html", Test_UserFunction, &TestData) == TRUE);

  	/* Test outputting an invalid HTML file */
  ASSERT(cgiOutputHTML("nofile.html", Test_UserFunction, &TestData) == FALSE);
  
	/* Test outputting a HTML with errors */
  ASSERT(cgiOutputHTML("testhtml2.html", Test_UserFunction, &TestData) == FALSE);
 }
/*===========================================================================
 *		End of Function Test_cgiOutputHTML()
 *=========================================================================*/


/*===========================================================================
 *
 * Function - void Test_cgiParseFormHexCode (void);
 *
 * Tests the cgiParseFormHexCode() function
 *	1. Tests with typical inputs
 *	2. Test with invalid inputs
 *
 *=========================================================================*/
void Test_cgiParseFormHexCode (void) {
  DEFINE_FUNCTION("Test_cgiParseFormHexCode()");
  char  TestChar;
  char  TestString[101];
  char* pString;

  SystemLog.Printf (stdout, "=============== Testing cgiParseFormHexCode() =================");

	/* Test typical function inputs */
  strcpy(TestString, "%10");
  pString = &TestString[0];
  ASSERT(cgiParseFormHexCode(TestChar, &pString) == TRUE);
  ASSERT(*pString == '0');
  ASSERT(TestChar == 0x10);

  strcpy(TestString, "%00");
  pString = &TestString[0];
  ASSERT(cgiParseFormHexCode(TestChar, &pString) == TRUE);
  ASSERT(TestChar == 0x00);

  strcpy(TestString, "%0a");
  pString = &TestString[0];
  ASSERT(cgiParseFormHexCode(TestChar, &pString) == TRUE);
  ASSERT(*pString == 'a');
  ASSERT(TestChar == 0x0a);

  strcpy(TestString, "%23");
  pString = &TestString[0];
  ASSERT(cgiParseFormHexCode(TestChar, &pString) == TRUE);
  ASSERT(*pString == '3');
  ASSERT(TestChar == 0x23);

  strcpy(TestString, "%3F");
  pString = &TestString[0];
  ASSERT(cgiParseFormHexCode(TestChar, &pString) == TRUE);
  ASSERT(*pString == 'F');
  ASSERT(TestChar == 0x3f);

  strcpy(TestString, "%E4");
  pString = &TestString[0];
  ASSERT(cgiParseFormHexCode(TestChar, &pString) == TRUE);
  ASSERT(*pString == '4');
  ASSERT(TestChar == (byte)0xE4);

  strcpy(TestString, "%D5");
  pString = &TestString[0];
  ASSERT(cgiParseFormHexCode(TestChar, &pString) == TRUE);
  ASSERT(TestChar == (byte)0xd5);

  strcpy(TestString, "%C6");
  pString = &TestString[0];
  ASSERT(cgiParseFormHexCode(TestChar, &pString) == TRUE);
  ASSERT(TestChar == (byte)0xc6);

  strcpy(TestString, "%b7");
  pString = &TestString[0];
  ASSERT(cgiParseFormHexCode(TestChar, &pString) == TRUE);
  ASSERT(TestChar == (byte)0xb7);

  strcpy(TestString, "%a8");
  pString = &TestString[0];
  ASSERT(cgiParseFormHexCode(TestChar, &pString) == TRUE);
  ASSERT(TestChar == (byte)0xa8);

  strcpy(TestString, "%99");
  pString = &TestString[0];
  ASSERT(cgiParseFormHexCode(TestChar, &pString) == TRUE);
  ASSERT(TestChar == (byte)0x99);
  
	/* Test with invalid inputs */
  strcpy(TestString, "99");
  pString = &TestString[0];
  ASSERT(cgiParseFormHexCode(TestChar, &pString) == FALSE);

  strcpy(TestString, "");
  pString = &TestString[0];
  ASSERT(cgiParseFormHexCode(TestChar, &pString) == FALSE);

  strcpy(TestString, "%");
  pString = &TestString[0];
  ASSERT(cgiParseFormHexCode(TestChar, &pString) == FALSE);

  strcpy(TestString, "%1");
  pString = &TestString[0];
  ASSERT(cgiParseFormHexCode(TestChar, &pString) == FALSE);
 }
/*===========================================================================
 *		End of Function Test_cgiParseFormHexCode()
 *=========================================================================*/


/*===========================================================================
 *
 * Function - void Test_cgiUnWebFormOutput (void);
 *
 * Tests the cgiUnWebFormOutput() function.
 *	1. Test with typical inputs
 *	2. Test with invalid input
 *
 *=========================================================================*/
void Test_cgiUnWebFormOutput (void) {
  DEFINE_FUNCTION("Test_cgiUnWebFormOutput()");
  char  TestString[101];

  SystemLog.Printf (stdout, "=============== Testing cgiUnWebFormOutput() =================");

	/* Test typical function inputs */
  strcpy(TestString, "This+is+a+test");
  ASSERT(cgiUnWebFormOutput(TestString) == TRUE);
  ASSERT(strcmp(TestString, "This is a test") == 0);

  strcpy(TestString, "This+is%20a+test");
  ASSERT(cgiUnWebFormOutput(TestString) == TRUE);
  ASSERT(strcmp(TestString, "This is a test") == 0);

  	/* Test with invalid inputs */
  strcpy(TestString, "This+is+a+test%");
  ASSERT(cgiUnWebFormOutput(TestString) == FALSE);

  strcpy(TestString, "This+is+a+test%2");
  ASSERT(cgiUnWebFormOutput(TestString) == FALSE);
 }
/*===========================================================================
 *		End of Function Test_cgiUnWebFormOutput()
 *=========================================================================*/


/*===========================================================================
 *
 * Function - void Test_DL_Cgi (void);
 *
 * Tests all functions in this module.
 *	1. Tests the cgiParseFormHexCode() function
 *	2. Test the cgiUnWebFormOutput() function
 *	3. Test the cgiCheckReferers() function
 *	4. Test the cgiOutputHTML() function
 *
 *=========================================================================*/
void Test_DL_Cgi (void) {
  DEFINE_FUNCTION("Test_DL_Cgi()");

  Test_cgiParseFormHexCode();
  Test_cgiUnWebFormOutput();
  Test_cgiCheckReferers();
  Test_cgiOutputHTML();

  //cgiOutputError("uesperr.shtml", "Just testing the error output, %s, %d", "another test", 111111);
  //cgiOutputErrorPage("Just testing the error output, %s, %d", "another test", 111111);

 }
/*===========================================================================
 *		End of Function Test_DL_Cgi()
 *=========================================================================*/


#endif
/*===========================================================================
 *		End of Module Test Routines
 *=========================================================================*/