"Fossies" - the Fresh Open Source Software Archive

Member "tin-2.4.5/src/rfc2047.c" (1 Dec 2020, 37149 Bytes) of package /linux/misc/tin-2.4.5.tar.xz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) C and C++ source code syntax highlighting (style: standard) with prefixed line numbers and code folding option. Alternatively you can here view or download the uninterpreted source code file. For more information about "rfc2047.c" see the Fossies "Dox" file reference documentation and the latest Fossies "Diffs" side-by-side code changes report: 2.4.4_vs_2.4.5.

    1 /*
    2  *  Project   : tin - a Usenet reader
    3  *  Module    : rfc2047.c
    4  *  Author    : Chris Blum <chris@resolution.de>
    5  *  Created   : 1995-09-01
    6  *  Updated   : 2019-07-08
    7  *  Notes     : MIME header encoding/decoding stuff
    8  *
    9  * Copyright (c) 1995-2021 Chris Blum <chris@resolution.de>
   10  * All rights reserved.
   11  *
   12  * Redistribution and use in source and binary forms, with or without
   13  * modification, are permitted provided that the following conditions
   14  * are met:
   15  *
   16  * 1. Redistributions of source code must retain the above copyright notice,
   17  *    this list of conditions and the following disclaimer.
   18  *
   19  * 2. Redistributions in binary form must reproduce the above copyright
   20  *    notice, this list of conditions and the following disclaimer in the
   21  *    documentation and/or other materials provided with the distribution.
   22  *
   23  * 3. Neither the name of the copyright holder nor the names of its
   24  *    contributors may be used to endorse or promote products derived from
   25  *    this software without specific prior written permission.
   26  *
   27  * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
   28  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
   29  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
   30  * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
   31  * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
   32  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
   33  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
   34  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
   35  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
   36  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
   37  * POSSIBILITY OF SUCH DAMAGE.
   38  */
   39 
   40 
   41 #ifndef TIN_H
   42 #   include "tin.h"
   43 #endif /* !TIN_H */
   44 
   45 
   46 #define isreturn(c) ((c) == '\r' || ((c) == '\n'))
   47 
   48 /*
   49  * Modified to return TRUE for '(' and ')' only if
   50  * it's in structured header field. '(' and ')' are
   51  * NOT to be treated differently than other characters
   52  * in unstructured headers like Subject, Keyword and Summary
   53  * c.f. RFC 2047
   54  */
   55 /*
   56  * On some systems isspace(0xa0) returns TRUE (UTF-8 locale).
   57  * 0xa0 can be the second byte of a UTF-8 character and must not be
   58  * treated as whitespace, otherwise Q and B encoding fails.
   59  */
   60 #if 0
   61 #   define isbetween(c, s) (isspace((unsigned char) c) || ((s) && ((c) == '(' || (c) == ')' || (c) == '"')))
   62 #else
   63 #   define my_isspace(c) ((c) == '\t' || (c) == '\n' || (c) == '\v' || (c) == '\f' || (c) == '\r' || (c) == ' ')
   64 #   define isbetween(c, s) (my_isspace(c) || ((s) && ((c) == '(' || (c) == ')' || (c) == '"')))
   65 #endif /* 0 */
   66 #define NOT_RANKED 255
   67 
   68 #if 0
   69     /* inside a quoted word these 7bit chars need to be encoded too */
   70 #   define RFC2047_ESPECIALS "[]<>.;@,=?_\"\\"
   71 #endif /* 0 */
   72 
   73 const char base64_alphabet[64] =
   74 {
   75     'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P',
   76     'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f',
   77     'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v',
   78     'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/'};
   79 
   80 static unsigned char base64_rank[256];
   81 static int base64_rank_table_built;
   82 
   83 /* fixed prefix and default part for tin-generated MIME boundaries */
   84 static const char MIME_BOUNDARY_PREFIX[] = "=_tin=_";
   85 static const char MIME_BOUNDARY_DEFAULT_PART[] = "====____====____====____";
   86 /* required size of a buffer containing a MIME boundary, including the final '\0' */
   87 enum {
   88     MIME_BOUNDARY_SIZE = sizeof(MIME_BOUNDARY_PREFIX) + sizeof(MIME_BOUNDARY_DEFAULT_PART) - 1
   89 };
   90 
   91 /*
   92  * local prototypes
   93  */
   94 static FILE *compose_message_rfc822(FILE *articlefp, t_bool *is_8bit);
   95 static FILE *compose_multipart_mixed(FILE *textfp, FILE *articlefp);
   96 static int do_b_encode(char *w, char *b, size_t max_ewsize, t_bool isstruct_head);
   97 static int sizeofnextword(char *w);
   98 static int which_encoding(char *w);
   99 static t_bool contains_8bit_characters(FILE *fp);
  100 static t_bool contains_nonprintables(char *w, t_bool isstruct_head);
  101 static t_bool contains_string(FILE *fp, const char *str);
  102 static t_bool rfc1522_do_encode(char *what, char **where, const char *charset, t_bool break_long_line);
  103 static t_bool split_mail(const char *filename, FILE **headerfp, FILE **textfp);
  104 static unsigned hex2bin(int x);
  105 static void build_base64_rank_table(void);
  106 static void do_rfc15211522_encode(FILE *f, constext * mime_encoding, struct t_group *group, t_bool allow_8bit_header, t_bool ismail, t_bool contains_headers);
  107 static void generate_mime_boundary(char *boundary, FILE *f, FILE *g);
  108 static void generate_random_mime_boundary(char *boundary, size_t len);
  109 static void str2b64(const char *from, char *to);
  110 
  111 
  112 static void
  113 build_base64_rank_table(
  114     void)
  115 {
  116     int i;
  117 
  118     if (!base64_rank_table_built) {
  119         for (i = 0; i < 256; i++)
  120             base64_rank[i] = NOT_RANKED;
  121         for (i = 0; i < 64; i++)
  122             base64_rank[(int) base64_alphabet[i]] = i;
  123         base64_rank_table_built = TRUE;
  124     }
  125 }
  126 
  127 
  128 static unsigned
  129 hex2bin(
  130     int x)
  131 {
  132     if (x >= '0' && x <= '9')
  133         return (x - '0');
  134     if (x >= 'A' && x <= 'F')
  135         return (x - 'A') + 10;
  136     if (x >= 'a' && x <= 'f')
  137         return (x - 'a') + 10;
  138     return 255;
  139 }
  140 
  141 
  142 /*
  143  * Do B or Q decoding of a chunk of data in 'what' to 'where'
  144  * Return number of bytes decoded into 'where' or -1.
  145  */
  146 int
  147 mmdecode(
  148     const char *what,
  149     int encoding,
  150     int delimiter,
  151     char *where)
  152 {
  153     char *t;
  154 
  155     t = where;
  156     encoding = my_tolower((unsigned char) encoding);
  157     if (encoding == 'q') {      /* quoted-printable */
  158         int x;
  159         unsigned hi, lo;
  160 
  161         if (!what || !where) /* should not happen with 'q'-encoding */
  162             return -1;
  163 
  164         while (*what != delimiter) {
  165             if (*what != '=') {
  166                 if (!delimiter || *what != '_')
  167                     *t++ = *what++;
  168                 else {
  169                     *t++ = ' ';
  170                     what++;
  171                 }
  172                 continue;
  173             }
  174             what++;
  175             if (*what == delimiter)     /* failed */
  176                 return -1;
  177 
  178             x = *what++;
  179             if (x == '\n')
  180                 continue;
  181             if (*what == delimiter)
  182                 return -1;
  183 
  184             hi = hex2bin(x);
  185             lo = hex2bin(*what);
  186             what++;
  187             if (hi == 255 || lo == 255)
  188                 return -1;
  189             x = (hi << 4) + lo;
  190             *EIGHT_BIT(t)++ = x;
  191         }
  192         return t - where;
  193     } else if (encoding == 'b') {       /* base64 */
  194         static unsigned short pattern = 0;
  195         static int bits = 0;
  196         unsigned char x;
  197 
  198         if (!what || !where) {      /* flush */
  199             pattern = 0;
  200             bits = 0;
  201             return 0;
  202         }
  203 
  204         build_base64_rank_table();
  205 
  206         while (*what != delimiter) {
  207             x = base64_rank[(unsigned char) (*what++)];
  208             /* ignore everything not in the alphabet, including '=' */
  209             if (x == NOT_RANKED)
  210                 continue;
  211             pattern <<= 6;
  212             pattern |= x;
  213             bits += 6;
  214             if (bits >= 8) {
  215                 x = (pattern >> (bits - 8)) & 0xff;
  216                 *t++ = x;
  217                 bits -= 8;
  218             }
  219         }
  220         return t - where;
  221     }
  222     return -1;
  223 }
  224 
  225 
  226 /*
  227  * This routine decodes encoded headers in the
  228  * =?charset?encoding?coded text?=
  229  * format
  230  */
  231 char *
  232 rfc1522_decode(
  233     const char *s)
  234 {
  235     char *c, *sc;
  236     const char *d;
  237     char *t;
  238     static char *buffer = NULL;
  239     static int buffer_len = 0;
  240     size_t max_len;
  241     char charset[1024];
  242     char encoding;
  243     t_bool adjacentflag = FALSE;
  244 
  245     if (!s) {
  246         FreeAndNull(buffer);
  247         return NULL;
  248     }
  249 
  250     charset[0] = '\0';
  251     c = my_strdup(s);
  252     max_len = strlen(c) + 1;
  253 
  254     if (!buffer) {
  255         buffer_len = max_len;
  256         buffer = my_malloc((size_t) buffer_len);
  257     } else if (max_len > (size_t) buffer_len) {
  258             buffer_len = max_len;
  259             buffer = my_realloc(buffer, (size_t) buffer_len);
  260     }
  261 
  262     t = buffer;
  263 
  264     /*
  265      * remove non-ASCII chars if MIME_STRICT_CHARSET is set
  266      * must be changed if UTF-8 becomes default charset for headers:
  267      *
  268      * process_charsets(c, len, "UTF-8", tinrc.mm_local_charset, FALSE);
  269      */
  270 #ifndef CHARSET_CONVERSION
  271     process_charsets(&c, &max_len, "US-ASCII", tinrc.mm_local_charset, FALSE);
  272 #else
  273     process_charsets(&c, &max_len, (CURR_GROUP.attribute->undeclared_charset) ? (CURR_GROUP.attribute->undeclared_charset) : "US-ASCII", tinrc.mm_local_charset, FALSE);
  274 #endif /* !CHARSET_CONVERSION */
  275     sc = c;
  276 
  277     while (*c && t - buffer < buffer_len - 1) {
  278         if (*c != '=') {
  279             if (adjacentflag && isspace((unsigned char) *c)) {
  280                 const char *dd;
  281 
  282                 dd = c + 1;
  283                 while (isspace((unsigned char) *dd))
  284                     dd++;
  285                 if (*dd == '=') {       /* brute hack, makes mistakes under certain circumstances comp. 6.2 */
  286                     c++;
  287                     continue;
  288                 }
  289             }
  290             adjacentflag = FALSE;
  291             *t++ = *c++;
  292             continue;
  293         }
  294         d = c++;
  295         if (*c == '?') {
  296             char *e;
  297 
  298             e = charset;
  299             c++;
  300             while (*c && *c != '?') {
  301                 /* skip over optional language tags (RFC2231, RFC5646) */
  302                 if (*c == '*') {
  303                     while (*++c && *c != '?')
  304                         ;
  305                     continue;
  306                 }
  307                 *e++ = *c++;
  308             }
  309             *e = '\0';
  310             if (*c == '?') {
  311                 c++;
  312                 encoding = my_tolower((unsigned char) *c);
  313                 if (encoding == 'b')
  314                     (void) mmdecode(NULL, 'b', 0, NULL);    /* flush */
  315                 c++;
  316                 if (*c == '?') {
  317                     c++;
  318                     if ((e = strchr(c, '?'))) {
  319                         int i;
  320 
  321                         i = mmdecode(c, encoding, '?', t);
  322                         if (i > 0) {
  323                             char *tmpbuf;
  324                             int chars_to_copy;
  325 
  326                             max_len = i + 1;
  327                             tmpbuf = my_malloc(max_len);
  328                             strncpy(tmpbuf, t, i);
  329                             *(tmpbuf + i) = '\0';
  330                             process_charsets(&tmpbuf, &max_len, charset, tinrc.mm_local_charset, FALSE);
  331                             chars_to_copy = strlen(tmpbuf);
  332                             if (chars_to_copy > buffer_len - (t - buffer) - 1)
  333                                 chars_to_copy = buffer_len - (t - buffer) - 1;
  334                             strncpy(t, tmpbuf, chars_to_copy);
  335                             free(tmpbuf);
  336                             t += chars_to_copy;
  337                             e++;
  338                             if (*e == '=')
  339                                 e++;
  340                             d = c = e;
  341                             adjacentflag = TRUE;
  342                         }
  343                     }
  344                 }
  345             }
  346         }
  347         while (d != c && t - buffer < buffer_len - 1)
  348             *t++ = *d++;
  349     }
  350     *t = '\0';
  351     free(sc);
  352 
  353     return buffer;
  354 }
  355 
  356 
  357 /*
  358  * adopted by J. Shin(jshin@pantheon.yale.edu) from
  359  * Woohyung Choi's(whchoi@cosmos.kaist.ac.kr) sdn2ks and ks2sdn
  360  */
  361 static void
  362 str2b64(
  363     const char *from,
  364     char *to)
  365 {
  366     short int i, count;
  367     unsigned long tmp;
  368 
  369     while (*from) {
  370         for (i = count = 0, tmp = 0; i < 3; i++)
  371             if (*from) {
  372                 tmp = (tmp << 8) | (unsigned long) (*from++ & 0x0ff);
  373                 count++;
  374             } else
  375                 tmp = (tmp << 8) | (unsigned long) 0;
  376 
  377         *to++ = base64_alphabet[(0x0fc0000 & tmp) >> 18];
  378         *to++ = base64_alphabet[(0x003f000 & tmp) >> 12];
  379         *to++ = count >= 2 ? base64_alphabet[(0x0000fc0 & tmp) >> 6] : '=';
  380         *to++ = count >= 3 ? base64_alphabet[0x000003f & tmp] : '=';
  381     }
  382 
  383     *to = '\0';
  384 }
  385 
  386 
  387 static int
  388 do_b_encode(
  389     char *w,
  390     char *b,
  391     size_t max_ewsize,
  392     t_bool isstruct_head)
  393 {
  394     char tmp[60];               /* strings to be B encoded */
  395     char *t = tmp;
  396     int count = max_ewsize / 4 * 3;
  397     t_bool isleading_between = TRUE;        /* are we still processing leading space */
  398 
  399 #if defined(MULTIBYTE_ABLE) && !defined(NO_LOCALE)
  400     while (count-- > 0 && (!isbetween(*w, isstruct_head) || isleading_between) && *w) {
  401         if (!isbetween(*w, isstruct_head))
  402             isleading_between = FALSE;
  403         *(t++) = *(w++);
  404         /*
  405          * ensure that the next multi-octet character
  406          * fits into the remaining space
  407          */
  408         if (mbtowc(NULL, w, MB_CUR_MAX) > count)
  409             break;
  410     }
  411 #else
  412     int len8 = 0;               /* the number of trailing 8bit chars, which
  413                                    should be even (i.e. the first and second byte
  414                                    of wide_char should NOT be split into two
  415                                    encoded words) in order to be compatible with
  416                                    some CJK mail client */
  417 
  418     while (count-- > 0 && (!isbetween(*w, isstruct_head) || isleading_between) && *w) {
  419         len8 += (is_EIGHT_BIT(w) ? 1 : -len8);
  420         if (!isbetween(*w, isstruct_head))
  421             isleading_between = FALSE;
  422         *(t++) = *(w++);
  423     }
  424 
  425 /* if (len8 & (unsigned long) 1 && !isbetween(*w,isstruct_head)) */
  426     if (len8 != len8 / 2 * 2 && !isbetween(*w, isstruct_head) && (*w))
  427         t--;
  428 #endif /* MULTIBYTE_ABLE && !NOLOCALE */
  429 
  430     *t = '\0';
  431 
  432     str2b64(tmp, b);
  433 
  434     return t - tmp;
  435 }
  436 
  437 
  438 /*
  439  * find out whether encoding is necessary and which encoding
  440  * to use if necessary by scanning the whole header field
  441  * instead of each fragment of it.
  442  * This will ensure that either Q or B encoding will be used in a single
  443  * header (i.e. two encoding won't be mixed in a single header line).
  444  * Mixing two encodings is not a violation of RFC 2047 but may break
  445  * some news/mail clients.
  446  *
  447  * mmnwcharset is ignored unless CHARSET_CONVERSION
  448  */
  449 static int
  450 which_encoding(
  451     char *w)
  452 {
  453     int chars = 0;
  454     int schars = 0;
  455     int nonprint = 0;
  456 
  457     while (*w && isspace((unsigned char) *w))
  458         w++;
  459     while (*w) {
  460         if (is_EIGHT_BIT(w))
  461             nonprint++;
  462         if (!nonprint && *w == '=' && *(w + 1) == '?')
  463             nonprint = 1;
  464         if (*w == '=' || *w == '?' || *w == '_')
  465             schars++;
  466         chars++;
  467         w++;
  468     }
  469     if (nonprint) {
  470         if (chars + 2 * (nonprint + schars) /* QP size */ >
  471              (chars * 4 + 3) / 3        /* B64 size */)
  472             return 'B';
  473         return 'Q';
  474     }
  475     return 0;
  476 }
  477 
  478 
  479 /* now only checks if there's any 8bit chars in a given "fragment" */
  480 static t_bool
  481 contains_nonprintables(
  482     char *w,
  483     t_bool isstruct_head)
  484 {
  485     t_bool nonprint = FALSE;
  486 
  487     /* first skip all leading whitespaces */
  488     while (*w && isspace((unsigned char) *w))
  489         w++;
  490 
  491     /* then check the next word */
  492     while (!nonprint && *w && !isbetween(*w, isstruct_head)) {
  493         if (is_EIGHT_BIT(w))
  494             nonprint = TRUE;
  495         else if (*w == '=' && *(w + 1) == '?') {
  496             /*
  497              * to be exact we must look for ?= in the same word
  498              * not in the whole string and check ?B? or ?Q? in the word...
  499              * best would be using a regexp like
  500              * ^=\?\S+\?[qQbB]\?\S+\?=
  501              */
  502             if (strstr(w, "?=") != NULL)
  503                 nonprint = TRUE;
  504         }
  505         w++;
  506     }
  507     return nonprint;
  508 }
  509 
  510 
  511 /*
  512  * implement mandatory break-up of long lines in mail messages in accordance
  513  * with rfc 2047 (rfc 1522)
  514  */
  515 static int
  516 sizeofnextword(
  517     char *w)
  518 {
  519     char *x;
  520 
  521     x = w;
  522     while (*x && isspace((unsigned char) *x))
  523         x++;
  524     while (*x && !isspace((unsigned char) *x))
  525         x++;
  526     return x - w;
  527 }
  528 
  529 
  530 static t_bool
  531 rfc1522_do_encode(
  532     char *what,
  533     char **where,
  534     const char *charset,
  535     t_bool break_long_line)
  536 {
  537     /*
  538      * We need to meet several partly contradictional requirements here.
  539      * First of all, a line containing MIME encodings must not be longer
  540      * than 76 chars (including delimiters, charset, encoding). Second,
  541      * we should not encode more than necessary. Third, we should not
  542      * produce more overhead than absolutely necessary; this means we
  543      * should extend chunks over several words if there are more
  544      * characters-to-quote to come. This means we have to rely on some
  545      * heuristics. We process whole words, checking if it contains
  546      * characters to be quoted. If not, the word is output 'as is',
  547      * previous quoting being terminated before. If two adjoining words
  548      * contain non-printable characters, they are encoded together (up
  549      * to 60 characters). If a resulting encoded word would break the
  550      * 76 characters boundary, we 'break' the line, output a SPACE, then
  551      * output the encoded word. Note that many wide-spread news applications,
  552      * notably INN's xover support, does not understand multiple-lines,
  553      * so it's a compile-time feature with default off.
  554      *
  555      * To make things a bit easier, we do all processing in two stages;
  556      * first we build all encoded words without any bells and whistles
  557      * (just checking that they don get longer than 76 characters),
  558      * then, in a second pass, we replace all SPACEs inside encoded
  559      * words by '_', break long lines, etc.
  560      */
  561     char *buffer;               /* buffer for encoded stuff */
  562     char *c;
  563     char *t;
  564     char buf2[80];              /* buffer for this and that */
  565     int encoding;               /* which encoding to use ('B' or 'Q') */
  566     size_t ew_taken_len;
  567     int word_cnt = 0;
  568     int offset;
  569     size_t bufferlen = 2048;        /* size of buffer */
  570     size_t ewsize = 0;          /* size of current encoded-word */
  571     t_bool quoting = FALSE;     /* currently inside quote block? */
  572     t_bool any_quoting_done = FALSE;
  573     t_bool isbroken_within = FALSE; /* is word broken due to length restriction on encoded of word? */
  574     t_bool isstruct_head = FALSE;       /* are we dealing with structured header? */
  575     t_bool rightafter_ew = FALSE;
  576 /*
  577  * the list of structured header fields where '(' and ')' are
  578  * treated specially in rfc 1522 encoding
  579  */
  580     static const char *struct_header[] = {
  581         "Approved: ", "From: ", "Originator: ",
  582         "Reply-To: ", "Sender: ", "X-Cancelled-By: ", "X-Comment-To: ",
  583         "X-Submissions-To: ", "To: ", "Cc: ", "Bcc: ", "X-Originator: ", NULL };
  584     const char **strptr = struct_header;
  585 
  586     do {
  587         if (!strncasecmp(what, *strptr, strlen(*strptr))) {
  588             isstruct_head = TRUE;
  589             break;
  590         }
  591     } while (*(++strptr) != NULL);
  592 
  593     t = buffer = my_malloc(bufferlen);
  594     encoding = which_encoding(what);
  595     ew_taken_len = strlen(charset) + 7 /* =?c?E?d?= */;
  596     while (*what) {
  597         if (break_long_line)
  598             word_cnt++;
  599         /*
  600          * if a word with 8bit chars is broken in the middle, whatever
  601          * follows after the point where it's split should be encoded (i.e.
  602          * even if they are made of only 7bit chars)
  603          */
  604         if (contains_nonprintables(what, isstruct_head) || isbroken_within) {
  605             if (encoding == 'Q') {
  606                 if (!quoting) {
  607                     snprintf(buf2, sizeof(buf2), "=?%s?%c?", charset, encoding);
  608                     while (t - buffer + strlen(buf2) >= bufferlen) {
  609                         /* buffer too small, double its size */
  610                         offset = t - buffer;
  611                         bufferlen <<= 1;
  612                         buffer = my_realloc(buffer, bufferlen * sizeof(*buffer));
  613                         t = buffer + offset;
  614                     }
  615                     ewsize = mystrcat(&t, buf2);
  616                     if (break_long_line) {
  617                         if (word_cnt == 2) {
  618                             /*
  619                              * Make sure we fit the first encoded
  620                              * word in with the header keyword,
  621                              * since we cannot break the line
  622                              * directly after the keyword.
  623                              */
  624                             ewsize = t - buffer;
  625                         }
  626                     }
  627                     quoting = TRUE;
  628                     any_quoting_done = TRUE;
  629                 }
  630                 isbroken_within = FALSE;
  631                 while (*what && !isbetween(*what, isstruct_head)) {
  632 #if 0
  633                     if (is_EIGHT_BIT(what) || (strchr(RFC2047_ESPECIALS, *what)))
  634 #else
  635                     if (is_EIGHT_BIT(what) || !isalnum((int)(unsigned char) *what))
  636 #endif /* 0 */
  637                     {
  638                         snprintf(buf2, sizeof(buf2), "=%2.2X", *EIGHT_BIT(what));
  639                         if ((size_t)(t - buffer + 3) >= bufferlen) {
  640                             /* buffer too small, double its size */
  641                             offset = t - buffer;
  642                             bufferlen <<= 1;
  643                             buffer = my_realloc(buffer, bufferlen * sizeof(*buffer));
  644                             t = buffer + offset;
  645                         }
  646                         *t++ = buf2[0];
  647                         *t++ = buf2[1];
  648                         *t++ = buf2[2];
  649                         ewsize += 3;
  650                     } else {
  651                         if ((size_t) (t - buffer + 1) >= bufferlen) {
  652                             /* buffer too small, double its size */
  653                             offset = t - buffer;
  654                             bufferlen <<= 1;
  655                             buffer = my_realloc(buffer, bufferlen * sizeof(*buffer));
  656                             t = buffer + offset;
  657                         }
  658                         *t++ = *what;
  659                         ewsize++;
  660                     }
  661                     what++;
  662                     /*
  663                      * Be sure to encode at least one char, even if
  664                      * that overflows the line limit, otherwise, we
  665                      * will be stuck in a loop (if this were in the
  666                      * while condition above). (Can only happen in
  667                      * the first line, if we have a very long
  668                      * header keyword, I think).
  669                      */
  670                     if (ewsize >= 71) {
  671                         isbroken_within = TRUE;
  672                         break;
  673                     }
  674                 }
  675                 if (!contains_nonprintables(what, isstruct_head) || ewsize >= 70 - strlen(charset)) {
  676                     /* next word is 'clean', close encoding */
  677                     if ((size_t) (t - buffer + 2) >= bufferlen) {
  678                         /* buffer too small, double its size */
  679                         offset = t - buffer;
  680                         bufferlen <<= 1;
  681                         buffer = my_realloc(buffer, bufferlen * sizeof(*buffer));
  682                         t = buffer + offset;
  683                     }
  684                     *t++ = '?';
  685                     *t++ = '=';
  686                     ewsize += 2;
  687                     /*
  688                      */
  689                     if (ewsize >= 70 - strlen(charset) && (contains_nonprintables(what, isstruct_head) || isbroken_within)) {
  690                         if ((size_t) (t - buffer + 1) >= bufferlen) {
  691                             /* buffer too small, double its size */
  692                             offset = t - buffer;
  693                             bufferlen <<= 1;
  694                             buffer = my_realloc(buffer, bufferlen * sizeof(*buffer));
  695                             t = buffer + offset;
  696                         }
  697                         *t++ = ' ';
  698                         ewsize++;
  699                     }
  700                     quoting = FALSE;
  701                 } else {
  702                     /* process whitespace in-between by quoting it properly */
  703                     while (*what && isspace((unsigned char) *what)) {
  704                         if ((size_t) (t - buffer + 3) >= bufferlen) {
  705                             /* buffer probably too small, double its size */
  706                             offset = t - buffer;
  707                             bufferlen <<= 1;
  708                             buffer = my_realloc(buffer, bufferlen * sizeof(*buffer));
  709                             t = buffer + offset;
  710                         }
  711                         if (*what == 32 /* not ' ', compare chapter 4! */ ) {
  712                             *t++ = '_';
  713                             ewsize++;
  714                         } else {
  715                             snprintf(buf2, sizeof(buf2), "=%2.2X", *EIGHT_BIT(what));
  716                             *t++ = buf2[0];
  717                             *t++ = buf2[1];
  718                             *t++ = buf2[2];
  719                             ewsize += 3;
  720                         }
  721                         what++;
  722                     }                   /* end of while */
  723                 }                       /* end of else */
  724             } else {                    /* end of Q encoding and beg. of B encoding */
  725                 /*
  726                  * if what immediately precedes the current fragment with 8bit
  727                  * char is encoded word, the leading spaces should be encoded
  728                  * together with 8bit chars following them. No need to worry
  729                  * about '(',')' and '"' as they're already excluded with
  730                  * contain_nonprintables used in outer if-clause
  731                  */
  732                 while (*what && (!isbetween(*what, isstruct_head) || rightafter_ew)) {
  733                     snprintf(buf2, sizeof(buf2), "=?%s?%c?", charset, encoding);
  734                     while (t - buffer + strlen(buf2) >= bufferlen) {
  735                         /* buffer too small, double its size */
  736                         offset = t - buffer;
  737                         bufferlen <<= 1;
  738                         buffer = my_realloc(buffer, bufferlen * sizeof(*buffer));
  739                         t = buffer + offset;
  740                     }
  741                     ewsize = mystrcat(&t, buf2);
  742 
  743                     if (word_cnt == 2)
  744                         ewsize = t - buffer;
  745                     what += do_b_encode(what, buf2, 75 - ew_taken_len, isstruct_head);
  746                     while (t - buffer + strlen(buf2) + 3 >= bufferlen) {
  747                         /* buffer too small, double its size */
  748                         offset = t - buffer;
  749                         bufferlen <<= 1;
  750                         buffer = my_realloc(buffer, bufferlen * sizeof(*buffer));
  751                         t = buffer + offset;
  752                     }
  753                     ewsize += mystrcat(&t, buf2);
  754                     *t++ = '?';
  755                     *t++ = '=';
  756                     *t++ = ' ';
  757                     ewsize += 3;
  758                     if (break_long_line)
  759                         word_cnt++;
  760                     rightafter_ew = FALSE;
  761                     any_quoting_done = TRUE;
  762                 }
  763                 rightafter_ew = TRUE;
  764                 word_cnt--;     /* compensate double counting */
  765                 /*
  766                  * if encoded word is followed by 7bit-only fragment, we need to
  767                  * eliminate ' ' inserted in while-block above
  768                  */
  769                 if (!contains_nonprintables(what, isstruct_head)) {
  770                     t--;
  771                     ewsize--;
  772                 }
  773             }       /* end of B encoding */
  774         } else {
  775             while (*what && !isbetween(*what, isstruct_head)) {
  776                 if ((size_t) (t - buffer + 1) >= bufferlen) {
  777                     /* buffer too small, double its size */
  778                     offset = t - buffer;
  779                     bufferlen <<= 1;
  780                     buffer = my_realloc(buffer, bufferlen * sizeof(*buffer));
  781                     t = buffer + offset;
  782                 }
  783                 *t++ = *what++;     /* output word unencoded */
  784             }
  785             while (*what && isbetween(*what, isstruct_head)) {
  786                 if ((size_t) (t - buffer + 1) >= bufferlen) {
  787                     /* buffer too small, double its size */
  788                     offset = t - buffer;
  789                     bufferlen <<= 1;
  790                     buffer = my_realloc(buffer, bufferlen * sizeof(*buffer));
  791                     t = buffer + offset;
  792                 }
  793                 *t++ = *what++;     /* output trailing whitespace unencoded */
  794             }
  795             rightafter_ew = FALSE;
  796         }
  797     }       /* end of pass 1 while loop */
  798     *t = '\0';
  799 
  800     /* Pass 2: break long lines if there are MIME-sequences in the result */
  801     c = buffer;
  802     if (break_long_line && any_quoting_done) {
  803         char *new_buffer;
  804         size_t new_bufferlen = strlen(buffer) * 2 + 1; /* maximum length if
  805 every "word" were a space ... */
  806         int column = 0;             /* current column */
  807 
  808         new_buffer = my_malloc(new_bufferlen);
  809         t = new_buffer;
  810         word_cnt = 1;           /*
  811                          * note, if the user has typed a continuation
  812                          * line, we will consider the initial
  813                          * whitespace to be delimiting word one (well,
  814                          * just assume an empty word).
  815                          */
  816         while (*c) {
  817             if (isspace((unsigned char) *c)) {
  818                 /*
  819                  * According to rfc1522, header lines containing encoded
  820                  * words are limited to 76 chars, but if the first line is
  821                  * too long (due to a long header keyword), we cannot stick
  822                  * to that, since we would break the line directly after the
  823                  * keyword's colon, which is not allowed. The same is
  824                  * necessary for a continuation line with an unencoded word
  825                  * that is too long.
  826                  */
  827                 if (sizeofnextword(c) + column > 76 && word_cnt != 1) {
  828                     *t++ = '\n';
  829                     column = 0;
  830                 }
  831                 if (c > buffer && !isspace((unsigned char) *(c - 1)))
  832                     word_cnt++;
  833                 *t++ = *c++;
  834                 column++;
  835             } else
  836                 while (*c && !isspace((unsigned char) *c)) {
  837                     *t++ = *c++;
  838                     column++;
  839                 }
  840         }
  841         FreeIfNeeded(buffer);
  842         buffer = new_buffer;
  843     }
  844     *t = '\0';
  845     *where = buffer;
  846     return any_quoting_done;
  847 }
  848 
  849 
  850 /*
  851  * calling code must free() the result if it's no longer needed
  852  */
  853 char *
  854 rfc1522_encode(
  855     char *s,
  856     const char *charset,
  857     t_bool ismail)
  858 {
  859     char *buf;
  860 
  861     /*
  862      * break_long_line is FALSE for news posting unless
  863      * MIME_BREAK_LONG_LINES is defined, but it's TRUE for mail messages
  864      * regardless of whether or not MIME_BREAK_LONG_LINES is defined
  865      */
  866 #ifdef MIME_BREAK_LONG_LINES
  867     t_bool break_long_line = TRUE;
  868 #else
  869     /*
  870      * Even if MIME_BREAK_LONG_LINES is NOT defined, long headers in mail
  871      * messages should be broken up in accordance with RFC 2047(1522)
  872      */
  873     t_bool break_long_line = ismail;
  874 #endif /* MIME_BREAK_LONG_LINES */
  875 
  876     rfc1522_do_encode(s, &buf, charset, break_long_line);
  877 
  878     return buf;
  879 }
  880 
  881 
  882 /*
  883  * Helper function doing the hard work for rfc15211522_encode().
  884  * Code moved from rfc15211522_encode(), with some adjustments to work on a
  885  * file specified by a FILE* instead of a filename.
  886  */
  887 static void
  888 do_rfc15211522_encode(
  889     FILE *f,
  890     constext * mime_encoding,
  891     struct t_group *group,
  892     t_bool allow_8bit_header,
  893     t_bool ismail,
  894     t_bool contains_headers)
  895 {
  896     FILE *g;
  897     char *c;
  898     char *header;
  899     char encoding;
  900     char buffer[2048];
  901     t_bool mime_headers_needed = FALSE;
  902     BodyPtr body_encode;
  903     int i;
  904 #ifdef CHARSET_CONVERSION
  905     int mmnwcharset;
  906 
  907     if (group) /* Posting */
  908         mmnwcharset = group->attribute->mm_network_charset;
  909     else /* E-Mail */
  910         mmnwcharset = tinrc.mm_network_charset;
  911 #endif /* CHARSET_CONVERSION */
  912 
  913     if ((g = tmpfile()) == NULL)
  914         return;
  915 
  916     while (contains_headers && (header = tin_fgets(f, TRUE))) {
  917 #ifdef CHARSET_CONVERSION
  918         buffer_to_network(header, mmnwcharset);
  919 #endif /* CHARSET_CONVERSION */
  920         if (*header == '\0')
  921             break;
  922 
  923         /*
  924          * TODO: - what about 8bit chars in the mentioned headers
  925          *         when !allow_8bit_header?
  926          *       - what about lines longer 998 octets?
  927          */
  928         if (allow_8bit_header || (!strncasecmp(header, "References: ", 12) || !strncasecmp(header, "Message-ID: ", 12) || !strncasecmp(header, "Date: ", 6) || !strncasecmp(header, "Newsgroups: ", 12) || !strncasecmp(header, "Distribution: ", 14) || !strncasecmp(header, "Followup-To: ", 13) || !strncasecmp(header, "X-Face: ", 8) || !strncasecmp(header, "Cancel-Lock: ", 13) || !strncasecmp(header, "Cancel-Key: ", 12)))
  929             fputs(header, g);
  930         else {
  931             char *p;
  932 
  933 #ifdef CHARSET_CONVERSION
  934             p = rfc1522_encode(header, txt_mime_charsets[mmnwcharset], ismail);
  935 #else
  936             p = rfc1522_encode(header, tinrc.mm_charset, ismail);
  937 #endif /* CHARSET_CONVERSION */
  938 
  939             fputs(p, g);
  940             free(p);
  941         }
  942         fputc('\n', g);
  943     }
  944 
  945     fputc('\n', g);
  946 
  947     while (fgets(buffer, 2048, f)) {
  948 #ifdef CHARSET_CONVERSION
  949         buffer_to_network(buffer, mmnwcharset);
  950 #endif /* CHARSET_CONVERSION */
  951         fputs(buffer, g);
  952         if (!allow_8bit_header) {
  953             /* see if there are any 8bit chars in the body... */
  954             for (c = buffer; *c && !isreturn(*c); c++) {
  955                 if (is_EIGHT_BIT(c)) {
  956                     mime_headers_needed = TRUE;
  957                     break;
  958                 }
  959             }
  960         }
  961     }
  962 
  963     rewind(g);
  964     rewind(f);
  965 #ifdef HAVE_FTRUNCATE
  966     (void) ftruncate(fileno(f), 0);
  967 #endif /* HAVE_FTRUNCATE */
  968 
  969     /* copy header */
  970     while (fgets(buffer, 2048, g) && !isreturn(buffer[0]))
  971         fputs(buffer, f);
  972 
  973     if (!allow_8bit_header) {
  974         /*
  975          * 7bit charsets except US-ASCII also need mime headers
  976          */
  977         for (i = 1; txt_mime_7bit_charsets[i] != NULL; i++) {
  978 #ifdef CHARSET_CONVERSION
  979             if (!strcasecmp(txt_mime_charsets[mmnwcharset], txt_mime_7bit_charsets[i])) {
  980                 mime_headers_needed = TRUE;
  981                 break;
  982             }
  983 #else
  984             if (!strcasecmp(tinrc.mm_charset, txt_mime_7bit_charsets[i])) {
  985                 mime_headers_needed = TRUE;
  986                 break;
  987             }
  988 #endif /* CHARSET_CONVERSION */
  989         }
  990 
  991         /*
  992          * now add MIME headers as necessary
  993          */
  994         if (mime_headers_needed) {
  995             if (contains_headers)
  996                 fprintf(f, "MIME-Version: %s\n", MIME_SUPPORTED_VERSION);
  997 #ifdef CHARSET_CONVERSION
  998             fprintf(f, "Content-Type: text/plain; charset=%s\n", txt_mime_charsets[mmnwcharset]);
  999 #else
 1000             fprintf(f, "Content-Type: text/plain; charset=%s\n", tinrc.mm_charset);
 1001 #endif /* CHARSET_CONVERSION */
 1002             fprintf(f, "Content-Transfer-Encoding: %s\n", mime_encoding);
 1003         }
 1004     }
 1005     fputc('\n', f);
 1006 
 1007     if (!allow_8bit_header) {
 1008         if (!strcasecmp(mime_encoding, txt_base64))
 1009             encoding = 'b';
 1010         else if (!strcasecmp(mime_encoding, txt_quoted_printable))
 1011             encoding = 'q';
 1012         else if (!strcasecmp(mime_encoding, txt_7bit))
 1013             encoding = '7';
 1014         else
 1015             encoding = '8';
 1016 
 1017         /* avoid break of long lines for US-ASCII/quoted-printable */
 1018         if (!mime_headers_needed)
 1019             encoding = '8';
 1020 
 1021         body_encode = rfc1521_encode;
 1022 
 1023         while (fgets(buffer, 2048, g))
 1024             body_encode(buffer, f, encoding);
 1025 
 1026         if (encoding == 'b' || encoding == 'q' || encoding == '7')
 1027             body_encode(NULL, f, encoding); /* flush */
 1028     } else {
 1029         while (fgets(buffer, 2048, g))
 1030             fputs(buffer, f);
 1031     }
 1032 
 1033     fclose(g);
 1034 }
 1035 
 1036 
 1037 void
 1038 rfc15211522_encode(
 1039     const char *filename,
 1040     constext * mime_encoding,
 1041     struct t_group *group,
 1042     t_bool allow_8bit_header,
 1043     t_bool ismail)
 1044 {
 1045     FILE *fp;
 1046 
 1047     if ((fp = fopen(filename, "r+")) == NULL)
 1048         return;
 1049 
 1050     do_rfc15211522_encode(fp, mime_encoding, group, allow_8bit_header, ismail, TRUE);
 1051 
 1052     fclose(fp);
 1053 }
 1054 
 1055 
 1056 /*
 1057  * Generate a MIME boundary being unique with high probability, consisting
 1058  * of len - 1 random characters.
 1059  * This function is used as a last resort if anything else failed to
 1060  * generate a truly unique boundary.
 1061  */
 1062 static void
 1063 generate_random_mime_boundary(
 1064     char *boundary,
 1065     size_t len)
 1066 {
 1067     size_t i;
 1068 
 1069     srand((unsigned int) time(NULL));
 1070     for (i = 0; i < len - 1; i++)
 1071         boundary[i] = base64_alphabet[rand() % sizeof(base64_alphabet)];
 1072     boundary[len - 1] = '\0';
 1073 }
 1074 
 1075 
 1076 /*
 1077  * Generate a unique MIME boundary.
 1078  * boundary must have enough space for at least MIME_BOUNDARY_SIZE characters.
 1079  */
 1080 static void
 1081 generate_mime_boundary(
 1082     char *boundary,
 1083     FILE *f,
 1084     FILE *g)
 1085 {
 1086     const char nice_chars[] = { '-', '_', '=' };
 1087     const size_t prefix_len = sizeof(MIME_BOUNDARY_PREFIX) - 1;
 1088     char *s;
 1089     size_t i = 0;
 1090     t_bool unique = FALSE;
 1091 
 1092     /*
 1093      * Choose MIME boundary as follows:
 1094      *   - Always start with MIME_BOUNDARY_PREFIX.
 1095      *   - Append MIME_BOUNDARY_DEFAULT_PART.
 1096      *   - If necessary, change it from right to left, choosing from a set of
 1097      *     `nice_chars' characters.
 1098      *   - After that, if it is still not unique, replace MIME_BOUNDARY_DEFAULT_PART
 1099      *     with random characters and hope the best.
 1100      */
 1101 
 1102     strcpy(boundary, MIME_BOUNDARY_PREFIX);
 1103     strcat(boundary, MIME_BOUNDARY_DEFAULT_PART);
 1104 
 1105     s = boundary + MIME_BOUNDARY_SIZE - 2; /* set s to last character before '\0' */
 1106     do {
 1107         /*
 1108          * Scan for entire boundary in both f and g.
 1109          * When found: modify and redo.
 1110          */
 1111         if (contains_string(f, boundary) || contains_string(g, boundary)) {
 1112             *s = nice_chars[i];
 1113             if ((i = (i + 1) % sizeof(nice_chars)) == 0)
 1114                 --s;
 1115         } else
 1116             unique = TRUE;
 1117     } while (!unique && s >= boundary + prefix_len);
 1118 
 1119     if (!unique)
 1120         generate_random_mime_boundary(boundary + prefix_len, sizeof(MIME_BOUNDARY_DEFAULT_PART));
 1121 }
 1122 
 1123 
 1124 /*
 1125  * Split mail into header and (optionally) body.
 1126  *
 1127  * If textfp is not NULL, everything behind the header is stored in it.
 1128  * Whenever an error is encountered, all files are closed and FALSE is returned.
 1129  */
 1130 static t_bool
 1131 split_mail(
 1132     const char *filename,
 1133     FILE **headerfp,
 1134     FILE **textfp)
 1135 {
 1136     FILE *fp;
 1137     char *line;
 1138 
 1139     if ((fp = fopen(filename, "r")) == NULL)
 1140         return FALSE;
 1141 
 1142     /* Header */
 1143     if ((*headerfp = tmpfile()) == NULL) {
 1144         fclose(fp);
 1145         return FALSE;
 1146     }
 1147 
 1148     while ((line = tin_fgets(fp, TRUE))) {
 1149         if (*line == '\0')
 1150             break;
 1151         else
 1152             fprintf(*headerfp, "%s\n", line);
 1153     }
 1154 
 1155     /* Body */
 1156     if (textfp != NULL) {
 1157         if ((*textfp = tmpfile()) == NULL) {
 1158             fclose(fp);
 1159             fclose(*headerfp);
 1160             return FALSE;
 1161         }
 1162 
 1163         while ((line = tin_fgets(fp, FALSE)))
 1164             fprintf(*textfp, "%s\n", line);
 1165     }
 1166 
 1167     fclose(fp);
 1168     return TRUE;
 1169 }
 1170 
 1171 
 1172 /*
 1173  * Compose a mail consisting of a sole text/plain MIME part.
 1174  */
 1175 void
 1176 compose_mail_text_plain(
 1177     const char *filename,
 1178     struct t_group *group)
 1179 {
 1180     rfc15211522_encode(filename, txt_mime_encodings[(group ? group->attribute->mail_mime_encoding : tinrc.mail_mime_encoding)], group, (group ? group->attribute->mail_8bit_header : tinrc.mail_8bit_header), TRUE);
 1181 }
 1182 
 1183 
 1184 /*
 1185  * Compose a mail consisting of an optional text/plain and a message/rfc822
 1186  * part.
 1187  *
 1188  * At this point, the file denoted by `filename' contains some common headers
 1189  * and any text the user entered. The file `articlefp' contains the forwarded
 1190  * article in raw form.
 1191  */
 1192 void
 1193 compose_mail_mime_forwarded(
 1194     const char *filename,
 1195     FILE *articlefp,
 1196     t_bool include_text,
 1197     struct t_group *group)
 1198 {
 1199     FILE *fp;
 1200     FILE *headerfp;
 1201     FILE *textfp = NULL;
 1202     FILE *entityfp;
 1203     char *line;
 1204     constext* encoding = txt_mime_encodings[(group ? group->attribute->mail_mime_encoding : tinrc.mail_mime_encoding)];
 1205     t_bool allow_8bit_header = (group ? group->attribute->mail_8bit_header : tinrc.mail_8bit_header);
 1206     t_bool _8bit;
 1207 
 1208     /* Split mail into headers and text */
 1209     if (!split_mail(filename, &headerfp, include_text ? &textfp : NULL))
 1210         return;
 1211 
 1212     /* Encode header and text */
 1213     rewind(headerfp);
 1214     do_rfc15211522_encode(headerfp, encoding, group, allow_8bit_header, TRUE, TRUE);
 1215 
 1216     if (textfp) {
 1217         rewind(textfp);
 1218         do_rfc15211522_encode(textfp, encoding, group, allow_8bit_header, TRUE, FALSE);
 1219         entityfp = compose_multipart_mixed(textfp, articlefp);  /* Compose top-level MIME entity */
 1220     } else
 1221         entityfp = compose_message_rfc822(articlefp, &_8bit);
 1222 
 1223     if (entityfp == NULL) {
 1224         fclose(headerfp);
 1225         if (textfp)
 1226             fclose(textfp);
 1227         return;
 1228     }
 1229 
 1230     if ((fp = fopen(filename, "w")) == NULL) {
 1231         fclose(headerfp);
 1232         fclose(entityfp);
 1233         if (textfp)
 1234             fclose(textfp);
 1235         return;
 1236     }
 1237 
 1238     /* Put it all together */
 1239     rewind(headerfp);
 1240     while ((line = tin_fgets(headerfp, TRUE))) {
 1241         if (*line != '\0')
 1242             fprintf(fp, "%s\n", line);
 1243     }
 1244     fprintf(fp, "MIME-Version: %s\n", MIME_SUPPORTED_VERSION);
 1245     rewind(entityfp);
 1246     copy_fp(entityfp, fp);
 1247 
 1248     /* Clean up */
 1249     fclose(fp);
 1250     fclose(headerfp);
 1251     fclose(entityfp);
 1252     if (textfp)
 1253         fclose(textfp);
 1254 }
 1255 
 1256 
 1257 /*
 1258  * Compose a message/rfc822 MIME entity containing articlefp.
 1259  */
 1260 static FILE *
 1261 compose_message_rfc822(
 1262     FILE *articlefp,
 1263     t_bool *is_8bit)
 1264 {
 1265     FILE *fp;
 1266 
 1267     if ((fp = tmpfile()) == NULL)
 1268         return NULL;
 1269 
 1270     *is_8bit = contains_8bit_characters(articlefp);
 1271 
 1272     /* Header: CT, CD, CTE */
 1273     fprintf(fp, "Content-Type: message/rfc822\n");
 1274     fprintf(fp, "Content-Disposition: inline\n");
 1275     fprintf(fp, "Content-Transfer-Encoding: %s\n", *is_8bit ? txt_8bit : txt_7bit);
 1276     fputc('\n', fp);
 1277 
 1278     /* Body: articlefp */
 1279     rewind(articlefp);
 1280     copy_fp(articlefp, fp);
 1281 
 1282     return fp;
 1283 }
 1284 
 1285 
 1286 /*
 1287  * Compose a multipart/mixed MIME entity consisting of a text/plain and a
 1288  * message/rfc822 part.
 1289  */
 1290 static FILE *
 1291 compose_multipart_mixed(
 1292     FILE *textfp,
 1293     FILE *articlefp)
 1294 {
 1295     FILE *fp;
 1296     FILE *messagefp;
 1297     char boundary[MIME_BOUNDARY_SIZE];
 1298     t_bool requires_8bit;
 1299 
 1300     if ((fp = tmpfile()) == NULL)
 1301         return NULL;
 1302 
 1303     /* First compose message/rfc822 part (needed for choosing the appropriate CTE) */
 1304     if ((messagefp = compose_message_rfc822(articlefp, &requires_8bit)) == NULL) {
 1305         fclose(fp);
 1306         return NULL;
 1307     }
 1308 
 1309     requires_8bit = (requires_8bit || contains_8bit_characters(textfp));
 1310 
 1311     /*
 1312      * Header: CT with multipart boundary, CTE
 1313      * TODO: -> lang.c
 1314      */
 1315     generate_mime_boundary(boundary, textfp, articlefp);
 1316     fprintf(fp, "Content-Type: multipart/mixed; boundary=\"%s\"\n", boundary);
 1317     fprintf(fp, "Content-Transfer-Encoding: %s\n\n", requires_8bit ? txt_8bit : txt_7bit);
 1318 
 1319     /*
 1320      * preamble
 1321      * TODO: -> lang.c
 1322      */
 1323     fprintf(fp, _("This message has been composed in the 'multipart/mixed' MIME-format. If you\n\
 1324 are reading this prefix, your mail reader probably has not yet been modified\n\
 1325 to understand the new format, and some of what follows may look strange.\n\n"));
 1326 
 1327     /*
 1328      * Body: boundary+text, message/rfc822 part, closing boundary
 1329      */
 1330     /* text */
 1331     fprintf(fp, "--%s\n", boundary);
 1332     rewind(textfp);
 1333     copy_fp(textfp, fp);
 1334     fputc('\n', fp);
 1335 
 1336     /* message/rfc822 part */
 1337     fprintf(fp, "--%s\n", boundary);
 1338     rewind(messagefp);
 1339     copy_fp(messagefp, fp);
 1340     fclose(messagefp);
 1341     fputc('\n', fp);
 1342 
 1343     /* closing boundary */
 1344     fprintf(fp, "--%s--\n", boundary);
 1345     /* TODO: insert an epilogue here? */
 1346     return fp;
 1347 }
 1348 
 1349 
 1350 /*
 1351  * Determines whether the file denoted by fp contains 8bit characters.
 1352  */
 1353 static t_bool
 1354 contains_8bit_characters(
 1355     FILE *fp)
 1356 {
 1357     char *line;
 1358 
 1359     rewind(fp);
 1360     while ((line = tin_fgets(fp, FALSE))) {
 1361         for (; *line != '\0'; line++) {
 1362             if (is_EIGHT_BIT(line))
 1363                 return TRUE;
 1364         }
 1365     }
 1366 
 1367     return FALSE;
 1368 }
 1369 
 1370 
 1371 /*
 1372  * Determines whether any line of the file denoted by fp contains str.
 1373  */
 1374 static t_bool
 1375 contains_string(
 1376     FILE *fp,
 1377     const char *str)
 1378 {
 1379     char *line;
 1380 
 1381     rewind(fp);
 1382     while ((line = tin_fgets(fp, FALSE))) {
 1383         if (strstr(line, str))
 1384             return TRUE;
 1385     }
 1386 
 1387     return FALSE;
 1388 }