/*************************************************
*     Exim - an Internet mail transport agent    *
*************************************************/

/* Copyright (c) The Exim Maintainers 2023 - 2026 */
/* Copyright (c) University of Cambridge 1995 - 2018 */
/* See the file NOTICE for conditions of use and distribution. */
/* SPDX-License-Identifier: GPL-2.0-or-later */

#include "exim.h"

#ifdef SUPPORT_I18N

const uschar *
imap_utf7_encode(const uschar * string, const uschar * charset, uschar sep,
  const uschar * specials, uschar ** error)
{
static uschar encode_base64[64] =
  "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+,";
size_t slen;
const uschar * sptr;
gstring * yield = NULL;
int i = 0;	/* compiler quietening */
uschar c = 0;	/* compiler quietening */
BOOL base64mode = FALSE;
BOOL lastsep = FALSE;
uschar utf16buf[256];
uschar *utf16ptr;
const uschar * s;
uschar outbuf[256];
uschar *outptr = outbuf;
#if HAVE_ICONV
iconv_t icd;
#endif

if (!specials) specials = US"";

/* Pass over the string. If it consists entirely of "normal" characters
   (possibly with leading seps), return it as is. */
for (s = string; *s; s++)
  {
  if (s == string && *s == sep)
    string++;
  if (  *s >= 0x7f
     || *s < 0x20
     || strchr("./&", *s)
     || *s == sep
     || Ustrchr(specials, *s)
     )
    break;
  }

if (!*s)
  return string;

sptr = string;
slen = Ustrlen(string);

#if HAVE_ICONV
if ((icd = iconv_open("UTF-16BE", CCS charset)) == (iconv_t)-1)
  {
  *error = string_sprintf(
	"imapfolder: iconv_open(\"UTF-16BE\", %q) failed: %s%s",
    charset, strerror(errno),
    errno == EINVAL ? " (maybe unsupported conversion)" : "");
  return NULL;
  }
#endif

while (slen > 0)
  {
#if HAVE_ICONV
  size_t left = sizeof(utf16buf);
  utf16ptr = utf16buf;

  if (  iconv(icd, (ICONV_ARG2_TYPE)&sptr, &slen, CSS &utf16ptr, &left)
		== (size_t)-1
     && errno != E2BIG
	 )
    {
    *error = string_sprintf("imapfolder: iconv() failed to convert from %s: %s",
			      charset, strerror(errno));
    iconv_close(icd);
    return NULL;
    }
#else
  for (utf16ptr = utf16buf;
       slen > 0 && (utf16ptr - utf16buf) < sizeof(utf16buf);
       utf16ptr += 2, slen--, sptr++)
    {
    *utf16ptr = *sptr;
    *(utf16ptr+1) = '\0';
    }
#endif

  s = utf16buf;
  while (s < utf16ptr)
    {
    /* Now encode utf16buf as modified UTF-7 */
    if (  s[0] != 0
       || s[1] >= 0x7f
       || s[1] < 0x20
       || (Ustrchr(specials, s[1]) && s[1] != sep)
       )
      {
      lastsep = FALSE;
      /* Encode as modified BASE64 */
      if (!base64mode)
        {
        *outptr++ = '&';
        base64mode = TRUE;
        i = 0;
        }

      for (int j = 0; j < 2; j++, s++) switch (i++)
	{
	case 0:
	  /* Top 6 bits of the first octet */
	  *outptr++ = encode_base64[(*s >> 2) & 0x3F];
	  c = (*s & 0x03); break;
	case 1:
	  /* Bottom 2 bits of the first octet, and top 4 bits of the second */
	  *outptr++ = encode_base64[(c << 4) | ((*s >> 4) & 0x0F)];
	  c = (*s & 0x0F); break;
	case 2:
	  /* Bottom 4 bits of the second octet and top 2 bits of the third */
	  *outptr++ = encode_base64[(c << 2) | ((*s >> 6) & 0x03)];
	  /* Bottom 6 bits of the third octet */
	  *outptr++ = encode_base64[*s & 0x3F];
	  i = 0;
	}
      }

    else if (  (s[1] != '.' && s[1] != '/')
	    || s[1] == sep
	    )
      {
      /* Encode as self (almost) */
      if (base64mode)
        {
        switch (i)
          {
          case 1:
		/* Remaining bottom 2 bits of the last octet */
		*outptr++ = encode_base64[c << 4];
		break;
	  case 2:
		/* Remaining bottom 4 bits of the last octet */
		*outptr++ = encode_base64[c << 2];
	  }
	*outptr++ = '-';
	base64mode = FALSE;
	}

      if (*++s == sep)
	{
	if (!lastsep)
	  {
	  *outptr++ = '.';
	  lastsep = TRUE;
	  }
	}
      else
        {
        *outptr++ = *s;
        if (*s == '&')
	  *outptr++ = '-';
	lastsep = FALSE;
        }

      s++;
      }
    else
      {
      *error = string_sprintf("imapfolder: illegal character '%c'", s[1]);
      return NULL;
      }

    if (outptr > outbuf + sizeof(outbuf) - 3)
      {
      yield = string_catn(yield, outbuf, outptr - outbuf);
      outptr = outbuf;
      }

    }
  } /* End of input string */

if (base64mode)
  {
  switch (i)
    {
    case 1:
      /* Remaining bottom 2 bits of the last octet */
      *outptr++ = encode_base64[c << 4];
      break;
    case 2:
      /* Remaining bottom 4 bits of the last octet */
      *outptr++ = encode_base64[c << 2];
    }
  *outptr++ = '-';
  }

#if HAVE_ICONV
iconv_close(icd);
#endif

yield = string_catn(yield, outbuf, outptr - outbuf);
gstring_trim_trailing(yield, '.');

return string_from_gstring(yield);
}

#endif	/* whole file */
/* vi: aw ai sw=2
*/
