summaryrefslogtreecommitdiffstats
path: root/src/lib-imap/imap-date.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/lib-imap/imap-date.c')
-rw-r--r--src/lib-imap/imap-date.c247
1 files changed, 247 insertions, 0 deletions
diff --git a/src/lib-imap/imap-date.c b/src/lib-imap/imap-date.c
new file mode 100644
index 0000000..2e5569a
--- /dev/null
+++ b/src/lib-imap/imap-date.c
@@ -0,0 +1,247 @@
+/* Copyright (c) 2002-2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "utc-offset.h"
+#include "utc-mktime.h"
+#include "imap-date.h"
+
+#include <ctype.h>
+
+static const char *month_names[] = {
+ "Jan", "Feb", "Mar", "Apr", "May", "Jun",
+ "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
+};
+
+static int parse_timezone(const char *str)
+{
+ int offset;
+
+ /* +|-zone */
+ if ((*str != '+' && *str != '-') ||
+ !i_isdigit(str[1]) || !i_isdigit(str[2]) ||
+ !i_isdigit(str[3]) || !i_isdigit(str[4]))
+ return 0;
+
+ offset = (str[1]-'0') * 10*60 + (str[2]-'0') * 60 +
+ (str[3]-'0') * 10 + (str[4]-'0');
+ return *str == '+' ? offset : -offset;
+}
+
+static const char *imap_parse_date_internal(const char *str, struct tm *tm)
+{
+ int i;
+
+ if (str == NULL || *str == '\0')
+ return NULL;
+
+ i_zero(tm);
+
+ /* "dd-mon-yyyy [hh:mi:ss +|-zone]"
+ dd is 1-2 digits and may be prefixed with space or zero. */
+
+ if (str[0] == ' ') {
+ /* " d-..." */
+ str++;
+ }
+
+ if (!(i_isdigit(str[0]) && (str[1] == '-' ||
+ (i_isdigit(str[1]) && str[2] == '-'))))
+ return NULL;
+
+ tm->tm_mday = (str[0]-'0');
+ if (str[1] == '-')
+ str += 2;
+ else {
+ tm->tm_mday = (tm->tm_mday * 10) + (str[1]-'0');
+ str += 3;
+ }
+
+ /* month name */
+ for (i = 0; i < 12; i++) {
+ if (strncasecmp(month_names[i], str, 3) == 0) {
+ tm->tm_mon = i;
+ break;
+ }
+ }
+ if (i == 12 || str[3] != '-')
+ return NULL;
+ str += 4;
+
+ /* yyyy */
+ if (!i_isdigit(str[0]) || !i_isdigit(str[1]) ||
+ !i_isdigit(str[2]) || !i_isdigit(str[3]))
+ return NULL;
+
+ tm->tm_year = (str[0]-'0') * 1000 + (str[1]-'0') * 100 +
+ (str[2]-'0') * 10 + (str[3]-'0') - 1900;
+
+ str += 4;
+ return str;
+}
+
+static bool imap_mktime(struct tm *tm, time_t *time_r)
+{
+ *time_r = utc_mktime(tm);
+ if (*time_r != (time_t)-1)
+ return TRUE;
+
+ /* the date is outside valid range for time_t. it might still be
+ technically valid though, so try to handle this case.
+ with 64bit time_t the full 0..9999 year range is valid. */
+ if (tm->tm_year <= 100) {
+ /* too old. time_t can be signed or unsigned, handle
+ both cases. */
+ *time_r = (time_t)-1 < (int)0 ? INT_MIN : 0;
+ } else {
+ /* too high. return the highest allowed value.
+ we shouldn't get here with 64bit time_t,
+ but handle that anyway. */
+#if (TIME_T_MAX_BITS == 32 || TIME_T_MAX_BITS == 64)
+ *time_r = (1UL << (TIME_T_MAX_BITS-1)) - 1;
+#else
+ *time_r = (1UL << TIME_T_MAX_BITS) - 1;
+#endif
+ }
+ return FALSE;
+}
+
+bool imap_parse_date(const char *str, time_t *timestamp_r)
+{
+ struct tm tm;
+
+ str = imap_parse_date_internal(str, &tm);
+ if (str == NULL || str[0] != '\0')
+ return FALSE;
+
+ tm.tm_isdst = -1;
+ (void)imap_mktime(&tm, timestamp_r);
+ return TRUE;
+}
+
+bool imap_parse_datetime(const char *str, time_t *timestamp_r,
+ int *timezone_offset_r)
+{
+ struct tm tm;
+
+ str = imap_parse_date_internal(str, &tm);
+ if (str == NULL)
+ return FALSE;
+
+ if (str[0] != ' ')
+ return FALSE;
+ str++;
+
+ /* hh: */
+ if (!i_isdigit(str[0]) || !i_isdigit(str[1]) || str[2] != ':')
+ return FALSE;
+ tm.tm_hour = (str[0]-'0') * 10 + (str[1]-'0');
+ str += 3;
+
+ /* mm: */
+ if (!i_isdigit(str[0]) || !i_isdigit(str[1]) || str[2] != ':')
+ return FALSE;
+ tm.tm_min = (str[0]-'0') * 10 + (str[1]-'0');
+ str += 3;
+
+ /* ss */
+ if (!i_isdigit(str[0]) || !i_isdigit(str[1]) || str[2] != ' ')
+ return FALSE;
+ tm.tm_sec = (str[0]-'0') * 10 + (str[1]-'0');
+ str += 3;
+
+ /* timezone */
+ *timezone_offset_r = parse_timezone(str);
+
+ tm.tm_isdst = -1;
+ if (imap_mktime(&tm, timestamp_r))
+ *timestamp_r -= *timezone_offset_r * 60;
+ return TRUE;
+}
+
+static void imap_to_date_tm(char buf[11], const struct tm *tm)
+{
+ int year;
+
+ /* dd-mon- */
+ buf[0] = (tm->tm_mday / 10) + '0';
+ buf[1] = (tm->tm_mday % 10) + '0';
+ buf[2] = '-';
+ memcpy(buf+3, month_names[tm->tm_mon], 3);
+ buf[6] = '-';
+
+ /* yyyy */
+ year = tm->tm_year + 1900;
+ buf[7] = (year / 1000) + '0';
+ buf[8] = ((year / 100) % 10) + '0';
+ buf[9] = ((year / 10) % 10) + '0';
+ buf[10] = (year % 10) + '0';
+}
+
+static const char *
+imap_to_datetime_tm(const struct tm *tm, int timezone_offset)
+{
+ char *buf;
+
+ /* @UNSAFE: but faster than t_strdup_printf() call.. */
+ buf = t_malloc0(27);
+ imap_to_date_tm(buf, tm);
+ buf[11] = ' ';
+
+ /* hh:mi:ss */
+ buf[12] = (tm->tm_hour / 10) + '0';
+ buf[13] = (tm->tm_hour % 10) + '0';
+ buf[14] = ':';
+ buf[15] = (tm->tm_min / 10) + '0';
+ buf[16] = (tm->tm_min % 10) + '0';
+ buf[17] = ':';
+ buf[18] = (tm->tm_sec / 10) + '0';
+ buf[19] = (tm->tm_sec % 10) + '0';
+ buf[20] = ' ';
+
+ /* timezone */
+ if (timezone_offset >= 0)
+ buf[21] = '+';
+ else {
+ buf[21] = '-';
+ timezone_offset = -timezone_offset;
+ }
+ buf[22] = (timezone_offset / 600) + '0';
+ buf[23] = ((timezone_offset / 60) % 10) + '0';
+ buf[24] = ((timezone_offset % 60) / 10) + '0';
+ buf[25] = (timezone_offset % 10) + '0';
+ buf[26] = '\0';
+
+ return buf;
+}
+
+const char *imap_to_datetime(time_t timestamp)
+{
+ struct tm *tm;
+ int timezone_offset;
+
+ tm = localtime(&timestamp);
+ timezone_offset = utc_offset(tm, timestamp);
+ return imap_to_datetime_tm(tm, timezone_offset);
+}
+
+const char *imap_to_datetime_tz(time_t timestamp, int timezone_offset)
+{
+ const struct tm *tm;
+ time_t adjusted = timestamp + timezone_offset*60;
+
+ tm = gmtime(&adjusted);
+ return imap_to_datetime_tm(tm, timezone_offset);
+}
+
+bool imap_to_date(time_t timestamp, const char **str_r)
+{
+ const struct tm *tm;
+ char *buf;
+
+ tm = localtime(&timestamp);
+
+ buf = t_malloc0(12);
+ imap_to_date_tm(buf, tm);
+ *str_r = buf;
+ return tm->tm_hour == 0 && tm->tm_min == 0 && tm->tm_sec == 0;
+}