#include "xmlrpc_config.h" #include <stddef.h> #include <stdlib.h> #include <string.h> #include <errno.h> #include <ctype.h> #include <limits.h> #include <float.h> #include "bool.h" #include "xmlrpc-c/base.h" #include "xmlrpc-c/base_int.h" #include "xmlrpc-c/string_int.h" #include "xmlrpc-c/util.h" #include "xmlrpc-c/xmlparser.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, "<data> element has <%s> child. " "Only <value> 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) { unsigned int const childCount = xml_element_children_size(arrayElemP); if (childCount != 1) setParseFault(envP, "<array> element has %u children. Only one <data> " "makes sense.", 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, "<array> element has <%s> child. Only <data> " "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) { unsigned int const childCount = xml_element_children_size(nameElemP); if (childCount > 0) setParseFault(envP, "<name> element has %u children. " "Should have none.", 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, "<member> has no <name> 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, "<member> has no <value> 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) { unsigned int const childCount = xml_element_children_size(memberP); if (childCount != 2) setParseFault(envP, "<member> element has %u children. Only one <name> and " "one <value> make sense.", 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 <struct> 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 <member> " "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 <int> 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, "<int> XML element content is empty"); else if (isspace(str[0])) setParseFault(envP, "<int> 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, "<int> 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 <int> 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, "<int> value %d is below the range allowed " "by XML-RPC (minimum is %d)", i, XMLRPC_INT32_MIN); else if (i > XMLRPC_INT32_MAX) setParseFault(envP, "<int> value %d is above the range allowed " "by XML-RPC (maximum is %d)", i, XMLRPC_INT32_MAX); else { if (tail[0] != '\0') setParseFault(envP, "<int> 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 <boolean> 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, "<boolean> 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 <double> 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, "<double> 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 <base64> 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 <i8> 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, "<i8> XML element content is empty"); else if (isspace(str[0])) setParseFault(envP, "<i8> content '%s' starts with white space", str); else { xmlrpc_int64 i; char * tail; errno = 0; i = strtoll(str, &tail, 10); if (errno == ERANGE) setParseFault(envP, "<i8> XML element value '%s' represents a " "number beyond the range that " "XML-RPC allows (%d - %d)", str, XMLRPC_INT64_MIN, XMLRPC_INT64_MAX); else if (errno != 0) setParseFault(envP, "unexpected error parsing <i8> XML element " "value '%s'. strtoll() failed with errno %d (%s)", str, errno, strerror(errno)); else { /* Look for out-of-range errors which didn't produce ERANGE. */ if (i < XMLRPC_INT64_MIN) setParseFault(envP, "<i8> value %d is below the range allowed " "by XML-RPC (minimum is %d)", i, XMLRPC_INT64_MIN); else if (i > XMLRPC_INT64_MAX) setParseFault(envP, "<i8> value %d is above the range allowed " "by XML-RPC (maximum is %d)", i, XMLRPC_INT64_MAX); else { if (tail[0] != '\0') setParseFault(envP, "<i8> value '%s' contains non-numerical " "junk: '%s'", str, tail); else *valuePP = xmlrpc_i8_new(envP, i); } } } } 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 <string>. 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 <string> parser assumes it's UTF 8 with embedded NULs. But the <int> 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. 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")) *valuePP = xmlrpc_datetime_new_str(envP, cdata); 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) { unsigned int const childCount = xml_element_children_size(elemP); if (childCount > 0) setParseFault(envP, "The child of a <value> element " "is neither <array> nor <struct>, " "but has %u child elements of its own.", 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 <value> 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 <value> expected", xml_element_name(elemP)); else { unsigned int 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, "<value> has %u child elements. " "Only zero or one make sense.", 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); } } } }