diff options
Diffstat (limited to 'libsylph/procheader.c')
-rw-r--r-- | libsylph/procheader.c | 799 |
1 files changed, 799 insertions, 0 deletions
diff --git a/libsylph/procheader.c b/libsylph/procheader.c new file mode 100644 index 00000000..4ca1490c --- /dev/null +++ b/libsylph/procheader.c @@ -0,0 +1,799 @@ +/* + * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client + * Copyright (C) 1999-2005 Hiroyuki Yamamoto + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include <glib.h> +#include <glib/gi18n.h> +#include <stdio.h> +#include <string.h> +#include <stdlib.h> +#include <time.h> +#include <sys/stat.h> + +#include "procheader.h" +#include "procmsg.h" +#include "codeconv.h" +#include "prefs_common.h" +#include "utils.h" + +#define BUFFSIZE 8192 + +gint procheader_get_one_field(gchar *buf, size_t len, FILE *fp, + HeaderEntry hentry[]) +{ + gint nexthead; + gint hnum = 0; + HeaderEntry *hp = NULL; + + if (hentry != NULL) { + /* skip non-required headers */ + do { + do { + if (fgets(buf, len, fp) == NULL) + return -1; + if (buf[0] == '\r' || buf[0] == '\n') + return -1; + } while (buf[0] == ' ' || buf[0] == '\t'); + + for (hp = hentry, hnum = 0; hp->name != NULL; + hp++, hnum++) { + if (!g_ascii_strncasecmp(hp->name, buf, + strlen(hp->name))) + break; + } + } while (hp->name == NULL); + } else { + if (fgets(buf, len, fp) == NULL) return -1; + if (buf[0] == '\r' || buf[0] == '\n') return -1; + } + + /* unfold the specified folded line */ + if (hp && hp->unfold) { + gboolean folded = FALSE; + gchar *bufp = buf + strlen(buf); + + for (; bufp > buf && + (*(bufp - 1) == '\n' || *(bufp - 1) == '\r'); + bufp--) + *(bufp - 1) = '\0'; + + while (1) { + nexthead = fgetc(fp); + + /* folded */ + if (nexthead == ' ' || nexthead == '\t') + folded = TRUE; + else if (nexthead == EOF) + break; + else if (folded == TRUE) { + if ((len - (bufp - buf)) <= 2) break; + + if (nexthead == '\n') { + folded = FALSE; + continue; + } + + /* replace return code on the tail end + with space */ + *bufp++ = ' '; + *bufp++ = nexthead; + *bufp = '\0'; + + /* concatenate next line */ + if (fgets(bufp, len - (bufp - buf), fp) + == NULL) break; + bufp += strlen(bufp); + + for (; bufp > buf && + (*(bufp - 1) == '\n' || *(bufp - 1) == '\r'); + bufp--) + *(bufp - 1) = '\0'; + + folded = FALSE; + } else { + ungetc(nexthead, fp); + break; + } + } + + return hnum; + } + + while (1) { + nexthead = fgetc(fp); + if (nexthead == ' ' || nexthead == '\t') { + size_t buflen = strlen(buf); + + /* concatenate next line */ + if ((len - buflen) > 2) { + gchar *p = buf + buflen; + + *p++ = nexthead; + *p = '\0'; + buflen++; + if (fgets(p, len - buflen, fp) == NULL) + break; + } else + break; + } else { + if (nexthead != EOF) + ungetc(nexthead, fp); + break; + } + } + + /* remove trailing return code */ + strretchomp(buf); + + return hnum; +} + +gchar *procheader_get_unfolded_line(gchar *buf, size_t len, FILE *fp) +{ + gboolean folded = FALSE; + gint nexthead; + gchar *bufp; + + if (fgets(buf, len, fp) == NULL) return NULL; + if (buf[0] == '\r' || buf[0] == '\n') return NULL; + bufp = buf + strlen(buf); + + for (; bufp > buf && + (*(bufp - 1) == '\n' || *(bufp - 1) == '\r'); + bufp--) + *(bufp - 1) = '\0'; + + while (1) { + nexthead = fgetc(fp); + + /* folded */ + if (nexthead == ' ' || nexthead == '\t') + folded = TRUE; + else if (nexthead == EOF) + break; + else if (folded == TRUE) { + if ((len - (bufp - buf)) <= 2) break; + + if (nexthead == '\n') { + folded = FALSE; + continue; + } + + /* replace return code on the tail end + with space */ + *bufp++ = ' '; + *bufp++ = nexthead; + *bufp = '\0'; + + /* concatenate next line */ + if (fgets(bufp, len - (bufp - buf), fp) + == NULL) break; + bufp += strlen(bufp); + + for (; bufp > buf && + (*(bufp - 1) == '\n' || *(bufp - 1) == '\r'); + bufp--) + *(bufp - 1) = '\0'; + + folded = FALSE; + } else { + ungetc(nexthead, fp); + break; + } + } + + /* remove trailing return code */ + strretchomp(buf); + + return buf; +} + +GSList *procheader_get_header_list_from_file(const gchar *file) +{ + FILE *fp; + GSList *hlist; + + if ((fp = g_fopen(file, "rb")) == NULL) { + FILE_OP_ERROR(file, "fopen"); + return NULL; + } + + hlist = procheader_get_header_list(fp); + + fclose(fp); + return hlist; +} + +GSList *procheader_get_header_list(FILE *fp) +{ + gchar buf[BUFFSIZE]; + gchar *p; + GSList *hlist = NULL; + Header *header; + + g_return_val_if_fail(fp != NULL, NULL); + + while (procheader_get_unfolded_line(buf, sizeof(buf), fp) != NULL) { + if (*buf == ':') continue; + for (p = buf; *p && *p != ' '; p++) { + if (*p == ':') { + header = g_new(Header, 1); + header->name = g_strndup(buf, p - buf); + p++; + while (*p == ' ' || *p == '\t') p++; + header->body = conv_unmime_header(p, NULL); + + hlist = g_slist_append(hlist, header); + break; + } + } + } + + return hlist; +} + +GSList *procheader_add_header_list(GSList *hlist, const gchar *header_name, + const gchar *body) +{ + Header *header; + + g_return_val_if_fail(header_name != NULL, hlist); + + header = g_new(Header, 1); + header->name = g_strdup(header_name); + header->body = g_strdup(body); + + return g_slist_append(hlist, header); +} + +GSList *procheader_merge_header_list(GSList *hlist1, GSList *hlist2) +{ + GSList *cur; + + for (cur = hlist2; cur != NULL; cur = cur->next) { + Header *header = (Header *)cur->data; + if (procheader_find_header_list(hlist1, header->name) < 0) + hlist1 = g_slist_append(hlist1, header); + } + + return hlist1; +} + +gint procheader_find_header_list(GSList *hlist, const gchar *header_name) +{ + GSList *cur; + gint index = 0; + Header *header; + + g_return_val_if_fail(header_name != NULL, -1); + + for (cur = hlist; cur != NULL; cur = cur->next, index++) { + header = (Header *)cur->data; + if (g_ascii_strcasecmp(header->name, header_name) == 0) + return index; + } + + return -1; +} + +GPtrArray *procheader_get_header_array(FILE *fp, const gchar *encoding) +{ + gchar buf[BUFFSIZE]; + gchar *p; + GPtrArray *headers; + Header *header; + + g_return_val_if_fail(fp != NULL, NULL); + + headers = g_ptr_array_new(); + + while (procheader_get_unfolded_line(buf, sizeof(buf), fp) != NULL) { + if (*buf == ':') continue; + for (p = buf; *p && *p != ' '; p++) { + if (*p == ':') { + header = g_new(Header, 1); + header->name = g_strndup(buf, p - buf); + p++; + while (*p == ' ' || *p == '\t') p++; + header->body = conv_unmime_header(p, encoding); + + g_ptr_array_add(headers, header); + break; + } + } + } + + return headers; +} + +GPtrArray *procheader_get_header_array_asis(FILE *fp, const gchar *encoding) +{ + gchar buf[BUFFSIZE]; + gchar *p; + GPtrArray *headers; + Header *header; + + g_return_val_if_fail(fp != NULL, NULL); + + headers = g_ptr_array_new(); + + while (procheader_get_one_field(buf, sizeof(buf), fp, NULL) != -1) { + if (*buf == ':') continue; + for (p = buf; *p && *p != ' '; p++) { + if (*p == ':') { + header = g_new(Header, 1); + header->name = g_strndup(buf, p - buf); + p++; + header->body = conv_unmime_header(p, encoding); + + g_ptr_array_add(headers, header); + break; + } + } + } + + return headers; +} + +void procheader_header_list_destroy(GSList *hlist) +{ + Header *header; + + while (hlist != NULL) { + header = hlist->data; + procheader_header_free(header); + hlist = g_slist_remove(hlist, header); + } +} + +void procheader_header_array_destroy(GPtrArray *harray) +{ + gint i; + Header *header; + + for (i = 0; i < harray->len; i++) { + header = g_ptr_array_index(harray, i); + procheader_header_free(header); + } + + g_ptr_array_free(harray, TRUE); +} + +void procheader_header_free(Header *header) +{ + if (!header) return; + + g_free(header->name); + g_free(header->body); + g_free(header); +} + +void procheader_get_header_fields(FILE *fp, HeaderEntry hentry[]) +{ + gchar buf[BUFFSIZE]; + HeaderEntry *hp; + gint hnum; + gchar *p; + + if (hentry == NULL) return; + + while ((hnum = procheader_get_one_field(buf, sizeof(buf), fp, hentry)) + != -1) { + hp = hentry + hnum; + + p = buf + strlen(hp->name); + while (*p == ' ' || *p == '\t') p++; + + if (hp->body == NULL) + hp->body = g_strdup(p); + else if (!g_ascii_strcasecmp(hp->name, "To:") || + !g_ascii_strcasecmp(hp->name, "Cc:")) { + gchar *tp = hp->body; + hp->body = g_strconcat(tp, ", ", p, NULL); + g_free(tp); + } + } +} + +MsgInfo *procheader_parse_file(const gchar *file, MsgFlags flags, + gboolean full) +{ + struct stat s; + FILE *fp; + MsgInfo *msginfo; + + if (g_stat(file, &s) < 0) { + FILE_OP_ERROR(file, "stat"); + return NULL; + } + if (!S_ISREG(s.st_mode)) + return NULL; + + if ((fp = g_fopen(file, "rb")) == NULL) { + FILE_OP_ERROR(file, "fopen"); + return NULL; + } + + msginfo = procheader_parse_stream(fp, flags, full); + fclose(fp); + + if (msginfo) { + msginfo->size = s.st_size; + msginfo->mtime = s.st_mtime; + } + + return msginfo; +} + +MsgInfo *procheader_parse_str(const gchar *str, MsgFlags flags, gboolean full) +{ + FILE *fp; + MsgInfo *msginfo; + + if ((fp = str_open_as_stream(str)) == NULL) + return NULL; + + msginfo = procheader_parse_stream(fp, flags, full); + fclose(fp); + return msginfo; +} + +enum +{ + H_DATE = 0, + H_FROM = 1, + H_TO = 2, + H_NEWSGROUPS = 3, + H_SUBJECT = 4, + H_MSG_ID = 5, + H_REFERENCES = 6, + H_IN_REPLY_TO = 7, + H_CONTENT_TYPE = 8, + H_SEEN = 9, + H_CC = 10, + H_X_FACE = 11 +}; + +MsgInfo *procheader_parse_stream(FILE *fp, MsgFlags flags, gboolean full) +{ + static HeaderEntry hentry_full[] = {{"Date:", NULL, FALSE}, + {"From:", NULL, TRUE}, + {"To:", NULL, TRUE}, + {"Newsgroups:", NULL, TRUE}, + {"Subject:", NULL, TRUE}, + {"Message-Id:", NULL, FALSE}, + {"References:", NULL, FALSE}, + {"In-Reply-To:", NULL, FALSE}, + {"Content-Type:", NULL, FALSE}, + {"Seen:", NULL, FALSE}, + {"Cc:", NULL, TRUE}, + {"X-Face:", NULL, FALSE}, + {NULL, NULL, FALSE}}; + + static HeaderEntry hentry_short[] = {{"Date:", NULL, FALSE}, + {"From:", NULL, TRUE}, + {"To:", NULL, TRUE}, + {"Newsgroups:", NULL, TRUE}, + {"Subject:", NULL, TRUE}, + {"Message-Id:", NULL, FALSE}, + {"References:", NULL, FALSE}, + {"In-Reply-To:", NULL, FALSE}, + {"Content-Type:", NULL, FALSE}, + {"Seen:", NULL, FALSE}, + {NULL, NULL, FALSE}}; + + MsgInfo *msginfo; + gchar buf[BUFFSIZE]; + gchar *p; + gchar *hp; + HeaderEntry *hentry; + gint hnum; + gchar *from = NULL, *to = NULL, *subject = NULL, *cc = NULL; + gchar *charset = NULL; + + hentry = full ? hentry_full : hentry_short; + + if (MSG_IS_QUEUED(flags)) { + while (fgets(buf, sizeof(buf), fp) != NULL) + if (buf[0] == '\r' || buf[0] == '\n') break; + } + + msginfo = g_new0(MsgInfo, 1); + msginfo->flags = flags; + msginfo->references = NULL; + msginfo->inreplyto = NULL; + + while ((hnum = procheader_get_one_field(buf, sizeof(buf), fp, hentry)) + != -1) { + hp = buf + strlen(hentry[hnum].name); + while (*hp == ' ' || *hp == '\t') hp++; + + switch (hnum) { + case H_DATE: + if (msginfo->date) break; + msginfo->date_t = + procheader_date_parse(NULL, hp, 0); + msginfo->date = g_strdup(hp); + break; + case H_FROM: + if (from) break; + from = g_strdup(hp); + break; + case H_TO: + if (to) { + p = to; + to = g_strconcat(p, ", ", hp, NULL); + g_free(p); + } else + to = g_strdup(hp); + break; + case H_NEWSGROUPS: + if (msginfo->newsgroups) { + p = msginfo->newsgroups; + msginfo->newsgroups = + g_strconcat(p, ",", hp, NULL); + g_free(p); + } else + msginfo->newsgroups = g_strdup(buf + 12); + break; + case H_SUBJECT: + if (msginfo->subject) break; + subject = g_strdup(hp); + break; + case H_MSG_ID: + if (msginfo->msgid) break; + + extract_parenthesis(hp, '<', '>'); + remove_space(hp); + msginfo->msgid = g_strdup(hp); + break; + case H_REFERENCES: + msginfo->references = + references_list_prepend(msginfo->references, + hp); + break; + case H_IN_REPLY_TO: + if (msginfo->inreplyto) break; + + eliminate_parenthesis(hp, '(', ')'); + if ((p = strrchr(hp, '<')) != NULL && + strchr(p + 1, '>') != NULL) { + extract_parenthesis(p, '<', '>'); + remove_space(p); + if (*p != '\0') + msginfo->inreplyto = g_strdup(p); + } + break; + case H_CONTENT_TYPE: + if (!g_ascii_strncasecmp(hp, "multipart", 9)) { + MSG_SET_TMP_FLAGS(msginfo->flags, MSG_MIME); + } else if (!charset) { + procmime_scan_content_type_str + (hp, NULL, &charset, NULL, NULL); + } + break; + case H_SEEN: + /* mnews Seen header */ + MSG_UNSET_PERM_FLAGS(msginfo->flags, MSG_NEW|MSG_UNREAD); + break; + case H_CC: + if (cc) { + p = cc; + cc = g_strconcat(p, ", ", hp, NULL); + g_free(p); + } else + cc = g_strdup(hp); + break; + case H_X_FACE: + if (msginfo->xface) break; + msginfo->xface = g_strdup(hp); + break; + default: + break; + } + } + + if (from) { + msginfo->from = conv_unmime_header(from, charset); + msginfo->fromname = procheader_get_fromname(msginfo->from); + g_free(from); + } + if (to) { + msginfo->to = conv_unmime_header(to, charset); + g_free(to); + } + if (subject) { + msginfo->subject = conv_unmime_header(subject, charset); + g_free(subject); + } + if (cc) { + msginfo->cc = conv_unmime_header(cc, charset); + g_free(cc); + } + + if (!msginfo->inreplyto && msginfo->references) + msginfo->inreplyto = + g_strdup((gchar *)msginfo->references->data); + + g_free(charset); + + return msginfo; +} + +gchar *procheader_get_fromname(const gchar *str) +{ + gchar *tmp, *name; + + Xstrdup_a(tmp, str, return NULL); + + if (*tmp == '\"') { + extract_quote(tmp, '\"'); + g_strstrip(tmp); + } else if (strchr(tmp, '<')) { + eliminate_parenthesis(tmp, '<', '>'); + g_strstrip(tmp); + if (*tmp == '\0') { + strcpy(tmp, str); + extract_parenthesis(tmp, '<', '>'); + g_strstrip(tmp); + } + } else if (strchr(tmp, '(')) { + extract_parenthesis(tmp, '(', ')'); + g_strstrip(tmp); + } + + if (*tmp == '\0') + name = g_strdup(str); + else + name = g_strdup(tmp); + + return name; +} + +static gint procheader_scan_date_string(const gchar *str, + gchar *weekday, gint *day, + gchar *month, gint *year, + gint *hh, gint *mm, gint *ss, + gchar *zone) +{ + gint result; + + result = sscanf(str, "%10s %d %9s %d %2d:%2d:%2d %5s", + weekday, day, month, year, hh, mm, ss, zone); + if (result == 8) return 0; + + result = sscanf(str, "%3s,%d %9s %d %2d:%2d:%2d %5s", + weekday, day, month, year, hh, mm, ss, zone); + if (result == 8) return 0; + + result = sscanf(str, "%d %9s %d %2d:%2d:%2d %5s", + day, month, year, hh, mm, ss, zone); + if (result == 7) return 0; + + *zone = '\0'; + result = sscanf(str, "%10s %d %9s %d %2d:%2d:%2d", + weekday, day, month, year, hh, mm, ss); + if (result == 7) return 0; + + result = sscanf(str, "%d %9s %d %2d:%2d:%2d", + day, month, year, hh, mm, ss); + if (result == 6) return 0; + + *ss = 0; + result = sscanf(str, "%10s %d %9s %d %2d:%2d %5s", + weekday, day, month, year, hh, mm, zone); + if (result == 7) return 0; + + result = sscanf(str, "%d %9s %d %2d:%2d %5s", + day, month, year, hh, mm, zone); + if (result == 6) return 0; + + *zone = '\0'; + result = sscanf(str, "%10s %d %9s %d %2d:%2d", + weekday, day, month, year, hh, mm); + if (result == 6) return 0; + + result = sscanf(str, "%d %9s %d %2d:%2d", + day, month, year, hh, mm); + if (result == 5) return 0; + + return -1; +} + +time_t procheader_date_parse(gchar *dest, const gchar *src, gint len) +{ + static gchar monthstr[] = "JanFebMarAprMayJunJulAugSepOctNovDec"; + gchar weekday[11]; + gint day; + gchar month[10]; + gint year; + gint hh, mm, ss; + gchar zone[6]; + GDateMonth dmonth = G_DATE_BAD_MONTH; + struct tm t; + gchar *p; + time_t timer; + time_t tz_offset; + + if (procheader_scan_date_string(src, weekday, &day, month, &year, + &hh, &mm, &ss, zone) < 0) { + if (dest && len > 0) + strncpy2(dest, src, len); + return 0; + } + + /* Y2K compliant :) */ + if (year < 1000) { + if (year < 50) + year += 2000; + else + year += 1900; + } + + month[3] = '\0'; + for (p = monthstr; *p != '\0'; p += 3) { + if (!g_ascii_strncasecmp(p, month, 3)) { + dmonth = (gint)(p - monthstr) / 3 + 1; + break; + } + } + + t.tm_sec = ss; + t.tm_min = mm; + t.tm_hour = hh; + t.tm_mday = day; + t.tm_mon = dmonth - 1; + t.tm_year = year - 1900; + t.tm_wday = 0; + t.tm_yday = 0; + t.tm_isdst = -1; + + timer = mktime(&t); + tz_offset = remote_tzoffset_sec(zone); + if (tz_offset != -1) + timer += tzoffset_sec(&timer) - tz_offset; + + if (dest) + procheader_date_get_localtime(dest, len, timer); + + return timer; +} + +void procheader_date_get_localtime(gchar *dest, gint len, const time_t timer) +{ + struct tm *lt; + gchar *default_format = "%y/%m/%d(%a) %H:%M"; + gchar *tmp, *buf; + + Xalloca(tmp, len + 1, dest[0] = '\0'; return;); + + lt = localtime(&timer); + + if (prefs_common.date_format) + strftime(tmp, len, prefs_common.date_format, lt); + else + strftime(tmp, len, default_format, lt); + + buf = conv_localetodisp(tmp, NULL); + strncpy2(dest, buf, len); + g_free(buf); +} |