/* 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 */ #include #include #include #include #include #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 = ¶mArg[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(¶mEnv); computeParam(¶mEnv, params[i], ¶mP); if (!paramEnv.fault_occurred) { xmlrpc_array_append_item(¶mEnv, 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(¶mEnv); } *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, ¶mArrayP); 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; }