/src/dovecot/src/lib/iso8601-date.c
Line | Count | Source |
1 | | /* Copyright (c) Dovecot authors, see top-level COPYING file */ |
2 | | |
3 | | #include "lib.h" |
4 | | #include "utc-offset.h" |
5 | | #include "utc-mktime.h" |
6 | | #include "iso8601-date.h" |
7 | | |
8 | | #include <ctype.h> |
9 | | |
10 | | /* RFC3339/ISO8601 date-time syntax |
11 | | |
12 | | date-fullyear = 4DIGIT |
13 | | date-month = 2DIGIT ; 01-12 |
14 | | date-mday = 2DIGIT ; 01-28, 01-29, 01-30, 01-31 based on |
15 | | ; month/year |
16 | | time-hour = 2DIGIT ; 00-23 |
17 | | time-minute = 2DIGIT ; 00-59 |
18 | | time-second = 2DIGIT ; 00-58, 00-59, 00-60 based on leap second |
19 | | ; rules |
20 | | time-secfrac = "." 1*DIGIT |
21 | | time-numoffset = ("+" / "-") time-hour ":" time-minute |
22 | | time-offset = "Z" / time-numoffset |
23 | | |
24 | | partial-time = time-hour ":" time-minute ":" time-second [time-secfrac] |
25 | | full-date = date-fullyear "-" date-month "-" date-mday |
26 | | full-time = partial-time time-offset |
27 | | |
28 | | date-time = full-date "T" full-time |
29 | | */ |
30 | | |
31 | | struct iso8601_date_parser { |
32 | | const unsigned char *cur, *end; |
33 | | |
34 | | struct tm tm; |
35 | | int timezone_offset; |
36 | | }; |
37 | | |
38 | | static inline int |
39 | | iso8601_date_parse_number(struct iso8601_date_parser *parser, |
40 | | int digits, int *number_r) |
41 | 0 | { |
42 | 0 | int i; |
43 | |
|
44 | 0 | if (parser->cur >= parser->end || !i_isdigit(parser->cur[0])) |
45 | 0 | return 0; |
46 | | |
47 | 0 | *number_r = parser->cur[0] - '0'; |
48 | 0 | parser->cur++; |
49 | |
|
50 | 0 | for (i=0; i < digits-1; i++) { |
51 | 0 | if (parser->cur >= parser->end || !i_isdigit(parser->cur[0])) |
52 | 0 | return -1; |
53 | 0 | *number_r = ((*number_r) * 10) + parser->cur[0] - '0'; |
54 | 0 | parser->cur++; |
55 | 0 | } |
56 | 0 | return 1; |
57 | 0 | } |
58 | | |
59 | | static int |
60 | | iso8601_date_parse_secfrac(struct iso8601_date_parser *parser) |
61 | 0 | { |
62 | | /* time-secfrac = "." 1*DIGIT |
63 | | |
64 | | NOTE: Currently not applied anywhere, so fraction is just skipped. |
65 | | */ |
66 | | |
67 | | /* "." */ |
68 | 0 | if (parser->cur >= parser->end || parser->cur[0] != '.') |
69 | 0 | return 0; |
70 | 0 | parser->cur++; |
71 | | |
72 | | /* 1DIGIT */ |
73 | 0 | if (parser->cur >= parser->end || !i_isdigit(parser->cur[0])) |
74 | 0 | return -1; |
75 | 0 | parser->cur++; |
76 | | |
77 | | /* *DIGIT */ |
78 | 0 | while (parser->cur < parser->end && i_isdigit(parser->cur[0])) |
79 | 0 | parser->cur++; |
80 | 0 | return 1; |
81 | 0 | } |
82 | | |
83 | | static int is08601_date_parse_time_offset(struct iso8601_date_parser *parser) |
84 | 0 | { |
85 | 0 | int tz_sign = 1, tz_hour = 0, tz_min = 0; |
86 | | |
87 | | /* time-offset = "Z" / time-numoffset |
88 | | time-numoffset = ("+" / "-") time-hour ":" time-minute |
89 | | time-hour = 2DIGIT ; 00-23 |
90 | | time-minute = 2DIGIT ; 00-59 |
91 | | */ |
92 | |
|
93 | 0 | if (parser->cur >= parser->end) |
94 | 0 | return 0; |
95 | | |
96 | | /* time-offset = "Z" / time-numoffset */ |
97 | 0 | switch (parser->cur[0]) { |
98 | 0 | case '-': |
99 | 0 | tz_sign = -1; |
100 | | /* fall through */ |
101 | 0 | case '+': |
102 | 0 | parser->cur++; |
103 | | |
104 | | /* time-hour = 2DIGIT */ |
105 | 0 | if (iso8601_date_parse_number(parser, 2, &tz_hour) <= 0) |
106 | 0 | return -1; |
107 | 0 | if (tz_hour > 23) |
108 | 0 | return -1; |
109 | | |
110 | | /* ":" */ |
111 | 0 | if (parser->cur >= parser->end || parser->cur[0] != ':') |
112 | 0 | return -1; |
113 | 0 | parser->cur++; |
114 | | |
115 | | /* time-minute = 2DIGIT */ |
116 | 0 | if (iso8601_date_parse_number(parser, 2, &tz_min) <= 0) |
117 | 0 | return -1; |
118 | 0 | if (tz_min > 59) |
119 | 0 | return -1; |
120 | 0 | break; |
121 | 0 | case 'Z': |
122 | 0 | case 'z': |
123 | 0 | parser->cur++; |
124 | 0 | break; |
125 | 0 | default: |
126 | 0 | return -1; |
127 | 0 | } |
128 | | |
129 | 0 | parser->timezone_offset = tz_sign*(tz_hour*60 + tz_min); |
130 | 0 | return 1; |
131 | 0 | } |
132 | | |
133 | | static int is08601_date_parse_full_time(struct iso8601_date_parser *parser) |
134 | 0 | { |
135 | | /* full-time = partial-time time-offset |
136 | | partial-time = time-hour ":" time-minute ":" time-second [time-secfrac] |
137 | | time-hour = 2DIGIT ; 00-23 |
138 | | time-minute = 2DIGIT ; 00-59 |
139 | | time-second = 2DIGIT ; 00-58, 00-59, 00-60 based on leap second |
140 | | ; rules |
141 | | */ |
142 | | |
143 | | /* time-hour = 2DIGIT */ |
144 | 0 | if (iso8601_date_parse_number(parser, 2, &parser->tm.tm_hour) <= 0) |
145 | 0 | return -1; |
146 | | |
147 | | /* ":" */ |
148 | 0 | if (parser->cur >= parser->end || parser->cur[0] != ':') |
149 | 0 | return -1; |
150 | 0 | parser->cur++; |
151 | | |
152 | | /* time-minute = 2DIGIT */ |
153 | 0 | if (iso8601_date_parse_number(parser, 2, &parser->tm.tm_min) <= 0) |
154 | 0 | return -1; |
155 | | |
156 | | /* ":" */ |
157 | 0 | if (parser->cur >= parser->end || parser->cur[0] != ':') |
158 | 0 | return -1; |
159 | 0 | parser->cur++; |
160 | | |
161 | | /* time-second = 2DIGIT */ |
162 | 0 | if (iso8601_date_parse_number(parser, 2, &parser->tm.tm_sec) <= 0) |
163 | 0 | return -1; |
164 | | |
165 | | /* [time-secfrac] */ |
166 | 0 | if (iso8601_date_parse_secfrac(parser) < 0) |
167 | 0 | return -1; |
168 | | |
169 | | /* time-offset */ |
170 | 0 | if (is08601_date_parse_time_offset(parser) <= 0) |
171 | 0 | return -1; |
172 | 0 | return 1; |
173 | 0 | } |
174 | | |
175 | | static int is08601_date_parse_full_date(struct iso8601_date_parser *parser) |
176 | 0 | { |
177 | | /* full-date = date-fullyear "-" date-month "-" date-mday |
178 | | date-fullyear = 4DIGIT |
179 | | date-month = 2DIGIT ; 01-12 |
180 | | date-mday = 2DIGIT ; 01-28, 01-29, 01-30, 01-31 based on |
181 | | ; month/year |
182 | | */ |
183 | | |
184 | | /* date-fullyear = 4DIGIT */ |
185 | 0 | if (iso8601_date_parse_number(parser, 4, &parser->tm.tm_year) <= 0) |
186 | 0 | return -1; |
187 | 0 | if (parser->tm.tm_year < 1900) |
188 | 0 | return -1; |
189 | 0 | parser->tm.tm_year -= 1900; |
190 | | |
191 | | /* "-" */ |
192 | 0 | if (parser->cur >= parser->end || parser->cur[0] != '-') |
193 | 0 | return -1; |
194 | 0 | parser->cur++; |
195 | | |
196 | | /* date-month = 2DIGIT */ |
197 | 0 | if (iso8601_date_parse_number(parser, 2, &parser->tm.tm_mon) <= 0) |
198 | 0 | return -1; |
199 | 0 | parser->tm.tm_mon -= 1; |
200 | | |
201 | | /* "-" */ |
202 | 0 | if (parser->cur >= parser->end || parser->cur[0] != '-') |
203 | 0 | return -1; |
204 | 0 | parser->cur++; |
205 | | |
206 | | /* time-second = 2DIGIT */ |
207 | 0 | if (iso8601_date_parse_number(parser, 2, &parser->tm.tm_mday) <= 0) |
208 | 0 | return -1; |
209 | 0 | return 1; |
210 | 0 | } |
211 | | |
212 | | static int iso8601_date_parse_date_time(struct iso8601_date_parser *parser) |
213 | 0 | { |
214 | | /* date-time = full-date "T" full-time */ |
215 | | |
216 | | /* full-date */ |
217 | 0 | if (is08601_date_parse_full_date(parser) <= 0) |
218 | 0 | return -1; |
219 | | |
220 | | /* "T" */ |
221 | 0 | if (parser->cur >= parser->end || |
222 | 0 | (parser->cur[0] != 'T' && parser->cur[0] != 't')) |
223 | 0 | return -1; |
224 | 0 | parser->cur++; |
225 | | |
226 | | /* full-time */ |
227 | 0 | if (is08601_date_parse_full_time(parser) <= 0) |
228 | 0 | return -1; |
229 | | |
230 | 0 | if (parser->cur != parser->end) |
231 | 0 | return -1; |
232 | 0 | return 1; |
233 | 0 | } |
234 | | |
235 | | static bool |
236 | | iso8601_date_do_parse(const unsigned char *data, size_t size, struct tm *tm_r, |
237 | | time_t *timestamp_r, int *timezone_offset_r) |
238 | 0 | { |
239 | 0 | struct iso8601_date_parser parser; |
240 | 0 | time_t timestamp; |
241 | |
|
242 | 0 | i_zero(&parser); |
243 | 0 | parser.cur = data; |
244 | 0 | parser.end = data + size; |
245 | |
|
246 | 0 | if (iso8601_date_parse_date_time(&parser) <= 0) |
247 | 0 | return FALSE; |
248 | | |
249 | 0 | parser.tm.tm_isdst = -1; |
250 | 0 | timestamp = utc_mktime(&parser.tm); |
251 | 0 | if (timestamp == (time_t)-1) |
252 | 0 | return FALSE; |
253 | | |
254 | 0 | *timezone_offset_r = parser.timezone_offset; |
255 | 0 | *tm_r = parser.tm; |
256 | 0 | *timestamp_r = timestamp - parser.timezone_offset * 60; |
257 | 0 | return TRUE; |
258 | 0 | } |
259 | | |
260 | | bool iso8601_date_parse(const unsigned char *data, size_t size, |
261 | | time_t *timestamp_r, int *timezone_offset_r) |
262 | 0 | { |
263 | 0 | struct tm tm; |
264 | |
|
265 | 0 | return iso8601_date_do_parse(data, size, &tm, |
266 | 0 | timestamp_r, timezone_offset_r); |
267 | 0 | } |
268 | | |
269 | | bool iso8601_date_parse_tm(const unsigned char *data, size_t size, |
270 | | struct tm *tm_r, int *timezone_offset_r) |
271 | 0 | { |
272 | 0 | time_t timestamp; |
273 | |
|
274 | 0 | return iso8601_date_do_parse(data, size, tm_r, |
275 | 0 | ×tamp, timezone_offset_r); |
276 | 0 | } |
277 | | |
278 | | const char *iso8601_date_create_tm(struct tm *tm, int timezone_offset) |
279 | 0 | { |
280 | 0 | const char *time_offset; |
281 | |
|
282 | 0 | if (timezone_offset == INT_MAX) |
283 | 0 | time_offset = "Z"; |
284 | 0 | else { |
285 | 0 | char sign = '+'; |
286 | 0 | if (timezone_offset < 0) { |
287 | 0 | timezone_offset = -timezone_offset; |
288 | 0 | sign = '-'; |
289 | 0 | } |
290 | 0 | time_offset = t_strdup_printf("%c%02d:%02d", sign, |
291 | 0 | timezone_offset / 60, |
292 | 0 | timezone_offset % 60); |
293 | 0 | } |
294 | |
|
295 | 0 | return t_strdup_printf("%04d-%02d-%02dT%02d:%02d:%02d%s", |
296 | 0 | tm->tm_year + 1900, tm->tm_mon+1, tm->tm_mday, |
297 | 0 | tm->tm_hour, tm->tm_min, tm->tm_sec, time_offset); |
298 | 0 | } |
299 | | |
300 | | const char *iso8601_date_create(time_t timestamp) |
301 | 0 | { |
302 | 0 | struct tm *tm; |
303 | 0 | int timezone_offset; |
304 | |
|
305 | 0 | tm = localtime(×tamp); |
306 | 0 | timezone_offset = utc_offset(tm, timestamp); |
307 | |
|
308 | 0 | return iso8601_date_create_tm(tm, timezone_offset); |
309 | 0 | } |