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