#include "xmlrpc_config.h" #include #include #include #include #include #include #include #include "bool.h" #include "xmlrpc-c/base.h" #include "xmlrpc-c/base_int.h" #include "xmlrpc-c/string_int.h" #include "xmlrpc-c/string_number.h" #include "xmlrpc-c/util.h" #include "xmlrpc-c/xmlparser.h" #include "parse_datetime.h" #include "parse_value.h" static void setParseFault(xmlrpc_env * const envP, const char * const format, ...) { va_list args; va_start(args, format); xmlrpc_set_fault_formatted_v(envP, XMLRPC_PARSE_ERROR, format, args); va_end(args); } static void parseArrayDataChild(xmlrpc_env * const envP, xml_element * const childP, unsigned int const maxRecursion, xmlrpc_value * const arrayP) { const char * const elemName = xml_element_name(childP); if (!xmlrpc_streq(elemName, "value")) setParseFault(envP, " element has <%s> child. " "Only makes sense.", elemName); else { xmlrpc_value * itemP; xmlrpc_parseValue(envP, maxRecursion-1, childP, &itemP); if (!envP->fault_occurred) { xmlrpc_array_append_item(envP, arrayP, itemP); xmlrpc_DECREF(itemP); } } } static void parseArray(xmlrpc_env * const envP, unsigned int const maxRecursion, xml_element * const arrayElemP, xmlrpc_value ** const arrayPP) { xmlrpc_value * arrayP; XMLRPC_ASSERT_ENV_OK(envP); XMLRPC_ASSERT(arrayElemP != NULL); arrayP = xmlrpc_array_new(envP); if (!envP->fault_occurred) { size_t const childCount = xml_element_children_size(arrayElemP); if (childCount != 1) setParseFault(envP, " element has %u children. Only one " "makes sense.", (unsigned int)childCount); else { xml_element * const dataElemP = xml_element_children(arrayElemP)[0]; const char * const elemName = xml_element_name(dataElemP); if (!xmlrpc_streq(elemName, "data")) setParseFault(envP, " element has <%s> child. Only " "makes sense.", elemName); else { xml_element ** const values = xml_element_children(dataElemP); unsigned int const size = xml_element_children_size(dataElemP); unsigned int i; for (i = 0; i < size && !envP->fault_occurred; ++i) parseArrayDataChild(envP, values[i], maxRecursion, arrayP); } } if (envP->fault_occurred) xmlrpc_DECREF(arrayP); else *arrayPP = arrayP; } } static void parseName(xmlrpc_env * const envP, xml_element * const nameElemP, xmlrpc_value ** const valuePP) { size_t const childCount = xml_element_children_size(nameElemP); if (childCount > 0) setParseFault(envP, " element has %u children. " "Should have none.", (unsigned int)childCount); else { const char * const cdata = xml_element_cdata(nameElemP); size_t const cdataSize = xml_element_cdata_size(nameElemP); *valuePP = xmlrpc_string_new_lp(envP, cdataSize, cdata); } } static void getNameChild(xmlrpc_env * const envP, xml_element * const parentP, xml_element * * const childPP) { xml_element ** const children = xml_element_children(parentP); size_t const childCount = xml_element_children_size(parentP); xml_element * childP; unsigned int i; for (i = 0, childP = NULL; i < childCount && !childP; ++i) { if (xmlrpc_streq(xml_element_name(children[i]), "name")) childP = children[i]; } if (!childP) xmlrpc_env_set_fault(envP, XMLRPC_PARSE_ERROR, " has no child"); else *childPP = childP; } static void getValueChild(xmlrpc_env * const envP, xml_element * const parentP, xml_element * * const childPP) { xml_element ** const children = xml_element_children(parentP); size_t const childCount = xml_element_children_size(parentP); xml_element * childP; unsigned int i; for (i = 0, childP = NULL; i < childCount && !childP; ++i) { if (xmlrpc_streq(xml_element_name(children[i]), "value")) childP = children[i]; } if (!childP) xmlrpc_env_set_fault(envP, XMLRPC_PARSE_ERROR, " has no child"); else *childPP = childP; } static void parseMember(xmlrpc_env * const envP, xml_element * const memberP, unsigned int const maxRecursion, xmlrpc_value ** const keyPP, xmlrpc_value ** const valuePP) { size_t const childCount = xml_element_children_size(memberP); if (childCount != 2) setParseFault(envP, " element has %u children. Only one and " "one make sense.", (unsigned int)childCount); else { xml_element * nameElemP = NULL; getNameChild(envP, memberP, &nameElemP); if (!envP->fault_occurred) { parseName(envP, nameElemP, keyPP); if (!envP->fault_occurred) { xml_element * valueElemP = NULL; getValueChild(envP, memberP, &valueElemP); if (!envP->fault_occurred) xmlrpc_parseValue(envP, maxRecursion-1, valueElemP, valuePP); if (envP->fault_occurred) xmlrpc_DECREF(*keyPP); } } } } static void parseStruct(xmlrpc_env * const envP, unsigned int const maxRecursion, xml_element * const elemP, xmlrpc_value ** const structPP) { /*---------------------------------------------------------------------------- Parse the element 'elemP'. -----------------------------------------------------------------------------*/ xmlrpc_value * structP; XMLRPC_ASSERT_ENV_OK(envP); XMLRPC_ASSERT(elemP != NULL); structP = xmlrpc_struct_new(envP); if (!envP->fault_occurred) { /* Iterate over our children, extracting key/value pairs. */ xml_element ** const members = xml_element_children(elemP); unsigned int const size = xml_element_children_size(elemP); unsigned int i; for (i = 0; i < size && !envP->fault_occurred; ++i) { const char * const elemName = xml_element_name(members[i]); if (!xmlrpc_streq(elemName, "member")) setParseFault(envP, "<%s> element found where only " "makes sense", elemName); else { xmlrpc_value * keyP = NULL; xmlrpc_value * valueP; parseMember(envP, members[i], maxRecursion, &keyP, &valueP); if (!envP->fault_occurred) { xmlrpc_struct_set_value_v(envP, structP, keyP, valueP); xmlrpc_DECREF(keyP); xmlrpc_DECREF(valueP); } } } if (envP->fault_occurred) xmlrpc_DECREF(structP); else *structPP = structP; } } static void parseInt(xmlrpc_env * const envP, const char * const str, xmlrpc_value ** const valuePP) { /*---------------------------------------------------------------------------- Parse the content of a XML-RPC XML element, e.g. "34". 'str' is that content. -----------------------------------------------------------------------------*/ XMLRPC_ASSERT_ENV_OK(envP); XMLRPC_ASSERT_PTR_OK(str); if (str[0] == '\0') setParseFault(envP, " XML element content is empty"); else if (isspace(str[0])) setParseFault(envP, " content '%s' starts with white space", str); else { long i; char * tail; errno = 0; i = strtol(str, &tail, 10); /* Look for ERANGE. */ if (errno == ERANGE) setParseFault(envP, " XML element value '%s' represents a " "number beyond the range that " "XML-RPC allows (%d - %d)", str, XMLRPC_INT32_MIN, XMLRPC_INT32_MAX); else if (errno != 0) setParseFault(envP, "unexpected error parsing XML element " "value '%s'. strtol() failed with errno %d (%s)", str, errno, strerror(errno)); else { /* Look for out-of-range errors which didn't produce ERANGE. */ if (i < XMLRPC_INT32_MIN) setParseFault(envP, " value %ld is below the range allowed " "by XML-RPC (minimum is %d)", i, XMLRPC_INT32_MIN); else if (i > XMLRPC_INT32_MAX) setParseFault(envP, " value %ld is above the range allowed " "by XML-RPC (maximum is %d)", i, XMLRPC_INT32_MAX); else { if (tail[0] != '\0') setParseFault(envP, " value '%s' contains non-numerical " "junk: '%s'", str, tail); else *valuePP = xmlrpc_int_new(envP, i); } } } } static void parseBoolean(xmlrpc_env * const envP, const char * const str, xmlrpc_value ** const valuePP) { /*---------------------------------------------------------------------------- Parse the content of a XML-RPC XML element, e.g. "1". 'str' is that content. -----------------------------------------------------------------------------*/ XMLRPC_ASSERT_ENV_OK(envP); XMLRPC_ASSERT_PTR_OK(str); if (xmlrpc_streq(str, "0") || xmlrpc_streq(str, "1")) *valuePP = xmlrpc_bool_new(envP, xmlrpc_streq(str, "1") ? 1 : 0); else setParseFault(envP, " XML element content must be either " "'0' or '1' according to XML-RPC. This one has '%s'", str); } static void scanAndValidateDoubleString(xmlrpc_env * const envP, const char * const string, const char ** const mantissaP, const char ** const mantissaEndP, const char ** const fractionP, const char ** const fractionEndP) { const char * mantissa; const char * dp; const char * p; if (string[0] == '-' || string[0] == '+') mantissa = &string[1]; else mantissa = &string[0]; for (p = mantissa, dp = NULL; *p; ++p) { char const c = *p; if (c == '.') { if (dp) { setParseFault(envP, "Two decimal points"); return; } else dp = p; } else if (c < '0' || c > '9') { setParseFault(envP, "Garbage (not sign, digit, or period) " "starting at '%s'", p); return; } } *mantissaP = mantissa; if (dp) { *mantissaEndP = dp; *fractionP = dp+1; *fractionEndP = p; } else { *mantissaEndP = p; *fractionP = p; *fractionEndP = p; } } static bool isInfinite(double const value) { return value > DBL_MAX; } static void parseDoubleString(xmlrpc_env * const envP, const char * const string, double * const valueP) { /*---------------------------------------------------------------------------- Turn e.g. "4.3" into 4.3 . -----------------------------------------------------------------------------*/ /* strtod() is no good for this because it is designed for human interfaces; it parses according to locale. As a practical matter that sometimes means that it does not recognize "." as a decimal point. In XML-RPC, "." is a decimal point. Design note: in my experiments, using strtod() was 10 times slower than using this function. */ const char * mantissa = NULL; const char * mantissaEnd = NULL; const char * fraction = NULL; const char * fractionEnd = NULL; scanAndValidateDoubleString(envP, string, &mantissa, &mantissaEnd, &fraction, &fractionEnd); if (!envP->fault_occurred) { double accum; accum = 0.0; if (mantissa == mantissaEnd && fraction == fractionEnd) { setParseFault(envP, "No digits"); return; } { /* Add in the whole part */ const char * p; for (p = mantissa; p < mantissaEnd; ++p) { accum *= 10; accum += (*p - '0'); } } { /* Add in the fractional part */ double significance; const char * p; for (significance = 0.1, p = fraction; p < fractionEnd; ++p, significance *= 0.1) { accum += (*p - '0') * significance; } } if (isInfinite(accum)) setParseFault(envP, "Value exceeds the size allowed by XML-RPC"); else *valueP = string[0] == '-' ? (- accum) : accum; } } static void parseDoubleStringStrtod(const char * const str, bool * const failedP, double * const valueP) { if (strlen(str) == 0) { /* strtod() happily interprets empty string as 0.0. We don't think the user will appreciate that XML-RPC extension. */ *failedP = true; } else { char * tail; errno = 0; *valueP = strtod(str, &tail); if (errno != 0) *failedP = true; else { if (tail[0] != '\0') *failedP = true; else *failedP = false; } } } static void parseDouble(xmlrpc_env * const envP, const char * const str, xmlrpc_value ** const valuePP) { /*---------------------------------------------------------------------------- Parse the content of a XML-RPC XML element, e.g. "34.5". 'str' is that content. -----------------------------------------------------------------------------*/ xmlrpc_env parseEnv; double valueDouble = 0; XMLRPC_ASSERT_ENV_OK(envP); XMLRPC_ASSERT_PTR_OK(str); xmlrpc_env_init(&parseEnv); parseDoubleString(&parseEnv, str, &valueDouble); if (parseEnv.fault_occurred) { /* As an alternative, try a strtod() parsing. strtod() accepts other forms, e.g. "3.4E6"; "3,4"; " 3.4". These are not permitted by XML-RPC, but an almost-XML-RPC partner might use one. In fact, for many years, Xmlrpc-c generated such alternatives (by mistake). */ bool failed; parseDoubleStringStrtod(str, &failed, &valueDouble); if (failed) setParseFault(envP, " element value '%s' is not a valid " "floating point number. %s", str, parseEnv.fault_string); } if (!envP->fault_occurred) *valuePP = xmlrpc_double_new(envP, valueDouble); xmlrpc_env_clean(&parseEnv); } static void parseBase64(xmlrpc_env * const envP, const char * const str, size_t const strLength, xmlrpc_value ** const valuePP) { /*---------------------------------------------------------------------------- Parse the content of a XML-RPC XML element, e.g. "FD32YY". 'str' is that content. -----------------------------------------------------------------------------*/ xmlrpc_mem_block * decoded; XMLRPC_ASSERT_ENV_OK(envP); XMLRPC_ASSERT_PTR_OK(str); decoded = xmlrpc_base64_decode(envP, str, strLength); if (!envP->fault_occurred) { unsigned char * const bytes = XMLRPC_MEMBLOCK_CONTENTS(unsigned char, decoded); size_t const byteCount = XMLRPC_MEMBLOCK_SIZE(unsigned char, decoded); *valuePP = xmlrpc_base64_new(envP, byteCount, bytes); XMLRPC_MEMBLOCK_FREE(unsigned char, decoded); } } static void parseI8(xmlrpc_env * const envP, const char * const str, xmlrpc_value ** const valuePP) { /*---------------------------------------------------------------------------- Parse the content of a XML-RPC XML element, e.g. "34". 'str' is that content. -----------------------------------------------------------------------------*/ XMLRPC_ASSERT_ENV_OK(envP); XMLRPC_ASSERT_PTR_OK(str); if (str[0] == '\0') setParseFault(envP, " XML element content is empty"); else if (isspace(str[0])) setParseFault(envP, " content '%s' starts with white space", str); else { xmlrpc_int64 i; xmlrpc_env env; xmlrpc_env_init(&env); xmlrpc_parse_int64(&env, str, &i); if (env.fault_occurred) setParseFault(envP, " XML element value '%s' is invalid " "because it does not represent " "a 64 bit integer. %s", env.fault_string); else *valuePP = xmlrpc_i8_new(envP, i); xmlrpc_env_clean(&env); } } static void parseSimpleValueCdata(xmlrpc_env * const envP, const char * const elementName, const char * const cdata, size_t const cdataLength, xmlrpc_value ** const valuePP) { /*---------------------------------------------------------------------------- Parse an XML element is supposedly a data type element such as . Its name is 'elementName', and it has no children, but contains cdata 'cdata', which is 'dataLength' characters long. -----------------------------------------------------------------------------*/ /* We need to straighten out the whole character set / encoding thing some day. What is 'cdata', and what should it be? Does it have embedded NUL? Some of the code here assumes it doesn't. Is it text? The parser assumes it's UTF 8 with embedded NULs. But the parser will get terribly confused if there are any UTF-8 multibyte sequences or NUL characters. So will most of the others. The "ex:XXX" element names are what the Apache XML-RPC facility uses: http://ws.apache.org/xmlrpc/types.html. (Technically, it isn't "ex" but an arbitrary prefix that identifies a namespace declared earlier in the XML document -- this is an XML thing. But we aren't nearly sophisticated enough to use real XML namespaces, so we exploit the fact that XML-RPC actually uses "ex"). "i1" and "i2" are just from my imagination. */ if (xmlrpc_streq(elementName, "int") || xmlrpc_streq(elementName, "i4") || xmlrpc_streq(elementName, "i1") || xmlrpc_streq(elementName, "i2") || xmlrpc_streq(elementName, "ex:i1") || xmlrpc_streq(elementName, "ex:i2")) parseInt(envP, cdata, valuePP); else if (xmlrpc_streq(elementName, "boolean")) parseBoolean(envP, cdata, valuePP); else if (xmlrpc_streq(elementName, "double")) parseDouble(envP, cdata, valuePP); else if (xmlrpc_streq(elementName, "dateTime.iso8601")) xmlrpc_parseDatetime(envP, cdata, valuePP); else if (xmlrpc_streq(elementName, "string")) *valuePP = xmlrpc_string_new_lp(envP, cdataLength, cdata); else if (xmlrpc_streq(elementName, "base64")) parseBase64(envP, cdata, cdataLength, valuePP); else if (xmlrpc_streq(elementName, "nil") || xmlrpc_streq(elementName, "ex:nil")) *valuePP = xmlrpc_nil_new(envP); else if (xmlrpc_streq(elementName, "i8") || xmlrpc_streq(elementName, "ex:i8")) parseI8(envP, cdata, valuePP); else setParseFault(envP, "Unknown value type -- XML element is named " "<%s>", elementName); } static void parseSimpleValue(xmlrpc_env * const envP, xml_element * const elemP, xmlrpc_value ** const valuePP) { size_t childCount = xml_element_children_size(elemP); if (childCount > 0) setParseFault(envP, "The child of a element " "is neither nor , " "but has %u child elements of its own.", (unsigned int)childCount); else { const char * const elemName = xml_element_name(elemP); const char * const cdata = xml_element_cdata(elemP); size_t const cdataSize = xml_element_cdata_size(elemP); parseSimpleValueCdata(envP, elemName, cdata, cdataSize, valuePP); } } void xmlrpc_parseValue(xmlrpc_env * const envP, unsigned int const maxRecursion, xml_element * const elemP, xmlrpc_value ** const valuePP) { /*---------------------------------------------------------------------------- Compute the xmlrpc_value represented by the XML element 'elem'. Return that xmlrpc_value. We call convert_array() and convert_struct(), which may ultimately call us recursively. Don't recurse any more than 'maxRecursion' times. -----------------------------------------------------------------------------*/ XMLRPC_ASSERT_ENV_OK(envP); XMLRPC_ASSERT(elemP != NULL); /* Assume we'll need to recurse, make sure we're allowed */ if (maxRecursion < 1) xmlrpc_env_set_fault(envP, XMLRPC_PARSE_ERROR, "Nested data structure too deep."); else { if (!xmlrpc_streq(xml_element_name(elemP), "value")) setParseFault(envP, "<%s> element where expected", xml_element_name(elemP)); else { size_t const childCount = xml_element_children_size(elemP); if (childCount == 0) { /* We have no type element, so treat the value as a string. */ char * const cdata = xml_element_cdata(elemP); size_t const cdata_size = xml_element_cdata_size(elemP); *valuePP = xmlrpc_string_new_lp(envP, cdata_size, cdata); } else if (childCount > 1) setParseFault(envP, " has %u child elements. " "Only zero or one make sense.", (unsigned int)childCount); else { /* We should have a type tag inside our value tag. */ xml_element * const childP = xml_element_children(elemP)[0]; const char * const childName = xml_element_name(childP); if (xmlrpc_streq(childName, "struct")) parseStruct(envP, maxRecursion, childP, valuePP); else if (xmlrpc_streq(childName, "array")) parseArray(envP, maxRecursion, childP, valuePP); else parseSimpleValue(envP, childP, valuePP); } } } }