/*****************************************************************************/
/*
                                  Dir.c

This module implements a full multi-threaded, AST-driven, asynchronous 
directory listing.  The AST-driven nature makes the code a little more 
difficult to follow, but creates a powerful, event-driven, multi-threaded 
server.  All of the necessary functions implementing this module are designed 
to be non-blocking. 

This module never returns a valid status and ALWAYS calls the supplied next
task (AST) function.  This should check for a generated error message to
determine is there were any problems.

This module uses the NetWriteBuffered() function to buffer any output it can
into larger chunks before sending it to the client.

A directory listing is recognised by the server whenever a wildcard is present 
in a specification and there is no query string directing another activity 
(e.g. a query/search).  Compliant with other HTTPD implementations, a 
directory listing is also generated if a URL specifies a directory only and 
the directory contains no home page. 

The directory listing is designed to look very much like the default layout of 
the CERN and NCSA HTTPD implementations, with the exception that all 
directories are grouped at the top, not dispersed throughout the file names.  
This looks and functions better than the above (at least in the experience of 
the author). 


ACCESS CONTROL
--------------
If the server configuration specifies "DirAccessSelective" then the directory
must contain the file ".WWW_BROWSABLE" to be listed by this module.

If a directory contains the file ".WWW_HIDDEN" it cannot be listed with this
module, but individual files are accessable for server access.  If a
subdirectory contains this file it does not appear in a listing.

If a directory contains the file ".WWW_NOWILD" a wildcard directory
specification will not work even if allowed by server configuration.

If a directory contains the file ".WWW_NOP" it does not show a parent.
If a directory contains the file ".WWW_NOS" it does not show subdirectories.
If a directory contains the file ".WWW_NOPS" both the above apply.


LISTING LAYOUT
--------------
The layout of the listing can be controlled from the configuration file or via
a server directive.  A decimal number may optionally, immediately precede any
directive, this becomes the width of that particular field.  If a width is not
specified an appropriate default is used.  Information wider than the field
width is truncated, generally without indication.  The following layout
directives provide:

  _     (underscore) each underscore provides one space between fields
  C     creation date
  D     content-type description (BEST be the last field specified)
  D:L   content-type description as a link
  I     icon
  L     file anchor (link, including name as link)
  L:F   file anchor, ODS-5 file system name (e.g. spaces, not %20)
  L:N   file anchor, name-only, do not display the file's extension
  L:U   file anchor, force the name to upper-case
        (these can have multiple specifications, e.g. "__L:U:N__")
  N     file name (without anchor, why I can't imagine :^)
  O     file owner (only when SYSUAF-authenticated and profile accessed)
  P     file protection (only when SYSUAF-authenticated and profile accessed)
  R     revision date
  S     file size
  S:B   file size to be in comma-formatted bytes
  S:D   express file sizes in 1000 (decimal) kilos not 1024 (binary) kilos
  S:F   file size Mb and Kb to be expressed to one significant place
  S:K   file size to be in K-bytes
  S:M   file size to be in M-bytes
  U     file/directory name in upper case (MUST be the FIRST directive)

These may placed in any order ("D" is best last because it does not use a 
field width) and with any (reasonable) field-width.  For example see 
'DirDefaultLayout' below. 


QUERY STRING DIRECTIVES
-----------------------
These directives may be added to a directory lsiting URL to modifiy the format
and/or behaviour of the resultant listing.  Multiple query string directives
may be added using the normal query string ampersand syntax.  This example
forces an ODS-2 volumes file name to upper-case and does not display  the file
type (extension)

  ?httpd=index&upper=1&notype=1

The following

  autoscript=   yes (default), no, true, false, 1, 0
  delimit=      header, footer, both (default), none
  expired=      yes, no, true, false, 1, 0 (listing pre-expired)
  layout=       see "listing layout" immediately above
  notype=       yes, no (default), true, false, 1, 0 (do not display file type)
  nop=          yes, no (default), true, false, 1, 0 (as if .WWW_NOP found)
  nops=         yes, no (default), true, false, 1, 0 (as if .WWW_NOPS found)
  nos=          yes, no (default), true, false, 1, 0 (as if .WWW_NOS found)
  readme=       yes, no (default), true, false, 1, 0
  type=         force file content-type (e.g. "&type=text/plain")
  upper=        yes, no (default), true, false, 1, 0


VMS (ODS-2) FILE NAMES
----------------------
Generally directory listings on ODS-2 volumes (VMS disks prior to VMS V7.2) are
displayed lower-case.  To force an upper-case listing use any of a directory
listing format beginning with "U", or the "httpd=index&upper=1" query string.


EXTENDED (ODS-5) FILE NAMES
---------------------------
On ODS-5 volumes (available on Alpha VMS V7.2 and following) file names are
displayed in mixed, on-disk case.  These file names may contain characters that
need to be escaped.  These are displayed as what the escape sequence represents
For example; '^_' as ' ' and '^.' as '.'.


PATHWORKS AND SRI ENCODED FILE NAMES
------------------------------------
These will be displayed as what the encoding represents.  For example; in
Pathworks encoding a '$20' sequence represents a ' ' and in SRI encoding the
encoding '$7A' also as ' '.  SRI encodings representing mixed-case file names
are displayed in mixed case.


VERSION HISTORY
---------------
06-SEP-2004  MGD  change from self-relative to absolute links in "Index of"
                  anchor generation (broke usage in some SSI documents)
05-SEP-2003  MGD  bugfix; according to the doco "Index of"s from SSI should
                  not be delimited top or bottom (up to SSI to caption it!)
05-JUN-2003  MGD  bugfix; DirFormatLayout() static flags (jpp@esme.fr)
11-MAR-2003  MGD  set html= directory listing header and footer,
                  there are now three directory listing styles implementing
                  set dir=style[=default|original|anchor|htdir]
15-OCT-2002  MGD  demo mode ignores .WWW_HIDDEN, etc.
05-OCT-2002  MGD  refine VMS security profile usage
20-SEP-2002  MGD  bale-out early if no access
14-SEP-2002  MGD  implement SET dir=charset directory listing charset
09-SEP-2002  MGD  for consistency return an (if configured) empty listing
                  if protection on the directory doesn't allow wildcards
04-SEP-2002  MGD  bugfix; 'Index of..' string encoding
20-AUG-2002  MGD  modify formatting for SRI and PWK ODS encodings,
                  remove 'filesys=' listing modifier - it's now all FILESYS!
10-JUL-2002  MGD  'RealPath' for absoluting the likes of SSI 'index of's
31-MAY-2002  MGD  path SET directory access control
27-APR-2002  MGD  make SYSPRV enabled ASTs asynchronous
07-APR-2002  MGD  bugfix; ODS-5 parent directories with multiple periods
02-FEB-2002  MGD  for non-textual content types the icon is now an
                  anchor for a "text/plain" content-type access
04-AUG-2001  MGD  support module WATCHing
17-JUL-2001  MGD  bugfix; 'layout=U' upper-casing
21-FEB-2001  MGD  include ODS-5 "hidden" files beginning '^.'
13-FEB-2001  MGD  bugfix; directory specfication length (sys$check_access())
04-DEC-2000  MGD  bugfix; DirSearchFiles() call DirFiles() with FAB
04-MAR-2000  MGD  use WriteFaol(), et.al.
27-DEC-1999  MGD  support ODS-2 and ODS-5 using ODS module
18-SEP-1999  MGD  bugfix; sys$parse() NAM$M_NOCONCEAL for search lists
23-MAY-1999  MGD  make year 4 digits for creation and revision date
19-SEP-1998  MGD  improve granularity with use of directory/file/ACP services
                  (not as much as I would have liked, but some things are just
                  not important enough to be worth the trouble using ASTs!)
08-AUG-1998  MGD  configurable implied wildcards
19-JUL-1998  MGD  bugfix; MapUrl_Map() pass 'rqptr' for conditionals to work
14-MAY-1998  MGD  request-specified content-type ("httpd=index&type=")
16-MAR-1998  MGD  bugfix; file bytes incorrect when 'FirstFreeByte' zero
28-FEB-1998  MGD  improved icon handling efficiency
19-FEB-1998  MGD  size layout allows ":B", ":D", ":F", ":K", ":M" modifiers,
                  description layout allows ":L" to make a link out of it,
                  description may now have an associated field width,
                  allow for directory paths like "/dir1/dir2"
02-NOV-1997  MGD  "delimit=", "nop=", "nops=", "nos=" and "readme=" directives
                  changed file sizes back from 1000 to 1024 kilos, megas, etc.
17-AUG-1997  MGD  message database, internationalized file date/times,
                  file/directory names moved to lower-case (see 'U' above),
                  SYSUAF-authenticated users security-profile
20-APR-1997  MGD  changed file sizes from 1024 to 1000 kilos, megas, etc.
01-FEB-1997  MGD  HTTPd version 4
24-APR-1996  MGD  chagrin ... just realized I don't need full URLs in links
01-DEC-1995  MGD  HTTPd version 3
27-SEP-1995  MGD  major revision of file and directory anchor generation
                  (far fewer MapUrl()s yielding greater efficiency);
                  added query string capability;
                  added automatic script capability
07-AUG-1995  MGD  include VMS-style directory layout and file sizes
16-JUN-1995  MGD  file contents description for non-HTML files (see config.c)
25-MAR-1995  MGD  bugfix; error handling with DirReadMe() and DirFileExists()
20-DEC-1994  MGD  multi-threaded daemon (it suddenly got very complex!)
20-JUN-1994  MGD  single-threaded daemon
*/
/*****************************************************************************/

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

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

/* VMS related header files */
#include <atrdef.h>
#include <descrip.h>
#include <dvidef.h>
#include <fibdef.h>
#include <libdtdef.h>
#include <libdef.h>
#include <rmsdef.h>
#include <rms.h>
#include <ssdef.h>
#include <stsdef.h>

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

#define WASD_MODULE "DIR"

/**********/
/* macros */
/**********/

#define DEFAULT_FIELD_WIDTH_INTERFIELD 1
#define DEFAULT_FIELD_WIDTH_CDT 17
#define DEFAULT_FIELD_WIDTH_NAME 25
#define DEFAULT_FIELD_WIDTH_OWNER 20
#define DEFAULT_FIELD_WIDTH_PROTECTION 19
#define DEFAULT_FIELD_WIDTH_RDT 17
#define DEFAULT_FIELD_WIDTH_SIZE 6
#define DEFAULT_FIELD_WIDTH_DECIMAL 11

#define DELIMIT_BOTH    1
#define DELIMIT_HEADER  2
#define DELIMIT_FOOTER  3
#define DELIMIT_NONE    4

#define LAYOUT_PARAMETER ':'

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

char  DirBrowsableFileName [] = ".WWW_BROWSABLE",
      DirHiddenFileName [] = ".WWW_HIDDEN",
      DirNoWildFileName [] = ".WWW_NOWILD",
      DirNopFileName [] = ".WWW_NOP",
      DirNosFileName [] = ".WWW_NOS",
      DirNopsFileName [] = ".WWW_NOPS";

char  *DirBlankIconPtr,
      *DirDirIconPtr,
      *DirParentIconPtr;

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

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

extern BOOL  CliDemo,
             NaturalLanguageEnglish,
             OdsExtended;

extern int  FileBufferSize,
            SsiSizeMax;

extern unsigned long  SysPrvMask[];

extern char  *ConfigBlankIconPtr,
             *ConfigDirIconPtr,
             *ConfigParentIconPtr;

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

extern ACCOUNTING_STRUCT  *AccountingPtr;
extern CONFIG_STRUCT  Config;
extern MSG_STRUCT  Msgs;
extern WATCH_STRUCT  Watch;

/*****************************************************************************/
/*
This function never returns a valid status and ALWAYS calls the supplied next
task (AST) function.  This should check for a generated error message to
determine is there were any problems.
*/ 
 
DirBegin
(
REQUEST_STRUCT *rqptr,
REQUEST_AST NextTaskFunction,
char *DirSpec,
char *DirQueryString,
char *RealPath,
BOOL AuthorizePath
)
{
   int  status;
   char  *cptr, *sptr, *zptr;
   DIR_TASK  *tkptr;

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

   if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD_DIR))
      WatchThis (rqptr, FI_LI, WATCH_MOD_DIR,
                 "DirBegin() !&A !&Z !&Z !&Z !&B", 
                 NextTaskFunction, DirSpec, DirQueryString,
                 RealPath, AuthorizePath);

   if (ERROR_REPORTED (rqptr))
   {
      /* previous error, cause threaded processing to unravel */
      SysDclAst (NextTaskFunction, rqptr);
      return;
   }

   /* network writes are checked for success, fudge the first one! */
   rqptr->rqNet.WriteIOsb.Status = SS$_NORMAL;

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

   if (WATCHING(rqptr) &&
       WATCH_CATEGORY(WATCH_RESPONSE))
      WatchThis (rqptr, FI_LI, WATCH_RESPONSE, "INDEXOF !AZ", DirSpec);

   if ((!Config.cfDir.Access &&
        !Config.cfDir.AccessSelective &&
        !rqptr->rqPathSet.DirAccess &&
        !rqptr->rqPathSet.DirAccessSelective) ||
        rqptr->rqPathSet.DirNoAccess)
   {
      rqptr->rqResponse.HttpStatus = 403;
      ErrorGeneral (rqptr, MsgFor(rqptr,MSG_GENERAL_DISABLED), FI_LI);
      SysDclAst (NextTaskFunction, rqptr);
      return;
   }

   /* set up the task structure (possibly multiple concurrent) */
   if (rqptr->DirTaskPtr &&
       LIST_HAS_NEXT (rqptr->DirTaskPtr))
   {
      rqptr->DirTaskPtr = tkptr = LIST_GET_NEXT (rqptr->DirTaskPtr);
      memset (LIST_GET_DATA(tkptr), 0,
              sizeof(DIR_TASK) - sizeof(LIST_ENTRY));
   }
   else
   {
      rqptr->DirTaskPtr = tkptr =
         VmGetHeap (rqptr, sizeof(DIR_TASK));
      ListAddTail (&rqptr->DirTaskList, tkptr);
   }
   tkptr->NextTaskFunction = NextTaskFunction;

   cptr = MapVmsPath (DirSpec, rqptr);
   if (!cptr[0])
   {
      rqptr->rqResponse.HttpStatus = 403;
      ErrorGeneral (rqptr, cptr+1, FI_LI);
      DirEnd (rqptr);
      return;
   }
   zptr = (sptr = tkptr->DirPath) + sizeof(tkptr->DirPath);
   while (*cptr && sptr < zptr) *sptr++ = *cptr++;
   if (sptr >= zptr)
   {
      ErrorGeneralOverflow (rqptr, FI_LI);
      DirEnd (rqptr);
      return;
   }
   *sptr = '\0';

   /* when the path may be different from base path (e.g. SSI) */
   cptr = RealPath;
   zptr = (sptr = tkptr->RealPath) + sizeof(tkptr->RealPath);
   while (*cptr && sptr < zptr) *sptr++ = *cptr++;
   if (sptr >= zptr)
   {
      ErrorGeneralOverflow (rqptr, FI_LI);
      DirEnd (rqptr);
      return;
   }
   *sptr = '\0';
   while (sptr > tkptr->RealPath && *sptr != '/') sptr--;
   if (*sptr == '/') sptr++;
   *sptr = '\0';

   cptr = DirSpec;
   zptr = (sptr = tkptr->DirSpec) + sizeof(tkptr->DirSpec);
   while (*cptr && sptr < zptr) *sptr++ = *cptr++;
   if (sptr >= zptr)
   {
      ErrorGeneralOverflow (rqptr, FI_LI);
      DirEnd (rqptr);
      return;
   }
   *sptr = '\0';
   tkptr->DirSpecLength = sptr - tkptr->DirSpec;

   /********************************************/
   /* check if there are any server directives */
   /********************************************/

   zptr = (sptr = tkptr->QueryString) + sizeof(tkptr->QueryString);
   if (DirQueryString[0])
   {
      if (!strsame (DirQueryString, "httpd=index&", 12))
      {
         cptr = "httpd=index&";
         while (*cptr && sptr < zptr) *sptr++ = *cptr++;
      }
      cptr = DirQueryString;
      while (*cptr && sptr < zptr) *sptr++ = *cptr++;
      if (sptr >= zptr)
      {
         ErrorGeneralOverflow (rqptr, FI_LI);
         DirEnd (rqptr);
         return;
      }
   }
   *sptr = '\0';

   if (Debug) fprintf (stdout, "tkptr->QueryString |%s|\n", tkptr->QueryString);

   rqptr->rqResponse.PreExpired = Config.cfDir.PreExpired;
   tkptr->AsIfNopFound = tkptr->AsIfNosFound = false;
   tkptr->AutoScriptEnabled = tkptr->IncludeAnyReadme = true;
   if (tkptr->RealPath[0])
      tkptr->Delimit = DELIMIT_NONE;
   else
      tkptr->Delimit = DELIMIT_BOTH;

   if (tkptr->QueryString[0])
   {
      cptr = tkptr->QueryString;
      while (*cptr)
      {
         if (Debug) fprintf (stdout, "cptr |%s|\n", cptr);

         if (tolower(*cptr) == 'a' &&
             strsame (cptr, "autoscript=", 11))
         {
            cptr += 11;
            /* if "false", "no" or "0" then turn off auto-scripting */
            if (tolower(*cptr) == 'f' || tolower(*cptr) == 'n' || *cptr == '0')
               tkptr->AutoScriptEnabled = false;
            else
               tkptr->AutoScriptEnabled = true;
         }
         else
         if (tolower(*cptr) == 'd' &&
             strsame (cptr, "delimit=", 8))
         {
            cptr += 8;
            switch (tolower(*cptr))
            {
               case 'h' :
                  tkptr->Delimit = DELIMIT_HEADER;
                  break;
               case 'f' :
                  tkptr->Delimit = DELIMIT_FOOTER;
                  break;
               case 'n' :
                  tkptr->Delimit = DELIMIT_NONE;
                  break;
               default :
                  tkptr->Delimit = DELIMIT_BOTH;
            }
         }
         else
         /* experience shows both make it easier! */
         if (tolower(*cptr) == 'e' &&
             (strsame (cptr, "expire=", 7) ||
              strsame (cptr, "expired=", 8)))
         {
            if (cptr[7] == '=')
               cptr += 8;
            else
               cptr += 7;
            /* "true", "yes", "1", "false", "no" or "0" */
            if (tolower(*cptr) == 't' || tolower(*cptr) == 'y' || *cptr == '1')
               rqptr->rqResponse.PreExpired = true;
            else
            if (tolower(*cptr) == 'f' || tolower(*cptr) == 'n' || *cptr == '0')
               rqptr->rqResponse.PreExpired = false;
         }
         else
         if (tolower(*cptr) == 'l' &&
             strsame (cptr, "layout=", 7))
         {
            cptr += 7;
            for (sptr = cptr; *sptr && *sptr != '&'; sptr++);
            tkptr->LayoutPtr = VmGetHeap (rqptr, sptr-cptr+1);
            memcpy (tkptr->LayoutPtr, cptr, sptr-cptr);
            tkptr->LayoutPtr[sptr-cptr] = '\0';
         }
         else
         if (tolower(*cptr) == 'n' &&
             strsame (cptr, "notype=", 7))
         {
            cptr += 7;
            /* if "true", "yes" or "1" then do not display parent directory */
            if (tolower(*cptr) == 't' || tolower(*cptr) == 'y' || *cptr == '1')
               tkptr->ShowNoType = true;
            else
               tkptr->ShowNoType = false;
         }
         else
         if (tolower(*cptr) == 'n' &&
             strsame (cptr, "nop=", 4))
         {
            cptr += 4;
            /* if "true", "yes" or "1" then do not display parent directory */
            if (tolower(*cptr) == 't' || tolower(*cptr) == 'y' || *cptr == '1')
               tkptr->AsIfNopFound = true;
            else
               tkptr->AsIfNopFound = false;
         }
         else
         if (tolower(*cptr) == 'n' &&
             strsame (cptr, "nops=", 5))
         {
            cptr += 5;
            /* if "true", "yes" or "1" don't display parent or sub directory */
            if (tolower(*cptr) == 't' || tolower(*cptr) == 'y' || *cptr == '1')
               tkptr->AsIfNopFound = tkptr->AsIfNosFound = true;
            else
               tkptr->AsIfNopFound = tkptr->AsIfNosFound = false;
         }
         else
         if (tolower(*cptr) == 'n' &&
             strsame (cptr, "nos=", 4))
         {
            cptr += 4;
            /* if "true", "yes" or "1" then do not display sub directory */
            if (tolower(*cptr) == 't' || tolower(*cptr) == 'y' || *cptr == '1')
               tkptr->AsIfNosFound = true;
            else
               tkptr->AsIfNosFound = false;
         }
         else
         if (tolower(*cptr) == 'r' &&
             strsame (cptr, "readme=", 7))
         {
            cptr += 7;
            /* if "false", "no" or "0" then do not display any readme */
            if (tolower(*cptr) == 'f' || tolower(*cptr) == 'n' || *cptr == '0')
               tkptr->IncludeAnyReadme = false;
            else
               tkptr->IncludeAnyReadme = true;
         }
         else
         if (tolower(*cptr) == 's' &&
             strsame (cptr, "script=", 7))
         {
            /* local storage */
            char  *zptr;

            cptr += 7;
            zptr = (sptr = tkptr->ScriptName) + sizeof(tkptr->ScriptName);
            if (*cptr != '/') *sptr++ = '/';
            while (*cptr && *cptr != '&' && sptr < zptr) *sptr++ = *cptr++;
            if (sptr >= zptr)
            {
               ErrorGeneralOverflow (rqptr, FI_LI);
               DirEnd (rqptr);
               return;
            }
            *sptr = '\0';
         }
         else
         if (tolower(*cptr) == 't' &&
             strsame (cptr, "type=", 5))
         {
            cptr += 5;
            for (sptr = cptr; *sptr && *sptr != '&'; sptr++);
            tkptr->QueryContentTypePtr = VmGetHeap (rqptr, sptr-cptr+1);
            memcpy (tkptr->QueryContentTypePtr, cptr, sptr-cptr);
            tkptr->QueryContentTypePtr[sptr-cptr] = '\0';
         }
         else
         if (tolower(*cptr) == 'u' &&
             strsame (cptr, "upper=", 6))
         {
            cptr += 6;
            /* if "true", "yes" or "1" then do not display parent directory */
            if (tolower(*cptr) == 't' || tolower(*cptr) == 'y' || *cptr == '1')
               tkptr->ShowUpperCase = true;
            else
               tkptr->ShowUpperCase = false;
         }

         while (*cptr && *cptr != '&') cptr++;
         if (*cptr) cptr++;
      }
   }

   if (AuthorizePath)
   {
      /***********************/
      /* check authorization */
      /***********************/

      cptr = MapVmsPath (tkptr->DirSpec, rqptr);
      Authorize (rqptr, cptr, -1, NULL, 0, &DirAuthorizationAst);
      if (Debug)
         fprintf (stdout, "rqAuth.FinalStatus: %%X%08.08X\n",
                  rqptr->rqAuth.FinalStatus);
      if (VMSnok (rqptr->rqAuth.FinalStatus))
      {
         /* if asynchronous authentication is underway then just wait for it */
         if (rqptr->rqAuth.FinalStatus == AUTH_PENDING) return;
         DirEnd (rqptr);
         return;
      }
   }

   /* not to-be-authorized, or authorized ... just carry on regardless! */
   DirAuthorizationAst (rqptr);
} 

/*****************************************************************************/
/*
This function provides an AST target is Authorize()ation ended up being done
asynchronously, otherwise it is just called directly to continue the modules
processing.
*/

DirAuthorizationAst (REQUEST_STRUCT *rqptr)

{
   int  status;
   unsigned short  Length;
   unsigned long  FaoVector [32];
   unsigned long  *vecptr;
   char  Scratch [ODS_MAX_FILE_NAME_LENGTH+1];
   char  *cptr, *sptr,
         *IndexOfPtr;
   DIR_TASK  *tkptr;
   REQUEST_AST AstFunction;

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

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

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

   tkptr = rqptr->DirTaskPtr;

   if (rqptr->rqAuth.VmsUserProfileLength)
   {
      /*****************************************/
      /* check VMS-authenticated user's access */
      /*****************************************/

      /* find the length of the device:[directory] part */
      for (cptr = tkptr->DirSpec + tkptr->DirSpecLength;
           cptr > tkptr->DirSpec && *cptr != ']';
           cptr--);
      if (*cptr == ']') cptr++;

      status = AuthVmsCheckUserAccess (rqptr, tkptr->DirSpec,
                                       cptr - tkptr->DirSpec);
      if (VMSnok (status))
      {
         if (status == SS$_NOPRIV)
         {
            rqptr->rqResponse.ErrorTextPtr = tkptr->DirPath;
            rqptr->rqResponse.ErrorOtherTextPtr = tkptr->DirSpec;
            ErrorVmsStatus (rqptr, status, FI_LI);
         }
         DirEnd (rqptr);
         return;
      }
   }
   else
   {
      /**********************************/
      /* can the average Joe get to it? */
      /**********************************/

      status = OdsFileExists (tkptr->DirSpec, "*.*");
      if (status == RMS$_SYN)
         status = OdsFileExists (NULL, tkptr->DirSpec);
      if (status == RMS$_PRV)
      {
         rqptr->rqResponse.ErrorTextPtr = tkptr->DirPath;
         rqptr->rqResponse.ErrorOtherTextPtr = tkptr->DirSpec;
         ErrorVmsStatus (rqptr, status, FI_LI);
         DirEnd (rqptr);
         return;
      }
   }


   /***************************/
   /* parse to get components */
   /***************************/

   OdsParse (&tkptr->SearchOds,
             tkptr->DirSpec, tkptr->DirSpecLength, NULL, 0,
             tkptr->NamNop, NULL, rqptr);

   if (VMSnok (status = tkptr->SearchOds.Fab.fab$l_sts))
   {
      rqptr->rqResponse.ErrorTextPtr = tkptr->DirPath;
      rqptr->rqResponse.ErrorOtherTextPtr = tkptr->DirSpec;
      ErrorVmsStatus (rqptr, status, FI_LI);
      DirEnd (rqptr);
      return;
   }

   memcpy (tkptr->DirectoryPart,
           tkptr->SearchOds.NamDevicePtr,
           Length = tkptr->SearchOds.NamNamePtr -
                    tkptr->SearchOds.NamDevicePtr);
   tkptr->DirectoryPart[Length] = '\0';
   if (Debug) fprintf (stdout, "DirectoryPart |%s|\n", tkptr->DirectoryPart);

   Scratch[0] = '\0';
   sptr = MapUrl_Map (Scratch, sizeof(Scratch), tkptr->DirectoryPart, 0,
                      NULL, 0, NULL, 0, NULL, 0, NULL, rqptr);
   if (!sptr[0] && sptr[1])
   {
      /* mapping report */
      rqptr->rqResponse.HttpStatus = 403;
      ErrorGeneral (rqptr, sptr+1, FI_LI);
      DirEnd (rqptr);
      return;
   }
   Length = strlen(Scratch) + 1;
   tkptr->DirectoryPathPtr = VmGetHeap (rqptr, Length);
   memcpy (tkptr->DirectoryPathPtr, Scratch, Length);

   if (Debug)
      fprintf (stdout,
         "DirectoryPart |%s|\nDirDirectoryUrl |%s|\n",
         tkptr->DirectoryPart, tkptr->DirectoryPathPtr);

   if (tkptr->SearchOds.Nam_fnb & NAM$M_WILD_DIR)
   {
      rqptr->rqResponse.HttpStatus = 403;
      ErrorGeneral (rqptr, MsgFor(rqptr,MSG_GENERAL_NO_WILDCARD), FI_LI);
      DirEnd (rqptr);
      return;
   }

   /************************/
   /* directory access ok? */
   /************************/

   if (CliDemo ||
       rqptr->rqAuth.SkelKeyAuthenticated ||
       rqptr->rqAuth.VmsUserProfileLength)
   {
      /* the VMS security profile allows access, why further prohibit it? */
      status = SS$_NORMAL;
   }
   else
   {
      /* ensure we can read these directory listing "control" files */
      sys$setprv (1, &SysPrvMask, 0, 0);
      if (VMSok (status =
          OdsFileExists (tkptr->DirectoryPart, DirHiddenFileName)))
         status = RMS$_DNF;
      else
      if ((Config.cfDir.AccessSelective ||
           rqptr->rqPathSet.DirAccessSelective) &&
           !rqptr->rqPathSet.DirAccess)
      {
         status = OdsFileExists (tkptr->DirectoryPart, DirBrowsableFileName);
         if (status == RMS$_FNF) status = SS$_ABORT;
      }
      else
      if (VMSok (status =
                 OdsFileExists (tkptr->DirectoryPart, DirNoWildFileName)))
      {
         if (tkptr->SearchOds.Nam_fnb & NAM$M_WILD_NAME ||
             tkptr->SearchOds.Nam_fnb & NAM$M_WILD_TYPE ||
             tkptr->SearchOds.Nam_fnb & NAM$M_WILD_VER)
            status = SS$_ABORT;
         else
            status = SS$_NORMAL;
      }
      else
         status = SS$_NORMAL;
      sys$setprv (0, &SysPrvMask, 0, 0);
   }

   if (VMSnok (status))
   {
      if (status == SS$_ABORT)
      {
         /* abort here means the facility is disabled */
         rqptr->rqResponse.HttpStatus = 403;
         ErrorGeneral (rqptr, MsgFor(rqptr,MSG_GENERAL_DISABLED), FI_LI);
      }
      else
      {
         /* give some "disinformation"  ;^)  */
         if (status == RMS$_FNF) status = RMS$_DNF;
         rqptr->rqResponse.ErrorTextPtr = tkptr->DirPath;
         rqptr->rqResponse.ErrorOtherTextPtr = tkptr->DirSpec;
         ErrorVmsStatus (rqptr, status, FI_LI);
      }
      DirEnd (rqptr);
      return;
   }

   /*********************************/
   /* further process specification */
   /*********************************/

   tkptr->DirSpecIncludedFilePart = tkptr->SearchOds.NamNameLength > 0 ||
                                    tkptr->SearchOds.NamTypeLength > 1 ||
                                    tkptr->SearchOds.NamVersionLength > 1;

   if (!tkptr->DirSpecIncludedFilePart)
   {     
      /* no name or type was supplied with the request, wildcard it */
      OdsParse (&tkptr->SearchOds,
                tkptr->DirSpec, tkptr->DirSpecLength, "*.*", 3,
                tkptr->NamNop, NULL, rqptr);

      if (VMSnok (status = tkptr->SearchOds.Fab.fab$l_sts))
      {
         rqptr->rqResponse.ErrorTextPtr = tkptr->DirPath;
         rqptr->rqResponse.ErrorOtherTextPtr = tkptr->DirSpec;
         ErrorVmsStatus (rqptr, status, FI_LI);
         DirEnd (rqptr);
         return;
      }
   }

   if (tkptr->SearchOds.Nam_fnb & NAM$M_WILD_VER ||
       tkptr->SearchOds.Nam_fnb & NAM$M_EXP_VER)
   {
      /* wildcard or explicit version number supplied ... VMS format */
      memcpy (tkptr->FilePart,
              tkptr->SearchOds.NamNamePtr,
              Length = tkptr->SearchOds.NamVersionPtr -
                       tkptr->SearchOds.NamNamePtr +
                       tkptr->SearchOds.NamVersionLength);
      tkptr->FormatLikeVms = true;
   }
   else
   {
      /* name and/or type or implied wildcards */
      memcpy (tkptr->FilePart,
              tkptr->SearchOds.NamNamePtr,
              Length = tkptr->SearchOds.NamVersionPtr -
                       tkptr->SearchOds.NamNamePtr);
   }
   tkptr->FilePart[Length] = '\0';
   if (Debug) fprintf (stdout, "FilePart |%s|\n", tkptr->FilePart);

   /********************/
   /* begin processing */
   /********************/

   tkptr->FileCount = tkptr->DirectoryCount = 0;

   if (VMSnok (DirFormatLayout (rqptr)))
   {
      DirEnd (rqptr);
      return;
   }

   if (tkptr->ResponseHeaderSent = !rqptr->rqResponse.HeaderSent)
   {
      /* if directory listing charset has been set impose that on any other */
      cptr = rqptr->rqPathSet.CharsetPtr;
      if (rqptr->rqPathSet.DirCharsetPtr)
         rqptr->rqPathSet.CharsetPtr = rqptr->rqPathSet.DirCharsetPtr;

      /* "index of"s can have pre-expiry controlled from the query string */
      RESPONSE_HEADER_200_HTML (rqptr);

      /* restore any previous (just to be neat) */
      rqptr->rqPathSet.CharsetPtr = cptr;

      if (rqptr->rqHeader.Method == HTTP_METHOD_HEAD)
      {
         DirEnd (rqptr);
         return;
      }
   }

   if (Config.cfDir.ReadMeTop && tkptr->IncludeAnyReadme)
      AstFunction = &DirReadMeTop;
   else
      AstFunction = &DirHeading;

   if (tkptr->Delimit == DELIMIT_BOTH || tkptr->Delimit == DELIMIT_HEADER)
   {
      vecptr = &FaoVector;

      if (Config.cfDir.MetaInfoEnabled)
      {
         *vecptr++ = "!AZ<META NAME=\"kilo\" CONTENT=\"!UL\">\n";
         *vecptr++ = HtmlMetaInfo (rqptr, tkptr->DirSpec);
         *vecptr++ = tkptr->SizeKilo;
      }
      else
         *vecptr++ = "";

      IndexOfPtr = MsgFor(rqptr,MSG_DIR_INDEX_OF);

      /* <title>Index of </title> */
      if (tkptr->FormatLikeVms)
      {
         MapUrl_UrlToVms (tkptr->DirPath, Scratch, sizeof(Scratch),
                          0, rqptr->rqPathSet.MapEllipsis, rqptr->PathOds);
         *vecptr++ = "!AZ !&;AZ";
         *vecptr++ = IndexOfPtr;
         *vecptr++ = Scratch;
      }
      else
      if (rqptr->rqPathSet.DirStyle == MAPURL_DIR_STYLE_HTDIR)
      {
         *vecptr++ = "//!AZ!&;&[AZ";
         if (rqptr->rqHeader.HostPtr &&
             rqptr->rqHeader.HostPtr[0])
            *vecptr++ = rqptr->rqHeader.HostPtr;
         else
            *vecptr++ = rqptr->ServicePtr->ServerHostPort;
         *vecptr++ = rqptr->PathOds;
         *vecptr++ = tkptr->DirPath;
      }
      else
      {
         *vecptr++ = "!AZ //!AZ!&;&[AZ";
         *vecptr++ = IndexOfPtr;
         if (rqptr->rqHeader.HostPtr &&
             rqptr->rqHeader.HostPtr[0])
            *vecptr++ = rqptr->rqHeader.HostPtr;
         else
            *vecptr++ = rqptr->ServicePtr->ServerHostPort;
         *vecptr++ = rqptr->PathOds;
         *vecptr++ = tkptr->DirPath;
      }

      if (rqptr->rqPathSet.HtmlBodyTagPtr)
      {
         /* <body..> */
         if (rqptr->rqPathSet.HtmlBodyTagPtr[0] == '<')
            *vecptr++ = "!AZ\n";
         else
            *vecptr++ = "<BODY!&+AZ>\n";
         *vecptr++ = rqptr->rqPathSet.HtmlBodyTagPtr;
      }
      else
      {
         *vecptr++ = "!AZ\n";
         *vecptr++ = Config.cfDir.BodyTag;
      }

      if (rqptr->rqPathSet.HtmlHeaderPtr ||
          rqptr->rqPathSet.HtmlHeaderTagPtr)
      {
         if (rqptr->rqPathSet.HtmlHeaderTagPtr &&
             rqptr->rqPathSet.HtmlHeaderTagPtr[0] == '<')
            *vecptr++ = "!AZ\n!&/AZ";
         else
            *vecptr++ =
"<TABLE CELLPADDING=5 CELLSPACING=0 BORDER=0 WIDTH=100%><TR><TD!&+AZ>\n!&/AZ";
         *vecptr++ = rqptr->rqPathSet.HtmlHeaderTagPtr;
         *vecptr++ = rqptr->rqPathSet.HtmlHeaderPtr;
      }
      else
         *vecptr++ = "";

      status = NetWriteFaol (rqptr,
"<HTML>\n\
<HEAD>\n\
!&@\
<TITLE>!&@</TITLE>\n\
</HEAD>\n\
!&@\
!&@",
         &FaoVector);

      if (VMSok (status))
         status = DirIndexOf (rqptr, IndexOfPtr,
                              tkptr->FormatLikeVms ? Scratch : NULL);

      if (VMSnok (status))
      {
         ErrorNoticed (status, "NetWriteFaol()", FI_LI);
         rqptr->rqResponse.ErrorTextPtr = "NetWriteFaol()";
         ErrorVmsStatus (rqptr, status, FI_LI);
         DirEnd (rqptr);
         return;
      }

      NetWriteFullFlush (rqptr, AstFunction);
   }
   else
      SysDclAst (AstFunction, rqptr);
}

/*****************************************************************************/
/*
Provide the "Index of" page heading in any of the post-v8.2, traditional WASD,
or the "ABI" styles.
*/ 

int DirIndexOf
(
REQUEST_STRUCT *rqptr,
char *IndexOfPtr,
char *UrlAsVms
)
{
   int  cnt, status,
        SlashCount;
   unsigned long  FaoVector [32];
   unsigned long  *vecptr;
   char  *cptr, *sptr, *zptr,
         *FinalSlashPtr,
         *ThisSlashPtr;
   char  Scratch [ODS_MAX_FILE_NAME_LENGTH+1];
   DIR_TASK  *tkptr;

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

   if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD_DIR))
      WatchThis (rqptr, FI_LI, WATCH_MOD_DIR,
                 "DirIndexOf() !&Z !&Z", IndexOfPtr, UrlAsVms);

   /* get the pointer to the task structure */
   tkptr = rqptr->DirTaskPtr;

   if (rqptr->rqPathSet.DirStyle == MAPURL_DIR_STYLE_ORIGINAL)
   {   
      /**************************/
      /* traditional WASD style */
      /**************************/

      vecptr = &FaoVector;
      *vecptr++ = IndexOfPtr;
      *vecptr++ = rqptr->PathOds;
      *vecptr++ = tkptr->DirPath;
      status = NetWriteFaol (rqptr,
                  "<H2><NOBR>!AZ &nbsp;!&;&_&[AZ</NOBR></H2>\n",
                             &FaoVector);
      return (status);
   }

   /****************/
   /* other styles */
   /****************/

   /* convert the path into a displayable path (might be ODS-5, etc.) */
   vecptr = &FaoVector;
   *vecptr++ = rqptr->PathOds;
   *vecptr++ = tkptr->DirPath;
   status = WriteFaol (Scratch, sizeof(Scratch), NULL, "!&;&_&[AZ", &FaoVector);
   if (VMSnok (status)) return (status);

   /* calculate the number of directory elements */
   SlashCount = 0;
   FinalSlashPtr = "";
   for (cptr = Scratch; *cptr; cptr++)
   {
      if (*cptr == '/')
      {
         SlashCount++;
         FinalSlashPtr = cptr;
      }
   }
   if (SlashCount > 64) return (SS$_BUGCHECK);
   if (*FinalSlashPtr) FinalSlashPtr++;

   vecptr = &FaoVector;
   if (UrlAsVms)
   {
      /* VMS format listing (no ABI rendering) */
      *vecptr++ = IndexOfPtr;
      status = NetWriteFaol (rqptr, "<H3><NOBR>!AZ &nbsp;", &FaoVector);
      if (VMSnok (status)) return (status);
      cptr = UrlAsVms;
   }
   else
   { 
      if (rqptr->rqPathSet.DirStyle == MAPURL_DIR_STYLE_HTDIR)
      {
         /* ABI's style begins with the server name as a 'root' anchor */
         *vecptr++ = FinalSlashPtr;
         *vecptr++ = tkptr->QueryString[0];
         *vecptr++ = tkptr->QueryString;
         if (rqptr->rqHeader.HostPtr &&
             rqptr->rqHeader.HostPtr[0])
            *vecptr++ = rqptr->rqHeader.HostPtr;
         else
            *vecptr++ = rqptr->ServicePtr->ServerHostPort;
         status = NetWriteFaol (rqptr,
"<H3><NOBR><A HREF=\"/!AZ!&??\r\r!AZ\">//!AZ/</A>",
                                &FaoVector);
      }
      else
      {
         /* WASD style provides an "Index of" heading */
         *vecptr++ = IndexOfPtr;
         *vecptr++ = FinalSlashPtr;
         *vecptr++ = tkptr->QueryString[0];
         *vecptr++ = tkptr->QueryString;
         if (rqptr->rqHeader.HostPtr &&
             rqptr->rqHeader.HostPtr[0])
            *vecptr++ = rqptr->rqHeader.HostPtr;
         else
            *vecptr++ = rqptr->ServicePtr->ServerHostPort;
         status = NetWriteFaol (rqptr,
"<H3><NOBR>!AZ &nbsp;//<A HREF=\"/!AZ!&??\r\r!AZ\">!AZ</A>/",
                                &FaoVector);
      }
      if (VMSnok (status)) return (status);
      cptr = Scratch;
   }

   /* provide an individual anchor for each directory element */
   ThisSlashPtr = tkptr->DirPath;
   if (*ThisSlashPtr == '/') ThisSlashPtr++;
   if (SlashCount) SlashCount--;
   while (SlashCount-- > 0)
   {
      while (*ThisSlashPtr && *ThisSlashPtr != '/') ThisSlashPtr++;
      if (*ThisSlashPtr == '/') ThisSlashPtr++;
      vecptr = &FaoVector;
      *vecptr++ = ThisSlashPtr - tkptr->DirPath;
      *vecptr++ = tkptr->DirPath;
      *vecptr++ = FinalSlashPtr;
      *vecptr++ = tkptr->QueryString[0];
      *vecptr++ = tkptr->QueryString;
      if (UrlAsVms)
      {
         /* VMS format, parse based on VMS directory delimiting characters */
         while (*cptr == '.' || *cptr == ':' || *cptr == '[' || *cptr == ']')
            cptr++;
         for (sptr = cptr;
              *sptr && *sptr != '.' && *sptr != ':' && *sptr != ']';
              sptr++)
            if (*sptr == '^') sptr++;
         *vecptr++ = sptr - cptr;
         *vecptr++ = cptr;
         if (*sptr == ':')
            *vecptr++ = 2;
         else
            *vecptr++ = 1;
         *vecptr++ = sptr;
      }
      else
      {
         /* URL format, parse based on delimiting slashes */
         if (*cptr == '/') cptr++;
         for (sptr = cptr; *sptr && *sptr != '/'; sptr++);
         if (rqptr->rqPathSet.DirStyle == MAPURL_DIR_STYLE_HTDIR)
         {
            /* ABI's style, trailing slashes are part of the link */
            if (*sptr) sptr++;
            *vecptr++ = sptr - cptr;
            *vecptr++ = cptr;
            *vecptr++ = 0;
            *vecptr++ = "";
         }
         else
         {
            /* WASD style, trailing slashes are not part of the link */
            *vecptr++ = sptr - cptr;
            *vecptr++ = cptr;
            *vecptr++ = 1;
            *vecptr++ = sptr;
         }
      }
      if (VMSnok (status)) return (status);
      status = NetWriteFaol (rqptr,
"<A HREF=\"!#AZ!AZ!&??\r\r!&;AZ\">!#AZ</A>!#AZ",
                             &FaoVector);
      cptr = sptr;
   }

   if (!UrlAsVms && rqptr->rqPathSet.DirStyle == MAPURL_DIR_STYLE_HTDIR)
   {
      /* ABI's style, 'file' name and type element as an anchor */
      if (*cptr == '/') cptr++;
      vecptr = &FaoVector;
      *vecptr++ = cptr;
      *vecptr++ = tkptr->QueryString[0];
      *vecptr++ = tkptr->QueryString;
      *vecptr++ = cptr;
      status = NetWriteFaol (rqptr,
                  "<A HREF=\"!AZ!&??\r\r!AZ\">!AZ</A></NOBR></H3>\n",
                             &FaoVector);
   } 
   else
   {
      /* WASD style, URL or VMS format, 'file' name and type just displayed */
      if (UrlAsVms)
         while (*cptr == ':' || *cptr == '[' || *cptr == '.' || *cptr == ']')
            cptr++;
      else
      if (*cptr == '/')
         cptr++;

      vecptr = &FaoVector;
      *vecptr++ = cptr;
      status = NetWriteFaol (rqptr, "!AZ</NOBR></H3>\n", &FaoVector);
   }

   return (status);
}

/*****************************************************************************/
/*
Release any dynamic memory allocated for parse structures.
*/ 

DirEnd (REQUEST_STRUCT *rqptr)

{
   int  status;
   unsigned long  FaoVector [16];
   unsigned long  *vecptr;
   DIR_TASK  *tkptr;

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

   if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD_DIR))
      WatchThis (rqptr, FI_LI, WATCH_MOD_DIR, "DirEnd() !&F", &DirEnd);

   /* get the pointer to the task structure */
   tkptr = rqptr->DirTaskPtr;

   /* ensure parse internal data structures are released */
   if (tkptr->SearchOds.ParseInUse) OdsParseRelease (&tkptr->SearchOds);

   if (!ERROR_REPORTED (rqptr))
   {
      vecptr = &FaoVector;

      if (rqptr->rqPathSet.HtmlFooterPtr ||
          rqptr->rqPathSet.HtmlFooterTagPtr)
      {
         if (rqptr->rqPathSet.HtmlFooterTagPtr &&
             rqptr->rqPathSet.HtmlFooterTagPtr[0] == '<')
            *vecptr++ = "!AZ\n!&@";
         else
            *vecptr++ =
"<TABLE CELLPADDING=5 CELLSPACING=0 BORDER=0 WIDTH=100%><TR><TD!&+AZ>\n!&@";
         *vecptr++ = rqptr->rqPathSet.HtmlFooterTagPtr;
         *vecptr++ = "!&/AZ</TD></TR></TABLE>\n";
         *vecptr++ = rqptr->rqPathSet.HtmlFooterPtr;
      }
      else
         *vecptr++ = "";

      status = NetWriteFaol (rqptr, "!&@</BODY>\n</HTML>\n", &FaoVector);
      if (VMSnok (status))
      {
         ErrorNoticed (status, "NetWriteFaol()", FI_LI);
         rqptr->rqResponse.ErrorTextPtr = "NetWriteFaol()";
         ErrorVmsStatus (rqptr, status, FI_LI);
      }
   }

   /* restore previous directory task (if any) */
   rqptr->DirTaskPtr = LIST_GET_PREV (tkptr);

   /* declare the next task */
   SysDclAst (tkptr->NextTaskFunction, rqptr);
}

/*****************************************************************************/
/*
AST-driven output of column headings (these have already generated by 
DirFormatLayout()).
*/ 

DirHeading (REQUEST_STRUCT *rqptr)

{
   int  status;
   DIR_TASK  *tkptr;

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

   if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD_DIR))
      WatchThis (rqptr, FI_LI, WATCH_MOD_DIR, "DirHeading() !&F", DirHeading);

   /* error in readme file? */
   if (ERROR_REPORTED (rqptr))
   {
      DirEnd (rqptr);
      return;
   }

   /* get the pointer to the task structure */
   tkptr = rqptr->DirTaskPtr;

   if (rqptr->rqPathSet.HtmlHeaderPtr ||
       rqptr->rqPathSet.HtmlHeaderTagPtr)
   {
      status = NetWriteFao (rqptr, "</TD></TR></TABLE>\n");
      if (VMSnok (status))
      {
         ErrorNoticed (status, "NetWriteFaol()", FI_LI);
         rqptr->rqResponse.ErrorTextPtr = tkptr->DirPath;
         rqptr->rqResponse.ErrorOtherTextPtr = tkptr->LayoutFaoPtr;
         ErrorVmsStatus (rqptr, status, FI_LI);
         DirEnd (rqptr);
         return;
      }
   }

   if (tkptr->Delimit == DELIMIT_BOTH ||
       tkptr->Delimit == DELIMIT_HEADER)
   {
      /* header and horizontal line at top of page */
      NetWriteBuffered (rqptr, &DirBeginDirectories,
                        tkptr->LayoutHeadingPtr, tkptr->LayoutHeadingLength);
      return;
   }

   /* just start the preformtted text */
   NetWriteBuffered (rqptr, &DirBeginDirectories, "\n<PRE>", 6);
}

/*****************************************************************************/
/*
Begin a listing with directories if not expressly forbidden by configuration 
or local directory-specific configuration files.  If required check if there 
is an accessable parent directory and if so generate a formatted entry for it. 
*/ 
 
DirBeginDirectories (REQUEST_STRUCT *rqptr)

{
   int  status;
   char  *cptr;
   DIR_TASK  *tkptr;

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

   if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD_DIR))
      WatchThis (rqptr, FI_LI, WATCH_MOD_DIR,
                 "DirBeginDirectories() !&F", &DirBeginDirectories);

   tkptr = rqptr->DirTaskPtr;

   /*
      Determine if any parent directory or subdirectories will be
      displayed by checking for the presence of directory listing
      control files (and a kludge for user directory mapping).
   */

   /* ensure we can read these directory listing "control" files */
   sys$setprv (1, &SysPrvMask, 0, 0);

   cptr = tkptr->DirectoryPart;

   if (rqptr->rqPathSet.DirStyle == MAPURL_DIR_STYLE_ORIGINAL)
      tkptr->ListParentDir = true;
   else
      tkptr->ListParentDir = false;
   tkptr->ListSubDir = true;
   if (tkptr->AsIfNopFound && tkptr->AsIfNosFound)
      tkptr->ListParentDir = tkptr->ListSubDir = false;
   else
   if (OdsFileExists (cptr, DirNopsFileName) != RMS$_FNF)
      tkptr->ListParentDir = tkptr->ListSubDir = false;
   else
   {
      if (tkptr->AsIfNosFound)
         tkptr->ListSubDir = false;
      else
      if (OdsFileExists (cptr, DirNosFileName) != RMS$_FNF)
         tkptr->ListSubDir = false;

      if (tkptr->AsIfNopFound)
         tkptr->ListParentDir = false;
      else
      if (OdsFileExists (cptr, DirNopFileName) != RMS$_FNF)
         tkptr->ListParentDir = false;
      else
      if (*(unsigned short*)tkptr->DirPath == '/~')
      {
         /*
            This is a kludge to better support user directories ("/~username/").
            It prevents a "top-level" user directory from displaying a parent
            directory even if it is not a "top-level" VMS directory.  It does
            this by looking to see if there is any subdirectory to the "top-
            level" user directory (e.g. "/~username/subdirectory/").
         */

         for (cptr = tkptr->DirPath; *cptr && *cptr != '/'; cptr++);
         if (*(unsigned short*)cptr == '/\0') tkptr->ListParentDir = false;
      }
   }

   sys$setprv (0, &SysPrvMask, 0, 0);

   if (tkptr->ListSubDir)
   {
      OdsParse (&tkptr->SearchOds,
                tkptr->DirectoryPart, 0, "*.DIR;", 6,
                tkptr->NamNop, &DirBeginDirectoriesParseAst, rqptr);
      return;
   }

   DirBeginDirectoriesParseAst (&tkptr->SearchOds.Fab);
} 

/*****************************************************************************/
/*
AST called from DirDirectoriesBegin() when the asynchronous parse completes,
or when called explicitly if not listing subdirectories.
*/

DirBeginDirectoriesParseAst (struct FAB *FabPtr)

{
   int  status;
   char  *cptr, *sptr;
   char  ParentDirPath [ODS_MAX_FILE_NAME_LENGTH+1],
         ParentDirVms [ODS_MAX_FILE_NAME_LENGTH+1];
   REQUEST_STRUCT  *rqptr;
   DIR_TASK  *tkptr;
   REQUEST_AST AstFunction;

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

   rqptr = FabPtr->fab$l_ctx;

   if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD_DIR))
      WatchThis (rqptr, FI_LI, WATCH_MOD_DIR,
                 "DirBeginDirectoriesParseAst() !&F sts:!&S stv:!&S",
                 &DirBeginDirectoriesParseAst, FabPtr->fab$l_sts,
                 FabPtr->fab$l_stv);

   tkptr = rqptr->DirTaskPtr;

   if (tkptr->ListSubDir)
   {
      /* check the status from the actual sys$parse() */
      if (VMSnok (status = tkptr->SearchOds.Fab.fab$l_sts))
      {
         rqptr->rqResponse.ErrorTextPtr = tkptr->DirPath;
         rqptr->rqResponse.ErrorOtherTextPtr = tkptr->DirSpec;
         ErrorVmsStatus (rqptr, status, FI_LI);
         DirEnd (rqptr);
         return;
      }
      AstFunction = &DirSearchDirectories;
   }
   else
      AstFunction = &DirBeginFiles;

   if (tkptr->ListParentDir &&
       *(unsigned short*)tkptr->DirPath == '/~')
   {
      /* no parent directories for top-level user directories */
      for (cptr = tkptr->DirPath + 2; *cptr && *cptr != '/'; cptr++);
      if (*cptr) cptr++;
      while (*cptr && *cptr != '/') cptr++;
      if (!*cptr) tkptr->ListParentDir = false;
   }

   if (tkptr->ListParentDir)
   {
      cptr = tkptr->DirectoryPart;
      sptr = ParentDirVms;
      while (*cptr && *cptr != '[') *sptr++ = *cptr++;
      if (!memcmp (cptr, "[000000]", 8))
      {
         /* in Master-File-Directory, therefore no parent */
         tkptr->ListParentDir = false;
      }
      else
      {
         /*
            Not in a Master-File-Directory, create a parent directory
            (e.g. "DEVICE:[DIR1.DIR2]" from "DEVICE:[DIR1.DIR2.DIR3]",
            and "DEVICE:[000000]" from "DEVICE:[DIR1]", etc.)
         */
         if (!memcmp (cptr, "[000000.", 8))
         {
            /* skip over the Master-File-Directory in the specification */
            *sptr++ = *cptr++;
            cptr += 7;
         }
         while (*cptr) *sptr++ = *cptr++;
         *sptr = '\0';
         while (*sptr != ']') sptr--;
         while (*sptr != '[')
         {
            while (*sptr != '.' && *sptr != '[') sptr--;
            if (*sptr == '[' || *(sptr-1) != '^') break;
            sptr--;
         }
         if (*sptr == '.')
         {
            *sptr++ = ']';
            *sptr = '\0';
         }
         else
            memcpy (sptr, "[000000]", 9);
         if (Debug) fprintf (stdout, "ParentDirVms |%s|\n", ParentDirVms);

         /*
            If access is allowed display the details.
            Otherwise, its as if the directory just didn't exist!
         */

         /* if the VMS profile allows access, why further prohibit it? */
         if (!CliDemo &&
             !rqptr->rqAuth.SkelKeyAuthenticated &&
             !rqptr->rqAuth.VmsUserProfileLength)
         {
            /* ensure we can read these directory listing "control" files */
            sys$setprv (1, &SysPrvMask, 0, 0);

            if (OdsFileExists (ParentDirVms, DirHiddenFileName) != RMS$_FNF)
               tkptr->ListParentDir = false;
            else
            if ((Config.cfDir.AccessSelective ||
                 rqptr->rqPathSet.DirAccessSelective) &&
                 !rqptr->rqPathSet.DirAccess)
            {
               if (VMSnok (OdsFileExists (ParentDirVms, DirBrowsableFileName)))
                  tkptr->ListParentDir = false;
            }

            sys$setprv (0, &SysPrvMask, 0, 0);
         }
      }

      if (tkptr->ListParentDir)
      {
         ParentDirPath[0] = '\0';
         sptr = MapUrl_Map (ParentDirPath, sizeof(ParentDirPath),
                            ParentDirVms, 0, NULL, 0, NULL, 0, NULL, 0,
                            NULL, rqptr);
         if (sptr[0])
         {
            DirFormat (rqptr, AstFunction, ParentDirPath, false);
            return;
         }
         /*
            Mapping error, most probably access forbidden, we'll assume that.
            This can happen where a subdirectory is mapable but the parent is
            not (e.g. "pass /parent/subd/* /device/parent/subd/*"), making it
            forbidden to request "/parent/file" or "/parent/*.*".
            Hence, for directory listings, it's as if parent does not exist!
         */
         tkptr->ListParentDir = false;
      }
   }

   if (!tkptr->ListParentDir)
   {
      /* parent directory was not output, explicitly drive the search */
      SysDclAst (AstFunction, rqptr);
   }
}

/*****************************************************************************/
/*
AST function to invoke another sys$search() call when listing directories.
*/ 

DirSearchDirectories (REQUEST_STRUCT *rqptr)

{
   DIR_TASK  *tkptr;

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

   if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD_DIR))
      WatchThis (rqptr, FI_LI, WATCH_MOD_DIR,
                 "DirSearchDirectories() !&F !&S",
                 &DirSearchDirectories, rqptr->rqNet.WriteIOsb.Status);

   if (VMSnok (rqptr->rqNet.WriteIOsb.Status))
   {
      /* network write has failed (as AST), bail out now */
      DirEnd (rqptr);
      return;
   }

   /* get the pointer to the task structure */
   tkptr = rqptr->DirTaskPtr;

   if (rqptr->rqAuth.VmsUserProfileLength) sys$setprv (1, &SysPrvMask, 0, 0);
   OdsSearch (&tkptr->SearchOds, &DirDirectories, rqptr);
   if (rqptr->rqAuth.VmsUserProfileLength) sys$setprv (0, &SysPrvMask, 0, 0);
}

/*****************************************************************************/
/*
AST completion routine called each time sys$search() completes.  It will 
either point to another file name found or have "no more files found" status 
(or an error!).  If a file (directory) has been found check if its a "hidden" 
directory and if not format its details for the listing.
*/ 

DirDirectories (struct FAB *FabPtr)

{
   int  status;
   char  LocalDirectory [ODS_MAX_FILE_NAME_LENGTH+1];
   char  *cptr, *sptr;
   REQUEST_STRUCT  *rqptr;
   DIR_TASK  *tkptr;

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

#if WATCH_MOD
   HttpdCheckPriv (FI_LI);
#endif /* WATCH_MOD */

   rqptr = FabPtr->fab$l_ctx;

   if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD_DIR))
      WatchThis (rqptr, FI_LI, WATCH_MOD_DIR,
                 "DirDirectories() !&F sts:!&S stv:!&S",
                 &DirDirectories, FabPtr->fab$l_sts, FabPtr->fab$l_stv);

   tkptr = rqptr->DirTaskPtr;

   if (VMSnok (status = tkptr->SearchOds.Fab.fab$l_sts))
   {
      /* if its a search list treat directory not found as if file not found */
      if ((tkptr->SearchOds.Nam_fnb & NAM$M_SEARCH_LIST) && status == RMS$_DNF)
         status = RMS$_FNF;
      
      if (status == RMS$_FNF || status == RMS$_NMF)
      {
         /***************************/
         /* end of directory search */
         /***************************/

         tkptr->SearchOds.ParseInUse = false;
         DirBeginFiles (rqptr);
         return;
      }

      /*************************/
      /* protection violation? */
      /*************************/

      if ((status == SS$_NOPRIV || status == RMS$_PRV) &&
          Config.cfDir.NoPrivIgnore)
      {
         tkptr->SearchOds.ParseInUse = false;
         if (tkptr->FormatLikeVms)
         {
            DirFormat (rqptr, &DirEndOutput, "", true);
            return;
         }
         else
         {
            DirEndOutput (rqptr);
            return;
         }
      }

      /**********************/
      /* sys$search() error */
      /**********************/

      rqptr->rqResponse.ErrorTextPtr = tkptr->DirPath;
      rqptr->rqResponse.ErrorOtherTextPtr = tkptr->SearchOds.NamDevicePtr;
      ErrorVmsStatus (rqptr, status, FI_LI);
      DirEnd (rqptr);
      return;
   }

   /* terminate following the last character in the version number */
   tkptr->SearchOds.NamVersionPtr[tkptr->SearchOds.NamVersionLength] = '\0';

   if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD_DIR))
      WatchThis (rqptr, FI_LI, WATCH_MOD_DIR,
                 "!&Z", tkptr->SearchOds.NamDevicePtr);

   /************************/
   /* directory access ok? */
   /************************/

   /*
      Create a directory specification from a directory file name.
      (e.g. "DEVICE:[DIR1.DIR2]" from "DEVICE:[DIR1]DIR2.DIR")
   */
   sptr = LocalDirectory;
   cptr = tkptr->SearchOds.NamDevicePtr;
   tkptr->SearchOds.NamTypePtr[0] = '\0';
   while (*cptr) *sptr++ = *cptr++;
   tkptr->SearchOds.NamTypePtr[0] = '.';
   *sptr++ = ']';
   *sptr = '\0';
   sptr -= 2;
   while (*sptr != ']') sptr--;
   *sptr = '.';

   /*
      If access is allowed display the details.
      Otherwise, its as if the directory just didn't exist!
      Access may also be denied if file protections prevent checking!
   */

   if (CliDemo ||
       rqptr->rqAuth.SkelKeyAuthenticated ||
       rqptr->rqAuth.VmsUserProfileLength)
   {
      /* the VMS security profile allows access, why further prohibit it? */
      status = SS$_NORMAL;
   }
   else
   if (VMSok (status = OdsFileExists (LocalDirectory, DirHiddenFileName)))
      status = RMS$_DNF;
   else
   if ((Config.cfDir.AccessSelective ||
        rqptr->rqPathSet.DirAccessSelective) &&
        !rqptr->rqPathSet.DirAccess)
      status = OdsFileExists (LocalDirectory, DirBrowsableFileName);
   else
      status = SS$_NORMAL;

   if (VMSnok (status))
   {
      /* directory shouldn't/couldn't be accessed */
      DirSearchDirectories (rqptr);
      return;
   }

   tkptr->Description[0] = '\0';
   ConfigContentType (&tkptr->ContentInfo, tkptr->SearchOds.NamTypePtr);
   DirFormat (rqptr, &DirSearchDirectories, "", false);
}

/*****************************************************************************/
/*
Initiate the listing of non-directory files.
*/ 

DirBeginFiles (REQUEST_STRUCT *rqptr)

{
   DIR_TASK  *tkptr;

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

   if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD_DIR))
      WatchThis (rqptr, FI_LI, WATCH_MOD_DIR,
                 "DirBeginFiles() !&F", &DirBeginFiles);

   tkptr = rqptr->DirTaskPtr;

   OdsParse (&tkptr->SearchOds,
             tkptr->FilePart, 0, tkptr->DirectoryPart, 0,
             tkptr->NamNop, &DirBeginFilesParseAst, rqptr);
}

/*****************************************************************************/
/*
Asynchronous parse from DirBeginFiles() has completed.
*/ 

DirBeginFilesParseAst (struct FAB *FabPtr)

{
   int  status;
   REQUEST_STRUCT  *rqptr;
   DIR_TASK  *tkptr;

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

   rqptr = FabPtr->fab$l_ctx;

   if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD_DIR))
      WatchThis (rqptr, FI_LI, WATCH_MOD_DIR,
                 "DirBeginFilesParseAst() !&F sts:!&S stv:!&S",
                 &DirBeginFilesParseAst, FabPtr->fab$l_sts, FabPtr->fab$l_stv);

   tkptr = rqptr->DirTaskPtr;

   /* check the status from the actual sys$parse() */
   if (VMSnok (status = tkptr->SearchOds.Fab.fab$l_sts))
   {
      rqptr->rqResponse.ErrorTextPtr = tkptr->DirPath;
      rqptr->rqResponse.ErrorOtherTextPtr = tkptr->DirSpec;
      ErrorVmsStatus (rqptr, status, FI_LI);
      DirEnd (rqptr);
      return;
   }

   /* fudge this status for the first call */
   rqptr->rqNet.WriteIOsb.Status = SS$_NORMAL;
   DirSearchFiles (rqptr);
}

/*****************************************************************************/
/*
AST completion routine called each time sys$search() completes.  It will 
either point to another file found or have "no more files found" status 
(or an error!).
*/ 

DirSearchFiles (REQUEST_STRUCT *rqptr)

{
   int  status;
   DIR_TASK  *tkptr;

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

   if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD_DIR))
      WatchThis (rqptr, FI_LI, WATCH_MOD_DIR, "DirSearchFiles() !&F !&S",
                 &DirSearchFiles, rqptr->rqNet.WriteIOsb.Status);

   tkptr = rqptr->DirTaskPtr;

   if (VMSnok (rqptr->rqNet.WriteIOsb.Status))
   {
      /* network write has failed (as AST), bail out now */
      DirEnd (rqptr);
      return;
   }

   if (rqptr->rqAuth.VmsUserProfileLength) sys$setprv (1, &SysPrvMask, 0, 0);
   OdsSearch (&tkptr->SearchOds, &DirFiles, rqptr);
   if (rqptr->rqAuth.VmsUserProfileLength) sys$setprv (0, &SysPrvMask, 0, 0);
}

/*****************************************************************************/
/*
This is an AST completion routine called each time sys$search() completes.  It 
will either point to another file name found or have "no more files found" 
status (or an error!).  If a file call a function to determine if the file 
will contain a "description".  When that function returns just return to the 
AST interrupted routine.
*/ 

DirFiles (struct FAB *FabPtr)

{
   int  status;
   char  *cptr, *sptr;
   REQUEST_AST AstFunction;
   REQUEST_STRUCT  *rqptr;
   DIR_TASK  *tkptr;

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

#if WATCH_MOD
   HttpdCheckPriv (FI_LI);
#endif /* WATCH_MOD */

   rqptr = FabPtr->fab$l_ctx;

   if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD_DIR))
      WatchThis (rqptr, FI_LI, WATCH_MOD_DIR,
                 "DirFiles() !&F sts:!&S stv:!&S",
                 &DirFiles, FabPtr->fab$l_sts, FabPtr->fab$l_stv);

   tkptr = rqptr->DirTaskPtr;

   if (VMSnok (status = tkptr->SearchOds.Fab.fab$l_sts))
   {
      /* if its a search list treat directory not found as if file not found */
      if ((tkptr->SearchOds.Nam_fnb & NAM$M_SEARCH_LIST) &&
          status == RMS$_DNF)
         status = RMS$_FNF;

      if (status == RMS$_FNF || status == RMS$_NMF)
      {
         /**********************/
         /* end of file search */
         /**********************/

         tkptr->SearchOds.ParseInUse = false;
         if (tkptr->FormatLikeVms)
         {
            DirFormat (rqptr, &DirEndOutput, "", true);
            return;
         }
         else
         {
            DirEndOutput (rqptr);
            return;
         }
      }

      /**********************/
      /* sys$search() error */
      /**********************/

      rqptr->rqResponse.ErrorTextPtr = tkptr->DirPath;
      rqptr->rqResponse.ErrorOtherTextPtr = tkptr->SearchOds.NamDevicePtr;
      ErrorVmsStatus (rqptr, status, FI_LI);
      DirEnd (rqptr);
      return;
   }

   /* terminate following the last character in the version number */
   tkptr->SearchOds.NamVersionPtr[tkptr->SearchOds.NamVersionLength] = '\0';

   if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD_DIR))
      WatchThis (rqptr, FI_LI, WATCH_MOD_DIR,
                 "!&Z", tkptr->SearchOds.NamDevicePtr);

   /* we've already output the directories, ignore them */
   if (strsame (tkptr->SearchOds.NamTypePtr, ".DIR;", 5))
   {
      if (Debug) fprintf (stdout, ".DIR\n");
      DirSearchFiles (rqptr);
      return;
   }

   /* in true Unix-style "hidden" files do not appear (those beginning ".") */
   if (*tkptr->SearchOds.NamNamePtr == '.' ||
       *(unsigned short*)tkptr->SearchOds.NamNamePtr == '^.')
   {
      /* except in demo mode or to VMS authenticated and profiled requests */
      if (!CliDemo &&
          !rqptr->rqAuth.SkelKeyAuthenticated &&
          !rqptr->rqAuth.VmsUserProfileLength)
      {
         if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD_DIR))
            WatchThis (rqptr, FI_LI, WATCH_MOD_DIR, ".FILE (hidden)");
         DirSearchFiles (rqptr);
         return;
      }
   }

   if (rqptr->PathOds == MAPURL_PATH_ODS_ADS ||
       rqptr->PathOds == MAPURL_PATH_ODS_SMB)
      cptr = MapUrl_AdsFileType (tkptr->SearchOds.NamTypePtr);
   else
   if (rqptr->PathOds == MAPURL_PATH_ODS_SRI)
      cptr = MapUrl_SriFileType (tkptr->SearchOds.NamTypePtr);
   else
      cptr = tkptr->SearchOds.NamTypePtr;
   ConfigContentType (&tkptr->ContentInfo, cptr);

   if (Config.cfDir.DescriptionLines &&
       ConfigSameContentType (tkptr->ContentInfo.ContentTypePtr,
                              "text/html", -1))
   {
      /* generate an HTML description */
      Description (rqptr,
                   &DirEndDescription, 
                   tkptr->SearchOds.ResFileName,
                   tkptr->Description,
                   sizeof(tkptr->Description),
                   DESCRIPTION_TEXT_HTML);
      return;
   }
   else
   {
      /* description is the content-type */
      tkptr->Description[0] = '\0';
      DirFormat (rqptr, &DirSearchFiles, "", false);
      return;
   }
}

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

DirEndDescription (REQUEST_STRUCT *rqptr)

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

   if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD_DIR))
      WatchThis (rqptr, FI_LI, WATCH_MOD_DIR,
                 "DirEndDescription() !&F", &DirEndDescription);

   DirFormat (rqptr, &DirSearchFiles, "", false);
}

/*****************************************************************************/
/*
Directories have been listed, files have been listed.  If appropriate provide 
a readme at the bottom, then conclude the HTML and the directory listing.
*/ 

DirEndOutput (REQUEST_STRUCT *rqptr)

{
   int  status;
   REQUEST_AST AstFunction;
   DIR_TASK  *tkptr;

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

   if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD_DIR))
      WatchThis (rqptr, FI_LI, WATCH_MOD_DIR,
                 "DirEndOutput() !&F", &DirEndOutput);

   /* get the pointer to the task structure */
   tkptr = rqptr->DirTaskPtr;

   if (Config.cfDir.ReadMeBottom && tkptr->IncludeAnyReadme)
      AstFunction = &DirReadMeBottom;
   else
      AstFunction = &DirEnd;
 
   if (tkptr->Delimit == DELIMIT_BOTH ||
       tkptr->Delimit == DELIMIT_FOOTER)
   {
      NetWriteBuffered (rqptr, AstFunction,
                        "<HR SIZE=2 NOSHADE></PRE>\n", 27);
      return;
   }

   /* footer not required, just tie-off the preformatted text */
   NetWriteBuffered (rqptr, AstFunction, "</PRE>\n", 7);
}

/*****************************************************************************/
/*
Using the directive string pointed to by 'tkptr->LayoutPtr' format the 
directory listing column headings and sys$fao() directive string used to format
the information for each directory/file.  Return normal or error status.  This
function buffers the details of the default generic and VMS directory layout
the first time each is required.  There-after the buffered layout information
is used, reducing overhead.  Only if a user-supplied layout is required does
any format processing occur again.
*/ 
 
DirFormatLayout (REQUEST_STRUCT *rqptr)

{
#  define LAYOUT_SIZE 256

   static  BOOL  MakeDescriptionLink,
                 ShowNoType,
                 ShowUpperCase;

   static int  FieldWidthCdt,
               FieldWidthDescription,
               FieldWidthName,
               FieldWidthOwner,
               FieldWidthRdt,
               FieldWidthSize,
               HeadingCreatedLength,
               HeadingDescriptionLength,
               HeadingNameLength,
               HeadingOwnerLength,
               HeadingProtectionLength,
               HeadingRevisedLength,
               HeadingSizeLength,
               LayoutFaoLength,
               LayoutHeadingLength,
               LayoutSizeKilo,
               VmsFieldWidthCdt,
               VmsFieldWidthDescription,
               VmsFieldWidthName,
               VmsFieldWidthOwner,
               VmsFieldWidthRdt,
               VmsFieldWidthSize,
               VmsLayoutFaoLength,
               VmsLayoutHeadingLength,
               VmsLayoutSizeKilo;

   static char  LayoutFao [LAYOUT_SIZE/2],
                LayoutHeading [LAYOUT_SIZE],
                VmsLayoutFao [LAYOUT_SIZE/2],
                VmsLayoutHeading [LAYOUT_SIZE];

   static char  *HeadingCreatedPtr,
                *HeadingDescriptionPtr,
                *HeadingNamePtr,
                *HeadingOwnerPtr,
                *HeadingProtectionPtr,
                *HeadingRevisedPtr,
                *HeadingSizePtr,
                *ColumnHeadingsPtr;

   BOOL  ForbiddenLayout;
   int   cnt, idx, size;
   char  String [LAYOUT_SIZE];
   char  *cptr, *lptr, *sptr, *wptr, *zptr,
         *LayoutBufferPtr,
         *LayoutFaoPtr,
         *LayoutHeadingPtr;
   DIR_TASK  *tkptr;

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

   if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD_DIR))
      WatchThis (rqptr, FI_LI, WATCH_MOD_DIR, "DirFormatLayout()");

   /* get the pointer to the task structure */
   tkptr = rqptr->DirTaskPtr;

   tkptr->MakeDescriptionLink = false;

   if (!ColumnHeadingsPtr)
   {
      /******************************/
      /* initialize column headings */
      /******************************/

      sptr = MsgFor(rqptr,MSG_DIR_COLUMN_HEADINGS);
      cptr = ColumnHeadingsPtr = VmGet (strlen(sptr));
      strcpy (cptr, sptr);

      HeadingCreatedPtr = cptr;
      while (*cptr && *cptr != '|') cptr++;
      if (*cptr) *cptr++ = '\0';
      HeadingCreatedLength = strlen(HeadingCreatedPtr);
      HeadingDescriptionPtr = cptr;
      while (*cptr && *cptr != '|') cptr++;
      if (*cptr) *cptr++ = '\0';
      HeadingDescriptionLength = strlen(HeadingDescriptionPtr);
      HeadingNamePtr = cptr;
      while (*cptr && *cptr != '|') cptr++;
      if (*cptr) *cptr++ = '\0';
      HeadingNameLength = strlen(HeadingNamePtr);
      HeadingOwnerPtr = cptr;
      while (*cptr && *cptr != '|') cptr++;
      if (*cptr) *cptr++ = '\0';
      HeadingOwnerLength = strlen(HeadingOwnerPtr);
      HeadingProtectionPtr = cptr;
      while (*cptr && *cptr != '|') cptr++;
      if (*cptr) *cptr++ = '\0';
      HeadingProtectionLength = strlen(HeadingProtectionPtr);
      HeadingRevisedPtr = cptr;
      while (*cptr && *cptr != '|') cptr++;
      if (*cptr) *cptr++ = '\0';
      HeadingRevisedLength = strlen(HeadingRevisedPtr);
      HeadingSizePtr = cptr;
      while (*cptr && *cptr != '|') cptr++;
      if (*cptr) *cptr++ = '\0';
      HeadingSizeLength = strlen(HeadingSizePtr);
   }

   if (!tkptr->LayoutPtr)
   {
      /******************/
      /* default layout */
      /******************/

      if (Config.cfDir.DefaultLayout[0])
         tkptr->LayoutPtr = Config.cfDir.DefaultLayout;
      else
         tkptr->LayoutPtr = DEFAULT_DIR_LAYOUT;

      if (!tkptr->FormatLikeVms && LayoutFaoLength)
      {
         /* default generic heading and fao have already been generated */
         tkptr->LayoutFaoPtr = LayoutFao;
         tkptr->LayoutFaoLength = LayoutFaoLength;
         tkptr->LayoutHeadingPtr = LayoutHeading;
         tkptr->LayoutHeadingLength = LayoutHeadingLength;
         tkptr->FieldWidthCdt = FieldWidthCdt;
         tkptr->FieldWidthDescription = FieldWidthDescription;
         tkptr->FieldWidthName = FieldWidthName;
         tkptr->FieldWidthOwner = FieldWidthOwner;
         tkptr->FieldWidthRdt = FieldWidthRdt;
         tkptr->FieldWidthSize = FieldWidthSize;
         tkptr->SizeKilo = LayoutSizeKilo;
         tkptr->MakeDescriptionLink = MakeDescriptionLink;
         tkptr->ShowNoType = ShowNoType;
         tkptr->ShowUpperCase = ShowUpperCase;

         if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD_DIR))
         {
            WatchData (LayoutHeading, strlen(LayoutHeading));
            WatchData (LayoutFao, strlen(LayoutFao));
         }

         return (SS$_NORMAL);
      }

      if (tkptr->FormatLikeVms && VmsLayoutFaoLength)
      {
         /* default VMS heading and fao have already been generated */
         tkptr->LayoutFaoPtr = VmsLayoutFao;
         tkptr->LayoutFaoLength = VmsLayoutFaoLength;
         tkptr->LayoutHeadingPtr = VmsLayoutHeading;
         tkptr->LayoutHeadingLength = VmsLayoutHeadingLength;
         tkptr->FieldWidthCdt = VmsFieldWidthCdt;
         tkptr->FieldWidthDescription = VmsFieldWidthDescription;
         tkptr->FieldWidthName = VmsFieldWidthName;
         tkptr->FieldWidthOwner = VmsFieldWidthOwner;
         tkptr->FieldWidthRdt = VmsFieldWidthRdt;
         tkptr->FieldWidthSize = VmsFieldWidthSize;
         tkptr->SizeKilo = VmsLayoutSizeKilo;
         tkptr->MakeDescriptionLink = MakeDescriptionLink;
         tkptr->ShowNoType = ShowNoType;
         tkptr->ShowUpperCase = ShowUpperCase;

         if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD_DIR))
         if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD_DIR))
         {
            WatchData (VmsLayoutHeading, strlen(VmsLayoutHeading));
            WatchData (VmsLayoutFao, strlen(VmsLayoutFao));
         }

         return (SS$_NORMAL);
      }

      /* default headings and layouts have not been generated ... do it! */
      if (tkptr->FormatLikeVms)
      {
         /* use the function-internal VMS buffers */
         LayoutHeadingPtr = VmsLayoutHeading;
         LayoutFaoPtr = VmsLayoutFao;
      }
      else
      {
         /* use the function-internal generic buffers */
         LayoutHeadingPtr = LayoutHeading;
         LayoutFaoPtr = LayoutFao;
      }
   }
   else
   {
      /**********************/
      /* non-default layout */
      /**********************/

      /* use scratch space to generate the non-default heading and fao */
      LayoutHeadingPtr = LayoutFaoPtr = String;
      tkptr->SizeKilo = 1024;
   }

   /********************************/
   /* assess layout for forbiddens */
   /********************************/

   if (!Config.cfDir.OwnerEnabled && !rqptr->rqAuth.VmsUserProfileLength)
   {
      /* remove OWNER and PROTECTION directives if found */
      ForbiddenLayout = false;
      cptr = tkptr->LayoutPtr;
      while (*cptr)
      {
         if (*cptr == ':')
         {
            cptr++;
            if (*cptr) cptr++;
            continue;
         }
         if (toupper(*cptr) == 'P' || toupper(*cptr) == 'O')
         {
            ForbiddenLayout = true;
            cptr++;
            continue;
         }
         cptr++;
      }
      if (ForbiddenLayout)
      {
         /* remove forbidden directives creating new layout dynamic buffer */
         sptr = LayoutBufferPtr = VmGetHeap (rqptr, LAYOUT_SIZE);
         zptr = sptr + LAYOUT_SIZE;
         cptr = tkptr->LayoutPtr;
         while (*cptr)
         {
            if (sptr >= zptr) break;
            if (*cptr == ':')
            {
               if (sptr < zptr) *sptr++ = *cptr++;
               if (*cptr && sptr < zptr) *sptr++ = *cptr++;
               continue;
            }
            if (toupper(*cptr) == 'P' || toupper(*cptr) == 'O')
            {
               cptr++;
               while (*cptr == '_') cptr++;
               continue;
            }
            if (sptr < zptr) *sptr++ = *cptr++;
         }
         if (sptr >= zptr)
         {
            ErrorGeneralOverflow (rqptr, FI_LI);
            DirEnd (rqptr);
            return;
         }
         *sptr = '\0';
         tkptr->LayoutPtr = LayoutBufferPtr;
         if (Debug)
            fprintf (stdout, "tkptr->LayoutPtr |%s|\n", tkptr->LayoutPtr);
      }
   }

   /******************************/
   /* first, the column headings */
   /******************************/

   if (Debug) fprintf (stdout, "tkptr->LayoutPtr |%s|\n", tkptr->LayoutPtr);

   zptr = (sptr = LayoutHeadingPtr) + LAYOUT_SIZE;
   lptr = tkptr->LayoutPtr;
   wptr = "";

   if (rqptr->rqPathSet.DirStyle == MAPURL_DIR_STYLE_HTDIR)
      cptr = "<PRE><B>";
   else
      cptr = "<PRE>";
   while (*cptr && sptr < zptr) *sptr++ = *cptr++;

   while (*lptr && *lptr != '&' && sptr < zptr)
   {
      /** if (Debug) fprintf (stdout, "lptr |%s|\n", lptr); **/

      if (isdigit(*lptr))
      {
         wptr = lptr;
         while (*lptr && isdigit(*lptr)) lptr++;
         continue;
      }

      if (wptr[0])
      {
         cnt = atoi(wptr);
         if (cnt < 0) cnt = 0;
         wptr = "";
      }
      else
         cnt = 0;

      switch (toupper(*lptr))
      {
         case ' ' :
         case '_' :
            if (!cnt) cnt = DEFAULT_FIELD_WIDTH_INTERFIELD;
            break;

         case 'C' :
            cptr = HeadingCreatedPtr;
            if (!cnt) cnt = DEFAULT_FIELD_WIDTH_CDT;
            if (cnt < (size = HeadingCreatedLength)) cnt = size;
            if (tkptr->FormatLikeVms) cnt += 5;
            tkptr->FieldWidthCdt = cnt;
            break;

         case 'D' :
            /* flush left */
            cptr = HeadingDescriptionPtr;
            if (cnt)
            {
               if (cnt > 255) cnt = 255;
               if (cnt < (size = HeadingDescriptionLength)) cnt = size;
               tkptr->FieldWidthDescription = cnt;
            }
            else
            {
               cnt = size = HeadingDescriptionLength;
               tkptr->FieldWidthDescription = 0;
            }
            if (lptr[1] == LAYOUT_PARAMETER)
            {
               switch (toupper(lptr[2]))
               {
                  case 'L' :
                     tkptr->MakeDescriptionLink = true;
                     break;
                  default :
                     /* unknown layout directive character */
                     rqptr->rqResponse.HttpStatus = 501;
                     ErrorGeneral (rqptr, MsgFor(rqptr,MSG_DIR_LAYOUT), FI_LI);
                     return (STS$K_ERROR);
               }
            }
            break;

         case 'I' :  /* icon */
            break;

         case 'L' :
         case 'N' :
            cptr = HeadingNamePtr;
            if (!cnt) cnt = DEFAULT_FIELD_WIDTH_NAME;
            if (cnt < (size = HeadingNameLength)) cnt = size;
            tkptr->FieldWidthName = cnt;
            /* this layout parameter can be used like "L:F:U" */
            idx = 0;
            while (lptr[idx+1] == LAYOUT_PARAMETER)
            {
               switch (toupper(lptr[idx+2]))
               {
                  case 'F' :
                     /* for backward compatibility just ignore this */
                     break;
                  case 'N' :
                     tkptr->ShowNoType = true;
                     break;
                  case 'U' :
                     tkptr->ShowUpperCase = true;
                     break;
                  default :
                     /* unknown layout directive character */
                     rqptr->rqResponse.HttpStatus = 501;
                     ErrorGeneral (rqptr, MsgFor(rqptr,MSG_DIR_LAYOUT), FI_LI);
                     return (STS$K_ERROR);
               }
               idx += 2;
            }
            break;

         case 'O' :
            cptr = HeadingOwnerPtr;
            if (!cnt) cnt = DEFAULT_FIELD_WIDTH_OWNER;
            if (cnt < (size = HeadingOwnerLength)) cnt = size;
            tkptr->FieldWidthOwner = cnt;
            break;

         case 'P' :
            cptr = HeadingProtectionPtr;
            if (!cnt) cnt = DEFAULT_FIELD_WIDTH_PROTECTION;
            if (cnt < (size = HeadingProtectionLength)) cnt = size;
            tkptr->FieldWidthProtection = cnt;
            break;

         case 'R' :
            cptr = HeadingRevisedPtr;
            if (!cnt) cnt = DEFAULT_FIELD_WIDTH_RDT;
            if (cnt < (size = HeadingRevisedLength)) cnt = size;
            if (tkptr->FormatLikeVms) cnt += 5;
            tkptr->FieldWidthRdt = cnt;
            break;

         case 'S' :
            if (tkptr->FormatLikeVms)
            {
               if (!cnt) cnt = DEFAULT_FIELD_WIDTH_SIZE;
               if (cnt < (size = DEFAULT_FIELD_WIDTH_SIZE - 2)) cnt = size;
               /* expand size field width by 7 for blocks allocated/used */
               cnt += 7;
            }
            else
            if (lptr[1] == LAYOUT_PARAMETER)
            {
               switch (toupper(lptr[2]))
               {
                  case 'D' :
                  case 'F' :
                     if (!cnt) cnt = DEFAULT_FIELD_WIDTH_SIZE;
                     if (cnt < (size = DEFAULT_FIELD_WIDTH_SIZE - 2))
                        cnt = size;
                     break;
                  case 'B' :
                  case 'K' :
                  case 'M' :
                     if (!cnt) cnt = DEFAULT_FIELD_WIDTH_DECIMAL;
                     if (cnt < (size = DEFAULT_FIELD_WIDTH_DECIMAL - 7))
                        cnt = size;
                     break;
                  default :
                     /* unknown layout directive character */
                     rqptr->rqResponse.HttpStatus = 501;
                     ErrorGeneral (rqptr, MsgFor(rqptr,MSG_DIR_LAYOUT), FI_LI);
                     return (STS$K_ERROR);
               }
               tkptr->SizeKilo = 1000;
            }
            else
            {
               if (!cnt) cnt = DEFAULT_FIELD_WIDTH_SIZE;
               if (cnt < (size = DEFAULT_FIELD_WIDTH_SIZE - 2)) cnt = size;
               /* expand size field width by 7 for blocks allocated/used */
               if (tkptr->FormatLikeVms) cnt += 7;
            }
            cptr = HeadingSizePtr;
            tkptr->FieldWidthSize = cnt;
            break;

         case 'U' :
            /* force to upper case, just ignore, MUST be first directive */
            tkptr->ShowUpperCase = true;
            break;

         default :
            /* unknown layout directive character */
            rqptr->rqResponse.HttpStatus = 501;
            ErrorGeneral (rqptr, MsgFor(rqptr,MSG_DIR_LAYOUT), FI_LI);
            return (STS$K_ERROR);
      }

      switch (toupper(*lptr))
      {
         case ' ' :
         case '_' :
            while (cnt-- && sptr < zptr) *sptr++ = ' ';
            break;

         case 'C' :
         case 'R' :
         case 'S' :
            /* flush the column heading right using spaces */
            cnt -= size;
            while (cnt-- && sptr < zptr) *sptr++ = ' ';
            while (*cptr && sptr < zptr) *sptr++ = *cptr++;
            break;

         case 'D' :
         case 'L' :
         case 'N' :
         case 'O' :
         case 'P' :
            /* flush the column heading left using spaces */
            cnt -= size;
            while (*cptr && sptr < zptr) *sptr++ = *cptr++;
            while (cnt-- && sptr < zptr) *sptr++ = ' ';
            break;

         case 'I' :
            cptr = ConfigBlankIconPtr;
            while (*cptr && sptr < zptr) *sptr++ = *cptr++;
            break;
      }
      while (lptr[1] == LAYOUT_PARAMETER) lptr += 2;
      lptr++;
   }

   if (rqptr->rqPathSet.DirStyle == MAPURL_DIR_STYLE_HTDIR)
      cptr = "</B><HR SIZE=2 NOSHADE>\n";
   else
      cptr = "<HR SIZE=2 NOSHADE>\n";
   while (*cptr && sptr < zptr) *sptr++ = *cptr++;

   if (sptr >= zptr)
   {
      /* should be enough for all but a deliberate formatting hack! */
      rqptr->rqResponse.HttpStatus = 501;
      ErrorGeneral (rqptr, MsgFor(rqptr,MSG_DIR_LAYOUT), FI_LI);
      return (STS$K_ERROR);
   }
   *sptr = '\0';
   cnt = sptr - LayoutHeadingPtr;

   if (LayoutHeadingPtr == LayoutHeading)
   {
      /* first time a generic layout has been required, buffer details */
      tkptr->LayoutHeadingPtr = LayoutHeading;
      tkptr->LayoutHeadingLength = LayoutHeadingLength = cnt;
      FieldWidthCdt = tkptr->FieldWidthCdt;
      FieldWidthDescription = tkptr->FieldWidthDescription;
      FieldWidthName = tkptr->FieldWidthName;
      FieldWidthOwner = tkptr->FieldWidthOwner;
      FieldWidthRdt = tkptr->FieldWidthRdt;
      FieldWidthSize = tkptr->FieldWidthSize;
      MakeDescriptionLink = tkptr->MakeDescriptionLink; 
      ShowNoType = tkptr->ShowNoType;
      ShowUpperCase = tkptr->ShowUpperCase;
   }
   else
   if (LayoutHeadingPtr == VmsLayoutHeading)
   {
      /* first time a VMS layout has been required, buffer details */
      tkptr->LayoutHeadingPtr = VmsLayoutHeading;
      tkptr->LayoutHeadingLength = VmsLayoutHeadingLength = cnt;
      VmsFieldWidthCdt = tkptr->FieldWidthCdt;
      VmsFieldWidthDescription = tkptr->FieldWidthDescription;
      VmsFieldWidthName = tkptr->FieldWidthName;
      VmsFieldWidthOwner = tkptr->FieldWidthOwner;
      VmsFieldWidthRdt = tkptr->FieldWidthRdt;
      VmsFieldWidthSize = tkptr->FieldWidthSize;
      MakeDescriptionLink = tkptr->MakeDescriptionLink; 
      ShowNoType = tkptr->ShowNoType;
      ShowUpperCase = tkptr->ShowUpperCase;
   }
   else
   {
      /* this is a user-supplied layout, place it in the thread's heap */
      tkptr->LayoutHeadingPtr = lptr = VmGetHeap (rqptr, cnt+1);
      memcpy (lptr, LayoutHeadingPtr, cnt+1);
      tkptr->LayoutHeadingLength = cnt;
   }

   /******************************************/
   /* second, the sys$fao() directive string */
   /******************************************/

   zptr = (sptr = LayoutFaoPtr) + LAYOUT_SIZE / 2;
   lptr = tkptr->LayoutPtr;
   wptr = "";

   /* accomodate an optional leading new-line (for creating a blank line) */
   cptr = "!AZ";
   while (*cptr && sptr < zptr) *sptr++ = *cptr++;

   while (*lptr && *lptr != '&' && sptr < zptr)
   {
      /** if (Debug) fprintf (stdout, "lptr |%s|\n", lptr); **/

      if (isdigit(*lptr))
      {
         wptr = lptr;
         while (*lptr && isdigit(*lptr)) lptr++;
         continue;
      }

      if (wptr[0])
      {
         cnt = atoi(wptr);
         if (cnt < 0) cnt = 0;
         wptr = "";
      }
      else
         cnt = 0;

      switch (toupper(*lptr))
      {
         case ' ' :
         case '_' :
            if (!cnt) cnt = DEFAULT_FIELD_WIDTH_INTERFIELD;
            while (cnt-- && sptr < zptr) *sptr++ = ' ';
            break;

         case 'C' :
         case 'R' :
         case 'S' :
            /* right justify, then value */
            cptr = "!#<!#AZ!AZ!>";
            while (*cptr && sptr < zptr) *sptr++ = *cptr++;
            break;

         case 'D' :
            /* nothing fancy (allow for leading/trailing quotes and link) */
            if (tkptr->MakeDescriptionLink)
               if (tkptr->FieldWidthDescription)
                  cptr = "!AZ!AZ!AZ!AZ!#<!#AZ!AZ!AZ!AZ!>";
               else
                  cptr = "!AZ!AZ!AZ!AZ!AZ!AZ!AZ";
            else
               if (tkptr->FieldWidthDescription)
                  cptr = "!AZ!#<!#AZ!AZ!AZ!>";
               else
                  cptr = "!AZ!AZ!AZ";
            while (*cptr && sptr < zptr) *sptr++ = *cptr++;
            break;

         case 'I' :
            /* nothing fancy */
            cptr = "!AZ!AZ!AZ!AZ!AZ!AZ";
            while (*cptr && sptr < zptr) *sptr++ = *cptr++;
            break;

         case 'L' :
            /* left justify, then link, name, and name overflow */
            cptr = "!AZ!AZ!AZ!#<!AZ!AZ!AZ!>";
            while (*cptr && sptr < zptr) *sptr++ = *cptr++;
            break;

         case 'N' :
            /* left justify, string, plus overflow */
            cptr = "!#<!AZ!AZ!>";
            while (*cptr && sptr < zptr) *sptr++ = *cptr++;
            break;

         case 'O' :
         case 'P' :
            /* '!#AZ' will left justify value with spaces */
            cptr = "!#AZ";
            while (*cptr && sptr < zptr) *sptr++ = *cptr++;
            break;
      }
      if (lptr[1] == LAYOUT_PARAMETER) lptr += 2;
      lptr++;
   }
   if (sptr < zptr) *sptr++ = '\n';

   if (sptr >= zptr)
   {
      /* should be enough for all but a deliberate formatting hack! */
      rqptr->rqResponse.HttpStatus = 501;
      ErrorGeneral (rqptr, MsgFor(rqptr,MSG_DIR_LAYOUT), FI_LI);
      return (STS$K_ERROR);
   }
   *sptr = '\0';
   cnt = sptr - LayoutFaoPtr;

   if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD_DIR))
   {
       WatchData (LayoutHeadingPtr, strlen(LayoutHeadingPtr));
       WatchData (LayoutFaoPtr, strlen(LayoutFaoPtr));
   }

   if (LayoutFaoPtr == LayoutFao)
   {
      /* first time a generic layout has been required, buffer details */
      tkptr->LayoutFaoPtr = LayoutFao;
      tkptr->LayoutFaoLength = LayoutFaoLength = cnt;
      LayoutSizeKilo = tkptr->SizeKilo;
   }
   else
   if (LayoutFaoPtr == VmsLayoutFao)
   {
      /* first time a VMS layout has been required, buffer details */
      tkptr->LayoutFaoPtr = VmsLayoutFao;
      tkptr->LayoutFaoLength = VmsLayoutFaoLength = cnt;
      VmsLayoutSizeKilo = 1024;
   }
   else
   {
      /* this is a user-supplied layout, place it in the thread's heap */
      tkptr->LayoutFaoPtr = lptr = VmGetHeap (rqptr, cnt+1);
      memcpy (lptr, LayoutFaoPtr, cnt+1);
      tkptr->LayoutFaoLength = cnt;
   }

   return (SS$_NORMAL);
}

/*****************************************************************************/
/*
This function uses the ACP-QIO interface detailed in the "OpenVMS I/O User's 
Reference Manual", and is probably as fast as we can get for this type of file
system functionality!
*/

DirFormat
(
REQUEST_STRUCT *rqptr,
REQUEST_AST AstFunction,
char *ParentDirPath,
BOOL BlockTotals
)
{
   int  status;
   DIR_TASK  *tkptr;

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

   if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD_DIR))
       WatchThis (rqptr, FI_LI, WATCH_MOD_DIR,
                  "DirFormat() !AZ !UL", ParentDirPath, BlockTotals);

   tkptr = rqptr->DirTaskPtr;

   tkptr->DirFormatAstFunction = AstFunction;
   tkptr->DirFormatParentDirPath = ParentDirPath;
   tkptr->DirFormatBlockTotals = BlockTotals;

   if (ParentDirPath[0] || BlockTotals)
   {
      /***************************/
      /* do not queue an ACP I/O */
      /***************************/

      /* explicitly call the AST format routine */
      tkptr->SearchOds.FileQio.IOsb.Status = SS$_NORMAL;
      DirFormatAcpInfoAst (rqptr);
      return;
   }

   /* specially check permissions for each file! */
   if (rqptr->rqAuth.VmsUserProfileLength)
   {
      /*
         SYSUAF-authenticated user.
         Interestingly the sys$qio() could still return valid information
         if the directory had group permissions allowing it, even though
         sys$check_access() indicated no privilege!
      */
      status = AuthVmsCheckUserAccess (rqptr,
                                       tkptr->SearchOds.ResFileName,
                                       tkptr->SearchOds.ResFileNameLength);
      if (VMSok (status))
      {
         sys$setprv (1, &SysPrvMask, 0, 0);
         OdsFileAcpInfo (&tkptr->SearchOds, NULL, rqptr); 
         sys$setprv (0, &SysPrvMask, 0, 0);
      }
      else
         tkptr->SearchOds.FileQio.IOsb.Status = status;

      /* explicitly call the AST format routine */
      DirFormatAcpInfoAst (rqptr);
      return;
   }
   else
      OdsFileAcpInfo (&tkptr->SearchOds, &DirFormatAcpInfoAst, rqptr); 
}

/****************************************************************************/
/*
AST called from OdsFileAcpInfo() when ACP QIO completes.

This overly-long (sorry) function formats and outputs a line of information 
for a parent directory, directory file, non-directory file, or for the final 
line giving used/allocated totals for a VMS format listing. 
*/ 

DirFormatAcpInfoAst (REQUEST_STRUCT *rqptr)

{
   static unsigned long  LibDateTimeContext = 0,
                         LibDateTimeVmsContext = 0,
                         LibOutputFormat = LIB$K_OUTPUT_FORMAT;

   static $DESCRIPTOR (BlocksFaoDsc, "!UL/!UL");
   static $DESCRIPTOR (BytesFaoDsc, "!UL");
   static $DESCRIPTOR (CdtDsc, "");
   static $DESCRIPTOR (DeviceDsc, "");
   static $DESCRIPTOR (KBytesFaoDsc, "!ULK");
   static $DESCRIPTOR (KBytesFractFaoDsc, "!UL.!ULK");
   static $DESCRIPTOR (KBytesFullFractFaoDsc, "!UL.!3ZLK");
   static $DESCRIPTOR (MBytesFaoDsc, "!ULM");
   static $DESCRIPTOR (MBytesFractFaoDsc, "!UL.!ULM");
   static $DESCRIPTOR (MBytesFullFractFaoDsc, "!UL.!6ZLM");
   static $DESCRIPTOR (OutputFormatDsc, "|!DB-!MAAC-!Y4|!H04:!M0|");
   static $DESCRIPTOR (OutputFormatVmsDsc, "|!DB-!MAAU-!Y4|!H04:!M0:!S0|");
   static $DESCRIPTOR (OwnerFaoDsc, "!%I");
   static $DESCRIPTOR (OwnerDsc, "");
   static $DESCRIPTOR (RdtDsc, "");
   static $DESCRIPTOR (SizeDsc, "");
   static $DESCRIPTOR (SizeFaoDsc, "!#* !AZ");
   static $DESCRIPTOR (TotalFilesDsc, "");
   static $DESCRIPTOR (TotalFilesFaoDsc, "!UL files");

   BOOL  ThisIsADirectory;
   int  idx,
        status,
        Bytes,
        Count,
        DateLength,
        EncodedNameDelta,
        MaxIdx;
   unsigned short  AcpChannel,
                   Length;
   unsigned short  NumTime [7];
   unsigned long  AllocatedVbn,
                  EndOfFileVbn;
   unsigned long  FaoVector [64];
   char  *cptr, *sptr, *zptr,
         *IconPtr,
         *AnchorNameOverflowPtr;
   char  AnchorLink [ODS_MAX_FILE_NAME_LENGTH+1],
         AnchorName [ODS_MAX_FILE_NAME_LENGTH+1],
         Cdt [32],
         Description [256],
         IconAnchor [ODS_MAX_FILE_NAME_LENGTH+1],
         Owner [64],
         Protection [64],
         Rdt [32],
         Size [32],
         String [2048],
         TotalFiles [32];
   DIR_TASK  *tkptr;

   unsigned long  *ctxptr;

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

#if WATCH_MOD
   HttpdCheckPriv (FI_LI);
#endif /* WATCH_MOD */

   if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD_DIR))
       WatchThis (rqptr, FI_LI, WATCH_MOD_DIR,
                  "DirFormatAcpInfoAst() !&F !&S", &DirFormatAcpInfoAst,
                  rqptr->DirTaskPtr->SearchOds.FileQio.IOsb.Status);

   tkptr = rqptr->DirTaskPtr;

   /* first deassign the channel allocated by OdsFileAcpInfo() */
   sys$dassgn (tkptr->SearchOds.FileQio.AcpChannel);

   if ((status = tkptr->SearchOds.FileQio.IOsb.Status) == SS$_NOSUCHFILE)
      status = RMS$_FNF;

   if (VMSnok (status)) 
   {
      /*************************/
      /* protection violation? */
      /*************************/

      if ((status == SS$_NOPRIV || status == RMS$_PRV) &&
          Config.cfDir.NoPrivIgnore)
      {
         /* can't return without queueing the next bit of processing! */
         SysDclAst (tkptr->DirFormatAstFunction, rqptr);
         return;
      }

      /*******************/
      /* something else! */
      /*******************/

      rqptr->rqResponse.ErrorTextPtr = tkptr->DirPath;
      rqptr->rqResponse.ErrorOtherTextPtr = tkptr->SearchOds.NamDevicePtr;
      ErrorVmsStatus (rqptr, status, FI_LI);
      DirEnd (rqptr);
      return;
   }

   if (tkptr->DirFormatBlockTotals)
      ThisIsADirectory = false;
   else
      ThisIsADirectory = strsame (tkptr->SearchOds.NamTypePtr, ".DIR;", 5);

   if (ThisIsADirectory)
   {
      /******************************/
      /* no need to display the MFD */
      /******************************/

      if (tkptr->SearchOds.NamNamePtr[0] == '0' &&
          strsame (tkptr->SearchOds.NamNamePtr, "000000.DIR;", 11))
      {
         /* can't return without queueing the next bit of processing! */
         SysDclAst (tkptr->DirFormatAstFunction, rqptr);
         return;
      }
   }

   /****************/
   /* begin format */
   /****************/

   if (!LibDateTimeContext)
   {
      /* initialize the date/time formats */
      status = lib$init_date_time_context (&LibDateTimeContext,
                                           &LibOutputFormat,
                                           &OutputFormatDsc);
      if (VMSok (status))
         status = lib$init_date_time_context (&LibDateTimeVmsContext,
                                              &LibOutputFormat,
                                              &OutputFormatVmsDsc);

      if (VMSnok (status))
         ErrorExitVmsStatus (status, "lib$init_date_time_context()", FI_LI);
   }

   /********************************/
   /* process file name components */
   /********************************/

   IconAnchor[0] = '\0';

   MaxIdx = sizeof(FaoVector);
   idx = 0;

   if (tkptr->DirFormatBlockTotals)
   {
      /****************/
      /* block totals */
      /****************/

      /* must be block totals, which requires a leading newline */
      if (idx < MaxIdx) FaoVector[idx++] = "\n";
   }
   else
   if (tkptr->DirFormatParentDirPath[0])
   {
      /********************/
      /* parent directory */
      /********************/

      unsigned long  LocalFaoVector [32];
      unsigned long  *vecptr;

      /* no leading newline required for the parent directory */
      if (idx < MaxIdx) FaoVector[idx++] = "";
      tkptr->DirectoryCount = -1;

      vecptr = &LocalFaoVector;

      /* when the path may be different from base path (e.g. SSI) */
      if (tkptr->RealPath[0])
         *vecptr++ = tkptr->DirFormatParentDirPath;
      else
         *vecptr++ = "../";
      /* any file name/type/version from the original spec */
      if (tkptr->DirSpecIncludedFilePart ||
          (!Config.cfDir.NoImpliedWildcard &&
           !rqptr->rqPathSet.DirNoImpliedWildcard))
      {
         *vecptr++ = rqptr->PathOds;
         *vecptr++ = tkptr->FilePart;
      }
      else
      {
         *vecptr++ = 0;
         *vecptr++ = "";
      }
      *vecptr++ = tkptr->QueryString[0];
      *vecptr++ = tkptr->QueryString;

      status = WriteFaol (AnchorLink, sizeof(AnchorLink), NULL,
                          "!&%AZ!&%&[AZ!&??\r\r!&;AZ", &LocalFaoVector);
      if (VMSnok (status) || status == SS$_BUFFEROVF)
         ErrorNoticed (status, "WriteFaol()", FI_LI);
      if (Debug) fprintf (stdout, "AnchorLink |%s|\n", AnchorLink);

      if (tkptr->FormatLikeVms)
         memcpy (AnchorName, "[-]", 4);
      else
         memcpy (AnchorName, "../", 4);
      if (Debug) fprintf (stdout, "AnchorName |%s|\n", AnchorName);
   }
   else
   if (ThisIsADirectory)
   {
      /***************/
      /* a directory */
      /***************/

      unsigned long  LocalFaoVector [32];
      unsigned long  *vecptr;

      /* no leading newline required for directories */
      if (idx < MaxIdx) FaoVector[idx++] = "";
      if (tkptr->DirectoryCount <= 0)
         tkptr->DirectoryCount = 1;
      else
         tkptr->DirectoryCount++;

      /* terminate to remove the file type (".DIR") */
      tkptr->SearchOds.NamTypePtr[0] = '\0';

      vecptr = &LocalFaoVector;

      /* when the path may be different from base path (e.g. SSI) */
      *vecptr++ = tkptr->RealPath;
      /* the name of this directory */
      *vecptr++ = "!&%&[AZ";
      *vecptr++ = rqptr->PathOds;
      *vecptr++ = tkptr->SearchOds.NamNamePtr;
      /* any file name/type/version from the original spec */
      if (tkptr->DirSpecIncludedFilePart ||
          (!Config.cfDir.NoImpliedWildcard &&
           !rqptr->rqPathSet.DirNoImpliedWildcard))
      {
         *vecptr++ = "!&%&[AZ";
         *vecptr++ = rqptr->PathOds;
         *vecptr++ = tkptr->FilePart;
      }
      else
         *vecptr++ = "";
      *vecptr++ = tkptr->QueryString[0];
      *vecptr++ = tkptr->QueryString;

      status = WriteFaol (AnchorLink, sizeof(AnchorLink), NULL,
                          "!&%AZ!&@/!&@!&??\r\r!&;AZ", &LocalFaoVector);
      if (VMSnok (status) || status == SS$_BUFFEROVF)
         ErrorNoticed (status, "WriteFaol()", FI_LI);
      if (Debug) fprintf (stdout, "AnchorLink |%s|\n", AnchorLink);

      vecptr = &LocalFaoVector;
      if (tkptr->FormatLikeVms)
         if (!rqptr->PathOds || rqptr->PathOds == MAPURL_PATH_ODS_2)
            *vecptr++ = "[.!&;&^AZ]";
         else
            *vecptr++ = "[.!&;AZ]";
      else
      {
         *vecptr++ = "!&;&[AZ/";
         *vecptr++ = rqptr->PathOds;
      }
      *vecptr++ = tkptr->SearchOds.NamNamePtr;

      status = WriteFaol (AnchorName, sizeof(AnchorName), NULL,
                          "!&@", &LocalFaoVector);
      if (VMSnok (status) || status == SS$_BUFFEROVF)
         ErrorNoticed (status, "WriteFaol()", FI_LI);
      if (Debug) fprintf (stdout, "AnchorName |%s|\n", AnchorName);

      /* restore the type delimitter */
      tkptr->SearchOds.NamTypePtr[0] = '.';

      if (tkptr->ShowUpperCase)
         for (cptr = AnchorName; *cptr; cptr++) *cptr = toupper(*cptr);
   }
   else
   {
      /**********/
      /* a file */
      /**********/

      unsigned long  LocalFaoVector [32];
      unsigned long  *vecptr, *vecptrbuf;

      /* leading newline required for first file after directories */
      if (tkptr->DirectoryCount && !tkptr->FileCount)
         { if (idx < MaxIdx) FaoVector[idx++] = "\n"; }
      else
         if (idx < MaxIdx) FaoVector[idx++] = "";
      tkptr->FileCount++;

      /* if appropriate, terminate to remove the version number */
      if (!tkptr->FormatLikeVms)
      {
         if (tkptr->SearchOds.NamTypeLength <= 1)
            tkptr->SearchOds.NamTypePtr[0] = '\0';
         else
            tkptr->SearchOds.NamVersionPtr[0] = '\0';
      }

      vecptr = &LocalFaoVector;

      if (tkptr->ScriptName[0])
      {
         /* prepend the (optional) script component of the path */
         *vecptr++ = "!&%AZ!&%&[AZ";
         *vecptr++ = tkptr->ScriptName;
         /* need the directory portion of the URL for scripting */
         *vecptr++ = rqptr->PathOds;
         *vecptr++ = tkptr->DirectoryPathPtr;
      }
      else
      {
         *vecptr++ = "!&%&[AZ";
         /* when the path may be different from base path (e.g. SSI) */
         *vecptr++ = rqptr->PathOds;
         *vecptr++ = tkptr->RealPath;
      }

      /* file name and type */
      *vecptr++ = "!&%&[AZ";
      *vecptr++ = rqptr->PathOds;
      *vecptr++ = tkptr->SearchOds.NamNamePtr;

      /* disable auto-scripting by appending a (most-recent) version number */
      if (!tkptr->FormatLikeVms && !tkptr->AutoScriptEnabled)
         *vecptr++ = ";0";
      else
         *vecptr++ = "";

      /* note where we got up to so it can restart at this point */
      vecptrbuf = vecptr;

      /* propagate any request-specified content-type as a query string */
      if (tkptr->QueryContentTypePtr &&
          tkptr->QueryContentTypePtr[0])
      {
         *vecptr++ = "?httpd=content&type=!&%AZ";
         *vecptr++ = tkptr->QueryContentTypePtr;
      }
      else
         *vecptr++ = "";

      status = WriteFaol (AnchorLink, sizeof(AnchorLink), NULL,
                          "!&@!&@!AZ!&@", &LocalFaoVector);
      if (VMSnok (status) || status == SS$_BUFFEROVF)
         ErrorNoticed (status, "WriteFaol()", FI_LI);
      if (Debug) fprintf (stdout, "AnchorLink |%s|\n", AnchorLink);

      if (!tkptr->ContentInfo.TypeText)
      {
         /* just restart back a little to create the icon anchor */
         vecptr = vecptrbuf;

         *vecptr++ = "?httpd=content&type=!&%AZ";
         *vecptr++ = ConfigContentType (NULL, ".TXT");

         status = WriteFaol (IconAnchor, sizeof(IconAnchor), NULL,
                             "<A HREF=\"!&@!&@!AZ!&@\">", &LocalFaoVector);
         if (VMSnok (status) || status == SS$_BUFFEROVF)
            ErrorNoticed (status, "WriteFaol()", FI_LI);
         if (Debug) fprintf (stdout, "IconAnchor |%s|\n", IconAnchor);
      }

      vecptr = &LocalFaoVector;
      if (tkptr->FormatLikeVms)
         if (!rqptr->PathOds || rqptr->PathOds == MAPURL_PATH_ODS_2)
            *vecptr++ = "!&;&^AZ";
         else
            *vecptr++ = "!&;AZ";
      else
      {
         *vecptr++ = "!&;&[AZ";
         *vecptr++ = rqptr->PathOds;
      }
      *vecptr++ = tkptr->SearchOds.NamNamePtr;

      status = WriteFaol (AnchorName, sizeof(AnchorName), NULL,
                          "!&@", &LocalFaoVector);
      if (VMSnok (status) || status == SS$_BUFFEROVF)
         ErrorNoticed (status, "WriteFaol()", FI_LI);
      if (Debug) fprintf (stdout, "AnchorName |%s|\n", AnchorName);

      /* if appropriate, restore the version delimitter */
      if (!tkptr->FormatLikeVms)
         if (tkptr->SearchOds.NamTypeLength <= 1)
            tkptr->SearchOds.NamTypePtr[0] = '.';
         else
            tkptr->SearchOds.NamVersionPtr[0] = ';';

      if (tkptr->ShowUpperCase)
         for (cptr = AnchorName; *cptr; cptr++) *cptr = toupper(*cptr);
   }

   if (!tkptr->DirFormatBlockTotals)
   {
      /************************/
      /* name post-processing */
      /************************/

      /* count the extra characters introduced by escaping/encoding */
      EncodedNameDelta = 0;
      cptr = AnchorName;
      while (*cptr &&
             cptr - AnchorName <= tkptr->FieldWidthName + EncodedNameDelta)
      {
         if (*cptr != '&')
         {
            cptr++;
            continue;
         }
         while (*cptr)
         {
            EncodedNameDelta++;
            cptr++;
            if (*cptr == ';') break;
         }
      }

      if (cptr - AnchorName > tkptr->FieldWidthName + EncodedNameDelta)
      {
         AnchorName[tkptr->FieldWidthName+EncodedNameDelta-1] = '\0';
         AnchorNameOverflowPtr = "+";
      }
      else
         AnchorNameOverflowPtr = "";
   }

   /***********************************/
   /* build the sys$faol() paremeters */
   /***********************************/

   cptr = tkptr->LayoutPtr;
   while (*cptr)
   {
      if (!isalpha(*cptr))
      {
         cptr++;
         continue;
      }
      /** if (Debug) fprintf (stdout, "idx: %d cptr |%s|\n", idx, cptr); **/

      switch (toupper(*cptr))
      {
         case 'C' :

            /*****************/
            /* creation date */
            /*****************/

            if (tkptr->DirFormatParentDirPath[0] || tkptr->DirFormatBlockTotals)
            {
               /* make it an empty field */
               if (idx < MaxIdx) FaoVector[idx++] = tkptr->FieldWidthCdt;
               if (idx < MaxIdx) FaoVector[idx++] = tkptr->FieldWidthCdt;
               if (idx < MaxIdx) FaoVector[idx++] = "";
               if (idx < MaxIdx) FaoVector[idx++] = "";
               break;
            }

            /* format the creation date/time */
            CdtDsc.dsc$a_pointer = Cdt;
            CdtDsc.dsc$w_length = sizeof(Cdt)-1;
            if (tkptr->FormatLikeVms)
               ctxptr = &LibDateTimeVmsContext;
            else
               ctxptr = &LibDateTimeContext;
            if (VMSnok (status =
                lib$format_date_time (&CdtDsc,
                                      &tkptr->SearchOds.FileQio.CdtBinTime,
                                      ctxptr, &DateLength, 0)))
            {
               rqptr->rqResponse.ErrorTextPtr = "lib$format_date_time()";
               ErrorVmsStatus (rqptr, status, FI_LI);
               DirEnd (rqptr);
               return;
            }
            else
               Cdt[DateLength] = '\0';
            /** if (Debug) fprintf (stdout, "Cdt |%s|\n", Cdt); **/
            if (idx < MaxIdx) FaoVector[idx++] = tkptr->FieldWidthCdt;
            /* next parameter right justifies the field */
            if (tkptr->FieldWidthCdt - DateLength > 0)
            {
               if (idx < MaxIdx)
                  FaoVector[idx++] = tkptr->FieldWidthCdt - DateLength;
            }
            else
               if (idx < MaxIdx) FaoVector[idx++] = 0;
            if (idx < MaxIdx) FaoVector[idx++] = "";
            if (idx < MaxIdx) FaoVector[idx++] = Cdt;
            break;

         case 'D' :

            /***************/
            /* description */
            /***************/

            if (tkptr->DirFormatParentDirPath[0])
            {
               /********************/
               /* parent directory */
               /********************/

               if (idx < MaxIdx) FaoVector[idx++] = "";
               if (tkptr->MakeDescriptionLink)
               {
                  if (idx < MaxIdx) FaoVector[idx++] = "";
                  if (idx < MaxIdx) FaoVector[idx++] = "";
                  if (idx < MaxIdx) FaoVector[idx++] = "";
               }
               if (idx < MaxIdx && tkptr->FieldWidthDescription)
               {
                  FaoVector[idx++] = tkptr->FieldWidthDescription;
                  FaoVector[idx++] = tkptr->FieldWidthDescription;
               }
               if (idx < MaxIdx) FaoVector[idx++] = 
                  MsgFor(rqptr,MSG_DIR_PARENT);
               if (idx < MaxIdx && tkptr->MakeDescriptionLink)
                  FaoVector[idx++] = "";
               if (idx < MaxIdx && tkptr->FieldWidthDescription)
                  FaoVector[idx++] = "";
               if (idx < MaxIdx) FaoVector[idx++] = "";
            }
            else
            if (tkptr->DirFormatBlockTotals)
            {
               /***************************/
               /* VMS format block totals */
               /***************************/

               if (tkptr->DirectoryCount < 0)
                  tkptr->DirectoryCount = 0;
               TotalFilesDsc.dsc$a_pointer = TotalFiles;
               TotalFilesDsc.dsc$w_length = sizeof(TotalFiles)-1;
               sys$fao (&TotalFilesFaoDsc, &Length, &TotalFilesDsc,
                        tkptr->DirectoryCount + tkptr->FileCount);
               TotalFiles[Length] = '\0';
               if (idx < MaxIdx) FaoVector[idx++] = "";
               if (tkptr->MakeDescriptionLink)
               {
                  if (idx < MaxIdx) FaoVector[idx++] = "";
                  if (idx < MaxIdx) FaoVector[idx++] = "";
                  if (idx < MaxIdx) FaoVector[idx++] = "";
               }
               if (idx < MaxIdx && tkptr->FieldWidthDescription)
               {
                  FaoVector[idx++] = tkptr->FieldWidthDescription;
                  FaoVector[idx++] = tkptr->FieldWidthDescription;
               }
               if (idx < MaxIdx) FaoVector[idx++] = TotalFiles;
               if (idx < MaxIdx && tkptr->MakeDescriptionLink)
                  FaoVector[idx++] = "";
               if (idx < MaxIdx && tkptr->FieldWidthDescription)
                  FaoVector[idx++] = "";
               if (idx < MaxIdx) FaoVector[idx++] = "";
            }
            else
            if (ThisIsADirectory)
            {
               /*************/
               /* directory */
               /*************/

               if (idx < MaxIdx) FaoVector[idx++] = "";
               if (tkptr->MakeDescriptionLink)
               {
                  if (idx < MaxIdx) FaoVector[idx++] = "";
                  if (idx < MaxIdx) FaoVector[idx++] = "";
                  if (idx < MaxIdx) FaoVector[idx++] = "";
               }
               if (idx < MaxIdx && tkptr->FieldWidthDescription)
               {
                  FaoVector[idx++] = tkptr->FieldWidthDescription;
                  FaoVector[idx++] = tkptr->FieldWidthDescription;
               }
               if (idx < MaxIdx)
               {
                  if (!tkptr->MsgSubDirPtr)
                     FaoVector[idx++] = tkptr->MsgSubDirPtr =
                        MsgFor(rqptr,MSG_DIR_SUB);
                  else
                     FaoVector[idx++] = tkptr->MsgSubDirPtr;
               }
               if (idx < MaxIdx && tkptr->MakeDescriptionLink)
                  FaoVector[idx++] = "";
               if (idx < MaxIdx && tkptr->FieldWidthDescription)
                  FaoVector[idx++] = "";
               if (idx < MaxIdx) FaoVector[idx++] = "";
            }
            else
            if (tkptr->Description[0] &&
                tkptr->Description[0] != DESCRIPTION_IMPOSSIBLE)
            {
               /******************************/
               /* file with HTML description */
               /******************************/

               if (idx < MaxIdx) FaoVector[idx++] = "\"";
               if (tkptr->MakeDescriptionLink)
               {
                  if (idx < MaxIdx) FaoVector[idx++] = "<A HREF=\"";
                  if (idx < MaxIdx) FaoVector[idx++] = AnchorLink;
                  if (idx < MaxIdx) FaoVector[idx++] = "\">";
               }
               if (idx < MaxIdx && tkptr->FieldWidthDescription)
               {
                  /* the 4 - 1 allows for the leading quote */
                  if (tkptr->MakeDescriptionLink)
                     FaoVector[idx++] = tkptr->FieldWidthDescription + 3;
                  else
                     FaoVector[idx++] = tkptr->FieldWidthDescription - 1;
                  if ((Count = strlen(tkptr->Description)) >
                      tkptr->FieldWidthDescription - 3)
                     FaoVector[idx++] = tkptr->FieldWidthDescription - 3;
                  else
                     FaoVector[idx++] = Count;
               }
               if (idx < MaxIdx) FaoVector[idx++] = tkptr->Description;
               if (idx < MaxIdx && tkptr->MakeDescriptionLink)
                  FaoVector[idx++] = "</A>";
               if (idx < MaxIdx && tkptr->FieldWidthDescription)
               {
                  if (Count > tkptr->FieldWidthDescription - 3)
                     FaoVector[idx++] = "+";
                  else
                     FaoVector[idx++] = "";
               }
               if (idx < MaxIdx) FaoVector[idx++] = "\"";
            }
            else
            {
               /*********************************/
               /* file with content description */
               /*********************************/

               if (idx < MaxIdx) FaoVector[idx++] = "";
               if (tkptr->MakeDescriptionLink)
               {
                  if (idx < MaxIdx) FaoVector[idx++] = "<A HREF=\"";
                  if (idx < MaxIdx) FaoVector[idx++] = AnchorLink;
                  if (idx < MaxIdx) FaoVector[idx++] = "\">";
               }
               if (idx < MaxIdx && tkptr->FieldWidthDescription)
               {
                  if (tkptr->MakeDescriptionLink)
                     FaoVector[idx++] = tkptr->FieldWidthDescription + 4;
                  else
                     FaoVector[idx++] = tkptr->FieldWidthDescription;
                  if ((Count =
                      strlen(tkptr->ContentInfo.DescriptionPtr)) >
                      tkptr->FieldWidthDescription - 1)
                     FaoVector[idx++] = tkptr->FieldWidthDescription - 1;
                  else
                     FaoVector[idx++] = Count;
               }
               if (idx < MaxIdx)
                  FaoVector[idx++] = tkptr->ContentInfo.DescriptionPtr;
               if (idx < MaxIdx && tkptr->MakeDescriptionLink)
                  FaoVector[idx++] = "</A>";
               if (idx < MaxIdx && tkptr->FieldWidthDescription)
               {
                  if (Count > tkptr->FieldWidthDescription - 3)
                     FaoVector[idx++] = "+";
                  else
                     FaoVector[idx++] = "";
               }
               if (idx < MaxIdx) FaoVector[idx++] = "";
            }
            break;

         case 'I' :

            /********/
            /* icon */
            /********/

            if (idx < MaxIdx) FaoVector[idx++] = "<A NAME=\"";

            if (idx < MaxIdx) 
               if (ThisIsADirectory)
                  FaoVector[idx++] = AnchorName;
               else
                  FaoVector[idx++] = AnchorLink;

            if (idx < MaxIdx) FaoVector[idx++] = "\">";

            if (idx < MaxIdx)
               if (IconAnchor[0])
                  FaoVector[idx++] = IconAnchor;
               else
                  FaoVector[idx++] = "";

            if (tkptr->DirFormatBlockTotals)
               IconPtr = ConfigBlankIconPtr;
            else
            if (ThisIsADirectory)
               IconPtr = ConfigDirIconPtr;
            else
            if (tkptr->DirFormatParentDirPath[0])
               IconPtr = ConfigParentIconPtr;
            else
               IconPtr = tkptr->ContentInfo.IconPtr;

            if (idx < MaxIdx) FaoVector[idx++] = IconPtr;

            if (idx < MaxIdx)
               if (IconAnchor[0])
                  FaoVector[idx++] = "</A></A>";
               else
                  FaoVector[idx++] = "</A>";

            break;

         case 'L' :

            /***************/
            /* link/anchor */
            /***************/

            if (tkptr->DirFormatBlockTotals)
            {
               /* make it an empty field */
               if (idx < MaxIdx) FaoVector[idx++] = "";
               if (idx < MaxIdx) FaoVector[idx++] = "";
               if (idx < MaxIdx) FaoVector[idx++] = "";
               if (idx < MaxIdx) FaoVector[idx++] = tkptr->FieldWidthName;
               if (idx < MaxIdx) FaoVector[idx++] = "";
               if (idx < MaxIdx) FaoVector[idx++] = "";
               if (idx < MaxIdx) FaoVector[idx++] = "";
               break;
            }

            if (idx < MaxIdx) FaoVector[idx++] = "<A HREF=\"";
            if (idx < MaxIdx) FaoVector[idx++] = AnchorLink;
            if (idx < MaxIdx) FaoVector[idx++] = "\">";
            if (idx < MaxIdx)
               FaoVector[idx++] = tkptr->FieldWidthName + EncodedNameDelta + 4;
            if (idx < MaxIdx) FaoVector[idx++] = AnchorName;
            if (idx < MaxIdx) FaoVector[idx++] = "</A>";
            if (idx < MaxIdx) FaoVector[idx++] = AnchorNameOverflowPtr;
            break;

         case 'N' :

            /********/
            /* name */
            /********/

            if (tkptr->DirFormatBlockTotals)
            {
               /* make it an empty field */
               if (idx < MaxIdx) FaoVector[idx++] = tkptr->FieldWidthName;
               if (idx < MaxIdx) FaoVector[idx++] = "";
               if (idx < MaxIdx) FaoVector[idx++] = "";
               break;
            }

            if (idx < MaxIdx) FaoVector[idx++] = tkptr->FieldWidthName +
                                                 EncodedNameDelta;
            if (idx < MaxIdx) FaoVector[idx++] = AnchorName;
            if (idx < MaxIdx) FaoVector[idx++] = AnchorNameOverflowPtr;
            break;

         case 'O' :

            /*********/
            /* owner */
            /*********/

            if (tkptr->DirFormatParentDirPath[0] || tkptr->DirFormatBlockTotals)
            {
               /* make it an empty field */
               if (idx < MaxIdx) FaoVector[idx++] = tkptr->FieldWidthOwner;
               if (idx < MaxIdx) FaoVector[idx++] = "";
               break;
            }

            OwnerDsc.dsc$a_pointer = Owner;
            OwnerDsc.dsc$w_length = sizeof(Owner)-1;
            sys$fao (&OwnerFaoDsc, &Length, &OwnerDsc,
                     tkptr->SearchOds.FileQio.AtrUic);
            Owner[Length] = '\0';
            if (idx < MaxIdx) FaoVector[idx++] = tkptr->FieldWidthOwner;
            if (idx < MaxIdx) FaoVector[idx++] = Owner;
            break;

         case 'P' :

            /**************/
            /* protection */
            /**************/

            if (tkptr->DirFormatParentDirPath[0] || tkptr->DirFormatBlockTotals)
            {
               /* make it an empty field */
               if (idx < MaxIdx) FaoVector[idx++] = tkptr->FieldWidthProtection;
               if (idx < MaxIdx) FaoVector[idx++] = "";
               break;
            }

            FormatProtection (tkptr->SearchOds.FileQio.AtrFpro, Protection);
            if (idx < MaxIdx) FaoVector[idx++] = tkptr->FieldWidthProtection;
            if (idx < MaxIdx) FaoVector[idx++] = Protection;
            break;

         case 'R' :

            /*****************/
            /* revision date */
            /*****************/

            if (tkptr->DirFormatParentDirPath[0] || tkptr->DirFormatBlockTotals)
            {
               /* make it an empty field */
               if (idx < MaxIdx) FaoVector[idx++] = tkptr->FieldWidthRdt;
               if (idx < MaxIdx) FaoVector[idx++] = tkptr->FieldWidthRdt;
               if (idx < MaxIdx) FaoVector[idx++] = "";
               if (idx < MaxIdx) FaoVector[idx++] = "";
               break;
            }

            /* format the revision date/time */
            RdtDsc.dsc$a_pointer = Rdt;
            RdtDsc.dsc$w_length = sizeof(Rdt)-1;
            if (tkptr->FormatLikeVms)
               ctxptr = &LibDateTimeVmsContext;
            else
               ctxptr = &LibDateTimeContext;
            if (VMSnok (status =
                lib$format_date_time (&RdtDsc,
                                      &tkptr->SearchOds.FileQio.RdtBinTime,
                                      ctxptr, &DateLength, 0)))
            {
               rqptr->rqResponse.ErrorTextPtr = "lib$format_date_time()";
               ErrorVmsStatus (rqptr, status, FI_LI);
               DirEnd (rqptr);
               return;
            }
            else
               Rdt[DateLength] = '\0';
            /** if (Debug) fprintf (stdout, "Rdt |%s|\n", Rdt); **/
            if (idx < MaxIdx) FaoVector[idx++] = tkptr->FieldWidthRdt;
            /* next parameter right justifies the field */
            if (tkptr->FieldWidthRdt - DateLength > 0)
            {
               if (idx < MaxIdx)
                  FaoVector[idx++] = tkptr->FieldWidthRdt - DateLength;
            }
            else
               if (idx < MaxIdx) FaoVector[idx++] = 0;
            if (idx < MaxIdx) FaoVector[idx++] = "";
            if (idx < MaxIdx) FaoVector[idx++] = Rdt;
            break;

         case 'S' :

            /********/
            /* size */
            /********/

            if (tkptr->DirFormatParentDirPath[0])
            {                                   
               /* make it an empty field */
               if (idx < MaxIdx) FaoVector[idx++] = tkptr->FieldWidthSize;
               if (idx < MaxIdx) FaoVector[idx++] = tkptr->FieldWidthSize;
               if (idx < MaxIdx) FaoVector[idx++] = "";
               if (idx < MaxIdx) FaoVector[idx++] = "";
               break;
            }

            SizeDsc.dsc$a_pointer = Size;
            SizeDsc.dsc$w_length = sizeof(Size)-1;

            if (tkptr->DirFormatBlockTotals)
            {
               sys$fao (&BlocksFaoDsc, &Length, &SizeDsc,
                        tkptr->TotalUsedBlocks,
                        tkptr->TotalAllocatedBlocks);
            }
            else
            {
               AllocatedVbn =
((tkptr->SearchOds.FileQio.RecAttr.fat$l_hiblk & 0xffff) << 16) |
((tkptr->SearchOds.FileQio.RecAttr.fat$l_hiblk & 0xffff0000) >> 16);
               EndOfFileVbn =
((tkptr->SearchOds.FileQio.RecAttr.fat$l_efblk & 0xffff) << 16) |
((tkptr->SearchOds.FileQio.RecAttr.fat$l_efblk & 0xffff0000) >> 16);

               if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD_DIR))
                  WatchThis (rqptr, FI_LI, WATCH_MOD_DIR,
                     "AllocatedVbn:!UL EndOfFileVbn:!UL FirstFreeByte:!UL",
                     AllocatedVbn, EndOfFileVbn,
                     tkptr->SearchOds.FileQio.RecAttr.fat$w_ffbyte);

               if (tkptr->FormatLikeVms)
               {
                  sys$fao (&BlocksFaoDsc, &Length, &SizeDsc,
                           EndOfFileVbn, AllocatedVbn);

                  tkptr->TotalAllocatedBlocks += AllocatedVbn;
                  tkptr->TotalUsedBlocks += EndOfFileVbn;
               }
               else
               {
                  if (EndOfFileVbn <= 1)
                     Bytes = tkptr->SearchOds.FileQio.RecAttr.fat$w_ffbyte;
                  else
                     Bytes = ((EndOfFileVbn-1) << 9) +
                             tkptr->SearchOds.FileQio.RecAttr.fat$w_ffbyte;
                  if (Debug) fprintf (stdout, "Bytes %d\n", Bytes);

                  if (cptr[1] == LAYOUT_PARAMETER)
                  {
                     switch (toupper(cptr[2]))
                     {
                        case 'D' :
                           if (Bytes >= 1000000)
                              sys$fao (&MBytesFaoDsc, &Length, &SizeDsc,
                                       Bytes / 1000000);
                           else
                           if (Bytes >= 1000)
                              sys$fao (&KBytesFaoDsc, &Length, &SizeDsc,
                                       Bytes / 1000);
                           else
                              sys$fao (&BytesFaoDsc, &Length, &SizeDsc, Bytes);
                           break;
      
                        case 'F' :
                           if (Bytes >= 1000000)
                              sys$fao (&MBytesFractFaoDsc, &Length, &SizeDsc,
                                       Bytes / 1000000,
                                       (Bytes % 1000000) / 100000);
                           else
                           if (Bytes >= 1000)
                              sys$fao (&KBytesFractFaoDsc, &Length, &SizeDsc,
                                       Bytes / 1000, (Bytes % 1000) / 100);
                           else
                              sys$fao (&BytesFaoDsc, &Length, &SizeDsc, Bytes);
                           break;
      
                        case 'B' :
                           status = WriteFao (Size, sizeof(Size), &Length,
                                              "!&,UL", Bytes);
                           if (VMSnok (status))
                              ErrorNoticed (status, "WriteFao()", FI_LI);
                           break;

                        case 'K' :
                           sys$fao (&KBytesFullFractFaoDsc, &Length, &SizeDsc,
                                    Bytes / 1000, Bytes % 1000);
                           break;

                        case 'M' :
                           sys$fao (&MBytesFullFractFaoDsc, &Length, &SizeDsc,
                                    Bytes / 1000000, Bytes % 1000000);
                           break;
                     }
                  }
                  else
                  {
                     if (Bytes >= 1048576)
                        sys$fao (&MBytesFaoDsc, &Length, &SizeDsc,
                                 Bytes >> 20);
                     else
                     if (Bytes >= 1024)
                        sys$fao (&KBytesFaoDsc, &Length, &SizeDsc,
                                 Bytes >> 10);
                     else
                        sys$fao (&BytesFaoDsc, &Length, &SizeDsc, Bytes);
                  }
               }
            }

            Size[Length] = '\0';
            /** if (Debug) fprintf (stdout, "Size |%s|\n", Size); **/

            if (idx < MaxIdx) FaoVector[idx++] = tkptr->FieldWidthSize;
            /* next parameter right justifies the field */
            if (tkptr->FieldWidthSize - Length > 0)
            {
               if (idx < MaxIdx)
                  FaoVector[idx++] = tkptr->FieldWidthSize - Length;
            }
            else
               if (idx < MaxIdx) FaoVector[idx++] = 0;
            if (idx < MaxIdx) FaoVector[idx++] = "";
            if (idx < MaxIdx) FaoVector[idx++] = Size;
            break;
      }

      if (cptr[1] == LAYOUT_PARAMETER) cptr += 2;
      cptr++;

      if (idx >= MaxIdx)
      {
         /* should be enough for all but a deliberate formatting hack! */
         rqptr->rqResponse.HttpStatus = 501;
         ErrorGeneral (rqptr, MsgFor(rqptr,MSG_DIR_LAYOUT), FI_LI);
         DirEnd (rqptr);
         return;
      }
   }

   /** if (Debug) fprintf (stdout, "idx: %d\n", idx); **/

   /*********************/
   /* format and output */
   /*********************/

   status = NetWriteFaol (rqptr, tkptr->LayoutFaoPtr, &FaoVector);
   if (VMSnok (status))
   {
      ErrorNoticed (status, "NetWriteFaol()", FI_LI);
      rqptr->rqResponse.ErrorTextPtr = tkptr->DirPath;
      rqptr->rqResponse.ErrorOtherTextPtr = tkptr->LayoutFaoPtr;
      ErrorVmsStatus (rqptr, status, FI_LI);
      DirEnd (rqptr);
      return;
   }

   /* empty any file-internal description previously generated */
   tkptr->Description[0] = '\0';

   NetWritePartFlush (rqptr, tkptr->DirFormatAstFunction);
}

/*****************************************************************************/
/*
These comments apply equally to DirReadMeBottom() below.

Attempt to send one of possible multiple files ("README.", et. al.) in the 
specified directory.  Then, provided there was not serious error with the 
send, execute the function specified by the address in 'NextTaskFunction'.  
The file can be HTML or plain-text.  With plain-text the contents will be 
encapsulated.
*/ 
 
DirReadMeTop (REQUEST_STRUCT *rqptr)

{
   int  status;
   char  *cptr, *rptr, *sptr,
         *TypePtr;
   char  FileName [ODS_MAX_FILE_NAME_LENGTH+1];
   DIR_TASK  *tkptr;

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

   if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD_DIR))
       WatchThis (rqptr, FI_LI, WATCH_MOD_DIR,
                  "DirReadMeTop() !&F", &DirReadMeTop);

   /* get the pointer to the task structure */
   tkptr = rqptr->DirTaskPtr;

   while (*(rptr = ConfigReadMeFile(tkptr->ReadMeFileIndex++)))
   {
      sptr = FileName;
      for (cptr = tkptr->DirectoryPart; *cptr; *sptr++ = *cptr++);
      TypePtr = NULL;
      for ( /* 'rptr' initialized above! */ ; *rptr; *sptr++ = *rptr++)
         if (*rptr == '.') TypePtr = rptr;
      *sptr = '\0';
      if (!TypePtr) TypePtr = rptr;

      cptr = ConfigContentType (&tkptr->ContentInfo, TypePtr);

      /* ignore any non-text types! */
      if (!ConfigSameContentType (cptr, "text/", 5)) continue;

      if (ConfigSameContentType (cptr, ConfigContentTypeSsi, -1))
      {
         FileSetContentHandler (rqptr, &SsiBegin, SsiSizeMax);
         FileSetCacheAllowed (rqptr, true);
         FileBegin (rqptr, &DirHeading, &DirReadMeTop, NULL, FileName, cptr);
      }
      else
      {
         if (ConfigSameContentType (cptr, "text/plain", -1))
            FileSetPreTag (rqptr, true);
         FileSetCacheAllowed (rqptr, true);
         FileBegin (rqptr, &DirHeading, &DirReadMeTop, NULL, FileName, cptr);
      }
      return;
   }

   DirHeading (rqptr);
}

/*****************************************************************************/
/*
See comment in DirReadMeTop() above.
*/ 
 
DirReadMeBottom (REQUEST_STRUCT *rqptr)

{
   int  status;
   char  *cptr, *rptr, *sptr,
         *TypePtr;
   char  FileName [ODS_MAX_FILE_NAME_LENGTH+1];
   DIR_TASK  *tkptr;

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

   if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD_DIR))
       WatchThis (rqptr, FI_LI, WATCH_MOD_DIR,
                  "DirReadMeBottom() !&F", DirReadMeBottom);

   /* get the pointer to the task structure */
   tkptr = rqptr->DirTaskPtr;

   while (*(rptr = ConfigReadMeFile(tkptr->ReadMeFileIndex++)))
   {
      sptr = FileName;
      for (cptr = tkptr->DirectoryPart; *cptr; *sptr++ = *cptr++);
      TypePtr = NULL;
      for ( /* 'rptr' initialized above! */ ; *rptr; *sptr++ = *rptr++)
         if (*rptr == '.') TypePtr = rptr;
      *sptr = '\0';
      if (!TypePtr) TypePtr = rptr;

      cptr = ConfigContentType (&tkptr->ContentInfo, TypePtr);

      /* ignore any non-text types! */
      if (!ConfigSameContentType (cptr, "text/", 5)) continue;

      if (ConfigSameContentType (cptr, ConfigContentTypeSsi, -1))
      {
         FileSetContentHandler (rqptr, &SsiBegin, SsiSizeMax);
         FileSetCacheAllowed (rqptr, true);
         FileBegin (rqptr, &DirHeading, &DirReadMeBottom, NULL, FileName, cptr);
      }
      else
      {
         if (ConfigSameContentType (cptr, "text/plain", -1))
            FileSetPreTag (rqptr, true);
         FileSetCacheAllowed (rqptr, true);
         FileBegin (rqptr, &DirHeading, &DirReadMeBottom, NULL, FileName, cptr);
      }
      return;
   }

   DirEnd (rqptr);
}

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