/* Copyright (c) 2013-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "utc-offset.h" #include "utc-mktime.h" #include "iso8601-date.h" #include /* RFC3339/ISO8601 date-time syntax date-fullyear = 4DIGIT date-month = 2DIGIT ; 01-12 date-mday = 2DIGIT ; 01-28, 01-29, 01-30, 01-31 based on ; month/year time-hour = 2DIGIT ; 00-23 time-minute = 2DIGIT ; 00-59 time-second = 2DIGIT ; 00-58, 00-59, 00-60 based on leap second ; rules time-secfrac = "." 1*DIGIT time-numoffset = ("+" / "-") time-hour ":" time-minute time-offset = "Z" / time-numoffset partial-time = time-hour ":" time-minute ":" time-second [time-secfrac] full-date = date-fullyear "-" date-month "-" date-mday full-time = partial-time time-offset date-time = full-date "T" full-time */ struct iso8601_date_parser { const unsigned char *cur, *end; struct tm tm; int timezone_offset; }; static inline int iso8601_date_parse_number(struct iso8601_date_parser *parser, int digits, int *number_r) { int i; if (parser->cur >= parser->end || !i_isdigit(parser->cur[0])) return 0; *number_r = parser->cur[0] - '0'; parser->cur++; for (i=0; i < digits-1; i++) { if (parser->cur >= parser->end || !i_isdigit(parser->cur[0])) return -1; *number_r = ((*number_r) * 10) + parser->cur[0] - '0'; parser->cur++; } return 1; } static int iso8601_date_parse_secfrac(struct iso8601_date_parser *parser) { /* time-secfrac = "." 1*DIGIT NOTE: Currently not applied anywhere, so fraction is just skipped. */ /* "." */ if (parser->cur >= parser->end || parser->cur[0] != '.') return 0; parser->cur++; /* 1DIGIT */ if (parser->cur >= parser->end || !i_isdigit(parser->cur[0])) return -1; parser->cur++; /* *DIGIT */ while (parser->cur < parser->end && i_isdigit(parser->cur[0])) parser->cur++; return 1; } static int is08601_date_parse_time_offset(struct iso8601_date_parser *parser) { int tz_sign = 1, tz_hour = 0, tz_min = 0; /* time-offset = "Z" / time-numoffset time-numoffset = ("+" / "-") time-hour ":" time-minute time-hour = 2DIGIT ; 00-23 time-minute = 2DIGIT ; 00-59 */ if (parser->cur >= parser->end) return 0; /* time-offset = "Z" / time-numoffset */ switch (parser->cur[0]) { case '-': tz_sign = -1; /* fall through */ case '+': parser->cur++; /* time-hour = 2DIGIT */ if (iso8601_date_parse_number(parser, 2, &tz_hour) <= 0) return -1; if (tz_hour > 23) return -1; /* ":" */ if (parser->cur >= parser->end || parser->cur[0] != ':') return -1; parser->cur++; /* time-minute = 2DIGIT */ if (iso8601_date_parse_number(parser, 2, &tz_min) <= 0) return -1; if (tz_min > 59) return -1; break; case 'Z': case 'z': parser->cur++; break; default: return -1; } parser->timezone_offset = tz_sign*(tz_hour*60 + tz_min); return 1; } static int is08601_date_parse_full_time(struct iso8601_date_parser *parser) { /* full-time = partial-time time-offset partial-time = time-hour ":" time-minute ":" time-second [time-secfrac] time-hour = 2DIGIT ; 00-23 time-minute = 2DIGIT ; 00-59 time-second = 2DIGIT ; 00-58, 00-59, 00-60 based on leap second ; rules */ /* time-hour = 2DIGIT */ if (iso8601_date_parse_number(parser, 2, &parser->tm.tm_hour) <= 0) return -1; /* ":" */ if (parser->cur >= parser->end || parser->cur[0] != ':') return -1; parser->cur++; /* time-minute = 2DIGIT */ if (iso8601_date_parse_number(parser, 2, &parser->tm.tm_min) <= 0) return -1; /* ":" */ if (parser->cur >= parser->end || parser->cur[0] != ':') return -1; parser->cur++; /* time-second = 2DIGIT */ if (iso8601_date_parse_number(parser, 2, &parser->tm.tm_sec) <= 0) return -1; /* [time-secfrac] */ if (iso8601_date_parse_secfrac(parser) < 0) return -1; /* time-offset */ if (is08601_date_parse_time_offset(parser) <= 0) return -1; return 1; } static int is08601_date_parse_full_date(struct iso8601_date_parser *parser) { /* full-date = date-fullyear "-" date-month "-" date-mday date-fullyear = 4DIGIT date-month = 2DIGIT ; 01-12 date-mday = 2DIGIT ; 01-28, 01-29, 01-30, 01-31 based on ; month/year */ /* date-fullyear = 4DIGIT */ if (iso8601_date_parse_number(parser, 4, &parser->tm.tm_year) <= 0) return -1; if (parser->tm.tm_year < 1900) return -1; parser->tm.tm_year -= 1900; /* "-" */ if (parser->cur >= parser->end || parser->cur[0] != '-') return -1; parser->cur++; /* date-month = 2DIGIT */ if (iso8601_date_parse_number(parser, 2, &parser->tm.tm_mon) <= 0) return -1; parser->tm.tm_mon -= 1; /* "-" */ if (parser->cur >= parser->end || parser->cur[0] != '-') return -1; parser->cur++; /* time-second = 2DIGIT */ if (iso8601_date_parse_number(parser, 2, &parser->tm.tm_mday) <= 0) return -1; return 1; } static int iso8601_date_parse_date_time(struct iso8601_date_parser *parser) { /* date-time = full-date "T" full-time */ /* full-date */ if (is08601_date_parse_full_date(parser) <= 0) return -1; /* "T" */ if (parser->cur >= parser->end || (parser->cur[0] != 'T' && parser->cur[0] != 't')) return -1; parser->cur++; /* full-time */ if (is08601_date_parse_full_time(parser) <= 0) return -1; if (parser->cur != parser->end) return -1; return 1; } static bool iso8601_date_do_parse(const unsigned char *data, size_t size, struct tm *tm_r, time_t *timestamp_r, int *timezone_offset_r) { struct iso8601_date_parser parser; time_t timestamp; i_zero(&parser); parser.cur = data; parser.end = data + size; if (iso8601_date_parse_date_time(&parser) <= 0) return FALSE; parser.tm.tm_isdst = -1; timestamp = utc_mktime(&parser.tm); if (timestamp == (time_t)-1) return FALSE; *timezone_offset_r = parser.timezone_offset; *tm_r = parser.tm; *timestamp_r = timestamp - parser.timezone_offset * 60; return TRUE; } bool iso8601_date_parse(const unsigned char *data, size_t size, time_t *timestamp_r, int *timezone_offset_r) { struct tm tm; return iso8601_date_do_parse(data, size, &tm, timestamp_r, timezone_offset_r); } bool iso8601_date_parse_tm(const unsigned char *data, size_t size, struct tm *tm_r, int *timezone_offset_r) { time_t timestamp; return iso8601_date_do_parse(data, size, tm_r, ×tamp, timezone_offset_r); } const char *iso8601_date_create_tm(struct tm *tm, int timezone_offset) { const char *time_offset; if (timezone_offset == INT_MAX) time_offset = "Z"; else { char sign = '+'; if (timezone_offset < 0) { timezone_offset = -timezone_offset; sign = '-'; } time_offset = t_strdup_printf("%c%02d:%02d", sign, timezone_offset / 60, timezone_offset % 60); } return t_strdup_printf("%04d-%02d-%02dT%02d:%02d:%02d%s", tm->tm_year + 1900, tm->tm_mon+1, tm->tm_mday, tm->tm_hour, tm->tm_min, tm->tm_sec, time_offset); } const char *iso8601_date_create(time_t timestamp) { struct tm *tm; int timezone_offset; tm = localtime(×tamp); timezone_offset = utc_offset(tm, timestamp); return iso8601_date_create_tm(tm, timezone_offset); }