aboutsummaryrefslogtreecommitdiff
path: root/libsylph/procheader.c
diff options
context:
space:
mode:
Diffstat (limited to 'libsylph/procheader.c')
-rw-r--r--libsylph/procheader.c799
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);
+}