f20be42795efb2b46b19cb90b9098f72ddd85b29
[citadel.git] / webcit / ical_dezonify.c
1 // Function to go through an ical component set and convert all non-UTC
2 // date/time properties to UTC.  It also strips out any VTIMEZONE
3 // subcomponents afterwards, because they're irrelevant.
4 //
5 // Everything here will work on both components and subcomponents.  If subcomponents are discovered it will recurse through them.
6 //
7 // Copyright (c) 2002-2024 by the citadel.org team (Art Cancro et al)
8 // This program is open source software.  Use, duplication, or disclosure is subject to the GNU General Public License v3.
9
10 #include "webcit.h"
11 #include "webserver.h"
12
13 // Figure out which time zone needs to be used for timestamps that are
14 // not UTC and do not have a time zone specified.
15 icaltimezone *get_default_icaltimezone(void) {
16
17         icaltimezone *zone = NULL;
18         const char *default_zone_name = ChrPtr(WC->serv_info->serv_default_cal_zone);
19
20         if (!zone) {
21                 zone = icaltimezone_get_builtin_timezone(default_zone_name);
22         }
23         if (!zone) {
24                 syslog(LOG_WARNING, "Unable to load '%s' time zone.  Defaulting to UTC.\n", default_zone_name);
25                 zone = icaltimezone_get_utc_timezone();
26         }
27         if (!zone) {
28                 syslog(LOG_ERR, "Unable to load UTC time zone!\n");
29         }
30         return zone;
31 }
32
33
34 // Back end function for ical_dezonify()
35 //
36 // We supply this with the master component, the relevant component,
37 // and the property (which will be a DTSTART, DTEND, etc.)
38 // which we want to convert to UTC.
39 void ical_dezonify_backend(icalcomponent *cal, icalcomponent *rcal, icalproperty *prop) {
40
41         icaltimezone *t = NULL;
42         icalparameter *param;
43         const char *tzid = NULL;
44         struct icaltimetype TheTime;
45         int utc_declared_as_tzid = 0;   // Component declared 'TZID=GMT' instead of using Z syntax
46
47         // Give me nothing and I will give you nothing in return.
48         if (cal == NULL) return;
49
50         // Hunt for a TZID parameter in this property.
51         param = icalproperty_get_first_parameter(prop, ICAL_TZID_PARAMETER);
52
53         // Get the stringish name of this TZID.
54         if (param != NULL) {
55                 tzid = icalparameter_get_tzid(param);
56
57                 // Convert it to an icaltimezone type.
58                 if (tzid != NULL) {
59 #ifdef DBG_ICAL
60                         syslog(LOG_DEBUG, "                * Stringy supplied timezone is: '%s'\n", tzid);
61 #endif
62                         if ( (!strcasecmp(tzid, "UTC")) || (!strcasecmp(tzid, "GMT")) ) {
63                                 utc_declared_as_tzid = 1;
64 #ifdef DBG_ICAL
65                                 syslog(LOG_DEBUG, "                * ...and we handle that internally.\n");
66 #endif
67                         }
68                         else {
69                                 // try attached first
70                                 t = icalcomponent_get_timezone(cal, tzid);
71 #ifdef DBG_ICAL
72                                 syslog(LOG_DEBUG, "                * ...and I %s have tzdata for that zone.\n",
73                                         (t ? "DO" : "DO NOT")
74                                 );
75 #endif
76                                 // then try built-in timezones
77                                 if (!t) {
78                                         t = icaltimezone_get_builtin_timezone(tzid);
79 #ifdef DBG_ICAL
80                                         if (t) {
81                                                 syslog(LOG_DEBUG, "                * Using system tzdata!\n");
82                                         }
83 #endif
84                                 }
85                         }
86                 }
87
88         }
89
90         // Now we know the timezone.  Convert to UTC.
91
92         if (icalproperty_isa(prop) == ICAL_DTSTART_PROPERTY) {
93                 TheTime = icalproperty_get_dtstart(prop);
94         }
95         else if (icalproperty_isa(prop) == ICAL_DTEND_PROPERTY) {
96                 TheTime = icalproperty_get_dtend(prop);
97         }
98         else if (icalproperty_isa(prop) == ICAL_DUE_PROPERTY) {
99                 TheTime = icalproperty_get_due(prop);
100         }
101         else if (icalproperty_isa(prop) == ICAL_EXDATE_PROPERTY) {
102                 TheTime = icalproperty_get_exdate(prop);
103         }
104         else {
105                 return;
106         }
107
108 #ifdef DBG_ICAL
109         syslog(LOG_DEBUG, "                * Was: %s\n", icaltime_as_ical_string(TheTime));
110 #endif
111
112         if (icaltime_is_utc(TheTime)) {
113 #ifdef DBG_ICAL
114                 syslog(LOG_DEBUG, "                * This property is ALREADY UTC.\n");
115 #endif
116         }
117
118         else if (utc_declared_as_tzid) {
119 #ifdef DBG_ICAL
120                 syslog(LOG_DEBUG, "                * Replacing '%s' TZID with 'Z' suffix.\n", tzid);
121 #endif
122                 TheTime.zone = icaltimezone_get_utc_timezone();
123         }
124
125         else {
126                 // Do the conversion.
127                 if (t != NULL) {
128 #ifdef DBG_ICAL
129                         syslog(LOG_DEBUG, "                * Timezone prop found.  Converting to UTC.\n");
130 #endif
131                 }
132                 else {
133 #ifdef DBG_ICAL
134                         syslog(LOG_DEBUG, "                * Converting default timezone to UTC.\n");
135 #endif
136                 }
137
138                 if (t == NULL) {
139                         t = get_default_icaltimezone();
140                 }
141                 icaltimezone_convert_time(&TheTime, t, icaltimezone_get_utc_timezone());
142                 TheTime.zone = icaltimezone_get_utc_timezone();
143         }
144
145         icalproperty_remove_parameter_by_kind(prop, ICAL_TZID_PARAMETER);
146 #ifdef DBG_ICAL
147         syslog(LOG_DEBUG, "                * Now: %s\n", icaltime_as_ical_string(TheTime));
148 #endif
149
150         // Now add the converted property back in.
151         if (icalproperty_isa(prop) == ICAL_DTSTART_PROPERTY) {
152                 icalproperty_set_dtstart(prop, TheTime);
153         }
154         else if (icalproperty_isa(prop) == ICAL_DTEND_PROPERTY) {
155                 icalproperty_set_dtend(prop, TheTime);
156         }
157         else if (icalproperty_isa(prop) == ICAL_DUE_PROPERTY) {
158                 icalproperty_set_due(prop, TheTime);
159         }
160         else if (icalproperty_isa(prop) == ICAL_EXDATE_PROPERTY) {
161                 icalproperty_set_exdate(prop, TheTime);
162         }
163 }
164
165
166 // Recursive portion of ical_dezonify()
167 void ical_dezonify_recurse(icalcomponent *cal, icalcomponent *rcal) {
168         icalcomponent *c;
169         icalproperty *p;
170
171         // Recurse through all subcomponents *except* VTIMEZONE ones.
172         for (   c=icalcomponent_get_first_component(rcal, ICAL_ANY_COMPONENT);
173                 c != NULL;
174                 c = icalcomponent_get_next_component(rcal, ICAL_ANY_COMPONENT)
175         ) {
176                 if (icalcomponent_isa(c) != ICAL_VTIMEZONE_COMPONENT) {
177                         ical_dezonify_recurse(cal, c);
178                 }
179         }
180
181         // Now look for DTSTART and DTEND properties
182         for (   p=icalcomponent_get_first_property(rcal, ICAL_ANY_PROPERTY);
183                 p != NULL;
184                 p = icalcomponent_get_next_property(rcal, ICAL_ANY_PROPERTY)
185         ) {
186                 if (
187                         (icalproperty_isa(p) == ICAL_DTSTART_PROPERTY)
188                         || (icalproperty_isa(p) == ICAL_DTEND_PROPERTY)
189                         || (icalproperty_isa(p) == ICAL_DUE_PROPERTY)
190                         || (icalproperty_isa(p) == ICAL_EXDATE_PROPERTY)
191                 ) {
192                         ical_dezonify_backend(cal, rcal, p);
193                 }
194         }
195 }
196
197
198 // Convert all DTSTART and DTEND properties in all subcomponents to UTC.
199 // This function will search any VTIMEZONE subcomponents to learn the
200 // relevant timezone information.
201 void ical_dezonify(icalcomponent *cal) {
202         icalcomponent *vt = NULL;
203
204 #ifdef DBG_ICAL
205         syslog(LOG_DEBUG, "ical_dezonify() started\n");
206 #endif
207
208         // Convert all times to UTC
209         ical_dezonify_recurse(cal, cal);
210
211         // Strip out VTIMEZONE subcomponents -- we don't need them anymore.
212         while (vt = icalcomponent_get_first_component(cal, ICAL_VTIMEZONE_COMPONENT), vt != NULL) {
213                 icalcomponent_remove_component(cal, vt);
214                 icalcomponent_free(vt);
215         }
216
217 #ifdef DBG_ICAL
218         syslog(LOG_DEBUG, "ical_dezonify() completed\n");
219 #endif
220 }