/*****************************************************************************/
/*
                                  Upd.c

This module implements a full multi-threaded, AST-driven, asynchronous file
and directory update facility.

It provides for directory navigation, allowing subdirectories to be created,
moved to, listed and deleted.  Files within directories may be selected for
viewing, update, copying and deletion.  The module honours the directory
access controls implemented by the directory module (e.g. ".WWW_HIDDEN",
etc., see Dir.c).

It allows the creation or update of "text/*" files via an editing page.  The
<textarea></textarea> tag widget is used as the edit window.

The file editing page also has custom functionality for providing for the
partial administration of HTTPd server configuration files.

This module behaves as if it was an external script :^).  This allows the
generic script processing functionality of the the request module to provide
all necessary parsing of the script and file components of the path, as well
as the all-important authorization checking of these.  The script component
of the path is detected before an external script is launched, and the request
redirected to this module.  This has certain efficiencies as well as allowing
a "generic" module to provide some of the functionality for server
configuration.  This module does its own query string parsing.

An essential component of this modules functionality is the ability to
generate a redirection.  This allows the parent directory and name to be
reconstructed as a path and then processed as if a new request.  Although
resulting in a higher overhead this considerably simplifies the handling of
these requests.


VERSION HISTORY
---------------
12-SEP-2022  MGD  move onclick=s to addEventListener()s to support
                    content-security-policy: 'strict-dynamic'
14-SEP-2019  MGD  bugfix; UpdEnd() call HTAdminEnd() (e.g. $HTL)
12-NOV-2017  MGD  bugfix; (long-standing) always use UpdEnd() not SysDclAst()
28-SEP-2013  MGD  some refinements to Upd..() layout and functionality
03-AUG-2013  MGD  bugfix; UpdFileRename() ACCVIO with AuthAccessEnable()
10-MAR-2010  JPP  bugfix; UpdBegin() [goto] processing
23-AUG-2009  MGD  WasdCss[] and some refinements
11-AUG-2007  MGD  AuthAccessEnable() to control access
                  refine edit with a little JavaScript
26-AUG-2003  MGD  rework access to server configuration files
15-AUG-2003  MGD  where CDATA constraints make using &#10; entity impossible
                  use a field name of hidden$lf and &#94; substituted for it
08-MAR-2003  MGD  set html= directory listing header and footer,
                  there are now three directory listing styles implementing
                  set dir=style[=default|wasd1|wasd2|abi]
15-OCT-2002  MGD  demo mode ignores .WWW_HIDDEN, etc.
05-OCT-2002  MGD  refine VMS security profile usage
24-SEP-2002  MGD  add AuthAccessReadCheck() to special-case file
                  parse/open() in UpdEditFileBegin()
23-AUG-2002  MGD  supply fab$b_rfm and fab$b_rat as file copy fields to allow
                  PUT.C to set these attributes identically to the original,
                  modify formatting for SRI and PWK ODS encodings,
                  bugfix; change protection for directories,
                  bugfix; enable SYSPRV to rename (ACP remove) file,
                  bugfix; enable SYSPRV to change protection (ACP modify) file
27-APR-2002  MGD  make SYSPRV enabled ASTs asynchronous
02-FEB-2002  MGD  rework file processing due to request body processing changes
03-JAN-2002  MGD  bugfix; server admin revise
04-AUG-2001  MGD  support module WATCHing
15-FEB-2001  MGD  refine 'view' and 'list' redirection
22-DEC-2000  MGD  allow navigation on wildcard file specification,
                  provide special case edit (only) HTL list and
                  CA verification files
29-JUN-2000  MGD  bugfix; ODS-5 file rename
18-JUN-2000  MGD  support site log update
04-MAR-2000  MGD  support ODS-2 and ODS-5 using ODS module,
                  substantial rework of entire module,
                  remove browser check for file upload (many now support)
10-OCT-1999  MGD  change AuthCheckVmsUserAccess() always supply 'rqptr'
04-APR-1999  MGD  bugfix; rule check still included (woops, obsoleted in v5.3)
07-NOV-1998  MGD  WATCH facility
12-MAR-1998  MGD  add file protection selector
24-FEB-1998  MGD  request scheme ("http:" or "https:") for hard-wired "http:"
18-OCT-1997  MGD  facility to wildcard file deletes
17-AUG-1997  MGD  message database,
                  internationalized file date/times,
                  SYSUAF-authenticated users security-profile
07-JUL-1997  MGD  apparently MSIE 3.0ff allows file upload
30-JUN-1997  MGD  bugfix; rename failed without SYSPRV (wonders will never)
12-APR-1997  MGD  extended tree facility to providing general directory tree
25-MAR-1997  MGD  added tree and filter capabilities
01-FEB-1997  MGD  new for HTTPd version 4 (adapted from the UPD script)
*/
/*****************************************************************************/

#ifdef WASD_VMS_V7
#undef _VMS__V6__SOURCE
#define _VMS__V6__SOURCE
#undef __VMS_VER
#define __VMS_VER 70000000
#undef __CRTL_VER
#define __CRTL_VER 70000000
#endif

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

/* VMS related header files */
#include <iodef.h>
#include <libdef.h>
#include <libdtdef.h>
#include <rmsdef.h>
#include <rms.h>
#include <ssdef.h>
#include <stsdef.h>

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

#define WASD_MODULE "UPD"

/****************/
/* global stuff */
/****************/

/* number of rows in directory and file lists */
#define NAVIGATE_SIZE 15

#define REFRESH_SYMBOL "&#10227;";

/* this could be relocated without change by using a mapping rule */
#define UPD_HELP_PATH "/httpd/-/UpdHelp.html"

/*
The preview note is introduced at the start of a file being previewed.
See PUT.C module for how it is further processed.
Experimentation (Navigator 3 and MSIE 3) has shown it can contain text
but no page or line layup tags (e.g. <br>).
Here is a choice between an animated GIF and text.
*/
#define UPD_PREVIEW_NOTE_PLAIN "***** PREVIEW *****&#94;&#94;"

#ifndef UPD_PREVIEW_TEXT

#define UPD_PREVIEW_NOTE_HTML \
"&lt;IMG SRC=&quot;/httpd/-/UpdPreview.gif&quot; \
ALT=&quot;-PREVIEW- &quot;&gt;&lt;BR&gt;"
#define UPD_PREVIEW_NOTE_HTML_NEWLINE "&#94;"
#define UPD_PREVIEW_NOTE_MENU_NEWLINE "&lt;BR&gt;"

#else

#define UPD_PREVIEW_NOTE_HTML \
"&lt;FONT size=\"1\" COLOR=&quot;#ff0000&quot;&gt;\
&lt;SUP&gt;&lt;BLINK&gt;&lt;B&gt;-PREVIEW-&lt;/B&gt;&lt;/BLINK&gt;&lt;/SUP&gt;\
&lt;/FONT&gt;&amp;nbsp;&amp;nbsp;"
#define UPD_PREVIEW_NOTE_HTML_NEWLINE "&#94;"
#define UPD_PREVIEW_NOTE_MENU_NEWLINE "&lt;BR&gt;"

#endif /* UPD_PREVIEW_TEXT */

char  UpdProtectionListFao [] =
"!AZ name=\"protection\">\n\
<option value=\"aa00\"!AZ>!AZ\n\
<option value=\"fa00\"!AZ>!AZ\n\
<option value=\"ff00\"!AZ>!AZ\n\
</select>\n";

char  UpdLayout [64];

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

extern BOOL  CliDemo,
             NaturalLanguageEnglish,
             OdsExtended;

extern unsigned long  SysPrvMask[];

extern char*  DayName[];

extern char  ClientHostName[],
             ClientIpAddressString[],
             DirBrowsableFileName[],
             DirHiddenFileName[],
             DirNopFileName[],
             DirNosFileName[],
             DirNopsFileName[],
             DirNoWildFileName[],
             ErrorSanityCheck[],
             ServerHostPort[],
             SoftwareID[];

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

/*****************************************************************************/
/*
Begin processing an update request by allocating an update task structure,
processing the query string, and initiating the required function.
*/

void UpdBegin
(
REQUEST_STRUCT *rqptr,
REQUEST_AST NextTaskFunction
)
{
   BOOL  DoCopy,
         DoDirProtect,
         DoFileProtect,
         DoFileRename,
         DoTree,
         Navigate,
         NameIsPeriod,
         SubmitCopy,
         SubmitCreate,
         SubmitDelete,
         SubmitDirProtect,
         SubmitEdit,
         SubmitFilter,
         SubmitFileRename,
         SubmitFileProtect,
         SubmitGoto,
         SubmitList,
         SubmitMkdir,
         SubmitRmdir,
         SubmitTree,
         SubmitView,
         WatchThisOne;
   int  status;
   unsigned short  Length;
   unsigned long  FaoVector [32];
   unsigned long  *vecptr;
   char  *cptr, *qptr, *sptr, *zptr,
         *WatchPathPtr;
   char  AsName [256],
         DoThis [128],
         FieldName [128],
         FieldValue [256],
         Location [ODS_MAX_FILE_NAME_LENGTH+1],
         Name [128],
         Scratch [ODS_MAX_FILE_NAME_LENGTH+1],
         SubmitButton [16];
   UPD_TASK  *tkptr;

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

   if (WATCHMOD (rqptr, WATCH_MOD_UPD))
      WatchThis (WATCHITM(rqptr), WATCH_MOD_UPD, "UpdBegin()");

   /* if there is a request body this will be called again as an AST */
   if (!(tkptr = rqptr->UpdTaskPtr))
   {
      /* set up the task structure (only ever one per request!) */
      rqptr->UpdTaskPtr = tkptr =
         (UPD_TASK*) VmGetHeap (rqptr, sizeof(UPD_TASK));

      OdsStructInit (&tkptr->FileOds, false);
      OdsStructInit (&tkptr->SearchOds, false);

      /* don't want to try this when called as an AST! */
      tkptr->NextTaskFunction = NextTaskFunction;

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

   if (rqptr->rqHeader.Method == HTTP_METHOD_POST &&
       !rqptr->rqBody.DataPtr)
   {
      /* read all the request body (special case) then AST back */
      BodyReadBegin (rqptr, &UpdBegin, &BodyProcessReadAll);
      return;
   }

   /* must be after task allocation as function may be called as an AST */
   if (ERROR_REPORTED (rqptr))
   {
      /* previous error, cause threaded processing to unravel */
      UpdEnd (rqptr);
      return;
   }

   if (!tkptr->ProtectionMask)
      tkptr->ProtectionMask = PUT_DEFAULT_FILE_PROTECTION;

   /* terminate the path string at the first character of any file name part */
   zptr = (sptr = tkptr->PathInfoDir) + sizeof(tkptr->PathInfoDir)-1;
   for (cptr = rqptr->rqHeader.PathInfoPtr;
        *cptr && sptr < zptr;
        *sptr++ = *cptr++);
   *sptr = '\0';
   while (sptr > tkptr->PathInfoDir && *sptr != '/') sptr--;
   if (sptr > tkptr->PathInfoDir) sptr++;
   *sptr = '\0';
   tkptr->PathInfoDirLength = sptr - tkptr->PathInfoDir;

   if (WATCHING (rqptr, WATCH_RESPONSE))
      WatchThisOne = true;
   else
      WatchThisOne = false;

   if (strsame (rqptr->ScriptName, ADMIN_SCRIPT_TREE,
                sizeof(ADMIN_SCRIPT_TREE)-1))      
   {
      /***********/
      /* tree of */
      /***********/

      tkptr->UpdateTree = false;

      if (WatchThisOne)
         WatchThis (WATCHITM(rqptr), WATCH_RESPONSE, "TREEOF !AZ !AZ",
                    rqptr->rqHeader.PathInfoPtr, rqptr->ParseOds.ExpFileName);

      if (!rqptr->ParseOds.ExpFileNameLength)
      {
         rqptr->rqResponse.HttpStatus = 400;
         ErrorGeneral (rqptr, MsgFor(rqptr,MSG_UPD_DIRECTORY), FI_LI);
         UpdEnd (rqptr);
         return;
      }

      if (rqptr->ParseOds.Nam_fnb & NAM$M_WILD_VER ||
          rqptr->ParseOds.Nam_fnb & NAM$M_EXP_VER)
         tkptr->FormatTreeLikeVms = true;
      else
         tkptr->FormatTreeLikeVms = false;

      /* get any file name, type, version from request specification */
      zptr = (sptr = tkptr->FileNamePart) + sizeof(tkptr->FileNamePart)-1;
      for (cptr = rqptr->ParseOds.NamNamePtr;
           *cptr && sptr < zptr;
           *sptr++ = *cptr++);
      if (sptr >= zptr)
      {
         ErrorGeneralOverflow (rqptr, FI_LI);
         UpdEnd (rqptr);
         return;
      }
      *sptr = '\0';

      rqptr->rqResponse.PreExpired = Config.cfDir.PreExpired;
      /* scan the query string looking for "expired=[yes|no|true|false|1|0]" */
      for (cptr = rqptr->rqHeader.QueryStringLength ?
                     rqptr->rqHeader.QueryStringPtr : ""; *cptr; cptr++)
      {
         /* experience shows both make it easier! */
         if (to_lower(*cptr) == 'e' && 
             (strsame (cptr, "expired=", 8)) || strsame (cptr, "expire=", 7))
         {
            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;
            break;
         }
         while (*cptr && *cptr != '&') cptr++;
         if (*cptr) cptr++;
      }

      UpdTreeBegin (rqptr);

      return;
   }

   /**********/
   /* update */
   /**********/

   AsName[0] = DoThis[0] = Name[0] = SubmitButton[0] = '\0';
   tkptr->CxR[0] = tkptr->FileProtectionList[0] =
      tkptr->ProtectionList[0] = '\0';
   DoCopy = DoDirProtect = DoFileProtect = DoFileRename = DoTree =
      SubmitCreate = SubmitCopy = SubmitDelete = SubmitDirProtect =
      SubmitEdit = SubmitFileProtect = SubmitFilter = SubmitFileRename =
      SubmitGoto = SubmitList = SubmitMkdir = SubmitRmdir = SubmitTree = 
      SubmitView = false;

   if (rqptr->rqHeader.Method == HTTP_METHOD_POST)
      qptr = rqptr->rqBody.DataPtr;
   else
   if (rqptr->rqHeader.QueryStringLength)
      qptr = rqptr->rqHeader.QueryStringPtr;
   else
      qptr = "";

   if (WATCHMOD (rqptr, WATCH_MOD_UPD))
      WatchDataFormatted ("!&Z\n", qptr);

   while (*qptr)
   {
      status = StringParseQuery (&qptr, FieldName, sizeof(FieldName),
                                        FieldValue, sizeof(FieldValue));
      if (VMSnok (status))
      {
         /* error occured */
         if (status == SS$_IVCHAR) rqptr->rqResponse.HttpStatus = 400;
         rqptr->rqResponse.ErrorTextPtr = "parsing query string";
         ErrorVmsStatus (rqptr, status, FI_LI);
         UpdEnd (rqptr);
         return;
      }

      /********************/
      /* get field values */
      /********************/

      if (strsame (FieldName, "as", -1))
      {
         cptr = FieldValue;
         zptr = (sptr = AsName) + sizeof(AsName);
         while (*cptr && sptr < zptr) *sptr++ = *cptr++;
         if (sptr >= zptr)
         {
            ErrorGeneralOverflow (rqptr, FI_LI);
            UpdEnd (rqptr);
            return;
         }
         *sptr = '\0';
      }
      else
      if (strsame (FieldName, "cxr", -1))
      {
         cptr = FieldValue;
         zptr = (sptr = tkptr->CxR) + sizeof(tkptr->CxR);
         while (*cptr && sptr < zptr) *sptr++ = *cptr++;
         if (sptr >= zptr)
         {
            ErrorGeneralOverflow (rqptr, FI_LI);
            UpdEnd (rqptr);
            return;
         }
         *sptr = '\0';
      }
      else
      if (strsame (FieldName, "do", -1))
      {
         /* primarily for use from HTADMIN module */
         cptr = FieldValue;
         zptr = (sptr = DoThis) + sizeof(DoThis);
         while (*cptr && sptr < zptr) *sptr++ = *cptr++;
         if (sptr >= zptr)
         {
            ErrorGeneralOverflow (rqptr, FI_LI);
            UpdEnd (rqptr);
            return;
         }
         *sptr = '\0';
      }
      else
      if (strsame (FieldName, "name", -1))
      {
         cptr = FieldValue;
         zptr = (sptr = Name) + sizeof(Name);
         while (*cptr && sptr < zptr) *sptr++ = *cptr++;
         if (sptr >= zptr)
         {
            ErrorGeneralOverflow (rqptr, FI_LI);
            UpdEnd (rqptr);
            return;
         }
         *sptr = '\0';
      }
      else
      if (strsame (FieldName, "protection", -1))
      {
         if (isxdigit(FieldValue[0]))
            tkptr->ProtectionMask = strtol (FieldValue, NULL, 16);
      }
      else
      if (to_lower(FieldName[0]) == 'd' && FieldName[1] == '-')
      {
         if (strsame (FieldName, "d-copy", -1))
            DoCopy = true;
         else
         if (strsame (FieldName, "d-dirprotect", -1))
            DoDirProtect = true;
         else
         if (strsame (FieldName, "d-fileprotect", -1))
            DoDirProtect = true;
         else
         if (strsame (FieldName, "d-filerename", -1))
            DoFileRename = true;
         else
         if (strsame (FieldName, "d-tree", -1))
            DoTree = true;
      }
      else
      if (to_lower(FieldName[0]) == 's' && FieldName[1] == '-')
      {
         if (strsame (FieldName, "s-copy", -1))
            SubmitCopy = true;
         else
#ifdef UPD_CREATE
         if (strsame (FieldName, "s-create", -1))
            SubmitCreate = true;
         else
#endif /* UPD_CREATE */
         if (strsame (FieldName, "s-delete", -1))
            SubmitDelete = true;
         else
         if (strsame (FieldName, "s-dirprotect", -1))
            SubmitDirProtect = true;
         else
         if (strsame (FieldName, "s-edit", -1))
            SubmitEdit = true;
         else
         if (strsame (FieldName, "s-fileprotect", -1))
            SubmitFileProtect = true;
         else
#ifdef UPD_FILTER
         if (strsame (FieldName, "s-filter", -1))
            SubmitFilter = true;
         else
#endif /* UPD_FILTER */
         if (strsame (FieldName, "s-goto", -1))
            SubmitGoto = true;
         else
         if (strsame (FieldName, "s-list", -1))
            SubmitList = true;
         else
         if (strsame (FieldName, "s-mkdir", -1))
            SubmitMkdir = true;
         else
         if (strsame (FieldName, "s-filerename", -1))
            SubmitFileRename = true;
         else
         if (strsame (FieldName, "s-rmdir", -1))
            SubmitRmdir = true;
         else
         if (strsame (FieldName, "s-tree", -1))
            SubmitTree = true;
         else
         if (strsame (FieldName, "s-view", -1))
            SubmitView = true;
         else
         {
            rqptr->rqResponse.HttpStatus = 400;
            ErrorGeneral (rqptr, MsgFor(rqptr,MSG_UPD_QUERY_FIELD), FI_LI);
            UpdEnd (rqptr);
            return;
         }
         strzcpy (SubmitButton, FieldName+2, sizeof(SubmitButton));
      }
      else
      if (strsame (FieldName, "virtual", -1))
      {
         /* just ignore (from Admin Menu) */
      }
      else
      {
         rqptr->rqResponse.HttpStatus = 403;
         ErrorGeneral (rqptr, MsgFor(rqptr,MSG_UPD_QUERY_FIELD), FI_LI);
         UpdEnd (rqptr);
         return;
      }
   }

   /**************************/
   /* end parse query string */
   /**************************/

   /* ensure something has been entered into the file name field */
   if (SAME2(Name, '.\0'))
   {
      NameIsPeriod = true;
      Name[0] = '\0';
   }
   else
      NameIsPeriod = false;

   if (SubmitDelete)
   {
      /***************/
      /* delete file */
      /***************/

      if (!Name[0] && !AsName[0])
      {
         rqptr->rqResponse.HttpStatus = 400;
         ErrorGeneral (rqptr, MsgFor(rqptr,MSG_UPD_FILENAME), FI_LI);
         UpdEnd (rqptr);
         return;
      }

      if (WatchThisOne)
         WatchThis (WATCHITM(rqptr), WATCH_RESPONSE,
                    "UPDATE !AZ !AZ!AZ",
                    SubmitButton, rqptr->rqHeader.PathInfoPtr,
                    AsName[0] ? AsName : Name);

      UpdConfirmDelete (rqptr, AsName[0] ? AsName : Name, "");
      return;
   }

   if (SubmitDirProtect || SubmitFileProtect)
   {
      /*********************/
      /* change protection */
      /*********************/

      if (!Name[0])
      {
         rqptr->rqResponse.HttpStatus = 400;
         ErrorGeneral (rqptr, MsgFor(rqptr,MSG_UPD_FILENAME), FI_LI);
         UpdEnd (rqptr);
         return;
      }

      if (WatchThisOne)
         WatchThis (WATCHITM(rqptr), WATCH_RESPONSE,
                    "UPDATE !AZ !AZ!AZ",
                    SubmitButton, rqptr->rqHeader.PathInfoPtr,
                    AsName[0] ? AsName : Name);

      if (SubmitDirProtect)
         UpdConfirmProtect (rqptr, AsName[0] ? AsName : Name, "/");
      else
         UpdConfirmProtect (rqptr, AsName[0] ? AsName : Name, "");
      return;
   }

   if (SubmitMkdir)
   {
      /***********************/
      /* create subdirectory */
      /***********************/

      if (!AsName[0])
      {
         rqptr->rqResponse.HttpStatus = 400;
         ErrorGeneral (rqptr, MsgFor(rqptr,MSG_UPD_DIRECTORY), FI_LI);
         UpdEnd (rqptr);
         return;
      }

      if (WatchThisOne)
         WatchThis (WATCHITM(rqptr), WATCH_RESPONSE,
                    "UPDATE !AZ !AZ!AZ",
                    SubmitButton, rqptr->rqHeader.PathInfoPtr, AsName);

      UpdConfirmMkdir (rqptr, AsName);
      return;
   }

   if (SubmitRmdir)
   {
      /***********************/
      /* delete subdirectory */
      /***********************/

      if (!Name[0])
      {
         rqptr->rqResponse.HttpStatus = 400;
         ErrorGeneral (rqptr, MsgFor(rqptr,MSG_UPD_DIRECTORY), FI_LI);
         UpdEnd (rqptr);
         return;
      }

      if (WatchThisOne)
         WatchThis (WATCHITM(rqptr), WATCH_RESPONSE,
                    "UPDATE !AZ !AZ!AZ",
                    SubmitButton, rqptr->rqHeader.PathInfoPtr, Name);

      UpdConfirmDelete (rqptr, Name, "/");
      return;
   }

   if (SubmitCopy ||
#ifdef UPD_CREATE
       SubmitCreate ||
#endif /* UPD_CREATE */
       SubmitDirProtect ||
       SubmitEdit ||
       SubmitFileProtect ||
#ifdef UPD_FILTER
       SubmitFilter ||
#endif /* UPD_FILTER */
       SubmitGoto ||
       SubmitList ||
       SubmitMkdir ||
       SubmitFileRename ||
       SubmitTree ||
       SubmitView)
   {
      /******************************/
      /* REDIRECT (via "Location:") */
      /******************************/

      if (SubmitCopy)
      {
         if (!Name[0])
         {
            rqptr->rqResponse.HttpStatus = 400;
            ErrorGeneral (rqptr, MsgFor(rqptr,MSG_UPD_SRC_FILENAME), FI_LI);
            UpdEnd (rqptr);
            return;
         }
         if (!AsName[0])
         {
            rqptr->rqResponse.HttpStatus = 400;
            ErrorGeneral (rqptr, MsgFor(rqptr,MSG_UPD_DST_FILENAME), FI_LI);
            UpdEnd (rqptr);
            return;
         }
         status = FaoToBuffer (Location, sizeof(Location), &Length,
                     "!&%AZ!&%AZ!&%AZ?d-copy=1&as=!&%AZ&protection=!4XL",
                     rqptr->ScriptName, rqptr->rqHeader.PathInfoPtr, Name,
                     AsName, tkptr->ProtectionMask);
      }
      else
#ifdef UPD_CREATE
      if (SubmitCreate)
      {
         if (!AsName[0])
         {
            rqptr->rqResponse.HttpStatus = 400;
            ErrorGeneral (rqptr, MsgFor(rqptr,MSG_UPD_FILENAME), FI_LI);
            UpdEnd (rqptr);
            return;
         }
         status = FaoToBuffer (Location, sizeof(Location), &Length,
                     "!&%AZ!&%AZ!&%AZ",
                     rqptr->ScriptName, rqptr->rqHeader.PathInfoPtr, AsName);
      }
      else
#endif /* UPD_CREATE */
      if (SubmitEdit)
      {
         if (!Name[0] && !AsName[0])
         {
            rqptr->rqResponse.HttpStatus = 400;
            ErrorGeneral (rqptr, MsgFor(rqptr,MSG_UPD_FILENAME), FI_LI);
            UpdEnd (rqptr);
            return;
         }
         status = FaoToBuffer (Location, sizeof(Location), &Length,
                               "!&%AZ!&%AZ!&%AZ!AZ!&%AZ?protection=!4XL",
                               rqptr->ScriptName, rqptr->rqHeader.PathInfoPtr,
                               AsName[0] && !Name[0] ? AsName : Name,
                               AsName[0] && Name[0] ? "?as=" : "",
                               AsName[0] && Name[0] ? AsName : "",
                               tkptr->ProtectionMask);
      }
      else
#ifdef UPD_FILTER
      if (SubmitFilter)
      {
         if (!Name[0])
         {
            rqptr->rqResponse.HttpStatus = 400;
            ErrorGeneral (rqptr, MsgFor(rqptr,MSG_UPD_FILENAME), FI_LI);
            UpdEnd (rqptr);
            return;
         }
         if (!AsName[0])
         {
            rqptr->rqResponse.HttpStatus = 400;
            ErrorGeneral (rqptr, MsgFor(rqptr,MSG_UPD_FILTER), FI_LI);
            UpdEnd (rqptr);
            return;
         }
         status = FaoToBuffer (Location, sizeof(Location), &Length,
                               "!AZ!&%AZ!&%AZ!&%AZ",
                               AsName[0] ? "/" : "",
                               AsName, rqptr->rqHeader.PathInfoPtr, AsName);
      }
      else
#endif /* UPD_FILTER */
      if (SubmitFileProtect)
      {
         if (!Name[0] && !AsName)
         {
            rqptr->rqResponse.HttpStatus = 400;
            ErrorGeneral (rqptr, MsgFor(rqptr,MSG_UPD_FILENAME), FI_LI);
            UpdEnd (rqptr);
            return;
         }
         status = FaoToBuffer (Location, sizeof(Location), &Length,
                     "!&%AZ!&%AZ!&%AZ?d-fileprotect=1&protection=!4XL",
                     rqptr->ScriptName, rqptr->rqHeader.PathInfoPtr,
                     AsName[0] ? AsName : Name,
                     tkptr->ProtectionMask);
      }
      else
      if (SubmitGoto)
      {
         if (!Name[0])
         {
            rqptr->rqResponse.HttpStatus = 400;
            ErrorGeneral (rqptr, MsgFor(rqptr,MSG_UPD_DIRECTORY), FI_LI);
            UpdEnd (rqptr);
            return;
         }
         if (MATCH2 (Name, ".."))
         {
            /* parent directory */
            sptr = (cptr = tkptr->PathInfoDir) + tkptr->PathInfoDirLength;
            while (sptr > cptr && *sptr != '/') sptr--;
            if (sptr > cptr && *sptr == '/') sptr--;
            while (sptr > cptr && *sptr != '/') sptr--;
            if (sptr > cptr && *sptr == '/') sptr++;
            if (sptr == cptr)
            {
               rqptr->rqResponse.HttpStatus = 400;
               rqptr->rqResponse.ErrorTextPtr = "../";
               rqptr->rqResponse.ErrorOtherTextPtr = "[-]";
               ErrorVmsStatus (rqptr, RMS$_DNF, FI_LI);
               UpdEnd (rqptr);
               return;
            }
            status = FaoToBuffer (Location, sizeof(Location), &Length,
                        "!&%AZ!#&%AZ?protection=!4XL",
                        rqptr->ScriptName,
                        sptr-cptr, cptr,
                        tkptr->ProtectionMask);
         }
         else
            status = FaoToBuffer (Location, sizeof(Location), &Length,
                        "!&%AZ!&%AZ!&%AZ/?protection=!4XL",
                        rqptr->ScriptName, rqptr->rqHeader.PathInfoPtr, Name,
                        tkptr->ProtectionMask);
      }
      else
      if (SubmitList)
      {
         if (!Name[0] && !NameIsPeriod)
         {
            rqptr->rqResponse.HttpStatus = 400;
            ErrorGeneral (rqptr, MsgFor(rqptr,MSG_UPD_DIRECTORY), FI_LI);
            UpdEnd (rqptr);
            return;
         }

         /* update directory listing layout including protection */
         if (!UpdLayout[0])
         {
            zptr = (sptr = UpdLayout) + sizeof(UpdLayout);
            for (cptr = "&layout="; *cptr; *sptr++ = *cptr++);

            if (Config.cfDir.DefaultLayout[0])
               cptr = Config.cfDir.DefaultLayout;
            else
               cptr = DEFAULT_DIR_LAYOUT;

            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')
               {
                  cptr++;
                  while (*cptr == '_') cptr++;
                  continue;
               }
               if (to_upper(*cptr) == 'S')
               {
                  if (sptr < zptr) *sptr++ = *cptr++;
                  if (*cptr == ':')
                  {
                     *sptr++ = *cptr++;
                     if (*cptr && sptr < zptr) *sptr++ = *cptr++;
                  }
                  if (sptr < zptr) *sptr++ = '_';
                  if (sptr < zptr) *sptr++ = '_';
                  if (sptr < zptr) *sptr++ = 'P';
                  while (*cptr == '_' && sptr < zptr) *sptr++ = *cptr++;
                  continue;
               }
               if (sptr < zptr) *sptr++ = *cptr++;
            }
            if (sptr >= zptr)
            {
               UpdLayout[0] = '\0';
               ErrorGeneralOverflow (rqptr, FI_LI);
               UpdEnd (rqptr);
               return;
            }
            *sptr = '\0';
         }

         status = FaoToBuffer (Location, sizeof(Location), &Length,
                     "!AZ//!&%AZ!&%AZ!AZ!&%AZ?httpd=index&expired=yes!AZ",
                     rqptr->ServicePtr->RequestSchemeNamePtr,
                     rqptr->rqHeader.PathInfoPtr, Name,
                     Name[0] ? "/" : "",
                     AsName[0] ? AsName : "*.*",
                     UpdLayout);
      }
      else
      if (SubmitMkdir)
      {
         if (!AsName[0])
         {
            rqptr->rqResponse.HttpStatus = 400;
            ErrorGeneral (rqptr, MsgFor(rqptr,MSG_UPD_DIRECTORY), FI_LI);
            UpdEnd (rqptr);
            return;
         }
         status = FaoToBuffer (Location, sizeof(Location), &Length,
                     "!&%AZ!&%AZ!&%AZ/?d-mkdir=1",
                     rqptr->ScriptName, rqptr->rqHeader.PathInfoPtr, AsName);
      }
      else
      if (SubmitFileRename)
      {
         if (!Name[0])
         {
            rqptr->rqResponse.HttpStatus = 400;
            ErrorGeneral (rqptr, MsgFor(rqptr,MSG_UPD_CUR_FILENAME), FI_LI);
            UpdEnd (rqptr);
            return;
         }
         if (!AsName[0])
         {
            rqptr->rqResponse.HttpStatus = 400;
            ErrorGeneral (rqptr, MsgFor(rqptr,MSG_UPD_NEW_FILENAME), FI_LI);
            UpdEnd (rqptr);
            return;
         }
         if (strsame (Name, AsName, -1))
         {
            ErrorGeneral (rqptr, MsgFor(rqptr,MSG_UPD_RENAME_SAME), FI_LI);
            UpdEnd (rqptr);
            return;
         }

         UpdConfirmRename (rqptr, Name, AsName);
         return;
      }
      else
      if (SubmitTree)
      {
         if (!Name[0] && !NameIsPeriod)
         {
            rqptr->rqResponse.HttpStatus = 400;
            ErrorGeneral (rqptr, MsgFor(rqptr,MSG_UPD_DIRECTORY), FI_LI);
            UpdEnd (rqptr);
            return;
         }
         status = FaoToBuffer (Location, sizeof(Location), &Length,
                               "!&%AZ!&%AZ!&%AZ!AZ?d-tree=1",
                               rqptr->ScriptName, rqptr->rqHeader.PathInfoPtr, 
                               Name, Name[0] ? "/" : "");
      }
      else
      if (SubmitView)
      {
         if (!Name[0])
         {
            rqptr->rqResponse.HttpStatus = 400;
            ErrorGeneral (rqptr, MsgFor(rqptr,MSG_UPD_FILENAME), FI_LI);
            UpdEnd (rqptr);
            return;
         }
         status = FaoToBuffer (Location, sizeof(Location), &Length,
                               "!AZ//!&%AZ!&%AZ",
                               rqptr->ServicePtr->RequestSchemeNamePtr,
                               rqptr->rqHeader.PathInfoPtr, Name);
      }
      else
      {
         rqptr->rqResponse.HttpStatus = 400;
         ErrorGeneral (rqptr, MsgFor(rqptr,MSG_UPD_ACTION), FI_LI);
         UpdEnd (rqptr);
         return;
      }

      if (VMSnok (status) || status == SS$_BUFFEROVF)
      {
         ErrorNoticed (rqptr, status, NULL, FI_LI);
         rqptr->rqResponse.ErrorTextPtr = "FaoToBuffer()";
         ErrorVmsStatus (rqptr, status, FI_LI);
         UpdEnd (rqptr);
         return;
      }

      ResponseLocation (rqptr, Location, Length);

      if (WatchThisOne)
         WatchThis (WATCHITM(rqptr), WATCH_RESPONSE,
                    "UPDATE !AZ !AZ", SubmitButton, Location);

      UpdEnd (rqptr);
      return;
   }

   /********************************/
   /* navigate, edit, copy, rename */
   /********************************/

   if (DoTree)
   {
      /********/
      /* tree */
      /********/

      if (WatchThisOne)
         WatchThis (WATCHITM(rqptr), WATCH_RESPONSE,
                    "UPDATE treeof !AZ", rqptr->ParseOds.ExpFileName);

      /* remove the query string that got us here */
      rqptr->rqHeader.QueryStringPtr = NULL;
      rqptr->rqHeader.QueryStringLength = 0;

      rqptr->rqResponse.PreExpired = PRE_EXPIRE_UPD_TREE_OF;
      tkptr->UpdateTree = true;
      UpdTreeBegin (rqptr);
      return;
   }

   if (DoFileProtect || DoDirProtect)
   {
      /*********************/
      /* change protection */
      /*********************/

      /* this function supplies it's own watch response item */
      UpdProtection (rqptr);
      return;
   }

   if (DoFileRename)
   {
      /***************/
      /* file rename */
      /***************/

      /* this function supplies it's own watch response item */
      UpdFileRename (rqptr, Name, AsName);
      return;
   }

   if (DoCopy)
   {
      /********/
      /* copy */
      /********/

      /* this function supplies it's own watch response item */
      UpdCopyFileBegin (rqptr, AsName);
      return;
   }

   /********************/
   /* protection lists */
   /********************/

   strzcpy (Scratch, MsgFor (rqptr, MSG_UPD_PROTECTION_LIST), sizeof(Scratch));
   cptr = Scratch;

   vecptr = FaoVector;
   *vecptr++ = "<select";
   if (tkptr->ProtectionMask == 0xaa00) *vecptr++ = " selected"; else *vecptr++ = "";
   *vecptr++ = cptr;
   while (*cptr && *cptr != '|') cptr++;
   if (*cptr) *cptr++ = '\0';
   if (tkptr->ProtectionMask == 0xfa00) *vecptr++ = " selected"; else *vecptr++ = "";
   *vecptr++ = cptr;
   while (*cptr && *cptr != '|') cptr++;
   if (*cptr) *cptr++ = '\0';
   if (tkptr->ProtectionMask == 0xff00) *vecptr++ = " selected"; else *vecptr++ = "";
   *vecptr++ = cptr;

   status = FaolToBuffer (tkptr->ProtectionList,
                          sizeof(tkptr->ProtectionList), 0,
                          UpdProtectionListFao, &FaoVector);
   if (VMSnok (status)) ErrorNoticed (rqptr, status, NULL, FI_LI);

   /* bang an id into this other list */
   vecptr = FaoVector;
   *vecptr++ = "<select id=\"fileprot\"";

   status = FaolToBuffer (tkptr->FileProtectionList,
                          sizeof(tkptr->FileProtectionList), 0,
                          UpdProtectionListFao, &FaoVector);
   if (VMSnok (status)) ErrorNoticed (rqptr, status, NULL, FI_LI);

   /**********************/
   /* what are we doing? */
   /**********************/

   if (strsame (rqptr->rqHeader.PathInfoPtr,
                ADMIN_REVISE, sizeof(ADMIN_REVISE)-1))
      Navigate = false;
   else
   if (strsame (rqptr->rqHeader.PathInfoPtr,
                ADMIN_VS_REVISE, sizeof(ADMIN_VS_REVISE)-1))
      Navigate = false;
   else
   if (rqptr->ParseOds.Nam_fnb & NAM$M_WILDCARD)
      Navigate = true;
   else
   if (rqptr->ParseOds.NamNameLength ||
       rqptr->ParseOds.NamTypeLength ||
       rqptr->ParseOds.NamVersionLength)
      Navigate = false;
   else
      Navigate = true;

   if (Navigate)
   {
      /******************/
      /* let's navigate */
      /******************/

      /* this function supplies it's own watch response item */
      UpdNavigateBegin (rqptr);
      return;
   }

   /*****************/
   /* edit the file */
   /*****************/

   /* this function supplies it's own watch response item */
   UpdEditFileBegin (rqptr, NULL, AsName);
}

/*****************************************************************************/
/*
All requests processed by the UPD.C module should call this function at the
end of processing.
*/ 

void UpdEnd (REQUEST_STRUCT *rqptr)

{
   int  status;
   UPD_TASK  *tkptr;

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

   if (WATCHMOD (rqptr, WATCH_MOD_UPD))
      WatchThis (WATCHITM(rqptr), WATCH_MOD_UPD, "UpdEnd()");

   tkptr = rqptr->UpdTaskPtr;

   if (tkptr->FileOds.Fab.fab$w_ifi) OdsClose (&tkptr->FileOds, NULL, rqptr);

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

   /* indicate the task has concluded (allocated memory freed with request) */
   rqptr->UpdTaskPtr = NULL;

   if (rqptr->HTAdminTaskPtr)
      HTAdminEnd (rqptr);
   else
      SysDclAst (tkptr->NextTaskFunction, rqptr);
}

/*****************************************************************************/
/*
All requests processed by the UPD.C module should call this function at the
end of processing.
*/ 

char* UpdSetClicks (REQUEST_STRUCT *rqptr)

{
   static char  SetClicks [] =
"function setClicks ()\n\
{\n\
   if (!document.getElementById('ender')) {\n\
      setTimeout(setClicks,50);\n\
      return;\n\
   }\n\
\
   var clk = document.getElementById('doDirList');\n\
   if (clk) clk.addEventListener('click',dirList);\n\
   clk = document.getElementById('doDirTree');\n\
   if (clk) clk.addEventListener('click',dirTree);\n\
   clk = document.getElementById('doReload');\n\
   if (clk) clk.addEventListener('click',doReload);\n\
   clk = document.getElementById('doFileView');\n\
   if (clk) clk.addEventListener('click',fileView);\n\
   clk = document.getElementById('doFileEdit');\n\
   if (clk) clk.addEventListener('click',fileEdit);\n\
\
   var sel = document.getElementById('dirName');\n\
   if (sel) {\n\
      for (var cnt = 0; cnt < sel.options.length; cnt++) {\n\
         sel[cnt].addEventListener('dblclick',dirDblClick);\n\
      }\n\
   }\n\
\
   var sel = document.getElementById('fileName');\n\
   if (sel) {\n\
      for (var cnt = 0; cnt < sel.options.length; cnt++) {\n\
         sel[cnt].addEventListener('dblclick',fileDblClick);\n\
      }\n\
   }\n\
}\n\
\
function doReload ()\n\
{\n\
   location.reload(true);\n\
   return false;\n\
}\n\
\
setClicks();\n";

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

   if (WATCHMOD (rqptr, WATCH_MOD_UPD))
      WatchThis (WATCHITM(rqptr), WATCH_MOD_UPD, "UpdSetClicks()");

   return (SetClicks);
}

/*****************************************************************************/
/*
Begin by setting up a search for all directories.
*/

void UpdNavigateBegin (REQUEST_STRUCT *rqptr)

{
   static char  ResponseFao [] =
"!AZ\
<html>\n\
<head>\n\
!AZ\
!AZ\
!&@\
<title>!AZ !AZ</title>\n\
<style type=\"text/css\">\n\
input[type=submit], input[type=reset] { min-width:6em; }\n\
</style>\n\
\
<script nonce=\"!AZ\">\n\
\
function dirDblClick() {\n\
   document.getElementById('s-goto').click();\n\
}\n\
\
function fileDblClick() {\n\
   document.getElementById('s-view-open').click();\n\
}\n\
\
function theName(what) {\n\
   var name = document.getElementById(what+'as').value;\n\
   var id = document.getElementById(what+'Name');\n\
   id = id.options[id.selectedIndex];\n\
   if (id && !!name.length) name = id.value;\n\
   if (name.length) return escape(name);\n\
   return null;\n\
}\n\
\
function dirList() {\n\
   event.preventDefault();\n\
   if (name = theName('dir'))\n\
      window.open('!AZ?name='+name+'&s-list=List','_blank');\n\
   else\n\
      alert('!AZ');\n\
}\n\
\
function dirTree() {\n\
   event.preventDefault();\n\
   if (name = theName('dir'))\n\
      window.open('!AZ?name='+name+'&s-tree=Tree','_blank');\n\
   else\n\
      alert('!AZ');\n\
}\n\
\
function fileEdit() {\n\
   event.preventDefault();\n\
   var as = document.getElementById('fileas').value;\n\
   var prot = document.getElementById('fileprot');\n\
   prot = prot.options[prot.selectedIndex];\n\
   if (prot) prot=prot.value; else prot = '';\n\
   var id = document.getElementById('fileName');\n\
   id = id.options[id.selectedIndex];\n\
   if (id) var name = id.value; else var name = as;\n\
   if (name.length)\n\
      window.open('!AZ?name='+escape(name)+'&as='+escape(as)+'\
&protection='+escape(prot)+'&s-edit=Edit','_blank');\n\
   else\n\
      alert('!AZ');\n\
}\n\
\
function fileView() {\n\
   event.preventDefault();\n\
   if (name = theName('file'))\n\
      window.open('!AZ'+name,'_blank');\n\
   else\n\
      alert('!AZ');\n\
}\n\
\
!AZ\
\
</script>\n\
\
</head>\n\
!AZ\n\
<div class=\"wasd\">\n\
<h2>!AZ !AZ</h2>\n\
\
<p><table class=\"ctgry\">\n\
\
<tr><th class=\"ctttl\" style=\"font-size:125%;\" colspan=\"2\">\n\
<a href=\"!&%AZ\">!&;&[AZ</a>\n\
!AZ\n\
<a class=\"abttn\" target=\"_blank\" href=\"!&;AZ\" \
style=\"margin-left:2em;\">!&;AZ</a>\n\
</th></tr>\n\
\
<tr>\n\
\
<td style=\"padding:0.5em 0 0.5em 1em;\">\n\
\
<form id=\"navForm\" METHOD=\"GET\" action=\"!&%AZ!&%AZ\">\n\
<table class=\"lftlft\">\n\
\
<tr><th class=\"sbttl\" colspan=\"2\">!AZ</th></tr>\n\
\
<tr><td>\n\
<select size=\"!UL\" id=\"dirName\" name=\"name\" \
style=\"min-width:20em;max-width:40em;\">\n\
<option value=\".\" selected>&nbsp;./\n\
!AZ";

   int  status;
   unsigned short  Length;
   unsigned long  FaoVector [32];
   unsigned long  *vecptr;
   char  *cptr, *sptr, *zptr;
   char  LocationFile [ODS_MAX_FILE_NAME_LENGTH+1],
         LocationUpd [ODS_MAX_FILE_NAME_LENGTH+1],
         Scratch [256];
   REQUEST_AST  AstFunction;
   UPD_TASK  *tkptr;

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

   if (WATCHMOD (rqptr, WATCH_MOD_UPD))
      WatchThis (WATCHITM(rqptr), WATCH_MOD_UPD, "UpdNavigateBegin()");

   tkptr = rqptr->UpdTaskPtr;

   if (WATCHPNT(rqptr) &&
       WATCH_CATEGORY(WATCH_RESPONSE))
      WatchThis (WATCHITM(rqptr), WATCH_RESPONSE,
                 "UPDATE navigate !AZ", rqptr->ParseOds.ExpFileName);

   /************/
   /* navigate */
   /************/

   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 (rqptr->ParseOds.ExpFileName, DirHiddenFileName)))
         status = RMS$_DNF;
      else
      if ((Config.cfDir.AccessSelective ||
           rqptr->rqPathSet.DirAccessSelective) &&
           !rqptr->rqPathSet.DirAccess)
         status = OdsFileExists (rqptr->ParseOds.ExpFileName,
                                 DirBrowsableFileName);
      else
      if (VMSok (status =
          OdsFileExists (rqptr->ParseOds.ExpFileName, DirNoWildFileName)))
         status = RMS$_WLD;
      else
         status = SS$_NORMAL;

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

   if (VMSnok (status))
   {
      /* give some "disinformation"  ;^)  */
      if (status == RMS$_FNF) status = RMS$_DNF;
      rqptr->rqResponse.ErrorTextPtr = rqptr->rqHeader.PathInfoPtr;
      rqptr->rqResponse.ErrorOtherTextPtr = rqptr->ParseOds.ExpFileName;
      ErrorVmsStatus (rqptr, status, FI_LI);
      UpdEnd (rqptr);
      return;
   }

   /* check if we can see its subdirectories, if not then straight to files */
   sys$setprv (1, &SysPrvMask, 0, 0);

   if (VMSnok (status =
       OdsFileExists (rqptr->ParseOds.ExpFileName, DirNosFileName)))
      status = OdsFileExists (rqptr->ParseOds.ExpFileName, DirNopsFileName);

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

   if (VMSnok(status))
   {
      AstFunction = &UpdNavigateSearchDirs;

      /***************************/
      /* set up directory search */
      /***************************/

      AuthAccessEnable (rqptr, rqptr->ParseOds.ExpFileName, AUTH_ACCESS_READ);

      OdsParse (&tkptr->SearchOds,
                rqptr->ParseOds.ExpFileName,
                rqptr->ParseOds.NamNamePtr - rqptr->ParseOds.ExpFileName,
                "*.DIR;", 6, 0, NULL, rqptr);

      AuthAccessEnable (rqptr, 0, 0);

      if (VMSnok (status = tkptr->SearchOds.Fab.fab$l_sts))
      {
         rqptr->rqResponse.ErrorTextPtr = rqptr->rqHeader.PathInfoPtr;
         rqptr->rqResponse.ErrorOtherTextPtr = rqptr->ParseOds.ExpFileName;
         ErrorVmsStatus (rqptr, status, FI_LI);
         UpdEnd (rqptr);
         return;
      }
   }
   else
      AstFunction = &UpdNavigateBeginFiles;

   /**************/
   /* begin page */
   /**************/

   cptr = MsgFor(rqptr,MSG_UPD_NAVIGATE);
   zptr = (sptr = tkptr->MsgString) + sizeof(tkptr->MsgString);
   while (*cptr && sptr < zptr)
   {
      /* don't need any extraneous linefeeds from this long string */
      if (*cptr == '\n') cptr++; else *sptr++ = *cptr++;
   }
   if (sptr >= zptr)
   {
      ErrorGeneralOverflow (rqptr, FI_LI);
      UpdEnd (rqptr);
      return;
   }
   *sptr = '\0';

   FaoToBuffer (LocationFile, sizeof(LocationFile), 0, "!AZ//!AZ!&%AZ",
                rqptr->ServicePtr->RequestSchemeNamePtr,
                rqptr->ServicePtr->ServerHostPort,
                rqptr->rqHeader.PathInfoPtr);

   FaoToBuffer (LocationUpd, sizeof(LocationUpd), 0, "!AZ//!AZ!AZ!&%AZ",
                rqptr->ServicePtr->RequestSchemeNamePtr,
                rqptr->ServicePtr->ServerHostPort,
                rqptr->ScriptName, 
                rqptr->rqHeader.PathInfoPtr);

   /* ensure CSP nonce is generated before header */
   GenerateCspNonce (rqptr);

   rqptr->rqResponse.PreExpired = PRE_EXPIRE_UPD;
   ResponseHeader200 (rqptr, "text/html", NULL);

   vecptr = FaoVector;

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

   /* "Update" */
   *vecptr++ = cptr = tkptr->MsgString;
   *vecptr++ = rqptr->ServicePtr->ServerHostPort;
   *vecptr++ = GenerateCspNonce (rqptr);

   *vecptr++ = LocationUpd;
   *vecptr++ = MsgFor(rqptr,MSG_UPD_FILENAME);
   *vecptr++ = LocationUpd;
   *vecptr++ = MsgFor(rqptr,MSG_UPD_FILENAME);
   *vecptr++ = LocationUpd;
   *vecptr++ = MsgFor(rqptr,MSG_UPD_FILENAME);
   *vecptr++ = LocationFile;
   *vecptr++ = MsgFor(rqptr,MSG_UPD_FILENAME);

   *vecptr++ = UpdSetClicks (rqptr);

   *vecptr++ = ADMIN_BODY_TAG;

   /* "Update" again */
   *vecptr++ = cptr;
   while (*cptr && *cptr != '|') cptr++;
   if (*cptr) *cptr++ = '\0';
   *vecptr++ = rqptr->ServicePtr->ServerHostPort;

   *vecptr++ = rqptr->rqHeader.PathInfoPtr;
   *vecptr++ = rqptr->PathOds;
   *vecptr++ = rqptr->rqHeader.PathInfoPtr;

#ifdef ODS_EXTENDED
   if (OdsExtended && (!rqptr->PathOds || rqptr->PathOds == MAPURL_PATH_ODS_2))
      *vecptr++ = "&nbsp;&nbsp;&nbsp;ODS-2";
   else
   if (OdsExtended && rqptr->PathOds == MAPURL_PATH_ODS_5)
      *vecptr++ = "&nbsp;&nbsp;&nbsp;ODS-5";
   else
#endif /* ODS_EXTENDED */
   if (rqptr->PathOds == MAPURL_PATH_ODS_ADS)
      *vecptr++ = "&nbsp;&nbsp;&nbsp;ODS-ADS";
   else
   if (rqptr->PathOds == MAPURL_PATH_ODS_PWK)
      *vecptr++ = "&nbsp;&nbsp;&nbsp;ODS-PWK";
   else
   if (rqptr->PathOds == MAPURL_PATH_ODS_SMB)
      *vecptr++ = "&nbsp;&nbsp;&nbsp;ODS-SMB";
   else
   if (rqptr->PathOds == MAPURL_PATH_ODS_SRI)
      *vecptr++ = "&nbsp;&nbsp;&nbsp;ODS-SRI";
   else
   if (rqptr->PathOds)
      *vecptr++ = "ODS-?";
   else
      *vecptr++ = "";

   *vecptr++ = UPD_HELP_PATH;

   /* ["help"] */
   *vecptr++ = cptr;
   while (*cptr && *cptr != '|') cptr++;
   if (*cptr) *cptr++ = '\0';

   /* form action */
   *vecptr++ = rqptr->ScriptName;
   *vecptr++ = tkptr->PathInfoDir;

   /* "subdirectories" title */
   *vecptr++ = cptr;
   while (*cptr && *cptr != '|') cptr++;
   if (*cptr) *cptr++ = '\0';

   /* save the current position for use in later parts of the page */
   tkptr->MsgStringPtr = cptr;

   *vecptr++ = NAVIGATE_SIZE;

   /* if there is a parent directory */
   sptr = (cptr = tkptr->PathInfoDir) + tkptr->PathInfoDirLength;
   while (sptr > cptr && *sptr != '/') sptr--;
   if (sptr > cptr && *sptr == '/') sptr--;
   while (sptr > cptr && *sptr != '/') sptr--;
   if (sptr > cptr && *sptr == '/') sptr++;
   if (sptr == cptr)
      *vecptr++ = "";
   else
      *vecptr++ = "<option value=\"..\">../\n";

   status = FaolToNet (rqptr, ResponseFao, &FaoVector);

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

   tkptr->FileCount = 0;

   NetWritePartFlush (rqptr, AstFunction);
}

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

UpdNavigateSearchDirs (REQUEST_STRUCT *rqptr)

{
   UPD_TASK  *tkptr;

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

   if (WATCHMOD (rqptr, WATCH_MOD_UPD))
      WatchThis (WATCHITM(rqptr), WATCH_MOD_UPD,
                 "UpdNavigateSearchDirs() !&F", &UpdNavigateSearchDirs);

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

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

   OdsSearch (&tkptr->SearchOds, &UpdNavigateDirs, 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!).
*/ 

void UpdNavigateDirs (REQUEST_STRUCT  *rqptr)

{
   int  status;
   unsigned long  FaoVector [8];
   unsigned long  *vecptr;
   char  ch;
   char  *cptr, *sptr;
   UPD_TASK  *tkptr;

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

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

   if (WATCHMOD (rqptr, WATCH_MOD_UPD))
      WatchThis (WATCHITM(rqptr), WATCH_MOD_UPD,
                 "UpdNavigateDirs() !&F sts:!&X stv:!&X",
                 &UpdNavigateDirs,
                 rqptr->UpdTaskPtr->SearchOds.Fab.fab$l_sts,
                 rqptr->UpdTaskPtr->SearchOds.Fab.fab$l_stv);

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

   tkptr = rqptr->UpdTaskPtr;

   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, list files */
         tkptr->SearchOds.ParseInUse = false;
         UpdNavigateBeginFiles (rqptr);
         return;
      }

      /* sys$search() error */
      rqptr->rqResponse.ErrorTextPtr = rqptr->rqHeader.PathInfoPtr;
      rqptr->rqResponse.ErrorOtherTextPtr = tkptr->SearchOds.NamDevicePtr;
      ErrorVmsStatus (rqptr, status, FI_LI);
      UpdEnd (rqptr);
      return;
   }

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

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

   /* not interested in master file directories :^) */
   if (MATCH7 (tkptr->SearchOds.NamNamePtr, "000000."))
   {
      UpdNavigateSearchDirs (rqptr);
      return;
   }

   tkptr->SearchOds.NamNamePtr[-1] = '.';
   tkptr->SearchOds.NamTypePtr[0] = ']';
   ch = tkptr->SearchOds.NamTypePtr[1];
   tkptr->SearchOds.NamTypePtr[1] = '\0';

   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->SearchOds.ResFileName, DirHiddenFileName)))
         status = RMS$_DNF;
      else
      if ((Config.cfDir.AccessSelective ||
           rqptr->rqPathSet.DirAccessSelective) &&
           !rqptr->rqPathSet.DirAccess)
         status = OdsFileExists (tkptr->SearchOds.ResFileName,
                                 DirBrowsableFileName);
      else
         status = SS$_NORMAL;

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

   tkptr->SearchOds.NamNamePtr[-1] = ']';
   tkptr->SearchOds.NamTypePtr[0] = '.';
   tkptr->SearchOds.NamTypePtr[1] = ch;

   if (VMSnok (status))
   {
      /* no, it shouldn't/couldn't be accessed */
      UpdNavigateSearchDirs (rqptr);
      return;
   }

   tkptr->FileCount++;

   tkptr->SearchOds.NamTypePtr[0] = '\0';

   vecptr = FaoVector;
   *vecptr++ = rqptr->PathOds;
   *vecptr++ = tkptr->SearchOds.NamNamePtr,
   *vecptr++ = rqptr->PathOds;
   *vecptr++ = tkptr->SearchOds.NamNamePtr,

   status = FaolToNet (rqptr, "<option value=\"!&;&[AZ\">!&;&[AZ\n",
                       &FaoVector);
   if (VMSnok (status)) ErrorNoticed (rqptr, status, NULL, FI_LI);

   tkptr->SearchOds.NamTypePtr[0] = '.';

   NetWritePartFlush (rqptr, &UpdNavigateSearchDirs);
}

/*****************************************************************************/
/*
End directory search.  Begin file search.
*/

void UpdNavigateBeginFiles (REQUEST_STRUCT *rqptr)

{
static char EndSelectFao [] = 

"</select>\n\
<div style=\"padding:0.3em;\">\n\
<sup>1.</sup> <i>!AZ</i>\n\
<br><sup>2.</sup> <i>!AZ</i>\n\
</div>\n\
<input type=\"text\" id=\"diras\" name=\"as\" size=\"20\">\n\
<br>!AZ\
<br><input type=reset value=\"!AZ\">\n\
</td><td>\n\
<table class=\"lftlft\">\n\
<tr>\
<td><input type=\"submit\" name=\"s-goto\" id=\"s-goto\" value=\"!AZ\"></td>\
<td><sup>1.</sup></td></tr>\n\
<tr><td>\
<input type=\"submit\" name=\"s-list\" value=\"!AZ\" \
style=\"margin-right:0;\">\
<input id=\"doDirList\" type=\"submit\" style=\"min-width:1em;\" value=\"^\" \
style=\"margin-left:0;\">\
</td>\
<td><sup>1.</sup></td></tr>\n\
<tr><td>\
<input type=\"submit\" name=\"s-tree\" value=\"!AZ\" \
style=\"margin-right:0;\">\
<input id=\"doDirTree\" type=\"submit\" style=\"min-width:1em;\" \
name=\"s-tree^\" value=\"^\" style=\"margin-left:0;\">\
</td>\
<td><sup>1.</sup></td></tr>\n\
<tr><td>\
<input type=\"submit\" name=\"s-mkdir\" value=\"!AZ\"></td>\
<td><sup>2.</sup></td></tr>\n\
<tr>\
<td><input type=\"submit\" name=\"s-dirprotect\" value=\"!AZ\"></td>\
<td><sup>1./2.</sup></td></tr>\n\
<tr><td>\
<input type=\"submit\" name=\"s-rmdir\" value=\"!AZ\"></td>\
<td><sup>1.</sup></td></tr>\n\
<tr><td><input id=\"doReload\" type=\"submit\" value=\"!AZ\"></td>\
<td style=\"font-size:170%;position:relative;\
top:-0.3em;left:-0.2em;\">!AZ</td></tr>\n\
</table>\n\
</td></tr>\n\
</table>\n\
</form>\n\
</td>\n\
\
<td style=\"padding:0.5em 0 0.5em 1em;\">\n\
\
<form method=\"GET\" action=\"!&%AZ!&%AZ\">\n\
<table class=\"lftlft\">\n\
\
<tr><th class=\"sbttl\" colspan=\"2\">!AZ</th></tr>\n\
\
<tr><td>\n\
<select size=\"!UL\" id=\"fileName\" name=\"name\" \
style=\"min-width:20em;max-width:40em;\">\n";

   int  status;
   unsigned long  FaoVector [32];
   unsigned long  *vecptr;
   char  *cptr, *sptr, *zptr,
         *MsgFilesPtr;
   UPD_TASK  *tkptr;

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

   if (WATCHMOD (rqptr, WATCH_MOD_UPD))
      WatchThis (WATCHITM(rqptr), WATCH_MOD_UPD, "UpdNavigateBeginFiles()");

   tkptr = rqptr->UpdTaskPtr;

   AuthAccessEnable (rqptr, rqptr->ParseOds.ExpFileName, AUTH_ACCESS_READ);

   OdsParse (&tkptr->SearchOds,
             rqptr->ParseOds.ExpFileName,
             rqptr->ParseOds.NamVersionPtr - rqptr->ParseOds.ExpFileName,
             "*.*;0", 5, 0, NULL, rqptr);

   AuthAccessEnable (rqptr, 0, 0);

   if (VMSnok (status = tkptr->SearchOds.Fab.fab$l_sts))
   {
      rqptr->rqResponse.ErrorTextPtr = rqptr->rqHeader.PathInfoPtr;
      rqptr->rqResponse.ErrorOtherTextPtr = rqptr->ParseOds.ExpFileName;
      ErrorVmsStatus (rqptr, status, FI_LI);
      UpdEnd (rqptr);
      return;
   }

   /**************/
   /* begin page */
   /**************/

   vecptr = FaoVector;

   /* retrieve the previously saved position in the string */
   cptr = tkptr->MsgStringPtr;

   /* "files" (for historical reasons is here) */
   MsgFilesPtr = cptr;
   while (*cptr && *cptr != '|') cptr++;
   if (*cptr) *cptr++ = '\0';

   if (tkptr->FileCount)
   {
      /* "select from list" */
      *vecptr++ = cptr;
      while (*cptr && *cptr != '|') cptr++;
      if (*cptr) *cptr++ = '\0';
      /* skip over "none available" */
      while (*cptr && *cptr != '|') cptr++;
      if (*cptr) cptr++;
   }
   else
   {
      /* skip over "select from list" */
      while (*cptr && *cptr != '|') cptr++;
      if (*cptr) cptr++;
      /* "none available" */
      *vecptr++ = cptr;
      while (*cptr && *cptr != '|') cptr++;
      if (*cptr) *cptr++ = '\0';
   }

   /* "enter name" */
   *vecptr++ = cptr;
   while (*cptr && *cptr != '|') cptr++;
   if (*cptr) *cptr++ = '\0';

   /* file protection */
   *vecptr++ = tkptr->ProtectionList;

   /* reset */
   *vecptr++ = cptr;
   while (*cptr && *cptr != '|') cptr++;
   if (*cptr) *cptr++ = '\0';

   /* goto */
   *vecptr++ = cptr;
   while (*cptr && *cptr != '|') cptr++;
   if (*cptr) *cptr++ = '\0';

   /* list */
   *vecptr++ = cptr;
   while (*cptr && *cptr != '|') cptr++;
   if (*cptr) *cptr++ = '\0';

   /* tree */
   *vecptr++ = cptr;
   while (*cptr && *cptr != '|') cptr++;
   if (*cptr) *cptr++ = '\0';

   /* create */
   *vecptr++ = cptr;
   while (*cptr && *cptr != '|') cptr++;
   if (*cptr) *cptr++ = '\0';

   /* protection */
   *vecptr++ = cptr;
   while (*cptr && *cptr != '|') cptr++;
   if (*cptr) *cptr++ = '\0';

   /* delete */
   *vecptr++ = cptr;
   while (*cptr && *cptr != '|') cptr++;
   if (*cptr) *cptr++ = '\0';

   /* save the current position for use in later parts of the page */
   tkptr->MsgStringPtr = cptr;

   *vecptr++ = REFRESH_SYMBOL;
   *vecptr++ = REFRESH_SYMBOL;

   /* form action */
   *vecptr++ = rqptr->ScriptName;
   *vecptr++ = tkptr->PathInfoDir;

   /* "files" title */
   *vecptr++ = MsgFilesPtr;

   *vecptr++ = NAVIGATE_SIZE;

   status = FaolToNet (rqptr, EndSelectFao, &FaoVector);

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

   tkptr->FileCount = 0;

   NetWritePartFlush (rqptr, &UpdNavigateSearchFiles);
}

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

void UpdNavigateSearchFiles (REQUEST_STRUCT *rqptr)

{
   UPD_TASK  *tkptr;

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

   if (WATCHMOD (rqptr, WATCH_MOD_UPD))
      WatchThis (WATCHITM(rqptr), WATCH_MOD_UPD,
                 "UpdNavigateSearchFiles() !&F", &UpdNavigateSearchFiles);

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

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

   OdsSearch (&tkptr->SearchOds, &UpdNavigateFiles, 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!).
*/ 

UpdNavigateFiles (REQUEST_STRUCT *rqptr)

{
   int  status;
   unsigned long  FaoVector [8];
   unsigned long  *vecptr;
   char  *cptr, *sptr;
   UPD_TASK  *tkptr;

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

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

   if (WATCHMOD (rqptr, WATCH_MOD_UPD))
      WatchThis (WATCHITM(rqptr), WATCH_MOD_UPD,
                 "UpdNavigateFiles() !&F sts:!&X stv:!&X",
                 &UpdNavigateFiles,
                 rqptr->UpdTaskPtr->SearchOds.Fab.fab$l_sts,
                 rqptr->UpdTaskPtr->SearchOds.Fab.fab$l_stv);

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

   tkptr = rqptr->UpdTaskPtr;

   if (VMSnok (status = tkptr->SearchOds.Fab.fab$l_sts))
   {
      if (status == RMS$_FNF || status == RMS$_NMF)
      {
         /* end of file search */
         tkptr->SearchOds.ParseInUse = false;
         UpdNavigateEnd (rqptr);
         return;
      }

      /* sys$search() error */
      rqptr->rqResponse.ErrorTextPtr = rqptr->rqHeader.PathInfoPtr;
      rqptr->rqResponse.ErrorOtherTextPtr = tkptr->SearchOds.NamDevicePtr;
      ErrorVmsStatus (rqptr, status, FI_LI);
      UpdEnd (rqptr);
      return;
   }

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

   if (MATCH5 (tkptr->SearchOds.NamTypePtr, ".DIR;") ||
       MATCH5 (tkptr->SearchOds.NamTypePtr, ".dir;"))
   {
      /* it has a dot-dir type but is it really a directory file? */
      status = OdsReallyADir (rqptr, &tkptr->SearchOds);
      if (VMSok (status))
      {
         /* already have directories */
         UpdNavigateSearchFiles (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 (!(CliDemo ||
            rqptr->rqAuth.SkelKeyAuthenticated ||
            rqptr->rqAuth.VmsUserProfileLength))
      {
         if (WATCHMOD (rqptr, WATCH_MOD_UPD))
            WatchThis (WATCHITM(rqptr), WATCH_MOD_UPD, ".FILE (hidden)");
         UpdNavigateSearchFiles (rqptr);
         return;
      }
   }

   /* specially check permissions for each file! */
   status = AuthAccessReadCheck (rqptr, tkptr->SearchOds.ResFileName,
                                        tkptr->SearchOds.ResFileNameLength);
   if (VMSnok (status))
   {
      if (status == SS$_NOPRIV)
      {
         /* does not have access */
         UpdNavigateSearchFiles (rqptr);
         return;
      }
      /* error reported by access check */
      UpdEnd (rqptr);
      return;
   }

   tkptr->FileCount++;

   tkptr->SearchOds.NamVersionPtr[0] = '\0';

   vecptr = FaoVector;
   *vecptr++ = rqptr->PathOds;
   *vecptr++ = tkptr->SearchOds.NamNamePtr,
   *vecptr++ = rqptr->PathOds;
   *vecptr++ = tkptr->SearchOds.NamNamePtr,

   status = FaolToNet (rqptr, "<option value=\"!&;&[AZ\">!&;&[AZ\n",
                       &FaoVector);
   if (VMSnok (status)) ErrorNoticed (rqptr, status, NULL, FI_LI);

   tkptr->SearchOds.NamVersionPtr[0] = ';';

   NetWritePartFlush (rqptr, &UpdNavigateSearchFiles);
}

/*****************************************************************************/
/*
End file search and end of navigation functions.
*/

void UpdNavigateEnd (REQUEST_STRUCT *rqptr)

{
   static char  EndSelectFao [] =
"</select>\n\
<div style=\"padding:0.3em;\">\n\
<sup>1.</sup> <i>!AZ</i>\n\
<br><sup>2.</sup> <i>!AZ</i>\n\
</div>\n\
<input type=\"text\" id=\"fileas\" name=\"as\" size=\"20\">\n\
<br>!AZ\
<br><input type=reset value=\"!AZ\">\n\
</td><td>\n\
<table class=\"lftlft\">\n\
<tr><td>\
<input type=\"submit\" name=\"s-view\" id=\"s-view\" \
value=\"!AZ\" style=\"margin-right:0;\">\
<input type=\"submit\" style=\"min-width:1em;\" \
value=\"^\" id=\"doFileView\" style=\"margin-left:0;\">\
</td>\
<td><sup>1.</sup></td></tr>\n\
<tr><td>\
<input type=\"submit\" name=\"s-edit\" value=\"!AZ\" \
style=\"margin-right:0;\">\
<input id=\"doFileEdit\" type=\"submit\" style=\"min-width:1em;\" \
name=\"s-edit^\" value=\"^\" style=\"margin-left:0;\">\
</td>\
<td><sup>1.[&amp;2.]</sup></td></tr>\n"

#ifdef UPD_CREATE
"<tr><td>\
<input type=\"submit\" name=\"s-create\" value=\"!AZ\"></td>\
<td><sup>2.</sup></td></tr>\n"
#endif /* UPD_CREATE */

#ifdef UPD_FILTER
"<tr><td>\
<input type=\"submit\" name=\"s-filter\" value=\"!AZ\"></td>\
<td><sup>1.&amp;2.</sup></td></tr>\n"
#endif /* UPD_FILTER */

"<tr><td>\
<input type=\"submit\" name=\"s-filerename\" value=\"!AZ\">\
</td>\
<td><sup>1.&amp;2.</sup></td></tr>\n\
<tr><td>\
<input type=\"submit\" name=\"s-copy\" value=\"!AZ\"></td>\
<td><sup>1.&amp;2.</sup></td></tr>\n\
<tr><td>\
<input type=\"submit\" name=\"s-fileprotect\" value=\"!AZ\">\
</td>\
<td><sup>1./2.</sup></td></tr>\n\
<tr><td>\
<input type=\"submit\" name=\"s-delete\" value=\"!AZ\"></td>\
<td><sup>1./2.</sup></td></tr>\n\
<tr><td><input id=\"doReload\" type=\"submit\" value=\"!AZ\"></td>\n\
<td style=\"font-size:170%;position:relative;\
top:-0.3em;left:-0.2em;\">!AZ</td></tr>\n\
</table>\n\
</td></tr>\n\
</table>\n\
</form>\n\
\
<form method=\"POST\" action=\"!&%AZ\" enctype=\"multipart/form-data\">\n\
<p>\n\
&nbsp;<input type=\"submit\" value=\"!AZ\">\n\
!AZ <input type=\"text\" name=\"uploadfilename\" size=\"20\">\n\
<br>&nbsp;!AZ\
<br>&nbsp;!AZ <input type=\"file\" name=\"name\" size=\"20\">\n\
</form>\n\
</td>\n\
\
</tr>\n\
</table>\n\
\
</div>\n\
<span id=\"ender\"><span>\n\
</body>\n\
</html>\n";

   int  status;
   unsigned long  FaoVector [32];
   unsigned long  *vecptr;
   char  *cptr, *sptr,
         *MsgAsPtr,
         *MsgLocalFilePtr,
         *MsgUploadPtr;
   UPD_TASK  *tkptr;

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

   if (WATCHMOD (rqptr, WATCH_MOD_UPD))
      WatchThis (WATCHITM(rqptr), WATCH_MOD_UPD, "UpdNavigateEnd()");

   tkptr = rqptr->UpdTaskPtr;

   /* retrieve the previously saved position in the string */
   cptr = tkptr->MsgStringPtr;

   /* for historical reasons "Upload", "As", "Local File" messages are here */
   MsgUploadPtr = cptr;
   while (*cptr && *cptr != '|') cptr++;
   if (*cptr) *cptr++ = '\0';
   MsgAsPtr = cptr;
   while (*cptr && *cptr != '|') cptr++;
   if (*cptr) *cptr++ = '\0';
   MsgLocalFilePtr = cptr;
   while (*cptr && *cptr != '|') cptr++;
   if (*cptr) *cptr++ = '\0';

   vecptr = FaoVector;

   if (tkptr->FileCount)
   {
      /* "select from list" */
      *vecptr++ = cptr;
      while (*cptr && *cptr != '|') cptr++;
      if (*cptr) *cptr++ = '\0';
      /* skip over "none available" */
      while (*cptr && *cptr != '|') cptr++;
      if (*cptr) cptr++;
   }
   else
   {
      /* skip over "select from list" */
      while (*cptr && *cptr != '|') cptr++;
      if (*cptr) cptr++;
      /* "none available" */
      *vecptr++ = cptr;
      while (*cptr && *cptr != '|') cptr++;
      if (*cptr) *cptr++ = '\0';
   }

   /* "enter name/path" */
   *vecptr++ = cptr;
   while (*cptr && *cptr != '|') cptr++;
   if (*cptr) *cptr++ = '\0';

   /* file protection */
   *vecptr++ = tkptr->FileProtectionList;

   /* "reset" */
   *vecptr++ = cptr;
   while (*cptr && *cptr != '|') cptr++;
   if (*cptr) *cptr++ = '\0';

   /* view */
   *vecptr++ = cptr;
   while (*cptr && *cptr != '|') cptr++;
   if (*cptr) *cptr++ = '\0';

   /* edit */
   *vecptr++ = cptr;
   while (*cptr && *cptr != '|') cptr++;
   if (*cptr) *cptr++ = '\0';

   /* create */
#ifdef UPD_CREATE
   *vecptr++ = cptr;
#endif /* UPD_CREATE */
   while (*cptr && *cptr != '|') cptr++;
   if (*cptr) *cptr++ = '\0';

   /* filter */
#ifdef UPD_FILTER
   *vecptr++ = cptr;
#endif /* UPD_FILTER */
   while (*cptr && *cptr != '|') cptr++;
   if (*cptr) *cptr++ = '\0';

   /* rename */
   *vecptr++ = cptr;
   while (*cptr && *cptr != '|') cptr++;
   if (*cptr) *cptr++ = '\0';

   /* copy */
   *vecptr++ = cptr;
   while (*cptr && *cptr != '|') cptr++;
   if (*cptr) *cptr++ = '\0';

   /* protection */
   *vecptr++ = cptr;
   while (*cptr && *cptr != '|') cptr++;
   if (*cptr) *cptr++ = '\0';

   /* delete */
   *vecptr++ = cptr;
   while (*cptr && *cptr != '|') cptr++;
   if (*cptr) *cptr++ = '\0';

   *vecptr++ = REFRESH_SYMBOL;
   *vecptr++ = REFRESH_SYMBOL;

   *vecptr++ = tkptr->PathInfoDir;
   *vecptr++ = MsgUploadPtr;
   *vecptr++ = MsgAsPtr;
   *vecptr++ = tkptr->ProtectionList;
   *vecptr++ = MsgLocalFilePtr;

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

   UpdEnd (rqptr);
}

/*****************************************************************************/
/*
Provide a directory tree display.  Provides two types of tree.  The first, an
update tree, where tree node selection opens a new directory update page. 
Second, a general directory tree, where selecting a tree node behaves as any
other directory URL (i.e. if a wildcard name.type;version is provided a listing
is produced, if none then any home page existing in the directory is returned,
else a directory listing, any query string supplied is reproduced in the tree
link paths).
*/

void UpdTreeBegin (REQUEST_STRUCT *rqptr)

{
   static char  UpdateTreeFao [] =
"!AZ\
<html>\n\
<head>\n\
!AZ\
!AZ\
!&@\
<title>Update&nbsp; !AZ</title>\n\
</head>\n\
!AZ\
<div class=\"wasd\">\n\
<h2>Update&nbsp; !AZ</h2>\n\
<hr size=\"2\" noshade><p>\n\
<pre>  <a href=\"!&%AZ\"><b>!&;AZ</b></a>\n";

   static char  Tree1Fao [] =
"!AZ\
<html>\n\
<head>\n\
!AZ\
!AZ\
!&@\
<title>!&@</title>\n\
</head>\n\
!&@\
!&@\
<div class=\"wasd\">\n";

   static char  Tree2Fao [] =
"!AZ\
<pre><hr size=\"2\" noshade>\n\
  <a href=\"!&%AZ!&??\r\r!AZ\"><b>!&;AZ</b></a>\n";

   int  status;
   unsigned short  ShortLength;
   unsigned long  FaoVector [32];
   unsigned long  *vecptr;
   char  *cptr, *sptr, *zptr,
         *TreeOfPtr;
   char  NameBuffer [ODS_MAX_FILE_NAME_LENGTH+1],
         PathBuffer [ODS_MAX_FILE_NAME_LENGTH+1];
   REQUEST_AST  AstFunction;
   UPD_TASK  *tkptr;
   UPD_TREE  *tnptr;

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

   if (WATCHMOD (rqptr, WATCH_MOD_UPD))
      WatchThis (WATCHITM(rqptr), WATCH_MOD_UPD, "UpdTreeBegin()");

   tkptr = rqptr->UpdTaskPtr;

   /* first check if the directory can be accessed */
   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 (rqptr->ParseOds.ExpFileName, DirHiddenFileName)))
         status = RMS$_DNF;
      else
      if ((Config.cfDir.AccessSelective ||
           rqptr->rqPathSet.DirAccessSelective) &&
           !rqptr->rqPathSet.DirAccess)
         status = OdsFileExists (rqptr->ParseOds.ExpFileName,
                                 DirBrowsableFileName);
      else
      if (VMSok (status =
          OdsFileExists (rqptr->ParseOds.ExpFileName, DirNoWildFileName)))
         status = RMS$_WLD;
      else
         status = SS$_NORMAL;

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

   if (VMSnok (status))
   {
      /* give some "disinformation"  ;^)  */
      if (status == RMS$_FNF) status = RMS$_DNF;
      rqptr->rqResponse.ErrorTextPtr = rqptr->rqHeader.PathInfoPtr;
      rqptr->rqResponse.ErrorOtherTextPtr = rqptr->ParseOds.ExpFileName;
      ErrorVmsStatus (rqptr, status, FI_LI);
      UpdEnd (rqptr);
      return;
   }

   /* allocate heap memory for the initial tree structure */
   tnptr = (UPD_TREE*)VmGetHeap (rqptr, sizeof(UPD_TREE));
   tnptr->PrevTreeNodePtr = tnptr->NextTreeNodePtr = NULL;
   tkptr->TreeHeadPtr = tkptr->TreeNodePtr = tnptr;

   OdsStructInit (&tnptr->SearchOds, false);

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

   status = OdsFileExists (rqptr->ParseOds.ExpFileName, DirNosFileName);
   if (status = RMS$_FNF)
      status = OdsFileExists (rqptr->ParseOds.ExpFileName, DirNopsFileName);

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

   /* if we can see its subdirectories, if not then end */
   if (status == RMS$_FNF)
   {
      AstFunction = &UpdTreeListDirs;

      /***************************/
      /* set up directory search */
      /***************************/

      AuthAccessEnable (rqptr, rqptr->ParseOds.ExpFileName, AUTH_ACCESS_READ);

      OdsParse (&tnptr->SearchOds,
                rqptr->ParseOds.ExpFileName,
                /* i.e. just the length of the dev:[dir] */
                rqptr->ParseOds.NamNamePtr - rqptr->ParseOds.ExpFileName,
                "*.DIR;", 6, 0, NULL, rqptr);

      AuthAccessEnable (rqptr, 0, 0);

      if (VMSnok (status = tnptr->SearchOds.Fab.fab$l_sts))
      {
         rqptr->rqResponse.ErrorTextPtr = rqptr->rqHeader.PathInfoPtr;
         rqptr->rqResponse.ErrorOtherTextPtr = rqptr->ParseOds.ExpFileName;
         ErrorVmsStatus (rqptr, status, FI_LI);
         UpdEnd (rqptr);
         return;
      }
   }
   else
      AstFunction = &UpdTreeEnd;

   /**************/
   /* begin page */
   /**************/

   /* just use 'PathBuffer' as scratch space */
   strzcpy (PathBuffer, tkptr->PathInfoDir, sizeof(PathBuffer));

   if (tkptr->FormatTreeLikeVms)
   {
      MapOdsUrlToVms (PathBuffer, NameBuffer, sizeof(NameBuffer), 0,
                      rqptr->rqPathSet.MapEllipsis, rqptr->PathOds);
      if (!rqptr->PathOds || rqptr->PathOds == MAPURL_PATH_ODS_2)
         for (sptr = NameBuffer; *sptr; sptr++) *sptr = to_upper(*sptr);
      else
         for (sptr = NameBuffer; *sptr; sptr++);
      /* get the length of all but the last directory element */
      while (sptr > NameBuffer && *sptr != '.' && *sptr != '[') sptr--;
      tkptr->TreeIndent = sptr - NameBuffer + 3;
   }
   else
   {
      vecptr = FaoVector;
      *vecptr++ = rqptr->PathOds;
      *vecptr++ = PathBuffer;
      FaolToBuffer (NameBuffer, sizeof(NameBuffer), &ShortLength,
                    "!&[AZ", &FaoVector);
      /* get the length of all but the last directory element */
      sptr = NameBuffer + ShortLength;
      if (sptr > NameBuffer) sptr--;
      while (sptr > NameBuffer && *sptr != '/') sptr--;
      if (sptr > NameBuffer) sptr--;
      while (sptr > NameBuffer && *sptr != '/') sptr--;
      tkptr->TreeIndent = sptr - NameBuffer + 3;
   }

   zptr = (sptr = PathBuffer) + sizeof(PathBuffer)-1;
   if (tkptr->UpdateTree)
   {
      cptr = rqptr->ScriptName;
      while (*cptr && sptr < zptr) *sptr++ = *cptr++;
   }
   cptr = tkptr->PathInfoDir;
   while (*cptr && sptr < zptr) *sptr++ = *cptr++;
   *sptr = '\0';

   if (WATCHMOD (rqptr, WATCH_MOD_UPD))
      WatchThis (WATCHITM(rqptr), WATCH_MOD_UPD, "!UL !&Z !&Z",
                 tkptr->TreeIndent, PathBuffer, NameBuffer);

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

   vecptr = FaoVector;

   *vecptr++ = WASD_DOCTYPE;
   *vecptr++ = HtmlMetaInfo (rqptr, rqptr->ParseOds.ExpFileName);
   if (tkptr->UpdateTree)
      *vecptr++ = AdminWasdCss ();
   else
      *vecptr++ = "";
   if (rqptr->rqPathSet.StyleSheetPtr)
   {
      *vecptr++ = "<link rel=\"stylesheet\" type=\"text/css\" href=\"!AZ\">\n";
      *vecptr++ = rqptr->rqPathSet.StyleSheetPtr;
   }
   else
      *vecptr++ = "";

   if (tkptr->UpdateTree)
   {
      *vecptr++ = rqptr->ServicePtr->ServerHostPort;
      *vecptr++ = ADMIN_BODY_TAG;
      *vecptr++ = rqptr->ServicePtr->ServerHostPort;
      *vecptr++ = PathBuffer;
      *vecptr++ = NameBuffer;

      status = FaolToNet (rqptr, UpdateTreeFao, &FaoVector);
      if (VMSnok (status)) ErrorNoticed (rqptr, status, NULL, FI_LI);
   }
   else
   {
      TreeOfPtr = MsgFor(rqptr,MSG_DIR_TREE_OF);
      /* <title>..</title> */
      if (tkptr->FormatTreeLikeVms)
      {
         *vecptr++ = "!AZ !&;AZ";
         *vecptr++ = TreeOfPtr;
         *vecptr++ = NameBuffer;
      }
      else
      if (rqptr->rqPathSet.DirStyle == MAPURL_DIR_STYLE_HTDIR)
      {
         *vecptr++ = "//!AZ/!&;AZ";
         if (rqptr->rqHeader.HostPtr &&
             rqptr->rqHeader.HostPtr[0])
            *vecptr++ = rqptr->rqHeader.HostPtr;
         else
            *vecptr++ = rqptr->ServicePtr->ServerHostPort;
         *vecptr++ = NameBuffer;
      }
      else
      {
         *vecptr++ = "!AZ //!AZ!&;AZ";
         *vecptr++ = TreeOfPtr;
         if (rqptr->rqHeader.HostPtr &&
             rqptr->rqHeader.HostPtr[0])
            *vecptr++ = rqptr->rqHeader.HostPtr;
         else
            *vecptr++ = rqptr->ServicePtr->ServerHostPort;
         *vecptr++ = NameBuffer;
      }

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

      if (rqptr->rqPathSet.HtmlHeaderPtr ||
          rqptr->rqPathSet.HtmlHeaderTagPtr)
      {
         if (rqptr->rqPathSet.HtmlHeaderTagPtr &&
             rqptr->rqPathSet.HtmlHeaderTagPtr[0] == '<')
            *vecptr++ = "!AZ\n!&/AZ";
         else
            *vecptr++ = "<table class=\"lftlft\"><tr><td!&+AZ>\n!&/AZ";
         *vecptr++ = rqptr->rqPathSet.HtmlHeaderTagPtr;
         *vecptr++ = rqptr->rqPathSet.HtmlHeaderPtr;
      }
      else
         *vecptr++ = "";

      status = FaolToNet (rqptr, Tree1Fao, &FaoVector);
      if (VMSnok (status)) ErrorNoticed (rqptr, status, NULL, FI_LI);

      UpdTreeOf (rqptr, TreeOfPtr, PathBuffer, NameBuffer);

      vecptr = FaoVector;

      if (rqptr->rqPathSet.HtmlHeaderPtr ||
          rqptr->rqPathSet.HtmlHeaderTagPtr)
         *vecptr++ = "</td></tr></table>\n";
      else
         *vecptr++ = "";

      *vecptr++ = PathBuffer;
      *vecptr++ = rqptr->rqHeader.QueryStringLength ?
                     rqptr->rqHeader.QueryStringPtr[0] : 0;
      *vecptr++ = rqptr->rqHeader.QueryStringLength ?
                     rqptr->rqHeader.QueryStringPtr : "";
      *vecptr++ = NameBuffer;

      status = FaolToNet (rqptr, Tree2Fao, &FaoVector);
      if (VMSnok (status)) ErrorNoticed (rqptr, status, NULL, FI_LI);
   }

   tkptr->TreeLevel = 0;

   NetWriteFullFlush (rqptr, AstFunction);
}

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

int UpdTreeOf
(
REQUEST_STRUCT *rqptr,
char *TreeOfPtr,
char *PathBuffer,
char *NameBuffer
)
{
   /* allows as directory nesting of up to 64 (should be enough ;^) */
   static char  DotDotSlash64 [] =
"<a href=\"./\
../../../../../../../../../../../../../../../../\
../../../../../../../../../../../../../../../../\
../../../../../../../../../../../../../../../../\
../../../../../../../../../../../../../../../../";

   int  cnt, status,
        SlashCount;
   unsigned long  FaoVector [32];
   unsigned long  *vecptr;
   char  *cptr, *sptr, *zptr,
         *FinalSlashPtr;
   UPD_TASK  *tkptr;

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

   if (WATCHMOD (rqptr, WATCH_MOD_UPD))
      WatchThis (WATCHITM(rqptr), WATCH_MOD_UPD,
                 "UpdTreeOf() !&Z !&Z !&Z", TreeOfPtr, PathBuffer, NameBuffer);

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

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

      vecptr = &FaoVector;
      *vecptr++ = TreeOfPtr;
      *vecptr++ = PathBuffer;
      status = FaolToNet (rqptr, "<h2>!AZ &nbsp;!&;&_AZ</h2>\n", &FaoVector);
      return (status);
   }

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

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

   if (tkptr->FormatTreeLikeVms)
   {
      vecptr = &FaoVector;
      *vecptr++ = TreeOfPtr;
      status = FaolToNet (rqptr, "<h3>!AZ &nbsp;", &FaoVector);
      if (VMSnok (status)) return (status);
      cptr = NameBuffer;
   }
   else
   { 
      vecptr = &FaoVector;
      if (rqptr->rqPathSet.DirStyle == MAPURL_DIR_STYLE_HTDIR)
      {
         /* ABI's style begins with the server name as a 'root' anchor */
         *vecptr++ = rqptr->ScriptName;
         *vecptr++ = FinalSlashPtr;
         *vecptr++ = rqptr->rqHeader.QueryStringLength ?
                        rqptr->rqHeader.QueryStringPtr[0] : 0;
         *vecptr++ = rqptr->rqHeader.QueryStringLength ?
                        rqptr->rqHeader.QueryStringPtr : "";
         if (rqptr->rqHeader.HostPtr &&
             rqptr->rqHeader.HostPtr[0])
            *vecptr++ = rqptr->rqHeader.HostPtr;
         else
            *vecptr++ = rqptr->ServicePtr->ServerHostPort;
         status = FaolToNet (rqptr,
"<h3><a href=\"!AZ/!AZ!&??\r\r!AZ\">//!AZ/</a>",
                             &FaoVector);
      }
      else
      {
         /* WASD style provides an "Tree of" heading */
         *vecptr++ = TreeOfPtr;
         *vecptr++ = rqptr->ScriptName;
         *vecptr++ = FinalSlashPtr;
         *vecptr++ = rqptr->rqHeader.QueryStringLength ?
                        rqptr->rqHeader.QueryStringPtr[0] : 0;
         *vecptr++ = rqptr->rqHeader.QueryStringLength ?
                        rqptr->rqHeader.QueryStringPtr : "";
         if (rqptr->rqHeader.HostPtr &&
             rqptr->rqHeader.HostPtr[0])
            *vecptr++ = rqptr->rqHeader.HostPtr;
         else
            *vecptr++ = rqptr->ServicePtr->ServerHostPort;
         status = FaolToNet (rqptr,
"<h3>!AZ &nbsp;//<a href=\"!AZ/!AZ!&??\r\r!AZ\">!AZ</a>/",
                             &FaoVector);
      }
      if (VMSnok (status)) return (status);
      cptr = PathBuffer;
   }

   /* provide a self-relative (../) anchor for each directory element */
   if (SlashCount) SlashCount--;
   while (SlashCount-- > 0)
   {
      vecptr = &FaoVector;
      *vecptr++ = 11 + (SlashCount * 3);
      *vecptr++ = DotDotSlash64;
      *vecptr++ = FinalSlashPtr;
      *vecptr++ = rqptr->rqHeader.QueryStringLength ?
                     rqptr->rqHeader.QueryStringPtr[0] : 0;
      *vecptr++ = rqptr->rqHeader.QueryStringLength ?
                     rqptr->rqHeader.QueryStringPtr : "";
      if (tkptr->FormatTreeLikeVms)
      {
         /* VMS format, parse based on VMS directory delimiting characters */
         while (*cptr == '.' || *cptr == ':' || *cptr == '[' || *cptr == ']')
            cptr++;
         for (sptr = cptr;
              *sptr && *sptr != '.' && *sptr != ':' && *sptr != ']';
              sptr++)
            if (*sptr == '^') sptr++;
         *vecptr++ = sptr - cptr;
         *vecptr++ = cptr;
         if (*sptr == ':')
            *vecptr++ = 2;
         else
            *vecptr++ = 1;
         *vecptr++ = sptr;
      }
      else
      {
         /* URL format, parse based on delimiting slashes */
         if (*cptr == '/') cptr++;
         for (sptr = cptr; *sptr && *sptr != '/'; sptr++);
         if (rqptr->rqPathSet.DirStyle == MAPURL_DIR_STYLE_HTDIR)
         {
            /* ABI's style, trailing slashes are part of the link */
            if (*sptr) sptr++;
            *vecptr++ = sptr - cptr;
            *vecptr++ = cptr;
            *vecptr++ = 0;
            *vecptr++ = "";
         }
         else
         {
            /* WASD style, trailing slashes are not part of the link */
            *vecptr++ = sptr - cptr;
            *vecptr++ = cptr;
            *vecptr++ = 1;
            *vecptr++ = sptr;
         }
      }
      if (VMSnok (status)) return (status);
      status = FaolToNet (rqptr, "!#AZ!AZ!&??\r\r!&;AZ\">!#AZ</a>!#AZ",
                          &FaoVector);
      cptr = sptr;
   }

   if (!PathBuffer && rqptr->rqPathSet.DirStyle == MAPURL_DIR_STYLE_HTDIR)
   {
      /* ABI's style, 'file' name and type element as an anchor */
      if (*cptr == '/') cptr++;
      vecptr = &FaoVector;
      *vecptr++ = cptr;
      *vecptr++ = rqptr->rqHeader.QueryStringLength ?
                     rqptr->rqHeader.QueryStringPtr[0] : 0;
      *vecptr++ = rqptr->rqHeader.QueryStringLength ?
                     rqptr->rqHeader.QueryStringPtr : "";
      *vecptr++ = cptr;
      status = FaolToNet (rqptr,
                  "<a href=\"!AZ!&??\r\n!AZ\">!AZ</a></h3>\n",
                          &FaoVector);
   } 
   else
   {
      /* WASD style, URL or VMS format, 'file' name and type just displayed */
      if (tkptr->FormatTreeLikeVms)
         while (*cptr == ':' || *cptr == '[' || *cptr == '.' || *cptr == ']')
            cptr++;
      else
      if (*cptr == '/')
         cptr++;

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

   return (status);
}

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

void UpdTreeListDirs (REQUEST_STRUCT *rqptr)

{
   UPD_TREE  *tnptr;

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

   if (WATCHMOD (rqptr, WATCH_MOD_UPD))
      WatchThis (WATCHITM(rqptr), WATCH_MOD_UPD,
                 "UpdTreeListDirs() !&F", &UpdTreeListDirs);

   /* get the pointer to the tree structure */
   tnptr = rqptr->UpdTaskPtr->TreeNodePtr;

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

   OdsSearch (&tnptr->SearchOds, &UpdTreeDirs, rqptr);

   AuthAccessEnable (rqptr, 0, 0);
}

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

void UpdTreeDirs (REQUEST_STRUCT *rqptr)

{
   int  status,
        cnt;
   unsigned long  FaoVector [32];
   unsigned long  *vecptr;
   char  ch;
   char  *cptr, *sptr, *tptr, *zptr;
   char  Buffer [2048],
         NameBuffer [ODS_MAX_FILE_NAME_LENGTH+64+1],
         PathBuffer [ODS_MAX_FILE_NAME_LENGTH+64+1],
         NestingBuffer [512];
   UPD_TASK  *tkptr;
   UPD_TREE  *tnptr,
             *tmptnptr,
             *NextTreeNodePtr,
             *PrevTreeNodePtr;

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

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

   if (WATCHMOD (rqptr, WATCH_MOD_UPD))
      WatchThis (WATCHITM(rqptr), WATCH_MOD_UPD,
                 "UpdTreeDirs() !&F sts:!&X stv:!&X",
                 &UpdTreeDirs,
                 rqptr->UpdTaskPtr->SearchOds.Fab.fab$l_sts,
                 rqptr->UpdTaskPtr->SearchOds.Fab.fab$l_stv);

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

   tkptr = rqptr->UpdTaskPtr;
   tnptr = tkptr->TreeNodePtr;

   if (VMSnok (status = tnptr->SearchOds.Fab.fab$l_sts))
   {
      if ((status == SS$_NOPRIV || status == RMS$_PRV) &&
          Config.cfDir.NoPrivIgnore)
      {
         /* protection violation that we're configured to ignore */
         tkptr->TreeLevel--;
         if (tnptr->PrevTreeNodePtr)
         {
            tkptr->TreeNodePtr = tnptr->PrevTreeNodePtr;
            UpdTreeListDirs (rqptr);
         }
         else
            UpdTreeEnd (rqptr);
         return;
      }

      if (status == RMS$_FNF || status == RMS$_NMF)
      {
         /* end of directory search, nest back one level or end */
         tnptr->SearchOds.ParseInUse = false;
         tkptr->TreeLevel--;
         if (tnptr->PrevTreeNodePtr)
         {
            tkptr->TreeNodePtr = tnptr->PrevTreeNodePtr;
            UpdTreeListDirs (rqptr);
         }
         else
            UpdTreeEnd (rqptr);
         return;
      }

      /* sys$search() error */
      rqptr->rqResponse.ErrorTextPtr = rqptr->rqHeader.PathInfoPtr;
      rqptr->rqResponse.ErrorOtherTextPtr = tnptr->SearchOds.NamDevicePtr;
      ErrorVmsStatus (rqptr, status, FI_LI);
      UpdTreeEnd (rqptr);
      return;
   }

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

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

   if (MATCH7 (tnptr->SearchOds.NamNamePtr, "000000."))
   {
      /* not interested in master file directories :^) */
      UpdTreeListDirs (rqptr);
      return;
   }


   /****************************************/
   /* check permissions for each directory */
   /****************************************/

   status = AuthAccessReadCheck (rqptr, tnptr->SearchOds.ResFileName,
                                        tnptr->SearchOds.ResFileNameLength);
   if (VMSnok (status))
   {
      if (status == SS$_NOPRIV)
      {
         /* does not have access, as if it didn't exist! */
         UpdTreeListDirs (rqptr);
         return;
      }
      UpdEnd (rqptr);
      return;
   }

   /************/
   /* continue */
   /************/

   /* first check if the directory can be accessed */

   tnptr->SearchOds.NamNamePtr[-1] = '.';
   tnptr->SearchOds.NamTypePtr[0] = ']';
   ch = tnptr->SearchOds.NamTypePtr[1];
   tnptr->SearchOds.NamTypePtr[1] = '\0';

   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 (tnptr->SearchOds.ResFileName, DirHiddenFileName)))
         status = RMS$_DNF;
      else
      if ((Config.cfDir.AccessSelective ||
           rqptr->rqPathSet.DirAccessSelective) &&
           !rqptr->rqPathSet.DirAccess)
         status = OdsFileExists (tnptr->SearchOds.ResFileName,
                                 DirBrowsableFileName);
      else
         status = SS$_NORMAL;

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

   tnptr->SearchOds.NamNamePtr[-1] = ']';
   tnptr->SearchOds.NamTypePtr[0] = '.';
   tnptr->SearchOds.NamTypePtr[1] = ch;

   if (VMSnok (status))
   {
      /* no, it can't be accessed */
      UpdTreeListDirs (rqptr);
      return;
   }

   /*********************/
   /* new level of tree */
   /*********************/

   if (tnptr->NextTreeNodePtr)
   {
      /* reuse previously allocated structure */
      tnptr = tnptr->NextTreeNodePtr;
      NextTreeNodePtr = tnptr->NextTreeNodePtr;
      PrevTreeNodePtr = tnptr->PrevTreeNodePtr;
      memset (tnptr, 0, sizeof(UPD_TREE));
      tnptr->NextTreeNodePtr = NextTreeNodePtr;
      tnptr->PrevTreeNodePtr = PrevTreeNodePtr;
   }
   else
   {
      /* allocate heap memory for the new (nested) tree task structure */
      tnptr = (UPD_TREE*)VmGetHeap (rqptr, sizeof(UPD_TREE));
      tnptr->PrevTreeNodePtr = tkptr->TreeNodePtr;
      tnptr->PrevTreeNodePtr->NextTreeNodePtr = tnptr;
      tnptr->NextTreeNodePtr = NULL;

      OdsStructInit (&tnptr->SearchOds, false);
   }
   tkptr->TreeNodePtr = tnptr;

   tkptr->TreeLevel++;

   sptr = tnptr->FileName;
   for (cptr = tnptr->PrevTreeNodePtr->SearchOds.ResFileName;
        cptr < tnptr->PrevTreeNodePtr->SearchOds.NamNamePtr;
        *sptr++ = *cptr++);
   sptr[-1] = '.';
   while (cptr < tnptr->PrevTreeNodePtr->SearchOds.NamTypePtr)
      *sptr++ = *cptr++;
   *sptr++ = ']';
   *sptr = '\0';
   tnptr->FileNameLength = sptr - tnptr->FileName;

   /***************************/
   /* set up directory search */
   /***************************/

   AuthAccessEnable (rqptr, tnptr->FileName, AUTH_ACCESS_READ);

   OdsParse (&tnptr->SearchOds,
             tnptr->FileName, tnptr->FileNameLength, "*.DIR;", 6,
             0, NULL, rqptr);

   AuthAccessEnable (rqptr, 0, 0);

   if (VMSnok (status = tnptr->SearchOds.Fab.fab$l_sts))
   {
      rqptr->rqResponse.ErrorTextPtr = rqptr->rqHeader.PathInfoPtr;
      rqptr->rqResponse.ErrorOtherTextPtr = tnptr->FileName;
      ErrorVmsStatus (rqptr, status, FI_LI);
      UpdEnd (rqptr);
      return;
   }

   /**************************/
   /* display this directory */
   /**************************/

   zptr = (sptr = NameBuffer) + sizeof(NameBuffer)-1;
   cptr = tnptr->PrevTreeNodePtr->SearchOds.NamNamePtr;
   tptr = tnptr->PrevTreeNodePtr->SearchOds.NamTypePtr;
   while (*cptr && cptr < tptr && sptr < zptr) *sptr++ = *cptr++;
   *sptr = '\0';

   zptr = (sptr = PathBuffer) + sizeof(PathBuffer)-1;
   if (tkptr->UpdateTree)
   {
      cptr = rqptr->ScriptName;
      while (*cptr && sptr < zptr) *sptr++ = *cptr++;
   }
   cptr = tkptr->PathInfoDir;
   while (*cptr && sptr < zptr) *sptr++ = *cptr++;

   /* add the name of all the directories to the link path */
   tmptnptr = tkptr->TreeHeadPtr;
   while (tmptnptr != tnptr)
   {
      cptr = tmptnptr->SearchOds.NamNamePtr;
      tptr = tmptnptr->SearchOds.NamTypePtr;
      if (rqptr->PathOdsExtended && tkptr->FormatTreeLikeVms)
         while (*cptr && cptr < tptr && sptr < zptr) *sptr++ = *cptr++;
      else
         while (*cptr && cptr < tptr && sptr < zptr) *sptr++ = to_lower(*cptr++);
      if (sptr < zptr) *sptr++ = '/';
      tmptnptr = tmptnptr->NextTreeNodePtr;
   }
   *sptr = '\0';

   /* indent tree diagram using spaces, '|' and '-' */
   zptr = (sptr = NestingBuffer) + sizeof(NestingBuffer)-1;
   for (cnt = tkptr->TreeIndent; cnt && sptr < zptr; cnt--) *sptr++ = ' ';
   for (cnt = tkptr->TreeLevel-1; cnt && sptr < zptr; cnt--)
      for (cptr = "|   "; *cptr && sptr < zptr; *sptr++ = *cptr++);
   for (cptr = "|---"; *cptr && sptr < zptr; *sptr++ = *cptr++);
   *sptr = '\0';

   vecptr = FaoVector;

   *vecptr++ = NestingBuffer;
   *vecptr++ = "!&%&[AZ!&%&[AZ!&??\r\r!AZ";
   *vecptr++ = rqptr->PathOds;
   *vecptr++ = PathBuffer;
   *vecptr++ = rqptr->PathOds;
   *vecptr++ = rqptr->ParseOds.NamNamePtr;
   *vecptr++ = rqptr->rqHeader.QueryStringLength ?
                  rqptr->rqHeader.QueryStringPtr[0] : 0;
   *vecptr++ = rqptr->rqHeader.QueryStringLength ?
                  rqptr->rqHeader.QueryStringPtr : "";

   if (tkptr->FormatTreeLikeVms)
      if (!rqptr->PathOds || rqptr->PathOds == MAPURL_PATH_ODS_2)
         *vecptr++ = "!&;&^AZ";
      else
         *vecptr++ = "!&;AZ";
   else
   {
      *vecptr++ = "!&;&[AZ";
      *vecptr++ = rqptr->PathOds;
   }
   *vecptr++ = NameBuffer;

   status = FaolToNet (rqptr, "!AZ<a href=\"!&@\">!&@</a>\n", &FaoVector);
   if (VMSnok (status))
   {
      ErrorNoticed (rqptr, status, NULL, FI_LI);
      FaolToNet (rqptr, "\n<font color=\"#ff0000\">[Error]</font>\n", NULL);
   }

   NetWritePartFlush (rqptr, &UpdTreeListDirs);
}

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

void UpdTreeEnd (REQUEST_STRUCT *rqptr)

{
   static char  TreeEndFao [] =
"\n<hr size=\"2\" noshade></pre>\n\
!&@\
</div>\n\
</body>\n\
</html>\n";

   int  status;
   unsigned long  FaoVector [32];
   unsigned long  *vecptr;
   UPD_TASK  *tkptr;
   UPD_TREE  *tnptr;

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

   if (WATCHMOD (rqptr, WATCH_MOD_UPD))
      WatchThis (WATCHITM(rqptr), WATCH_MOD_UPD, "UpdTreeEnd()");

   /* get the pointer to the task structure */
   tkptr = rqptr->UpdTaskPtr;
   /* get the pointer to the tree structure */
   tnptr = tkptr->TreeNodePtr;

   while (tnptr->PrevTreeNodePtr)
   {
      /* ensure parse internal data structures are released */
      OdsParseRelease (&tnptr->SearchOds);
      /* get any previous node */
      tnptr = tnptr->PrevTreeNodePtr;
   }

   vecptr = &FaoVector;
   if (rqptr->rqPathSet.HtmlFooterPtr ||
       rqptr->rqPathSet.HtmlFooterTagPtr)
   {
      if (rqptr->rqPathSet.HtmlFooterTagPtr &&
          rqptr->rqPathSet.HtmlFooterTagPtr[0] == '<')
         *vecptr++ = "!AZ\n!&@";
      else
         *vecptr++ = "<table class=\"lftlft\"><tr><td!&+AZ>\n!&@";
      *vecptr++ = rqptr->rqPathSet.HtmlFooterTagPtr;
      *vecptr++ = "!&/AZ</td></tr></table>\n";
      *vecptr++ = rqptr->rqPathSet.HtmlFooterPtr;
   }
   else
      *vecptr++ = "";

   status = FaolToNet (rqptr, TreeEndFao, &FaoVector);
   if (VMSnok (status)) ErrorNoticed (rqptr, status, NULL, FI_LI);

   UpdEnd (rqptr);
}

/*****************************************************************************/
/*
Generate a file editing page.  This comprises an HTTP response header.  Open
the file.  This checks whether it is an update (file exists) or create (file
does not exist).  Generate an HTML page and the start of a text area widget.
Then initiate the file records being read, to be included within the
<textarea></textarea> tags.
*/

void UpdEditFileBegin
(
REQUEST_STRUCT *rqptr,
REQUEST_AST NextTaskFunction,
char *AsNamePtr
)
{
   static char  ResponseFao [] =
"!AZ\
<html>\n\
<head>\n\
!AZ\
!AZ\
!&@\
<title>!AZ !AZ</title>\n\
\
<script nonce=\"!AZ\">\n\
\
function changeEditWindow()\n\
{\n\
   ted = document.getElementById('textedit');\n\
   cxr = document.getElementById(\"cxr\");\n\
   cxrval = document.getElementById(\"cxr\").value;\n\
   xpos = cxrval.indexOf('x');\n\
   ted.cols = parseInt(cxrval.substring(0,xpos));\n\
   ted.rows = parseInt(cxrval.substring(xpos+1));\n\
   return false;\n\
}\n\
\
!AZ\
\
</script>\n\
\
</head>\n\
!AZ\n\
<div class=\"wasd\">\n\
<h2>!AZ !AZ</h2>\n\
!AZ\
!AZ\
<p>\n\
<form method=\"POST\" action=\"!&%AZ\">\n\
<input type=\"submit\" value=\"!AZ\">\n\
!&@\
<input type=\"reset\" value=\"!AZ\">\n\
!AZ\
<p>\n\
<textarea name=\"document\" id=\"textedit\" cols=\"!UL\" rows=\"!UL\">\
!AZ!AZ";

   static $DESCRIPTOR (BytesFaoDsc, " (!UL bytes)\0");
   static $DESCRIPTOR (DayDateFaoDsc, "!AZ, !20%D\0");
   static $DESCRIPTOR (OutputFormatDsc, "|!WC, !DB-!MAAU-!Y4|!H04:!M0:!S0|");
   static $DESCRIPTOR (NewFileFaoDsc, "<font color=\"#ff0000\">!AZ</font>\0");

   BOOL  ConfigEdit,
         FileExists;
   int  status,
        ByteCount,
        RevDayOfWeek;
   unsigned long  DateLength;
   unsigned long  FaoVector [32];
   unsigned long  *vecptr;
   char  *cptr, *sptr, *zptr,
         *ContentTypePtr,
         *HtmlTemplatePtr,
         *TitlePtr;
   char  Bytes [32],
         DayDate [128],
         PostPath [ODS_MAX_FILE_NAME_LENGTH+1],
         FileSpec [ODS_MAX_FILE_NAME_LENGTH+1],
         SiteLogEntry [256],
         Source [1024];
   UPD_TASK  *tkptr;
   SERVICE_STRUCT  *svptr;
   REQUEST_AST AstFunction;
   $DESCRIPTOR (BytesDsc, Bytes);
   $DESCRIPTOR (DayDateDsc, DayDate);

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

   if (WATCHMOD (rqptr, WATCH_MOD_UPD))
      WatchThis (WATCHITM(rqptr), WATCH_MOD_UPD, "UpdEditFileBegin() !&Z !&Z",
                 AsNamePtr, rqptr->rqHeader.PathInfoPtr);

   /* this function CAN be called directly from the ADMIN.C module */
   if (!(tkptr = rqptr->UpdTaskPtr))
   {
      /* set up the task structure (only ever one per request!) */
      rqptr->UpdTaskPtr = tkptr =
         (UPD_TASK*) VmGetHeap (rqptr, sizeof(UPD_TASK));
      tkptr->NextTaskFunction = NextTaskFunction;
      if (!rqptr->AccountingDone++)
         InstanceGblSecIncrLong (&AccountingPtr->DoUpdateCount);
   }

   SiteLogEntry[0] = '\0';

   ConfigEdit = true;
   cptr = rqptr->rqHeader.PathInfoPtr;
   if (strsame (cptr, ADMIN_REPORT_ACCEPT, -1) ||
       strsame (cptr, ADMIN_REVISE_ACCEPT, -1))
      TitlePtr = "<h3>Edit Network Accept File</h3>\n";
   else
   if (strsame (cptr, ADMIN_REPORT_CONFIG, -1) ||
       strsame (cptr, ADMIN_REVISE_CONFIG, -1))
      TitlePtr = "<h3>Edit Server Configuration File</h3>\n";
   else
   if (strsame (cptr, ADMIN_REPORT_INLINE, -1) ||
       strsame (cptr, ADMIN_REVISE_INLINE, -1))
      TitlePtr = "<h3>Edit Inline-Configuration File</h3>\n";
   else
   if (strsame (cptr, ADMIN_REPORT_MESSAGES, -1) ||
       strsame (cptr, ADMIN_REVISE_MESSAGES, -1))
      TitlePtr = "<h3>Edit Message File</h3>\n";
   else
   if (strsame (cptr, ADMIN_REPORT_AUTH_PATHS, -1) ||
       strsame (cptr, ADMIN_REVISE_AUTH_PATHS, -1))
      TitlePtr = "<h3>Edit Path Authorization File</h3>\n";
   else
   if (strsame (cptr, ADMIN_REVISE_MAPPING, -1) ||
       strsame (cptr, ADMIN_REPORT_MAPPING, -1))
      TitlePtr = "<h3>Edit Mapping Rule File</h3>\n";
   else
   if (strsame (cptr, ADMIN_REPORT_REJECT, -1) ||
       strsame (cptr, ADMIN_REVISE_REJECT, -1))
      TitlePtr = "<h3>Edit Network Reject File</h3>\n";
   else
   if (strsame (cptr, ADMIN_REPORT_SERVICES, -1) ||
       strsame (cptr, ADMIN_REVISE_SERVICES, -1))
      TitlePtr = "<h3>Edit Service Configuration File</h3>\n";
   else
   if (strsame (cptr, ADMIN_REVISE_SITELOG, -1))
      TitlePtr = "<h3>Edit Site Log</h3>\n";
   else
   if (strsame (cptr, ADMIN_REVISE_SITELOG_ENTRY, -1))
   {
      TitlePtr = "<h3>Edit Site Log</h3>\n";
      vecptr = FaoVector;
      *vecptr++ = &rqptr->rqTime.BeginTime64;
      *vecptr++ = rqptr->RemoteUser;
      *vecptr++ = rqptr->rqAuth.RealmDescrPtr;
      *vecptr++ = rqptr->ClientPtr->Lookup.HostName;
      *vecptr++ = strlen(rqptr->RemoteUser) +
                  strlen(rqptr->rqAuth.RealmDescrPtr) +
                  strlen(rqptr->ClientPtr->Lookup.HostName) + 5;
      status = FaolToBuffer (SiteLogEntry, sizeof(SiteLogEntry), NULL,
                  "+!79*-\n+ !17&W\n+ !&;AZ.'!&;AZ'@!&;AZ\n+!#*-\n\n\n\n",
                  &FaoVector);
      if (VMSnok (status)) ErrorNoticed (rqptr, status, NULL, FI_LI);
   }
   else
   if (strsame (cptr, ADMIN_REPORT_SSL_CA, -1) ||
       strsame (cptr, ADMIN_REVISE_SSL_CA, -1))
      TitlePtr = "<h3>Edit SSL CA Verification File</h3>\n";
   else
   if (strsame (cptr, ADMIN_REVISE_HTL, sizeof(ADMIN_REVISE_HTL)-1))
      TitlePtr = "<h3>Edit Authorization List</h3>\n";
   else
   if (strsame (cptr, ADMIN_VS_REVISE_HTL, sizeof(ADMIN_VS_REVISE_HTL)-1))
      TitlePtr = "<h3>Edit Authorization List</h3>\n";
   else
   {
      ConfigEdit = false;
      TitlePtr = "";
   }

   if (WATCHPNT(rqptr) &&
       WATCH_CATEGORY(WATCH_RESPONSE))
      WatchThis (WATCHITM(rqptr), WATCH_RESPONSE,
                 "UPDATE edit !AZ", rqptr->ParseOds.ExpFileName);

   /**********************/
   /* check content type */
   /**********************/

   /* from the request file parse */
   if (rqptr->PathOds == MAPURL_PATH_ODS_ADS ||
       rqptr->PathOds == MAPURL_PATH_ODS_SMB)
      cptr = MapOdsAdsFileType (rqptr->ParseOds.NamTypePtr);
   else
   if (rqptr->PathOds == MAPURL_PATH_ODS_SRI)
      cptr = MapOdsSriFileType (rqptr->ParseOds.NamTypePtr);
   else
      cptr = rqptr->ParseOds.NamTypePtr;
   ContentTypePtr = ConfigContentType (NULL, cptr);

   if (!ConfigSameContentType (ContentTypePtr, "text/", 5) &&
       !strsame (cptr, HTL_FILE_TYPE, sizeof(HTL_FILE_TYPE)-1))
   {
      rqptr->rqResponse.HttpStatus = 400;
      ErrorGeneral (rqptr, MsgFor(rqptr,MSG_UPD_NOT_TEXT_FILE), FI_LI);
      UpdEnd (rqptr);
      return;
   }

   /*************/
   /* open file */
   /*************/

   AuthAccessEnable (rqptr, rqptr->ParseOds.ExpFileName, AUTH_ACCESS_READ);

   OdsOpen (&tkptr->FileOds,
            rqptr->ParseOds.ExpFileName, rqptr->ParseOds.ExpFileNameLength,
            NULL, 0, 0, FAB$M_GET, FAB$M_SHRGET, NULL, rqptr);  

   AuthAccessEnable (rqptr, 0, 0);

   status = tkptr->FileOds.Fab.fab$l_sts;
   if (VMSnok (status) && status != RMS$_FNF)
   {
      rqptr->rqResponse.ErrorTextPtr = rqptr->rqHeader.PathInfoPtr;
      rqptr->rqResponse.ErrorOtherTextPtr = rqptr->ParseOds.ExpFileName;
      ErrorVmsStatus (rqptr, status, FI_LI);
      UpdEnd (rqptr);
      return;
   }

   if (status == RMS$_FNF)
      FileExists = tkptr->RecordFormatFixed = false;
   else
   {
      FileExists = true;
      if (tkptr->FileOds.Fab.fab$b_rfm == FAB$C_FIX ||
          tkptr->FileOds.Fab.fab$b_rfm == FAB$C_UDF)
         tkptr->RecordFormatFixed = true;
      else
         tkptr->RecordFormatFixed = false;
   }

   if (VMSnok (status = OdsParseTerminate (&tkptr->FileOds)))
   {
      ErrorNoticed (rqptr, status, NULL, FI_LI);
      UpdEnd (rqptr);
      return;
   }

   if (FileExists)
   {
      /* record access block */
      tkptr->FileOds.Rab = cc$rms_rab;
      tkptr->FileOds.Rab.rab$l_ctx = rqptr;
      tkptr->FileOds.Rab.rab$l_fab = &tkptr->FileOds.Fab;
      /* 2 buffers, transfer 16 blocks */
      tkptr->FileOds.Rab.rab$b_mbc = 16;
      tkptr->FileOds.Rab.rab$b_mbf = 2;
      /* read ahead performance option */
      tkptr->FileOds.Rab.rab$l_rop = RAB$M_RAH;
      tkptr->FileOds.Rab.rab$l_ubf = tkptr->EditBuffer;
      /* allow for two extra characters, a newline and a terminating null */
      tkptr->FileOds.Rab.rab$w_usz = sizeof(tkptr->EditBuffer)-2;

      status = sys$connect (&tkptr->FileOds.Rab, 0, 0);
      if (VMSnok (status))
      {
         rqptr->rqResponse.ErrorTextPtr = rqptr->rqHeader.PathInfoPtr;
         rqptr->rqResponse.ErrorOtherTextPtr = rqptr->ParseOds.ExpFileName;
         ErrorVmsStatus (rqptr, status, FI_LI);
         UpdEnd (rqptr);
         return;
      }
   }

   /**************/
   /* begin page */
   /**************/

   if (!ConfigEdit)
   {
      cptr = MsgFor(rqptr,MSG_UPD_EDIT);
      zptr = (sptr = tkptr->MsgString) + sizeof(tkptr->MsgString);
      while (*cptr && sptr < zptr)
      {
         /* don't need any extraneous linefeeds from this long string */
         if (*cptr == '\n')
            cptr++;
         else
            *sptr++ = *cptr++;
      }
      if (sptr >= zptr)
      {
         ErrorGeneralOverflow (rqptr, FI_LI);
         UpdEnd (rqptr);
         return;
      }
      *sptr = '\0';

      /* "Start at the very beginning, very good place to start ..." */
      tkptr->MsgStringPtr = tkptr->MsgString;
   }

   if (FileExists)
   {
      if (ConfigEdit || NaturalLanguageEnglish)
      {
         lib$day_of_week (&tkptr->FileOds.XabDat.xab$q_rdt, &RevDayOfWeek);
         sys$fao (&DayDateFaoDsc, 0, &DayDateDsc,
                  DayName[RevDayOfWeek], &tkptr->FileOds.XabDat.xab$q_rdt);
      }
      else
      {
         static unsigned long  LibDateTimeContext = 0,
                               LibOutputFormat = LIB$K_OUTPUT_FORMAT;

         if (!LibDateTimeContext)
         {
            /* initialize this particular date/time format */
            if (VMSnok (status =
               lib$init_date_time_context (&LibDateTimeContext,
                                           &LibOutputFormat,
                                           &OutputFormatDsc)))
            {
               rqptr->rqResponse.ErrorTextPtr = "lib$init_date_time_context()";
               ErrorVmsStatus (rqptr, status, FI_LI);
               UpdEnd (rqptr);
               return;
            }
         }

         if (VMSnok (status =
             lib$format_date_time (&DayDateDsc,
                                   &tkptr->FileOds.XabDat.xab$q_rdt,
                                   &LibDateTimeContext, &DateLength, 0)))
         {
            if (status != LIB$_ENGLUSED)
            {
               rqptr->rqResponse.ErrorTextPtr = "lib$format_date_time()";
               ErrorVmsStatus (rqptr, status, FI_LI);
               UpdEnd (rqptr);
               return;
            }
         }
         DayDate[DateLength] = '\0';
      }

      /* true for non-VAR record formats, almost true for those :^) */
      if (tkptr->FileOds.XabFhc.xab$l_ebk)
         ByteCount = ((tkptr->FileOds.XabFhc.xab$l_ebk - 1) * 512) +
                     tkptr->FileOds.XabFhc.xab$w_ffb;
      else
         ByteCount = tkptr->FileOds.XabFhc.xab$w_ffb;
      sys$fao (&BytesFaoDsc, 0, &BytesDsc, ByteCount);

      if (!ConfigEdit)
      {
         /* retrieve the previously saved position in the string */
         cptr = tkptr->MsgStringPtr;
         /* step over "New Document" */
         while (*cptr && *cptr != '|') cptr++;
         if (*cptr) cptr++;
        /* save the current position for use in later parts of the page */
        tkptr->MsgStringPtr = cptr;
     }
   }
   else
   {
      if (ConfigEdit)
         sptr = "New File";
      else
      {
         /* "New Document" */
         sptr = cptr = tkptr->MsgStringPtr;
         while (*cptr && *cptr != '|') cptr++;
         if (*cptr) *cptr++ = '\0';
         /* save the current position for use in later parts of the page */
         tkptr->MsgStringPtr = cptr;
      }

      if (VMSnok (status = sys$fao (&NewFileFaoDsc, 0, &DayDateDsc, sptr)))
         strzcpy (DayDate, "New File", sizeof(DayDate));

      Bytes[0] = '\0';
   }

   zptr = (sptr = PostPath) + sizeof(PostPath);
   if (ConfigEdit)
      cptr = rqptr->rqHeader.PathInfoPtr;
   else
      cptr = MapVmsPath (rqptr->ParseOds.ExpFileName, rqptr);
   /* remove any explicit version from the path to be POSTed */
   while (*cptr && sptr < zptr) *sptr++ = *cptr++;
   if (AsNamePtr[0] && sptr < zptr)
   {
      /* eliminate the trailing file name */
      while (sptr > PostPath && *sptr != '/') sptr--;
      if (*sptr == '/') sptr++;
      /* add the destination file name */
      for (cptr = AsNamePtr; *cptr && sptr < zptr; *sptr++ = *cptr++);
   }
   if (sptr >= zptr)
   {
      ErrorGeneralOverflow (rqptr, FI_LI);
      UpdEnd (rqptr);
      return;
   }
   *sptr = '\0';

   if (WATCHMOD (rqptr, WATCH_MOD_UPD))
      WatchThis (WATCHITM(rqptr), WATCH_MOD_UPD, "!&Z", PostPath);

   if (ConfigEdit)
   {
      /**************************************************/
      /* editing a configuration file from server admin */
      /**************************************************/

      vecptr = FaoVector;

      *vecptr++ = tkptr->FileOds.NamDevicePtr;
      *vecptr++ = PostPath;
      *vecptr++ = DayDate;

      if (AsNamePtr[0])
         *vecptr++ = "Save As";
      else
      if (FileExists)
         *vecptr++ = "Update";
      else
         *vecptr++ = "Create";

      status = FaolToBuffer (Source, sizeof(Source), NULL,
"<p><table class=\"ctgry\">\n\
<tr><th class=\"ctttl\">Source: &quot;File&quot;</th></tr>\n\
<tr><td>\n\
<table class=\"rghtlft\">\n\
<tr><th>File:</th><td>!AZ</td><td style=\"padding-left:1em;\">\
<a class=\"abttn\" target=\"_blank\" href=\"!&;AZ\">View</a>\
</td></tr>\n\
<tr><th>Revised:</th><td>!AZ</td></tr>\n\
</table>\n\
</td></tr>\n\
</table>\n",
                             &FaoVector);

      /* the administration menus, etc. are always in English (for now!) */
      strzcpy (tkptr->MsgStringPtr = tkptr->MsgString,
"Update|Save As|Update|Create|Preview|Undo Editing|Change Edit Window",
               sizeof(tkptr->MsgString));
   }
   else
   {
      /******************************/
      /* editing any other document */
      /******************************/

      /* retrieve the previously saved position in the string */
      cptr = tkptr->MsgStringPtr;

      vecptr = FaoVector;

      /* "Document" */
      *vecptr++ = cptr;
      while (*cptr && *cptr != '|') cptr++;
      if (*cptr) *cptr++ = '\0';

      *vecptr++ = rqptr->rqHeader.PathInfoPtr;
      *vecptr++ = rqptr->rqHeader.PathInfoPtr;
      *vecptr++ = Bytes;

      *vecptr++ = UPD_HELP_PATH;

      /* "Help" */
      *vecptr++ = cptr;
      while (*cptr && *cptr != '|') cptr++;
      if (*cptr) *cptr++ = '\0';

      /* "Revised" */
      *vecptr++ = cptr;
      while (*cptr && *cptr != '|') cptr++;
      if (*cptr) *cptr++ = '\0';

      *vecptr++ = DayDate;

      /* save the current position for use in later parts of the page */
      tkptr->MsgStringPtr = cptr;

      if (AsNamePtr[0] && FileExists)
      {
         char  ch;

         /* "Save As" */
         while (*cptr && *cptr != '|') cptr++;
         if (*cptr) cptr++;
         *vecptr++ = cptr;
         while (*cptr && *cptr != '|') cptr++;
         ch = *cptr;
         *cptr = '\0';

         *vecptr++ = PostPath;
         *vecptr++ = PostPath;

         status = FaolToBuffer (Source, sizeof(Source), NULL,
"<table class==\"ctgry\">\n\
<tr><td>\n\
<table class=\"rghtlft\">\n\
<tr><th>!AZ:</th>\
<td><a class=\"abttn\" target=\"_blank\" \
href=\"!&%AZ\">!&;AZ</a>!AZ</td>\n\
<td style=\"padding-left:1em;\">\
<a class=\"abttn\" target=\"_blank\" href=\"!&%AZ\">!&;AZ</a></th></td>\n\
</tr>\n\
<tr><th>!&;AZ:</th><td>!&;AZ</td></tr>\n\
<tr><th>!&;AZ:</th><td>\
<a class=\"abttn\" target=\"_blank\" href=\"!&%AZ\">!&;AZ</a></td>\
</tr>\n\
</table>\n\
</td></tr>\n\
</table>\n",
            &FaoVector);

         *cptr = ch;
      }
      else
      {
         status = FaolToBuffer (Source, sizeof(Source), NULL,
"<table class=\"ctgry\">\n\
<tr><td>\n\
<table class=\"rghtlft\">\n\
<tr><th>!AZ:</th>\
<td><a target=\"_blank\" href=\"!&%AZ\">!&;AZ</a>!AZ</td>\n\
<td style=\"padding-left:1em;\">\
<a class=\"abttn\" target=\"_blank\" href=\"!&%AZ\">!&;AZ</a></td>\
</tr>\n\
<tr><th>!AZ:</th><td>!AZ</td></tr>\n\
</table>\n\
</td></tr>\n\
</table>\n",
            &FaoVector);
      }
   }
   if (VMSnok (status) || status == SS$_BUFFEROVF)
   {
      rqptr->rqResponse.ErrorTextPtr = "FaolToBuffer()";
      ErrorVmsStatus (rqptr, status, FI_LI);
      UpdEnd (rqptr);
      return;
   }

   if (!tkptr->CxR[0]) strzcpy (tkptr->CxR, "80x24", sizeof(tkptr->CxR));

   if (!FileExists &&
       ConfigSameContentType (ContentTypePtr, "text/html", 9))
   {
      HtmlTemplatePtr =
"&lt;HTML&gt;\n\
&lt;HEAD&gt;\n\
&lt;TITLE&gt;&lt;/TITLE&gt;\n\
&lt;/HEAD&gt;\n\
&lt;BODY&gt;\n\
&lt;/BODY&gt;\n\
&lt;/HTML&gt;\n";
   }
   else
      HtmlTemplatePtr = "";

   cptr = tkptr->CxR;
   tkptr->Cols = atoi(cptr);
   while (*cptr && to_lower(*cptr) != 'x') cptr++;
   if (*cptr) cptr++;
   tkptr->Rows = atoi(cptr);

   if (tkptr->Cols <= 0)
      tkptr->Cols = 80;
   else
   if (tkptr->Cols >= 132)
      tkptr->Cols = 132;
   if (tkptr->Rows <= 0)
      tkptr->Rows = 12;
   else
   if (tkptr->Rows >= 48)
      tkptr->Rows = 48;

   /* ensure CSP nonce is generated before header */
   GenerateCspNonce (rqptr);

   /*
      The file edit page is deliberately not pre-expired.
      To do so results in the page being reloaded each time it's displayed.
   */
   ResponseHeader200 (rqptr, "text/html", NULL);

   /* retrieve the previously saved position in the string */
   cptr = tkptr->MsgStringPtr;

   vecptr = FaoVector;

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

   /* ordinary editing or server administration file editing again */
   if (ConfigEdit)
   {
      *vecptr++ = "WASD";
      *vecptr++ = ServerHostPort;
      *vecptr++ = GenerateCspNonce (rqptr);
      *vecptr++ = UpdSetClicks (rqptr);
      *vecptr++ = ADMIN_BODY_TAG;
      *vecptr++ = "WASD";
      *vecptr++ = ServerHostPort;
   }
   else
   {
      *vecptr++ = cptr;
      *vecptr++ = rqptr->ServicePtr->ServerHostPort;
      *vecptr++ = GenerateCspNonce (rqptr);
      *vecptr++ = UpdSetClicks (rqptr);
      *vecptr++ = ADMIN_BODY_TAG;
      *vecptr++ = cptr;
      *vecptr++ = rqptr->ServicePtr->ServerHostPort;
   }

   /* step over the "Update" */
   while (*cptr && *cptr != '|') cptr++;
   if (*cptr) *cptr++ = '\0';

   *vecptr++ = TitlePtr;
   *vecptr++ = Source;
   *vecptr++ = PostPath;

   if (AsNamePtr[0])
   {
      /* "save as" */
      *vecptr++ = cptr;
      while (*cptr && *cptr != '|') cptr++;
      if (*cptr) *cptr++ = '\0';
      /* step over "Update" */
      while (*cptr && *cptr != '|') cptr++;
      if (*cptr) cptr++;
      /* step over "Create" */
      while (*cptr && *cptr != '|') cptr++;
      if (*cptr) cptr++;
   }
   else
   if (FileExists)
   {
      /* step over "save as" */
      while (*cptr && *cptr != '|') cptr++;
      if (*cptr) cptr++;
      /* "Update" */
      *vecptr++ = cptr;
      while (*cptr && *cptr != '|') cptr++;
      if (*cptr) *cptr++ = '\0';
      /* step over "Create" */
      while (*cptr && *cptr != '|') cptr++;
      if (*cptr) cptr++;
   }
   else
   {
      /* step over "save as" */
      while (*cptr && *cptr != '|') cptr++;
      if (*cptr) cptr++;
      /* step over "Update" */
      while (*cptr && *cptr != '|') cptr++;
      if (*cptr) cptr++;
      /* "Create" */
      *vecptr++ = cptr;
      while (*cptr && *cptr != '|') cptr++;
      if (*cptr) *cptr++ = '\0';
   }

   /* "Preview" */
   if (ConfigEdit)
      *vecptr++ = "";
   else
   {
      *vecptr++ =
"<input type=\"submit\" name=\"previewonly\" value=\"!AZ\">\n\
<input type=\"hidden\" name=\"previewnote\" value=\"!AZ!AZ\">\n";

      *vecptr++ = cptr;
      if (ConfigSameContentType (ContentTypePtr, "text/html", -1))
      {
         *vecptr++ = UPD_PREVIEW_NOTE_HTML;
         *vecptr++ = UPD_PREVIEW_NOTE_HTML_NEWLINE;
      }
      else
      if (ConfigSameContentType (ContentTypePtr, "text/x-menu", -1))
      {
         *vecptr++ = UPD_PREVIEW_NOTE_HTML;
         *vecptr++ = UPD_PREVIEW_NOTE_MENU_NEWLINE;
      }
      else
      {
         *vecptr++ = UPD_PREVIEW_NOTE_PLAIN;
         *vecptr++ = "";
      }
   }
   while (*cptr && *cptr != '|') cptr++;
   if (*cptr) *cptr++ = '\0';

   /* Undo Editing */
   *vecptr++ = cptr;
   while (*cptr && *cptr != '|') cptr++;
   if (*cptr) *cptr++ = '\0';

   /* save the current position for use in later parts of the page */
   tkptr->MsgStringPtr = cptr;

   /* file protection */
   *vecptr++ = tkptr->ProtectionList;

   *vecptr++ = tkptr->Cols;
   *vecptr++ = tkptr->Rows;

   *vecptr++ = SiteLogEntry;
   *vecptr++ = HtmlTemplatePtr;

   status = FaolToNet (rqptr, ResponseFao, &FaoVector);

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

   if (FileExists)
      AstFunction = &UpdEditFileNextRecord;
   else
      AstFunction = &UpdEditFileEnd;

   NetWriteFullFlush (rqptr, AstFunction);
}

/*****************************************************************************/
/*
Queue a read of the next record from the file.  When the read completes call 
UpdEditFileNextRecordAst() function to HTML-escape and include in the text
area.
*/ 

void UpdEditFileNextRecord (REQUEST_STRUCT *rqptr)

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

   if (WATCHMOD (rqptr, WATCH_MOD_UPD))
      WatchThis (WATCHITM(rqptr), WATCH_MOD_UPD,
                 "UpdEditFileNextRecord() !&F", &UpdEditFileNextRecord);

   /* asynchronous get service */
   rqptr->UpdTaskPtr->FileOds.Rab.rab$l_rop |= RAB$M_ASY;
   sys$get (&rqptr->UpdTaskPtr->FileOds.Rab,
            &UpdEditFileNextRecordAst,
            &UpdEditFileNextRecordAst);
}

/*****************************************************************************/
/*
A record has been read.  Ensure it is newline terminated.  Escape any
HTML-forbidden characters (it is being included within <textarea></textarea>
tags!).  Buffer it.
*/ 

void UpdEditFileNextRecordAst (struct RAB *RabPtr)

{
   int  rsz,
        status,
        Length;
   REQUEST_STRUCT  *rqptr;
   UPD_TASK  *tkptr;

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

   rqptr = RabPtr->rab$l_ctx;

   if (WATCHMOD (rqptr, WATCH_MOD_UPD))
      WatchThis (WATCHITM(rqptr), WATCH_MOD_UPD,
                 "UpdEditFileNextRecordAst() !&F sts:!&X stv:!&X rsz:!UL\n",
                 &UpdEditFileNextRecordAst,
                 RabPtr->rab$l_sts, RabPtr->rab$l_stv,  RabPtr->rab$w_rsz);

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

   tkptr = rqptr->UpdTaskPtr;

   if (VMSnok (tkptr->FileOds.Rab.rab$l_sts))
   {
      if (tkptr->FileOds.Rab.rab$l_sts == RMS$_EOF)
      {
         UpdEditFileEnd (rqptr);
         return;
      }

      rqptr->rqResponse.ErrorTextPtr = rqptr->rqHeader.PathInfoPtr;
      rqptr->rqResponse.ErrorOtherTextPtr = tkptr->FileOds.ExpFileName;
      ErrorVmsStatus (rqptr, tkptr->FileOds.Rab.rab$l_sts, FI_LI);
      UpdEnd (rqptr);
      return;
   }

   rsz = tkptr->FileOds.Rab.rab$w_rsz;
   if (!tkptr->RecordFormatFixed)
   {
      /* add newline if empty, or if last character in line not a newline! */
      if (rsz && tkptr->FileOds.Rab.rab$l_ubf[rsz-1] != '\n')
         tkptr->FileOds.Rab.rab$l_ubf[rsz++] = '\n';
      else
      if (!rsz)
         tkptr->FileOds.Rab.rab$l_ubf[rsz++] = '\n';
   }
   tkptr->FileOds.Rab.rab$l_ubf[rsz] = '\0';

   status = FaoToNet (rqptr, "!&;AZ", tkptr->FileOds.Rab.rab$l_ubf);
   if (VMSnok (status)) ErrorNoticed (rqptr, status, NULL, FI_LI);

   NetWritePartFlush (rqptr, &UpdEditFileNextRecord);
}

/*****************************************************************************/
/*
End-of-file detected.  End text area, add form submit botton, change text
edit area form, etc., complete editing page HTML.
*/

void UpdEditFileEnd (REQUEST_STRUCT *rqptr)

{
   int  status;
   unsigned long  FaoVector [16];
   unsigned long  *vecptr;
   char  *cptr;
   UPD_TASK  *tkptr;

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

   if (WATCHMOD (rqptr, WATCH_MOD_UPD))
      WatchThis (WATCHITM(rqptr), WATCH_MOD_UPD, "UpdEditFileEnd()");

   tkptr = rqptr->UpdTaskPtr;

   vecptr = FaoVector;

   *vecptr++ = rqptr->ScriptName;
   *vecptr++ = rqptr->rqHeader.PathInfoPtr;

   /* "Change Edit Window" */
   *vecptr++ = cptr = tkptr->MsgStringPtr;
   while (*cptr && *cptr != '|') cptr++;
   if (*cptr) *cptr++ = '\0';

   *vecptr++ = tkptr->Cols;
   *vecptr++ = tkptr->Rows;
   *vecptr++ = tkptr->Cols;
   *vecptr++ = tkptr->Rows;

   status = FaolToNet (rqptr,
"</textarea>\n\
</form>\n\
<p><form method=\"GET\" action=\"!&%AZ!&%AZ\">\n\
<input type=\"submit\" value=\"!AZ\">\n\
<select name=\"cxr\" id=\"cxr\" \
ONCHANGE=\"return changeEditWindow()\">\n\
<option value=\"!ULx!UL\" selected>!ULx!UL\n\
<option value=\"132x48\">132x48\n\
<option value=\"132x24\">132x24\n\
<option value=\"132x16\">132x16\n\
<option value=\"132x12\">132x12\n\
<option value=\"80x48\">80x48\n\
<option value=\"80x24\">80x24\n\
<option value=\"80x16\">80x16\n\
<option value=\"80x12\">80x12\n\
<option value=\"40x48\">40x48\n\
<option value=\"40x24\">40x24\n\
<option value=\"40x12\">40x12\n\
</select>\n\
</form>\n\
</div>\n\
<span id=\"ender\"><span>\n\
</body>\n\
</html>\n",
      &FaoVector);

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

   UpdEnd (rqptr);
}

/*****************************************************************************/
/*
Rename a file.
*/

void UpdFileRename
(
REQUEST_STRUCT *rqptr,
char *OldOne,
char *NewOne
)
{
   int  status,
        RenameCount;
   unsigned short  Length;
   unsigned long  Context,
                  RenameFlags;
   char  *cptr;
   char  NewName [ODS_MAX_FILE_NAME_LENGTH+1],
         NewFileName [ODS_MAX_FILE_NAME_LENGTH+1],
         OldName [ODS_MAX_FILE_NAME_LENGTH+1],
         OldFileName [ODS_MAX_FILE_NAME_LENGTH+1],
         Scratch [ODS_MAX_FILE_NAME_LENGTH+1];
   UPD_TASK  *tkptr;
   $DESCRIPTOR (OldFileNameDsc, OldFileName);
   $DESCRIPTOR (NewFileNameDsc, NewFileName);

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

   if (WATCHMOD (rqptr, WATCH_MOD_UPD))
      WatchThis (WATCHITM(rqptr), WATCH_MOD_UPD,
                 "UpdFileRename() !&Z !&Z !&Z",
                 rqptr->rqHeader.PathInfoPtr, OldOne, NewOne);

   tkptr = rqptr->UpdTaskPtr;

   /* authentication is mandatory for a PUT, DELETE or POST */
   if (!rqptr->RemoteUser[0] ||
       !((rqptr->rqAuth.RequestCan & HTTP_METHOD_PUT) ||
         (rqptr->rqAuth.RequestCan & HTTP_METHOD_POST) ||
         (rqptr->rqAuth.RequestCan & HTTP_METHOD_DELETE)))
   {
      rqptr->rqResponse.ErrorTextPtr = rqptr->rqHeader.PathInfoPtr;
      ErrorVmsStatus (rqptr, SS$_NOPRIV, FI_LI);
      UpdEnd (rqptr);
      return;
   }

   if (!OldOne[0] || !NewOne[0] || rqptr->rqHeader.Method != HTTP_METHOD_POST)
   {
      ErrorInternal (rqptr, 0, ErrorSanityCheck, FI_LI);
      UpdEnd (rqptr);
      return;
   }

   MapOdsUrlToVms (OldOne, OldName, sizeof(OldName), '_',
                   rqptr->rqPathSet.MapEllipsis, rqptr->PathOds);
   FaoToBuffer (OldFileName, sizeof(OldFileName), &Length, "!AZ!AZ",
                rqptr->ParseOds.ExpFileName, OldName);
   OldFileNameDsc.dsc$w_length = Length;

   MapOdsUrlToVms (NewOne, NewName, sizeof(NewName), '_',
                    rqptr->rqPathSet.MapEllipsis, rqptr->PathOds);
   FaoToBuffer (NewFileName, sizeof(NewFileName), &Length, "!AZ!AZ",
                rqptr->ParseOds.ExpFileName, NewName);
   NewFileNameDsc.dsc$w_length = Length;

   if (WATCHPNT(rqptr) &&
       WATCH_CATEGORY(WATCH_RESPONSE))
      WatchThis (WATCHITM(rqptr), WATCH_RESPONSE,
                 "UPDATE rename !AZ !AZ", OldFileName, NewFileName);

   if (strsame (OldName, NewName, -1))
   {
      ErrorGeneral (rqptr, MsgFor(rqptr,MSG_UPD_RENAME_SAME), FI_LI);
      UpdEnd (rqptr);
      return;
   }

   AuthAccessEnable (rqptr, rqptr->ParseOds.ExpFileName, AUTH_ACCESS_WRITE);

   RenameFlags = 0x1;
#ifdef ODS_EXTENDED
   if (rqptr->PathOdsExtended) RenameFlags += 0x4;
#endif /* ODS_EXTENDED */

   RenameCount = Context = 0;
   while (VMSok (status =
          lib$rename_file (&OldFileNameDsc, &NewFileNameDsc,
                           0, 0, &RenameFlags, 0, 0, 0, 0, 0, 0, &Context)))
      RenameCount++;

   AuthAccessEnable (rqptr, 0, 0);

   if (!RenameCount)
   {
      rqptr->rqResponse.ErrorOtherTextPtr = rqptr->ParseOds.ExpFileName;
      if (status == RMS$_FNF)
      {
         FaoToBuffer (Scratch, sizeof(Scratch), NULL, "!AZ!AZ", 
                      rqptr->rqHeader.PathInfoPtr, OldOne);
         rqptr->rqResponse.ErrorTextPtr = Scratch;
         ErrorVmsStatus (rqptr, status, FI_LI);
      }
      else
      if (status == RMS$_SYN)
      {
         FaoToBuffer (Scratch, sizeof(Scratch), NULL, "!AZ!AZ", 
                      rqptr->rqHeader.PathInfoPtr, NewOne);
         rqptr->rqResponse.ErrorTextPtr = Scratch;
         ErrorVmsStatus (rqptr, status, FI_LI);
      }
      else
      {
         FaoToBuffer (Scratch, sizeof(Scratch), NULL, "!AZ!AZ", 
                      rqptr->rqHeader.PathInfoPtr, NewOne);
         rqptr->rqResponse.ErrorTextPtr = Scratch;
         ErrorVmsStatus (rqptr, status, FI_LI);
      }
      UpdEnd (rqptr);
      return;
   }

   /***********/
   /* success */
   /***********/

   ReportSuccess (rqptr,
      "!AZ&nbsp; !&;&_&[AZ&nbsp; !AZ&nbsp; !&;&_&[AZ\n",
       MsgFor(rqptr,MSG_GENERAL_FILE), rqptr->PathOds, OldName,
       MsgFor(rqptr,MSG_UPD_RENAMED), rqptr->PathOds, NewName);

   UpdEnd (rqptr);
}

/*****************************************************************************/
/*
Copy a file by reading each 512 byte block and converting these into
4 x 128 byte, hexadecimal-encoded, form fields that can be recreated into a
file when POSTed to this server (see "hexencoded" in BODY.C module).
*/

void UpdCopyFileBegin
(
REQUEST_STRUCT *rqptr,
char *AsNamePtr
)
{
   static char  ResponseFao [] =
"!AZ\
<html>\n\
<head>\n\
!AZ\
!AZ\
!&@\
<title>!AZ !AZ</title>\n\
</head>\n\
!AZ\n\
<div class=\"wasd\">\n\
<h2>!AZ !AZ</h2>\n\
<form method=\"POST\" action=\"!&%AZ\">\n\
<input type=\"hidden\" name=\"protection\" name=\"protection\" value=\"!4XL\">\n\
<p><table class=\"ctgry\">\n\
<tr><td>\n\
<table class=\"rghtlft\">\n\
<tr><th>!AZ</th>\
<td><a class=\"alnk\" target=\"_blank\" href=\"!&%AZ\">!&;&[AZ</a></td>\
<td rowspan=\"2\" style=\"padding-left:1em;\">\
<input type=\"submit\" value=\"!AZ\">\
</td></tr>\n\
<tr><th>!AZ</th>\
<td><a class=\"alnk\" target=\"_blank\" href=\"!&%AZ\">!&;&[AZ</a>\
</td></tr>\n\
</table>\n\
</td></tr>\n\
</table>\n\
<input type=\"hidden\" name=\"fab$b_rfm\" value=\"!UL\">\n\
<input type=\"hidden\" name=\"fab$b_rat\" value=\"!UL\">\n";

   int  status,
        ByteCount,
        RevDayOfWeek;
   unsigned long  FaoVector [32];
   unsigned long  *vecptr;
   char  *cptr, *sptr, *zptr,
         *ToPtr;
   char  PostPath [ODS_MAX_FILE_NAME_LENGTH+1],
         Scratch [256];
   UPD_TASK  *tkptr;

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

   if (WATCHMOD (rqptr, WATCH_MOD_UPD))
      WatchThis (WATCHITM(rqptr), WATCH_MOD_UPD,
                 "UpdCopyFileBegin() !&Z", AsNamePtr);

   tkptr = rqptr->UpdTaskPtr;

   if (WATCHPNT(rqptr) &&
       WATCH_CATEGORY(WATCH_RESPONSE))
      WatchThis (WATCHITM(rqptr), WATCH_RESPONSE,
                 "UPDATE copy !AZ", rqptr->ParseOds.ExpFileName);

   AuthAccessEnable (rqptr, rqptr->ParseOds.ExpFileName, AUTH_ACCESS_READ);

   OdsOpen (&tkptr->FileOds,
            rqptr->ParseOds.ExpFileName, rqptr->ParseOds.ExpFileNameLength,
            NULL, 0, FAB$M_GET | FAB$M_BIO, 0, FAB$M_SHRGET,
            NULL, rqptr);  

   AuthAccessEnable (rqptr, 0, 0);

   if (VMSnok (status = tkptr->FileOds.Fab.fab$l_sts))
   {
      rqptr->rqResponse.ErrorTextPtr = rqptr->rqHeader.PathInfoPtr;
      rqptr->rqResponse.ErrorOtherTextPtr = rqptr->ParseOds.ExpFileName;
      ErrorVmsStatus (rqptr, status, FI_LI);
      UpdEnd (rqptr);
      return;
   }

   if (tkptr->FileOds.Fab.fab$b_org != FAB$C_SEQ)
   {
      rqptr->rqResponse.ErrorTextPtr = rqptr->rqHeader.PathInfoPtr;
      rqptr->rqResponse.ErrorOtherTextPtr = rqptr->ParseOds.ExpFileName;
      ErrorVmsStatus (rqptr, SS$_UNSUPPORTED, FI_LI);
      UpdEnd (rqptr);
      return;
   }

   if (VMSnok (status = OdsParseTerminate (&tkptr->FileOds)))
   {
      ErrorNoticed (rqptr, status, NULL, FI_LI);
      UpdEnd (rqptr);
      return;
   }

   /* record access block */
   tkptr->FileOds.Rab = cc$rms_rab;
   tkptr->FileOds.Rab.rab$l_ctx = rqptr;
   tkptr->FileOds.Rab.rab$l_fab = &tkptr->FileOds.Fab;
   tkptr->FileOds.Rab.rab$l_bkt = 0;
   tkptr->FileOds.Rab.rab$l_rop = RAB$M_RAH | RAB$M_BIO;
   tkptr->FileOds.Rab.rab$l_ubf = tkptr->CopyBuffer;
   /* MUST be 512 bytes (one block) */
   tkptr->FileOds.Rab.rab$w_usz = sizeof(tkptr->CopyBuffer);

   status = sys$connect (&tkptr->FileOds.Rab, 0, 0);
   if (VMSnok (status))
   {
      rqptr->rqResponse.ErrorTextPtr = rqptr->rqHeader.PathInfoPtr;
      rqptr->rqResponse.ErrorOtherTextPtr = tkptr->FileOds.ExpFileName;
      ErrorVmsStatus (rqptr, status, FI_LI);
      UpdEnd (rqptr);
      return;
   }

   /**************/
   /* begin page */
   /**************/

   /* remove any explicit version from the path to be POSTed */
   zptr = (sptr = PostPath) + sizeof(PostPath);
   /* a full (local) path could have been supplied */
   if (AsNamePtr[0] != '/')
      for (cptr = rqptr->rqHeader.PathInfoPtr;
           *cptr && sptr < zptr;
           *sptr++ = *cptr++);
   if (sptr < zptr)
   {
      /* eliminate the trailing file name */
      while (sptr > PostPath && *sptr != '/') sptr--;
      if (*sptr == '/') sptr++;
      /* add the destination file name */
      for (cptr = AsNamePtr; *cptr && sptr < zptr; *sptr++ = *cptr++);
   }
   if (sptr >= zptr)
   {
      ErrorGeneralOverflow (rqptr, FI_LI);
      UpdEnd (rqptr);
      return;
   }
   *sptr = '\0';

   rqptr->rqResponse.PreExpired = PRE_EXPIRE_UPD;
   ResponseHeader200 (rqptr, "text/html", NULL);

   cptr = MsgFor(rqptr,MSG_UPD_COPY);
   zptr = (sptr = Scratch) + sizeof(Scratch);
   while (*cptr && sptr < zptr) *sptr++ = *cptr++;
   if (sptr >= zptr)
   {
      ErrorGeneralOverflow (rqptr, FI_LI);
      UpdEnd (rqptr);
      return;
   }
   *sptr = '\0';

   vecptr = FaoVector;

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

   /* "Update" */
   *vecptr++ = cptr = Scratch;
   *vecptr++ = rqptr->ServicePtr->ServerHostPort;

   *vecptr++ = ADMIN_BODY_TAG;

   /* "Update" again */
   *vecptr++ = cptr;
   while (*cptr && *cptr != '|') cptr++;
   if (*cptr) *cptr++ = '\0';
   *vecptr++ = rqptr->ServicePtr->ServerHostPort;

   *vecptr++ = PostPath;
   *vecptr++ = tkptr->ProtectionMask;

   /* "Copy" */
   *vecptr++ = cptr;
   while (*cptr && *cptr != '|') cptr++;
   if (*cptr) *cptr++ = '\0';

   *vecptr++ = rqptr->rqHeader.PathInfoPtr;
   *vecptr++ = rqptr->PathOds;
   *vecptr++ = rqptr->rqHeader.PathInfoPtr;

   /* "Confirm" (for historical reasons "confirm" is after "to") */
   ToPtr = cptr;
   while (*cptr && *cptr != '|') cptr++;
   if (*cptr) *cptr++ = '\0';
   *vecptr++ = cptr;
   while (*cptr && *cptr != '|') cptr++;
   if (*cptr) *cptr++ = '\0';

   /* "To" */
   *vecptr++ = ToPtr;
   while (*cptr && *cptr != '|') cptr++;
   if (*cptr) *cptr++ = '\0';

   *vecptr++ = PostPath;
   *vecptr++ = rqptr->PathOds;
   *vecptr++ = PostPath;

   *vecptr++ = tkptr->FileOds.Fab.fab$b_rfm;
   *vecptr++ = tkptr->FileOds.Fab.fab$b_rat;

   status = FaolToNet (rqptr, ResponseFao, &FaoVector);

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

   NetWriteFullFlush (rqptr, &UpdCopyFileNextBlock);
}

/*****************************************************************************/
/*
Queue a read of the next record from the file.  When the read completes call 
UpdCopyFileNextBlockAst() function to HTML-escape and include in the text
area.
*/ 

void UpdCopyFileNextBlock (REQUEST_STRUCT *rqptr)

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

   if (WATCHMOD (rqptr, WATCH_MOD_UPD))
      WatchThis (WATCHITM(rqptr), WATCH_MOD_UPD,
                 "UpdCopyFileNextBlock() !&F", &UpdCopyFileNextBlock);

   rqptr->UpdTaskPtr->FileOds.Rab.rab$l_bkt++;

   /* asynchronous read service */
   rqptr->UpdTaskPtr->FileOds.Rab.rab$l_rop |= RAB$M_ASY;
   sys$read (&rqptr->UpdTaskPtr->FileOds.Rab,
             &UpdCopyFileNextBlockAst,
             &UpdCopyFileNextBlockAst);
}

/*****************************************************************************/
/*
A record has been read.  Turn the record into 128 byte, hexdecimal-encoded
form hidden fields.
*/ 

void UpdCopyFileNextBlockAst (struct RAB *RabPtr)

{
   static char  HexDigit [] = "0123456789ABCDEF";
   static char  Bucket [64];
   static $DESCRIPTOR (BucketDsc, Bucket);
   static $DESCRIPTOR (BucketFaoDsc,
          "<input type=\"hidden\" name=\"hexencoded_!UL_!UL\" value=\"\0");

   int  cnt, status;
   char  *cptr, *sptr;
   char  Buffer [2048];
   REQUEST_STRUCT  *rqptr;
   UPD_TASK  *tkptr;

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

   rqptr = RabPtr->rab$l_ctx;

   if (WATCHMOD (rqptr, WATCH_MOD_UPD))
      WatchThis (WATCHITM(rqptr), WATCH_MOD_UPD,
                 "UpdCopyFileNextBlockAst() !&F sts:!&X stv:!&X rsz:!UL\n",
                 &UpdCopyFileNextBlockAst,
                 RabPtr->rab$l_sts, RabPtr->rab$l_stv,  RabPtr->rab$w_rsz);

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

   tkptr = rqptr->UpdTaskPtr;

   if (VMSnok (tkptr->FileOds.Rab.rab$l_sts))
   {
      if (tkptr->FileOds.Rab.rab$l_sts == RMS$_EOF)
      {
         UpdCopyFileEnd (rqptr);
         return;
      }

      rqptr->rqResponse.ErrorTextPtr = rqptr->rqHeader.PathInfoPtr;
      rqptr->rqResponse.ErrorOtherTextPtr = tkptr->FileOds.ExpFileName;
      ErrorVmsStatus (rqptr, tkptr->FileOds.Rab.rab$l_sts, FI_LI);
      UpdEnd (rqptr);
      return;
   }

   sptr = Buffer;
   for (cnt = 0; cnt < RabPtr->rab$w_rsz; cnt++)
   {
       if (!(cnt % 128))
       {
          if (cnt) for (cptr = "\">\n"; *cptr; *sptr++ = *cptr++);
          sys$fao (&BucketFaoDsc, 0, &BucketDsc,
                   tkptr->FileOds.Rab.rab$l_bkt, cnt);          
          for (cptr = Bucket; *cptr; *sptr++ = *cptr++);
          cptr = tkptr->FileOds.Rab.rab$l_ubf + cnt;
      }
      *sptr++ = HexDigit[((unsigned char)*cptr & 0xf0) >> 4];
      *sptr++ = HexDigit[(unsigned char)*cptr & 0x0f];
      cptr++;
   }
   for (cptr = "\">\n"; *cptr; *sptr++ = *cptr++);
   *sptr = '\0';

   NetWriteBuffered (rqptr, &UpdCopyFileNextBlock, Buffer, sptr-Buffer);
}

/*****************************************************************************/
/*
End-of-file detected.
*/

void UpdCopyFileEnd (REQUEST_STRUCT *rqptr)

{
   static char  CopyFileEndFao [] = 
"</form>\n\
</div>\n\
</body>\n\
</html>\n";

   int  status;

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

   if (WATCHMOD (rqptr, WATCH_MOD_UPD))
      WatchThis (WATCHITM(rqptr), WATCH_MOD_UPD, "UpdCopyFileEnd()");

   status = FaolToNet (rqptr, CopyFileEndFao, NULL);
   if (VMSnok (status)) ErrorNoticed (rqptr, status, NULL, FI_LI);

   UpdEnd (rqptr);
}

/*****************************************************************************/
/*
Output HTML allowing confirmation of directory creation.
*/

void UpdConfirmMkdir
(
REQUEST_STRUCT *rqptr,
char *AsNamePtr
)
{
   static char  ResponseFao [] =
"!AZ\
<html>\n\
<head>\n\
!AZ\
!AZ\
!&@\
<title>!AZ !AZ</title>\n\
</head>\n\
!AZ\n\
<div class=\"wasd\">\n\
<h2>!AZ !AZ</h2>\n\
<form method=\"POST\" action=\"!&%AZ!&%AZ/\">\n\
<input type=\"hidden\" name=\"protection\" value=\"!4XL\">\n\
<table class=\"ctgry\">\n\
<tr><td>\n\
<table class=\"rghtlft\">\n\
<th>!AZ</th><td>\
<a class=\"alnk\" target=\"_blank\" href=\"!&%AZ!&%AZ/\">!&;AZ!&;AZ/</a>\
</td><td style=\"padding-left:1em;\">\
<input type=\"submit\" value=\"!AZ\"></td></tr>\n\
</table>\n\
</td></tr>\n\
</table>\n\
</form>\n\
</div>\n\
</body>\n\
</html>\n";

   int  status;
   unsigned long  FaoVector [32];
   unsigned long  *vecptr;
   char  *cptr, *sptr, *zptr;
   char  Scratch [256];
   UPD_TASK  *tkptr;

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

   if (WATCHMOD (rqptr, WATCH_MOD_UPD))
      WatchThis (WATCHITM(rqptr), WATCH_MOD_UPD,
                 "UpdConfirmMkdir() !&Z", AsNamePtr);

   tkptr = rqptr->UpdTaskPtr;

   rqptr->rqResponse.PreExpired = PRE_EXPIRE_UPD;
   ResponseHeader200 (rqptr, "text/html", NULL);

   cptr = MsgFor(rqptr,MSG_UPD_CREATE);
   zptr = (sptr = Scratch) + sizeof(Scratch);
   while (*cptr && sptr < zptr) *sptr++ = *cptr++;
   if (sptr >= zptr)
   {
      ErrorGeneralOverflow (rqptr, FI_LI);
      UpdEnd (rqptr);
      return;
   }
   *sptr = '\0';

   vecptr = FaoVector;

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

   /* "Update" */
   *vecptr++ = cptr = Scratch;
   *vecptr++ = rqptr->ServicePtr->ServerHostPort;

   *vecptr++ = ADMIN_BODY_TAG;

   /* "Update" again */
   *vecptr++ = cptr;
   while (*cptr && *cptr != '|') cptr++;
   if (*cptr) *cptr++ = '\0';

   *vecptr++ = rqptr->ServicePtr->ServerHostPort;
   *vecptr++ = rqptr->rqHeader.PathInfoPtr;
   *vecptr++ = AsNamePtr;
   *vecptr++ = tkptr->ProtectionMask;

   /* "Create" */
   *vecptr++ = cptr;
   while (*cptr && *cptr != '|') cptr++;
   if (*cptr) *cptr++ = '\0';

   *vecptr++ = rqptr->rqHeader.PathInfoPtr;
   *vecptr++ = AsNamePtr;
   *vecptr++ = rqptr->rqHeader.PathInfoPtr;
   *vecptr++ = AsNamePtr;

   /* "Confirm" */
   *vecptr++ = cptr;
   while (*cptr && *cptr != '|') cptr++;
   if (*cptr) *cptr++ = '\0';

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

   UpdEnd (rqptr);
}

/*****************************************************************************/
/*
Output HTML allowing confirmation of directory deletion.
*/

void UpdConfirmDelete
(
REQUEST_STRUCT *rqptr,
char *Name,
char *Slash
)
{
   static char  ResponseFao [] =
"!AZ\
<html>\n\
<head>\n\
!AZ\
!AZ\
!&@\
<title>!AZ !AZ</title>\n\
</head>\n\
!AZ\n\
<div class=\"wasd\">\n\
<h2>!AZ !AZ</h2>\n\
<form method=\"POST\" action=\"!&%AZ!&%AZ!&%AZ;*\">\n\
<table class=\"ctgry\">\n\
<tr><td>\n\
<table class=\"rghtlft\">\n\
<tr><th>!AZ</th><td>\
<a class=\"alnk\" target=\"_blank\" href=\"!&%AZ!&%AZ!AZ\">!&;AZ!&;AZ!AZ</a>\
</td><td style=\"padding-left:1em;\">\
<input type=\"submit\" value=\"!AZ\"></td></tr>\n\
</table>\n\
</td></tr>\n\
</table>\n\
</form>\n\
</div>\n\
</body>\n\
</html>\n";

   int  status;
   unsigned long  FaoVector [32];
   unsigned long  *vecptr;
   char  *cptr, *sptr, *zptr;
   char  Scratch [256];
   UPD_TASK  *tkptr;

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

   if (WATCHMOD (rqptr, WATCH_MOD_UPD))
      WatchThis (WATCHITM(rqptr), WATCH_MOD_UPD, 
                 "UpdConfirmDelete() !&Z !&Z", Name, Slash);

   tkptr = rqptr->UpdTaskPtr;

   rqptr->rqResponse.PreExpired = PRE_EXPIRE_UPD;
   ResponseHeader200 (rqptr, "text/html", NULL);

   cptr = MsgFor(rqptr,MSG_UPD_DELETE);
   zptr = (sptr = Scratch) + sizeof(Scratch);
   while (*cptr && sptr < zptr) *sptr++ = *cptr++;
   if (sptr >= zptr)
   {
      ErrorGeneralOverflow (rqptr, FI_LI);
      UpdEnd (rqptr);
      return;
   }
   *sptr = '\0';

   vecptr = FaoVector;

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

   /* "Update" */
   *vecptr++ = cptr = Scratch;
   *vecptr++ = rqptr->ServicePtr->ServerHostPort;

   *vecptr++ = ADMIN_BODY_TAG;

   /* "Update" again */
   *vecptr++ = cptr;
   *vecptr++ = rqptr->ServicePtr->ServerHostPort;

   *vecptr++ = rqptr->rqHeader.PathInfoPtr;
   *vecptr++ = Name;
   *vecptr++ = Slash;

   /* "delete" */
   while (*cptr && *cptr != '|') cptr++;
   if (*cptr) *cptr++ = '\0';
   *vecptr++ = cptr;

   *vecptr++ = rqptr->rqHeader.PathInfoPtr;
   *vecptr++ = Name;
   *vecptr++ = Slash;
   *vecptr++ = rqptr->rqHeader.PathInfoPtr;
   *vecptr++ = Name;
   *vecptr++ = Slash;

   /* "Confirm" */
   while (*cptr && *cptr != '|') cptr++;
   if (*cptr) *cptr++ = '\0';
   *vecptr++ = cptr;

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

   UpdEnd (rqptr);
}

/*****************************************************************************/
/*
Output HTML allowing confirmation of protection change.
*/

void UpdConfirmProtect
(
REQUEST_STRUCT *rqptr,
char *Name,
char *Slash
)
{
   static char  ResponseFao [] =
"!AZ\
<html>\n\
<head>\n\
!AZ\
!AZ\
!&@\
<title>!AZ !AZ</title>\n\
</head>\n\
!AZ\n\
<div class=\"wasd\">\n\
<h2>!AZ !AZ</h2>\n\
<form method=\"POST\" action=\"!&%AZ!&%AZ!&%AZ!&%AZ\">\n\
<table class=\"ctgry\">\n\
<tr><td>\n\
<table class=\"rghtlft\">\n\
<tr><th>!AZ</th><td>\
<a class=\"alnk\" target=\"_blank\" href=\"!&%AZ!&%AZ!AZ\">!&;AZ!&;AZ!AZ</a>\
</td><td>!AZ</td><td>!AZ</td><td style=\"padding-left:1em;\">\n\
<input type=\"hidden\" name=\"d-dirprotect\" value=\"1\">\n\
<input type=\"hidden\" name=\"protection\" value=\"!4XL\">\n\
<input type=\"submit\" value=\"!AZ\">\n\
</td></tr>\n\
</table>\n\
</td></tr>\n\
</table>\n\
</form>\n\
</div>\n\
</body>\n\
</html>\n";

   int  status;
   unsigned long  FaoVector [32];
   unsigned long  *vecptr;
   char  *cptr, *sptr, *zptr;
   char  ProtectionString[32],
         Scratch [256];
   UPD_TASK  *tkptr;

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

   if (WATCHMOD (rqptr, WATCH_MOD_UPD))
      WatchThis (WATCHITM(rqptr), WATCH_MOD_UPD, 
                 "UpdConfirmProtect() !&Z !&Z", Name, Slash);

   tkptr = rqptr->UpdTaskPtr;

   FormatProtection (tkptr->ProtectionMask, ProtectionString);

   rqptr->rqResponse.PreExpired = PRE_EXPIRE_UPD;
   ResponseHeader200 (rqptr, "text/html", NULL);

   cptr = MsgFor(rqptr,MSG_UPD_PROTECTION);
   zptr = (sptr = Scratch) + sizeof(Scratch);
   while (*cptr && sptr < zptr) *sptr++ = *cptr++;
   if (sptr >= zptr)
   {
      ErrorGeneralOverflow (rqptr, FI_LI);
      UpdEnd (rqptr);
      return;
   }
   *sptr = '\0';

   vecptr = FaoVector;

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

   /* "Update" */
   *vecptr++ = cptr = Scratch;
   *vecptr++ = rqptr->ServicePtr->ServerHostPort;

   *vecptr++ = ADMIN_BODY_TAG;

   /* "Update" again */
   *vecptr++ = cptr;
   *vecptr++ = rqptr->ServicePtr->ServerHostPort;

   *vecptr++ = rqptr->ScriptName;
   *vecptr++ = rqptr->rqHeader.PathInfoPtr;
   *vecptr++ = Name;
   *vecptr++ = Slash;

   /* "protect" */
   while (*cptr && *cptr != '|') cptr++;
   if (*cptr) *cptr++ = '\0';
   *vecptr++ = cptr;

   *vecptr++ = rqptr->rqHeader.PathInfoPtr;
   *vecptr++ = Name;
   *vecptr++ = Slash;
   *vecptr++ = rqptr->rqHeader.PathInfoPtr;
   *vecptr++ = Name;
   *vecptr++ = Slash;

   /* "to" */
   while (*cptr && *cptr != '|') cptr++;
   if (*cptr) *cptr++ = '\0';
   *vecptr++ = cptr;

   *vecptr++ = ProtectionString;
   *vecptr++ = tkptr->ProtectionMask;

   /* "Confirm" */
   while (*cptr && *cptr != '|') cptr++;
   if (*cptr) *cptr++ = '\0';
   *vecptr++ = cptr;

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

   UpdEnd (rqptr);
}

/*****************************************************************************/
/*
Output HTML allowing confirmation of action (file/directory creation/deletion).
*/

void UpdConfirmRename
(
REQUEST_STRUCT *rqptr,
char *OldName,
char *NewName
)
{
   static char  ResponseFao [] =
"!AZ\
<html>\n\
<head>\n\
!AZ\
!AZ\
!&@\
<title>!AZ !AZ</title>\n\
</head>\n\
!AZ\n\
<div class=\"wasd\">\n\
<h2>!AZ !AZ</h2>\n\
<form method=\"POST\" action=\"!&%AZ!&%AZ\">\n\
<input type=\"hidden\" name=\"d-filerename\" value=\"1\">\n\
<input type=\"hidden\" name=\"name\" value=\"!AZ\">\n\
<input type=\"hidden\" name=\"as\" value=\"!AZ\">\n\
<p><table class=\"ctgry\">\n\
<tr><td>\n\
<table class=\"rghtlft\">\n\
<tr><th>!AZ</th>\
<td><a class=\"alnk\" target=\"_blank\" href=\"!&%AZ!&%AZ\">!&;AZ!&;AZ</a></td>\
<td rowspan=\"2\">\
<input type=\"submit\" value=\"!AZ\">\
</td></tr>\n\
<tr><th>!AZ</th>\
<td><a class=\"alnk\" target=\"_blank\" href=\"!&%AZ!&%AZ\">!&;AZ!&;AZ</a></td></tr>\n\
</table>\n\
</td></tr>\n\
</table>\n\
</form>\n\
</div>\n\
</body>\n\
</html>\n";

   int  status;
   unsigned long  FaoVector [32];
   unsigned long  *vecptr;
   char  *cptr, *sptr, *zptr,
         *ToPtr;
   char  Scratch [256];
   UPD_TASK  *tkptr;

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

   if (WATCHMOD (rqptr, WATCH_MOD_UPD))
      WatchThis (WATCHITM(rqptr), WATCH_MOD_UPD,
                 "UpdConfirmRename() !&Z !&Z", OldName, NewName);

   tkptr = rqptr->UpdTaskPtr;

   rqptr->rqResponse.PreExpired = PRE_EXPIRE_UPD;
   ResponseHeader200 (rqptr, "text/html", NULL);

   cptr = MsgFor(rqptr,MSG_UPD_RENAME);
   zptr = (sptr = Scratch) + sizeof(Scratch);
   while (*cptr && sptr < zptr) *sptr++ = *cptr++;
   if (sptr >= zptr)
   {
      ErrorGeneralOverflow (rqptr, FI_LI);
      UpdEnd (rqptr);
      return;
   }
   *sptr = '\0';

   vecptr = FaoVector;

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

   /* "Update" */
   *vecptr++ = cptr = Scratch;
   *vecptr++ = rqptr->ServicePtr->ServerHostPort;

   *vecptr++ = ADMIN_BODY_TAG;

   /* "Update" again */
   *vecptr++ = cptr;
   while (*cptr && *cptr != '|') cptr++;
   if (*cptr) *cptr++ = '\0';
   *vecptr++ = rqptr->ServicePtr->ServerHostPort;

   *vecptr++ = rqptr->ScriptName;
   *vecptr++ = rqptr->rqHeader.PathInfoPtr;
   *vecptr++ = OldName,
   *vecptr++ = NewName,

   /* "Rename" */
   *vecptr++ = cptr;
   while (*cptr && *cptr != '|') cptr++;
   if (*cptr) *cptr++ = '\0';

   *vecptr++ = rqptr->rqHeader.PathInfoPtr;
   *vecptr++ = OldName,
   *vecptr++ = rqptr->rqHeader.PathInfoPtr;
   *vecptr++ = OldName,

   /* "Confirm" (for historical reasons "confirm" is after "to") */
   ToPtr = cptr;
   while (*cptr && *cptr != '|') cptr++;
   if (*cptr) *cptr++ = '\0';
   *vecptr++ = cptr;
   while (*cptr && *cptr != '|') cptr++;
   if (*cptr) *cptr++ = '\0';

   /* "to" */
   *vecptr++ = ToPtr;

   *vecptr++ = rqptr->rqHeader.PathInfoPtr;
   *vecptr++ = NewName,
   *vecptr++ = rqptr->rqHeader.PathInfoPtr;
   *vecptr++ = NewName,

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

   UpdEnd (rqptr);
}

/*****************************************************************************/
/*
Update the protection of rqptr->ParseOds.ExpFileName.
*/ 

void UpdProtection (REQUEST_STRUCT *rqptr)

{
   static unsigned short  EnsureSystemAccessMask = 0xfff0; /* S:RWED */

   int  status,
        Length;
   unsigned short  ProtectionMask;
   char  *cptr;
   char  DirFile [ODS_MAX_FILE_NAME_LENGTH+1],
         ProtectionString[32];
   UPD_TASK  *tkptr;

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

   if (WATCHMOD (rqptr, WATCH_MOD_UPD))
      WatchThis (WATCHITM(rqptr), WATCH_MOD_UPD, "UpdProtection()");

   tkptr = rqptr->UpdTaskPtr;

   if (WATCHPNT(rqptr) &&
       WATCH_CATEGORY(WATCH_RESPONSE))
      WatchThis (WATCHITM(rqptr), WATCH_RESPONSE,
                 "UPDATE protection !AZ", rqptr->ParseOds.ExpFileName);

   /* authentication is mandatory for such an update */
   if (!rqptr->RemoteUser[0] ||
       !((rqptr->rqAuth.RequestCan & HTTP_METHOD_PUT) ||
         (rqptr->rqAuth.RequestCan & HTTP_METHOD_POST) ||
         (rqptr->rqAuth.RequestCan & HTTP_METHOD_DELETE)))
   {
      rqptr->rqResponse.ErrorTextPtr = rqptr->rqHeader.PathInfoPtr;
      rqptr->rqResponse.ErrorOtherTextPtr = rqptr->ParseOds.ExpFileName;
      ErrorVmsStatus (rqptr, SS$_NOPRIV, FI_LI);
      UpdEnd (rqptr);
      return;
   }

   cptr = rqptr->ParseOds.ExpFileName;
   Length = rqptr->ParseOds.ExpFileNameLength;
   if (!rqptr->ParseOds.NamNameLength &&
       !rqptr->ParseOds.NamTypeLength)
      OdsNameOfDirectoryFile (rqptr->ParseOds.ExpFileName, Length,
                              cptr = DirFile, &Length);

   ProtectionMask = tkptr->ProtectionMask & EnsureSystemAccessMask;

   status = OdsParse (&tkptr->FileOds, cptr, Length, NULL, 0, 0, NULL, rqptr);

   AuthAccessEnable (rqptr, rqptr->ParseOds.ExpFileName, AUTH_ACCESS_WRITE);

   status = OdsFileAcpModify (&tkptr->FileOds, &ProtectionMask,
                              NULL, NULL, rqptr);

   AuthAccessEnable (rqptr, 0, 0);

   if (VMSnok (status)) 
   {
      rqptr->rqResponse.ErrorTextPtr = rqptr->rqHeader.PathInfoPtr;
      rqptr->rqResponse.ErrorOtherTextPtr = rqptr->ParseOds.ExpFileName;
      ErrorVmsStatus (rqptr, status, FI_LI);
      UpdEnd (rqptr);
      return;
   }

   /***********/
   /* success */
   /***********/

   FormatProtection (tkptr->ProtectionMask, ProtectionString);

   ReportSuccess (rqptr,
"!AZ&nbsp; <a target=\"_blank\" href=\"!&%AZ\">!&;&_AZ</a>\
&nbsp; !AZ&nbsp; (!AZ)\n",
       MsgFor(rqptr,MSG_GENERAL_FILE),
       rqptr->rqHeader.PathInfoPtr, rqptr->rqHeader.PathInfoPtr,
       MsgFor(rqptr,MSG_UPD_PROTECTION_CHANGED), ProtectionString);

   UpdEnd (rqptr);
}

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

