/*
Copyright (C) 1996

This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.

This program 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 General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.

The author can be contacted via Email at bmorin@wpi.edu
*/
#include <windows.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <memory.h>
#include "build.h"
#include "error.h"
#include "debug.h"
#pragma hdrstop
#include "netio.h"
#include "registry.h"
#include "httplog.h"
#include "httpreq.h"
#include "httpsend.h"
#include "httpext.h"
#include "isapi.h"

#define MAX_VAR_STR_LEN 256

struct ConnTableEntry {
	BOOL Allocated;
   RequestInfoT *RequestInfo;
   RequestFieldsT *RequestFields;
	};

static ConnTableEntry *ConnTable;
static int MaxNumConn;
static CRITICAL_SECTION GetTableEntryCritSec;

//Private Function Prototypes
ConnTableEntry *HConnToConnTableEntry(HCONN hConn);
void ISAPIRedirect(char *URL, RequestInfoT &RequestInfo,
   RequestFieldsT &RequestFields);
void ISAPISendURI(char *URI, RequestInfoT &RequestInfo,
   RequestFieldsT &RequestFields);
void ISAPISendHeaders(char *Status, char *OtherHeaders, DWORD OtherHeadersLen,
   RequestInfoT &RequestInfo, RequestFieldsT &RequestFields);

//Public Functions
/******************************************************************************/
void InitISAPI() {
	//Init the critical section
   InitializeCriticalSection(&GetTableEntryCritSec);

   //Get registry vars
   MaxNumConn = GetMaxNumThreads();

	//Allocate the connection table
   ConnTable = new ConnTableEntry[MaxNumConn];

   //Set all of the entries as free
   int i;
   for (i=0; i<MaxNumConn; i++) {
   	ConnTable[i].Allocated = FALSE;
   	}
	}
/******************************************************************************/
void CleanupISAPI() {
	//Free the critical section
   DeleteCriticalSection(&GetTableEntryCritSec);
	//Deallocate the connection table
   delete[] ConnTable;
	}
/******************************************************************************/
void ProcessISAPIScript(RequestInfoT &RequestInfo, RequestFieldsT &RequestFields,
	char *QueryStr, char *FilePath){

   //Varify the file exits
	DWORD FileAttr;
	FileAttr = GetFileAttributes(FilePath);
	if (FileAttr == 0xFFFFFFFF) {
		//Can't find it
		SendHTTPError(404, "File Not Found", "Could not find ISAPI Application module", RequestInfo, RequestFields);
		return;
		}
	else if ((FileAttr & FILE_ATTRIBUTE_DIRECTORY) == FILE_ATTRIBUTE_DIRECTORY) {
		//It's a directory, we can't run directories <grin>
		SendHTTPError(405, "Method Not Supported", "This resource does not support query", RequestInfo, RequestFields);
		return;
		}

   int ConnIndex; //hConn will be ConnIndex + 1  (don't want 0 to be a valid hConn)

   //Find an empty entry in the connection table
   EnterCriticalSection(&GetTableEntryCritSec); //So 2 threads don't get the same entry
   ConnIndex = 0;
   while ((ConnTable[ConnIndex].Allocated != FALSE) && (ConnIndex < MaxNumConn)) {
   	ConnIndex++;
   	}
   LeaveCriticalSection(&GetTableEntryCritSec);
   if (ConnIndex == MaxNumConn) {
   	LogError("Unable to find free space in ISAPI Connection Table");
      SendHTTPError(501, "Internal Server Error",
      	"Unable to find free space in ISAPI Connection Table",
         RequestInfo, RequestFields);
      return;
   	}

   //Fill in the entry
   ConnTable[ConnIndex].Allocated = TRUE;
   ConnTable[ConnIndex].RequestInfo = &RequestInfo;
   ConnTable[ConnIndex].RequestFields = &RequestFields;

   //Load the module (I'm depending on the OS to suppress exessive reloads)
   HMODULE AppHnd;
   AppHnd = LoadLibrary(FilePath);
   if (AppHnd == NULL) {
   	LogError("Failure to load IAPI application module");
      SendHTTPError(501, "Internal Server Error",
      	"Failure to load IAPI application module",
         RequestInfo, RequestFields);
      return;
   	}

   //Check the version number
   PFN_GETEXTENSIONVERSION GetExtensionVersion;
   HSE_VERSION_INFO Ver;

   GetExtensionVersion = (PFN_GETEXTENSIONVERSION) GetProcAddress(AppHnd, "GetExtensionVersion");
   if (GetExtensionVersion == NULL) {
   	LogError("Failure to get pointer to GetExtensionVersion() in IAPI application module");
      SendHTTPError(501, "Internal Server Error",
      	"Failure to get pointer to GetExtensionVersion() in IAPI application module",
         RequestInfo, RequestFields);
      return;
   	}
   if(!GetExtensionVersion(&Ver)) {
   	LogError("IAPI GetExtensionVersion() returned FALSE");
      SendHTTPError(501, "Internal Server Error",
      	"IAPI GetExtensionVersion() returned FALSE",
         RequestInfo, RequestFields);
   	}
   if (Ver.dwExtensionVersion < MAKELONG(HSE_VERSION_MINOR, HSE_VERSION_MAJOR)) {
   	LogError("IAPI version not supported");
      SendHTTPError(501, "Internal Server Error",
      	"IAPI version not supported",
         RequestInfo, RequestFields);
      return;
   	}

   //Fill in the control block
   EXTENSION_CONTROL_BLOCK ExtCtrlBlk;
   memset(&ExtCtrlBlk, 0, sizeof(ExtCtrlBlk));

   ExtCtrlBlk.cbSize = sizeof(ExtCtrlBlk);
   ExtCtrlBlk.dwVersion = MAKELONG(HSE_VERSION_MINOR, HSE_VERSION_MAJOR);
   ExtCtrlBlk.ConnID = (HCONN) (ConnIndex + 1);
   ExtCtrlBlk.dwHttpStatusCode = 200; //default to 200 Ok
   ExtCtrlBlk.lpszLogData[0] = '\0';  //default to empty string
   ExtCtrlBlk.lpszMethod = RequestFields.MethodStr;
   ExtCtrlBlk.lpszQueryString = QueryStr;
   ExtCtrlBlk.lpszPathInfo = RequestFields.PathInfoStr;
   ExtCtrlBlk.lpszPathTranslated = RequestFields.PathTranslatedStr;
   ExtCtrlBlk.cbTotalBytes = RequestFields.ContentLength;
   ExtCtrlBlk.cbAvailable = RequestFields.ContentLength;
   ExtCtrlBlk.lpbData = RequestFields.Content;
   ExtCtrlBlk.lpszContentType = RequestFields.ContentTypeStr;


   //Call the entry procedure
   PFN_HTTPEXTENSIONPROC HttpExtensionProc;

   HttpExtensionProc = (PFN_HTTPEXTENSIONPROC) GetProcAddress(AppHnd, "HttpExtensionProc");
   if (GetExtensionVersion == NULL) {
   	LogError("Failure to get pointer to HttpExtensionProc() in IAPI application module");
      SendHTTPError(501, "Internal Server Error",
      	"Failure to get pointer to HttpExtensionProc() in IAPI application module",
         RequestInfo, RequestFields);
      return;
   	}

   DWORD Ret;
   
   Ret = HttpExtensionProc(&ExtCtrlBlk);

   if (Ret == HSE_STATUS_PENDING) {
   	//Wait for the app to finish
   	}

   switch(Ret) {
      case HSE_STATUS_SUCCESS_AND_KEEP_CONN:
      RequestInfo.KeepAlive = TRUE;

      case HSE_STATUS_SUCCESS:
      case HSE_STATUS_ERROR:
      default:
      RequestInfo.KeepAlive = FALSE;
      }

   //Unload the module
   FreeLibrary(AppHnd);

   //We're done, remove it from the connection table
   ConnTable[ConnIndex].Allocated = FALSE;

   //Log the transaction
	}
/******************************************************************************/
//Functions to be called from ISAPI Apps
/******************************************************************************/
BOOL WINAPI GetServerVariableExport(HCONN hConn, LPSTR lpszVariableName,
	LPVOID lpvBuffer, LPDWORD lpdwSize) {
   ConnTableEntry *ConnInfo;
   RequestFieldsT *RequestFields;
   RequestInfoT *RequestInfo;

   ConnInfo = HConnToConnTableEntry(hConn);
   if (ConnInfo == NULL) {
   	LogError("ISAPI exported funciton GetServerVariable got invalid hConn");
      SetLastError(ERROR_INVALID_PARAMETER);
   	return FALSE;
   	}
   RequestFields = ConnInfo->RequestFields;
   RequestInfo = ConnInfo->RequestInfo;

   //Convert the Variable name to uppercase so we can do case sensitive compares
   char VarStr[MAX_VAR_STR_LEN];
   strncpy(VarStr, lpszVariableName, MAX_VAR_STR_LEN);
   VarStr[MAX_VAR_STR_LEN] = '\0';
   CharUpper(VarStr);

   //Look for a match
   char NumStr[33];

   if (strcmp("CONTENT_TYPE", VarStr) == 0) {
      strncpy((char *) lpvBuffer, RequestFields->ContentTypeStr, *lpdwSize);
      ((char *)lpvBuffer)[*lpdwSize - 1] = '\0';
      *lpdwSize = strlen((char *) lpvBuffer);
   	}
   else if (strcmp("CONTENT_LENGTH", VarStr) == 0) {
      if (RequestFields->ContentLengthStr[0] == '\0') {
         strncpy((char *) lpvBuffer, "0", *lpdwSize);
         }
      else {
         strncpy((char *) lpvBuffer, RequestFields->ContentLengthStr, *lpdwSize);
         }
      ((char *)lpvBuffer)[*lpdwSize - 1] = '\0';
      *lpdwSize = strlen((char *) lpvBuffer);
   	}
	else if (strcmp("SERVER_SOFTWARE", VarStr) == 0) {
      strncpy((char *) lpvBuffer, "Fnord", *lpdwSize);
      ((char *)lpvBuffer)[*lpdwSize - 1] = '\0';
      *lpdwSize = strlen((char *) lpvBuffer);
   	}
	else if (strcmp("SERVER_NAME", VarStr) == 0) {
      strncpy((char *) lpvBuffer, GetServerName(), *lpdwSize);
      ((char *)lpvBuffer)[*lpdwSize - 1] = '\0';
      *lpdwSize = strlen((char *) lpvBuffer);
   	}
	else if (strcmp("GATEWAY_INTERFACE", VarStr) == 0) {
      strncpy((char *) lpvBuffer, "CGI/1.1", *lpdwSize);
      ((char *)lpvBuffer)[*lpdwSize - 1] = '\0';
      *lpdwSize = strlen((char *) lpvBuffer);
   	}
	else if (strcmp("SERVER_PROTOCOL", VarStr) == 0) {
      strncpy((char *) lpvBuffer, RequestFields->VersionStr, *lpdwSize);
      ((char *)lpvBuffer)[*lpdwSize - 1] = '\0';
      *lpdwSize = strlen((char *) lpvBuffer);
   	}
	else if (strcmp("SERVER_PORT", VarStr) == 0) {
      itoa(GetPortNum(), NumStr, 10);
      strncpy((char *) lpvBuffer, NumStr, *lpdwSize);
      ((char *)lpvBuffer)[*lpdwSize - 1] = '\0';
      *lpdwSize = strlen((char *) lpvBuffer);
   	}
   else if (strcmp("AUTH_TYPE", VarStr) == 0) {
   	if (RequestFields->AuthorizedUserStr[0] != 0) {
         strncpy((char *) lpvBuffer, "Basic", *lpdwSize);
         ((char *)lpvBuffer)[*lpdwSize - 1] = '\0';
         *lpdwSize = strlen((char *) lpvBuffer);
         }
      else {
         ((char *) lpvBuffer)[0] = '\0';
         *lpdwSize = 0;
      	}
   	}
   else if (strcmp("REMOTE_USER", VarStr) == 0) {
   	if (RequestFields->AuthorizedUserStr[0] != 0) {
         strncpy((char *) lpvBuffer, RequestFields->AuthorizedUserStr, *lpdwSize);
         ((char *)lpvBuffer)[*lpdwSize - 1] = '\0';
         *lpdwSize = strlen((char *) lpvBuffer);
         }
      else {
         ((char *) lpvBuffer)[0] = '\0';
         *lpdwSize = 0;
      	}
   	}
	else if (strcmp("REMOTE_HOST", VarStr) == 0) {
	   hostent *DNSResult;
	   char HostName[200];

      if (GetScriptDNS() == TRUE) { //Do a reverse DNS lookup
	      DNSResult = gethostbyaddr((char *)&(RequestInfo->ClientSockAddr.sin_addr), RequestInfo->AddrLen, PF_INET);
         }
      else {                        //Don't bother
         DNSResult = NULL;
         }
	   if (DNSResult == NULL) {
		   strcpy(HostName, inet_ntoa(RequestInfo->ClientSockAddr.sin_addr));
         }
	   else {
		   strcpy(HostName, DNSResult->h_name);
         }
      strncpy((char *) lpvBuffer, HostName, *lpdwSize);
      ((char *)lpvBuffer)[*lpdwSize - 1] = '\0';
      *lpdwSize = strlen((char *) lpvBuffer);
   	}
   else if (strcmp("REMOTE_ADDR", VarStr) == 0) {
	   char *AddrStr;
	   AddrStr = inet_ntoa(RequestInfo->ClientSockAddr.sin_addr);
      strncpy((char *) lpvBuffer, AddrStr, *lpdwSize);
      ((char *)lpvBuffer)[*lpdwSize - 1] = '\0';
      *lpdwSize = strlen((char *) lpvBuffer);
   	}
	else if (strcmp("QUERY_STRING", VarStr) == 0) {
   	}
   else if (strcmp("HTTP_USER_AGENT", VarStr) == 0) {
   	}
   else if (strcmp("HTTP_ACCEPT", VarStr) == 0) {
      strncpy((char *) lpvBuffer, RequestFields->AcceptStr, *lpdwSize);
      ((char *)lpvBuffer)[*lpdwSize - 1] = '\0';
      *lpdwSize = strlen((char *) lpvBuffer);
   	}
   else if (strcmp("HTTP_REFERER", VarStr) == 0) {
   	}
	else if (strcmp("PATH_INFO", VarStr) == 0) {
      strncpy((char *) lpvBuffer, RequestFields->PathInfoStr, *lpdwSize);
      ((char *)lpvBuffer)[*lpdwSize - 1] = '\0';
      *lpdwSize = strlen((char *) lpvBuffer);
   	}
	else if (strcmp("PATH_TRANSLATED", VarStr) == 0) {
      strncpy((char *) lpvBuffer, RequestFields->PathTranslatedStr, *lpdwSize);
      ((char *)lpvBuffer)[*lpdwSize - 1] = '\0';
      *lpdwSize = strlen((char *) lpvBuffer);
   	}
	else if (strcmp("SCRIPT_NAME", VarStr) == 0) {
      strncpy((char *) lpvBuffer, RequestFields->ScriptNameStr, *lpdwSize);
      ((char *)lpvBuffer)[*lpdwSize - 1] = '\0';
      *lpdwSize = strlen((char *) lpvBuffer);
   	}
   else if (strcmp("ALL_HTTP", VarStr) == 0) {
   	}
   else {
   	}
   return TRUE;
   };
/******************************************************************************/
BOOL WINAPI WriteClientExport(HCONN hConn, LPVOID Buffer, LPDWORD lpdwBytes,
	DWORD /*dwReserved*/){
   ConnTableEntry *ConnInfo;
   RequestInfoT *RequestInfo;

   ConnInfo = HConnToConnTableEntry(hConn);
   if (ConnInfo == NULL) {
   	LogError("ISAPI exported funciton WriteClientExport got invalid hConn");
      SetLastError(ERROR_INVALID_PARAMETER);
   	return FALSE;
   	}

   RequestInfo = ConnInfo->RequestInfo;

   int Ret;
   Ret = SendData(RequestInfo->ClientSocket, (BYTE *) Buffer, *lpdwBytes,
       RequestInfo->ThreadNum);

   if (Ret == -1) { //Failure to send data
      *lpdwBytes = 0;
      return FALSE;
   	}
   else {
      //*lpdwBytes = *lpdwBytes  (everything sent)
	   return TRUE;
      }
   };
/******************************************************************************/
BOOL WINAPI ReadClientExport(HCONN hConn, LPVOID lpvBuffer, LPDWORD lpdwSize ) {
   ConnTableEntry *ConnInfo;
   RequestInfoT *RequestInfo;

   ConnInfo = HConnToConnTableEntry(hConn);
   if (ConnInfo == NULL) {
      LogError("ISAPI exported funciton ReadClientExport got invalid hConn");
      SetLastError(ERROR_INVALID_PARAMETER);
   	return FALSE;
   	}

   RequestInfo = ConnInfo->RequestInfo;

   int NumRead;

   NumRead = GetData(RequestInfo->ClientSocket, (BYTE *) lpvBuffer, *lpdwSize,
       RequestInfo->ThreadNum);

   if (NumRead == -1) {
      *lpdwSize = 0;
      return FALSE;
   	}
   else {
      *lpdwSize = NumRead;
	   return TRUE;
      }
	};
/******************************************************************************/
BOOL WINAPI ServerSupportFunctionExport(HCONN hConn, DWORD dwHSERRequest,
	LPVOID lpvBuffer, LPDWORD lpdwSize, LPDWORD lpdwDataType ) {
   ConnTableEntry *ConnInfo;

   ConnInfo = HConnToConnTableEntry(hConn);
   if (ConnInfo == NULL) {
   	LogError("ISAPI exported funciton ServerSupportFunctionExport got invalid hConn");
      SetLastError(ERROR_INVALID_PARAMETER);
   	return FALSE;
   	}

   switch (dwHSERRequest) {
   	case HSE_REQ_SEND_URL_REDIRECT_RESP:
        ISAPIRedirect((char *)lpvBuffer, *(ConnInfo->RequestInfo),
           *(ConnInfo->RequestFields));
        break;
      case HSE_REQ_SEND_URL:
        ISAPISendURI((char*)lpvBuffer, *(ConnInfo->RequestInfo),
           *(ConnInfo->RequestFields));
        break;
      case HSE_REQ_SEND_RESPONSE_HEADER:
        ISAPISendHeaders((char*)lpvBuffer, (char*)lpdwDataType, *lpdwSize,
            *(ConnInfo->RequestInfo), *(ConnInfo->RequestFields));
        break;
      case HSE_REQ_DONE_WITH_SESSION:
        //Send event to request thread so it can finish if hung up...
      default:
         //Unsupported request
      	return FALSE;
   	}
   return TRUE;
   };
/******************************************************************************/
//Private Functions
/******************************************************************************/
ConnTableEntry *HConnToConnTableEntry(HCONN hConn) {
   int ConnIndex = ((int) hConn) - 1;
   ConnTableEntry *ConnInfo;

   //Make sure we got an ConnIndex within range
   if ((ConnIndex < 0) || (ConnIndex >= MaxNumConn)) {
   	return NULL;
   	}

   //Make sure the table entry is allocated
   ConnInfo = &(ConnTable[ConnIndex]); //ConnIndex is hConn - 1
   if (ConnInfo->Allocated == FALSE) {
   	return NULL;
   	}

   return ConnInfo;
	}
/******************************************************************************/
void ISAPIRedirect(char *URL, RequestInfoT &RequestInfo,
   RequestFieldsT &RequestFields) {
   //See if it's a URL
   int i;
   i = 0;
   while ((URL[i] != 0) && (URL[i] != ':') && (URL[i] != '/')) {
      i++;
      }
   if (URL[i] == ':') {
      //Redirect to the associated URL
      SendHTTPRedirect(URL, RequestInfo, RequestFields);
      }
   else {
      //Redirect to the associated URI
      char NewURL[MAX_PATH];

      strcpy(NewURL, "http://");
      strcat(NewURL, GetServerName());
      if (URL[0] != '/') {
         //Make sure there is a slash to seperate location and URI
         strcat(NewURL, "/");
         }
      strcat(NewURL, URL);
      SendHTTPRedirect(NewURL, RequestInfo, RequestFields);
      }
	}
/******************************************************************************/
void ISAPISendURI(char *URI, RequestInfoT &RequestInfo,
   RequestFieldsT &RequestFields) {
   //Replace the URI related headers and go back to HTTPreq
   strcpy(RequestFields.URIStr, URI);
   //RequestFields.QueryStr[0] = '\0';
   RequestFields.PathInfoStr[0] = '\0';
   RequestFields.PathTranslatedStr[0] = '\0';
   RequestFields.ScriptNameStr[0] = '\0';
   Process10Request(RequestInfo, RequestFields);
	}
/******************************************************************************/
void ISAPISendHeaders(char *Status, char *OtherHeaders, DWORD OtherHeadersLen,
   RequestInfoT &RequestInfo, RequestFieldsT &/*RequestFields*/) {

   //Send Status Line and server header
   char StatusLine[200];

   if (Status == NULL) {
      strcpy(StatusLine, "HTTP/1.0 200 OK\r\n");
   	}
   else {
      strcpy(StatusLine, "HTTP/1.0 ");
   	strcat(StatusLine, Status);
      strcat(StatusLine, "\r\n");
   	}
   strcat(StatusLine, "Server: Fnord\r\n");

   if (SendData(RequestInfo.ClientSocket, (BYTE *) StatusLine,
      strlen(StatusLine), RequestInfo.ThreadNum) == -1) {
      return;
      }

   if (OtherHeaders == NULL) {
   	//Send our headers
      char Headers[200];
      strcpy(Headers, "Content-Type: text/html\r\n\r\n");   //Assume HTML output
      SendData(RequestInfo.ClientSocket, (BYTE *) Headers,
      strlen(Headers), RequestInfo.ThreadNum);
   	}
   else {
      //Send their headers
      SendData(RequestInfo.ClientSocket, (BYTE *) OtherHeaders,
      OtherHeadersLen, RequestInfo.ThreadNum);
   	}
	}
/******************************************************************************/
