/*****************************************************************************/
/*
                                  message.c

Displays a list of messages in a folder.
Displays the contents of a message (header, body, links to attachments).
Displays a 'print-view' (more suitable for printing) of a message.
Displays the raw message (header+body, or header alone).
Deletes specified messages.


CHARACTER SETS AND DIRECTIONALITY
---------------------------------
Most scripts are written left-to-right.  This is certainly the case for the
lion's share of soyMAIL environments.  However soyMAIL does use a mail
message's or MIME part's content-type specified character set if available.  It
applies this to the entire message page.  On browsers and platforms supporting
multiple such sets this will leave the soyMAIL labeling in it's original format
(some Latin character set) while displaying the message content in the
transmitted character set.  This can be a non-Latin set.

Of course, some scripts (a minority in number though a significant population
of users) are written right-to-left.   Two common examples are Arabic (and
languages using that character set) and Hebrew.  HTML supports rendering using
this direction.  All that needs to be done is identify the encoding.  With
the deprecated ISO-8859-6 (Arabic) and ISO-8859-8 (Hebrew) this was
straight-forward.  However with the more modern UTF-8 this is less obvious. 
Hence with character sets not explicitly known to be left-to-right renderings
only two buttons are prevised in the attachment panel, "|>>" and "<<|" to allow
the directionality to be explicitly requested by the user.

Known left-to-rights are specified by macro CHARSETS_COMMONLY_LTR and known
right-to-lefts by CHARSETS_COMMONLY_RTL and configuration directive
[charsets-rtl] (which must (re-)specify all such characters sets desired).


THE FILE DOES NOT BEGIN WITH '%PDF-'
------------------------------------
And other similar (MS platform) issues.  Often this sort of issue occurs
because the attachment button does not result in a URL with a trailing file
type (extension).  The associated [^] button results in a request with such a
URL.  To allow the main [attachment] button to provide a similar function (and
thus avoid the issue of users getting their knickers in a knot) the following
configuration directive can be employed.

   [if-CGI-http_user_agent] Windows NT
   [attachment-MIME-types] text/ image/
   [if-END]

Modify as required.


COPYRIGHT
---------
Copyright (C) 2005-2025 Mark G.Daniel
This program, comes with ABSOLUTELY NO WARRANTY.
This is free software, and you are welcome to redistribute it under the
conditions of the GNU GENERAL PUBLIC LICENSE, version 3, or any later version.


VERSION HISTORY
---------------
01-JUL-2025  MGD  ensure attachment buttons are pushed left
25-MAR-2023  MGD  MessageCollapseLineBreaks() reduce '\r\n\r\n' to '\r\n'
13-JUL-2016  MGD  bugfix; MessagePrintView() missing SoyMailJavaScriptStuff()
09-JUN-2013  MGD  MessagePreview() munge end-of-line into newline so
                    that new CSS can preview as pre-formatted text
25-FEB-2010  MGD  HtmlToPlain() supply charset for UTF-8 deentify
16-FEB-2010  MGD  MessagePreView() and associated JavaScript
22-AUG-2008  MGD  MessageAttachments() improve attachment name parsing
29-JUL-2008  MGD  MessageHeader() [^] immediate edit when adding contact
                  MessagePageMsgRequest() change contact addition processing
05-JUL-2008  MGD  MessageBodyText() and MessagePrintView() hack of CGILIB
                    to make "mailto:"s access soyMAIL as mail agent
07-NOV-2007  MGD  MessageFolderList() use JavaScript and ONMOUSE.. to
                    highlight currently focused message item
12-AUG-2007  MGD  MessageFolderList() add JavaScript to popup a message by
                    using any of the 'Received', 'From' or 'Subject' fields
11-JUL-2007  MGD  bugfix; MessageBodyProcess() return if RFC822 header error
31-MAY-2007  MGD  relax 'cannot copy to' sent items (allows NEWMAIL, etc.)
13-MAY-2007  MGD  bugfix; MessagePageFolderRequest() apply 'MessageCountDelta'
                    to 'RangeIdStart' after MOVE, WASTE, DELETE or EMPTY 
20-APR-2007  MGD  MessageBodyProcess() refine RFC822 header error reporting
16-MAR-2007  MGD  RequestVmsMailContext() establishes file/folder context
10-FEB-2007  MGD  Purveyor support removed
27-JAN-2007  MGD  specially process PMDF IMAP folder subdirectories
                  MessagePage() position MessageAttachments() above or below
                    message body according to [message-attachment-panel]
03-JAN-2007  MGD  bugfix; MessageAttachments() perform URL_ENCODE() rather
                  than HTML_ESCAPE() on buttons for message attachment URLs
28-DEC-2006  MGD  MessageDestListChange() for destination list refresh
15-OCT-2006  MGD  MessageBodyButtons() rearrange [delete][waste] buttons
                    to be that same order as MessagePageFolderRequest()
19-JUL-2006  MGD  MessageFolderList() user optional message ordering
24-JUN-2006  MGD  MessageBodyProcess() to perform message part processing
20-JUN-2006  MGD  CallMailUserDiskQuota() and MessageDiskQuotaUsed() notifies
                    the client with each folder open once disk quota reaches
                    the percentage specified by default or configuration
18-JUN-2006  MGD  MessageAttachments() for any RFC822 message
                  identify and make available any HTML content in a non-MIME
                    message or in MIME text/plain in-line content
24-APR-2006  MGD  MessageHeader() show reply-to: field when different to from:
03-APR-2005  MGD  significant rework to rationalise public and private
                  MessageMassage() massaged to be massaged in situ!
30-MAR-2006  MGD  MessagePage() precede each message part/attachment with
                  an anchor/link to allow for right-click browser save-as
25-MAR-2006  MGD  MessagePgpSigned() and MessagePgpCut() remove PGP signature
                  MessagePart() small accomodation for Purveyor
14-MAR-2006  MGD  bugfix; MessageBodyIdentify() when generating plain from
                    HTML-only content call MimeDecProcessPart() to decode
                  bugfix; MessageBodyIdentify() default a non-MIME message
                    message to 8-bit, plain-text
10-MAR-2006  MGD  check return from MimeDecParseBody() and process accordingly
                  remove 'noisy' message count from [message] range button
04-MAR-2006  MGD  MessagePage() add MessageBodyButtons() after longer messages
25-FEB-2006  MGD  refine MessageBodyIdentify() to manufacture plain-text
                    content when there is only HTML content
07-FEB-2006  MGD  optional folder buttons on left-side of message listing
05-FEB-2006  MGD  support saving message as "message/rfc822" 'attachment'
01-FEB-2005  MGD  initial
*/

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

#ifdef SOYMAIL_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

#pragma nomember_alignment

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

/* VMS related header files */
#include <descrip.h>
#include <mailmsgdef.h>
#include <ssdef.h>
#include <stsdef.h>
#include <time.h>

/* application header file */
#include "soymail.h"
#include "attach.h"
#include "callmail.h"
#include "cgilib.h"
#include "compose.h"
#include "config.h"
#include "html.h"
#include "mainmenu.h"
#include "other.h"
#include "options.h"
#include "message.h"
#include "request.h"

/* can't get the include dependencies settled (this'll do in the interim) */
int ComposeWrapAt (int, USER_OPTIONS*);

#define FI_LI "MESSAGE", __LINE__

/* number of message lines before second set of message disposition buttons */
#define MESSAGE_BUTTONS_AFTER 32

#define MAIL$M_NEWMSG  0x01
#define MAIL$M_REPLIED 0x02
#define MAIL$M_MARKED  0x80

#define FLAG_NEWMSG  "&times;"
#define FLAG_MARKED  "&Dagger;"
#define FLAG_REPLIED "&bull;"

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

extern BOOL  Debug,
             IsCgiPlus,
             WatchEnabled;

extern int  VmsVersion;

extern char  *CgiEnvironmentPtr,
             *CgiLib__soyMAILhack;

extern char  CurrentVmsTimeString[],
             DocType[],
             SoftwareCopy[],
             SoftwareEnv[],
             SoftwareId[],
             SoftwareVn[];

extern CONFIG_DATA  SoyMailConfig;
extern USER_OPTIONS  UserOptions;
extern REQUEST_DATA  RequestData;
extern VMS_MAIL_USER  VmsMailUser;

/*****************************************************************************/
/*
This is a fundamental function used before (nearly) all message content access.
soyMAIL processes all message content as one or more real and pseudo MIME 
parts linked together from the root at 'vmptr->MimeDataPtr'. This function
processes message header and body so as to create these.  If the message is not
MIME, or even not RFC822, it creates a plain-text pseudo-MIME part.
*/

void MessageBodyProcess (VMS_MAIL_MSG *vmptr)

{
   MIME_DATA  *mdptr,
              *HtmlMimePtr,
              *PlainMimePtr;

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

   if (WatchEnabled) WatchThis (FI_LI, "MessageBodyProcess()");

   /* only need to do it the once! */
   if (vmptr->BodyProcessed) return;
   vmptr->BodyProcessed = TRUE;

   if (!InetMailParseHeader (vmptr))
      if (vmptr->LooksLikeRfc)
      {
         StatusMessage (FI_LI, 1, LangFor("rfc822_header_error"),
                        vmptr->RfcErrorLineNumber);
         return;
      }

   if (vmptr->VmsForeign)
   {
      mdptr = MimeDecManufacVmsForeign (vmptr->BodyPtr, vmptr->BodyLength);
      vmptr->MimeDataPtr = mdptr;
      return;
   }

   if (vmptr->MimeDataPtr)
   {
      /* the message contained a MIME header */
      if (!MimeDecParseBody (vmptr->MimeDataPtr))
      {
         /* the message is broken in some way */
         vmptr->MimeDataPtr = NULL;
         return;
      }
   }

   /* count plain plus HTML parts */
   for (mdptr = vmptr->MimeDataPtr; mdptr; mdptr = mdptr->NextMimePtr)
      if (mdptr->ContentTypeIs == MIME_CONTENT_TEXT_PLAIN)
         mdptr->PlainCount++;
   for (mdptr = vmptr->MimeDataPtr; mdptr; mdptr = mdptr->NextMimePtr)
      if (mdptr->ContentTypeIs == MIME_CONTENT_TEXT_HTML)
         mdptr->HtmlCount++;

   if (WatchEnabled)
      if (mdptr = vmptr->MimeDataPtr)
         WatchThis (FI_LI, "PLAIN:!UL HTML:!UL",
                    mdptr->PlainCount, mdptr->HtmlCount);

   if (!vmptr->MimeDataPtr)
   {
      /* not a MIME message, manufacture a MIME-like part from the body */
      if (vmptr->RfcBodyPtr)
         mdptr = MimeDecManufacTextPlain (vmptr->RfcBodyPtr,
                                          vmptr->RfcBodyLength, 1);
      else
         mdptr = MimeDecManufacTextPlain (vmptr->BodyPtr,
                                          vmptr->BodyLength, 1);
      vmptr->MimeDataPtr = mdptr;
   }

   HtmlMimePtr = PlainMimePtr = NULL;
   mdptr = vmptr->MimeDataPtr;
   if (mdptr->ContentTypeIsMultipart)
   {
      /* look for the first plain-text and first HTML non-named parts */
      for (mdptr = vmptr->MimeDataPtr; mdptr; mdptr = mdptr->NextMimePtr)
         if (mdptr->ContentTypeIs == MIME_CONTENT_TEXT_PLAIN)
            if (!mdptr->NamePtr && !mdptr->FileNamePtr) break;
      PlainMimePtr = mdptr;
      for (mdptr = vmptr->MimeDataPtr; mdptr; mdptr = mdptr->NextMimePtr)
         if (mdptr->ContentTypeIs == MIME_CONTENT_TEXT_HTML)
            if (!mdptr->NamePtr && !mdptr->FileNamePtr) break;
      HtmlMimePtr = mdptr;
   }
   else
   {
      /* must be in-line content */
      if (mdptr->ContentTypeIs == MIME_CONTENT_TEXT_PLAIN)
         PlainMimePtr = mdptr;
      else
      if (mdptr->ContentTypeIs == MIME_CONTENT_TEXT_HTML)
         HtmlMimePtr = mdptr;
   }
   if (WatchEnabled)
      WatchThis (FI_LI, "PLAIN:!AZ HTML:!AZ",
                 PlainMimePtr ? "yes" : "no", HtmlMimePtr ? "yes" : "no");

   if (mdptr && !HtmlMimePtr && PlainMimePtr)
   {
      if (HtmlInDisguise (mdptr->PartContentPtr))
      {
         for (mdptr = vmptr->MimeDataPtr;
              mdptr && mdptr->NextMimePtr;
              mdptr = mdptr->NextMimePtr);
         mdptr->PartCount++;
         mdptr = MimeDecManufacTextHtml (mdptr->PartContentPtr,
                                         mdptr->PartContentLength,
                                         mdptr->PartCount);
         HtmlMimePtr = mdptr;
         for (mdptr = vmptr->MimeDataPtr;
              mdptr && mdptr->NextMimePtr;
              mdptr = mdptr->NextMimePtr);
         mdptr->NextMimePtr = HtmlMimePtr;
      }
   }

   if (vmptr->MimeDataPtr)
   {
      if (vmptr->MimeDataPtr->HtmlToPlainMimePtr)
      {
         /* a plain-text from HTML part has already been manufactured */
         PlainMimePtr = vmptr->MimeDataPtr->HtmlToPlainMimePtr;
      }
      else
      if (HtmlMimePtr && !PlainMimePtr)
      {
         /* there is an HTML but no plain-text part, manufacture one! */
         if (MimeDecProcessPart (HtmlMimePtr))
         {
            for (mdptr = vmptr->MimeDataPtr;
                 mdptr && mdptr->NextMimePtr;
                 mdptr = mdptr->NextMimePtr);
            mdptr->PartCount++;
            mdptr = MimeDecManufacTextPlain (HtmlMimePtr->PartContentPtr,
                                             HtmlMimePtr->PartContentLength,
                                             mdptr->PartCount);
            /* point the primary MIME structure to this manufactured part */
            PlainMimePtr = vmptr->MimeDataPtr->HtmlToPlainMimePtr = mdptr;
            PlainMimePtr->CharSetPtr = HtmlMimePtr->CharSetPtr;
            for (mdptr = vmptr->MimeDataPtr;
                 mdptr && mdptr->NextMimePtr;
                 mdptr = mdptr->NextMimePtr);
            mdptr->NextMimePtr = PlainMimePtr;
            PlainMimePtr->PartContentLength =
               HtmlToPlain (PlainMimePtr->PartContentPtr,
                            HtmlMimePtr->CharSetPtr);
         }
      }
   }
}

/****************************************************************************/
/*
Parse the buttons associated with the folder message list.
*/ 

void MessagePageFolderRequest (REQUEST_DATA *rdptr)

{
   int  cnt, hash, msgid;
   char  *cptr, *sptr, *zptr,
         *MsgRangeListPtr;
   VMS_MAIL_USER  *muptr;
   USER_OPTIONS  *uoptr;
   REQUEST_DATA  *sdptr;

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

   if (WatchEnabled) WatchThis (FI_LI, "MessagePageFolderRequest()");

   /* a bit of sanity checking */
   if (!rdptr->PrivateAccess) ErrorExit (SS$_BUGCHECK, FI_LI);

   muptr = &VmsMailUser;
   sdptr = (REQUEST_DATA*)rdptr->StateDataPtr;
   uoptr = &rdptr->UserOptions;

   rdptr->MassageMessage = uoptr->MassageRead;

   /* the string containing comma-separated message id dot hashes */
   CGIVAR (MsgRangeListPtr, "FORM_MSG_RANGE_LIST");

   /*****************************/
   /* individual message button */
   /*****************************/

   CGIVARNULL (cptr, "FORM_MSG_BTN");
   if (cptr)
   {
      rdptr->MessageId = atol(cptr);
      rdptr->MessageHash = MessageRangeString (rdptr->MessageId,
                                               MsgRangeListPtr);

      if (!strcmp (rdptr->FolderName, uoptr->FolderDraftItems))
         ComposePageRequest (rdptr);
      else   
      {
         rdptr->ResetNewMsgFlag = TRUE;
         MessagePage (rdptr);
      }
      return;
   }

   /************************/
   /* popup message button */
   /************************/

   CGIVARNULL (cptr, "FORM_MSG_BTN_POPUP");
   if (cptr && (cnt = atol(cptr)))
   {
      rdptr->MessageId = cnt;
      rdptr->MessageHash = MessageRangeString (rdptr->MessageId,
                                               MsgRangeListPtr);
      if (!strcmp (rdptr->FolderName, uoptr->FolderDraftItems))
         ComposePageRequest (rdptr);
      else   
      {
         rdptr->ResetNewMsgFlag = TRUE;
         MessagePage (rdptr);
      }
      return;
   }

   /*****************/
   /* message range */
   /*****************/

   /* the range button or range 'onchange=' */
   CGIVARNULL (cptr, "FORM_RANGE_MSGS");
   if (!cptr) CGIVARNULL (cptr, "FORM_RANGE_LIST_CHANGE");
   if (cptr && *cptr) CGIVARNULL (cptr, "FORM_RANGE_LIST");
   /* also check that the range list is not empty */
   if (cptr && *cptr)
   {
      rdptr->RangeIdStart = atoi(cptr);
      while (*cptr && *cptr != '-') cptr++;
      if (*cptr)
      {
         cptr++;
         rdptr->RangeIdCount = atoi(cptr);
         rdptr->RangeIdCount -= rdptr->RangeIdStart - 1;
      }
      else
         rdptr->RangeIdCount = uoptr->MessagesPerPage;
      rdptr->RangeIdVector = +1;
      MessageFolderPage (rdptr);
      return;
   }

   /******************************/
   /* previous range of messages */
   /******************************/

   CGIVARNULL (cptr, "FORM_RANGE_PREV");
   if (cptr)
   {
      /* get the lowest id in the list and start one less than that */
      rdptr->RangeIdStart = MessageRangeString (-1, MsgRangeListPtr);
      if (rdptr->RangeIdStart <= 1)
      {
         rdptr->RangeIdStart = sdptr->RangeIdStart;
         StatusMessage (FI_LI, 1, LangFor("no_more_messages"));
      }
      else
         rdptr->RangeIdStart--;
      rdptr->RangeIdCount = uoptr->MessagesPerPage;
      rdptr->RangeIdVector = -1;
      MessageFolderPage (rdptr);
      return;
   }

   /**************************/
   /* next range of messages */
   /**************************/

   CGIVARNULL (cptr, "FORM_RANGE_NEXT");
   if (cptr)
   {
      /* the highest id is always the first one in the string */
      rdptr->RangeIdStart = atoi(MsgRangeListPtr) + 1;
      if (rdptr->RangeIdStart >= sdptr->FolderMessageCount)
      {
         /* just go back to the start of whatever we had before */
         rdptr->RangeIdStart = sdptr->RangeIdStart;
         StatusMessage (FI_LI, 1, LangFor("no_more_messages"));
      }
      rdptr->RangeIdCount = uoptr->MessagesPerPage;
      rdptr->RangeIdVector = +1;
      MessageFolderPage (rdptr);
      return;
   }

   /*****************************/
   /* copy, move, waste, delete */
   /*****************************/

   CGIVARNULL (cptr, "FORM_MSG_COPY");
   if (!cptr) CGIVARNULL (cptr, "FORM_MSG_MOVE");
   if (!cptr) CGIVARNULL (cptr, "FORM_MSG_WASTE");
   if (!cptr) CGIVARNULL (cptr, "FORM_MSG_DELETE");
   if (!cptr) CGIVARNULL (cptr, "FORM_MSG_EMPTY");
   if (cptr)
   {
      MessageIdList (rdptr);

      CGIVARNULL (cptr, "FORM_MSG_COPY");
      if (cptr)
         MessageCopy (rdptr, FALSE);
      else
      {
         CGIVARNULL (cptr, "FORM_MSG_MOVE");
         if (cptr)
            MessageCopy (rdptr, TRUE);
         else
         {
            CGIVARNULL (cptr, "FORM_MSG_WASTE");
            if (cptr)
            {
               strcpy (rdptr->DestMailFileName, "MAIL");
               rdptr->DestMailFileNameLength = strlen(rdptr->DestMailFileName);
               strcpy (rdptr->DestFolderName, uoptr->FolderWasteItems);
               rdptr->DestFolderNameLength = strlen(rdptr->DestFolderName);
               RequestVmsMailContext (rdptr);
               MessageCopy (rdptr, TRUE);
            }
            else
            {
               CGIVARNULL (cptr, "FORM_MSG_EMPTY");
               if (cptr)
               {
                  /* all messages in soyWASTE folder! */
                  if (!strcmp (rdptr->FolderName, uoptr->FolderWasteItems))
                  {
                     /* CAREFUL! only the waste items folder!! */
                     rdptr->RangeIdStart = rdptr->RangeIdCount = 0;
                     rdptr->MsgIdListPtr = "*";
                     rdptr->MsgIdListCount = 1;
                     MessageDelete (rdptr);
                  }
               }
               else
               {
                  CGIVARNULL (cptr, "FORM_MSG_DELETE");
                  if (cptr)
                     MessageDelete (rdptr);
                  else
                     ErrorExit (SS$_BUGCHECK, FI_LI);
               }
            }
         }
      }

      rdptr->RangeIdStart = sdptr->RangeIdStart - rdptr->MessageCountDelta;
      if (rdptr->RangeIdStart <= 0) rdptr->RangeIdStart = 1;
      rdptr->RangeIdCount = uoptr->MessagesPerPage;
      rdptr->RangeIdVector = -1;

      MessageFolderPage (rdptr);

      /* make sure (any) deleted messages are purged from the WASTEBASKET */
      CallMailFileClose (muptr);

      return;
   }

   /*****************/
   /* folder button */
   /*****************/

   /* this button is also checked for in RequestPrivateDefaults() */
   CGIVARNULL (cptr, "FORM_FOLDER_BTN");
   if (cptr)
   {
      /* the list was peremtorially generated by RequestPrivateDefaults() */
      if (rdptr->MsgIdListCount)
      {
         /* message checkboxes result in message moves to buttoned folder */
         RequestParseDestFileFolder (rdptr, cptr);
         RequestVmsMailContext (rdptr);
         MessageCopy (rdptr, TRUE);
      }

      MessageFolderPage (rdptr);

      return;
   }

   /*********************/
   /* none of the above */
   /*********************/

   rdptr->RangeIdStart = sdptr->RangeIdStart;
   rdptr->RangeIdCount = uoptr->MessagesPerPage;

   MessageFolderPage (rdptr);
}

/****************************************************************************/
/*
When 'MessageId' is positive find that message number in the string and return
it's associated hash.  Return zero if the message is not found (error).  When
'MessageId' is zero step through each of the message numbers in the string,
returning zero at end of string.  When 'MessageId' is negative find the first
message id (the last one in the string) and return it's message number.  Return
zero if the string is empty (error).
*/ 

int MessageRangeString
(
int MessageId,
char *RangeStringPtr
)
{
   static char  *LastPtr = NULL;

   int  id, hash;
   char  *cptr;

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

   if (Debug)
      fprintf (stdout, "MessageRangeString() %d |%s|\n",
               MessageId, RangeStringPtr);

   if (MessageId > 0)
   {
      /* return the hash of the specified message id from the string */
      cptr = RangeStringPtr;
      while (*cptr)
      {
         id = atol(cptr);
         if (!id) return (0);
         if (id == MessageId)
         {
            while (*cptr && *cptr != '.') cptr++;
            if (*cptr != '.') return (0);
            cptr++;
            hash = atol(cptr);
            return (hash);
         }
         while (*cptr && *cptr != ',') cptr++;
         if (*cptr) cptr++;
      }
      return (0);
   }

   if (MessageId == 0)
   {
      /* return each of the message IDs in the string in turn */
      if (!LastPtr) LastPtr = RangeStringPtr;
      cptr = LastPtr;
      id = atol(cptr);
      if (!id)
      {
         LastPtr = NULL;
         return (0);
      }
      while (*cptr && *cptr != ',') cptr++;
      if (*cptr) cptr++;
      LastPtr = cptr;
      return (id);
   }

   if (MessageId < 0)
   {
      /* return the number of the very last message id in the string */
      cptr = RangeStringPtr;
      id = 0;
      while (*cptr)
      {
         id = atol(cptr);
         if (!id) return (0);
         while (*cptr && *cptr != ',') cptr++;
         if (*cptr) cptr++;
      }
      return (id);
   }
}

/****************************************************************************/
/*
Parse the buttons from the message display page.
*/ 

void MessagePageMsgRequest (REQUEST_DATA *rdptr)

{
   static char  SingleMessageList [32];

   char  *cptr, *sptr, *zptr;
   USER_OPTIONS  *uoptr;
   REQUEST_DATA  *sdptr;
   VMS_MAIL_USER  *muptr;

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

   if (WatchEnabled) WatchThis (FI_LI, "MessagePageMsgRequest()");

   /* a bit of sanity checking */
   if (!rdptr->PrivateAccess) ErrorExit (SS$_BUGCHECK, FI_LI);

   muptr = &VmsMailUser;
   uoptr = &rdptr->UserOptions;
   sdptr = (REQUEST_DATA*)rdptr->StateDataPtr;

   rdptr->MassageMessage = uoptr->MassageRead;

   /****************************/
   /* previous/at/next message */
   /****************************/

   CGIVARNULL (cptr, "FORM_MSG_PREV");
   if (cptr)
   {
      rdptr->MessageId = sdptr->MessageId;
      if (rdptr->MessageId > 1)
         rdptr->MessageId--;
      else
         StatusMessage (FI_LI, 1, LangFor("no_more_messages"));
      rdptr->MessageHash = 0;
      rdptr->ResetNewMsgFlag = TRUE;
      MessagePage (rdptr);
      return;
   }

   CGIVARNULL (cptr, "FORM_MSG_FOLDER_AT");
   if (cptr)
   {
      rdptr->RangeIdCount = uoptr->MessagesPerPage;
      for (rdptr->RangeIdStart = sdptr->FolderMessageCount;
           rdptr->RangeIdStart >= sdptr->MessageId;
           rdptr->RangeIdStart -= rdptr->RangeIdCount);
      rdptr->RangeIdStart++;
      if (rdptr->RangeIdStart < 1) rdptr->RangeIdStart = 1;
      MessageFolderPage (rdptr);
      return;
   }

   CGIVARNULL (cptr, "FORM_MSG_NEXT");
   if (cptr)
   {
      rdptr->MessageId = sdptr->MessageId;
      if (rdptr->MessageId < sdptr->FolderMessageCount)
         rdptr->MessageId++;
      else
         StatusMessage (FI_LI, 1, LangFor("no_more_messages"));
      rdptr->MessageHash = 0;
      rdptr->ResetNewMsgFlag = TRUE;
      MessagePage (rdptr);
      return;
   }

   /********************/
   /* specific message */
   /********************/

   CGIVAR (cptr, "FORM_MSG_ID_HASH");
   rdptr->MessageId = atol(cptr);
   while (*cptr && *cptr != '.') cptr++;
   if (*cptr) cptr++;
   if (!*cptr) ErrorExit (SS$_BUGCHECK, FI_LI);;
   rdptr->MessageHash = atol(cptr);

   /**********************************/
   /* copy/move/waste/delete message */
   /**********************************/

   CGIVARNULL (cptr, "FORM_MSG_COPY");
   if (!cptr) CGIVARNULL (cptr, "FORM_MSG_MOVE");
   if (!cptr) CGIVARNULL (cptr, "FORM_MSG_WASTE");
   if (!cptr) CGIVARNULL (cptr, "FORM_MSG_DELETE");
   if (cptr)
   {
      sprintf (SingleMessageList, "%d.%d",
               rdptr->MessageId, rdptr->MessageHash);
      rdptr->MsgIdListPtr = SingleMessageList;
      rdptr->MsgIdListCount = 1;

      CGIVARNULL (cptr, "FORM_MSG_COPY");
      if (cptr)
         MessageCopy (rdptr, FALSE);
      else
      {
         CGIVARNULL (cptr, "FORM_MSG_MOVE");
         if (cptr)
            MessageCopy (rdptr, TRUE);
         else
         {
            CGIVARNULL (cptr, "FORM_MSG_WASTE");
            if (cptr)
            {
               strcpy (rdptr->DestMailFileName, "MAIL");
               rdptr->DestMailFileNameLength = strlen(rdptr->DestMailFileName);
               strcpy (rdptr->DestFolderName, uoptr->FolderWasteItems);
               rdptr->DestFolderNameLength = strlen(rdptr->DestFolderName);
               RequestVmsMailContext (rdptr);
               MessageCopy (rdptr, TRUE);
            }
            else
            {
               CGIVARNULL (cptr, "FORM_MSG_DELETE");
               if (cptr)
                  MessageDelete (rdptr);
               else
                  ErrorExit (SS$_BUGCHECK, FI_LI);
            }
         }
         if (sdptr->MessageId == sdptr->FolderMessageCount)
            rdptr->MessageId = sdptr->MessageId - 1; 
      }

      if (VMSnok (muptr->MailVmsStatus))
      {
         /* same behaviour and formula as folder-at */
         rdptr->RangeIdCount = uoptr->MessagesPerPage;
         for (rdptr->RangeIdStart = sdptr->FolderMessageCount;
              rdptr->RangeIdStart >= sdptr->MessageId;
              rdptr->RangeIdStart -= rdptr->RangeIdCount);
         rdptr->RangeIdStart++;
         if (rdptr->RangeIdStart < 1) rdptr->RangeIdStart = 1;
         MessageFolderPage (rdptr);
      }
      else
      {
         /* adjust this to reflect the successful move/copy/delete */
         if (sdptr->FolderMessageCount) sdptr->FolderMessageCount--;

         if (rdptr->MessageId)
         {
            /* still have message(s) in the folder */
            rdptr->MessageHash = 0;
            MessagePage (rdptr);
         }
         else
         {
            /* folder empty */
            rdptr->RangeIdStart = RANGE_MOST_RECENT;
            rdptr->RangeIdCount = uoptr->MessagesPerPage;
            rdptr->FolderMessageCount = 0;
            MessageFolderPage (rdptr);
         }
      }

      /* make sure (any) deleted messages are purged from the WASTEBASKET */
      CallMailFileClose (muptr);

      return;
   }

   /*****************/
   /* message parts */
   /*****************/

   CGIVARNULL (cptr, "FORM_PART_BTN");
   if (cptr)
   {
      zptr = (sptr = rdptr->AttachmentName) + sizeof(rdptr->AttachmentName)-1;
      while (*cptr && sptr < zptr) *sptr++ = *cptr++;
      *sptr = '\0';
      CGIVARNULL (cptr, "FORM_MESSAGE_PART");
      if (cptr && isdigit(*cptr))
         sprintf (rdptr->AttachPartName, "%s_%d",
                  LangFor("mime_part"), atoi(cptr));
      /* if the attachment is successfully accessed then just finish up */
      if (MessagePart (rdptr)) return;
      MessagePage (rdptr);
      return;
   }

   CGIVARNULL (cptr, "FORM_PART_SAVE");
   if (cptr)
   {
      AttachSave (rdptr, FALSE);
      MessagePage (rdptr);
      return;
   }

   if (!SoyMailConfig.VmsOccluded)
   {
      CGIVARNULL (cptr, "FORM_PART_EXTRACT");
      if (cptr)
      {
         AttachSave (rdptr, TRUE);
         MessagePage (rdptr);
         return;
      }
   }

   /*************************/
   /* message reply buttons */
   /*************************/

   CGIVARNULL (cptr, "FORM_MSG_REPLY");
   if (!cptr) CGIVARNULL (cptr, "FORM_MSG_REPLY_POPUP");
   if (!cptr) CGIVARNULL (cptr, "FORM_MSG_REPLY_ALL");
   if (!cptr) CGIVARNULL (cptr, "FORM_MSG_REPLY_ALL_POPUP");
   if (!cptr) CGIVARNULL (cptr, "FORM_MSG_FORWARD");
   if (!cptr) CGIVARNULL (cptr, "FORM_MSG_FORWARD_POPUP");
   if (!cptr) CGIVARNULL (cptr, "FORM_MSG_EDIT_NEW");
   if (!cptr) CGIVARNULL (cptr, "FORM_MSG_EDIT_NEW_POPUP");
   if (cptr)
   {
      ComposePageRequest (rdptr);
      return;
   }

   /************************/
   /* message read buttons */
   /************************/

   CGIVARNULL (cptr, "FORM_MSG_PRINT");
   if (!cptr) CGIVARNULL (cptr, "FORM_MSG_PRINT_POPUP");
   if (cptr)
   {
      if (MessagePrintView (rdptr)) return;
      MessagePage (rdptr);
      return;
   }

   CGIVARNULL (cptr, "FORM_MSG_RAW");
   if (!cptr) CGIVARNULL (cptr, "FORM_MSG_RAW_POPUP");
   if (cptr)
   {
      if (MessageRawView (rdptr)) return;
      MessagePage (rdptr);
      return;
   }

   CGIVARNULL (cptr, "FORM_MSG_HDR");
   if (!cptr) CGIVARNULL (cptr, "FORM_MSG_HDR_POPUP");
   if (cptr)
   {
      if (MessageHdrView (rdptr)) return;
      MessagePage (rdptr);
      return;
   }

   /**************/
   /* HTML/plain */
   /**************/

   CGIVARNULL (cptr, "FORM_MSG_READ_PREF");
   if (cptr)
   {
      /* one is on, minus one is off, zero is not set! */
      if (LangSame ("message_read_html", cptr))
         rdptr->MessageReadPreference = OPTIONS_READ_PLAIN2_HTML1;
      if (LangSame ("message_read_plain", cptr))
         rdptr->MessageReadPreference = OPTIONS_READ_PLAIN1_HTML2;
      MessagePage (rdptr);
      return;
   }

   /****************/
   /* message wrap */
   /****************/

   CGIVARNULL (cptr, "FORM_MSG_WRAP");
   if (cptr)
   {
      while (*cptr && !isdigit(*cptr)) cptr++;
      rdptr->MassageMessage = atoi(cptr);
      /* use the read preference from the previous request */
      rdptr->MessageReadPreference = sdptr->MessageReadPreference;
      MessagePage (rdptr);
      return;
   }

   /******************/
   /* add as contact */
   /******************/

   CGIVARNULL (cptr, "FORM_MSG_ADD_CONTACT");
   if (cptr)
   {
      if (*cptr == '^')
      {
         /****************/
         /* edit contact */
         /****************/
   
         ContactsLoad (rdptr, NULL, NULL);
         ContactsPage (rdptr, FALSE);
         return;
      }

      ContactsAppend (rdptr);
      MessagePage (rdptr);
      return;
   }

   /****************/
   /* flag message */
   /****************/

   if (SoyMailConfig.MessageFlags & 0xff)
   {
      CGIVARNULL (cptr, "FORM_MSG_FLAG");
      if (cptr)
      {
         /* trim the character used to represent the flag */
         for (sptr = cptr; *sptr && *sptr != ' '; sptr++);
         *sptr = '\0';

         if (strsame ("mark", cptr, -1))
            MessageFlags (rdptr, MAIL$M_MARKED);
         else
         if (strsame ("unmark", cptr, -1))
            MessageFlags (rdptr, ~MAIL$M_MARKED);
         else
         if (strsame ("unread", cptr, -1))
            MessageFlags (rdptr, MAIL$M_NEWMSG);
         else
         if (strsame ("read", cptr, -1))
            MessageFlags (rdptr, ~MAIL$M_NEWMSG);
         else
         if (strsame ("replied-to", cptr, -1))
            MessageFlags (rdptr, MAIL$M_REPLIED);
         else
         if (strsame ("unreplied-to", cptr, -1))
            MessageFlags (rdptr, ~MAIL$M_REPLIED);

         MessagePage (rdptr);
         return;
      }
   }

   /* whatever we were doing before (disabled search can do this) */
   MessagePage (rdptr);
}

/****************************************************************************/
/*
Generate a string containing a comma-separated list of message ID numbers (in
textual form of course) representing the checkboxes against each message on the
message list page.  Return the number of checkboxes checked.
*/ 

int MessageIdList (REQUEST_DATA *rdptr)

{
   int  cnt, hash, id;
   char  *cptr, *sptr,
         *MsgRangeListPtr;
   char  MsgCbxString [32];
   REQUEST_DATA  *sdptr;

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

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

   sdptr = (REQUEST_DATA*)rdptr->StateDataPtr;

   rdptr->MsgIdListPtr = NULL;
   rdptr->MsgIdListCount = 0;
   CGIVARNULL (cptr, "FORM_MSG_CBX_ALL");
   if (cptr && *cptr)
   {
      /* all messages checkbox! */
      rdptr->RangeIdStart = rdptr->RangeIdCount = 0;
      rdptr->MsgIdListPtr = "*";
      rdptr->MsgIdListCount = 1;
   }
   else
   {
      /* the string containing comma-separated message id dot hashes */
      CGIVAR (MsgRangeListPtr, "FORM_MSG_RANGE_LIST");

      /* count the number of messages in the range */
      for (cnt = 0; MessageRangeString (0, MsgRangeListPtr); cnt++);

      if (cnt)
      {
         /* message checkboxes */
         rdptr->MsgIdListPtr = sptr = CgiLibVeeMemCalloc (cnt * 32);
         if (!rdptr->MsgIdListPtr) ErrorExit (vaxc$errno, FI_LI);
         while (id = MessageRangeString (0, MsgRangeListPtr))
         {
            sprintf (MsgCbxString, "FORM_MSG_CBX_%d", id);
            CGIVARNULL (cptr, MsgCbxString);
            if (cptr && *cptr)
            {
               hash = MessageRangeString (id, MsgRangeListPtr);
               sptr += sprintf (sptr, "%s%d.%d",
                                rdptr->MsgIdListCount ? "," : "", id, hash);
               rdptr->MsgIdListCount++;
            }
         }
         *sptr = '\0';
         if (WatchEnabled)
            WatchThis (FI_LI, "CBX !UL!AZ!AZ",
                       rdptr->MsgIdListCount,
                       rdptr->MsgIdListCount ? " " : "", rdptr->MsgIdListPtr);
      }
   }

   if (Debug)
      fprintf (stdout, "%d %d %d %d|%s|\n",
               rdptr->MessageId, rdptr->RangeIdStart, rdptr->RangeIdCount,
               rdptr->MsgIdListCount, rdptr->MsgIdListPtr);

   return (rdptr->MsgIdListCount);
}

/*****************************************************************************/
/*
Display the basic private folder and message list page.
*/

void MessageFolderPage (REQUEST_DATA *rdptr)

{
   USER_OPTIONS  *uoptr;
   REQUEST_DATA  *sdptr;
   VMS_MAIL_MSG  *vmptr;
   VMS_MAIL_USER  *muptr;

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

   if (WatchEnabled)
      WatchThis (FI_LI, "MessageFolderPage() !AZ !AZ !SL !SL !SL",
                 rdptr->MailFileName, rdptr->FolderName,
                 rdptr->RangeIdStart, rdptr->RangeIdCount,
                 rdptr->RangeIdVector);

   muptr = &VmsMailUser;
   uoptr = &rdptr->UserOptions;
   vmptr = &muptr->VmsMailMsg;
   sdptr = (REQUEST_DATA*)rdptr->StateDataPtr;

   if (rdptr->SearchData.InProgress) SearchCriteria (rdptr);

   /* determine if we're changing folders */
   if (strcmp (rdptr->MailFileName, sdptr->MailFileName) ||
       strcmp (rdptr->FolderName, sdptr->FolderName))
      rdptr->RangeIdCount = 0;
   else
      rdptr->FolderMessageCount = sdptr->FolderMessageCount;

   if (!rdptr->RangeIdCount)
   {
      rdptr->RangeIdStart = RANGE_MOST_RECENT;
      rdptr->RangeIdCount = uoptr->MessagesPerPage;
      rdptr->RangeIdVector = -1;
   }

   if (uoptr->CharSetDefault[0])
      CgiLibResponseSetCharset (uoptr->CharSetDefault);
   else
   if (SoyMailConfig.CharSetDefault && SoyMailConfig.CharSetDefault[0])
      CgiLibResponseSetCharset (SoyMailConfig.CharSetDefault);
   else
      CgiLibResponseSetCharset (CHARSET_DEFAULT);

   MessageListInit (rdptr);

   MainMenuPageBegin (rdptr, PAGE_FOLDER);
   MainMenuBar (rdptr);
   StatusInfoPanel (rdptr);
   MessageFolderList (rdptr);

   MainMenuPageEnd (rdptr);
}

/*****************************************************************************/
/*
Display the message header and body page.
*/

void MessagePage (REQUEST_DATA *rdptr)

{
   int  idx, status,
        LineCount;
   MIME_DATA  *mdptr;
   USER_OPTIONS  *uoptr;
   REQUEST_DATA  *sdptr;
   VMS_MAIL_MSG  *vmptr;
   VMS_MAIL_USER  *muptr;
   VMS_MAIL_USER  UserData;

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

   if (WatchEnabled) WatchThis (FI_LI, "MessagePage()");

   muptr = &VmsMailUser;
   uoptr = &rdptr->UserOptions;
   vmptr = &muptr->VmsMailMsg;
   sdptr = (REQUEST_DATA*)rdptr->StateDataPtr;

   if (rdptr->SearchData.InProgress) SearchCriteria (rdptr);

   rdptr->FolderMessageCount = sdptr->FolderMessageCount;

   vmptr->MessageId = rdptr->MessageId;
   vmptr->ConfirmHash = rdptr->MessageHash;
   vmptr->ResetNewMsgFlag = rdptr->ResetNewMsgFlag;

   CallMailMessageGet (muptr, NULL);

   /* in case a message hash was not supplied use the one just generated */
   rdptr->MessageHash = vmptr->MessageHash;

   if (VMSnok (muptr->MailVmsStatus))
   {
      if (muptr->MailVmsStatus == MAIL$_OPENIN)
      {
         /* error opening external message file */
         StatusMessage (FI_LI, 1, "MAIL: %s %s",
                        SysGetMsg(MAIL$_OPENIN), vmptr->ExtId);
      }
      else
      {
         if (muptr->MailVmsStatus == SS$_BADCHECKSUM ||
             muptr->MailVmsStatus == MAIL$_NOMOREMSG)
            StatusMessage (FI_LI, 1, "%s", LangFor("message_checksum"));
         else
            StatusMessage (FI_LI, 1, "MAIL: %s.",
                           SysGetMsg(muptr->MailVmsStatus));
      }
      MessageFolderPage (rdptr);
      return;
   }

   /********************/
   /* display the page */
   /********************/

   MessageContentIdentify (rdptr);

   MainMenuPageBegin (rdptr, PAGE_MESSAGE);

   MainMenuBar (rdptr);

   StatusInfoPanel (rdptr);

   MessageHeaderButtons (rdptr);

   MessageHeader (rdptr);

   MessageBodyButtons (rdptr);

   if (uoptr->MessageAttachmentPanel == 1) MessageAttachments (rdptr);

   LineCount = MessageBodyText (rdptr);

   if (LineCount > MESSAGE_BUTTONS_AFTER) MessageBodyButtons (rdptr);

   if (uoptr->MessageAttachmentPanel == 0) MessageAttachments (rdptr);

   MainMenuPageEnd (rdptr);
}

/*****************************************************************************/
/*
Check the message for textual content based on user preferences.  Once decided
generate a description of that and the overall message  for the status
information panel.
*/

void MessageContentIdentify (REQUEST_DATA *rdptr)

{
   int  cnt, status,
        ReadPreference;
   char  *sptr;
   char  Description [256],
         Scratch [128];
   MIME_DATA  *mdptr;
   USER_OPTIONS  *uoptr;
   VMS_MAIL_MSG  *vmptr;
   VMS_MAIL_USER  *muptr;

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

   if (WatchEnabled) WatchThis (FI_LI, "MessageContentIdentify()");

   muptr = &VmsMailUser;
   vmptr = &muptr->VmsMailMsg;
   uoptr = &rdptr->UserOptions;

   MessageBodyProcess (vmptr);

   /**********************/
   /* determine the text */
   /**********************/

   /* 'probe' the content to see if there is an alternative */
   mdptr = MessageBodyIdentify (vmptr, OPTIONS_READ_PLAIN0_HTML1);
   if (mdptr) rdptr->HtmlTextIsPresent = TRUE;
   mdptr = MessageBodyIdentify (vmptr, OPTIONS_READ_PLAIN1_HTML0);
   if (mdptr) rdptr->PlainTextIsPresent = TRUE;

   if (rdptr->MessageReadPreference)
      ReadPreference = rdptr->MessageReadPreference;
   else
      ReadPreference = uoptr->ReadPreference;

   mdptr = MessageBodyIdentify (vmptr, ReadPreference);
   if (mdptr)
   {
      MimeDecProcessPart (mdptr);
      rdptr->CharSetPtr = mdptr->CharSetPtr;
      if (mdptr->ContentTypeIs == MIME_CONTENT_TEXT_PLAIN)
         rdptr->ReadPlainText = TRUE;
      else
      if (mdptr->ContentTypeIs == MIME_CONTENT_TEXT_HTML)
         rdptr->ReadHtmlText = TRUE;
      else
      {
         StatusMessage (FI_LI, 1, "%s", LangFor("mime_type_unknown"));
         return;
      }
   }
   else
   if (!vmptr->VmsForeign)
   {
      StatusMessage (FI_LI, 1, "%s", LangFor("mime_type_unknown"));
      return;
   }

   /************************/
   /* describe the content */
   /************************/

   if (rdptr->PrivateAccess || rdptr->PublicWildcard)
      sprintf (Scratch, "%s&nbsp;/&nbsp;%04d",
               HTML_ESCAPE(rdptr->FolderName), rdptr->MessageId);
   else
      sprintf (Scratch, "%s&nbsp;/&nbsp;%04d",
               HTML_ESCAPE(rdptr->PublicAccessString), rdptr->MessageId);

   sptr += sprintf (sptr = Description, LangFor("message_opened"), Scratch);

   if (vmptr->MimeDataPtr->VersionNumber)
      sptr += sprintf (sptr, "&nbsp; MIME.");
   else
   if (vmptr->LooksLikeRfc)
      sptr += sprintf (sptr, "&nbsp; RFC.");
   else
   if (vmptr->VmsForeign)
      sptr += sprintf (sptr, "&nbsp; VMS/FOREIGN.");
   else
      sptr += sprintf (sptr, "&nbsp; VMS.");

   if (vmptr->RfcBccPtr)
      sptr += sprintf (sptr, "&nbsp; BCC.");

   if (mdptr)
      if (MessagePgpSigned (mdptr->PartContentPtr))
         sptr += sprintf (sptr, "&nbsp; PGP.");

   for (mdptr = vmptr->MimeDataPtr; mdptr; mdptr = mdptr->NextMimePtr)
      if (mdptr->ContentTypeIs == MIME_CONTENT_TEXT_HTML)
         if (mdptr->ManufacturedMime) break;
   if (mdptr) sptr += sprintf (sptr, "&nbsp; HTML?");

   if (rdptr->CharSetPtr &&
       !strsame (rdptr->CharSetPtr, uoptr->CharSet, -1))
      sptr += sprintf (sptr, "&nbsp; %s.", rdptr->CharSetPtr);

   cnt = 0;
   for (mdptr = vmptr->MimeDataPtr; mdptr; mdptr = mdptr->NextMimePtr)
      if (mdptr->NamePtr) cnt++;
   if (cnt)
      sptr += sprintf (sptr, "&nbsp; %d %s.",
                       cnt, LangFor("message_attachments"));

   cnt = 0;
   for (mdptr = vmptr->MimeDataPtr; mdptr; mdptr = mdptr->NextMimePtr)
      if (!mdptr->ContentTypeIsMultipart && mdptr->PartName[0]) cnt++;
   if (cnt > 1)
      sptr += sprintf (sptr, "&nbsp; %d %s.",
                       cnt, LangFor("message_parts"));

   if (mdptr = vmptr->MimeDataPtr)
   {
      if (rdptr->ReadPlainText &&
          mdptr->PlainCount &&
          !mdptr->HtmlCount &&
          HtmlInDisguise (mdptr->PartContentPtr))
         sptr += sprintf (sptr, "&nbsp; (Plain(HTML))&#x279e;Plain&thinsp;");
      else
      if (rdptr->ReadPlainText &&
          !mdptr->PlainCount &&
          mdptr->HtmlCount)
         sptr += sprintf (sptr, "&nbsp; HTML&#x279e;Plain&thinsp;");
      else
      if (rdptr->ReadPlainText &&
          mdptr->PlainCount &&
          !mdptr->HtmlCount)
         sptr += sprintf (sptr, "&nbsp; Plain&thinsp;(&#x2212;HTML)&thinsp;");
      else
      if (rdptr->ReadHtmlText &&
          !mdptr->PlainCount &&
          mdptr->HtmlCount)
         sptr += sprintf (sptr, "&nbsp; HTML&thinsp;(-Plain)&thinsp;");
      else
      if (rdptr->ReadHtmlText &&
          mdptr->PlainCount &&
          mdptr->HtmlCount)
         sptr += sprintf (sptr, "&nbsp; HTML&thinsp;(+Plain)&thinsp;");
      else
      if (rdptr->ReadPlainText && rdptr->HtmlTextIsPresent)
         sptr += sprintf (sptr, "&nbsp; Plain&thinsp;(+HTML)&thinsp;");
      else
      if (rdptr->ReadHtmlText && rdptr->PlainTextIsPresent)
         sptr += sprintf (sptr, "&nbsp; HTML&thinsp;(+Plain)&thinsp;");
   }

   if (vmptr->BodyLength < 2048)
      sptr += sprintf (sptr, "&nbsp; %d bytes.", vmptr->BodyLength);
   else
      sptr += sprintf (sptr, "&nbsp; %d kbytes.", vmptr->BodyLength >> 10);

   if (vmptr->ExtIdLength)
      sptr += sprintf (sptr, "\n<!-- %s -->", vmptr->ExtId);

   StatusMessage (FI_LI, 0, "%s", Description);
}

/*****************************************************************************/
/*
Display the message header ("Subject:", "From:", "Date:", etc).
*/

void MessageHeader (REQUEST_DATA *rdptr)

{
   USER_OPTIONS  *uoptr;
   VMS_MAIL_MSG  *vmptr;
   VMS_MAIL_USER  *muptr;

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

   if (WatchEnabled) WatchThis (FI_LI, "MessageHeader()");

   muptr = &VmsMailUser;
   vmptr = &muptr->VmsMailMsg;
   uoptr = &rdptr->UserOptions;

   fprintf (stdout,
"<!-- header begin -->\n\
<p class=\"css_p06\">\n\
<table width=\"100%%\" cellpadding=\"0\" cellspacing=\"10\" \
class=\"css_panel_3\">\n\
<tr><td align=\"left\">\n\
<input type=\"hidden\" name=\"msg_id_hash\" value=\"%d.%d\">\n\
<table cellpadding=\"0\" cellspacing=\"2\" border=\"0\" width=\"100%%\">\n\
<tr><td class=\"css_rfchdr\">%s:</td>\
<td class=\"css_rfcval\" width=\"100%%\">%s</td>\
<td class=\"css_msgnum\">%04d&nbsp;/&nbsp;%04d</td></tr>\n\
<tr><td class=\"css_rfchdr\">%s:</td>\
<td class=\"css_rfcval\" width=\"100%%\">%s</td>",
            vmptr->MessageId, vmptr->MessageHash,
            LangFor("subject"),
            vmptr->MsgSubjPtr[0] ? HTML_TEXT(vmptr->MsgSubjPtr) :
                                   LangFor("(none)"),
            vmptr->MessageId, rdptr->FolderMessageCount,
            LangFor("from"),
            vmptr->MsgFromPtr[0] ? HTML_ADDR(vmptr->MsgFromPtr) :
                                   LangFor("(none)"));

   if (rdptr->PrivateAccess)
   {
      fprintf (stdout,
"<td class=\"css_addas\" rowspan=\"2\">\
<input class=\"css_button_panel_1\" type=\"submit\" \
name=\"msg_add_contact\" value=\"%s:\">\
<script language=\"JavaScript\">\
document.write(\'<input class=\"css_button_panel_1\" \
style=\"border-left-width:0px;\" \
type=\"submit\" name=\"msg_add_contact\" value=\"^\" \
onclick=\"targetBlank()\">\');\
</script>\n\
<input class=\"css_text_1\" type=\"text\" size=\"15\" \
name=\"msg_add_contact_as\" value=\"%s\">\
<input class=\"css_text_1\" type=\"hidden\" \
name=\"msg_add_contact_from\" value=\"%s\">\
</td></tr>\n",
               LangFor("message_add_contact"),
               HTML_ESCAPE(ContactsNameAs(vmptr->MsgFromPtr)),
               HTML_ESCAPE(InetMailStripTransport(vmptr->MsgFromPtr)));
   }

   if (vmptr->RfcReplyToPtr &&
       !strsame (vmptr->RfcFromPtr, vmptr->RfcReplyToPtr, -1))
      fprintf (stdout,
"<tr><td class=\"css_rfchdr\">%s:</td>\
<td class=\"css_rfcval\" colspan=\"2\" width=\"100%%\">%s</td></tr>\n",
            LangFor("compose_reply_to"),
            HTML_ADDR(vmptr->RfcReplyToPtr));

   fprintf (stdout,
"<tr><td class=\"css_rfchdr\">%s:</td>\
<td class=\"css_rfcval\" width=\"100%%\">%s%s%s%s</td></tr>\n\
<tr><td class=\"css_rfchdr\">%s:</td>\
<td class=\"css_rfcval\">%s</td>",
            LangFor("date"), HTML_TEXT(vmptr->MsgDatePtr),
            vmptr->LooksLikeRfc ? "&nbsp; [" : "",
            vmptr->LooksLikeRfc ? InetMailRfcVmsDate(vmptr->Date) : "",
            vmptr->LooksLikeRfc ? "]" : "",
            LangFor("to"),
            vmptr->MsgToPtr[0] ? HTML_ADDR(vmptr->MsgToPtr) :
                                 LangFor("(none)"));

   if (rdptr->PrivateAccess)
      if (vmptr->XVmsToPtr)
         fprintf (stdout,
"<tr><td class=\"css_rfchdr\">X-VMS-To:</td>\
<td class=\"css_rfcval\" colspan=\"2\" width=\"100%%\">%s</td></tr>\n",
                  vmptr->XVmsToPtr);

   if (SoyMailConfig.MessageFlags & 0xff)
   {
      if (rdptr->PrivateAccess)
      {
         fprintf (stdout,
"<td class=\"css_addas\"><!-- %%X%04.04X -->",
                  vmptr->MessageFlags);

         if (SoyMailConfig.MessageFlags & MAIL$M_MARKED)
            fprintf (stdout,
"<input class=\"css_button_panel_1\" style=\"margin-left:0.3em;\" \
type=\"submit\" name=\"msg_flag\" value=\"%s &nbsp;%s\">",
                     vmptr->MessageFlags & MAIL$M_MARKED ?
                        "Unmark" : "Mark",
                     FLAG_MARKED);

         if (SoyMailConfig.MessageFlags & MAIL$M_NEWMSG)
            fprintf (stdout,
"<input class=\"css_button_panel_1\" style=\"margin-left:0.3em;\" \
type=\"submit\" name=\"msg_flag\" value=\"%s &nbsp;%s\">",
                     vmptr->MessageFlags & MAIL$M_NEWMSG ?
                        "Read" : "Unread",
                     FLAG_NEWMSG);

         if (SoyMailConfig.MessageFlags & MAIL$M_REPLIED)
            fprintf (stdout,
"<input class=\"css_button_panel_1\" style=\"margin-left:0.3em;\" \
type=\"submit\" name=\"msg_flag\" value=\"%s &nbsp;%s\">",
                     vmptr->MessageFlags & MAIL$M_REPLIED ?
                        "Unreplied-to" : "Replied-to",
                     FLAG_REPLIED);

         fputs ("</td>", stdout);
      }
      else
         fputs ("<td></td>", stdout);
   }
   else
      fputs ("<td></td>", stdout);

   fputs ("</tr>\n", stdout);

   if (vmptr->MsgCcPtr[0])
      fprintf (stdout,
"<tr><td class=\"css_rfchdr\">%s:</td>\
<td class=\"css_rfcval\" colspan=\"2\" width=\"100%%\">%s</td></tr>\n",
               LangFor("cc"), HTML_ADDR(vmptr->MsgCcPtr));

   fprintf (stdout,
"</table>\n\
</td></tr>\n\
</table>\n\
<!-- header end -->\n\n");

}

/*****************************************************************************/
/*
Provide the [print], [raw], header] buttons, etc.
*/

void MessageHeaderButtons (REQUEST_DATA *rdptr)

{
   char  *cptr;
   USER_OPTIONS  *uoptr;
   VMS_MAIL_MSG  *vmptr;
   VMS_MAIL_USER  *muptr;

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

   if (WatchEnabled) WatchThis (FI_LI, "MessageHeaderButtons()");

   muptr = &VmsMailUser;
   vmptr = &muptr->VmsMailMsg;
   uoptr = &rdptr->UserOptions;

   fprintf (stdout,
"<!-- header buttons begin -->\n\
<p class=\"css_p06\">\n\
<table width=\"100%%\" cellpadding=\"1\" cellspacing=\"5\" \
align=\"center\" class=\"css_panel_1\">\n\
\
<tr><td class=\"css_left\">\n\
<input class=\"css_button_panel_1\" type=\"submit\" \
name=\"msg_print\" value=\"%s\">\
<script language=\"JavaScript\">\
document.write(\'<input class=\"css_button_panel_1\" \
style=\"border-left-width:0px;\" \
type=\"submit\" name=\"msg_print_popup\" value=\"^\" \
onclick=\"targetBlank();\">\');\
</script>\n\
<input class=\"css_button_panel_1\" type=\"submit\" \
name=\"msg_raw\" value=\"%s\"%s>\
<script language=\"JavaScript\">\
document.write(\'<input class=\"css_button_panel_1\" \
style=\"border-left-width:0px;\" \
type=\"submit\" name=\"msg_raw_popup\" value=\"^\"%s>\');\
</script>\n\
<input class=\"css_button_panel_1\" type=\"submit\" \
name=\"msg_hdr\" value=\"%s\"%s>\
<script language=\"JavaScript\">\
document.write(\'<input class=\"css_button_panel_1\" \
style=\"border-left-width:0px;\" \
type=\"submit\" name=\"msg_hdr_popup\" value=\"^\"%s>\');\
</script>\n\
<input class=\"css_button_panel_1\" type=\"submit\" \
name=\"msg_prev\" value=\"%s\"%s>\n\
<input class=\"css_button_panel_1\" type=\"submit\" \
name=\"msg_folder_at\" value=\"%s\"%s>\n\
<input class=\"css_button_panel_1\" type=\"submit\" \
name=\"msg_next\" value=\"%s\"%s>\n\
</td><td class=\"css_right\">\n\
<input class=\"css_button_panel_1\" type=\"submit\" \
name=\"msg_copy\" value=\"%s &gt;&gt;\"%s>\n\
<input class=\"css_button_panel_1\" type=\"submit\" \
name=\"msg_move\" value=\"%s &gt;&gt;\"%s>\n",
            LangFor("print"),
            LangFor("raw"),
            vmptr->LooksLikeRfc ? "" : " onclick=\"return notApplicable();\"",
               vmptr->LooksLikeRfc ? "onclick=\"targetBlank();\"" :
                                     "onclick=\"return notApplicable();\"",
            LangFor("header"),
            vmptr->LooksLikeRfc ? "" : " onclick=\"return notApplicable();\"",
            vmptr->LooksLikeRfc ? "onclick=\"targetBlank();\"" :
                                  "onclick=\"return notApplicable();\"",
            LangFor("prev"),
            CLICK_NOMORE_STARTWORKING(vmptr->MessageId == 1),
            LangFor("folder_at"),
            CLICK_STARTWORKING,
            LangFor("next"),
            CLICK_NOMORE_STARTWORKING(vmptr->MessageId ==
                                      rdptr->FolderMessageCount),
            LangFor("copy"),
            CLICK_STARTWORKING,
            LangFor("move"),
            CLICK_STARTWORKING);

   MessageDestSelector (rdptr);

   fprintf (stdout,
"</td></tr>\n\
</table>\n\
<!-- header buttons end -->\n\n");
}

/*****************************************************************************/
/*
Display the message body.
*/

int MessageBodyText (REQUEST_DATA *rdptr)

{
   BOOL  ReadPreference;
   int  status,
        LineCount = 0;
   char  *cptr, *sptr, *zptr;
   char  PartUri [512],
         MailTo [128];
   MIME_DATA  *mdptr;
   USER_OPTIONS  *uoptr;
   VMS_MAIL_MSG  *vmptr;
   VMS_MAIL_USER  *muptr;

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

   if (WatchEnabled) WatchThis (FI_LI, "MessageBodyText()");

   muptr = &VmsMailUser;
   vmptr = &muptr->VmsMailMsg;
   uoptr = &rdptr->UserOptions;

   /* hack of CGILIB to make "mailto:"s access soyMAIL as mail agent */
   zptr = (sptr = MailTo) + sizeof(MailTo)-1;
   for (cptr = rdptr->FormAction; *cptr && sptr < zptr; *sptr++ = *cptr++);
   for (cptr = "?mailto:"; *cptr && sptr < zptr; *sptr++ = *cptr++);
   *sptr = '\0';

   fprintf (stdout, "<!-- message begin -->\n");

   fprintf (stdout,
"<p class=\"css_p06\">\n\
<table width=\"100%%\" cellpadding=\"0\" cellspacing=\"10\" \
class=\"css_panel_3\">\n");

   if (rdptr->MessageReadPreference)
      ReadPreference = rdptr->MessageReadPreference;
   else
      ReadPreference = rdptr->UserOptions.ReadPreference;

   mdptr = MessageBodyIdentify (vmptr, ReadPreference);
   if (mdptr)
   {
      if ((cptr = mdptr->CharSetPtr) ||
          (cptr = rdptr->CharSetPtr))
      {
         fprintf (stdout,
"<input type=\"hidden\" name=\"message_charset\" id=\"content_type\" \
value=\"%s\">\n",
                  HTML_ESCAPE(cptr));
      }

      /* this in case a 'reply', etc., button is used */
      rdptr->ComposeData.ContentTypeIs = mdptr->ContentTypeIs;

      if (mdptr->ContentTypeIs == MIME_CONTENT_TEXT_PLAIN)
      {
         if (MimeDecProcessPart (mdptr))
         {
            LineCount = MessageLineCount (mdptr->PartContentPtr);

            fprintf (stdout,
"<tr><td id=\"message_text\" class=\"css_left\" dir=\"%s\">\
<pre class=\"css_text_message\">",
                     MessageCharSetDir(mdptr->CharSetPtr));

            if (rdptr->MassageMessage)
               MessageMassage (mdptr->PartContentPtr, rdptr->MassageMessage);

            CgiLib__soyMAILhack = MailTo;
            CgiLibVeeMemContinue (1);
            if (sptr = HTML_ANCHOR(mdptr->PartContentPtr))
               fputs (sptr, stdout);
            else
               fputs ("%LIB-F-INSVIRMEM, insufficient virtual memory\n", stdout);
            CgiLibVeeMemContinue (0);
            CgiLib__soyMAILhack = NULL;

            fputs ("</pre></td></tr>\n", stdout);
         }
      }
      else
      if (mdptr->ContentTypeIs == MIME_CONTENT_TEXT_HTML)
      {
         if (MimeDecProcessPart (mdptr))
         {
            /* bit hit-and-miss for HTML but what the hey! */
            LineCount = MessageLineCount (mdptr->PartContentPtr);

            if (rdptr->PrivateAccess)
               sprintf (PartUri, "%s/%s/%s/%d.%d/--%s/--%s",
                        rdptr->FormAction,
                        rdptr->MailFileName,
                        HTML_ESCAPE(rdptr->FolderName),
                        rdptr->MessageId, rdptr->MessageHash,
                        HTML_ESCAPE(mdptr->PartName),
                        HTML_ESCAPE(mdptr->PartName));
            else
            if (rdptr->PublicWildcard)
               sprintf (PartUri, "%s%s/%d.%d/--%s/--%s",
                        rdptr->FormAction,
                        HTML_ESCAPE(rdptr->FolderName),
                        rdptr->MessageId, rdptr->MessageHash,
                        HTML_ESCAPE(mdptr->PartName),
                        HTML_ESCAPE(mdptr->PartName));
            else
               sprintf (PartUri, "%s%d.%d/--%s/--%s",
                        rdptr->FormAction,
                        rdptr->MessageId, rdptr->MessageHash,
                        HTML_ESCAPE(mdptr->PartName),
                        HTML_ESCAPE(mdptr->PartName));

            fprintf (stdout,
"<tr><td dir=\"%s\"><iframe name=\"iframeHTML\" id=\"iframeHTML\" \
frameborder=\"0\" width=\"100%%\" style=\"height:0em;\" \
src=\"%s\" onload=\"parent.msgResizeHTML(this)\">\
*** NO &lt;IFRAME&gt;s! ***</iframe></td></tr>\n",
                     MessageCharSetDir(mdptr->CharSetPtr),
                     PartUri);
         }
      }
      else
         ErrorExit (SS$_BUGCHECK, FI_LI);
   }

   fprintf (stdout, "</table>\n<!-- message end -->\n\n");

   return (LineCount);
}

/*****************************************************************************/
/*
Display (any) message attachment buttons.
*/

void MessageAttachments (REQUEST_DATA *rdptr)

{
   int  len, status, output,
        ImageCount,
        PartCount;
   char  *cptr, *sptr, *zptr,
         *ButtonNamePtr;
   char  scratch [256],
         PartUri [512];
   MIME_DATA  Rfc822mime;
   MIME_DATA  *mdptr;
   USER_OPTIONS  *uoptr;
   VMS_MAIL_MSG  *vmptr;
   VMS_MAIL_USER  *muptr;

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

   if (WatchEnabled) WatchThis (FI_LI, "MessageAttachments()");

   muptr = &VmsMailUser;
   vmptr = &muptr->VmsMailMsg;
   uoptr = &rdptr->UserOptions;

   /* if a MIME message is 'broken' there will be no MIME data at all!! */
   if (!(mdptr = vmptr->MimeDataPtr)) return;

   fprintf (stdout,
"<!-- attachment buttons begin -->\n\
<p class=\"css_p06\">\n\
<table class=\"css_panel_1\" cellpadding=\"1\" cellspacing=\"5\">\n");

   if (vmptr->LooksLikeRfc)
   {
      /***************/
      /* RFC822 part */
      /***************/

      memset (&Rfc822mime, 0, sizeof(Rfc822mime));

      zptr = (sptr = scratch) + sizeof(scratch)-1;
      if (vmptr->RfcSubjPtr)
         for (cptr = vmptr->RfcSubjPtr; *cptr && sptr < zptr; *sptr++ = *cptr++);
      else
      if (vmptr->Subject[0])
         for (cptr = vmptr->Subject; *cptr && sptr < zptr; *sptr++ = *cptr++);
      else
         for (cptr = vmptr->Date; *cptr && sptr < zptr; *sptr++ = *cptr++);
      for (cptr = ".eml"; *cptr && sptr < zptr; *sptr++ = *cptr++);
      *sptr++ = '\0';
      Rfc822mime.NamePtr = CgiLibVeeMemCalloc (sptr - scratch);
      strcpy (Rfc822mime.NamePtr, scratch);

      strcpy (Rfc822mime.PartName, "message_part_999");
      Rfc822mime.ContentTypePtr = "message/rfc822";
      Rfc822mime.PartCount = 999;
      Rfc822mime.NextMimePtr = mdptr;
      mdptr = &Rfc822mime;
   }

   /****************/
   /* (MIME) parts */
   /****************/

   if (uoptr->ImageInline <= 0)
      uoptr->ImageInline = 0;
   else
   if (uoptr->ImageInline <= 25)
      uoptr->ImageInline = 25;
   else
   if (uoptr->ImageInline <= 50)
      uoptr->ImageInline = 50;
   else
   if (uoptr->ImageInline <= 75)
      uoptr->ImageInline = 75;
   else
   if (uoptr->ImageInline > 100)
      uoptr->ImageInline = 100;

   ImageCount = PartCount = 0;

   for (; mdptr; mdptr = mdptr->NextMimePtr)
   {
      /************/
      /* continue */
      /************/

      output = 0;

      if (mdptr->PartCount == 0)
         ButtonNamePtr = LangFor("attach_message");
      else
      if (!(ButtonNamePtr = mdptr->NamePtr))
      {
         if (ButtonNamePtr = mdptr->FileNamePtr)
         {
            while (*ButtonNamePtr) ButtonNamePtr++;
            while (ButtonNamePtr > mdptr->FileNamePtr)
            {
               if (*ButtonNamePtr == '\\' ||
                   *ButtonNamePtr == '/' ||
                   *ButtonNamePtr == ']') break;
               ButtonNamePtr--;
            }
            if (ButtonNamePtr > mdptr->FileNamePtr) ButtonNamePtr++;
         }
         else
            ButtonNamePtr = mdptr->PartName;
      }

      if (!mdptr->ContentTypeIsMultipart)
      {
         if (rdptr->PrivateAccess)
            sprintf (PartUri, "%s/%s/%s/%d.%d/%s/%s",
                     rdptr->FormAction,
                     URL_ENCODE(rdptr->MailFileName),
                     URL_ENCODE(rdptr->FolderName),
                     rdptr->MessageId,
                     rdptr->MessageHash,
                     URL_ENCODE(mdptr->PartName),
                     mdptr->NamePtr ? URL_ENCODE(mdptr->NamePtr) :
                                      URL_ENCODE(mdptr->PartName));
         else
         if (rdptr->PublicWildcard)
            sprintf (PartUri, "%s%s/%d.%d/%s/%s",
                     rdptr->FormAction,
                     URL_ENCODE(rdptr->FolderName),
                     rdptr->MessageId, rdptr->MessageHash,
                     URL_ENCODE(mdptr->PartName),
                     mdptr->NamePtr ? URL_ENCODE(mdptr->NamePtr) :
                                      URL_ENCODE(mdptr->PartName));
         else
            sprintf (PartUri, "%s%d.%d/%s/%s",
                     rdptr->FormAction,
                     rdptr->MessageId, rdptr->MessageHash,
                     URL_ENCODE(mdptr->PartName),
                     mdptr->NamePtr ? URL_ENCODE(mdptr->NamePtr) :
                                      URL_ENCODE(mdptr->PartName));

         fprintf (stdout,
"<tr><td width=\"5%%\"></td>\
<td class=\"css_right\" width=\"10%%\">\
<a style=\"text-decoration:none;\" href=\"%s\" \
onclick=\"return false\">%s</A>&nbsp;",
                  PartUri,
                  SAVE_AS_LINK);

         if (cptr = SoyMailConfig.AttachmentMimeTypes)
         {
            /********************/
            /* separate window? */
            /********************/

            while (*cptr)
            {
               sptr = mdptr->ContentTypePtr;
               if (WatchEnabled) WatchThis (FI_LI, "MIME \"!AZ\" !AZ", sptr, cptr);
               while (*cptr && !isspace(*cptr) && *sptr &&
                      tolower(*cptr) == tolower(*sptr))
               {
                  cptr++;
                  sptr++;
               }
               if (!*cptr || isspace(*cptr)) { cptr = NULL; break; }
               while (*cptr && !isspace(*cptr)) cptr++;
               while (*cptr && isspace(*cptr)) cptr++;
            }
         }

         /* cptr is non-NULL to indicate hit */
         if (!cptr)
         {
            /******/
            /* no */
            /******/

            /* default or internally handled type */
            fprintf (stdout,
"<input class=\"css_button_panel_1\" type=\"submit\" \
name=\"part_btn\" value=\"%s\" \
onclick=\"document.getElementById(\'message_part\').value=\'%d\';\
return true;\">",
               HTML_ESCAPE(ButtonNamePtr),
               mdptr->PartCount);
         }
         else
         {
            /*******/
            /* yes */
            /*******/

            /* not an internally handled type (with JavaScript) */
            fprintf (stdout,
"<script language=\"JavaScript\">\
var part%did=\'part_type_%d\';\
var part%dUrl=\'%s?\';\
document.write(\'<input class=\"css_button_panel_1\" \
type=\"submit\" name=\"part_btn\" value=\"%s\" \
onclick=\"targetBlank();\
window.open(part%dUrl+document.getElementById(part%did).value);\
return false;\">\');\
</script>",
                mdptr->PartCount,
                mdptr->PartCount,
                mdptr->PartCount,
                EscapeForQuotes(PartUri),
                HTML_ESCAPE(EscapeForQuotes(ButtonNamePtr)),
                mdptr->PartCount,
                mdptr->PartCount);
         }

         fprintf (stdout,
"<script language=\"JavaScript\">\
var part%did=\'part_type_%d\';\
var part%dUrl=\'%s?\';\
document.write(\'<input class=\"css_button_panel_1\" \
style=\"border-left-width:0px;\" \
type=\"submit\" name=\"part_btn\" value=\"^\" \
onclick=\"targetBlank();\
window.open(part%dUrl+document.getElementById(part%did).value);\
return false;\">\');\
</script>&nbsp;\
</td><td class=\"css_left\" width=\"90%%\">\
<input class=\"css_text_1\" type=\"text\" size=\"24\" \
name=\"part_type_%d\" id=\"part_type_%d\" value=\"%s\" \
onfocus=\"this.select()\">&nbsp;\
<input type=\"checkbox\" \
name=\"part_cbx_%d\" id=\"part_cbx_%d\" value=\"%s\">",
                  mdptr->PartCount,
                  mdptr->PartCount,
                  mdptr->PartCount,
                  EscapeForQuotes(PartUri),
                  mdptr->PartCount,
                  mdptr->PartCount,
                  mdptr->PartCount,
                  mdptr->PartCount,
                  mdptr->ContentTypePtr ? HTML_ESCAPE(mdptr->ContentTypePtr) :
                                          "application/octet-stream",
                  mdptr->PartCount,
                  mdptr->PartCount,
                  HTML_ESCAPE(EscapeForQuotes(ButtonNamePtr)));

         output++;
      }

      if (mdptr->PartCount == 999)
      {
         /******************************/
         /* save/extract/reset buttons */
         /******************************/

         fputs ("&nbsp;&nbsp;", stdout);

         if (rdptr->PrivateAccess)
         {
            fprintf (stdout,
"<input class=\"css_button_panel_1\" type=\"submit\" \
name=\"part_save\" value=\"%s\">&nbsp;",
                     LangFor ("attach_save_part"));

            if (!SoyMailConfig.VmsOccluded)
               fprintf (stdout,
"<input class=\"css_button_panel_1\" type=\"submit\" \
name=\"part_extract\" value=\"%s\">&nbsp;",
                        LangFor ("attach_extract_part"));
         }

         fprintf (stdout,
"<input class=\"css_button_panel_1\" type=\"reset\" value=\"%s\">",
                  LangFor ("reset"));
      }

      if (uoptr->ImageInline)
      {
         /****************/
         /* inline image */
         /****************/

         if (strsame (mdptr->ContentTypePtr, "image/", 6))
         {
            if (ImageCount++)
               scratch[0] = '\0';
            else
               sprintf (scratch,
"<tr><td></td><td class=\"css_right\">\
<i style=\"font-size:90%%;\">%s</i>",
                        LangFor("message_image_inline"));

            /* bit convoluted; avoid saturating server CGI(plus) */
            /* delay image load by the set period for each */
            fprintf (stdout,
"%s\
</td></tr>\n\
<tr><td></td><td id=\"image_load_%d\" colspan=\"2\" class=\"css_left\">\n\
<script language=\"JavaScript\">\n\
setTimeout('imageInlineLoad(%d,%d,\"%s\",\"%s\")',%d);\n\
</script>\n",
                     scratch,
                     mdptr->PartCount,
                     mdptr->PartCount,
                     uoptr->ImageInline,
                     EscapeForQuotes(PartUri),
                     HTML_ESCAPE(EscapeForQuotes(ButtonNamePtr)),
                     SoyMailConfig.ImageInline ?
                        SoyMailConfig.ImageInline * ImageCount :
                        500 * ImageCount);
         }
      }

      /***************/
      /* finish here */
      /***************/

      if (output) fprintf (stdout, "</td></tr>\n");

      PartCount = mdptr->PartCount;
   }

   fprintf (stdout,
"</table>\n\
<input type=\"hidden\" name=\"message_part\" id=\"message_part\" value=\"\">\n\
<input type=\"hidden\" name=\"part_count\" value=\"%d\">\n\
<!-- attachment buttons end -->\n\n",
            PartCount);
}

/*****************************************************************************/
/*
Display the [reply], [reply to all], [forward], etc., buttons.
*/

void MessageBodyButtons (REQUEST_DATA *rdptr)

{
   int  status, value;
   char  MassageBuffer [256];
   USER_OPTIONS  *uoptr;
   VMS_MAIL_MSG  *vmptr;
   VMS_MAIL_USER  *muptr;

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

   if (WatchEnabled) WatchThis (FI_LI, "MessageBodyButtons()");

   muptr = &VmsMailUser;
   vmptr = &muptr->VmsMailMsg;
   uoptr = &rdptr->UserOptions;

   fprintf (stdout,
"<!-- message buttons begin -->\n\
<p class=\"css_p06\">\n\
<table width=\"100%%\" cellpadding=\"1\" cellspacing=\"5\" \
class=\"css_panel_1\">\n\
<tr><td class=\"css_left css_nw\">\n\
<input class=\"css_button_panel_1\" type=\"submit\" \
name=\"msg_reply\" value=\"%s\">\
<script language=\"JavaScript\">\
document.write(\'<input class=\"css_button_panel_1\" \
style=\"border-left-width:0px;\" \
type=\"submit\" name=\"msg_reply_popup\" value=\"^\" \
onclick=\"targetBlank();\">\');\
</script>\n\
<input class=\"css_button_panel_1\" type=\"submit\" \
name=\"msg_reply_all\" value=\"%s\">\
<script language=\"JavaScript\">\
document.write(\'<input class=\"css_button_panel_1\" \
style=\"border-left-width:0px;\" \
type=\"submit\" name=\"msg_reply_all_popup\" value=\"^\" \
onclick=\"targetBlank();\">\');\
</script>\n\
<input class=\"css_button_panel_1\" type=\"submit\" \
name=\"msg_forward\" value=\"%s\">\
<script language=\"JavaScript\">\
document.write(\'<input class=\"css_button_panel_1\" style=\"border-left-width:0px;\" \
type=\"submit\" name=\"msg_forward_popup\" value=\"^\" \
onclick=\"targetBlank();\">\');\
</script>\n\
<input class=\"css_button_panel_1\" type=\"submit\" \
name=\"msg_edit_new\" value=\"%s\">\
<script language=\"JavaScript\">\
document.write(\'<input class=\"css_button_panel_1\" \
style=\"border-left-width:0px;\" \
type=\"submit\" name=\"msg_edit_new_popup\" value=\"^\" \
onclick=\"targetBlank();\">\');\
</script>\n",
            LangFor("reply"),
            LangFor("reply_all"),
            LangFor("forward"),
            LangFor("edit_as_new"));

   if (rdptr->ReadPlainText && rdptr->HtmlTextIsPresent)
   {
      fprintf (stdout,
"<input class=\"css_button_panel_1\" type=\"submit\" \
name=\"msg_read_pref\" value=\"%s\">\n",
               LangFor("message_read_html"));
   }
   else
   if (rdptr->ReadHtmlText && rdptr->PlainTextIsPresent)
   {
      fprintf (stdout,
"<input class=\"css_button_panel_1\" type=\"submit\"  \
name=\"msg_read_pref\" value=\"%s\">\n",
               LangFor("message_read_plain"));
   }

   if (rdptr->ReadPlainText)
   {
      value = ComposeWrapAt (rdptr->MassageMessage, uoptr);
      if (value < 0) value = ComposeWrapAt (value, uoptr);
      if (value)
         fprintf (stdout,
"<input class=\"css_button_panel_1\" type=\"submit\" \
name=\"msg_wrap\" value=\"%s %d\">\n", LangFor("message_massage_on"), value);
      else
         fprintf (stdout,
"<input class=\"css_button_panel_1\" type=\"submit\" \
name=\"msg_wrap\" value=\"%s\">\n", LangFor("message_massage_off"));
   }

   fprintf (stdout,
"</td><td class=\"css_right css_nw\">\n\
<input class=\"css_button_panel_1\" type=\"submit\" \
name=\"msg_prev\" value=\"%s\"%s>\n\
<input class=\"css_button_panel_1\" type=\"submit\" \
name=\"msg_folder_at\" value=\"%s\"%s>\n\
<input class=\"css_button_panel_1\" type=\"submit\" \
name=\"msg_next\" value=\"%s\"%s>\n\
<input class=\"css_button_panel_1\" type=\"submit\" \
name=\"msg_waste\" value=\"%s\"%s>\n\
<input class=\"css_button_panel_1\" type=\"submit\" \
name=\"msg_delete\" value=\"%s\"%s>\n",
            LangFor("prev"),
            CLICK_NOMORE_STARTWORKING(vmptr->MessageId == 1),
            LangFor("folder_at"),
            CLICK_STARTWORKING,
            LangFor("next"),
            CLICK_NOMORE_STARTWORKING(vmptr->MessageId ==
                                      rdptr->FolderMessageCount),
            LangFor("waste"),
            CLICK_STARTWORKING,
            LangFor("delete"),
            CLICK_STARTWORKING);

   fprintf (stdout,
"</td></tr>\n</table>\n\
<!-- message buttons end -->\n\n");
}

/*****************************************************************************/
/*
Provide a message in a format more suitable for printing.
*/

BOOL MessagePrintView (REQUEST_DATA *rdptr)

{
   int  cnt, status,
        ReadPreference;
   char  *cptr, *sptr, *zptr;
   char  MailTo [128],
         MessageDescription [512];
   MIME_DATA  *mdptr;
   USER_OPTIONS  *uoptr;
   REQUEST_DATA  *sdptr;
   VMS_MAIL_MSG  *vmptr;
   VMS_MAIL_USER  *muptr;
   VMS_MAIL_USER  UserData;

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

   if (WatchEnabled) WatchThis (FI_LI, "MessagePrintView()");

   uoptr = &rdptr->UserOptions;
   sdptr = (REQUEST_DATA*)rdptr->StateDataPtr;
   muptr = &VmsMailUser;
   vmptr = &muptr->VmsMailMsg;

   /* hack of CGILIB to make "mailto:"s access soyMAIL as mail agent */
   zptr = (sptr = MailTo) + sizeof(MailTo)-1;
   for (cptr = rdptr->FormAction; *cptr && sptr < zptr; *sptr++ = *cptr++);
   for (cptr = "?mailto:"; *cptr && sptr < zptr; *sptr++ = *cptr++);
   *sptr = '\0';

   /* use the read preference from the previous request */
   if (sdptr->MessageReadPreference)
      ReadPreference = sdptr->MessageReadPreference;
   else
      ReadPreference = uoptr->ReadPreference;

   vmptr->MessageId = rdptr->MessageId;
   vmptr->ConfirmHash = rdptr->MessageHash;

   CallMailMessageGet (muptr, NULL);
   if (VMSnok (muptr->MailVmsStatus))
   {
      if (muptr->MailVmsStatus == SS$_BADCHECKSUM ||
          muptr->MailVmsStatus == MAIL$_NOMOREMSG)
         StatusMessage (FI_LI, 1, "%s", LangFor("message_checksum"));
      else
         StatusMessage (FI_LI, 1, "MAIL: %s.", SysGetMsg(muptr->MailVmsStatus));
      return (FALSE);
   }

   MessageBodyProcess (vmptr);

   if (rdptr->PrivateAccess)
      sprintf (MessageDescription, "%s / %s / %s / %04d",
               rdptr->UserName, rdptr->MailFileName,
               rdptr->FolderName, rdptr->MessageId);
   else
   if (rdptr->PublicAccess)
      sprintf (MessageDescription, "%s / %04d",
               rdptr->PublicAccessString, rdptr->MessageId);
   else
      MessageDescription[0] = '\0';

   mdptr = MessageBodyIdentify (vmptr, ReadPreference);
   if (mdptr)
      rdptr->CharSetPtr = mdptr->CharSetPtr;
   else
   if (!vmptr->VmsForeign)
      StatusMessage (FI_LI, 1, "%s", LangFor("mime_type_unknown"));

   if (rdptr->CharSetPtr) CgiLibResponseSetCharset (rdptr->CharSetPtr);

   CgiLibResponseHeader (200, "text/html", "%s", rdptr->LoginSetCookiePtr);

   fprintf (stdout,
"%s\n\
<html>\n\
<head>\n\
%s\
<meta name=\"generator\" content=\"%s\">\n\
<meta name=\"environment\" content=\"%s\">\n\
<meta name=\"copyright\" content=\"%s - Apache-2.0 licensed\">\n",
            DocType,
            CgiLibXUACompatible(NULL),
            SoftwareEnv,
            CgiEnvironmentPtr,
            SoftwareCopy);

   SoyMailJavaScriptStuff (rdptr);

   SoyMailStyleStuff (rdptr, "print");

   /* setTimeout() gives Firefox 1.5 a chance to render the page */
   fprintf (stdout,
"<title>%s</title>\n\
</head>\n\n\
<body onload=\"setTimeout(\'print()\',500);\">\n\
\n\
<table width=\"100%%\" cellpadding=\"5\">\n\n",
            SoyMailPageTitle(rdptr));

   if (SoyMailConfig.PrintHeader)
   {
      /***************/
      /* page header */
      /***************/

      /* if it appears to contain it's own markup then use that */
      if (strchr (SoyMailConfig.PrintHeader, '<'))
         cptr = "";
      else
         cptr =
" style=\"font-size:120%;font-weight:bold;\
text-decoration:underline;text-align:center;\"";

      fprintf (stdout,
"<!-- page header begin -->\n\
<tr><td colspan=\"4\">\n\
<table cellpadding=\"0\" cellspacing=\"0\" border=\"0\" width=\"100%%\">\n\
<tr><td%s>%s</td></tr>\n\
</table><p class=\"css_p06\">\n\
</td></tr>\n\
<!-- page header end -->\n\n",
               cptr, SoyMailConfig.PrintHeader);
   }

   /******************/
   /* message header */
   /******************/

   fprintf (stdout,
"<!-- message begin -->\n\
<tr><td>\n\
<table width=\"100%%\" cellpadding=\"0\" cellspacing=\"2\" border=\"0\">\n\
<tr><td class=\"css_rfchdr\">%s:</td>\
<td class=\"css_rfcval\" width=\"100%%\" colspan=\"2\">%s</td>\
<td class=\"css_rfcval\" \
style=\"white-space:nowrap;text-align:right;\">%s</td></tr>\n\
<tr><td class=\"css_rfchdr\">%s:</td>\
<td class=\"css_rfcval\" colspan=3>%s</td></tr>\n\
<tr><td class=\"css_rfchdr\">%s:</td>\
<td class=\"css_rfcval\" colspan=2 style=\"white-space:nowrap;\">%s%s%s%s</td>\
<td width=\"100%%\"></td></tr>\n\
<tr><td class=\"css_rfchdr\">%s:</td>\
<td class=\"css_rfcval\" colspan=3>%s</td></tr>\n",
            LangFor("subject"),
            vmptr->MsgSubjPtr[0] ? HTML_TEXT(vmptr->MsgSubjPtr) :
                                   LangFor("(none)"),
            InetMailRfcVmsDate(CurrentVmsTimeString),
            LangFor("from"),
            vmptr->MsgFromPtr[0] ? HTML_ADDR(vmptr->MsgFromPtr) :
                                   LangFor("(none)"),
            LangFor("date"), HTML_TEXT(vmptr->MsgDatePtr),
            vmptr->LooksLikeRfc ? "&nbsp; [" : "",
            vmptr->LooksLikeRfc ? InetMailRfcVmsDate(vmptr->Date) : "",
            vmptr->LooksLikeRfc ? "]" : "",
            LangFor("to"),
            vmptr->MsgToPtr[0] ? HTML_ADDR(vmptr->MsgToPtr) :
                                 LangFor("(none)"));

   if (vmptr->MsgCcPtr[0])
      fprintf (stdout,
"<tr><td class=\"css_rfchdr\">%s:</td>\
<td class=\"css_rfcval\" colspan=3>%s</td></tr>\n",
               LangFor("cc"), HTML_ADDR(vmptr->MsgCcPtr));

   fprintf (stdout,
"<tr><td class=\"css_rfchdr\">%s:</td>\
<td class=\"css_rfcval\" colspan=3>%s</td></tr>\n\
<tr><td colspan=\"4\" width=\"100%%\"><hr align=\"left\" \
width=\"90%%\" size=\"1\" noshade class=\"css_print_hr\"></td></tr>\n\n",
            LangFor("message"), HTML_ESCAPE(MessageDescription));

   mdptr = MessageBodyIdentify (vmptr, ReadPreference);
   if (mdptr)
   {
      if (mdptr->ContentTypeIs == MIME_CONTENT_TEXT_PLAIN)
      {
         if (MimeDecProcessPart (mdptr))
         {
            fprintf (stdout,
"<tr><td colspan=\"4\" id=\"message_text\" dir=\"%s\">\
<pre class=\"css_text_message\" style=\"overflow:visible;\">",
            MessageCharSetDir(mdptr->CharSetPtr));

            if (sdptr->MassageMessage)
               MessageMassage (mdptr->PartContentPtr, sdptr->MassageMessage);

            CgiLib__soyMAILhack = MailTo;
            CgiLibVeeMemContinue (1);
            if (sptr = HTML_ANCHOR(mdptr->PartContentPtr))
               fputs (sptr, stdout);
            else
               fputs ("%LIB-F-INSVIRMEM, insufficient virtual memory\n", stdout);
            CgiLibVeeMemContinue (0);
            CgiLib__soyMAILhack = NULL;

            fputs ("</pre></td></tr>\n", stdout);
         }
      }
      else
      if (mdptr->ContentTypeIs == MIME_CONTENT_TEXT_HTML)
      {
         fprintf (stdout,
"<tr><td colspan=\"4\" id=\"message_text\" dir=\"%s\">\
<iframe name=\"iframeHTML\" id=\"iframeHTML\" \
frameborder=\"0\" width=\"100%%\" style=\"height:0em;\"",
         MessageCharSetDir(mdptr->CharSetPtr));

         if (rdptr->PrivateAccess)
            fprintf (stdout, " src=\"%s/%s/%s/%d.%d//--%s\" ",
                     rdptr->FormAction,
                     rdptr->MailFileName, rdptr->FolderName,
                     rdptr->MessageId, rdptr->MessageHash,
                     HTML_ESCAPE(mdptr->PartName));
         else
         if (rdptr->PublicAccess)
            fprintf (stdout, " src=\"%s%d.%d//--%s\" ",
                     rdptr->FormAction,
                     rdptr->MessageId, rdptr->MessageHash,
                     HTML_ESCAPE(mdptr->PartName));

         fprintf (stdout,
" onload=\"parent.msgResizeHTML(this)\">\
*** NO &lt;IFRAME&gt;s! ***</iframe></td></tr>\n");
      }
      else
         ErrorExit (SS$_BUGCHECK, FI_LI);
   }

   fprintf (stdout,
"<tr><td colspan=\"4\" width=\"100%%\"><hr align=\"left\" \
width=\"90%%\" size=\"1\" noshade class=\"css_print_hr\"></td></tr>\n\
<!-- message end -->\n\n");

   /*******************/
   /* any attachments */
   /*******************/

   for (mdptr = vmptr->MimeDataPtr; mdptr; mdptr = mdptr->NextMimePtr)
      if (mdptr->NamePtr || mdptr->FileNamePtr) break;

   if (mdptr)
   {
      fprintf (stdout,
"<!-- attachments begin -->\n\
<tr><td colspan=\"4\">\n\
<table cellpadding=\"0\" cellspacing=\"2\" border=\"0\" width=\"100%%\">\n");

      while (mdptr)
      {
         fprintf (stdout,
"<tr><td class=\"css_tabhdr\" \
style=\"padding-left:20px;white-space:nowrap;\">[%s]</td>\
<td class=\"css_tabdat\" \
style=\"padding-left:5px;font-style:italic;\">%s</td></tr>\n",
                  mdptr->NamePtr ? mdptr->NamePtr : mdptr->FileNamePtr,
                  mdptr->ContentTypePtr ? mdptr->ContentTypePtr : "?");

         mdptr = mdptr->NextMimePtr;
      }

      fprintf (stdout,
"</table>\n\
</td></tr>\n\
<!-- attachments end -->\n\n");
   }

   if (SoyMailConfig.PrintFooter)
   {
      /***************/
      /* page footer */
      /***************/

      /* if it appears to contain it's own markup then use that */
      if (strchr (SoyMailConfig.PrintFooter, '<'))
         cptr = "";
      else
         cptr = " style=\"font-weight:bold;text-align:center;\"";

      fprintf (stdout,
"<!-- page footer begin -->\n\
<tr><td colspan=\"4\"><p class=\"css_p06\">\n\
<table cellpadding=\"0\" cellspacing=\"0\" border=\"0\" width=\"100%%\">\n\
<tr><td%s>%s</td></tr>\n\
</table>\n\
</td></tr>\n\
<!-- page footer end -->\n\n",
               cptr, SoyMailConfig.PrintFooter);
   }

   /************/
   /* page end */
   /************/

   fprintf (stdout,
"</table>\n\
</body>\n\
</html>\n");

   return (TRUE);
}

/*****************************************************************************/
/*
Display the entire mail message (header and body) as a plain text document.
*/

BOOL MessageRawView (REQUEST_DATA *rdptr)

{
   int  status;
   VMS_MAIL_MSG  *vmptr;
   VMS_MAIL_USER  *muptr;

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

   if (WatchEnabled) WatchThis (FI_LI, "MessageRawView()");

   muptr = &VmsMailUser;
   vmptr = &muptr->VmsMailMsg;

   vmptr->MessageId = rdptr->MessageId;
   vmptr->ConfirmHash = rdptr->MessageHash;

   CallMailMessageGet (muptr, NULL);
   if (VMSnok (muptr->MailVmsStatus))
   {
      if (muptr->MailVmsStatus == SS$_BADCHECKSUM ||
          muptr->MailVmsStatus == MAIL$_NOMOREMSG)
         StatusMessage (FI_LI, 1, "%s", LangFor("message_checksum"));
      else
         StatusMessage (FI_LI, 1, "MAIL: %s.", SysGetMsg(muptr->MailVmsStatus));
      return (FALSE);
   }

   InetMailParseHeader (vmptr);

   if (!vmptr->LooksLikeRfc)
   {
      StatusMessage (FI_LI, 1, "%s", LangFor("not_applicable"));
      return (FALSE);
   }

   CgiLibResponseHeader (200, "text/plain", "%s", rdptr->LoginSetCookiePtr);

   fputs (vmptr->BodyPtr, stdout);

   return (TRUE);
}

/*****************************************************************************/
/*
Display the RFC822/2822 header as a plain text document.
*/

BOOL MessageHdrView (REQUEST_DATA *rdptr)

{
   int  status;
   char  ch;
   VMS_MAIL_MSG  *vmptr;
   VMS_MAIL_USER  *muptr;

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

   if (WatchEnabled) WatchThis (FI_LI, "MessageHdrView()");

   muptr = &VmsMailUser;
   vmptr = &muptr->VmsMailMsg;

   vmptr->MessageId = rdptr->MessageId;
   vmptr->ConfirmHash = rdptr->MessageHash;

   CallMailMessageGet (muptr, NULL);
   if (VMSnok (muptr->MailVmsStatus))
   {
      if (muptr->MailVmsStatus == SS$_BADCHECKSUM ||
          muptr->MailVmsStatus == MAIL$_NOMOREMSG)
         StatusMessage (FI_LI, 1, "%s", LangFor("message_checksum"));
      else
         StatusMessage (FI_LI, 1, "MAIL: %s.", SysGetMsg(muptr->MailVmsStatus));
      return (FALSE);
   }

   InetMailParseHeader (vmptr);

   if (!vmptr->LooksLikeRfc)
   {
      StatusMessage (FI_LI, 1, "%s", LangFor("not_applicable"));
      return (FALSE);
   }

   CgiLibResponseHeader (200, "text/plain", "%s", rdptr->LoginSetCookiePtr);

   if (vmptr->RfcHeaderPtr)
   {
      ch = vmptr->RfcHeaderPtr[vmptr->RfcHeaderLength];
      vmptr->RfcHeaderPtr[vmptr->RfcHeaderLength] = '\0';
      fputs (vmptr->RfcHeaderPtr, stdout);
      vmptr->RfcHeaderPtr[vmptr->RfcHeaderLength] = ch;
   }
   else
      fputs (vmptr->BodyPtr, stdout);

   return (TRUE);
}

/*****************************************************************************/
/*
Provide a message preview.
*/

void MessagePreView (REQUEST_DATA *rdptr)

{
   static char  AttachReport [] =
"<span class=\"css_preview_status\">%d %s</span>",
                StatusReport [] =
"<span class=\"css_preview_status\">%s</span>";

   int  attcnt, chcnt, status,
        ReadPreference;
   char  *aptr, *cptr, *sptr, *zptr,
         *rtlptr = "",
         *PreviewText = "";
   char  StatusText [512] = "";
   MIME_DATA  *mdptr;
   USER_OPTIONS  *uoptr;
   REQUEST_DATA  *sdptr;
   VMS_MAIL_MSG  *vmptr;
   VMS_MAIL_USER  *muptr;

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

   if (WatchEnabled) WatchThis (FI_LI, "MessagePreView()");

   uoptr = &rdptr->UserOptions;
   sdptr = (REQUEST_DATA*)rdptr->StateDataPtr;
   muptr = &VmsMailUser;
   vmptr = &muptr->VmsMailMsg;

   RequestVmsMailContext (rdptr);

   vmptr->MessageId = rdptr->MessageId;
   vmptr->ConfirmHash = rdptr->MessageHash;

   CallMailMessageGet (muptr, NULL);

   if (VMSnok (muptr->MailVmsStatus))
   {
      if (muptr->MailVmsStatus == SS$_BADCHECKSUM ||
          muptr->MailVmsStatus == MAIL$_NOMOREMSG)
         sprintf (StatusText, StatusReport, LangFor("message_checksum"));
      else
         sprintf (StatusText, StatusReport, SysGetMsg(muptr->MailVmsStatus));
   }

   if (!StatusText[0])
   {
      MessageBodyProcess (vmptr);

      if (rdptr->MessageReadPreference)
         ReadPreference = rdptr->MessageReadPreference;
      else
         ReadPreference = uoptr->ReadPreference;

      mdptr = MessageBodyIdentify (vmptr, ReadPreference);
      if (mdptr)
      {
         rdptr->CharSetPtr = mdptr->CharSetPtr;
         if (!strcmp (MessageCharSetDir(mdptr->CharSetPtr), "rtl"))
            rtlptr = " dir=\"rtl\"";
      }
      else
      if (!vmptr->VmsForeign)
         sprintf (StatusText, StatusReport, LangFor("mime_type_unknown"));
   }

   if (!StatusText[0])
   {
      attcnt = 0;
      for (mdptr = vmptr->MimeDataPtr; mdptr; mdptr = mdptr->NextMimePtr)
         if (mdptr->NamePtr) attcnt++;

      if (!(mdptr = MessageBodyIdentify (vmptr, ReadPreference)))
         sprintf (StatusText, StatusReport, "?");
   }

   if (rdptr->CharSetPtr) CgiLibResponseSetCharset (rdptr->CharSetPtr);

   CgiLibResponseHeader (200, "text/html", "%s", rdptr->LoginSetCookiePtr);

   PreviewText =  CgiLibVeeMemCalloc (uoptr->PreviewSize+256);
   if (!PreviewText) ErrorExit (vaxc$errno, FI_LI);
   zptr = (sptr = PreviewText) + uoptr->PreviewSize;

   if (StatusText[0])
      for (cptr = StatusText; *cptr; *sptr++ = *cptr++);
   else
   {
      MimeDecProcessPart (mdptr);
      mdptr->PartContentLength = MessageRemovePgpSig (mdptr->PartContentPtr);
      if (mdptr->ContentTypeIs == MIME_CONTENT_TEXT_HTML)
         HtmlSanitise (mdptr->PartContentPtr, mdptr->CharSetPtr);

      for (cptr = "<pre style=\"margin:0;\""; *cptr; *sptr++ = *cptr++);
      for (cptr = rtlptr; *cptr; *sptr++ = *cptr++);
      *sptr++ = '>';
      aptr = sptr;
      for (cptr = mdptr->PartContentPtr;
           *cptr == '\r' || *cptr == '\n';
           cptr++);
      chcnt = 0;
      while (*cptr && sptr < zptr)
      {
         switch (*cptr)
         {
            case '<' :
               strcpy (sptr, "&lt;");
               sptr += 4;
               chcnt++;
               break;
            case '>' :
               strcpy (sptr, "&gt;");
               sptr += 4;
               chcnt++;
               break;
            case '&' :
               strcpy (sptr, "&amp;");
               sptr += 5;
               chcnt++;
               break;
            case '\'' :
               strcpy (sptr, "&#39;");
               sptr += 5;
               chcnt++;
               break;
            case ' ' :
            case '\t' :
               *sptr++ = ' ';
               chcnt++;
               break;
            case '\r' :
            case '\n' :
               while (*(cptr+1) == '\r' || *(cptr+1) == '\n') cptr++;
               if (!*(cptr+1) || sptr >= zptr) break;
               *sptr++ = '\\';
               *sptr++ = 'n';
               chcnt = 0;
               break;
            default :
               *sptr++ = *cptr;
               chcnt++;
         }
         cptr++;
         if (chcnt > 96)
         {
            strcpy (sptr, "\\n&crarr;");
            sptr += 9;
            chcnt = 0;
         }
      }
      if (sptr >= zptr)
      {
         while (sptr > PreviewText && *(sptr-1) != ' ') sptr--;
         if (sptr > PreviewText) sptr--;
         while (sptr > PreviewText && *(sptr-1) == ' ') sptr--;
      }
      *sptr = '\0';
      if (sptr == aptr)
         sptr = PreviewText;
      else
      {
         if (*cptr)
         {
            while (sptr > PreviewText && *(USHORTPTR)(sptr-2) == '\\n')
               sptr -= 2;
            for (cptr = "&nbsp;&#9988;";
                 *cptr;
                 *sptr++ = *cptr++); 
         }
         for (cptr = "</pre>"; *cptr; *sptr++ = *cptr++);
      }
      *sptr = '\0';
      if (!PreviewText[0])
         sprintf (StatusText, StatusReport, LangFor("message_empty"));
      if (attcnt)
         sptr += sprintf (sptr, AttachReport,
                          attcnt, LangFor("message_attachments"));
      if (StatusText[0])
      {
         if (PreviewText[0]) for (cptr = "&nbsp;"; *cptr; *sptr++ = *cptr++);
         for (cptr = StatusText; *cptr; *sptr++ = *cptr++);
      }
      *sptr = '\0';
   }

   fprintf (stdout,
"%s\n\
<html>\n\
<head>\n\
<script language=\"JavaScript\">msgNum=%d;msgText=\'%s\'</script>\n\
</head>\n\
<body onload=\"parent.msgPreviewText(msgNum,msgText)\">\n\
</body>\n\
</html>\n",
            DocType,
            rdptr->MessageId,
            PreviewText);
}

/*****************************************************************************/
/*
Looks through each of (any) MIME parts for a likely message text.  Likely parts
are the first "text/plain" of a multipart without a file name, or a
non-multipart "text/plain" body.  If the message is not MIME then allocate and
populate a MIME structure using the textual body.

The 'ReadPreference' parameter provides the OPTIONS_READ_.. constants that
specify whether plain or HTML text is the preference.

Return a pointer to the MIME data representing the message body, NULL if there
is a problem.
*/

MIME_DATA* MessageBodyIdentify 
(
VMS_MAIL_MSG *vmptr,
int ReadPreference
)
{
   char  *cptr;
   MIME_DATA  *mdptr,
              *HtmlMimePtr,
              *PlainMimePtr;

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

   if (WatchEnabled) WatchThis (FI_LI, "MessageBodyIdentify() !UL", ReadPreference);

   if (vmptr->VmsForeign) return (NULL);

   HtmlMimePtr = PlainMimePtr = NULL;

   mdptr = vmptr->MimeDataPtr;
   if (mdptr)
   {
      if (mdptr->ContentTypeIsMultipart)
      {
         /* a MIME message with a multipart content-type */
         for (mdptr = vmptr->MimeDataPtr; mdptr; mdptr = mdptr->NextMimePtr)
         {
            /* get the first instance of each of these */
            if (mdptr->ContentTypeIs == MIME_CONTENT_TEXT_PLAIN)
            {
               /* looking for plain text part without a file name */
               if (!PlainMimePtr)
                  if (!mdptr->NamePtr && !mdptr->FileNamePtr)
                     PlainMimePtr = mdptr;
            }
            else
            if (mdptr->ContentTypeIs == MIME_CONTENT_TEXT_HTML)
            {
               /* looking for HTML text part without a file name */
               if (!HtmlMimePtr)
                  if (!mdptr->NamePtr && !mdptr->FileNamePtr)
                     HtmlMimePtr = mdptr;
            }
         }
      }
      else
      {
         /* look for the first plain-text and first HTML non-named parts */
         for (mdptr = vmptr->MimeDataPtr; mdptr; mdptr = mdptr->NextMimePtr)
            if (mdptr->ContentTypeIs == MIME_CONTENT_TEXT_PLAIN)
               if (!mdptr->NamePtr && !mdptr->FileNamePtr) break;
         PlainMimePtr = mdptr;
         for (mdptr = vmptr->MimeDataPtr; mdptr; mdptr = mdptr->NextMimePtr)
            if (mdptr->ContentTypeIs == MIME_CONTENT_TEXT_HTML)
               if (!mdptr->NamePtr && !mdptr->FileNamePtr) break;
         HtmlMimePtr = mdptr;
      }
   }

   mdptr = NULL;
   if (ReadPreference == OPTIONS_READ_PLAIN1_HTML2)
   {
      if (PlainMimePtr)
         mdptr = PlainMimePtr;
      else
         mdptr = HtmlMimePtr;
   }
   else
   if (ReadPreference == OPTIONS_READ_PLAIN2_HTML1)
   {
      if (HtmlMimePtr)
         mdptr = HtmlMimePtr;
      else
         mdptr = PlainMimePtr;
   }
   else
   if (ReadPreference == OPTIONS_READ_PLAIN0_HTML1)
   {
      /* only used to 'probe' whether an HTML alternative is present! */
      if (HtmlMimePtr) mdptr = HtmlMimePtr;
   }
   else
   {
      /* anything else, including zero (not set), gets any plain text */
      mdptr = PlainMimePtr;
   }

   if (WatchEnabled)
   {
      if (mdptr)
         WatchThis (FI_LI, "BODY !AZ", mdptr->ContentTypePtr);
      else
         WatchThis (FI_LI, "BODY not found");
   }

   return (mdptr);
}

/*****************************************************************************/
/*
Make a message (ostensibly) more readable by ...

  1) Remove any PGP signature
  2) Collapse extra line breaks
  3) Wrap lines longer than specified by the macro SOY_WRAP_AT

All these are performed on the text in situ!
*/

int MessageMassage
(
char *TextPtr,
int WrapAt
)
{
   int  colnum;
   char  *cptr, *sptr, *tptr;

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

   if (WatchEnabled) WatchThis (FI_LI, "MessageMassage() %d\n", WrapAt);

   if (WrapAt < 0) WrapAt = atoi(DEFAULT_WRAP_AT);

   MessageRemovePgpSig (TextPtr);

   MessageCollapseLineBreaks (TextPtr, 2);

   colnum = 0;
   cptr = sptr = TextPtr;
   while (*cptr)
   {
      if (*cptr == '\n')
      {
         colnum = 0;
         cptr++;
         continue;
      }
      if (colnum++ < WrapAt)
      {
         cptr++;
         continue;
      }
      for (tptr = cptr; tptr >= sptr; tptr--)
         if (isspace(*tptr)) break;
      if (tptr >= sptr)
      {
         /* found an intervening space */
         if (*(tptr+1) == '\n')
            tptr++;
         else
            *tptr++ = '\n';
         cptr = tptr;
         colnum = 0;
      }
      else
      {
         /* no intervening space, find next */
         while (*cptr && !isspace(*cptr))
         {
            colnum++;
            cptr++;
         }
      }
      sptr = cptr;
   }

   return (cptr-TextPtr);
}

/*****************************************************************************/
/*
Eliminate all empty lines and then collapse the number of consecutive newlines
down to whatevers specified in the parameter.
*/

int MessageCollapseLineBreaks
(
char *StringPtr,
int MaxConsecutive
)
{
   int  nlcnt;
   char  *cptr, *sptr, *tptr, *solptr;

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

   if (WatchEnabled)
      WatchThis (FI_LI, "MessageCollapseLineBreaks() !UL !UL |!AZ|",
                 MaxConsecutive, strlen(StringPtr), StringPtr);

   for (cptr = sptr = StringPtr; *cptr; *sptr++ = *cptr++)
   {
      if (*cptr == ' ')
      {
         /* eliminate empty lines */
         tptr = cptr;
         while (*cptr == ' ') cptr++;
         if (*cptr != '\r') cptr = tptr;
      }
      if (*(ULONGPTR)cptr != '\r\n\r\n') continue;
      if (*(USHORTPTR)cptr != '\n\n') continue;
      cptr += 2;
   }
   *sptr = '\0';

   if (MaxConsecutive < 1) MaxConsecutive = 1;
   cptr = sptr = StringPtr;
   while (*cptr)
   {
      solptr = sptr;
      while (*cptr && isspace(*cptr) && *cptr != '\n') *sptr++ = *cptr++;
      if (*cptr == '\n')
         sptr = solptr;
      else
         while (*cptr && *cptr != '\n') *sptr++ = *cptr++;
      if (*cptr) *sptr++ = *cptr++;
   }
   *sptr = '\0';

   cptr = sptr = StringPtr;
   while (*cptr == '\n') cptr++;
   while (*cptr)
   {
      while (*cptr && *cptr != '\n') *sptr++ = *cptr++;
      nlcnt = MaxConsecutive;
      while (*cptr == '\n' && nlcnt--) *sptr++ = *cptr++;
      while (*cptr == '\n') cptr++;
   }
   while (sptr > StringPtr && *(sptr-1) == '\n') sptr--;
   *sptr = '\0';

   if (WatchEnabled)
      WatchThis (FI_LI, "!UL |!AZ|", sptr-StringPtr, StringPtr);

   return (sptr-StringPtr);
}

/*****************************************************************************/
/*
Return true if text appears PGP clear-text signed (RFC2440).
*/

BOOL MessagePgpSigned (char *TextPtr)

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

   if (WatchEnabled) WatchThis (FI_LI, "MessagePgpSigned()");

   if (!TextPtr) return (FALSE);
   if (memcmp (TextPtr, "-----BEGIN PGP ", 15)) return (FALSE);
   return (TRUE);
}

/*****************************************************************************/
/*
Remove any PGP clear-text signature (RFC2440) from a message part.
*/

int MessageRemovePgpSig (char *TextPtr)

{
   char  *cptr, *sptr, *zptr;

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

   if (WatchEnabled) WatchThis (FI_LI, "MessageRemovePgpSig()");

   /* return if it doesn't appear to be PGP signed text */
   if (memcmp (TextPtr, "-----BEGIN PGP ", 15)) return (strlen(TextPtr));

   /* find the end-of-armor header empty line */
   for (cptr = sptr = TextPtr; *cptr; cptr++)
      if (*(USHORTPTR)cptr == '\n\n' ||
          *(ULONGPTR)cptr == '\r\n\r\n') break;

   if (!*cptr) return (strlen(TextPtr));

   if (*(USHORTPTR)cptr == '\n\n')
      cptr += 2;
   else
      cptr += 4;

   while (*cptr)
   {
      while (*cptr && *cptr != '\n') *sptr++ = *cptr++;
      if (!*cptr) break;
      while (*cptr == '\n') *sptr++ = *cptr++;
      if (!*cptr) break;
      /* start of line */
      if (*cptr == '-')
      {
         if (*(USHORTPTR)cptr == '- ')
            cptr += 2;
         else
         if (!memcmp (cptr, "-----BEGIN PGP ", 15))
            break;
      }
   }
   *sptr = '\0';

   return (sptr - TextPtr);
}

/*****************************************************************************/
/*
Return number of newline characters (lines) in text.
*/

int MessageLineCount (char *TextPtr)

{
   int  LineCount = 0;
   char  *cptr;

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

   if (WatchEnabled) WatchThis (FI_LI, "MessageLineCount()");

   if (!TextPtr) return (0);

   for (cptr = TextPtr; *cptr; cptr++)
      if (*cptr == '\n') LineCount++;
   if (MessagePgpSigned (TextPtr)) LineCount -= 10;
   if (LineCount < 0) LineCount = 0;

   return (LineCount);
}

/*****************************************************************************/
/*
Retrieve the content of a specific MIME part (often an attachment) and provide
it to the client using the appropriate content-type.  If an HTML part then
sanitise the HTML unless it's in a popup (a full window of it's own) and the
user has set the appropriate configuration option.
*/

BOOL MessagePart (REQUEST_DATA *rdptr)

{
   BOOL  SanitiseHtml = FALSE;
   int  status,
        DataLength,
        PartCount,
        RightToLeft = 0;
   char  *cptr,
         *DataPtr;
   char  PartTypeField [64],
         ResponseBuffer [256];
   MIME_DATA  *mdptr;
   USER_OPTIONS  *uoptr;
   VMS_MAIL_MSG  *vmptr;
   VMS_MAIL_USER  *muptr;

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

   if (WatchEnabled) WatchThis (FI_LI, "MessagePart()");

   muptr = &VmsMailUser;
   vmptr = &muptr->VmsMailMsg;
   uoptr = &rdptr->UserOptions;

   vmptr->MessageId = rdptr->MessageId;
   vmptr->ConfirmHash = rdptr->MessageHash;

   CallMailMessageGet (muptr, NULL);
   if (VMSnok (muptr->MailVmsStatus))
   {
      if (muptr->MailVmsStatus == SS$_BADCHECKSUM ||
          muptr->MailVmsStatus == MAIL$_NOMOREMSG)
         StatusMessage (FI_LI, 1, "%s", LangFor("message_checksum"));
      else
         StatusMessage (FI_LI, 1, "MAIL: %s.", SysGetMsg(muptr->MailVmsStatus));
      return (FALSE);
   }

   MessageBodyProcess (vmptr);

   cptr = rdptr->AttachPartName;
   if (!*cptr) cptr = rdptr->AttachmentName;
   if (WatchEnabled) WatchThis (FI_LI, "!AZ", cptr);
   if (*(USHORTPTR)cptr == '--')  /* hyphens */
   {
      SanitiseHtml = TRUE;
      cptr += 2;
   }

   if (strsame (cptr, "message_part_999", -1))
   {
      /******************/
      /* message/rfc822 */
      /******************/

      DataPtr = vmptr->BodyPtr;
      DataLength = vmptr->BodyLength;

      CGIVARNULL (cptr, "FORM_PART_TYPE_999");
      if (!cptr)
         if (!*(cptr = rdptr->CgiQueryStringPtr))
            cptr = "message/rfc822";
      if (!*cptr || !isalpha(*cptr)) cptr = "application/octet-stream";

      sprintf (ResponseBuffer, "Content-Length: %d\n", DataLength);

      CgiLibResponseHeader (200, cptr, ResponseBuffer,
                            rdptr->LoginSetCookiePtr);

      fwrite (DataPtr, DataLength, 1, stdout);

      return (TRUE);
   }

   if (*cptr)
   {
      /*****************/
      /* look for part */
      /*****************/

      for (mdptr = vmptr->MimeDataPtr; mdptr; mdptr = mdptr->NextMimePtr)
      {
         if (strsame (mdptr->PartName, cptr, -1)) break;
         if (!mdptr->ContentTypeIsMultipart)
         {
            if (WatchEnabled)
               WatchThis (FI_LI, "!UL |!AZ|!AZ",
                          mdptr->PartCount, mdptr->PartName,
                          mdptr->NamePtr ? mdptr->NamePtr : "(null)");
            if (mdptr->PartName[0] && strsame (mdptr->PartName, cptr, -1)) break;
            if (mdptr->NamePtr && strsame (mdptr->NamePtr, cptr, -1)) break;
         }
      }
   }
   else
   {
      /* empty part name just allows retrieval of first part */
      mdptr = vmptr->MimeDataPtr;
   }

   if (mdptr)
   {
      /**************/
      /* part found */
      /**************/

      if (MimeDecProcessPart (mdptr))
      {
         DataPtr = mdptr->PartContentPtr;
         DataLength = mdptr->PartContentLength;

         if (!mdptr->ContentTypePtr) ErrorExit (SS$_BUGCHECK, FI_LI);
         /* use the content-type from the form */
         sprintf (PartTypeField, "FORM_PART_TYPE_%d", mdptr->PartCount);
         CGIVARNULL (cptr, PartTypeField);
         if (!cptr || !*cptr)
            if (!*(cptr = rdptr->CgiQueryStringPtr))
               cptr = mdptr->ContentTypePtr;
         if (!*cptr || !isalpha(*cptr)) cptr = "application/octet-stream";
      }
      else
      {
         /* can't do any more */
         StatusMessage (FI_LI, 1, LangFor("part_not_found"));
         return (FALSE);
      }
   }
   else
   if (!memcmp (cptr, HTML_IN_DISGUISE, strlen(HTML_IN_DISGUISE)))
   {
      /* looks like HTML as text/plain */
      DataPtr = vmptr->RfcBodyPtr;
      DataLength = vmptr->RfcBodyLength;
      cptr = "text/html";
      if (uoptr->MassageRead) DataLength = MessageRemovePgpSig (DataPtr);
      if (SanitiseHtml) DataLength = HtmlSanitise (DataPtr, "UTF-8");
   }
   else
   {
      /******************/
      /* part not found */
      /******************/

      StatusMessage (FI_LI, 1, LangFor("part_not_found"));
      return (FALSE);
   }

   /***************/
   /* return part */
   /***************/

   if (mdptr && mdptr->ContentTypeIs == MIME_CONTENT_TEXT_HTML)
   {
      if (!cptr || !strncmp (cptr, "text/", 5))
      {
         if (uoptr->MassageRead) DataLength = MessageRemovePgpSig (DataPtr);
         if (SanitiseHtml) DataLength = HtmlSanitise (DataPtr, mdptr->CharSetPtr);
      }
      /* try to ensure right-to-left directionality is rendered */
      if (!strcmp (MessageCharSetDir(mdptr->CharSetPtr), "rtl"))
         RightToLeft = 33 + 8;
   }

   if (mdptr && !strncmp (cptr, "text/", 5) && mdptr->CharSetPtr)
      sprintf (ResponseBuffer, "; charset=%s\nContent-Length: %d\n%%s",
               mdptr->CharSetPtr, DataLength+RightToLeft);
   else
      sprintf (ResponseBuffer, "Content-Length: %d\n%%s",
               DataLength+RightToLeft);

   CgiLibResponseHeader (200, cptr, ResponseBuffer, rdptr->LoginSetCookiePtr);

   if (RightToLeft)
   {
      fwrite (DocType, strlen(DocType), 1, stdout);
      fwrite ("\n<html id=\"soyMAILrtl\" dir=\"rtl\">\n", 33, 1, stdout);
   }

   fwrite (DataPtr, DataLength, 1, stdout);

   if (RightToLeft) fwrite ("</html>\n", 8, 1, stdout);

   return (TRUE);
}

/*****************************************************************************/
/*
Provide an HTML form selection list of the available message ranges.
*/

void MessageRangeSelect (REQUEST_DATA *rdptr)

{
   int  first, last,
        FirstId, LastId, ScratchId;
   char  *MsgRangeListPtr;
   VMS_MAIL_USER  *muptr;

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

   if (WatchEnabled) WatchThis (FI_LI, "MessageRangeSelect()");

   muptr = &VmsMailUser;

   if (rdptr->FolderMessageCount <= rdptr->UserOptions.MessagesPerPage)
   {
      fprintf (stdout, "<td><!-- no message range --></td>\n\n");
      return;
   }

   if (rdptr->SearchData.InProgress)
   {
      /* get highest and lowest id from this last search */
      LastId = rdptr->SearchData.LastIdSearched;
      FirstId = rdptr->SearchData.FirstIdSearched;
   }
   else
   if (rdptr->MsgIdListPtr)
   {
      /* get highest and lowest id in the current message list */
      LastId = atol(rdptr->MsgIdListPtr);
      FirstId = MessageRangeString (-1, rdptr->MsgIdListPtr);
   }
   else
      FirstId = LastId = 0;

   if (FirstId > LastId)
   {
      /* can happen when using the [prev][next] buttons */
      ScratchId = LastId;
      LastId = FirstId;
      FirstId = ScratchId;
   }
   if (WatchEnabled) WatchThis (FI_LI, "first:!UL last:!UL", FirstId, LastId);

   fprintf (stdout,
"<!-- message range begin -->\n\
<td style=\"text-align:left;white-space:nowrap;\">\n\
<input class=\"css_button_panel_1\" type=\"submit\" \
name=\"range_prev\" value=\"%s\"%s>&nbsp;\n",
            LangFor("prev"),
            CLICK_NOMORE_STARTWORKING(FirstId == 1));

   if (rdptr->SearchData.InProgress)
   {
      /* more of a reminder that we're searching than anything else */
      fprintf (stdout,
"<input class=\"css_button_panel_1\" type=\"submit\" \
name=\"main_btn\" value=\"%s\">&nbsp;\n",
               LangFor("search_new"));
   }
   else
   {
      fprintf (stdout,
"<input type=\"hidden\" name=\"range_list_change\" \
id=\"range_list_change\" value=\"\">\n\
<input class=\"css_button_panel_1\" type=\"submit\" \
name=\"range_msgs\" value=\"%s\">\n\
<select class=\"css_select_1\" name=\"range_list\" \
onchange=\"document.getElementById(\'range_list_change\').value=true;\
startWorking();document.getElementById(\'soymail_form\').submit();\">\n\
<option value=\"%d\">&nbsp;%s\n",
               LangFor("range"),
               RANGE_MOST_RECENT,
               LangFor("most_recent"));

      last = rdptr->FolderMessageCount;
      while (last > 0)
      {
         first = last - rdptr->UserOptions.MessagesPerPage;
         if (first < 0) first = 0;
         first++;

         /* depending on which way we're moving */
         if (first == rdptr->RangeIdStart || last == rdptr->RangeIdStart)
            fprintf (stdout, "<option value=\"%d-%d\" selected>&nbsp;%d-%d\n",
                     first, last, first, last);
         else
            fprintf (stdout, "<option value=\"%d-%d\">&nbsp;%d-%d\n",
                     first, last, first, last);

         last = first - 1;
      }

      fprintf (stdout,
"<option value=\"%d\"%s>&nbsp;%s\n\
</select>&nbsp;\n",
               RANGE_ALL_MSGS,
               rdptr->RangeIdStart == RANGE_ALL_MSGS ? " selected" : "",
               LangFor("all"));
   }

   fprintf (stdout,
"<input class=\"css_button_panel_1\" type=\"submit\" \
name=\"range_next\" value=\"%s\"%s>\n\
</td>\n\
<!-- message range end -->\n\n",
            LangFor("next"),
            CLICK_NOMORE_STARTWORKING(LastId == rdptr->FolderMessageCount));
}

/*****************************************************************************/
/*
Generate the list of messages.
*/

void MessageListInit (REQUEST_DATA *rdptr)

{
   int  cnt, status;
   unsigned long  EndSeconds,
                  StartSeconds;
   char  *cptr, *fldptr, *sptr, *zptr;
   char  MessageBuffer [512];
   USER_OPTIONS  *uoptr;
   VMS_MAIL_MSG  *vmptr, *vmlptr;
   VMS_MAIL_USER  *muptr;

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

   if (WatchEnabled)
      WatchThis (FI_LI, "MessageListInit() !SL !SL !SL",
                 rdptr->RangeIdStart, rdptr->RangeIdCount,
                 rdptr->RangeIdVector);

   muptr = &VmsMailUser;
   vmptr = &muptr->VmsMailMsg;
   uoptr = &rdptr->UserOptions;

   muptr->ListMessageIdStart = rdptr->RangeIdStart;
   muptr->ListMessageIdCount = rdptr->RangeIdCount;
   muptr->ListMessageIdVector = rdptr->RangeIdVector;

   if (rdptr->SearchData.InProgress)
   {
      muptr->SearchFunction = &SearchMessage;
      muptr->SearchDataPtr = (void*)&rdptr->SearchData;
      muptr->ListMessageIdMax = rdptr->SearchData.ResultsEvery;
      if (SoyMailConfig.SelectNotSearch)
         muptr->SelectNotSearch = SoyMailConfig.SelectNotSearch;
      else
         muptr->SelectNotSearch = rdptr->SearchData.SelectNotSearch;
      rdptr->SearchData.SearchCount =
         rdptr->SearchData.HitCount =
         rdptr->SearchData.FirstIdSearched =
         rdptr->SearchData.LastIdSearched = 0;
      time (&StartSeconds);
   }
   else
      rdptr->SearchData.SearchSeconds = 0;

   CallMailMessageList (muptr);
   if (VMSnok (muptr->MailVmsStatus))
   {
      /* shouldn't get any of this nonsense with vanilla callable Mail */
      if (!uoptr->PmdfImap) return;

      /*
         FISH is used to indicate a non-existant PMDF IMAP MAIL.MAI
         Folders can non-exist because they are listed in PMDF IMAP files.
      */
      if (muptr->MailVmsStatus != SS$_FISH &&
          muptr->MailVmsStatus != MAIL$_NOTEXIST) return;
      muptr->MailVmsStatus = SS$_NORMAL;
   }

   if (rdptr->SearchData.InProgress)
   {
      time (&EndSeconds);
      rdptr->SearchData.SearchSeconds = EndSeconds - StartSeconds;
      muptr->SearchFunction = NULL;
      muptr->SearchDataPtr = NULL;
      muptr->ListMessageIdMax = 0;
      /* restore if the process priority was changed during the search */
      SearchRestorePriority ();
   }

   if (Debug)
      fprintf (stdout, "%%X%08.08X MessageListCount:%d\n",
               muptr->MailVmsStatus, muptr->MessageListCount);

   if (rdptr->PrivateAccess)
      fldptr = FolderDescribe (rdptr->MailFileName, rdptr->FolderName);
   else
   if (rdptr->PublicWildcard)
      fldptr = HTML_ESCAPE(rdptr->FolderName);
   else
      fldptr = rdptr->PublicAccessString;

   if (VMSnok (muptr->MailVmsStatus))
   {
      if (muptr->MailVmsStatus == MAIL$_OPENIN && muptr->MailFileDeletedCount)
         StatusMessage (FI_LI, -1, LangFor("mail_file_deleted"),
                        rdptr->MailFileName);
      else
      if (muptr->MailVmsStatus == MAIL$_OPENIN && vmptr->ExtIdLength)
         StatusMessage (FI_LI, 1, "MAIL: %s %s",
                        SysGetMsg(muptr->MailVmsStatus), vmptr->ExtId);
      else
      if (muptr->MailVmsStatus == MAIL$_NOTEXIST)
         StatusMessage (FI_LI, 0, LangFor("folder_notexist"),
                        HTML_ESCAPE(fldptr));
      else
         StatusMessage (FI_LI, 1, "MAIL: %s.",
                        SysGetMsg(muptr->MailVmsStatus));

      /* not a razoo */
      rdptr->RangeIdStart = rdptr->RangeIdCount =
         rdptr->FolderMessageCount = 0;
   }
   else
   {
      if (rdptr->SearchData.InProgress)
      {
         sptr = MessageBuffer;
         sptr += sprintf (sptr, LangFor("search_searched"),
                          HTML_ESCAPE(fldptr),
                          vmptr->MessageSelectedCount,
                          rdptr->SearchData.SearchCount,
                          HTML_ESCAPE(rdptr->SearchData.Criteria));
         if (rdptr->SearchData.SearchSeconds >= 10)
            sprintf (sptr, "&nbsp; %s: %02.02d:%02.02d.",
                     LangFor("duration"),
                     rdptr->SearchData.SearchSeconds / 60,
                     rdptr->SearchData.SearchSeconds % 60);
         StatusMessage (FI_LI, 0, "%s", MessageBuffer);
      }
      else
      {
         CGIVARNULL (cptr, "FORM_LIST_DEST_CHANGE");
         if (cptr && *cptr) CGIVARNULL (cptr, "FORM_FOLDER_LIST_DEST");
         if (cptr && *cptr)
         {
            fldptr = FolderDescribe (NULL, cptr);
            StatusMessage (FI_LI, -1, LangFor("folder_dest"), fldptr);
         }
         else
         {
            /* a folder has been opened */
            status = CallMailUserDiskQuota (muptr);
            if (VMSnok (status))
               StatusMessage (FI_LI, 1, "DISK-QUOTA: %s.", SysGetMsg(status));
            else
            {
               sprintf (MessageBuffer, LangFor("folder_opened"),
                        HTML_ESCAPE(fldptr), vmptr->MessageSelectedCount);
               StatusMessage (FI_LI, 0, "%s%s",
                              MessageBuffer, MessageDiskQuotaUsed(rdptr));
            }
         }
      }

      /* how many messages are in the folder */
      rdptr->FolderMessageCount = vmptr->MessageSelectedCount;
   }

   if (rdptr->MsgIdListPtr)
   {
      /* it is also used to store checkbox numbers */
      CgiLibVeeMemFree (rdptr->MsgIdListPtr);
      rdptr->MsgIdListPtr = NULL;
   }
   rdptr->MsgIdListCount = muptr->MessageListCount;
   if (rdptr->MsgIdListCount)
   {
      rdptr->MsgIdListPtr = sptr =
         CgiLibVeeMemCalloc (rdptr->MsgIdListCount * 32);
      if (!rdptr->MsgIdListPtr) ErrorExit (vaxc$errno, FI_LI);
      cptr = "";
      for (vmlptr = muptr->MessageList; vmlptr; vmlptr = vmlptr->NextPtr)
      {
         sptr += sprintf (sptr, "%s%d.%d",
                          cptr, vmlptr->CurrentId, vmlptr->MessageHash);
         cptr = ",";
      }
   }
   else
      rdptr->MsgIdListPtr = "";
}

/*****************************************************************************/
/*
Display the basic message folder and message list page.
*/

void MessageFolderList (REQUEST_DATA *rdptr)

{
   int  cnt, idx, status;

   BOOL  BoxChecked,
         DraftItemsFolder,
         ListDestChange,
         SentItemsFolder;
   char  *cptr, *sptr, *zptr;
   char  AddrBuffer [256],
         FolderDest [256],
         MsgCbxString [32],
         PreviewUri [512],
         SubjBuffer [256];
   USER_OPTIONS  *uoptr;
   VMS_MAIL_MSG  *vmptr;
   VMS_MAIL_USER  *muptr;

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

   if (WatchEnabled) WatchThis (FI_LI, "MessageFolderList()");

   muptr = &VmsMailUser;
   uoptr = &rdptr->UserOptions;
   vmptr = &muptr->VmsMailMsg;

   fprintf (stdout,
"<p class=\"css_p06\">\n\
<table width=\"100%%\" cellpadding=\"1\" cellspacing=\"6\" \
class=\"css_panel_1\" style=\"white-space:nowrap;\"><tr>\n\
\n");

   /******************/
   /* message ranges */
   /******************/

   MessageRangeSelect (rdptr);

   /**************************/
   /* message action buttons */
   /**************************/

   fprintf (stdout, "<!-- message action begin -->\n");

   fprintf (stdout,
"<script language=\"JavaScript\">\n\
var lang_All = \"%s\";\n\
var lang_AllMessages = \"%s\";\n\
</script>\n\
<td style=\"text-align:right;vertical-align:top;white-space:nowrap;\">\n\
<span class=\"css_nw\">\n\
<input class=\"css_button_panel_1\" type=\"submit\" \
name=\"msg_copy\" value=\"%s &gt;&gt;\" \
onclick=\"return checkAllMsg(\'%s\')\">\n\
<input class=\"css_button_panel_1\" type=\"submit\" \
name=\"msg_move\" value=\"%s &gt;&gt;\" \
onclick=\"return checkAllMsg(\'%s\')\">\n",
            LANG_FOR_CONFIRM("all"),
            LANG_FOR_CONFIRM("all_messages"),
            LangFor("copy"), LANG_FOR_CONFIRM("copy"),
            LangFor("move"), LANG_FOR_CONFIRM("move"));

   MessageDestSelector (rdptr);

   if (!strcmp (rdptr->FolderName, uoptr->FolderWasteItems) &&
       rdptr->FolderMessageCount)
      fprintf (stdout,
"<input class=\"css_button_panel_1\" type=\"submit\" \
name=\"msg_empty\" value=\"%s\" \
onclick=\"return checkAllMsg(\'%s\')\">\n",
               LangFor("empty"), LANG_FOR_CONFIRM("empty"));
   else
      fprintf (stdout,
"<input class=\"css_button_panel_1\" type=\"submit\" \
name=\"msg_waste\" value=\"%s\" \
onclick=\"return checkAllMsg(\'%s\')\">\n",
               LangFor("waste"), LANG_FOR_CONFIRM("waste"));

   fprintf (stdout,
"<input class=\"css_button_panel_1\" type=\"submit\" \
name=\"msg_delete\" value=\"%s\" \
onclick=\"return checkAllMsg(\'%s\')\">\n\
</span>\n\
</td></tr>\n\
<!-- message action end -->\n\n",
            LangFor("delete"), LANG_FOR_CONFIRM("delete"));

   /******************/
   /* folder buttons */
   /******************/

   if (uoptr->FolderButtons)
   {
      fprintf (stdout,
"<tr><td colspan=\"2\"><table width=\"100%%\" cellpadding=\"0\" \
cellspacing=\"0\" border=\"0\">\n\
\n\
<!-- folder buttons begin -->\n\
<tr><td width=\"5%%\" align=\"left\" valign=\"top\" \
style=\"padding:0.3em;padding-right:1.2em;\
font-size:0.8em;white-space:nowrap;\">\n");

      FolderButtonListEach (rdptr);

      fprintf (stdout,
"</td>\n\
<!-- folder buttons end -->\n\
\n\
<!-- message list begin -->\n\
<td width=\"95%%\" align=\"left\" valign=\"top\">\n");
   }
   else
      fprintf (stdout,
"<!-- message list begin -->\n\
<tr><td colspan=\"2\" align=\"left\" valign=\"top\">\n");

   if (!muptr->MessageListCount)
   {
      /****************/
      /* empty folder */
      /****************/

      rdptr->RangeIdStart = rdptr->RangeIdCount = 0;

      if (uoptr->FolderButtons) fprintf (stdout, "</td></tr>\n</table>\n");

      fprintf (stdout,
"</td></tr>\n\
<!-- message list end -->\n\n\
</table>\n\n");

      MessageListFooter ();

      return;
   }

   /********************/
   /* list of messages */
   /********************/

   DraftItemsFolder = SentItemsFolder = FALSE;
   if (!strcmp (rdptr->MailFileName, "MAIL"))
   {
      if (!strcmp (muptr->VmsMailFolderName, uoptr->FolderDraftItems))
         DraftItemsFolder = TRUE;
      if (!strcmp (muptr->VmsMailFolderName, uoptr->FolderSentItems))
         if (strcmp (muptr->VmsMailFolderName, "MAIL") &&
             strcmp (muptr->VmsMailFolderName, "NEWMAIL"))
            SentItemsFolder = TRUE;
   }

   CGIVARNULL (cptr, "FORM_LIST_DEST_CHANGE");
   if (cptr && *cptr) CGIVARNULL (cptr, "FORM_FOLDER_LIST_DEST");
   if (cptr && *cptr)
      ListDestChange = TRUE;
   else
      ListDestChange = FALSE;

   if (!DraftItemsFolder &&
       uoptr->PreviewAfter)
   {
      if (rdptr->PrivateAccess)
         sprintf (PreviewUri, "%s/%s/%s/",
                  rdptr->FormAction,
                  rdptr->MailFileName,
                  HTML_ESCAPE(rdptr->FolderName));
      else
      if (rdptr->PublicWildcard)
         sprintf (PreviewUri, "%s%s/",
                  rdptr->FormAction,
                  HTML_ESCAPE(rdptr->FolderName));
      else
         sprintf (PreviewUri, "%s/", rdptr->FormAction);

      fprintf (stdout,
"<script language=\"JavaScript\">\n\
var soy_TargetMsgNum = %d;\n\
var soy_TargetPreviewTimer = null;\n\
var soy_TargetPreviewUri = \'%s\';\n\
var soy_TargetMsgDisplay = \'%s\';\n\
</script>\n\
<iframe id=\"preview_iframe\" \
style=\"width:0px;height:0px;border:0px;display:none;\" \
src=\"javascript:true;\"></iframe>\n",
               uoptr->PreviewAfter,
               PreviewUri,
               (rdptr->UserAgent == SOY_USER_AGENT_MSIE) ?
                  /* MSIE being its usual contrarian self! */
                  "inline" :
                  "table-row");
   }

   fprintf (stdout,
"<input type=\"hidden\" name=\"msg_btn_popup\" id=\"msg_btn_popup\" value=\"\">\n\
<input type=\"hidden\" name=\"msg_range_list\" id=\"msg_range_list\" value=\"%s\">\n\
<table cellpadding=\"0\" cellspacing=\"5\" border=\"0\">\n\
<tr><td>",
            rdptr->MsgIdListPtr);

   /* careful how this is done or Opera produces a checkbox artifact */
   fprintf (stdout,
"<script language=\"JavaScript\">\
document.write(\'<input type=\"checkbox\" \
name=\"msg_cbx_page\" id=\"msg_cbx_page\" value=\"1\" \
onclick=\"return checkAllId()\">\')\
</script>");

   if (DraftItemsFolder)
   {
      cptr = LangFor("compose_save_last") ;
      sptr = LangFor("to") ;
   }
   else
   if (SentItemsFolder)
   {
      cptr = LangFor("sent") ;
      sptr = LangFor("to") ;
   }
   else
   {
      cptr = LangFor("received") ;
      sptr = LangFor("from") ;
   }

   fprintf (stdout,
"</td><td colspan=\"4\"></td>\
<td class=\"css_nwtalbu\">%s</td>\
<td class=\"css_nwtalbu\">%s</td>\
<td class=\"css_nwtalbu\">%s</td>\
</tr>\n",
            cptr, sptr, LangFor("subject"));

   if (uoptr->MessageOrder < 0)
   {
      /* find the last entry in the list */
      for (vmptr = muptr->MessageList;
           vmptr && vmptr->NextPtr;
           vmptr = vmptr->NextPtr);
   }
   else
      vmptr = muptr->MessageList;
   
   while (vmptr)
   {
      if (Debug)
         fprintf (stdout,
"%%X%08.08X id:%d size:%d hash:%d\n|%s|\n|%s|\n|%s|\n|%s|\n|%s|\n\n",
                  muptr->MailVmsStatus,
                  vmptr->CurrentId, vmptr->SizeInRecords, vmptr->MessageHash,
                  vmptr->Date,
                  DraftItemsFolder || SentItemsFolder ? vmptr->To : vmptr->From,
                  vmptr->Subject,
                  vmptr->To, vmptr->Cc);

      zptr = (sptr = AddrBuffer) + sizeof(AddrBuffer)-1;
      if (DraftItemsFolder || SentItemsFolder)
         for (cptr = vmptr->To; *cptr && sptr < zptr; *sptr++ = *cptr++);
      else
         for (cptr = vmptr->From; *cptr && sptr < zptr; *sptr++ = *cptr++);
      *sptr = '\0';
      cnt = MimeDecIsoString (AddrBuffer);
      if (cnt > 40) *((unsigned long*)(AddrBuffer+40)) = '...\0';

      zptr = (sptr = SubjBuffer) + sizeof(SubjBuffer)-1;
      if (vmptr->Subject[0])
         cptr = vmptr->Subject;
      else
         cptr = LangFor("(none)");
      while (*cptr && sptr < zptr) *sptr++ = *cptr++;
      *sptr = '\0';
      cnt = MimeDecIsoString (SubjBuffer);
      if (cnt > MSG_LIST_SUBJ_ELLIPSIS)
      {
         for (sptr = SubjBuffer + MSG_LIST_SUBJ_ELLIPSIS;
              sptr > SubjBuffer && *sptr != ' ';
              sptr--);
         if (sptr == SubjBuffer) sptr = SubjBuffer + MSG_LIST_SUBJ_ELLIPSIS;
         *((unsigned long*)sptr) = '...\0';
      }
      BoxChecked = FALSE;
      if (ListDestChange)
      {
         /* reset checkbox if the destination list was refreshed */
         sprintf (MsgCbxString, "FORM_MSG_CBX_%d", vmptr->CurrentId);
         CGIVARNULL (cptr, MsgCbxString);
         if (cptr) BoxChecked = TRUE;
      }

      /* this mix of styles to get both Mozilla and MSIE to look right! */
      fprintf (stdout,
"<tr id=\"msg_pop_%d\" class=\"css_nw%s\" \
onmouseover=\"targetMsgOn(%d)\" \
onmouseout=\"targetMsgOff(%d)\">\
<td><input type=\"checkbox\" name=\"msg_cbx_%d\" value=\"1\"%s></td>\
<td onmouseover=\"msgPreviewOn(event,%d,%d)\" \
onmouseout=\"msgPreviewOff(false,%d)\" \
onclick=\"msgPreviewOff(false,%d)\">\
<input class=\"css_button_panel_1\" style=\"width:3.5em;\" \
type=\"submit\" name=\"msg_btn\" value=\"%04d\" \
onclick=\"targetPopup(0);\">\
<script language=\"JavaScript\">popupMsgBtn(%d)</script>",
               vmptr->MessageId,
               vmptr->MessageFlags & MAIL$M_NEWMSG ? "b" : "",
               vmptr->MessageId,
               vmptr->MessageId,
               vmptr->CurrentId,
               BoxChecked ? "  checked" : "",
               vmptr->CurrentId,
               vmptr->MessageHash,
               vmptr->MessageId,
               vmptr->MessageId,
               vmptr->MessageId,
               vmptr->CurrentId);

      fprintf (stdout, "</td><!-- %%X%04.04X -->", vmptr->MessageFlags);

      if (SoyMailConfig.MessageFlags && vmptr->MessageFlags)
         fprintf (stdout, "<td>%s</td><td>%s</td><td>%s</td>",
                  ((SoyMailConfig.MessageFlags & MAIL$M_REPLIED) &&
                     (vmptr->MessageFlags & MAIL$M_NEWMSG)) ? FLAG_NEWMSG : "",
                  ((SoyMailConfig.MessageFlags & MAIL$M_MARKED) &&
                     (vmptr->MessageFlags & MAIL$M_MARKED)) ? FLAG_MARKED : "",
                  ((SoyMailConfig.MessageFlags & MAIL$M_MARKED) &&
                     (vmptr->MessageFlags & MAIL$M_REPLIED)) ? FLAG_REPLIED : "");
      else
         fprintf (stdout, "<td colspan=\"3\">&nbsp;</td>");

      fprintf (stdout,
"<td class=\"css_nwtar\"><span id=\"msg_date_%d\" \
onclick=\"targetMsgPop(%d);\">%17.17s</span></td>\
<td class=\"css_nwtal\"><span id=\"msg_from_%d\" \
onclick=\"targetMsgPop(%d);\">%s</span></td>\
<td class=\"css_nwtal\"><span id=\"msg_subj_%d\" \
onclick=\"targetMsgPop(%d);\">%s</span></td>\
</tr>\n\
<tr id=\"preview_row_%d\" style=\"display:none;\">\
<td colspan=\"5\"></td>\
<td id=\"preview_msg_%d\" class=\"css_preview\" colspan=\"3\"></td>\
<td width=\"100%%\"></td></tr>\n",
               vmptr->CurrentId,
               vmptr->CurrentId,
               InetMailRfcVmsDate(vmptr->Date),
               vmptr->CurrentId,
               vmptr->CurrentId,
               HTML_ADDR(AddrBuffer),
               vmptr->CurrentId,
               vmptr->CurrentId,
               HTML_TEXT(SubjBuffer),
               vmptr->CurrentId,
               vmptr->CurrentId);

      if (uoptr->Accessability1)
         fprintf (stdout,
"<tr>\
<td colspan=\"5\"></td>\
<td colspan=\"3\"><hr class=\"css_accessability_1\"></td>\
</tr>\n");

      if (uoptr->MessageOrder < 0)
         vmptr = vmptr->PrevPtr;
      else
         vmptr = vmptr->NextPtr;
   }

   
   if (!rdptr->SearchData.InProgress)
      fprintf (stdout,
"<tr class=\"css_nw\">\
<td><input type=\"checkbox\" \
name=\"msg_cbx_all\" id=\"msg_cbx_all\" value=\"1\"></td>\
<td colspan=\"7\" style=\"font-size:0.8em;\">%s %d %s</td></tr>\n",
               LangFor("all"), rdptr->FolderMessageCount,
               LangFor("all_messages"));

   fprintf (stdout, "</table>\n");

   if (uoptr->FolderButtons) fprintf (stdout, "</td></tr>\n</table>\n");

   fprintf (stdout,
"</td></tr>\n\
<!-- message list end -->\n\n\
</table>\n\n");

   MessageListFooter ();
}

/*****************************************************************************/
/*
Provide the message [copy][move] destination selector and input field(s).
*/

void MessageDestSelector (REQUEST_DATA *rdptr)

{
   char  *cptr;
   USER_OPTIONS  *uoptr;
   VMS_MAIL_USER  *muptr;

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

   if (WatchEnabled) WatchThis (FI_LI, "MessageDestSelector()");

   muptr = &VmsMailUser;
   uoptr = &rdptr->UserOptions;

   fprintf (stdout,
"<input type=\"hidden\" name=\"list_dest_change\" \
id=\"list_dest_change\" value=\"\">\n\
<select class=\"css_select_1\" name=\"folder_list_dest\" \
id=\"folder_list_dest\"%s onchange=\"return folderDestListChanged()\">\n",
            muptr->DestFolderMaxLen > 20 ? " style=\"width:12em;\"" : "");

   FolderDestListEach (rdptr);

   fprintf (stdout, "</select>\n");

   if (uoptr->AllMailFiles)
   {
      CGIVAR (cptr, "FORM_FILE_TEXT_DEST");

      fprintf (stdout,
"<input class=\"css_text_1\" type=\"text\" \
name=\"file_text_dest\" id=\"file_text_dest\" \
size=\"10\" maxlength=\"39\" value=\"%s\" \
onclick=\"this.select()\">\
<span style=\"padding-left:1px;padding-right:1px;\">/</span>",
               HTML_ESCAPE(cptr));
   }

   CGIVAR (cptr, "FORM_FOLDER_TEXT_DEST");

   fprintf (stdout,
"<input class=\"css_text_1\" type=\"text\" \
name=\"folder_text_dest\" id=\"folder_text_dest\" \
size=\"10\" maxlength=\"39\" value=\"%s\" \
onclick=\"this.select()\">\n",
            HTML_ESCAPE(cptr));
}

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

void MessageListFooter ()

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

   if (!SoyMailConfig.MessageListFooter) return;

   fprintf (stdout,
"<!-- site footer begin -->\n\
<p class=\"css_p06\">\n\
<table width=\"100%%\" cellpadding=\"1\" cellspacing=\"5\" \
class=\"css_panel_1\">\n\
<tr><td align=\"left\" valign=\"top\" style=\"white-space:normal;\">\
%s\n\
</td></tr>\n\
</table>\n\
<!-- site footer end -->\n\n",
            SoyMailConfig.MessageListFooter);
}

/*****************************************************************************/
/*
Copy, with optional delete (i.e. a move), from one folder to another.
*/

void MessageCopy
(
REQUEST_DATA *rdptr,
BOOL DeleteAfterCopy
)
{
   int  cnt, idx;
   int  *midptr;
   char  *cptr;
   USER_OPTIONS  *uoptr;
   VMS_MAIL_USER  *muptr;

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

   uoptr = &rdptr->UserOptions;
   muptr = &VmsMailUser;

   if (WatchEnabled)
      WatchThis (FI_LI, "MessageCopy() !AZ !AZ/!AZ !AZ/!AZ !AZ",
                 DeleteAfterCopy ? "MOVE" : "COPY",
                 rdptr->MailFileName, rdptr->FolderName,
                 rdptr->DestMailFileName, rdptr->DestFolderName,
                 rdptr->MsgIdListPtr ? rdptr->MsgIdListPtr : "(null)");

   if (!strcmp (rdptr->MailFileName, rdptr->DestMailFileName) &&
       !strcmp (rdptr->FolderName, rdptr->DestFolderName))
   {
      StatusMessage (FI_LI, 1, "%s", LangFor("folder_same"));
      return;
   }

   if (!strcmp (rdptr->DestMailFileName, "MAIL"))
   {
      if (!strcmp (rdptr->DestFolderName, uoptr->FolderDraftItems))
      {
         StatusMessage (FI_LI, 1, "%s", LangFor("folder_cannot"));
         return;
      }
   }

   muptr->MsgIdListPtr = rdptr->MsgIdListPtr;
   muptr->MsgIdListCount = rdptr->MsgIdListCount;

   if (!muptr->MsgIdListCount)
   {
      StatusMessage (FI_LI, 1, "%s", LangFor("none_selected"));
      return;
   }

   if (!muptr->VmsMailDestFolderNameLength)
   {
      StatusMessage (FI_LI, 1, "%s", LangFor("dest_not_specified"));
      return;
   }

   if (DeleteAfterCopy)
      CallMailMessageMove (muptr);
   else
      CallMailMessageCopy (muptr);
   if (Debug) fprintf (stdout, "%%X%08.08X\n", muptr->MailVmsStatus);

   if (DeleteAfterCopy) rdptr->MessageCountDelta = muptr->MessageCopiedCount;

   if (VMSnok (muptr->MailVmsStatus))
   {
      if (muptr->MailVmsStatus == SS$_BADCHECKSUM ||
          muptr->MailVmsStatus == MAIL$_NOMOREMSG)
         StatusMessage (FI_LI, 1, "%s", LangFor("message_checksum"));
      else
         StatusMessage (FI_LI, 1, "MAIL: %s", SysGetMsg(muptr->MailVmsStatus));
   }
   else
   {
      /* just in case we've tinkered with NEWMAIL */
      rdptr->NewMessages = muptr->VmsMailNewMessages;

      cptr = FolderDescribe (rdptr->DestMailFileName, rdptr->DestFolderName);

      StatusMessage (FI_LI, 0, "%d %s &quot;%s&quot;.",
                     muptr->MessageCopiedCount,
                     DeleteAfterCopy ? LangFor("msgs_moved") :
                                       LangFor("msgs_copied"),
                     HTML_ESCAPE(cptr));
   }

}

/*****************************************************************************/
/*
Delete the specified messages from the folder.
*/

void MessageDelete (REQUEST_DATA *rdptr)

{
   int  cnt, idx;
   int  *midptr;
   char  *cptr;
   VMS_MAIL_USER  *muptr;

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

   muptr = &VmsMailUser;

   if (WatchEnabled)
      WatchThis (FI_LI, "MessageDelete() !AZ",
                 rdptr->MsgIdListPtr ? rdptr->MsgIdListPtr : "(null)");

   strcpy (muptr->VmsMailFileName, rdptr->MailFileName);
   muptr->VmsMailFileNameLength = rdptr->MailFileNameLength;

   muptr->MsgIdListPtr = rdptr->MsgIdListPtr;
   muptr->MsgIdListCount = rdptr->MsgIdListCount;

   if (!muptr->MsgIdListCount)
   {
      StatusMessage (FI_LI, 1, "%s", LangFor("none_selected"));
      return;
   }

   CallMailMessageDelete (muptr);
   if (Debug) fprintf (stdout, "%%X%08.08X\n", muptr->MailVmsStatus);
   rdptr->MessageCountDelta = muptr->MessageDeletedCount;

   if (VMSnok (muptr->MailVmsStatus))
   {
      if (muptr->MailVmsStatus == SS$_BADCHECKSUM ||
          muptr->MailVmsStatus == MAIL$_NOMOREMSG)
         StatusMessage (FI_LI, 1, "%s", LangFor("message_checksum"));
      else
         StatusMessage (FI_LI, 1, "MAIL: %s.", SysGetMsg(muptr->MailVmsStatus));
   }
   else
   {
      /* just in case we've tinkered with NEWMAIL */
      rdptr->NewMessages = muptr->VmsMailNewMessages;

      cptr = FolderDescribe (rdptr->MailFileName, rdptr->FolderName);

      StatusMessage (FI_LI, 0, "%d %s &quot;%s&quot;.",
                     muptr->MessageDeletedCount,
                     LangFor("msgs_deleted"),
                     HTML_ESCAPE(cptr));
   }
}

/*****************************************************************************/
/*
Determine script directionlity in a message read context.
Functionality somewhat duplicated in ComposeCharSetDir().
*/

char* MessageCharSetDir (char *CharSetPtr)

{
   char  *cptr, *sptr, *zptr;
   char  CharSetName [128];

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

   if (Debug) fprintf (stdout, "MessageCharSetDir() |%s|\n", CharSetPtr);

   /* buttons from message pages */
   CGIVARNULL (cptr, "FORM_MSG_LTR");
   if (cptr) return ("ltr");
   CGIVARNULL (cptr, "FORM_MSG_RTL");
   if (cptr) return ("rtl");

   if (!CharSetPtr || !CharSetPtr[0]) return ("ltr");

   if (!(cptr = SoyMailConfig.CharSetsRightToLeft)) return ("ltr");

   while (*cptr)
   {
      while (*cptr == ',' || isspace(*cptr)) cptr++;
      if (!*cptr) break;
      zptr = (sptr = CharSetName) + sizeof(CharSetName)-1;
      while (*cptr && *cptr != ',' && !isspace(*cptr) && sptr < zptr)
         *sptr++ = *cptr++;
      *sptr = '\0';
      if (strsame (CharSetName, CharSetPtr, -1)) return ("rtl");
   }

   return ("ltr");
}

/*****************************************************************************/
/*
Return true is the character set is known to be left-to-right only.
*/

BOOL MessageCharSetLtr (char *CharSetPtr)

{
   char  *cptr, *sptr, *zptr;
   char  CharSetName [128];

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

   if (Debug) fprintf (stdout, "MessageCharSetLtr() |%s|\n", CharSetPtr);

   /* no charset then assume left-to-right */
   if (!CharSetPtr || !CharSetPtr[0]) return (TRUE);

   cptr = CHARSETS_COMMONLY_LTR;
   while (*cptr)
   {
      while (*cptr == ',' || isspace(*cptr)) cptr++;
      if (!*cptr) break;
      zptr = (sptr = CharSetName) + sizeof(CharSetName)-1;
      while (*cptr && *cptr != ',' && !isspace(*cptr) && sptr < zptr)
         *sptr++ = *cptr++;
      *sptr = '\0';
      if (strsame (CharSetName, CharSetPtr, -1)) return (TRUE);
   }

   return (FALSE);
}

/*****************************************************************************/
/*
Just wrap a call to CallMailMessageFlags() to set/reset header flags.
*/

void MessageFlags
(
REQUEST_DATA *rdptr,
unsigned short MailFlags
)
{
   VMS_MAIL_MSG  *vmptr;
   VMS_MAIL_USER  *muptr;

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

   muptr = &VmsMailUser;

   if (WatchEnabled) WatchThis (FI_LI, "MessageFlags() %X!4XL", MailFlags);

   vmptr = &muptr->VmsMailMsg;

   vmptr->MessageId = rdptr->MessageId;
   vmptr->ConfirmHash = rdptr->MessageHash;

   CallMailMessageFlags (muptr, vmptr, MailFlags);

   if (VMSok (muptr->MailVmsStatus))
      StatusMessage (FI_LI, 0, "%s", "Message flagged.");
   else
      StatusMessage (FI_LI, 1, "MAIL: %s", SysGetMsg(muptr->MailVmsStatus));
}

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

char* MessageDiskQuotaUsed (REQUEST_DATA *rdptr)

{
   static char  DiskQuotaUsed [96] = "&nbsp;";

   int  PercentUsed;
   VMS_MAIL_USER  *muptr;

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

   muptr = &VmsMailUser;

   if (muptr->DiskQuotaPerm == (unsigned long)-1)
   {
      if (WatchEnabled) WatchThis (FI_LI, "DISK-QUOTA disabled");
      return ("");
   } 

   if (muptr->DiskQuotaPerm)
      PercentUsed = muptr->DiskQuotaUsed * 100 / muptr->DiskQuotaPerm;
   else
      PercentUsed = 0;

   if (WatchEnabled)
      WatchThis (FI_LI, "DISK-QUOTA !UL/!UL blocks !UL/!UL MB !UL%",
                 muptr->DiskQuotaUsed, muptr->DiskQuotaPerm,
                 muptr->DiskQuotaUsed >> 11, muptr->DiskQuotaPerm >> 11,
                 PercentUsed);

   if (PercentUsed < SoyMailConfig.DiskQuotaPercent) return ("");

   sprintf (DiskQuotaUsed+6, LangFor("disk_quota_used"),
            muptr->DiskQuotaUsed >> 11, muptr->DiskQuotaPerm >> 11,
            PercentUsed);

   return (DiskQuotaUsed);
}

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

