"Fossies" - the Fresh Open Source Software Archive

Member "tin-2.4.2/src/rfc2047.c" (8 Dec 2017, 36759 Bytes) of package /linux/misc/tin-2.4.2.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.1_vs_2.4.2.

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