/*****************************************************************************/
/*
                                Redirect.c

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

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

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

If "//:port/path" (i.e. begins with "//:") the request scheme and host are
substituted into the URL and it becomes effectively a redirect to a different
port on the same host.

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

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

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

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

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

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


VERSION HISTORY
---------------
02-FEB-2019  MGD  allow a specified port when redirecting, i.e. http[s]//:nnn
                  bugfix; non-local without "Host:" use name not host:port
21-MAR-2017  MGD  bugfix; use rqHeader.RequestBody.. for body with header
28-DEC-2015  MGD  move from REQUEST.C to indepedent module
*/
/*****************************************************************************/

#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>

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

#define WASD_MODULE "REDIRECT"

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

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

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

extern BOOL  Http2Enabled;

extern int  ToLowerCase[],
            ToUpperCase[];

extern char  ErrorSanityCheck[],
             SoftwareID[];

#define acptr AccountingPtr
extern ACCOUNTING_STRUCT  *AccountingPtr;
extern CONFIG_STRUCT  Config;
extern MSG_STRUCT  Msgs;
extern WATCH_STRUCT  Watch;

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

int RedirectRequest (REQUEST_STRUCT *rqptr)

{
#define STRCAT(string) { \
   for (cptr = string; *cptr && sptr < zptr; *sptr++ = *cptr++); \
}
#define CHRCAT(ch) { \
   if (sptr < zptr) *sptr++ = ch; \
}
   static char  *SchemeHttp = "/http://",
                *SchemeHttps = "/https://",
                *SchemeFtp = "/ftp://";

   static BOOL  RedirectWildcardChecked,
                RedirectWildcardEnabled;

   static char  TextContentLength [32];
   static $DESCRIPTOR (TextContentLengthDsc, TextContentLength);
   static $DESCRIPTOR (TextContentLengthFaoDsc, "Content-Length: !UL\r\n\0");

   BOOL  IncludeQueryString,
         ConcatQueryString,
         LocalRedirection;
   int  idx, length, status,
        AlphaCount,
        BodyCount,
        ExciseCount,
        HostDotCount,
        PortNumber,
        ReadBufferSize,
        RequestHeaderLength,
        RequestLineLength,
        SchemeLength,
        ServiceDotCount,
        StringDotCount,
        UrlLength;
   ulong  BytesRx [QUAD2];
   char  *aptr, *cptr, *sptr, *zptr,
         *BodyPtr,
         *HttpMethodNamePtr,
         *ReadBufferPtr,
         *RequestLinePtr,
         *PathPtr,
         *SchemePtr,
         *UrlPtr,
         *UrlEndPtr;
   char  HttpMethodName [32],
         HostField [128+16],
         /* allow for agents with silly amounts of request header */
         RedirectionUrl [16384];
   DICT_STRUCT  *dicptr;
   DICT_ENTRY_STRUCT  *denptr;

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

   if (WATCHMOD (rqptr, WATCH_MOD_REQUEST))
      WatchThis (WATCHITM(rqptr), WATCH_MOD_REQUEST,
                 "RedirectRequest() !&Z", rqptr->rqResponse.LocationPtr);

/* at this stage just to delineate this wildcard annoyance */
#define REDIRECT_WILDCARD 1
#if REDIRECT_WILDCARD
   if (!RedirectWildcardChecked)
   {
      /* bit of a kludge but "Acckkk Alex!" */
      RedirectWildcardChecked = true;
      RedirectWildcardEnabled = (SysTrnLnm("WASD_REDIRECT_WILDCARD") != NULL);
   }
#endif /* REDIRECT_WILDCARD */

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

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

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

   rqptr->rqResponse.HttpStatus = 302;

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

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

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

   if (IncludeQueryString) UrlLength += rqptr->rqHeader.QueryStringLength;

   if (UrlLength > sizeof(RedirectionUrl)-1) UrlLength = -1;
   if (UrlLength < 0)
   {
      ErrorNoticed (rqptr, SS$_RESULTOVF, NULL, FI_LI);
      ErrorGeneralOverflow (rqptr, FI_LI);
      return (STS$K_ERROR);
   }

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

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

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

      InstanceGblSecIncrLong (&acptr->RedirectLocalCount);

      /* create a dictionary to contain the rebuilt request */
      dicptr = DictCreate (rqptr, -1);

      if (!rqptr->Http2Stream.Http2Ptr &&
          rqptr->rqHeader.ContentLength)
      {
         /*  allow for any content received along with the header */
         PUT_QUAD_LONG (rqptr->BytesRx, BodyCount);
         BodyCount -= rqptr->rqHeader.RequestHeaderLength;
         sptr = rqptr->rqHeader.RequestHeaderPtr +
                rqptr->rqHeader.RequestHeaderLength;
         if (BodyCount)
         {
            BodyPtr = VmGetHeap (rqptr, BodyCount);
            memcpy (BodyPtr, sptr, BodyCount);
         }
      }
      else
         BodyCount = 0;

      HostField[0] = '\0';

      if (RedirectionUrl[0] == '/' &&
          (MATCH8 (RedirectionUrl, SchemePtr = SchemeHttp) ||
           MATCH9 (RedirectionUrl, SchemePtr = SchemeHttps) ||
           MATCH7 (RedirectionUrl, SchemePtr = SchemeFtp)))
      {
         /************/
         /* to proxy */
         /************/

         if (WATCHMOD (rqptr, WATCH_MOD_REQUEST))
            WatchThis (WATCHITM(rqptr), WATCH_MOD_REQUEST,
                       "!&Z", RedirectionUrl);

         if (SchemePtr == SchemeHttp)
            SchemeLength = 8;
         else
         if (SchemePtr == SchemeHttps)
            SchemeLength = 9;
         else
            SchemeLength = 7;

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

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

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

            if (WATCHMOD (rqptr, WATCH_MOD_REQUEST))
               WatchDataFormatted ("!UL !UL !&Z !UL !&Z\n",
                  AlphaCount, HostDotCount, rqptr->rqHeader.HostPtr,
                  ServiceDotCount, rqptr->ServicePtr->ServerHostPort);
         }
#endif /* REDIRECT_WILDCARD */

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

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

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

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

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

            /* reserve space in redirection dictionary */
            length = UrlLength + 256;
            denptr = DictInsert (dicptr, DICT_TYPE_INTERNAL,
                                 "request_line", 12, NULL, length); 

            /* build request line directly in dictionary entry */
            RequestLinePtr = DICT_GET_VALUE(denptr);
            zptr = (sptr = RequestLinePtr) + length;
            STRCAT (HttpMethodNamePtr)
            CHRCAT (' ')
            STRCAT (SchemePtr)
            STRCAT (HostField)
            STRCAT (PathPtr)
         }
         else
#endif /* REDIRECT_WILDCARD */
         {
            /**********************/
            /* one-shot DNS proxy */
            /**********************/

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

            /* reserve space in redirection dictionary */
            length = UrlLength + 256;
            denptr = DictInsert (dicptr, DICT_TYPE_INTERNAL,
                                 "request_line", 12, NULL, length); 

            /* build request line directly in dictionary entry */
            RequestLinePtr = DICT_GET_VALUE(denptr);
            zptr = (sptr = RequestLinePtr) + length;
            STRCAT (HttpMethodNamePtr)
            CHRCAT (' ')
            STRCAT (RedirectionUrl+1)
         }
      }
      else
      {
         /****************/
         /* not to proxy */
         /****************/

         /* reserve space in redirection dictionary */
         length = UrlLength + 256;
         denptr = DictInsert (dicptr, DICT_TYPE_INTERNAL,
                              "request_line", 12, NULL, length); 

         /* build request line directly in dictionary entry */
         RequestLinePtr = DICT_GET_VALUE(denptr);
         zptr = (sptr = RequestLinePtr) + length;
         STRCAT (HttpMethodNamePtr)
         CHRCAT (' ')
         STRCAT (RedirectionUrl)
      }

      if (IncludeQueryString)
      {
         if (ConcatQueryString) CHRCAT ('&')
         STRCAT (rqptr->rqHeader.QueryStringPtr)
      }

      if (rqptr->rqResponse.HttpVersion == HTTP_VERSION_1_1)
         STRCAT (" HTTP/1.1")
      else
         STRCAT (" HTTP/1.0")

      /* complete the "request_line" dictionary entry */
      RequestLineLength = sptr - RequestLinePtr;
      DictValueLength (denptr, RequestLineLength);

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

         if (TOUP(*cptr) == 'D' && strsame (cptr, "Destination:", 12))
         {
            if (HostField[0])
            {
               /* redirecting to proxy */
               aptr = cptr + 12;
               while (*aptr && *aptr != '/') aptr++;
               if (*aptr && *(aptr+1) == '/')
               {
                  aptr += 2;
                  while (*aptr && *aptr != '/') aptr++;
               }
               /* reserve space in the dictionary then build value */
               length = SchemeLength + strlen(HostField) + strlen(aptr);
               denptr = DictInsert (dicptr, DICT_TYPE_REQUEST,
                                    "destination", 11, NULL, length); 
               zptr = (sptr = DICT_GET_VALUE (denptr)) + length;
               STRCAT (SchemePtr)
               STRCAT (HostField)
               STRCAT (aptr)
               continue;
            }
         }
         else
         if (TOUP(*cptr) == 'H' && strsame (cptr, "Host:", 5))
         {
            if (HostField[0])
            {
               /* redirecting to proxy */
               DictInsert (dicptr, DICT_TYPE_REQUEST,
                           "host", 4, HostField, -1); 
               continue;
            }
         }
/** TODO MGD 30-AUG-2005 **/
#if 0
         else
         if (TOUP(*cptr) == 'R' && strsame (cptr, "Referer:", 8))
         {
            /* too complex massaging "Referer:" when redirecting to proxy */
            if (HostField[0]) continue;
         }
#endif
         /* otherwise just include the field */
         DictInsert (dicptr, DICT_TYPE_REQUEST,
                     DICT_GET_KEY(denptr), DICT_GET_KEY_LEN(denptr),
                     DICT_GET_VALUE(denptr), DICT_GET_VALUE_LEN(denptr)); 
      }

      if (rqptr->rqHeader.HttpVersion == HTTP_VERSION_1_0)
         if (rqptr->PersistentRequest)
            DictInsert (dicptr, DICT_TYPE_REQUEST,
                        "connection", 10, "keep-alive", 10); 

      /* if generated by a CGI "Location:" */
      if (rqptr->rqCgi.HeaderLength)
      {
         /* parse CGI response fields into the dictionary */
         zptr = (cptr = rqptr->rqCgi.HeaderPtr) + rqptr->rqCgi.HeaderLength;
         while (cptr < zptr)
         {
            int  klen, vlen;
            char  *kptr, *vptr;

            for (kptr = cptr; *cptr != ':' && cptr < zptr; cptr++);
            if (cptr >= zptr) break;
            klen = cptr - kptr;
            for (++cptr; ISLWS(*cptr) && cptr < zptr; cptr++);
            if (cptr >= zptr) break;
            for (vptr = cptr; NOTEOL(*cptr) && cptr < zptr; cptr++);
            if (cptr >= zptr) break;
            vlen = cptr - vptr;
            while (ISLWS(*cptr) && cptr < zptr) cptr++;
            DictInsert (dicptr, DICT_TYPE_REQUEST, kptr, klen, vptr, vlen); 
         }
      }

      /*******************/
      /* request restart */
      /*******************/

      if (WATCHING (rqptr, WATCH_INTERNAL))
      {
         WatchThis (WATCHITM(rqptr), WATCH_REQUEST,
                    "REDIRECT dictionary before");
         DictWatch (rqptr->rqDictPtr, NULL, "*");
      }

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

      /* including any directory meta entries from current to new */
      DictIterate (rqptr->rqDictPtr, NULL);
      while (denptr = DictIterate (rqptr->rqDictPtr, DICT_TYPE_CONFIG))
      {
         /* can only be done because the memory belongs to the same request */
         DictExtractEntry (denptr);
         DictInsertEntry (dicptr, denptr);
      }

      /* and any notepad entry */
      if (denptr = rqptr->NotePadDictEntry)
      {
         DictExtractEntry (denptr);
         DictInsertEntry (dicptr, denptr);
      }

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

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

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

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

      /* restore buffered stuff */
      PUT_QUAD_QUAD (BytesRx, rqptr->BytesRx);
      rqptr->rqNet.ReadBufferPtr = ReadBufferPtr;
      rqptr->rqNet.ReadBufferSize = ReadBufferSize;

      if (BodyCount)
      {
         /* restore body supplied with the request header */
         memcpy (rqptr->rqNet.ReadBufferPtr, BodyPtr, BodyCount);
         rqptr->rqHeader.RequestBodyPtr = rqptr->rqNet.ReadBufferPtr;
         rqptr->rqHeader.RequestBodyCount = BodyCount;
         VmFreeFromHeap (rqptr, BodyPtr, FI_LI);
         rqptr->NetIoPtr->ReadCount = BodyCount;
         /* fudge the bytes received */
         PUT_LONG_QUAD (BodyCount, rqptr->BytesRx);
         PUT_LONG_QUAD (BodyCount, rqptr->NetIoPtr->BytesRawRx);
      }

      /* destroy the current request dictionary */
      DictDestroy (rqptr->rqDictPtr);

      /* use the redirect dictionary just built */
      rqptr->rqDictPtr = dicptr;

      /* restore from when the entry was placed in the dictionary */
      rqptr->rqHeader.RequestLinePtr = RequestLinePtr;
      rqptr->rqHeader.RequestLineLength = RequestLineLength;

      if (WATCHING (rqptr, WATCH_INTERNAL))
      {
         WatchThis (WATCHITM(rqptr), WATCH_REQUEST,
                    "REDIRECT dictionary after");
         DictWatch (rqptr->rqDictPtr, NULL, "*");
      }

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

      InstanceGblSecIncrLong (&acptr->RedirectRemoteCount);

      /* there will three occasions the URL is used */
      length = (UrlLength * 3) + (256 * 3);
      aptr = VmGetHeap (rqptr, length);
      zptr = (sptr = aptr) + length;

      /* begin building the textual (body) redirection information */
      STRCAT (HttpStatusCodeText(302));
      STRCAT (" <a href=\"");

      /* bit convoluted but it saves extra copying and stuff */
      UrlPtr = sptr;  /* note the start of the redirection URL */
      if (MATCH3 (RedirectionUrl, "///"))
      {
         /* request scheme and host:port need to be supplied locally */
         STRCAT (rqptr->ServicePtr->RequestSchemeNamePtr)
         STRCAT ("//")
         if (rqptr->rqHeader.HostPtr && rqptr->rqHeader.HostPtr[0])
            STRCAT (rqptr->rqHeader.HostPtr)
         else
            STRCAT (rqptr->ServicePtr->ServerHostPort)
         STRCAT (RedirectionUrl+2)
      }
      else
      if (MATCH3 (RedirectionUrl, "//:"))
      {
         /* request scheme and host needs to be supplied locally */
         STRCAT (rqptr->ServicePtr->RequestSchemeNamePtr)
         STRCAT ("//")
         if (rqptr->rqHeader.HostPtr && rqptr->rqHeader.HostPtr[0])
            for (cptr = rqptr->rqHeader.HostPtr;
                 *cptr && *cptr != ':' && sptr < zptr;
                 *sptr++ = *cptr++);
         else
            STRCAT (rqptr->ServicePtr->ServerHostName);
         STRCAT (RedirectionUrl+2)
      }
      else
      if (MATCH2 (RedirectionUrl, "//"))
      {
         /* request scheme needs to be supplied locally */
         STRCAT (rqptr->ServicePtr->RequestSchemeNamePtr)
         STRCAT (RedirectionUrl)
      }
      else
      if (MATCH8 (RedirectionUrl, "http:///") ||
          MATCH8 (RedirectionUrl, "http://:"))
      {
         /* host needs to be supplied locally */
         STRCAT ("http://")
         if (rqptr->rqHeader.HostPtr && rqptr->rqHeader.HostPtr[0])
            for (cptr = rqptr->rqHeader.HostPtr;
                 *cptr && *cptr != ':' && sptr < zptr;
                 *sptr++ = *cptr++);
         else
            STRCAT (rqptr->ServicePtr->ServerHostName);
         STRCAT (RedirectionUrl+7)
      }
      else
      if (MATCH9 (RedirectionUrl, "https:///") ||
          MATCH9 (RedirectionUrl, "https://:"))
      {
         /* host needs to be supplied locally */
         STRCAT ("https://")
         if (rqptr->rqHeader.HostPtr && rqptr->rqHeader.HostPtr[0])
            for (cptr = rqptr->rqHeader.HostPtr;
                 *cptr && *cptr != ':' && sptr < zptr;
                 *sptr++ = *cptr++);
         else
            STRCAT (rqptr->ServicePtr->ServerHostName)
         STRCAT (RedirectionUrl+8)
      }
      else
      {
         /* redirection contains full URL */
         STRCAT (RedirectionUrl)
      }
      if (IncludeQueryString)
      {
         if (ConcatQueryString) CHRCAT ('&')
         STRCAT (rqptr->rqHeader.QueryStringPtr);
      }
      UrlEndPtr = sptr;  /* note the end of the redirection URL */

      STRCAT ("\">")
      for (cptr = UrlPtr; cptr < UrlEndPtr && sptr < zptr; *sptr++ = *cptr++);
      STRCAT ("</a>\n")
      *sptr = '\0';
      length = sptr - aptr;

      /* now (somewhat shonkily) use remaining buffer for location header */
      sptr++;
      for (cptr = "Location:"; *cptr && sptr < zptr; *sptr++ = *cptr++);
      for (cptr = UrlPtr; cptr < UrlEndPtr && sptr < zptr; *sptr++ = *cptr++);
      *sptr = '\0';

      if (sptr >= zptr)
         ErrorNoticed (rqptr, SS$_BUFFEROVF, ErrorSanityCheck, FI_LI);

      ResponseHeader (rqptr, 302, "text/html", length, NULL, aptr+length+1);

      /* return response to client */
      NetWrite (rqptr, &RequestEnd, aptr, length);

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

#undef STRCAT
#undef CHRCAT
}

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

