/*****************************************************************************/ /* Response.c HTTP response generation related functions. Includes NCS character set conversion. NCS CHARACTER SET CONVERSION ---------------------------- The [CharsetConvert] configuration directive provides the information required to convert one character set to another based on the "Accept-Charset:" of the request and the character set associated with the response. The basic format of the directive is doc-charset accept-charset[,accept-charset..] [NCS-conv-function[=factor]] At least one 'doc-charset' and one 'accept-charset' must be present. If only these two are present (i.e. no 'NCS-conversion-function') it indicates that the two character sets are aliases (i.e. the same set of characters, different name) and no conversion is necessary. If an 'NCS-conversion-function' is supplied it indicates that the document 'doc-charset' can be converted to the request 'Accept-Charset:' preference of the 'accept-charset' using the NCS conversion function name specified. A 'factor' parameter can be appended to the conversion function. Some conversion functions requires more than one output byte to represent one input byte for some characters. The 'factor' is an integer between 1 and 4 indicating how much more buffer space may be required for the converted string. It works by allocating that many times more output buffer space than is occupied by the input buffer. If not specified it defaults to 1, or an output buffer the same size as the input buffer. Multiple comma-separated 'accept-charset's may be included as the second component for either of the above behaviours, with each being matched individually. Wildcard '*' and '%' may be used in the 'doc-charset and 'accept-charset' strings. 1) If the document character set matches any client accepted character set *exactly* no conversion or change of charset is required. [CharsetConvert] windows-1251 windows-1251,cp-1251 windows-1251 koi8-r koi8r_to_windows1251_to_koi8r "Accept-Charset: iso-8859-1, cp-1251, *" (document charset: windows-251) "Content-Type: text/plain; charset: cp-1251" (with no conversion) 2) If any document-accepted pair of the directive matches the document and accepted combination of the request and an NCS conversion function has been specified then it is set to be converted to that. The response charset is changed to that specified by the 'accept-charset' of the directive. Note the third conversion function shows a conversion factor of 4, so the output buffer will be allocated at four times the size of the input buffer. [CharsetConvert] koi8-r koi8-r,koi8 koi8-r windows-1251,cp-1251 koi8r_to_windows1251 koi8-r utf8 koi8-r_to_utf8=4 "Accept-Charset: iso-8859-1, cp-1251, *" (document charset: koi8-r) "Content-Type: text/plain; charset: cp-1251" (with conversion) 3) If no document-accepted pairs of the directive match the document and accepted combination of the request and the 'accept-charset' list of the request included a full wildcard (e.g. ", *") then no conversion is required and the document charset is retained for the response. [CharsetConvert] koi8-r koi8-r,koi8 koi8-r windows-1251,cp-1251 koi8r_to_windows1251 "Accept-Charset: iso-8859-1, mac-cyr, *" (document charset: koi8-r) "Content-Type: text/plain; charset: koi8-r" (with no conversion) 4) If no document-accepted pairs of the directive match the document and accepted combination of the request and the 'accept-charset' list of the request contains no wildcard then 406 (not acceptable) error is returned. [CharsetConvert] koi8-r koi8-r,koi8 koi8-r windows-1251,cp-1251 koi8r_to_windows1251 "Accept-Charset: iso-8859-1, mac-cyr" (document charset: koi8-r) "HTTP/1.0 406 Not Acceptable" Testing the NCS Convert ~~~~~~~~~~~~~~~~~~~~~~~ An $NCS/LIST provides a listing of the available character set conversion modules. The following setup should convert all documents accessed with a path beginning /tolower/ to lower case (e.g. http://the.host.name/tolower/ht_root/) and similarly for upper case. # WASD_CONFIG_GLOBAL [CharsetConvert] iso-8859-1_lower iso-8859-1 Multi_to_Lower iso-8859-1_upper iso-8859-1 Multi_to_Upper # WASD_CONFIG_MAP set /tolower/* charset=iso-8859-1_lower map /tolower/* /* set /toupper/* charset=iso-8859-1_upper map /toupper/* /* VERSION HISTORY --------------- 17-JUN-2010 MGD ResponseWebSocketHeader() draft-ietf-hybi-..-76 10-FEB-2010 MGD according to http://www.ietf.org/rfc/rfc2145.txt a server should respond with the minor HTTP version reflecting its own compliance rather than the client's provided the response itself is compliant with the client minor version (i.e. HTTP/1.0 requests should get HTTP/1.1 in the response status line - and now implemented by ResponseHeader()) 24-JAN-2010 MGD ResponseWebSocketHeader() 08-AUG-2006 MGD bugfix; ResponseHeader() accomodate 304 (not modified) 24-NOV-2005 MGD support for the OPAQUE realm 06-OCT-2005 MGD bugfix; ResponseHeader() ensure a charset= supplied with a text content-type (e.g. from a CGI script) is used 14-JUL-2005 MGD ResponseOptions() discriminate server and resource requests and respond appropriately using "Allow:" 20-JAN-2005 MGD ResponseHeader() include 'rqResponse.LocationPtr' 16-DEC-2004 MGD ResponseHeader() where content-length is unknown attempt to enable chunked transfer-encoding 06-SEP-2004 MGD allow ResponseHiss() to specify kBytes 20-JUL-2004 MGD HTTP/1.1 compliance, modification to ResponseHeader() processing, if any NCS conversion factor is one-to-many then any document content-length becomes invalid and is suppressed 21-AUG-2003 MGD "Accept-Ranges:" response field 12-JUL-2003 MGD ensure content type and length set in response header data 03-JUL-2002 MGD move RequestEcho() and RequestWhere() as ResponseEcho() and ResponseWhere(), add ResponseHiss() 30-APR-2002 MGD "Cache-Control:" field for Mozilla compatibility 23-APR-2002 MGD bugfix; ResponseCharsetConvert() must be FreeFromHeap()!! 10-NOV-2001 MGD initial (some functions moved from SUPPORT.C) */ /*****************************************************************************/ #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 /* VMS related header files */ #include #include /* application-related header files */ #include "wasd.h" #include "sha1.h" #define WASD_MODULE "RESPONSE" /******************/ /* global storage */ /******************/ int ResponseCharsetCount; char ResponseAllowServer [] = "Allow: CONNECT, DELETE, GET, HEAD, OPTIONS, POST, PUT, TRACE\r\n"; char ResponseAllowResource [] = "Allow: DELETE, GET, HEAD, POST, PUT\r\n"; /* "MS-Author-Via: DAV" see explanation in DAVWEB.C */ char ResponseAllowWebDavServer [] = "Allow: CONNECT, COPY, DELETE, GET, HEAD, MKCOL, MOVE, \ OPTIONS, POST, PROPFIND, PROPPATCH, PUT, TRACE\r\n\ MS-Author-Via: DAV\r\n\ DAV: 1\r\n"; char ResponseAllowWebDavLockServer [] = "Allow: CONNECT, COPY, DELETE, GET, HEAD, LOCK, MKCOL, MOVE, \ OPTIONS, POST, PROPFIND, PROPPATCH, PUT, TRACE, UNLOCK\r\n\ MS-Author-Via: DAV\r\n\ DAV: 1,2\r\n"; char ResponseAllowWebDavResource [] = "Allow: COPY, DELETE, GET, HEAD, MKCOL, MOVE, POST, PROPFIND, \ PROPPATCH, PUT\r\n\ MS-Author-Via: DAV\r\n\ DAV: 1\r\n"; char ResponseAllowWebDavLockResource [] = "Allow: COPY, DELETE, GET, HEAD, LOCK, MKCOL, MOVE, POST, PROPFIND, \ PROPPATCH, PUT, UNLOCK\r\n\ MS-Author-Via: DAV\r\n\ DAV: 1,2\r\n"; LIST_HEAD ResponseCharsetList; char ResponseHeader500 [] = "HTTP/1.0 500 Internal error\r\n\ Content-Type: text/plain\r\n\ \r\n\ A server error occured when generating the response!\n\ Please report to the site administrator.\n\ \n\0"; /********************/ /* external storage */ /********************/ extern BOOL AuthPromiscuous, GzipAccept, GzipResponse, WebDavEnabled, WebDavLockingEnabled; extern int NetReadBufferSize, OutputBufferSize; extern unsigned long HttpdBinTime[]; extern int ErrorSanityCheck[], ToLowerCase[], ToUpperCase[]; extern char SoftwareID[]; extern ACCOUNTING_STRUCT *AccountingPtr; extern CONFIG_STRUCT Config; extern MSG_STRUCT Msgs; extern WATCH_STRUCT Watch; /*****************************************************************************/ /* Generate a VmGetHeap()ed string containing an HTTP response header, pointed to by 'rqptr->rqResponse.HeaderPtr' with 'rqptr->rqResponse.HeaderLength' also set. Will generate success as well as error response headers, incorporating required authorization challenges if a 401 status. If no response status code is supplied as a parameter it defaults to whatever 'rqptr->rqResponse.HttpStatus' is set. If this is not set both default to 200. UNLESS this header is for a redirected error report in which case both are set to the status code of the original request and generate any required authorization challenges against the original realm if a 401 status! If a content-type has been supplied with the call (and it always should be!) generate a "content-type:" header line, with "charset" component if set for the server or request. A path-set content-type overrides the parameter. If a modified time is supplied generate a "last-modified:" header line. If the request is marked as pre-expired then generate an "expires:" header line containing the current GMT time. 'OtherHeaderPtr' is a catch-all parameter. This string is directly included as part of the HTTP header and so should contain correct carriage control, e.g. "Keep-Alive:\r\n". This function will always generate a header (only failing if it cannot allocate memory, in which case the server exits). If an error is detected when doing so a bogus 500 header is created with an embedded indication of where the problem occured. The request will continue but generally when delivered the error indication will be obvious. */ ResponseHeader ( REQUEST_STRUCT *rqptr, int HttpStatusCode, char *ContentTypePtr, int ContentLength, unsigned long *ModifiedBinTimePtr, char *OtherHeaderPtr ) { #define STRCAT(string) { \ for (cptr = string; *cptr && sptr < zptr; *sptr++ = *cptr++); \ } #define CHRCAT(character) { \ if (sptr < zptr) *sptr++ = character; \ } static char Number [16]; static $DESCRIPTOR (NumberDsc, Number); static $DESCRIPTOR (NumberFaoDsc, "!UL\0"); int status, idx, BufferLength; char *cptr, *sptr, *zptr; char *AuthRealmBufferPtr, *CharsetPtr, *LocationPtr, *SuppliedCharsetPtr; char ContentTypeString [128], ModifiedString [32], Buffer [8192]; /*********/ /* begin */ /*********/ if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD_RESPONSE)) WatchThis (rqptr, FI_LI, WATCH_MOD_RESPONSE, "ResponseHeader() !UL !&Z !SL !%D !&Z", HttpStatusCode, ContentTypePtr, ContentLength, ModifiedBinTimePtr ? ModifiedBinTimePtr : 0, OtherHeaderPtr); rqptr->rqResponse.HeaderLength = 0; rqptr->rqResponse.HeaderPtr = NULL; if (!HttpStatusCode) HttpStatusCode = rqptr->rqResponse.HttpStatus; if (!HttpStatusCode) HttpStatusCode = 200; rqptr->rqResponse.HttpStatus = HttpStatusCode; if (rqptr->RedirectErrorStatusCode) { /********************************************/ /* special case ... redirected error report */ /********************************************/ rqptr->rqResponse.HttpStatus = rqptr->RedirectErrorStatusCode; /* if authorization error then generate authentication challenge(s) */ if (rqptr->rqResponse.HttpStatus == 401) { /* of course, use the realm of the original request! */ sptr = rqptr->rqAuth.RealmDescrPtr; rqptr->rqAuth.RealmDescrPtr = rqptr->RedirectErrorAuthRealmDescrPtr; ResponseHeaderChallenge (rqptr); rqptr->rqAuth.RealmDescrPtr = sptr; } } else if (rqptr->rqResponse.HttpStatus == 401 || rqptr->rqResponse.HttpStatus == 407) ResponseHeaderChallenge (rqptr); /* if the path has a content-type SET against it then that overrides */ if (rqptr->rqPathSet.ContentTypePtr) ContentTypePtr = rqptr->rqPathSet.ContentTypePtr; if (LocationPtr = rqptr->rqResponse.LocationPtr) rqptr->rqResponse.LocationPtr = NULL; if (rqptr->rqResponse.HttpStatus == 304) { /* not-modified can have no content-type or response body */ ContentTypePtr = NULL; /* make the content-length zero so there's no temptation to chunk */ ContentLength = 0; } if (ContentTypePtr && strsame (ContentTypePtr, "text/", 5)) { /*********************/ /* text of some sort */ /*********************/ SuppliedCharsetPtr = NULL; zptr = (sptr = ContentTypeString) + sizeof(ContentTypeString); /* as we copy note where any "; charset=" begins */ for (cptr = ContentTypePtr; *cptr && sptr < zptr; *sptr++ = *cptr++) if (*cptr == ';') SuppliedCharsetPtr = sptr; if (SuppliedCharsetPtr) { cptr = SuppliedCharsetPtr; if (*cptr) cptr++; while (ISLWS(*cptr)) cptr++; if (!strsame (cptr, "charset=", 8)) SuppliedCharsetPtr = NULL; } /* if the response character set has been specifically set */ if (!(CharsetPtr = rqptr->rqResponse.MsgCharsetPtr)) /* if the path has a charset SET against it then that overrides */ if (CharsetPtr = rqptr->rqPathSet.CharsetPtr) if (*CharsetPtr == '(') CharsetPtr = NULL; /* otherwise, if the server has a default charset then use that */ if (!SuppliedCharsetPtr && !CharsetPtr && Config.cfContent.CharsetDefault[0]) CharsetPtr = Config.cfContent.CharsetDefault; /* if response specifies a character set and we're converting them */ if (CharsetPtr && CharsetPtr[0] && ResponseCharsetCount) CharsetPtr = ResponseCharsetConvertBegin (rqptr, CharsetPtr); if (CharsetPtr && CharsetPtr[0]) { if (SuppliedCharsetPtr) sptr = SuppliedCharsetPtr; for (cptr = "; charset="; *cptr && sptr < zptr; *sptr++ = *cptr++); for (cptr = CharsetPtr; *cptr && sptr < zptr; *sptr++ = *cptr++); } if (sptr >= zptr) { ErrorNoticed (rqptr, SS$_BUFFEROVF, NULL, FI_LI); rqptr->rqResponse.HeaderPtr = ResponseHeader500; rqptr->rqResponse.HeaderLength = sizeof(ResponseHeader500)-1; return; } *sptr = '\0'; ContentTypePtr = ContentTypeString; } /*******************/ /* generate header */ /*******************/ zptr = (sptr = Buffer) + sizeof(Buffer)-1; /* see http://www.ietf.org/rfc/rfc2145.txt */ if (rqptr->rqResponse.HttpVersion == HTTP_VERSION_0_9) STRCAT ("HTTP/1.0") else if (rqptr->rqResponse.HttpVersion == HTTP_VERSION_1_0 && rqptr->rqPathSet.ResponseHttpOriginal) STRCAT ("HTTP/1.0") else STRCAT ("HTTP/1.1") /* default and fallback */ CHRCAT (' ') sys$fao (&NumberFaoDsc, 0, &NumberDsc, rqptr->rqResponse.HttpStatus); STRCAT (Number) CHRCAT (' ') STRCAT (HttpStatusCodeText(rqptr->rqResponse.HttpStatus)) STRCAT ("\r\nServer: ") STRCAT (SoftwareID) STRCAT ("\r\nDate: ") STRCAT (rqptr->rqTime.GmDateTime) STRCAT ("\r\nAccept-Ranges: bytes\r\n") if (GzipAccept) STRCAT ("Accept-Encoding: gzip, deflate\r\n") if (LocationPtr) { STRCAT ("Location: ") STRCAT (LocationPtr) STRCAT ("\r\n") } if (rqptr->rqResponse.HttpStatus == 401 || rqptr->rqResponse.HttpStatus == 407) { /* requires authorization challenge */ if (rqptr->rqAuth.BasicChallengePtr && rqptr->rqAuth.BasicChallengePtr[0]) { STRCAT (rqptr->rqAuth.BasicChallengePtr) STRCAT ("\r\n") } if (rqptr->rqAuth.DigestChallengePtr && rqptr->rqAuth.DigestChallengePtr[0]) { STRCAT (rqptr->rqAuth.DigestChallengePtr) STRCAT ("\r\n") } } if (rqptr->rqResponse.EntityTag[0]) { STRCAT ("ETag: \"") STRCAT (rqptr->rqResponse.EntityTag) STRCAT ("\"\r\n") } if (ModifiedBinTimePtr) { status = HttpGmTimeString (ModifiedString, ModifiedBinTimePtr); if (VMSnok (status)) { ErrorNoticed (rqptr, status, NULL, FI_LI); rqptr->rqResponse.HeaderPtr = ResponseHeader500; rqptr->rqResponse.HeaderLength = sizeof(ResponseHeader500)-1; return; } STRCAT ("Last-Modified: ") STRCAT (ModifiedString) STRCAT ("\r\n") } if (rqptr->rqResponse.PreExpired || rqptr->rqPathSet.Expired) { STRCAT ("Expires: ") STRCAT (rqptr->rqTime.GmDateTime) /* trying fairly hard here */ STRCAT ("\r\nCache-Control: no-cache, no-store\r\nPragma: no-cache\r\n") } if (ContentTypePtr) { STRCAT ("Content-Type: ") STRCAT (ContentTypePtr) STRCAT ("\r\n") cptr = VmGetHeap (rqptr, strlen(ContentTypePtr)+1); strcpy (cptr, ContentTypePtr); rqptr->rqResponse.ContentTypePtr = cptr; } if (rqptr->rqResponse.CharsetNcsCfFactor > 1) { /* if the conversion is one-to-many the content-length is invalid */ ContentLength = -1; } if (GzipResponse) rqptr->rqResponse.ContentEncodeAsGzip = GzipShouldDeflate (rqptr, ContentTypePtr, ContentLength); if (rqptr->rqResponse.ContentEncodeAsGzip) { STRCAT ("Content-Encoding: gzip\r\n"); /* disable the content-length header */ ContentLength = -1; } if (rqptr->rqResponse.HttpVersion == HTTP_VERSION_1_1) { if (!rqptr->rqPathSet.ResponseNoChunked) { if (rqptr->rqCgi.TransferEncodingChunked == CGI_OUTPUT_CHUNK) { /* any script has explicitly requested content chunking */ if (!rqptr->rqCgi.TransferEncoding) { rqptr->rqResponse.TransferEncodingChunked = true; ContentLength = -1; } } else if (rqptr->rqCgi.TransferEncodingChunked != CGI_OUTPUT_NO_CHUNK) { /* any script has NOT explicitly requested NO content chunking */ if (ContentLength < 0 && rqptr->PersistentRequest && !rqptr->rqCgi.TransferEncoding) { /* there's a chance of connection persistence if it's chunked */ rqptr->rqResponse.TransferEncodingChunked = true; } } if (rqptr->rqResponse.TransferEncodingChunked) STRCAT ("Transfer-Encoding: chunked\r\n"); } } if (ContentLength != -1) { STRCAT ("Content-Length: ") sys$fao (&NumberFaoDsc, 0, &NumberDsc, ContentLength); STRCAT (Number) STRCAT ("\r\n") /* any time we've got a content-length this becomes possible */ rqptr->PersistentResponse = true; rqptr->rqResponse.ContentLength = ContentLength; } else if (rqptr->rqResponse.TransferEncodingChunked) { /* chunking contains it's own implicit content-length */ rqptr->PersistentResponse = true; } else { /* without content-length this is not possible */ rqptr->PersistentResponse = false; } for (idx = 0; idx < RESPONSE_COOKIE_MAX; idx++) { if (!rqptr->rqResponse.CookiePtr[idx]) continue; STRCAT ("Set-Cookie: ") STRCAT (rqptr->rqResponse.CookiePtr[idx]) STRCAT ("\r\n") } if (rqptr->rqResponse.HttpVersion == HTTP_VERSION_1_1) { if (!rqptr->PersistentRequest || !rqptr->PersistentResponse) STRCAT ("Connection: close\r\n") } else { if (rqptr->PersistentRequest && rqptr->PersistentResponse) STRCAT ("Connection: keep-alive\r\nKeep-Alive:\r\n") } if (rqptr->rqPathSet.ResponseHeaderAddLength) STRCAT (rqptr->rqPathSet.ResponseHeaderAddPtr) if (OtherHeaderPtr) STRCAT (OtherHeaderPtr) STRCAT ("\r\n") if (sptr >= zptr) { ErrorNoticed (rqptr, SS$_RESULTOVF, NULL, FI_LI); rqptr->rqResponse.HeaderPtr = ResponseHeader500; rqptr->rqResponse.HeaderLength = sizeof(ResponseHeader500)-1; return; } *sptr = '\0'; BufferLength = sptr - Buffer; cptr = VmGetHeap (rqptr, BufferLength+1); memcpy (cptr, Buffer, BufferLength+1); rqptr->rqResponse.HeaderLength = BufferLength; rqptr->rqResponse.HeaderPtr = cptr; if (rqptr->RedirectedXray) rqptr->PersistentResponse = false; if (Watch.Category && Watch.Category != WATCH_ONE_SHOT_CAT) WatchFilterHttpStatus (rqptr); #undef STRCAT #undef CHRCAT } /*****************************************************************************/ /* The HTTP/1.1 compliant "100 Continue" interim response written synchronously. */ ResponseHeader100Continue (REQUEST_STRUCT *rqptr) { #define STRCAT(string) { \ for (cptr = string; *cptr && sptr < zptr; *sptr++ = *cptr++); \ } #define CHRCAT(character) { \ if (sptr < zptr) *sptr++ = character; \ } int status, BufferLength; char *cptr, *sptr, *zptr; char Buffer [256]; /*********/ /* begin */ /*********/ if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD_RESPONSE)) WatchThis (rqptr, FI_LI, WATCH_MOD_RESPONSE, "ResponseHeader100Continue()"); zptr = (sptr = Buffer) + sizeof(Buffer)-1; if (rqptr->rqResponse.HttpVersion == HTTP_VERSION_1_1) STRCAT ("HTTP/1.1 100 Continue\r\nServer: ") else STRCAT ("HTTP/1.0 100 Continue\r\nServer: ") STRCAT (SoftwareID) STRCAT ("\r\nDate: ") STRCAT (rqptr->rqTime.GmDateTime) STRCAT ("\r\nAccept-Ranges: bytes\r\n\r\n") if (sptr >= zptr) { ErrorNoticed (rqptr, SS$_RESULTOVF, NULL, FI_LI); rqptr->rqResponse.HeaderPtr = ResponseHeader500; rqptr->rqResponse.HeaderLength = sizeof(ResponseHeader500)-1; return; } *sptr = '\0'; BufferLength = sptr - Buffer; /* synchronous network write (just for the convenience of it!) */ NetWrite (rqptr, NULL, Buffer, BufferLength); InstanceMutexLock (INSTANCE_MUTEX_HTTPD); AccountingPtr->ResponseStatusCodeGroup[1]++; AccountingPtr->ResponseStatusCodeCount[RequestHttpStatusIndex(100)]++; InstanceMutexUnLock (INSTANCE_MUTEX_HTTPD); #undef STRCAT #undef CHRCAT } /*****************************************************************************/ /* Generate a 200 header response. If output has been buffered and a reference to the descriptor provided then total-up the content length in all the associated descriptors and provide that. */ ResponseHeader200 ( REQUEST_STRUCT *rqptr, char *ContentType, STR_DSC *sdptr ) { int ContentLength; /*********/ /* begin */ /*********/ if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD_RESPONSE)) WatchThis (rqptr, FI_LI, WATCH_MOD_RESPONSE, "ResponseHeader200() !AZ !&X", ContentType, sdptr); if (sdptr) { /* total-up the content of all associated descriptors */ ContentLength = 0; while (sdptr) { ContentLength += STR_DSC_LEN(sdptr); sdptr = STR_DSC_NEXT(sdptr); } } else ContentLength = -1; ResponseHeader (rqptr, 200, ContentType, ContentLength, NULL, NULL); } /*****************************************************************************/ /* Generate DIGEST and/or BASIC challenges as appropriate. If the response status code is 401 (authorization needed) and no realm has been specified change the code to 403 (forbidden) and just return. */ ResponseHeaderChallenge (REQUEST_STRUCT *rqptr) { int status; /*********/ /* begin */ /*********/ if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD_RESPONSE)) WatchThis (rqptr, FI_LI, WATCH_MOD_RESPONSE, "ResponseHeaderChallenge()"); if (rqptr->rqResponse.HttpStatus == 401 || rqptr->rqResponse.HttpStatus == 407) { /* for OPAQUE realm it's all up to the script */ if (rqptr->rqAuth.SourceRealm == AUTH_SOURCE_OPAQUE) return; /* for EXTERNAL realm with a "param=/NO401" it's all up to the script */ if (rqptr->rqAuth.SourceRealm == AUTH_SOURCE_EXTERNAL && rqptr->rqAuth.PathParameterPtr && strsame (rqptr->rqAuth.PathParameterPtr, "/NO401", 6)) return; /* without a realm for authentication convert it to forbidden */ if (!rqptr->rqAuth.RealmDescrPtr || !rqptr->rqAuth.RealmDescrPtr[0]) { rqptr->rqResponse.HttpStatus = 403; return; } } if (!rqptr->rqAuth.ChallengeScheme) { /* ensure at least a BASIC challenge is generated if promiscuous */ if (Config.cfAuth.BasicEnabled || AuthPromiscuous) rqptr->rqAuth.ChallengeScheme |= AUTH_SCHEME_BASIC; if (Config.cfAuth.DigestEnabled) rqptr->rqAuth.ChallengeScheme |= AUTH_SCHEME_DIGEST; /* if neither scheme enabled don't challenge */ if (!rqptr->rqAuth.ChallengeScheme) rqptr->rqResponse.HttpStatus = 403; } if ((rqptr->rqAuth.ChallengeScheme & AUTH_SCHEME_BASIC) && !rqptr->rqAuth.BasicChallengePtr) BasicChallenge (rqptr); if (!rqptr->rqAuth.BasicChallengePtr) rqptr->rqAuth.BasicChallengePtr = ""; if ((rqptr->rqAuth.ChallengeScheme & AUTH_SCHEME_DIGEST) && !rqptr->rqAuth.DigestChallengePtr) DigestChallenge (rqptr, ""); if (!rqptr->rqAuth.DigestChallengePtr) rqptr->rqAuth.DigestChallengePtr = ""; } /*****************************************************************************/ /* Append a string onto a HTTP header located in HTTPd heap allocated memory. This header string must already exist (created by ResponseHeader200() or equivalent). The new string is appended to the current header, just before the header terminating empty line. Any string added must contain correct HTTP header carriage control. */ ResponseHeaderAppend ( REQUEST_STRUCT *rqptr, char *StringPtr, int StringLength ) { char *cptr, *HeaderPtr; int HeaderLength; /*********/ /* begin */ /*********/ if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD_RESPONSE)) WatchThis (rqptr, FI_LI, WATCH_MOD_RESPONSE, "ResponseHeaderAppend() !&Z", rqptr->rqResponse.HeaderPtr); if (!(HeaderPtr = rqptr->rqResponse.HeaderPtr)) return; HeaderLength = rqptr->rqResponse.HeaderLength; if (StringLength <= 0) StringLength = strlen(StringPtr); HeaderPtr = VmReallocHeap (rqptr, HeaderPtr, HeaderLength+StringLength, FI_LI); /* point to just before the header terminating empty line */ cptr = HeaderPtr + HeaderLength - 2; memcpy (cptr, StringPtr, StringLength); /* add the new header terminating empty line */ cptr += StringLength; *cptr++ = '\r'; *cptr++ = '\n'; *cptr = '\0'; rqptr->rqResponse.HeaderPtr = HeaderPtr; rqptr->rqResponse.HeaderLength = HeaderLength + StringLength; } /****************************************************************************/ /* Generate a WebSocket 101 Switching Protocols response. Based on section "Sending the server's opening handshake" from http://tools.ietf.org/html/draft-hixie-thewebsocketprotocol-05 */ int ResponseWebSocketHeader (REQUEST_STRUCT *rqptr) { static char WebSockHand [] = "HTTP/1.1 101 Switching Protocols\r\n\ Upgrade: websocket\r\n\ Connection: Upgrade\r\n\ Sec-WebSocket-Accept: "; static char WebSockExts [] = "\r\nSec-WebSocket-Extensions: ", WebSockProt [] = "\r\nSec-WebSocket-Protocol: "; int AcceptBase64Length; char *cptr, *sptr, *zptr; char AcceptBase64 [48], Buffer [1024], KeyGuid [96]; unsigned char KeyGuidHash [20]; SHA1Context Sha1Ctx; /*********/ /* begin */ /*********/ if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD_RESPONSE)) WatchThis (rqptr, FI_LI, WATCH_MOD_RESPONSE, "ResponseWebSocketHeader()"); if (!rqptr->rqHeader.SecWebSocketKeyPtr) { ErrorNoticed (rqptr, SS$_BUGCHECK, ErrorSanityCheck, FI_LI); return (SS$_BUGCHECK); } zptr = (sptr = KeyGuid) + sizeof(KeyGuid)-1; for (cptr = rqptr->rqHeader.SecWebSocketKeyPtr; *cptr && sptr < zptr; *sptr++ = *cptr++); for (cptr = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; *cptr && sptr < zptr; *sptr++ = *cptr++); *sptr = '\0'; SHA1Reset (&Sha1Ctx); SHA1Input (&Sha1Ctx, KeyGuid, sptr-KeyGuid); if (!SHA1Result (&Sha1Ctx)) ErrorNoticed (rqptr, SS$_BUGCHECK, ErrorSanityCheck, FI_LI); /* copy into the hash buffer (converting from big to little endian) */ SHA1LitEnd (&Sha1Ctx, KeyGuidHash); AcceptBase64Length = sizeof(AcceptBase64)-1; base64_encode (AcceptBase64, &AcceptBase64Length, KeyGuidHash, sizeof(KeyGuidHash)); zptr = (sptr = Buffer) + sizeof(Buffer)-1; for (cptr = WebSockHand; *cptr && sptr < zptr; *sptr++ = *cptr++); for (cptr = AcceptBase64; *cptr && sptr < zptr; *sptr++ = *cptr++); if (rqptr->rqHeader.SecWebSocketProtocolPtr) { for (cptr = WebSockProt; *cptr && sptr < zptr; *sptr++ = *cptr++); for (cptr = rqptr->rqHeader.SecWebSocketProtocolPtr; *cptr && sptr < zptr; *sptr++ = *cptr++); } for (cptr = "\r\n\r\n"; *cptr && sptr < zptr; *sptr++ = *cptr++); if (WATCHING(rqptr) && WATCH_CATEGORY(WATCH_RESPONSE_HEADER)) { WatchThis (rqptr, FI_LI, WATCH_RESPONSE_HEADER, "HEADER !UL bytes", sptr-Buffer); WatchData (Buffer, sptr-Buffer); } /* synchronous network write (just for the convenience of it!) */ NetWrite (rqptr, NULL, Buffer, sptr-Buffer); InstanceMutexLock (INSTANCE_MUTEX_HTTPD); AccountingPtr->ResponseStatusCodeGroup[1]++; AccountingPtr->ResponseStatusCodeCount[RequestHttpStatusIndex(101)]++; InstanceMutexUnLock (INSTANCE_MUTEX_HTTPD); rqptr->rqResponse.HttpStatus = 101; rqptr->rqResponse.HeaderSent = true; /* if it's a WebSocket then by default do-not-disturb */ if (rqptr->DclTaskPtr) if (rqptr->DclTaskPtr->LifeTimeSecond != DCL_DO_NOT_DISTURB) rqptr->DclTaskPtr->LifeTimeSecond = DCL_WEBSOCKET_DND; HttpdTimerSet (rqptr, TIMER_OUTPUT, -1); HttpdTimerSet (rqptr, TIMER_NOPROGRESS, -1); return (SS$_NORMAL); } /*****************************************************************************/ /* CONFIG.C module calls this function with a string list of all/any [CharsetConvert] directive entries. Enter the required parameters into a charset NCS conversion structure and places this at the end of a linked list. The conversion information is stored as a series of null-terminated strings addressed by the three pointers. The first, the 'DocCharsetPtr', may itself be one or more null-terminated strings, terminated by a null-string. Then the 'AccCharsetPtr' and 'NcsCfNamePtr' strings. */ ResponseCharsetConfig (META_CONFIG *mcptr) { static char ProblemParam [] = "NCS conversion parameter problem\n\\!AZ\\", ProblemNcsCf [] = "NCS conversion function !AZ\n%!&M"; int status; char *aptr, *cptr, *sptr; $DESCRIPTOR (CsNameDsc, ""); RESPONSE_CHARSET *csptr; /*********/ /* begin */ /*********/ if (WATCH_MODULE(WATCH_MOD_CONFIG)) WatchThis (NULL, FI_LI, WATCH_MOD_CONFIG, "ResponseCharsetConfig()\n!&Z", Config.cfContent.CharsetConvertPtr); cptr = Config.cfContent.CharsetConvertPtr; while (*cptr) { /* scan to get maximum possible length of this string */ for (aptr = sptr = cptr; *sptr && *sptr != STRING_LIST_CHAR; sptr++); /* allocate sufficient storage */ csptr = (RESPONSE_CHARSET*) VmGet (sizeof(RESPONSE_CHARSET)+((sptr-cptr)*2)+4); /* this first, literal string is used only for WATCH purposes */ for (sptr = csptr->Storage; *cptr && *cptr != STRING_LIST_CHAR; *sptr++ = *cptr++); *sptr++ = '\0'; /* back to start, skip leading white space */ for (cptr = aptr; *cptr && ISLWS(*cptr); cptr++); /* parse 'doc-charset' string */ csptr->DocCharsetPtr = sptr; while (*cptr && !ISLWS(*cptr) && *cptr != STRING_LIST_CHAR) *sptr++ = *cptr++; *sptr++ = '\0'; while (*cptr && ISLWS(*cptr)) cptr++; /* parse 'accept-charset' string */ csptr->AccCharsetPtr = sptr; while (*cptr) { /* parse out comma/white-space delimited string(s) */ while (*cptr && !ISLWS(*cptr) && *cptr != ',' && *cptr != STRING_LIST_CHAR) *sptr++ = *cptr++; *sptr++ = '\0'; while (*cptr && ISLWS(*cptr)) cptr++; if (*cptr != ',') break; cptr++; while (*cptr && ISLWS(*cptr)) cptr++; } /* empty string terminate (possible set of) 'accept-charset' string(s) */ *sptr++ = '\0'; while (*cptr && ISLWS(*cptr)) cptr++; /* parse 'NCS-conversion function' string */ csptr->NcsCfFactor = 1; csptr->NcsCfNamePtr = sptr; while (*cptr && !ISLWS(*cptr) && *cptr != '=' && *cptr != STRING_LIST_CHAR) *sptr++ = *cptr++; *sptr++ = '\0'; if (*cptr == '=') { cptr++; csptr->NcsCfFactor = atoi(cptr); if (csptr->NcsCfFactor < 1) csptr->NcsCfFactor = 1; if (csptr->NcsCfFactor > 4) csptr->NcsCfFactor = 4; while (*cptr && !ISLWS(*cptr) && *cptr != STRING_LIST_CHAR) cptr++; } /* scan over any trailing white-space */ while (*cptr && *cptr != STRING_LIST_CHAR) cptr++; if (*cptr) cptr++; if (!*csptr->DocCharsetPtr || !*csptr->AccCharsetPtr) { MetaConReport (mcptr, METACON_REPORT_ERROR, ProblemParam, aptr); VmFree (csptr, FI_LI); continue; } if (*csptr->NcsCfNamePtr) { CsNameDsc.dsc$a_pointer = csptr->NcsCfNamePtr; CsNameDsc.dsc$w_length = strlen(csptr->NcsCfNamePtr); status = ncs$get_cf (&csptr->NcsCf, &CsNameDsc, NULL); if (VMSnok (status)) { MetaConReport (mcptr, METACON_REPORT_ERROR, ProblemNcsCf, csptr->NcsCfNamePtr, status); VmFree (csptr, FI_LI); continue; } } else csptr->NcsCf = 0; ListAddTail (&ResponseCharsetList, csptr); ResponseCharsetCount++; } } /*****************************************************************************/ /* Accepts a character set name string. Compares this to the request's accepted character sets, one by one. The comparison algorithm is described in this module's preamble. Returns a pointer to the response character set. Modifies the '->rqResponse.CharsetNcsCf' longword to contain the NCS character set conversion function. This will be used to indicate a conversion should be performed before writing a network buffer. */ char* ResponseCharsetConvertBegin ( REQUEST_STRUCT *rqptr, char *CharsetPtr ) { BOOL MatchAllWildcard; int status; char ch; char *aptr, *cptr, *sptr; RESPONSE_CHARSET *csptr; LIST_ENTRY *leptr; /*********/ /* begin */ /*********/ if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD_RESPONSE)) WatchThis (rqptr, FI_LI, WATCH_MOD_RESPONSE, "ResponseCharsetConvertBegin() !&Z !&Z", CharsetPtr, rqptr->rqHeader.AcceptCharsetPtr); if (!(cptr = rqptr->rqHeader.AcceptCharsetPtr)) { if (WATCHING(rqptr) && WATCH_CATEGORY(WATCH_RESPONSE_BODY)) WatchThis (rqptr, FI_LI, WATCH_RESPONSE_BODY, "NCS$CONVERT no request charset"); return (CharsetPtr); } if (WATCHING(rqptr) && WATCH_CATEGORY(WATCH_RESPONSE_BODY)) WatchThis (rqptr, FI_LI, WATCH_RESPONSE_BODY, "NCS$CONVERT !AZ to !AZ", CharsetPtr, cptr); InstanceGblSecIncrLong (&AccountingPtr->NcsCount); /* attempt to match character set with one accepted by the request header */ while (*cptr) { while (*cptr && ISLWS(*cptr)) cptr++; sptr = cptr; while (*cptr && *cptr != ';' && *cptr != ',' && !ISLWS(*cptr)) cptr++; ch = *cptr; *cptr = '\0'; if (SAME2(sptr,'*\0')) { /* note the presence of a match-all wildcard */ MatchAllWildcard = true; if (*cptr = ch) cptr++; while (*cptr && *cptr != ',') cptr++; while (*cptr == ',') cptr++; continue; } if (strsame (CharsetPtr, sptr, -1)) { /* exact match, no conversion necessary */ if (WATCHING(rqptr) && WATCH_CATEGORY(WATCH_RESPONSE_BODY)) WatchDataFormatted ("\"!AZ\" match, no conversion\n", sptr); *cptr = ch; return (CharsetPtr); } /* look through the list of sets of convertable character sets */ for (leptr = ResponseCharsetList.HeadPtr; leptr; leptr = leptr->NextPtr) { csptr = (RESPONSE_CHARSET*)leptr; if (WATCHING(rqptr) && WATCH_CATEGORY(WATCH_RESPONSE_BODY)) WatchDataFormatted ("\"!AZ\" with \"!AZ\"\n", sptr, csptr->Storage); /* if the 'doc-charset' does not match the 'document' charset */ if (!StringMatch (rqptr, CharsetPtr, csptr->DocCharsetPtr)) continue; /* if (one of) 'acc-charset' does not match 'Accept-Charset:' */ aptr = csptr->AccCharsetPtr; while (*aptr) { if (StringMatch (rqptr, sptr, aptr)) break; while (*aptr) aptr++; aptr++; } if (!*aptr) continue; /* matches both charset strings */ rqptr->rqResponse.CharsetNcsCf = csptr->NcsCf; rqptr->rqResponse.CharsetNcsCfFactor = csptr->NcsCfFactor; if (WATCHING(rqptr) && WATCH_CATEGORY(WATCH_RESPONSE_BODY)) if (rqptr->rqResponse.CharsetNcsCf) WatchDataFormatted ("\"!AZ\" conversion using \"!AZ\"\n", sptr, csptr->NcsCfNamePtr); else WatchDataFormatted ("\"!AZ\" alias, no conversion\n", sptr); if (rqptr->rqResponse.CharsetNcsCf) InstanceGblSecIncrLong (&AccountingPtr->NcsConvertCount); CharsetPtr = VmGetHeap (rqptr, strlen(sptr)+1); strcpy (CharsetPtr, sptr); *cptr = ch; return (CharsetPtr); } if (*cptr = ch) cptr++; while (*cptr && *cptr != ',') cptr++; while (*cptr == ',') cptr++; } if (WATCHING(rqptr) && WATCH_CATEGORY(WATCH_RESPONSE_BODY)) if (MatchAllWildcard) WatchDataFormatted ("\"*\" wildcard, no conversion\n"); else WatchDataFormatted ("no match, no conversion\n"); return (CharsetPtr); } /*****************************************************************************/ /* When 'rqResponse.CharsetNcsCf' is non-zero NetWrite() calls this function immediately before writing the buffer specified by 'DataPtr' and 'DataLength' passed as the addresses of these two parameters by 'DataPtrPtr' and 'DataLengthPtr'. This function uses ncs$convert() to transliterate each character into the target character set. It allocates specific buffer space (which may vary in size depending on whether it coming from cache, file or other modules), the conversion is performed into that, and the data pointer and size originally passed as parameters are adjusted to point to this. Returns a VMS status which NetWrite() should report and abort the write if an error. */ int ResponseCharsetConvert ( REQUEST_STRUCT *rqptr, char **DataPtrPtr, int *DataLengthPtr ) { static $DESCRIPTOR (SrcDsc, ""); static $DESCRIPTOR (DstDsc, ""); int status, BufferSize; unsigned short CvtLen, NotCvtLen; /*********/ /* begin */ /*********/ if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD_RESPONSE)) WatchThis (rqptr, FI_LI, WATCH_MOD_RESPONSE, "ResponseCharsetConvert() !&X !UL !UL !UL", *DataPtrPtr, *DataLengthPtr, rqptr->rqResponse.CharsetNcsBufferSize, rqptr->rqResponse.CharsetNcsCfFactor); if (rqptr->rqResponse.CharsetNcsBufferSize < *DataLengthPtr * rqptr->rqResponse.CharsetNcsCfFactor) { if (*DataLengthPtr < OutputBufferSize) BufferSize = OutputBufferSize; else BufferSize = *DataLengthPtr; BufferSize *= rqptr->rqResponse.CharsetNcsCfFactor; if (BufferSize > 65535) { ErrorNoticed (rqptr, SS$_IVBUFLEN, NULL, FI_LI); return (SS$_IVBUFLEN); } if (rqptr->rqResponse.CharsetNcsBufferPtr) VmFreeFromHeap (rqptr, rqptr->rqResponse.CharsetNcsBufferPtr, FI_LI); rqptr->rqResponse.CharsetNcsBufferPtr = VmGetHeap (rqptr, BufferSize); rqptr->rqResponse.CharsetNcsBufferSize = BufferSize; } SrcDsc.dsc$a_pointer = *DataPtrPtr; SrcDsc.dsc$w_length = *DataLengthPtr; DstDsc.dsc$a_pointer = rqptr->rqResponse.CharsetNcsBufferPtr; DstDsc.dsc$w_length = rqptr->rqResponse.CharsetNcsBufferSize; status = ncs$convert (&rqptr->rqResponse.CharsetNcsCf, &SrcDsc, &DstDsc, &CvtLen, &NotCvtLen); if (VMSok (status) && NotCvtLen) status = SS$_BUGCHECK; if (VMSnok (status)) ErrorNoticed (rqptr, status, NULL, FI_LI); if (WATCHING(rqptr) && WATCH_CATEGORY(WATCH_RESPONSE_BODY)) WatchThis (rqptr, FI_LI, WATCH_RESPONSE_BODY, "NCS$CONVERT src:!UL dst:!UL cvt:!UL not:!UL !&S", *DataLengthPtr, rqptr->rqResponse.CharsetNcsBufferSize, CvtLen, NotCvtLen, status); *DataPtrPtr = rqptr->rqResponse.CharsetNcsBufferPtr; *DataLengthPtr = CvtLen; return (status); } /*****************************************************************************/ /* Implements the HTTP/1.1 OPTIONS method by returning an appropriate response header. Discriminate between per-server ('*') and per-resource (path) requests and respond using "Allow:". */ ResponseOptions (REQUEST_STRUCT *rqptr) { BOOL WebDavLockingOptions, WebDavOptions; /*********/ /* begin */ /*********/ if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD_RESPONSE)) WatchThis (rqptr, FI_LI, WATCH_MOD_RESPONSE, "ResponseOptions() !&F", &ResponseOptions); if (!rqptr->rqHeader.RequestUriPtr || rqptr->rqHeader.RequestUriPtr[0] == '*') { if (WebDavLockingEnabled) ResponseHeader (rqptr, 200, NULL, 0, NULL, ResponseAllowWebDavLockServer); else if (WebDavEnabled) ResponseHeader (rqptr, 200, NULL, 0, NULL, ResponseAllowWebDavServer); else ResponseHeader (rqptr, 200, NULL, 0, NULL, ResponseAllowServer); } else { WebDavOptions = WebDavEnabled && (rqptr->rqPathSet.WebDavProfile || rqptr->rqPathSet.WebDavRead || rqptr->rqPathSet.WebDavServer || rqptr->rqPathSet.WebDavWrite); WebDavLockingOptions = WebDavOptions && WebDavLockingEnabled && !rqptr->rqPathSet.WebDavNoLock; if (WebDavLockingOptions) ResponseHeader (rqptr, 200, NULL, 0, NULL, ResponseAllowWebDavLockResource); else if (WebDavOptions) ResponseHeader (rqptr, 200, NULL, 0, NULL, ResponseAllowWebDavResource); else ResponseHeader (rqptr, 200, NULL, 0, NULL, ResponseAllowResource); } RequestEnd (rqptr); } /*****************************************************************************/ /* Echo back to the client as a "message/http" response the entire request header and any body content. This implements the HTTP/1.1 TRACE method and the WASD /echo/ 'script'. */ ResponseTrace (REQUEST_STRUCT *rqptr) { /*********/ /* begin */ /*********/ if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD_RESPONSE)) WatchThis (rqptr, FI_LI, WATCH_MOD_RESPONSE, "ResponseTrace() !&F", &ResponseTrace); if (!rqptr->rqBody.DataPtr) { /* first call */ BodyReadBegin (rqptr, &ResponseTrace, NULL); return; } if (!rqptr->rqResponse.PreExpired) { /* second call (from BodyRead()) */ rqptr->rqResponse.PreExpired = rqptr->rqResponse.NoGzip = true; if (rqptr->rqHeader.Method == HTTP_METHOD_TRACE) ResponseHeader (rqptr, 200, "message/http", -1, NULL, NULL); else ResponseHeader (rqptr, 200, "text/plain", -1, NULL, NULL); NetWrite (rqptr, &ResponseTrace, rqptr->rqHeader.RequestHeaderPtr, rqptr->rqHeader.RequestHeaderLength); return; } /* third and subsequent calls */ if (VMSnok (rqptr->rqBody.DataStatus)) { /* error or end-of-file (body) */ RequestEnd (rqptr); return; } NetWrite (rqptr, &BodyRead, rqptr->rqBody.DataPtr, rqptr->rqBody.DataCount); } /*****************************************************************************/ /* Return a stream of pseudo-random alpha-numeric noise. Designed to return low-cost 'discouraging' responses to clients that may be sourcing attacks. This function is called directly to initiate and thereafter as an AST. Output stops after the client disconnects or when the path specified (e.g. "/hiss/30" kilobytes) or ADMIN_SCRIPT_HISS_MAX_BYTES number of bytes is reached. */ ResponseHiss (REQUEST_STRUCT *rqptr) { char *cptr, *sptr, *zptr; unsigned long RandomNumber; /*********/ /* begin */ /*********/ if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD_RESPONSE)) WatchThis (rqptr, FI_LI, WATCH_MOD_RESPONSE, "ResponseHiss() !&F !AZ", &ResponseHiss, rqptr->MappedPathPtr); if (!STR_DSC_SIZE(&rqptr->NetWriteBufferDsc)) { /* first call, initialize */ rqptr->rqResponse.HttpStatus = 403; rqptr->rqResponse.HeaderSent = true; StrDscIfNotBegin (rqptr, &rqptr->NetWriteBufferDsc, NetReadBufferSize); zptr = (sptr = STR_DSC_PTR(&rqptr->NetWriteBufferDsc)) + STR_DSC_SIZE(&rqptr->NetWriteBufferDsc); RandomNumber = HttpdBinTime[0]; while (sptr < zptr) { /* cheap (no subroutine call) MTH$RANDOM() */ RandomNumber = RandomNumber * 69069 + 1; cptr = (char*)&RandomNumber; if (isalnum(*cptr)) *sptr++ = *cptr; cptr++; if (isalnum(*cptr)) *sptr++ = *cptr; cptr++; if (isalnum(*cptr)) *sptr++ = *cptr; cptr++; if (isalnum(*cptr)) *sptr++ = *cptr; } /* get value optionally following path */ cptr = rqptr->MappedPathPtr; if (*cptr == '/') cptr++; if (isdigit(*cptr)) rqptr->rqResponse.HissBytesMax = atoi(cptr) * 1024; if (!rqptr->rqResponse.HissBytesMax || rqptr->rqResponse.HissBytesMax > ADMIN_SCRIPT_HISS_MAX_BYTES) rqptr->rqResponse.HissBytesMax = ADMIN_SCRIPT_HISS_MAX_BYTES; /* before any real network write just fudge the status */ rqptr->rqNet.WriteIOsb.Status = SS$_NORMAL; } if (VMSnok (rqptr->rqNet.WriteIOsb.Status) || rqptr->BytesRawTx[0] >= rqptr->rqResponse.HissBytesMax) { StrDscNoContent(&rqptr->NetWriteBufferDsc); RequestEnd (rqptr); return; } NetWriteRaw (rqptr, &ResponseHiss, STR_DSC_PTR(&rqptr->NetWriteBufferDsc), STR_DSC_SIZE(&rqptr->NetWriteBufferDsc)); } /*****************************************************************************/ /* After the mapping the path, etc., parse it again (in case of error, RequestScript() ignores errors) and return the VMS file name as a plain text document. This function merely translates any logicals, etc., and reports the resulting name, it does not demonstrate the directory or file actually exists. The call is set up in RequestScript(). */ ResponseWhere ( REQUEST_STRUCT *rqptr, char *MappedFile ) { int status; /*********/ /* begin */ /*********/ if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD_RESPONSE)) WatchThis (rqptr, FI_LI, WATCH_MOD_RESPONSE, "ResponseWhere()"); OdsParse (&rqptr->ParseOds, MappedFile, 0, NULL, 0, NAM$M_SYNCHK, NULL, rqptr); if (VMSok (status = rqptr->ParseOds.Fab.fab$l_sts)) { if (VMSnok (status = OdsParseTerminate (&rqptr->ParseOds))) { ErrorNoticed (rqptr, status, NULL, FI_LI); RequestEnd (rqptr); return; } rqptr->rqResponse.PreExpired = true; ResponseHeader200 (rqptr, "text/plain", NULL); /* queue a network write to the client, AST to end processing */ NetWrite (rqptr, &RequestEnd, rqptr->ParseOds.ExpFileName, rqptr->ParseOds.ExpFileNameLength); return; } else { rqptr->rqResponse.ErrorTextPtr = rqptr->rqHeader.PathInfoPtr; ErrorVmsStatus (rqptr, status, FI_LI); RequestEnd (rqptr); return; } } /*****************************************************************************/ /* Match any request match conditionals to any entity present. Generate a 412 HTTP response (precondition failed) and return false to halt continued request processing if the conditional(s) requires, otherwise return true to continue processing the request. */ BOOL ResponseEntityMatch ( REQUEST_STRUCT *rqptr, char *EntityTag ) { BOOL DoesMatch; char *cptr, *sptr; /*********/ /* begin */ /*********/ if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD_RESPONSE)) WatchThis (rqptr, FI_LI, WATCH_MOD_RESPONSE, "ResponseEntityMatch()"); if (rqptr->rqHeader.IfMatchPtr) { /***********************/ /* if match entity tag */ /***********************/ if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD_RESPONSE)) WatchThis (rqptr, FI_LI, WATCH_MOD_RESPONSE, "if-match !&Z !&Z", rqptr->rqHeader.IfMatchPtr, EntityTag && *EntityTag ? EntityTag : "(none)"); if (!(EntityTag && *EntityTag)) DoesMatch = false; else if (rqptr->rqHeader.IfMatchPtr[0] == '*') DoesMatch = true; else { DoesMatch = false; cptr = rqptr->rqHeader.IfMatchPtr; while (*cptr) { while (*cptr && *cptr != '\"') cptr++; if (*cptr) cptr++; for (sptr = cptr; *sptr && *sptr != '\"'; sptr++); if (*sptr) { *sptr = '\0'; DoesMatch = !strcmp (cptr, EntityTag); if (WATCHING(rqptr) && WATCH_CATEGORY(WATCH_RESPONSE)) WatchThis (rqptr, FI_LI, WATCH_RESPONSE, "IF match !AZ !AZ !&B", EntityTag, cptr, DoesMatch); *sptr = '\"'; if (DoesMatch) break; cptr = sptr + 1; } } } if (!DoesMatch) { /* precondition failed */ if (rqptr->WebDavTaskPtr) DavWebResponse (rqptr, 412, 0, "If-Match:", FI_LI); else ResponseHeader (rqptr, 412, NULL, 0, NULL, NULL); return (false); } } if (rqptr->rqHeader.IfNoneMatchPtr) { /****************************/ /* if none-match entity tag */ /****************************/ if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD_RESPONSE)) WatchThis (rqptr, FI_LI, WATCH_MOD_RESPONSE, "if-none-match !&Z !&Z", rqptr->rqHeader.IfNoneMatchPtr, EntityTag && *EntityTag ? EntityTag : "(none)"); DoesMatch = false; if (!EntityTag || !*EntityTag) DoesMatch = false; else if (rqptr->rqHeader.IfNoneMatchPtr[0] == '*') DoesMatch = true; else { cptr = rqptr->rqHeader.IfNoneMatchPtr; while (*cptr) { while (*cptr && *cptr != '\"') cptr++; if (*cptr) cptr++; for (sptr = cptr; *sptr && *sptr != '\"'; sptr++); if (*sptr) { *sptr = '\0'; DoesMatch = !strcmp (cptr, EntityTag); if (WATCHING(rqptr) && WATCH_CATEGORY(WATCH_RESPONSE)) WatchThis (rqptr, FI_LI, WATCH_RESPONSE, "IF none-match !AZ !AZ !&B", EntityTag, cptr, DoesMatch); *sptr = '\"'; if (DoesMatch) break; cptr = sptr + 1; } } } if (DoesMatch && rqptr->rqHeader.Method != HTTP_METHOD_GET && rqptr->rqHeader.Method != HTTP_METHOD_HEAD) { /* precondition failed */ if (rqptr->WebDavTaskPtr) DavWebResponse (rqptr, 412, 0, "If-None-Match:", FI_LI); else ResponseHeader (rqptr, 412, NULL, 0, NULL, NULL); return (false); } } return (true); } /*****************************************************************************/