/*****************************************************************************/
/*
                                  Menu.c

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

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

Implements the WASD menu file.  A WASD menu comprises a file with 1, 2, 3 or 
more blank-line delimitted sections.  The optional first is the document 
title.  The optional second is a menu description.  The mandatory section 
consists of the menu items.  Any blank-line delimtted sections thereafter 
alternate between descriptive paragraphs and menu sections.  For example: 

|This is the HTML <TITLE> line.  It must be only one line.|
||
|This is the descriptive paragraph.|
|It can be multi-line.|
||
|HT_DATA:FILE1.TXT   This is the first text file.|
|HT_DATA:FILE2.TXT   This is the second text file.|
|HT_DATA:FILE1.HTML  This would be an HTML file.|
||
|This is another (optional) descriptive paragraph.|
|It can also be multi-line.|
||
|HT_DATA:FILE1.TXT   This is the first file of the second menu.|
|HT_DATA:FILE2.TXT   This is the second file, etc.|
|!This is a comment|
|*.TXT?  This is a search item
|!The next item lists all matching files from their internal description|
|*.HTML|
 

VERSION HISTORY
---------------
05-OCT-2002  MGD  refine VMS security profile usage
27-APR-2002  MGD  make SYSPRV enabled ASTs asynchronous
04-AUG-2001  MGD  support module WATCHing
30-DEC-2000  MGD  rework for FILE.C getting file contents in-memory
01-SEP-2000  MGD  add optional, local path authorization
                  (for calls from the likes of SSI.C)
04-MAR-2000  MGD  use WriteFao(), et.al.
27-DEC-1999  MGD  support ODS-2 and ODS-5 using ODS module
07-NOV-1998  MGD  WATCH facility
17-AUG-1997  MGD  message database,
                  SYSUAF-authenticated users security-profile
27-FEB-1997  MGD  delete on close for "temporary" files
01-FEB-1997  MGD  HTTPd version 4
23-MAY-1996  MGD  added listing from file descriptions (i.e. Description())
28-MAR-1996  MGD  bugfix, MenuMextContent() caused RequestEnd() to be
                  called twice (two log entries only deleterious effect :^)
01-DEC-1995  MGD  HTTPd version 3
27-SEP-1995  MGD  added "If-Modified-Since:" functionality
07-AUG-1995  MGD  ConfigcfReport.MetaInfoEnabled to allow physical file
                  specification to be included as commentary within menu output
20-DEC-1994  MGD  initial development for multi-threaded daemon
*/
/*****************************************************************************/

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

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

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

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

#define WASD_MODULE "MENU"

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

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

extern int  FileBufferSize;

extern unsigned long  SysPrvMask[];

extern char  SoftwareID[];

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

/*****************************************************************************/
/*
Parse the contents of the file according to the menu statements.
*/

MenuBegin (REQUEST_STRUCT *rqptr)

{
   FILE_CONTENT  *fcptr;
   MENU_TASK  *tkptr;

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

   if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD__OTHER))
      WatchThis (rqptr, FI_LI, WATCH_MOD__OTHER, "MenuBegin()");

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

   /* set up the task structure (only ever one per request!) */
   rqptr->MenuTaskPtr = tkptr =
      (MENU_TASK*)VmGetHeap (rqptr, sizeof(MENU_TASK));

   /* take control of the file contents structure */
   tkptr->FileContentPtr = fcptr = rqptr->FileContentPtr;
   rqptr->FileContentPtr = NULL;

   /* set up read to parse the file's body */
   tkptr->LinePtr = tkptr->ParsePtr = fcptr->ContentPtr;

   if (WATCHING(rqptr) && WATCH_CATEGORY(WATCH_RESPONSE))
      WatchThis (rqptr, FI_LI, WATCH_RESPONSE, "MENU !AZ !UL bytes",
                 fcptr->FileName, fcptr->ContentLength);

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

   MenuContents (rqptr);
}

/*****************************************************************************/
/*
End menu processing.  No need to explicitly free the contents structure as
there will only ever be one lot of menu processing per request and the contents
structure will be cleaned up in the general request heap freeing.
*/ 

MenuEnd (REQUEST_STRUCT *rqptr)

{
   MENU_TASK  *tkptr;

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

   if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD__OTHER))
      WatchThis (rqptr, FI_LI, WATCH_MOD__OTHER, "MenuEnd()");

   tkptr = rqptr->MenuTaskPtr;

   /* release internal RMS parse structures */
   if (tkptr->FileOds.ParseInUse) OdsParseRelease (&tkptr->SearchOds);

   SysDclAst (tkptr->FileContentPtr->NextTaskFunction, rqptr);
}

/*****************************************************************************/
/*
Process the lines of the menu file according to the total count of the sections
(blank-line delimited text) in the file and the section number the current line
falls within.
*/ 

MenuContents (REQUEST_STRUCT *rqptr)

{
   int  status;
   char  *cptr, *sptr, *zptr;
   MENU_TASK  *tkptr;

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

   if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD__OTHER))
      WatchThis (rqptr, FI_LI, WATCH_MOD__OTHER, "MenuContents()");

   tkptr = rqptr->MenuTaskPtr;

   if (!*tkptr->ParsePtr)
   {
      if (!((tkptr->MenuSectionNumber-1) % 2))
         NetWriteBuffered (rqptr, &MenuEnd, "<UL>\n</BODY>\n</HTML>\n", 21);
      else
         NetWriteBuffered (rqptr, &MenuEnd, "</BODY>\n</HTML>\n", 16);
      return;
   }

   tkptr->LinePtr = tkptr->ParsePtr;
   for (;;)
   {
      for (cptr = tkptr->ParsePtr; *cptr && *cptr != '\n'; cptr++);
      if (cptr > tkptr->ParsePtr && cptr[-1] == '\\')
      {
         /* continuation character, move line down two characters */
         sptr = (zptr = cptr) - 2;
         while (sptr >= tkptr->LinePtr) *zptr-- = *sptr--;
         tkptr->LinePtr += 2;
         continue;
      }
      if (*cptr) *cptr++ = '\0';
      tkptr->ParsePtr = cptr;
      break;
   }
   tkptr->LineLength = tkptr->ParsePtr - tkptr->LinePtr;
   if (Debug) fprintf (stdout, "%d |%s|\n", tkptr->LineLength, tkptr->LinePtr);

   if (!tkptr->LinePtr[0])
   {
      /**************/
      /* empty line */
      /**************/

      tkptr->MenuBlankLineCount++;
      tkptr->MenuLineCount = 0;
      SysDclAst (&MenuContents, rqptr);
      return;
   }

   if (tkptr->LinePtr[0] == '!')
   {
      /*********************/
      /* comment-only line */
      /*********************/

      SysDclAst (&MenuContents, rqptr);
      return;
   }

   /******************/
   /* non-blank line */
   /******************/

   if (tkptr->MenuBlankLineCount) tkptr->MenuSectionNumber++;
   tkptr->MenuLineCount++;
   tkptr->MenuBlankLineCount = 0;
   if (!tkptr->MenuSectionNumber) tkptr->MenuSectionNumber++;

   /******************/
   /* interpret line */
   /******************/

   if (tkptr->MenuSectionNumber == 1 && tkptr->MenuLineCount == 1)
      MenuTitle (rqptr);
   else
   if (!(tkptr->MenuSectionNumber % 2))
      MenuDescription (rqptr);
   else
   if (!((tkptr->MenuSectionNumber-1) % 2))
   {
      if (tkptr->MenuLineCount == 1)
         NetWriteBuffered (rqptr, &MenuItems, "<P>\n<UL>\n", 9);
      else
         MenuItems (rqptr);
   }
}

/*****************************************************************************/
/*
The line just read is the menu title.
*/
MenuTitle (REQUEST_STRUCT *rqptr)

{
   static char TitleFao [] =
"<HTML>\n\
<HEAD>\n\
!AZ\
<TITLE>!AZ</TITLE>\n\
</HEAD>\n\
!AZ\n\
<H2>!AZ</H2>\n";

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

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

   if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD__OTHER))
      WatchThis (rqptr, FI_LI, WATCH_MOD__OTHER, "MenuTitle()");

   tkptr = rqptr->MenuTaskPtr;

   /* should only be one line, ignore all but the first */
   if (tkptr->MenuLineCount > 1) SysDclAst (&MenuContents, rqptr);

   vecptr = FaoVector;

   *vecptr++ = HtmlMetaInfo (rqptr, tkptr->FileContentPtr->FileName);
   *vecptr++ = tkptr->LinePtr;
   if (rqptr->ServicePtr->BodyTag[0])
      *vecptr++ = rqptr->ServicePtr->BodyTag;
   else
      *vecptr++ = Config.cfServer.ReportBodyTag;
   *vecptr++ = tkptr->LinePtr;

   status = NetWriteFaol (rqptr, TitleFao, &FaoVector);
   if (VMSnok (status)) ErrorNoticed (status, "WriteFaol()", FI_LI);

   SysDclAst (&MenuContents, rqptr);
}

/*****************************************************************************/
/*
If this is the first (and possibly only) description section separate using a 
<P> tag.  If the second (or more) description section first terminate the <UL>
begun in the preceding item section before separating using the <P> tag.
*/ 

MenuDescription (REQUEST_STRUCT *rqptr)

{
   MENU_TASK  *tkptr;

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

   if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD__OTHER))
      WatchThis (rqptr, FI_LI, WATCH_MOD__OTHER, "MenuDescription()");

   tkptr = rqptr->MenuTaskPtr;

   if (tkptr->MenuLineCount == 1)
   {
      /* first line of the description section */
      if (tkptr->MenuSectionNumber == 2)
      {
         /* first description section of the menu */
         NetWriteFao (rqptr, "<P>!AZ\n", tkptr->LinePtr);
      }
      else
      {
         /* second or subsequent description section of the menu */
         NetWriteFao (rqptr, "</UL>\n<P>!AZ\n", tkptr->LinePtr);
      }
   }
   else
   {
      /* second or subsequent line of the description section */
      NetWriteFao (rqptr, "!AZ\n", tkptr->LinePtr);
   }
   SysDclAst (&MenuContents, rqptr);
}

/*****************************************************************************/
/*
Interpret this line as a menu item comprising a URI, white-space, and a 
description comprising the rest of the line after the first non-space.  Each 
line in the item section is output as an item in an non-ordered list, or <UL>.
*/

MenuItems (REQUEST_STRUCT *rqptr)

{
   static char  NoDescription [] = "<I>(no description for this line)</I>";

   unsigned short  Length;
   char  *cptr, *sptr, *zptr,
         *DescriptionPtr;
   char  Description [256],
         String [512],
         Uri [256];
   MENU_TASK  *tkptr;

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

   if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD__OTHER))
      WatchThis (rqptr, FI_LI, WATCH_MOD__OTHER, "MenuItems()");

   tkptr = rqptr->MenuTaskPtr;

   cptr = tkptr->LinePtr;
   /* skip leading white-space */
   while (*cptr && ISLWS(*cptr)) cptr++;
   zptr = (sptr = Uri) + sizeof(Uri)-2;
   while (*cptr && !ISLWS(*cptr) && sptr < zptr) *sptr++ = *cptr++;
   *sptr = '\0';

   /* skip intervening white-space */
   while (*cptr && ISLWS(*cptr)) cptr++;
   zptr = (sptr = Description) + sizeof(Description)-2;
   while (*cptr && sptr < zptr) *sptr++ = *cptr++;
   *sptr = '\0';

   if (Description[0])
      DescriptionPtr = Description;
   else
      DescriptionPtr = NoDescription;

   if (Uri[0] == '\"')
   {
      /* full URI/URL specification, remove quotes and include as-is */
      for (cptr = Uri+1; *cptr && *cptr != '\"'; cptr++);
      if (*cptr == '\"') *cptr = '\0';
      NetWriteFao (rqptr, "<LI><A HREF=\"!AZ\">!AZ</A>\n",
                   Uri+1, DescriptionPtr);
      SysDclAst (&MenuContents, rqptr);
   }
   else
   {
      for (cptr = Uri; *cptr && *cptr != '?'; cptr++);
      if (*cptr == '?')
      {
         /* search item */
         *cptr++ = '\0';
         MenuSearchItem (rqptr, &MenuContents, Uri, Description, cptr);
      }
      else
      if (Uri[0] == '*' && !*cptr)
      {
         /* wildcard description list item */
         MenuFileDescription (rqptr, &MenuContents, Uri);
      }
      else
      {
         /* file item */
         NetWriteFao (rqptr, "<LI><A HREF=\"!AZ\">!AZ</A>\n",
                      Uri, DescriptionPtr);
         SysDclAst (&MenuContents, rqptr);
      }
   }
}

/*****************************************************************************/
/*
The URI contains a question mark, which is menu-speak for "this is a search
item".  Create and output a search form.  Optional characters following the 
question mark include an "A" for and "about the search" link, "C" for case-
sensitivity buttons, and "O" for output style buttons.
*/

MenuSearchItem
(
REQUEST_STRUCT *rqptr,
REQUEST_AST AstFunction,
char *Uri,
char *Description,
char *SearchOptions
)
{
   /*********/
   /* begin */
   /*********/

   if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD__OTHER))
      WatchThis (rqptr, FI_LI, WATCH_MOD__OTHER,
                 "MenuSearchItem() !&A !&Z !&Z !&Z",
                 AstFunction, Uri, Description, SearchOptions);

   NetWriteFao (rqptr,
"<LI>\n\
<FORM ACTION=\"!AZ\">\n\
<INPUT TYPE=submit VALUE=\"!&;AZ\">\n\
<INPUT TYPE=text NAME=\"search\">\n\
<INPUT TYPE=reset VALUE=\"Reset\">\n",
      Uri, Description);

   while (*SearchOptions)
   {
      if (toupper(*SearchOptions) == 'A')
         NetWriteFao (rqptr,
"<BR><I><A HREF=\"?about=search\">About</A> this search.</I>\n");
      else
      if (toupper(*SearchOptions) == 'O')
         NetWriteFao (rqptr,
"<BR><I>Case Sensitive:  \
no<INPUT TYPE=radio NAME=\"case\" VALUE=\"no\" CHECKED>\
 yes<INPUT TYPE=radio NAME=\"case\" VALUE=\"yes\"></I>\n");
      else
      if (toupper(*SearchOptions) == 'C')
         NetWriteFao (rqptr,
"<BR><I>Output By:  \
line<INPUT TYPE=radio NAME=\"hits\" VALUE=\"line\" CHECKED>\
 document<INPUT TYPE=radio NAME=\"hits\" VALUE=\"document\"></I>\n");
      SearchOptions++;
   }

   NetWriteFao (rqptr, "</FORM>\n");

   SysDclAst (AstFunction, rqptr);
}

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

MenuFileDescription
(
REQUEST_STRUCT *rqptr,
REQUEST_AST AstFunction,
char *FileWildcard
)
{
   int  status;
   MENU_TASK  *tkptr;

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

   if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD__OTHER))
      WatchThis (rqptr, FI_LI, WATCH_MOD__OTHER,
                 "MenuSearchItem() !&A !&Z", AstFunction, FileWildcard);

   tkptr = rqptr->MenuTaskPtr;

   tkptr->MenuAstFunction = AstFunction;

   OdsParse (&tkptr->SearchOds,
             FileWildcard, 0,
             tkptr->FileOds.NamDevicePtr,
             tkptr->FileOds.NamDeviceLength +
               tkptr->FileOds.NamDirectoryLength,
             0, NULL, rqptr);

   status = tkptr->FileOds.Fab.fab$l_sts;

   if (VMSok (status) &&
       tkptr->FileOds.Nam_fnb & NAM$M_SEARCH_LIST)
   {
      /*******************************/
      /* search to get actual device */
      /*******************************/

      if (Debug) fprintf (stdout, "SEARCH-LIST!\n");
      if (rqptr->rqAuth.VmsUserProfileLength) sys$setprv (1, &SysPrvMask, 0, 0);
      OdsSearch (&tkptr->FileOds, NULL, 0);
      if (rqptr->rqAuth.VmsUserProfileLength) sys$setprv (0, &SysPrvMask, 0, 0);
      status = tkptr->FileOds.Fab.fab$l_sts;
   }

   if (VMSnok (status))
   {
      tkptr->FileOds.NamNamePtr[0] = '\0';
      rqptr->rqResponse.ErrorTextPtr =
         MapVmsPath (tkptr->FileOds.NamDevicePtr, rqptr);
      rqptr->rqResponse.ErrorOtherTextPtr = tkptr->FileOds.NamDevicePtr;
      ErrorVmsStatus (rqptr, status, FI_LI);
      MenuEnd (rqptr);
      return;
   }

   MenuFileSearch (rqptr);
}

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

MenuFileSearch (REQUEST_STRUCT *rqptr)

{
   int  status;
   MENU_TASK  *tkptr;

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

   if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD__OTHER))
      WatchThis (rqptr, FI_LI, WATCH_MOD__OTHER, "MenuFileSearch()");

   tkptr = rqptr->MenuTaskPtr;

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

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

MenuFileDescriptionOf (struct FAB *FabPtr)

{
   int  status;
   char  *cptr, *sptr;
   MENU_TASK  *tkptr;
   REQUEST_STRUCT  *rqptr;

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

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

   rqptr = FabPtr->fab$l_ctx;

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

   tkptr = rqptr->MenuTaskPtr;

   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->FileOds.Nam_fnb & NAM$M_SEARCH_LIST) && status == RMS$_DNF)
         status = RMS$_FNF;

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

         tkptr->SearchOds.ParseInUse = false;
         SysDclAst (tkptr->MenuAstFunction, rqptr);
         return;
      }

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

      rqptr->rqResponse.ErrorTextPtr =
         MapVmsPath (tkptr->SearchOds.ExpFileName, rqptr);
      rqptr->rqResponse.ErrorOtherTextPtr = tkptr->SearchOds.ExpFileName;
      ErrorVmsStatus (rqptr, status, FI_LI);
      MenuEnd (rqptr);
      return;
   }

   /* terminate following the last character in the version number */
   tkptr->SearchOds.NamVersionPtr[tkptr->SearchOds.NamVersionLength] = '\0';
   if (Debug) fprintf (stdout, "|%s|\n", tkptr->SearchOds.ResFileName);

   if (!strcmp (tkptr->SearchOds.NamNamePtr, tkptr->FileOds.NamNamePtr))
   {
      /* this is the same menu file, don't need a description of this! */
      MenuFileSearch (rqptr);
      return;
   }

   Description (rqptr,
                &MenuFileDescriptionDone,
                tkptr->SearchOds.ResFileName,
                tkptr->Description,
                sizeof(tkptr->Description),
                DESCRIPTION_ALL);
}

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

MenuFileDescriptionDone (REQUEST_STRUCT *rqptr)

{
   int  status;
   unsigned short  Length;
   char  *cptr, *sptr;
   char  LowerCaseName [128];
   MENU_TASK  *tkptr;

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

   if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD__OTHER))
      WatchThis (rqptr, FI_LI, WATCH_MOD__OTHER, "MenuFileDescriptionDone()");

   tkptr = rqptr->MenuTaskPtr;

   if (tkptr->Description[0] == DESCRIPTION_IMPOSSIBLE)
      SysDclAst (&MenuFileSearch, rqptr);

   sptr = LowerCaseName;
   for (cptr = tkptr->SearchOds.NamNamePtr;
        *cptr && *cptr != ';';
        *sptr++ = tolower(*cptr++));
   *sptr = '\0';
   if (tkptr->Description[0])
      NetWriteFao (rqptr, "<LI><A HREF=\"!AZ\">&quot;!AZ&quot;</A>\n",
                   LowerCaseName, tkptr->Description);
   else
      NetWriteFao (rqptr, "<LI><A HREF=\"!AZ\">!AZ</A>\n",
                   LowerCaseName, LowerCaseName);

   SysDclAst (&MenuFileSearch, rqptr);
}

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

