talons

Fork of Claws Mail https://www.claws-mail
Log | Files | Refs | README | LICENSE

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 }