procheader.c (29855B)
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 20 #include <glib.h> 21 #include <glib/gi18n.h> 22 #include <stdio.h> 23 #include <string.h> 24 #include <stdlib.h> 25 #include <time.h> 26 #include <sys/stat.h> 27 28 #include "procheader.h" 29 #include "procmsg.h" 30 #include "codeconv.h" 31 #include "prefs_common.h" 32 #include "hooks.h" 33 #include "utils.h" 34 #include "defs.h" 35 #include "file-utils.h" 36 37 #define BUFFSIZE 8192 38 39 static gchar monthstr[] = "JanFebMarAprMayJunJulAugSepOctNovDec"; 40 41 typedef char *(*getlinefunc) (char *, size_t, void *); 42 typedef int (*peekcharfunc) (void *); 43 typedef int (*getcharfunc) (void *); 44 typedef gint (*get_one_field_func) (gchar **, void *, HeaderEntry[]); 45 46 static gint string_get_one_field(gchar **buf, char **str, 47 HeaderEntry hentry[]); 48 49 static char *string_getline(char *buf, size_t len, char **str); 50 static int string_peekchar(char **str); 51 static int file_peekchar(FILE *fp); 52 static gint generic_get_one_field(gchar **bufptr, void *data, 53 HeaderEntry hentry[], 54 getlinefunc getline, 55 peekcharfunc peekchar, 56 gboolean unfold); 57 static MsgInfo *parse_stream(void *data, gboolean isstring, MsgFlags flags, 58 gboolean full, gboolean decrypted); 59 60 61 gint procheader_get_one_field(gchar **buf, FILE *fp, 62 HeaderEntry hentry[]) 63 { 64 return generic_get_one_field(buf, fp, hentry, 65 (getlinefunc)fgets_crlf, (peekcharfunc)file_peekchar, 66 TRUE); 67 } 68 69 static gint string_get_one_field(gchar **buf, char **str, 70 HeaderEntry hentry[]) 71 { 72 return generic_get_one_field(buf, str, hentry, 73 (getlinefunc)string_getline, 74 (peekcharfunc)string_peekchar, 75 TRUE); 76 } 77 78 gboolean procheader_skip_headers(FILE *fp) 79 { 80 gchar *buf = g_malloc(BUFFSIZE); 81 do { 82 if (fgets_crlf(buf, BUFFSIZE - 1, fp) == NULL) { 83 g_free(buf); 84 return FALSE; 85 } 86 if (buf[0] == '\r' || buf[0] == '\n') { 87 break; 88 } 89 } while (TRUE); 90 g_free(buf); 91 92 return TRUE; 93 } 94 95 96 static char *string_getline(char *buf, size_t len, char **str) 97 { 98 gboolean is_cr = FALSE; 99 gboolean last_was_cr = FALSE; 100 101 if (!*str || !**str) 102 return NULL; 103 104 for (; **str && len > 1; --len) { 105 is_cr = (**str == '\r'); 106 if ((*buf++ = *(*str)++) == '\n') { 107 break; 108 } 109 if (last_was_cr) { 110 *(--buf) = '\n'; 111 buf++; 112 break; 113 } 114 last_was_cr = is_cr; 115 } 116 117 *buf = '\0'; 118 119 return buf; 120 } 121 122 static int string_peekchar(char **str) 123 { 124 return **str; 125 } 126 127 static int file_peekchar(FILE *fp) 128 { 129 return ungetc(getc(fp), fp); 130 } 131 132 static gint generic_get_one_field(gchar **bufptr, void *data, 133 HeaderEntry *hentry, 134 getlinefunc getline, peekcharfunc peekchar, 135 gboolean unfold) 136 { 137 /* returns -1 in case of failure of any kind, whatever it's a parsing error 138 or an allocation error. if returns -1, *bufptr is always NULL, and vice-versa, 139 and if returning 0 (OK), *bufptr is always non-NULL, so callers just have to 140 test the return value 141 */ 142 gint nexthead; 143 gint hnum = 0; 144 HeaderEntry *hp = NULL; 145 size_t len; 146 gchar *buf; 147 148 cm_return_val_if_fail(bufptr != NULL, -1); 149 150 len = BUFFSIZE; 151 buf = g_malloc(len); 152 153 if (hentry != NULL) { 154 /* skip non-required headers */ 155 /* and get hentry header line */ 156 do { 157 do { 158 if (getline(buf, len, data) == NULL) { 159 debug_print("generic_get_one_field: getline\n"); 160 g_free(buf); 161 *bufptr = NULL; 162 return -1; 163 } 164 if (buf[0] == '\r' || buf[0] == '\n') { 165 debug_print("generic_get_one_field: empty line\n"); 166 g_free(buf); 167 *bufptr = NULL; 168 return -1; 169 } 170 } while (buf[0] == ' ' || buf[0] == '\t'); 171 172 for (hp = hentry, hnum = 0; hp->name != NULL; 173 hp++, hnum++) { 174 if (!g_ascii_strncasecmp(hp->name, buf, 175 strlen(hp->name))) 176 break; 177 } 178 } while (hp->name == NULL); 179 } else { 180 /* read first line */ 181 if (getline(buf, len, data) == NULL) { 182 debug_print("generic_get_one_field: getline\n"); 183 g_free(buf); 184 *bufptr = NULL; 185 return -1; 186 } 187 if (buf[0] == '\r' || buf[0] == '\n') { 188 debug_print("generic_get_one_field: empty line\n"); 189 g_free(buf); 190 *bufptr = NULL; 191 return -1; 192 } 193 } 194 /* reduce initial buffer to its useful part */ 195 len = strlen(buf)+1; 196 buf = g_realloc(buf, len); 197 if (buf == NULL) { 198 debug_print("generic_get_one_field: reallocation error\n"); 199 *bufptr = NULL; 200 return -1; 201 } 202 203 /* unfold line */ 204 while (1) { 205 nexthead = peekchar(data); 206 /* ([*WSP CRLF] 1*WSP) */ 207 if (nexthead == ' ' || nexthead == '\t') { 208 size_t buflen; 209 gchar *tmpbuf; 210 size_t tmplen; 211 212 gboolean skiptab = (nexthead == '\t'); 213 /* trim previous trailing \n if requesting one header or 214 * unfolding was requested */ 215 if ((!hentry && unfold) || (hp && hp->unfold)) 216 strretchomp(buf); 217 218 buflen = strlen(buf); 219 220 /* read next line */ 221 tmpbuf = g_malloc(BUFFSIZE); 222 223 if (getline(tmpbuf, BUFFSIZE, data) == NULL) { 224 g_free(tmpbuf); 225 break; 226 } 227 tmplen = strlen(tmpbuf)+1; 228 229 /* extend initial buffer and concatenate next line */ 230 len += tmplen; 231 buf = g_realloc(buf, len); 232 if (buf == NULL) { 233 debug_print("generic_get_one_field: reallocation error\n"); 234 g_free(buf); 235 *bufptr = NULL; 236 return -1; 237 } 238 memcpy(buf+buflen, tmpbuf, tmplen); 239 g_free(tmpbuf); 240 if (skiptab) { /* replace tab with space */ 241 *(buf + buflen) = ' '; 242 } 243 } else { 244 /* remove trailing new line */ 245 strretchomp(buf); 246 break; 247 } 248 } 249 250 *bufptr = buf; 251 252 return hnum; 253 } 254 255 gint procheader_get_one_field_asis(gchar **buf, FILE *fp) 256 { 257 return generic_get_one_field(buf, fp, NULL, 258 (getlinefunc)fgets_crlf, 259 (peekcharfunc)file_peekchar, 260 FALSE); 261 } 262 263 GPtrArray *procheader_get_header_array(FILE *fp) 264 { 265 gchar *buf = NULL; 266 GPtrArray *headers; 267 Header *header; 268 269 cm_return_val_if_fail(fp != NULL, NULL); 270 271 headers = g_ptr_array_new(); 272 273 while (procheader_get_one_field(&buf, fp, NULL) != -1) { 274 if ((header = procheader_parse_header(buf)) != NULL) 275 g_ptr_array_add(headers, header); 276 g_free(buf); 277 buf = NULL; 278 } 279 280 return headers; 281 } 282 283 void procheader_header_array_destroy(GPtrArray *harray) 284 { 285 gint i; 286 Header *header; 287 288 cm_return_if_fail(harray != NULL); 289 290 for (i = 0; i < harray->len; i++) { 291 header = g_ptr_array_index(harray, i); 292 procheader_header_free(header); 293 } 294 295 g_ptr_array_free(harray, TRUE); 296 } 297 298 void procheader_header_free(Header *header) 299 { 300 if (!header) return; 301 302 g_free(header->name); 303 g_free(header->body); 304 g_free(header); 305 } 306 307 /* 308 tests whether two headers' names are equal 309 remove the trailing ':' or ' ' before comparing 310 */ 311 312 gboolean procheader_headername_equal(char * hdr1, char * hdr2) 313 { 314 int len1; 315 int len2; 316 317 len1 = strlen(hdr1); 318 len2 = strlen(hdr2); 319 if (hdr1[len1 - 1] == ':') 320 len1--; 321 if (hdr2[len2 - 1] == ':') 322 len2--; 323 if (len1 != len2) 324 return 0; 325 326 return (g_ascii_strncasecmp(hdr1, hdr2, len1) == 0); 327 } 328 329 /* 330 parse headers, for example : 331 From: dinh@enseirb.fr becomes : 332 header->name = "From:" 333 header->body = "dinh@enseirb.fr" 334 */ 335 static gboolean header_is_addr_field(const gchar *hdr) 336 { 337 static char *addr_headers[] = { 338 "To:", 339 "Cc:", 340 "Bcc:", 341 "From:", 342 "Reply-To:", 343 "Followup-To:", 344 "Followup-and-Reply-To:", 345 "Disposition-Notification-To:", 346 "Return-Receipt-To:", 347 NULL}; 348 int i; 349 350 if (!hdr) 351 return FALSE; 352 353 for (i = 0; addr_headers[i] != NULL; i++) 354 if (!strcasecmp(hdr, addr_headers[i])) 355 return FALSE; 356 357 return FALSE; 358 } 359 360 Header * procheader_parse_header(gchar * buf) 361 { 362 gchar *p; 363 Header * header; 364 gboolean addr_field = FALSE; 365 366 cm_return_val_if_fail(buf != NULL, NULL); 367 368 if ((*buf == ':') || (*buf == ' ')) 369 return NULL; 370 371 for (p = buf; *p ; p++) { 372 if ((*p == ':') || (*p == ' ')) { 373 header = g_new(Header, 1); 374 header->name = g_strndup(buf, p - buf + 1); 375 addr_field = header_is_addr_field(header->name); 376 p++; 377 while (*p == ' ' || *p == '\t') p++; 378 header->body = conv_unmime_header(p, NULL, addr_field); 379 return header; 380 } 381 } 382 return NULL; 383 } 384 385 void procheader_get_header_fields(FILE *fp, HeaderEntry hentry[]) 386 { 387 gchar *buf = NULL; 388 HeaderEntry *hp; 389 gint hnum; 390 gchar *p; 391 392 if (hentry == NULL) return; 393 394 while ((hnum = procheader_get_one_field(&buf, fp, hentry)) != -1) { 395 hp = hentry + hnum; 396 397 p = buf + strlen(hp->name); 398 while (*p == ' ' || *p == '\t') p++; 399 400 if (hp->body == NULL) 401 hp->body = g_strdup(p); 402 else if (procheader_headername_equal(hp->name, "To") || 403 procheader_headername_equal(hp->name, "Cc")) { 404 gchar *tp = hp->body; 405 hp->body = g_strconcat(tp, ", ", p, NULL); 406 g_free(tp); 407 } 408 g_free(buf); 409 buf = NULL; 410 } 411 } 412 413 MsgInfo *procheader_parse_file(const gchar *file, MsgFlags flags, 414 gboolean full, gboolean decrypted) 415 { 416 GStatBuf s; 417 FILE *fp; 418 MsgInfo *msginfo; 419 420 if (g_stat(file, &s) < 0) { 421 FILE_OP_ERROR(file, "stat"); 422 return NULL; 423 } 424 if (!S_ISREG(s.st_mode)) 425 return NULL; 426 427 if ((fp = g_fopen(file, "rb")) == NULL) { 428 FILE_OP_ERROR(file, "g_fopen"); 429 return NULL; 430 } 431 432 msginfo = procheader_parse_stream(fp, flags, full, decrypted); 433 fclose(fp); 434 435 if (msginfo) { 436 msginfo->size = s.st_size; 437 msginfo->mtime = s.st_mtime; 438 } 439 return msginfo; 440 } 441 442 MsgInfo *procheader_parse_str(const gchar *str, MsgFlags flags, gboolean full, 443 gboolean decrypted) 444 { 445 return parse_stream(&str, TRUE, flags, full, decrypted); 446 } 447 448 enum 449 { 450 H_DATE = 0, 451 H_FROM, 452 H_TO, 453 H_CC, 454 H_NEWSGROUPS, 455 H_SUBJECT, 456 H_MSG_ID, 457 H_REFERENCES, 458 H_IN_REPLY_TO, 459 H_CONTENT_TYPE, 460 H_SEEN, 461 H_STATUS, 462 H_FROM_SPACE, 463 H_SC_PLANNED_DOWNLOAD, 464 H_SC_MESSAGE_SIZE, 465 H_FACE, 466 H_X_FACE, 467 H_DISPOSITION_NOTIFICATION_TO, 468 H_RETURN_RECEIPT_TO, 469 H_SC_PARTIALLY_RETRIEVED, 470 H_SC_ACCOUNT_SERVER, 471 H_SC_ACCOUNT_LOGIN, 472 H_LIST_POST, 473 H_LIST_SUBSCRIBE, 474 H_LIST_UNSUBSCRIBE, 475 H_LIST_HELP, 476 H_LIST_ARCHIVE, 477 H_LIST_OWNER, 478 H_RESENT_FROM, 479 }; 480 481 static HeaderEntry hentry_full[] = { 482 {"Date:", NULL, FALSE}, 483 {"From:", NULL, TRUE}, 484 {"To:", NULL, TRUE}, 485 {"Cc:", NULL, TRUE}, 486 {"Newsgroups:", NULL, TRUE}, 487 {"Subject:", NULL, TRUE}, 488 {"Message-ID:", NULL, FALSE}, 489 {"References:", NULL, FALSE}, 490 {"In-Reply-To:", NULL, FALSE}, 491 {"Content-Type:", NULL, FALSE}, 492 {"Seen:", NULL, FALSE}, 493 {"Status:", NULL, FALSE}, 494 {"From ", NULL, FALSE}, 495 {"SC-Marked-For-Download:", NULL, FALSE}, 496 {"SC-Message-Size:", NULL, FALSE}, 497 {"Face:", NULL, FALSE}, 498 {"X-Face:", NULL, FALSE}, 499 {"Disposition-Notification-To:", NULL, FALSE}, 500 {"Return-Receipt-To:", NULL, FALSE}, 501 {"SC-Partially-Retrieved:", NULL, FALSE}, 502 {"SC-Account-Server:", NULL, FALSE}, 503 {"SC-Account-Login:",NULL, FALSE}, 504 {"List-Post:", NULL, TRUE}, 505 {"List-Subscribe:", NULL, TRUE}, 506 {"List-Unsubscribe:",NULL, TRUE}, 507 {"List-Help:", NULL, TRUE}, 508 {"List-Archive:", NULL, TRUE}, 509 {"List-Owner:", NULL, TRUE}, 510 {"Resent-From:", NULL, TRUE}, 511 {NULL, NULL, FALSE}}; 512 513 static HeaderEntry hentry_short[] = { 514 {"Date:", NULL, FALSE}, 515 {"From:", NULL, TRUE}, 516 {"To:", NULL, TRUE}, 517 {"Cc:", NULL, TRUE}, 518 {"Newsgroups:", NULL, TRUE}, 519 {"Subject:", NULL, TRUE}, 520 {"Message-ID:", NULL, FALSE}, 521 {"References:", NULL, FALSE}, 522 {"In-Reply-To:", NULL, FALSE}, 523 {"Content-Type:", NULL, FALSE}, 524 {"Seen:", NULL, FALSE}, 525 {"Status:", NULL, FALSE}, 526 {"From ", NULL, FALSE}, 527 {"SC-Marked-For-Download:", NULL, FALSE}, 528 {"SC-Message-Size:",NULL, FALSE}, 529 {NULL, NULL, FALSE}}; 530 531 static HeaderEntry* procheader_get_headernames(gboolean full) 532 { 533 return full ? hentry_full : hentry_short; 534 } 535 536 MsgInfo *procheader_parse_stream(FILE *fp, MsgFlags flags, gboolean full, 537 gboolean decrypted) 538 { 539 return parse_stream(fp, FALSE, flags, full, decrypted); 540 } 541 542 static MsgInfo *parse_stream(void *data, gboolean isstring, MsgFlags flags, 543 gboolean full, gboolean decrypted) 544 { 545 MsgInfo *msginfo; 546 gchar *buf = NULL; 547 gchar *p, *tmp; 548 gchar *hp; 549 HeaderEntry *hentry; 550 gint hnum; 551 void *orig_data = data; 552 553 get_one_field_func get_one_field = 554 isstring ? (get_one_field_func)string_get_one_field 555 : (get_one_field_func)procheader_get_one_field; 556 557 hentry = procheader_get_headernames(full); 558 559 if (MSG_IS_QUEUED(flags) || MSG_IS_DRAFT(flags)) { 560 while (get_one_field(&buf, data, NULL) != -1) { 561 if ((!strncmp(buf, "X-Claws-End-Special-Headers: 1", 562 strlen("X-Claws-End-Special-Headers:"))) || 563 (!strncmp(buf, "X-Sylpheed-End-Special-Headers: 1", 564 strlen("X-Sylpheed-End-Special-Headers:")))) { 565 g_free(buf); 566 buf = NULL; 567 break; 568 } 569 /* from other mailers */ 570 if (!strncmp(buf, "Date: ", 6) 571 || !strncmp(buf, "To: ", 4) 572 || !strncmp(buf, "From: ", 6) 573 || !strncmp(buf, "Subject: ", 9)) { 574 if (isstring) 575 data = orig_data; 576 else 577 rewind((FILE *)data); 578 g_free(buf); 579 buf = NULL; 580 break; 581 } 582 g_free(buf); 583 buf = NULL; 584 } 585 } 586 587 msginfo = procmsg_msginfo_new(); 588 589 if (flags.tmp_flags || flags.perm_flags) 590 msginfo->flags = flags; 591 else 592 MSG_SET_PERM_FLAGS(msginfo->flags, MSG_NEW | MSG_UNREAD); 593 594 msginfo->inreplyto = NULL; 595 596 while ((hnum = get_one_field(&buf, data, hentry)) != -1) { 597 hp = buf + strlen(hentry[hnum].name); 598 while (*hp == ' ' || *hp == '\t') hp++; 599 600 switch (hnum) { 601 case H_DATE: 602 if (msginfo->date) break; 603 msginfo->date_t = 604 procheader_date_parse(NULL, hp, 0); 605 if (g_utf8_validate(hp, -1, NULL)) { 606 msginfo->date = g_strdup(hp); 607 } else { 608 gchar *utf = conv_codeset_strdup( 609 hp, 610 conv_get_locale_charset_str_no_utf8(), 611 CS_INTERNAL); 612 if (utf == NULL || 613 !g_utf8_validate(utf, -1, NULL)) { 614 g_free(utf); 615 utf = g_malloc(strlen(buf)*2+1); 616 conv_localetodisp(utf, 617 strlen(hp)*2+1, hp); 618 } 619 msginfo->date = utf; 620 } 621 break; 622 case H_FROM: 623 if (msginfo->from) break; 624 msginfo->from = conv_unmime_header(hp, NULL, TRUE); 625 msginfo->fromname = procheader_get_fromname(msginfo->from); 626 remove_return(msginfo->from); 627 remove_return(msginfo->fromname); 628 break; 629 case H_TO: 630 tmp = conv_unmime_header(hp, NULL, TRUE); 631 remove_return(tmp); 632 if (msginfo->to) { 633 p = msginfo->to; 634 msginfo->to = 635 g_strconcat(p, ", ", tmp, NULL); 636 g_free(p); 637 } else 638 msginfo->to = g_strdup(tmp); 639 g_free(tmp); 640 break; 641 case H_CC: 642 tmp = conv_unmime_header(hp, NULL, TRUE); 643 remove_return(tmp); 644 if (msginfo->cc) { 645 p = msginfo->cc; 646 msginfo->cc = 647 g_strconcat(p, ", ", tmp, NULL); 648 g_free(p); 649 } else 650 msginfo->cc = g_strdup(tmp); 651 g_free(tmp); 652 break; 653 case H_SUBJECT: 654 if (msginfo->subject) break; 655 msginfo->subject = conv_unmime_header(hp, NULL, FALSE); 656 unfold_line(msginfo->subject); 657 break; 658 case H_MSG_ID: 659 if (msginfo->msgid) break; 660 661 extract_parenthesis(hp, '<', '>'); 662 remove_space(hp); 663 msginfo->msgid = g_strdup(hp); 664 break; 665 case H_REFERENCES: 666 msginfo->references = 667 references_list_prepend(msginfo->references, 668 hp); 669 break; 670 case H_IN_REPLY_TO: 671 if (msginfo->inreplyto) break; 672 673 eliminate_parenthesis(hp, '(', ')'); 674 if ((p = strrchr(hp, '<')) != NULL && 675 strchr(p + 1, '>') != NULL) { 676 extract_parenthesis(p, '<', '>'); 677 remove_space(p); 678 if (*p != '\0') 679 msginfo->inreplyto = g_strdup(p); 680 } 681 break; 682 case H_CONTENT_TYPE: 683 if (!g_ascii_strncasecmp(hp, "multipart/", 10)) 684 MSG_SET_TMP_FLAGS(msginfo->flags, MSG_MULTIPART); 685 break; 686 case H_DISPOSITION_NOTIFICATION_TO: 687 if (!msginfo->extradata) 688 msginfo->extradata = g_new0(MsgInfoExtraData, 1); 689 if (msginfo->extradata->dispositionnotificationto) break; 690 msginfo->extradata->dispositionnotificationto = g_strdup(hp); 691 break; 692 case H_RETURN_RECEIPT_TO: 693 if (!msginfo->extradata) 694 msginfo->extradata = g_new0(MsgInfoExtraData, 1); 695 if (msginfo->extradata->returnreceiptto) break; 696 msginfo->extradata->returnreceiptto = g_strdup(hp); 697 break; 698 /* partial download infos */ 699 case H_SC_PARTIALLY_RETRIEVED: 700 if (!msginfo->extradata) 701 msginfo->extradata = g_new0(MsgInfoExtraData, 1); 702 if (msginfo->extradata->partial_recv) break; 703 msginfo->extradata->partial_recv = g_strdup(hp); 704 break; 705 case H_SC_ACCOUNT_SERVER: 706 if (!msginfo->extradata) 707 msginfo->extradata = g_new0(MsgInfoExtraData, 1); 708 if (msginfo->extradata->account_server) break; 709 msginfo->extradata->account_server = g_strdup(hp); 710 break; 711 case H_SC_ACCOUNT_LOGIN: 712 if (!msginfo->extradata) 713 msginfo->extradata = g_new0(MsgInfoExtraData, 1); 714 if (msginfo->extradata->account_login) break; 715 msginfo->extradata->account_login = g_strdup(hp); 716 break; 717 case H_SC_MESSAGE_SIZE: 718 if (msginfo->total_size) break; 719 msginfo->total_size = atoi(hp); 720 break; 721 case H_SC_PLANNED_DOWNLOAD: 722 msginfo->planned_download = atoi(hp); 723 break; 724 /* end partial download infos */ 725 case H_FROM_SPACE: 726 if (msginfo->fromspace) break; 727 msginfo->fromspace = g_strdup(hp); 728 remove_return(msginfo->fromspace); 729 break; 730 /* list infos */ 731 case H_LIST_POST: 732 if (!msginfo->extradata) 733 msginfo->extradata = g_new0(MsgInfoExtraData, 1); 734 if (msginfo->extradata->list_post) break; 735 msginfo->extradata->list_post = g_strdup(hp); 736 break; 737 case H_LIST_SUBSCRIBE: 738 if (!msginfo->extradata) 739 msginfo->extradata = g_new0(MsgInfoExtraData, 1); 740 if (msginfo->extradata->list_subscribe) break; 741 msginfo->extradata->list_subscribe = g_strdup(hp); 742 break; 743 case H_LIST_UNSUBSCRIBE: 744 if (!msginfo->extradata) 745 msginfo->extradata = g_new0(MsgInfoExtraData, 1); 746 if (msginfo->extradata->list_unsubscribe) break; 747 msginfo->extradata->list_unsubscribe = g_strdup(hp); 748 break; 749 case H_LIST_HELP: 750 if (!msginfo->extradata) 751 msginfo->extradata = g_new0(MsgInfoExtraData, 1); 752 if (msginfo->extradata->list_help) break; 753 msginfo->extradata->list_help = g_strdup(hp); 754 break; 755 case H_LIST_ARCHIVE: 756 if (!msginfo->extradata) 757 msginfo->extradata = g_new0(MsgInfoExtraData, 1); 758 if (msginfo->extradata->list_archive) break; 759 msginfo->extradata->list_archive = g_strdup(hp); 760 break; 761 case H_LIST_OWNER: 762 if (!msginfo->extradata) 763 msginfo->extradata = g_new0(MsgInfoExtraData, 1); 764 if (msginfo->extradata->list_owner) break; 765 msginfo->extradata->list_owner = g_strdup(hp); 766 break; 767 case H_RESENT_FROM: 768 if (!msginfo->extradata) 769 msginfo->extradata = g_new0(MsgInfoExtraData, 1); 770 if (msginfo->extradata->resent_from) break; 771 msginfo->extradata->resent_from = g_strdup(hp); 772 break; 773 /* end list infos */ 774 default: 775 break; 776 } 777 g_free(buf); 778 buf = NULL; 779 } 780 781 if (!msginfo->inreplyto && msginfo->references) 782 msginfo->inreplyto = 783 g_strdup((gchar *)msginfo->references->data); 784 785 return msginfo; 786 } 787 788 gchar *procheader_get_fromname(const gchar *str) 789 { 790 gchar *tmp, *name; 791 792 Xstrdup_a(tmp, str, return NULL); 793 794 if (*tmp == '\"') { 795 extract_quote(tmp, '\"'); 796 g_strstrip(tmp); 797 } else if (strchr(tmp, '<')) { 798 eliminate_parenthesis(tmp, '<', '>'); 799 g_strstrip(tmp); 800 if (*tmp == '\0') { 801 strcpy(tmp, str); 802 extract_parenthesis(tmp, '<', '>'); 803 g_strstrip(tmp); 804 } 805 } else if (strchr(tmp, '(')) { 806 extract_parenthesis(tmp, '(', ')'); 807 g_strstrip(tmp); 808 } 809 810 if (*tmp == '\0') 811 name = g_strdup(str); 812 else 813 name = g_strdup(tmp); 814 815 return name; 816 } 817 818 static gint procheader_remove_comment_in_date_string(gchar *o_str) 819 { 820 gchar str[strlen(o_str)+1]; 821 int i, j = 0; 822 int in_comment_nest_level = 0; 823 gboolean flag_escape_backslash = FALSE; 824 825 for (i=0; i < strlen(o_str); i++) { 826 switch (o_str[i]) { 827 case '(': 828 in_comment_nest_level++; 829 if (in_comment_nest_level > 16) { 830 str[j] = '\0'; 831 return TRUE; 832 } 833 continue; 834 case '\\': 835 if (in_comment_nest_level > 0) { 836 flag_escape_backslash = TRUE; 837 continue; 838 } 839 break; 840 case ')': 841 if (flag_escape_backslash == TRUE) { 842 flag_escape_backslash = FALSE; 843 continue; 844 } 845 in_comment_nest_level--; 846 if (in_comment_nest_level < 0) { 847 str[j] = '\0'; 848 return TRUE; 849 } 850 continue; 851 default: 852 if (in_comment_nest_level > 0) { 853 if (flag_escape_backslash == TRUE) 854 flag_escape_backslash = FALSE; 855 continue; 856 } 857 break; 858 } 859 str[j++] = o_str[i]; 860 } 861 str[j] = '\0'; 862 strcpy(o_str, str); 863 return TRUE; 864 } 865 866 867 static gint procheader_scan_date_string(const gchar *o_str, 868 gchar *weekday, gint *day, 869 gchar *month, gint *year, 870 gint *hh, gint *mm, gint *ss, 871 gchar *zone) 872 { 873 gint result; 874 gint month_n; 875 gint secfract; 876 gint zone1 = 0, zone2 = 0; 877 gchar offset_sign, zonestr[7]; 878 gchar sep1; 879 880 if (o_str == NULL) 881 return -1; 882 883 gchar str[strlen(o_str)+1]; 884 885 strcpy(str, o_str); 886 if (strchr(str, '(') != NULL) 887 procheader_remove_comment_in_date_string(str); 888 889 result = sscanf(str, "%10s %d %9s %d %2d:%2d:%2d %6s", 890 weekday, day, month, year, hh, mm, ss, zone); 891 if (result == 8) return 0; 892 893 /* RFC2822 */ 894 result = sscanf(str, "%3s,%d %9s %d %2d:%2d:%2d %6s", 895 weekday, day, month, year, hh, mm, ss, zone); 896 if (result == 8) return 0; 897 898 result = sscanf(str, "%3s %3s %d %2d:%2d:%2d %d %6s", 899 weekday, month, day, hh, mm, ss, year, zone); 900 if (result == 8) return 0; 901 902 result = sscanf(str, "%d %9s %d %2d:%2d:%2d %6s", 903 day, month, year, hh, mm, ss, zone); 904 if (result == 7) return 0; 905 906 *zone = '\0'; 907 result = sscanf(str, "%10s %d %9s %d %2d:%2d:%2d", 908 weekday, day, month, year, hh, mm, ss); 909 if (result == 7) return 0; 910 911 result = sscanf(str, "%3s %3s %d %2d:%2d:%2d %d", 912 weekday, month, day, hh, mm, ss, year); 913 if (result == 7) return 0; 914 915 result = sscanf(str, "%d %9s %d %2d:%2d:%2d", 916 day, month, year, hh, mm, ss); 917 if (result == 6) return 0; 918 919 *ss = 0; 920 result = sscanf(str, "%10s %d %9s %d %2d:%2d %6s", 921 weekday, day, month, year, hh, mm, zone); 922 if (result == 7) return 0; 923 924 result = sscanf(str, "%d %9s %d %2d:%2d %5s", 925 day, month, year, hh, mm, zone); 926 if (result == 6) return 0; 927 928 *zone = '\0'; 929 result = sscanf(str, "%10s %d %9s %d %2d:%2d", 930 weekday, day, month, year, hh, mm); 931 if (result == 6) return 0; 932 933 result = sscanf(str, "%d %9s %d %2d:%2d", 934 day, month, year, hh, mm); 935 if (result == 5) return 0; 936 937 *weekday = '\0'; 938 939 /* RFC3339 subset, with fraction of second */ 940 result = sscanf(str, "%4d-%2d-%2d%c%2d:%2d:%2d.%d%6s", 941 year, &month_n, day, &sep1, hh, mm, ss, &secfract, zonestr); 942 if (result == 9 943 && (sep1 == 'T' || sep1 == 't' || sep1 == ' ')) { 944 if (month_n >= 1 && month_n <= 12) { 945 strncpy2(month, monthstr+((month_n-1)*3), 4); 946 if (zonestr[0] == 'z' || zonestr[0] == 'Z') { 947 strlcat(zone, "+00:00", sizeof(zone)); 948 } else if (sscanf(zonestr, "%c%2d:%2d", 949 &offset_sign, &zone1, &zone2) == 3) { 950 strlcat(zone, zonestr, sizeof(zone)); 951 } 952 return 0; 953 } 954 } 955 956 /* RFC3339 subset, no fraction of second */ 957 result = sscanf(str, "%4d-%2d-%2d%c%2d:%2d:%2d%6s", 958 year, &month_n, day, &sep1, hh, mm, ss, zonestr); 959 if (result == 8 960 && (sep1 == 'T' || sep1 == 't' || sep1 == ' ')) { 961 if (month_n >= 1 && month_n <= 12) { 962 strncpy2(month, monthstr+((month_n-1)*3), 4); 963 if (zonestr[0] == 'z' || zonestr[0] == 'Z') { 964 strlcat(zone, "+00:00", sizeof(zone)); 965 } else if (sscanf(zonestr, "%c%2d:%2d", 966 &offset_sign, &zone1, &zone2) == 3) { 967 strlcat(zone, zonestr, sizeof(zone)); 968 } 969 return 0; 970 } 971 } 972 973 *zone = '\0'; 974 975 /* RFC3339 subset, no fraction of second, and no timezone offset */ 976 /* This particular "subset" is invalid, RFC requires the offset */ 977 result = sscanf(str, "%4d-%2d-%2d %2d:%2d:%2d", 978 year, &month_n, day, hh, mm, ss); 979 if (result == 6) { 980 if (1 <= month_n && month_n <= 12) { 981 strncpy2(month, monthstr+((month_n-1)*3), 4); 982 return 0; 983 } 984 } 985 986 /* ISO8601 format with just date (YYYY-MM-DD) */ 987 result = sscanf(str, "%4d-%2d-%2d", 988 year, &month_n, day); 989 if (result == 3) { 990 *hh = *mm = *ss = 0; 991 if (1 <= month_n && month_n <= 12) { 992 strncpy2(month, monthstr+((month_n-1)*3), 4); 993 return 0; 994 } 995 } 996 997 return -1; 998 } 999 1000 /* 1001 * Hiro, most UNIXen support this function: 1002 * http://www.mcsr.olemiss.edu/cgi-bin/man-cgi?getdate 1003 */ 1004 gboolean procheader_date_parse_to_tm(const gchar *src, struct tm *t, char *zone) 1005 { 1006 gchar weekday[11]; 1007 gint day; 1008 gchar month[10]; 1009 gint year; 1010 gint hh, mm, ss; 1011 GDateMonth dmonth; 1012 gchar *p; 1013 1014 if (!t) 1015 return FALSE; 1016 1017 memset(t, 0, sizeof *t); 1018 1019 if (procheader_scan_date_string(src, weekday, &day, month, &year, 1020 &hh, &mm, &ss, zone) < 0) { 1021 g_warning("invalid date: %s", src); 1022 return FALSE; 1023 } 1024 1025 /* Y2K compliant :) */ 1026 if (year < 100) { 1027 if (year < 70) 1028 year += 2000; 1029 else 1030 year += 1900; 1031 } 1032 1033 month[3] = '\0'; 1034 if ((p = strstr(monthstr, month)) != NULL) 1035 dmonth = (gint)(p - monthstr) / 3 + 1; 1036 else { 1037 g_warning("invalid month: %s", month); 1038 dmonth = G_DATE_BAD_MONTH; 1039 } 1040 1041 t->tm_sec = ss; 1042 t->tm_min = mm; 1043 t->tm_hour = hh; 1044 t->tm_mday = day; 1045 t->tm_mon = dmonth - 1; 1046 t->tm_year = year - 1900; 1047 t->tm_wday = 0; 1048 t->tm_yday = 0; 1049 t->tm_isdst = -1; 1050 1051 mktime(t); 1052 1053 return TRUE; 1054 } 1055 1056 time_t procheader_date_parse(gchar *dest, const gchar *src, gint len) 1057 { 1058 gchar weekday[11]; 1059 gint day; 1060 gchar month[10]; 1061 gint year; 1062 gint hh, mm, ss; 1063 gchar zone[7]; 1064 GDateMonth dmonth = G_DATE_BAD_MONTH; 1065 gchar *p; 1066 time_t timer; 1067 1068 if (procheader_scan_date_string(src, weekday, &day, month, &year, 1069 &hh, &mm, &ss, zone) < 0) { 1070 if (dest && len > 0) 1071 strncpy2(dest, src, len); 1072 return 0; 1073 } 1074 1075 month[3] = '\0'; 1076 for (p = monthstr; *p != '\0'; p += 3) { 1077 if (!g_ascii_strncasecmp(p, month, 3)) { 1078 dmonth = (gint)(p - monthstr) / 3 + 1; 1079 break; 1080 } 1081 } 1082 struct tm t; 1083 time_t tz_offset; 1084 1085 /* Y2K compliant :) */ 1086 if (year < 1000) { 1087 if (year < 50) 1088 year += 2000; 1089 else 1090 year += 1900; 1091 } 1092 1093 t.tm_sec = ss; 1094 t.tm_min = mm; 1095 t.tm_hour = hh; 1096 t.tm_mday = day; 1097 t.tm_mon = dmonth - 1; 1098 t.tm_year = year - 1900; 1099 t.tm_wday = 0; 1100 t.tm_yday = 0; 1101 t.tm_isdst = -1; 1102 1103 timer = mktime(&t); 1104 tz_offset = remote_tzoffset_sec(zone); 1105 if (tz_offset != -1) 1106 timer += tzoffset_sec(&timer) - tz_offset; 1107 1108 if (dest) 1109 procheader_date_get_localtime(dest, len, timer); 1110 1111 return timer; 1112 } 1113 1114 void procheader_date_get_localtime(gchar *dest, gint len, const time_t timer) 1115 { 1116 struct tm *lt; 1117 gchar *default_format = "%y/%m/%d(%a) %H:%M"; 1118 gchar *str; 1119 const gchar *src_codeset, *dest_codeset; 1120 struct tm buf; 1121 1122 if (timer > 0) 1123 lt = localtime_r(&timer, &buf); 1124 else { 1125 time_t dummy = 1; 1126 lt = localtime_r(&dummy, &buf); 1127 } 1128 1129 if (prefs_common.date_format) 1130 fast_strftime(dest, len, prefs_common.date_format, lt); 1131 else 1132 fast_strftime(dest, len, default_format, lt); 1133 1134 if (!g_utf8_validate(dest, -1, NULL)) { 1135 src_codeset = conv_get_locale_charset_str_no_utf8(); 1136 dest_codeset = CS_UTF_8; 1137 str = conv_codeset_strdup(dest, src_codeset, dest_codeset); 1138 if (str) { 1139 strncpy2(dest, str, len); 1140 g_free(str); 1141 } 1142 } 1143 } 1144 1145 /* Added by Mel Hadasht on 27 Aug 2001 */ 1146 /* Get a header from msginfo */ 1147 gint procheader_get_header_from_msginfo(MsgInfo *msginfo, gchar **buf, gchar *header) 1148 { 1149 gchar *file; 1150 FILE *fp; 1151 HeaderEntry hentry[]={ { NULL, NULL, TRUE }, 1152 { NULL, NULL, FALSE } }; 1153 gint val; 1154 1155 cm_return_val_if_fail(msginfo != NULL, -1); 1156 cm_return_val_if_fail(buf != NULL, -1); 1157 cm_return_val_if_fail(header != NULL, -1); 1158 1159 hentry[0].name = header; 1160 1161 file = procmsg_get_message_file_path(msginfo); 1162 if ((fp = g_fopen(file, "rb")) == NULL) { 1163 FILE_OP_ERROR(file, "g_fopen"); 1164 g_free(file); 1165 g_free(*buf); 1166 *buf = NULL; 1167 return -1; 1168 } 1169 val = procheader_get_one_field(buf, fp, hentry); 1170 1171 if (fclose(fp) == EOF) { 1172 FILE_OP_ERROR(file, "fclose"); 1173 unlink(file); 1174 g_free(file); 1175 g_free(*buf); 1176 *buf = NULL; 1177 return -1; 1178 } 1179 1180 g_free(file); 1181 if (val == -1) { 1182 /* *buf is already NULL in that case, see procheader_get_one_field() */ 1183 return -1; 1184 } 1185 1186 return 0; 1187 } 1188 1189 HeaderEntry *procheader_entries_from_str(const gchar *str) 1190 { 1191 HeaderEntry *entries = NULL, *he; 1192 int numh = 0, i = 0; 1193 gchar **names = NULL; 1194 const gchar *s = str; 1195 1196 if (s == NULL) { 1197 return NULL; 1198 } 1199 while (*s != '\0') { 1200 if (*s == ' ') ++numh; 1201 ++s; 1202 } 1203 if (numh == 0) { 1204 return NULL; 1205 } 1206 entries = g_new0(HeaderEntry, numh + 1); /* room for last NULL */ 1207 s = str; 1208 ++s; /* skip first space */ 1209 names = g_strsplit(s, " ", numh); 1210 he = entries; 1211 while (names[i]) { 1212 he->name = g_strdup_printf("%s:", names[i]); 1213 he->body = NULL; 1214 he->unfold = FALSE; 1215 ++i, ++he; 1216 } 1217 he->name = NULL; 1218 g_strfreev(names); 1219 return entries; 1220 } 1221 1222 void procheader_entries_free (HeaderEntry *entries) 1223 { 1224 if (entries != NULL) { 1225 HeaderEntry *he = entries; 1226 while (he->name != NULL) { 1227 g_free(he->name); 1228 if (he->body != NULL) 1229 g_free(he->body); 1230 ++he; 1231 } 1232 g_free(entries); 1233 } 1234 } 1235 1236 gboolean procheader_header_is_internal(const gchar *hdr_name) 1237 { 1238 const gchar *internal_hdrs[] = { 1239 "AF:", "NF:", "PS:", "SRH:", "SFN:", "DSR:", "MID:", "CFG:", 1240 "PT:", "S:", "RQ:", "SSV:", "NSV:", "SSH:", "R:", "MAID:", 1241 "SCF:", "RRCPT:", "RMID:", "FMID:", "NAID:", 1242 "X-Claws-Account-Id:", 1243 "X-Claws-Auto-Wrapping:", 1244 "X-Claws-Auto-Indent:", 1245 "X-Claws-End-Special-Headers:", 1246 "X-Sylpheed-Account-Id:", 1247 "X-Sylpheed-End-Special-Headers:", 1248 NULL 1249 }; 1250 int i; 1251 1252 for (i = 0; internal_hdrs[i]; i++) { 1253 if (!strcmp(hdr_name, internal_hdrs[i])) 1254 return TRUE; 1255 } 1256 return FALSE; 1257 }