1 /* http_tzdist.c -- Routines for handling tzdist service requests in httpd
2  *
3  * Copyright (c) 1994-2014 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 
44 /*
45  * TODO:
46  * - Implement localized names / handle Accept-Language header field?
47  */
48 
49 #include <config.h>
50 
51 #ifdef HAVE_UNISTD_H
52 #include <unistd.h>
53 #endif
54 #include <ctype.h>
55 #include <math.h>
56 #include <string.h>
57 #include <syslog.h>
58 #include <assert.h>
59 #include <errno.h>
60 
61 #include "global.h"
62 #include "hash.h"
63 #include "httpd.h"
64 #include "http_dav.h"
65 #include "http_proxy.h"
66 #include "ical_support.h"
67 #include "jcal.h"
68 #include "map.h"
69 #include "strhash.h"
70 #include "times.h"
71 #include "tok.h"
72 #include "util.h"
73 #include "version.h"
74 #include "xcal.h"
75 #include "xstrlcpy.h"
76 #include "zoneinfo_db.h"
77 
78 /* generated headers are not necessarily in current directory */
79 #include "imap/http_err.h"
80 #include "imap/tz_err.h"
81 
82 #define TZDIST_WELLKNOWN_URI "/.well-known/timezone"
83 
84 static time_t compile_time;
85 static unsigned synctoken_prefix;
86 static ptrarray_t *leap_seconds = NULL;
87 static int geo_enabled = 0;
88 static const char *zoneinfo_dir = NULL;
89 static void tzdist_init(struct buf *serverinfo);
90 static void tzdist_shutdown(void);
91 static int meth_get(struct transaction_t *txn, void *params);
92 static int action_capa(struct transaction_t *txn);
93 static int action_leap(struct transaction_t *txn);
94 static int action_list(struct transaction_t *txn);
95 static int action_get(struct transaction_t *txn);
96 static int action_expand(struct transaction_t *txn);
97 static int json_response(int code, struct transaction_t *txn, json_t *root,
98                          char **resp);
99 static int json_error_response(struct transaction_t *txn, long tz_code,
100                                struct strlist *param, icaltimetype *time);
101 static struct buf *icaltimezone_as_tzif(icalcomponent* comp);
102 static struct buf *icaltimezone_as_tzif_leap(icalcomponent* comp);
103 static struct buf *_icaltimezone_as_tzif(icalcomponent* ical, bit32 leapcnt,
104                                          icaltimetype *startp, icaltimetype *endp);
105 
106 struct observance {
107     const char *name;
108     icaltimetype onset;
109     int offset_from;
110     int offset_to;
111     int is_daylight;
112     int is_std;
113     int is_gmt;
114 };
115 
116 static struct mime_type_t tz_mime_types[] = {
117     /* First item MUST be the default type and storage format */
118     { "text/calendar; charset=utf-8", "2.0", "ics",
119       (struct buf* (*)(void *)) &my_icalcomponent_as_ical_string,
120       NULL, NULL, NULL, NULL
121     },
122     { "application/calendar+xml; charset=utf-8", NULL, "xcs",
123       (struct buf* (*)(void *)) &icalcomponent_as_xcal_string,
124       NULL, NULL, NULL, NULL
125     },
126     { "application/calendar+json; charset=utf-8", NULL, "jcs",
127       (struct buf* (*)(void *)) &icalcomponent_as_jcal_string,
128       NULL, NULL, NULL, NULL
129     },
130     { "application/tzif", NULL, NULL,
131       (struct buf* (*)(void *)) &icaltimezone_as_tzif,
132       NULL, NULL, NULL, NULL
133     },
134     { "application/tzif-leap", NULL, NULL,
135       (struct buf* (*)(void *)) &icaltimezone_as_tzif_leap,
136       NULL, NULL, NULL, NULL
137     },
138     { NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL }
139 };
140 
141 
142 /* Namespace for tzdist service */
143 struct namespace_t namespace_tzdist = {
144     URL_NS_TZDIST, 0, "tzdist", "/tzdist", TZDIST_WELLKNOWN_URI,
145     http_allow_noauth, /*authschemes*/0,
146     /*mbtype*/0,
147     ALLOW_READ,
148     tzdist_init, NULL, NULL, tzdist_shutdown, NULL, NULL,
149     {
150         { NULL,                 NULL },                 /* ACL          */
151         { NULL,                 NULL },                 /* BIND         */
152         { NULL,                 NULL },                 /* CONNECT      */
153         { NULL,                 NULL },                 /* COPY         */
154         { NULL,                 NULL },                 /* DELETE       */
155         { &meth_get,            NULL },                 /* GET          */
156         { &meth_get,            NULL },                 /* HEAD         */
157         { NULL,                 NULL },                 /* LOCK         */
158         { NULL,                 NULL },                 /* MKCALENDAR   */
159         { NULL,                 NULL },                 /* MKCOL        */
160         { NULL,                 NULL },                 /* MOVE         */
161         { &meth_options,        NULL },                 /* OPTIONS      */
162         { NULL,                 NULL },                 /* POST         */
163         { NULL,                 NULL },                 /* PATCH        */
164         { NULL,                 NULL },                 /* PROPFIND     */
165         { NULL,                 NULL },                 /* PROPPATCH    */
166         { NULL,                 NULL },                 /* PUT          */
167         { NULL,                 NULL },                 /* REPORT       */
168         { &meth_trace,          NULL },                 /* TRACE        */
169         { NULL,                 NULL },                 /* UNBIND       */
170         { NULL,                 NULL }                  /* UNLOCK       */
171     }
172 };
173 
174 
175 #ifdef HAVE_SHAPELIB
176 #include <shapefil.h>
177 
178 struct tz_shape_t {
179     int valid;
180     SHPHandle shp;
181     DBFHandle dbf;
182 };
183 
184 static struct tz_shape_t tz_world = { 0, NULL, NULL };
185 static struct tz_shape_t tz_aq    = { 0, NULL, NULL };
186 
open_shape_file(struct buf * serverinfo)187 static void open_shape_file(struct buf *serverinfo)
188 {
189     char buf[1024];
190     int nrecords, shapetype;
191     double minbound[4], maxbound[4];
192     DBFFieldType fieldtype;
193 
194     buf_printf(serverinfo, " ShapeLib/%s", SHAPELIB_VERSION);
195 
196     /* Open the tz_world shape files */
197     snprintf(buf, sizeof(buf), "%s%s", zoneinfo_dir, FNAME_WORLD_SHAPEFILE);
198     if (!(tz_world.shp = SHPOpen(buf, "rb"))) {
199         syslog(LOG_ERR, "Failed to open file %s", buf);
200         return;
201     }
202 
203     if (!(tz_world.dbf = DBFOpen(buf, "rb"))) {
204         syslog(LOG_ERR, "Failed to open file %s", buf);
205         return;
206     }
207 
208     /* Sanity check the shape files */
209     SHPGetInfo(tz_world.shp, &nrecords, &shapetype, minbound, maxbound);
210     if (!nrecords || shapetype != SHPT_POLYGON ||       /* polygons */
211         minbound[0] < -180.0 || maxbound[0] > 180.0 ||  /* longitude range */
212         minbound[1] <  -90.0 || maxbound[1] >  90.0 ||  /* latitude range */
213         nrecords != DBFGetRecordCount(tz_world.dbf)) {  /* record counts */
214         syslog(LOG_ERR, "%s appears to contain invalid data", buf);
215         return;
216     }
217 
218     fieldtype = DBFGetFieldInfo(tz_world.dbf, 0 /* column 1 */, buf, NULL, NULL);
219     if (fieldtype != FTString || strcasecmp(buf, "TZID")) {   /* TZIDs */
220         syslog(LOG_ERR, "%s appears to contain invalid data", buf);
221         return;
222     }
223 
224     geo_enabled = tz_world.valid = 1;
225 
226     /* Open the tz_antarctica shape files (optional) */
227     snprintf(buf, sizeof(buf), "%s%s", zoneinfo_dir, FNAME_AQ_SHAPEFILE);
228     if (!(tz_aq.shp = SHPOpen(buf, "rb"))) {
229         syslog(LOG_NOTICE, "Failed to open file %s", buf);
230         return;
231     }
232 
233     if (!(tz_aq.dbf = DBFOpen(buf, "rb"))) {
234         syslog(LOG_NOTICE, "Failed to open file %s", buf);
235         SHPClose(tz_aq.shp);
236         return;
237     }
238 
239     /* Sanity check the shape files */
240     SHPGetInfo(tz_aq.shp, &nrecords, &shapetype, minbound, maxbound);
241     if (!nrecords || shapetype != SHPT_POINT ||         /* points */
242         minbound[0] < -180.0 || maxbound[0] > 180.0 ||  /* longitude range */
243         minbound[1] <  -90.0 || maxbound[1] > -60.0 ||  /* latitude range */
244         nrecords != DBFGetRecordCount(tz_aq.dbf)) {     /* record counts */
245         syslog(LOG_ERR, "%s appears to contain invalid data", buf);
246         return;
247     }
248 
249     fieldtype = DBFGetFieldInfo(tz_aq.dbf, 1 /* column 2 */, buf, NULL, NULL);
250     if (fieldtype != FTString || strcasecmp(buf, "TZID")) {  /* TZIDs */
251         syslog(LOG_ERR, "%s appears to contain invalid data", buf);
252         return;
253     }
254 
255     tz_aq.valid = 1;
256 }
257 
close_shape_file()258 static void close_shape_file()
259 {
260     if (tz_world.dbf) DBFClose(tz_world.dbf);
261     if (tz_world.shp) SHPClose(tz_world.shp);
262     if (tz_aq.dbf) DBFClose(tz_aq.dbf);
263     if (tz_aq.shp) SHPClose(tz_aq.shp);
264 }
265 
pt_in_poly(int nvert,double * vx,double * vy,double px,double py)266 static int pt_in_poly(int nvert, double *vx, double *vy, double px, double py)
267 {
268     int i, j, in = 0;
269 
270     for (i = 0, j = nvert - 1; i < nvert; j = i++) {
271         if (((vy[i] > py) != (vy[j] > py)) &&
272             (px < (vx[j] - vx[i]) * (py - vy[i]) / (vy[j] - vy[i]) + vx[i])) {
273             in = !in;
274         }
275     }
276 
277     return in;
278 }
279 
280 
281 #define M_EARTH_RADIUS    6371008.7                    /* mean radius (meters) */
282 
283 #define M_PI_180          0.01745329251994329547       /* pi / 180             */
284 
285 #define deg2rad(deg)      (deg * M_PI_180)             /* degrees -> radians   */
286 
287 #define vec_normal(v)     vec_mult(1 / vec_mag(v), v)  /* normalize vector     */
288 
289 #define vec_diff(v1, v2)  acos(vec_dot_prod(v1, v2))   /* angular difference   */
290 
291 struct vector {
292     double x, y, z;
293 };
294 
geo2vec(double lat,double lon,struct vector * p)295 static struct vector *geo2vec(double lat, double lon, struct vector *p)
296 {
297     /* Convert lat/lon to radians */
298     lat = deg2rad(lat);
299     lon = deg2rad(lon);
300 
301     /* Convert lat/lon to unit vector */
302     p->x = cos(lat) * cos(lon);
303     p->y = cos(lat) * sin(lon);
304     p->z = sin(lat);
305 
306     return p;
307 }
308 
vec_mag(const struct vector * v)309 static double vec_mag(const struct vector *v)
310 {
311     return sqrt(v->x * v->x + v->y * v->y + v->z * v->z);
312 }
313 
vec_mult(double m,struct vector * v)314 static struct vector *vec_mult(double m, struct vector *v)
315 {
316     v->x *= m;
317     v->y *= m;
318     v->z *= m;
319 
320     return v;
321 }
322 
vec_dot_prod(const struct vector * v1,const struct vector * v2)323 static double vec_dot_prod(const struct vector *v1, const struct vector *v2)
324 {
325     return (v1->x * v2->x + v1->y * v2->y + v1->z * v2->z);
326 }
327 
vec_cross_prod(const struct vector * v1,const struct vector * v2,struct vector * r)328 static struct vector *vec_cross_prod(const struct vector *v1,
329                                     const struct vector *v2,
330                                     struct vector *r)
331 {
332     r->x = v1->y * v2->z - v1->z * v2->y;
333     r->y = v1->z * v2->x - v1->x * v2->z;
334     r->z = v1->x * v2->y - v1->y * v2->x;
335 
336     return r;
337 }
338 
pt_near_poly(int nvert,double * vx,double * vy,struct vector * p,double range)339 static int pt_near_poly(int nvert, double *vx, double *vy,
340                         struct vector *p, double range)
341 {
342     int i, j;
343 
344     for (i = 0, j = nvert - 1; i < nvert; j = i++) {
345         struct vector a, b, n;
346 
347         geo2vec(vy[j], vx[j], &a);
348         geo2vec(vy[i], vx[i], &b);
349 
350         /* Check if either end point of the line is within range */
351         if (vec_diff(p, &a) <= range || vec_diff(p, &b) <= range) return 1;
352 
353         /* Find unit normal vector (n) for plane passing through a & b */
354         vec_normal(vec_cross_prod(&a, &b, &n));
355 
356         /* Shortest distance between p and geodesic passing through a & b (ab) */
357         if (asin(fabs(vec_dot_prod(&n, p))) <= range) {
358             struct vector c, d;
359             double ab_len;
360 
361             /* Find perpendicular geodesic (d) through p to ab */
362             vec_cross_prod(p, &n, &d);
363 
364             /* Find intersection point (c) of d and ab */
365             vec_normal(vec_cross_prod(&n, &d, &c));
366 
367             /* Make sure intersection point (c) lies between a & b */
368             ab_len = vec_diff(&a, &b);
369             if (vec_diff(&a, &c) <= ab_len && vec_diff(&b, &c) <= ab_len) {
370                 return 1;
371             }
372         }
373     }
374 
375     return 0;
376 }
377 
tzid_from_geo(struct transaction_t * txn,double latitude,double longitude,double uncertainty)378 static strarray_t *tzid_from_geo(struct transaction_t *txn,
379                                  double latitude, double longitude,
380                                  double uncertainty)
381 {
382     strarray_t *tzids = strarray_new();
383     const char *tzid;
384     struct vector p, a;
385     int i, npoly;
386     double minbound[4], maxbound[4];
387 
388     /* using unit vectors */
389     uncertainty /= M_EARTH_RADIUS;
390     geo2vec(latitude, longitude, &p);  /* vector for point */
391     geo2vec(-60, longitude, &a);       /* perpendicular vector to Antarctic */
392 
393     if (tz_aq.valid &&
394         /* Check if point is within or near Antarctic region */
395         (latitude <= -60 || (uncertainty && vec_diff(&p, &a) <= uncertainty))) {
396 
397         /* check if point is near an Antarctic base */
398         double dist = uncertainty;
399 
400         if (!dist) {
401             /* default to 10km radius */
402             dist = 10000 / M_EARTH_RADIUS;
403         }
404 
405         for (i = 0; i < tz_aq.shp->nRecords; i++) {
406             SHPObject *base = SHPReadObject(tz_aq.shp, i);
407             struct vector b;
408 
409             geo2vec(base->padfY[0], base->padfX[0], &b);  /* vector for base */
410 
411             if (vec_diff(&p, &b) <= dist) {
412                 /* Point is near a base, check if it has a known time zone */
413                 tzid = DBFReadStringAttribute(tz_aq.dbf, i, 1 /* column 2 */);
414                 if (strcmp(tzid, "unknown")) strarray_append(tzids, tzid);
415             }
416 
417             SHPDestroyObject(base);
418 
419             keepalive_response(txn);
420         }
421     }
422 
423     /* Check if point is within or near bounding box of tz_world */
424     SHPGetInfo(tz_world.shp, &npoly, NULL, minbound, maxbound);
425 
426     double WbbX[5] =
427         { minbound[0], minbound[0], maxbound[0], maxbound[0], minbound[0] };
428     double WbbY[5] =
429         { minbound[1], maxbound[1], maxbound[1], minbound[1], minbound[1] };
430 
431     if (pt_in_poly(5, WbbX, WbbY, longitude, latitude) ||
432         (uncertainty && pt_near_poly(5, WbbX, WbbY, &p, uncertainty))) {
433         /* Check if point is within or near a time zone boundary */
434 
435         for (i = 0; i < npoly; i++) {
436             SHPObject *poly = SHPReadObject(tz_world.shp, i);
437             double bbX[5] = { poly->dfXMin, poly->dfXMin,
438                               poly->dfXMax, poly->dfXMax, poly->dfXMin };
439             double bbY[5] = { poly->dfYMin, poly->dfYMax,
440                               poly->dfYMax, poly->dfYMin, poly->dfYMin };
441 
442             /* Check if point is within or near bounding box of boundary */
443             int within = pt_in_poly(5, bbX, bbY, longitude, latitude);
444             int near = uncertainty && pt_near_poly(5, bbX, bbY, &p, uncertainty);
445             int r = 0;
446 
447             if (within || near) {
448                 if (within) {
449                     /* Check if point is within boundary */
450                     r = pt_in_poly(poly->nVertices, poly->padfX, poly->padfY,
451                                    longitude, latitude);
452                 }
453 
454                 if (!r && uncertainty) {
455                     /* Check if point is near boundary */
456                     r = pt_near_poly(poly->nVertices, poly->padfX, poly->padfY,
457                                      &p, uncertainty);
458                 }
459             }
460 
461             if (r) {
462                 tzid = DBFReadStringAttribute(tz_world.dbf, i, 0 /* column 1 */);
463                 strarray_append(tzids, tzid);
464             }
465 
466             SHPDestroyObject(poly);
467 
468             keepalive_response(txn);
469         }
470     }
471 
472     if (!strarray_size(tzids)) {
473         /* No tzids found in shapefile(s) */
474         char tzid_buf[20];
475 
476         if (latitude <= -60) {
477             /* Antarctic region - guess-timate offset from GMT based on:
478 
479                https://en.wikipedia.org/wiki/Time_in_Antarctica
480                https://en.wikipedia.org/wiki/Territorial_claims_in_Antarctica
481                https://en.wikipedia.org/wiki/Australian_Antarctic_Territory
482                https://en.wikipedia.org/wiki/Queen_Maud_Land
483                https://en.wikipedia.org/wiki/Princess_Martha_Coast
484                https://en.wikipedia.org/wiki/Princess_Astrid_Coast
485                https://en.wikipedia.org/wiki/Princess_Ragnhild_Coast
486                https://en.wikipedia.org/wiki/Prince_Harald_Coast
487                https://en.wikipedia.org/wiki/Prince_Olav_Coast
488             */
489             if (latitude <= -89 || (longitude >= 160 || longitude <= -150)) {
490                 /* South Pole and New Zealand Claim (Ross Dependency) */
491                 tzid = "Antarctica/South_Pole";
492             }
493             else if (longitude >= -20 && latitude <= -80) {
494                 /* Uninhabited */
495                 tzid = "Etc/GMT";
496             }
497             else if (longitude >= 142.033333) {     /* 142° 2' */
498                 /* Australian Claim (George V / Oates Lands) */
499                 tzid = "Etc/GMT+10";
500             }
501             else if (longitude >= 136.183333) {     /* 136° 11' */
502                 /* French Claim (Adelie Land) */
503                 tzid = "Etc/GMT+10";
504             }
505             else if (longitude >= 44.633333) {      /*  44° 38' */
506                 /* Australian Claim */
507                 if (longitude >= 100.5) {           /* 100° 30' */
508                     /* Wilkes Land */
509                     tzid = "Etc/GMT+8";
510                 }
511                 else if (longitude >= 72.583333) {  /*  72° 35' */
512                     /* Princess Elizabeth / Kaiser Wilhelm II / Queen Mary Lands */
513                     tzid = "Etc/GMT+7";
514                 }
515                 else {
516                     /* Enderby / Kemp / Mac. Robertson Lands */
517                     tzid = "Etc/GMT+6";
518                 }
519             }
520             else if (longitude >= -20) {
521                 /* Norwegian Claim (Queen Maud Land) */
522                 if (longitude >= 20) {
523                     /* Princess Ragnhild / Prince Harald / Prince Olav Coasts */
524                     tzid = "Etc/GMT+3";
525                 }
526                 else {
527                     /* Princess Martha / Princess Astrid Coasts */
528                     tzid = "Etc/GMT";
529                 }
530             }
531             else if (longitude >= -90) {
532                 /* British / Argentine / Chilean Claims */
533                 if (longitude >= -74) {
534                     /* Argentine / British Claims */
535                     tzid = "Etc/GMT-3";
536                 }
537                 /* XXX  Is there a GMT-4 + DST region? */
538                 else {
539                     /* Chilean / British Claims */
540                     tzid = "Etc/GMT-4";
541                 }
542             }
543             else {
544                 /* Unclaimed */
545                 if (latitude <= -80) {
546                     /* Uninhabited */
547                     tzid = "Etc/GMT";
548                 }
549                 else tzid = "Etc/GMT-6";
550             }
551         }
552         else {
553             /* Assume international waters -
554                calculate offset from GMT based on longitude
555 
556                XXX  Which offset does an exact multiple of +/- 7.5
557                and +/- 180 degrees belong to?
558             */
559             snprintf(tzid_buf, sizeof(tzid_buf), "Etc/GMT%+d",
560                     (short) (longitude + copysign(1.0, longitude) * 7.5) / 15);
561             tzid = tzid_buf;
562         }
563 
564         strarray_append(tzids, tzid);
565     }
566 
567     return tzids;
568 }
569 #else
570 
open_shape_file(struct buf * serverinfo)571 static void open_shape_file(struct buf *serverinfo __attribute__((unused)))
572 {
573     return;
574 }
575 
close_shape_file()576 static void close_shape_file()
577 {
578     return;
579 }
580 
tzid_from_geo(struct transaction_t * txn,double latitude,double longitude,double uncertainty)581 static strarray_t *tzid_from_geo(struct transaction_t *txn __attribute__((unused)),
582                                  double latitude __attribute__((unused)),
583                                  double longitude __attribute__((unused)),
584                                  double uncertainty __attribute__((unused)))
585 {
586     return NULL;
587 }
588 
589 #endif /* HAVE_SHAPELIB */
590 
591 
592 struct leapsec {
593     long long int t;      /* transition time */
594     long int sec;         /* leap seconds */
595 };
596 
read_leap_seconds()597 static void read_leap_seconds()
598 {
599     FILE *fp;
600     char buf[1024];
601     struct leapsec *leap;
602 
603     snprintf(buf, sizeof(buf), "%s%s", zoneinfo_dir, FNAME_LEAPSECFILE);
604     if (!(fp = fopen(buf, "r"))) {
605         syslog(LOG_ERR, "Failed to open file %s", buf);
606         return;
607     }
608 
609     /* expires record is always at idx=0, if exists */
610     leap_seconds = ptrarray_new();
611     leap = xzmalloc(sizeof(struct leapsec));
612     ptrarray_append(leap_seconds, leap);
613 
614     while (fgets(buf, sizeof(buf), fp)) {
615         if (buf[0] == '#') {
616             /* comment line */
617 
618             if (buf[1] == '@') {
619                 /* expires */
620                 leap = ptrarray_nth(leap_seconds, 0);
621                 sscanf(buf+2, "\t%lld", &leap->t);
622                 leap->t -= NIST_EPOCH_OFFSET;
623             }
624         }
625         else if (isdigit(buf[0])) {
626             /* leap second */
627             leap = xmalloc(sizeof(struct leapsec));
628             ptrarray_append(leap_seconds, leap);
629             sscanf(buf, "%lld\t%ld", &leap->t, &leap->sec);
630             leap->t -= NIST_EPOCH_OFFSET;
631         }
632     }
633     fclose(fp);
634 }
635 
636 
tzdist_init(struct buf * serverinfo)637 static void tzdist_init(struct buf *serverinfo __attribute__((unused)))
638 {
639     struct buf buf = BUF_INITIALIZER;
640 
641     namespace_tzdist.enabled =
642         config_httpmodules & IMAP_ENUM_HTTPMODULES_TZDIST;
643 
644     if (!namespace_tzdist.enabled) return;
645 
646     /* Open zoneinfo db */
647     if (zoneinfo_open(NULL)) {
648         namespace_tzdist.enabled = 0;
649         return;
650     }
651 
652     /* Find configured zoneinfo_zir */
653     zoneinfo_dir = config_getstring(IMAPOPT_ZONEINFO_DIR);
654     if (!zoneinfo_dir) {
655         syslog(LOG_ERR, "zoneinfo_dir must be set for tzdist service");
656         namespace_tzdist.enabled = 0;
657         return;
658     }
659 
660     compile_time = calc_compile_time(__TIME__, __DATE__);
661 
662     buf_printf(&buf, "Cyrus TZdist: %s", config_servername);
663     synctoken_prefix = strhash(buf_cstring(&buf));
664     buf_free(&buf);
665 
666     initialize_tz_error_table();
667 
668     open_shape_file(serverinfo);
669 
670     read_leap_seconds();
671     if (!leap_seconds || leap_seconds->count < 2) {
672         /* Disable application/tzif-leap */
673         struct mime_type_t *mime;
674 
675         for (mime = tz_mime_types; mime->content_type; mime++) {
676             if (!strcmp(mime->content_type, "application/tzif-leap")) {
677                 mime->content_type = NULL;
678                 break;
679             }
680         }
681     }
682 }
683 
684 
tzdist_shutdown(void)685 static void tzdist_shutdown(void)
686 {
687     struct leapsec *leap;
688 
689     zoneinfo_close(NULL);
690 
691     close_shape_file();
692 
693     if (!leap_seconds) return;
694 
695     while ((leap = ptrarray_pop(leap_seconds))) free(leap);
696     ptrarray_free(leap_seconds);
697 }
698 
699 
700 /* Perform a GET/HEAD request */
meth_get(struct transaction_t * txn,void * params)701 static int meth_get(struct transaction_t *txn,
702                     void *params __attribute__((unused)))
703 {
704     struct request_target_t *tgt = &txn->req_tgt;
705     int (*action)(struct transaction_t *txn) = NULL;
706     unsigned levels = 0;
707     char *p;
708 
709     /* Make a working copy of target path */
710     strlcpy(tgt->path, txn->req_uri->path, sizeof(tgt->path));
711     p = tgt->path;
712 
713     /* Skip namespace */
714     p += strlen(namespace_tzdist.prefix);
715     if (*p == '/') *p++ = '\0';
716 
717     /* Check for path after prefix */
718     if (*p) {
719         /* Get collection (action) */
720         tgt->collection = p;
721         p += strcspn(p, "/");
722         if (*p == '/') *p++ = '\0';
723 
724         if (!strcmp(tgt->collection, "capabilities")) {
725             if (!*p) action = &action_capa;
726         }
727         else if (!strcmp(tgt->collection, "leapseconds")) {
728             if (!*p) action = &action_leap;
729         }
730         else if (!strcmp(tgt->collection, "zones")) {
731             if (!*p) {
732                 action = &action_list;
733             }
734             else {
735                 /* Get resource (tzid) */
736                 tgt->resource = p;
737                 p += strlen(p);
738                 if (p[-1] == '/') *--p = '\0';
739 
740                 /* Check for sub-action */
741                 p = strstr(tgt->resource, "observances");
742                 if (!p) {
743                     action = &action_get;
744                 }
745                 else if (p[-1] == '/') {
746                     *--p = '\0';
747                     action = &action_expand;
748                 }
749 
750                 /* XXX  Hack - probably need to check for %2F vs '/'
751                    Count the number of "levels".  Current tzid have max of 3. */
752                 for (p = tgt->resource; p && ++levels; (p = strchr(p+1, '/')));
753             }
754         }
755     }
756 
757     if (!action || levels > 3)
758         return json_error_response(txn, TZ_INVALID_ACTION, NULL, NULL);
759 
760     if (tgt->resource && strchr(tgt->resource, '.'))  /* paranoia */
761         return json_error_response(txn, TZ_NOT_FOUND, NULL, NULL);
762 
763     return action(txn);
764 }
765 
766 
767 /* Perform a capabilities action */
action_capa(struct transaction_t * txn)768 static int action_capa(struct transaction_t *txn)
769 {
770     int precond;
771     struct message_guid guid;
772     const char *etag;
773     static time_t lastmod = 0;
774     static char *resp = NULL;
775     json_t *root = NULL;
776 
777     /* Generate ETag based on compile date/time of this source file.
778      * Extend this to include config file size/mtime if we add run-time options.
779      */
780     assert(!buf_len(&txn->buf));
781     buf_printf(&txn->buf, TIME_T_FMT, compile_time);
782     message_guid_generate(&guid, buf_cstring(&txn->buf), buf_len(&txn->buf));
783     etag = message_guid_encode(&guid);
784 
785     /* Check any preconditions, including range request */
786     txn->flags.ranges = 1;
787     precond = check_precond(txn, etag, compile_time);
788 
789     switch (precond) {
790     case HTTP_OK:
791     case HTTP_PARTIAL:
792     case HTTP_NOT_MODIFIED:
793         /* Fill in Etag,  Last-Modified, Expires */
794         txn->resp_body.etag = etag;
795         txn->resp_body.lastmod = compile_time;
796         txn->resp_body.maxage = 86400;  /* 24 hrs */
797         txn->flags.cc |= CC_MAXAGE;
798         if (!httpd_userisanonymous) txn->flags.cc |= CC_PUBLIC;
799 
800         if (precond != HTTP_NOT_MODIFIED) break;
801 
802         GCC_FALLTHROUGH
803 
804     default:
805         /* We failed a precondition - don't perform the request */
806         return precond;
807     }
808 
809     if (txn->resp_body.lastmod > lastmod) {
810         struct zoneinfo info;
811         struct mime_type_t *mime;
812         json_t *formats;
813         int r;
814 
815         /* Get info record from the database */
816         if ((r = zoneinfo_lookup_info(&info))) return HTTP_SERVER_ERROR;
817 
818         buf_reset(&txn->buf);
819         buf_printf(&txn->buf, "%s:%s", info.data->s, info.data->next->s);
820 
821         /* Construct our response */
822         root = json_pack("{ s:i"                        /* version */
823                          "  s:{"                        /* info */
824                          "      s:s"                    /*   primary-source */
825                          "      s:[]"                   /*   formats */
826                          "      s:{s:b s:b}"            /*   truncated */
827 //                       "      s:s"                    /*   provider-details */
828 //                       "      s:[]"                   /*   contacts */
829                          "    }"
830                          "  s:["                        /* actions */
831                          "    {s:s s:s s:["             /*   capabilities */
832                          "    ]}"
833                          "    {s:s s:s s:["             /*   list */
834                          "      {s:s}"                  /*     changedsince */
835                          "    ]}"
836                          "    {s:s s:s s:["             /*   get */
837                          "      {s:s}"                  /*     start */
838                          "      {s:s}"                  /*     end */
839                          "    ]}"
840                          "    {s:s s:s s:["             /*   expand */
841                          "      {s:s s:b}"              /*     start */
842                          "      {s:s s:b}"              /*     end */
843                          "    ]}"
844                          "    {s:s s:s s:["             /*   find */
845                          "      {s:s s:b}"              /*     pattern */
846                          "    ]}"
847                          "    {s:s s:s s:["             /*   leapseconds */
848                          "    ]}"
849                          "  ]}",
850 
851                          "version", 1,
852 
853                          "info",
854                          "primary-source", buf_cstring(&txn->buf), "formats",
855                          "truncated", "any", 1, "untruncated", 1,
856 //                       "provider-details", "", "contacts",
857 
858                          "actions",
859                          "name", "capabilities",
860                          "uri-template", "/capabilities", "parameters",
861 
862                          "name", "list",
863                          "uri-template", "/zones{?changedsince}", "parameters",
864                          "name", "changedsince",
865 
866                          "name", "get", "uri-template",
867                          "/zones{/tzid}{?start,end}", "parameters",
868                          "name", "start",
869                          "name", "end",
870 
871                          "name", "expand", "uri-template",
872                          "/zones{/tzid}/observances{?start,end}",
873                          "parameters",
874                          "name", "start", "required", 1,
875                          "name", "end", "required", 1,
876 
877                          "name", "find",
878                          "uri-template", "/zones{?pattern}", "parameters",
879                          "name", "pattern", "required", 1,
880 
881                          "name", "leapseconds",
882                          "uri-template", "/leapseconds", "parameters");
883 
884         freestrlist(info.data);
885 
886         if (!root) {
887             txn->error.desc = "Unable to create JSON response";
888             return HTTP_SERVER_ERROR;
889         }
890 
891         if (geo_enabled) {
892             /* Add geolocate action */
893             json_t *actions = json_object_get(root, "actions");
894 
895             json_array_append_new(actions,
896                                   json_pack("{s:s s:s s:["
897                                             "  {s:s s:b}"
898                                             "]}",
899                                             "name", "geolocate", "uri-template",
900                                             "/zones{?location}",
901                                             "parameters",
902                                             "name", "location", "required", 1));
903         }
904 
905         /* Add supported formats */
906         formats = json_object_get(json_object_get(root, "info"), "formats");
907         for (mime = tz_mime_types; mime->content_type; mime++) {
908             buf_setcstr(&txn->buf, mime->content_type);
909             buf_truncate(&txn->buf, strcspn(mime->content_type, ";"));
910             json_array_append_new(formats, json_string(buf_cstring(&txn->buf)));
911         }
912         buf_reset(&txn->buf);
913 
914         /* Update lastmod */
915         lastmod = txn->resp_body.lastmod;
916     }
917 
918     /* Output the JSON object */
919     return json_response(precond, txn, root, &resp);
920 }
921 
922 /* Perform a leapseconds action */
action_leap(struct transaction_t * txn)923 static int action_leap(struct transaction_t *txn)
924 {
925     int r, ret = 0, precond;
926     struct resp_body_t *resp_body = &txn->resp_body;
927     struct zoneinfo info, leap;
928 
929     /* Get info record from the database */
930     if ((r = zoneinfo_lookup_info(&info))) return HTTP_SERVER_ERROR;
931 
932     /* Get leap record from the database */
933     if ((r = zoneinfo_lookup_leap(&leap))) {
934         ret = (r == CYRUSDB_NOTFOUND ? HTTP_NOT_FOUND : HTTP_SERVER_ERROR);
935         goto done;
936     }
937 
938     /* Check any preconditions, including range request */
939     txn->flags.ranges = 1;
940     precond = check_precond(txn, leap.data->s, leap.dtstamp);
941 
942     switch (precond) {
943     case HTTP_OK:
944     case HTTP_PARTIAL:
945     case HTTP_NOT_MODIFIED:
946         /* Fill in ETag, Last-Modified, and Expires */
947         resp_body->etag = leap.data->s;
948         resp_body->lastmod = leap.dtstamp;
949         resp_body->maxage = 86400;  /* 24 hrs */
950         txn->flags.cc |= CC_MAXAGE | CC_REVALIDATE;
951         if (!httpd_userisanonymous) txn->flags.cc |= CC_PUBLIC;
952 
953         if (precond != HTTP_NOT_MODIFIED) break;
954 
955         GCC_FALLTHROUGH
956 
957     default:
958         /* We failed a precondition - don't perform the request */
959         resp_body->type = NULL;
960         ret = precond;
961         goto done;
962     }
963 
964 
965     if (txn->meth != METH_HEAD) {
966         json_t *root, *expires, *leapseconds;
967         struct leapsec *leapsec;
968         char buf[1024];
969         int n;
970 
971         if (!leap_seconds) {
972             ret = HTTP_NOT_FOUND;
973             goto done;
974         }
975 
976         /* Construct our response */
977         root = json_pack("{s:s s:s s:s s:[]}",
978                          "expires", "", "publisher", info.data->s,
979                          "version", info.data->next->s, "leapseconds");
980         if (!root) {
981             txn->error.desc = "Unable to create JSON response";
982             ret = HTTP_SERVER_ERROR;
983             goto done;
984         }
985 
986         expires = json_object_get(root, "expires");
987         leapseconds = json_object_get(root, "leapseconds");
988 
989         leapsec = ptrarray_nth(leap_seconds, 0);
990         if (leapsec->t) {
991             time_to_rfc3339(leapsec->t, buf, 11 /* clip time */);
992             json_string_set(expires, buf);
993         }
994 
995         for (n = 1; n < leap_seconds->count; n++) {
996             json_t *leap;
997 
998             leapsec = ptrarray_nth(leap_seconds, n);
999             time_to_rfc3339(leapsec->t, buf, 11 /* clip time */);
1000             leap = json_pack("{s:i s:s}",
1001                              "utc-offset", leapsec->sec, "onset", buf);
1002             json_array_append_new(leapseconds, leap);
1003         }
1004 
1005         /* Output the JSON object */
1006         ret = json_response(precond, txn, root, NULL);
1007     }
1008 
1009   done:
1010     freestrlist(leap.data);
1011     freestrlist(info.data);
1012     return ret;
1013 }
1014 
1015 
1016 struct list_rock {
1017     struct strlist *meta;
1018     json_t *tzarray;
1019     struct hash_table *tztable;
1020 };
1021 
list_cb(const char * tzid,int tzidlen,struct zoneinfo * zi,void * rock)1022 static int list_cb(const char *tzid, int tzidlen,
1023                    struct zoneinfo *zi, void *rock)
1024 {
1025     struct list_rock *lrock = (struct list_rock *) rock;
1026     char tzidbuf[200], etag[32], lastmod[RFC3339_DATETIME_MAX];
1027     json_t *tz;
1028 
1029     snprintf(tzidbuf, sizeof(tzidbuf), "%.*s", tzidlen, tzid);
1030 
1031     if (lrock->tztable) {
1032         if (hash_lookup(tzidbuf, lrock->tztable)) return 0;
1033         hash_insert(tzidbuf, (void *) 0xDEADBEEF, lrock->tztable);
1034     }
1035 
1036     sprintf(etag, "%u-" TIME_T_FMT, strhash(tzidbuf), zi->dtstamp);
1037     time_to_rfc3339(zi->dtstamp, lastmod, RFC3339_DATETIME_MAX);
1038 
1039     tz = json_pack("{s:s s:s s:s s:s s:s}",
1040                    "tzid", tzidbuf, "etag", etag, "last-modified", lastmod,
1041                    "publisher", lrock->meta->s, "version", lrock->meta->next->s);
1042     json_array_append_new(lrock->tzarray, tz);
1043 
1044     if (zi->data) {
1045         struct strlist *sl;
1046         json_t *aliases = json_array();
1047 
1048         json_object_set_new(tz, "aliases", aliases);
1049 
1050         for (sl = zi->data; sl; sl = sl->next)
1051             json_array_append_new(aliases, json_string(sl->s));
1052     }
1053 
1054     return 0;
1055 }
1056 
1057 /* Perform a list action */
action_list(struct transaction_t * txn)1058 static int action_list(struct transaction_t *txn)
1059 {
1060     int r, ret, precond;
1061     struct strlist *param;
1062     const char *pattern = NULL;
1063     struct resp_body_t *resp_body = &txn->resp_body;
1064     struct zoneinfo info;
1065     time_t changedsince = 0, lastmod;
1066     double latitude = 99.9, longitude = 0.0;
1067     double altitude = 0.0, uncertainty = 0.0;
1068     strarray_t *geo_tzids = NULL;
1069     json_t *root = NULL;
1070 
1071     /* Get info record from the database */
1072     if ((r = zoneinfo_lookup_info(&info))) return HTTP_SERVER_ERROR;
1073 
1074     /* Sanity check the parameters */
1075     if ((param = hash_lookup("pattern", &txn->req_qparams))) {
1076         if (param->next                   /* once only */
1077             || !param->s || !*param->s    /* not empty */
1078             || strspn(param->s, "*") == strlen(param->s)) {  /* not (*)+ */
1079             return json_error_response(txn, TZ_INVALID_PATTERN, param, NULL);
1080         }
1081         pattern = param->s;
1082     }
1083     else if (geo_enabled &&
1084              (param = hash_lookup("location", &txn->req_qparams))) {
1085         /* Parse 'geo' URI */
1086         char *endptr;
1087 
1088         if (param->next                         /* once only */
1089             || strncmp(param->s, "geo:", 4)) {  /* value value */
1090             return json_error_response(txn, TZ_INVALID_LOCATION, param, NULL);
1091         }
1092 
1093         latitude = strtod(param->s + 4, &endptr);
1094         if (errno || *endptr != ','
1095             || latitude < -90.0 || latitude > 90.0) {  /* valid value */
1096             return json_error_response(txn, TZ_INVALID_LOCATION, param, NULL);
1097         }
1098 
1099         longitude = strtod(++endptr, &endptr);
1100         if (errno || (*endptr && !strchr(",;", *endptr))
1101             || longitude < -180.0 || longitude > 180.0) {  /* valid value */
1102             return json_error_response(txn, TZ_INVALID_LOCATION, param, NULL);
1103         }
1104 
1105         if (*endptr == ',') {
1106             altitude = strtod(++endptr, &endptr);
1107             if (*endptr && *endptr != ';') {  /* valid value */
1108                 return json_error_response(txn, TZ_INVALID_LOCATION, param, NULL);
1109             }
1110             (void) altitude;
1111         }
1112 
1113         if (!strncmp(endptr, ";crs=", 5)) {
1114             char *crs = endptr + 5;
1115             size_t len = strcspn(crs, ";");
1116 
1117             if (len != 5 || strncmp(crs, "wgs84", 5)) {  /* unsupported value */
1118                 return json_error_response(txn, TZ_INVALID_LOCATION, param, NULL);
1119             }
1120             endptr = crs + len;
1121         }
1122 
1123         if (!strncmp(endptr, ";u=", 3)) {
1124             uncertainty = strtod(endptr + 3, &endptr);
1125             if (errno || uncertainty < 0) {  /* valid value */
1126                 return json_error_response(txn, TZ_INVALID_LOCATION, param, NULL);
1127             }
1128         }
1129 
1130         if (*endptr && *endptr != ';') {  /* valid value */
1131             return json_error_response(txn, TZ_INVALID_LOCATION, param, NULL);
1132         }
1133     }
1134     else if ((param = hash_lookup("changedsince", &txn->req_qparams))) {
1135         unsigned prefix = 0;
1136 
1137         if (param->next) {  /* once only */
1138             return json_error_response(txn, TZ_INVALID_CHANGEDSINCE,
1139                                        param, NULL);
1140         }
1141 
1142         /* Parse and sanity check the changedsince token */
1143         sscanf(param->s, "%u-" TIME_T_FMT, &prefix, &changedsince);
1144         if (prefix != synctoken_prefix || changedsince > info.dtstamp) {
1145             changedsince = 0;
1146         }
1147     }
1148     else if (hash_numrecords(&txn->req_qparams)) {
1149         return json_error_response(txn, TZ_INVALID_ACTION, NULL, NULL);
1150     }
1151 
1152     /* Generate ETag & Last-Modified from info record */
1153     assert(!buf_len(&txn->buf));
1154     buf_printf(&txn->buf, "%u-" TIME_T_FMT, synctoken_prefix, info.dtstamp);
1155     lastmod = info.dtstamp;
1156 
1157     /* Check any preconditions, including range request */
1158     txn->flags.ranges = 1;
1159     precond = check_precond(txn, buf_cstring(&txn->buf), lastmod);
1160 
1161     switch (precond) {
1162     case HTTP_OK:
1163     case HTTP_PARTIAL:
1164     case HTTP_NOT_MODIFIED:
1165         /* Fill in ETag, Last-Modified, and Expires */
1166         resp_body->etag = buf_cstring(&txn->buf);
1167         resp_body->lastmod = lastmod;
1168         resp_body->maxage = 86400;  /* 24 hrs */
1169         txn->flags.cc |= CC_MAXAGE | CC_REVALIDATE;
1170         if (!httpd_userisanonymous) txn->flags.cc |= CC_PUBLIC;
1171 
1172         if (precond != HTTP_NOT_MODIFIED) break;
1173 
1174         GCC_FALLTHROUGH
1175 
1176     default:
1177         /* We failed a precondition - don't perform the request */
1178         resp_body->type = NULL;
1179         ret = precond;
1180         goto done;
1181     }
1182 
1183 
1184     if (txn->meth != METH_HEAD) {
1185         struct list_rock lrock = { NULL, NULL, NULL };
1186         struct hash_table tzids = HASH_TABLE_INITIALIZER;
1187         int i = 0;
1188 
1189         /* Start constructing our response */
1190         root = json_pack("{s:s s:[]}",
1191                          "synctoken", resp_body->etag, "timezones");
1192         if (!root) {
1193             txn->error.desc = "Unable to create JSON response";
1194             ret = HTTP_SERVER_ERROR;
1195             goto done;
1196         }
1197 
1198         lrock.meta = info.data;
1199         lrock.tzarray = json_object_get(root, "timezones");
1200 
1201         if (latitude <= 90) {
1202             geo_tzids = tzid_from_geo(txn, latitude, longitude, uncertainty);
1203             pattern = strarray_nth(geo_tzids, 0);
1204             if (!pattern) pattern = "/";  /* force lookup failure */
1205         }
1206 
1207         if (pattern) {
1208             construct_hash_table(&tzids, 500, 1);
1209             lrock.tztable = &tzids;
1210         }
1211 
1212         /* Add timezones to array */
1213         do {
1214             zoneinfo_find(pattern, !pattern, changedsince, &list_cb, &lrock);
1215 
1216         } while (geo_tzids && (pattern = strarray_nth(geo_tzids, ++i)));
1217 
1218         free_hash_table(&tzids, NULL);
1219     }
1220 
1221     /* Output the JSON object */
1222     ret = json_response(precond, txn, root, NULL);
1223 
1224   done:
1225     strarray_free(geo_tzids);
1226     freestrlist(info.data);
1227     return ret;
1228 }
1229 
1230 
check_tombstone(struct observance * tombstone,struct observance * obs)1231 static void check_tombstone(struct observance *tombstone,
1232                             struct observance *obs)
1233 {
1234     if (icaltime_compare(obs->onset, tombstone->onset) > 0) {
1235         /* onset is closer to cutoff than existing tombstone */
1236         tombstone->name = icalmemory_tmp_copy(obs->name);
1237         tombstone->offset_from = tombstone->offset_to = obs->offset_to;
1238         tombstone->is_daylight = obs->is_daylight;
1239         tombstone->onset = obs->onset;
1240     }
1241 }
1242 
1243 struct rdate {
1244     icalproperty *prop;
1245     struct icaldatetimeperiodtype date;
1246 };
1247 
rdate_compare(const void * rdate1,const void * rdate2)1248 static int rdate_compare(const void *rdate1, const void *rdate2)
1249 {
1250     return icaltime_compare(((struct rdate *) rdate1)->date.time,
1251                             ((struct rdate *) rdate2)->date.time);
1252 }
1253 
observance_compare(const void * obs1,const void * obs2)1254 static int observance_compare(const void *obs1, const void *obs2)
1255 {
1256     return icaltime_compare(((struct observance *) obs1)->onset,
1257                             ((struct observance *) obs2)->onset);
1258 }
1259 
icalproperty_get_isstd_isgmt(icalproperty * prop,struct observance * obs)1260 static void icalproperty_get_isstd_isgmt(icalproperty *prop,
1261                                          struct observance *obs)
1262 {
1263     const char *time_type =
1264         icalproperty_get_parameter_as_string(prop, "X-OBSERVED-AT");
1265 
1266     if (!time_type) time_type = "W";
1267 
1268     switch (time_type[0]) {
1269     case 'G': case 'g':
1270     case 'U': case 'u':
1271     case 'Z': case 'z':
1272         obs->is_gmt = obs->is_std = 1;
1273         break;
1274     case 'S': case 's':
1275         obs->is_gmt = 0;
1276         obs->is_std = 1;
1277         break;
1278     case 'W': case 'w':
1279     default:
1280         obs->is_gmt = obs->is_std = 0;
1281         break;
1282     }
1283 }
1284 
truncate_vtimezone(icalcomponent * vtz,icaltimetype * startp,icaltimetype * endp,icalarray * obsarray,struct observance ** proleptic,icalcomponent ** eternal_std,icalcomponent ** eternal_dst,icaltimetype * last_dtstart,int ms_compatible)1285 static void truncate_vtimezone(icalcomponent *vtz,
1286                                icaltimetype *startp, icaltimetype *endp,
1287                                icalarray *obsarray,
1288                                struct observance **proleptic,
1289                                icalcomponent **eternal_std,
1290                                icalcomponent **eternal_dst,
1291                                icaltimetype *last_dtstart,
1292                                int ms_compatible)
1293 {
1294     icaltimetype start = *startp, end = *endp;
1295     icalcomponent *comp, *nextc, *tomb_std = NULL, *tomb_day = NULL;
1296     icalproperty *prop, *proleptic_prop = NULL;
1297     static struct observance tombstone;
1298     unsigned need_tomb = !icaltime_is_null_time(start);
1299     unsigned adjust_start = !icaltime_is_null_time(start);
1300     unsigned adjust_end = !icaltime_is_null_time(end);
1301 
1302     if (last_dtstart) *last_dtstart = icaltime_null_time();
1303 
1304     /* See if we have a proleptic tzname in VTIMEZONE */
1305     for (prop = icalcomponent_get_first_property(vtz, ICAL_X_PROPERTY);
1306          prop;
1307          prop = icalcomponent_get_next_property(vtz, ICAL_X_PROPERTY)) {
1308         if (!strcmp("X-PROLEPTIC-TZNAME", icalproperty_get_x_name(prop))) {
1309             proleptic_prop = prop;
1310             break;
1311         }
1312     }
1313 
1314     memset(&tombstone, 0, sizeof(struct observance));
1315     tombstone.name = icalmemory_tmp_copy(proleptic_prop ?
1316                                          icalproperty_get_x(proleptic_prop) :
1317                                          "LMT");
1318     if (!proleptic_prop ||
1319         !icalproperty_get_parameter_as_string(prop, "X-NO-BIG-BANG"))
1320       tombstone.onset.year = -1;
1321 
1322     /* Process each VTMEZONE STANDARD/DAYLIGHT subcomponent */
1323     for (comp = icalcomponent_get_first_component(vtz, ICAL_ANY_COMPONENT);
1324          comp; comp = nextc) {
1325         icalproperty *dtstart_prop = NULL, *rrule_prop = NULL;
1326         icalarray *rdate_array = icalarray_new(sizeof(struct rdate), 10);
1327         icaltimetype dtstart;
1328         struct observance obs;
1329         unsigned n, trunc_dtstart = 0;
1330         int r;
1331 
1332         nextc = icalcomponent_get_next_component(vtz, ICAL_ANY_COMPONENT);
1333 
1334         memset(&obs, 0, sizeof(struct observance));
1335         obs.offset_from = obs.offset_to = INT_MAX;
1336         obs.is_daylight = (icalcomponent_isa(comp) == ICAL_XDAYLIGHT_COMPONENT);
1337 
1338         /* Grab the properties that we require to expand recurrences */
1339         for (prop = icalcomponent_get_first_property(comp, ICAL_ANY_PROPERTY);
1340              prop;
1341              prop = icalcomponent_get_next_property(comp, ICAL_ANY_PROPERTY)) {
1342 
1343             switch (icalproperty_isa(prop)) {
1344             case ICAL_TZNAME_PROPERTY:
1345                 obs.name = icalproperty_get_tzname(prop);
1346                 break;
1347 
1348             case ICAL_DTSTART_PROPERTY:
1349                 dtstart_prop = prop;
1350                 obs.onset = dtstart = icalproperty_get_dtstart(prop);
1351                 icalproperty_get_isstd_isgmt(prop, &obs);
1352                 if (last_dtstart && icaltime_compare(dtstart, *last_dtstart))
1353                     *last_dtstart = dtstart;
1354                 break;
1355 
1356             case ICAL_TZOFFSETFROM_PROPERTY:
1357                 obs.offset_from = icalproperty_get_tzoffsetfrom(prop);
1358                 break;
1359 
1360             case ICAL_TZOFFSETTO_PROPERTY:
1361                 obs.offset_to = icalproperty_get_tzoffsetto(prop);
1362                 break;
1363 
1364             case ICAL_RRULE_PROPERTY:
1365                 rrule_prop = prop;
1366                 break;
1367 
1368             case ICAL_RDATE_PROPERTY: {
1369                 struct rdate rdate = { prop, icalproperty_get_rdate(prop) };
1370 
1371                 icalarray_append(rdate_array, &rdate);
1372                 break;
1373             }
1374 
1375             default:
1376                 /* ignore all other properties */
1377                 break;
1378             }
1379         }
1380 
1381         /* We MUST have DTSTART, TZNAME, TZOFFSETFROM, and TZOFFSETTO */
1382         if (!dtstart_prop || !obs.name ||
1383             obs.offset_from == INT_MAX || obs.offset_to == INT_MAX) {
1384             icalarray_free(rdate_array);
1385             continue;
1386         }
1387 
1388         /* Adjust DTSTART observance to UTC */
1389         icaltime_adjust(&obs.onset, 0, 0, 0, -obs.offset_from);
1390         icaltime_set_utc(&obs.onset, 1);
1391 
1392         /* Check DTSTART vs window close */
1393         if (!icaltime_is_null_time(end) &&
1394             icaltime_compare(obs.onset, end) >= 0) {
1395             /* All observances occur on/after window close - remove component */
1396             icalcomponent_remove_component(vtz, comp);
1397             icalcomponent_free(comp);
1398 
1399             /* Actual range end == request range end */
1400             adjust_end = 0;
1401 
1402             /* Nothing else to do */
1403             icalarray_free(rdate_array);
1404             continue;
1405         }
1406 
1407         /* Check DTSTART vs window open */
1408         r = icaltime_compare(obs.onset, start);
1409         if (r < 0) {
1410             /* DTSTART is prior to our window open - check it vs tombstone */
1411             if (need_tomb) check_tombstone(&tombstone, &obs);
1412 
1413             /* Adjust it */
1414             trunc_dtstart = 1;
1415 
1416             /* Actual range start == request range start */
1417             adjust_start = 0;
1418         }
1419         else {
1420             /* DTSTART is on/after our window open */
1421             if (r == 0) need_tomb = 0;
1422 
1423             if (obsarray && !rrule_prop) {
1424                 /* Add the DTSTART observance to our array */
1425                 icalarray_append(obsarray, &obs);
1426             }
1427         }
1428 
1429         if (rrule_prop) {
1430             struct icalrecurrencetype rrule =
1431                 icalproperty_get_rrule(rrule_prop);
1432             icalrecur_iterator *ritr = NULL;
1433             unsigned eternal = icaltime_is_null_time(rrule.until);
1434             unsigned trunc_until = 0;
1435 
1436             if (eternal) {
1437                 if (obs.is_daylight) {
1438                     if (eternal_dst) *eternal_dst = comp;
1439                 }
1440                 else if (eternal_std) *eternal_std = comp;
1441             }
1442 
1443             /* Check RRULE duration */
1444             if (!eternal && icaltime_compare(rrule.until, start) < 0) {
1445                 /* RRULE ends prior to our window open -
1446                    check UNTIL vs tombstone */
1447                 obs.onset = rrule.until;
1448                 if (need_tomb) check_tombstone(&tombstone, &obs);
1449 
1450                 /* Remove RRULE */
1451                 icalcomponent_remove_property(comp, rrule_prop);
1452                 icalproperty_free(rrule_prop);
1453             }
1454             else {
1455                 /* RRULE ends on/after our window open */
1456                 if (!icaltime_is_null_time(end) &&
1457                     (eternal || icaltime_compare(rrule.until, end) >= 0)) {
1458                     /* RRULE ends after our window close - need to adjust it */
1459                     trunc_until = 1;
1460                 }
1461 
1462                 if (!eternal) {
1463                     /* Adjust UNTIL to local time (for iterator) */
1464                     icaltime_adjust(&rrule.until, 0, 0, 0, obs.offset_from);
1465                     icaltime_set_utc(&rrule.until, 0);
1466                 }
1467 
1468                 if (trunc_dtstart) {
1469                     /* Bump RRULE start to 1 year prior to our window open */
1470                     dtstart.year = start.year - 1;
1471                     dtstart.month = start.month;
1472                     dtstart.day = start.day;
1473                     icaltime_normalize(dtstart);
1474                 }
1475 
1476                 ritr = icalrecur_iterator_new(rrule, dtstart);
1477             }
1478 
1479             /* Process any RRULE observances within our window */
1480             if (ritr) {
1481                 icaltimetype recur, prev_onset;
1482 
1483                 /* Mark original DTSTART (UTC) */
1484                 dtstart = obs.onset;
1485 
1486                 while (!icaltime_is_null_time(obs.onset = recur =
1487                                               icalrecur_iterator_next(ritr))) {
1488                     unsigned ydiff;
1489 
1490                     /* Adjust observance to UTC */
1491                     icaltime_adjust(&obs.onset, 0, 0, 0, -obs.offset_from);
1492                     icaltime_set_utc(&obs.onset, 1);
1493 
1494                     if (trunc_until && icaltime_compare(obs.onset, end) >= 0) {
1495                         /* Observance is on/after window close */
1496 
1497                         /* Actual range end == request range end */
1498                         adjust_end = 0;
1499 
1500                         /* Check if DSTART is within 1yr of prev onset */
1501                         ydiff = prev_onset.year - dtstart.year;
1502                         if (ydiff <= 1) {
1503                             /* Remove RRULE */
1504                             icalcomponent_remove_property(comp, rrule_prop);
1505                             icalproperty_free(rrule_prop);
1506 
1507                             if (ydiff) {
1508                                 /* Add previous onset as RDATE */
1509                                 struct icaldatetimeperiodtype rdate = {
1510                                     prev_onset,
1511                                     icalperiodtype_null_period()
1512                                 };
1513                                 prop = icalproperty_new_rdate(rdate);
1514                                 icalcomponent_add_property(comp, prop);
1515                             }
1516                         }
1517                         else if (!eternal) {
1518                             /* Set UNTIL to previous onset */
1519                             rrule.until = prev_onset;
1520                             icalproperty_set_rrule(rrule_prop, rrule);
1521                         }
1522 
1523                         /* We're done */
1524                         break;
1525                     }
1526 
1527                     /* Check observance vs our window open */
1528                     r = icaltime_compare(obs.onset, start);
1529                     if (r < 0) {
1530                         /* Observance is prior to our window open -
1531                            check it vs tombstone */
1532                         if (ms_compatible) {
1533                             /* XXX  We don't want to move DTSTART of the RRULE
1534                                as Outlook/Exchange doesn't appear to like
1535                                truncating the frontend of RRULEs */
1536                             need_tomb = 0;
1537                             trunc_dtstart = 0;
1538                             if (proleptic_prop) {
1539                                 icalcomponent_remove_property(vtz,
1540                                                               proleptic_prop);
1541                                 icalproperty_free(proleptic_prop);
1542                                 proleptic_prop = NULL;
1543                             }
1544                         }
1545                         if (need_tomb) check_tombstone(&tombstone, &obs);
1546                     }
1547                     else {
1548                         /* Observance is on/after our window open */
1549                         if (r == 0) need_tomb = 0;
1550 
1551                         if (trunc_dtstart) {
1552                             /* Make this observance the new DTSTART */
1553                             icalproperty_set_dtstart(dtstart_prop, recur);
1554                             dtstart = obs.onset;
1555                             trunc_dtstart = 0;
1556 
1557                             if (last_dtstart &&
1558                                 icaltime_compare(dtstart, *last_dtstart) > 0) {
1559                                 *last_dtstart = dtstart;
1560                             }
1561 
1562                             /* Check if new DSTART is within 1yr of UNTIL */
1563                             ydiff = rrule.until.year - recur.year;
1564                             if (!trunc_until && ydiff <= 1) {
1565                                 /* Remove RRULE */
1566                                 icalcomponent_remove_property(comp, rrule_prop);
1567                                 icalproperty_free(rrule_prop);
1568 
1569                                 if (ydiff) {
1570                                     /* Add UNTIL as RDATE */
1571                                     struct icaldatetimeperiodtype rdate = {
1572                                         rrule.until,
1573                                         icalperiodtype_null_period()
1574                                     };
1575                                     prop = icalproperty_new_rdate(rdate);
1576                                     icalcomponent_add_property(comp, prop);
1577                                 }
1578                             }
1579                         }
1580 
1581                         if (obsarray) {
1582                             /* Add the observance to our array */
1583                             icalarray_append(obsarray, &obs);
1584                         }
1585                         else if (!trunc_until) {
1586                             /* We're done */
1587                             break;
1588                         }
1589                     }
1590                     prev_onset = obs.onset;
1591                 }
1592                 icalrecur_iterator_free(ritr);
1593             }
1594         }
1595 
1596         /* Sort the RDATEs by onset */
1597         icalarray_sort(rdate_array, &rdate_compare);
1598 
1599         /* Check RDATEs */
1600         for (n = 0; n < rdate_array->num_elements; n++) {
1601             struct rdate *rdate = icalarray_element_at(rdate_array, n);
1602 
1603             if (n == 0 && icaltime_compare(rdate->date.time, dtstart) == 0) {
1604                 /* RDATE is same as DTSTART - remove it */
1605                 icalcomponent_remove_property(comp, rdate->prop);
1606                 icalproperty_free(rdate->prop);
1607                 continue;
1608             }
1609 
1610             obs.onset = rdate->date.time;
1611             icalproperty_get_isstd_isgmt(rdate->prop, &obs);
1612 
1613             /* Adjust observance to UTC */
1614             icaltime_adjust(&obs.onset, 0, 0, 0, -obs.offset_from);
1615             icaltime_set_utc(&obs.onset, 1);
1616 
1617             if (!icaltime_is_null_time(end) &&
1618                 icaltime_compare(obs.onset, end) >= 0) {
1619                 /* RDATE is after our window close - remove it */
1620                 icalcomponent_remove_property(comp, rdate->prop);
1621                 icalproperty_free(rdate->prop);
1622 
1623                 /* Actual range end == request range end */
1624                 adjust_end = 0;
1625 
1626                 continue;
1627             }
1628 
1629             r = icaltime_compare(obs.onset, start);
1630             if (r < 0) {
1631                 /* RDATE is prior to window open - check it vs tombstone */
1632                 if (need_tomb) check_tombstone(&tombstone, &obs);
1633 
1634                 /* Remove it */
1635                 icalcomponent_remove_property(comp, rdate->prop);
1636                 icalproperty_free(rdate->prop);
1637 
1638                 /* Actual range start == request range start */
1639                 adjust_start = 0;
1640             }
1641             else {
1642                 /* RDATE is on/after our window open */
1643                 if (r == 0) need_tomb = 0;
1644 
1645                 if (trunc_dtstart) {
1646                     /* Make this RDATE the new DTSTART */
1647                     icalproperty_set_dtstart(dtstart_prop,
1648                                              rdate->date.time);
1649                     trunc_dtstart = 0;
1650 
1651                     icalcomponent_remove_property(comp, rdate->prop);
1652                     icalproperty_free(rdate->prop);
1653                 }
1654 
1655                 if (obsarray) {
1656                     /* Add the observance to our array */
1657                     icalarray_append(obsarray, &obs);
1658                 }
1659             }
1660         }
1661         icalarray_free(rdate_array);
1662 
1663         /* Final check */
1664         if (trunc_dtstart) {
1665             /* All observances in comp occur prior to window open, remove it
1666                unless we haven't saved a tombstone comp of this type yet */
1667             if (icalcomponent_isa(comp) == ICAL_XDAYLIGHT_COMPONENT) {
1668                 if (!tomb_day) {
1669                     tomb_day = comp;
1670                     comp = NULL;
1671                 }
1672             }
1673             else if (!tomb_std) {
1674                 tomb_std = comp;
1675                 comp = NULL;
1676             }
1677 
1678             if (comp) {
1679                 icalcomponent_remove_component(vtz, comp);
1680                 icalcomponent_free(comp);
1681             }
1682         }
1683     }
1684 
1685     if (need_tomb && !icaltime_is_null_time(tombstone.onset)) {
1686         /* Need to add tombstone component/observance starting at window open
1687            as long as its not prior to start of TZ data */
1688         icalcomponent *tomb;
1689         icalproperty *prop, *nextp;
1690 
1691         if (obsarray) {
1692             /* Add the tombstone to our array */
1693             tombstone.onset = start;
1694             tombstone.is_gmt = tombstone.is_std = 1;
1695             icalarray_append(obsarray, &tombstone);
1696         }
1697 
1698         /* Determine which tombstone component we need */
1699         if (tombstone.is_daylight) {
1700             tomb = tomb_day;
1701             tomb_day = NULL;
1702         }
1703         else {
1704             tomb = tomb_std;
1705             tomb_std = NULL;
1706         }
1707 
1708         /* Set property values on our tombstone */
1709         for (prop = icalcomponent_get_first_property(tomb, ICAL_ANY_PROPERTY);
1710              prop; prop = nextp) {
1711 
1712             nextp = icalcomponent_get_next_property(tomb, ICAL_ANY_PROPERTY);
1713 
1714             switch (icalproperty_isa(prop)) {
1715             case ICAL_TZNAME_PROPERTY:
1716                 icalproperty_set_tzname(prop, tombstone.name);
1717                 break;
1718             case ICAL_TZOFFSETFROM_PROPERTY:
1719                 icalproperty_set_tzoffsetfrom(prop, tombstone.offset_from);
1720                 break;
1721             case ICAL_TZOFFSETTO_PROPERTY:
1722                 icalproperty_set_tzoffsetto(prop, tombstone.offset_to);
1723                 break;
1724             case ICAL_DTSTART_PROPERTY:
1725                 /* Adjust window open to local time */
1726                 icaltime_adjust(&start, 0, 0, 0, tombstone.offset_from);
1727                 icaltime_set_utc(&start, 0);
1728 
1729                 icalproperty_set_dtstart(prop, start);
1730                 break;
1731             default:
1732                 icalcomponent_remove_property(tomb, prop);
1733                 icalproperty_free(prop);
1734                 break;
1735             }
1736         }
1737 
1738         /* Remove X-PROLEPTIC-TZNAME as it no longer applies */
1739         if (proleptic_prop) {
1740             icalcomponent_remove_property(vtz, proleptic_prop);
1741             icalproperty_free(proleptic_prop);
1742         }
1743     }
1744 
1745     /* Remove any unused tombstone components */
1746     if (tomb_std) {
1747         icalcomponent_remove_component(vtz, tomb_std);
1748         icalcomponent_free(tomb_std);
1749     }
1750     if (tomb_day) {
1751         icalcomponent_remove_component(vtz, tomb_day);
1752         icalcomponent_free(tomb_day);
1753     }
1754 
1755     if (obsarray) {
1756         struct observance *obs;
1757 
1758         /* Sort the observances by onset */
1759         icalarray_sort(obsarray, &observance_compare);
1760 
1761         /* Set offset_to for tombstone, if necessary */
1762         obs = icalarray_element_at(obsarray, 0);
1763         if (!tombstone.offset_to) tombstone.offset_to = obs->offset_from;
1764 
1765         /* Adjust actual range if necessary */
1766         if (adjust_start) {
1767             *startp = obs->onset;
1768         }
1769         if (adjust_end) {
1770             obs = icalarray_element_at(obsarray, obsarray->num_elements-1);
1771             *endp = obs->onset;
1772             icaltime_adjust(endp, 0, 0, 0, 1);
1773         }
1774     }
1775 
1776     if (proleptic) *proleptic = &tombstone;
1777 }
1778 
1779 /* Perform a get action */
action_get(struct transaction_t * txn)1780 static int action_get(struct transaction_t *txn)
1781 {
1782     int r, precond;
1783     struct strlist *param;
1784     const char *tzid = txn->req_tgt.resource;
1785     struct zoneinfo zi;
1786     time_t lastmod;
1787     icaltimetype start = icaltime_null_time(), end = icaltime_null_time();
1788     char *data = NULL;
1789     unsigned long datalen = 0;
1790     struct resp_body_t *resp_body = &txn->resp_body;
1791     struct mime_type_t *mime = NULL;
1792     const char **hdr;
1793 
1794     /* Check/find requested MIME type:
1795        1st entry in gparams->mime_types array MUST be default MIME type */
1796     if ((param = hash_lookup("format", &txn->req_qparams))) {
1797         for (mime = tz_mime_types;
1798              mime->content_type && !is_mediatype(mime->content_type, param->s);
1799              mime++);
1800     }
1801     else if ((hdr = spool_getheader(txn->req_hdrs, "Accept")))
1802         mime = get_accept_type(hdr, tz_mime_types);
1803     else mime = tz_mime_types;
1804 
1805     if (!mime || !mime->content_type)
1806         return json_error_response(txn, TZ_INVALID_FORMAT, NULL, NULL);
1807 
1808     /* Sanity check the parameters */
1809     if ((param = hash_lookup("start", &txn->req_qparams))) {
1810         start = icaltime_from_string(param->s);
1811         if (param->next || !icaltime_is_utc(start)) {  /* once only, UTC */
1812             return json_error_response(txn, TZ_INVALID_START, param, &start);
1813         }
1814     }
1815 
1816     if ((param = hash_lookup("end", &txn->req_qparams))) {
1817         end = icaltime_from_string(param->s);
1818         if (param->next || !icaltime_is_utc(end)  /* once only, UTC */
1819             || icaltime_compare(end, start) <= 0) {  /* end MUST be > start */
1820             return json_error_response(txn, TZ_INVALID_END, param, &end);
1821         }
1822     }
1823 
1824     /* Get info record from the database */
1825     if ((r = zoneinfo_lookup(tzid, &zi))) {
1826         return (r == CYRUSDB_NOTFOUND ?
1827                 json_error_response(txn, TZ_NOT_FOUND, NULL, NULL)
1828                 : HTTP_SERVER_ERROR);
1829     }
1830 
1831     /* Generate ETag & Last-Modified from info record */
1832     assert(!buf_len(&txn->buf));
1833     buf_printf(&txn->buf, "%u-" TIME_T_FMT, strhash(tzid), zi.dtstamp);
1834     lastmod = zi.dtstamp;
1835     freestrlist(zi.data);
1836 
1837     /* Check any preconditions, including range request */
1838     txn->flags.ranges = 1;
1839     precond = check_precond(txn, buf_cstring(&txn->buf), lastmod);
1840 
1841     switch (precond) {
1842     case HTTP_OK:
1843     case HTTP_PARTIAL:
1844     case HTTP_NOT_MODIFIED:
1845         /* Fill in Content-Type, ETag, Last-Modified, and Expires */
1846         resp_body->type = mime->content_type;
1847         resp_body->etag = buf_cstring(&txn->buf);
1848         resp_body->lastmod = lastmod;
1849         resp_body->maxage = 86400;  /* 24 hrs */
1850         txn->flags.cc |= CC_MAXAGE | CC_REVALIDATE;
1851         if (!httpd_userisanonymous) txn->flags.cc |= CC_PUBLIC;
1852 
1853         if (precond != HTTP_NOT_MODIFIED) break;
1854 
1855         GCC_FALLTHROUGH
1856 
1857     default:
1858         /* We failed a precondition - don't perform the request */
1859         resp_body->type = NULL;
1860         return precond;
1861     }
1862 
1863 
1864     if (txn->meth != METH_HEAD) {
1865         static struct buf pathbuf = BUF_INITIALIZER;
1866         const char *p, *path, *proto, *host, *msg_base = NULL;
1867         size_t msg_size = 0;
1868         icalcomponent *ical, *vtz;
1869         icalproperty *prop;
1870         struct buf *buf = NULL;
1871         int fd;
1872 
1873         /* Open, mmap, and parse the file */
1874         buf_reset(&pathbuf);
1875         buf_printf(&pathbuf, "%s/%s.ics", zoneinfo_dir, tzid);
1876         path = buf_cstring(&pathbuf);
1877         if ((fd = open(path, O_RDONLY)) == -1) return HTTP_SERVER_ERROR;
1878 
1879         map_refresh(fd, 1, &msg_base, &msg_size, MAP_UNKNOWN_LEN, path, NULL);
1880         if (!msg_base) return HTTP_SERVER_ERROR;
1881 
1882         ical = icalparser_parse_string(msg_base);
1883         map_free(&msg_base, &msg_size);
1884         close(fd);
1885 
1886         vtz = icalcomponent_get_first_component(ical, ICAL_VTIMEZONE_COMPONENT);
1887         prop = icalcomponent_get_first_property(vtz, ICAL_TZID_PROPERTY);
1888 
1889         if ((zi.type == ZI_LINK) &&
1890             !icalcomponent_get_first_property(vtz, ICAL_TZIDALIASOF_PROPERTY)) {
1891             /* Add TZID-ALIAS-OF */
1892             const char *aliasof = icalproperty_get_tzid(prop);
1893             icalproperty *atzid = icalproperty_new_tzidaliasof(aliasof);
1894 
1895             icalcomponent_add_property(vtz, atzid);
1896 
1897             /* Substitute TZID alias */
1898             icalproperty_set_tzid(prop, tzid);
1899         }
1900 
1901         /* Start constructing TZURL */
1902         buf_reset(&pathbuf);
1903         http_proto_host(txn->req_hdrs, &proto, &host);
1904         buf_printf(&pathbuf, "%s://%s%s/zones/",
1905                    proto, host, namespace_tzdist.prefix);
1906 
1907         /* Escape '/' and ' ' in tzid */
1908         for (p = tzid; *p; p++) {
1909             switch (*p) {
1910             case '/':
1911             case ' ':
1912                 buf_printf(&pathbuf, "%%%02X", *p);
1913                 break;
1914 
1915             default:
1916                 buf_putc(&pathbuf, *p);
1917                 break;
1918             }
1919         }
1920 
1921         if (!icaltime_is_null_time(start) || !icaltime_is_null_time(end)) {
1922 
1923             if (!icaltime_is_null_time(end)) {
1924                 /* Add TZUNTIL to VTIMEZONE */
1925                 icalproperty *tzuntil = icalproperty_new_tzuntil(end);
1926                 icalcomponent_add_property(vtz, tzuntil);
1927             }
1928 
1929             /* Add truncation parameter(s) to TZURL */
1930             buf_printf(&pathbuf, "?%s", URI_QUERY(txn->req_uri));
1931 
1932             if (!strncmp(mime->content_type, "application/tzif", 16)) {
1933                 /* Truncate and convert the VTIMEZONE */
1934                 bit32 leapcnt = 0;
1935 
1936                 if (!strcmp(mime->content_type + 16, "-leap"))
1937                     leapcnt = leap_seconds->count - 2;
1938 
1939                 buf =_icaltimezone_as_tzif(ical, leapcnt, &start, &end);
1940             }
1941             else {
1942                 /* Truncate the VTIMEZONE */
1943                 truncate_vtimezone(vtz, &start, &end, NULL, NULL,
1944                                    NULL, NULL, NULL, 0);
1945             }
1946         }
1947 
1948         /* Set TZURL property */
1949         prop = icalproperty_new_tzurl(buf_cstring(&pathbuf));
1950         icalcomponent_add_property(vtz, prop);
1951 
1952         /* Convert to requested MIME type */
1953         if (!buf) buf = mime->from_object(ical);
1954         datalen = buf_len(buf);
1955         data = buf_release(buf);
1956         buf_destroy(buf);
1957 
1958         /* Set Content-Disposition filename */
1959         buf_setcstr(&pathbuf, tzid);
1960         if (mime->file_ext) buf_printf(&pathbuf, ".%s", mime->file_ext);
1961         resp_body->dispo.fname = buf_cstring(&pathbuf);
1962 
1963         txn->flags.vary |= VARY_ACCEPT;
1964 
1965         icalcomponent_free(ical);
1966     }
1967 
1968     write_body(precond, txn, data, datalen);
1969 
1970     if (data) free(data);
1971 
1972     return 0;
1973 }
1974 
1975 
1976 #define CTIME_FMT "%s %s %2d %02d:%02d:%02d %4d"
1977 #define CTIME_ARGS(tt) \
1978     wday[icaltime_day_of_week(tt)-1], monthname[tt.month-1], \
1979     tt.day, tt.hour, tt.minute, tt.second, tt.year
1980 
1981 
1982 /* Perform an expand action */
action_expand(struct transaction_t * txn)1983 static int action_expand(struct transaction_t *txn)
1984 {
1985     int r, precond, zdump = 0;
1986     struct strlist *param;
1987     const char *tzid = txn->req_tgt.resource;
1988     struct zoneinfo zi;
1989     time_t lastmod;
1990     icaltimetype start, end;
1991     struct resp_body_t *resp_body = &txn->resp_body;
1992     json_t *root = NULL;
1993 
1994     /* Sanity check the parameters */
1995     param = hash_lookup("start", &txn->req_qparams);
1996     if (!param || param->next)  /* mandatory, once only */
1997         return json_error_response(txn, TZ_INVALID_START, param, NULL);
1998 
1999     start = icaltime_from_string(param->s);
2000     if (!icaltime_is_utc(start))  /* MUST be UTC */
2001         return json_error_response(txn, TZ_INVALID_START, param, &start);
2002 
2003     param = hash_lookup("end", &txn->req_qparams);
2004     if (!param || param->next)  /* mandatory, once only */
2005         return json_error_response(txn, TZ_INVALID_END, param, NULL);
2006 
2007     end = icaltime_from_string(param->s);
2008     if (!icaltime_is_utc(end)  /* MUST be UTC */
2009         || icaltime_compare(end, start) <= 0) {  /* end MUST be > start */
2010         return json_error_response(txn, TZ_INVALID_END, param, &end);
2011     }
2012 
2013     /* Check requested format (debugging only) */
2014     if ((param = hash_lookup("format", &txn->req_qparams)) &&
2015         !strcmp(param->s, "application/zdump")) {
2016         /* Mimic zdump(8) -V output for comparison:
2017 
2018            For each zonename, print the times both one  second  before  and
2019            exactly at each detected time discontinuity, the time at one day
2020            less than the highest possible time value, and the time  at  the
2021            highest  possible  time value.  Each line is followed by isdst=D
2022            where D is positive, zero, or negative depending on whether  the
2023            given time is daylight saving time, standard time, or an unknown
2024            time type, respectively.  Each line is also followed by gmtoff=N
2025            if  the given local time is known to be N seconds east of Green‐
2026            wich.
2027         */
2028         zdump = 1;
2029     }
2030 
2031     /* Get info record from the database */
2032     if ((r = zoneinfo_lookup(tzid, &zi))) {
2033         return (r == CYRUSDB_NOTFOUND ?
2034                 json_error_response(txn, TZ_NOT_FOUND, NULL, NULL)
2035                 : HTTP_SERVER_ERROR);
2036     }
2037 
2038     /* Generate ETag & Last-Modified from info record */
2039     assert(!buf_len(&txn->buf));
2040     buf_printf(&txn->buf, "%u-" TIME_T_FMT, strhash(tzid), zi.dtstamp);
2041     lastmod = zi.dtstamp;
2042     freestrlist(zi.data);
2043 
2044     /* Check any preconditions, including range request */
2045     txn->flags.ranges = 1;
2046     precond = check_precond(txn, buf_cstring(&txn->buf), lastmod);
2047 
2048     switch (precond) {
2049     case HTTP_OK:
2050     case HTTP_PARTIAL:
2051     case HTTP_NOT_MODIFIED:
2052         /* Fill in ETag, Last-Modified, and Expires */
2053         resp_body->etag = buf_cstring(&txn->buf);
2054         resp_body->lastmod = lastmod;
2055         resp_body->maxage = 86400;  /* 24 hrs */
2056         txn->flags.cc |= CC_MAXAGE | CC_REVALIDATE;
2057         if (!httpd_userisanonymous) txn->flags.cc |= CC_PUBLIC;
2058 
2059         if (precond != HTTP_NOT_MODIFIED) break;
2060 
2061         GCC_FALLTHROUGH
2062 
2063     default:
2064         /* We failed a precondition - don't perform the request */
2065         resp_body->type = NULL;
2066         return precond;
2067     }
2068 
2069 
2070     if (txn->meth != METH_HEAD) {
2071         static struct buf pathbuf = BUF_INITIALIZER;
2072         const char *path, *msg_base = NULL;
2073         size_t msg_size = 0;
2074         icalcomponent *ical, *vtz;
2075         struct observance *proleptic;
2076         icalarray *obsarray;
2077         json_t *jobsarray;
2078         unsigned n;
2079         int fd;
2080 
2081         /* Open, mmap, and parse the file */
2082         buf_reset(&pathbuf);
2083         buf_printf(&pathbuf, "%s/%s.ics", zoneinfo_dir, tzid);
2084         path = buf_cstring(&pathbuf);
2085         if ((fd = open(path, O_RDONLY)) == -1) return HTTP_SERVER_ERROR;
2086 
2087         map_refresh(fd, 1, &msg_base, &msg_size, MAP_UNKNOWN_LEN, path, NULL);
2088         if (!msg_base) return HTTP_SERVER_ERROR;
2089 
2090         ical = icalparser_parse_string(msg_base);
2091         map_free(&msg_base, &msg_size);
2092         close(fd);
2093 
2094 
2095         /* Create an array of observances */
2096         obsarray = icalarray_new(sizeof(struct observance), 20);
2097         vtz = icalcomponent_get_first_component(ical, ICAL_VTIMEZONE_COMPONENT);
2098         truncate_vtimezone(vtz, &start, &end, obsarray, &proleptic,
2099                            NULL, NULL, NULL, 0);
2100 
2101         if (zdump) {
2102             struct buf *body = &txn->resp_body.payload;
2103             struct icaldurationtype off = icaldurationtype_null_duration();
2104             const char *prev_name = proleptic->name;
2105             int prev_isdst = proleptic->is_daylight;
2106 
2107             for (n = 0; n < obsarray->num_elements; n++) {
2108                 struct observance *obs = icalarray_element_at(obsarray, n);
2109                 struct icaltimetype local, ut;
2110 
2111                 /* Skip any no-ops as zdump doesn't output them */
2112                 if (obs->offset_from == obs->offset_to
2113                     && prev_isdst == obs->is_daylight
2114                     && !strcmp(prev_name, obs->name)) continue;
2115 
2116                 /* UT and local time 1 second before onset */
2117                 off.seconds = -1;
2118                 ut = icaltime_add(obs->onset, off);
2119 
2120                 off.seconds = obs->offset_from;
2121                 local = icaltime_add(ut, off);
2122 
2123                 buf_printf(body,
2124                            "%s  " CTIME_FMT " UT = " CTIME_FMT " %s"
2125                            " isdst=%d gmtoff=%d\n",
2126                            tzid, CTIME_ARGS(ut), CTIME_ARGS(local),
2127                            prev_name, prev_isdst, obs->offset_from);
2128 
2129                 /* UT and local time at onset */
2130                 icaltime_adjust(&ut, 0, 0, 0, 1);
2131 
2132                 off.seconds = obs->offset_to;
2133                 local = icaltime_add(ut, off);
2134 
2135                 buf_printf(body,
2136                            "%s  " CTIME_FMT " UT = " CTIME_FMT " %s"
2137                            " isdst=%d gmtoff=%d\n",
2138                            tzid, CTIME_ARGS(ut), CTIME_ARGS(local),
2139                            obs->name, obs->is_daylight, obs->offset_to);
2140 
2141                 prev_name = obs->name;
2142                 prev_isdst = obs->is_daylight;
2143             }
2144         }
2145         else {
2146             /* Start constructing our response */
2147             root = json_pack("{s:s}", "tzid", tzid);
2148             if (!root) {
2149                 txn->error.desc = "Unable to create JSON response";
2150                 return HTTP_SERVER_ERROR;
2151             }
2152 
2153             json_object_set_new(root, "start",
2154                                 json_string(icaltime_as_iso_string(start)));
2155             json_object_set_new(root, "end",
2156                                 json_string(icaltime_as_iso_string(end)));
2157 
2158             /* Add observances to JSON array */
2159             jobsarray = json_array();
2160             for (n = 0; n < obsarray->num_elements; n++) {
2161                 struct observance *obs = icalarray_element_at(obsarray, n);
2162 
2163                 json_array_append_new(jobsarray,
2164                                       json_pack(
2165                                           "{s:s s:s s:i s:i}",
2166                                           "name", obs->name,
2167                                           "onset",
2168                                           icaltime_as_iso_string(obs->onset),
2169                                           "utc-offset-from", obs->offset_from,
2170                                           "utc-offset-to", obs->offset_to));
2171             }
2172             json_object_set_new(root, "observances", jobsarray);
2173         }
2174         icalarray_free(obsarray);
2175 
2176         icalcomponent_free(ical);
2177     }
2178 
2179     if (zdump) {
2180         struct resp_body_t *body = &txn->resp_body;
2181 
2182         body->type = "text/plain; charset=us-ascii";
2183 
2184         write_body(precond, txn,
2185                    buf_cstring(&body->payload), buf_len(&body->payload));
2186 
2187         return 0;
2188     }
2189     else {
2190         /* Output the JSON object */
2191         return json_response(precond, txn, root, NULL);
2192     }
2193 }
2194 
2195 
json_response(int code,struct transaction_t * txn,json_t * root,char ** resp)2196 static int json_response(int code, struct transaction_t *txn, json_t *root,
2197                          char **resp)
2198 {
2199     size_t flags = JSON_PRESERVE_ORDER;
2200     static char *buf = NULL;  /* keep generated data until next call */
2201     char *json = NULL;
2202 
2203     free(buf);
2204 
2205     if (root) {
2206         /* Dump JSON object into a text buffer */
2207         flags |= (config_httpprettytelemetry ? JSON_INDENT(2) : JSON_COMPACT);
2208         json = buf = json_dumps(root, flags);
2209         json_decref(root);
2210 
2211         if (!buf) {
2212             txn->error.desc = "Error dumping JSON object";
2213             return HTTP_SERVER_ERROR;
2214         }
2215         else if (resp) {
2216             if (*resp) free(*resp);
2217             *resp = buf;
2218             buf = NULL;
2219         }
2220     }
2221     else if (resp) json = *resp;
2222 
2223     /* Output the JSON object */
2224     if (code == HTTP_OK)
2225         txn->resp_body.type = "application/json; charset=utf-8";
2226     else
2227         txn->resp_body.type = "application/problem+json; charset=utf-8";
2228     write_body(code, txn, json, json ? strlen(json) : 0);
2229 
2230     return 0;
2231 }
2232 
2233 
2234 /* Array of parameter names - MUST be kept in sync with tz_err.et */
2235 static const char *param_names[] = {
2236     "action",
2237     "pattern",
2238     "format",
2239     "start",
2240     "end",
2241     "changedsince",
2242     "latitude",
2243     "longitude",
2244     "tzid"
2245 };
2246 
json_error_response(struct transaction_t * txn,long tz_code,struct strlist * param,icaltimetype * time)2247 static int json_error_response(struct transaction_t *txn, long tz_code,
2248                                struct strlist *param, icaltimetype *time)
2249 {
2250     long http_code = HTTP_BAD_REQUEST;
2251     const char *param_name, *fmt = NULL;
2252     json_t *root;
2253 
2254     param_name = param_names[tz_code - tz_err_base];
2255 
2256     if (!param) {
2257         switch (tz_code) {
2258         case TZ_INVALID_ACTION:
2259             fmt = "Request URI doesn't map to a known action";
2260             break;
2261 
2262         case TZ_INVALID_FORMAT:
2263             http_code = HTTP_NOT_ACCEPTABLE;
2264             fmt = "Unsupported media type";
2265             break;
2266 
2267         case TZ_NOT_FOUND:
2268             http_code = HTTP_NOT_FOUND;
2269             fmt = "Time zone identifier not found";
2270             break;
2271 
2272         default:
2273             fmt = "Missing %s parameter";
2274             break;
2275         }
2276     }
2277     else if (param->next) fmt = "Multiple %s parameters";
2278     else if (!param->s || !param->s[0]) fmt = "Missing %s value";
2279     else if (!time) fmt = "Invalid %s value";
2280     else if (!icaltime_is_utc(*time)) fmt = "Invalid %s UTC value";
2281     else fmt = "End date-time <= start date-time";
2282 
2283     assert(!buf_len(&txn->buf));
2284     buf_printf(&txn->buf, fmt, param_name);
2285 
2286     root = json_pack("{s:s s:s s:i}", "title", buf_cstring(&txn->buf),
2287                      "type", error_message(tz_code),
2288                      "status", atoi(error_message(http_code)));
2289     if (!root) {
2290         txn->error.desc = "Unable to create JSON response";
2291         return HTTP_SERVER_ERROR;
2292     }
2293 
2294     return json_response(http_code, txn, root, NULL);
2295 }
2296 
2297 
2298 #ifndef BIG_BANG
2299 #define BIG_BANG (- (1LL << 59))  /* from zic.c */
2300 #endif
2301 
2302 #ifndef INT32_MAX
2303 #define INT32_MAX 0x7fffffff
2304 #endif
2305 #ifndef INT32_MIN
2306 #define INT32_MIN (-INT32_MAX - 1)
2307 #endif
2308 
2309 #define NUM_LEAP_DAYS(y) ((y-1) / 4 - (y-1) / 100 + (y-1) / 400)
2310 #define NUM_YEAR_DAYS(y) (365 * y + NUM_LEAP_DAYS(y))
2311 
2312 /* Day of year offsets for each month.  Second array is for leap years. */
2313 static const int month_doy_offsets[2][12] = {
2314     /* jan  feb  mar  apr  may  jun  jul  aug  sep  oct  nov  dec */
2315     {    0,  31,  59,  90, 120, 151, 181, 212, 243, 273, 304, 334 },
2316     {    0,  31,  60,  91, 121, 152, 182, 213, 244, 274, 305, 335 }
2317 };
2318 
2319 /* Convert icaltimetype to 64-bit time_t.  0 = Jan 1 00:00:00 1970 UTC */
icaltime_to_gmtime64(const struct icaltimetype tt)2320 static long long int icaltime_to_gmtime64(const struct icaltimetype tt)
2321 {
2322     long long int days;
2323 
2324     days = NUM_YEAR_DAYS(tt.year) - NUM_YEAR_DAYS(1970);
2325     days += month_doy_offsets[icaltime_is_leap_year(tt.year)][tt.month - 1];
2326     days += tt.day - 1;
2327 
2328     return (((days * 24 + tt.hour) * 60 + tt.minute) * 60 + tt.second);
2329 }
2330 
2331 struct ttinfo {
2332     long int offset;      /* offset from GMT */
2333     unsigned char isdst;  /* transition time is for DST */
2334     unsigned char idx;    /* index into 'abbrev' buffer */
2335     unsigned char isstd;  /* transition time is in standard time */
2336     unsigned char isgmt;  /* transition time is in GMT */
2337 };
2338 
set_ttinfo(struct ttinfo * ttinfo,const struct observance * obs,unsigned char idx)2339 static void set_ttinfo(struct ttinfo *ttinfo,
2340                        const struct observance *obs, unsigned char idx)
2341 {
2342     ttinfo->offset = obs->offset_to;
2343     ttinfo->isdst = obs->is_daylight;
2344     ttinfo->isstd = obs->is_std;
2345     ttinfo->isgmt = obs->is_gmt;
2346     ttinfo->idx = idx;
2347 }
2348 
buf_append_utcoffset_as_iso_string(struct buf * buf,int off)2349 static void buf_append_utcoffset_as_iso_string(struct buf *buf, int off)
2350 {
2351     int h, m, s;
2352 
2353     h = -off/3600;
2354     m = (abs(off) % 3600) / 60;
2355     s = abs(off) % 60;
2356     buf_printf(buf, "%d", h);
2357     if (m || s) buf_printf(buf, ":%02d", m);
2358     if (s) buf_printf(buf, ":%02d", s);
2359 }
2360 
2361 /* Generate a POSIX rule from iCal RRULE.
2362    We assume that the RRULE parts are sane for a VTIMEZONE
2363    and all rules refer to a day of week in a single month. */
buf_append_rrule_as_posix_string(struct buf * buf,icalcomponent * comp)2364 static unsigned buf_append_rrule_as_posix_string(struct buf *buf,
2365                                                  icalcomponent *comp)
2366 {
2367     icalproperty *prop;
2368     icaltimetype at;
2369     struct icalrecurrencetype rrule;
2370     unsigned ver = '2';
2371     int hour;
2372 
2373     prop = icalcomponent_get_first_property(comp, ICAL_RRULE_PROPERTY);
2374     rrule = icalproperty_get_rrule(prop);
2375 
2376 #ifdef HAVE_RSCALE
2377     if (rrule.rscale && strcasecmp(rrule.rscale, "GREGORIAN")) {
2378         /* POSIX rules are based on Gregorian calendar only */
2379         return 0;
2380     }
2381 #endif
2382 
2383     prop = icalcomponent_get_first_property(comp, ICAL_DTSTART_PROPERTY);
2384     at = icalproperty_get_dtstart(prop);
2385     hour = at.hour;
2386 
2387     if (rrule.by_day[0] == ICAL_RECURRENCE_ARRAY_MAX) {
2388         /* date - Julian yday */
2389         buf_printf(buf, ",J%u", month_doy_offsets[0][at.month - 1] + at.day);
2390     }
2391     else {
2392         /* BYDAY */
2393         unsigned month;
2394         int week = icalrecurrencetype_day_position(rrule.by_day[0]);
2395         int wday = icalrecurrencetype_day_day_of_week(rrule.by_day[0]);
2396         int yday = rrule.by_year_day[0];
2397 
2398         if (yday != ICAL_RECURRENCE_ARRAY_MAX) {
2399             /* BYYEARDAY */
2400 
2401             if (yday >= 0 || hour) {
2402                 /* Bogus?  Either way, we can't handle this */
2403                 return 0;
2404             }
2405 
2406             /* Rewrite as last (wday-1) @ 24:00 */
2407             week = -1;
2408             wday--;
2409             hour = 24;
2410 
2411             /* Find month that contains this yday */
2412             yday += 365;
2413             for (month = 0; month < 12; month++) {
2414                 if (yday <= month_doy_offsets[0][month]) break;
2415             }
2416         }
2417         else {
2418             /* BYMONTH */
2419             int mday = rrule.by_month_day[0];
2420 
2421             month = rrule.by_month[0];
2422 
2423             if (mday != ICAL_RECURRENCE_ARRAY_MAX) {
2424                 /* MONTHDAY:  wday >= mday */
2425 
2426                 /* Need to use an extension to POSIX: -167 <= hour <= 167 */
2427                 ver = '3';
2428 
2429                 if (mday + 7 == icaltime_days_in_month(month, 0)) {
2430                     /* Rewrite as last (wday+1) @ hour < 0 */
2431                     week = -1;
2432                     wday++;
2433                     hour -= 24;
2434                 }
2435                 else {
2436                     /* Rewrite as nth (wday-offset) @ hour > 24 */
2437                     unsigned mday_offset;
2438 
2439                     week = (mday - 1) / 7 + 1;
2440                     mday_offset = mday - ((week - 1) * 7 + 1);
2441                     wday -= mday_offset;
2442                     hour += 24 * mday_offset;
2443                 }
2444             }
2445         }
2446 
2447         /* date - month, week, wday */
2448         buf_printf(buf, ",M%u.%u.%u", month,
2449                    (week + 6) % 6,   /* normalize; POSIX uses 5 for last (-1) */
2450                    (wday + 6) % 7);  /* normalize; POSIX is 0-based */
2451     }
2452 
2453     /* time - default is 02:00:00 */
2454     if (hour != 2 || at.minute || at.second) {
2455         buf_printf(buf, "/%d", hour);
2456         if (at.minute || at.second) buf_printf(buf, ":%02u", at.minute);
2457         if (at.second) buf_printf(buf, ":%02u", at.second);
2458     }
2459 
2460     return ver;
2461 }
2462 
2463 /* Convert VTIMEZONE into tzif format (draft-murchison-tzdist-tzif) */
_icaltimezone_as_tzif(icalcomponent * ical,bit32 leapcnt,icaltimetype * startp,icaltimetype * endp)2464 static struct buf *_icaltimezone_as_tzif(icalcomponent* ical, bit32 leapcnt,
2465                                          icaltimetype *startp, icaltimetype *endp)
2466 {
2467     icalcomponent *vtz, *eternal_std = NULL, *eternal_dst = NULL;
2468     icalarray *obsarray;
2469     struct observance *proleptic;
2470     icaltimetype start = icaltime_null_time();
2471     icaltimetype end = icaltime_from_day_of_year(1, 2100);
2472     icaltimetype last_dtstart = icaltime_null_time();
2473     char header[] =  {
2474         'T', 'Z', 'i', 'f',   /* magic */
2475         '2',                  /* version */
2476         0, 0, 0, 0, 0,        /* reserved */
2477         0, 0, 0, 0, 0,        /* reserved */
2478         0, 0, 0, 0, 0         /* reserved */
2479     };
2480     struct transition {
2481         long long int t;      /* transition time */
2482         unsigned char idx;    /* index into 'types' array */
2483     } *times = NULL;
2484     struct ttinfo types[256]; /* only indexed by unsigned char */
2485     struct buf *tzif, posix = BUF_INITIALIZER, abbrev = BUF_INITIALIZER;
2486     struct observance *obs;
2487     unsigned do_bit64;
2488     struct leapsec *leap = NULL;
2489     bit32 leap_init = 0, leap_sec = 0;
2490 
2491     tzif = buf_new();
2492 
2493     vtz = icalcomponent_get_first_component(ical, ICAL_VTIMEZONE_COMPONENT);
2494     if (!vtz) return tzif;
2495 
2496     if (leapcnt) {
2497         leap = ptrarray_nth(leap_seconds, 1);
2498         leap_init = leap->sec;
2499     }
2500 
2501     if (!startp) startp = &start;
2502     if (!endp || icaltime_is_null_time(*endp)) endp = &end;
2503 
2504     /* Create an array of observances */
2505     obsarray = icalarray_new(sizeof(struct observance), 100);
2506     truncate_vtimezone(vtz, startp, endp, obsarray,
2507                        &proleptic, &eternal_std, &eternal_dst, &last_dtstart, 0);
2508 
2509     /* Create an array of transitions */
2510     times = xmalloc((obsarray->num_elements+1) * sizeof(struct transition));
2511 
2512     /* Try to create POSIX tz rule */
2513     if (eternal_dst) {
2514         unsigned d_ver, s_ver;
2515 
2516         if ((d_ver = buf_append_rrule_as_posix_string(&posix, eternal_dst)) &&
2517             (s_ver = buf_append_rrule_as_posix_string(&posix, eternal_std))) {
2518             /* Set format version */
2519             header[4] = d_ver | s_ver;
2520         }
2521         else {
2522             /* Can't create rule */
2523             buf_reset(&posix);
2524         }
2525     }
2526 
2527     /* Add two tzif datasets:
2528        The first using 32-bit times and the second using 64-bit times. */
2529     for (do_bit64 = 0; do_bit64 <= 1; do_bit64++) {
2530         long long int epoch = do_bit64 ? BIG_BANG : INT32_MIN;
2531         struct observance *prev_obs = proleptic;
2532         bit32 timecnt = 0, typecnt = 0;
2533         int leapidx = 2;
2534         size_t n;
2535 
2536         buf_reset(&abbrev);
2537 
2538         leap_sec = 0;
2539         if (leapcnt) leap = ptrarray_nth(leap_seconds, leapidx);
2540 
2541         /* Populate array of transitions & types */
2542         for (n = 0; n < obsarray->num_elements; n++) {
2543             long long int t;
2544             unsigned typeidx;
2545             icaltimetype tt_1601 = icaltime_from_string("1601-01-01T00:00:00Z");
2546 
2547             obs = icalarray_element_at(obsarray, n);
2548             t = icaltime_to_gmtime64(obs->onset);
2549             icaltime_adjust(&tt_1601, 0, 0, 0, -obs->offset_to);
2550 
2551             if (obs->onset.year > 2037 &&
2552                 (!do_bit64 || obs->onset.year > last_dtstart.year)) {
2553                 /* tzdata doesn't seem to go any further */
2554                 break;
2555             }
2556             else if (!timecnt) {
2557                 if (t > epoch && proleptic->onset.year < 0) {
2558                     /* Insert a tombstone prior to first real transition */
2559                     t = epoch;
2560                     obs = prev_obs;
2561 
2562                     /* Need to reprocess current observance */
2563                     n--;
2564                 }
2565                 else {
2566                     /* Reset types and abbreviations */
2567                     typecnt = 0;
2568                     buf_reset(&abbrev);
2569                 }
2570             }
2571             else if (!icaltime_compare(obs->onset, tt_1601)) {
2572                 /* Skip vzic tombstone for YEAR_MINIMUM */
2573                 continue;
2574             }
2575             else if (obs->offset_from == obs->offset_to
2576                 && prev_obs->is_daylight == obs->is_daylight
2577                 && prev_obs->is_std == obs->is_std
2578                 && prev_obs->is_gmt == obs->is_gmt
2579                 && !strcmp(prev_obs->name, obs->name)) {
2580                 /* Skip any no-ops */
2581                 continue;
2582             }
2583             prev_obs = obs;
2584 
2585             /* Check for existing type */
2586             for (typeidx = 0; typeidx < typecnt; typeidx++) {
2587                 if ((obs->offset_to == types[typeidx].offset) &&
2588                     (obs->is_daylight == types[typeidx].isdst) &&
2589                     (obs->is_std == types[typeidx].isstd) &&
2590                     (obs->is_gmt == types[typeidx].isgmt) &&
2591                     !strcmp(obs->name, buf_cstring(&abbrev) + types[typeidx].idx))
2592                     break;
2593             }
2594 
2595             if (typeidx == typecnt) {
2596                 /* Didn't find existing type */
2597                 const char *p = buf_base(&abbrev);
2598                 const char *endp = p + buf_len(&abbrev);
2599 
2600                 /* Check for existing abbreviation */
2601                 while (p < endp) {
2602                     if (!strcmp(p, obs->name)) break;
2603                     p += strlen(p) + 1;
2604                 }
2605 
2606                 /* Add new type */
2607                 set_ttinfo(&types[typecnt++], obs, p - buf_base(&abbrev));
2608 
2609                 if (p == endp) {
2610                     /* Add new abbreviation (including the NUL) */
2611                     buf_appendmap(&abbrev, obs->name, strlen(obs->name) + 1);
2612                 }
2613             }
2614 
2615             if (t < epoch) {
2616                 /* Skip transitions earlier than our epoch */
2617                 continue;
2618             }
2619 
2620             /* Add transition */
2621             if (leapcnt) {
2622                 while (t >= leap->t && leapidx < leap_seconds->count) {
2623                     leap_sec = leap->sec - leap_init;
2624                     if (++leapidx < leap_seconds->count)
2625                         leap = ptrarray_nth(leap_seconds, leapidx);
2626                 }
2627                 t += leap_sec;
2628             }
2629             times[timecnt].t = t;
2630             times[timecnt].idx = typeidx;
2631             timecnt++;
2632         }
2633 
2634 
2635         /* Output dataset */
2636 
2637         /* Header */
2638         buf_appendmap(tzif, header, sizeof(header));
2639         buf_appendbit32(tzif, typecnt);           /* isgmtcnt */
2640         buf_appendbit32(tzif, typecnt);           /* isstdcnt */
2641         buf_appendbit32(tzif, leapcnt);           /* leapcnt */
2642         buf_appendbit32(tzif, timecnt);           /* timecnt */
2643         buf_appendbit32(tzif, typecnt);           /* typecnt */
2644         buf_appendbit32(tzif, buf_len(&abbrev));  /* charcnt */
2645 
2646         /* Transition times */
2647         for (n = 0; n < timecnt; n++) {
2648             if (do_bit64) buf_appendbit64(tzif, times[n].t);
2649             else buf_appendbit32(tzif, times[n].t);
2650         }
2651 
2652         /* Transition time indices */
2653         for (n = 0; n < timecnt; n++) buf_putc(tzif, times[n].idx);
2654 
2655         /* Types structures */
2656         for (n = 0; n < typecnt; n++) {
2657             buf_appendbit32(tzif, types[n].offset);
2658             buf_putc(tzif, types[n].isdst);
2659             buf_putc(tzif, types[n].idx);
2660         }
2661 
2662         /* Abbreviation array */
2663         buf_append(tzif, &abbrev);
2664 
2665         /* Leap second records */
2666         if (leapcnt) {
2667             leap_sec = 0;
2668 
2669             for (leapidx = 2; leapidx < leap_seconds->count; leapidx++) {
2670                 long long int t;
2671 
2672                 leap = ptrarray_nth(leap_seconds, leapidx);
2673                 t = leap->t + leap_sec;
2674                 if (do_bit64) buf_appendbit64(tzif, t);
2675                 else buf_appendbit32(tzif, t);
2676 
2677                 leap_sec = leap->sec - leap_init;
2678                 buf_appendbit32(tzif, leap_sec);
2679             }
2680         }
2681 
2682         /* Standard/wall indicators */
2683         for (n = 0; n < typecnt; n++) buf_putc(tzif, types[n].isstd);
2684 
2685         /* GMT/local indicators */
2686         for (n = 0; n < typecnt; n++) buf_putc(tzif, types[n].isgmt);
2687     }
2688 
2689     free(times);
2690     buf_free(&abbrev);
2691 
2692 
2693     /* POSIX timezone string */
2694     buf_putc(tzif, '\n');
2695 
2696     /* std offset [dst [offset] [,rule] ] */
2697     if (buf_len(&posix)) {
2698         /* Use POSIX rule */
2699         icalproperty *prop;
2700         int stdoff, dstoff;
2701 
2702         /* std name */
2703         prop = icalcomponent_get_first_property(eternal_std,
2704                                                 ICAL_TZNAME_PROPERTY);
2705         buf_appendcstr(tzif, icalproperty_get_tzname(prop));
2706 
2707         /* std offset */
2708         prop = icalcomponent_get_first_property(eternal_std,
2709                                                 ICAL_TZOFFSETTO_PROPERTY);
2710         stdoff = icalproperty_get_tzoffsetto(prop);
2711         buf_append_utcoffset_as_iso_string(tzif, stdoff);
2712 
2713         /* dst name */
2714         prop = icalcomponent_get_first_property(eternal_dst,
2715                                                 ICAL_TZNAME_PROPERTY);
2716         buf_appendcstr(tzif, icalproperty_get_tzname(prop));
2717 
2718         /* dst offset */
2719         prop = icalcomponent_get_first_property(eternal_dst,
2720                                                 ICAL_TZOFFSETTO_PROPERTY);
2721         dstoff = icalproperty_get_tzoffsetto(prop);
2722         if (dstoff - stdoff != 3600) {  /* default is 1hr from std */
2723             buf_append_utcoffset_as_iso_string(tzif, dstoff);
2724         }
2725 
2726         /* rule */
2727         buf_append(tzif, &posix);
2728     }
2729     else if (!eternal_dst &&
2730              !icalcomponent_get_tzuntil_property(vtz)) {
2731         /* Use last observance as fixed offset */
2732         obs = icalarray_element_at(obsarray, obsarray->num_elements - 1);
2733 
2734         /* std name */
2735         if (obs->name[0] == ':' ||
2736             strcspn(obs->name, ",+-0123456789") < strlen(obs->name)) {
2737             buf_printf(tzif, "<%s>", obs->name);
2738         }
2739         else buf_appendcstr(tzif, obs->name);
2740 
2741         /* std offset */
2742         buf_append_utcoffset_as_iso_string(tzif, obs->offset_to);
2743     }
2744     buf_putc(tzif, '\n');
2745 
2746     buf_free(&posix);
2747     icalarray_free(obsarray);
2748 
2749     return tzif;
2750 }
2751 
tzdist_truncate_vtimezone(icalcomponent * vtz,icaltimetype * startp,icaltimetype * endp)2752 static void tzdist_truncate_vtimezone(icalcomponent *vtz,
2753                                       icaltimetype *startp, icaltimetype *endp)
2754 {
2755     truncate_vtimezone(vtz, startp, endp, NULL, NULL, NULL, NULL, NULL,
2756                        1 /* ms_compatible */);
2757 }
2758 
icaltimezone_as_tzif(icalcomponent * ical)2759 static struct buf *icaltimezone_as_tzif(icalcomponent* ical)
2760 {
2761     return _icaltimezone_as_tzif(ical, 0, NULL, NULL);
2762 }
2763 
icaltimezone_as_tzif_leap(icalcomponent * ical)2764 static struct buf *icaltimezone_as_tzif_leap(icalcomponent* ical)
2765 {
2766     return _icaltimezone_as_tzif(ical, leap_seconds->count - 2, NULL, NULL);
2767 }
2768 
tz_from_tzid(const char * tzid)2769 static icaltimezone *tz_from_tzid(const char *tzid)
2770 {
2771     if (!tzid)
2772         return NULL;
2773 
2774     /* libical doesn't return the UTC singleton for Etc/UTC */
2775     if (!strcmp(tzid, "Etc/UTC") || !strcmp(tzid, "UTC"))
2776         return icaltimezone_get_utc_timezone();
2777 
2778     return icaltimezone_get_builtin_timezone(tzid);
2779 }
2780 
collect_timezones_cb(icalparameter * param,void * data)2781 static void collect_timezones_cb(icalparameter *param, void *data)
2782 {
2783     ptrarray_t *tzs = (ptrarray_t*) data;
2784     int i;
2785     icaltimezone *tz;
2786 
2787     tz = tz_from_tzid(icalparameter_get_tzid(param));
2788     if (!tz) {
2789         return;
2790     }
2791     for (i = 0; i < tzs->count; i++) {
2792         if (ptrarray_nth(tzs, i) == tz) {
2793             return;
2794         }
2795     }
2796     ptrarray_push(tzs, tz);
2797 }
2798 
icalcomponent_add_required_timezones(icalcomponent * ical)2799 EXPORTED void icalcomponent_add_required_timezones(icalcomponent *ical)
2800 {
2801     icalcomponent *comp, *tzcomp, *next;
2802     icalproperty *prop;
2803     struct icalperiodtype span;
2804     ptrarray_t tzs = PTRARRAY_INITIALIZER;
2805 
2806     /* Determine recurrence span. */
2807     comp = icalcomponent_get_first_real_component(ical);
2808     span = icalrecurrenceset_get_utc_timespan(ical, icalcomponent_isa(comp),
2809                                               NULL, NULL, NULL, NULL);
2810 
2811     /* Remove all VTIMEZONE components for known TZIDs. This operation is
2812      * a bit hairy: we could expunge a timezone which is in use by an ical
2813      * property that is unknown to us. But since we don't know what to
2814      * look for, we can't make sure to preserve these timezones. */
2815     for (tzcomp = icalcomponent_get_first_component(ical,
2816                                                     ICAL_VTIMEZONE_COMPONENT);
2817          tzcomp;
2818          tzcomp = next) {
2819 
2820         next = icalcomponent_get_next_component(ical,
2821                 ICAL_VTIMEZONE_COMPONENT);
2822 
2823         prop = icalcomponent_get_first_property(tzcomp, ICAL_TZID_PROPERTY);
2824         if (prop) {
2825             const char *tzid = icalproperty_get_tzid(prop);
2826             if (tzid && tz_from_tzid(tzid)) {
2827                 icalcomponent_remove_component(ical, tzcomp);
2828                 icalcomponent_free(tzcomp);
2829             }
2830         }
2831     }
2832 
2833     /* Collect timezones by TZID */
2834     icalcomponent_foreach_tzid(ical, collect_timezones_cb, &tzs);
2835 
2836     /* Now add each timezone, truncated by this events span. */
2837     int i;
2838     for (i = 0; i < tzs.count; i++) {
2839         icaltimezone *tz = ptrarray_nth(&tzs, i);
2840 
2841         /* Clone tz to overwrite its TZID property. */
2842         icalcomponent *tzcomp =
2843             icalcomponent_clone(icaltimezone_get_component(tz));
2844         icalproperty *tzprop =
2845             icalcomponent_get_first_property(tzcomp, ICAL_TZID_PROPERTY);
2846         icalproperty_set_tzid(tzprop, icaltimezone_get_location(tz));
2847 
2848         /* Truncate the timezone to the events timespan. */
2849         tzdist_truncate_vtimezone(tzcomp, &span.start, &span.end);
2850 
2851         if (icaltime_as_timet_with_zone(span.end, NULL) < caldav_eternity) {
2852             /* Add TZUNTIL to timezone */
2853             icalproperty *tzuntil = icalproperty_new_tzuntil(span.end);
2854             icalcomponent_add_property(tzcomp, tzuntil);
2855         }
2856 
2857         /* Strip any COMMENT property */
2858         /* XXX  These were added by KSM in a previous version of vzic,
2859            but libical doesn't allow them in its restrictions checks */
2860         tzprop = icalcomponent_get_first_property(tzcomp, ICAL_COMMENT_PROPERTY);
2861         if (tzprop) {
2862             icalcomponent_remove_property(tzcomp, tzprop);
2863             icalproperty_free(tzprop);
2864         }
2865 
2866         /* Add the truncated timezone. */
2867         icalcomponent_add_component(ical, tzcomp);
2868     }
2869 
2870     ptrarray_fini(&tzs);
2871 }
2872