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