/*****************************************************************************/ /* Request.c Get, process and execute the HTTP request from the client. Some server directives are detected in this module. Server directives take the form of a special path or query string and are CASE SENSISTIVE. ?http=... any query string beginning "httpd=" will be ignored by the default query script and the HTTPd will pass it to the requested functionality /httpd/-/admin/ generates a server administration menu /httpd/-/admin/graphic/... generates graphics /httpd/-/admin/report/... generates various reports /httpd/-/admin/revise/... allows form-based configuration /httpd/-/admin/control/... allows server to be stopped, restarted, etc. /httpd/-/change/... change user authentication /httpd/-/verify/... reverse proxy authorization verification /echo/ echoes complete request (header and any body) /tree/ directory tree /upd/ activates the update module /where/ reports mapped and parsed path /Xray/ returns an "Xray" of the request (i.e. header and body of response as a plain-text document) Error counting within this module attempts to track the reason that any connection accepted fails before being passsed to another module for processing (i.e. request format error, path forbidden by rule). AUTHORIZATION MAY BE PERFORMED TWICE! ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ The first, to authorize the full path provided with the request (after it has been mapped!) The second, if the request path contained a script any path specification following the script component is also checked. In this way access to controlled information is not inadvertantly allowed because the script itself is not controlled. In general, script specifications should not be part of an authorized path if that script is used to access other paths (i.e. does not return data generated by itself), apply authorization to data paths. VERSION HISTORY --------------- 07-JUL-2013 MGD remove reset of persistent flag from OPTIONS and DELETE 25-JUL-2012 MGD DNT: request header (do not track) 21-APR-2012 MGD bugfix; RequestBegin() remove RequestEnd() following failed SesolaNetBegin() resulted in redundant request rundown 06-FEB-2012 MGD RequestGet() no longer report 408 for unused connections RequestEndEnd() likewise ignore unused connections (Chrome) 14-NOV-2011 MGD bugfix; RequestEndEnd() '->WebSocketCount' already locked 17-SEP-2011 MGD bugfix; RequestRedirect() only concat '&' if including query 06-FEB-2011 MGD "Vary:" request header field 30-NOV-2010 MGD exclude WebSocket and proxy tunnel requests from overall min/max/ave duration statistics 04-SEP-2010 MGD RequestLineParse() allow for proxied http://[ipv6%eth0]/ 28-AUG-2010 MGD bugfix; regression at 10.0.1 with proxy authorization 19-JUN-2010 MGD bugfix; RequestGet() MAX_REQUEST_HEADER (per JPP) 29-MAY-2010 MGD RequestExecutePostCache() UTF-8 decode WebDAV objects 11-MAY-2010 JPP RequestRedirect() add support for WebDAV "Destination:" field 20-JAN-2010 MGD WebSockets support, including "Connection: upgrade", "Origin:", "WebSocket-Protocol:" 11-JAN-2010 MGD RequestRedirect() add return length (overflow) check 02-JAN-2010 MGD make proxy requests subject to throttle (per JPP) 14-NOV-2009 MGD [ServiceShareSSH] and RequestSshBegin() allows a Web service to "share" with and proxy to SSH 26-MAY-2009 MGD RequestRedirect() allow a redirect to include its own query string and then concatenate any request query with '&'.. 15-FEB-2008 MGD RequestReport() per-current, per-connection, per-throttle and per-history 05-JAN-2008 MGD RequestGblSecUpdate() include remote user and realm in request monitor data 10-DEC-2007 MGD RequestExecutePostCache() check again for RequestHomePage() before final RequestFile() 25-NOV-2007 MGD RequestRedirect() include response cookie(s) RequestRedact() and RequestEnd() redact buffer processing 06-JUN-2007 MGD RequestHttpStatusIndex() 29-MAY-2007 JPP bugfix; RequestGet() buggy browser kludge 02-MAY-2006 JPP RequestGet() now handles extraneous which buggy browsers can incorrectly insert after the body of a valid request. (See RFC 2616 section 4.1) 19-APR-2007 MGD RequestExecutePostAuth1() kludge to allow 'implied' scripts 19-SEP-2006 MGD RequestHttpStatusCode() provides more fine-grained HTTP response status code accounting (mainly for WOTSUP) 15-JUL-2006 MGD bugfix; RequestGet() timestamp the event immediately 26-JAN-2006 MGD RequestRedirect() "//:port/path" (i.e. begins with "//:") allows a redirect to a different port on the same host 26-DEC-2005 MGD bugfix; RequestFields() allow for header lines with no white-space between field name and value (jpp@esme.fr) 11-JUN-2005 MGD bugfix; "?httpd=content" should include SSI files 22-FEB-2005 MGD bugfix; keyword search exclusion on configured file type 21-JAN-2005 MGD disable SSL persistent connections for VMS Navigator Gold 05-JAN-2005 MGD RequestParseAndExecute() remove explicit disable of POST & PUT connection persistence 16-DEC-2004 MGD handle chunked transfer-encoding during request rundown 16-OCT-2004 MGD request rundown changes for GZIP encoding, configurable service unavailable 503 when log write fails 06-OCT-2004 MGD allow throttling of mapping/mapped error messages 12-AUG-2004 MGD bugfix; HttpTimerSet() after mapping in case of SET timeout 20-JUL-2004 MGD HTTP/1.1 compliance, "ETag:", "Expect:", "If-Match:", "If-None-Match:", "Max-Forwards:", "Transfer-Encoding:", "Trailer:" headers, persistent connection detection and processing refine, pipelined request processing, refine redirection request header rebuild 27-JAN-2004 MGD add connect processing and keep-alive accounting items 15-JAN-2004 MGD bugfix; RequestExecute() error by redirect 12-JAN-2004 MGD RequestExecute() resolve virtual service if local path 10-JAN-2004 MGD 'delete-on-close' file specification extended 16-DEC-2003 MGD mapping now URL-encodes a redirect wildcard path portions 18-NOV-2003 MGD reverse proxy 302 "Location:" rewrite persistent storage 07-OCT-2003 MGD bugfix; "internal" script detection 15-SEP-2003 MGD bugfix; keyword search exclude file type bugfix; keepalive notepad needs to be explicitly NULLed 21-AUG-2003 MGD "Range:" header field 03-AUG-2003 MGD RequestDump() 09-JUL-2003 MGD revise request and history report format 31-MAY-2003 MGD RequestHomePage() check [Welcome] against [DclScriptRunTime] for welcome/home pages that are provided by scripting 10-MAY-2003 MGD revise request header field processing and storage, improve efficiency of RequestRedirect() 02-APR-2003 MGD allow for "X-Forwarded-For:" request header field 26-MAR-2003 MGD minor changes to alert processing RequestRedirect() append remaining CGI response header 24-MAR-2003 MGD bugfix; RequestDiscardBody() reset of body processing ASTs 07-FEB-2003 MGD no default search script path setting 17-JAN-2003 MGD implement path setting 'script=path=find' 12-OCT-2002 MGD check for device and directory (minimum) before parse 15-AUG-2002 MGD rework (yet again) path alert for more flexibility, bugfix; 'Xray' broken in v8, repaired and reworked 08-AUG-2002 MGD RequestRedirect() should URL-encode local redirection 03-JUL-2002 MGD add ResponseHiss() 30-JUN-2002 MGD adjust RequestBodyDiscard() for already-started read 22-MAY-2002 MGD refine scheme detection in RequestRedirect() 06-JUN-2002 MGD RequestDiscardBody() for (at least) Netscape 3/4 15-MAY-2002 MGD RequestRedirect() allow for wildcard DNS "proxy", "Cache-Control:" field for Mozilla compatibility 31-MAR-2002 MGD mask potential passwords in request URIs, keep-alive decision logic to RequestFields() 02-FEB-2002 MGD rework echo due to request body processing changes 14-OCT-2001 MGD add an explicit test for, and message regarding, DECnet use in mapped file names, reporting it as unsupported 04-AUG-2001 MGD modifications in line with changes in the handling of file and cache (now MD5 hash based) processing, support module WATCHing 11-JUL-2001 MGD allow '?' on the end of a REDIRECT mapping template to propagate the original request's query string 28-JUN-2001 MGD extend local redirection syntax to reinstate "reverse proxy" (e.g. "/http://the.host.name/path") 10-MAY-2001 MGD calls to throttle module 27-FEB-2001 MGD script path parse content-type check 08-JAN-2001 MGD bugfix; RequestDiscardAst() 30-DEC-2000 MGD rework for FILE.C getting file contents in-memory 01-OCT-2000 MGD authorize either request *or* mapped path (script path authorization is/has always been on mapped) 26-AUG-2000 MGD WATCH processing, peek, or peek+processing 08-AUG-2000 MGD bugfix; include Accept-Encoding when redirecting, bugfix; (sort-of) ensure redirected BytesRawRx carried over 24-JUN-2000 MGD persistent run-time environments, bugfix; HEAD requests specifying content-length bugfix; increase size of buffer in RequestRedirect() 07-MAY-2000 MGD session track 04-MAR-2000 MGD use FaolToNet(), et.al., add "http:///" and "https:///" to redirection syntax 08-FEB-2000 MGD search script exclude specified file types 27-DEC-1999 MGD support ODS-2 and ODS-5 using ODS module 11-NOV-1999 MGD allow for "ETag:" (only for proxy propagation) 20-OCT-1999 MGD redirect now substitutes the scheme and "Host:" or server host:port into a mapping rule like "REDIRECT ///some/path" or just the scheme into "REDIRECT //host.domain/path/" 28-AUG-1999 MGD accomodation for asynchronous authorization 30-JUL-1999 MGD bugfix; HttpdExit() requires a parameter! 12-JUN-1999 MGD looks like a proxy request? send it to ProxyRequestBegin() 04-APR-1999 MGD provide HTTP/0.9 functionality (finally!) 10-JAN-1999 MGD proxy serving, history report format refined, added service information to history report 07-NOV-1998 MGD WATCH facility 18-OCT-1998 MGD error report redirection 19-SEP-1998 MGD improve granularity of cache search, RequestFileNoType() now redirects for directories, automatic scripting suppressed with HTTPd query string 12-JUL-1998 MGD bugfix; RequestEnd() no status returned from RequestBegin()! 14-MAY-1998 MGD ?httpd=... generalized from index processing to all requests 02-APR-1998 MGD no longer log internal redirects 28-MAR-1998 MGD declare an AST for local redirection parsing (in case a CGIplus script redirected and is exiting!) 28-JAN-1998 MGD moved more appropriate functions from HTTPd.C into here, header parsing now allows for hiatus in end-header blank line, allow for directories specified as "/dir1/dir2" 07-JAN-1998 MGD provide URL-encoded decode on path 05-OCT-1997 MGD file cache, added "Accept-Charset:", "Forwarded:" and "Host:" 18-SEP-1997 MGD HTTP status code mapping 09-AUG-1997 MGD message database 27-JUL-1997 MGD modified "Accept:" header lines processing 08-JUN-1997 MGD added "Pragma:" header field detection 27-MAR-1997 MGD added "temporary" file detection (for UPD/PUT preview) 01-FEB-1997 MGD HTTPd version 4 01-OCT-1996 MGD added more reports 25-JUL-1996 MGD use optional "length=" within "If-Modified-Since:" header 12-APR-1996 MGD RMS parse structures moved to thread data; persistent connections ("keep-alive"); changed internal directive from query string to path; observed Multinet disconnection/zero-byte behaviour (request now aborts if Multinet returns zero bytes) 01-DEC-1995 MGD HTTPd version 3 27-SEP-1995 MGD extensive rework of some functions; added 'Referer:', 'User-Agent:', 'If-Modified-Since:' 01-APR-1995 MGD initial development for addition to multi-threaded daemon */ /*****************************************************************************/ #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 #include /* application header files */ #include "wasd.h" #define WASD_MODULE "REQUEST" /******************/ /* global storage */ /******************/ LIST_HEAD RequestList; LIST_HEAD RequestHistoryList; int RequestHistoryCount, RequestHistoryMax; /********************/ /* external storage */ /********************/ #ifdef DBUG extern BOOL Debug; #else #define Debug 0 #endif extern BOOL CacheEnabled, ControlExitRequested, ControlRestartRequested, HttpdTicking, LoggingEnabled, LoggingFileError, MonitorEnabled, MapUrlExtensionMethod, NetConnectSuspend, OdsExtended, WebDavEnabled, WebDavLockingEnabled; extern int ActivityConnectCurrent, ActivityConnectProcessing, ActivityTotalMinutes, DclSysOutputSize, ErrorsNoticedCount, ExitStatus, HttpdGblSecPages, InstanceNodeConfig, NetConcurrentProcessMax, NetConnectCurrent, NetConnectProcessing, NetConnectProcessingMax, NetReadBufferSize, OpcomMessages, OutputBufferSize, ProxyServiceCount, ServiceCount, ServiceTunnelSshCount, SsiSizeMax, WebSockCurrent; extern int ToLowerCase[], ToUpperCase[]; extern unsigned long ErrorsNoticedBinTime[], InstanceMutexCount[], InstanceMutexWaitCount[]; extern char ConfigContentTypeIsMap[], ConfigContentTypeMenu[], ConfigContentTypeSsi[], ConfigContentTypeUrl[], ErrorSanityCheck[], ServerHostPort[], SoftwareID[]; extern ACCOUNTING_STRUCT *AccountingPtr; extern CONFIG_STRUCT Config; extern HTTPD_GBLSEC *HttpdGblSecPtr; extern HTTPD_PROCESS HttpdProcess; extern MSG_STRUCT Msgs; extern MAPPING_META *MappingMetaPtr; extern SERVICE_STRUCT *ServiceListHead; extern SUPERVISOR_LIST SupervisorListArray[]; extern WATCH_STRUCT Watch; /*****************************************************************************/ /* This function is called from NetAccept() where a new connection has been established and memory has just been allocated for this connection's thread structure. This function also endeavours to allow "sharing" of a service port for HTTP requests and other clients such as SSH. The objective is to allow tunneling of SSH via proxy server CONNECT which is usually confined to port 433 (tunneling of SSH, or any other protocol, on a standalone port is already possible using a RAW tunnel). Now port 443 is invariably configured to talk SSL and must talk raw octets if it's to be used as an opaque tunnel. The approach taken is to peek at the incoming TCP byte stream and see if it's TLS/SSL. If not the socket (request) is associated with a proxy tunneling service (to get the raw octets), the essential request data fudged, and proxy tunneling initiated. However, and of course, some clients do not initiate their exchange until after the server has. This is a Catch-22 of sorts. So what WASD does is after input timeout (the client waiting) it sets up the tunnel anyway and begins the proxy. The proxied server should then initiate the protocol and the client respond. The directive [ServiceShareSSH] non-zero both enables this facility for a service and sets the input timeout period (which perhaps should be shorter than the default 30 seconds because such clients will wait that long for any SSH server response). If it's a slow SSL connection (or other) the client setup will just fail with the server disconnecting when the protocol exchange makes no sense. A workable accomodation it would seem. */ RequestBegin (REQUEST_STRUCT *rqptr) { int status; char *cptr, *sptr; SERVICE_STRUCT *svptr; /*********/ /* begin */ /*********/ if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD_REQUEST)) WatchThis (rqptr, FI_LI, WATCH_MOD_REQUEST, "RequestBegin()"); if (!rqptr->rqNet.PeekIOsb.Status) { /* add entry to the top of the request list */ ListAddHead (&RequestList, rqptr); /* timestamp the transaction */ sys$gettim (&rqptr->rqTime.Vms64bit); sys$numtim (&rqptr->rqTime.VmsVector, &rqptr->rqTime.Vms64bit); HttpGmTimeString (rqptr->rqTime.GmDateTime, &rqptr->rqTime.Vms64bit); /* if it's not already running kick-off the HTTPd ticker */ if (!HttpdTicking) HttpdTick (0); /* initialize the timer for input (sort-of the same thing, I know!) */ if (rqptr->ServicePtr->ShareSSH) HttpdTimerSet (rqptr, TIMER_INPUT, rqptr->ServicePtr->ShareSSH); else HttpdTimerSet (rqptr, TIMER_INPUT, 0); } if (rqptr->ServicePtr->ShareSSH) { /* service "shareable" with SSH */ if (!rqptr->rqNet.PeekIOsb.Status) { /* peek to see if an SSH handshake is initiated */ rqptr->rqNet.PeekInProgress = true; NetPeek (rqptr, RequestBegin, rqptr->rqNet.PeekBuffer, sizeof(rqptr->rqNet.PeekBuffer)); return; } rqptr->rqNet.PeekInProgress = false; if (WATCH_CATEGORY(WATCH_NETWORK) || WATCH_CATEGORY(WATCH_NETWORK_OCTETS)) { WatchThis (rqptr, FI_LI, WATCH_NETWORK, "PEEK !&S !UL bytes", rqptr->rqNet.PeekIOsb.Status, rqptr->rqNet.PeekIOsb.Count); if (VMSok(rqptr->rqNet.PeekIOsb.Status)) if WATCH_CATEGORY(WATCH_NETWORK_OCTETS) WatchDataDump (rqptr->rqNet.PeekBuffer, rqptr->rqNet.PeekIOsb.Count); } if (SAME4(rqptr->rqNet.PeekBuffer,'SSH-')) { RequestShareBegin (rqptr); return; } if (rqptr->rqNet.PeekIOsb.Status == SS$_CANCEL) { strcpy (rqptr->rqNet.PeekBuffer, "SSH-(timeout)"); RequestShareBegin (rqptr); return; } /* drop through to continue with request */ } /* if available then set any initial report to be via the script */ if (rqptr->ServicePtr->ErrorReportPath[0]) rqptr->rqResponse.ErrorReportByRedirect = true; if (rqptr->ServicePtr->RequestScheme == SCHEME_HTTPS) { /* Secure Sockets Layer ("https://") transaction */ SesolaNetBegin (rqptr); return; } rqptr->rqNet.ReadBufferSize = NetReadBufferSize; rqptr->rqNet.ReadBufferPtr = VmGetHeap (rqptr, NetReadBufferSize); if (rqptr->ServicePtr->ProxyTunnel == PROXY_TUNNEL_FIREWALL || rqptr->ServicePtr->ProxyTunnel == PROXY_TUNNEL_RAW) { /* fudge these as if it came from the network */ rqptr->rqNet.ReadIOsb.Status = SS$_NORMAL; rqptr->rqNet.ReadIOsb.Count = 0; if (rqptr->ServicePtr->ProxyTunnel == PROXY_TUNNEL_FIREWALL) { cptr = MsgFor(rqptr,MSG_PROXY_FIREWALL_PROMPT); if (cptr[0]) { sptr = VmGetHeap (rqptr, strlen(cptr)+3); SET2(sptr,'\r\n'); strcpy (sptr+2, cptr); NetWrite (rqptr, &RequestGet, sptr, strlen(sptr)); } else SysDclAst (RequestGet, rqptr); } else SysDclAst (RequestGet, rqptr); } else NetRead (rqptr, &RequestGet, rqptr->rqNet.ReadBufferPtr, rqptr->rqNet.ReadBufferSize); } /*****************************************************************************/ /* When shared SSH the underlying protocol is opaque to WASD and so much of it needs to be fudged and the usual request processing pathway avoided. */ RequestShareBegin (REQUEST_STRUCT *rqptr) { int status; char *cptr, *sptr, *zptr; SERVICE_STRUCT *svptr; /*********/ /* begin */ /*********/ if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD_REQUEST)) WatchThis (rqptr, FI_LI, WATCH_MOD_REQUEST, "RequestShareBegin()"); rqptr->rqHeader.Method = HTTP_METHOD_SHARE_SSH; strcpy (rqptr->rqHeader.MethodName, "SSH"); rqptr->rqHeader.HttpVersion = HTTP_VERSION_0_9; /* proxy tunneling uses the mapped path for it's host info */ rqptr->rqNet.PeekBuffer[sizeof(rqptr->rqNet.PeekBuffer)-1] = '\0'; cptr = MapUrl_Map (rqptr->rqNet.PeekBuffer, 0, NULL, 0, NULL, 0, NULL, 0, NULL, 0, 0, rqptr, &rqptr->rqPathSet); /* buffer the mapped path (cptr+1 allows error and non-error mappings) */ rqptr->MappedPathLength = strlen(cptr+1)+1; rqptr->MappedPathPtr = VmGetHeap (rqptr, rqptr->MappedPathLength); memcpy (rqptr->MappedPathPtr, cptr, rqptr->MappedPathLength); /* if mapping error */ if (!cptr[0] && cptr[1]) { /* the next function uses the buffered mapping error */ RequestExecuteMappingError (rqptr); return; } if (WATCHING(rqptr) && WATCH_CATEGORY(WATCH_REQUEST)) WatchThis (rqptr, FI_LI, WATCH_REQUEST, "SSH !AZ", *cptr ? cptr : cptr+1); if (strncmp (cptr, "raw://", 6)) { /* if its not raw then it's not on! */ rqptr->rqResponse.HttpStatus = 500; RequestEnd (rqptr); return; } if (rqptr->rqPathSet.ChangeServicePtr) { /* change service */ if (!ServiceChange (rqptr, rqptr->rqPathSet.ChangeServicePtr)) { RequestEnd (rqptr); return; } } rqptr->rqHeader.RequestUriPtr = rqptr->MappedPathPtr; rqptr->rqHeader.PathInfoPtr = rqptr->MappedPathPtr; rqptr->rqHeader.QueryStringPtr = ""; rqptr->rqNet.ReadBufferSize = NetReadBufferSize; rqptr->rqNet.ReadBufferPtr = VmGetHeap (rqptr, NetReadBufferSize); HttpdTimerSet (rqptr, TIMER_OUTPUT, 0); InstanceMutexLock (INSTANCE_MUTEX_HTTPD); AccountingPtr->ConnectProcessing++; ActivityConnectProcessing = AccountingPtr->ConnectProcessing; NetConnectProcessing++; if (AccountingPtr->ConnectProcessing > AccountingPtr->ConnectProcessingPeak) AccountingPtr->ConnectProcessingPeak = AccountingPtr->ConnectProcessing; AccountingPtr->MethodSshCount++; InstanceMutexUnLock (INSTANCE_MUTEX_HTTPD); ProxyRequestBegin (rqptr); } /*****************************************************************************/ /* This function MAY be AST-delivery executed ONE OR MORE TIMES, FROM ITSELF, before a request and/or connection can be finally disposed of. Basically, all ASTs hard-wired or declared *MUST* deliver back to here! This is the primary request rundown function. It checks for various tasks and/or associated structures and calls their finalize function to progressively shut the request processing down in an orderly fashion. */ RequestEnd (REQUEST_STRUCT *rqptr) { int status; FILE_CONTENT *fcptr; /*********/ /* begin */ /*********/ if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD_REQUEST)) WatchThis (rqptr, FI_LI, WATCH_MOD_REQUEST, "RequestEnd() !&F", &RequestEnd); /* asynchronous WebSockets commonly need to run themselves down */ if (WEBSOCKET_REQUEST(rqptr)) if (!WebSockEnd (rqptr)) return; if (QUAD_ZERO(rqptr->BytesRx)) { /* this request just did not get off the ground */ RequestEndEnd (rqptr); return; } /* can be still be in a throttle queue without a client connection */ if (rqptr->rqPathSet.ThrottleSet) ThrottleEnd (rqptr); /* if a proxied request */ if (rqptr->ProxyTaskPtr) { ProxyEnd (rqptr->ProxyTaskPtr); return; } if (!rqptr->rqClient.Channel) { /* as all of the following processing is for the client's benefit */ RequestEndEnd (rqptr); return; } if (rqptr->rqResponse.RedactBufferPtr) { /********************/ /* redacted request */ /********************/ if (rqptr->rqResponse.HeaderSent) { if (WATCHING(rqptr) && WATCH_CATEGORY(WATCH_RESPONSE)) WatchThis (rqptr, FI_LI, WATCH_RESPONSE, "REDACT ignored, HTTP response already sent!!"); } else if (!rqptr->rqResponse.RedactBufferCount) { rqptr->rqResponse.HttpStatus = 500; ErrorGeneral (rqptr, MsgFor(rqptr,MSG_REQUEST_REDIRECTION), FI_LI); } else if (rqptr->RedactCount >= REQUEST_REDACT_MAX) { rqptr->rqResponse.HttpStatus = 500; ErrorGeneral (rqptr, MsgFor(rqptr,MSG_REQUEST_REDIRECTION), FI_LI); } else { /*********/ /* do it */ /*********/ RequestPersistentConnection (rqptr); return; } /* explicitly free this non-heap memory */ VmFree (rqptr->rqResponse.RedactBufferPtr, FI_LI); rqptr->rqResponse.RedactBufferPtr = NULL; rqptr->rqResponse.RedactBufferSize = rqptr->rqResponse.RedactBufferCount = 0; } if (rqptr->rqNet.RedactBufferPtr) { /******************************/ /* processed redacted request */ /******************************/ /* explicitly free this non-heap memory */ VmFree (rqptr->rqNet.RedactBufferPtr, FI_LI); rqptr->rqNet.RedactBufferPtr = NULL; rqptr->rqNet.RedactBufferSize = rqptr->rqNet.RedactBufferCount = 0; } if (rqptr->rqResponse.LocationPtr && rqptr->rqResponse.LocationPtr[0]) { /***************/ /* redirection */ /***************/ status = RequestRedirect (rqptr); /* SS$_NORMAL is returned when local redirect */ if (status == SS$_NORMAL) { if (!RequestLineParse (rqptr)) { RequestEnd (rqptr); return; } RequestParseAndExecute (rqptr); return; } /* SS$_NONLOCAL is returned when non-local redirect :^) */ if (status == SS$_NONLOCAL) return; /* legitimate error whilst generating redirection */ RequestEnd (rqptr); return; } if (fcptr = rqptr->FileContentPtr) { /***********************/ /* content and handler */ /***********************/ if (fcptr->ContentLength > fcptr->ContentSizeMax) { if (WATCHING(rqptr) && (WATCH_CATEGORY(WATCH_CGI) || WATCH_CATEGORY(WATCH_REQUEST))) WatchThis (rqptr, FI_LI, WATCH_CATEGORY(WATCH_CGI) ? WATCH_CGI : WATCH_REQUEST, "CONTENT !UL exceeded !UL bytes max", fcptr->ContentLength, fcptr->ContentSizeMax); ErrorVmsStatus (rqptr, SS$_BUFFEROVF, FI_LI); rqptr->FileContentPtr = NULL; } else { /* next task gets control once file has been content-handled */ rqptr->FileContentPtr->NextTaskFunction = &RequestEnd; /* file contents loaded, now process using the specified handler */ SysDclAst (rqptr->FileContentPtr->ContentHandlerFunction, rqptr); rqptr->FileContentPtr->ContentHandlerFunction = NULL; return; } } /*******************/ /* request rundown */ /*******************/ /* if this request was WATCHing */ if (rqptr == Watch.RequestPtr) WatchEnd (rqptr); /* flush anything currently remaining in the request output buffer */ if (STR_DSC_LEN(&rqptr->NetWriteBufferDsc)) { NetWriteFullFlush (rqptr, &RequestEnd); return; } /* finalize output compression (before error reporting in plain text!) */ if (rqptr->GzipCompress.DeflateStartStream && !rqptr->GzipCompress.DeflateEndOfStream) { /* flush the GZIP compressed stream (ZLIB) buffers */ NetWrite (rqptr, &RequestEnd, NULL, 0); return; } /* finalize output transfer encoding */ if (rqptr->rqResponse.TransferEncodingChunked) { /* provide the empty chunk */ NetWrite (rqptr, &RequestEnd, NULL, 0); return; } /* if an error has been reported send this to the client */ if (ERROR_REPORTED (rqptr)) { ErrorSendToClient (rqptr); return; } /* if the response header has not yet been sent then do it */ if (rqptr->rqResponse.HeaderPtr && !rqptr->rqResponse.HeaderSent) { NetWrite (rqptr, &RequestEnd, NULL, 0); return; } if (rqptr->rqHeader.ContentLength && rqptr->rqBody.DataStatus != SS$_ENDOFFILE && rqptr->rqHeader.Method != HTTP_METHOD_GET && rqptr->rqHeader.Method != HTTP_METHOD_HEAD && rqptr->rqHeader.Method != HTTP_METHOD_CONNECT && rqptr->rqHeader.Method != HTTP_METHOD_DELETE && rqptr->rqHeader.Method != HTTP_METHOD_TRACE) { RequestDiscardBody (rqptr); return; } RequestEndEnd (rqptr); } /*****************************************************************************/ /* Request rundown has reached a point where final accounting and other post-request processing can be performed. Called directly from RequestEnd(). */ RequestEndEnd (REQUEST_STRUCT *rqptr) { int idx, status; unsigned long Remainder; unsigned long BinaryTime [2], BytesRawTx [2], BytesRawRx [2], ResultTime [2]; char *cptr; SERVICE_STRUCT *svptr; /*********/ /* begin */ /*********/ if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD_REQUEST)) WatchThis (rqptr, FI_LI, WATCH_MOD_REQUEST, "RequestEndEnd()"); /* if keeping activity statistics (before counters are decremented) */ if (ActivityTotalMinutes) { InstanceMutexLock (INSTANCE_MUTEX_ACTIVITY); GraphActivityUpdate (rqptr, true); InstanceMutexUnLock (INSTANCE_MUTEX_ACTIVITY); } #if WATCH_MOD if (WATCHING(rqptr) && Watch.Category == WATCH_ONE_SHOT_CAT && Watch.Module == WATCH_ONE_SHOT_MOD && Watch.RequestPtr) WatchPeek (Watch.RequestPtr, rqptr); #endif /* WATCH_MOD */ /* cache load from network access point */ if (rqptr->rqCache.LoadFromNet) { rqptr->rqCache.LoadStatus = SS$_ENDOFFILE; CacheLoadEnd (rqptr); } else /* cache load has been unsuccessful somewhere, tidy-up */ if (rqptr->rqCache.Loading) { rqptr->rqCache.LoadStatus = SS$_ABORT; CacheLoadEnd (rqptr); } /* Mask possible password component of a request URI before logging, etc. (e.g. turn "ftp://username:password@the.host.name/" into "ftp://username:********@the.host.name/"). This permanently changes the supplied URI (not a problem at this point). Note the '&P' directive in FAO.C that performs the same function. */ if (cptr = rqptr->rqHeader.RequestUriPtr) { while (*cptr) { if (*cptr++ != ':') continue; if (*cptr++ != '/') continue; if (*cptr++ != '/') continue; break; } if (*cptr) { while (*cptr && *cptr != '/' && *cptr != '@') cptr++; if (*cptr == '@') { while (cptr > rqptr->rqHeader.RequestUriPtr && *cptr != ':') cptr--; if (*cptr == ':') { cptr++; while (*cptr && *cptr != '@') *cptr++ = '*'; } } } } if (Watch.Category && Watch.Category != WATCH_ONE_SHOT_CAT) WatchFilterHttpStatus (rqptr); /*************************/ /* locked global section */ /*************************/ InstanceMutexLock (INSTANCE_MUTEX_HTTPD); if (QUAD_ZERO(rqptr->BytesRx)) { /* persistent connection but no bytes received, assume timeout, etc. */ if (AccountingPtr->ConnectCurrentPersistent) AccountingPtr->ConnectCurrentPersistent--; } else { /* this was a real request, do some accounting */ svptr = rqptr->ServicePtr; svptr->ConnectCount++; if (AccountingPtr->ConnectProcessing) AccountingPtr->ConnectProcessing--; ActivityConnectProcessing = AccountingPtr->ConnectProcessing; if (NetConnectProcessing) NetConnectProcessing--; if (rqptr->rqNet.ConnectionCount > AccountingPtr->RequestPersistentMax) AccountingPtr->RequestPersistentMax = rqptr->rqNet.ConnectionCount; if (rqptr->rqResponse.HttpVersion == HTTP_VERSION_1_1) AccountingPtr->RequestHttp11Count++; else if (rqptr->rqResponse.HttpVersion == HTTP_VERSION_1_0) AccountingPtr->RequestHttp10Count++; else if (rqptr->rqResponse.HttpVersion == HTTP_VERSION_0_9) AccountingPtr->RequestHttp09Count++; sys$gettim (&BinaryTime); status = lib$sub_times (&BinaryTime, &rqptr->rqTime.Vms64bit, &rqptr->rqResponse.Duration); if (VMSnok (status)) PUT_ZERO_QUAD (rqptr->rqResponse.Duration); if (WEBSOCKET_REQUEST(rqptr)) AccountingPtr->WebSocketCount++; else if (!rqptr->ProxyTunnelRequest) { /* some (double-precision) accounting */ AccountingPtr->ResponseDurationCount++; ADD_QUAD_QUAD (rqptr->rqResponse.Duration, AccountingPtr->ResponseDuration); /* only update delta times greater than 1 uS */ if ((rqptr->rqResponse.Duration[1] && rqptr->rqResponse.Duration[1] != 0xffffffff) || (rqptr->rqResponse.Duration[1] == 0xffffffff && rqptr->rqResponse.Duration[0] < 0xfffffff6)) { status = lib$sub_times (&rqptr->rqResponse.Duration, &AccountingPtr->ResponseDurationMin, &ResultTime); if (status == LIB$_NEGTIM || QUAD_ZERO (AccountingPtr->ResponseDurationMin)) { /* result is negative, this is therefore smaller than min */ PUT_QUAD_QUAD (rqptr->rqResponse.Duration, AccountingPtr->ResponseDurationMin); } } /* only update maxima from non-timer-expired requests */ if (!rqptr->rqTmr.TerminatedCount) { status = lib$sub_times (&AccountingPtr->ResponseDurationMax, &rqptr->rqResponse.Duration, &ResultTime); if (status == LIB$_NEGTIM) { /* result is negative, this is therefore larger than max */ PUT_QUAD_QUAD (rqptr->rqResponse.Duration, AccountingPtr->ResponseDurationMax); } } } PUT_QUAD_QUAD (rqptr->BytesRawRx, BytesRawRx); ADD_QUAD_QUAD (BytesRawRx, svptr->BytesRawRx) /* allow for that already accounted for (if any) */ SUB_QUAD_QUAD (rqptr->BytesAccountedForRx, BytesRawRx); ADD_QUAD_QUAD (BytesRawRx, AccountingPtr->BytesRawRx) ADD_QUAD_QUAD (BytesRawRx, AccountingPtr->BytesRawTotal) PUT_QUAD_QUAD (rqptr->BytesRawTx, BytesRawTx); ADD_QUAD_QUAD (BytesRawTx, svptr->BytesRawTx) /* allow for that already accounted for (if any) */ SUB_QUAD_QUAD (rqptr->BytesAccountedForTx, BytesRawTx); ADD_QUAD_QUAD (BytesRawTx, AccountingPtr->BytesRawTx) ADD_QUAD_QUAD (BytesRawTx, AccountingPtr->BytesRawTotal) if (rqptr->WebDavRequest || rqptr->WhiffOfWebDav) { ADD_QUAD_QUAD (rqptr->BytesRawTx, AccountingPtr->WebDavBytesRawTx) ADD_QUAD_QUAD (rqptr->BytesRawRx, AccountingPtr->WebDavBytesRawRx) } svptr->ReadErrorCount += rqptr->rqNet.ReadErrorCount; svptr->WriteErrorCount += rqptr->rqNet.WriteErrorCount; AccountingPtr->NetReadErrorCount += rqptr->rqNet.ReadErrorCount; AccountingPtr->NetWriteErrorCount += rqptr->rqNet.WriteErrorCount; rqptr->BytesPerSecond = BytesPerSecond (&rqptr->BytesRawRx, &rqptr->BytesRawTx, &rqptr->rqResponse.Duration); /* only for "meaningful" responses */ if (rqptr->BytesPerSecond && !rqptr->InternalRequest && !WEBSOCKET_REQUEST(rqptr) && rqptr->rqResponse.HttpStatus / 100 == 2 && rqptr->rqHeader.Method != HTTP_METHOD_OPTIONS && rqptr->rqHeader.Method != HTTP_METHOD_TRACE) { ADD_QUAD_QUAD (BytesRawRx, AccountingPtr->BytesPerSecondRawRx) ADD_QUAD_QUAD (BytesRawRx, AccountingPtr->BytesPerSecondRawTotal) ADD_QUAD_QUAD (BytesRawTx, AccountingPtr->BytesPerSecondRawTx) ADD_QUAD_QUAD (BytesRawTx, AccountingPtr->BytesPerSecondRawTotal) if (rqptr->BytesPerSecond > AccountingPtr->BytesPerSecondMax) { AccountingPtr->BytesPerSecondMax = rqptr->BytesPerSecond; PUT_QUAD_QUAD (rqptr->rqResponse.Duration, AccountingPtr->BytesPerSecondMaxDuration); PLUS_QUAD_QUAD (rqptr->BytesRawRx, rqptr->BytesRawTx, AccountingPtr->BytesPerSecondMaxBytes) } else if (!AccountingPtr->BytesPerSecondMin || rqptr->BytesPerSecond < AccountingPtr->BytesPerSecondMin) { AccountingPtr->BytesPerSecondMin = rqptr->BytesPerSecond; PUT_QUAD_QUAD (rqptr->rqResponse.Duration, AccountingPtr->BytesPerSecondMinDuration); PLUS_QUAD_QUAD (rqptr->BytesRawRx, rqptr->BytesRawTx, AccountingPtr->BytesPerSecondMinBytes) } AccountingPtr->BytesPerSecondAve = BytesPerSecond (&AccountingPtr->BytesPerSecondRawRx, &AccountingPtr->BytesPerSecondRawTx, &AccountingPtr->ResponseDuration); } /* update the accumulators related to the HTTP status code */ RequestHttpStatusCode (rqptr); /* update the global section used by HTTPDMON utility */ if (MonitorEnabled) RequestGblSecUpdate (rqptr); } if (InstanceNodeConfig > 1) { /* update the global mutex accounting (with whatever up to this point) */ for (idx = 1; idx <= INSTANCE_MUTEX_COUNT; idx++) { if (InstanceMutexCount[idx]) { HttpdGblSecPtr->MutexCount[idx] += InstanceMutexCount[idx]; InstanceMutexCount[idx] = 0; } if (InstanceMutexWaitCount[idx]) { HttpdGblSecPtr->MutexWaitCount[idx] += InstanceMutexWaitCount[idx]; InstanceMutexWaitCount[idx] = 0; } } } if (ErrorsNoticedCount) { /* update the global accounting here to avoid deadlock issues */ AccountingPtr->ErrorsNoticedCount += ErrorsNoticedCount; if (CompareVmsBinTimes (&ErrorsNoticedBinTime, &AccountingPtr->ErrorsNoticedBinTime) > 0) PUT_QUAD_QUAD (ErrorsNoticedBinTime, AccountingPtr->ErrorsNoticedBinTime) PUT_ZERO_QUAD (ErrorsNoticedBinTime); ErrorsNoticedCount = 0; } InstanceMutexUnLock (INSTANCE_MUTEX_HTTPD); /***************************/ /* unlocked global section */ /***************************/ if (QUAD_NOT_ZERO(rqptr->BytesRx)) { /* this was a real request, do some finalizing */ if (LoggingEnabled && rqptr->BytesRawRx[0] && (!rqptr->RedirectErrorStatusCode || (rqptr->RedirectErrorStatusCode && !rqptr->rqResponse.ErrorReportByRedirect))) Logging (rqptr, LOGGING_ENTRY); /* if a request history is being kept then provide the entry */ if (RequestHistoryMax) RequestHistory (rqptr); /* if it's not already been reported */ if (rqptr->rqPathSet.Alert && !(rqptr->rqPathSet.Alert & MAPURL_PATH_ALERT_DONE)) RequestAlert (rqptr); if (WATCHING(rqptr) && WATCH_CATEGORY(WATCH_REQUEST)) { if (rqptr->rqNet.ReadErrorCount) WatchThis (rqptr, FI_LI, WATCH_REQUEST, "RX errors !UL !&S", rqptr->rqNet.ReadErrorCount, rqptr->rqNet.ReadErrorStatus); if (rqptr->rqNet.WriteErrorCount) WatchThis (rqptr, FI_LI, WATCH_REQUEST, "TX errors !UL !&S", rqptr->rqNet.WriteErrorCount, rqptr->rqNet.WriteErrorStatus); WatchThis (rqptr, FI_LI, WATCH_REQUEST, "STATUS !3ZL (!AZ) rx:!@SQ tx:!@SQ bytes !AZ seconds !&L B/s!AZ", rqptr->rqResponse.HttpStatus, HttpStatusCodeText(rqptr->rqResponse.HttpStatus), &rqptr->BytesRawRx, &rqptr->BytesRawTx, DurationString (rqptr, &rqptr->rqResponse.Duration), rqptr->BytesPerSecond, rqptr->rqTmr.TerminatedCount ? " TIMED-OUT" : ""); } } RequestConnection (rqptr); } /*****************************************************************************/ /* Increment the approriate HTTP status code accumulator. Assumes that the appropriate global section is already locked. The accumulator for status codes is a 'somewhat sparse' array where each status code group, 100.., 200.., etc., is given 30 integers for codes 00..29 (heaps for most groups) resulting in 102 being index 32, 410 being index 130, 501 being index 151, etc. Convenient without being too profligate with memory. */ RequestHttpStatusCode (REQUEST_STRUCT *rqptr) { int idx, StatusCode, StatusGroup, StatusMod; /*********/ /* begin */ /*********/ if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD_REQUEST)) WatchThis (rqptr, FI_LI, WATCH_MOD_REQUEST, "RequestHttpStatusCode() !UL", rqptr->rqResponse.HttpStatus); #if WATCH_MOD /* small sanity check when module WATCHing is compiled-in */ if (sizeof(AccountingPtr->ResponseStatusCodeCount) != sizeof(unsigned long) * (6*30)) ErrorExitVmsStatus (SS$_BUGCHECK, ErrorSanityCheck, FI_LI); #endif /* WATCH_MOD */ StatusGroup = (StatusCode = rqptr->rqResponse.HttpStatus) / 100; if (StatusGroup < 0 || StatusGroup > 5) StatusGroup = 0; AccountingPtr->ResponseStatusCodeGroup[StatusGroup]++; if (StatusCode) { if ((StatusCode >= 100 && StatusCode <= 102) || (StatusCode >= 200 && StatusCode <= 207) || (StatusCode >= 300 && StatusCode <= 307) || (StatusCode >= 400 && StatusCode <= 417) || (StatusCode >= 422 && StatusCode <= 424) || (StatusCode >= 500 && StatusCode <= 505) || (StatusCode == 507)) { /* constrain to n00..n29 */ StatusMod = StatusCode % (StatusGroup * 10); /* an index from 0..179 */ idx = (StatusGroup * 30) + StatusMod; } else { /* HTTP status code set to something we don't know about */ idx = 0; } AccountingPtr->ResponseStatusCodeCount[idx]++; if (StatusCode == 403) AccountingPtr->RequestForbiddenCount++; } } /*****************************************************************************/ /* Return an integer that can be used as an index into the HTTP status code accumulator array. See explanation in RequestHttpStatusCode() above. */ int RequestHttpStatusIndex (int StatusCode) { int StatusGroup, StatusMod; /*********/ /* begin */ /*********/ StatusGroup = StatusCode / 100; if (StatusGroup < 0 || StatusGroup > 5) StatusGroup = 0; /* constrain to 0..599 */ if (StatusCode < 100 || StatusCode > 599) StatusCode = 0; /* constrain to n00..n29 */ if (!StatusCode || (StatusMod = (StatusCode % (StatusGroup * 10))) > 29) StatusCode = StatusMod = 0; /* generate an index from 0..179 */ return ((StatusGroup * 30) + StatusMod); } /*****************************************************************************/ /* For a persistent connection (request/response) this function calls the approriate setup OR disposes of any SSL connection, then closes the network socket and disposes of the per-request memory. This function can be recalled from SeseolaNetShutdown(). */ RequestConnection (REQUEST_STRUCT *rqptr) { /*********/ /* begin */ /*********/ if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD_REQUEST)) WatchThis (rqptr, FI_LI, WATCH_MOD_REQUEST, "RequestConnection() !&F !&B !&B", &RequestConnection, rqptr->PersistentRequest, rqptr->PersistentResponse); if (rqptr->rqResponse.RedactBufferPtr && rqptr->rqResponse.RedactBufferCount) { /* a redacted request is implicitly persistent */ rqptr->PersistentRequest = rqptr->PersistentResponse = true; } if (rqptr->PersistentRequest) { /* if disabled, server control requested, or reached the limit */ if (!Config.cfTimeout.Persistent) rqptr->PersistentRequest = false; else if (rqptr->rqNet.ConnectionCount >= PERSISTENT_CONNECTION_MAX) rqptr->PersistentRequest = false; else if (ControlExitRequested || ControlRestartRequested) rqptr->PersistentRequest = false; else /* Netscape Navigator 3.03 on VMS doesn't seem to like persistent SSL */ if (rqptr->rqHeader.VmsNavigatorGold && rqptr->ServicePtr->RequestScheme == SCHEME_HTTPS) rqptr->PersistentRequest = false; } if (!NetConnectSuspend && rqptr->PersistentRequest && rqptr->PersistentResponse && rqptr->rqClient.Channel) { /*************************/ /* persistent connection */ /*************************/ RequestPersistentConnection (rqptr); return; } /********************/ /* that's all folks */ /********************/ /* if Secure Sockets Layer request */ if (rqptr->rqNet.SesolaPtr) { /* this function will recall RequestConnection() when it's finished */ SesolaNetShutdown (rqptr->rqNet.SesolaPtr); return; } /****************************/ /* end of possible re-calls */ /****************************/ HttpdSupervisorList (rqptr, -1); NetCloseSocket (rqptr); InstanceMutexLock (INSTANCE_MUTEX_HTTPD); /* one less to worry about! */ if (AccountingPtr->ConnectCurrent) AccountingPtr->ConnectCurrent--; ActivityConnectCurrent = AccountingPtr->ConnectCurrent; if (WEBSOCKET_REQUEST(rqptr)) if (AccountingPtr->WebSocketCurrent) AccountingPtr->WebSocketCurrent--; WebSockCurrent = AccountingPtr->WebSocketCurrent; if (NetConnectCurrent) NetConnectCurrent--; InstanceMutexUnLock (INSTANCE_MUTEX_HTTPD); /* free the per-request memory heap */ VmFreeHeap (rqptr, FI_LI); /* remove from the request list */ ListRemove (&RequestList, rqptr); /* free memory allocated for the connection structure */ VmFreeRequest (rqptr, FI_LI); /* one-shot WATCH request is finished, no need to continue WATCHing */ if (WATCHING(rqptr) && Watch.Category == WATCH_ONE_SHOT_CAT) if (Watch.RequestPtr) { REQUEST_STRUCT *wrqptr; WatchEnd (wrqptr = Watch.RequestPtr); NetCloseSocket (wrqptr); } /* don't bother checking for exit or restart if connections still exist */ if (NetConnectProcessing) return; /* if the control module has requested server exit or restart */ if (ControlExitRequested) { fprintf (stdout, "%%HTTPD-I-CONTROL, delayed server exit\n"); ExitStatus = SS$_NORMAL; HttpdExit (&ExitStatus); /* cancel any startup messages provided for the monitor */ HttpdGblSecPtr->StatusMessage[0] = '\0'; /* record server event */ GraphActivityEvent (ACTIVITY_DELPRC); sys$delprc (0, 0); } HttpdCheckPriv (FI_LI); } /*****************************************************************************/ /* Assumes the connection is capable of being persistent. It checks if a potential pipelined request has been (previously) detected. If it has it notes the fact and buffers the pipelined network data. It then reset the request data structure ready for a receiving the nertwork data of a new request. If pipelined data was buffered this is restored to the network buffer (as if 'fresh' from the network connection) otherwise a buffer is read from the network. In either case this is passed to RequestGet(). */ RequestPersistentConnection (REQUEST_STRUCT *rqptr) { #define acptr AccountingPtr static char *PipelineBufferPtr; BOOL PipelineRequest, RedactRequest, WatchThisOne; int PathSetTimeoutPersistent, PipelineBufferCount, RedactBufferCount, RedactBufferSize; char *RedactBufferPtr; /*********/ /* begin */ /*********/ if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD_REQUEST)) WatchThis (rqptr, FI_LI, WATCH_MOD_REQUEST, "RequestPersistentConnection() !&F", &RequestPersistentConnection); rqptr->rqNet.ConnectionCount++; PathSetTimeoutPersistent = rqptr->rqPathSet.TimeoutPersistent; PipelineRequest = RedactRequest = false; if (rqptr->rqResponse.RedactBufferPtr && rqptr->rqResponse.RedactBufferCount) { /********************/ /* redacted request */ /********************/ RedactRequest = true; InstanceGblSecIncrLong (&AccountingPtr->RedactRequestCount); /* this is NOT heap memory and so will persist across requests */ RedactBufferPtr = rqptr->rqResponse.RedactBufferPtr; RedactBufferSize = rqptr->rqResponse.RedactBufferSize; RedactBufferCount = rqptr->rqResponse.RedactBufferCount; if (WATCHING(rqptr) && (WATCH_CATEGORY(WATCH_RESPONSE) || WATCH_CATEGORY(WATCH_RESPONSE_HEADER))) { char *cptr, *zptr; zptr = (cptr = RedactBufferPtr) + RedactBufferCount; while (cptr < zptr && !(*cptr == '\r' && SAME4(cptr,'\r\n\r\n')) && !(*cptr == '\n' && SAME2(cptr,'\n\n'))) cptr++; if (cptr < zptr && *cptr == '\r') cptr += 4; else if (cptr < zptr && *cptr == '\n') cptr += 2; WatchThis (rqptr, FI_LI, WATCH_RESPONSE, "REDACT !UL bytes (!UL header, !UL body)", RedactBufferCount, cptr-RedactBufferPtr, RedactBufferCount-(cptr-RedactBufferPtr)); if (WATCH_CATEGORY(WATCH_RESPONSE_HEADER)) WatchDataDump (RedactBufferPtr, cptr-RedactBufferPtr); } } else if (rqptr->rqNet.PipelineBufferPtr && rqptr->rqNet.PipelineBufferCount) { /*********************/ /* pipelined request */ /*********************/ /* probable HTTP/1.1 pipelining (either that or client error!) */ PipelineRequest = true; if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD_REQUEST)) WatchDataFormatted ("PIPELINE {!UL}!-!#AZ\n", rqptr->rqNet.PipelineBufferCount, rqptr->rqNet.PipelineBufferPtr); /* buffer the pipelined octets */ PipelineBufferCount = rqptr->rqNet.PipelineBufferCount; if (PipelineBufferCount > NetReadBufferSize) ErrorExitVmsStatus (SS$_BUGCHECK, ErrorSanityCheck, FI_LI); if (!PipelineBufferPtr) PipelineBufferPtr = VmGet (NetReadBufferSize); memcpy (PipelineBufferPtr, rqptr->rqNet.PipelineBufferPtr, PipelineBufferCount); /* better reset these! */ rqptr->rqNet.PipelineBufferPtr = NULL; rqptr->rqNet.PipelineBufferCount = 0; InstanceMutexLock (INSTANCE_MUTEX_HTTPD); /* there's always one that's been processed before this! */ if (!rqptr->rqNet.PipelineRequestCount) { rqptr->rqNet.PipelineRequestCount = 2; acptr->PipelineRequestCount += 2; } else { rqptr->rqNet.PipelineRequestCount++; acptr->PipelineRequestCount++; } if (rqptr->rqNet.PipelineRequestCount > acptr->PipelineRequestMax) acptr->PipelineRequestMax = rqptr->rqNet.PipelineRequestCount; InstanceMutexUnLock (INSTANCE_MUTEX_HTTPD); if (WATCHING(rqptr) && WATCH_CATEGORY(WATCH_CONNECT)) WatchThis (rqptr, FI_LI, WATCH_CONNECT, "PIPELINE !UL !AZ,!UL", rqptr->rqNet.PipelineRequestCount, rqptr->rqClient.Lookup.HostName, rqptr->rqClient.IpPort); } else { if (WATCHING(rqptr) && WATCH_CATEGORY(WATCH_CONNECT)) WatchThis (rqptr, FI_LI, WATCH_CONNECT, "PERSISTENT !UL !AZ,!UL", rqptr->rqNet.ConnectionCount, rqptr->rqClient.Lookup.HostName, rqptr->rqClient.IpPort); } if (!PipelineRequest) { /* not a pipelined request so resets counter */ rqptr->rqNet.PipelineRequestCount = 0; } if (!RedactRequest) { InstanceMutexLock (INSTANCE_MUTEX_HTTPD); /* persistent connection waiting for a (potential) subsequent request */ acptr->ConnectCurrentPersistent++; if (acptr->ConnectCurrentPersistent > acptr->ConnectPeakPersistent) acptr->ConnectPeakPersistent = acptr->ConnectCurrentPersistent; InstanceMutexUnLock (INSTANCE_MUTEX_HTTPD); } /****************/ /* prepare next */ /****************/ /* free the per-request memory heap */ VmFreeHeap (rqptr, FI_LI); /* These storage areas exist in the permanent request structure but the memory is from the request heap. They persist across redirects but not persistent connections (keep-alives). */ rqptr->NotePadPtr = rqptr->ProxyReverseLocationPtr = NULL; rqptr->NotePadLength = rqptr->RedactCount = rqptr->RedirectCount = rqptr->RedirectedXray = rqptr->WatchItem = 0; /* zero the portion of the request structure that is not persistent */ memset ((char*)&rqptr->ZeroedBegin, 0, (char*)&rqptr->ZeroedEnd - (char*)&rqptr->ZeroedBegin); /* Timestamp the persistent connection period for RequestReport(). Transaction will again be time-stamped if/when request received! */ sys$gettim (&rqptr->rqTime.Vms64bit); if (rqptr->ServicePtr->ErrorReportPath[0]) { rqptr->rqResponse.ErrorReportByRedirect = true; /* reset thread-permanent storage */ rqptr->RedirectErrorStatusCode = 0; rqptr->RedirectErrorAuthRealmDescrPtr = NULL; } /* initialize the timer for persistent connection */ HttpdTimerSet (rqptr, TIMER_PERSISTENT, PathSetTimeoutPersistent); rqptr->rqNet.ReadBufferSize = NetReadBufferSize; rqptr->rqNet.ReadBufferPtr = VmGetHeap (rqptr, NetReadBufferSize); if (PipelineRequest) { /*********************/ /* pipelined request */ /*********************/ /* restore the data into the read buffer */ memcpy (rqptr->rqNet.ReadBufferPtr, PipelineBufferPtr, PipelineBufferCount); /* fudge these as if it came from the network */ rqptr->rqNet.ReadIOsb.Status = SS$_NORMAL; rqptr->rqNet.ReadIOsb.Count = PipelineBufferCount; PUT_LONG_QUAD (PipelineBufferCount, rqptr->BytesRawRx); SysDclAst (RequestGet, rqptr); return; } if (RedactRequest) { /********************/ /* redacted request */ /********************/ rqptr->rqNet.RedactBufferPtr = RedactBufferPtr; /* size here becomes the actual count of data in the buffer */ rqptr->rqNet.RedactBufferSize = RedactBufferCount; /* count tracks the quantity of data read from the buffer */ rqptr->rqNet.RedactBufferCount = 0; } NetRead (rqptr, &RequestGet, rqptr->rqNet.ReadBufferPtr, rqptr->rqNet.ReadBufferSize); #undef acptr } /*****************************************************************************/ /* Provide a path alert notification. */ RequestAlert (REQUEST_STRUCT *rqptr) { int status, AlertItem, AlertValue; char Buffer [1024]; unsigned long *vecptr; unsigned long FaoVector [16]; /*********/ /* begin */ /*********/ if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD_REQUEST)) WatchThis (rqptr, FI_LI, WATCH_MOD_REQUEST, "RequestAlert() 0x!2XL", rqptr->rqPathSet.Alert); AlertValue = rqptr->rqPathSet.Alert; /* set this bit to indicated the alert has been signalled */ rqptr->rqPathSet.Alert |= MAPURL_PATH_ALERT_DONE; if (AlertValue >= 100 && AlertValue <= 599) { /* alert on a specific HTTP status code */ AlertItem = AlertValue % 100; if (AlertItem == 99) { /* comparing to a category (e.g. 4nn, 5nn) but not that category */ if (AlertValue / 100 != rqptr->rqResponse.HttpStatus / 100) return; } else { /* comparing to an individual status value */ if (rqptr->rqResponse.HttpStatus != AlertValue) return; } /* continue on to report the alert */ } InstanceGblSecIncrLong (&AccountingPtr->PathAlertCount); vecptr = &FaoVector; *vecptr++ = UserAtClient(rqptr); *vecptr++ = rqptr->rqHeader.MethodName; *vecptr++ = rqptr->rqHeader.RequestUriPtr; if (rqptr->rqResponse.HttpStatus) { *vecptr++ = " !UL"; *vecptr++ = rqptr->rqResponse.HttpStatus; } else *vecptr++ = ""; status = FaolToBuffer (Buffer, sizeof(Buffer), NULL, "!&@ !AZ !AZ!&@", &FaoVector); if (VMSnok (status)) ErrorNoticed (rqptr, status, NULL, FI_LI); FaoToStdout ("%HTTPD-W-ALERT, !20%D, !AZ\n", 0, Buffer); if (Config.cfOpcom.Messages & OPCOM_ADMIN) FaoToOpcom ("%HTTPD-W-ALERT, !AZ", Buffer); if (WATCHING(rqptr) && WATCH_CATEGORY(WATCH_REQUEST)) WatchThis (rqptr, FI_LI, WATCH_REQUEST, "ALERT !AZ", Buffer); } /*****************************************************************************/ /* Some browsers seem unhappy if a request is aborted too quickly (and it's socket closed) during the POST or PUT of a request body (reports connection errors). We're here because all the body was not read during request processing (probably because an error is to be reported). Read some more of the body (just storing it in the bit-bucket). Then after sufficient has been read (or end-of-content) dispose of the request. */ RequestDiscardBody (REQUEST_STRUCT *rqptr) { int status; char *cptr; /*********/ /* begin */ /*********/ if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD_REQUEST)) WatchThis (rqptr, FI_LI, WATCH_MOD_REQUEST, "RequestDiscardBody() !&F !&X !UL", &RequestDiscardBody, rqptr->rqBody.DataStatus, rqptr->rqBody.DiscardReadCount); /* ensure we just read raw and throw-away, no fancy processing thanks */ rqptr->rqBody.UnEncodeStream = false; if (rqptr->rqBody.AstFunction) { /* body processing underway, reset AST routine(s) to discard the rest */ rqptr->rqBody.AstFunction = &RequestDiscardBodyAst; rqptr->rqBody.ProcessFunction = NULL; BodyRead (rqptr); } else BodyReadBegin (rqptr, &RequestDiscardBodyAst, NULL); } /*****************************************************************************/ /* See RequestBodyDiscard(). */ RequestDiscardBodyAst (REQUEST_STRUCT *rqptr) { int status; /*********/ /* begin */ /*********/ if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD_REQUEST)) WatchThis (rqptr, FI_LI, WATCH_MOD_REQUEST, "RequestDiscardBodyAst() !&F !&X !UL", &RequestDiscardBodyAst, rqptr->rqBody.DataStatus, rqptr->rqBody.DiscardReadCount); if (VMSnok (rqptr->rqBody.DataStatus)) { /* error or enough read (ensure it looks like it anyway) */ rqptr->rqBody.DataStatus = SS$_ENDOFFILE; RequestEnd (rqptr); return; } BodyRead (rqptr); } /****************************************************************************/ /* The 'rqptr->rqResponse.LocationPtr' is non-NULL indicating redirection. This can be local, indicated by a leading '/', or it can be non-local, indicated by anything other than a leading '/', usually a full URL including scheme (e.g. "http:"). If the URL comprises something like "///some/path/or/other" then the request scheme ("http:", "https:") and "Host:" or service host:port is substituted in the appropriate position in the URL. If "//:port/path" (i.e. begins with "//:") the request scheme and host are substituted into the URL and it becomes effectively a redirect to a different port on the same host. If "//host.domain/path/" then just the request scheme is added. If "http:///path/" or "https:///path/" then the service host:port is added to the redirection URL (essentially this provides for a change of request scheme). If the 'rqptr->rqResponse.LocationPtr' has as it's last character a '?' and no query string itself, and if the request contains a query string then that is appended to the redirection URL when building the redirected request. Special case redirect; relies on being able to manipulate host record in the DNS or local name resolution database. If a "*.the.proxy.host" DNS (CNAME) record is resolved it allows any host name ending in ".the.proxy.host" to resolve to the corresponding IP address. Similarly (at least the Compaq TCP/IP Services) local host database allows an alias like "another.host.name.proxy.host.name" for the proxy host name. Both of these would allow a browser to access "another.host.name.proxy.host.name" with it resolved to the proxy service. The request "Host:" field would contain "another.host.name.proxy.host.name". This is specially searched for, detected and processed by this function. See description in PROXY.C for more detail on usage. If authorization is enabled then all redirected requests should be returned to the client in case authorization information needs to be applied to the new request. This may involve reformating a local redirection into a non-local one. If authentication is not enabled then check both local and non-local redirections to see if it can be handled locally. Return normal status to indicate local-redirection (and that the thread should NOT be disposed of), or an error status to indicate client-handled, non-local-redirection, or a legitimate error (and that the thread can go). */ int RequestRedirect (REQUEST_STRUCT *rqptr) { #define STRCAT(string) { \ for (cptr = string; *cptr && sptr < zptr; *sptr++ = *cptr++); \ } #define CHRCAT(ch) { \ if (sptr < zptr) *sptr++ = ch; \ } static char TextContentLength [32]; static $DESCRIPTOR (TextContentLengthDsc, TextContentLength); static $DESCRIPTOR (TextContentLengthFaoDsc, "Content-Length: !UL\r\n\0"); BOOL IncludeQueryString, ConcatQueryString, LocalRedirection; int idx, status, AlphaCount, BodyCount, ExciseCount, HostDotCount, Length, PortNumber, SchemeLength, ServiceDotCount, StringDotCount, TextLength; char *cptr, *eptr, *sptr, *zptr, *dptr, *BodyPtr, *HttpMethodNamePtr, *NetReadBufferPtr, *PathPtr, *TextPtr, *UrlPtr, *UrlEndPtr; char HttpMethodName [32], HostField [128+16], /* allow for agents with silly amounts of request header */ RedirectionUrl [16384]; /*********/ /* begin */ /*********/ if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD_REQUEST)) WatchThis (rqptr, FI_LI, WATCH_MOD_REQUEST, "RequestRedirect() !&Z", rqptr->rqResponse.LocationPtr); if (rqptr->rqPathSet.Alert) RequestAlert (rqptr); if (WATCHING(rqptr) && WATCH_CATEGORY(WATCH_REQUEST)) WatchThis (rqptr, FI_LI, WATCH_REQUEST, "REDIRECT !AZ", rqptr->rqResponse.LocationPtr); if (rqptr->RedirectCount++ > REQUEST_REDIRECTION_MAX) { rqptr->rqResponse.LocationPtr = NULL; rqptr->rqResponse.HttpStatus = 500; ErrorGeneral (rqptr, MsgFor(rqptr,MSG_REQUEST_REDIRECTION), FI_LI); return (STS$K_ERROR); } rqptr->rqResponse.HttpStatus = 302; /* A leading space is normally not possible. This indicates a specific HTTP method must be used. Format is: "METHOD". */ if (rqptr->rqResponse.LocationPtr[0] == ' ') { HttpMethodNamePtr = sptr = HttpMethodName; for (cptr = rqptr->rqResponse.LocationPtr + 1; *cptr && *cptr != ' '; *sptr++ = *cptr++); *sptr = '\0'; rqptr->rqResponse.LocationPtr = cptr + 1; } else HttpMethodNamePtr = rqptr->rqHeader.MethodName; LocalRedirection = true; for (cptr = rqptr->rqResponse.LocationPtr; *cptr && *cptr != '?'; cptr++) { if (*cptr != ':' && *cptr != '/') continue; if (*cptr == ':') LocalRedirection = false; while (*cptr && *cptr != '?') cptr++; break; } if (ConcatQueryString = ((*cptr == '?') && !SAME2(cptr,'?\0'))) while (*cptr && !SAME2(cptr,'?\0')) cptr++; if (IncludeQueryString = SAME2(cptr,'?\0')) { if (!rqptr->rqHeader.QueryStringLength || !rqptr->rqHeader.QueryStringPtr[0]) { IncludeQueryString = ConcatQueryString = false; *cptr = '\0'; } } if (ConcatQueryString) *cptr = '\0'; if (LocalRedirection) TextLength = MapUrl_VirtualPath (rqptr->rqHeader.PathInfoPtr, rqptr->rqResponse.LocationPtr, RedirectionUrl, sizeof(RedirectionUrl)); else { TextLength = strzcpy (RedirectionUrl, rqptr->rqResponse.LocationPtr, sizeof(RedirectionUrl)); if (TextLength > sizeof(RedirectionUrl)-1) TextLength = -1; } if (TextLength < 0) { ErrorNoticed (rqptr, SS$_RESULTOVF, NULL, FI_LI); ErrorGeneralOverflow (rqptr, FI_LI); return (STS$K_ERROR); } /* now ignore old value of location redirection pointer */ rqptr->rqResponse.LocationPtr = NULL; if (RedirectionUrl[0] == '/' && RedirectionUrl[1] != '/') LocalRedirection = true; else LocalRedirection = false; if (LocalRedirection) { /*********************/ /* local redirection */ /*********************/ InstanceGblSecIncrLong (&AccountingPtr->RedirectLocalCount); /* create space for a new request header */ if (rqptr->rqHeader.ContentLength) { /* allow for any content received along with the header */ BodyCount = rqptr->BytesRx[0] - rqptr->rqHeader.RequestHeaderLength; if (BodyCount) { BodyPtr = VmGetHeap (rqptr, BodyCount); memcpy (BodyPtr, rqptr->rqHeader.RequestHeaderPtr + rqptr->rqHeader.RequestHeaderLength, BodyCount); } rqptr->rqNet.ReadBufferPtr = VmGetHeap (rqptr, NetReadBufferSize + BodyCount); /* actual and indicated size may be different in this circumstance */ rqptr->rqNet.ReadBufferSize = NetReadBufferSize; } else BodyCount = 0; HostField[0] = '\0'; if (RedirectionUrl[0] == '/' && (strsame (RedirectionUrl, "/http://", SchemeLength = 8) || strsame (RedirectionUrl, "/https://", SchemeLength = 9) || strsame (RedirectionUrl, "/ftp://", SchemeLength = 7))) { /************/ /* to proxy */ /************/ if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD_REQUEST)) WatchThis (rqptr, FI_LI, WATCH_MOD_REQUEST, "!&Z", RedirectionUrl); if (!rqptr->rqHeader.HostPtr) { rqptr->rqResponse.HttpStatus = 400; ErrorGeneral (rqptr, MsgFor(rqptr,MSG_PROXY_GATEWAY), FI_LI); return (STS$K_ERROR); } /* If the path has been SET 'proxy=reverse=location=' then this and the original host information is conveyed to the redirected proxy request using redirect-persistent storage made available specifically for this purpose. */ if (rqptr->rqPathSet.ProxyReverseLocationPtr) { Length = strlen(rqptr->ServicePtr->RequestSchemeNamePtr) + strlen(rqptr->rqHeader.HostPtr) + strlen(rqptr->rqPathSet.ProxyReverseLocationPtr) + 3; rqptr->ProxyReverseLocationPtr = sptr = VmGetHeap (rqptr, Length); for (cptr = rqptr->ServicePtr->RequestSchemeNamePtr; *cptr; *sptr++ = *cptr++); *sptr++ = '/'; *sptr++ = '/'; for (cptr = rqptr->rqHeader.HostPtr; *cptr; *sptr++ = *cptr++); for (cptr = rqptr->rqPathSet.ProxyReverseLocationPtr; *cptr; *sptr++ = *cptr++); *sptr = '\0'; } /* Check for a DNS 'wildcard' leading the service name. That is the "Host:" field contains more domain name components than the proxy service domain name. Do this by counting the number of periods in the strings. Acckkk Alex! Never been quite convinced of the need for this ;^) */ AlphaCount = HostDotCount = ServiceDotCount = 0; for (cptr = rqptr->rqHeader.HostPtr; *cptr; cptr++) { if (isalpha(*cptr)) AlphaCount++; if (*cptr != '.') continue; HostDotCount++; } if (AlphaCount) { /* only bother doing this if it's not dotted-decimal */ for (cptr = rqptr->ServicePtr->ServerHostPort; *cptr; cptr++) { if (*cptr != '.') continue; ServiceDotCount++; } } if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD_REQUEST)) WatchDataFormatted ("!UL !UL !&Z !UL !&Z\n", AlphaCount, HostDotCount, rqptr->rqHeader.HostPtr, ServiceDotCount, rqptr->ServicePtr->ServerHostPort); if (AlphaCount && HostDotCount > ServiceDotCount) { /**********************/ /* wildcard DNS proxy */ /**********************/ /* At this stage the "Host:" field will look something like 'the.host.name.the.proxy.service:port' and the redirection URL '/https://the.proxy.service/http://the.host.name.the.proxy.service:port/path' We need to turn this into a proxy redirection looking like 'https://the.host.name/path'. */ sptr = RedirectionUrl + SchemeLength; if (*sptr && *sptr != '/') { /* count the number of periods 'the.proxy.service/' component */ StringDotCount = 0; for (cptr = sptr; *cptr && *cptr != '/'; cptr++) { if (*cptr != '.') continue; StringDotCount++; } /* possible but not desirable */ if (StringDotCount >= HostDotCount) StringDotCount = HostDotCount; /* find the start of the redirection URL path */ if (*cptr) cptr++; while (*cptr && *cptr != '/') cptr++; PathPtr = cptr; if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD_REQUEST)) WatchDataFormatted ("!UL !&Z !UL !&Z\n", HostDotCount, rqptr->rqHeader.HostPtr, StringDotCount, PathPtr); } else { StringDotCount = ServiceDotCount; PathPtr = RedirectionUrl + SchemeLength; } /* recreate the "Host:" field */ zptr = (sptr = HostField) + sizeof(HostField)-1; cptr = rqptr->rqHeader.HostPtr; while (HostDotCount-- > StringDotCount) { if (*cptr == '.') { if (sptr < zptr) *sptr++ = *cptr; cptr++; } while (*cptr && *cptr != '.') { if (sptr < zptr) *sptr++ = *cptr; cptr++; } } *sptr-- = '\0'; /* check if the last 'name' component was actually a 'port' */ while (sptr > HostField && isdigit(*sptr)) sptr--; /* if so change the period to a colon */ if (sptr > HostField && *sptr == '.') *sptr = ':'; if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD_REQUEST)) WatchDataFormatted ("!&Z\n", HostField); /* begin building the redirection buffer */ /* NO MORE USING cptr, eptr, sptr, zptr! */ sptr = rqptr->rqNet.ReadBufferPtr; zptr = sptr + rqptr->rqNet.ReadBufferSize; STRCAT (HttpMethodNamePtr) if (SchemeLength == 8) STRCAT (" http://") else if (SchemeLength == 9) STRCAT (" https://") else STRCAT (" ftp://") STRCAT (HostField) STRCAT (PathPtr) } else { /**********************/ /* one-shot DNS proxy */ /**********************/ /* recreate the "Host:" field */ zptr = (sptr = HostField) + sizeof(HostField)-1; cptr = RedirectionUrl + SchemeLength; while (*cptr && *cptr != '/' && sptr < zptr) *sptr++ = *cptr++; *sptr = '\0'; if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD_REQUEST)) WatchDataFormatted ("!&Z\n", HostField); /* begin building the redirection buffer */ /* NO MORE USING cptr, eptr, sptr, zptr! */ sptr = rqptr->rqNet.ReadBufferPtr; zptr = sptr + rqptr->rqNet.ReadBufferSize; STRCAT (HttpMethodNamePtr) CHRCAT (' ') STRCAT (RedirectionUrl+1) } } else { /****************/ /* not to proxy */ /****************/ /* begin building the redirection buffer */ /* NO MORE USING cptr, eptr, sptr, zptr! */ sptr = rqptr->rqNet.ReadBufferPtr; zptr = sptr + rqptr->rqNet.ReadBufferSize; STRCAT (HttpMethodNamePtr) CHRCAT (' ') STRCAT (RedirectionUrl) } if (IncludeQueryString) { if (ConcatQueryString) CHRCAT ('&') STRCAT (rqptr->rqHeader.QueryStringPtr) } if (rqptr->rqResponse.HttpVersion == HTTP_VERSION_1_1) STRCAT (" HTTP/1.1\r\n") else STRCAT (" HTTP/1.0\r\n") /* add (with some massage) the original request's header fields */ for (idx = 0; idx < rqptr->rqHeader.RequestFieldsCount; idx++) { cptr = rqptr->rqHeader.RequestFieldsPtr[idx]; if (TOUP(*cptr) == 'D' && strsame (cptr, "Destination:", 12)) { if (HostField[0]) { /* redirecting to proxy */ dptr = cptr + 12; while (*dptr && *dptr != '/') dptr++; if (*dptr && *(dptr+1) == '/') { dptr += 2; while (*dptr && *dptr != '/') dptr++; } STRCAT ("Destination: ") if (SchemeLength == 8) STRCAT ("http://") else STRCAT ("https://") STRCAT (HostField) STRCAT (dptr) STRCAT ("\r\n") continue; } } else if (TOUP(*cptr) == 'H' && strsame (cptr, "Host:", 5)) { if (HostField[0]) { /* redirecting to proxy */ STRCAT ("Host: ") STRCAT (HostField) STRCAT ("\r\n") continue; } } /** TODO MGD 30-AUG-2005 **/ #if 0 else if (TOUP(*cptr) == 'R' && strsame (cptr, "Referer:", 8)) { /* too complex massaging "Referer:" when redirecting to proxy */ if (HostField[0]) continue; } #endif /* otherwise, include this field */ STRCAT (cptr) STRCAT ("\r\n") } if (rqptr->rqHeader.HttpVersion == HTTP_VERSION_1_0) { if (rqptr->PersistentRequest) STRCAT ("Connection: keep-alive\r\n") } /* if generated by a CGI "Location:", supply any remaining header */ if (rqptr->rqCgi.HeaderLength) STRCAT (rqptr->rqCgi.HeaderPtr) /* terminating empty line */ STRCAT ("\r\n") if (sptr >= zptr) { ErrorNoticed (rqptr, SS$_RESULTOVF, NULL, FI_LI); ErrorGeneralOverflow (rqptr, FI_LI); return (STS$K_ERROR); } *sptr = '\0'; if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD_REQUEST)) WatchDataFormatted ("!&Z", rqptr->rqNet.ReadBufferPtr); /* append any body supplied with the request header */ if (BodyCount) memcpy (sptr, BodyPtr, BodyCount); /* buffer anything that is necessary to retain after memset() */ NetReadBufferPtr = rqptr->rqNet.ReadBufferPtr; /* zero the portion of the request structure that is not persistent */ memset ((char*)&rqptr->ZeroedBegin, 0, (char*)&rqptr->ZeroedEnd - (char*)&rqptr->ZeroedBegin); /* re-timestamp the transaction */ sys$gettim (&rqptr->rqTime.Vms64bit); sys$numtim (&rqptr->rqTime.VmsVector, &rqptr->rqTime.Vms64bit); HttpGmTimeString (rqptr->rqTime.GmDateTime, &rqptr->rqTime.Vms64bit); /* initialize the timer for input */ HttpdTimerSet (rqptr, TIMER_INPUT, 0); /* if available then set any initial report to be via the script */ if (rqptr->ServicePtr->ErrorReportPath[0]) rqptr->rqResponse.ErrorReportByRedirect = true; /* restore buffered stuff */ rqptr->rqNet.ReadBufferPtr = NetReadBufferPtr; /* (this one is "restored" from global storage!! */ rqptr->rqNet.ReadBufferSize = NetReadBufferSize; /* point the request header at the newly-created contents */ rqptr->rqHeader.RequestHeaderPtr = rqptr->rqNet.ReadBufferPtr; rqptr->rqHeader.RequestHeaderLength = sptr - rqptr->rqNet.ReadBufferPtr; /* fudge the bytes received (for log and monitor purposes) */ PUT_LONG_QUAD (rqptr->rqHeader.RequestHeaderLength + BodyCount, rqptr->BytesRx); PUT_LONG_QUAD (rqptr->rqHeader.RequestHeaderLength + BodyCount, rqptr->BytesRawRx); /* normal status indicates its a local redirection */ return (SS$_NORMAL); } else { /*************************/ /* non-local redirection */ /*************************/ InstanceGblSecIncrLong (&AccountingPtr->RedirectRemoteCount); /* begin building the redirection buffer */ /* NO MORE USING cptr, eptr, sptr, zptr! */ TextPtr = sptr = VmGetHeap (rqptr, OutputBufferSize); zptr = sptr + OutputBufferSize; /* begin building the textual (body) redirection information */ STRCAT (HttpStatusCodeText(302)) STRCAT (" ServicePtr->RequestSchemeNamePtr) STRCAT ("//") if (rqptr->rqHeader.HostPtr && rqptr->rqHeader.HostPtr[0]) STRCAT (rqptr->rqHeader.HostPtr) else STRCAT (rqptr->ServicePtr->ServerHostPort) STRCAT (RedirectionUrl+2) } else if (MATCH3 (RedirectionUrl, "//:")) { /* request scheme and host needs to be supplied locally */ STRCAT (rqptr->ServicePtr->RequestSchemeNamePtr) STRCAT ("//") if (rqptr->rqHeader.HostPtr && rqptr->rqHeader.HostPtr[0]) cptr = rqptr->rqHeader.HostPtr; else cptr = rqptr->ServicePtr->ServerHostPort; while (*cptr && *cptr != ':' && sptr < zptr) *sptr++ = *cptr++; STRCAT (RedirectionUrl+2) } else if (MATCH2 (RedirectionUrl, "//")) { /* request scheme needs to be supplied locally */ STRCAT (rqptr->ServicePtr->RequestSchemeNamePtr) STRCAT (RedirectionUrl) } else if (MATCH8 (RedirectionUrl, "http:///")) { /* host needs to be supplied locally */ STRCAT ("http://") if (rqptr->rqHeader.HostPtr && rqptr->rqHeader.HostPtr[0]) STRCAT (rqptr->rqHeader.HostPtr) else STRCAT (rqptr->ServicePtr->ServerHostPort); STRCAT (RedirectionUrl+7) } else if (MATCH0 (RedirectionUrl, "https:///", 9)) { /* host needs to be supplied locally */ STRCAT ("https://") if (rqptr->rqHeader.HostPtr && rqptr->rqHeader.HostPtr[0]) STRCAT (rqptr->rqHeader.HostPtr) else STRCAT (rqptr->ServicePtr->ServerHostPort) STRCAT (RedirectionUrl+8) } else { /* redirection contains full URL */ STRCAT (RedirectionUrl) } if (IncludeQueryString) { if (ConcatQueryString) CHRCAT ('&') STRCAT (rqptr->rqHeader.QueryStringPtr); } UrlEndPtr = sptr; /* note the end of the redirection URL */ STRCAT ("\">") for (cptr = UrlPtr; cptr < UrlEndPtr && sptr < zptr; *sptr++ = *cptr++); STRCAT ("\n") TextLength = sptr - TextPtr; if (sptr < zptr) *sptr++ = '\0'; sys$fao (&TextContentLengthFaoDsc, 0, &TextContentLengthDsc, TextLength); rqptr->rqResponse.HeaderPtr = sptr; if (rqptr->rqResponse.HttpVersion == HTTP_VERSION_1_1) STRCAT ("HTTP/1.1") else STRCAT ("HTTP/1.0") STRCAT (" 302 "); STRCAT (HttpStatusCodeText(302)); STRCAT ("\r\nLocation: "); for (cptr = UrlPtr; cptr < UrlEndPtr && sptr < zptr; *sptr++ = *cptr++); STRCAT ("\r\nServer: ") STRCAT (SoftwareID) STRCAT ("\r\nDate: ") STRCAT (rqptr->rqTime.GmDateTime); STRCAT ("\r\nContent-Type: text/html\r\n") STRCAT (TextContentLength) 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 generated by a CGI "Location:", supply any remaining header */ if (rqptr->rqCgi.HeaderLength) STRCAT (rqptr->rqCgi.HeaderPtr) if (rqptr->rqResponse.HttpVersion == HTTP_VERSION_1_1) { if (rqptr->PersistentRequest) rqptr->PersistentResponse = true; else STRCAT ("Connection: close\r\n") } else { if (rqptr->PersistentRequest) { rqptr->PersistentResponse = true; STRCAT ("Connection: keep-alive\r\nKeep-Alive:\r\n") } } /* terminating empty line */ STRCAT ("\r\n") /* end of the response header */ rqptr->rqResponse.HeaderSent = false; rqptr->rqResponse.HeaderLength = sptr - rqptr->rqResponse.HeaderPtr; if (sptr >= zptr) { ErrorNoticed (rqptr, SS$_RESULTOVF, NULL, FI_LI); ErrorGeneralOverflow (rqptr, FI_LI); return (STS$K_ERROR); } *sptr = '\0'; if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD_REQUEST)) WatchDataFormatted ("!&Z!&Z", rqptr->rqResponse.HeaderPtr, TextPtr); /* return text, and of course response header, to client */ NetWrite (rqptr, &RequestEnd, TextPtr, TextLength); /* indicate it's non-local redirection */ return (SS$_NONLOCAL); } #undef STRCAT #undef CHRCAT } /*****************************************************************************/ /* Process (authorization or standard) script 'redact' callout. */ RequestRedact ( REQUEST_STRUCT *rqptr, char *OutputPtr, int OutputCount, BOOL ProvideResponse ) { static char RspBadParam [] = "400 Bad parameter", RspSuccess [] = "200 Success", RspTooLarge [] = "413 Entity too large"; int MaxKbytes; char *cptr; /*********/ /* begin */ /*********/ if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD_REQUEST)) WatchThis (rqptr, FI_LI, WATCH_MOD_REQUEST, "RequestRedact() !UL", OutputCount); if (!(MaxKbytes = rqptr->rqPathSet.PutMaxKbytes)) MaxKbytes = Config.cfMisc.PutMaxKbytes; if (strsame (OutputPtr, "REDACT:", 7)) { /***********/ /* REDACT: */ /***********/ /* restart a request from a rebuilt header (and body) */ OutputPtr += 7; OutputCount -= 7; if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD_REQUEST)) WatchThis (rqptr, FI_LI, WATCH_MOD_REQUEST, "!UL+!UL=!UL !UL 0x!XL", rqptr->rqResponse.RedactBufferCount, OutputCount, rqptr->rqResponse.RedactBufferCount+OutputCount, rqptr->rqResponse.RedactBufferSize, rqptr->rqResponse.RedactBufferPtr); if (rqptr->rqResponse.RedactBufferCount + OutputCount > rqptr->rqResponse.RedactBufferSize) { /* expand the buffer size in chunks the size of a DCL I/O */ rqptr->rqResponse.RedactBufferSize += DclSysOutputSize; if ((rqptr->rqResponse.RedactBufferSize >> 10) > MaxKbytes) { /* limit total to the maximum request body size anyway */ if (ProvideResponse) DclCalloutQio (rqptr, RspTooLarge, sizeof(RspTooLarge)-1); return; } if (rqptr->rqResponse.RedactBufferPtr) { /* note, this is NOT heap memory */ rqptr->rqResponse.RedactBufferPtr = VmRealloc (rqptr->rqResponse.RedactBufferPtr, rqptr->rqResponse.RedactBufferSize, FI_LI); } else { /* note, this is NOT heap memory */ rqptr->rqResponse.RedactBufferPtr = VmGet (rqptr->rqResponse.RedactBufferSize); rqptr->RedactCount++; } } if (OutputCount) { memcpy (rqptr->rqResponse.RedactBufferPtr + rqptr->rqResponse.RedactBufferCount, OutputPtr, OutputCount); rqptr->rqResponse.RedactBufferCount += OutputCount; } if (ProvideResponse) DclCalloutQio (rqptr, RspSuccess, sizeof(RspSuccess)-1); return; } if (strsame (OutputPtr, "REDACT-SIZE:", 12)) { /****************/ /* REDACT-SIZE: */ /****************/ for (cptr = OutputPtr+12; *cptr && isspace(*cptr); cptr++); if (isdigit(*cptr) && !rqptr->rqResponse.RedactBufferSize) { rqptr->rqResponse.RedactBufferSize = atoi(cptr); if (*cptr == '0' && !rqptr->rqResponse.RedactBufferSize) { /* a redact size of zero resets any in-progress redact */ if (rqptr->rqResponse.RedactBufferPtr) { VmFreeFromHeap (rqptr, rqptr->rqResponse.RedactBufferPtr, FI_LI); rqptr->rqResponse.RedactBufferPtr = NULL; rqptr->rqResponse.RedactBufferCount = 0; if (rqptr->RedactCount) rqptr->RedactCount--; } if (ProvideResponse) DclCalloutQio (rqptr, RspSuccess, sizeof(RspSuccess)-1); return; } else if (rqptr->rqResponse.RedactBufferPtr) { if (ProvideResponse) DclCalloutQio (rqptr, RspBadParam, sizeof(RspBadParam)-1); return; } else if ((rqptr->rqResponse.RedactBufferSize >> 10) <= MaxKbytes) { /* limit total to the maximum request body size anyway */ rqptr->rqResponse.RedactBufferSize = ((rqptr->rqResponse.RedactBufferSize / 512) + 1) * 512; rqptr->rqResponse.RedactBufferPtr = VmGet (rqptr->rqResponse.RedactBufferSize); rqptr->RedactCount++; if (ProvideResponse) DclCalloutQio (rqptr, RspSuccess, sizeof(RspSuccess)-1); return; } if (ProvideResponse) DclCalloutQio (rqptr, RspTooLarge, sizeof(RspTooLarge)-1); return; } if (ProvideResponse) DclCalloutQio (rqptr, RspBadParam, sizeof(RspBadParam)-1); return; } ErrorNoticed (rqptr, SS$_BUGCHECK, NULL, FI_LI); } /*****************************************************************************/ /* Process first packet read from the client. In most cases this will contain the complete HTTP header and it can be processed without building up a buffered header. If it does not contain the full header allocate heap memory to contain the current packet and queue subsequent read(s), expanding the request header heap memory, until all is available (or it becomes completely rediculous!). */ RequestGet (REQUEST_STRUCT *rqptr) { #define STRCAT(string) { \ for (cptr = string; *cptr && sptr < zptr; *sptr++ = *cptr++); \ } #define CONTROL_D 0x04 #define CONTROL_Z 0x1a int cnlcnt, cnt, status, SSLv2len; char *cptr, *sptr, *zptr; /*********/ /* begin */ /*********/ if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD_REQUEST)) WatchThis (rqptr, FI_LI, WATCH_MOD_REQUEST, "RequestGet() !&F !&S !UL !UL !UL", &RequestGet, rqptr->rqNet.ReadIOsb.Status, rqptr->rqNet.ReadIOsb.Count, rqptr->rqNet.ReadBufferSize, rqptr->rqHeader.RequestHeaderLength); if (rqptr->rqNet.ConnectionCount) { /*************************/ /* persistent connection */ /*************************/ /* re-timestamp the persistent connection */ sys$gettim (&rqptr->rqTime.Vms64bit); sys$numtim (&rqptr->rqTime.VmsVector, &rqptr->rqTime.Vms64bit); HttpGmTimeString (rqptr->rqTime.GmDateTime, &rqptr->rqTime.Vms64bit); /* redo what is done in NetAcceptProcess() */ if (Watch.Category && Watch.Category != WATCH_ONE_SHOT_CAT) WatchFilterClientService (rqptr); } if (VMSnok (rqptr->rqNet.ReadIOsb.Status)) { RequestEnd (rqptr); return; } if (QUAD_ZERO (rqptr->BytesRx) && rqptr->rqNet.ConnectionCount) { /* request is underway, reinitialize for input (longer period) */ HttpdTimerSet (rqptr, TIMER_INPUT, 0); } if (!rqptr->rqHeader.RequestHeaderPtr && !rqptr->rqNet.RedactBufferPtr) { /*****************/ /* request begin */ /*****************/ if (NetConnectProcessing >= NetConcurrentProcessMax) { /************/ /* too busy */ /************/ if (WATCHING(rqptr) && WATCH_CATEGORY(WATCH_CONNECT)) WatchThis (rqptr, FI_LI, WATCH_CONNECT, "PROCESS !UL too-busy !AZ,!UL on !AZ//!AZ,!AZ", NetConcurrentProcessMax, rqptr->rqClient.Lookup.HostName, rqptr->rqClient.IpPort, rqptr->ServicePtr->RequestSchemeNamePtr, rqptr->ServicePtr->ServerIpAddressString, rqptr->ServicePtr->ServerPortString); InstanceGblSecIncrLong (&AccountingPtr->ConnectProcessingTooBusyCount); InstanceGblSecIncrLong (&AccountingPtr->ResponseStatusCodeGroup[5]); rqptr->rqResponse.HttpStatus = 503; ErrorGeneral (rqptr, MsgFor(rqptr,MSG_GENERAL_TOO_BUSY), FI_LI); ErrorSendToClient (rqptr); return; } InstanceMutexLock (INSTANCE_MUTEX_HTTPD); if (rqptr->rqNet.ConnectionCount && AccountingPtr->ConnectCurrentPersistent) AccountingPtr->ConnectCurrentPersistent--; AccountingPtr->ConnectProcessing++; ActivityConnectProcessing = AccountingPtr->ConnectProcessing; NetConnectProcessing++; if (AccountingPtr->ConnectProcessing > AccountingPtr->ConnectProcessingPeak) AccountingPtr->ConnectProcessingPeak = AccountingPtr->ConnectProcessing; InstanceMutexUnLock (INSTANCE_MUTEX_HTTPD); /* these need to be built in RequestGet() due to SesolaNetAccept() */ if (rqptr->ServicePtr->ProxyTunnel == PROXY_TUNNEL_RAW) { /* fake a CONNECT request */ zptr = (sptr = rqptr->rqNet.ReadBufferPtr) + rqptr->rqNet.ReadBufferSize; STRCAT ("CONNECT "); STRCAT (rqptr->ServicePtr->ServerHostName); STRCAT (":0 HTTP/1.1\r\nUser-Agent: "); STRCAT (SoftwareID); STRCAT ("\r\nHost: "); STRCAT (rqptr->ServicePtr->ServerHostPort); STRCAT ("\r\nUpgrade: WASD-tunnel-raw\r\n\r\n"); /* as if it came from the network */ rqptr->rqNet.ReadIOsb.Count = sptr - rqptr->rqNet.ReadBufferPtr; } else if (rqptr->ServicePtr->ProxyTunnel == PROXY_TUNNEL_FIREWALL) { /* fake the start of a connect request */ memcpy (rqptr->rqNet.ReadBufferPtr, "CONNECT ", 8); /* as if it came from the network */ rqptr->rqNet.ReadIOsb.Count = 8; } } else if (rqptr->ServicePtr->ProxyTunnel == PROXY_TUNNEL_FIREWALL) { /* look for the end-of-line */ sptr = rqptr->rqNet.ReadBufferPtr + 8; while (*sptr && ISLWS(*sptr)) sptr++; cptr = sptr; while (!ISLWS(*sptr) && NOTEOL(*sptr) && *sptr != CONTROL_Z && *sptr != CONTROL_D) sptr++; if (*sptr) { if (cptr == sptr) { RequestEnd (rqptr); return; } /* end-of-line (hostname[:port]), complete the pseudo-header */ zptr = rqptr->rqNet.ReadBufferPtr + rqptr->rqNet.ReadBufferSize; STRCAT (" HTTP/1.1\r\nUser-Agent: "); STRCAT (SoftwareID); STRCAT ("\r\nHost: "); STRCAT (rqptr->ServicePtr->ServerHostPort); STRCAT ("\r\n\r\n"); /* as if it came from the network */ rqptr->rqNet.ReadIOsb.Count = sptr - rqptr->rqNet.ReadBufferPtr; } } cnt = rqptr->rqNet.ReadIOsb.Count; ADD_LONG_QUAD (cnt, rqptr->BytesRx) if (cnt >= MAX_REQUEST_HEADER) { /* not likely to be a legitimate HTTP request! */ rqptr->rqResponse.HttpStatus = 414; RequestEnd (rqptr); return; } if (!rqptr->rqHeader.RequestHeaderPtr) { rqptr->rqHeader.RequestHeaderPtr = rqptr->rqNet.ReadBufferPtr; rqptr->rqHeader.RequestHeaderLength = rqptr->rqHeader.RequestLineLength = 0; } rqptr->rqHeader.RequestHeaderPtr[cnt] = '\0'; rqptr->rqHeader.RequestHeaderLength += cnt; if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD_REQUEST)) WatchDataFormatted ("!&Z", rqptr->rqHeader.RequestHeaderPtr); if (!rqptr->rqHeader.HttpVersion) { /**************************/ /* parse the request line */ /**************************/ /* avoid Dijkstra's wrath */ for (;;) { cptr = rqptr->rqNet.ReadBufferPtr; if (cnt > 64) { /* if it looks suspiciously like an SSLv3 or SSLv2 handshake */ SSLv2len = cptr[0] & 0x80 ? ((cptr[0] & 0x7f) << 8) | cptr[1] : ((cptr[0] & 0x3f) << 8) | cptr[1]; if (SAME2(cptr,0x0316) || SSLv2len == cnt-2) { InstanceGblSecIncrLong (&AccountingPtr->RequestErrorCount); rqptr->rqResponse.HttpStatus = 400; ErrorGeneral (rqptr, MsgFor(rqptr,MSG_GENERAL_NOT_SSL), FI_LI); RequestEnd (rqptr); return; } } /* check for buggy at the end of last request body */ if (cnt >= 2 && SAME2(cptr,'\r\n')) { if (WATCHING(rqptr) && WATCH_CATEGORY(WATCH_REQUEST)) { WatchThis (rqptr, FI_LI, WATCH_REQUEST, "RFC 2616 section 4.1 bug?"); WatchDataDump (rqptr->rqNet.ReadBufferPtr, cnt); } /* if next request is already in the buffer */ if (cnt > 2) { /* skip it and adjust counts */ rqptr->rqNet.ReadBufferPtr += 2; cnt -= 2; rqptr->BytesRx[0] -= 2; rqptr->rqHeader.RequestHeaderLength -= 2; continue; } RequestEnd (rqptr); return; } while (*cptr && *cptr != '\n') cptr++; if (*cptr == '\n') { /* have a full line of request (or at least one full newline!) */ if (!RequestLineParse (rqptr)) { RequestEnd (rqptr); return; } } break; } } if (rqptr->rqHeader.HttpVersion == HTTP_VERSION_1_1 || rqptr->rqHeader.HttpVersion == HTTP_VERSION_1_0) { /*************************/ /* supported HTTP version */ /**************************/ /* look for the end of header blank line */ cnlcnt = rqptr->rqHeader.ConsecutiveNewLineCount; cptr = rqptr->rqHeader.RequestHeaderPtr; while (*cptr && cnlcnt < 2) { if (*cptr == '\r') cptr++; if (!*cptr) break; if (*cptr != '\n') { cptr++; cnlcnt = 0; continue; } cptr++; cnlcnt++; } if (cnlcnt >= 2) { rqptr->rqHeader.RequestHeaderPtr = rqptr->rqNet.ReadBufferPtr; rqptr->rqHeader.RequestHeaderLength = cptr - (char*)rqptr->rqNet.ReadBufferPtr; RequestParseAndExecute (rqptr); return; } rqptr->rqHeader.ConsecutiveNewLineCount = cnlcnt; } else if (rqptr->rqHeader.HttpVersion == HTTP_VERSION_0_9) { /************/ /* HTTP/0.9 */ /************/ rqptr->rqHeader.RequestHeaderPtr = rqptr->rqNet.ReadBufferPtr; rqptr->rqHeader.RequestHeaderLength = cptr - (char*)rqptr->rqNet.ReadBufferPtr; RequestParseAndExecute (rqptr); return; } else if (rqptr->rqHeader.HttpVersion) { /****************************/ /* unsupported HTTP version */ /****************************/ rqptr->rqResponse.HttpStatus = 505; ErrorGeneral (rqptr, MsgFor(rqptr,MSG_REQUEST_FORMAT), FI_LI); RequestEnd (rqptr); return; } /*****************************/ /* read more from the client */ /*****************************/ if (rqptr->rqHeader.RequestHeaderLength >= rqptr->rqNet.ReadBufferSize) { /* need more buffer space, allow one for termination of the buffer */ rqptr->rqNet.ReadBufferSize += NetReadBufferSize; rqptr->rqNet.ReadBufferPtr = VmReallocHeap (rqptr, rqptr->rqNet.ReadBufferPtr, rqptr->rqNet.ReadBufferSize+1, FI_LI); } /* because a realloc() may move the memory reset the pointers */ rqptr->rqHeader.RequestHeaderPtr = rqptr->rqNet.ReadBufferPtr + rqptr->rqHeader.RequestHeaderLength; /* asynchronous read to get more of the header from the client */ NetRead (rqptr, &RequestGet, rqptr->rqHeader.RequestHeaderPtr, rqptr->rqNet.ReadBufferSize - rqptr->rqHeader.RequestHeaderLength); #undef STRCAT #undef CONTROL_D #undef CONTROL_Z } /****************************************************************************/ /* Get the method, path and query string, and some header field line values, then execute the request. This function can be called from two points. First, from the function RequestGet(), after it has received a complete HTTP header from the client. Second, from the function RequestRedirect(), where an HTTP header has been reconstructed in the 'NetReadBufferPtr' in order to effect a local redirection. */ RequestParseAndExecute (REQUEST_STRUCT *rqptr) { char *cptr; /*********/ /* begin */ /*********/ if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD_REQUEST)) { WatchThis (rqptr, FI_LI, WATCH_MOD_REQUEST, "RequestParseAndExecute() !UL", rqptr->rqHeader.RequestHeaderLength); WatchDataDump (rqptr->rqHeader.RequestHeaderPtr, rqptr->rqHeader.RequestHeaderLength); } if (Watch.Category && Watch.Category != WATCH_ONE_SHOT_CAT) WatchFilterRequestHeader (rqptr); if (WATCHING(rqptr) && WATCH_CATEGORY(WATCH_REQUEST_HEADER)) { WatchThis (rqptr, FI_LI, WATCH_REQUEST_HEADER, "HEADER !UL bytes", rqptr->rqHeader.RequestHeaderLength); WatchData (rqptr->rqHeader.RequestHeaderPtr, rqptr->rqHeader.RequestHeaderLength); } InstanceMutexLock (INSTANCE_MUTEX_HTTPD); AccountingPtr->RequestTotalCount++; AccountingPtr->RequestParseCount++; if (rqptr->rqNet.ConnectionCount) AccountingPtr->RequestPersistentCount++; InstanceMutexUnLock (INSTANCE_MUTEX_HTTPD); /* step to the end of the request line (e.g. "GET /path HTTP/1.0\n") */ cptr = rqptr->rqHeader.RequestHeaderPtr + rqptr->rqHeader.RequestLineLength; /* step over the carriage control at the end of the request line */ if (*cptr == '\r') cptr++; if (*cptr == '\n') cptr++; if (!RequestFields (rqptr, cptr)) { RequestEnd (rqptr); return; } RequestParseAndExecute2 (rqptr); } /****************************************************************************/ /* Continue processing and executing the request. */ RequestParseAndExecute2 (REQUEST_STRUCT *rqptr) { char *cptr; /*********/ /* begin */ /*********/ if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD_REQUEST)) WatchThis (rqptr, FI_LI, WATCH_MOD_REQUEST, "RequestParseAndExecute2() !&F", &RequestParseAndExecute2); if (rqptr->ServicePtr->TrackEnabled) if (!TrackGetCookie (rqptr)) TrackSetCookie (rqptr); if (Watch.Category && Watch.Category != WATCH_ONE_SHOT_CAT) WatchFilterPathTrack (rqptr); if (rqptr->rqResponse.HttpVersion == HTTP_VERSION_1_1) { /* HTTP/1.1 */ if (!rqptr->rqHeader.HostPtr) { /* HTTP/1.1 mandates a "Host:" field */ InstanceGblSecIncrLong (&AccountingPtr->RequestErrorCount); rqptr->rqResponse.HttpStatus = 400; ErrorGeneral (rqptr, MsgFor(rqptr,MSG_REQUEST_FORMAT), FI_LI); RequestEnd (rqptr); return; } if (rqptr->rqHeader.ExpectUnsupported) { /* HTTP/1.1 mandates a supported "Expect:" field */ InstanceGblSecIncrLong (&AccountingPtr->RequestErrorCount); rqptr->rqResponse.HttpStatus = 417; ErrorGeneral (rqptr, MsgFor(rqptr,MSG_REQUEST_FORMAT), FI_LI); RequestEnd (rqptr); return; } /* HTTP/1.1 connections are persistent unless otherwise directed */ if (!WEBSOCKET_REQUEST(rqptr)) if (!rqptr->rqHeader.ConnectionClose) rqptr->PersistentRequest = true; /* test for possible request pipelining */ if (Config.cfMisc.PipelineRequests && !rqptr->rqHeader.ContentLength && rqptr->BytesRx[0] > rqptr->rqHeader.RequestHeaderLength) { /* just a request header, no body, and extra octets - so perhaps! */ if (rqptr->BytesRx[0] - rqptr->rqHeader.RequestHeaderLength > NetReadBufferSize) { /* won't fit into the pipeline buffer - shouldn't happen often */ rqptr->PersistentRequest = false; } else { /* set the buffer data to the trailing octets */ rqptr->rqNet.PipelineBufferPtr = rqptr->rqHeader.RequestHeaderPtr + rqptr->rqHeader.RequestHeaderLength; rqptr->rqNet.PipelineBufferCount = rqptr->BytesRx[0] - rqptr->rqHeader.RequestHeaderLength; } } } else if (rqptr->rqResponse.HttpVersion == HTTP_VERSION_1_0) { /* HTTP/1.0 */ if (rqptr->rqHeader.KeepAliveConnection || rqptr->rqHeader.ConnectionKeepAlive) rqptr->PersistentRequest = true; } if (rqptr->rqHeader.ContentEncodingUnknown) { rqptr->rqResponse.HttpStatus = 415; ErrorGeneral (rqptr, MsgFor(rqptr,MSG_REQUEST_FORMAT), FI_LI); RequestEnd (rqptr); return; } if (rqptr->rqHeader.CacheControlNoCache || rqptr->rqHeader.CacheControlNoStore || rqptr->rqHeader.CacheControlMaxAgeZero || rqptr->rqHeader.PragmaNoCache) { rqptr->NotFromCache = true; InstanceGblSecIncrLong (&AccountingPtr->RequestNotFromCacheCount); } if (LoggingFileError && Config.cfLog.WriteFail503 && !strsame (rqptr->rqHeader.RequestUriPtr, HTTPD_ADMIN, sizeof(HTTPD_ADMIN)-1)) { rqptr->rqResponse.HttpStatus = 503; ErrorGeneral (rqptr, MsgFor(rqptr,MSG_GENERAL_TRY_LATER), FI_LI); RequestEnd (rqptr); return; } if (WebDavEnabled) { if (rqptr->WebDavMethod || rqptr->rqHeader.WebDavDepthFieldPtr || rqptr->rqHeader.WebDavDestinationFieldPtr || rqptr->rqHeader.WebDavIfFieldPtr || rqptr->rqHeader.WebDavLockTokenFieldPtr || rqptr->rqHeader.WebDavTimeoutFieldPtr || rqptr->rqHeader.WebDavTranslateFieldPtr || rqptr->rqHeader.WebDavOverwriteFieldPtr) { /* it's a fair bet considering these are WebDAV header fields */ rqptr->WebDavRequest = true; } else if (rqptr->rqHeader.UserAgentPtr && (cptr = strstr (rqptr->rqHeader.UserAgentPtr, "DAV"))) { /* sniff around the user agent string */ if (cptr > rqptr->rqHeader.UserAgentPtr+1 && !isalpha(cptr[-1]) && !isalpha(cptr[3])) rqptr->WhiffOfWebDav = true; else if (cptr > rqptr->rqHeader.UserAgentPtr+3 && strsame (cptr-3, "WebDAV", 6)) rqptr->WhiffOfWebDav = true; } if (rqptr->WebDavRequest) InstanceGblSecIncrLong (&AccountingPtr->WebDavRequestCount); else if (rqptr->WhiffOfWebDav) InstanceGblSecIncrLong (&AccountingPtr->WebDavAromaCount); } if (WEBSOCKET_REQUEST(rqptr)) if (!WebSockRequest (rqptr)) return; /********************************************/ /* evaluated in order of probable occurance */ /********************************************/ if (rqptr->rqHeader.Method == HTTP_METHOD_GET) { InstanceMutexLock (INSTANCE_MUTEX_HTTPD); AccountingPtr->MethodGetCount++; if (rqptr->WebDavRequest || rqptr->WhiffOfWebDav) AccountingPtr->MethodWebDav_GetCount++; if (WEBSOCKET_REQUEST(rqptr)) WebSockCurrent = ++AccountingPtr->WebSocketCurrent; InstanceMutexUnLock (INSTANCE_MUTEX_HTTPD); RequestExecute (rqptr); return; } if (rqptr->ServicePtr->ProxyTunnel) { if (rqptr->rqHeader.Method == HTTP_METHOD_CONNECT) { InstanceGblSecIncrLong (&AccountingPtr->MethodConnectCount); rqptr->PersistentRequest = false; RequestExecute (rqptr); return; } } if (rqptr->rqHeader.HttpVersion == HTTP_VERSION_0_9) { /*********************************/ /* HTTP/0.9 must be GET requests */ /*********************************/ InstanceGblSecIncrLong (&AccountingPtr->RequestErrorCount); rqptr->rqResponse.HttpStatus = 501; ErrorGeneral (rqptr, MsgFor(rqptr,MSG_REQUEST_METHOD), FI_LI); RequestEnd (rqptr); return; } if (rqptr->rqHeader.Method == HTTP_METHOD_HEAD) { InstanceGblSecIncrLong (&AccountingPtr->MethodHeadCount); RequestExecute (rqptr); return; } if (rqptr->rqHeader.Method == HTTP_METHOD_POST) { InstanceGblSecIncrLong (&AccountingPtr->MethodPostCount); RequestExecute (rqptr); return; } if (rqptr->rqHeader.Method == HTTP_METHOD_PUT) { InstanceGblSecIncrLong (&AccountingPtr->MethodPutCount); if (rqptr->WebDavRequest || rqptr->WhiffOfWebDav) InstanceGblSecIncrLong (&AccountingPtr->MethodWebDav_PutCount); RequestExecute (rqptr); return; } if (rqptr->rqHeader.Method == HTTP_METHOD_CONNECT) { InstanceGblSecIncrLong (&AccountingPtr->MethodConnectCount); rqptr->PersistentRequest = false; RequestExecute (rqptr); return; } if (rqptr->rqHeader.Method == HTTP_METHOD_DELETE) { InstanceGblSecIncrLong (&AccountingPtr->MethodDeleteCount); if (rqptr->WebDavRequest || rqptr->WhiffOfWebDav) InstanceGblSecIncrLong (&AccountingPtr->MethodWebDav_DeleteCount); RequestExecute (rqptr); return; } if (rqptr->rqHeader.Method == HTTP_METHOD_TRACE) { InstanceGblSecIncrLong (&AccountingPtr->MethodTraceCount); rqptr->PersistentRequest = false; RequestExecute (rqptr); return; } if (rqptr->rqHeader.Method == HTTP_METHOD_OPTIONS) { InstanceGblSecIncrLong (&AccountingPtr->MethodOptionsCount); if (rqptr->WebDavRequest || rqptr->WhiffOfWebDav) InstanceGblSecIncrLong (&AccountingPtr->MethodWebDav_OptionsCount); RequestExecute (rqptr); return; } if (rqptr->rqHeader.Method == HTTP_METHOD_EXTENSION) { InstanceGblSecIncrLong (&AccountingPtr->MethodExtensionCount); RequestExecute (rqptr); return; } if (rqptr->rqHeader.Method == HTTP_METHOD_WEBDAV_PROPPATCH) { InstanceGblSecIncrLong (&AccountingPtr->MethodWebDavPropPatchCount); RequestExecute (rqptr); return; } if (rqptr->rqHeader.Method == HTTP_METHOD_WEBDAV_PROPFIND) { InstanceGblSecIncrLong (&AccountingPtr->MethodWebDavPropFindCount); RequestExecute (rqptr); return; } if (rqptr->rqHeader.Method == HTTP_METHOD_WEBDAV_COPY) { InstanceGblSecIncrLong (&AccountingPtr->MethodWebDavCopyCount); RequestExecute (rqptr); return; } if (rqptr->rqHeader.Method == HTTP_METHOD_WEBDAV_MOVE) { InstanceGblSecIncrLong (&AccountingPtr->MethodWebDavMoveCount); RequestExecute (rqptr); return; } if (rqptr->rqHeader.Method == HTTP_METHOD_WEBDAV_MKCOL) { InstanceGblSecIncrLong (&AccountingPtr->MethodWebDavMkColCount); RequestExecute (rqptr); return; } if (rqptr->rqHeader.Method == HTTP_METHOD_WEBDAV_LOCK) { InstanceGblSecIncrLong (&AccountingPtr->MethodWebDavLockCount); RequestExecute (rqptr); return; } if (rqptr->rqHeader.Method == HTTP_METHOD_WEBDAV_UNLOCK) { InstanceGblSecIncrLong (&AccountingPtr->MethodWebDavUnLockCount); RequestExecute (rqptr); return; } if (WATCHING(rqptr) && WATCH_CATEGORY(WATCH_REQUEST)) WatchThis (rqptr, FI_LI, WATCH_REQUEST, "!#AZ", rqptr->rqHeader.RequestLineLength, rqptr->rqHeader.RequestHeaderPtr); InstanceGblSecIncrLong (&AccountingPtr->RequestErrorCount); rqptr->rqResponse.HttpStatus = 501; ErrorGeneral (rqptr, MsgFor(rqptr,MSG_REQUEST_METHOD), FI_LI); RequestEnd (rqptr); } /****************************************************************************/ /* Parse the first (strict HTTP) or newline (de facto HTTP) terminated string from the client's request packet into HTTP method, path information and query string. Return true to continue processing the request, false to stop immediately. If false this function or any it called must have reported the problem to the client and/or disposed of the thread. */ RequestLineParse (REQUEST_STRUCT *rqptr) { char *cptr, *sptr, *uptr, *zptr; /*********/ /* begin */ /*********/ if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD_REQUEST)) { WatchThis (rqptr, FI_LI, WATCH_MOD_REQUEST, "RequestLineParse()"); WatchDataDump (rqptr->rqNet.ReadBufferPtr, strlen(rqptr->rqNet.ReadBufferPtr)); } cptr = rqptr->rqNet.ReadBufferPtr; zptr = (sptr = rqptr->rqHeader.MethodName) + sizeof(rqptr->rqHeader.MethodName)-1; while (!ISLWS(*cptr) && NOTEOL(*cptr) && sptr < zptr) *sptr++ = *cptr++; *sptr = '\0'; /* error with method name */ if (!ISLWS(*cptr) && NOTEOL(*cptr)) goto RequestLineParseFormatError; if (!rqptr->rqHeader.MethodName[0]) goto RequestLineParseFormatError; /* also match the trailing nulls */ if (SAME4 (rqptr->rqHeader.MethodName, 'GET\0')) rqptr->rqHeader.Method = HTTP_METHOD_GET; else if (MATCH5 (rqptr->rqHeader.MethodName, "HEAD")) rqptr->rqHeader.Method = HTTP_METHOD_HEAD; else if (MATCH5 (rqptr->rqHeader.MethodName, "POST")) rqptr->rqHeader.Method = HTTP_METHOD_POST; else if (MATCH4 (rqptr->rqHeader.MethodName, "PUT")) { rqptr->rqHeader.Method = HTTP_METHOD_PUT; rqptr->WebDavMethod = WebDavEnabled; } else if (MATCH8 (rqptr->rqHeader.MethodName, "CONNECT")) rqptr->rqHeader.Method = HTTP_METHOD_CONNECT; else if (MATCH7 (rqptr->rqHeader.MethodName, "DELETE")) { rqptr->rqHeader.Method = HTTP_METHOD_DELETE; rqptr->WebDavMethod = WebDavEnabled; } else if (MATCH6 (rqptr->rqHeader.MethodName, "TRACE")) rqptr->rqHeader.Method = HTTP_METHOD_TRACE; else if (MATCH8 (rqptr->rqHeader.MethodName, "OPTIONS")) rqptr->rqHeader.Method = HTTP_METHOD_OPTIONS; else if (WebDavEnabled) { if (MATCH5 (rqptr->rqHeader.MethodName, "COPY")) { rqptr->rqHeader.Method = HTTP_METHOD_WEBDAV_COPY; rqptr->WebDavMethod = true; } else if (MATCH6 (rqptr->rqHeader.MethodName, "MKCOL")) { rqptr->rqHeader.Method = HTTP_METHOD_WEBDAV_MKCOL; rqptr->WebDavMethod = true; } else if (MATCH5 (rqptr->rqHeader.MethodName, "MOVE")) { rqptr->rqHeader.Method = HTTP_METHOD_WEBDAV_MOVE; rqptr->WebDavMethod = true; } else if (MATCH0 (rqptr->rqHeader.MethodName, "PROPPATCH", 10)) { rqptr->rqHeader.Method = HTTP_METHOD_WEBDAV_PROPPATCH; rqptr->WebDavMethod = true; } else if (MATCH0 (rqptr->rqHeader.MethodName, "PROPFIND", 9)) { rqptr->rqHeader.Method = HTTP_METHOD_WEBDAV_PROPFIND; rqptr->WebDavMethod = true; } else if (WebDavLockingEnabled) { if (MATCH5 (rqptr->rqHeader.MethodName, "LOCK")) { rqptr->rqHeader.Method = HTTP_METHOD_WEBDAV_LOCK; rqptr->WebDavMethod = true; } else if (MATCH7 (rqptr->rqHeader.MethodName, "UNLOCK")) { rqptr->rqHeader.Method = HTTP_METHOD_WEBDAV_UNLOCK; rqptr->WebDavMethod = true; } else if (MapUrlExtensionMethod) rqptr->rqHeader.Method = HTTP_METHOD_EXTENSION; else rqptr->rqHeader.Method = HTTP_METHOD_UNSUPPORTED; } else if (MapUrlExtensionMethod) rqptr->rqHeader.Method = HTTP_METHOD_EXTENSION; else rqptr->rqHeader.Method = HTTP_METHOD_UNSUPPORTED; } else if (MapUrlExtensionMethod) rqptr->rqHeader.Method = HTTP_METHOD_EXTENSION; else rqptr->rqHeader.Method = HTTP_METHOD_UNSUPPORTED; /* skip across white-space between method and path */ while (ISLWS(*cptr)) cptr++; /* get the path information, noting the start of the requested URI */ for (uptr = zptr = cptr; !ISLWS(*zptr) && *zptr != '?' && NOTEOL(*zptr); zptr++); /* error, no path on request line */ if (cptr == zptr) goto RequestLineParseFormatError; rqptr->rqHeader.PathInfoLength = zptr - cptr; rqptr->rqHeader.PathInfoPtr = sptr = VmGetHeap (rqptr, rqptr->rqHeader.PathInfoLength+1); while (cptr < zptr) *sptr++ = *cptr++; if ((rqptr->rqHeader.PathInfoLength = StringUrlDecode (rqptr->rqHeader.PathInfoPtr)) < 0) goto RequestLineParseUrlEncodeError; /* get any query string, or create an empty one */ if (*cptr == '?') cptr++; for (zptr = cptr; !ISLWS(*zptr) && NOTEOL(*zptr); zptr++); rqptr->rqHeader.QueryStringLength = zptr - cptr; rqptr->rqHeader.QueryStringPtr = sptr = VmGetHeap (rqptr, rqptr->rqHeader.QueryStringLength+1); while (cptr < zptr) *sptr++ = *cptr++; /* store the requested resource */ rqptr->rqHeader.RequestUriLength = cptr - uptr; rqptr->rqHeader.RequestUriPtr = sptr = VmGetHeap (rqptr, rqptr->rqHeader.RequestUriLength+1); while (uptr < cptr) *sptr++ = *uptr++; if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD_REQUEST)) WatchDataFormatted ("!&Z !&Z !&Z !&Z\n", rqptr->rqHeader.MethodName, rqptr->rqHeader.PathInfoPtr, rqptr->rqHeader.QueryStringPtr, rqptr->rqHeader.RequestUriPtr); /* check the HTTP version */ while (ISLWS(*cptr)) cptr++; for (zptr = cptr; NOTEOL(*zptr); zptr++); rqptr->rqHeader.RequestLineLength = zptr - (char*)rqptr->rqNet.ReadBufferPtr; if (MATCH8 (cptr, "HTTP/1.1")) { rqptr->rqHeader.HttpVersion = rqptr->rqResponse.HttpVersion = HTTP_VERSION_1_1; return (true); } if (MATCH8 (cptr, "HTTP/1.0")) { rqptr->rqHeader.HttpVersion = rqptr->rqResponse.HttpVersion = HTTP_VERSION_1_0; return (true); } if (MATCH5 (cptr, "HTTP/")) { rqptr->rqHeader.HttpVersion = HTTP_VERSION_UNKNOWN; return (true); } /* no HTTP version followed the path */ rqptr->rqHeader.HttpVersion = rqptr->rqResponse.HttpVersion = HTTP_VERSION_0_9; return (true); /* report a request line format problem and return false */ RequestLineParseFormatError: { InstanceGblSecIncrLong (&AccountingPtr->RequestErrorCount); rqptr->rqResponse.HttpStatus = 400; ErrorGeneral (rqptr, MsgFor(rqptr,MSG_REQUEST_FORMAT), FI_LI); return (false); } RequestLineParseUrlEncodeError: { InstanceGblSecIncrLong (&AccountingPtr->RequestErrorCount); rqptr->rqResponse.HttpStatus = 400; ErrorGeneral (rqptr, MsgFor(rqptr,MSG_REQUEST_URL_ENC), FI_LI); return (false); } } /*****************************************************************************/ /* Scan the header buffer looking for relevant fields. Return true to continue processing the request, false to stop immediately. If false this function or any it called must have reported the problem to the client and/or disposed of the thread. */ BOOL RequestFields ( REQUEST_STRUCT *rqptr, char *FieldsPtr ) { int idx, rfidx, status, NameLength, RequestFieldsMaxExceeded, RequestUnknownFieldsMaxExceeded, TotalLength, ValueLength; char *cptr, *lptr, *sptr, *tcptr; char **rfptr; RANGE_BYTE *rbptr; /*********/ /* begin */ /*********/ if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD_REQUEST)) WatchThis (rqptr, FI_LI, WATCH_MOD_REQUEST, "RequestFields()"); RequestFieldsMaxExceeded = RequestUnknownFieldsMaxExceeded = 0; lptr = FieldsPtr; /* look for the fields-terminating blank line */ while (*lptr) { /* skip over the field name and white space */ for (cptr = lptr; *lptr != ':' && NOTEOL(*lptr); lptr++); if (*lptr == ':') lptr++; while (ISLWS(*lptr)) lptr++; NameLength = lptr - cptr; /* note the start of the field parameter */ sptr = lptr; /* skip to the end-of-line */ while (NOTEOL(*lptr)) lptr++; /* length of field parameter */ ValueLength = lptr - sptr; TotalLength = lptr - cptr; if (lptr[0] == '\n' || (lptr[0] == '\r' && lptr[1] == '\n')) { /* break if blank line (i.e. end-of-header) */ if (cptr == lptr) break; } if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD_REQUEST)) WatchDataFormatted ("{!UL}!-!#AZ\n{!UL}!-!#AZ\n", TotalLength, cptr, ValueLength, cptr+NameLength); /* step over the carriage control at the end of the line */ if (*lptr == '\r') lptr++; if (*lptr == '\n') lptr++; /* put the field into a dynamic buffer (ready for assignment) */ tcptr = VmGetHeap (rqptr, TotalLength+1); memcpy (tcptr, cptr, TotalLength); tcptr[TotalLength] = '\0'; /***********/ /* Accept: */ /***********/ if (TOUP(cptr[0]) == 'A' && cptr[6] == ':' && strsame (cptr, "Accept:", 7)) { if (!rqptr->rqHeader.AcceptFieldPtr) { rqptr->rqHeader.AcceptFieldPtr = tcptr; rqptr->rqHeader.AcceptFieldLength = TotalLength; rqptr->rqHeader.AcceptPtr = tcptr + NameLength; rqptr->rqHeader.AcceptLength = ValueLength; } else { NameLength = rqptr->rqHeader.AcceptPtr - rqptr->rqHeader.AcceptFieldPtr; TotalLength = rqptr->rqHeader.AcceptFieldLength + ValueLength + 1; tcptr = rqptr->rqHeader.AcceptFieldPtr; tcptr = VmReallocHeap (rqptr, tcptr, TotalLength+1, FI_LI); rqptr->rqHeader.AcceptFieldPtr = tcptr; rqptr->rqHeader.AcceptPtr = tcptr + NameLength; rqptr->rqHeader.AcceptLength = TotalLength - NameLength; tcptr += rqptr->rqHeader.AcceptFieldLength; rqptr->rqHeader.AcceptFieldLength = TotalLength; *tcptr++ = ','; memcpy (tcptr, sptr, ValueLength); tcptr[ValueLength] = '\0'; } continue; } /*******************/ /* Accept-Charset: */ /*******************/ if (TOUP(cptr[0]) == 'A' && TOUP(cptr[7]) == 'C' && strsame (cptr, "Accept-Charset:", 15)) { if (!rqptr->rqHeader.AcceptCharsetFieldPtr) { rqptr->rqHeader.AcceptCharsetFieldPtr = tcptr; rqptr->rqHeader.AcceptCharsetFieldLength = TotalLength; rqptr->rqHeader.AcceptCharsetPtr = tcptr + NameLength; rqptr->rqHeader.AcceptCharsetLength = ValueLength; } else { NameLength = rqptr->rqHeader.AcceptCharsetPtr - rqptr->rqHeader.AcceptCharsetFieldPtr; TotalLength = rqptr->rqHeader.AcceptCharsetFieldLength + ValueLength + 1; tcptr = rqptr->rqHeader.AcceptCharsetFieldPtr; tcptr = VmReallocHeap (rqptr, tcptr, TotalLength+1, FI_LI); rqptr->rqHeader.AcceptCharsetFieldPtr = tcptr; rqptr->rqHeader.AcceptCharsetPtr = tcptr + NameLength; rqptr->rqHeader.AcceptCharsetLength = TotalLength - NameLength; tcptr += rqptr->rqHeader.AcceptCharsetFieldLength; rqptr->rqHeader.AcceptCharsetFieldLength = TotalLength; *tcptr++ = ','; memcpy (tcptr, sptr, ValueLength); tcptr[ValueLength] = '\0'; } continue; } /********************/ /* Accept-Encoding: */ /********************/ if (TOUP(cptr[0]) == 'A' && TOUP(cptr[7]) == 'E' && strsame (cptr, "Accept-Encoding:", 16)) { if (!rqptr->rqHeader.AcceptEncodingFieldPtr) { rqptr->rqHeader.AcceptEncodingFieldPtr = tcptr; rqptr->rqHeader.AcceptEncodingFieldLength = TotalLength; rqptr->rqHeader.AcceptEncodingPtr = tcptr + NameLength; rqptr->rqHeader.AcceptEncodingLength = ValueLength; } else { NameLength = rqptr->rqHeader.AcceptEncodingPtr - rqptr->rqHeader.AcceptEncodingFieldPtr; TotalLength = rqptr->rqHeader.AcceptEncodingFieldLength + ValueLength + 1; tcptr = rqptr->rqHeader.AcceptEncodingFieldPtr; tcptr = VmReallocHeap (rqptr, tcptr, TotalLength+1, FI_LI); rqptr->rqHeader.AcceptEncodingFieldPtr = tcptr; rqptr->rqHeader.AcceptEncodingPtr = tcptr + NameLength; rqptr->rqHeader.AcceptEncodingLength = TotalLength - NameLength; tcptr += rqptr->rqHeader.AcceptEncodingFieldLength; rqptr->rqHeader.AcceptEncodingFieldLength = TotalLength; *tcptr++ = ','; memcpy (tcptr, sptr, ValueLength); tcptr[ValueLength] = '\0'; } /* look for parameters to this header field */ while (*tcptr) { while (*tcptr && ISLWS(*tcptr)) tcptr++; if (strsame (tcptr, "gzip", 4)) { rqptr->rqHeader.AcceptEncodingGzip = true; tcptr += 4; } else if (strsame (tcptr, "deflate", 7)) { rqptr->rqHeader.AcceptEncodingGzip = true; tcptr += 7; } while (*tcptr && !ISLWS(*tcptr) && *tcptr != ',') tcptr++; while (*tcptr && (ISLWS(*tcptr) || *tcptr == ',')) tcptr++; } if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD_REQUEST)) WatchDataFormatted ("gzip:!&B\n", rqptr->rqHeader.AcceptEncodingGzip); continue; } /********************/ /* Accept-Language: */ /********************/ if (TOUP(cptr[0]) == 'A' && TOUP(cptr[7]) == 'L' && strsame (cptr, "Accept-Language:", 16)) { if (!rqptr->rqHeader.AcceptLangFieldPtr) { rqptr->rqHeader.AcceptLangFieldPtr = tcptr; rqptr->rqHeader.AcceptLangFieldLength = TotalLength; rqptr->rqHeader.AcceptLangPtr = tcptr + NameLength; rqptr->rqHeader.AcceptLangLength = ValueLength; } else { NameLength = rqptr->rqHeader.AcceptLangPtr - rqptr->rqHeader.AcceptLangFieldPtr; TotalLength = rqptr->rqHeader.AcceptLangFieldLength + ValueLength + 1; tcptr = rqptr->rqHeader.AcceptLangFieldPtr; tcptr = VmReallocHeap (rqptr, tcptr, TotalLength+1, FI_LI); rqptr->rqHeader.AcceptLangFieldPtr = tcptr; rqptr->rqHeader.AcceptLangPtr = tcptr + NameLength; rqptr->rqHeader.AcceptLangLength = TotalLength - NameLength; tcptr += rqptr->rqHeader.AcceptLangFieldLength; rqptr->rqHeader.AcceptLangFieldLength = TotalLength; *tcptr++ = ','; memcpy (tcptr, sptr, ValueLength); tcptr[ValueLength] = '\0'; } continue; } /******************/ /* Authorization: */ /******************/ if (TOUP(cptr[0]) == 'A' && TOUP(cptr[1]) == 'U' && strsame (cptr, "Authorization:", 14)) { rqptr->rqHeader.AuthorizationFieldPtr = tcptr; rqptr->rqHeader.AuthorizationPtr = tcptr + NameLength; continue; } /*****************************/ /* Cache-Control: (HTTP/1.1) */ /*****************************/ if (TOUP(cptr[0]) == 'C' && cptr[5] == '-' && strsame (cptr, "Cache-Control:", 14)) { rqptr->rqHeader.CacheControlFieldPtr = tcptr; rqptr->rqHeader.CacheControlPtr = tcptr + NameLength; tcptr += NameLength; while (*tcptr) { while (*tcptr && ISLWS(*tcptr)) tcptr++; if (strsame (tcptr, "no-cache", 8)) { rqptr->rqHeader.CacheControlNoCache = true; tcptr += 8; } else if (strsame (tcptr, "no-store", 8)) { rqptr->rqHeader.CacheControlNoStore = true; tcptr += 8; } else if (strsame (tcptr, "no-transform", 12)) { rqptr->rqHeader.CacheControlNoTransform = true; tcptr += 12; } else if (strsame (tcptr, "max-age=", 8)) { tcptr += 8; rqptr->rqHeader.CacheControlMaxAge = atoi(tcptr); if (!rqptr->rqHeader.CacheControlMaxAge) rqptr->rqHeader.CacheControlMaxAgeZero = true; } else if (strsame (tcptr, "max-stale=", 10)) { tcptr += 10; rqptr->rqHeader.CacheControlMaxStale = atoi(tcptr); if (!rqptr->rqHeader.CacheControlMaxStale) rqptr->rqHeader.CacheControlMaxStaleZero = true; } else if (strsame (tcptr, "max-stale", 9)) { rqptr->rqHeader.CacheControlMaxStaleZero = true; tcptr += 9; } else if (strsame (tcptr, "min-fresh=", 10)) { tcptr += 10; rqptr->rqHeader.CacheControlMinFresh = atoi(tcptr); if (!rqptr->rqHeader.CacheControlMinFresh) rqptr->rqHeader.CacheControlMinFreshZero = true; } else if (strsame (tcptr, "only-if-cached", 14)) { rqptr->rqHeader.CacheControlOnlyIfCached = true; tcptr += 14; } while (*tcptr && !ISLWS(*tcptr) && *tcptr != ',') tcptr++; while (*tcptr && (ISLWS(*tcptr) || *tcptr == ',')) tcptr++; } if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD_REQUEST)) WatchThis (rqptr, FI_LI, WATCH_MOD_REQUEST, "!UL/!&B !UL/!&B !UL/!&B !&B !&B !&B !&B", rqptr->rqHeader.CacheControlMaxAge, rqptr->rqHeader.CacheControlMaxAgeZero, rqptr->rqHeader.CacheControlMaxStale, rqptr->rqHeader.CacheControlMaxStaleZero, rqptr->rqHeader.CacheControlMinFresh, rqptr->rqHeader.CacheControlMinFreshZero, rqptr->rqHeader.CacheControlNoCache, rqptr->rqHeader.CacheControlNoTransform, rqptr->rqHeader.CacheControlNoStore, rqptr->rqHeader.CacheControlOnlyIfCached); continue; } /***************/ /* Connection: */ /***************/ if (TOUP(cptr[0]) == 'C' && TOUP(cptr[8]) == 'O' && strsame (cptr, "Connection:", 11)) { rqptr->rqHeader.ConnectionFieldPtr = tcptr; rqptr->rqHeader.ConnectionPtr = tcptr + NameLength; /* look for parameters to this header field */ tcptr += NameLength; while (*tcptr) { while (*tcptr && ISLWS(*tcptr)) tcptr++; if (strsame (tcptr, "close", 5)) { rqptr->rqHeader.ConnectionClose = true; tcptr += 5; } else if (strsame (tcptr, "keep-alive", 10)) { rqptr->rqHeader.ConnectionKeepAlive = true; tcptr += 10; } else if (strsame (tcptr, "upgrade", 7)) { rqptr->rqHeader.ConnectionUpgrade = true; tcptr += 7; } while (*tcptr && !ISLWS(*tcptr) && *tcptr != ',') tcptr++; while (*tcptr && (ISLWS(*tcptr) || *tcptr == ',')) tcptr++; } if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD_REQUEST)) WatchDataFormatted ("close:!&B keep-alive:!&B\n", rqptr->rqHeader.ConnectionClose, rqptr->rqHeader.ConnectionKeepAlive); continue; } /*********************/ /* Content-Encoding: */ /*********************/ if (TOUP(cptr[0]) == 'C' && TOUP(cptr[8]) == 'E' && strsame (cptr, "Content-Encoding:", 17)) { rqptr->rqHeader.ContentEncodingFieldPtr = tcptr; rqptr->rqHeader.ContentEncodingPtr = tcptr + NameLength; /* look for parameters to this header field */ tcptr += NameLength; while (*tcptr) { while (*tcptr && ISLWS(*tcptr)) tcptr++; if (strsame (tcptr, "gzip", 4)) { rqptr->rqHeader.ContentEncodingGzip = true; tcptr += 4; } else if (strsame (tcptr, "deflate", 7)) { rqptr->rqHeader.ContentEncodingGzip = true; tcptr += 7; } else rqptr->rqHeader.ContentEncodingUnknown = true; while (*tcptr && !ISLWS(*tcptr) && *tcptr != ',') tcptr++; while (*tcptr && (ISLWS(*tcptr) || *tcptr == ',')) tcptr++; } if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD_REQUEST)) WatchDataFormatted ("gzip:!&B unknown:!&B\n", rqptr->rqHeader.ContentEncodingGzip, rqptr->rqHeader.ContentEncodingUnknown); continue; } /*******************/ /* Content-Length: */ /*******************/ if (TOUP(cptr[0]) == 'C' && TOUP(cptr[8]) == 'L' && strsame (cptr, "Content-Length:", 15)) { rqptr->rqHeader.ContentLengthFieldPtr = tcptr; rqptr->rqHeader.ContentLengthPtr = tcptr + NameLength; tcptr += NameLength; if (isdigit(*tcptr)) { rqptr->rqHeader.ContentLength = atoi (tcptr); if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD_REQUEST)) WatchDataFormatted ("!UL\n", rqptr->rqHeader.ContentLength); continue; } InstanceGblSecIncrLong (&AccountingPtr->RequestErrorCount); rqptr->rqResponse.HttpStatus = 400; ErrorGeneral (rqptr, MsgFor(rqptr,MSG_REQUEST_FORMAT), FI_LI); return (false); } /*****************/ /* Content-Type: */ /*****************/ if (TOUP(cptr[0]) == 'C' && TOUP(cptr[8]) == 'T' && strsame (cptr, "Content-Type:", 13)) { rqptr->rqHeader.ContentTypeFieldPtr = tcptr; rqptr->rqHeader.ContentTypePtr = tcptr + NameLength; continue; } /***********/ /* Cookie: */ /***********/ if (TOUP(cptr[0]) == 'C' && cptr[6] == ':' && strsame (cptr, "Cookie:", 7)) { rqptr->rqHeader.CookieFieldPtr = tcptr; rqptr->rqHeader.CookiePtr = tcptr + NameLength; continue; } /********/ /* DNT: */ /********/ if (TOUP(cptr[0]) == 'D' && cptr[3] == ':' && strsame (cptr, "DNT:", 4)) { rqptr->rqHeader.DoNotTrackFieldPtr = tcptr; rqptr->rqHeader.DoNotTrackPtr = tcptr + NameLength; continue; } /********************/ /* ETag: (HTTP/1.1) */ /********************/ if (TOUP(cptr[0]) == 'E' && cptr[4] == ':' && strsame (cptr, "ETag:", 5)) { rqptr->rqHeader.ETagFieldPtr = tcptr; rqptr->rqHeader.ETagPtr = tcptr + NameLength; continue; } /**********************/ /* Expect: (HTTP/1.1) */ /**********************/ if (TOUP(cptr[0]) == 'E' && cptr[6] == ':' && strsame (cptr, "Expect:", 7)) { rqptr->rqHeader.ExpectFieldPtr = tcptr; rqptr->rqHeader.ExpectPtr = tcptr + NameLength; /* look for parameters to this header field */ tcptr += NameLength; while (*tcptr) { while (*tcptr && ISLWS(*tcptr)) tcptr++; if (strsame (tcptr, "100-continue", 12)) { rqptr->rqHeader.Expect100Continue = true; tcptr += 12; } else rqptr->rqHeader.ExpectUnsupported = true; while (*tcptr && !ISLWS(*tcptr) && *tcptr != ',') tcptr++; while (*tcptr && (ISLWS(*tcptr) || *tcptr == ',')) tcptr++; } if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD_REQUEST)) WatchDataFormatted ("100-continue:!&B unsupported:!&B\n", rqptr->rqHeader.Expect100Continue, rqptr->rqHeader.ExpectUnsupported); continue; } /**************/ /* Forwarded: */ /**************/ if (TOUP(cptr[0]) == 'F' && cptr[9] == ':' && strsame (cptr, "Forwarded:", 10)) { if (!rqptr->rqHeader.ForwardedFieldPtr) { rqptr->rqHeader.ForwardedFieldPtr = tcptr; rqptr->rqHeader.ForwardedFieldLength = TotalLength; rqptr->rqHeader.ForwardedPtr = tcptr + NameLength; rqptr->rqHeader.ForwardedLength = ValueLength; } else { NameLength = rqptr->rqHeader.ForwardedPtr - rqptr->rqHeader.ForwardedFieldPtr; TotalLength = rqptr->rqHeader.ForwardedFieldLength + ValueLength + 1; tcptr = rqptr->rqHeader.ForwardedFieldPtr; tcptr = VmReallocHeap (rqptr, tcptr, TotalLength+1, FI_LI); rqptr->rqHeader.ForwardedFieldPtr = tcptr; rqptr->rqHeader.ForwardedPtr = tcptr + NameLength; rqptr->rqHeader.ForwardedLength = TotalLength - NameLength; tcptr += rqptr->rqHeader.ForwardedFieldLength; rqptr->rqHeader.ForwardedFieldLength = TotalLength; *tcptr++ = ','; memcpy (tcptr, sptr, ValueLength); tcptr[ValueLength] = '\0'; } continue; } /************************/ /* If-Match: (HTTP/1.1) */ /************************/ if (TOUP(cptr[0]) == 'I' && TOUP(cptr[4]) == 'A' && strsame (cptr, "If-Match:", 9)) { rqptr->rqHeader.IfMatchFieldPtr = tcptr; rqptr->rqHeader.IfMatchPtr = tcptr + NameLength; if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD_REQUEST)) WatchDataFormatted ("!&Z\n", rqptr->rqHeader.IfMatchPtr); continue; } /**********************/ /* If-Modified-Since: */ /**********************/ if (TOUP(cptr[0]) == 'I' && TOUP(cptr[4]) == 'O' && strsame (cptr, "If-Modified-Since:", 18)) { rqptr->rqHeader.IfModifiedSinceFieldPtr = tcptr; rqptr->rqHeader.IfModifiedSincePtr = tcptr + NameLength; tcptr += NameLength; status = HttpGmTime (tcptr, &rqptr->rqTime.IfModifiedSinceVMS64bit); if (VMSnok (status)) { rqptr->rqHeader.IfModifiedSincePtr = NULL; PUT_ZERO_QUAD (rqptr->rqTime.IfModifiedSinceVMS64bit); if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD_REQUEST)) WatchDataFormatted ("!&Z !&S\n", rqptr->rqHeader.IfModifiedSincePtr, status); continue; } /* indicate that no "length=" was found (if none is found :^) */ rqptr->rqHeader.IfModifiedSinceLength = -1; /* look for a "length=" parameter to this header field */ while (*tcptr) { while (*tcptr && *tcptr != ';') tcptr++; if (!*tcptr) break; tcptr++; while (*tcptr && ISLWS(*tcptr)) tcptr++; if (!*tcptr) break; if (!strsame (tcptr, "length", 6)) continue; tcptr += 6; while (*tcptr && *tcptr != '=') tcptr++; if (!*tcptr) break; tcptr++; while (*tcptr && ISLWS(*tcptr)) tcptr++; if (!isdigit (*tcptr)) continue; rqptr->rqHeader.IfModifiedSinceLength = atol(tcptr); } if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD_REQUEST)) WatchDataFormatted ("!&Z !&S !%D !SL\n", rqptr->rqHeader.IfModifiedSincePtr, status, &rqptr->rqTime.IfModifiedSinceVMS64bit, rqptr->rqHeader.IfModifiedSinceLength); continue; } /*****************************/ /* If-None-Match: (HTTP/1.1) */ /*****************************/ if (TOUP(cptr[0]) == 'I' && TOUP(cptr[8]) == 'M' && strsame (cptr, "If-None-Match:", 14)) { rqptr->rqHeader.IfNoneMatchFieldPtr = tcptr; rqptr->rqHeader.IfNoneMatchPtr = tcptr + NameLength; if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD_REQUEST)) WatchDataFormatted ("!&Z\n", rqptr->rqHeader.IfNoneMatchPtr); continue; } /************************/ /* If-Range: (HTTP/1.1) */ /************************/ if (TOUP(cptr[0]) == 'I' && TOUP(cptr[3]) == 'R' && strsame (cptr, "If-Range:", 9)) { rqptr->rqHeader.IfRangeFieldPtr = tcptr; rqptr->rqHeader.IfRangePtr = tcptr + NameLength; tcptr += NameLength; status = HttpGmTime (tcptr, &rqptr->rqTime.IfRangeVMS64bit); if (VMSnok (status)) { rqptr->rqHeader.IfRangePtr = NULL; PUT_ZERO_QUAD (rqptr->rqTime.IfRangeVMS64bit); if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD_REQUEST)) WatchDataFormatted ("!&Z !&S\n", rqptr->rqHeader.IfRangePtr, status); continue; } if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD_REQUEST)) WatchDataFormatted ("!&Z !&S !%D \n", rqptr->rqHeader.IfRangePtr, status, &rqptr->rqTime.IfRangeVMS64bit); continue; } /***********************************/ /* If-Unmodified-Since: (HTTP/1.1) */ /***********************************/ if (TOUP(cptr[0]) == 'I' && TOUP(cptr[3]) == 'U' && strsame (cptr, "If-Unmodified-Since:", 20)) { rqptr->rqHeader.IfUnModifiedSinceFieldPtr = tcptr; rqptr->rqHeader.IfUnModifiedSincePtr = tcptr + NameLength; tcptr += NameLength; status = HttpGmTime (tcptr, &rqptr->rqTime.IfUnModifiedSinceVMS64bit); if (VMSnok (status)) { rqptr->rqHeader.IfUnModifiedSincePtr = NULL; PUT_ZERO_QUAD (rqptr->rqTime.IfUnModifiedSinceVMS64bit); if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD_REQUEST)) WatchDataFormatted ("!&Z !&S\n", rqptr->rqHeader.IfUnModifiedSincePtr, status); continue; } if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD_REQUEST)) WatchDataFormatted ("!&Z !&S !%D \n", rqptr->rqHeader.IfUnModifiedSincePtr, status, &rqptr->rqTime.IfUnModifiedSinceVMS64bit); continue; } /*********/ /* Host: */ /*********/ if (TOUP(cptr[0]) == 'H' && cptr[4] == ':' && strsame (cptr, "Host:", 5)) { rqptr->rqHeader.HostFieldPtr = tcptr; rqptr->rqHeader.HostPtr = tcptr + NameLength; rqptr->rqHeader.HostLength = ValueLength; continue; } /***************/ /* Keep-Alive: */ /***************/ if (TOUP(cptr[0]) == 'K' && cptr[10] == ':' && strsame (cptr, "Keep-Alive:", 11)) { rqptr->rqHeader.KeepAliveFieldPtr = tcptr; rqptr->rqHeader.KeepAlivePtr = tcptr + NameLength; tcptr += NameLength; rqptr->rqHeader.KeepAliveConnection = true;; if (isdigit (*tcptr)) rqptr->rqHeader.KeepAliveSeconds = atoi(tcptr); if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD_REQUEST)) WatchDataFormatted ("keep-alive:!&B (!UL)\n", rqptr->rqHeader.KeepAliveConnection, rqptr->rqHeader.KeepAliveSeconds); continue; } /*****************/ /* Max-Forwards: */ /*****************/ if (TOUP(cptr[0]) == 'M' && cptr[12] == ':' && strsame (cptr, "Max-Forwards:", 13)) { rqptr->rqHeader.MaxForwardsFieldPtr = tcptr; rqptr->rqHeader.MaxForwardsPtr = tcptr + NameLength; tcptr += NameLength; rqptr->rqHeader.MaxForwards = atoi(tcptr); if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD_REQUEST)) WatchDataFormatted ("max-forwards:!UL\n", rqptr->rqHeader.MaxForwards); continue; } /***********/ /* Origin: */ /***********/ if (TOUP(cptr[0]) == 'O' && cptr[6] == ':' && strsame (cptr, "Origin:", 7)) { rqptr->rqHeader.OriginFieldPtr = tcptr; rqptr->rqHeader.OriginPtr = tcptr + NameLength; continue; } /***********/ /* Pragma: */ /***********/ if (TOUP(cptr[0]) == 'P' && cptr[6] == ':' && strsame (cptr, "Pragma:", 7)) { rqptr->rqHeader.PragmaFieldPtr = tcptr; rqptr->rqHeader.PragmaPtr = tcptr + NameLength; tcptr += NameLength; if (strsame (tcptr, "no-cache", 8)) rqptr->rqHeader.PragmaNoCache = true; if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD_REQUEST)) WatchDataFormatted ("!&B\n", rqptr->rqHeader.PragmaNoCache); continue; } /************************/ /* Proxy-Authorization: */ /************************/ if (TOUP(cptr[0]) == 'P' && TOUP(cptr[6]) == 'A' && strsame (cptr, "Proxy-Authorization:", 20)) { rqptr->rqHeader.ProxyAuthorizationFieldPtr = tcptr; rqptr->rqHeader.ProxyAuthorizationPtr = tcptr + NameLength; continue; } /*********************/ /* Proxy-Connection: */ /*********************/ if (TOUP(cptr[0]) == 'P' && TOUP(cptr[6]) == 'C' && strsame (cptr, "Proxy-Connection:", 17)) { rqptr->rqHeader.ProxyConnectionFieldPtr = tcptr; rqptr->rqHeader.ProxyConnectionPtr = tcptr + NameLength; /* look for parameters to this header field */ tcptr += NameLength; while (*tcptr) { while (*tcptr && ISLWS(*tcptr)) tcptr++; if (strsame (tcptr, "close", 5)) { rqptr->rqHeader.ProxyConnectionClose = true; tcptr += 5; } else if (strsame (tcptr, "keep-alive", 10)) { rqptr->rqHeader.ProxyConnectionKeepAlive = true; tcptr += 10; } while (*tcptr && !ISLWS(*tcptr) && *tcptr != ',') tcptr++; while (*tcptr && (ISLWS(*tcptr) || *tcptr == ',')) tcptr++; } if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD_REQUEST)) WatchDataFormatted ("close:!&B keep-alive:!&B\n", rqptr->rqHeader.ProxyConnectionClose, rqptr->rqHeader.ProxyConnectionKeepAlive); continue; } /**********/ /* Range: */ /**********/ if (TOUP(cptr[0]) == 'R' && cptr[5] == ':' && strsame (cptr, "Range:", 6)) { rqptr->rqHeader.RangeFieldPtr = tcptr; rqptr->rqHeader.RangePtr = tcptr + NameLength; rqptr->rqHeader.RangeBytePtr = rbptr = VmGetHeap (rqptr, sizeof(RANGE_BYTE)); tcptr = rqptr->rqHeader.RangePtr; if (strsame (tcptr, "bytes=", 6)) { /* ok, that's a start */ tcptr += 6; idx = 0; while (*tcptr) { while (*tcptr && ISLWS(*tcptr) || *tcptr == ',') tcptr++; if (!*tcptr || *tcptr == ';') break; if (idx >= RANGE_BYTE_MAX) { ErrorNoticed (rqptr, 0, "RANGE_BYTE_MAX exceeded", FI_LI); idx = 0; break; } if (isdigit(*tcptr)) { /* starting offset */ rbptr->First[idx] = atol(tcptr); while (*tcptr && isdigit(*tcptr)) tcptr++; if (*tcptr == '-') { /* ending offset */ tcptr++; rbptr->Last[idx] = atol(tcptr); while (*tcptr && isdigit(*tcptr)) tcptr++; } idx++; } else if (*tcptr == '-') { /* negative offset (from the end-of-file) */ rbptr->Last[idx] = atol(tcptr); tcptr++; while (*tcptr && isdigit(*tcptr)) tcptr++; idx++; } else { /* range format error */ idx = 0; break; } } rbptr->Total = idx; if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD_REQUEST)) { WatchDataFormatted ("!UL range!%s\n", idx); for (idx = 0; idx < rbptr->Total; idx++) WatchDataFormatted ("!UL !SL - !SL\n", idx, rbptr->First[idx], rbptr->Last[idx]); } } continue; } /************/ /* Referer: */ /************/ if (TOUP(cptr[0]) == 'R' && cptr[7] == ':' && strsame (cptr, "Referer:", 8)) { rqptr->rqHeader.RefererFieldPtr = tcptr; rqptr->rqHeader.RefererPtr = tcptr + NameLength; continue; } /*****************************/ /* Sec-WebSocket-Extensions: */ /*****************************/ if (TOUP(cptr[0]) == 'S' && cptr[24] == ':' && strsame (cptr, "Sec-WebSocket-Extensions:", 25)) { rqptr->rqHeader.SecWebSocketExtensionsFieldPtr = tcptr; rqptr->rqHeader.SecWebSocketExtensionsPtr = tcptr + NameLength; continue; } /**********************/ /* Sec-WebSocket-Key: */ /**********************/ if (TOUP(cptr[0]) == 'S' && cptr[17] == ':' && strsame (cptr, "Sec-WebSocket-Key:", 18)) { rqptr->rqHeader.SecWebSocketKeyFieldPtr = tcptr; rqptr->rqHeader.SecWebSocketKeyPtr = tcptr + NameLength; continue; } /***************************/ /* Sec-WebSocket-Protocol: */ /***************************/ if (TOUP(cptr[0]) == 'S' && cptr[22] == ':' && strsame (cptr, "Sec-WebSocket-Protocol:", 23)) { rqptr->rqHeader.SecWebSocketProtocolFieldPtr = tcptr; rqptr->rqHeader.SecWebSocketProtocolPtr = tcptr + NameLength; continue; } /**************************/ /* Sec-WebSocket-Version: */ /**************************/ if (TOUP(cptr[0]) == 'S' && cptr[21] == ':' && strsame (cptr, "Sec-WebSocket-Version:", 22)) { rqptr->rqHeader.SecWebSocketVersionFieldPtr = tcptr; rqptr->rqHeader.SecWebSocketVersionPtr = tcptr + NameLength; rqptr->rqHeader.WebSocketVersion = atoi(rqptr->rqHeader.SecWebSocketVersionPtr); continue; } /***********************/ /* Trailer: (HTTP/1.1) */ /***********************/ if (TOUP(cptr[0]) == 'T' && cptr[7] == ':' && strsame (cptr, "Trailer:", 8)) { rqptr->rqHeader.TrailerFieldPtr = tcptr; rqptr->rqHeader.TrailerPtr = tcptr + NameLength; continue; } /*********************************/ /* Transfer-Encoding: (HTTP/1.1) */ /*********************************/ if (TOUP(cptr[0]) == 'T' && cptr[17] == ':' && strsame (cptr, "Transfer-encoding:", 18)) { rqptr->rqHeader.TransferEncodingFieldPtr = tcptr; rqptr->rqHeader.TransferEncodingPtr = tcptr + NameLength; tcptr += NameLength; if (strsame (tcptr, "chunked", 7)) rqptr->rqHeader.TransferEncodingChunked = true; if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD_REQUEST)) WatchDataFormatted ("chunked:!&B\n", rqptr->rqHeader.TransferEncodingChunked); continue; } /************/ /* Upgrade: */ /************/ if (TOUP(cptr[0]) == 'U' && cptr[7] == ':' && strsame (cptr, "Upgrade:", 8)) { rqptr->rqHeader.UpgradeFieldPtr = tcptr; rqptr->rqHeader.UpgradePtr = tcptr + NameLength; tcptr += NameLength; if (strsame (tcptr, "WebSocket", 9)) rqptr->rqHeader.UpgradeWebSocket = true; else if (strsame (tcptr, "WASD-tunnel", 11)) rqptr->rqHeader.UpgradeWASDtunnel = true; continue; } /***************/ /* User-Agent: */ /***************/ if (TOUP(cptr[0]) == 'U' && cptr[10] == ':' && strsame (cptr, "User-Agent:", 11)) { rqptr->rqHeader.UserAgentFieldPtr = tcptr; rqptr->rqHeader.UserAgentPtr = tcptr + NameLength; /* occasionally need to accomodate our old friend Netscape 3.03 */ tcptr += NameLength; if (MATCH0 (tcptr, "Mozilla/3.", 10) && strstr (tcptr, "VMS")) rqptr->rqHeader.VmsNavigatorGold = true; else rqptr->rqHeader.VmsNavigatorGold = false; continue; } /********************/ /* X-Forwarded-For: */ /********************/ if (TOUP(cptr[0]) == 'X' && cptr[15] == ':' && strsame (cptr, "X-Forwarded-For:", 16)) { rqptr->rqHeader.XForwardedForFieldPtr = tcptr; rqptr->rqHeader.XForwardedForPtr = tcptr + NameLength; continue; } if (WebDavEnabled) { /********************************/ /* WebDAV request header fields */ /********************************/ if (TOUP(cptr[0]) == 'D' && cptr[5] == ':' && strsame (cptr, "Depth:", 6)) { /*********/ /* depth */ /*********/ rqptr->rqHeader.WebDavDepthFieldPtr = tcptr; rqptr->rqHeader.WebDavDepthPtr = tcptr + NameLength; tcptr += NameLength; if (*tcptr == '0') rqptr->rqHeader.WebDavDepth = WEBDAV_DEPTH_ZERO; else if (*tcptr == '1') rqptr->rqHeader.WebDavDepth = WEBDAV_DEPTH_ONE; else if (strsame (tcptr, "infinity", 8)) rqptr->rqHeader.WebDavDepth = WEBDAV_DEPTH_INFINITY; else { InstanceGblSecIncrLong (&AccountingPtr->RequestErrorCount); rqptr->rqResponse.HttpStatus = 400; ErrorGeneral (rqptr, MsgFor(rqptr,MSG_REQUEST_FORMAT), FI_LI); return (false); } if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD_REQUEST)) WatchDataFormatted ("!UL\n", rqptr->rqHeader.WebDavDepth); continue; } if (TOUP(cptr[0]) == 'D' && cptr[11] == ':' && strsame (cptr, "Destination:", 12)) { /***************/ /* destination */ /***************/ rqptr->rqHeader.WebDavDestinationFieldPtr = tcptr; rqptr->rqHeader.WebDavDestinationPtr = tcptr + NameLength; if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD_REQUEST)) WatchDataFormatted ("!&Z\n", rqptr->rqHeader.WebDavDestinationPtr); continue; } if (TOUP(cptr[0]) == 'I' && cptr[2] == ':' && strsame (cptr, "If:", 3)) { /******/ /* if */ /******/ rqptr->rqHeader.WebDavIfFieldPtr = tcptr; rqptr->rqHeader.WebDavIfPtr = tcptr + NameLength; if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD_REQUEST)) WatchDataFormatted ("!&Z\n", rqptr->rqHeader.WebDavIfPtr); continue; } if (TOUP(cptr[0]) == 'L' && cptr[10] == ':' && strsame (cptr, "Lock-Token:", 11)) { /**************/ /* lock token */ /**************/ rqptr->rqHeader.WebDavLockTokenFieldPtr = tcptr; rqptr->rqHeader.WebDavLockTokenPtr = tcptr + NameLength; if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD_REQUEST)) WatchDataFormatted ("!&Z\n", rqptr->rqHeader.WebDavLockTokenPtr); DavWebMicrosoftMunge2 (rqptr); continue; } if (TOUP(cptr[0]) == 'O' && cptr[9] == ':' && strsame (cptr, "Overwrite:", 10)) { /*************/ /* overwrite */ /*************/ rqptr->rqHeader.WebDavOverwriteFieldPtr = tcptr; tcptr += NameLength; if (TOUP(*tcptr) == 'T') rqptr->rqHeader.WebDavOverwrite = true; else if (TOUP(*tcptr) == 'F') rqptr->rqHeader.WebDavOverwrite = false; else { InstanceGblSecIncrLong (&AccountingPtr->RequestErrorCount); rqptr->rqResponse.HttpStatus = 400; ErrorGeneral (rqptr, MsgFor(rqptr,MSG_REQUEST_FORMAT), FI_LI); return (false); } if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD_REQUEST)) WatchDataFormatted ("!&B\n", rqptr->rqHeader.WebDavOverwrite); continue; } if (TOUP(cptr[0]) == 'T' && cptr[7] == ':' && strsame (cptr, "Timeout:", 8)) { /***********/ /* timeout */ /***********/ rqptr->rqHeader.WebDavTimeoutFieldPtr = tcptr; rqptr->rqHeader.WebDavTimeoutPtr = tcptr + NameLength; if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD_REQUEST)) WatchDataFormatted ("!&Z\n", rqptr->rqHeader.WebDavTimeoutPtr); continue; } if (TOUP(cptr[0]) == 'T' && cptr[9] == ':' && strsame (cptr, "Translate:", 10)) { /************************/ /* translate (de facto) */ /************************/ rqptr->rqHeader.WebDavTranslateFieldPtr = tcptr; tcptr += NameLength; rqptr->rqHeader.WebDavTranslatePtr = tcptr; if (TOUP(*tcptr) == 'F') rqptr->rqHeader.WebDavTranslateFalse = true; if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD_REQUEST)) WatchDataFormatted ("!&Z\n", rqptr->rqHeader.WebDavTranslatePtr); continue; } /*****************************/ /* X-Expected-Entity-Length: */ /*****************************/ /* an OSX (Apple) quirk */ if (TOUP(cptr[0]) == 'X' && cptr[24] == ':' && strsame (cptr, "X-Expected-Entity-Length:", 25)) { rqptr->rqHeader.XExpectedEntityLengthFieldPtr = tcptr; rqptr->rqHeader.XExpectedEntityLengthPtr = tcptr + NameLength; tcptr += NameLength; if (isdigit(*tcptr)) { rqptr->rqHeader.XExpectedEntityLength = atoi (tcptr); if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD_REQUEST)) WatchDataFormatted ("!UL\n", rqptr->rqHeader.XExpectedEntityLength); continue; } InstanceGblSecIncrLong (&AccountingPtr->RequestErrorCount); rqptr->rqResponse.HttpStatus = 400; ErrorGeneral (rqptr, MsgFor(rqptr,MSG_REQUEST_FORMAT), FI_LI); return (false); continue; } } /***************************/ /* unrecognised field name */ /***************************/ if (rqptr->rqHeader.UnknownFieldsCount < REQUEST_UNKNOWN_FIELDS_MAX) { rqptr->rqHeader.UnknownFieldsPtr[ rqptr->rqHeader.UnknownFieldsCount++] = tcptr; continue; } else if (!RequestUnknownFieldsMaxExceeded++) { ErrorNoticed (rqptr, 0, "REQUEST_UNKNOWN_FIELDS_MAX exceeded", FI_LI); FaoToStdout ("%HTTPD-I-HEADER, !20%D, !AZ, !UL bytes\n \\!AZ\\\n", 0, rqptr->rqClient.Lookup.HostName, rqptr->rqHeader.RequestHeaderLength, rqptr->rqHeader.RequestHeaderPtr); } } if (rqptr->rqHeader.TransferEncodingFieldPtr) { /* If a message is received with both a Transfer-Encoding header field and a Content-Length header field, the latter MUST be ignored. RFC2616; 4.4 Message Length */ rqptr->rqHeader.ContentLengthFieldPtr = rqptr->rqHeader.ContentLengthPtr = NULL; rqptr->rqHeader.ContentLength = 0; } /* populate the array of pointers to the request fields */ rfidx = 0; rfptr = &rqptr->rqHeader.RequestFieldsPtr; /* When adding specific fields here do not forget to adjust macro REQUEST_KNOWN_FIELDS in WASD.H! There are currently 30 known fields processed here. */ if (cptr = rqptr->rqHeader.AcceptFieldPtr) rfptr[rfidx++] = cptr; if (cptr = rqptr->rqHeader.AcceptCharsetFieldPtr) rfptr[rfidx++] = cptr; if (cptr = rqptr->rqHeader.AcceptEncodingFieldPtr) rfptr[rfidx++] = cptr; if (cptr = rqptr->rqHeader.AcceptLangFieldPtr) rfptr[rfidx++] = cptr; if (cptr = rqptr->rqHeader.AuthorizationFieldPtr) rfptr[rfidx++] = cptr; if (cptr = rqptr->rqHeader.CacheControlFieldPtr) rfptr[rfidx++] = cptr; if (cptr = rqptr->rqHeader.ConnectionFieldPtr) rfptr[rfidx++] = cptr; if (cptr = rqptr->rqHeader.ContentEncodingFieldPtr) rfptr[rfidx++] = cptr; if (cptr = rqptr->rqHeader.ContentLengthFieldPtr) rfptr[rfidx++] = cptr; if (cptr = rqptr->rqHeader.ContentTypeFieldPtr) rfptr[rfidx++] = cptr; if (cptr = rqptr->rqHeader.CookieFieldPtr) rfptr[rfidx++] = cptr; if (cptr = rqptr->rqHeader.DoNotTrackFieldPtr) rfptr[rfidx++] = cptr; if (cptr = rqptr->rqHeader.ETagFieldPtr) rfptr[rfidx++] = cptr; if (cptr = rqptr->rqHeader.ExpectFieldPtr) rfptr[rfidx++] = cptr; if (cptr = rqptr->rqHeader.ForwardedFieldPtr) rfptr[rfidx++] = cptr; if (cptr = rqptr->rqHeader.HostFieldPtr) rfptr[rfidx++] = cptr; if (cptr = rqptr->rqHeader.IfMatchFieldPtr) rfptr[rfidx++] = cptr; if (cptr = rqptr->rqHeader.IfNoneMatchFieldPtr) rfptr[rfidx++] = cptr; if (cptr = rqptr->rqHeader.IfModifiedSinceFieldPtr) rfptr[rfidx++] = cptr; if (cptr = rqptr->rqHeader.IfUnModifiedSinceFieldPtr) rfptr[rfidx++] = cptr; if (cptr = rqptr->rqHeader.IfRangeFieldPtr) rfptr[rfidx++] = cptr; if (cptr = rqptr->rqHeader.KeepAliveFieldPtr) rfptr[rfidx++] = cptr; if (cptr = rqptr->rqHeader.MaxForwardsFieldPtr) rfptr[rfidx++] = cptr; if (cptr = rqptr->rqHeader.OriginFieldPtr) rfptr[rfidx++] = cptr; if (cptr = rqptr->rqHeader.PragmaFieldPtr) rfptr[rfidx++] = cptr; if (cptr = rqptr->rqHeader.ProxyAuthorizationFieldPtr) rfptr[rfidx++] = cptr; if (cptr = rqptr->rqHeader.RangeFieldPtr) rfptr[rfidx++] = cptr; if (cptr = rqptr->rqHeader.RefererFieldPtr) rfptr[rfidx++] = cptr; if (cptr = rqptr->rqHeader.TrailerFieldPtr) rfptr[rfidx++] = cptr; if (cptr = rqptr->rqHeader.TransferEncodingFieldPtr) rfptr[rfidx++] = cptr; if (cptr = rqptr->rqHeader.UpgradeFieldPtr) rfptr[rfidx++] = cptr; if (cptr = rqptr->rqHeader.UserAgentFieldPtr) rfptr[rfidx++] = cptr; if (cptr = rqptr->rqHeader.SecWebSocketExtensionsFieldPtr) rfptr[rfidx++] = cptr; if (cptr = rqptr->rqHeader.SecWebSocketKeyFieldPtr) rfptr[rfidx++] = cptr; if (cptr = rqptr->rqHeader.SecWebSocketProtocolFieldPtr) rfptr[rfidx++] = cptr; if (cptr = rqptr->rqHeader.SecWebSocketVersionFieldPtr) rfptr[rfidx++] = cptr; if (cptr = rqptr->rqHeader.XForwardedForFieldPtr) rfptr[rfidx++] = cptr; if (WebDavEnabled) { if (cptr = rqptr->rqHeader.WebDavDepthFieldPtr) rfptr[rfidx++] = cptr; if (cptr = rqptr->rqHeader.WebDavDestinationFieldPtr) rfptr[rfidx++] = cptr; if (cptr = rqptr->rqHeader.WebDavIfFieldPtr) rfptr[rfidx++] = cptr; if (cptr = rqptr->rqHeader.WebDavLockTokenFieldPtr) rfptr[rfidx++] = cptr; if (cptr = rqptr->rqHeader.WebDavOverwriteFieldPtr) rfptr[rfidx++] = cptr; if (cptr = rqptr->rqHeader.WebDavTimeoutFieldPtr) rfptr[rfidx++] = cptr; if (cptr = rqptr->rqHeader.WebDavTranslateFieldPtr) rfptr[rfidx++] = cptr; if (cptr = rqptr->rqHeader.XExpectedEntityLengthFieldPtr) rfptr[rfidx++] = cptr; } /* tack on the unknown fields */ for (idx = 0; idx < rqptr->rqHeader.UnknownFieldsCount; idx++) { if (rfidx < REQUEST_FIELDS_MAX) rfptr[rfidx++] = rqptr->rqHeader.UnknownFieldsPtr[idx]; else if (!RequestFieldsMaxExceeded++) ErrorNoticed (rqptr, 0, "REQUEST_FIELDS_MAX exceeded", FI_LI); } rqptr->rqHeader.RequestFieldsCount = rfidx; if (WATCHING(rqptr) && WATCH_CATEGORY(WATCH_REQUEST_HEADER)) { WatchThis (rqptr, FI_LI, WATCH_REQUEST_HEADER, "!UL field!%s, !UL unknown", rqptr->rqHeader.RequestFieldsCount, rqptr->rqHeader.UnknownFieldsCount); rfptr = &rqptr->rqHeader.RequestFieldsPtr; for (rfidx = 0; rfidx < rqptr->rqHeader.RequestFieldsCount; rfidx++) WatchDataFormatted ("!UL. {!UL}!AZ\n", rfidx+1, strlen(rfptr[rfidx]), rfptr[rfidx]); } return (true); } /*****************************************************************************/ /* If a file specification does not contain a file name the function looks for a home page in the directory. If no home page found it generates a directory listing ("Index of" documents). If the file specification contains a wildcard and there is no query string a directory listing (index) is generated. If there is a query string it must begin with the literal "?httpd=index" for a directory listing (index) to be generated (this allows directives in the query string to be passed to the directory listing functions). If it does not begin with this literal the default query (search) script is invoked. Detects file types that have an associated script. If a script is returned by ConfigContentType(), and the file specification does not include a specific version number (even such as ";0"), an automatic redirection is generated so that the specified document is processed by the script, and the output from that returned to the client. If it does contain a version number the file is always directly returned to the client. */ RequestExecute (REQUEST_STRUCT *rqptr) { int status; char *cptr, *mpptr; /*********/ /* begin */ /*********/ if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD_REQUEST)) WatchThis (rqptr, FI_LI, WATCH_MOD_REQUEST, "RequestExecute()"); if (rqptr->rqHeader.PathInfoPtr[0] == '/') { if (!ServiceFindVirtual (rqptr)) { /* virtual service was not found */ if (Config.cfServer.ServiceNotFoundUrl[0]) { /* configuration specifies a redirection URL */ rqptr->rqResponse.LocationPtr = Config.cfServer.ServiceNotFoundUrl; RequestEnd (rqptr); return; } rqptr->UnknownVirtualService = true; } /* re-set the error reporting mechanism for the different service */ if (rqptr->ServicePtr->ErrorReportPath[0]) rqptr->rqResponse.ErrorReportByRedirect = true; else rqptr->rqResponse.ErrorReportByRedirect = false; } if (WATCHING(rqptr) && WATCH_CATEGORY(WATCH_REQUEST)) { WatchThis (rqptr, FI_LI, WATCH_REQUEST, "!AZ !&P", rqptr->rqHeader.MethodName, rqptr->rqHeader.RequestUriPtr); if (rqptr->NotePadPtr && rqptr->NotePadPtr[0]) WatchThis (rqptr, FI_LI, WATCH_REQUEST, "NOTEPAD !AZ", rqptr->NotePadPtr); } /* ensure any old values are ignored, in case its a redirection */ rqptr->rqResponse.LocationPtr = NULL; /************************/ /* map the request path */ /************************/ rqptr->RequestMappedFile[0] = rqptr->ScriptName[0] = rqptr->RequestMappedScript[0] = '\0'; /* Map the path, converting it to a VMS file path (or script file name) and returning a pointer to a static buffer containing the mapped path. */ mpptr = MapUrl_Map (rqptr->rqHeader.PathInfoPtr, sizeof(rqptr->rqHeader.PathInfoPtr), rqptr->RequestMappedFile, sizeof(rqptr->RequestMappedFile), rqptr->ScriptName, sizeof(rqptr->ScriptName), rqptr->RequestMappedScript, sizeof(rqptr->RequestMappedScript), rqptr->RequestMappedRunTime, sizeof(rqptr->RequestMappedRunTime), &rqptr->PathOds, rqptr, &rqptr->rqPathSet); /* immediately after mapping in case of SET timeout rules */ HttpdTimerSet (rqptr, TIMER_OUTPUT, 0); if (mpptr[0] == '\1' && mpptr[1]) { /***********************/ /* mapping redirection */ /***********************/ rqptr->rqResponse.LocationPtr = VmGetHeap (rqptr, strlen(mpptr+1)+1); strcpy (rqptr->rqResponse.LocationPtr, mpptr+1); RequestEnd (rqptr); return; } if (rqptr->rqPathSet.Alert == MAPURL_PATH_ALERT_MAP) RequestAlert (rqptr); /* buffer the mapped path (mpptr+1 allows error and non-error mappings) */ rqptr->MappedPathLength = strlen(mpptr+1)+1; rqptr->MappedPathPtr = VmGetHeap (rqptr, rqptr->MappedPathLength); memcpy (rqptr->MappedPathPtr, mpptr, rqptr->MappedPathLength); if (!mpptr[0] && mpptr[1]) { /*****************/ /* mapping error */ /*****************/ /* if this path has any request throttling limits */ if (rqptr->rqPathSet.ThrottleSet) { status = ThrottleBegin (rqptr); if (VMSnok (status)) { /* either on the waiting list or waiting list exceeded */ if (status == SS$_ABORT) RequestEnd (rqptr); return; } /* continue to process the request (is on the active list) */ } RequestExecuteMappingError (rqptr); return; } if (rqptr->rqPathSet.ChangeServicePtr) { /******************/ /* change service */ /******************/ if (!ServiceChange (rqptr, rqptr->rqPathSet.ChangeServicePtr)) { RequestEnd (rqptr); return; } } for (cptr = rqptr->RequestMappedFile; *cptr && *cptr != ':'; cptr++); if (SAME2(cptr,'::')) { /************************/ /* DECnet not supported */ /************************/ /* if a node is part of the VMS file name then it's not supported */ rqptr->rqResponse.HttpStatus = 501; rqptr->rqResponse.ErrorTextPtr = rqptr->rqHeader.PathInfoPtr; rqptr->rqResponse.ErrorOtherTextPtr = rqptr->RequestMappedFile; ErrorVmsStatus (rqptr, RMS$_SUPPORT, FI_LI); RequestEnd (rqptr); return; } #ifdef ODS_EXTENDED if (OdsExtended) rqptr->PathOdsExtended = (rqptr->PathOds == MAPURL_PATH_ODS_5); else #endif /* ODS_EXTENDED */ rqptr->PathOdsExtended = 0; /* MapUrl_Map() returns CGIplus mappings indicated by a leading '+' */ if (rqptr->ScriptName[0] == '+') { rqptr->IsCgiPlusScript = true; rqptr->ScriptName[0] = '/'; } else rqptr->IsCgiPlusScript = false; if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD_REQUEST)) WatchDataFormatted ("!&Z !&Z !&Z !&B !&Z !&Z !&Z\n", rqptr->rqHeader.PathInfoPtr, rqptr->RequestMappedFile, rqptr->ScriptName, rqptr->IsCgiPlusScript, rqptr->RequestMappedScript, rqptr->RequestMappedRunTime, mpptr); /* if it's been SET as an extension method by a mapping rule */ if (rqptr->rqPathSet.MapExtensionMethod) rqptr->rqHeader.Method = HTTP_METHOD_EXTENSION; /******************/ /* proxy request? */ /******************/ if (!rqptr->ScriptName[0] && (rqptr->rqHeader.Method == HTTP_METHOD_CONNECT || MATCH7 (mpptr, "http://") || MATCH8 (mpptr, "https://") || MATCH6 (mpptr, "ftp://")) || MATCH6 (mpptr, "raw://")) { /* looks awfully like a scheme */ rqptr->ProxyRequest = true; /* if it's a trace and there are no more hops available */ if (rqptr->rqHeader.Method == HTTP_METHOD_TRACE || rqptr->rqHeader.MaxForwards) rqptr->ProxyRequest = false; } /*********************************/ /* authorization of request path */ /*********************************/ if (rqptr->ProxyRequest) { if (rqptr->ServicePtr->LocalAuthRequired) rqptr->rqAuth.RequestAuthorizationPtr = rqptr->rqHeader.AuthorizationPtr; else if (rqptr->ServicePtr->ProxyAuthRequired) rqptr->rqAuth.RequestAuthorizationPtr = rqptr->rqHeader.ProxyAuthorizationPtr; else if (rqptr->rqPathSet.ProxyReverseVerify) rqptr->rqAuth.RequestAuthorizationPtr = rqptr->rqHeader.AuthorizationPtr; else { /* if this proxy has any request throttling limits */ if (rqptr->rqPathSet.ThrottleSet) { status = ThrottleBegin (rqptr); if (VMSnok (status)) { /* either on waiting list or waiting list has been exceeded */ if (status == SS$_ABORT) RequestEnd (rqptr); return; } /* continue to process the request (is on the active list) */ } ProxyRequestBegin (rqptr); return; } } else rqptr->rqAuth.RequestAuthorizationPtr = rqptr->rqHeader.AuthorizationPtr; if (!rqptr->rqPathSet.AuthorizeMapped) Authorize (rqptr, rqptr->rqHeader.PathInfoPtr, rqptr->rqHeader.PathInfoLength, rqptr->rqAuth.Protect1Ptr, rqptr->rqAuth.Protect1Length, &RequestExecutePostAuth1); else if (rqptr->ScriptName[0]) Authorize (rqptr, rqptr->ScriptName, strlen(rqptr->ScriptName), rqptr->rqAuth.Protect1Ptr, rqptr->rqAuth.Protect1Length, &RequestExecutePostAuth1); else Authorize (rqptr, rqptr->MappedPathPtr, rqptr->MappedPathLength, rqptr->rqAuth.Protect1Ptr, rqptr->rqAuth.Protect1Length, &RequestExecutePostAuth1); if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD_REQUEST)) WatchDataFormatted ("!&S\n", rqptr->rqAuth.FinalStatus); if (VMSnok (rqptr->rqAuth.FinalStatus)) { /* if asynchronous authentication is underway then just wait for it */ if (rqptr->rqAuth.FinalStatus == AUTH_PENDING) return; RequestEnd (rqptr); return; } /* authorization completed, continue to process */ RequestExecutePostAuth1 (rqptr); } /*****************************************************************************/ /* An error message has been returnd by MapUrl_Map(). An independent function so that it can be called directly after initial mapping if no throttling involved or from RequestExecutePostThrottle() if throttle was specified. */ RequestExecuteMappingError (REQUEST_STRUCT *rqptr) { int status; char *mpptr; /*********/ /* begin */ /*********/ if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD_REQUEST)) WatchThis (rqptr, FI_LI, WATCH_MOD_REQUEST, "RequestExecuteMappingError() !&Z", rqptr->MappedPathPtr+1); /* MapUrl_Map() returns errors with a leading null character */ mpptr = rqptr->MappedPathPtr + 1; if (isdigit (*mpptr)) { /* an explicitly mapped error (e.g. pass * "403 uh-ahh!") */ rqptr->rqResponse.HttpStatus = atol(mpptr); while (*mpptr && isdigit(*mpptr)) mpptr++; while (*mpptr && ISLWS(*mpptr)) mpptr++; if (rqptr->rqResponse.HttpStatus >= 300 && rqptr->rqResponse.HttpStatus <= 399) { /* redirection, message "text" should be the location */ rqptr->rqResponse.LocationPtr = mpptr; RequestEnd (rqptr); return; } if (rqptr->rqResponse.HttpStatus >= 400 && rqptr->rqResponse.HttpStatus <= 599) { ErrorGeneral (rqptr, mpptr, FI_LI); RequestEnd (rqptr); return; } /* no point to codes other 3nn/4nn/5nn, use to abort connection */ NetCloseSocket (rqptr); RequestEnd (rqptr); return; } else { /* some implicitly generated message */ if (!strcmp (mpptr, MAPURL_USER_RULE_FORBIDDEN_MSG+1)) { /* convert USER mapping rule forbidden into directory not found */ rqptr->rqResponse.HttpStatus = 404; rqptr->rqResponse.ErrorTextPtr = rqptr->rqHeader.PathInfoPtr; ErrorVmsStatus (rqptr, RMS$_DNF, FI_LI); RequestEnd (rqptr); return; } /* just a rule-mapping message */ rqptr->rqResponse.HttpStatus = 403; ErrorGeneral (rqptr, mpptr, FI_LI); RequestEnd (rqptr); return; } } /*****************************************************************************/ /* This function continues to initiate the request processing, either called directly from RequestExecute() or as an AST after asynchronous authentication. */ RequestExecutePostAuth1 (REQUEST_STRUCT *rqptr) { int status; char *cptr, *sptr; /*********/ /* begin */ /*********/ if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD_REQUEST)) WatchThis (rqptr, FI_LI, WATCH_MOD_REQUEST, "RequestExecutePostAuth1() !&F !&S", &RequestExecutePostAuth1, rqptr->rqAuth.FinalStatus); if (VMSnok (rqptr->rqAuth.FinalStatus)) { RequestEnd (rqptr); return; } if (Watch.Category && Watch.Category != WATCH_ONE_SHOT_CAT) WatchFilterRealmUser (rqptr); if (rqptr->rqPathSet.Alert == MAPURL_PATH_ALERT_AUTH) RequestAlert (rqptr); /*****************************/ /* authorized proxy request? */ /*****************************/ if (rqptr->ProxyRequest) { if ((rqptr->ServicePtr->LocalAuthRequired || rqptr->ServicePtr->ProxyAuthRequired) && !rqptr->RemoteUser[0]) { /* authorization never occured! */ rqptr->rqResponse.HttpStatus = 403; ErrorGeneral (rqptr, MsgFor(rqptr,MSG_GENERAL_DISABLED), FI_LI); RequestEnd (rqptr); return; } RequestExecutePostAuth2 (rqptr); return; } /************************************/ /* handled internally by the server */ /************************************/ if (rqptr->rqHeader.Method == HTTP_METHOD_OPTIONS) { rqptr->rqCache.DoNotCache = true; ResponseOptions (rqptr); return; } if (rqptr->rqHeader.Method == HTTP_METHOD_TRACE) { if (Config.cfMisc.HttpTraceEnabled) { rqptr->rqCache.DoNotCache = true; ResponseTrace (rqptr); } else { rqptr->rqResponse.HttpStatus = 501; ErrorGeneral (rqptr, MsgFor(rqptr,MSG_REQUEST_METHOD), FI_LI); RequestEnd (rqptr); } return; } if (strsame (rqptr->rqHeader.RequestUriPtr, HTTPD_ADMIN, sizeof(HTTPD_ADMIN)-1)) { rqptr->rqCache.DoNotCache = rqptr->InternalRequest = true; AdminBegin (rqptr, &RequestEnd); return; } if (strsame (rqptr->rqHeader.RequestUriPtr, HTTPD_VS_ADMIN, sizeof(HTTPD_VS_ADMIN)-1)) { rqptr->rqCache.DoNotCache = rqptr->InternalRequest = true; AdminBegin (rqptr, &RequestEnd); return; } if (strsame (rqptr->rqHeader.RequestUriPtr, HTTPD_VERIFY, sizeof(HTTPD_VERIFY)-1)) { rqptr->rqCache.DoNotCache = rqptr->InternalRequest = true; ProxyVerifyRequest (rqptr, &RequestEnd); return; } if (strsame (rqptr->rqHeader.RequestUriPtr, INTERNAL_PASSWORD_CHANGE, sizeof(INTERNAL_PASSWORD_CHANGE)-1)) { rqptr->rqCache.DoNotCache = rqptr->InternalRequest = true; HTAdminBegin (rqptr, &RequestEnd); return; } if (strsame (rqptr->rqHeader.RequestUriPtr, INTERNAL_TRACK_CANCEL, sizeof(INTERNAL_TRACK_CANCEL)-1)) { rqptr->rqCache.DoNotCache = rqptr->InternalRequest = true; TrackCancelCookie (rqptr, &RequestEnd); return; } /******************************/ /* check for script execution */ /******************************/ if (rqptr->ScriptName[0]) { /* get the path derived from the script specification */ cptr = rqptr->rqHeader.PathInfoPtr; sptr = rqptr->ScriptName; /* kludge to allow for 'implied' scripts (e.g. "/path/info") */ if (!SAME2(sptr,'/\0')) { while (*sptr) { if (TOLO(*sptr) != TOLO(*cptr)) break; sptr++; cptr++; } if (!*sptr) { for (sptr = rqptr->rqHeader.PathInfoPtr; *cptr; *sptr++ = *cptr++); *sptr++ = '\0'; rqptr->rqHeader.PathInfoLength = sptr - rqptr->rqHeader.PathInfoPtr; } } if (!rqptr->rqPathSet.AuthorizeOnce) { /******************************************/ /* authorization of script path parameter */ /******************************************/ if (!rqptr->rqPathSet.AuthorizeMapped) Authorize (rqptr, rqptr->rqHeader.PathInfoPtr, rqptr->rqHeader.PathInfoLength, rqptr->rqAuth.Protect2Ptr, rqptr->rqAuth.Protect2Length, &RequestExecutePostAuth2); else Authorize (rqptr, rqptr->MappedPathPtr, rqptr->MappedPathLength, rqptr->rqAuth.Protect2Ptr, rqptr->rqAuth.Protect2Length, &RequestExecutePostAuth2); if (VMSnok (rqptr->rqAuth.FinalStatus)) { /* if authentication by an agent underway then just wait for it */ if (rqptr->rqAuth.FinalStatus == AUTH_PENDING) return; RequestEnd (rqptr); return; } } } /***********************/ /* continue non-script */ /***********************/ RequestExecutePostAuth2 (rqptr); } /*****************************************************************************/ /* This function continues to initiate the request processing, either called directly from RequestExecutePostAuth() or as an AST after asynchronous authentication. */ RequestExecutePostAuth2 (REQUEST_STRUCT *rqptr) { int status; /*********/ /* begin */ /*********/ if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD_REQUEST)) WatchThis (rqptr, FI_LI, WATCH_MOD_REQUEST, "RequestExecutePostAuth2() !&F !&S", &RequestExecutePostAuth2, rqptr->rqAuth.FinalStatus); if (VMSnok (rqptr->rqAuth.FinalStatus)) { RequestEnd (rqptr); return; } if (rqptr->WebDavRequest && !rqptr->RemoteUser[0]) { /* WebDAV requests MUST have authorization applied! */ if (WATCHING(rqptr) && WATCH_CATEGORY(WATCH_REQUEST)) WatchThis (rqptr, FI_LI, WATCH_REQUEST, "WEBDAV paths must have an authorization rule"); ErrorVmsStatus (rqptr, SS$_NOPRIV, FI_LI); RequestEnd (rqptr); return; } if (Watch.Category && Watch.Category != WATCH_ONE_SHOT_CAT) WatchFilterRealmUser (rqptr); if (rqptr->rqPathSet.Alert == MAPURL_PATH_ALERT_AUTH) RequestAlert (rqptr); /* if this path has any request throttling limits */ if (rqptr->rqPathSet.ThrottleSet) { status = ThrottleBegin (rqptr); if (VMSnok (status)) { /* either on the waiting list or waiting list has been exceeded */ if (status == SS$_ABORT) RequestEnd (rqptr); return; } /* continue to process the request (is on the active list) */ } RequestExecutePostThrottle (rqptr); } /*****************************************************************************/ /* Called by RequestExecutePostAuth2() after throttle is assessed against the path and not initiated, or as an AST initiated by ThrottleRelease() after throttling has been employed against the request. */ RequestExecutePostThrottle (REQUEST_STRUCT *rqptr) { BOOL WildcardName; int status; char *cptr, *sptr, *zptr; char Md5String [2048]; /*********/ /* begin */ /*********/ if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD_REQUEST)) WatchThis (rqptr, FI_LI, WATCH_MOD_REQUEST, "RequestExecutePostThrottle() !&F", &RequestExecutePostThrottle); /* MapUrl_Map() returns errors with a leading null character */ if (!rqptr->MappedPathPtr[0] && rqptr->MappedPathPtr[1]) { /******************************/ /* mapped but throttled error */ /******************************/ RequestExecuteMappingError (rqptr); return; } if (rqptr->ProxyRequest) { /*****************/ /* proxy request */ /*****************/ ProxyRequestBegin (rqptr); return; } /***********************/ /* MD5 hash of request */ /***********************/ if (rqptr->ScriptName[0]) { /* non-file hashes are generated using the service plus URI */ zptr = (sptr = Md5String) + sizeof(Md5String); for (cptr = rqptr->ServicePtr->ServerHostPort; *cptr && sptr < zptr; *sptr++ = *cptr++); for (cptr = rqptr->rqHeader.RequestUriPtr; *cptr && *cptr != '?' && sptr < zptr; *sptr++ = *cptr++); if (rqptr->rqPathSet.CacheQuery) while (*cptr && sptr < zptr) *sptr++ = *cptr++; if (sptr >= zptr) { ErrorNoticed (rqptr, SS$_RESULTOVF, "Md5String", FI_LI); ErrorGeneralOverflow (rqptr, FI_LI); RequestEnd (rqptr); return; } *sptr = '\0'; Md5Digest (Md5String, sptr-Md5String, &rqptr->Md5HashPath); } else { /* file hash is generated using the path equivalent of VMS file spec */ Md5Digest (rqptr->MappedPathPtr, rqptr->MappedPathLength, &rqptr->Md5HashPath); } status = CacheSearch (rqptr); if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD_REQUEST)) WatchThis (rqptr, FI_LI, WATCH_MOD_REQUEST, "CacheSearch() !&S", status); /* success status indicates the request is being supplied from cache */ if (VMSok (status)) return; if (rqptr->ScriptName[0]) { /**********/ /* script */ /**********/ RequestScript (rqptr, rqptr->RequestMappedScript, rqptr->RequestMappedFile, rqptr->RequestMappedRunTime); return; } if (rqptr->rqHeader.UpgradeWebSocket) { /*********************/ /* WebSocket request */ /*********************/ /* should have been take care of by now! */ rqptr->rqResponse.HttpStatus = 500; RequestEnd (rqptr); return; } /* check for wildcard in file name or type */ for (cptr = rqptr->MappedPathPtr; *cptr; cptr++); sptr = cptr; while (cptr > rqptr->MappedPathPtr && *cptr != ';' && *cptr != '/') cptr--; WildcardName = false; if (*cptr == ';') { /* not interested in caching specific version, only version-less paths */ if (!cptr[1] || isdigit(cptr[1])) rqptr->rqCache.DoNotCache = true; /* check for wildcard characters in name or type */ while (cptr > rqptr->MappedPathPtr && *cptr != '/') { if (*cptr == '*' || *cptr == '%' || *cptr == '?') WildcardName = true; cptr--; } } /* now resolve content-type */ cptr = sptr; while (cptr > rqptr->MappedPathPtr && *cptr != '.' && *cptr != '/') cptr--; if (*cptr != '.') cptr = ""; ConfigContentType (&rqptr->rqContentInfo, cptr); /* if not resolving a language-specific document and GET or HEAD */ if (!(WildcardName || rqptr->rqCache.DoNotCache) && (rqptr->rqHeader.Method == HTTP_METHOD_GET || rqptr->rqHeader.Method == HTTP_METHOD_HEAD)) { /* as there is no file name specified this will just search the cache */ FileBegin (rqptr, &RequestEnd, &RequestExecutePostCache, &rqptr->Md5HashPath, NULL, NULL); return; } RequestExecutePostCache (rqptr); } /*****************************************************************************/ /* If a file was not found by searching the file cache using the mapped path hash as the key then this function is called as the file-not-found AST of the FileBegin() call in RequestExecutePostThrottle() above. */ RequestExecutePostCache (REQUEST_STRUCT *rqptr) { BOOL OfWebDav; int status, Count; char *cptr, *sptr, *MappedFilePtr; char MappedFile [ODS_MAX_FILE_NAME_LENGTH+1]; /*********/ /* begin */ /*********/ if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD_REQUEST)) WatchThis (rqptr, FI_LI, WATCH_MOD_REQUEST, "RequestExecutePostCache() !&F", &RequestExecutePostCache); MappedFilePtr = rqptr->RequestMappedFile; /* check for device and directory (minimum) */ cptr = MappedFilePtr; if (*cptr == ':') cptr = ""; while (*cptr && !SAME2(cptr,':[')) cptr++; if (*cptr) cptr += 2; if (*cptr == ']') cptr = ""; if (!*cptr) { /* didn't find a device and/or directory - forbidden */ if (WATCHING(rqptr) && WATCH_CATEGORY(WATCH_REQUEST)) WatchThis (rqptr, FI_LI, WATCH_REQUEST, "MAPPING provided NO DEVICE and/or DIRECTORY!!"); rqptr->rqResponse.HttpStatus = 500; rqptr->rqResponse.ErrorOtherTextPtr = rqptr->RequestMappedFile; ErrorVmsStatus (rqptr, RMS$_SYN, FI_LI); RequestEnd (rqptr); return; } OfWebDav = rqptr->WebDavRequest || rqptr->rqPathSet.WebDavProfile || rqptr->rqPathSet.WebDavRead || rqptr->rqPathSet.WebDavServer || rqptr->rqPathSet.WebDavWrite; if (OfWebDav) { /* is there a potentially UTF-8 bit pattern here? */ for (cptr = MappedFilePtr; *cptr && ((*cptr & 0xc0) != 0xc0); cptr++); if (*cptr) { /* some UTF-8 encode (e.g. KDE), others (e.g. MS WebFolders) do not */ strzcpy (MappedFile, rqptr->RequestMappedFile, sizeof(MappedFile)); if (ConvertFromUtf8 (MappedFile, -1, '$') > 0) MappedFilePtr = MappedFile; } } /* parse the file specification (must have device and directory!!) */ OdsParse (&rqptr->ParseOds, MappedFilePtr, 0, NULL, 0, NAM$M_SYNCHK, NULL, rqptr); if (VMSnok (status = rqptr->ParseOds.Fab.fab$l_sts)) { if (OfWebDav) { if (status == RMS$_SYN) { DavWebMicrosoftMunge1 (rqptr); if (rqptr->rqResponse.LocationPtr) { RequestEnd (rqptr); return; } } } rqptr->rqResponse.ErrorOtherTextPtr = rqptr->RequestMappedFile; ErrorVmsStatus (rqptr, status, FI_LI); RequestEnd (rqptr); return; } if (VMSnok (status = OdsParseTerminate (&rqptr->ParseOds))) { ErrorNoticed (rqptr, status, NULL, FI_LI); RequestEnd (rqptr); return; } if (WebDavEnabled) { /******************/ /* WebDAV enabled */ /******************/ if (rqptr->WebDavMethod) { DavWebRequest (rqptr); return; } } /* If the file name component comprises a leading hyphen followed by 20 digits (post v8.4.1, "-yyyymmddhhmmsshhnnnn-") or 16 digits (pre v8.4.2, "-yyyymmddhhmmsshh-") and a trailing hyphen, then consider it temporary and delete it on close. See comments in PUT.C. */ rqptr->DeleteOnClose = false; if (rqptr->ParseOds.NamNameLength == 22 && rqptr->ParseOds.NamNamePtr[0] == '-' && rqptr->ParseOds.NamNamePtr[21] == '-') { for (cptr = rqptr->ParseOds.NamNamePtr+1; isdigit(*cptr); cptr++); if (cptr == rqptr->ParseOds.NamNamePtr+21) { rqptr->DeleteOnClose = true; rqptr->rqResponse.PreExpired = PRE_EXPIRE_DELETE_ON_CLOSE; } } else if (rqptr->ParseOds.NamNameLength == 18 && rqptr->ParseOds.NamNamePtr[0] == '-' && rqptr->ParseOds.NamNamePtr[17] == '-') { for (cptr = rqptr->ParseOds.NamNamePtr+1; isdigit(*cptr); cptr++); if (cptr == rqptr->ParseOds.NamNamePtr+17) { rqptr->DeleteOnClose = true; rqptr->rqResponse.PreExpired = PRE_EXPIRE_DELETE_ON_CLOSE; } } /* all these "file write" methods are handled by the one set of functions */ if ((rqptr->rqHeader.Method == HTTP_METHOD_PUT || rqptr->rqHeader.Method == HTTP_METHOD_POST || rqptr->rqHeader.Method == HTTP_METHOD_DELETE) && !rqptr->RedirectErrorStatusCode) { /* when WebDAV is enabled PUT and DELETE are intercepted above */ PutBegin (rqptr, &RequestEnd); return; } if (rqptr->rqHeader.QueryStringLength && (TOLO(rqptr->rqHeader.QueryStringPtr[0]) == 'h' && strsame (rqptr->rqHeader.QueryStringPtr, "httpd=index", 11)) || (((rqptr->ParseOds.Nam_fnb & NAM$M_WILDCARD) || (rqptr->ParseOds.Nam_fnb & NAM$M_EXP_VER && rqptr->ParseOds.NamNamePtr == rqptr->ParseOds.NamTypePtr)) && !rqptr->rqHeader.QueryStringPtr[0])) { /******************************/ /* generate directory listing */ /******************************/ if ((Config.cfDir.WildcardEnabled || rqptr->rqPathSet.DirWildcard) && !rqptr->rqPathSet.DirNoWildcard) { if (rqptr->rqPathSet.IndexPtr) cptr = rqptr->rqPathSet.IndexPtr; else if (rqptr->rqHeader.QueryStringLength) cptr = rqptr->rqHeader.QueryStringPtr; else cptr = ""; DirBegin (rqptr, &RequestEnd, rqptr->ParseOds.ExpFileName, cptr, "", false); return; } else { rqptr->rqResponse.HttpStatus = 403; ErrorGeneral (rqptr, MsgFor(rqptr,MSG_GENERAL_DISABLED), FI_LI); RequestEnd (rqptr); return; } } if (!rqptr->ParseOds.NamNameLength && !rqptr->ParseOds.NamTypeLength && !rqptr->rqHeader.QueryStringLength) { /*********************************************/ /* no file name supplied, look for home page */ /*********************************************/ RequestHomePage (rqptr); return; } /********************************/ /* get the content-type details */ /********************************/ if (!isdigit(rqptr->ParseOds.NamVersionPtr[1]) && !(rqptr->rqHeader.QueryStringLength && TOLO(rqptr->rqHeader.QueryStringPtr[0]) == 'h' && strsame (rqptr->rqHeader.QueryStringPtr, "httpd=", 6))) { /* If a file specification does not include a specific version number (which indicates send-me-this-file-regardless), and the request does not contain an HTTPd query string, and ConfigContentType() has returned a script name, indicating an automatic script handling for this file type, then redirect. */ if (rqptr->rqContentInfo.AutoScriptNamePtr[0] == '/') { /********************************/ /* automatic script redirection */ /********************************/ InstanceGblSecIncrLong (&AccountingPtr->DoAutoScriptCount); if (WATCHING(rqptr) && WATCH_CATEGORY(WATCH_REQUEST)) WatchThis (rqptr, FI_LI, WATCH_REQUEST, "AUTOSCRIPT \'!AZ\'", rqptr->rqContentInfo.AutoScriptNamePtr); Count = strlen(cptr = rqptr->rqContentInfo.AutoScriptNamePtr); Count += rqptr->rqHeader.PathInfoLength; rqptr->rqResponse.LocationPtr = sptr = VmGetHeap (rqptr, Count+2); while (*cptr) *sptr++ = *cptr++; for (cptr = rqptr->rqHeader.PathInfoPtr; *cptr; *sptr++ = *cptr++); *sptr++ = '?'; *sptr = '\0'; RequestEnd (rqptr); return; } } /*************************************/ /* internal types with query strings * /*************************************/ if (ConfigSameContentType (rqptr->rqContentInfo.ContentTypePtr, ConfigContentTypeIsMap, -1)) { /* once completely read, give the file over to the ISMAP engine */ FileSetAuthorizePath (rqptr, false); FileSetCacheAllowed (rqptr, true); FileSetContentHandler (rqptr, &IsMapBegin, FILE_TYPE_ISMAP_SIZE_MAX); FileBegin (rqptr, &RequestEnd, NULL, &rqptr->Md5HashPath, rqptr->ParseOds.ExpFileName, rqptr->rqContentInfo.ContentTypePtr); return; } /**************************/ /* implied keyword search */ /**************************/ if (rqptr->rqHeader.QueryStringLength && Config.cfScript.DefaultSearchLength && !rqptr->rqPathSet.NoDefaultSearch && !(rqptr->rqHeader.QueryStringLength && TOLO(rqptr->rqHeader.QueryStringPtr[0]) == 'h' && strsame (rqptr->rqHeader.QueryStringPtr, "httpd=", 6))) { if (Config.cfScript.DefaultSearchExcludePtr) { /********************************/ /* exclude specified file types */ /********************************/ if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD_REQUEST)) WatchDataFormatted ("!&Z\n", Config.cfScript.DefaultSearchExcludePtr); cptr = ""; sptr = Config.cfScript.DefaultSearchExcludePtr; while (*sptr) { cptr = rqptr->ParseOds.NamTypePtr; if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD_REQUEST)) WatchDataFormatted ("!&Z !&Z\n", sptr, cptr); while (*cptr) { if (*sptr == STRING_LIST_CHAR || *cptr == ';') break; if (TOUP(*cptr) != TOUP(*sptr)) break; if (*cptr) cptr++; if (*sptr) sptr++; } if ((!*cptr || *cptr == ';') && (!*sptr || *sptr == STRING_LIST_CHAR)) break; while (*sptr && *sptr != STRING_LIST_CHAR) sptr++; if (*sptr) sptr++; } if ((!*cptr || *cptr == ';') && (!*sptr || *sptr == STRING_LIST_CHAR)) if (WATCHING(rqptr) && WATCH_CATEGORY(WATCH_REQUEST)) WatchThis (rqptr, FI_LI, WATCH_REQUEST, "KEYWORD search \"!AZ\" excluded in \'!AZ\'", rqptr->ParseOds.NamTypePtr, Config.cfScript.DefaultSearchExcludePtr); } if (!Config.cfScript.DefaultSearchExcludePtr || (*cptr && *cptr != ';') || (*sptr && *sptr != STRING_LIST_CHAR)) { /******************/ /* search file(s) */ /******************/ if (WATCHING(rqptr) && WATCH_CATEGORY(WATCH_REQUEST)) WatchThis (rqptr, FI_LI, WATCH_REQUEST, "KEYWORD search \"!AZ\"", rqptr->rqHeader.QueryStringPtr); /* local redirection, so keyword search script gets remapped */ Count = Config.cfScript.DefaultSearchLength; Count += rqptr->rqHeader.PathInfoLength; rqptr->rqResponse.LocationPtr = sptr = VmGetHeap (rqptr, Count+2); for (cptr = Config.cfScript.DefaultSearch; *cptr; *sptr++ = *cptr++); for (cptr = rqptr->rqHeader.PathInfoPtr; *cptr; *sptr++ = *cptr++); *sptr++ = '?'; *sptr = '\0'; RequestEnd (rqptr); return; } } if (!rqptr->ParseOds.NamNameLength && !rqptr->ParseOds.NamTypeLength) { /*********************************************/ /* no file name supplied, look for home page */ /*********************************************/ RequestHomePage (rqptr); return; } /****************************************/ /* otherwise ... send the file contents */ /****************************************/ /* allow for directories specified as "/dir1/dir2" (i.e. no trailing '/') */ if (rqptr->ParseOds.NamTypeLength <= 1) RequestFile (rqptr, &RequestEnd, &RequestFileNoType); else RequestFile (rqptr, &RequestEnd, NULL); } /*****************************************************************************/ /* This function can be called one or more times per request. It steps through all possible home page files until either one is found or a default directory listing is generated. 'rqptr->HomePageStatus' attempts to short-circuit searches for conditions other than file-not-found. For example, if the status is directory-not-found then there is little point in continuing to look for home pages in that location! */ RequestHomePage (REQUEST_STRUCT *rqptr) { int idx, status, Count; char *cptr, *sptr, *FileTypePtr, *HomePageNamePtr; /*********/ /* begin */ /*********/ if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD_REQUEST)) WatchThis (rqptr, FI_LI, WATCH_MOD_REQUEST, "RequestHomePage() !UL", rqptr->RequestHomePageIndex); if (rqptr->rqResponse.HeaderPtr || ERROR_REPORTED (rqptr)) { RequestEnd (rqptr); return; } if (rqptr->RequestHomePageIndex && VMSnok (rqptr->HomePageStatus) && rqptr->HomePageStatus != RMS$_FNF) { rqptr->rqResponse.ErrorTextPtr = rqptr->rqHeader.PathInfoPtr; rqptr->rqResponse.ErrorOtherTextPtr = rqptr->ParseOds.NamDevicePtr; ErrorVmsStatus (rqptr, rqptr->HomePageStatus, FI_LI); RequestEnd (rqptr); return; } if (!*(HomePageNamePtr = ConfigHomePage(rqptr->RequestHomePageIndex++))) { /********************************************/ /* no home page, generate directory listing */ /********************************************/ if ((!Config.cfDir.Access && !rqptr->rqPathSet.DirAccess && !rqptr->rqPathSet.DirAccessSelective) || rqptr->rqPathSet.DirNoAccess) { /* directory listing is disabled */ rqptr->rqResponse.ErrorTextPtr = rqptr->rqHeader.PathInfoPtr; rqptr->rqResponse.ErrorOtherTextPtr = rqptr->ParseOds.NamDevicePtr; ErrorVmsStatus (rqptr, RMS$_FNF, FI_LI); RequestEnd (rqptr); return; } rqptr->ParseOds.NamNamePtr[0] = '\0'; if (rqptr->rqPathSet.IndexPtr) cptr = rqptr->rqPathSet.IndexPtr; else if (rqptr->rqHeader.QueryStringLength) cptr = rqptr->rqHeader.QueryStringPtr; else cptr = ""; DirBegin (rqptr, &RequestEnd, rqptr->ParseOds.NamDevicePtr, cptr, "", false); return; } for (cptr = HomePageNamePtr; *cptr && *cptr != '.'; cptr++); FileTypePtr = cptr; /******************************/ /* check for script home page */ /******************************/ for (idx = 0; idx < Config.cfScript.RunTimeCount; idx++) { if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD_REQUEST)) WatchDataFormatted ("!&Z !&Z !UL\n", cptr, Config.cfScript.RunTime[idx].StringPtr, Config.cfScript.RunTime[idx].FileTypeLength); if (strsame (cptr, Config.cfScript.RunTime[idx].StringPtr, Config.cfScript.RunTime[idx].FileTypeLength-1)) break; } if (idx < Config.cfScript.RunTimeCount) { /* found an appropriate script file type, does the file exist? */ status = OdsFileExists (rqptr->ParseOds.NamDevicePtr, HomePageNamePtr); if (VMSnok (status)) { /* (probably) file does not exist */ rqptr->HomePageStatus = status; SysDclAst (RequestHomePage, rqptr); return; } /********************/ /* script home page */ /********************/ if (WATCHING(rqptr) && WATCH_CATEGORY(WATCH_REQUEST)) WatchThis (rqptr, FI_LI, WATCH_REQUEST, "SCRIPT welcome !#AZ!AZ", rqptr->ParseOds.NamDeviceLength + rqptr->ParseOds.NamDirectoryLength, rqptr->ParseOds.NamDevicePtr, HomePageNamePtr); /* create a redirection to the original path plus the file name */ Count = rqptr->rqHeader.PathInfoLength; Count += strlen(HomePageNamePtr); rqptr->rqResponse.LocationPtr = sptr = VmGetHeap (rqptr, Count+2); for (cptr = rqptr->rqHeader.PathInfoPtr; *cptr; *sptr++ = *cptr++); for (cptr = HomePageNamePtr; *cptr; *sptr++ = *cptr++); *sptr++ = '?'; *sptr = '\0'; RequestEnd (rqptr); return; } /********************************/ /* generate home page file name */ /********************************/ /* overwrite any existing file name in the NAM block */ strcpy (rqptr->ParseOds.NamNamePtr, HomePageNamePtr); /* set the mime content type for this file type */ ConfigContentType (&rqptr->rqContentInfo, FileTypePtr); rqptr->HomePageStatus = SS$_NORMAL; RequestFile (rqptr, &RequestEnd, &RequestHomePage); } /*****************************************************************************/ /* Send a file to the client. Special cases are menu and pre-processed HTML. */ RequestFile ( REQUEST_STRUCT *rqptr, REQUEST_AST NextTaskFunction, REQUEST_AST NoSuchFileFunction ) { /*********/ /* begin */ /*********/ if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD_REQUEST)) WatchThis (rqptr, FI_LI, WATCH_MOD_REQUEST, "RequestFile() !&A !&A !&Z !&Z", NextTaskFunction, NoSuchFileFunction, rqptr->ParseOds.ExpFileName, rqptr->rqContentInfo.ContentTypePtr); if (ConfigSameContentType (rqptr->rqContentInfo.ContentTypePtr, ConfigContentTypeMenu, -1)) { FileSetAuthorizePath (rqptr, false); FileSetCacheAllowed (rqptr, true); FileSetContentHandler (rqptr, &MenuBegin, FILE_TYPE_MENU_SIZE_MAX); FileBegin (rqptr, NextTaskFunction, NoSuchFileFunction, &rqptr->Md5HashPath, rqptr->ParseOds.ExpFileName, rqptr->rqContentInfo.ContentTypePtr); return; } if (!(rqptr->rqHeader.QueryStringLength && rqptr->rqHeader.QueryStringPtr[0] && TOLO(rqptr->rqHeader.QueryStringPtr[0]) == 'h' && strsame (rqptr->rqHeader.QueryStringPtr, "httpd=content", 13))) { if (ConfigSameContentType (rqptr->rqContentInfo.ContentTypePtr, ConfigContentTypeUrl, -1)) { FileSetAuthorizePath (rqptr, false); FileSetCacheAllowed (rqptr, true); FileSetContentHandler (rqptr, &FileDotUrlBegin, FILE_TYPE_URL_SIZE_MAX); FileBegin (rqptr, NextTaskFunction, NoSuchFileFunction, &rqptr->Md5HashPath, rqptr->ParseOds.ExpFileName, rqptr->rqContentInfo.ContentTypePtr); return; } if (ConfigSameContentType (rqptr->rqContentInfo.ContentTypePtr, ConfigContentTypeSsi, -1)) { FileSetAuthorizePath (rqptr, false); FileSetCacheAllowed (rqptr, true); FileSetContentHandler (rqptr, &SsiBegin, SsiSizeMax); FileBegin (rqptr, NextTaskFunction, NoSuchFileFunction, /* A NULL path hash will cause the *file name* to be used. This allows the SSI output to be cached as well if the path indicates that is required. */ rqptr->rqPathSet.CacheSSI ? NULL : &rqptr->Md5HashPath, rqptr->ParseOds.ExpFileName, rqptr->rqContentInfo.ContentTypePtr); return; } } /* JAF */ FileSetAuthorizePath (rqptr, false); FileSetCacheAllowed (rqptr, true); FileBegin (rqptr, NextTaskFunction, NoSuchFileFunction, &rqptr->Md5HashPath, rqptr->ParseOds.ExpFileName, rqptr->rqContentInfo.ContentTypePtr); } /*****************************************************************************/ /* The file requested had no type (extension) and was not found. Check if there is a directory of that name. This allows for directories to be specified as "/dir1/dir2" (i.e. no trailing '/') which is not strictly kosher in WWW syntax but is allowed by some servers and so does occur in some documents. Generate a redirection to the same URL but with a trailing slash on the directory name. */ RequestFileNoType (REQUEST_STRUCT *rqptr) { char DirName [ODS_MAX_FILE_NAME_LENGTH+1]; char ch; char *cptr, *sptr, *zptr; /*********/ /* begin */ /*********/ if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD_REQUEST)) WatchThis (rqptr, FI_LI, WATCH_MOD_REQUEST, "RequestFileNoType()"); zptr = (sptr = DirName) + sizeof(DirName)-1; cptr = rqptr->ParseOds.NamNamePtr; while (*cptr && sptr < zptr) *sptr++ = *cptr++; cptr = ".DIR;"; while (*cptr && sptr < zptr) *sptr++ = *cptr++; *sptr = '\0'; ch = rqptr->ParseOds.NamNamePtr[0]; rqptr->ParseOds.NamNamePtr[0] = '\0'; if (VMSok (OdsFileExists (rqptr->ParseOds.NamDevicePtr, DirName))) { if (WATCHING(rqptr) && WATCH_CATEGORY(WATCH_REQUEST)) WatchThis (rqptr, FI_LI, WATCH_REQUEST, "DIRECTORY exists \'!AZ!AZ\'", rqptr->ParseOds.NamDevicePtr, DirName); rqptr->rqResponse.LocationPtr = sptr = VmGetHeap (rqptr, rqptr->rqHeader.PathInfoLength+5); *sptr++ = '/'; *sptr++ = '/'; for (cptr = rqptr->rqHeader.PathInfoPtr; *cptr; *sptr++ = *cptr++); *sptr++ = '/'; *sptr++ = '?'; *sptr = '\0'; RequestEnd (rqptr); return; } else { rqptr->ParseOds.NamNamePtr[0] = ch; if (!rqptr->AccountingDone++) InstanceGblSecIncrLong (&AccountingPtr->DoFileCount); rqptr->rqResponse.ErrorTextPtr = MapVmsPath (rqptr->ParseOds.NamDevicePtr, rqptr); rqptr->rqResponse.ErrorOtherTextPtr = rqptr->ParseOds.NamDevicePtr; ErrorVmsStatus (rqptr, RMS$_FNF, FI_LI); RequestEnd (rqptr); return; } } /*****************************************************************************/ /* Process request by executing a script. The 'MappedFile' was the non-script-name portion of the path, returned by the original MapUrl(), attempt to parse this as a VMS file specification, but if that fails (and it well might not be a file specification) then do not report it. Provide to the script a "best-guess" as to the file system ODS. */ RequestScript ( REQUEST_STRUCT *rqptr, char *MappedScript, char *MappedFile, char *MappedRunTime ) { int status; char *cptr; /*********/ /* begin */ /*********/ if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD_REQUEST)) WatchThis (rqptr, FI_LI, WATCH_MOD_REQUEST, "RequestScript() !&Z !&Z !&Z !&Z !&Z", rqptr->ScriptName, rqptr->rqHeader.PathInfoPtr, MappedScript, MappedFile, MappedRunTime); /***************************************/ /* check for internal server "scripts" */ /***************************************/ if (TOLO(rqptr->ScriptName[0]) == TOLO(ADMIN_SCRIPT_ECHO[0]) && strsame (rqptr->ScriptName, ADMIN_SCRIPT_ECHO, sizeof(ADMIN_SCRIPT_ECHO)-1)) { ResponseTrace (rqptr); return; } if (TOLO(rqptr->ScriptName[0]) == TOLO(ADMIN_SCRIPT_HISS[0]) && strsame (rqptr->ScriptName, ADMIN_SCRIPT_HISS, sizeof(ADMIN_SCRIPT_HISS)-1)) { ResponseHiss (rqptr); return; } if (TOLO(rqptr->ScriptName[0]) == TOLO(ADMIN_SCRIPT_WHERE[0]) && strsame (rqptr->ScriptName, ADMIN_SCRIPT_WHERE, sizeof(ADMIN_SCRIPT_WHERE)-1)) { ResponseWhere (rqptr, MappedFile); return; } /********************************/ /* parse any file specification */ /********************************/ /* mapping must provide a device and directory */ cptr = MappedFile; if (*cptr == ':') cptr = ""; while (*cptr && !SAME2(cptr,':[')) cptr++; if (*cptr) cptr += 2; if (*cptr == ']') cptr = ""; if (!*cptr) MappedFile[0] = '\0'; if (MappedFile[0]) { 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; } if (!(rqptr->ParseOds.Nam_fnb & NAM$M_WILDCARD)) ConfigContentType (&rqptr->rqContentInfo, rqptr->ParseOds.NamTypePtr); } else { /* problem parsing (probably not intended as a file specification) */ char *cptr, *sptr, *zptr; zptr = (sptr = rqptr->ParseOds.ExpFileName) + sizeof(rqptr->ParseOds.ExpFileName); for (cptr = MappedFile; *cptr && sptr < zptr; *sptr++ = *cptr++); if (sptr >= zptr) sptr = rqptr->ParseOds.ExpFileName; *sptr = '\0'; while (sptr > rqptr->ParseOds.ExpFileName && *sptr != '.') sptr--; if (*sptr == '.') ConfigContentType (&rqptr->rqContentInfo, sptr); } } else rqptr->ParseOds.ExpFileName[0] = '\0'; /*********************************************/ /* again check for internal server "scripts" */ /*********************************************/ if (TOLO(rqptr->ScriptName[0]) == TOLO(ADMIN_SCRIPT_UPD[0]) && strsame (rqptr->ScriptName, ADMIN_SCRIPT_UPD, -1)) { UpdBegin (rqptr, &RequestEnd); return; } else if (TOLO(rqptr->ScriptName[0]) == TOLO(ADMIN_SCRIPT_TREE[0]) && strsame (rqptr->ScriptName, ADMIN_SCRIPT_TREE, -1)) { UpdBegin (rqptr, &RequestEnd); return; } else if (TOLO(rqptr->ScriptName[0]) == TOLO(ADMIN_SCRIPT_XRAY[0]) && strsame (rqptr->ScriptName, ADMIN_SCRIPT_XRAY, -1)) { /* create a plain-text response header */ rqptr->PersistentRequest = false; rqptr->rqResponse.PreExpired = rqptr->rqResponse.NoGzip = rqptr->RedirectedXray = true; ResponseHeader200 (rqptr, "text/plain", NULL); /* set up a LOCAL redirection, stripping the '/xray' from the path */ rqptr->rqResponse.LocationPtr = rqptr->rqHeader.RequestUriPtr + sizeof(ADMIN_SCRIPT_XRAY)-1; /* write that header, returning to RequestEnd() for redirection */ NetWriteFullFlush (rqptr, &RequestEnd); return; } /***************************/ /* "real", external script */ /***************************/ if (rqptr->rqPathSet.ScriptPathFind) { status = OdsFileExists (NULL, rqptr->ParseOds.ExpFileName); if (VMSnok (status)) { ErrorVmsStatus (rqptr, status, FI_LI); RequestEnd (rqptr); return; } } for (cptr = MappedScript; *cptr && *cptr != ':'; cptr++); if (SAME2(cptr,'::')) DECnetBegin (rqptr, &RequestEnd, MappedScript, MappedRunTime); else if (rqptr->IsCgiPlusScript) DclBegin (rqptr, &RequestEnd, NULL, rqptr->ScriptName, NULL, MappedScript, MappedRunTime, NULL, NULL); else DclBegin (rqptr, &RequestEnd, NULL, rqptr->ScriptName, MappedScript, NULL, MappedRunTime, NULL, NULL); } /*****************************************************************************/ /* This function updates the global section with a formatted representation of the data of the latest request. The acounting data is of course permanently located in the area and so does not require any explicit placement into the section. */ RequestGblSecUpdate (REQUEST_STRUCT *rqptr) { int status; unsigned long FaoVector [16]; unsigned long *vecptr; char *cptr, *sptr, *zptr; HTTPD_GBLSEC *gsptr; /*********/ /* begin */ /*********/ if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD_REQUEST)) WatchThis (rqptr, FI_LI, WATCH_MOD_REQUEST, "RequestGblSecUpdate()"); gsptr = HttpdGblSecPtr; if (!rqptr) { /* reset request data */ memset (&gsptr->Request, 0, sizeof(gsptr->Request)); return; } /***********************************/ /* update the monitor request data */ /***********************************/ gsptr->Request.HttpStatus = rqptr->rqResponse.HttpStatus; PUT_QUAD_QUAD (rqptr->BytesRawRx, gsptr->Request.BytesRawRx); PUT_QUAD_QUAD (rqptr->BytesRawTx, gsptr->Request.BytesRawTx); gsptr->Request.BytesPerSecond = rqptr->BytesPerSecond; gsptr->Request.BytesTxGzipPercent = rqptr->BytesTxGzipPercent; vecptr = FaoVector; *vecptr++ = rqptr->rqTime.VmsVector[2]; *vecptr++ = rqptr->rqTime.VmsVector[3]; *vecptr++ = rqptr->rqTime.VmsVector[4]; *vecptr++ = rqptr->rqTime.VmsVector[5]; FaolToBuffer (gsptr->Request.Time, sizeof(gsptr->Request.Time), NULL, "!2ZL !2ZL:!2ZL:!2ZL", &FaoVector); vecptr = FaoVector; *vecptr++ = DurationString (rqptr, &rqptr->rqResponse.Duration); FaolToBuffer (gsptr->Request.Duration, sizeof(gsptr->Request.Duration), NULL, "!AZ", &FaoVector); /* e.g. http://the.server.host.name:port */ zptr = (sptr = gsptr->Request.Service) + sizeof(gsptr->Request.Service)-1; for (cptr = rqptr->ServicePtr->RequestSchemeNamePtr; *cptr && sptr < zptr; *sptr++ = *cptr++); if (sptr < zptr) *sptr++ = '/'; if (sptr < zptr) *sptr++ = '/'; for (cptr = rqptr->ServicePtr->ServerHostName; *cptr && sptr < zptr; *sptr++ = *cptr++); if (sptr < zptr) *sptr++ = ':'; for (cptr = rqptr->ServicePtr->ServerPortString; *cptr && sptr < zptr; *sptr++ = *cptr++); *sptr = '\0'; if (InstanceNodeConfig > 1) { zptr = (sptr = gsptr->Request.PrcNam) + sizeof(gsptr->Request.PrcNam)-1; for (cptr = HttpdProcess.PrcNam; *cptr && sptr < zptr; *sptr++ = *cptr++); *sptr = '\0'; } else gsptr->Request.PrcNam[0] = '\0'; zptr = (sptr = gsptr->Request.ClientHostName) + sizeof(gsptr->Request.ClientHostName)-1; for (cptr = rqptr->rqClient.Lookup.HostName; *cptr && sptr < zptr; *sptr++ = *cptr++); *sptr = '\0'; zptr = (sptr = gsptr->Request.MethodName) + sizeof(gsptr->Request.MethodName)-1; for (cptr = rqptr->rqHeader.MethodName; *cptr && sptr < zptr; *sptr++ = *cptr++); *sptr = '\0'; if (rqptr->rqHeader.RequestUriPtr) { zptr = (sptr = gsptr->Request.Uri) + sizeof(gsptr->Request.Uri)-1; for (cptr = rqptr->rqHeader.RequestUriPtr; *cptr && sptr < zptr; *sptr++ = *cptr++); *sptr = '\0'; } else gsptr->Request.Uri[0] = '\0'; if (rqptr->RemoteUser[0]) { zptr = (sptr = gsptr->Request.AuthUser) + sizeof(gsptr->Request.AuthUser)-1; for (cptr = rqptr->RemoteUser; *cptr; cptr++) if (!isalnum(*cptr) && *cptr != '_' && *cptr != '-') break; if (*cptr) *sptr++ = '\''; for (cptr = rqptr->RemoteUser; *cptr && sptr < zptr; *sptr++ = *cptr++); if (gsptr->Request.AuthUser[0] == '\'' && sptr rqAuth.RealmPtr) { if (sptr < zptr) *sptr++ = '.'; for (cptr = rqptr->rqAuth.RealmPtr; *cptr && sptr < zptr; *sptr++ = *cptr++); } *sptr = '\0'; } else gsptr->Request.AuthUser[0] = '\0'; gsptr->Request.Alert = rqptr->rqPathSet.Alert; if (rqptr->rqNet.ReadErrorCount) status = FaoToBuffer (gsptr->Request.ReadError, sizeof(gsptr->Request.ReadError), NULL, "%X!8XL !-!&m", rqptr->rqNet.ReadErrorStatus); else gsptr->Request.ReadError[0] = '\0'; if (rqptr->rqNet.WriteErrorCount) status = FaoToBuffer (gsptr->Request.WriteError, sizeof(gsptr->Request.WriteError), NULL, "%X!8XL !-!&m", rqptr->rqNet.WriteErrorStatus); else gsptr->Request.WriteError[0] = '\0'; } /*****************************************************************************/ /* Keeps a linked list history from most to least recent that can be reported on by RequestReport(). */ RequestHistory (REQUEST_STRUCT *rqptr) { int status; char *cptr, *sptr, *zptr; struct RequestHistoryStruct *hlptr; /*********/ /* begin */ /*********/ if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD_REQUEST)) WatchThis (rqptr, FI_LI, WATCH_MOD_REQUEST, "RequestHistory()"); if (RequestHistoryCount < RequestHistoryMax) { /* allocate memory for a new entry */ hlptr = VmGet (sizeof (struct RequestHistoryStruct)); RequestHistoryCount++; } else { /* reuse the tail entry (least recent) */ hlptr = RequestHistoryList.TailPtr; ListRemove (&RequestHistoryList, hlptr); } /* add entry to the head of the history list (most recent) */ ListAddHead (&RequestHistoryList, hlptr); PUT_QUAD_QUAD (rqptr->rqTime.Vms64bit, hlptr->BinaryTime); hlptr->ConnectNumber = rqptr->ConnectNumber; hlptr->ServicePtr = rqptr->ServicePtr; PUT_QUAD_QUAD (rqptr->BytesRawRx, hlptr->BytesRawRx); PUT_QUAD_QUAD (rqptr->BytesRawTx, hlptr->BytesRawTx); hlptr->BytesPerSecond = rqptr->BytesPerSecond; hlptr->BytesTxGzipPercent = rqptr->BytesTxGzipPercent; PUT_QUAD_QUAD (rqptr->rqResponse.Duration, hlptr->ResponseDuration); hlptr->ResponseStatusCode = rqptr->rqResponse.HttpStatus; zptr = (sptr = hlptr->ClientAndRequest) + sizeof(hlptr->ClientAndRequest); if (rqptr->RemoteUser[0]) { cptr = rqptr->RemoteUser; while (*cptr && sptr < zptr) *sptr++ = *cptr++; if (rqptr->rqAuth.RealmPtr) { if (sptr < zptr) *sptr++ = '.'; cptr = rqptr->rqAuth.RealmPtr; while (*cptr && sptr < zptr) *sptr++ = *cptr++; } if (sptr < zptr) *sptr++ = '@'; } cptr = rqptr->rqClient.Lookup.HostName; while (*cptr && sptr < zptr) *sptr++ = *cptr++; if (sptr < zptr) *sptr++ = '\0'; hlptr->RequestPtr = sptr; if (rqptr->rqHeader.PathInfoPtr && rqptr->rqHeader.QueryStringPtr) { cptr = rqptr->rqHeader.MethodName; while (*cptr && sptr < zptr) *sptr++ = *cptr++; if (sptr < zptr) *sptr++ = ' '; if (!(cptr = rqptr->rqHeader.RequestUriPtr)) cptr = ""; while (*cptr && sptr < zptr) *sptr++ = *cptr++; } if (sptr >= zptr) { hlptr->Truncated = true; sptr--; } else hlptr->Truncated = false; *sptr = '\0'; } /*****************************************************************************/ /* Return a report on current and request history, listed most to least recent. This function blocks while executing. */ RequestReport ( REQUEST_STRUCT *rqptr, REQUEST_AST NextTaskFunction ) { #define REPORT_CURRENT 1 #define REPORT_CONNECT 2 #define REPORT_HISTORY 3 #define REPORT_THROTTLE 4 /* the final column just adds a little white-space on the page far right */ static char BeginPageFao [] = "

\n\ \ \ \ \ \ \ \ \ \ \ \ \ \n\ \n"; /* the empty 99% column just forces the rest left with long request URIs */ static char CurrentRequestFao [] = "\ \ \ \ \ \ \ \ \ \ \ \ \ \n\ \ \ \ \n"; /* the final column just adds a little white-space on the page far right */ static char HistoryFao [] = "
Service / Client  Time / Request  Rx  Tx  Gzip%  Bytes/Sec  Duration  Throttle  WATCH  Connect
!3ZL  !AZ//!AZ  !20%D  !&,@SQ  !&,@SQ  !UL  !&L  !AZ!AZ  !&@  !&@  !UL
!&@  !&@
\n\

\n\ \ \ \ \ \ \ \ \ \ \ \n\ \n"; /* the empty 99% column just forces the rest left with long request URIs */ static char HistoryRequestFao [] = "\ \ \ \ \ \ \ \ \ \ \ \ \n\ \ \ \ \n"; static char RequestEmptyFao [] = "\ \ \ \n"; static char ConnectHistoryButtonFao [] = "
Service / Client  Time / Request  Rx  Tx  Gzip%  Bytes/Sec  Duration  Status  \ Connect
!3ZL  !AZ//!AZ  !20%D  !&,@SQ  !&,@SQ  !UL  !&L  !AZ  !UL  !UL
!AZ  \ !AZ!&?[truncated!]\r\r
000  empty
\n\


\n\ \n\ \n\ \n\
\n\
\n\ \n\
\n\
 \n\
\n\ \n\
\n\
\n\ [Connect]  (!UL)\n\
[Throttle]  (!UL)\n\
[History]
\n\
\n\ \n\ \n"; static char EndPageFao [] = "\n\


\n\ \n\ \n\
\n\
\n\ \n\
\n\
 \n\
\n\ \n\
\n\
\n\ \n\ \n"; int status, DisplayCount, EntryCount, ThrottleCount, ReportType; unsigned long *vecptr; unsigned long BinaryTime [2], FaoVector [32], ResponseDuration [2], ResultTime [2]; char *cptr; REQUEST_STRUCT *rqeptr; LIST_ENTRY *leptr; struct RequestHistoryStruct *rqhptr; /*********/ /* begin */ /*********/ if (WATCH_MODULE(WATCH_MOD_REQUEST)) WatchThis (NULL, FI_LI, WATCH_MOD_REQUEST, "RequestReport() !&A", NextTaskFunction); cptr = rqptr->rqHeader.PathInfoPtr; if (strsame (cptr, ADMIN_REPORT_REQUEST_CURRENT, -1)) ReportType = REPORT_CURRENT; else if (strsame (cptr, ADMIN_REPORT_REQUEST_CONNECT, -1)) ReportType = REPORT_CONNECT; else if (strsame (cptr, ADMIN_REPORT_REQUEST_HISTORY, -1)) ReportType = REPORT_HISTORY; else if (strsame (cptr, ADMIN_REPORT_REQUEST_THROTTLE, -1)) ReportType = REPORT_THROTTLE; else if (strsame (cptr, ADMIN_REPORT_REQUEST, -1)) ReportType = REPORT_CURRENT; else { ReportType = REPORT_CURRENT; ErrorNoticed (rqptr, SS$_BUGCHECK, ErrorSanityCheck, FI_LI); } if (ReportType == REPORT_THROTTLE) AdminPageTitle (rqptr, "Throttled Request Report"); else if (ReportType == REPORT_HISTORY) AdminPageTitle (rqptr, "Request & History Report"); else if (ReportType == REPORT_CONNECT) AdminPageTitle (rqptr, "Request & Connection Report"); else AdminPageTitle (rqptr, "Request Report"); status = FaolToNet (rqptr, BeginPageFao, NULL); if (VMSnok (status)) ErrorNoticed (rqptr, status, "FaolToNet()", FI_LI); /********************/ /* current requests */ /********************/ DisplayCount = EntryCount = ThrottleCount = 0; sys$gettim (&BinaryTime); /* process the request list from least to most recent */ for (leptr = RequestList.HeadPtr; leptr; leptr = leptr->NextPtr) { rqeptr = (REQUEST_STRUCT*)leptr; EntryCount++; if (rqeptr->rqPathSet.ThrottleFrom) ThrottleCount++; if (ReportType != REPORT_CONNECT) if (!(rqeptr->rqHeader.PathInfoPtr && rqeptr->rqHeader.QueryStringPtr)) continue; if (ReportType == REPORT_THROTTLE) if (!rqeptr->rqPathSet.ThrottleFrom) continue; vecptr = FaoVector; *vecptr++ = ++DisplayCount; *vecptr++ = rqeptr->ServicePtr->RequestSchemeNamePtr; *vecptr++ = rqeptr->ServicePtr->ServerHostPort; *vecptr++ = &rqeptr->rqTime.Vms64bit; *vecptr++ = &rqeptr->BytesRawRx; *vecptr++ = &rqeptr->BytesRawTx; *vecptr++ = rqeptr->BytesTxGzipPercent; status = lib$sub_times (&BinaryTime, &rqeptr->rqTime.Vms64bit, &ResponseDuration); if (VMSnok (status)) PUT_ZERO_QUAD (ResponseDuration); *vecptr++ = BytesPerSecond (&rqeptr->BytesRawRx, &rqeptr->BytesRawTx, &ResponseDuration); *vecptr++ = DurationString (rqptr, &ResponseDuration); if (rqeptr->rqTmr.TerminatedCount) *vecptr++ = " (timed-out)"; else *vecptr++ = ""; if (rqeptr->rqPathSet.ThrottleFrom) { if (rqeptr->ThrottleListEntry.DataPtr) { *vecptr++ = "\ [release]
\ [terminate]\
"; *vecptr++ = ADMIN_CONTROL_THROTTLE_RELEASE; *vecptr++ = rqeptr->ConnectNumber; *vecptr++ = ADMIN_CONTROL_THROTTLE_TERMINATE; *vecptr++ = rqeptr->ConnectNumber; } else *vecptr++ = "processing"; } else *vecptr++ = ""; if (rqeptr->ConnectNumber != rqptr->ConnectNumber) { *vecptr++ = "[P]\ [+]\ [W]"; *vecptr++ = ADMIN_REPORT_WATCH; *vecptr++ = rqeptr->ConnectNumber; *vecptr++ = ADMIN_REPORT_WATCH; *vecptr++ = rqeptr->ConnectNumber; *vecptr++ = rqeptr->ConnectNumber; *vecptr++ = ADMIN_REPORT_WATCH; *vecptr++ = rqeptr->ConnectNumber; } else *vecptr++ = "current"; *vecptr++ = rqeptr->ConnectNumber; *vecptr++ = UserAtClient(rqeptr); if (rqeptr->rqHeader.PathInfoPtr && rqeptr->rqHeader.QueryStringPtr) { *vecptr++ = "!AZ !AZ"; *vecptr++ = rqeptr->rqHeader.MethodName; *vecptr++ = rqeptr->rqHeader.RequestUriPtr; } else { if (rqeptr->rqNet.ConnectionCount) { *vecptr++ = "[persistent:!UL]"; *vecptr++ = rqeptr->rqNet.ConnectionCount; } else *vecptr++ = ""; } status = FaolToNet (rqptr, CurrentRequestFao, &FaoVector); if (VMSnok (status)) ErrorNoticed (rqptr, status, "FaolToNet()", FI_LI); } if (ReportType != REPORT_HISTORY) { if (ReportType == REPORT_CURRENT) status = FaoToNet (rqptr, ConnectHistoryButtonFao, ADMIN_CONTROL_NET_PURGE, ADMIN_CONTROL_NET_PURGE_ALL, ADMIN_REPORT_REQUEST_CONNECT, EntryCount - DisplayCount, MappingMetaPtr->ThrottleTotal ? "HREF" : "FERH", ADMIN_REPORT_REQUEST_THROTTLE, ThrottleCount, ADMIN_REPORT_REQUEST_HISTORY); else if (ReportType == REPORT_THROTTLE) { if (!DisplayCount) FaolToNet (rqptr, RequestEmptyFao, NULL); status = FaoToNet (rqptr, EndPageFao, ADMIN_CONTROL_NET_PURGE, ADMIN_CONTROL_NET_PURGE_ALL); } else status = FaoToNet (rqptr, EndPageFao, ADMIN_CONTROL_NET_PURGE, ADMIN_CONTROL_NET_PURGE_ALL); if (VMSnok (status)) ErrorNoticed (rqptr, status, "FaoToNet()", FI_LI); rqptr->rqResponse.PreExpired = PRE_EXPIRE_ADMIN; ResponseHeader200 (rqptr, "text/html", NULL); SysDclAst (NextTaskFunction, rqptr); return; } /*******************/ /* request history */ /*******************/ vecptr = FaoVector; *vecptr++ = RequestHistoryMax; status = FaolToNet (rqptr, HistoryFao, &FaoVector); if (VMSnok (status)) ErrorNoticed (rqptr, status, "FaolToNet()", FI_LI); DisplayCount = 0; /* process the request list from least to most recent */ for (leptr = RequestHistoryList.HeadPtr; leptr; leptr = leptr->NextPtr) { rqhptr = (struct RequestHistoryStruct*)leptr; vecptr = FaoVector; *vecptr++ = ++DisplayCount; *vecptr++ = rqhptr->ServicePtr->RequestSchemeNamePtr; *vecptr++ = rqhptr->ServicePtr->ServerHostPort; *vecptr++ = &rqhptr->BinaryTime; *vecptr++ = &rqhptr->BytesRawRx; *vecptr++ = &rqhptr->BytesRawTx; *vecptr++ = rqhptr->BytesTxGzipPercent; *vecptr++ = rqhptr->BytesPerSecond; *vecptr++ = DurationString (rqptr, &rqhptr->ResponseDuration); *vecptr++ = rqhptr->ResponseStatusCode; *vecptr++ = rqhptr->ConnectNumber; *vecptr++ = rqhptr->ClientAndRequest; *vecptr++ = rqhptr->RequestPtr; *vecptr++ = rqhptr->Truncated; status = FaolToNet (rqptr, HistoryRequestFao, &FaoVector); if (VMSnok (status)) ErrorNoticed (rqptr, status, "FaolToNet()", FI_LI); } if (!RequestHistoryList.HeadPtr) { status = FaolToNet (rqptr, RequestEmptyFao, NULL); if (VMSnok (status)) ErrorNoticed (rqptr, status, "FaolToNet()", FI_LI); } status = FaoToNet (rqptr, EndPageFao, ADMIN_CONTROL_NET_PURGE, ADMIN_CONTROL_NET_PURGE_ALL); if (VMSnok (status)) ErrorNoticed (rqptr, status, "FaolToNet()", FI_LI); rqptr->rqResponse.PreExpired = PRE_EXPIRE_ADMIN; ResponseHeader200 (rqptr, "text/html", NULL); SysDclAst (NextTaskFunction, rqptr); } /*****************************************************************************/ /* Dump the current and history request lists, for 'crash' analysis. Use vanilla output in case other routines are implicated. */ RequestDump () { static $DESCRIPTOR (RequestFaoDsc, "C!3ZL !UL !AZ//!AZ !%D !AZ!AZ !UL(!UL) !UL(!UL) \ !AZ!AZ!AZ!AZ!AZ !AZ!AZ!AZ !3ZL\n\0"); static $DESCRIPTOR (HistoryFaoDsc, "H!3ZL !UL !AZ//!AZ !%D !AZ !UL !UL !AZ !AZ!AZ !3ZL\n\0"); int status, Count; unsigned long *vecptr; unsigned long BinaryTime [2], FaoVector [32], ResponseDuration [2], ResultTime [2]; char Buffer [8192]; LIST_ENTRY *leptr; REQUEST_STRUCT *rqeptr; struct RequestHistoryStruct *rqhptr; $DESCRIPTOR (BufferDsc, Buffer); /*********/ /* begin */ /*********/ if (WATCH_MODULE(WATCH_MOD_REQUEST)) WatchThis (NULL, FI_LI, WATCH_MOD_REQUEST, "RequestDump()"); sys$gettim (&BinaryTime); Count = 0; fflush (stdout); for (leptr = RequestList.HeadPtr; leptr; leptr = leptr->NextPtr) { rqeptr = (REQUEST_STRUCT*)leptr; vecptr = FaoVector; *vecptr++ = ++Count; *vecptr++ = rqeptr->ConnectNumber; *vecptr++ = rqeptr->ServicePtr->RequestSchemeNamePtr; *vecptr++ = rqeptr->ServicePtr->ServerHostPort; *vecptr++ = &rqeptr->rqTime.Vms64bit; PUT_QUAD_QUAD (BinaryTime, ResponseDuration); SUB_QUAD_QUAD (rqeptr->rqTime.Vms64bit, ResponseDuration); *vecptr++ = DurationString (NULL, ResponseDuration); if (rqeptr->rqTmr.TerminatedCount) *vecptr++ = "(timed-out)"; else *vecptr++ = ""; /* unlikely to have >4GB! */ *vecptr++ = rqeptr->BytesRawRx[0]; *vecptr++ = rqeptr->rqNet.ReadErrorCount; *vecptr++ = rqeptr->BytesRawTx[0]; *vecptr++ = rqeptr->rqNet.WriteErrorCount; if (rqeptr->RemoteUser[0]) { if (rqeptr->rqAuth.RealmPtr) { *vecptr++ = rqeptr->RemoteUser; *vecptr++ = "."; *vecptr++ = rqeptr->rqAuth.RealmPtr; } else { *vecptr++ = ""; *vecptr++ = ""; *vecptr++ = rqeptr->RemoteUser; } *vecptr++ = "@"; } else { *vecptr++ = ""; *vecptr++ = ""; *vecptr++ = ""; *vecptr++ = ""; } *vecptr++ = rqeptr->rqClient.Lookup.HostName; if (rqeptr->rqHeader.PathInfoPtr && rqeptr->rqHeader.QueryStringPtr) { *vecptr++ = rqeptr->rqHeader.MethodName; *vecptr++ = " "; *vecptr++ = rqeptr->rqHeader.RequestUriPtr; } else if (rqeptr->rqNet.ConnectionCount) { static char PersistString [32]; sprintf (PersistString, "[persistent:%u]", rqeptr->rqNet.ConnectionCount); *vecptr++ = ""; *vecptr++ = ""; *vecptr++ = PersistString; } else { *vecptr++ = ""; *vecptr++ = ""; *vecptr++ = "[null]"; } *vecptr++ = rqeptr->rqResponse.HttpStatus; status = sys$faol (&RequestFaoDsc, NULL, &BufferDsc, &FaoVector); if (VMSnok (status)) FaoErrorNoticed (status, "sys$fao()", FI_LI); /* just in case of an overflow */ Buffer[sizeof(Buffer)-1] = '\0'; fputs (Buffer, stdout); } Count = 0; for (leptr = RequestHistoryList.HeadPtr; leptr; leptr = leptr->NextPtr) { rqhptr = (struct RequestHistoryStruct*)leptr; vecptr = FaoVector; *vecptr++ = ++Count; *vecptr++ = rqhptr->ConnectNumber; *vecptr++ = rqhptr->ServicePtr->RequestSchemeNamePtr; *vecptr++ = rqhptr->ServicePtr->ServerHostPort; *vecptr++ = &rqhptr->BinaryTime; *vecptr++ = DurationString (NULL, &rqhptr->ResponseDuration); /* unlikely to have >4GB! */ *vecptr++ = rqhptr->BytesRawRx[0]; *vecptr++ = rqhptr->BytesRawTx[0]; *vecptr++ = rqhptr->ClientAndRequest; *vecptr++ = rqhptr->RequestPtr; *vecptr++ = rqhptr->Truncated ? "[truncated]" : ""; *vecptr++ = rqhptr->ResponseStatusCode; status = sys$faol (&HistoryFaoDsc, NULL, &BufferDsc, &FaoVector); if (VMSnok (status)) FaoErrorNoticed (status, "sys$fao()", FI_LI); /* just in case of an overflow */ Buffer[sizeof(Buffer)-1] = '\0'; fputs (Buffer, stdout); } fflush (stdout); } /*****************************************************************************/