605 lines
17 KiB
C
605 lines
17 KiB
C
#include "xmlrpc_config.h"
|
|
|
|
#define _XOPEN_SOURCE 600 /* Make sure strdup() is in <string.h> */
|
|
|
|
#include <time.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <ctype.h>
|
|
#include <assert.h>
|
|
#include <stdio.h>
|
|
#if MSVCRT
|
|
#include <windows.h>
|
|
#endif
|
|
|
|
#include "bool.h"
|
|
#include "mallocvar.h"
|
|
|
|
#include "xmlrpc-c/c_util.h"
|
|
#include "xmlrpc-c/base.h"
|
|
#include "xmlrpc-c/base_int.h"
|
|
#include "xmlrpc-c/string_int.h"
|
|
#include "xmlrpc-c/time_int.h"
|
|
|
|
|
|
#if HAVE_REGEX
|
|
#include <regex.h>
|
|
#endif
|
|
|
|
#if MSVCRT
|
|
|
|
static const __int64 SECS_BETWEEN_EPOCHS = 11644473600;
|
|
static const __int64 SECS_TO_100NS = 10000000; /* 10^7 */
|
|
|
|
|
|
void
|
|
UnixTimeToFileTime(time_t const t,
|
|
LPFILETIME const pft) {
|
|
|
|
int64_t const ll =
|
|
Int32x32To64(t, SECS_TO_100NS) + SECS_BETWEEN_EPOCHS * SECS_TO_100NS;
|
|
|
|
pft->dwLowDateTime = (DWORD)ll;
|
|
pft->dwHighDateTime = (DWORD)(ll >> 32);
|
|
}
|
|
|
|
|
|
|
|
void
|
|
UnixTimeToSystemTime(time_t const t,
|
|
LPSYSTEMTIME const pst) {
|
|
FILETIME ft;
|
|
|
|
UnixTimeToFileTime(t, &ft);
|
|
FileTimeToSystemTime(&ft, pst);
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
UnixTimeFromFileTime(xmlrpc_env * const envP,
|
|
LPFILETIME const pft,
|
|
time_t * const timeValueP) {
|
|
|
|
int64_t const WinEpoch100Ns =
|
|
((int64_t)pft->dwHighDateTime << 32) + pft->dwLowDateTime;
|
|
int64_t const unixEpoch100Ns =
|
|
WinEpoch100Ns - (SECS_BETWEEN_EPOCHS * SECS_TO_100NS);
|
|
int64_t const unixEpochSeconds =
|
|
unixEpoch100Ns / SECS_TO_100NS;
|
|
|
|
if ((time_t)unixEpochSeconds != unixEpochSeconds) {
|
|
/* Value is too big for a time_t; fail. */
|
|
xmlrpc_faultf(envP, "Does not indicate a valid date");
|
|
*timeValueP = (time_t)(-1);
|
|
} else
|
|
*timeValueP = (time_t)unixEpochSeconds;
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
UnixTimeFromSystemTime(xmlrpc_env * const envP,
|
|
LPSYSTEMTIME const pst,
|
|
time_t * const timeValueP) {
|
|
FILETIME filetime;
|
|
|
|
SystemTimeToFileTime(pst, &filetime);
|
|
UnixTimeFromFileTime(envP, &filetime, timeValueP);
|
|
}
|
|
|
|
#endif /* MSVCRT */
|
|
|
|
|
|
|
|
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_type_name(valueP->_type),
|
|
xmlrpc_type_name(XMLRPC_TYPE_DATETIME));
|
|
}
|
|
}
|
|
|
|
|
|
|
|
void
|
|
xmlrpc_read_datetime(xmlrpc_env * const envP,
|
|
const xmlrpc_value * const valueP,
|
|
xmlrpc_datetime * const dtP) {
|
|
|
|
validateDatetimeType(envP, valueP);
|
|
if (!envP->fault_occurred) {
|
|
*dtP = valueP->_value.dt;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
void
|
|
xmlrpc_read_datetime_str(xmlrpc_env * const envP,
|
|
const xmlrpc_value * const valueP,
|
|
const char ** const stringValueP) {
|
|
/*----------------------------------------------------------------------------
|
|
This exists for backward compatibility. No normal modern program would
|
|
want to see a datetime value in this format. Note that the format isn't
|
|
even ISO 8601 -- it's a bizarre hybrid of two ISO 8601 formats.
|
|
|
|
Do not extend this.
|
|
|
|
This exists because Xmlrpc-c was at one time lazy and this was the only way
|
|
to extract the value. An xmlrpc_value in those days represented a datetime
|
|
with the actual XML-RPC wire format of a datetime, and this function simply
|
|
returned a copy of it.
|
|
-----------------------------------------------------------------------------*/
|
|
validateDatetimeType(envP, valueP);
|
|
if (!envP->fault_occurred) {
|
|
time_t secs;
|
|
unsigned int usecs;
|
|
|
|
xmlrpc_read_datetime_usec(envP, valueP, &secs, &usecs);
|
|
|
|
if (!envP->fault_occurred) {
|
|
struct tm brokenTime;
|
|
char dtString[64];
|
|
|
|
xmlrpc_gmtime(secs, &brokenTime);
|
|
|
|
/* Note that this format is NOT ISO 8601 -- it's a bizarre
|
|
hybrid of two ISO 8601 formats.
|
|
*/
|
|
strftime(dtString, sizeof(dtString), "%Y%m%dT%H:%M:%S",
|
|
&brokenTime);
|
|
|
|
if (usecs != 0) {
|
|
char usecString[64];
|
|
assert(usecs < 1000000);
|
|
snprintf(usecString, sizeof(usecString), ".%06u", usecs);
|
|
STRSCAT(dtString, usecString);
|
|
}
|
|
|
|
*stringValueP = strdup(dtString);
|
|
if (*stringValueP == NULL)
|
|
xmlrpc_faultf(envP,
|
|
"Unable to allocate memory for datetime string");
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
void
|
|
xmlrpc_read_datetime_str_old(xmlrpc_env * const envP,
|
|
const xmlrpc_value * const valueP,
|
|
const char ** const stringValueP) {
|
|
|
|
assert(valueP->_cache);
|
|
|
|
validateDatetimeType(envP, valueP);
|
|
if (!envP->fault_occurred) {
|
|
const char ** const readBufferP = valueP->_cache;
|
|
|
|
if (!*readBufferP)
|
|
/* Nobody's asked for the internal buffer before. Set it up. */
|
|
xmlrpc_read_datetime_str(envP, valueP, readBufferP);
|
|
|
|
*stringValueP = *readBufferP;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
void
|
|
xmlrpc_read_datetime_usec(xmlrpc_env * const envP,
|
|
const xmlrpc_value * const valueP,
|
|
time_t * const secsP,
|
|
unsigned int * const usecsP) {
|
|
|
|
validateDatetimeType(envP, valueP);
|
|
|
|
if (!envP->fault_occurred) {
|
|
if (valueP->_value.dt.Y < 1970)
|
|
xmlrpc_faultf(envP, "Year (%u) is too early to represent as "
|
|
"a standard Unix time",
|
|
valueP->_value.dt.Y);
|
|
else {
|
|
struct tm brokenTime;
|
|
const char * error;
|
|
|
|
brokenTime.tm_sec = valueP->_value.dt.s;
|
|
brokenTime.tm_min = valueP->_value.dt.m;
|
|
brokenTime.tm_hour = valueP->_value.dt.h;
|
|
brokenTime.tm_mday = valueP->_value.dt.D;
|
|
brokenTime.tm_mon = valueP->_value.dt.M - 1;
|
|
brokenTime.tm_year = valueP->_value.dt.Y - 1900;
|
|
|
|
xmlrpc_timegm(&brokenTime, secsP, &error);
|
|
|
|
if (error) {
|
|
/* Ideally, this wouldn't be possible - it wouldn't be
|
|
possible to create an xmlrpc_value that doesn't actually
|
|
represent a real datetime. But today, we're lazy and
|
|
don't fully validate incoming XML-RPC <dateTime.iso8601>
|
|
elements, and we also have the legacy
|
|
xmlrpc_datetime_new_str() constructor to which the user
|
|
may feed garbage.
|
|
|
|
We should tighten that up and then simply assert here that
|
|
xmlrpc_timegm() succeeded.
|
|
*/
|
|
xmlrpc_env_set_fault_formatted(envP, XMLRPC_PARSE_ERROR,
|
|
"A datetime received in an XML-RPC message "
|
|
"or generated with legacy Xmlrpc-c facilities "
|
|
"does not validly describe a datetime. %s",
|
|
error);
|
|
xmlrpc_strfree(error);
|
|
} else
|
|
*usecsP = valueP->_value.dt.u;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
void
|
|
xmlrpc_read_datetime_sec(xmlrpc_env * const envP,
|
|
const xmlrpc_value * const valueP,
|
|
time_t * const timeValueP) {
|
|
|
|
unsigned int usecs;
|
|
|
|
xmlrpc_read_datetime_usec(envP, valueP, timeValueP, &usecs);
|
|
}
|
|
|
|
|
|
|
|
#if XMLRPC_HAVE_TIMEVAL
|
|
|
|
void
|
|
xmlrpc_read_datetime_timeval(xmlrpc_env * const envP,
|
|
const xmlrpc_value * const valueP,
|
|
struct timeval * const timeValueP) {
|
|
|
|
time_t secs;
|
|
unsigned int usecs;
|
|
|
|
xmlrpc_read_datetime_usec(envP, valueP, &secs, &usecs);
|
|
|
|
timeValueP->tv_sec = secs;
|
|
timeValueP->tv_usec = usecs;
|
|
}
|
|
#endif
|
|
|
|
|
|
|
|
#if XMLRPC_HAVE_TIMESPEC
|
|
|
|
void
|
|
xmlrpc_read_datetime_timespec(xmlrpc_env * const envP,
|
|
const xmlrpc_value * const valueP,
|
|
struct timespec * const timeValueP) {
|
|
|
|
time_t secs;
|
|
unsigned int usecs;
|
|
|
|
xmlrpc_read_datetime_usec(envP, valueP, &secs, &usecs);
|
|
|
|
timeValueP->tv_sec = secs;
|
|
timeValueP->tv_nsec = usecs * 1000;
|
|
}
|
|
#endif
|
|
|
|
|
|
|
|
xmlrpc_value *
|
|
xmlrpc_datetime_new(xmlrpc_env * const envP,
|
|
xmlrpc_datetime const dt) {
|
|
|
|
xmlrpc_value * valP;
|
|
|
|
const char ** readBufferP;
|
|
|
|
MALLOCVAR(readBufferP);
|
|
|
|
if (!readBufferP)
|
|
xmlrpc_faultf(envP, "Couldn't get memory for the cache part of the "
|
|
"XML-RPC datetime value object");
|
|
|
|
else {
|
|
*readBufferP = NULL;
|
|
|
|
xmlrpc_createXmlrpcValue(envP, &valP);
|
|
|
|
if (!envP->fault_occurred) {
|
|
valP->_type = XMLRPC_TYPE_DATETIME;
|
|
|
|
valP->_value.dt = dt;
|
|
|
|
valP->_cache = readBufferP;
|
|
}
|
|
if (envP->fault_occurred)
|
|
free(readBufferP);
|
|
}
|
|
return valP;
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
parseDatetimeString(const char * const datetimeString,
|
|
xmlrpc_datetime * const dtP) {
|
|
|
|
size_t const dtStrlen = strlen(datetimeString);
|
|
|
|
char year[4+1];
|
|
char month[2+1];
|
|
char day[2+1];
|
|
char hour[2+1];
|
|
char minute[2+1];
|
|
char second[2+1];
|
|
|
|
/* Because we require input to be valid: */
|
|
assert(dtStrlen >= 17 && dtStrlen != 18 && dtStrlen <= 24);
|
|
|
|
year[0] = datetimeString[ 0];
|
|
year[1] = datetimeString[ 1];
|
|
year[2] = datetimeString[ 2];
|
|
year[3] = datetimeString[ 3];
|
|
year[4] = '\0';
|
|
|
|
month[0] = datetimeString[ 4];
|
|
month[1] = datetimeString[ 5];
|
|
month[2] = '\0';
|
|
|
|
day[0] = datetimeString[ 6];
|
|
day[1] = datetimeString[ 7];
|
|
day[2] = '\0';
|
|
|
|
assert(datetimeString[ 8] == 'T');
|
|
|
|
hour[0] = datetimeString[ 9];
|
|
hour[1] = datetimeString[10];
|
|
hour[2] = '\0';
|
|
|
|
assert(datetimeString[11] == ':');
|
|
|
|
minute[0] = datetimeString[12];
|
|
minute[1] = datetimeString[13];
|
|
minute[2] = '\0';
|
|
|
|
assert(datetimeString[14] == ':');
|
|
|
|
second[0] = datetimeString[15];
|
|
second[1] = datetimeString[16];
|
|
second[2] = '\0';
|
|
|
|
if (dtStrlen > 17) {
|
|
size_t const pad = 24 - dtStrlen;
|
|
size_t i;
|
|
|
|
dtP->u = atoi(&datetimeString[18]);
|
|
for (i = 0; i < pad; ++i)
|
|
dtP->u *= 10;
|
|
} else
|
|
dtP->u = 0;
|
|
|
|
dtP->Y = atoi(year);
|
|
dtP->M = atoi(month);
|
|
dtP->D = atoi(day);
|
|
dtP->h = atoi(hour);
|
|
dtP->m = atoi(minute);
|
|
dtP->s = atoi(second);
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
validateFirst17(xmlrpc_env * const envP,
|
|
const char * const dt) {
|
|
/*----------------------------------------------------------------------------
|
|
Assuming 'dt' is at least 17 characters long, validate that the first
|
|
17 characters are a valid XML-RPC datetime, e.g.
|
|
"20080628T16:35:02"
|
|
-----------------------------------------------------------------------------*/
|
|
unsigned int i;
|
|
|
|
for (i = 0; i < 8 && !envP->fault_occurred; ++i)
|
|
if (!isdigit(dt[i]))
|
|
xmlrpc_faultf(envP, "Not a digit: '%c'", dt[i]);
|
|
|
|
if (dt[8] != 'T')
|
|
xmlrpc_faultf(envP, "9th character is '%c', not 'T'", dt[8]);
|
|
if (!isdigit(dt[9]))
|
|
xmlrpc_faultf(envP, "Not a digit: '%c'", dt[9]);
|
|
if (!isdigit(dt[10]))
|
|
xmlrpc_faultf(envP, "Not a digit: '%c'", dt[10]);
|
|
if (dt[11] != ':')
|
|
xmlrpc_faultf(envP, "Not a colon: '%c'", dt[11]);
|
|
if (!isdigit(dt[12]))
|
|
xmlrpc_faultf(envP, "Not a digit: '%c'", dt[12]);
|
|
if (!isdigit(dt[13]))
|
|
xmlrpc_faultf(envP, "Not a digit: '%c'", dt[13]);
|
|
if (dt[14] != ':')
|
|
xmlrpc_faultf(envP, "Not a colon: '%c'", dt[14]);
|
|
if (!isdigit(dt[15]))
|
|
xmlrpc_faultf(envP, "Not a digit: '%c'", dt[15]);
|
|
if (!isdigit(dt[16]))
|
|
xmlrpc_faultf(envP, "Not a digit: '%c'", dt[16]);
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
validateFractionalSeconds(xmlrpc_env * const envP,
|
|
const char * const dt) {
|
|
/*----------------------------------------------------------------------------
|
|
Validate the fractional seconds part of the XML-RPC datetime string
|
|
'dt', if any. That's the decimal point and everything following
|
|
it.
|
|
-----------------------------------------------------------------------------*/
|
|
if (strlen(dt) > 17) {
|
|
if (dt[17] != '.') {
|
|
xmlrpc_faultf(envP, "'%c' where only a period is valid", dt[17]);
|
|
} else {
|
|
if (dt[18] == '\0')
|
|
xmlrpc_faultf(envP, "Nothing after decimal point");
|
|
else {
|
|
unsigned int i;
|
|
for (i = 18; dt[i] != '\0' && !envP->fault_occurred; ++i) {
|
|
if (!isdigit(dt[i]))
|
|
xmlrpc_faultf(envP,
|
|
"Non-digit in fractional seconds: '%c'",
|
|
dt[i]);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
validateFormat(xmlrpc_env * const envP,
|
|
const char * const dt) {
|
|
|
|
if (strlen(dt) < 17)
|
|
xmlrpc_faultf(envP,
|
|
"Invalid length of %u of datetime string. "
|
|
"Must be at least 17 characters",
|
|
(unsigned)strlen(dt));
|
|
else {
|
|
validateFirst17(envP, dt);
|
|
|
|
if (!envP->fault_occurred)
|
|
validateFractionalSeconds(envP, dt);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/* Microsoft Visual C in debug mode produces code that complains about
|
|
returning an undefined value from xmlrpc_datetime_new_str(). It's a bogus
|
|
complaint, because this function is defined to return nothing meaningful
|
|
those cases. So we disable the check.
|
|
*/
|
|
#pragma runtime_checks("u", off)
|
|
|
|
|
|
|
|
xmlrpc_value *
|
|
xmlrpc_datetime_new_str(xmlrpc_env * const envP,
|
|
const char * const datetimeString) {
|
|
/*----------------------------------------------------------------------------
|
|
This exists only for backward compatibility. Originally, this was the
|
|
only way to create a datetime XML-RPC value, because we had a really
|
|
lazy implementation of XML-RPC serialization and parsing (basically, the
|
|
user did it!).
|
|
|
|
Do not extend this. The user should use more normal C representations
|
|
of datetimes.
|
|
-----------------------------------------------------------------------------*/
|
|
xmlrpc_value * retval;
|
|
|
|
validateFormat(envP, datetimeString);
|
|
if (!envP->fault_occurred) {
|
|
xmlrpc_datetime dt;
|
|
|
|
parseDatetimeString(datetimeString, &dt);
|
|
|
|
/* Note that parseDatetimeString() can generate an invalid datetime
|
|
value, e.g. Hour 25 or February 30. Ideally, we would catch that
|
|
here, but due to laziness, we simply accept the possibility of
|
|
invalid xmlrpc_datetime in xmlrpc_value and whoever uses the the
|
|
xmlrpc_value has to deal with it.
|
|
*/
|
|
retval = xmlrpc_datetime_new(envP, dt);
|
|
}
|
|
|
|
return retval;
|
|
}
|
|
|
|
|
|
|
|
#pragma runtime_checks("u", restore)
|
|
|
|
|
|
|
|
xmlrpc_value *
|
|
xmlrpc_datetime_new_usec(xmlrpc_env * const envP,
|
|
time_t const secs,
|
|
unsigned int const usecs) {
|
|
|
|
xmlrpc_value * valueP;
|
|
|
|
if (usecs >= 1000000)
|
|
xmlrpc_faultf(envP, "Number of fractional microseconds must be less "
|
|
"than one million. You specified %u", usecs);
|
|
else {
|
|
struct tm brokenTime;
|
|
xmlrpc_datetime dt;
|
|
|
|
xmlrpc_gmtime(secs, &brokenTime);
|
|
|
|
dt.s = brokenTime.tm_sec;
|
|
dt.m = brokenTime.tm_min;
|
|
dt.h = brokenTime.tm_hour;
|
|
dt.D = brokenTime.tm_mday;
|
|
dt.M = brokenTime.tm_mon + 1;
|
|
dt.Y = 1900 + brokenTime.tm_year;
|
|
dt.u = usecs;
|
|
|
|
valueP = xmlrpc_datetime_new(envP, dt);
|
|
}
|
|
return valueP;
|
|
}
|
|
|
|
|
|
|
|
xmlrpc_value *
|
|
xmlrpc_datetime_new_sec(xmlrpc_env * const envP,
|
|
time_t const value) {
|
|
|
|
return xmlrpc_datetime_new_usec(envP, value, 0);
|
|
}
|
|
|
|
|
|
|
|
#if XMLRPC_HAVE_TIMEVAL
|
|
|
|
xmlrpc_value *
|
|
xmlrpc_datetime_new_timeval(xmlrpc_env * const envP,
|
|
struct timeval const value) {
|
|
|
|
return xmlrpc_datetime_new_usec(envP, value.tv_sec, value.tv_usec);
|
|
}
|
|
#endif
|
|
|
|
|
|
|
|
#if XMLRPC_HAVE_TIMESPEC
|
|
|
|
xmlrpc_value *
|
|
xmlrpc_datetime_new_timespec(xmlrpc_env * const envP,
|
|
struct timespec const value) {
|
|
|
|
return xmlrpc_datetime_new_usec(envP, value.tv_sec, value.tv_nsec/1000);
|
|
}
|
|
#endif
|
|
|
|
|
|
|
|
void
|
|
xmlrpc_destroyDatetime(xmlrpc_value * const datetimeP) {
|
|
|
|
const char ** const readBufferP = datetimeP->_cache;
|
|
|
|
if (*readBufferP)
|
|
xmlrpc_strfree(*readBufferP);
|
|
|
|
free(datetimeP->_cache);
|
|
}
|