date.c: Remove a bunch of undesired code.
[notmuch] / date.c
1 /* date.c - Date-parsing utility for the notmuch mail system.
2  *
3  *  Copyright © 2000-2009 Jeffrey Stedfast
4  *
5  * This program is free software: you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation, either version 3 of the License, or
8  * (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program.  If not, see http://www.gnu.org/licenses/ 
17  */
18
19 /* This code was originally written by from Jeffrey Stedfast
20  * as part of his GMime library (http://spruce.sourceforge.net/gmime/)
21  *
22  * Carl Worth <cworth@cworth.org> imported it into notmuch and removed
23  * some glib-isms.
24  */
25
26 #ifdef HAVE_CONFIG_H
27 #include <config.h>
28 #endif
29
30 #define _GNU_SOURCE
31
32 #include <glib.h>
33
34 #include <stdio.h>
35 #include <stdlib.h>
36 #include <string.h>
37 #ifdef HAVE_SYS_PARAM_H
38 #include <sys/param.h>      /* for MAXHOSTNAMELEN */
39 #else
40 #define MAXHOSTNAMELEN 64
41 #endif
42 #ifdef HAVE_UTSNAME_DOMAINNAME
43 #include <sys/utsname.h>    /* for uname() */
44 #endif
45 #include <sys/types.h>
46 #ifdef HAVE_UNISTD_H
47 #include <unistd.h>         /* Unix header for getpid() */
48 #endif
49 #ifdef G_OS_WIN32
50 #include <winsock2.h>
51 #include <ws2tcpip.h>
52 #include <process.h>
53 #define getpid() _getpid()
54 #endif
55 #ifdef HAVE_NETDB_H
56 #include <netdb.h>
57 #endif
58 #include <ctype.h>
59 #include <errno.h>
60
61 #include "gmime-utils.h"
62 #include "gmime-table-private.h"
63 #include "gmime-parse-utils.h"
64 #include "gmime-part.h"
65 #include "gmime-charset.h"
66 #include "gmime-iconv.h"
67 #include "gmime-iconv-utils.h"
68
69 #ifdef ENABLE_WARNINGS
70 #define w(x) x
71 #else
72 #define w(x)
73 #endif /* ENABLE_WARNINGS */
74
75 #define d(x)
76
77 #define GMIME_FOLD_PREENCODED  (GMIME_FOLD_LEN / 2)
78
79 /* date parser macros */
80 #define NUMERIC_CHARS          "1234567890"
81 #define WEEKDAY_CHARS          "SundayMondayTuesdayWednesdayThursdayFridaySaturday"
82 #define MONTH_CHARS            "JanuaryFebruaryMarchAprilMayJuneJulyAugustSeptemberOctoberNovemberDecember"
83 #define TIMEZONE_ALPHA_CHARS   "UTCGMTESTEDTCSTCDTMSTPSTPDTZAMNY()"
84 #define TIMEZONE_NUMERIC_CHARS "-+1234567890"
85 #define TIME_CHARS             "1234567890:"
86
87 #define DATE_TOKEN_NON_NUMERIC          (1 << 0)
88 #define DATE_TOKEN_NON_WEEKDAY          (1 << 1)
89 #define DATE_TOKEN_NON_MONTH            (1 << 2)
90 #define DATE_TOKEN_NON_TIME             (1 << 3)
91 #define DATE_TOKEN_HAS_COLON            (1 << 4)
92 #define DATE_TOKEN_NON_TIMEZONE_ALPHA   (1 << 5)
93 #define DATE_TOKEN_NON_TIMEZONE_NUMERIC (1 << 6)
94 #define DATE_TOKEN_HAS_SIGN             (1 << 7)
95
96 static unsigned char gmime_datetok_table[256] = {
97         128,111,111,111,111,111,111,111,111,111,111,111,111,111,111,111,
98         111,111,111,111,111,111,111,111,111,111,111,111,111,111,111,111,
99         111,111,111,111,111,111,111,111, 79, 79,111,175,111,175,111,111,
100          38, 38, 38, 38, 38, 38, 38, 38, 38, 38,119,111,111,111,111,111,
101         111, 75,111, 79, 75, 79,105, 79,111,111,107,111,111, 73, 75,107,
102          79,111,111, 73, 77, 79,111,109,111, 79, 79,111,111,111,111,111,
103         111,105,107,107,109,105,111,107,105,105,111,111,107,107,105,105,
104         107,111,105,105,105,105,107,111,111,105,111,111,111,111,111,111,
105         111,111,111,111,111,111,111,111,111,111,111,111,111,111,111,111,
106         111,111,111,111,111,111,111,111,111,111,111,111,111,111,111,111,
107         111,111,111,111,111,111,111,111,111,111,111,111,111,111,111,111,
108         111,111,111,111,111,111,111,111,111,111,111,111,111,111,111,111,
109         111,111,111,111,111,111,111,111,111,111,111,111,111,111,111,111,
110         111,111,111,111,111,111,111,111,111,111,111,111,111,111,111,111,
111         111,111,111,111,111,111,111,111,111,111,111,111,111,111,111,111,
112         111,111,111,111,111,111,111,111,111,111,111,111,111,111,111,111,
113 };
114
115 /* hrm, is there a library for this shit? */
116 static struct {
117         char *name;
118         int offset;
119 } tz_offsets [] = {
120         { "UT", 0 },
121         { "GMT", 0 },
122         { "EST", -500 },        /* these are all US timezones.  bloody yanks */
123         { "EDT", -400 },
124         { "CST", -600 },
125         { "CDT", -500 },
126         { "MST", -700 },
127         { "MDT", -600 },
128         { "PST", -800 },
129         { "PDT", -700 },
130         { "Z", 0 },
131         { "A", -100 },
132         { "M", -1200 },
133         { "N", 100 },
134         { "Y", 1200 },
135 };
136
137 static char *tm_months[] = {
138         "Jan", "Feb", "Mar", "Apr", "May", "Jun",
139         "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
140 };
141
142 static char *tm_days[] = {
143         "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"
144 };
145
146 /* This is where it gets ugly... */
147
148 typedef struct _date_token {
149         struct _date_token *next;
150         unsigned char mask;
151         const char *start;
152         size_t len;
153 } date_token;
154
155 #define date_token_free(tok) g_slice_free (date_token, tok)
156 #define date_token_new() g_slice_new (date_token)
157
158 static date_token *
159 datetok (const char *date)
160 {
161         date_token *tokens = NULL, *token, *tail = (date_token *) &tokens;
162         const char *start, *end;
163         unsigned char mask;
164         
165         start = date;
166         while (*start) {
167                 /* kill leading whitespace */
168                 while (*start == ' ' || *start == '\t')
169                         start++;
170                 
171                 if (*start == '\0')
172                         break;
173                 
174                 mask = gmime_datetok_table[(unsigned char) *start];
175                 
176                 /* find the end of this token */
177                 end = start + 1;
178                 while (*end && !strchr ("-/,\t\r\n ", *end))
179                         mask |= gmime_datetok_table[(unsigned char) *end++];
180                 
181                 if (end != start) {
182                         token = date_token_new ();
183                         token->next = NULL;
184                         token->start = start;
185                         token->len = end - start;
186                         token->mask = mask;
187                         
188                         tail->next = token;
189                         tail = token;
190                 }
191                 
192                 if (*end)
193                         start = end + 1;
194                 else
195                         break;
196         }
197         
198         return tokens;
199 }
200
201 static int
202 decode_int (const char *in, size_t inlen)
203 {
204         register const char *inptr;
205         int sign = 1, val = 0;
206         const char *inend;
207         
208         inptr = in;
209         inend = in + inlen;
210         
211         if (*inptr == '-') {
212                 sign = -1;
213                 inptr++;
214         } else if (*inptr == '+')
215                 inptr++;
216         
217         for ( ; inptr < inend; inptr++) {
218                 if (!(*inptr >= '0' && *inptr <= '9'))
219                         return -1;
220                 else
221                         val = (val * 10) + (*inptr - '0');
222         }
223         
224         val *= sign;
225         
226         return val;
227 }
228
229 #if 0
230 static int
231 get_days_in_month (int month, int year)
232 {
233         switch (month) {
234         case 1:
235         case 3:
236         case 5:
237         case 7:
238         case 8:
239         case 10:
240         case 12:
241                 return 31;
242         case 4:
243         case 6:
244         case 9:
245         case 11:
246                 return 30;
247         case 2:
248                 if (g_date_is_leap_year (year))
249                         return 29;
250                 else
251                         return 28;
252         default:
253                 return 0;
254         }
255 }
256 #endif
257
258 static int
259 get_wday (const char *in, size_t inlen)
260 {
261         int wday;
262         
263         g_return_val_if_fail (in != NULL, -1);
264         
265         if (inlen < 3)
266                 return -1;
267         
268         for (wday = 0; wday < 7; wday++) {
269                 if (!g_ascii_strncasecmp (in, tm_days[wday], 3))
270                         return wday;
271         }
272         
273         return -1;  /* unknown week day */
274 }
275
276 static int
277 get_mday (const char *in, size_t inlen)
278 {
279         int mday;
280         
281         g_return_val_if_fail (in != NULL, -1);
282         
283         mday = decode_int (in, inlen);
284         
285         if (mday < 0 || mday > 31)
286                 mday = -1;
287         
288         return mday;
289 }
290
291 static int
292 get_month (const char *in, size_t inlen)
293 {
294         int i;
295         
296         g_return_val_if_fail (in != NULL, -1);
297         
298         if (inlen < 3)
299                 return -1;
300         
301         for (i = 0; i < 12; i++) {
302                 if (!g_ascii_strncasecmp (in, tm_months[i], 3))
303                         return i;
304         }
305         
306         return -1;  /* unknown month */
307 }
308
309 static int
310 get_year (const char *in, size_t inlen)
311 {
312         int year;
313         
314         g_return_val_if_fail (in != NULL, -1);
315         
316         if ((year = decode_int (in, inlen)) == -1)
317                 return -1;
318         
319         if (year < 100)
320                 year += (year < 70) ? 2000 : 1900;
321         
322         if (year < 1969)
323                 return -1;
324         
325         return year;
326 }
327
328 static gboolean
329 get_time (const char *in, size_t inlen, int *hour, int *min, int *sec)
330 {
331         register const char *inptr;
332         int *val, colons = 0;
333         const char *inend;
334         
335         *hour = *min = *sec = 0;
336         
337         inend = in + inlen;
338         val = hour;
339         for (inptr = in; inptr < inend; inptr++) {
340                 if (*inptr == ':') {
341                         colons++;
342                         switch (colons) {
343                         case 1:
344                                 val = min;
345                                 break;
346                         case 2:
347                                 val = sec;
348                                 break;
349                         default:
350                                 return FALSE;
351                         }
352                 } else if (!(*inptr >= '0' && *inptr <= '9'))
353                         return FALSE;
354                 else
355                         *val = (*val * 10) + (*inptr - '0');
356         }
357         
358         return TRUE;
359 }
360
361 static int
362 get_tzone (date_token **token)
363 {
364         const char *inptr, *inend;
365         size_t inlen;
366         int i, t;
367         
368         for (i = 0; *token && i < 2; *token = (*token)->next, i++) {
369                 inptr = (*token)->start;
370                 inlen = (*token)->len;
371                 inend = inptr + inlen;
372                 
373                 if (*inptr == '+' || *inptr == '-') {
374                         return decode_int (inptr, inlen);
375                 } else {
376                         if (*inptr == '(') {
377                                 inptr++;
378                                 if (*(inend - 1) == ')')
379                                         inlen -= 2;
380                                 else
381                                         inlen--;
382                         }
383                         
384                         for (t = 0; t < 15; t++) {
385                                 size_t len = strlen (tz_offsets[t].name);
386                                 
387                                 if (len != inlen)
388                                         continue;
389                                 
390                                 if (!strncmp (inptr, tz_offsets[t].name, len))
391                                         return tz_offsets[t].offset;
392                         }
393                 }
394         }
395         
396         return -1;
397 }
398
399 static time_t
400 mktime_utc (struct tm *tm)
401 {
402         time_t tt;
403         long tz;
404         
405         tm->tm_isdst = -1;
406         tt = mktime (tm);
407         
408 #if defined (G_OS_WIN32)
409         _get_timezone (&tz);
410         if (tm->tm_isdst > 0) {
411                 int dst;
412                 
413                 _get_dstbias (&dst);
414                 tz += dst;
415         }
416 #elif defined (HAVE_TM_GMTOFF)
417         tz = -tm->tm_gmtoff;
418 #elif defined (HAVE_TIMEZONE)
419         if (tm->tm_isdst > 0) {
420 #if defined (HAVE_ALTZONE)
421                 tz = altzone;
422 #else /* !defined (HAVE_ALTZONE) */
423                 tz = (timezone - 3600);
424 #endif
425         } else {
426                 tz = timezone;
427         }
428 #elif defined (HAVE__TIMEZONE)
429         tz = _timezone;
430 #else
431 #error Neither HAVE_TIMEZONE nor HAVE_TM_GMTOFF defined. Rerun autoheader, autoconf, etc.
432 #endif
433         
434         return tt - tz;
435 }
436
437 static time_t
438 parse_rfc822_date (date_token *tokens, int *tzone)
439 {
440         int hour, min, sec, offset, n;
441         date_token *token;
442         struct tm tm;
443         time_t t;
444         
445         g_return_val_if_fail (tokens != NULL, (time_t) 0);
446         
447         token = tokens;
448         
449         memset ((void *) &tm, 0, sizeof (struct tm));
450         
451         if ((n = get_wday (token->start, token->len)) != -1) {
452                 /* not all dates may have this... */
453                 tm.tm_wday = n;
454                 token = token->next;
455         }
456         
457         /* get the mday */
458         if (!token || (n = get_mday (token->start, token->len)) == -1)
459                 return (time_t) 0;
460         
461         tm.tm_mday = n;
462         token = token->next;
463         
464         /* get the month */
465         if (!token || (n = get_month (token->start, token->len)) == -1)
466                 return (time_t) 0;
467         
468         tm.tm_mon = n;
469         token = token->next;
470         
471         /* get the year */
472         if (!token || (n = get_year (token->start, token->len)) == -1)
473                 return (time_t) 0;
474         
475         tm.tm_year = n - 1900;
476         token = token->next;
477         
478         /* get the hour/min/sec */
479         if (!token || !get_time (token->start, token->len, &hour, &min, &sec))
480                 return (time_t) 0;
481         
482         tm.tm_hour = hour;
483         tm.tm_min = min;
484         tm.tm_sec = sec;
485         token = token->next;
486         
487         /* get the timezone */
488         if (!token || (n = get_tzone (&token)) == -1) {
489                 /* I guess we assume tz is GMT? */
490                 offset = 0;
491         } else {
492                 offset = n;
493         }
494         
495         t = mktime_utc (&tm);
496         
497         /* t is now GMT of the time we want, but not offset by the timezone ... */
498         
499         /* this should convert the time to the GMT equiv time */
500         t -= ((offset / 100) * 60 * 60) + (offset % 100) * 60;
501         
502         if (tzone)
503                 *tzone = offset;
504         
505         return t;
506 }
507
508
509 #define date_token_mask(t)  (((date_token *) t)->mask)
510 #define is_numeric(t)       ((date_token_mask (t) & DATE_TOKEN_NON_NUMERIC) == 0)
511 #define is_weekday(t)       ((date_token_mask (t) & DATE_TOKEN_NON_WEEKDAY) == 0)
512 #define is_month(t)         ((date_token_mask (t) & DATE_TOKEN_NON_MONTH) == 0)
513 #define is_time(t)          (((date_token_mask (t) & DATE_TOKEN_NON_TIME) == 0) && (date_token_mask (t) & DATE_TOKEN_HAS_COLON))
514 #define is_tzone_alpha(t)   ((date_token_mask (t) & DATE_TOKEN_NON_TIMEZONE_ALPHA) == 0)
515 #define is_tzone_numeric(t) (((date_token_mask (t) & DATE_TOKEN_NON_TIMEZONE_NUMERIC) == 0) && (date_token_mask (t) & DATE_TOKEN_HAS_SIGN))
516 #define is_tzone(t)         (is_tzone_alpha (t) || is_tzone_numeric (t))
517
518 static time_t
519 parse_broken_date (date_token *tokens, int *tzone)
520 {
521         gboolean got_wday, got_month, got_tzone;
522         int hour, min, sec, offset, n;
523         date_token *token;
524         struct tm tm;
525         time_t t;
526         
527         memset ((void *) &tm, 0, sizeof (struct tm));
528         got_wday = got_month = got_tzone = FALSE;
529         offset = 0;
530         
531         token = tokens;
532         while (token) {
533                 if (is_weekday (token) && !got_wday) {
534                         if ((n = get_wday (token->start, token->len)) != -1) {
535                                 d(printf ("weekday; "));
536                                 got_wday = TRUE;
537                                 tm.tm_wday = n;
538                                 goto next;
539                         }
540                 }
541                 
542                 if (is_month (token) && !got_month) {
543                         if ((n = get_month (token->start, token->len)) != -1) {
544                                 d(printf ("month; "));
545                                 got_month = TRUE;
546                                 tm.tm_mon = n;
547                                 goto next;
548                         }
549                 }
550                 
551                 if (is_time (token) && !tm.tm_hour && !tm.tm_min && !tm.tm_sec) {
552                         if (get_time (token->start, token->len, &hour, &min, &sec)) {
553                                 d(printf ("time; "));
554                                 tm.tm_hour = hour;
555                                 tm.tm_min = min;
556                                 tm.tm_sec = sec;
557                                 goto next;
558                         }
559                 }
560                 
561                 if (is_tzone (token) && !got_tzone) {
562                         date_token *t = token;
563                         
564                         if ((n = get_tzone (&t)) != -1) {
565                                 d(printf ("tzone; "));
566                                 got_tzone = TRUE;
567                                 offset = n;
568                                 goto next;
569                         }
570                 }
571                 
572                 if (is_numeric (token)) {
573                         if (token->len == 4 && !tm.tm_year) {
574                                 if ((n = get_year (token->start, token->len)) != -1) {
575                                         d(printf ("year; "));
576                                         tm.tm_year = n - 1900;
577                                         goto next;
578                                 }
579                         } else {
580                                 /* Note: assumes MM-DD-YY ordering if '0 < MM < 12' holds true */
581                                 if (!got_month && token->next && is_numeric (token->next)) {
582                                         if ((n = decode_int (token->start, token->len)) > 12) {
583                                                 goto mday;
584                                         } else if (n > 0) {
585                                                 d(printf ("mon; "));
586                                                 got_month = TRUE;
587                                                 tm.tm_mon = n - 1;
588                                         }
589                                         goto next;
590                                 } else if (!tm.tm_mday && (n = get_mday (token->start, token->len)) != -1) {
591                                 mday:
592                                         d(printf ("mday; "));
593                                         tm.tm_mday = n;
594                                         goto next;
595                                 } else if (!tm.tm_year) {
596                                         if ((n = get_year (token->start, token->len)) != -1) {
597                                                 d(printf ("2-digit year; "));
598                                                 tm.tm_year = n - 1900;
599                                         }
600                                         goto next;
601                                 }
602                         }
603                 }
604                 
605                 d(printf ("???; "));
606                 
607         next:
608                 
609                 token = token->next;
610         }
611         
612         d(printf ("\n"));
613         
614         t = mktime_utc (&tm);
615         
616         /* t is now GMT of the time we want, but not offset by the timezone ... */
617         
618         /* this should convert the time to the GMT equiv time */
619         t -= ((offset / 100) * 60 * 60) + (offset % 100) * 60;
620         
621         if (tzone)
622                 *tzone = offset;
623         
624         return t;
625 }
626
627 #if 0
628 static void
629 gmime_datetok_table_init (void)
630 {
631         int i;
632         
633         memset (gmime_datetok_table, 0, sizeof (gmime_datetok_table));
634         
635         for (i = 0; i < 256; i++) {
636                 if (!strchr (NUMERIC_CHARS, i))
637                         gmime_datetok_table[i] |= DATE_TOKEN_NON_NUMERIC;
638                 
639                 if (!strchr (WEEKDAY_CHARS, i))
640                         gmime_datetok_table[i] |= DATE_TOKEN_NON_WEEKDAY;
641                 
642                 if (!strchr (MONTH_CHARS, i))
643                         gmime_datetok_table[i] |= DATE_TOKEN_NON_MONTH;
644                 
645                 if (!strchr (TIME_CHARS, i))
646                         gmime_datetok_table[i] |= DATE_TOKEN_NON_TIME;
647                 
648                 if (!strchr (TIMEZONE_ALPHA_CHARS, i))
649                         gmime_datetok_table[i] |= DATE_TOKEN_NON_TIMEZONE_ALPHA;
650                 
651                 if (!strchr (TIMEZONE_NUMERIC_CHARS, i))
652                         gmime_datetok_table[i] |= DATE_TOKEN_NON_TIMEZONE_NUMERIC;
653                 
654                 if (((char) i) == ':')
655                         gmime_datetok_table[i] |= DATE_TOKEN_HAS_COLON;
656                 
657                 if (strchr ("+-", i))
658                         gmime_datetok_table[i] |= DATE_TOKEN_HAS_SIGN;
659         }
660         
661         printf ("static unsigned char gmime_datetok_table[256] = {");
662         for (i = 0; i < 256; i++) {
663                 if (i % 16 == 0)
664                         printf ("\n\t");
665                 printf ("%3d,", gmime_datetok_table[i]);
666         }
667         printf ("\n};\n");
668 }
669 #endif
670
671
672 /**
673  * g_mime_utils_header_decode_date:
674  * @str: input date string
675  * @tz_offset: timezone offset
676  *
677  * Decodes the rfc822 date string and saves the GMT offset into
678  * @tz_offset if non-NULL.
679  *
680  * Returns: the time_t representation of the date string specified by
681  * @str or (time_t) %0 on error. If @tz_offset is non-NULL, the value
682  * of the timezone offset will be stored.
683  **/
684 time_t
685 g_mime_utils_header_decode_date (const char *str, int *tz_offset)
686 {
687         date_token *token, *tokens;
688         time_t date;
689         
690         if (!(tokens = datetok (str))) {
691                 if (tz_offset)
692                         *tz_offset = 0;
693                 
694                 return (time_t) 0;
695         }
696         
697         if (!(date = parse_rfc822_date (tokens, tz_offset)))
698                 date = parse_broken_date (tokens, tz_offset);
699         
700         /* cleanup */
701         while (tokens) {
702                 token = tokens;
703                 tokens = tokens->next;
704                 date_token_free (token);
705         }
706         
707         return date;
708 }