/*****************************************************************************/ /* CGIsapi.c CGIplus-based ISAPI (Internet Server API; Microsoft/Process Software Purveyor joint specification) extensions (not filters) scripting environment. The CGIplus environment, due to it's ability to support persistant scripts, and basic similarities between ISAPI and CGI processing, can quite conveniently support an ISAPI scripting environment. This program essentially becomes a CGIplus wrapper around an ISAPI DLL (dynamic link library, or with VMS known as a sharable image). In this environment the ISAPI extension does NOT execute in the server process space, it executes in a separate subprocess. Unlike implementations where ISAPI extensions are loaded into the server space the WASD approach effectively insulates buggy ISAPI extensions from crashing the server. The worst they can do is crash the subprocess. The down side is the separate process context is more expensive and slower than the native loadable, but performance measurements indicate persistant CGISAPI is still approximately four to five times faster than an equivalent CGI script! This makes development for the environment worthwhile for those resource-hungry applications. Like CGIplus it's persistant. This means scripts that have high initialization overheads (such as opening large databases) have far less latency on subsequent uses. A distinct advantage is no multi-threading issues. There will only ever be the one thread being executed by CGISAPI! CGISAPI DLLs must be executed via a CGIplus-enabled path. CGISAPI successfully loads and executes the Process Software Corporation Purveyor VMS example extension, SAMPLE_DLL_AXP.DLL and SAMPLE_DLL_VAX.DLL. CONSIDERATIONS -------------- This application is designed to be ISAPI 1.0 compliant. It should also be vanilla ISAPI 2.0 compliant (not the Microsoft WIN32 variety, so don't think you'll necessarily be able to grab all those IIS extensions and just recompile and use ;^) For experimentation the compliance version check can be set to allow any version DLL using the /VERSION= qualifier (major digit only). With CGISAPI multiple instances of any one extension may be active on the one server (each in an autonomous subprocess, unlike a server-process-space loaded extension where only one would ever be active at any one time). Be aware this could present different concurrency issues than a single multi or single threaded instance. When CGIplus subprocesses are idle they can be sys$delprc()ed at any time by the server at expiry of lifetime or to free up required server resources. For this reason ISAPI extensions (scripts) should finalize the processing of transactions when finished, not leave anything in a state where it's unexpected demise might corrupt resources or otherwise cause problems (which is fairly good general advice anyway ;^) That is, when finished tidy up as much as is necessary. Obviously do not exit completely or a complete reactivation will be necessary (why would you be using something like ISAPI if you didn't know that?) CGISAPI is not thread-aware, it doesn't need to be due to the single context of the subprocess it executes within. As a consequence, HttpExtensionProc() cannot be returned from before all processing is complete. When this function returns CGISAPI considers the extension is finished and ready to process another request. Returning HSE_STATUS_PENDING or using ServerSupportFunction() with HSE_REQ_DONE_WITH_SESSION are reported as errors and CGISAPI exits. When CGISAPI invokes the HttpExtensionProc() with the EXTENSION_CONTROL_BLOCK initialized, any request body is immediately and completely available via the 'cbTotalBytes', 'cbAvailable' and 'lpbData' fields. No subsequent calls to ReadClient() are necessary (although can be made). Any WASD CGI variable can be requested via GetServerVariable(), as well as ISAPI-specific "ALL_HTTP" and the WASD-specific "*". Note that some will not be portable. If a variable name does not exist '*lpdwSize' is set to zero, 'lpvBuffer' has a null character set at [0] and FALSE is returned. If the supplied size overflows 'lpvBuffer' contains a null-terminated string of as much as would be contained, '*lpdwSize' is set to the required size (including terminating null) and false is returned. If found and no overflow '*lpdwSize' is set to the size of the string (including null) and TRUE is returned. Output is explicitly buffered for efficiency reasons. For occasions where small amounts of output need to be sent to the client periodically (perhaps as status information during a more extended period of processing) use WriteClient() with a zero-length 'lpdwBytes' parameter. This will flush the output buffer. The size of this buffer may be explicitly set using the command line qualifier /WBUFFER= or a WASD extension call of ServerSupportFunction() with HSE_REQ_DLL_WRITE_BUFFER_SIZE and 'lpdwSize' pointing to a word containing the required buffer size (although this should seldom, if ever, be necessary). Setting the buffer size to zero, *before any output*, disables the buffering, resulting in immediate writes. CGISAPI loaded extensions can exit at any time they wish. The subprocess context allows this. Of course, normally a server-process-space loaded instance would not be able to do so! Any status code and log message set in EXTENSION_CONTROL_BLOCK at return from HttpExtensionProc() are ignored. Normal request logging is performed by the server. DEBUGGING --------- The CGISAPI implementation of ISAPI includes a facility to assist with debugging DLLs. Basic information on function parameters is output to the client whenever a call is made to an ISAPI function. This debugging can be toggled on and off whenever desired using a call to ServerSupportFunction() with 'dwHSERRequest' parameter set to the WASD values of HSE_REQ_DLL_DEBUG_ON or HSE_REQ_DLL_DEBUG_OFF. Once enabled DLL debugging remains active through multiple uses of a CGISAPI instance, or until disabled, or until the particular CGISAPI subprocess' lifetime expires. SERVER CONFIGURATION -------------------- Ensure the following are in the appropriate sections of HTTPD$CONFIG. [DclScriptRunTime] .DLL $CGI-BIN:[000000]CGISAPI.EXE [AddType] .DLL application/octet-stream - ISAPI extension DLL Ensure this is in the scripting section of HTTPD$MAP. exec+ /isapi/* /cgi-bin/* DLLs may then be accessed with paths such as: http://host.name.domain/isapi/isapiexample.dll ISAPI RESOURCES --------------- The code in this application, data structures, etc., has been derived in part from information contained in the QUE book "Using ISAPI", Stephen Genusa, et.al., 1997. http://www.genusa.com/isapi/ http://www.microsoft.com/win32dev/apitext/isalegal.htm http://www.process.com/news/spec.htm http://vms.process.com/ There's not a lot obvious anywhere for anything but Microsoft platforms, let alone specifically for VMS :^( Still, the efficiencies of having persistant scripts, along with ISAPI being a well-documented interface (in contrast to CGIplus, though which is simpler and more efficient), will provide real benefits for some sites. PARAMETERS ---------- The full file specification of the DLL must be supplied. QUALIFIERS ---------- /CHARSET= "Content-Type: text/html; charset=..." /DBUG turns on all "if (Debug)" statements /VERSION= set the ISAPI major version digit to allow non-1.0 DLLs (this is for experimentation, it doesn't really change anything, just stops the version check error report) /WBUFFER= integer, write buffer size in bytes, zero to disable LOGICAL NAMES ------------- CGISAPI$DBUG turns on all "if (Debug)" statements CGISAPI$DBUG_DLL turns on DLL debug mode before an extension is loaded CGISAPI$PARAM equivalent to (overrides) the command line parameters/qualifiers (define as a system-wide logical) BUILD DETAILS ------------- See BUILD_CGISAPI.COM procedure. COPYRIGHT --------- Copyright (C) 1999-2020 Mark G.Daniel This program comes with ABSOLUTELY NO WARRANTY. This is free software, and you are welcome to redistribute it under the conditions of the GNU GENERAL PUBLIC LICENSE, version 3, or any later version. http://www.gnu.org/licenses/gpl.txt VERSION HISTORY (update SOFTWAREVN as well!) --------------- 25-OCT-2009 MGD V1.3.2, use CGI response to allow HTTP/1.1 functionalities (e.g. GZIP response encoding, chunking, persistence) 23-DEC-2003 MGD v1.3.1, minor conditional mods to support IA64 28-OCT-2000 MGD v1.3.0, use CGILIB object module, modifications for RELAXED_ANSI compilation 16-SEP-2000 MGD v1.2.2, make DLL debug span whole requests only 12-APR-2000 MGD v1.2.1, minor changes for CGILIB 1.4 20-NOV-1999 MGD v1.2.0, write buffer to reduce script I/O, use more of the CGILIB functionality 11-JUN-1999 MGD v1.1.1, improve (DllDebug) output, bugfix; "#define CGILIB_NONE NULL" 24-APR-1999 MGD v1.1.0, use CGILIB.C 14-MAR-1999 MGD v1.0.0, initial development */ /*****************************************************************************/ #define SOFTWAREVN "1.3.2" #define SOFTWARENM "CGISAPI" #ifdef __ALPHA # define SOFTWAREID SOFTWARENM " AXP-" SOFTWAREVN #endif #ifdef __ia64 # define SOFTWAREID SOFTWARENM " IA64-" SOFTWAREVN #endif #ifdef __VAX # define SOFTWAREID SOFTWARENM " VAX-" SOFTWAREVN #endif #ifdef __x86_64 # define SOFTWAREID SOFTWARENM " X86-" SOFTWAREVN #endif /* standard C header files */ #include #include #include #include #include /* VMS-related header files */ #include #include #include #include /* application header files */ #include "cgisapi.h" #include #define boolean int #define true 1 #define false 0 #define FI_LI __FILE__, __LINE__ #ifndef __VAX # pragma nomember_alignment #endif #define VMSok(x) ((x) & STS$M_SUCCESS) #define VMSnok(x) (!((x) & STS$M_SUCCESS)) #define WRITE_BUFFER_SIZE_DEFAULT 4096 #define WRITE_BUFFER_SIZE_MIN 256 #define WRITE_BUFFER_SIZE_MAX 32768 #define DEFAULT_CHARSET "ISO-8859-1" #define CGISAPI_MAJOR_VERSION 1 #define CGISAPI_MINOR_VERSION 0 #define HTTP_VARIABLE_PREFIX "HTTP_" #define HTTP_VARIABLE_PREFIX_LENGTH sizeof(HTTP_VARIABLE_PREFIX)-1 char Utility [] = "CGISAPI"; boolean AllowStandardCgi, Debug, DllDebug, DllDebugOn, IsCgiPlus; int DllActivatedCount, IsapiMajorVersion = CGISAPI_MAJOR_VERSION, WriteBufferSize = WRITE_BUFFER_SIZE_DEFAULT; char *CliCharsetPtr, *CliDllFileNamePtr; char SoftwareID [48]; /* required function prototypes */ int FindImageSymbolHandler (); BOOL IsapiServerSupportFunction (HCONN, DWORD, LPVOID, LPDWORD, LPDWORD); BOOL IsapiGetServerVariable (HCONN, LPSTR, LPVOID, LPDWORD); BOOL IsapiReadClient (HCONN, LPVOID, LPDWORD); BOOL IsapiWriteClient (HCONN, LPVOID, LPDWORD, DWORD); /*****************************************************************************/ /* */ main () { /*********/ /* begin */ /*********/ sprintf (SoftwareID, "%s (%s)", SOFTWAREID, CgiLibEnvironmentVersion()); if (getenv ("CGISAPI$DBUG") != NULL) Debug = true; if (getenv ("CGISAPI$DBUG_DLL") != NULL) DllDebug = true; /* only for testing certain behaviours! */ if (getenv ("CGISAPI$CGI") != NULL) AllowStandardCgi = true; GetParameters (); if (CliDllFileNamePtr == NULL) { fprintf (stdout, "%%%s-E-DLL, not specified\n", Utility); exit (STS$K_ERROR | STS$M_INHIB_MSG); } CgiLibEnvironmentInit (0, NULL, false); CgiLibEnvironmentSetDebug (Debug); /* if a CGILIB CGI variable does not exist then return a NULL pointer */ CgiLibEnvironmentSetVarNone (NULL); CgiLibResponseSetCharset (CliCharsetPtr); CgiLibResponseSetSoftwareID (SoftwareID); CgiLibResponseSetErrorMessage ("Reported by CGISAPI"); IsCgiPlus = CgiLibEnvironmentIsCgiPlus (); if (!IsCgiPlus && !AllowStandardCgi) { /* only allows standard CGI environment when debug switched on */ CgiLibResponseError (FI_LI, 0, "ISAPI scripts must be executed in CGIplus environment!"); exit (SS$_NORMAL); } /***********/ /* process */ /***********/ if (IsCgiPlus) { for (;;) { /* block waiting for next request */ CgiLibVar (""); IsapiMain (); CgiLibCgiPlusEOF (); } } else IsapiMain (); exit (SS$_NORMAL); } /*****************************************************************************/ /* This function gets called each time the CGISAPI script gets activated. The first call it must load the DLL (shareable image), ensure the two required entry-points are available and check the ISAPI version of the DLL. Subsequent calls it can just invoke the DLL procedure entry-point to execute it's functionality. */ IsapiMain () { static BOOL (*GetExtensionVersion)(LPHSE_VERSION_INFO); static DWORD (*HttpExtensionProc)(LPEXTENSION_CONTROL_BLOCK); static $DESCRIPTOR (FileNameDsc, ""); static $DESCRIPTOR (HttpExtensionProcDsc, "HttpExtensionProc"); static $DESCRIPTOR (ImageNameDsc, "CGI-BIN:[000000].DLL"); static $DESCRIPTOR (GetExtensionVersionDsc, "GetExtensionVersion"); register char *cptr, *sptr, *zptr; boolean ok; int status, MajorVersion, MinorVersion; char FileName [256], ImageName [256]; HSE_VERSION_INFO HseVersion; EXTENSION_CONTROL_BLOCK ExtensionControlBlock; /*********/ /* begin */ /*********/ if (Debug) fprintf (stdout, "IsapiMain() %d\n", DllActivatedCount); if (DllActivatedCount++) { /********************/ /* subsequent calls */ /********************/ IsapiEcbInit (&ExtensionControlBlock); /* invoke the DLL main procedure entry-point */ ok = (*HttpExtensionProc)(&ExtensionControlBlock); if (Debug) fprintf (stdout, "(*HttpExtensionProc)() ok:%d\n", ok); IsapiEnd (&ExtensionControlBlock, ok); return; } /**************/ /* first call */ /**************/ cptr = CliDllFileNamePtr; zptr = (sptr = ImageName) + sizeof(ImageName)-1; while (*cptr && *cptr != ']' && sptr < zptr) *sptr++ = *cptr++; if (cptr[1] == '[') { if (sptr < zptr) *sptr++ = *cptr++; while (*cptr && *cptr != ']' && sptr < zptr) *sptr++ = *cptr++; } if (*cptr && sptr < zptr) *sptr++ = *cptr++; ImageNameDsc.dsc$a_pointer = sptr; zptr = (sptr = FileName) + sizeof(FileName)-1; while (*cptr && *cptr != '.' && sptr < zptr) *sptr++ = *cptr++; *sptr = '\0'; FileNameDsc.dsc$a_pointer = FileName; FileNameDsc.dsc$w_length = strlen(FileName); if (Debug) fprintf (stdout, "|%s|\n", FileName); zptr = ImageName + sizeof(ImageName)-1; sptr = ImageNameDsc.dsc$a_pointer; while (*cptr && *cptr != ';' && sptr < zptr) *sptr++ = *cptr++; *sptr = '\0'; ImageNameDsc.dsc$a_pointer = ImageName; ImageNameDsc.dsc$w_length = strlen(ImageName); if (Debug) fprintf (stdout, "|%s|\n", ImageName); lib$establish (FindImageSymbolHandler); status = lib$find_image_symbol (&FileNameDsc, &HttpExtensionProcDsc, &HttpExtensionProc, &ImageNameDsc); if (Debug) fprintf (stdout, "lib$find_image_symbol() %%X%08.08X\n", status); lib$revert (); if (VMSnok (status)) { /* could not locate the required entry-point in the DLL */ if (status == LIB$_KEYNOTFOU) CgiLibResponseError (FI_LI, 0, "DLL HttpExtensionProc() not found"); else CgiLibResponseError (FI_LI, status, CliDllFileNamePtr); exit (SS$_NORMAL); } status = lib$find_image_symbol (&FileNameDsc, &GetExtensionVersionDsc, &GetExtensionVersion, &ImageNameDsc); if (Debug) fprintf (stdout, "lib$find_image_symbol() %%X%08.08X\n", status); if (VMSnok (status)) { /* could not locate the required entry-point in the DLL */ if (status == LIB$_KEYNOTFOU) CgiLibResponseError (FI_LI, 0, "DLL GetExtensionVersion() not found"); else CgiLibResponseError (FI_LI, status, CliDllFileNamePtr); exit (SS$_NORMAL); } /* invoke the DLL version information entry-point */ ok = (*GetExtensionVersion)(&HseVersion); if (Debug) fprintf (stdout, "(*GetExtensionVersion)() ok:%d\n", ok); if (ok) { /* check version compatibility */ MajorVersion = HseVersion.dwExtensionVersion >> 16; MinorVersion = HseVersion.dwExtensionVersion & 0x0ffff; if (Debug) fprintf (stdout, "HseVersion: %d.%d |%s|\n", MajorVersion, MinorVersion, HseVersion.lpszExtensionDesc); if (MajorVersion > IsapiMajorVersion) { char String [256]; sprintf (String, "DLL version is %d.%d, %s script is %d.%d", MajorVersion, MinorVersion, Utility, IsapiMajorVersion, CGISAPI_MINOR_VERSION); CgiLibResponseError (FI_LI, 0, String); exit (SS$_NORMAL); } } else { CgiLibResponseError (FI_LI, 0, "DLL GetExtensionVersion() failed"); exit (SS$_NORMAL); } /********************************/ /* call the extension procedure */ /********************************/ IsapiEcbInit (&ExtensionControlBlock); /* invoke the DLL main procedure entry-point */ ok = (*HttpExtensionProc)(&ExtensionControlBlock); if (Debug) fprintf (stdout, "(*HttpExtensionProc)() ok:%d\n", ok); IsapiEnd (&ExtensionControlBlock, ok); } /*****************************************************************************/ /* Just continue, to report an error if the image couldn't be activated or the required symbol not found. */ FindImageSymbolHandler () { /*********/ /* begin */ /*********/ if (Debug) fprintf (stdout, "FindImageSymbolHandler()\n"); return (SS$_CONTINUE); } /*****************************************************************************/ /* Executed after the DLL's main procedure entry-point has returned. */ IsapiEnd ( LPEXTENSION_CONTROL_BLOCK lpEcb, DWORD HttpExtensionProcReturn ) { /*********/ /* begin */ /*********/ if (Debug) fprintf (stdout, "IsapiEnd()\n"); if (Debug) fprintf (stdout, "dwHttpStatusCode: %d\n\ lpszLogData |%s|\n", lpEcb->dwHttpStatusCode, lpEcb->lpszLogData); if (DllDebug) { fprintf (stdout, "[HttpExtensionProc() returned: %d\n\ dwHttpStatusCode: %d\n\ lpszLogData: |%s|]\n", HttpExtensionProcReturn, lpEcb->dwHttpStatusCode, lpEcb->lpszLogData); } if (lpEcb->dwHttpStatusCode == HSE_STATUS_PENDING) { /* we're not threaded here, so anything pending's not supported! */ CgiLibResponseError (FI_LI, 0, "HSE_STATUS_PENDING not supported!"); exit (SS$_NORMAL); } /* flush anything in the write buffer */ IsapiWriteClient (lpEcb, NULL, 0, 0); } /*****************************************************************************/ /* Initialize the extension control block, filling out the required fields. */ IsapiEcbInit (LPEXTENSION_CONTROL_BLOCK lpEcb) { char *cptr; /*********/ /* begin */ /*********/ if (Debug) fprintf (stdout, "IsapiEcbInit()\n"); DllDebug = DllDebugOn; memset (lpEcb, 0, sizeof(EXTENSION_CONTROL_BLOCK)); lpEcb->cbSize = sizeof(EXTENSION_CONTROL_BLOCK); lpEcb->dwVersion = CGISAPI_MAJOR_VERSION << 16 | CGISAPI_MINOR_VERSION; lpEcb->ConnID = lpEcb; if ((lpEcb->lpszMethod = CgiLibVar("REQUEST_METHOD")) == NULL) lpEcb->lpszMethod = ""; if ((lpEcb->lpszQueryString = CgiLibVar("QUERY_STRING")) == NULL) lpEcb->lpszQueryString = ""; if ((lpEcb->lpszPathInfo = CgiLibVar("PATH_INFO")) == NULL) lpEcb->lpszPathInfo = ""; if ((lpEcb->lpszPathTranslated = CgiLibVar("PATH_TRANSLATED")) == NULL) lpEcb->lpszPathTranslated = ""; if ((lpEcb->lpszContentType = CgiLibVar("CONTENT_TYPE")) == NULL) lpEcb->lpszContentType = ""; /* initialize the request body procedure, indicated by a NULL 'lpdBuffer' */ IsapiReadClient (lpEcb, NULL, NULL); lpEcb->GetServerVariable = &IsapiGetServerVariable; lpEcb->ReadClient = &IsapiReadClient; lpEcb->WriteClient = &IsapiWriteClient; lpEcb->ServerSupportFunction = &IsapiServerSupportFunction; if (DllDebug) { CgiLibResponseHeader (200, "text/plain"); fprintf (stdout, "[%s %s activation: %d]\n", SoftwareID, CliDllFileNamePtr, DllActivatedCount); if ((cptr = CgiLibVar("*")) != NULL) { fprintf (stdout, "[%s", cptr); while ((cptr = CgiLibVar("*")) != NULL) fprintf (stdout, "\n %s", cptr); fputs ("]\n", stdout); } fprintf (stdout, "[HttpExtensionProc()\n\ ConnID: %d\n\ lpszMethod: |%s|\n\ lpszQueryString: |%s|\n\ lpszPathInfo: |%s|\n\ lpszPathTranslated: |%s|\n\ lpszContentType: |%s|\n\ cbTotalBytes: %d\n\ cbAvailable: %d\n\ lpbData: %d]\n", lpEcb->ConnID, lpEcb->lpszMethod, lpEcb->lpszQueryString, lpEcb->lpszPathInfo, lpEcb->lpszPathTranslated, lpEcb->lpszContentType, lpEcb->cbTotalBytes, lpEcb->cbAvailable, lpEcb->lpbData); } } /*****************************************************************************/ /* DLL callback that will return the requested CGI-like variable value. Returns TRUE if the variable exists, FALSE if it doesn't (sets 'lpvBuffer' to an empty string). Returns FALSE if the buffer overflows (puts as much as possible into it as a null-terminated stream, then sets 'lpdwSize' to what was actually required). Special case is "content-length", sets 'lpvBuffer' to a 4 byte binary value. */ BOOL IsapiGetServerVariable ( HCONN hConn, LPSTR lpszVariableName, LPVOID lpvBuffer, LPDWORD lpdwSize ) { register char *cptr, *sptr, *zptr; int len; char *CgiLibVarPtr; /*********/ /* begin */ /*********/ if (Debug) fprintf (stdout, "IsapiGetServerVariable() |%s|\n", lpszVariableName); if (DllDebug) { fprintf (stdout, "[GetServerVariable()\n\ hConn: %d\n\ lpszVariableName: |%s|\n\ lpvBuffer: %d\n\ lpdwSize: %d %d\n", hConn, lpszVariableName, lpvBuffer, lpdwSize, lpdwSize == NULL ? 0 : *lpdwSize); } if (lpszVariableName[0] == '*' || strsame (lpszVariableName, "ALL_HTTP", -1)) { /************************/ /* all, or all HTTP_... */ /************************/ zptr = (sptr = lpvBuffer) + *lpdwSize; while ((cptr = CgiLibVar("*")) != NULL) { if (lpszVariableName[0] == '*' || !memcmp (cptr, HTTP_VARIABLE_PREFIX, HTTP_VARIABLE_PREFIX_LENGTH)) { while (*cptr && *cptr != '=' && sptr < zptr) *sptr++ = *cptr++; if (*cptr) cptr++; if (sptr < zptr) *sptr++ = ':'; if (sptr < zptr) *sptr++ = ' '; while (*cptr && sptr < zptr) *sptr++ = *cptr++; if (sptr < zptr) *sptr++ = '\n'; if (sptr >= zptr) break; } } if (sptr >= zptr) { /* too small, suggest to double it's size */ *(char*)lpvBuffer = '\0'; *lpdwSize = *lpdwSize < 1; if (DllDebug) { fprintf (stdout, " BUFFER OVERFLOW\n\ lpvBuffer: |%s|\n\ lpdwsize: %d %d\n\ return: FALSE]\n", (char*)lpvBuffer, lpdwSize, lpdwSize == NULL ? 0 : *lpdwSize); } return (FALSE); } *sptr = '\0'; *lpdwSize = sptr - (char*)lpvBuffer + 1; if (DllDebug) { fprintf (stdout, " FOUND\n\ lpvBuffer: |%s|\n\ lpdwsize: %d %d\n\ return: TRUE]\n", (char*)lpvBuffer, lpdwSize, lpdwSize == NULL ? 0 : *lpdwSize); } return (TRUE); } /***********************/ /* single CGI variable */ /***********************/ /* fudge: Purveyor must use "user-agent" because it's example DLL does! */ if (strsame (lpszVariableName, "user-agent", -1)) lpszVariableName = "http_user_agent"; if ((cptr = CgiLibVarPtr = CgiLibVar (lpszVariableName)) == NULL) { *(char*)lpvBuffer = '\0'; *lpdwSize = 0; if (DllDebug) { fprintf (stdout, " NOT FOUND\n\ lpvBuffer: |%s|\n\ lpdwsize: %d %d\n\ return: FALSE]\n", (char*)lpvBuffer, lpdwSize, lpdwSize == NULL ? 0 : *lpdwSize); } return (FALSE); } else { if (strsame (lpszVariableName, "content_length", -1)) { /* special case, return binary value */ if (*lpdwSize < sizeof(DWORD)) { *lpdwSize = sizeof(DWORD); if (DllDebug) { fprintf (stdout, " BUFFER OVERFLOW\n\ lpvBuffer: |%s|\n\ lpdwsize: %d %d\n\ return: FALSE]\n", (char*)lpvBuffer, lpdwSize, lpdwSize == NULL ? 0 : *lpdwSize); } return (FALSE); } *(DWORD*)lpvBuffer = atoi(cptr); *lpdwSize = sizeof(DWORD); if (DllDebug) { fprintf (stdout, " FOUND\n\ lpvBuffer: %d %d\n\ lpdwsize: %d %d\n\ return: TRUE]\n", lpvBuffer, *(DWORD*)lpvBuffer, lpdwSize, lpdwSize == NULL ? 0 : *lpdwSize); } return (TRUE); } zptr = (sptr = lpvBuffer) + *lpdwSize - 1; while (*cptr && sptr < zptr) *sptr++ = *cptr++; *sptr = '\0'; if (sptr < zptr) { *lpdwSize = sptr - (char*)lpvBuffer + 1; if (DllDebug) { fprintf (stdout, " FOUND\n\ lpvBuffer: |%s|\n\ lpdwsize: %d %d\n\ return: TRUE]\n", (char*)lpvBuffer, lpdwSize, lpdwSize == NULL ? 0 : *lpdwSize); } return (TRUE); } else { while (*cptr) cptr++; *lpdwSize = cptr - CgiLibVarPtr + 1; if (DllDebug) { fprintf (stdout, " BUFFER OVERFLOW\n\ lpvBuffer: |%s|\n\ lpdwsize: %d %d\n\ return: FALSE]\n", (char*)lpvBuffer, lpdwSize, lpdwSize == NULL ? 0 : *lpdwSize); } return (FALSE); } } } /*****************************************************************************/ /* DLL callback to read (further) request content (body) from the client. CGISAPI _always_ supplies all the request content with the initial ECB supplied to the HttpExtensionProc() entry-point, so this really never will need to be called. It it is it just indicates there is no more data available. IsapiEcbInit() calls this procedure with 'lpvBuffer' set to NULL to initially set the body content fields correctly whether there is content or not. */ BOOL IsapiReadClient ( HCONN ConnID, LPVOID lpvBuffer, LPDWORD lpdwSize ) { static int BufferCount; static char *BufferPtr; char *cptr; EXTENSION_CONTROL_BLOCK *lpEcb; /*********/ /* begin */ /*********/ if (Debug) fprintf (stdout, "IsapiReadClient() %d\n", ConnID); lpEcb = (EXTENSION_CONTROL_BLOCK*)ConnID; if (lpvBuffer == NULL) { if ((cptr = CgiLibVar("CONTENT_LENGTH")) == NULL) { lpEcb->cbTotalBytes = lpEcb->cbAvailable = BufferCount = 0; lpEcb->lpbData = BufferPtr = ""; return (FALSE); } else { lpEcb->cbTotalBytes = atoi(cptr); if (Debug) fprintf (stdout, "lpEcb->cbTotalBytes: %d\n", lpEcb->cbTotalBytes); } if (CgiLibReadRequestBody (&BufferPtr, &BufferCount)) { if (Debug) fprintf (stdout, "BufferCount: %d\n", BufferCount); if (lpEcb->cbTotalBytes == BufferCount) { /* always return the entire body with the initial call! */ lpEcb->lpbData = BufferPtr; lpEcb->cbAvailable = BufferCount; return (TRUE); } else { lpEcb->cbTotalBytes = lpEcb->cbAvailable = BufferCount = 0; lpEcb->lpbData = BufferPtr = ""; return (FALSE); } } else { lpEcb->cbTotalBytes = lpEcb->cbAvailable = BufferCount = 0; lpEcb->lpbData = BufferPtr = ""; return (FALSE); } } if (DllDebug) { fprintf (stdout, "[ReadClient()\n\ ConnID: %d\n\ lpvBuffer: %d\n\ lpdwSize: %d %d\n", ConnID, lpvBuffer, lpdwSize, lpdwSize == NULL ? 0 : *lpdwSize); } /* explicit calls to IsapiReadClient() always have nothing to return! */ *(char*)lpvBuffer = '\0'; *lpdwSize = 0; if (DllDebug) { fprintf (stdout, " lpvBuffer: |%s|\n\ lpdwsize: %d %d\n\ return: FALSE]\n", (char*)lpvBuffer, lpdwSize, lpdwSize == NULL ? 0 : *lpdwSize); } return (FALSE); } /*****************************************************************************/ /* DLL callback, write the supplied buffer to the client. The efficiencies provided by buffering this data, reducing the number of I/Os between the script and the server, are worth explicitly buffering it here. To flush what is buffered at any time call this function with a zero-length 'lpdwBytes'. Why not use the C-RTL for this? Two reasons. The data given to this function is specified by pointer and a length, meaning an fwrite() needs to be used (which does an implicit flush). Why not then freopen(,,,"ctx=xplct")? This worked on Alpha VMS 7.1 but barfed on VAX VMS 6.2 (at least), hence this work around. This buffering reduced response time to 12.5% of what it was without it!! (as measured using the ISAPIEXAMPLE.C program outputing lines of 80 characters). */ BOOL IsapiWriteClient ( HCONN ConnID, LPVOID lpvBuffer, LPDWORD lpdwBytes, DWORD dwReserved ) { static int WriteBufferCapacity; static char *WriteBuffer = NULL, *WriteBufferPtr; int DataBytes, DataCount; /*********/ /* begin */ /*********/ if (Debug) fprintf (stdout, "IsapiWriteClient()\n"); if (DllDebug) { fprintf (stdout, "[WriteClient()\n\ ConnID: %d\n\ lpvBuffer: %d\n\ lpdwSize: %d %d\n\ dwReserved: %d]\n", ConnID, lpvBuffer, lpdwBytes, lpdwBytes == NULL ? 0 : *lpdwBytes, dwReserved); } if (Debug) fprintf (stdout, "%d %d %d\n", WriteBuffer, WriteBufferPtr, WriteBufferCapacity); if (!WriteBufferSize || (DllDebug && lpdwBytes != NULL && (DataBytes = *lpdwBytes))) { /* if buffer size set zero, or when debugging the DLL, do not buffer */ fwrite (lpvBuffer, *lpdwBytes, 1, stdout); } else if (WriteBufferSize && lpdwBytes != NULL && (DataBytes = *lpdwBytes)) { if (WriteBuffer == NULL) if ((WriteBufferPtr = WriteBuffer = calloc (1, WriteBufferCapacity = WriteBufferSize)) == NULL) exit (vaxc$errno); if (DataBytes <= WriteBufferCapacity) { /* initial space in the buffer */ memcpy (WriteBufferPtr, lpvBuffer, DataBytes); WriteBufferPtr += DataBytes; WriteBufferCapacity -= DataBytes; } else { /* no initial space in the buffer */ DataCount = WriteBufferCapacity; DataBytes -= DataCount; for (;;) { memcpy (WriteBufferPtr, lpvBuffer, DataCount); fwrite (WriteBuffer, WriteBufferSize, 1, stdout); WriteBufferPtr = WriteBuffer; WriteBufferCapacity = WriteBufferSize; lpvBuffer = (void*)((int)lpvBuffer + DataCount); if (DataBytes <= WriteBufferCapacity) DataCount = DataBytes; else DataCount = WriteBufferCapacity; memcpy (WriteBufferPtr, lpvBuffer, DataCount); WriteBufferPtr += DataCount; WriteBufferCapacity -= DataCount; DataBytes -= DataCount; if (!DataBytes) break; } } } else if (WriteBufferSize && !DllDebug && WriteBuffer != NULL && WriteBufferPtr > WriteBuffer) { /* flush the write buffer */ fwrite (WriteBuffer, WriteBufferPtr-WriteBuffer, 1, stdout); WriteBufferPtr = WriteBuffer; WriteBufferCapacity = WriteBufferSize; } if (DllDebug) fprintf (stdout, "\n[return: TRUE]\n"); return (TRUE); } /*****************************************************************************/ /* DLL callback, provide support functions. */ BOOL IsapiServerSupportFunction ( HCONN hConn, DWORD dwHSERRequest, LPVOID lpvBuffer, LPDWORD lpdwSize, LPDWORD lpdwDataType ) { char *CharsetPtr; EXTENSION_CONTROL_BLOCK *lpEcb; /*********/ /* begin */ /*********/ if (Debug) fprintf (stdout, "IsapiServerSupportFunction() %d %d %d\n", dwHSERRequest, lpdwSize == NULL ? 0 : *lpdwSize, lpdwDataType); lpEcb = (EXTENSION_CONTROL_BLOCK*)hConn; if (DllDebug) { fprintf (stdout, "[ServerSupportFunction()\n\ hConn: %d\n\ dwHSERRequest: %d\n\ lpvBuffer: %d\n\ lpdwSize: %d %d\n\ lpdwDataType: %d %d\n", hConn, dwHSERRequest, lpvBuffer, lpdwSize, lpdwSize == NULL ? 0 : *lpdwSize, lpdwDataType, lpdwDataType == NULL ? 0 : *lpdwDataType); } switch (dwHSERRequest) { case HSE_REQ_SEND_URL_REDIRECT_RESP : if (DllDebug || Debug) fprintf (stdout, " HSE_REQ_SEND_URL_REDIRECT_RESP]\n"); CgiLibResponseRedirect (lpvBuffer); fflush (stdout); if (DllDebug) fprintf (stdout, "[return: TRUE]\n"); return (TRUE); case HSE_REQ_SEND_URL : if (DllDebug || Debug) fprintf (stdout, " HSE_REQ_SEND_URL]\n"); /* this should be in effect a WASD CGI internal redirect */ CgiLibResponseRedirect (lpvBuffer); fflush (stdout); if (DllDebug) fprintf (stdout, "[return: TRUE]\n"); return (TRUE); case HSE_REQ_SEND_RESPONSE_HEADER : if (DllDebug || Debug) fprintf (stdout, " HSE_REQ_SEND_RESPONSE_HEADER]\n"); if (lpvBuffer == NULL) fputs ("Status: 200\r\n", stdout); else fputs (lpvBuffer, stdout); if (lpdwDataType == NULL) { if ((CharsetPtr = CliCharsetPtr) == NULL) { /* there is no script-enforced character set */ CharsetPtr = CgiLibVar ("REQUEST_CHARSET"); if (!CharsetPtr[0]) CharsetPtr = CgiLibVar ("SERVER_CHARSET"); if (!CharsetPtr[0]) CharsetPtr = DEFAULT_CHARSET; } fprintf (stdout, "Content-Type: text/plain; charset=%s\n\n", CharsetPtr); } else fputs ((char*)lpdwDataType, stdout); fflush (stdout); if (DllDebug) fprintf (stdout, "[return: TRUE]\n"); return (TRUE); case HSE_REQ_DONE_WITH_SESSION : if (DllDebug || Debug) fprintf (stdout, " HSE_REQ_DONE_WITH_SESSION]\n"); /* we're not threaded here, so anything pending's not supported! */ CgiLibResponseError (FI_LI, 0, "HSE_REQ_DONE_WITH_SESSION not supported!"); exit (SS$_NORMAL); case HSE_REQ_DLL_DEBUG_ON : if (DllDebug || Debug) fprintf (stdout, " HSE_REQ_DLL_DEBUG_ON\n return: TRUE]\n"); DllDebugOn = true; return (TRUE); case HSE_REQ_DLL_DEBUG_OFF : if (DllDebug || Debug) fprintf (stdout, " HSE_REQ_DLL_DEBUG_OFF\n return: TRUE]\n"); DllDebugOn = false; return (TRUE); case HSE_REQ_DLL_WRITE_BUFFER_SIZE : if (DllDebug || Debug) fprintf (stdout, " HSE_REQ_DLL_WRITE_BUFFER_SIZE\n return: TRUE]\n"); if (lpdwSize != NULL) { WriteBufferSize = *lpdwSize; /* zero disables output buffering */ if (WriteBufferSize) { /* make sure it's a sensible size */ if (WriteBufferSize < WRITE_BUFFER_SIZE_MIN) WriteBufferSize = WRITE_BUFFER_SIZE_MIN; else if (WriteBufferSize > WRITE_BUFFER_SIZE_MAX) WriteBufferSize = WRITE_BUFFER_SIZE_MAX; } } return (TRUE); default : if (DllDebug) fprintf (stdout, " return: FALSE]\n"); return (FALSE); } } /*****************************************************************************/ /* Get "command-line" parameters, whether from the command-line or from a configuration symbol or logical containing the equivalent. */ GetParameters () { static char CommandLine [256]; static unsigned long Flags = 0; register char *aptr, *cptr, *clptr, *sptr; int status; unsigned short Length; char ch; $DESCRIPTOR (CommandLineDsc, CommandLine); /*********/ /* begin */ /*********/ if (Debug) fprintf (stdout, "GetParameters()\n"); if ((clptr = getenv ("CGISAPI$PARAM")) == NULL) { /* get the entire command line following the verb */ if (VMSnok (status = lib$get_foreign (&CommandLineDsc, 0, &Length, &Flags))) exit (status); (clptr = CommandLine)[Length] = '\0'; } aptr = NULL; ch = *clptr; for (;;) { if (aptr != NULL && *aptr == '/') *aptr = '\0'; if (!ch) break; *clptr = ch; if (Debug) fprintf (stdout, "clptr |%s|\n", clptr); while (*clptr && isspace(*clptr)) *clptr++ = '\0'; aptr = clptr; if (*clptr == '/') clptr++; while (*clptr && !isspace (*clptr) && *clptr != '/') { if (*clptr != '\"') { clptr++; continue; } cptr = clptr; clptr++; while (*clptr) { if (*clptr == '\"') if (*(clptr+1) == '\"') clptr++; else break; *cptr++ = *clptr++; } *cptr = '\0'; if (*clptr) clptr++; } ch = *clptr; if (*clptr) *clptr = '\0'; if (Debug) fprintf (stdout, "aptr |%s|\n", aptr); if (!*aptr) continue; if (strsame (aptr, "/CHARSET=", 4)) { for (cptr = aptr; *cptr && *cptr != '='; cptr++); if (*cptr) cptr++; CliCharsetPtr = cptr; continue; } if (strsame (aptr, "/DBUG", -1)) { Debug = true; continue; } if (strsame (aptr, "/VERSION=", 4)) { for (cptr = aptr; *cptr && *cptr != '='; cptr++); if (*cptr) cptr++; IsapiMajorVersion = atoi(cptr); continue; } if (strsame (aptr, "/WBUFFER=", 4)) { for (cptr = aptr; *cptr && *cptr != '='; cptr++); if (*cptr) cptr++; WriteBufferSize = atoi(cptr); continue; } if (*aptr == '/') { fprintf (stdout, "%%%s-E-IVQUAL, unrecognized qualifier\n \\%s\\\n", Utility, aptr+1); exit (STS$K_ERROR | STS$M_INHIB_MSG); } if (CliDllFileNamePtr == NULL) { CliDllFileNamePtr = aptr; continue; } fprintf (stdout, "%%%s-E-MAXPARM, too many parameters\n \\%s\\\n", Utility, aptr); exit (STS$K_ERROR | STS$M_INHIB_MSG); } } /****************************************************************************/ /* Does a case-insensitive, character-by-character string compare and returns true if two strings are the same, or false if not. If a maximum number of characters are specified only those will be compared, if the entire strings should be compared then specify the number of characters as 0. */ boolean strsame ( register char *sptr1, register char *sptr2, register int count ) { while (*sptr1 && *sptr2) { if (toupper (*sptr1++) != toupper (*sptr2++)) return (false); if (count) if (!--count) return (true); } if (*sptr1 || *sptr2) return (false); else return (true); } /*****************************************************************************/