/* Copyright (c) 2002-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "ioloop.h" #include "str.h" #include "utc-mktime.h" #include "utc-offset.h" #include "mbox-from.h" #include #include static const char *weekdays[] = { "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" }; static const char *months[] = { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" }; static int mbox_parse_month(const unsigned char *msg, struct tm *tm) { int i; for (i = 0; i < 12; i++) { if (i_memcasecmp(months[i], msg, 3) == 0) { tm->tm_mon = i; break; } } if (i == 12 && memcmp(msg, "???", 3) == 0) { /* just a hack to parse one special mbox I have :) */ i = 0; } if (i == 12 || msg[3] != ' ') return -1; return 0; } static int mbox_parse_year(const unsigned char *msg, struct tm *tm) { if (!i_isdigit(msg[0]) || !i_isdigit(msg[1]) || !i_isdigit(msg[2]) || !i_isdigit(msg[3])) return -1; tm->tm_year = (msg[0]-'0') * 1000 + (msg[1]-'0') * 100 + (msg[2]-'0') * 10 + (msg[3]-'0') - 1900; return 0; } int mbox_from_parse(const unsigned char *msg, size_t size, time_t *time_r, int *tz_offset_r, char **sender_r) { const unsigned char *msg_start, *sender_end, *msg_end; struct tm tm; bool esc, alt_stamp, seen_timezone = FALSE; int timezone_secs = 0; time_t t; *time_r = (time_t)-1; *sender_r = NULL; /* */ msg_start = msg; msg_end = msg + size; /* get sender */ if (msg < msg_end && *msg == '"') { /* "x y z"@domain - skip the quoted part */ esc = FALSE; msg++; while (msg < msg_end && (*msg != '"' || esc)) { if (*msg == '\r' || *msg == '\n') return -1; esc = *msg == '\\'; msg++; } msg++; } while (msg < msg_end && *msg != ' ') { if (*msg == '\r' || *msg == '\n') return -1; msg++; } sender_end = msg; while (msg < msg_end && *msg == ' ') msg++; /* next 29 chars should be in the date in asctime() format, eg. "Thu Nov 9 22:33:52 2001 +0300" - Some put the timezone before the year - Some use a named timezone before or after year, which we ignore - Some don't include seconds (-3) - Some don't include timezone (-5) */ if (msg+29-3-5 > msg_end) return -1; i_zero(&tm); /* skip weekday */ msg += 4; /* month */ if (mbox_parse_month(msg, &tm) < 0) { /* Try alternate timestamp: "Thu, 9 Nov 2002 22:33:52" */ alt_stamp = TRUE; msg++; if (!i_isdigit(msg[0])) return -1; tm.tm_mday = msg[0]-'0'; msg++; if (i_isdigit(msg[0])) { tm.tm_mday = tm.tm_mday*10 + msg[0]-'0'; msg++; } if (msg[0] != ' ') return -1; msg++; if (mbox_parse_month(msg, &tm) < 0) return -1; msg += 4; if (mbox_parse_year(msg, &tm) < 0) return -1; msg += 5; } else { alt_stamp = FALSE; msg += 4; /* day. single digit is usually preceded by extra space */ if (msg[0] == ' ') msg++; if (msg[1] == ' ') { if (!i_isdigit(msg[0])) return -1; tm.tm_mday = msg[0]-'0'; msg += 2; } else { if (!i_isdigit(msg[0]) || !i_isdigit(msg[1]) || msg[2] != ' ') return -1; tm.tm_mday = (msg[0]-'0') * 10 + (msg[1]-'0'); msg += 3; } } if (tm.tm_mday == 0) tm.tm_mday = 1; /* hour */ if (!i_isdigit(msg[0]) || !i_isdigit(msg[1]) || msg[2] != ':') return -1; tm.tm_hour = (msg[0]-'0') * 10 + (msg[1]-'0'); msg += 3; /* minute */ if (!i_isdigit(msg[0]) || !i_isdigit(msg[1])) return -1; tm.tm_min = (msg[0]-'0') * 10 + (msg[1]-'0'); msg += 2; /* optional second */ if (msg[0] == ':') { msg++; if (!i_isdigit(msg[0]) || !i_isdigit(msg[1])) return -1; tm.tm_sec = (msg[0]-'0') * 10 + (msg[1]-'0'); msg += 2; if (!alt_stamp) { if (msg[0] == ' ') msg++; else return -1; } } else if (!alt_stamp) { if (msg[0] != ' ') return -1; msg++; } /* optional named timezone */ if (alt_stamp) ; else if (!i_isdigit(msg[0]) || !i_isdigit(msg[1]) || !i_isdigit(msg[2]) || !i_isdigit(msg[3])) { /* skip to next space */ while (msg < msg_end && *msg != ' ') { if (*msg == '\r' || *msg == '\n') return -1; msg++; } if (msg+5 > msg_end) return -1; msg++; } else if ((msg[0] == '-' || msg[0] == '+') && i_isdigit(msg[1]) && i_isdigit(msg[2]) && i_isdigit(msg[3]) && i_isdigit(msg[4]) && msg[5] == ' ') { /* numeric timezone, use it */ seen_timezone = TRUE; timezone_secs = (msg[1]-'0') * 10*60*60 + (msg[2]-'0') * 60*60 + (msg[3]-'0') * 10 + (msg[4]-'0'); if (msg[0] == '-') timezone_secs = -timezone_secs; msg += 6; } if (!alt_stamp) { /* year */ if (mbox_parse_year(msg, &tm) < 0) return -1; msg += 4; } tm.tm_isdst = -1; if (!seen_timezone && msg != msg_end && msg[0] == ' ' && (msg[1] == '-' || msg[1] == '+') && i_isdigit(msg[2]) && i_isdigit(msg[3]) && i_isdigit(msg[4]) && i_isdigit(msg[5])) { seen_timezone = TRUE; timezone_secs = (msg[2]-'0') * 10*60*60 + (msg[3]-'0') * 60*60 + (msg[4]-'0') * 10 + (msg[5]-'0'); if (msg[1] == '-') timezone_secs = -timezone_secs; } if (seen_timezone) { t = utc_mktime(&tm); if (t == (time_t)-1) return -1; t -= timezone_secs; *time_r = t; *tz_offset_r = timezone_secs/60; } else { /* assume local timezone */ *time_r = mktime(&tm); *tz_offset_r = utc_offset(localtime(time_r), *time_r); } *sender_r = i_strdup_until(msg_start, sender_end); return 0; } const char *mbox_from_create(const char *sender, time_t timestamp) { string_t *str; struct tm *tm; int year; str = t_str_new(256); str_append(str, "From "); str_append(str, sender); str_append(str, " "); /* we could use simply asctime(), but i18n etc. may break it. Example: "Thu Nov 29 22:33:52 2001" */ tm = localtime(×tamp); /* week day */ str_append(str, weekdays[tm->tm_wday]); str_append_c(str, ' '); /* month */ str_append(str, months[tm->tm_mon]); str_append_c(str, ' '); /* day */ str_append_c(str, (tm->tm_mday / 10) + '0'); str_append_c(str, (tm->tm_mday % 10) + '0'); str_append_c(str, ' '); /* hour */ str_append_c(str, (tm->tm_hour / 10) + '0'); str_append_c(str, (tm->tm_hour % 10) + '0'); str_append_c(str, ':'); /* minute */ str_append_c(str, (tm->tm_min / 10) + '0'); str_append_c(str, (tm->tm_min % 10) + '0'); str_append_c(str, ':'); /* second */ str_append_c(str, (tm->tm_sec / 10) + '0'); str_append_c(str, (tm->tm_sec % 10) + '0'); str_append_c(str, ' '); /* year */ year = tm->tm_year + 1900; str_append_c(str, (year / 1000) + '0'); str_append_c(str, ((year / 100) % 10) + '0'); str_append_c(str, ((year / 10) % 10) + '0'); str_append_c(str, (year % 10) + '0'); str_append_c(str, '\n'); return str_c(str); }