/* * 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 #include #include #include #include #include #include #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 (!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 = 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_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 (!strcasecmp(hp->name, "To:") || !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 (stat(file, &s) < 0) { FILE_OP_ERROR(file, "stat"); return NULL; } if (!S_ISREG(s.st_mode)) return NULL; if ((fp = 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 *reference = NULL; 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->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: case H_IN_REPLY_TO: if (!reference) { eliminate_parenthesis(hp, '(', ')'); if ((p = strrchr(hp, '<')) != NULL && strchr(p + 1, '>') != NULL) { extract_parenthesis(p, '<', '>'); remove_space(p); if (*p != '\0') reference = g_strdup(p); } } break; case H_CONTENT_TYPE: if (!g_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); } msginfo->inreplyto = reference; 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) { g_warning("Invalid date: %s\n", src); 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 (!strncasecmp(p, month, 3)) { dmonth = (gint)(p - monthstr) / 3 + 1; break; } } if (*p == '\0') g_warning("Invalid month: %s\n", month); 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); }