/* 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;
    int 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;
    int 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;
    int 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;
    int 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, 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, 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;
}
