/*****************************************************************************/ /* CgiLib.c For C Language scripts these functions provide a number of common CGI activities as a simple object code module. One objective is the reasonably transparent, core support for WASD CGI and CGIplus, VMS Apache (CSWS), Purveyor, "vanilla" CGI (e.g. Netscape FastTrack), and OSU DECnet-based scripting. Of course all this is basically from the perspective of the WASD environment. THESE ROUTINES ARE NOT DESIGNED TO BE THREAD-SAFE! *** NOTE *** ------------ As of version 1.7 CGILIB only supports compilation into an object module as described immediately below. IT NO LONGER SUPPORTS THE DIRECT #include! ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ As of version 1.5 CGILIB was reworked to allow compilation into an object module by defining the macro CGILIB_OBJECT_MODULE for inclusion at link-time rather than at compilation time. This is obviously a more elegant way to build code and the author can only plead being brain-dead during the period it exploded from a handful of original functions just separated from application code for convenience to the far more extensive collection currently present. It *should* also allow for the pre-1.5 behaviour of source-code-inclusion for backward compatibility - but no absolute guarantees! A number of additional functions have been provided to allow 'Set'ting of run-time characteristics that previously could be pre-configured at compilation using macros. See a number of WASD scripts for hints of how to use as an object module. USING IN YOUR CODE ------------------ Build as an object module, It may then be linked against the rest of the application. $ CC CGILIB /DECC /STANDARD=RELAXED_ANSI /INCLUDE=[] Once compiled it may linked as an object module directly $ LINK /EXEC=HT_EXE:APPLICATION.EXE APPLICATION,[.OBJ_AXP]CGILIB.OBJ or if it has been placed into an object library $ LINK /EXEC=HT_EXE:APPLICATION.EXE APPLICATION,[.OBJ_AXP]CGILIB.OLB/LIBRARY or to the WASD_CGILIBSHR32 sharable image (if installed and defined) $ LINK /EXEC=HT_EXE:APPLICATION.EXE APPLICATION,SYS$INPUT/OPTIONS WASD_CGILIBSHR32/SHARE To change the default size of the CGIplus variable stream record buffer add an appropriate definition before #including the CGILIB header file, as shown in the following example: #define CGILIB_CGIPLUS_RECORD_SIZE 4096 FUNCTIONALITY ------------- Functions and global storage beginning 'CgiLib__' are for internal CGILIB use only, and should not directly be called by script code. SCRIPTING ENVIRONMENT --------------------- A number of functions are used to initialize and support behaviour in the various scripting environments. It should always be called before the use of any other functions. void CgiLibEnvironmentInit (int argc, char *argv[], int DoResponseCgi); The above function initializes the scripting environment detected according to various environment variables characteristic to each. The 'argc' and 'argv' standard C command-line parameters are only needed if the script is to support the OSU environment (zero and NULL may be substituted otherwise). Parameter 'DoResponseCgi' "forces" the functions to return a CGI response regardless of which environment it is executing in. int CgiLibEnvironmentIsApache (); int CgiLibEnvironmentIsCgi (); int CgiLibEnvironmentIsCgiPlus (); int CgiLibEnvironmentIsWasd (); int CgiLibEnvironmentWasdVersion (); int CgiLibEnvironmentIsOsu (); int CgiLibEnvironmentIsPurveyor (); Each of these functions returns true or false depending on which environment the script is executing in. CgiLibEnvironmentBinaryOut (); CgiLibEnvironmentRecordOut (); The above functions ensure the stream is appropriately set for either binary (non-record, no translation of newline characters, etc.) or standard output. Generally WASD and OSU always function in binary mode while standard CGI environments normally accept record-oriented output. Binary mode should always be used for non-textual output. char* CgiLibEnvironmentName (); char* CgiLibEnvironmentVersion (); char* CgiLibEnvironmentSetVarNone (char*); int CgiLibEnvironmentSetDebug (int); int CgiLibEnvironmentWasdVersion (); These five functions set the run-time characteristsic of the CGILIB environment. The first and second return a string identifying the server and CGILIB environment. The third accepts a pointer to either NULL or a string (most usually ""). The fourth turns CGILIB debug on or off. The fifth returns an integer representing the WASD server version (e.g. 80201 for v8.2.1). void CgiLibInit (int argc, char* argv[], void ProcessRequest(void)); This function can be used to execute a script function without the script needing to concern itself with whether it's been called in a standard CGI or CGIplus environment. REQUEST RESPONSES ----------------- These functions allow HTTP response headers and defacto WASD error and "success" report pages to be generated. int CgiLibResponseHeader (int StatusCode, char *ContentType, ...); The above function generates an HTTP response header. In most environments these will be CGI-compliant responses. This allows the server to provide whatever additional header fields it deems required. On an OSU server this will always be a full NPH HTTP response. This function also handles 302 (redirection, see also 'CgiLibResponseRedirect()') and 401 (authentication) responses. Supply NULL for 'ContentType' parameter and the location URL or authentication header line as parameter three. The third can be a 'printf()' format string with associated parameter supplied following. This can also be used to supply additional newline terminated header lines into the response. If a specific character set is required, begin the format string with "; charset=" and then the charset string, followed by a newline character. int CgiLibResponseRedirect (char *FormatString, ...); This function generates an HTTP 302 redirect response. If the format string begins "%!" then the redirect is made absolute with the appropriate local server host name and if necessary port prefixing whatever are supplied as parameters. If the "%!" does not precede the format string then it can either be a full absolute or local redirection URL, it's just appended to the "Location: " field name. The format string can be for a 'printf()' with associated parameters supplied. Strings fed to this function must be URL-encoded if necessary. int CgiLibResponseError (char *SrcFileName, int SrcLineNumber, int StatusValue, char *FormatString, ...); Generates a WASD format error report. If 'StatusValue' is non-zero then it is assumed to be a VMS status code and a message is generated from it. If zero then only the format string is used. The format string may be a plain-text message or contain 'printf()' formatting characters with any associated parameters. int CgiLibResponseSuccess (char *FormatString, ...); Generates a "success" report in the usual WASD format. The format string may be a plain-text message or contain 'printf()' formatting characters with any associated parameters. int CgiLibResponseGetStatus () Returns the HTTP response status (e.g. 200, 404) after being set by response generation (or 0). char* CgiLibResponseSetBody (char *string) char* CgiLibResponseSetCharset (char *string) char* CgiLibResponseSetDefaultCharset (char *string) char* CgiLibResponseSetErrorMessage (char *string) int CgiLibResponseSetErrorStatus (int status) char* CgiLibResponseSetErrorTitle (char *string) char* CgiLibResponseSetErrorInfo (char *string) char* CgiLibResponseSetSoftwareID (char *string) char* CgiLibResponseSetSuccessMessage (char *string) int CgiLibResponseSetSuccessStatus (int status) char* CgiLibResponseSetSuccessTitle (char *string) The above collection of functions allow various response strings and status codes to be set at run-time. Note that these functions do not allocate storage, whatever they are called with must point to something permanent. int CgiLibResponseSetBufferRecords (int); int CgiLibResponseSetRecordMode (int); int CgiLibResponseSetStreamMode (int); Tell the WASD server that these CGI response characteristics should be enabled/disabled. Must be used prior to CgiLibResponseHeader(). Returns previous value. NOTE ON CSWS V1.0-1 CGI RESPONSE -------------------------------- 12-JAN-2001: There seems to be a bug in the Apache 1.3.12 CGI engine, confirmed by Compaq VMS Engineering with the ASF. If the request has a "Pragma: no-cache" field (i.e. [RELOAD] button) and the script returns a "Status: nnn" response status line, with a trailing "Last-Modified: date" field, the CSWS server returns a header but no body. Although I haven't tried all the in-and-outs of possible field combinations this one's a sure winner. Hence this library introduces a fudge, not returning the "Status: nnn" field for responses where it is not absolutely required, when Apache is detected. This reults in a "Content-Type: xxxxx", "Location: URL", etc., being returned as the first line and seems to work well enough and without the original problem. CGI VARIABLES ------------- This library provides a simple, uniform mechanism for access to CGI variable values transparently in the WASD CGI and CGIplus environments, as well as OSU (via the request dialog phase) and "vanilla" CGI environments (e.g. Netscape FastTrack). CGI variables names are always assumed to be in upper-case and to be preceded by "WWW_". It will automatically detect and adjust behaviour according to whether the CGI variables are actually prefixed with "WWW_" (as with WASD, OSU and Purveyor) or not (as with Netscape FastTrack). char* CgiVar (char *VarName); char* CgiLibVar (char *VarName); Simply call the CgiLibVar() function (or CgiVar() for backward-compatibility) with the CGI variable name to be accessed and if that CGI variable exists a pointer to the value will be returned. Non-existant variables return CGILIB_NONE, which is by default an empty string (""). The values pointed to should NOT be modified in any way (copy the value if this is required). char* CgiLibVarNull (char *VarName); Function CgiLibVarNull() will always return NULL if the CGI variable does not exist, or a pointer to the variable string value. This may be used to differentiate between a CGI variable containing an empty value and not actually existing at all. int CgiLibVarByDesc ( struct dsc$descriptor *NameDsc, struct dsc$descriptor *ValueDsc, short int *Length ); This function may be used to obtain CGI variables using VMS descriptors. Returns zero and a space-filled value descriptor if the variable does not exist, one and a space padded value if it does. The 'Length' address parameter is optional and if supplied the actual length of the variable value string (excluding any padding spaces) is set. CGIPLUS ------- In the WASD CGIplus environment supplying an empty variable name, CgiLibVar(""), will cause the function to block until the arrival of a request, upon which it just returns a CGILIB_NONE pointer, allowing start-of-request synchronization. This also resets the variables in preparation for reading the CGI data stream of the next request, an alternative to calling with a NULL parameter at the end of request processing. Supplying a wildcard variable name, CgiLibVar ("*"), returns each CGIplus variable 'name=value' string in turn, with end-of-variables being indicated by a NULL pointer. CgiLibCgiPlusEOF (); This function will output a suitably flushed (to make it a single record) CGIplus end-of-file indicator telling the server the script is finished servicing the response. CgiLibCgiPlusESC (); CgiLibCgiPlusEOT (); These two functions output records to delimit the beginning and and (respectively) of a CGIplus callout. void CgiLibCgiPlusInGets (char *String, int SizeOfString); This CGIplus function allows a single string to be read from the CGIPLUSIN stream. This is intended for a script in callout mode to read a single record response from the server. If the record read overflows the string buffer provided it is truncated and null-terminated. char *CgiLibCgiPlusCallout (char *Format, ...); The above function allows a CGIplus callout to be performed with a format string and variable number of arguments. If the format string (callout) does not begin with a '!' or '#' (no response expected) then a callout response is read into static storage and and a pointer to that returned. void CgiLibCgiPlusSetVarRecord (); void CgiLibCgiPlusSetVarStruct (); The two functions above switch the CGI variable stream between 'record' mode (the default, where each CGI variable occupies an individual I/O on CGIPLUSIN stream) and 'struct' mode (the CGI variables are I/Oed as a single, binary structure). 'Struct' mode is SIGNIFICANTLY more efficient and has lower latency. The WASD HTTPd version is detected and for 7.2 and following the stream is automatically switched to 'struct' mode if neither of the functions have been used to explicitly set the mode. CGILIB may also be forced to retain 'record' mode by creating the environment variable CGILIB_CGIPLUS_VAR_RECORD (e.g. define it as a logical, assign symbol). This facility may be useful for performance comparisons, etc. See [SRC.HTTPD]CGI.C for information on the internal structure of the 'struct' stream. Transfering the CGIplus variables as a single structure (rather than as per-variable records) can double the throughput!! Demonstrated using the [SRC.CGIPLUS]CGIPLUSTEST.C program. This indicates that there is much less than half the overhead for performing this using the 'struct' method! FORM-ENCODED PARSE ------------------ char* CgiLibFormEncodedParse (char *String, int *Context); This function parses a "application/x-www-form-urlencoded" string into it's decoded, constituent name and value pairs. It can be passed *any* urlencoded string including QUERY_STRING CGI variables and POSTed request bodies. It returns a dynamically allocated string comprising a "name=value" pair, in much the same format as a CgiLibVar("*") call. When the form fields are exhausted it returns a pointer to NULL. The form field name is not prefixed by "WWW_FORM", or anything else. This buffer is reused with each call and so should not be modified, any contents should be copied as with CgiLibVar() constraints. The 'Context' parameter should be initialized to zero before the initial call. At the end of a parse it is reinitialized to zero. URL-DECODE ---------- int CgiLibUrlDecode (char *String); Decodes a URL-encoded string (i.e. string containing "+" substituted for spaces and other forbidden characters encoded using the "%xx" hexadecimal scheme). Resultant string is always the same size or smaller so it can be done IN-SITU! Returns the size of the resultant string. URL-ENCODE ---------- int CgiLibUrlEncode (char *PlainString, int NumberOfChars, char *EncodedString, int SizeOfEncodedString); URL-encode all non-alpha-numeric characters. If 'EncodedString' is NULL sufficient memory will be dynamically allocated to hold the encoded string. For fixed size 'EncodedString' it will not overflow the supplied string but does not return any indication it reached the limit. Returns the length of the encoded string or a pointer to the encoded string (which should be cast to (char*) by the calling routine). If 'NumberOfChars' is -1 all characters up to the null are copied. If 'SizeOfEncodedString' is -1 the target storage if considered "big enough". URL-ENCODE FILE NAME -------------------- int CgiLibUrlEncodeFileName (char *FileName, char *EncodedString, int SizeOfEncodedString, int RelaxedEncode, int ForceLower); URL-encode (%nn) a possibly extended specification file name. This can be done in two ways. Leave the VMS extended file specification escape sequences in place (e.g. "^_") but encode absolutely forbidden characters (.e.g ' ', '?', "%", etc.), called 'RelaxedEncode' here. Or a strict encode, where the extended escaped sequences are first un-escaped into characters, then those charaters URL-encoded as necessary. If 'ForceLower' true, and no extended file specification characters were found (e.g. lower-case, escaped characters) then replace all upper-case alphabetics with lower-case. If 'SizeOfEncodedString' is -1 the target storage if considered "big enough". If 'EncodedString' is NULL sufficient memory will be dynamically allocated to hold the encoded string. HTML-ESCAPE ----------- int CgiLibHtmlEscape (char *PlainString, int NumberOfChars, char *EscapedString, int SizeOfEscapedString); This function escapes characters forbidden to occur in HTML documents (i.e. '<', '>' and '&'). If 'EscapedString' is NULL sufficient memory will be dynamically allocated to hold the encoded string. For fixed size 'EscapedString' it will not overflow the supplied string but does not return any indication it reached the limit. Returns the length of the escaped string or a pointer to the encoded string (which should be cast to (char*) by the calling routine). int CgiLibAnchorHtmlEscape (char *PlainString, int NumberOfChars, char *EscapedString, int SizeOfEscapedString, int AnchorUrls); With this function 'AnchorUrls' may be used to turn HTML URLs into HTML anchors in the text by calling with CGILIB_ANCHOR_WEB. It will also attempt to detect mail addresses (anything like 'this@wherever.host.name') and create "mailto:" links out of them, call with CGILIB_ANCHOR_MAIL. To get both use a bit-wise OR. Normally this will open the link in the same page. To split the link into a left half that opens target="_top" and a right half that opens target="_blank" bitwise OR in CGILIB_ANCHOR_SPLIT. If 'NumberOfChars' is -1 all characters up to the null are copied. If 'SizeOfEscapedString' is -1 the target storage if considered "big enough". int CgiLibHtmlDeEntify (char *String) Converts numeric HTML entities in a string into their character equivalents (e.g. "&" to '&', "� to 0x00, etc.) Also converts common alphabetic entities (e.g. "&",  ", <", etc.) but not all (any that are not recognised are left untouched). Does not URL-decode! Resultant string is always the same size or smaller so it can be done IN-SITU! Returns the size of the resultant string. VEEMEM ------ Virtual memory support functions using the LIB$*_VM_* routines. Use CgiLibVeeMemInit(), CgiLibVeeMemCalloc(), CgiLibVeeMemRealloc(), CgiLibVeeMemFree() and CgiLibVeeMemReset() to manage application dynamic memory. Using CgiLibVeeMemReset() at the conclusion of processing releases *all* zone allocated memory in one fell swoop. This significantly simplifies memory management and reduces the chances of memory leaks. CgiLibVeeMemInUse() controls whether applicable CGILIB functions also use VeeMem. CGILIB functions that are VeeMem-capable: CgiLibUrlEncode(), CgiLibUrlEncodeFileName(), CgiLibHtmlEscape(), CgiLibAnchorHtmlEscape(), CgiLibFormEnocdedParse(), CgiLibFaoPrint(), CgiLibVarByDesc() . CAUTION: All allocated memory must no longer be in use before zone reset!! Storage that needs to persist across VeeMemReset() should be calloc()ed. CgiLibVeeMemContinue() is used to control behaviour on memory allocation failure. CGIlib usually just exits with an error status when a memory allocation fails. With some memory allocation it's better to know that it failed. Calling this with either 1 (continue returning the expected result when failed) or 0 (just exit with the error status) allows fine-grained behaviour control when using functions that employ dynamic memory. Works for both CgiLibVeeMem..() allocated memory and calloc()/realloc() etc. Returns the previous state. MISCELLANEOUS ------------- char* CgiLibHttpStatusCodeText (int StatusCode); Returns a string to a "standard" status code meaning, e.g. 200 "Success". char* CgiLibFaoPrint (FILE *StreamPtr, char *FormatString, ...); This is really just a convenience function and has little to do with CGI scripting. VMS $FAO-formatted print statement. Output is either to the specified stream, or if stream pointer NULL return a pointer to a dynamic buffer buffer containing the formatted output (requires caller to dispose of it as necessary). char* CgiLibXUACompatible (char* ContentPtr); Set and return the MSIE "X-UA-Compatible" compatibility HTML meta field. http://blogs.msdn.com/b/askie/archive/2009/03/23/understanding-compatibility-modes-in-internet-explorer-8.aspx The meta string is designed to allow being directly included in the response .. data. It should be the first field following the opening . If 'ContentPtr' is NULL return the current value of the meta string (will be an empty string if uninitialised). If 'ContentPtr' is to an empty string it defaults to "IE=edge". If a non-empty string the meta content=".." will contain the string parameter. If the string pointer has the value (char*)(-1) then reset the meta to NULL. Setting this value also results in CGILIB .. data containing the meta. PROCESSING POSTED REQUESTS -------------------------- The processing POSTed request bodies can often be tiresome to code without the use of a library. The functions provided by CGILIB attempt to provide the basic function required. char* CgiLibReadRequestBody (char **BodyPtrPtr, int *BodyLength); The above function will read a POSTed request body into a single array of char (doesn't matter whether it's text or binary). Returns NULL if it's not a POST, a pointer if it is. The caller must free() this memory when finished with it. This works with WASD, "vanilla" CGI and OSU environments. With OSU it _must_ be called before initializing output (with CgiLibOsuStdoutCgi/Raw()) which takes the script out of dialog and into output phase. int CgiLibFormRequestBody (char *BodyPtr, int BodyLength); This takes a "application/x-www-form-urlencoded" or "multipart/form-data" body (which could have been read with the preceding function) and decodes the form fields inserting them into the list of CGI variables as if they were "WWW_FORM_" values processed from the query string. The values may then be accessed as if standard CGI variables using CgiLibVar("WWW_FORM_..."). The ability to access the contents of such POSTed requests in this fashion is a *VERY USEFUL CAPABILITY*. int CgiLibBodyIsFormUrlEncoded (); int CgiLibBodyIsMultipartFormData (); These functions return true if the request body is "application/x-www-form-urlencoded" and "multipart/form-data" respectively. NOTE ON MULTIPART/FORM-DATA: As it is possible to POST un-encoded characters ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ with this method (all characters from 0x00 to 0xff are transmitted un-encoded) non-printable characters (0x00-0x1f, 0x7f-0x9f, 0xff) are HTML-entified (i.e. 0x00 becomes "�", 0x01 "", etc.) before storage as variables. These would need to be de-entified before use (with CgiLibHtmlDeEntify()). "multipart/form-data" is generated using the "" HTML form tag. If the field is an upload ("type=file") and there is an associated content-type and file name (as there should be) these are placed in variables with the same name as the field with "_MIME_CONTENT_TYPE" and "_MIME_FILENAME" added. OSU ENVIRONMENT --------------- The OSU (DECthreads) HTTP server DECnet-based scripting environment is supported semi-transparently. CgiLibOsuInit (int argc, char *argv[]); When OSU is detected the CgiLibOsuInit() initializes the OSU request information (by engaging in the dialog phase) storing CGI-like variables for later access by CgiLibVar(). It must be supplied with command line arguments. Supplying a wildcard variable name (i.e. "*") to CgiLibVar() returns each OSU variable 'name=value' string in turn, with no more being indicated by a NULL pointer. CgiLibOsuStdoutRaw (); The function CgiLibStdoutRaw() reopen()s to NET_LINK. An explicit used of this function is not required if CgiLibEnvironmentInit() has been called. Script responses are designed to be returned in OSU "raw" mode; the script taking care of the full response header and correctly carriage-controlled data stream, text or binary!! The script standard output stream is reopened in binary mode (no translation of '\n') with a maximum record size set to 4096 bytes (failure to set this results in the OSU server reporting %SYSTEM-F-DATAOVERUN errors). The binary output is enclosed by suitably fflushed() "" and "" control tags (the flush make them individual records as required by OSU). If in debug the script output is placed into "text" mode. CgiLibOsuStdoutCgi (); This function reopen()s to NET_LINK. An explicit used of this function is not required if CgiLibEnvironmentInit() has been called. Script responses are designed to be returned in OSU "CGI" mode; very similar to other implementations where the output begins with a CGI-compliant header. The script standard output stream is reopened in binary mode (no translation of '\n') with a maximum record size set to 4096. The script output is enclosed by suitably fflushed() "" and "" control tags (the flush make them individual records as required by OSU). If in debug the script output is placed into "text" mode. COPYRIGHT --------- Copyright (C) 1999-2022 Mark G.Daniel Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. VERSION HISTORY (update CGILIB_VERSION as well!) --------------- 26-AUG-2023 MGD v2.1.2, GATEWAY_SYMBOLS allows vanilla CGI CgiVar("*") 28-MAY-2022 MGD v2.1.1, CgiLibAnchorHtmlEscape() escape single quote 05-JUN-2021 MGD v2.1.0, CgiLibCgiPlusCallout() bugfix; CgiLibCgiPlusInGets() absorb empty lines 22-MAY-2020 MGD v1.9.9, bugfix; CgiLib__ApacheSetSockOpt() LIB$M_FIS_MIXEDCASE 08-FEB-2020 MGD v1.9.8, CgiLibAnchorHtmlEscape() handle NULL string 22-MAY-2018 MGD v1.9.7, CgiLib__ApacheSetSockOpt() both VSI and HP(E) symbols 23-DEC-2017 MGD v1.9.6, CgiLibVeeMemContinue() allocation failure behaviour 16-OCT-2013 MGD v1.9.5, CgiLibXUACompatible() 09-JUN-2013 MGD v1.9.4, tweak HTML a little 25-FEB-2012 MGD V1.9.3, CgiLibUrlDecode() decode URL-encoded 8 bit UTF-8 04-JAN-2011 MGD v1.9.2, bugfix; CgiLibHttpStatusCodeText() 203/204 transposed 04-MAR-2010 MGD v1.9.1, CgiLibReadRequestBody() accomodate CgiLibVeeMem..() CgiLibVeeMem..() to use void* rather than char* 23-MAY-2009 MGD v1.9.0, CgiLibVeeMem..() functions 19-MAY-2009 MGD v1.8.21, bugfix; CgiLib__ApacheExitRecordMode() flush stream before reverting to record mode 11-MAR-2009 MGD v1.8.20, bugfix; CgiLibResponseHeader() make "text/" content-type detection case-insensitive 21-JAN-2009 MGD v1.8.19, bugfix; CgiLib__ApacheExitRecordMode() allow for both Apache versions 1.n and 2.n 12-NOV-2008 MGD v1.8.18, CgiLibHtmlEscape() retain empty URLs as text 19-OCT-2008 MGD v1.8.17, CgiLibOsuInit() noticed OSU had no SERVER_ADDR 21-JUL-2008 MGD v1.8.16, CgiLibAnchorHtmlEscape() soyMAIL hack to make "mailto:"s access soyMAIL as mail agent bugfix; CgiLib__BodyMultipartFormData() boundary= bugfix; CgiLib__VarList() use offsets rather than pointers when realloc() exhausted storage 13-APR-2008 MGD v1.8.15, bugfix; CgiLibResponseSet[Default]Charset() do not allow a null pointer charset to be set 10-APR-2008 JPP v1.8.14, bugfix; CgiLib__VarList() storage overflow 28-MAR-2008 MGD v1.8.13, CgiLibHtmlDeEntify() enhance entity scope 03-APR-2007 MGD v1.8.12, add CgiLibResponseGetStatus() and assoc code emulate CGI variable REQUEST_URI for OSU 26-MAR-2007 MGD v1.8.11, bugfix; CgiLibOsuInit() no URL-decode 12-JAN-2007 MGD v1.8.10, CgiLib__VarList() kludge to obfuscate FORM_ variables for soyMAIL purposes 30-OCT-2006 MGD v1.8.9, bugfix; CgiLib__GetPathTranslated() 21-APR-2006 MGD v1.8.8, CgiLibAnchorHtmlEscape() allow for 'split anchors' (left side of anchor opens target=_top, right side opens target=_blank) using CGILIB_ANCHOR_SPLIT 27-MAR-2006 MGD v1.8.7, CgiLibResponseRedirect() Purveyor (at least v1.2.2) seems to require a full URL for the "Location:" 16-MAR-2006 MGD v1.8.6, bugfix; CgiLibResponseRedirect() OSU seems to need a "Content-Type:.." in with the "Status: 302"!? 02-MAR-2006 MGD v1.8.5, bugfix; CgiLibEnvironmentInit() for VMS Apache V2.n and later (as with Apache V1.n) freopen("mrs=16384") 25-JAN-2006 MGD v1.8.4, bugfix; when URL-encoded decoding use unsigned char to prevent sign bit issues with the likes of %FC, bugfix; CgiLibEnvironmentName() static storage 30-SEP-2005 MGD v1.8.3, only generate a "100 Continue" if there was an appropriate "Expect: 100-continue" request header 07-SEP-2005 MGD v1.8.2, CgiLibBodyIsFormUrlEncoded() CgiLibBodyIsMultipartFormData() add file:// to CgiLibAnchorHtmlEscape() 19-MAY-2005 MGD v1.8.1, bugfix; APACHE$INPUT seems to return -1 rather than 0 (as HTTP$INPUT) when input stream exhausted CgiLibReadRequestBody() check read against length 04-MAY-2005 MGD v1.8.0, support SWS 2.0 along with CSWS 1.2/1.3, CgiLib__ApacheSetSockOpt() for SWS 2.0 BG control 22-JAN-2005 MGD v1.7.4, CgiLibResponseSetBufferRecords(), CgiLibResponseSetRecordMode(), CgiLibResponseSetStreamMode() 12-DEC-2004 MGD v1.7.3, bugfix; CgiLib__PrepareRequest() check for content-type "multipart/form-data" 21-OCT-2004 MGD v1.7.2, detection of WASD server request body unencode using a CGI variable CONTENT_LENGTH set to "?", CgiLibEnvironmentWasdVersion() 07-OCT-2004 JFP v1.7.1, options file and build procedure for sharable image 25-SEP-2004 MGD v1.7.0, HTTP/1.1 compliance, obsolete ability to #include CGILIB source new function CgiLibFaoPrint() 20-MAY-2004 MGD v1.6.13, bugfix; (introduced with 1.6.12) with CGIplus ensure body is read for each request 26-JAN-2004 MGD v1.6.12, bugfix; CgiLibReadRequestBody() if the body has already been read then just return the ptr (CSWS) 23-DEC-2003 MGD v1.6.11, minor conditional mods to support IA64 14-JUN-2003 MGD v1.6.10, bugfix; CgiLibVarByDesc() (jpp@esme.fr) bugfix; CgiLib_PrepareRequest() (jpp@esme.fr) 24-MAR-2003 MGD v1.6.9, (courtesy Jean-Pierre Petit, jpp@esme.fr) CgiLibFormRequestBody() use strstr() on "..form-urlencoded" to accomodate some browsers, CgiLibInit(), CgiLib__PrepareRequest() and CgiLibVarByDesc() 03-MAR-2003 MGD v1.6.8, CgiLib__BodyMultipartFormData() now entifies only 'control' characters (backward compatibility via environment variable CGILIB_MULTIPART_ISPRINT 30-DEC-2002 MGD v1.6.7, bugfix; freopen() in CgiLibEnvironmentInit() 11-OCT-2002 MGD v1.6.6, refine use of APACHE$FIXBG bit toggling (also ensuring SYS$OUTPUT returns to record mode on exit) 11-SEP-2002 MGD v1.6.5, bugfix; CgiLibResponseHeader() 'charset=' supplied is parameter format string 15-AUG-2002 MGD v1.6.4, add debug content-type to CGIplus CgiLib__GetVar() (somewhat unsuccessfully, see note), bugfix; query string key values for non-WWW prefix 22-APR-2002 MGD v1.6.3, CSWS APACHE_SHARED_SOCKET to APACHE$SHARED_SOCKET, bugfix; more aspects of "WWW_"-less CGI variables 27-JUN-2001 MGD v1.6.2, bugfix; adjust CgiLib__SetEnvironment() GATEWAY_INTERFACE detection (for OSU) 03-MAY-2001 MGD v1.6.1, bugfix; some aspects of "WWW_"-less CGI variables 07-APR-2001 MGD v1.6.0, provide for streamed CGIplus variables, bugfix; refine changes from 1.5.3 22-MAR-2001 MGD v1.5.3, refine CgiLib__GetVar() to allow environments without "WWW_" CGI variable prefix to be supported 08-JAN-2001 MGD v1.5.2, CSWS V1.0-1 (VMS Apache) "fixbg" support (see note above), APACHE_INPUT becomes APACHE$INPUT, bugfix; encode/escape terminate empty result string, bugfix; CgiLib__GetKey() 09-DEC-2000 MGD v1.5.1, bugfix; CgiLibResponseHeader() charset 28-OCT-2000 MGD v1.5.0, make CGILIB available as an object module, add 'Set' functions to parallel previous macros, make adjustments for RELAXED_ANSI compilation 24-JUN-2000 MGD v1.4.2, make CgiLibVar() real function (not #define), bugfix; CgiLibResponseError(), bugfix; form fields in non-CGIplus environments (Imre Fazekas, fazekas@datex-sw.hu) 30-MAY-2000 MGD v1.4.1, explicitly exit SS$_INSFMEM if allocation fails 09-APR-2000 MGD v1.4.0, VMS Apache environment (1.3.9 BETA), CgiLibEnvironmentName() returns WWW_SERVER_SOFTWARE, modify report format inline with WASD changes 19-FEB-2000 MGD v1.3.0, CgiLibUrlEncodeFileName(), CgiLibHtmlDeEntify(), CgiLibFormRequestBody() will now also process "multipart/form-data" bodies, change CgiLib__VarList() data structure to use full integers instead of short integers for lengths, bugfix; worst-case %nn in URL-encodes 24-SEP-1999 MGD v1.2.0, changes to support Purveyor, CgiLibCgiPlus...(), CgiLibEnvironment...(), CgiLibResponse...(), CgiLibAnchorHtmlEscape() 26-JUN-1999 MGD v1.1.0, support POSTed body forms under standard CGI 24-APR-1999 MGD v1.0.0, initial '#include'able code module */ /*****************************************************************************/ #define CGILIB_VERSION "2.1.2" #ifdef __ALPHA # define CGILIB_SOFTWAREID "CGILIB AXP-" CGILIB_VERSION #endif #ifdef __ia64 # define CGILIB_SOFTWAREID "CGILIB IA64-" CGILIB_VERSION #endif #ifdef __x86_64 # define CGILIB_SOFTWAREID "CGILIB X86-" CGILIB_VERSION #endif /* standard C headers */ #include #include #include #include #include #include #include #include #include #include /* VMS headers */ #include #include #include #include #include #include #include /* required for OSU server address */ /** HP C V7.1-015 on OpenVMS Alpha V8.3 reports the following (apparently) because of a nesting error in RESOLV.H due to the __CRTL_VER constraint that WASD applications are built with. This does not happen with Compaq C V6.4-005 on OpenVMS Alpha V7.3-2 for instance. Just avoid the error by pretending the header file is already loaded. Doesn't *seem* to cause side-effects. #pragma __member_alignment __restore ........^ %CC-W-ALIGNPOP, This "restore" has underflowed the member alignment's stack. No corresponding "save" was found. at line number 451 in module RESOLV of text library SYS$COMMON:[SYSLIB]DECC$RTLD **/ #define __RESOLV_LOADED 1 #include /* module header */ #ifndef CGILIB_OBJECT_MODULE #define CGILIB_OBJECT_MODULE #endif #ifdef CGILIB_OBJECT_MODULE #include "cgilib.h" #else #error *** CGILIB V1.7 #include at compile time is no longer supported *** #endif /* just modifies the handling of a couple of things */ #define CGILIB_DEVELOPMENT 1 #define CGILIB_VARLIST_DEBUG 0 /******************/ /* global storage */ /******************/ int CgiLib__Debug, CgiLib__CgiPrefixWWW, CgiLib__CgiPlusLoadVariables = 1, CgiLib__CgiPlusUsageCount, CgiLib__CgiPlusVarRecord, CgiLib__CgiPlusVarStruct, CgiLib__Environment, CgiLib__EnvironmentStdoutType = CGILIB_ENVIRONMENT_STDOUT_RECORD, CgiLib__ApacheVersion, CgiLib__HttpProtocolVersion, CgiLib__BodyIsFormUrlEncoded, CgiLib__BodyIsMultipartFormData, CgiLib__ResponseBufferRecords, CgiLib__ResponseCount, CgiLib__ResponseCgi, CgiLib__ResponseErrorStatus, CgiLib__ResponseRecordMode, CgiLib__ResponseStatusCode, CgiLib__ResponseStreamMode, CgiLib__ResponseSuccessStatus, CgiLib__VeeMemCurrent, CgiLib__VeeMemInUse, CgiLib__VeeMemPeak, CgiLib__VeeMemContinue, CgiLib__WasdVersion; unsigned long CgiLib__VeeMemZoneId; char *CgiLib__CgiPlusEofPtr, *CgiLib__CgiPlusEotPtr, *CgiLib__CgiPlusEscPtr, *CgiLib__HttpProtocolVersionPtr, *CgiLib__QueryStringPtr, *CgiLib__ResponseBodyPtr, *CgiLib__ResponseCharsetPtr, *CgiLib__ResponseDefCharsetPtr, *CgiLib__ResponseErrorInfoPtr, *CgiLib__ResponseErrorMessagePtr, *CgiLib__ResponseErrorTitlePtr, *CgiLib__ResponseSoftwareIdPtr, *CgiLib__ResponseSuccessMsgPtr, *CgiLib__ResponseSuccessTitlePtr, *CgiLib__soyMAILhack, *CgiLib__VarNonePtr = CGILIB_NONE, *CgiLib__XUACompatiblePtr; char CgiLib__SoftwareID [] = CGILIB_SOFTWAREID, CgiLib__VarEmpty [] = ""; FILE *CgiLib__CgiPlusInFile; /*****************************************************************************/ /**************/ /* INITIALIZE */ /**************/ /*****************************************************************************/ /* (jpp@esme.fr) Wrapper function for transparently handling a script without it needing to concern itself with whether it's been called in a standard CGI or CGIplus environment. */ void CgiLibInit ( int argc, char* argv[], void ProcessRequest(void) ) { int Debug, IsCgiPlus; /*********/ /* begin */ /*********/ Debug = (getenv ("CGILIB_DEBUG") != NULL); CgiLibEnvironmentSetDebug (Debug); CgiLibEnvironmentInit (argc, argv, 0); IsCgiPlus = CgiLibEnvironmentIsCgiPlus (); if (IsCgiPlus) { for (;;) { /* block waiting for the next request */ CgiLibVar (""); CgiLib__PrepareRequest (ProcessRequest); CgiLibCgiPlusEOF (); } } else CgiLib__PrepareRequest (ProcessRequest); } /*****************************************************************************/ /* (jpp@esme.fr) Support function for CgiLibInit(). */ void CgiLib__PrepareRequest (void ProcessRequest(void)) { int PostBufferCount; char *PostBufferPtr, *CgiRequestMethodPtr, *CgiContentTypePtr; /*********/ /* begin */ /*********/ CgiRequestMethodPtr = CgiLibVar ("WWW_REQUEST_METHOD"); if (!strcmp (CgiRequestMethodPtr, "POST")) { CgiContentTypePtr = CgiLibVar ("WWW_CONTENT_TYPE"); if (strstr (CgiContentTypePtr, "application/x-www-form-urlencoded") || strstr (CgiContentTypePtr, "multipart/form-data")) { /* is URL form encoded, turn that into pseudo CGI variables */ CgiLibReadRequestBody (&PostBufferPtr, &PostBufferCount); CgiLibFormRequestBody (PostBufferPtr, PostBufferCount); if (PostBufferPtr == NULL || PostBufferCount == 0) { CgiLibResponseError (__FILE__, __LINE__, 0, "No body in the POST!"); return; } if (CgiLib__VeeMemInUse) CgiLibVeeMemFree (PostBufferPtr); else free (PostBufferPtr); } } ProcessRequest (); } /*****************************************************************************/ /* Initialize the scripting environment. Parameter 'DoResponseCgi' if true sets the script do return CGI compliant responses regardless of whether it can support others or not. */ void CgiLibEnvironmentInit ( int argc, char *argv[], int DoResponseCgi ) { int OsuBodyLength; char *OsuBodyPtr, *OsuMethodPtr; /*********/ /* begin */ /*********/ CgiLib__ResponseBodyPtr = CGILIB_RESPONSE_BODY; CgiLib__ResponseCharsetPtr = CGILIB_RESPONSE_CHARSET; CgiLib__ResponseDefCharsetPtr = CGILIB_RESPONSE_DEFAULT_CHARSET; CgiLib__ResponseErrorInfoPtr = CGILIB_RESPONSE_ERROR_INFO; CgiLib__ResponseErrorMessagePtr = CGILIB_RESPONSE_ERROR_MESSAGE; CgiLib__ResponseErrorStatus = CGILIB_RESPONSE_ERROR_STATUS; CgiLib__ResponseErrorTitlePtr = CGILIB_RESPONSE_ERROR_TITLE; CgiLib__ResponseSoftwareIdPtr = CGILIB_RESPONSE_SOFTWAREID; CgiLib__ResponseSuccessMsgPtr = CGILIB_RESPONSE_SUCCESS_MESSAGE; CgiLib__ResponseSuccessStatus = CGILIB_RESPONSE_SUCCESS_STATUS; CgiLib__ResponseSuccessTitlePtr = CGILIB_RESPONSE_SUCCESS_TITLE; if (CgiLib__VarNonePtr != NULL && !CgiLib__VarNonePtr[0]) CgiLib__VarNonePtr = CgiLib__VarEmpty; if (CgiLib__Debug) { /* force it to CGI compliant response */ DoResponseCgi = 1; /* CGI interfaces object to this non-CGI output before the header */ /** if (CgiLib__Debug) fprintf (stdout, "CgiLibEnvironmentInit()\n"); **/ } CgiLib__EnvironmentStdoutType = CGILIB_ENVIRONMENT_STDOUT_RECORD; CgiLib__CgiPlusEofPtr = CgiLib__CgiPlusEotPtr = CgiLib__CgiPlusEscPtr = NULL; CgiLib__ResponseCgi = DoResponseCgi; CgiLib__SetEnvironment (); if (CgiLib__Environment == CGILIB_ENVIRONMENT_OSU) { /*******************/ /* OSU environment */ /*******************/ /* OSU is always sent as a binary stream ... *always* */ CgiLib__EnvironmentStdoutType = CGILIB_ENVIRONMENT_STDOUT_BINARY; CgiLibOsuInit (argc, argv); OsuMethodPtr = CgiLib__GetVar ("WWW_REQUEST_METHOD", ""); if (strcmp (OsuMethodPtr, "POST") || strcmp (OsuMethodPtr, "PUT")) { /* any body MUST be read before terminating the dialog phase! */ CgiLibReadRequestBody (&OsuBodyPtr, &OsuBodyLength); } if (CgiLib__ResponseCgi = DoResponseCgi) CgiLibOsuStdoutCgi (); else CgiLibOsuStdoutRaw (); } else #ifdef BLAH if (CgiLib__Debug) { /* debug output is plain-text */ fputs ( "Content-Type: text/plain\nScript-Control: X-content-encoding-gzip=0\n\n", stdout); /* this works with WASD, Apache, doesn't with OSU, others? */ if (CgiLib__Environment == CGILIB_ENVIRONMENT_APACHE || CgiLib__Environment == CGILIB_ENVIRONMENT_CGIPLUS || CgiLib__Environment == CGILIB_ENVIRONMENT_WASD) { fputs ("$ SHOW PROCESS /ALL\n", stdout); system ("show process/all"); fputs ("$ SHOW LOG /PROCESS *\n", stdout); system ("show log /process *"); fputs ("$ SHOW SYMBOL *\n", stdout); system ("show symbol *"); } } else #endif if (CgiLib__Environment == CGILIB_ENVIRONMENT_APACHE || CgiLib__Environment == CGILIB_ENVIRONMENT_CGIPLUS || CgiLib__Environment == CGILIB_ENVIRONMENT_WASD) { /* WASD CGI/CGIplus and Apache default is a binary stream. Binary streams are more efficient because the C-RTL buffers multiple '\n'-delimited records up to the size of the stdout device buffer capacity, then QIOs, rather that QIOing each '\n' record. */ CgiLib__EnvironmentStdoutType = CGILIB_ENVIRONMENT_STDOUT_BINARY; if (CgiLib__Environment == CGILIB_ENVIRONMENT_APACHE) { if (CgiLib__ApacheVersion == 1) CgiLib__ApacheFixBg(1); else { /* SWS 2.0 (and hopefully later :-) binary mode and buffer size */ CgiLib__ApacheSetSockOpt(0); CgiLib__ApacheSetSockOpt(16384); } stdout = freopen ("SYS$OUTPUT:", "w", stdout, "ctx=bin", "mrs=16384"); } else stdout = freopen ("SYS$OUTPUT:", "w", stdout, "ctx=bin"); if (stdout == NULL) exit (vaxc$errno); } CgiLib__ResponseCount = 0; } /*****************************************************************************/ /* Determine which environment we're executing within. */ void CgiLib__SetEnvironment () { char *cptr; /*********/ /* begin */ /*********/ /* CGI interfaces object to this non-CGI output before the header */ /** if (CgiLib__Debug) fprintf (stdout, "CgiLib__EnvironmentCheck()\n"); **/ /* look for WASD request body I/O stream logical */ if (getenv ("HTTP$INPUT") != NULL) { /* look for CGIplus stream logical */ if ((CgiLib__CgiPlusEofPtr = getenv("CGIPLUSEOF")) != NULL) { CgiLib__Environment = CGILIB_ENVIRONMENT_CGIPLUS; return; } } #if CGILIB_DEVELOPMENT if (getenv ("CGILIB__ENVIRONMENT") != NULL) { CgiLib__Environment = atoi (getenv ("CGILIB__ENVIRONMENT")); CgiLib__CgiPrefixWWW = 1; return; } #endif CgiLib__Environment = 0; if (getenv ("WWWEXEC_RUNDOWN_STRING") != NULL) { /* OSU environment */ CgiLib__Environment = CGILIB_ENVIRONMENT_OSU; CgiLib__CgiPrefixWWW = 1; return; } /* determine if CGI variables begin with or without "WWW_" */ cptr = getenv ("WWW_GATEWAY_INTERFACE"); if (cptr == NULL) CgiLib__CgiPrefixWWW = 0; else CgiLib__CgiPrefixWWW = 1; if (getenv ("HTTP$INPUT") != NULL) { /* WASD request body I/O stream logical detected, CGIplus? */ if ((CgiLib__CgiPlusEofPtr = getenv("CGIPLUSEOF")) != NULL) { CgiLib__Environment = CGILIB_ENVIRONMENT_CGIPLUS; return; } CgiLib__Environment = CGILIB_ENVIRONMENT_WASD; return; } /* the first is CSWS 1.2/1.3, the other two for SWS 2.0 */ if (getenv ("APACHE$SHARED_SOCKET") != NULL || getenv ("APACHE$PARENT_PID") != NULL || getenv ("APACHE$SERVER_TAG") != NULL) { CgiLib__Environment = CGILIB_ENVIRONMENT_APACHE; /* note whether it's CSWS version 1.2/1.3 or SWS 2.0 */ if (getenv ("APACHE$SHARED_SOCKET") != NULL) CgiLib__ApacheVersion = 1; else CgiLib__ApacheVersion = 2; return; } if ((cptr = getenv ("WWW_SERVER_SOFTWARE")) != NULL) { if (strstr (cptr, "Purveyor") != NULL) { CgiLib__Environment = CGILIB_ENVIRONMENT_PURVEYOR; return; } } /* assume vanilla CGI */ CgiLib__Environment = CGILIB_ENVIRONMENT_CGI; } /*****************************************************************************/ /*************************/ /* OUTPUT STREAM CONTROL */ /*************************/ /*****************************************************************************/ /* Set the stream to binary mode (no translation of '\n', etc.) */ void CgiLibEnvironmentBinaryOut () { /*********/ /* begin */ /*********/ if (CgiLib__Debug) fprintf (stdout, "CgiLibEnvironmentBinaryOut()\n"); CgiLib__EnvironmentStdoutType = CGILIB_ENVIRONMENT_STDOUT_BINARY; if (CgiLib__Environment == CGILIB_ENVIRONMENT_OSU) return; if (CgiLib__Debug) return; if (CgiLib__Environment == CGILIB_ENVIRONMENT_APACHE) { if (CgiLib__ApacheVersion == 1) CgiLib__ApacheFixBg(1); else CgiLib__ApacheSetSockOpt(0); } if ((stdout = freopen ("SYS$OUTPUT:", "w", stdout, "ctx=bin")) == NULL) exit (vaxc$errno); } /*****************************************************************************/ /* Set the stream is back to record mode. */ /* for backward compatibility */ #pragma inline(CgiLibEnvironmentStandardOut) void CgiLibEnvironmentStandardOut () { CgiLibEnvironmentRecordOut(); } void CgiLibEnvironmentRecordOut () { /*********/ /* begin */ /*********/ if (CgiLib__Debug) fprintf (stdout, "CgiLibEnvironmentRecordOut()\n"); CgiLib__EnvironmentStdoutType = CGILIB_ENVIRONMENT_STDOUT_RECORD; if (CgiLib__Environment == CGILIB_ENVIRONMENT_OSU) return; if (CgiLib__Environment == CGILIB_ENVIRONMENT_APACHE) { if (CgiLib__ApacheVersion == 1) CgiLib__ApacheFixBg(0); else CgiLib__ApacheSetSockOpt(1); } if ((stdout = freopen ("SYS$OUTPUT:", "w", stdout, "ctx=rec")) == NULL) exit (vaxc$errno); } /*****************************************************************************/ /* For SWS version 2.0! Based on information provided in private communication with Richard Barry (thanks Rick) this function calls the apache$$setsockopt() function to adjust the BG device's characteristics suitable for binary and record output. To select binary mode call with a parameter of 0, record mode with 1, toggle the modes with -1, and set the BG device buffer size with any value greater than 255 and less than or equal to 65535. This is based on a function description provided by Rick, but makes the sharable image activation and symbol resolution dynamic to allow the object module to be linked on systems without VMS Apache and/or the APACHE$APR_SHRP shareable image available. */ void CgiLib__ApacheSetSockOpt (int OptionValue) { #define OPT_CCLBIT 1 #define OPT_BUFSIZ 2 #define OPT_SHAREABLE 3 #define LIB$M_FIS_MIXEDCASE 0x10 static int IsBin; static unsigned short SysOutputChannel; static int (*ApacheSetSockOpt)(unsigned short, int, int*, int); static $DESCRIPTOR (StdOutputDsc, "SYS$OUTPUT"); static $DESCRIPTOR (ApacheAprShrpImageDsc, "APACHE$APR_SHRP"); static $DESCRIPTOR (HpeApacheSetSockOptDsc, "APACHE$$SETSOCKOPT"); static $DESCRIPTOR (VsiApacheSetSockOptDsc, "apr$$setsockopt"); int status; char *aptr; /*********/ /* begin */ /*********/ if (CgiLib__Debug) fprintf (stdout, "CgiLib__ApacheSetSockOpt() %d\n", OptionValue); if (SysOutputChannel) status = SS$_NORMAL; else { #ifndef __VAX /* first look for the VSI Apache symbol */ aptr = VsiApacheSetSockOptDsc.dsc$a_pointer; lib$establish (CgiLib__ApacheSetSockOptHandler); status = lib$find_image_symbol (&ApacheAprShrpImageDsc, &VsiApacheSetSockOptDsc, &ApacheSetSockOpt, 0, LIB$M_FIS_MIXEDCASE); if (CgiLib__Debug) fprintf (stdout, "lib$find_image_symbol() %%X%08.08X\n", status); lib$revert (); if (!(status & 1)) #endif /* __VAX */ { /* second look for the HP(E) Apache symbol */ aptr = HpeApacheSetSockOptDsc.dsc$a_pointer; lib$establish (CgiLib__ApacheSetSockOptHandler); status = lib$find_image_symbol (&ApacheAprShrpImageDsc, &HpeApacheSetSockOptDsc, &ApacheSetSockOpt, 0, 0); if (CgiLib__Debug) fprintf (stdout, "lib$find_image_symbol() %%X%08.08X\n", status); lib$revert (); } if (status & 1) { status = sys$assign (&StdOutputDsc, &SysOutputChannel, 0, 0); atexit (&CgiLib__ApacheExitRecordMode); } } if (status & 1) { if (OptionValue > 255) status = ApacheSetSockOpt (SysOutputChannel, OPT_BUFSIZ, &OptionValue, sizeof(OptionValue)); else status = ApacheSetSockOpt (SysOutputChannel, OPT_CCLBIT, &OptionValue, sizeof(OptionValue)); } if (status & 1) return; /* let's make it a bit more obvious than when delving through error logs */ fprintf (stdout, "Content-Type: text/plain\r\n\ \r\n\ *** ERROR ACTIVATING %s in APACHE$APR_SHRP ***\n\ \n", aptr); exit (status); #if 0 static int IsBin; static unsigned long flags = LIB$M_FIS_MIXEDCASE; static unsigned short SysOutputChannel; static int (*ApacheSetSockOpt)(unsigned short, int, int*, int); static $DESCRIPTOR (StdOutputDsc, "SYS$OUTPUT"); static $DESCRIPTOR (ApacheAprShrpImageDsc, "APACHE$APR_SHRP"); static $DESCRIPTOR (HpeApacheSetSockOptDsc, "APACHE$SETSOCKOPT"); static $DESCRIPTOR (VsiApacheSetSockOptDsc, "apr$$setsockopt"); int status; /*********/ /* begin */ /*********/ if (CgiLib__Debug) fprintf (stdout, "CgiLib__ApacheSetSockOpt() %d\n", OptionValue); /** CgiLib__Debug = 1; printf("content-type: text/plain\n"); fflush (stdout); puts ("\n"); fflush (stdout); **/ if (SysOutputChannel) status = SS$_NORMAL; else { lib$establish (CgiLib__ApacheSetSockOptHandler); status = lib$find_image_symbol (&ApacheAprShrpImageDsc, &ApacheSetSockOptDsc, &ApacheSetSockOpt, 0, flags); if (CgiLib__Debug) fprintf (stdout, "lib$find_image_symbol() %%X%08.08X\n", status); lib$revert (); if (status & 1) { status = sys$assign (&StdOutputDsc, &SysOutputChannel, 0, 0); atexit (&CgiLib__ApacheExitRecordMode); } #endif } /*****************************************************************************/ /* Just continue, to report an error if the image couldn't be activated or the required symbol not found. */ int CgiLib__ApacheSetSockOptHandler () { /*********/ /* begin */ /*********/ if (CgiLib__Debug) fprintf (stdout, "CgiLib__ApacheSetSockOptHandler()\n"); return (SS$_CONTINUE); } /*****************************************************************************/ /* For CSWS version 1.2 and 1.3! According to "Compaq Secure Web Server Version 1.0-1 for OpenVMS Alpha (based on Apache 1.3.12) Version 1.0-1 Release Notes" this is required to support the transfer of any binary content in excess of 32 kbytes. This is based on a code snippet in these release notes, but makes the sharable image activation and symbol resolution dynamic to allow the object module to be linked on systems without VMS Apache and/or the APACHE$FIXBG.EXE shareable image available. 'WantBin' if one sets to binary, if 0 resets to record mode. This toggling has it's limitations but that's the way APACHE$FIXBG.C works! This function assumes the environment starts out in record mode. */ void CgiLib__ApacheFixBg (int WantBin) { static int IsBin; static unsigned short SysOutputChannel; static int (*ApacheFixBg)(unsigned short, int); static $DESCRIPTOR (StdOutputDsc, "SYS$OUTPUT"); static $DESCRIPTOR (ApacheFixBgImageDsc, "APACHE$FIXBG"); static $DESCRIPTOR (ApacheFixBgDsc, "APACHE$FIXBG"); int status; /*********/ /* begin */ /*********/ if (CgiLib__Debug) fprintf (stdout, "CgiLib__ApacheFixBg() %d %d\n", WantBin, IsBin); /* if want binary and already binary just return */ if (WantBin && IsBin) return; /* if want record and already record just return */ if (!WantBin && !IsBin) return; IsBin = !IsBin; if (SysOutputChannel) status = SS$_NORMAL; else { lib$establish (CgiLib__ApacheFixBgHandler); status = lib$find_image_symbol (&ApacheFixBgImageDsc, &ApacheFixBgDsc, &ApacheFixBg, 0); if (CgiLib__Debug) fprintf (stdout, "lib$find_image_symbol() %%X%08.08X\n", status); lib$revert (); if (status & 1) { status = sys$assign (&StdOutputDsc, &SysOutputChannel, 0, 0); atexit (&CgiLib__ApacheExitRecordMode); } } if (status & 1) status = ApacheFixBg (SysOutputChannel, 1); if (status & 1) return; /* let's make it a bit more obvious than when delving through error logs */ fprintf (stdout, "Content-Type: text/plain\r\n\ \r\n\ *** ERROR ACTIVATING apache$fixbg() in APACHE$FIXBG ***\n\ \n"); exit (status); } /*****************************************************************************/ /* Just continue, to report an error if the image couldn't be activated or the required symbol not found. */ int CgiLib__ApacheFixBgHandler () { /*********/ /* begin */ /*********/ if (CgiLib__Debug) fprintf (stdout, "CgiLib__ApacheFixBgHandler()\n"); return (SS$_CONTINUE); } /*****************************************************************************/ /* Set the Apache stream is back to record mode. */ void CgiLib__ApacheExitRecordMode () { /*********/ /* begin */ /*********/ if (CgiLib__Debug) fprintf (stdout, "CgiLib__ApacheExitRecordMode()\n"); fflush (stdout); if (CgiLib__ApacheVersion == 1) CgiLib__ApacheFixBg(0); else CgiLib__ApacheSetSockOpt(1); } /*****************************************************************************/ /**********************/ /* SERVER ENVIRONMENT */ /**********************/ /*****************************************************************************/ /* Return true or false depending on whether the statement is true or false! */ int CgiLibEnvironmentIsApache () { return (CgiLib__Environment == CGILIB_ENVIRONMENT_APACHE); } int CgiLibEnvironmentIsCgi () { return (CgiLib__Environment == CGILIB_ENVIRONMENT_CGI); } int CgiLibEnvironmentIsCgiPlus () { return (CgiLib__Environment == CGILIB_ENVIRONMENT_CGIPLUS); } int CgiLibEnvironmentIsOsu () { return (CgiLib__Environment == CGILIB_ENVIRONMENT_OSU); } int CgiLibEnvironmentIsPurveyor () { return (CgiLib__Environment == CGILIB_ENVIRONMENT_PURVEYOR); } int CgiLibEnvironmentIsWasd () { return (CgiLib__Environment == CGILIB_ENVIRONMENT_WASD); } /*****************************************************************************/ /* Return a pointer to a string with the name of the scripting environment. For the WASD server append whether it's CGI or CGIplus. */ char* CgiLibEnvironmentName () { static char Environment [64]; char *cptr, *sptr, *zptr; /*********/ /* begin */ /*********/ if (CgiLib__Debug) fprintf (stdout, "CgiLibEnvironmentName()\n"); if (CgiLib__Environment == CGILIB_ENVIRONMENT_CGIPLUS && CgiLib__CgiPlusLoadVariables) { /* with CGIplus we can't use a variable before the stream is read from */ return ("WASD (CGIplus)"); } cptr = CgiLib__GetVar ("WWW_SERVER_SOFTWARE", ""); if (!*cptr) return ("(unknown)"); zptr = (sptr = Environment) + sizeof(Environment)-1; while (*cptr && sptr < zptr) *sptr++ = *cptr++; if (CgiLib__Environment == CGILIB_ENVIRONMENT_WASD) cptr = " (CGI)"; if (CgiLib__Environment == CGILIB_ENVIRONMENT_CGIPLUS) cptr = " (CGIplus)"; while (*cptr && sptr < zptr) *sptr++ = *cptr++; *sptr = '\0'; return (Environment); } /*****************************************************************************/ /* Return a pointer to a string with the version of CGILIB. */ char* CgiLibEnvironmentVersion () { /*********/ /* begin */ /*********/ if (CgiLib__Debug) fprintf (stdout, "CgiLibEnvironmentVersion()\n"); return (CgiLib__SoftwareID); } /*****************************************************************************/ /* If WASD, from the SERVER_SOFTWARE containing something like "HTTPd-WASD/8.2.1 OpenVMS/AXP SSL" return a number like 080201 (80,201). */ int CgiLibEnvironmentWasdVersion () { char *cptr; /*********/ /* begin */ /*********/ if (CgiLib__Debug) fprintf (stdout, "CgiLibEnvironmentWasdVersion()\n"); if (CgiLib__Environment == CGILIB_ENVIRONMENT_CGIPLUS || CgiLib__Environment == CGILIB_ENVIRONMENT_WASD) { if (!CgiLib__WasdVersion) { cptr = CgiLib__GetVar ("WWW_SERVER_SOFTWARE", ""); if (cptr) { while (*cptr && !isdigit(*cptr)) cptr++; CgiLib__WasdVersion = atoi(cptr) * 10000; while (*cptr && isdigit(*cptr)) cptr++; if (*cptr) cptr++; CgiLib__WasdVersion += atoi(cptr) * 100; while (*cptr && isdigit(*cptr)) cptr++; if (*cptr) cptr++; CgiLib__WasdVersion += atoi(cptr); } } } if (CgiLib__Debug) fprintf (stdout, "%d\n", CgiLib__WasdVersion); return (CgiLib__WasdVersion); } /*****************************************************************************/ /* Set or reset the CGILIB debug. Return previous value. */ int CgiLibEnvironmentSetDebug (int SetDebug) { int PrevDebug; /*********/ /* begin */ /*********/ if (CgiLib__Debug) fprintf (stdout, "CgiLibEnvironmentSetDebug()\n"); PrevDebug = CgiLib__Debug; CgiLib__Debug = SetDebug; return (PrevDebug); } /*****************************************************************************/ /* Set the string value used when a CGI variable return NONE. This is usually "" (empty string) or NULL (zero). Return previous value. */ char* CgiLibEnvironmentSetVarNone (char* VarNonePtr) { char *PrevVarNonePtr; /*********/ /* begin */ /*********/ if (CgiLib__Debug) fprintf (stdout, "CgiLibEnvironmentSetVarNone()\n"); PrevVarNonePtr = CgiLib__VarNonePtr; CgiLib__VarNonePtr = VarNonePtr; return (PrevVarNonePtr); } /*****************************************************************************/ /* Returns an integer 11 if the current request HTTP protocol version is 1.1, 10 if it's 1.0, or 0 if it's unknown or otherwise not determined, as provided by the SERVER_PROTOCOL CGI variable. */ int CgiLibHttpProtocolVersion () { static int UsageCount; char *cptr; /*********/ /* begin */ /*********/ if (CgiLib__Debug) fprintf (stdout, "CgiLibHttpProtocolVersion()\n"); if (!UsageCount || UsageCount != CgiLib__CgiPlusUsageCount) { UsageCount = CgiLib__CgiPlusUsageCount; /* a misnomer, it's really request protocol, but never mind */ cptr = CgiLib__GetVar ("WWW_SERVER_PROTOCOL", ""); if (!strcmp (cptr, "HTTP/1.1")) { CgiLib__HttpProtocolVersion = 11; CgiLib__HttpProtocolVersionPtr = "HTTP/1.1"; } else if (!strcmp (cptr, "HTTP/1.0")) { CgiLib__HttpProtocolVersion = 10; CgiLib__HttpProtocolVersionPtr = "HTTP/1.0"; } else { CgiLib__HttpProtocolVersion = 0; CgiLib__HttpProtocolVersionPtr = "HTTP/?.?"; } } if (CgiLib__Debug) fprintf (stdout, "|%s| %d\n", CgiLib__HttpProtocolVersionPtr, CgiLib__HttpProtocolVersion); return (CgiLib__HttpProtocolVersion); } /*****************************************************************************/ /* Set and return the MSIE "X-UA-Compatible" compatibility HTML meta field. */ char* CgiLibXUACompatible (char* ContentPtr) { static char MetaHttpEquiv [] = "\n"; static char MetaXUA [sizeof(MetaHttpEquiv)+64]; /*********/ /* begin */ /*********/ if (CgiLib__Debug) fprintf (stdout, "CgiLibXUACompatible()\n"); if (ContentPtr == NULL) return (CgiLib__XUACompatiblePtr ? CgiLib__XUACompatiblePtr : ""); if (ContentPtr == (char*)(-1)) return (CgiLib__XUACompatiblePtr = NULL); if (!*ContentPtr) ContentPtr = "IE=edge"; if (strlen(ContentPtr) > 64) return (CgiLib__XUACompatiblePtr = NULL); sprintf (CgiLib__XUACompatiblePtr = MetaXUA, MetaHttpEquiv, ContentPtr); return (CgiLib__XUACompatiblePtr); } /****************************************************************************/ /*****************/ /* HTTP RESPONSE */ /*****************/ /****************************************************************************/ /* Generate a full HTTP (WASD and OSU) or CGI response header appropriate to the environment. 302 (redirection) and 401 (authentication) responses are handled. Supply NULL for content parameters and the location URL or authentication header line as parameter three. The third can be a 'printf()' format string with associated parameter supplied following. This can also be used to supply additional newline terminated header lines into the response. If a specific character set is required begin this format string with "; charset=" and then charset string, followed by a newline character. */ int CgiLibResponseHeader ( int StatusCode, char *ContentType, ... ) { static FILE *ResponseStdout = NULL; int argcnt, charcnt, ContentTypeText; char *CharsetPtr, *ContentTypeNewLine, *FormatStringPtr, *RequestTimeGmtPtr, *ServerSoftwarePtr; va_list argptr; /*********/ /* begin */ /*********/ va_count (argcnt); if (CgiLib__Debug) fprintf (stdout, "CgiLibResponseHeader() %d %d |%s|\n", argcnt, StatusCode, ContentType); if (!CgiLib__Environment) CgiLib__SetEnvironment (); if (ResponseStdout == NULL) { if (CgiLib__Environment == CGILIB_ENVIRONMENT_CGI || CgiLib__Environment == CGILIB_ENVIRONMENT_PURVEYOR) { /* open a permanent, record-oriented stream */ if ((ResponseStdout = fopen ("SYS$OUTPUT:", "w", "ctx=rec")) == NULL) exit (vaxc$errno); } else ResponseStdout = stdout; } if (argcnt > 2) { /* more response header line(s)/information has been included */ va_start (argptr, ContentType); FormatStringPtr = (char*)va_arg (argptr, char*); if (CgiLib__Debug) fprintf (stdout, "|%s|\n", FormatStringPtr); } else FormatStringPtr = NULL; ContentTypeNewLine = "\n"; if (ContentType == NULL) ContentType = ""; if (!ContentType[0]) ContentType = "text/plain"; if ((ContentTypeText = !strncmp (ContentType, "text/", 5)) || (ContentTypeText = !strncmp (ContentType, "TEXT/", 5))) { if (FormatStringPtr != NULL && (!strncmp (FormatStringPtr, ";charset=", 9) || !strncmp (FormatStringPtr, "; charset=", 10))) { /* caller has specifically included a character set specification */ CharsetPtr = ContentTypeNewLine = ""; } else { /* check script, request and server, before using default */ if ((CharsetPtr = CgiLib__ResponseCharsetPtr) == NULL) { /* there is no script-enforced character set */ CharsetPtr = CgiLib__GetVar ("WWW_REQUEST_CHARSET", ""); if (!CharsetPtr[0]) CharsetPtr = CgiLib__GetVar ("WWW_SERVER_CHARSET", ""); if (!CharsetPtr[0]) CharsetPtr = CgiLib__ResponseDefCharsetPtr; } } } else CharsetPtr = ""; CgiLib__ResponseStatusCode = StatusCode; if (!CgiLib__ResponseCgi && CgiLib__Environment == CGILIB_ENVIRONMENT_OSU) { /* full response header (only for OSU these days) */ ServerSoftwarePtr = CgiLib__GetVar ("WWW_SERVER_SOFTWARE", ""); if (!ServerSoftwarePtr[0]) ServerSoftwarePtr = CgiLib__ResponseSoftwareIdPtr; RequestTimeGmtPtr = CgiLibVarNull ("REQUEST_TIME_GMT"); CgiLibHttpProtocolVersion (); if (StatusCode == 302) { charcnt = fprintf (ResponseStdout, "%s 302 %s\n\ Server: %s\n\ %s%s%s\ Location: ", CgiLib__HttpProtocolVersionPtr, CgiLibHttpStatusCodeText(StatusCode), ServerSoftwarePtr, RequestTimeGmtPtr == NULL ? "" : "Date: ", RequestTimeGmtPtr == NULL ? "" : RequestTimeGmtPtr, RequestTimeGmtPtr == NULL ? "" : "\n"); } else if (StatusCode == 401) { charcnt = fprintf (ResponseStdout, "%s 401 %s\n\ Server: %s\n\ %s%s%s", CgiLib__HttpProtocolVersionPtr, CgiLibHttpStatusCodeText(StatusCode), ServerSoftwarePtr, RequestTimeGmtPtr == NULL ? "" : "Date: ", RequestTimeGmtPtr == NULL ? "" : RequestTimeGmtPtr, RequestTimeGmtPtr == NULL ? "" : "\n"); if (argcnt > 2) fprintf (stdout, "WWW-Authenticate: "); } else if (ContentTypeText) { charcnt = fprintf (ResponseStdout, "%s %d %s\n\ Server: %s\n\ %s%s%s\ Content-Type: %s%s%s%s", CgiLib__HttpProtocolVersionPtr, StatusCode, CgiLibHttpStatusCodeText(StatusCode), ServerSoftwarePtr, RequestTimeGmtPtr == NULL ? "" : "Date: ", RequestTimeGmtPtr == NULL ? "" : RequestTimeGmtPtr, RequestTimeGmtPtr == NULL ? "" : "\n", ContentType, CharsetPtr[0] ? "; charset=" : "", CharsetPtr, ContentTypeNewLine); } else { charcnt = fprintf (ResponseStdout, "%s %d %s\n\ Server: %s\n\ %s%s%s\ Content-Type: %s\n", CgiLib__HttpProtocolVersionPtr, StatusCode, CgiLibHttpStatusCodeText(StatusCode), ServerSoftwarePtr, RequestTimeGmtPtr == NULL ? "" : "Date: ", RequestTimeGmtPtr == NULL ? "" : RequestTimeGmtPtr, RequestTimeGmtPtr == NULL ? "" : "\n", ContentType); } } else { /* vanilla CGI environment/response */ if (StatusCode == 302) { if (CgiLib__Environment == CGILIB_ENVIRONMENT_APACHE) charcnt = fprintf (ResponseStdout, "Location: "); else charcnt = fprintf (ResponseStdout, "Status: 302 %s\n\ Location: ", CgiLibHttpStatusCodeText(StatusCode)); } else if (StatusCode == 401) { charcnt = fprintf (ResponseStdout, "Status: 401 %s\n", CgiLibHttpStatusCodeText(StatusCode)); if (argcnt > 2) fprintf (stdout, "WWW-Authenticate: "); } else if (ContentTypeText) { ServerSoftwarePtr = CgiLib__GetVar ("WWW_SERVER_SOFTWARE", ""); if (strstr (ServerSoftwarePtr, "WASD/5.") != NULL || strstr (ServerSoftwarePtr, "WASD/6.0") != NULL) { /* bit of backward compatibility for WASD 5.n and 6.0 */ charcnt = fprintf (ResponseStdout, "Content-Type: %s%s%s%s", ContentType, CharsetPtr[0] ? "; charset=" : "", CharsetPtr, ContentTypeNewLine); } else { /* all others, including WASD 6.1ff */ if (CgiLib__Environment == CGILIB_ENVIRONMENT_APACHE && StatusCode / 100 == 2) charcnt = fprintf (ResponseStdout, "Content-Type: %s%s%s%s", ContentType, CharsetPtr[0] ? "; charset=" : "", CharsetPtr, ContentTypeNewLine); else charcnt = fprintf (ResponseStdout, "Status: %d %s\n\ Content-Type: %s%s%s%s", StatusCode, CgiLibHttpStatusCodeText(StatusCode), ContentType, CharsetPtr[0] ? "; charset=" : "", CharsetPtr, ContentTypeNewLine); } } else { if (CgiLib__Environment == CGILIB_ENVIRONMENT_APACHE && StatusCode / 100 == 2) charcnt = fprintf (ResponseStdout, "Content-Type: %s\n", ContentType); else charcnt = fprintf (ResponseStdout, "Status: %d %s\n\ Content-Type: %s\n", StatusCode, CgiLibHttpStatusCodeText(StatusCode), ContentType); } } if (argcnt > 2) { /* more response header line(s)/information has been included */ charcnt += vfprintf (ResponseStdout, FormatStringPtr, argptr); va_end (argptr); } if (StatusCode == 302 || (StatusCode == 401 && argcnt > 2)) { fputs ("\n", ResponseStdout); charcnt++; } if (CgiLib__Environment == CGILIB_ENVIRONMENT_WASD || CgiLib__Environment == CGILIB_ENVIRONMENT_CGIPLUS) { if (CgiLib__ResponseStreamMode > 0) charcnt = fprintf (ResponseStdout, "Script-Control: X-stream-mode=1\n"); else if (CgiLib__ResponseStreamMode < 0) charcnt = fprintf (ResponseStdout, "Script-Control: X-stream-mode=0\n"); else if (CgiLib__ResponseRecordMode > 0) charcnt = fprintf (ResponseStdout, "Script-Control: X-record-mode=1\n"); else if (CgiLib__ResponseRecordMode < 0) charcnt = fprintf (ResponseStdout, "Script-Control: X-record-mode=0\n"); else if (CgiLib__EnvironmentStdoutType == CGILIB_ENVIRONMENT_STDOUT_BINARY) charcnt = fprintf (ResponseStdout, "Script-Control: X-stream-mode=1\n"); else if (CgiLib__EnvironmentStdoutType == CGILIB_ENVIRONMENT_STDOUT_RECORD) charcnt = fprintf (ResponseStdout, "Script-Control: X-record-mode=1\n"); if (CgiLib__ResponseBufferRecords > 0) charcnt = fprintf (ResponseStdout, "Script-Control: X-buffer-records=1\n"); else if (CgiLib__ResponseBufferRecords < 0) charcnt = fprintf (ResponseStdout, "Script-Control: X-buffer-records=0\n"); } fputs ("\n", ResponseStdout); charcnt++; fflush (ResponseStdout); CgiLib__ResponseCount += charcnt; return (charcnt); } /****************************************************************************/ /* Generate an HTTP 302 redirect response. If the format string begins "%!" then the redirect is made absolute with the appropriate local server host name and if necessary port prefixing whatever are supplied as parameters. If the "%!" does not precede the format string then it can either be a full absolute or local redirection URL, it's just appended to the "Location: " field name. The format string can be for a 'printf()' with associated parameters supplied. Strings fed to this function must be appropriately URL-encoded if necessary. */ int CgiLibResponseRedirect ( char *FormatString, ... ) { int argcnt, charcnt; char *FormatStringPtr, *HttpHostPtr, *HostPortPtr, *RequestTimeGmtPtr, *SchemePtr, *ServerNamePtr, *ServerPortPtr, *ServerSoftwarePtr; char HostPort [256]; va_list argptr; /*********/ /* begin */ /*********/ va_count (argcnt); if (CgiLib__Debug) fprintf (stdout, "CgiLibResponseRedirect() %d |%s|\n", argcnt, FormatString); FormatStringPtr = FormatString; if (*(USHORTPTR)FormatStringPtr == '%!' || CgiLib__Environment == CGILIB_ENVIRONMENT_PURVEYOR) { if (*(USHORTPTR)FormatStringPtr == '%!') FormatStringPtr += 2; if (CgiLib__Environment == CGILIB_ENVIRONMENT_PURVEYOR) { /* Purveyor requires a full URL (scheme://server:port/path) */ SchemePtr = CgiLibVarNull ("SECURITY_STATUS"); if (!SchemePtr) SchemePtr = "http://"; else if (strstr (SchemePtr, "SSL")) SchemePtr = "https://"; else SchemePtr = "http://"; } else SchemePtr = "//"; HttpHostPtr = CgiLib__GetVar ("WWW_HTTP_HOST", ""); if (HttpHostPtr[0]) { /* use the request supplied host[:port] */ HostPortPtr = HttpHostPtr; } else { /* build it up from the server name and port */ ServerNamePtr = CgiLib__GetVar ("WWW_SERVER_NAME", ""); ServerPortPtr = CgiLib__GetVar ("WWW_SERVER_PORT", ""); if (!strcmp (ServerPortPtr, "80") && !strcmp (ServerPortPtr, "443")) HostPortPtr = ServerNamePtr; else sprintf (HostPortPtr = HostPort, "%s:%s", ServerNamePtr, ServerPortPtr); } } else HostPortPtr = SchemePtr = ""; CgiLib__ResponseStatusCode = 302; if (!CgiLib__ResponseCgi && CgiLib__Environment == CGILIB_ENVIRONMENT_OSU) { /* full response header (only for OSU these days) */ ServerSoftwarePtr = CgiLib__GetVar ("WWW_SERVER_SOFTWARE", ""); if (!ServerSoftwarePtr[0]) ServerSoftwarePtr = CgiLib__ResponseSoftwareIdPtr; RequestTimeGmtPtr = CgiLibVarNull ("REQUEST_TIME_GMT"); CgiLibHttpProtocolVersion (); charcnt = fprintf (stdout, "%s 302 %s\n\ Server: %s\n\ %s%s%s\ Content-Type: text/html\n\ Location: %s%s", CgiLib__HttpProtocolVersionPtr, CgiLibHttpStatusCodeText(302), ServerSoftwarePtr, RequestTimeGmtPtr == NULL ? "" : "Date: ", RequestTimeGmtPtr == NULL ? "" : RequestTimeGmtPtr, RequestTimeGmtPtr == NULL ? "" : "\n", SchemePtr, HostPortPtr); } else { /* vanilla CGI environment/response */ charcnt = fprintf (stdout, "Status: 302 %s\n\ %s\ Location: %s%s", CgiLibHttpStatusCodeText(302), CgiLib__Environment == CGILIB_ENVIRONMENT_OSU ? "Content-Type: text/html\n" : "", SchemePtr, HostPortPtr); } va_start (argptr, FormatString); vfprintf (stdout, FormatStringPtr, argptr); va_end (argptr); fputs ("\n\n", stdout); charcnt += 2; return (charcnt); } /*****************************************************************************/ /* Generate an error report in the quasi-standard WASD format. If 'StatusValue' is non-zero then it is assumed to be a VMS status code and a message generated from it. If zero then only the format string is used. The format string may be a plain-text message or contain 'printf()' formatting characters with any associated parameters. */ int CgiLibResponseError ( char *SrcFileName, int SrcLineNumber, int StatusValue, char *FormatString, ... ) { int argcnt, charcnt, status, FullResponse; short int Length; char *cptr, *sptr, *zptr; char FileName [128]; va_list argptr; /*********/ /* begin */ /*********/ va_count (argcnt); if (CgiLib__Debug) fprintf (stdout, "CgiLibResponseError() %d %d\n", argcnt, StatusValue); FullResponse = !CgiLib__ResponseCount; charcnt = 0; /* The source file format provided by the "__FILE__" macro will be "device:[directory]name.type;ver". Reduce that to "name.type". */ zptr = (sptr = FileName) + sizeof(FileName)-1; for (cptr = SrcFileName; *cptr; cptr++); while (cptr > SrcFileName && *cptr != ']') cptr--; if (*cptr == ']') { cptr++; while (*cptr && *cptr != ';' && sptr < zptr) *sptr++ = *cptr++; *sptr = '\0'; } else { /* assume it's just a module name */ strncpy (FileName, cptr, sizeof(FileName)); FileName[sizeof(FileName)-1] = '\0'; } if (FullResponse) { CgiLib__ResponseStatusCode = CgiLib__ResponseErrorStatus; CgiLibResponseHeader (CgiLib__ResponseErrorStatus, "text/html"); charcnt += fprintf (stdout, "\n\ \n\ %s\ \n\ \n\ \n\ \n\ %s %d %s\n\ \n\ \n", CgiLib__XUACompatiblePtr ? CgiLib__XUACompatiblePtr : "", CgiLib__ResponseSoftwareIdPtr, FileName, SrcLineNumber, CgiLibEnvironmentName(), CgiLib__ResponseErrorTitlePtr, CgiLib__ResponseErrorStatus, CgiLibHttpStatusCodeText(CgiLib__ResponseErrorStatus), CgiLib__ResponseBodyPtr ? " " : "", CgiLib__ResponseBodyPtr); } else /* this is a kludge originally for soyMAIL */ if (StatusValue != -1) { charcnt += fprintf (stdout, "\n


\n\ \n", CgiLib__ResponseSoftwareIdPtr, FileName, SrcLineNumber); } charcnt += fprintf (stdout, "%s %d  -  %s\n", CgiLib__ResponseErrorTitlePtr, CgiLib__ResponseErrorStatus, CgiLib__ResponseErrorMessagePtr); if (StatusValue && StatusValue != -1 /* see kludge above */) { # pragma message __save # pragma message disable (ADDRCONSTEXT) char StatusMsg [256]; $DESCRIPTOR (StatusMsgDsc, StatusMsg); /* cater for VAX environment as best we can */ # pragma message disable (IMPLICITFUNC) # ifdef CGILIB_OBJECT_MODULE # ifdef __VAX # define SYS$GETMSG sys$getmsg # endif # endif status = sys$getmsg (StatusValue, &Length, &StatusMsgDsc, 1, 0); # pragma message __restore if (status & 1) { StatusMsg[Length] = '\0'; StatusMsg[0] = toupper(StatusMsg[0]); charcnt += fprintf (stdout, "

%s ... ", StatusValue, StatusMsg); } else exit (status); } else { fputs ("

", stdout); charcnt += 4; } va_start (argptr, FormatString); charcnt += vfprintf (stdout, FormatString, argptr); va_end (argptr); fputs ("\n", stdout); charcnt++; if (CgiLib__ResponseErrorInfoPtr != NULL && CgiLib__ResponseErrorInfoPtr[0]) charcnt += fprintf (stdout, "

%s", CgiLib__ResponseErrorInfoPtr); cptr = CgiLib__GetVar ("WWW_SERVER_SIGNATURE", ""); if (cptr[0]) charcnt += fprintf (stdout, "


\n%s", cptr); if (FullResponse) { fputs ( "\n\ \n", stdout); charcnt += 16; } CgiLib__ResponseCount += charcnt; return (charcnt); } /*****************************************************************************/ /* Generate a "success" report in the defacto standard WASD format. The format string may be a plain-text message or contain 'printf()' formatting characters with any associated parameters. */ int CgiLibResponseSuccess ( char *FormatString, ... ) { int argcnt, charcnt; short int Length; char *cptr; va_list argptr; /*********/ /* begin */ /*********/ va_count (argcnt); if (CgiLib__Debug) fprintf (stdout, "CgiLibResponseSuccess() %d\n", argcnt); charcnt = 0; CgiLib__ResponseStatusCode = CgiLib__ResponseSuccessStatus; CgiLibResponseHeader (CgiLib__ResponseSuccessStatus, "text/html"); charcnt += fprintf (stdout, "\n\ \n\ %s\ \n\ \n\ %s %d %s\n\ \n\ \n\ %s %d  -  %s\n\

", CgiLib__XUACompatiblePtr ? CgiLib__XUACompatiblePtr : "", CgiLib__ResponseSoftwareIdPtr, CgiLibEnvironmentName(), CgiLib__ResponseSuccessTitlePtr, CgiLib__ResponseSuccessStatus, CgiLibHttpStatusCodeText(CgiLib__ResponseSuccessStatus), CgiLib__ResponseBodyPtr ? " " : "", CgiLib__ResponseBodyPtr, CgiLib__ResponseSuccessTitlePtr, CgiLib__ResponseSuccessStatus, CgiLib__ResponseSuccessMsgPtr); va_start (argptr, FormatString); charcnt += vfprintf (stdout, FormatString, argptr); va_end (argptr); cptr = CgiLib__GetVar ("WWW_SERVER_SIGNATURE", ""); if (cptr[0]) charcnt += fprintf (stdout, "


\n%s", cptr); fputs ( "\n\ \n", stdout); charcnt += 17; CgiLib__ResponseCount += charcnt; return (charcnt); } /*****************************************************************************/ /* Return the HTTP status code (e.g. 200, 302) set by response generation. */ int CgiLibResponseGetStatus () { /*********/ /* begin */ /*********/ if (CgiLib__Debug) fprintf (stdout, "CgiLibResponseGetStatus()\n"); return (CgiLib__ResponseStatusCode); } /*****************************************************************************/ /* Tell the WASD server via the CGI response that it should interpret the response body as being discrete records requiring carriage-control. Return the previous value. */ int CgiLibResponseSetRecordMode (int enable) { int prevalue; /*********/ /* begin */ /*********/ if (CgiLib__Debug) fprintf (stdout, "CgiLibResponseSetRecordMode() %d\n", enable); prevalue = CgiLib__ResponseRecordMode; if (enable) CgiLib__ResponseRecordMode = 1; else CgiLib__ResponseRecordMode = -1; CgiLib__ResponseStreamMode = 0; return (prevalue > 0); } /*****************************************************************************/ /* Tell the WASD server via the CGI response that it should consider the response body as already containing all required carriage-control. Return the previous value. */ int CgiLibResponseSetStreamMode (int enable) { int prevalue; /*********/ /* begin */ /*********/ if (CgiLib__Debug) fprintf (stdout, "CgiLibResponseSetStreamMode() %d\n", enable); prevalue = CgiLib__ResponseStreamMode; if (enable) CgiLib__ResponseStreamMode = 1; else CgiLib__ResponseStreamMode = -1; CgiLib__ResponseRecordMode = 0; return (prevalue > 0); } /*****************************************************************************/ /* Tell the WASD server via the CGI response to buffer discrete records, flushing only when full. Return the previous value. */ int CgiLibResponseSetBufferRecords (int enable) { int prevalue; /*********/ /* begin */ /*********/ if (CgiLib__Debug) fprintf (stdout, "CgiLibResponseSetBufferRecords() %d\n", enable); prevalue = CgiLib__ResponseBufferRecords; if (enable) CgiLib__ResponseBufferRecords = 1; else CgiLib__ResponseBufferRecords = -1; return (prevalue > 0); } /*****************************************************************************/ /* Return the a pointer to abbreviated meaning of the supplied HTTP status code. These are typical of those included on the response header status line. */ char* CgiLibHttpStatusCodeText (int StatusCode) { /*********/ /* begin */ /*********/ if (CgiLib__Debug) fprintf (stdout, "CgiLibHttpStatusCodeText() %d\n", StatusCode); switch (StatusCode) { case 000 : return ("Script Error!"); case 100 : return ("Continue"); /* (HTTP/1.1) */ case 101 : return ("Switching Protocols"); /* (HTTP/1.1) */ case 200 : return ("OK"); case 201 : return ("Created"); case 202 : return ("Accepted"); case 203 : return ("Non-authoritative"); /* (HTTP/1.1) */ case 204 : return ("No Content"); case 205 : return ("Reset Content"); /* (HTTP/1.1) */ case 206 : return ("Partial Content"); /* (HTTP/1.1) */ case 300 : return ("Multiple Choices"); /* (HTTP/1.1) */ case 301 : return ("Moved Permanently"); case 302 : return ("Found (Moved Temporarily)"); case 303 : return ("See Other"); /* (HTTP/1.1) */ case 304 : return ("Not Modified"); case 305 : return ("Use Proxy"); /* (HTTP/1.1) */ case 306 : return ("Temporary Redirect"); /* (HTTP/1.1) */ case 400 : return ("Bad Request"); case 401 : return ("Authorization Required"); case 402 : return ("Payment Required"); /* (HTTP/1.1) */ case 403 : return ("Forbidden"); case 404 : return ("Not Found"); case 405 : return ("Method Not Allowed"); /* (HTTP/1.1) */ case 406 : return ("Not Acceptable"); /* (HTTP/1.1) */ case 407 : return ("Proxy Authentication Required"); /* (HTTP/1.1) */ case 408 : return ("Request Timeout"); /* (HTTP/1.1) */ case 409 : return ("Conflict"); /* (HTTP/1.1) */ case 410 : return ("Gone"); /* (HTTP/1.1) */ case 411 : return ("Length Required"); /* (HTTP/1.1) */ case 412 : return ("Precondition Failed"); /* (HTTP/1.1) */ case 413 : return ("Request entity too large"); /* (HTTP/1.1) */ case 414 : return ("Request URI Too Long"); /* (HTTP/1.1) */ case 415 : return ("Unsupported Media Type"); /* (HTTP/1.1) */ case 416 : return ("Requested Range Not Satisfiable"); /* (HTTP/1.1) */ case 417 : return ("Expectation Failed"); /* (HTTP/1.1) */ case 500 : return ("Internal Error"); case 501 : return ("Not Implemented"); case 502 : return ("Bad Gateway"); case 503 : return ("Service Unavailable"); case 504 : return ("Gateway Timeout"); /* (HTTP/1.1) */ case 505 : return ("HTTP Version Not Supported"); /* (HTTP/1.1) */ default : return ("Unknown Code!"); } } /*****************************************************************************/ /* Set the tag parameters. Returns pointer to any previous. */ char* CgiLibResponseSetBody (char *cptr) { char *sptr; /*********/ /* begin */ /*********/ if (CgiLib__Debug) fprintf (stdout, "CgiLibResponseSetBody()\n"); sptr = CgiLib__ResponseBodyPtr; CgiLib__ResponseBodyPtr = cptr; return (sptr); } /*****************************************************************************/ /* Set the response character set. Returns pointer to any previous. */ char* CgiLibResponseSetCharset (char *cptr) { char *sptr; /*********/ /* begin */ /*********/ if (CgiLib__Debug) fprintf (stdout, "CgiLibResponseSetCharset() |%s|\n", cptr); sptr = CgiLib__ResponseCharsetPtr; if (!(CgiLib__ResponseCharsetPtr = cptr)) CgiLib__ResponseCharsetPtr = CgiLib__ResponseDefCharsetPtr; return (sptr); } /*****************************************************************************/ /* Set the default character set. Returns pointer to any previous. */ char* CgiLibResponseSetDefaultCharset (char *cptr) { char *sptr; /*********/ /* begin */ /*********/ if (CgiLib__Debug) fprintf (stdout, "CgiLibResponseSetDefaultCharset()\n"); sptr = CgiLib__ResponseDefCharsetPtr; if (!(CgiLib__ResponseDefCharsetPtr = cptr)) CgiLib__ResponseDefCharsetPtr = CGILIB_RESPONSE_DEFAULT_CHARSET; return (sptr); } /*****************************************************************************/ /* Set the error message. Returns pointer to any previous. */ char* CgiLibResponseSetErrorMessage (char *cptr) { char *sptr; /*********/ /* begin */ /*********/ if (CgiLib__Debug) fprintf (stdout, "CgiLibResponseSetErrorMessage()\n"); sptr = CgiLib__ResponseErrorMessagePtr; CgiLib__ResponseErrorMessagePtr = cptr; return (sptr); } /*****************************************************************************/ /* Set the error HTTP status code. Returns pointer to any previous. */ int CgiLibResponseSetErrorStatus (int status) { int pstatus; /*********/ /* begin */ /*********/ if (CgiLib__Debug) fprintf (stdout, "CgiLibResponseSetErrorStatus()\n"); pstatus = CgiLib__ResponseErrorStatus; CgiLib__ResponseErrorStatus = status; return (pstatus); } /*****************************************************************************/ /* Set the error string. Returns pointer to any previous. */ char* CgiLibResponseSetErrorTitle (char *cptr) { char *sptr; /*********/ /* begin */ /*********/ if (CgiLib__Debug) fprintf (stdout, "CgiLibResponseSetErrorTitle()\n"); sptr = CgiLib__ResponseErrorTitlePtr; CgiLib__ResponseErrorTitlePtr = cptr; return (sptr); } /*****************************************************************************/ /* Set the error additional information string. Returns pointer to any previous. */ char* CgiLibResponseSetErrorInfo (char *cptr) { char *sptr; /*********/ /* begin */ /*********/ if (CgiLib__Debug) fprintf (stdout, "CgiLibResponseSetErrorInfo()\n"); sptr = CgiLib__ResponseErrorInfoPtr; CgiLib__ResponseErrorInfoPtr = cptr; return (sptr); } /*****************************************************************************/ /* Set the success message string. Returns pointer to any previous. */ char* CgiLibResponseSetSuccessMessage (char *cptr) { char *sptr; /*********/ /* begin */ /*********/ if (CgiLib__Debug) fprintf (stdout, "CgiLibResponseSetSuccessMessage()\n"); sptr = CgiLib__ResponseSuccessMsgPtr; CgiLib__ResponseSuccessMsgPtr = cptr; return (sptr); } /*****************************************************************************/ /* Set the success HTTP status code. Returns pointer to any previous. */ int CgiLibResponseSetSuccessStatus (int status) { int pstatus; /*********/ /* begin */ /*********/ if (CgiLib__Debug) fprintf (stdout, "CgiLibResponseSetSuccessStatus()\n"); pstatus = CgiLib__ResponseSuccessStatus; CgiLib__ResponseSuccessStatus = status; return (pstatus); } /*****************************************************************************/ /* Set the success <title> string. Returns pointer to any previous. */ char* CgiLibResponseSetSuccessTitle (char *cptr) { char *sptr; /*********/ /* begin */ /*********/ if (CgiLib__Debug) fprintf (stdout, "CgiLibResponseSetSuccessTitle()\n"); sptr = CgiLib__ResponseSuccessTitlePtr; CgiLib__ResponseSuccessTitlePtr = cptr; return (sptr); } /*****************************************************************************/ /* Set the software ID string. Returns pointer to any previous. */ char* CgiLibResponseSetSoftwareID (char *cptr) { char *sptr; /*********/ /* begin */ /*********/ if (CgiLib__Debug) fprintf (stdout, "CgiLibResponseSetSoftwareID()\n"); sptr = CgiLib__ResponseSoftwareIdPtr; CgiLib__ResponseSoftwareIdPtr = cptr; if (CgiLib__Debug) fprintf (stdout, "|%s|%s|\n", cptr, sptr); return (sptr); } /*****************************************************************************/ /**************************/ /* URL/HTML ENCODE/DECODE */ /**************************/ /*****************************************************************************/ /* Decode URL-encoded string. Resultant string is always the same size or smaller so it can be done in-situ! Returns the size of the resultant string. */ int CgiLibUrlDecode (char *String) { unsigned char ch, pch = 0; char *cptr, *sptr; /*********/ /* begin */ /*********/ if (CgiLib__Debug) fprintf (stdout, "CgiLibUrlDecode() |%s|\n", String); cptr = sptr = String; while (*cptr) { switch (*cptr) { case '+' : *sptr++ = ' '; cptr++; pch = 0; continue; case '%' : cptr++; while (*cptr == '\r' || *cptr == '\n') cptr++; ch = 0; if (*cptr >= '0' && *cptr <= '9') ch = (*cptr - (int)'0') << 4; else if (toupper(*cptr) >= 'A' && toupper(*cptr) <= 'F') ch = (toupper(*cptr) - (int)'A' + 10) << 4; else { *String = '\0'; return (-1); } if (*cptr) cptr++; while (*cptr == '\r' || *cptr == '\n') cptr++; if (*cptr >= '0' && *cptr <= '9') ch += (*cptr - (int)'0'); else if (toupper(*cptr) >= 'A' && toupper(*cptr) <= 'F') ch += (toupper(*cptr) - (int)'A' + 10); else { *String = '\0'; return (-1); } if (*cptr) cptr++; /* if URL-encoded 8 bit UTF-8 (e.g. %C3%A9) */ if (pch == 0xc2 && ch >= 0x80 && ch <= 0xbf) { *(sptr-1) = ch; pch = 0; } else if (pch == 0xc3 && ch >= 0x80 && ch <= 0xbf) { *(sptr-1) = ch + 0x40; pch = 0; } else *sptr++ = pch = ch; continue; case '\r' : case '\n' : cptr++; pch = 0; continue; default : *sptr++ = *cptr++; pch = 0; } } *sptr = '\0'; if (CgiLib__Debug) fprintf (stdout, "|%s|\n", String); return (sptr-String); } /****************************************************************************/ /* URL-encode (nearly) all non-alpha-numeric characters. If 'EncodedString' is NULL sufficient memory will be dynamically allocated to hold the encoded string. For fixed size 'EncodedString' it will not overflow the supplied string but does not return any indication it reached the limit. Returns the length of the encoded string or a pointer to the encoded string (which should be cast to (char*) by the calling routine). */ int CgiLibUrlEncode ( char *PlainString, int NumberOfChars, char *EncodedString, int SizeOfEncodedString ) { static char EmptyString [] = "", HexDigits [] = "0123456789abcdef"; int ccnt, CurrentLength, EncodedSize; char *cptr, *sptr, *zptr, *EncodedPtr; /*********/ /* begin */ /*********/ if (CgiLib__Debug) fprintf (stdout, "CgiLibUrlEncode() |%s| %d\n", PlainString, NumberOfChars); if (!*(cptr = PlainString)) { if (EncodedString == NULL) return ((int)EmptyString); else { EncodedString[0] = '\0'; return (0); } } if (EncodedString == NULL) { sptr = zptr = EncodedPtr = NULL; EncodedSize = 0; } else { sptr = EncodedPtr = EncodedString; if (SizeOfEncodedString == -1) zptr = (char*)INT_MAX; else zptr = sptr + SizeOfEncodedString - 1; } if (NumberOfChars == -1) ccnt = INT_MAX; else ccnt = NumberOfChars; while (*cptr && ccnt) { if (EncodedString == NULL && sptr >= zptr) { /* out of storage (or first use) get more (or some) memory */ CurrentLength = sptr - EncodedPtr; if (CgiLib__VeeMemInUse) EncodedPtr = CgiLibVeeMemRealloc (EncodedPtr, EncodedSize+CGILIB_ENCODED_CHUNK); else EncodedPtr = realloc (EncodedPtr, EncodedSize+CGILIB_ENCODED_CHUNK); if (!EncodedPtr) if (CgiLib__VeeMemContinue) return ((int)NULL); else exit (vaxc$errno); EncodedSize += CGILIB_ENCODED_CHUNK; /* recalculate pointers */ sptr = EncodedPtr + CurrentLength; /* three allows a worst-case %nn encoding without failing */ zptr = EncodedPtr + EncodedSize - 3; } else if (sptr >= zptr) break; if (isalnum(*cptr) || *cptr == '/' || *cptr == '-' || *cptr == '_' || *cptr == '.' || /* strictly, the following characters should be encoded */ *cptr == '$' || *cptr == '*' || *cptr == ';' || *cptr == '~' || *cptr == '^') { *sptr++ = *cptr++; ccnt--; continue; } if (sptr < zptr) *sptr++ = '%'; if (sptr < zptr) *sptr++ = HexDigits[(*cptr & 0xf0) >> 4]; if (sptr < zptr) *sptr++ = HexDigits[*cptr & 0x0f]; cptr++; ccnt--; } *sptr = '\0'; if (CgiLib__Debug) fprintf (stdout, "%d |%s|\n", sptr-EncodedPtr, EncodedPtr); if (EncodedString == NULL) return ((int)EncodedPtr); else return (sptr - EncodedPtr); } /*****************************************************************************/ /* URL-encode (%nn) a possibly extended specification file name. This can be done in two ways. Leave the VMS extended file specification escape sequences in place (e.g. "^_") but encode absolutely forbidden characters (.e.g ' ', '?', "%", etc.), called 'RelaxedEncode' here. Or a strict encode, where the extended escaped sequences are first un-escaped into characters, then those charaters URL-encoded as necessary. If 'ForceLower' true, and no extended file specification characters were found (e.g. lower-case, escaped characters) then replace all upper-case alphabetics with lower-case. If 'EncodedString' is NULL sufficient memory will be dynamically allocated to hold the encoded string. For fixed size 'EncodedString' it will not overflow the supplied string but does not return any indication it reached the limit. Returns the length of the encoded string or a pointer to the encoded string (which should be cast to (char*) by the calling routine). */ int CgiLibUrlEncodeFileName ( char *FileName, char *EncodedString, int SizeOfEncodedString, int RelaxedEncode, int ForceLower ) { static char EmptyString [] = "", HexDigits [] = "0123456789abcdef"; int CurrentLength, EncodedSize, HitExtended; unsigned char ch; char *cptr, *sptr, *zptr, *EncodedPtr; /*********/ /* begin */ /*********/ if (CgiLib__Debug) fprintf (stdout, "CgiLibUrlEncodeFileName() |%s| %d %d\n", FileName, RelaxedEncode, ForceLower); if (!*(cptr = FileName)) { if (EncodedString == NULL) return ((int)EmptyString); else { EncodedString[0] = '\0'; return (0); } } if (EncodedString == NULL) { sptr = zptr = EncodedPtr = NULL; EncodedSize = 0; } else { sptr = EncodedPtr = EncodedString; if (SizeOfEncodedString == -1) zptr = (char*)INT_MAX; else zptr = sptr + SizeOfEncodedString - 1; } HitExtended = 0; cptr = FileName; while (ch = *cptr) { if (EncodedString == NULL && sptr >= zptr) { /* out of storage (or first use) get more (or some) memory */ CurrentLength = sptr - EncodedPtr; if (CgiLib__VeeMemInUse) EncodedPtr = CgiLibVeeMemRealloc (EncodedPtr, EncodedSize+CGILIB_ENCODED_CHUNK); else EncodedPtr = realloc (EncodedPtr, EncodedSize+CGILIB_ENCODED_CHUNK); if (!EncodedPtr) if (CgiLib__VeeMemContinue) return ((int)NULL); else exit (vaxc$errno); EncodedSize += CGILIB_ENCODED_CHUNK; /* recalculate pointers */ sptr = EncodedPtr + CurrentLength; /* three allows a worst-case %nn encoding without failing */ zptr = EncodedPtr + EncodedSize - 3; } else if (sptr >= zptr) break; if (islower(ch)) HitExtended = 1; if (!RelaxedEncode && ch == '^') { /* extended file specification escape character */ HitExtended = 1; ch = *++cptr; switch (ch) { case '!' : case '#' : case '&' : case '\'' : case '`' : case '(' : case ')' : case '+' : case '@' : case '{' : case '}' : case '.' : case ',' : case ';' : case '[' : case ']' : case '%' : case '^' : case '=' : case ' ' : break; case '_' : ch = ' '; break; default : if (isxdigit (ch)) { ch = 0; if (*cptr >= '0' && *cptr <= '9') ch = (*cptr - (int)'0') << 4; else if (tolower(*cptr) >= 'a' && tolower(*cptr) <= 'f') ch = (tolower(*cptr) - (int)'a' + 10) << 4; cptr++; if (*cptr >= '0' && *cptr <= '9') ch += (*cptr - (int)'0'); else if (tolower(*cptr) >= 'a' && tolower(*cptr) <= 'f') ch += (tolower(*cptr) - (int)'a' + 10); } else ch = *cptr; } } /* URL-encode if necessary */ if (isalnum(ch) || ch == '/' || ch == '-' || ch == '_' || ch == '.' || ch == '$' || ch == '*' || ch == ':' || ch == ';' || (RelaxedEncode && (ch == ',' || ch == '\'' || ch == '=' || ch == '~' || ch == '^' || ch == '[' || ch == ']' || ch == '(' || ch == ')' || ch == '{' || ch == '}' ))) *sptr++ = ch; else { if (sptr < zptr) *sptr++ = '%'; if (sptr < zptr) *sptr++ = HexDigits[(ch & 0xf0) >> 4]; if (sptr < zptr) *sptr++ = HexDigits[ch & 0x0f]; } if (*cptr) cptr++; } *sptr = '\0'; /* if it's not an extended specification then force to lower-case */ if (ForceLower && !HitExtended) for (sptr = EncodedPtr; *sptr; sptr++) if (isupper(*sptr)) *sptr = tolower(*sptr); if (CgiLib__Debug) fprintf (stdout, "%d |%s|\n", sptr-EncodedPtr, EncodedPtr); if (EncodedString == NULL) return ((int)EncodedPtr); else return (sptr - EncodedPtr); } /****************************************************************************/ /* Escape plain-text characters forbidden to occur in HTML documents (i.e. '<', '>' and '&'). If 'EscapedString' is NULL sufficient memory will be dynamically allocated to hold the encoded string. For fixed size 'EscapedString' it will not overflow the supplied string but does not return any indication it reached the limit. Returns the length of the escaped string or a pointer to the encoded string (which should be cast to (char*) by the calling routine). 'AnchorUrls' may be used to turn HTML URLs into HTML anchors in the text by calling with CGILIB_ANCHOR_WEB. It will also attempt to detect mail addresses (anything like 'this@wherever.host.name') and create "mailto:" links out of them, call with CGILIB_ANCHOR_MAIL. To get both use a bit-wise OR. */ int CgiLibHtmlEscape ( char *PlainString, int NumberOfChars, char *EscapedString, int SizeOfEscapedString ) { if (CgiLib__Debug) fprintf (stdout, "CgiLibHtmlEscape() |%s| %d\n", PlainString, NumberOfChars); return (CgiLibAnchorHtmlEscape (PlainString, NumberOfChars, EscapedString, SizeOfEscapedString, CGILIB_ANCHOR_NONE)); } int CgiLibAnchorHtmlEscape ( char *PlainString, int NumberOfChars, char *EscapedString, int SizeOfEscapedString, int AnchorUrls ) { static char EmptyString [] = ""; int ccnt, len, tcnt, AtCount, CurrentLength, EscapedSize, PeriodCount, SchemeLength, SplitAnchor, SplitCount, UrlEscapedLength, UrlOriginalLength, UrlSplitEscapedLength, WasMailAddress, WasMailtoLink; char *cptr, *sptr, *tptr, *zptr, *EscapedPtr, *UrlAnchorPtr, *UrlEscapedPtr, *UrlOriginalPtr, *UrlSplitEscapedPtr; char UrlOriginal [1024+128]; /*********/ /* begin */ /*********/ if (CgiLib__Debug) fprintf (stdout, "CgiLibAnchorHtmlEscape() |%s| %d %d\n", PlainString, NumberOfChars, AnchorUrls); if (!PlainString) { if (EscapedString == NULL) return ((int)NULL); if (SizeOfEscapedString >= 1) *EscapedString = '\0'; return (0); } if (!*(cptr = PlainString)) { if (EscapedString == NULL) return ((int)EmptyString); else { EscapedString[0] = '\0'; return (0); } } if (EscapedString == NULL) { sptr = zptr = EscapedPtr = NULL; EscapedSize = 0; } else { sptr = EscapedPtr = EscapedString; if (SizeOfEscapedString == -1) zptr = (char*)INT_MAX; else zptr = sptr + SizeOfEscapedString - 1; } if (NumberOfChars == -1) ccnt = INT_MAX; else ccnt = NumberOfChars; while (*cptr && ccnt) { if (AnchorUrls != CGILIB_ANCHOR_NONE) { UrlOriginalLength = WasMailAddress = WasMailtoLink = SchemeLength = 0; UrlOriginal[0] = '\0'; if ((AnchorUrls & CGILIB_ANCHOR_MASK1 & CGILIB_ANCHOR_WEB) && ccnt >= 6 && (cptr[3] == ':' || cptr[4] == ':' || cptr[5] == ':' || cptr[6] == ':') && (!memcmp (cptr, "http://", SchemeLength = 7) || !memcmp (cptr, "https://", SchemeLength = 8) || !memcmp (cptr, "mailto:", SchemeLength = 7) || !memcmp (cptr, "file://", SchemeLength = 7) || !memcmp (cptr, "ftp://", SchemeLength = 6) || !memcmp (cptr, "gopher://", SchemeLength = 9) || !memcmp (cptr, "news://", SchemeLength = 7) || !memcmp (cptr, "wais://", SchemeLength = 7))) { /***********/ /* web URL */ /***********/ if (!memcmp (cptr, "mailto:", 7)) { WasMailAddress = WasMailtoLink = 1; if (CgiLib__soyMAILhack) { /* don't need the scheme */ cptr += SchemeLength; ccnt -= SchemeLength; } } tptr = UrlOriginal; while (ccnt && *cptr && !isspace(*cptr) && tptr < UrlOriginal+sizeof(UrlOriginal)-1) { *tptr++ = *cptr++; ccnt--; } if (tptr < UrlOriginal+sizeof(UrlOriginal)-1) { /* if trailed by what is probably textual punctuation */ while (tptr > UrlOriginal && (tptr[-1] == '.' || tptr[-1] == ',' || tptr[-1] == ')' || tptr[-1] == ']' || tptr[-1] == '>' || tptr[-1] == '\"' || tptr[-1] == '\'' || tptr[-1] == '!')) { tptr--; cptr--; ccnt++; } } *tptr = '\0'; UrlOriginalLength = tptr - UrlOriginal; if (!UrlOriginalLength || UrlOriginalLength == SchemeLength) { /* just and empty scheme, replace the text */ cptr -= SchemeLength; ccnt += SchemeLength; UrlOriginalLength = WasMailAddress = WasMailtoLink = 0; UrlOriginal[0] = '\0'; } } else if ((AnchorUrls & CGILIB_ANCHOR_MASK1 & CGILIB_ANCHOR_MAIL) && isalnum (*cptr)) { /************************************/ /* look for a possible mail address */ /************************************/ AtCount = PeriodCount = 0; tcnt = ccnt; tptr = cptr; while (ccnt && *cptr && (isalnum(*cptr) || *cptr == '@' || *cptr == '.' || *cptr == '_' || *cptr == '-')) { if (*cptr == '@') { PeriodCount = 0; AtCount++; } else if (*cptr == '.') PeriodCount++; ccnt--; cptr++; } /* mail addresses shouldn't have a trailing period! */ if (*(cptr-1) == '.' && PeriodCount) PeriodCount--; ccnt = tcnt; cptr = tptr; if (WasMailAddress = (AtCount == 1 && PeriodCount >= 1)) { /***************************/ /* mail address (probably) */ /***************************/ tptr = UrlOriginal; if (!CgiLib__soyMAILhack) { /* fudge the scheme */ strcpy (tptr, "mailto:"); tptr += 7; } while (ccnt && *cptr && (isalnum(*cptr) || *cptr == '@' || *cptr == '.' || *cptr == '_' || *cptr == '-') && tptr < UrlOriginal+sizeof(UrlOriginal)-1) { ccnt--; *tptr++ = *cptr++; } /* mail addresses shouldn't have a trailing period! */ if (*(cptr-1) == '.') { ccnt++; cptr--; tptr--; } *tptr = '\0'; UrlOriginalLength = tptr - UrlOriginal; } } if (UrlOriginalLength) { /***************************/ /* make URL into an anchor */ /***************************/ if ((len = UrlOriginalLength) > sizeof(UrlOriginal)-1) exit (SS$_BUGCHECK); UrlOriginalPtr = UrlOriginal; if (len >= 2 && (!WasMailAddress || (WasMailAddress && CgiLib__soyMAILhack)) && AnchorUrls != CGILIB_ANCHOR_ALL && AnchorUrls & CGILIB_ANCHOR_MASK2 & CGILIB_ANCHOR_SPLIT) { SplitAnchor = 1; SplitCount = 2; len /= 2; } else { SplitAnchor = 0; SplitCount = 1; } UrlEscapedPtr = (char*)CgiLibHtmlEscape (UrlOriginalPtr, -1, NULL, 0); UrlEscapedLength = strlen(UrlEscapedPtr); while (SplitCount--) { UrlSplitEscapedPtr = (char*)CgiLibHtmlEscape (UrlOriginalPtr, len, NULL, 0); UrlSplitEscapedLength = strlen(UrlSplitEscapedPtr); if (SplitCount) { UrlOriginalPtr += len; len = strlen(UrlOriginalPtr); } if (CgiLib__Debug) fprintf (stdout, "Url |%s|%s|\n", UrlOriginal, UrlEscapedPtr); if (WasMailAddress && CgiLib__soyMAILhack) { if (CgiLib__VeeMemInUse) tptr = UrlAnchorPtr = CgiLibVeeMemCalloc (UrlEscapedLength + UrlSplitEscapedLength + strlen(CgiLib__soyMAILhack) + 64); else tptr = UrlAnchorPtr = calloc (1, UrlEscapedLength + UrlSplitEscapedLength + strlen(CgiLib__soyMAILhack) + 64); } else { if (CgiLib__VeeMemInUse) tptr = UrlAnchorPtr = CgiLibVeeMemCalloc (UrlEscapedLength + UrlSplitEscapedLength + 64); else tptr = UrlAnchorPtr = calloc (1, UrlEscapedLength + UrlSplitEscapedLength + 64); } if (tptr == NULL) if (CgiLib__VeeMemContinue) return ((int)NULL); else exit (vaxc$errno); strcpy (tptr, "<a href=\""); tptr += 9; if (WasMailAddress && CgiLib__soyMAILhack) { strcpy (tptr, CgiLib__soyMAILhack); while (*tptr) tptr++; } strcpy (tptr, UrlEscapedPtr); tptr += UrlEscapedLength; if (!SplitAnchor || SplitCount) { strcpy (tptr, "\" target=\"_top\">"); tptr += 16; } else { strcpy (tptr, "\" target=\"_blank\">"); tptr += 18; } if (WasMailAddress && !WasMailtoLink && !CgiLib__soyMAILhack) { /* don't need the introduced "mailto:" in the text */ strcpy (tptr, UrlSplitEscapedPtr+7); tptr += UrlSplitEscapedLength-7; } else { strcpy (tptr, UrlSplitEscapedPtr); tptr += UrlSplitEscapedLength; } strcpy (tptr, "</A>"); if (CgiLib__Debug) fprintf (stdout, "UrlAnchorPtr |%s|\n", UrlAnchorPtr); for (tptr = UrlAnchorPtr; *tptr; *sptr++ = *tptr++) { if (EscapedString == NULL && sptr >= zptr) { /* out of storage (or first use) get memory */ CurrentLength = sptr - EscapedPtr; if (CgiLib__VeeMemInUse) EscapedPtr = CgiLibVeeMemRealloc (EscapedPtr, EscapedSize + CGILIB_ESCAPED_CHUNK); else EscapedPtr = realloc (EscapedPtr, EscapedSize + CGILIB_ESCAPED_CHUNK); if (!EscapedPtr) if (CgiLib__VeeMemContinue) return ((int)NULL); else exit (vaxc$errno); EscapedSize += CGILIB_ESCAPED_CHUNK; /* recalculate pointers */ sptr = EscapedPtr + CurrentLength; zptr = EscapedPtr + EscapedSize - 1; } else if (sptr >= zptr) break; } if (CgiLib__VeeMemInUse) CgiLibVeeMemFree (UrlSplitEscapedPtr); else free (UrlSplitEscapedPtr); } if (CgiLib__VeeMemInUse) CgiLibVeeMemFree (UrlEscapedPtr); else free (UrlEscapedPtr); continue; } } /********************/ /* simple character */ /********************/ /* 'sptr+6' is the "worst-case" character substitution */ if (EscapedString == NULL && sptr+6 >= zptr) { /* out of storage (or first use) get more (or some) memory */ CurrentLength = sptr - EscapedPtr; if (CgiLib__VeeMemInUse) EscapedPtr = CgiLibVeeMemRealloc (EscapedPtr, EscapedSize+CGILIB_ESCAPED_CHUNK); else EscapedPtr = realloc (EscapedPtr, EscapedSize+CGILIB_ESCAPED_CHUNK); if (!EscapedPtr) if (CgiLib__VeeMemContinue) return ((int)NULL); else exit (vaxc$errno); EscapedSize += CGILIB_ESCAPED_CHUNK; /* recalculate pointers */ sptr = EscapedPtr + CurrentLength; zptr = EscapedPtr + EscapedSize - 1; } else if (sptr+6 >= zptr) break; switch (*cptr) { case '<' : memcpy (sptr, "<", 4); sptr += 4; cptr++; ccnt--; continue; case '>' : memcpy (sptr, ">", 4); sptr += 4; cptr++; ccnt--; continue; case '&' : memcpy (sptr, "&", 5); sptr += 5; cptr++; ccnt--; continue; case '"' : memcpy (sptr, """, 6); sptr += 6; cptr++; ccnt--; continue; case '\'' : memcpy (sptr, "'", 5); sptr += 5; cptr++; ccnt--; continue; default : *sptr++ = *cptr++; ccnt--; } } *sptr = '\0'; if (CgiLib__Debug) fprintf (stdout, "%d |%s|\n", sptr-EscapedPtr, EscapedPtr); if (EscapedString == NULL) return ((int)EscapedPtr); else return (sptr - EscapedPtr); } /*****************************************************************************/ /* Convert numeric HTML entities in a string into their character equivalents (e.g. "&" to '&', "� to 0x00, etc.) Also converts common alphabetic entities (e.g. "&",  ", <", etc.) but not all (any that are not recognised are left untouched). Does not URL-decode! Resultant string is always the same size or smaller so it can be done in-situ! Returns the size of the resultant string. */ int CgiLibHtmlDeEntify (char *String) { struct HtmlEntityStruct { char *ent; int len, val; } HtmlEntity [] = { { "amp;", 4, '&' }, { "lt;", 3, '<' }, { "gt;", 3, '>' }, { "quot;", 5, '\"' }, { "apos;", 5, '\'' }, { "nbsp;", 5, 160 }, { "iexcl;", 6, 161 }, { "cent;", 5, 162 }, { "pound;", 6, 163 }, { "curren;", 7, 164 }, { "yen;", 4, 165 }, { "brvbar;", 7, 166 }, { "sect;", 5, 167 }, { "uml;", 4, 168 }, { "copy;", 5, 169 }, { "ordf;", 5, 170 }, { "laquo;", 6, 171 }, { "not;", 4, 172 }, { "shy;", 4, 173 }, { "reg;", 4, 174 }, { "macr;", 5, 175 }, { "deg;", 4, 176 }, { "plusmn;", 7, 177 }, { "sup2;", 5, 178 }, { "sup3;", 5, 179 }, { "acute;", 6, 180 }, { "micro;", 6, 181 }, { "para;", 5, 182 }, { "middot;", 7, 183 }, { "cedil;", 6, 184 }, { "sup1;", 5, 185 }, { "ordm;", 5, 186 }, { "raquo;", 6, 187 }, { "frac14;", 7, 188 }, { "frac12;", 7, 189 }, { "frac34;", 7, 190 }, { "iquest;", 7, 191 }, { "Agrave;", 7, 192 }, { "Aacute;", 7, 193 }, { "Acirc;", 6, 194 }, { "Atilde;", 7, 195 }, { "Auml;", 5, 196 }, { "Aring;", 6, 197 }, { "AElig;", 6, 198 }, { "Ccedil;", 7, 199 }, { "Egrave;", 7, 200 }, { "Eacute;", 7, 201 }, { "Ecirc;", 6, 202 }, { "Euml;", 5, 203 }, { "Igrave;", 7, 204 }, { "Iacute;", 7, 205 }, { "Icirc;", 6, 206 }, { "Iuml;", 5, 207 }, { "ETH;", 4, 208 }, { "Ntilde;", 7, 209 }, { "Ograve;", 7, 210 }, { "Oacute;", 7, 211 }, { "Ocirc;", 6, 212 }, { "Otilde;", 7, 213 }, { "Ouml;", 5, 214 }, { "times;", 6, 215 }, { "Oslash;", 7, 216 }, { "Ugrave;", 7, 217 }, { "Uacute;", 7, 218 }, { "Ucirc;", 6, 219 }, { "Uuml;", 5, 220 }, { "Yacute;", 7, 221 }, { "THORN;", 6, 222 }, { "szlig;", 6, 223 }, { "agrave;", 7, 224 }, { "aacute;", 7, 225 }, { "acirc;", 6, 226 }, { "atilde;", 7, 227 }, { "auml;", 5, 228 }, { "aring;", 6, 229 }, { "aelig;", 6, 230 }, { "ccedil;", 7, 231 }, { "egrave;", 7, 232 }, { "eacute;", 7, 233 }, { "ecirc;", 6, 234 }, { "euml;", 5, 235 }, { "igrave;", 7, 236 }, { "iacute;", 7, 237 }, { "icirc;", 6, 238 }, { "iuml;", 5, 239 }, { "eth;", 4, 240 }, { "ntilde;", 7, 241 }, { "ograve;", 7, 242 }, { "oacute;", 7, 243 }, { "ocirc;", 6, 244 }, { "otilde;", 7, 245 }, { "ouml;", 5, 246 }, { "divide;", 7, 247 }, { "oslash;", 7, 248 }, { "ugrave;", 7, 249 }, { "uacute;", 7, 250 }, { "ucirc;", 6, 251 }, { "uuml;", 5, 252 }, { "yacute;", 7, 253 }, { "thorn;", 6, 254 }, { "yuml;", 5, 255 }, { NULL, 0, 0 } }; int idx; unsigned char ch; char *cptr, *sptr; /*********/ /* begin */ /*********/ if (CgiLib__Debug) fprintf (stdout, "CgiLibHtmlDeEntify() |%s|\n", String); cptr = sptr = String; while (*cptr) { if (*cptr != '&') { *sptr++ = *cptr++; continue; } cptr++; if (*cptr == '#') { cptr++; if (isdigit(*cptr)) ch = atoi(cptr); else ch = (unsigned char)256; if (ch > 255) { if (CgiLib__Debug) fprintf (stdout, "%d |%s|\n", ch, cptr); return (-1); } *sptr++ = ch & 0xff; while (*cptr && *cptr != ';') cptr++; if (*cptr) cptr++; continue; } for (idx = 0; HtmlEntity[idx].ent; idx++) { if (!memcmp (cptr, HtmlEntity[idx].ent, HtmlEntity[idx].len)) { *sptr++ = HtmlEntity[idx].val; cptr += HtmlEntity[idx].len; break; } } if (HtmlEntity[idx].ent) continue; *sptr++ = '&'; *sptr++ = *cptr++; } *sptr = '\0'; if (CgiLib__Debug) fprintf (stdout, "|%s|\n", String); return (sptr-String); } /*****************************************************************************/ /* This function parses a "application/x-www-form-urlencoded" string into it's decoded, constituent name and value pairs. It can be passed *any* urlencoded string including QUERY_STRING CGI variables and POSTed request bodies. It returns a dynamically allocated string comprising a "name=value" pair, in much the same format as a CgiLibVar("*") call. When the form fields are exhausted it returns a pointer to NULL. The form field name is not prefixed by "WWW_FORM", or anything else. This buffer is reused with each call and so should not be modified, any contents should be copied as with CgiLibVar() constraints. The 'Context' parameter should be initialized to zero before the initial call. At the end of a parse it is reinitialized to zero. */ char* CgiLibFormEncodedParse ( char *String, int *Context ) { static int BufferSize = 0; static char *BufferPtr = NULL; char *cptr, *sptr; /*********/ /* begin */ /*********/ if (CgiLib__Debug) fprintf (stdout, "CgiLibFormEncodedParse()\n"); /* get the 'current distance from the start of the string' */ cptr = sptr = String + *Context; if (!*cptr) { /* end of string */ if (CgiLib__VeeMemInUse) CgiLibVeeMemFree (BufferPtr); else free (BufferPtr); BufferPtr = NULL; BufferSize = 0; *Context = 0; return (NULL); } while (*sptr && *sptr != '&') sptr++; if (BufferSize < sptr - cptr + 1) { BufferSize = sptr - cptr + 1; if (CgiLib__VeeMemInUse) BufferPtr = CgiLibVeeMemRealloc (BufferPtr, BufferSize); else BufferPtr = realloc (BufferPtr, BufferSize); if (!BufferPtr) if (CgiLib__VeeMemContinue) return (NULL); else exit (vaxc$errno); } memcpy (BufferPtr, cptr, sptr-cptr); BufferPtr[sptr-cptr] = '\0'; CgiLibUrlDecode (BufferPtr); /* set the 'new distance from the start of the string' */ if (*sptr) sptr++; *Context = sptr - String; return (BufferPtr); } /****************************************************************************/ /* This is really just a convenience function and has little to do with CGI scripting. VMS $FAO-formatted print statement. Output is either to the specified stream, or if stream pointer NULL return a pointer to a dynamic buffer buffer containing the formatted output (requires caller to dispose of it as necessary). Will accomodate up to 64 $FAO directives and resultant strings to 32kB in size. */ char* CgiLibFaoPrint ( FILE *StreamPtr, char *FormatString, ... ) { /* size of allocated buffer */ #define CGILIB_FAO_BUFFER_ARG 64 #define CGILIB_FAO_BUFFER_MIN 256 #define CGILIB_FAO_BUFFER_MAX 32768 static $DESCRIPTOR (BufferDsc, ""); static $DESCRIPTOR (FormatFaoDsc, ""); int argcnt, blen, status; unsigned short slen; unsigned long *vecptr; unsigned long FaoVector [CGILIB_FAO_BUFFER_ARG]; char *bptr; va_list argptr; /*********/ /* begin */ /*********/ va_count (argcnt); if (CgiLib__Debug) fprintf (stdout, "CgiLibFaoPrint() |%s| %d\n", FormatString, argcnt); if (argcnt > CGILIB_FAO_BUFFER_ARG+2) return ("[CgiLibFaoPrint():argovf]"); vecptr = FaoVector; va_start (argptr, FormatString); for (argcnt -= 2; argcnt; argcnt--) *vecptr++ = va_arg (argptr, unsigned long); va_end (argptr); FormatFaoDsc.dsc$a_pointer = FormatString; FormatFaoDsc.dsc$w_length = strlen(FormatString); /* start modestly and work up to it if necessary */ for (blen = CGILIB_FAO_BUFFER_MIN; blen < CGILIB_FAO_BUFFER_MAX; blen *= 2) { if (CgiLib__VeeMemInUse) bptr = CgiLibVeeMemCalloc (blen); else bptr = calloc (1, blen); if (bptr == NULL) if (CgiLib__VeeMemContinue) return (NULL); else exit (vaxc$errno); BufferDsc.dsc$a_pointer = bptr; BufferDsc.dsc$w_length = blen; status = sys$faol (&FormatFaoDsc, &slen, &BufferDsc, &FaoVector); if (status == SS$_BUFFEROVF) { if (CgiLib__VeeMemInUse) CgiLibVeeMemFree (bptr); else free (bptr); continue; } if (!(status & 1)) sprintf (bptr, "[CgiLibFaoPrint():%%X%08.08X]\n", status); break; } if (blen >= CGILIB_FAO_BUFFER_MAX) { if (CgiLib__VeeMemInUse) bptr = CgiLibVeeMemCalloc (64); else bptr = calloc (1, 64); if (bptr == NULL) if (CgiLib__VeeMemContinue) return (NULL); else exit (vaxc$errno); sprintf (bptr, "[CgiLibFaoPrint():%%X%08.08X]\n", status); } if (CgiLib__Debug) fprintf (stdout, "|%s|\n", bptr); if (StreamPtr != NULL) { fwrite (bptr, slen, 1, StreamPtr); if (CgiLib__VeeMemInUse) CgiLibVeeMemFree (bptr); else free (bptr); bptr = NULL; } return (bptr); #undef CGILIB_FAO_BUFFER_ARG #undef CGILIB_FAO_BUFFER_MIN #undef CGILIB_FAO_BUFFER_MAX } /*****************************************************************************/ /********************/ /* CGIPLUS SPECIFIC */ /********************/ /*****************************************************************************/ /* For CGIplus output the "end-of-file" record (end of script output). */ void CgiLibCgiPlusEOF () { /*********/ /* begin */ /*********/ if (CgiLib__Debug) fprintf (stdout, "CgiLibCgiPlusEOF()\n"); if (CgiLib__Environment != CGILIB_ENVIRONMENT_CGIPLUS) return; /* CGI EOF must be in a record by itself ... flush! */ fflush (stdout); fputs (CgiLib__CgiPlusEofPtr, stdout); fflush (stdout); /* reset these ready for any subsequent request */ CgiLib__ResponseCount = CgiLib__ResponseStatusCode = 0; } /*****************************************************************************/ /* For CGIplus output the "end-of-text" record (end of CGIplus callout). */ void CgiLibCgiPlusEOT () { /*********/ /* begin */ /*********/ if (CgiLib__Debug) fprintf (stdout, "CgiLibCgiPlusEOT()\n"); if (CgiLib__Environment != CGILIB_ENVIRONMENT_CGIPLUS) return; if (CgiLib__CgiPlusEotPtr == NULL) CgiLib__CgiPlusEotPtr = getenv("CGIPLUSEOT"); /* CGI EOT must be in a record by itself ... flush! */ fflush (stdout); fputs (CgiLib__CgiPlusEotPtr, stdout); fflush (stdout); } /*****************************************************************************/ /* For CGIplus output the "escape" record (start of CGIplus callout). */ void CgiLibCgiPlusESC () { /*********/ /* begin */ /*********/ if (CgiLib__Debug) fprintf (stdout, "CgiLibCgiPlusESC()\n"); if (CgiLib__Environment != CGILIB_ENVIRONMENT_CGIPLUS) return; if (CgiLib__CgiPlusEscPtr == NULL) CgiLib__CgiPlusEscPtr = getenv("CGIPLUSESC"); /* CGI ESC must be in a record by itself ... flush! */ fflush (stdout); fputs (CgiLib__CgiPlusEscPtr, stdout); fflush (stdout); } /*****************************************************************************/ /* Provide a callout to WASD server. */ char* CgiLibCgiPlusCallout (char *fmt, ...) { static char CalloutResponse [256]; int retval; char *cptr; char buf [256]; va_list ap; /*********/ /* begin */ /*********/ if (CgiLib__Debug) fprintf (stdout, "CgiLibCgiPlusCallout()\n"); if (CgiLib__Environment != CGILIB_ENVIRONMENT_CGIPLUS) return (NULL); CgiLibCgiPlusESC(); va_start (ap, fmt); retval = vsnprintf (buf, sizeof(buf), fmt, ap); va_end (ap); if (retval >= 0) fputs (buf, stdout); CgiLibCgiPlusEOT(); if (buf[0] == '!' || buf[0] == '#') return (NULL); memset (CalloutResponse, 0, sizeof(CalloutResponse)); CgiLibCgiPlusInGets (CalloutResponse, sizeof(CalloutResponse)); *(strchr (CalloutResponse, '\n')) = '\0'; return (CalloutResponse); } /*****************************************************************************/ /* Read a record (string) from the CGIPLUSIN stream. If the buffer is too small the record will be truncated, but will always be null-terminated. This function is provided to allow a CGIplus callout to read a single record response from the server. */ void CgiLibCgiPlusInGets ( char *String, int SizeOfString ) { /*********/ /* begin */ /*********/ if (CgiLib__Debug) fprintf (stdout, "CgiLibCgiPlusInGets()\n"); /* the CGIplus input stream should always have been opened by now! */ if (CgiLib__CgiPlusInFile == NULL) exit (SS$_BUGCHECK); if (fgets (String, SizeOfString, CgiLib__CgiPlusInFile) == NULL) exit (vaxc$errno); /* absorb intermediate empty lines that seem to be present */ if (String[0] == '\n' && !String[1]) if (fgets (String, SizeOfString, CgiLib__CgiPlusInFile) == NULL) exit (vaxc$errno); String[SizeOfString-1] = '\0'; } /*****************************************************************************/ /* Switch CGIplus variable stream into 'record' mode. In this mode each CGI variable "name=value" pair is transmitted as an individual I/O. */ void CgiLibCgiPlusSetVarRecord () { /*********/ /* begin */ /*********/ if (CgiLib__Debug) fprintf (stdout, "CgiLibCgiPlusSetVarRecord()\n"); CgiLibCgiPlusESC (); fflush (stdout); fputs ("!CGIPLUS: record\n", stdout); fflush (stdout); CgiLibCgiPlusEOT (); fflush (stdout); CgiLib__CgiPlusVarRecord = 1; CgiLib__CgiPlusVarStruct = 0; } /*****************************************************************************/ /* Switch CGIplus variable stream into 'struct' mode. In this mode all CGI variables are transmitted in a single I/O. The "name=value" pairs must be explicitly parsed from the buffer. */ void CgiLibCgiPlusSetVarStruct () { /*********/ /* begin */ /*********/ if (CgiLib__Debug) fprintf (stdout, "CgiLibCgiPlusSetVarStruct()\n"); CgiLibCgiPlusESC (); fflush (stdout); fputs ("!CGIPLUS: struct\n", stdout); fflush (stdout); CgiLibCgiPlusEOT (); fflush (stdout); CgiLib__CgiPlusVarRecord = 0; CgiLib__CgiPlusVarStruct = 1; } /*****************************************************************************/ /*****************/ /* CGI VARIABLES */ /*****************/ /*****************************************************************************/ /* (jpp@esme.fr) Return a CGI variable by descriptor. Takes an optional third pointer to short int parameter to receive the string length short. */ int CgiLibVarByDesc ( struct dsc$descriptor *NameDsc, struct dsc$descriptor *ValueDsc, ... ) { int idx, argcnt; char *name, *value; unsigned short *slenptr; va_list argptr; /*********/ /* begin */ /*********/ va_count (argcnt); if (argcnt > 2) { va_start (argptr, ValueDsc); slenptr = (unsigned short*)va_arg (argptr, unsigned short*); } else slenptr = NULL; if (CgiLib__VeeMemInUse) name = CgiLibVeeMemCalloc (NameDsc->dsc$w_length+1); else name = calloc (1, NameDsc->dsc$w_length+1); if (!name) if (CgiLib__VeeMemContinue) return (0); else exit (vaxc$errno); for (idx = 0; idx < NameDsc->dsc$w_length; idx++) name[idx] = (NameDsc->dsc$a_pointer)[idx]; name[idx] = '\0'; value = CgiLibVarNull (name); if (CgiLib__VeeMemInUse) CgiLibVeeMemFree (name); else free (name); if (value) { for (idx = 0; idx < ValueDsc->dsc$w_length && value[idx]; idx++) (ValueDsc->dsc$a_pointer)[idx] = value[idx]; if (slenptr) *slenptr = (unsigned short)idx; if (ValueDsc->dsc$w_length - idx > 0) memset (ValueDsc->dsc$a_pointer+idx, ' ', ValueDsc->dsc$w_length-idx); return (1); } else { if (ValueDsc->dsc$w_length) memset (ValueDsc->dsc$a_pointer, ' ', ValueDsc->dsc$w_length); if (slenptr) *slenptr = 0; return (0); } } /*****************************************************************************/ /* Get a CGI variable from a DCL symbol or from the CGIplus stream. Returns a pointer to the string value if the CGI variables exists or a pointer to 'NonePtr' if not. Adjusts behaviour according to CGI or CGIplus environments and to whether symbols are prefixd with "WWW_" or not. */ char* CgiVar (char *VarName) { return (CgiLib__GetVar (VarName, CgiLib__VarNonePtr)); } char* CgiLibVar (char *VarName) { return (CgiLib__GetVar (VarName, CgiLib__VarNonePtr)); } char* CgiLibVarNull (char *VarName) { return (CgiLib__GetVar (VarName, NULL)); } char* CgiLibVeeMemVar (char *VarName) { char *cptr, *sptr; if (!CgiLib__VeeMemZoneId) return (NULL); cptr = CgiLib__GetVar (VarName, CgiLib__VarNonePtr); sptr = CgiLibVeeMemCalloc (strlen(cptr)); if (!sptr) if (CgiLib__VeeMemContinue) return (NULL); else exit (vaxc$errno); strcpy (sptr, cptr); if (sptr[0] && CgiLib__BodyIsMultipartFormData && (!strncmp (VarName, "form_", 5) || !strncmp (VarName, "FORM_", 5))) CgiLibHtmlDeEntify (sptr); return (sptr); } char* CgiLibVeeMemVarNull (char *VarName) { char *cptr, *sptr; if (!CgiLib__VeeMemZoneId) return (NULL); if (!(cptr = CgiLib__GetVar (VarName, NULL))) return (cptr); sptr = CgiLibVeeMemCalloc (strlen(cptr)); if (!sptr) if (CgiLib__VeeMemContinue) return (NULL); else exit (vaxc$errno); strcpy (sptr, cptr); if (sptr[0] && CgiLib__BodyIsMultipartFormData && (!strncmp (VarName, "form_", 5) || !strncmp (VarName, "FORM_", 5))) CgiLibHtmlDeEntify (sptr); return (sptr); } char* CgiLib__GetVar ( char *VarName, char *NonePtr ) { static int CgiPrefixChecked = 0, HttpMethodPost = 0, VarStructCallout = 0; static char *HttpMethodPtr = NULL; static char WwwVarName [256] = "WWW_"; char *cptr, *sptr; unsigned short Length; int VersionNumber; /*********/ /* begin */ /*********/ /** 15-AUG-2002 MGD This debug header *should* work! However ... it's as if any <stdout> from CGILIB after the end of the previous request is absorbed, whereas <stdout> from immediately following the CgiLibVar("") start-of-CGIplus-request isn't. This is most peculiar. I cannot resolve it and *suspect* something in the C-RTL (of course :^) The usage-counter shows that this function is definitely being called. The work-around is to put the debug content-type header <stdout> into the script proper. **/ /* provide debug response header (subsequent requests) */ if (CgiLib__Debug && CgiLib__Environment == CGILIB_ENVIRONMENT_CGIPLUS && VarName && !VarName[0] && CgiLib__CgiPlusUsageCount) fputs ( "Content-Type: text/plain\nScript-Control: X-content-encoding-gzip=0\n\n", stdout); if (CgiLib__Debug) fprintf (stdout, "CgiLib__GetVar() |%s|\n", VarName); if (!CgiLib__Environment) CgiLib__SetEnvironment (); if (CgiLib__Environment != CGILIB_ENVIRONMENT_CGIPLUS) { /***************************/ /* non-CGIplus environment */ /***************************/ if (VarName == NULL || !VarName[0]) { /* reinitialize */ CgiLib__VarList (NULL, NULL, NULL); if ((cptr = getenv("WWW_GATEWAY_SYMBOLS")) || (cptr = getenv("GATEWAY_SYMBOLS"))) { while (*cptr) { for (sptr = cptr; *sptr && *sptr != ','; sptr++); if (!*sptr) break; *sptr = '\0'; if (CgiLib__Debug) fprintf (stdout, "|%s|\n", cptr); CgiLib__VarList (cptr, getenv (cptr), NULL); *sptr++ = ','; cptr = sptr; } } return (NULL); } if (VarName != NULL && VarName[0] && VarName[0] != '*' && CgiLib__CgiPrefixWWW && memcmp (VarName, "WWW_", 4)) { /* variable name does not begin with "WWW_", make one that does */ strncpy (WwwVarName+4, VarName, sizeof(WwwVarName)-5); (VarName = WwwVarName)[sizeof(WwwVarName)-1] = '\0'; } else if (!CgiLib__CgiPrefixWWW && !memcmp (VarName, "WWW_", 4)) { /* variables do not begin with "WWW_" and this one does */ VarName += 4; } /* a POSTed body can be turned into _FORM_ variables */ if (VarName[0] == '*') return (CgiLib__VarList (VarName, NULL, NonePtr)); if (CgiLib__Environment == CGILIB_ENVIRONMENT_OSU) { /*******************/ /* OSU environment */ /*******************/ return (CgiLib__VarList (VarName, NULL, NonePtr)); } if (CgiLib__Environment == CGILIB_ENVIRONMENT_WASD) { /********************/ /* WASD environment */ /********************/ if ((cptr = getenv(VarName)) == NULL) { /* a POSTed body can be turned into _FORM_ variables */ return (CgiLib__VarList (VarName, NULL, NonePtr)); } else { if (CgiLib__Debug) fprintf (stdout, "|%s=%s|\n", VarName, cptr); return (cptr); } } /*************************************/ /* Purveyor, vanilla CGI environment */ /*************************************/ if (!strcmp (VarName, "WWW_PATH_TRANSLATED") || !strcmp (VarName, "PATH_TRANSLATED")) return (CgiLib__GetPathTranslated (NonePtr)); if (!strncmp (VarName, "WWW_KEY_", 8) || !strncmp (VarName, "KEY_", 4) || !strncmp (VarName, "WWW_FORM_", 9) || !strncmp (VarName, "FORM_", 5)) { if (HttpMethodPtr == NULL) { if (CgiLib__CgiPrefixWWW) HttpMethodPtr = getenv("WWW_REQUEST_METHOD"); else HttpMethodPtr = getenv("REQUEST_METHOD"); if (HttpMethodPtr != NULL && !strcmp (HttpMethodPtr, "POST")) HttpMethodPost = 1; else HttpMethodPost = 0; } if (HttpMethodPost) { /* a POSTed body can be turned into _FORM_ variables */ return (CgiLib__VarList (VarName, NULL, NonePtr)); } if (!strncmp (VarName, "WWW_KEY_", 8) || !strncmp (VarName, "KEY_", 4)) return (CgiLib__GetKey (VarName, NonePtr)); if (!strncmp (VarName, "WWW_FORM_", 9) || !strncmp (VarName, "FORM_", 5)) return (CgiLib__GetForm (VarName, NonePtr)); } if ((cptr = getenv(VarName)) == NULL) { if (CgiLib__Debug) fprintf (stdout, "|%s|=CGILIB_NONE\n", VarName); return (NonePtr); } else { if (CgiLib__Debug) fprintf (stdout, "|%s=%s|\n", VarName, cptr); return (cptr); } } /***********************/ /* CGIplus environment */ /***********************/ if (VarName == NULL || !VarName[0]) { /* end of request or waiting for next, reinitialize */ CgiLib__CgiPlusLoadVariables = 1; CgiLib__VarList (NULL, NULL, NULL); /* if just initializing then return now */ if (VarName == NULL) return (NULL); } if (CgiLib__CgiPlusLoadVariables) { /* read lines containing CGIplus variables from <CGIPLUSIN> */ int StructLength; char CgiPlusRecord [CGILIB_CGIPLUS_RECORD_SIZE]; CgiLib__CgiPlusUsageCount++; if (CgiLib__Debug) fprintf (stdout, "CGIplus usage: %d\n", CgiLib__CgiPlusUsageCount); if (CgiLib__CgiPlusInFile == NULL) if ((CgiLib__CgiPlusInFile = fopen (getenv("CGIPLUSIN"), "r", "ctx=rec")) == NULL) exit (vaxc$errno); CgiLib__CgiPlusLoadVariables = 0; /* get the starting sentinal record */ for (;;) { cptr = fgets (CgiPlusRecord, sizeof(CgiPlusRecord), CgiLib__CgiPlusInFile); if (cptr == NULL) exit (vaxc$errno); /* if the starting record is detected then break */ if (*(USHORTPTR)cptr == '!\0' || *(USHORTPTR)cptr == '!\n' || (*(USHORTPTR)cptr == '!!' && isdigit(*(cptr+2)))) break; } if (*(USHORTPTR)cptr == '!!') { /********************/ /* CGIplus 'struct' */ /********************/ /* CGIplus variables transmitted as 'struct'ure */ StructLength = atoi(cptr+2); if (StructLength <= 0 || StructLength > sizeof(CgiPlusRecord)) { fprintf (stdout, "%%CGILIB-E-STRUCT, CGIplus \'struct\' error\n"); exit (SS$_BUGCHECK | STS$M_INHIB_MSG); } if (!fread (CgiPlusRecord, 1, StructLength, CgiLib__CgiPlusInFile)) exit (vaxc$errno); /* explicitly parse the "name=value" pairs from the single I/O */ cptr = CgiPlusRecord; for (;;) { if (!(Length = *(USHORTPTR)cptr)) break; cptr += sizeof(short); CgiLib__VarList (cptr, "", NULL); cptr += Length; } } else { /*******************/ /* CGIplus records */ /*******************/ /* CGIplus variables transmitted in records */ while (fgets (CgiPlusRecord, sizeof(CgiPlusRecord), CgiLib__CgiPlusInFile) != NULL) { if (CgiLib__Debug) fprintf (stdout, "|%s|\n", CgiPlusRecord); /* first empty record (line) terminates variables */ if (CgiPlusRecord[0] == '\n') break; CgiLib__VarList (CgiPlusRecord, "", NULL); } } if (!CgiPrefixChecked) { /* must have the CGI variables beginning with or without "WWW_" */ CgiPrefixChecked = 1; cptr = CgiLib__VarList ("WWW_GATEWAY_INTERFACE", NULL, NULL); if (cptr == NULL) { cptr = CgiLib__VarList ("GATEWAY_INTERFACE", NULL, NULL); if (cptr == NULL) { fprintf (stdout, "%%CGILIB-E-CGI, no [WWW_]GATEWAY_INTERFACE\n"); exit (SS$_BUGCHECK | STS$M_INHIB_MSG); } CgiLib__CgiPrefixWWW = 0; } else CgiLib__CgiPrefixWWW = 1; } if (!(VarStructCallout || CgiLib__CgiPlusVarStruct || CgiLib__CgiPlusVarRecord)) { /* attempt to switch CGI variable 'record' to 'struct' mode */ VarStructCallout = 1; fflush (stdout); CgiLibCgiPlusESC (); fflush (stdout); /* the leading '!' means we won't need to read a response */ fputs ("!CGIPLUS: struct\n", stdout); fflush (stdout); CgiLibCgiPlusEOT (); fflush (stdout); } } /* if waiting for next request return now it's arrived */ if (!VarName[0]) return (NULL); if (VarName[0] != '*' && CgiLib__CgiPrefixWWW && memcmp (VarName, "WWW_", 4)) { /* variable name does not begin with "WWW_", make one that does */ strncpy (WwwVarName+4, VarName, sizeof(WwwVarName)-5); (VarName = WwwVarName)[sizeof(WwwVarName)-1] = '\0'; } else if (!CgiLib__CgiPrefixWWW && !memcmp (VarName, "WWW_", 4)) { /* variables do not begin with "WWW_" and this one does */ VarName += 4; } /* get a CGIplus variable */ return (CgiLib__VarList (VarName, NULL, NonePtr)); } /*****************************************************************************/ /* Set or get a CGI variable. Used to store CGIplus variables (read from CGIPLUSIN stream), OSU variables (created individually during dialog phase) and the pseudo-CGI-variables from a form-URL-encoded body. Implemented using a simple list in allocated memory. Each entry comprises a total-length-of-entry int, followed by a length-of-variable-name int, followed by a null-terminated string comprising the variable name, an equate symbol, and the variable value string. If 'VarValue' is NULL then get 'VarName', returning a pointer to it's string value, or if not found to CGILIB_NONE (usually ""). Successive calls with 'VarName' set to "*" (via CgiLibVar() of course) returns each of the list's entries in turn, a NULL indicating end-of-list. The list can be reset (for CGIplus use) with 'VarName' equal to NULL. If 'VarValue' is not NULL then store the value against the name (duplicated variables names result in duplicated entries, not overwriting!) */ char* CgiLib__VarList ( char *VarName, char *VarValue, char *NonePtr ) { static int ListNextOffset = 0, ListSize = 0; static char *NextPtr = NULL, *ListPtr = NULL; int LengthOffset, VarNameLength, VarNameLengthOffset, VarNameOffset; unsigned int Length; char *cptr, *sptr, *zptr, *LengthPtr; /*********/ /* begin */ /*********/ if (CGILIB_VARLIST_DEBUG) fprintf (stdout, "CgiLib__VarList() |%s|%s|%s|\n", VarName, VarValue, NonePtr); if (VarName == NULL) { /* initialize */ NextPtr = NULL; ListNextOffset = 0; return (NULL); } if (VarValue != NULL) { /******************************/ /* manipulate variable values */ /******************************/ if (VarName[0] == '!' && !strcmp (VarName, "!FORM_")) { /****************/ /* special case */ /****************/ /* Remove from access the current FORM_ variables, 'hiding' them by changing the variable name from 'FORM_' to '!ORM_'. This leaves the current values intact and any existing pointers still valid. A kludge to allow soyMAIL v1.3 to re-process POSTed content. */ cptr = ListPtr; for (;;) { if (!(Length = *((int*)(sptr = cptr)))) return (NULL); cptr = sptr + Length; sptr += sizeof(int) + sizeof(int); if (CGILIB_VARLIST_DEBUG) fprintf (stdout, "|%s|\n", sptr); if (!strncmp (sptr, "FORM_", 5)) *sptr = '!'; } return (NULL); } /******************/ /* add a variable */ /******************/ if (ListPtr == NULL) { /* always allow extra space (allows sloppier/faster code ;^) */ ListPtr = realloc (ListPtr, CGILIB_VARLIST_CHUNK+16); if (!ListPtr) exit (SS$_INSFMEM); ListSize = CGILIB_VARLIST_CHUNK; } /* point to next "[total-len][var-name-len]var-name=var-value" storage */ sptr = ListPtr + ListNextOffset; /* offset to where the total length is stored */ LengthOffset = sptr - ListPtr; /* offset to where the length of the variable name is stored */ VarNameLengthOffset = LengthOffset + sizeof(int); /* now point to the start of the "var-name=var-value" string */ VarNameOffset = VarNameLengthOffset + sizeof(int); /* so we don't over-run the list storage */ sptr = ListPtr + VarNameOffset; zptr = ListPtr + ListSize; /* CGIplus, stream has "name=value\n\0" */ cptr = VarName; while (*cptr && *(USHORTPTR)cptr != '\n\0') { while (*cptr && *(USHORTPTR)cptr != '\n\0' && sptr < zptr) { if (VarNameLengthOffset && *cptr == '=') { /* store the length of the CGIplus variable name */ *((int*)(ListPtr+VarNameLengthOffset)) = sptr - ListPtr - VarNameOffset; VarNameLengthOffset = 0; } *sptr++ = *cptr++; } if (VarNameLengthOffset && !*cptr) { /* store the length of the CGI variable name */ *((int*)(ListPtr+VarNameLengthOffset)) = sptr - ListPtr - VarNameOffset; VarNameLengthOffset = 0; *sptr++ = '='; cptr = VarValue; } if (sptr >= zptr) { /* out of storage, get more memory */ Length = sptr - ListPtr; ListPtr = realloc (ListPtr, ListSize+CGILIB_VARLIST_CHUNK+16); if (!ListPtr) exit (SS$_INSFMEM); ListSize += CGILIB_VARLIST_CHUNK; /* recalculate pointers */ sptr = ListPtr + Length; zptr = ListPtr + ListSize; } } /* terminate the "name=value" */ *sptr++ = '\0'; /* insert the length of this name=value pair immediately before it */ *((int*)(ListPtr+LengthOffset)) = sptr - ListPtr - LengthOffset; /* adjust the value of the current variable storage space used */ ListNextOffset = sptr - ListPtr; /* ensure the list is terminated with a zero length entry */ *((int*)(ListPtr+ListNextOffset)) = 0; if (CGILIB_VARLIST_DEBUG) { fprintf (stdout, "ListPtr: %d ListSize: %d ListNextOffset: %d\n", ListPtr, ListSize, ListNextOffset); sptr = ListPtr; for (sptr = ListPtr; Length = *((int*)sptr); sptr += Length) { fprintf (stdout, "%5d %2d |%s|\n", Length, *((int*)(sptr+sizeof(int))), sptr+sizeof(int)+sizeof(int)); } } return (NULL); } if (VarName[0] == '+') { fprintf(stdout,"<!-- %u %d %d\n", ListPtr, ListSize, ListNextOffset); CgiLibVeeMemShow(NULL); fprintf(stdout,"-->\n"); } if (ListPtr == NULL) { if (CgiLib__Debug) fprintf (stdout, "|%s|=CGILIB_NONE (empty list)\n", VarName); if (VarName[0] == '*') return (NULL); return (NonePtr); } if (VarName[0] == '*') { /*****************************************/ /* return each stored line one at a time */ /*****************************************/ if (NextPtr == NULL) NextPtr = ListPtr; for (;;) { if (!(Length = *((int*)(sptr = NextPtr)))) { if (CgiLib__Debug) fprintf (stdout, "|*|=NULL\n"); return (NextPtr = NULL); } NextPtr = sptr + Length; sptr += sizeof(int) + sizeof(int); if (CGILIB_VARLIST_DEBUG) fprintf (stdout, "|%s|\n", sptr); if (!VarName[1]) return (sptr); } return (NULL); } /*************************************************/ /* search variable strings for required variable */ /*************************************************/ VarNameLength = strlen(VarName); for (sptr = LengthPtr = ListPtr; Length = *((int*)sptr); sptr = LengthPtr = LengthPtr + Length) { if (CGILIB_VARLIST_DEBUG) fprintf (stdout, "%d %d\n", Length, *((int*)sptr+4)); /* step over the total-length int */ sptr += sizeof(int); if (*((int*)sptr) != VarNameLength) continue; /* step over the var-name-length int */ sptr += sizeof(int); /* simple comparison between supplied and in-string variable names */ cptr = VarName; if (CGILIB_VARLIST_DEBUG) fprintf (stdout, "|%s|%s|\n", cptr, sptr); while (*cptr && *sptr && *sptr != '=') { if (toupper(*cptr) != toupper(*sptr)) break; cptr++; sptr++; } if (*cptr || *sptr != '=') continue; /* found, return a pointer to the value */ if (CgiLib__Debug) fprintf (stdout, "|%s=%s|\n", VarName, sptr+1); return (sptr+1); } /* not found */ if (CgiLib__Debug) fprintf (stdout, "|%s|=CGILIB_NONE\n", VarName); return (NonePtr); } /*****************************************************************************/ /* For "standard" CGI environment (e.g. Netscape FastTrack) derive the "KEY_COUNT", "KEY_1" ... "KEY_n" variables from "QUERY_STRING". */ char* CgiLib__GetKey ( char *VarName, char *NonePtr ) { int KeyCount, VarKeyCount; char *aptr, *cptr, *sptr; /*********/ /* begin */ /*********/ if (CgiLib__Debug) fprintf (stdout, "CgiLib__GetKey() |%s|\n", VarName); if (!VarName[0]) return (NonePtr); /* step over the "WWW_" */ if (CgiLib__CgiPrefixWWW) VarName += 4; if ((cptr = CgiLib__QueryStringPtr) == NULL) { if (CgiLib__CgiPrefixWWW) CgiLib__QueryStringPtr = cptr = getenv ("WWW_QUERY_STRING"); else CgiLib__QueryStringPtr = cptr = getenv ("QUERY_STRING"); if (CgiLib__Debug) fprintf (stdout, "query_string |%s|\n", cptr); if (cptr == NULL) return (NonePtr); } if (!strcmp (VarName, "KEY_COUNT")) { KeyCount = 0; for (sptr = cptr; *sptr; sptr++) { if (*sptr == '=') return (NonePtr); if (*sptr == '+') KeyCount++; } if (sptr == cptr) return ("0"); KeyCount++; if (CgiLib__VeeMemInUse) aptr = CgiLibVeeMemCalloc (16); else aptr = calloc (1, 16); if (aptr == NULL) if (CgiLib__VeeMemContinue) return (NonePtr); else exit (vaxc$errno); sprintf (aptr, "%d", KeyCount); if (CgiLib__Debug) fprintf (stdout, "|%s=%s|\n", VarName, aptr); return (aptr); } VarKeyCount = atoi (VarName+4); if (CgiLib__Debug) fprintf (stdout, "VarKeyCount: %d\n", VarKeyCount); KeyCount = 1; while (*cptr && VarKeyCount) { if (CgiLib__Debug) fprintf (stdout, "|%s|\n", cptr); sptr = cptr; while (*cptr && *cptr != '+') { if (*cptr == '=') return (NonePtr); cptr++; } if (KeyCount == VarKeyCount) { if (CgiLib__VeeMemInUse) aptr = CgiLibVeeMemCalloc (cptr - sptr + 1); else aptr = calloc (1, cptr - sptr + 1); if (aptr == NULL) if (CgiLib__VeeMemContinue) return (NonePtr); else exit (vaxc$errno); memcpy (aptr, sptr, cptr-sptr); aptr[cptr-sptr] = '\0'; CgiLibUrlDecode (aptr); if (CgiLib__Debug) fprintf (stdout, "|%s=%s|\n", VarName, aptr); return (aptr); } if (*cptr) cptr++; KeyCount++; } if (CgiLib__Debug) fprintf (stdout, "|%s|=CGILIB_NONE\n", VarName); return (NonePtr); } /*****************************************************************************/ /* For "standard" CGI environment (e.g. Netscape FastTrack, Purveyor, Apache) derive the "FORM_xxxxx" variables from "QUERY_STRING". */ char* CgiLib__GetForm ( char *VarName, char *NonePtr ) { char *aptr, *cptr, *sptr; /*********/ /* begin */ /*********/ if (CgiLib__Debug) fprintf (stdout, "CgiLib__GetForm() |%s|\n", VarName); if (!VarName[0]) return (NonePtr); /* step over the "WWW_" (as necessary) and the "FORM_" */ if (!strncmp (VarName, "WWW_", 4)) VarName += 4; VarName += 5; if ((cptr = CgiLib__QueryStringPtr) == NULL) { if (CgiLib__CgiPrefixWWW) CgiLib__QueryStringPtr = cptr = getenv ("WWW_QUERY_STRING"); else CgiLib__QueryStringPtr = cptr = getenv ("QUERY_STRING"); if (CgiLib__Debug) fprintf (stdout, "|%s|\n", cptr); if (cptr == NULL) return (NonePtr); } while (*cptr) { sptr = cptr; while (*cptr && *cptr != '=') cptr++; if (!*cptr) return (NonePtr); *cptr = '\0'; aptr = VarName; while (*aptr && *sptr && toupper(*aptr) == toupper(*sptr)) { aptr++; sptr++; } if (*aptr || *sptr) { *cptr++ = '='; while (*cptr && *cptr != '&') cptr++; if (*cptr) cptr++; continue; } *cptr++ = '='; sptr = cptr; while (*cptr && *cptr != '&') cptr++; if (CgiLib__VeeMemInUse) aptr = CgiLibVeeMemCalloc (cptr - sptr + 1); else aptr = calloc (1, cptr - sptr + 1); if (aptr == NULL) if (CgiLib__VeeMemContinue) return (NonePtr); else exit (vaxc$errno); memcpy (aptr, sptr, cptr-sptr); aptr[cptr-sptr] = '\0'; CgiLibUrlDecode (aptr); if (CgiLib__Debug) fprintf (stdout, "|%s=%s|\n", VarName, aptr); return (aptr); } if (CgiLib__Debug) fprintf (stdout, "|%s|=CGILIB_NONE\n", VarName); return (NonePtr); } /*****************************************************************************/ /* For vanilla CGI environment (e.g. Apache, OSU) get the "PATH_TRANSLATED" variable and check if it's in Unix-style or VMS-style format. Convert to a VMS specification as necessary. */ char* CgiLib__GetPathTranslated (char *NonePtr) { int scnt, Length; char *aptr, *cptr, *sptr, *zptr; char PathTranslated [256]; /*********/ /* begin */ /*********/ if (CgiLib__Debug) fprintf (stdout, "CgiLib__GetPathTranslated()\n"); if (CgiLib__CgiPrefixWWW) cptr = aptr = getenv ("WWW_PATH_TRANSLATED"); else cptr = aptr = getenv ("PATH_TRANSLATED"); if (cptr == NULL) return (NonePtr); if (*cptr == '/') { /* unix-style specification */ scnt = 0; for (aptr = cptr; *aptr; aptr++) if (*aptr == '/') scnt++; zptr = (sptr = PathTranslated) + sizeof(PathTranslated)-1; cptr++; if (scnt == 1) { /* just a filename, e.g. "/file.txt" */ while (*cptr && sptr < zptr) *sptr++ = *cptr++; } else if (scnt == 2) { /* logical plus file name, e.g. "/this/file.txt" */ while (*cptr && *cptr != '/' && sptr < zptr) *sptr++ = *cptr++; if (sptr < zptr) *sptr++ = ':'; if (*cptr) cptr++; while (*cptr && sptr < zptr) *sptr++ = *cptr++; } else { /* full file spec, e.g. "/device/dir1/file.txt" */ while (*cptr && *cptr != '/' && sptr < zptr) *sptr++ = *cptr++; if (*cptr) cptr++; if (sptr < zptr) *sptr++ = ':'; if (sptr < zptr) *sptr++ = '['; while (*cptr && *cptr != '/' && sptr < zptr) *sptr++ = *cptr++; if (*cptr) cptr++; for (scnt -= 3; scnt; scnt--) { if (sptr < zptr) *sptr++ = '.'; while (*cptr && *cptr != '/' && sptr < zptr) *sptr++ = *cptr++; if (*cptr) cptr++; } if (sptr < zptr) *sptr++ = ']'; while (*cptr && sptr < zptr) *sptr++ = *cptr++; } *sptr = '\0'; if (CgiLib__VeeMemInUse) aptr = CgiLibVeeMemCalloc (Length = (sptr - PathTranslated) + 1); else aptr = calloc (1, Length = (sptr - PathTranslated) + 1); if (aptr == NULL) if (CgiLib__VeeMemContinue) return (NULL); else exit (vaxc$errno); strcpy (aptr, PathTranslated); } if (CgiLib__Debug) fprintf (stdout, "|PATH_TRANSLATED=%s|\n", aptr); return (aptr); } /*****************************************************************************/ /****************/ /* OSU SPECIFIC */ /****************/ /*****************************************************************************/ /* Initialize the OSU variable list using the request network dialog phase. */ void CgiLibOsuInit ( int argc, char *argv[] ) { int ContentLengthSet, ContentTypeSet, IpAddress, KeyCount; char *cptr, *sptr, *zptr; char PathInfo [512], PathTranslated [512], Scratch [256], ScriptName [256], SymbolName [256]; struct hostent *HostEntryPtr; /*********/ /* begin */ /*********/ if (argc < 4) { fprintf (stdout, "%%CGILIB-E-OSU, P1=<method> P2=<path> P3=<protocol>\n"); exit (SS$_INSFARG | STS$M_INHIB_MSG); } if (CgiLib__Debug) { /* let's have a look at what's available! */ fprintf (stdout, "CgiLibOsuInit()\n----------\n"); CgiLib__OsuQueryNetLink ("<DNETARG>", 1); CgiLib__OsuQueryNetLink ("<DNETARG2>", 1); CgiLib__OsuQueryNetLink ("<DNETHOST>", 1); CgiLib__OsuQueryNetLink ("<DNETID>", 1); CgiLib__OsuQueryNetLink ("<DNETID2>", 1); CgiLib__OsuQueryNetLink ("<DNETRQURL>", 1); CgiLib__OsuQueryNetLink ("<DNETBINDIR>", 1); CgiLib__OsuQueryNetLink ("<DNETPATH>", 1); CgiLib__OsuQueryNetLink ("<DNETHDR>", 999); fprintf (stdout, "----------\n"); } /**********/ /* basics */ /**********/ CgiLib__VarList ("WWW_GATEWAY_INTERFACE", "CGI/1.1", NULL); CgiLib__VarList ("WWW_REQUEST_METHOD", argv[1], NULL); CgiLib__VarList ("WWW_SERVER_PROTOCOL", argv[3], NULL); /********************************************************/ /* SCRIPT_NAME, PATH_INFO, PATH_TRANSLATED, REQUEST_URI */ /********************************************************/ strcpy (sptr = ScriptName, CgiLib__OsuQueryNetLink ("<DNETPATH>", 1)); cptr = CgiLib__OsuQueryNetLink ("<DNETRQURL>", 1); CgiLib__VarList ("WWW_REQUEST_URI", cptr, NULL); while (*cptr && *cptr && *cptr == *sptr) { cptr++; sptr++; } while (*cptr && *cptr != '/' && *cptr != '?') *sptr++ = *cptr++; *sptr = '\0'; CgiLibUrlDecode (ScriptName); CgiLib__VarList ("WWW_SCRIPT_NAME", ScriptName, NULL); /* continue to get the path information from the request URL */ sptr = PathInfo; while (*cptr && *cptr != '?') *sptr++ = *cptr++; *sptr = '\0'; CgiLibUrlDecode (PathInfo); if (*PathInfo && *(USHORTPTR)PathInfo != '/\0') { CgiLib__OsuQueryNetLink ("<DNETXLATE>", 0); sptr = CgiLib__OsuQueryNetLink (PathInfo, 1); cptr = PathTranslated; if (*sptr == '/') sptr++; while (*sptr && *sptr != '/') *cptr++ = toupper(*sptr++); if (*sptr) { sptr++; *cptr++ = ':'; *cptr++ = '['; } while (*sptr) *cptr++ = toupper(*sptr++); *cptr-- = '\0'; while (cptr > PathTranslated && *cptr != '/') cptr--; if (*cptr == '/') { *cptr-- = ']'; while (cptr >= PathTranslated) { if (*cptr == '/') *cptr = '.'; if (*(ULONGPTR)cptr == '....') memmove (cptr, cptr+1, strlen(cptr)); cptr--; } } CgiLib__VarList ("WWW_PATH_INFO", PathInfo, NULL); CgiLib__VarList ("WWW_PATH_TRANSLATED", PathTranslated, NULL); } else { CgiLib__VarList ("WWW_PATH_INFO", "/", NULL); CgiLib__VarList ("WWW_PATH_TRANSLATED", "", NULL); } /***********************************/ /* QUERY_STRING, FORM_..., KEY_... */ /***********************************/ cptr = CgiLib__OsuQueryNetLink ("<DNETARG>", 1); if (*cptr == '?') cptr++; CgiLib__VarList ("WWW_QUERY_STRING", cptr, NULL); KeyCount = 0; sptr = cptr; while (*cptr && *cptr != '=') cptr++; if (*cptr) { /* FORM-formatted query string */ while (*cptr) { *cptr++ = '\0'; sprintf (SymbolName, "WWW_FORM_%s", sptr); sptr = cptr; while (*cptr && *cptr != '&') cptr++; if (*cptr) *cptr++ = '\0'; CgiLibUrlDecode (SymbolName); CgiLibUrlDecode (sptr); CgiLib__VarList (SymbolName, sptr, NULL); sptr = cptr; while (*cptr && *cptr != '=') cptr++; } } else { /* ISQUERY-formatted query string */ cptr = sptr; while (*cptr && *cptr != '+') cptr++; while (*sptr) { *cptr++ = '\0'; sprintf (SymbolName, "WWW_KEY_%d", ++KeyCount); CgiLibUrlDecode (sptr); CgiLib__VarList (SymbolName, sptr, NULL); sptr = cptr; while (*cptr && *cptr != '+') cptr++; } } sprintf (Scratch, "%d", KeyCount); CgiLib__VarList ("WWW_KEY_COUNT", Scratch, NULL); /**********************************************/ /* SERVER_SOFTWARE, SERVER_NAME, SERVER_PORT, */ /* REMOTE_HOST, REMOTE_ADDR, REMOTE_USER */ /**********************************************/ cptr = sptr = CgiLib__OsuQueryNetLink ("<DNETID2>", 1); while (*cptr && !isspace(*cptr)) cptr++; if (*cptr) *cptr++ = '\0'; CgiLib__VarList ("WWW_SERVER_SOFTWARE", sptr, NULL); sptr = cptr; while (*cptr && !isspace(*cptr)) cptr++; if (*cptr) *cptr++ = '\0'; CgiLib__VarList ("WWW_SERVER_NAME", sptr, NULL); /* just derive the absent server address from the host address */ if (HostEntryPtr = gethostbyname (sptr)) { memcpy (&IpAddress, HostEntryPtr->h_addr, 4); sprintf (Scratch, "%d.%d.%d.%d", *(unsigned char*)&IpAddress, *((unsigned char*)(&IpAddress)+1), *((unsigned char*)(&IpAddress)+2), *((unsigned char*)(&IpAddress)+3)); CgiLib__VarList ("WWW_SERVER_ADDR", Scratch, NULL); } sptr = cptr; while (*cptr && !isspace(*cptr)) cptr++; if (*cptr) *cptr++ = '\0'; CgiLib__VarList ("WWW_SERVER_PORT", sptr, NULL); /* skip over client port */ while (*cptr && !isspace(*cptr)) cptr++; if (*cptr) *cptr++ = '\0'; /* host address */ sptr = cptr; while (*cptr && !isspace(*cptr)) cptr++; if (*cptr) *cptr++ = '\0'; for (zptr = sptr; *zptr && *zptr != '.'; zptr++); if (*zptr != '.') { IpAddress = atoi(sptr); sprintf (Scratch, "%d.%d.%d.%d", *(unsigned char*)&IpAddress, *((unsigned char*)(&IpAddress)+1), *((unsigned char*)(&IpAddress)+2), *((unsigned char*)(&IpAddress)+3)); sptr = Scratch; } CgiLib__VarList ("WWW_REMOTE_ADDR", sptr, NULL); CgiLib__VarList ("WWW_REMOTE_HOST", sptr, NULL); sptr = cptr; while (*cptr && !isspace(*cptr)) cptr++; if (*cptr) *cptr++ = '\0'; CgiLib__VarList ("WWW_REMOTE_USER", sptr, NULL); sptr = cptr; while (*cptr && !isspace(*cptr)) cptr++; if (*cptr) { /* DNS-resolved host name */ *cptr++ = '\0'; CgiLib__VarList ("WWW_REMOTE_HOST", sptr, NULL); } /******************************************/ /* CONTENT_LENGTH, CONTENT_TYPE, HTTP_... */ /******************************************/ ContentLengthSet = ContentTypeSet = 0; cptr = sptr = CgiLib__OsuQueryNetLink ("<DNETHDR>", 999); while (*cptr) { while (*cptr && *cptr != ':') { if (isalnum(*cptr)) *cptr = toupper(*cptr); else *cptr = '_'; cptr++; } if (*cptr) { *cptr++ = '\0'; sprintf (SymbolName, "WWW_HTTP_%s", sptr); while (*cptr && isspace(*cptr) && *cptr != '\n') *cptr++; sptr = cptr; while (*cptr && *cptr != '\n') *cptr++; if (*cptr) *cptr++ = '\0'; /** 26-MAR-2007 CgiLibUrlDecode (SymbolName); CgiLibUrlDecode (sptr); **/ if (!strcmp (SymbolName+9, "CONTENT_TYPE")) { ContentTypeSet = 1; CgiLib__VarList ("WWW_CONTENT_TYPE", sptr, NULL); } else if (!strcmp (SymbolName+9, "CONTENT_LENGTH")) { ContentLengthSet = 1; CgiLib__VarList ("WWW_CONTENT_LENGTH", sptr, NULL); } else CgiLib__VarList (SymbolName, sptr, NULL); sptr = cptr; } } if (!ContentTypeSet) CgiLib__VarList ("WWW_CONTENT_TYPE", "", NULL); if (!ContentLengthSet) CgiLib__VarList ("WWW_CONTENT_LENGTH", "0", NULL); } /*****************************************************************************/ /* Reopen <stdout> to NET_LINK: in binary mode. This is much more efficient as the CRTL buffers individual prints into a stream. Output an appropriate output-phase tag and establish an exit function to complete the output dialog. */ void CgiLibOsuStdoutCgi () { /*********/ /* begin */ /*********/ if (CgiLib__Debug) fprintf (stdout, "CgiLibOsuStdoutCgi()\n"); /* reopen so that '\r' and '\n' are not filtered, maximum record size */ if ((stdout = freopen ("NET_LINK:", "w", stdout, "ctx=bin", "mrs=4096")) == NULL) exit (vaxc$errno); if (CgiLib__Debug) { fputs ("<DNETTEXT>", stdout); fflush(stdout); fputs ("200 Debug", stdout); } else fputs ("<DNETCGI>", stdout); fflush(stdout); atexit (&CgiLib__OsuExitCgi); } void CgiLib__OsuExitCgi () { if (CgiLib__Debug) fprintf (stdout, "CgiLib__OsuExitCgi()\n"); fflush(stdout); if (CgiLib__Debug) fputs ("</DNETTEXT>", stdout); else fputs ("</DNETCGI>", stdout); fflush(stdout); } /*****************************************************************************/ /* Reopen <stdout> to NET_LINK: in binary mode. This is much more efficient as the CRTL buffers individual prints into a stream. Output an appropriate output-phase tag and establish an exit function to complete the output dialog. */ void CgiLibOsuStdoutRaw () { /*********/ /* begin */ /*********/ if (CgiLib__Debug) fprintf (stdout, "CgiLibOsuStdoutRaw()\n"); /* reopen so that '\r' and '\n' are not filtered, maximum record size */ if ((stdout = freopen ("NET_LINK:", "w", stdout, "ctx=bin", "mrs=4096")) == NULL) exit (vaxc$errno); if (CgiLib__Debug) { fputs ("<DNETTEXT>", stdout); fflush(stdout); fputs ("200 Debug", stdout); } else fputs ("<DNETRAW>", stdout); fflush (stdout); atexit (&CgiLib__OsuExitRaw); } void CgiLib__OsuExitRaw () { if (CgiLib__Debug) fprintf (stdout, "CgiLib__OsuExitRaw()\n"); fflush (stdout); if (CgiLib__Debug) fputs ("</DNETTEXT>", stdout); else fputs ("</DNETRAW>", stdout); fflush (stdout); } /*****************************************************************************/ /* Write a single record to the OSU network link, then if expecting a response wait for one or more records to be returned. A single line response has no carriage control. A multi-line response has each record concatenated into a single string of newline-separated records. Returns pointer to static storage! Open files to the link if first call. */ char* CgiLib__OsuQueryNetLink ( char *Query, int ResponseLinesExpected ) { static char Response [4096]; static FILE *NetLinkRead, *NetLinkWrite; int status; char *cptr; /*********/ /* begin */ /*********/ if (CgiLib__Debug) fprintf (stdout, "CgiLib__OsuQueryNetLink() |%s|\n", Query); if (NetLinkRead == NULL) { /*************************/ /* first call, open link */ /*************************/ if ((NetLinkRead = fopen ("NET_LINK:", "r", "ctx=rec")) == NULL) { status = vaxc$errno; fprintf (stdout, "%%CGILIB-E-BODYREAD, read open error\n"); exit (status); } if ((NetLinkWrite = fopen ("NET_LINK:", "w", "ctx=bin")) == NULL) { status = vaxc$errno; fprintf (stdout, "%%CGILIB-E-BODYREAD, write open error\n"); exit (status); } } if (Query != NULL) { /*********/ /* write */ /*********/ if (fputs (Query, NetLinkWrite) == EOF) { status = vaxc$errno; fprintf (stdout, "%%CGILIB-E-BODYREAD, write error\n"); exit (status); } fflush (NetLinkWrite); } if (ResponseLinesExpected > 1) { /**************************/ /* multiple line response */ /**************************/ cptr = Response; for (;;) { if (fgets (cptr, sizeof(Response)-(cptr-Response), NetLinkRead) == NULL) { status = vaxc$errno; fprintf (stdout, "%%CGILIB-E-BODYREAD, read error\n"); exit (status); } /** if (CgiLib__Debug) fprintf (stdout, "cptr |%s|\n", cptr); **/ if (*cptr != '\n') { while (*cptr) cptr++; continue; } *cptr = '\0'; break; } } else if (ResponseLinesExpected > 0) { /************************/ /* single line response */ /************************/ if (fgets (Response, sizeof(Response), NetLinkRead) == NULL) { status = vaxc$errno; fprintf (stdout, "%%CGILIB-E-BODYREAD, read error\n"); exit (status); } for (cptr = Response; *cptr; cptr++); if (cptr > Response) { if (*--cptr == '\n') *cptr = '\0'; else cptr++; } } else *(cptr = Response) = '\0'; if (CgiLib__Debug) fprintf (stdout, "%d |%s|\n", cptr-Response, Response); return (Response); } /*****************************************************************************/ /***************************/ /* REQUEST BODY PROCESSING */ /***************************/ /*****************************************************************************/ /* Read a POSTed request body into a single array of char (doesn't matter whether it's text or binary). Returns NULL if it's not a POST, a pointer if it is. The caller can free() this memory when finished with it or it is implicitly freed when called again (i.e. CGIplus). Note: the OSU DECnet scripting stream is NET_LINK:, the WASD CGI DECnet scripting stream is NET$LINK, the two should not be confused. Will processes an RFC2068 (HTTP/1.1) "Transfer-Encoding: chunked" request body from 'chunked' format into a binary bag of bytes. */ char* CgiLibReadRequestBody ( char **BodyPtrPtr, int *BodyLengthPtr ) { static int BodyReadFd = 0, BufferCount = 0, InitializeEnvironment = 1, PrevUsageCount; static char *BufferPtr = NULL; int status, BufferSize, ChunkLength, ContentLength, ContentUnEncoded, EncodingChunked, ReadCount; char *cptr, *sptr, *zptr, *RequestTimeGmtPtr, *ServerSoftwarePtr; char ChunkString [32]; /*********/ /* begin */ /*********/ if (CgiLib__Debug) fprintf (stdout, "CgiLibReadRequestBody()\n"); if (InitializeEnvironment) { InitializeEnvironment = 0; if (!CgiLib__Environment) CgiLib__SetEnvironment (); cptr = CgiLib__GetVar ("WWW_REQUEST_METHOD", ""); if (strcmp (cptr, "POST")) { *BodyPtrPtr = NULL; *BodyLengthPtr = 0; if (CgiLib__Debug) fprintf (stdout, "%d %d\n", *BodyLengthPtr, *BodyPtrPtr); return (NULL); } if (CgiLib__Environment == CGILIB_ENVIRONMENT_WASD) { if (getenv ("NET$LINK") != NULL) BodyReadFd = open ("NET$LINK:", O_RDONLY, 0, "ctx=bin"); else BodyReadFd = open ("HTTP$INPUT:", O_RDONLY, 0, "ctx=bin"); } else if (CgiLib__Environment == CGILIB_ENVIRONMENT_APACHE) BodyReadFd = open ("APACHE$INPUT:", O_RDONLY, 0, "ctx=bin"); else if (CgiLib__Environment == CGILIB_ENVIRONMENT_CGIPLUS) BodyReadFd = open ("HTTP$INPUT:", O_RDONLY, 0, "ctx=bin"); else if (CgiLib__Environment == CGILIB_ENVIRONMENT_OSU) BodyReadFd = open ("NET_LINK:", O_RDONLY, 0, "ctx=bin"); else /* vanilla CGI environment */ if (getenv ("WWW_IN") != NULL) { /* Purveyor, Cern? */ BodyReadFd = open ("WWW_IN:", O_RDONLY, 0, "ctx=bin"); } else BodyReadFd = open ("SYS$INPUT:", O_RDONLY, 0, "ctx=bin"); if (BodyReadFd == -1) { status = vaxc$errno; fprintf (stdout, "%%CGILIB-E-BODYREAD, binary read open error\n"); exit (status); } } else /* if not CGIplus or if multiple calls in the one CGIplus request */ if (CgiLib__Environment != CGILIB_ENVIRONMENT_CGIPLUS || CgiLib__CgiPlusUsageCount == PrevUsageCount) { /* body has already been read by call from CgiLibEnvironmentInit() */ *BodyPtrPtr = BufferPtr; *BodyLengthPtr = BufferCount; if (CgiLib__Debug) fprintf (stdout, "%d %d\n", *BodyLengthPtr, *BodyPtrPtr); return (BufferPtr); } PrevUsageCount = CgiLib__CgiPlusUsageCount; if (BufferPtr) { /* if called again (i.e. CGIplus) */ free (BufferPtr); BufferPtr = NULL; } cptr = CgiLib__GetVar ("WWW_CONTENT_TYPE", ""); CgiLib__BodyIsFormUrlEncoded = CgiLib__BodyIsMultipartFormData = 0; if (!strncmp (cptr, "application/x-www-form-urlencoded", 33)) CgiLib__BodyIsFormUrlEncoded = 1; else if (!strncmp (cptr, "multipart/form-data;", 20)) CgiLib__BodyIsMultipartFormData = 1; cptr = CgiLib__GetVar ("WWW_CONTENT_LENGTH", ""); if (*cptr == '?') { ContentUnEncoded = 1; ContentLength = 0; } else { ContentUnEncoded = 0; ContentLength = atoi(cptr); } if (CgiLib__Debug) fprintf (stdout, "ContentLength: %d ContentUnEncoded: %d\n", ContentLength, ContentUnEncoded); if (ContentUnEncoded) EncodingChunked = 0; else { cptr = CgiLib__GetVar ("WWW_HTTP_TRANSFER_ENCODING", ""); if (cptr[0] && strstr (cptr, "chunked")) EncodingChunked = 1; else EncodingChunked = 0; } if ((CgiLib__Environment == CGILIB_ENVIRONMENT_CGIPLUS || CgiLib__Environment == CGILIB_ENVIRONMENT_WASD) && CgiLibHttpProtocolVersion() == 11 && !CgiLib__ResponseCount) { cptr = CgiLib__GetVar ("WWW_HTTP_EXPECT", ""); if (cptr[0] && strstr (cptr, "100-continue") || strstr (cptr, "100-Continue")) { /* supply a "100 Continue" interim response header */ RequestTimeGmtPtr = CgiLib__GetVar ("REQUEST_TIME_GMT", NULL); ServerSoftwarePtr = CgiLib__GetVar ("SERVER_SOFTWARE", ""); fprintf (stdout, "%s 100 %s\n\ Server: %s\n\ %s%s%s\ \n", CgiLib__HttpProtocolVersionPtr, CgiLibHttpStatusCodeText(100), ServerSoftwarePtr, RequestTimeGmtPtr == NULL ? "" : "Date: ", RequestTimeGmtPtr == NULL ? "" : RequestTimeGmtPtr, RequestTimeGmtPtr == NULL ? "" : "\n"); fflush(stdout); } } if (EncodingChunked) { /************************/ /* chunked request body */ /************************/ BufferPtr = NULL; BufferSize = 0; for (;;) { /* read one character at a time until the size string is available */ for (zptr=(sptr=ChunkString)+sizeof(ChunkString); sptr < zptr; sptr++) { if (ReadCount = read (BodyReadFd, sptr, 1) < 0) { status = vaxc$errno; fprintf (stdout, "%%CGILIB-E-CHUNK, binary read error\n"); exit (status); } if (!isxdigit(*sptr)) break; } if (sptr >= zptr) { fprintf (stdout, "%%CGILIB-E-CHUNK, size overflow\n"); exit (SS$_BUGCHECK); } *sptr = '\0'; if (!ChunkString[0]) { fprintf (stdout, "%%CGILIB-E-CHUNK, no size\n"); exit (SS$_BUGCHECK); } ChunkLength = strtol (ChunkString, NULL, 16); if (CgiLib__Debug) fprintf (stdout, "|%s| %d\n", ChunkString, ChunkLength); /* zero length chunk, end of chunked body */ if (!ChunkLength) break; /* skip to the end of the chunk size line (one character at a time) */ for (;;) { ChunkString[0] = '\0'; if (read (BodyReadFd, ChunkString, 1) < 0) break; if (ChunkString[0] == '\n') break; } if (ChunkString[0] != '\n') { fprintf (stdout, "%%CGILIB-E-CHUNK, format error\n"); exit (SS$_BUGCHECK); } if (CgiLib__VeeMemInUse) BufferPtr = CgiLibVeeMemRealloc (BufferPtr, BufferSize+ChunkLength+1); else BufferPtr = realloc (BufferPtr, BufferSize+ChunkLength+1); if (!BufferPtr) if (CgiLib__VeeMemContinue) return (NULL); else exit (vaxc$errno); BufferSize += ChunkLength; while (BufferCount < BufferSize) { if (CgiLib__Environment == CGILIB_ENVIRONMENT_OSU) CgiLib__OsuQueryNetLink ("<DNETINPUT>", 0); ReadCount = read (BodyReadFd, BufferPtr+BufferCount, BufferSize-BufferCount); if (CgiLib__Debug) fprintf (stdout, "ReadCount: %d\n", ReadCount); if (!ReadCount) break; if (ReadCount == -1) { status = vaxc$errno; fprintf (stdout, "%%CGILIB-E-CHUNK, binary read error\n"); exit (status); } BufferCount += ReadCount; } /* skip over the trailing <CR><LF> (one character at a time) */ for (;;) { ChunkString[0] = '\0'; if (read (BodyReadFd, ChunkString, 1) < 0) break; if (ChunkString[0] == '\n') break; } if (ChunkString[0] != '\n') { fprintf (stdout, "%%CGILIB-E-CHUNK, format error\n"); exit (SS$_BUGCHECK); } } } else { /**************************/ /* unchunked request body */ /**************************/ /* an ambit quantity (it doubles each time it's about the overflow) */ if (ContentUnEncoded) ContentLength = 8192; if (!ContentLength) { *BodyPtrPtr = NULL; *BodyLengthPtr = 0; return (NULL); } if (CgiLib__VeeMemInUse) BufferPtr = CgiLibVeeMemCalloc (ContentLength+1); else BufferPtr = calloc (1, ContentLength+1); if (!BufferPtr) if (CgiLib__VeeMemContinue) return (NULL); else exit (vaxc$errno); BufferSize = ContentLength; BufferCount = 0; while (BufferCount < BufferSize) { if (CgiLib__Environment == CGILIB_ENVIRONMENT_OSU) CgiLib__OsuQueryNetLink ("<DNETINPUT>", 0); ReadCount = read (BodyReadFd, BufferPtr+BufferCount, BufferSize-BufferCount); if (CgiLib__Debug) fprintf (stdout, "ReadCount: %d ContentLength: %d\n", ReadCount, ContentLength); if (!ReadCount) break; if (ReadCount == -1) { status = vaxc$errno; fprintf (stdout, "%%CGILIB-E-BODYREAD, binary read error\n"); exit (status); } BufferCount += ReadCount; if (ContentUnEncoded) { if (BufferCount >= BufferSize) { ContentLength *= 2; if (CgiLib__VeeMemInUse) BufferPtr = CgiLibVeeMemRealloc (BufferPtr, ContentLength+1); else BufferPtr = realloc (BufferPtr, ContentLength+1); if (!BufferPtr) if (CgiLib__VeeMemContinue) return (NULL); else exit (vaxc$errno); BufferSize = ContentLength; } } else if (BufferCount == ContentLength) break; } } if (CgiLib__Debug) fprintf (stdout, "size: %d count: %d\n", BufferSize, BufferCount); /* terminate with a null in case it's text (not counted in 'BufferCount') */ BufferPtr[BufferCount] = '\0'; /** if (CgiLib__Debug) fprintf (stdout, "|%s|\n", BufferPtr); **/ *BodyPtrPtr = BufferPtr; *BodyLengthPtr = BufferCount; if (CgiLib__Debug) fprintf (stdout, "%d %d\n", *BodyLengthPtr, *BodyPtrPtr); return (BufferPtr); } /*****************************************************************************/ /* Return true if the body of the request is "application/x-www-form-urlencoded". */ int CgiLibBodyIsFormUrlEncoded () { /*********/ /* begin */ /*********/ if (CgiLib__Debug) fprintf (stdout, "CgiLibBodyIsFormUrlEncoded() %d\n", CgiLib__BodyIsFormUrlEncoded); return (CgiLib__BodyIsFormUrlEncoded); } /*****************************************************************************/ /* Return true if the body of the request is "multipart/form-data". */ int CgiLibBodyIsMultipartFormData () { /*********/ /* begin */ /*********/ if (CgiLib__Debug) fprintf (stdout, "CgiLibBodyIsMultipartFormData() %d\n", CgiLib__BodyIsMultipartFormData); return (CgiLib__BodyIsMultipartFormData); } /*****************************************************************************/ /* Converts the POSTed body parameter into "WWW_FORM_field-name" CGI variables accessable from the variable list. Field names are limited to 255 characters, after which they are truncated. */ int CgiLibFormRequestBody ( char *BodyPtr, int BodyLength ) { char *cptr; /*********/ /* begin */ /*********/ if (CgiLib__Debug) fprintf (stdout, "CgiLibFormRequestBody()\n"); if (!BodyLength) return (0); cptr = CgiLib__GetVar ("WWW_CONTENT_TYPE", ""); if (strstr (cptr, "application/x-www-form-urlencoded")) return (CgiLib__BodyFormUrlEncoded (BodyPtr, BodyLength)); if (!strncmp (cptr, "multipart/form-data;", 20)) return (CgiLib__BodyMultipartFormData (BodyPtr, BodyLength, cptr+20)); return (0); } /*****************************************************************************/ /* Converts the POSTed url-form-encoded body parameter into "WWW_FORM_field-name" CGI variables accessable from the variable list. */ int CgiLib__BodyFormUrlEncoded ( char *BodyPtr, int BodyLength ) { static char VarName [256] = "WWW_FORM_"; int VarCount; char *cptr, *sptr, *zptr, *BufferPtr, *VarValuePtr; /*********/ /* begin */ /*********/ if (CgiLib__Debug) fprintf (stdout, "CgiLib__FormUrlEncoded()\n"); /* no need for any CgiLibVeeMem..() functionality */ cptr = BufferPtr = calloc (1, BodyLength+1); if (!BufferPtr) exit (SS$_INSFMEM); memcpy (cptr, BodyPtr, BodyLength); cptr[BodyLength] = '\0'; VarCount = 0; while (*cptr) { zptr = (sptr = VarName) + sizeof(VarName)-1; sptr += 9; while (*cptr && *cptr != '=' && sptr < zptr) *sptr++ = toupper(*cptr++); if (sptr >= zptr) while (*cptr && *cptr != '=') cptr++; if (!*cptr) { free (BufferPtr); return (VarCount); } *sptr = '\0'; VarValuePtr = ++cptr; while (*cptr && *cptr != '&') cptr++; if (*cptr) *cptr++ = '\0'; if (VarName[9]) CgiLibUrlDecode (VarName+9); if (VarValuePtr[0]) CgiLibUrlDecode (VarValuePtr); if (CgiLib__CgiPrefixWWW) CgiLib__VarList (VarName, VarValuePtr, NULL); else CgiLib__VarList (VarName+4, VarValuePtr, NULL); VarCount++; } free (BufferPtr); return (VarCount); } /*****************************************************************************/ /* Converts the POSTed MIME multipart form-data body parameters into "WWW_FORM_field-name" CGI variables accessable from the variable list. As it is possible to POST unencoded characters with this method (all characters from 0x00 to 0xff are transmitted unencoded) non-printable characters are HTML-entified (i.e. 0x00 becomes "�", 0x01 "", etc.) before storage as variables. These of course would need to be de-entified before use (with CgiLibHtmlDeEntify()). If the field is an upload ("type=file") and there is an associated content-type and file name (as there should be) these are placed in variables with the same name as the field with "_MIME_CONTENT_TYPE" and "_MIME_FILENAME" added. This is an incomplete MIME decode and may be a bit brain-dead, I don't know much about it at all. Seems to work with the big-three browsers I've tried it on, NetNav, MSIE and Opera. */ int CgiLib__BodyMultipartFormData ( char *BodyPtr, int BodyLength, char *BoundaryPtr ) { static int CheckedEntifyIsprint, IsprintEntify; static char VarName [256] = "WWW_FORM_"; int BoundaryLength, BufferSize, ContentLength, VarCount, VarNameLength; unsigned char ch; char *bptr, *cptr, *sptr, *zptr, *BufferPtr, *ContentTypePtr, *EndBodyPtr, *EndHeaderPtr, *StartHeaderPtr; char Boundary [256], ContentType [256], FileName [256]; /*********/ /* begin */ /*********/ if (CgiLib__Debug) fprintf (stdout, "CgiLib__BodyMultipartFormData()\n"); if (!CheckedEntifyIsprint) { /* v1.6.7 used isprint(), this allows selected backward compatibility */ CheckedEntifyIsprint = 1; if (getenv ("CGILIB_MULTIPART_ISPRINT")) IsprintEntify = 1; } zptr = (sptr = Boundary) + sizeof(Boundary)-1; for (cptr = BoundaryPtr; *cptr && *(ULONGPTR)cptr != 'ary='; cptr++); if (!*cptr) return (0); cptr += sizeof(unsigned long); if (*cptr == '\"') { cptr++; while (*cptr && *cptr != '\"' && sptr < zptr) *sptr++ = *cptr++; } else while (*cptr && sptr < zptr) *sptr++ = *cptr++; *sptr = '\0'; BoundaryLength = sptr - Boundary; if (CgiLib__Debug) fprintf (stdout, "Boundary %d |%s|\n", BoundaryLength, Boundary); /***************************/ /* loop through body parts */ /***************************/ VarCount = 0; EndBodyPtr = (cptr = BodyPtr) + BodyLength; while (cptr < EndBodyPtr) { if (CgiLib__Debug) fprintf (stdout, "|%.128s|\n", cptr); if ((cptr = strstr (cptr, Boundary)) == NULL) return (0); cptr += BoundaryLength; if (*(USHORTPTR)cptr == '--') break; if (*(USHORTPTR)cptr != '\r\n') return (0); cptr += 2; /***********************/ /* process part header */ /***********************/ /* note the start and end of the part header information */ StartHeaderPtr = cptr; if ((EndHeaderPtr = strstr (cptr, "\r\n\r\n")) == NULL) return (0); if ((cptr = strstr (StartHeaderPtr, "Content-Disposition:")) == NULL) return (0); cptr += 20; if ((cptr = strstr (StartHeaderPtr, "form-data;")) == NULL) return (0); cptr += 10; if ((cptr = strstr (StartHeaderPtr, "name=")) == NULL) return (0); if (cptr > EndHeaderPtr) return (0); cptr += 5; ch = '\0'; if (*cptr == '\"') ch = *cptr++; /* minus 19, one for the terminating null, 18 for max "_MIME_..." */ zptr = (sptr = VarName) + sizeof(VarName)-19; sptr += 9; while (*cptr && *cptr != ch && sptr < zptr) *sptr++ = toupper(*cptr++); if (sptr >= zptr) while (*cptr && *cptr != ch) cptr++; if (*cptr) cptr++; *sptr = '\0'; VarNameLength = sptr - VarName; if (CgiLib__Debug) fprintf (stdout, "VarName |%s|\n", VarName); FileName[0] = '\0'; if ((cptr = strstr (StartHeaderPtr, "filename=")) != NULL && cptr < EndHeaderPtr) { cptr += 9; ch = '\0'; if (*cptr == '\"') ch = *cptr++; zptr = (sptr = FileName) + sizeof(FileName)-1; while (*cptr && *cptr != ch && sptr < zptr) *sptr++ = *cptr++; if (sptr >= zptr) while (*cptr && *cptr != ch) cptr++; if (*cptr) cptr++; *sptr = '\0'; if (CgiLib__Debug) fprintf (stdout, "FileName |%s|\n", FileName); } ContentType[0] = '\0'; if ((cptr = strstr (StartHeaderPtr, "Content-Type:")) != NULL && cptr < EndHeaderPtr) { for (cptr += 13; *cptr && isspace(*cptr); cptr++); zptr = (sptr = ContentType) + sizeof(ContentType)-1; while (*cptr && !isspace(*cptr) && sptr < zptr) *sptr++ = *cptr++; *sptr = '\0'; if (CgiLib__Debug) fprintf (stdout, "ContentType |%s|\n", ContentType); } /**************************/ /* retrieve the part data */ /**************************/ sptr = cptr = EndHeaderPtr + 4; for (;;) { /* find the next boundary */ if (CgiLib__Debug) fprintf (stdout, "|%.72s|\n", sptr); for (;;) { if (sptr >= EndBodyPtr) return (0); while (sptr < EndBodyPtr && *sptr != '\r') sptr++; if (!strncmp (sptr, "\r\n--", 4)) break; sptr++; } /* if end of body, break; */ if (!sptr[4]) break; /* if not end of body then should be a boundary */ if (!strncmp (sptr+4, Boundary, BoundaryLength)) break; /* nope, must have been data */ sptr += 4; } if (CgiLib__Debug) fprintf (stdout, "start: %d end: %d bytes: %d\n", cptr, sptr, sptr-cptr); /* get a worst-case value for HTML-entifying all unacceptable chars */ BufferSize = sptr - cptr + 1; if (IsprintEntify) { /* pre-v1.6.7 */ for (bptr = cptr; bptr < sptr; bptr++) if ((!isprint(*bptr) && !isspace(*bptr)) || *bptr == '&') BufferSize += 5; } else { /* current default method */ for (bptr = cptr; bptr < sptr; bptr++) if ((unsigned char)*bptr <= 0x1f || ((unsigned char)*bptr >= 0x7f && (unsigned char)*bptr <= 0x9f) || (unsigned char)*bptr == 0xff || *bptr == '&') BufferSize += 5; } if (CgiLib__Debug) fprintf (stdout, "BufferSize: %d\n", BufferSize); /* no need for any CgiLibVeeMem..() functionality */ bptr = BufferPtr = calloc (1, BufferSize); if (!BufferPtr) exit (SS$_INSFMEM); if (IsprintEntify) { /* pre-v1.6.7 */ while (cptr < sptr) { if ((isprint (*cptr) || isspace(*cptr)) && *cptr != '&') { *bptr++ = *cptr++; continue; } bptr += sprintf (bptr, "&#%u;", *cptr++ & 0xff); } } else { /* current default method */ while (cptr < sptr) { if (!((unsigned char)*cptr <= 0x1f || ((unsigned char)*cptr >= 0x7f && (unsigned char)*cptr <= 0x9f) || (unsigned char)*cptr == 0xff || *cptr == '&')) { *bptr++ = *cptr++; continue; } bptr += sprintf (bptr, "&#%u;", *cptr++ & 0xff); } } *bptr = '\0'; if (CgiLib__Debug) fprintf (stdout, "BufferPtr |%s|\n", BufferPtr); /******************************/ /* store associated variables */ /******************************/ if (CgiLib__CgiPrefixWWW) CgiLib__VarList (VarName, BufferPtr, NULL); else CgiLib__VarList (VarName+4, BufferPtr, NULL); VarCount++; free (BufferPtr); /* if a file name associated with the field (i.e. and upload) */ if (ContentType[0]) { strcpy (VarName + VarNameLength, "_MIME_CONTENT_TYPE"); if (CgiLib__CgiPrefixWWW) CgiLib__VarList (VarName, ContentType, NULL); else CgiLib__VarList (VarName+4, ContentType, NULL); VarCount++; } /* if a file name associated with the field (i.e. and upload) */ if (FileName[0]) { strcpy (VarName + VarNameLength, "_MIME_FILENAME"); if (CgiLib__CgiPrefixWWW) CgiLib__VarList (VarName, FileName, NULL); else CgiLib__VarList (VarName+4, FileName, NULL); VarCount++; } /* step over the "\r\n--" */ cptr += 4; } return (VarCount); } /*****************************************************************************/ /********************/ /* VEEMEM FUNCTIONS */ /********************/ /*****************************************************************************/ /* Enable/disable (1/0) VeeMem for applicable CGILIB functions. Return the previous setting. Any call resets VeeMem memory. This only enables/disables VeeMem for CGILIB functions, an application can use VeeMem without CGILIB also using it (but there is not much point if you're also using the applicable CGILIB functions - see CGILIB prologue). */ int CgiLibVeeMemInUse (int OffOn) { int PrevVeeMemInUse; /*********/ /* begin */ /*********/ if (CgiLib__Debug) fprintf (stdout, "CgiLibVeeMemInUse() %d\n", OffOn); PrevVeeMemInUse = CgiLib__VeeMemInUse; CgiLibVeeMemInit (); CgiLib__VeeMemInUse = OffOn; return (PrevVeeMemInUse); } /*****************************************************************************/ /* The first call initialises the virtual memory zone. Subsequent calls (and there should be one at the conclusion of each request processed) releases back into the virtual memory zone all of the individually allocated memory chunks. */ int CgiLibVeeMemInit () { static $DESCRIPTOR (ZoneNameDsc, "CGILIB_VEEMEM_ZONE"); static unsigned long Algorithm = LIB$K_VM_QUICK_FIT, AlgorithmArg = 64, Flags = LIB$M_VM_BOUNDARY_TAGS | LIB$M_VM_GET_FILL0 | LIB$M_VM_TAIL_LARGE, ExtendSize = 512, InitialSize = 512, BlockSize = 64; int status; /*********/ /* begin */ /*********/ if (CgiLib__Debug) fprintf (stdout, "CgiLibVeeMemInit() %d\n", CgiLib__VeeMemZoneId); if (CgiLib__VeeMemZoneId) status = lib$reset_vm_zone (&CgiLib__VeeMemZoneId); else status = lib$create_vm_zone (&CgiLib__VeeMemZoneId, &Algorithm, &AlgorithmArg, &Flags, &ExtendSize, &InitialSize, &BlockSize, 0, 0, 0, &ZoneNameDsc, 0, 0); CgiLib__VeeMemCurrent = CgiLib__VeeMemPeak = 0; return (status); } /*****************************************************************************/ /* Allocate memory from the zone. */ void* CgiLibVeeMemCalloc (unsigned long ChunkSize) { int status; unsigned long BaseAddress; void *ChunkPtr; /*********/ /* begin */ /*********/ if (CgiLib__Debug) fprintf (stdout, "CgiLibVeeMemCalloc() %u\n", ChunkSize); vaxc$errno = 0; ChunkSize += sizeof(unsigned long) + CGILIB_VEEMEM_EBROOM; status = lib$get_vm (&ChunkSize, &BaseAddress, &CgiLib__VeeMemZoneId); if (!(status & 1)) { vaxc$errno = status; return (NULL); } ChunkSize -= sizeof(unsigned long) + CGILIB_VEEMEM_EBROOM; *(ULONGPTR)BaseAddress = ChunkSize; ChunkPtr = (void*)(BaseAddress + sizeof(unsigned long)); CgiLib__VeeMemCurrent += ChunkSize; if (CgiLib__VeeMemCurrent > CgiLib__VeeMemPeak) CgiLib__VeeMemPeak = CgiLib__VeeMemCurrent; if (CgiLib__Debug) fprintf (stdout, "current:%u peak:%u\n", CgiLib__VeeMemCurrent, CgiLib__VeeMemPeak); return (ChunkPtr); } /*****************************************************************************/ /* Expand (or even contract) an individual a general-use chunk. See VmGet(). */ void* CgiLibVeeMemRealloc ( void *ChunkPtr, unsigned long ChunkSize ) { int status, CurrentChunkSize; unsigned long BaseAddress, CurrentBaseAddress; void *CurrentChunkPtr; /*********/ /* begin */ /*********/ if (CgiLib__Debug) fprintf (stdout, "CgiLibVeeMemRealloc() %d\n", ChunkSize); vaxc$errno = 0; if (!ChunkSize) return (NULL); if (!ChunkPtr) return (CgiLibVeeMemCalloc (ChunkSize)); CurrentChunkPtr = ChunkPtr; CurrentBaseAddress = (unsigned long)CurrentChunkPtr; CurrentBaseAddress -= sizeof(unsigned long); CurrentChunkSize = *(ULONGPTR)CurrentBaseAddress; /* if already large enough */ if (CurrentChunkSize >= ChunkSize) return (ChunkPtr); /* allocate a new, larger chunk */ ChunkSize += sizeof(unsigned long) + CGILIB_VEEMEM_EBROOM; status = lib$get_vm (&ChunkSize, &BaseAddress, &CgiLib__VeeMemZoneId); if (!(status & 1)) { vaxc$errno = status; return (NULL); } ChunkSize -= sizeof(unsigned long) + CGILIB_VEEMEM_EBROOM; *(ULONGPTR)BaseAddress = ChunkSize; ChunkPtr = (void*)(BaseAddress + sizeof(unsigned long)); /* copy the existing chunk into the new chunk */ memcpy (ChunkPtr, CurrentChunkPtr, CurrentChunkSize); /* free the previous chunk */ status = lib$free_vm (0, &CurrentBaseAddress, &CgiLib__VeeMemZoneId); if (!(status & 1)) { vaxc$errno = status; return (NULL); } CgiLib__VeeMemCurrent -= CurrentChunkSize; CgiLib__VeeMemCurrent += ChunkSize; if (CgiLib__VeeMemCurrent > CgiLib__VeeMemPeak) CgiLib__VeeMemPeak = CgiLib__VeeMemCurrent; if (CgiLib__Debug) fprintf (stdout, "current:%u peak:%u\n", CgiLib__VeeMemCurrent, CgiLib__VeeMemPeak); return (ChunkPtr); } /*****************************************************************************/ /* Explicitly release memory back into the zone. */ void CgiLibVeeMemFree (void *ChunkPtr) { int status, ChunkSize; unsigned long BaseAddress; /*********/ /* begin */ /*********/ if (CgiLib__Debug) fprintf (stdout, "CgiLibVeeMemFree()\n"); vaxc$errno = 0; if (!ChunkPtr) return; BaseAddress = (unsigned long)ChunkPtr; BaseAddress -= sizeof(unsigned long); ChunkSize = *(ULONGPTR)BaseAddress; status = lib$free_vm (0, &BaseAddress, &CgiLib__VeeMemZoneId); if (!(status & 1)) vaxc$errno = status; CgiLib__VeeMemCurrent -= ChunkSize; if (CgiLib__Debug) fprintf (stdout, "chunk:%u current:%u peak:%u\n", ChunkSize, CgiLib__VeeMemCurrent, CgiLib__VeeMemPeak); } /*****************************************************************************/ /* CGIlib usually just exits with an error status when a memory allocation fails. With some memory allocation it's better to know that it failed. Calling this with either 1 (continue returning the expected result when failed) or 0 (just exit with the error status) allows fine-grained behaviour control when using functions that employ dynamic memory. Works for both CgiLibVeeMem..() allocated memory and calloc()/realloc() etc. Returns the previous state. */ int CgiLibVeeMemContinue (int OffOn) { int WasOffOn; /*********/ /* begin */ /*********/ if (CgiLib__Debug) fprintf (stdout, "CgiLibVeeMemContinue() %d\n", OffOn); WasOffOn = CgiLib__VeeMemContinue; CgiLib__VeeMemContinue = OffOn; return (WasOffOn); } /*****************************************************************************/ /* Show (debug) the virtual memory zone. (Just not interested in the user-arg parameter.) Casting from (void*) to avoid compiler message %CC-I-PROTOSCOPE. */ int CgiLibVeeMemShow (void *StrDscPtr) { static unsigned long DetailLevel = 3; static int veeMemZone = 0; int status; unsigned long FindContext, ZoneId; struct dsc$descriptor *DscPtr; /*********/ /* begin */ /*********/ if (CgiLib__Debug) fprintf (stdout, "CgiLibVeeMemShow()\n"); if (StrDscPtr) { /* action-routine */ DscPtr = (struct dsc$descriptor*)StrDscPtr; #if 0 if (!veeMemZone) { if (DscPtr->dsc$w_length < 53) return (SS$_NORMAL); if (memcmp (DscPtr->dsc$a_pointer+21, "Zone name = \"CGILIB_VEEMEM_ZONE\"", 32)) return (SS$_NORMAL); veeMemZone = 1; fputs ("\n", stdout); } #endif fprintf (stdout, "%*.*s\n", DscPtr->dsc$w_length, DscPtr->dsc$w_length, DscPtr->dsc$a_pointer); return (SS$_NORMAL); } FindContext = 0; while ((status = lib$find_vm_zone (&FindContext, &ZoneId)) == SS$_NORMAL) { status = lib$show_vm_zone (&ZoneId, &DetailLevel, &CgiLibVeeMemShow, 0); if (!(status & 1)) break; } if (status != LIB$_NOTFOU) return (status); fflush (stdout); veeMemZone = 0; return (SS$_NORMAL); } /*****************************************************************************/