/*****************************************************************************/
/*
                                 Proxy.c

*************
** CAUTION **
*************

THIS MODULE IS TASK-ORIENTED, NOT REQUEST-ORIENTED.

That is, most of the functions take a pointer to proxy task rather than a
pointer to request as do other modules. The reason is simple. Many of these
functions are designed for use independently of a request.

Implements a basic HTTP proxy service in conjunction with ProxyCache.c module.
This module provides all the request parsing, networking, response parsing and
writing to the client.  The ProxyCache.c module provides the caching in files
of candidate requests.

All errors related to connecting to, requesting from, and processing the
response from the proxied server are reported as 502s (gateway failures).  Any
errors such as buffer overflows, etc., are reported as 500 (internal problems).


NON-PROXY REDIRECTION
---------------------
WASD provides for redirection from non-proxy to proxy request, allowing a type
of gatewaying between http: and https:, ftp: and ftp:, https: and http:, etc.
Warning: THIS REEKS OF BEING KLUDGY.  Although the redirection is implemented
in RequestRedirect() in REQUEST.C module RequestRedirect() function it is
described in greater detail here because it is wholly dependent on proxying
functionality.

Note that there are many combinations of mappings and services that could be
used to provide gatewaying between protocols.  What follows here are the
special case and some suggestions.  There are many other ways to organise these
capabilities.

Note that the proxy services must support the type of proxy request being
demanded of it (i.e. http:, https: and/or ftp:).


1. Simple in-line URL ("one-shot" proxy)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
This is when a request to a proxy service contains, *not* a proxy format

  GET http://the.host.name:port/path HTTP/1.0

but a standard format request, with the URL containing another "full" URL,

  GET /http://the.host.name:port/path HTTP/1.0

because the 'user' entered

  http://the.proxy.service:8080/http://the.host.name:port/path

into the browser location field.  This known with WASD as a "one-shot" proxy
because the server will retrieve it but the anything other than a self-relative
link within any HTML document will not be correct.

The configuration requirements for this type of proxy are fairly simple.

  [[the.proxy.service:port]]
  pass /http://*  http://*
  pass /https://* https://*
  pass /ftp://*   ftp://*


2. Wildcard DNS (CNAME) record
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
This relies on being able to manipulate host records 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".

Using this approach a complete proxy may be implemented, where returned HTML
documents contain links that are always correct with reference to the host used
to request them.  For a wildcard DNS (CNAME) record the browser user may enter
any host name appended by the proxy service host name and port and have the
request proxied to the host name.  Entering the following URL into the browser
location field,

  http://the.host.name.the.proxy.service:8080/path

would result in a standard HTTP proxy request for "/path" being made to
"the.host.name:80".  The URL,

  https://the.host.name.the.proxy.service:8080/path

in an SSL proxy request.  Note that normally the well-known port would be used
to connect to (80 for http: and 443 for https:).  If the final,
period-separated component of the wildcard host name is all digits it is
interpreted as a specific port to connect to.  The example

  http://the.host.name.8001.the.proxy.service:8080/path

would connect to "the.host.name:8001", and

  https://the.host.name.8443.the.proxy.service:8080/path

to "the.host.name:8443".

A further special case may be developed where part of that host name may be
used to indicate the proxy gateway desired.

The REDIRECT mapping rule allows for the following configuration being
specially processed.  The following example shows a complex but powerful
arrangement where the host name used provides an indication of the type of
gatewaying required to be performed.

  [[the.proxy.service:port]]
  if (host:*.ftp.the.proxy.service:*)
     redirect * /ftp://ftp.the.proxy.service/*?
  elif (host:*.https.the.proxy.service:*)
     redirect * /https://https.the.proxy.service/*?
  elif (host:*.http.the.proxy.service:*)
     redirect * /http://http.the.proxy.service/*?
  elif (host:*.the.proxy.service:*)
     # fallback to HTTP if no other specified
     redirect * /http://the.proxy.service/*?
  else
     pass https://* https://*
     pass http://* http://*
     pass ftp://* ftp://*
  endif

In this case a user may specify to connect via HTTP but gateway to an
HTTP-over-SSL service using a request such as

  http://the.host.name.https.the.proxy.service:8080/path

where a proxy request will effectively be made to

  https://the.host.name:443/path

All digit components may still be used to indicate specific ports to be
connected to, as in this example

  http://the.host.name.7443.https.the.proxy.service:8080/path

where the proxy request would effectively be made to

  https://the.host.name:7443/path

Notice that the example configuration allows for gatewayed HTTP, HTTP-over-SSL
and FTP based on the original host name used for the request and the consequent
contents of the "Host:" request field (which is adjusted to reflect the changed
host contents during the gatewaying).  Note also that is a script author is
going to use the mechansim for 'protocol conversion' then the script needs to
provide an appropriate "Host:" request header field.


CLIENT TO ORIGIN SERVER AFFINITY
--------------------------------
Courtesy Jean-Pierre Petit (jpp@esme.fr) 

High performance/highly available proxy server configurations require more than
one instance configured and running.  Whether this is done by running multiple
instances on the same host or one instance on multiple hosts, it leads to
situations where successive requests will be processed by different instances.
As those instances don't share a common name to IP address cache, they will
eventually use different IP addresses when trying to connect to an origin
server running on multiple hosts.

This may results in the following, user visible, issues:

  *  multiple requests for authentication (one from each origin host)

  *  loss of icons, images, javascripts, CSS because requests for these files,
although they return a 401 status, will not trigger a browser authentication
dialog

  *  loss of context and performance issues where scripts/environments need to
be started on a new host (php, python, webware,...)

For these reasons, the proxy server will make every effort to relay successive
requests from a given client to the same origin host as long as this one is
available (built-in failover capability will ultimately trigger the choice of a
new host). This is known as client to origin affinity or proxy affinity
capability.

After the first successful connection to an origin host, the proxy server will
send a cookie indicating the IP address used to the client browser. Upon
subsequent requests, this cookie will be used to select the same host. The
cookie is named WasdProxyAffinity_the.origin.server and his value is simply the
IP address. This behaviour can be WATCHed by checking the "Proxy Processing"
box.


WEBSOCKET PROXY
---------------
This is a second guess because the RFC specifies that CONNECT should be used to
establish a connection to the WebSocket host.  However the WASD proxy facility
does recognise a WebSocket upgrade request in a standard proxied GET and will
recognise an upgrade response from the proxied server and set up a WASD tunnel
between client and proxied server.


VERSION HISTORY
---------------
14-AUG-2020  MGD  bugfix; ProxyEnd() fix NetIoEnd() fix
20-JUL-2020  MGD  bugfix; ProxyEnd() free ioptr using NetIoEnd()
19-JAN-2020  MGD  more proxy persistent connection (per JPP)
23-APR-2018  MGD  ProxyRequestRebuild() proxy-authorization opaque:
12-DEC-2017  MGD  ProxyRequestRebuild() refine rebuild memory allocation
18-MAR-2017  MGD  ProxyRequestRebuild() replace the "Host:" header
07-MAR-2017  MGD  ProxyRequestRebuild() implement proxy=header=<name>[=<string>]
15-APR-2016  JPP  bugfix; ProxyRequestBegin() task connection persistence
10-JAN-2016  MGD  rework request processing due to dictionary
11-AUG-2015  MGD  restructure of network I/O abstractions
19-JAN-2015  JPP  ProxyResponseRebuild() and ProxyRequestRebuild() provide
                    timeout=n parameter with Keep-Alive: header field (some
                    origin servers hang when no parameters supplied)
06-NOV-2014  MGD  ProxyInit() unescaped "!" in FaoToStdout() (thanks Tony)
09-JUN-2014  MGD  accounting for network blocks
18-NOV-2013  JMB  bugfix; no $QIO buffer should exceed 65535!
20-APR-2013  MGD  bugfix; ProxyResponseRebuild() call ProxyRebuildLocation()
                    can return a pointer to the original location!
18-MAR-2013  MGD  ProxyResponseRebuild() additional header length bumped
                    from an ambit 256 to an ambit 1024 (Uni Malaga :-)
08-JUN-2012  JPP  set ProxyReadBufferSize to 64k
05-FEB-2012  MGD  proxy WebSocket upgrade requests as raw tunnels
31-AUG-2010  MGD  ProxyRequestParse() allow for IPv6 addressed hosts 
                    e.g. http://[fe80::200:f8ff:fe75:c062%eth0]:6680/
24-AUG-2010  MGD  ProxyResponseRebuild() generate "Proxy-Authorization:"
                    header for [ServiceProxyChainCred] and proxy=chain=cred=
20-JAN-2010  JPP  ProxyResponseRebuild() "accept" && !"accept-encoding"
20-SEP-2008  MGD  bugfix; ProxyResponseRebuild() proxy->client compression
                    chunk only for HTTP/1.1 responses and connection
                    persistence header fields reflect non-chunked GZIP stream
23-OCT-2007  MGD  ProxyRequestRebuild() allow "Proxy-Authorization:" header
                    only if configured for REMOTE proxy authentication
02-JUN-2007  MGD  WebDAV requirements
04-MAY-2007  MGD  ProxyRequestBegin() restrict HTTP methods for FTP scheme
                  ProxyResponseRebuild() make request HTTP version a
                    consideration before chunking proxy->client (with JPP)
10-JAN-2007  MGD  bugfix; ProxyRequestRebuild() proxy verify
                    "Authorization:" request header field carriage-control
21-APR-2006  JPP  add support for sending proxy generated cookies in 
                    ProxyResponseRebuild() (to support proxy affinity)
12-APR-2006  MGD  ProxyEnd() minor rework
10-SEP-2005  JPP  implement proxy=reverse=[no]auth
14-JUL-2005  MGD  detect and absorb a "Public:" response header field
                    (present in RFC2068 but obsoleted in RFC2116)
03-MAY-2005  MGD  "Cache-Control: max-age=0" applied only to proxy cache
16-MAR-2005  JPP  allow client-side GZIPing of proxied responses
20-JAN-2005  MGD  bugfix; ProxyReadResponseAst() if required, chunking needs
                    to be performed after header as well as body processing
05-JAN-2005  MGD  ProxyRequestBegin() remove explicit disable of
                    POST & PUT connection persistence
18-NOV-2004  MGD  ensure (persistent) HEAD responses do not wait for a body,
                    allow for a server sending more body than in content-length
14-NOV-2004  MGD  remove suppression of "Accept-Encoding:" request header,
                    detect "Content-Encoding:" responses (to suppress caching)
26-OCT-2004  MGD  bugfix; ProxyEnd() check for outstanding client SSL I/O
30-AUG-2004  MGD  increased HTTP/1.1 compliance,
                  now supports independent client->proxy and proxy-origin
                    connection persistence,
                  significant revision to cache file management
10-AUG-2004  MGD  some functionality moved to ProxyTunnel.c
30-APR-2004  MGD  significant changes to eliminate RMS from cache file read
                  by using ACP/QIOs (saves a few cycles and a little latency,
                  continue to use the RMS abstraction for handling the greater
                  complexity of file creation and population).
10-APR-2004  MGD  significant modifications to support IPv6,
                  use generic name-to-address resolution function
20-NOV-2003  MGD  reverse proxy 302 "Location:" rewrite,
                  reverse proxy authorization verification
28-OCT-2003  MGD  bugfix; chained proxy CONNECT processing
                  bugfix; keep track of outstanding body reads
19-SEP-2003  MGD  bugfix; ProxyRequestRebuild() rebuild buffer space
21-AUG-2003  MGD  "Range:" header field
25-MAY-2003  MGD  improve efficiency of ProxyRequestRebuild(),
                  un-recognised/known request fields,
                  remove explicit setting 'rqBody.DataStatus = SS$_ENDOFFILE'
02-APR-2003  MGD  "X-Forwarded-For:" header field,
                  modify to "Forwarded: by" processing
15-MAY-2002  MGD  proxy gateway statistics
30-APR-2002  MGD  "Cache-Control:" field for Mozilla compatibility
17-MAR-2002  MGD  ParseRequest() allow for incomplete FTP URLs
02-FEB-2002  MGD  rework for request body processing changes
22-JAN-2002  MGD  ProxyNetHostConnectAst() invalidate host cache entry
                  if connection fails (jpp@esme.fr),
                  ProxyRequestParse() ignore leading '/' allowing "one-shot"
                  proxying using a standard request
04-JAN-2002  MGD  proxy HTTP-to-SSL gateway
14-OCT-2001  MGD  outgoing sockets may be bound to a specific IP address
04-AUG-2001  MGD  support module WATCHing
27-APR-2001  MGD  requirement for ProxyResolveHostCacheTimer() has been
                  eliminated with HttpTick() and 'HttpdTickSecond'
03-SEP-2000  MGD  bugfix; ProxyResolveHostLookup() can be called multiple
                  during host name resolution - only allocate channel once!!
                  bugfix; ProxyResolveHostLookup() hash collision list
04-JUL-2000  MGD  bugfix; ProxyEnd() for CONNECT (proxy SSL)
11-JUN-2000  MGD  refine numeric host detection,
                  ProxyRequestRebuild() now dynamically allocates a rebuild
                  buffer based on 'NetReadBufferSize'
01-JUN-2000  MGD  bugfix; use 'rqHeader.RequestUriPtr' as '->RequestUriPtr'
                  (non-URL-decoded path plus query string)
29-APR-2000  MGD  proxy authorization
04-MAR-2000  MGD  use FaolToNet(), et.al.
04-DEC-1999  MGD  default host name cache purge now 24 hours
11-NOV-1999  MGD  provide "ETag:" propagation
25-OCT-1999  MGD  remove NETLIB support
10-OCT-1999  MGD  just sys$dassn() the socket
26-JUN-1999  MGD  bugfix; ProxyEnd() call in ProxyReadResponseAst(),
                  bugfix; header pointer in ProxyHttp09ResponseHeader(),
                  always initialize cache parameters (even if not enabled),
                  revised request run down ProxyShutdownSocket()
19-AUG-1998  MGD  initial development (recommenced DEC 1998)
*/
/*****************************************************************************/

#ifdef WASD_VMS_V7
#undef _VMS__V6__SOURCE
#define _VMS__V6__SOURCE
#undef __VMS_VER
#define __VMS_VER 70000000
#undef __CRTL_VER
#define __CRTL_VER 70000000
#endif

/* standard C header files */
#include <ctype.h>
#include <errno.h>
#include <stdio.h>
#include <string.h>

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

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

#define WASD_MODULE "PROXY"

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

int ProxyReadBufferSize = 65535;

BOOL ProxyServingEnabled,
     ProxyUnknownRequestFields;

int  ProxyConnectPersistMax,
     ProxyConnectPersistSeconds,
     ProxyConnectTimeoutSeconds,
     ProxyForwardedBy,
     ProxyHostLookupRetryCount,
     ProxyXForwardedFor;

PROXY_ACCOUNTING_STRUCT  *ProxyAccountingPtr;

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

extern int  ToLowerCase[],
            ToUpperCase[];

extern unsigned long  HttpdTime64[];

extern BOOL GzipResponse;

extern char  ErrorSanityCheck[],
             ServerHostPort[],
             SoftwareID[];

extern ACCOUNTING_STRUCT  *AccountingPtr;
extern CONFIG_STRUCT  Config;
extern IPADDRESS  TcpIpEmptyAddress;
extern MSG_STRUCT  Msgs;
extern LIST_HEAD  ServiceList;
extern WATCH_STRUCT  Watch;

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

ProxyInit ()

{
   BOOL  CacheEnabled;
   char  *cptr;
   SERVICE_STRUCT  *svptr, *tsvptr;

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

   if (WATCH_MODULE(WATCH_MOD_PROXY))
      WatchThis (WATCHALL, WATCH_MOD_PROXY, "ProxyInit()");
 
   ProxyForwardedBy = ProxyXForwardedFor = 0;
   ProxyServingEnabled = CacheEnabled = false;

   /* copy the configuration parameters, ensuring they're reasonable */
   ProxyHostLookupRetryCount = Config.cfProxy.HostLookupRetryCount;
   if (!ProxyHostLookupRetryCount)
      ProxyHostLookupRetryCount = Config.cfMisc.DnsLookupRetryCount;
   if (!ProxyHostLookupRetryCount)
      ProxyHostLookupRetryCount = TCPIP_LOOKUP_RETRY_COUNT;
   if (ProxyHostLookupRetryCount < 0 || ProxyHostLookupRetryCount > 100)
      ProxyHostLookupRetryCount = 0;

   /* just some information about the proxy services */
   LIST_ITERATE (svptr, &ServiceList)
   {
      if (!(svptr->ProxyService || svptr->ConnectService)) continue;

      if (svptr->ProxyChainHostName[0])
         FaoToStdout ("%HTTPD-I-PROXY, !AZ chains to !AZ\n",
                      svptr->ServerHostPort, svptr->ProxyChainHostPort);

      if (svptr->ProxyService)
      {
         FaoToStdout ("%HTTPD-I-PROXY, HTTP service !AZ",
                      svptr->ServerHostPort);
         if (svptr->ProxyAuthRequired)
            fprintf (stdout, " (auth)");
         if (svptr->ProxyFileCacheEnabled)
         {
            CacheEnabled = true;
            fprintf (stdout, " (cached)");
         }
         if (svptr->ConnectService)
            fprintf (stdout, " (connect)");
         fputs ("\n", stdout);
      }
      else
      if (svptr->ConnectService)
      {
         FaoToStdout ("%HTTPD-I-PROXY, connect service !AZ",
                      svptr->ServerHostPort);
         if (svptr->ProxyAuthRequired)
            fprintf (stdout, " (auth)");
         fputs ("\n", stdout);
      }
   }

   if (!Config.cfProxy.CacheEnabled) CacheEnabled = false;

   if (ProxyServiceCount())
   {
      /* initialize the proxy file cache (at least parameters) */
      ProxyCacheInit (CacheEnabled);
      if (Config.cfProxy.ServingEnabled)
      {
         ProxyServingEnabled = true;
         FaoToStdout ("%HTTPD-I-PROXY, processing enabled\n");
      }
      else
         FaoToStdout ("%HTTPD-W-PROXY, \
disabled in configuration, services not enabled!!\n");
   }

   ProxyForwardedBy = Config.cfProxy.ForwardedBy;
   ProxyXForwardedFor = Config.cfProxy.XForwardedFor;
   ProxyUnknownRequestFields = Config.cfProxy.UnknownRequestFields;
   ProxyConnectPersistMax = Config.cfProxy.ConnectPersistMax;
   ProxyConnectPersistSeconds = Config.cfProxy.ConnectPersistSeconds;
   ProxyConnectTimeoutSeconds = Config.cfProxy.ConnectTimeoutSeconds;

   InstanceMutexLock (INSTANCE_MUTEX_HTTPD);
   ProxyAccountingPtr->ServingEnabled = ProxyServingEnabled;
   ProxyAccountingPtr->TunnelCurrent = 0;
   InstanceMutexUnLock (INSTANCE_MUTEX_HTTPD);

   ProxyNetInit ();

   ProxyVerifyInit ();
}

/*****************************************************************************/
/*
Return the number of proxy-capable services in the global list.
*/

int ProxyServiceCount ()

{
   int  count;
   SERVICE_STRUCT  *svptr;

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

   count = 0;
   LIST_ITERATE (svptr, &ServiceList)
      if (svptr->ProxyService || svptr->ConnectService) count++;
   return (count);
}

/****************************************************************************/
/*
************
*** NOTE ***  This function takes a pointer to a request!!!
************

*/

ProxyRequestBegin (REQUEST_STRUCT *rqptr)

{
   int  status;
   char  *cptr, *sptr, *zptr;
   PROXY_TASK  *tkptr;
   DICT_ENTRY_STRUCT  *denptr;

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

   if (WATCHMOD (rqptr, WATCH_MOD_PROXY))
      WatchThis (WATCHITM(rqptr), WATCH_MOD_PROXY, "ProxyRequestBegin()");

   if (!ProxyServingEnabled)
   {
      rqptr->rqResponse.HttpStatus = 503;
      ErrorGeneral (rqptr, MsgFor(rqptr,MSG_PROXY_DISABLED), FI_LI);
      RequestEnd (rqptr);
      return;
   }

   if (HTTP2_REQUEST(rqptr))
   {
      rqptr->rqResponse.HttpStatus = 400;
      ErrorGeneral (rqptr, MsgFor(rqptr,MSG_PROXY_NONE_CONFIGURED), FI_LI);
      RequestEnd (rqptr);
      return;
   }

   if (rqptr->rqHeader.Method == HTTP_METHOD_CONNECT ||
       rqptr->rqHeader.Method == HTTP_METHOD_SHARE_SSH)
   {
      if (!rqptr->ServicePtr->ProxyTunnel)
      {
         rqptr->rqResponse.HttpStatus = 403;
         ErrorGeneral (rqptr, MsgFor(rqptr,MSG_PROXY_NOT_CONNECT), FI_LI);
         RequestEnd (rqptr);
         return;
      }
   }
   else
   {
      if (!rqptr->ServicePtr->ProxyService)
      {
         rqptr->rqResponse.HttpStatus = 403;
         ErrorGeneral (rqptr, MsgFor(rqptr,MSG_PROXY_NOT_SERVICE), FI_LI);
         RequestEnd (rqptr);
         return;
      }
   }

   if (!rqptr->AccountingDone++)
      InstanceGblSecIncrLong (&AccountingPtr->DoProxyCount);

   if (denptr = DictLookup (rqptr->rqDictPtr, DICT_TYPE_REQUEST,
                            "proxy-connection", 16))
   {
      cptr = DICT_GET_VALUE(denptr);
      while (*cptr)
      {
         while (*cptr && ISLWS(*cptr)) cptr++; 
         if (strsame (cptr, "close", 5))
         {
            rqptr->rqHeader.ProxyConnectionClose = true;
            cptr += 5;
         }
         else
         if (strsame (cptr, "keep-alive", 10))
         {
            rqptr->rqHeader.ProxyConnectionKeepAlive = true;
            cptr += 10;
         }
         while (*cptr && !ISLWS(*cptr) && *cptr != ',') cptr++; 
         while (*cptr && (ISLWS(*cptr) || *cptr == ',')) cptr++; 
      }
      if (WATCHMOD (rqptr, WATCH_MOD_REQUEST))
         WatchDataFormatted ("close:!&B keep-alive:!&B\n",
                             rqptr->rqHeader.ProxyConnectionClose,
                             rqptr->rqHeader.ProxyConnectionKeepAlive);
   }

   /* set up the task structure (only ever one per request!) */
   rqptr->ProxyTaskPtr = tkptr = VmGetHeap (rqptr, sizeof(PROXY_TASK));

   /* populate fundamental task fields */
   tkptr->RequestPtr = rqptr;
   tkptr->ServicePtr = rqptr->ServicePtr;
   tkptr->WatchItem = rqptr->WatchItem;

   tkptr->NetIoPtr = NetIoProxyBegin();
   tkptr->NetIoPtr->WatchItem = tkptr->WatchItem;
   NetIoQioMaxSeg (tkptr->NetIoPtr);

   /* copy the request method number and HTTP version number */
   tkptr->HttpMethod = rqptr->rqHeader.Method;
   tkptr->RequestHttpVersion = rqptr->rqHeader.HttpVersion;

   tkptr->PersistentRequest = true;
   if (rqptr->rqHeader.Method != HTTP_METHOD_GET &&
       rqptr->rqHeader.Method != HTTP_METHOD_HEAD &&
       rqptr->rqHeader.Method != HTTP_METHOD_POST &&
       rqptr->rqHeader.Method != HTTP_METHOD_PUT)
      rqptr->PersistentRequest = tkptr->PersistentRequest = false;
   else
   if (rqptr->PersistentRequest ||
       (rqptr->rqHeader.ProxyConnectionKeepAlive &&
        !rqptr->rqHeader.ProxyConnectionClose))
      tkptr->PersistentRequest = true;
   else
      tkptr->PersistentRequest = false;

   /* if there is a specific proxy IP address to bind to */
   if (IPADDRESS_IS_SET (&rqptr->rqPathSet.ProxyBindIpAddress))
      IPADDRESS_COPY (&tkptr->BindIpAddress,
                      &rqptr->rqPathSet.ProxyBindIpAddress)

   if (IPADDRESS_IS_SET (&rqptr->rqPathSet.ProxyChainIpAddress))
   {
      /* a chain has been set by mapping rule, overrides any service */
      IPADDRESS_COPY (&tkptr->ChainIpAddress,
                      &rqptr->rqPathSet.ProxyChainIpAddress)
      tkptr->ChainIpPort = rqptr->rqPathSet.ProxyChainPort;
      tkptr->ChainHostPortPtr = tkptr->ChainHostPort;
      /* a local copy is needed because of the volatility of request heap */
      zptr = (sptr = tkptr->ChainHostPort) + sizeof(tkptr->ChainHostPort)-1;
      for (cptr = rqptr->rqPathSet.ProxyChainHostPortPtr;
           *cptr && sptr < zptr;
           *sptr++ = *cptr++);
      *sptr = '\0';
   }
   else
   if (IPADDRESS_IS_SET (&tkptr->ServicePtr->ProxyChainIpAddress))
   {
      /* chaining to the next proxy server in a cascade */
      IPADDRESS_COPY (&tkptr->ChainIpAddress,
                      &tkptr->ServicePtr->ProxyChainIpAddress)
      tkptr->ChainIpPort = tkptr->ServicePtr->ProxyChainPort;
      tkptr->ChainHostPortPtr = tkptr->ServicePtr->ProxyChainHostPort;
   }

   /* allocate a buffer used for creating the MD5 digest, network I/O, etc. */
   if (!tkptr->ResponseBufferPtr)
   {
      tkptr->ResponseBufferPtr = tkptr->ResponseBufferCurrentPtr =
         VmGetHeap (rqptr, ProxyReadBufferSize);
      tkptr->ResponseBufferSize = tkptr->ResponseBufferRemaining =
         ProxyReadBufferSize;
   }

   /* set the lookup retry count to default if necessary */
   if (!tkptr->ProxyLookupRetryCount)
      tkptr->ProxyLookupRetryCount = ProxyHostLookupRetryCount;

   /* a little accounting */
   switch (tkptr->HttpMethod)
   {
      case HTTP_METHOD_CONNECT :
         InstanceGblSecIncrLong (&ProxyAccountingPtr->MethodConnectCount);
         break;
      case HTTP_METHOD_DELETE :
         InstanceGblSecIncrLong (&ProxyAccountingPtr->MethodDeleteCount);
         break;
      case HTTP_METHOD_EXTENSION :
         InstanceGblSecIncrLong (&ProxyAccountingPtr->MethodExtensionCount);
         break;
      case HTTP_METHOD_GET :
         InstanceGblSecIncrLong (&ProxyAccountingPtr->MethodGetCount);
         break;
      case HTTP_METHOD_HEAD :
         InstanceGblSecIncrLong (&ProxyAccountingPtr->MethodHeadCount);
         break;
      case HTTP_METHOD_OPTIONS :
         InstanceGblSecIncrLong (&ProxyAccountingPtr->MethodOptionsCount);
         break;
      case HTTP_METHOD_POST :
         InstanceGblSecIncrLong (&ProxyAccountingPtr->MethodPostCount);
         break;
      case HTTP_METHOD_PUT :
         InstanceGblSecIncrLong (&ProxyAccountingPtr->MethodPutCount);
         break;
      case HTTP_METHOD_TRACE :
         InstanceGblSecIncrLong (&ProxyAccountingPtr->MethodTraceCount);
         break;
      case HTTP_METHOD_WEBDAV_COPY:
         InstanceGblSecIncrLong (&ProxyAccountingPtr->MethodWebDavCopyCount);
         break;
      case HTTP_METHOD_WEBDAV_LOCK :
         InstanceGblSecIncrLong (&ProxyAccountingPtr->MethodWebDavLockCount);
         break;
      case HTTP_METHOD_WEBDAV_MKCOL :
         InstanceGblSecIncrLong (&ProxyAccountingPtr->MethodWebDavMkColCount);
         break;
      case HTTP_METHOD_WEBDAV_MOVE :
         InstanceGblSecIncrLong (&ProxyAccountingPtr->MethodWebDavMoveCount);
         break;
      case HTTP_METHOD_WEBDAV_PROPFIND :
         InstanceGblSecIncrLong (&ProxyAccountingPtr->MethodWebDavPropFindCount);
         break;
      case HTTP_METHOD_WEBDAV_PROPPATCH :
         InstanceGblSecIncrLong (&ProxyAccountingPtr->MethodWebDavPropPatchCount);
         break;
      case HTTP_METHOD_WEBDAV_UNLOCK :
         InstanceGblSecIncrLong (&ProxyAccountingPtr->MethodWebDavUnLockCount);
         break;
      case HTTP_METHOD_SHARE_SSH :
         InstanceGblSecIncrLong (&ProxyAccountingPtr->MethodSshCount);
         break;
      default :
         ErrorExitVmsStatus (SS$_BUGCHECK, ErrorSanityCheck, FI_LI);
   }

   if (rqptr->rqHeader.Method == HTTP_METHOD_CONNECT ||
       rqptr->rqHeader.Method == HTTP_METHOD_SHARE_SSH)
   {
      /**************/
      /* tunnelling */
      /**************/

      /* these definitely do not persist! */
      rqptr->PersistentRequest = false;

      if (tkptr->ProxyTunnel == PROXY_TUNNEL_HTTPS)
      {
         if (!tkptr->ServicePtr->SSLclientEnabled)
         {
            if (WATCHING (rqptr, WATCH_PROXY))
               WatchThis (WATCHITM(rqptr), WATCH_PROXY,
                          "CLIENT SSL not configured");
            rqptr->rqResponse.HttpStatus = 503;
            ErrorGeneral (rqptr, MsgFor(rqptr,MSG_PROXY_GATEWAY), FI_LI);
            ProxyEnd (tkptr);
            return;
         }
      }

      if (VMSnok (status = ProxyTunnelRequestParse (rqptr)))
      {
         RequestEnd (rqptr);
         return;
      }

      ProxyNetResolveHost (tkptr);
      return;
   }

   /***************************/
   /* GET, POST, etc. methods */
   /***************************/

   /* get the method, host (and port), path and query string */
   if (VMSnok (status = ProxyRequestParse (rqptr)))
   {
      RequestEnd (rqptr);
      return;
   }

   if (WATCHING (tkptr, WATCH_PROXY))
      WatchThis (WATCHITM(rqptr), WATCH_PROXY,
                 "!AZ !AZ!AZ", tkptr->RequestHttpMethodNamePtr,
                 tkptr->RequestHostPort, tkptr->RequestUriPtr);

   if (tkptr->RequestScheme == PROXY_SCHEME_FTP)
   {
      /*************/
      /* proxy FTP */
      /*************/

      /* ensure only meaningful methods are used against an FTP service */
      switch (tkptr->HttpMethod)
      {
         case HTTP_METHOD_GET :
         case HTTP_METHOD_HEAD :
         case HTTP_METHOD_POST :
         case HTTP_METHOD_PUT :
            break;
         default:
            rqptr->rqResponse.HttpStatus = 405;
            ErrorGeneral (rqptr, MsgFor(rqptr,MSG_HTTP_405), FI_LI);
            RequestEnd (rqptr);
            return;
      }

      /* get (any requested) authentication before we do too much else */
      if (*(cptr = rqptr->rqHeader.QueryStringPtr))
      {
         if (ProxyFtpInQueryString (cptr, "login"))
         {
            if (rqptr->rqHeader.AuthorizationPtr)
            {
               /* our authorization source is "local" not proxy */
               rqptr->rqAuth.RequestAuthorizationPtr =
                  rqptr->rqHeader.AuthorizationPtr;
               BasicAuthorization (rqptr);
            }
            if (!rqptr->RemoteUser[0] ||
                !rqptr->RemoteUserPassword[0])
            {
               rqptr->rqResponse.HttpStatus = 401;
               rqptr->rqAuth.RealmDescrPtr =
                  MsgFor(rqptr,MSG_PROXY_FTP_SERVER);
               ErrorGeneral (rqptr,
                  MsgFor(rqptr,MSG_PROXY_FTP_USERNAME_PWD), FI_LI);
               RequestEnd (rqptr);
               return;
            }
         }
         if (ProxyFtpInQueryString (cptr, "upload"))
         {
            ProxyFtpStoreForm (rqptr);
            RequestEnd (rqptr);
            return;
         }
      }
   }
   else
   if (tkptr->RequestScheme == PROXY_SCHEME_HTTP)
   {
      /**************/
      /* proxy HTTP */
      /**************/

      /* if caching is enabled for this service then see if it can be used */
      if (rqptr->ServicePtr->ProxyFileCacheEnabled)
         if (VMSok (ProxyCacheReadBegin (tkptr)))
            return;
   }

   ProxyNetResolveHost (tkptr);
}

/*****************************************************************************/
/*
Only called when a cache check has failed to be able to supply the request.
Merely call ProxyNetResolveHost() to begin an attempt to supply the request from
the remote server.
*/

ProxyReadCacheFailed (PROXY_TASK *tkptr)

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

   if (WATCH_MOD && (!tkptr->RequestPtr || tkptr->RequestPtr->WatchItem) &&
       WATCH_MODULE(WATCH_MOD_PROXY))
      WatchThis (WATCHITM(tkptr), WATCH_MOD_PROXY,
                 "ProxyReadCacheFailed()");

   ProxyNetResolveHost (tkptr);
} 

/****************************************************************************/
/*
************
*** NOTE ***  This function takes a pointer to a request!!!
************

Parse the various components from the proxy request.
*/

int ProxyRequestParse (REQUEST_STRUCT *rqptr)

{
#define paptr ProxyAccountingPtr

   int  Length;
   char  *cptr, *sptr, *zptr;
   PROXY_TASK  *tkptr;

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

   if (WATCHMOD (rqptr, WATCH_MOD_PROXY))
      WatchThis (WATCHITM(rqptr), WATCH_MOD_PROXY,
                 "ProxyRequestParse() !&Z", rqptr->MappedPathPtr);

   /* get a local pointer to the proxy task structure */
   tkptr = rqptr->ProxyTaskPtr;

   cptr = rqptr->rqHeader.RequestUriPtr;
   zptr = (sptr = tkptr->RequestSchemeName) +
          sizeof(tkptr->RequestSchemeName)-1;
   /* this allows a proxy service to be used non-proxy (if mapped) */
   if (*cptr == '/') cptr++;
   while (*cptr && *cptr != ':' && sptr < zptr) *sptr++ = *cptr++;
   if (*cptr && sptr < zptr) *sptr++ = *cptr++;
   if (sptr >= zptr)
   {
      InstanceGblSecIncrLong (&AccountingPtr->RequestErrorCount);
      rqptr->rqResponse.HttpStatus = 502;
      ErrorGeneral (rqptr, MsgFor(rqptr,MSG_REQUEST_FORMAT), FI_LI);
      return (STS$K_ERROR);
   }
   *sptr = '\0';
   if (WATCHMOD (rqptr, WATCH_MOD_PROXY))
      WatchThis (WATCHITM(rqptr), WATCH_MOD_PROXY,
                 "!&Z", tkptr->RequestSchemeName);

   if (tkptr->HttpMethod == HTTP_METHOD_CONNECT)
      tkptr->RequestScheme = PROXY_SCHEME_CONNECT;
   else
   if (strsame (tkptr->RequestSchemeName, "http:", -1))
   {
      tkptr->RequestScheme = PROXY_SCHEME_HTTP;
      /* [http=0,https=1][http=0,https=1,ftp=2] */
      if (rqptr->ServicePtr->RequestScheme == SCHEME_HTTP)
         InstanceGblSecIncrLong (&paptr->GatewaySchemeCount[0][0]);
      else
         InstanceGblSecIncrLong (&paptr->GatewaySchemeCount[1][0]);
   }
   else
   if (strsame (tkptr->RequestSchemeName, "ftp:", -1))
   {
      tkptr->RequestScheme = PROXY_SCHEME_FTP;
      /* [http=0,https=1][http=0,https=1,ftp=2] */
      if (rqptr->ServicePtr->RequestScheme == SCHEME_HTTP)
         InstanceGblSecIncrLong (&paptr->GatewaySchemeCount[0][2]);
      else
         InstanceGblSecIncrLong (&paptr->GatewaySchemeCount[1][2]);
   }
   else
   if (strsame (tkptr->RequestSchemeName, "https:", -1))
   {
      if (tkptr->ServicePtr->SSLclientEnabled)
      {
         /* proxy HTTP-to-SSL gateway "one-shot" request */
         tkptr->RequestScheme = PROXY_SCHEME_HTTPSSL;
         /* [http=0,https=1][http=0,https=1,ftp=2] */
         if (rqptr->ServicePtr->RequestScheme == SCHEME_HTTP)
            InstanceGblSecIncrLong (&paptr->GatewaySchemeCount[0][1]);
         else
            InstanceGblSecIncrLong (&paptr->GatewaySchemeCount[1][1]);
      }
      else
      {
         if (WATCHING (rqptr, WATCH_PROXY))
            WatchThis (WATCHITM(rqptr), WATCH_PROXY,
                       "CLIENT SSL not configured");
         rqptr->rqResponse.HttpStatus = 503;
         ErrorGeneral (rqptr, MsgFor(rqptr,MSG_PROXY_GATEWAY), FI_LI);
         return (STS$K_ERROR);
      }
   }
   else
      tkptr->RequestScheme = 0;

   if (!tkptr->RequestScheme)
   {
      rqptr->rqResponse.HttpStatus = 502;
      ErrorGeneral (rqptr, MsgFor(rqptr,MSG_PROXY_REQUEST_SCHEME), FI_LI);
      return (STS$K_ERROR);
   }

   if (*((USHORTPTR)cptr) != '//')
   {
      InstanceGblSecIncrLong (&AccountingPtr->RequestErrorCount);
      rqptr->rqResponse.HttpStatus = 502;
      ErrorGeneral (rqptr, MsgFor(rqptr,MSG_REQUEST_FORMAT), FI_LI);
      return (STS$K_ERROR);
   }
   cptr += 2;

   /*******************************/
   /* check for username:password */
   /*******************************/

   for (sptr = cptr;
        *sptr && *sptr != '@' && *sptr != '/' && *sptr != '?';
        sptr++);
   if (*sptr == '@')
   {
      /* looks like we found one */
      zptr = (sptr = tkptr->UrlUserName) + sizeof(tkptr->UrlUserName)-1;
      while (*cptr && *cptr != ':' && *cptr != '@' && 
             *cptr != '/' && *cptr != '?' && sptr < zptr)
         *sptr++ = *cptr++;
      *sptr = '\0';
      if (sptr >= zptr) cptr = "";
      if (*cptr == ':')
      {
         cptr++;
         zptr = (sptr = tkptr->UrlPassword) + sizeof(tkptr->UrlPassword)-1;
         while (*cptr && *cptr != '@' && *cptr != '/' &&
                *cptr != '?' && sptr < zptr)
            *sptr++ = *cptr++;
         *sptr = '\0';
         if (sptr >= zptr) cptr = "";
      }
      if (*cptr != '@')
      {
         InstanceGblSecIncrLong (&AccountingPtr->RequestErrorCount);
         rqptr->rqResponse.HttpStatus = 502;
         ErrorGeneral (rqptr, MsgFor(rqptr,MSG_REQUEST_FORMAT), FI_LI);
         return (STS$K_ERROR);
      }
      cptr++;
   }

   /*************************/
   /* get server host name  */
   /*************************/

   zptr = (sptr = tkptr->RequestHostName) + sizeof(tkptr->RequestHostName)-1;
   if (*cptr == '[')
   {
      /* square bracket delimited IPv6 address (de facto?) */
      cptr++;
      while (*cptr && *cptr != ']' && *cptr != '%' && sptr < zptr)
         *sptr++ = *cptr++;
      if (*cptr == '%')
      {
         /* e.g. http://[fe80::200:f8ff:fe75:c062%eth0]:6680 */
         while (*cptr && *cptr != ']') cptr++;
      }
      if (*cptr) cptr++;
   }
   else
      while (*cptr && *cptr != ':' && *cptr != '/' &&
             *cptr != '?' && sptr < zptr) *sptr++ = *cptr++;
   *sptr = '\0';
   if (sptr >= zptr) cptr = "";
   if (*cptr && *cptr != ':' && *cptr != '/' && *cptr != '?')
   {
      InstanceGblSecIncrLong (&AccountingPtr->RequestErrorCount);
      rqptr->rqResponse.HttpStatus = 502;
      ErrorGeneral (rqptr, MsgFor(rqptr,MSG_REQUEST_FORMAT), FI_LI);
      return (STS$K_ERROR);
   }
   if (WATCHMOD (rqptr, WATCH_MOD_PROXY))
      WatchThis (WATCHITM(rqptr), WATCH_MOD_PROXY,
                 "!&Z", tkptr->RequestHostName);

   /******************************/
   /* get (optional) server port */
   /******************************/

   if (*cptr == ':')
   {
      cptr++;
      if (isdigit(*cptr))
      {
         zptr = (sptr = tkptr->RequestPortString) +
                sizeof(tkptr->RequestPortString)-1;
         while (*cptr && isdigit(*cptr) && sptr < zptr) *sptr++ = *cptr++;
         if (sptr >= zptr)
         {
            InstanceGblSecIncrLong (&AccountingPtr->RequestErrorCount);
            rqptr->rqResponse.HttpStatus = 502;
            ErrorGeneral (rqptr, MsgFor(rqptr,MSG_REQUEST_FORMAT), FI_LI);
            return (STS$K_ERROR);
         }
         *sptr = '\0';
         tkptr->RequestPort = atol (tkptr->RequestPortString);
      }
   }

   if (!tkptr->RequestPort)
   {
      if (tkptr->RequestScheme == PROXY_SCHEME_HTTP)
      {
         tkptr->RequestPort = 80;
         SET4(tkptr->RequestPortString,'80\0\0');
      }
      else
      if (tkptr->RequestScheme == PROXY_SCHEME_FTP)
      {
         tkptr->RequestPort = 21;
         SET4(tkptr->RequestPortString,'21\0\0');
      }
      else
      if (tkptr->RequestScheme == PROXY_SCHEME_HTTPSSL)
      {
         tkptr->RequestPort = 443;
         SET4(tkptr->RequestPortString,'443\0');
      }
   }
   if (WATCHMOD (rqptr, WATCH_MOD_PROXY))
      WatchThis (WATCHITM(rqptr), WATCH_MOD_PROXY,
                 "!UL !&Z", tkptr->RequestPort, tkptr->RequestPortString);

   /***************************************/
   /* get request URI and if query string */
   /***************************************/

   if (*cptr == '/')
      tkptr->RequestUriPtr = cptr;
   else
   if (tkptr->RequestScheme == PROXY_SCHEME_FTP)
   {
      /* no trailing slash after host[:port], redirect to add one */
      sptr = ResponseLocation (rqptr, rqptr->rqHeader.RequestUriPtr,
                                      rqptr->rqHeader.RequestUriLength+1);
      sptr[rqptr->rqHeader.RequestUriLength] = '/';
      return (STS$K_ERROR);
   }
   else
      tkptr->RequestUriPtr = "/";

   if (rqptr->rqHeader.QueryStringPtr[0])
   {
      while (*cptr && *cptr != '?') cptr++;
      tkptr->RequestUriQueryStringPtr = cptr;
   }
   else
      tkptr->RequestUriQueryStringPtr = "";

   /*******************************/
   /* point to any request cookie */
   /*******************************/

   tkptr->RequestHttpCookiePtr = rqptr->rqHeader.CookiePtr;

   /***********************/
   /* composite name:port */
   /***********************/

   /* store host name/port as FIRST ITEM in cache data block */
   zptr = (sptr = tkptr->RequestHostPort) + sizeof(tkptr->RequestHostPort);
   for (cptr = tkptr->RequestHostName;
        *cptr && sptr < zptr;
        *sptr++ = *cptr++);
   if (sptr < zptr) *sptr++ = ':';
   for (cptr = tkptr->RequestPortString;
        *cptr && sptr < zptr;
        *sptr++ = *cptr++);
   if (sptr >= zptr)
   {
      rqptr->rqResponse.HttpStatus = 500;
      ErrorGeneralOverflow (rqptr, FI_LI);
      return (STS$K_ERROR);
   }
   *sptr = '\0';
   tkptr->RequestHostPortLength = sptr - tkptr->RequestHostPort;
   if (WATCHMOD (rqptr, WATCH_MOD_PROXY))
      WatchThis (WATCHITM(rqptr), WATCH_MOD_PROXY,
                 "!&Z", tkptr->RequestHostPort);

   tkptr->RequestHttpMethod = rqptr->rqHeader.Method;
   tkptr->RequestHttpMethodNamePtr = rqptr->rqHeader.MethodName;
   /* max-age=0 applies to intermediate caches but not origin servers */
   tkptr->RequestNotFromCache = rqptr->NotFromCache ||
                                rqptr->rqHeader.CacheControlMaxAgeZero;

   return (SS$_NORMAL);

#undef paptr
} 

/*****************************************************************************/
/*
If a proxied request is being supplied from cache then end that (basically
close the associated cache file).  If supplied via a network transaction and
the socket was created then shut and close the socket.  If concurrently writing
a request body to the proxied server then just return after closing the socket. 
The body write will receive an error and call this function to close the
request down.  Finally deallocate proxy memory and if an associated request
structure end that request.
*/

ProxyEnd (PROXY_TASK *tkptr)

{
#define paptr ProxyAccountingPtr

   int  status,
        StatusCodeGroup;
   NETIO_STRUCT  *ioptr;
   REQUEST_STRUCT  *rqptr;

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

   if (WATCHMOD (tkptr, WATCH_MOD_PROXY))
      WatchThis (WATCHITM(tkptr), WATCH_MOD_PROXY, "ProxyEnd()");
 
   ioptr = tkptr->NetIoPtr;
   rqptr = tkptr->RequestPtr;

   if (tkptr->ParseInUse ||
       tkptr->CacheFileQio.AcpChannel ||
       tkptr->CacheFileQio.QioChannel ||
       tkptr->LoadFab.fab$w_ifi)
   {
      /* finalize cache file processing */
      ProxyCacheEnd (tkptr);
      return;
   }

   if (ioptr->SesolaPtr &&
      !(tkptr->PersistentRequest &&
       tkptr->PersistentResponse &&
       NETIO_CONNECTED (tkptr->NetIoPtr)))
   {
      /* proxy Secure Sockets Layer request */
      SesolaNetClientShutdown (ioptr->SesolaPtr);
      return;
   }

   if (tkptr->ProxyTunnel)
   {
      if (NETIO_CONNECTED (rqptr->NetIoPtr)) NetCloseSocket (rqptr);
      if (NETIO_CONNECTED (tkptr->NetIoPtr)) ProxyNetCloseSocket (tkptr);
   }

   /* if outstanding I/O then wait until it concludes */
   if (NETIO_IN_PROGRESS (ioptr)) return;
   if (NETIO_IN_PROGRESS (rqptr->NetIoPtr)) return;

   /********************/
   /* proxy accounting */
   /********************/

   InstanceMutexLock (INSTANCE_MUTEX_HTTPD);

   StatusCodeGroup = tkptr->ResponseStatusCode / 100;
   if (StatusCodeGroup < 0 || StatusCodeGroup > 5) StatusCodeGroup = 0;
   if (tkptr->ProxyCacheReadBytes)
      paptr->CacheStatusCodeCount[StatusCodeGroup]++;
   else
      paptr->NetStatusCodeCount[StatusCodeGroup]++;

   /* update the stats using the underlying network I/O structure */
   ADD_QUAD_QUAD (ioptr->BytesTallyRx, paptr->BytesRawRx)
   ADD_QUAD_QUAD (ioptr->BlocksTallyRx, paptr->BlocksRawRx)
   if (tkptr->CannotCache)
      ADD_QUAD_QUAD (ioptr->BytesTallyRx, paptr->BytesCannotCacheRx);

   ADD_QUAD_QUAD (ioptr->BytesTallyTx, paptr->BytesRawTx)
   ADD_QUAD_QUAD (ioptr->BlocksTallyTx, paptr->BlocksRawTx)
   if (tkptr->CannotCache)
      ADD_QUAD_QUAD (ioptr->BytesTallyTx, paptr->BytesCannotCacheTx);

   if (tkptr->ProxyCacheReadBytes)
   {
      ADD_LONG_QUAD (tkptr->ProxyCacheReadBytes, paptr->BytesCacheTx);
      ADD_LONG_QUAD (rqptr->rqHeader.RequestHeaderLength, paptr->BytesCacheRx);
   }

   /* reset the intermediate accumulators used to update the stats */
   PUT_ZERO_QUAD (ioptr->BlocksTallyRx);
   PUT_ZERO_QUAD (ioptr->BlocksTallyTx);
   PUT_ZERO_QUAD (ioptr->BytesTallyRx);
   PUT_ZERO_QUAD (ioptr->BytesTallyTx);

   if (tkptr->ProxyTunnel)
      if (paptr->TunnelCurrent > 0)
         paptr->TunnelCurrent--;

   InstanceMutexUnLock (INSTANCE_MUTEX_HTTPD);

   /****************/
   /* task rundown */
   /****************/

   if (tkptr->VerifyRecordPtr) ProxyVerifyRecordReset (tkptr);

   if (tkptr->ResponseStatusCode)
      rqptr->rqResponse.HttpStatus = tkptr->ResponseStatusCode;

   if (tkptr->ProxyTunnel)
   {
      rqptr->ProxyTunnelRequest = true;

      if (WATCHING (tkptr, WATCH_PROXY))
      {
         char  *cptr;
         switch (tkptr->ProxyTunnel)
         {
            case PROXY_TUNNEL_CONNECT : cptr = "CONNECT"; break;
            case PROXY_TUNNEL_HTTP : cptr = "HTTP"; break;
            case PROXY_TUNNEL_HTTPS : cptr = "HTTPS"; break;
            case PROXY_TUNNEL_RAW : cptr = "RAW"; break;
            default : cptr = "?";
         }
         WatchThis (WATCHITM(tkptr), WATCH_PROXY,
                    "TUNNEL (!AZ) removed", cptr);
      }
   }

   /* use |tkptr->NetIoPtr| not |ioptr| */
   if (tkptr->PersistentRequest &&
       tkptr->PersistentResponse &&
       NETIO_CONNECTED (tkptr->NetIoPtr))
   {
      /* try to make the connection persist */
      ProxyNetConnectPersist (tkptr);
   }

   /* use |tkptr->NetIoPtr| not |ioptr| */
   if (NETIO_CONNECTED (tkptr->NetIoPtr))
   {
      /* still have the socket open (obviously couldn't persist) */
      if (ioptr->SesolaPtr)
      {
         SesolaNetClientShutdown (ioptr->SesolaPtr);
         return;
      }
      ProxyNetCloseSocket (tkptr);
      NetIoEnd (ioptr);
      tkptr->NetIoPtr = NULL;
   }
   else
   if (tkptr->ProxyTunnel)
   {
      NetIoEnd (ioptr);
      tkptr->NetIoPtr = NULL;
   }

   /* indicate this no longer has an associated proxy task */
   rqptr->ProxyTaskPtr = NULL;

   /* request heap memory will be freed by request rundown */
   RequestEnd (rqptr);

#undef paptr
} 

/****************************************************************************/
/*
Called from ProxyNetHostConnectAst() or SesolaNetClientConnect() to rebuild and
then write the request to the proxied server.
*/

ProxyWriteRequest (PROXY_TASK *tkptr)

{
   int  status;
   REQUEST_STRUCT *rqptr;

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

   if (WATCHMOD (tkptr, WATCH_MOD_PROXY))
      WatchThis (WATCHITM(tkptr), WATCH_MOD_PROXY, "ProxyWriteRequest()");

   rqptr = tkptr->RequestPtr;

   ProxyRequestRebuild (tkptr);

   if (WATCHING (tkptr, WATCH_PROXY_REQU_HDR))
   {
      WatchThis (WATCHITM(tkptr), WATCH_PROXY_REQU_HDR,
                 "REQUEST HEADER !UL bytes", tkptr->RebuiltRequestLength);
      WatchData (tkptr->RebuiltRequestPtr, tkptr->RebuiltRequestLength);
   }

   ProxyNetWrite (tkptr, &ProxyWriteRequestAst,
                  tkptr->RebuiltRequestPtr,
                  tkptr->RebuiltRequestLength);
}

/****************************************************************************/
/*
Called as an AST from ProxyWriteRequest().  The write of the request header to
the remote server has completed. If the is a body to the request (i.e. a POST
or PUT method) then call the function to BEGIN CONCURRENTLY writing that.  If
no body check the status of the proxy write. If not successful then report the
error and end the proxy processing.  If OK then queue a read from the remote
server to begin to get the response.
*/

ProxyWriteRequestAst (PROXY_TASK *tkptr)

{
   int  DataLength;
   char  *cptr, *sptr, *zptr,
         *DataPtr;
   REQUEST_STRUCT *rqptr;

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

   if (WATCHMOD (tkptr, WATCH_MOD_PROXY))
      WatchThis (WATCHITM(tkptr), WATCH_MOD_PROXY,
                 "ProxyWriteRequestAst() !&F !&S",
                 &ProxyWriteRequestAst, tkptr->NetIoPtr->WriteStatus);

   rqptr = tkptr->RequestPtr;

   if (VMSnok (tkptr->NetIoPtr->WriteStatus))
   {
      /***********************/
      /* write request error */
      /***********************/

      if (tkptr->NetIoPtr->Channel)
      {
         /* the socket has not been explicitly closed, so ... */
         tkptr->ResponseStatusCode = 502;
         if (rqptr)
         {
            /* a request associated with this proxy task */
            rqptr->rqResponse.HttpStatus = 502;
            rqptr->rqResponse.ErrorTextPtr = tkptr->RequestHostPort;
            ErrorVmsStatus (rqptr, tkptr->NetIoPtr->WriteStatus, FI_LI);
         }
      }

      /* toodleoo */
      ProxyEnd (tkptr);
      return;
   }

   /**********************************************************/
   /* concurrently send body as well as waiting for response */
   /**********************************************************/

   if (rqptr->rqHeader.ContentLength)
   {
      BodyReadBegin (rqptr, &ProxyWriteRequestBody, NULL);
      tkptr->QueuedBodyRead++;
   }

   /*************************************/
   /* begin (wait for) reading response */
   /*************************************/

   tkptr->ResponseHeaderPtr = tkptr->ResponseBufferCurrentPtr;
   tkptr->ResponseBodyLength = 0;
   tkptr->ResponseHeaderLength = 0;
   tkptr->ResponseConsecutiveNewLineCount = 0;

   /* responses can quite legitimately have a content-length of zero */
   tkptr->ResponseContentLength = -1;

   ProxyNetRead (tkptr, &ProxyReadResponseAst,
                 tkptr->ResponseBufferCurrentPtr,
                 tkptr->ResponseBufferRemaining);
}

/*****************************************************************************/
/*
A network read of the request body has completed and BodyReadAst() has called
this function as an AST.  Write it to the remote server with a completion AST
to ProxyWriteRequestBodyAst().
*/ 

ProxyWriteRequestBody (REQUEST_STRUCT *rqptr)

{
   int  status;
   PROXY_TASK  *tkptr;

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

   tkptr = rqptr->ProxyTaskPtr;

   if (WATCHMOD (rqptr, WATCH_MOD_PROXY))
      WatchThis (WATCHITM(rqptr), WATCH_MOD_PROXY,
         "ProxyWriteRequestBody() !&F !&S !&X !UL",
         &ProxyWriteRequestBody,
         rqptr->rqBody.DataStatus, rqptr->rqBody.DataPtr,
         rqptr->rqBody.DataCount);

   if (tkptr->QueuedBodyRead) tkptr->QueuedBodyRead--;

   if (rqptr->rqBody.DataStatus == SS$_ENDOFFILE)
   {
      /****************************/
      /* request body is finished */
      /****************************/

      /* just finish up this concurrent activity */
      return;
   }

   if (VMSnok (rqptr->rqBody.DataStatus))
   {
      ProxyEnd (tkptr);
      return;
   }

   ProxyNetWrite (tkptr, &ProxyWriteRequestBodyAst,
                  rqptr->rqBody.DataPtr,
                  rqptr->rqBody.DataCount);
}

/*****************************************************************************/
/*
A queued write to the remote server has completed.
Get more of the request body.
*/ 

ProxyWriteRequestBodyAst (PROXY_TASK *tkptr)

{
   int  status;
   REQUEST_STRUCT  *rqptr;

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

   if (WATCHMOD (tkptr, WATCH_MOD_PROXY))
      WatchThis (WATCHITM(tkptr), WATCH_MOD_PROXY,
                 "ProxyWriteRequestBodyAst() !&F !&S",
                 &ProxyWriteRequestBodyAst, tkptr->NetIoPtr->WriteStatus);

   rqptr = tkptr->RequestPtr;

   if (VMSnok (tkptr->NetIoPtr->WriteStatus))
   {
      /* write body error */
      if (tkptr->NetIoPtr->Channel)
      {
         /* only if the remote server has not responded do we report this */
         if (!tkptr->ResponseStatusCode)
         {
            /* the socket has not been deliberately closed */
            tkptr->ResponseStatusCode = 502;
            if (rqptr)
            {
               /* a request associated with this proxy task */
               rqptr->rqResponse.HttpStatus = 502;
               rqptr->rqResponse.ErrorTextPtr = tkptr->RequestHostPort;
               ErrorVmsStatus (rqptr, tkptr->NetIoPtr->WriteStatus, FI_LI);
            }
         }
      }

      /* finale */
      ProxyEnd (tkptr);
      return;
   }

   /* get more from the client */
   BodyRead (rqptr);
   tkptr->QueuedBodyRead++;
}

/****************************************************************************/
/*
Called as an AST when the read of a buffer from the proxied server completes.
It checks for, and analyzes, the response header.  When the response header
has been analyzed a decision is made as to whether the response is cachable.
If cacheable this function stops executing while the cache file is created.
When that is complete ProxyReadResponseCacheWrite() is called as an AST to
write the first buffer of data into the file.  If not cacheable then if
associated with a request the data is written to the client via the network.
*/

ProxyReadResponseAst (PROXY_TASK *tkptr)

{
   int  status;
   char  *cptr, *sptr, *zptr;
   REQUEST_STRUCT  *rqptr;

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

   if (WATCHMOD (tkptr, WATCH_MOD_PROXY))
      WatchThis (WATCHITM(tkptr), WATCH_MOD_PROXY,
                 "ProxyReadResponseAst() !&F !&X !UL", &ProxyReadResponseAst,
                 tkptr->NetIoPtr->ReadStatus, tkptr->NetIoPtr->ReadCount);

   rqptr = tkptr->RequestPtr;

   if (VMSnok (tkptr->NetIoPtr->ReadStatus))
   {
      /**************/
      /* read error */
      /**************/

      if (tkptr->NetIoPtr->Channel)
      {
         /* the socket has not been explicitlyly shut down */
         if (tkptr->ResponseBytes)
         {
            /* we got some bytes back from the remote server */
            if (tkptr->ResponseHttpVersion != HTTP_VERSION_0_9 &&
                !tkptr->ResponseHeaderLength)
            {
               /* header was not completely received */
               tkptr->ResponseStatusCode = 502;
               if (rqptr)
               {
                  /* request associated with task */
                  rqptr->rqResponse.HttpStatus = 502;
                  ErrorGeneral (rqptr, MsgFor(rqptr,MSG_PROXY_HEADER), FI_LI);
               }
            }
         }
         else
         {
            /* absolutely no bytes at all from the remote server */
            tkptr->ResponseStatusCode = 502;
            if (rqptr)
            {
               /* request associated with task */
               rqptr->rqResponse.HttpStatus = 502;
               rqptr->rqResponse.ErrorTextPtr = tkptr->RequestHostPort;
               switch (tkptr->NetIoPtr->ReadStatus)
               {
                  case PROXY_ERROR_HOST_DISCONNECTED :
                     /* disconnected by third party */
                     ErrorGeneral (rqptr,
                                   MsgFor(rqptr, MSG_PROXY_DISCON), FI_LI);
                     break;
                  default :
                     ErrorVmsStatus (rqptr, tkptr->NetIoPtr->ReadStatus, FI_LI);
               }
            }
         }
      }

      /* finis */
      ProxyEnd (tkptr);
      return;
   }

   tkptr->ResponseBytes += tkptr->NetIoPtr->ReadCount;

   if (tkptr->ResponseConsecutiveNewLineCount < 2)
   {
      tkptr->ResponseBufferCurrentPtr += tkptr->NetIoPtr->ReadCount;
      tkptr->ResponseBufferRemaining -= tkptr->NetIoPtr->ReadCount;

      /* careful of this loop - it's only here to help with 100 continues */
      while (tkptr->ResponseConsecutiveNewLineCount < 2)
      {
         /***************************/
         /* examine response header */
         /***************************/

         status = ProxyResponseHeader (tkptr);
         if (VMSnok (status))
         {
            ProxyEnd (tkptr);
            return;
         }

         /* if the full header has not yet been received */
         if (tkptr->ResponseConsecutiveNewLineCount < 2) break;

         if (tkptr->ResponseStatusCode < 100 ||
             tkptr->ResponseStatusCode > 599 ||
             tkptr->ResponseTransferEncodingUnsupported)
         {
            tkptr->ResponseStatusCode = 502;
            ErrorGeneral (rqptr, MsgFor(rqptr,MSG_PROXY_HEADER),
                          FI_LI);
            ProxyEnd (tkptr);
            return;
         }

         if (tkptr->ResponseHttpVersion == HTTP_VERSION_1_1 &&
             tkptr->ResponseStatusCode == 100 &&
             tkptr->ResponseConsecutiveNewLineCount >= 2)
         {
            /*******************************/
            /* special case - 100 continue */
            /*******************************/

            /* synchronous write, convenient and won't happen very often */
            NetWrite (tkptr->RequestPtr, 0,
                      tkptr->RebuiltResponsePtr,
                      tkptr->RebuiltResponseLength);

            /* reset the response header processing */
            ProxyResponseRebuild (tkptr, NULL, 0);

            /* start again, at the point where the 100 header finished */
            tkptr->ResponseHeaderPtr += tkptr->ResponseHeaderLength;
            tkptr->ResponseHeaderLength = 0;
            tkptr->ResponseConsecutiveNewLineCount = 0;

            /* if more still in the read buffer - use that while loop now! */
            if (tkptr->ResponseBufferCurrentPtr > tkptr->ResponseHeaderPtr)
               continue;
         }

         /* make sure we're out of that while loop */
         break;
      }

      if (tkptr->ResponseConsecutiveNewLineCount < 2)
      {
         /*******************************************/
         /* entire header has not yet been received */
         /*******************************************/

         ProxyNetRead (tkptr, &ProxyReadResponseAst,
                       tkptr->ResponseBufferCurrentPtr,
                       tkptr->ResponseBufferRemaining);
         return;
      }

      /****************************/
      /* header has been received */
      /****************************/

      rqptr->rqResponse.HttpStatus = tkptr->ResponseStatusCode;

      /* only the body portion goes to the client (header has been rebuilt) */
      tkptr->ResponseBufferNetPtr = tkptr->ResponseHeaderPtr +
                                    tkptr->ResponseHeaderLength;
      tkptr->ResponseBufferNetCount = tkptr->ResponseBufferCurrentPtr -
                                      tkptr->ResponseBufferNetPtr;

      /* everything in the buffer goes to the cache file */
      tkptr->ResponseBufferCachePtr = tkptr->ResponseBufferPtr;
      tkptr->ResponseBufferCacheCount = tkptr->ResponseBufferCurrentPtr -
                                        tkptr->ResponseBufferCachePtr;

      if (tkptr->ResponseHttpVersion == HTTP_VERSION_1_1 &&
          tkptr->ResponseStatusCode == 101 &&
          tkptr->ResponseUpgradeWebSocket &&
          rqptr->WebSocketRequest)
      {
         /***************************************/
         /* special case - 101 change protocols */
         /***************************************/

         if (WATCHING (rqptr, WATCH_PROXY))
            WatchThis (WATCHITM(rqptr), WATCH_PROXY, "UPGRADE WebSocket");

         /* the proxied WebSocket will be handled by a raw tunnel */
         tkptr->ProxyTunnel = PROXY_TUNNEL_RAW;

         rqptr->rqResponse.HeaderSent = true;

         NetWriteRaw (rqptr, &ProxyTunnelConnectResponseAst,
                      tkptr->ResponseHeaderPtr,
                      tkptr->ResponseHeaderLength);

         return;
      }

      if (WATCHING (tkptr, WATCH_PROXY_RESP_BDY))
      {
         if (tkptr->ResponseBufferNetCount)
         {
            WatchThis (WATCHITM(rqptr), WATCH_PROXY_RESP_BDY,
                       "RESPONSE BODY !UL bytes",
                       tkptr->ResponseBufferNetCount);
            WatchDataDump (tkptr->ResponseBufferNetPtr,
                           tkptr->ResponseBufferNetCount);
         }
      }

      if (rqptr->rqHeader.Method == HTTP_METHOD_HEAD)
      {
         /* no possibility of a body */
         tkptr->ResponseContentLength = tkptr->ResponseBufferNetCount = 0;
      }
      else
      {
         /* whatever is left in the buffer */
         tkptr->ResponseBodyLength = tkptr->ResponseBufferNetCount;
      }

      if (tkptr->ResponseTransferEncodingChunked)
      {
         status = ProxyResponseChunked (tkptr);
         if (VMSnok (status))
         {
            tkptr->ResponseStatusCode = 502;
            ErrorGeneral (rqptr, MsgFor(rqptr,MSG_PROXY_HEADER),
                          FI_LI);
            ProxyEnd (tkptr);
            return;
         }
      }

      if (tkptr->ServicePtr->ProxyFileCacheEnabled &&
          tkptr->ProxyCacheSuitable)
      {
         /***********************/
         /* begin proxy caching */
         /***********************/

         status = ProxyCacheLoadBegin (tkptr);
         if (VMSok (status)) return;
         /* error status means it was not a candidate for caching */
      }
   }
   else
   {
      /*******************************/
      /* just receiving the body now */
      /*******************************/

      tkptr->ResponseBodyLength += tkptr->NetIoPtr->ReadCount;

      /* both cache file and network contents are identical from here */
      tkptr->ResponseBufferCachePtr =
         tkptr->ResponseBufferNetPtr = tkptr->ResponseBufferPtr;
      tkptr->ResponseBufferCacheCount =
         tkptr->ResponseBufferNetCount = tkptr->NetIoPtr->ReadCount;

      if (WATCHING (tkptr, WATCH_PROXY_RESP_BDY))
      {
         WatchThis (WATCHITM(rqptr), WATCH_PROXY_RESP_BDY,
                    "RESPONSE BODY !UL bytes", tkptr->NetIoPtr->ReadCount);
         WatchDataDump (tkptr->ResponseBufferPtr, tkptr->NetIoPtr->ReadCount);

#if WATCH_MOD
         if (tkptr->ResponseContentTypeText)
         {
            if (tkptr->NetIoPtr->ReadCount &&
                tkptr->ResponseBufferPtr[tkptr->NetIoPtr->ReadCount-1] == '\n')
               WatchDataFormatted ("!#AZ", tkptr->NetIoPtr->ReadCount,
                                           tkptr->ResponseBufferPtr);
            else
               WatchDataFormatted ("!#AZ\n", tkptr->NetIoPtr->ReadCount,
                                             tkptr->ResponseBufferPtr);
         }
#endif /* WATCH_MOD */
      }

      if (tkptr->ResponseTransferEncodingChunked)
      {
         status = ProxyResponseChunked (tkptr);
         if (VMSnok (status))
         {
            tkptr->ResponseStatusCode = 502;
            ErrorGeneral (rqptr, MsgFor(rqptr,MSG_PROXY_HEADER), FI_LI);
            ProxyEnd (tkptr);
            return;
         }
      }
   }

   /*******************************************************/
   /* to cache file (then any client) or direct to client */
   /*******************************************************/

   if (tkptr->LoadFab.fab$w_ifi)
      ProxyCacheLoadWrite (tkptr);
   else
      ProxyResponseNetWrite (tkptr);
}

/****************************************************************************/
/*
The body is being "Transfer-Encoding: chunked" so the internal structure of
this data stream needs to be parsed to detect end-of-body.
*/

int ProxyResponseChunked (PROXY_TASK *tkptr)

{
#define WATCH_CHUNK \
   if (WATCHMOD (tkptr, WATCH_MOD_PROXY)) \
      WatchThis (WATCHITM(tkptr), WATCH_MOD_PROXY, \
                 "CHUNKED !SL !UL !UL !UL !&B !&B !&B !&Z", \
                 BufferRemaining, \
                 tkptr->ResponseChunkedNewlineCount, \
                 tkptr->ResponseChunkedSize, \
                 tkptr->ResponseChunkedCount, \
                 tkptr->ResponseChunkedEol, \
                 tkptr->ResponseChunkedEot, \
                 tkptr->ResponseChunkedEnd, \
                 tkptr->ResponseChunkedString);

   int  BufferRemaining;
   char  *cptr, *czptr, *sptr, *zptr;
   REQUEST_STRUCT  *rqptr;

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

   if (WATCHMOD (tkptr, WATCH_MOD_PROXY))
      WatchThis (WATCHITM(tkptr), WATCH_MOD_PROXY,
                 "ProxyResponseChunked()");

   rqptr = tkptr->RequestPtr;

   if (!tkptr->ResponseChunkedInit)
   {
      tkptr->ResponseChunkedInit = true;
      tkptr->ResponseChunkedEnd = 
         tkptr->ResponseChunkedEol =
         tkptr->ResponseChunkedEot = false;
      tkptr->ResponseChunkedCount =
         tkptr->ResponseChunkedSize =
         tkptr->ResponseChunkedNewlineCount =  0;
      tkptr->ResponseChunkedString[0] = '\0';
   }

   czptr = (cptr = tkptr->ResponseBufferNetPtr) + tkptr->ResponseBufferNetCount;

   while (cptr < czptr)
   {
      BufferRemaining = czptr - cptr; 
      WATCH_CHUNK

      if (tkptr->ResponseChunkedSize &&
          !tkptr->ResponseChunkedEot)
      {
         /* currently transfering a chunk */
         if (BufferRemaining >=
             tkptr->ResponseChunkedSize - tkptr->ResponseChunkedCount)
         {
            /* have enough for this chunk (at least) */
            cptr += tkptr->ResponseChunkedSize - tkptr->ResponseChunkedCount;
            tkptr->ResponseChunkedCount = tkptr->ResponseChunkedSize = 0;
         }
         else
         {
            /* have not reached the end of this chunk yet */
            tkptr->ResponseChunkedCount += BufferRemaining;
            break;
         }
      }

      if (cptr >= czptr) break;
      BufferRemaining = czptr - cptr; 
      WATCH_CHUNK

      while (cptr < czptr &&
             !tkptr->ResponseChunkedSize &&
             !tkptr->ResponseChunkedEot)
      {
         /* getting the size of the next chunk */
         if (tkptr->ResponseChunkedEol)
         {
            /* looking for the end of the chunk size line */
            while (cptr < czptr && *cptr != '\n') cptr++;
            if (cptr >= czptr) break;
            cptr++;

            /* hit the end-of-line */
            tkptr->ResponseChunkedSize =
               strtol (tkptr->ResponseChunkedString, NULL, 16);

            if (WATCHING (tkptr, WATCH_PROXY_RESP_BDY))
               WatchThis (WATCHITM(rqptr), WATCH_PROXY_RESP_BDY,
                          "RESPONSE BODY CHUNK \"!AZ\" !UL bytes",
                          tkptr->ResponseChunkedString,
                          tkptr->ResponseChunkedSize);

            tkptr->ResponseChunkedEol = false;
            tkptr->ResponseChunkedString[0] = '\0';
            tkptr->ResponseChunkedCount = 0;
            if (!tkptr->ResponseChunkedSize)
            {
               tkptr->ResponseChunkedEot = true;
               tkptr->ResponseChunkedNewlineCount = 1;
            }
         }
         else
         {
            if (!tkptr->ResponseChunkedString[0])
            {
               /* step over any trailing CR-LF of any preceding chunk */
               if (*cptr == '\r' && cptr < czptr) cptr++;
               if (*cptr == '\n' && cptr < czptr) cptr++;
            }
            zptr = (sptr = tkptr->ResponseChunkedString) +
                   sizeof(tkptr->ResponseChunkedString);
            /* find the end of the currently buffered string */
            for (sptr = tkptr->ResponseChunkedString;
                 *sptr && sptr < zptr;
                 sptr++);
            while (isxdigit(*cptr) && cptr < czptr && sptr < zptr)
               *sptr++ = *cptr++;
            /* this shouldn't happen! */
            if (sptr >= zptr) return (STS$K_ERROR);
            *sptr = '\0';
            /* if reached the end of the hex number look for the end-of-line */
            if (cptr < czptr && !isxdigit(*cptr))
               tkptr->ResponseChunkedEol = true;
         }
      }

      if (cptr >= czptr) break;
      BufferRemaining = czptr - cptr; 
      WATCH_CHUNK

      if (tkptr->ResponseChunkedEot)
      {
         /* looking for the end-of-trailer consecutive newlines */
         while (cptr < czptr && tkptr->ResponseChunkedNewlineCount < 2)
         {
            if (*cptr == '\n')
               tkptr->ResponseChunkedNewlineCount++;
            else
            if (*cptr != '\r')
               tkptr->ResponseChunkedNewlineCount = 0;
            cptr++;
         }
         if (tkptr->ResponseChunkedNewlineCount >= 2) break;
      }

      if (cptr >= czptr) break;
      BufferRemaining = czptr - cptr; 
   }

   if (tkptr->ResponseChunkedNewlineCount >= 2)
      tkptr->ResponseChunkedEnd = true;

   if (cptr >= czptr) BufferRemaining = 0;
   WATCH_CHUNK

   return (SS$_NORMAL);
}

/****************************************************************************/
/*
If a cache load is in progress this function is called when the file write is
complete. If not in progress then this is called directly from
ProxyReadResponseAst(). If a request is associated with the task the current
buffer is written out to the client via the network.  If no request then the
next buffer is read from the proxied server.
*/

ProxyResponseNetWrite (PROXY_TASK *tkptr)

{
   int  dcnt;
   char  *dptr;

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

   if (WATCHMOD (tkptr, WATCH_MOD_PROXY))
      WatchThis (WATCHITM(tkptr), WATCH_MOD_PROXY,
                 "ProxyResponseNetWrite()");

   if (!tkptr->ResponseHeaderSent)
   {
      dptr = tkptr->RebuiltResponsePtr,
      dcnt = tkptr->RebuiltResponseLength;
   }
   else
   {
      dptr = tkptr->ResponseBufferNetPtr;
      dcnt = tkptr->ResponseBufferNetCount;
   }

   NetWrite (tkptr->RequestPtr, &ProxyResponseNetWriteAst, dptr, dcnt);
}

/****************************************************************************/
/*
************
*** NOTE ***  This function takes a pointer to a request!!!
************

Just a wrapper for when NetWrite() is used to AST queue a read of the next part
of the response.  Check the network write status and if OK then queue another
read of data from the proxied server.
*/

ProxyResponseNetWriteAst (REQUEST_STRUCT *rqptr)

{
   int  BufferSize;
   PROXY_TASK  *tkptr;

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

   tkptr = rqptr->ProxyTaskPtr; 

   if (WATCHMOD (rqptr, WATCH_MOD_PROXY))
      WatchThis (WATCHITM(rqptr), WATCH_MOD_PROXY,
                 "ProxyResponseNetWriteAst() !&F !&X !&B !UL !UL",
                 &ProxyResponseNetWriteAst, rqptr->NetIoPtr->WriteStatus,
                 tkptr->PersistentResponse,
                 tkptr->ResponseBodyLength,
                 tkptr->ResponseContentLength);

   if (VMSnok (rqptr->NetIoPtr->WriteStatus))
   {
      /* write to client failed, just abandon the request */
      ProxyEnd (tkptr);
      return;
   }

   if (!tkptr->ResponseHeaderSent)
   {
      /* response header has now been sent */
      tkptr->ResponseHeaderSent = true;
      if (tkptr->ResponseBufferNetCount)
      {
         /* there is response body available to be sent */
         ProxyResponseNetWrite (tkptr);
         return;
      }
   }

   if (tkptr->ResponseTransferEncodingChunked)
   {
      if (tkptr->ResponseChunkedEnd)
      {
         /* end of chunked response body */
         ProxyEnd (tkptr);
         return;
      }
      BufferSize = tkptr->ResponseBufferSize;
   }
   else
   if (tkptr->ResponseContentLength >= 0)
   {
      /* a content-length was specified, read only enough to satisfy that */
      BufferSize = tkptr->ResponseContentLength - tkptr->ResponseBodyLength;
      if (BufferSize > tkptr->ResponseBufferSize)
         BufferSize = tkptr->ResponseBufferSize;
      /* if more was initially sent than content-length this can be negative */
      if (BufferSize <= 0)
      {
         /* have read enough to satisfy this (perhaps persistent) response */
         ProxyEnd (tkptr);
         return;
      }
   }
   else
      BufferSize = tkptr->ResponseBufferSize;

   ProxyNetRead (tkptr, &ProxyReadResponseAst,
                 tkptr->ResponseBufferPtr, BufferSize);
}

/****************************************************************************/
/*
Check the HTTP version.  If it doesn't look like an HTTP/1.n then assume its an
HTTP/0.9 (stream of HTML) and don't look for header fields.  Look through the
data received from the remote server so far for two consecutive blank lines
(two newlines or two carriage-return/newline combinations).  This delimits the
response header.  When found parse the response header.
*/

int ProxyResponseHeader (PROXY_TASK *tkptr)

{
   int  status,
        cnlcnt;
   char  *cptr, *zptr;

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

   if (WATCHMOD (tkptr, WATCH_MOD_PROXY))
      WatchThis (WATCHITM(tkptr), WATCH_MOD_PROXY, "ProxyResponseHeader()");

   cnlcnt = tkptr->ResponseConsecutiveNewLineCount;
   cptr = tkptr->ResponseHeaderPtr;
   zptr = tkptr->ResponseBufferCurrentPtr;

   if (!tkptr->ResponseHttpVersion)
   {
      /******************************/
      /* establish response version */
      /******************************/

      if (MATCH8 (cptr, "HTTP/1.1") && ISLWS(cptr[8]))
         tkptr->ResponseHttpVersion = HTTP_VERSION_1_1;
      else
      if (MATCH8 (cptr, "HTTP/1.0") && ISLWS(cptr[8]))
         tkptr->ResponseHttpVersion = HTTP_VERSION_1_0;
      else
      if (MATCH5 (cptr, "HTTP/"))
         tkptr->ResponseHttpVersion = HTTP_VERSION_UNKNOWN;
      else
      {
         ProxyHttp09ResponseHeader (tkptr);
         return (SS$_NORMAL);
      }
   }

   /******************************/
   /* look for the end-of-header */
   /******************************/

   while (*cptr && cnlcnt < 2 && cptr < zptr)
   {
      if (*cptr == '\r') cptr++;
      if (!*cptr) break;
      if (*cptr != '\n')
      {
         cptr++;
         cnlcnt = 0;
         continue;
      }
      cptr++;
      cnlcnt++;
   }

   if ((tkptr->ResponseConsecutiveNewLineCount = cnlcnt) >= 2)
   {
      /* found the end of the response header */
      tkptr->ResponseHeaderLength = cptr - (char*)tkptr->ResponseHeaderPtr;
      status = ProxyResponseRebuild (tkptr, tkptr->ResponseHeaderPtr,
                                            tkptr->ResponseHeaderLength);
      return (status);
   }

   return (SS$_NORMAL);
}


/****************************************************************************/
/*
Fudge status response components for HTTP/0.9.  Leave the
'tkptr->ResponseHeaderPtr' pointing where-ever but set the
'tkptr->ResponseHeaderLength' to zero.
*/

ProxyHttp09ResponseHeader (PROXY_TASK *tkptr)

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

   if (WATCHMOD (tkptr, WATCH_MOD_PROXY))
      WatchThis (WATCHITM(tkptr), WATCH_MOD_PROXY, "ProxyHttp09ResponseHeader()");

   tkptr->ProxyCacheSuitable = false;
   tkptr->ResponseConsecutiveNewLineCount = 2;
   tkptr->ResponseHeaderLength = 0;

   /* do not touch 'tkptr->ResponseHeaderPtr' upon penalty of bug! */
   tkptr->ResponseHttpVersion = HTTP_VERSION_0_9;
   tkptr->ResponseStatusCode = 200;
}


/****************************************************************************/
/*
Rebuild the response header pointed to by the supplied paramaters into a new
dynamically allocated buffer.  This function is intended to process the
response header either direct from the network or as read from a cache file. 
The rebuilt header has been modified to remove hop-by-hop fields and to add
persistent connection fields as required.  Other fields, perhaps unknown ones,
are just propagated.
*/

int ProxyResponseRebuild 
(
PROXY_TASK *tkptr,
char *HeaderPtr,
int HeaderLength
)
{
   static char  AgeField [32];
   static $DESCRIPTOR (AgeFieldDsc, AgeField);
   static $DESCRIPTOR (AgeFieldFaoDsc, "Age: !UL\r\n\0");
   static char  KeepAliveField [32];
   static $DESCRIPTOR (KeepAliveFieldDsc, KeepAliveField);
   static $DESCRIPTOR (KeepAliveFieldFaoDsc, "Keep-Alive: timeout=!UL\r\n\0");

   int  idx,
        status,
        AgeSeconds,
	KeepAliveSeconds,
        RebuiltHeaderLength;
   char  *cptr, *hzptr, *lzptr, *sptr, *tcptr, *tsptr, *tzptr, *zptr,
         *RebuiltHeaderPtr,*ContentLengthPtr=NULL,*ContentLengthEndPtr,
	 *ContentTypePtr=NULL;
   unsigned long  *Time64Ptr;
   PROXY_CACHE_FILE_QIO  *cfqptr;
   REQUEST_STRUCT  *rqptr;

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

   if (WATCHMOD (tkptr, WATCH_MOD_PROXY))
   {
      WatchThis (WATCHITM(tkptr), WATCH_MOD_PROXY,
                 "ProxyResponseRebuild() !&X !UL", HeaderPtr, HeaderLength);
      if (HeaderPtr) WatchDataDump (HeaderPtr, HeaderLength);
   }

   /* initialize header storage/flags potentially modified by this function */
   tkptr->ResponseContentLength = -1;
   tkptr->PersistentResponse =
      tkptr->ResponseConnectionClose =
      tkptr->ResponseConnectionKeepAlive =
      tkptr->ResponseContentEncodingGzip = 
      tkptr->ResponseContentEncodingUnknown = 
      tkptr->ResponseContentTypeMultipart = 
      tkptr->ResponseNoCache =
      tkptr->ResponseTransferEncodingChunked =
      tkptr->ResponseTransferEncodingUnsupported =
      tkptr->ResponseVaryUnsupported = false;
   tkptr->ResponseExpires[0] =
      tkptr->ResponseLastModified[0] = '\0';

   /* can be called with the 100-Continue response header to reset the above */
   if (!HeaderPtr) return (SS$_NORMAL);

   rqptr = tkptr->RequestPtr;

   /* non-zero QIO channel to check if it's from a cache file */
   cfqptr = &tkptr->CacheFileQio;

   /* so we can detect whether the original header contained an age field */
   AgeSeconds = -1;

   if (WATCHING (tkptr, WATCH_PROXY_RESP_HDR))
   {
      WatchThis (WATCHITM(rqptr), WATCH_PROXY_RESP_HDR,
                 "RESPONSE HEADER server->proxy !UL bytes", HeaderLength);
      WatchData (HeaderPtr, HeaderLength);
   }

   RebuiltHeaderLength = HeaderLength + 1024;
   if (rqptr->ProxyReverseLocationPtr &&
       (tkptr->ResponseStatusCode == 301 ||
        tkptr->ResponseStatusCode == 302))
      RebuiltHeaderLength += PROXY_LOCATION_REBUILD_BUFFER;
   RebuiltHeaderPtr = VmGetHeap (rqptr, RebuiltHeaderLength);

   zptr = (sptr = RebuiltHeaderPtr) + RebuiltHeaderLength;
   cptr = HeaderPtr;
   hzptr = cptr + HeaderLength;

   /************************/
   /* response status line */
   /************************/

   /* HTTP protocol version (redundant for network, needed for cache file) */
   if (MATCH8 (cptr, "HTTP/1.1"))
      tkptr->ResponseHttpVersion = HTTP_VERSION_1_1;
   else
   if (MATCH8 (cptr, "HTTP/1.0"))
      tkptr->ResponseHttpVersion = HTTP_VERSION_1_0;
   else
   if (MATCH5 (cptr, "HTTP/"))
      tkptr->ResponseHttpVersion = HTTP_VERSION_UNKNOWN;
   while (!ISLWS(*cptr) && NOTEOL(*cptr) && cptr < hzptr && sptr < zptr)
      *sptr++ = *cptr++;
   while (ISLWS(*cptr) && cptr < hzptr && sptr < zptr) *sptr++ = *cptr++;

   /* get the HTTP status code */
   tkptr->ResponseStatusCode = atoi(cptr);
   /* copy the rest of the response status line */
   while (NOTEOL(*cptr) && cptr < hzptr && sptr < zptr) *sptr++ = *cptr++;
   if (*cptr == '\r' && cptr < hzptr && sptr < zptr) *sptr++ = *cptr++;
   if (*cptr == '\n' && cptr < hzptr && sptr < zptr) *sptr++ = *cptr++;

   if (cptr == HeaderPtr ||
       cptr == hzptr ||
       !tkptr->ResponseStatusCode)
   {
      ErrorNoticed (rqptr, SS$_BUGCHECK, ErrorSanityCheck, FI_LI);
      rqptr->rqResponse.HttpStatus = 500;
      ErrorGeneral (rqptr, ErrorSanityCheck, FI_LI);
      return (STS$K_ERROR);
   }

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

   while (cptr < hzptr && sptr < zptr)
   {
      /* get a pointer to the end-of-line character */
      for (lzptr = cptr; lzptr < hzptr && NOTEOL(*lzptr); lzptr++);
      /* if reached the end-of-header empty line */
      if (cptr == lzptr) break;

      if (WATCHMOD (tkptr, WATCH_MOD_PROXY))
         WatchDataFormatted ("{!UL}!-!#AZ\n", lzptr-cptr, cptr);

      if (TOUP(*cptr) == 'A' &&
          strsame (cptr, "Age:", 4))
      {
         /*******/
         /* age */
         /*******/

         tcptr = cptr + 4;
         while (ISLWS(*tcptr) && tcptr < lzptr) tcptr++;
         AgeSeconds = atoi(tcptr);
         if (WATCHMOD (tkptr, WATCH_MOD_PROXY))
            WatchThis (WATCHITM(tkptr), WATCH_MOD_PROXY,
                       "!UL", AgeSeconds);
         /* we'll add our own later, the original header is not propagated */
         cptr += 4;
         while (cptr < lzptr) cptr++;
         if (*cptr == '\r' && cptr < hzptr) cptr++;
         if (*cptr == '\n' && cptr < hzptr) cptr++;
         continue;
      }
      else
      if (TOUP(*cptr) == 'C')
      {
         if (strsame (cptr, "Cache-Control:", 14))
         {
            /*****************/
            /* cache control */
            /*****************/

            tcptr = cptr + 14;
            while (ISLWS(*tcptr) && tcptr < lzptr) tcptr++;
            while (tcptr < lzptr)
            {
               if (strsame (tcptr, "must-revalidate", 15))
               {
                  tkptr->ResponseCacheControlMustReval = true;
                  tcptr += 15;
               }
               else
               if (strsame (tcptr, "max-age=", 8))
               {
                  tcptr += 8;
                  tkptr->ResponseCacheControlMaxAge = atoi(tcptr);
                  if (!tkptr->ResponseCacheControlMaxAge)
                     tkptr->ResponseCacheControlMaxAgeZero = true;
               }
               else
               if (strsame (tcptr, "no-cache", 8))
               {
                  tkptr->ResponseCacheControlNoCache = true;
                  tcptr += 8;
               }
               else
               if (strsame (tcptr, "no-store", 8))
               {
                  tkptr->ResponseCacheControlNoStore = true;
                  tcptr += 8;
               }
               else
               if (strsame (tcptr, "no-transform", 12))
               {
                  tkptr->ResponseCacheControlNoTransform = true;
                  tcptr += 12;
               }
               else
               if (strsame (tcptr, "private", 7))
               {
                  tkptr->ResponseCacheControlPrivate = true;
                  tcptr += 7;
               }
               else
               if (strsame (tcptr, "proxy-revalidate", 16))
               {
                  tkptr->ResponseCacheControlProxyReval = true;
                  tcptr += 16;
               }
               else
               if (strsame (tcptr, "public", 6))
               {
                  tkptr->ResponseCacheControlPublic = true;
                  tcptr += 6;
               }
               else
               if (strsame (tcptr, "s-maxage=", 9))
               {
                  tcptr += 9;
                  tkptr->ResponseCacheControlSMaxAge = atoi(tcptr);
                  if (!tkptr->ResponseCacheControlSMaxAge)
                     tkptr->ResponseCacheControlMaxAgeZero = true;
               }

               while (!ISLWS(*tcptr) && *tcptr != ',' && tcptr < lzptr)
                  tcptr++;
               while ((ISLWS(*tcptr) || *tcptr == ',') && tcptr < lzptr)
                  tcptr++;
            }

            if (tkptr->ResponseCacheControlMaxAgeZero ||
                tkptr->ResponseCacheControlMustReval ||
                tkptr->ResponseCacheControlNoCache ||
                tkptr->ResponseCacheControlNoStore ||
                tkptr->ResponseCacheControlPrivate ||
                tkptr->ResponseCacheControlProxyReval)
               tkptr->ResponseNoCache = true;

            if (WATCHMOD (tkptr, WATCH_MOD_PROXY))
               WatchThis (WATCHITM(tkptr), WATCH_MOD_PROXY,
                          "!&B - !&B !UL/!&B !&B !&B !&B !&B !&B !&B !UL",
                          tkptr->ResponseNoCache,
                          tkptr->ResponseCacheControlMustReval,
                          tkptr->ResponseCacheControlMaxAge,
                          tkptr->ResponseCacheControlMaxAgeZero,
                          tkptr->ResponseCacheControlNoCache,
                          tkptr->ResponseCacheControlNoStore,
                          tkptr->ResponseCacheControlNoTransform,
                          tkptr->ResponseCacheControlPrivate,
                          tkptr->ResponseCacheControlProxyReval,
                          tkptr->ResponseCacheControlPublic,
                          tkptr->ResponseCacheControlSMaxAge);

            /* drop through to copy this header */
         }
         else
         if (strsame (cptr, "Content-Length:", 15))
         {
            /******************/
            /* content-length */
            /******************/

            tcptr = cptr + 15;
            while (ISLWS(*tcptr) && tcptr < lzptr) tcptr++;
            tkptr->ResponseContentLength = atoi(tcptr);
            if (WATCHMOD (tkptr, WATCH_MOD_PROXY))
               WatchThis (WATCHITM(tkptr), WATCH_MOD_PROXY,
                          "!UL", tkptr->ResponseContentLength);
            /* do not copy this header until we are sure we don't deflate */
	    /* save pointers to Content-Length header for subsequent use */
	    ContentLengthPtr = cptr;
            while (cptr < lzptr) cptr++;
            if (*cptr == '\r' && cptr < hzptr) cptr++;
            if (*cptr == '\n' && cptr < hzptr) cptr++;
	    ContentLengthEndPtr = cptr;
	    continue;
         }
         else
         if (strsame (cptr, "Content-Encoding:", 17))
         {
            /********************/
            /* content-encoding */
            /********************/

            tcptr = cptr + 17;
            while (ISLWS(*tcptr) && tcptr < lzptr) tcptr++;
            if (strsame (tcptr, "gzip", 4))
               tkptr->ResponseContentEncodingGzip = true;
            else
               tkptr->ResponseContentEncodingUnknown = true;
            if (WATCHMOD (tkptr, WATCH_MOD_PROXY))
               WatchThis (WATCHITM(tkptr), WATCH_MOD_PROXY,
                          "!&B", tkptr->ResponseContentEncodingGzip);
            /* drop through to copy this header */
         }
         else
         if (strsame (cptr, "Content-Type:", 13))
         {
            /****************/
            /* content-type */
            /****************/

            tcptr = cptr + 13;
            while (ISLWS(*tcptr) && tcptr < lzptr) tcptr++;
	    ContentTypePtr = tcptr;
            if (*tcptr == 'm' && strsame (tcptr, "multipart/", 10))
               tkptr->ResponseContentTypeMultipart = true;
#if WATCH_MOD
            else
            if (*tcptr == 't' && strsame (tcptr, "text/", 5))
               tkptr->ResponseContentTypeText = true;
#endif /* WATCH_MOD */
            if (WATCHMOD (tkptr, WATCH_MOD_PROXY))
               WatchThis (WATCHITM(tkptr), WATCH_MOD_PROXY,
                          "!&B", tkptr->ResponseContentTypeMultipart);
            /* drop through to copy this header */
         }
         else
         if (strsame (cptr, "Connection:", 11))
         {
            /**************/
            /* connection */
            /**************/

            cptr += 11;
            while (ISLWS(*cptr) && cptr < lzptr) cptr++;
            while (cptr < lzptr)
            {
               if (strsame (cptr, "close", 5))
               {
                  tkptr->ResponseConnectionClose = true;
                  cptr += 5;
               }
               else
               if (strsame (cptr, "keep-alive", 10))
               {
                  tkptr->ResponseConnectionKeepAlive = true;
                  cptr += 10;
               }
               while (!ISLWS(*cptr) && *cptr != ',' && cptr < lzptr) cptr++;
               while ((ISLWS(*cptr) || *cptr == ',') && cptr < lzptr) cptr++;
            }
            if (WATCHMOD (tkptr, WATCH_MOD_PROXY))
               WatchThis (WATCHITM(tkptr), WATCH_MOD_PROXY,
                          "!&B !&B", tkptr->ResponseConnectionClose,
                          tkptr->ResponseConnectionKeepAlive);
            /* do not drop through, this header is not propagated */
            while (cptr < lzptr) cptr++;
            if (*cptr == '\r' && cptr < hzptr) cptr++;
            if (*cptr == '\n' && cptr < hzptr) cptr++;
            continue;
         }
      }
      else
      if (TOUP(*cptr) == 'E' &&
          strsame (cptr, "Expires:", 8))
      {
         /***********/
         /* expires */
         /***********/

         tcptr = cptr + 8;
         while (ISLWS(*tcptr) && tcptr < lzptr) tcptr++;
         tzptr = (tsptr = tkptr->ResponseExpires) +
                 sizeof(tkptr->ResponseExpires);
         while (tcptr < lzptr && tsptr < tzptr) *tsptr++ = *tcptr++;
         if (tsptr >= tzptr) tsptr = tkptr->ResponseExpires;
         *tsptr = '\0';
         status = HttpGmTime (tkptr->ResponseExpires,
                              &tkptr->ResponseExpiresTime64);
         if (VMSnok (status))
         {
            PUT_ZERO_QUAD (tkptr->ResponseExpiresTime64);
            tkptr->ResponseExpires[0] = '\0';
         }
         if (WATCHMOD (tkptr, WATCH_MOD_PROXY))
            WatchThis (WATCHITM(tkptr), WATCH_MOD_PROXY,
                       "!&Z !&S !%D", tkptr->ResponseExpires, status,
                       &tkptr->ResponseExpiresTime64);
         /* drop through to copy this header */
      }
      else
      if (TOUP(*cptr) == 'K' &&
          strsame (cptr, "Keep-Alive:", 11))
      {
         /**************/
         /* keep-alive */
         /**************/

         /* just note the presence of the field */
         tkptr->ResponseKeepAlive = true;
         /* do not drop through, this header is not propagated */
         cptr += 11;
         while (cptr < lzptr) cptr++;
         if (*cptr == '\r' && cptr < hzptr) cptr++;
         if (*cptr == '\n' && cptr < hzptr) cptr++;
         continue;
      }
      else
      if (TOUP(*cptr) == 'L')
      {
         if (strsame (cptr, "Last-Modified:", 14))
         {
            /*****************/
            /* last-modified */
            /*****************/

            tcptr = cptr + 14;
            while (ISLWS(*tcptr) && tcptr < lzptr) tcptr++;
            tzptr = (tsptr = tkptr->ResponseLastModified) +
                    sizeof(tkptr->ResponseLastModified);
            while (tcptr < lzptr && tsptr < tzptr) *tsptr++ = *tcptr++;
            if (tsptr >= tzptr) tsptr = tkptr->ResponseLastModified;
            *tsptr = '\0';
            status = HttpGmTime (tkptr->ResponseLastModified,
                                 &tkptr->ResponseLastModifiedTime64);
            if (VMSnok (status))
            {
               PUT_ZERO_QUAD (tkptr->ResponseLastModifiedTime64);
               tkptr->ResponseLastModified[0] = '\0';
            }
            if (WATCHMOD (tkptr, WATCH_MOD_PROXY))
               WatchThis (WATCHITM(tkptr), WATCH_MOD_PROXY,
                          "!&Z !&S !%D", tkptr->ResponseLastModified, status,
                          &tkptr->ResponseLastModifiedTime64);
            /* drop through to copy this header */
         }
         else
         /* rewrite only if necessary, otherwise fall through to copy */
         if (strsame (cptr, "Location:", 9) &&
             rqptr->ProxyReverseLocationPtr &&
             (tkptr->ResponseStatusCode == 301 ||
              tkptr->ResponseStatusCode == 302))
         {
            /************/
            /* location */
            /************/

            cptr += 9;
            while (ISLWS(*cptr) && cptr < lzptr) cptr++;
            tcptr = "Location: ";
            while (*tcptr && sptr < zptr) *sptr++ = *tcptr++;
            if (tcptr = ProxyRebuildLocation (tkptr, cptr))
            {
               /* the location was rebuilt */
               while (*tcptr && sptr < zptr) *sptr++ = *tcptr++;
               if (sptr < zptr) *sptr++ = '\r';
               if (sptr < zptr) *sptr++ = '\n';
               /* do not drop through, this header is not propagated as-is */
               while (cptr < lzptr) cptr++;
               if (*cptr == '\r' && cptr < hzptr) cptr++;
               if (*cptr == '\n' && cptr < hzptr) cptr++;
               continue;
            }
            /* else drop through to copy this header */
         }
      }
      else
      if (TOUP(*cptr) == 'P')
      {
         if (strsame (cptr, "Pragma:", 7))
         {
            /**********/
            /* pragma */
            /**********/

            cptr += 7;
            while (ISLWS(*cptr) && cptr < lzptr) cptr++;
            if (strsame (cptr, "no-cache", 8)) tkptr->ResponseNoCache = true;
            if (WATCHMOD (tkptr, WATCH_MOD_PROXY))
               WatchThis (WATCHITM(tkptr), WATCH_MOD_PROXY,
                          "!&B", tkptr->ResponseNoCache);
            /* do not drop through, this header is not propagated */
            while (cptr < lzptr) cptr++;
            if (*cptr == '\r' && cptr < hzptr) cptr++;
            if (*cptr == '\n' && cptr < hzptr) cptr++;
            continue;
         }
         else
         if (strsame (cptr, "Public:", 7))
         {
            /**********/
            /* public */
            /**********/

            cptr += 7;
            while (ISLWS(*cptr) && cptr < lzptr) cptr++;
            /* do not drop through, this header is not propagated */
            while (cptr < lzptr) cptr++;
            if (*cptr == '\r' && cptr < hzptr) cptr++;
            if (*cptr == '\n' && cptr < hzptr) cptr++;
            continue;
         }         
      }
      else
      if (TOUP(*cptr) == 'T' &&
          strsame (cptr, "Transfer-Encoding:", 18))
      {
         /*********************/
         /* transfer-encoding */
         /*********************/

         tcptr = cptr + 18;
         while (ISLWS(*tcptr) && tcptr < lzptr) tcptr++;
         if (strsame (tcptr, "chunked", 7))
            tkptr->ResponseTransferEncodingChunked = true;
         else
            tkptr->ResponseTransferEncodingUnsupported = true;
         if (WATCHMOD (tkptr, WATCH_MOD_PROXY))
            WatchThis (WATCHITM(tkptr), WATCH_MOD_PROXY, "!&B !&B",
                       tkptr->ResponseTransferEncodingChunked,
                       tkptr->ResponseTransferEncodingUnsupported);
         /* drop through to copy this header */
      }
      else
      if (TOUP(*cptr) == 'U' &&
          strsame (cptr, "Upgrade:", 8))
      {
         /**********************/
         /* upgrade: websocket */
         /**********************/

         tcptr = cptr + 8;
         while (ISLWS(*tcptr) && tcptr < lzptr) tcptr++;
         if (strsame (tcptr, "websocket", 9))
            tkptr->ResponseUpgradeWebSocket = true;
         /* drop through to copy this header */
      }
      else
      if (TOUP(*cptr) == 'V' &&
          strsame (cptr, "Vary:", 5))
      {
         /********/
         /* vary */
         /********/

         tcptr = cptr + 5;
         while (ISLWS(*tcptr) && tcptr < lzptr) tcptr++;
         while (tcptr < lzptr)
         {
            if (*tcptr == '*')
            {
               /* something not related to header fields */
               tkptr->ResponseVaryUnsupported = true;
               tcptr++;
            }
            else
            if (strsame (tcptr, "accept", 6) &&
                !strsame (tcptr, "accept-encoding", 15))
            {
               /* "Accept:", "Accept-Charset:, all-but, etc. */
               tkptr->ResponseVaryUnsupported = true;
               tcptr += 6;
            }
            while (!ISLWS(*tcptr) && *tcptr != ',' && tcptr < lzptr) tcptr++;
            while ((ISLWS(*tcptr) || *tcptr == ',') && tcptr < lzptr) tcptr++;
         }
         if (WATCHMOD (tkptr, WATCH_MOD_PROXY))
            WatchThis (WATCHITM(tkptr), WATCH_MOD_PROXY,
                       "!&B", tkptr->ResponseVaryUnsupported);
         /* drop through to copy this header */
      }

      /* got all the way down here, copy the header into the rebuilt buffer */
      while (cptr < lzptr && sptr < zptr) *sptr++ = *cptr++;
      if (*cptr == '\r' && cptr < hzptr && sptr < zptr) *sptr++ = *cptr++;
      if (*cptr == '\n' && cptr < hzptr && sptr < zptr) *sptr++ = *cptr++;
   }

   /*********************/
   /* add proxy headers */
   /*********************/

   if (tkptr->ResponseStatusCode != 100)
   {
      /*******/
      /* age */
      /*******/

      /* if this header is coming from a cache file, use last loaded time */
      if (cfqptr->QioChannel &&
          /* interim v6.0 backward compatibility (didn't use BDT) */
          QUAD_NOT_ZERO(cfqptr->BdtTime64))
         Time64Ptr = &cfqptr->BdtTime64;
      else
      /* if an age field was supplied with the original header */
      if (AgeSeconds >= 0)
         Time64Ptr = NULL;
      else
      /* if there's a last modified supplied with the response */
      if (QUAD_NOT_ZERO(tkptr->ResponseLastModifiedTime64))
         Time64Ptr = &tkptr->ResponseLastModifiedTime64;
      else
      /* otherwise just use the current time */
      {
         Time64Ptr = NULL;
         AgeSeconds = 0;
      }
      if (Time64Ptr)
         AgeSeconds = ProxyCacheDeltaSeconds (&HttpdTime64, Time64Ptr);
      if (AgeSeconds < 0) AgeSeconds = 0;
      sys$fao (&AgeFieldFaoDsc, 0, &AgeFieldDsc, AgeSeconds);
      for (cptr = AgeField; *cptr && sptr < zptr; *sptr++ = *cptr++);

      /*************/
      /* compress? */
      /*************/

      if (!tkptr->ResponseContentEncodingGzip &&
          !tkptr->ResponseContentEncodingUnknown &&
          !tkptr->ResponseTransferEncodingChunked)
      {
	 if (GzipResponse)
	    rqptr->rqResponse.ContentEncodeAsGzip =
               GzipShouldDeflate (rqptr, ContentTypePtr,
		                  tkptr->ResponseContentLength);

	 if (rqptr->rqResponse.ContentEncodeAsGzip)
	 {
            for (cptr = "Content-Encoding: gzip\r\n";
                 *cptr && sptr < zptr;
                 *sptr++ = *cptr++);
            if (tkptr->ResponseHttpVersion == HTTP_VERSION_1_1 &&
                tkptr->RequestHttpVersion == HTTP_VERSION_1_1)
            {
               rqptr->rqResponse.TransferEncodingChunked = true;
               for (cptr = "Transfer-Encoding: chunked\r\n";
                    *cptr && sptr < zptr;
                    *sptr++ = *cptr++);
            }
	 }
	 else
         if (ContentLengthPtr)
	 {
            /* we don't deflate, so output any Content-Length header */
            for (cptr = ContentLengthPtr;
                 cptr < ContentLengthEndPtr && sptr < zptr;
                 *sptr++ = *cptr++);
	 }
      }
      else
      if (ContentLengthPtr)
      {
         /* we don't deflate, so output Content-Length header */
	 for (cptr = ContentLengthPtr;
	      cptr < ContentLengthEndPtr && sptr < zptr;
	      *sptr++ = *cptr++);
      }

      /**************/
      /* connection */
      /**************/

      /* MUST follow the COMPRESS? decision immediately above */

      /* special case, 304's SHALL NOT contain a body (the only one AFICT) */
      if (tkptr->ResponseStatusCode == 304 &&
          tkptr->ResponseContentLength < 0)
      {
         /* to make persistence work just assume a content-length of 0 */
         tkptr->ResponseContentLength = 0;
      }

      if (tkptr->RequestHttpVersion == HTTP_VERSION_1_1)
      {
         /* proxy->origin persistence */
         if (tkptr->PersistentRequest &&
             (tkptr->ResponseContentLength >= 0 ||
              tkptr->ResponseTransferEncodingChunked) &&
             !tkptr->ResponseConnectionClose &&
	     !rqptr->rqResponse.ContentEncodeAsGzip)
            tkptr->PersistentResponse = true;
         else
            tkptr->PersistentResponse = false;

         /* client->proxy persistence */
         if (tkptr->PersistentRequest &&
             tkptr->PersistentResponse)
         {
            rqptr->PersistentResponse = true;
            /* not strictly required but many HTTP/1.1 servers supply it */
            for (cptr = "Connection: keep-alive\r\n";
                 *cptr && sptr < zptr;
                 *sptr++ = *cptr++);
         }
         else
         {
            rqptr->PersistentResponse = false;
            for (cptr = "Connection: close\r\n";
                 *cptr && sptr < zptr;
                 *sptr++ = *cptr++);
         }
      }
      else
      if (tkptr->RequestHttpVersion == HTTP_VERSION_1_0)
      {
         /* proxy->origin persistence */
         if (tkptr->PersistentRequest &&
             (tkptr->ResponseContentLength >= 0 ||
              tkptr->ResponseTransferEncodingChunked) &&
             (tkptr->ResponseKeepAlive ||
              tkptr->ResponseConnectionKeepAlive) &&
             !tkptr->ResponseConnectionClose &&
	     !rqptr->rqResponse.ContentEncodeAsGzip)
            tkptr->PersistentResponse = true;
         else
            tkptr->PersistentResponse = false;

         /* client->proxy persistence */
         if (tkptr->PersistentRequest &&
             tkptr->PersistentResponse)
         {
            rqptr->PersistentResponse = true;
            for (cptr = "Connection: keep-alive\r\n";
                 *cptr && sptr < zptr;
                 *sptr++ = *cptr++);
            if (rqptr->rqPathSet.TimeoutPersistent)
               KeepAliveSeconds = rqptr->rqPathSet.TimeoutPersistent;
	    else
               KeepAliveSeconds = Config.cfTimeout.Persistent;
            sys$fao (&KeepAliveFieldFaoDsc, 0, &KeepAliveFieldDsc,
                     KeepAliveSeconds);
            for (cptr = KeepAliveField;
                 *cptr && sptr < zptr;
                 *sptr++ = *cptr++);
         }
         else
            rqptr->PersistentResponse = false;
      }

      /***************************/
      /* proxy generated cookies */
      /***************************/

      for (idx = 0; idx < RESPONSE_COOKIE_MAX; idx++)
      {
         if (rqptr->rqResponse.CookiePtr[idx])
         {
            for (cptr = "Set-Cookie: ";
                 *cptr && sptr < zptr;
                 *sptr++ = *cptr++);
	    for (cptr = rqptr->rqResponse.CookiePtr[idx];
	         *cptr && sptr < zptr;
	         *sptr++ = *cptr++);
            if (sptr < zptr) *sptr++ = '\r';
            if (sptr < zptr) *sptr++ = '\n';
         }
      }
   }

   /* header terminating empty line */
   if (sptr < zptr) *sptr++ = '\r';
   if (sptr < zptr) *sptr++ = '\n';

   if (sptr >= zptr)
   {
      ErrorNoticed (rqptr, SS$_BUGCHECK, ErrorSanityCheck, FI_LI);
      rqptr->rqResponse.HttpStatus = 500;
      ErrorGeneralOverflow (rqptr, FI_LI);
      return (STS$K_ERROR);
   }
   *sptr = '\0';
   RebuiltHeaderLength = sptr - RebuiltHeaderPtr;

   if (WATCHING (tkptr, WATCH_PROXY_RESP_HDR))
   {
      WatchThis (WATCHITM(rqptr), WATCH_PROXY_RESP_HDR,
                 "RESPONSE HEADER proxy->client !UL bytes",
                 RebuiltHeaderLength);
      WatchData (RebuiltHeaderPtr, RebuiltHeaderLength);
   }

   tkptr->RebuiltResponsePtr = RebuiltHeaderPtr;
   tkptr->RebuiltResponseLength = RebuiltHeaderLength;

   return (SS$_NORMAL);
}

/****************************************************************************/
#ifdef COMMENTS_WITH_COMMENTS
/*
The reverse-proxy response header "Location:" field needs to be rewritten.
The original mapping rule would have been something like

  redirect  /foo/*  /http://foo.bar/*  proxy=reverse=location=/foo/

Check the "Location:" field for the proxied-to host name and if so rewrite it
to use the proxy server and the path indicated.  If the location field does not
contain the proxied-to host name then assume it's not being redirected back to
the same service.

If the 'proxy=reverse=location=<string>' ends in an asterisk the entire 302
"Location:" URL is appended (rather than just the path) resulting in something
along the lines of

  Location: http://original.host/foo/http://foo.bar/example/

which once redirected by the client can be subsequently tested for and some
action made according to the content (just a bell or whistle ;^).

The original scheme, host and 'proxy=reverse=location=<string>' information is
conveyed across the reverse-proxy redirect using redirect-persistent storage.

Returns a pointer to a dynamic buffer containing the new location string or
NULL if no rebuild was necesary.
*/ 
#endif /* COMMENTS_WITH_COMMENTS */

char* ProxyRebuildLocation
(
PROXY_TASK *tkptr,
char *LocationPtr
)
{
   boolean  RebuildLocation;
   int  len; 
   char  *cptr, *lptr, *sptr, *zptr;
   REQUEST_STRUCT  *rqptr;

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

   if (WATCHMOD (tkptr, WATCH_MOD_PROXY))
      WatchThis (WATCHITM(tkptr), WATCH_MOD_PROXY,
                 "ProxyRebuildLocation() !&Z",
                 tkptr->RequestPtr->ProxyReverseLocationPtr);

   rqptr = tkptr->RequestPtr;

   for (cptr = rqptr->ProxyReverseLocationPtr; *cptr; *cptr++);
   if (cptr > rqptr->ProxyReverseLocationPtr) cptr--;
   if (*cptr == '*')
   {
      /* ends in a wildcard, use whatever is in the location field */
      RebuildLocation = true;
   }
   else
   {
      RebuildLocation = false;
      lptr = LocationPtr;

      while (*lptr && *lptr != ':' && NOTEOL(*lptr)) lptr++;
      if (*lptr == ':') lptr++;
      if (*lptr == '/') lptr++;
      if (*lptr == '/') lptr++;
      /* this will contain the redirect-to/proxied-to host */
      sptr = rqptr->rqHeader.HostPtr;
      while (*sptr && *lptr && TOLO(*sptr) == TOLO(*lptr))
      {
         sptr++;
         lptr++;
      }
      if (!*sptr && (!*lptr || *lptr == '/')) RebuildLocation = true;
   }

   if (WATCHMOD (tkptr, WATCH_MOD_PROXY))
      WatchThis (WATCHITM(tkptr), WATCH_MOD_PROXY,
                 "!&?REBUILD\rNO REBUILD\r", RebuildLocation);

   if (!RebuildLocation) return (NULL);

   LocationPtr = sptr = VmGetHeap (rqptr, PROXY_LOCATION_REBUILD_BUFFER);
   zptr = sptr + PROXY_LOCATION_REBUILD_BUFFER;
   for (cptr = rqptr->ProxyReverseLocationPtr;
        *cptr && sptr < zptr;
        *sptr++ = *cptr++);
   /* finished with this now */
   rqptr->ProxyReverseLocationPtr = NULL;
   /* prevent consecutive slashes */
   if (*(sptr-1) == '/' && *lptr == '/') lptr++;
   while (*lptr && !ISLWS(*lptr) && NOTEOL(*lptr) && sptr < zptr)
      *sptr++ = *lptr++;
   if (sptr >= zptr)
   {
      ErrorNoticed (rqptr, SS$_RESULTOVF, NULL, FI_LI);
      strcpy (LocationPtr, "error-buffer-overflow!");
      return (LocationPtr);
   }
   *sptr = '\0';

   if (WATCHMOD (tkptr, WATCH_MOD_PROXY))
      WatchDataFormatted ("!&Z", LocationPtr);

   return (LocationPtr);
}

/****************************************************************************/
/*
Using the request header fields rebuild a request header suitable for use by
the proxy server.
*/ 

int ProxyRequestRebuild (PROXY_TASK *tkptr)

{
#define STRCAT(str) for (cptr = str; *cptr && sptr < zptr; *sptr++ = *cptr++);
#define CHRCAT(ch) { if (sptr < zptr) *sptr++ = ch; }

   static char  KeepAliveField [32];
   static $DESCRIPTOR (KeepAliveFieldDsc, KeepAliveField);
   static $DESCRIPTOR (KeepAliveFieldFaoDsc, "Keep-Alive: timeout=!UL\r\n\0");

   BOOL  ForwardedBy,
         XForwardedFor;
   int  idx, klen,
        HeaderSize,
        KeepAliveSeconds;
   char  *aptr, *cptr, *kptr, *sptr, *zptr;
   char  EncodedString [256];
   DICT_ENTRY_STRUCT  *denptr;
   REQUEST_STRUCT  *rqptr;

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

   if (WATCHMOD (tkptr, WATCH_MOD_PROXY))
      WatchThis (WATCHITM(tkptr), WATCH_MOD_PROXY, "ProxyRequestRebuild()");

   rqptr = tkptr->RequestPtr;
   tkptr->RebuiltRequestPtr = NULL;

   for (HeaderSize = 1024; HeaderSize < MAX_REQUEST_HEADER; HeaderSize *= 2)
   {
      if (tkptr->RebuiltRequestPtr)
         VmFreeFromHeap (rqptr, tkptr->RebuiltRequestPtr, FI_LI);
      tkptr->RebuiltRequestPtr = sptr = VmGetHeap (rqptr, HeaderSize);
      zptr = sptr + HeaderSize;

      STRCAT (rqptr->rqHeader.MethodName)
      CHRCAT (' ')
      if (IPADDRESS_IS_SET (&tkptr->ChainIpAddress))
      {
         STRCAT ("http://")
         STRCAT (tkptr->RequestHostPort)
      }
      STRCAT (tkptr->RequestUriPtr)

      if (tkptr->RequestHttpVersion == HTTP_VERSION_0_9)
      {
         /*******************/
         /* HTTP/0.9 header */
         /*******************/

         /* just end the line (and header) without an HTTP protocol version */
         STRCAT ("\r\n")
      }
      else
      {
         /*******************/
         /* HTTP/1.n header */
         /*******************/

         if (rqptr->rqHeader.HttpVersion == HTTP_VERSION_1_1)
            STRCAT (" HTTP/1.1\r\n")
         else
            STRCAT (" HTTP/1.0\r\n")

         /******************/
         /* request fields */
         /******************/

         /* add (with some massage) the original request's header fields */
         DictIterate (rqptr->rqDictPtr, NULL); 
         while (denptr = DictIterate (rqptr->rqDictPtr, DICT_TYPE_REQUEST))
         {
            cptr = kptr = DICT_GET_KEY(denptr);

            if (TOUP(*cptr) == 'A')
            {
               /* match including the terminating null */
               if (MATCH14 (cptr, "authorization") ||
                   strsame (cptr, "Authorization", -1))
               {
                  if (rqptr->rqPathSet.ProxyReverseNoAuthHeader)
                     continue;
                  if (rqptr->rqPathSet.ProxyReverseVerify)
                  {
                     /* (very) special case */
                     ProxyVerifyRecordSet (tkptr);
                     if (tkptr->VerifyRecordPtr)
                     {
                        STRCAT ("Authorization: Basic ")
                        STRCAT (tkptr->VerifyRecordPtr->AuthorizationString)
                        STRCAT ("\r\n")
                     }
                     else
                     {
                        /* busy indicates increase [ProxyVerifyRecordMax] */
                        rqptr->rqResponse.HttpStatus = 503;
                        ErrorGeneral (rqptr, MsgFor(rqptr,MSG_GENERAL_TOO_BUSY),
                                      FI_LI);
                        return (STS$K_ERROR);
                     }
                     continue;
                  }

                  /* inhibits caching (unless "cache-control: public") */
                  tkptr->RequestAuthorization = true;
               }
            }

            /*
               Hop-by-hop header fields ...
               For strict HTTP/1.1 compliance the following header fields are
               also hop-by-hop; "TE:", "Trailers:", "Transfer-Encoding:".
               However WASD transfers chunked request bodies directly to
               the proxied server and does not perform any re-encoding.
               For the upgrade header field it is just suppressed as WASD does
               not support changing protocols let alone HTTP-incompatible ones.
            */

            if (TOUP(*cptr) == 'C' &&
                (MATCH11 (cptr, "connection") ||
                 strsame (cptr, "Connection", -1)))
            {
               /* proxied WebSocket requests use connection upgrade */
               if (!rqptr->WebSocketRequest) continue;
            }

            if (TOUP(*cptr) == 'K' &&
                (MATCH11 (cptr, "keep-alive") ||
                 strsame (cptr, "Keep-Alive", -1)))
               continue;

            if (TOUP(*cptr) == 'H')
            {
               /* replace "Host:" field */
               if (MATCH5 (cptr, "host") &&
                   strsame (cptr, "host", -1))
               {
                  STRCAT ("Host: ")
                  STRCAT (tkptr->RequestHostPort)
                  STRCAT ("\r\n")
                  continue;
               }
            }

            if (TOUP(*cptr) == 'P')
            {
               /* absorb proxy authentication credentials unless CHAIN */
               if (MATCH16 (cptr, "proxy-authorization") &&
                   strsame (cptr, "Proxy-Authorization", -1))
               {
                  if (!rqptr->ServicePtr->ProxyChainAuthRequired ||
                      rqptr->rqPathSet.ProxyChainCredPtr)
                     continue;
               }

               if (MATCH16 (cptr, "proxy-connection") &&
                   strsame (cptr, "Proxy-Connection", -1))
                  continue;
            }

            if (TOUP(*cptr) == 'U' &&
                (MATCH8 (cptr, "upgrade") ||
                strsame (cptr, "Upgrade", -1)))
            {
               /* proxied WebSocket requests use connection upgrade */
               if (!rqptr->WebSocketRequest) continue;
            }

            if (rqptr->rqPathSet.ProxyHeaderCount)
            {
               klen = strlen(kptr);
               for (idx = 0; idx < rqptr->rqPathSet.ProxyHeaderCount; idx++)
               {
                  for (aptr = rqptr->rqPathSet.ProxyHeader[idx];
                       *aptr && *aptr != '=';
                       aptr++);
                  /* if not this header then just on to the next */
                  if (aptr - rqptr->rqPathSet.ProxyHeader[idx] != klen)
                     continue;
                  if (!strsame (rqptr->rqPathSet.ProxyHeader[idx], kptr, klen))
                     continue;
                  break;
               }
               if (idx < rqptr->rqPathSet.ProxyHeaderCount)
               {
                  /* if no parameter then exclude the header */
                  if (!*aptr) continue;
                  /* else set header value to the parameter (even if empty) */
                  aptr++;
               }
               else
                  aptr = NULL;
            }
            else
               aptr = NULL;

            /* otherwise, include this field */
            while (*cptr && sptr < zptr)
            {
               /* make it look more like an HTTP/1.1 request by capitalising */
               if (cptr == kptr || *(cptr-1) == '-')
                  *sptr++ = TOUP(*cptr++);
               else
                  *sptr++ = *cptr++;
            }
            STRCAT (": ")

            if ((cptr = aptr) == NULL) cptr = DICT_GET_VALUE(denptr);
            STRCAT (cptr)

            STRCAT ("\r\n")
         }

         if ((cptr = rqptr->ServicePtr->ProxyChainCred)[0] ||
             (cptr = rqptr->rqPathSet.ProxyChainCredPtr))
         {
            /* upstream (chained) proxy require authorization */
            if (strsame (cptr, "basic:", 6))
            {
               /* format is "basic:<username>:<password>" */
               cptr = BasicPrintableEncode (cptr+6, EncodedString,
                                                    sizeof(EncodedString));
               if (cptr[0])
               {
                  /* error report returned by the encode function */
                  rqptr->rqResponse.HttpStatus = 401;
                  ErrorGeneral (rqptr, cptr, FI_LI);
                  return (STS$K_ERROR);
               }
               STRCAT ("Proxy-Authorization: basic ");
               STRCAT (EncodedString);
               STRCAT ("\r\n")
            }
            else
            if (strsame (cptr, "opaque:", 7))
            {
               STRCAT ("Proxy-Authorization: ");
               STRCAT (cptr + 7);
               STRCAT ("\r\n")
            }
            else
            {
               rqptr->rqResponse.HttpStatus = 500;
               ErrorGeneral (rqptr, MsgFor(rqptr,MSG_AUTH_SCHEME), FI_LI);
               return (STS$K_ERROR);
            }
         }

         /* proxy to remote server connection persistence */
         if (tkptr->PersistentRequest)
         {
            STRCAT ("Connection: keep-alive\r\n")
   	 if (rqptr->rqPathSet.TimeoutPersistent)
   	    KeepAliveSeconds = rqptr->rqPathSet.TimeoutPersistent;
   	 else
   	    KeepAliveSeconds = Config.cfTimeout.Persistent;
            sys$fao (&KeepAliveFieldFaoDsc, 0, &KeepAliveFieldDsc,
                     KeepAliveSeconds);
            STRCAT (KeepAliveField)
         }
         else
            STRCAT ("Connection: close\r\n")

         /*******************************/
         /* this proxy's proxied fields */
         /*******************************/

         ForwardedBy = ProxyForwardedBy;
         if (rqptr->rqPathSet.ProxyForwardedBy)
         {
            if (rqptr->rqPathSet.ProxyForwardedBy == PROXY_FORWARDED_NONE)
               ForwardedBy = PROXY_FORWARDED_DISABLED;
            else
               ForwardedBy = rqptr->rqPathSet.ProxyForwardedBy;
         }
         if (ForwardedBy)
         {
            STRCAT ("Forwarded: ")
            if (ForwardedBy == PROXY_FORWARDED_BY ||
                ForwardedBy == PROXY_FORWARDED_FOR ||
                ForwardedBy == PROXY_FORWARDED_ADDRESS)
            {
               STRCAT ("by http://")
               if (rqptr->ServicePtr->ServerPort == 80)
                  STRCAT (rqptr->ServicePtr->ServerHostName)
               else
                  STRCAT (rqptr->ServicePtr->ServerHostPort)
               STRCAT (" (")
               STRCAT (SoftwareID)
               CHRCAT (')')
            }
            if (ForwardedBy == PROXY_FORWARDED_FOR)
            {
               STRCAT (" for ")
               STRCAT (rqptr->ClientPtr->Lookup.HostName)
            }
            else
            if (ForwardedBy == PROXY_FORWARDED_ADDRESS)
            {
               STRCAT (" for ")
               STRCAT (&rqptr->ClientPtr->IpAddressString)
            }
            if (rqptr->rqHeader.ForwardedPtr)
            {
               if (ForwardedBy == PROXY_FORWARDED_BY ||
                ForwardedBy == PROXY_FORWARDED_FOR ||
                ForwardedBy == PROXY_FORWARDED_ADDRESS)
               STRCAT (", ")
               STRCAT (rqptr->rqHeader.ForwardedPtr)
            }
            STRCAT ("\r\n")
         }

         XForwardedFor = ProxyXForwardedFor;
         if (rqptr->rqPathSet.ProxyXForwardedFor)
         {
            if (rqptr->rqPathSet.ProxyXForwardedFor == PROXY_XFORWARDEDFOR_NONE)
               XForwardedFor = PROXY_XFORWARDEDFOR_DISABLED;
            else
               XForwardedFor = rqptr->rqPathSet.ProxyXForwardedFor;
         }
         if (XForwardedFor)
         {
            STRCAT ("X-Forwarded-For: ")
            if (rqptr->rqHeader.XForwardedForPtr)
            {
               STRCAT (rqptr->rqHeader.XForwardedForPtr)
               STRCAT (", ")
            }
            if (XForwardedFor == PROXY_XFORWARDEDFOR_ENABLED)
               STRCAT (rqptr->ClientPtr->Lookup.HostName)
            else
            if (XForwardedFor == PROXY_XFORWARDEDFOR_ADDRESS)
               STRCAT (&rqptr->ClientPtr->IpAddressString)
            else
               STRCAT ("unknown")
            STRCAT ("\r\n")
         }

         /**************************/
         /* end of HTTP/1.n header */
         /**************************/

         /* terminate last line, end-of-header empty line */
         STRCAT ("\r\n")
      }

      if (sptr < zptr) break;
   }

   if (sptr >= zptr)
   {
      rqptr->rqResponse.HttpStatus = 500;
      ErrorGeneralOverflow (rqptr, FI_LI);
      return (STS$K_ERROR);
   }

   *sptr = '\0';
   tkptr->RebuiltRequestLength = sptr - tkptr->RebuiltRequestPtr;

   if (WATCHMOD (tkptr, WATCH_MOD_PROXY))
      WatchDataFormatted ("!&Z\n", tkptr->RebuiltRequestPtr);

   return (SS$_NORMAL);

#undef STRCAT
#undef CHRCAT
}

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

