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