initial load of upstream version 1.06.32
[xmlrpc-c] / src / xmlrpc_datetime.c
diff --git a/src/xmlrpc_datetime.c b/src/xmlrpc_datetime.c
new file mode 100644 (file)
index 0000000..bd190d2
--- /dev/null
@@ -0,0 +1,480 @@
+#include "xmlrpc_config.h"
+
+#include <time.h>
+#include <stdlib.h>
+#include <string.h>
+#include <ctype.h>
+#include <assert.h>
+
+#include "bool.h"
+
+#include "xmlrpc-c/base.h"
+#include "xmlrpc-c/base_int.h"
+
+
+/* Future work: the XMLRPC_TYPE_DATETIME xmlrpc_value should store the
+   datetime as something computation-friendly, not as a string.  The
+   XML-RPC XML parser should parse the string value and reject the XML if
+   it isn't valid.
+
+   But this file should remain the authority on datetimes, so the XML
+   parser and builder should call on routines in here to do that.
+
+   time_t won't work because it can't represent times before 1970 or
+   after 2038.  We need to figure out something better.
+*/
+
+
+#ifdef WIN32
+
+static const bool win32 = TRUE;
+static const __int64 SECS_BETWEEN_EPOCHS = 11644473600;
+static const __int64 SECS_TO_100NS = 10000000; /* 10^7 */
+
+
+void UnixTimeToFileTime(const time_t t, LPFILETIME pft)
+{
+    // Note that LONGLONG is a 64-bit value
+    LONGLONG ll;
+    ll = Int32x32To64(t, SECS_TO_100NS) + SECS_BETWEEN_EPOCHS * SECS_TO_100NS;
+    pft->dwLowDateTime = (DWORD)ll;
+    pft->dwHighDateTime = ll >> 32;
+}
+
+void UnixTimeToSystemTime(const time_t t, LPSYSTEMTIME pst)
+{
+    FILETIME ft;
+
+    UnixTimeToFileTime(t, &ft);
+    FileTimeToSystemTime(&ft, pst);
+}
+
+static void UnixTimeFromFileTime(xmlrpc_env *  const envP, LPFILETIME pft, time_t * const timeValueP) 
+{ 
+    LONGLONG ll;
+
+    ll = ((LONGLONG)pft->dwHighDateTime << 32) + pft->dwLowDateTime;
+    /* convert to the Unix epoch */
+    ll -= (SECS_BETWEEN_EPOCHS * SECS_TO_100NS);
+    /* now convert to seconds */
+    ll /= SECS_TO_100NS; 
+
+    if ( (time_t)ll != ll )
+    {
+        //fail - value is too big for a time_t
+        xmlrpc_faultf(envP, "Does not indicate a valid date");
+        *timeValueP = (time_t)-1;
+        return;
+    }
+    *timeValueP = (time_t)ll;
+}
+
+static void UnixTimeFromSystemTime(xmlrpc_env *  const envP, LPSYSTEMTIME pst, time_t * const timeValueP) 
+{
+    FILETIME filetime;
+
+    SystemTimeToFileTime(pst, &filetime); 
+    UnixTimeFromFileTime(envP, &filetime, timeValueP); 
+}
+
+#else
+static const bool win32 = false;
+#endif
+
+
+static void
+validateDatetimeType(xmlrpc_env *         const envP,
+                     const xmlrpc_value * const valueP) {
+
+    if (valueP->_type != XMLRPC_TYPE_DATETIME) {
+        xmlrpc_env_set_fault_formatted(
+            envP, XMLRPC_TYPE_ERROR, "Value of type %s supplied where "
+            "type %s was expected.", 
+            xmlrpc_typeName(valueP->_type), 
+            xmlrpc_typeName(XMLRPC_TYPE_DATETIME));
+    }
+}
+
+
+
+void
+xmlrpc_read_datetime_str(xmlrpc_env *         const envP,
+                         const xmlrpc_value * const valueP,
+                         const char **        const stringValueP) {
+    
+    validateDatetimeType(envP, valueP);
+    if (!envP->fault_occurred) {
+        const char * const contents = 
+            XMLRPC_MEMBLOCK_CONTENTS(char, &valueP->_block);
+        *stringValueP = strdup(contents);
+        if (*stringValueP == NULL)
+            xmlrpc_env_set_fault_formatted(
+                envP, XMLRPC_INTERNAL_ERROR, "Unable to allocate space "
+                "for datetime string");
+    }
+}
+
+
+
+void
+xmlrpc_read_datetime_str_old(xmlrpc_env *         const envP,
+                             const xmlrpc_value * const valueP,
+                             const char **        const stringValueP) {
+    
+    validateDatetimeType(envP, valueP);
+    if (!envP->fault_occurred) {
+        *stringValueP = XMLRPC_MEMBLOCK_CONTENTS(char, &valueP->_block);
+    }
+}
+
+
+
+static void
+parseDateNumbers(const char * const t,
+                 unsigned int * const YP,
+                 unsigned int * const MP,
+                 unsigned int * const DP,
+                 unsigned int * const hP,
+                 unsigned int * const mP,
+                 unsigned int * const sP) {
+
+    char year[4+1];
+    char month[2+1];
+    char day[2+1];
+    char hour[2+1];
+    char minute[2+1];
+    char second[2+1];
+
+    assert(strlen(t) == 17);
+
+    year[0]   = t[ 0];
+    year[1]   = t[ 1];
+    year[2]   = t[ 2];
+    year[3]   = t[ 3];
+    year[4]   = '\0';
+
+    month[0]  = t[ 4];
+    month[1]  = t[ 5];
+    month[2]  = '\0';
+
+    day[0]    = t[ 6];
+    day[1]    = t[ 7];
+    day[2]    = '\0';
+
+    assert(t[ 8] == 'T');
+
+    hour[0]   = t[ 9];
+    hour[1]   = t[10];
+    hour[2]   = '\0';
+
+    assert(t[11] == ':');
+
+    minute[0] = t[12];
+    minute[1] = t[13];
+    minute[2] = '\0';
+
+    assert(t[14] == ':');
+
+    second[0] = t[15];
+    second[1] = t[16];
+    second[2] = '\0';
+
+    *YP = atoi(year);
+    *MP = atoi(month);
+    *DP = atoi(day);
+    *hP = atoi(hour);
+    *mP = atoi(minute);
+    *sP = atoi(second);
+}
+
+
+#ifdef HAVE_SETENV
+xmlrpc_bool const haveSetenv = true;
+#else
+xmlrpc_bool const haveSetenv = false;
+static void
+setenv(const char * const name ATTR_UNUSED,
+       const char * const value ATTR_UNUSED,
+       int          const replace ATTR_UNUSED) {
+    assert(FALSE);
+}
+#endif
+
+static void
+makeTimezoneUtc(xmlrpc_env *  const envP,
+                const char ** const oldTzP) {
+
+    const char * const tz = getenv("TZ");
+
+#ifdef WIN32
+       /* Windows implementation does not exist */
+       assert(TRUE);
+#endif
+
+    if (haveSetenv) {
+        if (tz) {
+            *oldTzP = strdup(tz);
+            if (*oldTzP == NULL)
+                xmlrpc_faultf(envP, "Unable to get memory to save TZ "
+                              "environment variable.");
+        } else
+            *oldTzP = NULL;
+
+        if (!envP->fault_occurred)
+            setenv("TZ", "", 1);
+    } else {
+        if (tz && strlen(tz) == 0) {
+            /* Everything's fine.  Nothing to change or restore */
+        } else {
+            /* Note that putenv() is not sufficient.  You can't restore
+               the original value with that, because it sets a pointer into
+               your own storage.
+            */
+            xmlrpc_faultf(envP, "Your TZ environment variable is not a "
+                          "null string and your C library does not have "
+                          "setenv(), so we can't change it.");
+        }
+    }
+}
+    
+
+
+static void
+restoreTimezone(const char * const oldTz) {
+
+    if (haveSetenv) {
+        setenv("TZ", oldTz, 1);
+        free((char*)oldTz);
+    }
+}
+
+
+
+static void
+mkAbsTimeWin32(xmlrpc_env * const envP ATTR_UNUSED,
+               struct tm    const brokenTime ATTR_UNUSED,
+               time_t     * const timeValueP ATTR_UNUSED) {
+#ifdef WIN32
+    /* Windows Implementation */
+    SYSTEMTIME stbrokenTime;
+
+    stbrokenTime.wHour = brokenTime.tm_hour;
+    stbrokenTime.wMinute = brokenTime.tm_min;
+    stbrokenTime.wSecond = brokenTime.tm_sec;
+    stbrokenTime.wMonth = brokenTime.tm_mon;
+    stbrokenTime.wDay = brokenTime.tm_mday;
+    stbrokenTime.wYear = brokenTime.tm_year;
+    stbrokenTime.wMilliseconds = 0;
+
+    /* When the date string is parsed into the tm structure, it was
+       modified to decrement the month count by one and convert the
+       4 digit year to a two digit year.  We undo what the parser 
+       did to make it a true SYSTEMTIME structure, then convert this
+       structure into a UNIX time_t structure
+    */
+    stbrokenTime.wYear+=1900;
+    stbrokenTime.wMonth+=1;
+
+    UnixTimeFromSystemTime(envP, &stbrokenTime,timeValueP);
+#endif
+}
+
+
+static void
+mkAbsTimeUnix(xmlrpc_env * const envP ATTR_UNUSED,
+              struct tm    const brokenTime ATTR_UNUSED,
+              time_t     * const timeValueP ATTR_UNUSED) {
+
+#ifndef WIN32
+    time_t mktimeResult;
+    const char * oldTz;
+    struct tm mktimeWork;
+
+    /* We use mktime() to create the time_t because it's the
+       best we have available, but mktime() takes a local time
+       argument, and we have absolute time.  So we fake it out
+       by temporarily setting the timezone to UTC.
+    */
+    makeTimezoneUtc(envP, &oldTz);
+
+    if (!envP->fault_occurred) {
+        mktimeWork = brokenTime;
+        mktimeResult = mktime(&mktimeWork);
+
+        restoreTimezone(oldTz);
+
+        if (mktimeResult == (time_t)-1)
+            xmlrpc_faultf(envP, "Does not indicate a valid date");
+        else
+            *timeValueP = mktimeResult;
+    }
+#endif
+}
+
+
+static void
+mkAbsTime(xmlrpc_env * const envP,
+          struct tm    const brokenTime,
+          time_t     * const timeValueP) {
+
+    if (win32)
+        mkAbsTimeWin32(envP, brokenTime, timeValueP);
+    else
+        mkAbsTimeUnix(envP, brokenTime, timeValueP);
+}
+
+
+
+static void
+validateFormat(xmlrpc_env * const envP,
+               const char * const t) {
+
+    if (strlen(t) != 17)
+        xmlrpc_faultf(envP, "%u characters instead of 15.", strlen(t));
+    else if (t[8] != 'T')
+        xmlrpc_faultf(envP, "9th character is '%c', not 'T'", t[8]);
+    else {
+        unsigned int i;
+
+        for (i = 0; i < 8 && !envP->fault_occurred; ++i)
+            if (!isdigit(t[i]))
+                xmlrpc_faultf(envP, "Not a digit: '%c'", t[i]);
+
+        if (!isdigit(t[9]))
+            xmlrpc_faultf(envP, "Not a digit: '%c'", t[9]);
+        if (!isdigit(t[10]))
+            xmlrpc_faultf(envP, "Not a digit: '%c'", t[10]);
+        if (t[11] != ':')
+            xmlrpc_faultf(envP, "Not a colon: '%c'", t[11]);
+        if (!isdigit(t[12]))
+            xmlrpc_faultf(envP, "Not a digit: '%c'", t[12]);
+        if (!isdigit(t[13]))
+            xmlrpc_faultf(envP, "Not a digit: '%c'", t[13]);
+        if (t[14] != ':')
+            xmlrpc_faultf(envP, "Not a colon: '%c'", t[14]);
+        if (!isdigit(t[15]))
+            xmlrpc_faultf(envP, "Not a digit: '%c'", t[15]);
+        if (!isdigit(t[16]))
+            xmlrpc_faultf(envP, "Not a digit: '%c'", t[16]);
+    }
+}        
+
+
+
+static void
+parseDatetime(xmlrpc_env * const envP,
+              const char * const t,
+              time_t *     const timeValueP) {
+/*----------------------------------------------------------------------------
+   Parse a time in the format stored in an xmlrpc_value and return the
+   time that it represents.
+
+   t[] is the input time string.  We return the result as *timeValueP.
+
+   Example of the format we parse: "19980717T14:08:55"
+   Note that this is not quite ISO 8601.  It's a bizarre combination of
+   two ISO 8601 formats.
+-----------------------------------------------------------------------------*/
+    validateFormat(envP, t);
+
+    if (!envP->fault_occurred) {
+        unsigned int Y, M, D, h, m, s;
+        
+        parseDateNumbers(t, &Y, &M, &D, &h, &m, &s);
+        
+        if (Y < 1900)
+            xmlrpc_faultf(envP, "Year is too early to represent as "
+                          "a standard Unix time");
+        else {
+            struct tm brokenTime;
+            
+            brokenTime.tm_sec   = s;
+            brokenTime.tm_min   = m;
+            brokenTime.tm_hour  = h;
+            brokenTime.tm_mday  = D;
+            brokenTime.tm_mon   = M - 1;
+            brokenTime.tm_year  = Y - 1900;
+            
+            mkAbsTime(envP, brokenTime, timeValueP);
+        }
+    }
+}
+
+
+
+void
+xmlrpc_read_datetime_sec(xmlrpc_env *         const envP,
+                         const xmlrpc_value * const valueP,
+                         time_t *             const timeValueP) {
+    
+    validateDatetimeType(envP, valueP);
+    if (!envP->fault_occurred)
+        parseDatetime(envP,
+                      XMLRPC_MEMBLOCK_CONTENTS(char, &valueP->_block),
+                      timeValueP);
+}
+
+
+
+xmlrpc_value *
+xmlrpc_datetime_new_str(xmlrpc_env * const envP, 
+                        const char * const value) {
+
+    xmlrpc_value * valP;
+
+    xmlrpc_createXmlrpcValue(envP, &valP);
+
+    if (!envP->fault_occurred) {
+        valP->_type = XMLRPC_TYPE_DATETIME;
+
+        XMLRPC_TYPED_MEM_BLOCK_INIT(
+            char, envP, &valP->_block, strlen(value) + 1);
+        if (!envP->fault_occurred) {
+            char * const contents =
+                XMLRPC_TYPED_MEM_BLOCK_CONTENTS(char, &valP->_block);
+            strcpy(contents, value);
+        }
+        if (envP->fault_occurred)
+            free(valP);
+    }
+    return valP;
+}
+
+
+
+xmlrpc_value *
+xmlrpc_datetime_new_sec(xmlrpc_env * const envP, 
+                        time_t       const value) {
+
+    xmlrpc_value * valP;
+    
+    xmlrpc_createXmlrpcValue(envP, &valP);
+
+    if (!envP->fault_occurred) {
+        struct tm brokenTime;
+        char timeString[64];
+        
+        valP->_type = XMLRPC_TYPE_DATETIME;
+
+        gmtime_r(&value, &brokenTime);
+        
+        /* Note that this format is NOT ISO 8601 -- it's a bizarre
+           hybrid of two ISO 8601 formats.
+        */
+        strftime(timeString, sizeof(timeString), "%Y%m%dT%H:%M:%S", 
+                 &brokenTime);
+        
+        XMLRPC_TYPED_MEM_BLOCK_INIT(
+            char, envP, &valP->_block, strlen(timeString) + 1);
+        if (!envP->fault_occurred) {
+            char * const contents =
+                XMLRPC_TYPED_MEM_BLOCK_CONTENTS(char, &valP->_block);
+            
+            strcpy(contents, timeString);
+        }
+        if (envP->fault_occurred)
+            free(valP);
+    }
+    return valP;
+}