/*****************************************************************************/
/*
                                  inetmail.c

Functions relating to the processing of RFC822/2822 message and RFC
1521/1522/1523 (MIME) message headers and their fields.


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
---------------
25-FEB-2018  MGD  bugfix; InetMailParseMimeAttrib() span space after equate
18-APR-2013  MGD  InetMailParseHeader() allow for a bloody-minded and probably
                    illegal combination of <LF> and <CR><LF> 
22-JUL-2012  WK   bugfix; InetMailParseHeader() ignore <cr>s at end-of-header
04-AUG-2011  MGD  InetMailParseHeader() liberalise RFC822 detection
19-APR-2009  MGD  bugfix; InetMailParseHeader() end-of-header detection
12-MAR-2009  MGD  bugfix; InetMailParseField() exit from second-guess
23-FEB-2009  MGD  InetMailParseField() try to second-guess when a VMS record
                    buffer overflowed due to unbreakable character sequence
10-DEC-2008  MGD  InetMailGetField() improve parsing of leading continuation
                  bugfix; InetMailFoldRfc822Header() line folding
05-NOV-2008  MGD  bugfix; InetMailParseHeader() empty field from 23-SEP-2008
01-NOV-2008  MGD  InetMailParseHeader() "In-Reply-To:" and "References:"
23-SEP-2008  MGD  InetMailParseHeader() if a field is empty just ignore it
                    (prompted by an empty reply-to)
18-AUG-2007  MGD  InetMailParseHeader() manufacture MIME for message with
                    content-type of HTML but no MIME version header (kosher?)
15-JUL-2007  MGD  report RFC header error line number
07-FEB-2007  MGD  bugfix; InetMailParseField() handling continuation
                   line after <CR><LF> carriage-control ("Sent Items")
25-APR-2006  MGD  modify message from derivation slightly
16-MAR-2006  MGD  bugfix; InetMailParseHeader() non-MIME MIME-like headers
08-MAR-2006  MGD  bugfix; allow MIME related headers (e.g. "Content-type:")
                    to precede the "MIME-version:" header (but cancel MIME
                    if it is not ultimately found in the message header)
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 <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

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

/* application header file */
#include <soymail.h>
#include <mta.h>

#define MAIL$M_EXTNSTD 0x40

#define FI_LI "INETMAIL", __LINE__

/* global storage */

/* prototypes */

/* external storage */

extern BOOL  Debug,
             WatchEnabled;

extern int  VmsVersion;

extern char  LocalHostName[],
             SoftwareId[],
             SoftwareVn[];

extern VMS_MAIL_USER  VmsMailUser;

/*****************************************************************************/
/*
Parse the RFC822 header getting the values of fields of interest.
*/

BOOL InetMailParseHeader (VMS_MAIL_MSG *vmptr)

{
   int  crcnt, len, lcnt, nlcnt, rfcnt;
   char  *cptr, *fptr, *sptr, *tptr, *zptr;
   char  FieldBuffer [4096];

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

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

   if (!vmptr->BodyPtr) ErrorExit(SS$_BUGCHECK, FI_LI);

   /* initially set these to the VMS Mail header items */
   vmptr->MsgFromPtr = vmptr->From;
   vmptr->MsgToPtr = vmptr->To;
   vmptr->MsgCcPtr = vmptr->Cc;
   vmptr->MsgSubjPtr = vmptr->Subject;
   vmptr->MsgDatePtr = vmptr->Date;
   vmptr->MsgBodyPtr = vmptr->BodyPtr;

   if (vmptr->MessageFlags & MAIL$M_EXTNSTD)
   {
      if (Debug) fprintf (stdout, "MAIL$M_EXTNSTD\n");
      vmptr->VmsForeign = TRUE;
      return (FALSE);
   }

   lcnt = rfcnt = 0;
   cptr = vmptr->BodyPtr;
   while (*cptr)
   {
      /* check to see if it looks like a leading RFC822 header */
      len = InetMailParseField (cptr);
      /* if the parse function could not make sense of the text */
      if (len == -1) return (FALSE);
      if (strsame (cptr, "content-type:", 13) ||
          strsame (cptr, "date:", 5) ||
          strsame (cptr, "from:", 5) ||
          strsame (cptr, "message-id:", 11) ||
          strsame (cptr, "mime:", 5) ||
          strsame (cptr, "return-path:", 12) ||
          strsame (cptr, "received:", 9)) rfcnt++;
      cptr += len;
      /* break if it's beginning to smell like RFC822 */
      if (rfcnt >= 2) break;
      /* return if it looks like an empty line */
      if (*(USHORTPTR)cptr == '\r\n') return (FALSE);
      if (*cptr == '\n') return (FALSE);
      /* if we've looked far enough */
      if (lcnt++ > INETMAIL_RFC_SEARCH_MAX) return (FALSE);
      /* otherwise continue looking */
   }
   /* nope, doesn't look like an RFC822 header */
   if (!*cptr) return (FALSE);

   vmptr->LooksLikeRfc = TRUE;

   cptr = vmptr->BodyPtr;
   while (*cptr)
   {
      /* note the start of this line */
      sptr = cptr;
      len = InetMailParseField (cptr);
      /* if the parse function could not make sense of the text */
      if (len == -1)
      {
         vmptr->RfcErrorLineNumber = 1;
         for (cptr = vmptr->BodyPtr; cptr < sptr; cptr++)
            if (*cptr == '\n') vmptr->RfcErrorLineNumber++;
         return (FALSE);
      }
      /* adjust to the beginning of the next line */
      cptr += len;
      /* get the field value from the current line */
      len = InetMailGetField (sptr, len, FieldBuffer, sizeof(FieldBuffer));
      /* if the get function could not make sense of the text */
      if (len == -1)
      {
         vmptr->RfcErrorLineNumber = 1;
         for (cptr = vmptr->BodyPtr; cptr < sptr; cptr++)
            if (*cptr == '\n') vmptr->RfcErrorLineNumber++;
         return (FALSE);
      }
      len = MimeDecIsoString (FieldBuffer);
      if (len == -1) len = strlen(FieldBuffer);

      if (len == 0)
      {
         /* if the field is empty ignore it! */
         if (Debug) fprintf (stdout, "empty field!\n");
      }
      else
      if (strsame (sptr, "bcc:", 4))
      {
         sptr = vmptr->RfcBccPtr =  CgiLibVeeMemCalloc (len+1);
         if (!sptr) ErrorExit (vaxc$errno, FI_LI);
         strcpy (sptr, FieldBuffer);
         vmptr->RfcBccLength = len;
      }
      else
      if (strsame (sptr, "cc:", 3))
      {
         sptr = vmptr->RfcCcPtr =  CgiLibVeeMemCalloc (len+1);
         if (!sptr) ErrorExit (vaxc$errno, FI_LI);
         strcpy (sptr, FieldBuffer);
         vmptr->RfcCcLength = len;
      }
      else
      if (strsame (sptr, "date:", 5))
      {
         sptr = vmptr->RfcDatePtr =  CgiLibVeeMemCalloc (len+1);
         if (!sptr) ErrorExit (vaxc$errno, FI_LI);
         strcpy (sptr, FieldBuffer);
         vmptr->RfcDateLength = len;
      }
      else
      if (strsame (sptr, "from:", 5))
      {
         sptr = vmptr->RfcFromPtr =  CgiLibVeeMemCalloc (len+1);
         if (!sptr) ErrorExit (vaxc$errno, FI_LI);
         strcpy (sptr, FieldBuffer);
         vmptr->RfcFromLength = len;
      }
      else
      if (strsame (sptr, "in-reply-to:", 12))
      {
         sptr = vmptr->RfcInReplyToPtr =  CgiLibVeeMemCalloc (len+1);
         if (!sptr) ErrorExit (vaxc$errno, FI_LI);
         strcpy (sptr, FieldBuffer);
         vmptr->RfcInReplyToLength = len;
      }
      else
      if (strsame (sptr, "return-path:", 12))
      {
         sptr = vmptr->RfcReturnPathPtr =  CgiLibVeeMemCalloc (len+1);
         if (!sptr) ErrorExit (vaxc$errno, FI_LI);
         strcpy (sptr, FieldBuffer);
         vmptr->RfcReturnPathLength = len;
      }
      else
      if (strsame (sptr, "message-id:", 11))
      {
         sptr = vmptr->RfcMessageIdPtr =  CgiLibVeeMemCalloc (len+1);
         if (!sptr) ErrorExit (vaxc$errno, FI_LI);
         strcpy (sptr, FieldBuffer);
         vmptr->RfcMessageIdLength = len;
      }
      else
      if (strsame (sptr, "references:", 11))
      {
         sptr = vmptr->RfcReferencesPtr =  CgiLibVeeMemCalloc (len+1);
         if (!sptr) ErrorExit (vaxc$errno, FI_LI);
         strcpy (sptr, FieldBuffer);
         vmptr->RfcReferencesLength = len;
      }
      else
      if (strsame (sptr, "reply-to:", 9))
      {
         sptr = vmptr->RfcReplyToPtr =  CgiLibVeeMemCalloc (len+1);
         if (!sptr) ErrorExit (vaxc$errno, FI_LI);
         strcpy (sptr, FieldBuffer);
         vmptr->RfcReplyToLength = len;
      }
      else
      if (strsame (sptr, "subject:", 8))
      {
         sptr = vmptr->RfcSubjPtr =  CgiLibVeeMemCalloc (len+1);
         if (!sptr) ErrorExit (vaxc$errno, FI_LI);
         strcpy (sptr, FieldBuffer);
         vmptr->RfcSubjLength = len;
      }
      else
      if (strsame (sptr, "to:", 3))
      {
         sptr = vmptr->RfcToPtr = CgiLibVeeMemCalloc (len+1);
         if (!sptr) ErrorExit (vaxc$errno, FI_LI);
         strcpy (sptr, FieldBuffer);
         vmptr->RfcToLength = len;
      }
      else
      if (strsame (sptr, "X-VMS-To:", 9))
      {
         sptr = vmptr->XVmsToPtr = CgiLibVeeMemCalloc (len+1);
         if (!sptr) ErrorExit (vaxc$errno, FI_LI);
         strcpy (sptr, FieldBuffer);
         vmptr->XVmsToLength = len;
      }
      else
      if (strsame (sptr, "MIME-version:", 13) ||
          strsame (sptr, "content-type:", 13) ||
          strsame (sptr, "content-length:", 15) ||
          strsame (sptr, "content-location:", 17) ||
          strsame (sptr, "content-disposition:", 20) ||
          strsame (sptr, "content-description:", 20) ||
          strsame (sptr, "content-transfer-encoding:", 26))
      {
         /* all MIME related header fields */
         if (!vmptr->MimeDataPtr)
         {
            vmptr->MimeDataPtr = (MIME_DATA*)  CgiLibVeeMemCalloc (sizeof(MIME_DATA));
            if (!vmptr->MimeDataPtr) ErrorExit (vaxc$errno, FI_LI);
         }
         InetMailParseMimeHeader (vmptr->MimeDataPtr, sptr, FieldBuffer);
      }

      /* count consecutative-ish new lines */
      nlcnt = crcnt = 0;
      for (tptr = cptr; nlcnt < 2 && (*tptr == '\n' || *tptr == '\r'); tptr++)
      {
          if (*tptr == '\n') nlcnt += 1;
          crcnt += 1;
      }
      if (nlcnt == 2)
      {
         if (Debug) fprintf (stdout, "End of header |%d|\n", crcnt);
         cptr += crcnt;
         break;
      }
   }

   vmptr->RfcHeaderPtr = vmptr->BodyPtr;
   vmptr->RfcHeaderLength = cptr - vmptr->BodyPtr;

   vmptr->RfcBodyPtr = cptr;
   vmptr->RfcBodyLength = vmptr->BodyLength - vmptr->RfcHeaderLength;

   if (vmptr->MimeDataPtr)
   {
      if (vmptr->MimeDataPtr->VersionNumber)
      {
         vmptr->MimeDataPtr->PartContentPtr = vmptr->RfcBodyPtr;
         vmptr->MimeDataPtr->PartContentLength = vmptr->RfcBodyLength;
      }
      else
      if (vmptr->MimeDataPtr->ContentTypeIs == MIME_CONTENT_TEXT_HTML)
      {
         /* had a content-type header of HTML but no MIME header */
         vmptr->MimeDataPtr->ManufacturedMime = TRUE;
         vmptr->MimeDataPtr->PartContentPtr = vmptr->RfcBodyPtr;
         vmptr->MimeDataPtr->PartContentLength = vmptr->RfcBodyLength;
         vmptr->MimeDataPtr->PartCount = 1;
      }
      else
      {
         /* without a usable "MIME-version:" header it aint gonna stay MIME! */
         vmptr->MimeDataPtr = NULL;
      }
   }

   if (!(vmptr->MsgFromPtr = vmptr->RfcFromPtr))
      if (!(vmptr->MsgFromPtr = vmptr->RfcReplyToPtr))
         vmptr->MsgFromPtr = "";
   if (!(vmptr->MsgToPtr = vmptr->RfcToPtr)) vmptr->MsgToPtr = "";
   if (!(vmptr->MsgCcPtr = vmptr->RfcCcPtr)) vmptr->MsgCcPtr = "";
   if (!(vmptr->MsgSubjPtr = vmptr->RfcSubjPtr)) vmptr->MsgSubjPtr = "";
   if (!(vmptr->MsgDatePtr = vmptr->RfcDatePtr)) vmptr->MsgDatePtr = "";
   if (!(vmptr->MsgBodyPtr = vmptr->RfcBodyPtr)) vmptr->MsgBodyPtr = "";

   return (TRUE);
}

/*****************************************************************************/
/*
Parse a header field from the text.  This can be a continued header field. 
Return the number of characters to the last character of that field (includes
the carriage-control of that line).  This section of characters may contain
embedded carriage control if header field continuation line(s) were encountered
and contains the trailing carriage control.  Return -1 if the header does not
seem to make sense.
*/

int InetMailParseField (char *TextPtr)

{
   char  *cptr, *sptr, *zptr;

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

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

   cptr = TextPtr;
   while (*cptr)
   {
      /* scan the end of the current line */
      while (*cptr && *cptr != '\r' && *cptr != '\n') cptr++;
      /* should see carriage-control */
      if (!*cptr) return (-1);
      /* if it's an empty line (the end-of-header) */
      if (cptr - TextPtr <= 0) return (0);
      if (*cptr == '\n')
      {
         if (*(cptr+1) == '\n') return (cptr-TextPtr);
         /* if a bloody-minded combination of <LF> and <CR><LF> */
         if (*(USHORTPTR)(cptr+1) == '\r\n') return (cptr-TextPtr);
         cptr++;
         /* if it's not a continuation line */
         if (!ISLWS(*cptr))
         {
            /* second-guess VMS line buffer 'overflow' */
            if (cptr - TextPtr < 255) return (cptr-TextPtr);
            /* look ahead for something like a header name */
            for (sptr = cptr;
                 *sptr && *sptr != ':' && *sptr != '\n' && sptr-cptr < 64;
                 sptr++);
            if (*sptr == ':') return (cptr-TextPtr);
            /* nope, looks like a pesky 'overflow' situation */
         }
      }
      else
      if (*(USHORTPTR)cptr == '\r\n')
      {
         if (*(USHORTPTR)(cptr+2) == '\r\n') return (cptr-TextPtr);
         cptr += 2;
         /* if it's not a continuation line */
         if (!ISLWS(*cptr))
         {
            /* second-guess VMS line buffer 'overflow' */
            if (cptr - TextPtr < 255) return (cptr-TextPtr);
            /* look ahead for something like a header name */
            for (sptr = cptr;
                 *sptr && *sptr != ':' && *sptr != '\n' && sptr-cptr < 64;
                 sptr++);
            if (*sptr == ':') return (cptr-TextPtr);
            /* nope, looks like a pesky 'overflow' situation */
         }
      }
      else
      {
         /* doesn't make sense */
         return (-1);
      }
      /* continuation line, skip leading white-space */
      while (*cptr && ISLWS(*cptr)) cptr++;
   }
   return (-1);
}

/*****************************************************************************/
/*
Using the length determined by InetMailParseField() get that field's value from
the text placing it in 'FieldBuffer' and returning the length of that value. 
return -1 on error.
*/

int InetMailGetField
(
char *TextPtr,
int FieldLength,
char *FieldBuffer,
int FieldBufferSize
)
{
   int  FieldNameLength;
   char  *cptr, *czptr, *sptr, *zptr;

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

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

   czptr = (cptr = TextPtr) + FieldLength;
   *(sptr = FieldBuffer) = '\0';

   /* if there is no field (will happen) */
   if (ISLWS(*cptr) || *cptr == '\r' || *cptr == '\n') return (0);

   zptr = sptr + FieldBufferSize;

   /* skip over the field name and past any leading white-space */
   while (*cptr && cptr < czptr && *cptr != ':' &&
          *cptr != '\r' && *cptr != '\n') cptr++;

   /* if there is an error in the message then indicate */
   if (*cptr == '\r' || *cptr == '\n') return (-1);

   if (!*cptr || cptr >= czptr) return (-1);
   cptr++;
   FieldNameLength = cptr - TextPtr;
   while (*cptr && cptr < czptr && ISLWS(*cptr)) cptr++;

   /* get the field value */
   while (*cptr && cptr < czptr && sptr < zptr)
   {
      if (*cptr != '\r' && *cptr != '\n')
      {
         *sptr++ = *cptr++;
         continue;
      }
      while (*cptr && cptr < czptr &&
             (*cptr == '\r' || *cptr == '\n' || ISLWS(*cptr))) cptr++;
      if (sptr > FieldBuffer)
      {
         /* replace continuation white-space with a single space character */ 
         if (*cptr && cptr < czptr && sptr < zptr) *sptr++ = ' ';
      }
   }

   /* on overflow just return an empty field value */
   if (sptr >= zptr) sptr = FieldBuffer;
   *sptr = '\0';

   if (WatchEnabled)
      WatchThis (FI_LI, "HEADER (!3UL) !#AZ !AZ",
                 sptr - FieldBuffer, FieldNameLength, TextPtr, FieldBuffer);

   return (sptr - FieldBuffer);
}

/*****************************************************************************/
/*
Parse the contents of a MIME-related header field into the specified MIME data
structure.  If the MIME data structure has not been allocated then create one.
*/

int InetMailParseMimeHeader
(
MIME_DATA *mdptr,
char *FieldName,
char *FieldBuffer
)
{
   int  len;
   char  *cptr, *fnptr, *sptr, *zptr;

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

   if (Debug)
      fprintf (stdout, "InetMailParseMimeHeader() |%132.132s|%s|\n",
               FieldName, FieldBuffer);

   if (!mdptr) ErrorExit (SS$_BUGCHECK, FI_LI);

   /* if it's an 'empty field' then just finalise */
   if (!FieldBuffer[0]) return (0);

   cptr = FieldBuffer;

   if (strsame (FieldName, fnptr = "MIME-version:", 13))
   {
      if (!memcmp (cptr, "1.0", 3))
         mdptr->VersionNumber = 10;
      else
         mdptr->VersionNumber = 0;
      while (*cptr && (isdigit(*cptr) || *cptr == '.')) cptr++;
   }
   else
   if (strsame (FieldName, fnptr = "Content-Type:", 13))
   {
      for (cptr = sptr = FieldBuffer;
           *sptr && !isspace(*sptr) && *sptr != ';';
           sptr++);
      len = sptr - FieldBuffer;
      sptr = mdptr->ContentTypePtr =  CgiLibVeeMemCalloc (len+1);
      if (!sptr) ErrorExit (vaxc$errno, FI_LI);
      while (len--) *sptr++ = *cptr++;
      *sptr = '\0';

      if (strsame (mdptr->ContentTypePtr, "application/octet-stream", -1))
         mdptr->ContentTypeIs = MIME_CONTENT_OCTET_STREAM;
      else
      if (strsame (mdptr->ContentTypePtr, "multipart/mixed", 16))
      {
         mdptr->ContentTypeIs = MIME_CONTENT_MULTIPART_MIX;
         mdptr->ContentTypeIsMultipart = TRUE;
      }
      else
      if (strsame (mdptr->ContentTypePtr, "multipart/alternative", 21))
      {
         mdptr->ContentTypeIs = MIME_CONTENT_MULTIPART_ALT;
         mdptr->ContentTypeIsMultipart = TRUE;
      }
      else
      if (strsame (mdptr->ContentTypePtr, "multipart/", 10))
      {
         mdptr->ContentTypeIs = MIME_CONTENT_MULTIPART;
         mdptr->ContentTypeIsMultipart = TRUE;
      }
      else
      if (strsame (mdptr->ContentTypePtr, "text/plain", -1))
         mdptr->ContentTypeIs = MIME_CONTENT_TEXT_PLAIN;
      else
      if (strsame (mdptr->ContentTypePtr, "text/html", -1))
         mdptr->ContentTypeIs = MIME_CONTENT_TEXT_HTML;
      else
      if (strsame (mdptr->ContentTypePtr, "message/rfc822", 14))
         mdptr->ContentTypeIs = MIME_CONTENT_MESSAGE_RFC822;
      else
         mdptr->ContentTypeIs = MIME_CONTENT_OTHER;

      /* just in case, ensure these are reset */
      mdptr->BoundaryPtr = NULL;
      mdptr->BoundaryLength = 0;
   }
   else
   if (strsame (FieldName, fnptr = "Content-Disposition:", 20))
   {
      for (cptr = sptr = FieldBuffer;
           *sptr && !isspace(*sptr) && *sptr != ';';
           sptr++);
      len = sptr - FieldBuffer;
      sptr = mdptr->ContentDispositionPtr =  CgiLibVeeMemCalloc (len+1);
      if (!sptr) ErrorExit (vaxc$errno, FI_LI);
      while (len--) *sptr++ = *cptr++;
      *sptr = '\0';
   }
   else
   if (strsame (FieldName, fnptr = "Content-Description:", 20))
   {
      for (cptr = sptr = FieldBuffer;
           *sptr && !isspace(*sptr) && *sptr != ';';
           sptr++);
      len = sptr - FieldBuffer;
      sptr = mdptr->ContentDescriptionPtr =  CgiLibVeeMemCalloc (len+1);
      if (!sptr) ErrorExit (vaxc$errno, FI_LI);
      while (len--) *sptr++ = *cptr++;
      *sptr = '\0';
   }
   else
   if (strsame (FieldName, fnptr = "Content-Location:", 17))
   {
      for (cptr = sptr = FieldBuffer;
           *sptr && !isspace(*sptr) && *sptr != ';';
           sptr++);
      len = sptr - FieldBuffer;
      sptr = mdptr->ContentLocationPtr =  CgiLibVeeMemCalloc (len+1);
      if (!sptr) ErrorExit (vaxc$errno, FI_LI);
      while (len--) *sptr++ = *cptr++;
      *sptr = '\0';
   }
   else
   if (strsame (FieldName, fnptr = "Content-Transfer-Encoding:", 26))
   {
      for (cptr = sptr = FieldBuffer;
           *sptr && !isspace(*sptr) && *sptr != ';';
           sptr++);
      len = sptr - FieldBuffer;
      sptr = mdptr->TransferEncodingPtr =  CgiLibVeeMemCalloc (len+1);
      if (!sptr) ErrorExit (vaxc$errno, FI_LI);
      while (len--) *sptr++ = *cptr++;
      *sptr = '\0';

      if (strsame (mdptr->TransferEncodingPtr, "7bit", -1))
         mdptr->TransferEncodingIs = MIME_TRANSENC_7BIT;
      else
      if (strsame (mdptr->TransferEncodingPtr, "8bit", -1) ||
          strsame (mdptr->TransferEncodingPtr, "binary", -1))
         mdptr->TransferEncodingIs = MIME_TRANSENC_8BIT;
      else
      if (strsame (mdptr->TransferEncodingPtr, "base64", -1))
         mdptr->TransferEncodingIs = MIME_TRANSENC_BASE64;
      else
      if (strsame (mdptr->TransferEncodingPtr, "quoted-printable", -1))
         mdptr->TransferEncodingIs = MIME_TRANSENC_QUOTED;
      else
         mdptr->TransferEncodingIs = MIME_TRANSENC_OTHER;
   }
   else
   if (strsame (FieldName, fnptr = "Content-Length:", 15))
   {
      for (sptr = cptr = FieldBuffer; *cptr && isdigit(*cptr); cptr++);
      len = cptr - sptr;
      sptr = mdptr->ContentLengthPtr =  CgiLibVeeMemCalloc (len+1);
      if (!sptr) ErrorExit (vaxc$errno, FI_LI);
      cptr = FieldBuffer;
      while (len--) *sptr++ = *cptr++;
      *sptr = '\0';
      mdptr->ContentLength = atol(mdptr->ContentLengthPtr);
   }
   else
   {
      if (WatchEnabled)
         WatchThis (FI_LI, "MIME !UL unknown !48AZ", mdptr->PartCount, FieldName);
      return (-1);
   }

   if (WatchEnabled)
      WatchThis (FI_LI, "MIME !UL HEADER !AZ !AZ",
                 mdptr->PartCount, fnptr, FieldBuffer);

   /*********************************/
   /* retrieve any field attributes */
   /*********************************/

   while (*cptr)
   {
      while (*cptr && *cptr != ';') cptr++;
      if (!*cptr) break;
      cptr++;
      while (*cptr && ISLWS(*cptr)) cptr++;
      if (!*cptr) break;

      if (strsame (cptr, "name=", 5))
         cptr += InetMailParseMimeAttrib (mdptr, cptr,
                                          &mdptr->NamePtr, NULL);
      else
      if (strsame (cptr, "filename=", 9))
         cptr += InetMailParseMimeAttrib (mdptr, cptr,
                                          &mdptr->FileNamePtr, NULL);
      else
      if (strsame (cptr, "charset=", 8))
         cptr += InetMailParseMimeAttrib (mdptr, cptr,
                                          &mdptr->CharSetPtr, NULL);
      else
      if (strsame (cptr, "format=", 7))
         cptr += InetMailParseMimeAttrib (mdptr, cptr,
                                          &mdptr->FormatPtr, NULL);
      else
      if (strsame (cptr, "boundary=", 9))
      {
         cptr += InetMailParseMimeAttrib (mdptr, cptr,
                                          &mdptr->BoundaryPtr,
                                          &mdptr->BoundaryLength);
         if (mdptr->BoundaryPtr &&
             mdptr->BoundaryLength)
         {
            /* rebuild the boundary to include the two leading hyphens */
            sptr =  CgiLibVeeMemCalloc (mdptr->BoundaryLength+3);
            if (!sptr) ErrorExit (vaxc$errno, FI_LI);
            *sptr++ = '-';
            *sptr++ = '-';
            strcpy (sptr, mdptr->BoundaryPtr);
            mdptr->BoundaryPtr = sptr-2;
            mdptr->BoundaryLength += 2;
         }
      }
      else
      {
         /* just skip over this unknown one */
         cptr += InetMailParseMimeAttrib (mdptr, cptr, NULL, NULL);
      }
   }

   if (mdptr->CharSetPtr &&
       strsame (mdptr->CharSetPtr, "unicode-1-1-utf-7", -1))
   {
      /* don't know how to do this yet and causes all sorts of strangeness! */
      if (WatchEnabled) WatchThis (FI_LI, "*** UNICODE-1-1-UTF-7 ***");
      mdptr->CharSetPtr = SOY_DEFAULT_CHARSET;
   }

   return (0);
}

/*****************************************************************************/
/*
Parse a 'name=value;' or 'name="value";' attribute from a mime header field. 
If the attribute is to be stored then allocate dynamic memory for the value
string and adjust the supplied attribute pointer and optionally length.  Return
the number of characters parse across the string.
*/

int InetMailParseMimeAttrib
(
MIME_DATA *mdptr,
char *StringPtr,
char **AttribPtrPtr,
int *AttribLengthPtr
)
{
   int  len;
   char  *cptr, *sptr, *zptr;

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

   if (Debug)
      fprintf (stdout, "InetMailParseMimeAttrib() |%s|\n", StringPtr);

   if (!StringPtr) ErrorExit (SS$_BUGCHECK, FI_LI);

   for (cptr = StringPtr; *cptr && *cptr != '='; cptr++);
   if (*cptr) cptr++;
   while (*cptr && isspace(*cptr)) cptr++;
   if (*cptr == '\"')
   {
      cptr++;
      for (sptr = cptr; *sptr && *sptr != '\"'; *sptr++);
      len = sptr - cptr;
      if (AttribPtrPtr)
      {
         sptr =  CgiLibVeeMemCalloc (len+1);
         if (!sptr) ErrorExit (vaxc$errno, FI_LI);
         memcpy (sptr, cptr, len);
         sptr[len] = '\0';
      }
      cptr = cptr + len;
      if (*cptr == '\"') cptr++;
   }
   else
   {
      for (sptr = cptr; *sptr && !isspace(*sptr) && *sptr != ';'; *sptr++);
      len = sptr - cptr;
      if (AttribPtrPtr)
      {
         sptr =  CgiLibVeeMemCalloc (len+1);
         if (!sptr) ErrorExit (vaxc$errno, FI_LI);
         memcpy (sptr, cptr, len);
         sptr[len] = '\0';
      }
      cptr = cptr + len;
   }

   len = MimeDecIsoString (sptr);
   if (len == -1) len = strlen(sptr);

   if (AttribPtrPtr) *AttribPtrPtr = sptr;
   if (AttribLengthPtr) *AttribLengthPtr = len;

   if (WatchEnabled)
      WatchThis (FI_LI, "MIME !UL ATTRIBUTE !#AZ",
                 mdptr->PartCount, cptr-StringPtr, StringPtr);

   return (cptr - StringPtr);
}

/*****************************************************************************/
/*
Strip anything looking like 'TRANS%"<string>"' from the supplied string and
return a pointer to an allocated string containing the stripped results.
*/

char* InetMailStripTransport (char *address)

{
   char  *cptr, *sptr, *zptr;
   char  buffer [4096];

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

   if (Debug) fprintf (stdout, "InetMailStripTransport() |%s|\n", address);

   zptr = (sptr = buffer) + sizeof(buffer)-1;
   cptr = address;
   while (*cptr)
   {
      if (*(USHORTPTR)cptr == '%\"')
      {
         if (sptr > buffer) sptr--;
         while (sptr > buffer &&
                (isalnum(*sptr) || *sptr == '_' || *sptr == '$')) sptr--;
         if (sptr > buffer) sptr++;
         cptr += 2;
         while (*cptr && *cptr != '\"')
         {
            if (sptr < zptr) *sptr++ = *cptr;
            cptr++;
         }
         if (*cptr) cptr++;
      }
      else
      {
         if (sptr < zptr) *sptr++ = *cptr;
         cptr++;
      }
   }
   *sptr = '\0';

   sptr =  CgiLibVeeMemCalloc (sptr-buffer+1);
   strcpy (sptr, buffer);
   return (sptr);
}

/*****************************************************************************/
/*
Process a VMS-style date/time string in the format "31-MAR-2005 20:35:16.32"
returning a pointer to an allocated string in the RFC/Unix-style format
"31 Mar 2005 20:35:16".
*/

char* InetMailRfcVmsDate (char *VmsDateString)

{
   int  cnt, dd, yyyy, hh, mm, ss;
   char  *cptr, *sptr;
   char  mon [32],
         RfcDateString [64];

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

   if (Debug) fprintf (stdout, "InetMailRfcVmsDate() |%s|\n", VmsDateString);

   cnt = sscanf (VmsDateString, "%2d-%3s-%4d %2d:%2d:%2d",
                 &dd, &mon, &yyyy, &hh, &mm, &ss);
   if (cnt != 6) cnt = 0;

   if (dd < 1 || dd > 31) cnt = 0;
   if (strlen(mon) != 3) cnt = 0;
   if (yyyy < 1970 || yyyy > 2099) cnt = 0;
   if (hh < 0 || hh > 24) cnt = 0;
   if (mm < 0 || mm > 59) cnt = 0;
   if (ss < 0 || ss > 59) cnt = 0;

   if (cnt)
   {
      mon[0] = toupper(mon[0]);
      mon[1] = tolower(mon[1]);
      mon[2] = tolower(mon[2]);

      cnt = sprintf (cptr = RfcDateString, "%02d %s %d %02d:%02d:%02d",
                     dd, mon, yyyy, hh, mm, ss);
   }
   else
      cnt = strlen(cptr = VmsDateString);

   sptr =  CgiLibVeeMemCalloc (cnt+1);
   memcpy (sptr, cptr, cnt);

   return (sptr);
}

/*****************************************************************************/
/*
Ensure an RFC822 header line does not exceed maximum allowed length.
*/

int InetMailFoldRfc822Header
(
char *FieldBuffer,
int FieldBufferSize,
char *FieldName,
char *FieldValue
)
{
   int  cnt;
   char  *aptr, *cptr, *sptr, *zptr;

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

   if (Debug)
      fprintf (stdout, "InetMailFoldRfc822Header() %d |%s|%s|\n",
               FieldBufferSize, FieldName, FieldValue);

   /* if field value string is NULL or empty return zero length */
   if (!FieldValue) return (0);
   for (cptr = FieldValue; *cptr && isspace(*cptr); cptr++);
   if (!*cptr) return (0);

   zptr = (sptr = FieldBuffer) + FieldBufferSize;

   for (cptr = FieldName; *cptr && sptr < zptr; *sptr++ = *cptr++);
   if (sptr < zptr) *sptr++ = ' ';

   cptr = FieldValue;
   while (*cptr && sptr < zptr)
   {
      cnt = 0;
      aptr = cptr;
      while (*cptr && sptr < zptr)
      {
         if (cnt++ > SOY_WRAP_AT)
         {
            *sptr = '\0';
            while (cptr > aptr && !isspace(*cptr)) { cptr--; sptr--; }
            if (isspace(*cptr))
            {
               if (sptr < zptr) *sptr++ = '\r';
               if (sptr < zptr) *sptr++ = '\n';
               if (sptr < zptr) *sptr++ = ' ';
               cnt = 0;
               cptr++;
            }
            else
               while (*sptr) { sptr++; cptr++; }
            aptr = cptr;
         }
         if (sptr < zptr)
         {
            if (isprint(*cptr))
               *sptr++ = *cptr;
            else
            if (*cptr == '\n')
               *sptr++ = ' ';
         }
         cptr++;
      }
      if (sptr < zptr) *sptr++ = '\r';
      if (sptr < zptr) *sptr++ = '\n';
   }

   if (sptr >= zptr) return (-1);
   *sptr = '\0';
   return (sptr - FieldBuffer);
}

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

