/******************************************************************************/
/*
                                  mimeenc.c

MIME content encoding.

Plain-text to quoted-printable.
Plain-text to ISO-quoted string.
Any data (blob) into base-64.


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
---------------
24-FEB-2007  MGD  MimeEncQuotedPrintable() bugfix for very small texts
04-JUL-2006  MGD  MimeEncNeedsQuotedPrintable() line length now 250
15-APR-2005  MGD  initial
*/
/******************************************************************************/

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

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

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

#define FI_LI "MIMEENC", __LINE__

/* global storage */

char MimeEncBase64Chars[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ\
abcdefghijklmnopqrstuvwxyz0123456789+/";

#define CHAR64(c)  (MimeEncBase64Chars[(unsigned char)(c)])

char  MimeEncHexDigits [] = "0123456789ABCDEF";

/* external storage */

extern BOOL  Debug;

/******************************************************************************/
/*
Does this text need to encoded quoted-printable?
*/

BOOL MimeEncNeedsQuotedPrintable (char *TextPtr)

{
/* somewhat arbitrary (and now definitely not RFC!) */
#define QP_LONG_LINE 250

   char  *cptr, *sptr;

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

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

   for (cptr = sptr = TextPtr; *cptr; cptr++)
   {
      /* if an 8 bit character */
      if (!isascii(*cptr)) return (1);
      /* if it's far too long */
      if (cptr - sptr > QP_LONG_LINE) return (1);
      if (*cptr == '\r' || *cptr == '\n') sptr = cptr + 1;
   }
   return (0);
}

/******************************************************************************/
/*
Convert plain-text string to quoted-printable.
Returns a pointer to dynamic storeage allocated.
*/

char* MimeEncQuotedPrintable
(
char *string,
int length
)
{
#define QP_LINE_LEN 78

   char  *bptr, *cptr, *lptr, *sptr, *zptr;

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

   if (Debug) fprintf (stdout, "MimeEncQuotedPrintable() |%s|\n", string);

   if (length <= 0) length = strlen(string);

   for (;;)
   {
      /* allow 20% for encoding */
      length += length / 5;
      if (length < 256) length = 256;
      bptr =  CgiLibVeeMemCalloc (length);
      if (!bptr) exit (vaxc$errno);
      zptr = (sptr = bptr) + length-1;
      lptr = sptr + QP_LINE_LEN;

      cptr = string;
      while (*cptr && sptr < zptr)
      {
         if ((*cptr >= 33 && *cptr <= 60) ||
             (*cptr >= 62 && *cptr <= 126) ||
             *cptr == 10 ||
             *cptr == 13)
         {
            if (sptr >= lptr)
            {
               if (sptr < zptr) *sptr++ = '=';
               if (sptr < zptr) *sptr++ = '\n';
               lptr = sptr + QP_LINE_LEN;
            }
            *sptr++ = *cptr;
            if (*cptr == 13) lptr = sptr + QP_LINE_LEN;
            cptr++;
         }
         else
         if ((*cptr == 9 || *cptr == 32) &&
             *(cptr+1) != 10 &&
             *(cptr+1) != 13)
         {
            if (sptr >= lptr)
            {
               if (sptr < zptr) *sptr++ = '=';
               if (sptr < zptr) *sptr++ = '\n';
               lptr = sptr + QP_LINE_LEN;
            }
            *sptr++ = *cptr++;
         }
         else
         {
            if (sptr+3 >= lptr)
            {
               if (sptr < zptr) *sptr++ = '=';
               if (sptr < zptr) *sptr++ = '\n';
               lptr = sptr + QP_LINE_LEN;
            }
            *sptr++ = '=';
            if (sptr < zptr) *sptr++ = MimeEncHexDigits[(*cptr & 0xf0) >> 4];
            if (sptr < zptr) *sptr++ = MimeEncHexDigits[*cptr & 0x0f];
            cptr++;
         }
      }
      *sptr = '\0';
      /* break if the buffer didn't overflow */
      if (sptr < zptr) break;
      CgiLibVeeMemFree (bptr);
   }

   if (Debug) fprintf (stdout, "%d |%s|\n", sptr-bptr, bptr);
   return (bptr);
}

/******************************************************************************/
/*
Convert any data to base-64.
Allocates storage to parameter length plus 40% and populates.
Returns pointer to same, or NULL if there was some issue during the encoding.
The carriage control would 'normally' be "\r\n" but to generate without any can
be "", or even "\n".
*/

char* MimeEncBase64
(
char *DataPtr,
int DataLength,
char *CarCon
)
{
   int  ccnt, llen,
        EncodedSize;
   unsigned char  c1, c2, c3;
   char  *cptr, *ccptr, *czptr, *sptr, *zptr,
         *EncodedPtr;

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

   if (Debug) fprintf (stdout, "MimeEncBase64() %d\n", DataLength);

   /* base-64 requires about 40% more storage than the original data */
   if (DataLength >= 128)
      EncodedSize = DataLength + (DataLength / 2);
   else
      EncodedSize = DataLength + DataLength + DataLength;
   if (Debug) fprintf (stdout, "%d\n", EncodedSize);
   EncodedPtr =  CgiLibVeeMemCalloc (EncodedSize+16);
   if (!EncodedPtr) ErrorExit (vaxc$errno, FI_LI);

   zptr = (sptr = EncodedPtr) + EncodedSize;

   czptr = (cptr = DataPtr) + DataLength;

   llen = 0;
   while (cptr < czptr && sptr < zptr)
   {
      ccnt = 1;
      c1 = (unsigned char)*cptr++;
      if (cptr < czptr)
      {
         c2 = (unsigned char)*cptr++;
         ccnt++;
      }
      else
         c2 = 0;
      if (cptr < czptr)
      {
         c3 = (unsigned char)*cptr++;
         ccnt++;
      }
      else
         c3 = 0;

      *sptr++ = CHAR64 (c1>>2);
      *sptr++ = CHAR64 (((c1 & 0x3)<< 4) | ((c2 & 0xf0) >> 4));
      if (ccnt == 1)
      {
         *sptr++ = '=';
         *sptr++ = '=';
      }
      else
      if (ccnt == 2)
      {
         *sptr++ = CHAR64 (((c2 & 0x0f) << 2) | ((c3 & 0xc0) >> 6));
         *sptr++ = '=';
      }
      else
      {
        *sptr++ = CHAR64 (((c2 & 0xf) << 2) | ((c3 & 0xc0) >> 6));
        *sptr++ = CHAR64 (c3 & 0x3f);
      }
      llen += 4;

      if (llen > 71)
      {
         for (ccptr = CarCon; *ccptr && sptr < zptr; *sptr++ = *ccptr++);
         llen = 0;
      }
   }

   if (llen) for (ccptr = CarCon; *ccptr && sptr < zptr; *sptr++ = *ccptr++);
   *sptr = '\0';

   if (sptr >= zptr)
   {
      if (Debug) fprintf (stdout, "OVERFLOW!\n");
      return (NULL);
   }

   return (EncodedPtr);
}

/******************************************************************************/
/*
Convert plain to ISO-quoted, first checks to ensure it's needed.  Any character
from 1 to 127 (isascii()) is included as-is, only characters with the eighth
bit set are encoded.  Then encodes each space-delimitted 'word'.  By rights
this encoded word should be limited to 76 characters.  This function does not
enforce that.
*/

char* MimeEncIsoString
(
char *String,
char *CharSet
)
{
   char  *cptr, *sptr, *tptr, *zptr;
   char  IsoBuffer [4096];

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

   if (Debug) fprintf (stdout, "MimeEncIsoString() |%s|\n", String);

   /* first check if it contains any '8 bit' characters */
   for (cptr = String; *cptr && isascii(*cptr); cptr++);
   /* nope */
   if (!*cptr) return (String);

   if (!CharSet || !CharSet[0]) CharSet = SOY_DEFAULT_CHARSET;
   zptr = (sptr = IsoBuffer) + sizeof(IsoBuffer)-1;
   cptr = String;

   while (*cptr && sptr < zptr)
   {
      /* copy leading/intervening white-space */
      while (*cptr && (isascii(*cptr) && isspace(*cptr)) && sptr < zptr)
         *sptr++ = *cptr++;
      /* look ahead across this 'word' for a character that needs encoding */
      for (tptr = cptr; *tptr && isascii(*tptr) && !isspace(*tptr); tptr++);
      /* if no character requires encoding then just copy it straight up! */
      if (!*tptr || (isascii(*tptr) && isspace(*tptr)))
      {
         while (*cptr && !(isascii(*cptr) && isspace(*cptr)) && sptr < zptr)
            *sptr++ = *cptr++;
         continue;
      }
      /* needs encoding */
      if (sptr < zptr) *sptr++ = '=';
      if (sptr < zptr) *sptr++ = '?';
      for (tptr = CharSet; *tptr && sptr < zptr; *sptr++ = *tptr++);
      if (sptr < zptr) *sptr++ = '?';
      if (sptr < zptr) *sptr++ = 'Q';
      if (sptr < zptr) *sptr++ = '?';
      while (*cptr && !(isascii(*cptr) && isspace(*cptr)) && sptr < zptr)
      {
         if (isascii(*cptr))
            *sptr++ = *cptr++;
         else
         {
            *sptr++ = '=';
            if (sptr < zptr) *sptr++ = MimeEncHexDigits[(*cptr & 0xf0) >> 4];
            if (sptr < zptr) *sptr++ = MimeEncHexDigits[*cptr & 0x0f];
            cptr++;
         }
      }
      if (sptr < zptr) *sptr++ = '?';
      if (sptr < zptr) *sptr++ = '=';
   }
   if (sptr >= zptr) ErrorExit (SS$_BUGCHECK, FI_LI);  
   *sptr = '\0';

   cptr =  CgiLibVeeMemCalloc ((sptr-IsoBuffer)+1);
   if (!cptr) ErrorExit (vaxc$errno, FI_LI); 
   strcpy (cptr, IsoBuffer);

   if (Debug) fprintf (stdout, "|%s|\n", cptr);
   return (cptr);
}

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

