"Fossies" - the Fresh Open Source Software Archive

Member "tin-2.4.1/src/rfc2047.c" (12 Oct 2016, 36755 Bytes) of package /linux/misc/tin-2.4.1.tar.gz:


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.0_vs_2.4.1.

    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   : 2016-03-10
    7  *  Notes     : MIME header encoding/decoding stuff
    8  *
    9  * Copyright (c) 1995-2017 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) {
  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     return;
  368 }
  369 
  370 
  371 static int
  372 do_b_encode(
  373     char *w,
  374     char *b,
  375     size_t max_ewsize,
  376     t_bool isstruct_head)
  377 {
  378     char tmp[60];               /* strings to be B encoded */
  379     char *t = tmp;
  380     int count = max_ewsize / 4 * 3;
  381     t_bool isleading_between = TRUE;        /* are we still processing leading space */
  382 
  383 #if defined(MULTIBYTE_ABLE) && !defined(NO_LOCALE)
  384     while (count-- > 0 && (!isbetween(*w, isstruct_head) || isleading_between) && *w) {
  385         if (!isbetween(*w, isstruct_head))
  386             isleading_between = FALSE;
  387         *(t++) = *(w++);
  388         /*
  389          * ensure that the next multi-octet character
  390          * fits into the remaining space
  391          */
  392         if (mbtowc(NULL, w, MB_CUR_MAX) > count)
  393             break;
  394     }
  395 #else
  396     int len8 = 0;               /* the number of trailing 8bit chars, which
  397                                    should be even (i.e. the first and second byte
  398                                    of wide_char should NOT be split into two
  399                                    encoded words) in order to be compatible with
  400                                    some CJK mail client */
  401 
  402     while (count-- > 0 && (!isbetween(*w, isstruct_head) || isleading_between) && *w) {
  403         len8 += (is_EIGHT_BIT(w) ? 1 : -len8);
  404         if (!isbetween(*w, isstruct_head))
  405             isleading_between = FALSE;
  406         *(t++) = *(w++);
  407     }
  408 
  409 /* if (len8 & (unsigned long) 1 && !isbetween(*w,isstruct_head)) */
  410     if (len8 != len8 / 2 * 2 && !isbetween(*w, isstruct_head) && (*w))
  411         t--;
  412 #endif /* MULTIBYTE_ABLE && !NOLOCALE */
  413 
  414     *t = '\0';
  415 
  416     str2b64(tmp, b);
  417 
  418     return t - tmp;
  419 }
  420 
  421 
  422 /*
  423  * find out whether encoding is necessary and which encoding
  424  * to use if necessary by scanning the whole header field
  425  * instead of each fragment of it.
  426  * This will ensure that either Q or B encoding will be used in a single
  427  * header (i.e. two encoding won't be mixed in a single header line).
  428  * Mixing two encodings is not a violation of RFC 2047 but may break
  429  * some news/mail clients.
  430  *
  431  * mmnwcharset is ignored unless CHARSET_CONVERSION
  432  */
  433 static int
  434 which_encoding(
  435     char *w)
  436 {
  437     int chars = 0;
  438     int schars = 0;
  439     int nonprint = 0;
  440 
  441     while (*w && isspace((unsigned char) *w))
  442         w++;
  443     while (*w) {
  444         if (is_EIGHT_BIT(w))
  445             nonprint++;
  446         if (!nonprint && *w == '=' && *(w + 1) == '?')
  447             nonprint = 1;
  448         if (*w == '=' || *w == '?' || *w == '_')
  449             schars++;
  450         chars++;
  451         w++;
  452     }
  453     if (nonprint) {
  454         if (chars + 2 * (nonprint + schars) /* QP size */ >
  455              (chars * 4 + 3) / 3        /* B64 size */)
  456             return 'B';
  457         return 'Q';
  458     }
  459     return 0;
  460 }
  461 
  462 
  463 /* now only checks if there's any 8bit chars in a given "fragment" */
  464 static t_bool
  465 contains_nonprintables(
  466     char *w,
  467     t_bool isstruct_head)
  468 {
  469     t_bool nonprint = FALSE;
  470 
  471     /* first skip all leading whitespaces */
  472     while (*w && isspace((unsigned char) *w))
  473         w++;
  474 
  475     /* then check the next word */
  476     while (!nonprint && *w && !isbetween(*w, isstruct_head)) {
  477         if (is_EIGHT_BIT(w))
  478             nonprint = TRUE;
  479         else if (*w == '=' && *(w + 1) == '?') {
  480             /*
  481              * to be exact we must look for ?= in the same word
  482              * not in the whole string and check ?B? or ?Q? in the word...
  483              * best would be using a regexp like
  484              * ^=\?\S+\?[qQbB]\?\S+\?=
  485              */
  486             if (strstr(w, "?=") != NULL)
  487                 nonprint = TRUE;
  488         }
  489         w++;
  490     }
  491     return nonprint;
  492 }
  493 
  494 
  495 /*
  496  * implement mandatory break-up of long lines in mail messages in accordance
  497  * with rfc 2047 (rfc 1522)
  498  */
  499 static int
  500 sizeofnextword(
  501     char *w)
  502 {
  503     char *x;
  504 
  505     x = w;
  506     while (*x && isspace((unsigned char) *x))
  507         x++;
  508     while (*x && !isspace((unsigned char) *x))
  509         x++;
  510     return x - w;
  511 }
  512 
  513 
  514 static t_bool
  515 rfc1522_do_encode(
  516     char *what,
  517     char **where,
  518     const char *charset,
  519     t_bool break_long_line)
  520 {
  521     /*
  522      * We need to meet several partly contradictional requirements here.
  523      * First of all, a line containing MIME encodings must not be longer
  524      * than 76 chars (including delimiters, charset, encoding). Second,
  525      * we should not encode more than necessary. Third, we should not
  526      * produce more overhead than absolutely necessary; this means we
  527      * should extend chunks over several words if there are more
  528      * characters-to-quote to come. This means we have to rely on some
  529      * heuristics. We process whole words, checking if it contains
  530      * characters to be quoted. If not, the word is output 'as is',
  531      * previous quoting being terminated before. If two adjoining words
  532      * contain non-printable characters, they are encoded together (up
  533      * to 60 characters). If a resulting encoded word would break the
  534      * 76 characters boundary, we 'break' the line, output a SPACE, then
  535      * output the encoded word. Note that many wide-spread news applications,
  536      * notably INN's xover support, does not understand multiple-lines,
  537      * so it's a compile-time feature with default off.
  538      *
  539      * To make things a bit easier, we do all processing in two stages;
  540      * first we build all encoded words without any bells and whistles
  541      * (just checking that they don get longer than 76 characters),
  542      * then, in a second pass, we replace all SPACEs inside encoded
  543      * words by '_', break long lines, etc.
  544      */
  545     char *buffer;               /* buffer for encoded stuff */
  546     char *c;
  547     char *t;
  548     char buf2[80];              /* buffer for this and that */
  549     int encoding;               /* which encoding to use ('B' or 'Q') */
  550     size_t ew_taken_len;
  551     int word_cnt = 0;
  552     int offset;
  553     size_t bufferlen = 2048;        /* size of buffer */
  554     size_t ewsize = 0;          /* size of current encoded-word */
  555     t_bool quoting = FALSE;     /* currently inside quote block? */
  556     t_bool any_quoting_done = FALSE;
  557     t_bool isbroken_within = FALSE; /* is word broken due to length restriction on encoded of word? */
  558     t_bool isstruct_head = FALSE;       /* are we dealing with structured header? */
  559     t_bool rightafter_ew = FALSE;
  560 /*
  561  * the list of structured header fields where '(' and ')' are
  562  * treated specially in rfc 1522 encoding
  563  */
  564     static const char *struct_header[] = {
  565         "Approved: ", "From: ", "Originator: ",
  566         "Reply-To: ", "Sender: ", "X-Cancelled-By: ", "X-Comment-To: ",
  567         "X-Submissions-To: ", "To: ", "Cc: ", "Bcc: ", "X-Originator: ", 0 };
  568     const char **strptr = struct_header;
  569 
  570     do {
  571         if (!strncasecmp(what, *strptr, strlen(*strptr))) {
  572             isstruct_head = TRUE;
  573             break;
  574         }
  575     } while (*(++strptr) != 0);
  576 
  577     t = buffer = my_malloc(bufferlen);
  578     encoding = which_encoding(what);
  579     ew_taken_len = strlen(charset) + 7 /* =?c?E?d?= */;
  580     while (*what) {
  581         if (break_long_line)
  582             word_cnt++;
  583         /*
  584          * if a word with 8bit chars is broken in the middle, whatever
  585          * follows after the point where it's split should be encoded (i.e.
  586          * even if they are made of only 7bit chars)
  587          */
  588         if (contains_nonprintables(what, isstruct_head) || isbroken_within) {
  589             if (encoding == 'Q') {
  590                 if (!quoting) {
  591                     snprintf(buf2, sizeof(buf2), "=?%s?%c?", charset, encoding);
  592                     while (t - buffer + strlen(buf2) >= bufferlen) {
  593                         /* buffer too small, double its size */
  594                         offset = t - buffer;
  595                         bufferlen <<= 1;
  596                         buffer = my_realloc(buffer, bufferlen * sizeof(*buffer));
  597                         t = buffer + offset;
  598                     }
  599                     ewsize = mystrcat(&t, buf2);
  600                     if (break_long_line) {
  601                         if (word_cnt == 2) {
  602                             /*
  603                              * Make sure we fit the first encoded
  604                              * word in with the header keyword,
  605                              * since we cannot break the line
  606                              * directly after the keyword.
  607                              */
  608                             ewsize = t - buffer;
  609                         }
  610                     }
  611                     quoting = TRUE;
  612                     any_quoting_done = TRUE;
  613                 }
  614                 isbroken_within = FALSE;
  615                 while (*what && !isbetween(*what, isstruct_head)) {
  616 #if 0
  617                     if (is_EIGHT_BIT(what) || (strchr(RFC2047_ESPECIALS, *what)))
  618 #else
  619                     if (is_EIGHT_BIT(what) || !isalnum((int)(unsigned char) *what))
  620 #endif /* 0 */
  621                     {
  622                         snprintf(buf2, sizeof(buf2), "=%2.2X", *EIGHT_BIT(what));
  623                         if ((size_t)(t - buffer + 3) >= bufferlen) {
  624                             /* buffer too small, double its size */
  625                             offset = t - buffer;
  626                             bufferlen <<= 1;
  627                             buffer = my_realloc(buffer, bufferlen * sizeof(*buffer));
  628                             t = buffer + offset;
  629                         }
  630                         *t++ = buf2[0];
  631                         *t++ = buf2[1];
  632                         *t++ = buf2[2];
  633                         ewsize += 3;
  634                     } else {
  635                         if ((size_t) (t - buffer + 1) >= bufferlen) {
  636                             /* buffer too small, double its size */
  637                             offset = t - buffer;
  638                             bufferlen <<= 1;
  639                             buffer = my_realloc(buffer, bufferlen * sizeof(*buffer));
  640                             t = buffer + offset;
  641                         }
  642                         *t++ = *what;
  643                         ewsize++;
  644                     }
  645                     what++;
  646                     /*
  647                      * Be sure to encode at least one char, even if
  648                      * that overflows the line limit, otherwise, we
  649                      * will be stuck in a loop (if this were in the
  650                      * while condition above). (Can only happen in
  651                      * the first line, if we have a very long
  652                      * header keyword, I think).
  653                      */
  654                     if (ewsize >= 71) {
  655                         isbroken_within = TRUE;
  656                         break;
  657                     }
  658                 }
  659                 if (!contains_nonprintables(what, isstruct_head) || ewsize >= 70 - strlen(charset)) {
  660                     /* next word is 'clean', close encoding */
  661                     if ((size_t) (t - buffer + 2) >= bufferlen) {
  662                         /* buffer too small, double its size */
  663                         offset = t - buffer;
  664                         bufferlen <<= 1;
  665                         buffer = my_realloc(buffer, bufferlen * sizeof(*buffer));
  666                         t = buffer + offset;
  667                     }
  668                     *t++ = '?';
  669                     *t++ = '=';
  670                     ewsize += 2;
  671                     /*
  672                      */
  673                     if (ewsize >= 70 - strlen(charset) && (contains_nonprintables(what, isstruct_head) || isbroken_within)) {
  674                         if ((size_t) (t - buffer + 1) >= bufferlen) {
  675                             /* buffer too small, double its size */
  676                             offset = t - buffer;
  677                             bufferlen <<= 1;
  678                             buffer = my_realloc(buffer, bufferlen * sizeof(*buffer));
  679                             t = buffer + offset;
  680                         }
  681                         *t++ = ' ';
  682                         ewsize++;
  683                     }
  684                     quoting = FALSE;
  685                 } else {
  686                     /* process whitespace in-between by quoting it properly */
  687                     while (*what && isspace((unsigned char) *what)) {
  688                         if ((size_t) (t - buffer + 3) >= bufferlen) {
  689                             /* buffer probably too small, double its size */
  690                             offset = t - buffer;
  691                             bufferlen <<= 1;
  692                             buffer = my_realloc(buffer, bufferlen * sizeof(*buffer));
  693                             t = buffer + offset;
  694                         }
  695                         if (*what == 32 /* not ' ', compare chapter 4! */ ) {
  696                             *t++ = '_';
  697                             ewsize++;
  698                         } else {
  699                             snprintf(buf2, sizeof(buf2), "=%2.2X", *EIGHT_BIT(what));
  700                             *t++ = buf2[0];
  701                             *t++ = buf2[1];
  702                             *t++ = buf2[2];
  703                             ewsize += 3;
  704                         }
  705                         what++;
  706                     }                   /* end of while */
  707                 }                       /* end of else */
  708             } else {                    /* end of Q encoding and beg. of B encoding */
  709                 /*
  710                  * if what immediately precedes the current fragment with 8bit
  711                  * char is encoded word, the leading spaces should be encoded
  712                  * together with 8bit chars following them. No need to worry
  713                  * about '(',')' and '"' as they're already excluded with
  714                  * contain_nonprintables used in outer if-clause
  715                  */
  716                 while (*what && (!isbetween(*what, isstruct_head) || rightafter_ew)) {
  717                     snprintf(buf2, sizeof(buf2), "=?%s?%c?", charset, encoding);
  718                     while (t - buffer + strlen(buf2) >= bufferlen) {
  719                         /* buffer too small, double its size */
  720                         offset = t - buffer;
  721                         bufferlen <<= 1;
  722                         buffer = my_realloc(buffer, bufferlen * sizeof(*buffer));
  723                         t = buffer + offset;
  724                     }
  725                     ewsize = mystrcat(&t, buf2);
  726 
  727                     if (word_cnt == 2)
  728                         ewsize = t - buffer;
  729                     what += do_b_encode(what, buf2, 75 - ew_taken_len, isstruct_head);
  730                     while (t - buffer + strlen(buf2) + 3 >= bufferlen) {
  731                         /* buffer too small, double its size */
  732                         offset = t - buffer;
  733                         bufferlen <<= 1;
  734                         buffer = my_realloc(buffer, bufferlen * sizeof(*buffer));
  735                         t = buffer + offset;
  736                     }
  737                     ewsize += mystrcat(&t, buf2);
  738                     *t++ = '?';
  739                     *t++ = '=';
  740                     *t++ = ' ';
  741                     ewsize += 3;
  742                     if (break_long_line)
  743                         word_cnt++;
  744                     rightafter_ew = FALSE;
  745                     any_quoting_done = TRUE;
  746                 }
  747                 rightafter_ew = TRUE;
  748                 word_cnt--;     /* compensate double counting */
  749                 /*
  750                  * if encoded word is followed by 7bit-only fragment, we need to
  751                  * eliminate ' ' inserted in while-block above
  752                  */
  753                 if (!contains_nonprintables(what, isstruct_head)) {
  754                     t--;
  755                     ewsize--;
  756                 }
  757             }       /* end of B encoding */
  758         } else {
  759             while (*what && !isbetween(*what, isstruct_head)) {
  760                 if ((size_t) (t - buffer + 1) >= bufferlen) {
  761                     /* buffer too small, double its size */
  762                     offset = t - buffer;
  763                     bufferlen <<= 1;
  764                     buffer = my_realloc(buffer, bufferlen * sizeof(*buffer));
  765                     t = buffer + offset;
  766                 }
  767                 *t++ = *what++;     /* output word unencoded */
  768             }
  769             while (*what && isbetween(*what, isstruct_head)) {
  770                 if ((size_t) (t - buffer + 1) >= bufferlen) {
  771                     /* buffer too small, double its size */
  772                     offset = t - buffer;
  773                     bufferlen <<= 1;
  774                     buffer = my_realloc(buffer, bufferlen * sizeof(*buffer));
  775                     t = buffer + offset;
  776                 }
  777                 *t++ = *what++;     /* output trailing whitespace unencoded */
  778             }
  779             rightafter_ew = FALSE;
  780         }
  781     }       /* end of pass 1 while loop */
  782     *t = 0;
  783 
  784     /* Pass 2: break long lines if there are MIME-sequences in the result */
  785     c = buffer;
  786     if (break_long_line && any_quoting_done) {
  787         char *new_buffer;
  788         size_t new_bufferlen = strlen(buffer) * 2 + 1; /* maximum length if
  789 every "word" were a space ... */
  790         int column = 0;             /* current column */
  791 
  792         new_buffer = my_malloc(new_bufferlen);
  793         t = new_buffer;
  794         word_cnt = 1;           /*
  795                          * note, if the user has typed a continuation
  796                          * line, we will consider the initial
  797                          * whitespace to be delimiting word one (well,
  798                          * just assume an empty word).
  799                          */
  800         while (*c) {
  801             if (isspace((unsigned char) *c)) {
  802                 /*
  803                  * According to rfc1522, header lines containing encoded
  804                  * words are limited to 76 chars, but if the first line is
  805                  * too long (due to a long header keyword), we cannot stick
  806                  * to that, since we would break the line directly after the
  807                  * keyword's colon, which is not allowed. The same is
  808                  * necessary for a continuation line with an unencoded word
  809                  * that is too long.
  810                  */
  811                 if (sizeofnextword(c) + column > 76 && word_cnt != 1) {
  812                     *t++ = '\n';
  813                     column = 0;
  814                 }
  815                 if (c > buffer && !isspace((unsigned char) *(c - 1)))
  816                     word_cnt++;
  817                 *t++ = *c++;
  818                 column++;
  819             } else
  820                 while (*c && !isspace((unsigned char) *c)) {
  821                     *t++ = *c++;
  822                     column++;
  823                 }
  824         }
  825         FreeAndNull(buffer);
  826         buffer = new_buffer;
  827     }
  828     *t = 0;
  829     *where = buffer;
  830     return any_quoting_done;
  831 }
  832 
  833 
  834 /*
  835  * calling code must free() the result if it's no longer needed
  836  */
  837 char *
  838 rfc1522_encode(
  839     char *s,
  840     const char *charset,
  841     t_bool ismail)
  842 {
  843     char *buf;
  844 
  845     /*
  846      * break_long_line is FALSE for news posting unless
  847      * MIME_BREAK_LONG_LINES is defined, but it's TRUE for mail messages
  848      * regardless of whether or not MIME_BREAK_LONG_LINES is defined
  849      */
  850 #ifdef MIME_BREAK_LONG_LINES
  851     t_bool break_long_line = TRUE;
  852 #else
  853     /*
  854      * Even if MIME_BREAK_LONG_LINES is NOT defined, long headers in mail
  855      * messages should be broken up in accordance with RFC 2047(1522)
  856      */
  857     t_bool break_long_line = ismail;
  858 #endif /* MIME_BREAK_LONG_LINES */
  859 
  860     rfc1522_do_encode(s, &buf, charset, break_long_line);
  861 
  862     return buf;
  863 }
  864 
  865 
  866 /*
  867  * Helper function doing the hard work for rfc15211522_encode().
  868  * Code moved from rfc15211522_encode(), with some adjustments to work on a
  869  * file specified by a FILE* instead of a filename.
  870  */
  871 static void
  872 do_rfc15211522_encode(
  873     FILE *f,
  874     constext * mime_encoding,
  875     struct t_group *group,
  876     t_bool allow_8bit_header,
  877     t_bool ismail,
  878     t_bool contains_headers)
  879 {
  880     FILE *g;
  881     char *c;
  882     char *header;
  883     char encoding;
  884     char buffer[2048];
  885     t_bool mime_headers_needed = FALSE;
  886     BodyPtr body_encode;
  887     int i;
  888 #ifdef CHARSET_CONVERSION
  889     int mmnwcharset;
  890 
  891     if (group) /* Posting */
  892         mmnwcharset = group->attribute->mm_network_charset;
  893     else /* E-Mail */
  894         mmnwcharset = tinrc.mm_network_charset;
  895 #endif /* CHARSET_CONVERSION */
  896 
  897     if ((g = tmpfile()) == NULL)
  898         return;
  899 
  900     while (contains_headers && (header = tin_fgets(f, TRUE))) {
  901 #ifdef CHARSET_CONVERSION
  902         buffer_to_network(header, mmnwcharset);
  903 #endif /* CHARSET_CONVERSION */
  904         if (*header == '\0')
  905             break;
  906 
  907         /*
  908          * TODO: - what about 8bit chars in the mentioned headers
  909          *         when !allow_8bit_header?
  910          *       - what about lines longer 998 octets?
  911          */
  912         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)))
  913             fputs(header, g);
  914         else {
  915             char *p;
  916 
  917 #ifdef CHARSET_CONVERSION
  918             p = rfc1522_encode(header, txt_mime_charsets[mmnwcharset], ismail);
  919 #else
  920             p = rfc1522_encode(header, tinrc.mm_charset, ismail);
  921 #endif /* CHARSET_CONVERSION */
  922 
  923             fputs(p, g);
  924             free(p);
  925         }
  926         fputc('\n', g);
  927     }
  928 
  929     fputc('\n', g);
  930 
  931     while (fgets(buffer, 2048, f)) {
  932 #ifdef CHARSET_CONVERSION
  933         buffer_to_network(buffer, mmnwcharset);
  934 #endif /* CHARSET_CONVERSION */
  935         fputs(buffer, g);
  936         if (!allow_8bit_header) {
  937             /* see if there are any 8bit chars in the body... */
  938             for (c = buffer; *c && !isreturn(*c); c++) {
  939                 if (is_EIGHT_BIT(c)) {
  940                     mime_headers_needed = TRUE;
  941                     break;
  942                 }
  943             }
  944         }
  945     }
  946 
  947     rewind(g);
  948     rewind(f);
  949 #ifdef HAVE_FTRUNCATE
  950     (void) ftruncate(fileno(f), 0);
  951 #endif /* HAVE_FTRUNCATE */
  952 
  953     /* copy header */
  954     while (fgets(buffer, 2048, g) && !isreturn(buffer[0]))
  955         fputs(buffer, f);
  956 
  957     if (!allow_8bit_header) {
  958         /*
  959          * 7bit charsets except US-ASCII also need mime headers
  960          */
  961         for (i = 1; txt_mime_7bit_charsets[i] != NULL; i++) {
  962 #ifdef CHARSET_CONVERSION
  963             if (!strcasecmp(txt_mime_charsets[mmnwcharset], txt_mime_7bit_charsets[i])) {
  964                 mime_headers_needed = TRUE;
  965                 break;
  966             }
  967 #else
  968             if (!strcasecmp(tinrc.mm_charset, txt_mime_7bit_charsets[i])) {
  969                 mime_headers_needed = TRUE;
  970                 break;
  971             }
  972 #endif /* CHARSET_CONVERSION */
  973         }
  974 
  975         /*
  976          * now add MIME headers as necessary
  977          */
  978         if (mime_headers_needed) {
  979             if (contains_headers)
  980                 fprintf(f, "MIME-Version: %s\n", MIME_SUPPORTED_VERSION);
  981 #ifdef CHARSET_CONVERSION
  982             fprintf(f, "Content-Type: text/plain; charset=%s\n", txt_mime_charsets[mmnwcharset]);
  983 #else
  984             fprintf(f, "Content-Type: text/plain; charset=%s\n", tinrc.mm_charset);
  985 #endif /* CHARSET_CONVERSION */
  986             fprintf(f, "Content-Transfer-Encoding: %s\n", mime_encoding);
  987         }
  988     }
  989     fputc('\n', f);
  990 
  991     if (!allow_8bit_header) {
  992         if (!strcasecmp(mime_encoding, txt_base64))
  993             encoding = 'b';
  994         else if (!strcasecmp(mime_encoding, txt_quoted_printable))
  995             encoding = 'q';
  996         else if (!strcasecmp(mime_encoding, txt_7bit))
  997             encoding = '7';
  998         else
  999             encoding = '8';
 1000 
 1001         /* avoid break of long lines for US-ASCII/quoted-printable */
 1002         if (!mime_headers_needed)
 1003             encoding = '8';
 1004 
 1005         body_encode = rfc1521_encode;
 1006 
 1007         while (fgets(buffer, 2048, g))
 1008             body_encode(buffer, f, encoding);
 1009 
 1010         if (encoding == 'b' || encoding == 'q' || encoding == '7')
 1011             body_encode(NULL, f, encoding); /* flush */
 1012     } else {
 1013         while (fgets(buffer, 2048, g))
 1014             fputs(buffer, f);
 1015     }
 1016 
 1017     fclose(g);
 1018 }
 1019 
 1020 
 1021 void
 1022 rfc15211522_encode(
 1023     const char *filename,
 1024     constext * mime_encoding,
 1025     struct t_group *group,
 1026     t_bool allow_8bit_header,
 1027     t_bool ismail)
 1028 {
 1029     FILE *fp;
 1030 
 1031     if ((fp = fopen(filename, "r+")) == NULL)
 1032         return;
 1033 
 1034     do_rfc15211522_encode(fp, mime_encoding, group, allow_8bit_header, ismail, TRUE);
 1035 
 1036     fclose(fp);
 1037 }
 1038 
 1039 
 1040 /*
 1041  * Generate a MIME boundary being unique with high probability, consisting
 1042  * of len - 1 random characters.
 1043  * This function is used as a last resort if anything else failed to
 1044  * generate a truly unique boundary.
 1045  */
 1046 static void
 1047 generate_random_mime_boundary(
 1048     char *boundary,
 1049     size_t len)
 1050 {
 1051     size_t i;
 1052 
 1053     srand((unsigned int) time(NULL));
 1054     for (i = 0; i < len - 1; i++)
 1055         boundary[i] = base64_alphabet[rand() % sizeof(base64_alphabet)];
 1056     boundary[len - 1] = '\0';
 1057 }
 1058 
 1059 
 1060 /*
 1061  * Generate a unique MIME boundary.
 1062  * boundary must have enough space for at least MIME_BOUNDARY_SIZE characters.
 1063  */
 1064 static void
 1065 generate_mime_boundary(
 1066     char *boundary,
 1067     FILE *f,
 1068     FILE *g)
 1069 {
 1070     const char nice_chars[] = { '-', '_', '=' };
 1071     const size_t prefix_len = sizeof(MIME_BOUNDARY_PREFIX) - 1;
 1072     char *s;
 1073     size_t i = 0;
 1074     t_bool unique = FALSE;
 1075 
 1076     /*
 1077      * Choose MIME boundary as follows:
 1078      *   - Always start with MIME_BOUNDARY_PREFIX.
 1079      *   - Append MIME_BOUNDARY_DEFAULT_PART.
 1080      *   - If necessary, change it from right to left, choosing from a set of
 1081      *     `nice_chars' characters.
 1082      *   - After that, if it is still not unique, replace MIME_BOUNDARY_DEFAULT_PART
 1083      *     with random characters and hope the best.
 1084      */
 1085 
 1086     strcpy(boundary, MIME_BOUNDARY_PREFIX);
 1087     strcat(boundary, MIME_BOUNDARY_DEFAULT_PART);
 1088 
 1089     s = boundary + MIME_BOUNDARY_SIZE - 2; /* set s to last character before '\0' */
 1090     do {
 1091         /*
 1092          * Scan for entire boundary in both f and g.
 1093          * When found: modify and redo.
 1094          */
 1095         if (contains_string(f, boundary) || contains_string(g, boundary)) {
 1096             *s = nice_chars[i];
 1097             if ((i = (i + 1) % sizeof(nice_chars)) == 0)
 1098                 --s;
 1099         } else
 1100             unique = TRUE;
 1101     } while (!unique && s >= boundary + prefix_len);
 1102 
 1103     if (!unique)
 1104         generate_random_mime_boundary(boundary + prefix_len, sizeof(MIME_BOUNDARY_DEFAULT_PART));
 1105 }
 1106 
 1107 
 1108 /*
 1109  * Split mail into header and (optionally) body.
 1110  *
 1111  * If textfp is not NULL, everything behind the header is stored in it.
 1112  * Whenever an error is encountered, all files are closed and FALSE is returned.
 1113  */
 1114 static t_bool
 1115 split_mail(
 1116     const char *filename,
 1117     FILE **headerfp,
 1118     FILE **textfp)
 1119 {
 1120     FILE *fp;
 1121     char *line;
 1122 
 1123     if ((fp = fopen(filename, "r")) == NULL)
 1124         return FALSE;
 1125 
 1126     /* Header */
 1127     if ((*headerfp = tmpfile()) == NULL) {
 1128         fclose(fp);
 1129         return FALSE;
 1130     }
 1131 
 1132     while ((line = tin_fgets(fp, TRUE))) {
 1133         if (*line == '\0')
 1134             break;
 1135         else
 1136             fprintf(*headerfp, "%s\n", line);
 1137     }
 1138 
 1139     /* Body */
 1140     if (textfp != NULL) {
 1141         if ((*textfp = tmpfile()) == NULL) {
 1142             fclose(fp);
 1143             fclose(*headerfp);
 1144             return FALSE;
 1145         }
 1146 
 1147         while ((line = tin_fgets(fp, FALSE)))
 1148             fprintf(*textfp, "%s\n", line);
 1149     }
 1150 
 1151     fclose(fp);
 1152     return TRUE;
 1153 }
 1154 
 1155 
 1156 /*
 1157  * Compose a mail consisting of a sole text/plain MIME part.
 1158  */
 1159 void
 1160 compose_mail_text_plain(
 1161     const char *filename,
 1162     struct t_group *group)
 1163 {
 1164     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);
 1165 }
 1166 
 1167 
 1168 /*
 1169  * Compose a mail consisting of an optional text/plain and a message/rfc822
 1170  * part.
 1171  *
 1172  * At this point, the file denoted by `filename' contains some common headers
 1173  * and any text the user entered. The file `articlefp' contains the forwarded
 1174  * article in raw form.
 1175  */
 1176 void
 1177 compose_mail_mime_forwarded(
 1178     const char *filename,
 1179     FILE *articlefp,
 1180     t_bool include_text,
 1181     struct t_group *group)
 1182 {
 1183     FILE *fp;
 1184     FILE *headerfp;
 1185     FILE *textfp = NULL;
 1186     FILE *entityfp;
 1187     char *line;
 1188     constext* encoding = txt_mime_encodings[(group ? group->attribute->mail_mime_encoding : tinrc.mail_mime_encoding)];
 1189     t_bool allow_8bit_header = (group ? group->attribute->mail_8bit_header : tinrc.mail_8bit_header);
 1190     t_bool _8bit;
 1191 
 1192     /* Split mail into headers and text */
 1193     if (!split_mail(filename, &headerfp, include_text ? &textfp : NULL))
 1194         return;
 1195 
 1196     /* Encode header and text */
 1197     rewind(headerfp);
 1198     do_rfc15211522_encode(headerfp, encoding, group, allow_8bit_header, TRUE, TRUE);
 1199 
 1200     if (textfp) {
 1201         rewind(textfp);
 1202         do_rfc15211522_encode(textfp, encoding, group, allow_8bit_header, TRUE, FALSE);
 1203     }
 1204 
 1205     /* Compose top-level MIME entity */
 1206     if (include_text)
 1207         entityfp = compose_multipart_mixed(textfp, articlefp);
 1208     else
 1209         entityfp = compose_message_rfc822(articlefp, &_8bit);
 1210 
 1211     if (entityfp == NULL) {
 1212         fclose(headerfp);
 1213         if (textfp)
 1214             fclose(textfp);
 1215         return;
 1216     }
 1217 
 1218     /* Put it all together */
 1219     if ((fp = fopen(filename, "w")) == NULL) {
 1220         fclose(headerfp);
 1221         fclose(entityfp);
 1222         return;
 1223     }
 1224 
 1225     rewind(headerfp);
 1226     while ((line = tin_fgets(headerfp, TRUE))) {
 1227         if (*line != '\0')
 1228             fprintf(fp, "%s\n", line);
 1229     }
 1230     fprintf(fp, "MIME-Version: %s\n", MIME_SUPPORTED_VERSION);
 1231     rewind(entityfp);
 1232     copy_fp(entityfp, fp);
 1233 
 1234     /* Clean up */
 1235     fclose(fp);
 1236     fclose(headerfp);
 1237     fclose(entityfp);
 1238     if (textfp)
 1239         fclose(textfp);
 1240 }
 1241 
 1242 
 1243 /*
 1244  * Compose a message/rfc822 MIME entity containing articlefp.
 1245  */
 1246 static FILE *
 1247 compose_message_rfc822(
 1248     FILE *articlefp,
 1249     t_bool *is_8bit)
 1250 {
 1251     FILE *fp;
 1252 
 1253     if ((fp = tmpfile()) == NULL)
 1254         return NULL;
 1255 
 1256     *is_8bit = contains_8bit_characters(articlefp);
 1257 
 1258     /* Header: CT, CD, CTE */
 1259     fprintf(fp, "Content-Type: message/rfc822\n");
 1260     fprintf(fp, "Content-Disposition: inline\n");
 1261     fprintf(fp, "Content-Transfer-Encoding: %s\n", *is_8bit ? txt_8bit : txt_7bit);
 1262     fputc('\n', fp);
 1263 
 1264     /* Body: articlefp */
 1265     rewind(articlefp);
 1266     copy_fp(articlefp, fp);
 1267 
 1268     return fp;
 1269 }
 1270 
 1271 
 1272 /*
 1273  * Compose a multipart/mixed MIME entity consisting of a text/plain and a
 1274  * message/rfc822 part.
 1275  */
 1276 static FILE *
 1277 compose_multipart_mixed(
 1278     FILE *textfp,
 1279     FILE *articlefp)
 1280 {
 1281     FILE *fp;
 1282     FILE *messagefp;
 1283     char boundary[MIME_BOUNDARY_SIZE];
 1284     t_bool requires_8bit;
 1285 
 1286     if ((fp = tmpfile()) == NULL)
 1287         return NULL;
 1288 
 1289     /* First compose message/rfc822 part (needed for choosing the appropriate CTE) */
 1290     if ((messagefp = compose_message_rfc822(articlefp, &requires_8bit)) == NULL) {
 1291         fclose(fp);
 1292         return NULL;
 1293     }
 1294 
 1295     requires_8bit = (requires_8bit || contains_8bit_characters(textfp));
 1296 
 1297     /*
 1298      * Header: CT with multipart boundary, CTE
 1299      * TODO: -> lang.c
 1300      */
 1301     generate_mime_boundary(boundary, textfp, articlefp);
 1302     fprintf(fp, "Content-Type: multipart/mixed; boundary=\"%s\"\n", boundary);
 1303     fprintf(fp, "Content-Transfer-Encoding: %s\n\n", requires_8bit ? txt_8bit : txt_7bit);
 1304 
 1305     /*
 1306      * preamble
 1307      * TODO: -> lang.c
 1308      */
 1309     fprintf(fp, _("This message has been composed in the 'multipart/mixed' MIME-format. If you\n\
 1310 are reading this prefix, your mail reader probably has not yet been modified\n\
 1311 to understand the new format, and some of what follows may look strange.\n\n"));
 1312 
 1313     /*
 1314      * Body: boundary+text, message/rfc822 part, closing boundary
 1315      */
 1316     /* text */
 1317     fprintf(fp, "--%s\n", boundary);
 1318     rewind(textfp);
 1319     copy_fp(textfp, fp);
 1320     fputc('\n', fp);
 1321 
 1322     /* message/rfc822 part */
 1323     fprintf(fp, "--%s\n", boundary);
 1324     rewind(messagefp);
 1325     copy_fp(messagefp, fp);
 1326     fclose(messagefp);
 1327     fputc('\n', fp);
 1328 
 1329     /* closing boundary */
 1330     fprintf(fp, "--%s--\n", boundary);
 1331     /* TODO: insert an epilogue here? */
 1332     return fp;
 1333 }
 1334 
 1335 
 1336 /*
 1337  * Determines whether the file denoted by fp contains 8bit characters.
 1338  */
 1339 static t_bool
 1340 contains_8bit_characters(
 1341     FILE *fp)
 1342 {
 1343     char *line;
 1344 
 1345     rewind(fp);
 1346     while ((line = tin_fgets(fp, FALSE))) {
 1347         for (; *line != '\0'; line++) {
 1348             if (is_EIGHT_BIT(line))
 1349                 return TRUE;
 1350         }
 1351     }
 1352 
 1353     return FALSE;
 1354 }
 1355 
 1356 
 1357 /*
 1358  * Determines whether any line of the file denoted by fp contains str.
 1359  */
 1360 static t_bool
 1361 contains_string(
 1362     FILE *fp,
 1363     const char *str)
 1364 {
 1365     char *line;
 1366 
 1367     rewind(fp);
 1368     while ((line = tin_fgets(fp, FALSE))) {
 1369         if (strstr(line, str))
 1370             return TRUE;
 1371     }
 1372 
 1373     return FALSE;
 1374 }