initial load of upstream version 1.06.32
[xmlrpc-c] / src / xmlrpc_datetime.c
1 #include "xmlrpc_config.h"
2
3 #include <time.h>
4 #include <stdlib.h>
5 #include <string.h>
6 #include <ctype.h>
7 #include <assert.h>
8
9 #include "bool.h"
10
11 #include "xmlrpc-c/base.h"
12 #include "xmlrpc-c/base_int.h"
13
14
15 /* Future work: the XMLRPC_TYPE_DATETIME xmlrpc_value should store the
16    datetime as something computation-friendly, not as a string.  The
17    XML-RPC XML parser should parse the string value and reject the XML if
18    it isn't valid.
19
20    But this file should remain the authority on datetimes, so the XML
21    parser and builder should call on routines in here to do that.
22
23    time_t won't work because it can't represent times before 1970 or
24    after 2038.  We need to figure out something better.
25 */
26
27
28 #ifdef WIN32
29
30 static const bool win32 = TRUE;
31 static const __int64 SECS_BETWEEN_EPOCHS = 11644473600;
32 static const __int64 SECS_TO_100NS = 10000000; /* 10^7 */
33
34
35 void UnixTimeToFileTime(const time_t t, LPFILETIME pft)
36 {
37     // Note that LONGLONG is a 64-bit value
38     LONGLONG ll;
39     ll = Int32x32To64(t, SECS_TO_100NS) + SECS_BETWEEN_EPOCHS * SECS_TO_100NS;
40     pft->dwLowDateTime = (DWORD)ll;
41     pft->dwHighDateTime = ll >> 32;
42 }
43
44 void UnixTimeToSystemTime(const time_t t, LPSYSTEMTIME pst)
45 {
46     FILETIME ft;
47
48     UnixTimeToFileTime(t, &ft);
49     FileTimeToSystemTime(&ft, pst);
50 }
51
52 static void UnixTimeFromFileTime(xmlrpc_env *  const envP, LPFILETIME pft, time_t * const timeValueP) 
53
54     LONGLONG ll;
55
56     ll = ((LONGLONG)pft->dwHighDateTime << 32) + pft->dwLowDateTime;
57     /* convert to the Unix epoch */
58     ll -= (SECS_BETWEEN_EPOCHS * SECS_TO_100NS);
59     /* now convert to seconds */
60     ll /= SECS_TO_100NS; 
61
62     if ( (time_t)ll != ll )
63     {
64         //fail - value is too big for a time_t
65         xmlrpc_faultf(envP, "Does not indicate a valid date");
66         *timeValueP = (time_t)-1;
67         return;
68     }
69     *timeValueP = (time_t)ll;
70 }
71
72 static void UnixTimeFromSystemTime(xmlrpc_env *  const envP, LPSYSTEMTIME pst, time_t * const timeValueP) 
73 {
74     FILETIME filetime;
75
76     SystemTimeToFileTime(pst, &filetime); 
77     UnixTimeFromFileTime(envP, &filetime, timeValueP); 
78 }
79
80 #else
81 static const bool win32 = false;
82 #endif
83
84
85 static void
86 validateDatetimeType(xmlrpc_env *         const envP,
87                      const xmlrpc_value * const valueP) {
88
89     if (valueP->_type != XMLRPC_TYPE_DATETIME) {
90         xmlrpc_env_set_fault_formatted(
91             envP, XMLRPC_TYPE_ERROR, "Value of type %s supplied where "
92             "type %s was expected.", 
93             xmlrpc_typeName(valueP->_type), 
94             xmlrpc_typeName(XMLRPC_TYPE_DATETIME));
95     }
96 }
97
98
99
100 void
101 xmlrpc_read_datetime_str(xmlrpc_env *         const envP,
102                          const xmlrpc_value * const valueP,
103                          const char **        const stringValueP) {
104     
105     validateDatetimeType(envP, valueP);
106     if (!envP->fault_occurred) {
107         const char * const contents = 
108             XMLRPC_MEMBLOCK_CONTENTS(char, &valueP->_block);
109         *stringValueP = strdup(contents);
110         if (*stringValueP == NULL)
111             xmlrpc_env_set_fault_formatted(
112                 envP, XMLRPC_INTERNAL_ERROR, "Unable to allocate space "
113                 "for datetime string");
114     }
115 }
116
117
118
119 void
120 xmlrpc_read_datetime_str_old(xmlrpc_env *         const envP,
121                              const xmlrpc_value * const valueP,
122                              const char **        const stringValueP) {
123     
124     validateDatetimeType(envP, valueP);
125     if (!envP->fault_occurred) {
126         *stringValueP = XMLRPC_MEMBLOCK_CONTENTS(char, &valueP->_block);
127     }
128 }
129
130
131
132 static void
133 parseDateNumbers(const char * const t,
134                  unsigned int * const YP,
135                  unsigned int * const MP,
136                  unsigned int * const DP,
137                  unsigned int * const hP,
138                  unsigned int * const mP,
139                  unsigned int * const sP) {
140
141     char year[4+1];
142     char month[2+1];
143     char day[2+1];
144     char hour[2+1];
145     char minute[2+1];
146     char second[2+1];
147
148     assert(strlen(t) == 17);
149
150     year[0]   = t[ 0];
151     year[1]   = t[ 1];
152     year[2]   = t[ 2];
153     year[3]   = t[ 3];
154     year[4]   = '\0';
155
156     month[0]  = t[ 4];
157     month[1]  = t[ 5];
158     month[2]  = '\0';
159
160     day[0]    = t[ 6];
161     day[1]    = t[ 7];
162     day[2]    = '\0';
163
164     assert(t[ 8] == 'T');
165
166     hour[0]   = t[ 9];
167     hour[1]   = t[10];
168     hour[2]   = '\0';
169
170     assert(t[11] == ':');
171
172     minute[0] = t[12];
173     minute[1] = t[13];
174     minute[2] = '\0';
175
176     assert(t[14] == ':');
177
178     second[0] = t[15];
179     second[1] = t[16];
180     second[2] = '\0';
181
182     *YP = atoi(year);
183     *MP = atoi(month);
184     *DP = atoi(day);
185     *hP = atoi(hour);
186     *mP = atoi(minute);
187     *sP = atoi(second);
188 }
189
190
191 #ifdef HAVE_SETENV
192 xmlrpc_bool const haveSetenv = true;
193 #else
194 xmlrpc_bool const haveSetenv = false;
195 static void
196 setenv(const char * const name ATTR_UNUSED,
197        const char * const value ATTR_UNUSED,
198        int          const replace ATTR_UNUSED) {
199     assert(FALSE);
200 }
201 #endif
202
203 static void
204 makeTimezoneUtc(xmlrpc_env *  const envP,
205                 const char ** const oldTzP) {
206
207     const char * const tz = getenv("TZ");
208
209 #ifdef WIN32
210         /* Windows implementation does not exist */
211         assert(TRUE);
212 #endif
213
214     if (haveSetenv) {
215         if (tz) {
216             *oldTzP = strdup(tz);
217             if (*oldTzP == NULL)
218                 xmlrpc_faultf(envP, "Unable to get memory to save TZ "
219                               "environment variable.");
220         } else
221             *oldTzP = NULL;
222
223         if (!envP->fault_occurred)
224             setenv("TZ", "", 1);
225     } else {
226         if (tz && strlen(tz) == 0) {
227             /* Everything's fine.  Nothing to change or restore */
228         } else {
229             /* Note that putenv() is not sufficient.  You can't restore
230                the original value with that, because it sets a pointer into
231                your own storage.
232             */
233             xmlrpc_faultf(envP, "Your TZ environment variable is not a "
234                           "null string and your C library does not have "
235                           "setenv(), so we can't change it.");
236         }
237     }
238 }
239     
240
241
242 static void
243 restoreTimezone(const char * const oldTz) {
244
245     if (haveSetenv) {
246         setenv("TZ", oldTz, 1);
247         free((char*)oldTz);
248     }
249 }
250
251
252
253 static void
254 mkAbsTimeWin32(xmlrpc_env * const envP ATTR_UNUSED,
255                struct tm    const brokenTime ATTR_UNUSED,
256                time_t     * const timeValueP ATTR_UNUSED) {
257 #ifdef WIN32
258     /* Windows Implementation */
259     SYSTEMTIME stbrokenTime;
260
261     stbrokenTime.wHour = brokenTime.tm_hour;
262     stbrokenTime.wMinute = brokenTime.tm_min;
263     stbrokenTime.wSecond = brokenTime.tm_sec;
264     stbrokenTime.wMonth = brokenTime.tm_mon;
265     stbrokenTime.wDay = brokenTime.tm_mday;
266     stbrokenTime.wYear = brokenTime.tm_year;
267     stbrokenTime.wMilliseconds = 0;
268
269     /* When the date string is parsed into the tm structure, it was
270        modified to decrement the month count by one and convert the
271        4 digit year to a two digit year.  We undo what the parser 
272        did to make it a true SYSTEMTIME structure, then convert this
273        structure into a UNIX time_t structure
274     */
275     stbrokenTime.wYear+=1900;
276     stbrokenTime.wMonth+=1;
277
278     UnixTimeFromSystemTime(envP, &stbrokenTime,timeValueP);
279 #endif
280 }
281
282
283 static void
284 mkAbsTimeUnix(xmlrpc_env * const envP ATTR_UNUSED,
285               struct tm    const brokenTime ATTR_UNUSED,
286               time_t     * const timeValueP ATTR_UNUSED) {
287
288 #ifndef WIN32
289     time_t mktimeResult;
290     const char * oldTz;
291     struct tm mktimeWork;
292
293     /* We use mktime() to create the time_t because it's the
294        best we have available, but mktime() takes a local time
295        argument, and we have absolute time.  So we fake it out
296        by temporarily setting the timezone to UTC.
297     */
298     makeTimezoneUtc(envP, &oldTz);
299
300     if (!envP->fault_occurred) {
301         mktimeWork = brokenTime;
302         mktimeResult = mktime(&mktimeWork);
303
304         restoreTimezone(oldTz);
305
306         if (mktimeResult == (time_t)-1)
307             xmlrpc_faultf(envP, "Does not indicate a valid date");
308         else
309             *timeValueP = mktimeResult;
310     }
311 #endif
312 }
313  
314
315
316 static void
317 mkAbsTime(xmlrpc_env * const envP,
318           struct tm    const brokenTime,
319           time_t     * const timeValueP) {
320
321     if (win32)
322         mkAbsTimeWin32(envP, brokenTime, timeValueP);
323     else
324         mkAbsTimeUnix(envP, brokenTime, timeValueP);
325 }
326
327
328
329 static void
330 validateFormat(xmlrpc_env * const envP,
331                const char * const t) {
332
333     if (strlen(t) != 17)
334         xmlrpc_faultf(envP, "%u characters instead of 15.", strlen(t));
335     else if (t[8] != 'T')
336         xmlrpc_faultf(envP, "9th character is '%c', not 'T'", t[8]);
337     else {
338         unsigned int i;
339
340         for (i = 0; i < 8 && !envP->fault_occurred; ++i)
341             if (!isdigit(t[i]))
342                 xmlrpc_faultf(envP, "Not a digit: '%c'", t[i]);
343
344         if (!isdigit(t[9]))
345             xmlrpc_faultf(envP, "Not a digit: '%c'", t[9]);
346         if (!isdigit(t[10]))
347             xmlrpc_faultf(envP, "Not a digit: '%c'", t[10]);
348         if (t[11] != ':')
349             xmlrpc_faultf(envP, "Not a colon: '%c'", t[11]);
350         if (!isdigit(t[12]))
351             xmlrpc_faultf(envP, "Not a digit: '%c'", t[12]);
352         if (!isdigit(t[13]))
353             xmlrpc_faultf(envP, "Not a digit: '%c'", t[13]);
354         if (t[14] != ':')
355             xmlrpc_faultf(envP, "Not a colon: '%c'", t[14]);
356         if (!isdigit(t[15]))
357             xmlrpc_faultf(envP, "Not a digit: '%c'", t[15]);
358         if (!isdigit(t[16]))
359             xmlrpc_faultf(envP, "Not a digit: '%c'", t[16]);
360     }
361 }        
362
363
364
365 static void
366 parseDatetime(xmlrpc_env * const envP,
367               const char * const t,
368               time_t *     const timeValueP) {
369 /*----------------------------------------------------------------------------
370    Parse a time in the format stored in an xmlrpc_value and return the
371    time that it represents.
372
373    t[] is the input time string.  We return the result as *timeValueP.
374
375    Example of the format we parse: "19980717T14:08:55"
376    Note that this is not quite ISO 8601.  It's a bizarre combination of
377    two ISO 8601 formats.
378 -----------------------------------------------------------------------------*/
379     validateFormat(envP, t);
380
381     if (!envP->fault_occurred) {
382         unsigned int Y, M, D, h, m, s;
383         
384         parseDateNumbers(t, &Y, &M, &D, &h, &m, &s);
385         
386         if (Y < 1900)
387             xmlrpc_faultf(envP, "Year is too early to represent as "
388                           "a standard Unix time");
389         else {
390             struct tm brokenTime;
391             
392             brokenTime.tm_sec   = s;
393             brokenTime.tm_min   = m;
394             brokenTime.tm_hour  = h;
395             brokenTime.tm_mday  = D;
396             brokenTime.tm_mon   = M - 1;
397             brokenTime.tm_year  = Y - 1900;
398             
399             mkAbsTime(envP, brokenTime, timeValueP);
400         }
401     }
402 }
403
404
405
406 void
407 xmlrpc_read_datetime_sec(xmlrpc_env *         const envP,
408                          const xmlrpc_value * const valueP,
409                          time_t *             const timeValueP) {
410     
411     validateDatetimeType(envP, valueP);
412     if (!envP->fault_occurred)
413         parseDatetime(envP,
414                       XMLRPC_MEMBLOCK_CONTENTS(char, &valueP->_block),
415                       timeValueP);
416 }
417
418
419
420 xmlrpc_value *
421 xmlrpc_datetime_new_str(xmlrpc_env * const envP, 
422                         const char * const value) {
423
424     xmlrpc_value * valP;
425
426     xmlrpc_createXmlrpcValue(envP, &valP);
427
428     if (!envP->fault_occurred) {
429         valP->_type = XMLRPC_TYPE_DATETIME;
430
431         XMLRPC_TYPED_MEM_BLOCK_INIT(
432             char, envP, &valP->_block, strlen(value) + 1);
433         if (!envP->fault_occurred) {
434             char * const contents =
435                 XMLRPC_TYPED_MEM_BLOCK_CONTENTS(char, &valP->_block);
436             strcpy(contents, value);
437         }
438         if (envP->fault_occurred)
439             free(valP);
440     }
441     return valP;
442 }
443
444
445
446 xmlrpc_value *
447 xmlrpc_datetime_new_sec(xmlrpc_env * const envP, 
448                         time_t       const value) {
449
450     xmlrpc_value * valP;
451     
452     xmlrpc_createXmlrpcValue(envP, &valP);
453
454     if (!envP->fault_occurred) {
455         struct tm brokenTime;
456         char timeString[64];
457         
458         valP->_type = XMLRPC_TYPE_DATETIME;
459
460         gmtime_r(&value, &brokenTime);
461         
462         /* Note that this format is NOT ISO 8601 -- it's a bizarre
463            hybrid of two ISO 8601 formats.
464         */
465         strftime(timeString, sizeof(timeString), "%Y%m%dT%H:%M:%S", 
466                  &brokenTime);
467         
468         XMLRPC_TYPED_MEM_BLOCK_INIT(
469             char, envP, &valP->_block, strlen(timeString) + 1);
470         if (!envP->fault_occurred) {
471             char * const contents =
472                 XMLRPC_TYPED_MEM_BLOCK_CONTENTS(char, &valP->_block);
473             
474             strcpy(contents, timeString);
475         }
476         if (envP->fault_occurred)
477             free(valP);
478     }
479     return valP;
480 }