You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
768 lines
25 KiB
768 lines
25 KiB
16 years ago
|
/* Copyright (C) 2001 by First Peer, Inc. All rights reserved.
|
||
|
**
|
||
|
** Redistribution and use in source and binary forms, with or without
|
||
|
** modification, are permitted provided that the following conditions
|
||
|
** are met:
|
||
|
** 1. Redistributions of source code must retain the above copyright
|
||
|
** notice, this list of conditions and the following disclaimer.
|
||
|
** 2. Redistributions in binary form must reproduce the above copyright
|
||
|
** notice, this list of conditions and the following disclaimer in the
|
||
|
** documentation and/or other materials provided with the distribution.
|
||
|
** 3. The name of the author may not be used to endorse or promote products
|
||
|
** derived from this software without specific prior written permission.
|
||
|
**
|
||
|
** THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
|
||
|
** ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||
|
** IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||
|
** ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
|
||
|
** FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||
|
** DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
||
|
** OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
||
|
** HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
||
|
** LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
||
|
** OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
||
|
** SUCH DAMAGE. */
|
||
|
|
||
|
#include "xmlrpc_config.h"
|
||
|
|
||
|
#include <stddef.h>
|
||
|
#include <stdlib.h>
|
||
|
#include <string.h>
|
||
|
#include <errno.h>
|
||
|
#include <ctype.h>
|
||
|
|
||
|
#include "xmlrpc.h"
|
||
|
#include "xmlrpc_int.h"
|
||
|
#include "xmlrpc_xmlparser.h"
|
||
|
|
||
|
|
||
|
/*=========================================================================
|
||
|
** Data Format
|
||
|
**=========================================================================
|
||
|
** All XML-RPC documents contain a single methodCall or methodResponse
|
||
|
** element.
|
||
|
**
|
||
|
** methodCall methodName, params
|
||
|
** methodResponse (params|fault)
|
||
|
** params param*
|
||
|
** param value
|
||
|
** fault value
|
||
|
** value (i4|int|boolean|string|double|dateTime.iso8601|base64|
|
||
|
** struct|array)
|
||
|
** array data
|
||
|
** data value*
|
||
|
** struct member*
|
||
|
** member name, value
|
||
|
**
|
||
|
** Contain CDATA: methodName, i4, int, boolean, string, double,
|
||
|
** dateTime.iso8601, base64, name
|
||
|
**
|
||
|
** We attempt to validate the structure of the XML document carefully.
|
||
|
** We also try *very* hard to handle malicious data gracefully, and without
|
||
|
** leaking memory.
|
||
|
**
|
||
|
** The CHECK_NAME and CHECK_CHILD_COUNT macros examine an XML element, and
|
||
|
** invoke XMLRPC_FAIL if something looks wrong.
|
||
|
*/
|
||
|
|
||
|
#define CHECK_NAME(env,elem,name) \
|
||
|
do \
|
||
|
if (strcmp((name), xml_element_name(elem)) != 0) \
|
||
|
XMLRPC_FAIL2(env, XMLRPC_PARSE_ERROR, \
|
||
|
"Expected element of type <%s>, found <%s>", \
|
||
|
(name), xml_element_name(elem)); \
|
||
|
while (0)
|
||
|
|
||
|
#define CHECK_CHILD_COUNT(env,elem,count) \
|
||
|
do \
|
||
|
if (xml_element_children_size(elem) != (count)) \
|
||
|
XMLRPC_FAIL3(env, XMLRPC_PARSE_ERROR, \
|
||
|
"Expected <%s> to have %d children, found %d", \
|
||
|
xml_element_name(elem), (count), \
|
||
|
xml_element_children_size(elem)); \
|
||
|
while (0)
|
||
|
|
||
|
static xml_element *
|
||
|
get_child_by_name (xmlrpc_env *env, xml_element *parent, char *name)
|
||
|
{
|
||
|
size_t child_count, i;
|
||
|
xml_element **children;
|
||
|
|
||
|
children = xml_element_children(parent);
|
||
|
child_count = xml_element_children_size(parent);
|
||
|
for (i = 0; i < child_count; i++) {
|
||
|
if (0 == strcmp(xml_element_name(children[i]), name))
|
||
|
return children[i];
|
||
|
}
|
||
|
|
||
|
xmlrpc_env_set_fault_formatted(env, XMLRPC_PARSE_ERROR,
|
||
|
"Expected <%s> to have child <%s>",
|
||
|
xml_element_name(parent), name);
|
||
|
return NULL;
|
||
|
}
|
||
|
|
||
|
|
||
|
/*=========================================================================
|
||
|
** Number-Parsing Functions
|
||
|
**=========================================================================
|
||
|
** These functions mirror atoi, atof, etc., but provide better
|
||
|
** error-handling. These routines may reset errno to zero.
|
||
|
*/
|
||
|
|
||
|
static xmlrpc_int32
|
||
|
xmlrpc_atoi(xmlrpc_env *env, char *str, size_t stringLength,
|
||
|
xmlrpc_int32 min, xmlrpc_int32 max)
|
||
|
{
|
||
|
long i;
|
||
|
char *end;
|
||
|
|
||
|
XMLRPC_ASSERT_ENV_OK(env);
|
||
|
XMLRPC_ASSERT_PTR_OK(str);
|
||
|
|
||
|
/* Suppress compiler warnings. */
|
||
|
i = 0;
|
||
|
|
||
|
/* Check for leading white space. */
|
||
|
if (isspace((int)(str[0])))
|
||
|
XMLRPC_FAIL1(env, XMLRPC_PARSE_ERROR,
|
||
|
"\"%s\" must not contain whitespace", str);
|
||
|
|
||
|
/* Convert the value. */
|
||
|
end = str + stringLength;
|
||
|
errno = 0;
|
||
|
i = strtol(str, &end, 10);
|
||
|
|
||
|
/* Look for ERANGE. */
|
||
|
if (errno != 0)
|
||
|
/* XXX - Do all operating systems have thread-safe strerror? */
|
||
|
XMLRPC_FAIL3(env, XMLRPC_PARSE_ERROR,
|
||
|
"error parsing \"%s\": %s (%d)",
|
||
|
str, strerror(errno), errno);
|
||
|
|
||
|
/* Look for out-of-range errors which didn't produce ERANGE. */
|
||
|
if (i < min || i > max)
|
||
|
XMLRPC_FAIL3(env, XMLRPC_PARSE_ERROR,
|
||
|
"\"%s\" must be in range %d to %d", str, min, max);
|
||
|
|
||
|
/* Check for unused characters. */
|
||
|
if (end != str + stringLength)
|
||
|
XMLRPC_FAIL1(env, XMLRPC_PARSE_ERROR,
|
||
|
"\"%s\" contained trailing data", str);
|
||
|
|
||
|
cleanup:
|
||
|
errno = 0;
|
||
|
if (env->fault_occurred)
|
||
|
return 0;
|
||
|
return (xmlrpc_int32) i;
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
static double
|
||
|
xmlrpc_atod(xmlrpc_env *env, char *str, size_t stringLength)
|
||
|
{
|
||
|
double d;
|
||
|
char *end;
|
||
|
|
||
|
XMLRPC_ASSERT_ENV_OK(env);
|
||
|
XMLRPC_ASSERT_PTR_OK(str);
|
||
|
|
||
|
/* Suppress compiler warnings. */
|
||
|
d = 0.0;
|
||
|
|
||
|
/* Check for leading white space. */
|
||
|
if (isspace((int)(str[0])))
|
||
|
XMLRPC_FAIL1(env, XMLRPC_PARSE_ERROR,
|
||
|
"\"%s\" must not contain whitespace", str);
|
||
|
|
||
|
/* Convert the value. */
|
||
|
end = str + stringLength;
|
||
|
errno = 0;
|
||
|
d = strtod(str, &end);
|
||
|
|
||
|
/* Look for ERANGE. */
|
||
|
if (errno != 0)
|
||
|
/* XXX - Do all operating systems have thread-safe strerror? */
|
||
|
XMLRPC_FAIL3(env, XMLRPC_PARSE_ERROR,
|
||
|
"error parsing \"%s\": %s (%d)",
|
||
|
str, strerror(errno), errno);
|
||
|
|
||
|
/* Check for unused characters. */
|
||
|
if (end != str + stringLength)
|
||
|
XMLRPC_FAIL1(env, XMLRPC_PARSE_ERROR,
|
||
|
"\"%s\" contained trailing data", str);
|
||
|
|
||
|
cleanup:
|
||
|
errno = 0;
|
||
|
if (env->fault_occurred)
|
||
|
return 0.0;
|
||
|
return d;
|
||
|
}
|
||
|
|
||
|
|
||
|
/*=========================================================================
|
||
|
** make_string
|
||
|
**=========================================================================
|
||
|
** Make an XML-RPC string.
|
||
|
**
|
||
|
** SECURITY: We validate our UTF-8 first. This incurs a performance
|
||
|
** penalty, but ensures that we will never pass maliciously malformed
|
||
|
** UTF-8 data back up to the user layer, where it could wreak untold
|
||
|
** damange. Don't comment out this check unless you know *exactly* what
|
||
|
** you're doing. (Win32 developers who remove this check are *begging*
|
||
|
** to wind up on BugTraq, because many of the Win32 filesystem routines
|
||
|
** rely on an insecure UTF-8 decoder.)
|
||
|
**
|
||
|
** XXX - This validation is redundant if the user chooses to convert
|
||
|
** UTF-8 data into a wchar_t string.
|
||
|
*/
|
||
|
|
||
|
static xmlrpc_value *
|
||
|
make_string(xmlrpc_env *env, char *cdata, size_t cdata_size)
|
||
|
{
|
||
|
#ifdef HAVE_UNICODE_WCHAR
|
||
|
xmlrpc_validate_utf8(env, cdata, cdata_size);
|
||
|
#endif
|
||
|
|
||
|
if (env->fault_occurred)
|
||
|
return NULL;
|
||
|
return xmlrpc_build_value(env, "s#", cdata, cdata_size);
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
/*=========================================================================
|
||
|
** convert_value
|
||
|
**=========================================================================
|
||
|
** Convert an XML element representing a value into an xmlrpc_value.
|
||
|
*/
|
||
|
|
||
|
static xmlrpc_value *
|
||
|
convert_array (xmlrpc_env *env, unsigned *depth, xml_element *elem);
|
||
|
static xmlrpc_value *
|
||
|
convert_struct(xmlrpc_env *env, unsigned *depth, xml_element *elem);
|
||
|
|
||
|
|
||
|
|
||
|
static xmlrpc_value *
|
||
|
convert_value(xmlrpc_env *env, unsigned *depth, xml_element *elem)
|
||
|
{
|
||
|
xml_element *child;
|
||
|
size_t child_count;
|
||
|
char *cdata, *child_name;
|
||
|
size_t cdata_size, ascii_len;
|
||
|
xmlrpc_mem_block *decoded;
|
||
|
unsigned char *ascii_data;
|
||
|
xmlrpc_value *retval;
|
||
|
xmlrpc_int32 i;
|
||
|
double d;
|
||
|
|
||
|
XMLRPC_ASSERT_ENV_OK(env);
|
||
|
XMLRPC_ASSERT(elem != NULL);
|
||
|
|
||
|
/* Error-handling precoditions.
|
||
|
** If we haven't changed any of these from their default state, we're
|
||
|
** allowed to tail-call xmlrpc_build_value. */
|
||
|
retval = NULL;
|
||
|
decoded = NULL;
|
||
|
|
||
|
/* Make sure we haven't recursed too deeply. */
|
||
|
if (*depth > xmlrpc_limit_get(XMLRPC_NESTING_LIMIT_ID))
|
||
|
XMLRPC_FAIL(env, XMLRPC_PARSE_ERROR,
|
||
|
"Nested data structure too deep.");
|
||
|
|
||
|
/* Validate our structure, and see whether we have a child element. */
|
||
|
CHECK_NAME(env, elem, "value");
|
||
|
child_count = xml_element_children_size(elem);
|
||
|
|
||
|
if (child_count == 0) {
|
||
|
/* We have no type element, so treat the value as a string. */
|
||
|
cdata = xml_element_cdata(elem);
|
||
|
cdata_size = xml_element_cdata_size(elem);
|
||
|
return make_string(env, cdata, cdata_size);
|
||
|
} else {
|
||
|
/* We should have a type tag inside our value tag. */
|
||
|
CHECK_CHILD_COUNT(env, elem, 1);
|
||
|
child = xml_element_children(elem)[0];
|
||
|
|
||
|
/* Parse our value-containing element. */
|
||
|
child_name = xml_element_name(child);
|
||
|
if (strcmp(child_name, "struct") == 0) {
|
||
|
return convert_struct(env, depth, child);
|
||
|
} else if (strcmp(child_name, "array") == 0) {
|
||
|
CHECK_CHILD_COUNT(env, child, 1);
|
||
|
return convert_array(env, depth, child);
|
||
|
} else {
|
||
|
CHECK_CHILD_COUNT(env, child, 0);
|
||
|
cdata = xml_element_cdata(child);
|
||
|
cdata_size = xml_element_cdata_size(child);
|
||
|
if (strcmp(child_name, "i4") == 0 ||
|
||
|
strcmp(child_name, "int") == 0)
|
||
|
{
|
||
|
i = xmlrpc_atoi(env, cdata, strlen(cdata),
|
||
|
XMLRPC_INT32_MIN, XMLRPC_INT32_MAX);
|
||
|
XMLRPC_FAIL_IF_FAULT(env);
|
||
|
return xmlrpc_build_value(env, "i", i);
|
||
|
} else if (strcmp(child_name, "string") == 0) {
|
||
|
return make_string(env, cdata, cdata_size);
|
||
|
} else if (strcmp(child_name, "boolean") == 0) {
|
||
|
i = xmlrpc_atoi(env, cdata, strlen(cdata), 0, 1);
|
||
|
XMLRPC_FAIL_IF_FAULT(env);
|
||
|
return xmlrpc_build_value(env, "b", (xmlrpc_bool) i);
|
||
|
} else if (strcmp(child_name, "double") == 0) {
|
||
|
d = xmlrpc_atod(env, cdata, strlen(cdata));
|
||
|
XMLRPC_FAIL_IF_FAULT(env);
|
||
|
return xmlrpc_build_value(env, "d", d);
|
||
|
} else if (strcmp(child_name, "dateTime.iso8601") == 0) {
|
||
|
return xmlrpc_build_value(env, "8", cdata);
|
||
|
} else if (strcmp(child_name, "base64") == 0) {
|
||
|
/* No more tail calls once we do this! */
|
||
|
decoded = xmlrpc_base64_decode(env, cdata, cdata_size);
|
||
|
XMLRPC_FAIL_IF_FAULT(env);
|
||
|
ascii_data = XMLRPC_TYPED_MEM_BLOCK_CONTENTS(unsigned char,
|
||
|
decoded);
|
||
|
ascii_len = XMLRPC_TYPED_MEM_BLOCK_SIZE(unsigned char,
|
||
|
decoded);
|
||
|
retval = xmlrpc_build_value(env, "6", ascii_data, ascii_len);
|
||
|
XMLRPC_FAIL_IF_FAULT(env);
|
||
|
} else {
|
||
|
XMLRPC_FAIL1(env, XMLRPC_PARSE_ERROR,
|
||
|
"Unknown value type <%s>", child_name);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
cleanup:
|
||
|
if (decoded)
|
||
|
xmlrpc_mem_block_free(decoded);
|
||
|
if (env->fault_occurred) {
|
||
|
if (retval)
|
||
|
xmlrpc_DECREF(retval);
|
||
|
return NULL;
|
||
|
}
|
||
|
return retval;
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
/*=========================================================================
|
||
|
** convert_array
|
||
|
**=========================================================================
|
||
|
** Convert an XML element representing an array into an xmlrpc_value.
|
||
|
*/
|
||
|
|
||
|
static xmlrpc_value *
|
||
|
convert_array(xmlrpc_env *env, unsigned *depth, xml_element *elem)
|
||
|
{
|
||
|
xml_element *data, **values, *value;
|
||
|
xmlrpc_value *array, *item;
|
||
|
size_t size, i;
|
||
|
|
||
|
XMLRPC_ASSERT_ENV_OK(env);
|
||
|
XMLRPC_ASSERT(elem != NULL);
|
||
|
|
||
|
/* Set up our error-handling preconditions. */
|
||
|
item = NULL;
|
||
|
(*depth)++;
|
||
|
|
||
|
/* Allocate an array to hold our values. */
|
||
|
array = xmlrpc_build_value(env, "()");
|
||
|
XMLRPC_FAIL_IF_FAULT(env);
|
||
|
|
||
|
/* We don't need to check our element name--our callers do that. */
|
||
|
CHECK_CHILD_COUNT(env, elem, 1);
|
||
|
data = xml_element_children(elem)[0];
|
||
|
CHECK_NAME(env, data, "data");
|
||
|
|
||
|
/* Iterate over our children. */
|
||
|
values = xml_element_children(data);
|
||
|
size = xml_element_children_size(data);
|
||
|
for (i = 0; i < size; i++) {
|
||
|
value = values[i];
|
||
|
item = convert_value(env, depth, value);
|
||
|
XMLRPC_FAIL_IF_FAULT(env);
|
||
|
|
||
|
xmlrpc_array_append_item(env, array, item);
|
||
|
xmlrpc_DECREF(item);
|
||
|
item = NULL;
|
||
|
XMLRPC_FAIL_IF_FAULT(env);
|
||
|
}
|
||
|
|
||
|
cleanup:
|
||
|
(*depth)--;
|
||
|
if (item)
|
||
|
xmlrpc_DECREF(item);
|
||
|
if (env->fault_occurred) {
|
||
|
if (array)
|
||
|
xmlrpc_DECREF(array);
|
||
|
return NULL;
|
||
|
}
|
||
|
return array;
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
/*=========================================================================
|
||
|
** convert_struct
|
||
|
**=========================================================================
|
||
|
** Convert an XML element representing a struct into an xmlrpc_value.
|
||
|
*/
|
||
|
|
||
|
static xmlrpc_value *
|
||
|
convert_struct(xmlrpc_env *env, unsigned *depth, xml_element *elem)
|
||
|
{
|
||
|
xmlrpc_value *strct, *key, *value;
|
||
|
xml_element **members, *member, *name_elem, *value_elem;
|
||
|
size_t size, i;
|
||
|
char *cdata;
|
||
|
size_t cdata_size;
|
||
|
|
||
|
XMLRPC_ASSERT_ENV_OK(env);
|
||
|
XMLRPC_ASSERT(elem != NULL);
|
||
|
|
||
|
/* Set up our error-handling preconditions. */
|
||
|
key = value = NULL;
|
||
|
(*depth)++;
|
||
|
|
||
|
/* Allocate an array to hold our members. */
|
||
|
strct = xmlrpc_struct_new(env);
|
||
|
XMLRPC_FAIL_IF_FAULT(env);
|
||
|
|
||
|
/* Iterate over our children, extracting key/value pairs. */
|
||
|
/* We don't need to check our element name--our callers do that. */
|
||
|
members = xml_element_children(elem);
|
||
|
size = xml_element_children_size(elem);
|
||
|
for (i = 0; i < size; i++) {
|
||
|
member = members[i];
|
||
|
CHECK_NAME(env, member, "member");
|
||
|
CHECK_CHILD_COUNT(env, member, 2);
|
||
|
|
||
|
/* Get our key. */
|
||
|
name_elem = get_child_by_name(env, member, "name");
|
||
|
XMLRPC_FAIL_IF_FAULT(env);
|
||
|
CHECK_CHILD_COUNT(env, name_elem, 0);
|
||
|
cdata = xml_element_cdata(name_elem);
|
||
|
cdata_size = xml_element_cdata_size(name_elem);
|
||
|
key = make_string(env, cdata, cdata_size);
|
||
|
XMLRPC_FAIL_IF_FAULT(env);
|
||
|
|
||
|
/* Get our value. */
|
||
|
value_elem = get_child_by_name(env, member, "value");
|
||
|
XMLRPC_FAIL_IF_FAULT(env);
|
||
|
value = convert_value(env, depth, value_elem);
|
||
|
XMLRPC_FAIL_IF_FAULT(env);
|
||
|
|
||
|
/* Add the key/value pair to our struct. */
|
||
|
xmlrpc_struct_set_value_v(env, strct, key, value);
|
||
|
XMLRPC_FAIL_IF_FAULT(env);
|
||
|
|
||
|
/* Release our references & memory, and restore our invariants. */
|
||
|
xmlrpc_DECREF(key);
|
||
|
key = NULL;
|
||
|
xmlrpc_DECREF(value);
|
||
|
value = NULL;
|
||
|
}
|
||
|
|
||
|
cleanup:
|
||
|
(*depth)--;
|
||
|
if (key)
|
||
|
xmlrpc_DECREF(key);
|
||
|
if (value)
|
||
|
xmlrpc_DECREF(value);
|
||
|
if (env->fault_occurred) {
|
||
|
if (strct)
|
||
|
xmlrpc_DECREF(strct);
|
||
|
return NULL;
|
||
|
}
|
||
|
return strct;
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
/*=========================================================================
|
||
|
** convert_params
|
||
|
**=========================================================================
|
||
|
** Convert an XML element representing a list of params into an
|
||
|
** xmlrpc_value (of type array).
|
||
|
*/
|
||
|
|
||
|
static xmlrpc_value *
|
||
|
convert_params(xmlrpc_env *env, unsigned *depth, xml_element *elem)
|
||
|
{
|
||
|
xmlrpc_value *array, *item;
|
||
|
size_t size, i;
|
||
|
xml_element **params, *param, *value;
|
||
|
|
||
|
XMLRPC_ASSERT_ENV_OK(env);
|
||
|
XMLRPC_ASSERT(elem != NULL);
|
||
|
|
||
|
/* Set up our error-handling preconditions. */
|
||
|
item = NULL;
|
||
|
|
||
|
/* Allocate an array to hold our parameters. */
|
||
|
array = xmlrpc_build_value(env, "()");
|
||
|
XMLRPC_FAIL_IF_FAULT(env);
|
||
|
|
||
|
/* We're responsible for checking our own element name. */
|
||
|
CHECK_NAME(env, elem, "params");
|
||
|
|
||
|
/* Iterate over our children. */
|
||
|
size = xml_element_children_size(elem);
|
||
|
params = xml_element_children(elem);
|
||
|
for (i = 0; i < size; i++) {
|
||
|
param = params[i];
|
||
|
CHECK_NAME(env, param, "param");
|
||
|
CHECK_CHILD_COUNT(env, param, 1);
|
||
|
|
||
|
value = xml_element_children(param)[0];
|
||
|
item = convert_value(env, depth, value);
|
||
|
XMLRPC_FAIL_IF_FAULT(env);
|
||
|
|
||
|
xmlrpc_array_append_item(env, array, item);
|
||
|
xmlrpc_DECREF(item);
|
||
|
item = NULL;
|
||
|
XMLRPC_FAIL_IF_FAULT(env);
|
||
|
}
|
||
|
|
||
|
cleanup:
|
||
|
if (env->fault_occurred) {
|
||
|
if (array)
|
||
|
xmlrpc_DECREF(array);
|
||
|
if (item)
|
||
|
xmlrpc_DECREF(item);
|
||
|
return NULL;
|
||
|
}
|
||
|
return array;
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
static void
|
||
|
parseCallXml(xmlrpc_env * const envP,
|
||
|
const char * const xmlData,
|
||
|
size_t const xmlLen,
|
||
|
xml_element ** const callElemP) {
|
||
|
|
||
|
xmlrpc_env env;
|
||
|
|
||
|
xmlrpc_env_init(&env);
|
||
|
*callElemP = xml_parse(&env, xmlData, (int)xmlLen);
|
||
|
if (env.fault_occurred)
|
||
|
xmlrpc_env_set_fault_formatted(
|
||
|
envP, env.fault_code, "Call is not valid XML. %s",
|
||
|
env.fault_string);
|
||
|
xmlrpc_env_clean(&env);
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
/*=========================================================================
|
||
|
** xmlrpc_parse_call
|
||
|
**=========================================================================
|
||
|
** Given some XML text, attempt to parse it as an XML-RPC call. Return
|
||
|
** a newly allocated xmlrpc_call structure (or NULL, if an error occurs).
|
||
|
** The two output variables will contain either valid values (which
|
||
|
** must free() and xmlrpc_DECREF(), respectively) or NULLs (if an error
|
||
|
** occurs).
|
||
|
*/
|
||
|
|
||
|
void
|
||
|
xmlrpc_parse_call(xmlrpc_env * const envP,
|
||
|
const char * const xml_data,
|
||
|
size_t const xml_len,
|
||
|
const char ** const out_method_nameP,
|
||
|
xmlrpc_value ** const out_param_arrayPP) {
|
||
|
|
||
|
xml_element *call_elem, *name_elem, *params_elem;
|
||
|
char *cdata;
|
||
|
unsigned depth;
|
||
|
size_t call_child_count;
|
||
|
char * outMethodName;
|
||
|
xmlrpc_value * outParamArrayP;
|
||
|
|
||
|
XMLRPC_ASSERT_ENV_OK(envP);
|
||
|
XMLRPC_ASSERT(xml_data != NULL);
|
||
|
XMLRPC_ASSERT(out_method_nameP != NULL && out_param_arrayPP != NULL);
|
||
|
|
||
|
/* Set up our error-handling preconditions. */
|
||
|
outMethodName = NULL;
|
||
|
outParamArrayP = NULL;
|
||
|
call_elem = NULL;
|
||
|
|
||
|
/* SECURITY: Last-ditch attempt to make sure our content length is legal.
|
||
|
** XXX - This check occurs too late to prevent an attacker from creating
|
||
|
** an enormous memory block in RAM, so you should try to enforce it
|
||
|
** *before* reading any data off the network. */
|
||
|
if (xml_len > xmlrpc_limit_get(XMLRPC_XML_SIZE_LIMIT_ID))
|
||
|
XMLRPC_FAIL(envP, XMLRPC_LIMIT_EXCEEDED_ERROR,
|
||
|
"XML-RPC request too large");
|
||
|
|
||
|
parseCallXml(envP, xml_data, xml_len, &call_elem);
|
||
|
XMLRPC_FAIL_IF_FAULT(envP);
|
||
|
|
||
|
/* Pick apart and verify our structure. */
|
||
|
CHECK_NAME(envP, call_elem, "methodCall");
|
||
|
call_child_count = xml_element_children_size(call_elem);
|
||
|
if (call_child_count != 2 && call_child_count != 1)
|
||
|
XMLRPC_FAIL1(envP, XMLRPC_PARSE_ERROR,
|
||
|
"Expected <methodCall> to have 1 or 2 children, found %d",
|
||
|
call_child_count);
|
||
|
|
||
|
/* Extract the method name.
|
||
|
** SECURITY: We make sure the method name is valid UTF-8. */
|
||
|
name_elem = get_child_by_name(envP, call_elem, "methodName");
|
||
|
XMLRPC_FAIL_IF_FAULT(envP);
|
||
|
CHECK_CHILD_COUNT(envP, name_elem, 0);
|
||
|
cdata = xml_element_cdata(name_elem);
|
||
|
#ifdef HAVE_UNICODE_WCHAR
|
||
|
xmlrpc_validate_utf8(envP, cdata, strlen(cdata));
|
||
|
XMLRPC_FAIL_IF_FAULT(envP);
|
||
|
#endif /* HAVE_UNICODE_WCHAR */
|
||
|
outMethodName = malloc(strlen(cdata) + 1);
|
||
|
XMLRPC_FAIL_IF_NULL(outMethodName, envP, XMLRPC_INTERNAL_ERROR,
|
||
|
"Could not allocate memory for method name");
|
||
|
strcpy(outMethodName, cdata);
|
||
|
|
||
|
/* Convert our parameters. */
|
||
|
if (call_child_count == 1) {
|
||
|
/* Workaround for Ruby XML-RPC and old versions of xmlrpc-epi. */
|
||
|
outParamArrayP = xmlrpc_build_value(envP, "()");
|
||
|
XMLRPC_FAIL_IF_FAULT(envP);
|
||
|
} else {
|
||
|
params_elem = get_child_by_name(envP, call_elem, "params");
|
||
|
XMLRPC_FAIL_IF_FAULT(envP);
|
||
|
depth = 0;
|
||
|
outParamArrayP = convert_params(envP, &depth, params_elem);
|
||
|
XMLRPC_ASSERT(depth == 0);
|
||
|
XMLRPC_FAIL_IF_FAULT(envP);
|
||
|
}
|
||
|
|
||
|
cleanup:
|
||
|
if (call_elem)
|
||
|
xml_element_free(call_elem);
|
||
|
if (envP->fault_occurred) {
|
||
|
if (outMethodName)
|
||
|
free(outMethodName);
|
||
|
if (outParamArrayP)
|
||
|
xmlrpc_DECREF(outParamArrayP);
|
||
|
outMethodName = NULL;
|
||
|
outParamArrayP = NULL;
|
||
|
}
|
||
|
*out_method_nameP = outMethodName;
|
||
|
*out_param_arrayPP = outParamArrayP;
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
/*=========================================================================
|
||
|
** xmlrpc_parse_response
|
||
|
**=========================================================================
|
||
|
** Given some XML text, attempt to parse it as an XML-RPC response.
|
||
|
** If the response is a regular, valid response, return a new reference
|
||
|
** to the appropriate value. If the response is a fault, or an error
|
||
|
** occurs during processing, return NULL and set up env appropriately.
|
||
|
*/
|
||
|
|
||
|
xmlrpc_value *
|
||
|
xmlrpc_parse_response(xmlrpc_env *env,
|
||
|
const char *xml_data,
|
||
|
size_t xml_len) {
|
||
|
|
||
|
xml_element *response, *child, *value;
|
||
|
unsigned depth;
|
||
|
xmlrpc_value *params, *retval, *fault;
|
||
|
int retval_incremented;
|
||
|
|
||
|
xmlrpc_value *fault_code_value, *fault_str_value;
|
||
|
xmlrpc_int32 fault_code;
|
||
|
char *fault_str;
|
||
|
|
||
|
XMLRPC_ASSERT_ENV_OK(env);
|
||
|
XMLRPC_ASSERT(xml_data != NULL);
|
||
|
|
||
|
/* Set up our error-handling preconditions. */
|
||
|
response = NULL;
|
||
|
params = fault = NULL;
|
||
|
retval_incremented = 0;
|
||
|
|
||
|
/* SECURITY: Last-ditch attempt to make sure our content length is legal.
|
||
|
** XXX - This check occurs too late to prevent an attacker from creating
|
||
|
** an enormous memory block in RAM, so you should try to enforce it
|
||
|
** *before* reading any data off the network. */
|
||
|
if (xml_len > xmlrpc_limit_get(XMLRPC_XML_SIZE_LIMIT_ID))
|
||
|
XMLRPC_FAIL(env, XMLRPC_LIMIT_EXCEEDED_ERROR,
|
||
|
"XML-RPC response too large");
|
||
|
|
||
|
/* SECURITY: Set up our recursion depth counter. */
|
||
|
depth = 0;
|
||
|
|
||
|
/* Parse our XML data. */
|
||
|
response = xml_parse(env, xml_data, (int)xml_len);
|
||
|
XMLRPC_FAIL_IF_FAULT(env);
|
||
|
|
||
|
/* Pick apart and verify our structure. */
|
||
|
CHECK_NAME(env, response, "methodResponse");
|
||
|
CHECK_CHILD_COUNT(env, response, 1);
|
||
|
child = xml_element_children(response)[0];
|
||
|
|
||
|
/* Parse the response itself. */
|
||
|
if (strcmp("params", xml_element_name(child)) == 0) {
|
||
|
|
||
|
/* Convert our parameter list. */
|
||
|
params = convert_params(env, &depth, child);
|
||
|
XMLRPC_FAIL_IF_FAULT(env);
|
||
|
|
||
|
/* Extract the return value, and jiggle our reference counts. */
|
||
|
xmlrpc_parse_value(env, params, "(V)", &retval);
|
||
|
XMLRPC_FAIL_IF_FAULT(env);
|
||
|
xmlrpc_INCREF(retval);
|
||
|
retval_incremented = 1;
|
||
|
|
||
|
} else if (strcmp("fault", xml_element_name(child)) == 0) {
|
||
|
|
||
|
/* Convert our fault structure. */
|
||
|
CHECK_CHILD_COUNT(env, child, 1);
|
||
|
value = xml_element_children(child)[0];
|
||
|
fault = convert_value(env, &depth, value);
|
||
|
XMLRPC_FAIL_IF_FAULT(env);
|
||
|
XMLRPC_TYPE_CHECK(env, fault, XMLRPC_TYPE_STRUCT);
|
||
|
|
||
|
/* Get our fault code. */
|
||
|
fault_code_value = xmlrpc_struct_get_value(env, fault, "faultCode");
|
||
|
XMLRPC_FAIL_IF_FAULT(env);
|
||
|
xmlrpc_parse_value(env, fault_code_value, "i", &fault_code);
|
||
|
XMLRPC_FAIL_IF_FAULT(env);
|
||
|
|
||
|
/* Get our fault string. */
|
||
|
fault_str_value = xmlrpc_struct_get_value(env, fault, "faultString");
|
||
|
XMLRPC_FAIL_IF_FAULT(env);
|
||
|
xmlrpc_parse_value(env, fault_str_value, "s", &fault_str);
|
||
|
XMLRPC_FAIL_IF_FAULT(env);
|
||
|
|
||
|
/* Return our fault. */
|
||
|
XMLRPC_FAIL(env, fault_code, fault_str);
|
||
|
|
||
|
} else {
|
||
|
XMLRPC_FAIL(env, XMLRPC_PARSE_ERROR,
|
||
|
"Expected <params> or <fault> in <methodResponse>");
|
||
|
}
|
||
|
|
||
|
/* Sanity-check our depth-counting code. */
|
||
|
XMLRPC_ASSERT(depth == 0);
|
||
|
|
||
|
cleanup:
|
||
|
if (response)
|
||
|
xml_element_free(response);
|
||
|
if (params)
|
||
|
xmlrpc_DECREF(params);
|
||
|
if (fault)
|
||
|
xmlrpc_DECREF(fault);
|
||
|
|
||
|
if (env->fault_occurred) {
|
||
|
if (retval_incremented)
|
||
|
xmlrpc_DECREF(retval);
|
||
|
return NULL;
|
||
|
}
|
||
|
return retval;
|
||
|
}
|