/* Make an XML-RPC call.

   User specifies details of the call on the command line.

   We print the result on Standard Output.

   Example:

     $ xmlrpc http://localhost:8080/RPC2 sample.add i/3 i/5
     Result:
       Integer: 8

     $ xmlrpc localhost:8080 sample.add i/3 i/5
     Result:
       Integer: 8
*/

#define _XOPEN_SOURCE 600  /* Make sure strdup() is in <string.h> */

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <assert.h>

#include "xmlrpc_config.h"  /* information about this build environment */
#include "bool.h"
#include "int.h"
#include "mallocvar.h"
#include "girstring.h"
#include "casprintf.h"
#include "string_parser.h"
#include "cmdline_parser.h"
#include "dumpvalue.h"

#include "xmlrpc-c/base.h"
#include "xmlrpc-c/client.h"
#include "xmlrpc-c/string_int.h"

#define NAME "xmlrpc command line program"
#define VERSION "1.0"

struct cmdlineInfo {
    const char *  url;
    const char *  username;
    const char *  password;
    const char *  methodName;
    unsigned int  paramCount;
    const char ** params;
        /* Array of parameters, in order.  Has 'paramCount' entries. */
    const char *  transport;
        /* Name of XML transport he wants to use.  NULL if he has no 
           preference.
        */
    const char *  curlinterface;
        /* "network interface" parameter for the Curl transport.  (Not
           valid if 'transport' names a non-Curl transport).
        */
    xmlrpc_bool   curlnoverifypeer;
    xmlrpc_bool   curlnoverifyhost;
    const char *  curluseragent;
};



static void 
die_if_fault_occurred (xmlrpc_env * const envP) {
    if (envP->fault_occurred) {
        fprintf(stderr, "Failed.  %s\n", envP->fault_string);
        exit(1);
    }
}



static void GNU_PRINTF_ATTR(2,3)
setError(xmlrpc_env * const envP, const char format[], ...) {
    va_list args;
    const char * faultString;

    va_start(args, format);

    cvasprintf(&faultString, format, args);
    va_end(args);

    xmlrpc_env_set_fault(envP, XMLRPC_INTERNAL_ERROR, faultString);

    strfree(faultString);
}
      


static void
processArguments(xmlrpc_env *         const envP,
                 cmdlineParser        const cp,
                 struct cmdlineInfo * const cmdlineP) {

    if (cmd_argumentCount(cp) < 2)
        setError(envP, "Not enough arguments.  Need at least a URL and "
                 "method name.");
    else {
        unsigned int i;
        
        cmdlineP->url        = cmd_getArgument(cp, 0);
        cmdlineP->methodName = cmd_getArgument(cp, 1);
        cmdlineP->paramCount = cmd_argumentCount(cp) - 2;
        MALLOCARRAY_NOFAIL(cmdlineP->params, cmdlineP->paramCount);
        for (i = 0; i < cmdlineP->paramCount; ++i)
            cmdlineP->params[i] = cmd_getArgument(cp, i+2);
    }
}



static void
chooseTransport(xmlrpc_env *  const envP ATTR_UNUSED,
                cmdlineParser const cp,
                const char ** const transportPP) {
    
    const char * transportOpt = cmd_getOptionValueString(cp, "transport");

    if (transportOpt) {
        *transportPP = transportOpt;
    } else {
        if (cmd_optionIsPresent(cp, "curlinterface") || 
            cmd_optionIsPresent(cp, "curlnoverifypeer") ||
            cmd_optionIsPresent(cp, "curlnoverifyhost") ||
            cmd_optionIsPresent(cp, "curluseragent"))

            *transportPP = strdup("curl");
        else
            *transportPP = NULL;
    }
}



static void
parseCommandLine(xmlrpc_env *         const envP,
                 int                  const argc,
                 const char **        const argv,
                 struct cmdlineInfo * const cmdlineP) {

    cmdlineParser const cp = cmd_createOptionParser();

    const char * error;

    cmd_defineOption(cp, "transport",        OPTTYPE_STRING);
    cmd_defineOption(cp, "username",         OPTTYPE_STRING);
    cmd_defineOption(cp, "password",         OPTTYPE_STRING);
    cmd_defineOption(cp, "curlinterface",    OPTTYPE_STRING);
    cmd_defineOption(cp, "curlnoverifypeer", OPTTYPE_STRING);
    cmd_defineOption(cp, "curlnoverifyhost", OPTTYPE_STRING);
    cmd_defineOption(cp, "curluseragent",    OPTTYPE_STRING);

    cmd_processOptions(cp, argc, argv, &error);

    if (error) {
        setError(envP, "Command syntax error.  %s", error);
        strfree(error);
    } else {
        cmdlineP->username  = cmd_getOptionValueString(cp, "username");
        cmdlineP->password  = cmd_getOptionValueString(cp, "password");

        if (cmdlineP->username && !cmdlineP->password)
            setError(envP, "When you specify -username, you must also "
                     "specify -password.");
        else {
            chooseTransport(envP, cp, &cmdlineP->transport);

            cmdlineP->curlinterface = 
                cmd_getOptionValueString(cp, "curlinterface");
            cmdlineP->curlnoverifypeer =
                cmd_optionIsPresent(cp, "curlnoverifypeer");
            cmdlineP->curlnoverifyhost =
                cmd_optionIsPresent(cp, "curlnoverifyhost");
            cmdlineP->curluseragent =
                cmd_getOptionValueString(cp, "curluseragent");

            if ((!cmdlineP->transport || 
                 !streq(cmdlineP->transport, "curl"))
                &&
                (cmdlineP->curlinterface ||
                 cmdlineP->curlnoverifypeer ||
                 cmdlineP->curlnoverifyhost ||
                 cmdlineP->curluseragent))
                setError(envP, "You may not specify a Curl transport "
                         "option unless you also specify -transport=curl");

            processArguments(envP, cp, cmdlineP);
        }
    }
    cmd_destroyOptionParser(cp);
}



static void
freeCmdline(struct cmdlineInfo const cmdline) {

    unsigned int i;
    
    strfree(cmdline.url);
    strfree(cmdline.methodName);
    if (cmdline.transport)
        strfree(cmdline.transport);
    if (cmdline.curlinterface)
        strfree(cmdline.curlinterface);
    if (cmdline.curluseragent)
        strfree(cmdline.curluseragent);
    if (cmdline.username)
        strfree(cmdline.username);
    if (cmdline.password)
        strfree(cmdline.password);
    for (i = 0; i < cmdline.paramCount; ++i)
        strfree(cmdline.params[i]);
}



static void
computeUrl(const char *  const urlArg,
           const char ** const urlP) {

    if (strstr(urlArg, "://") != 0)
        casprintf(urlP, "%s", urlArg);
    else
        casprintf(urlP, "http://%s/RPC2", urlArg);
}


enum TokenType {COMMA, COLON, LEFTPAREN, RIGHTPAREN,
                LEFTBRACE, RIGHTBRACE, END};

static const char *
tokenTypeName(enum TokenType const type) {

    switch (type) {
    case COMMA:      return "comma";             
    case COLON:      return "colon";             
    case LEFTPAREN:  return "left parenthesis";  
    case RIGHTPAREN: return "right parenthesis"; 
    case LEFTBRACE:  return "left brace";        
    case RIGHTBRACE: return "right brace";       
    case END:        return "end of string";     
    }
    return NULL; /* defeat bogus compiler warning */
}



static void
getDelimiter(xmlrpc_env *     const envP,
             const char **    const cursorP,
             enum TokenType * const typeP) {

    const char * cursor;
    enum TokenType tokenType;

    cursor = *cursorP;

    switch (*cursor) {
    case ',': tokenType = COMMA; break;
    case ':': tokenType = COLON; break;
    case '(': tokenType = LEFTPAREN; break;
    case ')': tokenType = RIGHTPAREN; break;
    case '{': tokenType = LEFTBRACE; break;
    case '}': tokenType = RIGHTBRACE; break;
    case '\0': tokenType = END; break;
    default:
        setError(envP, "Unrecognized delimiter character '%c'", *cursor);
    }

    if (!envP->fault_occurred && tokenType != END)
        ++cursor;

    *cursorP = cursor;
    *typeP = tokenType;
}



static void
getCdata(xmlrpc_env *  const envP,
         const char ** const cursorP,
         const char ** const cdataP) {

    size_t const cdataSizeBound = strlen(*cursorP) + 1;

    char * text;

    MALLOCARRAY(text, cdataSizeBound);

    if (text == NULL)
        setError(envP, "Failed to allocate a buffer of size %u "
                 "to compute cdata", (unsigned)cdataSizeBound);
    else {
        unsigned int textCursor;
        bool end;
        const char * cursor;

        cursor = *cursorP;  /* initial value */

        for (textCursor = 0, end = false; !end; ) {
            switch (*cursor) {
            case ',': 
            case ':': 
            case '(': 
            case ')': 
            case '{': 
            case '}': 
            case '\0':
                end = true;
                break;
            case '\\': {
                ++cursor;  // skip backslash escape character
                if (!*cursor)
                    setError(envP, "Nothing after escape character ('\\')");
                else
                    text[textCursor++] = *cursor++;
            }; break;
            default:
                text[textCursor++]= *cursor++;
            }
            assert(textCursor <= cdataSizeBound);
        }
        text[textCursor++] = '\0';

        assert(textCursor <= cdataSizeBound);
    
        *cdataP  = text;
        *cursorP = cursor;
    }
}



static void
buildValue(xmlrpc_env *    const envP,
           const char **   const cursorP,
           xmlrpc_value ** const valuePP);  // for recursion


static void
buildString(xmlrpc_env *    const envP,
            const char *    const valueString,
            xmlrpc_value ** const paramPP) {

    *paramPP = xmlrpc_string_new(envP, valueString);
}



static void
interpretHex(xmlrpc_env *    const envP,
             const char *    const valueString,
             size_t          const valueStringSize,
             unsigned char * const byteString) {

    size_t bsCursor;
    size_t strCursor;

    for (strCursor = 0, bsCursor = 0;
         strCursor < valueStringSize && !envP->fault_occurred;
        ) {
        int rc;

        rc = sscanf(&valueString[strCursor], "%2hhx",
                    &byteString[bsCursor++]);

        if (rc != 1)
            xmlrpc_faultf(envP, "Invalid hex data '%s'",
                          &valueString[strCursor]);
        else
            strCursor += 2;
    }
}



static void
buildBytestring(xmlrpc_env *    const envP,
                const char *    const valueString,
                xmlrpc_value ** const paramPP) {

    size_t const valueStringSize = strlen(valueString);

    if (valueStringSize / 2 * 2 != valueStringSize)
        xmlrpc_faultf(envP, "Hexadecimal text is not an even "
                      "number of characters (it is %u characters)",
                      (unsigned)strlen(valueString));
    else {
        size_t const byteStringSize = strlen(valueString)/2;
        
        unsigned char * byteString;

        MALLOCARRAY(byteString, byteStringSize);

        if (byteString == NULL)
            xmlrpc_faultf(envP, "Failed to allocate %u-byte buffer",
                          (unsigned)byteStringSize);
        else {
            interpretHex(envP, valueString, valueStringSize, byteString);

            if (!envP->fault_occurred)
                *paramPP = xmlrpc_base64_new(envP, byteStringSize, byteString);

            free(byteString);
        }
    }
}



static void
buildInt(xmlrpc_env *    const envP,
         const char *    const valueString,
         xmlrpc_value ** const paramPP) {

    if (strlen(valueString) < 1)
        setError(envP, "Integer argument has nothing after the 'i/'");
    else {
        int value;
        const char * error;

        interpretInt(valueString, &value, &error);

        if (error) {
            setError(envP, "'%s' is not a valid 32-bit integer.  %s",
                     valueString, error);
            strfree(error);
        } else
            *paramPP = xmlrpc_int_new(envP, value);
    }
}



static void
buildBool(xmlrpc_env *    const envP,
          const char *    const valueString,
          xmlrpc_value ** const paramPP) {

    if (streq(valueString, "t") || streq(valueString, "true"))
        *paramPP = xmlrpc_bool_new(envP, true);
    else if (streq(valueString, "f") || streq(valueString, "false"))
        *paramPP = xmlrpc_bool_new(envP, false);
    else
        setError(envP, "Boolean argument has unrecognized value '%s'.  "
                 "recognized values are 't', 'f', 'true', and 'false'.",
                 valueString);
} 



static void
buildDouble(xmlrpc_env *    const envP,
            const char *    const valueString,
            xmlrpc_value ** const paramPP) {

    if (strlen(valueString) < 1)
        setError(envP, "\"Double\" argument has nothing after the 'd/'");
    else {
        double value;
        char * tailptr;

        value = strtod(valueString, &tailptr);

        if (*tailptr != '\0')
            setError(envP, 
                     "\"Double\" argument has non-decimal crap in it: '%s'",
                     tailptr);
        else
            *paramPP = xmlrpc_double_new(envP, value);
    }
}



static void
buildNil(xmlrpc_env *    const envP,
         const char *    const valueString,
         xmlrpc_value ** const paramPP) {

    if (strlen(valueString) > 0)
        setError(envP, "Nil argument has something after the 'n/'");
    else {
        *paramPP = xmlrpc_nil_new(envP);
    }
}



static void
buildI8(xmlrpc_env *    const envP,
        const char *    const valueString,
        xmlrpc_value ** const paramPP) {

    if (strlen(valueString) < 1)
        setError(envP, "Integer argument has nothing after the 'I/'");
    else {
        int64_t value;
        const char * error;

        interpretLl(valueString, &value, &error);

        if (error) {
            setError(envP, "'%s' is not a valid 64-bit integer.  %s",
                     valueString, error);
            strfree(error);
        } else
            *paramPP = xmlrpc_i8_new(envP, value);
    }
}



static void
addArrayItem(xmlrpc_env *   const envP,
             const char **  const cursorP,
             xmlrpc_value * const arrayP,
             bool *         const endP) {

    xmlrpc_value * itemP;

    buildValue(envP, cursorP, &itemP);

    if (!envP->fault_occurred) {
        xmlrpc_array_append_item(envP, arrayP, itemP);

        if (!envP->fault_occurred) {
            enum TokenType delim;

            getDelimiter(envP, cursorP, &delim);

            if (!envP->fault_occurred) {
                switch (delim) {
                case COMMA: break;
                case RIGHTPAREN: *endP = true; break;
                default:
                    setError(envP, "Array specifier has garbage where "
                             "there should be a comma "
                             "(element separator) "
                             "or close parenthesis "
                             "(marking the end of the element list)");
                }
            }
        }
        xmlrpc_DECREF(itemP);
    }
}



static void
buildArray(xmlrpc_env *    const envP,
           const char **   const cursorP,
           xmlrpc_value ** const valuePP) {

    enum TokenType tokenType;

    getDelimiter(envP, cursorP, &tokenType);

    if (!envP->fault_occurred) {
        if (tokenType != LEFTPAREN)
            setError(envP, "Array specifier value starts with %s instead of "
                     "left parenthesis", tokenTypeName(tokenType));
        else {
            xmlrpc_value * const arrayP = xmlrpc_array_new(envP);

            if (!envP->fault_occurred) {
                bool end;  /* We've reached the end of the array elements */
                for (end = false; !end && !envP->fault_occurred; )
                    addArrayItem(envP, cursorP, arrayP, &end);

                if (envP->fault_occurred)
                    xmlrpc_DECREF(arrayP);
                else
                    *valuePP = arrayP;
            }
        }
    }
}



static void
addStructMember(xmlrpc_env *   const envP,
                const char **  const cursorP,
                xmlrpc_value * const structP,
                bool *         const endP) {

    const char * key;

    getCdata(envP, cursorP, &key);

    if (!envP->fault_occurred) {
        enum TokenType delim;

        getDelimiter(envP, cursorP, &delim);

        if (!envP->fault_occurred) {
            if (delim != COLON)
                setError(envP, "Something other than a colon follows the "
                         "key value '%s' in a structure member.", key);
            else {
                xmlrpc_value * valueP;

                buildValue(envP, cursorP, &valueP);

                if (!envP->fault_occurred) {
                    xmlrpc_struct_set_value(envP, structP, key, valueP);

                    if (!envP->fault_occurred) {
                        enum TokenType delim;

                        getDelimiter(envP, cursorP, &delim);

                        if (!envP->fault_occurred) {
                            switch (delim) {
                            case COMMA: break;
                            case RIGHTBRACE: *endP = true; break;
                            default:
                                setError(envP, "Struct specifier "
                                         "has garbage where "
                                         "there should be a comma "
                                         "(member separator) "
                                         "or close brace "
                                         "(marking the end of the "
                                         "member list)");
                            }
                        }
                    }
                    xmlrpc_DECREF(valueP);
                }
            }
        }
        strfree(key);
    }
}




static void
buildStruct(xmlrpc_env *    const envP,
            const char **   const cursorP,
            xmlrpc_value ** const valuePP) {

    enum TokenType tokenType;

    getDelimiter(envP, cursorP, &tokenType);

    if (!envP->fault_occurred) {
        if (tokenType != LEFTBRACE)
            setError(envP, "Struct specifier value starts with %s instead of "
                     "left brace", tokenTypeName(tokenType));
        else {
            xmlrpc_value * const structP = xmlrpc_struct_new(envP);

            if (!envP->fault_occurred) {
                bool end;  /* We've reached the end of the struct members */
                for (end = false; !end && !envP->fault_occurred; )
                    addStructMember(envP, cursorP, structP, &end);

                if (envP->fault_occurred)
                    xmlrpc_DECREF(structP);
                else
                    *valuePP = structP;
            }
        }
    }
}



static void
buildValue(xmlrpc_env *    const envP,
           const char **   const cursorP,
           xmlrpc_value ** const valuePP) {
/*----------------------------------------------------------------------------
   Parse the text at *cursorP as a specification of an XML-RPC value
   (e.g. "i/4" or "array/(i/0,i/2,i/2)")

   Stop parsing at the end of the specification of one value.  Advance
   *cursorP to that spot.
-----------------------------------------------------------------------------*/
    const char * cdata;

    getCdata(envP, cursorP, &cdata);
        /* This should get e.g. "i/492" or "hello" or "array/" */

    if (!envP->fault_occurred) {
        if (strlen(cdata) == 0)
            setError(envP, "Expected value type specifier such as 'i/' or "
                     "'array/' but found '%s'", *cursorP);

        if (xmlrpc_strneq(cdata, "s/", 2))
            buildString(envP, &cdata[2], valuePP);
        else if (xmlrpc_strneq(cdata, "h/", 2))
            buildBytestring(envP, &cdata[2], valuePP);
        else if (xmlrpc_strneq(cdata, "i/", 2)) 
            buildInt(envP, &cdata[2], valuePP);
        else if (xmlrpc_strneq(cdata, "I/", 2)) 
            buildI8(envP, &cdata[2], valuePP);
        else if (xmlrpc_strneq(cdata, "d/", 2)) 
            buildDouble(envP, &cdata[2], valuePP);
        else if (xmlrpc_strneq(cdata, "b/", 2))
            buildBool(envP, &cdata[2], valuePP);
        else if (xmlrpc_strneq(cdata, "n/", 2))
            buildNil(envP, &cdata[2], valuePP);
        else if (xmlrpc_strneq(cdata, "array/", 6)) {
            if (cdata[6] != '\0')
                setError(envP, "Junk after 'array/' instead of "
                         "left parenthesis: '%s'", &cdata[6]);
            else
                buildArray(envP, cursorP, valuePP);
        } else if (xmlrpc_strneq(cdata, "struct/", 7)) {
            if (cdata[7] != '\0')
                setError(envP, "Junk after 'struct/' instead of "
                         "left brace: '%s'", &cdata[7]);
            else
                buildStruct(envP, cursorP, valuePP);
        } else {
            /* It's not in normal type/value format, so we take it to be
               the shortcut string notation 
            */
            buildString(envP, cdata, valuePP);
        }
        strfree(cdata);
    }
}



static void
computeParam(xmlrpc_env *    const envP,
             const char *    const paramArg,
             xmlrpc_value ** const paramPP) {

    const char * cursor;

    cursor = &paramArg[0];

    buildValue(envP, &cursor, paramPP);

    if (!envP->fault_occurred) {
        if (*cursor != '\0')
            setError(envP, "Junk after parameter specification: '%s'", cursor);
    }
}



static void
computeParamArray(xmlrpc_env *    const envP,
                  unsigned int    const paramCount,
                  const char **   const params,
                  xmlrpc_value ** const paramArrayPP) {
    
    unsigned int i;

    xmlrpc_value * paramArrayP;

    paramArrayP = xmlrpc_array_new(envP);

    for (i = 0; i < paramCount && !envP->fault_occurred; ++i) {
        xmlrpc_value * paramP;
        xmlrpc_env paramEnv;

        xmlrpc_env_init(&paramEnv);

        computeParam(&paramEnv, params[i], &paramP);

        if (!paramEnv.fault_occurred) {
            xmlrpc_array_append_item(&paramEnv, paramArrayP, paramP);

            xmlrpc_DECREF(paramP);
        }
        if (paramEnv.fault_occurred)
            setError(envP, "Invalid specification of parameter %u "
                     "(starting at zero).  %s", i, paramEnv.fault_string);

        xmlrpc_env_clean(&paramEnv);
    }
    *paramArrayPP = paramArrayP;
}



static void
dumpResult(xmlrpc_value * const resultP) {

    printf("Result:\n\n");

    dumpValue("", resultP);
}



static void
callWithClient(xmlrpc_env *               const envP,
               const xmlrpc_server_info * const serverInfoP,
               const char *               const methodName,
               xmlrpc_value *             const paramArrayP,
               xmlrpc_value **            const resultPP) {
               
    xmlrpc_env env;
    xmlrpc_env_init(&env);
    *resultPP = xmlrpc_client_call_server_params(
        &env, serverInfoP, methodName, paramArrayP);
    
    if (env.fault_occurred)
        xmlrpc_faultf(envP, "Call failed.  %s.  (XML-RPC fault code %d)",
                      env.fault_string, env.fault_code);
    xmlrpc_env_clean(&env);
}



static void
doCall(xmlrpc_env *               const envP,
       const char *               const transport,
       const char *               const curlinterface,
       xmlrpc_bool                const curlnoverifypeer,
       xmlrpc_bool                const curlnoverifyhost,
       const char *               const curluseragent,
       const xmlrpc_server_info * const serverInfoP,
       const char *               const methodName,
       xmlrpc_value *             const paramArrayP,
       xmlrpc_value **            const resultPP) {
    
    struct xmlrpc_clientparms clientparms;

    XMLRPC_ASSERT(xmlrpc_value_type(paramArrayP) == XMLRPC_TYPE_ARRAY);

    clientparms.transport = transport;

    if (transport && streq(transport, "curl")) {
        struct xmlrpc_curl_xportparms * curlXportParmsP;
        MALLOCVAR(curlXportParmsP);

        curlXportParmsP->network_interface = curlinterface;
        curlXportParmsP->no_ssl_verifypeer = curlnoverifypeer;
        curlXportParmsP->no_ssl_verifyhost = curlnoverifyhost;
        curlXportParmsP->user_agent        = curluseragent;
        
        clientparms.transportparmsP    = curlXportParmsP;
        clientparms.transportparm_size = XMLRPC_CXPSIZE(user_agent);
    } else {
        clientparms.transportparmsP = NULL;
        clientparms.transportparm_size = 0;
    }
    xmlrpc_client_init2(envP, XMLRPC_CLIENT_NO_FLAGS, NAME, VERSION, 
                        &clientparms, XMLRPC_CPSIZE(transportparm_size));
    if (!envP->fault_occurred) {
        callWithClient(envP, serverInfoP, methodName, paramArrayP, resultPP);

        xmlrpc_client_cleanup();
    }
    if (clientparms.transportparmsP)
        free((void*)clientparms.transportparmsP);
}



static void
createServerInfo(xmlrpc_env *          const envP,
                 const char *          const serverUrl,
                 const char *          const userName,
                 const char *          const password,
                 xmlrpc_server_info ** const serverInfoPP) {

    xmlrpc_server_info * serverInfoP;

    serverInfoP = xmlrpc_server_info_new(envP, serverUrl);
    if (!envP->fault_occurred) {
        if (userName) {
            xmlrpc_server_info_set_basic_auth(
                envP, serverInfoP, userName, password);
        }
    }
    *serverInfoPP = serverInfoP;
}



int 
main(int           const argc, 
     const char ** const argv) {

    struct cmdlineInfo cmdline;
    xmlrpc_env env;
    xmlrpc_value * paramArrayP;
    xmlrpc_value * resultP;
    const char * url;
    xmlrpc_server_info * serverInfoP;

    xmlrpc_env_init(&env);

    parseCommandLine(&env, argc, argv, &cmdline);
    die_if_fault_occurred(&env);

    computeUrl(cmdline.url, &url);

    computeParamArray(&env, cmdline.paramCount, cmdline.params, &paramArrayP);
    die_if_fault_occurred(&env);

    createServerInfo(&env, url, cmdline.username, cmdline.password,
                     &serverInfoP);
    die_if_fault_occurred(&env);

    doCall(&env, cmdline.transport, cmdline.curlinterface,
           cmdline.curlnoverifypeer, cmdline.curlnoverifyhost,
           cmdline.curluseragent,
           serverInfoP,
           cmdline.methodName, paramArrayP, 
           &resultP);
    die_if_fault_occurred(&env);

    dumpResult(resultP);
    
    strfree(url);

    xmlrpc_DECREF(resultP);

    freeCmdline(cmdline);

    xmlrpc_env_clean(&env);
    
    return 0;
}
