ba5133ff2eca6c82f145b629decd8abf9224da3c
[claws.git] / src / procheader.c
1 /*
2  * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client
3  * Copyright (C) 1999-2001 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 2 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, write to the Free Software
17  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
18  */
19
20 #ifdef HAVE_CONFIG_H
21 #  include "config.h"
22 #endif
23
24 #include <glib.h>
25 #include <stdio.h>
26 #include <string.h>
27 #include <stdlib.h>
28 #include <time.h>
29
30 #include "intl.h"
31 #include "procheader.h"
32 #include "procmsg.h"
33 #include "codeconv.h"
34 #include "prefs_common.h"
35 #include "utils.h"
36
37 #define BUFFSIZE        8192
38
39 /*
40   procheader_get_one_field
41   - reads fp and puts the header and the corresponding content into buf
42     if one of these is one of hentry table.
43   - if hentry is NULL, ignores no headers
44  */
45
46 gint procheader_get_one_field(gchar *buf, gint len, FILE *fp,
47                               HeaderEntry hentry[])
48 {
49         gint nexthead;
50         gint hnum = 0;
51         HeaderEntry *hp = NULL;
52
53         if (hentry != NULL) {
54                 /* skip non-required headers */
55                 do {
56                         do {
57                                 if (fgets(buf, len, fp) == NULL)
58                                         return -1;
59                                 if (buf[0] == '\r' || buf[0] == '\n')
60                                         return -1;
61                         } while (buf[0] == ' ' || buf[0] == '\t');
62
63                         for (hp = hentry, hnum = 0; hp->name != NULL;
64                              hp++, hnum++) {
65                                 if (!strncasecmp(hp->name, buf,
66                                                  strlen(hp->name)))
67                                         break;
68                         }
69                 } while (hp->name == NULL);
70         } else {
71                 if (fgets(buf, len, fp) == NULL) return -1;
72                 if (buf[0] == '\r' || buf[0] == '\n') return -1;
73         }
74
75         /* unfold the specified folded line */
76         if (hp && hp->unfold) {
77                 gboolean folded = FALSE;
78                 gchar *bufp = buf + strlen(buf);
79
80                 while (1) {
81                         nexthead = fgetc(fp);
82
83                         /* folded */
84                         if (nexthead == ' ' || nexthead == '\t')
85                                 folded = TRUE;
86                         else if (nexthead == EOF)
87                                 break;
88                         else if (folded == TRUE) {
89                                 /* concatenate next line */
90                                 if ((len - (bufp - buf)) <= 2) break;
91
92                                 /* replace return code on the tail end
93                                    with space */
94                                 *(bufp - 1) = ' ';
95                                 *bufp++ = nexthead;
96                                 *bufp = '\0';
97                                 if (nexthead == '\r' || nexthead == '\n') {
98                                         folded = FALSE;
99                                         continue;
100                                 }
101                                 if (fgets(bufp, len - (bufp - buf), fp)
102                                     == NULL) break;
103                                 bufp += strlen(bufp);
104
105                                 folded = FALSE;
106                         } else {
107                                 ungetc(nexthead, fp);
108                                 break;
109                         }
110                 }
111
112                 /* remove trailing return code */
113                 strretchomp(buf);
114
115                 return hnum;
116         }
117
118         while (1) {
119                 nexthead = fgetc(fp);
120                 if (nexthead == ' ' || nexthead == '\t') {
121                         size_t buflen = strlen(buf);
122
123                         /* concatenate next line */
124                         if ((len - buflen) > 2) {
125                                 gchar *p = buf + buflen;
126
127                                 *p++ = nexthead;
128                                 *p = '\0';
129                                 buflen++;
130                                 if (fgets(p, len - buflen, fp) == NULL)
131                                         break;
132                         } else
133                                 break;
134                 } else {
135                         if (nexthead != EOF)
136                                 ungetc(nexthead, fp);
137                         break;
138                 }
139         }
140
141         /* remove trailing return code */
142         strretchomp(buf);
143
144         return hnum;
145 }
146
147 gchar *procheader_get_unfolded_line(gchar *buf, gint len, FILE *fp)
148 {
149         gboolean folded = FALSE;
150         gint nexthead;
151         gchar *bufp;
152
153         if (fgets(buf, len, fp) == NULL) return NULL;
154         if (buf[0] == '\r' || buf[0] == '\n') return NULL;
155         bufp = buf + strlen(buf);
156
157         while (1) {
158                 nexthead = fgetc(fp);
159
160                 /* folded */
161                 if (nexthead == ' ' || nexthead == '\t')
162                         folded = TRUE;
163                 else if (nexthead == EOF)
164                         break;
165                 else if (folded == TRUE) {
166                         /* concatenate next line */
167                         if ((len - (bufp - buf)) <= 2) break;
168
169                         /* replace return code on the tail end
170                            with space */
171                         *(bufp - 1) = ' ';
172                         *bufp++ = nexthead;
173                         *bufp = '\0';
174                         if (nexthead == '\r' || nexthead == '\n') {
175                                 folded = FALSE;
176                                 continue;
177                         }
178                         if (fgets(bufp, len - (bufp - buf), fp)
179                             == NULL) break;
180                         bufp += strlen(bufp);
181
182                         folded = FALSE;
183                 } else {
184                         ungetc(nexthead, fp);
185                         break;
186                 }
187         }
188
189         /* remove trailing return code */
190         strretchomp(buf);
191
192         return buf;
193 }
194
195 /*
196   tests whether two headers are equal
197 */
198
199 gboolean procheader_headername_equal(char * hdr1, char * hdr2)
200 {
201         int len1;
202         int len2;
203
204         len1 = strlen(hdr1);
205         len2 = strlen(hdr2);
206         if ((hdr1[len1 - 1] == ':') || (hdr1[len1 - 1] == ' '))
207                 len1--;
208         if ((hdr2[len2 - 1] == ':') || (hdr2[len2 - 1] == ' '))
209                 len2--;
210         if (len1 != len2)
211                 return 0;
212         return (strncasecmp(hdr1, hdr2, len1) == 0);
213 }
214
215 void procheader_header_free(Header * header)
216 {
217         g_free(header->name);
218         g_free(header->body);
219         g_free(header);
220 }
221
222 /*
223   parse headers, for example :
224   From: dinh@enseirb.fr becomes :
225   header->name = "From:"
226   header->body = "dinh@enseirb.fr"
227  */
228
229 Header * procheader_parse_header(gchar * buf)
230 {
231         gchar tmp[BUFFSIZE];
232         gchar *p = buf;
233         Header * header;
234
235         if ((*buf == ':') || (*buf == ' '))
236                 return NULL;
237
238         for (p = buf; *p ; p++) {
239                 if ((*p == ':') || (*p == ' ')) {
240                         header = g_new(Header, 1);
241                         header->name = g_strndup(buf, p - buf + 1);
242                         p++;
243                         while (*p == ' ' || *p == '\t') p++;
244                         conv_unmime_header(tmp, sizeof(tmp), p, NULL);
245                         header->body = g_strdup(tmp);
246                         return header;
247                 }
248         }
249         return NULL;
250 }
251
252 GSList *procheader_get_header_list(const gchar *file)
253 {
254         FILE *fp;
255         gchar buf[BUFFSIZE], tmp[BUFFSIZE];
256         gchar *p;
257         GSList *hlist = NULL;
258         Header *header;
259
260         if ((fp = fopen(file, "r")) == NULL) {
261                 FILE_OP_ERROR(file, "fopen");
262                 return NULL;
263         }
264
265         while (procheader_get_unfolded_line(buf, sizeof(buf), fp) != NULL) {
266                 header = procheader_parse_header(buf);
267                 if (header != NULL)
268                         hlist = g_slist_append(hlist, header);
269         }
270
271         fclose(fp);
272         return hlist;
273 }
274
275 void procheader_header_list_destroy(GSList *hlist)
276 {
277         Header *header;
278
279         while (hlist != NULL) {
280                 header = hlist->data;
281
282                 procheader_header_free(header);
283                 hlist = g_slist_remove(hlist, header);
284         }
285 }
286
287 void procheader_get_header_fields(FILE *fp, HeaderEntry hentry[])
288 {
289         gchar buf[BUFFSIZE];
290         HeaderEntry *hp;
291         gint hnum;
292         gchar *p;
293
294         if (hentry == NULL) return;
295
296         while ((hnum = procheader_get_one_field(buf, sizeof(buf), fp, hentry))
297                != -1) {
298                 hp = hentry + hnum;
299
300                 p = buf + strlen(hp->name);
301                 while (*p == ' ' || *p == '\t') p++;
302
303                 if (hp->body == NULL)
304                         hp->body = g_strdup(p);
305                 else if (!strcasecmp(hp->name, "To:") ||
306                          !strcasecmp(hp->name, "Cc:")) {
307                         gchar *tp = hp->body;
308                         hp->body = g_strconcat(tp, ", ", p, NULL);
309                         g_free(tp);
310                 }
311         }
312 }
313
314 enum
315 {
316         H_DATE          = 0,
317         H_FROM          = 1,
318         H_TO            = 2,
319         H_CC            = 3,
320         H_NEWSGROUPS    = 4,
321         H_SUBJECT       = 5,
322         H_MSG_ID        = 6,
323         H_REFERENCES    = 7,
324         H_IN_REPLY_TO   = 8,
325         H_CONTENT_TYPE  = 9,
326         H_SEEN          = 10,
327         H_X_FACE        = 11,
328         H_DISPOSITION_NOTIFICATION_TO = 12
329 };
330
331 MsgInfo *procheader_parse(const gchar *file, MsgFlags flags, gboolean full)
332 {
333         static HeaderEntry hentry_full[] = {{"Date:",           NULL, FALSE},
334                                            {"From:",            NULL, TRUE},
335                                            {"To:",              NULL, TRUE},
336                                            {"Cc:",              NULL, TRUE},
337                                            {"Newsgroups:",      NULL, TRUE},
338                                            {"Subject:",         NULL, TRUE},
339                                            {"Message-Id:",      NULL, FALSE},
340                                            {"References:",      NULL, FALSE},
341                                            {"In-Reply-To:",     NULL, FALSE},
342                                            {"Content-Type:",    NULL, FALSE},
343                                            {"Seen:",            NULL, FALSE},
344                                            {"X-Face:",          NULL, FALSE},
345                                            {"Disposition-Notification-To:",NULL, FALSE},
346                                            {NULL,               NULL, FALSE}};
347
348         static HeaderEntry hentry_short[] = {{"Date:",          NULL, FALSE},
349                                             {"From:",           NULL, TRUE},
350                                             {"To:",             NULL, TRUE},
351                                             {"Cc:",             NULL, TRUE},
352                                             {"Newsgroups:",     NULL, TRUE},
353                                             {"Subject:",        NULL, TRUE},
354                                             {"Message-Id:",     NULL, FALSE},
355                                             {"References:",     NULL, FALSE},
356                                             {"In-Reply-To:",    NULL, FALSE},
357                                             {"Content-Type:",   NULL, FALSE},
358                                             {"Seen:",           NULL, FALSE},
359                                             {NULL,              NULL, FALSE}};
360
361         FILE *fp;
362         MsgInfo *msginfo;
363         gchar buf[BUFFSIZE], tmp[BUFFSIZE];
364         gchar *reference = NULL;
365         gchar *p;
366         gchar *hp;
367         HeaderEntry *hentry;
368         gint hnum;
369
370         hentry = full ? hentry_full : hentry_short;
371
372         if ((fp = fopen(file, "r")) == NULL) {
373                 FILE_OP_ERROR(file, "fopen");
374                 return NULL;
375         }
376         if (MSG_IS_QUEUED(flags)) {
377                 while (fgets(buf, sizeof(buf), fp) != NULL)
378                         if (buf[0] == '\r' || buf[0] == '\n') break;
379         }
380
381         msginfo = g_new0(MsgInfo, 1);
382         msginfo->flags = flags != 0 ? flags : MSG_NEW|MSG_UNREAD;
383         msginfo->inreplyto = NULL;
384
385         while ((hnum = procheader_get_one_field(buf, sizeof(buf), fp, hentry))
386                != -1) {
387                 hp = buf + strlen(hentry[hnum].name);
388                 while (*hp == ' ' || *hp == '\t') hp++;
389
390                 switch (hnum) {
391                 case H_DATE:
392                         if (msginfo->date) break;
393                         msginfo->date_t =
394                                 procheader_date_parse(NULL, hp, 0);
395                         msginfo->date = g_strdup(hp);
396                         break;
397                 case H_FROM:
398                         if (msginfo->from) break;
399                         conv_unmime_header(tmp, sizeof(tmp), hp, NULL);
400                         msginfo->from = g_strdup(tmp);
401                         msginfo->fromname = procheader_get_fromname(tmp);
402                         break;
403                 case H_TO:
404                         conv_unmime_header(tmp, sizeof(tmp), hp, NULL);
405                         if (msginfo->to) {
406                                 p = msginfo->to;
407                                 msginfo->to =
408                                         g_strconcat(p, ", ", tmp, NULL);
409                                 g_free(p);
410                         } else
411                                 msginfo->to = g_strdup(tmp);
412                         break;
413                 case H_CC:
414                         conv_unmime_header(tmp, sizeof(tmp), hp, NULL);
415                         if (msginfo->cc) {
416                                 p = msginfo->cc;
417                                 msginfo->cc =
418                                         g_strconcat(p, ", ", tmp, NULL);
419                                 g_free(p);
420                         } else
421                                 msginfo->cc = g_strdup(tmp);
422                         break;
423                 case H_NEWSGROUPS:
424                         if (msginfo->newsgroups) {
425                                 p = msginfo->newsgroups;
426                                 msginfo->newsgroups =
427                                         g_strconcat(p, ",", hp, NULL);
428                                 g_free(p);
429                         } else
430                                 msginfo->newsgroups = g_strdup(buf + 12);
431                         break;
432                 case H_SUBJECT:
433                         if (msginfo->subject) break;
434                         conv_unmime_header(tmp, sizeof(tmp), hp, NULL);
435                         msginfo->subject = g_strdup(tmp);
436                         break;
437                 case H_MSG_ID:
438                         if (msginfo->msgid) break;
439
440                         extract_parenthesis(hp, '<', '>');
441                         remove_space(hp);
442                         msginfo->msgid = g_strdup(hp);
443                         break;
444                 case H_REFERENCES:
445                         if (!reference) {
446                                 eliminate_parenthesis(hp, '(', ')');
447                                 if ((p = strrchr(hp, '<')) != NULL &&
448                                     strchr(p + 1, '>') != NULL) {
449                                         extract_parenthesis(p, '<', '>');
450                                         remove_space(p);
451                                         if (*p != '\0')
452                                                 reference = g_strdup(p);
453                                 }
454                         }
455                         break;
456                 case H_IN_REPLY_TO:
457                         if (!reference) {
458                                 eliminate_parenthesis(hp, '(', ')');
459                                 extract_parenthesis(hp, '<', '>');
460                                 remove_space(hp);
461                                 if (*hp != '\0')
462                                         reference = g_strdup(hp);
463                         }
464                         break;
465                 case H_CONTENT_TYPE:
466                         if (!strncasecmp(hp, "multipart", 9))
467                                 msginfo->flags |= MSG_MIME;
468                         break;
469                 case H_SEEN:
470                         /* mnews Seen header */
471                         MSG_UNSET_FLAGS(msginfo->flags, MSG_NEW|MSG_UNREAD);
472                         break;
473                 case H_X_FACE:
474                         if (msginfo->xface) break;
475                         msginfo->xface = g_strdup(hp);
476                         break;
477                 case H_DISPOSITION_NOTIFICATION_TO:
478                         if (msginfo->dispositionnotificationto) break;
479                         msginfo->dispositionnotificationto = g_strdup(hp);
480                         break;
481                 default:
482                 }
483         }
484         msginfo->inreplyto = reference;
485
486         fclose(fp);
487
488         return msginfo;
489 }
490
491 gchar *procheader_get_fromname(const gchar *str)
492 {
493         gchar *tmp, *name;
494
495         Xalloca(tmp, strlen(str) + 1, return NULL);
496         strcpy(tmp, str);
497
498         if (*tmp == '\"') {
499                 extract_quote(tmp, '\"');
500                 g_strstrip(tmp);
501         } else if (strchr(tmp, '<')) {
502                 eliminate_parenthesis(tmp, '<', '>');
503                 g_strstrip(tmp);
504                 if (*tmp == '\0') {
505                         strcpy(tmp, str);
506                         extract_parenthesis(tmp, '<', '>');
507                         g_strstrip(tmp);
508                 }
509         } else if (strchr(tmp, '(')) {
510                 extract_parenthesis(tmp, '(', ')');
511                 g_strstrip(tmp);
512         }
513
514         if (*tmp == '\0')
515                 name = g_strdup(str);
516         else
517                 name = g_strdup(tmp);
518
519         return name;
520 }
521
522 time_t procheader_date_parse(gchar *dest, const gchar *src, gint len)
523 {
524         static gchar monthstr[] = "JanFebMarAprMayJunJulAugSepOctNovDec";
525         gchar weekday[4];
526         gint day;
527         gchar month[4];
528         gint year;
529         gint hh, mm, ss;
530         gchar zone[6];
531         gint result;
532         GDateMonth dmonth;
533         struct tm t;
534         gchar *p;
535         time_t timer;
536
537         /* parsing date field... */
538         result = sscanf(src, "%3s, %d %3s %d %2d:%2d:%2d %5s",
539                         weekday, &day, month, &year, &hh, &mm, &ss, zone);
540         if (result != 8) {
541                 result = sscanf(src, "%d %3s %d %2d:%2d:%2d %5s",
542                                 &day, month, &year, &hh, &mm, &ss, zone);
543                 if (result != 7) {
544                         ss = 0;
545                         result = sscanf(src, "%3s, %d %3s %d %2d:%2d %5s",
546                                         weekday, &day, month, &year, &hh, &mm, zone);
547                         if (result != 7) {
548                                 result = sscanf(src, "%d %3s %d %2d:%2d %5s",
549                                                 &day, month, &year, &hh, &mm,
550                                                 zone);
551                                 if (result != 6) {
552                                         g_warning("Invalid date: %s\n", src);
553                                         if (dest && len > 0)
554                                                 strncpy2(dest, src, len);
555                                         return 0;
556                                 }
557                         }
558                 }
559         }
560
561         /* Y2K compliant :) */
562         if (year < 100) {
563                 if (year < 70)
564                         year += 2000;
565                 else
566                         year += 1900;
567         }
568
569         if ((p = strstr(monthstr, month)) != NULL)
570                 dmonth = (gint)(p - monthstr) / 3 + 1;
571         else {
572                 g_warning("Invalid month: %s\n", month);
573                 dmonth = G_DATE_BAD_MONTH;
574         }
575
576         t.tm_sec = ss;
577         t.tm_min = mm;
578         t.tm_hour = hh;
579         t.tm_mday = day;
580         t.tm_mon = dmonth - 1;
581         t.tm_year = year - 1900;
582         t.tm_wday = 0;
583         t.tm_yday = 0;
584         t.tm_isdst = -1;
585
586         timer = mktime(&t);
587         timer += tzoffset_sec(&timer) - remote_tzoffset_sec(zone);
588
589         if (dest)
590                 procheader_date_get_localtime(dest, len, timer);
591
592         return timer;
593 }
594
595 void procheader_date_get_localtime(gchar *dest, gint len, const time_t timer)
596 {
597         struct tm *lt;
598         gchar *default_format = "%y/%m/%d(%a) %H:%M";
599
600         lt = localtime(&timer);
601
602         if (prefs_common.date_format)
603                 strftime(dest, len, prefs_common.date_format, lt);
604         else
605                 strftime(dest, len, default_format, lt);
606 }