/*****************************************************************************
/*
                                  FAO.c

Formatted write using $FAO()-like directives, just a functional subset, it also
differs in some significant ways, including extended functionality.  The
functions can write into either specified storage or into the request's output
buffer (probably more efficient that $FAO()ing into storage then copying this
to the network buffer).  All parameters are passed via a $FAOL()-like
pointer-to-longword-vector.  Strings are always assumed to be null-terminated.

A resultant string is always null-terminated (even after an error), hence the
'BufferSize' can always only store 'BufferSize'-1 characters.  'LengthPtr'
provides storage for the number of characters minus the null-termination.

It is no exageration to state that this functionality has made the sorts of
things undertaken with it ten times easier and more efficient!!


$FAO DIRECTIVES SUPPORTED
-------------------------
!AC    counted ASCII string (first byte must be count, i.e. 0 to 255 chars)
!AZ    null-terminated string
!SL    decimal, signed longword
!SQ    signed quadword
!UL    decimal, unsigned longword
!XL    hexadecimal, longword
!ZL    zero-filled decimal, unsigned longword
!%D    date/time
!%I    numeric to ASCII identifier
!%S    if the last converted numeric value was not 1 add "S"
!%s    if the last converted numeric value was not 1 add "s"
!%T    time
!*     multiple '*'+1 characters
!+     step-over the next parameter from the vector (anything BUT a "!&@")
!-     back-up to the previous parameter in the vector (anything BUT a "!&@")
!<     begin specified width output field (cannot nest, must have width!)
!>     end specified width output field
!!     exclamation mark


WASD EXTENSIONS
---------------
!&,    comma-ize the following number (e.g. "!&,SL")
!&;    HTML-escape the following string (e.g. "!&;AZ")
!&%    URL-encode the following string (e.g. "!&%AZ")
!&[    format string as a file specification (requires preceding ODS flag)
!&$    format string as a VMS file specification
!&^    force the following string to upper case
!&!    force the following string to lower case
!&_    underline the following string if it contains a space (<U>..</U>)
!&"    quote/escape the following string if it contains a space ("..\"..")
!&+    if !AZ string is not null or empty then add a leading space
!&/    if !AZ string is not null or empty the add a trailing newline
!&A    address as a parameter, as "&00000000" (for module WATCHing) 
!&B    boolean (for WATCHing)
!&C    a single character
!&D    date/time string truncated after the minute
!&F    local function address, as "00000000()" (for module WATCHing) 
!&H    bytes in hexadecimal (field width in number)
!&I    dotted-decimal from 32 bit network-order IP address
!&L    the next line (i.e. up to the next <CR> or <LF>)
!&M    full status message string (returned from sys$getmsg())
!&m    text-only status message string (returned from sys$getmsg())
!&O    add " SELECTED" if true, "" if false (for <select> option items)
!&P    request URI (mask password, for module WATCHing)
!&R    add " CHECKED" if true, "" if false (for <input> radio buttons)
!&S    VMS status value, as "%X00000000"
!&U    URL encode
!&W    weekday/date/time (field-width applies to date/time component only)
!&X    hexadecimal value, as "0x00000000"
!&Z    null-terminated string, as "{length}string" (for module WATCHing)
!&?    simple conditional, "!&?string if *vector TRUE\rstring if FALSE\r"
!&@    nested format string address from vector (FILO stacks the current one)
!%%    same as !&@ (for backward compatibility of strings in HTTPD$MSG)


The WASD extensions perform two roles.  Some modify the way a following
directive is formatted, for example HTML-escaping it.  Others provide
additonal format directives to standard $FAO.  Field width should always be
placed BEFORE the WASD extensions.  Indirection should be indicated immediately
before the respective formatting directive.

Each directive can have a positive, decimal integer between the '!' and the
directive providing a field width, e.g. "!10AZ", or have the field width taken
from the vector using "!#AZ".  These behave in the same manner as the sys$fao()
field width parameter. 

Any directive may use the indirect character (unlike sys$fao(), where only
numeric directives may).  This indicates the vector value is the 32 bit address
of the value, rather than the value itself, e.g. "!@AZ", "!10@UL", etc.


VERSION HISTORY
---------------
01-MAR-2003  MGD  '&+' if not null or empty !AZ adds a leading space,
                  '&/' if not null or empty !AZ adds a trailing newline,
                  change behaviour of NULL !AZ to showing an empty string
18-OCT-2002  MGD  '&"' quotes if there is a space in the string
19-AUG-2002  MGD  '&[' for ODS file naming conversions,
                  '&^' now force-to-upper-case, '&!' force-to-lower,
                  '&_' underlines if there is a space in the string
08-AUG-2002  MGD  '&U' request URI (mask password) now '&P',
                  '&U' now URL-encodes a URL (allowing for query string, etc.)
16-JUN-2002  MGD  make field-width work for '&,'
31-MAR-2002  MGD  '&U' request URI (mask password)
17-MAR-2002  MGD  'SQ' kludge for VAX quadword values 
10-JAN-2002  MGD  add !&R and !&O
04-AUG-2001  MGD  differentiate WASD extensions with '!&',
                  support module WATCHing
07-JUL-2001  MGD  add !IP to WriteFormatted()
17-MAY-2001  MGD  dignified with a module of it's own
02-FEB-2001  MGD  add !%I to WriteFormatted()
27-AUG-2000  MGD  add !SL to WriteFormatted()
04-MAR-2000  MGD  add WriteFormatted() and it's siblings
                  WriteFao(), WriteFaol(), WriteFaoNet()
*/
/*****************************************************************************/

#ifdef WASD_VMS_V6
#undef _VMS_V6_SOURCE
#define _VMS_V6_SOURCE
#undef __VMS_VER
#define __VMS_VER 60000000
#undef __CRTL_VER
#define __CRTL_VER 60000000
#endif

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

/* VMS related header files */
#include <descrip.h>
#include <jpidef.h>
#include <libdef.h>
#include <libdtdef.h>
#include <lnmdef.h>
#include <opcdef.h>
#include <prvdef.h>
#include <psldef.h>
#include <rms.h>
#include <secdef.h>
#include <ssdef.h>
#include <stsdef.h>

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

#define WASD_MODULE "FAO"

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

#if WATCH_MOD
   BOOL  DebugFaoWrite;
#  define DEBUG_FAO_WRITE DebugFaoWrite
#else
#  define DEBUG_FAO_WRITE 0
#endif

int  OpcomMessages,
     OpcomTarget;

char  *FaoUrlEncodeTable [] =
{
   "%00", "%01", "%02", "%03", "%04", "%05", "%06", "%07",
   "%08", "%09", "%0a", "%0b", "%0c", "%0d", "%0e", "%0f", 
   "%10", "%11", "%12", "%13", "%14", "%15", "%16", "%17", 
   "%18", "%19", "%1a", "%1b", "%1c", "%1d", "%1e", "%1f", 
   "%20", "%21", "%22", "%23", "$",   "%25", "%26", "%27",
   "%28", "%29", "*",   "%2b", "%2c", "-",   ".",   "/",
   "0",   "1",   "2",   "3",   "4",   "5",   "6",   "7",
   "8",   "9",   "%3a", ";",   "%3c", "%3d", "%3e", "%3f",
   "%40", "A",   "B",   "C",   "D",   "E",   "F",   "G",    /* 0x47 */
   "H",   "I",   "J",   "K",   "L",   "M",   "N",   "O",    /* 0x4f */
   "P",   "Q",   "R",   "S",   "T",   "U",   "V",   "W",    /* 0x57 */
   "X",   "Y",   "Z",   "%5b", "%5c", "%5d", "%5e", "_",    /* 0x5f */
   "%60", "a",   "b",   "c",   "d",   "e",   "f",   "g",    /* 0x67 */
   "h",   "i",   "j",   "k",   "l",   "m",   "n",   "o",    /* 0x6f */
   "p",   "q",   "r",   "s",   "t",   "u",   "v",   "w",    /* 0x77 */
   "x",   "y",   "z",   "%7b", "%7c", "%7d", "%7e", "%7f",
   "%80", "%81", "%82", "%83", "%84", "%85", "%86", "%87",
   "%88", "%89", "%8a", "%8b", "%8c", "%8d", "%8e", "%8f",
   "%90", "%91", "%92", "%93", "%94", "%95", "%96", "%97",
   "%98", "%99", "%9a", "%9b", "%9c", "%9d", "%9e", "%9f",
   "%a0", "%a1", "%a2", "%a3", "%a4", "%a5", "%a6", "%a7",
   "%a8", "%a9", "%aa", "%ab", "%ac", "%ad", "%ae", "%af",
   "%b0", "%b1", "%b2", "%b3", "%b4", "%b5", "%b6", "%b7",
   "%b8", "%b9", "%ba", "%bb", "%bc", "%bd", "%be", "%bf",
   "%c0", "%c1", "%c2", "%c3", "%c4", "%c5", "%c6", "%c7",
   "%c8", "%c9", "%ca", "%cb", "%cc", "%cd", "%ce", "%cf",
   "%d0", "%d1", "%d2", "%d3", "%d4", "%d5", "%d6", "%d7",
   "%d8", "%d9", "%da", "%db", "%dc", "%dd", "%de", "%df",
   "%e0", "%e1", "%e2", "%e3", "%e4", "%e5", "%e6", "%e7",
   "%e8", "%e9", "%ea", "%eb", "%ec", "%ed", "%ee", "%ef",
   "%f0", "%f1", "%f2", "%f3", "%f4", "%f5", "%f6", "%f7",
   "%ff", "%f9", "%fa", "%fb", "%fc", "%fd", "%fe", "%ff"
};

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

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

extern BOOL  OdsExtended;

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

extern char  *DayName[];

extern HTTPD_PROCESS  HttpdProcess;
extern WATCH_STRUCT  Watch;

/*****************************************************************************/
/*
This is the primary FAO function described in the module prologue.  It will be
more than obvious it has been written to optimize efficiency (in the author's
limited appreciation anyway) rather than readability.  There is a #define in
NET.H making it available as NetWriteFaol(), and a #define in SUPPORT.H making
it available as WriteFaol().
*/ 

int WriteFormatted
(
REQUEST_STRUCT *rqptr,
char *BufferPtr,
int BufferSize,
unsigned short *LengthPtr,
char *FormatString,
unsigned long *VectorPtr
)
{
#define FORMAT_STACK_MAX 8

   static char  HexDigits [] = "0123456789abcdef";
   static char  HexDigitsUpper [] = "0123456789ABCDEF";
   static char  EncBuffer [] = "%xx";
   static $DESCRIPTOR (DayDateTimeFaoDsc, "!AZ, !#%D\0");
   static $DESCRIPTOR (HexValueFaoDsc, "0x!8XL\0");
   static $DESCRIPTOR (LengthStringFaoDsc, "{!UL}!-!#AZ\0");
   static $DESCRIPTOR (Md5FaoDsc, "!8XL!8XL!8XL!8XL\0");
   static $DESCRIPTOR (Md5WidthFaoDsc, "!#<!8XL!8XL!8XL!8XL!>\0");
   static $DESCRIPTOR (PDFaoDsc, "!%D\0");
   static $DESCRIPTOR (PIFaoDsc, "!%I\0");
   static $DESCRIPTOR (PTFaoDsc, "!%T\0");
   static $DESCRIPTOR (SLFaoDsc, "!SL\0");
   static $DESCRIPTOR (SLWidthFaoDsc, "!#SL\0");
   static $DESCRIPTOR (SQFaoDsc, "!@SQ\0");
   static $DESCRIPTOR (SQWidthFaoDsc, "!#@SQ\0");
   static $DESCRIPTOR (ULFaoDsc, "!UL\0");
   static $DESCRIPTOR (ULWidthFaoDsc, "!#UL\0");
   static $DESCRIPTOR (VmsStatusFaoDsc, "%X!8XL\0");
   static $DESCRIPTOR (WatchingAddressFaoDsc, "&!8XL/!AZ()\0");
   static $DESCRIPTOR (WatchingFunctionFaoDsc, "!8XL()\0");
   static $DESCRIPTOR (XLFaoDsc, "!XL\0");
   static $DESCRIPTOR (XLWidthFaoDsc, "!#XL\0");
   static $DESCRIPTOR (ZLFaoDsc, "!ZL\0");
   static $DESCRIPTOR (ZLWidthFaoDsc, "!#ZL\0");

   BOOL  CommaNumber,
         FileSpec,
         ForceLowerCase,
         ForceUpperCase,
         HtmlEscape,
         IndirectValue,
         QuoteIfSpace,
         SpaceThenString,
         TrailingNewlineString,
         UnderlineIfSpace,
         UrlEncode;
   int  ch, bcnt, brcnt, fw, ofw, idx,
        status,
        CommaCount,
        FileOds,
        LastNumericValue, 
        PrevBcnt,
        SysGetMsgFlags;
   unsigned short  Length;
   unsigned long  DayOfWeek,
                  VectorValue;
   unsigned long  BinTime [2];
   unsigned long  *ulptr,
                  *BinTimePtr;
   char  *bptr, *cptr, *fptr, *sptr, *zptr,
         *HexDigitsPtr,
         *LastFptr;
   char  *FormatStack [FORMAT_STACK_MAX];
   char  FileString [ODS_MAX_FILE_NAME_LENGTH],
         String [2048];
   $DESCRIPTOR (StringDsc, String);
#ifdef __VAX
   double  dValue;
#endif

#if WATCH_MOD
   {
      static BOOL  DebugFaoWriteCheck;
      if (!DebugFaoWriteCheck)
      {
         DebugFaoWriteCheck = true;
         DebugFaoWrite = getenv ("WASD_DEBUG_FAO_WRITE");
      }
   }
#endif

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

   if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD_FAO))
      WatchThis (rqptr, FI_LI, WATCH_MOD_FAO,
                 "WriteFormatted() !&X !UL !&X !AZ",
                 BufferPtr, BufferSize, LengthPtr, FormatString);

   status = SS$_NORMAL;
   LastNumericValue = 1; 
   /* start with output-field-width set to infinite */
   ofw = -1;

   if (LengthPtr) *LengthPtr = 0;

   if (BufferPtr)
   {
      /* writing to specified storage */
      if (!(brcnt = BufferSize)) return (SS$_BADPARAM);
      bptr = BufferPtr;
      bcnt = PrevBcnt = 0;
   }
   else
   {
      /* first call, initialize a buffer */
      if (!rqptr->rqOutput.BufferStructPtr)
         NetWriteBufferedInit (rqptr, true);

      bptr = rqptr->rqOutput.BufferCurrentPtr;
      bcnt = PrevBcnt = rqptr->rqOutput.BufferCount;
      brcnt = rqptr->rqOutput.BufferRemaining;
   }

   /* put initial format string into index zero of the format string stack */
   idx = 0;
   FormatStack[idx] = FormatString;

   /*************************/
   /* nested format strings */
   /*************************/

   for (;;)
   {
      /*****************/
      /* format string */
      /*****************/

      fptr = FormatStack[idx];
      while (*fptr)
      {
         while (*fptr && *fptr != '!' && ofw)
         {
            /* a literal character */
            if (brcnt--)
            {
               *bptr++ = *fptr++;
               bcnt++; 
               ofw--;
               continue;
            }
            /* if writing to storage then it's overflowed */
            if (BufferPtr)
            {
               BufferPtr[BufferSize-1] = '\0';
               return (SS$_BUFFEROVF);
            }
            /* need another network buffer */
            NetWriteBufferedInit (rqptr, false);
            bptr = rqptr->rqOutput.BufferCurrentPtr;
            brcnt = rqptr->rqOutput.BufferRemaining;
         }
         /* if end of literal characters (i.e. not a '!') then break */
         if (!*fptr) break;

         /* if no character follows the '!' then just forget it */
         LastFptr = fptr;
         if (!*++fptr) break;

         /*********************/
         /* format directives */
         /*********************/

         if (DEBUG_FAO_WRITE)  
            fprintf (stdout, "fptr |%10.10s|\n", fptr);

         /*********************/
         /* field width, etc. */
         /*********************/

         if (isdigit(*fptr))
         {
            /* field-width */
            fw = atoi(fptr);
            while (isdigit(*fptr)) fptr++;
            /* for writes into network buffers, let's keep this < 1024 */
            if (!BufferPtr) fw &= 0xfff;
         }
         else
         if (*fptr == '#')
         {
            /* field-width from vector */
            fptr++;
            fw = (int)*VectorPtr++;
            /* for writes into network buffers, let's keep this < 1024 */
            if (!BufferPtr) fw &= 0xfff;
         }
         else
            fw = -1;
         /** if (DEBUG_FAO_WRITE)  fprintf (stdout, "fw: %d\n", fw); **/

         if (*fptr == '@')
         {
            fptr++;
            IndirectValue = true;
         }
         else
            IndirectValue = false;

         FileOds = 0;
         CommaNumber = FileSpec = ForceLowerCase = ForceUpperCase =
            HtmlEscape = QuoteIfSpace = SpaceThenString =
            TrailingNewlineString = UnderlineIfSpace = UrlEncode = false;

         while (*fptr == '&')
         {
            /************************/
            /* formatting modifiers */
            /************************/

            if (*(unsigned short*)fptr == '&,')
            {
               fptr += 2;
               CommaNumber = true;
               continue;
            }
            if (*(unsigned short*)fptr == '&;')
            {
               fptr += 2;
               HtmlEscape = true;
               continue;
            }
            if (*(unsigned short*)fptr == '&%')
            {
               fptr += 2;
               UrlEncode = true;
               continue;
            }
            if (*(unsigned short*)fptr == '&[')
            {
               fptr += 2;
               FileSpec = true;
               /* get the numeric value of the file naming schema */
               FileOds = *VectorPtr++;
               continue;
            }
            if (*(unsigned short*)fptr == '&!')
            {
               fptr += 2;
               ForceLowerCase = true;
               continue;
            }
            if (*(unsigned short*)fptr == '&^')
            {
               fptr += 2;
               ForceUpperCase = true;
               continue;
            }
            if (*(unsigned short*)fptr == '&_')
            {
               fptr += 2;
               UnderlineIfSpace = true;
               continue;
            }
            if (*(unsigned short*)fptr == '&"')
            {
               fptr += 2;
               QuoteIfSpace = true;
               continue;
            }
            if (*(unsigned short*)fptr == '&+')
            {
               fptr += 2;
               SpaceThenString = true;
               continue;
            }
            if (*(unsigned short*)fptr == '&/')
            {
               fptr += 2;
               TrailingNewlineString = true;
               continue;
            }
            break;
         }

         if (*fptr == '@')
         {
            fptr++;
            IndirectValue = true;
         }
         else
            IndirectValue = false;

         /* default is no string to copy if it drops through */
         sptr = NULL;

         /* directives in guessed order of most common usage */
         if (*(unsigned short*)fptr == 'AZ')
         {
            /**************************/
            /* null-terminated string */
            /**************************/

            fptr += 2;
            if (IndirectValue)
            {
               if (!*VectorPtr)
               {
                  status = SS$_ACCVIO;
                  break;
               }
               sptr = *(char*)*VectorPtr++;
            }
            else
               sptr = (char*)*VectorPtr++;
            if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD_FAO))
               if (!sptr)
                  WatchThis (rqptr, FI_LI, WATCH_MOD_FAO, "(null)");
            if (!sptr) sptr = "";
            /* null or empty string suppresses these */
            if (!sptr[0]) SpaceThenString = TrailingNewlineString = false;
            if (DEBUG_FAO_WRITE) fprintf (stdout, "!AZ |%s|\n", sptr);
            /* drop through to buffer the string pointed to by 'sptr' */
         }
         else
         if (*(unsigned short*)fptr == 'UL')
         {
            /*********************/
            /* unsigned longword */
            /*********************/
   
            fptr += 2;
            if (IndirectValue)
            {
               if (!*VectorPtr)
               {
                  status = SS$_ACCVIO;
                  break;
               }
               LastNumericValue = *(unsigned long*)VectorPtr++;
            }
            else
               LastNumericValue = *VectorPtr++;
            if (DEBUG_FAO_WRITE) 
               fprintf (stdout, "!UL %d\n", LastNumericValue);
            if (fw > 0 && !CommaNumber)
               status = sys$fao (&ULWidthFaoDsc, 0, &StringDsc,
                                 fw, LastNumericValue);
            else
               status = sys$fao (&ULFaoDsc, 0, &StringDsc,
                                 LastNumericValue);
            if (VMSnok (status)) break;
            if (!CommaNumber) fw = -1;
            sptr = String;
            /* drop through to buffer the string pointed to by 'sptr' */
         }
         else
         if (*(unsigned short*)fptr == 'XL')
         {
            /************************/
            /* hexadecimal longword */
            /************************/

            fptr += 2;
            if (IndirectValue)
            {
               if (!*VectorPtr)
               {
                  status = SS$_ACCVIO;
                  break;
               }
               LastNumericValue = *(unsigned long*)VectorPtr++;
            }
            else
               LastNumericValue = *VectorPtr++;
            if (DEBUG_FAO_WRITE) 
               fprintf (stdout, "!UL %d\n", LastNumericValue);
            if (fw > 0 && !CommaNumber)
               status = sys$fao (&XLWidthFaoDsc, 0, &StringDsc,
                                 fw, LastNumericValue);
            else
               status = sys$fao (&XLFaoDsc, 0, &StringDsc,
                                 LastNumericValue);
            if (VMSnok (status)) break;
            if (!CommaNumber) fw = -1;
            sptr = String;
            /* drop through to buffer the string pointed to by 'sptr' */
         }
         else
         if (*(unsigned short*)fptr == 'ZL')
         {
            /************************/
            /* zero-filled longword */
            /************************/

            fptr += 2;
            if (IndirectValue)
            {
               if (!*VectorPtr)
               {
                  status = SS$_ACCVIO;
                  break;
               }
               LastNumericValue = *(unsigned long*)VectorPtr++;
            }
            else
               LastNumericValue = *VectorPtr++;
            if (DEBUG_FAO_WRITE) 
               fprintf (stdout, "!UL %d\n", LastNumericValue);
            if (fw > 0 && !CommaNumber)
               status = sys$fao (&ZLWidthFaoDsc, 0, &StringDsc,
                                 fw, LastNumericValue);
            else
               status = sys$fao (&ZLFaoDsc, 0, &StringDsc,
                                 LastNumericValue);
            if (VMSnok (status)) break;
            if (!CommaNumber) fw = -1;
            sptr = String;
            /* drop through to buffer the string pointed to by 'sptr' */
         }
         else
         if (*(unsigned short*)fptr == '&@' ||
             *(unsigned short*)fptr == '%%')  /* for backward compatibility */
         {
            /************************/
            /* nested format string */
            /************************/

            fptr += 2;
            if (idx >= FORMAT_STACK_MAX)
            {
               status = SS$_OVRMAXARG;
               break;
            }
            if (IndirectValue)
            {
               if (!*VectorPtr)
               {
                  status = SS$_ACCVIO;
                  break;
               }
               cptr = *(char*)*VectorPtr++;
            }
            else
               cptr = (char*)*VectorPtr++;
            if (!cptr)
            {
               status = SS$_ACCVIO;
               break;
            }
            /* if empty then just continue with the current format string */
            if (!cptr[0]) continue;
            /* save the current format string pointer */
            FormatStack[idx++] = fptr;
            /* set the new format string pointer */
            fptr = FormatStack[idx] = cptr;
            if (DEBUG_FAO_WRITE) 
               fprintf (stdout, "!&@ idx: %d fptr |%s|\n", idx, fptr);
            continue;
         }
         else
         if (*(unsigned short*)fptr == '&h' ||
             *(unsigned short*)fptr == '&H')
         {
            /***************/
            /* hexadecimal */
            /***************/
   
            fptr += 2;
            if (IndirectValue)
            {
               if (!*VectorPtr)
               {
                  status = SS$_ACCVIO;
                  break;
               }
               cptr = *(char*)*VectorPtr++;
            }
            else
               cptr = (char*)*VectorPtr++;
            if (!cptr)
               sptr = "(null)";
            else
            {
               if (*(unsigned short*)fptr == '&h')
                  HexDigitsPtr = HexDigits;
               else
                  HexDigitsPtr = HexDigitsUpper;
               zptr = (sptr = String) + sizeof(String)-1;
               if (fw <= 0) fw = 32;
               while (fw-- > 0)
               {
                  if (sptr >= zptr) break;
                  *sptr++ = HexDigitsPtr[(*(unsigned char*)cptr >> 4) & 0x0f];
                  if (sptr >= zptr) break;
                  *sptr++ = HexDigitsPtr[*(unsigned char*)cptr & 0x0f];
                  cptr++;
               }
               *sptr = '\0';
               sptr = String;
            }
            fw = -1;
            /* drop through to buffer the string pointed to by 'sptr' */
         }
         else
         if (*(unsigned short*)fptr == '&I')
         {
            /**************************************/
            /* dotted-decimal/IPv6-hex IP address */
            /**************************************/
   
            fptr += 2;
            if (IndirectValue)
            {
               if (!*VectorPtr)
               {
                  status = SS$_ACCVIO;
                  break;
               }
               LastNumericValue = *(unsigned long*)VectorPtr++;
            }
            else
               LastNumericValue = *VectorPtr++;
            /* 'LastNumericValue' can be a pointer or an integer */
            sptr = TcpIpAddressToString (LastNumericValue, fw);
            fw = -1;
            /* drop through to buffer the string pointed to by 'sptr' */
         }
         else
         if (*(unsigned short*)fptr == '&L')
         {
            /*****************/
            /* the next line */
            /*****************/

            fptr += 2;
            if (IndirectValue)
            {
               if (!*VectorPtr)
               {
                  status = SS$_ACCVIO;
                  break;
               }
               sptr = *(char*)*VectorPtr++;
            }
            else
               sptr = (char*)*VectorPtr++;
            if (sptr)
            {
               for (zptr = sptr;
                    *zptr && *zptr != '\r' && *zptr != '\n';
                    zptr++);
               /* use field-width to control how much of the string is output */
               fw = zptr - sptr;
               if (DEBUG_FAO_WRITE)
                  fprintf (stdout, "!&L |%*.*s|\n", fw, fw, sptr);
               status = sys$fao (&LengthStringFaoDsc, 0, &StringDsc, fw, sptr);
               sptr = String;
            }
            else
               sptr = "{0}(null)";
            fw = -1;
            /* drop through to buffer the string pointed to by 'sptr' */
         }
         else
         if (*(unsigned short*)fptr == '&M' ||
             *(unsigned short*)fptr == '&m')
         {
            /******************/
            /* status message */
            /******************/

            if (*(unsigned short*)fptr == '&M')
               SysGetMsgFlags = 15;
            else
               SysGetMsgFlags = 1;
            fptr += 2;
            if (IndirectValue)
            {
               if (!*VectorPtr)
               {
                  status = SS$_ACCVIO;
                  break;
               }
               VectorValue = *(unsigned long*)VectorPtr++;
            }
            else
               VectorValue = *VectorPtr++;
            if (DEBUG_FAO_WRITE) 
               fprintf (stdout, "!%%M %d\n", VectorValue);

            status = sys$getmsg (VectorValue, &Length, &StringDsc,
                                 SysGetMsgFlags, 0);
            if (VMSnok (status))
               sptr = "*ERROR* sys$getmsg()";
            else
            {
               (sptr = String)[Length] = '\0';
               if (SysGetMsgFlags == 15) sptr++;
            }
            if (DEBUG_FAO_WRITE) 
               fprintf (stdout, "sys$getmsg() |%s|\n", String);
            /* drop through to buffer the string pointed to by 'sptr' */
         }
         else
         if (*(unsigned short*)fptr == 'SQ')
         {
            /*******************/
            /* signed quadword */
            /*******************/
   
            /*
               Note that the "!SQ" directive is being used, for VMS 6.2
               the "!UQ" && "!UJ" loop infinitely for signed values.
               Also note the indirect character MUST be used (i.e. "!@SQ").
               Also that VAX doesn't support it - so a kludge is employed.
            */

            fptr += 2;
            if (IndirectValue)
            {
               if (!*VectorPtr)
               {
                  status = SS$_ACCVIO;
                  break;
               }
               LastNumericValue = *((unsigned long*)(*VectorPtr));
            }
            else
            {
               /* must be indirect */
               status = SS$_BADPARAM;
               break;
            }
            if (DEBUG_FAO_WRITE) 
               fprintf (stdout, "!SQ %d\n", LastNumericValue);
#ifdef __VAX
            /* kludge, apparently ok all the way up to 53 bits */
            dValue = (double)((unsigned long*)(*VectorPtr))[0];
            dValue += (double)((unsigned long*)(*VectorPtr))[1] * 4294967296.0;
            VectorPtr++;
            status = SS$_NORMAL;
            if (fw > 0 && !CommaNumber)
               sprintf (String, "%*.0f", fw, dValue);
            else
               sprintf (String, "%.0f", dValue);
#else /* Alpha or ia64 */
            if (fw > 0 && !CommaNumber)
               status = sys$fao (&SQWidthFaoDsc, 0, &StringDsc,
                                 fw, *VectorPtr++);
            else
               status = sys$fao (&SQFaoDsc, 0, &StringDsc,
                                 *VectorPtr++);
#endif /* ifdef __VAX */
            if (VMSnok (status)) break;
            if (!CommaNumber) fw = -1;
            sptr = String;
            /* drop through to buffer the string pointed to by 'sptr' */
         }
         else
         if (*(unsigned short*)fptr == 'SL')
         {
            /*******************/
            /* signed longword */
            /*******************/
   
            fptr += 2;
            if (IndirectValue)
            {
               if (!*VectorPtr)
               {
                  status = SS$_ACCVIO;
                  break;
               }
               LastNumericValue = *(unsigned long*)VectorPtr++;
            }
            else
               LastNumericValue = *VectorPtr++;
            if (DEBUG_FAO_WRITE) 
               fprintf (stdout, "!SL %d\n", LastNumericValue);
            if (fw > 0 && !CommaNumber)
               status = sys$fao (&SLWidthFaoDsc, 0, &StringDsc,
                                 fw, LastNumericValue);
            else
               status = sys$fao (&SLFaoDsc, 0, &StringDsc,
                                 LastNumericValue);
            if (VMSnok (status)) break;
            if (!CommaNumber) fw = -1;
            sptr = String;
            /* drop through to buffer the string pointed to by 'sptr' */
         }
         else
         if (*(unsigned short*)fptr == '%D' ||
             *(unsigned short*)fptr == '&D')
         {
            /*************/
            /* date/time */
            /*************/

            cptr = fptr;
            fptr += 2;
            if (IndirectValue)
            {
               if (!*VectorPtr)
               {
                  status = SS$_ACCVIO;
                  break;
               }
               BinTimePtr = *(unsigned long*)VectorPtr++;
            }
            else
               BinTimePtr = *VectorPtr++;
            if (DEBUG_FAO_WRITE) 
               fprintf (stdout, "!%%D %d\n", BinTimePtr);

            if (!BinTimePtr) sys$gettim (BinTimePtr = &BinTime);
            if (!BinTimePtr[0] && !BinTimePtr[1])
            {
               sptr = "(none)";
               if (fw > 0) fw = -1;
            }
            else
            {
               status = sys$fao (&PDFaoDsc, 0, &StringDsc, BinTimePtr);
               if (VMSnok (status)) break;

               for (sptr = String; *sptr && *sptr != '-'; sptr++);
               /* absolute times have leading space changed to leading zero */
               if (*sptr && String[0] == ' ')
                  (sptr = String)[0] = '0';
               else
               if (*sptr)
                  sptr = String;
               else
                  /* delta times have leading spaces absorbed */
                  for (sptr = String; *sptr && *sptr == ' '; sptr++);

               if (*(unsigned short*)cptr == '&D')
               {
                  /* massage the time a little further */
                  for (cptr = sptr; *cptr && *cptr != ' '; cptr++);
                  if (*cptr)
                     if (sptr[2] == '-')
                        *cptr++ = ':';
                     else
                        *cptr++ = '-';
                  while (*cptr && *cptr != ':') cptr++;
                  if (*cptr) cptr++;
                  while (*cptr && *cptr != ':') cptr++;
                  *cptr = '\0';
               }
            }
            /* drop through to buffer the string pointed to by 'sptr' */
         }
         else
         if (*(unsigned short*)fptr == '%I')
         {
            /***********************/
            /* numeric to ASCII ID */
            /***********************/

            fptr += 2;
            if (IndirectValue)
            {
               if (!*VectorPtr)
               {
                  status = SS$_ACCVIO;
                  break;
               }
               VectorValue = *(unsigned long*)VectorPtr++;
            }
            else
               VectorValue = *VectorPtr++;
            if (DEBUG_FAO_WRITE) 
               fprintf (stdout, "!%%D %d\n", VectorValue);

            status = sys$fao (&PIFaoDsc, 0, &StringDsc, VectorValue);
            if (VMSnok (status)) break;
            sptr = String;
            /* drop through to buffer the string pointed to by 'sptr' */
         }
         else
         if (*(unsigned short*)fptr == '%T')
         {
            /********/
            /* time */
            /********/

            fptr += 2;
            if (IndirectValue)
            {
               if (!*VectorPtr)
               {
                  status = SS$_ACCVIO;
                  break;
               }
               BinTimePtr = *(unsigned long*)VectorPtr++;
            }
            else
               BinTimePtr = *VectorPtr++;
            if (DEBUG_FAO_WRITE) 
               fprintf (stdout, "!%%T %d\n", BinTimePtr);

            if (!BinTimePtr) sys$gettim (BinTimePtr = &BinTime);
            if (!BinTimePtr[0] && !BinTimePtr[1])
            {
               sptr = "(none)";
               if (fw > 0) fw = -1;
            }
            else
            {
               status = sys$fao (&PTFaoDsc, 0, &StringDsc, BinTimePtr);
               if (VMSnok (status)) break;
               sptr = String;
            }
            /* drop through to buffer the string pointed to by 'sptr' */
         }
         else
         if (*(unsigned short*)fptr == '%s' ||
             *(unsigned short*)fptr == '%S')
         {
            /******************/
            /* add 'S' or 's' */
            /******************/

            if (DEBUG_FAO_WRITE) 
               fprintf (stdout, "!%%%c\n", *(fptr+1));
            if (LastNumericValue == 1)
            {
               fptr += 2;
               continue;
            }
            fptr++;
            fw = -1;
            if (*fptr++ == 's')
               sptr = "s";
            else
               sptr = "S";
            /* drop through to buffer the string pointed to by 'sptr' */
         }
         else
         if (*(unsigned short*)fptr == 'AC')
         {
            /************************/
            /* counted ASCII string */
            /************************/

            fptr += 2;
            if (IndirectValue)
            {
               if (!*VectorPtr)
               {
                  status = SS$_ACCVIO;
                  break;
               }
               cptr = *(char*)*VectorPtr++;
            }
            else
               cptr = (char*)*VectorPtr++;
            if (!cptr)
            {
               status = SS$_ACCVIO;
               break;
            }
            if (DEBUG_FAO_WRITE) 
               fprintf (stdout, "!AC %d |%s|\n", (unsigned int)*cptr, cptr+1);

            /* get the count byte */
            ch = (unsigned int)*cptr++;
            while (ch)
            {
               if (brcnt--)
               {
                  *bptr++ = *cptr++;
                  bcnt++; 
                  ch--;
                  continue;
               }
               /* if writing to storage then it's overflowed */
               if (BufferPtr)
               {
                  BufferPtr[BufferSize-1] = '\0';
                  return (SS$_BUFFEROVF);
               }
               /* need another network buffer */
               NetWriteBufferedInit (rqptr, false);
               bptr = rqptr->rqOutput.BufferCurrentPtr;
               brcnt = rqptr->rqOutput.BufferRemaining;
            }
            /* only if required drop through to space-fill */
            if (fw <= 0) continue;
            /* no string to copy  */
            sptr = NULL;
         }
         else
         if (*(unsigned short*)fptr == '&C')
         {
            /* a single character */
            fptr += 2;
            String[0] = *VectorPtr++;
            String[1] = '\0';
            sptr = String;
            fw = -1;
            /* drop through to buffer the string pointed to by 'sptr' */
         }
         else
         if (*(unsigned short*)fptr == '&P')
         {
            /*****************/
            /* mask password */
            /*****************/

            fptr += 2;
            cptr = (char*)*VectorPtr++;
            if (!cptr) cptr = "(null)";
            zptr = (sptr = String) + sizeof(String)-1;
            while (*cptr && sptr < zptr)
            {
               if (cptr[0] == ':' && cptr[1] == '/' && cptr[2] == '/') break;
               *sptr++ = *cptr++; 
            }
            if (*cptr)
            {
               if (sptr < zptr) *sptr++ = *cptr++;
               if (sptr < zptr) *sptr++ = *cptr++;
               if (sptr < zptr) *sptr++ = *cptr++;
               while (*cptr && *cptr != '/' && *cptr != '@' && sptr < zptr)
                  *sptr++ = *cptr++;
               if (*cptr == '@')
               {
                  if (sptr < zptr) *sptr++ = *cptr++;
                  while (sptr > String && *sptr != ':') sptr--;
                  if (*sptr == ':')
                  {
                     sptr++;
                     while (*sptr && *sptr != '@') *sptr++ = '*';
                     if (*sptr) sptr++;
                  }
               }
               while (*cptr && sptr < zptr) *sptr++ = *cptr++;
            }
            *sptr = '\0';
            sptr = String;
            /* drop through to buffer the string pointed to by 'sptr' */
         }
         else
         if (*(unsigned short*)fptr == '&U')
         {
            /******************/
            /* encode URL/URI */
            /******************/

            fptr += 2;
            cptr = (char*)*VectorPtr++;
            if (cptr)
               StringUrlEncodeURL (cptr, sptr = String, sizeof(String));
            else
               sptr = "(null)";
            /* drop through to buffer the string pointed to by 'sptr' */
         }
         else
         if (*(unsigned short*)fptr == '&W')
         {
            /*********************/
            /* weekday/date/time */
            /*********************/

            fptr += 2;
            if (IndirectValue)
            {
               if (!*VectorPtr)
               {
                  status = SS$_ACCVIO;
                  break;
               }
               BinTimePtr = *(unsigned long*)VectorPtr++;
            }
            else
               BinTimePtr = *VectorPtr++;
            if (DEBUG_FAO_WRITE) 
               fprintf (stdout, "!%%W %d\n", BinTimePtr);

            if (!BinTimePtr) sys$gettim (BinTimePtr = &BinTime);
            if (!BinTimePtr[0] && !BinTimePtr[1])
            {
               sptr = "(none)";
               if (fw > 0) fw = -1;
            }
            else
            if (VMSnok (lib$day_of_week (BinTimePtr, &DayOfWeek)))
               sptr = "*ERROR*1";
            else
            if (VMSnok (sys$fao (&DayDateTimeFaoDsc, 0, &StringDsc,
                DayName[DayOfWeek], fw > 0 ? fw : 23, BinTimePtr)))
               sptr = "*ERROR*2";
            else
               sptr = String;
            /* reset the field-width (only applies to date/time) */
            if (fw > 0) fw = -1;
            /* drop through to buffer the string pointed to by 'sptr' */
         }
         else
         if (*(unsigned short*)fptr == '&R')
         {
            /************************/
            /* radio button boolean */
            /************************/

            fptr += 2;
            if (IndirectValue)
            {
               if (!*VectorPtr)
               {
                  status = SS$_ACCVIO;
                  break;
               }
               VectorValue = *(unsigned long*)VectorPtr++;
            }
            else
               VectorValue = *VectorPtr++;
            if (DEBUG_FAO_WRITE) 
               fprintf (stdout, "!&? %d\n", VectorValue);

            if (VectorValue) sptr = " CHECKED"; else sptr = "";
            fw = -1;
            /* drop through to buffer the string pointed to by 'sptr' */
         }
         else
         if (*(unsigned short*)fptr == '&O')
         {
            /*************************/
            /* select option boolean */
            /*************************/

            fptr += 2;
            if (IndirectValue)
            {
               if (!*VectorPtr)
               {
                  status = SS$_ACCVIO;
                  break;
               }
               VectorValue = *(unsigned long*)VectorPtr++;
            }
            else
               VectorValue = *VectorPtr++;
            if (DEBUG_FAO_WRITE) 
               fprintf (stdout, "!&? %d\n", VectorValue);

            if (VectorValue) sptr = " SELECTED"; else sptr = "";
            fw = -1;
            /* drop through to buffer the string pointed to by 'sptr' */
         }
         else
         if (*(unsigned short*)fptr == '&?')
         {
            /**********************/
            /* simple conditional */
            /**********************/

            /* e.g. "!&?string if *vector TRUE\rstring if FALSE\r" */
            fptr += 2;
            if (IndirectValue)
            {
               if (!*VectorPtr)
               {
                  status = SS$_ACCVIO;
                  break;
               }
               VectorValue = *(unsigned long*)VectorPtr++;
            }
            else
               VectorValue = *VectorPtr++;
            if (DEBUG_FAO_WRITE) 
               fprintf (stdout, "!&? %d\n", VectorValue);

            if (!VectorValue)
            {
               /* if false skip over the true string */
               while (*fptr && *fptr != '\r') fptr++;
               if (*fptr == '\r') fptr++;
               if (!*fptr) break;
            }
            while (*fptr && *fptr != '\r' && fw && ofw)
            {
               if (brcnt--)
               {
                  *bptr++ = *fptr++;
                  bcnt++; 
                  fw--;
                  ofw--;
                  continue;
               }
               /* if writing to storage then it's overflowed */
               if (BufferPtr)
               {
                  BufferPtr[BufferSize-1] = '\0';
                  return (SS$_BUFFEROVF);
               }
               /* need another network buffer */
               NetWriteBufferedInit (rqptr, false);
               bptr = rqptr->rqOutput.BufferCurrentPtr;
               brcnt = rqptr->rqOutput.BufferRemaining;
            }
            if (*fptr != '\r')
            {
               /* field-width has expired before the return */
               while (*fptr && *fptr != '\r') fptr++;
            }
            if (*fptr == '\r') fptr++;
            if (VectorValue)
            {
               /* was true, skip over the false string */
               while (*fptr && *fptr != '\r') fptr++;
               if (*fptr == '\r') fptr++;
            }
            /* only if required drop through to space-fill */
            if (fw <= 0) continue;
         }
         else
         if (*fptr == '+')
         {
            /**************************/
            /* discard next parameter */
            /**************************/

            fptr++;
            VectorValue = *VectorPtr++;
            if (DEBUG_FAO_WRITE) 
               fprintf (stdout, "!+ %d\n", VectorValue);
         }
         else
         if (*fptr == '-')
         {
            /****************************/
            /* reuse previous parameter */
            /****************************/

            fptr++;
            VectorValue = *--VectorPtr;
            if (DEBUG_FAO_WRITE) 
               fprintf (stdout, "!- %d\n", VectorValue);
         }
         else
         if (*fptr == '!')
         {
            /****************************/
            /* an escaped '!' character */
            /****************************/

            if (DEBUG_FAO_WRITE)  fprintf (stdout, "!!\n");
            fptr++;
            sptr = "!";
            /* drop through to buffer the string pointed to by 'sptr' */
         }
         else
         if (*fptr == '*')
         {
            /*****************************/
            /* multiple '*'+1 characters */
            /*****************************/

            if (DEBUG_FAO_WRITE)  fprintf (stdout, "!*\n");
            if (!*++fptr) fw = 0;
            while (fw > 0 && ofw)
            {
               if (brcnt--)
               {
                  *bptr++ = *fptr;
                  bcnt++; 
                  fw--;
                  ofw--;
                  continue;
               }
               /* if writing to storage then it's overflowed */
               if (BufferPtr)
               {
                  BufferPtr[BufferSize-1] = '\0';
                  return (SS$_BUFFEROVF);
               }
               /* need another network buffer */
               NetWriteBufferedInit (rqptr, false);
               bptr = rqptr->rqOutput.BufferCurrentPtr;
               brcnt = rqptr->rqOutput.BufferRemaining;
            }
            if (*fptr) fptr++;
            continue;
         }
         else
         if (*fptr == '<')
         {
            /****************************/
            /* begin output-field-width */
            /****************************/

            if (DEBUG_FAO_WRITE)  fprintf (stdout, "!<\n");
            fptr++;
            if (ofw >= 0)
            {
               /* can't nest these little blighters! */
               status = SS$_BADPARAM;
               break;
            }
            ofw = fw;
            continue;
         }
         else
         if (*fptr == '>')
         {
            /**************************/
            /* end output-field-width */
            /**************************/

            if (DEBUG_FAO_WRITE)  fprintf (stdout, "!>\n");
            fptr++;
            if (ofw < 0)
            {
               /* didn't notice a leading "!<" */
               status = SS$_BADPARAM;
               break;
            }
            /* get (any) current outstanding that needs space-filling */
            fw = ofw;
            /* reset output-field-width to infinite */
            ofw = -1;
            /* only if required drop through to space-fill */
            if (fw <= 0) continue;
         }

         /***********************/
         /* mainly for WATCHing */
         /***********************/
   
         else
         if (*(unsigned short*)fptr == '&A')
         {
            /* an "address" as &x00000000 */
            fptr += 2;
            LastNumericValue = *VectorPtr++;
            if (!(cptr = WatchFunction (LastNumericValue))) cptr = "?";
            status = sys$fao (&WatchingAddressFaoDsc, 0, &StringDsc,
                              LastNumericValue, cptr);
            if (VMSnok (status)) break;
            fw = -1;
            sptr = String;
            /* drop through to buffer the string pointed to by 'sptr' */
         }
         else
         if (*(unsigned short*)fptr == '&B')
         {
            /* a boolean */
            fptr += 2;
            LastNumericValue = *VectorPtr++;
            if (LastNumericValue) sptr = "TRUE"; else sptr = "FALSE";
            fw = -1;
            /* drop through to buffer the string pointed to by 'sptr' */
         }
         else
         if (*(unsigned short*)fptr == '&F')
         {
            /* a "function" address as &00000000 */
            fptr += 2;
            LastNumericValue = *VectorPtr++;
            status = sys$fao (&WatchingFunctionFaoDsc, 0, &StringDsc,
                              LastNumericValue);
            if (VMSnok (status)) break;
            fw = -1;
            sptr = String;
            /* drop through to buffer the string pointed to by 'sptr' */
         }
         else
         if (*(unsigned short*)fptr == '&S')
         {
            /* VMS status value as %X00000000 */
            fptr += 2;
            LastNumericValue = *VectorPtr++;
            status = sys$fao (&VmsStatusFaoDsc, 0, &StringDsc,
                              LastNumericValue);
            if (VMSnok (status)) break;
            fw = -1;
            sptr = String;
            /* drop through to buffer the string pointed to by 'sptr' */
         }
         else
         if (*(unsigned short*)fptr == '&X')
         {
            /* hexadecimal as 0x00000000 */
            fptr += 2;
            LastNumericValue = *VectorPtr++;
            status = sys$fao (&HexValueFaoDsc, 0, &StringDsc,
                              LastNumericValue);
            if (VMSnok (status)) break;
            fw = -1;
            sptr = String;
            /* drop through to buffer the string pointed to by 'sptr' */
         }
         else
         if (*(unsigned short*)fptr == '&Z')
         {
            /* a null-terminated string as {length}string */
            fptr += 2;
            sptr = (char*)*VectorPtr++;
            if (sptr)
            {
               status = sys$fao (&LengthStringFaoDsc, 0, &StringDsc,
                                 strlen(sptr), sptr);
               sptr = String;
            }
            else
               sptr = "{0}(null)";
            if (DEBUG_FAO_WRITE)  fprintf (stdout, "!AZ |%s|\n", sptr);
            /* drop through to buffer the string pointed to by 'sptr' */
         }
         else
         {
            status = SS$_BADPARAM;
            break;
         }

         /*********************************/
         /* copy a string into the buffer */
         /*********************************/

         if (DEBUG_FAO_WRITE) 
            fprintf (stdout, "sptr |%s|\n", sptr ? sptr : "(null)");

         if (sptr)
         {
            if (CommaNumber)
            {
               /****************/
               /* comma number */
               /****************/

               Length = strlen(sptr);
               while (fw && ofw && Length + ((Length-1) / 3) < fw)
               {
                  if (brcnt--)
                  {
                     *bptr++ = ' ';
                     bcnt++; 
                     fw--;
                     ofw--;
                     continue;
                  }
                  /* if writing to storage then it's overflowed */
                  if (BufferPtr)
                  {
                     BufferPtr[BufferSize-1] = '\0';
                     return (SS$_BUFFEROVF);
                  }
                  /* need another network buffer */
                  NetWriteBufferedInit (rqptr, false);
                  bptr = rqptr->rqOutput.BufferCurrentPtr;
                  brcnt = rqptr->rqOutput.BufferRemaining;
               }
               if (((Length-1) / 3) >= 1)
               {
                  if (!(CommaCount = Length % 3))
                     CommaCount = 3;

                  while (*sptr && fw && ofw)
                  {
                     if (brcnt--)
                     {
                        if (!CommaCount--)
                        {
                           *bptr++ = ',';
                           bcnt++; 
                           fw--;
                           ofw--;
                           CommaCount = 3;
                           continue;
                        }
                        *bptr++ = *sptr++;
                        bcnt++; 
                        fw--;
                        ofw--;
                        continue;
                     }
                     /* if writing to storage then it's overflowed */
                     if (BufferPtr)
                     {
                        BufferPtr[BufferSize-1] = '\0';
                        return (SS$_BUFFEROVF);
                     }
                     /* need another network buffer */
                     NetWriteBufferedInit (rqptr, false);
                     bptr = rqptr->rqOutput.BufferCurrentPtr;
                     brcnt = rqptr->rqOutput.BufferRemaining;
                  }
                  /* only if required drop through to space-fill */
                  if (fw <= 0) continue;
                  sptr = NULL;
               }
               /* drop thru to copy 3 or less digits into the buffer */
               FileSpec = HtmlEscape = QuoteIfSpace = SpaceThenString =
                  TrailingNewlineString = UnderlineIfSpace = UrlEncode = false;
            }
         }

         if (sptr)
         {
            if (FileSpec)
            {
               /**********************/
               /* file specification */
               /**********************/

               switch (FileOds)
               {
                  case 0 :
                  case MAPURL_PATH_ODS_2 :
                     if (!ForceUpperCase) ForceLowerCase = true;
                     /* just leave 'sptr' the way it is! */
                     break;

#ifdef ODS_EXTENDED
                  case MAPURL_PATH_ODS_5 :
                     MapUrl_Ods5VmsToUrl (FileString, sptr, 
                                          sizeof(FileString), true);
                     sptr = FileString;
                     break;
#endif /* ODS_EXTENDED */

                  case MAPURL_PATH_ODS_ADS :
                  case MAPURL_PATH_ODS_SMB :
                     MapUrl_AdsVmsToUrl (FileString, sptr,
                                         sizeof(FileString), true);
                     sptr = FileString;
                     break;

                  case MAPURL_PATH_ODS_PWK :
                     MapUrl_PwkVmsToUrl (FileString, sptr,
                                         sizeof(FileString), true);
                     sptr = FileString;
                     break;

                  case MAPURL_PATH_ODS_SRI :
                     MapUrl_SriVmsToUrl (FileString, sptr,
                                         sizeof(FileString), true);
                     sptr = FileString;
                     break;

                  default :
                     return (SS$_BADPARAM);
               }
            }

            if (QuoteIfSpace || SpaceThenString || UnderlineIfSpace)
            {
               for (cptr = sptr; *cptr && *cptr != ' '; cptr++);
               if (*cptr || SpaceThenString)
               {
                  if (QuoteIfSpace)
                     cptr = "\"";
                  else
                  if (SpaceThenString)
                     cptr = " ";
                  else
                     cptr = "<U>";
                  while (*cptr)
                  {
                     if (brcnt--)
                     {
                        *bptr++ = *cptr++;
                        bcnt++; 
                        continue;
                     }
                     /* if writing to storage then it's overflowed */
                     if (BufferPtr)
                     {
                        BufferPtr[BufferSize-1] = '\0';
                        return (SS$_BUFFEROVF);
                     }
                     /* need another network buffer */
                     NetWriteBufferedInit (rqptr, false);
                     bptr = rqptr->rqOutput.BufferCurrentPtr;
                     brcnt = rqptr->rqOutput.BufferRemaining;
                  }
               }
               else
                  QuoteIfSpace = UnderlineIfSpace = false;
            }

            if (HtmlEscape)
            {
               /**********************/
               /* HTML-escape string */
               /**********************/

               while (*sptr && fw && ofw)
               {
                  fw--;
                  ofw--;
                  if (ForceLowerCase)
                     ch = tolower(*sptr++);
                  else
                  if (ForceUpperCase)
                     ch = toupper(*sptr++);
                  else
                     ch = *sptr++;
                  switch (ch)
                  {
                     case '<' :
                        cptr = "&lt;";
                        break;
                     case '>' :
                        cptr = "&gt;";
                        break;
                     case '&' :
                        cptr = "&amp;";
                        break;
                     case '\"' :
                        if (QuoteIfSpace)
                           cptr = "\\&quot;";
                        else
                           cptr = "&quot;";
                        break;
                     default :
                        /* insert this character as-is */
                        if (!brcnt)
                        {
                           /* if writing to storage then it's overflowed */
                           if (BufferPtr)
                           {
                              BufferPtr[BufferSize-1] = '\0';
                              return (SS$_BUFFEROVF);
                           }
                           /* need another network buffer */
                           NetWriteBufferedInit (rqptr, false);
                           bptr = rqptr->rqOutput.BufferCurrentPtr;
                           brcnt = rqptr->rqOutput.BufferRemaining;
                        }
                        *bptr++ = ch;
                        bcnt++; 
                        brcnt--;
                        continue;
                  }

                  /* add the escaped character */
                  while (*cptr)
                  {
                     if (brcnt--)
                     {
                        *bptr++ = *cptr++;
                        bcnt++; 
                        continue;
                     }
                     /* if writing to storage then it's overflowed */
                     if (BufferPtr)
                     {
                        BufferPtr[BufferSize-1] = '\0';
                        return (SS$_BUFFEROVF);
                     }
                     /* need another network buffer */
                     NetWriteBufferedInit (rqptr, false);
                     bptr = rqptr->rqOutput.BufferCurrentPtr;
                     brcnt = rqptr->rqOutput.BufferRemaining;
                  }
               }
            }
            else
            if (UrlEncode)
            {
               /*********************/
               /* URL-encode string */
               /*********************/

               while (*sptr && fw && ofw)
               {
                  fw--;
                  ofw--;
                  if (ForceLowerCase)
                     ch = tolower(*sptr++);
                  else
                  if (ForceUpperCase)
                     ch = toupper(*sptr++);
                  else
                     ch = *sptr++;

                  if (QuoteIfSpace && ch == '\"')
                     cptr = "%5c%22";
                  else
                     cptr = FaoUrlEncodeTable[(unsigned char)ch];
                  while (*cptr)
                  {
                     if (brcnt--)
                     {
                        *bptr++ = *cptr++;
                        bcnt++; 
                        continue;
                     }
                     /* if writing to storage then it's overflowed */
                     if (BufferPtr)
                     {
                        BufferPtr[BufferSize-1] = '\0';
                        return (SS$_BUFFEROVF);
                     }
                     /* need another network buffer */
                     NetWriteBufferedInit (rqptr, false);
                     bptr = rqptr->rqOutput.BufferCurrentPtr;
                     brcnt = rqptr->rqOutput.BufferRemaining;
                  }
               }
            }
            else
            {
               /******************/
               /* literal string */
               /******************/

               ch = '\0';
               while (*sptr && fw && ofw)
               {
                  if (brcnt--)
                  {
                     if (ch)
                     {
                        *bptr++ = ch;
                        ch = '\0';
                        sptr++;
                     }
                     else
                     if (QuoteIfSpace && *sptr == '\"')
                     {
                        *bptr++ = '\\';
                        ch = *sptr;
                     }
                     else
                     if (ForceLowerCase)
                        *bptr++ = tolower(*sptr++);
                     else
                     if (ForceUpperCase)
                        *bptr++ = toupper(*sptr++);
                     else
                        *bptr++ = *sptr++;
                     bcnt++; 
                     fw--;
                     ofw--;
                     continue;
                  }
                  /* if writing to storage then it's overflowed */
                  if (BufferPtr)
                  {
                     BufferPtr[BufferSize-1] = '\0';
                     return (SS$_BUFFEROVF);
                  }
                  /* need another network buffer */
                  NetWriteBufferedInit (rqptr, false);
                  bptr = rqptr->rqOutput.BufferCurrentPtr;
                  brcnt = rqptr->rqOutput.BufferRemaining;
               }
            }

            if (QuoteIfSpace || TrailingNewlineString || UnderlineIfSpace)
            {
               if (QuoteIfSpace)
                  cptr = "\"";
               else
               if (TrailingNewlineString)
                  cptr = "\n";
               else
                  cptr = "</U>";
               while (*cptr)
               {
                  if (brcnt--)
                  {
                     *bptr++ = *cptr++;
                     bcnt++; 
                     continue;
                  }
                  /* if writing to storage then it's overflowed */
                  if (BufferPtr)
                  {
                     BufferPtr[BufferSize-1] = '\0';
                     return (SS$_BUFFEROVF);
                  }
                  /* need another network buffer */
                  NetWriteBufferedInit (rqptr, false);
                  bptr = rqptr->rqOutput.BufferCurrentPtr;
                  brcnt = rqptr->rqOutput.BufferRemaining;
               }
            }
         }

         /***************************************************/
         /* if positive field-width, right-fill with spaces */
         /***************************************************/

         if (fw <= 0) continue;

         while (fw > 0 && ofw)
         {
            if (brcnt--)
            {
               *bptr++ = ' ';
               bcnt++; 
               fw--;
               ofw--;
               continue;
            }
            /* if writing to storage then it's overflowed */
            if (BufferPtr)
            {
               BufferPtr[BufferSize-1] = '\0';
               return (SS$_BUFFEROVF);
            }
            /* need another network buffer */
            NetWriteBufferedInit (rqptr, false);
            bptr = rqptr->rqOutput.BufferCurrentPtr;
            brcnt = rqptr->rqOutput.BufferRemaining;
         }
      }

      /*********************/
      /* end format string */
      /*********************/

      if (VMSnok (status)) break;


      /* if there are still pointers on the format stack */
      if (DEBUG_FAO_WRITE) fprintf (stdout, "idx: %d\n", idx);
      if (!idx) break;
      idx--;
   }

   /*****************************/
   /* end nested format strings */
   /*****************************/

   if (VMSnok (status))
   {
      if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD_FAO))
         WatchThis (rqptr, FI_LI, WATCH_MOD_FAO, "ERROR !AZ", LastFptr);

      /* if an error was detected append from the offending directive */
      fptr = LastFptr;
      while (*fptr)
      {
         if (brcnt--)
         {
            *bptr++ = *fptr++;
            bcnt++; 
            fw--;
            ofw--;
            continue;
         }
         /* if writing to storage then it's overflowed */
         if (BufferPtr)
         {
            BufferPtr[BufferSize-1] = '\0';
            return (SS$_BUFFEROVF);
         }
         /* need another network buffer */
         NetWriteBufferedInit (rqptr, false);
         bptr = rqptr->rqOutput.BufferCurrentPtr;
         brcnt = rqptr->rqOutput.BufferRemaining;
      }
   }

   if (DEBUG_FAO_WRITE) 
      fprintf (stdout, "%d bytes\n", bcnt - PrevBcnt);

   if (BufferPtr)
   {
      *bptr = '\0';
      if (DEBUG_FAO_WRITE)  fprintf (stdout, "|%s|\n", BufferPtr);
   }
   else
   {
      rqptr->rqOutput.BufferCurrentPtr = bptr;
      rqptr->rqOutput.BufferRemaining = brcnt;
      rqptr->rqOutput.BufferCount = bcnt;
   }

   if (LengthPtr)
   {
      if (bcnt - PrevBcnt > 65535)
      {
         /* indicate that we can't represent what we've written to buffer */
         *LengthPtr = 65535;
         status = SS$_BUFFEROVF;
      }
      else
         *LengthPtr = bcnt - PrevBcnt;
   }

   if (Watch.Category)
   {
      /* if we're WATCHing anything at all */
      if (VMSnok (status))
      {
         /* and an error is being reported */
         int  cnt;
         WatchThis (rqptr, FI_LI, WATCH_MOD_FAO, "!&S !-%!&M", status);
         WatchDataFormatted ("!&Z\n", FormatString);
         for (cnt = 0; cnt <= idx; cnt++)
            WatchDataFormatted ("!UL !&Z\n", cnt, FormatStack[cnt]);
      }
   }

   return (status);
}

/*****************************************************************************/
/*
Sanity bugchecks if the 'vectorptr' has overflowed the 'FaoVector' space. 
Should be called immedaitely before the WriteFao() call for writes that have
the potential to become buggy because of code modifications (and yes, I got
caught, and yes it cost me a few hours).
*/

FaoVectorCheck
(
int SizeOfFaoVector,
unsigned long *FaoVectorPtr,
unsigned long *VectorPtr,
char *SourceModuleName,
int SourceLineNumber
)
{
   char  String [64];

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

   if ((char*)VectorPtr - (char*)FaoVectorPtr <= SizeOfFaoVector) return;
   sprintf (String, "storage: %d vector: %d (longwords)",
            SizeOfFaoVector/4, ((char*)VectorPtr-(char*)FaoVectorPtr)/4);
   ErrorExitVmsStatus (SS$_BUGCHECK, String,
                       SourceModuleName, SourceLineNumber);
}

/*****************************************************************************/
/*
This is a variable-argument wrapper for WriteFormatted(), see that for greater
detail.  This function just gets all the arguments from the call stack and puts
them into a longword vector that WriteFormatted() can use.  'LengthPtr'
provides storage for returning the length of the generated string.  The
resultant string is always null-terminated (unless an error), hence the
'BufferSize' can always only store 'BufferSize'-1 characters.  'LengthPtr'
provides storage for the number of characters minus the null-termination.
*/

int WriteFao
(
char *BufferPtr,
int BufferSize,
unsigned short *LengthPtr,
char *FormatString,
...
)
{
   int  status,
        argcnt;
   unsigned long  *vecptr;
   unsigned long  FaoVector [128];
   va_list  argptr;

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

   va_count (argcnt);

   if WATCH_MODULE(WATCH_MOD_FAO)
      WatchThis (NULL, FI_LI, WATCH_MOD_FAO, "WriteFao() !UL", argcnt);

   if (argcnt > 128+4) return (SS$_OVRMAXARG);

   vecptr = FaoVector;
   va_start (argptr, FormatString);
   for (argcnt -= 4; argcnt; argcnt--)
      *vecptr++ = va_arg (argptr, unsigned long);
   va_end (argptr);

   status = WriteFormatted (NULL, BufferPtr, BufferSize, LengthPtr,
                            FormatString, &FaoVector);

   if WATCH_MODULE(WATCH_MOD_FAO)
      WatchThis (NULL, FI_LI, WATCH_MOD_FAO, "!&S", status);

   return (status);
}

/*****************************************************************************/
/*
This is a variable-argument wrapper for WriteFormatted(), see that for greater
detail.  This function just gets all the arguments from the call stack and puts
them into a longword vector that WriteFormatted() can use.

This function has a #define in NET.H making it available as NetWriteFao().
*/

int WriteFaoNet
(
REQUEST_STRUCT *rqptr,
char *FormatString,
...
)
{
   int  status,
        argcnt;
   unsigned long  *vecptr;
   unsigned long  FaoVector [128];
   va_list  argptr;

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

   va_count (argcnt);

   if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD_FAO))
      WatchThis (rqptr, FI_LI, WATCH_MOD_FAO, "WriteFaoNet() !UL", argcnt);

   if (argcnt > 128+2) return (SS$_OVRMAXARG);

   vecptr = FaoVector;
   va_start (argptr, FormatString);
   for (argcnt -= 2; argcnt; argcnt--)
      *vecptr++ = va_arg (argptr, unsigned long);
   va_end (argptr);

   status = WriteFormatted (rqptr, NULL, 0, NULL, FormatString, &FaoVector);

   if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD_FAO))
      WatchThis (rqptr, FI_LI, WATCH_MOD_FAO, "!&S", status);

   return (status);
}

/****************************************************************************/
/*
WriteFaol()-formatted print statement.  A fixed-size, internal buffer of 986
bytes maximum is used and the result output as an OPCOM message.
*/

int WriteFaoOpcom
(
char *FormatString,
...
)
{
   static $DESCRIPTOR (OpcomDsc, "");

   int  status,
        argcnt;
   unsigned short  Length;
   unsigned long  *vecptr;
   unsigned long  FaoVector [128+2];
   va_list  argptr;
   struct
   {
      unsigned long  TargetType;
      unsigned long  RequestId;
      char  MsgText [986+1];
   } OpcomMsg;

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

   va_count (argcnt);

   if WATCH_MODULE(WATCH_MOD_FAO)
      WatchThis (NULL, FI_LI, WATCH_MOD_FAO,
                 "WriteFaoNet() !UL !AZ", argcnt, FormatString);

   if (argcnt > 128+1) return (SS$_OVRMAXARG);

   vecptr = FaoVector;
   *vecptr++ = HttpdProcess.PrcNam;
   *vecptr++ = FormatString;
   va_start (argptr, FormatString);
   for (argcnt -= 1; argcnt; argcnt--)
      *vecptr++ = va_arg (argptr, unsigned long);
   va_end (argptr);

   status = WriteFaol (OpcomMsg.MsgText, sizeof(OpcomMsg.MsgText), &Length,
                       "Process !AZ reports\r\n!&@", &FaoVector);
   if (VMSnok (status)) ErrorNoticed (status, "WriteFaol()", FI_LI);

   if (DEBUG_FAO_WRITE) 
      fprintf (stdout, "%d |%s|\n", Length, OpcomMsg.MsgText);

   OpcomMsg.TargetType = OPC$_RQ_RQST + ((OpcomTarget & 0xffffff) << 8);
   OpcomMsg.RequestId = 0;

   OpcomDsc.dsc$a_pointer = &OpcomMsg;
   OpcomDsc.dsc$w_length = Length + 8;

   status = sys$sndopr (&OpcomDsc, 0);
   if (VMSnok (status)) ErrorNoticed (status, "sys$sndopr()", FI_LI);

   return (status);
}

/****************************************************************************/
/*
WriteFaol()-formatted print statement.  A fixed-size, internal buffer is used
and the result output to the <stdout> stream.
*/

int WriteFaoStdout
(
char *FormatString,
...
)
{
   int  status,
        argcnt;
   unsigned short  Length;
   unsigned long  *vecptr;
   unsigned long  FaoVector [128];
   char  Buffer [32767];
   va_list  argptr;

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

   va_count (argcnt);

   if WATCH_MODULE(WATCH_MOD_FAO)
      WatchThis (NULL, FI_LI, WATCH_MOD_FAO,
                 "WriteFaoStdout() !UL !AZ", argcnt, FormatString);

   if (argcnt > 128+1) return (SS$_OVRMAXARG);

   vecptr = FaoVector;
   va_start (argptr, FormatString);
   for (argcnt -= 1; argcnt; argcnt--)
      *vecptr++ = va_arg (argptr, unsigned long);
   va_end (argptr);

   status = WriteFaol (Buffer, sizeof(Buffer), &Length,
                       FormatString, &FaoVector);
   if (VMSnok (status)) ErrorNoticed (status, "WriteFaol()", FI_LI);

   fputs (Buffer, stdout);

   return (status);
}

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

