1 /* times.c -- Time/date utilities
2  *
3  * Copyright (c) 1994-2010 Carnegie Mellon University.  All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
8  *
9  * 1. Redistributions of source code must retain the above copyright
10  *    notice, this list of conditions and the following disclaimer.
11  *
12  * 2. Redistributions in binary form must reproduce the above copyright
13  *    notice, this list of conditions and the following disclaimer in
14  *    the documentation and/or other materials provided with the
15  *    distribution.
16  *
17  * 3. The name "Carnegie Mellon University" must not be used to
18  *    endorse or promote products derived from this software without
19  *    prior written permission. For permission or any legal
20  *    details, please contact
21  *      Carnegie Mellon University
22  *      Center for Technology Transfer and Enterprise Creation
23  *      4615 Forbes Avenue
24  *      Suite 302
25  *      Pittsburgh, PA  15213
26  *      (412) 268-7393, fax: (412) 268-7395
27  *      innovation@andrew.cmu.edu
28  *
29  * 4. Redistributions of any form whatsoever must retain the following
30  *    acknowledgment:
31  *    "This product includes software developed by Computing Services
32  *     at Carnegie Mellon University (http://www.cmu.edu/computing/)."
33  *
34  * CARNEGIE MELLON UNIVERSITY DISCLAIMS ALL WARRANTIES WITH REGARD TO
35  * THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
36  * AND FITNESS, IN NO EVENT SHALL CARNEGIE MELLON UNIVERSITY BE LIABLE
37  * FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
38  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN
39  * AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
40  * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
41  */
42 
43 #include <ctype.h>
44 #include <memory.h>
45 #include <stdio.h>
46 #include <string.h>
47 #include <strings.h>
48 
49 #include "assert.h"
50 #include "times.h"
51 #include "util.h"
52 #include "gmtoff.h"
53 #include "mkgmtime.h"
54 
55 EXPORTED const char monthname[][4] = {
56     "Jan", "Feb", "Mar", "Apr", "May", "Jun",
57     "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
58 };
59 EXPORTED const char wday[][4] = {
60     "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"
61 };
62 
63 
64 #define EOB (-1)            /* End Of Buffer */
65 
66 static const char rfc5322_special[256] = {
67     [' ']  = 1,
68     ['\t'] = 1,
69     ['\r'] = 1,
70     ['\n'] = 1,
71 };
72 
73 static const char rfc5322_separators[256] = {
74     [' ']  = 1,
75     [',']  = 1,
76     ['-']  = 1,
77     ['+']  = 1,
78     [':']  = 1,
79 };
80 
81 enum {
82     Alpha = 1,              /* Alphabet */
83     UAlpha = 2,             /* Uppercase Alphabet */
84     LAlpha = 4,             /* Lowercase Alphabet */
85     Digit = 8,              /* Digits/Numbers */
86     TZSign = 16,            /* Timezone sign +/- */
87 };
88 
89 static const long rfc5322_usascii_charset[257] = {
90     ['0' + 1 ... '9' + 1] = Digit,
91     ['A' + 1 ... 'Z' + 1] = Alpha | UAlpha,
92     ['a' + 1 ... 'z' + 1] = Alpha | LAlpha,
93     ['+' + 1] = TZSign,
94     ['-' + 1] = TZSign
95 };
96 
97 struct rfc5322dtbuf {
98     const char *str;
99     int len;
100     int offset;
101 };
102 
103 #define isleap(year) (!((year) % 4) && (((year) % 100) || !((year) % 400)))
104 
monthdays(int year,int month)105 static int monthdays(int year/*since 1900*/, int month/*0-based*/)
106 {
107     int leapday;
108     static const int mdays[12] = {
109         31, 28, 31, 30, 31, 30,
110         31, 31, 30, 31, 30, 31
111     };
112 
113     leapday = (month == 1 && isleap(year+1900));
114     return mdays[month] + leapday;
115 }
116 
dayofyear(int year,int month,int day)117 static int dayofyear(int year/*since 1900*/, int month/*0-based*/, int day)
118 {
119     static const int ydays[2][13] = {
120         {0, 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334},
121         {0, 0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335}
122     };
123     return ydays[isleap(year+1900)][month+1] + day;
124 }
125 
126 #undef isleap
127 
dayofweek(int year,int month,int day)128 static int dayofweek(int year/*since 1900*/, int month/*0-based*/, int day)
129 {
130     /* Uses Zeller's congruence for the Gregorian calendar
131      * https://en.wikipedia.org/wiki/Zeller%27s_congruence
132      * h is the day of the week with Saturday = 0 */
133     int q = day; // day of month
134     int m = month <= 1 ? month + 13 : month + 1; // month, (3 = March, 4 = April, ..., 14 = February)
135     int Y = month <= 1 ? 1900 + year - 1 : 1900 + year; // year
136     int h = (q + ((13 * (m + 1)) / 5) + Y + (Y / 4) - (Y / 100) + (Y / 400)) % 7;
137     return (h + 6) % 7;
138 }
139 
140 /* 'buf' must be at least 80 characters */
time_to_rfc822(time_t t,char * buf,size_t len)141 EXPORTED int time_to_rfc822(time_t t, char *buf, size_t len)
142 {
143     struct tm *tm;
144     long gmtoff;
145     int gmtnegative = 0;
146 
147     assert(buf != NULL);
148 
149     tm = localtime(&t);
150     gmtoff = gmtoff_of(tm, t);
151     if (gmtoff < 0) {
152         gmtoff = -gmtoff;
153         gmtnegative = 1;
154     }
155     gmtoff /= 60;
156 
157     return snprintf(buf, len, "%s, %02d %s %4d %02d:%02d:%02d %c%.2lu%.2lu",
158              wday[tm->tm_wday],
159              tm->tm_mday, monthname[tm->tm_mon], tm->tm_year + 1900,
160              tm->tm_hour, tm->tm_min, tm->tm_sec,
161              gmtnegative ? '-' : '+', gmtoff / 60, gmtoff % 60);
162 }
163 
164 
165 /*
166  * Skip RFC 822 FWS = Folding White Space.  This is the white
167  * space that can be inserted harmlessly into structured
168  * RFC 822 headers, including splitting them over multiple lines.
169  *
170  * Note that RFC 822 isn't entirely clear about whether such
171  * space may be present in date-times, but it's successor
172  * RFC 2822 is quite clear and explicit.  Note also that
173  * neither RFC allows for (comments) inside a date-time,
174  * so we don't attempt to handle that here.
175  */
skip_fws(const char * p)176 static const char *skip_fws(const char *p)
177 {
178     if (!p)
179         return NULL;
180     while (*p && Uisspace(*p)) {
181         /* check for end of an RFC 822 header line */
182         if (*p == '\n') {
183             p++;
184             if (*p != ' ' && *p != '\t')
185                 return NULL;
186         }
187         else
188             p++;
189     }
190     return (*p ? p : NULL);
191 }
192 
parse_rfc822(const char * s,time_t * tp,int dayonly)193 static int parse_rfc822(const char *s, time_t *tp, int dayonly)
194 {
195     const char *origs = s;
196     struct tm tm;
197     time_t t;
198     char month[4];
199     int zone_off = 0;
200 
201     if (!s)
202         goto baddate;
203 
204     memset(&tm, 0, sizeof(tm));
205 
206     s = skip_fws(s);
207     if (!s)
208         goto baddate;
209 
210     if (Uisalpha(*s)) {
211         /* Day name -- skip over it */
212         s++;
213         if (!Uisalpha(*s))
214             goto baddate;
215         s++;
216         if (!Uisalpha(*s))
217             goto baddate;
218         s++;
219         s = skip_fws(s);
220         if (!s || *s++ != ',')
221             goto baddate;
222         s = skip_fws(s);
223         if (!s)
224             goto baddate;
225     }
226 
227     if (!Uisdigit(*s))
228         goto baddate;
229     tm.tm_mday = *s++ - '0';
230     if (Uisdigit(*s)) {
231         tm.tm_mday = tm.tm_mday*10 + *s++ - '0';
232     }
233 
234     /* Parse month name */
235     s = skip_fws(s);
236     if (!s)
237         goto baddate;
238     month[0] = *s++;
239     if (!Uisalpha(month[0]))
240         goto baddate;
241     month[1] = *s++;
242     if (!Uisalpha(month[1]))
243         goto baddate;
244     month[2] = *s++;
245     if (!Uisalpha(month[2]))
246         goto baddate;
247     month[3] = '\0';
248     for (tm.tm_mon = 0; tm.tm_mon < 12; tm.tm_mon++) {
249         if (!strcasecmp(month, monthname[tm.tm_mon])) break;
250     }
251     if (tm.tm_mon == 12)
252         goto baddate;
253 
254     /* Parse year */
255     s = skip_fws(s);
256     if (!s || !Uisdigit(*s))
257         goto baddate;
258     tm.tm_year = *s++ - '0';
259     if (!Uisdigit(*s))
260         goto baddate;
261     tm.tm_year = tm.tm_year * 10 + *s++ - '0';
262     if (Uisdigit(*s)) {
263         if (tm.tm_year < 19)
264             goto baddate;
265         tm.tm_year -= 19;
266         tm.tm_year = tm.tm_year * 10 + *s++ - '0';
267         if (!Uisdigit(*s))
268             goto baddate;
269         tm.tm_year = tm.tm_year * 10 + *s++ - '0';
270     } else {
271         if (tm.tm_year < 70) {
272             /* two-digit year, probably after 2000.
273              * This patent was overturned, right?
274              */
275             tm.tm_year += 100;
276         }
277     }
278     if (Uisdigit(*s)) {
279        /* five-digit date */
280        goto baddate;
281      }
282 
283     if (tm.tm_mday > monthdays(tm.tm_year, tm.tm_mon))
284         goto baddate;
285 
286     s = skip_fws(s);
287     if (s && !dayonly) {
288         /* Parse hour */
289         if (!s || !Uisdigit(*s))
290             goto badtime;
291         tm.tm_hour = *s++ - '0';
292         if (!Uisdigit(*s))
293             goto badtime;
294         tm.tm_hour = tm.tm_hour * 10 + *s++ - '0';
295         if (!s || *s++ != ':')
296             goto badtime;
297 
298         /* Parse min */
299         if (!s || !Uisdigit(*s))
300             goto badtime;
301         tm.tm_min = *s++ - '0';
302         if (!Uisdigit(*s))
303             goto badtime;
304         tm.tm_min = tm.tm_min * 10 + *s++ - '0';
305 
306         if (*s == ':') {
307             /* Parse sec */
308             if (!++s || !Uisdigit(*s))
309                 goto badtime;
310             tm.tm_sec = *s++ - '0';
311             if (!Uisdigit(*s))
312                 goto badtime;
313             tm.tm_sec = tm.tm_sec * 10 + *s++ - '0';
314         }
315 
316         s = skip_fws(s);
317         if (s) {
318             /* Parse timezone offset */
319             if (*s == '+' || *s == '-') {
320                 /* Parse numeric offset */
321                 int east = (*s++ == '-');
322 
323                 if (!s || !Uisdigit(*s))
324                     goto badzone;
325                 zone_off = *s++ - '0';
326                 if (!s || !Uisdigit(*s))
327                     goto badzone;
328                 zone_off = zone_off * 10 + *s++ - '0';
329                 if (!s || !Uisdigit(*s))
330                     goto badzone;
331                 zone_off = zone_off * 6 + *s++ - '0';
332                 if (!s || !Uisdigit(*s))
333                     goto badzone;
334                 zone_off = zone_off * 10 + *s++ - '0';
335 
336                 if (east)
337                     zone_off = -zone_off;
338             }
339             else if (Uisalpha(*s)) {
340                 char zone[4];
341 
342                 zone[0] = *s++;
343                 if (!Uisalpha(*s)) {
344                     /* Parse military (single-char) zone */
345                     zone[1] = '\0';
346                     lcase(zone);
347                     if (zone[0] < 'j')
348                         zone_off = (zone[0] - 'a' + 1) * 60;
349                     else if (zone[0] == 'j')
350                         goto badzone;
351                     else if (zone[0] <= 'm')
352                         zone_off = (zone[0] - 'a') * 60;
353                     else if (zone[0] < 'z')
354                         zone_off = ('m' - zone[0]) * 60;
355                     else
356                         zone_off = 0;
357                 }
358                 else {
359                     zone[1] = *s++;
360                     if (!Uisalpha(*s)) {
361                         /* Parse UT (universal time) */
362                         zone[2] = '\0';
363                         lcase(zone);
364                         if (strcmp(zone, "ut"))
365                             goto badzone;
366                         zone_off = 0;
367                     }
368                     else {
369                         /* Parse 3-char time zone */
370                         char *p;
371 
372                         zone[2] = *s;
373                         zone[3] = '\0';
374                         lcase(zone);
375                         /* GMT (Greenwich mean time) */
376                         if (!strcmp(zone, "gmt"))
377                             zone_off = 0;
378 
379                         /* US time zone */
380                         else {
381                             p = strchr("aecmpyhb", zone[0]);
382                             if (!p || zone[2] != 't')
383                                 goto badzone;
384                             zone_off = (strlen(p) - 12) * 60;
385                             if (zone[1] == 'd')
386                                 zone_off += 60;
387                             else if (zone[1] != 's')
388                                 goto badzone;
389                         }
390                     }
391                 }
392             }
393             else
394  badzone:
395                 zone_off = 0;
396         }
397     }
398     else
399  badtime:
400         tm.tm_hour = 12;
401 
402     tm.tm_isdst = -1;
403 
404     if (!dayonly)
405         t = mkgmtime(&tm);
406     else {
407         assert(zone_off == 0);
408         t = mktime(&tm);
409     }
410     if (t >= 0) {
411         *tp = (t - zone_off * 60);
412         return s - origs;
413     }
414 
415  baddate:
416     return -1;
417 }
418 
419 /*
420  * Parse an RFC 822 (strictly speaking, RFC 2822) date-time
421  * from the @s into a UNIX time_t *@tp.  The string @s is
422  * terminated either by a NUL or by an RFC 822 end of header
423  * line (CRLF not followed by whitespace); this allows
424  * parsing dates directly out of mapped messages.
425  *
426  * Returns: number of characters consumed from @s or -1 on error.
427  */
time_from_rfc822(const char * s,time_t * tp)428 EXPORTED int time_from_rfc822(const char *s, time_t *tp)
429 {
430     return parse_rfc822(s, tp, 0);
431 }
432 
433 /*
434  * Parse an RFC 822 (strictly speaking, RFC 2822) date-time
435  * from the @s into a UNIX time_t *@tp, but parse only the
436  * date portion, ignoring the time and timezone and returning
437  * a time in the server's timezone.  This is a godawful hack
438  * designed to support the Cyrus implementation of the
439  * IMAP SEARCH command.
440  *
441  * Returns: number of characters consumed from @s or -1 on error.
442  */
day_from_rfc822(const char * s,time_t * tp)443 EXPORTED int day_from_rfc822(const char *s, time_t *tp)
444 {
445     return parse_rfc822(s, tp, 1);
446 }
447 
offsettime_normalize(struct offsettime * t)448 static int offsettime_normalize(struct offsettime *t)
449 {
450     /* sanity check the date/time (including leap day & second) */
451     if (t->tm.tm_mon < 0 || t->tm.tm_mon > 11 ||
452         t->tm.tm_mday < 1 ||
453         t->tm.tm_mday > monthdays(t->tm.tm_year, t->tm.tm_mon) ||
454         t->tm.tm_hour > 23 || t->tm.tm_min > 59 || t->tm.tm_sec > 60) {
455         return 0;
456     }
457     /* Set day of week and year fields */
458     t->tm.tm_wday = dayofweek(t->tm.tm_year, t->tm.tm_mon, t->tm.tm_mday);
459     t->tm.tm_yday = dayofyear(t->tm.tm_year, t->tm.tm_mon, t->tm.tm_mday);
460     t->tm.tm_isdst = -1;
461     return 1;
462 }
463 
464 /*
465  * Parse an RFC 3339 = ISO 8601 format date-time string,
466  * preserving the zone offset.
467  * Returns: number of characters in @s consumed, or -1 on error.
468  */
offsettime_from_iso8601(const char * s,struct offsettime * t)469 EXPORTED int offsettime_from_iso8601(const char *s, struct offsettime *t)
470 {
471     const char *origs = s;
472     int n;
473 
474     /* parse the ISO 8601 date/time */
475     /* XXX should use strptime ? */
476     memset(t, 0, sizeof(struct offsettime));
477     n = sscanf(s, "%4d-%2d-%2dT%2d:%2d:%2d",
478                &t->tm.tm_year, &t->tm.tm_mon, &t->tm.tm_mday,
479                &t->tm.tm_hour, &t->tm.tm_min, &t->tm.tm_sec);
480     if (n != 6)
481         return -1;
482 
483     s += 19;
484     if (*s == '.') {
485         /* skip fractional secs */
486         while (Uisdigit(*(++s)));
487     }
488 
489     /* handle offset */
490     switch (*s++) {
491     case 'Z': t->tm_off = 0; break;
492     case '-': t->tm_off = -1; break;
493     case '+': t->tm_off = 1; break;
494     default: return -1;
495     }
496     if (t->tm_off) {
497         int tm_houroff, tm_minoff;
498 
499         n = sscanf(s, "%2d:%2d", &tm_houroff, &tm_minoff);
500         if (n != 2)
501             return -1;
502         t->tm_off *= 60 * (60 * tm_houroff + tm_minoff);
503         s += 5;
504     }
505 
506     t->tm.tm_year -= 1900; /* normalize to years since 1900 */
507     t->tm.tm_mon--; /* normalize to months since January */
508 
509     if (!offsettime_normalize(t))
510         return -1;
511 
512     return s - origs;
513 }
514 
515 
516 /*
517  * Parse an RFC 3339 = ISO 8601 format date-time string.
518  * Returns: number of characters in @s consumed, or -1 on error.
519  */
time_from_iso8601(const char * s,time_t * tp)520 EXPORTED int time_from_iso8601(const char *s, time_t *tp)
521 {
522     struct offsettime ot;
523 
524     int r = offsettime_from_iso8601(s, &ot);
525     if (r < 0) return r;
526 
527     /* normalize to GMT */
528     *tp = mkgmtime(&ot.tm) - ot.tm_off;
529     return r;
530 }
531 
breakdown_time_to_iso8601(const struct timeval * t,struct tm * tm,enum timeval_precision tv_precision,long gmtoff,char * buf,size_t len,int withsep)532 static int breakdown_time_to_iso8601(const struct timeval *t, struct tm *tm,
533                                      enum timeval_precision tv_precision,
534                                      long gmtoff, char *buf, size_t len,
535                                      int withsep)
536 {
537     int gmtnegative = 0;
538     size_t rlen;
539     const char *datefmt = withsep ? "%Y-%m-%dT%H:%M:%S" : "%Y%m%dT%H%M%S";
540 
541     /*assert(date > 0); - it turns out these can happen, annoyingly enough */
542     /*assert(tm->tm_year >= 69);*/
543 
544     if (gmtoff < 0) {
545         gmtoff = -gmtoff;
546         gmtnegative = 1;
547     }
548     gmtoff /= 60;
549 
550     rlen = strftime(buf, len, datefmt, tm);
551     if (rlen > 0) {
552         switch(tv_precision) {
553         case timeval_ms:
554             rlen += snprintf(buf+rlen, len-rlen, ".%.3lu", t->tv_usec/1000);
555             break;
556         case timeval_us:
557             rlen += snprintf(buf+rlen, len-rlen, ".%.6lu", t->tv_usec);
558             break;
559         case timeval_s:
560             break;
561         }
562 
563         /* UTC can be written "Z" or "+00:00" */
564         if (gmtoff == 0)
565             rlen += snprintf(buf+rlen, len-rlen, "Z");
566         else
567             rlen += snprintf(buf+rlen, len-rlen, "%c%.2lu:%.2lu",
568                              gmtnegative ? '-' : '+', gmtoff/60, gmtoff%60);
569     }
570 
571     return rlen;
572 }
573 
574 /*
575  * Generate an RFC 3339 = ISO 8601 format date-time string in Zulu (UTC).
576  *
577  * Returns: number of characters in @buf generated, or -1 on error.
578  */
time_to_iso8601(time_t t,char * buf,size_t len,int withsep)579 EXPORTED int time_to_iso8601(time_t t, char *buf, size_t len, int withsep)
580 {
581     struct tm *tm = (struct tm *) gmtime(&t);
582     struct timeval tv = { t, 0 };
583     long gmtoff = gmtoff_of(tm, tv.tv_sec);
584 
585     return breakdown_time_to_iso8601(&tv, tm, timeval_s, gmtoff, buf, len, withsep);
586 }
587 
offsettime_to_iso8601(struct offsettime * t,char * buf,size_t len,int withsep)588 EXPORTED int offsettime_to_iso8601(struct offsettime *t, char *buf, size_t len, int withsep)
589 {
590     struct timeval tv = { mktime(&t->tm), 0 };
591     return breakdown_time_to_iso8601(&tv, &t->tm, timeval_s, t->tm_off, buf, len, withsep);
592 }
593 
594 /*
595  * Generate an RFC 3339 = ISO 8601 format date-time string in local time with
596  * offset from UTC and fractions of second.
597  *
598  * Returns: number of characters in @buf generated, or -1 on error.
599  */
timeval_to_iso8601(const struct timeval * tv,enum timeval_precision tv_prec,char * buf,size_t len)600 EXPORTED int timeval_to_iso8601(const struct timeval *tv, enum timeval_precision tv_prec,
601                        char *buf, size_t len)
602 {
603     struct tm *tm = localtime(&(tv->tv_sec));
604     long gmtoff = gmtoff_of(tm, tv->tv_sec);
605     return breakdown_time_to_iso8601(tv, tm, tv_prec, gmtoff, buf, len, 1);
606 }
607 
time_to_rfc3339(time_t t,char * buf,size_t len)608 EXPORTED int time_to_rfc3339(time_t t, char *buf, size_t len)
609 {
610     struct tm *tm = gmtime(&t);
611 
612     return snprintf(buf, len, "%04d-%02d-%02dT%02d:%02d:%02dZ",
613                     tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday,
614                     tm->tm_hour, tm->tm_min, tm->tm_sec);
615 }
616 
617 /*
618  * Convert a time_t date to an IMAP-style date
619  * datebuf needs to be >= 30 bytes.
620  *
621  * Returns: number of characters in @buf generated, or -1 on error.
622  */
time_to_rfc3501(time_t date,char * buf,size_t len)623 EXPORTED int time_to_rfc3501(time_t date, char *buf, size_t len)
624 {
625     struct tm *tm = localtime(&date);
626     long gmtoff = gmtoff_of(tm, date);
627     int gmtnegative = 0;
628 
629     /*assert(date > 0); - it turns out these can happen, annoyingly enough */
630     assert(tm->tm_year >= 69);
631 
632     if (gmtoff < 0) {
633         gmtoff = -gmtoff;
634         gmtnegative = 1;
635     }
636     gmtoff /= 60;
637     return snprintf(buf, len,
638             "%2u-%s-%u %.2u:%.2u:%.2u %c%.2lu%.2lu",
639             tm->tm_mday, monthname[tm->tm_mon], tm->tm_year+1900,
640             tm->tm_hour, tm->tm_min, tm->tm_sec,
641             gmtnegative ? '-' : '+', gmtoff/60, gmtoff%60);
642 }
643 
644 
645 /*
646  * Parse a string in IMAP date-time format (and some more
647  * obscure legacy formats too) to a time_t.  Parses both
648  * date and time parts.
649  *
650  * Specific formats accepted are listed below.  Note that only
651  * the first two are compliant with RFC 3501, the remainder
652  * are legacy formats.  Note that the " quotes are not part
653  * of the format, they're just used in this comment to show
654  * where the leading spaces are.
655  *
656  *  "dd-mmm-yyyy HH:MM:SS zzzzz"
657  *  " d-mmm-yyyy HH:MM:SS zzzzz"
658  *  "dd-mmm-yy HH:MM:SS z"
659  *  " d-mmm-yy HH:MM:SS z"
660  *  "dd-mmm-yy HH:MM:SS zz"
661  *  " d-mmm-yy HH:MM:SS zz"
662  *  "dd-mmm-yy HH:MM:SS zzz"
663  *  " d-mmm-yy HH:MM:SS zzz"
664  *
665  * where:
666  *  dd  is the day-of-month between 1 and 31 inclusive.
667  * mmm  is the three-letter abbreviation for the English
668  *      month name (case insensitive).
669  * yy   is the 2 digit year, between 00 (the year 1900)
670  *      and 99 (the year 1999) inclusive.
671  * yyyy is the 4 digit year, between 1900 and disaster
672  *      (31b time_t wrapping in 2038 is not handled, sorry).
673  * HH   is the hour, zero padded, between 00 and 23 inclusive.
674  * MM   is the minute, zero padded, between 00 and 59 inclusive.
675  * MM   is the second, zero padded, between 00 and 60 inclusive
676  *      (to account for leap seconds).
677  * z    is a US military style single character time zone.
678  *          A (Alpha) is +0100 ... I (India) is +0900
679  *          J (Juliet) is not defined
680  *          K (Kilo) is +1000 ... M (Mike) is +1200
681  *          N (November) is -0100 ... Y (Yankee) is -1200
682  *          Z (Zulu) is UTC
683  * zz   is the case-insensitive string "UT", denoting UTC time.
684  * zzz  is a three-character case insensitive North American
685  *      time zone name, one of the following (listed with the
686  *      UTC offsets and comments):
687  *          AST -0400   Atlantic Standard Time
688  *          ADT -0300   Atlantic Daylight Time
689  *          EST -0500   Eastern Standard Time
690  *          EDT -0400   Eastern Daylight Time
691  *          CST -0600   Central Standard Time
692  *          CDT -0500   Central Daylight Time
693  *          MST -0700   Mountain Standard Time
694  *          MDT -0600   Mountain Daylight Time
695  *          PST -0800   Pacific Standard Time
696  *          PDT -0700   Pacific Daylight Time
697  *          YST -0900   Yukon Standard Time
698  *                      (Obsolete, now AKST = Alaska S.T.)
699  *          YDT -0800   Yukon Daylight Time
700  *                      (Obsolete, now AKDT = Alaska D.T.)
701  *          HST -1000   Hawaiian Standard Time
702  *                      (Obsolete, now HAST = Hawaiian/Aleutian S.T.)
703  *          HDT -0900   Hawaiian Daylight Time
704  *                      (Obsolete, now HADT = Hawaiian/Aleutian D.T.)
705  *          BST -1100   Used in American Samoa & Midway Island
706  *                      (Obsolete, now SST = Samoa S.T.)
707  *          BDT -1000   Nonsensical, standard time is used
708  *                      all year around in the SST territories.
709  * zzzzz is a numeric time zone offset in the form +HHMM
710  *      or -HMMM.
711  *
712  * Returns: Number of characters consumed from @s on success,
713  *          or -1 on error.
714  */
time_from_rfc3501(const char * s,time_t * date)715 EXPORTED int time_from_rfc3501(const char *s, time_t *date)
716 {
717     const char *origs = s;
718     int c;
719     struct tm tm;
720     int old_format = 0;
721     char month[4], zone[4], *p;
722     time_t tmp_gmtime;
723     int zone_off;   /* timezone offset in minutes */
724 
725     memset(&tm, 0, sizeof tm);
726 
727     /* Day of month */
728     c = *s++;
729     if (c == ' ')
730         c = '0';
731     else if (!isdigit(c))
732         goto baddate;
733     tm.tm_mday = c - '0';
734 
735     c = *s++;
736     if (isdigit(c)) {
737         tm.tm_mday = tm.tm_mday * 10 + c - '0';
738         c = *s++;
739         if (tm.tm_mday <= 0 || tm.tm_mday > 31)
740             goto baddate;
741     }
742 
743     if (c != '-')
744         goto baddate;
745     c = *s++;
746 
747     /* Month name */
748     if (!isalpha(c))
749         goto baddate;
750     month[0] = c;
751     c = *s++;
752     if (!isalpha(c))
753         goto baddate;
754     month[1] = c;
755     c = *s++;
756     if (!isalpha(c))
757         goto baddate;
758     month[2] = c;
759     c = *s++;
760     month[3] = '\0';
761 
762     for (tm.tm_mon = 0; tm.tm_mon < 12; tm.tm_mon++) {
763         if (!strcasecmp(month, monthname[tm.tm_mon]))
764             break;
765     }
766     if (tm.tm_mon == 12)
767         goto baddate;
768 
769     if (c != '-')
770         goto baddate;
771     c = *s++;
772 
773     /* Year */
774     if (!isdigit(c))
775         goto baddate;
776     tm.tm_year = c - '0';
777     c = *s++;
778     if (!isdigit(c))
779         goto baddate;
780     tm.tm_year = tm.tm_year * 10 + c - '0';
781     c = *s++;
782     if (isdigit(c)) {
783         if (tm.tm_year < 19)
784             goto baddate;
785         tm.tm_year -= 19;
786         tm.tm_year = tm.tm_year * 10 + c - '0';
787         c = *s++;
788         if (!isdigit(c))
789             goto baddate;
790         tm.tm_year = tm.tm_year * 10 + c - '0';
791         c = *s++;
792     }
793     else
794         old_format++;
795 
796     if (tm.tm_mday > monthdays(tm.tm_year, tm.tm_mon))
797         goto baddate;
798 
799     /* Hour */
800     if (c != ' ')
801         goto baddate;
802     c = *s++;
803     if (!isdigit(c))
804         goto baddate;
805     tm.tm_hour = c - '0';
806     c = *s++;
807     if (!isdigit(c))
808         goto baddate;
809     tm.tm_hour = tm.tm_hour * 10 + c - '0';
810     c = *s++;
811     if (tm.tm_hour > 23)
812         goto baddate;
813 
814     /* Minute */
815     if (c != ':')
816         goto baddate;
817     c = *s++;
818     if (!isdigit(c))
819         goto baddate;
820     tm.tm_min = c - '0';
821     c = *s++;
822     if (!isdigit(c))
823         goto baddate;
824     tm.tm_min = tm.tm_min * 10 + c - '0';
825     c = *s++;
826     if (tm.tm_min > 59)
827         goto baddate;
828 
829     /* Second */
830     if (c != ':')
831         goto baddate;
832     c = *s++;
833     if (!isdigit(c))
834         goto baddate;
835     tm.tm_sec = c - '0';
836     c = *s++;
837     if (!isdigit(c))
838         goto baddate;
839     tm.tm_sec = tm.tm_sec * 10 + c - '0';
840     c = *s++;
841     if (tm.tm_min > 60)
842         goto baddate;
843 
844     /* Time zone */
845     if (old_format) {
846         if (c != ' ')
847             goto baddate;
848         c = *s++;
849 
850         if (!isalpha(c))
851             goto baddate;
852         zone[0] = c;
853         c = *s++;
854 
855         if (c == '\0') {
856             /* Military (single-char) zones */
857             zone[1] = '\0';
858             lcase(zone);
859             if (zone[0] <= 'i') {
860                 zone_off = (zone[0] - 'a' + 1)*60;
861             }
862             else if (zone[0] == 'j') {
863                 goto baddate;
864             }
865             else if (zone[0] <= 'm') {
866                 zone_off = (zone[0] - 'k' + 10)*60;
867             }
868             else if (zone[0] < 'z') {
869                 zone_off = ('m' - zone[0])*60;
870             }
871             else    /* 'z' */
872                 zone_off = 0;
873         }
874         else {
875             /* UT (universal time) */
876             zone[1] = c;
877             c = *s++;
878             if (c == '\0') {
879                 zone[2] = '\0';
880                 lcase(zone);
881                 if (!strcmp(zone, "ut"))
882                     goto baddate;
883                 zone_off = 0;
884             }
885             else {
886                 /* 3-char time zone */
887                 zone[2] = c;
888                 c = *s++;
889                 if (c != '\0')
890                     goto baddate;
891                 zone[3] = '\0';
892                 lcase(zone);
893                 p = strchr("aecmpyhb", zone[0]);
894                 if (c != '\0' || zone[2] != 't' || !p)
895                     goto baddate;
896                 zone_off = (strlen(p) - 12)*60;
897                 if (zone[1] == 'd')
898                     zone_off += 60;
899                 else if (zone[1] != 's')
900                     goto baddate;
901             }
902         }
903     }
904     else {
905         if (c != ' ')
906             goto baddate;
907         c = *s++;
908 
909         if (c != '+' && c != '-')
910             goto baddate;
911         zone[0] = c;
912 
913         c = *s++;
914         if (!isdigit(c))
915             goto baddate;
916         zone_off = c - '0';
917         c = *s++;
918         if (!isdigit(c))
919             goto baddate;
920         zone_off = zone_off * 10 + c - '0';
921         c = *s++;
922         if (!isdigit(c))
923             goto baddate;
924         zone_off = zone_off * 6 + c - '0';
925         c = *s++;
926         if (!isdigit(c))
927             goto baddate;
928         zone_off = zone_off * 10 + c - '0';
929 
930         if (zone[0] == '-')
931             zone_off = -zone_off;
932 
933         c = *s++;
934         if (c != '\0')
935             goto baddate;
936     }
937 
938     tm.tm_isdst = -1;
939 
940     tmp_gmtime = mkgmtime(&tm);
941     if (tmp_gmtime == -1)
942         goto baddate;
943 
944     *date = tmp_gmtime - zone_off*60;
945 
946     return s-1 - origs;
947 
948 baddate:
949     return -1;
950 }
951 
952 /**
953  ** Support functions for time_from_rfc5322()
954  **/
get_next_char(struct rfc5322dtbuf * buf)955 static inline int get_next_char(struct rfc5322dtbuf *buf)
956 {
957     int c;
958 
959     if (buf->offset < buf->len) {
960         buf->offset++;
961         c = buf->str[buf->offset];
962         return c;
963     }
964 
965     return EOB;
966 }
967 
get_current_char(struct rfc5322dtbuf * buf)968 static inline int get_current_char(struct rfc5322dtbuf *buf)
969 {
970     int offset = buf->offset;
971 
972     if (offset < buf->len)
973         return buf->str[offset];
974     else
975         return EOB;
976 }
977 
978 /*
979   TODO: Support comments as per RFC.
980 */
skip_ws(struct rfc5322dtbuf * buf,int skipcomment)981 static int skip_ws(struct rfc5322dtbuf *buf,
982                    int skipcomment __attribute__((unused)))
983 {
984     int c = buf->str[buf->offset];
985 
986     while (c != EOB) {
987         if (rfc5322_special[c]) {
988             c = get_next_char(buf);
989             continue;
990         }
991 
992         break;
993     }
994 
995     return 1;
996 }
997 
get_next_token(struct rfc5322dtbuf * buf,char ** str,int * len)998 static int get_next_token(struct rfc5322dtbuf *buf, char **str, int *len)
999 {
1000     int c, ret = 1;
1001     long ch;
1002     static char cache[RFC5322_DATETIME_MAX];
1003 
1004     memset(cache, 1, RFC5322_DATETIME_MAX);
1005 
1006     c = get_current_char(buf);
1007     if (c == EOB) {
1008         ret = 0;
1009         goto failed;
1010     }
1011 
1012     *len = 0;
1013     for (;;) {
1014         if (rfc5322_special[c] || rfc5322_separators[c])
1015             break;
1016 
1017         ch = rfc5322_usascii_charset[c + 1];
1018         if (!(ch & (Alpha | Digit)))
1019             break;
1020 
1021         if (*len >= RFC5322_DATETIME_MAX)
1022             break;
1023 
1024         cache[*len] = c;
1025         *len += 1;
1026 
1027         c = get_next_char(buf);
1028         if (c == EOB) {
1029             ret = 0;
1030             break;
1031         }
1032     }
1033 
1034  failed:
1035     *str = cache;
1036 
1037     return ret;
1038 }
1039 
to_int(char * str,int len)1040 static inline int to_int(char *str, int len)
1041 {
1042     int i, num = 0;
1043 
1044     for (i = 0; i < len; i++) {
1045         if (rfc5322_usascii_charset[str[i] + 1] & Digit)
1046             num = num * 10 + (str[i] - '0');
1047         else {
1048             num = -9999;
1049             break;
1050         }
1051     }
1052 
1053     return num;
1054 }
1055 
to_upper(char ch)1056 static inline int to_upper(char ch)
1057 {
1058     if (rfc5322_usascii_charset[ch + 1] & LAlpha)
1059         ch =  ch - 32;
1060 
1061     return ch;
1062 }
1063 
to_lower(char ch)1064 static inline int to_lower(char ch)
1065 {
1066     if (rfc5322_usascii_charset[ch + 1] & UAlpha)
1067         ch = ch + 32;
1068 
1069     return ch;
1070 }
1071 
compute_tzoffset(char * str,int len,int sign)1072 static int compute_tzoffset(char *str, int len, int sign)
1073 {
1074     int offset = 0;
1075 
1076     if (len == 1) {         /* Military timezone */
1077         int ch;
1078         ch = to_upper(str[0]);
1079         if (ch < 'J')
1080             return (str[0] - 'A' + 1) * 60;
1081         if (ch == 'J')
1082             return 0;
1083         if (ch <= 'M')
1084             return (str[0] - 'A') * 60;;
1085         if (ch < 'Z')
1086             return ('M' - str[0]) * 60;
1087 
1088         return 0;
1089     }
1090 
1091     if (len == 2 &&
1092         to_upper(str[0]) == 'U' &&
1093         to_upper(str[1]) == 'T') {         /* Universal Time zone (UT) */
1094         return 0;
1095     }
1096 
1097     if (len == 3) {
1098         char *p;
1099 
1100         if (to_upper(str[2]) != 'T')
1101             return 0;
1102 
1103         p = strchr("AECMPYHB", to_upper(str[0]));
1104         if (!p)
1105             return 0;
1106         offset = (strlen(p) - 12) *  60;
1107 
1108         if (to_upper(str[1]) == 'D')
1109             return offset + 60;
1110         if (to_upper(str[1]) == 'S')
1111             return offset;
1112     }
1113 
1114     if (len == 4) {         /* The number timezone offset */
1115         int i;
1116 
1117         for (i = 0; i < len; i++) {
1118             if (!(rfc5322_usascii_charset[str[i] + 1] & Digit))
1119                 return 0;
1120         }
1121 
1122         offset = ((str[0] - '0') * 10 + (str[1] - '0')) * 60 +
1123             (str[2] - '0') * 10 +
1124             (str[3] - '0');
1125 
1126         return (sign == '+') ? offset : -offset;
1127     }
1128 
1129     return 0;
1130 }
1131 
1132 /*
1133  *  Date Format as per https://tools.ietf.org/html/rfc5322#section-3.3:
1134  *
1135  * date-time = [ ([FWS] day-name) "," ]
1136  *             ([FWS] 1*2DIGIT FWS)
1137  *             month
1138  *             (FWS 4*DIGIT FWS)
1139  *             2DIGIT ":" 2DIGIT [ ":" 2DIGIT ]
1140  *             (FWS ( "+" / "-" ) 4DIGIT)
1141  *             [CFWS]
1142  *
1143  * day-name = "Mon" / "Tue" / "Wed" / "Thu" / "Fri" / "Sat" / "Sun"
1144  * month = "Jan" / "Feb" / "Mar" / "Apr" / "May" / "Jun" / "Jul" / "Aug" /
1145  *         "Sep" / "Oct" / "Nov" / "Dec"
1146  *
1147  */
1148 
tokenise_str_and_create_tm(struct rfc5322dtbuf * buf,struct tm * tm,long * tz_offset,enum datetime_parse_mode mode)1149 static int tokenise_str_and_create_tm(struct rfc5322dtbuf *buf, struct tm *tm,
1150                                       long *tz_offset,
1151                                       enum datetime_parse_mode mode)
1152 {
1153     long ch;
1154     int c, i, len;
1155     char *str_token = NULL;
1156 
1157 
1158     /* Skip leading WS, if any */
1159     skip_ws(buf, 0);
1160 
1161     c = get_current_char(buf);
1162     if (c == EOB)
1163         goto failed;
1164 
1165     ch = rfc5322_usascii_charset[c + 1];
1166     if (ch & Alpha) {       /* Most likely a weekday at the start. */
1167         if (!get_next_token(buf, &str_token, &len))
1168             goto failed;
1169 
1170         /* We might have a weekday token here */
1171         if (len != 3)
1172             goto failed;
1173 
1174         /* Determine week day */
1175         int i ;
1176         for (i = 0; i < 7; i++) {
1177             if (!strncasecmp(wday[i], str_token, len)) {
1178                 tm->tm_wday = i;
1179                 break;
1180             }
1181         }
1182 
1183         /* The weekday is followed by a ',', consume that. */
1184         if (get_current_char(buf) == ',')
1185             get_next_char(buf);
1186         else
1187             goto failed;
1188 
1189         skip_ws(buf, 0);
1190     }
1191 
1192     /** DATE **/
1193     /* date (1 or 2 digits) */
1194     if (!get_next_token(buf, &str_token, &len))
1195         goto failed;
1196 
1197     if (len < 1 || len > 2 ||
1198         !(rfc5322_usascii_charset[str_token[0] + 1] & Digit))
1199         goto failed;
1200 
1201     tm->tm_mday = to_int(str_token, len);
1202     if (tm->tm_mday == -9999)
1203         goto failed;
1204 
1205     /* the separator here is either '-' or FWS */
1206     c = get_next_char(buf);
1207     if (rfc5322_special[c])
1208         skip_ws(buf, 0);
1209 
1210     /* month name */
1211     if (!get_next_token(buf, &str_token, &len) ||
1212         len != 3 ||
1213         !(rfc5322_usascii_charset[str_token[0] + 1] & Alpha))
1214         goto failed;
1215 
1216     str_token[0] = to_upper(str_token[0]);
1217     str_token[1] = to_lower(str_token[1]);
1218     str_token[2] = to_lower(str_token[2]);
1219     for (i = 0; i < 12; i++) {
1220         if (memcmp(monthname[i], str_token, 3) == 0) {
1221             tm->tm_mon = i;
1222             break;
1223         }
1224     }
1225     if (i == 12)
1226         goto failed;
1227 
1228     /* the separator here is either '-' or FWS */
1229     c = get_next_char(buf);
1230     if (rfc5322_special[c])
1231         skip_ws(buf, 0);
1232 
1233     /* year 2, 4 or >4 digits */
1234     if (!get_next_token(buf, &str_token, &len))
1235         goto failed;
1236 
1237     tm->tm_year = to_int(str_token, len);
1238     if (tm->tm_year == -9999)
1239         goto failed;
1240 
1241     if (len == 2) {
1242         /* A 2 digit year */
1243         if (tm->tm_year < 70)
1244             tm->tm_year += 100;
1245     } else {
1246         if (tm->tm_year < 1900)
1247             goto failed;
1248         tm->tm_year -= 1900;
1249     }
1250 
1251     if (mode == DATETIME_DATE_ONLY) {
1252         *tz_offset = 0;
1253         goto done;
1254     }
1255 
1256     /** TIME **/
1257     skip_ws(buf, 0);
1258     /* hour */
1259     if (!get_next_token(buf, &str_token, &len))
1260         goto failed;
1261 
1262     if (len < 1 || len > 2 ||
1263         !(rfc5322_usascii_charset[str_token[0] + 1] & Digit))
1264         goto failed;
1265 
1266     tm->tm_hour = to_int(str_token, len);
1267     if (tm->tm_hour == -9999)
1268         goto failed;
1269 
1270     /* minutes */
1271     if (get_current_char(buf) == ':' ||
1272         get_current_char(buf) == '.')
1273         get_next_char(buf); /* Consume ':'/'.' */
1274     else
1275         goto failed;    /* Something is broken */
1276 
1277     if (!get_next_token(buf, &str_token, &len))
1278         goto failed;
1279 
1280     if (len < 1 || len > 2 ||
1281         !(rfc5322_usascii_charset[str_token[0] + 1] & Digit))
1282         goto failed;
1283 
1284     tm->tm_min = to_int(str_token, len);
1285     if (tm->tm_min == -9999)
1286         goto failed;
1287 
1288 
1289     /* seconds[optional] */
1290     if (get_current_char(buf) == ':' ||
1291         get_current_char(buf) == '.') {
1292         get_next_char(buf); /* Consume ':'/'.' */
1293 
1294         if (!get_next_token(buf, &str_token, &len))
1295             goto failed;
1296 
1297         if (len < 1 || len > 2 ||
1298             !(rfc5322_usascii_charset[str_token[0] + 1] & Digit))
1299             goto failed;
1300 
1301         tm->tm_sec = to_int(str_token, len);
1302         if (tm->tm_sec == -9999)
1303             goto failed;
1304 
1305     }
1306 
1307     /* timezone */
1308     skip_ws(buf, 0);
1309     c = get_current_char(buf);
1310     if (c == '+' || c == '-') {    /* the '+' or '-' in the timezone */
1311         get_next_char(buf);        /* consume '+' or '-' */
1312     }
1313 
1314     if (!get_next_token(buf, &str_token, &len)) {
1315         *tz_offset = 0;
1316     } else {
1317         *tz_offset = compute_tzoffset(str_token, len, c);
1318     }
1319 
1320  done:
1321     /* dst */
1322     tm->tm_isdst = -1;
1323     *tz_offset *= 60;
1324     return buf->offset;
1325 
1326  failed:
1327     return -1;
1328 }
1329 
1330 
1331 /*
1332  * time_from_rfc5322()
1333  * This is meant to be the replacement function for time_from_rfc822() and
1334  * time_from_rfc3501() functions.
1335  *
1336  * The argument `mode` is set to `DATETIME_DATE_ONLY` when we don't want to
1337  * parse the time and timezone parts of the RFC 5322 date-time string. This is
1338  * a hack designed to support the Cyrus implementation of the IMAP SEARCH
1339  * command.
1340  * For all other cases, the mode should be set to `DATETIME_FULL`.
1341  *
1342  * Returns: Number of characters consumed from @s on success,
1343  *          or -1 on error.
1344  */
time_from_rfc5322(const char * s,time_t * date,enum datetime_parse_mode mode)1345 EXPORTED int time_from_rfc5322(const char *s, time_t *date,
1346                                enum datetime_parse_mode mode)
1347 {
1348     struct rfc5322dtbuf buf;
1349     struct tm tm;
1350     time_t tmp_time;
1351     long tzone_offset = 0;
1352 
1353     if (!s)
1354         goto baddate;
1355 
1356     memset(&tm, 0, sizeof(struct tm));
1357     *date = 0;
1358 
1359     buf.str = s;
1360     buf.len = strlen(s);
1361     buf.offset = 0;
1362 
1363     if (tokenise_str_and_create_tm(&buf, &tm, &tzone_offset, mode) == -1)
1364         goto baddate;
1365 
1366     if (mode == DATETIME_DATE_ONLY)
1367         tmp_time = mktime(&tm);
1368     else
1369         tmp_time = mkgmtime(&tm);
1370 
1371     if (tmp_time == -1)
1372         goto baddate;
1373 
1374     *date = tmp_time - tzone_offset;
1375 
1376     return buf.offset;
1377 
1378  baddate:
1379     return -1;
1380 }
1381 
1382 /*
1383  * Parse a RFC 5322 timestamp into an offset time.
1384  * Wrong week days are ignored.
1385  *
1386  * Returns: Number of characters consumed from @s on success,
1387  *          or -1 on error.
1388  */
offsettime_from_rfc5322(const char * s,struct offsettime * t,enum datetime_parse_mode mode)1389 EXPORTED int offsettime_from_rfc5322(const char *s, struct offsettime *t,
1390                                      enum datetime_parse_mode mode)
1391 {
1392     struct rfc5322dtbuf buf;
1393 
1394     if (!s)
1395         goto baddate;
1396 
1397     memset(t, 0, sizeof(struct offsettime));
1398 
1399     buf.str = s;
1400     buf.len = strlen(s);
1401     buf.offset = 0;
1402 
1403     if (tokenise_str_and_create_tm(&buf, &t->tm, &t->tm_off, mode) == -1)
1404         goto baddate;
1405 
1406     if (!offsettime_normalize(t))
1407         goto baddate;
1408 
1409     return buf.offset;
1410 
1411  baddate:
1412     return -1;
1413 }
1414 
1415 /*
1416  * time_to_rfc5322()
1417  * Convert a time_t date to an IMAP-style date.
1418  * `buf` which is the buffer this function is going to write into, needs to
1419  * be atleast RFC5322_DATETIME_MAX (32), if not more.
1420  *
1421  */
time_to_rfc5322(time_t date,char * buf,size_t len)1422 EXPORTED int time_to_rfc5322(time_t date, char *buf, size_t len)
1423 {
1424     struct tm *tm = localtime(&date);
1425     long gmtoff = gmtoff_of(tm, date);
1426     int gmtnegative = 0;
1427 
1428     if (gmtoff < 0) {
1429         gmtoff = -gmtoff;
1430         gmtnegative = 1;
1431     }
1432 
1433     gmtoff /= 60;
1434 
1435     return snprintf(buf, len,
1436              "%s, %02d %s %04d %02d:%02d:%02d %c%02lu%02lu",
1437              wday[tm->tm_wday],
1438              tm->tm_mday, monthname[tm->tm_mon], tm->tm_year + 1900,
1439              tm->tm_hour, tm->tm_min, tm->tm_sec,
1440              gmtnegative ? '-' : '+', gmtoff/60, gmtoff%60);
1441 }
1442 
offsettime_to_rfc5322(struct offsettime * t,char * buf,size_t len)1443 EXPORTED int offsettime_to_rfc5322(struct offsettime *t, char *buf, size_t len)
1444 {
1445     long gmtoff = t->tm_off;
1446     int gmtnegative = 0;
1447 
1448     if (gmtoff < 0) {
1449         gmtoff = -gmtoff;
1450         gmtnegative = 1;
1451     }
1452 
1453     gmtoff /= 60;
1454 
1455     return snprintf(buf, len,
1456              "%s, %02d %s %04d %02d:%02d:%02d %c%02lu%02lu",
1457              wday[t->tm.tm_wday],
1458              t->tm.tm_mday, monthname[t->tm.tm_mon], t->tm.tm_year + 1900,
1459              t->tm.tm_hour, t->tm.tm_min, t->tm.tm_sec,
1460              gmtnegative ? '-' : '+', gmtoff/60, gmtoff%60);
1461 }
1462