/*****************************************************************************/
/*
                                  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:0   file sizes in 1000 (decimal) not 1024 (binary) kilos
  S:2   file sizes in 1024 (binary) not 1000 (decimal) kilos
  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
  S:V   file size to be in blocks (VMS-ish)
        (these can have multiple specifications, e.g. "__S:2:F__")
  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. 


DIRECTORY DIRECTIVES
--------------------
These directives may be added to a directory listing URL, or placed into the
".WWW_WASD" file, 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

  # .WWW_WASD
  upper=yes
  notype=yes

Precedence is; .WWW_WASD file directives, then if override=yes SSI query
string, then if override=yes URI query string; if no .WWW_WASD file then SSI
query string and if override=yes URI query string; finally if no SSI query
string then URI query string.

The following are the directives:

  autoscript=   yes (default), no, true, false, 1, 0
  delimit=      header, footer, both (default), none
  expired=      yes, no, true, false, 1, 0 (listing pre-expired)
  font=         inherit, or monospace (default)
  ilink=        yes, no, true, false, 1, 0 (icon is a plain-text link)
  layout=       see "listing layout" immediately above
  local=        yes, no (default), true, false, 1, 0 (do not propagate)
  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)
  override=     yes, no (default), true, false, 1, 0 (query string may override)
  query=        set this string as the query string for subsequent requests
  readme=       yes, no (default), true, false, 1, 0
  script=       script path that will be used when a file link is clicked
  sort=         on column (e.g. 'R','S') then '+' for ascending '-' descending
  style=        any of the mapping SET dir=style=
  target=       open a file in this window (e.g. "_blank")
  these=        one or more comma-separated wildcard file name.type(s)
  type=         force file content-type (e.g. "&type=\"text\"/plain")
  upper=        yes, no (default), true, false, 1, 0
  versions=     up to <integer> many of a file or if '*' then all


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
---------------
04-NOV-2024  MGD  bugfix; DirFormatLayout() return SS$_RESULTOVF
01-SEP-2023  MGD  bugfix; DirFormatAcpInfoAst() 64 bit file size
04-JAN-2021  MGD  bugfix; DirDirectString() title=this=...
24-JAN-2019  MGD  DirFormatAcpInfoAst() double size of AnchorLink
04-JAN-2019  MGD  bugfix; AuthCompleted()
25-JAN-2018  MGD  SET DIR=TITLE=[default|owner|remote|<integer>|this=<string>]
                  ?httpd=index&title=[(as above)]
04-MAY-2017  MGD  after twenty-plus years do not list "hidden" directories
22-JUN-2016  MGD  DirFiles() ignore ambiguous file names
                    containing an escaped ("^.") period but no type
02-MAY-2015  MGD  tblst refinement
08-NOV-2014  MGD  bugfix; and refine DirFormatSize()
16-SEP-2014  MGD  default directory listing style now <table>ed
                  ?httpd=index&font=[inherit|monospace(D)]
                  ?httpd=index&style=table[2]
                  suppress WebDAV metadata subdirectory
                  when path SET ods=name=utf8 then response charset=utf-8
                  SET dir=font=[inherit|monospace(D)]
                  SET dir=style=TABLE[2]
                  SET ods=name=8bit, ods=name=utf8, ods=name=default
                  SET webdav=meta=dir=<string>
                  SET webdav=[no]hidden *always* conceals hidden file names
                  remove <a name=".."></a> from link (nested <a></a>s)
                  bugfix; DirAuthorizationAst() tabular with no delimiter
                  bugfix; DirFormatAcpInfoAst() ThisIsADirectory = false;
27-SEP-2013  MGD  ?httpd=index&versions=<integer>|*
                  SET dir=versions=<integer>|*
18-SEP-2013  MGD  refine sortable whitespace in older MSIE and font in all MSIE 
10-JUL-2013  MGD  .WWW_WASD directive file
                  sortable directory listing
                  ?httpd=index&local=[yes|no]
                  ?httpd=index&override=[yes|no]
                  ?httpd=index&query=<string>
                  ?httpd=index&style=<which>
                  ?httpd=index&sort=<char>[+|-]
                  ?httpd=index&target=<string>
                  ?httpd=index&these=<wildcard1>[,<wildcard2>]
                  SET dir=delimit=<which>
                  SET dir=style=<which>
                  SET dir=sort=<char>[+|-]
                  SET dir=target=<string>
                  SET dir=these=<wildcard1>[,<wildcard2>]
                  bugfix; DirFormatSize() bytes
01-JUN-2013  MGD  bugfix; non-ODS_EXTENDED platforms (e.g. VAX) must
                    OdsParse() NAM$M_NOCONCEAL before OdsSearchNoConceal()
30-JAN-2012  MGD  DirFormatSize() now uses quadword
                  DirFormatSize() adjusts units to fit size width
27-SEP-2011  MGD  bugfix; OdsSearchNoConceal() to successfully process
                    concealed, multi-value logical device names
28-APR-2010  MGD  bugfix; DirAuthorizationAst() only check access on
                    non-empty expanded file names
24-MAR-2010  MGD  bugfix; DirBegin() "httpd=index&" detection (since v9.3.0)
                  bugfix; DirEnd() suppress </html> unless RequestEnd() AST
23-AUG-2009  MGD  WasdCss[] and some refinements
11-JUN-2009  MGD  "*.*__WASDAV;" files ignored
10-DEC-2007  MGD  DirBegin() only use query string if it begins "httpd=index&"
09-APR-2007  MGD  bugfix; DirFormatAcpInfoAst() 'S' (size) processing for
                    block totals at the end of a listing
24-MAY-2006  MGD  DirFormat() and DirFormatSize() allow in-line layouts to
                    specify size with VMS format listings, as well as
                    adding size specification of 'V' (VMS-ish, in blocks)
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 FaolToBuffer(), 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_V7
#undef _VMS__V6__SOURCE
#define _VMS__V6__SOURCE
#undef __VMS_VER
#define __VMS_VER 70000000
#undef __CRTL_VER
#define __CRTL_VER 70000000
#endif

/* standard C header files */
#include <ctype.h>
#include <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 LAYOUT_PARAMETER ':'

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

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

char  *DirBlankIconPtr,
      *DirDirIconPtr,
      *DirParentIconPtr;

static char  DirColSpan [96];
static $DESCRIPTOR (DirColSpanDsc, DirColSpan);

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

extern BOOL  CliDemo,
             NaturalLanguageEnglish,
             OdsExtended,
             WebDavEnabled;

extern int  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 (WATCHMOD (rqptr, WATCH_MOD_DIR))
      WatchThis (WATCHITM(rqptr), 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->NetIoPtr->WriteStatus = SS$_NORMAL;

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

   if (WATCHING (rqptr, WATCH_RESPONSE))
      WatchThis (WATCHITM(rqptr), 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) */
   rqptr->DirTaskPtr = tkptr = VmGetHeap (rqptr, sizeof(DIR_TASK));
   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;

   rqptr->rqResponse.PreExpired = Config.cfDir.PreExpired;
   tkptr->AsIfNopFound = tkptr->AsIfNosFound = false;
   tkptr->AutoScriptEnabled = tkptr->IconLinkEnabled =
      tkptr->IncludeAnyReadme = true;

   if (tkptr->RealPath[0])
      tkptr->DirDelimit = MAPURL_DIR_DELIMIT_NONE;
   else
   if (rqptr->rqPathSet.DirDelimit)
      tkptr->DirDelimit = rqptr->rqPathSet.DirDelimit;
   else
      tkptr->DirDelimit = MAPURL_DIR_DELIMIT_BOTH;

   if (rqptr->rqPathSet.DirSort[0])
   {
      tkptr->DirSort[0] = rqptr->rqPathSet.DirSort[0];
      tkptr->DirSort[1] = rqptr->rqPathSet.DirSort[1];
   }

   if (rqptr->rqPathSet.DirFont)
      tkptr->DirFont = rqptr->rqPathSet.DirFont;
   else
      tkptr->DirFont = MAPURL_DIR_FONT_MONOSPACE;

   if (rqptr->rqPathSet.DirStyle)
      tkptr->DirStyle = rqptr->rqPathSet.DirStyle;
   else
      tkptr->DirStyle = MAPURL_DIR_STYLE_TABLE;

   if (rqptr->rqPathSet.DirTitle)
   {
      tkptr->DirTitle = rqptr->rqPathSet.DirTitle;
      tkptr->DirTitlePtr = rqptr->rqPathSet.DirTitlePtr;
   }
   else
      tkptr->DirTitle = MAPURL_DIR_TITLE_DEFAULT;

   if (rqptr->rqPathSet.DirTargetPtr)
   {
      zptr = (sptr = tkptr->LinkTarget) + sizeof(tkptr->LinkTarget)-2;
      for (cptr = " target=\""; *cptr; *sptr++ = *cptr++);
      for (cptr = rqptr->rqPathSet.DirTargetPtr; *cptr && sptr < zptr; cptr++)
         if (*cptr != '\"') *sptr++ = *cptr;
      *sptr++ = '\"';
      *sptr = '\0';
   }

   if (rqptr->rqPathSet.DirThesePtr)
   {
      for (cptr = sptr = rqptr->rqPathSet.DirThesePtr; *sptr; sptr++);
      tkptr->ThesePtr = sptr = VmGetHeap (rqptr, sptr-cptr);
      /* null-separated series of strings terminated by empty string */
      while (*cptr)
      {
         if (*cptr == ',')
            *sptr++ = '\0';
         else
            *sptr++ = *cptr;
         cptr++;
      }
   }

   if (rqptr->rqPathSet.DirVersionsOf)
      /* negative 1 indicates to revert to zero */
      if (rqptr->rqPathSet.DirVersionsOf == -1)
         tkptr->VersionsOf = 0;
      else
         tkptr->VersionsOf = rqptr->rqPathSet.DirVersionsOf;

   /************************/
   /* directory directives */
   /************************/

   /* .WWW_WASD file */
   if (VMSnok (DirWwwWasd (rqptr)))
      tkptr->QueryOverride = true;

   /* SSI query string */
   if (tkptr->QueryOverride)
      if (cptr = DirQueryString)
         if (cptr && to_lower(*cptr) == 'h' &&
             strsame (cptr, "httpd=index&", 12))
            DirDirectString (rqptr, cptr+12);

   /* URI query string */
   if (tkptr->QueryOverride)
      if (cptr = tkptr->QueryStringPtr)
         if (cptr && to_lower(*cptr) == 'h' &&
             strsame (cptr, "httpd=index&", 12))
            DirDirectString (rqptr, cptr+12);

   /* map any default into the current default */
   if (tkptr->DirStyle == MAPURL_DIR_STYLE_DEFAULT)
      tkptr->DirStyle = MAPURL_DIR_STYLE_TABLE;
   else
   if (tkptr->DirStyle == MAPURL_DIR_STYLE_DEFAULT2)
      tkptr->DirStyle = MAPURL_DIR_STYLE_TABLE2;

   if (tkptr->DirStyle == MAPURL_DIR_STYLE_TABLE ||
       tkptr->DirStyle == MAPURL_DIR_STYLE_TABLE2 ||
       tkptr->DirStyle == MAPURL_DIR_STYLE_SORT ||
       tkptr->DirStyle == MAPURL_DIR_STYLE_SORT2)
      tkptr->DirStyleTabular = true;
   else
      tkptr->DirStyleTabular = false;

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

      cptr = MapVmsPath (tkptr->DirSpec, rqptr);
      Authorize (rqptr, cptr, -1, NULL, 0, &DirAuthorizationAst);
      if (VMSnok (rqptr->rqAuth.FinalStatus))
      {
         /* if asynchronous authentication is notunderway */
         if (rqptr->rqAuth.FinalStatus != AUTH_PENDING) 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)

{
   static char  StyleBasicList [] =
"<style type=\"text/css\">\n\
hr { width:95%; margin:0 1em 0 0; height: 2px; \
color:#000000; background-color:#000000; border:1px; }\n\
</style>\n";

   /* four strings in this array, [0]&[3] fixed, [1]&[2] selected between */
   static char  *JavaScriptList [] = {
"<script src=\"/httpd/-/wasd.js\"></script>\n\
<style type=\"text/css\">\n\
.sortable { min-width:40%; border-collapse:collapse; }\n",
".sortable th, .sortable td { font-family:inherit; }\n",
".sortable th, .sortable td { font-family:monospace; }\n",
".sortable td { padding:0 1.5em 0 0; margin:0; white-space:pre-wrap; \
vertical-align:top; }\n\
.sortable.sort2 thead td { padding-bottom:0.5em; white-space:pre-wrap; \
vertical-align:top; }\n\
.rghtd { text-align:right; }\n\
.rnwtd { text-align:right; white-space:nowrap!important; }\n\
.wasdI { text-decoration:none; letter-spacing:0; }\n\
hr { width:105%; margin:1em 1em 1em 0; height: 2px; \
color:#000000; background-color:#000000; border:1px; }\n\
</style>\n" };

   /* four strings in this array, [0]&[3] fixed, [1]&[2] selected between */
   static char  *StyleTableList [] = {
"<style type=\"text/css\">\n\
.tblst { min-width:40%; max-width:95%; border-collapse:collapse; }\n",
".tblst th, .tblst td { font-family:inherit; }\n",
".tblst th, .tblst td { font-family:monospace; }\n",
".tblst td { padding:0 1.5em 0 0; margin:0; white-space:pre-wrap; \
vertical-align:top; }\n\
.tblst.list2 thead td { padding-bottom:0.5em; white-space:pre-wrap; \
vertical-align:top; }\n\
.rghtd { text-align:right; }\n\
.rnwtd { text-align:right; white-space:nowrap!important; }\n\
hr { width:105%; margin:1em 0 1em 0; height: 2px; \
color:#000000; background-color:#000000; border:1px; }\n\
</style>\n" };

   static $DESCRIPTOR (OwnerFaoDsc, "!%I\0");

   int  status;
   unsigned short  slen;
   unsigned long  FaoVector [32];
   unsigned long  *vecptr;
   char  Owner [64],
         Scratch [ODS_MAX_FILE_NAME_LENGTH+1];
   char  *cptr, *sptr,
         *IndexOfPtr;
   $DESCRIPTOR (OwnerDsc, Owner);
   DIR_TASK  *tkptr;

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

   if (WATCHMOD (rqptr, WATCH_MOD_DIR))
      WatchThis (WATCHITM(rqptr), WATCH_MOD_DIR,
                 "DirAuthorizationAst() !&F !&S", &DirAuthorizationAst,
                 rqptr->rqAuth.FinalStatus);

   /* may have been delivered asynchronously */
   rqptr->rqAuth.AstFunction = NULL;

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

   tkptr = rqptr->DirTaskPtr;

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

   OdsStructInit (&tkptr->SearchOds, false);

   if (tkptr->SearchOds.ExpFileNameLength)
      AuthAccessEnable (rqptr, tkptr->SearchOds.ExpFileName, AUTH_ACCESS_READ);

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

   AuthAccessEnable (rqptr, 0, 0);

   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,
           slen = tkptr->SearchOds.NamNamePtr - tkptr->SearchOds.NamDevicePtr);
   tkptr->DirectoryPart[slen] = '\0';

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

   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 */
      AuthAccessEnable (rqptr, tkptr->SearchOds.ExpFileName, AUTH_ACCESS_READ);

      OdsParse (&tkptr->SearchOds,
                tkptr->DirSpec, tkptr->DirSpecLength, "*.*", 3, 0,
                NULL, rqptr);

      AuthAccessEnable (rqptr, 0, 0);

      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,
              slen = 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,
              slen = tkptr->SearchOds.NamVersionPtr -
                       tkptr->SearchOds.NamNamePtr);
   }
   if (tkptr->VersionsOf)
   {
      /* ?httpd=index&versions=<integer> */
      if (!(tkptr->SearchOds.Nam_fnb & NAM$M_WILD_VER ||
            tkptr->SearchOds.Nam_fnb & NAM$M_EXP_VER))
         tkptr->FilePart[slen++] = ';';
      if (!(tkptr->SearchOds.Nam_fnb & NAM$M_WILD_VER))
         tkptr->FilePart[slen++] = '*';
   }
   tkptr->FilePart[slen] = '\0';

   /********************/
   /* 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;
     else
     if (rqptr->rqPathSet.OdsName == MAPURL_ODS_UTF8)
        rqptr->rqPathSet.CharsetPtr = "utf-8";

      /* "index of"s can have pre-expiry controlled from the query string */
      ResponseHeader200 (rqptr, "text/html", NULL);

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

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

   if (tkptr->DirDelimit == MAPURL_DIR_DELIMIT_BOTH ||
       tkptr->DirDelimit == MAPURL_DIR_DELIMIT_HEADER)
   {
      /******************/
      /* header delimit */
      /******************/

      vecptr = &FaoVector;

      *vecptr++ = WASD_DOCTYPE;
      if (tkptr->DirStyle == MAPURL_DIR_STYLE_TABLE ||
          tkptr->DirStyle == MAPURL_DIR_STYLE_TABLE2)
      {
         *vecptr++ = StyleTableList[0];
         if (tkptr->DirFont == MAPURL_DIR_FONT_INHERIT)
            *vecptr++ = StyleTableList[1];
         else
            *vecptr++ = StyleTableList[2];
         *vecptr++ = StyleTableList[3];
      }
      else
      if (tkptr->DirStyle == MAPURL_DIR_STYLE_SORT ||
          tkptr->DirStyle == MAPURL_DIR_STYLE_SORT2)
      {
         *vecptr++ = JavaScriptList[0];
         if (tkptr->DirFont == MAPURL_DIR_FONT_INHERIT)
            *vecptr++ = JavaScriptList[1];
         else
            *vecptr++ = JavaScriptList[2];
         *vecptr++ = JavaScriptList[3];
      }
      else
      {
         *vecptr++ = StyleBasicList;
         *vecptr++ = "";
         *vecptr++ = "";
      }

      if (rqptr->rqPathSet.StyleSheetPtr)
      {
         *vecptr++ =
"<link rel=\"stylesheet\" type=\"text/css\" href=\"!AZ\">\n";
         *vecptr++ = rqptr->rqPathSet.StyleSheetPtr;
      }
      else
         *vecptr++ = "";

      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>...</title> */
      if (tkptr->DirTitle &&
          tkptr->DirTitle != MAPURL_DIR_TITLE_DEFAULT)
      {
         if (tkptr->DirTitle == MAPURL_DIR_TITLE_NONE)
            *vecptr++ = "&nbsp;";
         else
         if (tkptr->DirTitle == MAPURL_DIR_TITLE_OWNER)
         {
            OdsFileAcpInfo (&tkptr->SearchOds, NULL, 0);
            sys$fao (&OwnerFaoDsc, &slen, &OwnerDsc,
                     tkptr->SearchOds.FileQio.AtrUic);
            *vecptr++ = "!&;AZ";
            *vecptr++ = Owner;
         }
         else
         if (tkptr->DirTitle == MAPURL_DIR_TITLE_REMOTE)
         {
            *vecptr++ = "!&;AZ";
            if (rqptr->rqAuth.SourceRealm == AUTH_SOURCE_X509)
               if (cptr = SesolaClientCertRemoteUser (rqptr))
                  *vecptr++ = cptr;
               else
                  *vecptr++ = "";
            else
               *vecptr++ = rqptr->RemoteUser;
         }
         else
         if (tkptr->DirTitle == MAPURL_DIR_TITLE_THIS)
         {
            *vecptr++ = "!AZ";
            *vecptr++ = tkptr->DirTitlePtr;
         }
         else
         if (tkptr->DirTitle >= 1)
         {
            *vecptr++ = "!&;AZ";
            *vecptr++ = DirTitleInteger (rqptr);
         }
         else
            *vecptr++ = "SS$_BUGCHECK";
      }
      else
      if (tkptr->FormatLikeVms)
      {
         MapOdsUrlToVms (tkptr->DirPath, Scratch, sizeof(Scratch),
                          0, rqptr->rqPathSet.MapEllipsis, rqptr->PathOds);
         *vecptr++ = "!AZ !&;AZ";
         *vecptr++ = IndexOfPtr;
         *vecptr++ = Scratch;
      }
      else
      if (tkptr->DirStyle == MAPURL_DIR_STYLE_HTDIR ||
          tkptr->DirStyle == MAPURL_DIR_STYLE_HTDIR2)
      {
         *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<div class=\"wasd\">\n";
         else
            *vecptr++ = "<body!&+AZ>\n<div class=\"wasd\">\n";
         *vecptr++ = rqptr->rqPathSet.HtmlBodyTagPtr;
      }
      else
      {
         *vecptr++ = "!AZ\n<div class=\"wasd\">\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 = FaolToNet (rqptr,
"!AZ\
<html>\n\
<head>\n\
!AZ!AZ!AZ\
!&@\
!&@\
<title>!&@</title>\n\
</head>\n\
!&@\
!&@",
         &FaoVector);

      if (VMSok (status))
         status = DirIndexOf (rqptr, IndexOfPtr,
                              tkptr->FormatLikeVms ? Scratch : NULL);
   }
   else
   {
      /*********************/
      /* no header delimit */
      /*********************/

      vecptr = &FaoVector;

      if (tkptr->DirStyle == MAPURL_DIR_STYLE_TABLE ||
          tkptr->DirStyle == MAPURL_DIR_STYLE_TABLE2)
      {
         *vecptr++ = StyleTableList[0];
         if (tkptr->DirFont == MAPURL_DIR_FONT_INHERIT)
            *vecptr++ = StyleTableList[1];
         else
            *vecptr++ = StyleTableList[2];
         *vecptr++ = StyleTableList[3];
      }
      else
      if (tkptr->DirStyle == MAPURL_DIR_STYLE_SORT ||
          tkptr->DirStyle == MAPURL_DIR_STYLE_SORT2)
      {
         *vecptr++ = JavaScriptList[0];
         if (tkptr->DirFont == MAPURL_DIR_FONT_INHERIT)
            *vecptr++ = JavaScriptList[1];
         else
            *vecptr++ = JavaScriptList[2];
         *vecptr++ = JavaScriptList[3];
      }
      else
      {
         *vecptr++ = StyleBasicList;
         *vecptr++ = "";
         *vecptr++ = "";
      }

      if (rqptr->rqPathSet.StyleSheetPtr)
      {
         *vecptr++ =
"<link rel=\"stylesheet\" type=\"text/css\" href=\"!AZ\">\n";
         *vecptr++ = rqptr->rqPathSet.StyleSheetPtr;
      }
      else
         *vecptr++ = "";

      status = FaolToNet (rqptr, "!AZ!AZ!AZ!&@", &FaoVector);
   }

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

   if ((tkptr->DirStyle == MAPURL_DIR_STYLE_SORT ||
        tkptr->DirStyle == MAPURL_DIR_STYLE_SORT2) && VMSok(status))
   {
      char  NoScript [] = "<noscript style=\"font-size:120%;color:red;\">\
JavaScript?</noscript>\n";
      NetWriteBuffered (rqptr, NULL, NoScript, sizeof(NoScript)-1);
   }

   if (Config.cfDir.ReadMeTop && rqptr->DirTaskPtr->IncludeAnyReadme)
      SysDclAst (DirReadMeTop, rqptr);
   else
      SysDclAst (DirHeading, 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 (WATCHMOD (rqptr, WATCH_MOD_DIR))
      WatchThis (WATCHITM(rqptr), WATCH_MOD_DIR,
                 "DirIndexOf() !&Z !&Z", IndexOfPtr, UrlAsVms);

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

   if (tkptr->DirStyle == MAPURL_DIR_STYLE_ORIGINAL ||
       tkptr->DirStyle == MAPURL_DIR_STYLE_ORIGINAL2)
   {   
      /**************************/
      /* traditional WASD style */
      /**************************/

      vecptr = &FaoVector;
      *vecptr++ = IndexOfPtr;
      *vecptr++ = rqptr->PathOds;
      *vecptr++ = tkptr->DirPath;
      status = FaolToNet (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 = FaolToBuffer (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 = FaolToNet (rqptr, "<h3><nobr>!AZ &nbsp;", &FaoVector);
      if (VMSnok (status)) return (status);
      cptr = UrlAsVms;
   }
   else
   { 
      if (tkptr->DirStyle == MAPURL_DIR_STYLE_HTDIR ||
          tkptr->DirStyle == MAPURL_DIR_STYLE_HTDIR2)
      {
         /* ABI's style begins with the server name as a 'root' anchor */
         *vecptr++ = FinalSlashPtr;
         if (tkptr->QueryStringPtr)
         {
            *vecptr++ = "?httpd=index&!AZ";
            *vecptr++ = tkptr->QueryStringPtr;
         }
         else
            *vecptr++ = "";
         if (rqptr->rqHeader.HostPtr &&
             rqptr->rqHeader.HostPtr[0])
            *vecptr++ = rqptr->rqHeader.HostPtr;
         else
            *vecptr++ = rqptr->ServicePtr->ServerHostPort;
         status = FaolToNet (rqptr,
"<h3><nobr><a href=\"/!AZ!&@\">//!AZ/</a>",
                             &FaoVector);
      }
      else
      {
         /* WASD style provides an "Index of" heading */
         *vecptr++ = IndexOfPtr;
         *vecptr++ = FinalSlashPtr;
         if (tkptr->QueryStringPtr)
         {
            *vecptr++ = "?httpd=index&!AZ";
            *vecptr++ = tkptr->QueryStringPtr;
         }
         else
            *vecptr++ = "";
         if (rqptr->rqHeader.HostPtr &&
             rqptr->rqHeader.HostPtr[0])
            *vecptr++ = rqptr->rqHeader.HostPtr;
         else
            *vecptr++ = rqptr->ServicePtr->ServerHostPort;
         status = FaolToNet (rqptr,
"<h3><nobr>!AZ &nbsp;//<a href=\"/!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;
      if (tkptr->QueryStringPtr)
      {
         *vecptr++ = "?httpd=index&!AZ";
         *vecptr++ = tkptr->QueryStringPtr;
      }
      else
         *vecptr++ = "";
      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 (tkptr->DirStyle == MAPURL_DIR_STYLE_HTDIR ||
             tkptr->DirStyle == MAPURL_DIR_STYLE_HTDIR2)
         {
            /* 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 = FaolToNet (rqptr,
"<a href=\"!#AZ!AZ!&;&@\">!#AZ</a>!#AZ",
                          &FaoVector);
      cptr = sptr;
   }

   if (!UrlAsVms &&
       (tkptr->DirStyle == MAPURL_DIR_STYLE_HTDIR ||
        tkptr->DirStyle == MAPURL_DIR_STYLE_HTDIR2))
   {
      /* ABI's style, 'file' name and type element as an anchor */
      if (*cptr == '/') cptr++;
      vecptr = &FaoVector;
      *vecptr++ = cptr;
      if (tkptr->QueryStringPtr)
      {
         *vecptr++ = "?httpd=index&!AZ";
         *vecptr++ = tkptr->QueryStringPtr;
      }
      else
         *vecptr++ = "";
      *vecptr++ = cptr;
      status = FaolToNet (rqptr,
"<a href=\"!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 = FaolToNet (rqptr, "!AZ</nobr></h3>\n", &FaoVector);
   }

   return (status);
}

/*****************************************************************************/
/*
(0 is no title)
1 the top-level directory (device?)
2 the second-level directory
... and so on until the bottom-level directory
99 bottom-level directory.
*/ 

char* DirTitleInteger (REQUEST_STRUCT *rqptr)

{
   static char  buffer [256];

   int  number;
   char  *cptr, *sptr, *zptr;
   char  Scratch [ODS_MAX_FILE_NAME_LENGTH+1];
   DIR_TASK  *tkptr;

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

   if (WATCHMOD (rqptr, WATCH_MOD_DIR))
      WatchThis (WATCHITM(rqptr), WATCH_MOD_DIR, "DirTitleInteger()");

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

   buffer[0] = '\0';
   number = tkptr->DirTitle;

   if (tkptr->FormatLikeVms)
   {
      MapOdsUrlToVms (tkptr->DirPath, Scratch, sizeof(Scratch),
                      0, rqptr->rqPathSet.MapEllipsis, rqptr->PathOds);
      if (number == 1)
         cptr = Scratch;
      else
      if (number >= 2)
      {
         for (cptr = Scratch; *cptr && *cptr != ':'; cptr++);
         if (SAME2(cptr,':[')) cptr += 2; else cptr = "?";
         for (number -= 2; number > 0; number--)
         {
            for (sptr = cptr; *sptr != '.' && *sptr != ']'; sptr++)
               if (*sptr == '^' && *(sptr+1)) sptr++;
            if (*sptr == '.')
               cptr = sptr + 1;
            else
            if (*sptr == ']')
               break;
         }
      }
      zptr = (sptr = buffer) + sizeof(buffer)-1;
      while (*cptr && *cptr != ':' && *cptr != '.' && *cptr != ']' &&
             sptr < zptr)
      {
         if (*cptr == '^')
         {
            *sptr++ = *cptr++;
            if (sptr < zptr) *sptr++ = *cptr++;
         }
         else
            *sptr++ = *cptr++;
      }
      *sptr = '\0';
      if (sptr >= zptr)
      {
         strzcpy (buffer, "?", sizeof(buffer));
         ErrorNoticed (rqptr, SS$_RESULTOVF, NULL, FI_LI);
      }
   }
   else
   {
      cptr = tkptr->DirPath;
      if (*cptr == '/' && *(cptr+1)) cptr++;
      while (--number)
      {
         for (sptr = cptr; *sptr && *sptr != '/'; sptr++);
         if (*sptr == '/' && *(sptr+1))
         {
            for (zptr = sptr+1; *zptr && *zptr != '/'; zptr++);
            if (*zptr == '/') cptr = sptr + 1;
         }
         else
            break;
      }
      zptr = (sptr = buffer) + sizeof(buffer)-1;
      while (*cptr && *cptr != '/' && sptr < zptr) *sptr++ = *cptr++;
      *sptr = '\0';
      if (sptr >= zptr)
      {
         strzcpy (buffer, "?", sizeof(buffer));
         ErrorNoticed (rqptr, SS$_RESULTOVF, NULL, FI_LI);
      }
   }

   return (buffer);
}

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

DirEnd (REQUEST_STRUCT *rqptr)

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

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

   if (WATCHMOD (rqptr, WATCH_MOD_DIR))
      WatchThis (WATCHITM(rqptr), WATCH_MOD_DIR, "DirEnd() !&F", &DirEnd);

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

   NextTaskFunction = tkptr->NextTaskFunction;

   /* ensure parse internal data structures are released */
   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++ = "";

      if (NextTaskFunction == RequestEnd)
      {
         status = FaolToNet (rqptr, "!&@</div>\n</body>\n</html>\n",
                             &FaoVector);
         if (VMSnok (status))
         {
            ErrorNoticed (rqptr, status, NULL, FI_LI);
            rqptr->rqResponse.ErrorTextPtr = "FaolToNet()";
            ErrorVmsStatus (rqptr, status, FI_LI);
         }
      }
   }

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

   VmFreeFromHeap (rqptr, tkptr, FI_LI);

   /* explicitly flushed in case it's an SSI activity */
   NetWriteFullFlush (rqptr, NextTaskFunction);
}

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

DirHeading (REQUEST_STRUCT *rqptr)

{
   int  status;
   DIR_TASK  *tkptr;

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

   if (WATCHMOD (rqptr, WATCH_MOD_DIR))
      WatchThis (WATCHITM(rqptr), 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 = FaolToNet (rqptr, "</td></tr></table>\n", NULL);
      if (VMSnok (status))
      {
         ErrorNoticed (rqptr, status, NULL, FI_LI);
         rqptr->rqResponse.ErrorTextPtr = tkptr->DirPath;
         rqptr->rqResponse.ErrorOtherTextPtr = tkptr->LayoutFaoPtr;
         ErrorVmsStatus (rqptr, status, FI_LI);
         DirEnd (rqptr);
         return;
      }
   }

   if (tkptr->DirDelimit == MAPURL_DIR_DELIMIT_BOTH ||
       tkptr->DirDelimit == MAPURL_DIR_DELIMIT_HEADER ||
       tkptr->DirStyleTabular)
   {
      /* header and horizontal line at top of page */
      NetWriteBuffered (rqptr, &DirBeginDirectories,
                        tkptr->LayoutHeadingPtr, tkptr->LayoutHeadingLength);
      return;
   }
   else
   if (!tkptr->DirStyleTabular)
   {
      /* just start the preformtted text */
      NetWriteBuffered (rqptr, &DirBeginDirectories, "\n", 1);
   }
   else
      SysDclAst (&DirBeginDirectories, rqptr);
}

/*****************************************************************************/
/*
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 (WATCHMOD (rqptr, WATCH_MOD_DIR))
      WatchThis (WATCHITM(rqptr), 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 (tkptr->DirStyle == MAPURL_DIR_STYLE_ORIGINAL ||
       tkptr->DirStyle == MAPURL_DIR_STYLE_ORIGINAL2)
      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 (SAME2(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 (SAME2(cptr,'/\0')) tkptr->ListParentDir = false;
      }
   }

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

   if (tkptr->ListSubDir)
   {
      AuthAccessEnable (rqptr, tkptr->SearchOds.ExpFileName, AUTH_ACCESS_READ);

      /* OdsParse() NAM$M_NOCONCEAL before OdsSearchNoConceal() */
      OdsParse (&tkptr->SearchOds,
                tkptr->DirectoryPart, 0, "*.DIR;", 6, NAM$M_NOCONCEAL,
                &DirBeginDirectoriesParseAst, rqptr);

      AuthAccessEnable (rqptr, 0, 0);

      return;
   }

   DirBeginDirectoriesParseAst (rqptr);
} 

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

DirBeginDirectoriesParseAst (REQUEST_STRUCT *rqptr)

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

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

   if (WATCHMOD (rqptr, WATCH_MOD_DIR))
      WatchThis (WATCHITM(rqptr), WATCH_MOD_DIR,
"DirBeginDirectoriesParseAst() !&F sts:!&S stv:!&S", &DirBeginDirectoriesParseAst,
                 rqptr->DirTaskPtr->SearchOds.Fab.fab$l_sts,
                 rqptr->DirTaskPtr->SearchOds.Fab.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 &&
       SAME2(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 (MATCH8 (cptr, "[000000]"))
      {
         /* 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 (MATCH8 (cptr, "[000000."))
         {
            /* 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 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, NULL);
         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 (WATCHMOD (rqptr, WATCH_MOD_DIR))
      WatchThis (WATCHITM(rqptr), WATCH_MOD_DIR,
                 "DirSearchDirectories() !&F !&S", &DirSearchDirectories,
                 rqptr->NetIoPtr->WriteStatus);

   if (rqptr->RequestState >= REQUEST_STATE_ABORT)
   {
      DirEnd (rqptr);
      return;
   }

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

   tkptr = rqptr->DirTaskPtr;

   AuthAccessEnable (rqptr, tkptr->SearchOds.ExpFileName, AUTH_ACCESS_READ);

   OdsSearchNoConceal (&tkptr->SearchOds, &DirDirectories, rqptr);

   AuthAccessEnable (rqptr, 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 (REQUEST_STRUCT  *rqptr)

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

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

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

   if (WATCHMOD (rqptr, WATCH_MOD_DIR))
      WatchThis (WATCHITM(rqptr), WATCH_MOD_DIR,
                 "DirDirectories() !&F sts:!&S stv:!&S", &DirDirectories,
                 rqptr->DirTaskPtr->SearchOds.Fab.fab$l_sts,
                 rqptr->DirTaskPtr->SearchOds.Fab.fab$l_stv);

   if (rqptr->RequestState >= REQUEST_STATE_ABORT)
   {
      DirEnd (rqptr);
      return;
   }

   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;
   }

   /* check if the .DIR is really a directory */
   status = OdsReallyADir (rqptr, &tkptr->SearchOds);
   if (VMSnok (status))
   {
      DirSearchDirectories (rqptr);
      return;
   }

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

   if (WATCHMOD (rqptr, WATCH_MOD_DIR))
      WatchThis (WATCHITM(rqptr), WATCH_MOD_DIR,
                 "!&Z", tkptr->SearchOds.NamDevicePtr);

   /***********************************/
   /* WebDAV metadata (sub)directory? */
   /***********************************/

   if (WebDavEnabled)
   {
      if (rqptr->rqPathSet.WebDavRead ||
          rqptr->rqPathSet.WebDavWrite ||
          rqptr->rqPathSet.WebDavProfile ||
          rqptr->rqPathSet.WebDavServer)
      {
         if (DavMetaDir (rqptr, &tkptr->SearchOds))
         {
            DirSearchDirectories (rqptr);
            return;
         }
      }
   }

   /*********************/
   /* hidden directory? */
   /*********************/

   /* Unix-style "hidden" directories do not appear (those beginning ".") */
   if (SAME1(tkptr->SearchOds.NamNamePtr,'.') ||
       SAME2(tkptr->SearchOds.NamNamePtr,'^.'))
   {
      /* except in demo mode or to VMS authenticated and profiled requests */
      if (rqptr->rqPathSet.WebDavNoHidden ||
          !(CliDemo ||
            rqptr->rqAuth.SkelKeyAuthenticated ||
            rqptr->rqAuth.VmsUserProfileLength))
      {
         if (WATCHMOD (rqptr, WATCH_MOD_DIR))
            WatchThis (WATCHITM(rqptr), WATCH_MOD_DIR, ".DIRECTORY (hidden)");
         DirSearchDirectories (rqptr);
         return;
      }
   }

   /************************/
   /* 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 (WATCHMOD (rqptr, WATCH_MOD_DIR))
      WatchThis (WATCHITM(rqptr), WATCH_MOD_DIR,
                 "DirBeginFiles() !&F", &DirBeginFiles);

   tkptr = rqptr->DirTaskPtr;

   AuthAccessEnable (rqptr, tkptr->SearchOds.ExpFileName, AUTH_ACCESS_READ);

   /* OdsParse() NAM$M_NOCONCEAL before OdsSearchNoConceal() */
   OdsParse (&tkptr->SearchOds,
             tkptr->FilePart, 0, tkptr->DirectoryPart, 0, NAM$M_NOCONCEAL,
             &DirBeginFilesParseAst, rqptr);

   AuthAccessEnable (rqptr, 0, 0);
}

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

DirBeginFilesParseAst (REQUEST_STRUCT *rqptr)

{
   int  status;
   DIR_TASK  *tkptr;

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

   if (WATCHMOD (rqptr, WATCH_MOD_DIR))
      WatchThis (WATCHITM(rqptr), WATCH_MOD_DIR,
"DirBeginFilesParseAst() !&F sts:!&S stv:!&S", &DirBeginFilesParseAst,
                 rqptr->DirTaskPtr->SearchOds.Fab.fab$l_sts,
                 rqptr->DirTaskPtr->SearchOds.Fab.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;
   }

   if (WebDavEnabled)
   {
      /* WebDAV metadata (sub)directory? */
      if (rqptr->rqPathSet.WebDavRead ||
          rqptr->rqPathSet.WebDavWrite ||
          rqptr->rqPathSet.WebDavProfile ||
          rqptr->rqPathSet.WebDavServer)
      {
         if (DavMetaDir (rqptr, &tkptr->SearchOds))
         {
            DirEnd (rqptr);
            return;
         }
      }
   }

   /* fudge this status for the first call */
   rqptr->NetIoPtr->WriteStatus = 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 (WATCHMOD (rqptr, WATCH_MOD_DIR))
      WatchThis (WATCHITM(rqptr), WATCH_MOD_DIR,
                 "DirSearchFiles() !&F !&S", &DirSearchFiles,
                 rqptr->NetIoPtr->WriteStatus);

   if (rqptr->RequestState >= REQUEST_STATE_ABORT)
   {
      DirEnd (rqptr);
      return;
   }

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

   tkptr = rqptr->DirTaskPtr;

   AuthAccessEnable (rqptr, tkptr->SearchOds.ExpFileName, AUTH_ACCESS_READ);

   OdsSearchNoConceal (&tkptr->SearchOds, &DirFiles, rqptr);

   AuthAccessEnable (rqptr, 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 (REQUEST_STRUCT *rqptr)

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

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

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

   if (WATCHMOD (rqptr, WATCH_MOD_DIR))
      WatchThis (WATCHITM(rqptr), WATCH_MOD_DIR,
                 "DirFiles() !&F sts:!&S stv:!&S", &DirFiles,
                 rqptr->DirTaskPtr->SearchOds.Fab.fab$l_sts,
                 rqptr->DirTaskPtr->SearchOds.Fab.fab$l_stv);

   if (rqptr->RequestState >= REQUEST_STATE_ABORT)
   {
      DirEnd (rqptr);
      return;
   }

   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 (WATCHMOD (rqptr, WATCH_MOD_DIR))
      WatchThis (WATCHITM(rqptr), WATCH_MOD_DIR,
                 "!&Z", tkptr->SearchOds.NamDevicePtr);

   /* we've already output the directories, ignore them */
   if (MATCH5 (tkptr->SearchOds.NamTypePtr, ".DIR;") ||
       MATCH5 (tkptr->SearchOds.NamTypePtr, ".dir;"))
   {
      /* check if the .DIR is really a directory */
      status = OdsReallyADir (rqptr, &tkptr->SearchOds);
      if (VMSok (status))
      {
         DirSearchFiles (rqptr);
         return;
      }
   }

   /* in true Unix-style "hidden" files do not appear (those beginning ".") */
   if (SAME1(tkptr->SearchOds.NamNamePtr,'.') ||
       SAME2(tkptr->SearchOds.NamNamePtr,'^.'))
   {
      /* except in demo mode or to VMS authenticated and profiled requests */
      if (rqptr->rqPathSet.WebDavNoHidden ||
          !(CliDemo ||
            rqptr->rqAuth.SkelKeyAuthenticated ||
            rqptr->rqAuth.VmsUserProfileLength))
      {
         if (WATCHMOD (rqptr, WATCH_MOD_DIR))
            WatchThis (WATCHITM(rqptr), WATCH_MOD_DIR, ".FILE (hidden)");
         DirSearchFiles (rqptr);
         return;
      }
   }

   if (DavMetaFile (&tkptr->SearchOds))
   {
      /* found one, next file please */
      DirSearchFiles (rqptr);
      return;
   }

   if (tkptr->ThesePtr)
   {
      if (!DirTheseFiles (rqptr))
      {
         /* nope, next file please */
         DirSearchFiles (rqptr);
         return;
      }
   }

   if (tkptr->VersionsOf)
   {
      if (tkptr->VersionLength)
         if (tkptr->VersionLength != tkptr->SearchOds.NamNameLength +
                                     tkptr->SearchOds.NamTypeLength)
            tkptr->VersionLength = 0;

      if (tkptr->VersionLength)
         if (strncmp (tkptr->VersionName,
                      tkptr->SearchOds.NamNamePtr,
                      tkptr->VersionLength))
            tkptr->VersionLength = 0;

      if (!tkptr->VersionLength)
      {
         tkptr->VersionLength = tkptr->SearchOds.NamNameLength +
                                tkptr->SearchOds.NamTypeLength;
         memcpy (tkptr->VersionName,
                 tkptr->SearchOds.NamNamePtr,
                 tkptr->VersionLength);
         tkptr->VersionName[tkptr->VersionLength] = '\0';
         tkptr->VersionCount = 0;
      }
           
      if (tkptr->VersionCount++ >= tkptr->VersionsOf)
      {
         /* enough is enough! */
         DirSearchFiles (rqptr);
         return;
      }
   }

   if (tkptr->SearchOds.NamTypeLength <= 1)
   {
      /* no explicit type - look to see if name contains escaped period */
      for (cptr = tkptr->SearchOds.NamNamePtr;
           cptr < tkptr->SearchOds.NamTypePtr;
           cptr++)
         if (*(USHORTPTR)cptr == '^.') break;
      if (cptr < tkptr->SearchOds.NamTypePtr)
      {
         /* escaped period - do not list these ambiguous file names */
         if (WATCHING (rqptr, WATCH_RESPONSE))
            WatchThis (WATCHITM(rqptr), WATCH_RESPONSE,
                       "^.NAME.; !AZ", tkptr->SearchOds.NamNamePtr);
         DirSearchFiles (rqptr);
         return;
      }
   }

   if (rqptr->PathOds == MAPURL_PATH_ODS_ADS ||
       rqptr->PathOds == MAPURL_PATH_ODS_SMB)
      cptr = MapOdsAdsFileType (tkptr->SearchOds.NamTypePtr);
   else
   if (rqptr->PathOds == MAPURL_PATH_ODS_SRI)
      cptr = MapOdsSriFileType (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 (WATCHMOD (rqptr, WATCH_MOD_DIR))
      WatchThis (WATCHITM(rqptr), 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)

{
   static $DESCRIPTOR (ColSpanFaoDsc,
"!AZ\
<tr><td colspan=\"!UL\"><hr size=\"1\" noshade></td></tr>\n\
</tfoot>\n\
</table>\n\0");

   static $DESCRIPTOR (DirSortFaoDsc,
"!AZ<script nonce=\"!AZ\">wasdDirSort(\'!AZ\',true)</script>\n\0");

   static char  DirSort [256];
   static $DESCRIPTOR (DirSortDsc, DirSort);

   int  cnt, status;
   unsigned short  slen = 0;
   char  *cptr, *sptr;
   DIR_TASK  *tkptr;

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

   if (WATCHMOD (rqptr, WATCH_MOD_DIR))
      WatchThis (WATCHITM(rqptr), WATCH_MOD_DIR,
                 "DirEndOutput() !&F", &DirEndOutput);

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

   if (tkptr->DirDelimit == MAPURL_DIR_DELIMIT_BOTH ||
       tkptr->DirDelimit == MAPURL_DIR_DELIMIT_FOOTER)
   {
      if (tkptr->DirStyle == MAPURL_DIR_STYLE_TABLE ||
          tkptr->DirStyle == MAPURL_DIR_STYLE_SORT)
      {
         sys$fao (&ColSpanFaoDsc, &slen, &DirColSpanDsc,
                  tkptr->DirFormatBlockTotals ? "" : "</tbody>\n<tfoot>\n",
                  tkptr->ColSpanCount);
         cptr = DirColSpan;
         slen--;

         if (tkptr->DirSort[0])
         {
            sys$fao (&DirSortFaoDsc, &slen, &DirSortDsc,
                     cptr, GenerateCspNonce(rqptr), tkptr->DirSort);
            cptr = DirSort;
            slen--;
         }
      }
      else
      if (tkptr->DirStyle == MAPURL_DIR_STYLE_TABLE2 ||
          tkptr->DirStyle == MAPURL_DIR_STYLE_SORT2)
      {
         if (tkptr->DirFormatBlockTotals)
            cptr = "</tfoot>\n</table>\n";
         else
            cptr = "</tbody>\n</table>\n";
         slen = 18;

         if (tkptr->DirSort[0])
         {
            sys$fao (&DirSortFaoDsc, &slen, &DirSortDsc,
                     cptr, tkptr->DirSort);
            cptr = DirSort;
            slen--;
         }
      }
      else
      if (tkptr->DirStyle == MAPURL_DIR_STYLE_ANCHOR2 ||
          tkptr->DirStyle == MAPURL_DIR_STYLE_HTDIR2 ||
          tkptr->DirStyle == MAPURL_DIR_STYLE_ORIGINAL2)
      {
         cptr = "\n</pre>\n";
         slen = 8;
      }
      else
      {
         cptr = "\n<hr size=\"1\" noshade></pre>\n";
         slen = 29;
      }
   }
   else
   if (tkptr->DirStyleTabular)
   {
      if (tkptr->DirFormatBlockTotals)
         cptr = "</tfoot>\n</table>\n";
      else
         cptr = "</tbody>\n</table>\n";
      slen = 16;

      if (tkptr->DirSort[0])
      {
         sys$fao (&DirSortFaoDsc, &slen, &DirSortDsc, cptr, tkptr->DirSort);
         cptr = DirSort;
         slen--;
      }
   }
   else
   {
      /* footer not required, just tie-off the preformatted text */
      cptr = "</pre>\n";
      slen = 7;
   }

   if (Config.cfDir.ReadMeBottom && tkptr->IncludeAnyReadme)
      NetWriteBuffered (rqptr, DirReadMeBottom, cptr, slen);
   else
      NetWriteBuffered (rqptr, DirEnd, cptr, slen);
}

/*****************************************************************************/
/*
A series of null-terminated strings (wildcarded file names and/or types) are
each matched to the name and type of the currently searched-for file.  Return
true if the file is matched, false if not.  Preceding a string with a '!'
negates the match (i.e. not these files).  First match is considered a hit.
*/ 

BOOL DirTheseFiles (REQUEST_STRUCT *rqptr)

{
   BOOL  hit = false,
         negate = false;
   char  *cptr;
   DIR_TASK  *tkptr;

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

   if (WATCHMOD (rqptr, WATCH_MOD_DIR))
      WatchThis (WATCHITM(rqptr), WATCH_MOD_DIR, "DirTheseFiles() !AZ",
                 rqptr->DirTaskPtr->ThesePtr);

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

   *tkptr->SearchOds.NamVersionPtr = '\0';
   cptr = tkptr->ThesePtr;
   while (*cptr)
   {
      if (negate = (*cptr == '!')) cptr++;
      if (hit = StringMatch (NULL, tkptr->SearchOds.NamNamePtr, cptr)) break;
      while (*cptr) cptr++;
      if (*(cptr+1)) cptr++;
   }
   *tkptr->SearchOds.NamVersionPtr = ';';
   if (negate) return (!hit);
   return (hit);
}

/*****************************************************************************/
/*
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 512

   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;

   static $DESCRIPTOR (ColSpanFaoDsc,
"</tr>\n\
<tr><td colspan=\"!UL\"><hr size=\"1\" noshade></td></tr>\n\
</thead>\n\
<tbody>\n\0");

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

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

   if (WATCHMOD (rqptr, WATCH_MOD_DIR))
      WatchThis (WATCHITM(rqptr), WATCH_MOD_DIR, "DirFormatLayout()");

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

   IconInPlace = false;
   tkptr->MakeDescriptionLink = false;

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

      sptr = MsgFor(rqptr,MSG_DIR_COLUMN_HEADINGS);
      cptr = ColumnHeadingsPtr = VmGet (size = strlen(sptr)+1);
      strzcpy (cptr, sptr, size);

      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 = DirDefaultLayout;

      if (tkptr->DirStyle <= 0)
      {
         /* default or no style */
         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 (WATCHMOD (rqptr, 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 (WATCHMOD (rqptr, 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
      {
         /* use scratch space to generate the non-default heading and fao */
         LayoutHeadingPtr = LayoutFaoPtr = String;
         tkptr->SizeKilo = 1024;
      }
   }
   else
   {
      /**********************/
      /* non-default layout */
      /**********************/

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

   if (tkptr->DirStyle == MAPURL_DIR_STYLE_TABLE ||
       tkptr->DirStyle == MAPURL_DIR_STYLE_SORT)
      for (cptr = tkptr->LayoutPtr; *cptr; cptr++)
         if (isupper(*cptr)) tkptr->ColSpanCount++;

   /********************************/
   /* 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 (to_upper(*cptr) == 'P' || to_upper(*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 (to_upper(*cptr) == 'P' || to_upper(*cptr) == 'O')
            {
               cptr++;
               while (*cptr == '_') cptr++;
               continue;
            }
            if (sptr < zptr) *sptr++ = *cptr++;
         }
         if (sptr >= zptr)
         {
            ErrorGeneralOverflow (rqptr, FI_LI);
            return (SS$_RESULTOVF);
         }
         *sptr = '\0';
         tkptr->LayoutPtr = LayoutBufferPtr;
      }
   }

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

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

   switch (tkptr->DirStyle)
   {
      case MAPURL_DIR_STYLE_ANCHOR :
         cptr = "<pre class=\"anchor\">"; break;
      case MAPURL_DIR_STYLE_ANCHOR2 :
         cptr = "<pre class=\"anchor2\">"; break;
      case MAPURL_DIR_STYLE_HTDIR :
         cptr = "<pre class=\"htdir\">"; break;
      case MAPURL_DIR_STYLE_HTDIR2 :
         cptr = "<pre class=\"htdir2\">"; break;
      case MAPURL_DIR_STYLE_ORIGINAL :
         cptr = "<pre class=\"original\">"; break;
      case MAPURL_DIR_STYLE_ORIGINAL2 :
         cptr = "<pre class=\"original2\">"; break;
      case MAPURL_DIR_STYLE_TABLE :
         cptr = "<p><table class=\"tblst\">\n<thead>\n<tr>"; break;
      case MAPURL_DIR_STYLE_TABLE2 :
         cptr = "<p><table class=\"tblst list2\">\n<thead>\n<tr>"; break;
      case MAPURL_DIR_STYLE_SORT :
         cptr = "<p><table class=\"sortable\">\n<thead>\n<tr>"; break;
      case MAPURL_DIR_STYLE_SORT2 :
         cptr = "<p><table class=\"sortable sort2\">\n<thead>\n<tr>"; break;
      default :
         cptr = "<pre>";
   }
   while (*cptr && sptr < zptr) *sptr++ = *cptr++;

   while (*lptr && *lptr != '&' && sptr < zptr)
   {
      if (isdigit(*lptr))
      {
         wptr = lptr;
         while (*lptr && isdigit(*lptr)) lptr++;
         continue;
      }
      else
         wptr = NULL;

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

      switch (to_upper(*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 (to_upper(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 (tkptr->DirStyleTabular)
            {
               cnt = size = HeadingNameLength;
               tkptr->FieldWidthName = 0;
            }
            else
            {
               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 (to_upper(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' :
            /* this layout parameter can be used like "S:2:F" */
            idx = 0;
            while (lptr[idx+1] == LAYOUT_PARAMETER)
            {
               switch (to_upper(lptr[idx+2]))
               {
                  case '0' :
                     tkptr->SizeKilo = 1000;
                     break;
                  case '2' :
                     tkptr->SizeKilo = 1024;
                     break;
                  case 'D' :
                     tkptr->SizeKilo = 1000;
                     /* drop thru into 'f' */
                  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;
                  case 'V' :
                     if (!cnt) cnt = DEFAULT_FIELD_WIDTH_SIZE;
                     if (cnt < (size = DEFAULT_FIELD_WIDTH_SIZE - 2))
                        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);
               }
               idx += 2;
            }

            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 (!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 (to_upper(*lptr))
      {
         case ' ' :
         case '_' :
            if (tkptr->DirStyleTabular) break;
            while (cnt-- && sptr < zptr) *sptr++ = ' ';
            break;

         case 'C' :
         case 'R' :
         case 'S' :

            if (tkptr->DirStyleTabular)
            {
               if (tkptr->DirStyle == MAPURL_DIR_STYLE_TABLE ||
                   tkptr->DirStyle == MAPURL_DIR_STYLE_TABLE2)
                  aptr = "<td class=\"rghtd\"><b>";
               else
               {
                  if (to_upper(*lptr) == 'C')
                     aptr = "<td id=\"wasdC\" class=\"rghtd\"><b>";
                  else
                  if (to_upper(*lptr) == 'R')
                     aptr = "<td id=\"wasdR\" class=\"rghtd\"><b>";
                  else
                     aptr = "<td id=\"wasdS\" class=\"rghtd\"><b>";
               }
               while (*aptr && sptr < zptr) *sptr++ = *aptr++;
               if (tkptr->DirStyle == MAPURL_DIR_STYLE_TABLE2 ||
                   tkptr->DirStyle == MAPURL_DIR_STYLE_SORT2)
                  for (aptr = "<u>"; *aptr && sptr < zptr; *sptr++ = *aptr++);
            }
            else
            {
               /* flush the column heading right using spaces */
               if (cnt) cnt -= size;
               while (cnt-- && sptr < zptr) *sptr++ = ' ';
               for (aptr = "<b>"; *aptr && sptr < zptr; *sptr++ = *aptr++);
               if (tkptr->DirStyle == MAPURL_DIR_STYLE_ANCHOR2 ||
                   tkptr->DirStyle == MAPURL_DIR_STYLE_HTDIR2 ||
                   tkptr->DirStyle == MAPURL_DIR_STYLE_ORIGINAL2)
                  for (aptr = "<u>"; *aptr && sptr < zptr; *sptr++ = *aptr++);
            }

            while (*cptr && sptr < zptr) *sptr++ = *cptr++;

            if (tkptr->DirStyle == MAPURL_DIR_STYLE_TABLE2 ||
                tkptr->DirStyle == MAPURL_DIR_STYLE_ANCHOR2 ||
                tkptr->DirStyle == MAPURL_DIR_STYLE_SORT2 ||
                tkptr->DirStyle == MAPURL_DIR_STYLE_HTDIR2 ||
                tkptr->DirStyle == MAPURL_DIR_STYLE_ORIGINAL2)
               for (aptr = "</u>"; *aptr && sptr < zptr; *sptr++ = *aptr++);
            for (aptr = "</b>"; *aptr && sptr < zptr; *sptr++ = *aptr++);

            if (tkptr->DirStyleTabular)
               for (aptr = "</td>"; *aptr && sptr < zptr; *sptr++ = *aptr++);

            break;

         case 'D' :
         case 'L' :
         case 'N' :
         case 'O' :
         case 'P' :

            if (tkptr->DirStyleTabular)
            {
               if (tkptr->DirStyle == MAPURL_DIR_STYLE_TABLE ||
                   tkptr->DirStyle == MAPURL_DIR_STYLE_TABLE2)
                  aptr = "<td><b>";
               else
               {
                  switch (to_upper(*lptr))
                  {
                     case 'D' : aptr = "<td id=\"wasdD\"><b>"; break;
                     case 'L' : aptr = "<td id=\"wasdL\"><b>"; break;
                     case 'N' : aptr = "<td id=\"wasdN\"><b>"; break;
                     case 'O' : aptr = "<td id=\"wasdO\"><b>"; break;
                     case 'P' : aptr = "<td id=\"wasdP\"><b>";
                  }
               }
               while (*aptr && sptr < zptr) *sptr++ = *aptr++;
               if (tkptr->DirStyle == MAPURL_DIR_STYLE_TABLE2 ||
                   tkptr->DirStyle == MAPURL_DIR_STYLE_SORT2)
                  for (aptr = "<u>"; *aptr && sptr < zptr; *sptr++ = *aptr++);
            }
            else
            {
               for (aptr = "<b>"; *aptr && sptr < zptr; *sptr++ = *aptr++);
               if (tkptr->DirStyle == MAPURL_DIR_STYLE_ANCHOR2 ||
                   tkptr->DirStyle == MAPURL_DIR_STYLE_HTDIR2 ||
                   tkptr->DirStyle == MAPURL_DIR_STYLE_ORIGINAL2)
                  for (aptr = "<u>"; *aptr && sptr < zptr; *sptr++ = *aptr++);
            }

            while (*cptr && sptr < zptr) *sptr++ = *cptr++;

            if (tkptr->DirStyle == MAPURL_DIR_STYLE_TABLE2 ||
                tkptr->DirStyle == MAPURL_DIR_STYLE_ANCHOR2 ||
                tkptr->DirStyle == MAPURL_DIR_STYLE_SORT2 ||
                tkptr->DirStyle == MAPURL_DIR_STYLE_HTDIR2 ||
                tkptr->DirStyle == MAPURL_DIR_STYLE_ORIGINAL2)
               for (aptr = "</u>"; *aptr && sptr < zptr; *sptr++ = *aptr++);
            for (aptr = "</b>"; *aptr && sptr < zptr; *sptr++ = *aptr++);

            if (tkptr->DirStyleTabular)
               for (aptr = "</td>"; *aptr && sptr < zptr; *sptr++ = *aptr++);
            else
            {
               /* flush the column heading left using spaces */
               if (cnt) cnt -= size;
               while (cnt-- && sptr < zptr) *sptr++ = ' ';
            }

            break;

         case 'I' :

            if (tkptr->DirStyleTabular)
            {
               if (tkptr->DirStyle == MAPURL_DIR_STYLE_SORT ||
                   tkptr->DirStyle == MAPURL_DIR_STYLE_SORT2)
                  cptr = "<td id=\"wasdI\">&#9660;&#9650;</td>";
               else
                  cptr = "<td></td>";
            }
            else
               cptr = ConfigBlankIconPtr;
            while (*cptr && sptr < zptr) *sptr++ = *cptr++;
            IconInPlace = true;
            break;
      }

      while (lptr[1] == LAYOUT_PARAMETER) lptr += 2;
      lptr++;
   }

   if (tkptr->DirStyle == MAPURL_DIR_STYLE_ANCHOR2 ||
       tkptr->DirStyle == MAPURL_DIR_STYLE_HTDIR2 ||
       tkptr->DirStyle == MAPURL_DIR_STYLE_ORIGINAL2)
      cptr = "\n";
   else
   if (tkptr->DirStyle == MAPURL_DIR_STYLE_TABLE ||
       tkptr->DirStyle == MAPURL_DIR_STYLE_SORT)
   {
      sys$fao (&ColSpanFaoDsc, 0, &DirColSpanDsc, tkptr->ColSpanCount);
      cptr = DirColSpan;
   }
   else
   if (tkptr->DirStyle == MAPURL_DIR_STYLE_TABLE2 ||
       tkptr->DirStyle == MAPURL_DIR_STYLE_SORT2)
      cptr = "</tr>\n</thead>\n<tbody>\n";
   else
   if (tkptr->DirStyle == MAPURL_DIR_STYLE_HTDIR)
   {
      if (IconInPlace)
         cptr = "\n<hr size=\"1\" noshade>\n";
      else
         /* when an icon not providing appropriate line spacing to the <hr> */
         cptr = "\n\n<hr size=\"1\" noshade>\n";
   }
   else
   {
      if (IconInPlace)
         cptr = "\n<hr size=\"1\" noshade>\n";
      else
         /* when an icon not providing appropriate line spacing to the <hr> */
         cptr = "\n\n<hr size=\"1\" 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 = NULL;

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

   /* table row for sort */
   for (cptr = "!AZ"; *cptr && sptr < zptr; *sptr++ = *cptr++);

   while (*lptr && *lptr != '&' && sptr < zptr)
   {
      if (isdigit(*lptr))
      {
         wptr = lptr;
         while (*lptr && isdigit(*lptr)) lptr++;
         continue;
      }
      else
         wptr = NULL;

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

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

         case 'C' :
         case 'R' :
         case 'S' :
            /* right justify, then value */
            if (tkptr->DirStyleTabular)
            {
               if (tkptr->DirStyle == MAPURL_DIR_STYLE_SORT ||
                   tkptr->DirStyle == MAPURL_DIR_STYLE_SORT2)
                  cptr = "<td class=\"rnwtd\" wasd=\"!AZ\">!#<!#AZ!AZ!></td>";
               else           
                  cptr = "<td class=\"rnwtd\">!#<!#AZ!AZ!></td>";
            }
            else
               cptr = "!#<!#AZ!AZ!>";
            while (*cptr && sptr < zptr) *sptr++ = *cptr++;
            break;

         case 'D' :
            /* nothing fancy (allow for leading/trailing quotes and link) */
            if (tkptr->DirStyleTabular)
            {
               if (tkptr->MakeDescriptionLink)
                  if (tkptr->FieldWidthDescription)
                     cptr = "<td>!AZ!AZ!AZ!AZ!AZ!AZ!#<!#AZ!AZ!AZ!AZ!></td>";
                  else
                     cptr = "<td>!AZ!AZ!AZ!AZ!AZ!AZ!AZ!AZ!AZ</td>";
               else
                  if (tkptr->FieldWidthDescription)
                     cptr = "<td>!AZ!#<!#AZ!AZ!AZ!></td>";
                  else
                     cptr = "<td>!AZ!AZ!AZ</td>";
            }
            else
            {
               if (tkptr->MakeDescriptionLink)
                  if (tkptr->FieldWidthDescription)
                     cptr = "!AZ!AZ!AZ!AZ!AZ!AZ!#<!#AZ!AZ!AZ!AZ!>";
                  else
                     cptr = "!AZ!AZ!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 */
            if (tkptr->DirStyleTabular)
            {
               if (tkptr->DirStyle == MAPURL_DIR_STYLE_SORT ||
                   tkptr->DirStyle == MAPURL_DIR_STYLE_SORT2)
                  cptr = "<td wasd=\"!UL\">!AZ!AZ!AZ</td>";
               else
                  cptr = "<td>!AZ!AZ!AZ</td>";
            }
            else
               cptr = "!AZ!AZ!AZ";
            while (*cptr && sptr < zptr) *sptr++ = *cptr++;
            break;

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

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

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

   /* end table row for sort */
   for (cptr = "!AZ"; *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 - LayoutFaoPtr;

   if (WATCHMOD (rqptr, 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 (WATCHMOD (rqptr, WATCH_MOD_DIR))
       WatchThis (WATCHITM(rqptr), WATCH_MOD_DIR,
                  "DirFormat() !&Z !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;
   }

   AuthAccessEnable (rqptr, tkptr->SearchOds.ExpFileName, AUTH_ACCESS_READ);

   OdsFileAcpInfo (&tkptr->SearchOds, &DirFormatAcpInfoAst, rqptr); 

   AuthAccessEnable (rqptr, 0, 0);
}

/****************************************************************************/
/*
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  AddendZero = 0,
                         FiveTwelve = 512,
                         LibDateTimeContext = 0,
                         LibDateTimeSortTableContext = 0,
                         LibDateTimeVmsContext = 0,
                         LibOutputFormat = LIB$K_OUTPUT_FORMAT;

   static char  Cdt [32],
                CdtSortTable [16],
                Rdt [32],
                RdtSortTable [16];

   static $DESCRIPTOR (BlocksFaoDsc, "!UL/!UL");
   static $DESCRIPTOR (CdtDsc, Cdt);
   static $DESCRIPTOR (CdtSortTableDsc, CdtSortTable);
   static $DESCRIPTOR (DeviceDsc, "");
   static $DESCRIPTOR (OutputFormatDsc, "|!DB-!MAAC-!Y4|!H04:!M0|");
   static $DESCRIPTOR (OutputFormatSortTableDsc, "|!Y4!MN0!D0!H04!M0!S0|");
   static $DESCRIPTOR (OutputFormatVmsDsc, "|!DB-!MAAU-!Y4|!H04:!M0:!S0|");
   static $DESCRIPTOR (OwnerFaoDsc, "!%I");
   static $DESCRIPTOR (OwnerDsc, "");
   static $DESCRIPTOR (RdtDsc, Rdt);
   static $DESCRIPTOR (RdtSortTableDsc, RdtSortTable);
   static $DESCRIPTOR (SizeDsc, "");
   static $DESCRIPTOR (SizeFaoDsc, "!#* !AZ");
   static $DESCRIPTOR (TotalFilesDsc, "");
   static $DESCRIPTOR (TotalFilesFaoDsc, "!UL files");

   BOOL  ThisIsADirectory;
   int  idx,
        status,
        Count,
        DateLength,
        EncodedNameDelta,
        MaxIdx;
   uint64  bytes64;
   unsigned short  slen,
                   AcpChannel;
   unsigned short  Time7 [7];
   unsigned long  AllocatedVbn,
                  EndOfFileVbn;
   unsigned long  FaoVector [64];
   char  *cptr, *sptr, *zptr,
         *IconPtr,
         *AnchorNameOverflowPtr;
   char  AnchorLink [(ODS_MAX_FILE_NAME_LENGTH*2)+1],
         AnchorName [ODS_MAX_FILE_NAME_LENGTH+1],
         Description [256],
         IconAnchor [ODS_MAX_FILE_NAME_LENGTH+1],
         Owner [64],
         Protection [64],
         Size [32],
         SizeSortTable [32],
         String [2048],
         TotalFiles [32];
   DIR_TASK  *tkptr;

   unsigned long  *ctxptr;

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

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

   if (WATCHMOD (rqptr, WATCH_MOD_DIR))
       WatchThis (WATCHITM(rqptr), 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
   if (MATCH5 (tkptr->SearchOds.NamTypePtr, ".DIR;") ||
       MATCH5 (tkptr->SearchOds.NamTypePtr, ".dir;"))
   {
      /* check if the .DIR is really a directory */
      if (VMSok (OdsReallyADir (rqptr, &tkptr->SearchOds)))
         ThisIsADirectory = true;
      else
         ThisIsADirectory = false;
   }
   else
      ThisIsADirectory = false;

   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 (&LibDateTimeSortTableContext,
                                              &LibOutputFormat,
                                              &OutputFormatSortTableDsc);
      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) / sizeof(unsigned long);
   idx = 0;

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

      /* must be block totals, which requires termination of some sort */
      if (idx < MaxIdx)
         if (tkptr->DirStyleTabular)
            FaoVector[idx++] = "</tbody>\n<tfoot>\n";
         else
            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++ = "";
      }
      if (tkptr->QueryStringPtr)
      {
         *vecptr++ = "?httpd=index&!AZ";
         *vecptr++ = tkptr->QueryStringPtr;
      }
      else
         *vecptr++ = "";

      status = FaolToBuffer (AnchorLink, sizeof(AnchorLink), NULL,
                             "!&%AZ!&%&[AZ!&;&@", &LocalFaoVector);
      if (VMSnok (status) || status == SS$_BUFFEROVF)
         ErrorNoticed (rqptr, status, NULL, FI_LI);

      if (tkptr->FormatLikeVms)
         memcpy (AnchorName, "[-]", 4);
      else
         memcpy (AnchorName, "../", 4);
   }
   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++ = "";
      if (tkptr->QueryStringPtr)
      {
         *vecptr++ = "?httpd=index&!AZ";
         *vecptr++ = tkptr->QueryStringPtr;
      }
      else
         *vecptr++ = "";

      status = FaolToBuffer (AnchorLink, sizeof(AnchorLink), NULL,
                             "!&%AZ!&@/!&@!&;&@", &LocalFaoVector);
      if (VMSnok (status) || status == SS$_BUFFEROVF)
         ErrorNoticed (rqptr, status, NULL, FI_LI);

      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 = FaolToBuffer (AnchorName, sizeof(AnchorName), NULL,
                             "!&@", &LocalFaoVector);
      if (VMSnok (status) || status == SS$_BUFFEROVF)
         ErrorNoticed (rqptr, status, NULL, FI_LI);

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

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

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

      if (tkptr->DirectoryCount && !tkptr->FileCount)
      {
         /* leading line-break required for first file after directories */
         if (idx < MaxIdx)
         {
            if (tkptr->DirStyleTabular)
               if (tkptr->DirStyle == MAPURL_DIR_STYLE_SORT ||
                   tkptr->DirStyle == MAPURL_DIR_STYLE_SORT2)
                  FaoVector[idx++] = "<tr id=\"wasdB\"><td>&nbsp;</td></tr>\n";
               else
                  FaoVector[idx++] = "<tr><td>&nbsp;</td></tr>\n";
            else
               FaoVector[idx++] = "\n";
         }
      }
      else
         if (idx < MaxIdx) FaoVector[idx++] = "";
      tkptr->FileCount++;

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

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

      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++ = 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++ = "";

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

      status = FaolToBuffer (AnchorLink, sizeof(AnchorLink), NULL,
                             "!&@!&%&[AZ!AZ!&@", &LocalFaoVector);
      if (VMSnok (status) || status == SS$_BUFFEROVF)
         ErrorNoticed (rqptr, status, NULL, FI_LI);

      /*************/
      /* icon link */
      /*************/

      if (tkptr->IconLinkEnabled &&
          !rqptr->rqPathSet.DirNoIconLink)
      {
         vecptr = &LocalFaoVector;
         *vecptr++ = tkptr->LinkTarget;

         /* when the path may be different from base path (e.g. SSI) */
         *vecptr++ = rqptr->PathOds;
         *vecptr++ = tkptr->RealPath;

         /* file name and type */
         *vecptr++ = rqptr->PathOds;
         *vecptr++ = tkptr->SearchOds.NamNamePtr;

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

         *vecptr++ = ConfigContentType (NULL, ".TXT");

         status = FaolToBuffer (IconAnchor, sizeof(IconAnchor), NULL,
"<a!AZ href=\"!&%&[AZ!&%&[AZ!AZ?httpd=content&type=!&%AZ\">",
                                &LocalFaoVector);
         if (VMSnok (status) || status == SS$_BUFFEROVF)
            ErrorNoticed (rqptr, status, NULL, FI_LI);
      }
      else
         IconAnchor[0] = '\0';

      /***************/
      /* anchor name */
      /***************/

      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 = FaolToBuffer (AnchorName, sizeof(AnchorName), NULL,
                             "!&@", &LocalFaoVector);
      if (VMSnok (status) || status == SS$_BUFFEROVF)
         ErrorNoticed (rqptr, status, NULL, FI_LI);

      /* 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 = to_upper(*cptr);
   }

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

      /* count the extra characters introduced by escaping/encoding */
      EncodedNameDelta = 0;
      AnchorNameOverflowPtr = "";
      if (!tkptr->DirStyleTabular)
      {
         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 = "+";
         }
      }
   }

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

   if (idx < MaxIdx)
      if (tkptr->DirStyleTabular)
         FaoVector[idx++] = "<tr>";
      else
         FaoVector[idx++] = "";

   cptr = tkptr->LayoutPtr;
   while (*cptr)
   {
      if (!isalpha(*cptr))
      {
         cptr++;
         continue;
      }

      if (WATCHMOD (rqptr, WATCH_MOD_DIR))
         WatchThis (WATCHITM(rqptr), WATCH_MOD_DIR, "!UL \'!1AZ\' !AZ",
                    idx, cptr, cptr);

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

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

            if (tkptr->DirFormatParentDirPath[0] || tkptr->DirFormatBlockTotals)
            {
               /* make it an empty field */
               if (tkptr->DirStyle == MAPURL_DIR_STYLE_SORT ||
                   tkptr->DirStyle == MAPURL_DIR_STYLE_SORT2)
                  if (idx < MaxIdx) FaoVector[idx++] = "";
               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 */
            if (tkptr->FormatLikeVms)
               ctxptr = &LibDateTimeVmsContext;
            else
               ctxptr = &LibDateTimeContext;

            if (VMSnok (status =
                lib$format_date_time (&CdtDsc,
                                      &tkptr->SearchOds.FileQio.CdtTime64,
                                      ctxptr, &DateLength, 0)))
            {
               rqptr->rqResponse.ErrorTextPtr = "lib$format_date_time()";
               ErrorVmsStatus (rqptr, status, FI_LI);
               DirEnd (rqptr);
               return;
            }
            Cdt[DateLength] = '\0';

            if (tkptr->DirStyle == MAPURL_DIR_STYLE_SORT ||
                tkptr->DirStyle == MAPURL_DIR_STYLE_SORT2)
            {
               /* custom key as a 64 bit number */
               FaoToBuffer (CdtSortTable, sizeof(CdtSortTable), 0,
                            "!@UQ", &tkptr->SearchOds.FileQio.CdtTime64);
               if (idx < MaxIdx) FaoVector[idx++] = CdtSortTable;
            }

            if (idx < MaxIdx) FaoVector[idx++] = tkptr->FieldWidthCdt;
            if (idx < MaxIdx)
            {
               /* next parameter right justifies the field */
               if (tkptr->FieldWidthCdt - DateLength > 0)
                  FaoVector[idx++] = tkptr->FieldWidthCdt - DateLength;
                else
                  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) 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, &slen, &TotalFilesDsc,
                        tkptr->DirectoryCount + tkptr->FileCount);
               TotalFiles[slen] = '\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) 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) 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";
                  if (idx < MaxIdx) FaoVector[idx++] = tkptr->LinkTarget;
                  if (idx < MaxIdx) FaoVector[idx++] = " 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";
                  if (idx < MaxIdx) FaoVector[idx++] = tkptr->LinkTarget;
                  if (idx < MaxIdx) FaoVector[idx++] = " 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 (tkptr->DirStyle == MAPURL_DIR_STYLE_SORT ||
                tkptr->DirStyle == MAPURL_DIR_STYLE_SORT2)
               if (idx < MaxIdx)
                  FaoVector[idx++] = tkptr->DirectoryCount + tkptr->FileCount;

            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) 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++] = "";
               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";
            if (idx < MaxIdx)
               if (ThisIsADirectory)
                  FaoVector[idx++] = "";
               else
                  FaoVector[idx++] = tkptr->LinkTarget;
            if (idx < MaxIdx) FaoVector[idx++] = " href=\"";
            if (idx < MaxIdx) FaoVector[idx++] = AnchorLink;
            if (idx < MaxIdx) FaoVector[idx++] = "\">";
            if (idx < MaxIdx)
               if (tkptr->FieldWidthName)
                  FaoVector[idx++] = tkptr->FieldWidthName +
                                     EncodedNameDelta + 4;
               else
                  FaoVector[idx++] = strlen(AnchorName) + 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)
               if (tkptr->FieldWidthName)
                  FaoVector[idx++] = tkptr->FieldWidthName + EncodedNameDelta;
               else
                  FaoVector[idx++] = strlen(AnchorName);
            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 (!tkptr->DirStyleTabular)
                  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, &slen, &OwnerDsc,
                     tkptr->SearchOds.FileQio.AtrUic);
            Owner[slen] = '\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 (!tkptr->DirStyleTabular)
                  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 (tkptr->DirStyle == MAPURL_DIR_STYLE_SORT ||
                   tkptr->DirStyle == MAPURL_DIR_STYLE_SORT2)
                  if (idx < MaxIdx) FaoVector[idx++] = "";
               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 */
            if (tkptr->FormatLikeVms)
               ctxptr = &LibDateTimeVmsContext;
            else
               ctxptr = &LibDateTimeContext;

            if (VMSnok (status =
                lib$format_date_time (&RdtDsc,
                                      &tkptr->SearchOds.FileQio.RdtTime64,
                                      ctxptr, &DateLength, 0)))
            {
               rqptr->rqResponse.ErrorTextPtr = "lib$format_date_time()";
               ErrorVmsStatus (rqptr, status, FI_LI);
               DirEnd (rqptr);
               return;
            }
            Rdt[DateLength] = '\0';

            if (tkptr->DirStyle == MAPURL_DIR_STYLE_SORT ||
                tkptr->DirStyle == MAPURL_DIR_STYLE_SORT2)
            {
               /* custom key as a 64 bit number */
               FaoToBuffer (RdtSortTable, sizeof(RdtSortTable), 0,
                            "!@UQ", &tkptr->SearchOds.FileQio.RdtTime64);
               if (idx < MaxIdx) FaoVector[idx++] = RdtSortTable;
            }

            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 (tkptr->DirStyle == MAPURL_DIR_STYLE_SORT ||
                   tkptr->DirStyle == MAPURL_DIR_STYLE_SORT2)
                  if (idx < MaxIdx) FaoVector[idx++] = "";
               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->FieldWidthSize >= sizeof(Size)-1)
               SizeDsc.dsc$w_length = sizeof(Size)-1;
            else
               SizeDsc.dsc$w_length = tkptr->FieldWidthSize;

            if (tkptr->DirFormatBlockTotals)
            {
               if (tkptr->LayoutPtr == Config.cfDir.DefaultLayout ||
                   tkptr->LayoutPtr == DirDefaultLayout)
                  sptr = "";
               else
                  for (sptr = tkptr->LayoutPtr;
                       *sptr && to_upper(*sptr) != 'S';
                       sptr++);
               if (sptr[0] && sptr[1] == LAYOUT_PARAMETER)
               {
                  bytes64 = (uint64)tkptr->TotalUsedBlocks * 512;
                  slen = DirFormatSize (bytes64, sptr+2,
                                        tkptr->SizeKilo, &SizeDsc);
               }
               else
                  sys$fao (&BlocksFaoDsc, &slen, &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 (WATCHMOD (rqptr, WATCH_MOD_DIR))
                  WatchThis (WATCHITM(rqptr), WATCH_MOD_DIR,
                     "AllocatedVbn:!UL EndOfFileVbn:!UL FirstFreeByte:!UL",
                     AllocatedVbn, EndOfFileVbn,
                     tkptr->SearchOds.FileQio.RecAttr.fat$w_ffbyte);

               if (EndOfFileVbn <= 1)
                  bytes64 = (uint64)tkptr->SearchOds.FileQio.RecAttr.fat$w_ffbyte;
               else
               {
                  bytes64 = (uint64)EndOfFileVbn - 1;
                  bytes64 *= 512;
                  bytes64 += (uint64)tkptr->SearchOds.FileQio.RecAttr.fat$w_ffbyte;
               }

               if (tkptr->FormatLikeVms &&
                   (tkptr->LayoutPtr == Config.cfDir.DefaultLayout ||
                    tkptr->LayoutPtr == DirDefaultLayout))
                  sys$fao (&BlocksFaoDsc, &slen, &SizeDsc,
                           EndOfFileVbn, AllocatedVbn);
               else
               if (cptr[1] == LAYOUT_PARAMETER)
                  slen = DirFormatSize (bytes64, cptr+2,
                                        tkptr->SizeKilo, &SizeDsc);
               else
               if (tkptr->FormatLikeVms)
                  sys$fao (&BlocksFaoDsc, &slen, &SizeDsc,
                           EndOfFileVbn, AllocatedVbn);
               else
                  slen = DirFormatSize (bytes64, NULL,
                                        tkptr->SizeKilo, &SizeDsc);

               tkptr->TotalAllocatedBlocks += AllocatedVbn;
               tkptr->TotalUsedBlocks += EndOfFileVbn;

            }
            Size[slen] = '\0';

            if (tkptr->DirStyle == MAPURL_DIR_STYLE_SORT ||
                tkptr->DirStyle == MAPURL_DIR_STYLE_SORT2)
            {
               /* custom key as 64 bit number */
               FaoToBuffer (SizeSortTable, sizeof(SizeSortTable), 0,
                            "!@UQ", &bytes64);
               if (idx < MaxIdx) FaoVector[idx++] = SizeSortTable;
            }

            if (tkptr->DirStyleTabular)
            {
               if (idx < MaxIdx) FaoVector[idx++] = slen;
               if (idx < MaxIdx) FaoVector[idx++] = 0;
            }
            else
            {
               if (idx < MaxIdx) FaoVector[idx++] = tkptr->FieldWidthSize;
               /* next parameter right justifies the field */
               if (idx < MaxIdx)
                  if (tkptr->FieldWidthSize - slen > 0)
                     FaoVector[idx++] = tkptr->FieldWidthSize - slen;
                  else
                     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 (idx < MaxIdx)
      if (tkptr->DirStyleTabular)
         FaoVector[idx++] = "</tr>\n";
      else
         FaoVector[idx++] = "\n";

   if (WATCHMOD (rqptr, WATCH_MOD_DIR))
      WatchThis (WATCHITM(rqptr), WATCH_MOD_DIR, "!UL !AZ",
                 idx, tkptr->LayoutFaoPtr);

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

   status = FaolToNet (rqptr, tkptr->LayoutFaoPtr, &FaoVector);
   if (VMSnok (status))
   {
      ErrorNoticed (rqptr, status, NULL, 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);
}

/*****************************************************************************/
/*
Write a string into the supplied descriptor containing the size specified by
the 'QuadBytesPtr' parameter and the format string 'FormatCharPtr'.  It is
recognised the approach truncates rather than rounds the value but in context
this is close enough for jazz.  'SizeKilo' will be either 1024 (default) or
1000.  Return the number of characters written.  This function does not
null-terminate the string!
*/ 
 
int DirFormatSize
(
uint64 bytes64,
char *FormatPtr,
int SizeKilo,
struct dsc$descriptor *SizeDscPtr
)
{
   static $DESCRIPTOR (StatusFaoDsc, "%X!8XL");
   static $DESCRIPTOR (OverflowFaoDsc, "!256**");

   int  ccnt, status;
   unsigned short  slen;
   float  fvalue, fgiga, fkilo, fmega;
   char  ch;
   char  *cptr, *sptr, *zptr;
   char  buf [64];

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

   if (WATCH_MODULE(WATCH_MOD_DIR))
       WatchThis (WATCHALL, WATCH_MOD_DIR,
                  "DirFormatSize() \"!AZ\" !UL !&,@UQ",
                  FormatPtr, SizeKilo, &bytes64);

   slen = 0;
   status = SS$_NORMAL;
   fvalue = (float)bytes64;

   /* span over any leading kilo specification */
   if (!FormatPtr) FormatPtr = "";
   if (isdigit(*FormatPtr))
      if (*++FormatPtr == LAYOUT_PARAMETER)
         FormatPtr++;

   cptr = buf;
   zptr = (sptr = SizeDscPtr->dsc$a_pointer) + SizeDscPtr->dsc$w_length;

   switch (to_upper(*FormatPtr))
   {
      case 'B' :
         status = FaoToBuffer (buf, sizeof(buf),
                               &slen, "!&,@UQ", &bytes64);
         while (*cptr && sptr < zptr) *sptr++ = *cptr++;
         if (*cptr) status = SS$_BUFFEROVF; 
         break;

      case 'K' :
         fkilo = (float)SizeKilo;
         sprintf (cptr, "%f", fvalue / fkilo);
         while (*cptr && *cptr != '.' && sptr < zptr) *sptr++ = *cptr++;
         for (ccnt = 4; ccnt-- && *cptr && sptr < zptr; *sptr++ = *cptr++);
         if (sptr < zptr)
            *sptr++ = 'K';
         else
            status = SS$_BUFFEROVF; 
         break;

      case 'M' :
         fkilo = (float)SizeKilo;
         fmega = fkilo * fkilo;
         sprintf (cptr, "%f", fvalue / fmega);
         while (*cptr && *cptr != '.' && sptr < zptr) *sptr++ = *cptr++;
         for (ccnt = 7; ccnt-- && *cptr && sptr < zptr; *sptr++ = *cptr++);
         if (sptr < zptr)
            *sptr++ = 'M';
         else
            status = SS$_BUFFEROVF; 
         break;

      case 'V' :
         if (fvalue > 0.0)
            sprintf (cptr, "%f", (fvalue / 512.0) + 1.0);
         else
            sprintf (cptr, "%f", fvalue);
         while (*cptr && *cptr != '.' && sptr < zptr) *sptr++ = *cptr++;
         if (*cptr) status = SS$_BUFFEROVF; 
         break;

      case 'D' :
      case 'F' :
      default :
         fkilo = (float)SizeKilo;
         fmega = fkilo * fkilo;
         fgiga = fmega * fkilo;
         if (fvalue >= fgiga)
         {
            sprintf (cptr, "%f", fvalue / fgiga);
            ch = 'G';
         }
         else
         if (fvalue >= fmega)
         {
            sprintf (cptr, "%f", fvalue / fmega);
            ch = 'M';
         }
         else
         if (fvalue >= fkilo)
         {
            sprintf (cptr, "%f", fvalue / fkilo);
            ch = 'K';
         }
         else
         {
            sprintf (cptr, "%f", fvalue);
            ch = '\0';
         }
         while (*cptr && *cptr != '.' && sptr < zptr) *sptr++ = *cptr++;
         if (fvalue >= fkilo)
            for (ccnt = 2; ccnt-- && *cptr && sptr < zptr; *sptr++ = *cptr++);
         if (sptr < zptr) {
            if (ch) *sptr++ = ch;
         }
         else
            status = SS$_BUFFEROVF; 
   }

   /* fallback, first to Mbytes then to Gbytes */
   if (status == SS$_BUFFEROVF)
   {
      status = SS$_NORMAL;
      fkilo = (float)SizeKilo;
      fmega = fkilo * fkilo;
      zptr = (sptr = SizeDscPtr->dsc$a_pointer) + SizeDscPtr->dsc$w_length;
      sprintf (cptr = buf, "%f", fvalue / fmega);
      while (*cptr && *cptr != '.' && sptr < zptr) *sptr++ = *cptr++;
      while (*cptr && sptr < zptr-1) *sptr++ = *cptr++;
      if (*(sptr-1) == '.') sptr--;
      if (sptr < zptr)
         *sptr++ = 'M';
      else
      {
         fgiga = fmega * fkilo;
         zptr = (sptr = SizeDscPtr->dsc$a_pointer) + SizeDscPtr->dsc$w_length;
         sprintf (cptr = buf, "%f", fvalue / fgiga);
         while (*cptr && *cptr != '.' && sptr < zptr) *sptr++ = *cptr++;
         while (*cptr && sptr < zptr-1) *sptr++ = *cptr++;
         if (*(sptr-1) == '.') sptr--;
         if (sptr < zptr)
            *sptr++ = 'G';
         else
            status = SS$_BUFFEROVF;
      }
   }

   /* this function does not null-terminate the string! */
   slen = sptr - SizeDscPtr->dsc$a_pointer;

   if (VMSnok(status) && status != SS$_BUFFEROVF)
      sys$fao (&StatusFaoDsc, &slen, SizeDscPtr, status);
   if (status == SS$_BUFFEROVF)
      sys$fao (&OverflowFaoDsc, &slen, SizeDscPtr);

   return (slen);
}

/*****************************************************************************/
/*
Process a string of directory directives.
The directives can be '&' separated (query string) or '\n' separated
(.WWW_WASD) file based.
*/ 

DirDirectString
(
REQUEST_STRUCT *rqptr,
char *DirectString
)
{
   BOOL  LocalDirect;
   int  size, WwwWasd;
   char  *aptr, *cptr, *sptr, *zptr;
   DIR_TASK  *tkptr;

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

   if (WATCHMOD (rqptr, WATCH_MOD_DIR))
       WatchThis (WATCHITM(rqptr), WATCH_MOD_DIR,
                  "DirDirectString() \"!AZ\"", DirectString);

   if (WATCHING (rqptr, WATCH_RESPONSE))
      WatchData (DirectString, strlen(DirectString));

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

   /* override is always false unless the current source allows it */
   tkptr->QueryOverride = false;
   LocalDirect = false;
   WwwWasd = 0;

   cptr = DirectString;
   while (*cptr)
   {
      while (*cptr && (*cptr == '&' || *cptr == ' ' || *cptr == '\n'))
      {
         if (*cptr == '\n') WwwWasd++;
         cptr++;
      }
      if (!*cptr) break;
      if (*cptr == '#')
      {
         /* .WWW_WASD file can contain comments */
         while (*cptr && *cptr != '\n') cptr++;
         WwwWasd++;
         continue;
      }

      if (to_lower(*cptr) == 'a' &&
          strsame (cptr, "autoscript=", 11))
      {
         cptr += 11;
         /* if "false", "no" or "0" then turn off auto-scripting */
         if (to_lower(*cptr) == 'f' || to_lower(*cptr) == 'n' || *cptr == '0')
            tkptr->AutoScriptEnabled = false;
         else
            tkptr->AutoScriptEnabled = true;
      }  
      else
      if (to_lower(*cptr) == 'd' &&
          strsame (cptr, "delimit=", 8))
      {
         cptr += 8;
         switch (to_lower(*cptr))
         {
            case 'h' :
               tkptr->DirDelimit = MAPURL_DIR_DELIMIT_HEADER;
               break;
            case 'f' :
               tkptr->DirDelimit = MAPURL_DIR_DELIMIT_FOOTER;
               break;
            case 'n' :
               tkptr->DirDelimit = MAPURL_DIR_DELIMIT_NONE;
               break;
            default :
               tkptr->DirDelimit = MAPURL_DIR_DELIMIT_BOTH;
         }
      }
      else
      /* experience shows both make it easier! */
      if (to_lower(*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 (to_lower(*cptr) == 't' || to_lower(*cptr) == 'y' || *cptr == '1')
            rqptr->rqResponse.PreExpired = true;
         else
         if (to_lower(*cptr) == 'f' || to_lower(*cptr) == 'n' || *cptr == '0')
            rqptr->rqResponse.PreExpired = false;
      }
      else
      if (to_lower(*cptr) == 'f' &&
          strsame (cptr, "font=", 5))
      {
         cptr += 5;
         if (strsame (cptr, "inherit", 7))
            tkptr->DirFont = MAPURL_DIR_FONT_INHERIT;
         else
         if (strsame (cptr, "monospace", 8))
            tkptr->DirFont = MAPURL_DIR_FONT_MONOSPACE;
      }
      else
      if (to_lower(*cptr) == 'i' &&
          strsame (cptr, "ilink=", 6))
      {
         cptr += 6;
         /* if "false", "no" or "0" then icon is not a plain-text link */
         if (to_lower(*cptr) == 'f' || to_lower(*cptr) == 'n' || *cptr == '0')
            tkptr->IconLinkEnabled = false;
         else
            tkptr->IconLinkEnabled = true;
      }  
      else
      if (to_lower(*cptr) == 'l' &&
          strsame (cptr, "layout=", 7))
      {
         cptr += 7;
         for (sptr = cptr; *sptr && *sptr != '&' && *sptr != '\n'; sptr++);
         tkptr->LayoutPtr = sptr = VmGetHeap (rqptr, sptr-cptr+1);
         while (*cptr && *cptr != '&' && *cptr != '\n') *sptr++ = *cptr++;
      }
      else
      if (to_lower(*cptr) == 'l' &&
          strsame (cptr, "local=", 6))
      {
         cptr += 6;
         /* if "false", "no" or "0" then turn off auto-scripting */
         if (to_lower(*cptr) == 'f' || to_lower(*cptr) == 'n' || *cptr == '0')
            LocalDirect = false;
         else
            LocalDirect = true;
      }  
      else
      if (to_lower(*cptr) == 'n' &&
          strsame (cptr, "notype=", 7))
      {
         cptr += 7;
         /* if "true", "yes" or "1" then do not display parent directory */
         if (to_lower(*cptr) == 't' || to_lower(*cptr) == 'y' || *cptr == '1')
            tkptr->ShowNoType = true;
         else
            tkptr->ShowNoType = false;
      }
      else
      if (to_lower(*cptr) == 'n' &&
          strsame (cptr, "nop=", 4))
      {
         cptr += 4;
         /* if "true", "yes" or "1" then do not display parent directory */
         if (to_lower(*cptr) == 't' || to_lower(*cptr) == 'y' || *cptr == '1')
            tkptr->AsIfNopFound = true;
         else
            tkptr->AsIfNopFound = false;
      }
      else
      if (to_lower(*cptr) == 'n' &&
          strsame (cptr, "nops=", 5))
      {
         cptr += 5;
         /* if "true", "yes" or "1" don't display parent or sub directory */
         if (to_lower(*cptr) == 't' || to_lower(*cptr) == 'y' || *cptr == '1')
            tkptr->AsIfNopFound = tkptr->AsIfNosFound = true;
         else
            tkptr->AsIfNopFound = tkptr->AsIfNosFound = false;
      }
      else
      if (to_lower(*cptr) == 'n' &&
          strsame (cptr, "nos=", 4))
      {
         cptr += 4;
         /* if "true", "yes" or "1" then do not display sub directory */
         if (to_lower(*cptr) == 't' || to_lower(*cptr) == 'y' || *cptr == '1')
            tkptr->AsIfNosFound = true;
         else
            tkptr->AsIfNosFound = false;
      }
      else
      if (to_lower(*cptr) == 'o' &&
          strsame (cptr, "override=", 9))
      {
         cptr += 9;
         /* if "true", "yes" or "1" then allow query string to override */
         if (to_lower(*cptr) == 't' || to_lower(*cptr) == 'y' || *cptr == '1')
            tkptr->QueryOverride = true;
         else
            tkptr->QueryOverride = false;
      }
      else
      if (to_lower(*cptr) == 'q' &&
          strsame (cptr, "query=", 6))
      {
         cptr += 6;
         /* this directive is always terminated only by end-of-line */
         for (sptr = cptr; *sptr && *sptr != '\n'; sptr++);
         tkptr->QueryStringPtr = sptr = VmGetHeap (rqptr, sptr-cptr+1);
         while (*cptr && *cptr != '\n') *sptr++ = *cptr++;
      }
      else
      if (to_lower(*cptr) == 'r' &&
          strsame (cptr, "readme=", 7))
      {
         cptr += 7;
         /* if "false", "no" or "0" then do not display any readme */
         if (to_lower(*cptr) == 'f' || to_lower(*cptr) == 'n' || *cptr == '0')
            tkptr->IncludeAnyReadme = false;
         else
            tkptr->IncludeAnyReadme = true;
      }
      else
      if (to_lower(*cptr) == 's' &&
          strsame (cptr, "script=", 7))
      {
         cptr += 7;
         zptr = (sptr = tkptr->ScriptName) + sizeof(tkptr->ScriptName)-1;
         if (*cptr != '/') *sptr++ = '/';
         while (*cptr && *cptr != '&' && *cptr != '\n' && sptr < zptr)
         {
            if (*cptr != '&' && *cptr != '+') *sptr++ = *cptr;
            cptr++;
         }
         *sptr = '\0';
      }
      else
      if (to_lower(*cptr) == 's' &&
          strsame (cptr, "sort=", 5))
      {
         cptr += 5;
         tkptr->DirSort[0] = to_upper(*cptr++);
         if (*cptr == '+' || *cptr == '-')
            tkptr->DirSort[1] = *cptr;
         else
            tkptr->DirSort[1] = '\0';
         /* style is implicit! */
         if (tkptr->DirStyle != MAPURL_DIR_STYLE_SORT2)
            tkptr->DirStyle = MAPURL_DIR_STYLE_SORT;
      }
      else
      if (to_lower(*cptr) == 's' &&
          strsame (cptr, "style=", 6))
      {
         cptr += 6;
         if (strsame (cptr, "sort2", 5))
            tkptr->DirStyle = MAPURL_DIR_STYLE_SORT2;
         else
         if (strsame (cptr, "sort", 4))
            tkptr->DirStyle = MAPURL_DIR_STYLE_SORT;
         else
         if (strsame (cptr, "anchor2", 7))
            tkptr->DirStyle = MAPURL_DIR_STYLE_ANCHOR2;
         else
         if (strsame (cptr, "anchor", 6))
            tkptr->DirStyle = MAPURL_DIR_STYLE_ANCHOR;
         else
         if (strsame (cptr, "table2", 6))
            tkptr->DirStyle = MAPURL_DIR_STYLE_TABLE2;
         else
         if (strsame (cptr, "table", 5))
            tkptr->DirStyle = MAPURL_DIR_STYLE_TABLE;
         else
         if (strsame (cptr, "htdir2", 6))
            tkptr->DirStyle = MAPURL_DIR_STYLE_HTDIR2;
         else
         if (strsame (cptr, "htdir", 5))
            tkptr->DirStyle = MAPURL_DIR_STYLE_HTDIR;
         else
         if (strsame (cptr, "original2", 9))
            tkptr->DirStyle = MAPURL_DIR_STYLE_ORIGINAL2;
         else
         if (strsame (cptr, "original", 8))
            tkptr->DirStyle = MAPURL_DIR_STYLE_ORIGINAL;
         else
         if (strsame (cptr, "default2", 8))
            tkptr->DirStyle = MAPURL_DIR_STYLE_DEFAULT2;
         else
         if (strsame (cptr, "default", 7))
            tkptr->DirStyle = MAPURL_DIR_STYLE_DEFAULT;
      }
      else
      if (to_lower(*cptr) == 't' &&
          strsame (cptr, "target=", 7))
      {
         cptr += 7;
         zptr = (sptr = tkptr->LinkTarget) + sizeof(tkptr->LinkTarget)-2;
         for (aptr = " target=\""; *aptr; *sptr++ = *aptr++);
         while (*cptr && *cptr != '&' && *cptr != '\n' && sptr < zptr)
         {
            if (*cptr != '\"') *sptr++ = *cptr;
            cptr++;
         }
         *sptr++ = '\"';
         *sptr = '\0';
      }
      else
      if (to_lower(*cptr) == 't' &&
          strsame (cptr, "these=", 6))
      {
         cptr += 6;
         for (sptr = cptr; *sptr && *sptr != '&' && *sptr != '\n'; sptr++);
         tkptr->ThesePtr = sptr = VmGetHeap (rqptr, sptr-cptr+2);
         /* null-separated series of strings terminated by empty string */
         while (*cptr && *cptr != '&' && *cptr != '\n')
         {
            if (*cptr == ',')
               *sptr++ = '\0';
            else
               *sptr++ = *cptr;
            cptr++;
         }
      }
      else
      if (to_lower(*cptr) == 't' &&
          strsame (cptr, "title=", 6))
      {
         cptr += 6;
         if (strsame (cptr, "default", 7))
            tkptr->DirTitle = MAPURL_DIR_TITLE_DEFAULT;
         else
         if (strsame (cptr, "owner", 5))
            tkptr->DirTitle = MAPURL_DIR_TITLE_OWNER;
         else
         if (strsame (cptr, "remote", 6))
            tkptr->DirTitle = MAPURL_DIR_TITLE_REMOTE;
         else
         if (strsame (cptr, "this=", 5))
         {
            tkptr->DirTitle = MAPURL_DIR_TITLE_THIS;
            tkptr->DirTitlePtr = VmGetHeap (rqptr, size = strlen(cptr+5)+1);
            strzcpy (tkptr->DirTitlePtr, cptr+5, size);
            StringUrlDecode (tkptr->DirTitlePtr);
         }
         else
         if (*cptr == '0')
            tkptr->DirTitle = MAPURL_DIR_TITLE_NONE;
         else
         if (isdigit(*cptr))
            tkptr->DirTitle = atol(cptr);
      }
      else
      if (to_lower(*cptr) == 't' &&
          strsame (cptr, "type=", 5))
      {
         cptr += 5;
         for (sptr = cptr; *sptr && *sptr != '&' && *sptr != '\n'; sptr++);
         tkptr->QueryContentTypePtr = sptr = VmGetHeap (rqptr, sptr-cptr+1);
         while (*cptr && *cptr != '&' && *cptr != '\n') *sptr++ = *cptr++;
      }
      else
      if (to_lower(*cptr) == 'u' &&
          strsame (cptr, "upper=", 6))
      {
         cptr += 6;
         /* if "true", "yes" or "1" then force to upper case  */
         if (to_lower(*cptr) == 't' || to_lower(*cptr) == 'y' || *cptr == '1')
            tkptr->ShowUpperCase = true;
         else
            tkptr->ShowUpperCase = false;
      }
      else
      if (to_lower(*cptr) == 'v' &&
          strsame (cptr, "versions=", 9))
      {
         cptr += 9;
         if (*cptr == '*')
            tkptr->VersionsOf = 65536;
         else
            tkptr->VersionsOf = atoi(cptr);
      }
      else
      if (WATCHING (rqptr, WATCH_RESPONSE))
         WatchThis (WATCHITM(rqptr), WATCH_RESPONSE, "ERROR:!AZ", cptr);

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

   if (!WwwWasd && !LocalDirect)
   {
      /* propagate URI query string directive(s) */
      tkptr->QueryStringPtr = sptr = VmGetHeap (rqptr, cptr-DirectString+1);
      cptr = DirectString;
      while (*cptr && *cptr != '\n') *sptr++ = *cptr++;
   }
}


/*****************************************************************************/
/*
Open, read, and process the contents of the .WWW_WASD file.
*/ 
 
int DirWwwWasd (REQUEST_STRUCT *rqptr)

{
   int  status,
        SizeInBytes;
   char  *sptr;
   char  ReadBuffer [2048];
   DIR_TASK  *tkptr;
   ODS_STRUCT  OdsStruct;
   ODS_STRUCT  *odsptr = &OdsStruct;

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

   if (WATCHMOD (rqptr, WATCH_MOD_DIR))
       WatchThis (WATCHITM(rqptr), WATCH_MOD_DIR, "DirWwwWasd()");

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

   for (sptr = tkptr->DirSpec; *sptr; sptr++);
   while (sptr > tkptr->DirSpec && (*sptr != ']' || *(sptr-1) == '^]')) sptr--;
   if (*sptr == ']') sptr++;
   if (sptr - tkptr->DirSpec <= 0) return (SS$_BUGCHECK);

   OdsStructInit (&OdsStruct, true);

   odsptr->Fab = cc$rms_fab;
   odsptr->Fab.fab$b_fac = FAB$M_GET;
   odsptr->Fab.fab$b_shr = FAB$M_SHRGET;
   odsptr->Fab.fab$l_fop &= ~FAB$M_ASY;

#ifdef ODS_EXTENDED
   if (OdsExtended)
   {
      odsptr->Fab.fab$l_fna = odsptr->Fab.fab$l_dna =-1;  
      odsptr->Fab.fab$b_fns = odsptr->Fab.fab$b_dns = 0;
      odsptr->Fab.fab$l_nam = &odsptr->Naml;
      odsptr->NamlInUse = true;
      ENAMEL_RMS_NAML(odsptr->Naml)
      odsptr->Naml.naml$l_long_defname = tkptr->DirSpec;
      odsptr->Naml.naml$l_long_defname_size = sptr - tkptr->DirSpec;
      odsptr->Naml.naml$l_long_filename = DirWasdFileName;
      odsptr->Naml.naml$l_long_filename_size = sizeof(DirWasdFileName)-1;
      odsptr->Naml.naml$l_long_expand = odsptr->ExpFileName;
      odsptr->Naml.naml$l_long_expand_alloc = sizeof(odsptr->ExpFileName)-1;
   }
   else
#endif /* ODS_EXTENDED */
   {
      odsptr->Fab.fab$l_dna = tkptr->DirSpec;
      odsptr->Fab.fab$b_dns = sptr - tkptr->DirSpec;
      odsptr->Fab.fab$l_fna = DirWasdFileName;  
      odsptr->Fab.fab$b_fns = sizeof(DirWasdFileName)-1;
      odsptr->NamlInUse = false;
      odsptr->Nam = cc$rms_nam;
      odsptr->Nam.nam$l_esa = odsptr->ExpFileName;
      odsptr->Nam.nam$b_ess = ODS2_MAX_FILE_NAME_LENGTH;
   }

   /* ensure we can read this directory listing "control" file */
   sys$setprv (1, &SysPrvMask, 0, 0);
   status = sys$open (&odsptr->Fab, 0, 0);
   sys$setprv (0, &SysPrvMask, 0, 0);

   if (VMSnok (status) || VMSnok (status = odsptr->Fab.fab$l_sts))
   {
      if (WATCHMOD (rqptr, WATCH_MOD_DIR))
          WatchThis (WATCHITM(rqptr), WATCH_MOD_DIR, "!#AZ!AZ !AZ %X!8XL",
                     sptr - tkptr->DirSpec, tkptr->DirSpec,
                     DirWasdFileName, odsptr->ExpFileName, status);
      return (status);
   }

   /* record access block */
   odsptr->Rab = cc$rms_rab;
   odsptr->Rab.rab$l_fab = &odsptr->Fab;
   /* read ahead performance option, all synchronous */
   odsptr->Rab.rab$l_rop = RAB$M_RAH;
   odsptr->Rab.rab$l_rop &= ~FAB$M_ASY;

   status = sys$connect (&odsptr->Rab, 0, 0);
   if (VMSnok (status) || VMSnok (status = odsptr->Rab.rab$l_sts))
   {
      sys$close (&odsptr->Fab, 0, 0);
      return (status);
   }

   sptr = ReadBuffer;
   SizeInBytes = sizeof(ReadBuffer)-1;

   for (;;)
   {
      odsptr->Rab.rab$l_ubf = sptr;
      odsptr->Rab.rab$w_usz = SizeInBytes;

      status = sys$get (&odsptr->Rab, 0, 0);
      if (VMSnok (status)) break;

      sptr[odsptr->Rab.rab$w_rsz++] = '\n';
      sptr += odsptr->Rab.rab$w_rsz;
      SizeInBytes -= odsptr->Rab.rab$w_rsz;
   }
   *sptr = '\0';

   sys$close (&odsptr->Fab, 0, 0); 
   if (status == RMS$_EOF) status = SS$_NORMAL;

   if (VMSok(status)) DirDirectString (rqptr, ReadBuffer);

   return (status);
}

/*****************************************************************************/
/*
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 (WATCHMOD (rqptr, WATCH_MOD_DIR))
       WatchThis (WATCHITM(rqptr), 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 (WATCHMOD (rqptr, WATCH_MOD_DIR))
       WatchThis (WATCHITM(rqptr), 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);
}

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