/*****************************************************************************/
/*
                                 calendar.c

CGI-compliant script to produce a calendar.

Page colouration may be specified via the appropriate command-line qualifiers
(or corresponding logical name). Defaults for any not specified.  Specifiy as
/WHATEVER="" to NOT specify the corresponding colour (i.e. leave it to the
browser). See "Qualifiers" section below, and also about the logical name
"CALENDAR$PARAM".

An example of changing the page colour to white and the banner to red!

  /PBGCOLOR="#ffffff" /PHBGCOLOR="#ff0000"

The script can format a page in either of two layouts.

  1. Tables are used to create a coloured header and button bar (DEFAULT).
     Default colours are white page with grey heading and button outlines.
  2. Textual header, horizontal rules and a textual button bar.
     No default colours.


QUERY STRINGS ALLOWED
---------------------

  ?1999         specific year (anything greater than 99)
  ?0            current year
  ?1            current year plus one, two, etc., up to 99
  ?+1           same as above
  ?-1           current year minus one, two, etc., up to -99


COMMAND-LINE USE
----------------
To include a <PRE>..</PRE> delimited calendar inside other script output use
the /CALENDAR qualifier.   This takes two, comma-separated parameters.  The
first; the year to be output (empty or zero generates the current year).  The
second; the number of months across the page.  Examples:

  $ CALENDAR = "$CGI-BIN:[000000]CALENDAR"
  $ CALENDAR /CALENDAR=2003,3
  $ CALENDAR /CALENDAR=0,3
  $ CALENDAR /CALENDAR


CGI VARIABLE NAMES
------------------
WWW_KEY_1               query string containing year or increment to current
WWW_KEY_2               will contain increment if "?+1" is used
WWW_PATH_INFO           the URL path component
WWW_PATH_TRANSLATED     the VMS equivalent of the URL path component
WWW_QUERY_STRING        any of the form components
WWW_SCRIPT_NAME         the name of the script being executed
WWW_FORM_ACROSS         number of months across page
WWW_FORM_FROMMONTH      beginning with month (1 == jan, 12 = dec)
WWW_FORM_TOMONTH        ending with month
WWW_FORM_OTHERYEAR      explicitly specified year from form
WWW_FORM_YEAR           year selected from selection box in form


LOGICAL NAMES
-------------
CALENDAR$DBUG          turns on all "if (Debug)" statements
CALENDAR$PARAM         equivalent to (overrides) the command line
                       parameters/qualifiers (define as a system-wide logical)


QUALIFIERS
----------
/BUTTONS=       string containing button labels/paths
/CALENDAR=      allows a calendar-only to be produced (see above)
/DBUG           turns on all "if (Debug)" statements
/PBACKGROUND=   <body> background image path
/PBGCOLOR=      <body> background colour
/PBBGCOLOR=     button background color
/PBBORDER=      width of button border
/PHBGCOLOR=     heading background color
/PHBORDER=      width of heading and button-bar border
/PHLOCAL=       local information to be included in header
/PHTEXT=        heading text colour
/PLAYOUT=       1 is coloured header & buttons, 2 is text & horizontal rules
/PLINK=         <body> link colour
/PTEXT=         <body> text colour
/PVLINK=        <body> visited link colour


BUILD
-----
See BUILD_CALENDAR.COM


VERSION HISTORY
---------------
23-DEC-2003  MGD  v1.5.1, minor conditional mods to support IA64
02-OCT-2003  MGD  v1.5.0, /CALENDAR=<year>,<months-across-page>
12-APR-2003  MGD  v1.4.5, link colour changed to 0000cc
15-AUG-2002  MGD  v1.4.4, some accomodations for CSWS v1.2
01-JUL-2001  MGD  v1.4.3, add 'SkipParameters' for direct OSU support
28-OCT-2000  MGD  v1.4.2, *no changes* (leave this one as a check
                          against the #include variant of CGILIB)
12-APR-2000  MGD  v1.4.1, minor changes for CGILIB 1.4
07-AUG-1999  MGD  v1.4.0, use more of the CGILIB functionality
24-APR-1999  MGD  v1.3.0, use CGILIB.C,
                          standard CGI environment (Netscape FastTrack),
                          accomodations for OSU environment
24-JUL-1998  MGD  v1.2.1, suppress table background colours if empty
                          bugfix; action script instead of background colour!
10-MAY-1998  MGD  v1.2.0, cosmetic changes
24-SEP-1996  MGD  v1.1.0, allow query string containing year or increment
20-FEB-1996  MGD  v1.0.0, quick hack
*/
/*****************************************************************************/

#define SOFTWAREVN "1.5.1"
#define SOFTWARENM "CALENDAR"
#ifdef __ALPHA
   char SoftwareID [64] = SOFTWARENM " AXP-" SOFTWAREVN;
#endif
#ifdef __ia64
   char SoftwareID [64] = SOFTWARENM " IA64-" SOFTWAREVN;
#endif
#ifdef __VAX
   char SoftwareID [64] = SOFTWARENM " VAX-" SOFTWAREVN;
#endif

#ifndef __VAX
#   pragma nomember_alignment
#endif

#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include <errno.h>

#include <descrip.h>
#include <libdef.h>
#include <libdtdef.h>
#include <ssdef.h>
#include <stsdef.h>

#define VMSok(x) ((x) & STS$M_SUCCESS)
#define VMSnok(x) !(((x) & STS$M_SUCCESS))

#define boolean int
#define true 1
#define false 0

#define FI_LI __FILE__, __LINE__

#define DEFAULT_BUTTONS "Calendar Request Form"

/* this macro just plugs in some script-specific code into ButtonBar() */
#define SCRIPT_SPECIFIC_BUTTON_CODE \
   if (*CgiKey1Ptr || *CgiKey2Ptr) \
      ButtonInternal[0] = CgiScriptNamePtr; \
   else \
      ButtonInternal[0] = ""; \

#define DEFAULT_PS_BGCOLOR        "#ffffff"
#define DEFAULT_PS_TEXT           "#000000"
#define DEFAULT_PS_LINK           "#0000cc"
#define DEFAULT_PS_VLINK          "#0000cc"
#define DEFAULT_PS_HEADBGCOLOR    "#cccccc"
#define DEFAULT_PS_HEADBORDER     "0"
#define DEFAULT_PS_HEADTEXT       "#000000"
#define DEFAULT_PS_BUTTONBGCOLOR  "#ffffff"
#define DEFAULT_PS_BUTTONBORDER   "1"

#define PS_BACKGROUND     0
#define PS_BGCOLOR        1
#define PS_TEXT           2
#define PS_LINK           3
#define PS_VLINK          4
#define PS_HEADBGCOLOR    5
#define PS_HEADTEXT       6
#define PS_HEADBORDER     7
#define PS_BUTTONBGCOLOR  8
#define PS_BUTTONBORDER   9
#define PS_BODYTAG       10
#define PS_LAYOUT        11
#define PS_HEADLOCAL     12
#define PS_HEADPADDING   13

char  *PageScheme [16];

char  Utility [] = "CALENDAR";

int  DaysInMonth [] = { 0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };

char  *DayName [] =
      { "", "Sunday", "Monday", "Tuesday", "Wednesday",
            "Thursday", "Friday", "Saturday", };

char  *MonthName [] =
      { "", "January", "February", "March", "April", "May", "June",
            "July", "August", "September", "October", "November", "December" };

unsigned long  LibDayOfMonth = LIB$K_DAY_OF_MONTH,
               LibDayOfWeek = LIB$K_DAY_OF_WEEK;

boolean  Debug;

int  MonthsAcrossPage,
     MonthsOnLine;

char  *ButtonPtr = DEFAULT_BUTTONS,
      *CgiEnvironmentPtr,
      *CgiFormYearPtr, 
      *CgiFormOtherYearPtr,
      *CgiKey1Ptr,
      *CgiKey2Ptr,
      *CgiScriptNamePtr,
      *CgiServerSoftwarePtr,
      *CliCalendarPtr,
      *CliCharsetPtr;
      
#define MonthIncrement 23
#define MaxPageLines 8
#define PageLineLength 512
char  PageLines [MaxPageLines][PageLineLength];

/*****************************************************************************/
/*
Include the CGILIB.H/.C function code from the standard WASD location if
CGILIB_INCLUDE_H/_C has not been defined.  If it has been defined then the
value must be the #include location.
*/

/* indicate required functionality */
#define CGILIB_CGIVAR_SUPPORT   1
#define CGILIB_OSU_SUPPORT      1
#define CGILIB_RESPONSE_SUPPORT 1

#define CGILIB_RESPONSE_BODY           PageScheme[PS_BODYTAG]
#define CGILIB_RESPONSE_CHARSET        CliCharsetPtr
#define CGILIB_RESPONSE_SOFTWAREID     SoftwareID
#define CGILIB_RESPONSE_ERROR_MESSAGE  "Reported by Calendar"

#define CGILIB_DEBUG (Debug)

#ifndef CGILIB_INCLUDE_H
#   include "/ht_root/src/misc/cgilib.h"
#   include "/ht_root/src/misc/cgilib.c"
#else
#   include CGILIB_INCLUDE_H
#   include CGILIB_INCLUDE_C
#endif

/*****************************************************************************/
/*
'argc/argv' are only required to support OSU, in particular CgiLibOsuInit().
*/

main
(
int argc,
char *argv[]
)
{
   /*********/
   /* begin */
   /*********/

   strcat (SoftwareID+15, " (");
   strcat (SoftwareID+15, CGILIB_SOFTWAREID);
   strcat (SoftwareID+15, ")");

   if (getenv ("CALENDAR$DBUG")) Debug = true;

   GetParameters ();

   if (CliCalendarPtr)
   {
      CalendarOnly ();
      exit (SS$_NORMAL);
   }

   CgiLibEnvironmentInit (argc, argv, false);
   CgiEnvironmentPtr = CgiLibEnvironmentName ();

   SetPageScheme ();

   CgiScriptNamePtr = CgiLibVar ("WWW_SCRIPT_NAME");
   CgiServerSoftwarePtr = CgiLibVar ("WWW_SERVER_SOFTWARE");
   CgiKey1Ptr = CgiLibVar ("WWW_KEY_1");
   CgiKey2Ptr = CgiLibVar ("WWW_KEY_2");
   CgiFormOtherYearPtr = CgiLibVar ("WWW_FORM_OTHERYEAR");
   CgiFormYearPtr = CgiLibVar ("WWW_FORM_YEAR");

   if (*CgiKey1Ptr || *CgiKey2Ptr)
      Calendar ();
   else
   if (*CgiFormOtherYearPtr)
      Calendar ();
   else
   {
      if (*CgiFormYearPtr)
         Calendar ();
      else
         CalendarForm ();
   }

   exit (SS$_NORMAL);
}

/*****************************************************************************/
/*
Get "command-line" parameters, whether from the command-line or from a
configuration symbol or logical containing the equivalent.  OSU scripts have
the 'method', 'url' and 'protocol' supplied as P1, P2, P3 (these being detected
and used by CGILIB), and are of no interest to this function.
*/

GetParameters ()

{
   static char  CommandLine [256];
   static unsigned long  Flags = 0;

   int  status,
        SkipParameters;
   unsigned short  Length;
   char  ch;
   char  *aptr, *cptr, *clptr, *sptr;
   $DESCRIPTOR (CommandLineDsc, CommandLine);

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

   if (!(clptr = getenv ("CALENDAR$PARAM")))
   {
      /* get the entire command line following the verb */
      if (VMSnok (status =
          lib$get_foreign (&CommandLineDsc, 0, &Length, &Flags)))
         exit (status);
      (clptr = CommandLine)[Length] = '\0';
   }

   /* if CSWS (Apache) */
   if (getenv ("APACHE$SHARED_SOCKET"))
   {
      /* look for something non-space outside of quotes */
      for (cptr = clptr; *cptr; cptr++)
      {
         if (isspace(*cptr)) continue;
         if (*cptr != '\"') break;
         cptr++;
         while (*cptr && *cptr != '\"') cptr++;
         if (*cptr) cptr++;
      }
      /* if nothing outside of quotes then ignore the command line */
      if (!*cptr) return;
   }

   /* if OSU environment then skip P1, P2, P3 */
   if (getenv ("WWWEXEC_RUNDOWN_STRING") != NULL)
      SkipParameters = 3;
   else
      SkipParameters = 0;

   aptr = NULL;
   ch = *clptr;
   for (;;)
   {
      if (aptr) *aptr = '\0';
      if (!ch) break;

      *clptr = ch;
      if (Debug) fprintf (stdout, "clptr |%s|\n", clptr);
      while (*clptr && isspace(*clptr)) *clptr++ = '\0';
      aptr = clptr;
      if (*clptr == '/') clptr++;
      while (*clptr && !isspace (*clptr) && *clptr != '/')
      {
         if (*clptr != '\"')
         {
            clptr++;
            continue;
         }
         cptr = clptr;
         clptr++;
         while (*clptr)
         {
            if (*clptr == '\"')
               if (*(clptr+1) == '\"')
                  clptr++;
               else
                  break;
            *cptr++ = *clptr++;
         }
         *cptr = '\0';
         if (*clptr) clptr++;
      }
      ch = *clptr;
      if (*clptr) *clptr = '\0';
      if (Debug) fprintf (stdout, "aptr |%s|\n", aptr);
      if (!*aptr) continue;

      if (SkipParameters)
      {
         SkipParameters--;
         continue;
      }

      if (strsame (aptr, "/BUTTONS=", 4))
      {
         for (cptr = aptr; *cptr && *cptr != '='; cptr++);
         if (*cptr) cptr++;
         ButtonPtr = cptr;
         continue;
      }
      if (strsame (aptr, "/CALENDAR=", 4))
      {
         for (cptr = aptr; *cptr && *cptr != '='; cptr++);
         if (*cptr) cptr++;
         CliCalendarPtr = cptr;
         continue;
      }
      if (strsame (aptr, "/CHARSET=", 4))
      {
         for (cptr = aptr; *cptr && *cptr != '='; cptr++);
         if (*cptr) cptr++;
         CliCharsetPtr = cptr;
         continue;
      }
      if (strsame (aptr, "/CSWS=APACHE", -1))
      {
         /* just ignore this Apache indicator */
         continue;
      }
      if (strsame (aptr, "/DBUG", -1))
      {
         Debug = true;
         continue;
      }
      if (strsame (aptr, "/DBUG", -1))
      {
         Debug = true;
         continue;
      }

      if (GetPageParameter (aptr)) continue;

      if (*aptr != '/')
      {
         fprintf (stdout, "%%%s-E-MAXPARM, too many parameters\n \\%s\\\n",
                  Utility, aptr);
         exit (STS$K_ERROR | STS$M_INHIB_MSG);
      }
      else
      {
         fprintf (stdout, "%%%s-E-IVQUAL, unrecognized qualifier\n \\%s\\\n",
                  Utility, aptr+1);
         exit (STS$K_ERROR | STS$M_INHIB_MSG);
      }
   }
}

/*****************************************************************************/
/*
Get command-line parameters associated with page scheme.
*/

boolean GetPageParameter (char *aptr)

{
   char  *cptr;

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

   if (strsame (aptr, "/PBACKGROUND=", 4))
   {
      for (cptr = aptr; *cptr && *cptr != '='; cptr++);
      if (*cptr) cptr++;
      PageScheme[PS_BACKGROUND] = cptr;
      return (true);
   }
   if (strsame (aptr, "/PBGCOLOR=", 4))
   {
      for (cptr = aptr; *cptr && *cptr != '='; cptr++);
      if (*cptr) cptr++;
      PageScheme[PS_BGCOLOR] = cptr;
      return (true);
   }
   if (strsame (aptr, "/PBBGCOLOR=", 5))
   {
      for (cptr = aptr; *cptr && *cptr != '='; cptr++);
      if (*cptr) cptr++;
      PageScheme[PS_BUTTONBGCOLOR] = cptr;
      return (true);
   }
   if (strsame (aptr, "/PBBORDER=", 5))
   {
      for (cptr = aptr; *cptr && *cptr != '='; cptr++);
      if (*cptr) cptr++;
      PageScheme[PS_BUTTONBORDER] = cptr;
      return (true);
   }
   if (strsame (aptr, "/PHBGCOLOR=", 5))
   {
      for (cptr = aptr; *cptr && *cptr != '='; cptr++);
      if (*cptr) cptr++;
      PageScheme[PS_HEADBGCOLOR] = cptr;
      return (true);
   }
   if (strsame (aptr, "/PHBORDER=", 5))
   {
      for (cptr = aptr; *cptr && *cptr != '='; cptr++);
      if (*cptr) cptr++;
      PageScheme[PS_HEADBORDER] = cptr;
      return (true);
   }
   if (strsame (aptr, "/PHTEXT=", 4))
   {
      for (cptr = aptr; *cptr && *cptr != '='; cptr++);
      if (*cptr) cptr++;
      PageScheme[PS_HEADTEXT] = cptr;
      return (true);
   }
   if (strsame (aptr, "/PLAYOUT=", 4))
   {
      for (cptr = aptr; *cptr && *cptr != '='; cptr++);
      if (*cptr) cptr++;
      PageScheme[PS_LAYOUT] = cptr;
      return (true);
   }
   if (strsame (aptr, "/PLINK=", 4))
   {
      for (cptr = aptr; *cptr && *cptr != '='; cptr++);
      if (*cptr) cptr++;
      PageScheme[PS_LINK] = cptr;
      return (true);
   }
   if (strsame (aptr, "/PHLOCAL=", 4))
   {
      for (cptr = aptr; *cptr && *cptr != '='; cptr++);
      if (*cptr) cptr++;
      PageScheme[PS_HEADLOCAL] = cptr;
      return (true);
   }
   if (strsame (aptr, "/PTEXT=", 4))
   {
      for (cptr = aptr; *cptr && *cptr != '='; cptr++);
      if (*cptr) cptr++;
      PageScheme[PS_TEXT] = cptr;
      return (true);
   }
   if (strsame (aptr, "/PVLINK=", 4))
   {
      for (cptr = aptr; *cptr && *cptr != '='; cptr++);
      if (*cptr) cptr++;
      PageScheme[PS_VLINK] = cptr;
      return (true);
   }
   return (false);
}

/*****************************************************************************/
/*
Set the page layout and colouration.
*/

SetPageScheme ()

{
   int  size;
   char  *sptr;

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

   if (Debug) fprintf (stdout, "SetPageScheme()\n");

   if (!PageScheme[PS_LAYOUT])
      PageScheme[PS_LAYOUT] = "1";

   if (!PageScheme[PS_BACKGROUND])
      PageScheme[PS_BACKGROUND] = "";

   if (!PageScheme[PS_HEADLOCAL])
      PageScheme[PS_HEADLOCAL] = "";

   if (PageScheme[PS_LAYOUT][0] == '2')
   {
      if (!PageScheme[PS_BGCOLOR]) PageScheme[PS_BGCOLOR] = "";
      if (!PageScheme[PS_TEXT]) PageScheme[PS_TEXT] = "";
      if (!PageScheme[PS_LINK]) PageScheme[PS_LINK] = "";
      if (!PageScheme[PS_VLINK]) PageScheme[PS_VLINK] = "";
      if (!PageScheme[PS_HEADBGCOLOR]) PageScheme[PS_HEADBGCOLOR] = "";
      if (!PageScheme[PS_HEADBORDER]) PageScheme[PS_HEADBORDER] = "";
      if (!PageScheme[PS_HEADTEXT]) PageScheme[PS_HEADTEXT] = "";
      if (!PageScheme[PS_BUTTONBGCOLOR]) PageScheme[PS_BUTTONBGCOLOR] = "";
      if (!PageScheme[PS_BUTTONBORDER]) PageScheme[PS_BUTTONBORDER] = "";
   }
   else
   {
      if (!PageScheme[PS_BGCOLOR])
         PageScheme[PS_BGCOLOR] = DEFAULT_PS_BGCOLOR;
      if (!PageScheme[PS_TEXT])
         PageScheme[PS_TEXT] = DEFAULT_PS_TEXT;
      if (!PageScheme[PS_LINK])
         PageScheme[PS_LINK] = DEFAULT_PS_LINK;
      if (!PageScheme[PS_VLINK])
         PageScheme[PS_VLINK] = DEFAULT_PS_VLINK;
      if (!PageScheme[PS_HEADBGCOLOR])
         PageScheme[PS_HEADBGCOLOR] = DEFAULT_PS_HEADBGCOLOR;
      if (!PageScheme[PS_HEADBORDER])
         PageScheme[PS_HEADBORDER] = DEFAULT_PS_HEADBORDER;
      if (!PageScheme[PS_HEADTEXT])
         PageScheme[PS_HEADTEXT] = DEFAULT_PS_HEADTEXT;
      if (!PageScheme[PS_BUTTONBGCOLOR])
         PageScheme[PS_BUTTONBGCOLOR] = DEFAULT_PS_BUTTONBGCOLOR;
      if (!PageScheme[PS_BUTTONBORDER])
         PageScheme[PS_BUTTONBORDER] = DEFAULT_PS_BUTTONBORDER;
   }

   /* <BODY> tag attributes */
   size = strlen(PageScheme[PS_BACKGROUND]) +
          strlen(PageScheme[PS_BGCOLOR]) +
          strlen(PageScheme[PS_TEXT]) +
          strlen(PageScheme[PS_LINK]) +
          strlen(PageScheme[PS_VLINK]);
   if (size)
   {
      if (!(sptr = calloc (1, size+64))) exit (vaxc$errno);
      PageScheme[PS_BODYTAG] = sptr;
      if (PageScheme[PS_BACKGROUND][0])
         sptr += sprintf (sptr, " BACKGROUND=\"%s\"", PageScheme[PS_BACKGROUND]);
      if (PageScheme[PS_BGCOLOR][0])
         sptr += sprintf (sptr, " BGCOLOR=\"%s\"", PageScheme[PS_BGCOLOR]);
      if (PageScheme[PS_TEXT][0])
         sptr += sprintf (sptr, " TEXT=\"%s\"", PageScheme[PS_TEXT]);
      if (PageScheme[PS_LINK][0])
         sptr += sprintf (sptr, " LINK=\"%s\"", PageScheme[PS_LINK]);
      if (PageScheme[PS_VLINK][0])
         sptr += sprintf (sptr, " VLINK=\"%s\"", PageScheme[PS_VLINK]);
   }
   else
      PageScheme[PS_BODYTAG] = "";

   if (PageScheme[PS_HEADBGCOLOR][0])
   {
      if (!(sptr = calloc (1, strlen(PageScheme[PS_HEADBGCOLOR])+16)))
         exit (vaxc$errno);
      sprintf (sptr, " BGCOLOR=\"%s\"", PageScheme[PS_HEADBGCOLOR]);
      PageScheme[PS_HEADBGCOLOR] = sptr;
      PageScheme[PS_HEADPADDING] = "10";
   }
   else
      PageScheme[PS_HEADPADDING] = "0";

   if (PageScheme[PS_BUTTONBGCOLOR][0])
   {
      if (!(sptr = calloc (1, strlen(PageScheme[PS_BUTTONBGCOLOR])+16)))
         exit (vaxc$errno);
      sprintf (sptr, " BGCOLOR=\"%s\"", PageScheme[PS_BUTTONBGCOLOR]);
      PageScheme[PS_BUTTONBGCOLOR] = sptr;
   }
}

/*****************************************************************************/
/*
Provides a divider for top and bottom of the content of the page. This can be
a coloured bar (using <TABLE>) or a horizontal rule depending on the page
layout. "Buttons" providing script-internal and/or additional user-specified
links ('ButtonPtr' string) can be placed with(in) this bar. All button labels
are derived from 'ButtonPtr', with script-internal buttons using link-paths
set up via 'ButtonInternal[]' array, or any user-specified path depending on
requirement. An empty path (i.e. PathPtr[0] == '\0') obviously does not have a
link created, it just displays the button label. For a button-bar at the top
of the document use 1, bottom of the document use 2, and for just a bar with
no buttons at all use 0.
*/

ButtonBar (int Top1Bottom2)

{
#define MAX_BUTTON_COUNT 8

   static int  ButtonCount = -1;
   static char  *ButtonInternal [MAX_BUTTON_COUNT],
                *ButtonLabel [MAX_BUTTON_COUNT],
                *ButtonPath [MAX_BUTTON_COUNT];

   int  idx;
   char  *PathPtr;

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

   if (Debug) fprintf (stdout, "ButtonBar() %d\n", Top1Bottom2);

   if (ButtonCount == -1)
   {
      char  *cptr, *sptr;

      if (Debug) fprintf (stdout, "|%s|\n", ButtonPtr);
      cptr = ButtonPtr;
      for (ButtonCount = 0;
           ButtonCount < MAX_BUTTON_COUNT && *cptr;
           ButtonCount++)
      {
         for (sptr = cptr; *sptr && *sptr != '=' && *sptr != ';'; sptr++)
            if (*sptr == '\\') memcpy (sptr, sptr+1, strlen(sptr));
         if (*sptr == '=') *sptr++ = '\0';
         ButtonLabel[ButtonCount] = cptr;
         cptr = sptr;
         for (sptr = cptr; *sptr && *sptr != ';'; sptr++)
            if (*sptr == '\\') memcpy (sptr, sptr+1, strlen(sptr));
         if (*sptr) *sptr++ = '\0';
         ButtonPath[ButtonCount] = cptr;
         cptr = sptr;
      }
   }

   if (Top1Bottom2)
   {
      /***********************************/
      /* set up script-specified buttons */
      /***********************************/

      SCRIPT_SPECIFIC_BUTTON_CODE
   }

   if (PageScheme[PS_LAYOUT][0] == '2')
   {
      /************/
      /* format 2 */
      /************/

      if (Top1Bottom2 == 2 || !Top1Bottom2)
      {
         fprintf (stdout, "<HR ALIGN=left SIZE=2 WIDTH=95%%>\n");
         if (!Top1Bottom2) return;
      }

      fprintf (stdout, "<FONT SIZE=-1><NOBR>\n");
      for (idx = 0; idx < ButtonCount; idx++)
      {
         if (!ButtonInternal[idx])
            PathPtr = ButtonPath[idx];
         else
            PathPtr = ButtonInternal[idx];
         if (idx) fprintf (stdout, "&nbsp;");
         if (PathPtr[0])
            fprintf (stdout, "[<A HREF=\"%s\">%s</A>]\n",
                     PathPtr, ButtonLabel[idx]);
         else
            fprintf (stdout, "[%s]\n", ButtonLabel[idx]);
      }
      fprintf (stdout, "</NOBR></FONT>\n");

      if (Top1Bottom2 == 1)
         fprintf (stdout, "<HR ALIGN=left SIZE=2 WIDTH=95%%>\n");
   }
   else
   {
      /************/
      /* format 1 */
      /************/

      fprintf (stdout,
"<TABLE BORDER=0 CELLPADDING=0 CELLSPACING=0>\
<TR><TD HEIGHT=2></TD></TR>\
</TABLE>\n\
<TABLE BORDER=%s CELLPADDING=%s CELLSPACING=0 WIDTH=100%%>\n\
<TR><TD%s>\n",
         PageScheme[PS_HEADBORDER],
         PageScheme[PS_HEADBORDER],
         PageScheme[PS_HEADBGCOLOR]);

      if (ButtonCount == 0 || !Top1Bottom2)
         fprintf (stdout, "&nbsp;\n");
      else
      {
         fprintf (stdout,
"<TABLE BORDER=%s CELLPADDING=1 CELLSPACING=0>\n",
            PageScheme[PS_BUTTONBORDER]);

         for (idx = 0; idx < ButtonCount; idx++)
         {
            if (!ButtonInternal[idx])
               PathPtr = ButtonPath[idx];
            else
               PathPtr = ButtonInternal[idx];
            if (PathPtr[0])
               fprintf (stdout,
"<TD ALIGN=center%s><FONT SIZE=-1>\
<NOBR>&nbsp;&nbsp;<A HREF=\"%s\">%s</A>&nbsp;&nbsp;</NOBR></FONT></TD>\n",
                  PageScheme[PS_BUTTONBGCOLOR], PathPtr, ButtonLabel[idx]);
            else
               fprintf (stdout,
"<TD ALIGN=center%s><FONT SIZE=-1>\
<NOBR>&nbsp;&nbsp;%s&nbsp;&nbsp;</NOBR></FONT></TD>\n",
                  PageScheme[PS_BUTTONBGCOLOR], ButtonLabel[idx]);
         }

         fprintf (stdout, "</TR></TABLE>\n");
      }

      fprintf (stdout,
"</TD></TR>\n\
</TABLE>\n\
<TABLE BORDER=0 CELLPADDING=0 CELLSPACING=0>\
<TR><TD HEIGHT=2></TD></TR>\
</TABLE>\n");
   }
}

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

CalendarForm ()

{
   int  LastYear,
        Year;
   unsigned long  BinTime [2];
   unsigned short  NumTime [7];

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

   if (Debug) fprintf (stdout, "CalendarForm()\n");

   sys$gettim (&BinTime);
   sys$numtim (&NumTime, &BinTime);

   CgiLibResponseHeader (200, "text/html");

   fprintf (stdout,
"<HTML>\n\
<HEAD>\n\
<META NAME=\"generator\" CONTENT=\"%s\">\n\
<META NAME=\"environment\" CONTENT=\"%s\">\n\
<TITLE>Calendar</TITLE>\n\
</HEAD>\n\
<BODY%s>\n",
      SoftwareID, 
      CgiEnvironmentPtr,
      PageScheme[PS_BODYTAG]);

   if (PageScheme[PS_LAYOUT][0] == '2')
   {
      fprintf (stdout,
"%s<FONT SIZE=+2><B>\n\
Calendar Request Form\n\
</B></FONT>\n\
<HR ALIGN=left SIZE=2 WIDTH=95%%>\n\
<P>\n",
         PageScheme[PS_HEADLOCAL]);
   }
   else
   {
      fprintf (stdout,
"<TABLE BORDER=%s CELLPADDING=%s CELLSPACING=0 WIDTH=100%%>\n\
<TR><TD%s>\n\
<FONT COLOR=\"%s\" SIZE=+2><B>\n\
Calendar Request Form\n\
</B></FONT>\n\
</TD>%s</TR>\n\
</TABLE>\n",
         PageScheme[PS_HEADBORDER],
         PageScheme[PS_HEADPADDING],
         PageScheme[PS_HEADBGCOLOR],
         PageScheme[PS_HEADTEXT],
         PageScheme[PS_HEADLOCAL]);
   }

   fprintf (stdout,
"<BLOCKQUOTE>\n\
<FORM ACTION=\"%s\">\n\
<SELECT NAME=FromMonth>\n\
<OPTION VALUE=1 SELECTED> January\n\
<OPTION VALUE=2> February\n\
<OPTION VALUE=3> March\n\
<OPTION VALUE=4> April\n\
<OPTION VALUE=5> May\n\
<OPTION VALUE=6> June\n\
<OPTION VALUE=7> July\n\
<OPTION VALUE=8> August\n\
<OPTION VALUE=9> September\n\
<OPTION VALUE=10> October\n\
<OPTION VALUE=11> November\n\
<OPTION VALUE=12> December\n\
</SELECT>\n\
to <SELECT NAME=ToMonth>\n\
<OPTION VALUE=1> January\n\
<OPTION VALUE=2> February\n\
<OPTION VALUE=3> March\n\
<OPTION VALUE=4> April\n\
<OPTION VALUE=5> May\n\
<OPTION VALUE=6> June\n\
<OPTION VALUE=7> July\n\
<OPTION VALUE=8> August\n\
<OPTION VALUE=9> September\n\
<OPTION VALUE=10> October\n\
<OPTION VALUE=11> November\n\
<OPTION VALUE=12 SELECTED> December\n\
</SELECT>\n\
of <SELECT NAME=Year>\n",
      CgiScriptNamePtr);

   LastYear = (Year = NumTime[0] - 10) + 21;
   for (/* above */; Year < LastYear; Year++)
      if (Year == NumTime[0])
         fprintf (stdout, "<OPTION VALUE=%d SELECTED>%d\n", Year, Year);
      else
         fprintf (stdout, "<OPTION VALUE=%d>%d\n", Year, Year);

   fprintf (stdout,
"</SELECT>\n\
... or year <INPUT TYPE=text SIZE=4 NAME=OtherYear>\n\
<P><SELECT NAME=Across>\n\
<OPTION VALUE=1>1\n\
<OPTION VALUE=2>2\n\
<OPTION VALUE=3 SELECTED>3\n\
<OPTION VALUE=4>4\n\
<OPTION VALUE=5>5\n\
<OPTION VALUE=6>6\n\
<OPTION VALUE=7>7\n\
<OPTION VALUE=8>8\n\
<OPTION VALUE=9>9\n\
<OPTION VALUE=10>10\n\
<OPTION VALUE=11>11\n\
<OPTION VALUE=12 SELECTED>12\n\
</SELECT>\n\
months across page\n\
<P><INPUT TYPE=submit VALUE=\" generate \">\n\
<INPUT TYPE=reset VALUE=\" reset \">\n\
</FORM>\n\
</BLOCKQUOTE>\n\
<P>\n");

   ButtonBar (2);

   fprintf (stdout, "</BODY>\n</HTML>\n");
}

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

Calendar ()

{
   int  status,
        Day,
        FromMonth,
        Month,
        ToMonth,
        Year;
   unsigned long  BinTime [2];
   unsigned short  NumTime [7];
   char  *cptr;
   char  Range [32];

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

   if (Debug) fprintf (stdout, "Calendar()\n");

   sys$gettim (&BinTime);
   sys$numtim (&NumTime, &BinTime);

   if (*CgiKey1Ptr)
   {
      Year = atoi(CgiKey1Ptr);
      if (Year >= -99 && Year <= 99) Year = NumTime[0] + Year;
   }
   else
   if (*CgiKey2Ptr)
   {
      Year = atoi(CgiKey2Ptr);
      if (Year >= -99 && Year <= 99) Year = NumTime[0] + Year;
   }
   else
   if (*CgiFormOtherYearPtr)
      Year = atoi(CgiFormOtherYearPtr);
   else
      Year = atoi(CgiFormYearPtr);

   if (!Year) Year = NumTime[0];
   if (Year < 1859 || Year > 9999)
   {
      CgiLibResponseError (FI_LI, 0, "Year out of range (1859 to 9999).");
      exit (SS$_NORMAL);
   }

   cptr = CgiLibVar ("WWW_FORM_FROMMONTH");
   if (*cptr)
      FromMonth = atoi(cptr);
   else
      FromMonth = 1;
   if (FromMonth < 1 || FromMonth > 12)
   {
      CgiLibResponseError (FI_LI, 0, "<I>From</I> month out of range.");
      exit (SS$_NORMAL);
   }

   cptr = CgiLibVar ("WWW_FORM_TOMONTH");
   if (*cptr)
      ToMonth = atoi(cptr);
   else
      ToMonth = 12;
   if (ToMonth < 1 || ToMonth > 12)
   {
      CgiLibResponseError (FI_LI, 0, "<I>To</I> month out of range.");
      exit (SS$_NORMAL);
   }
   if (FromMonth > ToMonth)
   {
      CgiLibResponseError (FI_LI, 0, 
         "<I>From</I> month greater than <I>to</I> out of range.");
      exit (SS$_NORMAL);
   }
   cptr = CgiLibVar ("WWW_FORM_ACROSS");
   if (*cptr)
      MonthsAcrossPage = atoi(cptr);
   else
      MonthsAcrossPage = 3;
   if (MonthsAcrossPage < 1 || MonthsAcrossPage > 12)
   {
      CgiLibResponseError (FI_LI, 0, "Months across page out of range.");
      exit (SS$_NORMAL);
   }
   if (FromMonth > 1 || ToMonth < 12)
      sprintf (Range, " &nbsp;<FONT SIZE=+2>%s to %s</FONT>",
               MonthName[FromMonth], MonthName[ToMonth]);
   else
      Range[0] = '\0';

   CgiLibResponseHeader (200, "text/html");

   fprintf (stdout,
"<HTML>\n\
<HEAD>\n\
<META NAME=\"generator\" CONTENT=\"%s\">\n\
<META NAME=\"environment\" CONTENT=\"%s\">\n\
<TITLE>Calendar %d</TITLE>\n\
</HEAD>\n\
<BODY%s>\n",
      SoftwareID,
      CgiEnvironmentPtr,
      Year,
      PageScheme[PS_BODYTAG]);

   if (PageScheme[PS_LAYOUT][0] == '2')
   {
      fprintf (stdout,
"%s<FONT SIZE=+2><B>\n\
Calendar %d%s\n\
</B></FONT>\n\
<HR ALIGN=left SIZE=2 WIDTH=95%%>\n\
<BLOCKQUOTE>\n\
<PRE>",
         PageScheme[PS_HEADLOCAL],
         Year, Range);
   }
   else
   {
      fprintf (stdout,
"<TABLE BORDER=%s CELLPADDING=%s CELLSPACING=0 WIDTH=100%%%s>\n\
<TR><TD>\n\
<FONT COLOR=\"%s\" SIZE=+2><B>\n\
&nbsp;Calendar %d%s\n\
</B></FONT>\n\
</TD>%s</TR>\n\
</TABLE>\n\
<BLOCKQUOTE>\n\
<PRE>",
         PageScheme[PS_HEADBORDER],
         PageScheme[PS_HEADPADDING],
         PageScheme[PS_HEADBGCOLOR],
         PageScheme[PS_HEADTEXT],
         Year, Range,
         PageScheme[PS_HEADLOCAL]);
   }

   NumTime[0] = Year;
   NumTime[1] = NumTime[2] = 1;
   NumTime[3] = NumTime[4] = NumTime[5] = NumTime[6] = 0;

   if (!MonthsAcrossPage) MonthsAcrossPage = 3;

   for (Month = FromMonth; Month <= ToMonth; Month++)
       AddMonth (Year, Month);
   if (MonthsOnLine) OutputPageLines ();

   fprintf (stdout,
"</PRE>\n\
</BLOCKQUOTE>\n\
<P>\n");

   if (*CgiKey1Ptr || *CgiKey2Ptr)
      ButtonBar (2);
   else
      ButtonBar (0);

   fprintf (stdout, "</BODY>\n</HTML>\n");
}

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

CalendarOnly ()

{
   int  status,
        FromMonth,
        Month,
        ToMonth,
        Year;
   unsigned long  BinTime [2];
   unsigned short  NumTime [7];
   char  *cptr;

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

   if (Debug) fprintf (stdout, "CalendarOnly() |%s|\n", CliCalendarPtr);

   cptr = CliCalendarPtr;
   Year = atoi(cptr);
   while (isdigit(*cptr)) cptr++;
   while (*cptr && !isdigit(*cptr)) cptr++;
   MonthsAcrossPage = atoi(cptr);

   FromMonth = 1;
   ToMonth = 12;

   sys$gettim (&BinTime);
   sys$numtim (&NumTime, &BinTime);

   if (!Year) Year = NumTime[0];

   NumTime[0] = Year;
   NumTime[1] = NumTime[2] = 1;
   NumTime[3] = NumTime[4] = NumTime[5] = NumTime[6] = 0;

   if (!MonthsAcrossPage) MonthsAcrossPage = 3;

   fprintf (stdout, "<PRE>");

   for (Month = FromMonth; Month <= ToMonth; Month++)
       AddMonth (Year, Month);
   if (MonthsOnLine) OutputPageLines ();

   fprintf (stdout, "</PRE>");
}

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

AddMonth
(
int Year,
int Month
)
{
   int  ccnt, lcnt, status,
        Count,
        DaysInThisMonth;
   unsigned long  DayOfMonth,
                  DayOfWeek;
   unsigned long  BinTime [2];
   unsigned short  NumTime [7];
   char  *cptr;
   char  Line [256];

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

   if (Debug) fprintf (stdout, "AddMonth() %d %d\n", Year, Month);

   if (!MonthsOnLine || MonthsOnLine >= MonthsAcrossPage)
   {
      if (MonthsOnLine >= MonthsAcrossPage) OutputPageLines ();

      for (lcnt = 0; lcnt < MaxPageLines; lcnt++)
      {
          if (Debug) fprintf (stdout, "lcnt: %d\n", lcnt);
          memset (PageLines[lcnt], ' ', PageLineLength);
          PageLines[lcnt][PageLineLength-1] = '\0';
      }
      MonthsOnLine = 0;
   }
   lcnt = 0;

   memset (NumTime, 0, sizeof(NumTime));
   NumTime[0] = Year;
   NumTime[1] = Month;

   DaysInThisMonth = DaysInMonth[Month];
   if (Month == 2)
   {
      /* if the time conversion returns an error then it's not a leap year! */
      NumTime[2] = 29;
      status = lib$cvt_vectim (&NumTime, &BinTime);
      if (VMSnok (status) && status != LIB$_IVTIME)
      {
         CgiLibResponseError (FI_LI, status, "lib$cvt_vectim()");
         exit (SS$_NORMAL);
      }
      if (VMSok (status)) DaysInThisMonth = 29;
   }

   /* the the day of the week the month starts on */
   NumTime[2] = 1;
   if (VMSnok (status = lib$cvt_vectim (&NumTime, &BinTime)))
   {
      CgiLibResponseError (FI_LI, status, "lib$cvt_vectim()");
      exit (SS$_NORMAL);
   }
   if (VMSnok (status =
       lib$cvt_from_internal_time (&LibDayOfWeek, &DayOfWeek, &BinTime)))
   {
      CgiLibResponseError (FI_LI, status, "lib$cvt_from_internal_time()");
      exit (SS$_NORMAL);
   }

   /* we count our week days traditionally (Sunday to Saturday) */
   if (DayOfWeek == 7)
      DayOfWeek = 1;
   else
      DayOfWeek++;
   if (Debug)
       fprintf (stdout, "DayOfWeek: %d DaysInThisMonth: %d\n",
                DayOfWeek, DaysInThisMonth);

   /*********/
   /* month */
   /*********/

   cptr = PageLines[lcnt++] + MonthsOnLine * MonthIncrement;
   for (Count = 0; Count < MonthsOnLine; Count++) cptr += 7;
   ccnt = sprintf (cptr, "%*s<B>%s</B>",
                   (20 - strlen(MonthName[Month])) / 2, "", MonthName[Month]);
   cptr[ccnt] = ' ';

   /*************/
   /* day names */
   /*************/

   cptr = PageLines[lcnt++] + MonthsOnLine * MonthIncrement;
   for (Count = 0; Count < MonthsOnLine; Count++) cptr += 7;
   memcpy (cptr, "<B>", 3); 
   cptr += 3;
   for (Count = 1; Count <= 7; Count++)
   {
      if (Count > 1) *cptr++ = ' ';
      memcpy (cptr, DayName[Count], 2); 
      cptr += 2;
   }
   memcpy (cptr, "</B>", 4); 

   /**********************/
   /* leading empty days */
   /**********************/

   cptr = PageLines[lcnt++] + MonthsOnLine * MonthIncrement;
   for (Count = 1; Count < DayOfWeek; Count++)
   {
      memcpy (cptr, "   ", 3); 
      if (Count == 1)
         cptr += 2;
      else
         cptr += 3;
   }

   /********/
   /* days */
   /********/

   for (DayOfMonth = 1; DayOfMonth <= DaysInThisMonth; DayOfMonth++)
   {
      if (Count == 1)
         cptr += sprintf (cptr, "%2d", DayOfMonth);
      else
         cptr += sprintf (cptr, " %2d", DayOfMonth);
      *cptr = ' ';
      if (Count++ >= 7)
      {
         cptr = PageLines[lcnt++] + MonthsOnLine * MonthIncrement;
         Count = 1;
      }
   }

   MonthsOnLine++;
}

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

OutputPageLines ()

{
   int  lcnt;
   char  *cptr;

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

   if (Debug) fprintf (stdout, "OutputPageLines()\n");

   if (!MonthsOnLine) return;
   fprintf (stdout, "\n");
   for (lcnt = 0; lcnt < MaxPageLines; lcnt++)
   {
       if (Debug) fprintf (stdout, "lcnt: %d\n", lcnt);
       for (cptr = PageLines[lcnt]; *cptr && *cptr == ' '; cptr++);
       if (!*cptr) break;
       while (*cptr) cptr++;
       if (cptr > PageLines[lcnt]) cptr--;
       while (*cptr == ' ' && cptr > PageLines[lcnt]) cptr--;
       if (cptr > PageLines[lcnt]) cptr++;
       *cptr = '\0';
       fprintf (stdout, "%s\n", PageLines[lcnt]);
   }
   MonthsOnLine = 0;
}

/****************************************************************************/
/*
Does a case-insensitive, character-by-character string compare and returns 
true if two strings are the same, or false if not.  If a maximum number of 
characters are specified only those will be compared, if the entire strings 
should be compared then specify the number of characters as 0.
*/ 

boolean strsame
(
char *sptr1,
char *sptr2,
int  count
)
{
   while (*sptr1 && *sptr2)
   {
      if (toupper (*sptr1++) != toupper (*sptr2++)) return (false);
      if (count)
         if (!--count) return (true);
   }
   if (*sptr1 || *sptr2)
      return (false);
   else
      return (true);
}

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