/* ========================================================================== */
/*! \file
 * \brief Shared encoding related functions
 *
 * Copyright (c) 2012-2016 by the developers. See the LICENSE file for details.
 *
 * If nothing else is specified, functions return zero to indicate success
 * and a negative value to indicate an error.
 */


/* ========================================================================== */
/* Include headers */

#include "posix.h"  /* Include this first because of feature test macros */

#include <ctype.h>
#include <stddef.h>
#include <string.h>

#include "encoding.h"
#include "main.h"


/* ========================================================================== */
/* Constants */

/* Define this to nonzero to enable Unicode NFC normalization debugging */
#define ENC_UC_NORM_DEBUG  0

/*! \brief Maximum length of MIME parameter attribute tokens */
#define ENC_MIME_PARA_LENGTH_MAX  (size_t) 127


/* ========================================================================== */
/*! \addtogroup ENCODING
 *
 * The functions in this group should be compliant to the following standards:
 * ANSI X3.4,
 * ISO2022, ISO8601, ISO8859, ISO10646,
 * RFC1468, RFC2045, RFC2046, RFC2047, RFC2049, RFC2152, RFC2231, RFC2646,
 * RFC3629, RFC3676, RFC5198, RFC5536, RFC6657,
 * POSIX.1-1996,
 * Unicode 9
 *
 * \todo
 * We don't use \c iconv() because on old operating systems there may be no
 * Unicode support. And even on such old machines we don't want an external
 * dependency from GNU iconv.
 * <br>
 * There should be an option to use the systems \c iconv() on request.
 */
/* @{ */


/* ========================================================================== */
/* Data types */

/* ISO2022 decoder states */
enum iso2022_state
{
   ISO2022_ASCII,
   ISO2022_ISO646,
   ISO2022_JIS_X_0208
};

/* Unicode hangul syllable type */
enum uc_hs_type
{
   UC_HST_NONE,
   UC_HST_L,
   UC_HST_V,
   UC_HST_T,
   UC_HST_LV,
   UC_HST_LVT
};

/* Unicode canonical decomposition (and character combining class) */
struct uc_cdc
{
   long int  cp;          /* Codepoint */
   unsigned char  ccc;    /* Canonical combining class */
   long int  dc1;         /* Decomposition mapping (recursive part 1) */
   long int  dc2;         /* Decomposition mapping (non-recursive part 2) */
};

/* Unicode hangul syllable type ranges */
struct uc_hst
{
   long int  first;       /* First codepoint of range */
   long int  last;        /* Last codepoint of range */
   enum uc_hs_type  hst;  /* Hangul syllable type */
};

/* Unicode NFC quick check codepoint ranges indicating normalization required */
struct uc_qc_nfc
{
   long int  first;       /* First codepoint of range */
   long int  last;        /* Last codepoint of range */
};

/* Unicode full composition exclusion codepoint ranges */
struct uc_fce
{
   long int  first;       /* First codepoint of range */
   long int  last;        /* Last codepoint of range */
};

/* IOS2022-JP to Unicode codepoint mapping */
struct iso2022_jp
{
   long int  jis;         /* JIS X 0208 codepoint */
   long int  uc;          /* Unicode codepoint */
};

/* MIME parameter (for RFC2231 decoder) */
struct mime_parameter
{
   int  valid;
   char  attribute[ENC_MIME_PARA_LENGTH_MAX + 1];
   size_t  attribute_len;
   unsigned int  section;
   char  charset[ENC_MIME_PARA_LENGTH_MAX + 1];
   const char*  value_start;
   const char*  value_end;
};


/* ========================================================================== */
/* Constants */

/*
 * Size of Unicode decomposition buffer
 * Minimum size: 8
 */
#define ENC_UC_DECOMPOSITION_BUFSIZE  (size_t) 16

/* Maximum size of header line */
#define ENC_HDR_BUFSIZE  (size_t) 998

/* Buffer size for "Format" parameter of MIME "Content-Type" header field */
#define ENC_FMT_BUFLEN  (size_t) 7

/* Unicode canonical decomposition data */
#include "../uc_cdc.c"

/* Unicode hangul syllable data */
#include "../uc_hst.c"

/* Unicode NFC quick check data */
#include "../uc_qc_nfc.c"

/* Unicode full composition exclusion data */
#include "../uc_fce.c"

/* ISO2022-JP to Unicode mapping data */
#include "../iso2022-jp.c"

/* MIME base64 encoding table */
static const char  enc_base64[64] =
{
   'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H',
   'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P',
   'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X',
   'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f',
   'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n',
   'o', 'p', 'q', 'r', 's', 't', 'u', 'v',
   'w', 'x', 'y', 'z', '0', '1', '2', '3',
   '4', '5', '6', '7', '8', '9', '+', '/'
};


/* ========================================================================== */
/* Character sets */
/*
 * Lines with overlength are accepted here to keep 16 (one hexadecimal digit)
 * character codes on all lines. This is the most common representation in code
 * tables.
 */

/* UTF-8 NFC sequences for upper half of ISO8859-1 character set */
static const char*  enc_iso8859_1[128] =
{
   "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
   "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
   "\xC2\xA0", "\xC2\xA1", "\xC2\xA2", "\xC2\xA3", "\xC2\xA4", "\xC2\xA5", "\xC2\xA6", "\xC2\xA7", "\xC2\xA8", "\xC2\xA9", "\xC2\xAA", "\xC2\xAB", "\xC2\xAC", "\xC2\xAD", "\xC2\xAE", "\xC2\xAF",
   "\xC2\xB0", "\xC2\xB1", "\xC2\xB2", "\xC2\xB3", "\xC2\xB4", "\xC2\xB5", "\xC2\xB6", "\xC2\xB7", "\xC2\xB8", "\xC2\xB9", "\xC2\xBA", "\xC2\xBB", "\xC2\xBC", "\xC2\xBD", "\xC2\xBE", "\xC2\xBF",
   "\xC3\x80", "\xC3\x81", "\xC3\x82", "\xC3\x83", "\xC3\x84", "\xC3\x85", "\xC3\x86", "\xC3\x87", "\xC3\x88", "\xC3\x89", "\xC3\x8A", "\xC3\x8B", "\xC3\x8C", "\xC3\x8D", "\xC3\x8E", "\xC3\x8F",
   "\xC3\x90", "\xC3\x91", "\xC3\x92", "\xC3\x93", "\xC3\x94", "\xC3\x95", "\xC3\x96", "\xC3\x97", "\xC3\x98", "\xC3\x99", "\xC3\x9A", "\xC3\x9B", "\xC3\x9C", "\xC3\x9D", "\xC3\x9E", "\xC3\x9F",
   "\xC3\xA0", "\xC3\xA1", "\xC3\xA2", "\xC3\xA3", "\xC3\xA4", "\xC3\xA5", "\xC3\xA6", "\xC3\xA7", "\xC3\xA8", "\xC3\xA9", "\xC3\xAA", "\xC3\xAB", "\xC3\xAC", "\xC3\xAD", "\xC3\xAE", "\xC3\xAF",
   "\xC3\xB0", "\xC3\xB1", "\xC3\xB2", "\xC3\xB3", "\xC3\xB4", "\xC3\xB5", "\xC3\xB6", "\xC3\xB7", "\xC3\xB8", "\xC3\xB9", "\xC3\xBA", "\xC3\xBB", "\xC3\xBC", "\xC3\xBD", "\xC3\xBE", "\xC3\xBF"
};

/* UTF-8 NFC sequences for upper half of ISO8859-2 character set */
static const char*  enc_iso8859_2[128] =
{
   "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
   "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
   "\xC2\xA0", "\xC4\x84", "\xCB\x98", "\xC5\x81", "\xC2\xA4", "\xC4\xBD", "\xC5\x9A", "\xC2\xA7", "\xC2\xA8", "\xC5\xA0", "\xC5\x9E", "\xC5\xA4", "\xC5\xB9", "\xC2\xAD", "\xC5\xBD", "\xC5\xBB",
   "\xC2\xB0", "\xC4\x85", "\xCB\x9B", "\xC5\x82", "\xC2\xB4", "\xC4\xBE", "\xC5\x9B", "\xCB\x87", "\xC2\xB8", "\xC5\xA1", "\xC5\x9F", "\xC5\xA5", "\xC5\xBA", "\xCB\x9D", "\xC5\xBE", "\xC5\xBC",
   "\xC5\x94", "\xC3\x81", "\xC3\x82", "\xC4\x82", "\xC3\x84", "\xC4\xB9", "\xC4\x86", "\xC3\x87", "\xC4\x8C", "\xC3\x89", "\xC4\x98", "\xC3\x8B", "\xC4\x9A", "\xC3\x8D", "\xC3\x8E", "\xC4\x8E",
   "\xC4\x90", "\xC5\x83", "\xC5\x87", "\xC3\x93", "\xC3\x94", "\xC5\x90", "\xC3\x96", "\xC3\x97", "\xC5\x98", "\xC5\xAE", "\xC3\x9A", "\xC5\xB0", "\xC3\x9C", "\xC3\x9D", "\xC5\xA2", "\xC3\x9F",
   "\xC5\x95", "\xC3\xA1", "\xC3\xA2", "\xC4\x83", "\xC3\xA4", "\xC4\xBA", "\xC4\x87", "\xC3\xA7", "\xC4\x8D", "\xC3\xA9", "\xC4\x99", "\xC3\xAB", "\xC4\x9B", "\xC3\xAD", "\xC3\xAE", "\xC4\x8F",
   "\xC4\x91", "\xC5\x84", "\xC5\x88", "\xC3\xB3", "\xC3\xB4", "\xC5\x91", "\xC3\xB6", "\xC3\xB7", "\xC5\x99", "\xC5\xAF", "\xC3\xBA", "\xC5\xB1", "\xC3\xBC", "\xC3\xBD", "\xC5\xA3", "\xCB\x99"
};

/* UTF-8 NFC sequences for upper half of ISO8859-3 character set */
static const char*  enc_iso8859_3[128] =
{
   "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
   "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
   "\xC2\xA0", "\xC4\xA6", "\xCB\x98", "\xC2\xA3", "\xC2\xA4", "", "\xC4\xA4", "\xC2\xA7", "\xC2\xA8", "\xC4\xB0", "\xC5\x9E", "\xC4\x9E", "\xC4\xB4", "\xC2\xAD", "", "\xC5\xBB",
   "\xC2\xB0", "\xC4\xA7", "\xC2\xB2", "\xC2\xB3", "\xC2\xB4", "\xC2\xB5", "\xC4\xA5", "\xC2\xB7", "\xC2\xB8", "\xC4\xB1", "\xC5\x9F", "\xC4\x9F", "\xC4\xB5", "\xC2\xBD", "", "\xC5\xBC",
   "\xC3\x80", "\xC3\x81", "\xC3\x82", "", "\xC3\x84", "\xC4\x8A", "\xC4\x88", "\xC3\x87", "\xC3\x88", "\xC3\x89", "\xC3\x8A", "\xC3\x8B", "\xC3\x8C", "\xC3\x8D", "\xC3\x8E", "\xC3\x8F",
   "", "\xC3\x91", "\xC3\x92", "\xC3\x93", "\xC3\x94", "\xC4\xA0", "\xC3\x96", "\xC3\x97", "\xC4\x9C", "\xC3\x99", "\xC3\x9A", "\xC3\x9B", "\xC3\x9C", "\xC5\xAC", "\xC5\x9C", "\xC3\x9F",
   "\xC3\xA0", "\xC3\xA1", "\xC3\xA2", "", "\xC3\xA4", "\xC4\x8B", "\xC4\x89", "\xC3\xA7", "\xC3\xA8", "\xC3\xA9", "\xC3\xAA", "\xC3\xAB", "\xC3\xAC", "\xC3\xAD", "\xC3\xAE", "\xC3\xAF",
   "", "\xC3\xB1", "\xC3\xB2", "\xC3\xB3", "\xC3\xB4", "\xC4\xA1", "\xC3\xB6", "\xC3\xB7", "\xC4\x9D", "\xC3\xB9", "\xC3\xBA", "\xC3\xBB", "\xC3\xBC", "\xC5\xAD", "\xC5\x9D", "\xCB\x99"
};

/* UTF-8 NFC sequences for upper half of ISO8859-4 character set */
static const char*  enc_iso8859_4[128] =
{
   "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
   "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
   "\xC2\xA0", "\xC4\x84", "\xC4\xB8", "\xC5\x96", "\xC2\xA4", "\xC4\xA8", "\xC4\xBB", "\xC2\xA7", "\xC2\xA8", "\xC5\xA0", "\xC4\x92", "\xC4\xA2", "\xC5\xA6", "\xC2\xAD", "\xC5\xBD", "\xC2\xAF",
   "\xC2\xB0", "\xC4\x85", "\xCB\x9B", "\xC5\x97", "\xC2\xB4", "\xC4\xA9", "\xC4\xBC", "\xCB\x87", "\xC2\xB8", "\xC5\xA1", "\xC4\x93", "\xC4\xA3", "\xC5\xA7", "\xC5\x8A", "\xC5\xBE", "\xC5\x8B",
   "\xC4\x80", "\xC3\x81", "\xC3\x82", "\xC3\x83", "\xC3\x84", "\xC3\x85", "\xC3\x86", "\xC4\xAE", "\xC4\x8C", "\xC3\x89", "\xC4\x98", "\xC3\x8B", "\xC4\x96", "\xC3\x8D", "\xC3\x8E", "\xC4\xAA",
   "\xC4\x90", "\xC5\x85", "\xC5\x8C", "\xC4\xB6", "\xC3\x94", "\xC3\x95", "\xC3\x96", "\xC3\x97", "\xC3\x98", "\xC5\xB2", "\xC3\x9A", "\xC3\x9B", "\xC3\x9C", "\xC5\xA8", "\xC5\xAA", "\xC3\x9F",
   "\xC4\x81", "\xC3\xA1", "\xC3\xA2", "\xC3\xA3", "\xC3\xA4", "\xC3\xA5", "\xC3\xA6", "\xC4\xAF", "\xC4\x8D", "\xC3\xA9", "\xC4\x99", "\xC3\xAB", "\xC4\x97", "\xC3\xAD", "\xC3\xAE", "\xC4\xAB",
   "\xC4\x91", "\xC5\x86", "\xC5\x8D", "\xC4\xB7", "\xC3\xB4", "\xC3\xB5", "\xC3\xB6", "\xC3\xB7", "\xC3\xB8", "\xC5\xB3", "\xC3\xBA", "\xC3\xBB", "\xC3\xBC", "\xC5\xA9", "\xC5\xAB", "\xCB\x99"
};

/* UTF-8 NFC sequences for upper half of ISO8859-5 character set */
static const char*  enc_iso8859_5[128] =
{
   "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
   "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
   "\xC2\xA0", "\xD0\x81", "\xD0\x82", "\xD0\x83", "\xD0\x84", "\xD0\x85", "\xD0\x86", "\xD0\x87", "\xD0\x88", "\xD0\x89", "\xD0\x8A", "\xD0\x8B", "\xD0\x8C", "\xC2\xAD", "\xD0\x8E", "\xD0\x8F",
   "\xD0\x90", "\xD0\x91", "\xD0\x92", "\xD0\x93", "\xD0\x94", "\xD0\x95", "\xD0\x96", "\xD0\x97", "\xD0\x98", "\xD0\x99", "\xD0\x9A", "\xD0\x9B", "\xD0\x9C", "\xD0\x9D", "\xD0\x9E", "\xD0\x9F",
   "\xD0\xA0", "\xD0\xA1", "\xD0\xA2", "\xD0\xA3", "\xD0\xA4", "\xD0\xA5", "\xD0\xA6", "\xD0\xA7", "\xD0\xA8", "\xD0\xA9", "\xD0\xAA", "\xD0\xAB", "\xD0\xAC", "\xD0\xAD", "\xD0\xAE", "\xD0\xAF",
   "\xD0\xB0", "\xD0\xB1", "\xD0\xB2", "\xD0\xB3", "\xD0\xB4", "\xD0\xB5", "\xD0\xB6", "\xD0\xB7", "\xD0\xB8", "\xD0\xB9", "\xD0\xBA", "\xD0\xBB", "\xD0\xBC", "\xD0\xBD", "\xD0\xBE", "\xD0\xBF",
   "\xD1\x80", "\xD1\x81", "\xD1\x82", "\xD1\x83", "\xD1\x84", "\xD1\x85", "\xD1\x86", "\xD1\x87", "\xD1\x88", "\xD1\x89", "\xD1\x8A", "\xD1\x8B", "\xD1\x8C", "\xD1\x8D", "\xD1\x8E", "\xD1\x8F",
   "\xE2\x84\x96", "\xD1\x91", "\xD1\x92", "\xD1\x93", "\xD1\x94", "\xD1\x95", "\xD1\x96", "\xD1\x97", "\xD1\x98", "\xD1\x99", "\xD1\x9A", "\xD1\x9B", "\xD1\x9C", "\xC2\xA7", "\xD1\x9E", "\xD1\x9F"
};

/* UTF-8 NFC sequences for upper half of ISO8859-6 character set */
static const char*  enc_iso8859_6[128] =
{
   "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
   "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
   "\xC2\xA0", "", "", "", "\xC2\xA4", "", "", "", "", "", "", "", "\xD8\x8C", "\xC2\xAD", "", "",
   "", "", "", "", "", "", "", "", "", "", "", "\xD8\x9B", "", "", "", "\xD8\x9F",
   "", "\xD8\xA1", "\xD8\xA2", "\xD8\xA3", "\xD8\xA4", "\xD8\xA5", "\xD8\xA6", "\xD8\xA7", "\xD8\xA8", "\xD8\xA9", "\xD8\xAA", "\xD8\xAB", "\xD8\xAC", "\xD8\xAD", "\xD8\xAE", "\xD8\xAF",
   "\xD8\xB0", "\xD8\xB1", "\xD8\xB2", "\xD8\xB3", "\xD8\xB4", "\xD8\xB5", "\xD8\xB6", "\xD8\xB7", "\xD8\xB8", "\xD8\xB9", "\xD8\xBA", "", "", "", "", "",
   "\xD9\x80", "\xD9\x81", "\xD9\x82", "\xD9\x83", "\xD9\x84", "\xD9\x85", "\xD9\x86", "\xD9\x87", "\xD9\x88", "\xD9\x89", "\xD9\x8A", "\xD9\x8B", "\xD9\x8C", "\xD9\x8D", "\xD9\x8E", "\xD9\x8F",
   "\xD9\x90", "\xD9\x91", "\xD9\x92", "", "", "", "", "", "", "", "", "", "", "", "", ""
};

/* UTF-8 NFC sequences for upper half of ISO8859-7 character set */
static const char*  enc_iso8859_7[128] =
{
   "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
   "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
   "\xC2\xA0", "\xE2\x80\x98", "\xE2\x80\x99", "\xC2\xA3", "\xE2\x82\xAC", "\xE2\x82\xAF", "\xC2\xA6", "\xC2\xA7", "\xC2\xA8", "\xC2\xA9", "\xCD\xBA", "\xC2\xAB", "\xC2\xAC", "\xC2\xAD", "", "\xE2\x80\x95",
   "\xC2\xB0", "\xC2\xB1", "\xC2\xB2", "\xC2\xB3", "\xCE\x84", "\xCE\x85", "\xCE\x86", "\xC2\xB7", "\xCE\x88", "\xCE\x89", "\xCE\x8A", "\xC2\xBB", "\xCE\x8C", "\xC2\xBD", "\xCE\x8E", "\xCE\x8F",
   "\xCE\x90", "\xCE\x91", "\xCE\x92", "\xCE\x93", "\xCE\x94", "\xCE\x95", "\xCE\x96", "\xCE\x97", "\xCE\x98", "\xCE\x99", "\xCE\x9A", "\xCE\x9B", "\xCE\x9C", "\xCE\x9D", "\xCE\x9E", "\xCE\x9F",
   "\xCE\xA0", "\xCE\xA1", "", "\xCE\xA3", "\xCE\xA4", "\xCE\xA5", "\xCE\xA6", "\xCE\xA7", "\xCE\xA8", "\xCE\xA9", "\xCE\xAA", "\xCE\xAB", "\xCE\xAC", "\xCE\xAD", "\xCE\xAE", "\xCE\xAF",
   "\xCE\xB0", "\xCE\xB1", "\xCE\xB2", "\xCE\xB3", "\xCE\xB4", "\xCE\xB5", "\xCE\xB6", "\xCE\xB7", "\xCE\xB8", "\xCE\xB9", "\xCE\xBA", "\xCE\xBB", "\xCE\xBC", "\xCE\xBD", "\xCE\xBE", "\xCE\xBF",
   "\xCF\x80", "\xCF\x81", "\xCF\x82", "\xCF\x83", "\xCF\x84", "\xCF\x85", "\xCF\x86", "\xCF\x87", "\xCF\x88", "\xCF\x89", "\xCF\x8A", "\xCF\x8B", "\xCF\x8C", "\xCF\x8D", "\xCF\x8E", ""
};

/* UTF-8 NFC sequences for upper half of ISO8859-8 character set */
static const char*  enc_iso8859_8[128] =
{
   "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
   "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
   "\xC2\xA0", "", "\xC2\xA2", "\xC2\xA3", "\xC2\xA4", "\xC2\xA5", "\xC2\xA6", "\xC2\xA7", "\xC2\xA8", "\xC2\xA9", "\xC3\x97", "\xC2\xAB", "\xC2\xAC", "\xC2\xAD", "\xC2\xAE", "\xC2\xAF",
   "\xC2\xB0", "\xC2\xB1", "\xC2\xB2", "\xC2\xB3", "\xC2\xB4", "\xC2\xB5", "\xC2\xB6", "\xC2\xB7", "\xC2\xB8", "\xC2\xB9", "\xC3\xB7", "\xC2\xBB", "\xC2\xBC", "\xC2\xBD", "\xC2\xBE", "",
   "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
   "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "\xE2\x80\x97",
   "\xD7\x90", "\xD7\x91", "\xD7\x92", "\xD7\x93", "\xD7\x94", "\xD7\x95", "\xD7\x96", "\xD7\x97", "\xD7\x98", "\xD7\x99", "\xD7\x9A", "\xD7\x9B", "\xD7\x9C", "\xD7\x9D", "\xD7\x9E", "\xD7\x9F",
   "\xD7\xA0", "\xD7\xA1", "\xD7\xA2", "\xD7\xA3", "\xD7\xA4", "\xD7\xA5", "\xD7\xA6", "\xD7\xA7", "\xD7\xA8", "\xD7\xA9", "\xD7\xAA", "", "", "\xE2\x80\x8E", "\xE2\x80\x8F", ""
};

/* UTF-8 NFC sequences for upper half of ISO8859-9 character set */
static const char*  enc_iso8859_9[128] =
{
   "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
   "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
   "\xC2\xA0", "\xC2\xA1", "\xC2\xA2", "\xC2\xA3", "\xC2\xA4", "\xC2\xA5", "\xC2\xA6", "\xC2\xA7", "\xC2\xA8", "\xC2\xA9", "\xC2\xAA", "\xC2\xAB", "\xC2\xAC", "\xC2\xAD", "\xC2\xAE", "\xC2\xAF",
   "\xC2\xB0", "\xC2\xB1", "\xC2\xB2", "\xC2\xB3", "\xC2\xB4", "\xC2\xB5", "\xC2\xB6", "\xC2\xB7", "\xC2\xB8", "\xC2\xB9", "\xC2\xBA", "\xC2\xBB", "\xC2\xBC", "\xC2\xBD", "\xC2\xBE", "\xC2\xBF",
   "\xC3\x80", "\xC3\x81", "\xC3\x82", "\xC3\x83", "\xC3\x84", "\xC3\x85", "\xC3\x86", "\xC3\x87", "\xC3\x88", "\xC3\x89", "\xC3\x8A", "\xC3\x8B", "\xC3\x8C", "\xC3\x8D", "\xC3\x8E", "\xC3\x8F",
   "\xC4\x9E", "\xC3\x91", "\xC3\x92", "\xC3\x93", "\xC3\x94", "\xC3\x95", "\xC3\x96", "\xC3\x97", "\xC3\x98", "\xC3\x99", "\xC3\x9A", "\xC3\x9B", "\xC3\x9C", "\xC4\xB0", "\xC5\x9E", "\xC3\x9F",
   "\xC3\xA0", "\xC3\xA1", "\xC3\xA2", "\xC3\xA3", "\xC3\xA4", "\xC3\xA5", "\xC3\xA6", "\xC3\xA7", "\xC3\xA8", "\xC3\xA9", "\xC3\xAA", "\xC3\xAB", "\xC3\xAC", "\xC3\xAD", "\xC3\xAE", "\xC3\xAF",
   "\xC4\x9F", "\xC3\xB1", "\xC3\xB2", "\xC3\xB3", "\xC3\xB4", "\xC3\xB5", "\xC3\xB6", "\xC3\xB7", "\xC3\xB8", "\xC3\xB9", "\xC3\xBA", "\xC3\xBB", "\xC3\xBC", "\xC4\xB1", "\xC5\x9F", "\xC3\xBF"
};

/* UTF-8 NFC sequences for upper half of ISO8859-10 character set */
static const char*  enc_iso8859_10[128] =
{
   "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
   "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
   "\xC2\xA0", "\xC4\x84", "\xC4\x93", "\xC4\xA2", "\xC4\xAA", "\xC4\xA8", "\xC4\xB6", "\xC2\xA7", "\xC4\xBB", "\xC4\x90", "\xC5\xA0", "\xC5\xA6", "\xC5\xBD", "\xC2\xAD", "\xC5\xAA", "\xC5\x8A",
   "\xC2\xB0", "\xC4\x85", "\xC4\x93", "\xC4\xA3", "\xC4\xAB", "\xC4\xA9", "\xC4\xB7", "\xC2\xB7", "\xC4\xBC", "\xC4\x91", "\xC5\xA1", "\xC5\xA7", "\xC5\xBE", "\xE2\x80\x95", "\xC5\xAB", "\xC5\x8B",
   "\xC4\x80", "\xC3\x81", "\xC3\x82", "\xC3\x83", "\xC3\x84", "\xC3\x85", "\xC3\x86", "\xC4\xAE", "\xC4\x8C", "\xC3\x89", "\xC4\x98", "\xC3\x8B", "\xC4\x96", "\xC3\x8D", "\xC3\x8E", "\xC3\x8F",
   "\xC3\x90", "\xC5\x85", "\xC5\x8C", "\xC3\x93", "\xC3\x94", "\xC3\x95", "\xC3\x96", "\xC5\xA8", "\xC3\x98", "\xC5\xB2", "\xC3\x9A", "\xC3\x9B", "\xC3\x9C", "\xC3\x9D", "\xC3\x9E", "\xC3\x9F",
   "\xC4\x81", "\xC3\xA1", "\xC3\xA2", "\xC3\xA3", "\xC3\xA4", "\xC3\xA5", "\xC3\xA6", "\xC4\xAF", "\xC4\x8D", "\xC3\xA9", "\xC4\x99", "\xC3\xAB", "\xC4\x97", "\xC3\xAD", "\xC3\xAE", "\xC3\xAF",
   "\xC3\xB0", "\xC5\x86", "\xC5\x8D", "\xC3\xB3", "\xC3\xB4", "\xC3\xB5", "\xC3\xB6", "\xC5\xA9", "\xC3\xB8", "\xC5\xB3", "\xC3\xBA", "\xC3\xBB", "\xC3\xBC", "\xC3\xBD", "\xC3\xBE", "\xC4\xB8"
};

/* UTF-8 NFC sequences for upper half of ISO8859-11 character set */
static const char*  enc_iso8859_11[128] =
{
   "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
   "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
   "\xC2\xA0", "\xE0\xB8\x81", "\xE0\xB8\x82", "\xE0\xB8\x83", "\xE0\xB8\x84", "\xE0\xB8\x85", "\xE0\xB8\x86", "\xE0\xB8\x87", "\xE0\xB8\x88", "\xE0\xB8\x89", "\xE0\xB8\x8A", "\xE0\xB8\x8B", "\xE0\xB8\x8C", "\xE0\xB8\x8D", "\xE0\xB8\x8E", "\xE0\xB8\x8F",
   "\xE0\xB8\x90", "\xE0\xB8\x91", "\xE0\xB8\x92", "\xE0\xB8\x93", "\xE0\xB8\x94", "\xE0\xB8\x95", "\xE0\xB8\x96", "\xE0\xB8\x97", "\xE0\xB8\x98", "\xE0\xB8\x99", "\xE0\xB8\x9A", "\xE0\xB8\x9B", "\xE0\xB8\x9C", "\xE0\xB8\x9D", "\xE0\xB8\x9E", "\xE0\xB8\x9F",
   "\xE0\xB8\xA0", "\xE0\xB8\xA1", "\xE0\xB8\xA2", "\xE0\xB8\xA3", "\xE0\xB8\xA4", "\xE0\xB8\xA5", "\xE0\xB8\xA6", "\xE0\xB8\xA7", "\xE0\xB8\xA8", "\xE0\xB8\xA9", "\xE0\xB8\xAA", "\xE0\xB8\xAB", "\xE0\xB8\xAC", "\xE0\xB8\xAD", "\xE0\xB8\xAE", "\xE0\xB8\xAF",
   "\xE0\xB8\xB0", "\xE0\xB8\xB1", "\xE0\xB8\xB2", "\xE0\xB8\xB3", "\xE0\xB8\xB4", "\xE0\xB8\xB5", "\xE0\xB8\xB6", "\xE0\xB8\xB7", "\xE0\xB8\xB8", "\xE0\xB8\xB9", "\xE0\xB8\xBA", "", "", "", "", "\xE0\xB8\xBF",
   "\xE0\xB9\x80", "\xE0\xB9\x81", "\xE0\xB9\x82", "\xE0\xB9\x83", "\xE0\xB9\x84", "\xE0\xB9\x85", "\xE0\xB9\x86", "\xE0\xB9\x87", "\xE0\xB9\x88", "\xE0\xB9\x89", "\xE0\xB9\x8A", "\xE0\xB9\x8B", "\xE0\xB9\x8C", "\xE0\xB9\x8D", "\xE0\xB9\x8E", "\xE0\xB9\x8F",
   "\xE0\xB9\x90", "\xE0\xB9\x91", "\xE0\xB9\x92", "\xE0\xB9\x93", "\xE0\xB9\x94", "\xE0\xB9\x95", "\xE0\xB9\x96", "\xE0\xB9\x97", "\xE0\xB9\x98", "\xE0\xB9\x99", "\xE0\xB9\x9A", "\xE0\xB9\x9B", "", "", "", ""
};

/* UTF-8 NFC sequences for upper half of ISO8859-13 character set */
static const char*  enc_iso8859_13[128] =
{
   "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
   "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
   "\xC2\xA0", "\xE2\x80\x9D", "\xC2\xA2", "\xC4\xA3", "\xC2\xA4", "\xE2\x80\x9E", "\xC2\xA6", "\xC2\xA7", "\xC3\x98", "\xC2\xA9", "\xC5\x96", "\xC2\x9B", "\xC2\x9C", "\xC2\x9D", "\xC2\x9E", "\xC3\x86",
   "\xC2\xB0", "\xC2\xB1", "\xC2\xB2", "\xC2\xB3", "\xE2\x80\x9C", "\xC2\xB5", "\xC2\xB6", "\xC2\xB7", "\xC3\xB8", "\xC2\xB9", "\xC5\x97", "\xC2\xBB", "\xC2\xBC", "\xC2\xBD", "\xC2\xBE", "\xC3\xA6",
   "\xC4\x84", "\xC4\xAE", "\xC4\x80", "\xC4\x86", "\xC3\x84", "\xC3\x85", "\xC4\x98", "\xC4\x92", "\xC4\x8C", "\xC3\x89", "\xC5\xB9", "\xC4\x96", "\xC4\xA2", "\xC4\xB6", "\xC4\xAA", "\xC4\xBB",
   "\xC5\xA0", "\xC5\x83", "\xC5\x85", "\xC3\x93", "\xC5\x8C", "\xC3\x95", "\xC3\x96", "\xC3\x97", "\xC5\xB2", "\xC5\x81", "\xC5\x9A", "\xC5\xAA", "\xC3\x9C", "\xC5\xBB", "\xC5\xBD", "\xC3\x9F",
   "\xC4\x85", "\xC4\xAF", "\xC4\x81", "\xC4\x87", "\xC3\xA4", "\xC3\xA5", "\xC4\x99", "\xC4\x93", "\xC4\x8D", "\xC3\xA9", "\xC5\xBA", "\xC4\x97", "\xC4\xA3", "\xC4\xB7", "\xC4\xAB", "\xC4\xBC",
   "\xC5\xA1", "\xC5\x84", "\xC5\x86", "\xC3\xB3", "\xC5\x8D", "\xC3\xB5", "\xC3\xB6", "\xC3\xB7", "\xC5\xB3", "\xC5\x82", "\xC5\x9B", "\xC5\xAB", "\xC3\xBC", "\xC5\xBC", "\xC5\xBE", "\xE2\x80\x99"
};

/* UTF-8 NFC sequences for upper half of ISO8859-14 character set */
static const char*  enc_iso8859_14[128] =
{
   "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
   "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
   "\xC2\xA0", "\xE1\xB8\x82", "\xE1\xB8\x83", "\xC2\xA3", "\xC4\x8A", "\xC4\x8B","\xE1\xB8\x8A", "\xC2\xA7", "\xE1\xBA\x80", "\xC2\xA9", "\xE1\xBA\x82", "\xE1\xB8\x8B", "\xE1\xBB\xB2", "\xC2\xAD", "\xC2\xAE", "\xC5\xB8",
   "\xE1\xB8\x9E", "\xE1\xB8\x9F", "\xC4\xA0", "\xC4\xA1", "\xE1\xB9\x80", "\xE1\xB9\x81", "\xC2\xB6", "\xE1\xB9\x96", "\xE1\xBA\x81", "\xE1\xB9\x97", "\xE1\xBA\x83", "\xE1\xB9\xA0", "\xE1\xBB\xB3", "\xE1\xBA\x84", "\xE1\xBA\x85", "\xE1\xB9\xA1",
   "\xC3\x80", "\xC3\x81", "\xC3\x82", "\xC3\x83", "\xC3\x84", "\xC3\x85", "\xC3\x86", "\xC3\x87", "\xC3\x88", "\xC3\x89", "\xC3\x8A", "\xC3\x8B", "\xC3\x8C", "\xC3\x8D", "\xC3\x8E", "\xC3\x8F",
   "\xC5\xB4", "\xC3\x91", "\xC3\x92", "\xC3\x93", "\xC3\x94", "\xC3\x95", "\xC3\x96", "\xE1\xB9\xAA", "\xC3\x98", "\xC3\x99", "\xC3\x9A", "\xC3\x9B", "\xC3\x9C", "\xC3\x9D", "\xC5\xB6", "\xC3\x9F",
   "\xC3\xA0", "\xC3\xA1", "\xC3\xA2", "\xC3\xA3", "\xC3\xA4", "\xC3\xA5", "\xC3\xA6", "\xC3\xA7", "\xC3\xA8", "\xC3\xA9", "\xC3\xAA", "\xC3\xAB", "\xC3\xAC", "\xC3\xAD", "\xC3\xAE", "\xC3\xAF",
   "\xC5\xB5", "\xC3\xB1", "\xC3\xB2", "\xC3\xB3", "\xC3\xB4", "\xC3\xB5", "\xC3\xB6", "\xE1\xB9\xAB", "\xC3\xB8", "\xC3\xB9", "\xC3\xBA", "\xC3\xBB", "\xC3\xBC", "\xC3\xBD", "\xC5\xB7", "\xC3\xBF"
};

/* UTF-8 NFC sequences for upper half of ISO8859-15 character set */
static const char*  enc_iso8859_15[128] =
{
   "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
   "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
   "\xC2\xA0", "\xC2\xA1", "\xC2\xA2", "\xC2\xA3", "\xE2\x82\xAC", "\xC2\xA5", "\xC5\xA0", "\xC2\xA7", "\xC5\xA1", "\xC2\xA9", "\xC2\xAA", "\xC2\xAB", "\xC2\xAC", "\xC2\xAD", "\xC2\xAE", "\xC2\xAF",
   "\xC2\xB0", "\xC2\xB1", "\xC2\xB2", "\xC2\xB3", "\xC5\xBD", "\xC2\xB5", "\xC2\xB6", "\xC2\xB7", "\xC5\xBE", "\xC2\xB9", "\xC2\xBA", "\xC2\xBB", "\xC5\x92", "\xC5\x93", "\xC5\xB8", "\xC2\xBF",
   "\xC3\x80", "\xC3\x81", "\xC3\x82", "\xC3\x83", "\xC3\x84", "\xC3\x85", "\xC3\x86", "\xC3\x87", "\xC3\x88", "\xC3\x89", "\xC3\x8A", "\xC3\x8B", "\xC3\x8C", "\xC3\x8D", "\xC3\x8E", "\xC3\x8F",
   "\xC3\x90", "\xC3\x91", "\xC3\x92", "\xC3\x93", "\xC3\x94", "\xC3\x95", "\xC3\x96", "\xC3\x97", "\xC3\x98", "\xC3\x99", "\xC3\x9A", "\xC3\x9B", "\xC3\x9C", "\xC3\x9D", "\xC3\x9E", "\xC3\x9F",
   "\xC3\xA0", "\xC3\xA1", "\xC3\xA2", "\xC3\xA3", "\xC3\xA4", "\xC3\xA5", "\xC3\xA6", "\xC3\xA7", "\xC3\xA8", "\xC3\xA9", "\xC3\xAA", "\xC3\xAB", "\xC3\xAC", "\xC3\xAD", "\xC3\xAE", "\xC3\xAF",
   "\xC3\xB0", "\xC3\xB1", "\xC3\xB2", "\xC3\xB3", "\xC3\xB4", "\xC3\xB5", "\xC3\xB6", "\xC3\xB7", "\xC3\xB8", "\xC3\xB9", "\xC3\xBA", "\xC3\xBB", "\xC3\xBC", "\xC3\xBD", "\xC3\xBE", "\xC3\xBF"
};

/* UTF-8 NFC sequences for upper half of ISO8859-16 character set */
static const char*  enc_iso8859_16[128] =
{
   "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
   "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
   "\xC2\xA0", "\xC4\x84", "\xC4\x85", "\xC5\x81", "\xE2\x82\xAC", "\xE2\x80\x9E", "\xC5\xA0", "\xC2\xA7", "\xC5\xA1", "\xC2\xA9", "\xC8\x98", "\xC2\xAB", "\xC5\xB9", "\xC2\xAD", "\xC5\xBA", "\xC5\xBB",
   "\xC2\xB0", "\xC2\xB1", "\xC4\x8C", "\xC5\x82", "\xC5\xB3", "\xE2\x80\x9D", "\xC2\xB6", "\xC2\xB7", "\xC5\xBE", "\xC4\x8D", "\xC8\x99", "\xC2\xBB", "\xC5\x92", "\xC5\x93", "\xC5\xB8", "\xC5\xBC",
   "\xC3\x80", "\xC3\x81", "\xC3\x82", "\xC4\x82", "\xC3\x84", "\xC4\x86", "\xC3\x86", "\xC3\x87", "\xC3\x88", "\xC3\x89", "\xC3\x8A", "\xC3\x8B", "\xC3\x8C", "\xC3\x8D", "\xC3\x8E", "\xC3\x8F",
   "\xC4\x90", "\xC5\x83", "\xC3\x92", "\xC3\x93", "\xC3\x94", "\xC5\x90", "\xC3\x96", "\xC5\x9A", "\xC5\xB0", "\xC3\x99", "\xC3\x9A", "\xC3\x9B", "\xC3\x9C", "\xC4\x98", "\xC8\x9A", "\xC3\x9F",
   "\xC3\xA0", "\xC3\xA1", "\xC3\xA2", "\xC4\x83", "\xC3\xA4", "\xC4\x87", "\xC3\xA6", "\xC3\xA7", "\xC3\xA8", "\xC3\xA9", "\xC3\xAA", "\xC3\xAB", "\xC3\xAC", "\xC3\xAD", "\xC3\xAE", "\xC3\xAF",
   "\xC4\x91", "\xC5\x84", "\xC3\xB2", "\xC3\xB3", "\xC3\xB4", "\xC5\x91", "\xC3\xB6", "\xC5\x9B", "\xC5\xB1", "\xC3\xB9", "\xC3\xBA", "\xC3\xBB", "\xC3\xBC", "\xC4\x99", "\xC8\x9B", "\xC3\xBF"
};

/* UTF-8 NFC sequences for upper half of MacRoman character set */
static const char*  enc_mac_roman[128] =
{
   "\xC3\x84", "\xC3\x85", "\xC3\x87", "\xC3\x89", "\xC3\x91", "\xC3\x96", "\xC3\x9C", "\xC3\xA1", "\xC3\xA0", "\xC3\xA2", "\xC3\xA4", "\xC3\xA3", "\xC3\xA5", "\xC3\xA7", "\xC3\xA9", "\xC3\xA8",
   "\xC3\xAA", "\xC3\xAB", "\xC3\xAD", "\xC3\xAC", "\xC3\xAE", "\xC3\xAF", "\xC3\xB1", "\xC3\xB3", "\xC3\xB2", "\xC3\xB4", "\xC3\xB6", "\xC3\xB5", "\xC3\xBA", "\xC3\xB9", "\xC3\xBB", "\xC3\xBC",
   "\xE2\x80\xA0", "\xC2\xB0", "\xC2\xA2", "\xC2\xA3", "\xC2\xA7", "\xE2\x80\xA2", "\xC2\xB6", "\xC3\x9F", "\xC2\xAE", "\xC2\xA9", "\xE2\x84\xA2", "\xC2\xB4", "\xC2\xA8", "\xE2\x89\xA0", "\xC3\x86", "\xC3\x98",
   "\xE2\x88\x9E", "\xC2\xB1", "\xE2\x89\xA4", "\xE2\x89\xA5", "\xC2\xA5", "\xC2\xB5", "\xE2\x88\x82", "\xE2\x88\x91", "\xE2\x88\x8F", "\xCF\x80", "\xE2\x88\xAB", "\xC2\xAA", "\xC2\xBA", "\xCE\xA9", "\xC3\xA6", "\xC3\xB8",
   "\xC2\xBF", "\xC1\xA1", "\xC2\xAC", "\xE2\x88\x9A", "\xC6\x92", "\xE2\x89\x88", "\xE2\x88\x86", "\xC2\xAB", "\xC2\xBB", "\xE2\x80\xA6", "\xC2\xA0", "\xC3\x80", "\xC3\x83", "\xC3\x95", "\xC5\x92", "\xC5\x93",
   "\xE2\x80\x93", "\xE2\x80\x94", "\xE2\x80\x9C", "\xE2\x80\x9D", "\xE2\x80\x98", "\xE2\x80\x99", "\xC3\xB7", "\xE2\x97\x8A", "\xC3\xBF", "\xC5\xB8", "\xE2\x81\x84", "\xE2\x82\xAC", "\xE2\x80\xB9", "\xE2\x80\xBA", "\xEF\xAC\x81", "\xEF\xAC\x82",
   "\xE2\x80\xA1", "\xC2\xB7", "\xE2\x80\x9A", "\xE2\x80\x9E", "\xE2\x80\xB0", "\xC3\x82", "\xC3\x8A", "\xC3\x81", "\xC3\x8B", "\xC3\x88", "\xC3\x8D", "\xC3\x8E", "\xC3\x8F", "\xC3\x8C", "\xC3\x93", "\xC3\x94",
   "", "\xC3\x92", "\xC3\x9A", "\xC3\x9B", "\xC3\x99", "\xC4\xB1", "\xCB\x86", "\xCB\x9C", "\xC2\xAF", "\xCB\x98", "\xCB\x99", "\xCB\x9A", "\xC2\xB8", "\xCB\x9D", "\xCB\x9B", "\xCB\x87"
};


/* UTF-8 NFC sequences for upper half of Windows-1250 character set */
static const char*  enc_windows_1250[128] =
{
   "\xE2\x82\xAC", "", "\xE2\x80\x9A", "\xC6\x92", "\xE2\x80\x9E", "\xE2\x80\xA6", "\xE2\x80\xA0", "\xE2\x80\xA1", "", "\xE2\x80\xB0", "\xC5\xA0", "\xE2\x80\xB9", "\xC5\x9A", "\xC5\xA4", "\xC5\xBD", "\xC5\xB9",
   "", "\xE2\x80\x98", "\xE2\x80\x99", "\xE2\x80\x9C", "\xE2\x80\x9D", "\xE2\x80\xA2", "\xE2\x80\x93", "\xE2\x80\x94", "", "\xE2\x84\xA2", "\xC5\xA1", "\xE2\x80\xBA", "\xC5\x9B", "\xC5\xA5", "\xC5\xBE", "\xC5\xBA",
   "\xC2\xA0", "\xCB\x87", "\xCB\x98", "\xC5\x81", "\xC2\xA4", "\xC4\x84", "\xC2\xA6", "\xC2\xA7", "\xC2\xA8", "\xC2\xA9", "\xC5\x9E", "\xC2\xAB", "\xC2\xAC", "\xC2\xAD", "\xC2\xAE", "\xC5\xBB",
   "\xC2\xB0", "\xC2\xB1", "\xCB\x9B", "\xC5\x82", "\xC2\xB4", "\xC2\xB5", "\xC2\xB6", "\xC2\xB7", "\xC2\xB8", "\xC4\x85", "\xC5\x9F", "\xC2\xBB", "\xC4\xBD", "\xCB\x9D", "\xC4\xBE", "\xC5\xBC",
   "\xC5\x94", "\xC3\x81", "\xC3\x82", "\xC4\x82", "\xC3\x84", "\xC4\xB9", "\xC4\x86", "\xC3\x87", "\xC4\x8C", "\xC3\x89", "\xC4\x98", "\xC3\x8B", "\xC4\x9A", "\xC3\x8D", "\xC3\x8E", "\xC4\x8E",
   "\xC4\x90", "\xC5\x83", "\xC5\x87", "\xC3\x93", "\xC3\x94", "\xC5\x90", "\xC3\x96", "\xC3\x97", "\xC5\x98", "\xC5\xAE", "\xC3\x9A", "\xC5\xB0", "\xC3\x9C", "\xC3\x9D", "\xC5\xA2", "\xC3\x9F",
   "\xC5\x95", "\xC3\xA1", "\xC3\xA2", "\xC4\x83", "\xC3\xA4", "\xC4\xBA", "\xC4\x87", "\xC3\xA7", "\xC4\x8D", "\xC3\xA9", "\xC4\x99", "\xC3\xAB", "\xC4\x9B", "\xC3\xAD", "\xC3\xAE", "\xC4\x8F",
   "\xC4\x91", "\xC5\x84", "\xC5\x88", "\xC3\xB3", "\xC3\xB4", "\xC5\x91", "\xC3\xB6", "\xC3\xB7", "\xC5\x99", "\xC5\xAF", "\xC3\xBA", "\xC5\xB1", "\xC3\xBC", "\xC3\xBD", "\xC5\xA3", "\xCB\x99"
};

/* UTF-8 NFC sequences for upper half of Windows-1251 character set */
static const char*  enc_windows_1251[128] =
{
   "\xD0\x82", "\xD0\x83", "\xE2\x80\x9A", "\xD1\x93", "\xE2\x80\x9E", "\xE2\x80\xA6", "\xE2\x80\xA0", "\xE2\x80\xA1", "\xE2\x82\xAC", "\xE2\x80\xB0", "\xD0\x89", "\xE2\x80\xB9", "\xD0\x8A", "\xD0\x8C", "\xD0\x8B", "\xD0\x8F",
   "\xD1\x92", "\xE2\x80\x98", "\xE2\x80\x99", "\xE2\x80\x9C", "\xE2\x80\x9D", "\xE2\x80\xA2", "\xE2\x80\x93", "\xE2\x80\x94", "", "\xE2\x84\xA2", "\xD1\x99", "\xE2\x80\xBA", "\xD1\x9A", "\xD1\x9C", "\xD1\x9B", "\xD1\x9F",
   "\xC2\xA0", "\xD0\x8E", "\xD1\x9E", "\xD0\x88", "\xC2\xA4", "\xD2\x90", "\xC2\xA6", "\xC2\xA7", "\xD0\x81", "\xC2\xA9", "\xD0\x84", "\xC2\xAB", "\xC2\xAC", "\xC2\xAD", "\xC2\xAE", "\xD0\x87",
   "\xC2\xB0", "\xC2\xB1", "\xD0\x86", "\xD1\x96", "\xD2\x91", "\xC2\xB5", "\xC2\xB6", "\xC2\xB7", "\xD1\x91", "\xE2\x84\x96", "\xD1\x94", "\xC2\xBB", "\xD1\x98", "\xD0\x85", "\xD1\x95", "\xD1\x97",
   "\xD0\x90", "\xD0\x91", "\xD0\x92", "\xD0\x93", "\xD0\x94", "\xD0\x95", "\xD0\x96", "\xD0\x97", "\xD0\x98", "\xD0\x99", "\xD0\x9A", "\xD0\x9B", "\xD0\x9C", "\xD0\x9D", "\xD0\x9E", "\xD0\x9F",
   "\xD0\xA0", "\xD0\xA1", "\xD0\xA2", "\xD0\xA3", "\xD0\xA4", "\xD0\xA5", "\xD0\xA6", "\xD0\xA7", "\xD0\xA8", "\xD0\xA9", "\xD0\xAA", "\xD0\xAB", "\xD0\xAC", "\xD0\xAD", "\xD0\xAE", "\xD0\xAF",
   "\xD0\xB0", "\xD0\xB1", "\xD0\xB2", "\xD0\xB3", "\xD0\xB4", "\xD0\xB5", "\xD0\xB6", "\xD0\xB7", "\xD0\xB8", "\xD0\xB9", "\xD0\xBA", "\xD0\xBB", "\xD0\xBC", "\xD0\xBD", "\xD0\xBE", "\xD0\xBF",
   "\xD1\x80", "\xD1\x81", "\xD1\x82", "\xD1\x83", "\xD1\x84", "\xD1\x85", "\xD1\x86", "\xD1\x87", "\xD1\x88", "\xD1\x89", "\xD1\x8A", "\xD1\x8B", "\xD1\x8C", "\xD1\x8D", "\xD1\x8E", "\xD1\x8F"
};

/* UTF-8 NFC sequences for upper half of Windows-1252 character set */
static const char*  enc_windows_1252[128] =
{
   "\xE2\x82\xAC", "", "\xE2\x80\x9A", "\xC6\x92", "\xE2\x80\x9E", "\xE2\x80\xA6", "\xE2\x80\xA0", "\xE2\x80\xA1", "\xCB\x86", "\xE2\x80\xB0", "\xC5\xA0", "\xE2\x80\xB9", "\xC5\x92", "", "\xC5\xBD", "",
   "", "\xE2\x80\x98", "\xE2\x80\x99", "\xE2\x80\x9C", "\xE2\x80\x9D", "\xE2\x80\xA2", "\xE2\x80\x93", "\xE2\x80\x94", "\xCB\x9C", "\xE2\x84\xA2", "\xC5\xA1", "\xE2\x80\xBA", "\xC5\x93", "", "\xC5\xBE", "\xC5\xB8",
   "\xC2\xA0", "\xC2\xA1", "\xC2\xA2", "\xC2\xA3", "\xC2\xA4", "\xC2\xA5", "\xC2\xA6", "\xC2\xA7", "\xC2\xA8", "\xC2\xA9", "\xC2\xAA", "\xC2\xAB", "\xC2\xAC", "\xC2\xAD", "\xC2\xAE", "\xC2\xAF",
   "\xC2\xB0", "\xC2\xB1", "\xC2\xB2", "\xC2\xB3", "\xC2\xB4", "\xC2\xB5", "\xC2\xB6", "\xC2\xB7", "\xC2\xB8", "\xC2\xB9", "\xC2\xBA", "\xC2\xBB", "\xC2\xBC", "\xC2\xBD", "\xC2\xBE", "\xC2\xBF",
   "\xC3\x80", "\xC3\x81", "\xC3\x82", "\xC3\x83", "\xC3\x84", "\xC3\x85", "\xC3\x86", "\xC3\x87", "\xC3\x88", "\xC3\x89", "\xC3\x8A", "\xC3\x8B", "\xC3\x8C", "\xC3\x8D", "\xC3\x8E", "\xC3\x8F",
   "\xC3\x90", "\xC3\x91", "\xC3\x92", "\xC3\x93", "\xC3\x94", "\xC3\x95", "\xC3\x96", "\xC3\x97", "\xC3\x98", "\xC3\x99", "\xC3\x9A", "\xC3\x9B", "\xC3\x9C", "\xC3\x9D", "\xC3\x9E", "\xC3\x9F",
   "\xC3\xA0", "\xC3\xA1", "\xC3\xA2", "\xC3\xA3", "\xC3\xA4", "\xC3\xA5", "\xC3\xA6", "\xC3\xA7", "\xC3\xA8", "\xC3\xA9", "\xC3\xAA", "\xC3\xAB", "\xC3\xAC", "\xC3\xAD", "\xC3\xAE", "\xC3\xAF",
   "\xC3\xB0", "\xC3\xB1", "\xC3\xB2", "\xC3\xB3", "\xC3\xB4", "\xC3\xB5", "\xC3\xB6", "\xC3\xB7", "\xC3\xB8", "\xC3\xB9", "\xC3\xBA", "\xC3\xBB", "\xC3\xBC", "\xC3\xBD", "\xC3\xBE", "\xC3\xBF"
};

/* UTF-8 NFC sequences for upper half of Windows-1256 character set */
static const char*  enc_windows_1256[128] =
{
   "\xE2\x82\xAC", "\xD9\xBE", "\xE2\x80\x9A", "\xC6\x92", "\xE2\x80\x9E", "\xE2\x80\xA6", "\xE2\x80\xA0", "\xE2\x80\xA1", "\xCB\x86", "\xE2\x80\xB0", "\xD9\xB9", "\xE2\x80\xB9", "\xC5\x92", "\xDA\x86", "\xDA\x98", "\xDA\x88",
   "\xDA\xAF", "\xE2\x80\x98", "\xE2\x80\x99", "\xE2\x80\x9C", "\xE2\x80\x9D", "\xE2 \x80 \xA2", "\xE2\x80\x93", "\xE2\x80\x94", "\xDA\xA9", "\xE2\x84\xA2", "\xDA\x91", "\xE2\x80\xBA", "\xC5\x93", "\xE2\x80\x8C", "\xE2\x80\x8D", "\xDA\xBA",
   "\xC2\xA0", "\xD8\x8C", "\xC2\xA2", "\xC2\xA3", "\xC2\xA4", "\xC2\xA5", "\xC2\xA6", "\xC2\xA7", "\xC2\xA8", "\xC2\xA9", "\xDA\xBE", "\xC2\xAB", "\xC2\xAC", "\xC2\xAD", "\xC2\xAE", "\xC2\xAF",
   "\xC2\xB0", "\xC2\xB1", "\xC2\xB2", "\xC2\xB3", "\xC2\xB4", "\xC2\xB5", "\xC2\xB6", "\xC2\xB7", "\xC2\xB8", "\xC2\xB9", "\xD8\x9B", "\xC2\xBB", "\xC2\xBC", "\xC2\xBD", "\xC2\xBE", "\xC2\xBF",
   "\xDB\x81", "\xD8\xA1", "\xD8\xA2", "\xD8\xA3", "\xD8\xA4", "\xD8\xA5", "\xD8\xA6", "\xD8\xA7", "\xD8\xA8", "\xD8\xA9", "\xD8\xAA", "\xD8\xAB", "\xD8\xAC", "\xD8\xAD", "\xD8\xAE", "\xD8\xAF",
   "\xD8\xB0", "\xD8\xB1", "\xD8\xB2", "\xD8\xB3", "\xD8\xB4", "\xD8\xB5", "\xD8\xB6", "\xC3\x97", "\xD8\xB7", "\xD8\xB8", "\xD8\xB9", "\xD8\xBA", "\xD9\x80", "\xD9\x81", "\xD9\x82", "\xD9\x83",
   "\xC3\xA0", "\xD9\x84", "\xC3\xA2", "\xD9\x85", "\xD9\x86", "\xD9\x87", "\xD9\x88", "\xC3\xA7", "\xC3\xA8", "\xC3\xA9", "\xC3\xAA", "\xC3\xAB", "\xD9\x89", "\xD9\x8A", "\xC3\xAE", "\xC3\xAF",
   "\xD9\x8B", "\xD9\x8C", "\xD9\x8D", "\xD9\x8E", "\xC3\xB4", "\xD9\x8F", "\xD9\x90", "\xC3\xB7", "\xD9\x91", "\xC3\xB9", "\xD9\x92", "\xC3\xBB", "\xC3\xBC", "\xE2\x80\x8E", "\xE2\x80\x8F", "\xDB\x92"
};

/* UTF-8 NFC sequences for upper half of KOI8-R character set */
static const char*  enc_koi8r[128] =
{
   "\xE2\x94\x80", "\xE2\x94\x82", "\xE2\x94\x8C", "\xE2\x94\x90", "\xE2\x94\x94", "\xE2\x94\x98", "\xE2\x94\x9C", "\xE2\x94\xA4", "\xE2\x94\xAC", "\xE2\x94\xB4", "\xE2\x94\xBC", "\xE2\x96\x80", "\xE2\x96\x84", "\xE2\x96\x88", "\xE2\x96\x8C", "\xE2\x96\x90",
   "\xE2\x96\x91", "\xE2\x96\x92", "\xE2\x96\x93", "\xE2\x8C\xA0", "\xE2\x96\xA0", "\xE2\x88\x99", "\xE2\x88\x9A", "\xE2\x89\x88", "\xE2\x89\xA4", "\xE2\x89\xA5", "\xC2\xA0", "\xE2\x8C\xA1", "\xC2\xB0", "\xC2\xB2", "\xC2\xB7", "\xC3\xB7",
   "\xE2\x95\x90", "\xE2\x95\x91", "\xE5\x95\x92", "\xD1\x91", "\xE2\x95\x93", "\xE2\x95\x94", "\xE2\x95\x95", "\xE2\x95\x96", "\xE2\x95\x97", "\xE2\x95\x98", "\xE2\x95\x99", "\xE2\x95\x9A", "\xE2\x95\x9B", "\xE2\x95\x9C", "\xE2\x95\x9D", "\xE2\x95\x9E",
   "\xE2\x95\x9F", "\xE2\x95\xA0", "\xE2\x95\xA1", "\xD0\x81", "\xE2\x95\xA2", "\xE2\x95\xA3", "\xE2\x95\xA4", "\xE2\x95\xA5", "\xE2\x95\xA6", "\xE2\x95\xA7", "\xE2\x95\xA8", "\xE2\x95\xA9", "\xE2\x95\xAA", "\xE2\x95\xAB", "\xE2\x95\xAC", "\xC2\xA9",
   "\xD1\x8E", "\xD0\xB0", "\xD0\xB1", "\xD1\x86", "\xD0\xB4", "\xD0\xB5", "\xD1\x84", "\xD0\xB3", "\xD1\x85", "\xD0\xB8", "\xD0\xB9", "\xD0\xBA", "\xD0\xBB", "\xD0\xBC", "\xD0\xBD", "\xD0\xBE",
   "\xD0\xBF", "\xD1\x8F", "\xD1\x80", "\xD1\x81", "\xD1\x82", "\xD1\x83", "\xD0\xB6", "\xD0\xB2", "\xD1\x8C", "\xD1\x8B", "\xD0\xB7", "\xD1\x88", "\xD1\x8D", "\xD1\x89", "\xD1\x87", "\xD1\x8A",
   "\xD0\xAE", "\xD0\x90", "\xD0\x91", "\xD0\xA6", "\xD0\x94", "\xD0\x95", "\xD0\xA4", "\xD0\x93", "\xD0\xA5", "\xD0\x98", "\xD0\x99", "\xD0\x9A", "\xD0\x9B", "\xD0\x9C", "\xD0\x9D", "\xD0\x9E",
   "\xD0\x9F", "\xD0\xAF", "\xD0\xA0", "\xD0\xA1", "\xD0\xA2", "\xD0\xA3", "\xD0\x96", "\xD0\x92", "\xD0\xAC", "\xD0\xAB", "\xD0\x97", "\xD0\xA8", "\xD0\xAD", "\xD0\xA9", "\xD0\xA7", "\xD0\xAA"
};

/* UTF-8 NFC sequences for upper half of IBM437 character set */
static const char*  enc_ibm437[128] =
{
   "\xC3\x87", "\xC3\xBC", "\xC3\xA9", "\xC3\xA2", "\xC3\xA4", "\xC3\xA0", "\xC3\xA5", "\xC3\xA7", "\xC3\xAA", "\xC3\xAB", "\xC3\xA8", "\xC3\xAF", "\xC3\xAE", "\xC3\xAC", "\xC3\x84", "\xC3\x85",
   "\xC3\x89", "\xC3\xA6", "\xC3\x86", "\xC3\xB4", "\xC3\xB6", "\xC3\xB2", "\xC3\xBB", "\xC3\xB9", "\xC3\xBF", "\xC3\x96", "\xC3\x9C", "\xC2\xA2", "\xC2\xA3", "\xC2\xA5", "\xE2\x82\xA7", "\xC6\x92",
   "\xC3\xA1", "\xC3\xAD", "\xC3\xB3", "\xC3\xBA", "\xC3\xB1", "\xC3\x91", "\xC2\xAA", "\xC2\xBA", "\xC2\xBF", "\xE2\x8C\x90", "\xC2\xAC", "\xC2\xBD", "\xC2\xBC", "\xC2\xA1", "\xC2\xAB", "\xC2\xBB",
   "\xE2\x96\x91", "\xE2\x96\x92", "\xE2\x96\x93", "\xE2\x94\x82", "\xE2\x94\xA4", "\xE2\x95\xA1", "\xE2\x95\xA2", "\xE2\x95\x96", "\xE2\x95\x95", "\xE2\x95\xA3", "\xE2\x95\x91", "\xE2\x95\x97", "\xE2\x95\x9D", "\xE2\x95\x9C", "\xE2\x95\x9B", "\xE2\x94\x90",
   "\xE2\x94\x94", "\xE2\x94\xB4", "\xE2\x94\xAC", "\xE2\x94\x9C", "\xE2\x94\x80", "\xE2\x94\xBC", "\xE2\x95\x9E", "\xE2\x95\x9F", "\xE2\x95\x9A", "\xE2\x95\x94", "\xE2\x95\xA9", "\xE2\x95\xA6", "\xE2\x95\xA0", "\xE2\x95\x90", "\xE2\x95\xAC", "\xE2\x95\xA7",
   "\xE2\x95\xA8", "\xE2\x95\xA4", "\xE2\x95\xA5", "\xE2\x95\x99", "\xE2\x95\x98", "\xE2\x95\x92", "\xE2\x95\x93", "\xE2\x95\xAB", "\xE2\x95\xAA", "\xE2\x94\x98", "\xE2\x94\x8C", "\xE2\x96\x88", "\xE2\x96\x84", "\xE2\x96\x8C", "\xE2\x96\x90", "\xE2\x96\x80",
   "\xCE\xB1", "\xC3\x9F", "\xCE\x93", "\xCF\x80", "\xCE\xA3", "\xCF\x83", "\xC2\xB5", "\xCF\x84", "\xCE\xA6", "\xCE\x98", "\xCE\xA9", "\xCE\xB4", "\xE2\x88\x9E", "\xCF\x86", "\xCE\xB5", "\xE2\x88\xA9",
   "\xE2\x89\xA1", "\xC2\xB1", "\xE2\x89\xA5", "\xE2\x89\xA4", "\xE2\x8C\xA0", "\xE2\x8C\xA1", "\xC3\xB7", "\xE2\x89\x88", "\xC2\xB0", "\xE2\x88\x99", "\xC2\xB7", "\xE2\x88\x9A", "\xE2\x81\xBF", "\xC2\xB2", "\xE2\x96\xA0", "\xC2\xA0"
};

/* UTF-8 NFC sequences for upper half of IBM850 character set */
static const char*  enc_ibm850[128] =
{
   "\xC3\x87", "\xC3\xBC", "\xC3\xA9", "\xC3\xA2", "\xC3\xA4", "\xC3\xA0", "\xC3\xA5", "\xC3\xA7", "\xC3\xAA", "\xC3\xAB", "\xC3\xA8", "\xC3\xAF", "\xC3\xAE", "\xC3\xAC", "\xC3\x84", "\xC3\x85",
   "\xC3\x89", "\xC3\xA6", "\xC3\x86", "\xC3\xB4", "\xC3\xB6", "\xC3\xB2", "\xC3\xBB", "\xC3\xB9", "\xC3\xBF", "\xC3\x96", "\xC3\x9C", "\xC2\xB8", "\xC2\xA3", "\xC3\x98", "\xC3\x97", "\xC6\x92",
   "\xC3\xA1", "\xC3\xAD", "\xC3\xB3", "\xC3\xBA", "\xC3\xB1", "\xC3\x91", "\xC2\xAA", "\xC2\xBA", "\xC2\xBF", "\xC2\xAE", "\xC2\xAC", "\xC2\xBD", "\xC2\xBC", "\xC2\xA1", "\xC2\xAB", "\xC2\xBB",
   "\xE2\x96\x91", "\xE2\x96\x92", "\xE2\x96\x93", "\xE2\x94\x82", "\xE2\x94\xA4", "\xC3\x81", "\xC3\x82", "\xC3\x80", "\xC2\xA9", "\xE2\x95\xA3", "\xE2\x95\x91", "\xE2\x95\x97", "\xE2\x95\x9D", "\xC2\xA2", "\xC2\xA5", "\xE2\x94\x90",
   "\xE2\x94\x94", "\xE2\x94\xB4", "\xE2\x94\xAC", "\xE2\x94\x9C", "\xE2\x94\x80", "\xE2\x94\xBC", "\xC3\xA3", "\xC3\x83", "\xE2\x95\x9A", "\xE2\x95\x94", "\xE2\x95\xA9", "\xE2\x95\xA6", "\xE2\x95\xA0", "\xE2\x95\x90", "\xE2\x95\xAC", "\xC2\xA4",
   "\xC3\xB0", "\xC3\x90", "\xC3\x8A", "\xC3\x8B", "\xC3\x88", "\xC4\xB1", "\xC3\x8D", "\xC3\x8E", "\xC3\x8F", "\xE2\x94\x98", "\xE2\x94\x8C", "\xE2\x96\x88", "\xE2\x96\x84", "\xC2\xA6", "\xC3\x8C", "\xE2\x96\x80",
   "\xC3\x93", "\xC3\x9F", "\xC3\x94", "\xC3\x92", "\xC3\xB5", "\xC3\x95", "\xC2\xB5", "\xC3\xBE", "\xCE\x9E", "\xC3\x9A", "\xC3\x9B", "\xC3\x99", "\xC3\xBD", "\xC3\x9D", "\xC2\xAF", "\xC2\xB4",
   "\xC2\xAD", "\xC2\xB1", "\xE2\x80\x97", "\xC2\xBE", "\xC2\xB6", "\xC2\xA7", "\xC3\xB7", "\xC2\xB8", "\xC2\xB0", "\xC2\xA8", "\xC2\xB7", "\xC2\xB9", "\xC2\xB3", "\xC2\xB2", "\xE2\x96\xA0", "\xC2\xA0"
};

/* UTF-8 NFC sequences for upper half of IBM858 character set */
static const char*  enc_ibm858[128] =
{
   "\xC3\x87", "\xC3\xBC", "\xC3\xA9", "\xC3\xA2", "\xC3\xA4", "\xC3\xA0", "\xC3\xA5", "\xC3\xA7", "\xC3\xAA", "\xC3\xAB", "\xC3\xA8", "\xC3\xAF", "\xC3\xAE", "\xC3\xAC", "\xC3\x84", "\xC3\x85",
   "\xC3\x89", "\xC3\xA6", "\xC3\x86", "\xC3\xB4", "\xC3\xB6", "\xC3\xB2", "\xC3\xBB", "\xC3\xB9", "\xC3\xBF", "\xC3\x96", "\xC3\x9C", "\xC2\xB8", "\xC2\xA3", "\xC3\x98", "\xC3\x97", "\xC6\x92",
   "\xC3\xA1", "\xC3\xAD", "\xC3\xB3", "\xC3\xBA", "\xC3\xB1", "\xC3\x91", "\xC2\xAA", "\xC2\xBA", "\xC2\xBF", "\xC2\xAE", "\xC2\xAC", "\xC2\xBD", "\xC2\xBC", "\xC2\xA1", "\xC2\xAB", "\xC2\xBB",
   "\xE2\x96\x91", "\xE2\x96\x92", "\xE2\x96\x93", "\xE2\x94\x82", "\xE2\x94\xA4", "\xC3\x81", "\xC3\x82", "\xC3\x80", "\xC2\xA9", "\xE2\x95\xA3", "\xE2\x95\x91", "\xE2\x95\x97", "\xE2\x95\x9D", "\xC2\xA2", "\xC2\xA5", "\xE2\x94\x90",
   "\xE2\x94\x94", "\xE2\x94\xB4", "\xE2\x94\xAC", "\xE2\x94\x9C", "\xE2\x94\x80", "\xE2\x94\xBC", "\xC3\xA3", "\xC3\x83", "\xE2\x95\x9A", "\xE2\x95\x94", "\xE2\x95\xA9", "\xE2\x95\xA6", "\xE2\x95\xA0", "\xE2\x95\x90", "\xE2\x95\xAC", "\xC2\xA4",
   "\xC3\xB0", "\xC3\x90", "\xC3\x8A", "\xC3\x8B", "\xC3\x88", "\xE2\x82\xAC", "\xC3\x8D", "\xC3\x8E", "\xC3\x8F", "\xE2\x94\x98", "\xE2\x94\x8C", "\xE2\x96\x88", "\xE2\x96\x84", "\xC2\xA6", "\xC3\x8C", "\xE2\x96\x80",
   "\xC3\x93", "\xC3\x9F", "\xC3\x94", "\xC3\x92", "\xC3\xB5", "\xC3\x95", "\xC2\xB5", "\xC3\xBE", "\xCE\x9E", "\xC3\x9A", "\xC3\x9B", "\xC3\x99", "\xC3\xBD", "\xC3\x9D", "\xC2\xAF", "\xC2\xB4",
   "\xC2\xAD", "\xC2\xB1", "\xE2\x80\x97", "\xC2\xBE", "\xC2\xB6", "\xC2\xA7", "\xC3\xB7", "\xC2\xB8", "\xC2\xB0", "\xC2\xA8", "\xC2\xB7", "\xC2\xB9", "\xC2\xB3", "\xC2\xB2", "\xE2\x96\xA0", "\xC2\xA0"
};


/* ========================================================================== */
/* Decode hexadecimal nibble from ASCII to integer
 *
 * \param[in] nibble  ASCII encoded haxadecimal nibble to decode
 *
 * \return
 * - Integer value of \e nibble
 * - Negative value on error
 */

static int  enc_hex_decode_nibble(char  nibble)
{
   int  res = -1;
   int  n = nibble;

   if(0x30 <= n && 0x39 >= n)  { res = n - 0x30; }
   else if(0x41 <= n && 0x46 >= n)  { res = n - 0x41 + 10; }
   else if(0x61 <= n && 0x66 >= n)  { res = n - 0x61 + 10; }
   else  { print_error("ENC: Can't decode invalid hexadecimal nibble"); }
   /* printf("Hex nibble %c => %d\n", nibble, res); */

   return(res);
}


/* ========================================================================== */
/* Convert from supported 8 bit character sets to UTF-8 NFC
 *
 * \param[in] charset  8 bit character set used for string \e s
 * \param[in] s        String to convert
 *
 * \return
 * - Pointer to result (if not equal to \e s , a new memory block was allocated)
 * - NULL on error
 */

static const char*  enc_8bit_convert_to_utf8_nfc(enum enc_mime_cs  charset,
                                                 const char*  s)
{
   static const char  qm[] = "?";
   const char*  res = s;
   size_t  i = 0;
   char*  buf = NULL;
   size_t  len = 0;
   size_t  bi = 0;
   char*  p;
   const char*  seq;
   size_t  ii;
   unsigned char  c;
   char  scs[2];

   /*
    * Check whether data contains only of printable ASCII characters
    * (all supported character sets are ASCII extensions)
    */
   if(enc_ascii_check_printable(s))
   {
      /* Decode */
      res = NULL;
      while(s[i])
      {
         /* Attention: This assumes valid UTF-8 encoding for decoded data! */
         if(bi + (size_t) 5 >= len)  /* At least (4 + NUL) additional bytes */
         {
            /* Allocate more memory in exponentially increasing chunks */
            if(!len)  { len = 64; }
            p = posix_realloc((void*) buf, len *= (size_t) 2);
            if(NULL == p)  { posix_free((void*) buf);  buf = NULL;  break; }
            else  { buf = p; }
         }
         /* Decode next character */
         if(128U > (unsigned char) s[i])
         {
            /* ASCII => Delete all nonprintable characters except CR and LF */
            scs[0] = s[i];  scs[1] = 0;
            if('\r' == s[i] || '\n' == s[i] || !enc_ascii_check_printable(scs))
            {
               buf[bi++] = s[i];
            }
            ++i;
            continue;
         }
         /* Decode non-ASCII character */
         c = (unsigned char) s[i] - (unsigned char) 128;
         switch(charset)
         {
            case ENC_CS_ASCII:  { seq = qm;  break; }
            case ENC_CS_ISO8859_1:  { seq = enc_iso8859_1[c];  break; }
            case ENC_CS_ISO8859_2:  { seq = enc_iso8859_2[c];  break; }
            case ENC_CS_ISO8859_3:  { seq = enc_iso8859_3[c];  break; }
            case ENC_CS_ISO8859_4:  { seq = enc_iso8859_4[c];  break; }
            case ENC_CS_ISO8859_5:  { seq = enc_iso8859_5[c];  break; }
            case ENC_CS_ISO8859_6:  { seq = enc_iso8859_6[c];  break; }
            case ENC_CS_ISO8859_7:  { seq = enc_iso8859_7[c];  break; }
            case ENC_CS_ISO8859_8:  { seq = enc_iso8859_8[c];  break; }
            case ENC_CS_ISO8859_9:  { seq = enc_iso8859_9[c];  break; }
            case ENC_CS_ISO8859_10:  { seq = enc_iso8859_10[c];  break; }
            case ENC_CS_ISO8859_11:  { seq = enc_iso8859_11[c];  break; }
            case ENC_CS_ISO8859_13:  { seq = enc_iso8859_13[c];  break; }
            case ENC_CS_ISO8859_14:  { seq = enc_iso8859_14[c];  break; }
            case ENC_CS_ISO8859_15:  { seq = enc_iso8859_15[c];  break; }
            case ENC_CS_ISO8859_16:  { seq = enc_iso8859_16[c];  break; }
            case ENC_CS_MACINTOSH:  { seq = enc_mac_roman[c];  break; }
            case ENC_CS_KOI8R:  { seq = enc_koi8r[c];  break; }
            case ENC_CS_WINDOWS_1250:  { seq = enc_windows_1250[c];  break; }
            case ENC_CS_WINDOWS_1251:  { seq = enc_windows_1251[c];  break; }
            case ENC_CS_WINDOWS_1252:  { seq = enc_windows_1252[c];  break; }
            case ENC_CS_WINDOWS_1256:  { seq = enc_windows_1256[c];  break; }
            case ENC_CS_IBM437:  { seq = enc_ibm437[c];  break; }
            case ENC_CS_IBM850:  { seq = enc_ibm850[c];  break; }
            case ENC_CS_IBM858:  { seq = enc_ibm858[c];  break; }
            default:  { seq = NULL;  break; }
         }
         if(NULL == seq)  { posix_free((void*) buf);  buf = NULL;  break; }
         for(ii = 0; ii < 4; ++ii)
         {
            if(!seq[ii])  { break; }
            else  { buf[bi++] = seq[ii]; }
         }
         ++i;
      }
      if(NULL != buf)
      {
         buf[bi] = 0;
         res = buf;
      }
   }

   return(res);
}


/* ========================================================================== */
/* Decode next Unicode codepoint from UTF-8 string
 *
 * \param[in]     s  UTF-8 string to decode
 * \param[in,out] i  Pointer to current index in string
 *
 * \attention
 * The string \e s MUST be already checked for valid UTF-8 encoding before
 * calling this function!
 *
 * On success, the index of the next codepoint is written to the location
 * pointed to by \e i .
 *
 * \return
 * - Next Unicode codepoint on success
 * - -1 on error
 */

static long int  enc_uc_decode_utf8(const char*  s, size_t*  i)
{
   long int  res = -1L;
   int  c;
   int  multibyte = 0;
   size_t  len = 0;
   size_t  remaining = 0;
   unsigned long int  mbc = 0;
   int  error = 0;

   /* Assignment in truth expression is intended */
   while((c = (int) s[(*i)++]))
   {
      /* Check for singlebyte codepoint */
      if(!multibyte)
      {
         if(0 <= c && 127 >= c)  { res = (long int) c;  break; }
         else  { multibyte = 1; }
      }
      /* Decode multibyte codepoint */
      if(multibyte)
      {
         if(!remaining)
         {
            if((c & 0xE0) == 0xC0)  { len = 2; }
            else if((c & 0xF0) == 0xE0)  { len = 3; }
            else if((c & 0xF8) == 0xF0)  { len = 4; }
            switch(len)
            {
               case 2:  mbc |= (unsigned long int) (c & 0x1F) << 6;  break;
               case 3:  mbc |= (unsigned long int) (c & 0x0F) << 12;  break;
               case 4:  mbc |= (unsigned long int) (c & 0x07) << 18;  break;
               default:
               {
                  print_error("ENC: UTF-8 decoder called with invalid data");
                  error = 1;
                  break;
               }
            }
            if(error)  { res = -1L;  break; }
            remaining = len - (size_t) 1;
         }
         else
         {
            --remaining;
            mbc |= (unsigned long int) (c & 0x3F) << remaining * (size_t) 6;
            if(!remaining)
            {
               /* Codepoint decoding complete */
               res = (long int) mbc;
               break;
            }
         }
      }
   }

   return(res);
}


/* ========================================================================== */
/*! \brief Encode Unicode codepoints to UTF-8
 *
 * \param[out]     buf   Encoded UTF-8 string
 * \param[in,out]  i     Current index in \e buf
 * \param[in]      dbuf  Codepoint buffer
 * \param[in,out]  di    Number of codepoints in \e dbuf
 *
 * \attention
 * The target buffer \e buf must be large enough for the encoded data. This must
 * be ensured by the caller using worst case calculations.
 *
 * On success, the start index of the next codepoint is written to the location
 * pointed to by \e i and zero is written to the location pointed to by \e di .
 */

/* 'static' removed because test program must call this function */
void  enc_uc_encode_utf8(char*  buf, size_t*  i, long int*  dbuf, size_t*  di)
{
   size_t  ii;
   int  inval = 0;
   unsigned char  prefix;
   unsigned char  data;

   for(ii = 0; ii < *di; ++ii)
   {
      if (0L > dbuf[ii]) { inval = 1; }
      else if(0x00007FL >= dbuf[ii])  { buf[(*i)++] = (char) dbuf[ii]; }
      else if(0x0007FFL >= dbuf[ii])
      {
         data = (unsigned char) ((dbuf[ii] >> 6) & 0x1FL);
         prefix = 0xC0;
         buf[(*i)++] = (char) (prefix | data);
         data = (unsigned char) (dbuf[ii] & 0x3FL);
         prefix = 0x80;
         buf[(*i)++] = (char) (prefix | data);
      }
      else if(0x00FFFFL >= dbuf[ii])
      {
         data = (unsigned char) ((dbuf[ii] >> 12) & 0x0FL);
         prefix = 0xE0;
         buf[(*i)++] = (char) (prefix | data);
         data = (unsigned char) ((dbuf[ii] >> 6) & 0x3FL);
         prefix = 0x80;
         buf[(*i)++] = (char) (prefix | data);
         data = (unsigned char) (dbuf[ii] & 0x3FL);
         prefix = 0x80;
         buf[(*i)++] = (char) (prefix | data);
      }
      else if(0x10FFFFL >= dbuf[ii])
      {
         data = (unsigned char) ((dbuf[ii] >> 18) & 0x07L);
         prefix = 0xF0;
         buf[(*i)++] = (char) (prefix | data);
         data = (unsigned char) ((dbuf[ii] >> 12) & 0x3FL);
         prefix = 0x80;
         buf[(*i)++] = (char) (prefix | data);
         data = (unsigned char) ((dbuf[ii] >> 6) & 0x3FL);
         prefix = 0x80;
         buf[(*i)++] = (char) (prefix | data);
         data = (unsigned char) (dbuf[ii] & 0x3FL);
         prefix = 0x80;
         buf[(*i)++] = (char) (prefix | data);
      }
      else  { inval = 1; }
      if(inval)
      {
         print_error("ENC: Unicode UTF-8 encoder: Invalid codepoint detected");
         buf[(*i)++] = '?';
         inval = 0;
      }
   }
   *di = 0;
}


/* ========================================================================== */
/* Convert from UTF-16BE to UTF-8 NFC
 *
 * \param[in] mb   Multibyte input data to convert (in UTF-16BE format)
 * \param[in] len  Length in bytes
 *
 * If \e len is an odd number, the last byte of input data \e mb is ignored.
 *
 * \attention
 * The result string is only guaranteed to have valid UTF-8 encoding. It
 * nevertheless may be semantically invalid and is not normalized!
 *
 * \return
 * - Pointer to result string in UTF-8 format (a new memory block was allocated)
 * - NULL on error
 */

static const char*  enc_uc_convert_utf16be_to_utf8(const char*  mb, size_t  len)
{
   const char*  res = NULL;
   char*  buf = NULL;
   size_t  bi = 0;
   size_t  i;
   long int  ucp;  /* Unicode codepoint */
   int  surrogate = 0;  /* Flag indicating that trail surrogate is expected */
   long int  lead_surrogate = 0;
   size_t  di;  /* Number of codepoints for UTF-8 encoder */

   /* Ensure even byte count */
   if((size_t) 1 & len)  { --len; }
   if(len)
   {
      /* Allocate buffer */
      buf = (char*) posix_malloc(len * (size_t) 2 + (size_t) 1);
      if(NULL != buf)
      {
         /* Process 16 bit words */
         for(i = 0; i < len; i += 2)
         {
            /* Decode UTF-16 using big endian byte order */
            ucp = (long int) (unsigned char) mb[i] << 8
                  | (long int) (unsigned char) mb[i + (size_t) 1];
            /* Decode surrogate pair and extract codepoint */
            if(0x00D800L <= ucp && 0x00DFFFL >= ucp)
            {
               if(!surrogate
                  && (0x00D800L <= ucp && 0x00DBFFL >= ucp))
               {
                  lead_surrogate = ucp - 0x00D800L;
                  surrogate = 1;
                  continue;
               }
               else if(surrogate
                       && (0x00DC00L <= ucp && 0x00DFFFL >= ucp))
               {
                  ucp = (lead_surrogate << 10L) | (ucp - 0x00DC00L);
                  ucp += 0x010000L;
                  surrogate = 0;
               }
               else
               {
                  print_error("ENC: Missing UTF-16 lead surrogate");
                  ucp = (long int) (unsigned char) '?';
                  surrogate = 0;
               }
            }
            if(surrogate && !(0x00DC00L <= ucp && 0x00DFFFL >= ucp))
            {
               print_error("ENC: Invalid UTF-16 trail surrogate");
               ucp = (long int) (unsigned char) '?';
            }
            surrogate = 0;
            /* Encode codepoint as UTF-8 into target buffer */
            di = 1;
            enc_uc_encode_utf8(buf, &bi, &ucp, &di);
         }
      }
   }

   /* Check for error */
   if(!bi)
   {
      print_error("ENC: Unicode UTF-16 to UTF-8 converter failed");
      if(NULL != buf)  { posix_free((void*) buf); }
   }
   else
   {
      /* Terminate result string on success */
      buf[bi] = 0;
      res = buf;
   }

   return(res);
}


/* ========================================================================== */
/* Check whether Unicode codepoint is an unwanted control character
 *
 * \param[in] ucp   Unicode codepoint to check
 *
 * RFC5198 forbids C1 control characters => We reject them.
 *
 * Unicode variation selectors and control characters for bidirectional text
 * should be accepted. The Cocoa backend of FLTK (for Apple OSX) is able to
 * process them.
 */

static int  enc_uc_check_control(long int  ucp)
{
   int  res = 0;

   /* Check for ASCII C0 control characters plus DEL */
   if(0x1FL >= ucp || 0x7FL == ucp)
   {
      /* Accept only HT, LF and CR (required for MIME canonical format) */
      if(0x09L != ucp && 0x0AL != ucp && 0x0DL != ucp)  { res = -1; }
   }
   /* Reject ISO8859 C1 control characters */
   else if(0x80L <= ucp && 0x9FL >= ucp)  { res = -1; }
   /*
    * Reject Unicode ZERO WIDTH SPACE
    * It seems that there is a bug in FLTK showing this character with a
    * replacement glyph (instead of no glyph) in some situations.
    */
   else if(0x200BL == ucp)  { res = -1; }
   /* Reject Unicode INTERLINEAR ANNOTATION special characters */
   else if(0xFFF9L <= ucp && 0xFFFBL >= ucp)  { res = -1; }
   /* Reject Unicode LINE SEPARATOR and PARAGRAPH SEPARATOR */
   else if(0x2028L <= ucp && 0x2029L >= ucp)  { res = -1; }
   /* Reject Unicode LANGUAGE TAG and associated range */
   else if( 0xE0001L == ucp ||
           (0xE0020L <= ucp && 0xE007FL >= ucp) )  { res = -1; }

   return(res);
}


/* ========================================================================== */
/* Lookup canonical decomposition and combining class of Unicode codepoint
 *
 * \param[in]  ucp   Unicode codepoint to lookup
 * \param[out] res   Result
 */

static void  enc_uc_lookup_cdc(long int  ucp, struct uc_cdc*  res)
{
   size_t  i = 0;

   res->cp = ucp;
   res->ccc = 0;
   res->dc1 = -1L;
   res->dc2 = -1L;
   /* ASCII codepoints are always starters without canonical decomposition */
   if(128L <= ucp)
   {
      /* Lookup codepoint in Unicode database */
      while(-1L != uc_cdc_table[i].cp)
      {
         if(ucp == uc_cdc_table[i].cp)
         {
            /* Found nonstarter or canonical decomposable */
            res->ccc = uc_cdc_table[i].ccc;
            res->dc1 = uc_cdc_table[i].dc1;
            res->dc2 = uc_cdc_table[i].dc2;
            break;
         }
         ++i;
      }
   }
   /* Use codepoint as decomposition if no canonical decomposition was found */
   if(-1L == res->dc1)  { res->dc1 = ucp;  res->dc2 = -1L; }
}


/* ========================================================================== */
/* Lookup canonical composition of codepoint pair
 *
 * \param[in]  starter  Unicode codepoint of starter
 * \param[in]  cm       Unicode codepoint of combination mark (or other starter)
 *
 * \note
 * If \e starter is not a codepoint with canoncial combining class zero, the
 * function returns error.
 *
 * \return
 * - Codepoint of composition character on success
 * - -1 on error
 */

static long int  enc_uc_lookup_cc(long int  starter, long int  cm)
{
   long int  res = -1;
   struct uc_cdc  cdc;  /* Canonical decomposition data */
   size_t  i;
   size_t  ii;
   long int  first;
   long int  last;

   /* Only used for hangul algorithmic canonical composition */
   const long int  SBase = 0xAC00L;
   const long int  LBase = 0x1100L;
   const long int  VBase = 0x1161L;
   const long int  TBase = 0x11A7L;
   const long int  NCount = 588L;
   const long int  TCount = 28L;
   int  jamo = 0;  /* Number of jamo in syllable */
   enum uc_hs_type  hst;
   enum uc_hs_type  hst2;
   long int  LIndex;
   long int  VIndex;
   long int  TIndex;

   /* Check whether starter really is a starter */
   enc_uc_lookup_cdc(starter, &cdc);
   if(!cdc.ccc)
   {
      /* Yes => Lookup decomposition in Unicode database */
      i = 0;
      while(-1L != uc_cdc_table[i].cp)
      {
         if(uc_cdc_table[i].dc1 == starter && uc_cdc_table[i].dc2 == cm)
         {
            /* Found composition */
            res = uc_cdc_table[i].cp;
            /* Check for composition exception */
            ii = 0;
            while(-1L != uc_fce_table[ii].first)
            {
               first = uc_fce_table[ii].first;
               last = uc_fce_table[ii].last;
               if(first <= res && last >= res)
               {
                  /* Composition exception found */
#if ENC_UC_NORM_DEBUG
                  printf("   Canonical composition exception\n");
#endif  /* ENC_UC_NORM_DEBUG */
                  res = -1;
                  break;
               }
               ++ii;
            }
            break;
         }
         ++i;
      }

      /*
       * On error, check whether algorithmic composition is possible using
       * Unicode hangul syllable type database
       */
      enc_uc_lookup_cdc(cm, &cdc);
      if(!cdc.ccc)
      {
         i = 0;
         while(-1L == res && -1L != uc_hst_table[i].first)
         {
            first = uc_hst_table[i].first;
            last = uc_hst_table[i].last;
            hst = uc_hst_table[i].hst;
            if(first <= starter && last >= starter)
            {
               if(UC_HST_L == hst || UC_HST_LV == hst)
               {
                  /* Starter is a hangul L-type consonant or LV-type syllable */
                  ii = 0;
                  while(-1L != uc_hst_table[ii].first)
                  {
                     first = uc_hst_table[ii].first;
                     last = uc_hst_table[ii].last;
                     hst2 = uc_hst_table[ii].hst;
                     if(first <= cm && last >= cm)
                     {
                        if(UC_HST_L == hst && UC_HST_V == hst2)
                        {
#if ENC_UC_NORM_DEBUG
                           printf("   Canonical composition"
                                  " for hangul LV-syllable found\n");
#endif  /* ENC_UC_NORM_DEBUG */
                           jamo = 2;
                           if(LBase <= starter && VBase <= cm)
                           {
                              LIndex = starter - LBase;
                              VIndex = cm - VBase;
#if ENC_UC_NORM_DEBUG
                              printf("Hangul LIndex: %ld\n", LIndex);
                              printf("Hangul VIndex: %ld\n", VIndex);
#endif  /* ENC_UC_NORM_DEBUG */
                              res = SBase + LIndex * NCount + VIndex * TCount;
                           }
                        }
                        else if(UC_HST_LV == hst && UC_HST_T == hst2)
                        {
#if ENC_UC_NORM_DEBUG
                           printf("   Canonical composition"
                                  " for hangul LVT-syllable found\n");
#endif  /* ENC_UC_NORM_DEBUG */
                           jamo = 3;
                           if(TBase <= cm)
                           {
                              TIndex = cm - TBase;
#if ENC_UC_NORM_DEBUG
                              printf("Hangul TIndex: %ld\n", TIndex);
#endif  /* ENC_UC_NORM_DEBUG */
                              res = starter + TIndex;
                           }
                        }
                        if(jamo)
                        {
                           if(-1L == res)
                           {
                              print_error("ENC: Unicode algorithmic composition"
                                          " for hangul syllable failed");
                           }
#if 1
                           /* Optional: Check hangul syllable type of result */
                           else
                           {
                              ii = 0;
                              while(-1L != uc_hst_table[ii].first)
                              {
                                 first = uc_hst_table[ii].first;
                                 last = uc_hst_table[ii].last;
                                 hst = uc_hst_table[ii].hst;
                                 if(first <= res && last >= res)
                                 {
                                    if(2 == jamo && UC_HST_LV != hst)
                                    {
                                       /* Result should be a LV-syllable! */
                                       res = -1L;
                                    }
                                    if(3 == jamo && UC_HST_LVT != hst)
                                    {
                                       /* Result should be a LVT-syllable! */
                                       res = -1L;
                                    }
                                    break;
                                 }
                                 ++ii;
                              }
                              if(-1L == res)
                              {
                                 print_error("ENC: Invalid Unicode hangul syllable"
                                             " detected (Bug)");
                              }
                           }
#endif
                           break;
                        }
                     }
                     ++ii;
                  }
               }
            }
            ++i;
         }
      }
   }

   return(res);
}


/* ========================================================================== */
/* Get number of Unicode glyphs from UTF-8 string
 *
 * \param[in] s    UTF-8 string to decode
 * \param[in] end  Check glyph count up to (but not including) this index
 *
 * To check all glyphs of a string, set \e end to zero.
 *
 * \attention
 * The string \e s MUST be already checked for valid UTF-8 encoding before
 * calling this function!
 *
 * \note
 * For this function the glyph count is defined as "number of starters".
 * For complex scripts this may not match the display width.
 *
 * \return
 * - Glyph count
 */

static size_t  enc_uc_get_glyph_count(const char*  s, size_t  end)
{
   size_t  res = 0;
   size_t  i = 0;
   long int  ucp;
   struct uc_cdc  cdc;  /* Canonical decomposition data */

   while(1)
   {
      ucp = enc_uc_decode_utf8(s, &i);
      if(-1L == ucp)  { break; }
      else
      {
         /* Check whether codepoint is a starter */
         enc_uc_lookup_cdc(ucp, &cdc);
         if(!cdc.ccc)  { ++res; }
      }
      if(end && i >= end)  { break; }
   }

   return(res);
}


/* ========================================================================== */
/* Quick check for Unicode NFC normalization
 *
 * This function verify that:
 * - all codepoints are allowed in NFC
 * - the canonical ordering of combining marks is correct
 *
 * \param[in] s  UTF-8 string to check
 *
 * \attention
 * The string \e s MUST be already checked for valid UTF-8 encoding before
 * calling this function!
 */

static int  enc_uc_check_nfc(const char*  s)
{
   int  res = 0;
   size_t  i = 0;
   long int  ucp = 0;
   long int  first;
   long int  last;
   size_t  ii;
   struct uc_cdc  cdc;  /* Canonical decomposition data */
   unsigned char  ccc_last = 0;

   while(1)
   {
      ucp = enc_uc_decode_utf8(s, &i);
      if(-1L == ucp)  { break; }
      /* Quick check for ASCII */
      if(128L <= ucp)
      {
         /* Lookup codepoint in Unicode database */
         ii = 0;
         while(-1L != uc_qc_nfc_table[ii].first)
         {
            first = uc_qc_nfc_table[ii].first;
            last = uc_qc_nfc_table[ii].last;
            if(first <= ucp && last >= ucp)
            {
               /* Codepoint is (maybe) not allowed in NFC */
               res = -1;
               break;
            }
            ++ii;
         }
         if(res)  { break; }
         /* Check ordering of combining marks */
         enc_uc_lookup_cdc(ucp, &cdc);
         if(cdc.ccc && (cdc.ccc < ccc_last))  { res = -1;  break; }
         ccc_last = cdc.ccc;
      }
      else  { ccc_last = 0; }
   }

#if ENC_UC_NORM_DEBUG
   if(res)
   {
      printf("Maybe not NFC: %s (len: %u)\n  ", s, (unsigned int) strlen(s));
      i = 0;  while(s[i])
      {
         printf(" 0x%02X", (unsigned int) (unsigned char) s[i++]);
      }
      printf("\n");
   }
#endif  /* ENC_UC_NORM_DEBUG */

   return(res);
}


/* ========================================================================== */
/* Unicode canonical decomposition engine
 *
 * This function is reentrant and calls itself for recursive decomposition.
 *
 * \attention
 * The decomposition buffer \e dbuf must have the fixed size
 * \ref ENC_UC_DECOMPOSITION_BUFSIZE bytes.
 *
 * \param[in]      ucp   Unicode codepoint
 * \param[in,out]  dbuf  Decomposition buffer
 * \param[in]      di    Pointer to index in decomposition buffer
 */

static int  enc_uc_engine_decompose(long int  ucp, long int*  dbuf, size_t*  di)
{
   int  res = 0;
   struct uc_cdc  cdc;  /* Canonical decomposition data */

   /* Ensure that there is space for 2 codepoints in decomposition buffer */
   if(ENC_UC_DECOMPOSITION_BUFSIZE - (size_t) 2 <= *di)
   {
      /* Decomposition buffer not large enough */
      print_error("ENC: Unicode canonical decomposition engine failed");
      dbuf[0] = (long int) (unsigned char) '[';
      dbuf[1] = (long int) (unsigned char) 'E';
      dbuf[2] = (long int) (unsigned char) 'r';
      dbuf[3] = (long int) (unsigned char) 'r';
      dbuf[4] = (long int) (unsigned char) 'o';
      dbuf[5] = (long int) (unsigned char) 'r';
      dbuf[6] = (long int) (unsigned char) ']';
      *di = 7;
      res = -1;
   }
   else
   {
      /* Recursively decompose */
      enc_uc_lookup_cdc(ucp, &cdc);
      if(cdc.dc1 != ucp)  { res = enc_uc_engine_decompose(cdc.dc1, dbuf, di); }
      else  { dbuf[(*di)++] = cdc.dc1; }
      if(-1L != cdc.dc2)  { dbuf[(*di)++] = cdc.dc2; }
   }

   return(res);
}


/* ========================================================================== */
/* Unicode canonical ordering engine
 *
 * All bursts of codepoints with nonzero canonical combining class are
 * stable sorted in ascending order.
 *
 * \param[in,out]  dbuf  Decomposition buffer
 * \param[in]      di    Number of codepoints in decomposition buffer
 */

static void  enc_uc_engine_reorder(long int*  dbuf, size_t  di)
{
   size_t  i, ii, iii, iiii;
   struct uc_cdc  cdc;  /* Canonical decomposition data */
   size_t  len;
   long int  tmp;
   unsigned char  ccc1;
   unsigned char  ccc2;

   for(i = 0; i < di; ++i)
   {
      enc_uc_lookup_cdc(dbuf[i], &cdc);
      /* Starters (Ccc = 0) always stay in place */
      if(cdc.ccc)
      {
#if ENC_UC_NORM_DEBUG
         printf("   Nonstarter: U+%04lX (ccc=%u)\n",
                dbuf[i], (unsigned int)  cdc.ccc);
#endif  /* ENC_UC_NORM_DEBUG */
         ii = i;
         while(++ii < di)
         {
            enc_uc_lookup_cdc(dbuf[ii], &cdc);
            if(!cdc.ccc)  { break; }
         }
         len = ii - i;
         /* Sort burst of nonstarter codepoints (ccc != 0) to canonical order */
#if ENC_UC_NORM_DEBUG
         printf("   Sort burst: len=%u\n", (unsigned int)  len);
#endif  /* ENC_UC_NORM_DEBUG */
         for(iii = i; iii < i + len; ++iii)
         {
            /*
             * Bubble sort from end of buffer
             * This is very inefficient because the ccc lookup data is not
             * buffered. For european languages there are seldom more than
             * two or three codepoints combined => Keep it simple.
             */
            for(iiii = i + len - (size_t) 1; iiii > iii; --iiii)
            {
               enc_uc_lookup_cdc(dbuf[iiii - (size_t) 1], &cdc);
               ccc1 = cdc.ccc;
               enc_uc_lookup_cdc(dbuf[iiii], &cdc);
               ccc2 = cdc.ccc;
               if(ccc2 < ccc1)
               {
                  tmp = dbuf[iiii - (size_t) 1];
                  dbuf[iiii - (size_t) 1] = dbuf[iiii];
                  dbuf[iiii] = tmp;
               }
            }
         }
      }
#if ENC_UC_NORM_DEBUG
      else  { printf("   Starter   : U+%04lX\n", dbuf[i]); }
#endif  /* ENC_UC_NORM_DEBUG */
   }
}


/* ========================================================================== */
/* Unicode canonical composition engine
 *
 * \param[in,out]  dbuf  Codepoint buffer
 * \param[in]      di    Pointer to number of codepoints in buffer
 */

static void  enc_uc_engine_compose(long int*  dbuf, size_t*  di)
{
   size_t  i = 0;
   size_t  ii;
   long int  ucp;  /* Unicode codepoint */
   struct uc_cdc  cdc;  /* Canonical decomposition data */
   unsigned char  ccc;
   int  skip;

   while(++i < *di)
   {
      /* Check whether codepoint i can be canonically composed with starter */
#if ENC_UC_NORM_DEBUG
      printf("   ---\n");
      printf("   Starter at beginning : U+%04lX\n", dbuf[0]);
      printf("   Codepoint in question: U+%04lX\n", dbuf[i]);
#endif  /* ENC_UC_NORM_DEBUG */
      ucp = enc_uc_lookup_cc(dbuf[0], dbuf[i]);
      if(-1L != ucp)
      {
         /* Yes => Get canonical combining class */
         enc_uc_lookup_cdc(dbuf[i], &cdc);
         ccc = cdc.ccc;
#if ENC_UC_NORM_DEBUG
         printf("   Codepoint has ccc    : %u\n", (unsigned int) ccc);
         printf("   Canonical composition: U+%04lX\n", ucp);
#endif  /* ENC_UC_NORM_DEBUG */
         /* Search for other codepoints with same canonical combining class */
         skip = 0;
         for(ii = 1; ii < i; ++ii)
         {
            enc_uc_lookup_cdc(dbuf[ii], &cdc);
            if(cdc.ccc >= ccc)
            {
               /* Found => Preserve canonical ordering => Don't compose */
#if ENC_UC_NORM_DEBUG
               printf("   => Don't compose\n");
#endif  /* ENC_UC_NORM_DEBUG */
               skip = 1;
               break;
            }
         }
         if(skip)  { continue; }
         /* Not found => Compose */
#if ENC_UC_NORM_DEBUG
         printf("   => Compose\n");
#endif  /* ENC_UC_NORM_DEBUG */
         for(ii = i; ii < *di - (size_t) 1; ++ii)
         {
            dbuf[ii] = dbuf[ii + (size_t) 1];
         }
         dbuf[--*di] = -1L;
         dbuf[0] = ucp;
         /* Rewind index for now missing codepoint */
         --i;
      }
#if ENC_UC_NORM_DEBUG
      printf("   ---\n");
#endif  /* ENC_UC_NORM_DEBUG */
   }
}


/* ========================================================================== */
/* Unicode NFC normalization engine part 1
 *
 * \param[in]  s  Valid Unicode string with arbitrary or no normalization
 * \param[out] l  Pointer to length of result
 *
 * \attention
 * The string \e s MUST be already checked for valid UTF-8 encoding before
 * calling this function!
 *
 * Part 1 do all the work but can't compose starters, therefore the result my
 * contain starter pairs with canonical composition and must be postprocessed
 * by part 2 of the engine.
 *
 * \return
 * - Pointer to processed data (input for part 2 of the engine)
 *   A new memory block was allocated
 * - NULL on error (Original memory block for \e s is still allocated)
 */

static const char*  enc_uc_engine_nfc_part1(const char*  s, size_t*  l)
{
   char*  res = NULL;
   size_t  rlen = 0;
   size_t  ri = 0;
   char*  p;
   size_t  i = 0;
   size_t  last;
   long int  ucp;  /* Unicode codepoint */
   struct uc_cdc  cdc;  /* Canonical decomposition data */
   long int  dbuf[ENC_UC_DECOMPOSITION_BUFSIZE];
   size_t  di = 0;
   int  error = 0;  /* Error flag => Skip to next starter */

   while(1)
   {
      /* Allocate memory in exponentially increasing chunks */
      if(rlen - ri <= (size_t) 4 * ENC_UC_DECOMPOSITION_BUFSIZE)
      {
         /*
          * Ensure there is space in the result buffer for at least 4 times the
          * decompositon buffer size. Reason: Every Unicode codepoint can
          * consume up to 4 bytes after encoded to UTF-8 in worst case.
          */
         if(!rlen)  { rlen = (size_t) 4 * ENC_UC_DECOMPOSITION_BUFSIZE; }
         rlen *= (size_t) 2;
         p = posix_realloc((void*) res, rlen);
         if(NULL == p)  { posix_free((void*) res);  res = NULL;  break; }
         else  { res = p; }
      }
      /* Check whether next codepoint is a starter (ccc = 0) */
      last = i;
      ucp = enc_uc_decode_utf8(s, &i);
      if(-1L == ucp)  { break; }
      enc_uc_lookup_cdc(ucp, &cdc);
      if(!cdc.ccc)
      {
         /* Yes => Check for buffered sequence */
         if(di)
         {
            /* Present => Push last codepoint back and flush buffer first */
            i = last;
            enc_uc_engine_reorder(dbuf, di);
            enc_uc_engine_compose(dbuf, &di);
            enc_uc_encode_utf8(res, &ri, dbuf, &di);
            error = 0;
            continue;
         }
      }
      /* Recursive canonical decomposition */
      if(!error)  { error = enc_uc_engine_decompose(ucp, dbuf, &di); }
   }
   /* Flush buffer */
   if(di)
   {
      enc_uc_engine_reorder(dbuf, di);
      enc_uc_engine_compose(dbuf, &di);
      enc_uc_encode_utf8(res, &ri, dbuf, &di);
   }

   /* Terminate result string */
   if(NULL != res)
   {
      res[ri] = 0;
      *l = ri;
   }

   return(res);
}


/* ========================================================================== */
/* Unicode NFC normalization engine part 2
 *
 * \param[in]  s     Unicode string processed by part 1 of the engine
 * \param[in]  l     Length of \e s
 * \param[out] flag  Flag indicating modified data
 *
 * Part 2 is for canonical composition of codepoint pairs that are both
 * starters. This includes algorithmic canonical composition for hangul
 * syllables.
 *
 * \return
 * - Pointer to precomposed Unicode data (UTF-8 encoded with NFC normalization)
 *   A new memory block was allocated
 * - NULL on error (Undefined data written to \e flag)
 */

static const char*  enc_uc_engine_nfc_part2(const char*  s, size_t  l,
                                            int*  flag)
{
   char*  res = NULL;
   size_t  ri = 0;
   size_t  i = 0;
   long int  ucp;  /* Unicode codepoint */
   struct uc_cdc  cdc;  /* Canonical decomposition data */
   long int  dbuf[2];
   size_t  di = 0;

   *flag = 0;
#if ENC_UC_NORM_DEBUG
   printf("   *** Part 2 ***\n");
#endif  /* ENC_UC_NORM_DEBUG */
   res = posix_malloc(++l);
   if(NULL != res)
   {
      while(1)
      {
         /* Append next codepoint to buffer */
         ucp = enc_uc_decode_utf8(s, &i);
         if(-1L == ucp)  { break; }
         dbuf[di++] = ucp;
         /* Check whether codepoint is a starter (ccc = 0) */
         enc_uc_lookup_cdc(ucp, &cdc);
         if(cdc.ccc)
         {
            /* No => Flush buffer */
            enc_uc_encode_utf8(res, &ri, dbuf, &di);
         }
         else
         {
            /* Yes => Check for canonical composition of starter pair */
            if((size_t) 2 == di)
            {
               enc_uc_engine_compose(dbuf, &di);
               /* Flush first starter if there was no canonical composition */
               if((size_t) 2 == di)
               {
                  di = 1;
                  enc_uc_encode_utf8(res, &ri, dbuf, &di);
                  dbuf[0] = ucp;
                  di = 1;
               }
               else
               {
                  /* Canonical composition found for starter pair */
                  *flag = 1;
               }
            }
         }
      }
   }
   /* Flush buffer */
   if(di)  { enc_uc_encode_utf8(res, &ri, dbuf, &di); }
   /* Terminate result string */
   if(NULL != res)  { res[ri] = 0; }

#if ENC_UC_NORM_DEBUG
   if(NULL != res)
   {
      printf("Now NFC: %s (len: %u)\n  ", res, (unsigned int) strlen(res));
      i = 0;  while(res[i])
      {
         printf(" 0x%02X", (unsigned int) (unsigned char) res[i++]);
      }
      printf("\n\n");
   }
#endif  /* ENC_UC_NORM_DEBUG */

   return(res);
}


/* ========================================================================== */
/* Normalize UTF-8 string to NFC
 *
 * Documentation about Unicode normalization:
 * <br>
 * http://www.unicode.org/reports/tr15/
 *
 * \param[in] s  UTF-8 string to normalize
 *
 * RFC5198 recommends NFC for use in general Internet text messages => We do so.
 *
 * \attention
 * The string \e s MUST be already checked for valid UTF-8 encoding before
 * calling this function!
 *
 * \note
 * An Unicode string with valid transformation encoding can still be
 * semantically nonsense. If it starts with a codepoint that is not a "starter"
 * in terms of the standard, the resulting string starts with a "defective
 * combining character sequence" - but may still make sense if concatenated
 * with other data in front of it.
 * This function always strips defective combining character sequences at the
 * beginning so that the result becomes semantically valid even when standing
 * alone.
 *
 * \return
 * - Pointer to precomposed Unicode data (UTF-8 encoded with NFC normalization)
 *   If the result is not equal to \e s , a new memory block was allocated
 * - NULL on error
 */

static const char*  enc_uc_normalize_to_nfc(const char*  s)
{
   const char*  res = NULL;
   const char*  tgt;
   int  skip = 0;  /* Garbage at the beginning of string must be skipped */
   long int  ucp;  /* Unicode codepoint */
   struct uc_cdc  cdc;  /* Canonical decomposition data */
   size_t  i = 0;
   size_t  last;
   size_t  len;
   size_t  l = 0;
   int  flag;

   /* Strip all nonstarters at the beginning */
   while(1)
   {
      last = i;
      ucp = enc_uc_decode_utf8(s, &i);
      if(-1L == ucp)  { break; }
      enc_uc_lookup_cdc(ucp, &cdc);
      if(!cdc.ccc)  { break; }
      else
      {
         /* The Unicode data tries to compose something with void */
         if(!skip)  { print_error("ENC: Semantic error in Unicode string"); }
         skip = 1;
      }
   }
   i = last;
   if(skip)
   {
      len = strlen(&s[i]);
      res = (const char*) posix_malloc(++len);
      if(NULL != res)  { memcpy((void*) res, &s[i], len); }
   }
   else  { res = s; }
   i = 0;
   /* Quick check whether the string is already in NFC */
   if(enc_uc_check_nfc(res))
   {
      /* No => Normalize string to NFC */
      tgt = enc_uc_engine_nfc_part1(res, &l);
      if(res != s)  { posix_free((void*) res); }
      res = tgt;
      if(NULL != res)
      {
         /* Fixme: This is ugly and very slow => Should be single pass */
         tgt = enc_uc_engine_nfc_part2(res, l, &flag);
         posix_free((void*) res);
         res = tgt;
         if(NULL != res && flag)
         {
            /* Part 1 must be repeated if starters were composed */
            tgt = enc_uc_engine_nfc_part1(res, &l);
            posix_free((void*) res);
            res = tgt;
         }
      }
   }
   /* Check for error */
   if(NULL == res)  { print_error("ENC: Unicode normalization failed"); }

   return(res);
}


/* ========================================================================== */
/* Convert from supported ISO2022 character sets to UTF-8 NFC
 *
 * \note
 * This is a quick&dirty, incomplete implementation. If there should be added
 * support for other ISO2022 character sets in the future, this function should
 * be rewritten to use a shared, full-featured escape sequence decoder.
 *
 * \param[in] charset  8 bit character set used for string \e s
 * \param[in] s        String to convert
 *
 * \return
 * - Pointer to result (if not equal to \e s , a new memory block was allocated)
 * - NULL on error
 */

static const char*  enc_iso2022_convert_to_utf8_nfc(enum enc_mime_cs  charset,
                                                    const char*  s)
{
   const char*  res = s;
   size_t  len = strlen(s);
   size_t  i;
   enum iso2022_state  state = ISO2022_ASCII;
   int  esc = 0;
   int  mode = 1;
   int  first = 1;
   unsigned char  ku = 0;
   unsigned char  ten;
   char*  buf;
   size_t  bi = 0;
   size_t  ii;
   long int  jis;
   long int  ucp;

   switch(charset)
   {
      case ENC_CS_ISO2022_JP:
      {
         /* Original japanese JUNET variant according to RFC1468 */
         if(NULL != strchr(s, 0x1B))
         {
            /*
             * Allocate the result buffer twice as large as the original data
             * This is always sufficient because the escape sequences are deleted
             * and every multibyte character can't use more that 4 bytes in UTF-8.
             */
            buf = (char*) posix_malloc(++len * (size_t) 2);
            if(NULL != buf)
            {
               for(i = 0; i < len; ++i)
               {
                  /* Check for escape sequence */
                  if(0x1BU == (unsigned int) s[i])  { esc = 1;  continue; }
                  /* Decode escape sequence */
                  if(1 == esc)
                  {
                     mode = 0;
                     if('(' == s[i])  { mode = 1; }
                     else if('$' == s[i])  { mode = 2; }
                     esc = 2;
                     continue;
                  }
                  else if(2 == esc)
                  {
                     if(1 == mode)
                     {
                        if('B' == s[i])  { state = ISO2022_ASCII; }
                        else if('J' == s[i])  { state = ISO2022_ISO646; }
                     }
                     else if(2 == mode)
                     {
                        /* Don't distinguish between the JIS X 0208 variants */
                        state = ISO2022_JIS_X_0208;
                        first = 1;
                     }
                     else
                     {
                        /* Switch back to ASCII if escape sequence is invalid */
                        state = ISO2022_ASCII;
                        print_error("ENC: Invalid ISO2022 escape sequence");
                     }
                     esc = 0;
                     continue;
                  }
                  /* Copy next character */
                  switch(state)
                  {
                     case ISO2022_ISO646:
                     {
                        if(0x80U & (unsigned int) s[i])
                        {
                           print_error("ENC: Invalid ISO646 codepoint"
                                       " in ISO2022-JP data");
                           buf[bi++] = '?';
                        }
                        else
                        {
                           /* Handle Yen sign */
                           if(0x5CU == (unsigned int) s[i])
                           {
                              buf[bi++] = (char) 0xC2;
                              buf[bi++] = (char) 0xA5;
                           }
                           /* Handle overline */
                           else if('~' == s[i])
                           {
                              buf[bi++] = (char) 0xE2;
                              buf[bi++] = (char) 0x80;
                              buf[bi++] = (char) 0xBE;
                           }
                           else  { buf[bi++] = s[i]; }
                        }
                        break;
                     }
                     case ISO2022_JIS_X_0208:
                     {
                       /*
                        * All characters are 2 byte sequences
                        * These codepoints are called "kuten".
                        * They form a 94x94 character table.
                        */
                       if(first)  { ku = (unsigned char) s[i]; }
                       else
                       {
                          ten = (unsigned char) s[i];
                          if(33U > ku || 126U < ku || 33U > ten || 126U < ten)
                          {
                             print_error(
                                "ENC: Invalid kuten in ISO2022-JP data");
                             buf[bi++] = '?';
                          }
                          else
                          {
                             /* Convert kuten to Unicode */
                             if( (32U + 9U <= ku && 32U + 15U >= ku)
                                 || (32U + 85U <= ten && 32U + 94U >= ten) )
                             {
                                /* Unassigned codepoint */
                                buf[bi++] = (char) 0xEF;
                                buf[bi++] = (char) 0xBF;
                                buf[bi++] = (char) 0xBD;
                             }
                             else
                             {
                                jis = (long int) ((unsigned long int) ku << 8);
                                jis |= (long int) (unsigned long int) ten;
                                ucp = -1L;
                                ii = 0;
                                while(1)
                                {
                                   if(iso2022_jp_table[ii].jis == -1L)
                                   {
                                      print_error("ENC: Decoding of kuten"
                                                  " in ISO2022-JP data failed");
                                      break;
                                   }
                                   if(iso2022_jp_table[ii].jis == jis)
                                   {
                                      ucp = iso2022_jp_table[ii].uc;
                                      break;
                                   }
                                   ++ii;
                                }
                                if(0L > ucp)
                                {
                                   buf[bi++] = (char) 0xEF;
                                   buf[bi++] = (char) 0xBF;
                                   buf[bi++] = (char) 0xBD;
                                }
                                else
                                {
                                   ii = 1;
                                   enc_uc_encode_utf8(buf, &bi, &ucp, &ii);
                                }
                             }
                          }
                       }
                       first = !first;
                       break;
                     }
                     default:
                     {
                        if(0x80U & (unsigned int) s[i])
                        {
                           print_error("ENC: Invalid ISO646 codepoint"
                                       " in US-ASCII data");
                           buf[bi++] = '?';
                        }
                        else
                        {
                           buf[bi++] = s[i];
                        }
                        break;
                     }
                  }
               }
               /* Ensure result is terminated in any case */
               buf[bi] = 0;
               /* Check for correct exit state */
               if(ISO2022_ASCII != state)
               {
                  print_error("ENC: ISO2022-JP data don't end in ASCII state");
               }
            }
            res = buf;
         }
         break;
      }
      default:
      {
         /* Not supported */
         res = NULL;
         break;
      }
   }

   return(res);
}


/* ========================================================================== */
/* Decode IANA character set description
 *
 * \param[in] s    Description to decode
 * \param[in] len  Length of string \e s
 *
 * This function checks whether the description \e s represents a supported IANA
 * character set name and return the corresponding ID for it.
 * According to RFC2047 the character set is treated case-insensitive.
 *
 * \result
 * - MIME character set ID (from \ref enc_mime_cs )
 * - \c ENC_CS_UNKNOWN on error
 */

static enum enc_mime_cs  enc_mime_get_charset(const char*  s, size_t  len)
{
   enum enc_mime_cs  res = ENC_CS_UNKNOWN;
   char  buf[ENC_CS_BUFLEN];
   size_t  i;
   const char  not_supported[] = "ENC: MIME: Unsupported character set: ";
   char*  p;
   size_t  l;
   int  isoinv = 0;
   int  macinv = 0;
   int  ibmalt = 0;

   if(ENC_CS_BUFLEN <= len)
   {
      /* If you get this error, the value of 'ENC_CS_BUFLEN' is too small */
      print_error("ENC: MIME: Name of character set too long");
   }
   else
   {
      /* Convert description to upper case */
      for(i = 0; i < len; ++i)
      {
         buf[i] = (char) toupper((int) s[i]);
      }
      buf[len] = 0;
      /* Check for all known character sets */
      if(!strcmp(buf, "ISO-8859-1"))  { res = ENC_CS_ISO8859_1; }
      if(!strcmp(buf, "ISO-8859-2"))  { res = ENC_CS_ISO8859_2; }
      if(!strcmp(buf, "ISO-8859-3"))  { res = ENC_CS_ISO8859_3; }
      if(!strcmp(buf, "ISO-8859-4"))  { res = ENC_CS_ISO8859_4; }
      if(!strcmp(buf, "ISO-8859-5"))  { res = ENC_CS_ISO8859_5; }
      if(!strcmp(buf, "ISO-8859-6"))  { res = ENC_CS_ISO8859_6; }
      if(!strcmp(buf, "ISO-8859-7"))  { res = ENC_CS_ISO8859_7; }
      if(!strcmp(buf, "ISO-8859-8"))  { res = ENC_CS_ISO8859_8; }
      if(!strcmp(buf, "ISO-8859-9"))  { res = ENC_CS_ISO8859_9; }
      if(!strcmp(buf, "ISO-8859-10"))  { res = ENC_CS_ISO8859_10; }
      if(!strcmp(buf, "TIS-620"))  { res = ENC_CS_ISO8859_11; }
      if(!strcmp(buf, "ISO-8859-13"))  { res = ENC_CS_ISO8859_13; }
      if(!strcmp(buf, "ISO-8859-14"))  { res = ENC_CS_ISO8859_14; }
      if(!strcmp(buf, "ISO-8859-15"))  { res = ENC_CS_ISO8859_15; }
      if(!strcmp(buf, "ISO-8859-16"))  { res = ENC_CS_ISO8859_16; }
      if(!strcmp(buf, "US-ASCII"))  { res = ENC_CS_ASCII; }
      if(!strcmp(buf, "WINDOWS-1250"))  { res = ENC_CS_WINDOWS_1250; }
      if(!strcmp(buf, "WINDOWS-1251"))  { res = ENC_CS_WINDOWS_1251; }
      if(!strcmp(buf, "WINDOWS-1252"))  { res = ENC_CS_WINDOWS_1252; }
      if(!strcmp(buf, "WINDOWS-1256"))  { res = ENC_CS_WINDOWS_1256; }
      if(!strcmp(buf, "KOI8-R"))  { res = ENC_CS_KOI8R; }
      if(!strcmp(buf, "MACINTOSH"))  { res = ENC_CS_MACINTOSH; }
      if(!strcmp(buf, "IBM437"))  { res = ENC_CS_IBM437; }
      if(!strcmp(buf, "IBM850"))  { res = ENC_CS_IBM850; }
      if(!strcmp(buf, "IBM858"))  { res = ENC_CS_IBM858; }
      if(!strcmp(buf, "ISO-2022-JP"))  { res = ENC_CS_ISO2022_JP; }
      if(!strcmp(buf, "UTF-7"))  { res = ENC_CS_UTF_7; }
      if(!strcmp(buf, "UTF-8"))  { res = ENC_CS_UTF_8; }

      /* -------------------------------------------------------------------- */
      /* To be more tolerant: Check again for invalid ISO8859 declarations */
      if(!strcmp(buf, "ISO8859-1"))  { isoinv = 1;  res = ENC_CS_ISO8859_1; }
      if(!strcmp(buf, "ISO8859-2"))  { isoinv = 1;  res = ENC_CS_ISO8859_2; }
      if(!strcmp(buf, "ISO8859-3"))  { isoinv = 1;  res = ENC_CS_ISO8859_3; }
      if(!strcmp(buf, "ISO8859-4"))  { isoinv = 1;  res = ENC_CS_ISO8859_4; }
      if(!strcmp(buf, "ISO8859-5"))  { isoinv = 1;  res = ENC_CS_ISO8859_5; }
      if(!strcmp(buf, "ISO8859-6"))  { isoinv = 1;  res = ENC_CS_ISO8859_6; }
      if(!strcmp(buf, "ISO8859-7"))  { isoinv = 1;  res = ENC_CS_ISO8859_7; }
      if(!strcmp(buf, "ISO8859-8"))  { isoinv = 1;  res = ENC_CS_ISO8859_8; }
      if(!strcmp(buf, "ISO8859-9"))  { isoinv = 1;  res = ENC_CS_ISO8859_9; }
      if(!strcmp(buf, "ISO8859-10"))  { isoinv = 1;  res = ENC_CS_ISO8859_10; }
      if(!strcmp(buf, "ISO-8859-11"))  { isoinv = 1;  res = ENC_CS_ISO8859_11; }
      if(!strcmp(buf, "ISO8859-11"))  { isoinv = 1;  res = ENC_CS_ISO8859_11; }
      if(!strcmp(buf, "ISO8859-13"))  { isoinv = 1;  res = ENC_CS_ISO8859_13; }
      if(!strcmp(buf, "ISO8859-14"))  { isoinv = 1;  res = ENC_CS_ISO8859_14; }
      if(!strcmp(buf, "ISO8859-15"))  { isoinv = 1;  res = ENC_CS_ISO8859_15; }
      if(!strcmp(buf, "ISO8859-16"))  { isoinv = 1;  res = ENC_CS_ISO8859_16; }
      /* To be more tolerant: Check again for invalid MACINTOSH declarations */
      if(!strcmp(buf, "MAC"))  { macinv = 1;  res = ENC_CS_MACINTOSH; }
      if(!strcmp(buf, "MACROMAN"))  { macinv = 1;  res = ENC_CS_MACINTOSH; }
      if(!strcmp(buf, "X-MAC-ROMAN"))  { macinv = 1;  res = ENC_CS_MACINTOSH; }
      /* To be more tolerant: Check again for alternative IBM declarations */
      if(!strcmp(buf, "CP437"))  { ibmalt = 1;  res = ENC_CS_IBM437; }
      if(!strcmp(buf, "CP850"))  { ibmalt = 1;  res = ENC_CS_IBM850; }
      if(!strcmp(buf, "CP858"))  { ibmalt = 1;  res = ENC_CS_IBM858; }
      /* To be more tolerant: Check again for invalid UTF declaration */
      if(!strcmp(buf, "UTF7"))
      {
         print_error("ENC: MIME: Invalid character set UTF7 accepted as UTF-7");
         res = ENC_CS_UTF_7;
      }
      if(!strcmp(buf, "UTF8"))
      {
         print_error("ENC: MIME: Invalid character set UTF8 accepted as UTF-8");
         res = ENC_CS_UTF_8;
      }
      /* -------------------------------------------------------------------- */

      /* Check whether character set is supported */
      if(ENC_CS_UNKNOWN == res)
      {
         l = strlen(not_supported) + len;
         p = (char*) posix_malloc(++l);
         if(NULL != p)
         {
            /* No => Character set not supported */
            strcpy(p, not_supported);
            strncat(p, buf, len);
            print_error(p);
            posix_free((void*) p);
         }
         /* Special check for ISO8859-x character sets that are not supported */
         buf[8] = 0;
         if(!strcmp(buf, "ISO-8859"))  { res = ENC_CS_ISO8859_X; }
         /* To be more tolerant: Check again for invalid ISO8859 declaration */
         if(!strcmp(buf, "ISO8859"))  { isoinv = 1;  res = ENC_CS_ISO8859_X; }
      }
      if(isoinv)
      {
         print_error("ENC: MIME: Invalid ISO8859 character set accepted");
      }
      else if(macinv)
      {
         print_error("ENC: MIME: Invalid Macintosh character set accepted");
      }
      else if(ibmalt)
      {
         print_error("ENC: MIME: "
                     "Alternative name for IBM character set accepted");
      }
   }

   return(res);
}


/* ========================================================================== */
/* Convert MIME quoted printable data to Unicode (UTF-8 NFC)
 *
 * \param[in] charset  Character set of data
 * \param[in] start    Pointer to start of data
 * \param[in] end      Pointer to end of data
 * \param[in] ec       Flag to switch between normal and encoded-word syntax
 *
 * \attention
 * \e end points to the location after the last character.
 *
 * If \e ec is true, the syntax is switched to that for encoded-words according
 * to RFC2047.
 *
 * According to RFC2045 the following rules are applied:
 * - A robust decoder may exclude invalid input data and continue
 *   => We do so by decoding all invalid characters as '?'.
 * - The hexadecimal representation of a character must use upper case letters
 *   A decoder is allowed to accept lower case letters too => We do so.
 * - If an invalid sequence follows a '=' character, it is allowed to accept
 *   this data as plain ASCII => We do so.
 * - Lines are not allowed to be longer than 76 characters, but it is allowed
 *   that a decoder accept lines of arbitrary length => We do so.
 *
 * \result
 * - Pointer to new memory block containing the decoded data
 * - NULL on error
 */

static const char*  enc_mime_decode_q(enum enc_mime_cs  charset,
                                      const char*  start, const char*  end,
                                      int  ec)
{
   const char*  res = NULL;
   size_t  len = 0;
   size_t  bi = 0;
   size_t  i;
   char*  tmp = NULL;
   char*  p;
   char  current;
   unsigned char  c;
   int  state = 0;
   char  nibble_high = 0;
   int  v;
   int  invalid;

   /* Decode data */
   for(i = 0; i < (size_t) (end - start); ++i)
   {
      /* Allocate more memory in exponentially increasing chunks */
      /* Attention: An invalid QP sequence stays undecoded and 3 octets long! */
      if(bi + (size_t) 4 >= len)  /* We need (3 + NUL) additional bytes */
      {
         if(!len)  { len = 64; }
         p = posix_realloc((void*) tmp, len *= (size_t) 2);
         if(NULL == p)
         {
            posix_free((void*) tmp);
            tmp = NULL;
            break;
         }
         else  { tmp = p; }
      }
      /* Parse current character */
      current = start[i];
      /* Only printable ASCII characters, SPACE, HT, LF and CR are allowed */
      invalid = 0;
      v = (int) current;
      if(!((9 <= v && 10 >= v) || 13 == v || (32 <= v && 126 >= v)))
      {
         invalid = 1;
      }
      /* SPACE and HT are not allowed in encoded-words */
      if(ec && !invalid && (9 == v || 32 == v))  { invalid = 1; }
      if(invalid)
      {
         /* Invalid character detected */
         print_error("ENC: MIME: Decoding invalid quoted printable data");
         current = '?';
      }
      /* Equal sign sequence decoder state machine */
      c = 0;
      if(!state && '=' == current)  { ++state; }
      switch(state)
      {
         case 1:
         {
            /* Skip equal sign */
            ++state;
            break;
         }
         case 2:
         {
            /* SPACE and HT at end of line must be ignored */
            if(!(!ec && ((char) 0x09 == current || (char) 0x20 == current)))
            {
               /* Store CR of soft line break or high nibble of encoded octet */
               nibble_high = current;
               ++state;
            }
            break;
         }
         case 3:
         {
            state = 0;
            /* Check for soft line break */
            if(!ec && (char) 0x0D == nibble_high && (char) 0x0A == current)
            {
               /* printf("Soft line break\n"); */
               break;
            }
            /* Decode octet */
            invalid = 0;
            v = enc_hex_decode_nibble(nibble_high);
            if(0 > v)  { invalid = 1; }
            else
            {
               c = (unsigned char) (v * 16);
               v = enc_hex_decode_nibble(current);
               if(0 > v)  { invalid = 1; }
               else  { c += (unsigned char) v; }
            }
            if(invalid)
            {
               /* Invalid encoding => Accept data as ASCII */
               print_error("ENC: MIME: Invalid quoted printable encoded data");
               tmp[bi++] = '=';
               tmp[bi++] = nibble_high;
               c = (unsigned char) current;
            }
            break;
         }
         default:
         {
            /* Decode underscore to space */
            if(ec && '_' == current)  { c = (unsigned char) 0x20; }
            else  { c = (unsigned char) current; }
            break;
         }
      }
      if(c)  { tmp[bi++] = (char) c; }
   }
   if(NULL == tmp)  { res = NULL; }
   else
   {
      /* Terminate decoded string */
      tmp[bi] = 0;
      /* Convert result to Unicode and normalize to NFC */
      res = enc_convert_to_utf8_nfc(charset, tmp);
      if(tmp != res)  { posix_free((void*) tmp); }
   }

   return(res);
}


/* ========================================================================== */
/* Convert MIME base64 data to Unicode (UTF-8 NFC)
 *
 * \param[in] charset  Character set of data
 * \param[in] start    Pointer to start of data
 * \param[in] end      Pointer to end of data
 * \param[in] np       Flag to request no padding
 *
 * \attention
 * \e end points to the location after the last character.
 *
 * If \e np is true, the syntax is switched to that for base64 inside of UTF-7
 * according to RFC2152. The resulting Unicode data is not normalized because
 * an UTF-7 shift sequence may start with a non-starter codepoint and represent
 * a "Defective combining character sequence" if processed without context.
 * The UTF-7 converter must normalize the data together with the context.
 *
 * \result
 * - Pointer to new memory block containing the decoded data
 * - NULL on error
 */

static const char*  enc_mime_decode_b(enum enc_mime_cs  charset,
                                      const char*  start, const char*  end,
                                      int  np)
{
   const char*  res = NULL;
   size_t  len = 0;
   size_t  bi = 0;
   size_t  i;  /* Index in input data */
   char*  tmp = NULL;
   const char*  tmp2 = NULL;
   char*  p;
   int  valid = 0;
   int  state = 0;
   unsigned long int  ii = 0;  /* Index in base64 alphabet */
   unsigned long int  input = 0;
   int  padding = 0;
   int  abort = 0;

   for(i = 0; i < (size_t) (end - start); ++i)
   {
      /* Allocate more memory in exponentially increasing chunks */
      if(bi + (size_t) 4 >= len)  /* We need (3 + NUL) additional bytes */
      {
         if(!len)  { len = 64; }
         p = posix_realloc((void*) tmp, len *= (size_t) 2);
         if(NULL == p)
         {
            print_error("ENC: MIME: Out of memory while decoding base64 data");
            abort = 1;
            break;
         }
         else  { tmp = p; }
      }
      valid = 0;
      /* Check for padding */
      if('=' == start[i])
      {
         ii = 0;
         if(0 == state)
         {
            /* Invalid padding character detected => Abort */
            print_error("ENC: MIME: Invalid padding in base64 encoded data");
            abort = 1;
            break;
         }
         else  { valid = 1; }
      }
      /* Check for valid encoding */
      else
      {
         if(' ' == start[i] || 0x09 == start[i]
            || 0x0A == start[i] || 0x0D == start[i])
         {
            /* Accept and ignore whitespace and line breaks */
            continue;
         }
         for(ii = 0; ii < 64UL; ++ii)
         {
            if(enc_base64[ii] == start[i])
            {
               valid = 1;
               break;
            }
         }
      }
      if(!valid)
      {
         /* Invalid character detected => Abort */
         print_error("ENC: MIME: Invalid character in base64 encoded data");
         abort = 1;
         break;
      }
      /* Parse current character */
      switch(state)
      {
         case 0:
         {
            /* Get first input character */
            input = ii << 18;
            padding = 0;
            ++state;
            break;
         }
         case 1:
         {
            /* Get second input character */
            if('=' == start[i])  { padding = 1; }
            else  { input |= ii << 12; }
            ++state;
            break;
         }
         case 2:
         {
            /* Get third input character */
            if('=' == start[i])  { padding = 1; }
            else  { input |= ii << 6; }
            ++state;
            break;
         }
         case 3:
         {
            /* Get fourth input character */
            if('=' == start[i])  { padding = 1; }
            else  { input |= ii; }
            /* Decode 3 output octet from 4 input characters */
            tmp[bi++] = (unsigned char) ((input >> 16) & 0xFFUL);
            tmp[bi++] = (unsigned char) ((input >> 8) & 0xFFUL);
            tmp[bi++] = (unsigned char) (input & 0xFFUL);
            state = 0;
            break;
         }
         default:
         {
            print_error("ENC: MIME: base64 state machine failed (Bug)");
            abort = 1;
            break;
         }
      }
      if(abort)  { break; }
      if(padding && 0 == state)  { break; }
   }
   if(abort)
   {
      posix_free((void*) tmp);
      tmp = NULL;
   }

   /* Check for unpadded format */
   if(!abort && np && state)
   {
      switch(state)
      {
         case 2:
         {
            /* One character left */
            if(input & 0x000FFFUL)  { abort = 1; }
            else
            {
               tmp[bi++] = (unsigned char) ((input >> 16) & 0xFFUL);
            }
            break;
         }
         case 3:
         {
            /* Two characters left */
            if(input & 0x00003FUL)  { abort = 1; }
            else
            {
               tmp[bi++] = (unsigned char) ((input >> 16) & 0xFFUL);
               tmp[bi++] = (unsigned char) ((input >> 8) & 0xFFUL);
            }
            break;
         }
         default:
         {
            abort = 1;
            break;
         }
      }
      if(abort)
      {
         print_error("ENC: MIME: Invalid end of unpadded base64 data");
      }
   }

   /* Terminate result string */
   if(NULL == tmp)  { res = NULL; }
   else
   {
      /* Terminate decoded string */
      tmp[bi] = 0;
      tmp2 = tmp;
      /* Convert UTF-16BE with separate function because data may contain NUL */
      if(ENC_CS_UTF_16BE == charset)
      {
         tmp2 = enc_uc_convert_utf16be_to_utf8(tmp, bi);
         if(NULL == tmp2)
         {
            /* Error => Return empty string */
            tmp[0] = 0;
            tmp2 = tmp;
         }
         else  { posix_free((void*) tmp); }
         /* Do not normalize to NFC yet (maybe context is required) */
         res = tmp2;
      }
      else
      {
         /* Convert result to Unicode and normalize to NFC */
         res = enc_convert_to_utf8_nfc(charset, tmp2);
      }
      if(tmp2 != res)  { posix_free((void*) tmp2); }
   }

   return(res);
}


/* ========================================================================== */
/* Convert from UTF-7 to UTF-8 NFC
 *
 * \param[in] s  String to convert
 *
 * \return
 * - Pointer to result (if not equal to \e s , a new memory block was allocated)
 * - NULL on error
 */

static const char*  enc_uc_convert_utf7_to_utf8_nfc(const char*  s)
{
   const char*  res = NULL;
   size_t  i = 0;
   int  shift = 0;  /* State of decoder */
   char*  buf;
   size_t  bi = 0;
   size_t  len = strlen(s);
   int  c;
   size_t  ii;
   const char*  start = NULL;
   const char*  end = NULL;
   const char*  utf8;
   size_t  utf8_len;
   int  eod = 0;  /* Flag indicating end of (input) data */

   /*
    * Allocate the result buffer twice the size as the original data.
    * This is always sufficient.
    *
    * The same buffer size may not be sufficient even if the base64 decoding
    * always reduce the size of the data. The reason is the UTF-16BE
    * representation of the UTF-7 data that we want to convert to UTF-8.
    * The UTF-8 representation of Unicode can be larger than UTF-16!
    * This is the case for the codepoints that require 3 bytes in UTF-8
    * and therefore an enlargement factor of 1.5 must be calculated.
    */
   buf = (char*) posix_malloc(++len * (size_t) 2);
   if(NULL != buf)
   {
      while(s[i])
      {
         /* Check current state */
         if(!shift)
         {
            /* Check for shift */
            if('+' == s[i])
            {
               if('-' == s[i + (size_t) 1])
               {
                  /* Literal '+' detected */
                  ++i;
                  buf[bi++] = '+';
               }
               else
               {
                  start = &s[i + (size_t) 1];
                  shift = 1;
               }
            }
            else
            {
               c = (int) s[i];
               /* Check for control characters (Rule 3) */
               if(0x09 == c || 0x0A == c || 0x0D == c|| 0x20 == c)
               {
                  buf[bi++] = s[i];
               }
               /* Check for character set D (Rule 1) */
               else if( (0x30 <= c && 0x39 >= c)
                   || (0x41 <= c && 0x5A >= c) || (0x61 <= c && 0x7A >= c)
                   || (0x27 <= c && 0x29 >= c) || (0x2C <= c && 0x2F >= c)
                   || 0x3A == c || 0x3F == c )
               {
                  buf[bi++] = s[i];
               }
               /* Check for character set O (Rule 1) */
               else if( (0x21 <= c && 0x26 >= c) || 0x2A == c
                   || (0x3B <= c && 0x3E >= c)
                   || 0x40 == c || 0x5B == c
                   || (0x5D <= c && 0x60 >= c) || (0x7B <= c && 0x7D >= c) )
               {
                  buf[bi++] = s[i];
               }
               else
               {
                  print_error("ENC: Invalid character in UTF-7 data ignored");
               }
            }
         }
         else
         {
            /* Check for character set B (Rule 2) */
            c = 0;
            for(ii = 0; ii < (size_t) 64; ++ii)
            {
               if(enc_base64[ii] == s[i])  { c = (int) s[i];  break; }
            }
            /* Treat end of data like a termination character */
            if(!s[i + (size_t) 1])  { eod = 1; }
            if(!c || eod)
            {
               /* Attention: eod must have lower precedence if both are true */
               if(!c)  { end = &s[i]; }
               else  { end = &s[i + (size_t) 1]; }
               /* Decode Unicode data as UTF-16BE */
               utf8 = enc_mime_decode_b(ENC_CS_UTF_16BE, start, end, 1);
               if(NULL == utf8)  { buf[bi++] = '?'; }
               else
               {
                  utf8_len = strlen(utf8);
                  for(ii = 0; ii < utf8_len; ++ii)  { buf[bi++] = utf8[ii]; }
                  posix_free((void*) utf8);
               }
               /* Preserve any terminating character except '-' */
               if(!eod && '-' != s[i])  { --i; }
               /* Terminate shift state */
               shift = 0;
            }
         }
         ++i;
      }
      /* Terminate result string */
      buf[bi] = 0;
      res = buf;
   }

   /* Check for error */
   if(shift)
   {
      print_error("ENC: UTF-7 converter terminated in shift state (bug)");
   }

   return(res);
}


/* ========================================================================== */
/* Check for leap year in terms of gregorian calendar
 *
 * \return
 * - 0 if \e year is not a leap year
 * - Nonzero if \e year is a leap year
 */

static int  enc_check_leap_year(unsigned int  year)
{
   if(!(year % 400U) || (!(year % 4U) && (year % 100U)))  { return(1); }
   else  { return(0); }
}


/* ========================================================================== */
/* Encode date and time to POSIX timestamp (seconds since epoche)
 *
 * \param[out] pts      Pointer to seconds since epoche (as defined by POSIX.1)
 * \param[in]  year     Years
 * \param[in]  month    Months
 * \param[in]  day      Days
 * \param[in]  hour     Hours
 * \param[in]  minute   Minutes
 * \param[in]  seconds  Seconds
 * \param[in]  zone     Timezone correction in minutes (Zero for UTC)
 *
 * \attention
 * This function accepts no timestamps before the epoche (the USENET has not
 * existed yet at that time).
 *
 * On error, zero is written to \e pts .
 */

static int  enc_encode_posix_timestamp(core_time_t*  pts, unsigned int  year,
                                       unsigned int  month, unsigned int  day,
                                       unsigned int  hour, unsigned int  minute,
                                       unsigned int  second, int  zone)
{
   static const unsigned int  dom[12] = { 31U, 29U, 31U, 30U, 31U, 30U,
                                          31U, 31U, 30U, 31U, 30U, 31U };
   int  res = -1;
   core_time_t  ts = 0;
   core_time_t  zone_seconds;
   unsigned int  i;

   /* Clamp year down to 1970 */
   if(1970U <= year)
   {
      /* Check for 'core_time_t' overflow (leave at least one year) */
      if(2104U < year)
      {
         print_error(
            "ENC: Warning: core_time_t overflow while decoding timestamp");
         year = 2104U;
      }
      for(i = 1970U; i < year; ++i)
      {
         ts += (core_time_t) 365 * (core_time_t) 86400;
         /* Add an additional day for leap years */
         if(enc_check_leap_year(i))  { ts += (core_time_t) 86400; }
      }
      for(i = 0; i < month - 1U; ++i)
      {
         ts += (core_time_t) dom[i] * (core_time_t) 86400;
         /* Subtract one day if current year is not a leap year */
         if(1U == i && !enc_check_leap_year(year))
         {
            ts -= (core_time_t) 86400;
         }
      }
      ts += (core_time_t) (day - 1U) * (core_time_t) 86400;
      ts += (core_time_t) hour * (core_time_t) 3600;
      ts += (core_time_t) minute * (core_time_t) 60;
      ts += (core_time_t) second;
      if(0 > zone)
      {
         zone_seconds = (core_time_t) -zone * (core_time_t) 60;
         ts += zone_seconds;
         res = 0;
      }
      else
      {
         zone_seconds = (core_time_t) zone * (core_time_t) 60;
         if(ts >= zone_seconds)
         {
            ts -= zone_seconds;
            res = 0;
         }
      }
   }

   /* Store result */
   if(res)
   {
      print_error("ENC: Encoding POSIX timestamp failed");
      *pts = 0;
   }
   else  { *pts = ts; }

#if 0
   if(!res)
   {
      /* For debugging (not thread safe) */
      printf("Seconds  : %lu\n", (long int) ts);
      struct tm*  t;
      t = gmtime((posix_time_t*) &ts);
      printf("Conv. UTC: %04d-%02d-%02d %02d:%02d:%02d\n",
             t->tm_year + 1900, t->tm_mon + 1, t->tm_mday,
             t->tm_hour, t->tm_min, t->tm_sec);
   }
#endif

   return(res);
}


/* ========================================================================== */
/* Check RFC5322 atom
 *
 * \param[in] s  Pointer to single character
 *
 * \return
 * - 0 if character pointed to by \e s is allowed
 * - Negative value on error
 */

static int  enc_check_atom(const char*  s)
{
   int  res = -1;
   int  c = (int) *s;

   /* Allow 'atext' */
   if(0x30 <= c && 0x39 >=c)  { res = 0; }
   else if(0x41 <= c && 0x5A >=c)  { res = 0; }
   else if(0x61 <= c && 0x7A >=c)  { res = 0; }
   else if((int) '!' == c)  { res = 0; }
   else if((int) '#' == c)  { res = 0; }
   else if((int) '$' == c)  { res = 0; }
   else if((int) '%' == c)  { res = 0; }
   else if((int) '&' == c)  { res = 0; }
   else if(0x27 == c)  { res = 0; }
   else if((int) '*' == c)  { res = 0; }
   else if((int) '+' == c)  { res = 0; }
   else if((int) '-' == c)  { res = 0; }
   else if((int) '/' == c)  { res = 0; }
   else if((int) '=' == c)  { res = 0; }
   else if((int) '?' == c)  { res = 0; }
   else if((int) '^' == c)  { res = 0; }
   else if((int) '_' == c)  { res = 0; }
   else if((int) '`' == c)  { res = 0; }
   else if((int) '{' == c)  { res = 0; }
   else if((int) '|' == c)  { res = 0; }
   else if((int) '}' == c)  { res = 0; }
   else if((int) '~' == c)  { res = 0; }

   return(res);
}


/* ========================================================================== */
/* Check RFC5322 dot-atom
 *
 * \param[in] s  Pointer to single character
 *
 * \return
 * - 0 if character pointed to by \e s is allowed
 * - Negative value on error
 */

static int  enc_check_dotatom(const char*  s)
{
   int  res;

   /* Allow dot and atext */
   if('.' == *s)  { res = 0; }
   else  { res = enc_check_atom(s); }

   return(res);
}


/* ========================================================================== */
/* Encode words in display-name
 *
 * Words containing only 7 bit characters are encoded to atom or quoted-string.
 * Words containing 8 bit characters are preserved unchanged for MIME encoder.
 *
 * \param[in,out] s  Pointer to data buffer
 */

static void  enc_encode_dispname(char*  s)
{
   size_t  i = 0;
   char  buf[ENC_HDR_BUFSIZE + (size_t) 1];
   size_t  bi = 0;
   char  word[ENC_HDR_BUFSIZE + (size_t) 1];
   char*  w;
   size_t  word_len;
   int  last_word = 0;
   size_t  ii;
   int  atom;
   size_t  start;
   int  error;
   char  cbuf[2];

   while(s[i])
   {
      /* Extract next word */
      ii = i;  while(' ' == s[ii])  { ++ii; }
      w = strchr(&s[ii], (int) ' ');
      if(NULL == w)
      {
         word_len = strlen(&s[i]);
         last_word = 1;
      }
      else  { word_len = (size_t) (w - &s[i]); }
      if(ENC_HDR_BUFSIZE < word_len)  { word[0] = 0; }
      else
      {
         memcpy((void*) word, (void*) &s[i], word_len);
         word[word_len] = 0;
      }
      i += word_len;
      if(!last_word)  { ++i; }  /* Skip SP delimiter */

      /* Check word */
      atom = 1;
      ii = 0;
      while(word[ii])
      {
         if(0x80U <= (unsigned int) (unsigned char) word[ii])
         {
            atom = 1;
            break;
         }
         if(enc_check_atom(&word[ii]))  { atom = 0; }
         ++ii;
      }

      /* SP delimiter between words */
      if(bi)
      {
         if(ENC_HDR_BUFSIZE <= bi)  { break; }  else  { buf[bi++] = ' '; }
      }

      /* Copy data to buffer */
      if(atom)
      {
         if(ENC_HDR_BUFSIZE - bi < word_len)  { break; }
         else
         {
            memcpy((void*) &buf[bi], (void*) word, word_len);
            bi += word_len;
         }
      }
      else
      {
         /* Create quoted-string */
         start = bi;
         error = 0;
         /* Leading DQUOTE delimiter */
         if(ENC_HDR_BUFSIZE <= bi)  { error = 1; }  else  { buf[bi++] = '"'; }
         /* Process data */
         for(ii = 0; ii < word_len; ++ii)
         {
            /* Skip control characters */
            cbuf[0] = word[ii];  cbuf[1] = 0;
            if(enc_ascii_check_printable(cbuf))  { continue; }
            /* Check remaining buffer size */
            if(ENC_HDR_BUFSIZE - bi < (size_t) 2)  { error = 1;  break; }
            /* Check whether quoted pair is required */
            if('"' == word[ii] || 0x5C == (int) word[ii])  { buf[bi++] = 0x5C; }
            buf[bi++] = word[ii];
         }
         /* Trailing DQUOTE delimiter */
         if(ENC_HDR_BUFSIZE <= bi)  { error = 1; }  else  { buf[bi++] = '"'; }
         if(error)  { bi = start; }
      }
      if(last_word)  { break; }
   }
   /* Terminate buffer */
   buf[bi] = 0;
   /* Copy data back to callers buffer */
   strncpy(s, buf, ++bi);

   return;
}


/* ========================================================================== */
/* Decode MIME parameter percent encoding
 *
 * \param[in] buf  Pointer to data buffer
 * \param[in] cs   IANA name of character set
 *
 * This function decodes the percent encoding defined for MIME parameters by
 * RFC2231. The IANA name of the character set for the resulting octet stream
 * must be specified by \e cs . The data is converted to Unicode in UTF-8
 * representation with NFC normalization.
 *
 * \return
 * - Pointer to decoded data (a new memory block was allocated)
 * - NULL on error
 */

static char*  enc_mime_decode_parameter(const char*  buf, const char*  cs)
{
   char*  res = NULL;
   char*  tmp = NULL;
   const char*  tmp2 = NULL;
   size_t  len;
   int  rv;
   enum enc_mime_cs  charset;

   if(NULL != buf)
   {
      /* Percent decoder */
      len = strlen(buf);
      tmp = (char*) posix_malloc(++len);
      if(NULL != tmp)
      {
         memcpy((void*) tmp, (void*) buf, len);
         if(enc_ascii_check_printable(tmp))
         {
            print_error("ENC: MIME: Nonprintable characters in parameter");
         }
         else
         {
            rv = enc_percent_decode(tmp);
            if(0 > rv)
            {
               print_error("ENC: MIME: Percent encoding failed for parameter");
            }
            else
            {
               charset = enc_mime_get_charset(cs, strlen(cs));
               tmp2 = enc_convert_to_utf8_nfc(charset, tmp);
               if(NULL == tmp2)
               {
                  print_error("ENC: MIME: Parameter charset not supported");
               }
               else
               {
                  len = strlen(tmp2);
                  res = (char*) posix_malloc(++len);
                  if(NULL != res)
                  {
                     memcpy((void*) res, (void*) tmp2, len);
                  }
                  if(tmp != tmp2)  { posix_free((void*) tmp2); }
               }
            }
         }
      }
   }
   posix_free((void*) tmp);

   return(res);
}


/* ========================================================================== */
/*! \brief Create a "name-addr" construct according to RFC5322
 *
 * This function is intended to create the "From" and "Reply-To" header fields.
 *
 * \param[in] data       Input data
 * \param[in] offset     Folding offset, e.g. \c sizeof("From: ")
 *
 * The input data must have the following format: \c name \c \<addr-spec\> .
 *
 * \attention
 * The \c addr-spec construct is not allowed to contain comments or quoted
 * strings. Both parts, \c name and \c \<addr-spec\> must fit on a single header
 * line of 998 characters. Note that \e offset adds to the length of \c name .
 *
 * \c name must be an Unicode identifier corresponding to \c addr-spec . If it
 * contains non-ASCII characters, it is converted to a valid \c display-name
 * token. The result will be folded according to RFC2047.
 *
 * On success the caller is responsible to free the memory allocated for the
 * result.
 *
 * \return
 * - Pointer to encoded data (a new memory block was allocated)
 * - NULL on error
 */

const char*  enc_create_name_addr(const char*  data, size_t  offset)
{
   const char*  res = NULL;
   size_t  len = 4;  /* The space after name, the angle brackets and NUL */
   size_t  i;
   size_t  counter = 0;
   int  error = 0;
   char  c;
   char*  buf;
   int  rv;
   char  name[(size_t) 2 * ENC_HDR_BUFSIZE + (size_t) 1];
   char  addr_spec[ENC_HDR_BUFSIZE + (size_t) 1];

   /* Extract name and addr-spec parts from input data */
   if((size_t) 2 * ENC_HDR_BUFSIZE < strlen(data))  { error = 1; }
   else
   {
      strcpy(name, data);
      addr_spec[0] = 0;
      if(!strlen(name))  { error = 1; }
      else
      {
         i = 0;
         while(name[i])
         {
            if('<' == name[i])
            {
               if(NULL == strchr(&name[i + (size_t) 1], (int) '<'))
               {
                  if(!i)  { name[0] = 0; }
                  else  { name[i - (size_t) 1] = 0; }
                  if(ENC_HDR_BUFSIZE < strlen(&name[i]))  { error = 1; }
                  else
                  {
                     strcpy(addr_spec, &name[++i]);
                     i = strlen(addr_spec) - (size_t) 1;
                     if('>' != addr_spec[i])  { addr_spec[0] = 0; }
                     else  { addr_spec[i] = 0; }
                  }
                  break;
               }
            }
            ++i;
         }
      }
   }

   /* Prepare display-name */
   if(!error)
   {
      enc_encode_dispname(name);
      len += strlen(name);
   }

   /* Check addr-spec */
   if(!error)
   {
      len += strlen(addr_spec);
      error = enc_ascii_check(addr_spec);
      if(!error)
      {
         i = 0;
         do
         {
            c = addr_spec[i];  if(!c)  { break; }
            if('@' != c && enc_check_dotatom(&c))
            {
               /* Invalid dot-atom found */
               error = 1;
            }
            /* Handle "@" separator */
            else if('@' == c)
            {
               ++counter;
               if(!i || !addr_spec[i + (size_t) 1])
               {
                  /* Invalid separator found (at beginning or end) */
                  error = 1;
               }
               /* Verify that dot-atoms don't have dots at beginning or end */
               if('.' == addr_spec[i - (size_t) 1]
                  || '.' == addr_spec[i + (size_t) 1])
               {
                  /* Invalid dot-atom found */
                  error = 1;
               }
            }
            /* Verify that dot-atoms don't have dots at beginning or end */
            if(!error && '.' == c)
            {
               if(!i || !addr_spec[i + (size_t) 1])
               {
                  /* Invalid dot-atom found */
                  error = 1;
               }
            }
            ++i;
         }
         while(!error);
      }
      /* Final checks */
      if(! (error || (size_t) 1 != counter || (size_t) 5 > strlen(addr_spec)) )
      {
         /* Allocate buffer */
         buf = (char*) posix_malloc(len);
         if(NULL != buf)
         {
            /* Copy name and add trailing space (if not empty string) */
            if(name[0])
            {
               strcpy(buf, name);
               strcat(buf, " <");
            }
            else  { strcpy(buf, "<"); }
            /* Copy addr-spec between angle brackets */
            strcat(buf, addr_spec);
            strcat(buf, ">");
            /* MIME encoding */
            rv = enc_mime_word_encode(&res, buf, offset);
            if(0 >= rv)  { posix_free((void*) buf); }
            /* For positive return value, 'buf' was assigned to 'res'! */
         }
      }
   }

   /* Check for error */
   if(error)  { print_error("ENC: Creating name-addr construct failed"); }

   /* For code review: Do not 'free()' memory pointed to by 'buf' here! */

   return(res);
}

/* ========================================================================== */
/*! \brief Decode number of lines
 *
 * \param[in] lines  Number of lines
 *
 * \e lines must be a RFC5536 compliant body of the (now obsolete) "Lines"
 * header field.
 *
 * \return
 * - Number of lines
 * - 0 on error
 */

unsigned long int  enc_lines_decode(const char*  lines)
{
   unsigned long int  res;

   if(1 != sscanf(lines, "%lu", &res))  { res = 0; }

   return(res);
}


/* ========================================================================== */
/*! \brief Convert number of lines to string
 *
 * \param[out] l     Pointer to result buffer (at least 11 characters large)
 * \param[in] l_raw  Number of lines
 *
 * \attention
 * The value of \e l_raw must be representable as decimal number with not more
 * than 10 digits. Otherwise the string \c "Error" is returned.
 */

void  enc_convert_lines_to_string(char*  l, unsigned long int  l_raw)
{
   int  rv;

   rv = posix_snprintf(l, 11, "%lu", l_raw);
   if(0 > rv || 11 <= rv)
   {
      l[0] = 'E';
      l[1] = 'r';
      l[2] = 'r';
      l[3] = 'o';
      l[4] = 'r';
      l[5] = 0;
   }
}


/* ========================================================================== */
/*! \brief Decode canonical timestamp to POSIX time (seconds since epoche)
 *
 * According to RFC5322 all military timezones should be
 * treated as UTC because there was an error in RFC822
 * => We do so and accept "Z" as valid because it means UTC
 *
 * \note
 * This function accepts no timestamps before the epoche (the USENET has not
 * existed yet at that time).
 *
 * \param[in] timestamp  RFC5536 compliant timestamp string
 *
 * \return
 * - Seconds since epoche (as defined by POSIX.1)
 * - 0 on error
 */

core_time_t  enc_timestamp_decode(const char*  timestamp)
{
   static const char*  months[12] = { "JAN", "FEB", "MAR", "APR", "MAY", "JUN",
                                     "JUL", "AUG", "SEP", "OCT", "NOV", "DEC" };
   static const unsigned int  dom[12] = { 31U, 29U, 31U, 30U, 31U, 30U,
                                          31U, 31U, 30U, 31U, 30U, 31U };
   core_time_t  res = 0;
   int  error = 1;
   const char*  p;
   const char*  q;
   int  rv;
   char  m[4];
   char  z[6];
   unsigned int  zh = 0;
   unsigned int  zm = 0;
   unsigned int  i;
   unsigned int  year;
   unsigned int  month = 13U;
   unsigned int  day;
   unsigned int  hour = 0;
   unsigned int  minute = 0;
   unsigned int  second = 0;
   int  zone = 1;  /* Correction in minutes */
   int  pos = 0;

#if 0
   /* For debugging */
   printf("------------------------------------------------------------\n");
   printf("Timestamp: %s\n", timestamp);
#endif

   /* Skip optional day-of-week */
   p = strchr(timestamp, (int) ',');
   if(NULL == p)  { p = timestamp; }  else  { ++p; }

   /* Extract date */
   rv = sscanf(p, "%u %3c %u%n", &day, m, &year, &pos);
   if(3 != rv)  { print_error("ENC: Invalid date in timestamp"); }
   else
   {
      /* Check for obsolete year format as defined by RFC5322 */
      if(1000U > year)
      {
         if(50U > year)  { year += 2000U; }  else  { year += 1900U; }
      }
      /* Decode month */
      m[0] = (char) toupper((int) m[0]);
      m[1] = (char) toupper((int) m[1]);
      m[2] = (char) toupper((int) m[2]);
      m[3] = 0;
      for(i = 0; i < 12; ++i)
      {
         if(!strcmp(months[i], m))  { month = i + 1U;  break; }
      }
      if(13U <= month)  { print_error("ENC: Invalid month in timestamp"); }
      else if(i < 12)
      {
         /* Check day */
         if(1U > day || dom[i] < day)  { month = 13U; }
         if(13U > month)
         {
            if(2U == month && 29U == day)
            {
               /* Check for leap year in terms of gregorian calendar */
               if(!enc_check_leap_year(year))  { month = 13U; }
            }
         }
         if(13U <= month)
         {
            print_error("ENC: Invalid day of month in timestamp");
         }
      }
   }

   /* Extract time if date was found */
   if(13U > month)
   {
      p += pos;
      rv = sscanf(p, "%u : %u%n", &hour, &minute, &pos);
      if(2 != rv)
      {
         print_error("ENC: Invalid time in timestamp");
      }
      else
      {
         p += pos;
         q = strchr(p, (int) ':');
         if(NULL != q)  { p = q; }
         rv = sscanf(p, ": %u%n", &second, &pos);
         if(1 == rv)  { p += pos; }
         rv = sscanf(p, "%5s", z);
         z[5] = 0;
         if(1 != rv)
         {
            print_error("ENC: Missing timezone in timestamp");
         }
         else
         {
            /* Check time (accept leap second according to RFC5322) */
            if(23U < hour || 59U < minute || 60U < second)
            {
               print_error("ENC: Invalid time in timestamp");
            }
            else
            {
               /* Decode timezone */
               if('+' == z[0] || '-' == z[0])
               {
                  for(i = 1; i < 5; ++i)
                  {
                     if(enc_ascii_check_digit(&z[i]))  { zone = 0;  break; }
                  }
                  if(zone)
                  {
                     zh = ((unsigned int) z[1] - 0x30) * 10U;
                     zh += ((unsigned int) z[2] - 0x30);
                     zm = ((unsigned int) z[3] - 0x30) * 10U;
                     zm += ((unsigned int) z[4] - 0x30);
                     if(59U < zm)  { zone = 0; }
                  }
                  if(!zone)
                  {
                     print_error("ENC: Invalid timezone in timestamp");
                  }
                  else
                  {
                     zone = (int) (zh * 60U + zm);
                     if('-' == z[0])  { zone *= -1; }
                  }
               }
               else
               {
                  /* Check for obsolete timezone format */
                  if(!strcmp("GMT", z))  { zone = 0; }
                  else if(!strcmp("UT", z))  { zone = 0; }
                  else if(!strcmp("EDT", z))  { zone = -4 * 60; }
                  else if(!strcmp("EST", z))  { zone = -5 * 60; }
                  else if(!strcmp("CDT", z))  { zone = -5 * 60; }
                  else if(!strcmp("CST", z))  { zone = -6 * 60; }
                  else if(!strcmp("MDT", z))  { zone = -6 * 60; }
                  else if(!strcmp("MST", z))  { zone = -7 * 60; }
                  else if(!strcmp("PDT", z))  { zone = -7 * 60; }
                  else if(!strcmp("PST", z))  { zone = -8 * 60; }
                  else if(!strcmp("Z", z))  { zone = 0; }
                  else
                  {
                     zone = 0;
                     print_error(
                        "ENC: Decode unknown timezone in timestamp as UTC");
                  }
               }
#if 0
               /* For debugging */
               printf("Decoded  : %04u-%02u-%02u %02u:%02u:%02u %+d minutes\n",
                      year, month, day, hour, minute, second, zone);
#endif
               /* Decoding successful */
               error = 0;
            }
         }
      }
   }

   /* Calculate seconds since epoche */
   if(!error)
   {
      enc_encode_posix_timestamp(&res, year, month, day, hour, minute, second,
                                 zone);
   }

   return(res);
}


/* ========================================================================== */
/*! \brief Convert POSIX timestamp to ISO8601 compliant local date and time
 *
 * \param[out] isodate  Buffer for date string (at least 20 characters)
 * \param[in]  pts      Seconds since epoche (as defined by POSIX.1)
 *
 * ISO8601 allows to omit the 'T' character between the date and time fields
 * if there is no risk of confusing a date and time of day representation.
 * This is the case here => We omit the 'T' for better human readability
 *
 * \return
 * - 0 on success
 * - Negative value on error
 */

int  enc_convert_posix_to_iso8601(char*  isodate, core_time_t  pts)
{
   int  res = -1;
   posix_time_t  ts;
   struct tm  t_data;
   struct tm*  t;

   /*
    * Check for potential 'time_t' overflow
    * Many historical 32 bit Unix systems use 'signed int' for 'time_t'.
    * We clamp the 'pts' to this lowest common denominator.
    */
   if((core_time_t) INT_MAX < pts)
   {
      /* Clamp time up to 2038-01-19T03:14:07Z if system uses 32 bit 'int' */
      print_error("ENC: Warning: time_t overflow while converting timestamp");
      ts = (core_time_t) INT_MAX;
   }
   else  { ts = (posix_time_t) pts; }

   /* Convert POSIX timestamp to ISO8601 date */
   /*! \todo
    * Calling operating system for date conversion should be replaced until the
    * year 2038 (when 32 bit signed \c time_t implementations will overflow).
    */
   t = ts_localtime(&ts, &t_data);
   if(NULL != t)
   {
      posix_snprintf(isodate, 20, "%04d-%02d-%02d %02d:%02d:%02d",
                     t->tm_year + 1900, t->tm_mon + 1, t->tm_mday,
                     t->tm_hour, t->tm_min, t->tm_sec);
      res = 0;
   }

   if(0 > res)  { print_error("ENC: Timestamp conversion failed"); }

   return(res);
}


/* ========================================================================== */
/*! \brief Get current UTC date in ISO8601 compliant format
 *
 * \param[out] isodate  Buffer for date string (at least 21 characters)
 *
 * The date is written to \e isodate in \c YYYY-MM-DDTHH-MM-SSZ format.
 *
 * \return
 * - 0 on success
 * - Negative value on error
 */

int  enc_get_iso8601_utc(char*  isodate)
{
   int  res = -1;
   posix_time_t  ts;
   struct tm  t_data;
   struct tm*  t;

   /*
    * Check for potential 'time_t' overflow
    * Many historical 32 bit Unix systems use 'signed int' for 'time_t'.
    * We clamp the 'pts' to this lowest common denominator.
    */
   posix_time(&ts);
   if((posix_time_t) 0 > ts)  { res = -1; }
   else
   {
      /* Convert POSIX timestamp to ISO8601 date */
      /*! \todo
       * Calling operating system for date conversion should be replaced until the
       * year 2038 (when 32 bit signed \c time_t implementations will overflow).
       */
      t = ts_gmtime(&ts, &t_data);
      if(NULL != t)
      {
         posix_snprintf(isodate, 21, "%04d-%02d-%02dT%02d:%02d:%02dZ",
                        t->tm_year + 1900, t->tm_mon + 1, t->tm_mday,
                        t->tm_hour, t->tm_min, t->tm_sec);
         res = 0;
      }
   }

   if(0 > res)  { print_error("ENC: ISO8601 date request failed"); }

   return(res);
}


/* ========================================================================== */
/*! \brief Convert ISO8601 compliant UTC date and time to POSIX timestamp
 *
 * \param[out] pts      Seconds since epoche (as defined by POSIX.1)
 * \param[in]  isodate  Buffer for date string (at least 20 characters)
 *
 * \attention
 * The parameter \e isodate must be in \c YYYY-MM-DDTHH-MM-SSZ format (UTC).
 *
 * \note
 * This function accepts no date input before the epoche.
 *
 * \return
 * - 0 on success
 * - Negative value on error
 */

int  enc_convert_iso8601_to_posix(core_time_t*  pts, const char*  isodate)
{
   int  res = -1;
   int  rv;
   unsigned int  year;
   unsigned int  month;
   unsigned int  mday;
   unsigned int  hour;
   unsigned int  minute;
   unsigned int  second;

   /* Split ISO8601 date */
   rv = sscanf(isodate, "%u-%u-%uT%u:%u:%uZ", &year, &month, &mday,
               &hour, &minute, &second);
   if(6 != rv)  { print_error("ENC: ISO8601 timestamp has invalid format"); }
   else
   {
      if(1970U <= year && 9999U >= year
         && 1U <= month && 12U >= month
         && 1U <= mday && 31U >= mday
         && 23U >= hour && 59U >= minute && 59U >= second)  { res = 0; }
   }

   /* Calculate seconds since epoche */
   if(!res)
   {
      res = enc_encode_posix_timestamp(pts, year, month, mday,
                                       hour, minute, second, 0);
   }

   return(res);
}


/* ========================================================================== */
/*! \brief Convert ISO8601 compliant date to canonical timestamp
 *
 * \param[out]  ts       Pointer to canonical timestamp as defined by RFC5322
 * \param[in]   isodate  ISO8601 date string (exactly 10 characters)
 *
 * \attention
 * The parameter \e isodate must be in \c YYYY-MM-DD format (only date, time is
 * not supported).
 *
 * \note
 * On success, the caller is responsible to free the memory allocated for the
 * result string.
 *
 * \return
 * - 0 on success
 * - Negative value on error
 */

int  enc_convert_iso8601_to_timestamp(const char**  ts, const char*  isodate)
{
   static const char*  months[12] = { "Jan", "Feb", "Mar", "Apr", "May", "Jun",
                                     "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" };
   int  res = -1;
   int  rv;
   unsigned int  year;
   unsigned int  month;
   unsigned int  mday;
   char*  buf = NULL;
   size_t  len = 50;

   /* Split ISO8601 date */
   rv = sscanf(isodate, "%u-%u-%u", &year, &month, &mday);
   if(3 != rv)  { print_error("ENC: ISO8601 timestamp has invalid format"); }
   else
   {
      if(1900U <= year && 9999U >= year
         && 1U <= month && 12U >= month
         && 1U <= mday && 31U >= mday)  { res = 0; }
   }
   if(!res)
   {
      /* Allocate buffer for result */
      buf = (char*) posix_malloc(len);
      if(NULL == buf)  { res = -1; }
      else
      {
         posix_snprintf(buf, 50, "%u %s %04u %02d:%02d:%02d -0000",
                        mday, months[--month], year, 0, 0, 0);
         *ts = (const char*) buf;
      }
   }

   return(res);
}


/* ========================================================================== */
/*! \brief Convert article number from numerical format to ASCII
 *
 * \param[out] result  Pointer to result string buffer (Size: 17 bytes)
 * \param[out] len     Pointer to length of result string (Maximum value: 16)
 * \param[in]  wm      Article number (watermark) to convert
 *
 * RFC3977 allows max. 16 digits.
 *
 * \note
 * The output is locale independent.
 *
 * \return
 * - 0 on success
 * - Negative value on error (\e result and \e len are not valid)
 */

int  enc_convert_anum_to_ascii(char  result[17], size_t*  len, core_anum_t  wm)
{
   int  res = -1;

   /* C90 compilers are not required to support more than 32 bit data types */
   if (CORE_ANUM_T_MAX > ULONG_MAX)
   {
      print_error("ENC: Value of CORE_ANUM_T_MAX is too large");
   }
   else
   {
      *len = posix_snprintf(result, 17, "%lu", (unsigned long int) wm);
      if(*len > 0 && *len <= 16 )  { res = 0; }
   }

   return(res);
}


/* ========================================================================== */
/*! \brief Convert number from ASCII to numerical format
 *
 * \param[out] result  Pointer to result
 * \param[in]  wm      Article number (watermark) string to convert
 * \param[in]  len     Length of string \e wm
 *
 * RFC3977 allows max. 16 digits.
 * This function correctly processes leading zeros and do not use standard
 * library functions with locale dependent behaviour.
 *
 * \return
 * - 0 on success
 * - Negative value on error
 */

int  enc_convert_ascii_to_anum(core_anum_t*  result, const char  wm[16],
                               int  len)
{
   int  res = -1;
   unsigned char  c;
   nntp_anum_t  pot = 1;  /* 10^0 (0th power of 10) */
   nntp_anum_t  d;
   nntp_anum_t  v = 0;

   /* Check length */
   if(0 < len && 16 >= len)
   {
      /* Process every digit as a power of ten */
      while(len)
      {
         /* Get character and check whether it is a digit */
         c = (unsigned char) wm[len-- - 1];
         if(enc_ascii_check_digit((char*) &c))  { len = -1;  break; }
         /* ASCII decode it to numerical digit */
         d = (nntp_anum_t) (unsigned char) (c - 0x30U);
         /* Calculate value of digit */
         d *= pot;
         /* Avoid overflow */
         if(NNTP_ANUM_T_MAX - v < d)  { break; }
         /* Add value of digit to result */
         v += d;
         /* Calculate next power of ten */
         pot *= 10;
      }
      /* Check whether processing was successful */
      if(!len)
      {
         *result = v;
         res = 0;
      }
   }

   if(0 > res)  { print_error("ENC: Article number conversion failed"); }

   return(res);
}


/* ========================================================================== */
/*! \brief Convert octet to hexadecimal (ASCII) format
 *
 * \param[out] result  Pointer to result
 * \param[in]  octet   Octet to convert
 *
 * Exactly 3 bytes are written to the buffer pointed to by \e result .
 * If \e octet is smaller than 16, a leading zero is created.
 * On error, the result "XX" is generated.
 * The \e result is always a zero terminated string.
 *
 * \return
 * - 0 on success
 * - Negative value on error
 */


int  enc_convert_octet_to_hex(char*  result, unsigned int  octet)
{
   int  res = -1;

   if(255U >= octet)
   {
      if(2 == posix_snprintf(result, 3, "%02X", octet))  { res = 0; }
   }

   /* Check for error */
   if(res)  { strcpy(result, "XX"); }

   return(res);
}


/* ========================================================================== */
/*! \brief Encode or decode data with ROT13 algorithm
 *
 * \param[in] data  Pointer to buffer with Data to encode/decode
 *
 * Any character that is not a latin ASCII character in the ranges A..Z and
 * a..z will stay unchanged.
 *
 * No memory is allocated. The operation is executed in the buffer pointed to
 * by \e data .
 */

void  enc_rot13(char*  data)
{
   size_t  i = 0;
   int  c;
   int  modified = 0;

   while(data[i])
   {
      c = (int) data[i];
      /* Check for capital letter */
      if(65 <= c && 90 >= c)
      {
         c += 13;
         if(90 < c)  { c = 65 - 1 + (c - 90); }
         modified = 1;
      }
      /* Check for small letter */
      else if(97 <= c && 122 >= c)
      {
         c += 13;
         if(122 < c)  { c = 97 - 1 + (c - 122); }
         modified = 1;
      }
      /* Change character */
      if(modified)  { data[i] = (char) c; }
      /* Process next character */
      modified = 0;
      ++i;
   }
}


/* ========================================================================== */
/*! \brief Encode binary data to base64
 *
 * \param[out] enc   Pointer to result (zero terminated string)
 * \param[in]  data  Data to encode
 * \param[in]  len   Data length
 *
 * If \e len is zero, \e data is not dereferenced and the result will be an
 * empty string.
 *
 * On error, nothing is written to \e enc .
 *
 * On success a pointer to the result buffer will be written to \e enc .
 * The caller is responsible to free the memory allocated for this buffer.
 *
 * \return
 * - 0 on success
 * - Negative value on error
 */

int  enc_encode_base64(const char**  enc, const char*  data, size_t  len)
{
   int  res = -1;
   size_t  enc_len = (size_t) 4 * len / (size_t) 3;
   char*  buf = NULL;
   size_t  i = 0;
   size_t  enc_i = 0;
   unsigned long int  word;
   unsigned char  b64[4];

   enc_len += (size_t) 5;  /* Last quartet and termination */
   buf = (char*) posix_malloc(enc_len);
   if(NULL != buf)
   {
      if(len)
      {
         while((size_t) 3 <= len)
         {
            /* Compose 24 bit input word */
            word = (unsigned long int) (unsigned char) data[i++] << 16;
            word |= (unsigned long int) (unsigned char) data[i++] << 8;
            word |= (unsigned long int) (unsigned char) data[i++];
            len -= (size_t) 3;
            /* Encode */
            b64[3] = (unsigned char) (word & 0x00003FUL);
            b64[2] = (unsigned char) ((word & 0x000FC0UL) >> 6);
            b64[1] = (unsigned char) ((word & 0x03F000UL) >> 12);
            b64[0] = (unsigned char) ((word & 0xFC0000UL) >> 18);
            buf[enc_i++] = enc_base64[b64[0]];
            buf[enc_i++] = enc_base64[b64[1]];
            buf[enc_i++] = enc_base64[b64[2]];
            buf[enc_i++] = enc_base64[b64[3]];
         }
         if(len)
         {
            /* End of data and padding */
            word = (unsigned long int) (unsigned char) data[i++] << 16;
            if((size_t) 1 < len)
            {
               word |= (unsigned long int) (unsigned char) data[i++] << 8;
            }
            /* Encode */
            b64[3] = (unsigned char) (word & 0x00003FUL);
            b64[2] = (unsigned char) ((word & 0x000FC0UL) >> 6);
            b64[1] = (unsigned char) ((word & 0x03F000UL) >> 12);
            b64[0] = (unsigned char) ((word & 0xFC0000UL) >> 18);
            buf[enc_i++] = enc_base64[b64[0]];
            buf[enc_i++] = enc_base64[b64[1]];
            if((size_t) 1 < len)  { buf[enc_i++] = enc_base64[b64[2]]; }
            else  { buf[enc_i++] = '='; }
            buf[enc_i++] = '=';
         }
      }
      buf[enc_i] = 0;
      *enc = buf;
      res = 0;
   }

   /* Check for error */
   if(res)  { posix_free((void*) buf); }

   return(res);
}


/* ========================================================================== */
/*! \brief Extract addr-spec token from RFC5322 mailbox
 *
 * \param[in] mailbox  RFC5322 mailbox
 *
 * \attention
 * The checks are more restrictive than the formal specification of RFC5322.
 * White space is not allowed inside the \c addr-spec token!
 *
 * \note
 * It is tolerated that \e mailbox contains an invalid \c name-addr token
 * because it is ignored anyway.
 *
 * On success a pointer to the result buffer is returned.
 * The caller is responsible to free the memory allocated for this buffer.
 *
 * \return
 * - Pointer to new memory block containing the \c addr-spec token
 * - NULL on error
 */

const char*  enc_extract_addr_spec(const char*  mailbox)
{
   char*  res = NULL;
   unsigned int  state;
   const char*  s;
   const char*  e;
   size_t  len;
   size_t  i, ii;

   if(NULL != mailbox)
   {
      len = strlen(mailbox);
      /* A valid addr-spec is at least 3 characters long */
      if((size_t) 3 <= len)
      {
         /* Default to assumption that whole mailbox is an 'addr-spec' token */
         s = mailbox;
         e = &mailbox[len];
         /*
          * To tolerate arbitrary garbage for the 'name-addr' token search
          * backward from the end for an 'angle-addr' token.
          */
         state = 0;
         for(i = len; i; --i)
         {
            ii = i - (size_t) 1;
            if(!state && '>' == mailbox[ii])
            {
               e = &mailbox[ii];
               ++state;
               continue;
            }
            if(1U == state && '@' == mailbox[ii])  { ++state;  continue; }
            if(2U == state && '<' == mailbox[ii])
            {
               /* 'angle-addr' token found at end of mailbox */
               ++state;
               s = &mailbox[ii];
               ++s;
               break;
            }
         }
         if((!state || 3U <= state) && e > s + 2)
         {
            /* Allocate new memory block and copy 'addr-spec' to it */
            len = (size_t) (e - s);
            res = (char*) posix_malloc(len + (size_t) 1);
            if(NULL != res)
            {
               memcpy((void*) res, (void*) s, len);  res[len] = 0;
               /* Check whether result is a valid 'addr-spec' token */
               i = 0;
               state = 0;
               while(res[i] && '@' != res[i])
               {
                  if(enc_check_dotatom(&res[i]))  { state = 1;  break; }
                  ++i;
               }
               if(!state)
               {
                  if(!i || '@' != res[i])  { state = 1; }
                  /* Skip '@' and verify that there is more data */
                  else  { if(!res[++i])  { state = 1; } }
               }
               if(!state)
               {
                  while(res[i])
                  {
                     /*
                      * Relaxed check for 'domain-literal'
                      * Check for printable ASCII only
                      */
                     if('[' == res[i])
                     {
                        if(enc_ascii_check_printable(&res[i]))  { state = 1; }
                        break;
                     }
                     /* Check 'dot-atom' */
                     if(enc_check_dotatom(&res[i]))
                     {
                        state = 1;
                        break;
                     }
                     ++i;
                  }
               }
               if(state)
               {
                  /* Invalid address format */
                  posix_free((void*) res);
                  res = NULL;
               }
            }
         }
      }
   }

   /* Check for error */
   if(NULL == res)  { print_error("ENC: Invalid e-mail address"); }

   return(res);
}


/* ========================================================================== */
/*! \brief Verify ASCII encoding
 *
 * \param[in] s  String to verify
 *
 * \return
 * - 0 on success
 * - Negative value on error
 */

int  enc_ascii_check(const char*  s)
{
   int  res = 0;
   size_t  i = 0;
   int  c;

   /* Assignment in the truth expression is intended */
   while((c = (int) s[i++]))
   {
      if(!(0 <= c && 127 >= c))  { res = -1; }
   }

   return(res);
}


/* ========================================================================== */
/*! \brief Check for ASCII alphabetic characters
 *
 * \param[in] s  Pointer to single character
 *
 * Locale independent check based on ASCII.
 *
 * \return
 * - 0 if \e s is an alphabetic character
 * - Negative value if \e s is not an alphabetic character
 */

int  enc_ascii_check_alpha(const char*  s)
{
   int  res = 0;
   int  c = (int) *s;

   if(!(65 <= c && 90 >= c) && !(97 <= c && 122 >= c))  { res = -1; }

   return(res);
}


/* ========================================================================== */
/*! \brief Check for ASCII digit characters
 *
 * \param[in] s  Pointer to single character
 *
 * Locale independent check based on ASCII.
 *
 * \return
 * - 0 if \e s is a digit character
 * - Negative value if \e s is not a digit character
 */

int  enc_ascii_check_digit(const char*  s)
{
   int  res = 0;
   int  c = (int) *s;

   if(!(48 <= c && 57 >= c))  { res = -1; }

   return(res);
}


/* ========================================================================== */
/*! \brief Check for printable ASCII characters
 *
 * \param[in] s  String to check
 *
 * HT (9) and SPACE (32, 0x20) inside \e s are treated as "printable" to make
 * this function suitable to check header field bodies according to RFC5322.
 *
 * \note
 * The function \ref enc_ascii_convert_to_printable() can be used on error.
 *
 * \return
 * - 0 on success
 * - Negative value on error
 */

int  enc_ascii_check_printable(const char*  s)
{
   int  res = 0;
   size_t  i = 0;
   int  c;

   /* Assignment in the truth expression is intended */
   while((c = (int) s[i++]))
   {
      if(!(9 == c || (32 <= c && 126 >= c)))  { res = -1; }
   }

   return(res);
}


/* ========================================================================== */
/*! \brief Convert to printable ASCII format
 *
 * \param[in] s  String to convert
 *
 * This function should be used to repair a string in-place after the function
 * \ref enc_ascii_check_printable() have reported an error.
 *
 * Every invalid byte is replaced with '?'.
 */

void  enc_ascii_convert_to_printable(char*  s)
{
   size_t  i = 0;
   int  c;

   while(s[i])
   {
      c = (int) s[i];
      if(!(9 == c || (32 <= c && 126 >= c)))  { s[i] = '?'; }
      ++i;
   }
}


/* ========================================================================== */
/*! \brief Convert body of distribution header field
 *
 * \param[in] s  String with unfolded body to convert
 *
 * This function process \e s in-place. The result will always be shorter or
 * same length as the original data.
 *
 * Every element of \c dist-list that contains invalid characters is removed.
 */

void  enc_ascii_convert_distribution(char*  s)
{
   size_t  i;
   size_t  len;
   int  c;
   size_t  start = 0;
   int  error = 0;
   char*  p;

   /* Remove whitespace */
   i = 0;
   while(s[i])
   {
      c = (int) s[i];
      if(9 == c || 32 == c)
      {
         len = strlen(&s[i + (size_t) 1]);
         /* Move including NUL termination */
         memmove((void*) &s[i], (void*) &s[i + (size_t) 1], ++len);
      }
      else  { ++i; }
   }

   /* Check content */
   i = 0;
   while(s[i])
   {
      /* Check for alphanumeric characters */
      if(enc_ascii_check_alpha(&s[i]) && enc_ascii_check_digit(&s[i]))
      {
         /* No => Check first character of 'dist-name' token */
         if(!start && !i)  { error = 1; }
         else if(start && start + (size_t) 1 == i)  { error = 1; }
         else
         {
            /* Not first => Check other characters of 'dist-name' token */
            if('+' != s[i] && '-' != s[i] && '_' != s[i] && ',' != s[i])
            {
               error = 1;
            }
         }
         /* Check for separator between entries */
         if(!error && ',' == s[i])
         {
            start = i;
            if(!s[i + (size_t) 1])  { error = 1; }
         }
      }
      /* Check for error */
      if(error)
      {
         print_error("ENC: Invalid entry in distribution list removed");
         p = strchr(&s[i + (size_t) 1], (int) ',');
         i = start;
         if(NULL == p)  { s[i] = 0; }
         else
         {
            /* Remove invalid entry */
            if(!start)  { p += 1; }  /* No separator before first entry */
            len = strlen(p);
            /* Move including NUL termination */
            memmove((void*) &s[start], (void*) p, ++len);
         }
         error = 0;
      }
      else  { ++i; }
   }
}


/* ========================================================================== */
/*! \brief Verify UTF-8 encoding
 *
 * \param[in] s  String to verify
 *
 * \attention
 * Read chapter 10 of RFC3629 for UTF-8 security considerations.
 *
 * According to RFC3629 the following rules are applied:
 * - Character code points beyond 0x10FFFF are invalid => We reject them.
 * - Only the shortest possible code sequence is allowed => We verify this.
 * - Surrogate character code points are invalid for UTF-8 => We reject them.
 *
 * \return
 * - 0 on success
 * - Negative value on error
 */

int  enc_uc_check_utf8(const char*  s)
{
   int  res = 0;
   size_t  i = 0;
   int  c;
   int  multibyte = 0;
   size_t  len = 0;
   size_t  remaining = 0;
   unsigned long int  mbc = 0;

   /* Assignment in truth expression is intended */
   while((c = (int) s[i++]))
   {
      /* Verify singlebyte character */
      if(!multibyte)
      {
         if(!(0 <= c && 127 >= c))  { multibyte = 1; }
      }
      /* Verify multibyte character */
      if(multibyte)
      {
         if(!remaining)
         {
            if((c & 0xE0) == 0xC0)  { len = 2; }
            else if((c & 0xF0) == 0xE0)  { len = 3; }
            else if((c & 0xF8) == 0xF0)  { len = 4; }
            else
            {
               print_error("ENC: Invalid start of code sequence in UTF-8 data");
               res = -1;
               break;
            }
            switch(len)
            {
               case 2:  mbc |= (unsigned long int) (c & 0x1F) << 6;  break;
               case 3:  mbc |= (unsigned long int) (c & 0x0F) << 12;  break;
               case 4:  mbc |= (unsigned long int) (c & 0x07) << 18;  break;
            }
            remaining = len - (size_t) 1;
         }
         else
         {
            if((c & 0xC0) != 0x80)
            {
               print_error(
                  "ENC: Invalid continuation character in UTF-8 sequence");
               res = -1;
               break;
            }
            else
            {
               --remaining;
               mbc |= (unsigned long int) (c & 0x3F) << remaining * (size_t) 6;
            }
            if(!remaining)
            {
               /* Verify character code */
               switch(len)
               {
                  case 2:
                  {
                     if(0x000080UL > mbc)
                     {
                        print_error("ENC: Invalid UTF-8 2-byte code sequence");
                        res = -1;
                     }
                     break;
                  }
                  case 3:
                  {
                     if(0x000800UL > mbc
                        || (0x00D800UL <= mbc && 0x00DFFFUL >= mbc))
                     {
                        print_error("ENC: Invalid UTF-8 3-byte code sequence");
                        res = -1;
                     }
                     break;
                  }
                  case 4:
                  {
                     if(0x010000UL > mbc || 0x10FFFFUL < mbc)
                     {
                        print_error("ENC: Invalid UTF-8 4-byte code sequence");
                        res = -1;
                     }
                     break;
                  }
                  default:
                  {
                     print_error("ENC: Bug in UTF-8 verify state machine");
                     res = -1;
                     break;
                  }
               }
               if(res)  { break; }
               /* Code sequence completely checked => Reset state machine */
               multibyte = 0;
               remaining = 0;
               mbc = 0;
            }
         }
      }
   }
   /* Check for incomplete multibyte code sequence at end of string */
   if(multibyte)
   {
      print_error("ENC: Invalid code sequence at end of UTF-8 data");
      res = -1;
   }

   return(res);
}


/* ========================================================================== */
/*! \brief Create wildmat pattern array
 *
 * \param[out] obj  Pointer to wildmat pattern array
 * \param[in]  wm   RFC3977 compliant wildmat
 *
 * This function splits a RFC3977 compliant \c wildmat into its elements of
 * type \c wildmat-pattern . Every \c wildmat-pattern is converted to a POSIX
 * extended regular expression and stored together with a negation flag (that
 * is set if the \c wildmat-pattern was preceded by an exclamation mark) in
 * the array \e obj .
 *
 * On success the caller is responsible to free the memoy allocated for the
 * resulting array with the function \e enc_destory_wildmat() .
 *
 * \attention
 * If the wildmat \e wm contains Unicode data, it must be normalized to NFC by
 * the caller.
 *
 * \return
 * - Number of patterns in the object on success
 * - Negative value on error (\c NULL was written to \e obj)
 */

int  enc_create_wildmat(struct enc_wm_pattern**  obj, const char*  wm)
{
   int  res = 0;
   size_t  len;
   size_t  i = 0;
   char*  buf = NULL;
   size_t  bi = 0;
   int  error = 0;
   int  negate = 0;
   int  store = 0;
   int  eod = 0;
   struct enc_wm_pattern*  p;
   size_t  obj_len = 0;

   *obj = NULL;

   /* Wildmat must have valid UTF-8 encoding */
   if(!enc_uc_check_utf8(wm))
   {
      /* Check for invalid characters (backslash and brackets) */
      if(NULL == strpbrk(wm, "\x5C[]"))
      {
         /* Extract wildmat-pattern elements */
         do
         {
            store = 0;
            negate = 0;
            /* Allocate ERE buffer for next pattern */
            len = strlen(&wm[i]);
            /* Required buffer size (see below): Triple + 2 + NUL */
            buf = (char*) posix_malloc(len * (size_t) 3 + (size_t) 3);
            if(NULL == buf)  { break; }
            else
            {
               bi = 0;
               buf[bi++] = '^';
               while(!store)
               {
                  /* Check for EOD */
                  if(!wm[i])
                  {
                     if((size_t) 1 < bi)  { store = 1; }
                     eod = 1;
                     break;
                  }
                  /* Check for (remaining) special character */
                  if(NULL != strchr(".()*+?{|^$", (int) wm[i]))
                  {
                     switch((int) wm[i])
                     {
                        /* Match arbitrary single UTF-8 codepoint (not octet) */
                        case (int) ')':
                        {
                           /* Replace with "[)]" (*3) */
                           buf[bi++] = '[';
                           buf[bi++] = ')';
                           buf[bi++] = ']';
                           break;
                        }
                        case (int) '*':
                        {
                           /* Replace with ".*" (*2) */
                           buf[bi++] = '.';
                           buf[bi++] = '*';
                           break;
                        }
                        case (int) '?':
                        {
                           /* Replace with dot (*1) */
                           buf[bi++] = '.';
                           break;
                        }
                        default:
                        {
                           /* Escape special character with backslash (*2) */
                           buf[bi++] = 0x5C;
                           buf[bi++] = wm[i];
                           break;
                        }
                     }
                  }
                  else
                  {
                     switch((int) wm[i])
                     {
                        case 0x09:
                        case 0x20:
                        {
                           /* Ignore whitespace */
                           break;
                        }
                        case (int) '!':
                        {
                           negate = 1;
                           break;
                        }
                        case (int) ',':
                        {
                           store = 1;
                           break;
                        }
                        default:
                        {
                           /* Ordinary character */
                           buf[bi++] = wm[i];
                           break;
                        }
                     }
                  }
                  ++i;
               }
               /* Store element into object */
               if(!store)  { posix_free((void*) buf); }
               else
               {
                  if(INT_MAX == res)  { error = 1; }
                  else
                  {
                     buf[bi++] = '$';
                     buf[bi] = 0;
                     /* printf("Pattern converted to ERE: %s\n", buf); */
                     obj_len += sizeof(struct enc_wm_pattern);
                     p = (struct enc_wm_pattern*) posix_realloc(*obj, obj_len);
                     if(NULL == p)  { error = 1; }
                     else
                     {
                        *obj = p;
                        (*obj)[res].negate = negate;
                        (*obj)[res].ere = buf;
                        ++res;
                     }
                  }
                  if(error)
                  {
                     posix_free((void*) buf);
                     break;
                  }
               }
            }
         }
         while(!eod);
      }
   }

   /* Check for error */
   if(error || !eod || 0 >= res)
   {
      print_error("ENC: Failed to convert RFC3977 wildmat");
      enc_destroy_wildmat(obj, res);
      res = -1;
   }

   return(res);
}


/* ========================================================================== */
/*! \brief Destroy wildmat pattern array
 *
 * \param[in,out] obj  Pointer to wildmat pattern array
 * \param[in]     num  Number of elements in array
 *
 * \c NULL is written to the location pointed to by \e obj after releasing the
 * memory allocated for the array.
 */

void  enc_destroy_wildmat(struct enc_wm_pattern**  obj, int  num)
{
   int  i;

   if(NULL != obj && NULL != *obj)
   {
      for(i = 0; i < num; ++i)
      {
         posix_free((void*) (*obj)[i].ere);
      }
      posix_free((void*) *obj);
      *obj = NULL;
   }
}


/* ========================================================================== */
/*! \brief Convert from canonical (RFC822) to local (POSIX) form
 *
 * \param[in] s  String to convert
 *
 * According to RFC822 and RFC2049 this function accepts plain text article
 * content in canonical form and convert the CRLF line breaks to local (POSIX,
 * single LF) form. Single CR and LF characters are preserved.
 *
 * On success the caller is responsible to free the allocated memory.
 *
 * \return
 * - Pointer to decoded data (a new memory block was allocated)
 * - NULL on error
 */

const char*  enc_convert_canonical_to_posix(const char*  s)
{
   const char*  res = NULL;
   size_t  i = 0;
   char*  buf = NULL;
   size_t  len = 0;
   size_t  bi = 0;
   char*  p;
   size_t  escr = 0;
   size_t  eslf = 0;

   if(NULL != s)
   {
      /* Check for empty string and accept it */
      if(!s[0])
      {
         p = (char*) posix_malloc((size_t) 1);
         if(NULL != p)  { p[0] = 0;  res = p; }
      }
      else
      {
         while(s[i])
         {
            /* At least 1 octet must stay available for NUL termination */
            if(bi + (size_t) 1 >= len)
            {
               /* Allocate more memory in exponentially increasing chunks */
               if(!len)  { len = 64; }
               p = (char*) posix_realloc((void*) buf, len *= (size_t) 2);
               if(NULL == p)  { posix_free((void*) buf);  buf = NULL;  break; }
               else  { buf = p; }
            }
            /* Check for end of line (CRLF) */
            if(bi && i && 0x0A == (int) s[i] && 0x0D == (int) s[i - (size_t) 1])
            {
               /* Yes => Replace the CR with LF and don't increment position */
               buf[bi - (size_t) 1] = 0x0A;
            }
            else if(i && 0x0A != (int) s[i] && 0x0D == (int) s[i - (size_t) 1])
            {
               /* Single CR character (not part of a CRLF sequence) detected */
               ++escr;
               buf[bi++] = s[i];
            }
            else if(0x0A == (int) s[i])
            {
               /* Single LF character (not part of a CRLF sequence) detected */
               ++eslf;
               buf[bi++] = s[i];
            }
            else { buf[bi++] = s[i]; }
            ++i;
         }
         if(NULL != buf)
         {
            buf[bi] = 0;
            res = buf;
            /* Print stored errors */
            if(escr)
            {
               /* Print error message only once */
               print_error("ENC: Invalid CR control character(s) detected"
                           " while decoding canonical format");
            }
            if(eslf)
            {
               /* Print error message only once */
               print_error("ENC: Invalid LF control character(s) detected"
                           " while decoding canonical format");
            }
         }
      }
   }

   return(res);
}


/* ========================================================================== */
/*! \brief Convert from local (POSIX) to canonical (RFC822) form
 *
 * \param[in] s  String to convert
 *
 * According to RFC822 and RFC2049 this function accepts plain text article
 * content in local (POSIX) form and convert the single LF line breaks to
 * canonical (CRLF) form.
 *
 * According to RFC2045 and RFC2046 single CR characters are deleted.
 *
 * On success the caller is responsible to free the allocated memory.
 *
 * \return
 * - Pointer to decoded data (a new memory block was allocated)
 * - NULL on error
 */

const char*  enc_convert_posix_to_canonical(const char*  s)
{
   const char*  res = NULL;
   size_t  i = 0;
   char*  buf = NULL;
   size_t  len = 0;
   size_t  bi = 0;
   char*  p;

   if(NULL != s)
   {
      /* Check for empty string and accept it */
      if(!s[0])
      {
         p = (char*) posix_malloc((size_t) 1);
         if(NULL != p)  { p[0] = 0;  res = p; }
      }
      else
      {
         while(s[i])
         {
            /* At least 3 octets must stay available for CR + LF + NUL */
            if(bi + (size_t) 4 >= len)
            {
               /* Allocate more memory in exponentially increasing chunks */
               if(!len)  { len = 64; }
               p = (char*) posix_realloc((void*) buf, len *= (size_t) 2);
               if(NULL == p)  { posix_free((void*) buf);  buf = NULL;  break; }
               else  { buf = p; }
            }
            /* Check for end of line (LF) */
            if(0x0A == (int) s[i])
            {
               /* Yes => Add a CR before the LF */
               buf[bi++] = 0x0D;
               buf[bi++] = 0x0A;
            }
            else if(0x0D == (int) s[i])
            {
               print_error("ENC: Invalid CR control character deleted"
                           " while converting to canonical format");
            }
            else { buf[bi++] = s[i]; }
            ++i;
         }
         if(NULL != buf)
         {
            /* Ensure that last line of nonempty result ends with CRLF */
            if(bi)
            {
               if(0x0A != (int) buf[bi - (size_t) 1])
               {
                  /* Append CR+LF */
                  buf[bi++] = 0x0D;
                  buf[bi++] = 0x0A;
               }
            }
            /* Add termination */
            buf[bi] = 0;
            res = buf;
         }
      }
   }

   return(res);
}


/* ========================================================================== */
/*! \brief Convert string from supported character set to Unicode (UTF-8 NFC)
 *
 * \param[in] charset  Character set of string \e s
 * \param[in] s        String to convert
 *
 * According to RFC2049 the following rules are applied:
 * - For all character sets from the ISO8859 family that are not supported,
 *   at least the ASCII characters must be decoded correctly
 *   => We decode all non ASCII characters as "?" in this case.
 *
 * According to RFC3629 the following rules are applied:
 * - If the input data is already UTF-8 is is not allowed to accept it
 *   unchecked. It is mandatory to check the validity of the encoding
 *   => We do so.
 *
 * \note
 * Some control characters that may cause problems are removed.
 *
 * \return
 * - Pointer to decoded Unicode data (UTF-8 encoded with NFC normalization)
 *   If the result is not equal to \e s , a new memory block was allocated
 * - NULL on error (Original memory block for \e s is still allocated)
 */

const char*  enc_convert_to_utf8_nfc(enum enc_mime_cs  charset, const char*  s)
{
   const char*  res = NULL;
   char*  p;
   const char*  tmp;
   size_t  len;
   size_t  i;
   size_t  ii;
   long int  ucp;
   int  cc_flag = 0;
   size_t  di;

   switch(charset)
   {
      case ENC_CS_ISO8859_X:
      {
         print_error("ENC: Convert unsupported ISO8859 character set as ASCII");
         /* No break here is intended */
      }
      case ENC_CS_ASCII:
      {
         len = strlen(s);
         p = (char*) posix_malloc(++len);
         if(NULL == p)  { break; }
         for(i = 0; i < len; ++i)
         {
            p[i] = s[i];
            if((unsigned char) 127 < (unsigned char) p[i])  { p[i] = '?'; }
         }
         res = p;
         break;
      }
      case ENC_CS_ISO8859_1:
      case ENC_CS_ISO8859_2:
      case ENC_CS_ISO8859_3:
      case ENC_CS_ISO8859_4:
      case ENC_CS_ISO8859_5:
      case ENC_CS_ISO8859_6:
      case ENC_CS_ISO8859_7:
      case ENC_CS_ISO8859_8:
      case ENC_CS_ISO8859_9:
      case ENC_CS_ISO8859_10:
      case ENC_CS_ISO8859_11:
      case ENC_CS_ISO8859_13:
      case ENC_CS_ISO8859_14:
      case ENC_CS_ISO8859_15:
      case ENC_CS_ISO8859_16:
      case ENC_CS_MACINTOSH:
      case ENC_CS_KOI8R:
      case ENC_CS_WINDOWS_1250:
      case ENC_CS_WINDOWS_1251:
      case ENC_CS_WINDOWS_1252:
      case ENC_CS_WINDOWS_1256:
      case ENC_CS_IBM437:
      case ENC_CS_IBM850:
      case ENC_CS_IBM858:
      {
         res = enc_8bit_convert_to_utf8_nfc(charset, s);
         break;
      }
      case ENC_CS_ISO2022_JP:
      {
         res = enc_iso2022_convert_to_utf8_nfc(charset, s);
         break;
      }
      case ENC_CS_UTF_7:
      {
         res = enc_uc_convert_utf7_to_utf8_nfc(s);
         break;
      }
      case ENC_CS_UTF_8:
      {
         res = s;
         break;
      }
      default:
      {
         /* Not supported */
         res = NULL;
         break;
      }
   }

   /* Check encoding */
   if(NULL != res)
   {
      if(enc_uc_check_utf8(res))
      {
         if(ENC_CS_UTF_8 != charset && ENC_CS_UTF_7 != charset)
         {
            /* Likely a bug in the conversion tables or the ISO2022 decoder */
            print_error("ENC: Invalid UTF-8 encoding detected (bug)");
         }
         if(res != s)  { posix_free((void*) res); }
         res = NULL;
      }
   }

   /* Normalize to NFC */
   if(NULL != res)
   {
      tmp = enc_uc_normalize_to_nfc(res);
      if(res != tmp && res != s)  { posix_free((void*) res); }
      res = tmp;
   }

   /* Remove unwanted control characters */
   if(NULL != res)
   {
      i = 0;
      while(1)
      {
         ucp = enc_uc_decode_utf8(res, &i);
         if(-1L == ucp)  { break; }
         if(enc_uc_check_control(ucp))  { cc_flag = 1;  break; }
      }
      if(cc_flag)
      {
         /* Unwanted control characters found */
         len = strlen(res);
         p = (char*) posix_malloc(++len);
         if(NULL == p)
         {
            if(s != res)  { posix_free((void*) res); }
            res = NULL;
         }
         else
         {
            /* Remove them */
            i = 0;  ii = 0;
            while(1)
            {
               ucp = enc_uc_decode_utf8(res, &i);
               if(-1L == ucp)  { break; }
               if(!enc_uc_check_control(ucp))
               {
                  di = 1;
                  enc_uc_encode_utf8(p, &ii, &ucp, &di);
               }
            }
            p[ii] = 0;
            if(s != res)  { posix_free((void*) res); }
            res = p;
         }
      }
   }

   return(res);
}


/* ========================================================================== */
/*! \brief Convert string from Unicode (UTF-8 NFC) to an 8bit character set
 *
 * \param[out] charset  Pointer to character set of result (or \c NULL)
 * \param[in]  s        Unicode string to convert in UTF-8 NFC format
 * \param[out] cs_iana  Pointer to IANA character set name (or \c NULL)
 *
 * \attention
 * Ensure that the string \e s is valid UTF-8 and normalized to NFC. Otherwise
 * this function will not work as expected.
 *
 * According to RFC2046 the following rules are applied:
 * - In general, composition software should always use the "lowest common
 *   denominator" character set possible
 *   => We do so by preferring the widely supported ISO8859-1 character set.
 *
 * \note
 * If this function supports more character sets in the future, ISO8859-1 must
 * always stay the preferred one (because this is our fallback locale character
 * set to allow the use of POSIX regular expressions without Unicode support
 * from the system).
 *
 * If \c NULL is passed as parameter \e charset or \e cs_iana , this indicates
 * that the caller is not interested in this information. The corresponding
 * data is discarded in this case.
 *
 * \return
 * - Pointer to encoded data (the character set is written to \e charset)
 *   If the result is not equal to \e s , a new memory block was allocated
 * - NULL on error (Original memory block for \e s is still allocated)
 *   Nothing is written to \e charset and \e cs_iana in this case
 */

const char*  enc_convert_to_8bit(enum enc_mime_cs*  charset, const char*  s,
                                 const char**  cs_iana)
{
   const char*  res = NULL;
   size_t  i = 0;
   size_t  ii = 0;
   long int  ucp = 0;
   char*  p = NULL;
   size_t  len;
   int  error = 0;

   /*
    * Allocate target buffer with same size as source buffer.
    * This is always sufficient for every 8bit character set.
    */
   len = strlen(s);
   p = (char*) posix_malloc(++len);
   if(NULL != p)
   {
      while(1)
      {
         ucp = enc_uc_decode_utf8(s, &i);
         if(-1L == ucp)  { break; }
         /* ISO8859-1 is mapped 1:1 into the Unicode codepoint space */
         if(256L <= ucp)  { error = 1;  break; }
         else  { p[ii++] = (char) (unsigned char) ucp; }
      }
      /* Check for error */
      if(error)  { posix_free((void*) p); }
      else
      {
         p[ii] = 0;
         res = p;
         if(NULL != charset)  { *charset = ENC_CS_ISO8859_1; }
         if(NULL != cs_iana)  { *cs_iana = "ISO-8859-1"; }
      }
   }

   return(res);
}


/* ========================================================================== */
/*! \brief Encode header field body using MIME \c encoded-word tokens
 *
 * This function use quoted-printable encoding.
 *
 * \param[out] r   Pointer to result string pointer
 * \param[in]  b   Header field body that contains potential Unicode data
 * \param[in]  pl  Length of header field prefix (Length limit: 25)
 *
 * The header field body \e b must be verified by the caller to be valid UTF-8
 * (this function will do the normalization to NFC).
 * The CRLF termination must be removed before calling this function.
 *
 * The length \e pl must include the header field name, the colon and any
 * potential white space not included in \e b .
 *
 * According to RFC5536 the following rules are applied:
 * - A header field line is not allowed to be empty
 *   => The header field is never folded immediately after the name separator.
 * - Lines are not allowed to contain more than 1000 characters
 *   => We respect this by rejecting words that are longer than 998 characters.
 *
 * According to RFC2047 the following rules are applied:
 * - White space between encoded-words is semantically ignored
 *   => A single space between encoded-words is included in the trailing word,
 *      additional LWSP characters are included into the leading word.
 * - A header line containing encoded-words must be no longer than 76 characters
 *   => We fold before this limit.
 * - If folding is required, each encoded-word must contain an integral number
 *   of characters and must be self-contained
 *   => We only split between Unicode glyphs when using UTF-8
 * - If there is more than one character set that can represent the content of
 *   an encoded-word, ISO8859 should be preferred
 *   => We do so if the required ISO8859 encoder is available.
 *
 * According to RFC5198 the following rules are applied:
 * - It's recommended to use NFC normalization in general Internet text messages
 *   => We do so.
 *
 * On success, the address of the result buffer is written to the location
 * pointed to by \e r (this may be the same as \e b if there is nothing to do).
 * The caller is responsible to free the potentially allocated memory.
 * On error \c NULL is written to the location pointed to by \e r .
 *
 * \return
 * - 0 on success if a new memory block was allocated
 * - 1 on success if there was nothing to encode and no memory was allocated
 * - -1 on error
 */

int  enc_mime_word_encode(const char**  r, const char*  b, size_t  pl)
{
   static const char  error_msg[] = "[Error]";
   static const char  folding[] = "\n ";  /* Line break must be in POSIX form */
   int  res = 0;
   char*  rbuf = NULL;
   size_t  rbuf_len = 0;
   size_t  ri = 0;
   const char*  body = NULL;
   const char*  body_tmp = NULL;
   const char*  cs_iana = "UTF-8";
   enum enc_mime_cs  cs = ENC_CS_UTF_8;
   size_t  start = 0;
   size_t  end = 0;
   size_t  i = 0;
   size_t  ii;
   size_t  iii;
   int  enc_flag = 0;
   int  enc_last = 0;
   int  enc_split = 0;
   char  enc_word[1001];  /* sizeof(folding) + 998 + NUL */
   size_t  ei;
   size_t  word_len;
   unsigned int  dh, dl;
   char*  p;
   size_t  rem = 0;
   int  init = 1;  /* Flag indicating initial word */
   int  first = 1;  /* Flag indicating first line of header field */
   int  uc_split;  /* Flag indicating Unicode must be split here */
#if !ENC_MIME_HEADER_FOLD_ASCII_LINES
   int  no_ec = 1;  /* Flag indicating line contains no encoded-words */
#endif  /* ENC_MIME_HEADER_FOLD_ASCII_LINES */
   long int  ucp;  /* Unicode code point */
   struct uc_cdc  cdc;  /* Unicode canonical decomposition data */
   size_t  gcpsl;  /* Unicode glyph codepoint sequence length */
   int  eod;  /* End of data */

   /* Check parameters */
   if((size_t) 25 < pl)
   {
      print_error("ENC: MIME: Header field name too long");
      res = -1;
   }
   else
   {
      /* Calculate remaining bytes for folding */
      rem = (size_t) 76 - pl;
      /*
       * Check whether header field body contains only printable ASCII
       * and no "=?" or "?=" (to be more friendly) sequences
       */
      if(!enc_ascii_check_printable(b)
         && NULL == strstr(b, "=?") && NULL == strstr(b, "?="))
      {
         /* Nothing to do => Data can be used "as is" */
         res = 1;
      }
      else
      {
         /* Check Unicode */
         if(enc_uc_check_utf8(b))
         {
            /* Invalid Unicode */
            print_error("ENC: MIME: Encoding of header field failed");
            p = (char*) posix_malloc(strlen(error_msg) + (size_t) 1);
            if(NULL != p)  { strcpy(p, error_msg); }
            body_tmp = p;
         }
         else
         {
            /* Normalize Unicode */
            body_tmp = enc_uc_normalize_to_nfc(b);
         }
         if(NULL == body_tmp)  { res = -1; }
      }
   }

   /* Check for error */
   if(!res)
   {
      /* Convert body to target character set */
      body = enc_convert_to_8bit(&cs, body_tmp, &cs_iana);
      if(NULL == body)  { body = body_tmp; }
      /* Split body into words using SP delimiter */
      do
      {
         end = i++;
         if(!body[i] || ' ' == body[i])
         {
            /* Check for 2*LWSP */
            if(body[i])
            {
               if(' ' == body[i + (size_t) 1]
                  || (char) 0x09 == body[i + (size_t) 1])
               {
                  continue;
               }
            }
            /* Check whether word needs encoding */
            enc_last = enc_flag;  enc_flag = 0;
            ei = 0;
            for(ii = start; ii <= end; ++ii)
            {
               enc_word[ei++] = body[ii];
               if(128U <= (unsigned int) body[ii])  { enc_flag = 1;  break; }
               if('=' == (unsigned int) body[ii])
               {
                  if((ii < end && '?' == body[ii + (size_t) 1])
                     || (ii > start && '?' == body[ii - (size_t) 1]))
                  {
                     enc_flag = 1;
                     break;
                  }
               }
            }
            if(enc_split)  { enc_flag = 1; }
            if(enc_flag)
            {
               /* Create MIME encoded word using quoted printable encoding */
#if !ENC_MIME_HEADER_FOLD_ASCII_LINES
               no_ec = 0;
#endif  /* ENC_MIME_HEADER_FOLD_ASCII_LINES */
               strcpy(enc_word, "=?");
               strcat(enc_word, cs_iana);
               strcat(enc_word, "?Q?");
               uc_split = 0;
               if(enc_last && !enc_split)
               {
                  /* The space between encoded words is not semantical */
                  strcat(enc_word, "_");
               }
               ei = strlen(enc_word);
               for(ii = start; ii <= end; ++ii)
               {
                  /* Check for start of UTF-8 sequence */
                  if(ENC_CS_UTF_8 == cs && 0x80 != ((int) body[ii] & 0xC0))
                  {
                     /* Search for next starter */
                     eod = 0;
                     iii = 0;
                     while(!uc_split)
                     {
                        /* Count bytes as "=XX", even if encoded "as is" */
                        gcpsl = iii * (size_t) 3;
                        /* Check for end of data */
                        if(!body[ii + iii])  { eod = 1; }
                        else
                        {
                           /* Decode UTF-8 sequence for codepoint */
                           if(!body[ii + iii])  { break; }
                           ucp = enc_uc_decode_utf8(&body[ii], &iii);
                           if(0L > ucp)
                           {
                              print_error("ENC: MIME: Decoding UCP failed");
                             break;
                           }
                           enc_uc_lookup_cdc(ucp, &cdc);
                           /* Check whether first codepoint */
                           if(!gcpsl)
                           {
                               /* If Starter => Skip */
                               if(!cdc.ccc)  { continue; }
                               /* Else abort */
                               else  { break; }
                           }
                        }
                        /* Check for next Unicode glyph boundary */
                        if(eod || !cdc.ccc)  /* Check eod first */
                        {
                           /* Glyph boundary found */
                           /*
                            * Reserve space for encoded word prefix and suffix:
                            * "=?UTF-8?Q??="
                            * => 12 characters with the folding space
                            */
                           if((size_t) (75 - 12) < gcpsl)
                           {
                              /* Glyph codepoint sequence too long */
                              print_error("ENC: MIME: GCPS too long");
                              /*
                               * Replace with '?' (U+FFFD is too large!)
                               * Maximum allowed length is one "=XX" triplet.
                               */
                              enc_word[ei++] = '=';
                              enc_word[ei++] = '3';
                              enc_word[ei++] = 'F';
                              ii += iii - (size_t) 1;
                              uc_split = 1;
                           }
                           /*
                            * Check for length limit
                            * Reserve 2 characters for closing "?="
                            */
                           else if((size_t) (75 - 2) - gcpsl < ei)
                           {
                              uc_split = 1;
                           }
                           break;
                        }
                     }
                  }
                  if(uc_split)  { /* Rewind current byte */ --ii; }
                  else
                  {
                     /* Check whether character can be encoded "as is" */
                     if( ('0' <= body[ii] && '9' >= body[ii])
                        || ('A' <= body[ii] && 'Z' >= body[ii])
                        || ('a' <= body[ii] && 'z' >= body[ii])
                        || '!' == body[ii] || '*' == body[ii] || '+' == body[ii]
                        || '-' == body[ii] || '/' == body[ii] )
                     {
                        /* Yes */
                        enc_word[ei++] = body[ii];
                     }
                     else
                     {
                        /* No => Encode with hexadecimal syntax */
                        enc_word[ei++] = '=';
                        dh = (unsigned int) (unsigned char) body[ii] / 16U;
                        if(10U > dh)  { enc_word[ei++] = (char) (48U + dh); }
                        else  { enc_word[ei++] = (char) (65U + dh - 10U); }
                        dl = (unsigned int) (unsigned char) body[ii] % 16U;
                        if(10U > dl)  { enc_word[ei++] = (char) (48U + dl); }
                        else  { enc_word[ei++] = (char) (65U + dl - 10U); }
                     }
                  }
                  /*
                   * Check for length limit
                   * Reserve 3 characters for next hexdecimal value
                   * Reserve 2 characters for closing "?="
                   */
                  if(uc_split || (size_t) (75 - 3 - 2) < ei)
                  {
                     /* Terminate normally if there are no more characters */
                     if(ii < end)
                     {
                        enc_split = 1;
                        /* Rewind index to process skipped data in next run */
                        i -= (end - ii);
                        --i;
                        break;
                     }
                  }
                  else  { enc_split = 0; }
               }
               /* End mark of encoded-word */
               enc_word[ei++] = '?';
               enc_word[ei++] = '=';
            }
            /* Terminate word */
            enc_word[ei] = 0;
            /* printf("Word: |%s|\n", enc_word); */
            /* One additional character for potential delimiting space */
            word_len = strlen(enc_word) + (size_t) 1;
            if((size_t) 998 < word_len)
            {
               print_error("ENC: MIME: Encoded-word too long");
               res = -1;
               break;
            }
            /* Fold header field if lines get too long otherwise */
            if(word_len && (word_len > rem)
#if !ENC_MIME_HEADER_FOLD_ASCII_LINES
               && !(no_ec && !enc_flag && (word_len < rem + (size_t) 922))
#endif  /* ENC_MIME_HEADER_FOLD_ASCII_LINES */
              )
            {
               /* Fold => This automatically creates SP delimiter */
               if(first)
               {
                  print_error(
                     "ENC: MIME: Encoded-word too long for first line");
                  res = -1;
                  break;
               }
               else if(word_len > rem)
               {
                  memmove((void*) &enc_word[strlen(folding)], (void*) enc_word,
                          word_len--);
                  /* Decrement because SP delimitier is part of folding mark */
                  memcpy((void*) enc_word, (void*) folding, strlen(folding));
                  word_len += strlen(folding);
                  rem = (size_t) 75;
               }
#if !ENC_MIME_HEADER_FOLD_ASCII_LINES
               /* Check whether last word was an encoded word */
               if(!enc_flag)  { no_ec = 1; }
#endif  /* ENC_MIME_HEADER_FOLD_ASCII_LINES */
            }
            else
            {
               /*
                * Prepend SP delimiter
                * Note that this delimiter is always syntactical, but not sematical
                * between two encoded words!
                */
               if(init)  { init = 0;  --word_len; }
               else
               {
                  memmove((void*) &enc_word[1], (void*) enc_word, word_len);
                  enc_word[0] = ' ';
               }
            }
            /* Allocate more memory in exponentially increasing chunks */
            /* Attention: Be prepared for large data (ASCII only lines) */
            while(ri + word_len >= rbuf_len)  /* One additional byte for NUL */
            {
               if(!rbuf_len)  { rbuf_len = 128; }
               p = posix_realloc((void*) rbuf, rbuf_len *= (size_t) 2);
               if(NULL == p) { res = -1;  break; }
               else  { rbuf = p; }
            }
            if(-1 == res)  { break; }
            /* Copy word to result buffer */
            memcpy((void*) &rbuf[ri], (void*) enc_word, word_len);
            ri += word_len;
            if(rem < word_len)  { rem = 0; }
            else  { rem -= word_len; }
            first = 0;
            /* Store new start index */
            start = i + (size_t) 1;
         }
      }
      while(body[i]);
   }
   if(body != body_tmp)  { posix_free((void*) body); }
   if(body_tmp != b)  { posix_free((void*) body_tmp); }
   /* Terminate result string */
   if(NULL != rbuf)  { rbuf[ri] = 0; }

   /* Check result */
   switch(res)
   {
      case 0:
      {
         *r = (const char*) rbuf;
         break;
      }
      case 1:
      {
         *r = b;
         break;
      }
      default:
      {
         posix_free((void*) rbuf);
         *r = NULL;
         break;
      }
   }
   /* if(0 <= res)  { printf("Result: %s\n", *r); } */

   return(res);
}


/* ========================================================================== */
/*! \brief Decode header field containing potential MIME \c encoded-word tokens
 *
 * \param[out] r  Pointer to result string pointer
 * \param[in]  b  Header field body that contains potential encoded-words
 *
 * The header field body \e b must be unfolded before calling this function.
 *
 * According to RFC2047 the following rules are applied:
 * - An encoded-word is not allowed to be longer than 75 characters
 *   => We decode encoded-word of arbitrary length.
 * - An encoded-word not at the beginning can start after a 'linear-white-space'
 *   token => We resync the parser after every white space.
 * - Any amount of linear-space-white between 'encoded-word's must be ignored
 *   => We do so.
 * - The character set and encoding fields must be treated case-insensitive
 *   => We do so.
 * - All character sets from the ISO8859 family that are not supported must be
 *   handled in a way that contained ASCII characters are decoded correctly
 *   => We do so.
 *
 * According to RFC3629 the following rules are applied:
 * - If the content of an encoded word is UTF-8 encoded, it is is not allowed
 *   to accept it unchecked. It is mandatory to check the validity of the
 *   encoding => We do so.
 *
 * On success, the address of the result buffer is written to the location
 * pointed to by \e r (this may be the same as \e b if there is nothing to do).
 * The caller is responsible to free the potentially allocated memory.
 * On error \c NULL is written to the location pointed to by \e r .
 *
 * \return
 * - 0 on success if something was decoded and a new memory block was allocated
 * - 1 on success if there was nothing to decode and no memory was allocated
 * - -1 on error
 */

int  enc_mime_word_decode(const char**  r, const char*  b)
{
   int  res = 0;
   char*  rbuf = NULL;
   size_t  rbuf_len = 0;
   size_t  ri = 0;
   size_t  i = 0;
   const char*  target;
   char*  p;
   char*  p2;
   enum enc_mime_cs  charset;
   char  encoding;
   const char*  wbuf;
   const char*  nbuf;
   size_t  nbuf_len;
   int  word_flag = 0;
   size_t  word_trailing_space = 0;
   size_t  ii;

   /* Fast special check for "no equal sign" (and no unwanted line feeds) */
   target = strchr(&b[i], (int) '=');
   if(NULL == target)
   {
      target = strchr(&b[i], 0x0A);
      if(NULL == target)  { res = 1; }
   }

   while(!res && b[i])
   {
      wbuf = NULL;
      /* Skip white space */
      nbuf_len = 0;
      while(b[i] &&
            (' ' == b[i + nbuf_len] || (const char) 0x09 == b[i + nbuf_len]))
      {
         ++nbuf_len;
      }
      if(!nbuf_len)
      {
         /* Check for encoded word */
         p = NULL;
         target = &b[i];
         if('=' == target[0])
         {
            if('?' == target[1])
            {
               /* Start delimiter detected */
               p = strchr(&target[2], (int) '?');
               if(NULL != p)
               {
                  /* Extract character set (ignore RF2231 language tokens) */
                  p2 = strchr(&target[2], (int) '*');
                  if(NULL == p2)  { p2 = p; }
                  else if(p < p2)  { p2 = p; }
                  charset = enc_mime_get_charset(&target[2], p2 - &target[2]);
                  /* Extract encoding */
                  if(p[1])
                  {
                     encoding = (char) toupper((int) p[1]);
                     if('?' != p[2])
                     {
                        print_error("ENC: MIME: Syntax error in encoded-word");
                     }
                     else
                     {
                        /* Extract payload */
                        target = &p[3];
                        p = strchr(target, (int) '?');
                        if(NULL != p)
                        {
                           if('=' == p[1])
                           {
                              /* End delimiter detected */
                              switch(encoding)
                              {
                                 case 'Q':
                                 {
                                    /* Use quoted printable decoder */
                                    wbuf = enc_mime_decode_q(charset,
                                                             target, p, 1);
                                    break;
                                 }
                                 case 'B':
                                 {
                                    /* Use base64 decoder */
                                    wbuf = enc_mime_decode_b(charset,
                                                             target, p, 0);
                                    break;
                                 }
                                 default:
                                 {
                                    print_error(
                                       "ENC: MIME: Encoding not supported");
                                    break;
                                 }
                              }
                           }
                        }
                     }
                  }
               }
            }
         }
         if(NULL != wbuf)
         {
            /* Rewind white space between encoded words */
            if(word_flag)
            {
               while(ri && ' ' == rbuf[ri - (size_t) 1])  { --ri; }
               ri += word_trailing_space;
            }
            /* Copy encoded word */
            word_flag = 1;
            nbuf = wbuf;
            nbuf_len = strlen(nbuf);
            i += &p[2] - &b[i];
            /* Store number of trailing spaces */
            word_trailing_space = 0;
            if(nbuf_len)
            {
               ii = nbuf_len;
               while(ii--)
               {
                  if(' ' != nbuf[ii])  { break; }
                  else  { ++word_trailing_space; }
               }
            }
         }
         else
         {
            /* Copy as ASCII up to next white space */
            word_flag = 0;
            nbuf = &b[i];
            p = strchr(nbuf, (int) ' ');
            p2 = strchr(nbuf, 0x09);
            if(NULL != p2 && p2 < p)  { p = p2; }
            if(NULL == p)  { nbuf_len = strlen(nbuf); }
            else  { nbuf_len = (size_t) (p - nbuf); }
            i += nbuf_len;
         }
      }
      else
      {
         /* Copy white space */
         nbuf = &b[i];
         i += nbuf_len;
      }

      /* Allocate more memory in exponentially increasing chunks */
      while(ri + nbuf_len >= rbuf_len)  /* 1 additional byte for termination */
      {
         if(!rbuf_len)  { rbuf_len = 128; }
         p = posix_realloc((void*) rbuf, rbuf_len *= (size_t) 2);
         if(NULL == p) { res = -1;  break; }
         else  { rbuf = p; }
      }

      /* Copy decoded word to result buffer */
      memcpy((void*) &rbuf[ri], (void*) nbuf, nbuf_len);
      ri += nbuf_len;
      if(NULL != wbuf)  { posix_free((void*) wbuf); }
   }
   /* Terminate result string */
   if(NULL != rbuf)  { rbuf[ri] = 0; }

   /* Remove unwanted line feeds */
   if(NULL != rbuf)
   {
      ri = 0;
      do
      {
         if(0x0A == (int) rbuf[ri])  { rbuf[ri] = '?'; }
      }
      while(rbuf[++ri]);
   }

   /* Check result */
   switch(res)
   {
      case 0:
      {
         *r = (const char*) rbuf;
         break;
      }
      case 1:
      {
         *r = b;
         break;
      }
      default:
      {
         posix_free((void*) rbuf);
         *r = NULL;
         break;
      }
   }

   return(res);
}


/* ========================================================================== */
/*! \brief Decode header field containing potential MIME parameters
 *
 * \param[out] r   Pointer to result string pointer
 * \param[in]  b   Prepared header field body that contains potential parameters
 * \param[in]  m   Operating mode (see description below)
 *
 * The parameter \e m enable special processing if set to a nonzero value.
 * \e m should be set to 1 for the \c Content-Type header field.
 *
 * \attention
 * This function must be called after unfolding the field body, with comments
 * stripped and after decoding of \c quoted-string tokens. Whitespace must
 * already be merged into the semantically equivalent single SP (and removed
 * completely before semicolons and around equal signs) by the caller.
 *
 * According to RFC2231 the following rules are applied:
 * - Parameters can be split into multiple sections which can be listed in
 *   arbitrary order inside the header field body
 *   => We accept parameter sections in any order and merge them in ascending
 *      order.
 * - Parameter sections are allowed to contain literal content as well as
 *   \c quoted-string tokens. Mixing sections of both types is allowed
 *   => \c quoted-string tokens must already be decoded in \e b by the caller.
 * - Parameters can contain character set information
 *   => We accept content in any supported character set and decode it to
 *      Unicode NFC (non-US_ASCII octets of unsupported character sets are
 *      decoded to the underscore character).
 * - Parameter can contain language information => We accept and ignore it.
 *
 * According to RFC3629 the following rules are applied:
 * - If the content of a parameter is UTF-8 encoded, it is is not allowed to
 *   accept it unchecked. It is mandatory to check the validity of the encoding
 *   => We do so.
 *
 * On success, the address of the result buffer is written to the location
 * pointed to by \e r (this may be the same as \e b if there is nothing to do).
 * The caller is responsible to free the potentially allocated memory.
 * On error \c NULL is written to the location pointed to by \e r .
 *
 * \return
 * - 0 on success if something was decoded and a new memory block was allocated
 * - 1 on success if there was nothing to decode and no memory was allocated
 * - -1 on error
 */

int  enc_mime_para_decode(const char**  r, const char*  b, int  m)
{
   int  res = -1;
   posix_locale_t  loc_ctype_posix = 0;
   int  finished = 0;
   struct mime_parameter**  parray = NULL;
   size_t  ppsize = sizeof(struct mime_parameter*);
   struct mime_parameter*  pdata;
   size_t  psize = sizeof(struct mime_parameter);
   const char*  first_end;  /* Semicolon after first element */
   const char*  p;  /* Start of parameter */
   const char*  p_cs;  /* Start of charset name */
   const char*  p_start;  /* Start of parameter value */
   const char*  p_end;  /* End of parameter */
   const char*  p_eq_sign;  /* Position of equal sign */
   const char*  p_asterisk;  /* Position of asterisk (or 'p_eq_sign') */
   size_t  alen;  /* Length of attribute token */
   size_t  clen;  /* Length of charset token */
   size_t  i = 0;
   size_t  ii = 0;
   int  rv;
   struct mime_parameter**  tmp;
   char*  tmp2;
   char*  tmp3;
   unsigned int  sec_num;
   char  ext_mark;  /* Flag indicating extended-parameter (with charset) */
   const char*  q;
   char*  rbuf = NULL;
   size_t  rbuf_len;
   size_t  ri = 0;
   size_t  len;
   size_t  len2;
   int  rewind;
   int  error = 0;

   /* Create a locale object with LC_CTYPE == POSIX */
   loc_ctype_posix = posix_newlocale(POSIX_LC_CTYPE_MASK, "POSIX",
                                     (posix_locale_t) 0);
   if((posix_locale_t) 0 == loc_ctype_posix)
   {
      print_error("ENC: MIME: Cannot create locale object");
      return(res);
   }

   /* Nothing to do if there are no asterisks */
   if(NULL == strchr(b, (int) '*'))  { *r = b;  res = 1; }
   else
   {
#if 0
      /* For debugging */
      printf("---------------\n");
      printf("%s\n", b);
#endif
      /* Skip to end of content type */
      first_end = strchr(b, (int) ';');
      if(NULL == first_end)  { *r = b;  res = 1; }
      else
      {
         /* Initialize parameter section array */
         parray = (struct mime_parameter**) posix_malloc(ppsize);
         if(NULL != parray)
         {
            parray[0] = NULL;
            /* Parse parameters */
            p_end = first_end;
            do
            {
               p = p_end + 1;
               sec_num = 0;
               ext_mark = ' ';
               clen = 0;
               /* Skip potential space after semicolon */
               if(' ' == *p)  { ++p; }
               /* Seach for end of parameter section content */
               p_end = strchr(p, (int) ';');
               if(NULL == p_end)
               {
                  p_end = p + strlen(p);
                  /* Strip potential trailing space */
                  if(' ' == *(p_end - 1))  { --p_end; }
                  finished = 1;
               }
               /* Search for end of parameter name */
               p_eq_sign = strchr(p, (int) '=');
               if(NULL == p_eq_sign)  { break; }
               if(p_end < p_eq_sign)  { break; }
               /* Search for end of attribute token (asterisk) */
               p_asterisk = strchr(p, (int) '*');
               if(NULL != p_asterisk && p_eq_sign > p_asterisk)
               {
                  /* Extract section number */
                  rv = sscanf(p_asterisk, " * %u", &sec_num);
                  if(1 != rv)
                  {
                     /* No section number specified */
                     sec_num = 0;
                     /* Check for extended-parameter */
                     sscanf(p_asterisk, " %c", &ext_mark);
                  }
                  else
                  {
                     /* Check for extended-parameter */
                     sscanf(p_asterisk, " * %*u %c", &ext_mark);
                  }
               }
               else  { p_asterisk = p_eq_sign; }
               alen = (size_t) (p_asterisk - p);
               if(alen && ' ' == p[alen - (size_t) 1])
               {
                  /* Strip potential trailing space */
                  --alen;
               }
               /* Check for parameter attribute length limit */
               if(ENC_MIME_PARA_LENGTH_MAX < alen)
               {
                  print_error("ENC: MIME: Parameter attribute too long");
                  continue;
               }
               /* Extract charset */
               p_start = p_eq_sign + 1;
               p_cs = p_start;
               if(!sec_num && '*' == ext_mark)
               {
                  q = strchr(p_start, 0x27);
                  if(NULL == q)
                  {
                     print_error("ENC: MIME: Parameter charset field missing");
                  }
                  else
                  {
                     clen = (size_t) (q - p_start);
                     if(ENC_MIME_PARA_LENGTH_MAX < clen)
                     {
                        print_error("ENC: MIME: Parameter charset too long");
                        clen = 0;
                     }
                     p_start = q + 1;
                     q = strchr(p_start, 0x27);
                     if(NULL == q)
                     {
                        print_error(
                           "ENC: MIME: Parameter language field missing");
                     }
                     else  { p_start = q + 1; }
                  }
               }
               /* Remove unknown parameters for "Content-Type" mode */
               if(1 == m)
               {
                  if(posix_strncasecmp_l(p, "Charset", alen, loc_ctype_posix)
                     && posix_strncasecmp_l(p, "Format", alen, loc_ctype_posix)
                     && posix_strncasecmp_l(p, "DelSp", alen, loc_ctype_posix))
                  {
                     /* Ignore all other parameters */
                     continue;
                  }
               }
               /* Increase size of array */
               tmp = (struct mime_parameter**)
                     posix_realloc(parray,
                                   ppsize += sizeof(struct mime_parameter*));
               if(NULL == tmp)
               {
                  print_error("ENC: MIME: Parameter memory allocation failed");
                  break;
               }
               parray = tmp;
               /* Construct parameter structure ... */
               pdata = (struct mime_parameter*) posix_malloc(psize);
               if(NULL == pdata)
               {
                  print_error("ENC: MIME: Parameter memory allocation failed");
                  break;
               }
               strncpy(pdata->attribute, p, alen);
               pdata->attribute[alen] = 0;
               pdata->attribute_len = alen;
               pdata->section = sec_num;
#if 0
               /* For debugging */
               printf("Index: %u / Section: %u (%s)\n", i, sec_num,
                      pdata->attribute);
#endif
               strncpy(pdata->charset, p_cs, clen);
               pdata->charset[clen] = 0;
               pdata->value_start = p_start;
               pdata->value_end = p_end;
               pdata->valid = 1;
               /* ... and append it to array */
               parray[i] = pdata;
               parray[++i] = NULL;
            }
            while(!finished);
            /* -------------------------------------------------------------- */
            /* Allocate new memory buffer for result */
            rbuf_len = (size_t) (first_end - b);
            /* 3 additional bytes for "; " separator and NUL termination */
            rbuf = (char*) posix_malloc(rbuf_len + (size_t) 3);
            if(NULL != rbuf)
            {
               /* Copy first element (including the semicolon) */
               strncpy(rbuf, b, rbuf_len);
               rbuf[rbuf_len] = 0;
               /* Strip SPs from first element for "Content-Type" mode */
               if(1 == m)
               {
                  /* Assignment in truth expression is intended */
                  while(NULL != (q = strchr(rbuf, (int) ' ')))
                  {
                     memmove((void*) q, (void*) (q + 1),
                             strlen(q + 1) + (size_t) 1);
                     if(rbuf_len)  { --rbuf_len; }
                  }
               }
               ri += rbuf_len;
               rbuf_len += (size_t) 3;
               /* Merge parameter sections */
               res = 0;
               i = 0;
               if(NULL == parray[i])
               {
                  print_error("ENC: MIME: Missing parameters");
               }
               else do
               {
                  /* Array contain at least 1 element */
                  if(!parray[i]->valid)  { continue; }
                  if(parray[i]->section)  { continue; }
                  /* Found initial section => Insert separator */
                  rbuf[ri++] = ';';  rbuf[ri++] = ' ';
                  /* Select initial section to force first match */
                  sec_num = 0;
                  rewind = 0;
                  ii = 0;
                  do
                  {
                     /* Search for next segment */
                     if(rewind)  { rewind = 0; ii = 0; }
                     if(!parray[ii]->valid)  { continue; }
                     if(sec_num != parray[ii]->section)  { continue; }
                     else if(!strcmp(parray[i]->attribute,
                                     parray[ii]->attribute))
                     {
                        /* Calculate length */
                        if(!sec_num)
                        {
                           /* One additional byte for NUL (and later '=') */
                           alen = parray[ii]->attribute_len + (size_t) 1;
                        }
                        else  { alen = 0; }
                        len = alen;
                        len += parray[ii]->value_end - parray[ii]->value_start;
                        /* Allocate memory in exponentially increasing chunks */
                        len += 3;  /* For "; " separator and NUL termination */
                        while(ri + len >= rbuf_len)
                        {
                           tmp2 = posix_realloc((void*) rbuf,
                                                rbuf_len *= (size_t) 2);
                           if(NULL == tmp2)
                           {
                              print_error("ENC: MIME: Memory allocation"
                                          " for result buffer failed");
                              error = 1;
                              continue;
                           }
                           else  { rbuf = tmp2; }
                        }
                        len -= (size_t) 3;
                        /* Append attribute to result buffer for section 0 */
                        if(!sec_num)
                        {
                           strncpy(&rbuf[ri], parray[ii]->attribute, alen);
                           rbuf[ri + alen - (size_t) 1] = '=';
                           ri += alen;
                           len -= alen;
                        }
                        /* Append decoded value section to result buffer */
                        tmp3 = NULL;
                        if(strlen(parray[ii]->charset))
                        {
                           /* Decode charset of value section */
                           tmp2 = posix_malloc(len + (size_t) 1);
                           if(NULL != tmp2)
                           {
                              strncpy(tmp2, parray[ii]->value_start, len);
                              tmp2[len] = 0;
                              tmp3 = enc_mime_decode_parameter(tmp2,
                                                           parray[ii]->charset);
                              if(NULL != tmp3)
                              {
                                 len2 = strlen(tmp3);
                                 if(len < len2)
                                 {
                                    print_error("ENC: MIME: Decoding error");
                                    posix_free((void*) tmp3);
                                    tmp3 = NULL;
                                 }
                                 else
                                 {
                                    strncpy(&rbuf[ri], tmp3, len2);
                                    rbuf[ri + len2] = 0;
                                    ri += len2;
                                 }
                              }
                              posix_free((void*) tmp2);
                           }
                        }
                        if(NULL == tmp3)
                        {
                           strncpy(&rbuf[ri], parray[ii]->value_start, len);
                           rbuf[ri + len] = 0;
                           ri += len;
                        }
                        posix_free((void*) tmp3);
                        parray[ii]->valid = 0;
                        /* Rewind index for next section */
                        rewind = 1;
                        ++sec_num;
                     }
                  }
                  while(!error && (NULL != parray[++ii] || rewind));
                  parray[i]->valid = 0;
               }
               while(!error && NULL != parray[++i]);
            }
            /* Destroy parameter section array */
            i = 0;
            while(NULL != parray[i])  { posix_free((void*) parray[i++]); }
            posix_free((void*) parray);
         }
      }
   }
   if(error)  { res = -1; }

   /* Destroy locale object */
   if((posix_locale_t) 0 != loc_ctype_posix)
   {
      posix_freelocale(loc_ctype_posix);
   }

   /* Check for error */
   if(0 > res)  { *r = NULL; }
   if(!res)
   {
#if 0
      /* For debugging */
      printf("%s\n", rbuf);
      printf("---------------\n");
#endif
      *r = rbuf;
   }  else  { posix_free((void*) rbuf); }

   return(res);
}


/* ========================================================================== */
/*! \brief Decode MIME "Content-Type" header field
 *
 * \param[in]  ct       Pointer to result structure
 * \param[in]  hf_body  Header field body that contains the MIME content type
 * \param[out] bo       Pointer to buffer for multipart boundary delimiter
 *
 * The header field body \e hf_body is decoded and content IDs are written to
 * the structure pointed to by \e ct .
 *
 * The buffer for the boundary string used in messages with content type
 * "multipart" must be allocated by the caller with a size of at least
 * \ref ENC_BO_BUFLEN and a pointer to the start of this buffer must be passed
 * as \e bo parameter. It is allowed to pass \c NULL for \e bo if the caller
 * is not interested in the boundary string.
 *
 * According to RFC2045 the following rules are applied:
 * - If the content type is not present, "text/plain" and "US-ASCII" must be
 *   used as default => We do so.
 *
 * According to RFC2046 the following rules are applied:
 * - The content type and subtype must be treated case insensitive => We do so.
 * - The parameter names must be treated case insensitive => We do so.
 * - The default character set must be assumed as "US-ASCII" if the "charset"
 *   parameter is missing for "text/plain" content type => We do so.
 *
 * According to RFC3676 the following rules are applied:
 * - The values of parameters "Format" and "DelSp" must be treated case
 *   insensitive => We do so.
 * - The parameter "DelSp" should be ignored if content type is not "text/plain"
 *   with "format=flowed" => We do so.
 *
 * \note
 * This function never fails, instead \c ENC_xxx_UNKNOWN IDs are returned.
 */

void  enc_mime_get_ct(struct enc_mime_ct*  ct, const char*  hf_body, char*  bo)
{
   char*  body = NULL;
   size_t  len;
   size_t  i;
   size_t  ii;
   char  fmt[ENC_FMT_BUFLEN];
   char  cs[ENC_CS_BUFLEN];

   /* Initialize result */
   ct->type = ENC_CT_UNKNOWN;
   ct->subtype = ENC_CTS_UNKNOWN;
   ct->charset = ENC_CS_UNKNOWN;
   ct->flags = 0;

   /* Accept NULL pointer (treat as "field is not present") */
   if(NULL == hf_body)
   {
      ct->type = ENC_CT_TEXT;
      ct->subtype = ENC_CTS_PLAIN;
      ct->charset = ENC_CS_ASCII;
      return;
   }

   /* Allocate memory for case conversion */
   len = strlen(hf_body);
   body = (char*) posix_malloc(len + (size_t) 1);
   if(NULL != body)
   {
      /* Convert header field body to upper case */
      for(i = 0; i < len; ++i)  { body[i] = (char) toupper((int) hf_body[i]); }
      body[len] = 0;
      /* Check for content type "text" */
      if(!strncmp("TEXT", body, 4))
      {
         ct->type = ENC_CT_TEXT;
         ct->charset = ENC_CS_ASCII;
         if(!strncmp("TEXT/PLAIN", body, 10))
         {
            ct->subtype = ENC_CTS_PLAIN;
            /* Search for RFC3676 "Format" parameter (case insensitive) */
            for(i = 0; i < len; ++i)
            {
               if(!strncmp("FORMAT", &body[i], 6))
               {
                  /* Extract parameter value */
                  ii = i + (size_t) 6;
                  while(body[ii])
                  {
                     if('=' != body[ii] && ' ' != body[ii])  { break; }
                     else  { ++ii; }
                  }
                  for(i = 0; i < ENC_FMT_BUFLEN; ++i)
                  {
                     if(!body[ii + i]
                        || ';' == body[ii + i] || ' ' == body[ii + i])
                     {
                        fmt[i] = 0;  break;
                     }
                     else  { fmt[i] = body[ii + i]; }
                  }
                  fmt[ENC_FMT_BUFLEN - (size_t) 1] = 0;
                  if(!strncmp("FLOWED", fmt, 6))
                  {
                     ct->flags |= ENC_CT_FLAG_FLOWED;
                  }
                  break;
               }
            }
            if(ct->flags & ENC_CT_FLAG_FLOWED)
            {
               /* Search for RFC3676 "DelSp" parameter (case insensitive) */
               for(i = 0; i < len; ++i)
               {
                  if(!strncmp("DELSP", &body[i], 5))
                  {
                     /* Extract parameter value */
                     ii = i + (size_t) 5;
                     while(body[ii])
                     {
                        if('=' != body[ii] && ' ' != body[ii])  { break; }
                        else  { ++ii; }
                     }
                     for(i = 0; i < ENC_FMT_BUFLEN; ++i)
                     {
                        if(!body[ii + i]
                           || ';' == body[ii + i] || ' ' == body[ii + i])
                        {
                           fmt[i] = 0;  break;
                        }
                        else  { fmt[i] = body[ii + i]; }
                     }
                     fmt[ENC_FMT_BUFLEN - (size_t) 1] = 0;
                     if(!strncmp("YES", fmt, 3))
                     {
                        ct->flags |= ENC_CT_FLAG_DELSP;
                     }
                     break;
                  }
               }
            }
         }
         /* Search for "charset" parameter */
         for(i = 0; i < len; ++i)
         {
            if(!strncmp("CHARSET", &body[i], 7))
            {
               /* Extract parameter value */
               ii = i + (size_t) 7;
               while(body[ii])
               {
                  if('=' != body[ii] && ' ' != body[ii])  { break; }
                  else  { ++ii; }
               }
               for(i = 0; i < ENC_CS_BUFLEN; ++i)
               {
                  if(!body[ii + i]
                     || ';' == body[ii + i] || ' ' == body[ii + i])
                  {
                     cs[i] = 0;  break;
                  }
                  else  { cs[i] = body[ii + i]; }
               }
               cs[ENC_CS_BUFLEN - (size_t) 1] = 0;
               ct->charset = enc_mime_get_charset(cs, strlen(cs));
               break;
            }
         }
      }
      /* Check for content type "text" */
      else if(!strncmp("IMAGE", body, 5))
      {
         ct->type = ENC_CT_IMAGE;
      }
      /* Check for content type "multipart", map unknown subtypes to "mixed" */
      else if(!strncmp("MULTIPART", body, 9))
      {
         ct->type = ENC_CT_MULTIPART;
         ct->subtype = ENC_CTS_MIXED;
         if(!strncmp("MULTIPART/ALTERNATIVE", body, 21))
         {
            ct->subtype = ENC_CTS_ALTERNATIVE;
         }
         bo[0] = 0;
         /* Search for "boundary" parameter */
         for(i = 0; i < len; ++i)
         {
            if(!strncmp("BOUNDARY", &body[i], 8))
            {
               /* Extract case sensitive parameter value */
               ii = i + (size_t) 8;
               while(hf_body[ii])
               {
                  if(' ' != hf_body[ii])  { break; } else  { ++ii; }
               }
               if('=' != hf_body[ii++])
               {
                  print_error(
                     "ENC: MIME: Invalid multipart boundary parameter");
               }
               else
               {
                  while(hf_body[ii])
                  {
                     if(' ' != hf_body[ii])  { break; } else  { ++ii; }
                  }
                  for(i = 0; i < ENC_BO_BUFLEN; ++i)
                  {
                     if(!hf_body[ii + i]
                        || ';' == hf_body[ii + i] || ' ' == hf_body[ii + i])
                     {
                        bo[i] = 0;  break;
                     }
                     else  { bo[i] = hf_body[ii + i]; }
                  }
                  bo[ENC_BO_BUFLEN - (size_t) 1] = 0;
               }
               break;
            }
         }
      }
   }
   posix_free((void*) body);
}


/* ========================================================================== */
/*! \brief Decode content transfer encoding description
 *
 * \param[in] hf_body  MIME content transfer encoding description string
 *
 * This function checks whether the string \e hf_body represents a supported
 * content transfer encoding and return the corresponding ID for it.
 * According to RFC2047 the content transfer encoding is treated
 * case-insensitive.
 *
 * \note
 * It is allowed to call this function with \e hf_body set to \c NULL. This is
 * treated as an error and the return value will indicate an anknown transfer
 * encoding.
 *
 * \note
 * RFC2049 mandates that every non-7bit MIME content must be labeled with a
 * content transfer encoding header field of "8bit" or "binary".
 *
 * \result
 * - MIME content transfer encoding ID (from \ref enc_mime_cte )
 * - \c ENC_CTE_UNKNOWN on error
 */
/* If this header field is missing, we assume "binary" instead of "7bit". Don't
 * change this because it is required for handling unknown transfer
 * encodings!
 */

enum enc_mime_cte  enc_mime_get_cte(const char*  hf_body)
{
   enum enc_mime_cte  res = ENC_CTE_BIN;
   char  buf[ENC_CTE_BUFLEN];
   size_t  len;
   size_t  i;
   const char  not_supported[]
      = "ENC: MIME: Unsupported content transfer encoding: ";
   char*  p;
   size_t  l;

   /* Accept NULL pointer */
   if(NULL != hf_body)
   {
      res = ENC_CTE_UNKNOWN;
      len = strlen(hf_body);
      if(ENC_CTE_BUFLEN <= len)
      {
         /* If you get this error, the value of 'ENC_CTE_BUFLEN' is too small */
         print_error("ENC: MIME: Name of content transfer encoding too long");
      }
      else
      {
         /* Convert description to upper case */
         for(i = 0; i < len; ++i)
         {
            buf[i] = (char) toupper((int) hf_body[i]);
         }
         buf[len] = 0;
         /* Check for all known content transfer encodings */
         if(!strcmp(buf, "7BIT"))  { res = ENC_CTE_7BIT; }
         if(!strcmp(buf, "8BIT"))  { res = ENC_CTE_8BIT; }
         if(!strcmp(buf, "BINARY"))  { res = ENC_CTE_BIN; }
         if(!strcmp(buf, "QUOTED-PRINTABLE"))  { res = ENC_CTE_Q; }
         if(!strcmp(buf, "BASE64"))  { res = ENC_CTE_B; }
         /* Check whether content transfer encoding is supported */
         if(ENC_CTE_UNKNOWN == res)
         {
            l = strlen(not_supported) + len;
            p = (char*) posix_malloc(++l);
            if(NULL != p)
            {
               strcpy(p, not_supported);
               strncat(p, buf, len);
               print_error(p);
               posix_free((void*) p);
            }
         }
      }
   }

   return(res);
}


/* ========================================================================== */
/*! \brief Decode MIME content to UTF-8 NFC
 *
 * \param[in] cte      MIME content transfer encoding
 * \param[in] charset  MIME character set
 * \param[in] s        MIME encoded data
 *
 * According to RFC2049 all transfer encodings not defined in MIME 1.0 are
 * rejected.
 *
 * \return
 * - Pointer to decoded data
 *   If the result is not equal to \e s , a new memory block was allocated
 * - NULL on error (Original memory block for \e s is still allocated)
 */

const char*  enc_mime_decode(enum enc_mime_cte  cte, enum enc_mime_cs  charset,
                             const char*  s)
{
   const char*  res = NULL;
   size_t  len = strlen(s);

   /* Decode transfer encoding and convert charset to Unicode */
   switch(cte)
   {
      case ENC_CTE_Q:
      {
         res = enc_mime_decode_q(charset, s, &s[len], 0);
         break;
      }
      case ENC_CTE_B:
      {
         res = enc_mime_decode_b(charset, s, &s[len], 0);
         break;
      }
      case ENC_CTE_7BIT:
      case ENC_CTE_8BIT:
      case ENC_CTE_BIN:
      {
         res = enc_convert_to_utf8_nfc(charset, s);
         break;
      }
      default:
      {
         print_error("ENC: MIME: Content transfer encoding not supported");
         break;
      }
   }

   return(res);
}


/* ========================================================================== */
/*! \brief Decode MIME "text/plain" content with "format=flowed" parameter
 *
 * \param[in] delsp  Delete spaces at EOL if nonzero
 * \param[in] s      MIME encoded data in canonical form
 *
 * \attention
 * The encoding of the data referenced by \e s must be valid Unicode in UTF-8
 * representation. This must be checked by the caller before this function is
 * used.
 *
 * \return
 * - Pointer to decoded data
 *   (if the result is not equal to \e s , a new memory block was allocated)
 * - NULL on error (Original memory block for \e s is still allocated)
 */

const char*  enc_mime_flowed_decode(unsigned int  delsp, const char*  s)
{
   const char*  quote_mark;
   int  error = 0;
   int  abort;
   int  check;
   char*  p;
   size_t  ii;
   /* Index in input buffer */
   size_t  i = 0;
   /* Target buffer */
   char*  buf = NULL;
   size_t  len = 0;
   size_t  bi = 0;
   /* Paragraph buffer */
   char*  para = NULL;
   size_t  plen = 0;
   size_t  pi = 0;
   int  pflowed;
   /* Line buffer */
   size_t  start;
   size_t  end;
   size_t  llen;
   size_t  llimit;
   int  flowed;
   /* Quote depth */
   int  qdepth;
   size_t  qd;
   /* Index after last space suitable for line break */
   size_t  last_space;

   // Set quote mark style according to config file
   switch(config[CONF_QUOTESTYLE].val.i)
   {
      case 0:  { quote_mark = ">";  break; }
      case 1:  { quote_mark = "> ";  break; }
      default:
      {
         print_error("ENC: Quoting style configuration not supported");
         // Use default from old versions that can't be configured
         quote_mark = "> ";
         break;
      }
   }
   // Process data
   while(s[i])
   {
      /* Process next paragraph */
      pi = 0;
      pflowed = 0;
      qdepth = -1;
      do
      {
         /* Process next line */
         flowed = 0;
         /* Calculate quoting depth */
         qd = 0;
         while('>' == s[i])
         {
            if(POSIX_INT_MAX <= qd)  { break; }
            ++qd;
            ++i;
         }
         if(-1 == qdepth)  { qdepth = (int) qd; }
         else
         {
            if((int) qd != qdepth)
            {
               print_error("ENC: MIME: Invalid paragraph format"
                           " (format=flowed)");
               i -= qd;
               break;
            }
         }
         /* Remove space stuffing */
         if(' ' == s[i])  { ++i; }
         start = end = i;
         /* Search for EOL */
         while(s[i])
         {
            if(i && 0x0A == (int) s[i])
            {
               if(0x0D != (int) s[i - (size_t) 1])
               {
                  /* Canonical line termination must be CR+LF */
                  print_error("ENC: MIME: Invalid line termination"
                           " (format=flowed)");
                  end = i;
               }
               else  { end = i - (size_t) 1; }
               ++i;
               break;
            }
            /* Special handling for last line without CR+LF */
            if(!s[++i])  { end = i; }
         }
         llen = end - start;
         /* Check for flowed line */
         if(llen && ' ' == s[end - (size_t) 1])
         {
            /* Check for signature separator */
            if(!((size_t) 3 == llen
               && '-' == s[start] && '-' == s[start + (size_t) 1]))
            {
               flowed = 1;
               pflowed = 1;
               if(delsp)  { --llen;  --end; }
            }
         }
         /* Allocate memory in exponentially increasing chunks */
         while(pi + llen + (size_t) 1 >= plen)  /* At least 1 additional byte */
         {
            if(!plen)  { plen = 128; }
            p = (char*) posix_realloc((void*) para, plen *= (size_t) 2);
            if(NULL == p)
            {
               print_error("ENC: Memory allocation failed");
               error = 1;
               break;
            }
            else  { para = p; }
         }
         if(error)  { break; }
         /* Copy line to paragraph buffer */
         strncpy(&para[pi], &s[start], llen);
         pi += llen;
      }
      while(flowed);
      if(error)  { break; }
      para[pi] = 0;
      /* Copy fixed line or flowed paragraph to target buffer */
      pi = 0;
      do
      {
         llen = (size_t) qdepth * strlen(quote_mark);
         if(!pflowed)
         {
            start = 0;
            end = strlen(para);
            llen += end;
         }
         else
         {
            /* Rewrap flowed lines before 72 characters if possible */
            start = pi;
            last_space = 0;
            abort = 0;
            while(!abort)
            {
               check = 0;
               if(!para[pi])  { abort = 1; }
               else
               {
                  /* Check for SP */
                  if(' ' == para[pi])  { check = 1; }
                  /* Check for SHY (in UTF-8 encoding) */
                  else if(pi)
                  {
                     if(0xADU == (unsigned int) (unsigned char) para[pi]
                        && 0xC2U == (unsigned int)
                                    (unsigned char) para[pi - (size_t) 1])
                     {
                        check = 1;
                     }
                  }
                  ++pi;
               }
               if(abort || check)
               {
                  /* Allow max. 78 characters for quoted content */
                  llimit = (size_t) 72;
                  if(1 == qdepth)  { llimit = (size_t) 74; }
                  else if(2 == qdepth)  { llimit = (size_t) 76; }
                  else if(3 <= qdepth)  { llimit = (size_t) 78; }
                  /* Use 20 characters as minimum content width */
                  if(llimit - (size_t) 20
                     <= (size_t) qdepth * strlen(quote_mark))
                  {
                     llimit = (size_t) 20;
                  }
                  else
                  {
                     llimit -= (size_t) qdepth * strlen(quote_mark);
                  }
                  /* Check for line length limit */
                  if(llimit < enc_uc_get_glyph_count(&para[start], pi - start))
                  {
                     /* Check for second last line */
                     if(last_space)  { pi = last_space; }
                     /* Check for last line */
                     else if(abort)  { pflowed = 0; }
                     break;
                  }
                  /* Check for end of paragraph */
                  if(abort)  { pflowed = 0; }
                  else  { last_space = pi; }
               }
            }
            end = pi;
            llen += end - start;
         }
         /* Two additional characters for CR+LF line termination */
         llen += (size_t) 2;
         /* Allocate memory in exponentially increasing chunks */
         while(bi + llen + (size_t) 1 >= len)  /* At least 1 additional byte */
         {
            if(!len)  { len = 256; }
            p = (char*) posix_realloc((void*) buf, len *= (size_t) 2);
            if(NULL == p)
            {
               print_error("ENC: Memory allocation failed");
               error = 1;
               break;
            }
            else  { buf = p; }
         }
         if(error)  { break; }
         /* Copy quote marks */
         for(ii = 0; ii < (size_t) qdepth; ++ii)
         {
            strncpy(&buf[bi], quote_mark, strlen(quote_mark));
            bi += strlen(quote_mark);
         }
         /* Copy line */
         strncpy(&buf[bi], &para[start], end - start);
         bi += end - start;
         /* Copy line termination */
         buf[bi++] = (char) 0x0D;  buf[bi++] = (char) 0x0A;
      }
      while(pflowed);
      if(error)  { break; }
   }
   posix_free((void*) para);
   if(error)
   {
      print_error("ENC: MIME: Decoding of format=flowed content failed");
      posix_free((void*) buf);
      buf = NULL;
   }
   else if(NULL != buf)
   {
      /* Terminate string in target buffer */
      buf[bi] = 0;
   }

   return(buf);
}


/* ========================================================================== */
/*! \brief Parse MIME multipart content
 *
 * \param[in]  s    MIME Encoded multipart data
 * \param[in]  ct   MIME content type of decoded data
 * \param[in]  b    MIME boundary delimiter
 * \param[out] mpe  MIME multipart entity locations
 *
 * On success a pointer to the result array is written to \e mpe . The caller
 * is responsible to free the memory allocated for this array.
 *
 * \return
 * - Nonzero number of entities in multipart data on success
 * - 0 on error
 */

size_t  enc_mime_multipart(const char*  s, struct enc_mime_ct*  ct,
                           const char*  b, struct enc_mime_mpe**  mpe)
{
   size_t  res = 0;
   size_t  b_len;
   char  boundary[ENC_BO_BUFLEN] = "--";
   size_t  i = 0;
   int  preamble = 1;
   size_t  match;
   size_t  start = 0;
   size_t  end = 0;
   size_t  e_len;
   struct enc_mime_mpe*  array = NULL;
   struct enc_mime_mpe*  tmp;

   /* RFC2046 limits the boundary delimiter length to 70 characters */
   b_len = strlen(b);
   if(!b_len || (size_t) 70 < b_len)
   {
      print_error("ENC: Invalid MIME multipart boundary delimiter");
   }
   else if ((size_t) 75 > ENC_BO_BUFLEN)
   {
      print_error("ENC: Value of ENC_BO_BUFLEN must be at least 75");
   }
   else
   {
      /* Add "--" prefix to boundary */
      strncpy(&boundary[2], b, 71);
      b_len += (size_t) 2;
      /* Parse content */
      while(s[i])
      {
         /*
          * Store potential end of entity
          * RFC2046 specify that last CRLF of an entity is part of the following
          * boundary delimiter.
          */
         if((size_t) 2 <= i)  { end = i - (size_t) 2; }
         /* Compare boundary with beginning of line */
         match = 0;
         if(!strncmp(&s[i], boundary, b_len))  { match = 1; }
         /* Skip to beginning of next line (this also consumes potential LWS) */
         while(1)
         {
            if(!s[i])  { break; }
            else if((char) 0x0D == s[i++])
            {
               if((char) 0x0A == s[i++])  { break; }
            }
         }
         /* Check for start of entity */
         if(match)
         {
            /* Ignore preamble */
            if(!preamble && end > start)
            {
               /* Allocate memory for new array element */
               e_len = end - start;
               tmp = (struct enc_mime_mpe*)
                     posix_realloc(array, (res + (size_t) 1)
                                          * sizeof(struct enc_mime_mpe));
               if(NULL == tmp)
               {
                  print_error("ENC: Parsing multipart message aborted");
                  break;
               }
               else
               {
                  array = tmp;
                  /* Store start index and length of entity */
                  array[res].start = &s[start];
                  array[res++].len = e_len;
               }
            }
            /* Prepare for next entity */
            start = i;
            preamble = 0;
         }
      }
   }

   /* Check for success */
   *mpe = NULL;
   if(res)  { *mpe = array; }

   return(res);
}


/* ========================================================================== */
/*! \brief Percent decoder
 *
 * \param[in] s  String to decode (URI or MIME parameter value)
 *
 * \note
 * The data is decoded in place because it can't be larger after the decoding
 * operation.
 *
 * If \e s is \c NULL no operation is performed and success is returned.
 *
 * \return
 * - Positive value on success (if data in \e s was decoded)
 * - 0 on success (if there was nothing to do)
 * - Negative value if percent encoding in \e s is invalid
 */

int  enc_percent_decode(char*  s)
{
   int  res = 0;
   char*  p = s;
   char*  q;
   int  invalid;
   int  v;
   unsigned char  c = 0;
   size_t  len;

   while(NULL != p)
   {
      q = p;
      p = strchr(q, (int) '%');
      if(NULL != p)
      {
         /* Percent sign found => Decode character */
         res = 1;
         if((size_t) 3 > strlen(p))  { res = -1;  break; }
         invalid = 0;
         v = enc_hex_decode_nibble(p[1]);
         if(0 > v)  { invalid = 1; }
         else
         {
            c = (unsigned char) (v * 16);
            v = enc_hex_decode_nibble(p[2]);
            if(0 > v)  { invalid = 1; }
            else  { c += (unsigned char) v; }
         }
         /* Check for invalid data */
         if(invalid)  { res = -1;  break; }
         else
         {
            p[0] = (char) c;
            len = strlen(&p[3]);
            memmove((void*) &p[1], (void*) &p[3], ++len);
            ++p;
         }
      }
   }
   if(-1 == res)  { print_error("ENC: Percent decoding of URI failed"); }

   return(res);
}


/* ========================================================================== */
/*! \brief Percent encoding for URI content
 *
 * \param[in] s    URI body to encode
 * \param[in] sch  URI scheme
 *
 * Passing \c NULL for parameter \e s is allowed and treated as error.
 *
 * The following characters are percent encoded:
 * - The literal percent sign and space
 * - The list "reserved" defined in RFC3986 (not for "news" scheme)
 * - For the "mailto" scheme exactly one "commercial at" sign is required and
 *   treated literally
 * - For the "news" scheme a single "commercial at" sign is accepted literally
 *
 * \return
 * - Pointer to result on success.
 *   If the result is not equal to \e s , a new memory block was allocated
 * - NULL on error
 */

const char*  enc_uri_percent_encode(const char*  s, enum enc_uri_scheme  sch)
{
   const char*  res = NULL;
   const char*  reserved  =
      " %"            /* Space and literal percent */
      ":/?#[]@"       /* gen-delims */
      "!$&'()*+,;=";  /* sub-delims */
   const char*  unreserved  =
      "abcdefghijklmnopqrstuvwxyz"  /* Small letters */
      "ABCDEFGHIJKLMNOPQRSTUVWXYZ"  /* Capital letters */
      "0123456789"    /* Digits */
      "-._~";         /* Hyphen, Period, Underscore and Tilde */
   int  process = 0;
   size_t  i = 0;
   char*  buf = NULL;
   size_t  bi = 0;
   int  error = 0;
   int  encode;
   size_t  commercial_at = 0;
   unsigned int  nibble;

   if(NULL != s)
   {
      /* Check whether only unreserved characters are present */
      while(s[i])
      {
        if(NULL == strchr(unreserved, (int) s[i]))  { process = 1;  break; }
        ++i;
      }
      if(!process)  { res = s; }
      else
      {
         /* Allocate new buffer (Triple size is always sufficient) */
         buf = (char*) posix_malloc(strlen(s) * (size_t) 3 + (size_t) 1);
         if(NULL != buf)
         {
            i = 0;
            while(s[i])
            {
               encode = 0;
               switch(sch)
               {
                  case ENC_URI_SCHEME_HTTP:
                  case ENC_URI_SCHEME_FTP:
                  {
                     /*
                      * Because we don't parser the URI syntax, it is not
                      * possible to decide whether a slash is allowed or not
                      * here => Always accept it.
                      */
                     if('/' == s[i])  { encode = 0; }
                     else if(NULL == strchr(unreserved, (int) s[i]))
                     {
                        encode = 1;
                     }
                     break;
                  }
                  case ENC_URI_SCHEME_MAILTO:
                  {
                     if(' ' == s[i])  { error = 1; }
                     else if('@' == s[i])
                     {
                        if(!commercial_at)
                        {
                           /* Accept zero or one "commercial at" signs */
                           ++commercial_at;
                        }
                        else  { error = 1; }
                     }
                     else if(NULL != strchr(reserved, (int) s[i]))
                     {
                        encode = 1;
                     }
                     break;
                  }
                  case ENC_URI_SCHEME_NEWS:
                  {
                     if(' ' == s[i])  { error = 1; }
                     else if('%' == s[i])  { encode = 1; }
                     else if('@' == s[i])
                     {
                        if(!commercial_at)
                        {
                           /* Accept zero or one "commercial at" signs */
                           ++commercial_at;
                        }
                        else  { error = 1; }
                     }
                     break;
                  }
                  default:
                  {
                     print_error(
                        "ENC: Invalid URI scheme for percent encoding");
                     error = 1;
                     break;
                  }
               }
               if(error)  { break; }
               if(!encode)  { buf[bi++] = s[i]; }
               else
               {
                  /* Percent encoder */
                  buf[bi++] = '%';
                  /* High nibble */
                  nibble = ((unsigned int) s[i] & 0xF0U) >> 4;
                  if(10U > nibble)  { buf[bi] = 0x30; }
                  else  { buf[bi] = 0x41;  nibble -= 10U; }
                  buf[bi++] += (char) nibble;
                  /* Low nibble */
                  nibble = (unsigned int) s[i] & 0x0FU;
                  if(10 > nibble)  { buf[bi] = 0x30; }
                  else  { buf[bi] = 0x41;  nibble -= 10U; }
                  buf[bi++] += (char) nibble;
               }
               ++i;
            }
            /* Terminate result string */
            buf[bi] = 0;
            /* Check for error */
            if(!error)  { res = buf; }
         }
      }
   }
   if(NULL != res)
   {
      if(ENC_URI_SCHEME_MAILTO == sch && NULL == strchr(res, (int) '@'))
      {
         error = 1;
      }
   }
   if(error)
   {
      print_error("ENC: Percent encoding of URI failed");
      posix_free((void*) buf);
      res = NULL;
   }

   return(res);
}


/* ========================================================================== */
/*! \brief Free an object allocated by encoding module
 *
 * Use this function to release dynamic memory that was allocated by the
 * encoding module.
 *
 * \param[in] p  Pointer to object
 *
 * Release the memory for the object pointed to by \e p.
 *
 * \note
 * The pointer \e p is allowed to be \c NULL and no operation is performed in
 * this case.
 */

void  enc_free(void*  p)
{
   /*
    * Attention:
    * Parts of the CORE module (for historical reasons) are still using
    * \c posix_free() to release memory allocated by this module. Until the
    * separation is complete, the memory manager of this module cannot be
    * changed.
    */
   posix_free(p);
}


/* @} */

/* EOF */
