xmlrpc_serialize.c 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620
  1. /* Copyright (C) 2001 by First Peer, Inc. All rights reserved.
  2. **
  3. ** Redistribution and use in source and binary forms, with or without
  4. ** modification, are permitted provided that the following conditions
  5. ** are met:
  6. ** 1. Redistributions of source code must retain the above copyright
  7. ** notice, this list of conditions and the following disclaimer.
  8. ** 2. Redistributions in binary form must reproduce the above copyright
  9. ** notice, this list of conditions and the following disclaimer in the
  10. ** documentation and/or other materials provided with the distribution.
  11. ** 3. The name of the author may not be used to endorse or promote products
  12. ** derived from this software without specific prior written permission.
  13. **
  14. ** THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
  15. ** ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
  16. ** IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
  17. ** ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
  18. ** FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
  19. ** DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
  20. ** OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
  21. ** HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
  22. ** LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
  23. ** OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
  24. ** SUCH DAMAGE. */
  25. #include "xmlrpc_config.h"
  26. #include <stddef.h>
  27. #include <stdarg.h>
  28. #include <stdio.h>
  29. #include <string.h>
  30. #include "xmlrpc.h"
  31. #include "xmlrpc_int.h"
  32. #define CRLF "\015\012"
  33. #define SMALL_BUFFER_SZ (128)
  34. #define XML_PROLOGUE "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"CRLF
  35. /*=========================================================================
  36. ** format_out
  37. **=========================================================================
  38. ** A lightweight print routine for use with various serialization
  39. ** functions. Only use this routine for printing small objects--it uses
  40. ** a fixed-size internal buffer and returns an error on overflow.
  41. ** In particular, do NOT use this routine to print XML-RPC string values!
  42. */
  43. static void
  44. format_out(xmlrpc_env *env,
  45. xmlrpc_mem_block *output,
  46. char *format_string,
  47. ...) {
  48. va_list args;
  49. char buffer[SMALL_BUFFER_SZ];
  50. int count;
  51. XMLRPC_ASSERT_ENV_OK(env);
  52. va_start(args, format_string);
  53. /* We assume that this function is present and works correctly. Right. */
  54. count = vsnprintf(buffer, SMALL_BUFFER_SZ, format_string, args);
  55. /* Old C libraries return -1 if vsnprintf overflows its buffer.
  56. ** New C libraries return the number of characters which *would* have
  57. ** been printed if the error did not occur. This is impressively vile.
  58. ** Thank the C99 committee for this bright idea. But wait! We also
  59. ** need to keep track of the trailing NULL. */
  60. if (count < 0 || count >= (SMALL_BUFFER_SZ - 1))
  61. XMLRPC_FAIL(env, XMLRPC_INTERNAL_ERROR,
  62. "format_out overflowed internal buffer");
  63. /* Append our new data to our output. */
  64. XMLRPC_TYPED_MEM_BLOCK_APPEND(char, env, output, buffer, count);
  65. XMLRPC_FAIL_IF_FAULT(env);
  66. cleanup:
  67. va_end(args);
  68. }
  69. /*=========================================================================
  70. ** Warnings About Invalid UTF-8
  71. **=========================================================================
  72. ** We claim to send UTF-8 data to the network. But we rely on application
  73. ** programs to pass us correctly-formed UTF-8 data, which is very naive
  74. ** and optimistic.
  75. **
  76. ** In debudding mode, we call this routine to issue dire-sounding
  77. ** warnings. For the sake of safety, this routine never exits the
  78. ** program or does anything else drastic.
  79. **
  80. ** This routine almost certainly slows down our output.
  81. */
  82. #if !defined NDEBUG && defined HAVE_UNICODE_WCHAR
  83. static void
  84. sanity_check_utf8(const char * const str,
  85. size_t const len) {
  86. xmlrpc_env env;
  87. xmlrpc_env_init(&env);
  88. xmlrpc_validate_utf8(&env, str, len);
  89. if (env.fault_occurred)
  90. fprintf(stderr, "*** xmlrpc-c WARNING ***: %s (%s)\n",
  91. "Application sending corrupted UTF-8 data to network",
  92. env.fault_string);
  93. xmlrpc_env_clean(&env);
  94. }
  95. #endif
  96. /*=========================================================================
  97. ** Escaping Strings
  98. **=========================================================================
  99. */
  100. static xmlrpc_mem_block *
  101. escape_string(xmlrpc_env * const env,
  102. const char * const str,
  103. size_t const len) {
  104. xmlrpc_mem_block *retval;
  105. size_t i, needed;
  106. char *out;
  107. XMLRPC_ASSERT_ENV_OK(env);
  108. XMLRPC_ASSERT(str != NULL);
  109. /* Sanity-check this string before we print it. */
  110. #if !defined NDEBUG && defined HAVE_UNICODE_WCHAR
  111. sanity_check_utf8(str, len);
  112. #endif
  113. /* Calculate the amount of space we'll need. */
  114. needed = 0;
  115. for (i = 0; i < len; i++) {
  116. if (str[i] == '<')
  117. needed += 4; /* &lt; */
  118. else if (str[i] == '>')
  119. needed += 4; /* &gt; */
  120. else if (str[i] == '&')
  121. needed += 5; /* &amp; */
  122. else
  123. needed++;
  124. }
  125. /* Allocate our memory block. */
  126. retval = XMLRPC_TYPED_MEM_BLOCK_NEW(char, env, needed);
  127. XMLRPC_FAIL_IF_FAULT(env);
  128. /* Copy over the newly-allocated data. */
  129. out = XMLRPC_TYPED_MEM_BLOCK_CONTENTS(char, retval);
  130. for (i = 0; i < len; i++) {
  131. if (str[i] == '<') {
  132. *out++ = '&';
  133. *out++ = 'l';
  134. *out++ = 't';
  135. *out++ = ';';
  136. } else if (str[i] == '>') {
  137. *out++ = '&';
  138. *out++ = 'g';
  139. *out++ = 't';
  140. *out++ = ';';
  141. } else if (str[i] == '&') {
  142. *out++ = '&';
  143. *out++ = 'a';
  144. *out++ = 'm';
  145. *out++ = 'p';
  146. *out++ = ';';
  147. } else {
  148. *out++ = str[i];
  149. }
  150. }
  151. cleanup:
  152. if (env->fault_occurred) {
  153. if (retval)
  154. XMLRPC_TYPED_MEM_BLOCK_FREE(char, retval);
  155. retval = NULL;
  156. }
  157. return retval;
  158. }
  159. static xmlrpc_mem_block*
  160. escape_block (xmlrpc_env *env,
  161. xmlrpc_mem_block *block) {
  162. XMLRPC_ASSERT_ENV_OK(env);
  163. XMLRPC_ASSERT(block != NULL);
  164. return escape_string(env,
  165. XMLRPC_TYPED_MEM_BLOCK_CONTENTS(char, block),
  166. XMLRPC_TYPED_MEM_BLOCK_SIZE(char, block));
  167. }
  168. /*=========================================================================
  169. ** xmlrpc_serialize_string_data
  170. **=========================================================================
  171. ** Escape and print the contents of a string.
  172. */
  173. static void
  174. xmlrpc_serialize_string_data(xmlrpc_env *env,
  175. xmlrpc_mem_block *output,
  176. xmlrpc_value *string) {
  177. xmlrpc_mem_block *escaped;
  178. char *contents;
  179. size_t size;
  180. /* Since this routine can only be called internally, we only need
  181. ** an assertion here, not a runtime type check.
  182. ** XXX - Temporarily disabled because we're using this code to
  183. ** print <dateTime.iso8601> values as well. */
  184. /* XMLRPC_ASSERT(string->_type == XMLRPC_TYPE_STRING); */
  185. /* Escape any '&' and '<' characters in the string. */
  186. escaped = escape_block(env, &string->_block);
  187. XMLRPC_FAIL_IF_FAULT(env);
  188. contents = XMLRPC_TYPED_MEM_BLOCK_CONTENTS(char, escaped);
  189. size = XMLRPC_TYPED_MEM_BLOCK_SIZE(char, escaped) - 1;
  190. /* Print the string. */
  191. XMLRPC_TYPED_MEM_BLOCK_APPEND(char, env, output, contents, size);
  192. XMLRPC_FAIL_IF_FAULT(env);
  193. cleanup:
  194. if (escaped)
  195. XMLRPC_TYPED_MEM_BLOCK_FREE(char, escaped);
  196. }
  197. /*=========================================================================
  198. ** xmlrpc_serialize_base64_data
  199. **=========================================================================
  200. ** Print the contents of a memory block as well-formed Base64 data.
  201. */
  202. static void
  203. xmlrpc_serialize_base64_data (xmlrpc_env *env,
  204. xmlrpc_mem_block *output,
  205. unsigned char* data, size_t len) {
  206. xmlrpc_mem_block *encoded;
  207. unsigned char *contents;
  208. size_t size;
  209. /* Encode the data. */
  210. encoded = xmlrpc_base64_encode_without_newlines(env, data, len);
  211. XMLRPC_FAIL_IF_FAULT(env);
  212. contents = XMLRPC_TYPED_MEM_BLOCK_CONTENTS(unsigned char, encoded);
  213. size = XMLRPC_TYPED_MEM_BLOCK_SIZE(unsigned char, encoded);
  214. /* Print the data. */
  215. XMLRPC_TYPED_MEM_BLOCK_APPEND(char, env, output, contents, size);
  216. XMLRPC_FAIL_IF_FAULT(env);
  217. cleanup:
  218. if (encoded)
  219. XMLRPC_TYPED_MEM_BLOCK_FREE(char, encoded);
  220. }
  221. /*=========================================================================
  222. ** xmlrpc_serialize_struct
  223. **=========================================================================
  224. ** Dump the contents of a struct.
  225. */
  226. static void
  227. xmlrpc_serialize_struct(xmlrpc_env *env,
  228. xmlrpc_mem_block *output,
  229. xmlrpc_value *strct) {
  230. size_t size;
  231. size_t i;
  232. xmlrpc_value *key, *value;
  233. format_out(env, output, "<struct>"CRLF);
  234. XMLRPC_FAIL_IF_FAULT(env);
  235. size = xmlrpc_struct_size(env, strct);
  236. XMLRPC_FAIL_IF_FAULT(env);
  237. for (i = 0; i < size; i++) {
  238. xmlrpc_struct_get_key_and_value(env, strct, (int)i, &key, &value);
  239. XMLRPC_FAIL_IF_FAULT(env);
  240. format_out(env, output, "<member><name>");
  241. XMLRPC_FAIL_IF_FAULT(env);
  242. xmlrpc_serialize_string_data(env, output, key);
  243. XMLRPC_FAIL_IF_FAULT(env);
  244. format_out(env, output, "</name>"CRLF);
  245. XMLRPC_FAIL_IF_FAULT(env);
  246. xmlrpc_serialize_value(env, output, value);
  247. XMLRPC_FAIL_IF_FAULT(env);
  248. format_out(env, output, "</member>"CRLF);
  249. XMLRPC_FAIL_IF_FAULT(env);
  250. }
  251. format_out(env, output, "</struct>");
  252. XMLRPC_FAIL_IF_FAULT(env);
  253. cleanup:
  254. return;
  255. }
  256. /*=========================================================================
  257. ** xmlrpc_serialize_value
  258. **=========================================================================
  259. ** Dump a value in the appropriate fashion.
  260. */
  261. void
  262. xmlrpc_serialize_value(xmlrpc_env *env,
  263. xmlrpc_mem_block *output,
  264. xmlrpc_value *value) {
  265. xmlrpc_value *item;
  266. size_t size;
  267. unsigned char* contents;
  268. size_t i;
  269. XMLRPC_ASSERT_ENV_OK(env);
  270. XMLRPC_ASSERT(output != NULL);
  271. XMLRPC_ASSERT_VALUE_OK(value);
  272. /* Print our ubiquitous header. */
  273. format_out(env, output, "<value>");
  274. XMLRPC_FAIL_IF_FAULT(env);
  275. switch (value->_type) {
  276. case XMLRPC_TYPE_INT:
  277. /* XXX - We assume that '%i' is the appropriate format specifier
  278. ** for an xmlrpc_int32 value. We should add some test cases to
  279. ** make sure this works. */
  280. format_out(env, output, "<i4>%i</i4>", value->_value.i);
  281. break;
  282. case XMLRPC_TYPE_BOOL:
  283. /* XXX - We assume that '%i' is the appropriate format specifier
  284. ** for an xmlrpc_bool value. */
  285. format_out(env, output, "<boolean>%i</boolean>",
  286. (value->_value.b) ? 1 : 0);
  287. break;
  288. case XMLRPC_TYPE_DOUBLE:
  289. /* We must output a number of the form [+-]?\d*.\d*. */
  290. format_out(env, output, "<double>%.17g</double>", value->_value.d);
  291. break;
  292. case XMLRPC_TYPE_STRING:
  293. format_out(env, output, "<string>");
  294. XMLRPC_FAIL_IF_FAULT(env);
  295. xmlrpc_serialize_string_data(env, output, value);
  296. XMLRPC_FAIL_IF_FAULT(env);
  297. format_out(env, output, "</string>");
  298. break;
  299. case XMLRPC_TYPE_ARRAY:
  300. format_out(env, output, "<array><data>"CRLF);
  301. XMLRPC_FAIL_IF_FAULT(env);
  302. /* Serialize each item. */
  303. size = xmlrpc_array_size(env, value);
  304. XMLRPC_FAIL_IF_FAULT(env);
  305. for (i = 0; i < size; i++) {
  306. item = xmlrpc_array_get_item(env, value, (int)i);
  307. XMLRPC_FAIL_IF_FAULT(env);
  308. xmlrpc_serialize_value(env, output, item);
  309. XMLRPC_FAIL_IF_FAULT(env);
  310. format_out(env, output, CRLF);
  311. XMLRPC_FAIL_IF_FAULT(env);
  312. }
  313. format_out(env, output, "</data></array>");
  314. break;
  315. case XMLRPC_TYPE_STRUCT:
  316. xmlrpc_serialize_struct(env, output, value);
  317. break;
  318. case XMLRPC_TYPE_BASE64:
  319. format_out(env, output, "<base64>"CRLF);
  320. XMLRPC_FAIL_IF_FAULT(env);
  321. contents = XMLRPC_TYPED_MEM_BLOCK_CONTENTS(unsigned char,
  322. &value->_block);
  323. size = XMLRPC_TYPED_MEM_BLOCK_SIZE(unsigned char, &value->_block);
  324. xmlrpc_serialize_base64_data(env, output, contents, size);
  325. XMLRPC_FAIL_IF_FAULT(env);
  326. format_out(env, output, "</base64>");
  327. break;
  328. case XMLRPC_TYPE_DATETIME:
  329. format_out(env, output, "<dateTime.iso8601>");
  330. XMLRPC_FAIL_IF_FAULT(env);
  331. xmlrpc_serialize_string_data(env, output, value);
  332. XMLRPC_FAIL_IF_FAULT(env);
  333. format_out(env, output, "</dateTime.iso8601>");
  334. break;
  335. case XMLRPC_TYPE_C_PTR:
  336. xmlrpc_env_set_fault_formatted(
  337. env, XMLRPC_INTERNAL_ERROR,
  338. "Tried to serialize a C pointer value.");
  339. break;
  340. case XMLRPC_TYPE_DEAD:
  341. xmlrpc_env_set_fault_formatted(
  342. env, XMLRPC_INTERNAL_ERROR,
  343. "Tried to serialize a deaad value.");
  344. break;
  345. default:
  346. xmlrpc_env_set_fault_formatted(
  347. env, XMLRPC_INTERNAL_ERROR,
  348. "Invalid xmlrpc_value type: %d", value->_type);
  349. }
  350. XMLRPC_FAIL_IF_FAULT(env);
  351. /* Print our ubiquitous footer. */
  352. format_out(env, output, "</value>");
  353. XMLRPC_FAIL_IF_FAULT(env);
  354. cleanup:
  355. return;
  356. }
  357. /*=========================================================================
  358. ** xmlrpc_serialize_params
  359. **=========================================================================
  360. ** Serialize a list as a set of parameters.
  361. */
  362. void
  363. xmlrpc_serialize_params(xmlrpc_env *env,
  364. xmlrpc_mem_block *output,
  365. xmlrpc_value *param_array) {
  366. size_t size, i;
  367. xmlrpc_value *item;
  368. XMLRPC_ASSERT_ENV_OK(env);
  369. XMLRPC_ASSERT(output != NULL);
  370. XMLRPC_ASSERT_VALUE_OK(param_array);
  371. format_out(env, output, "<params>"CRLF);
  372. XMLRPC_FAIL_IF_FAULT(env);
  373. /* Dump each parameter. */
  374. size = xmlrpc_array_size(env, param_array);
  375. XMLRPC_FAIL_IF_FAULT(env);
  376. for (i = 0; i < size; i++) {
  377. format_out(env, output, "<param>");
  378. XMLRPC_FAIL_IF_FAULT(env);
  379. item = xmlrpc_array_get_item(env, param_array, (int)i);
  380. XMLRPC_FAIL_IF_FAULT(env);
  381. xmlrpc_serialize_value(env, output, item);
  382. XMLRPC_FAIL_IF_FAULT(env);
  383. format_out(env, output, "</param>"CRLF);
  384. XMLRPC_FAIL_IF_FAULT(env);
  385. }
  386. format_out(env, output, "</params>"CRLF);
  387. XMLRPC_FAIL_IF_FAULT(env);
  388. cleanup:
  389. return;
  390. }
  391. /*=========================================================================
  392. ** xmlrpc_serialize_call
  393. **=========================================================================
  394. ** Serialize an XML-RPC call.
  395. */
  396. void
  397. xmlrpc_serialize_call(xmlrpc_env * const env,
  398. xmlrpc_mem_block * const output,
  399. const char * const method_name,
  400. xmlrpc_value * const param_array) {
  401. xmlrpc_mem_block *escaped;
  402. char *contents;
  403. size_t size;
  404. XMLRPC_ASSERT_ENV_OK(env);
  405. XMLRPC_ASSERT(output != NULL);
  406. XMLRPC_ASSERT(method_name != NULL);
  407. XMLRPC_ASSERT_VALUE_OK(param_array);
  408. /* Set up our error-handling preconditions. */
  409. escaped = NULL;
  410. /* Dump our header. */
  411. format_out(env, output, XML_PROLOGUE);
  412. XMLRPC_FAIL_IF_FAULT(env);
  413. format_out(env, output, "<methodCall>"CRLF"<methodName>");
  414. XMLRPC_FAIL_IF_FAULT(env);
  415. /* Dump the method name. */
  416. escaped = escape_string(env, method_name, strlen(method_name));
  417. XMLRPC_FAIL_IF_FAULT(env);
  418. contents = XMLRPC_TYPED_MEM_BLOCK_CONTENTS(char, escaped);
  419. size = XMLRPC_TYPED_MEM_BLOCK_SIZE(char, escaped);
  420. XMLRPC_TYPED_MEM_BLOCK_APPEND(char, env, output, contents, size);
  421. XMLRPC_FAIL_IF_FAULT(env);
  422. /* Dump our parameters and footer. */
  423. format_out(env, output, "</methodName>"CRLF);
  424. XMLRPC_FAIL_IF_FAULT(env);
  425. xmlrpc_serialize_params(env, output, param_array);
  426. XMLRPC_FAIL_IF_FAULT(env);
  427. format_out(env, output, "</methodCall>"CRLF);
  428. XMLRPC_FAIL_IF_FAULT(env);
  429. cleanup:
  430. if (escaped)
  431. xmlrpc_mem_block_free(escaped);
  432. }
  433. /*=========================================================================
  434. ** xmlrpc_serialize_response
  435. **=========================================================================
  436. ** Serialize the (non-fault) response to an XML-RPC call.
  437. */
  438. void
  439. xmlrpc_serialize_response (xmlrpc_env *env,
  440. xmlrpc_mem_block *output,
  441. xmlrpc_value *value) {
  442. XMLRPC_ASSERT_ENV_OK(env);
  443. XMLRPC_ASSERT(output != NULL);
  444. XMLRPC_ASSERT_VALUE_OK(value);
  445. format_out(env, output, XML_PROLOGUE);
  446. XMLRPC_FAIL_IF_FAULT(env);
  447. format_out(env, output, "<methodResponse>"CRLF"<params>"CRLF"<param>");
  448. XMLRPC_FAIL_IF_FAULT(env);
  449. xmlrpc_serialize_value(env, output, value);
  450. XMLRPC_FAIL_IF_FAULT(env);
  451. format_out(env, output,
  452. "</param>"CRLF"</params>"CRLF"</methodResponse>"CRLF);
  453. XMLRPC_FAIL_IF_FAULT(env);
  454. cleanup:
  455. return;
  456. }
  457. /*=========================================================================
  458. ** xmlrpc_serialize_fault
  459. **=========================================================================
  460. ** Serialize an XML-RPC fault.
  461. **
  462. ** If this function fails, it will set up the first env argument. You'll
  463. ** need to take some other drastic action to produce a serialized fault
  464. ** of your own. (This function should only fail in an out-of-memory
  465. ** situation, AFAIK.)
  466. */
  467. void
  468. xmlrpc_serialize_fault(xmlrpc_env *env,
  469. xmlrpc_mem_block *output,
  470. xmlrpc_env *fault) {
  471. xmlrpc_value *strct;
  472. XMLRPC_ASSERT_ENV_OK(env);
  473. XMLRPC_ASSERT(output != NULL);
  474. XMLRPC_ASSERT(fault != NULL && fault->fault_occurred);
  475. /* Build a fault structure. */
  476. strct = xmlrpc_build_value(env, "{s:i,s:s}",
  477. "faultCode", (xmlrpc_int32) fault->fault_code,
  478. "faultString", fault->fault_string);
  479. XMLRPC_FAIL_IF_FAULT(env);
  480. /* Output our header. */
  481. format_out(env, output, XML_PROLOGUE);
  482. XMLRPC_FAIL_IF_FAULT(env);
  483. format_out(env, output, "<methodResponse>"CRLF"<fault>"CRLF);
  484. XMLRPC_FAIL_IF_FAULT(env);
  485. /* Serialize our fault structure. */
  486. xmlrpc_serialize_value(env, output, strct);
  487. XMLRPC_FAIL_IF_FAULT(env);
  488. /* Output our footer. */
  489. format_out(env, output, CRLF"</fault>"CRLF"</methodResponse>"CRLF);
  490. XMLRPC_FAIL_IF_FAULT(env);
  491. cleanup:
  492. if (strct)
  493. xmlrpc_DECREF(strct);
  494. }