/*****************************************************************************/
/*
                                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-AUG-2020  MGD  bugfix; RequestEnd() redirection
04-AUG-2020  MGD  bugfix; RequestExecutePostAuth1() INTERNAL_PASSWORD_CHANGE
                    should call HtAdminBegin() not AdminBegin()
01-AUG-2020  MGD  bugfix; RequestParseExecute() ensure PUT and DELETE have
                    WebDAV header field(s) before considering WebDAV
15-JUL-2020  MGD  bugfix; RequestPersistentConnection() pipelined request
15-MAR-2020  MGD  bugfix; RequestProcessFields() DictLookup (.."accept"..)
22-DEC-2019  MGD  RequestGet() and RequestFields() refine header parsing
                  bugfix; deliver via NetIoReadStatus()
18-NOV-2019  MGD  bugfix; RequestShareBegin() if (!MATCH6 (cptr, "raw://"))
11-OCT-2019  MGD  RequestAbort() accomodates HttpdSupervisor() refinement
                  RequestShareBegin() reimplement eliminating peek approach
18-JAN-2018  MGD  status code 418 (teapot) forces connection drop
04-JAN-2019  MGD  bugfix; AuthCompleted() and AuthNotComplete()
02-SEP-2018  MGD  RequestGet() PROXY_TUNNEL_RAW add "X-Forwarded-For:" header
                    to allow ProxyTunnelNetReadAst() to provide original
                    client host for the proxied-to server
27-JUL-2018  MGD  bugfix; RequestEnd2() call SesolaClientCertEnd()
11-JUN-2018  MGD  RequestControlRundown() implement /DO=REQUEST=RUNDOWN=..
18-MAY-2018  MGD  RequestAccept() and RequestBegin() ensure data available
                     before calling SesolaNetBegin() on TLS/SSL service 
23-APR-2018  MGD  bugfix; RequestRundown() allow for cache activity
18-APR-2018  MGD  RequestBegin() exit after consecutive SesolaNetBegin() fails
12-JAN-2018  MGD  bugfix; RequestGet() read fail call RequestEnd4()
07-DEC-2017  MGD  RequestExecuteMappingError() allow for 2nn
28-NOV-2017  MGD  WebDAV "authorisation" allowed to be EXTERNAL or OPAQUE
19-NOV-2017  MGD  RequestRundown() outstanding task sanity checks
26-MAY-2017  MGD  RequestReport() when zero disable throttle button
21-MAR-2017  MGD  bugfix; use rqHeader.RequestBody.. for body with header
31-OCT-2016  MGD  bugfix; RequestExecutePostCache() keyword redirection count
14-JUL-2016  MGD  bugfix; RequestProcessFields() if-range: regression
29-MAY-2016  MGD  bugfix; RequestEnd2() decrement processing rx or (SSH) method
22-MAY-2016  MGD  bugfix; RequestEnd2() read status OK -or- ENDOFFILE
09-MAY-2016  MGD  RequestGblSecUpdate() method and URI only printable chars
11-FEB-2016  MGD  add limit to consecutive failures on persistent connection
                  remove limit to consecutive requests on persistent connection
28-DEC-2015  MGD  RequestRedirect() moved to REDIRECT.C RedirectRequest()
05-AUG-2015  MGD  HTTP/2 support
                  remove flag of Netscape Navigator Gold 3.03 :-}
14-JUL-2015  MGD  logical name WASD_REDIRECT_WILDCARD must be defined
                    to enable "DNS wildcard" proxy redirection
                  bugfix; rework pipelined request handling
29-APR-2015  MGD  add WATCH "!42*x" to beginning and ending of requests
24-APR-2015  MGD  RequestRedirect() always use dynamic buffers
29-JAN-2015  MGD  RequestExececute() move ServiceChange() after mapping
21-JAN-2015  MGD  bugfix; RequestEnd2() use ZERO_DELTA_TIME macro
07-NOV-2014  MGD  bugfix; RequestEnd2() multi-instance ->DoNoModuleCount++
29-JAN-2014  MGD  bugfix; RequestRedirect() allocate using (possibly expanded)
                    header length (not fixed) when allocating POST buffer
29-DEC-2013  MGD  ResponseCorsProcess() for Cross-Origin Resource Sharing
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
                  RequestEnd2() likewise ignore unused connections (Chrome)
14-NOV-2011  MGD  bugfix; RequestEnd2() '->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 <CR><LF> 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  RequestParseHeader() 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 <ctype.h>
#include <errno.h>
#include <stdio.h>
#include <string.h>

/* VMS related header files */
#include <descrip.h>
#include <iodef.h>
#include <libdef.h>
#include <ssdef.h>
#include <stsdef.h>

/* application header files */
#include "wasd.h"

#define WASD_MODULE "REQUEST"

/******************/
/* global storage */
/******************/

/* used by VM.c */
const int  RequestStructSize = sizeof(REQUEST_STRUCT);

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,
             Http2Enabled,
             HttpdTicking,
             LoggingEnabled,
             LoggingFileError,
             MonitorEnabled,
             MapUrlExtensionMethod,
             NetConnectSuspend,
             OdsExtended,
             WebDavEnabled,
             WebDavLockingEnabled;

extern int  ActivityTotalMinutes,
            ConnectCountTotal,
            DclSysOutputSize,
            ErrorsNoticedCount,
            ExitStatus,
            Http2ClientPrefaceLength,
            HttpdGblSecPages,
            InstanceNodeConfig,
            InstanceNodeCurrent,
            InstanceNumber,
            NetConcurrentProcessMax,
            NetCurrentProcessing,
            NetReadBufferSize,
            OpcomMessages,
            OutputBufferSize,
            ServiceTunnelSshCount,
            SsiSizeMax,
            WebSockCurrent;

extern int  ToLowerCase[],
            ToUpperCase[];

extern unsigned long  ErrorsNoticedTime64[],
                      InstanceMutexCount[],
                      InstanceMutexWaitCount[];

extern char  ConfigContentTypeSsi[],
             ConfigContentTypeUrl[],
             ErrorSslFailure[],
             ErrorSanityCheck[],
             Http2ClientPreface[],
             ServerHostPort[],
             SoftwareID[];

#define acptr AccountingPtr
extern ACCOUNTING_STRUCT  *AccountingPtr;
extern CONFIG_STRUCT  Config;
extern HTTPD_GBLSEC  *HttpdGblSecPtr;
extern HTTPD_PROCESS  HttpdProcess;
extern LIST_HEAD  Http2List;
extern MSG_STRUCT  Msgs;
extern MAPPING_META  *MappingMetaPtr;
extern WATCH_STRUCT  Watch;

/*****************************************************************************/
/*
The network connection has been established.
Make a request around that connection and begin processing.
*/ 

void RequestAccept (NETIO_STRUCT *ioptr)

{
   REQUEST_STRUCT  *rqptr;
   SERVICE_STRUCT  *svptr;

   /*********/
   /* begin */
   /*********/

   if (WATCHMOD (ioptr, WATCH_MOD_REQUEST))
      WatchThis (WATCHITM(ioptr), WATCH_MOD_REQUEST, "RequestAccept()");

   rqptr = VmGetRequest (++ConnectCountTotal);

   /* add entry to the top of the request list */
   ListAddHead (&RequestList, rqptr, LIST_ENTRY_TYPE_REQUEST);

   rqptr->NetIoPtr = ioptr;
   ioptr->RequestPtr = rqptr;
   rqptr->ClientPtr = ioptr->ClientPtr;
   rqptr->ServicePtr = ioptr->ServicePtr;
   rqptr->WatchItem = ioptr->WatchItem;

   rqptr->rqDictPtr = DictCreate (rqptr, -1);

   InstanceMutexLock (INSTANCE_MUTEX_HTTPD);
   NetUpdateConnected (rqptr, +1);
   AccountingPtr->ConnectAcceptedCount++;
   InstanceMutexUnLock (INSTANCE_MUTEX_HTTPD);

   /* timestamp the transaction */
   sys$gettim (&rqptr->rqTime.BeginTime64);
   sys$numtim (&rqptr->rqTime.BeginNumTime, &rqptr->rqTime.BeginTime64);
   HttpGmTimeString (rqptr->rqTime.GmDateTime, &rqptr->rqTime.BeginTime64);

   /* if it's not already running kick-off the HTTPd ticker */
   if (!HttpdTicking) HttpdTick (0);

   if (Watch.Category && Watch.Category != WATCH_ONE_SHOT_CAT)
      WatchFilterClientService (rqptr);

   HttpdTimerSet (rqptr, TIMER_INPUT,
                  rqptr->NetIoPtr->ServicePtr->ShareSSH ?
                  rqptr->NetIoPtr->ServicePtr->ShareSSH : 0);

   RequestBegin (rqptr);
}

/*****************************************************************************/
/*
This function is called from NetAccept() where a new HTTP/1.n 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.
*/ 

void RequestBegin (REQUEST_STRUCT *rqptr)

{
   int  status;
   char  *cptr, *sptr;
   SERVICE_STRUCT  *svptr;

   /*********/
   /* begin */
   /*********/

   if (WATCHMOD (rqptr, WATCH_MOD_REQUEST))
      WatchThis (WATCHITM(rqptr), WATCH_MOD_REQUEST, "RequestBegin()");

   rqptr->RequestState = REQUEST_STATE_CONNECTED;

   rqptr->NetworkHttp = 1;

   NetIoQioMaxSeg (rqptr->NetIoPtr);

   if (rqptr->NetIoPtr->SesolaPtr == NULL &&
       rqptr->ServicePtr->RequestScheme == SCHEME_HTTPS)
   {
      /* Transport Layer Security (TLS/SSL) ("https://") transaction */
      SesolaNetBegin (rqptr);
      return;
   }

   rqptr->rqNet.ReadBufferSize = NetReadBufferSize;
   rqptr->rqNet.ReadBufferPtr = VmGetHeap (rqptr, NetReadBufferSize);

   OdsStructInit (&rqptr->ParseOds, false);

   /* if available then set any initial report to be via the script */
   if (rqptr->ServicePtr->ErrorReportPath[0])
      rqptr->rqResponse.ErrorReportByRedirect = true;

   if (rqptr->ServicePtr->ProxyTunnel == PROXY_TUNNEL_FIREWALL ||
       rqptr->ServicePtr->ProxyTunnel == PROXY_TUNNEL_RAW)
   {
      /* fudge these as if it came from the network */
      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
            /* without a real network read just fudge the status */
            NetIoReadStatus (rqptr->NetIoPtr, RequestGet, rqptr, SS$_NORMAL, 0);
      }
      else
         /* without a real network read just fudge the status */
         NetIoReadStatus (rqptr->NetIoPtr, RequestGet, rqptr, SS$_NORMAL, 0);
   }
   else
   if (rqptr->ServicePtr->RawSocket)
   {
      /* raw [web]sockets do not (necessarily) wait for client data */
      rqptr->RawSocketRequest = true;
      /* without a real network read just fudge the status */
      NetIoReadStatus (rqptr->NetIoPtr, RequestGet, rqptr, SS$_NORMAL, 0);
   }
   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.
*/ 

void RequestShareBegin (REQUEST_STRUCT *rqptr)

{
   int  idx, status;
   char  *cptr, *sptr, *zptr;
   SERVICE_STRUCT  *svptr;

   /*********/
   /* begin */
   /*********/

   if (WATCHMOD (rqptr, WATCH_MOD_REQUEST))
      WatchThis (WATCHITM(rqptr), WATCH_MOD_REQUEST, "RequestShareBegin()");

   if (!rqptr->rqNet.ReadBufferPtr)
   {
      /* via TLS/SSL not RequestGet() */
      rqptr->rqNet.ReadBufferSize = NetReadBufferSize;
      rqptr->rqNet.ReadBufferPtr = VmGetHeap (rqptr, NetReadBufferSize);
   }

   if (rqptr->NetIoPtr->SSHversionDigit)
   {
      /* TLS/SSL service so get the rest of the SSH version exchange */
      if (!SAME4 (rqptr->rqNet.ReadBufferPtr, 'SSH-'))
      {
         strcpy (rqptr->rqNet.ReadBufferPtr, "SSH-");
         rqptr->rqNet.ReadBufferPtr[4] = rqptr->NetIoPtr->SSHversionDigit;
         NetRead (rqptr, &RequestShareBegin,
                  rqptr->rqNet.ReadBufferPtr + 5,
                  rqptr->rqNet.ReadBufferSize - 5);
         return;
      }
      rqptr->NetIoPtr->ReadCount += 5;
      rqptr->NetIoPtr->SSHversionDigit = 0;
   }

   if (rqptr->RequestState == REQUEST_STATE_ABORT)
   {
      strcpy (rqptr->rqNet.ReadBufferPtr, "SSH-(timeout)");
      rqptr->NetIoPtr->ReadCount = 13;
   }

   rqptr->RequestState = REQUEST_STATE_PROCESSING;
   rqptr->rqHeader.Method = HTTP_METHOD_SHARE_SSH;
   strcpy (rqptr->rqHeader.MethodName, "SSH");
   rqptr->rqHeader.HttpVersion = HTTP_VERSION_0_9;

   InstanceMutexLock (INSTANCE_MUTEX_HTTPD);
   NetUpdateProcessing (rqptr, +1);
   InstanceMutexUnLock (INSTANCE_MUTEX_HTTPD);

   if (Watch.Category && Watch.Category != WATCH_ONE_SHOT_CAT)
      WatchFilterHttpProtocol (rqptr);
            
   /* proxy tunneling uses the mapped path for it's host info */
   cptr = MapUrl_Map (rqptr->rqNet.ReadBufferPtr, 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+1);
   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 (!MATCH6 (cptr, "raw://"))
   {
      /* 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;
      }
   }

   if (WATCHING (rqptr, WATCH_REQUEST))
      if (!rqptr->rqHeader.WatchNewRequest)
      {
         rqptr->rqHeader.WatchNewRequest = true;
         WatchDataFormatted ("|!#*+\n", 38 + Watch.ItemWidth);
      }

   if (WATCHING (rqptr, WATCH_REQUEST))
      WatchThis (WATCHITM(rqptr), WATCH_REQUEST,
                 "SSH !AZ", *cptr ? cptr : cptr+1);

   rqptr->rqHeader.RequestUriPtr = rqptr->MappedPathPtr;
   rqptr->rqHeader.PathInfoPtr = rqptr->MappedPathPtr;
   rqptr->rqHeader.QueryStringPtr = "";

   InstanceGblSecIncrLong (&AccountingPtr->MethodSshCount);

   HttpdTimerSet (rqptr, TIMER_OUTPUT, 0);

   ProxyRequestBegin (rqptr);
}

/****************************************************************************/
/*
Any request I/O currently outstanding?
Outstanding direct network is indicated by non-NULL AST functions.
Outstanding TLS (SSL) network can also have connection state I/O being underway
so that need to be checked as well.
*/

BOOL RequestIoInProgress (REQUEST_STRUCT *rqptr)

{
   /*********/
   /* begin */
   /*********/

   if (WATCHMOD (rqptr, WATCH_MOD_REQUEST))
      WatchThis (WATCHITM(rqptr), WATCH_MOD_REQUEST,
                 "RequestIoInProgress() !&B !&B",
                 rqptr->NetIoPtr && rqptr->NetIoPtr->WriteAstFunction,
                 rqptr->NetIoPtr && rqptr->NetIoPtr->ReadAstFunction);

   if (rqptr->NetIoPtr)
   {
      if (rqptr->NetIoPtr->WriteAstFunction) return (true);
      if (rqptr->NetIoPtr->ReadAstFunction) return (true);
      if (rqptr->NetIoPtr->SesolaPtr)
         if (SesolaNetIoInProgress (rqptr->NetIoPtr))
            return (true);
   }
   return (false);
}

/****************************************************************************/
/*
Cancel any outstanding request I/O.
*/

void RequestIoCancel (REQUEST_STRUCT *rqptr)

{
   /*********/
   /* begin */
   /*********/

   if (WATCHMOD (rqptr, WATCH_MOD_REQUEST))
      WatchThis (WATCHITM(rqptr), WATCH_MOD_REQUEST, "RequestIoCancel()");

   if (rqptr->NetIoPtr)
   {
      if (rqptr->NetIoPtr->Http2StreamPtr)
         Http2RequestCancel (rqptr);
      else
      if (rqptr->NetIoPtr->SesolaPtr)
         SesolaNetIoCancel (rqptr->NetIoPtr);
      else
         NetIoCancel (rqptr->NetIoPtr);
   }
}

/*****************************************************************************/
/*
Declare a general AST on behalf of the request.
The request ->AstFunction becomes non-zero meaning it won't be run down.
When initiating the AST |TheFunction| must be NULL.
When delivered the target must call this function again with |TheFunction|
non-NULL and AST function NULL.
*/

void RequestDclAst
(
REQUEST_STRUCT *rqptr,
void *TheFunction,
void *AstFunction
)
{
   /*********/
   /* begin */
   /*********/

   if (WATCHMOD (rqptr, WATCH_MOD_REQUEST))
      WatchThis (WATCHITM(rqptr), WATCH_MOD_REQUEST,
                 "RequestDclAst() !&A", rqptr->AstFunction);

   if (rqptr->AstFunction && rqptr->AstFunction != TheFunction)
   {
      char  buf [256];
      FaoToBuffer (buf, sizeof(buf), NULL, "!&A !&A",
                   rqptr->AstFunction, TheFunction); 
      ErrorExitVmsStatus (SS$_BUGCHECK, buf, FI_LI);
   }
   rqptr->AstFunction = AstFunction;
   if (AstFunction) SysDclAst (AstFunction, rqptr);
}

/*****************************************************************************/
/*
Initiate request rundown for one or more requests.
*/ 

int RequestControlRunDown (int ConnectNumber)

{
   int  count;
   LIST_ENTRY  *leptr;
   REQUEST_STRUCT  *rqeptr;

   /*********/
   /* begin */
   /*********/

   if (WATCH_MODULE(WATCH_MOD_REQUEST))
      WatchThis (WATCHALL, WATCH_MOD_REQUEST,
                 "RequestControlRunDown() !SL", ConnectNumber);

   if (!ConnectNumber) return (0);

   count = 0;
   for (leptr = RequestList.HeadPtr; leptr; leptr = leptr->NextPtr)
   {
      rqeptr = (REQUEST_STRUCT*)leptr;
      if (ConnectNumber < 0 || rqeptr->ConnectNumber == ConnectNumber)
      {
         HttpdTimerSet (rqeptr, TIMER_TERMINATE, 0);
         count++;
      }
   }

   return (count);
}

/*****************************************************************************/
/*
If this requests's state to ABORT.  A request may only be aborted the once.
If an HTTP/1 request has not begun any real processing then close the socket.
*/

void RequestAbort (REQUEST_STRUCT *rqptr)

{
   int  state;

   /*********/
   /* begin */
   /*********/

   if (WATCHMOD (rqptr, WATCH_MOD_REQUEST))
      WatchThis (WATCHITM(rqptr), WATCH_MOD_REQUEST,
                 "RequestAbort() !UL", rqptr->RequestState);

   if ((state = rqptr->RequestState) >= REQUEST_STATE_ABORT) return;

   rqptr->RequestState = REQUEST_STATE_ABORT;

   if (HTTP2_REQUEST(rqptr)) return;

   if (rqptr->NetIoPtr->ServicePtr->ShareSSH)
      if (!rqptr->rqNet.PersistentCount)
         if (QUAD_ZERO(rqptr->BytesRx))
         {
            sys$cancel (rqptr->NetIoPtr->Channel);
            return;
         }

   if (rqptr->ProxyTaskPtr) NetCloseSocket (rqptr); 

   if (rqptr->WebSocketRequest) NetCloseSocket (rqptr);

   if (state == REQUEST_STATE_PERSIST) NetIoCancel (rqptr->NetIoPtr);
}

/*****************************************************************************/
/*
Check for various tasks and/or associated structures and call their finalise
function to progressively shut the request processing down in an orderly
fashion.  Return true if the run down is complete or false if in-progress.
*/

BOOL RequestRunDown (REQUEST_STRUCT *rqptr)

{
   /*********/
   /* begin */
   /*********/

   if (WATCHMOD (rqptr, WATCH_MOD_REQUEST))
      WatchThis (WATCHITM(rqptr), WATCH_MOD_REQUEST, "RequestRunDown()");

   if (!rqptr->rqHeader.Method) return (true);

   if (rqptr->AstFunction) return (false);

   if (rqptr->rqAuth.AstFunction) return (false);

   if (rqptr->AdminTaskPtr) return (false);

   if (rqptr->DclTaskPtr)
   {
      DclTaskRunDown (rqptr->DclTaskPtr);
      return (false);
   }

   if (rqptr->DECnetTaskPtr)
      if (!DECnetEnd (rqptr))
         return (false);

   if (rqptr->DirTaskPtr) return (false);

   if (rqptr->FileTaskPtr) return (false);

   if (rqptr->rqCache.EntryPtr) return (false);

   if (rqptr->HissTaskPtr) return (false);

   if (rqptr->HTAdminTaskPtr) return (false); 

   if (rqptr->ProxyTaskPtr)
   {
      ProxyEnd (rqptr->ProxyTaskPtr);
      return (false);
   }

   if (rqptr->PutTaskPtr) return (false);

   if (rqptr->SsiTaskPtr) return (false);

   if (rqptr->StreamTaskPtr) return (false);

   if (rqptr->TraceTaskPtr) return (false);

   if (rqptr->UpdTaskPtr) return (false);

   if (rqptr->WebDavTaskPtr) return (false);

   if (rqptr->WebSocketRequest)
      if (!WebSockEnd (rqptr))
         return (false);

   return (true);
}

/*****************************************************************************/
/*
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!
*/

void RequestEnd (REQUEST_STRUCT *rqptr)

{
   int  status;
   FILE_CONTENT  *fcptr;

   /*********/
   /* begin */
   /*********/

   if (WATCHMOD (rqptr, WATCH_MOD_REQUEST))
      WatchThis (WATCHITM(rqptr), WATCH_MOD_REQUEST, "RequestEnd() !&F !AZ",
                 &RequestEnd, RequestState(rqptr->RequestState));

   if (rqptr->RequestState >= REQUEST_STATE_SHUTDOWN)
   {
      ErrorNoticed (rqptr, SS$_BUGCHECK,
                    "HTTP/!AZ !AZ//!AZ !AZ !AZ", FI_LI,
                    rqptr->NetworkHttp == 2 ? "2" : "1.n",
                    rqptr->ServicePtr->RequestSchemeNamePtr,
                    rqptr->ServicePtr->ServerHostPort,
                    (rqptr->rqHeader.RequestUriPtr &&
                     rqptr->rqHeader.RequestUriPtr[0]) ?
                     rqptr->rqHeader.RequestUriPtr : "(none)",
                     HttpdTimeoutType(rqptr->rqTmr.TimeoutType));
      return;
   }

   RequestDclAst (rqptr, RequestEnd, NULL);

   /****************/
   /* post-process */
   /****************/

   if (Watch.RequestPtr == rqptr) WatchEnd ();

   if (rqptr->RequestState <= REQUEST_STATE_PROCESSING)
   {
      /* redacted requests require post proceessing */
      if (rqptr->rqResponse.RedactBufferPtr)
         if (!RequestRedactEnd (rqptr))
            return;

      if (fcptr = rqptr->FileContentPtr)
      {
         /* content and handler */
         if (fcptr->ContentLength > fcptr->ContentSizeMax)
         {
            if (WATCHPNT(rqptr) && (WATCH_CATEGORY(WATCH_CGI) ||
                                    WATCH_CATEGORY(WATCH_REQUEST)))
               WatchThis (WATCHITM(rqptr), 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;
         }
      }
   }

   if (rqptr->rqResponse.LocationPtr)
   {
      /***************/
      /* redirection */
      /***************/

      status = RedirectRequest (rqptr);

      /* SS$_NONLOCAL is returned when non-local redirect :^) */
      if (status == SS$_NONLOCAL) return;

      /* SS$_NORMAL is returned when local redirect */
      if (status == SS$_NORMAL)
      {
         if (RequestLineParse (rqptr))
         {
            if (RequestProcessFields (rqptr))
            {
               RequestParseExecute (rqptr);
               return;
            }
         }
      }
   }

   /***********/
   /* rundown */
   /***********/

   if (rqptr->RequestState < REQUEST_STATE_ABORT)
      rqptr->RequestState = REQUEST_STATE_RUNDOWN;

   if (!RequestRunDown (rqptr)) return;

   /**********/
   /* ending */
   /**********/

   if (rqptr->RequestState < REQUEST_STATE_ABORT)
      rqptr->RequestState = REQUEST_STATE_ENDING;

   /* free any X509 client certificate */
   SesolaClientCertEnd (rqptr);

   if (!rqptr->rqHeader.Method)
   {
      if (HTTP2_REQUEST(rqptr))
         Http2RequestEnd5 (rqptr);
      else
         RequestEnd4 (rqptr);
      return;
   }

   /* can still be in a throttle queue even without a client connection */
   if (rqptr->rqPathSet.ThrottleSet) ThrottleEnd (rqptr);

   OdsParseRelease (&rqptr->ParseOds);

   /* not interested in finalising network if terminated */
   if (rqptr->RequestState >= REQUEST_STATE_ABORT)
   {
      if (HTTP2_REQUEST(rqptr))
         RequestEnd2 (rqptr);
      else
      if (rqptr->rqHeader.ContentLength &&
          rqptr->rqBody.DataStatus != SS$_ENDOFFILE)
         RequestDiscardBody (rqptr);
      else
         RequestEnd2 (rqptr);
      return;
   }

   /* 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) && !rqptr->RawSocketRequest)
   {
      ErrorSendToClient (rqptr);
      return;
   }

   /* if the response header has not yet been sent then do it */
   if (rqptr->rqResponse.HeaderGenerated &&
       !rqptr->rqResponse.HeaderSent)
   {
      NetWrite (rqptr, &RequestEnd, NULL, 0);
      return;
   }

   if (HTTP2_REQUEST(rqptr))
      RequestEnd2 (rqptr);
   else
   if (rqptr->rqHeader.ContentLength &&
       rqptr->rqBody.DataStatus != SS$_ENDOFFILE)
      RequestDiscardBody (rqptr);
   else
      RequestEnd2 (rqptr);
}

/*****************************************************************************/
/*
Request rundown has reached a point where final accounting and other
post-request processing can be performed.
*/

void RequestEnd2 (REQUEST_STRUCT *rqptr)

{
   static ulong  DeltaOneMicro [QUAD2] = { 0xfffffff6, 0xffffffff };

   int  idx, status;
   unsigned long  Remainder;
   unsigned long  BinaryTime [QUAD2],
                  ResultTime [QUAD2];
   char  *cptr;
   NETIO_STRUCT  *ioptr;
   SERVICE_STRUCT  *svptr;

   /*********/
   /* begin */
   /*********/

   if (WATCHMOD (rqptr, WATCH_MOD_REQUEST))
      WatchThis (WATCHITM(rqptr), WATCH_MOD_REQUEST, "RequestEnd2()");

   if (HTTP2_REQUEST (rqptr))
      if (!rqptr->Http2Stream.RequestEnd)
         return (Http2RequestEnd2 (rqptr));

   /* if request started but didn't get very far */
   if (!rqptr->rqHeader.HttpVersion) rqptr->rqResponse.HttpStatus = 408;

   /* 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 (WATCHPNT(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);

   ioptr = rqptr->NetIoPtr;

   /*************************/
   /* locked global section */
   /*************************/

   InstanceMutexLock (INSTANCE_MUTEX_HTTPD);

   if (rqptr->rqHeader.Method) NetUpdateProcessing (rqptr, -1);

#if WATCH_MOD
   if (rqptr->rqHeader.PathInfoPtr &&
       MATCH16 (rqptr->rqHeader.PathInfoPtr, ADMIN_REPORT) &&
       (strsame (rqptr->rqHeader.PathInfoPtr, ADMIN_REPORT_HTTP, -1) ||
        strsame (rqptr->rqHeader.PathInfoPtr, ADMIN_REPORT_HPACK, -1)))
   {
      /* for development purposes do not include these in the stats */
      PUT_ZERO_QUAD (rqptr->BytesRx);
   }
#endif

   if (QUAD_NOT_ZERO(rqptr->BytesRx))
   {
      /* some bytes received so assume a real real request */
      sys$gettim (&BinaryTime);
      status = lib$sub_times (&BinaryTime, &rqptr->rqTime.BeginTime64,
                              &rqptr->rqResponse.Duration);
      if (VMSnok (status)) ZERO_DELTA_TIME (rqptr->rqResponse.Duration);

      svptr = rqptr->ServicePtr;
      svptr->ConnectCount++;

      /* if a module was never engaged (e.g. mapping only) */
      if (!rqptr->AccountingDone++) acptr->DoNoModuleCount++;

      if (HTTP2_REQUEST(rqptr))
         acptr->RequestHttp2Count++;
      else
      if (rqptr->rqResponse.HttpVersion == HTTP_VERSION_1_1)
         acptr->RequestHttp11Count++;
      else
      if (rqptr->rqResponse.HttpVersion == HTTP_VERSION_1_0)
         acptr->RequestHttp10Count++;
      else
      if (rqptr->rqResponse.HttpVersion == HTTP_VERSION_0_9)
         acptr->RequestHttp09Count++;

      if (rqptr->WebSocketRequest)
      {
         acptr->WebSocketCount++;
         if (rqptr->RawSocketRequest) acptr->WebSocketRawCount++;
      }
      else
      if (!rqptr->ProxyTunnelRequest)
      {
         /* first update combined HTTP/1+HTTP/2, then HTTP/1 or HTTP/2 */
         for (idx = HTTP12;;)
         {
            acptr->ResponseDurationCount[idx]++;
            ADD_QUAD_QUAD (rqptr->rqResponse.Duration,
                           acptr->ResponseDuration[idx]);

            if (QUAD_LT_QUAD (rqptr->rqResponse.Duration, DeltaOneMicro))
            {
               /* do not update using durations less than 1 uS */
               if (QUAD_ZERO (acptr->ResponseDurationMin[idx]) ||
                   QUAD_LT_QUAD (acptr->ResponseDurationMin[idx],
                                 rqptr->rqResponse.Duration))
                  PUT_QUAD_QUAD (rqptr->rqResponse.Duration,
                                 acptr->ResponseDurationMin[idx]);
            }

            if (QUAD_ZERO (acptr->ResponseDurationMax[idx]) ||
                QUAD_LT_QUAD (rqptr->rqResponse.Duration,
                              acptr->ResponseDurationMax[idx]))
            {
               PUT_QUAD_QUAD (rqptr->rqResponse.Duration,
                              acptr->ResponseDurationMax[idx]);
            }

            if (idx != HTTP12) break;
            if (HTTP2_REQUEST(rqptr)) idx = HTTP2; else idx = HTTP1;
         }
      }

      /* update any remainders in the running tally accumulators */
      ADD_QUAD_QUAD (ioptr->BytesTallyRx, acptr->BytesRawRx);
      ADD_QUAD_QUAD (ioptr->BytesTallyTx, acptr->BytesRawTx);
      ADD_QUAD_QUAD (ioptr->BlocksTallyTx, acptr->BlocksRawTx);
      ADD_QUAD_QUAD (ioptr->BlocksTallyRx, acptr->BlocksRawRx);

      /* update the service accumulators */
      ADD_QUAD_QUAD (ioptr->BytesRawRx, svptr->BytesRawRx)
      ADD_QUAD_QUAD (ioptr->BytesRawTx, svptr->BytesRawTx)

      /* update the accounting accumulators */
      ADD_QUAD_QUAD (ioptr->BytesRawRx, acptr->BytesRawRx)
      ADD_QUAD_QUAD (ioptr->BlocksRawRx, acptr->BlocksRawRx)
      ADD_QUAD_QUAD (ioptr->BytesRawTx, acptr->BytesRawTx)
      ADD_QUAD_QUAD (ioptr->BlocksRawTx, acptr->BlocksRawTx)

      if (rqptr->WebDavRequest || rqptr->WhiffOfWebDav)
      {
         ADD_QUAD_QUAD (ioptr->BytesRawTx, acptr->WebDavBytesRawTx)
         ADD_QUAD_QUAD (ioptr->BytesRawRx, acptr->WebDavBytesRawRx)
      }

      if (rqptr->rqNet.PipelineBufferPtr)
      {
         rqptr->rqNet.PipelineRequestCount++;
         acptr->PipelineRequestCount++;
         if (rqptr->rqNet.PipelineRequestCount > acptr->PipelineRequestMax)
            acptr->PipelineRequestMax = rqptr->rqNet.PipelineRequestCount;
      }
      else
         rqptr->rqNet.PipelineRequestCount = 0;

      svptr->ReadErrorCount += rqptr->rqNet.ReadErrorCount;
      svptr->WriteErrorCount += rqptr->rqNet.WriteErrorCount;
      acptr->NetReadErrorCount += rqptr->rqNet.ReadErrorCount;
      acptr->NetWriteErrorCount += rqptr->rqNet.WriteErrorCount;

      rqptr->BytesPerSecond = BytesPerSecond (&ioptr->BytesRawRx,
                                              &ioptr->BytesRawTx,
                                              &rqptr->rqResponse.Duration);

      if (rqptr->BytesPerSecond &&
          !rqptr->InternalRequest &&
          !rqptr->WebSocketRequest &&
          rqptr->rqResponse.HttpStatus / 100 == 2 &&
          rqptr->rqHeader.Method != HTTP_METHOD_OPTIONS &&
          rqptr->rqHeader.Method != HTTP_METHOD_TRACE)
      {
         /***********************************/
         /* only for "meaningful" responses */
         /***********************************/

         /* first update combined HTTP/1+HTTP/2, then HTTP/1 or HTTP/2 */
         for (idx = HTTP12;;)
         {
            ADD_QUAD_QUAD (ioptr->BytesRawRx, acptr->BytesPerSecondRawRx[idx])
            ADD_QUAD_QUAD (ioptr->BytesRawRx, acptr->BytesPerSecondRawTotal[idx])
            ADD_QUAD_QUAD (ioptr->BytesRawTx, acptr->BytesPerSecondRawTx[idx])
            ADD_QUAD_QUAD (ioptr->BytesRawTx, acptr->BytesPerSecondRawTotal[idx])

            if (rqptr->BytesPerSecond > acptr->BytesPerSecondMax[idx])
            {
               acptr->BytesPerSecondMax[idx] = rqptr->BytesPerSecond;
               PUT_QUAD_QUAD (rqptr->rqResponse.Duration,
                              acptr->BytesPerSecondMaxDuration[idx]);
               PLUS_QUAD_QUAD (ioptr->BytesRawRx,
                               ioptr->BytesRawTx,
                               acptr->BytesPerSecondMaxBytes[idx])
            }

            if (!acptr->BytesPerSecondMin[idx] ||
                rqptr->BytesPerSecond < acptr->BytesPerSecondMin[idx])
            {
               acptr->BytesPerSecondMin[idx] = rqptr->BytesPerSecond;
               PUT_QUAD_QUAD (rqptr->rqResponse.Duration,
                              acptr->BytesPerSecondMinDuration[idx]);
               PLUS_QUAD_QUAD (ioptr->BytesRawRx,
                               ioptr->BytesRawTx,
                               acptr->BytesPerSecondMinBytes[idx])
            }

            acptr->BytesPerSecondAve[idx] =
                BytesPerSecond (&acptr->BytesPerSecondRawRx[idx],
                                &acptr->BytesPerSecondRawTx[idx],
                                &acptr->ResponseDuration[idx]);

            if (idx != HTTP12) break;
            if (HTTP2_REQUEST(rqptr)) idx = HTTP2; else idx = HTTP1;
         }
      }

      /* 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 */
      acptr->ErrorsNoticedCount += ErrorsNoticedCount;
      if (CompareVmsTime64 (&ErrorsNoticedTime64,
                            &acptr->ErrorsNoticedTime64) > 0)
         PUT_QUAD_QUAD (ErrorsNoticedTime64,
                        acptr->ErrorsNoticedTime64)
      PUT_ZERO_QUAD (ErrorsNoticedTime64);
      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 &&
          QUAD_NOT_ZERO(rqptr->NetIoPtr->BytesRawRx) &&
          (!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_REQUEST))
      {
         if (rqptr->rqNet.ReadErrorCount)
            WatchThis (WATCHITM(rqptr), WATCH_REQUEST,
                       "RX errors !UL !&S",
                       rqptr->rqNet.ReadErrorCount,
                       rqptr->rqNet.ReadErrorStatus);

         if (rqptr->rqNet.WriteErrorCount)
            WatchThis (WATCHITM(rqptr), WATCH_REQUEST,
                       "TX errors !UL !&S",
                       rqptr->rqNet.WriteErrorCount,
                       rqptr->rqNet.WriteErrorStatus);

         WatchThis (WATCHITM(rqptr), WATCH_REQUEST, 
"STATUS !3ZL (!AZ) rx:!@SQ tx:!@SQ bytes !AZ seconds !&L B/s!AZ",
            rqptr->rqResponse.HttpStatus,
            HttpStatusCodeText(rqptr->rqResponse.HttpStatus),
            &ioptr->BytesRawRx, &ioptr->BytesRawTx,
            DurationString (rqptr, &rqptr->rqResponse.Duration),
            rqptr->BytesPerSecond,
            HttpdTimeoutType(rqptr->rqTmr.TimeoutType));

            if (WATCHING (rqptr, WATCH_INTERNAL))
               DictWatch (rqptr->rqDictPtr, NULL, "*");

            WatchDataFormatted ("|!#*-\n", 38 + Watch.ItemWidth);
      }
   }
   else
   if (rqptr->rqNet.PersistentCount)
   {
      /* persistent connection timeout */
      if (rqptr->ServicePtr->LogTimeoutEvents)
      {
         /* timestamp the event for logging purposes */
         sys$gettim (&rqptr->rqTime.BeginTime64);
         sys$numtim (&rqptr->rqTime.BeginNumTime, &rqptr->rqTime.BeginTime64);
         Logging (rqptr, LOGGING_ENTRY);
      }
   }

   /* if client data was set for transparent proxy/balancer/accelerator */
   if (rqptr->ClientPtr->SetClientAddress) MapUrl_ResetClientAddress (rqptr);

   /* reset the running tally accumulators */
   PUT_ZERO_QUAD (ioptr->BytesTallyRx);
   PUT_ZERO_QUAD (ioptr->BytesTallyTx);
   PUT_ZERO_QUAD (ioptr->BlocksTallyRx);
   PUT_ZERO_QUAD (ioptr->BlocksTallyTx);

   /* reset these accumulators in case of persistent connection */
   PUT_ZERO_QUAD (ioptr->BytesRawRx);
   PUT_ZERO_QUAD (ioptr->BytesRawTx);
   PUT_ZERO_QUAD (ioptr->BlocksRawRx);
   PUT_ZERO_QUAD (ioptr->BlocksRawTx);

   /* reset any TLS/SSL data */
   if (ioptr->SesolaPtr) SesolaNetIoReset (ioptr); 

   /* HTTP/2 requests are not concerned with connection persistence */
   if (HTTP2_REQUEST(rqptr))
      Http2RequestEnd5 (rqptr);
   else
   if (rqptr->RequestState >= REQUEST_STATE_ABORT)
      RequestEnd4 (rqptr);
   else
   if (VMSok (ioptr->WriteStatus) &&
       (VMSok (ioptr->ReadStatus) ||
        ioptr->ReadStatus == SS$_ENDOFFILE))
      RequestEnd3 (rqptr);
   else
      RequestEnd4 (rqptr);
}

/*****************************************************************************/
/*
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.  HTTP/1.n only!
*/

void RequestEnd3 (REQUEST_STRUCT *rqptr)

{
#ifdef WATCH_MOD
   static BOOL  LimitChecked;
   static int  PersistentLimit;
   char  *cptr;
#endif

   /*********/
   /* begin */
   /*********/

   if (WATCHMOD (rqptr, WATCH_MOD_REQUEST))
      WatchThis (WATCHITM(rqptr), WATCH_MOD_REQUEST,
                 "RequestEnd3() !&F !&B !&B",
                 &RequestEnd3, rqptr->PersistentRequest,
                 rqptr->PersistentResponse);

#ifdef WATCH_MOD
   /* development purposes only */
   if (!LimitChecked)
   {
      LimitChecked = true;
      if (cptr = SysTrnLnm ("WASD_PERSISTENT_LIMIT"))
         PersistentLimit = atoi(cptr);
      else
         PersistentLimit = PERSISTENT_LIMIT;
  }
#endif

   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.ConsecutiveFailureCount >= PERSISTENT_FAILURE_MAX)
      {
         rqptr->PersistentRequest = false;
         if (WATCHING (rqptr, WATCH_CONNECT))
            WatchThis (WATCHITM(rqptr), WATCH_CONNECT,
                       "PERSISTENT consecutive failures exceed !UL !AZ,!UL",
                       PERSISTENT_FAILURE_MAX,
                       rqptr->ClientPtr->Lookup.HostName,
                       rqptr->ClientPtr->IpPort); 
      }
      else
#ifdef WATCH_MOD
      if (rqptr->rqNet.PersistentCount >= PersistentLimit)
         rqptr->PersistentRequest = false;
#else
      if (rqptr->rqNet.PersistentCount >= PERSISTENT_LIMIT)
         rqptr->PersistentRequest = false;
#endif
      else
      if (ControlExitRequested || ControlRestartRequested)
         rqptr->PersistentRequest = false;
      else
      if (VMSnok (rqptr->NetIoPtr->ReadStatus) &&
          rqptr->NetIoPtr->ReadStatus != SS$_ENDOFFILE)
         rqptr->PersistentRequest = false;
      else
      if (VMSnok (rqptr->NetIoPtr->WriteStatus))
         rqptr->PersistentRequest = false;
   }

   if (!NetConnectSuspend &&
       rqptr->PersistentRequest &&
       rqptr->PersistentResponse &&
       rqptr->rqResponse.HttpStatus != 418 &&
       rqptr->NetIoPtr->Channel)
   {
      /*************************/
      /* persistent connection */
      /*************************/

      rqptr->RequestState = REQUEST_STATE_PERSIST;

      RequestPersistentConnection (rqptr);
      return;
   }

   RequestEnd4 (rqptr);
}

/*****************************************************************************/
/*
Close the connection.
*/

void RequestEnd4 (REQUEST_STRUCT *rqptr)

{
   /*********/
   /* begin */
   /*********/

   if (WATCHMOD (rqptr, WATCH_MOD_REQUEST))
      WatchThis (WATCHITM(rqptr), WATCH_MOD_REQUEST, "RequestEnd4()");

   rqptr->RequestState = REQUEST_STATE_SHUTDOWN;

   NetIoEnd (rqptr->NetIoPtr);

   InstanceMutexLock (INSTANCE_MUTEX_HTTPD);
   NetUpdateConnected (rqptr, -1);
   InstanceMutexUnLock (INSTANCE_MUTEX_HTTPD);

   RequestEnd5 (rqptr);
}

/*****************************************************************************/
/*
Dispose of the request structures (finally!)
*/

void RequestEnd5 (REQUEST_STRUCT *rqptr)

{
   BOOL  OneShot;

   /*********/
   /* begin */
   /*********/

   if (WATCHMOD (rqptr, WATCH_MOD_REQUEST))
      WatchThis (WATCHITM(rqptr), WATCH_MOD_REQUEST,
                 "RequestEnd5() !&F", &RequestEnd5);

   rqptr->RequestState = REQUEST_STATE_NOMORE;

   HttpdSupervisorList (rqptr, -1);

   /* free any allocated pipeline buffer */
   if (rqptr->rqNet.PipelineBufferPtr)
      VmFree (rqptr->rqNet.PipelineBufferPtr, FI_LI);

   /* free the per-request memory heap */
   VmFreeHeap (rqptr, FI_LI);

   /* remove from the request list */
   ListRemove (&RequestList, rqptr);

   /* if this request was being (one-shot) WATCHed */
   OneShot = rqptr->WatchItem & WATCH_ITEM_ONE_SHOT_FLAG;

   /* resets any associated structures (e.g. HTTP/2) */
   if (rqptr->WatchItem) WatchSetWatch (rqptr, 0);

   /* free memory allocated for the connection structure */
   VmFreeRequest (rqptr, FI_LI);

   if (OneShot) WatchEnd ();

   /* don't bother checking for exit or restart if connections still exist */
   if (!NetCurrentProcessing)
   {
      /* 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);
      }

      /* check the server's doing the right thing */
      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().
*/

void RequestPersistentConnection (REQUEST_STRUCT *rqptr)

{
   BOOL  RedactRequest,
         WatchThisOne;
   int  PathSetTimeoutPersistent,
        RedactBufferCount,
        RedactBufferSize;
   char  *RedactBufferPtr;

   /*********/
   /* begin */
   /*********/

   if (WATCHMOD (rqptr, WATCH_MOD_REQUEST))
      WatchThis (WATCHITM(rqptr), WATCH_MOD_REQUEST,
                 "RequestPersistentConnection() !&F",
                 &RequestPersistentConnection);

   rqptr->rqNet.PersistentCount++;

   PathSetTimeoutPersistent = rqptr->rqPathSet.TimeoutPersistent;

   RedactRequest = false;

   if (rqptr->rqResponse.RedactBufferPtr &&
       rqptr->rqResponse.RedactBufferCount)
   {
      /********************/
      /* redacted request */
      /********************/

      RedactRequest = true;

      InstanceGblSecIncrLong (&acptr->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 (WATCHPNT(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 (WATCHITM(rqptr), 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 */
      /*********************/

      if (WATCHMOD (rqptr, WATCH_MOD_REQUEST))
         WatchDataFormatted ("PIPELINE {!UL}!-!#AZ\n",
                             rqptr->rqNet.PipelineBufferCount,
                             rqptr->rqNet.PipelineBufferPtr);

      if (WATCHING (rqptr, WATCH_CONNECT))
         WatchThis (WATCHITM(rqptr), WATCH_CONNECT, "PIPELINE !UL !AZ,!UL",
                    rqptr->rqNet.PipelineRequestCount+1,
                    rqptr->ClientPtr->Lookup.HostName,
                    rqptr->ClientPtr->IpPort); 
   }
   else
   {
      /**********************/
      /* persistent request */
      /**********************/

      if (rqptr->rqNet.PipelineBufferPtr)
      {
         /*  no need for this any longer */
         VmFree (rqptr->rqNet.PipelineBufferPtr, FI_LI);
         rqptr->rqNet.PipelineBufferPtr = NULL;
         rqptr->rqNet.PipelineBufferSize = 0;
      }

      if (WATCHING (rqptr, WATCH_CONNECT))
         WatchThis (WATCHITM(rqptr), WATCH_CONNECT,
                    "PERSISTENT !UL with !AZ,!UL",
                    rqptr->rqNet.PersistentCount,
                    rqptr->ClientPtr->Lookup.HostName,
                    rqptr->ClientPtr->IpPort); 
   }

   /****************/
   /* prepare next */
   /****************/

   /*
      These exist in the permanent part of the request structure
      but the memory is from the request heap.
      They persist across redirects but not persistent connections.
      No need to explicitly destroy the dictionary.
      It will go with the freed heap.
   */
   rqptr->NotePadDictEntry = NULL;
   rqptr->NotePadPtr = rqptr->ProxyReverseLocationPtr = NULL;
   rqptr->RedactCount = rqptr->RedirectCount = rqptr->RedirectedXray = 0;

   /* free the per-request memory heap */
   VmFreeHeap (rqptr, FI_LI);

   /* zero the portion of the request structure that is not persistent */
   memset ((char*)&rqptr->ZeroedBegin, 0,
           (char*)&rqptr->ZeroedEnd - (char*)&rqptr->ZeroedBegin);

   /* initialise a new dictionary */
   rqptr->rqDictPtr = DictCreate (rqptr, -1);

   /*
      Timestamp the persistent connection period for RequestReport().
      Transaction will again be time-stamped if/when request received!
   */
   sys$gettim (&rqptr->rqTime.BeginTime64);

   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);

   InstanceMutexLock (INSTANCE_MUTEX_HTTPD);
   NetUpdatePersistent (+1);
   InstanceMutexUnLock (INSTANCE_MUTEX_HTTPD);

   if (rqptr->rqNet.PipelineBufferPtr &&
       rqptr->rqNet.PipelineBufferCount)
   {
      /*********************/
      /* pipelined request */
      /*********************/

      rqptr->rqNet.ReadBufferSize = rqptr->rqNet.PipelineBufferSize;
      rqptr->rqNet.ReadBufferPtr = VmGetHeap (rqptr,
                                              rqptr->rqNet.ReadBufferSize);

      /* copy previous trailing data into the read buffer */
      memcpy (rqptr->rqNet.ReadBufferPtr,
              rqptr->rqNet.PipelineBufferPtr,
              rqptr->rqNet.PipelineBufferCount);

      /* without a real network read just fudge the status */
      NetIoReadStatus (rqptr->NetIoPtr, RequestGet, rqptr, SS$_NORMAL,
                       rqptr->rqNet.PipelineBufferCount);

      /* emptied the buffer (may fill again with further trailing octets) */
      rqptr->rqNet.PipelineBufferCount = 0;
      return;
   }

   rqptr->rqNet.ReadBufferSize = NetReadBufferSize;
   rqptr->rqNet.ReadBufferPtr = VmGetHeap (rqptr, NetReadBufferSize);

   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);
}

/*****************************************************************************/
/*
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 (WATCHMOD (rqptr, WATCH_MOD_REQUEST))
      WatchThis (WATCHITM(rqptr), WATCH_MOD_REQUEST,
                "RequestHttpStatusCode() !UL", rqptr->rqResponse.HttpStatus);

#if WATCH_MOD
   /* small sanity check when module WATCHing is compiled-in */
   if (sizeof(acptr->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;
   acptr->ResponseStatusCodeGroup[StatusGroup]++;

   if (StatusGroup == 1 ||
       StatusGroup == 2 ||
       StatusGroup == 3)
      rqptr->rqNet.ConsecutiveFailureCount = 0;
   else
      rqptr->rqNet.ConsecutiveFailureCount++;

   if (StatusCode)
   {
      if ((StatusCode >= 100 && StatusCode <= 102) ||
          (StatusCode >= 200 && StatusCode <= 207) ||
          (StatusCode >= 300 && StatusCode <= 307) ||
          (StatusCode >= 400 && StatusCode <= 418) ||
          (StatusCode >= 421 && 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;
      }
      acptr->ResponseStatusCodeCount[idx]++;

      if (StatusCode == 403) acptr->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);
}

/*****************************************************************************/
/*
Provide a path alert notification.
*/ 

void RequestAlert (REQUEST_STRUCT *rqptr)

{
   int  status,
        AlertItem,
        AlertValue;
   char  Buffer [1024];
   unsigned  long  *vecptr;
   unsigned  long  FaoVector [16];

   /*********/
   /* begin */
   /*********/

   if (WATCHMOD (rqptr, WATCH_MOD_REQUEST))
      WatchThis (WATCHITM(rqptr), 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 (&acptr->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++ = "";

   FaoCheck (sizeof(FaoVector), &FaoVector, vecptr, FI_LI);
   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_REQUEST))
      WatchThis (WATCHITM(rqptr), 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 dropping it into the bit-bucket).  Then after sufficient has been read
(or end-of-content) continue to dispose of the request.
*/ 

void RequestDiscardBody (REQUEST_STRUCT *rqptr)

{
   /*********/
   /* begin */
   /*********/

   if (WATCHMOD (rqptr, WATCH_MOD_REQUEST))
      WatchThis (WATCHITM(rqptr), WATCH_MOD_REQUEST, 
                 "RequestDiscardBody() !&F !UL !&S", &RequestDiscardBody,
                 rqptr->rqBody.DiscardReadCount, rqptr->rqBody.DataStatus);

   if (rqptr->rqBody.AstFunction != RequestDiscardBody)
   {
      /* ensure we just read raw and throw-away, no fancy processing thanks */
      rqptr->rqBody.ProcessFunction = NULL;
      rqptr->rqBody.UnEncodeStream = false;
      if (rqptr->rqBody.AstFunction == NULL)
      {
         BodyReadBegin (rqptr, RequestDiscardBody, NULL);
         return;
      }
      rqptr->rqBody.AstFunction = RequestDiscardBody;
   }

   if (VMSnok (rqptr->rqBody.DataStatus))
   {
      /* error or enough read (ensure it looks like it anyway) */
      rqptr->rqBody.DataStatus = SS$_ENDOFFILE;
      RequestEnd2 (rqptr);
      return;
   }

   BodyRead (rqptr);
}

/*****************************************************************************/
/*
Process (authorization or standard) script 'redact' callout.
*/ 

void 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 (WATCHMOD (rqptr, WATCH_MOD_REQUEST))
      WatchThis (WATCHITM(rqptr), 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 (WATCHMOD (rqptr, WATCH_MOD_REQUEST))
         WatchThis (WATCHITM(rqptr), 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);
}

/*****************************************************************************/
/*
Perform the redacted request and return false (do not run down the rquest) or
run down the redacted resources and return true (continue running down the
request).
*/ 

BOOL RequestRedactEnd (REQUEST_STRUCT *rqptr)

{
   /*********/
   /* begin */
   /*********/

   if (WATCHMOD (rqptr, WATCH_MOD_REQUEST))
      WatchThis (WATCHITM(rqptr), WATCH_MOD_REQUEST, "RequestRedactEnd()");

   if (rqptr->rqResponse.HeaderSent)
   {
      if (WATCHING (rqptr, WATCH_RESPONSE))
         WatchThis (WATCHITM(rqptr), 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
   {
      /* perform the redacted request */
      RequestPersistentConnection (rqptr);
      return (false);
   }

   /* explicitly free this non-heap memory */
   VmFree (rqptr->rqResponse.RedactBufferPtr, FI_LI);
   rqptr->rqResponse.RedactBufferPtr = NULL;
   rqptr->rqResponse.RedactBufferSize = 0;
   rqptr->rqResponse.RedactBufferCount = 0;

   return (true);
}

/*****************************************************************************/
/*
Process first HTTP/1.n 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!).
*/ 
 
void RequestGet (REQUEST_STRUCT *rqptr)

{
#define STRCAT(string) { \
   for (cptr = string; *cptr && sptr < zptr; *sptr++ = *cptr++); \
}
#define CONTROL_D 0x04
#define CONTROL_Z 0x1a

   int  idx, bcnt, status, SSLv2len;
   char  *bptr, *cptr, *czptr, *sptr, *zptr;
   DICT_ENTRY_STRUCT  *denptr;

   /*********/
   /* begin */
   /*********/

   if (WATCHMOD (rqptr, WATCH_MOD_REQUEST))
      WatchThis (WATCHITM(rqptr), WATCH_MOD_REQUEST,
                 "RequestGet() !&F !&S !UL !UL !UL", &RequestGet,
                 rqptr->NetIoPtr->ReadStatus,
                 rqptr->NetIoPtr->ReadCount,
                 rqptr->rqNet.ReadBufferSize,
                 rqptr->rqHeader.RequestHeaderLength);

   if (rqptr->NetIoPtr->ServicePtr->ShareSSH)
   {
      if (SAME4 (rqptr->rqNet.ReadBufferPtr, 'SSH-'))
      {
         RequestShareBegin (rqptr);
         return;
      }

      if (rqptr->NetIoPtr->ReadStatus == SS$_CANCEL)
         if (!rqptr->rqNet.PersistentCount)
            if (QUAD_ZERO(rqptr->BytesRx))
            {
               RequestShareBegin (rqptr);
               return;
            }
   }

   if (VMSnok (rqptr->NetIoPtr->ReadStatus))
   {
      /***************/
      /* read failed */
      /***************/

      /* disconnected or otherwise failed */
      if (QUAD_ZERO (rqptr->BytesRx))
         if (rqptr->rqNet.PersistentCount)
         {
            InstanceMutexLock (INSTANCE_MUTEX_HTTPD);
            NetUpdatePersistent (-1);
            InstanceMutexUnLock (INSTANCE_MUTEX_HTTPD);
         }

      RequestEnd (rqptr);
      return;
   }

   rqptr->RequestState = REQUEST_STATE_HEADER;

   if (!rqptr->rqHeader.RequestHeaderPtr &&
       !rqptr->rqNet.RedactBufferPtr)
   {
      /*****************/
      /* request begin */
      /*****************/

      if (NetCurrentProcessing >= NetConcurrentProcessMax)
      {
         /************/
         /* too busy */
         /************/

         if (WATCHING (rqptr, WATCH_CONNECT))
            WatchThis (WATCHITM(rqptr), WATCH_CONNECT,
                       "PROCESS !UL too-busy !AZ,!UL on !AZ//!AZ,!AZ",
                       NetConcurrentProcessMax,
                       rqptr->ClientPtr->Lookup.HostName,
                       rqptr->ClientPtr->IpPort, 
                       rqptr->ServicePtr->RequestSchemeNamePtr,
                       rqptr->ServicePtr->ServerIpAddressString,
                       rqptr->ServicePtr->ServerPortString);

         InstanceGblSecIncrLong (&acptr->ProcessingTooBusyCount);
         InstanceGblSecIncrLong (&acptr->ResponseStatusCodeGroup[5]);
         rqptr->rqResponse.HttpStatus = 503;
         ErrorGeneral (rqptr, MsgFor(rqptr,MSG_GENERAL_TOO_BUSY), FI_LI);
         RequestEnd (rqptr);
         return;
      }

      /* 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\nX-Forwarded-For: ");
         STRCAT (rqptr->ClientPtr->IpAddressString);
         STRCAT ("\r\nUpgrade: WASD-tunnel-raw\r\n\r\n");
         /* as if it came from the network */
         rqptr->NetIoPtr->ReadCount = 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->NetIoPtr->ReadCount = 8;
      }
      else
      if (rqptr->RawSocketRequest)
      {
         /* 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-websocket-raw\r\n\r\n");
         /* as if it came from the network */
         rqptr->NetIoPtr->ReadCount = sptr - rqptr->rqNet.ReadBufferPtr;
      }
   }
   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 (NOTLWS(*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->NetIoPtr->ReadCount = sptr - rqptr->rqNet.ReadBufferPtr;
      }
   }

   if (rqptr->NetIoPtr->ReadCount >= Http2ClientPrefaceLength &&
       MATCH8 (rqptr->rqNet.ReadBufferPtr, Http2ClientPreface) &&
       MATCH0 (rqptr->rqNet.ReadBufferPtr, Http2ClientPreface,
                                           Http2ClientPrefaceLength))
   {
      /******************/
      /* HTTP/2 preface */
      /******************/

      if (Http2Enabled && rqptr->ServicePtr->Http2Enabled) {
         if (Http2Preface (rqptr)) return;
      }
      else
      if (WATCHING (rqptr, WATCH_REQUEST))
         WatchThis (WATCHITM(rqptr), WATCH_REQUEST,
                    "HTTP/2 preface received but HTTP/2 not enabled");
   }

   if (QUAD_ZERO (rqptr->BytesRx))
   {
      /****************/
      /* initial read */
      /****************/

      if (rqptr->rqNet.PersistentCount)
      {
         InstanceMutexLock (INSTANCE_MUTEX_HTTPD);
         NetUpdatePersistent (-1);
         InstanceMutexUnLock (INSTANCE_MUTEX_HTTPD);

         /* re-timestamp the persistent connection */
         sys$gettim (&rqptr->rqTime.BeginTime64);
         sys$numtim (&rqptr->rqTime.BeginNumTime, &rqptr->rqTime.BeginTime64);
         HttpGmTimeString (rqptr->rqTime.GmDateTime,
                           &rqptr->rqTime.BeginTime64);

         HttpdTimerSet (rqptr, TIMER_INPUT, 0);

         /* redo what is done in NetAcceptProcess() */
         if (Watch.Category && Watch.Category != WATCH_ONE_SHOT_CAT)
            if (Watch.FilterSet)
               WatchFilterClientService (rqptr);
            else
               WatchSetWatch (rqptr, WATCH_NEW_ITEM);
      }
   }

   ADD_LONG_QUAD (rqptr->NetIoPtr->ReadCount, rqptr->BytesRx)

   cptr = bptr = rqptr->rqNet.ReadBufferPtr;
   czptr = bptr + (bcnt = rqptr->BytesRx[0]);

   if (WATCHMOD (rqptr, WATCH_MOD_REQUEST))
      WatchDataFormatted ("!#AZ", bcnt, bptr);

   if (!rqptr->rqHeader.HttpVersion)
   {
      /**************************/
      /* parse the request line */
      /**************************/

      for (;;)
      {
         if (bcnt > 64)
         {
            /* if it looks suspiciously like an SSLv3 or SSLv2 handshake */
            SSLv2len = bptr[0] & 0x80 ? ((bptr[0] & 0x7f) << 8) | bptr[1] :
                                        ((bptr[0] & 0x3f) << 8) | bptr[1];
            if (SAME2(bptr,0x0316) ||
                SSLv2len == bcnt-2)
            {
               InstanceGblSecIncrLong (&acptr->RequestErrorCount);
               rqptr->rqResponse.HttpStatus = 400;
               ErrorGeneral (rqptr, MsgFor(rqptr,MSG_GENERAL_NOT_SSL), FI_LI);
               RequestEnd (rqptr);
               return;
            }
         }

         /* check for buggy <CR><LF> at the end of last request body */
         if (bcnt >= 2 && SAME2(bptr,'\r\n'))
         {
            if (WATCHING (rqptr, WATCH_REQUEST))
            {
               WatchThis (WATCHITM(rqptr), WATCH_REQUEST,
                          "RFC 2616 section 4.1 <CR><LF> bug?");
               WatchDataDump (bptr, bcnt);
            }

            /* if next request is already in the buffer */
            if (bcnt > 2)
            {
               /* skip it and adjust counts */
               bcnt -= 2;
               rqptr->BytesRx[0] -= 2;
               *(ushort*)(bptr + bcnt) = '\0\0';
               continue;
            }

            RequestEnd (rqptr);
            return;
         }

         /* look for end-of-line while checking for errant control chars */
         while (cptr < czptr && NOTCTL(*cptr)) cptr++;

         if (cptr < czptr && (*cptr == '\n' || SAME2(cptr,'\r\n')))
         {
            /* have a full request line (or at least one full newline!) */
            denptr = DictInsert (rqptr->rqDictPtr, DICT_TYPE_INTERNAL,
                                 "request_line", 12, bptr, cptr - bptr); 
            rqptr->rqHeader.RequestLinePtr = DICT_GET_VALUE(denptr);
            rqptr->rqHeader.RequestLineLength = DICT_GET_VALUE_LEN(denptr);

            if (RequestLineParse (rqptr))
            {
               if (rqptr->rqHeader.Method)
               {
                  InstanceMutexLock (INSTANCE_MUTEX_HTTPD);
                  NetUpdateProcessing (rqptr, +1);
                  InstanceMutexUnLock (INSTANCE_MUTEX_HTTPD);
               }
               if (rqptr->rqHeader.HttpVersion)
               {
                  if (Watch.Category && Watch.Category != WATCH_ONE_SHOT_CAT)
                     WatchFilterHttpProtocol (rqptr);
         
                  if (WATCHING (rqptr, WATCH_REQUEST))
                     if (!rqptr->rqHeader.WatchNewRequest)
                     {
                        rqptr->rqHeader.WatchNewRequest = true;
                        WatchDataFormatted ("|!#*+\n", 38 + Watch.ItemWidth);
                     }
               }
               break;
            }
            RequestEnd (rqptr);
            return;
         }
         else
         if (cptr < czptr && ISCTL(*cptr))
         {
            /* lurking non-printable */
            if (WATCHING (rqptr, WATCH_REQUEST))
            {
               WatchThis (WATCHITM(rqptr), WATCH_REQUEST,
                          "CHAR \\x!2XL at !UL", *cptr, cptr - bptr);
               WatchDataDump (bptr, bcnt);
            }
            ErrorNoticed (rqptr, 0, "CHAR \\x!2XL at !UL of !UL", FI_LI,
                          *cptr, cptr - bptr, bcnt);
            RequestNoticedDump (bptr, bcnt);
            RequestNBG (rqptr);
            return;
         }
         break;
      }
   }

   if (rqptr->rqHeader.HttpVersion == HTTP_VERSION_1_1 ||
       rqptr->rqHeader.HttpVersion == HTTP_VERSION_1_0 ||
       rqptr->rqHeader.HttpVersion == HTTP_VERSION_0_9)
   {
      /*************************/
      /* supported HTTP version */
      /**************************/

      /* look for the end of header blank line */
      for (cptr = bptr + rqptr->rqHeader.RequestLineLength;
           cptr < czptr && !SAME2(cptr,'\n\n') && !SAME4(cptr,'\r\n\r\n');
           cptr++);
      if (cptr < czptr)
      {
         if (SAME2(cptr,'\n\n'))
            cptr += 2;
         else
            cptr += 4;
         rqptr->rqHeader.RequestHeaderPtr = bptr;
         rqptr->rqHeader.RequestHeaderLength = cptr - bptr;
         RequestParseHeader (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;
   }

   if (rqptr->rqHeader.RequestHeaderLength >= MAX_REQUEST_HEADER)
   {
      /* not likely to be a legitimate HTTP request! */
      rqptr->rqResponse.HttpStatus = 414;
      RequestEnd (rqptr);
      return;
   }

   /*****************************/
   /* read more from the client */
   /*****************************/

   if (rqptr->rqHeader.RequestHeaderLength >= rqptr->rqNet.ReadBufferSize)
   {
      /* need more buffer space */
      rqptr->rqNet.ReadBufferSize += NetReadBufferSize;
      rqptr->rqNet.ReadBufferPtr =
         VmReallocHeap (rqptr, rqptr->rqNet.ReadBufferPtr,
                        rqptr->rqNet.ReadBufferSize, FI_LI);
   }

   /* asynchronous read to get more of the header from the client */
   NetRead (rqptr, &RequestGet,
            rqptr->rqNet.ReadBufferPtr + rqptr->BytesRx[0],
            rqptr->rqNet.ReadBufferSize - rqptr->BytesRx[0]);

#undef STRCAT
#undef CONTROL_D
#undef CONTROL_Z
}

/****************************************************************************/
/*
Parse the rest of the HTTP/1.n header fields and execute the request.  This has
an equivalent non-HTTP/1.n function, RequestParseDictionary() which performs
similar processing for an HTTP/2 request or RequestRedirect() product.
*/

void RequestParseHeader (REQUEST_STRUCT *rqptr)

{
   int  length, retval;
   char  *cptr;

   /*********/
   /* begin */
   /*********/

   if (WATCHMOD (rqptr, WATCH_MOD_REQUEST))
   {
      WatchThis (WATCHITM(rqptr), WATCH_MOD_REQUEST,
                 "RequestParseHeader() !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_REQUEST_HEADER))
   {
      WatchThis (WATCHITM(rqptr), WATCH_REQUEST_HEADER,
                 "HEADER !UL bytes", rqptr->rqHeader.RequestHeaderLength);
      WatchData (rqptr->rqHeader.RequestHeaderPtr,
                 rqptr->rqHeader.RequestHeaderLength);
   }

   /* if not POST or PUT or zero content length these won't be referenced */
   rqptr->rqHeader.RequestBodyPtr = rqptr->rqHeader.RequestHeaderPtr +
                                    rqptr->rqHeader.RequestHeaderLength;
   rqptr->rqHeader.RequestBodyCount = rqptr->BytesRx[0] -
                                      rqptr->rqHeader.RequestHeaderLength;

   InstanceMutexLock (INSTANCE_MUTEX_HTTPD);

   if (rqptr->rqNet.PersistentCount)
   {
      acptr->RequestPersistentCount++;
      if (rqptr->rqNet.PersistentCount > acptr->RequestPersistentMax)
         acptr->RequestPersistentMax = rqptr->rqNet.PersistentCount;
   }

   InstanceMutexUnLock (INSTANCE_MUTEX_HTTPD);

   cptr = rqptr->rqHeader.RequestHeaderPtr;
   cptr += rqptr->rqHeader.RequestLineLength;
   /* step over carriage control at the end of the request line */
   if (*cptr == '\r') cptr++;
   if (*cptr == '\n') cptr++;
   length = rqptr->rqHeader.RequestHeaderLength -
            (cptr - rqptr->rqHeader.RequestHeaderPtr);

   /* -1 indicates RequestNBG(), 0 to end request, +1 to execute request */
   retval = RequestFields (rqptr, cptr, length);
   if (retval > 0)
      RequestParseExecute (rqptr);
   else
   if (retval == 0)
      RequestEnd (rqptr);
   else
      RequestNBG (rqptr);
}

/****************************************************************************/
/*
When the dictionary has already been populated with request fields, as with
HTTP/2 or RequestRedirect()ed requests.  Has an equivalent HTTP/1.n function
RequestParseHeader().
*/

void RequestParseDictionary (REQUEST_STRUCT *rqptr)

{
   char  *cptr;

   /*********/
   /* begin */
   /*********/

   if (WATCHMOD (rqptr, WATCH_MOD_REQUEST))
      WatchThis (WATCHITM(rqptr), WATCH_MOD_REQUEST,
                 "RequestParseDictionary()");

   if (RequestLineParse (rqptr))
      if (RequestProcessFields (rqptr))
         RequestParseExecute (rqptr);
      else
         RequestEnd (rqptr);
   else
      RequestEnd (rqptr);
}

/****************************************************************************/
/*
Continue processing and executing the request.
*/

void RequestParseExecute (REQUEST_STRUCT *rqptr)

{
   BOOL  IsAstCall,
         WebDavHeader;
   int  PerhapsPipeline;
   char  *cptr;

   /*********/
   /* begin */
   /*********/

#if WATCH_MOD
   IsAstCall = HttpdIsAstCall();
#endif

   if (WATCHMOD (rqptr, WATCH_MOD_REQUEST))
      WatchThis (WATCHITM(rqptr), WATCH_MOD_REQUEST,
                 "RequestParseExecute() !&F !&B",
                 &RequestParseExecute, IsAstCall);

   if (Watch.Category && Watch.Category != WATCH_ONE_SHOT_CAT)
      WatchFilterPathTrack (rqptr);

#if WATCH_MOD
   if (MATCH3 (rqptr->rqHeader.PathInfoPtr, "/$/"))
   {
      /* see each of the test functions for detail */
      if (!strcmp (rqptr->rqHeader.PathInfoPtr, "/$/NetIoWriteTest/"))
      {
         NetIoWriteTest (rqptr);
         return;
      }
      if (!strcmp (rqptr->rqHeader.PathInfoPtr, "/$/NetIoReadTest/"))
      {
         if (rqptr->rqHeader.Method == HTTP_METHOD_POST)
         {
            NetIoReadTest (rqptr);
            return;
         }
      }
      if (!strcmp (rqptr->rqHeader.PathInfoPtr, "/$/HttpdIsAstCall/"))
      {
         static int  count;
         if (count & 1)
            SysDclAst (RequestEnd, rqptr);
         else
         {
            rqptr->rqResponse.NoGzip = true;
            ResponseHeader200 (rqptr, "text/plain", NULL);
            SysDclAst (RequestParseExecute, rqptr);
         }
         FaoToNet (rqptr, "HttpdIsAstCall() !&B !&B\n", count & 1, IsAstCall);
         count++;
         return;
      }
   }
#endif /* WATCH_MOD */

   if (rqptr->rqHeader.UpgradeWebSocket ||
       rqptr->RawSocketRequest)
   {
      rqptr->WebSocketRequest = true;
      if (!WebSockRequest (rqptr)) return;
   }

   if (rqptr->rqResponse.HttpVersion == HTTP_VERSION_1_1)
   {
      /* HTTP/1.1 */
      if (!rqptr->rqHeader.HostPtr)
      {
         /* HTTP/1.1 mandates a "Host:" field */
         InstanceGblSecIncrLong (&acptr->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 (&acptr->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 (!rqptr->WebSocketRequest)
         if (!rqptr->rqHeader.ConnectionClose)
            rqptr->PersistentRequest = true;

      if (!rqptr->RedirectCount &&
          !rqptr->Http2Stream.Http2Ptr)
      {
         /* test for possible request pipelining */
         PerhapsPipeline = rqptr->BytesRx[0] -
                           rqptr->rqHeader.RequestHeaderLength;

         if (Config.cfMisc.PipelineRequests &&
             !rqptr->rqHeader.ContentLength &&
             PerhapsPipeline > 0)
         {
            /*********************/
            /* perhaps pipelined */
            /*********************/

            if (WATCHMOD (rqptr, WATCH_MOD_REQUEST))
               WatchThis (WATCHITM(rqptr), WATCH_MOD_REQUEST,
                          "PerhapsPipeline !UL", PerhapsPipeline);

            if (PerhapsPipeline > rqptr->rqNet.ReadBufferSize)
            {
               ErrorNoticed (rqptr, 0, "pipeline:!UL buffer:!UL", FI_LI,
                             PerhapsPipeline, rqptr->rqNet.ReadBufferSize);
               ErrorExitVmsStatus (SS$_BUGCHECK, ErrorSanityCheck, FI_LI);
            }

            if (rqptr->rqNet.ReadBufferSize > rqptr->rqNet.PipelineBufferSize)
            {
               if (rqptr->rqNet.PipelineBufferPtr)
                  VmFree (rqptr->rqNet.PipelineBufferPtr, FI_LI);
               rqptr->rqNet.PipelineBufferSize = rqptr->rqNet.ReadBufferSize;
               rqptr->rqNet.PipelineBufferPtr =
                  VmGet (rqptr->rqNet.PipelineBufferSize);
            }

            /* buffer the trailing octets */
            rqptr->rqNet.PipelineBufferCount = PerhapsPipeline;
            memcpy (rqptr->rqNet.PipelineBufferPtr,
                    rqptr->rqHeader.RequestHeaderPtr +
                       rqptr->rqHeader.RequestHeaderLength,
                    rqptr->rqNet.PipelineBufferCount);
         }
      }
   }
   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 (&acptr->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)
   {
      WebDavHeader = (rqptr->rqHeader.WebDavDepthPtr ||
                      rqptr->rqHeader.WebDavDestinationPtr ||
                      rqptr->rqHeader.WebDavIfPtr ||
                      rqptr->rqHeader.WebDavLockTokenPtr ||
                      rqptr->rqHeader.WebDavTimeoutPtr ||
                      rqptr->rqHeader.WebDavTranslatePtr ||
                      rqptr->rqHeader.WebDavOverwritePtr);

      if (WebDavHeader && (rqptr->rqHeader.Method == HTTP_METHOD_PUT ||
                           rqptr->rqHeader.Method == HTTP_METHOD_DELETE))
         rqptr->WebDavMethod = true;

      if (rqptr->WebDavMethod)
         rqptr->WebDavRequest = true;
      else
      if (rqptr->rqHeader.UserAgentPtr &&
          (strstr (rqptr->rqHeader.UserAgentPtr, "WebDAV") ||
           strstr (rqptr->rqHeader.UserAgentPtr, "webdav") ||
           strstr (rqptr->rqHeader.UserAgentPtr, "DAV") ||
           strstr (rqptr->rqHeader.UserAgentPtr, "WebDrive") ||
           strstr (rqptr->rqHeader.UserAgentPtr, "cadaver")))
         rqptr->WhiffOfWebDav = true;

      if (!rqptr->RedirectCount)
      {
         if (rqptr->WebDavRequest)
            InstanceGblSecIncrLong (&acptr->WebDavRequestCount);
         else
         if (rqptr->WhiffOfWebDav)
            InstanceGblSecIncrLong (&acptr->WebDavAromaCount);
      }
   }

   /* avoid the complexity of upgrading requests with a body */
   if (!rqptr->rqHeader.ContentLength)
      if (rqptr->rqHeader.UpgradeHttp2onHttp &&
          rqptr->rqHeader.ConnectionHttp2Settings &&
          rqptr->rqHeader.ConnectionUpgrade)
         if (Http2SwitchResponse (rqptr))
            return;

   /********************************************/
   /* evaluated in order of probable occurance */
   /********************************************/

   if (rqptr->rqHeader.Method == HTTP_METHOD_GET)
   {
      InstanceMutexLock (INSTANCE_MUTEX_HTTPD);

      if (!rqptr->RedirectCount)
      {
         acptr->MethodGetCount++;
         if (rqptr->WebDavRequest || rqptr->WhiffOfWebDav)
            acptr->MethodWebDav_GetCount++;
      }
      if (rqptr->WebSocketRequest)
      {
         acptr->CurrentWebSockets[InstanceNumber]++;
         WebSockCurrent++;
      }

      InstanceMutexUnLock (INSTANCE_MUTEX_HTTPD);

      RequestExecute (rqptr);
      return;
   }

   if (rqptr->ServicePtr->ProxyTunnel)
   {
      if (rqptr->rqHeader.Method == HTTP_METHOD_CONNECT)
      {
         if (!rqptr->RedirectCount)
            InstanceGblSecIncrLong (&acptr->MethodConnectCount);
         rqptr->PersistentRequest = false;
         RequestExecute (rqptr);
         return;
      }
   }

   if (rqptr->rqHeader.HttpVersion == HTTP_VERSION_0_9)
   {
      /*********************************/
      /* HTTP/0.9 must be GET requests */
      /*********************************/

      InstanceGblSecIncrLong (&acptr->RequestErrorCount);
      rqptr->rqResponse.HttpStatus = 501;
      ErrorGeneral (rqptr, MsgFor(rqptr,MSG_REQUEST_METHOD), FI_LI);
      RequestEnd (rqptr);
      return;
   }

   if (rqptr->rqHeader.Method == HTTP_METHOD_HEAD)
   {
      if (!rqptr->RedirectCount)
         InstanceGblSecIncrLong (&acptr->MethodHeadCount);
      RequestExecute (rqptr);
      return;
   }
   if (rqptr->rqHeader.Method == HTTP_METHOD_POST)
   {
      if (!rqptr->RedirectCount)
         InstanceGblSecIncrLong (&acptr->MethodPostCount);
      RequestExecute (rqptr);
      return;
   }
   if (rqptr->rqHeader.Method == HTTP_METHOD_PUT)
   {
      if (!rqptr->RedirectCount)
      {
         InstanceMutexLock (INSTANCE_MUTEX_HTTPD);
         acptr->MethodPutCount++;
         if (rqptr->WebDavRequest || rqptr->WhiffOfWebDav)
            acptr->MethodWebDav_PutCount++;
         InstanceMutexUnLock (INSTANCE_MUTEX_HTTPD);
      }
      RequestExecute (rqptr);
      return;
   }
   if (rqptr->rqHeader.Method == HTTP_METHOD_CONNECT)
   {
      if (!rqptr->RedirectCount)
         InstanceGblSecIncrLong (&acptr->MethodConnectCount);
      rqptr->PersistentRequest = false;
      RequestExecute (rqptr);
      return;
   }
   if (rqptr->rqHeader.Method == HTTP_METHOD_DELETE)
   {
      if (!rqptr->RedirectCount)
      {
         InstanceMutexLock (INSTANCE_MUTEX_HTTPD);
         acptr->MethodDeleteCount++;
         if (rqptr->WebDavRequest || rqptr->WhiffOfWebDav)
            acptr->MethodWebDav_DeleteCount++;
         InstanceMutexUnLock (INSTANCE_MUTEX_HTTPD);
      }
      RequestExecute (rqptr);
      return;
   }
   if (rqptr->rqHeader.Method == HTTP_METHOD_TRACE)
   {
      if (!rqptr->RedirectCount)
         InstanceGblSecIncrLong (&acptr->MethodTraceCount);
      rqptr->PersistentRequest = false;
      RequestExecute (rqptr);
      return;
   }
   if (rqptr->rqHeader.Method == HTTP_METHOD_OPTIONS)
   {
      if (!rqptr->RedirectCount)
      {
         InstanceMutexLock (INSTANCE_MUTEX_HTTPD);
         acptr->MethodOptionsCount++;
         if (rqptr->WebDavRequest || rqptr->WhiffOfWebDav)
            acptr->MethodWebDav_OptionsCount++;
         InstanceMutexUnLock (INSTANCE_MUTEX_HTTPD);
      }
      RequestExecute (rqptr);
      return;
   }
   if (rqptr->rqHeader.Method == HTTP_METHOD_EXTENSION)
   {
      if (!rqptr->RedirectCount)
         InstanceGblSecIncrLong (&acptr->MethodExtensionCount);
      RequestExecute (rqptr);
      return;
   }

   if (rqptr->rqHeader.Method == HTTP_METHOD_WEBDAV_PROPPATCH)
   {
      if (!rqptr->RedirectCount)
         InstanceGblSecIncrLong (&acptr->MethodWebDavPropPatchCount);
      RequestExecute (rqptr);
      return;
   }
   if (rqptr->rqHeader.Method == HTTP_METHOD_WEBDAV_PROPFIND)
   {
      if (!rqptr->RedirectCount)
         InstanceGblSecIncrLong (&acptr->MethodWebDavPropFindCount);
      RequestExecute (rqptr);
      return;
   }
   if (rqptr->rqHeader.Method == HTTP_METHOD_WEBDAV_COPY)
   {
      if (!rqptr->RedirectCount)
         InstanceGblSecIncrLong (&acptr->MethodWebDavCopyCount);
      RequestExecute (rqptr);
      return;
   }
   if (rqptr->rqHeader.Method == HTTP_METHOD_WEBDAV_MOVE)
   {
      if (!rqptr->RedirectCount)
         InstanceGblSecIncrLong (&acptr->MethodWebDavMoveCount);
      RequestExecute (rqptr);
      return;
   }
   if (rqptr->rqHeader.Method == HTTP_METHOD_WEBDAV_MKCOL)
   {
      if (!rqptr->RedirectCount)
         InstanceGblSecIncrLong (&acptr->MethodWebDavMkColCount);
      RequestExecute (rqptr);
      return;
   }
   if (rqptr->rqHeader.Method == HTTP_METHOD_WEBDAV_LOCK)
   {
      if (!rqptr->RedirectCount)
         InstanceGblSecIncrLong (&acptr->MethodWebDavLockCount);
      RequestExecute (rqptr);
      return;
   }
   if (rqptr->rqHeader.Method == HTTP_METHOD_WEBDAV_UNLOCK)
   {
      if (!rqptr->RedirectCount)
         InstanceGblSecIncrLong (&acptr->MethodWebDavUnLockCount);
      RequestExecute (rqptr);
      return;
   }

   InstanceGblSecIncrLong (&acptr->RequestErrorCount);
   rqptr->rqResponse.HttpStatus = 501;
   ErrorGeneral (rqptr, MsgFor(rqptr,MSG_REQUEST_METHOD), FI_LI);
   RequestEnd (rqptr);
}

/****************************************************************************/
/*
Parse the first <CR><LF> (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.
*/ 
 
BOOL RequestLineParse (REQUEST_STRUCT *rqptr)

{
   int  length;
   char  *cptr, *sptr, *uptr, *zptr;
   DICT_ENTRY_STRUCT  *denptr;

   /*********/
   /* begin */
   /*********/

   if (WATCHMOD (rqptr, WATCH_MOD_REQUEST))
   {
      WatchThis (WATCHITM(rqptr), WATCH_MOD_REQUEST, "RequestLineParse()");
      WatchDataDump (rqptr->rqHeader.RequestLinePtr,
                     rqptr->rqHeader.RequestLineLength);
   }

   cptr = rqptr->rqHeader.RequestLinePtr;

   zptr = (sptr = rqptr->rqHeader.MethodName) +
          sizeof(rqptr->rqHeader.MethodName)-1;
   while (*cptr && NOTLWS(*cptr) && sptr < zptr) *sptr++ = *cptr++;
   *sptr = '\0';

   /* error with method name */
   if (*cptr && NOTLWS(*cptr)) goto RequestLineParseFormatError;
   if (!rqptr->rqHeader.MethodName[0]) goto RequestLineParseFormatError;

   /* also match the trailing nulls */
   if (MATCH4 (rqptr->rqHeader.MethodName, "GET"))
      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;
   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;
   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 (MATCH10 (rqptr->rqHeader.MethodName, "PROPPATCH"))
      {
         rqptr->rqHeader.Method = HTTP_METHOD_WEBDAV_PROPPATCH;
         rqptr->WebDavMethod = true;
      }
      else
      if (MATCH9 (rqptr->rqHeader.MethodName, "PROPFIND"))
      {
         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 URI */
   while (*cptr && ISLWS(*cptr)) cptr++;
   if (!*cptr) goto RequestLineParseFormatError;

   /* get the path information, noting the start of the requested URI */
   for (uptr = cptr; *cptr && NOTLWS(*cptr) && *cptr != '?'; cptr++);

   /* error, no path on request line */
   if (cptr == uptr) goto RequestLineParseFormatError;

   denptr = DictInsert (rqptr->rqDictPtr, DICT_TYPE_INTERNAL,
                        "path_info", 9, uptr, cptr - uptr);
   rqptr->rqHeader.PathInfoPtr = DICT_GET_VALUE(denptr);
   if ((length = StringUrlDecode (rqptr->rqHeader.PathInfoPtr)) < 0)
      goto RequestLineParseUrlEncodeError;
   DictValueLength (denptr, length);
   rqptr->rqHeader.PathInfoLength = length;

   /* get any query string, or create an empty one */
   if (*cptr == '?') cptr++;
   for (sptr = cptr; *cptr && NOTLWS(*cptr); cptr++);

   denptr = DictInsert (rqptr->rqDictPtr, DICT_TYPE_INTERNAL,
                        "query_string", 12, sptr, cptr - sptr);
   rqptr->rqHeader.QueryStringPtr = DICT_GET_VALUE(denptr);
   rqptr->rqHeader.QueryStringLength = DICT_GET_VALUE_LEN(denptr);

   denptr = DictInsert (rqptr->rqDictPtr, DICT_TYPE_INTERNAL,
                        "request_uri", 11, uptr, cptr - uptr);
   rqptr->rqHeader.RequestUriPtr = DICT_GET_VALUE(denptr);
   rqptr->rqHeader.RequestUriLength = DICT_GET_VALUE_LEN(denptr);

   if (WATCHMOD (rqptr, 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 (*cptr && ISLWS(*cptr)) cptr++;

   if (MATCH8 (cptr, "HTTP/1.1"))
      rqptr->rqHeader.HttpVersion =
         rqptr->rqResponse.HttpVersion = HTTP_VERSION_1_1;
   else
   if (MATCH8 (cptr, "HTTP/1.0"))
      rqptr->rqHeader.HttpVersion =
         rqptr->rqResponse.HttpVersion = HTTP_VERSION_1_0;
   else
   if (MATCH5 (cptr, "HTTP/"))
      rqptr->rqHeader.HttpVersion = HTTP_VERSION_UNKNOWN;
   else
      rqptr->rqHeader.HttpVersion =
         rqptr->rqResponse.HttpVersion = HTTP_VERSION_0_9;

   return (true);

   /* report a request line format problem and return false */
   RequestLineParseFormatError:
   {
      InstanceGblSecIncrLong (&acptr->RequestErrorCount);
      rqptr->rqResponse.HttpStatus = 400;
      ErrorGeneral (rqptr, MsgFor(rqptr,MSG_REQUEST_FORMAT), FI_LI);
      return (false);
   }
   RequestLineParseUrlEncodeError:
   {
      InstanceGblSecIncrLong (&acptr->RequestErrorCount);
      rqptr->rqResponse.HttpStatus = 400;
      ErrorGeneral (rqptr, MsgFor(rqptr,MSG_REQUEST_URL_ENC), FI_LI);
      return (false);
   }
}

/*****************************************************************************/
/*
RFC7230 section 3.2 is the most recent definition of header fields.
Scan the header buffer looking for relevant fields.  Return 1 to continue
processing the request, 0 to end immediately, -1 to report gross header
malformation (for want of a less cumbersome phrase).
*/ 
 
int RequestFields
(
REQUEST_STRUCT *rqptr,
char *FieldsPtr,
int FieldsLength
)

{
   int  nlen, vlen;
   char  *cptr, *nptr, *vptr, *zptr;
   DICT_ENTRY_STRUCT  *denptr;

   /*********/
   /* begin */
   /*********/

   if (WATCHMOD (rqptr, WATCH_MOD_REQUEST))
      WatchThis (WATCHITM(rqptr), WATCH_MOD_REQUEST, "RequestFields()");

   zptr = (cptr = FieldsPtr) + FieldsLength;
   nlen = vlen = 0;

   while (cptr < zptr)
   {
      /* (potentially) the blank line terminating the header */
      if (cptr < zptr && *cptr == '\r') cptr++;
      if (cptr < zptr && *cptr == '\n') cptr++;
      if (cptr >= zptr) break;

      /* parse the field name */
      for (nptr = cptr; cptr < zptr && *cptr != ':'; cptr++)
         if (ISCTL(*cptr) || ISLWS(*cptr)) break;
      if ((nlen = cptr - nptr) > MAX_REQUEST_FIELD_NAME_LEN) break;
      if (*cptr != ':') break;
      for (cptr++; cptr < zptr && ISLWS(*cptr); cptr++);
      if (cptr >= zptr) break;

      /* parse the field value */
      for (vptr = cptr; cptr < zptr; cptr++)
         if (ISCTL(*cptr) && *cptr != '\t') break;
      if (NOTCRLF(*cptr)) break;
      if (cptr >= zptr) break;
      if ((*cptr == '\r' && ISLWS(*(cptr+2) && cptr+2 < zptr) ||
          (*cptr == '\n' && ISLWS(*(cptr+1) && cptr+1 < zptr))))
      {
         /* RFC7230 deprecates folded headers but let's be generous */
         if (!(cptr = RequestFoldedHeader (rqptr, nptr, nlen, vptr, zptr)))
            return (-1);
      }
      else
      {
         /* trim trailing white-space */
         while (cptr-1 > vptr && ISLWS(*(cptr-1))) cptr--;
         vlen = cptr - vptr;
         while (NOTCRLF(*cptr) && cptr < zptr) cptr++;

         if (WATCHMOD (rqptr, WATCH_MOD_REQUEST))
            WatchThis (WATCHITM(rqptr), WATCH_MOD_REQUEST,
                       "{!UL}!-!#AZ {!UL}!-!#AZ", nlen, nptr, vlen, vptr);

         /* enter this request header field into the dictionary */
         DictInsert (rqptr->rqDictPtr, DICT_TYPE_REQUEST, nptr, nlen,
                                                          vptr, vlen);
      }

      /* step over the carriage control at the end of the line */
      if (cptr < zptr && *cptr == '\r') cptr++;
      if (cptr < zptr && *cptr == '\n') cptr++;
      nlen = vlen = 0;
   }

   if (cptr >= zptr)
      if (RequestProcessFields (rqptr))
         return (1);
      else
         return (0);

   if (WATCHING (rqptr, WATCH_REQUEST))
   {
      if (cptr < zptr)
      {
         if (nlen > MAX_REQUEST_FIELD_NAME_LEN)
            WatchThis (WATCHITM(rqptr), WATCH_REQUEST,
                       "FIELD \"!32AZ..:\" exceeds !UL bytes",
                       nptr, MAX_REQUEST_FIELD_NAME_LEN);
         else
            WatchThis (WATCHITM(rqptr), WATCH_REQUEST,
                       "FIELD \"!#AZ\" char \\x!2XL at !UL",
                       nlen, nptr, *cptr, cptr - FieldsPtr);
      }
      WatchDataDump (rqptr->rqHeader.RequestHeaderPtr,
                     rqptr->rqHeader.RequestHeaderLength);

   }

   if (cptr < zptr)
   {
      if (nlen > MAX_REQUEST_FIELD_NAME_LEN)
         ErrorNoticed (rqptr, 0, "FIELD \"!32AZ..:\" exceeds !UL bytes",
                       FI_LI, nptr, MAX_REQUEST_FIELD_NAME_LEN);
      else
         ErrorNoticed (rqptr, 0,
                       "FIELD \"!#AZ\" char \\x!2XL at !UL of !UL",
                       FI_LI, nlen, nptr, *cptr, cptr - FieldsPtr,
                       rqptr->rqHeader.RequestHeaderLength);
      RequestNoticedDump (rqptr->rqHeader.RequestHeaderPtr,
                          rqptr->rqHeader.RequestHeaderLength);
   }

   return (-1);
}

/*****************************************************************************/
/*
RFC7230 deprecates folded headers but let's be generous.  Parse the folder
header value into a dynamic buffer and insert into the request dictionary. 
Return a pointer to the new position in the request header, or NULL if there
was a fatal error.
*/ 

char* RequestFoldedHeader
(
REQUEST_STRUCT *rqptr,
char *nptr,
int nlen,
char *hptr,
char *hzptr
)
{
   static int  vsize;
   static char  *vptr;

   int  vlen;
   char  *sptr, *zptr;

   /*********/
   /* begin */
   /*********/

   if (WATCHMOD (rqptr, WATCH_MOD_REQUEST))
      WatchThis (WATCHITM(rqptr), WATCH_MOD_REQUEST, "RequestFoldedHeader()");

   if (WATCHING (rqptr, WATCH_REQUEST))
      WatchThis (WATCHITM(rqptr), WATCH_REQUEST,
                 "RFC7230 3.2.4 deprecates folded value of \"!#AZ:\"",
                 nlen, nptr);

   zptr = (sptr = vptr) + vsize;
   for (;;)
   {
      if (hptr >= hzptr) return (NULL);
      if (sptr >= zptr)
      {
         if (vsize + 2048 >= MAX_REQUEST_HEADER) return (NULL);
         vsize += 2048;
         vlen = sptr - vptr;
         vptr = VmRealloc (vptr, vsize, FI_LI);
         zptr = (sptr = vptr) + vsize;
         sptr += vlen;
      }
      while (hptr < hzptr && sptr < zptr)
      {
         if (ISCTL(*hptr) && *hptr != '\t') break;
         *sptr++ = *hptr++;
      }
      if (sptr >= zptr) continue;
      if (hptr >= hzptr) return (NULL);

      if (*hptr == '\r' && *(hptr+1) == '\n' && !ISLWS(*(hptr+2)))
         break;
      else
      if (*hptr == '\n' && !ISLWS(*(hptr+1)))
         break;

      if (*hptr == '\r') hptr++;
      if (*hptr == '\n') hptr++;
      while (ISLWS(*hptr) && hptr < hzptr)
      {
         if (sptr >= zptr)
         {
            if (vsize + 2048 >= MAX_REQUEST_HEADER) return (NULL);
            vsize += 2048;
            vlen = sptr - vptr;
            vptr = VmRealloc (vptr, vsize, FI_LI);
            zptr = (sptr = vptr) + vsize;
            sptr += vlen;
         }
         *sptr++ = ' ';
         hptr++;
      }
   }
   /* trim trailing white-space */
   while (sptr-1 > vptr && ISLWS(*(sptr-1))) sptr--;
   *sptr = '\0';
   vlen = sptr - vptr;

   if (WATCHMOD (rqptr, WATCH_MOD_REQUEST))
      WatchThis (WATCHITM(rqptr), WATCH_MOD_REQUEST,
                 "{!UL}!-!#AZ {!UL}!-!#AZ", nlen, nptr, vlen, vptr);

   DictInsert (rqptr->rqDictPtr, DICT_TYPE_REQUEST, nptr, nlen, vptr, vlen);

   return (hptr);
}

/*****************************************************************************/
/*
Process the contents of specific fields stored in the dictionary.
*/ 
 
BOOL RequestProcessFields (REQUEST_STRUCT *rqptr)

{
   int  idx, status;
   char  *cptr;
   RANGE_BYTE  *rbptr;
   DICT_STRUCT  *dicptr;
   DICT_ENTRY_STRUCT  *denptr;

   /*********/
   /* begin */
   /*********/

   if (WATCHMOD (rqptr, WATCH_MOD_REQUEST))
      WatchThis (WATCHITM(rqptr), WATCH_MOD_REQUEST, "RequestProcessFields()");

   dicptr = rqptr->rqDictPtr;

   if (WATCHING (rqptr, WATCH_REQUEST_HEADER))
   {
      WatchThis (WATCHITM(rqptr), WATCH_REQUEST_HEADER, "DATA");
      DictWatchEntry (NULL);
      DictWatch (rqptr->rqDictPtr, DICT_TYPE_INTERNAL, "request_line");
      DictWatch (rqptr->rqDictPtr, DICT_TYPE_REQUEST, "*");
   }

   if (denptr = DictLookup (dicptr, DICT_TYPE_REQUEST, "accept-charset", 14))
      rqptr->rqHeader.AcceptCharsetPtr = DICT_GET_VALUE(denptr);

   if (denptr = DictLookup (dicptr, DICT_TYPE_REQUEST, "accept-encoding", 15))
   {
      rqptr->rqHeader.AcceptEncodingPtr = cptr = DICT_GET_VALUE(denptr);
      while (*cptr)
      {
         while (*cptr && ISLWS(*cptr)) cptr++; 
         if (MATCH4 (cptr, "gzip") ||
            strsame (cptr, "gzip", 4))
         {
            rqptr->rqHeader.AcceptEncodingGzip = true;
            cptr += 4;
         }
         else
         if (MATCH7 (cptr, "deflate") ||
            strsame (cptr, "deflate", 7))
         {
            rqptr->rqHeader.AcceptEncodingGzip = true;
            cptr += 7;
         }
         while (*cptr && NOTLWS(*cptr) && *cptr != ',') cptr++; 
         while (*cptr && (ISLWS(*cptr) || *cptr == ',')) cptr++; 
      }
   }

   if (denptr = DictLookup (dicptr, DICT_TYPE_REQUEST, "accept", 6))
      rqptr->rqHeader.AcceptPtr = DICT_GET_VALUE(denptr);

   if (denptr = DictLookup (dicptr, DICT_TYPE_REQUEST, "accept-language", 15))
      rqptr->rqHeader.AcceptLangPtr = DICT_GET_VALUE(denptr);

   if (denptr = DictLookup (dicptr, DICT_TYPE_REQUEST, "authorization", 13))
      rqptr->rqHeader.AuthorizationPtr = DICT_GET_VALUE(denptr);

   if (denptr = DictLookup (dicptr, DICT_TYPE_REQUEST, "cache-control", 13))
   {
      cptr = DICT_GET_VALUE(denptr);
      while (*cptr)
      {
         while (*cptr && ISLWS(*cptr)) cptr++; 
         if (MATCH8 (cptr, "no-cache") ||
            strsame (cptr, "no-cache", 8))
         {
            rqptr->rqHeader.CacheControlNoCache = true;
            cptr += 8;
         }
         else
         if (MATCH8 (cptr, "no-store") ||
            strsame (cptr, "no-store", 8))
         {
            rqptr->rqHeader.CacheControlNoStore = true;
            cptr += 8;
         }
         else
         if (MATCH12 (cptr, "no-transform") ||
             strsame (cptr, "no-transform", 12))
         {
            rqptr->rqHeader.CacheControlNoTransform = true;
            cptr += 12;
         }
         else
         if (MATCH8 (cptr, "max-age=") ||
            strsame (cptr, "max-age=", 8))
         {
            cptr += 8;
            rqptr->rqHeader.CacheControlMaxAge = atoi(cptr);
            if (!rqptr->rqHeader.CacheControlMaxAge)
               rqptr->rqHeader.CacheControlMaxAgeZero = true;
         }
         else
         if (MATCH10 (cptr, "max-stale=") ||
             strsame (cptr, "max-stale=", 10))
         {
            cptr += 10;
            rqptr->rqHeader.CacheControlMaxStale = atoi(cptr);
            if (!rqptr->rqHeader.CacheControlMaxStale)
               rqptr->rqHeader.CacheControlMaxStaleZero = true;
         }
         else
         if (MATCH9 (cptr, "max-stale") ||
             strsame (cptr, "max-stale", 9))
         {
            rqptr->rqHeader.CacheControlMaxStaleZero = true;
            cptr += 9;
         }
         else
         if (MATCH10 (cptr, "min-fresh=") ||
             strsame (cptr, "min-fresh=", 10))
         {
            cptr += 10;
            rqptr->rqHeader.CacheControlMinFresh = atoi(cptr);
            if (!rqptr->rqHeader.CacheControlMinFresh)
               rqptr->rqHeader.CacheControlMinFreshZero = true;
         }
         else
         if (MATCH14 (cptr, "only-if-cached") ||
             strsame (cptr, "only-if-cached", 14))
         {
            rqptr->rqHeader.CacheControlOnlyIfCached = true;
            cptr += 14;
         }
         while (*cptr && NOTLWS(*cptr) && *cptr != ',') cptr++; 
         while (*cptr && (ISLWS(*cptr) || *cptr == ',')) cptr++; 
      }

      if (WATCHMOD (rqptr, WATCH_MOD_REQUEST))
         WatchThis (WATCHITM(rqptr), WATCH_MOD_REQUEST,
                    "cache:!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);
   }

   if (denptr = DictLookup (dicptr, DICT_TYPE_REQUEST, "connection", 10))
   {
      cptr = DICT_GET_VALUE(denptr);
      while (*cptr)
      {
         while (*cptr && ISLWS(*cptr)) cptr++; 
         if (MATCH5 (cptr, "close") ||
            strsame (cptr, "close", 5))
         {
            rqptr->rqHeader.ConnectionClose = true;
            cptr += 5;
         }
         else
         if (MATCH14 (cptr, "HTTP2-Settings") ||
             strsame (cptr, "http2-settings", 14))
         {
            rqptr->rqHeader.ConnectionHttp2Settings = true;
            cptr += 14;
         }
         else
         if (MATCH10 (cptr, "keep-alive") ||
             strsame (cptr, "keep-alive", 10))
         {
            rqptr->rqHeader.ConnectionKeepAlive = true;
            cptr += 10;
         }
         else
         if (MATCH7 (cptr, "upgrade") ||
            strsame (cptr, "upgrade", 7))
         {
            rqptr->rqHeader.ConnectionUpgrade = true;
            cptr += 7;
         }
         while (*cptr && NOTLWS(*cptr) && *cptr != ',') cptr++; 
         while (*cptr && (ISLWS(*cptr) || *cptr == ',')) cptr++; 
      }

      if (WATCHMOD (rqptr, WATCH_MOD_REQUEST))
         WatchThis (WATCHITM(rqptr), WATCH_MOD_REQUEST,
                    "connection:!&B !&B !&B !&B",
                    rqptr->rqHeader.ConnectionClose,
                    rqptr->rqHeader.ConnectionHttp2Settings,
                    rqptr->rqHeader.ConnectionKeepAlive,
                    rqptr->rqHeader.ConnectionUpgrade);
   }

   if (denptr = DictLookup (dicptr, DICT_TYPE_REQUEST, "content-encoding", 16))
   {
      cptr = DICT_GET_VALUE(denptr);
      while (*cptr)
      {
         while (*cptr && ISLWS(*cptr)) cptr++; 
         if (MATCH4 (cptr, "gzip") ||
            strsame (cptr, "gzip", 4))
         {
            rqptr->rqHeader.ContentEncodingGzip = true;
            cptr += 4;
         }
         else
         if (MATCH7 (cptr, "deflate") ||
            strsame (cptr, "deflate", 7))
         {
            rqptr->rqHeader.ContentEncodingGzip = true;
            cptr += 7;
         }
         else
            rqptr->rqHeader.ContentEncodingUnknown = true;
         while (*cptr && NOTLWS(*cptr) && *cptr != ',') cptr++; 
         while (*cptr && (ISLWS(*cptr) || *cptr == ',')) cptr++; 
      }

      if (WATCHMOD (rqptr, WATCH_MOD_REQUEST))
         WatchDataFormatted ("gzip:!&B unknown:!&B\n",
             rqptr->rqHeader.ContentEncodingGzip,
             rqptr->rqHeader.ContentEncodingUnknown);
   }

   if (denptr = DictLookup (dicptr, DICT_TYPE_REQUEST, "content-length", 14))
      rqptr->rqHeader.ContentLength = atoi(DICT_GET_VALUE(denptr));

   if (denptr = DictLookup (dicptr, DICT_TYPE_REQUEST, "content-type", 12))
      rqptr->rqHeader.ContentTypePtr = DICT_GET_VALUE(denptr);

   if (denptr = DictLookup (dicptr, DICT_TYPE_REQUEST, "cookie", 6))
      rqptr->rqHeader.CookiePtr = DICT_GET_VALUE(denptr);

   if (denptr = DictLookup (dicptr, DICT_TYPE_REQUEST, "forwarded", 9))
      rqptr->rqHeader.ForwardedPtr = DICT_GET_VALUE(denptr);

   if (denptr = DictLookup (dicptr, DICT_TYPE_REQUEST, "http2-settings", 14))
      rqptr->rqHeader.Http2SettingsPtr = DICT_GET_VALUE(denptr);

   if (denptr = DictLookup (dicptr, DICT_TYPE_REQUEST, "if-match", 8))
      rqptr->rqHeader.IfMatchPtr = DICT_GET_VALUE(denptr);

   if (denptr = DictLookup (dicptr, DICT_TYPE_REQUEST, "if-modified-since", 17))
   {
      cptr = DICT_GET_VALUE(denptr);

      status = HttpGmTime (cptr, &rqptr->rqTime.IfModifiedSinceTime64);
      if (VMSnok (status))
      {
         PUT_ZERO_QUAD (rqptr->rqTime.IfModifiedSinceTime64);
         InstanceGblSecIncrLong (&acptr->RequestErrorCount);
         rqptr->rqResponse.HttpStatus = 400;
         ErrorGeneral (rqptr, MsgFor(rqptr,MSG_REQUEST_FORMAT), FI_LI);
         return (false);
      }

      /* indicate that no "length=" was found (if none is found :^) */
      rqptr->rqHeader.IfModifiedSinceLength = -1;
      /* look for a "length=" parameter to this header field */
      while (*cptr)
      {
         while (*cptr && *cptr != ';') cptr++; 
         if (!*cptr) break;
         cptr++;
         while (*cptr && ISLWS(*cptr)) cptr++; 
         if (!*cptr) break;
         if (!MATCH6 (cptr, "length")) continue;
         cptr += 6;
         while (*cptr && *cptr != '=') cptr++; 
         if (!*cptr) break;
         cptr++;
         while (*cptr && ISLWS(*cptr)) cptr++; 
         if (!isdigit (*cptr)) continue;
         rqptr->rqHeader.IfModifiedSinceLength = atol(cptr);
      }

      if (WATCHMOD (rqptr, WATCH_MOD_REQUEST))
         WatchDataFormatted ("since:!&S !%D !SL\n",
                             status, &rqptr->rqTime.IfModifiedSinceTime64,
                                     rqptr->rqHeader.IfModifiedSinceLength);
   }

   if (denptr = DictLookup (dicptr, DICT_TYPE_REQUEST, "if-range", 8))
   {
      cptr = DICT_GET_VALUE(denptr);
      status = HttpGmTime (cptr, &rqptr->rqTime.IfRangeBeginTime64);
      if (VMSnok (status))
         PUT_ZERO_QUAD (&rqptr->rqTime.IfRangeBeginTime64);
      if (WATCHMOD (rqptr, WATCH_MOD_REQUEST))
         WatchDataFormatted ("if-range:!&Z !&S !%D \n", cptr, status,
                             &rqptr->rqTime.IfRangeBeginTime64);
   }

   if (denptr = DictLookup (dicptr, DICT_TYPE_REQUEST,
                            "if-unmodified-since", 19))
   {
      cptr = DICT_GET_VALUE(denptr);
      status = HttpGmTime (cptr, &rqptr->rqTime.IfUnModifiedSinceTime64);
      if (VMSnok (status))
         PUT_ZERO_QUAD (rqptr->rqTime.IfUnModifiedSinceTime64);
      if (WATCHMOD (rqptr, WATCH_MOD_REQUEST))
         WatchDataFormatted ("unmod:!&Z !&S !%D \n", cptr, status,
                             &rqptr->rqTime.IfUnModifiedSinceTime64);
   }

   if (denptr = DictLookup (dicptr, DICT_TYPE_REQUEST, "host", 4))
   {
      rqptr->rqHeader.HostPtr = DICT_GET_VALUE(denptr);
      rqptr->rqHeader.HostLength = DICT_GET_VALUE_LEN(denptr);
   }

   if (denptr = DictLookup (dicptr, DICT_TYPE_REQUEST, "keep-alive", 10))
   {
      rqptr->rqHeader.KeepAliveConnection = true;
      cptr = DICT_GET_VALUE(denptr);
      if (isdigit (*cptr)) rqptr->rqHeader.KeepAliveSeconds = atoi(cptr);
      if (WATCHMOD (rqptr, WATCH_MOD_REQUEST))
         WatchDataFormatted ("keep-alive:!&B (!UL)\n",
                             rqptr->rqHeader.KeepAliveConnection,
                             rqptr->rqHeader.KeepAliveSeconds);
   }

   if (denptr = DictLookup (dicptr, DICT_TYPE_REQUEST, "origin", 6))
      rqptr->rqHeader.OriginPtr = DICT_GET_VALUE(denptr);

   if (denptr = DictLookup (dicptr, DICT_TYPE_REQUEST, "pragma", 6))
   {
      cptr = DICT_GET_VALUE(denptr);
      if (MATCH8 (cptr, "no-cache") || strsame (cptr, "no-cache", 8))
         rqptr->rqHeader.PragmaNoCache = true;
      if (WATCHMOD (rqptr, WATCH_MOD_REQUEST))
         WatchDataFormatted ("no-cache:!&B\n", rqptr->rqHeader.PragmaNoCache);
   }

   if (denptr = DictLookup (dicptr, DICT_TYPE_REQUEST, "range", 5))
   {
      cptr = DICT_GET_VALUE(denptr);
      rqptr->rqHeader.RangeBytePtr = rbptr =
         VmGetHeap (rqptr, sizeof(RANGE_BYTE));
      if (MATCH6 (cptr, "bytes="))
      {
         /* ok, that's a start */
         cptr += 6;
         idx = 0;
         while (*cptr)
         {
            while (*cptr && ISLWS(*cptr) || *cptr == ',') cptr++;
            if (!*cptr || *cptr == ';') break;
            if (idx >= RANGE_BYTE_MAX)
            {
               ErrorNoticed (rqptr, 0, "RANGE_BYTE_MAX exceeded", FI_LI);
               idx = 0;
               break;
            }
            if (isdigit(*cptr))
            {
               /* starting offset */
               rbptr->First[idx] = atol(cptr);
               while (*cptr && isdigit(*cptr)) cptr++;
               if (*cptr == '-')
               {
                  /* ending offset */
                  cptr++;
                  rbptr->Last[idx] = atol(cptr);
                  while (*cptr && isdigit(*cptr)) cptr++;
               }
               idx++;
            }
            else
            if (*cptr == '-')
            {
               /* negative offset (from the end-of-file) */
               rbptr->Last[idx] = atol(cptr);
               cptr++;
               while (*cptr && isdigit(*cptr)) cptr++;
               idx++;
            }
            else
            {
               /* range format error */
               idx = 0;
               break;
            }
         }
         rbptr->Total = idx;
         if (WATCHMOD (rqptr, 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]);
         }
      }
   }

   if (denptr = DictLookup (dicptr, DICT_TYPE_REQUEST, "referer", 7))
      rqptr->rqHeader.RefererPtr = DICT_GET_VALUE(denptr);

   if (denptr = DictLookup (dicptr, DICT_TYPE_REQUEST, "transfer-encoding", 17))
   {
      cptr = DICT_GET_VALUE(denptr);
      if (MATCH7 (cptr, "chunked") || strsame (cptr, "chunked", 7))
         rqptr->rqHeader.TransferEncodingChunked = true;
      if (WATCHMOD (rqptr, WATCH_MOD_REQUEST))
         WatchDataFormatted ("chunked:!&B\n",
            rqptr->rqHeader.TransferEncodingChunked);

      /*
         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
      */
      DictRemove (dicptr, "content-length", DICT_TYPE_REQUEST, 14);
      rqptr->rqHeader.ContentLength = 0;
   }

   if (denptr = DictLookup (dicptr, DICT_TYPE_REQUEST, "upgrade", 7))
   {
      cptr = DICT_GET_VALUE(denptr);
      if (MATCH3 (cptr, "h2c"))
      {
         if (rqptr->rqResponse.HttpVersion == HTTP_VERSION_1_1)
            rqptr->rqHeader.UpgradeHttp2onHttp = true;
      }
      else
      if (MATCH9 (cptr, "WebSocket") ||
         strsame (cptr, "WebSocket", 9))
         rqptr->rqHeader.UpgradeWebSocket = true;
      else
      if (MATCH11 (cptr, "WASD-tunnel") ||
          strsame (cptr, "WASD-tunnel", 11))
         rqptr->rqHeader.UpgradeWASDtunnel = true;
   }

   if (denptr = DictLookup (dicptr, DICT_TYPE_REQUEST, "user-agent", 10))
      rqptr->rqHeader.UserAgentPtr = DICT_GET_VALUE(denptr);

   if (denptr = DictLookup (dicptr, DICT_TYPE_REQUEST, "x-forwarded-for", 15))
      rqptr->rqHeader.XForwardedForPtr = DICT_GET_VALUE(denptr);

   if (WebDavEnabled)
   {
      if (denptr = DictLookup (dicptr, DICT_TYPE_REQUEST, "depth", 5))
      {
         rqptr->rqHeader.WebDavDepthPtr = cptr = DICT_GET_VALUE(denptr);
         if (*cptr == '0')
            rqptr->rqHeader.WebDavDepth = WEBDAV_DEPTH_ZERO;
         else
         if (*cptr == '1')
            rqptr->rqHeader.WebDavDepth = WEBDAV_DEPTH_ONE;
         else
         if (MATCH8 (cptr, "infinity") ||
            strsame (cptr, "infinity", 8))
            rqptr->rqHeader.WebDavDepth = WEBDAV_DEPTH_INFINITY;
         else
         {
            InstanceGblSecIncrLong (&acptr->RequestErrorCount);
            rqptr->rqResponse.HttpStatus = 400;
            ErrorGeneral (rqptr, MsgFor(rqptr,MSG_REQUEST_FORMAT), FI_LI);
            return (false);
         }

         if (WATCHMOD (rqptr, WATCH_MOD_REQUEST))
            WatchDataFormatted ("depth:!UL\n", rqptr->rqHeader.WebDavDepth);
      }

      if (denptr = DictLookup (dicptr, DICT_TYPE_REQUEST, "destination", 11))
         rqptr->rqHeader.WebDavDestinationPtr = DICT_GET_VALUE(denptr);

      if (denptr = DictLookup (dicptr, DICT_TYPE_REQUEST, "if", 2))
         rqptr->rqHeader.WebDavIfPtr = DICT_GET_VALUE(denptr);

      if (denptr = DictLookup (dicptr, DICT_TYPE_REQUEST, "lock-token", 10))
         rqptr->rqHeader.WebDavLockTokenPtr = DICT_GET_VALUE(denptr);

      if (denptr = DictLookup (dicptr, DICT_TYPE_REQUEST, "overwrite", 9))
      {
         cptr = DICT_GET_VALUE(denptr);
         if (TOUP(*cptr) == 'T')
            rqptr->rqHeader.WebDavOverwrite = true;
         else
         if (TOUP(*cptr) == 'F')
            rqptr->rqHeader.WebDavOverwrite = false;
         else
         {
            InstanceGblSecIncrLong (&acptr->RequestErrorCount);
            rqptr->rqResponse.HttpStatus = 400;
            ErrorGeneral (rqptr, MsgFor(rqptr,MSG_REQUEST_FORMAT), FI_LI);
            return (false);
         }

         if (WATCHMOD (rqptr, WATCH_MOD_REQUEST))
            WatchDataFormatted ("overwrite:!&B\n",
               rqptr->rqHeader.WebDavOverwrite);
      }

      if (denptr = DictLookup (dicptr, DICT_TYPE_REQUEST, "timeout", 7))
         rqptr->rqHeader.WebDavTimeoutPtr = DICT_GET_VALUE(denptr);

      if (denptr = DictLookup (dicptr, DICT_TYPE_REQUEST, "translate", 9))
      {
         /* defacto header */
         rqptr->rqHeader.WebDavTranslatePtr = cptr = DICT_GET_VALUE(denptr);
         if (TOUP(*cptr) == 'T')
            rqptr->rqHeader.WebDavTranslateFalse = true;
         else
         if (TOUP(*cptr) == 'F')
            rqptr->rqHeader.WebDavTranslateFalse = false;
         else
         {
            InstanceGblSecIncrLong (&acptr->RequestErrorCount);
            rqptr->rqResponse.HttpStatus = 400;
            ErrorGeneral (rqptr, MsgFor(rqptr,MSG_REQUEST_FORMAT), FI_LI);
            return (false);
         }

         if (WATCHMOD (rqptr, WATCH_MOD_REQUEST))
            WatchDataFormatted ("translate:!&Z\n",
               rqptr->rqHeader.WebDavTranslatePtr);
      }

      /* an OS X (Apple) quirk */
      if (denptr = DictLookup (dicptr, DICT_TYPE_REQUEST,
                               "x-expected-entity-length", 24))
      {
         cptr = DICT_GET_VALUE(denptr);
         if (isdigit(*cptr))
         {
            rqptr->rqHeader.XExpectedEntityLength = atoi (cptr);
            if (WATCHMOD (rqptr, WATCH_MOD_REQUEST))
               WatchDataFormatted ("entlen:!UL\n",
                                   rqptr->rqHeader.XExpectedEntityLength);
         }
         else
         {
            InstanceGblSecIncrLong (&acptr->RequestErrorCount);
            rqptr->rqResponse.HttpStatus = 400;
            ErrorGeneral (rqptr, MsgFor(rqptr,MSG_REQUEST_FORMAT), FI_LI);
            return (false);
         }
      }
   }

   return (true);
}

/*****************************************************************************/
/*
Build a complete request header from dictionary request line and fields and
insert into the dictionary.  Return a pointer to that entry.
*/ 

DICT_ENTRY_STRUCT* RequestDictHeader (REQUEST_STRUCT *rqptr)

{
   int  size;
   char  *bptr, *cptr, *sptr, *zptr;
   DICT_ENTRY_STRUCT  *denptr, *renptr, *rldenptr;

   /*********/
   /* begin */
   /*********/

   if (WATCHMOD (rqptr, WATCH_MOD_REQUEST))
      WatchThis (WATCHITM(rqptr), WATCH_MOD_REQUEST, "RequestDictHeader()");

   /* only need to do it the once */
   if (denptr = DictLookup (rqptr->rqDictPtr, DICT_TYPE_INTERNAL,
                            "request_header", 14))
      return (denptr);

   /* if a request header has not (perhaps yet - e.g. 101) been parsed */
   if ((rldenptr = DictLookup (rqptr->rqDictPtr, DICT_TYPE_INTERNAL,
                               "request_line", 12)) == NULL)
      return (rldenptr);

   size = 0;
   size += DICT_GET_VALUE_LEN(rldenptr) + 4;
   DictIterate (rqptr->rqDictPtr, NULL);
   while ((denptr = DictIterate (rqptr->rqDictPtr, DICT_TYPE_REQUEST)) != NULL)
      size += DICT_GET_KEY_LEN(denptr) + DICT_GET_VALUE_LEN(denptr) + 4;

   /* insert the entry reserving space */
   renptr = DictInsert (rqptr->rqDictPtr, DICT_TYPE_INTERNAL,
                        "request_header", 14, NULL, size);
   bptr = DICT_GET_VALUE(renptr);
   zptr = (sptr = bptr) + size;

   /* request line */
   for (cptr = DICT_GET_VALUE(rldenptr);
        *cptr && sptr < zptr;
        *sptr++ = *cptr++);
   if (sptr < zptr) *sptr++ = '\r';     
   if (sptr < zptr) *sptr++ = '\n';     

   /* request fields */
   DictIterate (rqptr->rqDictPtr, NULL);
   while ((denptr = DictIterate (rqptr->rqDictPtr, DICT_TYPE_REQUEST)) != NULL)
   {
      for (cptr = DICT_GET_KEY(denptr);
           *cptr && sptr < zptr;
           *sptr++ = *cptr++);
      if (sptr < zptr) *sptr++ = ':';     
      if (sptr < zptr) *sptr++ = ' ';     
      for (cptr = DICT_GET_VALUE(denptr);
           *cptr && sptr < zptr;
           *sptr++ = *cptr++);
      if (sptr < zptr) *sptr++ = '\r';     
      if (sptr < zptr) *sptr++ = '\n';     
   }
   if (sptr < zptr) *sptr++ = '\r';     
   if (sptr < zptr) *sptr++ = '\n';     
   DictValueLength (renptr, sptr - bptr);

   if (sptr > zptr) ErrorNoticed (rqptr, SS$_RESULTOVF, NULL, FI_LI);

   return (renptr);
}

/*****************************************************************************/
/*
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.
*/ 

void RequestExecute (REQUEST_STRUCT *rqptr)

{
   int  status;
   char  *cptr, *mpptr;
   DICT_ENTRY_STRUCT  *denptr;

   /*********/
   /* begin */
   /*********/

   if (WATCHMOD (rqptr, WATCH_MOD_REQUEST))
      WatchThis (WATCHITM(rqptr), WATCH_MOD_REQUEST, "RequestExecute()");

   rqptr->RequestState = REQUEST_STATE_PROCESSING;

   if (rqptr->rqHeader.PathInfoPtr[0] == '/')
   {
      if (!ServiceFindVirtual (rqptr))
      {
         /* virtual service was not found */
         if (Config.cfServer.ServiceNotFoundUrl[0])
         {
            /* configuration specifies a redirection URL */
            ResponseLocation (rqptr, Config.cfServer.ServiceNotFoundUrl, -1);
            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_REQUEST))
   {
      WatchThis (WATCHITM(rqptr), WATCH_REQUEST, "!AZ !&P",
                 rqptr->rqHeader.MethodName, rqptr->rqHeader.RequestUriPtr);

      if (WATCHING (rqptr, WATCH_INTERNAL))
         DictWatch (rqptr->rqDictPtr, NULL, "*");

      if (rqptr->NotePadPtr)
         WatchThis (WATCHITM(rqptr), 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 (rqptr->X509ClientCertMeta)
   {
      /********************/
      /* X509 client cert */
      /********************/

      status = SesolaClientCert (rqptr, SESOLA_VERIFY_PEER_OPTIONAL,
                                 RequestExecute);
      /* this faux status indicates renegotiation is underway */
      if (status == SS$_WAITUSRLBL) return;
      ErrorExitVmsStatus (SS$_BUGCHECK, ErrorSanityCheck, FI_LI);
   }

   if (rqptr->rqPathSet.Http2ToHttp11)
   {
      /**********************/
      /* HTTP/2 to HTTP/1.1 */
      /**********************/

      Http2Error (rqptr->Http2Stream.Http2Ptr, 0, HTTP2_ERROR_HTTP11);
      rqptr->rqResponse.HttpStatus = 101;
      RequestEnd (rqptr);
      return;
   }

   if (rqptr->rqPathSet.Http2SendGoAway)
   {
      /******************/
      /* HTTP/2 go-away */
      /******************/

      Http2Error (rqptr->Http2Stream.Http2Ptr, 0,
                  rqptr->rqPathSet.Http2SendGoAway - 1);
      rqptr->rqResponse.HttpStatus = 205;  /* reset content */
      RequestEnd (rqptr);
      return;
   }

   if (rqptr->rqPathSet.Http2SendReset)
   {
      /*********************/
      /* HTTP/2 rst_stream */
      /*********************/

      Http2Error (rqptr->Http2Stream.Http2Ptr, 0,
                  rqptr->rqPathSet.Http2SendReset - 1);
      rqptr->rqResponse.HttpStatus = 205;  /* reset content */
      RequestEnd (rqptr);
      return;
   }

   if (rqptr->rqPathSet.Http2SendPing)
   {
      /***************/
      /* HTTP/2 ping */
      /***************/

      Http2Ping (rqptr->Http2Stream.Http2Ptr, 0, NULL, 0);
   }

   if (rqptr->rqPathSet.ChangeServicePtr)
   {
      /******************/
      /* change service */
      /******************/

      if (!ServiceChange (rqptr, rqptr->rqPathSet.ChangeServicePtr))
      {
         RequestEnd (rqptr);
         return;
      }
   }

   if (mpptr[0] == '\1' && mpptr[1])
   {
      /***********************/
      /* mapping redirection */
      /***********************/

      ResponseLocation (rqptr, mpptr+1, -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+1);
   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;
   }

   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 (WATCHMOD (rqptr, 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 */
      if (rqptr->rqPathSet.Http2ToHttp11)
      {
         /* proxy not supported as HTTP/2 ... HTTP/2 to HTTP/1.1 */
         Http2Error (rqptr->Http2Stream.Http2Ptr, 0, HTTP2_ERROR_HTTP11);
         rqptr->rqResponse.HttpStatus = 101;
         RequestEnd (rqptr);
         return;
      }
      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 (denptr = DictLookup (rqptr->rqDictPtr, DICT_TYPE_REQUEST,
                               "proxy-authorization", 19))
         rqptr->rqHeader.ProxyAuthorizationPtr = DICT_GET_VALUE(denptr);

      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 (WATCHMOD (rqptr, WATCH_MOD_REQUEST))
       WatchDataFormatted ("!&S\n", rqptr->rqAuth.FinalStatus);

   if (VMSnok (rqptr->rqAuth.FinalStatus))
   {
      /* if asynchronous authentication is not underway */
      if (rqptr->rqAuth.FinalStatus != AUTH_PENDING) 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.
*/

void RequestExecuteMappingError (REQUEST_STRUCT *rqptr)

{
   int  status;
   char  *mpptr;

   /*********/
   /* begin */
   /*********/

   if (WATCHMOD (rqptr, WATCH_MOD_REQUEST))
      WatchThis (WATCHITM(rqptr), 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 >= 200 &&
          rqptr->rqResponse.HttpStatus <= 299)
      {
         if (rqptr->rqResponse.HttpStatus == 204)
            ResponseHeader (rqptr, 204, NULL, -1, NULL, NULL);
         else
         {
            ResponseHeader (rqptr, rqptr->rqResponse.HttpStatus,
                            NULL, strlen(mpptr)+1, NULL, NULL);
            FaoToNet (rqptr, "!AZ\n", mpptr);
         }
         RequestEnd (rqptr);
         return;
      }

      if (rqptr->rqResponse.HttpStatus >= 300 &&
          rqptr->rqResponse.HttpStatus <= 399)
      {
         /* redirection, message "text" should be the location */
         ResponseLocation (rqptr, mpptr, -1);
         RequestEnd (rqptr);
         return;
      }

      if (rqptr->rqResponse.HttpStatus == 418)
      {
         /* 418 can be used to drop a connection using a "standard" code */
         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 2nn/3nn/4nn/5nn, use to abort connection */
      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.
*/

void RequestExecutePostAuth1 (REQUEST_STRUCT *rqptr)

{
   int  length, status;
   char  *cptr, *sptr;
   DICT_ENTRY_STRUCT  *denptr;

   /*********/
   /* begin */
   /*********/

   if (WATCHMOD (rqptr, WATCH_MOD_REQUEST))
      WatchThis (WATCHITM(rqptr), WATCH_MOD_REQUEST,
                 "RequestExecutePostAuth1() !&F !&S",
                 &RequestExecutePostAuth1, rqptr->rqAuth.FinalStatus);

   /* this may have been delivered asynchronously */
   rqptr->rqAuth.AstFunction = NULL;

   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;
   }

   if (rqptr->rqHeader.OriginPtr &&
       rqptr->rqPathSet.CorsAllowOriginPtr)
   {
      /*********************************/
      /* Cross-Origin Resource Sharing */
      /*********************************/

      ResponseCorsProcess (rqptr);
   }

   /************************************/
   /* 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);
      return;
   }

   if (strsame (rqptr->rqHeader.RequestUriPtr, HTTPD_VS_ADMIN,
                sizeof(HTTPD_VS_ADMIN)-1))
   {
      rqptr->rqCache.DoNotCache = rqptr->InternalRequest = true;
      AdminBegin (rqptr);
      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);
      return;
   }

   /******************************/
   /* check for script execution */
   /******************************/

   if (rqptr->ScriptName[0])
   {
      /* get the path derived from the script specification */
      sptr = rqptr->ScriptName;
      /* kludge to allow for 'implied' scripts (e.g. "/path/info") */
      if (!SAME2(sptr,'/\0'))
      {
         cptr = rqptr->rqHeader.PathInfoPtr;
         while (*sptr)
         {
            if (TOLO(*sptr) != TOLO(*cptr)) break;
            sptr++;
            cptr++;
         }
         if (!*sptr)
         {
            for (sptr = rqptr->rqHeader.PathInfoPtr; *cptr; *sptr++ = *cptr++);
            *sptr = '\0';
            length = sptr - rqptr->rqHeader.PathInfoPtr;
            rqptr->rqHeader.PathInfoLength = length;
            denptr = DictLookup (rqptr->rqDictPtr, DICT_TYPE_INTERNAL,
                                 "path_info", 9);
            if (denptr) DictValueLength (denptr, length);
         }
      }

      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 asynchronous authentication is not underway */
            if (rqptr->rqAuth.FinalStatus != AUTH_PENDING) 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.
*/

void RequestExecutePostAuth2 (REQUEST_STRUCT *rqptr)

{
   int  status;

   /*********/
   /* begin */
   /*********/

   if (WATCHMOD (rqptr, WATCH_MOD_REQUEST))
      WatchThis (WATCHITM(rqptr), WATCH_MOD_REQUEST,
                 "RequestExecutePostAuth2() !&F !&S",
                 &RequestExecutePostAuth2, rqptr->rqAuth.FinalStatus);

   /* this may have been delivered asynchronously */
   rqptr->rqAuth.AstFunction = NULL;

   if (VMSnok (rqptr->rqAuth.FinalStatus))
   {
      RequestEnd (rqptr);
      return;
   }

   if (rqptr->WebDavRequest &&
       !rqptr->RemoteUser[0] &&
       rqptr->rqAuth.SourceRealm != AUTH_SOURCE_EXTERNAL &&
       rqptr->rqAuth.SourceRealm != AUTH_SOURCE_OPAQUE)
   {
      /* WebDAV requests MUST have authorization applied! */
      if (WATCHING (rqptr, WATCH_REQUEST))
         WatchThis (WATCHITM(rqptr), WATCH_REQUEST,
                    "WEBDAV paths must have an authorization rule");
      DavWebResponse (rqptr, 403, SS$_NOPRIV, "no authorisation", 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.
*/

void RequestExecutePostThrottle (REQUEST_STRUCT *rqptr)

{
   int  status;
   char  *cptr, *sptr, *zptr;
   char  Md5String [8192];

   /*********/
   /* begin */
   /*********/

   if (WATCHMOD (rqptr, WATCH_MOD_REQUEST))
      WatchThis (WATCHITM(rqptr), 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);
   }

   /***************/
   /* from cache? */
   /***************/

   status = CacheSearch (rqptr);
   if (WATCHMOD (rqptr, WATCH_MOD_REQUEST))
      WatchThis (WATCHITM(rqptr), WATCH_MOD_REQUEST, "CacheSearch() !&S", status);
   /* success status indicates the request is being supplied from cache */
   if (VMSok (status)) return;

   /* not from initial cache search */
   RequestExecutePostCache1 (rqptr);
}

/*****************************************************************************/
/*
Called by RequestExecutePostThrottle() after cache search is performed.
Called by CacheEnd() if ACP data is collected and unsuitable.
*/

void RequestExecutePostCache1 (REQUEST_STRUCT *rqptr)

{
   BOOL  WildcardName;
   int  len;
   char  *cptr, *sptr, *zptr;

   /*********/
   /* begin */
   /*********/

   if (WATCHMOD (rqptr, WATCH_MOD_REQUEST))
      WatchThis (WATCHITM(rqptr), WATCH_MOD_REQUEST,
                 "RequestExecutePostCache1() !&F", &RequestExecutePostCache1);

   if (rqptr->ScriptName[0])
   {
      /**********/
      /* script */
      /**********/

      if (rqptr->RawSocketRequest)
      {
         /* append the mapped script to the request URI for logging purposes */
         len = strlen(rqptr->ScriptName) + rqptr->rqHeader.RequestUriLength + 2;
         cptr = rqptr->rqHeader.RequestUriPtr;
         sptr = rqptr->rqHeader.RequestUriPtr = VmGetHeap (rqptr, len);
         while (*cptr) *sptr++ = *cptr++;
         *sptr++ = '-';
         *sptr++ = '>';
         for (cptr = rqptr->ScriptName; *cptr; *sptr++ = *cptr++);
      }

      RequestScript (rqptr,
                     rqptr->RequestMappedScript,
                     rqptr->RequestMappedFile,
                     rqptr->RequestMappedRunTime);
      return;
   }

   if (rqptr->WebSocketRequest)
   {
      /*********************/
      /* 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, &RequestExecutePostCache2,
                 &rqptr->Md5HashPath, NULL, NULL);
      return;
   }

   /* not a file */
   RequestExecutePostCache2 (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.
*/

void RequestExecutePostCache2 (REQUEST_STRUCT *rqptr)

{
   BOOL  OfWebDav;

   int  status,
        Count;
   char  *cptr, *sptr,
         *MappedFilePtr;
   char  MappedFile [ODS_MAX_FILE_NAME_LENGTH+1];

   /*********/
   /* begin */
   /*********/

   if (WATCHMOD (rqptr, WATCH_MOD_REQUEST))
      WatchThis (WATCHITM(rqptr), WATCH_MOD_REQUEST,
                 "RequestExecutePostCache2() !&F", &RequestExecutePostCache2);

   RequestDclAst (rqptr, RequestExecutePostCache2, NULL);

   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_REQUEST))
         WatchThis (WATCHITM(rqptr), 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;
   }

   if (!rqptr->rqPathSet.OdsName ||
       rqptr->rqPathSet.OdsName == MAPURL_ODS_8BIT)
   {
      /* is there a potentially UTF-8 bit pattern here? */
      for (cptr = MappedFilePtr; *cptr && ((*cptr & 0xe0) != 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;
            if (WATCHING (rqptr, WATCH_REQUEST))
               WatchThis (WATCHITM(rqptr), WATCH_REQUEST,
                          "UTF-8 to native !AZ", MappedFilePtr);
         }
      }
   }
   else
   if (rqptr->rqPathSet.OdsName == MAPURL_ODS_UTF8)
   {
      if (ConvertUtf8ToEscape (rqptr->RequestMappedFile,
                               MappedFile, sizeof(MappedFile)) > 0)
      {
         MappedFilePtr = MappedFile;
         if (WATCHING (rqptr, WATCH_REQUEST))
            WatchThis (WATCHITM(rqptr), WATCH_REQUEST,
                       "UTF-8 to escaped 8-bit !AZ", MappedFilePtr);
      }
   }

   OdsStructInit (&rqptr->ParseOds, false);

   /* 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))
   {
      OfWebDav = rqptr->WebDavRequest ||
                 rqptr->rqPathSet.WebDavProfile ||
                 rqptr->rqPathSet.WebDavRead ||
                 rqptr->rqPathSet.WebDavServer ||
                 rqptr->rqPathSet.WebDavWrite;

      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 */
      /******************/

      /* WebDAV metadata (sub)directory? */
      if (rqptr->rqPathSet.WebDavRead ||
          rqptr->rqPathSet.WebDavWrite ||
          rqptr->rqPathSet.WebDavProfile ||
          rqptr->rqPathSet.WebDavServer)
      {
         if (DavMetaDir (rqptr, &rqptr->ParseOds))
         {
            ErrorVmsStatus (rqptr, RMS$_DNF, FI_LI);
            RequestEnd (rqptr);
            return;
         }
      }

      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 (&acptr->DoAutoScriptCount);

         if (WATCHING (rqptr, WATCH_REQUEST))
            WatchThis (WATCHITM(rqptr), WATCH_REQUEST, "AUTOSCRIPT \'!AZ\'",
                       rqptr->rqContentInfo.AutoScriptNamePtr);

         Count = strlen(cptr = rqptr->rqContentInfo.AutoScriptNamePtr);
         Count += rqptr->rqHeader.PathInfoLength;
         sptr = ResponseLocation (rqptr, NULL, Count+1);
         while (*cptr) *sptr++ = *cptr++;
         for (cptr = rqptr->rqHeader.PathInfoPtr; *cptr; *sptr++ = *cptr++);
         *sptr++ = '?';
         RequestEnd (rqptr);
         return;
      }
   }

   /**************************/
   /* implied keyword search */
   /**************************/

   if (!rqptr->KeywordRedirectCount &&
       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 (WATCHMOD (rqptr, WATCH_MOD_REQUEST))
             WatchDataFormatted ("!&Z\n",
                Config.cfScript.DefaultSearchExcludePtr);

         cptr = "";
         sptr = Config.cfScript.DefaultSearchExcludePtr;
         while (*sptr)
         {
            cptr = rqptr->ParseOds.NamTypePtr;

            if (WATCHMOD (rqptr, 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_REQUEST))
               WatchThis (WATCHITM(rqptr), 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_REQUEST))
            WatchThis (WATCHITM(rqptr), WATCH_REQUEST, "KEYWORD search \"!AZ\"",
                       rqptr->rqHeader.QueryStringPtr);

         /* local redirection, so keyword search script gets remapped */
         Count = Config.cfScript.DefaultSearchLength;
         Count += rqptr->rqHeader.PathInfoLength;
         sptr = ResponseLocation (rqptr, NULL, Count+1);
         for (cptr = Config.cfScript.DefaultSearch; *cptr; *sptr++ = *cptr++);
         for (cptr = rqptr->rqHeader.PathInfoPtr; *cptr; *sptr++ = *cptr++);
         *sptr++ = '?';
         rqptr->KeywordRedirectCount++;
         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!
*/ 

void RequestHomePage (REQUEST_STRUCT *rqptr)

{
   int  idx, status,
        Count;
   char  *cptr, *sptr,
         *FileTypePtr,
         *HomePageNamePtr;

   /*********/
   /* begin */
   /*********/

   if (WATCHMOD (rqptr, WATCH_MOD_REQUEST))
      WatchThis (WATCHITM(rqptr), WATCH_MOD_REQUEST,
                 "RequestHomePage() !UL", rqptr->RequestHomePageIndex);

   RequestDclAst (rqptr, RequestHomePage, NULL);

   if (VMSok (rqptr->HomePageStatus))
   {
      RequestEnd (rqptr);
      return;
   }

   if (rqptr->rqResponse.HeaderGenerated || 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 (WATCHMOD (rqptr, 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;
         RequestDclAst (rqptr, NULL, RequestHomePage);
         return;
      }

      /********************/
      /* script home page */
      /********************/

      if (WATCHING (rqptr, WATCH_REQUEST))
         WatchThis (WATCHITM(rqptr), 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);
      sptr = ResponseLocation (rqptr, NULL, Count+1);
      for (cptr = rqptr->rqHeader.PathInfoPtr; *cptr; *sptr++ = *cptr++);
      for (cptr = HomePageNamePtr; *cptr; *sptr++ = *cptr++);
      *sptr++ = '?';
      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 case is pre-processed HTML.  
*/ 

void RequestFile
(
REQUEST_STRUCT *rqptr,
REQUEST_AST NextTaskFunction,
REQUEST_AST NoSuchFileFunction
)
{
   /*********/
   /* begin */
   /*********/

   if (WATCHMOD (rqptr, WATCH_MOD_REQUEST))
      WatchThis (WATCHITM(rqptr), WATCH_MOD_REQUEST,
                 "RequestFile() !&A !&A !&Z !&Z",
                  NextTaskFunction, NoSuchFileFunction,
                  rqptr->ParseOds.ExpFileName,
                  rqptr->rqContentInfo.ContentTypePtr);

   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,
                                 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.
*/ 

void RequestFileNoType (REQUEST_STRUCT *rqptr)

{
   char  DirName [ODS_MAX_FILE_NAME_LENGTH+1];
   char  ch;
   char  *cptr, *sptr, *zptr;

   /*********/
   /* begin */
   /*********/

   if (WATCHMOD (rqptr, WATCH_MOD_REQUEST))
      WatchThis (WATCHITM(rqptr), WATCH_MOD_REQUEST, "RequestFileNoType()");

   RequestDclAst (rqptr, RequestFileNoType, NULL);

   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_REQUEST))
         WatchThis (WATCHITM(rqptr), WATCH_REQUEST, "DIRECTORY exists \'!AZ!AZ\'",
                    rqptr->ParseOds.NamDevicePtr, DirName);

      sptr = ResponseLocation (rqptr, NULL, rqptr->rqHeader.PathInfoLength+4);
      *sptr++ = '/';
      *sptr++ = '/';
      for (cptr = rqptr->rqHeader.PathInfoPtr; *cptr; *sptr++ = *cptr++);
      *sptr++ = '/';
      *sptr++ = '?';
      RequestEnd (rqptr);
      return;
   }
   else
   {
      rqptr->ParseOds.NamNamePtr[0] = ch;
      if (!rqptr->AccountingDone++)
         InstanceGblSecIncrLong (&acptr->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.
*/ 

void RequestScript
(
REQUEST_STRUCT *rqptr,
char *MappedScript,
char *MappedFile,
char *MappedRunTime
)
{
   int  status;
   char  *cptr;

   /*********/
   /* begin */
   /*********/

   if (WATCHMOD (rqptr, WATCH_MOD_REQUEST))
      WatchThis (WATCHITM(rqptr), 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_STREAM[0]) &&
       strsame (rqptr->ScriptName, ADMIN_SCRIPT_STREAM,
                sizeof(ADMIN_SCRIPT_STREAM)-1))
   {
      ResponseStream (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 */
      ResponseLocation (rqptr, rqptr->rqHeader.RequestUriPtr +
                               sizeof(ADMIN_SCRIPT_XRAY)-1, -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.
*/ 

void RequestGblSecUpdate (REQUEST_STRUCT *rqptr)

{
   static char  HexDigits [] = "0123456789abcdef";

   int  status;
   unsigned long  FaoVector [16];
   unsigned long  *vecptr;
   char  *cptr, *sptr, *zptr;
   HTTPD_GBLSEC  *gsptr;

   /*********/
   /* begin */
   /*********/

   if (WATCHMOD (rqptr, WATCH_MOD_REQUEST))
      WatchThis (WATCHITM(rqptr), 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;
   if (HTTP2_REQUEST(rqptr))
      gsptr->Request.HttpProtocol = HTTP_VERSION_2;
   else
      gsptr->Request.HttpProtocol = rqptr->rqResponse.HttpVersion;
   PUT_QUAD_QUAD (rqptr->NetIoPtr->BytesRawRx, gsptr->Request.BytesRawRx);
   PUT_QUAD_QUAD (rqptr->NetIoPtr->BytesRawTx, gsptr->Request.BytesRawTx);
   gsptr->Request.BytesPerSecond = rqptr->BytesPerSecond;
   gsptr->Request.BytesTxGzipPercent = rqptr->BytesTxGzipPercent;

   vecptr = FaoVector;
   *vecptr++ = rqptr->rqTime.BeginNumTime[2];
   *vecptr++ = rqptr->rqTime.BeginNumTime[3];
   *vecptr++ = rqptr->rqTime.BeginNumTime[4];
   *vecptr++ = rqptr->rqTime.BeginNumTime[5];
   FaoCheck (sizeof(FaoVector), &FaoVector, vecptr, FI_LI);
   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->ClientPtr->Lookup.HostName;
        *cptr && sptr < zptr;
        *sptr++ = *cptr++); 
   *sptr = '\0';

   zptr = (sptr = gsptr->Request.ClientIpAddressString) +
          sizeof(gsptr->Request.ClientIpAddressString)-1;
   for (cptr = rqptr->ClientPtr->IpAddressString;
        *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; cptr++)
   {
      if (*cptr >= '\"' && *cptr <= '~')
         *sptr++ = *cptr;
      else 
      {
         *sptr++ = '!';
         if (sptr < zptr) *sptr++ = HexDigits[(*cptr & 0xf0) >> 4];
         if (sptr < zptr) *sptr++ = HexDigits[(*cptr & 0x0f)];
      }
   }
   *sptr = '\0';

   if (rqptr->rqHeader.RequestUriPtr)
   {
      zptr = (sptr = gsptr->Request.Uri) + sizeof(gsptr->Request.Uri)-1;
      for (cptr = rqptr->rqHeader.RequestUriPtr; *cptr && sptr < zptr; cptr++) 
      {
         if (*cptr >= '\"' && *cptr <= '~')
            *sptr++ = *cptr;
         else 
         {
            *sptr++ = '!';
            if (sptr < zptr) *sptr++ = HexDigits[(*cptr & 0xf0) >> 4];
            if (sptr < zptr) *sptr++ = HexDigits[(*cptr & 0x0f)];
         }
      }
      *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 <zptr) *sptr++ = '\'';
      if (rqptr->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().
*/

void RequestHistory (REQUEST_STRUCT *rqptr)

{
   int  status;
   char  *cptr, *sptr, *zptr;
   struct RequestHistoryStruct  *hlptr;

   /*********/
   /* begin */
   /*********/

   if (WATCHMOD (rqptr, WATCH_MOD_REQUEST))
      WatchThis (WATCHITM(rqptr), 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, LIST_ENTRY_TYPE_HISTORY);

   PUT_QUAD_QUAD (rqptr->rqTime.BeginTime64, hlptr->BinaryTime);
   hlptr->ConnectNumber = rqptr->ConnectNumber;
   hlptr->RequestState = rqptr->RequestState;
   hlptr->ServicePtr = rqptr->ServicePtr;
   PUT_QUAD_QUAD (rqptr->NetIoPtr->BytesRawRx, hlptr->BytesRawRx);
   PUT_QUAD_QUAD (rqptr->NetIoPtr->BytesRawTx, hlptr->BytesRawTx);
   hlptr->BytesPerSecond = rqptr->BytesPerSecond;
   hlptr->BytesTxGzipPercent = rqptr->BytesTxGzipPercent;
   PUT_QUAD_QUAD (rqptr->rqResponse.Duration, hlptr->ResponseDuration);
   hlptr->TimeoutType = rqptr->rqTmr.TimeoutType;
   hlptr->ResponseStatusCode = rqptr->rqResponse.HttpStatus;

   if (HTTP2_REQUEST(rqptr))
      hlptr->HttpMajorVersion = 2;
   else
   if (rqptr->rqResponse.HttpVersion == HTTP_VERSION_1_1 ||
       rqptr->rqResponse.HttpVersion == HTTP_VERSION_1_0)
      hlptr->HttpMajorVersion = 1;
   else
      hlptr->HttpMajorVersion = 0;

   zptr = (sptr = hlptr->ClientAndRequest) + sizeof(hlptr->ClientAndRequest)-1;

   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->ClientPtr->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;
   else
      hlptr->Truncated = false;
   *sptr = '\0';
}

/*****************************************************************************/
/*
Return a string describing the request current state.
*/ 

char* RequestState (int state)

{
   /*********/
   /* begin */
   /*********/

   if (WATCH_MODULE (WATCH_MOD_REQUEST))
      WatchThis (WATCHALL, WATCH_MOD_REQUEST,
                 "RequestState() !UL", state);

   switch (state)
   {
      case 0 :                        return ("zero");
      case REQUEST_STATE_CONNECTED :  return ("connected");
      case REQUEST_STATE_PERSIST :    return ("persist");
      case REQUEST_STATE_HEADER :     return ("header");
      case REQUEST_STATE_PROCESSING : return ("processing");
      case REQUEST_STATE_RUNDOWN :    return ("rundown");
      case REQUEST_STATE_ENDING :     return ("ending");
      case REQUEST_STATE_ABORT :      return ("abort");
      case REQUEST_STATE_SHUTDOWN :   return ("shutdown");
      case REQUEST_STATE_NOMORE :     return ("nomore");
      default :                       return ("undefined");
   }
}

/*****************************************************************************/
/*
The request received has a significant header flaw.
Advise the client as simply as possible and bail.
Only for use by HTTP/1.n protocol requests.
*/ 

void RequestNBG (REQUEST_STRUCT *rqptr)

{
   static char ResponseToNBG [] =
"HTTP/1.1 400 Request header is significantly broken!\r\n\
Content-Type: text/plain\r\n\
Connection: close\r\n\
\r\n\
The request header is significantly broken!\n";


   /*********/
   /* begin */
   /*********/

   /* ensure the count of 400s ticks upwards */
   rqptr->rqResponse.HttpStatus = 400;
   InstanceMutexLock (INSTANCE_MUTEX_HTTPD);
   RequestHttpStatusCode (rqptr);
   AccountingPtr->RequestErrorCount++;
   InstanceMutexUnLock (INSTANCE_MUTEX_HTTPD);
   NetUpdateProcessing (rqptr, -1);

   rqptr->RequestState = REQUEST_STATE_ABORT;

   NetIoWrite (rqptr->NetIoPtr, RequestEnd4, rqptr,
               ResponseToNBG, sizeof(ResponseToNBG)-1);
}

/*****************************************************************************/
/*
Ad hoc mechanism to suppress request header dump.  Where request headers are
malformed a dump of the header is inserted into the process log as an
informational (see call code points).  In foreseable circumstamces (random or
malicious) this might chew a lot of disk storage.  Defining the logical name
WASD_NOTICED_NODUMP will suppress this.
*/ 

void RequestNoticedDump
(
char *DataPtr,
int DataLength
)
{
   /*********/
   /* begin */
   /*********/

   if (WATCH_MODULE (WATCH_MOD_REQUEST))
      WatchThis (WATCHALL, WATCH_MOD_REQUEST, "RequestNoticedDump()");

   if (SysTrnLnm ("WASD_NOTICED_NODUMP")) return;

   WatchDataDump (DataPtr, -DataLength);
}

/*****************************************************************************/
/*
Return a report on current and request history, listed most to least recent.
This function blocks while executing.
*/ 

void RequestReport (REQUEST_STRUCT *rqptr)

{
#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 [] =
"<p><table class=\"rghtrght\">\n\
<tr><th></th>\
<th class=\"sbttl talft\">Service&nbsp;/&nbsp;Client</th>\
<th class=\"sbttl talft\">Time&nbsp;/&nbsp;Request</th>\
<th class=\"sbttl\">HTTP</th>\
<th class=\"sbttl\">Rx</th>\
<th class=\"sbttl\">Tx</th>\
<th class=\"sbttl\">Gzip%</th>\
<th class=\"sbttl\">Bytes/Sec</th>\
<th class=\"sbttl\">Duration</th>\
<th class=\"sbttl\">Throttle</th>\
<th class=\"sbttl\">Connect</th>\
<th class=\"sbttl\">WATCH</th>\
<td class=\"pshlft\"></td>\
</tr>\n";

   static char  CurrentRequestFao [] =
"<tr>\
<th>!3ZL</th>\
<td class=\"talft\">!AZ//!AZ</td>\
<td class=\"talft\">!20%D</td>\
<td>!UL</td>\
<td>!&,@SQ</td>\
<td>!&,@SQ</td>\
<td>!UL</td>\
<td>!&L</td>\
<td>!AZ!AZ</td>\
<td>!&@</td>\
<td>!&@</td>\
<td>!&@</td>\
<td class=\"pshlft\"></td>\
</tr>\n\
<tr class=\"hlght\"><th></th>\
<td class=\"talft\">!&@</td>\
<td class=\"talft\" colspan=\"11\">!&@</td>\
</tr>\n";

   static char  HistoryFao [] =
"</table>\n\
<p><table class=\"rghtrght\">\n\
<tr><th></th>\
<th class=\"sbttl talft\">Service&nbsp;/&nbsp;Client</th>\
<th class=\"sbttl talft\">Time&nbsp;/&nbsp;Request</th>\
<th class=\"sbttl\">HTTP</th>\
<th class=\"sbttl\">Rx</th>\
<th class=\"sbttl\">Tx</th>\
<th class=\"sbttl\">Gzip%</th>\
<th class=\"sbttl\">Bytes/Sec</th>\
<th class=\"sbttl\">Duration</th>\
<th class=\"sbttl\">Status</th>\
<th class=\"sbttl\">Connect</th>\
<td class=\"pshlft\"></td>\
</tr>\n";

   static char  HistoryRequestFao [] =
"<tr>\
<th>!3ZL</th>\
<td class=\"talft\">!AZ//!AZ</td>\
<td class=\"talft\">!20%D</td>\
<td>!UL</td>\
<td>!&,@SQ</td>\
<td>!&,@SQ</td>\
<td>!UL</td>\
<td>!&L</td>\
<td>!AZ!AZ</td>\
<td>!UL</td>\
<td>!UL</td>\
<td class=\"pshlft\"></td>\
</tr>\n\
<tr class=\"hlght\"><th></th>\
<td class=\"talft\">!AZ</td>\
<td class=\"talft\" colspan=\"10\">\
<tt>!AZ</tt>!&?[truncated!]\r\r</td>\
</tr>\n";

   static char  RequestEmptyFao [] =
"<tr class=\"hlght\">\
<th><b>000</b></th><td class=\"talft\" colspan=\"10\"><i>empty</i></td>\
</tr>\n";

   static char  ConnectHistoryButtonFao [] = 
"</table>\n\
<p><table class=\"lftlft\">\n\
<tr><td>\n\
<form method=\"GET\" action=\"!AZ\">\n\
<input type=\"submit\" value=\" Purge Idle \">\n\
</form>\n\
</td><td>\n\
<form method=\"GET\" action=\"!AZ\">\n\
<input type=\"submit\" value=\" Force Disconnect \">\n\
</form>\n\
</td></tr>\n\
<tr><td colspan=\"2\">\n\
<br><a class=\"abttn bttn700\" href=\"!AZ\">Connect (!UL)</a>\n\
<br>!AZ\n\
<br><a class=\"abttn bttn700\" href=\"!AZ\">History</a>\n\
</td></tr>\n\
</table>\n\
!AZ\
</div>\n\
</body>\n\
</html>\n";

   static char  EndPageFao [] =
"</table>\n\
<p><table class=\"lftlft\">\n\
<tr><td>\n\
<form method=\"GET\" action=\"!AZ\">\n\
<input type=\"submit\" value=\"Purge Idle\">\n\
</form>\n\
</td><td>&nbsp;</td><td>\n\
<form method=\"GET\" action=\"!AZ\">\n\
<input type=\"submit\" value=\"Force Disconnect\">\n\
</form>\n\
</td></tr>\n\
</table>\n\
!AZ\
</div>\n\
</body>\n\
</html>\n";

   int  status,
        DisplayCount,
        EntryCount,
        ThrottleCount,
        ReportType;
   unsigned long  *vecptr;
   unsigned long  BinaryTime [QUAD2],
                  FaoVector [48],
                  ResponseDuration [QUAD2],
                  ResultTime [QUAD2];
   char  *cptr;
   char  ThrottleButton [256];
   struct RequestHistoryStruct  *rqhptr;
   HTTP2_STRUCT  *h2ptr;
   LIST_ENTRY  *leptr;
   REQUEST_STRUCT  *rqeptr;

   /*********/
   /* begin */
   /*********/

   if (WATCH_MODULE(WATCH_MOD_REQUEST))
      WatchThis (WATCHALL, WATCH_MOD_REQUEST, "RequestReport()");

   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 &amp; History Report");
   else
   if (ReportType == REPORT_CONNECT)
      AdminPageTitle (rqptr, "Request &amp; 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.BeginTime64;

      if (rqeptr->Http2Stream.Http2Ptr)
         *vecptr++ = 2;
      else
      if (rqeptr->rqResponse.HttpVersion == HTTP_VERSION_1_1 ||
          rqeptr->rqResponse.HttpVersion == HTTP_VERSION_1_0)
         *vecptr++ = 1;
      else
         *vecptr++ = 0;

      *vecptr++ = &rqeptr->NetIoPtr->BytesRawRx;
      *vecptr++ = &rqeptr->NetIoPtr->BytesRawTx;
      *vecptr++ = rqeptr->BytesTxGzipPercent;

      status = lib$sub_times (&BinaryTime, &rqeptr->rqTime.BeginTime64,
                              &ResponseDuration);
      if (VMSnok (status)) ZERO_DELTA_TIME (rqeptr->rqResponse.Duration);

      *vecptr++ = BytesPerSecond (&rqeptr->NetIoPtr->BytesRawRx,
                                  &rqeptr->NetIoPtr->BytesRawTx,
                                  &ResponseDuration);
      *vecptr++ = HttpdTimeoutType(rqeptr->rqTmr.TimeoutType);
      *vecptr++ = DurationString (rqeptr, &ResponseDuration);

      if (rqeptr->rqPathSet.ThrottleFrom)
      {
         if (rqeptr->ThrottleListEntry.DataPtr)
         {
            *vecptr++ =
"<font size=\"-1\">\
<a class=\"abttn\" href=\"!AZ?this=!UL\">release</a><br>\
<a class=\"abttn\" href=\"!AZ?this=!UL\">terminate</a>\
</font>";
            *vecptr++ = ADMIN_CONTROL_THROTTLE_RELEASE;
            *vecptr++ = rqeptr->ConnectNumber;
            *vecptr++ = ADMIN_CONTROL_THROTTLE_TERMINATE;
            *vecptr++ = rqeptr->ConnectNumber;
         }
         else
            *vecptr++ = "<font size=\"-1\">processing</font>";
      }
      else
         *vecptr++ = "";

      if (rqeptr->Http2Stream.Http2Ptr)
      {
         *vecptr++ = "!UL (!UL)";
         *vecptr++ = rqeptr->ConnectNumber;
         *vecptr++ = rqeptr->Http2Stream.Http2Ptr->ConnectNumber;
      }
      else
      {
         *vecptr++ = "!UL";
         *vecptr++ = rqeptr->ConnectNumber;
      }

      if (rqeptr->ConnectNumber == rqptr->ConnectNumber)
         *vecptr++ = "current";
      else
      if (rqeptr == Watch.RequestPtr)
         *vecptr++ = "WATCHing";
      else
      {
         *vecptr++ =
"<a class=\"abttn bttn090 px1\" href=\"!AZ?at=!UL\">P</a>\
<a class=\"abttn bttn090 px1\" href=\"!AZ?at=!UL&this=!UL\">+</a>\
<a class=\"abttn bttn090 px1\" href=\"!AZ?this=!UL\">W</a>";
         *vecptr++ = ADMIN_REPORT_WATCH;
         *vecptr++ = rqeptr->ConnectNumber;
         *vecptr++ = ADMIN_REPORT_WATCH;
         *vecptr++ = rqeptr->ConnectNumber;
         *vecptr++ = rqeptr->ConnectNumber;
         *vecptr++ = ADMIN_REPORT_WATCH;
         *vecptr++ = rqeptr->ConnectNumber;
      }

      if (ReportType == REPORT_CONNECT)
      {
         *vecptr++ = "!AZ,!UL";
         *vecptr++ = rqeptr->ClientPtr->Lookup.HostName;
         *vecptr++ = rqeptr->ClientPtr->IpPort;
      }
      else
         *vecptr++ = UserAtClient(rqeptr);

      if (rqeptr->rqHeader.PathInfoPtr &&
          rqeptr->rqHeader.QueryStringPtr)
      {
         *vecptr++ = "<tt>!AZ&nbsp;!AZ</tt>";
         *vecptr++ = rqeptr->rqHeader.MethodName;
         *vecptr++ = rqeptr->rqHeader.RequestUriPtr;
      }
      else
      {
         if (rqeptr->rqNet.PersistentCount)
         {
            *vecptr++ = "[persistent]&nbsp;&nbsp;&nbsp;!UL";
            *vecptr++ = rqeptr->rqNet.PersistentCount;
         }
         else
            /* proactive connection (e.g. what Chrome tends to do) */
            *vecptr++ = "[connected]";
      }

      FaoCheck (sizeof(FaoVector), &FaoVector, vecptr, FI_LI);
      status = FaolToNet (rqptr, CurrentRequestFao, &FaoVector);
      if (VMSnok (status)) ErrorNoticed (rqptr, status, "FaolToNet()", FI_LI);
   }

   if (ReportType == REPORT_CONNECT)
   {
      /******************************/
      /* current HTTP/2 connections */
      /******************************/

      /* process the HTTP/2 connection list from least to most recent */
      for (leptr = Http2List.HeadPtr; leptr; leptr = leptr->NextPtr)
      {
         h2ptr = (HTTP2_STRUCT*)leptr;
         EntryCount++;

         vecptr = FaoVector;

         *vecptr++ = ++DisplayCount;
         *vecptr++ = h2ptr->ServicePtr->RequestSchemeNamePtr;
         *vecptr++ = h2ptr->ServicePtr->ServerHostPort;
         *vecptr++ = h2ptr->ConnectTime64;
         *vecptr++ = 2;
         *vecptr++ = h2ptr->NetIoPtr->BytesRawRx;
         *vecptr++ = h2ptr->NetIoPtr->BytesRawTx;
         *vecptr++ = 0;

         status = lib$sub_times (&BinaryTime, h2ptr->ConnectTime64,
                                 &ResponseDuration);
         if (VMSnok (status)) ZERO_DELTA_TIME (ResponseDuration);

         *vecptr++ = BytesPerSecond (h2ptr->NetIoPtr->BytesRawRx,
                                     h2ptr->NetIoPtr->BytesRawTx,
                                     ResponseDuration);

         *vecptr++ = DurationString (rqptr, &ResponseDuration);
         *vecptr++ = "";

         *vecptr++ = "";

         *vecptr++ = "!UL";
         *vecptr++ = h2ptr->ConnectNumber;

         if (rqptr->Http2Stream.Http2Ptr != NULL &&
             rqptr->Http2Stream.Http2Ptr->ConnectNumber == h2ptr->ConnectNumber)
            *vecptr++ = "current";
         else
         {
            *vecptr++ =
"<a class=\"abttn bttn090 px1\" href=\"!AZ?at=!UL\">P</a>\
<a class=\"abttn bttn090 px1\" href=\"!AZ?at=!UL&this=!UL\">+</a>\
<a class=\"abttn bttn090 px1\" href=\"!AZ?this=!UL\">W</a>";
            *vecptr++ = ADMIN_REPORT_WATCH;
            *vecptr++ = h2ptr->ConnectNumber;
            *vecptr++ = ADMIN_REPORT_WATCH;
            *vecptr++ = h2ptr->ConnectNumber;
            *vecptr++ = h2ptr->ConnectNumber;
            *vecptr++ = ADMIN_REPORT_WATCH;
            *vecptr++ = h2ptr->ConnectNumber;
         }
         
         *vecptr++ = "!AZ,!UL";
         *vecptr++ = h2ptr->ClientPtr->Lookup.HostName;
         *vecptr++ = h2ptr->ClientPtr->IpPort;
         *vecptr++ = "[HTTP/2]&nbsp;&nbsp;&nbsp;!UL:!UL:!UL";
         *vecptr++ = h2ptr->RequestCurrent;
         *vecptr++ = h2ptr->RequestPeak;
         *vecptr++ = h2ptr->RequestCount;

         FaoCheck (sizeof(FaoVector), &FaoVector, vecptr, FI_LI);
         status = FaolToNet (rqptr, CurrentRequestFao, &FaoVector);
         if (VMSnok (status))
            ErrorNoticed (rqptr, status, "FaolToNet()", FI_LI);
      }
   }
   else
   {
      /* just add in the number of HTTP/2 connections */
      for (leptr = Http2List.HeadPtr; leptr; leptr = leptr->NextPtr)
         EntryCount++;
   }

   if (ReportType != REPORT_HISTORY)
   {
      /***************/
      /* not history */
      /***************/

      if (ReportType == REPORT_CURRENT)
      {
         if (ThrottleCount)
            sprintf (ThrottleButton,
"<a class=\"abttn bttn700\" href=\"%s\">Throttle (%d)</a>",
                     ADMIN_REPORT_REQUEST_THROTTLE,
                     ThrottleCount);
         else
            strcpy (ThrottleButton,
"<a class=\"abttn bttn700 dbttn\">Throttle</a>");

         status = FaoToNet (rqptr, ConnectHistoryButtonFao,
                            ADMIN_CONTROL_NET_PURGE,
                            ADMIN_CONTROL_NET_PURGE_ALL,
                            ADMIN_REPORT_REQUEST_CONNECT,
                            EntryCount - DisplayCount,
                            ThrottleButton,
                            ADMIN_REPORT_REQUEST_HISTORY,
                            AdminRefresh());
      }
      else
      if (ReportType == REPORT_THROTTLE)
      {
         if (!DisplayCount) FaolToNet (rqptr, RequestEmptyFao, NULL);
         status = FaoToNet (rqptr, EndPageFao,
                            ADMIN_CONTROL_NET_PURGE,
                            ADMIN_CONTROL_NET_PURGE_ALL,
                            AdminRefresh());
      }
      else
         status = FaoToNet (rqptr, EndPageFao,
                            ADMIN_CONTROL_NET_PURGE,
                            ADMIN_CONTROL_NET_PURGE_ALL,
                            AdminRefresh());

      if (VMSnok (status)) ErrorNoticed (rqptr, status, "FaoToNet()", FI_LI);

      rqptr->rqResponse.PreExpired = PRE_EXPIRE_ADMIN;
      ResponseHeader200 (rqptr, "text/html", &rqptr->NetWriteBufferDsc);

      AdminEnd (rqptr);
      return;
   }

   /*******************/
   /* request history */
   /*******************/

   vecptr = FaoVector;
   *vecptr++ = RequestHistoryMax;

   FaoCheck (sizeof(FaoVector), &FaoVector, vecptr, FI_LI);
   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->HttpMajorVersion;

      *vecptr++ = &rqhptr->BytesRawRx;
      *vecptr++ = &rqhptr->BytesRawTx;
      *vecptr++ = rqhptr->BytesTxGzipPercent;
      *vecptr++ = rqhptr->BytesPerSecond;
      *vecptr++ = DurationString (rqptr, &rqhptr->ResponseDuration);
      *vecptr++ = HttpdTimeoutType(rqhptr->TimeoutType);

      *vecptr++ = rqhptr->ResponseStatusCode;
      *vecptr++ = rqhptr->ConnectNumber;

      *vecptr++ = rqhptr->ClientAndRequest;
      *vecptr++ = rqhptr->RequestPtr;
      *vecptr++ = rqhptr->Truncated;

      FaoCheck (sizeof(FaoVector), &FaoVector, vecptr, FI_LI);
      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,
                      AdminRefresh());
   if (VMSnok (status)) ErrorNoticed (rqptr, status, "FaolToNet()", FI_LI);

   rqptr->rqResponse.PreExpired = PRE_EXPIRE_ADMIN;
   ResponseHeader200 (rqptr, "text/html", &rqptr->NetWriteBufferDsc);

   AdminEnd (rqptr);
}

/*****************************************************************************/
/*
Dump the current and history request lists, for 'crash' analysis.
Use vanilla output in case other routines are implicated.
*/ 

void RequestDump ()

{
   static $DESCRIPTOR (RequestFaoDsc,
"C!3ZL !UL !AZ//!AZ !AZ HTTP/!UL !%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 !AZ HTTP/!UL !%D !AZ!AZ !UL !UL !AZ !AZ!AZ !3ZL\n\0");

   int  status,
        Count;
   unsigned long  *vecptr;
   unsigned long  BinaryTime [QUAD2],
                  FaoVector [32],
                  ResponseDuration [QUAD2],
                  ResultTime [QUAD2];
   char  Buffer [8192];
   LIST_ENTRY  *leptr;
   REQUEST_STRUCT  *rqeptr;
   struct RequestHistoryStruct  *rqhptr;
   $DESCRIPTOR (BufferDsc, Buffer);

   /*********/
   /* begin */
   /*********/

   if (WATCH_MODULE(WATCH_MOD_REQUEST))
      WatchThis (WATCHALL, 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++ = RequestState (rqeptr->RequestState);

      if (rqeptr->Http2Stream.Http2Ptr)
         *vecptr++ = 2;
      else
      if (rqeptr->rqResponse.HttpVersion == HTTP_VERSION_1_1 ||
          rqeptr->rqResponse.HttpVersion == HTTP_VERSION_1_0)
         *vecptr++ = 1;
      else
         *vecptr++ = 0;

      *vecptr++ = &rqeptr->rqTime.BeginTime64;

      PUT_QUAD_QUAD (BinaryTime, ResponseDuration);
      SUB_QUAD_QUAD (rqeptr->rqTime.BeginTime64, ResponseDuration);
      *vecptr++ = DurationString (NULL, ResponseDuration);
      *vecptr++ = HttpdTimeoutType(rqeptr->rqTmr.TimeoutType);

      /* unlikely to have >4GB! */
      *vecptr++ = rqeptr->NetIoPtr->BytesRawRx[0];
      *vecptr++ = rqeptr->rqNet.ReadErrorCount;
      *vecptr++ = rqeptr->NetIoPtr->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->ClientPtr->Lookup.HostName;

      if (rqeptr->rqHeader.PathInfoPtr &&
          rqeptr->rqHeader.QueryStringPtr)
      {
         *vecptr++ = rqeptr->rqHeader.MethodName;
         *vecptr++ = " ";
         *vecptr++ = rqeptr->rqHeader.RequestUriPtr;
      }
      else
      if (rqeptr->rqNet.PersistentCount)
      {
         static char  PersistString [32];
         sprintf (PersistString, "[persistent:%u]",
                  rqeptr->rqNet.PersistentCount); 
         *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++ = RequestState (rqhptr->RequestState);
      *vecptr++ = rqhptr->HttpMajorVersion;

      *vecptr++ = &rqhptr->BinaryTime;
      *vecptr++ = DurationString (NULL, &rqhptr->ResponseDuration);
      *vecptr++ = HttpdTimeoutType(rqhptr->TimeoutType);

      /* 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);
}

/*****************************************************************************/

