/
ext-date-common.c
593 lines (466 loc) · 14.2 KB
/
ext-date-common.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
/* Copyright (c) 2002-2016 Pigeonhole authors, see the included COPYING file
*/
#include "lib.h"
#include "utc-offset.h"
#include "str.h"
#include "iso8601-date.h"
#include "message-date.h"
#include "sieve-common.h"
#include "sieve-stringlist.h"
#include "sieve-code.h"
#include "sieve-interpreter.h"
#include "sieve-message.h"
#include "ext-date-common.h"
#include <time.h>
#include <ctype.h>
struct ext_date_context {
time_t current_date;
int zone_offset;
};
/*
* Runtime initialization
*/
static int ext_date_runtime_init
(const struct sieve_extension *ext,
const struct sieve_runtime_env *renv,
void *context ATTR_UNUSED, bool deferred ATTR_UNUSED)
{
struct ext_date_context *dctx;
pool_t pool;
struct timeval msg_time;
time_t current_date;
struct tm *tm;
int zone_offset;
/* Get current time at instance main script is started */
sieve_message_context_time(renv->msgctx, &msg_time);
current_date = msg_time.tv_sec;
tm = localtime(¤t_date);
zone_offset = utc_offset(tm, current_date);
/* Create context */
pool = sieve_message_context_pool(renv->msgctx);
dctx = p_new(pool, struct ext_date_context, 1);
dctx->current_date = current_date;
dctx->zone_offset = zone_offset;
sieve_message_context_extension_set
(renv->msgctx, ext, (void *) dctx);
return SIEVE_EXEC_OK;
}
static struct sieve_interpreter_extension
date_interpreter_extension = {
.ext_def = &date_extension,
.run = ext_date_runtime_init
};
bool ext_date_interpreter_load
(const struct sieve_extension *ext, const struct sieve_runtime_env *renv,
sieve_size_t *address ATTR_UNUSED)
{
/* Register runtime hook to obtain stript start timestamp */
if ( renv->msgctx == NULL ||
sieve_message_context_extension_get(renv->msgctx, ext) == NULL ) {
sieve_interpreter_extension_register
(renv->interp, ext, &date_interpreter_extension, NULL);
}
return TRUE;
}
/*
* Zone string
*/
bool ext_date_parse_timezone
(const char *zone, int *zone_offset_r)
{
const unsigned char *str = (const unsigned char *) zone;
size_t len = strlen(zone);
if (len == 5 && (*str == '+' || *str == '-')) {
int offset;
if (!i_isdigit(str[1]) || !i_isdigit(str[2]) ||
!i_isdigit(str[3]) || !i_isdigit(str[4]))
return FALSE;
offset = ((str[1]-'0') * 10 + (str[2]-'0')) * 60 +
(str[3]-'0') * 10 + (str[4]-'0');
if ( zone_offset_r != NULL )
*zone_offset_r = *str == '+' ? offset : -offset;
return TRUE;
}
return FALSE;
}
/*
* Current date
*/
time_t ext_date_get_current_date
(const struct sieve_runtime_env *renv, int *zone_offset_r)
{
const struct sieve_extension *this_ext = renv->oprtn->ext;
struct ext_date_context *dctx = (struct ext_date_context *)
sieve_message_context_extension_get(renv->msgctx, this_ext);
if ( dctx == NULL ) {
ext_date_runtime_init(this_ext, renv, NULL, FALSE);
dctx = (struct ext_date_context *)
sieve_message_context_extension_get(renv->msgctx, this_ext);
i_assert(dctx != NULL);
}
/* Read script start timestamp from message context */
if ( zone_offset_r != NULL )
*zone_offset_r = dctx->zone_offset;
return dctx->current_date;
}
/*
* Date parts
*/
/* "year" => the year, "0000" .. "9999".
*/
static const char *ext_date_year_part_get(struct tm *tm, int zone_offset);
static const struct ext_date_part year_date_part = {
"year",
ext_date_year_part_get
};
/* "month" => the month, "01" .. "12".
*/
static const char *ext_date_month_part_get(struct tm *tm, int zone_offset);
static const struct ext_date_part month_date_part = {
"month",
ext_date_month_part_get
};
/* "day" => the day, "01" .. "31".
*/
static const char *ext_date_day_part_get(struct tm *tm, int zone_offset);
static const struct ext_date_part day_date_part = {
"day",
ext_date_day_part_get
};
/* "date" => the date in "yyyy-mm-dd" format.
*/
static const char *ext_date_date_part_get(struct tm *tm, int zone_offset);
static const struct ext_date_part date_date_part = {
"date",
ext_date_date_part_get
};
/* "julian" => the Modified Julian Day, that is, the date
* expressed as an integer number of days since
* 00:00 UTC on November 17, 1858 (using the Gregorian
* calendar). This corresponds to the regular
* Julian Day minus 2400000.5. Sample routines to
* convert to and from modified Julian dates are
* given in Appendix A.
*/
static const char *ext_date_julian_part_get(struct tm *tm, int zone_offset);
static const struct ext_date_part julian_date_part = {
"julian",
ext_date_julian_part_get
};
/* "hour" => the hour, "00" .. "23".
*/
static const char *ext_date_hour_part_get(struct tm *tm, int zone_offset);
static const struct ext_date_part hour_date_part = {
"hour",
ext_date_hour_part_get
};
/* "minute" => the minute, "00" .. "59".
*/
static const char *ext_date_minute_part_get(struct tm *tm, int zone_offset);
static const struct ext_date_part minute_date_part = {
"minute",
ext_date_minute_part_get
};
/* "second" => the second, "00" .. "60".
*/
static const char *ext_date_second_part_get(struct tm *tm, int zone_offset);
static const struct ext_date_part second_date_part = {
"second",
ext_date_second_part_get
};
/* "time" => the time in "hh:mm:ss" format.
*/
static const char *ext_date_time_part_get(struct tm *tm, int zone_offset);
static const struct ext_date_part time_date_part = {
"time",
ext_date_time_part_get
};
/* "iso8601" => the date and time in restricted ISO 8601 format.
*/
static const char *ext_date_iso8601_part_get(struct tm *tm, int zone_offset);
static const struct ext_date_part iso8601_date_part = {
"iso8601",
ext_date_iso8601_part_get
};
/* "std11" => the date and time in a format appropriate
* for use in a Date: header field [RFC2822].
*/
static const char *ext_date_std11_part_get(struct tm *tm, int zone_offset);
static const struct ext_date_part std11_date_part = {
"std11",
ext_date_std11_part_get
};
/* "zone" => the time zone in use. If the user specified a
* time zone with ":zone", "zone" will
* contain that value. If :originalzone is specified
* this value will be the original zone specified
* in the date-time value. If neither argument is
* specified the value will be the server's default
* time zone in offset format "+hhmm" or "-hhmm". An
* offset of 0 (Zulu) always has a positive sign.
*/
static const char *ext_date_zone_part_get(struct tm *tm, int zone_offset);
static const struct ext_date_part zone_date_part = {
"zone",
ext_date_zone_part_get
};
/* "weekday" => the day of the week expressed as an integer between
* "0" and "6". "0" is Sunday, "1" is Monday, etc.
*/
static const char *ext_date_weekday_part_get(struct tm *tm, int zone_offset);
static const struct ext_date_part weekday_date_part = {
"weekday",
ext_date_weekday_part_get
};
/*
* Date part extraction
*/
static const struct ext_date_part *date_parts[] = {
&year_date_part, &month_date_part, &day_date_part, &date_date_part,
&julian_date_part, &hour_date_part, &minute_date_part, &second_date_part,
&time_date_part, &iso8601_date_part, &std11_date_part, &zone_date_part,
&weekday_date_part
};
unsigned int date_parts_count = N_ELEMENTS(date_parts);
const struct ext_date_part *ext_date_part_find(const char *part)
{
unsigned int i;
for ( i = 0; i < date_parts_count; i++ ) {
if ( strcasecmp(date_parts[i]->identifier, part) == 0 ) {
return date_parts[i];
}
}
return NULL;
}
const char *ext_date_part_extract
(const struct ext_date_part *dpart, struct tm *tm, int zone_offset)
{
if ( dpart == NULL || dpart->get_string == NULL )
return NULL;
return dpart->get_string(tm, zone_offset);
}
/*
* Date part implementations
*/
static const char *month_names[] = {
"Jan", "Feb", "Mar", "Apr", "May", "Jun",
"Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
};
static const char *weekday_names[] = {
"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"
};
static const char *ext_date_year_part_get
(struct tm *tm, int zone_offset ATTR_UNUSED)
{
return t_strdup_printf("%04d", tm->tm_year + 1900);
}
static const char *ext_date_month_part_get
(struct tm *tm, int zone_offset ATTR_UNUSED)
{
return t_strdup_printf("%02d", tm->tm_mon + 1);
}
static const char *ext_date_day_part_get
(struct tm *tm, int zone_offset ATTR_UNUSED)
{
return t_strdup_printf("%02d", tm->tm_mday);
}
static const char *ext_date_date_part_get
(struct tm *tm, int zone_offset ATTR_UNUSED)
{
return t_strdup_printf("%04d-%02d-%02d",
tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
}
static const char *ext_date_julian_part_get
(struct tm *tm, int zone_offset ATTR_UNUSED)
{
int year = tm->tm_year+1900;
int month = tm->tm_mon+1;
int day = tm->tm_mday;
int c, ya, jd;
/* Modified from RFC 5260 Appendix A (refer to Errata) */
if ( month > 2 )
month -= 3;
else {
month += 9;
year--;
}
c = year / 100;
ya = year - c * 100;
jd = c * 146097 / 4 + ya * 1461 / 4 + (month * 153 + 2) / 5 + day + 1721119;
return t_strdup_printf("%d", jd - 2400001);
}
static const char *ext_date_hour_part_get
(struct tm *tm, int zone_offset ATTR_UNUSED)
{
return t_strdup_printf("%02d", tm->tm_hour);
}
static const char *ext_date_minute_part_get
(struct tm *tm, int zone_offset ATTR_UNUSED)
{
return t_strdup_printf("%02d", tm->tm_min);
}
static const char *ext_date_second_part_get
(struct tm *tm, int zone_offset ATTR_UNUSED)
{
return t_strdup_printf("%02d", tm->tm_sec);
}
static const char *ext_date_time_part_get
(struct tm *tm, int zone_offset ATTR_UNUSED)
{
return t_strdup_printf("%02d:%02d:%02d", tm->tm_hour, tm->tm_min, tm->tm_sec);
}
static const char *ext_date_iso8601_part_get
(struct tm *tm, int zone_offset)
{
/* From RFC: `The restricted ISO 8601 format is specified by the date-time
* ABNF production given in [RFC3339], Section 5.6, with the added
* restrictions that the letters "T" and "Z" MUST be in upper case, and
* a time zone offset of zero MUST be represented by "Z" and not "+00:00".
*/
if ( zone_offset == 0 )
zone_offset = INT_MAX;
return iso8601_date_create_tm(tm, zone_offset);
}
static const char *ext_date_std11_part_get
(struct tm *tm, int zone_offset)
{
return t_strdup_printf("%s, %02d %s %04d %02d:%02d:%02d %s",
weekday_names[tm->tm_wday],
tm->tm_mday,
month_names[tm->tm_mon],
tm->tm_year+1900,
tm->tm_hour, tm->tm_min, tm->tm_sec,
ext_date_zone_part_get(tm, zone_offset));
}
static const char *ext_date_zone_part_get
(struct tm *tm ATTR_UNUSED, int zone_offset)
{
bool negative;
int offset = zone_offset;
if (zone_offset >= 0)
negative = FALSE;
else {
negative = TRUE;
offset = -offset;
}
return t_strdup_printf
("%c%02d%02d", negative ? '-' : '+', offset / 60, offset % 60);
}
static const char *ext_date_weekday_part_get
(struct tm *tm, int zone_offset ATTR_UNUSED)
{
return t_strdup_printf("%d", tm->tm_wday);
}
/*
* Date stringlist
*/
/* Forward declarations */
static int ext_date_stringlist_next_item
(struct sieve_stringlist *_strlist, string_t **str_r);
static void ext_date_stringlist_reset
(struct sieve_stringlist *_strlist);
/* Stringlist object */
struct ext_date_stringlist {
struct sieve_stringlist strlist;
struct sieve_stringlist *field_values;
int time_zone;
const struct ext_date_part *date_part;
time_t local_time;
int local_zone;
unsigned int read:1;
};
struct sieve_stringlist *ext_date_stringlist_create
(const struct sieve_runtime_env *renv, struct sieve_stringlist *field_values,
int time_zone, const struct ext_date_part *dpart)
{
struct ext_date_stringlist *strlist;
strlist = t_new(struct ext_date_stringlist, 1);
strlist->strlist.runenv = renv;
strlist->strlist.exec_status = SIEVE_EXEC_OK;
strlist->strlist.next_item = ext_date_stringlist_next_item;
strlist->strlist.reset = ext_date_stringlist_reset;
strlist->field_values = field_values;
strlist->time_zone = time_zone;
strlist->date_part = dpart;
strlist->local_time = ext_date_get_current_date(renv, &strlist->local_zone);
return &strlist->strlist;
}
/* Stringlist implementation */
static int ext_date_stringlist_next_item
(struct sieve_stringlist *_strlist, string_t **str_r)
{
struct ext_date_stringlist *strlist =
(struct ext_date_stringlist *) _strlist;
bool got_date = FALSE;
time_t date_value;
const char *part_value = NULL;
int original_zone;
/* Check whether the item was already read */
if ( strlist->read ) return 0;
if ( strlist->field_values != NULL ) {
string_t *hdr_item;
const char *header_value, *date_string;
int ret;
/* Use header field value */
/* Read first */
if ( (ret=sieve_stringlist_next_item(strlist->field_values, &hdr_item))
<= 0 )
return ret;
/* Extract the date string value */
header_value = str_c(hdr_item);
date_string = strrchr(header_value, ';');
if ( date_string == NULL ) {
/* Direct header value */
date_string = header_value;
} else {
/* Delimited by ';', e.g. a Received: header */
date_string++;
}
/* Parse the date value */
if ( message_date_parse((const unsigned char *) date_string,
strlen(date_string), &date_value, &original_zone) ) {
got_date = TRUE;
}
} else {
/* Use time stamp recorded at the time the script first started */
date_value = strlist->local_time;
original_zone = strlist->local_zone;
got_date = TRUE;
}
if ( got_date ) {
int wanted_zone;
struct tm *date_tm;
/* Apply wanted timezone */
switch ( strlist->time_zone ) {
case EXT_DATE_TIMEZONE_LOCAL:
wanted_zone = strlist->local_zone;
break;
case EXT_DATE_TIMEZONE_ORIGINAL:
wanted_zone = original_zone;
break;
default:
wanted_zone = strlist->time_zone;
}
date_value += wanted_zone * 60;
/* Convert timestamp to struct tm */
if ( (date_tm=gmtime(&date_value)) != NULL ) {
/* Extract the date part */
part_value = ext_date_part_extract
(strlist->date_part, date_tm, wanted_zone);
}
}
strlist->read = TRUE;
if ( part_value == NULL )
return 0;
*str_r = t_str_new_const(part_value, strlen(part_value));
return 1;
}
static void ext_date_stringlist_reset
(struct sieve_stringlist *_strlist)
{
struct ext_date_stringlist *strlist =
(struct ext_date_stringlist *) _strlist;
if ( strlist->field_values != NULL )
sieve_stringlist_reset(strlist->field_values);
strlist->read = FALSE;
}