diff options
Diffstat (limited to '')
-rw-r--r-- | src/lib-mail/mbox-from.c | 301 |
1 files changed, 301 insertions, 0 deletions
diff --git a/src/lib-mail/mbox-from.c b/src/lib-mail/mbox-from.c new file mode 100644 index 0000000..452e32e --- /dev/null +++ b/src/lib-mail/mbox-from.c @@ -0,0 +1,301 @@ +/* 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 <time.h> +#include <ctype.h> + +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; + + /* <sender> <date> <moreinfo> */ + 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); +} |