diff options
Diffstat (limited to 'src/lib-mail/message-date.c')
-rw-r--r-- | src/lib-mail/message-date.c | 283 |
1 files changed, 283 insertions, 0 deletions
diff --git a/src/lib-mail/message-date.c b/src/lib-mail/message-date.c new file mode 100644 index 0000000..7a7529a --- /dev/null +++ b/src/lib-mail/message-date.c @@ -0,0 +1,283 @@ +/* Copyright (c) 2002-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "str.h" +#include "utc-offset.h" +#include "utc-mktime.h" +#include "rfc822-parser.h" +#include "message-date.h" + +#include <ctype.h> + +/* RFC specifies ':' as the only allowed separator, + but be forgiving also for some broken ones */ +#define IS_TIME_SEP(c) \ + ((c) == ':' || (c) == '.') + +struct message_date_parser_context { + struct rfc822_parser_context parser; + string_t *str; +}; + +static const char *month_names[] = { + "Jan", "Feb", "Mar", "Apr", "May", "Jun", + "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" +}; + +static const char *weekday_names[] = { + "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" +}; + +static int parse_timezone(const unsigned char *str, size_t len) +{ + int offset; + char chr; + + if (len == 5 && (*str == '+' || *str == '-')) { + /* numeric offset */ + if (!i_isdigit(str[1]) || !i_isdigit(str[2]) || + !i_isdigit(str[3]) || !i_isdigit(str[4])) + return 0; + + offset = ((str[1]-'0') * 10 + (str[2]-'0')) * 60 + + (str[3]-'0') * 10 + (str[4]-'0'); + return *str == '+' ? offset : -offset; + } + + if (len == 1) { + /* military zone - handle them the correct way, not as + RFC822 says. RFC2822 though suggests that they'd be + considered as unspecified.. */ + chr = i_toupper(*str); + if (chr < 'J') + return (*str-'A'+1) * 60; + if (chr == 'J') + return 0; + if (chr <= 'M') + return (*str-'A') * 60; + if (chr < 'Z') + return ('M'-*str) * 60; + return 0; + } + + if (len == 2 && i_toupper(str[0]) == 'U' && i_toupper(str[1]) == 'T') { + /* UT - Universal Time */ + return 0; + } + + if (len == 3) { + /* GMT | [ECMP][DS]T */ + if (str[2] != 'T') + return 0; + + switch (i_toupper(*str)) { + case 'E': + offset = -5 * 60; + break; + case 'C': + offset = -6 * 60; + break; + case 'M': + offset = -7 * 60; + break; + case 'P': + offset = -8 * 60; + break; + default: + /* GMT and others */ + return 0; + } + + if (i_toupper(str[1]) == 'D') + return offset + 60; + if (i_toupper(str[1]) == 'S') + return offset; + } + + return 0; +} + +static int next_token(struct message_date_parser_context *ctx, + const unsigned char **value, size_t *value_len) +{ + int ret; + + str_truncate(ctx->str, 0); + ret = ctx->parser.data >= ctx->parser.end ? 0 : + rfc822_parse_atom(&ctx->parser, ctx->str); + + *value = str_data(ctx->str); + *value_len = str_len(ctx->str); + return ret < 0 ? -1 : *value_len > 0; +} + +static bool +message_date_parser_tokens(struct message_date_parser_context *ctx, + time_t *timestamp_r, int *timezone_offset_r) +{ + struct tm tm; + const unsigned char *value; + size_t i, len; + int ret; + + /* [weekday_name "," ] dd month_name [yy]yy hh:mi[:ss] timezone */ + i_zero(&tm); + + rfc822_skip_lwsp(&ctx->parser); + + /* skip the optional weekday */ + if (next_token(ctx, &value, &len) <= 0) + return FALSE; + if (len == 3) { + if (*ctx->parser.data != ',') + return FALSE; + ctx->parser.data++; + rfc822_skip_lwsp(&ctx->parser); + + if (next_token(ctx, &value, &len) <= 0) + return FALSE; + } + + /* dd */ + if (len < 1 || len > 2 || !i_isdigit(value[0])) + return FALSE; + + tm.tm_mday = value[0]-'0'; + if (len == 2) { + if (!i_isdigit(value[1])) + return FALSE; + tm.tm_mday = (tm.tm_mday * 10) + (value[1]-'0'); + } + + /* month name */ + if (next_token(ctx, &value, &len) <= 0 || len < 3) + return FALSE; + + for (i = 0; i < 12; i++) { + if (i_memcasecmp(month_names[i], value, 3) == 0) { + tm.tm_mon = i; + break; + } + } + if (i == 12) + return FALSE; + + /* [yy]yy */ + if (next_token(ctx, &value, &len) <= 0 || (len != 2 && len != 4)) + return FALSE; + + for (i = 0; i < len; i++) { + if (!i_isdigit(value[i])) + return FALSE; + tm.tm_year = tm.tm_year * 10 + (value[i]-'0'); + } + + if (len == 2) { + /* two digit year, assume 1970+ */ + if (tm.tm_year < 70) + tm.tm_year += 100; + } else { + if (tm.tm_year < 1900) + return FALSE; + tm.tm_year -= 1900; + } + + /* hh, allow also single digit */ + if (next_token(ctx, &value, &len) <= 0 || + len < 1 || len > 2 || !i_isdigit(value[0])) + return FALSE; + tm.tm_hour = value[0]-'0'; + if (len == 2) { + if (!i_isdigit(value[1])) + return FALSE; + tm.tm_hour = tm.tm_hour * 10 + (value[1]-'0'); + } + + /* :mm (may be the last token) */ + if (!IS_TIME_SEP(*ctx->parser.data)) + return FALSE; + ctx->parser.data++; + rfc822_skip_lwsp(&ctx->parser); + + if (next_token(ctx, &value, &len) < 0 || len != 2 || + !i_isdigit(value[0]) || !i_isdigit(value[1])) + return FALSE; + tm.tm_min = (value[0]-'0') * 10 + (value[1]-'0'); + + /* [:ss] */ + if (ctx->parser.data < ctx->parser.end && + IS_TIME_SEP(*ctx->parser.data)) { + ctx->parser.data++; + rfc822_skip_lwsp(&ctx->parser); + + if (next_token(ctx, &value, &len) <= 0 || len != 2 || + !i_isdigit(value[0]) || !i_isdigit(value[1])) + return FALSE; + tm.tm_sec = (value[0]-'0') * 10 + (value[1]-'0'); + } + + if ((ret = next_token(ctx, &value, &len)) < 0) + return FALSE; + if (ret == 0) { + /* missing timezone */ + *timezone_offset_r = 0; + } else { + /* timezone. invalid timezones are treated as GMT, because + we may not know all the possible timezones that are used + and it's better to give at least a mostly correct reply. + FIXME: perhaps some different strict version of this + function would be useful? */ + *timezone_offset_r = parse_timezone(value, len); + } + + tm.tm_isdst = -1; + *timestamp_r = utc_mktime(&tm); + if (*timestamp_r == (time_t)-1) + return FALSE; + + *timestamp_r -= *timezone_offset_r * 60; + + return TRUE; +} + +bool message_date_parse(const unsigned char *data, size_t size, + time_t *timestamp_r, int *timezone_offset_r) +{ + bool success; + + T_BEGIN { + struct message_date_parser_context ctx; + + rfc822_parser_init(&ctx.parser, data, size, NULL); + ctx.str = t_str_new(128); + success = message_date_parser_tokens(&ctx, timestamp_r, + timezone_offset_r); + rfc822_parser_deinit(&ctx.parser); + } T_END; + + return success; +} + +const char *message_date_create(time_t timestamp) +{ + struct tm *tm; + int offset; + bool negative; + + tm = localtime(×tamp); + offset = utc_offset(tm, timestamp); + if (offset >= 0) + negative = FALSE; + else { + negative = TRUE; + offset = -offset; + } + + return t_strdup_printf("%s, %02d %s %04d %02d:%02d:%02d %c%02d%02d", + weekday_names[tm->tm_wday], + tm->tm_mday, + month_names[tm->tm_mon], + tm->tm_year+1900, + tm->tm_hour, tm->tm_min, tm->tm_sec, + negative ? '-' : '+', offset / 60, offset % 60); +} |