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