procmime.c (66423B)
1 /* 2 * Claws Mail -- a GTK based, lightweight, and fast e-mail client 3 * Copyright (C) 1999-2024 the Claws Mail team and Hiroyuki Yamamoto 4 * 5 * This program is free software; you can redistribute it and/or modify 6 * it under the terms of the GNU General Public License as published by 7 * the Free Software Foundation; either version 3 of the License, or 8 * (at your option) any later version. 9 * 10 * This program is distributed in the hope that it will be useful, 11 * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 * GNU General Public License for more details. 14 * 15 * You should have received a copy of the GNU General Public License 16 * along with this program. If not, see <http://www.gnu.org/licenses/>. 17 */ 18 19 #include "defs.h" 20 21 #include <glib.h> 22 #include <glib/gi18n.h> 23 #include <string.h> 24 #include <locale.h> 25 #include <ctype.h> 26 #include <stdio.h> 27 #include <sys/types.h> 28 #include <sys/stat.h> 29 #include <unistd.h> 30 #include <errno.h> 31 32 #include "procmime.h" 33 #include "procheader.h" 34 #include "quoted-printable.h" 35 #include "unmime.h" 36 #include "html.h" 37 #include "codeconv.h" 38 #include "utils.h" 39 #include "prefs_common.h" 40 #include "prefs_gtk.h" 41 #include "alertpanel.h" 42 #include "account.h" 43 #include "file-utils.h" 44 45 static GHashTable *procmime_get_mime_type_table (void); 46 static MimeInfo *procmime_scan_file_short(const gchar *filename); 47 static MimeInfo *procmime_scan_queue_file_short(const gchar *filename); 48 static MimeInfo *procmime_scan_queue_file_full(const gchar *filename, gboolean short_scan); 49 50 MimeInfo *procmime_mimeinfo_new(void) 51 { 52 MimeInfo *mimeinfo; 53 54 mimeinfo = g_new0(MimeInfo, 1); 55 56 mimeinfo->content = MIMECONTENT_EMPTY; 57 mimeinfo->data.filename = NULL; 58 59 mimeinfo->type = MIMETYPE_UNKNOWN; 60 mimeinfo->encoding_type = ENC_UNKNOWN; 61 mimeinfo->typeparameters = g_hash_table_new(g_str_hash, g_str_equal); 62 63 mimeinfo->disposition = DISPOSITIONTYPE_UNKNOWN; 64 mimeinfo->dispositionparameters 65 = g_hash_table_new(g_str_hash, g_str_equal); 66 67 mimeinfo->node = g_node_new(mimeinfo); 68 69 return mimeinfo; 70 } 71 72 static gboolean procmime_mimeinfo_parameters_destroy(gpointer key, gpointer value, gpointer user_data) 73 { 74 g_free(key); 75 g_free(value); 76 77 return TRUE; 78 } 79 80 static gchar *forced_charset = NULL; 81 82 void procmime_force_charset(const gchar *str) 83 { 84 g_free(forced_charset); 85 forced_charset = NULL; 86 if (str) 87 forced_charset = g_strdup(str); 88 } 89 90 static EncodingType forced_encoding = 0; 91 92 void procmime_force_encoding(EncodingType encoding) 93 { 94 forced_encoding = encoding; 95 } 96 97 static gboolean free_func(GNode *node, gpointer data) 98 { 99 MimeInfo *mimeinfo = (MimeInfo *) node->data; 100 101 switch (mimeinfo->content) { 102 case MIMECONTENT_FILE: 103 if (mimeinfo->tmp) 104 unlink(mimeinfo->data.filename); 105 g_free(mimeinfo->data.filename); 106 break; 107 108 case MIMECONTENT_MEM: 109 if (mimeinfo->tmp) 110 g_free(mimeinfo->data.mem); 111 default: 112 break; 113 } 114 115 g_free(mimeinfo->subtype); 116 g_free(mimeinfo->description); 117 g_free(mimeinfo->id); 118 g_free(mimeinfo->location); 119 120 g_hash_table_foreach_remove(mimeinfo->typeparameters, 121 procmime_mimeinfo_parameters_destroy, NULL); 122 g_hash_table_destroy(mimeinfo->typeparameters); 123 g_hash_table_foreach_remove(mimeinfo->dispositionparameters, 124 procmime_mimeinfo_parameters_destroy, NULL); 125 g_hash_table_destroy(mimeinfo->dispositionparameters); 126 127 g_free(mimeinfo); 128 129 return FALSE; 130 } 131 132 void procmime_mimeinfo_free_all(MimeInfo **mimeinfo_ptr) 133 { 134 MimeInfo *mimeinfo = *mimeinfo_ptr; 135 GNode *node; 136 137 if (!mimeinfo) 138 return; 139 140 node = mimeinfo->node; 141 g_node_traverse(node, G_IN_ORDER, G_TRAVERSE_ALL, -1, free_func, NULL); 142 143 g_node_destroy(node); 144 145 *mimeinfo_ptr = NULL; 146 } 147 148 MimeInfo *procmime_mimeinfo_parent(MimeInfo *mimeinfo) 149 { 150 cm_return_val_if_fail(mimeinfo != NULL, NULL); 151 cm_return_val_if_fail(mimeinfo->node != NULL, NULL); 152 153 if (mimeinfo->node->parent == NULL) 154 return NULL; 155 return (MimeInfo *) mimeinfo->node->parent->data; 156 } 157 158 MimeInfo *procmime_mimeinfo_next(MimeInfo *mimeinfo) 159 { 160 cm_return_val_if_fail(mimeinfo != NULL, NULL); 161 cm_return_val_if_fail(mimeinfo->node != NULL, NULL); 162 163 if (mimeinfo->node->children) 164 return (MimeInfo *) mimeinfo->node->children->data; 165 if (mimeinfo->node->next) 166 return (MimeInfo *) mimeinfo->node->next->data; 167 168 if (mimeinfo->node->parent == NULL) 169 return NULL; 170 171 while (mimeinfo->node->parent != NULL) { 172 mimeinfo = (MimeInfo *) mimeinfo->node->parent->data; 173 if (mimeinfo->node->next) 174 return (MimeInfo *) mimeinfo->node->next->data; 175 } 176 177 return NULL; 178 } 179 180 MimeInfo *procmime_scan_message(MsgInfo *msginfo) 181 { 182 gchar *filename; 183 MimeInfo *mimeinfo; 184 185 filename = procmsg_get_message_file_path(msginfo); 186 if (!filename || !is_file_exist(filename)) { 187 g_free(filename); 188 return NULL; 189 } 190 191 if (!folder_has_parent_of_type(msginfo->folder, F_QUEUE) && 192 !folder_has_parent_of_type(msginfo->folder, F_DRAFT)) 193 mimeinfo = procmime_scan_file(filename); 194 else 195 mimeinfo = procmime_scan_queue_file(filename); 196 g_free(filename); 197 198 return mimeinfo; 199 } 200 201 MimeInfo *procmime_scan_message_short(MsgInfo *msginfo) 202 { 203 gchar *filename; 204 MimeInfo *mimeinfo; 205 206 filename = procmsg_get_message_file_path(msginfo); 207 if (!filename || !is_file_exist(filename)) { 208 g_free(filename); 209 return NULL; 210 } 211 212 if (!folder_has_parent_of_type(msginfo->folder, F_QUEUE) && 213 !folder_has_parent_of_type(msginfo->folder, F_DRAFT)) 214 mimeinfo = procmime_scan_file_short(filename); 215 else 216 mimeinfo = procmime_scan_queue_file_short(filename); 217 g_free(filename); 218 219 return mimeinfo; 220 } 221 222 enum 223 { 224 H_CONTENT_TRANSFER_ENCODING = 0, 225 H_CONTENT_TYPE = 1, 226 H_CONTENT_DISPOSITION = 2, 227 H_CONTENT_DESCRIPTION = 3, 228 H_SUBJECT = 4 229 }; 230 231 const gchar *procmime_mimeinfo_get_parameter(MimeInfo *mimeinfo, const gchar *name) 232 { 233 const gchar *value; 234 235 cm_return_val_if_fail(mimeinfo != NULL, NULL); 236 cm_return_val_if_fail(name != NULL, NULL); 237 238 value = g_hash_table_lookup(mimeinfo->dispositionparameters, name); 239 if (value == NULL) 240 value = g_hash_table_lookup(mimeinfo->typeparameters, name); 241 242 return value; 243 } 244 245 #define FLUSH_LASTLINE() { \ 246 if (*lastline != '\0') { \ 247 gint llen = 0; \ 248 strretchomp(lastline); \ 249 llen = strlen(lastline); \ 250 if (lastline[llen-1] == ' ' && \ 251 !(llen >= 2 && lastline[1] == ' ' && strchr(prefs_common.quote_chars, lastline[0]))) { \ 252 /* this is flowed */ \ 253 if (delsp) \ 254 lastline[llen-1] = '\0'; \ 255 if (fputs(lastline, outfp) == EOF) \ 256 err = TRUE; \ 257 } else { \ 258 if (fputs(lastline, outfp) == EOF) \ 259 err = TRUE; \ 260 if (fputs("\n", outfp) == EOF) \ 261 err = TRUE; \ 262 } \ 263 } \ 264 strcpy(lastline, buf); \ 265 } 266 267 gboolean procmime_decode_content(MimeInfo *mimeinfo) 268 { 269 gchar buf[BUFFSIZE]; 270 glong readend; 271 gchar *tmpfilename; 272 FILE *outfp, *infp; 273 GStatBuf statbuf; 274 gboolean tmp_file = FALSE; 275 gboolean flowed = FALSE; 276 gboolean delsp = FALSE; 277 gboolean err = FALSE; 278 gint state = 0; 279 guint save = 0; 280 281 cm_return_val_if_fail(mimeinfo != NULL, FALSE); 282 283 EncodingType encoding = forced_encoding 284 ? forced_encoding 285 : mimeinfo->encoding_type; 286 gchar lastline[BUFFSIZE]; 287 memset(lastline, 0, BUFFSIZE); 288 289 if (prefs_common.respect_flowed_format && 290 mimeinfo->type == MIMETYPE_TEXT && 291 !strcasecmp(mimeinfo->subtype, "plain")) { 292 if (procmime_mimeinfo_get_parameter(mimeinfo, "format") != NULL && 293 !strcasecmp(procmime_mimeinfo_get_parameter(mimeinfo, "format"),"flowed")) 294 flowed = TRUE; 295 if (flowed && 296 procmime_mimeinfo_get_parameter(mimeinfo, "delsp") != NULL && 297 !strcasecmp(procmime_mimeinfo_get_parameter(mimeinfo, "delsp"),"yes")) 298 delsp = TRUE; 299 } 300 301 if (!flowed && ( 302 encoding == ENC_UNKNOWN || 303 encoding == ENC_BINARY || 304 encoding == ENC_7BIT || 305 encoding == ENC_8BIT 306 )) 307 return TRUE; 308 309 if (mimeinfo->type == MIMETYPE_MULTIPART || mimeinfo->type == MIMETYPE_MESSAGE) 310 return TRUE; 311 312 if (mimeinfo->data.filename == NULL) 313 return FALSE; 314 315 infp = g_fopen(mimeinfo->data.filename, "rb"); 316 if (!infp) { 317 FILE_OP_ERROR(mimeinfo->data.filename, "g_fopen"); 318 return FALSE; 319 } 320 if (fseek(infp, mimeinfo->offset, SEEK_SET) < 0) { 321 FILE_OP_ERROR(mimeinfo->data.filename, "fseek"); 322 fclose(infp); 323 return FALSE; 324 } 325 326 outfp = get_tmpfile_in_dir(get_mime_tmp_dir(), &tmpfilename); 327 if (!outfp) { 328 perror("tmpfile"); 329 fclose(infp); 330 g_free(tmpfilename); 331 return FALSE; 332 } 333 334 tmp_file = TRUE; 335 readend = mimeinfo->offset + mimeinfo->length; 336 337 *buf = '\0'; 338 if (encoding == ENC_QUOTED_PRINTABLE) { 339 while ((ftell(infp) < readend) && (fgets(buf, sizeof(buf), infp) != NULL)) { 340 size_t len; 341 len = qp_decode_line(buf); 342 buf[len] = '\0'; 343 if (!flowed) { 344 if (fwrite(buf, 1, len, outfp) < len) 345 err = TRUE; 346 } else { 347 FLUSH_LASTLINE(); 348 } 349 } 350 if (flowed) 351 FLUSH_LASTLINE(); 352 } else if (encoding == ENC_BASE64) { 353 gchar outbuf[BUFFSIZE + 1]; 354 glong len; 355 gsize inlen, inread; 356 gboolean got_error = FALSE; 357 gboolean uncanonicalize = FALSE; 358 FILE *tmpfp = NULL; 359 gboolean null_bytes = FALSE; 360 gboolean starting = TRUE; 361 362 if (mimeinfo->type == MIMETYPE_TEXT || 363 mimeinfo->type == MIMETYPE_MESSAGE) { 364 uncanonicalize = TRUE; 365 tmpfp = my_tmpfile(); 366 if (!tmpfp) { 367 perror("my_tmpfile"); 368 if (tmp_file) 369 fclose(outfp); 370 fclose(infp); 371 g_free(tmpfilename); 372 return FALSE; 373 } 374 } else 375 tmpfp = outfp; 376 377 while ((inlen = MIN(readend - ftell(infp), sizeof(buf))) > 0 && !err) { 378 inread = fread(buf, 1, inlen, infp); 379 memset(outbuf, 0, sizeof(buf)); 380 len = (glong)g_base64_decode_step(buf, inlen, outbuf, &state, &save); 381 if (uncanonicalize == TRUE && strlen(outbuf) < (size_t)len && starting) { 382 uncanonicalize = FALSE; 383 null_bytes = TRUE; 384 } 385 starting = FALSE; 386 if (((inread != inlen) || len < 0) && !got_error) { 387 g_warning("bad BASE64 content"); 388 if (fwrite(_("[Error decoding BASE64]\n"), 389 sizeof(gchar), 390 strlen(_("[Error decoding BASE64]\n")), 391 tmpfp) < strlen(_("[Error decoding BASE64]\n"))) 392 g_warning("error decoding BASE64"); 393 got_error = TRUE; 394 continue; 395 } else if (len >= 0) { 396 /* print out the error message only once 397 * per block */ 398 if (null_bytes) { 399 /* we won't uncanonicalize, output to outfp directly */ 400 if (fwrite(outbuf, sizeof(gchar), (size_t)len, outfp) < (size_t)len) 401 err = TRUE; 402 } else { 403 if (fwrite(outbuf, sizeof(gchar), (size_t)len, tmpfp) < (size_t)len) 404 err = TRUE; 405 } 406 got_error = FALSE; 407 } 408 } 409 410 if (uncanonicalize) { 411 rewind(tmpfp); 412 while (fgets(buf, sizeof(buf), tmpfp) != NULL) { 413 strcrchomp(buf); 414 if (fputs(buf, outfp) == EOF) 415 err = TRUE; 416 } 417 } 418 if (tmpfp != outfp) { 419 fclose(tmpfp); 420 } 421 } else { 422 while ((ftell(infp) < readend) && (fgets(buf, sizeof(buf), infp) != NULL)) { 423 if (!flowed) { 424 if (fputs(buf, outfp) == EOF) 425 err = TRUE; 426 } else { 427 FLUSH_LASTLINE(); 428 } 429 } 430 if (flowed) 431 FLUSH_LASTLINE(); 432 if (err == TRUE) 433 g_warning("write error"); 434 } 435 436 fclose(outfp); 437 fclose(infp); 438 439 if (err == TRUE) { 440 g_free(tmpfilename); 441 return FALSE; 442 } 443 444 if (g_stat(tmpfilename, &statbuf) < 0) { 445 FILE_OP_ERROR(tmpfilename, "stat"); 446 g_free(tmpfilename); 447 return FALSE; 448 } 449 450 if (mimeinfo->tmp) 451 unlink(mimeinfo->data.filename); 452 g_free(mimeinfo->data.filename); 453 mimeinfo->data.filename = tmpfilename; 454 mimeinfo->tmp = TRUE; 455 mimeinfo->offset = 0; 456 mimeinfo->length = statbuf.st_size; 457 mimeinfo->encoding_type = ENC_BINARY; 458 459 return TRUE; 460 } 461 462 gboolean procmime_encode_content(MimeInfo *mimeinfo, EncodingType encoding) 463 { 464 FILE *infp = NULL, *outfp; 465 glong len; 466 gchar *tmpfilename; 467 GStatBuf statbuf; 468 gboolean err = FALSE; 469 470 if (mimeinfo->content == MIMECONTENT_EMPTY) 471 return TRUE; 472 473 if (mimeinfo->encoding_type != ENC_UNKNOWN && 474 mimeinfo->encoding_type != ENC_BINARY && 475 mimeinfo->encoding_type != ENC_7BIT && 476 mimeinfo->encoding_type != ENC_8BIT) 477 if(!procmime_decode_content(mimeinfo)) 478 return FALSE; 479 480 outfp = get_tmpfile_in_dir(get_mime_tmp_dir(), &tmpfilename); 481 if (!outfp) { 482 perror("tmpfile"); 483 g_free(tmpfilename); 484 return FALSE; 485 } 486 487 if (mimeinfo->content == MIMECONTENT_FILE && mimeinfo->data.filename) { 488 if ((infp = g_fopen(mimeinfo->data.filename, "rb")) == NULL) { 489 g_warning("can't open file %s", mimeinfo->data.filename); 490 g_free(tmpfilename); 491 fclose(outfp); 492 return FALSE; 493 } 494 } else if (mimeinfo->content == MIMECONTENT_MEM) { 495 infp = str_open_as_stream(mimeinfo->data.mem); 496 if (infp == NULL) { 497 g_free(tmpfilename); 498 fclose(outfp); 499 return FALSE; 500 } 501 } else { 502 g_free(tmpfilename); 503 fclose(outfp); 504 g_warning("unknown mimeinfo"); 505 return FALSE; 506 } 507 508 if (encoding == ENC_BASE64) { 509 gchar inbuf[B64_LINE_SIZE], *out; 510 FILE *tmp_fp = infp; 511 gchar *tmp_file = NULL; 512 513 if (mimeinfo->type == MIMETYPE_TEXT || 514 mimeinfo->type == MIMETYPE_MESSAGE) { 515 if (mimeinfo->content == MIMECONTENT_FILE) { 516 tmp_file = get_tmp_file(); 517 if (canonicalize_file(mimeinfo->data.filename, tmp_file) < 0) { 518 g_free(tmp_file); 519 g_free(tmpfilename); 520 fclose(infp); 521 fclose(outfp); 522 return FALSE; 523 } 524 if ((tmp_fp = g_fopen(tmp_file, "rb")) == NULL) { 525 FILE_OP_ERROR(tmp_file, "g_fopen"); 526 unlink(tmp_file); 527 g_free(tmp_file); 528 g_free(tmpfilename); 529 fclose(infp); 530 fclose(outfp); 531 return FALSE; 532 } 533 } else { 534 gchar *out = canonicalize_str(mimeinfo->data.mem); 535 fclose(infp); 536 infp = str_open_as_stream(out); 537 tmp_fp = infp; 538 g_free(out); 539 if (infp == NULL) { 540 g_free(tmpfilename); 541 fclose(outfp); 542 return FALSE; 543 } 544 } 545 } 546 547 while ((len = (glong)fread(inbuf, sizeof(gchar), 548 B64_LINE_SIZE, tmp_fp)) 549 == B64_LINE_SIZE) { 550 out = g_base64_encode(inbuf, B64_LINE_SIZE); 551 if (fputs(out, outfp) == EOF) 552 err = TRUE; 553 g_free(out); 554 if (fputc('\n', outfp) == EOF) 555 err = TRUE; 556 } 557 if (len > 0 && feof(tmp_fp)) { 558 out = g_base64_encode(inbuf, (gsize)len); 559 if (fputs(out, outfp) == EOF) 560 err = TRUE; 561 g_free(out); 562 if (fputc('\n', outfp) == EOF) 563 err = TRUE; 564 } 565 566 if (tmp_file) { 567 fclose(tmp_fp); 568 unlink(tmp_file); 569 g_free(tmp_file); 570 } 571 } else if (encoding == ENC_QUOTED_PRINTABLE) { 572 gchar inbuf[BUFFSIZE], outbuf[BUFFSIZE * 4]; 573 574 while (fgets(inbuf, sizeof(inbuf), infp) != NULL) { 575 qp_encode_line(outbuf, inbuf); 576 577 if (!strncmp("From ", outbuf, sizeof("From ")-1)) { 578 gchar *tmpbuf = outbuf; 579 580 tmpbuf += sizeof("From ")-1; 581 582 if (fputs("=46rom ", outfp) == EOF) 583 err = TRUE; 584 if (fputs(tmpbuf, outfp) == EOF) 585 err = TRUE; 586 } else { 587 if (fputs(outbuf, outfp) == EOF) 588 err = TRUE; 589 } 590 } 591 } else { 592 gchar buf[BUFFSIZE]; 593 594 while (fgets(buf, sizeof(buf), infp) != NULL) { 595 strcrchomp(buf); 596 if (fputs(buf, outfp) == EOF) 597 err = TRUE; 598 } 599 } 600 601 fclose(outfp); 602 fclose(infp); 603 604 if (err == TRUE) { 605 g_free(tmpfilename); 606 return FALSE; 607 } 608 609 if (mimeinfo->content == MIMECONTENT_FILE) { 610 if (mimeinfo->tmp && (mimeinfo->data.filename != NULL)) 611 unlink(mimeinfo->data.filename); 612 g_free(mimeinfo->data.filename); 613 } else if (mimeinfo->content == MIMECONTENT_MEM) { 614 if (mimeinfo->tmp && (mimeinfo->data.mem != NULL)) 615 g_free(mimeinfo->data.mem); 616 } 617 618 if (g_stat(tmpfilename, &statbuf) < 0) { 619 FILE_OP_ERROR(tmpfilename, "stat"); 620 g_free(tmpfilename); 621 return FALSE; 622 } 623 mimeinfo->content = MIMECONTENT_FILE; 624 mimeinfo->data.filename = tmpfilename; 625 mimeinfo->tmp = TRUE; 626 mimeinfo->offset = 0; 627 mimeinfo->length = statbuf.st_size; 628 mimeinfo->encoding_type = encoding; 629 630 return TRUE; 631 } 632 633 static gint procmime_get_part_to_stream(FILE *outfp, MimeInfo *mimeinfo) 634 { 635 FILE *infp; 636 gchar buf[BUFFSIZE]; 637 glong restlength, readlength; 638 gint saved_errno = 0; 639 640 cm_return_val_if_fail(outfp != NULL, -1); 641 cm_return_val_if_fail(mimeinfo != NULL, -1); 642 643 if (mimeinfo->encoding_type != ENC_BINARY && !procmime_decode_content(mimeinfo)) 644 return -EINVAL; 645 646 if ((infp = g_fopen(mimeinfo->data.filename, "rb")) == NULL) { 647 saved_errno = errno; 648 FILE_OP_ERROR(mimeinfo->data.filename, "g_fopen"); 649 return -(saved_errno); 650 } 651 if (fseek(infp, mimeinfo->offset, SEEK_SET) < 0) { 652 saved_errno = errno; 653 FILE_OP_ERROR(mimeinfo->data.filename, "fseek"); 654 fclose(infp); 655 return -(saved_errno); 656 } 657 658 restlength = mimeinfo->length; 659 660 while ((restlength > 0) && ((readlength = fread(buf, 1, restlength > BUFFSIZE ? BUFFSIZE : restlength, infp)) > 0)) { 661 if (fwrite(buf, 1, (size_t)readlength, outfp) != (size_t)readlength) { 662 saved_errno = errno; 663 fclose(infp); 664 return -(saved_errno); 665 } 666 restlength -= readlength; 667 } 668 669 fclose(infp); 670 rewind(outfp); 671 672 return 0; 673 } 674 675 gint procmime_get_part(const gchar *outfile, MimeInfo *mimeinfo) 676 { 677 FILE *outfp; 678 gint result; 679 gint saved_errno = 0; 680 681 cm_return_val_if_fail(outfile != NULL, -1); 682 683 if ((outfp = g_fopen(outfile, "wb")) == NULL) { 684 saved_errno = errno; 685 FILE_OP_ERROR(outfile, "g_fopen"); 686 return -(saved_errno); 687 } 688 689 result = procmime_get_part_to_stream(outfp, mimeinfo); 690 691 if (fclose(outfp) == EOF) { 692 saved_errno = errno; 693 FILE_OP_ERROR(outfile, "fclose"); 694 if (unlink(outfile) < 0) 695 FILE_OP_ERROR(outfile, "unlink"); 696 return -(saved_errno); 697 } 698 699 return result; 700 } 701 702 gboolean procmime_scan_text_content(MimeInfo *mimeinfo, 703 gboolean (*scan_callback)(const gchar *str, gpointer cb_data), 704 gpointer cb_data) 705 { 706 FILE *tmpfp; 707 const gchar *src_codeset; 708 gboolean conv_fail = FALSE; 709 gchar buf[BUFFSIZE]; 710 gchar *str; 711 gboolean scan_ret = FALSE; 712 int r; 713 714 cm_return_val_if_fail(mimeinfo != NULL, TRUE); 715 cm_return_val_if_fail(scan_callback != NULL, TRUE); 716 717 if (!procmime_decode_content(mimeinfo)) 718 return TRUE; 719 720 tmpfp = my_tmpfile(); 721 722 if (tmpfp == NULL) { 723 FILE_OP_ERROR("tmpfile", "open"); 724 return TRUE; 725 } 726 727 if ((r = procmime_get_part_to_stream(tmpfp, mimeinfo)) < 0) { 728 g_warning("procmime_get_part_to_stream error %d", r); 729 return TRUE; 730 } 731 732 src_codeset = forced_charset 733 ? forced_charset : 734 procmime_mimeinfo_get_parameter(mimeinfo, "charset"); 735 736 /* use supersets transparently when possible */ 737 if (!forced_charset && src_codeset && !strcasecmp(src_codeset, CS_ISO_8859_1)) 738 src_codeset = CS_WINDOWS_1252; 739 else if (!forced_charset && src_codeset && !strcasecmp(src_codeset, CS_X_GBK)) 740 src_codeset = CS_GB18030; 741 else if (!forced_charset && src_codeset && !strcasecmp(src_codeset, CS_GBK)) 742 src_codeset = CS_GB18030; 743 else if (!forced_charset && src_codeset && !strcasecmp(src_codeset, CS_GB2312)) 744 src_codeset = CS_GB18030; 745 else if (!forced_charset && src_codeset && !strcasecmp(src_codeset, CS_X_VIET_VPS)) 746 src_codeset = CS_WINDOWS_874; 747 748 if (mimeinfo->type == MIMETYPE_TEXT && !g_ascii_strcasecmp(mimeinfo->subtype, "html")) { 749 SC_HTMLParser *parser; 750 CodeConverter *conv; 751 752 conv = conv_code_converter_new(src_codeset); 753 parser = sc_html_parser_new(tmpfp, conv); 754 while ((str = sc_html_parse(parser)) != NULL) { 755 if ((scan_ret = scan_callback(str, cb_data)) == TRUE) 756 break; 757 } 758 sc_html_parser_destroy(parser); 759 conv_code_converter_destroy(conv); 760 } else if (mimeinfo->type == MIMETYPE_TEXT && mimeinfo->disposition != DISPOSITIONTYPE_ATTACHMENT) { 761 while (fgets(buf, sizeof(buf), tmpfp) != NULL) { 762 str = conv_codeset_strdup(buf, src_codeset, CS_UTF_8); 763 if (str) { 764 if ((scan_ret = scan_callback(str, cb_data)) == TRUE) { 765 g_free(str); 766 break; 767 } 768 g_free(str); 769 } else { 770 conv_fail = TRUE; 771 if ((scan_ret = scan_callback(buf, cb_data)) == TRUE) 772 break; 773 } 774 } 775 } 776 777 if (conv_fail) 778 g_warning("procmime_get_text_content(): code conversion failed"); 779 780 fclose(tmpfp); 781 782 return scan_ret; 783 } 784 785 static gboolean scan_fputs_cb(const gchar *str, gpointer fp) 786 { 787 if (fputs(str, (FILE *)fp) == EOF) 788 return TRUE; 789 790 return FALSE; 791 } 792 793 FILE *procmime_get_text_content(MimeInfo *mimeinfo) 794 { 795 FILE *outfp; 796 gboolean err; 797 798 if ((outfp = my_tmpfile()) == NULL) { 799 perror("my_tmpfile"); 800 return NULL; 801 } 802 803 err = procmime_scan_text_content(mimeinfo, scan_fputs_cb, outfp); 804 805 rewind(outfp); 806 if (err == TRUE) { 807 fclose(outfp); 808 return NULL; 809 } 810 return outfp; 811 812 } 813 814 FILE *procmime_get_binary_content(MimeInfo *mimeinfo) 815 { 816 FILE *outfp; 817 818 cm_return_val_if_fail(mimeinfo != NULL, NULL); 819 820 if (!procmime_decode_content(mimeinfo)) 821 return NULL; 822 823 outfp = my_tmpfile(); 824 825 if (procmime_get_part_to_stream(outfp, mimeinfo) < 0) { 826 return NULL; 827 } 828 829 return outfp; 830 } 831 832 /* search the first text part of (multipart) MIME message, 833 decode, convert it and output to outfp. */ 834 FILE *procmime_get_first_text_content(MsgInfo *msginfo) 835 { 836 FILE *outfp = NULL; 837 MimeInfo *mimeinfo, *partinfo; 838 gboolean empty_ok = FALSE, short_scan = TRUE; 839 840 cm_return_val_if_fail(msginfo != NULL, NULL); 841 842 /* first we try to short-scan (for speed), refusing empty parts */ 843 scan_again: 844 if (short_scan) 845 mimeinfo = procmime_scan_message_short(msginfo); 846 else 847 mimeinfo = procmime_scan_message(msginfo); 848 if (!mimeinfo) return NULL; 849 850 partinfo = mimeinfo; 851 while (partinfo && (partinfo->type != MIMETYPE_TEXT || 852 (partinfo->length == 0 && !empty_ok))) { 853 partinfo = procmime_mimeinfo_next(partinfo); 854 } 855 if (partinfo) 856 outfp = procmime_get_text_content(partinfo); 857 else if (!empty_ok && short_scan) { 858 /* if short scan didn't find a non-empty part, rescan 859 * fully for non-empty parts 860 */ 861 short_scan = FALSE; 862 procmime_mimeinfo_free_all(&mimeinfo); 863 goto scan_again; 864 } else if (!empty_ok && !short_scan) { 865 /* if full scan didn't find a non-empty part, rescan 866 * accepting empty parts 867 */ 868 empty_ok = TRUE; 869 procmime_mimeinfo_free_all(&mimeinfo); 870 goto scan_again; 871 } 872 procmime_mimeinfo_free_all(&mimeinfo); 873 874 return outfp; 875 } 876 877 gchar *procmime_get_tmp_file_name(MimeInfo *mimeinfo) 878 { 879 static guint32 id = 0; 880 gchar *base; 881 gchar *filename; 882 gchar f_prefix[10]; 883 884 cm_return_val_if_fail(mimeinfo != NULL, NULL); 885 886 g_snprintf(f_prefix, sizeof(f_prefix), "%08x.", id++); 887 888 if ((mimeinfo->type == MIMETYPE_TEXT) && !g_ascii_strcasecmp(mimeinfo->subtype, "html")) 889 base = g_strdup("mimetmp.html"); 890 else { 891 const gchar *basetmp1; 892 gchar *basetmp2; 893 894 basetmp1 = procmime_mimeinfo_get_parameter(mimeinfo, "filename"); 895 if (basetmp1 == NULL) 896 basetmp1 = procmime_mimeinfo_get_parameter(mimeinfo, "name"); 897 if (basetmp1 == NULL) 898 basetmp1 = "mimetmp"; 899 basetmp2 = g_path_get_basename(basetmp1); 900 if (*basetmp2 == '\0') { 901 g_free(basetmp2); 902 basetmp2 = g_strdup("mimetmp"); 903 } 904 base = conv_filename_from_utf8(basetmp2); 905 g_free(basetmp2); 906 subst_for_shellsafe_filename(base); 907 } 908 909 filename = g_strconcat(get_mime_tmp_dir(), G_DIR_SEPARATOR_S, 910 f_prefix, base, NULL); 911 912 g_free(base); 913 914 return filename; 915 } 916 917 static GList *mime_type_list = NULL; 918 919 gchar *procmime_get_mime_type(const gchar *filename) 920 { 921 const gchar *p; 922 gchar *ext = NULL; 923 gchar *base; 924 gchar *str; 925 static GHashTable *mime_type_table = NULL; 926 MimeType *mime_type; 927 928 if (!mime_type_table) { 929 mime_type_table = procmime_get_mime_type_table(); 930 if (!mime_type_table) return NULL; 931 } 932 933 if (filename == NULL) 934 return NULL; 935 936 base = g_path_get_basename(filename); 937 if ((p = strrchr(base, '.')) != NULL) 938 ext = g_utf8_strdown(p + 1, -1); 939 else 940 ext = g_utf8_strdown(base, -1); 941 g_free(base); 942 943 mime_type = g_hash_table_lookup(mime_type_table, ext); 944 945 if (mime_type) { 946 str = g_strconcat(mime_type->type, "/", mime_type->sub_type, 947 NULL); 948 debug_print("got type %s for %s\n", str, ext); 949 g_free(ext); 950 return str; 951 } 952 g_free(ext); 953 return NULL; 954 } 955 956 957 static guint procmime_str_hash(gconstpointer gptr) 958 { 959 guint hash_result = 0; 960 const char *str; 961 962 for (str = gptr; str && *str; str++) { 963 if (isupper(*str)) hash_result += (*str + ' '); 964 else hash_result += *str; 965 } 966 967 return hash_result; 968 } 969 970 static gint procmime_str_equal(gconstpointer gptr1, gconstpointer gptr2) 971 { 972 const char *str1 = gptr1; 973 const char *str2 = gptr2; 974 975 return !g_utf8_collate(str1, str2); 976 } 977 978 static GHashTable *procmime_get_mime_type_table(void) 979 { 980 GHashTable *table = NULL; 981 GList *cur; 982 MimeType *mime_type; 983 gchar **exts; 984 985 if (!mime_type_list) { 986 mime_type_list = procmime_get_mime_type_list(); 987 if (!mime_type_list) return NULL; 988 } 989 990 table = g_hash_table_new(procmime_str_hash, procmime_str_equal); 991 992 for (cur = mime_type_list; cur != NULL; cur = cur->next) { 993 gint i; 994 gchar *key; 995 996 mime_type = (MimeType *)cur->data; 997 998 if (!mime_type->extension) continue; 999 1000 exts = g_strsplit(mime_type->extension, " ", 16); 1001 for (i = 0; exts[i] != NULL; i++) { 1002 /* Don't overwrite previously inserted extension */ 1003 if (!g_hash_table_lookup(table, exts[i])) { 1004 key = g_strdup(exts[i]); 1005 g_hash_table_insert(table, key, mime_type); 1006 } 1007 } 1008 g_strfreev(exts); 1009 } 1010 1011 return table; 1012 } 1013 1014 GList *procmime_get_mime_type_list(void) 1015 { 1016 GList *list = NULL; 1017 FILE *fp; 1018 gchar buf[BUFFSIZE]; 1019 gchar *p; 1020 gchar *delim; 1021 MimeType *mime_type; 1022 gboolean fp_is_glob_file = TRUE; 1023 1024 if (mime_type_list) 1025 return mime_type_list; 1026 1027 if ((fp = g_fopen("/usr/share/mime/globs", "rb")) == NULL) 1028 { 1029 fp_is_glob_file = FALSE; 1030 if ((fp = g_fopen("/etc/mime.types", "rb")) == NULL) { 1031 if ((fp = g_fopen("/usr/share/misc/mime.types", "rb")) == NULL) { 1032 FILE_OP_ERROR("/usr/share/misc/mime.types", "g_fopen"); 1033 return NULL; 1034 } 1035 } 1036 } 1037 1038 while (fgets(buf, sizeof(buf), fp) != NULL) { 1039 p = strchr(buf, '#'); 1040 if (p) *p = '\0'; 1041 g_strstrip(buf); 1042 1043 p = buf; 1044 1045 if (fp_is_glob_file) { 1046 while (*p && !g_ascii_isspace(*p) && (*p!=':')) p++; 1047 } else { 1048 while (*p && !g_ascii_isspace(*p)) p++; 1049 } 1050 1051 if (*p) { 1052 *p = '\0'; 1053 p++; 1054 } 1055 delim = strchr(buf, '/'); 1056 if (delim == NULL) continue; 1057 *delim = '\0'; 1058 1059 mime_type = g_new(MimeType, 1); 1060 mime_type->type = g_strdup(buf); 1061 mime_type->sub_type = g_strdup(delim + 1); 1062 1063 if (fp_is_glob_file) { 1064 while (*p && (g_ascii_isspace(*p)||(*p=='*')||(*p=='.'))) p++; 1065 } else { 1066 while (*p && g_ascii_isspace(*p)) p++; 1067 } 1068 1069 if (*p) 1070 mime_type->extension = g_utf8_strdown(p, -1); 1071 else 1072 mime_type->extension = NULL; 1073 1074 list = g_list_append(list, mime_type); 1075 } 1076 1077 fclose(fp); 1078 1079 if (!list) 1080 g_warning("can't read mime.types"); 1081 1082 return list; 1083 } 1084 1085 EncodingType procmime_get_encoding_for_charset(const gchar *charset) 1086 { 1087 if (!charset) 1088 return ENC_8BIT; 1089 else if (!g_ascii_strncasecmp(charset, "ISO-2022-", 9) || 1090 !g_ascii_strcasecmp(charset, "US-ASCII")) 1091 return ENC_7BIT; 1092 else if (!g_ascii_strcasecmp(charset, "ISO-8859-5") || 1093 !g_ascii_strncasecmp(charset, "KOI8-", 5) || 1094 !g_ascii_strcasecmp(charset, "X-MAC-CYRILLIC") || 1095 !g_ascii_strcasecmp(charset, "MAC-CYRILLIC") || 1096 !g_ascii_strcasecmp(charset, "Windows-1251")) 1097 return ENC_8BIT; 1098 else if (!g_ascii_strncasecmp(charset, "ISO-8859-", 9)) 1099 return ENC_QUOTED_PRINTABLE; 1100 else if (!g_ascii_strncasecmp(charset, "UTF-8", 5)) 1101 return ENC_QUOTED_PRINTABLE; 1102 else 1103 return ENC_8BIT; 1104 } 1105 1106 EncodingType procmime_get_encoding_for_text_file(const gchar *file, gboolean *has_binary) 1107 { 1108 FILE *fp; 1109 guchar buf[BUFFSIZE]; 1110 size_t len; 1111 size_t octet_chars = 0; 1112 size_t total_len = 0; 1113 gfloat octet_percentage; 1114 gboolean force_b64 = FALSE; 1115 1116 if ((fp = g_fopen(file, "rb")) == NULL) { 1117 FILE_OP_ERROR(file, "g_fopen"); 1118 return ENC_UNKNOWN; 1119 } 1120 1121 while ((len = fread(buf, sizeof(guchar), sizeof(buf), fp)) > 0) { 1122 guchar *p; 1123 gulong i; 1124 1125 for (p = buf, i = 0; i < len; ++p, ++i) { 1126 if (*p & 0x80) 1127 ++octet_chars; 1128 if (*p == '\0') { 1129 force_b64 = TRUE; 1130 *has_binary = TRUE; 1131 } 1132 } 1133 total_len += len; 1134 } 1135 1136 fclose(fp); 1137 1138 if (total_len > 0) 1139 octet_percentage = (gfloat)octet_chars / (gfloat)total_len; 1140 else 1141 octet_percentage = 0.0; 1142 1143 debug_print("procmime_get_encoding_for_text_file(): " 1144 "8bit chars: %"G_GSIZE_FORMAT" / %"G_GSIZE_FORMAT" (%f%%)\n", octet_chars, total_len, 1145 100.0 * octet_percentage); 1146 1147 if (octet_percentage > 0.20 || force_b64) { 1148 debug_print("using BASE64\n"); 1149 return ENC_BASE64; 1150 } else if (octet_chars > 0) { 1151 debug_print("using quoted-printable\n"); 1152 return ENC_QUOTED_PRINTABLE; 1153 } else { 1154 debug_print("using 7bit\n"); 1155 return ENC_7BIT; 1156 } 1157 } 1158 1159 struct EncodingTable 1160 { 1161 gchar *str; 1162 EncodingType enc_type; 1163 }; 1164 1165 struct EncodingTable encoding_table[] = { 1166 {"7bit", ENC_7BIT}, 1167 {"8bit", ENC_8BIT}, 1168 {"binary", ENC_BINARY}, 1169 {"quoted-printable", ENC_QUOTED_PRINTABLE}, 1170 {"base64", ENC_BASE64}, 1171 {"x-uuencode", ENC_UNKNOWN}, 1172 {NULL, ENC_UNKNOWN}, 1173 }; 1174 1175 const gchar *procmime_get_encoding_str(EncodingType encoding) 1176 { 1177 struct EncodingTable *enc_table; 1178 1179 for (enc_table = encoding_table; enc_table->str != NULL; enc_table++) { 1180 if (enc_table->enc_type == encoding) 1181 return enc_table->str; 1182 } 1183 return NULL; 1184 } 1185 1186 /* --- NEW MIME STUFF --- */ 1187 struct TypeTable 1188 { 1189 gchar *str; 1190 MimeMediaType type; 1191 }; 1192 1193 static struct TypeTable mime_type_table[] = { 1194 {"text", MIMETYPE_TEXT}, 1195 {"image", MIMETYPE_IMAGE}, 1196 {"audio", MIMETYPE_AUDIO}, 1197 {"video", MIMETYPE_VIDEO}, 1198 {"font", MIMETYPE_FONT}, 1199 {"model", MIMETYPE_MODEL}, 1200 {"chemical", MIMETYPE_CHEMICAL}, 1201 {"application", MIMETYPE_APPLICATION}, 1202 {"message", MIMETYPE_MESSAGE}, 1203 {"multipart", MIMETYPE_MULTIPART}, 1204 {NULL, 0}, 1205 }; 1206 1207 const gchar *procmime_get_media_type_str(MimeMediaType type) 1208 { 1209 struct TypeTable *type_table; 1210 1211 for (type_table = mime_type_table; type_table->str != NULL; type_table++) { 1212 if (type_table->type == type) 1213 return type_table->str; 1214 } 1215 return NULL; 1216 } 1217 1218 MimeMediaType procmime_get_media_type(const gchar *str) 1219 { 1220 struct TypeTable *typetablearray; 1221 1222 for (typetablearray = mime_type_table; typetablearray->str != NULL; typetablearray++) 1223 if (g_ascii_strncasecmp(str, typetablearray->str, strlen(typetablearray->str)) == 0) 1224 return typetablearray->type; 1225 1226 return MIMETYPE_UNKNOWN; 1227 } 1228 1229 /*! 1230 *\brief Safe wrapper for content type string. 1231 * 1232 *\return const gchar * Pointer to content type string. 1233 */ 1234 gchar *procmime_get_content_type_str(MimeMediaType type, 1235 const char *subtype) 1236 { 1237 const gchar *type_str = NULL; 1238 1239 if (subtype == NULL || !(type_str = procmime_get_media_type_str(type))) 1240 return g_strdup("unknown"); 1241 return g_strdup_printf("%s/%s", type_str, subtype); 1242 } 1243 1244 static int procmime_parse_mimepart(MimeInfo *parent, 1245 gchar *content_type, 1246 gchar *content_encoding, 1247 gchar *content_description, 1248 gchar *content_id, 1249 gchar *content_disposition, 1250 gchar *content_location, 1251 const gchar *original_msgid, 1252 const gchar *disposition_notification_hdr, 1253 const gchar *filename, 1254 guint offset, 1255 guint length, 1256 gboolean short_scan); 1257 1258 static void procmime_parse_message_rfc822(MimeInfo *mimeinfo, gboolean short_scan) 1259 { 1260 HeaderEntry hentry[] = {{"Content-Type:", NULL, TRUE}, 1261 {"Content-Transfer-Encoding:", 1262 NULL, FALSE}, 1263 {"Content-Description:", 1264 NULL, TRUE}, 1265 {"Content-ID:", 1266 NULL, TRUE}, 1267 {"Content-Disposition:", 1268 NULL, TRUE}, 1269 {"Content-Location:", 1270 NULL, TRUE}, 1271 {"MIME-Version:", 1272 NULL, TRUE}, 1273 {"Original-Message-ID:", 1274 NULL, TRUE}, 1275 {"Disposition:", 1276 NULL, TRUE}, 1277 {NULL, NULL, FALSE}}; 1278 glong content_start; 1279 guint i; 1280 FILE *fp; 1281 gchar *tmp; 1282 glong len = 0; 1283 1284 procmime_decode_content(mimeinfo); 1285 1286 fp = g_fopen(mimeinfo->data.filename, "rb"); 1287 if (fp == NULL) { 1288 FILE_OP_ERROR(mimeinfo->data.filename, "g_fopen"); 1289 return; 1290 } 1291 if (fseek(fp, mimeinfo->offset, SEEK_SET) < 0) { 1292 FILE_OP_ERROR(mimeinfo->data.filename, "fseek"); 1293 fclose(fp); 1294 return; 1295 } 1296 procheader_get_header_fields(fp, hentry); 1297 if (hentry[0].body != NULL) { 1298 tmp = conv_unmime_header(hentry[0].body, NULL, FALSE); 1299 g_free(hentry[0].body); 1300 hentry[0].body = tmp; 1301 } 1302 if (hentry[2].body != NULL) { 1303 tmp = conv_unmime_header(hentry[2].body, NULL, FALSE); 1304 g_free(hentry[2].body); 1305 hentry[2].body = tmp; 1306 } 1307 if (hentry[4].body != NULL) { 1308 tmp = conv_unmime_header(hentry[4].body, NULL, FALSE); 1309 g_free(hentry[4].body); 1310 hentry[4].body = tmp; 1311 } 1312 if (hentry[5].body != NULL) { 1313 tmp = conv_unmime_header(hentry[5].body, NULL, FALSE); 1314 g_free(hentry[5].body); 1315 hentry[5].body = tmp; 1316 } 1317 if (hentry[7].body != NULL) { 1318 tmp = conv_unmime_header(hentry[7].body, NULL, FALSE); 1319 g_free(hentry[7].body); 1320 hentry[7].body = tmp; 1321 } 1322 if (hentry[8].body != NULL) { 1323 tmp = conv_unmime_header(hentry[8].body, NULL, FALSE); 1324 g_free(hentry[8].body); 1325 hentry[8].body = tmp; 1326 } 1327 1328 content_start = ftell(fp); 1329 fclose(fp); 1330 1331 len = mimeinfo->length - (content_start - mimeinfo->offset); 1332 if (len < 0) 1333 len = 0; 1334 procmime_parse_mimepart(mimeinfo, 1335 hentry[0].body, hentry[1].body, 1336 hentry[2].body, hentry[3].body, 1337 hentry[4].body, hentry[5].body, 1338 hentry[7].body, hentry[8].body, 1339 mimeinfo->data.filename, content_start, 1340 len, short_scan); 1341 1342 for (i = 0; i < (sizeof hentry / sizeof hentry[0]); i++) { 1343 g_free(hentry[i].body); 1344 hentry[i].body = NULL; 1345 } 1346 } 1347 1348 static void procmime_parse_disposition_notification(MimeInfo *mimeinfo, 1349 const gchar *original_msgid, const gchar *disposition_notification_hdr, 1350 gboolean short_scan) 1351 { 1352 HeaderEntry hentry[] = {{"Original-Message-ID:", NULL, TRUE}, 1353 {"Disposition:", NULL, TRUE}, 1354 {NULL, NULL, FALSE}}; 1355 guint i; 1356 FILE *fp; 1357 gchar *orig_msg_id = NULL; 1358 gchar *disp = NULL; 1359 1360 procmime_decode_content(mimeinfo); 1361 1362 debug_print("parse disposition notification\n"); 1363 fp = g_fopen(mimeinfo->data.filename, "rb"); 1364 if (fp == NULL) { 1365 FILE_OP_ERROR(mimeinfo->data.filename, "g_fopen"); 1366 return; 1367 } 1368 if (fseek(fp, mimeinfo->offset, SEEK_SET) < 0) { 1369 FILE_OP_ERROR(mimeinfo->data.filename, "fseek"); 1370 fclose(fp); 1371 return; 1372 } 1373 1374 if (original_msgid && disposition_notification_hdr) { 1375 hentry[0].body = g_strdup(original_msgid); 1376 hentry[1].body = g_strdup(disposition_notification_hdr); 1377 } else { 1378 procheader_get_header_fields(fp, hentry); 1379 } 1380 1381 fclose(fp); 1382 1383 if (!hentry[0].body || !hentry[1].body) { 1384 debug_print("MsgId %s, Disp %s\n", 1385 hentry[0].body ? hentry[0].body:"(nil)", 1386 hentry[1].body ? hentry[1].body:"(nil)"); 1387 goto bail; 1388 } 1389 1390 orig_msg_id = g_strdup(hentry[0].body); 1391 disp = g_strdup(hentry[1].body); 1392 1393 extract_parenthesis(orig_msg_id, '<', '>'); 1394 remove_space(orig_msg_id); 1395 1396 if (strstr(disp, "displayed")) { 1397 /* find sent message, if possible */ 1398 MsgInfo *info = NULL; 1399 GList *flist; 1400 debug_print("%s has been displayed.\n", orig_msg_id); 1401 for (flist = folder_get_list(); flist != NULL; flist = g_list_next(flist)) { 1402 FolderItem *outbox = ((Folder *)(flist->data))->outbox; 1403 if (!outbox) { 1404 debug_print("skipping folder with no outbox...\n"); 1405 continue; 1406 } 1407 info = folder_item_get_msginfo_by_msgid(outbox, orig_msg_id); 1408 debug_print("%s %s in %s\n", info?"found":"didn't find", orig_msg_id, outbox->path); 1409 if (info) { 1410 procmsg_msginfo_set_flags(info, MSG_RETRCPT_GOT, 0); 1411 procmsg_msginfo_free(&info); 1412 } 1413 } 1414 } 1415 g_free(orig_msg_id); 1416 g_free(disp); 1417 bail: 1418 for (i = 0; i < (sizeof hentry / sizeof hentry[0]); i++) { 1419 g_free(hentry[i].body); 1420 hentry[i].body = NULL; 1421 } 1422 } 1423 1424 #define GET_HEADERS() { \ 1425 procheader_get_header_fields(fp, hentry); \ 1426 if (hentry[0].body != NULL) { \ 1427 tmp = conv_unmime_header(hentry[0].body, NULL, FALSE); \ 1428 g_free(hentry[0].body); \ 1429 hentry[0].body = tmp; \ 1430 } \ 1431 if (hentry[2].body != NULL) { \ 1432 tmp = conv_unmime_header(hentry[2].body, NULL, FALSE); \ 1433 g_free(hentry[2].body); \ 1434 hentry[2].body = tmp; \ 1435 } \ 1436 if (hentry[4].body != NULL) { \ 1437 tmp = conv_unmime_header(hentry[4].body, NULL, FALSE); \ 1438 g_free(hentry[4].body); \ 1439 hentry[4].body = tmp; \ 1440 } \ 1441 if (hentry[5].body != NULL) { \ 1442 tmp = conv_unmime_header(hentry[5].body, NULL, FALSE); \ 1443 g_free(hentry[5].body); \ 1444 hentry[5].body = tmp; \ 1445 } \ 1446 if (hentry[6].body != NULL) { \ 1447 tmp = conv_unmime_header(hentry[6].body, NULL, FALSE); \ 1448 g_free(hentry[6].body); \ 1449 hentry[6].body = tmp; \ 1450 } \ 1451 if (hentry[7].body != NULL) { \ 1452 tmp = conv_unmime_header(hentry[7].body, NULL, FALSE); \ 1453 g_free(hentry[7].body); \ 1454 hentry[7].body = tmp; \ 1455 } \ 1456 } 1457 1458 static void procmime_parse_multipart(MimeInfo *mimeinfo, gboolean short_scan) 1459 { 1460 HeaderEntry hentry[] = {{"Content-Type:", NULL, TRUE}, 1461 {"Content-Transfer-Encoding:", 1462 NULL, FALSE}, 1463 {"Content-Description:", 1464 NULL, TRUE}, 1465 {"Content-ID:", 1466 NULL, TRUE}, 1467 {"Content-Disposition:", 1468 NULL, TRUE}, 1469 {"Content-Location:", 1470 NULL, TRUE}, 1471 {"Original-Message-ID:", 1472 NULL, TRUE}, 1473 {"Disposition:", 1474 NULL, TRUE}, 1475 {NULL, NULL, FALSE}}; 1476 gchar *tmp; 1477 gchar *boundary; 1478 gsize boundary_len = 0; 1479 glong lastoffset = -1; 1480 gulong i; 1481 gchar buf[BUFFSIZE]; 1482 FILE *fp; 1483 int result = 0; 1484 gboolean start_found = FALSE; 1485 gboolean end_found = FALSE; 1486 1487 boundary = g_hash_table_lookup(mimeinfo->typeparameters, "boundary"); 1488 if (!boundary) 1489 return; 1490 boundary_len = strlen(boundary); 1491 1492 procmime_decode_content(mimeinfo); 1493 1494 fp = g_fopen(mimeinfo->data.filename, "rb"); 1495 if (fp == NULL) { 1496 FILE_OP_ERROR(mimeinfo->data.filename, "g_fopen"); 1497 return; 1498 } 1499 1500 if (fseek(fp, mimeinfo->offset, SEEK_SET) < 0) { 1501 FILE_OP_ERROR(mimeinfo->data.filename, "fseek"); 1502 fclose(fp); 1503 return; 1504 } 1505 1506 while (fgets(buf, sizeof(buf), fp) != NULL && result == 0) { 1507 if (ftell(fp) - 1 > (mimeinfo->offset + mimeinfo->length)) 1508 break; 1509 1510 if (IS_BOUNDARY(buf, boundary, boundary_len)) { 1511 start_found = TRUE; 1512 1513 if (lastoffset != -1) { 1514 glong len = (ftell(fp) - strlen(buf)) - lastoffset - 1; 1515 if (len < 0) 1516 len = 0; 1517 result = procmime_parse_mimepart(mimeinfo, 1518 hentry[0].body, hentry[1].body, 1519 hentry[2].body, hentry[3].body, 1520 hentry[4].body, hentry[5].body, 1521 hentry[6].body, hentry[7].body, 1522 mimeinfo->data.filename, lastoffset, 1523 len, short_scan); 1524 if (result == 1 && short_scan) 1525 break; 1526 1527 } 1528 1529 if (buf[2 + boundary_len] == '-' && 1530 buf[2 + boundary_len + 1] == '-') { 1531 end_found = TRUE; 1532 break; 1533 } 1534 for (i = 0; i < (sizeof hentry / sizeof hentry[0]) ; i++) { 1535 g_free(hentry[i].body); 1536 hentry[i].body = NULL; 1537 } 1538 GET_HEADERS(); 1539 lastoffset = ftell(fp); 1540 } 1541 } 1542 1543 if (start_found && !end_found && lastoffset != -1) { 1544 glong len = (ftell(fp) - strlen(buf)) - lastoffset - 1; 1545 1546 if (len >= 0) { 1547 result = procmime_parse_mimepart(mimeinfo, 1548 hentry[0].body, hentry[1].body, 1549 hentry[2].body, hentry[3].body, 1550 hentry[4].body, hentry[5].body, 1551 hentry[6].body, hentry[7].body, 1552 mimeinfo->data.filename, lastoffset, 1553 len, short_scan); 1554 } 1555 mimeinfo->broken = TRUE; 1556 } 1557 1558 for (i = 0; i < (sizeof hentry / sizeof hentry[0]); i++) { 1559 g_free(hentry[i].body); 1560 hentry[i].body = NULL; 1561 } 1562 fclose(fp); 1563 } 1564 1565 static void parse_parameters(const gchar *parameters, GHashTable *table) 1566 { 1567 gchar *params, *param, *next; 1568 GSList *convlist = NULL, *concatlist = NULL, *cur; 1569 1570 params = g_strdup(parameters); 1571 param = params; 1572 next = params; 1573 for (; next != NULL; param = next) { 1574 gchar *attribute, *value, *tmp, *down_attr, *orig_down_attr; 1575 glong len; 1576 gboolean convert = FALSE; 1577 1578 next = strchr_with_skip_quote(param, '"', ';'); 1579 if (next != NULL) { 1580 next[0] = '\0'; 1581 next++; 1582 } 1583 1584 g_strstrip(param); 1585 1586 attribute = param; 1587 value = strchr(attribute, '='); 1588 if (value == NULL) 1589 continue; 1590 1591 value[0] = '\0'; 1592 value++; 1593 while (value[0] != '\0' && value[0] == ' ') 1594 value++; 1595 1596 down_attr = g_utf8_strdown(attribute, -1); 1597 orig_down_attr = down_attr; 1598 1599 len = down_attr ? strlen(down_attr):0; 1600 if (len > 0 && down_attr[len - 1] == '*') { 1601 gchar *srcpos, *dstpos, *endpos; 1602 1603 convert = TRUE; 1604 down_attr[len - 1] = '\0'; 1605 1606 srcpos = value; 1607 dstpos = value; 1608 endpos = value + strlen(value); 1609 while (srcpos < endpos) { 1610 if (*srcpos != '%') 1611 *dstpos = *srcpos; 1612 else { 1613 guchar dstvalue; 1614 1615 if (!get_hex_value(&dstvalue, srcpos[1], srcpos[2])) 1616 *dstpos = '?'; 1617 else 1618 *dstpos = dstvalue; 1619 srcpos += 2; 1620 } 1621 srcpos++; 1622 dstpos++; 1623 } 1624 *dstpos = '\0'; 1625 if (value[0] == '"') 1626 extract_quote(value, '"'); 1627 } else { 1628 if (value[0] == '"') 1629 extract_quote(value, '"'); 1630 else if ((tmp = strchr(value, ' ')) != NULL) 1631 *tmp = '\0'; 1632 } 1633 1634 if (down_attr) { 1635 while (down_attr[0] == ' ') 1636 down_attr++; 1637 while (down_attr[strlen(down_attr)-1] == ' ') 1638 down_attr[strlen(down_attr)-1] = '\0'; 1639 } 1640 1641 while (value[0] != '\0' && value[0] == ' ') 1642 value++; 1643 while (value[strlen(value)-1] == ' ') 1644 value[strlen(value)-1] = '\0'; 1645 1646 if (down_attr && strrchr(down_attr, '*') != NULL) { 1647 gchar *tmpattr; 1648 1649 tmpattr = g_strdup(down_attr); 1650 tmp = strrchr(tmpattr, '*'); 1651 tmp[0] = '\0'; 1652 1653 if ((tmp[1] == '0') && (tmp[2] == '\0') && 1654 (g_slist_find_custom(concatlist, down_attr, (GCompareFunc)g_strcmp0) == NULL)) 1655 concatlist = g_slist_prepend(concatlist, g_strdup(tmpattr)); 1656 1657 if (convert && (g_slist_find_custom(convlist, tmpattr, (GCompareFunc)g_strcmp0) == NULL)) 1658 convlist = g_slist_prepend(convlist, g_strdup(tmpattr)); 1659 1660 g_free(tmpattr); 1661 } else if (convert) { 1662 if (g_slist_find_custom(convlist, down_attr, (GCompareFunc)g_strcmp0) == NULL) 1663 convlist = g_slist_prepend(convlist, g_strdup(down_attr)); 1664 } 1665 1666 if (g_hash_table_lookup(table, down_attr) == NULL) 1667 g_hash_table_insert(table, g_strdup(down_attr), g_strdup(value)); 1668 g_free(orig_down_attr); 1669 } 1670 1671 for (cur = concatlist; cur != NULL; cur = g_slist_next(cur)) { 1672 gchar *attribute, *attrwnum, *partvalue; 1673 gint n = 0; 1674 GString *value; 1675 1676 attribute = (gchar *) cur->data; 1677 value = g_string_sized_new(64); 1678 1679 attrwnum = g_strdup_printf("%s*%d", attribute, n); 1680 while ((partvalue = g_hash_table_lookup(table, attrwnum)) != NULL) { 1681 g_string_append(value, partvalue); 1682 1683 g_hash_table_remove(table, attrwnum); 1684 g_free(attrwnum); 1685 n++; 1686 attrwnum = g_strdup_printf("%s*%d", attribute, n); 1687 } 1688 g_free(attrwnum); 1689 1690 g_hash_table_insert(table, g_strdup(attribute), g_strdup(value->str)); 1691 g_string_free(value, TRUE); 1692 } 1693 slist_free_strings_full(concatlist); 1694 1695 for (cur = convlist; cur != NULL; cur = g_slist_next(cur)) { 1696 gchar *attribute, *key, *value; 1697 gchar *charset, *lang, *oldvalue, *newvalue; 1698 1699 attribute = (gchar *) cur->data; 1700 if (!g_hash_table_lookup_extended( 1701 table, attribute, (gpointer *)(gchar *) &key, (gpointer *)(gchar *) &value)) 1702 continue; 1703 1704 charset = value; 1705 if (charset == NULL) 1706 continue; 1707 lang = strchr(charset, '\''); 1708 if (lang == NULL) 1709 continue; 1710 lang[0] = '\0'; 1711 lang++; 1712 oldvalue = strchr(lang, '\''); 1713 if (oldvalue == NULL) 1714 continue; 1715 oldvalue[0] = '\0'; 1716 oldvalue++; 1717 1718 newvalue = conv_codeset_strdup(oldvalue, charset, CS_UTF_8); 1719 1720 g_hash_table_remove(table, attribute); 1721 g_free(key); 1722 g_free(value); 1723 1724 g_hash_table_insert(table, g_strdup(attribute), newvalue); 1725 } 1726 slist_free_strings_full(convlist); 1727 1728 g_free(params); 1729 } 1730 1731 static void procmime_parse_content_type(const gchar *content_type, MimeInfo *mimeinfo) 1732 { 1733 cm_return_if_fail(content_type != NULL); 1734 cm_return_if_fail(mimeinfo != NULL); 1735 1736 /* RFC 2045, page 13 says that the mime subtype is MANDATORY; 1737 * if it's not available we use the default Content-Type */ 1738 if ((content_type[0] == '\0') || (strchr(content_type, '/') == NULL)) { 1739 mimeinfo->type = MIMETYPE_TEXT; 1740 mimeinfo->subtype = g_strdup("plain"); 1741 if (g_hash_table_lookup(mimeinfo->typeparameters, 1742 "charset") == NULL) { 1743 g_hash_table_insert(mimeinfo->typeparameters, 1744 g_strdup("charset"), 1745 g_strdup( 1746 conv_get_locale_charset_str_no_utf8())); 1747 } 1748 } else { 1749 gchar *type, *subtype, *params; 1750 1751 type = g_strdup(content_type); 1752 subtype = strchr(type, '/') + 1; 1753 *(subtype - 1) = '\0'; 1754 if ((params = strchr(subtype, ';')) != NULL) { 1755 params[0] = '\0'; 1756 params++; 1757 } 1758 1759 mimeinfo->type = procmime_get_media_type(type); 1760 mimeinfo->subtype = g_strstrip(g_strdup(subtype)); 1761 1762 /* Get mimeinfo->typeparameters */ 1763 if (params != NULL) 1764 parse_parameters(params, mimeinfo->typeparameters); 1765 1766 g_free(type); 1767 } 1768 } 1769 1770 static void procmime_parse_content_disposition(const gchar *content_disposition, MimeInfo *mimeinfo) 1771 { 1772 gchar *tmp, *params; 1773 1774 cm_return_if_fail(content_disposition != NULL); 1775 cm_return_if_fail(mimeinfo != NULL); 1776 1777 tmp = g_strdup(content_disposition); 1778 if ((params = strchr(tmp, ';')) != NULL) { 1779 params[0] = '\0'; 1780 params++; 1781 } 1782 g_strstrip(tmp); 1783 1784 if (!g_ascii_strcasecmp(tmp, "inline")) 1785 mimeinfo->disposition = DISPOSITIONTYPE_INLINE; 1786 else if (!g_ascii_strcasecmp(tmp, "attachment")) 1787 mimeinfo->disposition = DISPOSITIONTYPE_ATTACHMENT; 1788 else 1789 mimeinfo->disposition = DISPOSITIONTYPE_ATTACHMENT; 1790 1791 if (params != NULL) 1792 parse_parameters(params, mimeinfo->dispositionparameters); 1793 1794 g_free(tmp); 1795 } 1796 1797 1798 static void procmime_parse_content_encoding(const gchar *content_encoding, MimeInfo *mimeinfo) 1799 { 1800 struct EncodingTable *enc_table; 1801 1802 for (enc_table = encoding_table; enc_table->str != NULL; enc_table++) { 1803 if (g_ascii_strcasecmp(enc_table->str, content_encoding) == 0) { 1804 mimeinfo->encoding_type = enc_table->enc_type; 1805 return; 1806 } 1807 } 1808 mimeinfo->encoding_type = ENC_UNKNOWN; 1809 return; 1810 } 1811 1812 static GSList *registered_parsers = NULL; 1813 1814 static MimeParser *procmime_get_mimeparser_for_type(MimeMediaType type, const gchar *sub_type) 1815 { 1816 GSList *cur; 1817 for (cur = registered_parsers; cur; cur = cur->next) { 1818 MimeParser *parser = (MimeParser *)cur->data; 1819 if (parser->type == type && !g_strcmp0(parser->sub_type, sub_type)) 1820 return parser; 1821 } 1822 return NULL; 1823 } 1824 1825 void procmime_mimeparser_register(MimeParser *parser) 1826 { 1827 if (!procmime_get_mimeparser_for_type(parser->type, parser->sub_type)) 1828 registered_parsers = g_slist_append(registered_parsers, parser); 1829 } 1830 1831 1832 void procmime_mimeparser_unregister(MimeParser *parser) 1833 { 1834 registered_parsers = g_slist_remove(registered_parsers, parser); 1835 } 1836 1837 static gboolean procmime_mimeparser_parse(MimeParser *parser, MimeInfo *mimeinfo) 1838 { 1839 cm_return_val_if_fail(parser->parse != NULL, FALSE); 1840 return parser->parse(parser, mimeinfo); 1841 } 1842 1843 static int procmime_parse_mimepart(MimeInfo *parent, 1844 gchar *content_type, 1845 gchar *content_encoding, 1846 gchar *content_description, 1847 gchar *content_id, 1848 gchar *content_disposition, 1849 gchar *content_location, 1850 const gchar *original_msgid, 1851 const gchar *disposition_notification_hdr, 1852 const gchar *filename, 1853 guint offset, 1854 guint length, 1855 gboolean short_scan) 1856 { 1857 MimeInfo *mimeinfo; 1858 MimeParser *parser = NULL; 1859 gboolean parsed = FALSE; 1860 int result = 0; 1861 1862 /* Create MimeInfo */ 1863 mimeinfo = procmime_mimeinfo_new(); 1864 mimeinfo->content = MIMECONTENT_FILE; 1865 1866 if (parent != NULL) { 1867 if (g_node_depth(parent->node) > 32) { 1868 /* 32 is an arbitrary value 1869 * this avoids DOSsing ourselves 1870 * with enormous messages 1871 */ 1872 procmime_mimeinfo_free_all(&mimeinfo); 1873 return -1; 1874 } 1875 g_node_append(parent->node, mimeinfo->node); 1876 } 1877 mimeinfo->data.filename = g_strdup(filename); 1878 mimeinfo->offset = offset; 1879 mimeinfo->length = length; 1880 1881 if (content_type != NULL) { 1882 g_strchomp(content_type); 1883 procmime_parse_content_type(content_type, mimeinfo); 1884 } else { 1885 mimeinfo->type = MIMETYPE_TEXT; 1886 mimeinfo->subtype = g_strdup("plain"); 1887 if (g_hash_table_lookup(mimeinfo->typeparameters, 1888 "charset") == NULL) { 1889 g_hash_table_insert(mimeinfo->typeparameters, 1890 g_strdup("charset"), 1891 g_strdup( 1892 conv_get_locale_charset_str_no_utf8())); 1893 } 1894 } 1895 1896 if (content_encoding != NULL) { 1897 g_strchomp(content_encoding); 1898 procmime_parse_content_encoding(content_encoding, mimeinfo); 1899 } else { 1900 mimeinfo->encoding_type = ENC_UNKNOWN; 1901 } 1902 1903 if (content_description != NULL) 1904 mimeinfo->description = g_strdup(content_description); 1905 else 1906 mimeinfo->description = NULL; 1907 1908 if (content_id != NULL) 1909 mimeinfo->id = g_strdup(content_id); 1910 else 1911 mimeinfo->id = NULL; 1912 1913 if (content_location != NULL) 1914 mimeinfo->location = g_strdup(content_location); 1915 else 1916 mimeinfo->location = NULL; 1917 1918 if (content_disposition != NULL) { 1919 g_strchomp(content_disposition); 1920 procmime_parse_content_disposition(content_disposition, mimeinfo); 1921 } else 1922 mimeinfo->disposition = DISPOSITIONTYPE_UNKNOWN; 1923 1924 /* Call parser for mime type */ 1925 if ((parser = procmime_get_mimeparser_for_type(mimeinfo->type, mimeinfo->subtype)) != NULL) { 1926 parsed = procmime_mimeparser_parse(parser, mimeinfo); 1927 } 1928 if (!parsed) { 1929 switch (mimeinfo->type) { 1930 case MIMETYPE_TEXT: 1931 if (g_ascii_strcasecmp(mimeinfo->subtype, "plain") == 0 && short_scan) { 1932 return 1; 1933 } 1934 break; 1935 1936 case MIMETYPE_MESSAGE: 1937 if (g_ascii_strcasecmp(mimeinfo->subtype, "rfc822") == 0) { 1938 procmime_parse_message_rfc822(mimeinfo, short_scan); 1939 } 1940 if (g_ascii_strcasecmp(mimeinfo->subtype, "disposition-notification") == 0) { 1941 procmime_parse_disposition_notification(mimeinfo, 1942 original_msgid, disposition_notification_hdr, short_scan); 1943 } 1944 break; 1945 1946 case MIMETYPE_MULTIPART: 1947 procmime_parse_multipart(mimeinfo, short_scan); 1948 break; 1949 1950 case MIMETYPE_APPLICATION: 1951 if (g_ascii_strcasecmp(mimeinfo->subtype, "octet-stream") == 0 1952 && original_msgid && *original_msgid 1953 && disposition_notification_hdr && *disposition_notification_hdr) { 1954 procmime_parse_disposition_notification(mimeinfo, 1955 original_msgid, disposition_notification_hdr, short_scan); 1956 } 1957 break; 1958 default: 1959 break; 1960 } 1961 } 1962 1963 return result; 1964 } 1965 1966 static gchar *typenames[] = { 1967 "text", 1968 "image", 1969 "audio", 1970 "video", 1971 "application", 1972 "message", 1973 "multipart", 1974 "font", 1975 "model", 1976 "chemical", 1977 "unknown", 1978 }; 1979 1980 static gboolean output_func(GNode *node, gpointer data) 1981 { 1982 guint i, depth; 1983 MimeInfo *mimeinfo = (MimeInfo *) node->data; 1984 1985 depth = g_node_depth(node); 1986 for (i = 0; i < depth; i++) 1987 g_print(" "); 1988 g_print("%s/%s (offset:%ld length:%ld encoding: %d)\n", 1989 (mimeinfo->type <= MIMETYPE_UNKNOWN)? typenames[mimeinfo->type] : "unknown", 1990 mimeinfo->subtype, mimeinfo->offset, mimeinfo->length, mimeinfo->encoding_type); 1991 1992 return FALSE; 1993 } 1994 1995 static void output_mime_structure(MimeInfo *mimeinfo, int indent) 1996 { 1997 g_node_traverse(mimeinfo->node, G_PRE_ORDER, G_TRAVERSE_ALL, -1, output_func, NULL); 1998 } 1999 2000 static MimeInfo *procmime_scan_file_with_offset(const gchar *filename, glong offset, gboolean short_scan) 2001 { 2002 MimeInfo *mimeinfo; 2003 GStatBuf buf; 2004 2005 if (g_stat(filename, &buf) < 0) { 2006 FILE_OP_ERROR(filename, "stat"); 2007 return NULL; 2008 } 2009 2010 mimeinfo = procmime_mimeinfo_new(); 2011 mimeinfo->content = MIMECONTENT_FILE; 2012 mimeinfo->encoding_type = ENC_UNKNOWN; 2013 mimeinfo->type = MIMETYPE_MESSAGE; 2014 mimeinfo->subtype = g_strdup("rfc822"); 2015 mimeinfo->data.filename = g_strdup(filename); 2016 mimeinfo->offset = offset; 2017 mimeinfo->length = buf.st_size - offset; 2018 2019 procmime_parse_message_rfc822(mimeinfo, short_scan); 2020 if (debug_get_mode()) 2021 output_mime_structure(mimeinfo, 0); 2022 2023 return mimeinfo; 2024 } 2025 2026 static MimeInfo *procmime_scan_file_full(const gchar *filename, gboolean short_scan) 2027 { 2028 MimeInfo *mimeinfo; 2029 2030 cm_return_val_if_fail(filename != NULL, NULL); 2031 2032 mimeinfo = procmime_scan_file_with_offset(filename, 0, short_scan); 2033 2034 return mimeinfo; 2035 } 2036 2037 MimeInfo *procmime_scan_file(const gchar *filename) 2038 { 2039 return procmime_scan_file_full(filename, FALSE); 2040 } 2041 2042 static MimeInfo *procmime_scan_file_short(const gchar *filename) 2043 { 2044 return procmime_scan_file_full(filename, TRUE); 2045 } 2046 2047 static MimeInfo *procmime_scan_queue_file_full(const gchar *filename, gboolean short_scan) 2048 { 2049 FILE *fp; 2050 MimeInfo *mimeinfo; 2051 gchar buf[BUFFSIZE]; 2052 glong offset = 0; 2053 2054 cm_return_val_if_fail(filename != NULL, NULL); 2055 2056 /* Open file */ 2057 if ((fp = g_fopen(filename, "rb")) == NULL) 2058 return NULL; 2059 /* Skip queue header */ 2060 while (fgets(buf, sizeof(buf), fp) != NULL) { 2061 /* new way */ 2062 if ((!strncmp(buf, "X-Claws-End-Special-Headers: 1", 2063 strlen("X-Claws-End-Special-Headers:"))) || 2064 (!strncmp(buf, "X-Sylpheed-End-Special-Headers: 1", 2065 strlen("X-Sylpheed-End-Special-Headers:")))) 2066 break; 2067 /* old way */ 2068 if (buf[0] == '\r' || buf[0] == '\n') break; 2069 /* from other mailers */ 2070 if (!strncmp(buf, "Date: ", 6) 2071 || !strncmp(buf, "To: ", 4) 2072 || !strncmp(buf, "From: ", 6) 2073 || !strncmp(buf, "Subject: ", 9)) { 2074 rewind(fp); 2075 break; 2076 } 2077 } 2078 offset = ftell(fp); 2079 fclose(fp); 2080 2081 mimeinfo = procmime_scan_file_with_offset(filename, offset, short_scan); 2082 2083 return mimeinfo; 2084 } 2085 2086 MimeInfo *procmime_scan_queue_file(const gchar *filename) 2087 { 2088 return procmime_scan_queue_file_full(filename, FALSE); 2089 } 2090 2091 static MimeInfo *procmime_scan_queue_file_short(const gchar *filename) 2092 { 2093 return procmime_scan_queue_file_full(filename, TRUE); 2094 } 2095 2096 typedef enum { 2097 ENC_AS_TOKEN, 2098 ENC_AS_QUOTED_STRING, 2099 ENC_AS_EXTENDED, 2100 ENC_AS_ENCWORD 2101 } EncodeAs; 2102 2103 typedef struct _ParametersData { 2104 FILE *fp; 2105 guint len; 2106 gint error; 2107 } ParametersData; 2108 2109 static void write_parameters(gpointer key, gpointer value, gpointer user_data) 2110 { 2111 gchar *param = key; 2112 gchar *val = value, *valpos, *tmp; 2113 ParametersData *pdata = (ParametersData *)user_data; 2114 GString *buf = g_string_new(""); 2115 glong len; 2116 2117 EncodeAs encas = ENC_AS_TOKEN; 2118 2119 for (valpos = val; *valpos != 0; valpos++) { 2120 if (!IS_ASCII(*valpos)) { 2121 encas = ENC_AS_ENCWORD; 2122 break; 2123 } 2124 2125 /* CTLs */ 2126 if (((*valpos >= 0) && (*valpos < 037)) || (*valpos == 0177)) { 2127 encas = ENC_AS_QUOTED_STRING; 2128 continue; 2129 } 2130 2131 /* tspecials + SPACE */ 2132 switch (*valpos) { 2133 case ' ': 2134 case '(': 2135 case ')': 2136 case '<': 2137 case '>': 2138 case '@': 2139 case ',': 2140 case ';': 2141 case ':': 2142 case '\\': 2143 case '"': 2144 case '/': 2145 case '[': 2146 case ']': 2147 case '?': 2148 case '=': 2149 encas = ENC_AS_QUOTED_STRING; 2150 continue; 2151 } 2152 } 2153 2154 switch (encas) { 2155 case ENC_AS_TOKEN: 2156 g_string_append_printf(buf, "%s=%s", param, val); 2157 break; 2158 2159 case ENC_AS_QUOTED_STRING: 2160 g_string_append_printf(buf, "%s=\"%s\"", param, val); 2161 break; 2162 2163 #if 0 /* we don't use that for now */ 2164 case ENC_AS_EXTENDED: 2165 if (!g_utf8_validate(val, -1, NULL)) 2166 g_string_append_printf(buf, "%s*=%s''", param, 2167 conv_get_locale_charset_str()); 2168 else 2169 g_string_append_printf(buf, "%s*=%s''", param, 2170 CS_INTERNAL); 2171 for (valpos = val; *valpos != '\0'; valpos++) { 2172 if (IS_ASCII(*valpos) && isalnum(*valpos)) { 2173 g_string_append_printf(buf, "%c", *valpos); 2174 } else { 2175 gchar hexstr[3] = "XX"; 2176 get_hex_str(hexstr, *valpos); 2177 g_string_append_printf(buf, "%%%s", hexstr); 2178 } 2179 } 2180 break; 2181 #else 2182 case ENC_AS_EXTENDED: 2183 debug_print("Unhandled ENC_AS_EXTENDED.\n"); 2184 break; 2185 #endif 2186 case ENC_AS_ENCWORD: 2187 len = MAX(strlen(val)*6, 512); 2188 tmp = g_malloc(len+1); 2189 codeconv_set_strict(TRUE); 2190 conv_encode_header_full(tmp, len, val, pdata->len + strlen(param) + 4 , FALSE, 2191 prefs_common.outgoing_charset); 2192 codeconv_set_strict(FALSE); 2193 if (!tmp || !*tmp) { 2194 codeconv_set_strict(TRUE); 2195 conv_encode_header_full(tmp, len, val, pdata->len + strlen(param) + 4 , FALSE, 2196 conv_get_outgoing_charset_str()); 2197 codeconv_set_strict(FALSE); 2198 } 2199 if (!tmp || !*tmp) { 2200 codeconv_set_strict(TRUE); 2201 conv_encode_header_full(tmp, len, val, pdata->len + strlen(param) + 4 , FALSE, 2202 CS_UTF_8); 2203 codeconv_set_strict(FALSE); 2204 } 2205 if (!tmp || !*tmp) { 2206 conv_encode_header_full(tmp, len, val, pdata->len + strlen(param) + 4 , FALSE, 2207 CS_UTF_8); 2208 } 2209 g_string_append_printf(buf, "%s=\"%s\"", param, tmp); 2210 g_free(tmp); 2211 break; 2212 2213 } 2214 2215 if (buf->str && strlen(buf->str)) { 2216 tmp = strstr(buf->str, "\n"); 2217 if (tmp) 2218 len = (tmp - buf->str); 2219 else 2220 len = strlen(buf->str); 2221 if (pdata->len + len > 76) { 2222 if (fprintf(pdata->fp, ";\n %s", buf->str) < 0) 2223 pdata->error = TRUE; 2224 pdata->len = strlen(buf->str) + 1; 2225 } else { 2226 if (fprintf(pdata->fp, "; %s", buf->str) < 0) 2227 pdata->error = TRUE; 2228 pdata->len += strlen(buf->str) + 2; 2229 } 2230 } 2231 g_string_free(buf, TRUE); 2232 } 2233 2234 #define TRY(func) { \ 2235 if (!(func)) { \ 2236 return -1; \ 2237 } \ 2238 } 2239 2240 int procmime_write_mime_header(MimeInfo *mimeinfo, FILE *fp) 2241 { 2242 struct TypeTable *type_table; 2243 ParametersData *pdata = g_new0(ParametersData, 1); 2244 debug_print("procmime_write_mime_header\n"); 2245 2246 pdata->fp = fp; 2247 pdata->error = FALSE; 2248 for (type_table = mime_type_table; type_table->str != NULL; type_table++) 2249 if (mimeinfo->type == type_table->type) { 2250 gchar *buf = g_strdup_printf( 2251 "Content-Type: %s/%s", type_table->str, mimeinfo->subtype); 2252 if (fprintf(fp, "%s", buf) < 0) { 2253 g_free(buf); 2254 g_free(pdata); 2255 return -1; 2256 } 2257 pdata->len = strlen(buf); 2258 g_free(buf); 2259 break; 2260 } 2261 g_hash_table_foreach(mimeinfo->typeparameters, write_parameters, pdata); 2262 if (pdata->error == TRUE) { 2263 g_free(pdata); 2264 return -1; 2265 } 2266 g_free(pdata); 2267 2268 TRY(fprintf(fp, "\n") >= 0); 2269 2270 if (mimeinfo->encoding_type != ENC_UNKNOWN) 2271 TRY(fprintf(fp, "Content-Transfer-Encoding: %s\n", procmime_get_encoding_str(mimeinfo->encoding_type)) >= 0); 2272 2273 if (mimeinfo->description != NULL) 2274 TRY(fprintf(fp, "Content-Description: %s\n", mimeinfo->description) >= 0); 2275 2276 if (mimeinfo->id != NULL) 2277 TRY(fprintf(fp, "Content-ID: %s\n", mimeinfo->id) >= 0); 2278 2279 if (mimeinfo->location != NULL) 2280 TRY(fprintf(fp, "Content-Location: %s\n", mimeinfo->location) >= 0); 2281 2282 if (mimeinfo->disposition != DISPOSITIONTYPE_UNKNOWN) { 2283 ParametersData *pdata = g_new0(ParametersData, 1); 2284 gchar *buf = NULL; 2285 if (mimeinfo->disposition == DISPOSITIONTYPE_INLINE) 2286 buf = g_strdup("Content-Disposition: inline"); 2287 else if (mimeinfo->disposition == DISPOSITIONTYPE_ATTACHMENT) 2288 buf = g_strdup("Content-Disposition: attachment"); 2289 else 2290 buf = g_strdup("Content-Disposition: unknown"); 2291 2292 if (fprintf(fp, "%s", buf) < 0) { 2293 g_free(buf); 2294 g_free(pdata); 2295 return -1; 2296 } 2297 pdata->len = strlen(buf); 2298 g_free(buf); 2299 2300 pdata->fp = fp; 2301 pdata->error = FALSE; 2302 g_hash_table_foreach(mimeinfo->dispositionparameters, write_parameters, pdata); 2303 if (pdata->error == TRUE) { 2304 g_free(pdata); 2305 return -1; 2306 } 2307 g_free(pdata); 2308 TRY(fprintf(fp, "\n") >= 0); 2309 } 2310 2311 TRY(fprintf(fp, "\n") >= 0); 2312 2313 return 0; 2314 } 2315 2316 static gint procmime_write_message_rfc822(MimeInfo *mimeinfo, FILE *fp) 2317 { 2318 FILE *infp; 2319 GNode *childnode; 2320 MimeInfo *child; 2321 gchar buf[BUFFSIZE]; 2322 gboolean skip = FALSE; 2323 size_t len; 2324 2325 debug_print("procmime_write_message_rfc822\n"); 2326 2327 /* write header */ 2328 switch (mimeinfo->content) { 2329 case MIMECONTENT_FILE: 2330 if ((infp = g_fopen(mimeinfo->data.filename, "rb")) == NULL) { 2331 FILE_OP_ERROR(mimeinfo->data.filename, "g_fopen"); 2332 return -1; 2333 } 2334 if (fseek(infp, mimeinfo->offset, SEEK_SET) < 0) { 2335 FILE_OP_ERROR(mimeinfo->data.filename, "fseek"); 2336 fclose(infp); 2337 return -1; 2338 } 2339 while (fgets(buf, sizeof(buf), infp) == buf) { 2340 strcrchomp(buf); 2341 if (buf[0] == '\n' && buf[1] == '\0') 2342 break; 2343 if (skip && (buf[0] == ' ' || buf[0] == '\t')) 2344 continue; 2345 if (g_ascii_strncasecmp(buf, "MIME-Version:", 13) == 0 || 2346 g_ascii_strncasecmp(buf, "Content-Type:", 13) == 0 || 2347 g_ascii_strncasecmp(buf, "Content-Transfer-Encoding:", 26) == 0 || 2348 g_ascii_strncasecmp(buf, "Content-Description:", 20) == 0 || 2349 g_ascii_strncasecmp(buf, "Content-ID:", 11) == 0 || 2350 g_ascii_strncasecmp(buf, "Content-Location:", 17) == 0 || 2351 g_ascii_strncasecmp(buf, "Content-Disposition:", 20) == 0) { 2352 skip = TRUE; 2353 continue; 2354 } 2355 len = strlen(buf); 2356 if (fwrite(buf, sizeof(gchar), len, fp) < len) { 2357 g_warning("failed to dump %"G_GSIZE_FORMAT" bytes from file", len); 2358 fclose(infp); 2359 return -1; 2360 } 2361 skip = FALSE; 2362 } 2363 fclose(infp); 2364 break; 2365 2366 case MIMECONTENT_MEM: 2367 len = strlen(mimeinfo->data.mem); 2368 if (fwrite(mimeinfo->data.mem, sizeof(gchar), len, fp) < len) { 2369 g_warning("failed to dump %"G_GSIZE_FORMAT" bytes from mem", len); 2370 return -1; 2371 } 2372 break; 2373 2374 default: 2375 break; 2376 } 2377 2378 childnode = mimeinfo->node->children; 2379 if (childnode == NULL) 2380 return -1; 2381 2382 child = (MimeInfo *) childnode->data; 2383 if (fprintf(fp, "MIME-Version: 1.0\n") < 0) { 2384 g_warning("failed to write mime version"); 2385 return -1; 2386 } 2387 if (procmime_write_mime_header(child, fp) < 0) 2388 return -1; 2389 return procmime_write_mimeinfo(child, fp); 2390 } 2391 2392 static gint procmime_write_multipart(MimeInfo *mimeinfo, FILE *fp) 2393 { 2394 FILE *infp; 2395 GNode *childnode; 2396 gchar *boundary, *str, *str2; 2397 gchar buf[BUFFSIZE]; 2398 gboolean firstboundary; 2399 size_t len; 2400 2401 debug_print("procmime_write_multipart\n"); 2402 2403 boundary = g_hash_table_lookup(mimeinfo->typeparameters, "boundary"); 2404 2405 switch (mimeinfo->content) { 2406 case MIMECONTENT_FILE: 2407 if ((infp = g_fopen(mimeinfo->data.filename, "rb")) == NULL) { 2408 FILE_OP_ERROR(mimeinfo->data.filename, "g_fopen"); 2409 return -1; 2410 } 2411 if (fseek(infp, mimeinfo->offset, SEEK_SET) < 0) { 2412 FILE_OP_ERROR(mimeinfo->data.filename, "fseek"); 2413 fclose(infp); 2414 return -1; 2415 } 2416 while (fgets(buf, sizeof(buf), infp) == buf) { 2417 if (IS_BOUNDARY(buf, boundary, strlen(boundary))) 2418 break; 2419 len = strlen(buf); 2420 if (fwrite(buf, sizeof(gchar), len, fp) < len) { 2421 g_warning("failed to write %"G_GSIZE_FORMAT, len); 2422 fclose(infp); 2423 return -1; 2424 } 2425 } 2426 fclose(infp); 2427 break; 2428 2429 case MIMECONTENT_MEM: 2430 str = g_strdup(mimeinfo->data.mem); 2431 if (((str2 = strstr(str, boundary)) != NULL) && ((str2 - str) >= 2) && 2432 (*(str2 - 1) == '-') && (*(str2 - 2) == '-')) 2433 *(str2 - 2) = '\0'; 2434 len = strlen(str); 2435 if (fwrite(str, sizeof(gchar), len, fp) < len) { 2436 g_warning("failed to write %"G_GSIZE_FORMAT" from mem", len); 2437 g_free(str); 2438 return -1; 2439 } 2440 g_free(str); 2441 break; 2442 2443 default: 2444 break; 2445 } 2446 2447 childnode = mimeinfo->node->children; 2448 firstboundary = TRUE; 2449 while (childnode != NULL) { 2450 MimeInfo *child = childnode->data; 2451 2452 if (firstboundary) 2453 firstboundary = FALSE; 2454 else 2455 TRY(fprintf(fp, "\n") >= 0); 2456 2457 TRY(fprintf(fp, "--%s\n", boundary) >= 0); 2458 2459 if (procmime_write_mime_header(child, fp) < 0) 2460 return -1; 2461 if (procmime_write_mimeinfo(child, fp) < 0) 2462 return -1; 2463 2464 childnode = g_node_next_sibling(childnode); 2465 } 2466 TRY(fprintf(fp, "\n--%s--\n", boundary) >= 0); 2467 2468 return 0; 2469 } 2470 2471 gint procmime_write_mimeinfo(MimeInfo *mimeinfo, FILE *fp) 2472 { 2473 FILE *infp; 2474 size_t len; 2475 debug_print("procmime_write_mimeinfo\n"); 2476 2477 if (G_NODE_IS_LEAF(mimeinfo->node)) { 2478 switch (mimeinfo->content) { 2479 case MIMECONTENT_FILE: 2480 if ((infp = g_fopen(mimeinfo->data.filename, "rb")) == NULL) { 2481 FILE_OP_ERROR(mimeinfo->data.filename, "g_fopen"); 2482 return -1; 2483 } 2484 copy_file_part_to_fp(infp, mimeinfo->offset, mimeinfo->length, fp); 2485 fclose(infp); 2486 return 0; 2487 2488 case MIMECONTENT_MEM: 2489 len = strlen(mimeinfo->data.mem); 2490 if (fwrite(mimeinfo->data.mem, sizeof(gchar), len, fp) < len) 2491 return -1; 2492 return 0; 2493 2494 default: 2495 return 0; 2496 } 2497 } else { 2498 /* Call writer for mime type */ 2499 switch (mimeinfo->type) { 2500 case MIMETYPE_MESSAGE: 2501 if (g_ascii_strcasecmp(mimeinfo->subtype, "rfc822") == 0) { 2502 return procmime_write_message_rfc822(mimeinfo, fp); 2503 } 2504 break; 2505 2506 case MIMETYPE_MULTIPART: 2507 return procmime_write_multipart(mimeinfo, fp); 2508 2509 default: 2510 break; 2511 } 2512 2513 return -1; 2514 } 2515 2516 return 0; 2517 } 2518 2519 gchar *procmime_get_part_file_name(MimeInfo *mimeinfo) 2520 { 2521 gchar *base; 2522 2523 if ((mimeinfo->type == MIMETYPE_TEXT) && !g_ascii_strcasecmp(mimeinfo->subtype, "html")) 2524 base = g_strdup("mimetmp.html"); 2525 else { 2526 const gchar *basetmp; 2527 gchar *basename; 2528 2529 basetmp = procmime_mimeinfo_get_parameter(mimeinfo, "filename"); 2530 if (basetmp == NULL) 2531 basetmp = procmime_mimeinfo_get_parameter(mimeinfo, "name"); 2532 if (basetmp == NULL) 2533 basetmp = "mimetmp"; 2534 basename = g_path_get_basename(basetmp); 2535 if (*basename == '\0') { 2536 g_free(basename); 2537 basename = g_strdup("mimetmp"); 2538 } 2539 base = conv_filename_from_utf8(basename); 2540 g_free(basename); 2541 subst_for_shellsafe_filename(base); 2542 } 2543 2544 return base; 2545 } 2546 2547 void *procmime_get_part_as_string(MimeInfo *mimeinfo, 2548 gboolean null_terminate) 2549 { 2550 FILE *infp; 2551 gchar *data; 2552 glong length, readlength; 2553 2554 cm_return_val_if_fail(mimeinfo != NULL, NULL); 2555 2556 if (mimeinfo->encoding_type != ENC_BINARY && 2557 !procmime_decode_content(mimeinfo)) 2558 return NULL; 2559 2560 if (mimeinfo->content == MIMECONTENT_MEM) 2561 return g_strdup(mimeinfo->data.mem); 2562 2563 if ((infp = g_fopen(mimeinfo->data.filename, "rb")) == NULL) { 2564 FILE_OP_ERROR(mimeinfo->data.filename, "g_fopen"); 2565 return NULL; 2566 } 2567 2568 if (fseek(infp, mimeinfo->offset, SEEK_SET) < 0) { 2569 FILE_OP_ERROR(mimeinfo->data.filename, "fseek"); 2570 fclose(infp); 2571 return NULL; 2572 } 2573 2574 length = mimeinfo->length; 2575 2576 data = g_malloc(null_terminate ? length + 1 : length); 2577 if (data == NULL) { 2578 g_warning("could not allocate %ld bytes for procmime_get_part_as_string", 2579 (null_terminate ? length + 1 : length)); 2580 fclose(infp); 2581 return NULL; 2582 } 2583 2584 readlength = fread(data, length, 1, infp); 2585 if (readlength <= 0) { 2586 FILE_OP_ERROR(mimeinfo->data.filename, "fread"); 2587 g_free(data); 2588 fclose(infp); 2589 return NULL; 2590 } 2591 2592 fclose(infp); 2593 2594 if (null_terminate) 2595 data[length] = '\0'; 2596 2597 return data; 2598 } 2599 2600 /* Returns an open GInputStream. The caller should just 2601 * read mimeinfo->length bytes from it and then release it. */ 2602 GInputStream *procmime_get_part_as_inputstream(MimeInfo *mimeinfo) 2603 { 2604 cm_return_val_if_fail(mimeinfo != NULL, NULL); 2605 2606 if (mimeinfo->encoding_type != ENC_BINARY && 2607 !procmime_decode_content(mimeinfo)) { 2608 g_warning("could not decode part"); 2609 return NULL; 2610 } 2611 if (mimeinfo->content == MIMECONTENT_MEM) { 2612 /* NULL for destroy func, since we're not copying 2613 * the data for the stream. */ 2614 return g_memory_input_stream_new_from_data( 2615 mimeinfo->data.mem, 2616 (gssize)mimeinfo->length, NULL); 2617 } else { 2618 return g_memory_input_stream_new_from_data( 2619 procmime_get_part_as_string(mimeinfo, FALSE), 2620 mimeinfo->length, g_free); 2621 } 2622 } 2623 2624 GdkPixbuf *procmime_get_part_as_pixbuf(MimeInfo *mimeinfo, GError **error) 2625 { 2626 GdkPixbuf *pixbuf; 2627 GInputStream *stream; 2628 2629 if (error) 2630 *error = NULL; 2631 2632 stream = procmime_get_part_as_inputstream(mimeinfo); 2633 if (stream == NULL) { 2634 if (error) 2635 *error = g_error_new_literal(G_FILE_ERROR, -1, _("Could not decode part")); 2636 return NULL; 2637 } 2638 2639 pixbuf = gdk_pixbuf_new_from_stream(stream, NULL, error); 2640 g_object_unref(stream); 2641 2642 if (error && *error != NULL) 2643 return NULL; 2644 2645 return pixbuf; 2646 } 2647