/*****************************************************************************/ /* CGI.c Provide generalized CGI scripting support. Creates a buffer containing either, the DCL commands to create CGI variables at the VMS command line (e.g. for standard CGI scripts), or a stream of 'name=value' pairs for the CGI variables (e.g. for CGIplus scripts). The buffer is dynamically allocated and contains a series of sequential records comprising a word (16 bits) with the length of a following (varying) null-terminated string (including the null character). The end of the series is indicated by a zero length record. This buffer is scanned from from first to last and the strings passed to the script 'input' stream as appropriate. CGI RESPONSE HEADER ------------------- In common with other implementations, if the first line of the response header begins "HTTP/1." then it is considered to be a full HTTP response (non-parsed header output), the server does no more header processing and it is up to the script to provide a fully compliant HTTP response. Handling of the CGI response header is pretty standard. It should begin with "Content-Type:", "Status:", or "Location:". One of more of these should be received before the end-of-header blank line or other header lines. As soon as any other line is received the server considered the CGI header complete. The CGI header can be received record-by-record, with or without any carriage-control, or as a block of lines with correct carriage-control (only complete lines will be processed correctly). The "X-vms-record-mode:" field is a VMS Apacheism for indicating to the server whether VMS script output must be checked for correct carriage-control and corrected if necessary. It seems as if 0 is "not record mode", 1 is. If record mode carriage-control is being processed and completely empty records start being received then switch to a record-rpcoessing mode where only completely empty recards have carriage control inserted. Current "Script-Control:" Directives ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ no-abort CGI/1.2, do not abort script because of timeout X-buffer-records[=0|1] buffer records before sending to client X-cache-max= kBytes maximum X-cache-expires= "day", "hour", "min", 'hh:mm:ss' or minutes X-content-handler= "SSI", content should be processed by SSI engine X-content-encoding-gzip[=0|1] disable/enable gzip compression (if supported) X-crlf-mode[=0|1] ensure all records have a trailing X-error-line= source line error noted (optional) X-error-module= source code module error noted (optional) X-error-text= textual explanation of error (required) X-error-vms-status= if generated by a VMS status error (optional) X-error-vms-text= VMS explanation (often a file spec, optional) X-filter-stream[=0|1] remove all non-printable characters X-http-status= override response line status and for redirected error reporting script override original status X-lifetime= "none" or integer, set script process lifetime X-record-mode[=0|1] ensure all records have at least trailing X-record0-mode[=0|1] only empty records have a trailing inserted X-stream-mode[=0|1] do not alter records at all, WSSIWCG! X-timeout-output= "none" or integer, set request output timer X-timeout-noprogress= "none" or integer, set request no-progress timer X-transfer-encoding-chunked[=0|1] disable/enable chunking (all times are in minutes) The "Script-Control:" is a proposed CGI/1.2 header field intended for script control ;^). By default WASD writes each record to the client as it is received from the script (in case of slow or intermittent output). For script's where this is known not to be an issue this directive can be used to have the server buffer the records, outputting the block when the buffer fills or at script conclusion. This is often *much* more efficient. The record mode forces each record received to be checked for correct carriage-control (a trailing ) and if not present to be appended. SCRIPT REQUESTED ERROR MESSAGE ------------------------------ Using the script control error fields a script can request the server to generate an error response on it's behalf. This has the advantage of generating error messages conforming to the configured components and layout of server error responses. The following show examples of the response header lines required to use this facility. The 'X-error-text' field is what triggers the error generation and is the only mandatory field. Vanilla error message |Status: 400 |Script-Control: X-error-text="Object not found." | VMS status error message |Status: 403 |Script-Control: X-error-text="opening file" |Script-Control: X-error-vms-status=36 |Script-Control: X-error-vms-text="HT_ROOT:[SRC]EXAMPLE.TXT" | or using a VMS hexadecimal string (with module name and line information) |Status: 403 |Script-Control: X-error-text="opening file" |Script-Control: X-error-vms-status=%X00000024 |Script-Control: X-error-vms-text="HT_ROOT:[SRC]EXAMPLE.TXT" |Script-Control: X-error-module=EXAMPLE |Script-Control: X-error-line=123 | CGI VARIABLES ------------- Most of these CGI variable names are those supported by the INTERNET-DRAFT authored by D.Robinson (drtr@ast.cam.ac.uk), 8 January 1996, plus some "convenience" variables, breaking the query string into its components (KEY_, FORM_, etc.) The CGI symbols (CERN/VMS-HTTPd-like DCL symbols instead of Unix environment variables) are created by the SYS$COMMAND stream of each script process before the script DCL procedure is invoked. By default each variable name is prefixed by "WWW_" (similar to CERN HTTPd), although this can be modified at the command line when starting the server. VARIABLE NAME DESCRIPTION ORIGIN ------------- ----------- ------ (if the request is not authorized only AUTH_TYPE will exist) o AUTH_ACCESS ............ "READ" or "READ+WRITE" WASD o AUTH_AGENT ............. indicates authentication agent WASD o AUTH_GROUP ............. path authorization group WASD o AUTH_PASSWORD .......... only if "EXTERNAL" realm WASD o AUTH_PATH .............. path from rule (only in agent) WASD o AUTH_REALM ............. authentication realm WASD o AUTH_REALM_DESCRIPTION . realm description WASD o AUTH_REMOTE_USER ....... original, now proxied username WASD o AUTH_TYPE .............. "BASIC" or "DIGEST" (or empty) CGI o AUTH_USER .............. details of user WASD o CONTENT_LENGTH ......... "Content-Length:" from header CGI o CONTENT_TYPE ........... "Content-Type:" from header CGI o DOCUMENT_ROOT .......... base directory for serving files Apache o FORM_field ............. query "&" separated form elements WASD o FORM__integer .......... relaxed empty form field names WASD o GATEWAY_BG ............. socket BG device name WASD o GATEWAY_EOF ............ CGIPLUSEOF sentinal (if CGIplus) WASD o GATEWAY_EOT ............ CGIPLUSEOT sentinal (if not DECnet) WASD o GATEWAY_ESC ............ CGIPLUSESC sentinal (if not DECnet) WASD o GATEWAY_INTERFACE ...... "CGI/1.1" CGI o GATEWAY_MRS ............ maximum record size of mailbox WASD o HTML_BODYTAG ........... report tag (e.g. "BGCOLOR=") WASD o HTML_FOOTER ............ report footer text WASD o HTML_FOOTERTAG ......... report footer tag WASD o HTML_HEADER ............ report header text WASD o HTML_HEADERTAG ......... report header tag WASD o HTTP2_PING ............. most recent HTTP/2 ping result WASD o HTTP_ACCEPT ............ list of browser-accepted content types CGI o HTTP_ACCEPT_CHARSET .... list of browser-accepted character set CGI o HTTP_ACCEPT_ENCODING ... list of browser-accepted encodings CGI o HTTP_ACCEPT_LANGUAGE ... list of browser-accepted languages CGI o HTTP_AUTHORIZATION ..... only if "EXTERNAL" realm CGI o HTTP_CACHE_CONTROL ..... HTTP/1.1 cache control field CGI o HTTP_CONNECTION ........ connection control field CGI o HTTP_CONTENT_LENGTH .... content-length CGI o HTTP_CONTENT_TYPE ...... content-type CGI o HTTP_COOKIE ............ any cookie sent by the client CGI o HTTP_FORWARDED ......... list of proxy/gateway hosts CGI o HTTP_HOST .............. destination host name/port CGI o HTTP_IF_MATCH .......... HTTP/1.1 if-match request field CGI o HTTP_IF_NONE_MATCH ..... HTTP/1.1 if-none-match request field CGI o HTTP_IF_MODIFIED_SINCE ..... request field CGI o HTTP_IF_UNMODIFIED_SINCE ... HTTP/1.1 request field CGI o HTTP_IF_RANGE .......... HTTP/1.1 if-range request field CGI o HTTP_KEEP_ALIVE ........ request field CGI o HTTP_PRAGMA ............ request filed CGI o HTTP_RANGE ............. HTTP/1.1 requested body byte range CGI o HTTP_REFERER ........... source document URL for this request CGI o HTTP_TRAILER ........... HTTP/1.1 request field CGI o HTTP_TRANSFER_ENCODING ... HTTP/1.1 request field CGI o HTTP_USER_AGENT ........ client/browser identification string CGI o HTTP_X_FORWARDED_FOR ... proxy forwarded for this client SQUID o KEY_integer ............ query string "+" separated elements WASD o KEY_COUNT .............. number of "+" separated elements WASD o PATH_INFO .............. virtual path of data requested in URL CGI o PATH_ODS ............... on-disk structure of path (0, 2 or 5) WASD o PATH_TRANSLATED ........ VMS file path of data requested in URL CGI o QUERY_STRING ........... string following "?" in URL CGI o REMOTE_ADDR ............ IP host address of HTTP client CGI o REMOTE_HOST ............ IP host name of HTTP client CGI o REMOTE_PORT ............ IP port of HTTP client WASD o REMOTE_USER ............ authenticated username (or empty) CGI o REQUEST_CHARSET ........ charset SET by mapping rule WASD o REQUEST_CONTENT_TYPE ... content-type SET by mapping rule WASD o REQUEST_METHOD ......... "GET", "PUT", etc. CGI o REQUEST_PROTOCOL ....... HTTP protocol version ("HTTP/2") WASD o REQUEST_SCHEME ......... "http:" or "https:" WASD o REQUEST_TIME_GMT ....... request GMT time WASD o REQUEST_TIME_LOCAL ..... request local time WASD o REQUEST_URI ............ un-encoded path[?query-string] Apache o SCRIPT_DEFAULT ......... script process default directory WASD o SCRIPT_FILENAME ........ (e.g. "CGI-BIN:[000000]TEST.COM") Apache o SCRIPT_NAME ............ name of script (e.g. "/query") CGI o SCRIPT_RTE ............. script run-time environment WASD o SERVER_ADDR ............ IP host address of server system WASD o SERVER_ADMIN ........... "webmaster@site.domain" Apache o SERVER_CHARSET ......... default charset (e.g. "ISO-8895-1") WASD o SERVER_GMT ............. offset from GMT time (e.g. "+09:30) WASD o SERVER_MULTIHOME ....... IP address client used (if different) WASD o SERVER_NAME ............ IP host name of server system CGI o SERVER_PROTOCOL ........ HTTP protocol version ("HTTP/1.1") CGI o SERVER_PORT ............ IP port request was received on CGI o SERVER_SOFTWARE ........ software ID of the HTTPD daemon CGI o SERVER_SIGNATURE ....... "server host port" Apache o SERVER_TRUNCATE ........ if script=symbol=truncate in effect WASD o UNIQUE_ID .............. a "unique" request ID Apache o UPSTREAM_ADDR .......... client address rewritten - original WASD o some SSL related CGI variable can be generated when SSL is available (see Sesola.C module) NOTE: If script portability is a concern then confine use to the "standard" CGI variables. For WASD-specific scripts the extension variables may be used. Not all variables will be present for all requests, especially 'HTTP_' ones. For instance, the presence or not of HTTP_CONTENT_LENGTH may be used to determine whether it was actually present in the request header or not. CGI VARIABLES USING DCL SYMBOLS ------------------------------- Due to the mechanism used to create DCL symbols for the CGI variables (for standard CGI) the variable value is limited to the symbol size number of characters minus the length of the DCL symbol name assignment (e.g. "WWW_QUERY_STRING=A+B+C+D"). DCL symbol creation at the command line is limited by the CLI command line length (255 characters for pre 7.3-2, 4095 for post 7.3-1). Symbol values however can be up to 1024 (or pragmatically about 1000 characters) for pre 7.3-2 and 8192 (or thereabouts) for post 7.3-1, probably enough for any CGI variable value. If a CGI value is too large for for a single command-line assignment then build it up using multiple assignments, a symbol assignment kludge! All this is of course ultimately limited by the size of the DCL command mailbox. CGI VARIABLES USING CGIPLUS STREAM ---------------------------------- The maximum 'name=value' length for a CGI variable conveyed using the CGIPLUSIN stream is limited by the size of of the 'DclCgiPlusInSize' global storage value which is in turn set by the [BufferSizeDclCgiPlusIn] configuration directive. Hence if this is set to 3072 bytes (the current default) then the maximum 'name=value' length is the that value minus 3 bytes. This value is also the total mailbox buffer space allocated for script process IPC and so is also the limit on all variables! So, in the circumstance where the largest variable needs to be 4000 bytes in length, and the rest total up to another 3000 bytes, the [BufferSizeDclCgiPlusIn] is suggested to be 8192 bytes. When this value is adjusted it is recommended to review the HTTP server account's BYTLM quota. CGIPLUS VARIABLE STRUCTURE -------------------------- CGIplus (and standard CGI) variables are originally created as a single, structured block of data. The internal structure is quite simple. Each null-terminated "name=value" pair is preceded by an unsigned short (16 bit) length field. This value includes all characters in the "name=value" pair, plus the terminating null character. Hence the maximum length of one of these pairs is 65535 minus 1. The end of the "name=value" pairs is indicated by a zero-value (and hence "name=value" length) short. |length|name=value\0|length|name=value\0|length|name=value\0|zero| This data structure can easily be walked. Passing these CGI variables to the scripting process can be done in two ways, parsing each CGI variable and providing it individually as a "record", and by providing the entire data structure intact as a "struct". Of course with the latter it remains for the script itself to parse the structure to obtain the CGI variable values. 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! VERSION HISTORY --------------- 30-APR-2019 MGD bugfix; CgiOutput() Content-Length: strtoul() 25-FEB-2018 MGD CgiOutput() constrain response header field names 28-NOV-2017 MGD CgiOutput() reject subsequent non-header 04-OCT-2017 MGD CgiOutput() refine Content-Length: to report out-of-range 28-SEP-2017 MGD bugfix; CgiGenerateVariables() |rqptr->rqAuth.SourceRealm != AUTH_SOURCE_AGENT_OPAQUE &&| 24-MAY-2017 MGD bugfix; for HTTP/2 (sigh) we need NPH to generate a header 01-DEC-2016 MGD RAWSOCKET_.. variables 11-AUG-2016 MGD Script-Control: X-http-status= 20-JUL-2016 MGD CgiGenerateVariables() mitigate httpoxy vulnerability bugfix; CgiGenerateVariables() names from dictionary 24-JAN-2016 MGD REQUEST_PROTOCOL, HTTP2_PING and "cgi_.." dictionary 17-OCT-2015 MGD CgiVariable() if |SymbolValue| is NULL parse the name then value from |SymbolName| = CgiGenerateVariables() WATCH total bytes in variables 02-DEC-2014 MGD X-record0-mode[=0|1] a completely empty record can only be where carriage-control is being generated as records without C-RTL newline char insertion - so switch to a non-embedded CC record mode 06-NOV-2014 MGD UPSTREAM_ADDR client address rewitten - this is the original 25-MAY-2013 MGD bugfix; AUTH_GROUP selected using write/read status 26-JAN-2010 MGD Web Socket scripting ScriptControlField() returns non-zero to indicate success 01-JUN-2008 JPP CgiVariable() optimise single-quotation escaping 19-APR-2008 MGD WATCH_SCRIPT indicates when script WATCHing is enabled 17-JAN-2008 MGD CgiGenerateVariables() if SCRIPT_NAME is "/" (empty froM mapping) then make it "" (really empty :-) 24-NOV-2007 MGD AUTH_PATH provides path from rule for auth agent 08-APR-2007 MGD CgiGenerateVariables() provide TRACK_ID if present (for JPP) 08-FEB-2006 MGD potential bugfix; CgiOutput() CGI_OUTPUT_MODE_CRLF output count should be checked zero before using negative index, bugfix; CgiOutput() empty 'record' in stream mode should be ignored and not have carriage-control adjusted (JFP) 01-JUN-2005 MGD CgiVariable() allow path mapping script=symbol=truncate to truncate a CLI symbol within the limit of the current VMS version capacity, noting this in SERVER_TRUNCATE variable 20-APR-2005 MGD provide SERVER_MULTIHOME if present (see NET.C) 20-JAN-2005 MGD if CGI response "Location:" is supplied but no HTTP status turn it into a 302 (see also ResponseHeader()) 08-JAN-2005 MGD process script=control=<...> path setting 16-DEC-2004 MGD Script-Control: X-transfer-encoding-chunked[=0|1], detect when a script is providing content-encoded output 26-NOV-2004 MGD Script-Control: X-content-encoding-gzip[=0|1], allow other binary controls to specify off or on (=[0|1]) 07-NOV-2004 MGD massage PATH_TRANSLATED and SCRIPT_FILENAME if path SET script=syntax=unix 20-OCT-2004 MGD change content_length CGI variable to "?" if a POSTed (etc.) requests's body is being decoded by the server, use pseudo-random numbers in CgiSequence() 17-OCT-2004 MGD bugfix; CgiOutputFile() missing sizeof(FILE_CONTENT) when VmReallocHeap() increasing buffer space 20-JUL-2004 MGD rework provision of HTTP_ CGI variables, provide HTTP_CONTENT_LENGTH if in header, allow for "HTTP/1.1 100 Continue" responses 07-APR-2004 MGD bugfix; agent script should have non-strict-CGI ignored (stupid problem introduced with script output caching) 26-FEB-2004 MGD add SCRIPT_DEFAULT from SET 'script=default=' 10-JAN-2004 MGD absorb CGI/NPH header, Script-Control: X-content-handler=SSI field 23-DEC-2003 MGD for VMS 7.3-2 and later take advantage of the larger EDCL CLI line (255->4095) and symbol (1024->8192) sizes 28-SEP-2003 MGD add HTTP_KEEP_ALIVE and HTTP_CONNECTION 21-AUG-2003 MGD HTTP_RANGE field CGI variable 09-JUL-2003 MGD add new dynamic caching capability 21-JUN-2003 MGD bugfix; CgiOutput() (jpp@esme.fr) 11-MAY-2003 MGD unrecognised request fields as "HTTP_.." variables 02-APR-2003 MGD add HTTP_X_FORWARDED_FOR CGI variable 01-MAR-2003 MGD path setting HTML body/header/footer 24-JAN-2003 MGD relax initial CGI response line checking 17-JAN-2003 MGD path setting 'script=query=none' suppresses parsing of query string into form or key elements added GATEWAY_EOF/EOT/ESC variables, sentinals changed to have only RMS-compliant characters, path set carriage-control on CGIPLUSIN stream, supply more detail from "%DCL-E-OPENIN, blah" responses 10-JAN-2003 MGD path setting 'script=query=relaxed' allows form-url-encoded query strings to be non-conformant without error reporting 05-DEC-2002 MGD in non-strict CGI mode report anything like "%DCL-E-OPENIN, blah" as a failed script activation 08-OCT-2002 MGD Script-Control: X-error-... fields 17-AUG-2002 MGD modify PATH_ODS variable to allow for PWK and SRI 01-AUG-2002 MGD bugfix; do not count callout records for CGI header purposes 16-JUL-2002 MGD last-minute relaxation of stricter CGI header processing allowing a trailing CR to terminate a single header record 30-APR-2002 MGD "Cache-Control:" field for Mozilla compatibility 30-MAR-2002 MGD stream-mode with zero output as suppressed carriage-control 24-NOV-2001 MGD significant rework CgiOutput() CGI header processing 14-OCT-2001 MGD support script=params=(name=value) SET rule 15-SEP-2001 MGD modify CgiOutput() processing, X-filter-stream removes non-printable characters 04-AUG-2001 MGD support module WATCHing 27-FEB-2001 MGD suppress "x-internal/" CONTENT_TYPE 20-FEB-2001 MGD AUTH_REMOTE_USER for proxied authorization, refine detection of authorization agent reponse errors 01-FEB-2001 MGD bugfix; CGI strict output suppression for auth agent 18-JAN-2001 MGD bugfix; REMOTE_PORT ntohs() 19-DEC-2000 MGD refine [CgiStrictOutput] to detect an incomplete header 05-SEP-2000 MGD additional "Script-Control:" directives 08-AUG-2000 MGD if response "Content-Encoding:" force stream mode, add GATEWAY_BG and GATEWAY_MRS variables 01-JUL-2000 MGD modify CgiOutput() to better handle CGI responses, add "Script-Control:" (CGI/1.2) 24-JUN-2000 MGD SCRIPT_RTE for persistent run-time environments 10-MAY-2000 MGD refined CgiOutput() and CgiHeader() 08-APR-2000 MGD CGI binary header response can now be processed, (some) VMS Apache compatibility 31-DEC-1999 MGD PATH_ODS variable (to indicate on-disk file system) 17-DEC-1999 MGD bugfix; quote double-up in CgiVariable() 27-NOV-1999 MGD rework DCL variable stream, remove sys$fao() from stream CGI variable creation, avoid "''" substitution creating DCL symbol CGI variables 20-NOV-1999 MGD end-of-header blank line may now be an empty record, or a record containing a single '\n' or '\r\n' sequence 06-NOV-1999 MGD allow slightly more latitude with form fields, no "=value" (i.e. name terminated by '&') and back-to-back '&' 14-SEP-1999 MGD modify CgiOutput() for delayed CGI "Content-Type:" 28-AUG-1999 MGD support CGIplus "agents", added AUTH_USER 30-MAY-1999 MGD add SSL variables (via call to SesolaCgiGenerateVariables()) 21-FEB-1999 MGD change authorization CGI variables 28-DEC-1998 MGD added HTTP_ACCEPT_ENCODING 07-NOV-1998 MGD WATCH facility 17-OCT-1998 MGD CgiUrlDecodeString(), SERVER_CHARSET, REQUEST_CHARSET, REQUEST_CONTENT_TYPE 28-APR-1998 MGD CGI variable memory allocation changed in support of SSI 03-APR-1998 MGD bugfix; extra quotes generated in form field value 19-MAR-1998 MGD suppress 'Authorization:' unless "external" authorization 06-DEC-1997 MGD functionality unbundled from DCL.c, generalized for version 5 */ /*****************************************************************************/ #ifdef WASD_VMS_V7 #undef _VMS__V6__SOURCE #define _VMS__V6__SOURCE #undef __VMS_VER #define __VMS_VER 70000000 #undef __CRTL_VER #define __CRTL_VER 70000000 #endif /* standard C header files */ #include #include #include #include /* VMS related header files */ #include #include #include #include /* application header files */ #include "wasd.h" #define WASD_MODULE "CGI" /**********/ /* macros */ /**********/ #define CLI$_BUFOVF 229400 /******************/ /* global storage */ /******************/ char CgiNotStrict [] = "not a strict CGI response", ErrorCgiCli [] = "CLI reported \"!AZ\"", ErrorCgiSymbol [] = "DCL symbol assignment too large!", ErrorCgiNotStrict [] = "NOT a strict CGI response!!", ErrorCgiSetCookie [] = "Set-Cookie array exhausted"; /********************/ /* external storage */ /********************/ #ifdef DBUG extern BOOL Debug; #else #define Debug 0 #endif extern BOOL OdsExtended; extern int DclCgiHeaderSize, DclCgiPlusInSize, DclSysCommandSize, DclSysOutputSize, EfnWait, ServerPort, SsiSizeMax; extern int ToLowerCase[], ToUpperCase[]; extern unsigned long HttpdTime64[]; extern char DclCgiVariablePrefix[], ErrorSanityCheck[], SoftwareID[], TimeGmtString[]; extern CONFIG_STRUCT Config; extern HTTPD_PROCESS HttpdProcess; extern MSG_STRUCT Msgs; extern SYS_INFO SysInfo; extern WATCH_STRUCT Watch; /*****************************************************************************/ /* Creates a buffer containing a series of null-terminated strings, terminated by a null-string (two successive null characters). Each of the strings contains either DCL command(s) to create a DCL symbol (CGI variable) using the CLI, or an equate separated 'name=value' pair used for CGIplus variable streams, etc. */ int CgiGenerateVariables ( REQUEST_STRUCT *rqptr, int VarType ) { static $DESCRIPTOR (NumberFaoDsc, "!UL\0"); static $DESCRIPTOR (StatusValueFaoDsc, "%X!8UL\0"); static char BgDevNam [65]; static unsigned short BgDevNamLength; static struct { short buf_len; short item; char *buf_addr; short *ret_len; } BgDevNamItemList [] = { { sizeof(BgDevNam), DVI$_DEVNAM, &BgDevNam, &BgDevNamLength }, { 0, 0, 0, 0 } }; BOOL BodyUnEncodeStream; int idx, status, Count, EmptyFieldNameCount, KeyCount; char *cptr, *sptr, *zptr, *NameValuePtr; char FieldName [256], LocalDateTime [48]; #ifdef __VAX char String [1024]; #else /* allow for EDCL available with VMS 7.3-2 and later */ char String [8192]; #endif /* __VAX */ $DESCRIPTOR (StringDsc, String); struct { unsigned short Status; unsigned short Count; unsigned long Unused; } IOsb; DICT_ENTRY_STRUCT *denptr; /*********/ /* begin */ /*********/ if (WATCHMOD (rqptr, WATCH_MOD_CGI)) WatchThis (WATCHITM(rqptr), WATCH_MOD_CGI, "CgiGenerateVariables()"); if (WATCHING (rqptr, WATCH_CGI)) WatchThis (WATCHITM(rqptr), WATCH_CGI, "VARIABLES !&?stream\rdcl\r", VarType == CGI_VARIABLE_STREAM); if (rqptr->rqCgi.BufferLength) { /* free previous variables and storage */ if (rqptr->rqCgi.BufferPtr) VmFreeFromHeap (rqptr, rqptr->rqCgi.BufferPtr, FI_LI); rqptr->rqCgi.BufferLength = 0; rqptr->rqCgi.BufferPtr = rqptr->rqCgi.BufferCurrentPtr = NULL; } if (VarType == CGI_VARIABLE_STREAM) CgiVariableBufferMemory (rqptr, DclCgiPlusInSize*2); else CgiVariableBufferMemory (rqptr, DclSysCommandSize); HttpLocalTimeString (LocalDateTime, &rqptr->rqTime.BeginTime64); if (VMSnok (status = CgiVariable (rqptr, "AUTH_TYPE", rqptr->rqAuth.Type, VarType))) return (status); if (rqptr->rqAuth.Type[0] || rqptr->rqCgi.AgentScript) { if (rqptr->rqAuth.SourceRealm && rqptr->rqAuth.RequestCan) { if (VMSnok (status = CgiVariable (rqptr, "AUTH_ACCESS", AuthCanString (rqptr->rqAuth.RequestCan, AUTH_CAN_FORMAT_LONG), VarType))) return (status); } if (rqptr->rqAuth.PathParameterPtr && rqptr->rqAuth.PathParameterPtr[0]) { if (VMSnok (status = CgiVariable (rqptr, "AUTH_AGENT", rqptr->rqAuth.PathParameterPtr, VarType))) return (status); } if (rqptr->rqAuth.GroupWritePtr[0] && VMSok(rqptr->rqAuth.GroupWriteStatus)) { if (VMSnok (status = CgiVariable (rqptr, "AUTH_GROUP", rqptr->rqAuth.GroupWritePtr, VarType))) return (status); } else if (rqptr->rqAuth.GroupReadPtr[0] && VMSok(rqptr->rqAuth.GroupReadStatus)) { if (VMSnok (status = CgiVariable (rqptr, "AUTH_GROUP", rqptr->rqAuth.GroupReadPtr, VarType))) return (status); } if (rqptr->rqAuth.PathLocationPtr && rqptr->rqCgi.AgentScript) { if (VMSnok (status = CgiVariable (rqptr, "AUTH_PATH", rqptr->rqAuth.PathLocationPtr, VarType))) return (status); } if ((rqptr->RemoteUserPassword[0] && rqptr->rqAuth.PathParameterPtr && rqptr->rqAuth.PathParameterPtr[0]) || rqptr->rqAuth.SourceRealm == AUTH_SOURCE_EXTERNAL) { if (VMSnok (status = CgiVariable (rqptr, "AUTH_PASSWORD", rqptr->RemoteUserPassword, VarType))) return (status); } if (VMSnok (status = CgiVariable (rqptr, "AUTH_REALM", rqptr->rqAuth.RealmPtr, VarType))) return (status); if (VMSnok (status = CgiVariable (rqptr, "AUTH_REALM_DESCRIPTION", rqptr->rqAuth.RealmDescrPtr, VarType))) return (status); if (rqptr->rqAuth.RemoteUser[0]) if (VMSnok (status = CgiVariable (rqptr, "AUTH_REMOTE_USER", rqptr->rqAuth.RemoteUser, VarType))) return (status); if (rqptr->rqAuth.UserDetailsPtr && rqptr->rqAuth.UserDetailsPtr[0]) { if (VMSnok (status = CgiVariable (rqptr, "AUTH_USER", rqptr->rqAuth.UserDetailsPtr, VarType))) return (status); } } /* if there is an associated request body and it's being decoded */ if (rqptr->rqHeader.Method == HTTP_METHOD_POST) BodyUnEncodeStream = BodyReadUnEncode (rqptr); else BodyUnEncodeStream = false; if (BodyUnEncodeStream) { /* indicate the content-length is unknown because of the decode */ if (VMSnok (status = CgiVariable (rqptr, "CONTENT_LENGTH", "?", VarType))) return (status); } else { sys$fao (&NumberFaoDsc, 0, &StringDsc, rqptr->rqHeader.ContentLength); if (VMSnok (status = CgiVariable (rqptr, "CONTENT_LENGTH", String, VarType))) return (status); } if (rqptr->rqHeader.ContentTypePtr) { if (VMSnok (status = CgiVariable (rqptr, "CONTENT_TYPE", rqptr->rqHeader.ContentTypePtr, VarType))) return (status); } else if (rqptr->rqContentInfo.ContentTypePtr && !strsame (rqptr->rqContentInfo.ContentTypePtr, "x-internal/", 11)) { if (VMSnok (status = CgiVariable (rqptr, "CONTENT_TYPE", rqptr->rqContentInfo.ContentTypePtr, VarType))) return (status); } else { if (VMSnok (status = CgiVariable (rqptr, "CONTENT_TYPE", "", VarType))) return (status); } if (!(cptr = rqptr->rqPathSet.MapRootPtr)) cptr = ""; if (VMSnok (status = CgiVariable (rqptr, "DOCUMENT_ROOT", cptr, VarType))) return (status); if (Config.cfScript.GatewayBg && rqptr->ServicePtr->RequestScheme == SCHEME_HTTP) { status = sys$getdviw (EfnWait, rqptr->NetIoPtr->Channel, 0, &BgDevNamItemList, &IOsb, 0, 0, 0); if (VMSok (status)) status = IOsb.Status; if (VMSok(status)) { BgDevNam[BgDevNamLength] = '\0'; if (VMSnok (status = CgiVariable (rqptr, "GATEWAY_BG", BgDevNam[0] == '_' ? BgDevNam+1 : BgDevNam, VarType))) return (status); } else return (status); } if (VMSnok (status = CgiVariable (rqptr, "GATEWAY_INTERFACE", "CGI/1.1", VarType))) return (status); if (VarType == CGI_VARIABLE_STREAM) { /* if 'stream' then must be CGIplus */ if (rqptr->rqCgi.EofLength) if (VMSnok (status = CgiVariable (rqptr, "GATEWAY_EOF", rqptr->rqCgi.EofStr, VarType))) return (status); } if (rqptr->rqCgi.EotLength) if (VMSnok (status = CgiVariable (rqptr, "GATEWAY_EOT", rqptr->rqCgi.EotStr, VarType))) return (status); if (rqptr->rqCgi.EscLength) if (VMSnok (status = CgiVariable (rqptr, "GATEWAY_ESC", rqptr->rqCgi.EscStr, VarType))) return (status); if (rqptr->DclTaskPtr) { /* size of mailbox */ sys$fao (&NumberFaoDsc, 0, &StringDsc, DclSysOutputSize); if (VMSnok (status = CgiVariable (rqptr, "GATEWAY_MRS", String, VarType))) return (status); } /*************/ /* SET html= */ /*************/ if (rqptr->rqPathSet.HtmlBodyTagPtr) if (VMSnok (status = CgiVariable (rqptr, "HTML_BODYTAG", rqptr->rqPathSet.HtmlBodyTagPtr, VarType))) return (status); if (rqptr->rqPathSet.HtmlFooterPtr) if (VMSnok (status = CgiVariable (rqptr, "HTML_FOOTER", rqptr->rqPathSet.HtmlFooterPtr, VarType))) return (status); if (rqptr->rqPathSet.HtmlFooterTagPtr) if (VMSnok (status = CgiVariable (rqptr, "HTML_FOOTERTAG", rqptr->rqPathSet.HtmlFooterTagPtr, VarType))) return (status); if (rqptr->rqPathSet.HtmlHeaderPtr) if (VMSnok (status = CgiVariable (rqptr, "HTML_HEADER", rqptr->rqPathSet.HtmlHeaderPtr, VarType))) return (status); if (rqptr->rqPathSet.HtmlHeaderTagPtr) if (VMSnok (status = CgiVariable (rqptr, "HTML_HEADERTAG", rqptr->rqPathSet.HtmlHeaderTagPtr, VarType))) return (status); /**********************************/ /* request header field variables */ /**********************************/ memcpy (FieldName, "HTTP_", 5); zptr = FieldName + sizeof(FieldName); DictIterate (rqptr->rqDictPtr, NULL); while (denptr = DictIterate (rqptr->rqDictPtr, DICT_TYPE_REQUEST)) { cptr = DICT_GET_KEY(denptr); /* these are not supplied for one reason or another */ if (MATCH14 (cptr, "authorization") && rqptr->rqAuth.SourceRealm != AUTH_SOURCE_AGENT_OPAQUE && rqptr->rqAuth.SourceRealm != AUTH_SOURCE_EXTERNAL && rqptr->rqAuth.SourceRealm != AUTH_SOURCE_OPAQUE) continue; if (MATCH4(cptr,"prox")) { if (MATCH20 (cptr, "proxy-authorization")) continue; /* mitigate httpoxy vulnerability by introducing an extra underscore */ if (MATCH6 (cptr, "proxy")) cptr = "_PROXY"; } sptr = FieldName + 5; while (*cptr && sptr < zptr) { if (isalnum(*cptr)) *sptr++ = TOUP(*cptr); else if (!ISLWS(*cptr)) *sptr++ = '_'; cptr++; } if (sptr >= zptr) { ErrorVmsStatus (rqptr, SS$_RESULTOVF, FI_LI); return (STS$K_ERROR); } *sptr = '\0'; cptr = DICT_GET_VALUE(denptr); if (VMSnok (status = CgiVariable (rqptr, FieldName, cptr, VarType))) return (status); } /*********************/ /* rest of variables */ /*********************/ if (VMSnok (status = CgiVariable (rqptr, "PATH_INFO", rqptr->rqHeader.PathInfoPtr, VarType))) return (status); switch (rqptr->PathOds) { case 0 : cptr = NULL; break; case MAPURL_PATH_ODS_2 : cptr = "2"; break; case MAPURL_PATH_ODS_5 : if (OdsExtended) cptr = "5"; break; case MAPURL_PATH_ODS_ADS : cptr = "ADS"; break; case MAPURL_PATH_ODS_PWK : cptr = "PWK"; break; case MAPURL_PATH_ODS_SMB : cptr = "SMB"; break; case MAPURL_PATH_ODS_SRI : cptr = "SRI"; break; default : cptr = "?"; } if (cptr) if (VMSnok (status = CgiVariable (rqptr, "PATH_ODS", cptr, VarType))) return (status); cptr = rqptr->ParseOds.ExpFileName; if (*cptr && rqptr->rqPathSet.ScriptSyntaxUnix) { if (VMSnok (status = MapOdsVmsToUnix (cptr, String, sizeof(String)))) { ErrorVmsStatus (rqptr, status, FI_LI); return (status); } cptr = String; } if (VMSnok (status = CgiVariable (rqptr, "PATH_TRANSLATED", cptr, VarType))) return (status); if (VMSnok (status = CgiVariable (rqptr, "QUERY_STRING", rqptr->rqHeader.QueryStringPtr, VarType))) return (status); if (VMSnok (status = CgiVariable (rqptr, "REMOTE_ADDR", &rqptr->ClientPtr->IpAddressString, VarType))) return (status); if (VMSnok (status = CgiVariable (rqptr, "REMOTE_HOST", rqptr->ClientPtr->Lookup.HostName, VarType))) return (status); /* if client address has been rewritten the port is no longer reliable */ if (rqptr->ClientPtr->SetClientAddress) SET2(String,'0\0'); else sys$fao (&NumberFaoDsc, 0, &StringDsc, rqptr->ClientPtr->IpPort); if (VMSnok (status = CgiVariable (rqptr, "REMOTE_PORT", String, VarType))) return (status); if (rqptr->rqAuth.CaseLess) { /* force remote user name to upper-case if a case-less authentication */ zptr = (sptr = String) + sizeof(String)-1; for (cptr = rqptr->RemoteUser; *cptr && sptr < zptr; *sptr++ = TOUP(*cptr++)); *sptr = '\0'; if (VMSnok (status = CgiVariable (rqptr, "REMOTE_USER", String, VarType))) return (status); } else if (VMSnok (status = CgiVariable (rqptr, "REMOTE_USER", rqptr->RemoteUser, VarType))) return (status); if (rqptr->rqPathSet.CharsetPtr && rqptr->rqPathSet.CharsetPtr[0] && rqptr->rqPathSet.CharsetPtr[0] != '(') { if (VMSnok (status = CgiVariable (rqptr, "REQUEST_CHARSET", rqptr->rqPathSet.CharsetPtr, VarType))) return (status); } if (rqptr->rqPathSet.ContentTypePtr) { if (VMSnok (status = CgiVariable (rqptr, "REQUEST_CONTENT_TYPE", rqptr->rqPathSet.ContentTypePtr, VarType))) return (status); } if (VMSnok (status = CgiVariable (rqptr, "REQUEST_METHOD", rqptr->rqHeader.MethodName, VarType))) return (status); if (HTTP2_REQUEST(rqptr)) cptr = "HTTP/2"; else if (rqptr->rqHeader.HttpVersion == HTTP_VERSION_1_1) cptr = "HTTP/1.1"; else if (rqptr->rqHeader.HttpVersion == HTTP_VERSION_1_0) cptr = "HTTP/1.0"; else cptr = "HTTP/0.9"; if (VMSnok (status = CgiVariable (rqptr, "REQUEST_PROTOCOL", cptr, VarType))) return (status); if (HTTP2_REQUEST(rqptr)) if (rqptr->Http2Stream.Http2Ptr->PingMicroSeconds) if (denptr = DictLookup (rqptr->rqDictPtr, DICT_TYPE_INTERNAL, "http2_ping", 10)) if (VMSnok (status = CgiVariable (rqptr, "HTTP2_PING", DICT_GET_VALUE(denptr), VarType))) return (status); if (VMSnok (status = CgiVariable (rqptr, "REQUEST_SCHEME", rqptr->ServicePtr->RequestSchemeNamePtr, VarType))) return (status); if (VMSnok (status = CgiVariable (rqptr, "REQUEST_TIME_GMT", rqptr->rqTime.GmDateTime, VarType))) return (status); if (VMSnok (status = CgiVariable (rqptr, "REQUEST_TIME_LOCAL", LocalDateTime, VarType))) return (status); if (VMSnok (status = CgiVariable (rqptr, "REQUEST_URI", rqptr->rqHeader.RequestUriPtr, VarType))) return (status); /* Apache-like */ if (rqptr->rqCgi.ScriptFileNamePtr && rqptr->rqCgi.ScriptFileNamePtr[0]) { cptr = rqptr->rqCgi.ScriptFileNamePtr; if (rqptr->rqPathSet.ScriptSyntaxUnix) { if (VMSnok (status = MapOdsVmsToUnix (cptr, String, sizeof(String)))) { ErrorVmsStatus (rqptr, status, FI_LI); return (status); } cptr = String; } if (VMSnok (status = CgiVariable (rqptr, "SCRIPT_FILENAME", cptr, VarType))) return (status); } if (rqptr->rqPathSet.ScriptDefaultPtr && rqptr->rqPathSet.ScriptDefaultPtr[0] != '#') { /* script default directory ('#' means backward-compatible) */ if (VMSnok (status = CgiVariable (rqptr, "SCRIPT_DEFAULT", rqptr->rqPathSet.ScriptDefaultPtr, VarType))) return (status); } /* if it's "/" (empty from mapping) then make it "" (really empty :-) */ if (VMSnok (status = CgiVariable (rqptr, "SCRIPT_NAME", rqptr->ScriptName[0] && rqptr->ScriptName[1] ? rqptr->ScriptName : "", VarType))) return (status); if (rqptr->DclTaskPtr && rqptr->DclTaskPtr->ScriptRunTime[0] && rqptr->DclTaskPtr->ScriptRunTime[0] != '!') { /* Run-Time Environment (RTE engine file name) */ if (VMSnok (status = CgiVariable (rqptr, "SCRIPT_RTE", rqptr->DclTaskPtr->ScriptRunTime, VarType))) return (status); } if (VMSnok (status = CgiVariable (rqptr, "SERVER_ADDR", rqptr->ServicePtr->ServerIpAddressString, VarType))) return (status); /* Apache-like */ if (Config.cfServer.AdminEmail[0]) if (VMSnok (status = CgiVariable (rqptr, "SERVER_ADMIN", Config.cfServer.AdminEmail, VarType))) return (status); if (VMSnok (status = CgiVariable (rqptr, "SERVER_CHARSET", Config.cfContent.CharsetDefault, VarType))) return (status); if (VMSnok (status = CgiVariable (rqptr, "SERVER_GMT", TimeGmtString, VarType))) return (status); if (rqptr->ClientPtr->MultiHomeIpAddressString[0]) if (VMSnok (status = CgiVariable (rqptr, "SERVER_MULTIHOME", rqptr->ClientPtr->MultiHomeIpAddressString, VarType))) return (status); if (VMSnok (status = CgiVariable (rqptr, "SERVER_NAME", rqptr->ServicePtr->ServerHostName, VarType))) return (status); if (rqptr->rqResponse.HttpVersion == HTTP_VERSION_1_1) cptr = "HTTP/1.1"; else if (rqptr->rqResponse.HttpVersion == HTTP_VERSION_1_0) cptr = "HTTP/1.0"; else cptr = "HTTP/0.9"; if (VMSnok (status = CgiVariable (rqptr, "SERVER_PROTOCOL", cptr, VarType))) return (status); if (VMSnok (status = CgiVariable (rqptr, "SERVER_PORT", rqptr->ServicePtr->ServerPortString, VarType))) return (status); /* Apache-like */ ServerSignature (rqptr, String, sizeof(String)); if (String[0]) if (VMSnok (status = CgiVariable (rqptr, "SERVER_SIGNATURE", String, VarType))) return (status); if (VMSnok (status = CgiVariable (rqptr, "SERVER_SOFTWARE", SoftwareID, VarType))) return (status); /* Apache-like */ if (VMSnok (status = CgiVariable (rqptr, "UNIQUE_ID", GenerateUniqueId(rqptr), VarType))) return (status); /* if the client address has been rewritten */ if (rqptr->ClientPtr->SetClientAddress) if (VMSnok (status = CgiVariable (rqptr, "UPSTREAM_ADDR", rqptr->ClientPtr->UpstreamIpAddressString, VarType))) return (status); if (rqptr->RawSocketRequest) { if (rqptr->rqWebSocket.InputChannel) { if (VMSnok (status = CgiVariable (rqptr, "RAWSOCKET_INPUT", rqptr->rqWebSocket.InputDevName, VarType))) return (status); sys$fao (&NumberFaoDsc, 0, &StringDsc, rqptr->rqWebSocket.InputSize); if (VMSnok (status = CgiVariable (rqptr, "RAWSOCKET_INPUT_MRS", String, VarType))) return (status); } if (rqptr->rqWebSocket.OutputChannel) { if (VMSnok (status = CgiVariable (rqptr, "RAWSOCKET_OUTPUT", rqptr->rqWebSocket.OutputDevName, VarType))) return (status); sys$fao (&NumberFaoDsc, 0, &StringDsc, rqptr->rqWebSocket.OutputSize); if (VMSnok (status = CgiVariable (rqptr, "RAWSOCKET_OUTPUT_MRS", String, VarType))) return (status); } } else if (rqptr->WebSocketRequest) { sys$fao (&NumberFaoDsc, 0, &StringDsc, rqptr->rqHeader.WebSocketVersion); if (VMSnok (status = CgiVariable (rqptr, "WEBSOCKET_VERSION", String, VarType))) return (status); if (rqptr->rqWebSocket.InputChannel) { if (VMSnok (status = CgiVariable (rqptr, "WEBSOCKET_INPUT", rqptr->rqWebSocket.InputDevName, VarType))) return (status); sys$fao (&NumberFaoDsc, 0, &StringDsc, rqptr->rqWebSocket.InputSize); if (VMSnok (status = CgiVariable (rqptr, "WEBSOCKET_INPUT_MRS", String, VarType))) return (status); } if (rqptr->rqWebSocket.OutputChannel) { if (VMSnok (status = CgiVariable (rqptr, "WEBSOCKET_OUTPUT", rqptr->rqWebSocket.OutputDevName, VarType))) return (status); sys$fao (&NumberFaoDsc, 0, &StringDsc, rqptr->rqWebSocket.OutputSize); if (VMSnok (status = CgiVariable (rqptr, "WEBSOCKET_OUTPUT_MRS", String, VarType))) return (status); } } /* if we're WATCHing you - say something! */ if (WATCHING (rqptr, WATCH_SCRIPT)) if (VMSnok (status = CgiVariable (rqptr, "WATCH_SCRIPT", "TRUE", VarType))) return (status); /*****************/ /* SSL variables */ /*****************/ if (VMSnok (status = SesolaCgiGenerateVariables (rqptr, VarType))) return (status); /***************************/ /* query string components */ /***************************/ if (rqptr->rqHeader.QueryStringLength && !rqptr->rqPathSet.ScriptQueryNone) { EmptyFieldNameCount = KeyCount = 0; cptr = rqptr->rqHeader.QueryStringPtr; while (*cptr && *cptr != '=') cptr++; /* if an equal symbol was found then its a form not a keyword search */ if (*cptr) { /***************/ /* form fields */ /***************/ memcpy (FieldName, "FORM_", 5); cptr = rqptr->rqHeader.QueryStringPtr; while (*cptr) { sptr = FieldName + 5; zptr = FieldName + sizeof(FieldName); while (*cptr && *cptr != '=' && *cptr != '&' && sptr < zptr) { if (isalnum(*cptr)) *sptr++ = TOUP(*cptr++); else { *sptr++ = '_'; cptr++; } } if (sptr >= zptr) { ErrorVmsStatus (rqptr, SS$_RESULTOVF, FI_LI); return (STS$K_ERROR); } *sptr = '\0'; if (!FieldName[5] || (*cptr && *cptr != '=' && *cptr != '&')) { if (*cptr == '&') { /* allow back-to-back '&' (effectively null field name) */ cptr++; continue; } /* out-of-place '=' (no field name) */ if (rqptr->rqPathSet.ScriptQueryRelaxed) { /* let's be relaxed about this, script will handle it */ sys$fao (&NumberFaoDsc, 0, &StringDsc, ++EmptyFieldNameCount); FieldName[5] = '_'; strcpy (FieldName+6, String); } else { rqptr->rqResponse.HttpStatus = 400; ErrorGeneral (rqptr, MsgFor(rqptr,MSG_REQUEST_URL_FORM), FI_LI); return (STS$K_ERROR); } } /* if encountered an '=' */ if (*cptr == '=') cptr++; Count = CgiUrlDecodeString (rqptr, cptr, String, sizeof(String), '&'); if (Count < 0) return (STS$K_ERROR); cptr += Count; if (VMSnok (status = CgiVariable (rqptr, FieldName, String, VarType))) return (status); } if (VMSnok (status = CgiVariable (rqptr, "KEY_COUNT", "0", VarType))) return (status); } else { /******************/ /* query keywords */ /******************/ cptr = rqptr->rqHeader.QueryStringPtr; while (*cptr) { sys$fao (&NumberFaoDsc, 0, &StringDsc, ++KeyCount); memcpy (FieldName, "KEY_", 4); strcpy (FieldName+4, String); Count = CgiUrlDecodeString (rqptr, cptr, String, sizeof(String), '+'); if (Count < 0) return (STS$K_ERROR); cptr += Count; if (VMSnok (status = CgiVariable (rqptr, FieldName, String, VarType))) return (status); } sys$fao (&NumberFaoDsc, 0, &StringDsc, KeyCount); if (VMSnok (status = CgiVariable (rqptr, "KEY_COUNT", String, VarType))) return (status); } } else if (!rqptr->rqPathSet.ScriptQueryNone) { /* no keywords if no query string! */ if (VMSnok (status = CgiVariable (rqptr, "KEY_COUNT", "0", VarType))) return (status); } /*********************/ /* SET script=params */ /*********************/ if (rqptr->rqPathSet.ScriptParamsPtr) { if (WATCHMOD (rqptr, WATCH_MOD_CGI)) WatchThis (WATCHITM(rqptr), WATCH_MOD_CGI, "!&Z", rqptr->rqPathSet.ScriptParamsPtr); NameValuePtr = rqptr->rqPathSet.ScriptParamsPtr; for (;;) { status = StringParseNameValue (&NameValuePtr, true, FieldName, sizeof(FieldName), String, sizeof(String)); if (VMSnok (status)) break; status = CgiVariable (rqptr, FieldName, String, VarType); if (VMSnok (status)) break; } if (status != SS$_ENDOFFILE) ErrorVmsStatus (rqptr, status, FI_LI); } /******************/ /* dictionary CGI */ /******************/ /* these can be used to overwrite any CGI variable! */ DictIterate (rqptr->rqDictPtr, NULL); while (denptr = DictIterate (rqptr->rqDictPtr, DICT_TYPE_CONFIG)) { cptr = DICT_GET_KEY(denptr); if (!MATCH4 (cptr, "cgi_")) continue; sptr = DICT_GET_VALUE(denptr); if (VMSnok (status = CgiVariable (rqptr, cptr+4, sptr, VarType))) return (status); } /*********************/ /* agent script-meta */ /*********************/ if (rqptr->rqAuth.ScriptMetaPtr) { NameValuePtr = rqptr->rqAuth.ScriptMetaPtr; for (;;) { status = StringParseNullNameValue (&NameValuePtr, true, FieldName, sizeof(FieldName), String, sizeof(String)); if (VMSnok (status)) break; status = CgiVariable (rqptr, FieldName, String, VarType); if (VMSnok (status)) break; } if (status != SS$_ENDOFFILE) ErrorVmsStatus (rqptr, status, FI_LI); } /***********/ /* finally */ /***********/ if (rqptr->rqCgi.SymbolTruncatePtr) if (VMSnok (status = CgiVariable (rqptr, "SERVER_TRUNCATE", rqptr->rqCgi.SymbolTruncatePtr, VarType))) return (status); /********************/ /* end of variables */ /********************/ if (WATCHING (rqptr, WATCH_CGI)) WatchThis (WATCHITM(rqptr), WATCH_CGI, "VARIABLES !&,UL bytes", rqptr->rqCgi.BufferLength - rqptr->rqCgi.BufferRemaining); return (CgiVariable (rqptr, NULL, NULL, VarType)); } /*****************************************************************************/ /* URL-decode a URL-encoded string! Return -1 if an error occurs, otherwise return the number of characters read from the original, encoded string. */ int CgiUrlDecodeString ( REQUEST_STRUCT *rqptr, char *EncodedString, char *DecodedString, int SizeofDecodedString, char Separator ) { unsigned char ch; char *cptr, *sptr, *zptr; /*********/ /* begin */ /*********/ if (WATCHMOD (rqptr, WATCH_MOD_CGI)) WatchThis (WATCHITM(rqptr), WATCH_MOD_CGI, "CgiUrlDecodeString() !&Z", EncodedString); cptr = EncodedString; zptr = (sptr = DecodedString) + SizeofDecodedString; while (*cptr && *cptr != Separator && sptr < zptr) { if (*cptr == '+') { *sptr++ = ' '; cptr++; } else if (*cptr == '%') { /* an escaped character ("%xx" where xx is a hex number) */ cptr++; ch = 0; if (*cptr >= '0' && *cptr <= '9') { ch = (*cptr - (int)'0') << 4; cptr++; } else if (TOLO(*cptr) >= 'a' && TOLO(*cptr) <= 'f') { ch = (TOLO(*cptr) - (int)'a' + 10) << 4; cptr++; } else { rqptr->rqResponse.HttpStatus = 400; ErrorGeneral (rqptr, MsgFor(rqptr,MSG_REQUEST_URL_ENC), FI_LI); return (-1); } if (*cptr >= '0' && *cptr <= '9') { ch += (*cptr - (int)'0'); cptr++; } else if (TOLO(*cptr) >= 'a' && TOLO(*cptr) <= 'f') { ch += (TOLO(*cptr) - (int)'a' + 10); cptr++; } else { rqptr->rqResponse.HttpStatus = 400; ErrorGeneral (rqptr, MsgFor(rqptr,MSG_REQUEST_URL_ENC), FI_LI); return (-1); } if (sptr < zptr) *sptr++ = ch; } else *sptr++ = *cptr++; } if (sptr >= zptr) { ErrorGeneral (rqptr, MsgFor(rqptr,MSG_GENERAL_OVERFLOW), FI_LI); return (-1); } *sptr = '\0'; if (*cptr) cptr++; if (WATCHMOD (rqptr, WATCH_MOD_CGI)) WatchDataFormatted ("!&Z\n", DecodedString); return (cptr - EncodedString); } /*****************************************************************************/ /* Depending on whether the CGI variable is for creation at the DCL CLI or as part of a CGI variable stream (e.g. CGIplus) create a null-terminated string containing either commands to create a DCL symbol, or a 'name=value' pair. DCL symbol creation at the command line is limited by the CLI command line length (255 characters for pre 7.3-2, 4095 for post 7.3-1). Symbol values however can be up to 1024 (or pragmatically about 1000 characters) for pre 7.3-2 and 8192 (or thereabouts) for post 7.3-1, probably enough for any CGI variable value. If a CGI value is too large for for a single command-line assignment then build it up using multiple assignments, a symbol assignment kludge! */ int CgiVariable ( REQUEST_STRUCT *rqptr, char *SymbolName, char *SymbolValue, int VarType ) { static char DeleteAllLocalSymbol[] = "DELETE/SYMBOL/LOCAL/ALL"; int status, CliLineSize, CliSymbolSize, SymbolCount, SymbolValueLength; unsigned short Length; char *cptr, *sptr, *zptr, *CgiPlusInCCPtr, *CgiVariablePrefixPtr; char NameString [256], ValueString [2048]; /*********/ /* begin */ /*********/ if (WATCHMOD (rqptr, WATCH_MOD_CGI)) WatchThis (WATCHITM(rqptr), WATCH_MOD_CGI, "CgiVariable() !UL !AZ !&Z", VarType, SymbolName, SymbolValue); if (!SymbolName) { /************************/ /* end of CGI variables */ /************************/ if (rqptr->rqCgi.BufferRemaining < sizeof(short)) CgiVariableBufferMemory (rqptr, sizeof(short)); /* zero length record terminates generated CGI variables */ SET2(rqptr->rqCgi.BufferCurrentPtr,0); rqptr->rqCgi.BufferRemaining -= sizeof(short); rqptr->rqCgi.BufferCurrentPtr += sizeof(short); return (SS$_NORMAL); } if (!SymbolValue) { /************************/ /* parse = */ /************************/ zptr = (sptr = NameString) + sizeof(NameString)-1; cptr = SymbolName; while (*cptr && *cptr != '=' && sptr < zptr) *sptr++ = *cptr++; *sptr = '\0'; while (*cptr && *cptr != '=') cptr++; if (*cptr) cptr++; zptr = (sptr = ValueString) + sizeof(ValueString)-1; while (*cptr && sptr < zptr) *sptr++ = *cptr++; *sptr = '\0'; SymbolName = NameString; SymbolValue = ValueString; } if (rqptr->rqPathSet.CgiPrefixPtr) CgiVariablePrefixPtr = rqptr->rqPathSet.CgiPrefixPtr; else CgiVariablePrefixPtr = DclCgiVariablePrefix; CgiPlusInCCPtr = rqptr->rqPathSet.CgiPlusInCC; if (!SymbolValue) SymbolValue = ""; if (WATCHING (rqptr, WATCH_CGI)) WatchDataFormatted ("!AZ=!AZ\n", SymbolName, SymbolValue); if (VarType == CGI_VARIABLE_STREAM) { /******************************/ /* variables in record stream */ /******************************/ if (rqptr->rqCgi.BufferRemaining <= DclCgiPlusInSize + sizeof(short)) CgiVariableBufferMemory (rqptr, DclCgiPlusInSize*2); zptr = (sptr = rqptr->rqCgi.BufferCurrentPtr + sizeof(short)) + (rqptr->rqCgi.BufferRemaining - sizeof(short)); for (cptr = CgiVariablePrefixPtr; *cptr; *sptr++ = *cptr++); for (cptr = SymbolName; *cptr && sptr < zptr; *sptr++ = *cptr++); if (sptr < zptr) *sptr++ = '='; for (cptr = SymbolValue; *cptr && sptr < zptr; *sptr++ = *cptr++); if (sptr < zptr && CgiPlusInCCPtr[0]) *sptr++ = CgiPlusInCCPtr[0]; if (sptr < zptr && CgiPlusInCCPtr[1]) *sptr++ = CgiPlusInCCPtr[1]; if (sptr < zptr) *sptr++ = '\0'; if (sptr < zptr) { Length = sptr - (char*)rqptr->rqCgi.BufferCurrentPtr - sizeof(short); SET2(rqptr->rqCgi.BufferCurrentPtr,Length); rqptr->rqCgi.BufferRemaining -= Length + sizeof(short); rqptr->rqCgi.BufferCurrentPtr += Length + sizeof(short); return (SS$_NORMAL); } ErrorGeneral (rqptr, MsgFor(rqptr,MSG_GENERAL_OVERFLOW), FI_LI); return (STS$K_ERROR); } /********************************/ /* variables created at DCL CLI */ /********************************/ if (SysInfo.VersionInteger >= 732) { /* take advantage of EDCL - ultimately limited by 'DclSysCommandSize'! */ CliLineSize = 4095; CliSymbolSize = 8191; } else { /* same old command-line length we've grown up knowing and hating */ CliLineSize = 255; CliSymbolSize = 1023; } if (rqptr->rqCgi.BufferRemaining <= CliLineSize + sizeof(short)) CgiVariableBufferMemory (rqptr, DclSysCommandSize); /********************************/ /* try for a single assignment! */ /********************************/ zptr = (sptr = rqptr->rqCgi.BufferCurrentPtr + sizeof(short)) + CliLineSize; for (cptr = CgiVariablePrefixPtr; *cptr; *sptr++ = *cptr++); for (cptr = SymbolName; *cptr && sptr < zptr; *sptr++ = *cptr++); if (sptr < zptr) *sptr++ = '='; if (sptr < zptr) *sptr++ = '='; if (sptr < zptr) *sptr++ = '\"'; cptr = SymbolValue; while (*cptr && sptr < zptr) { if (*cptr == '\"') { /* escape double quotes, by doubling up */ if (sptr+2 < zptr) { cptr++; *sptr++ = '\"'; *sptr++ = '\"'; } else zptr = sptr; continue; } if (SAME2(cptr,'\'\'')) { /* escape doubled single quotes by concatenation */ if (sptr+4 < zptr) { cptr++; memcpy (sptr, "\'\"+\"", 4); sptr += 4; } else zptr = sptr; continue; } if (sptr < zptr) *sptr++ = *cptr++; } if (sptr < zptr) *sptr++ = '\"'; if (sptr < zptr) { /**********************************/ /* fitted into the one assignment */ /**********************************/ *sptr++ = '\0'; Length = sptr - (char*)rqptr->rqCgi.BufferCurrentPtr - sizeof(short); SET2(rqptr->rqCgi.BufferCurrentPtr,Length); rqptr->rqCgi.BufferRemaining -= Length + sizeof(short); rqptr->rqCgi.BufferCurrentPtr += Length + sizeof(short); return (SS$_NORMAL); } /************************/ /* multiple assignments */ /************************/ /* plus one allows for the first equate symbol in the global assignment */ SymbolValueLength = strlen(SymbolName) + 1; /* loop assigning maximum amount allowed by DCL until all assigned */ SymbolCount = 0; cptr = SymbolValue; while (*cptr) { if (rqptr->rqCgi.BufferRemaining <= CliLineSize + sizeof(short)) CgiVariableBufferMemory (rqptr, DclSysCommandSize); if (WATCHMOD (rqptr, WATCH_MOD_CGI)) WatchDataFormatted ("!&Z\n", cptr); /* set this to DCL line length minus one for the terminating quote */ zptr = (sptr = rqptr->rqCgi.BufferCurrentPtr + sizeof(short)) + CliLineSize - 1; /* allow for each of the +B+C apparently implemented as +".."+".." */ SymbolValueLength += 3; if (SymbolValueLength >= CliSymbolSize) break; switch (++SymbolCount) { case 1 : memcpy (sptr, "A=\"", 3); break; case 2 : memcpy (sptr, "B=\"", 3); break; case 3 : memcpy (sptr, "C=\"", 3); break; case 4 : memcpy (sptr, "D=\"", 3); break; case 5 : memcpy (sptr, "E=\"", 3); break; case 6 : memcpy (sptr, "F=\"", 3); break; default : memcpy (sptr, "X=\"", 3); break; } sptr += 3; while (*cptr && sptr < zptr) { SymbolValueLength++; /* quit one before completely full to allow for trailing quote */ if (SymbolValueLength >= CliSymbolSize) break; if (*cptr == '\"') { /* escape double quotes, by doubling up */ if (sptr+2 < zptr) { cptr++; *sptr++ = '\"'; *sptr++ = '\"'; } else zptr = sptr; continue; } if (SAME2(cptr,'\'\'')) { /* escape doubled single quotes by concatenation */ if (sptr+4 < zptr) { cptr++; memcpy (sptr, "\'\"+\"", 4); sptr += 4; } else zptr = sptr; continue; } *sptr++ = *cptr++; } *sptr++ = '\"'; *sptr++ = '\0'; Length = sptr - (char*)rqptr->rqCgi.BufferCurrentPtr - sizeof(short); SET2(rqptr->rqCgi.BufferCurrentPtr,Length); rqptr->rqCgi.BufferRemaining -= Length + sizeof(short); rqptr->rqCgi.BufferCurrentPtr += Length + sizeof(short); if (SymbolValueLength >= CliSymbolSize) break; } if (SymbolValueLength >= CliSymbolSize) { /**************************/ /* symbol value too large */ /**************************/ if (rqptr->rqPathSet.ScriptSymbolTruncate) { /* add this CGI variable name to the truncated symbol(s) list */ rqptr->rqCgi.SymbolTruncatePtr = VmReallocHeap (rqptr, rqptr->rqCgi.SymbolTruncatePtr, rqptr->rqCgi.SymbolTruncateLength + strlen(SymbolName)+2, FI_LI); if (rqptr->rqCgi.SymbolTruncateLength) { strcat (rqptr->rqCgi.SymbolTruncatePtr, ","); rqptr->rqCgi.SymbolTruncateLength++; } strcat (rqptr->rqCgi.SymbolTruncatePtr, SymbolName); rqptr->rqCgi.SymbolTruncateLength += strlen(SymbolName); } else { /* cannot create a symbol this size */ rqptr->rqResponse.ErrorTextPtr = SymbolName; rqptr->rqResponse.ErrorOtherTextPtr = ErrorCgiSymbol; ErrorVmsStatus (rqptr, CLI$_BUFOVF, FI_LI); ErrorNoticed (rqptr, CLI$_BUFOVF, "!AZ too big at !UL characters,", FI_LI, SymbolName, strlen(SymbolValue)); return (STS$K_ERROR); } } /* assign the temporary symbol value(s) to the CGI symbol */ if (rqptr->rqCgi.BufferRemaining <= CliLineSize + sizeof(short)) CgiVariableBufferMemory (rqptr, DclSysCommandSize); sptr = rqptr->rqCgi.BufferCurrentPtr + sizeof(short); for (cptr = CgiVariablePrefixPtr; *cptr; *sptr++ = *cptr++); for (cptr = SymbolName; *cptr; *sptr++ = *cptr++); switch (SymbolCount) { case 1 : memcpy (sptr, "==A", 4); sptr += 4; break; case 2 : memcpy (sptr, "==A+B", 6); sptr += 6; break; case 3 : memcpy (sptr, "==A+B+C", 8); sptr += 8; break; case 4 : memcpy (sptr, "==A+B+C+D", 10); sptr += 10; break; case 5 : memcpy (sptr, "==A+B+C+D+E", 12); sptr += 12; break; case 6 : memcpy (sptr, "==A+B+C+D+E+F", 14); sptr += 14; break; default : break; } Length = sptr - (char*)rqptr->rqCgi.BufferCurrentPtr - sizeof(short); SET2(rqptr->rqCgi.BufferCurrentPtr,Length); rqptr->rqCgi.BufferRemaining -= Length + sizeof(short); rqptr->rqCgi.BufferCurrentPtr += Length + sizeof(short); /* not really necessary, but let's be tidy */ if (rqptr->rqCgi.BufferRemaining <= sizeof(DeleteAllLocalSymbol) + sizeof(short)) CgiVariableBufferMemory (rqptr, DclSysCommandSize); memcpy (rqptr->rqCgi.BufferCurrentPtr + sizeof(short), DeleteAllLocalSymbol, sizeof(DeleteAllLocalSymbol)); SET2(rqptr->rqCgi.BufferCurrentPtr,sizeof(DeleteAllLocalSymbol)); rqptr->rqCgi.BufferRemaining -= sizeof(DeleteAllLocalSymbol) + sizeof(short); rqptr->rqCgi.BufferCurrentPtr += sizeof(DeleteAllLocalSymbol) + sizeof(short); return (SS$_NORMAL); } /*****************************************************************************/ /* Allocate, or reallocate on subsequent calls, memory for storing the CGI variables in. Adjusts the CGI variable buffer pointers, lengths, etc, appropriately. */ void CgiVariableBufferMemory ( REQUEST_STRUCT *rqptr, int BufferChunk ) { int CurrentLength, CurrentOffset; /*********/ /* begin */ /*********/ if (WATCHMOD (rqptr, WATCH_MOD_CGI)) WatchThis (WATCHITM(rqptr), WATCH_MOD_CGI, "CgiVariableBufferMemory() !UL !UL !UL", BufferChunk, rqptr->rqCgi.BufferLength, rqptr->rqCgi.BufferCurrentPtr - rqptr->rqCgi.BufferPtr); if (!rqptr->rqCgi.BufferLength) { /* initialize CGI variable buffer */ rqptr->rqCgi.BufferLength = rqptr->rqCgi.BufferRemaining = BufferChunk; rqptr->rqCgi.BufferPtr = rqptr->rqCgi.BufferCurrentPtr = VmGetHeap (rqptr, BufferChunk); return; } CurrentLength = rqptr->rqCgi.BufferLength; CurrentOffset = rqptr->rqCgi.BufferCurrentPtr - rqptr->rqCgi.BufferPtr; rqptr->rqCgi.BufferPtr = VmReallocHeap (rqptr, rqptr->rqCgi.BufferPtr, CurrentLength + BufferChunk, FI_LI); rqptr->rqCgi.BufferLength = CurrentLength + BufferChunk; rqptr->rqCgi.BufferRemaining += BufferChunk; rqptr->rqCgi.BufferCurrentPtr = rqptr->rqCgi.BufferPtr + CurrentOffset; } /*****************************************************************************/ /* Process output from a CGI/CGIplus/RTE/WebSocket script (or even plain DCL processing). For CGI scripts check the first output from each for CGI-relevant HTTP header lines. For all output check that carriage-control is as required. Returns one of two things. Either a constant value ("CGI_OUTPUT_...") that indicates some event in output processing, or a value indicating quantity of output still available in the stream. */ int CgiOutput ( REQUEST_STRUCT *rqptr, char *OutputPtr, int OutputCount ) { BOOL CgiCompliant, EndOfHeader, HTTP1dot1; int cnt, idx, status, AbsorbCount, HeaderLength, HeaderRemaining, HttpStatus, LineCount; char *cptr, *lptr, *sptr, *zptr; /*********/ /* begin */ /*********/ if (WATCHMOD (rqptr, WATCH_MOD_CGI)) { WatchThis (WATCHITM(rqptr), WATCH_MOD_CGI, "CgiOutput() !SL !&X", OutputCount, OutputPtr); WatchDataDump (OutputPtr, OutputCount); } cptr = OutputPtr; cnt = OutputCount; if (!cptr) { /********************/ /* reset CGI output */ /********************/ rqptr->rqCgi.CalloutInProgress = rqptr->rqCgi.ContentTypeText = rqptr->rqCgi.Header100Continue = rqptr->rqCgi.Header100ContinueDone = rqptr->rqCgi.ProcessingBody = false; rqptr->rqCgi.CalloutOutputCount = rqptr->rqCgi.OutputMode = rqptr->rqCgi.RecordCount = rqptr->rqCgi.XVMSRecordMode = 0; rqptr->rqCgi.ContentLength = -1; if (rqptr->rqCgi.HeaderPtr) VmFreeFromHeap (rqptr, rqptr->rqCgi.HeaderPtr, FI_LI); rqptr->rqCgi.HeaderLength = rqptr->rqCgi.HeaderLineCount = 0; rqptr->rqCgi.HeaderPtr = VmGetHeap (rqptr, DclCgiHeaderSize); return (0); } if (rqptr->rqCgi.EofLength) { if (cnt >= rqptr->rqCgi.EofLength && cnt <= rqptr->rqCgi.EofLength+2 && MATCH0 (cptr, rqptr->rqCgi.EofStr, rqptr->rqCgi.EofLength)) { /******************************/ /* end of output from script! */ /******************************/ rqptr->rqCgi.AbsorbHeader = rqptr->rqCgi.BufferRecords = false; if (!rqptr->rqCgi.IsCliDcl && !rqptr->rqCgi.AgentScript && !rqptr->rqCgi.ProcessingBody && /* ->HttpStatus can be set by DCL callout GATEWAY-BEGIN: */ !rqptr->rqResponse.HttpStatus && !rqptr->rqResponse.RedactBufferPtr && !rqptr->rqResponse.RedactBufferCount && Config.cfScript.CgiStrictOutput) { /* hmmmm, didn't receive any output */ if (WATCHING (rqptr, WATCH_CGI)) WatchThis (WATCHITM(rqptr), WATCH_CGI, ErrorCgiNotStrict); ErrorNoticed (rqptr, 0, CgiNotStrict, FI_LI); return (CGI_OUTPUT_NOT_STRICT); } rqptr->rqCgi.AgentScript = false; if (rqptr->rqCache.LoadFromCgi) { rqptr->rqCache.LoadStatus = SS$_ENDOFFILE; CacheLoadEnd (rqptr); } return (CGI_OUTPUT_END); } } if (rqptr->rqCgi.CalloutInProgress) { /********************************/ /* output escape is in progress */ /********************************/ if (cnt >= rqptr->rqCgi.EotLength && cnt <= rqptr->rqCgi.EotLength+2 && MATCH0 (cptr, rqptr->rqCgi.EotStr, rqptr->rqCgi.EotLength)) { /******************************/ /* end of escape from script! */ /******************************/ return (CGI_OUTPUT_ESCAPE_END); } return (CGI_OUTPUT_ESCAPE); } if (!rqptr->rqCgi.IsCliDcl && rqptr->rqCgi.EscLength) { if (cnt >= rqptr->rqCgi.EscLength && cnt <= rqptr->rqCgi.EscLength+2 && MATCH0 (cptr, rqptr->rqCgi.EscStr, rqptr->rqCgi.EscLength)) { /******************************/ /* escape from script output! */ /******************************/ return (CGI_OUTPUT_ESCAPE_BEGIN); } } rqptr->rqCgi.RecordCount++; if (!rqptr->rqCgi.ProcessingBody && !rqptr->rqCgi.IsCliDcl) { /******************************/ /* processing response header */ /******************************/ /* buffer part of, one, or multiple lines of script (header) output */ LineCount = 0; EndOfHeader = false; sptr = rqptr->rqCgi.HeaderPtr + rqptr->rqCgi.HeaderLength; zptr = sptr + DclCgiHeaderSize; while (*cptr && sptr < zptr) { if (*cptr != '\n' && !SAME2(cptr,'\r\n') && !SAME2(cptr,'\r\0')) { *sptr++ = *cptr++; continue; } if (*cptr == '\n') { /* record terminated by a trailing newline */ if (sptr > rqptr->rqCgi.HeaderPtr && *(sptr-1) == '\n') { cptr++; EndOfHeader = true; break; } *sptr++ = '\r'; if (sptr < zptr) *sptr++ = *cptr++; LineCount++; rqptr->rqCgi.HeaderCarRet = false; continue; } else if (SAME2(cptr,'\r\n')) { /* record 'correctly' terminated */ if (sptr > rqptr->rqCgi.HeaderPtr && *(sptr-1) == '\n') { cptr++; cptr++; EndOfHeader = true; break; } *sptr++ = *cptr++; if (sptr < zptr) *sptr++ = *cptr++; LineCount++; rqptr->rqCgi.HeaderCarRet = false; continue; } else { /* single record terminated by a trailing carriage-return */ if (sptr > rqptr->rqCgi.HeaderPtr && *(sptr-1) == '\n') { cptr++; EndOfHeader = true; break; } *sptr++ = *cptr++; if (sptr < zptr) *sptr++ = '\n'; LineCount++; rqptr->rqCgi.HeaderCarRet = false; continue; } } /* an empty record can only mean one thing, a '\n' in record-mode! */ if (!OutputCount || rqptr->rqCgi.HeaderCarRet) { /* header output contains no explicit carriage-control */ LineCount++; if (sptr > rqptr->rqCgi.HeaderPtr && *(sptr-1) == '\n') EndOfHeader = true; else { if (sptr < zptr) *sptr++ = '\r'; if (sptr < zptr) *sptr++ = '\n'; } } if (sptr >= zptr) { if (WATCHING (rqptr, WATCH_CGI)) WatchThis (WATCHITM(rqptr), WATCH_CGI, ErrorCgiNotStrict); ErrorNoticed (rqptr, 0, CgiNotStrict, FI_LI); return (CGI_OUTPUT_NOT_STRICT); } *sptr = '\0'; HeaderLength = sptr - rqptr->rqCgi.HeaderPtr; if (WATCHMOD (rqptr, WATCH_MOD_CGI)) WatchDataDump(rqptr->rqCgi.HeaderPtr, HeaderLength); } if (!rqptr->rqCgi.ProcessingBody && !rqptr->rqCgi.IsCliDcl && !rqptr->rqCgi.HeaderLineCount) { /****************/ /* first record */ /****************/ lptr = rqptr->rqCgi.HeaderPtr; if (MATCH7 (lptr, "HTTP/1.")) { HTTP1dot1 = (*(lptr += 7) == '1'); for (lptr++; ISLWS(*lptr) && NOTEOL(*lptr); lptr++); /* get (what should be) the response status code */ HttpStatus = atoi(lptr); if (HTTP1dot1 && HttpStatus == 100) { /****************/ /* 100 continue */ /****************/ if (rqptr->rqCgi.Header100ContinueDone) { /* hmmm, not putting up with two of these back-to-back! */ ErrorNoticed (rqptr, 0, CgiNotStrict, FI_LI); return (CGI_OUTPUT_NOT_STRICT); } rqptr->rqCgi.Header100Continue = true; } else if (HTTP1dot1 && HttpStatus == 101 && (rqptr->WebSocketRequest || rqptr->RawSocketRequest)) { /***************************/ /* 101 switching protocols */ /***************************/ /* we'll be generating a Web Socket specific response header */ rqptr->rqCgi.Header101Switching = rqptr->rqCgi.AbsorbHeader = true; } else { /**************************/ /* full HTTP stream (NPH) */ /**************************/ if (WATCHING (rqptr, WATCH_CGI)) WatchThis (WATCHITM(rqptr), WATCH_CGI, "RESPONSE NPH mode!AZ", HTTP2_REQUEST(rqptr) ? " (but HTTP/2)" : ""); /* status used for logging purposes */ rqptr->rqResponse.HttpStatus = HttpStatus; if (!rqptr->rqCgi.AbsorbHeader) { if (Watch.Category && Watch.Category != WATCH_ONE_SHOT_CAT) WatchFilterHttpStatus (rqptr); if (rqptr->rqPathSet.CacheNPH && !rqptr->rqCache.LoadCheck) { /* script output to be cached */ rqptr->rqCache.LoadFromCgi = CacheLoadBegin (rqptr, 0, NULL); if (rqptr->rqCache.LoadFromCgi) CacheLoadData (rqptr, OutputPtr, OutputCount); } /* HTTP/2 requires an explicit response header */ if (!HTTP2_REQUEST(rqptr)) { /* HTTP/1.n does not */ rqptr->rqCgi.ProcessingBody = true; return (OutputCount); } } } } else { /************************************/ /* check for CGI-like response line */ /************************************/ /* ensure it looks something like "blah-blah:" */ while (*lptr && *lptr != ':' && !ISLWS(*lptr)) lptr++; if (*lptr != ':') { /***********************/ /* non-CGI/NPH output? */ /***********************/ if (rqptr->rqCgi.AgentScript) { /* not allowed for an authorization agent */ AuthAgentCalloutResponseError (rqptr); ErrorNoticed (rqptr, 0, CgiNotStrict, FI_LI); return (CGI_OUTPUT_NOT_STRICT); } if (Config.cfScript.CgiStrictOutput) { if (WATCHING (rqptr, WATCH_CGI)) WatchThis (WATCHITM(rqptr), WATCH_CGI, ErrorCgiNotStrict); ErrorNoticed (rqptr, 0, CgiNotStrict, FI_LI); return (CGI_OUTPUT_NOT_STRICT); } lptr = rqptr->rqCgi.HeaderPtr; if (*lptr == '%') { /* check for something like "%DCL-E-OPENIN, blah" */ for (cptr = lptr+1; *cptr && isalpha(*cptr); cptr++); if (cptr[0] == '-' && (cptr[1] == 'F' || cptr[1] == 'E' || cptr[1] == 'W') && cptr[2] == '-') { cptr += 3; while (*cptr && isalpha(*cptr)) cptr++; if (*cptr == ',') { /* yep, looks like it */ if (WATCHING (rqptr, WATCH_CGI)) WatchThis (WATCHITM(rqptr), WATCH_CGI, ErrorCgiCli, lptr); rqptr->rqResponse.HttpStatus = 502; ErrorGeneral (rqptr, lptr, FI_LI); ErrorNoticed (rqptr, 0, CgiNotStrict, FI_LI); return (CGI_OUTPUT_NOT_STRICT); } } } ResponseHeader (rqptr, 200, "text/plain", -1, NULL, NULL); rqptr->rqCgi.ContentTypeText = rqptr->rqCgi.ProcessingBody = true; rqptr->rqCgi.OutputMode = CGI_OUTPUT_MODE_RECORD; } } if (!LineCount && rqptr->rqCgi.RecordCount == 1) { /* no carriage-control has been detected in first record */ rqptr->rqCgi.HeaderCarRet = true; if (sptr < zptr) *sptr++ = '\r'; if (sptr < zptr) *sptr++ = '\n'; LineCount++; } } if (!rqptr->rqCgi.ProcessingBody && !rqptr->rqCgi.IsCliDcl) { /***************************/ /* (still) response header */ /***************************/ rqptr->rqCgi.HeaderLineCount += LineCount; rqptr->rqCgi.HeaderLength = HeaderLength = sptr - rqptr->rqCgi.HeaderPtr; /* eliminate header line(s) */ memmove (OutputPtr, cptr, OutputCount - (cptr - OutputPtr)); /* adjust for what we have buffered/eliminated of the header */ OutputCount -= cptr - OutputPtr; if (!(rqptr->rqCgi.ProcessingBody = EndOfHeader)) return (OutputCount); /***************************/ /* end of response header! */ /***************************/ if (rqptr->rqCgi.Header100Continue) { /****************/ /* 100 continue */ /****************/ /* output a "100 Continue" header to the client */ ResponseHeader100Continue (rqptr); /* reset the CGI header storage */ CgiOutput (rqptr, NULL, 0); /* note that we've already done this once */ rqptr->rqCgi.Header100ContinueDone = true; /* restart the header processing with anything that's left */ if (OutputCount) OutputCount = CgiOutput (rqptr, OutputPtr, OutputCount); return (OutputCount); } /*****************/ /* "real" header */ /*****************/ /* look at each line in the script-generated CGI response header */ CgiCompliant = false; LineCount = 0; HeaderRemaining = rqptr->rqCgi.HeaderLength; cptr = rqptr->rqCgi.HeaderPtr; while (*cptr) { /**************************/ /* process header line(s) */ /**************************/ for (zptr = lptr = cptr; *zptr && *zptr != '\r' && !SAME2(zptr,'\r\n'); zptr++); LineCount++; if (WATCHING (rqptr, WATCH_CGI)) WatchThis (WATCHITM(rqptr), WATCH_CGI, "RESPONSE header line !UL \'!#AZ\' !UL bytes", LineCount, zptr-lptr, lptr, zptr-lptr); /* continue on to the beginning of (any) next line */ if (*zptr == '\r') zptr++; if (*zptr == '\n') zptr++; HeaderRemaining -= zptr - lptr; if (LineCount == 1 && MATCH7 (cptr, "HTTP/1.")) { /*********************/ /* absorb NPH header */ /*********************/ /* well, sort of! */ CgiCompliant = true; /* bump to the start of the next line */ if (!*(cptr = zptr)) break; continue; } /* ensure it looks like a response header field format */ for (sptr = cptr; sptr < zptr && *sptr != ':' && !ISLWS(*sptr); sptr++); /* a little more restrictive than RFC2616 but... */ if (!isalnum(*cptr) || *sptr != ':') { ErrorNoticed (rqptr, 0, CgiNotStrict, FI_LI); return (CGI_OUTPUT_NOT_STRICT); } if (TOUP(*cptr) == 'C' && strsame (cptr, "Content-Type:", 13)) { /****************/ /* content-type */ /****************/ CgiCompliant = true; cptr += 13; while (ISLWS(*cptr) && NOTEOL(*cptr)) cptr++; sptr = cptr; while (NOTEOL(*cptr)) cptr++; if (!rqptr->rqCgi.AbsorbHeader) { rqptr->rqResponse.ContentTypePtr = VmGetHeap (rqptr, cptr-sptr+1); memcpy (rqptr->rqResponse.ContentTypePtr, sptr, cptr-sptr); rqptr->rqResponse.ContentTypePtr[cptr-sptr] = '\0'; rqptr->rqHeader.ContentTypePtr = rqptr->rqResponse.ContentTypePtr; } if (strsame (sptr, "text/", 5)) rqptr->rqCgi.ContentTypeText = true; else rqptr->rqCgi.ContentTypeText = false; /* eliminate this header line */ rqptr->rqCgi.HeaderLength -= zptr - lptr; memmove (lptr, zptr, HeaderRemaining+1); zptr -= zptr - lptr; } else if (TOUP(*cptr) == 'C' && strsame (cptr, "Content-Length:", 15)) { /******************/ /* content-length */ /******************/ cptr += 15; while (ISLWS(*cptr)) cptr++; if (!rqptr->rqCgi.AbsorbHeader) { ulong result; /* must be 0 to 4,294,967,295 anything else is out-of-range */ result = strtoul (cptr, NULL, 10); if (!isdigit(*cptr) || result == ULONG_MAX) { for (sptr = cptr; *sptr && isdigit(*sptr); sptr++); if (WATCHING (rqptr, WATCH_CGI)) WatchThis (WATCHITM(rqptr), WATCH_CGI, "!#AZ !AZ", sptr-cptr, cptr, strerror(errno)); return (CGI_OUTPUT_NOT_STRICT); } rqptr->rqCgi.ContentLength = result; if (WATCHING (rqptr, WATCH_CGI)) WatchThis (WATCHITM(rqptr), WATCH_CGI, "!&,UL bytes", result); /* eliminate this header line */ rqptr->rqCgi.HeaderLength -= zptr - lptr; memmove (lptr, zptr, HeaderRemaining+1); zptr -= zptr - lptr; } } else if (TOUP(*cptr) == 'C' && strsame (cptr, "Content-Encoding:", 17)) { /********************/ /* content-encoding */ /********************/ cptr += 17; while (ISLWS(*cptr)) cptr++; if (strsame (cptr, "gzip", 4)) rqptr->rqResponse.ContentIsEncodedGzip = true; else rqptr->rqResponse.ContentIsEncodedUnknown = true; } else if (TOUP(*cptr) == 'S' && strsame (cptr, "Status:", 7)) { /**********/ /* status */ /**********/ CgiCompliant = true; cptr += 7; /* create HTTP header status line using supplied status */ while (*cptr++ && !isdigit(*cptr) && *cptr != '\n') cptr++; if (!rqptr->rqCgi.AbsorbHeader) { /* get response status code for header and logging purposes */ if (isdigit(*cptr)) rqptr->rqResponse.HttpStatus = atoi(cptr); if (rqptr->rqResponse.HttpStatus == 101 && rqptr->WebSocketRequest) { /******************************************/ /* 101 switching protocols (to WebSocket) */ /******************************************/ rqptr->rqCgi.Header101Switching = rqptr->rqCgi.AbsorbHeader = true; } } /* eliminate this header line */ rqptr->rqCgi.HeaderLength -= zptr - lptr; memmove (lptr, zptr, HeaderRemaining+1); zptr -= zptr - lptr; } else if (TOUP(*cptr) == 'S' && strsame (cptr, "Script-Control:", 15)) { /*****************************************/ /* script control (Apache Group CGI/1.2) */ /*****************************************/ cptr += 15; while (*cptr && NOTEOL(*cptr)) { while (ISLWS(*cptr) || *cptr == ',' || *cptr == ';') cptr++; if (ISEOL(*cptr)) break; if (cnt = CgiScriptControlField (rqptr, cptr)) cptr += cnt; else { /* discard unknown script-control */ char Scratch [1024]; StringParseValue (&cptr, Scratch, sizeof(Scratch)); } } /* eliminate this header line */ rqptr->rqCgi.HeaderLength -= zptr - lptr; memmove (lptr, zptr, HeaderRemaining+1); zptr -= zptr - lptr; } else if (TOUP(*cptr) == 'T' && strsame (cptr, "Transfer-Encoding:", 18)) { /*********************/ /* transfer-encoding */ /*********************/ /* take note the response body is transfer-encoded in some way */ rqptr->rqCgi.TransferEncoding = true; } else if (TOUP(*cptr) == 'X' && strsame (cptr, "X-vms-record-mode:", 18)) { /*****************************/ /* VMS Apache record control */ /*****************************/ /* VMS Apache (in pre-CSWS days) uses an eXtension header field so that the script can convey to the server whether the output should be processed as a binary stream or as records. (Keep this in post-CSWS days for backward compatibility.) */ cptr += 18; while (ISLWS(*cptr) && NOTEOL(*cptr)) cptr++; /* this value should either 0 or 1 ("no" or "yes") plus 1 */ rqptr->rqCgi.XVMSRecordMode = atoi(cptr)+1; /* eliminate this header line */ rqptr->rqCgi.HeaderLength -= zptr - lptr; memmove (lptr, zptr, HeaderRemaining+1); zptr -= zptr - lptr; } else if (TOUP(*cptr) == 'L' && strsame (cptr, "Location:", 9)) { /************/ /* location */ /************/ CgiCompliant = true; cptr += 9; while (ISLWS(*cptr) && NOTEOL(*cptr)) cptr++; sptr = cptr; while (NOTEOL(*cptr)) cptr++; if (!rqptr->rqCgi.AbsorbHeader) ResponseLocation (rqptr, sptr, cptr-sptr); /* eliminate this header line */ rqptr->rqCgi.HeaderLength -= zptr - lptr; memmove (lptr, zptr, HeaderRemaining+1); zptr -= zptr - lptr; } /* bump to the start of the next (if any) line */ if (!*(cptr = zptr)) break; } if (cptr = rqptr->rqPathSet.ScriptControlPtr) { if (WATCHING (rqptr, WATCH_CGI)) WatchThis (WATCHITM(rqptr), WATCH_CGI, "!AZ", cptr); while (*cptr) { while (ISLWS(*cptr) || *cptr == ',' || *cptr == ';') cptr++; if (!*cptr) break; if (cnt = CgiScriptControlField (rqptr, cptr)) cptr += cnt; else { /* discard unknown script-control */ char Scratch [1024]; StringParseValue (&cptr, Scratch, sizeof(Scratch)); } } } if (WATCHING (rqptr, WATCH_CGI)) if (rqptr->rqCgi.AbsorbHeader) WatchThis (WATCHITM(rqptr), WATCH_CGI, "RESPONSE header absorbed"); /****************************/ /* generate response header */ /****************************/ if (!rqptr->rqCgi.OutputMode) { if (rqptr->rqCgi.XVMSRecordMode) { if (rqptr->rqCgi.XVMSRecordMode-1) rqptr->rqCgi.OutputMode = CGI_OUTPUT_MODE_RECORD; else rqptr->rqCgi.OutputMode = CGI_OUTPUT_MODE_STREAM; } else /* always enforce stream mode if it's encoded in some way! */ if (rqptr->rqResponse.ContentIsEncodedGzip || rqptr->rqResponse.ContentIsEncodedUnknown) rqptr->rqCgi.OutputMode = CGI_OUTPUT_MODE_STREAM; else if (rqptr->rqCgi.ContentTypeText || rqptr->rqCgi.Header101Switching) rqptr->rqCgi.OutputMode = CGI_OUTPUT_MODE_RECORD; else rqptr->rqCgi.OutputMode = CGI_OUTPUT_MODE_STREAM; } if (WATCHING (rqptr, WATCH_CGI)) { switch (rqptr->rqCgi.OutputMode) { case CGI_OUTPUT_MODE_CRLF : cptr = "crlf"; break; case CGI_OUTPUT_MODE_RECORD0 : cptr = "record0"; break; case CGI_OUTPUT_MODE_RECORD : cptr = "record"; break; case CGI_OUTPUT_MODE_STREAM : cptr = "stream"; break; default : cptr = "?"; } WatchThis (WATCHITM(rqptr), WATCH_CGI, "RESPONSE !AZ mode", cptr); } if (rqptr->rqCgi.Header101Switching) { /***************************/ /* 101 switching protocols */ /***************************/ /* output the Web Socket handshake header to the client */ WebSockSwitchResponse (rqptr); /* delay WebSocket I/O ASTs until after response header */ WebSockInput (rqptr); WebSockOutput (rqptr); return (OutputCount); } if (!CgiCompliant) { ErrorNoticed (rqptr, 0, CgiNotStrict, FI_LI); return (CGI_OUTPUT_NOT_STRICT); } if (rqptr->rqCgi.ScriptControlErrorTextPtr) { /* script is requesting the server generate an error message */ sptr = rqptr->rqCgi.ScriptControlErrorModulePtr; cnt = rqptr->rqCgi.ScriptControlErrorLine; if (status = rqptr->rqCgi.ScriptControlErrorVmsStatus) { rqptr->rqResponse.ErrorTextPtr = rqptr->rqCgi.ScriptControlErrorTextPtr; rqptr->rqResponse.ErrorOtherTextPtr = rqptr->rqCgi.ScriptControlErrorVmsTextPtr; if (sptr) ErrorVmsStatus (rqptr, status, sptr, cnt); else ErrorVmsStatus (rqptr, status, FI_LI); } else { cptr = rqptr->rqCgi.ScriptControlErrorTextPtr; if (sptr) ErrorGeneral (rqptr, cptr, sptr, cnt); else ErrorGeneral (rqptr, cptr, FI_LI); } } else if (!rqptr->rqCgi.AbsorbHeader) { if (rqptr->rqPathSet.CacheCGI && !rqptr->rqCache.LoadCheck) { /* script output to be cached */ rqptr->rqCache.LoadFromCgi = CacheLoadBegin (rqptr, rqptr->rqResponse.ContentLength, rqptr->rqResponse.ContentTypePtr); if (rqptr->rqCache.LoadFromCgi && rqptr->rqCgi.HeaderLength) { /* plus one to include the terminating null */ CacheLoadData (rqptr, rqptr->rqCgi.HeaderPtr, rqptr->rqCgi.HeaderLength+1); } } /* if it was a 'redirect' and no other response status was supplied */ if (rqptr->rqResponse.LocationPtr && !rqptr->rqResponse.HttpStatus) rqptr->rqResponse.HttpStatus = 302; ResponseHeader (rqptr, rqptr->rqResponse.HttpStatus, rqptr->rqResponse.ContentTypePtr, rqptr->rqCgi.ContentLength, NULL, rqptr->rqCgi.HeaderPtr); } if (rqptr->WebSocketRequest) { /*************************/ /* shouldn't get to here */ /*************************/ /* encourage writing of the response header */ NetWrite (rqptr, &RequestEnd, NULL, 0); return (CGI_OUTPUT_END); } if (!OutputCount) return (OutputCount); } /*************************/ /* HTTP carriage-control */ /*************************/ if (rqptr->rqCgi.FilterStream) { /* remove all non-printable characters */ for (zptr=(cptr=OutputPtr)+OutputCount; (isprint(*cptr) || isspace(*cptr)) && cptr < zptr; cptr++); if (cptr < zptr) { /* a non-printable was detected, now eliminate it and any following */ sptr = cptr; while (cptr < zptr) { while (!(isprint(*cptr) || isspace(*cptr)) && cptr < zptr) cptr++; while ((isprint(*cptr) || isspace(*cptr)) && cptr < zptr) *sptr++ = *cptr++; } *sptr = '\0'; OutputCount = sptr - OutputPtr; } } if (rqptr->rqCgi.OutputMode == CGI_OUTPUT_MODE_RECORD || rqptr->rqCgi.IsCliDcl) { if (OutputCount) { if (OutputPtr[OutputCount-1] != '\n') { OutputPtr[OutputCount++] = '\n'; OutputPtr[OutputCount] = '\0'; } } else { OutputPtr[OutputCount++] = '\n'; OutputPtr[OutputCount] = '\0'; } if (rqptr->FileContentPtr) { CgiOutputFile (rqptr, OutputPtr, OutputCount); return (0); } if (rqptr->rqCache.LoadFromCgi) CacheLoadData (rqptr, OutputPtr, OutputCount); return (OutputCount); } if (rqptr->rqCgi.OutputMode == CGI_OUTPUT_MODE_RECORD0) { /* only have empty records as newlines */ if (!OutputCount) { OutputPtr[OutputCount++] = '\n'; OutputPtr[OutputCount] = '\0'; } if (rqptr->FileContentPtr) { CgiOutputFile (rqptr, OutputPtr, OutputCount); return (0); } if (rqptr->rqCache.LoadFromCgi) CacheLoadData (rqptr, OutputPtr, OutputCount); return (OutputCount); } if (rqptr->rqCgi.OutputMode == CGI_OUTPUT_MODE_CRLF) { if (!OutputCount || OutputPtr[OutputCount-1] != '\n' || (OutputCount >= 2 && OutputPtr[OutputCount-2] != '\r')) { /* record0 record, or no trailing , or no trailing */ if (OutputCount && OutputPtr[OutputCount-1] == '\n') OutputCount--; OutputPtr[OutputCount++] = '\r'; OutputPtr[OutputCount++] = '\n'; OutputPtr[OutputCount] = '\0'; if (rqptr->FileContentPtr) { CgiOutputFile (rqptr, OutputPtr, OutputCount); return (0); } if (rqptr->rqCache.LoadFromCgi) CacheLoadData (rqptr, OutputPtr, OutputCount); return (OutputCount); } } else if (!OutputCount) { /* stream-mode with zero bytes - do nothing, just return */ return (OutputCount); } if (rqptr->FileContentPtr) { CgiOutputFile (rqptr, OutputPtr, OutputCount); return (0); } if (rqptr->rqCache.LoadFromCgi) CacheLoadData (rqptr, OutputPtr, OutputCount); return (OutputCount); } /*****************************************************************************/ /* Process a CGI/1.2 "Script-Control:" response header field. Most of these are WASD extensions. Returns the number of characters parsed from the line. */ int CgiScriptControlField ( REQUEST_STRUCT *rqptr, char *ControlString ) { int Number; char *cptr, *sptr, *zptr; char Scratch [1024]; /*********/ /* begin */ /*********/ if (WATCH_MODULE(WATCH_MOD_CGI)) { for (cptr = ControlString; *cptr && NOTEOL(*cptr); cptr++); if (ISEOL(*cptr)) cptr--; WatchThis (WATCHITM(rqptr), WATCH_MOD_CGI, "CgiScriptControlField() !#AZ", cptr-ControlString+1, ControlString); } cptr = ControlString; /* the one and only "official" field of CGI/1.2 :^) */ if (strsame (cptr, "no-abort", 8)) { HttpdTimerSet (rqptr, TIMER_OUTPUT, -1); HttpdTimerSet (rqptr, TIMER_NOPROGRESS, -1); cptr += 8; while (*cptr && !ISLWS(*cptr) && NOTEOL(*cptr) && *cptr != ';') cptr++; return (cptr - ControlString); } /**************************/ /* carriage-control modes */ /**************************/ if (strsame (cptr, "X-crlf-mode", 11)) { cptr += 11; if (*cptr == '=') { cptr++; if (*cptr == '0') rqptr->rqCgi.OutputMode = CGI_OUTPUT_MODE_RECORD; else if (*cptr == '1') rqptr->rqCgi.OutputMode = CGI_OUTPUT_MODE_CRLF; } else rqptr->rqCgi.OutputMode = CGI_OUTPUT_MODE_CRLF; while (*cptr && !ISLWS(*cptr) && NOTEOL(*cptr) && *cptr != ';') cptr++; return (cptr - ControlString); } if (strsame (cptr, "X-record-mode", 13)) { cptr += 13; if (*cptr == '=') { cptr++; if (*cptr == '0') rqptr->rqCgi.OutputMode = CGI_OUTPUT_MODE_STREAM; else if (*cptr == '1') rqptr->rqCgi.OutputMode = CGI_OUTPUT_MODE_RECORD; } else rqptr->rqCgi.OutputMode = CGI_OUTPUT_MODE_RECORD; while (*cptr && !ISLWS(*cptr) && NOTEOL(*cptr) && *cptr != ';') cptr++; return (cptr - ControlString); } if (strsame (cptr, "X-record0-mode", 14)) { cptr += 14; if (*cptr == '=') { cptr++; if (*cptr == '0') rqptr->rqCgi.OutputMode = CGI_OUTPUT_MODE_RECORD; else if (*cptr == '1') rqptr->rqCgi.OutputMode = CGI_OUTPUT_MODE_RECORD0; } else rqptr->rqCgi.OutputMode = CGI_OUTPUT_MODE_RECORD0; while (*cptr && !ISLWS(*cptr) && NOTEOL(*cptr) && *cptr != ';') cptr++; return (cptr - ControlString); } if (strsame (cptr, "X-stream-mode", 13)) { cptr += 13; if (*cptr == '=') { cptr++; if (*cptr == '0') rqptr->rqCgi.OutputMode = CGI_OUTPUT_MODE_RECORD; else if (*cptr == '1') rqptr->rqCgi.OutputMode = CGI_OUTPUT_MODE_STREAM; } else rqptr->rqCgi.OutputMode = CGI_OUTPUT_MODE_STREAM; while (*cptr && !ISLWS(*cptr) && NOTEOL(*cptr) && *cptr != ';') cptr++; return (cptr - ControlString); } /*******************/ /* stream handling */ /*******************/ if (strsame (cptr, "X-buffer-records", 16)) { cptr += 16; if (*cptr == '=') { cptr++; if (*cptr == '0') rqptr->rqCgi.BufferRecords = false; else if (*cptr == '1') rqptr->rqCgi.BufferRecords = true; } else rqptr->rqCgi.BufferRecords = true; while (*cptr && !ISLWS(*cptr) && NOTEOL(*cptr) && *cptr != ';') cptr++; return (cptr - ControlString); } if (strsame (cptr, "X-content-encoding-gzip", 23)) { cptr += 23; if (*cptr == '=') { cptr++; if (*cptr == '0') rqptr->rqCgi.ContentEncodingGzip = CGI_OUTPUT_NO_GZIP; else if (*cptr == '1') rqptr->rqCgi.ContentEncodingGzip = CGI_OUTPUT_GZIP; } else rqptr->rqCgi.ContentEncodingGzip = CGI_OUTPUT_GZIP; while (*cptr && !ISLWS(*cptr) && NOTEOL(*cptr) && *cptr != ';') cptr++; return (cptr - ControlString); } if (strsame (cptr, "X-filter-stream", 15)) { cptr += 15; if (*cptr == '=') { cptr++; if (*cptr == '0') rqptr->rqCgi.FilterStream = false; else if (*cptr == '1') rqptr->rqCgi.FilterStream = true; } else rqptr->rqCgi.FilterStream = true; while (*cptr && !ISLWS(*cptr) && NOTEOL(*cptr) && *cptr != ';') cptr++; return (cptr - ControlString); } if (strsame (cptr, "X-transfer-encoding-chunked", 27)) { cptr += 27; if (*cptr == '=') { cptr++; if (*cptr == '0') rqptr->rqCgi.TransferEncodingChunked = CGI_OUTPUT_NO_CHUNK; else if (*cptr == '1') rqptr->rqCgi.TransferEncodingChunked = CGI_OUTPUT_CHUNK; } else rqptr->rqCgi.ContentEncodingGzip = CGI_OUTPUT_CHUNK; while (*cptr && !ISLWS(*cptr) && NOTEOL(*cptr) && *cptr != ';') cptr++; return (cptr - ControlString); } /************************/ /* server cache control */ /************************/ if (strsame (cptr, "X-cache-", 8)) { if (strsame (cptr+8, "max=", 4)) { cptr += 12; if (strsame (cptr, "none", 4)) Number = 0; else Number = atoi(cptr); rqptr->rqCgi.ScriptControlCacheMaxKBytes = Number; while (*cptr && !ISLWS(*cptr) && NOTEOL(*cptr) && *cptr != ';') cptr++; return (cptr - ControlString); } if (strsame (cptr+8, "expires=", 8)) { cptr += 16; if (strsame (cptr, "day", 3)) Number = CACHE_EXPIRES_DAY; else if (strsame (cptr, "hour", 4)) Number = CACHE_EXPIRES_HOUR; else if (strsame (cptr, "minute", 6)) Number = CACHE_EXPIRES_MINUTE; else if (*cptr == '0') Number = CACHE_EXPIRES_NONE; else { /* if it's not a 'hh:mm:ss' format then it's in minutes */ Number = MetaConSetSeconds (NULL, cptr, 60); if (Number < 0) Number = 0; } rqptr->rqCgi.ScriptControlCacheExpiresAfter = Number; while (*cptr && !ISLWS(*cptr) && NOTEOL(*cptr) && *cptr != ';') cptr++; return (cptr - ControlString); } } /**********/ /* timers */ /**********/ if (strsame (cptr, "X-timeout-", 10)) { if (strsame (cptr+10, "output=", 7)) { cptr += 17; if (strsame (cptr, "none", 4)) Number = -1; else Number = atoi(cptr); HttpdTimerSet (rqptr, TIMER_OUTPUT, Number); while (*cptr && !ISLWS(*cptr) && NOTEOL(*cptr) && *cptr != ';') cptr++; return (cptr - ControlString); } if (strsame (cptr+10, "noprogress=", 11)) { cptr += 21; if (strsame (cptr, "none", 4)) Number = -1; else Number = atoi(cptr); HttpdTimerSet (rqptr, TIMER_NOPROGRESS, Number); while (*cptr && !ISLWS(*cptr) && NOTEOL(*cptr) && *cptr != ';') cptr++; return (cptr - ControlString); } } if (rqptr->DclTaskPtr && strsame (cptr, "X-lifetime=", 11)) { cptr += 11; if (strsame (cptr, "none", 4)) Number = -1; else { if (!(Number = atoi(cptr) * 60)) { if (rqptr->DclTaskPtr->TaskType == DCL_TASK_TYPE_CGI_SCRIPT) Number = Config.cfScript.ZombieLifeTime+1; else Number = Config.cfScript.CgiPlusLifeTime+1; } } rqptr->DclTaskPtr->LifeTimeSecond = Number; while (*cptr && !ISLWS(*cptr) && NOTEOL(*cptr) && *cptr != ';') cptr++; return (cptr - ControlString); } /********************************/ /* error message control fields */ /********************************/ if (strsame (cptr, "X-error-", 8)) { if (strsame (cptr+8, "line=", 5)) { cptr += 13; rqptr->rqCgi.ScriptControlErrorLine = atoi(cptr); while (*cptr && !ISLWS(*cptr) && NOTEOL(*cptr) && *cptr != ';') cptr++; return (cptr - ControlString); } if (strsame (cptr+8, "module=", 7)) { cptr += 15; zptr = cptr; StringParseValue (&cptr, Scratch, sizeof(Scratch)); sptr = VmGetHeap (rqptr, cptr-zptr+1); strcpy (sptr, Scratch); rqptr->rqCgi.ScriptControlErrorModulePtr = sptr; return (cptr - ControlString); } if (strsame (cptr+8, "text=", 5)) { cptr += 13; zptr = cptr; StringParseValue (&cptr, Scratch, sizeof(Scratch)); sptr = VmGetHeap (rqptr, cptr-zptr+1); strcpy (sptr, Scratch); rqptr->rqCgi.ScriptControlErrorTextPtr = sptr; return (cptr - ControlString); } if (strsame (cptr+8, "vms-status=", 11)) { cptr += 19; if (SAME2(cptr,'%x') || SAME2(cptr,'%X')) rqptr->rqCgi.ScriptControlErrorVmsStatus = strtol(cptr+2, NULL, 16); else rqptr->rqCgi.ScriptControlErrorVmsStatus = atoi(cptr); while (*cptr && !ISLWS(*cptr) && NOTEOL(*cptr) && *cptr != ';') cptr++; return (cptr - ControlString); } if (strsame (cptr+8, "vms-text=", 9)) { cptr += 17; zptr = cptr; StringParseValue (&cptr, Scratch, sizeof(Scratch)); sptr = VmGetHeap (rqptr, cptr-zptr+1); strcpy (sptr, Scratch); rqptr->rqCgi.ScriptControlErrorVmsTextPtr = sptr; return (cptr - ControlString); } } /*******************/ /* content handler */ /*******************/ if (strsame (cptr, "X-content-handler=", 18)) { cptr += 18; StringParseValue (&cptr, Scratch, sizeof(Scratch)); if (strsame (Scratch, "SSI", -1)) { if (!rqptr->SsiTaskPtr && !rqptr->FileContentPtr) { /* initialize the file contents structure */ CgiOutputFile (rqptr, NULL, SsiSizeMax); /* set the content structure handler so SSI engine gets control */ rqptr->FileContentPtr->ContentHandlerFunction = &SsiBegin; /* don't want the CGI header in the contents */ rqptr->rqCgi.AbsorbHeader = true; } } return (cptr - ControlString); } /**************************/ /* force this HTTP status */ /**************************/ if (strsame (cptr, "X-http-status=", 14)) { /* applicable only to an error reporting script */ cptr += 14; rqptr->rqCgi.ScriptControlHttpStatus = atoi(cptr); while (*cptr && !ISLWS(*cptr) && NOTEOL(*cptr) && *cptr != ';') cptr++; return (cptr - ControlString); } /************************/ /* ignore anything else */ /************************/ if (WATCHING (rqptr, WATCH_CGI)) { WatchThis (WATCHITM(rqptr), WATCH_CGI, "UNRECOGNISED script control field"); WatchDataFormatted ("!&Z", cptr); } return (0); } /*****************************************************************************/ /* Capturing script output to a 'file contents' structure. This will be detected and 'handled' by RequestEnd(). */ void CgiOutputFile ( REQUEST_STRUCT *rqptr, char *OutputPtr, int OutputCount ) { #define CONTENT_SIZE_INITIAL 8192 int ContentSize; char *cptr, *sptr, *zptr; FILE_CONTENT *fcptr; /*********/ /* begin */ /*********/ if (WATCHMOD (rqptr, WATCH_MOD_CGI)) { if (fcptr = rqptr->FileContentPtr) WatchThis (WATCHITM(rqptr), WATCH_MOD_CGI, "CgiOutputFile() !UL !UL !UL !UL !&X", fcptr->ContentSizeMax, fcptr->ContentSize, fcptr->ContentLength, OutputCount, OutputPtr); else WatchThis (WATCHITM(rqptr), WATCH_MOD_CGI, "CgiOutputFile() !UL !&X", OutputCount, OutputPtr); if (OutputPtr) WatchDataDump (OutputPtr, OutputCount); } if (!OutputPtr) { /**************/ /* initialize */ /**************/ ContentSize = CONTENT_SIZE_INITIAL; if (ContentSize > OutputCount) ContentSize = OutputCount; /* allow one extra character for a terminating null */ rqptr->FileContentPtr = fcptr = (FILE_CONTENT*) VmGetHeap (rqptr, sizeof(FILE_CONTENT) + ContentSize+1); fcptr->ContentSizeMax = OutputCount; fcptr->ContentSize = ContentSize; if (WATCHING (rqptr, WATCH_CGI)) WatchThis (WATCHITM(rqptr), WATCH_CGI, "CONTENT !UL bytes max", fcptr->ContentSizeMax); /* buffer space immediately follows the structured storage */ fcptr->ContentPtr = (char*)fcptr + sizeof(FILE_CONTENT); /* populate the file contents structure with some file data */ zptr = (sptr = fcptr->FileName) + sizeof(fcptr->FileName); for (cptr = rqptr->rqCgi.ScriptFileNamePtr; *cptr && sptr < zptr; *sptr++ = *cptr++); if (sptr >= zptr) { ErrorGeneralOverflow (rqptr, FI_LI); return; } *sptr = '\0'; fcptr->FileNameLength = sptr - fcptr->FileName; PUT_QUAD_QUAD (rqptr->rqTime.BeginTime64, fcptr->CdtTime64); PUT_QUAD_QUAD (rqptr->rqTime.BeginTime64, fcptr->RdtTime64); fcptr->UicGroup = 0; fcptr->UicMember = 0; fcptr->Protection = 0; return; } /**********/ /* buffer */ /**********/ fcptr = rqptr->FileContentPtr; if (fcptr->ContentLength + OutputCount > fcptr->ContentSize) { if (fcptr->ContentSize >= fcptr->ContentSizeMax) { /* keep track of how large it would have grown */ fcptr->ContentLength += OutputCount; return; } /* double it's size each time, up to the size limit */ fcptr->ContentSize += fcptr->ContentSize; if (fcptr->ContentSize > fcptr->ContentSizeMax) fcptr->ContentSize = fcptr->ContentSizeMax; rqptr->FileContentPtr = fcptr = (FILE_CONTENT*) VmReallocHeap (rqptr, rqptr->FileContentPtr, sizeof(FILE_CONTENT) + fcptr->ContentSize+1, FI_LI); fcptr->ContentPtr = (char*)fcptr + sizeof(FILE_CONTENT); } memcpy (fcptr->ContentPtr + fcptr->ContentLength, OutputPtr, OutputCount); fcptr->ContentLength += OutputCount; fcptr->ContentPtr[fcptr->ContentLength] = '\0'; if (WATCHMOD (rqptr, WATCH_MOD_CGI)) WatchThis (WATCHITM(rqptr), WATCH_MOD_CGI, "!UL !UL !UL", fcptr->ContentSizeMax, fcptr->ContentSize, fcptr->ContentLength); } /*****************************************************************************/ /* Generate a (hopefully) unique sequence of 224 bits (28 bytes, which is a fair bit :^) The string is generated using three pseudo-random numbers (seeded each second from binary time) each time it is called. This provides a continuous, non-repeating series of unlikely bit combinations with a one in 2^224 chance (I think!) of presence in an I/O stream. */ void CgiSequence ( char OneChar, char *CgiSequencePtr, int *CgiSequenceLengthPtr ) { static $DESCRIPTOR (SequenceFaoDsc, "$!AZ-!8XL!8XL!8XL-\0"); static $DESCRIPTOR (SequenceDsc, ""); static char OneCharString [] = " "; static unsigned long PrevTime640, RandomNumber; unsigned short Length; unsigned long rand1, rand2, rand3; /*********/ /* begin */ /*********/ if (WATCH_MODULE(WATCH_MOD_CGI)) WatchThis (WATCHALL, WATCH_MOD_CGI, "CgiSequence()"); /* (re)seed the random number every second or so */ if (PrevTime640 != HttpdTime64[0]) RandomNumber += (PrevTime640 = HttpdTime64[0]); /* cheap (no subroutine call) MTH$RANDOM() */ rand1 = RandomNumber = RandomNumber * 69069 + 1; rand2 = RandomNumber = RandomNumber * 69069 + 1; rand3 = RandomNumber = RandomNumber * 69069 + 1; SequenceDsc.dsc$w_length = 29; SequenceDsc.dsc$a_pointer = CgiSequencePtr; OneCharString[0] = OneChar; sys$fao (&SequenceFaoDsc, &Length, &SequenceDsc, OneCharString, rand1, rand2, rand3); *CgiSequenceLengthPtr = Length-1; if (WATCH_MODULE(WATCH_MOD_CGI)) WatchThis (WATCHALL, WATCH_MOD_CGI, "!&Z", CgiSequencePtr); } /*****************************************************************************/