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