/*****************************************************************************/
/*
                                  draft.c

As of v1.3.0 soyMAIL draft messages are stored in a VMS Mail folder, by default
named "Draft Items", though configurable as a user option.  These messages are
[saved] into this folder by creating a sequential mail file which is then
copied into the folder (see DraftSave()).  Messages are [restore]d directly
from the "Draft Items" folder by selecting the message item button (either open
in current or in new window) reads the message, passes it to DraftRestore() to
process the soyMAIL-specific draft data stored in it, then opens it in the
compose window.  See DraftPurge() for a description of what is required to
maintain revisions of drafts in the Mail "Draft Items" folder.


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
---------------
04-OCT-2023  MGD  DraftSave() subject MimeEncIsoString()
25-SEP-2015  MGD  compose Confirm[x] and |ConfirmBeforeSend|
10-DEC-2013  MGD  bugfix; DraftPurge() finalise with mail$mailfile_end()
12-NOV-2011  WK   bugfix; force references into multiple lines in DraftSave()
01-NOV-2008  MGD  DraftSave() and DraftRestore() in-reply-to and references
04-AUG-2007  MGD  DraftSave() and DraftRestore() [charset] to propagate
                    any compose message character set
12-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 <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

/* VMS related header files */
#include <maildef.h>
#include <mailmsgdef.h>
#include <mail$routines.h>
#include <ssdef.h>
#include <stsdef.h>

/* application header file */
#include <soymail.h>
#include <address.h>
#include <draft.h>
#include <config.h>

#define FI_LI "DRAFT", __LINE__

/* global storage */

/* prototypes */

int sys$fao (__unknown_params);

/* external storage */

extern BOOL  Debug,
             WatchEnabled;

extern char  CurrentVmsTimeString[],
             SoftwareVn[];

extern CONFIG_DATA SoyMailConfig;
extern VMS_MAIL_USER VmsMailUser;

extern VMS_ITEM_LIST3  CallMailNullItem;
extern VMS_ITEM_LIST3  CallMailNoSignalItem[];

/*****************************************************************************/
/*
Save a copy of the current composition (draft) into the "Draft Items" folder. 
This is done by writing the content to a temporary sequential mail file and
then copying that into a mail folder.

It gets a little complicated because each of these copies is entered as a
separate message and for drafts we really only want/need to keep the latest
copy.  Can't use the message header to identify the same draft because the
detail in that may change from edit-to-edit (subject or desination address(es)
for example).  The approach taken is to generate a hash of the header detail
and use this to identify previous versions of the same draft.  These are then
purged as required (see DraftPurge() for further detail).

Constrain the header strings used in the VMS format message to provide a
maximum of 255 characters otherwise CallMailCopyFile() will barf.

The saved draft message body contains soyMAIL-specific data in plain-text that
are used to reconstruct the compose page it is saved from.  As soyMAIL does not
provide for 'reading' draft messages this is not an issue.  The content should
be (almost) obvious if another email agent is used and the data even used to manually
reconstruct the draft content.

Quoted printable encoding is used for the message text in case any one record
exceeds the 255/2048 limits.  This can commonly occur with <textarea>s where
WRAP is VIRTUAL or OFF, and is never set to PHYSICAL (the case for soyMAIL) and
users enter long 'paragraphs' without using the return key.
*/

int DraftSave (REQUEST_DATA *rdptr)

{
   static char  DateTime [32];
   static $DESCRIPTOR (DateTimeFaoDsc, "!20%D\0");

   int  retval, status;
   char  *cptr, *fnptr, *sptr, *zptr,
         *FileNamePtr,
         *DateTimePtr;
   char  Cc255 [256],
         Subj255 [256-14],
         To255 [256];
   FILE  *fp;
   $DESCRIPTOR (DateTimeDsc, DateTime);
   COMPOSE_DATA  *cdptr;
   USER_OPTIONS  *uoptr;
   VMS_MAIL_USER  *muptr;

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

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

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

   if (!uoptr->FolderDraftItems[0]) ErrorExit (SS$_BUGCHECK, FI_LI);

   status = SS$_NORMAL;

   if (!cdptr->DraftCreated[0])
   {
      sys$fao (&DateTimeFaoDsc, 0, &DateTimeDsc, 0);
      strcpy (cdptr->DraftCreated, DateTime);
   }

   /* need to limit to a VMS Mail acceptable 255 */
   zptr = (sptr = To255) + sizeof(To255)-1;
   for (cptr = cdptr->ToPtr;
        *cptr && *cptr != '\n' && sptr < zptr;
        *sptr++ = *cptr++);
   *sptr = '\0';

   /* need to limit to a VMS Mail acceptable 255 */
   zptr = (sptr = Cc255) + sizeof(Cc255)-1;
   for (cptr = cdptr->CcPtr;
        *cptr && *cptr != '\n' && sptr < zptr;
        *sptr++ = *cptr++);
   *sptr = '\0';

   /* need to limit to a VMS Mail acceptable 255 */
   zptr = (sptr = Subj255) + sizeof(Subj255)-1;
   for (cptr = MimeEncIsoString (cdptr->SubjPtr, cdptr->CharSetPtr);
        *cptr && sptr < zptr;
        *sptr++ = *cptr++);
   *sptr = '\0';

   cdptr->CharSetPtr = ComposeSetCharSet (rdptr);

   FileNamePtr = SoyMailTempFileName();

   fp = fopen (FileNamePtr, "w", "rfm=var", "rat=cr", "mbc=32", "mbf=64");
   if (!fp) return (vaxc$errno);

   /* sequential mail file header (NOTE THE LEADING FORM-FEED!) */
   retval = fprintf (fp,
"\f\n\
From:\t%s%c\t%s\n\
To:\t%s\n\
CC:\t%s\n\
Subj:\t%s%c\t[[#%08.08X]]\n\
\n",
            /* separate date/time using a null which tends to mask it */
            rdptr->RemoteUser, 0, CurrentVmsTimeString, To255, Cc255,
            /* separate hash using a null which tends to mask it  */
            Subj255, 0, cdptr->DraftHash);
   if (retval < 0) status = vaxc$errno;

   /* soyMAIL draft data */
   retval = fprintf (fp,
"# this is a soyMAIL draft and contains soyMAIL-specific data\n\
[soyMAIL]  %s\n\
[created]  %s\n\
[updated]  %s\n\
[editor]  %s\n\
[rows]  %d\n\
[columns]  %d\n\
[wrap-at]  %d\n\
[charset]  %s\n\
[confirm]  %s\n\
# message data\n",
            SoftwareVn, cdptr->DraftCreated, CurrentVmsTimeString,
            cdptr->EditorType == COMPOSE_EDITOR_PLAIN ? "plain" : "HTML",
            cdptr->EditRows, cdptr->EditCols, cdptr->WrapAt,
            cdptr->CharSetPtr, cdptr->ConfirmBeforeSend ? "true" : "false");
   if (retval < 0) status = vaxc$errno;

   if (cdptr->InReplyToPtr)
   {
      fprintf (fp, "[in-reply-to]  ");
      ConfigWriteEscaped (fp, cdptr->InReplyToPtr);
   }

   if (cdptr->ReferencesPtr)
   {
      fprintf (fp, "[references]  ");
      /* relies on COMPOSE having re-parsed into space-separated tokens */
      for (cptr = sptr = cdptr->ReferencesPtr; *cptr; sptr = cptr)
      {
         while ( *cptr && *cptr != '\n' && *cptr != ' ' ) cptr++;
         fprintf (fp, "%*.*s\n", cptr-sptr, cptr-sptr, sptr);
         if (*cptr) cptr++;
      }
   }

   cptr = AddressListMassage (cdptr->ToPtr, FALSE);
   retval = fprintf (fp, "[to]  ");
   if (retval < 0) status = vaxc$errno;
   ConfigWriteEscaped (fp, cptr);

   cptr = AddressListMassage (cdptr->CcPtr, FALSE);
   fprintf (fp, "[cc]  ");
   ConfigWriteEscaped (fp, cptr);

   cptr = AddressListMassage (cdptr->BccPtr, FALSE);
   fprintf (fp, "[bcc]  ");
   ConfigWriteEscaped (fp, cptr);

   fprintf (fp, "[reply-to]  ");
   ConfigWriteEscaped (fp, cdptr->ReplyToPtr);

   /* subject MUST be immediately before text */
   fprintf (fp, "[subj]  ");
   ConfigWriteEscaped (fp, cdptr->SubjPtr);

   /* quoted printable in case any one record exceeds 255/2048 limits */
   retval = fprintf (fp, "[text]\n%s",
                     MimeEncQuotedPrintable (cdptr->TextPtr, -1));
   if (retval < 0) status = vaxc$errno;
   
   fclose (fp);

   if (VMSok (status))
      status = CallMailCopyFile (&VmsMailUser,
                                 "MAIL", uoptr->FolderDraftItems,
                                 FileNamePtr);

   if (VMSok (status)) status = DraftPurge (rdptr);

   if (VMSok (status))
      StatusMessage (FI_LI, 0, "%s%s", LangFor("compose_save_draft"),
                                       ComposeCharsetAlert(cdptr->CharSetPtr));
   else
      StatusMessage (FI_LI, 1, "%s: %s.",
                     uoptr->FolderDraftItems, SysGetMsg(status));

   while (!remove (FileNamePtr));
}

/*****************************************************************************/
/*
Purge any unnecessary copy(ies) of the current draft message leaving only the
latest.  Drafts are identified by a 32 bit hash (as hex) of the message header
'hidden' in the subject line.  Messages sharing the same hash are likely (not
guaranteed but as-good-as for these purposes) to be the same draft.  If the
message hash has not changed between this draft and the previous then delete
all but the latest in the folder.  If it has changed then all the previous may
be deleted.  Under normal circumstances there should only ever be one previous
instance of the draft because this purge should always be called.
*/

int DraftPurge (REQUEST_DATA *rdptr)

{
   int  status,
        VmsMailStatus;
   unsigned int  HashCurrent;
   unsigned long  MailFileContext,
                  MailFileNameLength,
                  MessageContext,
                  MessageId,
                  MessageSelected,
                  SelectedCount;
   char  HashSubstring [32],
         MailFileName [256];

   COMPOSE_DATA  *cdptr;
   VMS_MAIL_USER  *muptr;
   USER_OPTIONS  *uoptr;
   VMS_ITEM_LIST3  *itptr;
   /* reminder: these require one extra element for the null item */
   VMS_ITEM_LIST3  MailFileInItem [3];
   VMS_ITEM_LIST3  MessageBeginInItem [3];
   VMS_ITEM_LIST3  MessageBeginOutItem [3];
   VMS_ITEM_LIST3  MessageIdItem [3];
   VMS_ITEM_LIST3  MessageSelectItem [7];
   VMS_ITEM_LIST3  MessageSelectedItem [3];

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

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

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

   if (!muptr->VmsMailFullDirectoryLength) ErrorExit (SS$_BUGCHECK, FI_LI);
   if (!muptr->VmsMailFileNameLength) ErrorExit (SS$_BUGCHECK, FI_LI);

   MailFileNameLength =
      CallMailBuildMailFileName (muptr, "MAIL",
                                 MailFileName, sizeof(MailFileName));

   /*********************/
   /* mail file context */
   /*********************/

   MailFileContext = 0;
   status = mail$mailfile_begin (&MailFileContext,
                                 &CallMailNullItem,
                                 &CallMailNullItem);
   if (Debug) fprintf (stdout, "mail$mailfile_begin() %%X%08.08X\n", status);
   VmsMailStatus = status;

   if (VMSok (VmsMailStatus))
   {
      itptr = &MailFileInItem[0];
      memset (itptr, 0, sizeof(MailFileInItem));

      itptr->buf_len = strlen(MailFileName);
      itptr->item = MAIL$_MAILFILE_NAME;
      itptr->buf_addr = MailFileName;
      itptr++;

      itptr->item = MAIL$_NOSIGNAL;

      status = mail$mailfile_open (&MailFileContext,
                                   &MailFileInItem,
                                   &CallMailNullItem);
      if (Debug) fprintf (stdout, "mail$mailfile_open() %%X%08.08X\n", status);
      if (VMSnok (status)) VmsMailStatus = status;
   }

   /*******************/
   /* message context */
   /*******************/

   if (VMSok (VmsMailStatus))
   {
      itptr = &MessageBeginInItem[0];
      memset (itptr, 0, sizeof(MessageBeginInItem));

      itptr->buf_len = sizeof(MailFileContext);
      itptr->item = MAIL$_MESSAGE_FILE_CTX;
      itptr->buf_addr = (void*)&MailFileContext;
      itptr++;

      itptr->item = MAIL$_NOSIGNAL;

      itptr = &MessageBeginOutItem[0];
      memset (itptr, 0, sizeof(MessageBeginOutItem));

      itptr->buf_len = sizeof(MessageSelected);
      itptr->item = MAIL$_MESSAGE_SELECTED;
      itptr->buf_addr = (void*)&MessageSelected;
      itptr++;

      itptr->item = MAIL$_NOSIGNAL;

      MessageContext = 0;
      status = mail$message_begin (&MessageContext,
                                   &MessageBeginInItem,
                                   &MessageBeginOutItem);
      if (Debug)
         fprintf (stdout, "mail$message_begin() %%X%08.08X selected:%d\n",
                  status, MessageSelected);
      if (VMSnok (status)) VmsMailStatus = status;
   }

   /******************/
   /* message select */
   /******************/

   if (VMSok (VmsMailStatus))
   {
      sprintf (HashSubstring, "[[#%08.08X]]", cdptr->PrevDraftHash);
      if (Debug) fprintf (stdout, "%s\n", HashSubstring);

      itptr = &MessageSelectItem[0];
      memset (itptr, 0, sizeof(MessageSelectItem));

      itptr->buf_len = strlen(uoptr->FolderDraftItems);
      itptr->item = MAIL$_MESSAGE_FOLDER;
      itptr->buf_addr = uoptr->FolderDraftItems;
      itptr++;

      itptr->buf_len = strlen(HashSubstring);
      itptr->item = MAIL$_MESSAGE_SUBJ_SUBSTRING;
      itptr->buf_addr = HashSubstring;
      itptr++;

      itptr->item = MAIL$_NOSIGNAL;

      itptr = &MessageSelectedItem[0];
      memset (itptr, 0, sizeof(MessageSelectedItem));

      itptr->buf_len = sizeof(SelectedCount);
      itptr->item = MAIL$_MESSAGE_SELECTED;
      itptr->buf_addr = &SelectedCount;
      itptr++;

      itptr->item = MAIL$_NOSIGNAL;

      status = mail$message_select (&MessageContext,
                                    &MessageSelectItem,
                                    &MessageSelectedItem);
      if (Debug)
         fprintf (stdout, "mail$message_select() %%X%08.08X selected: %d\n",
                  status, SelectedCount);
   }

   /********************/
   /* purge message(s) */
   /********************/

   /* if same as the hash for (any) previous draft leave the latest one */
   if (cdptr->DraftHash == cdptr->PrevDraftHash) SelectedCount--;

   MessageId = 0;
   while (VMSok (VmsMailStatus))
   {
      if (++MessageId > SelectedCount) break;

      itptr = &MessageIdItem[0];
      memset (itptr, 0, sizeof(MessageIdItem));

      itptr->buf_len = sizeof(MessageId);
      itptr->item = MAIL$_MESSAGE_ID;
      itptr->buf_addr = &MessageId;
      itptr++;

      itptr->item = MAIL$_NOSIGNAL;

      status = mail$message_delete (&MessageContext,
                                    &MessageIdItem,
                                    &CallMailNullItem);
      if (Debug) fprintf (stdout, "mail$message_delete() %%X%08.08X\n", status);
      if (VMSnok (status)) VmsMailStatus = status;
   }

   /******************/
   /* close contexts */
   /******************/

   status = mail$message_end (&MessageContext,
                              &CallMailNoSignalItem,
                              &CallMailNullItem);
   if (Debug) fprintf (stdout, "mail$message_end() %%X%08.08X\n", status);
   if (VMSnok (status)) VmsMailStatus = status;

   status = mail$mailfile_close (&MailFileContext,
                                 &CallMailNoSignalItem, &CallMailNoSignalItem);
   if (Debug) fprintf (stdout, "mail$mailfile_close() %%X%08.08X\n", status);
   if (VMSnok (status)) VmsMailStatus = status;

   status = mail$mailfile_end (&MailFileContext,
                               &CallMailNoSignalItem, &CallMailNullItem);
   if (Debug) fprintf (stdout, "mail$mailfile_end() %%X%08.08X\n", status);

   return (VmsMailStatus);
}                              

/*****************************************************************************/
/*
Read the saved draft from the body of the message just retrieved from the
"Draft Items"  folder.  Retrieve soyMAIL compose data.
*/

void DraftRestore (REQUEST_DATA *rdptr)

{
   char  *cptr, *sptr, *zptr,
         *CharSetPtr,
         *CreatedPtr,
         *DraftTextPtr,
         *EditorPtr,
         *NamePtr,
         *UpdatedPtr,
         *ValuePtr,
         *VersionPtr;
   COMPOSE_DATA  *cdptr;
   VMS_MAIL_MSG  *vmptr;
   VMS_MAIL_USER  *muptr;

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

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

   cdptr = &rdptr->ComposeData;
   muptr = &VmsMailUser;
   vmptr = &muptr->VmsMailMsg;

   CreatedPtr = EditorPtr = VersionPtr = UpdatedPtr = NULL;

   DraftTextPtr = vmptr->BodyPtr;
   while (DraftTextPtr && *DraftTextPtr)
   {
      cptr = ConfigTextParse (&DraftTextPtr, &NamePtr, &ValuePtr);
      if (!cptr) break;

      if (strsame (NamePtr, "soyMAIL", -1))
         VersionPtr = ValuePtr;
      else
      if (strsame (NamePtr, "created", -1))
         CreatedPtr = ValuePtr;
      else
      if (strsame (NamePtr, "updated", -1))
         UpdatedPtr = ValuePtr;
      else
      if (strsame (NamePtr, "editor", -1))
         EditorPtr = ValuePtr;
      else
      if (strsame (NamePtr, "rows", -1))
         cdptr->EditRows = atoi(ValuePtr);
      else
      if (strsame (NamePtr, "columns", -1))
         cdptr->EditCols = atoi(ValuePtr);
      else
      if (strsame (NamePtr, "wrap-at", -1))
         cdptr->WrapAt = atoi(ValuePtr);
      else
      if (strsame (NamePtr, "charset", -1))
         cdptr->CharSetPtr = ValuePtr;
      else
      if (strsame (NamePtr, "confirm", -1))
         cdptr->ConfirmBeforeSend = (*ValuePtr == 't');
      else
      if (strsame (NamePtr, "in-reply-to", -1))
         cdptr->InReplyToPtr = ValuePtr;
      else
      if (strsame (NamePtr, "references", -1))
         cdptr->ReferencesPtr = ValuePtr;
      else
      if (strsame (NamePtr, "to", -1))
         cdptr->ToPtr = ValuePtr;
      else
      if (strsame (NamePtr, "cc", -1))
         cdptr->CcPtr = ValuePtr;
      else
      if (strsame (NamePtr, "bcc", -1))
         cdptr->BccPtr = ValuePtr;
      else
      if (strsame (NamePtr, "reply-to", -1))
         cdptr->ReplyToPtr = ValuePtr;
      else
      if (strsame (NamePtr, "subj", -1))
      {
         cdptr->SubjPtr = ValuePtr;

         /********************/
         /* loop breaks here */
         /********************/

         break;
      }
      else
         DraftTextPtr = NULL;
   }

   if (DraftTextPtr)
   {
      /*************************/
      /* hand-parse draft text */
      /*************************/

      for (cptr = DraftTextPtr; *cptr && *cptr == '\n'; *cptr++);
      if (strsame (cptr, "[text]\n", 6))
      {
         /* quoted printable in case any one record exceeds 255/2048 limits */
         MimeDecQuotedPrintable(cdptr->TextPtr = cptr+6);
      }
      else
         DraftTextPtr = NULL;
   }

   if (EditorPtr && *EditorPtr == 'P')
      cdptr->EditorType = COMPOSE_EDITOR_PLAIN;
   else
   if (EditorPtr && *EditorPtr == 'H')
      cdptr->EditorType = COMPOSE_EDITOR_HTML;
   else
      cdptr->EditorType = 0;

   if (CreatedPtr)
   {
      zptr = (sptr = cdptr->DraftCreated) + sizeof(cdptr->DraftCreated)-1;
      for (cptr = CreatedPtr; *cptr && sptr < zptr; *sptr++ = *cptr++);
      *sptr = '\0';
   }   
   else
      cdptr->DraftCreated[0] = '\0';

   if (!UpdatedPtr) UpdatedPtr = "?";

   if (DraftTextPtr)
      StatusMessage (FI_LI, 0, "%s %s.%s",
                     LangFor("compose_restore_done"),
                     InetMailRfcVmsDate(UpdatedPtr),
                     ComposeCharsetAlert(cdptr->CharSetPtr));
   else
   {
      cdptr->ToPtr = cdptr->CcPtr = cdptr->BccPtr =
         cdptr->ReplyToPtr = cdptr->SubjPtr = cdptr->TextPtr = "";
      cdptr->EditorType = 0;
      StatusMessage (FI_LI, 1, "%s", LangFor("file_format_error"));
   }
}                              

/*****************************************************************************/
/*
Generate a hash representing the mail destination(s) and the subject.
Need to limit fields to a VMS Mail acceptable 255.
*/

unsigned int  DraftHash (COMPOSE_DATA *cdptr)

{
   unsigned int  hash32;
   char  *cptr, *sptr, *zptr;
   char  Buffer255 [256];

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

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

   CallMailElfHash (NULL, 0);  /* initialize */
   zptr = (sptr = Buffer255) + sizeof(Buffer255)-1;
   for (cptr = cdptr->ToPtr;
        *cptr && *cptr != '\r' && *cptr != '\n' && sptr < zptr;
        *sptr++ = *cptr++);
   *sptr = '\0';
   CallMailElfHash (Buffer255, sptr-Buffer255);
   zptr = (sptr = Buffer255) + sizeof(Buffer255)-1;
   for (cptr = cdptr->CcPtr;
        *cptr && *cptr != '\r' && *cptr != '\n' && sptr < zptr;
        *sptr++ = *cptr++);
   *sptr = '\0';
   CallMailElfHash (Buffer255, sptr-Buffer255);
   zptr = (sptr = Buffer255) + sizeof(Buffer255)-1;
   for (cptr = cdptr->SubjPtr; *cptr && sptr < zptr; *sptr++ = *cptr++);
   *sptr = '\0';
   hash32 = CallMailElfHash (Buffer255, sptr-Buffer255);
   if (Debug) fprintf (stdout, "%08.08X\n", hash32);
   return (hash32);
}                              

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

