summaryrefslogtreecommitdiffstats
path: root/src/lib-mail/mbox-from.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/lib-mail/mbox-from.c')
-rw-r--r--src/lib-mail/mbox-from.c301
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(&timestamp);
+
+ /* 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);
+}