1 /* $NetBSD: mime_decode.c,v 1.13 2008/04/28 20:24:14 martin Exp $ */ 2 3 /*- 4 * Copyright (c) 2006 The NetBSD Foundation, Inc. 5 * All rights reserved. 6 * 7 * This code is derived from software contributed to The NetBSD Foundation 8 * by Anon Ymous. 9 * 10 * Redistribution and use in source and binary forms, with or without 11 * modification, are permitted provided that the following conditions 12 * are met: 13 * 1. Redistributions of source code must retain the above copyright 14 * notice, this list of conditions and the following disclaimer. 15 * 2. Redistributions in binary form must reproduce the above copyright 16 * notice, this list of conditions and the following disclaimer in the 17 * documentation and/or other materials provided with the distribution. 18 * 19 * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS 20 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 21 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 22 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS 23 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 24 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 25 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 26 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 27 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 28 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 29 * POSSIBILITY OF SUCH DAMAGE. 30 */ 31 32 33 #ifdef MIME_SUPPORT 34 35 #include <sys/cdefs.h> 36 #ifndef __lint__ 37 __RCSID("$NetBSD: mime_decode.c,v 1.13 2008/04/28 20:24:14 martin Exp $"); 38 #endif /* not __lint__ */ 39 40 #include <assert.h> 41 #include <err.h> 42 #include <fcntl.h> 43 #include <libgen.h> 44 #include <setjmp.h> 45 #include <signal.h> 46 #include <stdio.h> 47 #include <stdlib.h> 48 #include <string.h> 49 #include <unistd.h> 50 #include <iconv.h> 51 52 #include "def.h" 53 #include "extern.h" 54 #ifdef USE_EDITLINE 55 #include "complete.h" 56 #endif 57 #ifdef MIME_SUPPORT 58 #include "mime.h" 59 #include "mime_child.h" 60 #include "mime_codecs.h" 61 #include "mime_header.h" 62 #include "mime_detach.h" 63 #endif 64 #include "glob.h" 65 #include "thread.h" 66 67 #if 0 68 #ifndef __lint__ 69 /* 70 * XXX - This block for debugging only and eventually should go away. 71 */ 72 static void 73 show_one_mime_info(FILE *fp, struct mime_info *mip) 74 { 75 #define XX(a) (a) ? (a) : "<null>" 76 77 (void)fprintf(fp, ">> --------\n"); 78 (void)fprintf(fp, "mip %d:\n", mip->mi_partnum); 79 (void)fprintf(fp, "** Version: %s\n", XX(mip->mi_version)); 80 (void)fprintf(fp, "** type: %s\n", XX(mip->mi_type)); 81 (void)fprintf(fp, "** subtype: %s\n", XX(mip->mi_subtype)); 82 (void)fprintf(fp, "** boundary: %s\n", XX(mip->mi_boundary)); 83 (void)fprintf(fp, "** charset: %s\n", XX(mip->mi_charset)); 84 (void)fprintf(fp, "** encoding: %s\n", XX(mip->mi_encoding)); 85 (void)fprintf(fp, "** disposition: %s\n", XX(mip->mi_disposition)); 86 (void)fprintf(fp, "** filename: %s\n", XX(mip->mi_filename)); 87 (void)fprintf(fp, "** %p: flag: 0x%x, block: %ld, offset: %d, size: %lld, lines: %ld:%ld\n", 88 mip->mp, 89 mip->mp->m_flag, 90 mip->mp->m_block, mip->mp->m_offset, mip->mp->m_size, 91 mip->mp->m_lines, mip->mp->m_blines); 92 (void)fprintf(fp, "** mip: %p\n", mip); 93 (void)fprintf(fp, "** mi_flink: %p\n", mip->mi_flink); 94 (void)fprintf(fp, "** mi_blink: %p\n", mip->mi_blink); 95 (void)fprintf(fp, "** mip %p, mp %p, parent_mip %p, parent_mp %p\n", 96 mip, mip->mp, mip->mi_parent.mip, mip->mi_parent.mp); 97 98 (void)fprintf(fp, "** mi_fo %p, mi_head_end %p, mi_pipe_end %p\n", 99 mip->mi_fo, mip->mi_head_end, mip->mi_pipe_end); 100 101 (void)fprintf(fp, "** mi_ignore_body: %d\n", mip->mi_ignore_body); 102 (void)fprintf(fp, "** mi_partnum: %d\n", mip->mi_partnum); 103 (void)fprintf(fp, "** mi_partstr: %s\n", mip->mi_partstr); 104 (void)fprintf(fp, "** mi_msgstr: %s\n", mip->mi_msgstr); 105 106 (void)fflush(fp); 107 108 #undef XX 109 } 110 111 __unused 112 static void 113 show_mime_info(FILE *fp, struct mime_info *mip, struct mime_info *end_mip) 114 { 115 for (/* EMTPY */; mip != end_mip; mip = mip->mi_flink) 116 show_one_mime_info(fp, mip); 117 118 (void)fprintf(fp, "++ =========\n"); 119 (void)fflush(fp); 120 } 121 #endif /* __lint__ */ 122 #endif /* #if */ 123 124 125 /* 126 * Our interface to the file registry in popen.c 127 */ 128 PUBLIC FILE * 129 pipe_end(struct mime_info *mip) 130 { 131 FILE *fp; 132 fp = last_registered_file(0); /* get last registered file or pipe */ 133 if (fp == NULL) 134 fp = mip->mi_fo; 135 return fp; 136 } 137 138 /* 139 * Copy the first ';' delimited substring from 'src' (null terminated) 140 * into 'dst', expanding quotes and removing comments (as per RFC 141 * 822). Returns a pointer in src to the next non-white character 142 * following ';'. The caller is responsible for ensuring 'dst' is 143 * sufficiently large to hold the result. 144 */ 145 static char * 146 get_param(char *dst, char *src) 147 { 148 char *lastq; 149 char *cp; 150 char *cp2; 151 int nesting; 152 153 cp2 = dst; 154 lastq = dst; 155 for (cp = src; *cp && *cp != ';'; cp++) { 156 switch (*cp) { 157 case '"': /* start of quoted string */ 158 for (cp++; *cp; cp++) { 159 if (*cp == '"') 160 break; 161 if (*cp == '\\' && cp[1] != '\0') 162 ++cp; 163 *cp2++ = *cp; 164 } 165 lastq = cp2-1; 166 break; 167 case '(': /* start of comment */ 168 nesting = 1; 169 while (nesting > 0 && *++cp) { 170 if (*cp == '\\' && cp[1] != '\0') 171 cp++; 172 if (*cp == '(') 173 nesting++; 174 if (*cp == ')') 175 nesting--; 176 } 177 break; 178 default: 179 *cp2++ = *cp; 180 break; 181 } 182 } 183 /* remove trailing white space */ 184 while (cp2 > lastq && is_WSP(cp2[-1])) 185 cp2--; 186 *cp2 = '\0'; 187 if (*cp == ';') 188 cp++; 189 cp = skip_WSP(cp); 190 return cp; 191 } 192 193 /* 194 * Content parameter 195 * if field is NULL, return the content "specifier". 196 */ 197 static char* 198 cparam(const char field[], char *src, int downcase) 199 { 200 char *cp; 201 char *dst; 202 203 if (src == NULL) 204 return NULL; 205 206 dst = salloc(strlen(src) + 1); /* large enough for any param in src */ 207 cp = skip_WSP(src); 208 cp = get_param(dst, cp); 209 210 if (field == NULL) 211 return dst; 212 213 while (*cp != '\0') { 214 size_t len = strlen(field); 215 cp = get_param(dst, cp); 216 if (strncasecmp(dst, field, len) == 0 && dst[len] == '=') { 217 char *cp2; 218 cp2 = dst + len + 1; 219 if (downcase) 220 istrcpy(cp2, cp2); 221 return cp2; 222 } 223 } 224 return NULL; 225 } 226 227 228 static void 229 get_content(struct mime_info *mip) 230 { 231 char *mime_disposition_field; 232 char *mime_type_field; 233 char *filename; 234 struct message *mp; 235 char *cp; 236 237 mp = mip->mp; 238 mip->mi_version = cparam(NULL, hfield(MIME_HDR_VERSION, mp), 0); 239 mip->mi_encoding = cparam(NULL, hfield(MIME_HDR_ENCODING, mp), 1); 240 241 mime_type_field = hfield(MIME_HDR_TYPE, mp); 242 mip->mi_type = cparam(NULL, mime_type_field, 1); 243 if (mip->mi_type) { 244 cp = strchr(mip->mi_type, '/'); 245 if (cp) 246 *cp++ = '\0'; 247 mip->mi_subtype = cp; 248 } 249 mip->mi_boundary = cparam("boundary", mime_type_field, 0); 250 mip->mi_charset = cparam("charset", mime_type_field, 1); 251 252 mime_disposition_field = hfield(MIME_HDR_DISPOSITION, mp); 253 mip->mi_disposition = cparam(NULL, mime_disposition_field, 1); 254 /* 255 * The type field typically has a "name" parameter for "image" 256 * and "video" types, and I assume for other types as well. 257 * We grab it, but override it if the disposition field has a 258 * filename parameter as it often does for "attachments". 259 * More careful analysis could be done, but this seems to work 260 * pretty well. 261 */ 262 filename = cparam("name", mime_type_field, 0); 263 if ((cp = cparam("filename", mime_disposition_field, 0)) != NULL) 264 filename = cp; 265 if (filename) { 266 filename = basename(filename); /* avoid absolute pathnames */ 267 filename = savestr(filename); /* save it! */ 268 } 269 mip->mi_filename = filename; 270 } 271 272 273 static struct message * 274 salloc_message(int flag, long block, short offset) 275 { 276 struct message *mp; 277 /* use csalloc in case someone adds a field someday! */ 278 mp = csalloc(1, sizeof(*mp)); 279 mp->m_flag = flag; 280 mp->m_block = block; 281 mp->m_offset = offset; 282 #if 0 283 mp->m_lines = 0; 284 mp->m_size = 0; 285 mp->m_blines = 0; 286 #endif 287 return mp; 288 } 289 290 static struct mime_info * 291 insert_new_mip(struct mime_info *this_mip, struct mime_info *top_mip, 292 struct message *top_mp, off_t end_pos, int partnum) 293 { 294 struct mime_info *new_mip; 295 296 new_mip = csalloc(1, sizeof(*new_mip)); 297 new_mip->mi_blink = this_mip; 298 new_mip->mi_flink = this_mip->mi_flink; 299 this_mip->mi_flink = new_mip; 300 301 new_mip->mp = salloc_message(this_mip->mp->m_flag, 302 (long)blockof(end_pos), blkoffsetof(end_pos)); 303 304 new_mip->mi_parent.mip = top_mip; 305 new_mip->mi_parent.mp = top_mp; 306 new_mip->mi_partnum = partnum; 307 308 return new_mip; 309 } 310 311 static void 312 split_multipart(struct mime_info *top_mip) 313 { 314 FILE *fp; 315 struct message *top_mp; 316 struct message *this_mp; 317 struct mime_info *this_mip; 318 off_t beg_pos; 319 const char *boundary; 320 size_t boundary_len; 321 long lines_left; /* must be signed and same size as m_lines */ 322 int partnum; 323 int in_header; 324 325 top_mp = top_mip->mp; 326 this_mp = salloc_message(top_mp->m_flag, top_mp->m_block, top_mp->m_offset); 327 this_mip = top_mip; 328 this_mip->mp = this_mp; 329 330 partnum = 1; 331 /* top_mip->mi_partnum = partnum++; */ /* Keep the number set by the caller */ 332 in_header = 1; 333 boundary = top_mip->mi_boundary; 334 boundary_len = boundary ? strlen(boundary) : 0; 335 336 fp = setinput(top_mp); 337 beg_pos = ftello(fp); 338 #if 0 339 warnx("beg_pos: %lld, m_lines: %ld, m_blines: %ld", 340 beg_pos, top_mp->m_lines, top_mp->m_blines); 341 #endif 342 for (lines_left = top_mp->m_lines - 1; lines_left >= 0; lines_left--) { 343 char *line; 344 size_t line_len; 345 346 line = fgetln(fp, &line_len); 347 348 this_mp->m_lines++; /* count the message lines */ 349 350 if (!in_header) 351 this_mp->m_blines++; /* count the body lines */ 352 353 if (lines_left == 0 || ( 354 !in_header && 355 line_len >= boundary_len + 2 && 356 line[0] == '-' && line[1] == '-' && 357 strncmp(line + 2, boundary, boundary_len) == 0)) { 358 off_t cur_pos; 359 off_t end_pos; 360 361 cur_pos = ftello(fp); 362 363 /* the boundary belongs to the next part */ 364 end_pos = cur_pos - line_len; 365 this_mp->m_lines -= 1; 366 this_mp->m_blines -= 1; 367 368 this_mp->m_size = end_pos - beg_pos; 369 #if 0 370 warnx("end_pos: %lld, m_lines: %ld, m_blines: %ld", 371 end_pos, this_mp->m_lines, this_mp->m_blines); 372 #endif 373 if (line[boundary_len + 2] == '-' && 374 line[boundary_len + 3] == '-') {/* end of multipart */ 375 /* do a sanity check on the EOM */ 376 if (lines_left != 1) { 377 /* 378 * XXX - this can happen! 379 * Should we display the 380 * trailing garbage or check 381 * that it is blank or just 382 * ignore it? 383 */ 384 #if 0 385 (void)printf("EOM: lines left: %ld\n", lines_left); 386 #endif 387 } 388 break; /* XXX - stop at this point or grab the rest? */ 389 } 390 this_mip = insert_new_mip(this_mip, top_mip, top_mp, end_pos, partnum++); 391 this_mp = this_mip->mp; 392 this_mp->m_lines = 1; /* already read the first line in the header! */ 393 beg_pos = end_pos; 394 in_header = 1; 395 } 396 397 if (line_len == 1) 398 in_header = 0; 399 } 400 } 401 402 static void 403 split_message(struct mime_info *top_mip) 404 { 405 struct mime_info *this_mip; 406 struct message *top_mp; 407 struct message *this_mp; 408 FILE *fp; 409 off_t beg_pos; 410 long lines_left; /* must be same size as m_lines */ 411 int in_header; 412 413 top_mp = top_mip->mp; 414 this_mp = salloc_message(top_mp->m_flag, top_mp->m_block, top_mp->m_offset); 415 this_mip = top_mip; 416 this_mip->mp = this_mp; 417 418 in_header = 1; 419 420 fp = setinput(top_mp); 421 beg_pos = ftello(fp); 422 423 for (lines_left = top_mp->m_lines; lines_left > 0; lines_left--) { 424 size_t line_len; 425 426 (void)fgetln(fp, &line_len); 427 428 this_mp->m_lines++; /* count the message lines */ 429 if (!in_header) 430 this_mp->m_blines++; /* count the body lines */ 431 432 if (in_header && line_len == 1) { /* end of header */ 433 off_t end_pos; 434 end_pos = ftello(fp); 435 this_mp->m_size = end_pos - beg_pos; 436 this_mip = insert_new_mip(this_mip, top_mip,top_mp, end_pos, 0); 437 this_mp = this_mip->mp; 438 this_mp->m_lines = 1; /* we already counted one line in the header! */ 439 beg_pos = end_pos; 440 in_header = 0; /* never in header again */ 441 } 442 } 443 444 /* close the last message */ 445 this_mp->m_size = ftello(fp) - beg_pos; 446 } 447 448 449 static const char * 450 get_command_hook(struct mime_info *mip, const char *domain) 451 { 452 char *key; 453 char *cmd; 454 455 if (mip->mi_type == NULL) 456 return NULL; 457 458 /* XXX - should we use easprintf() here? We are probably 459 * hosed elsewhere if this fails anyway. */ 460 461 cmd = NULL; 462 if (mip->mi_subtype) { 463 if (asprintf(&key, "mime%s-%s-%s", 464 domain, mip->mi_type, mip->mi_subtype) == -1) { 465 warn("get_command_hook: subtupe: asprintf"); 466 return NULL; 467 } 468 cmd = value(key); 469 free(key); 470 } 471 if (cmd == NULL) { 472 if (asprintf(&key, "mime%s-%s", domain, mip->mi_type) == -1) { 473 warn("get_command_hook: type: asprintf"); 474 return NULL; 475 } 476 cmd = value(key); 477 free(key); 478 } 479 return cmd; 480 } 481 482 483 static int 484 is_basic_alternative(struct mime_info *mip) 485 { 486 return 487 strcasecmp(mip->mi_type, "text") == 0 && 488 strcasecmp(mip->mi_subtype, "plain") == 0; 489 } 490 491 static struct mime_info * 492 select_alternative(struct mime_info *top_mip, struct mime_info *end_mip) 493 { 494 struct mime_info *the_mip; /* the chosen alternate */ 495 struct mime_info *this_mip; 496 /* 497 * The alternates are supposed to occur in order of 498 * increasing "complexity". So: if there is at least 499 * one alternate of type "text/plain", use the last 500 * one, otherwise default to the first alternate. 501 */ 502 the_mip = top_mip->mi_flink; 503 for (this_mip = top_mip->mi_flink; 504 this_mip != end_mip; 505 this_mip = this_mip->mi_flink) { 506 const char *cmd; 507 508 if (this_mip->mi_type == NULL || 509 this_mip->mi_subtype == NULL) 510 continue; 511 512 if (is_basic_alternative(this_mip)) 513 the_mip = this_mip; 514 else if ( 515 (cmd = get_command_hook(this_mip, "-hook")) || 516 (cmd = get_command_hook(this_mip, "-head")) || 517 (cmd = get_command_hook(this_mip, "-body"))) { 518 int flags; 519 /* just get the flags. */ 520 flags = mime_run_command(cmd, NULL); 521 if ((flags & CMD_FLAG_ALTERNATIVE) != 0) 522 the_mip = this_mip; 523 } 524 } 525 return the_mip; 526 } 527 528 529 static inline int 530 is_multipart(struct mime_info *mip) 531 { 532 return mip->mi_type && 533 strcasecmp("multipart", mip->mi_type) == 0; 534 } 535 static inline int 536 is_message(struct mime_info *mip) 537 { 538 return mip->mi_type && 539 strcasecmp("message", mip->mi_type) == 0; 540 } 541 542 static inline int 543 is_alternative(struct mime_info *mip) 544 { 545 return mip->mi_subtype && 546 strcasecmp("alternative", mip->mi_subtype) == 0; 547 } 548 549 550 /* 551 * Take a mime_info pointer and expand it recursively into all its 552 * mime parts. Only "multipart" and "message" types recursed into; 553 * they are handled separately. 554 */ 555 static struct mime_info * 556 expand_mip(struct mime_info *top_mip) 557 { 558 struct mime_info *this_mip; 559 struct mime_info *next_mip; 560 561 if (top_mip->mi_partnum == 0) { 562 if (top_mip->mi_blink) 563 top_mip->mi_partstr = top_mip->mi_blink->mi_partstr; 564 } 565 else if (top_mip->mi_parent.mip) { 566 const char *prefix; 567 char *cp; 568 prefix = top_mip->mi_parent.mip->mi_partstr; 569 (void)sasprintf(&cp, "%s%s%d", prefix, 570 *prefix ? "." : "", top_mip->mi_partnum); 571 top_mip->mi_partstr = cp; 572 } 573 574 next_mip = top_mip->mi_flink; 575 576 if (is_multipart(top_mip)) { 577 top_mip->mi_ignore_body = 1; /* the first body is ignored */ 578 split_multipart(top_mip); 579 580 for (this_mip = top_mip->mi_flink; 581 this_mip != next_mip; 582 this_mip = this_mip->mi_flink) { 583 get_content(this_mip); 584 } 585 if (is_alternative(top_mip)) { 586 this_mip = select_alternative(top_mip, next_mip); 587 this_mip->mi_partnum = 0; /* suppress partnum display */ 588 this_mip->mi_flink = next_mip; 589 this_mip->mi_blink = top_mip; 590 top_mip->mi_flink = this_mip; 591 } 592 /* 593 * Recurse into each part. 594 */ 595 for (this_mip = top_mip->mi_flink; 596 this_mip != next_mip; 597 this_mip = expand_mip(this_mip)) 598 continue; 599 } 600 else if (is_message(top_mip)) { 601 top_mip->mi_ignore_body = 1; /* the first body is ignored */ 602 split_message(top_mip); 603 604 this_mip = top_mip->mi_flink; 605 if (this_mip) { 606 get_content(this_mip); 607 /* 608 * If the one part is MIME encoded, recurse into it. 609 * XXX - Should this be conditional on subtype "rcs822"? 610 */ 611 if (this_mip->mi_type && 612 this_mip->mi_version && 613 equal(this_mip->mi_version, MIME_VERSION)) { 614 this_mip->mi_partnum = 0; 615 (void)expand_mip(this_mip); 616 } 617 } 618 } 619 return next_mip; 620 } 621 622 623 #if 0 624 static int 625 show_partnum(FILE *fp, struct mime_info *mip) 626 { 627 int need_dot; 628 need_dot = 0; 629 if (mip->mi_parent.mip && mip->mi_parent.mip->mi_parent.mip) 630 need_dot = show_partnum(fp, mip->mi_parent.mip); 631 632 if (mip->mi_partnum) { 633 (void)fprintf(fp, "%s%d", need_dot ? "." : "", mip->mi_partnum); 634 need_dot = 1; 635 } 636 return need_dot; 637 } 638 #endif 639 640 641 PUBLIC struct mime_info * 642 mime_decode_open(struct message *mp) 643 { 644 struct mime_info *mip; 645 struct mime_info *p; 646 647 mip = csalloc(1, sizeof(*mip)); 648 mip->mp = salloc(sizeof(*mip->mp)); 649 *mip->mp = *mp; /* copy this so we don't trash the master mp */ 650 651 get_content(mip); 652 653 /* RFC 2049 - sec 2 item 1 */ 654 if (mip->mi_version == NULL || 655 !equal(mip->mi_version, MIME_VERSION)) 656 return NULL; 657 658 mip->mi_partstr = ""; 659 if (mip->mi_type) 660 (void)expand_mip(mip); 661 662 /* 663 * Get the pipe_end and propagate it down the chain. 664 */ 665 mip->mi_pipe_end = last_registered_file(0); /* for mime_decode_close() */ 666 for (p = mip->mi_flink; p; p = p->mi_flink) 667 p->mi_pipe_end = mip->mi_pipe_end; 668 669 /* show_mime_info(stderr, mip, NULL); */ 670 671 return mip; 672 } 673 674 675 PUBLIC void 676 mime_decode_close(struct mime_info *mip) 677 { 678 if (mip) 679 close_top_files(mip->mi_pipe_end); 680 } 681 682 683 struct prefix_line_args_s { 684 const char *prefix; 685 size_t prefixlen; 686 }; 687 688 static void 689 prefix_line(FILE *fi, FILE *fo, void *cookie) 690 { 691 struct prefix_line_args_s *args; 692 const char *line; 693 const char *prefix; 694 size_t prefixlen; 695 size_t length; 696 697 args = cookie; 698 prefix = args->prefix; 699 prefixlen = args->prefixlen; 700 701 while ((line = fgetln(fi, &length)) != NULL) { 702 if (length > 1) 703 (void)fputs(prefix, fo); 704 else 705 (void)fwrite(prefix, sizeof(*prefix), 706 prefixlen, fo); 707 (void)fwrite(line, sizeof(*line), length, fo); 708 } 709 (void)fflush(fo); 710 } 711 712 PUBLIC int 713 mime_sendmessage(struct message *mp, FILE *obuf, struct ignoretab *igntab, 714 const char *prefix, struct mime_info *mip) 715 { 716 int error; 717 int detachall_flag; 718 const char *detachdir; 719 FILE *end_of_prefix; 720 721 if (mip == NULL) 722 return obuf ? /* were we trying to detach? */ 723 sendmessage(mp, obuf, igntab, prefix, NULL) : 0; 724 /* 725 * The prefix has two meanigs which we handle here: 726 * 1) If obuf == NULL, then we are detaching to the 'prefix' directory. 727 * 2) If obuf != NULL, then the prefix is prepended to each line. 728 */ 729 detachdir = NULL; 730 detachall_flag = igntab == detachall; 731 if (obuf == NULL) { 732 assert(prefix != NULL); /* coding error! */ 733 if ((obuf = last_registered_file(0)) == NULL) 734 obuf = stdout; 735 detachdir = prefix; 736 prefix = NULL; 737 igntab = ignoreall; /* always ignore the headers */ 738 } 739 /* 740 * Set this early so pipe_end() will work! 741 */ 742 mip->mi_fo = obuf; 743 744 (void)fflush(obuf); /* Be safe and flush! XXX - necessary? */ 745 746 /* 747 * Handle the prefix as a pipe stage so it doesn't get seen by 748 * any decoding or hooks. 749 */ 750 if (prefix != NULL) { 751 static struct prefix_line_args_s prefix_line_args; 752 const char *dp, *dp2 = NULL; 753 for (dp = prefix; *dp; dp++) 754 if (!is_WSP(*dp)) 755 dp2 = dp; 756 prefix_line_args.prefixlen = dp2 == 0 ? 0 : dp2 - prefix + 1; 757 prefix_line_args.prefix = prefix; 758 mime_run_function(prefix_line, pipe_end(mip), (void*)&prefix_line_args); 759 } 760 761 end_of_prefix = last_registered_file(0); 762 error = 0; 763 for (/*EMPTY*/; mip; mip = mip->mi_flink) { 764 mip->mi_fo = obuf; 765 mip->mi_head_end = obuf; 766 mip->mi_detachdir = detachdir; 767 mip->mi_detachall = detachall_flag; 768 error |= sendmessage(mip->mp, pipe_end(mip), igntab, NULL, mip); 769 close_top_files(end_of_prefix); /* don't close the prefixer! */ 770 } 771 return error; 772 } 773 774 775 #ifdef CHARSET_SUPPORT 776 /********************************************** 777 * higher level interface to run mime_ficonv(). 778 */ 779 static void 780 run_mime_ficonv(struct mime_info *mip, const char *charset) 781 { 782 FILE *fo; 783 iconv_t cd; 784 785 fo = pipe_end(mip); 786 787 if (charset == NULL || 788 mip->mi_charset == NULL || 789 strcasecmp(mip->mi_charset, charset) == 0 || 790 strcasecmp(mip->mi_charset, "unknown") == 0) 791 return; 792 793 cd = iconv_open(charset, mip->mi_charset); 794 if (cd == (iconv_t)-1) { 795 (void)fprintf(fo, "\t [ iconv_open failed: %s ]\n\n", 796 strerror(errno)); 797 (void)fflush(fo); /* flush here or see double! */ 798 return; 799 } 800 801 if (mip->mi_detachdir == NULL && /* don't contaminate the detach! */ 802 value(ENAME_MIME_CHARSET_VERBOSE)) 803 (void)fprintf(fo, "\t[ converting %s -> %s ]\n\n", 804 mip->mi_charset, charset); 805 806 mime_run_function(mime_ficonv, fo, cd); 807 808 (void)iconv_close(cd); 809 } 810 #endif /* CHARSET_SUPPORT */ 811 812 813 PUBLIC void 814 run_decoder(struct mime_info *mip, void(*fn)(FILE*, FILE*, void *)) 815 { 816 #ifdef CHARSET_SUPPORT 817 char *charset; 818 819 charset = value(ENAME_MIME_CHARSET); 820 if (charset && mip->mi_type && strcasecmp(mip->mi_type, "text") == 0) 821 run_mime_ficonv(mip, charset); 822 #endif /* CHARSET_SUPPORT */ 823 824 if (mip->mi_detachdir == NULL && 825 fn == mime_fio_copy)/* XXX - avoid an extra unnecessary pipe stage */ 826 return; 827 828 mime_run_function(fn, pipe_end(mip), 829 mip->mi_detachdir ? NULL : __UNCONST("add_lf")); 830 } 831 832 833 /* 834 * Determine how to handle the display based on the type and subtype 835 * fields. 836 */ 837 enum dispmode_e { 838 DM_IGNORE = 0x00, /* silently ignore part - must be zero! */ 839 DM_DISPLAY, /* decode and display the part */ 840 DM_UNKNOWN, /* unknown display */ 841 DM_BINARY, /* indicate binary data */ 842 DM_PGPSIGN, /* OpenPGP signed part */ 843 DM_PGPENCR, /* OpenPGP encrypted part */ 844 DM_PGPKEYS /* OpenPGP keys part */ 845 }; 846 #define APPLICATION_OCTET_STREAM DM_BINARY 847 848 static enum dispmode_e 849 get_display_mode(struct mime_info *mip, mime_codec_t dec) 850 { 851 struct mime_subtype_s { 852 const char *st_name; 853 enum dispmode_e st_dispmode; 854 }; 855 struct mime_type_s { 856 const char *mt_type; 857 const struct mime_subtype_s *mt_subtype; 858 enum dispmode_e mt_dispmode; /* default if NULL subtype */ 859 }; 860 static const struct mime_subtype_s text_subtype_tbl[] = { 861 { "plain", DM_DISPLAY }, 862 { "html", DM_DISPLAY }, /* rfc2854 */ 863 { "rfc822-headers", DM_DISPLAY }, 864 { "css", DM_DISPLAY }, /* rfc2318 */ 865 { "enriched", DM_DISPLAY }, /* rfc1523/rfc1563/rfc1896 */ 866 { "graphics", DM_DISPLAY }, /* rfc0553 */ 867 { "nroff", DM_DISPLAY }, /* rfc4263 */ 868 { "red", DM_DISPLAY }, /* rfc4102 */ 869 { NULL, DM_DISPLAY } /* default */ 870 }; 871 static const struct mime_subtype_s image_subtype_tbl[] = { 872 { "tiff", DM_BINARY }, /* rfc2302/rfc3302 */ 873 { "tiff-fx", DM_BINARY }, /* rfc3250/rfc3950 */ 874 { "t38", DM_BINARY }, /* rfc3362 */ 875 { NULL, DM_BINARY } /* default */ 876 }; 877 static const struct mime_subtype_s audio_subtype_tbl[] = { 878 { "mpeg", DM_BINARY }, /* rfc3003 */ 879 { "t38", DM_BINARY }, /* rfc4612 */ 880 { NULL, DM_BINARY } /* default */ 881 }; 882 static const struct mime_subtype_s video_subtype_tbl[] = { 883 { NULL, DM_BINARY } /* default */ 884 }; 885 static const struct mime_subtype_s application_subtype_tbl[] = { 886 { "octet-stream", APPLICATION_OCTET_STREAM }, 887 { "pgp-encrypted", DM_PGPENCR }, /* rfc3156 */ 888 { "pgp-keys", DM_PGPKEYS }, /* rfc3156 */ 889 { "pgp-signature", DM_PGPSIGN }, /* rfc3156 */ 890 { "pdf", DM_BINARY }, /* rfc3778 */ 891 { "whoispp-query", DM_UNKNOWN }, /* rfc2957 */ 892 { "whoispp-response", DM_UNKNOWN }, /* rfc2958 */ 893 { "font-tdpfr", DM_UNKNOWN }, /* rfc3073 */ 894 { "xhtml+xml", DM_UNKNOWN }, /* rfc3236 */ 895 { "ogg", DM_UNKNOWN }, /* rfc3534 */ 896 { "rdf+xml", DM_UNKNOWN }, /* rfc3870 */ 897 { "soap+xml", DM_UNKNOWN }, /* rfc3902 */ 898 { "mbox", DM_UNKNOWN }, /* rfc4155 */ 899 { "xv+xml", DM_UNKNOWN }, /* rfc4374 */ 900 { "smil", DM_UNKNOWN }, /* rfc4536 */ 901 { "smil+xml", DM_UNKNOWN }, /* rfc4536 */ 902 { "json", DM_UNKNOWN }, /* rfc4627 */ 903 { "voicexml+xml", DM_UNKNOWN }, /* rfc4267 */ 904 { "ssml+xml", DM_UNKNOWN }, /* rfc4267 */ 905 { "srgs", DM_UNKNOWN }, /* rfc4267 */ 906 { "srgs+xml", DM_UNKNOWN }, /* rfc4267 */ 907 { "ccxml+xml", DM_UNKNOWN }, /* rfc4267 */ 908 { "pls+xml.", DM_UNKNOWN }, /* rfc4267 */ 909 { NULL, APPLICATION_OCTET_STREAM } /* default */ 910 }; 911 static const struct mime_type_s mime_type_tbl[] = { 912 { "text", text_subtype_tbl, DM_DISPLAY }, 913 { "image", image_subtype_tbl, DM_IGNORE }, 914 { "audio", audio_subtype_tbl, DM_IGNORE }, 915 { "video", video_subtype_tbl, DM_IGNORE }, 916 { "application", application_subtype_tbl, APPLICATION_OCTET_STREAM }, 917 { NULL, NULL, DM_UNKNOWN }, /* default */ 918 }; 919 const struct mime_type_s *mtp; 920 const struct mime_subtype_s *stp; 921 const char *mi_type; 922 const char *mi_subtype; 923 924 /* 925 * Silently ignore all multipart bodies. 926 * 1) In the case of "multipart" types, this typically 927 * contains a message for non-mime enabled mail readers. 928 * 2) In the case of "message" type, there should be no body. 929 */ 930 if (mip->mi_ignore_body) /*is_multipart(mip) || is_message(mip))*/ 931 return DM_IGNORE; 932 933 /* 934 * If the encoding type given but not recognized, treat block 935 * as "application/octet-stream". rfc 2049 sec 2 part 2. 936 */ 937 if (mip->mi_encoding && dec == NULL) 938 return APPLICATION_OCTET_STREAM; 939 940 mi_type = mip->mi_type; 941 mi_subtype = mip->mi_type ? mip->mi_subtype : NULL; 942 943 /* 944 * If there was no type specified, display anyway so we don't 945 * miss anything. (The encoding type is known.) 946 */ 947 if (mi_type == NULL) 948 return DM_DISPLAY; /* XXX - default to something safe! */ 949 950 for (mtp = mime_type_tbl; mtp->mt_type; mtp++) { 951 if (strcasecmp(mtp->mt_type, mi_type) == 0) { 952 if (mi_subtype == NULL) 953 return mtp->mt_dispmode; 954 for (stp = mtp->mt_subtype; stp->st_name; stp++) { 955 if (strcasecmp(stp->st_name, mi_subtype) == 0) 956 return stp->st_dispmode; 957 } 958 return stp->st_dispmode; 959 } 960 } 961 return mtp->mt_dispmode; 962 } 963 964 965 PUBLIC FILE * 966 mime_decode_body(struct mime_info *mip) 967 { 968 static enum dispmode_e dispmode; 969 mime_codec_t dec; 970 const char *cmd; 971 972 /* close anything left over from mime_decode_head() */ 973 close_top_files(mip->mi_head_end); 974 975 /* 976 * Make sure we flush everything down the pipe so children 977 * don't see it. 978 */ 979 (void)fflush(pipe_end(mip)); 980 981 if (mip->mi_detachdir) /* We are detaching! Ignore the hooks. */ 982 return mime_detach_parts(mip); 983 984 cmd = NULL; 985 if (mip->mi_command_hook == NULL) 986 cmd = get_command_hook(mip, "-body"); 987 988 dec = mime_fio_decoder(mip->mi_encoding); 989 990 /* 991 * If there is a filter running, we need to send the message 992 * to it. Otherwise, get the default display mode for this body. 993 */ 994 dispmode = cmd || mip->mi_command_hook ? DM_DISPLAY : get_display_mode(mip, dec); 995 996 if (dec == NULL) /* make sure we have a usable decoder */ 997 dec = mime_fio_decoder(MIME_TRANSFER_7BIT); 998 999 if (dispmode == DM_DISPLAY) { 1000 int flags; 1001 if (cmd == NULL) 1002 /* just get the flags */ 1003 flags = mime_run_command(mip->mi_command_hook, NULL); 1004 else 1005 flags = mime_run_command(cmd, pipe_end(mip)); 1006 if ((flags & CMD_FLAG_NO_DECODE) == 0) 1007 run_decoder(mip, dec); 1008 return pipe_end(mip); 1009 } 1010 else { 1011 static const struct msg_tbl_s { 1012 enum dispmode_e dm; 1013 const char *msg; 1014 } msg_tbl[] = { 1015 { DM_BINARY, "binary content" }, 1016 { DM_PGPSIGN, "OpenPGP signature" }, 1017 { DM_PGPENCR, "OpenPGP encrypted" }, 1018 { DM_PGPKEYS, "OpenPGP keys" }, 1019 { DM_UNKNOWN, "unknown data" }, 1020 { DM_IGNORE, NULL }, 1021 { -1, NULL }, 1022 }; 1023 const struct msg_tbl_s *mp; 1024 1025 for (mp = msg_tbl; mp->dm != -1; mp++) 1026 if (mp->dm == dispmode) 1027 break; 1028 1029 assert(mp->dm != -1); /* msg_tbl is short if this happens! */ 1030 1031 if (mp->msg) 1032 (void)fprintf(pipe_end(mip), " [%s]\n\n", mp->msg); 1033 1034 return NULL; 1035 } 1036 } 1037 1038 1039 /************************************************************************ 1040 * Higher level header decoding interface. 1041 * 1042 * The core routines are in mime_header.c. 1043 */ 1044 1045 /* 1046 * Decode a portion of the header field. 1047 * 1048 * linebuf buffer to decode into. 1049 * bufsize size of linebuf. 1050 * hdrline full header line including header name. 1051 * srcstr pointer to string to decode 1052 */ 1053 PUBLIC char * 1054 mime_decode_hfield(char *linebuf, size_t bufsize, const char *hdrline, char *srcstr) 1055 { 1056 hfield_decoder_t decode; 1057 decode = mime_hfield_decoder(hdrline); 1058 if (decode) { 1059 decode(linebuf, bufsize, srcstr); 1060 return linebuf; 1061 } 1062 return srcstr; 1063 } 1064 1065 /* 1066 * Return the next header field found in the input stream. 1067 * Return 0 if something found, -1 otherwise. 1068 * For a proper header, "*colon" is set to point to the colon 1069 * terminating the header name. Otherwise it is NULL. 1070 * 1071 * NOTE: unlike gethfield() in support.c this: 1072 * 1) preserves folding (newlines), 1073 * 2) reads until fgetln() gets an EOF, 1074 * 3) only sets *colon if there is a "proper" one. 1075 */ 1076 static int 1077 get_folded_hfield(FILE *f, char *linebuf, size_t bufsize, char **colon) 1078 { 1079 char *cp, *cp2; 1080 char *line; 1081 size_t len; 1082 1083 if ((cp = fgetln(f, &len)) == NULL) 1084 return -1; 1085 for (cp2 = cp; 1086 cp2 < cp + len && isprint((unsigned char)*cp2) && 1087 !is_WSP(*cp2) && *cp2 != ':'; 1088 cp2++) 1089 continue; 1090 len = MIN(bufsize - 1, len); 1091 bufsize -= len; 1092 (void)memcpy(linebuf, cp, len); 1093 *colon = *cp2 == ':' ? linebuf + (cp2 - cp) : NULL; 1094 line = linebuf + len; 1095 for (;;) { 1096 int c; 1097 (void)ungetc(c = getc(f), f); 1098 if (!is_WSP(c)) 1099 break; 1100 1101 if ((cp = fgetln(f, &len)) == NULL) 1102 break; 1103 len = MIN(bufsize - 1, len); 1104 bufsize -= len; 1105 if (len == 0) 1106 break; 1107 (void)memcpy(line, cp, len); 1108 line += len; 1109 } 1110 *line = 0; 1111 return 0; 1112 } 1113 1114 static void 1115 decode_header(FILE *fi, FILE *fo, void *cookie __unused) 1116 { 1117 char linebuf[LINESIZE]; 1118 char *colon; 1119 #ifdef __lint__ 1120 cookie = cookie; 1121 #endif 1122 while (get_folded_hfield(fi, linebuf, sizeof(linebuf), &colon) >= 0) { 1123 char decbuf[LINESIZE]; 1124 char *hdrstr; 1125 hdrstr = linebuf; 1126 if (colon) 1127 hdrstr = mime_decode_hfield(decbuf, sizeof(decbuf), hdrstr, hdrstr); 1128 (void)fprintf(fo, hdrstr); 1129 } 1130 } 1131 1132 PUBLIC FILE * 1133 mime_decode_header(struct mime_info *mip) 1134 { 1135 int flags; 1136 const char *cmd; 1137 FILE *fo; 1138 1139 fo = pipe_end(mip); 1140 1141 if (mip->mi_detachdir) { /* We are detaching. Don't run anything! */ 1142 (void)fflush(fo); 1143 return pipe_end(mip); 1144 } 1145 1146 if (mip->mi_partnum) 1147 (void)fprintf(fo, "----- Part %s -----\n", mip->mi_partstr); 1148 1149 (void)fflush(fo); /* Flush so the childern don't see it. */ 1150 1151 /* 1152 * install the message hook before the head hook. 1153 */ 1154 cmd = get_command_hook(mip, "-hook"); 1155 mip->mi_command_hook = cmd; 1156 if (cmd) { 1157 flags = mime_run_command(cmd, pipe_end(mip)); 1158 mip->mi_head_end = last_registered_file(0); 1159 } 1160 else { 1161 cmd = get_command_hook(mip, "-head"); 1162 mip->mi_head_end = last_registered_file(0); 1163 flags = mime_run_command(cmd, pipe_end(mip)); 1164 } 1165 1166 if (value(ENAME_MIME_DECODE_HDR) && (flags & CMD_FLAG_NO_DECODE) == 0) 1167 mime_run_function(decode_header, pipe_end(mip), NULL); 1168 1169 return pipe_end(mip); 1170 } 1171 1172 #endif /* MIME_SUPPORT */ 1173