|
|
#include "xmlrpc_config.h" |
|
|
|
|
|
#include <stdlib.h> |
|
|
#include <assert.h> |
|
|
#include <ctype.h> |
|
|
#include <string.h> |
|
|
|
|
|
#if HAVE_REGEX |
|
|
#include <sys/types.h> |
|
|
#include <regex.h> |
|
|
#endif |
|
|
|
|
|
#include "bool.h" |
|
|
#include "c_util.h" |
|
|
|
|
|
#include "xmlrpc-c/base.h" |
|
|
#include "xmlrpc-c/base_int.h" |
|
|
|
|
|
#include "parse_datetime.h" |
|
|
|
|
|
|
|
|
|
|
|
#if HAVE_REGEX |
|
|
|
|
|
static unsigned int |
|
|
digitStringValue(const char * const string, |
|
|
regmatch_t const match) { |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
unsigned int i; |
|
|
unsigned int accum; |
|
|
|
|
|
assert(match.rm_so >= 0); |
|
|
assert(match.rm_eo >= 0); |
|
|
|
|
|
for (i = match.rm_so, accum = 0; i < (unsigned)match.rm_eo; ++i) { |
|
|
accum *= 10; |
|
|
assert(isdigit(string[i])); |
|
|
accum += string[i] - '0'; |
|
|
} |
|
|
return accum; |
|
|
} |
|
|
#endif |
|
|
|
|
|
|
|
|
|
|
|
#if HAVE_REGEX |
|
|
|
|
|
static unsigned int |
|
|
digitStringMillionths(const char * const string, |
|
|
regmatch_t const match) { |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
unsigned int i; |
|
|
unsigned int accum; |
|
|
|
|
|
assert(match.rm_so >= 0); |
|
|
assert(match.rm_eo >= 0); |
|
|
|
|
|
for (i = match.rm_so, accum = 0; i < (unsigned)match.rm_so+6; ++i) { |
|
|
accum *= 10; |
|
|
if (i < (unsigned)match.rm_eo) { |
|
|
assert(isdigit(string[i])); |
|
|
accum += string[i] - '0'; |
|
|
} |
|
|
} |
|
|
return accum; |
|
|
} |
|
|
#endif |
|
|
|
|
|
|
|
|
#if HAVE_REGEX |
|
|
|
|
|
static void |
|
|
subParseDtRegex_standard(regmatch_t * const matches, |
|
|
const char * const datetimeString, |
|
|
xmlrpc_datetime * const dtP) { |
|
|
|
|
|
dtP->Y = digitStringValue(datetimeString, matches[1]); |
|
|
dtP->M = digitStringValue(datetimeString, matches[2]); |
|
|
dtP->D = digitStringValue(datetimeString, matches[3]); |
|
|
dtP->h = digitStringValue(datetimeString, matches[4]); |
|
|
dtP->m = digitStringValue(datetimeString, matches[5]); |
|
|
dtP->s = digitStringValue(datetimeString, matches[6]); |
|
|
|
|
|
if (matches[7].rm_so == -1) |
|
|
dtP->u = 0; |
|
|
else |
|
|
dtP->u = digitStringMillionths(datetimeString, matches[7]); |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
static void |
|
|
subParseDtRegex_standardtzd(regmatch_t * const matches, |
|
|
const char * const datetimeString, |
|
|
xmlrpc_datetime * const dtP) { |
|
|
|
|
|
dtP->Y = digitStringValue(datetimeString, matches[1]); |
|
|
dtP->M = digitStringValue(datetimeString, matches[2]); |
|
|
dtP->D = digitStringValue(datetimeString, matches[3]); |
|
|
dtP->h = digitStringValue(datetimeString, matches[4]); |
|
|
dtP->m = digitStringValue(datetimeString, matches[5]); |
|
|
dtP->s = digitStringValue(datetimeString, matches[6]); |
|
|
dtP->u = 0; |
|
|
} |
|
|
|
|
|
#endif |
|
|
|
|
|
|
|
|
#if HAVE_REGEX |
|
|
|
|
|
typedef void (*regparsefunc_t)(regmatch_t * const matches, |
|
|
const char * const datetimeString, |
|
|
xmlrpc_datetime * const dtP); |
|
|
|
|
|
|
|
|
struct regexParser { |
|
|
const char * const regex; |
|
|
regparsefunc_t func; |
|
|
}; |
|
|
|
|
|
static const struct regexParser iso8601Regex[] |
|
|
|
|
|
/* Each entry of this table is instructions for recognizing and parsing |
|
|
some form of a "dateTime.iso8601" XML element. |
|
|
|
|
|
(Note that we recognize far more than just the XML-RPC standard |
|
|
dateTime.iso8601). |
|
|
*/ |
|
|
|
|
|
= { |
|
|
{ |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
"^([0-9]{4})\\-?([0-9]{2})\\-?([0-9]{2})T" |
|
|
"([0-9]{2}):?([0-9]{2}):?([0-9]{2})\\.?([0-9]+)?$", |
|
|
subParseDtRegex_standard |
|
|
}, |
|
|
|
|
|
{ |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
"^([0-9]{4})\\-?([0-9]{2})\\-?([0-9]{2})T" |
|
|
"([0-9]{2}):?([0-9]{2}):?([0-9]{2})[Z\\+\\-]([0-9]{2,4})?$", |
|
|
subParseDtRegex_standardtzd |
|
|
}, |
|
|
{ NULL, NULL } |
|
|
}; |
|
|
#endif |
|
|
|
|
|
|
|
|
|
|
|
#if HAVE_REGEX |
|
|
static void |
|
|
parseDtRegex(xmlrpc_env * const envP, |
|
|
const char * const datetimeString, |
|
|
xmlrpc_datetime * const dtP) { |
|
|
|
|
|
unsigned int i; |
|
|
const struct regexParser * parserP; |
|
|
|
|
|
|
|
|
|
|
|
regmatch_t matches[1024]; |
|
|
|
|
|
for (i = 0, parserP = NULL; iso8601Regex[i].regex && !parserP; ++i) { |
|
|
const struct regexParser * const thisParserP = &iso8601Regex[i]; |
|
|
|
|
|
regex_t re; |
|
|
int status; |
|
|
|
|
|
status = regcomp(&re, thisParserP->regex, REG_ICASE | REG_EXTENDED); |
|
|
|
|
|
|
|
|
assert(status == 0); if (status){}; |
|
|
{ |
|
|
int status; |
|
|
|
|
|
status = regexec(&re, datetimeString, ARRAY_SIZE(matches), |
|
|
matches, 0); |
|
|
|
|
|
if (status == 0) { |
|
|
assert(matches[0].rm_so != -1); |
|
|
|
|
|
parserP = thisParserP; |
|
|
} |
|
|
} |
|
|
regfree(&re); |
|
|
} |
|
|
|
|
|
if (parserP) { |
|
|
parserP->func(matches, datetimeString, dtP); |
|
|
} else { |
|
|
xmlrpc_env_set_fault_formatted( |
|
|
envP, XMLRPC_PARSE_ERROR, |
|
|
"value '%s' is not of any form we recognize " |
|
|
"for a <dateTime.iso8601> element", |
|
|
datetimeString); |
|
|
} |
|
|
|
|
|
} |
|
|
#endif |
|
|
|
|
|
|
|
|
|
|
|
static __inline__ void |
|
|
parseDtNoRegex(xmlrpc_env * const envP, |
|
|
const char * const datetimeString, |
|
|
xmlrpc_datetime * const dtP) { |
|
|
|
|
|
unsigned int 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]; |
|
|
|
|
|
if (dtStrlen < 17 || dtStrlen == 18 || dtStrlen > 24) |
|
|
xmlrpc_faultf(envP, "could not parse date, size incompatible: '%d'", |
|
|
dtStrlen); |
|
|
else { |
|
|
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) { |
|
|
unsigned int const pad = 24 - dtStrlen; |
|
|
unsigned int 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) { |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
unsigned int i; |
|
|
|
|
|
for (i = 0; i < 8 && !envP->fault_occurred; ++i) |
|
|
if (!isdigit(dt[i])) |
|
|
xmlrpc_env_set_fault_formatted( |
|
|
envP, XMLRPC_PARSE_ERROR, "Not a digit: '%c'", dt[i]); |
|
|
|
|
|
if (dt[8] != 'T') |
|
|
xmlrpc_env_set_fault_formatted( |
|
|
envP, XMLRPC_PARSE_ERROR, "9th character is '%c', not 'T'", |
|
|
dt[8]); |
|
|
if (!isdigit(dt[9])) |
|
|
xmlrpc_env_set_fault_formatted( |
|
|
envP, XMLRPC_PARSE_ERROR, "Not a digit: '%c'", dt[9]); |
|
|
if (!isdigit(dt[10])) |
|
|
xmlrpc_env_set_fault_formatted( |
|
|
envP, XMLRPC_PARSE_ERROR, "Not a digit: '%c'", dt[10]); |
|
|
if (dt[11] != ':') |
|
|
xmlrpc_env_set_fault_formatted( |
|
|
envP, XMLRPC_PARSE_ERROR, "Not a colon: '%c'", dt[11]); |
|
|
if (!isdigit(dt[12])) |
|
|
xmlrpc_env_set_fault_formatted( |
|
|
envP, XMLRPC_PARSE_ERROR, "Not a digit: '%c'", dt[12]); |
|
|
if (!isdigit(dt[13])) |
|
|
xmlrpc_env_set_fault_formatted( |
|
|
envP, XMLRPC_PARSE_ERROR, "Not a digit: '%c'", dt[13]); |
|
|
if (dt[14] != ':') |
|
|
xmlrpc_env_set_fault_formatted( |
|
|
envP, XMLRPC_PARSE_ERROR, "Not a colon: '%c'", dt[14]); |
|
|
if (!isdigit(dt[15])) |
|
|
xmlrpc_env_set_fault_formatted( |
|
|
envP, XMLRPC_PARSE_ERROR, "Not a digit: '%c'", dt[15]); |
|
|
if (!isdigit(dt[16])) |
|
|
xmlrpc_env_set_fault_formatted( |
|
|
envP, XMLRPC_PARSE_ERROR, "Not a digit: '%c'", dt[16]); |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
static void |
|
|
validateFractionalSeconds(xmlrpc_env * const envP, |
|
|
const char * const dt) { |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (strlen(dt) > 17) { |
|
|
if (dt[17] != '.') { |
|
|
xmlrpc_env_set_fault_formatted( |
|
|
envP, XMLRPC_PARSE_ERROR, |
|
|
"'%c' where only a period is valid", dt[17]); |
|
|
} else { |
|
|
if (dt[18] == '\0') |
|
|
xmlrpc_env_set_fault_formatted( |
|
|
envP, XMLRPC_PARSE_ERROR, "Nothing after decimal point"); |
|
|
else { |
|
|
unsigned int i; |
|
|
for (i = 18; dt[i] != '\0' && !envP->fault_occurred; ++i) { |
|
|
if (!isdigit(dt[i])) |
|
|
xmlrpc_env_set_fault_formatted( |
|
|
envP, XMLRPC_PARSE_ERROR, |
|
|
"Non-digit in fractional seconds: '%c'", dt[i]); |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
static __inline__ void |
|
|
validateFormatNoRegex(xmlrpc_env * const envP, |
|
|
const char * const dt) { |
|
|
|
|
|
if (strlen(dt) < 17) |
|
|
xmlrpc_env_set_fault_formatted( |
|
|
envP, XMLRPC_PARSE_ERROR, |
|
|
"Invalid length of %u of datetime. " |
|
|
"Must be at least 17 characters", |
|
|
(unsigned)strlen(dt)); |
|
|
else { |
|
|
validateFirst17(envP, dt); |
|
|
|
|
|
validateFractionalSeconds(envP, dt); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
static void |
|
|
validateXmlrpcDatetimeSome(xmlrpc_env * const envP, |
|
|
xmlrpc_datetime const dt) { |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (dt.M < 1 || dt.M > 12) |
|
|
xmlrpc_env_set_fault_formatted( |
|
|
envP, XMLRPC_PARSE_ERROR, |
|
|
"Month of year value %u is not in the range 1-12", dt.M); |
|
|
else if (dt.D < 1 || dt.D > 31) |
|
|
xmlrpc_env_set_fault_formatted( |
|
|
envP, XMLRPC_PARSE_ERROR, |
|
|
"Day of month value %u is not in the range 1-31", dt.D); |
|
|
else if (dt.h > 23) |
|
|
xmlrpc_env_set_fault_formatted( |
|
|
envP, XMLRPC_PARSE_ERROR, |
|
|
"Hour of day value %u is not in the range 0-23", dt.h); |
|
|
else if (dt.m > 59) |
|
|
xmlrpc_env_set_fault_formatted( |
|
|
envP, XMLRPC_PARSE_ERROR, |
|
|
"Minute of hour value %u is not in the range 0-59", dt.m); |
|
|
else if (dt.s > 59) |
|
|
xmlrpc_env_set_fault_formatted( |
|
|
envP, XMLRPC_PARSE_ERROR, |
|
|
"Second of minute value %u is not in the range 0-59", dt.s); |
|
|
else if (dt.u > 999999) |
|
|
xmlrpc_env_set_fault_formatted( |
|
|
envP, XMLRPC_PARSE_ERROR, |
|
|
"Microsecond of second value %u is not in the range 0-1M", dt.u); |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
void |
|
|
xmlrpc_parseDatetime(xmlrpc_env * const envP, |
|
|
const char * const datetimeString, |
|
|
xmlrpc_value ** const valuePP) { |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
xmlrpc_datetime dt; |
|
|
|
|
|
#if HAVE_REGEX |
|
|
parseDtRegex(envP, datetimeString, &dt); |
|
|
#else |
|
|
|
|
|
validateFormatNoRegex(envP, datetimeString); |
|
|
if (!envP->fault_occurred) |
|
|
parseDtNoRegex(envP, datetimeString, &dt); |
|
|
#endif |
|
|
|
|
|
if (!envP->fault_occurred) { |
|
|
validateXmlrpcDatetimeSome(envP, dt); |
|
|
|
|
|
if (!envP->fault_occurred) |
|
|
*valuePP = xmlrpc_datetime_new(envP, dt); |
|
|
} |
|
|
} |
|
|
|