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

A request for an HTTP/1.0 persistent connection is looked for in the request
header and if found a boolean set to indicate the request.

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
---------------
12-AUG-2004  MGD  bugfix; HttpTimerSet() after mapping in case of SET timeout
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  persistant 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 NetWriteFaol(), 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_V6
#undef _VMS_V6_SOURCE
#define _VMS_V6_SOURCE
#undef __VMS_VER
#define __VMS_VER 60000000
#undef __CRTL_VER
#define __CRTL_VER 60000000
#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 <ssdef.h>
#include <stsdef.h>

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

#define WASD_MODULE "REQUEST"

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

/** TODO this storage (see requestBodyDiscard()) is a temporary kludge **/
BodyDiscardChunkCount = BODY_DISCARD_CHUNK_COUNT;

LIST_HEAD  RequestList;
LIST_HEAD  RequestHistoryList;
int  RequestHistoryCount,
     RequestHistoryMax;

/********************/
/* external storage */
/********************/

#ifdef DBUG
extern BOOL Debug;
#else
#define Debug 0 
#endif

extern BOOL  CacheEnabled,
             ControlExitRequested,
             ControlRestartRequested,
             HttpdTicking,
             LoggingEnabled,
             MonitorEnabled,
             OdsExtended;

extern int  ActivityConnectCurrent,
            ActivityTotalMinutes,
            ErrorsNoticedCount,
            ExitStatus,
            HttpdGblSecPages,
            InstanceConnectCurrent,
            InstanceNodeConfig,
            NetReadBufferSize,
            OpcomMessages,
            OutputBufferSize,
            ProxyServiceCount,
            ServiceCount,
            SsiSizeMax;

extern unsigned long  InstanceMutexCount[],
                      InstanceMutexWaitCount[];

extern char  ConfigContentTypeIsMap[],
             ConfigContentTypeMenu[],
             ConfigContentTypeSsi[],
             ConfigContentTypeUrl[],
             ErrorSanityCheck[],
             HttpProtocol[],
             ServerHostPort[],
             SoftwareID[],
             Utility[];

extern ACCOUNTING_STRUCT  *AccountingPtr;
extern CONFIG_STRUCT  Config;
extern HTTPD_GBLSEC  *HttpdGblSecPtr;
extern HTTPD_PROCESS  HttpdProcess;
extern MSG_STRUCT  Msgs;
extern MAPPING_META  *MappingMetaPtr;
extern SUPERVISOR_LIST  SupervisorListArray[];
extern WATCH_STRUCT  Watch;

/*****************************************************************************/
/*
This function can be called from two sources.  First, NetAccept() where a new
connection has been established and memory has just been allocated for this
connection's thread structure.  Second, from RequestEnd() where a
persistent connection (HTTP/1.0) is involved.

Both then queue a read from the network connection which calls an AST
function when the read completes, to process the request.  The keep-alive
read usually has a far shorter timeout associated with it so that rapid,
sequential reads (as happens when a document is being populated with images
for instance) are serviced from the one connection, but these are not left
open consuming resources on the off-chance of another request in the
not-too-distance future.

Persistent connections are implemented by disposing of all memory allocated
on a request's heap, and all data in the connection thread structure by
zeroing, EXCEPT that above the field 'RetainAboveZeroBelow' (client
connection information), then treating the almost-amnesic structure like a
brand-new connection (only with a shorter timeout period).

Every hour recheck the GMT time offset to crudely detect daylight saving and
other timezone changes.
*/ 

RequestBegin
(
REQUEST_STRUCT *rqptr,
BOOL NewConnection
)
{
   static int  PrevHour = -1;

   int  status;

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

   if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD_REQUEST))
      WatchThis (rqptr, FI_LI, WATCH_MOD_REQUEST,
                 "RequestBegin() !&X !UL", rqptr, NewConnection);

   if (NewConnection)
   {
      /******************/
      /* new connection */
      /******************/

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

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

      if (PrevHour != rqptr->rqTime.VmsVector[3])
      {
         /* every hour recheck the GMT time offset */
         if (VMSnok (status = TimeSetGMT ()))
            ErrorExitVmsStatus (status, "TimeSetGMT()", FI_LI);
         PrevHour = rqptr->rqTime.VmsVector[3];
      }

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

      /* initialize the timer for input */
      HttpdTimerSet (rqptr, TIMER_INPUT, 0);

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

      if (rqptr->ServicePtr->RequestScheme == SCHEME_HTTPS)
      {
         /* Secure Sockets Layer ("https://") transaction */
         SesolaNetRequestBegin (rqptr);
         /* if NULL then SSL processing didn't get off the ground! */
         if (!rqptr->rqNet.SesolaPtr) RequestEnd (rqptr);
         return;
      }
   }
   else
   {
      /*************************/
      /* persistent connection */
      /*************************/

      if (WATCHING(rqptr) && WATCH_CATEGORY(WATCH_CONNECT))
         WatchThis (rqptr, FI_LI, WATCH_CONNECT,
                    "KEEP-ALIVE !AZ,!UL",
                    rqptr->rqClient.Lookup.HostName, rqptr->rqClient.IpPort); 

      /*
         These storage areas exist in the permanent request structure but
         the memory is from the request heap.  They persist across redirects
         but not persistent connections (keep-alives).
      */
      rqptr->NotePadPtr = rqptr->ProxyReverseLocationPtr = NULL;
      rqptr->NotePadLength = 0;

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

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

      if (rqptr->ServicePtr->ErrorReportPath[0])
      {
         rqptr->rqResponse.ErrorReportByRedirect = true;
         /* reset thread-permanent storage */
         rqptr->RedirectErrorStatusCode = 0;
         rqptr->RedirectErrorAuthRealmDescrPtr = NULL;
      }

      /* initialize the timer for keep-alive */
      HttpdTimerSet (rqptr, TIMER_KEEPALIVE, 0);
   }

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

   NetRead (rqptr, &RequestGet,
            rqptr->rqNet.ReadBufferPtr, rqptr->rqNet.ReadBufferSize);
}

/*****************************************************************************/
/*
This function may be AST-delivery executed ONE OR MORE TIMES, FROM ITSELF, 
before a connection is actually disposed of.  Basically, all ASTs hard-wired or
declared *MUST* deliver back to here!

This is the final request rundown function.  It checks for various tasks and/or
associated structures and calls their finalize function to progressively shut
the request processing down in a hopefully, orderly fashion.
*/

RequestEnd (REQUEST_STRUCT *rqptr)

{
   static long  Addx2 = 2,
                Subx2 = 2,
                EdivTen = 10;

   BOOL  PersistentConnection;
   int  idx, status,
        StatusCodeGroup;
   unsigned long  Remainder,
                  Seconds,
                  SubSeconds;
   unsigned long  BinaryTime [2],
                  QuadScratch [2],
                  ResultTime [2];
   char  *cptr;
   FILE_CONTENT  *fcptr;
   SERVICE_STRUCT  *svptr;

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

   if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD_REQUEST))
      WatchThis (rqptr, FI_LI, WATCH_MOD_REQUEST,
                 "RequestEnd() !&F", &RequestEnd);

   if (rqptr->rqPathSet.ThrottleFrom) ThrottleEnd (rqptr);

   if (rqptr->rqClient.Channel &&
       rqptr->BytesRx)
   {
      if (rqptr->rqResponse.LocationPtr &&
          rqptr->rqResponse.LocationPtr[0])
      {
         /***************/
         /* redirection */
         /***************/

         status = RequestRedirect (rqptr);

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

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

         /* legitimate error whilst generating redirection */
         RequestEnd (rqptr);
         return;
      }

      if (fcptr = rqptr->FileContentPtr)
      {
         /***********************/
         /* content and handler */
         /***********************/

         if (fcptr->ContentLength > fcptr->ContentSizeMax)
         {
            if (WATCHING(rqptr) && (WATCH_CATEGORY(WATCH_CGI) ||
                                    WATCH_CATEGORY(WATCH_REQUEST)))
               WatchThis (rqptr, FI_LI, WATCH_CATEGORY(WATCH_CGI) ?
                                           WATCH_CGI : WATCH_REQUEST,
                          "CONTENT !UL exceeded !UL bytes max",
                          fcptr->ContentLength, fcptr->ContentSizeMax);
            ErrorVmsStatus (rqptr, SS$_BUFFEROVF, FI_LI);
            rqptr->FileContentPtr = NULL;
         }
         else
         {
            /* next task gets control once the 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;
         }
      }

      /* flush anything currently remaining in the thread's output buffer */
      if (rqptr->rqOutput.BufferCount)
      {
         NetWriteFullFlush (rqptr, &RequestEnd);
         return;
      }

      /* if an error has been reported send this to the client */
      if (ERROR_REPORTED (rqptr))
      {
         ErrorSendToClient (rqptr);
         return;
      }

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

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

   if ((rqptr->rqHeader.Method == HTTP_METHOD_POST ||
        rqptr->rqHeader.Method == HTTP_METHOD_PUT) &&
        rqptr->rqHeader.ContentLength &&
        rqptr->rqBody.DataStatus != SS$_ENDOFFILE)
   {
      RequestDiscardBody (rqptr);
      return;
   }

#if WATCH_MOD

   if (WATCHING(rqptr) &&
       Watch.Category == WATCH_ONE_SHOT_CAT &&
       Watch.Module == WATCH_ONE_SHOT_MOD &&
       Watch.RequestPtr)
      WatchPeek (Watch.RequestPtr, rqptr);

#endif /* WATCH_MOD */

   /* if a proxied request */
   if (rqptr->ProxyTaskPtr)
   {
      ProxyEnd (rqptr->ProxyTaskPtr);
      return;
   }

   /* if Secure Sockets Layer request */
   if (rqptr->rqNet.SesolaPtr)
   {
      SesolaNetRequestEnd (rqptr);
      return;
   }

   /****************************/
   /* end of possible re-calls */
   /****************************/

   /* 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 this request was WATCHing */
   if (rqptr == Watch.RequestPtr) WatchEnd (rqptr);

   if (rqptr->KeepAliveResponse &&
       rqptr->KeepAliveRequest &&
       rqptr->rqClient.Channel &&
       !ControlExitRequested &&
       !ControlRestartRequested)
      PersistentConnection = true;
   else
      PersistentConnection = false;

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

   InstanceMutexLock (INSTANCE_MUTEX_HTTPD);

   if (rqptr->KeepAliveTimeout)
   {
      if (AccountingPtr->ConnectCurrentKeepAlive)
         AccountingPtr->ConnectCurrentKeepAlive--;
   }
   else
   {
      /* this was a real request, do some accounting */
      svptr = rqptr->ServicePtr;
      svptr->ConnectCount++;

      if (AccountingPtr->ConnectProcessing) AccountingPtr->ConnectProcessing--;

      if (rqptr->rqNet.KeepAliveCount > AccountingPtr->RequestKeepAliveMax)
         AccountingPtr->RequestKeepAliveMax = rqptr->rqNet.KeepAliveCount;  

      sys$gettim (&BinaryTime);
      status = lib$subx (&BinaryTime, &rqptr->rqTime.Vms64bit, 
                         &ResultTime, &Subx2);
      /* convert to microseconds (longword becomes 71 minutes max) */
      if (VMSok (status))
         status = lib$ediv (&EdivTen, &ResultTime,
                            &rqptr->rqResponse.Duration, &Remainder);
      if (VMSnok (status)) rqptr->rqResponse.Duration = 0;

      /* some (double-precision) accounting */
      AccountingPtr->ResponseDurationCount++;
      QuadScratch[0] = rqptr->rqResponse.Duration;
      QuadScratch[1] = 0;
      lib$addx (&QuadScratch, &AccountingPtr->QuadResponseDuration,
                &AccountingPtr->QuadResponseDuration, &Addx2);

      /* only update non-zero minima */
      if (rqptr->rqResponse.Duration &&
          rqptr->rqResponse.Duration < AccountingPtr->ResponseDurationMin)
         AccountingPtr->ResponseDurationMin = rqptr->rqResponse.Duration;
      /* only update maxima from non-timer-expired requests */
      if (!rqptr->rqTmr.TerminatedCount &&
          rqptr->rqResponse.Duration > AccountingPtr->ResponseDurationMax)
         AccountingPtr->ResponseDurationMax = rqptr->rqResponse.Duration;

      /* a little more (double-precision) accounting */
      QuadScratch[0] = rqptr->BytesRawTx;
      QuadScratch[1] = 0;
      lib$addx (&QuadScratch, &svptr->QuadBytesRawTx,
                &svptr->QuadBytesRawTx, &Addx2);
      lib$addx (&QuadScratch, &AccountingPtr->QuadBytesRawTx,
                &AccountingPtr->QuadBytesRawTx, &Addx2);
      QuadScratch[0] = rqptr->BytesRawRx;
      QuadScratch[1] = 0;
      lib$addx (&QuadScratch, &svptr->QuadBytesRawRx,
                &svptr->QuadBytesRawRx, &Addx2);
      lib$addx (&QuadScratch, &AccountingPtr->QuadBytesRawRx,
                &AccountingPtr->QuadBytesRawRx, &Addx2);

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

      StatusCodeGroup = rqptr->rqResponse.HttpStatus / 100;
      if (StatusCodeGroup < 0 || StatusCodeGroup > 5) StatusCodeGroup = 0;
      AccountingPtr->ResponseStatusCodeCountArray[StatusCodeGroup]++;
      if (rqptr->rqResponse.HttpStatus == 403)
         AccountingPtr->RequestForbiddenCount++;
   }

   if (PersistentConnection)
   {
      /* going into keep-alive waiting for a (potential) subsequent request */
      AccountingPtr->ConnectCurrentKeepAlive++;
      if (AccountingPtr->ConnectCurrentKeepAlive >
          AccountingPtr->ConnectPeakKeepAlive)
         AccountingPtr->ConnectPeakKeepAlive =
            AccountingPtr->ConnectCurrentKeepAlive;
   }
   else
   {
      /* one less to worry about! */
      ActivityConnectCurrent = AccountingPtr->ConnectCurrent;
      if (InstanceConnectCurrent) InstanceConnectCurrent--;
      if (AccountingPtr->ConnectCurrent) AccountingPtr->ConnectCurrent--;
   }

   /* update the global section used by HTTPDMON utility */
   if (MonitorEnabled) RequestGblSecUpdate (rqptr);

   if (InstanceNodeConfig > 1)
   {
      /* update the global mutex accounting (with whatever up to this point) */
      for (idx = 1; idx <= INSTANCE_MUTEX_COUNT; idx++)
      {
         if (InstanceMutexCount[idx])
         {
            HttpdGblSecPtr->MutexCount[idx] += InstanceMutexCount[idx];
            InstanceMutexCount[idx] = 0;
         }
         if (InstanceMutexWaitCount[idx])
         {
            HttpdGblSecPtr->MutexWaitCount[idx] += InstanceMutexWaitCount[idx];
            InstanceMutexWaitCount[idx] = 0;
         }
      }
   }

   if (ErrorsNoticedCount)
   {
      /* update the global accounting here to avoid deadlock issues */
      AccountingPtr->ErrorsNoticedCount += ErrorsNoticedCount;
      ErrorsNoticedCount = 0;
   }

   InstanceMutexUnLock (INSTANCE_MUTEX_HTTPD);

   /***************************/
   /* unlocked global section */
   /***************************/

   if (!rqptr->KeepAliveTimeout)
   {
      /* this was a real request, do some finalizing */
      if (LoggingEnabled &&
          rqptr->BytesRawRx &&
          rqptr->BytesRawTx &&
          (!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 keeping activity statistics */
      if (ActivityTotalMinutes) GraphActivityIncrement (rqptr);

      /* if it's not already been reported */
      if (rqptr->rqPathSet.Alert &&
          !(rqptr->rqPathSet.Alert & MAPURL_PATH_ALERT_DONE))
         RequestAlert (rqptr);

      if (WATCHING(rqptr) && WATCH_CATEGORY(WATCH_REQUEST))
      {
         Seconds = rqptr->rqResponse.Duration / USECS_IN_A_SECOND;
         SubSeconds = (rqptr->rqResponse.Duration % USECS_IN_A_SECOND) /
                      DURATION_UNITS_USECS;

         if (rqptr->rqNet.ReadErrorCount)
            WatchThis (rqptr, FI_LI, WATCH_REQUEST,
                       "RX errors !UL %X!08XL !-!&M",
                       rqptr->rqNet.ReadErrorCount,
                       rqptr->rqNet.ReadErrorStatus);

         if (rqptr->rqNet.WriteErrorCount)
            WatchThis (rqptr, FI_LI, WATCH_REQUEST,
                       "TX errors !UL %X!08XL !-!&M",
                       rqptr->rqNet.WriteErrorCount,
                       rqptr->rqNet.WriteErrorStatus);

         WatchThis (rqptr, FI_LI, WATCH_REQUEST, 
            "STATUS !3ZL rx:!UL tx:!UL bytes !UL.!#ZL seconds!AZ",
            rqptr->rqResponse.HttpStatus,
            rqptr->BytesRawRx, rqptr->BytesRawTx,
            Seconds, DURATION_DECIMAL_PLACES, SubSeconds,
            rqptr->rqTmr.TerminatedCount ? " TIMED-OUT" : "");
      }
   }

   HttpdSupervisorList (rqptr, -1);

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

   if (PersistentConnection)
   {
      /*************************/
      /* PERSISTENT connection */
      /*************************/

      RequestBegin (rqptr, false);
      return;
   }

   /********************/
   /* that's all folks */
   /********************/

   NetCloseSocket (rqptr);

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

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

   /* one-shot WATCH request is finished, no need to continue WATCHing */
   if (WATCHING(rqptr) && Watch.Category == WATCH_ONE_SHOT_CAT)
      if (Watch.RequestPtr)
      {
         REQUEST_STRUCT  *wrqptr;
         WatchEnd (wrqptr = Watch.RequestPtr);
         NetCloseSocket (wrqptr);
      }

   /* don't bother checking for exit or restart if connections still exist */
   if (InstanceConnectCurrent) return;

   /* if the control module has requested server exit or restart */
   if (ControlExitRequested)
   {
      fprintf (stdout, "%%%s-I-CONTROL, delayed server exit\n", Utility);
      ExitStatus = SS$_NORMAL;
      HttpdExit (&ExitStatus);
      /* cancel any startup messages provided for the monitor */
      HttpdGblSecPtr->StatusMessage[0] = '\0';
      /* record server event */
      GraphActivityEvent (ACTIVITY_DELPRC);
      sys$delprc (0, 0);
   }

   HttpdCheckPriv (FI_LI);
}

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

RequestAlert (REQUEST_STRUCT *rqptr)

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

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

   if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD_REQUEST))
      WatchThis (rqptr, FI_LI, WATCH_MOD_REQUEST,
                 "RequestAlert() 0x!2XL", rqptr->rqPathSet.Alert);

   AlertValue = rqptr->rqPathSet.Alert;

   /* set this bit to indicated the alert has been signalled */
   rqptr->rqPathSet.Alert |= MAPURL_PATH_ALERT_DONE;

   if (AlertValue >= 100 && AlertValue <= 599)
   {
      /* alert on a specific HTTP status code */
      AlertItem = AlertValue % 100;
      if (AlertItem == 99)
      {
         /* comparing to a category (e.g. 4nn, 5nn) but not that category */
         if (AlertValue / 100 != rqptr->rqResponse.HttpStatus / 100) return;
      }
      else
      {
         /* comparing to an individual status value */
         if (rqptr->rqResponse.HttpStatus != AlertValue) return;
      }
      /* continue on to report the alert */
   }

   InstanceGblSecIncrLong (&AccountingPtr->PathAlertCount);

   vecptr = &FaoVector;

   if (rqptr->RemoteUser[0])
   {
      *vecptr++ = "!AZ.\'!AZ\'@!AZ";
      *vecptr++ = rqptr->RemoteUser;
      if (rqptr->rqAuth.RealmPtr)
         *vecptr++ = rqptr->rqAuth.RealmPtr;
      else
         *vecptr++ = "?";
   }
   else
      *vecptr++ = "!AZ";
   *vecptr++ = rqptr->rqClient.Lookup.HostName;
   *vecptr++ = rqptr->rqHeader.MethodName;
   *vecptr++ = rqptr->rqHeader.RequestUriPtr;
   if (rqptr->rqResponse.HttpStatus)
   {
      *vecptr++ = " !UL";
      *vecptr++ = rqptr->rqResponse.HttpStatus;
   }
   else
      *vecptr++ = "";

   status = WriteFaol (Buffer, sizeof(Buffer), NULL,
                       "!&@ !AZ !AZ!&@", &FaoVector);
   if (VMSnok (status)) ErrorNoticed (status, "WriteFaol()", FI_LI);

   WriteFaoStdout ("%!AZ-W-ALERT, !20%D, !AZ\n", Utility, 0, Buffer);
 
   if (Config.cfOpcom.Messages & OPCOM_ADMIN)
      WriteFaoOpcom ("%!AZ-W-ALERT, !AZ", Utility, Buffer);

   if (WATCHING(rqptr) && WATCH_CATEGORY(WATCH_REQUEST))
      WatchThis (rqptr, FI_LI, WATCH_REQUEST, "ALERT !AZ", Buffer);
}

/*****************************************************************************/
/*
Some browsers seem unhappy if a request is aborted too quickly (and it's socket
closed) during the POST or PUT of a request body (reports connection errors). 
We're here because all the body was not read during request processing
(probably because an error is to be reported).  Read some more of the body
(just storing it in the bit-bucket).  Then after sufficient has been read (or
end-of-content) dispose of the request.
*/ 

RequestDiscardBody (REQUEST_STRUCT *rqptr)

{
   int  status;
   char  *cptr;

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

   if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD_REQUEST))
      WatchThis (rqptr, FI_LI, WATCH_MOD_REQUEST, 
                 "RequestDiscardBody() !&F !&X !UL",
                  &RequestDiscardBody, rqptr->rqBody.DataStatus,
                  rqptr->rqBody.DiscardChunkCount);

/** TODO remove at some stage **/
   if (cptr = getenv ("BODY_DISCARD_CHUNK_COUNT"))
      BodyDiscardChunkCount = atoi(cptr);

   if (rqptr->rqBody.AstFunction)
   {
      /* body processing underway, reset AST routine(s) to discard the rest */
      rqptr->rqBody.AstFunction = &RequestDiscardBodyAst;
      rqptr->rqBody.ProcessFunction = NULL;
      BodyRead (rqptr);
   }
   else
      BodyReadBegin (rqptr, &RequestDiscardBodyAst, NULL);
}

/*****************************************************************************/
/*
See RequestBodyDiscard().
*/ 

RequestDiscardBodyAst (REQUEST_STRUCT *rqptr)

{
   int  status;

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

   if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD_REQUEST))
      WatchThis (rqptr, FI_LI, WATCH_MOD_REQUEST, 
                 "RequestDiscardBodyAst() !&F !&X !UL",
                  &RequestDiscardBodyAst, rqptr->rqBody.DataStatus,
                  rqptr->rqBody.DiscardChunkCount);

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

   BodyRead (rqptr);
}

/****************************************************************************/
/*
The 'rqptr->rqResponse.LocationPtr' is non-NULL indicating redirection.

This  can be local, indicated by a leading '/', or it can be non-local,
indicated by anything other than a leading '/', usually a full URL including
scheme (e.g. "http:").

If the URL comprises something like "///some/path/or/other" then the request
scheme ("http:", "https:") and "Host:" or service host:port is substituted in
the appropriate position in the URL.

If "//host.domain/path/" then just the request scheme is added.

If "http:///path/" or  "https:///path/" then the service host:port is added to
the redirection URL (essentially this provides for a change of request scheme).

If the 'rqptr->rqResponse.LocationPtr' has as it's last character a '?' and no
query string itself, and if the request contains a query string then that is
appended to the redirection URL when building the redirected request.

Special case redirect; relies on being able to manipulate host record in the
DNS or local name resolution database.  If a "*.the.proxy.host" DNS (CNAME)
record is resolved it allows any host name ending in ".the.proxy.host" to
resolve to the corresponding IP address.  Similarly (at least the Compaq TCP/IP
Services) local host database allows an alias like
"another.host.name.proxy.host.name" for the proxy host name.  Both of these
would allow a browser to access  "another.host.name.proxy.host.name" with it
resolved to the proxy service.  The request "Host:" field would contain
"another.host.name.proxy.host.name".  This is specially searched for, detected
and processed by this function.  See description in PROXY.C for more detail on
usage.

If authorization is enabled then all redirected requests should be returned
to the client in case authorization information needs to be applied to the
new request.  This may involve reformating a local redirection into a
non-local one.  If authentication is not enabled then check both local and
non-local redirections to see if it can be handled locally.

Return normal status to indicate local-redirection (and that the thread
should NOT be disposed of), or an error status to indicate client-handled,
non-local-redirection, or a legitimate error (and that the thread can go).
*/ 

int RequestRedirect (REQUEST_STRUCT *rqptr)

{
#define STRCAT(string) { \
   for (cptr = string; *cptr && sptr < zptr; *sptr++ = *cptr++); \
}
#define CHRCAT(character) { \
   if (sptr < zptr) *sptr++ = character; \
}

   static char  Number [16];
   static $DESCRIPTOR (NumberDsc, Number);
   static $DESCRIPTOR (NumberFaoDsc, "!UL\0");

   BOOL  IncludeQueryString,
         LocalRedirection;
   int  idx, status,
        AlphaCount,
        BodyCount,
        ExciseCount,
        HostDotCount,
        Length,
        PortNumber,
        SchemeLength,
        ServiceDotCount,
        StringDotCount;
   char  *cptr, *eptr, *sptr, *zptr,
         *BodyPtr,
         *HttpMethodNamePtr,
         *NetReadBufferPtr,
         *PathPtr,
         *TextPtr,
         *UrlPtr,
         *UrlEndPtr;
   char  HttpMethodName [32],
         HostField [128+16],
         RedirectionUrl [1024];

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

   if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD_REQUEST))
      WatchThis (rqptr, FI_LI, WATCH_MOD_REQUEST,
                 "RequestRedirect() !&Z", rqptr->rqResponse.LocationPtr);

   if (rqptr->rqPathSet.Alert) RequestAlert (rqptr);

   if (WATCHING(rqptr) && WATCH_CATEGORY(WATCH_REQUEST))
      WatchThis (rqptr, FI_LI, WATCH_REQUEST,
                 "REDIRECT !AZ", rqptr->rqResponse.LocationPtr);

   if (rqptr->RedirectCount++ > REQUEST_REDIRECTION_MAX)
   {
      rqptr->rqResponse.LocationPtr = NULL;
      rqptr->rqResponse.HttpStatus = 500;
      ErrorGeneral (rqptr, MsgFor(rqptr,MSG_REQUEST_REDIRECTION), FI_LI);
      return (STS$K_ERROR);
   }

   rqptr->rqResponse.HttpStatus = 302;

   /*
      A leading space is normally not possible.
      This indicates a specific HTTP method must be used.
      Format is: "<space>METHOD<space><redirection-path>".
   */
   if (rqptr->rqResponse.LocationPtr[0] == ' ')
   {
      HttpMethodNamePtr = sptr = HttpMethodName;
      for (cptr = rqptr->rqResponse.LocationPtr + 1;
           *cptr && *cptr != ' ';
           *sptr++ = *cptr++);
      *sptr = '\0';
      rqptr->rqResponse.LocationPtr = cptr + 1;
   }
   else
      HttpMethodNamePtr = rqptr->rqHeader.MethodName;

   LocalRedirection = true;
   for (cptr = rqptr->rqResponse.LocationPtr; *cptr && *cptr != '?'; cptr++)
   {
      if (*cptr != ':' && *cptr != '/') continue;
      if (*cptr == ':') LocalRedirection = false;
      while (*cptr && *cptr != '?') cptr++;
      break;
   }
   if (IncludeQueryString = (*(unsigned short*)cptr == '?\0'))
   {
      if (!rqptr->rqHeader.QueryStringPtr[0])
      {
         IncludeQueryString = false;
         *cptr = '\0';
      }
   }

   if (LocalRedirection)
      MapUrl_VirtualPath (rqptr->rqHeader.PathInfoPtr,
                          rqptr->rqResponse.LocationPtr,
                          RedirectionUrl, sizeof(RedirectionUrl));
   else
      strzcpy (RedirectionUrl,
               rqptr->rqResponse.LocationPtr,
               sizeof(RedirectionUrl));

   /* now ignore old value of location redirection pointer */
   rqptr->rqResponse.LocationPtr = NULL;

   if (RedirectionUrl[0] == '/' && RedirectionUrl[1] != '/')
      LocalRedirection = true;
   else
      LocalRedirection = false;

   if (LocalRedirection)
   {
      /*********************/
      /* local redirection */
      /*********************/

      InstanceGblSecIncrLong (&AccountingPtr->RedirectLocalCount);

      /* for POST/PUT create space for a new request header */
      if (rqptr->rqHeader.Method == HTTP_METHOD_POST ||
          rqptr->rqHeader.Method == HTTP_METHOD_PUT ||
          rqptr->rqHeader.Method == HTTP_METHOD_DELETE)
      {
         /*  allow for any content received along with the header */
         BodyCount = rqptr->BytesRx - rqptr->rqHeader.RequestHeaderLength;
         if (BodyCount)
         {
            BodyPtr = VmGetHeap (rqptr, BodyCount);
            memcpy (BodyPtr,
                    rqptr->rqHeader.RequestHeaderPtr +
                       rqptr->rqHeader.RequestHeaderLength,
                    BodyCount);
         }
         rqptr->rqNet.ReadBufferPtr =
            VmGetHeap (rqptr, NetReadBufferSize + BodyCount);
         /* actual and indicated size may be different in this circumstance */
         rqptr->rqNet.ReadBufferSize = NetReadBufferSize;
      }
      else
         BodyCount = 0;

      HostField[0] = '\0';

      if (strsame (RedirectionUrl, "/http://", SchemeLength = 8) ||
          strsame (RedirectionUrl, "/https://", SchemeLength = 9) ||
          strsame (RedirectionUrl, "/ftp://", SchemeLength = 7))
      {
         /************/
         /* to proxy */
         /************/

         if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD_REQUEST))
            WatchThis (rqptr, FI_LI, WATCH_MOD_REQUEST,
                       "!&Z", RedirectionUrl);

         if (!rqptr->rqHeader.HostPtr)
         {
            rqptr->rqResponse.HttpStatus = 400;
            ErrorGeneral (rqptr, MsgFor(rqptr,MSG_PROXY_GATEWAY), FI_LI);
            return (STS$K_ERROR);
         }

         /*
            If the path has been SET 'proxy=reverse=location=<string>'
            then this and the original host information is conveyed
            to the redirected proxy request using redirect-persistent
            storage made available specifically for this purpose.
         */
         if (rqptr->rqPathSet.ProxyReverseLocationPtr)
         {
            Length = strlen(rqptr->ServicePtr->RequestSchemeNamePtr) +
                     strlen(rqptr->rqHeader.HostPtr) +
                     strlen(rqptr->rqPathSet.ProxyReverseLocationPtr) + 3;
            rqptr->ProxyReverseLocationPtr = sptr = VmGetHeap (rqptr, Length);
            for (cptr = rqptr->ServicePtr->RequestSchemeNamePtr;
                 *cptr;
                 *sptr++ = *cptr++);
            *sptr++ = '/';
            *sptr++ = '/';
            for (cptr = rqptr->rqHeader.HostPtr; *cptr; *sptr++ = *cptr++);
            for (cptr = rqptr->rqPathSet.ProxyReverseLocationPtr;
                 *cptr;
                 *sptr++ = *cptr++);
            *sptr = '\0';
         }

         /*
            Check for a DNS 'wildcard' leading the service name.
            That is the "Host:" field contains more domain name
            components than the proxy service domain name.
            Do this by counting the number of periods in the strings.
            Acckkk Alex! Never been quite convinced of the need for this ;^)
         */
         AlphaCount = HostDotCount = ServiceDotCount = 0;
         for (cptr = rqptr->rqHeader.HostPtr; *cptr; cptr++)
         {
            if (isalpha(*cptr)) AlphaCount++;
            if (*cptr != '.') continue;
            HostDotCount++;
         }
         if (AlphaCount)
         {
            /* only bother doing this if it's not dotted-decimal */
            for (cptr = rqptr->ServicePtr->ServerHostPort; *cptr; cptr++)
            {
               if (*cptr != '.') continue;
               ServiceDotCount++;
            }
         }

         if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD_REQUEST))
            WatchDataFormatted ("!UL !UL !&Z !UL !&Z\n",
               AlphaCount, HostDotCount, rqptr->rqHeader.HostPtr,
               ServiceDotCount, rqptr->ServicePtr->ServerHostPort);

         if (AlphaCount && HostDotCount > ServiceDotCount)
         {
            /**********************/
            /* wildcard DNS proxy */
            /**********************/

            /*
               At this stage the "Host:" field will look something like
               'the.host.name.the.proxy.service:port' and the redirection URL
               '/https://the.proxy.service/http://the.host.name.the.proxy.service:port/path'
               We need to turn this into a proxy redirection looking like
               'https://the.host.name/path'.
            */
            sptr = RedirectionUrl + SchemeLength;
            if (*sptr && *sptr != '/')
            {
               /* count the number of periods 'the.proxy.service/' component */
               StringDotCount = 0;
               for (cptr = sptr; *cptr && *cptr != '/'; cptr++)
               {
                  if (*cptr != '.') continue;
                  StringDotCount++;
               }
               /* possible but not desirable */
               if (StringDotCount >= HostDotCount)
                  StringDotCount = HostDotCount;

               /* find the start of the redirection URL path */
               if (*cptr) cptr++;
               while (*cptr && *cptr != '/') cptr++;
               PathPtr = cptr;

               if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD_REQUEST))
                  WatchDataFormatted ("!UL !&Z !UL !&Z\n",
                     HostDotCount, rqptr->rqHeader.HostPtr,
                     StringDotCount, PathPtr);
            }
            else
            {
               StringDotCount = ServiceDotCount;
               PathPtr = RedirectionUrl + SchemeLength;
            }

            /* recreate the "Host:" field */
            zptr = (sptr = HostField) + sizeof(HostField)-1;
            cptr = rqptr->rqHeader.HostPtr;
            while (HostDotCount-- > StringDotCount)
            {
               if (*cptr == '.')
               {
                  if (sptr < zptr) *sptr++ = *cptr;
                  cptr++;
               }
               while (*cptr && *cptr != '.')
               {
                  if (sptr < zptr) *sptr++ = *cptr;
                  cptr++;
               }
            }
            *sptr-- = '\0';
            /* check if the last 'name' component was actually a 'port' */
            while (sptr > HostField && isdigit(*sptr)) sptr--;
            /* if so change the period to a colon */
            if (sptr > HostField && *sptr == '.') *sptr = ':';
            if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD_REQUEST))
               WatchDataFormatted ("!&Z\n", HostField);

            /* begin building the redirection buffer */
            /* NO MORE USING cptr, eptr, sptr, zptr! */
            sptr = rqptr->rqNet.ReadBufferPtr;
            zptr = sptr + rqptr->rqNet.ReadBufferSize;

            STRCAT (HttpMethodNamePtr)
            if (SchemeLength == 8)
               STRCAT (" http://")
            else
            if (SchemeLength == 9)
               STRCAT (" https://")
            else
               STRCAT (" ftp://")
            STRCAT (HostField)
            STRCAT (PathPtr)
         }
         else
         {
            /**********************/
            /* one-shot DNS proxy */
            /**********************/

            /* recreate the "Host:" field */
            zptr = (sptr = HostField) + sizeof(HostField)-1;
            cptr =  RedirectionUrl + SchemeLength;
            while (*cptr && *cptr != '/' && sptr < zptr) *sptr++ = *cptr++;
            *sptr = '\0';
            if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD_REQUEST))
               WatchDataFormatted ("!&Z\n", HostField);

            /* begin building the redirection buffer */
            /* NO MORE USING cptr, eptr, sptr, zptr! */
            sptr = rqptr->rqNet.ReadBufferPtr;
            zptr = sptr + rqptr->rqNet.ReadBufferSize;

            STRCAT (HttpMethodNamePtr)
            CHRCAT (' ')
            STRCAT (RedirectionUrl+1)
         }
      }
      else
      {
         /****************/
         /* not to proxy */
         /****************/

         /* begin building the redirection buffer */
         /* NO MORE USING cptr, eptr, sptr, zptr! */
         sptr = rqptr->rqNet.ReadBufferPtr;
         zptr = sptr + rqptr->rqNet.ReadBufferSize;

         STRCAT (HttpMethodNamePtr)
         CHRCAT (' ')
         STRCAT (RedirectionUrl)
      }

      if (IncludeQueryString) STRCAT (rqptr->rqHeader.QueryStringPtr)
      CHRCAT (' ')
      STRCAT (HttpProtocol);

      /**************************/
      /* rest of request header */
      /**************************/

      if (rqptr->rqHeader.AcceptPtr)
      {
         STRCAT ("\r\nAccept: ")
         STRCAT (rqptr->rqHeader.AcceptPtr)
      }
      if (rqptr->rqHeader.AcceptLangPtr)
      {
         STRCAT ("\r\nAccept-Language: ")
         STRCAT (rqptr->rqHeader.AcceptLangPtr)
      }
      if (rqptr->rqHeader.AcceptCharsetPtr)
      {
         STRCAT ("\r\nAccept-Charset: ")
         STRCAT (rqptr->rqHeader.AcceptCharsetPtr)
      }
      if (rqptr->rqHeader.AcceptEncodingPtr)
      {
         STRCAT ("\r\nAccept-Encoding: ")
         STRCAT (rqptr->rqHeader.AcceptEncodingPtr)
      }
      if (rqptr->rqHeader.UserAgentPtr)
      {
         STRCAT ("\r\nUser-Agent: ")
         STRCAT (rqptr->rqHeader.UserAgentPtr)
      }
      if (rqptr->rqHeader.HostPtr)
      {
         STRCAT ("\r\nHost: ")
         if (HostField[0])
            STRCAT (HostField)
         else
            STRCAT (rqptr->rqHeader.HostPtr)
      }
      if (rqptr->rqHeader.RangePtr)
      {
         STRCAT ("\r\nRange: ")
         STRCAT (rqptr->rqHeader.RangePtr)
      }
      /* too complex massaging "Referer:" field when redirecting to proxy */
      if (rqptr->rqHeader.RefererPtr && !HostField[0])
      {
         STRCAT ("\r\nReferer: ")
         STRCAT (rqptr->rqHeader.RefererPtr)
      }
      if (rqptr->rqHeader.AuthorizationPtr)
      {
         STRCAT ("\r\nAuthorization: ")
         STRCAT (rqptr->rqHeader.AuthorizationPtr)
      }
      if (rqptr->rqHeader.IfModifiedSincePtr)
      {
         STRCAT ("\r\nIf-Modified-Since: ")
         STRCAT (rqptr->rqHeader.IfModifiedSincePtr)
      }
      if (rqptr->rqHeader.CacheControlPtr)
      {
         STRCAT ("\r\nCache-Control: ")
         STRCAT (rqptr->rqHeader.CacheControlPtr)
      }
      if (rqptr->rqHeader.PragmaPtr)
      {
         STRCAT ("\r\nPragma: ")
         STRCAT (rqptr->rqHeader.PragmaPtr)
      }
      if (rqptr->rqHeader.CookiePtr)
      {
         STRCAT ("\r\nCookie: ")
         STRCAT (rqptr->rqHeader.CookiePtr)
      }
      if (rqptr->rqHeader.ForwardedPtr)
      {
         STRCAT ("\r\nForwarded: ")
         STRCAT (rqptr->rqHeader.ForwardedPtr)
      }
      if (rqptr->rqHeader.XForwardedForPtr)
      {
         STRCAT ("\r\nX-Forwarded-For: ")
         STRCAT (rqptr->rqHeader.XForwardedForPtr)
      }
      if (rqptr->rqHeader.ContentTypePtr)
      {
         STRCAT ("\r\nContent-Type: ")
         STRCAT (rqptr->rqHeader.ContentTypePtr)
      }
      if (rqptr->rqHeader.ProxyConnectionPtr)
      {
         STRCAT ("\r\nProxy-Connection: ")
         STRCAT (rqptr->rqHeader.ProxyConnectionPtr)
      }
      if (rqptr->rqHeader.ETagPtr)
      {
         STRCAT ("\r\nETag: ")
         STRCAT (rqptr->rqHeader.ETagPtr)
      }
      if (rqptr->rqHeader.ContentLength)
      {
         sys$fao (&NumberFaoDsc, 0, &NumberDsc, rqptr->rqHeader.ContentLength);
         STRCAT ("\r\nContent-Length: ")
         STRCAT (Number)
      }
      if (rqptr->rqHeader.KeepAlivePtr)
      {
         STRCAT ("\r\nKeep-Alive: ")
         STRCAT (rqptr->rqHeader.KeepAlivePtr)
      }
      if (rqptr->KeepAliveRequest)
         STRCAT ("\r\nConnection: Keep-Alive")

      /* terminate whatever the last line was */
      STRCAT ("\r\n")

      /* append any "unknown fields" supplied with the request */
      for (idx = 0; idx < rqptr->rqHeader.UnknownFieldsCount; idx++)
      {
         STRCAT (rqptr->rqHeader.UnknownFieldsPtr[idx]);
         STRCAT ("\r\n")
      }

      /* if generated by a CGI "Location:", supply any remaining header */
      if (rqptr->rqCgi.HeaderLength) STRCAT (rqptr->rqCgi.HeaderPtr)

      /* terminating empty line */
      STRCAT ("\r\n")
 
      if (sptr >= zptr)
      {
         ErrorNoticed (SS$_RESULTOVF, "RequestRedirect()", FI_LI);
         ErrorGeneralOverflow (rqptr, FI_LI);
         return (STS$K_ERROR);
      }
      *sptr = '\0';

      if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD_REQUEST))
         WatchDataFormatted ("!&Z", rqptr->rqNet.ReadBufferPtr);

      /* append any body supplied with the request header */
      if (BodyCount) memcpy (sptr, BodyPtr, BodyCount);

      /* buffer anything that is necessary to retain after memset() */
      NetReadBufferPtr = rqptr->rqNet.ReadBufferPtr;

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

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

      /* initialize the timer for input */
      HttpdTimerSet (rqptr, TIMER_INPUT, 0);

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

      /* restore buffered stuff */
      rqptr->rqNet.ReadBufferPtr = NetReadBufferPtr;
      /* (this one is "restored" from global storage!! */
      rqptr->rqNet.ReadBufferSize = NetReadBufferSize;

      /* point the request header at the newly-created contents */
      rqptr->rqHeader.RequestHeaderPtr = rqptr->rqNet.ReadBufferPtr;
      rqptr->rqHeader.RequestHeaderLength = sptr - rqptr->rqNet.ReadBufferPtr;

      /* fudge the bytes received (for log and monitor purposes) */
      rqptr->BytesRx = rqptr->BytesRawRx =
         rqptr->rqHeader.RequestHeaderLength + BodyCount;

      /* normal status indicates its a local redirection */
      return (SS$_NORMAL);
   }
   else
   {
      /*************************/
      /* non-local redirection */
      /*************************/

      InstanceGblSecIncrLong (&AccountingPtr->RedirectRemoteCount);

      rqptr->rqResponse.HeaderPtr = VmGetHeap (rqptr, OutputBufferSize);

      /* begin building the redirection buffer */
      /* NO MORE USING cptr, eptr, sptr, zptr! */
      sptr = rqptr->rqResponse.HeaderPtr;
      zptr = sptr + OutputBufferSize;

      STRCAT (HttpProtocol);
      STRCAT (" 302 ");
      STRCAT (HttpStatusCodeText(302));

      STRCAT ("\r\nLocation: ");
      UrlPtr = sptr;  /* note the start of the redirection URL */
      if (!memcmp (RedirectionUrl, "///", 3))
      {
         /* request scheme and host need to be supplied locally */
         STRCAT (rqptr->ServicePtr->RequestSchemeNamePtr)
         STRCAT ("//")
         if (rqptr->rqHeader.HostPtr && rqptr->rqHeader.HostPtr[0])
            STRCAT (rqptr->rqHeader.HostPtr)
         else
            STRCAT (rqptr->ServicePtr->ServerHostPort)
         STRCAT (RedirectionUrl+2)
      }
      else
      if (!memcmp (RedirectionUrl, "//", 2))
      {
         /* request scheme needs to be supplied locally */
         STRCAT (rqptr->ServicePtr->RequestSchemeNamePtr)
         STRCAT (RedirectionUrl)
      }
      else
      if (!memcmp (RedirectionUrl, "http:///", 8))
      {
         /* host needs to be supplied locally */
         STRCAT ("http://")
         if (rqptr->rqHeader.HostPtr && rqptr->rqHeader.HostPtr[0])
            STRCAT (rqptr->rqHeader.HostPtr)
         else
            STRCAT (rqptr->ServicePtr->ServerHostPort);
         STRCAT (RedirectionUrl+7)
      }
      else
      if (!memcmp (RedirectionUrl, "https:///", 9))
      {
         /* host needs to be supplied locally */
         STRCAT ("https://")
         if (rqptr->rqHeader.HostPtr && rqptr->rqHeader.HostPtr[0])
            STRCAT (rqptr->rqHeader.HostPtr)
         else
            STRCAT (rqptr->ServicePtr->ServerHostPort)
         STRCAT (RedirectionUrl+8)
      }
      else
      {
         /* redirection contains full URL */
         STRCAT (RedirectionUrl)
      }
      if (IncludeQueryString) STRCAT (rqptr->rqHeader.QueryStringPtr);
      UrlEndPtr = sptr;  /* note the end of the redirection URL */

      STRCAT ("\r\nServer: ")
      STRCAT (SoftwareID)
      STRCAT ("\r\nDate: ")
      STRCAT (rqptr->rqTime.GmDateTime);
      STRCAT ("\r\nContent-Type: text/html\r\n")

      /* if generated by a CGI "Location:", supply any remaining header */
      if (rqptr->rqCgi.HeaderLength) STRCAT (rqptr->rqCgi.HeaderPtr)

      /* terminating empty line */
      STRCAT ("\r\n")

      /* end of the response header */
      rqptr->rqResponse.HeaderSent = false;
      rqptr->rqResponse.HeaderLength = sptr - rqptr->rqResponse.HeaderPtr;
      if (sptr < zptr) *sptr++ = '\0';

      /* begin building the textual (body) redirection information */
      TextPtr = sptr;
      STRCAT (HttpStatusCodeText(302))
      STRCAT (" <A HREF=\"")
      for (cptr = UrlPtr; cptr < UrlEndPtr && sptr < zptr; *sptr++ = *cptr++);
      STRCAT ("\">")
      for (cptr = UrlPtr; cptr < UrlEndPtr && sptr < zptr; *sptr++ = *cptr++);
      STRCAT ("</A>\n")

      if (sptr >= zptr)
      {
         ErrorNoticed (SS$_RESULTOVF, "RequestRedirect()", FI_LI);
         ErrorGeneralOverflow (rqptr, FI_LI);
         return (STS$K_ERROR);
      }
      *sptr = '\0';

      if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD_REQUEST))
         WatchDataFormatted ("!&Z!&Z", rqptr->rqResponse.HeaderPtr, TextPtr);

      /* return text, and of course response header, to client */
      NetWrite (rqptr, &RequestEnd, TextPtr, sptr - TextPtr);

      /* indicate it's non-local redirection */
      return (SS$_NONLOCAL);
   }

#undef STRCAT
#undef CHRCAT
#undef CRLFCAT
}

/*****************************************************************************/
/*
Process first packet read from the client.  In most cases this will contain 
the complete HTTP header and it can be processed without building up a 
buffered header.  If it does not contain the full header allocate heap memory
to contain the current packet and queue subsequent read(s), expanding the 
request header heap memory, until all is available (or it becomes completely 
rediculous!).
*/ 
 
RequestGet (REQUEST_STRUCT *rqptr)

{
   int  cnlcnt, cnt, status;
   char  *cptr, *sptr;

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

   if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD_REQUEST))
      WatchThis (rqptr, FI_LI, WATCH_MOD_REQUEST,
                 "RequestGet() !&F !&S !UL !UL !UL", &RequestGet,
                  rqptr->rqNet.ReadIOsb.Status,
                  rqptr->rqNet.ReadIOsb.Count,
                  rqptr->rqNet.ReadBufferSize,
                  rqptr->rqHeader.RequestHeaderLength);

   if (VMSnok (rqptr->rqNet.ReadIOsb.Status))
   {
      /*
         Most common cause will be broken connection.  Most common reason
         for the break ... "keep-alive" timeout on persistent connection.
         Don't bother to report!
      */
      if (!rqptr->rqHeader.RequestHeaderLength &&
          rqptr->rqNet.KeepAliveCount)
         rqptr->KeepAliveTimeout = true;
      RequestEnd (rqptr);
      return;
   }

   if (rqptr->rqNet.KeepAliveCount)
   {
      /* request is underway, reinitialize for input (longer period) */
      HttpdTimerSet (rqptr, TIMER_INPUT, 0);

      /* re-timestamp the kept-alive transaction */
      sys$gettim (&rqptr->rqTime.Vms64bit);
      sys$numtim (&rqptr->rqTime.VmsVector, &rqptr->rqTime.Vms64bit);
      HttpGmTimeString (rqptr->rqTime.GmDateTime, &rqptr->rqTime.Vms64bit);
   }

   if (!rqptr->rqHeader.RequestHeaderLength)
   {
      InstanceMutexLock (INSTANCE_MUTEX_HTTPD);
      if (rqptr->rqNet.KeepAliveCount &&
          AccountingPtr->ConnectCurrentKeepAlive)
         AccountingPtr->ConnectCurrentKeepAlive--;
      AccountingPtr->ConnectProcessing++;
      if (AccountingPtr->ConnectProcessing >
          AccountingPtr->ConnectProcessingPeak)
         AccountingPtr->ConnectProcessingPeak =
            AccountingPtr->ConnectProcessing;
      InstanceMutexUnLock (INSTANCE_MUTEX_HTTPD);
   }

   rqptr->BytesRx += (cnt = rqptr->rqNet.ReadIOsb.Count);

   if (rqptr->rqNet.ReadBufferSize >= MAX_REQUEST_HEADER)
   {
      /* not likely to be a legitimate HTTP request! */
      RequestEnd (rqptr);
      return;
   }

   if (!rqptr->rqHeader.RequestHeaderPtr)
   {
      rqptr->rqHeader.RequestHeaderPtr = rqptr->rqNet.ReadBufferPtr;
      rqptr->rqHeader.RequestHeaderLength =
         rqptr->rqHeader.RequestLineLength = 0;
   }

   rqptr->rqHeader.RequestHeaderPtr[cnt] = '\0';
   rqptr->rqHeader.RequestHeaderLength += cnt;

   if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD_REQUEST))
      WatchDataFormatted ("!&Z\n", rqptr->rqHeader.RequestHeaderPtr);

   /* looks like an HTTP request? (not SSL on the wrong port for instance) */
   if (rqptr->rqHeader.RequestHeaderLength >= 7)
   {
      /* evaluated in order of probable occurance */
      if (memcmp (rqptr->rqNet.ReadBufferPtr, "GET ", 4) &&
          memcmp (rqptr->rqNet.ReadBufferPtr, "HEAD ", 5) &&
          memcmp (rqptr->rqNet.ReadBufferPtr, "POST ", 5) &&
          memcmp (rqptr->rqNet.ReadBufferPtr, "CONNECT ", 8) &&
          memcmp (rqptr->rqNet.ReadBufferPtr, "PUT ", 4) &&
          memcmp (rqptr->rqNet.ReadBufferPtr, "DELETE ", 7))
      {
         InstanceGblSecIncrLong (&AccountingPtr->RequestErrorCount);
         rqptr->rqResponse.HttpStatus = 501;
         ErrorGeneral (rqptr, MsgFor(rqptr,MSG_REQUEST_METHOD), FI_LI);
         RequestEnd (rqptr);
         return;
      }
   }

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

      cptr = rqptr->rqNet.ReadBufferPtr;
      while (*cptr && *cptr != '\n') cptr++;
      if (*cptr == '\n')
      {
         if (!RequestLineParse (rqptr))
         {
            RequestEnd (rqptr);
            return;
         }
      }
   }

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

      rqptr->rqHeader.RequestHeaderPtr = rqptr->rqNet.ReadBufferPtr;
      rqptr->rqHeader.RequestHeaderLength =
         cptr - (char*)rqptr->rqNet.ReadBufferPtr;
      RequestParseAndExecute (rqptr);
      return;
   }
   else
   if (rqptr->rqHeader.HttpVersion)
   {
      /************/
      /* HTTP/1.n */
      /************/

      /* look for the end of header blank line */
      cnlcnt = rqptr->rqHeader.ConsecutiveNewLineCount;
      cptr = rqptr->rqHeader.RequestHeaderPtr;
      while (*cptr && cnlcnt < 2)
      {
         if (*cptr == '\r') cptr++;
         if (!*cptr) break;
         if (*cptr != '\n')
         {
            cptr++;
            cnlcnt = 0;
            continue;
         }
         cptr++;
         cnlcnt++;
      }

      if (cnlcnt >= 2)
      {
         rqptr->rqHeader.RequestHeaderPtr = rqptr->rqNet.ReadBufferPtr;
         rqptr->rqHeader.RequestHeaderLength =
            cptr - (char*)rqptr->rqNet.ReadBufferPtr;
         RequestParseAndExecute (rqptr);
         return;
      }

      rqptr->rqHeader.ConsecutiveNewLineCount = cnlcnt;
   }

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

   if (rqptr->rqHeader.RequestHeaderLength >= rqptr->rqNet.ReadBufferSize)
   {
      /* need more buffer space, allow one for termination of the buffer */
      rqptr->rqNet.ReadBufferSize += NetReadBufferSize;
      rqptr->rqNet.ReadBufferPtr =
         VmReallocHeap (rqptr, rqptr->rqNet.ReadBufferPtr,
                        rqptr->rqNet.ReadBufferSize+1, FI_LI);
   }

   /* because a realloc() may move the memory reset the pointers */
   rqptr->rqHeader.RequestHeaderPtr = rqptr->rqNet.ReadBufferPtr +
                                      rqptr->rqHeader.RequestHeaderLength;

   /* asynchronous read to get more of the header from the client */
   NetRead (rqptr, &RequestGet, rqptr->rqHeader.RequestHeaderPtr,
            rqptr->rqNet.ReadBufferSize - rqptr->rqHeader.RequestHeaderLength);
}

/****************************************************************************/
/*
Get the method, path and query string, and some header field line values, then
execute the request.  This function can be called from two points.  First, from
the function RequestGet(), after it has received a complete HTTP header from
the client.  Second, from the function RequestRedirect(), where an HTTP header
has been reconstructed in the 'NetReadBufferPtr' in order to effect a local
redirection.
*/

RequestParseAndExecute (REQUEST_STRUCT *rqptr)

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

   if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD_REQUEST))
   {
      WatchThis (rqptr, FI_LI, WATCH_MOD_REQUEST,
                 "RequestParseAndExecute() !UL",
                 rqptr->rqHeader.RequestHeaderLength);
      WatchDataDump (rqptr->rqHeader.RequestHeaderPtr,
                     rqptr->rqHeader.RequestHeaderLength);
   }

   if (WATCHING(rqptr) && WATCH_CATEGORY(WATCH_REQUEST_HEADER))
   {
      WatchThis (rqptr, FI_LI, WATCH_REQUEST_HEADER,
                 "HEADER !UL bytes", rqptr->rqHeader.RequestHeaderLength);
      WatchData (rqptr->rqHeader.RequestHeaderPtr,
                 rqptr->rqHeader.RequestHeaderLength);
   }

   InstanceMutexLock (INSTANCE_MUTEX_HTTPD);
   AccountingPtr->RequestTotalCount++;
   AccountingPtr->RequestParseCount++;
   if (rqptr->rqNet.KeepAliveCount) AccountingPtr->RequestKeepAliveCount++;
   InstanceMutexUnLock (INSTANCE_MUTEX_HTTPD);

   if (!RequestFields (rqptr))
   {
      RequestEnd (rqptr);
      return;
   }

   if (rqptr->ServicePtr->TrackEnabled)
      if (!TrackGetCookie (rqptr))
         TrackSetCookie (rqptr);

   if (WATCH_CAT && WATCHING(rqptr))
   {
      if (!rqptr->rqHeader.PathInfoPtr[0] ||
          rqptr->rqHeader.PathInfoPtr[0] == '/')
      {
         /* check if this non-proxy request should be filtered out on path */
         if (!WatchFilter (NULL, NULL, NULL, NULL,
                           rqptr->rqHeader.PathInfoPtr, rqptr->TrackId))
         {
            WatchThis (rqptr, FI_LI, WATCH_FILTER,
               "PATH/TRACK does not match filter, dropping from WATCH");
            rqptr->WatchItem = 0;
         }
      }
      else
      {
         /* check if this proxy request should be filtered out on path */
         if (!WatchFilter (NULL, NULL, NULL, NULL,
                           rqptr->rqHeader.RequestUriPtr, rqptr->TrackId))
         {
            WatchThis (rqptr, FI_LI, WATCH_FILTER,
               "PATH (proxy) does not match filter, dropping from WATCH");
            rqptr->WatchItem = 0;
         }
      }
   }

   /* evaluated in order of probable occurance */
   if (!strcmp (rqptr->rqHeader.MethodName, "GET"))
   {
      InstanceGblSecIncrLong (&AccountingPtr->MethodGetCount);
      rqptr->rqHeader.Method = HTTP_METHOD_GET;
      RequestExecute (rqptr);
      return;
   }

   /* simple requests (HTTP/0.9) are limited to GET requests */
   if (rqptr->rqHeader.HttpVersion == HTTP_VERSION_0_9)
   {
      InstanceGblSecIncrLong (&AccountingPtr->RequestErrorCount);
      rqptr->rqResponse.HttpStatus = 501;
      ErrorGeneral (rqptr, MsgFor(rqptr,MSG_REQUEST_METHOD), FI_LI);
      RequestEnd (rqptr);
      return;
   }

   if (!strcmp (rqptr->rqHeader.MethodName, "HEAD"))
   {
      InstanceGblSecIncrLong (&AccountingPtr->MethodHeadCount);
      rqptr->rqHeader.Method = HTTP_METHOD_HEAD;
      RequestExecute (rqptr);
      return;
   }
   if (!strcmp (rqptr->rqHeader.MethodName, "POST"))
   {
      InstanceGblSecIncrLong (&AccountingPtr->MethodPostCount);
      rqptr->rqHeader.Method = HTTP_METHOD_POST;
      rqptr->KeepAliveRequest = false;
      RequestExecute (rqptr);
      return;
   }
   if (!strcmp (rqptr->rqHeader.MethodName, "PUT"))
   {
      InstanceGblSecIncrLong (&AccountingPtr->MethodPutCount);
      rqptr->rqHeader.Method = HTTP_METHOD_PUT;
      rqptr->KeepAliveRequest = false;
      RequestExecute (rqptr);
      return;
   }
   if (!strcmp (rqptr->rqHeader.MethodName, "CONNECT"))
   {
      InstanceGblSecIncrLong (&AccountingPtr->MethodConnectCount);
      rqptr->rqHeader.Method = HTTP_METHOD_CONNECT;
      rqptr->KeepAliveRequest = false;
      RequestExecute (rqptr);
      return;
   }
   if (!strcmp (rqptr->rqHeader.MethodName, "DELETE"))
   {
      InstanceGblSecIncrLong (&AccountingPtr->MethodDeleteCount);
      rqptr->rqHeader.Method = HTTP_METHOD_DELETE;
      rqptr->KeepAliveRequest = false;
      RequestExecute (rqptr);
      return;
   }

   InstanceGblSecIncrLong (&AccountingPtr->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.
*/ 
 
RequestLineParse (REQUEST_STRUCT *rqptr)

{
   unsigned char  ch;
   char  *cptr, *rptr, *sptr, *zptr;

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

   if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD_REQUEST))
   {
      WatchThis (rqptr, FI_LI, WATCH_MOD_REQUEST, "RequestLineParse()");
      WatchDataDump (rqptr->rqNet.ReadBufferPtr,
                     strlen(rqptr->rqNet.ReadBufferPtr));
   }

   cptr = rqptr->rqNet.ReadBufferPtr;

   zptr = (sptr = rqptr->rqHeader.MethodName) +
          sizeof(rqptr->rqHeader.MethodName);
   while (!ISLWS(*cptr) && NOTEOL(*cptr) && sptr < zptr) *sptr++ = *cptr++;
   if (sptr >= zptr) sptr = rqptr->rqHeader.MethodName;
   *sptr = '\0';

   /*error, no method on request line */
   if (!rqptr->rqHeader.MethodName[0]) goto RequestLineParseFormatError;

   /* skip across white-space between method and path */
   while (ISLWS(*cptr) && NOTEOL(*cptr)) cptr++;

   /* get the path information, noting the start of the requested resource */
   rptr = zptr = cptr;
   while (!ISLWS(*zptr) && *zptr != '?' && NOTEOL(*zptr)) zptr++;

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

   rqptr->rqHeader.PathInfoPtr = sptr =
      VmGetHeap (rqptr, (rqptr->rqHeader.PathInfoLength = zptr-cptr)+1);
   memcpy (sptr, cptr, zptr-cptr);
   sptr[zptr-cptr] = '\0';
   /* unescape any hex-encoded characters */
   for (cptr = sptr; *cptr && *cptr != '%'; cptr++);
   if (*cptr)
   {
      sptr = cptr;
      while (*cptr)
      {
         while (*cptr && *cptr != '%') *sptr++ = *cptr++;
         if (!*cptr) break;
         /* an escaped character ("%xx" where xx is a hex number) */
         cptr++;
         ch = 0;

         if (*cptr >= '0' && *cptr <= '9')
            { ch = (*cptr - (int)'0') << 4; cptr++; }
         else
         if (tolower(*cptr) >= 'a' && tolower(*cptr) <= 'f')
            { ch = (tolower(*cptr) - (int)'a' + 10) << 4; cptr++; }
         else
            goto RequestLineParseUrlEncodeError;

         if (*cptr >= '0' && *cptr <= '9')
            { ch += (*cptr - (int)'0'); cptr++; }
         else
         if (tolower(*cptr) >= 'a' && tolower(*cptr) <= 'f')
            { ch += (tolower(*cptr) - (int)'a' + 10); cptr++; }
         else
            goto RequestLineParseUrlEncodeError;

         *sptr++ = ch;
      }
      *sptr = '\0';
      rqptr->rqHeader.PathInfoLength =
         sptr - (char*)rqptr->rqHeader.PathInfoPtr;
   }
   /* point back into the request */ 
   cptr = zptr;

   /* get any query string, or create an empty one */
   if (*cptr == '?') cptr++;
   zptr = cptr;
   while (!ISLWS(*zptr) && NOTEOL(*zptr)) zptr++;
   rqptr->rqHeader.QueryStringPtr = sptr = VmGetHeap (rqptr, zptr-cptr+1);
   if (rqptr->rqHeader.QueryStringLength = zptr-cptr)
      memcpy (sptr, cptr, zptr-cptr);
   sptr[zptr-cptr] = '\0';

   /* store the requested resource */
   rqptr->rqHeader.RequestUriPtr = sptr = VmGetHeap (rqptr, zptr-rptr+1);
   memcpy (sptr, rptr, rqptr->rqHeader.RequestUriLength = zptr-rptr);
   sptr[zptr-rptr] = '\0';

   if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD_REQUEST))
      WatchDataFormatted ("!&Z !&Z !&Z !&Z\n",
         rqptr->rqHeader.MethodName, rqptr->rqHeader.PathInfoPtr,
         rqptr->rqHeader.QueryStringPtr, rqptr->rqHeader.RequestUriPtr);

   /* check the HTTP version  */
   while (ISLWS(*zptr) && NOTEOL(*zptr)) zptr++;
   cptr = zptr;
   while (*zptr && NOTEOL(*zptr)) zptr++;
   rqptr->rqHeader.RequestLineLength = zptr - (char*)rqptr->rqNet.ReadBufferPtr;

   if (!memcmp (cptr, "HTTP/", 5))
   {
      if (!memcmp (cptr+5, "1.0", 3))
      {
         rqptr->rqHeader.HttpVersion = HTTP_VERSION_1_0;
         return (true);
      }
      else
      if (!memcmp (cptr+5, "1.1", 3))
      {
         rqptr->rqHeader.HttpVersion = HTTP_VERSION_1_1;
         return (true);
      }
      rqptr->rqHeader.HttpVersion = HTTP_VERSION_UNKNOWN;
      return (true);
   }
   else
   {
      /* no HTTP version followed the path */
      rqptr->rqHeader.HttpVersion = HTTP_VERSION_0_9;
      return (true);
   }

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

/*****************************************************************************/
/*
Scan the header buffer looking for relevant fields. Return true to continue
processing the request, false to stop immediately.  If false this function or
any it called must have reported the problem to the client and/or disposed of
the thread.
*/ 
 
BOOL RequestFields (REQUEST_STRUCT *rqptr)

{
   int  idx, rfidx, status,
        NameLength,
        RequestFieldsMaxExceeded,
        RequestUnknownFieldsMaxExceeded,
        TotalLength,
        ValueLength;
   char  *cptr, *lptr, *sptr, *tptr;
   char  **rfptr;
   RANGE_BYTE  *rbptr;

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

   if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD_REQUEST))
   {
      WatchThis (rqptr, FI_LI, WATCH_MOD_REQUEST, "RequestFields()");
      WatchDataFormatted ("{!UL}!-!#AZ\n", rqptr->rqHeader.RequestLineLength,
                                           rqptr->rqHeader.RequestHeaderPtr);
   }

   RequestFieldsMaxExceeded = RequestUnknownFieldsMaxExceeded = 0;

   /* step to the end of the request line (e.g. "GET /path HTTP/1.0\n") */
   lptr = rqptr->rqHeader.RequestHeaderPtr + rqptr->rqHeader.RequestLineLength;
   /* step over the carriage control at the end of the request line */
   if (*lptr == '\r') lptr++;
   if (*lptr == '\n') lptr++;

   /* look for the header-terminating blank line */
   while (*lptr)
   {
      /* skip over the field name and white space */
      for (cptr = lptr; !ISLWS(*lptr) && NOTEOL(*lptr); lptr++);
      while (ISLWS(*lptr)) lptr++;
      NameLength = lptr - cptr;
      /* note the start of the field parameter */
      sptr = lptr;
      /* skip to the end-of-line */
      while (NOTEOL(*lptr)) lptr++;
      /* length of field parameter */
      ValueLength = lptr - sptr;
      TotalLength = lptr - cptr;

      if (lptr[0] == '\n' || (lptr[0] == '\r' && lptr[1] == '\n'))
      {
         /* break if blank line (i.e. end-of-header) */
         if (cptr == lptr) break;
      }

      if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD_REQUEST))
         WatchDataFormatted ("{!UL}!-!#AZ\n{!UL}!-!#AZ\n",
                             TotalLength, cptr, ValueLength, cptr+NameLength);

      /* step over the carriage control at the end of the line */
      if (*lptr == '\r') lptr++;
      if (*lptr == '\n') lptr++;

      /***********/
      /* Accept: */
      /***********/

      if (toupper(cptr[0]) == 'A' && cptr[6] == ':' &&
          strsame (cptr, "Accept:", 7))
      {
         if (!rqptr->rqHeader.AcceptFieldPtr)
         {
            tptr = VmGetHeap (rqptr, TotalLength+1);
            memcpy (tptr, cptr, TotalLength);
            tptr[TotalLength] = '\0';
            rqptr->rqHeader.AcceptFieldPtr = tptr;
            rqptr->rqHeader.AcceptFieldLength = TotalLength;
            rqptr->rqHeader.AcceptPtr = tptr + NameLength;
            rqptr->rqHeader.AcceptLength = ValueLength;
         }
         else
         {
            NameLength = rqptr->rqHeader.AcceptPtr -
                         rqptr->rqHeader.AcceptFieldPtr;
            TotalLength = rqptr->rqHeader.AcceptFieldLength +
                          ValueLength + 1;
            tptr = rqptr->rqHeader.AcceptFieldPtr;
            tptr = VmReallocHeap (rqptr, tptr, TotalLength+1, FI_LI);
            rqptr->rqHeader.AcceptFieldPtr = tptr;
            rqptr->rqHeader.AcceptPtr = tptr + NameLength;
            rqptr->rqHeader.AcceptLength = TotalLength - NameLength;
            tptr += rqptr->rqHeader.AcceptFieldLength;
            rqptr->rqHeader.AcceptFieldLength = TotalLength;
            *tptr++ = ',';
            memcpy (tptr, sptr, ValueLength);
            tptr[ValueLength] = '\0';
         }
         continue;
      }

      /*******************/
      /* Accept-Charset: */
      /*******************/

      if (toupper(cptr[0]) == 'A' && tolower(cptr[7]) == 'c' &&
          strsame (cptr, "Accept-charset:", 15))
      {
         if (!rqptr->rqHeader.AcceptCharsetFieldPtr)
         {
            tptr = VmGetHeap (rqptr, TotalLength+1);
            memcpy (tptr, cptr, TotalLength);
            tptr[TotalLength] = '\0';
            rqptr->rqHeader.AcceptCharsetFieldPtr = tptr;
            rqptr->rqHeader.AcceptCharsetFieldLength = TotalLength;
            rqptr->rqHeader.AcceptCharsetPtr = tptr + NameLength;
            rqptr->rqHeader.AcceptCharsetLength = ValueLength;
         }
         else
         {
            NameLength = rqptr->rqHeader.AcceptCharsetPtr -
                         rqptr->rqHeader.AcceptCharsetFieldPtr;
            TotalLength = rqptr->rqHeader.AcceptCharsetFieldLength +
                          ValueLength + 1;
            tptr = rqptr->rqHeader.AcceptCharsetFieldPtr;
            tptr = VmReallocHeap (rqptr, tptr, TotalLength+1, FI_LI);
            rqptr->rqHeader.AcceptCharsetFieldPtr = tptr;
            rqptr->rqHeader.AcceptCharsetPtr = tptr + NameLength;
            rqptr->rqHeader.AcceptCharsetLength = TotalLength - NameLength;
            tptr += rqptr->rqHeader.AcceptCharsetFieldLength;
            rqptr->rqHeader.AcceptCharsetFieldLength = TotalLength;
            *tptr++ = ',';
            memcpy (tptr, sptr, ValueLength);
            tptr[ValueLength] = '\0';
         }
         continue;
      }

      /********************/
      /* Accept-Encoding: */
      /********************/

      if (toupper(cptr[0]) == 'A' && tolower(cptr[7]) == 'e' &&
          strsame (cptr, "Accept-encoding:", 16))
      {
         if (!rqptr->rqHeader.AcceptEncodingFieldPtr)
         {
            tptr = VmGetHeap (rqptr, TotalLength+1);
            memcpy (tptr, cptr, TotalLength);
            tptr[TotalLength] = '\0';
            rqptr->rqHeader.AcceptEncodingFieldPtr = tptr;
            rqptr->rqHeader.AcceptEncodingFieldLength = TotalLength;
            rqptr->rqHeader.AcceptEncodingPtr = tptr + NameLength;
            rqptr->rqHeader.AcceptEncodingLength = ValueLength;
         }
         else
         {
            NameLength = rqptr->rqHeader.AcceptEncodingPtr -
                         rqptr->rqHeader.AcceptEncodingFieldPtr;
            TotalLength = rqptr->rqHeader.AcceptEncodingFieldLength +
                          ValueLength + 1;
            tptr = rqptr->rqHeader.AcceptEncodingFieldPtr;
            tptr = VmReallocHeap (rqptr, tptr, TotalLength+1, FI_LI);
            rqptr->rqHeader.AcceptEncodingFieldPtr = tptr;
            rqptr->rqHeader.AcceptEncodingPtr = tptr + NameLength;
            rqptr->rqHeader.AcceptEncodingLength = TotalLength - NameLength;
            tptr += rqptr->rqHeader.AcceptEncodingFieldLength;
            rqptr->rqHeader.AcceptEncodingFieldLength = TotalLength;
            *tptr++ = ',';
            memcpy (tptr, sptr, ValueLength);
            tptr[ValueLength] = '\0';
         }
         continue;
      }

      /********************/
      /* Accept-Language: */
      /********************/

      if (toupper(cptr[0]) == 'A' && tolower(cptr[7]) == 'l' &&
          strsame (cptr, "Accept-language:", 16))
      {
         if (!rqptr->rqHeader.AcceptLangFieldPtr)
         {
            tptr = VmGetHeap (rqptr, TotalLength+1);
            memcpy (tptr, cptr, TotalLength);
            tptr[TotalLength] = '\0';
            rqptr->rqHeader.AcceptLangFieldPtr = tptr;
            rqptr->rqHeader.AcceptLangFieldLength = TotalLength;
            rqptr->rqHeader.AcceptLangPtr = tptr + NameLength;
            rqptr->rqHeader.AcceptLangLength = ValueLength;
         }
         else
         {
            NameLength = rqptr->rqHeader.AcceptLangPtr -
                         rqptr->rqHeader.AcceptLangFieldPtr;
            TotalLength = rqptr->rqHeader.AcceptLangFieldLength +
                          ValueLength + 1;
            tptr = rqptr->rqHeader.AcceptLangFieldPtr;
            tptr = VmReallocHeap (rqptr, tptr, TotalLength+1, FI_LI);
            rqptr->rqHeader.AcceptLangFieldPtr = tptr;
            rqptr->rqHeader.AcceptLangPtr = tptr + NameLength;
            rqptr->rqHeader.AcceptLangLength = TotalLength - NameLength;
            tptr += rqptr->rqHeader.AcceptLangFieldLength;
            rqptr->rqHeader.AcceptLangFieldLength = TotalLength;
            *tptr++ = ',';
            memcpy (tptr, sptr, ValueLength);
            tptr[ValueLength] = '\0';
         }
         continue;
      }

      /******************/
      /* Authorization: */
      /******************/

      if (toupper(cptr[0]) == 'A' && tolower(cptr[1]) == 'u' &&
          strsame (cptr, "Authorization:", 14))
      {
         tptr = VmGetHeap (rqptr, TotalLength+1);
         memcpy (tptr, cptr, TotalLength);
         tptr[TotalLength] = '\0';
         rqptr->rqHeader.AuthorizationFieldPtr = tptr;
         rqptr->rqHeader.AuthorizationPtr = tptr + NameLength;
         continue;
      }

      /*****************************/
      /* Cache-Control: (HTTP/1.1) */
      /*****************************/

      if (toupper(cptr[0]) == 'C' && tolower(cptr[12]) == 'l' &&
          strsame (cptr, "Cache-control:", 14))
      {
         tptr = VmGetHeap (rqptr, TotalLength+1);
         memcpy (tptr, cptr, TotalLength);
         tptr[TotalLength] = '\0';
         rqptr->rqHeader.CacheControlFieldPtr = tptr;
         rqptr->rqHeader.CacheControlPtr = tptr + NameLength;
         tptr += NameLength;
         while (*tptr)
         {
            if (strsame (tptr, "no-cache", 8) ||
                strsame (tptr, "no-store", 8) ||
                strsame (tptr, "max-age=0", 9))
            {
               rqptr->PragmaNoCache = true;
               InstanceGblSecIncrLong (&AccountingPtr->RequestPragmaNoCacheCount);
               break;
            }
            while (*tptr && !ISLWS(*tptr) && *tptr != ',') tptr++;
            while (*tptr && (ISLWS(*tptr) || *tptr == ',')) tptr++;
         }
         if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD_REQUEST))
            WatchDataFormatted ("!&B\n", rqptr->PragmaNoCache);
         continue;
      }

      /***************/
      /* Connection: */
      /***************/

      /*
         Only check for persistent connection request if it's configured.
         Persistent connection only allowed for object retrievals.
      */
      if (toupper(cptr[0]) == 'C' && tolower(cptr[8]) == 'o' &&
          strsame (cptr, "Connection:", 11))
      {
         tptr = VmGetHeap (rqptr, TotalLength+1);
         memcpy (tptr, cptr, TotalLength);
         tptr[TotalLength] = '\0';
         rqptr->rqHeader.ConnectionFieldPtr = tptr;
         rqptr->rqHeader.ConnectionPtr = tptr + NameLength;
         if (Config.cfTimeout.KeepAlive &&
             rqptr->rqNet.KeepAliveCount < KEEPALIVE_MAX &&
             rqptr->ServicePtr->RequestScheme != SCHEME_HTTPS)
         {
            tptr += NameLength;
            if (strsame (tptr, "keep-alive", 10))
               rqptr->KeepAliveRequest = true;
            if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD_REQUEST))
               WatchDataFormatted ("!&B\n", rqptr->KeepAliveRequest);
         }
         continue;
      }

      /*******************/
      /* Content-Length: */
      /*******************/

      if (toupper(cptr[0]) == 'C' && tolower(cptr[8]) == 'l' &&
          strsame (cptr, "Content-length:", 15))
      {
         tptr = VmGetHeap (rqptr, TotalLength+1);
         memcpy (tptr, cptr, TotalLength);
         tptr[TotalLength] = '\0';
         rqptr->rqHeader.ContentLengthFieldPtr = tptr;
         rqptr->rqHeader.ContentLengthPtr = tptr + NameLength;
         tptr += NameLength;
         if (isdigit(*tptr))
         {
            rqptr->rqHeader.ContentLength = atoi (tptr);
            if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD_REQUEST))
               WatchDataFormatted ("!UL\n", rqptr->rqHeader.ContentLength);
            continue;
         }
         InstanceGblSecIncrLong (&AccountingPtr->RequestErrorCount);
         rqptr->rqResponse.HttpStatus = 400;
         ErrorGeneral (rqptr, MsgFor(rqptr,MSG_REQUEST_FORMAT), FI_LI);
         return (false);
      }

      /*****************/
      /* Content-Type: */
      /*****************/

      if (toupper(cptr[0]) == 'C' && tolower(cptr[8]) == 't' &&
          strsame (cptr, "Content-type:", 13))
      {
         tptr = VmGetHeap (rqptr, TotalLength+1);
         memcpy (tptr, cptr, TotalLength);
         tptr[TotalLength] = '\0';
         rqptr->rqHeader.ContentTypeFieldPtr = tptr;
         rqptr->rqHeader.ContentTypePtr = tptr + NameLength;
         continue;
      }

      /***********/
      /* Cookie: */
      /***********/

      if (toupper(cptr[0]) == 'C' && cptr[6] == ':' &&
          strsame (cptr, "Cookie:", 7))
      {
         tptr = VmGetHeap (rqptr, TotalLength+1);
         memcpy (tptr, cptr, TotalLength);
         tptr[TotalLength] = '\0';
         rqptr->rqHeader.CookieFieldPtr = tptr;
         rqptr->rqHeader.CookiePtr = tptr + NameLength;
         continue;
      }

      /********************/
      /* ETag: (HTTP/1.1) */
      /********************/

      if (toupper(cptr[0]) == 'E' && cptr[4] == ':' &&
          strsame (cptr, "ETag:", 5))
      {
         tptr = VmGetHeap (rqptr, TotalLength+1);
         memcpy (tptr, cptr, TotalLength);
         tptr[TotalLength] = '\0';
         rqptr->rqHeader.ETagFieldPtr = tptr;
         rqptr->rqHeader.ETagPtr = tptr + NameLength;
         continue;
      }

      /**************/
      /* Forwarded: */
      /**************/

      if (toupper(cptr[0]) == 'F' && tolower(cptr[3]) == 'w' &&
          strsame (cptr, "Forwarded:", 10))
      {
         if (!rqptr->rqHeader.ForwardedFieldPtr)
         {
            tptr = VmGetHeap (rqptr, TotalLength+1);
            memcpy (tptr, cptr, TotalLength);
            tptr[TotalLength] = '\0';
            rqptr->rqHeader.ForwardedFieldPtr = tptr;
            rqptr->rqHeader.ForwardedFieldLength = TotalLength;
            rqptr->rqHeader.ForwardedPtr = tptr + NameLength;
            rqptr->rqHeader.ForwardedLength = ValueLength;
         }
         else
         {
            NameLength = rqptr->rqHeader.ForwardedPtr -
                         rqptr->rqHeader.ForwardedFieldPtr;
            TotalLength = rqptr->rqHeader.ForwardedFieldLength +
                          ValueLength + 1;
            tptr = rqptr->rqHeader.ForwardedFieldPtr;
            tptr = VmReallocHeap (rqptr, tptr, TotalLength+1, FI_LI);
            rqptr->rqHeader.ForwardedFieldPtr = tptr;
            rqptr->rqHeader.ForwardedPtr = tptr + NameLength;
            rqptr->rqHeader.ForwardedLength = TotalLength - NameLength;
            tptr += rqptr->rqHeader.ForwardedFieldLength;
            rqptr->rqHeader.ForwardedFieldLength = TotalLength;
            *tptr++ = ',';
            memcpy (tptr, sptr, ValueLength);
            tptr[ValueLength] = '\0';
         }
         continue;
      }

      /**********************/
      /* If-Modified-Since: */
      /**********************/

      if (toupper(cptr[0]) == 'I' && tolower(cptr[3]) == 'm' &&
          strsame (cptr, "If-modified-since:", 18))
      {
         tptr = VmGetHeap (rqptr, TotalLength+1);
         memcpy (tptr, cptr, TotalLength);
         tptr[TotalLength] = '\0';
         rqptr->rqHeader.IfModifiedSinceFieldPtr = tptr;
         rqptr->rqHeader.IfModifiedSincePtr = tptr + NameLength;
         tptr += NameLength;
         status = HttpGmTime (tptr, &rqptr->rqTime.IfModifiedSinceVMS64bit);
         if (VMSnok (status))
         {
            rqptr->rqHeader.IfModifiedSincePtr = NULL;
            rqptr->rqTime.IfModifiedSinceVMS64bit[0] =
            rqptr->rqTime.IfModifiedSinceVMS64bit[1] = 0;
            if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD_REQUEST))
               WatchDataFormatted ("!&Z !&S\n",
                  rqptr->rqHeader.IfModifiedSincePtr, status);
            continue;
         }

         /* indicate that no "length=" was found (if none is found :^) */
         rqptr->rqHeader.IfModifiedSinceLength = -1;

         /* look for a "length=" parameter to this header field */
         while (*tptr)
         {
            while (*tptr && *tptr != ';') tptr++; 
            if (!*tptr) break;
            tptr++;
            while (*tptr && ISLWS(*tptr)) tptr++; 
            if (!*tptr) break;
            if (!strsame (tptr, "length", 6)) continue;
            tptr += 6;
            while (*tptr && *tptr != '=') tptr++; 
            if (!*tptr) break;
            tptr++;
            while (*tptr && ISLWS(*tptr)) tptr++; 
            if (!isdigit (*tptr)) continue;
            rqptr->rqHeader.IfModifiedSinceLength = atol(tptr);
         }

         if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD_REQUEST))
            WatchDataFormatted ("!&Z !&S !%D !SL\n",
               rqptr->rqHeader.IfModifiedSincePtr, status,
               &rqptr->rqTime.IfModifiedSinceVMS64bit,
               rqptr->rqHeader.IfModifiedSinceLength);

         continue;
      }

      /*********/
      /* Host: */
      /*********/

      if (toupper(cptr[0]) == 'H' && tolower(cptr[3]) == 't' &&
          strsame (cptr, "Host:", 5))
      {
         tptr = VmGetHeap (rqptr, TotalLength+1);
         memcpy (tptr, cptr, TotalLength);
         tptr[TotalLength] = '\0';
         rqptr->rqHeader.HostFieldPtr = tptr;
         rqptr->rqHeader.HostPtr = tptr + NameLength;
         continue;
      }

      /***************/
      /* Keep-Alive: */
      /***************/

      if (toupper(cptr[0]) == 'K' && toupper(cptr[5]) == 'A' &&
          strsame (cptr, "Keep-Alive:", 11))
      {
         tptr = VmGetHeap (rqptr, TotalLength+1);
         memcpy (tptr, cptr, TotalLength);
         tptr[TotalLength] = '\0';
         rqptr->rqHeader.KeepAliveFieldPtr = tptr;
         rqptr->rqHeader.KeepAlivePtr = tptr + NameLength;
         if (Config.cfTimeout.KeepAlive &&
             rqptr->rqNet.KeepAliveCount < KEEPALIVE_MAX &&
             rqptr->ServicePtr->RequestScheme != SCHEME_HTTPS)
         {
            tptr += NameLength;
            if (isdigit (*tptr)) rqptr->KeepAliveRequest = true;
            if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD_REQUEST))
               WatchDataFormatted ("!&B\n", rqptr->KeepAliveRequest);
         }
         continue;
      }

      /***********/
      /* Pragma: */
      /***********/

      if (toupper(cptr[0]) == 'P' && tolower(cptr[3]) == 'g' &&
          strsame (cptr, "Pragma:", 7))
      {
         tptr = VmGetHeap (rqptr, TotalLength+1);
         memcpy (tptr, cptr, TotalLength);
         tptr[TotalLength] = '\0';
         rqptr->rqHeader.PragmaFieldPtr = tptr;
         rqptr->rqHeader.PragmaPtr = tptr + NameLength;
         tptr += NameLength;
         if (strsame (tptr, "no-cache", 8))
         {
            rqptr->PragmaNoCache = true;
            InstanceGblSecIncrLong (&AccountingPtr->RequestPragmaNoCacheCount);
         }
         if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD_REQUEST))
            WatchDataFormatted ("!&B\n", rqptr->PragmaNoCache);
         continue;
      }

      /************************/
      /* Proxy-Authorization: */
      /************************/

      if (toupper(cptr[0]) == 'P' && tolower(cptr[6]) == 'a' &&
          strsame (cptr, "Proxy-authorization:", 20))
      {
         tptr = VmGetHeap (rqptr, TotalLength+1);
         memcpy (tptr, cptr, TotalLength);
         tptr[TotalLength] = '\0';
         rqptr->rqHeader.ProxyAuthorizationFieldPtr = tptr;
         rqptr->rqHeader.ProxyAuthorizationPtr = tptr + NameLength;
         continue;
      }

      /*********************/
      /* Proxy-Connection: */
      /*********************/

      if (toupper(cptr[0]) == 'P' && tolower(cptr[6]) == 'c' &&
          strsame (cptr, "Proxy-connection:", 17))
      {
         tptr = VmGetHeap (rqptr, TotalLength+1);
         memcpy (tptr, cptr, TotalLength);
         tptr[TotalLength] = '\0';
         rqptr->rqHeader.ProxyConnectionFieldPtr = tptr;
         rqptr->rqHeader.ProxyConnectionPtr = tptr + NameLength;
         continue;
      }

      /**********/
      /* Range: */
      /**********/

      if (toupper(cptr[0]) == 'R' && tolower(cptr[2]) == 'n' &&
          strsame (cptr, "Range:", 6))
      {
         tptr = VmGetHeap (rqptr, TotalLength+1);
         memcpy (tptr, cptr, TotalLength);
         tptr[TotalLength] = '\0';
         rqptr->rqHeader.RangeFieldPtr = tptr;
         rqptr->rqHeader.RangePtr = tptr + NameLength;
         rqptr->rqHeader.RangeBytePtr = rbptr =
            VmGetHeap (rqptr, sizeof(RANGE_BYTE));
         tptr = rqptr->rqHeader.RangePtr;
         if (strsame (tptr, "bytes=", 6))
         {
            /* ok, that's a start */
            tptr += 6;
            idx = 0;
            while (*tptr)
            {
               while (*tptr && ISLWS(*tptr) || *tptr == ',') tptr++;
               if (!*tptr || *tptr == ';') break;
               if (idx >= RANGE_BYTE_MAX)
               {
                  ErrorNoticed (0, "RANGE_BYTE_MAX exceeded", FI_LI);
                  idx = 0;
                  break;
               }
               if (isdigit(*tptr))
               {
                  /* starting offset */
                  rbptr->First[idx] = atol(tptr);
                  while (*tptr && isdigit(*tptr)) tptr++;
                  if (*tptr == '-')
                  {
                     /* ending offset */
                     tptr++;
                     rbptr->Last[idx] = atol(tptr);
                     while (*tptr && isdigit(*tptr)) tptr++;
                  }
                  idx++;
               }
               else
               if (*tptr == '-')
               {
                  /* negative offset (from the end-of-file) */
                  rbptr->Last[idx] = atol(tptr);
                  tptr++;
                  while (*tptr && isdigit(*tptr)) tptr++;
                  idx++;
               }
               else
               {
                  /* range format error */
                  idx = 0;
                  break;
               }
            }
            rbptr->Total = idx;
            if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD_REQUEST))
            {
               WatchDataFormatted ("!UL range!%s\n", idx);
               for (idx = 0; idx < rbptr->Total; idx++)
                   WatchDataFormatted ("!UL !SL - !SL\n",
                      idx, rbptr->First[idx], rbptr->Last[idx]);
            }
         }
         continue;
      }

      /************/
      /* Referer: */
      /************/

      if (toupper(cptr[0]) == 'R' && tolower(cptr[2]) == 'f' &&
          strsame (cptr, "Referer:", 8))
      {
         tptr = VmGetHeap (rqptr, TotalLength+1);
         memcpy (tptr, cptr, TotalLength);
         tptr[TotalLength] = '\0';
         rqptr->rqHeader.RefererFieldPtr = tptr;
         rqptr->rqHeader.RefererPtr = tptr + NameLength;
         continue;
      }

      /***************/
      /* User-Agent: */
      /***************/

      if (toupper(cptr[0]) == 'U' && tolower(cptr[5]) == 'a' &&
          strsame (cptr, "User-agent:", 11))
      {
         tptr = VmGetHeap (rqptr, TotalLength+1);
         memcpy (tptr, cptr, TotalLength);
         tptr[TotalLength] = '\0';
         rqptr->rqHeader.UserAgentFieldPtr = tptr;
         rqptr->rqHeader.UserAgentPtr = tptr + NameLength;
         continue;
      }

      /********************/
      /* X-Forwarded-For: */
      /********************/

      if (toupper(cptr[0]) == 'X' && tolower(cptr[2]) == 'f' &&
          strsame (cptr, "X-Forwarded-For:", 16))
      {
         tptr = VmGetHeap (rqptr, TotalLength+1);
         memcpy (tptr, cptr, TotalLength);
         tptr[TotalLength] = '\0';
         rqptr->rqHeader.XForwardedForFieldPtr = tptr;
         rqptr->rqHeader.XForwardedForPtr = tptr + NameLength;
         continue;
      }

      /***************************/
      /* unrecognised field name */
      /***************************/

      if (rqptr->rqHeader.UnknownFieldsCount < REQUEST_UNKNOWN_FIELDS_MAX)
      {
         tptr = VmGetHeap (rqptr, TotalLength+1);
         memcpy (tptr, cptr, TotalLength);
         tptr[TotalLength] = '\0';
         rqptr->rqHeader.UnknownFieldsPtr[
            rqptr->rqHeader.UnknownFieldsCount++] = tptr;
         continue;
      }
      else
      if (!RequestUnknownFieldsMaxExceeded++)
      {
         ErrorNoticed (0, "REQUEST_UNKNOWN_FIELDS_MAX exceeded", FI_LI);
         WriteFaoStdout ("%!AZ-I-HEADER, !20%D, !AZ\n!AZ",
                         Utility, 0, rqptr->rqClient.Lookup.HostName,
                         rqptr->rqHeader.RequestHeaderPtr);
      }
   }

   /* populate the array of pointers to the request fields */
   rfidx = 0;
   rfptr = &rqptr->rqHeader.RequestFieldsPtr;

   /*
       When adding specific fields here do not forget
       to adjust macro REQUEST_KNOWN_FIELDS in WASD.H!
       There are currently 21 known fields processed here.
   */
   if (cptr = rqptr->rqHeader.AcceptFieldPtr) rfptr[rfidx++] = cptr;
   if (cptr = rqptr->rqHeader.AcceptCharsetFieldPtr) rfptr[rfidx++] = cptr;
   if (cptr = rqptr->rqHeader.AcceptEncodingFieldPtr) rfptr[rfidx++] = cptr;
   if (cptr = rqptr->rqHeader.AcceptLangFieldPtr) rfptr[rfidx++] = cptr;
   if (cptr = rqptr->rqHeader.AuthorizationFieldPtr) rfptr[rfidx++] = cptr;
   if (cptr = rqptr->rqHeader.CacheControlFieldPtr) rfptr[rfidx++] = cptr;
   if (cptr = rqptr->rqHeader.ConnectionFieldPtr) rfptr[rfidx++] = cptr;
   if (cptr = rqptr->rqHeader.ContentLengthFieldPtr) rfptr[rfidx++] = cptr;
   if (cptr = rqptr->rqHeader.ContentTypeFieldPtr) rfptr[rfidx++] = cptr;
   if (cptr = rqptr->rqHeader.CookieFieldPtr) rfptr[rfidx++] = cptr;
   if (cptr = rqptr->rqHeader.ETagFieldPtr) rfptr[rfidx++] = cptr;
   if (cptr = rqptr->rqHeader.ForwardedFieldPtr) rfptr[rfidx++] = cptr;
   if (cptr = rqptr->rqHeader.HostFieldPtr) rfptr[rfidx++] = cptr;
   if (cptr = rqptr->rqHeader.IfModifiedSinceFieldPtr) rfptr[rfidx++] = cptr;
   if (cptr = rqptr->rqHeader.KeepAlivePtr) rfptr[rfidx++] = cptr;
   if (cptr = rqptr->rqHeader.PragmaFieldPtr) rfptr[rfidx++] = cptr;
   if (cptr = rqptr->rqHeader.ProxyAuthorizationFieldPtr) rfptr[rfidx++] = cptr;
   if (cptr = rqptr->rqHeader.RangeFieldPtr) rfptr[rfidx++] = cptr;
   if (cptr = rqptr->rqHeader.RefererFieldPtr) rfptr[rfidx++] = cptr;
   if (cptr = rqptr->rqHeader.UserAgentFieldPtr) rfptr[rfidx++] = cptr;
   if (cptr = rqptr->rqHeader.XForwardedForFieldPtr) rfptr[rfidx++] = cptr;

   /* tack on the unknown fields */
   for (idx = 0; idx < rqptr->rqHeader.UnknownFieldsCount; idx++)
   {
       if (rfidx < REQUEST_FIELDS_MAX)
          rfptr[rfidx++] = rqptr->rqHeader.UnknownFieldsPtr[idx];
       else
       if (!RequestFieldsMaxExceeded++)
          ErrorNoticed (0, "REQUEST_FIELDS_MAX exceeded", FI_LI);
   }
   rqptr->rqHeader.RequestFieldsCount = rfidx;

   if (!(WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD_REQUEST))) return (true);

   WatchThis (rqptr, FI_LI, WATCH_MOD_REQUEST, "!UL fields !UL unknown",
              rqptr->rqHeader.RequestFieldsCount,
              rqptr->rqHeader.UnknownFieldsCount);
   rfptr = &rqptr->rqHeader.RequestFieldsPtr;
   for (rfidx = 0; rfidx < rqptr->rqHeader.RequestFieldsCount; rfidx++)
       WatchDataFormatted ("!UL. !&Z\n", rfidx+1, rfptr[rfidx]);

   return (true);
}

/*****************************************************************************/
/*
If a file specification does not contain a file name the function looks for a
home page in the directory.  If no home page found it generates a directory
listing ("Index of" documents).

If the file specification contains a wildcard and there is no query string a
directory listing (index) is generated.  If there is a query string it must
begin with the literal "?httpd=index" for a directory listing (index) to be
generated (this allows directives in the query string to be passed to the
directory listing functions).  If it does not begin with this literal the
default query (search) script is invoked.

Detects file types that have an associated script.  If a script is returned by
ConfigContentType(), and the file specification does not include a specific
version number (even such as ";0"), an automatic redirection is generated so
that the specified document is processed by the script, and the output from
that returned to the client.  If it does contain a version number the file is
always directly returned to the client.
*/ 

RequestExecute (REQUEST_STRUCT *rqptr)

{
   int  status,
        Length;
   char  *cptr, *mpptr;

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

   if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD_REQUEST))
      WatchThis (rqptr, FI_LI, WATCH_MOD_REQUEST, "RequestExecute()");

   if (rqptr->rqHeader.PathInfoPtr[0] == '/')
   {
      if (!ServiceFindVirtual (rqptr))
      {
         /* virtual service was not found */
         if (Config.cfServer.ServiceNotFoundUrl[0])
         {
            /* configuration specifies a redirection URL */
            rqptr->rqResponse.LocationPtr = Config.cfServer.ServiceNotFoundUrl;
            RequestEnd (rqptr);
            return;
         }
      }
      /* re-set the error reporting mechanism for the different service */
      if (rqptr->ServicePtr->ErrorReportPath[0])
         rqptr->rqResponse.ErrorReportByRedirect = true;
      else
         rqptr->rqResponse.ErrorReportByRedirect = false;
   }

   if (WATCHING(rqptr) && WATCH_CATEGORY(WATCH_REQUEST))
   {
      WatchThis (rqptr, FI_LI, WATCH_REQUEST, "!AZ !&P",
                 rqptr->rqHeader.MethodName, rqptr->rqHeader.RequestUriPtr);
      if (rqptr->NotePadPtr && rqptr->NotePadPtr[0])
         WatchThis (rqptr, FI_LI, WATCH_REQUEST, "NOTEPAD !AZ",
                    rqptr->NotePadPtr);
   }

   /* ensure any old values are ignored, in case its a redirection */
   rqptr->rqResponse.LocationPtr = NULL;

   /************************/
   /* map the request path */
   /************************/

   rqptr->RequestMappedFile[0] =
      rqptr->ScriptName[0] =
      rqptr->RequestMappedScript[0] = '\0';

   /*
      Map the path, converting it to a VMS file path (or script file name)
      and returning a pointer to a static buffer containing the mapped path.
   */
   mpptr = MapUrl_Map (rqptr->rqHeader.PathInfoPtr,
                       sizeof(rqptr->rqHeader.PathInfoPtr),
                       rqptr->RequestMappedFile,
                       sizeof(rqptr->RequestMappedFile),
                       rqptr->ScriptName,
                       sizeof(rqptr->ScriptName),
                       rqptr->RequestMappedScript,
                       sizeof(rqptr->RequestMappedScript),
                       rqptr->RequestMappedRunTime,
                       sizeof(rqptr->RequestMappedRunTime),
                       &rqptr->PathOds, rqptr);

   /* immediately after mapping in case of SET timeout rules */
   HttpdTimerSet (rqptr, TIMER_OUTPUT, 0);

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

      rqptr->rqResponse.LocationPtr = VmGetHeap (rqptr, strlen(mpptr+1));
      strcpy (rqptr->rqResponse.LocationPtr, mpptr+1);
      RequestEnd (rqptr);
      return;
   }

   if (rqptr->rqPathSet.Alert == MAPURL_PATH_ALERT_MAP) RequestAlert (rqptr);

   /* buffer the mapped path */
   rqptr->MappedPathLength = Length = strlen(mpptr);
   rqptr->MappedPathPtr = VmGetHeap (rqptr, Length+1);
   memcpy (rqptr->MappedPathPtr, mpptr, Length+1);

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

      /* MapUrl_Map() returns errors with a leading null character */
      mpptr++;
      if (isdigit(*mpptr))
      {
         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;
         }

         /* HTTP status code mapping, with following message */
         rqptr->rqResponse.HttpStatus = atol(mpptr);
         while (*mpptr && isdigit(*mpptr)) mpptr++;
         while (*mpptr && ISLWS(*mpptr)) mpptr++;

         if (rqptr->rqResponse.HttpStatus >= 300 &&
             rqptr->rqResponse.HttpStatus <= 399)
         {
            /* redirection, message "text" should be the location */
            rqptr->rqResponse.LocationPtr = mpptr;
            RequestEnd (rqptr);
            return;
         }
         else
         if (rqptr->rqResponse.HttpStatus >= 400 &&
             rqptr->rqResponse.HttpStatus <= 599)
         {
            ErrorGeneral (rqptr, mpptr, FI_LI);
            RequestEnd (rqptr);
            return;
         }
         else
         {
            /* no point to codes other 3nn/4nn/5nn, use to abort connection */
            RequestEnd (rqptr);
            return;
         }
      }
      else
      {
         /* just a rule-mapping message */
         rqptr->rqResponse.HttpStatus = 403;
         ErrorGeneral (rqptr, mpptr, FI_LI);
         RequestEnd (rqptr);
         return;
      }
   }

   for (cptr = rqptr->RequestMappedFile; *cptr && *cptr != ':'; cptr++);
   if (*(unsigned short*)cptr == '::')
   {
      /************************/
      /* DECnet not supported */
      /************************/

      /* if a node is part of the VMS file name then it's not supported */
      rqptr->rqResponse.HttpStatus = 501;
      rqptr->rqResponse.ErrorTextPtr = rqptr->rqHeader.PathInfoPtr;
      rqptr->rqResponse.ErrorOtherTextPtr = rqptr->RequestMappedFile;
      ErrorVmsStatus (rqptr, RMS$_SUPPORT, FI_LI);
      RequestEnd (rqptr);
      return;
   }

#ifdef ODS_EXTENDED
   if (OdsExtended)
      rqptr->PathOdsExtended = (rqptr->PathOds == MAPURL_PATH_ODS_5);
   else
#endif /* ODS_EXTENDED */
      rqptr->PathOdsExtended = 0;

   /* MapUrl_Map() returns CGIplus mappings indicated by a leading '+' */
   if (rqptr->ScriptName[0] == '+')
   {
      rqptr->IsCgiPlusScript = true;
      rqptr->ScriptName[0] = '/';
   }
   else
      rqptr->IsCgiPlusScript = false;

   if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD_REQUEST))
       WatchDataFormatted ("!&Z !&Z !&Z !&B !&Z !&Z !&Z\n",
          rqptr->rqHeader.PathInfoPtr, rqptr->RequestMappedFile,
          rqptr->ScriptName, rqptr->IsCgiPlusScript,
          rqptr->RequestMappedScript, rqptr->RequestMappedRunTime, mpptr);

   /******************/
   /* proxy request? */
   /******************/

   if (mpptr[0] != '/' && !rqptr->ScriptName[0])
   {
      rqptr->ProxyRequest = true;
      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 (rqptr->rqPathSet.Alert == MAPURL_PATH_ALERT_AUTH)
            RequestAlert (rqptr);
         /* look for a URL scheme delimiter */
         for (cptr = mpptr; *cptr && *cptr != '/' && *cptr != ':'; cptr++);
         if (*cptr == ':')
         {
            ProxyRequestBegin (rqptr);
            return;
         }
      }
   }
   else
      rqptr->rqAuth.RequestAuthorizationPtr =
         rqptr->rqHeader.AuthorizationPtr;

   /*********************************/
   /* authorization of request path */
   /*********************************/

   if (!rqptr->rqPathSet.AuthorizeMapped)
      Authorize (rqptr,
                 rqptr->rqHeader.PathInfoPtr,
                 rqptr->rqHeader.PathInfoLength,
                 rqptr->rqAuth.Protect1Ptr,
                 rqptr->rqAuth.Protect1Length,
                 &RequestExecutePostAuth1);
   else
   if (rqptr->ScriptName[0])
      Authorize (rqptr,
                 rqptr->ScriptName,
                 strlen(rqptr->ScriptName),
                 rqptr->rqAuth.Protect1Ptr,
                 rqptr->rqAuth.Protect1Length,
                 &RequestExecutePostAuth1);
   else
      Authorize (rqptr,
                 rqptr->MappedPathPtr,
                 rqptr->MappedPathLength,
                 rqptr->rqAuth.Protect1Ptr,
                 rqptr->rqAuth.Protect1Length,
                 &RequestExecutePostAuth1);

   if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD_REQUEST))
       WatchDataFormatted ("!&S\n", rqptr->rqAuth.FinalStatus);

   if (VMSnok (rqptr->rqAuth.FinalStatus))
   {
      /* if asynchronous authentication is underway then just wait for it */
      if (rqptr->rqAuth.FinalStatus == AUTH_PENDING) return;

      RequestEnd (rqptr);
      return;
   }

   /* authorization completed, continue to process */ 
   RequestExecutePostAuth1 (rqptr);
}

/*****************************************************************************/
/*
This function continues to initiate the request processing, either called
directly from RequestExecute() or as an AST after asynchronous authentication.
*/

RequestExecutePostAuth1 (REQUEST_STRUCT *rqptr)

{
   int  status;
   char  *cptr, *sptr;

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

   if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD_REQUEST))
      WatchThis (rqptr, FI_LI, WATCH_MOD_REQUEST,
                 "RequestExecutePostAuth1() !&F !&S",
                 &RequestExecutePostAuth1, rqptr->rqAuth.FinalStatus);

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

   if (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;
      }
      /* look for a URL scheme delimiter */
      for (cptr = rqptr->MappedPathPtr;
           *cptr && *cptr != '/' && *cptr != ':';
           cptr++);
      if (*cptr == ':')
      {
         ProxyRequestBegin (rqptr);
         return;
      }
   }

   /************************************/
   /* handled internally by the server */
   /************************************/

   if (strsame (rqptr->rqHeader.RequestUriPtr, HTTPD_ADMIN,
                sizeof(HTTPD_ADMIN)-1))
   {
      rqptr->rqCache.DoNotCache = true;
      AdminBegin (rqptr, &RequestEnd);
      return;
   }

   if (strsame (rqptr->rqHeader.RequestUriPtr, HTTPD_VS_ADMIN,
                sizeof(HTTPD_VS_ADMIN)-1))
   {
      rqptr->rqCache.DoNotCache = true;
      AdminBegin (rqptr, &RequestEnd);
      return;
   }

   if (strsame (rqptr->rqHeader.RequestUriPtr, HTTPD_VERIFY,
                sizeof(HTTPD_VERIFY)-1))
   {
      rqptr->rqCache.DoNotCache = true;
      ProxyVerifyRequest (rqptr, &RequestEnd);
      return;
   }

   if (strsame (rqptr->rqHeader.RequestUriPtr, INTERNAL_PASSWORD_CHANGE,
                sizeof(INTERNAL_PASSWORD_CHANGE)-1))
   {
      rqptr->rqCache.DoNotCache = true;
      HTAdminBegin (rqptr, &RequestEnd);
      return;
   }

   if (strsame (rqptr->rqHeader.RequestUriPtr, INTERNAL_TRACK_CANCEL,
                sizeof(INTERNAL_TRACK_CANCEL)-1))
   {
      rqptr->rqCache.DoNotCache = true;
      TrackCancelCookie (rqptr, &RequestEnd);
      return;
   }

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

   if (rqptr->ScriptName[0])
   {
      /* get the path derived from the script specification */
      cptr = rqptr->rqHeader.PathInfoPtr;
      sptr = rqptr->ScriptName;
      while (*sptr)
      {
         if (tolower(*sptr) != tolower(*cptr)) break;
         sptr++;
         cptr++;
      }
      if (!*sptr)
      {
         for (sptr = rqptr->rqHeader.PathInfoPtr; *cptr; *sptr++ = *cptr++);
         *sptr++ = '\0';
         rqptr->rqHeader.PathInfoLength = sptr - rqptr->rqHeader.PathInfoPtr;
      }

      if (!rqptr->rqPathSet.AuthorizeOnce)
      {
         /******************************************/
         /* authorization of script path parameter */
         /******************************************/

         if (!rqptr->rqPathSet.AuthorizeMapped)
            Authorize (rqptr,
                       rqptr->rqHeader.PathInfoPtr,
                       rqptr->rqHeader.PathInfoLength,
                       rqptr->rqAuth.Protect2Ptr,
                       rqptr->rqAuth.Protect2Length,
                       &RequestExecutePostAuth2);
         else
            Authorize (rqptr,
                       rqptr->MappedPathPtr,
                       rqptr->MappedPathLength,
                       rqptr->rqAuth.Protect2Ptr,
                       rqptr->rqAuth.Protect2Length,
                       &RequestExecutePostAuth2);

         if (VMSnok (rqptr->rqAuth.FinalStatus))
         {
            /* if authentication by an agent underway then just wait for it */
            if (rqptr->rqAuth.FinalStatus == AUTH_PENDING) return;

            RequestEnd (rqptr);
            return;
         }
      }
   }

   /***********************/
   /* continue non-script */ 
   /***********************/

   RequestExecutePostAuth2 (rqptr);
}

/*****************************************************************************/
/*
This function continues to initiate the request processing, either called
directly from RequestExecutePostAuth() or as an AST after asynchronous
authentication.
*/

RequestExecutePostAuth2 (REQUEST_STRUCT *rqptr)

{
   int  status;

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

   if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD_REQUEST))
      WatchThis (rqptr, FI_LI, WATCH_MOD_REQUEST,
                 "RequestExecutePostAuth2() !&F !&S",
                 &RequestExecutePostAuth2, rqptr->rqAuth.FinalStatus);

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

   if (rqptr->rqPathSet.Alert == MAPURL_PATH_ALERT_AUTH) RequestAlert (rqptr);

   /* if this path has any request throttling limits */
   if (rqptr->rqPathSet.ThrottleFrom)
   {
      status = ThrottleBegin (rqptr);
      if (VMSnok (status))
      {
         /* either on the waiting list or waiting list has been exceeded */
         if (status == SS$_ABORT) RequestEnd (rqptr);
         return;
      }
      /* continue to process the request (is on the active list) */
   }

   RequestExecutePostThrottle (rqptr);
}

/*****************************************************************************/
/*
Called by RequestExecutePostAuth2() after throttle is assessed against the path
and not initiated, or as an AST initiated by ThrottleRelease() after throttling
has been employed against the request.
*/

RequestExecutePostThrottle (REQUEST_STRUCT *rqptr)

{
   BOOL  WildcardName;
   int  status;
   char  *cptr, *sptr, *zptr;
   char  Md5String [2048];

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

   if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD_REQUEST))
      WatchThis (rqptr, FI_LI, WATCH_MOD_REQUEST,
                 "RequestExecutePostThrottle() !&F",
                 &RequestExecutePostThrottle);

   /* generate 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 (SS$_RESULTOVF, "Md5String", FI_LI);
         ErrorGeneralOverflow (rqptr, FI_LI);
         RequestEnd (rqptr);
         return;
      }
      *sptr = '\0';
      Md5Digest (Md5String, sptr-Md5String, &rqptr->Md5HashPath);
   }
   else
   {
      /* file hash is generated using the path equivalent of VMS file spec */
      Md5Digest (rqptr->MappedPathPtr,
                 rqptr->MappedPathLength,
                 &rqptr->Md5HashPath);
   }

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

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

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

   /* check for wildcard in file name or type */
   for (cptr = rqptr->MappedPathPtr; *cptr; cptr++);
   sptr = cptr;
   while (cptr > rqptr->MappedPathPtr && *cptr != ';' && *cptr != '/') cptr--;
   WildcardName = false;
   if (*cptr == ';')
   {
      /* not interested in caching specific version, only version-less paths */
      if (!cptr[1] || isdigit(cptr[1])) rqptr->rqCache.DoNotCache = true;
      /* check for wildcard characters in name or type */
      while (cptr > rqptr->MappedPathPtr && *cptr != '/')
      {
         if (*cptr == '*' || *cptr == '%' || *cptr == '?') WildcardName = true;
         cptr--;
      }
   }
   /* now resolve content-type */
   cptr = sptr;
   while (cptr > rqptr->MappedPathPtr && *cptr != '.' && *cptr != '/') cptr--;
   if (*cptr != '.') cptr = "";
   ConfigContentType (&rqptr->rqContentInfo, cptr);

   /* if not resolving a language-specific document and GET or HEAD */
   if (!(WildcardName || rqptr->rqCache.DoNotCache) &&
       (rqptr->rqHeader.Method == HTTP_METHOD_GET ||
        rqptr->rqHeader.Method == HTTP_METHOD_HEAD))
   {
      /* as there is no file name specified this will just search the cache */
      FileBegin (rqptr, &RequestEnd, &RequestExecutePostCache,
                 &rqptr->Md5HashPath, NULL, NULL);
      return;
   }

   RequestExecutePostCache (rqptr);
}

/*****************************************************************************/
/*
If a file was not found by searching the file cache using the mapped path
hash as the key then this function is called as the file-not-found AST of the
FileBegin() call in RequestExecutePostThrottle() above.
*/

RequestExecutePostCache (REQUEST_STRUCT *rqptr)

{
   int  status,
        Count;
   char  *cptr, *sptr;

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

   if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD_REQUEST))
      WatchThis (rqptr, FI_LI, WATCH_MOD_REQUEST,
                 "RequestExecutePostCache() !&F", &RequestExecutePostCache);

   /* check for device and directory (minimum) */
   cptr = rqptr->RequestMappedFile;
   if (*cptr == ':') cptr = "";
   while (*cptr && *(unsigned short*)cptr != ':[') cptr++;
   if (*cptr) cptr += 2;
   if (*cptr == ']') cptr = "";
   if (!*cptr)
   {
      /* didn't find a device and/or directory - forbidden */
      if (WATCHING(rqptr) && WATCH_CATEGORY(WATCH_REQUEST))
         WatchThis (rqptr, FI_LI, WATCH_REQUEST,
                    "MAPPING provided NO DEVICE and/or DIRECTORY!!");
      rqptr->rqResponse.HttpStatus = 500;
      rqptr->rqResponse.ErrorOtherTextPtr = rqptr->RequestMappedFile;
      ErrorVmsStatus (rqptr, RMS$_SYN, FI_LI);
      RequestEnd (rqptr);
      return;
   }

   /* parse the file specification (must have device and directory!!) */
   OdsParse (&rqptr->ParseOds,
             rqptr->RequestMappedFile, 0, NULL, 0,
             NAM$M_SYNCHK, NULL, rqptr);

   if (VMSnok (status = rqptr->ParseOds.Fab.fab$l_sts))
   {
      rqptr->rqResponse.ErrorOtherTextPtr = rqptr->RequestMappedFile;
      ErrorVmsStatus (rqptr, status, FI_LI);
      RequestEnd (rqptr);
      return;
   }

   if (VMSnok (status = OdsParseTerminate (&rqptr->ParseOds)))
   {
      ErrorNoticed (status, "OdsParseTerminate()", FI_LI);
      RequestEnd (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)
   {
      PutBegin (rqptr, &RequestEnd, NULL);
      return;
   }

   if ((tolower(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
            cptr = rqptr->rqHeader.QueryStringPtr;

         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.QueryStringPtr[0])
   {
      /*********************************************/
      /* no file name supplied, look for home page */
      /*********************************************/

      RequestHomePage (rqptr);
      return;
   }

   /********************************/
   /* get the content-type details */
   /********************************/

   if (!isdigit(rqptr->ParseOds.NamVersionPtr[1]) &&
       !(tolower(rqptr->rqHeader.QueryStringPtr[0]) == 'h' &&
         strsame (rqptr->rqHeader.QueryStringPtr, "httpd=", 6)))
   {
      /* 
         If a file specification does not include a specific version
         number (which indicates send-me-this-file-regardless), and
         the request does not contain an HTTPd query string, and
         ConfigContentType() has returned a script name, indicating
         an automatic script handling for this file type, then redirect.
      */
      if (rqptr->rqContentInfo.AutoScriptNamePtr[0] == '/')
      {
         /********************************/
         /* automatic script redirection */
         /********************************/

         InstanceGblSecIncrLong (&AccountingPtr->DoAutoScriptCount);

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

         Count = strlen(cptr = rqptr->rqContentInfo.AutoScriptNamePtr);
         Count += rqptr->rqHeader.PathInfoLength;
         rqptr->rqResponse.LocationPtr = sptr = VmGetHeap (rqptr, Count+2);
         while (*cptr) *sptr++ = *cptr++;
         for (cptr = rqptr->rqHeader.PathInfoPtr; *cptr; *sptr++ = *cptr++);
         *sptr++ = '?';
         *sptr = '\0';
         RequestEnd (rqptr);
         return;
      }
   }

   /*************************************/
   /* internal types with query strings *
   /*************************************/

   if (ConfigSameContentType (rqptr->rqContentInfo.ContentTypePtr,
                              ConfigContentTypeIsMap, -1))
   {
      /* once completely read, give the file over to the ISMAP engine */
      FileSetAuthorizePath (rqptr, false);
      FileSetCacheAllowed (rqptr, true);
      FileSetContentHandler (rqptr, &IsMapBegin, FILE_TYPE_ISMAP_SIZE_MAX);
      FileBegin (rqptr, &RequestEnd, NULL,
                 &rqptr->Md5HashPath,
                 rqptr->ParseOds.ExpFileName,
                 rqptr->rqContentInfo.ContentTypePtr);
      return;
   }

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

   if (rqptr->rqHeader.QueryStringPtr[0] &&
       Config.cfScript.DefaultSearchLength &&
       !rqptr->rqPathSet.NoDefaultSearch &&
       !(tolower(rqptr->rqHeader.QueryStringPtr[0]) == 'h' &&
         strsame (rqptr->rqHeader.QueryStringPtr, "httpd=", 6)))
   {
      if (Config.cfScript.DefaultSearchExcludePtr)
      {
         /********************************/
         /* exclude specified file types */
         /********************************/

         if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD_REQUEST))
             WatchDataFormatted ("!&Z\n",
                Config.cfScript.DefaultSearchExcludePtr);

         cptr = "";
         sptr = Config.cfScript.DefaultSearchExcludePtr;
         while (*sptr)
         {
            if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD_REQUEST))
                WatchDataFormatted ("!&Z\n", sptr);

            cptr = rqptr->ParseOds.NamTypePtr;
            while (*cptr)
            {
               if (*sptr == STRING_LIST_CHAR || *cptr == ';') break;
               if (toupper(*cptr) != toupper(*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 == STRING_LIST_CHAR)
            if (WATCHING(rqptr) && WATCH_CATEGORY(WATCH_REQUEST))
               WatchThis (rqptr, FI_LI, WATCH_REQUEST,
                          "KEYWORD search \"!AZ\" excluded in \'!AZ\'",
                          rqptr->ParseOds.NamTypePtr,
                          Config.cfScript.DefaultSearchExcludePtr);
      }

      if (!Config.cfScript.DefaultSearchExcludePtr ||
          (*cptr && *cptr != ';') || *sptr != STRING_LIST_CHAR)
      {
         /******************/
         /* search file(s) */
         /******************/

         if (WATCHING(rqptr) && WATCH_CATEGORY(WATCH_REQUEST))
            WatchThis (rqptr, FI_LI, WATCH_REQUEST, "KEYWORD search \"!AZ\"",
                       rqptr->rqHeader.QueryStringPtr);

         /* local redirection, so keyword search script gets remapped */
         Count = Config.cfScript.DefaultSearchLength;
         Count += rqptr->rqHeader.PathInfoLength;
         rqptr->rqResponse.LocationPtr = sptr = VmGetHeap (rqptr, Count+2);
         for (cptr = Config.cfScript.DefaultSearch; *cptr; *sptr++ = *cptr++);
         for (cptr = rqptr->rqHeader.PathInfoPtr; *cptr; *sptr++ = *cptr++);
         *sptr++ = '?';
         *sptr = '\0';
         RequestEnd (rqptr);
         return;
      }
   }

   /****************************************/ 
   /* otherwise ... send the file contents */
   /****************************************/ 

   /* allow for directories specified as "/dir1/dir2" (i.e. no trailing '/') */
   if (rqptr->ParseOds.NamTypeLength <= 1)
      RequestFile (rqptr, &RequestEnd, &RequestFileNoType);
   else
      RequestFile (rqptr, &RequestEnd, NULL);
}

/*****************************************************************************/
/*
This function can be called one or more times per request.  It steps through
all possible home page files until either one is found or a default directory
listing is generated.  'rqptr->HomePageStatus' attempts to short-circuit
searches for conditions other than file-not-found.  For example, if the status
is directory-not-found then there is little point in continuing to look for
home pages in that location!
*/ 

RequestHomePage (REQUEST_STRUCT *rqptr)

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

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

   if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD_REQUEST))
      WatchThis (rqptr, FI_LI, WATCH_MOD_REQUEST,
                 "RequestHomePage() !UL", rqptr->RequestHomePageIndex);

   if (rqptr->rqResponse.HeaderPtr || ERROR_REPORTED (rqptr))
   {
      RequestEnd (rqptr);
      return;
   }

   if (rqptr->RequestHomePageIndex &&
       VMSnok (rqptr->HomePageStatus) &&
       rqptr->HomePageStatus != RMS$_FNF)
   {
      rqptr->rqResponse.ErrorTextPtr = rqptr->rqHeader.PathInfoPtr;
      rqptr->rqResponse.ErrorOtherTextPtr = rqptr->ParseOds.NamDevicePtr;
      ErrorVmsStatus (rqptr, rqptr->HomePageStatus, FI_LI);
      RequestEnd (rqptr);
      return;
   }

   if (!*(HomePageNamePtr = ConfigHomePage(rqptr->RequestHomePageIndex++)))
   {
      /********************************************/
      /* no home page, generate directory listing */
      /********************************************/

      if ((!Config.cfDir.Access &&
           !rqptr->rqPathSet.DirAccess &&
           !rqptr->rqPathSet.DirAccessSelective) ||
          rqptr->rqPathSet.DirNoAccess)
      {
         /* directory listing is disabled */
         rqptr->rqResponse.ErrorTextPtr = rqptr->rqHeader.PathInfoPtr;
         rqptr->rqResponse.ErrorOtherTextPtr = rqptr->ParseOds.NamDevicePtr;
         ErrorVmsStatus (rqptr, RMS$_FNF, FI_LI);
         RequestEnd (rqptr);
         return;
      }

      rqptr->ParseOds.NamNamePtr[0] = '\0';
      if (rqptr->rqPathSet.IndexPtr)
         cptr = rqptr->rqPathSet.IndexPtr;
      else
         cptr = rqptr->rqHeader.QueryStringPtr;
      DirBegin (rqptr, &RequestEnd, rqptr->ParseOds.NamDevicePtr,
                cptr, "", false);
      return;
   }

   for (cptr = HomePageNamePtr; *cptr && *cptr != '.'; cptr++);
   FileTypePtr = cptr;

   /******************************/
   /* check for script home page */
   /******************************/

   for (idx = 0; idx < Config.cfScript.RunTimeCount; idx++)
   {
      if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD_REQUEST))
         WatchDataFormatted ("!&Z !&Z !UL\n",
                             cptr, Config.cfScript.RunTime[idx].String,
                             Config.cfScript.RunTime[idx].FileTypeLength);
      if (strsame (cptr, Config.cfScript.RunTime[idx].String,
                         Config.cfScript.RunTime[idx].FileTypeLength-1)) break;
   }
   if (idx < Config.cfScript.RunTimeCount)
   {
      /* found an appropriate script file type, does the file exist? */
      status = OdsFileExists (rqptr->ParseOds.NamDevicePtr, HomePageNamePtr);
      if (VMSnok (status))
      {
         /* (probably) file does not exist */
         rqptr->HomePageStatus = status;
         SysDclAst (RequestHomePage, rqptr);
         return;
      }

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

      if (WATCHING(rqptr) && WATCH_CATEGORY(WATCH_REQUEST))
         WatchThis (rqptr, FI_LI, WATCH_REQUEST, "SCRIPT welcome \'!AZ!AZ\'",
                    rqptr->ParseOds.NamDevicePtr, HomePageNamePtr);

      /* create a redirection to the original path plus the file name */
      Count = rqptr->rqHeader.PathInfoLength;
      Count += strlen(HomePageNamePtr);
      rqptr->rqResponse.LocationPtr = sptr = VmGetHeap (rqptr, Count+2);
      for (cptr = rqptr->rqHeader.PathInfoPtr; *cptr; *sptr++ = *cptr++);
      for (cptr = HomePageNamePtr; *cptr; *sptr++ = *cptr++);
      *sptr++ = '?';
      *sptr = '\0';
      RequestEnd (rqptr);
      return;
   }

   /********************************/
   /* generate home page file name */
   /********************************/

   /* overwrite any existing file name in the NAM block */
   strcpy (rqptr->ParseOds.NamNamePtr, HomePageNamePtr);
   /* set the mime content type for this file type */
   ConfigContentType (&rqptr->rqContentInfo, FileTypePtr);

   rqptr->HomePageStatus = SS$_NORMAL;
   RequestFile (rqptr, &RequestEnd, &RequestHomePage);
}

/*****************************************************************************/
/*
Send a file to the client.  Special cases are menu and pre-processed HTML.  
*/ 

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

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

   if (ConfigSameContentType (rqptr->rqContentInfo.ContentTypePtr,
                              ConfigContentTypeMenu, -1))
   {
      /* once completely read, give the file over to the MENU engine */
      FileSetAuthorizePath (rqptr, false);
      FileSetCacheAllowed (rqptr, true);
      FileSetContentHandler (rqptr, &MenuBegin, FILE_TYPE_MENU_SIZE_MAX);
      FileBegin (rqptr, NextTaskFunction, NoSuchFileFunction,
                 &rqptr->Md5HashPath,
                 rqptr->ParseOds.ExpFileName,
                 rqptr->rqContentInfo.ContentTypePtr);
      return;
   }

   if (ConfigSameContentType (rqptr->rqContentInfo.ContentTypePtr,
                              ConfigContentTypeUrl, -1))
   {
      if (!(rqptr->rqHeader.QueryStringPtr[0] &&
            tolower(rqptr->rqHeader.QueryStringPtr[0]) == 'h' &&
            strsame (rqptr->rqHeader.QueryStringPtr, "httpd=content", 13)))
      {
         /* once completely read, give the file over to the URL engine */
         FileSetAuthorizePath (rqptr, false);
         FileSetCacheAllowed (rqptr, true);
         FileSetContentHandler (rqptr, &FileDotUrlBegin,
                                FILE_TYPE_URL_SIZE_MAX);
         FileBegin (rqptr, NextTaskFunction, NoSuchFileFunction,
                    &rqptr->Md5HashPath,
                    rqptr->ParseOds.ExpFileName,
                    rqptr->rqContentInfo.ContentTypePtr);
         return;
      }
   }

   if (ConfigSameContentType (rqptr->rqContentInfo.ContentTypePtr,
                              ConfigContentTypeSsi, -1) &&
       (!rqptr->rqHeader.QueryStringPtr[0] ||
        (rqptr->rqHeader.QueryStringPtr[0] &&
         tolower(rqptr->rqHeader.QueryStringPtr[0]) == 'h' &&
         (strsame (rqptr->rqHeader.QueryStringPtr, "httpd=ssi", 9) ||
          strsame (rqptr->rqHeader.QueryStringPtr, "httpd=error", 11)))))
   {
      /* once completely read, give the file over to the SSI engine */
      FileSetAuthorizePath (rqptr, false);
      FileSetCacheAllowed (rqptr, true);
      FileSetContentHandler (rqptr, &SsiBegin, SsiSizeMax);
      FileBegin (rqptr, NextTaskFunction, NoSuchFileFunction,
                 /*
                    A NULL path hash will cause the *file name* to be used.
                    This allows the SSI output to be cached as well if the
                    path indicates that is required.
                 */
                 rqptr->rqPathSet.CacheSSI ? NULL : &rqptr->Md5HashPath,
                 rqptr->ParseOds.ExpFileName,
                 rqptr->rqContentInfo.ContentTypePtr);
      return;
   }

   /* JAF */
   FileSetAuthorizePath (rqptr, false);
   FileSetCacheAllowed (rqptr, true);
   FileBegin (rqptr, NextTaskFunction, NoSuchFileFunction,
              &rqptr->Md5HashPath,
              rqptr->ParseOds.ExpFileName,
              rqptr->rqContentInfo.ContentTypePtr);
}

/*****************************************************************************/
/*
The file requested had no type (extension) and was not found.  Check if there
is a directory of that name.  This allows for directories to be specified as
"/dir1/dir2" (i.e. no trailing '/') which is not strictly kosher in WWW syntax
but is allowed by some servers and so does occur in some documents.  Generate
a redirection to the same URL but with a trailing slash on the directory name.
*/ 

RequestFileNoType (REQUEST_STRUCT *rqptr)

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

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

   if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD_REQUEST))
      WatchThis (rqptr, FI_LI, WATCH_MOD_REQUEST, "RequestFileNoType()");

   zptr = (sptr = DirName) + sizeof(DirName)-1;
   cptr = rqptr->ParseOds.NamNamePtr;
   while (*cptr && sptr < zptr) *sptr++ = *cptr++;
   cptr = ".DIR;";
   while (*cptr && sptr < zptr) *sptr++ = *cptr++;
   *sptr = '\0';

   ch = rqptr->ParseOds.NamNamePtr[0];
   rqptr->ParseOds.NamNamePtr[0] = '\0';

   if (VMSok (OdsFileExists (rqptr->ParseOds.NamDevicePtr, DirName)))
   {
      if (WATCHING(rqptr) && WATCH_CATEGORY(WATCH_REQUEST))
         WatchThis (rqptr, FI_LI, WATCH_REQUEST, "DIRECTORY exists \'!AZ!AZ\'",
                    rqptr->ParseOds.NamDevicePtr, DirName);

      rqptr->rqResponse.LocationPtr = sptr =
          VmGetHeap (rqptr, rqptr->rqHeader.PathInfoLength+5);
      *sptr++ = '/';
      *sptr++ = '/';
      for (cptr = rqptr->rqHeader.PathInfoPtr; *cptr; *sptr++ = *cptr++);
      *sptr++ = '/';
      *sptr++ = '?';
      *sptr = '\0';
      RequestEnd (rqptr);
      return;
   }
   else
   {
      rqptr->ParseOds.NamNamePtr[0] = ch;
      if (!rqptr->AccountingDone++)
         InstanceGblSecIncrLong (&AccountingPtr->DoFileCount);
      rqptr->rqResponse.ErrorTextPtr =
         MapVmsPath (rqptr->ParseOds.NamDevicePtr, rqptr);
      rqptr->rqResponse.ErrorOtherTextPtr = rqptr->ParseOds.NamDevicePtr;
      ErrorVmsStatus (rqptr, RMS$_FNF, FI_LI);
      RequestEnd (rqptr);

      return;
   }
}

/*****************************************************************************/
/*
Process request by executing a script.  The 'MappedFile' was the 
non-script-name  portion of the path, returned by the original MapUrl(),
attempt to parse this as a VMS file specification, but if that fails (and it
well might not be a file specification) then do not report it.  Provide to the
script a "best-guess" as to the file system ODS.
*/ 

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

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

   if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD_REQUEST))
      WatchThis (rqptr, FI_LI, WATCH_MOD_REQUEST,
                 "RequestScript() !&Z !&Z !&Z !&Z !&Z",
                 rqptr->ScriptName, rqptr->rqHeader.PathInfoPtr,
                 MappedScript, MappedFile, MappedRunTime);

   /***************************************/
   /* check for internal server "scripts" */
   /***************************************/

   if (tolower(rqptr->ScriptName[0]) == tolower(ADMIN_SCRIPT_ECHO[0]) &&
       strsame (rqptr->ScriptName, ADMIN_SCRIPT_ECHO,
                sizeof(ADMIN_SCRIPT_ECHO)-1))
   {
      ResponseEcho (rqptr);
      return;
   }
   if (tolower(rqptr->ScriptName[0]) == tolower(ADMIN_SCRIPT_HISS[0]) &&
       strsame (rqptr->ScriptName, ADMIN_SCRIPT_HISS,
                sizeof(ADMIN_SCRIPT_HISS)-1))
   {
      ResponseHiss (rqptr);
      return;
   }
   if (tolower(rqptr->ScriptName[0]) == tolower(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 && *(unsigned short*)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 (status, "OdsParseTerminate()", 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 (tolower(rqptr->ScriptName[0]) == tolower(ADMIN_SCRIPT_UPD[0]) &&
        strsame (rqptr->ScriptName, ADMIN_SCRIPT_UPD, -1))
   {
      UpdBegin (rqptr, &RequestEnd);
      return;
   }
   else
   if (tolower(rqptr->ScriptName[0]) == tolower(ADMIN_SCRIPT_TREE[0]) &&
        strsame (rqptr->ScriptName, ADMIN_SCRIPT_TREE, -1))
   {
      UpdBegin (rqptr, &RequestEnd);
      return;
   }
   else
   if (tolower(rqptr->ScriptName[0]) == tolower(ADMIN_SCRIPT_XRAY[0]) &&
       strsame (rqptr->ScriptName, ADMIN_SCRIPT_XRAY, -1))
   {
      /* set up a LOCAL redirection, stripping the '/xray' from the path */
      rqptr->rqResponse.LocationPtr = rqptr->rqHeader.RequestUriPtr +
                                      sizeof(ADMIN_SCRIPT_XRAY)-1;

      /* create a plain-text response header */
      rqptr->KeepAliveRequest = false;
      rqptr->rqResponse.PreExpired = true;
      RESPONSE_HEADER_200_PLAIN (rqptr);

      /* 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 (*(unsigned short*)cptr == '::')
      DECnetBegin (rqptr, &RequestEnd, MappedScript, MappedRunTime);
   else
   if (rqptr->IsCgiPlusScript)
      DclBegin (rqptr, &RequestEnd, NULL,
                rqptr->ScriptName, NULL, MappedScript, MappedRunTime, NULL);
   else
      DclBegin (rqptr, &RequestEnd, NULL,
                rqptr->ScriptName, MappedScript, NULL, MappedRunTime, NULL);
}

/*****************************************************************************/
/*
This function updates the global section with a formatted representation of
the data of the latest request.  The acounting data is of course permanently
located in the area and so does not require any explicit placement into the
section.
*/ 

RequestGblSecUpdate (REQUEST_STRUCT *rqptr)

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

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

   if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD_REQUEST))
      WatchThis (rqptr, FI_LI, WATCH_MOD_REQUEST, "RequestGblSecUpdate()");

   gsptr = HttpdGblSecPtr;

   if (!rqptr)
   {
      /* reset request data */
      memset (&gsptr->Request, 0, sizeof(gsptr->Request));
      return;
   }

   /* if a keep-alive timeout then just return */
   if (rqptr->KeepAliveTimeout) return;

   /***********************************/
   /* update the monitor request data */
   /***********************************/

   gsptr->Request.HttpStatus = rqptr->rqResponse.HttpStatus;
   gsptr->Request.BytesRawRx = rqptr->BytesRawRx;
   gsptr->Request.BytesRawTx = rqptr->BytesRawTx;

   vecptr = FaoVector;
   *vecptr++ = rqptr->rqTime.VmsVector[2];
   *vecptr++ = rqptr->rqTime.VmsVector[3];
   *vecptr++ = rqptr->rqTime.VmsVector[4];
   *vecptr++ = rqptr->rqTime.VmsVector[5];
   WriteFaol (gsptr->Request.Time, sizeof(gsptr->Request.Time), NULL,
              "!2ZL !2ZL:!2ZL:!2ZL", &FaoVector);

   Seconds = rqptr->rqResponse.Duration / USECS_IN_A_SECOND;
   SubSeconds = (rqptr->rqResponse.Duration % USECS_IN_A_SECOND) /
                DURATION_UNITS_USECS;
   vecptr = FaoVector;
   *vecptr++ = Seconds;
   *vecptr++ = DURATION_DECIMAL_PLACES;
   *vecptr++ = SubSeconds;
   WriteFaol (gsptr->Request.Duration, sizeof(gsptr->Request.Duration), NULL,
              "!UL.!#ZL", &FaoVector);

   /* e.g. http://the.server.host.name:port */
   zptr = (sptr = gsptr->Request.Service) + sizeof(gsptr->Request.Service)-1;
   for (cptr = rqptr->ServicePtr->RequestSchemeNamePtr;
        *cptr && sptr < zptr;
        *sptr++ = *cptr++); 
   if (sptr < zptr) *sptr++ = '/';
   if (sptr < zptr) *sptr++ = '/';
   for (cptr = rqptr->ServicePtr->ServerHostName;
        *cptr && sptr < zptr;
        *sptr++ = *cptr++); 
   if (sptr < zptr) *sptr++ = ':';
   for (cptr = rqptr->ServicePtr->ServerPortString;
        *cptr && sptr < zptr;
        *sptr++ = *cptr++); 
   *sptr = '\0';

   if (InstanceNodeConfig > 1)
   {
      zptr = (sptr = gsptr->Request.PrcNam) + sizeof(gsptr->Request.PrcNam)-1;
      for (cptr = HttpdProcess.PrcNam;
           *cptr && sptr < zptr;
           *sptr++ = *cptr++); 
      *sptr = '\0';
   }
   else
      gsptr->Request.PrcNam[0] = '\0';

   zptr = (sptr = gsptr->Request.ClientHostName) +
          sizeof(gsptr->Request.ClientHostName)-1;
   for (cptr = rqptr->rqClient.Lookup.HostName;
        *cptr && sptr < zptr;
        *sptr++ = *cptr++); 
   *sptr = '\0';

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

   if (rqptr->rqHeader.RequestUriPtr)
   {
      zptr = (sptr = gsptr->Request.Uri) + sizeof(gsptr->Request.Uri)-1;
      for (cptr = rqptr->rqHeader.RequestUriPtr;
           *cptr && sptr < zptr;
           *sptr++ = *cptr++); 
      *sptr = '\0';
   }
   else
      gsptr->Request.Uri[0] = '\0';

   gsptr->Request.Alert = rqptr->rqPathSet.Alert;

   if (rqptr->rqNet.ReadErrorCount)
      status = WriteFao (gsptr->Request.ReadError,
                         sizeof(gsptr->Request.ReadError),
                         NULL, "!&S !-!&m",
                         rqptr->rqNet.ReadErrorStatus);
   else
      gsptr->Request.ReadError[0] = '\0';

   if (rqptr->rqNet.WriteErrorCount)
      status = WriteFao (gsptr->Request.WriteError,
                         sizeof(gsptr->Request.WriteError),
                         NULL, "!&S !-!&m",
                         rqptr->rqNet.WriteErrorStatus);
   else
      gsptr->Request.WriteError[0] = '\0';
}

/*****************************************************************************/
/*
Keeps a linked list history from most to least recent that can be reported on
by RequestReport().
*/

RequestHistory (REQUEST_STRUCT *rqptr)

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

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

   if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD_REQUEST))
      WatchThis (rqptr, FI_LI, WATCH_MOD_REQUEST, "RequestHistory()");

   if (RequestHistoryCount < RequestHistoryMax)
   {
      /* allocate memory for a new entry */
      hlptr = VmGet (sizeof (struct RequestHistoryStruct));
      RequestHistoryCount++;
   }
   else
   {
      /* reuse the tail entry (least recent) */
      hlptr = RequestHistoryList.TailPtr;
      ListRemove (&RequestHistoryList, hlptr);
   }

   /* add entry to the head of the history list (most recent) */
   ListAddHead (&RequestHistoryList, hlptr);

   memcpy (&hlptr->BinaryTime, &rqptr->rqTime.Vms64bit, 8);
   hlptr->ConnectNumber = rqptr->ConnectNumber;
   hlptr->ServicePtr = rqptr->ServicePtr;
   hlptr->BytesRawRx = rqptr->BytesRawRx;
   hlptr->BytesRawTx = rqptr->BytesRawTx;
   hlptr->ResponseDuration = rqptr->rqResponse.Duration;
   hlptr->ResponseStatusCode = rqptr->rqResponse.HttpStatus;

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

   if (rqptr->RemoteUser[0])
   {
      cptr = rqptr->RemoteUser;
      while (*cptr && sptr < zptr) *sptr++ = *cptr++;
      if (rqptr->rqAuth.RealmPtr)
      {
         if (sptr < zptr) *sptr++ = '.';
         cptr = rqptr->rqAuth.RealmPtr;
         while (*cptr && sptr < zptr) *sptr++ = *cptr++;
      }
      if (sptr < zptr) *sptr++ = '@';
   }
   cptr = rqptr->rqClient.Lookup.HostName;
   while (*cptr && sptr < zptr) *sptr++ = *cptr++;
   if (sptr < zptr) *sptr++ = '\0';
   hlptr->RequestPtr = sptr;

   if (rqptr->rqHeader.PathInfoPtr && rqptr->rqHeader.QueryStringPtr)
   {
      cptr = rqptr->rqHeader.MethodName;
      while (*cptr && sptr < zptr) *sptr++ = *cptr++;
      if (sptr < zptr) *sptr++ = ' ';
      cptr = rqptr->ScriptName;
      while (*cptr && sptr < zptr) *sptr++ = *cptr++;
      cptr = rqptr->rqHeader.PathInfoPtr;
      while (*cptr && sptr < zptr) *sptr++ = *cptr++;
      if (rqptr->rqHeader.QueryStringPtr[0] && sptr < zptr) *sptr++ = '?';
      cptr = rqptr->rqHeader.QueryStringPtr;
      while (*cptr && sptr < zptr) *sptr++ = *cptr++;
   }

   if (sptr >= zptr)
   {
      hlptr->Truncated = true;
      sptr--;
   }
   else
      hlptr->Truncated = false;
   *sptr = '\0';
}

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

RequestReport
(
REQUEST_STRUCT *rqptr,
REQUEST_AST NextTaskFunction,
BOOL IncludeHistory
)
{
   static long  Subx2 = 2,
                EdivTen = 10;

   /* the final column just adds a little white-space on the page far right */
   static char  BeginPageFao [] =
"<P><TABLE CELLPADDING=1 CELLSPACING=0 BORDER=0>\n\
<TR><TH></TH>\
<TH ALIGN=left><U>Service&nbsp;/&nbsp;Client</U>&nbsp;&nbsp;</TH>\
<TH ALIGN=left><U>Time&nbsp;/&nbsp;Request</U>&nbsp;&nbsp;</TH>\
<TH ALIGN=right><U>Rx</U>&nbsp;&nbsp;</TH>\
<TH ALIGN=right><U>Tx</U>&nbsp;&nbsp;</TH>\
<TH ALIGN=right><U>Duration</U>&nbsp;&nbsp;</TH>\
<TH ALIGN=right><U>Throttle</U>&nbsp;&nbsp;</TH>\
<TH ALIGN=right><U>WATCH</U>&nbsp;&nbsp;</TH>\
<TH ALIGN=right><U>Connect</U></TH>\
<TH></TH>\
<TH>&nbsp;&nbsp;</TH>\
</TR>\n\
<TR HEIGHT=5></TR>\n";

   /* the empty 99% column just forces the rest left with long request URIs */
   static char  CurrentRequestFao [] =
"<TR>\
<TH ALIGN=right>!3ZL&nbsp;&nbsp;</TH>\
<TD ALIGN=left>!AZ//!AZ&nbsp;&nbsp;</TD>\
<TD ALIGN=left><NOBR>!20%D&nbsp;&nbsp;</NOBR></TD>\
<TD ALIGN=right>!UL&nbsp;&nbsp;</TD>\
<TD ALIGN=right>!UL&nbsp;&nbsp;</TD>\
<TD ALIGN=right>!UL.!#ZL!AZ&nbsp;&nbsp;</TD>\
<TD ALIGN=right>!&@&nbsp;&nbsp;</TD>\
<TD ALIGN=right>!&@&nbsp;&nbsp;</TD>\
<TD ALIGN=right>!UL</TD>\
!AZ\
</TR>\n\
<TR><TH></TH>\
<TD ALIGN=left BGCOLOR=\"#eeeeee\">!&@&nbsp;&nbsp;</TD>\
<TD ALIGN=left COLSPAN=8 BGCOLOR=\"#eeeeee\">!&@</TD>\
</TR>\n";

   /* the final column just adds a little white-space on the page far right */
   static char  HistoryFao [] =
"</TABLE>\n\
<P><TABLE CELLPADDING=1 CELLSPACING=0 BORDER=0>\n\
<TR><TH></TH>\
<TH ALIGN=left><U>Service&nbsp;/&nbsp;Client</U>&nbsp;&nbsp;</TH>\
<TH ALIGN=left><U>Time&nbsp;/&nbsp;Request</U>&nbsp;&nbsp;</TH>\
<TH ALIGN=right><U>Rx</U>&nbsp;&nbsp;</TH>\
<TH ALIGN=right><U>Tx</U>&nbsp;&nbsp;</TH>\
<TH ALIGN=right><U>Duration</U>&nbsp;&nbsp;</TH>\
<TH ALIGN=right><U>Status</U>&nbsp;&nbsp;</HT>\
<TH ALIGN=right><U>Connect</U></TH>\
<TH></TH>\
<TH>&nbsp;&nbsp;</TH>\
</TR>\n\
<TR HEIGHT=5></TR>\n";

   /* the empty 99% column just forces the rest left with long request URIs */
   static char  HistoryRequestFao [] =
"<TR>\
<TH ALIGN=right>!3ZL&nbsp;&nbsp;</TH>\
<TD ALIGN=left>!AZ//!AZ&nbsp;&nbsp;</TD>\
<TD ALIGN=left><NOBR>!20%D&nbsp;&nbsp;</NOBR></TD>\
<TD ALIGN=right>!UL&nbsp;&nbsp;</TD>\
<TD ALIGN=right>!UL&nbsp;&nbsp;</TD>\
<TD ALIGN=right>!UL.!#ZL&nbsp;&nbsp;</TD>\
<TD ALIGN=right>!UL&nbsp;&nbsp;</TD>\
<TD ALIGN=right>!UL</TD>\
!AZ\
</TR>\n\
<TR><TH></TH>\
<TD ALIGN=left BGCOLOR=\"#eeeeee\">!AZ&nbsp;&nbsp;</TD>\
<TD ALIGN=left COLSPAN=7 BGCOLOR=\"#eeeeee\">\
<NOBR><TT>!AZ</TT>!&?[truncated!]\r\r</NOBR></TD>\
</TR>\n";

   static char  HistoryRequestEmptyFao [] =
"<TR>\
<TH ALIGN=right><B>000</B>&nbsp;&nbsp;</TH>\
<TD ALIGN=left COLSPAN=7 BGCOLOR=\"#eeeeee\"><I>empty</I></TD>\
</TR>\n";

   static char  HistoryButtonFao [] = 
"</TABLE>\n\
<P><HR SIZE=1 NOSHADE WIDTH=60% ALIGN=LEFT>\n\
<FONT SIZE=+1>[<A HREF=\"!AZ\">History</A>]</FONT>\n\
</BODY>\n\
</HTML>\n";

   static char  EndPageFao [] =
"</TABLE>\n\
<P><HR SIZE=1 NOSHADE WIDTH=60% ALIGN=LEFT>\n\
</BODY>\n\
</HTML>\n";

   int  status,
        Count;
   unsigned long  ResponseDuration,
                  Remainder,
                  Seconds,
                  SubSeconds;
   unsigned long  *vecptr;
   unsigned long  BinaryTime [2],
                  FaoVector [32],
                  ResultTime [2];
   REQUEST_STRUCT  *rqeptr;
   LIST_ENTRY  *leptr;
   struct RequestHistoryStruct  *rqhptr;

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

   if (WATCH_MODULE(WATCH_MOD_REQUEST))
      WatchThis (NULL, FI_LI, WATCH_MOD_REQUEST,
                 "RequestReport() !&A", NextTaskFunction);

   rqptr->rqResponse.PreExpired = PRE_EXPIRE_ADMIN;
   RESPONSE_HEADER_200_HTML (rqptr);
   AdminPageTitle (rqptr, "Request Report");

   status = NetWriteFaol (rqptr, BeginPageFao, NULL);
   if (VMSnok (status)) ErrorNoticed (status, "NetWriteFaol()", FI_LI);

   /********************/
   /* current requests */
   /********************/

   Count = 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;

      vecptr = FaoVector;

      *vecptr++ = ++Count;
      *vecptr++ = rqeptr->ServicePtr->RequestSchemeNamePtr;
      *vecptr++ = rqeptr->ServicePtr->ServerHostPort;
      *vecptr++ = &rqeptr->rqTime.Vms64bit;
      *vecptr++ = rqeptr->BytesRawRx;
      *vecptr++ = rqeptr->BytesRawTx;

      status = lib$subx (&BinaryTime, &rqeptr->rqTime.Vms64bit, 
                         &ResultTime, &Subx2);
      /* convert to microseconds (longword becomes 71 minutes max) */
      if (VMSok (status))
         status = lib$ediv (&EdivTen, &ResultTime,
                            &ResponseDuration, &Remainder);
      if (VMSnok (status)) ResponseDuration = 0;

      Seconds = ResponseDuration / USECS_IN_A_SECOND;
      SubSeconds = (ResponseDuration % USECS_IN_A_SECOND) /
                   DURATION_UNITS_USECS;
      *vecptr++ = Seconds;
      *vecptr++ = DURATION_DECIMAL_PLACES;
      *vecptr++ = SubSeconds;

      if (rqeptr->rqTmr.TerminatedCount)
         *vecptr++ = " (timed-out)";
      else
         *vecptr++ = "";

      if (rqeptr->rqPathSet.ThrottleFrom)
      {
         if (rqeptr->ThrottleListEntry.DataPtr)
         {
            *vecptr++ =
"<FONT SIZE=-1>\
[<A HREF=\"!AZ?this=!UL\">release</A>]<BR>\
[<A 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->ConnectNumber != rqptr->ConnectNumber)
      {
         *vecptr++ =
"[<A HREF=\"!AZ?at=!UL\">P</A>]\
[<A HREF=\"!AZ?at=!UL&this=!UL\">+</A>]\
[<A 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;
      }
      else
         *vecptr++ = "current";

      *vecptr++ = rqeptr->ConnectNumber;

      if (rqptr->rqHeader.AdminNetscapeGold)
         *vecptr++ = "<TD></TD>";
      else
         *vecptr++ = "<TD WIDTH=99%></TD>";

      if (rqeptr->RemoteUser[0])
      {
         if (rqeptr->rqAuth.RealmPtr)
            *vecptr++ = "!&;AZ.!&;AZ@!AZ";
         else
            *vecptr++ = "!&;AZ@!AZ";
         *vecptr++ = rqeptr->RemoteUser;
         if (rqeptr->rqAuth.RealmPtr)
            *vecptr++ = rqeptr->rqAuth.RealmPtr;
      }
      else
         *vecptr++ = "!AZ";
      *vecptr++ = rqeptr->rqClient.Lookup.HostName;

      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.KeepAliveCount)
         {
            *vecptr++ = "[Keep-Alive: !UL]";
            *vecptr++ = rqeptr->rqNet.KeepAliveCount;
         }
         else
            *vecptr++ = "";
      }

      status = NetWriteFaol (rqptr, CurrentRequestFao, &FaoVector);
      if (VMSnok (status)) ErrorNoticed (status, "NetWriteFaol()", FI_LI);
   }

   if (!IncludeHistory)
   {
      status = NetWriteFao (rqptr, HistoryButtonFao,
                            ADMIN_REPORT_REQUEST_HISTORY);
      if (VMSnok (status)) ErrorNoticed (status, "NetWriteFao()", FI_LI);
      SysDclAst (NextTaskFunction, rqptr);
      return;
   }

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

   vecptr = FaoVector;
   *vecptr++ = RequestHistoryMax;

   status = NetWriteFaol (rqptr, HistoryFao, &FaoVector);
   if (VMSnok (status)) ErrorNoticed (status, "NetWriteFaol()", FI_LI);

   Count = 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++ = ++Count;
      *vecptr++ = rqhptr->ServicePtr->RequestSchemeNamePtr;
      *vecptr++ = rqhptr->ServicePtr->ServerHostPort;
      *vecptr++ = &rqhptr->BinaryTime;
      *vecptr++ = rqhptr->BytesRawRx;
      *vecptr++ = rqhptr->BytesRawTx;

      Seconds = rqhptr->ResponseDuration / USECS_IN_A_SECOND;
      SubSeconds = (rqhptr->ResponseDuration % USECS_IN_A_SECOND) /
                   DURATION_UNITS_USECS;
      *vecptr++ = Seconds;
      *vecptr++ = DURATION_DECIMAL_PLACES;
      *vecptr++ = SubSeconds;

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

      if (rqptr->rqHeader.AdminNetscapeGold)
         *vecptr++ = "<TD></TD>";
      else
         *vecptr++ = "<TD WIDTH=99%></TD>";

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

      status = NetWriteFaol (rqptr, HistoryRequestFao, &FaoVector);
      if (VMSnok (status)) ErrorNoticed (status, "NetWriteFaol()", FI_LI);
   }
   if (!RequestHistoryList.HeadPtr)
   {
      status = NetWriteFaol (rqptr, HistoryRequestEmptyFao, NULL);
      if (VMSnok (status)) ErrorNoticed (status, "NetWriteFaol()", FI_LI);
   }

   status = NetWriteFaol (rqptr, EndPageFao, NULL);
   if (VMSnok (status)) ErrorNoticed (status, "NetWriteFaol()", FI_LI);

   SysDclAst (NextTaskFunction, rqptr);
}

/*****************************************************************************/
/*
Dump the current and history request lists, for 'crash' analysis.
*/ 

RequestDump ()

{
   static long  Subx2 = 2,
                EdivTen = 10;

   int  status,
        Count;
   unsigned long  ResponseDuration,
                  Remainder,
                  Seconds,
                  SubSeconds;
   unsigned long  *vecptr;
   unsigned long  BinaryTime [2],
                  FaoVector [32],
                  ResultTime [2];
   char  Buffer [2048];
   LIST_ENTRY  *leptr;
   REQUEST_STRUCT  *rqeptr;
   struct RequestHistoryStruct  *rqhptr;

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

   if (WATCH_MODULE(WATCH_MOD_REQUEST))
      WatchThis (NULL, FI_LI, WATCH_MOD_REQUEST, "RequestDump()");

   sys$gettim (&BinaryTime);

   Count = 0;

   for (leptr = RequestList.HeadPtr; leptr; leptr = leptr->NextPtr)
   {
      rqeptr = (REQUEST_STRUCT*)leptr;

      vecptr = FaoVector;

      *vecptr++ = ++Count;
      *vecptr++ = rqeptr->ConnectNumber;
      *vecptr++ = rqeptr->ServicePtr->RequestSchemeNamePtr;
      *vecptr++ = rqeptr->ServicePtr->ServerHostPort;
      *vecptr++ = &rqeptr->rqTime.Vms64bit;

      status = lib$subx (&BinaryTime, &rqeptr->rqTime.Vms64bit, 
                         &ResultTime, &Subx2);
      /* convert to microseconds (longword becomes 71 minutes max) */
      if (VMSok (status))
         status = lib$ediv (&EdivTen, &ResultTime,
                            &ResponseDuration, &Remainder);
      if (VMSnok (status)) ResponseDuration = 0;

      Seconds = ResponseDuration / USECS_IN_A_SECOND;
      SubSeconds = (ResponseDuration % USECS_IN_A_SECOND) /
                   DURATION_UNITS_USECS;

      *vecptr++ = Seconds;
      *vecptr++ = DURATION_DECIMAL_PLACES;
      *vecptr++ = SubSeconds;
      if (rqeptr->rqTmr.TerminatedCount)
         *vecptr++ = "(timed-out)";
      else
         *vecptr++ = "";

      *vecptr++ = rqeptr->BytesRawRx;
      *vecptr++ = rqeptr->BytesRawTx;

      if (rqeptr->RemoteUser[0])
      {
         if (rqeptr->rqAuth.RealmPtr)
            *vecptr++ = "!&;AZ.!&;AZ@!AZ";
         else
            *vecptr++ = "!&;AZ@!AZ";
         *vecptr++ = rqeptr->RemoteUser;
         if (rqeptr->rqAuth.RealmPtr)
            *vecptr++ = rqeptr->rqAuth.RealmPtr;
      }
      else
         *vecptr++ = "!AZ";
      *vecptr++ = rqeptr->rqClient.Lookup.HostName;

      if (rqeptr->rqHeader.PathInfoPtr &&
          rqeptr->rqHeader.QueryStringPtr)
      {
         *vecptr++ = "!AZ !AZ";
         *vecptr++ = rqeptr->rqHeader.MethodName;
         *vecptr++ = rqeptr->rqHeader.RequestUriPtr;
      }
      else
      if (rqeptr->rqNet.KeepAliveCount)
      {
         *vecptr++ = "[Keep-Alive:!UL]";
         *vecptr++ = rqeptr->rqNet.KeepAliveCount;
      }
      else
         *vecptr++ = "[null]";
      *vecptr++ = rqeptr->rqResponse.HttpStatus;

      status = WriteFaol (Buffer, sizeof(Buffer), NULL,
"C!3ZL !UL !AZ//!AZ !20%D !UL.!#ZL!AZ !UL !UL !&@ !&@ !3ZL\n",
                          &FaoVector);
      if (VMSnok (status)) fprintf (stdout, "WriteFaol() %%X%08.08X\n", status);
      fputs (Buffer, stdout);
   }

   Count = 0;

   for (leptr = RequestHistoryList.HeadPtr;
        leptr;
        leptr = leptr->NextPtr)
   {
      rqhptr = (struct RequestHistoryStruct*)leptr;

      vecptr = FaoVector;
      *vecptr++ = ++Count;
      *vecptr++ = rqhptr->ConnectNumber;
      *vecptr++ = rqhptr->ServicePtr->RequestSchemeNamePtr;
      *vecptr++ = rqhptr->ServicePtr->ServerHostPort;
      *vecptr++ = &rqhptr->BinaryTime;

      Seconds = rqhptr->ResponseDuration / USECS_IN_A_SECOND;
      SubSeconds = (rqhptr->ResponseDuration % USECS_IN_A_SECOND) /
                   DURATION_UNITS_USECS;
      *vecptr++ = Seconds;
      *vecptr++ = DURATION_DECIMAL_PLACES;
      *vecptr++ = SubSeconds;

      *vecptr++ = rqhptr->BytesRawRx;
      *vecptr++ = rqhptr->BytesRawTx;
      *vecptr++ = rqhptr->ClientAndRequest;
      *vecptr++ = rqhptr->RequestPtr;
      *vecptr++ = rqhptr->Truncated;
      *vecptr++ = rqhptr->ResponseStatusCode;

      status = WriteFaol (Buffer, sizeof(Buffer), NULL,
"H!3ZL !UL !AZ//!AZ !20%D !UL.!#ZL !UL !UL !AZ !AZ!&?[truncated!]\r\r !3ZL\n",
                          &FaoVector);
      if (VMSnok (status)) fprintf (stdout, "WriteFaol() %%X%08.08X\n", status);
      fputs (Buffer, stdout);
   }
}

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

