/*****************************************************************************
/*
                                 Support.c

Miscellaneous support functions for HTTPd.


VERSION HISTORY
---------------
26-JUL-2018  MGD  SysTrnLnm() moved from dyanamic to static return
23-DEC-2017  MGD  HttpIsoTimeString()
30-OCT-2017  MGD  ThisLongAgo()
04-MAY-2017  MGD  HtmlMetaInfo() MS Edge <meta name="format-detection..> :-{
26-JAN-2016  MGD  ListAdd..() enforce entry data type
13-NOV-2015  MGD  QuadLtQuad() for 32 bit platform
08-JUL-2014  MGD  ConvertUtf8ToEscape() for UTF-8 encoded file name
24-JAN-2010  MGD  8 bit ASCII ConvertToUtf8() and ConvertFromUtf8()
06-JUL-2009  MGD  v10orPrev10()
02-JUN-2009  MGD  UserAtClient()
                  VmsToHttpStatus() RMS$_FLK from 409 to 423
09-JAN-2008  MGD  CompareVmsTime64() accomodate (VAX?) LIB$ RTL issue
                  with pre-V7.3(?) lib$sub_times() returning SS$_IVTIME
31-MAY-2007  MGD  ErrorVmsToHttpStatus()
                  incorporate WebDAV (RFC 2518) response codes
04-JUL-2006  MGD  PercentOf() calculating percentages of unsigned longs
                  QuadPercentOf() calculating percentages of unsigned quads
31-JUL-2005  MGD  ClientHostString() for use in event logging
10-JUL-2005  MGD  ServerSignature() modifies signature if /SOFTWAREid= set
10-JUN-2005  MGD  refine SysDclAst() EXQUOTA reporting
20-JUL-2004  MGD  HTTP/1.1 compliance,
                  refine HttpIfModifiedSince(),
                  add HttpIfUnModifiedSince()
09-APR-2003  MGD  allow ParseNetMask() to accept IP address without a mask
10-JAN-2003  MGD  ServerSignature() use 'HttpdName' and 'HttpdVersion'
13-OCT-2001  MGD  move string functions to STRNG.C module
04-AUG-2001  MGD  support module WATCHing
17-MAY-2001  MGD  move ...Fao() functions to FAO.C
30-APR-2001  MGD  use permanent global section for monitor data
05-APR-2001  MGD  bugfix; ParseNetMask() VLSM mask octet ordering
21-FEB-2001  MGD  modify ParseNetMask() to allow VLSM (variable-length subnet
                  masking, e.g. '131.185.250.0/24')
02-FEB-2001  MGD  add !%I to FaolSAK()
17-JAN-2000  MGD  bugfix; HttpHeaderChallenge()
23-DEC-2000  MGD  GenerateUniqueId() substitute '@' for '_',
                  remove non-DECC strftime() kludge (VAXC no longer supported!)
01-OCT-2000  MGD  move privilege functions here
27-AUG-2000  MGD  add !SL to FaolSAK()
09-AUG-2000  MGD  bugfix; ParseQueryField() string length check
11-JUN-2000  MGD  add ParseNetMask()
08-APR-2000  MGD  add GenerateUniqueId() based on Apache implementation
04-MAR-2000  MGD  add FaolSAK() and it's siblings
                  FaoToBuffer(), FaolToBuffer(), FaoToNet(),
                  remove CopyTohtml(), UrlEncodeString(),
                  improved SearchTextString()
01-JAN-2000  MGD  support ODS-2 and ODS-5,
                  add formatted OPCOM messages
15-OCT-1999  MGD  use common 'HttpdProcess.Pid'
28-AUG-1999  MGD  add strzcpy(), GetSysInfo.VersionInteger()
25-MAY-1999  MGD  reverse the order of the digest and basic challenges
                  (MS IE5 will not authenticate if digest comes first!!)
31-MAR-1999  MGD  bugfix; HttpHeaderChallenge() 'AuthRealmPtr' NULL check
18-JAN-1999  MGD  FaoPrint() and FaoToStdout() for sys$fao()
                  variable-argument string printing,
                  provide proxy accounting logical and zeroing
01-OCT-1998  MGD  HttpHeader() "Content-Type: text/...; charset=...",
                  support SET mapping rules for charset and content-type,
                  UrlEncodeString(),
                  bugfix; NameOfDirectoryFile()
10-AUG-1998  MGD  refined CopyToHtml() slightly,
                  bugfix; TimeSetTimezone() unsigned to signed longs
10-JUL-1998  MGD  a little Y2K insurance
21-JUN-1998  MGD  changed format of request logical
02-APR-1998  MGD  after email about non-conforming date formats back to RFC1123
12-MAR-1998  MGD  added FormatProtection()
08-JAN-1997  MGD  TimeSetGmt() now can use SYS$TIMEZONE_DIFFERENTIAL
05-OCT-1997  MGD  additional list processing functions
28-SEP-1997  MGD  accounting structure has grown beyond 255 bytes
09-AUG-1997  MGD  message database, added SearchTextString() and
                  NameOfDirectoryFile() functions
12-JUL-1997  MGD  EnableSysPrv(), DisableSysPrv()
07-JUL-1997  MGD  attempt to reduce dynamic memory fragmentation within C RTL
                  using HEAP_MIN_CHUNK_SIZE functionality,
                   prevent request logical redefinition if keep-alive timeout 
16-JUN-1997  MGD  generic linked list functions
12-MAR-1997  MGD  HTTP header generation
01-FEB-1997  MGD  HTTPd version 4
01-OCT-1996  MGD  report menu
18-JUN-1996  MGD  bugfix; HTTPD$GMT conversion to VMS delta time TimeSetGmt()
06-APR-1996  MGD  persistent connections ("keep-alive")
01-DEC-1995  MGD  HTTPd version 3.0
27-SEP-1995  MGD  provide GMT functions
20-DEC-1994  MGD  initial development
*/
/*****************************************************************************/

#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 <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

/* VMS related header files */
#include <descrip.h>
#include <jpidef.h>
#include <libdef.h>
#ifndef __VAX
#include <libicb.h>
#endif
#include <libdtdef.h>
#include <libclidef.h>
#include <lnmdef.h>
#include <opcdef.h>
#include <prvdef.h>
#include <psldef.h>
#include <rms.h>
#include <ssdef.h>
#include <stsdef.h>

#ifndef __VAX
/* lib$cvts_from_internal_time() does not exist with V7.3-2 or earlier */
#include <cvtdef.h>
#include <cvt$routines.h>
#define CVT$K_VAX_F 0
#define CVT$K_IEEE_S 4
#endif

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

#define WASD_MODULE "SUPPORT"

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

#define RFC_1123_DATE yup

BOOL  TimeAheadOfGmt;

ulong  TimeGmtDeltaBinary [QUAD2];

char  TimeGmtString [8],
      TimeGmtVmsString [8],
      TimeZoneString [8];

char  *DayName [] =
   { "", "Monday", "Tuesday", "Wednesday",
     "Thursday", "Friday", "Saturday", "Sunday" };

char  *MonthName [] =
   { "", "January", "February", "March", "April", "May", "June",
         "July", "August", "September", "October", "November", "December" };

/* these arrays are used with the TOUP() and TOLO() macros defined in WASD.H */

int  ToUpperCase [] =
{  0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11, 12, 13, 14, 15,
  16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 
  32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47,
  48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63,
  64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79,
  80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95,
  96, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79,
  80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90,123,124,125,126,127,
 128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143,
 144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159,
 160,161,162,163,164,165,166,167,168,169,170,171,172,173,174,175,
 176,177,178,179,180,181,182,183,184,185,186,187,188,189,190,191,
 192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,
 208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,
 224,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,
 240,241,242,243,244,245,246,247,248,249,250,251,252,253,254,255
};  

int  ToLowerCase [] =
{  0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11, 12, 13, 14, 15,
  16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 
  32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47,
  48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63,
  64, 97, 98, 99,100,101,102,103,104,105,106,107,108,109,110,111,
 112,113,114,115,116,117,118,119,120,121,122, 91, 92, 93, 94, 95,
  96, 97, 98, 99,100,101,102,103,104,105,106,107,108,109,110,111,
 112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,
 128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143,
 144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159,
 160,161,162,163,164,165,166,167,168,169,170,171,172,173,174,175,
 176,177,178,179,180,181,182,183,184,185,186,187,188,189,190,191,
 192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,
 208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,
 224,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,
 240,241,242,243,244,245,246,247,248,249,250,251,252,253,254,255
};  

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

extern BOOL  AccountingZeroOnStartup,
             CliSoftwareID,
             MonitorEnabled;

extern int  CharsetCount,
            EfnWait,
            ServerPort;

extern ulong  SysPrvMask[];

extern char  ErrorSanityCheck[],
             HttpdName[],
             HttpdVersion[],
             ServerHostName[],
             SoftwareID[];

extern ACCOUNTING_STRUCT  *AccountingPtr;
extern CONFIG_STRUCT  Config;
extern HTTPD_GBLSEC  *HttpdGblSecPtr;
extern HTTPD_PROCESS  HttpdProcess;
extern MSG_STRUCT  Msgs;
#ifdef __VAX
extern SYS_INFO  SysInfo;
#endif
extern WATCH_STRUCT  Watch;

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

#ifdef __x86_64

/* interim functionality - tracking down issue with gethostname() */
void x86ASTs (char *fi, int li)
{
   static ulong  JpiAstEn;
   static struct
   {
      ushort  buf_len;
      ushort  item;
      uchar   *buf_addr;
      ushort  *short_ret_len;
   }
      JpiItems [] =
   {
      { sizeof(JpiAstEn),  JPI$_ASTEN,  &JpiAstEn, 0 },
      { 0,0,0,0 }
   };

   int  status;
   IO_SB  IOsb;

   status = sys$getjpi (0, 0, 0, &JpiItems, &IOsb, 0, 0);
   if (VMSok (status)) status = IOsb.Status;
   if (VMSok (status))
      printf ("K:%d E:%d S:%d U:%d %s:%d\n",
              JpiAstEn & 1, JpiAstEn & 2, JpiAstEn & 4, JpiAstEn & 8, fi, li); 
   else
      printf ("%%X%08.08X %s:%d\n", status, fi, li);
}

#endif /* __x86_64 */

/*****************************************************************************/
/*
Declare an AST, exit if there is any problem ... must have our ASTs!
*/ 

void SysDclAst
(
void *Address,
ulong Parameter
)
{
   int  status;

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

   if (0 && WATCH_MODULE(WATCH_MOD__OTHER))
      WatchThis (WATCHALL, WATCH_MOD__OTHER,
                 "SysDclAst() !&A(!&X)", Address, Parameter);

   status = sys$dclast (Address, Parameter, 0);
   if (VMSok (status)) return;

   if (status == SS$_EXQUOTA)
   {
      /* no ASTs means not much of anything else can happen so just exit! */
      sys$canexh(0);
      /* make the message a little more meaningful */
      sys$exit (SS$_EXASTLM);
   }
   else
      ErrorExitVmsStatus (status, "sys$dclast()", FI_LI);
}

/*****************************************************************************/
/*
Generic list handling function.  Add entry to head of list.
*/ 

LIST_ENTRY* ListAddHead
(
LIST_HEAD *lptr,
LIST_ENTRY *eptr,
ulong DataType
)
{
   /*********/
   /* begin */
   /*********/

   if (!lptr->HeadPtr)
   {
      /* empty list */
      lptr->HeadPtr = lptr->TailPtr = eptr;
      eptr->PrevPtr = eptr->NextPtr = NULL;
      if (DataType) eptr->DataType = DataType;
      lptr->EntryCount++;
      return (eptr);
   }
   else
   {
      /* non-empty list */
      eptr->PrevPtr = NULL;
      eptr->NextPtr = lptr->HeadPtr;
      eptr->NextPtr->PrevPtr = lptr->HeadPtr = eptr;
      if (DataType) eptr->DataType = DataType;
      lptr->EntryCount++;
      return (eptr);
   }
}

/*****************************************************************************/
/*
Generic list handling function.  Add entry to tail of list.
*/ 

LIST_ENTRY* ListAddTail
(
LIST_HEAD *lptr,
LIST_ENTRY *eptr,
ulong DataType
)
{
   /*********/
   /* begin */
   /*********/

   if (!lptr->HeadPtr)
   {
      /* empty list */
      lptr->HeadPtr = lptr->TailPtr = eptr;
      eptr->PrevPtr = eptr->NextPtr = NULL;
      if (DataType) eptr->DataType = DataType;
      lptr->EntryCount++;
      return (eptr);
   }
   else
   {
      /* non-empty list */
      eptr->NextPtr = NULL;
      eptr->PrevPtr = lptr->TailPtr;
      eptr->PrevPtr->NextPtr = lptr->TailPtr = eptr;
      if (DataType) eptr->DataType = DataType;
      lptr->EntryCount++;
      return (eptr);
   }
}

/*****************************************************************************/
/*
Generic list handling function.  Add entry 2 "in front of" entry 1.
*/ 

LIST_ENTRY* ListAddBefore
(
LIST_HEAD *lptr,
LIST_ENTRY *e1ptr,
LIST_ENTRY *e2ptr,
ulong DataType
)
{
   /*********/
   /* begin */
   /*********/

   if (lptr->HeadPtr == e1ptr)
   {
      /* at head of list */
      e1ptr->PrevPtr = e2ptr;
      e2ptr->PrevPtr = NULL;
      e2ptr->NextPtr = e1ptr;
      lptr->HeadPtr = e2ptr;
      if (DataType) e2ptr->DataType = DataType;
      lptr->EntryCount++;
      return (e2ptr);
   }
   else
   {
      /* not at head of list */
      if (e1ptr->PrevPtr)
      {
         e2ptr->PrevPtr = e1ptr->PrevPtr;
         e2ptr->PrevPtr->NextPtr = e2ptr;
      }
      e1ptr->PrevPtr = e2ptr;
      e2ptr->NextPtr = e1ptr;
      if (DataType) e2ptr->DataType = DataType;
      lptr->EntryCount++;
      return (e2ptr);
   }
}

/*****************************************************************************/
/*
Generic list handling function.  Remove entry from list.  Check first that
entry looks legitimate, return NULL if not!
*/ 

LIST_ENTRY* ListRemove
(
LIST_HEAD *lptr,
LIST_ENTRY *eptr
)
{
   /*********/
   /* begin */
   /*********/

   if (!eptr->PrevPtr)
   {                                                                        
      /* at head of list */
      if (!eptr->NextPtr)
      {
         /* only entry in list */
         if (lptr->HeadPtr != eptr || lptr->TailPtr != eptr) return (NULL);
         lptr->HeadPtr = lptr->TailPtr = NULL;
         if (lptr->EntryCount) lptr->EntryCount--;
         return (eptr);
      }
      else
      {
         /* remove from head of list */
         if (lptr->HeadPtr != eptr) return (NULL);
         eptr->NextPtr->PrevPtr = NULL;
         lptr->HeadPtr = eptr->NextPtr;
         if (lptr->EntryCount) lptr->EntryCount--;
         return (eptr);
      }
   }
   else
   {
      /* not at head of list */
      if (!eptr->NextPtr)
      {
         /* at tail of list */
         if (lptr->TailPtr != eptr) return (NULL);
         eptr->PrevPtr->NextPtr = NULL;
         lptr->TailPtr = eptr->PrevPtr;
         if (lptr->EntryCount) lptr->EntryCount--;
         return (eptr);
      }
      else
      {
         /* somewhere in the middle! */
         if (eptr->PrevPtr->NextPtr != eptr ||
             eptr->NextPtr->PrevPtr != eptr) return (NULL);
         eptr->PrevPtr->NextPtr = eptr->NextPtr;
         eptr->NextPtr->PrevPtr = eptr->PrevPtr;
         if (lptr->EntryCount) lptr->EntryCount--;
         return (eptr);
      }
   }
}

/*****************************************************************************/
/*
Generic list handling function.  If not already there move it to the head.
*/ 

#ifdef __DECC
#pragma inline(ListMoveHead)
#endif

LIST_ENTRY* ListMoveHead
(
LIST_HEAD *lptr,
LIST_ENTRY *eptr
)
{
   /*********/
   /* begin */
   /*********/

   /* move entry to the head of the cache list */
   if (lptr->HeadPtr != eptr)
   {
      ListRemove (lptr, eptr);
      ListAddHead (lptr, eptr, 0);
   }

   return (eptr);
}

/*****************************************************************************/
/*
Generic list handling function.  If not already there move it to the tail.
*/ 

#ifdef __DECC
#pragma inline(ListMoveTail)
#endif

LIST_ENTRY* ListMoveTail
(
LIST_HEAD *lptr,
LIST_ENTRY *eptr
)
{
   /*********/
   /* begin */
   /*********/

   /* move entry to the head of the cache list */
   if (lptr->TailPtr != eptr)
   {
      ListRemove (lptr, eptr);
      ListAddTail (lptr, eptr, 0);
   }

   return (eptr);
}

/*****************************************************************************/
/*
Generic list handling function.  Remove the head entry.
*/ 

#ifdef __DECC
#pragma inline(ListRemoveHead)
#endif

LIST_ENTRY* ListRemoveHead (LIST_HEAD *lptr)

{
   LIST_ENTRY  *eptr;

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

   if (eptr = lptr->HeadPtr) ListRemove (lptr, eptr);

   return (eptr);
}

/*****************************************************************************/
/*
Generic list handling function.  Remove the tail entry.
*/ 

#ifdef __DECC
#pragma inline(ListRemoveTail)
#endif

LIST_ENTRY* ListRemoveTail (LIST_HEAD *lptr)

{
   LIST_ENTRY  *eptr;

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

   if (eptr = lptr->TailPtr) ListRemove (lptr, eptr);

   return (eptr);
}

/*****************************************************************************/
/*
Generic list handling function.  Check if a specific entry is in specific list.
*/ 

LIST_ENTRY* ListCheck
(
LIST_HEAD *lptr,
LIST_ENTRY *eptr
)
{
   LIST_ENTRY  *teptr;

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

   fprintf (stdout, "ListCheck() %d %d %d %d\n",
            lptr, lptr->HeadPtr, lptr->TailPtr, lptr->EntryCount);

   for (teptr = lptr->HeadPtr; teptr; teptr = teptr->NextPtr)
      if (teptr == eptr) return (eptr);
   return (NULL);
}

/*****************************************************************************/
/*
list the entries in the list.
*/ 

ListDebug (LIST_HEAD* lptr)

{
   LIST_ENTRY  *eptr;

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

   for (eptr = lptr->HeadPtr; eptr; eptr = eptr->NextPtr)
      fprintf (stdout, "%d <- %d -> %d\n", eptr->PrevPtr, eptr, eptr->NextPtr);
}

/*****************************************************************************/
/*
Generate the appropriate server signature returned the supplied buffer.
*/

char* ServerSignature
(
REQUEST_STRUCT *rqptr,
char *BufferPtr,
int BufferSize
)
{
   int  status,
        ServerPortNumber;
   char  *cptr, *sptr, *zptr,
         *ServerHostNamePtr;
   char  MsgBuffer [256],
         EmailBuffer [256];

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

   if (WATCH_MODULE(WATCH_MOD__OTHER))
      WatchThis (WATCHALL, WATCH_MOD__OTHER, "ServerSignature()");

   if (!Config.cfServer.Signature)
   {
      BufferPtr[0] = '\0';
      return (BufferPtr);
   }

   if (rqptr)
   {
      ServerHostNamePtr = rqptr->ServicePtr->ServerHostName;
      ServerPortNumber = rqptr->ServicePtr->ServerPort;
   }
   else
   {
      ServerHostNamePtr = ServerHostName;
      ServerPortNumber = ServerPort;
   }

   if (Config.cfServer.Signature == CONFIG_SERVER_SIGNATURE_EMAIL &&
       Config.cfServer.AdminEmail[0])
   {
      status = FaoToBuffer (EmailBuffer, sizeof(EmailBuffer), NULL,
                            "<a href=\"mailto:!AZ\">!AZ</a>",
                            Config.cfServer.AdminEmail, ServerHostNamePtr);
      if (VMSnok (status)) ErrorNoticed (rqptr, status, NULL, FI_LI);
   }
   else
      EmailBuffer[0] = '\0';

   if (CliSoftwareID)
   {
      cptr = MsgFor(rqptr, MSG_STATUS_SIGNATURE);
      zptr = (sptr = MsgBuffer) + sizeof(MsgBuffer)-1;
      while (*cptr && *cptr != '!' && sptr < zptr) *sptr++ = *cptr++;
      if (strsame (cptr, "!AZ/", 4))
      {
         cptr += 4;
         while (*cptr && sptr < zptr) *sptr++ = *cptr++;
         *sptr = '\0';
         status = FaoToBuffer (BufferPtr, BufferSize, NULL, MsgBuffer,
                               SoftwareID,
                               EmailBuffer[0] ? EmailBuffer : ServerHostNamePtr,
                               ServerPortNumber);
         if (VMSnok (status)) ErrorNoticed (rqptr, status, NULL, FI_LI);
         return (BufferPtr);
      }
      ErrorNoticed (rqptr, SS$_BUGCHECK, ErrorSanityCheck, FI_LI);
   }

   status = FaoToBuffer (BufferPtr, BufferSize, NULL,
                         MsgFor(rqptr, MSG_STATUS_SIGNATURE),
                         HttpdName, HttpdVersion,
                         EmailBuffer[0] ? EmailBuffer : ServerHostNamePtr,
                         ServerPortNumber);
   if (VMSnok (status)) ErrorNoticed (rqptr, status, NULL, FI_LI);

   return (BufferPtr);
}
   
/*****************************************************************************/
/*
Create an HTTP format local time string in the storage pointed at by
'TimeString', e.g. "Fri, 25 Aug 1995 17:32:40" (RFC1123). If 'Time64Ptr' is
null the time string represents the current time. If it points to a quadword,
VMS time value the string represents that time. 'TimeString' must point to
storage large enough for 31 characters.
*/

int HttpLocalTimeString
(
char *TimeString,
ulong *Time64Ptr
)
{
   /* alternate formats for time string */
#ifdef RFC_1123_DATE
   /* day, dd mmm yyyy hh:mm */
   static $DESCRIPTOR (HttpTimeFaoDsc, "!3AZ, !2ZW !3AZ !4ZW !2ZW:!2ZW:!2ZW");
#else
   /* weekday, dd-mmm-yyyy hh:mm */
   static $DESCRIPTOR (HttpTimeFaoDsc, "!3AZ, !2ZW-!3AZ-!4ZW !2ZW:!2ZW:!2ZW");
#endif

   static $DESCRIPTOR (TimeStringDsc, "");

   int  status;
   ulong  Time64 [QUAD2];
   ushort  Length;
   ushort  NumTime [7];
   ulong  DayOfWeek;

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

   if (WATCH_MODULE(WATCH_MOD__OTHER))
      WatchThis (WATCHALL, WATCH_MOD__OTHER, "HttpLocalTimeString()");

   if (Time64Ptr)
      PUT_QUAD_QUAD (Time64Ptr, Time64)
   else
      sys$gettim (&Time64);

   status = sys$numtim (&NumTime, &Time64);

   if (VMSnok (status = lib$day_of_week (&Time64, &DayOfWeek)))
      return (status);

   /* set the descriptor address and size of the resultant time string */
   TimeStringDsc.dsc$w_length = 25;
   TimeStringDsc.dsc$a_pointer = TimeString;

   status = sys$fao (&HttpTimeFaoDsc, &Length, &TimeStringDsc,
                     DayName[DayOfWeek], NumTime[2], MonthName[NumTime[1]],
                     NumTime[0], NumTime[3], NumTime[4], NumTime[5]);
   if (VMSnok (status) || status == SS$_BUFFEROVF)
      TimeString[0] = '\0';
   else
      TimeString[Length] = '\0';

   return (status);
}

/*****************************************************************************/
/*
Create an HTTP format Greenwich Mean Time (UTC) time string in the storage 
pointed at by 'TimeString', e.g. "Fri, 25 Aug 1995 17:32:40 GMT" (RFC1123). 
This must be at least 30 characters capacity.  If 'Time64Ptr' is NULL the 
time string represents the current time.  If it points to a quadword, VMS time 
value the string represents that time.
*/

int HttpGmTimeString
(
char *TimeString,
ulong *Time64Ptr
)
{
   /* alternate formats for time string */
#ifdef RFC_1123_DATE
   /* day, dd mmm yyyy hh:mm */
   static $DESCRIPTOR (HttpTimeFaoDsc,
          "!3AZ, !2ZW !3AZ !4ZW !2ZW:!2ZW:!2ZW GMT");
#else
   /* weekday, dd-mmm-yyyy hh:mm */
   static $DESCRIPTOR (HttpTimeFaoDsc,
          "!3AZ, !2ZW-!3AZ-!4ZW !2ZW:!2ZW:!2ZW GMT");
#endif

   static $DESCRIPTOR (TimeStringDsc, "");

   int  status;
   ulong  Time64 [QUAD2];
   ushort  Length;
   ushort  NumTime [7];
   ulong  DayOfWeek;

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

   if (WATCH_MODULE(WATCH_MOD__OTHER))
      WatchThis (WATCHALL, WATCH_MOD__OTHER, "HttpGmTimeString()");

   if (Time64Ptr)
      PUT_QUAD_QUAD (Time64Ptr, Time64)
   else
      sys$gettim (&Time64);

   if (VMSnok (status = TimeAdjustGMT (true, &Time64)))
      return (status);

   status = sys$numtim (&NumTime, &Time64);

   if (VMSnok (status = lib$day_of_week (&Time64, &DayOfWeek)))
      return (status);

   /* set the descriptor address and size of the resultant time string */
   TimeStringDsc.dsc$w_length = 29;
   TimeStringDsc.dsc$a_pointer = TimeString;

   status = sys$fao (&HttpTimeFaoDsc, &Length, &TimeStringDsc,
                     DayName[DayOfWeek], NumTime[2], MonthName[NumTime[1]],
                     NumTime[0], NumTime[3], NumTime[4], NumTime[5]);
   if (VMSnok (status) || status == SS$_BUFFEROVF)
      TimeString[0] = '\0';
   else
      TimeString[Length] = '\0';

   return (status);
}

/*****************************************************************************/
/*
Create an ISO 8601 format UTC (GMT) basic time string in the storage pointed at
by 'TimeString', e.g. "19950825T173240.34Z+0000", or separated local time
string, e.g. "1995-08-25T17:32:40.34Z+0930".  This must be at least 32 
characters capacity.  If 'Time64Ptr' is NULL the time string represents the
current time.  If it points to a quadword, VMS time value the string represents
that time.
*/

int HttpIsoTimeString
(
BOOL LocalTimeZone,
char *TimeString,
ulong *Time64Ptr
)
{
   static $DESCRIPTOR (UtcTimeFaoDsc,
          "!4ZL!2ZL!2ZLT!2ZL!2ZL!2ZL.!2ZL+0000");
   static $DESCRIPTOR (LocalTimeFaoDsc,
          "!4ZL-!2ZL-!2ZLT!2ZL:!2ZL:!2ZL.!2ZL!AZ");
   static $DESCRIPTOR (TimeStringDsc, "");

   int  status;
   ulong  Time64 [QUAD2];
   ushort  Length;
   ushort  NumTime [7];

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

   if (WATCH_MODULE(WATCH_MOD__OTHER))
      WatchThis (WATCHALL, WATCH_MOD__OTHER, "HttpIsoTimeString()");

   if (Time64Ptr)
      PUT_QUAD_QUAD (Time64Ptr, Time64)
   else
      sys$gettim (&Time64);

   if (!LocalTimeZone)
      if (VMSnok (status = TimeAdjustGMT (true, &Time64)))
         return (status);

   status = sys$numtim (&NumTime, &Time64);

   /* set the descriptor address and size of the resultant time string */
   TimeStringDsc.dsc$w_length = 31;
   TimeStringDsc.dsc$a_pointer = TimeString;

   if (LocalTimeZone)
      status = sys$fao (&LocalTimeFaoDsc, &Length, &TimeStringDsc,
                        NumTime[0], NumTime[1], NumTime[2],
                        NumTime[3], NumTime[4], NumTime[5], NumTime[6],
                        TimeZoneString);
   else
      status = sys$fao (&UtcTimeFaoDsc, &Length, &TimeStringDsc,
                        NumTime[0], NumTime[1], NumTime[2],
                        NumTime[3], NumTime[4], NumTime[5], NumTime[6]);
   if (VMSnok (status) || status == SS$_BUFFEROVF)
      TimeString[0] = '\0';
   else
      TimeString[Length] = '\0';

   return (status);
}

/*****************************************************************************/
/*
Given a string such as "Fri, 25 Aug 1995 17:32:40 GMT" (RFC1123), or "Friday,
25-Aug-1995 17:32:40 GMT" (RFC 1036), create an internal, local, binary time
with the current GMT offset.  See complementary function HttpGmTimeString().
*/ 

int HttpGmTime
(
char *TimeString,
ulong *Time64Ptr
)
{
   static char  *MonthName [] =
      { "", "Jan", "Feb", "Mar", "Apr", "May", "Jun",
            "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" };

   int  status;
   ushort  Length;
   ushort  NumTime [7] = { 0,0,0,0,0,0,0 };
   ulong  DayOfWeek;
   char  *tptr;

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

   tptr = TimeString;
   /* hunt straight for the comma after the weekday name! */
   while (*tptr && *tptr != ',') tptr++;
   if (*tptr) tptr++;
   /* span white space between weekday name and date */
   while (*tptr && ISLWS(*tptr)) tptr++;
   if (!*tptr) return (STS$K_ERROR);

   /* get the date and then skip to month name */
   if (isdigit(*tptr)) NumTime[2] = atoi (tptr);
   while (*tptr && isdigit(*tptr)) tptr++;
   while (*tptr && (*tptr == '-' || ISLWS(*tptr))) tptr++;
   if (!*tptr) return (STS$K_ERROR);

   /* get the month number from the name and skip to the year */
   for (NumTime[1] = 1; NumTime[1] <= 12; NumTime[1]++)
      if (strsame (tptr, MonthName[NumTime[1]], 3)) break;
   if (NumTime[1] > 12) return (STS$K_ERROR);
   while (isalpha(*tptr)) tptr++;
   while (*tptr && (*tptr == '-' || ISLWS(*tptr))) tptr++;
   if (!*tptr) return (STS$K_ERROR);

   /* get the year and then skip to the hour */
   if (isdigit(*tptr))
   {
      NumTime[0] = atoi (tptr);
      if (NumTime[0] <= 99)
      {
         /* for older browsers supplying 2 digit years, some Y2K insurance! */
         if (NumTime[0] >= 70)
            NumTime[0] += 1900;
         else
            NumTime[0] += 2000;
      }
   }
   while (*tptr && isdigit(*tptr)) tptr++;
   while (*tptr && ISLWS(*tptr)) tptr++;
   if (!*tptr) return (STS$K_ERROR);

   /* get the hour, minute and second */
   if (isdigit(*tptr)) NumTime[3] = atoi (tptr);
   while (*tptr && isdigit(*tptr)) tptr++;
   if (*tptr == ':') tptr++;
   if (isdigit(*tptr)) NumTime[4] = atoi (tptr);
   while (*tptr && isdigit(*tptr)) tptr++;
   if (*tptr == ':') tptr++;
   if (isdigit(*tptr)) NumTime[5] = atoi (tptr);
   while (*tptr && isdigit(*tptr)) tptr++;
   if (*tptr == ':') tptr++;
   if (!*tptr) return (STS$K_ERROR);

   /* the only thing remaining should be the "GMT" */
   while (*tptr && ISLWS(*tptr)) tptr++;
   if (!MATCH3 (tptr, "GMT")) return (STS$K_ERROR);

   /*******************************************/
   /* convert what looks like legitimate GMT! */
   /*******************************************/

   status = lib$cvt_vectim (&NumTime, Time64Ptr);
   if (VMSnok (status)) return (status);

   return (TimeAdjustGMT (false, Time64Ptr));
}

/*****************************************************************************/
/*
Determine the offset from GMT (UTC) using either the WASD_GMT or HTTPD$GMT or
SYS$TIMEZONE_DIFFERENTIAL logicals. If WASD_GMT and HTTPD$GMT is not defined
time should be set from the timezone differential logical. Function
RequestBegin() calls this every hour to recheck GMT offset and detect daylight
saving or other timezone changes.
*/

int TimeSetGMT ()

{
   static BOOL  UseTimezoneDifferential = false;

   int  status;

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

   if (!UseTimezoneDifferential)
   {
      status = TimeSetHttpdGmt();
      /* return if error and that error was not that the name did not exist */
      if (VMSok (status) || status != SS$_NOLOGNAM) return (status);
   }

   UseTimezoneDifferential = true;
   return (TimeSetTimezone());
}

/*****************************************************************************/
/*
The SYS$TIMEZONE_DIFFERENTIAL logical contains the number of seconds offset
from GMT (UTC) as a positive (ahead) or negative (behind) number.  Set the
'TimeGmtString' global storage to a "+hh:mm" or "-hh:mm" string equivalent, and
the 'TimeAheadOfGmt' global boolean and 'TimeGmtVmsString' delta-time global
string.
*/

int TimeSetTimezone ()

{
   static ushort  Length;
   static $DESCRIPTOR (TimezoneLogicalNameDsc, "SYS$TIMEZONE_DIFFERENTIAL");
   static $DESCRIPTOR (LnmSystemDsc, "LNM$SYSTEM");
   static $DESCRIPTOR (TimeGmtStringFaoDsc, "!AZ!2ZL:!2ZL");
   static $DESCRIPTOR (TimeGmtVmsStringFaoDsc, "0 !2ZL:!2ZL");
   static $DESCRIPTOR (TimeZoneStringFaoDsc, "!AZ!2ZL!2ZL");
   static VMS_ITEM_LIST3  LnmItems [] =
   {
      { sizeof(TimeGmtString)-1, LNM$_STRING, TimeGmtString, &Length },
      { 0,0,0,0 }
   };

   int  status;
   long  Hours,
         Minutes,
         Seconds;
   char  *SignPtr;
   $DESCRIPTOR (TimeGmtStringDsc, TimeGmtString);
   $DESCRIPTOR (TimeGmtVmsStringDsc, TimeGmtVmsString);
   $DESCRIPTOR (TimeZoneStringDsc, TimeZoneString);

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

   status = sys$trnlnm (0, &LnmSystemDsc, &TimezoneLogicalNameDsc, 0, &LnmItems);
   if (VMSnok (status)) return (status);

   TimeGmtString[Length] = '\0';
   Seconds = atol(TimeGmtString);
   if (Seconds < 0)
   {
      TimeAheadOfGmt = false;
      Seconds = -Seconds;
      SignPtr = "-";
   }
   else
   {
      TimeAheadOfGmt = true;
      SignPtr = "+";
   }
   Hours = Seconds / 3600;
   Minutes = (Seconds - Hours * 3600) / 60;

   sys$fao (&TimeGmtStringFaoDsc, &Length, &TimeGmtStringDsc,
            SignPtr, Hours, Minutes);
   TimeGmtString[Length] = '\0';

   sys$fao (&TimeGmtVmsStringFaoDsc, &Length, &TimeGmtVmsStringDsc,
            Hours, Minutes);
   TimeGmtVmsString[Length] = '\0';

   sys$fao (&TimeZoneStringFaoDsc, &Length, &TimeZoneStringDsc,
            SignPtr, Hours, Minutes);
   TimeZoneString[Length] = '\0';

   TimeGmtVmsStringDsc.dsc$w_length = Length;

   if (VMSnok (status =
       sys$bintim (&TimeGmtVmsStringDsc, &TimeGmtDeltaBinary)))
      return (status);

   if (QUAD_NOT_ZERO(TimeGmtDeltaBinary))
      return (status);

   /* time must have been zero, make it one, one-hundreth of a second */
   TimeGmtDeltaBinary[0] = -100000;
   TimeGmtDeltaBinary[1] = -1;
   return (SS$_NORMAL);
}

/*****************************************************************************/
/*
Translate the logical WASD_GMT or HTTPD$GMT (defined to be something like
"+10:30" or "-01:15") and convert it into a delta time structure and store in 
'TimeGmtDeltaBinary'.  Store whether it is in advance or behind GMT in boolean 
'TimeAheadOfGmt'.  Store the logical string in 'TimeGmtString'.
*/

int TimeSetHttpdGmt ()

{
   static ushort  Length;
   static $DESCRIPTOR (WasdLogNameDsc, "WASD_GMT");
   static $DESCRIPTOR (HttpdLogNameDsc, "HTTPD$GMT");
   static $DESCRIPTOR (LnmFileDevDsc, "LNM$FILE_DEV");
   static VMS_ITEM_LIST3  LnmItems [] =
   {
      { sizeof(TimeGmtString)-1, LNM$_STRING, TimeGmtString, &Length },
      { 0,0,0,0 }
   };

   int  status;
   char  *cptr, *sptr;
   $DESCRIPTOR (TimeGmtVmsStringDsc, TimeGmtVmsString);

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

   status = sys$trnlnm (0, &LnmFileDevDsc, &WasdLogNameDsc, 0, &LnmItems);
   if (VMSnok (status))
   {
      status = sys$trnlnm (0, &LnmFileDevDsc, &HttpdLogNameDsc, 0, &LnmItems);
      if (VMSnok (status)) return (status);
   }

   TimeGmtString[Length] = '\0';

   if (TimeGmtString[0] == '$') return (SS$_NORMAL);

   if (*(cptr = TimeGmtString) == '-')
      TimeAheadOfGmt = false;
   else
      TimeAheadOfGmt = true;
   if (*cptr == '+' || *cptr == '-') cptr++;
   sptr = TimeGmtVmsString;
   *sptr++ = '0';
   *sptr++ = ' ';
   while (*cptr) *sptr++ = *cptr++;
   *sptr = '\0';

   TimeGmtVmsStringDsc.dsc$w_length = sptr - TimeGmtVmsString;

   if (VMSnok (status =
       sys$bintim (&TimeGmtVmsStringDsc, &TimeGmtDeltaBinary)))
      return (status);

   if (QUAD_NOT_ZERO(TimeGmtDeltaBinary))
      return (status);

   /* time must have been zero, make it one, one-hundreth of a second */
   TimeGmtDeltaBinary[0] = -100000;
   TimeGmtDeltaBinary[1] = -1;
   return (SS$_NORMAL);
}

/*****************************************************************************/
/*
The GMT is generated by calculating using an offset using 
'TimeGmtDeltaOffset' and boolean 'TimeAheadOfGmt'.  Adjust either to or from 
GMT.
*/ 

int TimeAdjustGMT
(
BOOL ToGmTime,
ulong *Time64Ptr
)
{
   int  status;
   ulong  AdjustedTime [QUAD2];

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

   if ((ToGmTime && TimeAheadOfGmt) || (!ToGmTime && !TimeAheadOfGmt))
   {
      /* to GMT from local and ahead of GMT, or to local from GMT and behind */
      status = lib$sub_times (Time64Ptr, &TimeGmtDeltaBinary, &AdjustedTime);
   }
   else
   {
      /* to GMT from local and behind GMT, or to local from GMT and ahead */
      status = lib$add_times (Time64Ptr, &TimeGmtDeltaBinary, &AdjustedTime);
   }

   PUT_QUAD_QUAD (AdjustedTime, Time64Ptr);

   return (status);
}

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

char* DigitDayTime (ulong *Time64Ptr)

{
   static char  TimeString [16];
   static $DESCRIPTOR (TimeFaoDsc, "!2ZL !2ZL:!2ZL:!2ZL\0");
   static $DESCRIPTOR (TimeStringDsc, TimeString);

   int  status;
   ulong  Time64[QUAD2];
   ushort  NumTime [7];

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

   if (!Time64Ptr) sys$gettim (Time64Ptr = &Time64);
   if (VMSnok (status = sys$numtim (&NumTime, Time64Ptr)))
      return ("*ERROR*");
   if (VMSnok (sys$fao (&TimeFaoDsc, 0, &TimeStringDsc,
               NumTime[2], NumTime[3], NumTime[4], NumTime[5])))
      return ("*ERROR*");
   return (TimeString);
}

/*****************************************************************************/
/*
Return a pointer to the name of the day of the week for the supplied binary
time.  If a NULL pointer to the binary time then return current day of week.
*/

char* DayOfWeekName (ulong *Time64Ptr)

{
   int  status;
   ulong  DayOfWeek;
   ulong  Time64[QUAD2];

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

   if (!Time64Ptr) sys$gettim (Time64Ptr = &Time64);
   if (VMSnok (status = lib$day_of_week (Time64Ptr, &DayOfWeek)))
      DayOfWeek = 0;
   return (DayName[DayOfWeek]);
}

/*****************************************************************************/
/*
HTTP/1.0 and HTTP/1.1 requests.  If the content length has changed (HTTP/1.0 de
facto standard), or if it has been modified since the specified date and time
then return a normal status indicating that the object has been modified.  If
not modified then create a 304 HTTP header and return a LIB$_NEGTIM status to
indicate the file should not be sent.  With VMS' fractions of a second, add one
second to the specified 'since' time to ensure a reliable comparison.

Returns NORMAL if modified, NEGTIM if not modified.

Implements defacto HTTP/1.0 persistent connections.  Currently we only provide
"keep-alive" response if we're sending a file in binary mode and know its
precise length.  An accurate "Content-Length:" field is vital for the client's
correct reception of the transmitted data.  The only other time a "keep-alive"
response can be provided is HERE, where a 304 header is returned (it has a
content length of precisely zero!)
*/ 
 
int HttpIfModifiedSince
(
REQUEST_STRUCT *rqptr,
ulong *RdtTime64Ptr,
int LastContentLength
)
{
   static ulong  OneSecondDelta [QUAD2] = { -10000000, -1 };

   int  status;
   ulong  AdjustedTime64[QUAD2],
          ScratchTime64 [QUAD2];

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

   if (WATCH_MODULE(WATCH_MOD__OTHER))
      WatchThis (WATCHALL, WATCH_MOD__OTHER,
                 "HttpIfModifiedSince() !UL !UL !%D",
                 LastContentLength, rqptr->rqHeader.IfModifiedSinceLength,
                 &rqptr->rqTime.IfModifiedSinceTime64[0]);

   if (QUAD_ZERO(rqptr->rqTime.IfModifiedSinceTime64))
      return (SS$_NORMAL);

   if (rqptr->rqHeader.HttpVersion == HTTP_VERSION_1_0 &&
       rqptr->rqHeader.IfModifiedSinceLength >= 0 &&
       LastContentLength >= 0 &&
       rqptr->rqHeader.IfModifiedSinceLength != LastContentLength)
      return (SS$_NORMAL);

   /* add one second to the modified time, ensures a negative time */
   status = lib$add_times (&rqptr->rqTime.IfModifiedSinceTime64,
                           &OneSecondDelta, &AdjustedTime64);
   if (VMSnok (status))
   {
      rqptr->rqResponse.ErrorTextPtr = "lib$add_times()";
      ErrorVmsStatus (rqptr, status, FI_LI);
      return (status);
   }

   if (WATCH_MODULE(WATCH_MOD__OTHER))
      WatchThis (WATCHALL, WATCH_MOD__OTHER, "!%D", &AdjustedTime64);

   /* a positive time results the file has been modified */
   status = lib$sub_times (RdtTime64Ptr, &AdjustedTime64, &ScratchTime64);

   /* modified */
   if (VMSok (status)) return (SS$_NORMAL);

   if (status == LIB$_NEGTIM)
   {
      /* not modified (note the zero content length!) */
      ResponseHeader (rqptr, 304, NULL, 0, NULL, NULL);
      return (LIB$_NEGTIM);
   }

   /* woops */
   rqptr->rqResponse.ErrorTextPtr = "lib$sub_times()";
   ErrorVmsStatus (rqptr, status, FI_LI);
   return (status);
}

/*****************************************************************************/
/*
HTTP/1.1 if-not-modified-since request.  If it has NOT been modified since the
specified date and time then return a normal status indicating that the object
has not been modified.  If modified then create a 412 HTTP header (precondition
failed) and return a LIB$_NEGTIM status to indicate the request should not be
processed.  With VMS' fractions of a second, add one second to the specified
'since' time to ensure a reliable comparison.

Returns NORMAL if modified, NEGTIM if not modified.
*/ 
 
int HttpIfUnModifiedSince
(
REQUEST_STRUCT *rqptr,
ulong *RdtTime64Ptr
)
{
   static ulong  OneSecondDelta [QUAD2] = { -10000000, -1 };

   int  status;
   ulong  AdjustedTime64[QUAD2],
          ScratchTime64 [QUAD2];

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

   if (WATCH_MODULE(WATCH_MOD__OTHER))
      WatchThis (WATCHALL, WATCH_MOD__OTHER,
                 "HttpIfUnModifiedSince() !%D",
                 &rqptr->rqTime.IfModifiedSinceTime64);

   if (QUAD_ZERO(rqptr->rqTime.IfUnModifiedSinceTime64))
      return (SS$_NORMAL);

   /* add one second to the modified time, ensures a negative time */
   status = lib$add_times (&rqptr->rqTime.IfModifiedSinceTime64,
                           &OneSecondDelta, &AdjustedTime64);
   if (VMSnok (status))
   {
      rqptr->rqResponse.ErrorTextPtr = "lib$add_times()";
      ErrorVmsStatus (rqptr, status, FI_LI);
      return (status);
   }

   if (WATCH_MODULE(WATCH_MOD__OTHER))
      WatchThis (WATCHALL, WATCH_MOD__OTHER, "!%D", &AdjustedTime64);

   /* a positive time results the file has been modified */
   status = lib$sub_times (RdtTime64Ptr, &AdjustedTime64, &ScratchTime64);

   if (VMSok (status))
   {
      /* modified, precondition failed (note the zero content length!) */
      ResponseHeader (rqptr, 412, NULL, 0, NULL, NULL);
      return (SS$_NORMAL);
   }

   /* if not modified! */
   if (status == LIB$_NEGTIM) return (LIB$_NEGTIM);

   /* woops */
   rqptr->rqResponse.ErrorTextPtr = "lib$sub_times()";
   ErrorVmsStatus (rqptr, status, FI_LI);
   return (status);
}

/*****************************************************************************/
/*
HTTP/1.1 if-range not modified since.

Returns NORMAL if modified, NEGTIM if not modified.
*/ 
 
int HttpIfRange
(
REQUEST_STRUCT *rqptr,
ulong *RdtTime64Ptr
)
{
   static ulong  OneSecondDelta [QUAD2] = { -10000000, -1 };

   int  status;
   ulong  AdjustedTime64[QUAD2],
          ScratchTime64 [QUAD2];

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

   if (WATCH_MODULE(WATCH_MOD__OTHER))
      WatchThis (WATCHALL, WATCH_MOD__OTHER,
                 "HttpIfRange() !%D", &rqptr->rqTime.IfRangeBeginTime64[0]);

   if (QUAD_ZERO(rqptr->rqTime.IfRangeBeginTime64))
      return (SS$_NORMAL);

   /* add one second to the modified time, ensures a negative time */
   status = lib$add_times (&rqptr->rqTime.IfRangeBeginTime64,
                           &OneSecondDelta, &AdjustedTime64);
   if (VMSnok (status))
   {
      rqptr->rqResponse.ErrorTextPtr = "lib$add_times()";
      ErrorVmsStatus (rqptr, status, FI_LI);
      return (status);
   }

   if (WATCH_MODULE(WATCH_MOD__OTHER))
      WatchThis (WATCHALL, WATCH_MOD__OTHER, "!%D", &AdjustedTime64);

   /* a positive time results the file has been modified */
   status = lib$sub_times (RdtTime64Ptr, &AdjustedTime64, &ScratchTime64);

   if (VMSok (status))
   {
      /* modified, cancel any range data */
      if (rqptr->rqHeader.RangeBytePtr) rqptr->rqHeader.RangeBytePtr->Total = 0;
      return (SS$_NORMAL);
   }

   /* if not modified! */
   if (status == LIB$_NEGTIM) return (LIB$_NEGTIM);

   /* woops */
   rqptr->rqResponse.ErrorTextPtr = "lib$sub_times()";
   ErrorVmsStatus (rqptr, status, FI_LI);
   return (status);
}

/*****************************************************************************/
/*
Return a reasonable HTTP status from the supplied VMS status.
*/
 
int VmsToHttpStatus (int VmsStatus) 

{
   int  HttpStatus;

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

   if (VmsStatus == RMS$_DNF ||
       VmsStatus == RMS$_DEV ||
       VmsStatus == RMS$_FNF ||
       VmsStatus == RMS$_NMF ||
       VmsStatus == SS$_NOSUCHDEV ||
       VmsStatus == SS$_NOSUCHFILE)
      HttpStatus = 404;
   else
   if (VmsStatus == RMS$_SYN ||
       VmsStatus == RMS$_FNM ||
       VmsStatus == RMS$_TYP ||
       VmsStatus == RMS$_VER ||
       VmsStatus == RMS$_DIR ||
       VmsStatus == SS$_BADFILENAME ||
       VmsStatus == SS$_BADFILEVER ||
       VmsStatus == SS$_BADIRECTORY)
      HttpStatus = 403;
   else
   if (VmsStatus == RMS$_FLK)
      HttpStatus = 423;
   else
   if (VmsStatus == SS$_DIRNOTEMPTY ||
       VmsStatus == SS$_EXDISKQUOTA)
      HttpStatus = 409;
   else
   if (VmsStatus == RMS$_PRV ||
       VmsStatus == SS$_NOPRIV)
      HttpStatus = 403;
   else
   if (VmsStatus == SS$_CREATED)
      HttpStatus = 201;
   else
   if (VmsStatus & 1)
      HttpStatus = 200;
   else
      HttpStatus = 500;

   if (WATCH_MODULE(WATCH_MOD__OTHER))
      WatchThis (WATCHALL, WATCH_MOD__OTHER,
                 "VmsToHttpStatus() !&S !UL",
                 VmsStatus, HttpStatus);

   return (HttpStatus);
}
 
/*****************************************************************************/
/*
Return the a pointer to abbreviated meaning of the supplied HTTP status code.
These are typical of those included on the response header status line.
*/

char *HttpStatusCodeText (int StatusCode)

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

   switch (StatusCode)
   {
      case 100 : return ("Continue"); /* (HTTP/1.1) */
      case 101 : return ("Switching Protocols"); /* (HTTP/1.1) */
      case 102 : return ("Processing"); /* (WebDAV - RFC2581) */
      case 200 : return ("OK");
      case 201 : return ("Created");
      case 202 : return ("Accepted");
      case 203 : return ("Non-authoritative"); /* (HTTP/1.1) */
      case 204 : return ("No Content");
      case 205 : return ("Reset Content"); /* (HTTP/1.1) */
      case 206 : return ("Partial Content"); /* (HTTP/1.1) */
      case 207 : return ("Multistatus"); /* (WebDAV - RFC2581) */
      case 300 : return ("Multiple Choices"); /* (HTTP/1.1) */
      case 301 : return ("Moved Permanently");
      case 302 : return ("Found (Moved Temporarily)");
      case 303 : return ("See Other"); /* (HTTP/1.1) */
      case 304 : return ("Not Modified");
      case 305 : return ("Use Proxy"); /* (HTTP/1.1) */
      case 306 : return ("[reserved]");
      case 307 : return ("Temporary Redirect"); /* (HTTP/1.1) */
      case 400 : return ("Bad Request");
      case 401 : return ("Authorization Required");
      case 402 : return ("Payment Required"); /* (HTTP/1.1) */
      case 403 : return ("Forbidden");
      case 404 : return ("Not Found");
      case 405 : return ("Method Not Allowed"); /* (HTTP/1.1) */
      case 406 : return ("Not Acceptable"); /* (HTTP/1.1) */
      case 407 : return ("Proxy Authentication Required"); /* (HTTP/1.1) */
      case 408 : return ("Request Timeout"); /* (HTTP/1.1) */
      case 409 : return ("Conflict"); /* (HTTP/1.1) */
      case 410 : return ("Gone"); /* (HTTP/1.1) */
      case 411 : return ("Length Required"); /* (HTTP/1.1) */
      case 412 : return ("Precondition Failed"); /* (HTTP/1.1) */
      case 413 : return ("Request Entity Too Large"); /* (HTTP/1.1) */
      case 414 : return ("Request URI Too Long"); /* (HTTP/1.1) */
      case 415 : return ("Unsupported Media Type"); /* (HTTP/1.1) */
      case 416 : return ("Requested Range Not Satisfiable"); /* (HTTP/1.1) */
      case 417 : return ("Expectation Failed"); /* (HTTP/1.1) */
      case 418 : return ("I'm a teapot"); /* (RFC2324) */
      case 421 : return ("Misdirected Request"); /* (HTTP/2 - RFC7540) */
      case 422 : return ("Unprocessable Entity"); /* (WebDAV - RFC2581) */
      case 423 : return ("Locked"); /* (WebDAV - RFC2581) */
      case 424 : return ("Failed Dependency"); /* (WebDAV - RFC2581) */
      case 426 : return ("Upgrade Required"); /* (RFC2847) */
      case 500 : return ("Internal Error");
      case 501 : return ("Not Implemented");
      case 502 : return ("Bad Gateway");
      case 503 : return ("Service Unavailable");
      case 504 : return ("Gateway Timeout"); /* (HTTP/1.1) */
      case 505 : return ("HTTP Version Not Supported"); /* (HTTP/1.1) */
      case 507 : return ("Insufficient Storage"); /* (WebDAV - RFC2581) */
      default :  return ("Unknown Code!");
   }
}

/*****************************************************************************/
/*
Return the a pointer an explanation of the supplied HTTP status code.  These
are supplied from the message database can be are used in error messages, etc.
*/

char *HttpStatusCodeExplanation
(
REQUEST_STRUCT *rqptr,
int StatusCode
)
{
   /*********/
   /* begin */
   /*********/

   switch (StatusCode)
   {
      /* continue (HTTP/1.1) */
      case 100 : return (MsgFor(rqptr,MSG_HTTP_100));
      /* switching protocols (HTTP/1.1) */
      case 101 : return (MsgFor(rqptr,MSG_HTTP_101));
      /* processing (WedDAV - RFC2518) */ 
      case 102 : return ("processing");
      /* success */
      case 200 : return (MsgFor(rqptr,MSG_HTTP_200));
      /* created */
      case 201 : return (MsgFor(rqptr,MSG_HTTP_201));
      /* accepted */
      case 202 : return (MsgFor(rqptr,MSG_HTTP_202));
      /* non-authoritative (HTTP/1.1) */
      case 203 : return (MsgFor(rqptr,MSG_HTTP_203));
      /* no content */
      case 204 : return (MsgFor(rqptr,MSG_HTTP_204));
      /* reset content (HTTP/1.1) */
      case 205 : return (MsgFor(rqptr,MSG_HTTP_205));
      /* partial content (HTTP/1.1) */
      case 206 : return (MsgFor(rqptr,MSG_HTTP_206));
      /* multi-status (WedDAV - RFC2518) */ 
      case 207 : return ("multi-status");
      /* multiple choices (HTTP/1.1) */
      case 300 : return (MsgFor(rqptr,MSG_HTTP_300));
      /* moved permanently */
      case 301 : return (MsgFor(rqptr,MSG_HTTP_301));
      /* moved temporarily */
      case 302 : return (MsgFor(rqptr,MSG_HTTP_302));
      /* see other (HTTP/1.1)  */
      case 303 : return (MsgFor(rqptr,MSG_HTTP_303));
      /* not modified */
      case 304 : return (MsgFor(rqptr,MSG_HTTP_304));
      /* use proxy (HTTP/1.1)  */
      case 305 : return (MsgFor(rqptr,MSG_HTTP_305));
      /* reserved (WedDAV - RFC2518) */ 
      case 306 : return ("reserved");
      /* temporary redirect (WedDAV - RFC2518) */ 
      case 307 : return ("temporary redirect");
      /* bad request */
      case 400 : return (MsgFor(rqptr,MSG_HTTP_400));
      /* authorization required */
      case 401 : return (MsgFor(rqptr,MSG_HTTP_401));
      /* payment required */
      case 402 : return (MsgFor(rqptr,MSG_HTTP_402));
      /* forbidden */
      case 403 : return (MsgFor(rqptr,MSG_HTTP_403));
      /* not found */
      case 404 : return (MsgFor(rqptr,MSG_HTTP_404));
      /* method not allowed (HTTP/1.1) */
      case 405 : return (MsgFor(rqptr,MSG_HTTP_405));
      /* not acceptable (HTTP/1.1) */
      case 406 : return (MsgFor(rqptr,MSG_HTTP_406));
      /* proxy authentication required (HTTP/1.1) */
      case 407 : return (MsgFor(rqptr,MSG_HTTP_407));
      /* request timeout (HTTP/1.1) */
      case 408 : return (MsgFor(rqptr,MSG_HTTP_408));
      /* resource conflict (HTTP/1.1) */
      case 409 : return (MsgFor(rqptr,MSG_HTTP_409));
      /* gone (HTTP/1.1) */
      case 410 : return (MsgFor(rqptr,MSG_HTTP_410));
      /* length required (HTTP/1.1) */
      case 411 : return (MsgFor(rqptr,MSG_HTTP_411));
      /* precondition failed (HTTP/1.1) */
      case 412 : return (MsgFor(rqptr,MSG_HTTP_412));
      /* request entity too large (HTTP/1.1) */
      case 413 : return (MsgFor(rqptr,MSG_HTTP_413));
      /* request-URI too long (HTTP/1.1) */
      case 414 : return (MsgFor(rqptr,MSG_HTTP_414));
      /* unsupported media type (HTTP/1.1) */
      case 415 : return (MsgFor(rqptr,MSG_HTTP_415));
      /*  requested range not satisfiable (HTTP/1.1) */
      case 416 : return (MsgFor(rqptr,MSG_HTTP_416));
      /*  expectation failed (HTTP/1.1) */
      case 417 : return (MsgFor(rqptr,MSG_HTTP_417));
      /* misdirected request (HTTP/2 - RFC7540) */
      case 421 : return ("misdirected request");
      /* unprocessable entity (WedDAV - RFC2518) */ 
      case 422 : return ("unprocessable entity");
      /* locked (WedDAV - RFC2518) */ 
      case 423 : return ("locked");
      /* internal error */
      case 500 : return (MsgFor(rqptr,MSG_HTTP_500));
      /* not implemented */
      case 501 : return (MsgFor(rqptr,MSG_HTTP_501));
      /* bad gateway */
      case 502 : return (MsgFor(rqptr,MSG_HTTP_502));
      /* service unavailable */
      case 503 : return (MsgFor(rqptr,MSG_HTTP_503));
      /* gateway timeout (HTTP/1.1) */
      case 504 : return (MsgFor(rqptr,MSG_HTTP_504));
      /* HTTP version not supported (HTTP/1.1) */
      case 505 : return (MsgFor(rqptr,MSG_HTTP_505));
      /* unknown code! */
      default :
         return ("The server is reporting an UNKNOWN status code!");
   }
}

/*****************************************************************************/
/*
Returns 1 if time 1 is later than time 2, 0 if the same, and -1 if time 1 is
earlier than time 2.
*/

int CompareVmsTime64
(
ulong *Time64Ptr1,
ulong *Time64Ptr2
)
{
   int  status;
   ulong  Time64 [QUAD2];

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

   status = lib$sub_times (Time64Ptr1, Time64Ptr2, Time64);
   if (status == LIB$_NEGTIM)
      return (-1);
   else
   if (VMSok (status))
   {
      if (QUAD_EQUAL_QUAD(Time64Ptr1, Time64Ptr2))
         return (0);
      else
         return (1); 
   }
   else
   {
#ifndef __VAX
      ErrorExitVmsStatus (status, "lib$sub_times()", FI_LI);
#else
      if (SysInfo.VersionInteger >= 730)
         ErrorExitVmsStatus (status, "lib$sub_times()", FI_LI);
      else
      if (status == SS$_IVTIME)
      {
         /* at least VAX VMS V6.2 returns IVTIME when others do not */
         ErrorNoticed (NULL, status, "lib$sub_times()", FI_LI);
         return (0);
      }
      else
         ErrorExitVmsStatus (status, "lib$sub_times()", FI_LI);
#endif
   }
}

/*****************************************************************************/
/*
Returns a pointer to a VmGetHeap()ed string of <meta ...> tags for an
internally generated HTML document.
*/

char* HtmlMetaInfo
(
REQUEST_STRUCT *rqptr,
char *VmsInfoPtr
)
{
   int  status;
   ulong  FaoVector [16];
   ulong  *vecptr;
   ushort  Length;
   char  *MetaPtr;
   char  Buffer [2048];

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

   vecptr = &FaoVector;
   *vecptr++ = SoftwareID;
   *vecptr++ = rqptr->rqTime.GmDateTime;

   if (rqptr->RemoteUser[0])
   {
      *vecptr++ = "<meta name=\"author\" content=\"!&;AZ.\'!&;AZ\'@!AZ\">\n";
      *vecptr++ = rqptr->RemoteUser;
      *vecptr++ = rqptr->rqAuth.RealmDescrPtr;
      *vecptr++ = rqptr->ServicePtr->ServerHostPort;
   }
   else
   {
      *vecptr++ = "<meta name=\"host\" content=\"!AZ\">\n";
      *vecptr++ = rqptr->ServicePtr->ServerHostPort;
   }

   if (VmsInfoPtr && VmsInfoPtr[0])
   {
      if (Config.cfReport.MetaInfoEnabled)
      {
         *vecptr++ = "<meta name=\"VMS\" content=\"!&;AZ\">\n!&@";
         *vecptr++ = VmsInfoPtr;
         if (rqptr->PathOds)
         {
            *vecptr++ = "<meta name=\"ODS\" content=\"!AZ\">\n";
            switch (rqptr->PathOds)
            {
               case MAPURL_PATH_ODS_2 : *vecptr++ = "2"; break;
               case MAPURL_PATH_ODS_5 : *vecptr++ = "5"; break;
               case MAPURL_PATH_ODS_ADS : *vecptr++ = "ADS"; break;
               case MAPURL_PATH_ODS_PWK : *vecptr++ = "PWK"; break;
               case MAPURL_PATH_ODS_SMB : *vecptr++ = "SMB"; break;
               case MAPURL_PATH_ODS_SRI : *vecptr++ = "SRI"; break;
               default : *vecptr++ = "?";
            }
         }
         else
            *vecptr++ = "";
      }
      else
         *vecptr++ = "";
   }
   else
      *vecptr++ = "";

   status = FaolToBuffer (Buffer, sizeof(Buffer), &Length,
"<meta name=\"generator\" content=\"!AZ\">\n\
<meta name=\"date\" content=\"!AZ\">\n\
!&@\
!&@\
<meta name=\"format-detection\" content=\"telephone=no\">\n",
      &FaoVector);

   if (VMSnok (status) || status == SS$_BUFFEROVF)
   {
      ErrorNoticed (rqptr, status, NULL, FI_LI);
      return ("<!-- META overflow ERROR -->\n");
   }

   MetaPtr = VmGetHeap (rqptr, Length+1);
   memcpy (MetaPtr, Buffer, Length+1);

   return (MetaPtr);
}

/*****************************************************************************/
/*
Return a string containing a duration string.
Durations are in standard (VMS) 100nS units.
If less than one minute provide as 'seconds.micro[milli]seconds'.
If a minute or greater as '[ddd-]hh.mm.ss'.
*/

char* DurationString
(
REQUEST_STRUCT *rqptr,
ulong *Time64Ptr
)
{
   static $DESCRIPTOR (DateFaoDsc, "!13%D\0");
   static $DESCRIPTOR (TimeFaoDsc, "!8%T\0");
   static $DESCRIPTOR (TimeDsc, "");

   double  dsecs;
   char  *sptr;

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

   if (WATCHMOD (rqptr, WATCH_MOD__OTHER))
      WatchThis (WATCHITM(rqptr), WATCH_MOD__OTHER,
                 "DurationString() !&X !&X !%T",
                 Time64Ptr[0], Time64Ptr[1], Time64Ptr);

   if (rqptr)
      sptr = VmGetHeap (rqptr, 32);
   else
      /* this might leak a bit! */
      sptr = VmGet (32);

   if (!Time64Ptr[1] && !Time64Ptr[0])
      sptr = "0.0";
   else
   /* if less than 60 seconds */
   if ((Time64Ptr[1] == 0xffffffff && 
        Time64Ptr[0] >= 0xdc3cba00) ||
       (!Time64Ptr[0] && !Time64Ptr[1]))
   {
      /* less than one minute, give in seconds and fractions */
      dsecs = (double)(-(long)Time64Ptr[0]);
#ifndef __VAX
      sprintf (sptr, "%.6f", dsecs / 10000000.0);
#else
      sprintf (sptr, "%.3f", dsecs / 10000000.0);
#endif
   }
   else
   if (Time64Ptr[1] > 0xffffff36 || 
       (Time64Ptr[1] == 0xffffff36 && 
        Time64Ptr[0] >= 0xd5960000))
   {
      /* less than twenty-four hours */
      TimeDsc.dsc$a_pointer = sptr;
      TimeDsc.dsc$w_length = 32;
      sys$fao (&TimeFaoDsc, 0, &TimeDsc, Time64Ptr);
   }
   else
   if (Time64Ptr[1] && Time64Ptr[0])
   {
      /* greater than twenty-four hours */
      TimeDsc.dsc$a_pointer = sptr;
      TimeDsc.dsc$w_length = 32;
      sys$fao (&DateFaoDsc, 0, &TimeDsc, Time64Ptr);
      sptr[4] = 160;  /* ISO8859-1 non-breaking space */
      while (*sptr == ' ') sptr++;
   }
   else
      sptr = "0.0";

   return (sptr);
}

/*****************************************************************************/
/*
Return an allocated string containing the average duration.
*/

char* AverageDurationString
(
REQUEST_STRUCT *rqptr,
ulong *Time64Ptr,
ulong Count
)
{
   int  status;
   ulong  Time64 [QUAD2];
   float  fcount;

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

   if (WATCHMOD (rqptr, WATCH_MOD__OTHER))
      WatchThis (WATCHITM(rqptr), WATCH_MOD__OTHER,
                 "AverageDurationString() !&X !&X !%T",
                 Time64Ptr[0], Time64Ptr[1], Time64Ptr);

   PUT_ZERO_QUAD (Time64);
   if (Count)
   {
      PUT_QUAD_QUAD (Time64Ptr, Time64);
      fcount = (float)Count;
      fcount = 1.0 / fcount;

#ifndef __VAX
      /* could use lib$mults_delta_time() only it doesn't exist on V7.3 :-( */
      status = cvt$convert_float (&fcount, CVT$K_IEEE_S,
                                  &fcount, CVT$K_VAX_F, 0);
      if (VMSnok (status))
      {
         ErrorNoticed (rqptr, status, NULL, FI_LI);
         PUT_ZERO_QUAD (Time64);
      }
#endif

      status = lib$multf_delta_time (&fcount, &Time64);
      if (VMSnok (status))
      {
         ErrorNoticed (rqptr, status, NULL, FI_LI);
         PUT_ZERO_QUAD (Time64);
      }
   }
   return (DurationString (rqptr, &Time64));
}

/*****************************************************************************/
/*
Format a string containing how many seconds, minutes, hours, days ago from the
current time.  For example; "15s", "10m", "17h", "152d".  Return VMS status.
|AgoBuf| should be at least 16 chars.
*/

int ThisLongAgo
(
ulong *Time64Ptr,
char *AgoBuf
)
{
   static ulong  LibDeltaSecs = LIB$K_DELTA_SECONDS;
   static $DESCRIPTOR (AgoFaoDsc, "!UL!AZ\0");
   static $DESCRIPTOR (AgoBufDsc, "");

   int  status;
   ulong  SecsAgo;
   ulong  AgoTime64 [2],
          NowTime64 [2];

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

   if (WATCH_MODULE(WATCH_MOD__OTHER))
      WatchThis (WATCHALL, WATCH_MOD__OTHER, "ThisLongAgo() !8XL", Time64Ptr);

   *(USHORTPTR)AgoBuf = '0\0';

   if (QUAD_ZERO(Time64Ptr)) return (SS$_NORMAL);

   sys$gettim (&NowTime64);
   status = lib$sub_times (&NowTime64, Time64Ptr, &AgoTime64);
   if (VMSok (status))
      status = lib$cvt_from_internal_time (&LibDeltaSecs, &SecsAgo, &AgoTime64);
   if (VMSnok (status)) return (status);

   AgoBufDsc.dsc$a_pointer = AgoBuf;
   AgoBufDsc.dsc$w_length = 16;
 
   status = SS$_NORMAL;

   if (SecsAgo >= (60*60*24))
      status = sys$fao (&AgoFaoDsc, 0, &AgoBufDsc, SecsAgo / (60*60*24), "d");
   else
   if (SecsAgo >= (60*60))
      status = sys$fao (&AgoFaoDsc, 0, &AgoBufDsc,  SecsAgo / (60*60), "h");
   else
   if (SecsAgo >= 60)
      status = sys$fao (&AgoFaoDsc, 0, &AgoBufDsc, SecsAgo / 60, "m");
   else
   if (SecsAgo > 1)
      status = sys$fao (&AgoFaoDsc, 0, &AgoBufDsc, SecsAgo, "s");
   else
      strcpy (AgoBuf, "now");

   return (status);
}

/*****************************************************************************/
/*
Put into the supplied buffer the time with the year removed (e.g. 31-OCT
23:01:15").  The buffer should have at least 16 bytes capacity.
*/

void TimeSansYear
(
ulong *Time64Ptr,
char *Buffer
)
{
   static $DESCRIPTOR (TimeFaoDsc, "!20%D\0");
   static char  TimeBuf [32];
   static $DESCRIPTOR (TimeBufDsc, TimeBuf);

   int  status;
   char  *cptr, *sptr;

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

   if (WATCH_MODULE(WATCH_MOD__OTHER))
      WatchThis (WATCHALL, WATCH_MOD__OTHER, "ThisLongAgo() !8XL", Time64Ptr);

   if (QUAD_ZERO(Time64Ptr))
   {
      strcpy (Buffer, "(none)");
      return;
   }

   Buffer[0] = '\0';
   status = sys$fao (&TimeFaoDsc, 0, &TimeBufDsc, Time64Ptr);
   if (VMSnok (status)) return;
   sptr = Buffer;
   for (cptr = TimeBuf; *cptr && *cptr != '-'; *sptr++ = *cptr++);
   for (*sptr++ = *cptr++; *cptr && *cptr != '-'; *sptr++ = *cptr++);
   while (*cptr && *cptr != ' ') cptr++;
   while (*cptr) *sptr++ = *cptr++;
   *sptr = '\0';
}

/*****************************************************************************/
/*
Convert a VMS quadword, binary time to a Unix-style time component structure.
*/

BOOL TimeVmsToUnix
(
ulong *Time64Ptr,
struct tm *TmPtr
)
{
   static ulong  LibDayOfWeek = LIB$K_DAY_OF_WEEK;
   static ulong  LibDayOfYear = LIB$K_DAY_OF_YEAR;

   int  status;
   ulong  DayOfWeek,
          DayOfYear;
   ushort  NumTime [7];
   ulong  Time64 [QUAD2];

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

   if (!Time64Ptr) sys$gettim (Time64Ptr = &Time64);

   if (VMSnok (status = sys$numtim (&NumTime, Time64Ptr)))
      return (status);

   lib$cvt_from_internal_time (&LibDayOfWeek, &DayOfWeek, Time64Ptr);
   lib$cvt_from_internal_time (&LibDayOfYear, &DayOfYear, Time64Ptr);

   TmPtr->tm_sec = NumTime[5];
   TmPtr->tm_min = NumTime[4];
   TmPtr->tm_hour = NumTime[3];
   TmPtr->tm_mday = NumTime[2];
   TmPtr->tm_mon = NumTime[1] - 1;
   TmPtr->tm_year = NumTime[0] - 1900;
   TmPtr->tm_wday = DayOfWeek;
   TmPtr->tm_yday = DayOfYear;
   TmPtr->tm_isdst = 0;

   return (SS$_NORMAL);
}

/****************************************************************************/
/*
generate a standard VMS protection string from the mask. 'String' must be at
least 28 bytes (better use 32 :^)  Returns the length of the string.
*/ 
 
int FormatProtection
(
ushort pmask,
char *String
)
{
   char  *sptr;

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

   sptr = String;

   if (!(pmask & 0x0001)) *sptr++ = 'R';
   if (!(pmask & 0x0002)) *sptr++ = 'W';
   if (!(pmask & 0x0004)) *sptr++ = 'E';
   if (!(pmask & 0x0008)) *sptr++ = 'D';
   *sptr++ = ',';
   if (!(pmask & 0x0010)) *sptr++ = 'R';
   if (!(pmask & 0x0020)) *sptr++ = 'W';
   if (!(pmask & 0x0040)) *sptr++ = 'E';
   if (!(pmask & 0x0080)) *sptr++ = 'D';
   *sptr++ = ',';
   if (!(pmask & 0x0100)) *sptr++ = 'R';
   if (!(pmask & 0x0200)) *sptr++ = 'W';
   if (!(pmask & 0x0400)) *sptr++ = 'E';
   if (!(pmask & 0x0800)) *sptr++ = 'D';
   *sptr++ = ',';
   if (!(pmask & 0x1000)) *sptr++ = 'R';
   if (!(pmask & 0x2000)) *sptr++ = 'W';
   if (!(pmask & 0x4000)) *sptr++ = 'E';
   if (!(pmask & 0x8000)) *sptr++ = 'D';

   *sptr = '\0';
   return (sptr - String);
}
 
/*****************************************************************************/
/*
Just return the process' current BYTLM quota.
*/ 

GetJpiBytLm ()

{
   static ulong  Pid = 0;

   static ulong  JpiBytLm;
   static VMS_ITEM_LIST3  JpiItems [] =
   {
      { sizeof(JpiBytLm), JPI$_BYTLM, &JpiBytLm, 0 },
      {0,0,0,0}
   };

   int  status;
   IO_SB  IOsb;

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

   status = sys$getjpiw (EfnWait, &Pid, 0, &JpiItems, &IOsb, 0, 0);
   if (VMSok (status)) status = IOsb.Status;
   if (VMSok (status)) return (JpiBytLm);
   ErrorExitVmsStatus (status, NULL, FI_LI);
}

/*****************************************************************************/
/*
Returns a pointer to a dynamic string containing the client host details as
"name (address)" or just "address".
*/

char* ClientHostString (REQUEST_STRUCT *rqptr)

{
   static $DESCRIPTOR (AddressFaoDsc, "!AZ\0");
   static $DESCRIPTOR (NameAddressFaoDsc, "!AZ (!AZ)\0");

   ushort  Length;
   char  *cptr;
   char  Buffer [256];
   $DESCRIPTOR (BufferDsc, Buffer);
   
   /*********/
   /* begin */
   /*********/

   if (!strcmp (rqptr->ClientPtr->Lookup.HostName,
                &rqptr->ClientPtr->IpAddressString))
      sys$fao (&AddressFaoDsc, &Length, &BufferDsc, 
               &rqptr->ClientPtr->IpAddressString);
   else
      sys$fao (&NameAddressFaoDsc, &Length, &BufferDsc, 
               rqptr->ClientPtr->Lookup.HostName,
               &rqptr->ClientPtr->IpAddressString);

   cptr = VmGetHeap (rqptr, Length);
   strcpy (cptr, Buffer);
   return (cptr);
}

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

char* UserAtClient (REQUEST_STRUCT *rqptr)

{
   char  *cptr, *sptr, *zptr;
   char  StringBuffer [256];

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

   if (!rqptr->RemoteUser[0]) return (rqptr->ClientPtr->Lookup.HostName);

   zptr = (sptr = StringBuffer) + sizeof(StringBuffer)-1;
   for (cptr = rqptr->RemoteUser; *cptr; cptr++)
      if (!isalnum(*cptr) && *cptr != '-' && *cptr != '_') break;
   if (*cptr) *sptr++ = '\"';
   for (cptr = rqptr->RemoteUser;
        *cptr && sptr < zptr;
        *sptr++ = *cptr++);
   if (StringBuffer[0] == '\"' && sptr < zptr) *sptr++ = '\"';
   if (rqptr->rqAuth.RealmPtr)
   {
      if (sptr < zptr) *sptr++ = '.';
      for (cptr = rqptr->rqAuth.RealmPtr;
           *cptr && sptr < zptr;
           *sptr++ = *cptr++);
   }
   if (sptr < zptr) *sptr++ = '@';
   for (cptr = rqptr->ClientPtr->Lookup.HostName;
        *cptr && sptr < zptr;
        *sptr++ = *cptr++);
   *sptr = '\0';

   cptr = VmGetHeap (rqptr, sptr-StringBuffer);
   strcpy (cptr, StringBuffer);
   return (cptr);
}

/****************************************************************************/
/*
Given a buffer of UTF-8 convert in-situ to 8 bit ASCII.  Ignore non- 8 bit
ASCII characters.  End-of-string is indicated by text-length not a
null-character, however the resultant string is nulled. If supplied the 8 bit
character 'SubsChar' is substituted for any non 8 bit code in the string.  
Return the number of converted characters.  Return -1 if there is an error. 
The string is mangled if an error.
*/

int ConvertFromUtf8
(
char *UtfPtr,
int UtfCount,
char SubsChar
)
{
   uchar  ch;
   uchar  *cptr, *sptr, *zptr;

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

   if (WATCH_MODULE(WATCH_MOD__OTHER))
      WatchThis (WATCHALL, WATCH_MOD__OTHER,
                 "ConvertFromUtf8() !&C !#H", SubsChar, UtfCount, UtfPtr);

   if (UtfCount == -1) UtfCount = strlen(UtfPtr);

   if (UtfCount < 0) return (-1);

    /* is there a potentially UTF-8 bit pattern here? */
    for (zptr = (cptr = (uchar*)UtfPtr) + UtfCount; cptr < zptr; cptr++)
      if ((*cptr & 0xc0) == 0xc0) break;

   /* return if no UTF-8 conversion necessary (i.e. all 7 bit characters) */
   if (cptr >= zptr) return (UtfCount);
   if (*cptr == 0xff) return (cptr - (uchar*)UtfPtr);

   sptr = cptr;
   while (cptr < zptr)
   {
      if ((*cptr & 0xf8) == 0xf0)
      {
         /* four byte sequence */
         if (++cptr >= zptr) goto utf8_nbg;
         if ((*cptr & 0xc0) != 0x80) goto utf8_nbg;
         if (++cptr >= zptr) goto utf8_nbg;
         if ((*cptr & 0xc0) != 0x80) goto utf8_nbg;
         if (++cptr >= zptr) goto utf8_nbg;
         if ((*cptr & 0xc0) != 0x80) goto utf8_nbg;
         if (SubsChar) *sptr++ = SubsChar;
      }
      else
      if ((*cptr & 0xf0) == 0xe0)
      {
         /* three byte sequence */
         if (++cptr >= zptr) goto utf8_nbg;
         if ((*cptr & 0xc0) != 0x80) goto utf8_nbg;
         if (++cptr >= zptr) goto utf8_nbg;
         if ((*cptr & 0xc0) != 0x80) goto utf8_nbg;
         if (SubsChar) *sptr++ = SubsChar;
      }
      else
      if ((*cptr & 0xe0) == 0xc0)
      {
         /* two byte sequence */
         if (*cptr & 0x1c)
         {
            /* out-of-range character */
            if (++cptr >= zptr) goto utf8_nbg;
            if ((*cptr & 0xc0) != 0x80) goto utf8_nbg;
            if (++cptr >= zptr) goto utf8_nbg;
            if (SubsChar) *sptr++ = SubsChar;
         }
         else
         {
            /* 8 bit ASCII 128 to 255 */
            ch = (*cptr & 0x03) << 6;
            if (++cptr >= zptr) goto utf8_nbg;
            if ((*cptr & 0xc0) != 0x80) goto utf8_nbg;
            ch |= *cptr & 0x3f;
            *sptr++ = ch;
            cptr++;
         }
      }
      else
      {
         /* 8 bit ASCII 0 to 127 */
         *sptr++ = *cptr++;
      }
   }
   *sptr = '\0';

   return (sptr - (uchar*)UtfPtr);

   utf8_nbg:
      return (-1);
}

/****************************************************************************/
/*
Given a buffer of 8 bit ASCII text convert it in-situ to UTF-8.  Worst-case the
buffer space needs to be two times in size (i.e. every character has the hi bit
set requiring a leading UTF-8 byte).  End-of-string is indicated by text-length
not a null-character, however the resultant string is nulled.  Return the
length of the converted string.  Return -1 if the buffer space will be too
small.
*/

int ConvertToUtf8
(
char *TextPtr,
int TextLength,
int SizeOfText
)
{
   int  HiBitCount = 0;
   char  *cptr, *sptr, *zptr;

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

   if (WATCH_MODULE(WATCH_MOD__OTHER))
      WatchThis (WATCHALL, WATCH_MOD__OTHER,
                 "ConvertFromUtf8() !!UL !#H", SizeOfText, TextLength, TextPtr);

   if (TextLength == -1) TextLength = strlen(TextPtr);

   for (zptr = (cptr = TextPtr) + TextLength; cptr < zptr; cptr++)
      if (*cptr & 0x80) HiBitCount++;

   if (!HiBitCount) return (TextLength);

   if (TextLength + HiBitCount >= SizeOfText - 1) return (-1);

   sptr = (cptr = (zptr = TextPtr) + TextLength - 1) + HiBitCount + 1;
   *sptr-- = '\0';
   while (cptr >= zptr)
   {
      if (*cptr & 0x80)
      {
         *sptr-- = (*cptr & 0x3f) | 0x80;
         *sptr-- = ((*cptr-- & 0xc0) >> 6) | 0xc0;
      }
      else
         *sptr-- = *cptr--;
   }

   return (TextLength + HiBitCount);
}

/****************************************************************************/
/*
For UTF-8 encoded file names hex-escape characters that cause issues.  This is
intended to feed into RMS services such as $PARSE.  Return zero if the original
string can be used.  Return minus one if the buffer overflowed.  Return the
number of characters in the escaped string when the buffer should be used.
*/ 

int ConvertUtf8ToEscape
(
char *FileSpec,
char *BufferPtr,
int BufferSize
)
{
   static char  HexDigit [] = "0123456789ABCDEF";
   static char  ScratchBuffer [ODS_MAX_FILE_NAME_LENGTH+1];

   char  *cptr, *sptr, *zptr;

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

   if (WATCH_MODULE(WATCH_MOD__OTHER))
      WatchThis (WATCHALL, WATCH_MOD__OTHER,
                 "ConvertUtf8ToEscape() !&Z", FileSpec);

   for (cptr = FileSpec; *cptr; cptr++)
      if (*(uchar*)cptr >= 0x80 && *(uchar*)cptr <= 0x9f)
         break;

   if (!*cptr) return (0);

   if (BufferPtr == FileSpec)
   {
      /* reusing the file spec buffer so put it into a scratch buffer */
      zptr = (sptr = ScratchBuffer) + sizeof(ScratchBuffer)-1;
      for (cptr = FileSpec; *cptr && sptr < zptr; *sptr++ = *cptr++);
      if (sptr >= zptr)
      {
         BufferPtr[0] = '\0';
         return (-1);
      }
      *sptr = '\0';
      cptr = ScratchBuffer;
   }
   else
      cptr = FileSpec;

   zptr = (sptr = BufferPtr) + BufferSize - 1;
   while (*cptr && sptr < zptr)
   {
      if (*(uchar*)cptr >= 0x80 && *(uchar*)cptr <= 0x9f)
      {
         *sptr++ = '^';
         if (sptr < zptr) *sptr++ = HexDigit[((uchar)*cptr & 0xf0) >> 4];
         if (sptr < zptr) *sptr++ = HexDigit[(uchar)*cptr & 0x0f];
         cptr++;
      }
      else
         *sptr++ = *cptr++;
   }

   if (sptr >= zptr)
   {
      BufferPtr[0] = '\0';
      return (-1);
   }

   *sptr = '\0';
   if (WATCH_MODULE(WATCH_MOD_ODS))
      WatchThis (WATCHALL, WATCH_MOD_ODS, "!&Z", BufferPtr);

   return (sptr - BufferPtr);
}

/*****************************************************************************/
/*
Generate a unique request identifier.  Based on the description and code of the
Apache Group's "mod_unique" module.  One change has been made to allow the ID
to be used as part of VMS file names, the substitution of '@' for '_'.
*/

char* GenerateUniqueId (REQUEST_STRUCT *rqptr)

{
   static uchar  ValueTable [] =
      "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_-";

   static int  UniqueIdCount = 0;
   static char  UniqueIdString [UNIQUE_ID_SIZE+8];

   int  idx;
   ulong  TimeStamp;
   char *sptr; 
   uchar  *cptr; 

#ifndef __VAX
#   pragma member_alignment __save
#   pragma nomember_alignment
#endif

   struct {
      uint  Stamp;
      uint  IpAddr;
      uint  Pid;
      ushort  Count;
      ushort  Zeroes;
   } UniqueId;

#ifndef __VAX
#   pragma member_alignment __restore
#endif

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

   TimeStamp = decc$fix_time (&rqptr->rqTime.BeginTime64);

   UniqueId.Stamp = htonl(TimeStamp);
   UniqueId.IpAddr = htonl(rqptr->ServicePtr->ServerIpAddress);
   UniqueId.Pid = htonl(HttpdProcess.Pid);
   UniqueId.Count = htons(UniqueIdCount);
   /* this is just padding for the encoding */
   UniqueId.Zeroes = 0;
   UniqueIdCount++;

   sptr = UniqueIdString;
   cptr = (uchar*)&UniqueId;
   for (idx = 0; idx < sizeof(UniqueId)-sizeof(short); idx += 3)
   {
      *sptr++ = ValueTable[cptr[idx]>>2];
      *sptr++ = ValueTable[((cptr[idx] & 0x03) << 4) |
                           ((cptr[idx+1] & 0xf0) >> 4)];
      *sptr++ = ValueTable[((cptr[idx+1] & 0x0f) << 2) |
                           ((cptr[idx+2] & 0xc0) >> 6)];
      *sptr++ = ValueTable[cptr[idx+2] & 0x3f];
   }
   UniqueIdString[UNIQUE_ID_SIZE] = '\0';

   return (UniqueIdString);
}

/*****************************************************************************/
/*
Support version 10 and pre-version-10 logical and file naming conventions. 
Look for one of multiple (usually just two but can be more) possible logical
names.  When one is successfully translated return a pointer to it's null
terminated name.  Otherwise test for a subsequent and return a pointer to it if
found.  If none is found then return NoneFound if zero or positive, return the
LogicalName if minus one.  The constants should be #defined in the manner of
"?WASD_CONFIG_GLOBAL\0HTTPD$CONFIG\0".
*/

char* v10orPrev10
(
char *LogicalName,
int NoneFound
)
{
   static ushort  Length;
   static char  ValueString [128];
   static $DESCRIPTOR (LogicalNameDsc, "");
   static $DESCRIPTOR (LnmFileDevDsc, "LNM$FILE_DEV");
   static VMS_ITEM_LIST3  LnmItems [] =
   {
      { sizeof(ValueString)-1, LNM$_STRING, ValueString, &Length },
      { 0,0,0,0 }
   };

   int  status;
   char  *cptr, *sptr;

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

   if (!LogicalName || LogicalName[0] != '?') return (LogicalName);

   /* stop at any logical name delimiting colon (perhaps of a file name) */
   for (sptr = cptr = LogicalName+1; *sptr && *sptr != ':'; sptr++);
   while (*cptr)
   {
      LogicalNameDsc.dsc$a_pointer = cptr;
      LogicalNameDsc.dsc$w_length = sptr - cptr;
      status = sys$trnlnm (0, &LnmFileDevDsc, &LogicalNameDsc, 0, &LnmItems);
      if (VMSok (status)) return (cptr);
      /* scan past any logical-delimiting colon */
      while (*sptr) sptr++;
      /* stop at any logical name delimiting colon (perhaps of a file name) */
      for (cptr = (sptr += 1); *sptr && *sptr != ':'; sptr++);
   }

   if (NoneFound != -1) return (NoneFound);
   return (LogicalName+1);
}

/*****************************************************************************/
/*
Translate a logical name using LNM$FILE_DEV.  Returns a pointer to the value
string as a pointer to a static buffer which must be copied if to be retained,
or NULL if the name does not exist.
*/

char* SysTrnLnm (char *LogName)

{
   static ushort  ValueLength;
   static ulong  LnmAttributes;
   static char  LogValue [256];
   static $DESCRIPTOR (LogNameDsc, "");
   static $DESCRIPTOR (LnmTableDsc, "LNM$FILE_DEV");
   static struct {
      short int  buf_len;
      short int  item;
      void  *buf_addr;
      ushort  *ret_len;
   } LnmItems [] =
   {
      { sizeof(LnmAttributes), LNM$_ATTRIBUTES, &LnmAttributes, 0 },
      { sizeof(LogValue)-1, LNM$_STRING, LogValue, &ValueLength },
      { 0,0,0,0 }
   };

   int  status;
   char  *cptr;

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

   for (cptr = LogName; *cptr; cptr++);
   LogNameDsc.dsc$w_length = cptr - LogName;
   LogNameDsc.dsc$a_pointer = LogName;

   memset (LogValue, 0, sizeof(LogValue));

   status = sys$trnlnm (0, &LnmTableDsc, &LogNameDsc, 0, &LnmItems);
   if (!(status & 1) || !(LnmAttributes & LNM$M_EXISTS)) return (NULL);

   return (LogValue);
}

/****************************************************************************/
/*
Assign a global symbol.
*/ 

int SetGlobalSymbol
(
char *Name,
char *String
)
{
   static int  GlobalSymbol = LIB$K_CLI_GLOBAL_SYM;
   static $DESCRIPTOR (NameDsc, "");
   static $DESCRIPTOR (ValueDsc, "");

   int  status;

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

   NameDsc.dsc$a_pointer = Name;
   NameDsc.dsc$w_length = strlen(Name);
   ValueDsc.dsc$a_pointer = String;
   ValueDsc.dsc$w_length = strlen(String);

   status = lib$set_symbol (&NameDsc, &ValueDsc, &GlobalSymbol);
   return (status);
}

/*****************************************************************************/
/*
Return a float number of seconds.
*/

float FloatDeltaTime (ulong *DeltaTimePtr)

{
   static int  LibDeltaSecs = LIB$K_DELTA_SECONDS_F;

   int  status;
   float  fsecs;

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

   if (IS_ZERO_DELTA_TIME(DeltaTimePtr)) return (0.0);

#ifdef __ia64
   status = lib$cvts_from_internal_time (&LibDeltaSecs, &fsecs, DeltaTimePtr);
#else
   status = lib$cvtf_from_internal_time (&LibDeltaSecs, &fsecs, DeltaTimePtr);
#  ifdef __ALPHA
   if (VMSok(status))
      status = cvt$convert_float (&fsecs, CVT$K_VAX_F,
                                  &fsecs, CVT$K_IEEE_S, 0);
#  endif
#endif
   if (VMSnok(status))
   {
      ErrorNoticed (NULL, status, "lib$cvts_from_internal_time()", FI_LI);
      return (0.0);
   }

   return (fsecs);
}

/*****************************************************************************/
/*
Return a longword bytes/second.
*/

int BytesPerSecond
(
ulong *BytesRxPtr,
ulong *BytesTxPtr,
ulong *DeltaTimePtr
)
{
   static int  LibDeltaSecs = LIB$K_DELTA_SECONDS_F;

   int  status;
   uint  bps;
   float  fbytes = 0.0,
          fsecs;

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

   /* if zero time then only an artifact will be generated! */
   if (IS_ZERO_DELTA_TIME(DeltaTimePtr)) return (0);

   if (BytesRxPtr)
      fbytes += (float)BytesRxPtr[0] +
                ((float)BytesRxPtr[1] * 4294967296.0);
   if (BytesTxPtr)
      fbytes += (float)BytesTxPtr[0] +
                ((float)BytesTxPtr[1] * 4294967296.0);

#ifdef __ia64
   status = lib$cvts_from_internal_time (&LibDeltaSecs, &fsecs, DeltaTimePtr);
#else
   status = lib$cvtf_from_internal_time (&LibDeltaSecs, &fsecs, DeltaTimePtr);
#  ifdef __ALPHA
   if (VMSok(status))
      status = cvt$convert_float (&fsecs, CVT$K_VAX_F,
                                  &fsecs, CVT$K_IEEE_S, 0);
#  endif
#endif
   if (VMSnok(status))
   {
      if (status != LIB$_DELTIMREQ)
         ErrorNoticed (NULL, status, "lib$cvts_from_internal_time()", FI_LI);
      return (0);
   }

   /* if greater than 2000000000 (2GB/S) it's likely to be an artifact! */
   if ((bps = (uint)(fbytes / fsecs)) < 2000000000)
      return ((int)bps);
   else
      return (0);
}

/*****************************************************************************/
/*
Calculate percentages of unsigned longs using floats to avoid integer overflow
and allowing more accurate rounding.
*/

int PercentOf
(
ulong arg1,
ulong arg2
)
{
   int  iperc;
   float  farg1, farg2, fperc;

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

   if (arg2)
   {
      farg1 = (float)arg1;
      farg2 = (float)arg2;
      fperc = farg1 * 100.0 / farg2;
      iperc = (int)fperc;
      if (fperc - (float)iperc >= 0.5) iperc++;
      return (iperc);
   }
   return (0);
}

/*****************************************************************************/
/*
Calculate percentages of quadwards using floats to avoid overflow and allowing
more accurate rounding.
*/

int QuadPercentOf
(
ulong *qarg1,
ulong *qarg2
)
{
   int  iqperc;
   float  fqarg1, fqarg2, fqperc;

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

   if (qarg2[0] || qarg2[1])
   {
      fqarg1 = (float)qarg1[0] + ((float)qarg1[1] * 4294967296.0);
      fqarg2 = (float)qarg2[0] + ((float)qarg2[1] * 4294967296.0);
      if (fqarg2 == 0.0) return (0);
      fqperc = fqarg1 * 100.0 / fqarg2;
      iqperc = (int)fqperc;
      if (fqperc - (float)iqperc >= 0.5) iqperc++;
      return (iqperc);
   }
   return (0);
}

/*****************************************************************************/
/*
For non-64 bit platform; is quadword one less than quadword two.
*/

#if !USE_INT64

BOOL QuadLtQuad
(
void *quad1,
void *quad2
)
{
   ulong  *q1 = quad1,
          *q2 = quad2;

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

   /* if q1 is negative and q2 not then it's less than */
   if ((q1[1] & 0x80000000) && !(q2[1] & 0x80000000)) return (true);
   /* if q1 is positive and and q2 not then it's greater than */
   if (!(q1[1] & 0x80000000) && (q2[1] & 0x80000000)) return (false);
   if ((q1[1] & 0x80000000) && (q2[1] & 0x80000000))
   {
      /* both negative numbers */
      if (q1[1] > q2[1]) return (true);
      if (q1[1] < q2[1]) return (false);
      if (q1[0] > q2[0]) return (true);
   }
   else
   {
      /* both positive numbers */
      if (q1[1] < q2[1]) return (true);
      if (q1[1] > q2[1]) return (false);
      if (q1[0] < q2[0]) return (true);
   }
   return (false);
}

#endif /* !USE_INT64 */

/*****************************************************************************/
/*
Integer power-of.
*/

int ipow
(
int base,
int exp
)
{
   int result = 1;

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

   while (exp)
   {
      if (exp & 1) result *= base;
      exp >>= 1;
      base *= base;
   }

   return (result);
}

/*****************************************************************************/
/*
$ HTTPD /TEST MATCH for the relative performance of MATCH1..8() and memcmp().
*/

#if WATCH_MOD

MatchPerf ()

#pragma optimize save
#pragma optimize level=0

{
   int  cnt, result;
   char  MatchTest1 [] = "01234567890",
         MatchTest2 [] = "012345678_0";
   int  MatchTest3 = '0123';

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

   lib$init_timer (0);
   for (cnt = 100000000; cnt; cnt--)
   {
      result = MATCH2 (MatchTest1, MatchTest2);
      result = MATCH3 (MatchTest1, MatchTest2);
      result = MATCH4 (MatchTest1, MatchTest2);
      result = MATCH5 (MatchTest1, MatchTest2);
      result = MATCH6 (MatchTest1, MatchTest2);
      result = MATCH7 (MatchTest1, MatchTest2);
      result = MATCH8 (MatchTest1, MatchTest2);
   }
   lib$show_timer (0,0,0,0);
   lib$init_timer (0);
   for (cnt = 100000000; cnt; cnt--)
   {
      result = !memcmp (MatchTest1, MatchTest2, 2);
      result = !memcmp (MatchTest1, MatchTest2, 3);
      result = !memcmp (MatchTest1, MatchTest2, 4);
      result = !memcmp (MatchTest1, MatchTest2, 5);
      result = !memcmp (MatchTest1, MatchTest2, 6);
      result = !memcmp (MatchTest1, MatchTest2, 7);
      result = !memcmp (MatchTest1, MatchTest2, 8);
   }
   lib$show_timer (0,0,0,0);
   lib$init_timer (0);
   for (cnt = 100000000; cnt; cnt--)
   {
      result = MATCH2 (MatchTest1, MatchTest2);
      result = MATCH3 (MatchTest1, MatchTest2);
      result = MATCH4 (MatchTest1, MatchTest2);
      result = MATCH2 (MatchTest1, MatchTest2);
      result = MATCH3 (MatchTest1, MatchTest2);
      result = MATCH4 (MatchTest1, MatchTest2);
   }
   lib$show_timer (0,0,0,0);
   lib$init_timer (0);
   for (cnt = 100000000; cnt; cnt--)
   {
      result = SAME2 (MatchTest1, MatchTest3);
      result = SAME3 (MatchTest1, MatchTest3);
      result = SAME4 (MatchTest1, MatchTest3);
      result = SAME2 (MatchTest1, MatchTest3);
      result = SAME3 (MatchTest1, MatchTest3);
      result = SAME4 (MatchTest1, MatchTest3);
   }
   lib$show_timer (0,0,0,0);
}

#pragma optimize restore

#endif /* WATCH_MOD */

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

