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