/*
  This file is part of libXMLRPC - a C library for xml-encoded function calls.

  Author: Dan Libby (dan@libby.com)
  Epinions.com may be contacted at feedback@epinions-inc.com
*/

/*  
  Copyright 2000 Epinions, Inc. 

  Subject to the following 3 conditions, Epinions, Inc.  permits you, free 
  of charge, to (a) use, copy, distribute, modify, perform and display this 
  software and associated documentation files (the "Software"), and (b) 
  permit others to whom the Software is furnished to do so as well.  

  1) The above copyright notice and this permission notice shall be included 
  without modification in all copies or substantial portions of the 
  Software.  

  2) THE SOFTWARE IS PROVIDED "AS IS", WITHOUT ANY WARRANTY OR CONDITION OF 
  ANY KIND, EXPRESS, IMPLIED OR STATUTORY, INCLUDING WITHOUT LIMITATION ANY 
  IMPLIED WARRANTIES OF ACCURACY, MERCHANTABILITY, FITNESS FOR A PARTICULAR 
  PURPOSE OR NONINFRINGEMENT.  

  3) IN NO EVENT SHALL EPINIONS, INC. BE LIABLE FOR ANY DIRECT, INDIRECT, 
  SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES OR LOST PROFITS ARISING OUT 
  OF OR IN CONNECTION WITH THE SOFTWARE (HOWEVER ARISING, INCLUDING 
  NEGLIGENCE), EVEN IF EPINIONS, INC.  IS AWARE OF THE POSSIBILITY OF SUCH 
  DAMAGES.    

*/


static const char rcsid[] = "#(@) $Id: xmlrpc.c,v 1.8.4.3 2007/09/18 19:49:53 iliaa Exp $";


/****h* ABOUT/xmlrpc
 * NAME
 *   XMLRPC_VALUE
 * AUTHOR
 *   Dan Libby, aka danda  (dan@libby.com)
 * CREATION DATE
 *   9/1999 - 10/2000
 * HISTORY
 *   $Log: xmlrpc.c,v $
 *   Revision 1.8.4.3  2007/09/18 19:49:53  iliaa
 *
 *   Fixed bug #42189 (xmlrpc_set_type() crashes php on invalid datetime
 *     values).
 *
 *   Revision 1.8.4.2  2007/06/07 09:07:36  tony2001
 *   MFH: php_localtime_r() checks
 *
 *   Revision 1.8.4.1  2006/11/30 16:38:37  iliaa
 *   last set of zts fixes
 *
 *   Revision 1.8  2005/03/28 00:07:24  edink
 *   Reshufle includes to make it compile on windows
 *
 *   Revision 1.7  2005/03/26 03:13:58  sniper
 *   - Made it possible to build ext/xmlrpc with libxml2
 *
 *   Revision 1.6  2004/04/27 17:33:59  iliaa
 *   Removed C++ style comments.
 *
 *   Revision 1.5  2003/12/16 21:00:21  sniper
 *   Fix some compile warnings (patch by Joe Orton)
 *
 *   Revision 1.4  2002/07/05 04:43:53  danda
 *   merged in updates from SF project.  bring php repository up to date with xmlrpc-epi version 0.51
 *
 *   Revision 1.22  2002/03/09 23:15:44  danda
 *   add fault interrogation funcs
 *
 *   Revision 1.21  2002/03/09 22:27:41  danda
 *   win32 build patches contributed by Jeff Lawson
 *
 *   Revision 1.20  2002/02/13 20:58:50  danda
 *   patch to make source more windows friendly, contributed by Jeff Lawson
 *
 *   Revision 1.19  2001/10/12 23:25:54  danda
 *   default to writing xmlrpc
 *
 *   Revision 1.18  2001/09/29 21:58:05  danda
 *   adding cvs log to history section
 *
 *   10/15/2000 -- danda -- adding robodoc documentation
 *   08/2000 -- danda -- PHP C extension that uses XMLRPC                     
 *   08/2000 -- danda -- support for two vocabularies: danda-rpc and xml-rpc
 *   09/1999 -- danda -- Initial API, before I even knew of standard XMLRPC vocab. Response only.
 *   07/2000 -- danda -- wrote new implementation to be compatible with xmlrpc standard and
 *                       incorporated some ideas from ensor, most notably the separation of
 *                       xml dom from xmlrpc api.
 *   06/2000 -- danda -- played with expat-ensor from www.ensor.org.  Cool, but some flaws.
 * TODO
 * PORTABILITY
 *   Coded on RedHat Linux 6.2.  Builds on Solaris x86.  Should build on just
 *   about anything with minor mods.
 * NOTES
 *   Welcome to XMLRPC.  For more info on the specification and history, see
 *   http://www.xmlrpc.org.
 *
 *   This code aims to be a full-featured C implementation of XMLRPC.  It does not
 *   have any networking code.  Rather, it is intended to be plugged into apps
 *   or libraries with existing networking facilities, eg PHP, apache, perl, mozilla, 
 *   home-brew application servers, etc.
 *
 *   Usage Paradigm:
 *     The user of this library will typically be implementing either an XMLRPC server,
 *     an XMLRPC client, or both.  The client will use the library to build an in-memory
 *     representation of a request, and then serialize (encode) that request into XML. The
 *     client will then send the XML to the server via external mechanism.  The server will
 *     de-serialize the XML back into an binary representation, call the appropriate registered
 *     method -- thereby generating a response.  The response will be serialized into XML and
 *     sent back to the client.  The client will de-serialize it into memory, and can
 *     iterate through the results via API.
 *
 *     Both the request and the response may consist of arbitrarily long, arbitrarily nested
 *     values.  The values may be one of several types, as defined by XMLRPC_VALUE_TYPE.
 *
 *   Features and Architecture:
 *     - The XML parsing (xml_element.c) is completely independent of the XMLRPC api. In fact,
 *       it can be used as a standalone dom implementation.
 *     - Because of this, the same XMLRPC data can be serialized into multiple xml vocabularies.
 *       It is simply a matter of writing a transport.  So far, two transports have been defined.
 *       The default xmlrpc vocab (xml_to_xmlrpc.c), and simple-rpc (xml_to_dandarpc.c) which is 
 *       proprietary, but imho more readable, and nice for proprietary legacy reasons.
 *     - Various output options, including: xml escaping via CDATA or entity, case folding,
 *       vocab version, and character encoding.
 *     - One to One mapping between C structures and actual values, unlike ensor which forces
 *       one to understand the arcana of the xmlrpc vocab.
 *     - support for mixed indexed/keyed vector types, making it more compatible with 
 *       languages such as PHP.
 *     - quite speedy compared to implementations written in interpreted languages. Also, uses
 *       intelligent string handling, so not many strlen() calls, etc.
 *     - comprehensive API for manipulation of values
 *******/

#include "ext/xml/expat_compat.h"
#include "main/php_reentrancy.h"
#ifdef _WIN32
#include "xmlrpc_win32.h"
#endif
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdarg.h>
#include <time.h>
#include <ctype.h>

#include "queue.h"
#include "xmlrpc.h"
#include "base64.h"

#include "xml_to_xmlrpc.h"
#include "xml_to_dandarpc.h"
#include "xml_to_soap.h"
#include "xml_element.h"
#include "xmlrpc_private.h"
#include "xmlrpc_introspection_private.h"
#include "system_methods_private.h"



/*-*********************
* Begin Time Functions *
***********************/

static int date_from_ISO8601 (const char *text, time_t * value) {
   struct tm tm;
   int n;
   int i;
	char buf[18];

	if (strchr (text, '-')) {
		char *p = (char *) text, *p2 = buf;
		while (p && *p) {
			if (*p != '-') {
				*p2 = *p;
				p2++;
			}
			p++;
		}
			text = buf;
	}


   tm.tm_isdst = -1;

   if(strlen(text) < 17) {
      return -1;
   }

#define XMLRPC_IS_NUMBER(x) if (x < '0' || x > '9') return -1;

   n = 1000;
   tm.tm_year = 0;
   for(i = 0; i < 4; i++) {
      XMLRPC_IS_NUMBER(text[i])
      tm.tm_year += (text[i]-'0')*n;
      n /= 10;
   }
   n = 10;
   tm.tm_mon = 0;
   for(i = 0; i < 2; i++) {
      XMLRPC_IS_NUMBER(text[i])
      tm.tm_mon += (text[i+4]-'0')*n;
      n /= 10;
   }
   tm.tm_mon --;

   n = 10;
   tm.tm_mday = 0;
   for(i = 0; i < 2; i++) {
      XMLRPC_IS_NUMBER(text[i])
      tm.tm_mday += (text[i+6]-'0')*n;
      n /= 10;
   }

   n = 10;
   tm.tm_hour = 0;
   for(i = 0; i < 2; i++) {
      XMLRPC_IS_NUMBER(text[i])
      tm.tm_hour += (text[i+9]-'0')*n;
      n /= 10;
   }

   n = 10;
   tm.tm_min = 0;
   for(i = 0; i < 2; i++) {
      XMLRPC_IS_NUMBER(text[i])
      tm.tm_min += (text[i+12]-'0')*n;
      n /= 10;
   }

   n = 10;
   tm.tm_sec = 0;
   for(i = 0; i < 2; i++) {
      XMLRPC_IS_NUMBER(text[i])
      tm.tm_sec += (text[i+15]-'0')*n;
      n /= 10;
   }

   tm.tm_year -= 1900;

   *value = mktime(&tm);

   return 0;

}

static int date_to_ISO8601 (time_t value, char *buf, int length) {
   struct tm *tm, tmbuf;
   tm = php_localtime_r(&value, &tmbuf);
   if (!tm) {
	   return 0;
   }
#if 0  /* TODO: soap seems to favor this method. xmlrpc the latter. */
	return strftime (buf, length, "%Y-%m-%dT%H:%M:%SZ", tm);
#else
   return strftime(buf, length, "%Y%m%dT%H:%M:%S", tm);
#endif
}

/*-*******************
* End Time Functions *
*********************/


/*-***************************
* Begin XMLRPC_REQUEST funcs *
*****************************/

/****f* REQUEST/XMLRPC_RequestNew
 * NAME
 *   XMLRPC_RequestNew
 * SYNOPSIS
 *   XMLRPC_REQUEST XMLRPC_RequestNew()
 * FUNCTION
 *   Creates a new XMLRPC_Request data struct
 * INPUTS
 *   none
 * SEE ALSO
 *   XMLRPC_RequestFree ()
 * SOURCE
 */
XMLRPC_REQUEST XMLRPC_RequestNew() {
   XMLRPC_REQUEST xRequest = calloc(1, sizeof(STRUCT_XMLRPC_REQUEST));
   if(xRequest) {
      simplestring_init(&xRequest->methodName);
   }
   return xRequest;
}

/*******/

/****f* REQUEST/XMLRPC_RequestFree
 * NAME
 *   XMLRPC_RequestFree
 * SYNOPSIS
 *   void XMLRPC_RequestFree(XMLRPC_REQUEST request, int bFreeIO)
 * FUNCTION
 *   Free XMLRPC Request and all sub-values
 * INPUTS
 *   request -- previously allocated request struct
 *   bFreeIO -- 1 = also free request value data, if any, 0 = ignore.
 * SEE ALSO
 *   XMLRPC_RequestNew ()
 *   XMLRPC_CleanupValue ()
 * SOURCE
 */
void XMLRPC_RequestFree(XMLRPC_REQUEST request, int bFreeIO) {
   if(request) {
      simplestring_free(&request->methodName);

      if(request->io && bFreeIO) {
         XMLRPC_CleanupValue(request->io);
      }
      if(request->error) {
         XMLRPC_CleanupValue(request->error);
      }
      my_free(request);
   }
}

/*******/

/* Set Method Name to call */
/****f* REQUEST/XMLRPC_RequestSetMethodName
 * NAME
 *   XMLRPC_RequestSetMethodName
 * SYNOPSIS
 *   const char* XMLRPC_RequestSetMethodName(XMLRPC_REQUEST request, const char* methodName)
 * FUNCTION
 *   Set name of method to call with this request.
 * INPUTS
 *   request -- previously allocated request struct
 *   methodName -- name of method
 * SEE ALSO
 *   XMLRPC_RequestNew ()
 *   XMLRPC_RequestGetMethodName ()
 *   XMLRPC_RequestFree ()
 * SOURCE
 */
const char* XMLRPC_RequestSetMethodName(XMLRPC_REQUEST request, const char* methodName) {
   if(request) {
      simplestring_clear(&request->methodName);
      simplestring_add(&request->methodName, methodName);
      return request->methodName.str;
   }
   return NULL;
}

/*******/

/****f* REQUEST/XMLRPC_RequestGetMethodName
 * NAME
 *   XMLRPC_RequestGetMethodName
 * SYNOPSIS
 *   const char* XMLRPC_RequestGetMethodName(XMLRPC_REQUEST request)
 * FUNCTION
 *   Get name of method called by this request
 * INPUTS
 *   request -- previously allocated request struct
 * SEE ALSO
 *   XMLRPC_RequestNew ()
 *   XMLRPC_RequestSetMethodName ()
 *   XMLRPC_RequestFree ()
 * SOURCE
 */
const char* XMLRPC_RequestGetMethodName(XMLRPC_REQUEST request) {
   return request ? request->methodName.str : NULL;
}

/*******/

/****f* REQUEST/XMLRPC_RequestSetRequestType
 * NAME
 *   XMLRPC_RequestSetRequestType
 * SYNOPSIS
 *   XMLRPC_REQUEST_TYPE XMLRPC_RequestSetRequestType(XMLRPC_REQUEST request, XMLRPC_REQUEST_TYPE type)
 * FUNCTION
 *   A request struct may be allocated by a caller or by xmlrpc
 *   in response to a request.  This allows setting the
 *   request type.
 * INPUTS
 *   request -- previously allocated request struct
 *   type    -- request type [xmlrpc_method_call | xmlrpc_method_response]
 * SEE ALSO
 *   XMLRPC_RequestNew ()
 *   XMLRPC_RequestGetRequestType ()
 *   XMLRPC_RequestFree ()
 *   XMLRPC_REQUEST_TYPE
 * SOURCE
 */
XMLRPC_REQUEST_TYPE XMLRPC_RequestSetRequestType (XMLRPC_REQUEST request,
																  XMLRPC_REQUEST_TYPE type) {
   if(request) {
      request->request_type = type;
      return request->request_type;
   }
   return xmlrpc_request_none;
}

/*******/

/****f* REQUEST/XMLRPC_RequestGetRequestType
 * NAME
 *   XMLRPC_RequestGetRequestType
 * SYNOPSIS
 *   XMLRPC_REQUEST_TYPE XMLRPC_RequestGetRequestType(XMLRPC_REQUEST request)
 * FUNCTION
 *   A request struct may be allocated by a caller or by xmlrpc
 *   in response to a request.  This allows setting the
 *   request type.
 * INPUTS
 *   request -- previously allocated request struct
 * RESULT
 *   type    -- request type [xmlrpc_method_call | xmlrpc_method_response]
 * SEE ALSO
 *   XMLRPC_RequestNew ()
 *   XMLRPC_RequestSetRequestType ()
 *   XMLRPC_RequestFree ()
 *   XMLRPC_REQUEST_TYPE
 * SOURCE
 */
XMLRPC_REQUEST_TYPE XMLRPC_RequestGetRequestType(XMLRPC_REQUEST request) {
   return request ? request->request_type : xmlrpc_request_none;
}

/*******/


/****f* REQUEST/XMLRPC_RequestSetData
 * NAME
 *   XMLRPC_RequestSetData
 * SYNOPSIS
 *   XMLRPC_VALUE XMLRPC_RequestSetData(XMLRPC_REQUEST request, XMLRPC_VALUE data)
 * FUNCTION
 *   Associates a block of xmlrpc data with the request.  The
 *   data is *not* copied.  A pointer is kept.  The caller
 *   should be careful not to doubly free the data value,
 *   which may optionally be free'd by XMLRPC_RequestFree().
 * INPUTS
 *   request -- previously allocated request struct
 *   data    -- previously allocated data struct
 * RESULT
 *   XMLRPC_VALUE -- pointer to value stored, or NULL
 * SEE ALSO
 *   XMLRPC_RequestNew ()
 *   XMLRPC_RequestGetData ()
 *   XMLRPC_RequestFree ()
 *   XMLRPC_REQUEST
 *   XMLRPC_VALUE
 * SOURCE
 */
XMLRPC_VALUE XMLRPC_RequestSetData(XMLRPC_REQUEST request, XMLRPC_VALUE data) {
   if(request && data) {
		if (request->io) {
			XMLRPC_CleanupValue (request->io);
		}
      request->io = XMLRPC_CopyValue(data);
      return request->io;
   }
   return NULL;
}

/*******/

/****f* REQUEST/XMLRPC_RequestGetData
 * NAME
 *   XMLRPC_RequestGetData
 * SYNOPSIS
 *   XMLRPC_VALUE XMLRPC_RequestGetData(XMLRPC_REQUEST request)
 * FUNCTION
 *   Returns data associated with request, if any.
 * INPUTS
 *   request -- previously allocated request struct
 * RESULT
 *   XMLRPC_VALUE -- pointer to value stored, or NULL
 * SEE ALSO
 *   XMLRPC_RequestNew ()
 *   XMLRPC_RequestSetData ()
 *   XMLRPC_RequestFree ()
 *   XMLRPC_REQUEST
 *   XMLRPC_VALUE
 * SOURCE
 */
XMLRPC_VALUE XMLRPC_RequestGetData(XMLRPC_REQUEST request) {
   return request ? request->io : NULL;
}

/*******/

/****f* REQUEST/XMLRPC_RequestSetError
 * NAME
 *   XMLRPC_RequestSetError
 * SYNOPSIS
 *   XMLRPC_VALUE XMLRPC_RequestSetError(XMLRPC_REQUEST request, XMLRPC_VALUE error)
 * FUNCTION
 *   Associates a block of xmlrpc data, representing an error
 *   condition, with the request. 
 * INPUTS
 *   request -- previously allocated request struct
 *   error   -- previously allocated error code or struct
 * RESULT
 *   XMLRPC_VALUE -- pointer to value stored, or NULL
 * NOTES
 *   This is a private function for usage by internals only.
 * SEE ALSO
 *   XMLRPC_RequestGetError ()
 * SOURCE
 */
XMLRPC_VALUE XMLRPC_RequestSetError (XMLRPC_REQUEST request, XMLRPC_VALUE error) {
	if (request && error) {
		if (request->error) {
			XMLRPC_CleanupValue (request->error);
		}
		request->error = XMLRPC_CopyValue (error);
		return request->error;
	}
	return NULL;
}

/*******/

/****f* REQUEST/XMLRPC_RequestGetError
 * NAME
 *   XMLRPC_RequestGetError
 * SYNOPSIS
 *   XMLRPC_VALUE XMLRPC_RequestGetError(XMLRPC_REQUEST request)
 * FUNCTION
 *   Returns error data associated with request, if any.
 * INPUTS
 *   request -- previously allocated request struct
 * RESULT
 *   XMLRPC_VALUE -- pointer to error value stored, or NULL
 * NOTES
 *   This is a private function for usage by internals only.
 * SEE ALSO
 *   XMLRPC_RequestSetError ()
 *   XMLRPC_RequestFree ()
 * SOURCE
 */
XMLRPC_VALUE XMLRPC_RequestGetError (XMLRPC_REQUEST request) {
	return request ? request->error : NULL;
}

/*******/


/****f* REQUEST/XMLRPC_RequestSetOutputOptions
 * NAME
 *   XMLRPC_RequestSetOutputOptions
 * SYNOPSIS
 *   XMLRPC_REQUEST_OUTPUT_OPTIONS XMLRPC_RequestSetOutputOptions(XMLRPC_REQUEST request, XMLRPC_REQUEST_OUTPUT_OPTIONS output)
 * FUNCTION
 *   Sets output options used for generating XML. The output struct
 *   is copied, and may be freed by the caller.
 * INPUTS
 *   request -- previously allocated request struct
 *   output  -- output options struct initialized by caller
 * RESULT
 *   XMLRPC_REQUEST_OUTPUT_OPTIONS -- pointer to value stored, or NULL
 * SEE ALSO
 *   XMLRPC_RequestNew ()
 *   XMLRPC_RequestGetOutputOptions ()
 *   XMLRPC_RequestFree ()
 *   XMLRPC_REQUEST
 *   XMLRPC_REQUEST_OUTPUT_OPTIONS
 * SOURCE
 */
XMLRPC_REQUEST_OUTPUT_OPTIONS XMLRPC_RequestSetOutputOptions(XMLRPC_REQUEST request, XMLRPC_REQUEST_OUTPUT_OPTIONS output) {
   if(request && output) {
		memcpy (&request->output, output,
				  sizeof (STRUCT_XMLRPC_REQUEST_OUTPUT_OPTIONS));
      return &request->output;
   }
   return NULL;
}

/*******/


/****f* REQUEST/XMLRPC_RequestGetOutputOptions
 * NAME
 *   XMLRPC_RequestGetOutputOptions
 * SYNOPSIS
 *   XMLRPC_REQUEST_OUTPUT_OPTIONS XMLRPC_RequestGetOutputOptions(XMLRPC_REQUEST request)
 * FUNCTION
 *   Gets a pointer to output options used for generating XML.
 * INPUTS
 *   request -- previously allocated request struct
 * RESULT
 *   XMLRPC_REQUEST_OUTPUT_OPTIONS -- pointer to options stored, or NULL
 * SEE ALSO
 *   XMLRPC_RequestNew ()
 *   XMLRPC_RequestSetOutputOptions ()
 *   XMLRPC_RequestFree ()
 *   XMLRPC_REQUEST
 *   XMLRPC_REQUEST_OUTPUT_OPTIONS
 * SOURCE
 */
XMLRPC_REQUEST_OUTPUT_OPTIONS XMLRPC_RequestGetOutputOptions(XMLRPC_REQUEST request) {
   return request ? &request->output : NULL;
}

/*******/

/*-*************************
* End XMLRPC_REQUEST funcs *
***************************/


/*-***************************
* Begin Serializiation funcs *
*****************************/

/****f* SERIALIZE/XMLRPC_VALUE_ToXML
 * NAME
 *   XMLRPC_VALUE_ToXML
 * SYNOPSIS
 *   char* XMLRPC_VALUE_ToXML(XMLRPC_VALUE val)
 * FUNCTION
 *   encode XMLRPC_VALUE into XML buffer.  Note that the generated
 *   buffer will not contain a methodCall.
 * INPUTS
 *   val -- previously allocated XMLRPC_VALUE
 *   buf_len -- length of returned buffer, if not null
 * RESULT
 *   char* -- newly allocated buffer containing XML. 
 *   It is the caller's responsibility to free it.
 * SEE ALSO
 *   XMLRPC_REQUEST_ToXML ()
 *   XMLRPC_VALUE_FromXML ()
 *   XMLRPC_Free ()
 *   XMLRPC_VALUE
 * SOURCE
 */
char* XMLRPC_VALUE_ToXML(XMLRPC_VALUE val, int* buf_len) {
   xml_element *root_elem = XMLRPC_VALUE_to_xml_element(val);
   char* pRet = NULL;

   if(root_elem) {
      pRet = xml_elem_serialize_to_string(root_elem, NULL, buf_len);
      xml_elem_free(root_elem);
   }
   return pRet;
}

/*******/

/****f* SERIALIZE/XMLRPC_REQUEST_ToXML
 * NAME
 *   XMLRPC_REQUEST_ToXML
 * SYNOPSIS
 *   char* XMLRPC_REQUEST_ToXML(XMLRPC_REQUEST request)
 * FUNCTION
 *   encode XMLRPC_REQUEST into XML buffer
 * INPUTS
 *   request -- previously allocated XMLRPC_REQUEST
 *   buf_len -- size of returned buf, if not null
 * RESULT
 *   char* -- newly allocated buffer containing XML. 
 *   It is the caller's responsibility to free it.
 * SEE ALSO
 *   XMLRPC_REQUEST_ToXML ()
 *   XMLRPC_REQUEST_FromXML ()
 *   XMLRPC_Free ()
 *   XMLRPC_VALUE_ToXML ()
 *   XMLRPC_REQUEST
 * SOURCE
 */
char* XMLRPC_REQUEST_ToXML(XMLRPC_REQUEST request, int* buf_len) {
      char* pRet = NULL;
	if (request) {
		xml_element *root_elem = NULL;
		if (request->output.version == xmlrpc_version_simple) {
			root_elem = DANDARPC_REQUEST_to_xml_element (request);
		}
		else if (request->output.version == xmlrpc_version_1_0 ||
					request->output.version == xmlrpc_version_none) {
			root_elem = XMLRPC_REQUEST_to_xml_element (request);
		}
		else if (request->output.version == xmlrpc_version_soap_1_1) {
			root_elem = SOAP_REQUEST_to_xml_element (request);
		}

      if(root_elem) {
			pRet =
			xml_elem_serialize_to_string (root_elem,
													&request->output.xml_elem_opts,
													buf_len);
         xml_elem_free(root_elem);
      }
   }
	return pRet;
}

/*******/

/****f* SERIALIZE/XMLRPC_VALUE_FromXML
 * NAME
 *   XMLRPC_VALUE_FromXML
 * SYNOPSIS
 *   XMLRPC_VALUE XMLRPC_VALUE_FromXML(const char* in_buf, int le
 * FUNCTION
 *   Retrieve XMLRPC_VALUE from XML buffer. Note that this will
 *   ignore any methodCall.  See XMLRPC_REQUEST_FromXML
 * INPUTS
 *   in_buf -- character buffer containing XML
 *   len    -- length of buffer
 * RESULT
 *   XMLRPC_VALUE -- newly allocated data, or NULL if error. Should
 *   be free'd by caller.
 * SEE ALSO
 *   XMLRPC_VALUE_ToXML ()
 *   XMLRPC_REQUEST_FromXML ()
 *   XMLRPC_VALUE
 * SOURCE
 */
XMLRPC_VALUE XMLRPC_VALUE_FromXML (const char *in_buf, int len, XMLRPC_REQUEST_INPUT_OPTIONS in_options) {
   XMLRPC_VALUE xResponse = NULL;
   XMLRPC_REQUEST req = XMLRPC_REQUEST_FromXML(in_buf, len, in_options);

   if(req) {
      xResponse = req->io;
      XMLRPC_RequestFree(req, 0);
   }
   return xResponse;
}

/*******/

/* map parser errors to standard xml-rpc errors */
static XMLRPC_VALUE map_expat_errors(XML_ELEM_ERROR error) {
   XMLRPC_VALUE xReturn = NULL;
   if(error) {
      XMLRPC_ERROR_CODE code;
      char buf[1024];
      snprintf(buf, sizeof(buf), 
               "error occurred at line %ld, column %ld, byte index %ld", 
					 error->line, error->column, error->byte_index);

      /* expat specific errors */
      switch(error->parser_code) {
      case XML_ERROR_UNKNOWN_ENCODING:
         code = xmlrpc_error_parse_unknown_encoding;
         break;
      case XML_ERROR_INCORRECT_ENCODING:
         code = xmlrpc_error_parse_bad_encoding;
         break;
      default:
         code = xmlrpc_error_parse_xml_syntax;
         break;
      }
      xReturn = XMLRPC_UtilityCreateFault(code, buf);
   }
   return xReturn;
}

/****f* SERIALIZE/XMLRPC_REQUEST_FromXML
 * NAME
 *   XMLRPC_REQUEST_FromXML
 * SYNOPSIS
 *   XMLRPC_REQUEST XMLRPC_REQUEST_FromXML(const char* in_buf, int le
 * FUNCTION
 *   Retrieve XMLRPC_REQUEST from XML buffer
 * INPUTS
 *   in_buf -- character buffer containing XML
 *   len    -- length of buffer
 * RESULT
 *   XMLRPC_REQUEST -- newly allocated data, or NULL if error. Should
 *   be free'd by caller.
 * SEE ALSO
 *   XMLRPC_REQUEST_ToXML ()
 *   XMLRPC_VALUE_FromXML ()
 *   XMLRPC_REQUEST
 * SOURCE
 */
XMLRPC_REQUEST XMLRPC_REQUEST_FromXML (const char *in_buf, int len, 
													XMLRPC_REQUEST_INPUT_OPTIONS in_options) {
   XMLRPC_REQUEST request = XMLRPC_RequestNew();
   STRUCT_XML_ELEM_ERROR error = {0};

   if(request) {
		xml_element *root_elem =
		xml_elem_parse_buf (in_buf, len,
								  (in_options ? &in_options->xml_elem_opts : NULL),
								  &error);

      if(root_elem) {
         if(!strcmp(root_elem->name, "simpleRPC")) {
            request->output.version = xmlrpc_version_simple;
            xml_element_to_DANDARPC_REQUEST(request, root_elem);
         }
			else if (!strcmp (root_elem->name, "SOAP-ENV:Envelope")) {
				request->output.version = xmlrpc_version_soap_1_1;
				xml_element_to_SOAP_REQUEST (request, root_elem);
			}
         else {
            request->output.version = xmlrpc_version_1_0;
            xml_element_to_XMLRPC_REQUEST(request, root_elem);
         }
         xml_elem_free(root_elem);
      }
      else {
         if(error.parser_error) {
				XMLRPC_RequestSetError (request, map_expat_errors (&error));
         }
      }
   }

   return request;
}

/*******/

/*-************************
* End Serialization Funcs *
**************************/



/****f* VALUE/XMLRPC_CreateValueEmpty
 * NAME
 *   XMLRPC_CreateValueEmpty
 * SYNOPSIS
 *   XMLRPC_VALUE XMLRPC_CreateValueEmpty ()
 * FUNCTION
 *   Create an XML value to be used/modified elsewhere.
 * INPUTS
 * RESULT
 *   XMLRPC_VALUE.  The new value, or NULL on failure.
 * SEE ALSO
 *   XMLRPC_CleanupValue ()
 *   XMLRPC_VALUE
 * SOURCE
 */
XMLRPC_VALUE XMLRPC_CreateValueEmpty() {
   XMLRPC_VALUE v = calloc(1, sizeof(STRUCT_XMLRPC_VALUE));
   if(v) {
#ifdef XMLRPC_DEBUG_REFCOUNT
		printf ("calloc'd 0x%x\n", v);
#endif
      v->type = xmlrpc_empty;
      simplestring_init(&v->id);
      simplestring_init(&v->str);
   }
   return v;
}

/*******/

/****f* VALUE/XMLRPC_SetValueID_Case
 * NAME
 *   XMLRPC_SetValueID_Case
 * SYNOPSIS
 *   const char *XMLRPC_SetValueID_Case(XMLRPC_VALUE value, const char* id, int len, XMLRPC_CASE id_case)
 * FUNCTION
 *   Assign an ID (key) to an XMLRPC value.
 * INPUTS
 *   value     The xml value who's ID we will set.
 *   id        The desired new id.
 *   len       length of id string if known, or 0 if unknown.
 *   id_case   one of XMLRPC_CASE
 * RESULT
 *   const char*  pointer to the newly allocated id string, or NULL
 * SEE ALSO
 *   XMLRPC_SetValueID ()
 *   XMLRPC_GetValueID ()
 *   XMLRPC_VALUE
 *   XMLRPC_CASE
 * SOURCE
 */
const char *XMLRPC_SetValueID_Case(XMLRPC_VALUE value, const char* id, int len, XMLRPC_CASE id_case) {
   const char* pRetval = NULL;
   if(value) {
      if(id) {
         simplestring_clear(&value->id);
         (len > 0) ? simplestring_addn(&value->id, id, len) :
                     simplestring_add(&value->id, id);

         /* upper or lower case string in place if required. could be a seperate func. */
         if(id_case == xmlrpc_case_lower || id_case == xmlrpc_case_upper) {
            int i;
            for(i = 0; i < value->id.len; i++) {
					value->id.str[i] =
					(id_case ==
					 xmlrpc_case_lower) ? tolower (value->id.
															 str[i]) : toupper (value->
																					  id.
																					  str[i]);
            }
         }

         pRetval = value->id.str;

#ifdef XMLRPC_DEBUG_REFCOUNT
         printf("set value id: %s\n", pRetval);
#endif 
      }
   }

   return pRetval;
}

/*******/


/****f* VALUE/XMLRPC_SetValueString
 * NAME
 *   XMLRPC_SetValueString
 * SYNOPSIS
 *   const char *XMLRPC_SetValueString(XMLRPC_VALUE value, const char* val, int len)
 * FUNCTION
 *   Assign a string value to an XMLRPC_VALUE, and set it to type xmlrpc_string
 * INPUTS
 *   value     The xml value who's ID we will set.
 *   val        The desired new string val.
 *   len       length of val string if known, or 0 if unknown.
 * RESULT
 *   const char*  pointer to the newly allocated value string, or NULL
 * SEE ALSO
 *   XMLRPC_GetValueString ()
 *   XMLRPC_VALUE
 *   XMLRPC_VALUE_TYPE
 * SOURCE
 */
const char *XMLRPC_SetValueString(XMLRPC_VALUE value, const char* val, int len) {
   char *pRetval = NULL;
   if(value && val) {
      simplestring_clear(&value->str);
      (len > 0) ? simplestring_addn(&value->str, val, len) :
                  simplestring_add(&value->str, val);
      value->type = xmlrpc_string;
      pRetval = (char *)value->str.str;
   }

   return pRetval;
}

/*******/

/****f* VALUE/XMLRPC_SetValueInt
 * NAME
 *   XMLRPC_SetValueInt
 * SYNOPSIS
 *   void XMLRPC_SetValueInt(XMLRPC_VALUE value, int val)
 * FUNCTION
 *   Assign an int value to an XMLRPC_VALUE, and set it to type xmlrpc_int
 * INPUTS
 *   value     The xml value who's ID we will set.
 *   val        The desired new integer value
 * RESULT
 * SEE ALSO
 *   XMLRPC_GetValueInt ()
 *   XMLRPC_VALUE
 *   XMLRPC_VALUE_TYPE
 * SOURCE
 */
void XMLRPC_SetValueInt(XMLRPC_VALUE value, int val) {
   if(value) {
      value->type = xmlrpc_int;
      value->i = val;
   }
}

/*******/

/****f* VALUE/XMLRPC_SetValueBoolean
 * NAME
 *   XMLRPC_SetValueBoolean
 * SYNOPSIS
 *   void XMLRPC_SetValueBoolean(XMLRPC_VALUE value, int val)
 * FUNCTION
 *   Assign a boolean value to an XMLRPC_VALUE, and set it to type xmlrpc_boolean
 * INPUTS
 *   value     The xml value who's value we will set.
 *   val        The desired new boolean value. [0 | 1]
 * RESULT
 * SEE ALSO
 *   XMLRPC_GetValueBoolean ()
 *   XMLRPC_VALUE
 *   XMLRPC_VALUE_TYPE
 * SOURCE
 */
void XMLRPC_SetValueBoolean(XMLRPC_VALUE value, int val) {
   if(value) {
      value->type = xmlrpc_boolean;
      value->i = val ? 1 : 0;
   }
}

/*******/


/****f* VECTOR/XMLRPC_SetIsVector
 * NAME
 *   XMLRPC_SetIsVector
 * SYNOPSIS
 *   int XMLRPC_SetIsVector(XMLRPC_VALUE value, XMLRPC_VECTOR_TYPE type)
 * FUNCTION
 *   Set the XMLRPC_VALUE to be a vector (list) type.  The vector may be one of
 *   [xmlrpc_array | xmlrpc_struct | xmlrpc_mixed].  An array has only index values.
 *   A struct has key/val pairs.  Mixed allows both index and key/val combinations. 
 * INPUTS
 *   value     The xml value who's vector type we will set
 *   type      New type of vector as enumerated by XMLRPC_VECTOR_TYPE
 * RESULT
 *   int       1 if successful, 0 otherwise
 * SEE ALSO
 *   XMLRPC_GetValueType ()
 *   XMLRPC_GetVectorType ()
 *   XMLRPC_VALUE
 *   XMLRPC_VECTOR_TYPE
 *   XMLRPC_VALUE_TYPE
 * SOURCE
 */
int XMLRPC_SetIsVector(XMLRPC_VALUE value, XMLRPC_VECTOR_TYPE type) {
   int bSuccess = 0;

	if (value) {
		/* we can change the type so long as nothing is currently stored. */
		if(value->type == xmlrpc_vector) {
			if(value->v) {
				if(!Q_Size(value->v->q)) {
					value->v->type = type;
				}
			}
		}
		else {
      value->v = calloc(1, sizeof(STRUCT_XMLRPC_VECTOR));
      if(value->v) {
         value->v->q = (queue*)malloc(sizeof(queue));
         if(value->v->q) {
            Q_Init(value->v->q);
            value->v->type = type;
            value->type = xmlrpc_vector;
            bSuccess = 1;
         }
      }
   }
	}

   return bSuccess;
}

/*******/

/****f* VECTOR/XMLRPC_CreateVector
 * NAME
 *   XMLRPC_CreateVector
 * SYNOPSIS
 *   XMLRPC_VALUE XMLRPC_CreateVector(const char* id, XMLRPC_VECTOR_TYPE type)
 * FUNCTION
 *   Create a new vector and optionally set an id.
 * INPUTS
 *   id        The id of the vector, or NULL
 *   type      New type of vector as enumerated by XMLRPC_VECTOR_TYPE
 * RESULT
 *   XMLRPC_VALUE  The new vector, or NULL on failure.
 * SEE ALSO
 *   XMLRPC_CreateValueEmpty ()
 *   XMLRPC_SetIsVector ()
 *   XMLRPC_GetValueType ()
 *   XMLRPC_GetVectorType ()
 *   XMLRPC_VALUE
 *   XMLRPC_VECTOR_TYPE
 *   XMLRPC_VALUE_TYPE
 * SOURCE
 */
XMLRPC_VALUE XMLRPC_CreateVector(const char* id, XMLRPC_VECTOR_TYPE type) {
   XMLRPC_VALUE val = NULL;

   val = XMLRPC_CreateValueEmpty();
   if(val) {
      if(XMLRPC_SetIsVector(val, type)) {
         if(id) {
            const char *pSVI = NULL;

            pSVI = XMLRPC_SetValueID(val, id, 0);
            if(NULL == pSVI) {
               val = NULL;
            }
         }
      }
      else {
         val = NULL;
      }
   }
   return val;
}

/*******/


/* Not yet implemented.
 *
 * This should use a hash to determine if a given target id has already
 * been appended.  
 *
 * Alternately, it could walk the entire vector, but that could be quite
 * slow for very large lists.
 */
static int isDuplicateEntry(XMLRPC_VALUE target, XMLRPC_VALUE source) {
   return 0;
}

/****f* VECTOR/XMLRPC_AddValueToVector
 * NAME
 *   XMLRPC_AddValueToVector
 * SYNOPSIS
 *   int XMLRPC_AddValueToVector(XMLRPC_VALUE target, XMLRPC_VALUE source)
 * FUNCTION
 *   Add (append) an existing XMLRPC_VALUE to a vector.
 * INPUTS
 *   target    The target vector
 *   source    The source value to append
 * RESULT
 *   int       1 if successful, else 0
 * SEE ALSO
 *   XMLRPC_AddValuesToVector ()
 *   XMLRPC_VectorGetValueWithID_Case ()
 *   XMLRPC_VALUE
 * NOTES
 *   The function will fail and return 0 if an attempt is made to add
 *   a value with an ID into a vector of type xmlrpc_vector_array. Such
 *   values can only be added to xmlrpc_vector_struct.
 * SOURCE
 */
int XMLRPC_AddValueToVector(XMLRPC_VALUE target, XMLRPC_VALUE source) {
   if(target && source) {
      if(target->type == xmlrpc_vector && target->v && 
         target->v->q && target->v->type != xmlrpc_vector_none) {

         /* guard against putting value of unknown type into vector */
         switch(source->type) {
            case xmlrpc_empty:
            case xmlrpc_base64:
            case xmlrpc_boolean:
            case xmlrpc_datetime:
            case xmlrpc_double:
            case xmlrpc_int:
            case xmlrpc_string:
            case xmlrpc_vector:
               /* Guard against putting a key/val pair into an array vector */
               if( !(source->id.len && target->v->type == xmlrpc_vector_array) ) {
					if (isDuplicateEntry (target, source)
						 || Q_PushTail (target->v->q, XMLRPC_CopyValue (source))) {
                     return 1;
                  }
               }
               else {
					fprintf (stderr,
								"xmlrpc: attempted to add key/val pair to vector of type array\n");
               }
               break;
            default:
				fprintf (stderr,
							"xmlrpc: attempted to add value of unknown type to vector\n");
               break;
         }
      }
   }
   return 0;
}

/*******/


/****f* VECTOR/XMLRPC_AddValuesToVector
 * NAME
 *   XMLRPC_AddValuesToVector
 * SYNOPSIS
 *   XMLRPC_AddValuesToVector ( target, val1, val2, val3, val(n), 0 )
 *   XMLRPC_AddValuesToVector( XMLRPC_VALUE, ... )
 * FUNCTION
 *   Add (append) a series of existing XMLRPC_VALUE to a vector.
 * INPUTS
 *   target    The target vector
 *   ...       The source value(s) to append.  The last item *must* be 0.
 * RESULT
 *   int       1 if successful, else 0
 * SEE ALSO
 *   XMLRPC_AddValuesToVector ()
 *   XMLRPC_VectorGetValueWithID_Case ()
 *   XMLRPC_VALUE
 * NOTES
 *   This function may actually return failure after it has already modified
 *     or added items to target.  You can not trust the state of target
 *     if this function returns failure.
 * SOURCE
 */
int XMLRPC_AddValuesToVector(XMLRPC_VALUE target, ...) {
   int iRetval = 0;

   if(target) {
      if(target->type == xmlrpc_vector) {
         XMLRPC_VALUE v = NULL;
         va_list vl;

         va_start(vl, target);

         do {
            v = va_arg(vl, XMLRPC_VALUE);
            if(v) {
               if(!XMLRPC_AddValueToVector(target, v)) {
                  iRetval = 0;
                  break;
               }
            }
			}
			while (v);

         va_end(vl);

         if(NULL == v) {
            iRetval = 1;
         }
      }
   }
   return iRetval;
}

/*******/


/****f* VECTOR/XMLRPC_VectorGetValueWithID_Case
 * NAME
 *   XMLRPC_VectorGetValueWithID_Case
 * SYNOPSIS
 *   XMLRPC_VALUE XMLRPC_VectorGetValueWithID_Case(XMLRPC_VALUE vector, const char* id, XMLRPC_CASE_COMPARISON id_case)
 * FUNCTION
 *   Get value from vector matching id (key)
 * INPUTS
 *   vector    The source vector
 *   id        The key to find
 *   id_case   Rule for how to match key
 * RESULT
 *   int       1 if successful, else 0
 * SEE ALSO
 *   XMLRPC_SetValueID_Case ()
 *   XMLRPC_VALUE
 *   XMLRPC_CASE_COMPARISON
 * SOURCE
 */
XMLRPC_VALUE XMLRPC_VectorGetValueWithID_Case (XMLRPC_VALUE vector, const char *id,
															  XMLRPC_CASE_COMPARISON id_case) {
   if(vector && vector->v && vector->v->q) {
       q_iter qi = Q_Iter_Head_F(vector->v->q);

       while(qi) {
          XMLRPC_VALUE xIter = Q_Iter_Get_F(qi);
          if(xIter && xIter->id.str) {
             if(id_case == xmlrpc_case_sensitive) {
                if(!strcmp(xIter->id.str, id)) {
                   return xIter;
                }
             }
             else if(id_case == xmlrpc_case_insensitive) {
                if(!strcasecmp(xIter->id.str, id)) {
                   return xIter;
                }
             }
          }
          qi = Q_Iter_Next_F(qi);
       }
   }
   return NULL;
}

/*******/


int XMLRPC_VectorRemoveValue(XMLRPC_VALUE vector, XMLRPC_VALUE value) {
   if(vector && vector->v && vector->v->q && value) {
       q_iter qi = Q_Iter_Head_F(vector->v->q);

       while(qi) {
          XMLRPC_VALUE xIter = Q_Iter_Get_F(qi);
          if(xIter == value) {
             XMLRPC_CleanupValue(xIter);
             Q_Iter_Del(vector->v->q, qi);
             return 1;
          }
          qi = Q_Iter_Next_F(qi);
       }
   }
   return 0;
}


/****f* VALUE/XMLRPC_CreateValueString
 * NAME
 *   XMLRPC_CreateValueString
 * SYNOPSIS
 *   XMLRPC_VALUE XMLRPC_CreateValueString(const char* id, const char* val, int len)
 * FUNCTION
 *   Create an XMLRPC_VALUE, and assign a string to it
 * INPUTS
 *   id        The id of the value, or NULL
 *   val       The desired new string val.
 *   len       length of val string if known, or 0 if unknown.
 * RESULT
 *   newly allocated XMLRPC_VALUE, or NULL
 * SEE ALSO
 *   XMLRPC_GetValueString ()
 *   XMLRPC_CreateValueEmpty ()
 *   XMLRPC_VALUE
 *   XMLRPC_VALUE_TYPE
 * SOURCE
 */
XMLRPC_VALUE XMLRPC_CreateValueString(const char* id, const char* val, int len) {
   XMLRPC_VALUE value = NULL;
   if(val) {
      value = XMLRPC_CreateValueEmpty();
      if(value) {
         XMLRPC_SetValueString(value, val, len);
         if(id) {
            XMLRPC_SetValueID(value, id, 0);
         }
      }
   }
   return value;
}

/*******/

/****f* VALUE/XMLRPC_CreateValueInt
 * NAME
 *   XMLRPC_CreateValueInt
 * SYNOPSIS
 *   XMLRPC_VALUE XMLRPC_CreateValueInt(const char* id, int i)
 * FUNCTION
 *   Create an XMLRPC_VALUE, and assign an int to it
 * INPUTS
 *   id        The id of the value, or NULL
 *   i         The desired new int val.
 * RESULT
 *   newly allocated XMLRPC_VALUE, or NULL
 * SEE ALSO
 *   XMLRPC_GetValueInt ()
 *   XMLRPC_CreateValueEmpty ()
 *   XMLRPC_VALUE
 *   XMLRPC_VALUE_TYPE
 * SOURCE
 */
XMLRPC_VALUE XMLRPC_CreateValueInt(const char* id, int i) {
   XMLRPC_VALUE val = XMLRPC_CreateValueEmpty();
   if(val) {
      XMLRPC_SetValueInt(val, i);
      if(id) {
         XMLRPC_SetValueID(val, id, 0);
      }
   }
   return val;
}

/*******/

/****f* VALUE/XMLRPC_CreateValueBoolean
 * NAME
 *   XMLRPC_CreateValueBoolean
 * SYNOPSIS
 *   XMLRPC_VALUE XMLRPC_CreateValueBoolean(const char* id, int i)
 * FUNCTION
 *   Create an XMLRPC_VALUE, and assign an int to it
 * INPUTS
 *   id        The id of the value, or NULL
 *   i         The desired new int val.
 * RESULT
 *   newly allocated XMLRPC_VALUE, or NULL
 * SEE ALSO
 *   XMLRPC_GetValueBoolean ()
 *   XMLRPC_CreateValueEmpty ()
 *   XMLRPC_VALUE
 *   XMLRPC_VALUE_TYPE
 * SOURCE
 */
XMLRPC_VALUE XMLRPC_CreateValueBoolean(const char* id, int i) {
   XMLRPC_VALUE val = XMLRPC_CreateValueEmpty();
   if(val) {
      XMLRPC_SetValueBoolean(val, i);
      if(id) {
         XMLRPC_SetValueID(val, id, 0);
      }
   }
   return val;
}

/*******/


/****f* VALUE/XMLRPC_CleanupValue
 * NAME
 *   XMLRPC_CleanupValue
 * SYNOPSIS
 *   void XMLRPC_CleanupValue(XMLRPC_VALUE value)
 * FUNCTION
 *   Frees all memory allocated for an XMLRPC_VALUE and any of its children (if a vector)
 * INPUTS
 *   value     The id of the value to be cleaned up.
 * RESULT
 *   void
 * NOTES
 *   Normally this function will be called for the topmost vector, thus free-ing
 *   all children.  If a child of a vector is free'd first, results are undefined.
 *   Failure to call this function *will* cause memory leaks.
 *
 *   Also, this function is implemented using reference counting.  Thus a value
 *   may be added and freed from multiple parents so long as a reference is added
 *   first using XMLRPC_CopyValue()
 * SEE ALSO
 *   XMLRPC_RequestFree ()
 *   XMLRPC_CreateValueEmpty ()
 *   XMLRPC_CopyValue()
 *   XMLRPC_VALUE
 * SOURCE
 */
void XMLRPC_CleanupValue(XMLRPC_VALUE value) {
   if(value) {
      if(value->iRefCount > 0) {
         value->iRefCount --;
      }

#ifdef XMLRPC_DEBUG_REFCOUNT
      if(value->id.str) {
			printf ("decremented refcount of %s, now %i\n", value->id.str,
					  value->iRefCount);
      }
      else {
			printf ("decremented refcount of 0x%x, now %i\n", value,
					  value->iRefCount);
      }
#endif

      if(value->type == xmlrpc_vector) {
         if(value->v) {
            if(value->iRefCount == 0) {
               XMLRPC_VALUE cur = (XMLRPC_VALUE)Q_Head(value->v->q);
               while( cur ) {
                  XMLRPC_CleanupValue(cur);
   
                  /* Make sure some idiot didn't include a vector as a child of itself
                   * and thus it would have already free'd these.
                   */
                  if(value->v && value->v->q) {
                     cur = Q_Next(value->v->q);
                  }
                  else {
                     break;
                  }
               }

               Q_Destroy(value->v->q);
               my_free(value->v->q);
               my_free(value->v);
            }
         }
      }


      if(value->iRefCount == 0) {

         /* guard against freeing invalid types */
         switch(value->type) {
            case xmlrpc_empty:
            case xmlrpc_base64:
            case xmlrpc_boolean:
            case xmlrpc_datetime:
            case xmlrpc_double:
            case xmlrpc_int:
            case xmlrpc_string:
            case xmlrpc_vector:
#ifdef XMLRPC_DEBUG_REFCOUNT
               if(value->id.str) {
                  printf("free'd %s\n", value->id.str);
               }
               else {
                  printf("free'd 0x%x\n", value);
               }
#endif 
               simplestring_free(&value->id);
               simplestring_free(&value->str);

               memset(value, 0, sizeof(STRUCT_XMLRPC_VALUE));
               my_free(value);
               break;
            default:
				fprintf (stderr,
							"xmlrpc: attempted to free value of invalid type\n");
               break;
         }
      }
   }
}

/*******/


/****f* VALUE/XMLRPC_SetValueDateTime
 * NAME
 *   XMLRPC_SetValueDateTime
 * SYNOPSIS
 *   void XMLRPC_SetValueDateTime(XMLRPC_VALUE value, time_t time)
 * FUNCTION
 *   Assign time value to XMLRPC_VALUE
 * INPUTS
 *   value     The target XMLRPC_VALUE
 *   time      The desired new unix time value (time_t)
 * RESULT
 *   void
 * SEE ALSO
 *   XMLRPC_GetValueDateTime ()
 *   XMLRPC_SetValueDateTime_ISO8601 ()
 *   XMLRPC_CreateValueDateTime ()
 *   XMLRPC_VALUE
 * SOURCE
 */
void XMLRPC_SetValueDateTime(XMLRPC_VALUE value, time_t time) {
   if(value) {
      char timeBuf[30];
      value->type = xmlrpc_datetime;
      value->i = time;

      timeBuf[0] = 0;

      date_to_ISO8601(time, timeBuf, sizeof(timeBuf));

      if(timeBuf[0]) {
         simplestring_clear(&value->str);
         simplestring_add(&value->str, timeBuf);
      }
   }
}

/*******/

/****f* VALUE/XMLRPC_CopyValue
 * NAME
 *   XMLRPC_CopyValue
 * SYNOPSIS
 *   XMLRPC_VALUE XMLRPC_CopyValue(XMLRPC_VALUE value)
 * FUNCTION
 *   Make a copy (reference) of an XMLRPC_VALUE
 * INPUTS
 *   value     The target XMLRPC_VALUE
 * RESULT
 *   XMLRPC_VALUE -- address of the copy
 * SEE ALSO
 *   XMLRPC_CleanupValue ()
 *   XMLRPC_DupValueNew ()
 * NOTES
 *   This function is implemented via reference counting, so the
 *   returned value is going to be the same as the passed in value.
 *   The value must be freed the same number of times it is copied
 *   or there will be a memory leak.
 * SOURCE
 */
XMLRPC_VALUE XMLRPC_CopyValue(XMLRPC_VALUE value) {
   if(value) {
      value->iRefCount ++;
#ifdef XMLRPC_DEBUG_REFCOUNT
      if(value->id.str) {
			printf ("incremented refcount of %s, now %i\n", value->id.str,
					  value->iRefCount);
		}
		else {
			printf ("incremented refcount of 0x%x, now %i\n", value,
					  value->iRefCount);
      }
#endif
   }
   return value;
}

/*******/


/****f* VALUE/XMLRPC_DupValueNew
 * NAME
 *   XMLRPC_DupValueNew
 * SYNOPSIS
 *   XMLRPC_VALUE XMLRPC_DupValueNew(XMLRPC_VALUE value)
 * FUNCTION
 *   Make a duplicate (non reference) of an XMLRPC_VALUE with newly allocated mem.
 * INPUTS
 *   value     The source XMLRPC_VALUE to duplicate
 * RESULT
 *   XMLRPC_VALUE -- address of the duplicate value
 * SEE ALSO
 *   XMLRPC_CleanupValue ()
 *   XMLRPC_CopyValue ()
 * NOTES
 *   Use this when function when you need to modify the contents of
 *   the copied value seperately from the original.
 *   
 *   this function is recursive, thus the value and all of its children
 *   (if any) will be duplicated.
 * SOURCE
 */
XMLRPC_VALUE XMLRPC_DupValueNew (XMLRPC_VALUE xSource) {
	XMLRPC_VALUE xReturn = NULL;
	if (xSource) {
		xReturn = XMLRPC_CreateValueEmpty ();
		if (xSource->id.len) {
			XMLRPC_SetValueID (xReturn, xSource->id.str, xSource->id.len);
		}

		switch (xSource->type) {
		case xmlrpc_int:
		case xmlrpc_boolean:
			XMLRPC_SetValueInt (xReturn, xSource->i);
			break;
		case xmlrpc_string:
		case xmlrpc_base64:
			XMLRPC_SetValueString (xReturn, xSource->str.str, xSource->str.len);
			break;
		case xmlrpc_datetime:
			XMLRPC_SetValueDateTime (xReturn, xSource->i);
			break;
		case xmlrpc_double:
			XMLRPC_SetValueDouble (xReturn, xSource->d);
			break;
		case xmlrpc_vector:
			{
				q_iter qi = Q_Iter_Head_F (xSource->v->q);
				XMLRPC_SetIsVector (xReturn, xSource->v->type);

				while (qi) {
					XMLRPC_VALUE xIter = Q_Iter_Get_F (qi);
					XMLRPC_AddValueToVector (xReturn, XMLRPC_DupValueNew (xIter));
					qi = Q_Iter_Next_F (qi);
				}
			}
			break;
		default:
			break;
		}
	}
	return xReturn;
}

/*******/



/****f* VALUE/XMLRPC_CreateValueDateTime
 * NAME
 *   XMLRPC_CreateValueDateTime
 * SYNOPSIS
 *   XMLRPC_VALUE XMLRPC_CreateValueDateTime(const char* id, time_t time)
 * FUNCTION
 *   Create new datetime value from time_t
 * INPUTS
 *   id        id of the new value, or NULL
 *   time      The desired unix time value (time_t)
 * RESULT
 *   void
 * SEE ALSO
 *   XMLRPC_GetValueDateTime ()
 *   XMLRPC_SetValueDateTime ()
 *   XMLRPC_CreateValueDateTime_ISO8601 ()
 *   XMLRPC_VALUE
 * SOURCE
 */
XMLRPC_VALUE XMLRPC_CreateValueDateTime(const char* id, time_t time) {
   XMLRPC_VALUE val = XMLRPC_CreateValueEmpty();
   if(val) {
      XMLRPC_SetValueDateTime(val, time);
      if(id) {
         XMLRPC_SetValueID(val, id, 0);
      }
   }
   return val;
}

/*******/


/****f* VALUE/XMLRPC_SetValueDateTime_ISO8601
 * NAME
 *   XMLRPC_SetValueDateTime_ISO8601
 * SYNOPSIS
 *   void XMLRPC_SetValueDateTime_ISO8601(XMLRPC_VALUE value, const char* s)
 * FUNCTION
 *   Set datetime value from IS08601 encoded string
 * INPUTS
 *   value     The target XMLRPC_VALUE
 *   s         The desired new time value
 * RESULT
 *   void                                
 * BUGS
 *   This function currently attempts to convert the time string to a valid unix time
 *   value before passing it. Behavior when the string is invalid or out of range
 *   is not well defined, but will probably result in Jan 1, 1970 (0) being passed.
 * SEE ALSO
 *   XMLRPC_GetValueDateTime_ISO8601 ()
 *   XMLRPC_CreateValueDateTime_ISO8601 ()
 *   XMLRPC_CreateValueDateTime ()
 *   XMLRPC_VALUE
 * SOURCE
 */
void XMLRPC_SetValueDateTime_ISO8601(XMLRPC_VALUE value, const char* s) {
   if(value) {
      time_t time_val = 0;
      if(s) {
         date_from_ISO8601(s, &time_val);
         XMLRPC_SetValueDateTime(value, time_val);
      }
   }
}

/*******/

/****f* VALUE/XMLRPC_CreateValueDateTime_ISO8601
 * NAME
 *   XMLRPC_CreateValueDateTime_ISO8601
 * SYNOPSIS
 *   XMLRPC_VALUE XMLRPC_CreateValueDateTime_ISO8601(const char* id, const char *s)
 * FUNCTION
 *   Create datetime value from IS08601 encoded string
 * INPUTS
 *   id        The id of the new value, or NULL
 *   s         The desired new time value
 * RESULT
 *   newly allocated XMLRPC_VALUE, or NULL if no value created.                                
 * BUGS
 *   See XMLRPC_SetValueDateTime_ISO8601 ()
 * SEE ALSO
 *   XMLRPC_GetValueDateTime_ISO8601 ()
 *   XMLRPC_SetValueDateTime_ISO8601 ()
 *   XMLRPC_CreateValueDateTime ()
 *   XMLRPC_VALUE
 * SOURCE
 */
XMLRPC_VALUE XMLRPC_CreateValueDateTime_ISO8601(const char* id, const char *s) {
   XMLRPC_VALUE val = XMLRPC_CreateValueEmpty();
   if(val) {
      XMLRPC_SetValueDateTime_ISO8601(val, s);
      if(id) {
         XMLRPC_SetValueID(val, id, 0);
      }
   }
   return val;
}

/*******/


/****f* VALUE/XMLRPC_SetValueBase64
 * NAME
 *   XMLRPC_SetValueBase64
 * SYNOPSIS
 *   void XMLRPC_SetValueBase64(XMLRPC_VALUE value, const char* s, int len)
 * FUNCTION
 *   Set base64 value.  Base64 is useful for transferring binary data, such as an image.
 * INPUTS
 *   value     The target XMLRPC_VALUE
 *   s         The desired new binary value
 *   len       The length of s, or NULL. If buffer is not null terminated, len *must* be passed.
 * RESULT
 *   void                                
 * NOTES
 *   Data is set/stored/retrieved as passed in, but is base64 encoded for XML transfer, and
 *   decoded on the other side.  This is transparent to the caller.
 * SEE ALSO
 *   XMLRPC_GetValueBase64 ()
 *   XMLRPC_CreateValueBase64 ()
 *   XMLRPC_VALUE
 * SOURCE
 */
void XMLRPC_SetValueBase64(XMLRPC_VALUE value, const char* s, int len) {
   if(value && s) {
      simplestring_clear(&value->str);
      (len > 0) ? simplestring_addn(&value->str, s, len) :
                  simplestring_add(&value->str, s);
      value->type = xmlrpc_base64;
   }
}

/*******/


/****f* VALUE/XMLRPC_CreateValueBase64
 * NAME
 *   XMLRPC_CreateValueBase64
 * SYNOPSIS
 *   XMLRPC_VALUE XMLRPC_CreateValueBase64(const char* id, const char* s, int len)
 * FUNCTION
 *   Create base64 value.  Base64 is useful for transferring binary data, such as an image.
 * INPUTS
 *   id        id of the new value, or NULL
 *   s         The desired new binary value
 *   len       The length of s, or NULL. If buffer is not null terminated, len *must* be passed.
 * RESULT
 *   newly allocated XMLRPC_VALUE, or NULL if error
 * NOTES
 *   See XMLRPC_SetValueBase64 ()
 * SEE ALSO
 *   XMLRPC_GetValueBase64 ()
 *   XMLRPC_SetValueBase64 ()
 *   XMLRPC_VALUE
 * SOURCE
 */
XMLRPC_VALUE XMLRPC_CreateValueBase64(const char* id, const char* s, int len) {
   XMLRPC_VALUE val = XMLRPC_CreateValueEmpty();
   if(val) {
      XMLRPC_SetValueBase64(val, s, len);
      if(id) {
         XMLRPC_SetValueID(val, id, 0);
      }
   }
   return val;
}

/*******/

/****f* VALUE/XMLRPC_SetValueDouble
 * NAME
 *   XMLRPC_SetValueDouble
 * SYNOPSIS
 *   void XMLRPC_SetValueDouble(XMLRPC_VALUE value, double val)
 * FUNCTION
 *   Set double (floating point) value.
 * INPUTS
 *   value     The target XMLRPC_VALUE
 *   val       The desired new double value
 * RESULT
 *   void                                
 * SEE ALSO
 *   XMLRPC_GetValueDouble ()
 *   XMLRPC_CreateValueDouble ()
 *   XMLRPC_VALUE
 * SOURCE
 */
void XMLRPC_SetValueDouble(XMLRPC_VALUE value, double val) {
   if(value) {
      value->type = xmlrpc_double;
      value->d = val;
   }
}

/*******/

/****f* VALUE/XMLRPC_CreateValueDouble
 * NAME
 *   XMLRPC_CreateValueDouble
 * SYNOPSIS
 *   XMLRPC_VALUE XMLRPC_CreateValueDouble(const char* id, double d)
 * FUNCTION
 *   Create double (floating point) value.
 * INPUTS
 *   id        id of the newly created value, or NULL
 *   d         The desired new double value
 * RESULT
 *   void                                
 * SEE ALSO
 *   XMLRPC_GetValueDouble ()
 *   XMLRPC_CreateValueDouble ()
 *   XMLRPC_VALUE
 * SOURCE
 */
XMLRPC_VALUE XMLRPC_CreateValueDouble(const char* id, double d) {
   XMLRPC_VALUE val = XMLRPC_CreateValueEmpty();
   if(val) {
      XMLRPC_SetValueDouble(val, d);
      if(id) {
         XMLRPC_SetValueID(val, id, 0);
      }
   }
   return val;
}

/*******/

/****f* VALUE/XMLRPC_GetValueString
 * NAME
 *   XMLRPC_GetValueString
 * SYNOPSIS
 *   const char* XMLRPC_GetValueString(XMLRPC_VALUE value)
 * FUNCTION
 *   retrieve string value
 * INPUTS
 *   value     source XMLRPC_VALUE of type xmlrpc_string
 * RESULT
 *   void                                
 * SEE ALSO
 *   XMLRPC_SetValueString ()
 *   XMLRPC_GetValueType ()
 *   XMLRPC_VALUE
 * SOURCE
 */
const char* XMLRPC_GetValueString(XMLRPC_VALUE value) {
    return ((value && value->type == xmlrpc_string) ? value->str.str : 0);
}

/*******/

/****f* VALUE/XMLRPC_GetValueStringLen
 * NAME
 *   XMLRPC_GetValueStringLen
 * SYNOPSIS
 *   int XMLRPC_GetValueStringLen(XMLRPC_VALUE value)
 * FUNCTION
 *   determine length of string value
 * INPUTS
 *   value     XMLRPC_VALUE of type xmlrpc_string 
 * RESULT
 *   length of string, or 0
 * NOTES
 * SEE ALSO
 *   XMLRPC_SetValueString ()
 *   XMLRPC_GetValueString ()
 * SOURCE
 */
int XMLRPC_GetValueStringLen(XMLRPC_VALUE value) {
    return ((value) ? value->str.len : 0);
}

/*******/

/****f* VALUE/XMLRPC_GetValueInt
 * NAME
 *   XMLRPC_GetValueInt
 * SYNOPSIS
 *   int XMLRPC_GetValueInt(XMLRPC_VALUE value)
 * FUNCTION
 *   retrieve integer value.
 * INPUTS
 *   value     XMLRPC_VALUE of type xmlrpc_int 
 * RESULT
 *   integer value or 0 if value is not valid int
 * NOTES
 *   use XMLRPC_GetValueType () to be sure if 0 is real return value or not
 * SEE ALSO
 *   XMLRPC_SetValueInt ()
 *   XMLRPC_CreateValueInt ()
 * SOURCE
 */
int XMLRPC_GetValueInt(XMLRPC_VALUE value) {
    return ((value && value->type == xmlrpc_int) ? value->i : 0);
}

/*******/

/****f* VALUE/XMLRPC_GetValueBoolean
 * NAME
 *   XMLRPC_GetValueBoolean
 * SYNOPSIS
 *   int XMLRPC_GetValueBoolean(XMLRPC_VALUE value)
 * FUNCTION
 *   retrieve boolean value.
 * INPUTS
 *   XMLRPC_VALUE of type xmlrpc_boolean
 * RESULT
 *   boolean value or 0 if value is not valid boolean
 * NOTES
 *   use XMLRPC_GetValueType() to be sure if 0 is real value or not
 * SEE ALSO
 *   XMLRPC_SetValueBoolean ()
 *   XMLRPC_CreateValueBoolean ()
 * SOURCE
 */
int XMLRPC_GetValueBoolean(XMLRPC_VALUE value) {
    return ((value && value->type == xmlrpc_boolean) ? value->i : 0);
}

/*******/

/****f* VALUE/XMLRPC_GetValueDouble
 * NAME
 *   XMLRPC_GetValueDouble
 * SYNOPSIS
 *   double XMLRPC_GetValueDouble(XMLRPC_VALUE value)
 * FUNCTION
 *   retrieve double value
 * INPUTS
 *   XMLRPC_VALUE of type xmlrpc_double
 * RESULT
 *   double value or 0 if value is not valid double.
 * NOTES
 *   use XMLRPC_GetValueType() to be sure if 0 is real value or not
 * SEE ALSO
 *   XMLRPC_SetValueDouble ()
 *   XMLRPC_CreateValueDouble ()
 * SOURCE
 */
double XMLRPC_GetValueDouble(XMLRPC_VALUE value) {
    return ((value && value->type == xmlrpc_double) ? value->d : 0);
}

/*******/

/****f* VALUE/XMLRPC_GetValueBase64
 * NAME
 *   XMLRPC_GetValueBase64
 * SYNOPSIS
 *   const char* XMLRPC_GetValueBase64(XMLRPC_VALUE value)
 * FUNCTION
 *   retrieve binary value
 * INPUTS
 *   XMLRPC_VALUE of type xmlrpc_base64
 * RESULT
 *   pointer to binary value or 0 if value is not valid.
 * SEE ALSO
 *   XMLRPC_SetValueBase64 ()
 *   XMLRPC_CreateValueBase64 ()
 * NOTES
 *   Call XMLRPC_GetValueStringLen() to retrieve real length of binary data.  strlen()
 *   will not be accurate, as returned data may contain embedded nulls.
 * SOURCE
 */
const char* XMLRPC_GetValueBase64(XMLRPC_VALUE value) {
    return ((value && value->type == xmlrpc_base64) ? value->str.str : 0);
}

/*******/

/****f* VALUE/XMLRPC_GetValueDateTime
 * NAME
 *   XMLRPC_GetValueDateTime
 * SYNOPSIS
 *   time_t XMLRPC_GetValueDateTime(XMLRPC_VALUE value)
 * FUNCTION
 *   retrieve time_t value
 * INPUTS
 *   XMLRPC_VALUE of type xmlrpc_datetime
 * RESULT
 *   time_t value or 0 if value is not valid datetime.
 * NOTES
 *   use XMLRPC_GetValueType() to be sure if 0 is real value or not
 * SEE ALSO
 *   XMLRPC_SetValueDateTime ()
 *   XMLRPC_GetValueDateTime_ISO8601 ()
 *   XMLRPC_CreateValueDateTime ()
 * SOURCE
 */
time_t XMLRPC_GetValueDateTime(XMLRPC_VALUE value) {
    return (time_t)((value && value->type == xmlrpc_datetime) ? value->i : 0);
}

/*******/

/****f* VALUE/XMLRPC_GetValueDateTime_IOS8601
 * NAME
 *   XMLRPC_GetValueDateTime_IOS8601
 * SYNOPSIS
 *   const char* XMLRPC_GetValueDateTime_IOS8601(XMLRPC_VALUE value)
 * FUNCTION
 *   retrieve ISO8601 formatted time value
 * INPUTS
 *   XMLRPC_VALUE of type xmlrpc_datetime
 * RESULT
 *   const char* value or 0 if value is not valid datetime.
 * SEE ALSO
 *   XMLRPC_SetValueDateTime_IOS8601 ()
 *   XMLRPC_GetValueDateTime ()
 *   XMLRPC_CreateValueDateTime_IOS8601 ()
 * SOURCE
 */
const char* XMLRPC_GetValueDateTime_ISO8601(XMLRPC_VALUE value) {
    return ((value && value->type == xmlrpc_datetime) ? value->str.str : 0);
}

/*******/

/* Get ID (key) of value or NULL */
/****f* VALUE/XMLRPC_GetValueID
 * NAME
 *   XMLRPC_GetValueID
 * SYNOPSIS
 *   const char* XMLRPC_GetValueID(XMLRPC_VALUE value)
 * FUNCTION
 *   retrieve id (key) of value
 * INPUTS
 *   XMLRPC_VALUE of any type
 * RESULT
 *   const char* pointer to id of value, or NULL
 * NOTES
 * SEE ALSO
 *   XMLRPC_SetValueID()
 *   XMLRPC_CreateValueEmpty()
 * SOURCE
 */
const char* XMLRPC_GetValueID(XMLRPC_VALUE value) {
    return (const char*)((value && value->id.len) ? value->id.str : 0);
}

/*******/


/****f* VECTOR/XMLRPC_VectorSize
 * NAME
 *   XMLRPC_VectorSize
 * SYNOPSIS
 *   int XMLRPC_VectorSize(XMLRPC_VALUE value)
 * FUNCTION
 *   retrieve size of vector
 * INPUTS
 *   XMLRPC_VALUE of type xmlrpc_vector
 * RESULT
 *   count of items in vector
 * NOTES
 *   This is a cheap operation even on large vectors.  Vector size is 
 *   maintained by queue during add/remove ops.
 * SEE ALSO
 *   XMLRPC_AddValueToVector ()
 * SOURCE
 */
int XMLRPC_VectorSize(XMLRPC_VALUE value) {
   int size = 0;
   if(value && value->type == xmlrpc_vector && value->v) {
      size = Q_Size(value->v->q);
   }
   return size;
}

/*******/

/****f* VECTOR/XMLRPC_VectorRewind
 * NAME
 *   XMLRPC_VectorRewind
 * SYNOPSIS
 *   XMLRPC_VALUE XMLRPC_VectorRewind(XMLRPC_VALUE value)
 * FUNCTION
 *   reset vector to first item
 * INPUTS
 *   XMLRPC_VALUE of type xmlrpc_vector
 * RESULT
 *   first XMLRPC_VALUE in list, or NULL if empty or error.
 * NOTES
 *   Be careful to rewind any vector passed in to you if you expect to
 *   iterate through the entire list.
 * SEE ALSO
 *   XMLRPC_VectorNext ()
 * SOURCE
 */
XMLRPC_VALUE XMLRPC_VectorRewind(XMLRPC_VALUE value) {
   XMLRPC_VALUE xReturn = NULL;
   if(value && value->type == xmlrpc_vector && value->v) {
      xReturn = (XMLRPC_VALUE)Q_Head(value->v->q);
   }
   return xReturn;
}

/*******/

/****f* VECTOR/XMLRPC_VectorNext
 * NAME
 *   XMLRPC_VectorNext
 * SYNOPSIS
 *   XMLRPC_VALUE XMLRPC_VectorNext(XMLRPC_VALUE value)
 * FUNCTION
 *   Iterate vector to next item in list.
 * INPUTS
 *   XMLRPC_VALUE of type xmlrpc_vector
 * RESULT
 *   Next XMLRPC_VALUE in vector, or NULL if at end.
 * NOTES
 * SEE ALSO
 *   XMLRPC_VectorRewind ()
 * SOURCE
 */
XMLRPC_VALUE XMLRPC_VectorNext(XMLRPC_VALUE value) {
   XMLRPC_VALUE xReturn = NULL;
   if(value && value->type == xmlrpc_vector && value->v) {
      xReturn = (XMLRPC_VALUE)Q_Next(value->v->q);
   }
   return xReturn;
}

/*******/

/****f* VALUE/XMLRPC_GetValueType
 * NAME
 *   XMLRPC_GetValueType
 * SYNOPSIS
 *   XMLRPC_VALUE_TYPE XMLRPC_GetValueType(XMLRPC_VALUE value)
 * FUNCTION
 *   determine data type of the XMLRPC_VALUE
 * INPUTS
 *   XMLRPC_VALUE target of query
 * RESULT
 *   data type of value as enumerated by XMLRPC_VALUE_TYPE
 * NOTES
 *   all values are of type xmlrpc_empty until set.
 *   Deprecated for public use.  See XMLRPC_GetValueTypeEasy
 * SEE ALSO
 *   XMLRPC_SetValue*
 *   XMLRPC_CreateValue*
 *   XMLRPC_Append*
 *   XMLRPC_GetValueTypeEasy ()
 * SOURCE
 */
XMLRPC_VALUE_TYPE XMLRPC_GetValueType(XMLRPC_VALUE value) {
   return value ? value->type : xmlrpc_empty;
}

/*******/

/* Vector type accessor */
/****f* VALUE/XMLRPC_GetVectorType
 * NAME
 *   XMLRPC_GetVectorType
 * SYNOPSIS
 *   XMLRPC_VECTOR_TYPE XMLRPC_GetVectorType(XMLRPC_VALUE value)
 * FUNCTION
 *   determine vector type of the XMLRPC_VALUE
 * INPUTS
 *   XMLRPC_VALUE of type xmlrpc_vector
 * RESULT
 *   vector type of value as enumerated by XMLRPC_VECTOR_TYPE.
 *   xmlrpc_none if not a value.
 * NOTES
 *   xmlrpc_none is returned if value is not a vector
 *   Deprecated for public use.  See XMLRPC_GetValueTypeEasy
 * SEE ALSO
 *   XMLRPC_SetIsVector ()
 *   XMLRPC_GetValueType ()
 *   XMLRPC_GetValueTypeEasy ()
 * SOURCE
 */
XMLRPC_VECTOR_TYPE XMLRPC_GetVectorType(XMLRPC_VALUE value) {
   return(value && value->v) ? value->v->type : xmlrpc_none;
}

/*******/

/****f* VALUE/XMLRPC_GetValueTypeEasy
 * NAME
 *   XMLRPC_GetValueTypeEasy
 * SYNOPSIS
 *   XMLRPC_VALUE_TYPE_EASY XMLRPC_GetValueTypeEasy(XMLRPC_VALUE value)
 * FUNCTION
 *   determine data type of the XMLRPC_VALUE. includes vector types.
 * INPUTS
 *   XMLRPC_VALUE target of query
 * RESULT
 *   data type of value as enumerated by XMLRPC_VALUE_TYPE_EASY
 *   xmlrpc_type_none if not a value.
 * NOTES
 *   all values are of type xmlrpc_type_empty until set. 
 * SEE ALSO
 *   XMLRPC_SetValue*
 *   XMLRPC_CreateValue*
 *   XMLRPC_Append*
 * SOURCE
 */
XMLRPC_VALUE_TYPE_EASY XMLRPC_GetValueTypeEasy (XMLRPC_VALUE value) {
	if (value) {
		switch (value->type) {
		case xmlrpc_vector:
			switch (value->v->type) {
			case xmlrpc_vector_none:
				return xmlrpc_type_none;
			case xmlrpc_vector_struct:
				return xmlrpc_type_struct;
			case xmlrpc_vector_mixed:
				return xmlrpc_type_mixed;
			case xmlrpc_vector_array:
				return xmlrpc_type_array;
			}
		default:
			/* evil cast, but we know they are the same */
			return(XMLRPC_VALUE_TYPE_EASY) value->type;
		}
	}
	return xmlrpc_none;
}

/*******/



/*-*******************
* Begin Server Funcs *
*********************/


/****f* VALUE/XMLRPC_ServerCreate
 * NAME
 *   XMLRPC_ServerCreate
 * SYNOPSIS
 *   XMLRPC_SERVER XMLRPC_ServerCreate()
 * FUNCTION
 *   Allocate/Init XMLRPC Server Resources.
 * INPUTS
 *   none
 * RESULT
 *   newly allocated XMLRPC_SERVER
 * NOTES
 * SEE ALSO
 *   XMLRPC_ServerDestroy ()
 *   XMLRPC_GetGlobalServer ()
 * SOURCE
 */
XMLRPC_SERVER XMLRPC_ServerCreate() {
   XMLRPC_SERVER server = calloc(1, sizeof(STRUCT_XMLRPC_SERVER));
   if(server) {
      Q_Init(&server->methodlist);
      Q_Init(&server->docslist);

      /* register system methods */
      xsm_register(server);
   }
   return server;
}

/*******/

/* Return global server.  Not locking! Not Thread Safe! */
/****f* VALUE/XMLRPC_GetGlobalServer
 * NAME
 *   XMLRPC_GetGlobalServer
 * SYNOPSIS
 *   XMLRPC_SERVER XMLRPC_GetGlobalServer()
 * FUNCTION
 *   Allocates a global (process-wide) server, or returns pointer if pre-existing.
 * INPUTS
 *   none
 * RESULT
 *   pointer to global server, or 0 if error.
 * NOTES
 *   ***WARNING*** This function is not thread safe.  It is included only for the very lazy.
 *   Multi-threaded programs that use this may experience problems.
 * BUGS
 *   There is currently no way to cleanup the global server gracefully.
 * SEE ALSO
 *   XMLRPC_ServerCreate ()
 * SOURCE
 */
XMLRPC_SERVER XMLRPC_GetGlobalServer() {
   static XMLRPC_SERVER xsServer = 0;
   if(!xsServer) {
      xsServer = XMLRPC_ServerCreate();
   }
   return xsServer;
}

/*******/

/****f* VALUE/XMLRPC_ServerDestroy
 * NAME
 *   XMLRPC_ServerDestroy
 * SYNOPSIS
 *   void XMLRPC_ServerDestroy(XMLRPC_SERVER server)
 * FUNCTION
 *   Free Server Resources
 * INPUTS
 *   server     The server to be free'd
 * RESULT
 *   void
 * NOTES
 *   This frees the server struct and any methods that have been added.
 * SEE ALSO
 *   XMLRPC_ServerCreate ()
 * SOURCE
 */
void XMLRPC_ServerDestroy(XMLRPC_SERVER server) {
   if(server) {
      doc_method* dm = Q_Head(&server->docslist);
      server_method* sm = Q_Head(&server->methodlist);
      while( dm ) {
         my_free(dm);
         dm = Q_Next(&server->docslist);
      }
      while( sm ) {
         if(sm->name) {
            my_free(sm->name);
         }
         if(sm->desc) {
            XMLRPC_CleanupValue(sm->desc);
         }
         my_free(sm);
         sm = Q_Next(&server->methodlist);
      }
      if(server->xIntrospection) {
         XMLRPC_CleanupValue(server->xIntrospection);
      }

      Q_Destroy(&server->methodlist);
      Q_Destroy(&server->docslist);
      my_free(server);
   }
}

/*******/


/****f* VALUE/XMLRPC_ServerRegisterMethod
 * NAME
 *   XMLRPC_ServerRegisterMethod
 * SYNOPSIS
 *   void XMLRPC_ServerRegisterMethod(XMLRPC_SERVER server, const char *name, XMLRPC_Callback cb)
 * FUNCTION
 *   Register new XMLRPC method with server
 * INPUTS
 *   server     The XMLRPC_SERVER to register the method with
 *   name       public name of the method
 *   cb         C function that implements the method
 * RESULT
 *   int  - 1 if success, else 0
 * NOTES
 *   A C function must be registered for every "method" that the server recognizes.  The
 *   method name is equivalent to <methodCall><name> method name </name></methodCall> in the
 *   XML syntax.
 * SEE ALSO
 *   XMLRPC_ServerFindMethod ()
 *   XMLRPC_ServerCallMethod ()
 * SOURCE
 */
int XMLRPC_ServerRegisterMethod(XMLRPC_SERVER server, const char *name, XMLRPC_Callback cb) {
   if(server && name && cb) {

      server_method* sm = malloc(sizeof(server_method));
      
      if(sm) {
         sm->name = strdup(name);
         sm->method = cb;
         sm->desc = NULL;

         return Q_PushTail(&server->methodlist, sm);
      }
   }
   return 0;
}

/*******/

server_method* find_method(XMLRPC_SERVER server, const char* name) {
   server_method* sm;

   q_iter qi = Q_Iter_Head_F(&server->methodlist);

   while( qi ) {
      sm = Q_Iter_Get_F(qi);
      if(sm && !strcmp(sm->name, name)) {
         return sm;
      }
      qi = Q_Iter_Next_F(qi);
   }
   return NULL;
}


const char* type_to_str(XMLRPC_VALUE_TYPE type, XMLRPC_VECTOR_TYPE vtype) {
    switch(type) {
       case xmlrpc_none:
          return "none";
       case xmlrpc_empty:
          return "empty";
       case xmlrpc_base64:
          return "base64";
       case xmlrpc_boolean:
          return "boolean";
       case xmlrpc_datetime:
          return "datetime";
       case xmlrpc_double:
          return "double";
       case xmlrpc_int:
          return "int";
       case xmlrpc_string:
          return "string";
       case xmlrpc_vector:
          switch(vtype) {
             case xmlrpc_vector_none:
                return "none";
             case xmlrpc_vector_array:
                return "array";
             case xmlrpc_vector_mixed:
                return "mixed vector (struct)";
             case xmlrpc_vector_struct:
                return "struct";
          }
    }
    return "unknown";
}

/****f* VALUE/XMLRPC_ServerFindMethod
 * NAME
 *   XMLRPC_ServerFindMethod
 * SYNOPSIS
 *   XMLRPC_Callback XMLRPC_ServerFindMethod(XMLRPC_SERVER server, const char* callName)
 * FUNCTION
 *   retrieve C callback associated with a given method name.
 * INPUTS       
 *   server     The XMLRPC_SERVER the method is registered with
 *   callName   the method to find
 * RESULT
 *   previously registered XMLRPC_Callback, or NULL
 * NOTES
 *   Typically, this is used to determine if a requested method exists, without actually calling it.
 * SEE ALSO
 *   XMLRPC_ServerCallMethod ()
 *   XMLRPC_ServerRegisterMethod ()
 * SOURCE
 */
XMLRPC_Callback XMLRPC_ServerFindMethod(XMLRPC_SERVER server, const char* callName) {
   if(server && callName) {
      q_iter qi = Q_Iter_Head_F(&server->methodlist);
      while( qi ) {
         server_method* sm = Q_Iter_Get_F(qi);
         if(sm && !strcmp(sm->name, callName)) {
            return sm->method;
         }
         qi = Q_Iter_Next_F(qi);
      }
   }
   return NULL;
}

/*******/


/* Call method specified in request */
/****f* VALUE/XMLRPC_ServerCallMethod
 * NAME
 *   XMLRPC_ServerCallMethod
 * SYNOPSIS
 *   XMLRPC_VALUE XMLRPC_ServerCallMethod(XMLRPC_SERVER server, XMLRPC_REQUEST request, void* userData)
 * FUNCTION
 *
 * INPUTS
 *   server     The XMLRPC_SERVER the method is registered with
 *   request    the request to handle
 *   userData   any additional data to pass to the C callback, or NULL
 * RESULT
 *   XMLRPC_VALUE allocated by the callback, or NULL
 * NOTES
 *   It is typically the caller's responsibility to free the returned value.
 *
 *   Often the caller will want to serialize the result as XML, via 
 *   XMLRPC_VALUE_To_XML () or XMLRPC_REQUEST_To_XML ()
 * SEE ALSO
 *   XMLRPC_ServerFindMethod ()
 *   XMLRPC_ServerRegisterMethod ()
 *   XMLRPC_CleanupValue ()
 * SOURCE
 */
XMLRPC_VALUE XMLRPC_ServerCallMethod(XMLRPC_SERVER server, XMLRPC_REQUEST request, void* userData) {
   XMLRPC_VALUE xReturn = NULL;

   /* check for error set during request parsing / generation */
   if(request && request->error) {
      xReturn = XMLRPC_CopyValue(request->error);
   }
	else if (server && request) {
		XMLRPC_Callback cb =
		XMLRPC_ServerFindMethod (server, request->methodName.str);
      if(cb) {
         xReturn = cb(server, request, userData);
      }
      else {
			xReturn =
			XMLRPC_UtilityCreateFault (xmlrpc_error_unknown_method,
												request->methodName.str);
      }
   }
   return xReturn;
}

/*******/

/*-*****************
* End server funcs *
*******************/


/*-***********************************
* Begin XMLRPC General Options funcs *
*************************************/

/* For options used by XMLRPC_VALUE funcs that otherwise do not have
 * parameters for options.  Kind of gross.  :(
 */
typedef struct _xmlrpc_options {
   XMLRPC_CASE id_case;
   XMLRPC_CASE_COMPARISON id_case_compare;
}
STRUCT_XMLRPC_OPTIONS, *XMLRPC_OPTIONS;

static XMLRPC_OPTIONS XMLRPC_GetDefaultOptions() {
   static STRUCT_XMLRPC_OPTIONS options = {
      xmlrpc_case_exact,
      xmlrpc_case_sensitive
   };
   return &options;
}

/****f* VALUE/XMLRPC_GetDefaultIdCase
 * NAME
 *   XMLRPC_GetDefaultIdCase
 * SYNOPSIS
 *   XMLRPC_CASE XMLRPC_GetDefaultIdCase()
 * FUNCTION
 *   Gets default case options used by XMLRPC_VALUE funcs
 * INPUTS
 *   none
 * RESULT
 *   XMLRPC_CASE
 * BUGS
 *   Nasty and gross.  Should be server specific, but that requires changing all
 *  the XMLRPC_VALUE api's.
 * SEE ALSO
 *   XMLRPC_SetDefaultIdCase ()
 * SOURCE
 */
XMLRPC_CASE XMLRPC_GetDefaultIdCase() {
   XMLRPC_OPTIONS options = XMLRPC_GetDefaultOptions();
   return options->id_case;
}

/*******/

/****f* VALUE/XMLRPC_SetDefaultIdCase
 * NAME
 *   XMLRPC_SetDefaultIdCase
 * SYNOPSIS
 *   XMLRPC_CASE XMLRPC_SetDefaultIdCase(XMLRPC_CASE id_case)
 * FUNCTION
 *   Sets default case options used by XMLRPC_VALUE funcs
 * INPUTS
 *   id_case   case options as enumerated by XMLRPC_CASE
 * RESULT
 *   XMLRPC_CASE -- newly set option
 * BUGS
 *   Nasty and gross.  Should be server specific, but that requires changing all
 *  the XMLRPC_VALUE api's.
 * SEE ALSO
 *   XMLRPC_GetDefaultIdCase ()
 * SOURCE
 */
XMLRPC_CASE XMLRPC_SetDefaultIdCase(XMLRPC_CASE id_case) {
   XMLRPC_OPTIONS options = XMLRPC_GetDefaultOptions();
   options->id_case = id_case;
   return options->id_case;
}

/*******/

/****f* VALUE/XMLRPC_GetDefaultIdCaseComparison
 * NAME
 *   XMLRPC_GetDefaultIdCaseComparison
 * SYNOPSIS
 *   XMLRPC_CASE XMLRPC_GetDefaultIdCaseComparison( )
 * FUNCTION
 *   Gets default case comparison options used by XMLRPC_VALUE funcs
 * INPUTS
 *   none
 * RESULT
 *   XMLRPC_CASE_COMPARISON default
 * BUGS
 *   Nasty and gross.  Should be server specific, but that requires changing all
 *  the XMLRPC_VALUE api's.
 * SEE ALSO
 *   XMLRPC_SetDefaultIdCaseComparison ()
 * SOURCE
 */
XMLRPC_CASE_COMPARISON XMLRPC_GetDefaultIdCaseComparison() {
   XMLRPC_OPTIONS options = XMLRPC_GetDefaultOptions();
   return options->id_case_compare;
}

/*******/

/****f* VALUE/XMLRPC_SetDefaultIdCaseComparison
 * NAME
 *   XMLRPC_SetDefaultIdCaseComparison
 * SYNOPSIS
 *   XMLRPC_CASE XMLRPC_SetDefaultIdCaseComparison( XMLRPC_CASE_COMPARISON id_case_compare )
 * FUNCTION
 *   Gets default case comparison options used by XMLRPC_VALUE funcs
 * INPUTS
 *   id_case_compare  case comparison rule to set as default
 * RESULT
 *   XMLRPC_CASE_COMPARISON newly set default
 * BUGS
 *   Nasty and gross.  Should be server specific, but that requires changing all
 *  the XMLRPC_VALUE api's.
 * SEE ALSO
 *   XMLRPC_GetDefaultIdCaseComparison ()
 * SOURCE
 */
XMLRPC_CASE_COMPARISON XMLRPC_SetDefaultIdCaseComparison(XMLRPC_CASE_COMPARISON id_case_compare) {
   XMLRPC_OPTIONS options = XMLRPC_GetDefaultOptions();
   options->id_case_compare = id_case_compare;
   return options->id_case_compare;
}

/*******/

/*-*********************************
* End XMLRPC General Options funcs *
***********************************/


/*-******************
* Fault API funcs   *
********************/

/****f* UTILITY/XMLRPC_UtilityCreateFault
 * NAME
 *   XMLRPC_UtilityCreateFault
 * SYNOPSIS
 *   XMLRPC_VALUE XMLRPC_UtilityCreateFault( int fault_code, const char* fault_string )
 * FUNCTION
 *   generates a struct containing a string member with id "faultString" and an int member
 *   with id "faultCode". When using the xmlrpc xml serialization, these will be translated
 *   to <fault><value><struct>... format.
 * INPUTS
 *   fault_code     application specific error code. can be 0.
 *   fault_string   application specific error string.  cannot be null.
 * RESULT
 *   XMLRPC_VALUE a newly created struct vector representing the error, or null on error.
 * NOTES
 *   This is a utility function. xmlrpc "faults" are not directly represented in this xmlrpc
 *   API or data structures. It is the author's view, that this API is intended for simple
 *   data types, and a "fault" is a complex data type consisting of multiple simple data
 *   types.  This function is provided for convenience only, the same result could be
 *   achieved directly by the application.
 *
 *   This function now supports some "standardized" fault codes, as specified at.
 *   http://xmlrpc-epi.sourceforge.net/specs/rfc.fault_codes.php.
 *   If one of these fault codes is received, the description string will automatically
 *   be prefixed with a standard error string and 2 newlines.  
 *
 *   The actual transformation between this complex type and the xml "<fault>" element takes
 *   place in the xmlrpc to xml serialization layer.  This step is not performed when using the
 *   simplerpc serialization, meaning that there will be no "<fault>" element in that
 *   serialization. There will simply be a standard struct with 2 child elements.  
 *   imho, the "<fault>" element is unnecessary and/or out of place as part of the standard API.
 *
 * SOURCE
 */
XMLRPC_VALUE XMLRPC_UtilityCreateFault(int fault_code, const char* fault_string) {
   XMLRPC_VALUE xOutput = NULL;

   char* string = NULL;
   simplestring description;
   simplestring_init(&description);

   switch (fault_code) {
	case xmlrpc_error_parse_xml_syntax:
		string = xmlrpc_error_parse_xml_syntax_str;
		break;
	case xmlrpc_error_parse_unknown_encoding:
		string = xmlrpc_error_parse_unknown_encoding_str;
		break;
	case xmlrpc_error_parse_bad_encoding:
		string = xmlrpc_error_parse_bad_encoding_str;
		break;
	case xmlrpc_error_invalid_xmlrpc:
		string = xmlrpc_error_invalid_xmlrpc_str;
		break;
	case xmlrpc_error_unknown_method:
		string = xmlrpc_error_unknown_method_str;
		break;
	case xmlrpc_error_invalid_params:
		string = xmlrpc_error_invalid_params_str;
		break;
	case xmlrpc_error_internal_server:
		string = xmlrpc_error_internal_server_str;
		break;
	case xmlrpc_error_application:
		string = xmlrpc_error_application_str;
		break;
	case xmlrpc_error_system:
		string = xmlrpc_error_system_str;
		break;
	case xmlrpc_error_transport:
		string = xmlrpc_error_transport_str;
		break;
   }

   simplestring_add(&description, string);

   if(string && fault_string) {
      simplestring_add(&description, "\n\n");
   }
   simplestring_add(&description, fault_string);


   if(description.len) {
      xOutput = XMLRPC_CreateVector(NULL, xmlrpc_vector_struct);

		XMLRPC_VectorAppendString (xOutput, "faultString", description.str,
											description.len);
      XMLRPC_VectorAppendInt(xOutput, "faultCode", fault_code);
   }

   simplestring_free(&description);

   return xOutput;
}

/*******/


/****f* FAULT/XMLRPC_ValueIsFault
 * NAME
 *   XMLRPC_ValueIsFault
 * SYNOPSIS
 *   int XMLRPC_ValueIsFault (XMLRPC_VALUE value)
 * FUNCTION
 *   Determines if a value encapsulates a fault "object"
 * INPUTS
 *   value  any XMLRPC_VALUE
 * RESULT
 *   1 if it is a fault, else 0
 * SEE ALSO
 *   XMLRPC_ResponseIsFault ()
 * SOURCE
 */
int XMLRPC_ValueIsFault (XMLRPC_VALUE value) {
   if( XMLRPC_VectorGetValueWithID(value, "faultCode") &&
       XMLRPC_VectorGetValueWithID(value, "faultString") ) {
      return 1;
   }
   return 0;
}
/*******/


/****f* FAULT/XMLRPC_ResponseIsFault
 * NAME
 *   XMLRPC_ResponseIsFault
 * SYNOPSIS
 *   int XMLRPC_ResponseIsFault (XMLRPC_REQUEST response)
 * FUNCTION
 *   Determines if a response contains an encapsulated fault "object"
 * INPUTS
 *   value  any XMLRPC_REQUEST. typically of type xmlrpc_request_response
 * RESULT
 *   1 if it contains a fault, else 0
 * SEE ALSO
 *   XMLRPC_ValueIsFault ()
 * SOURCE
 */
int XMLRPC_ResponseIsFault(XMLRPC_REQUEST response) {
   return XMLRPC_ValueIsFault( XMLRPC_RequestGetData(response) );
}

/*******/

/****f* FAULT/XMLRPC_GetValueFaultCode
 * NAME
 *   XMLRPC_GetValueFaultCode
 * SYNOPSIS
 *   int XMLRPC_GetValueFaultCode (XMLRPC_VALUE value)
 * FUNCTION
 *   returns fault code from a struct, if any
 * INPUTS
 *   value  XMLRPC_VALUE of type xmlrpc_vector_struct.
 * RESULT
 *   fault code, else 0.
 * BUGS
 *   impossible to distinguish faultCode == 0 from faultCode not present.
 * SEE ALSO
 *   XMLRPC_GetResponseFaultCode ()
 * SOURCE
 */
int XMLRPC_GetValueFaultCode (XMLRPC_VALUE value) {
   return XMLRPC_VectorGetIntWithID(value, "faultCode");
}

/*******/

/****f* FAULT/XMLRPC_GetResponseFaultCode
 * NAME
 *   XMLRPC_GetResponseFaultCode
 * SYNOPSIS
 *   int XMLRPC_GetResponseFaultCode(XMLRPC_REQUEST response)
 * FUNCTION
 *   returns fault code from a response, if any
 * INPUTS
 *   response  XMLRPC_REQUEST. typically of type xmlrpc_request_response.
 * RESULT
 *   fault code, else 0.
 * BUGS
 *   impossible to distinguish faultCode == 0 from faultCode not present.
 * SEE ALSO
 *   XMLRPC_GetValueFaultCode ()
 * SOURCE
 */
int XMLRPC_GetResponseFaultCode(XMLRPC_REQUEST response) {
   return XMLRPC_GetValueFaultCode( XMLRPC_RequestGetData(response) );
}

/*******/


/****f* FAULT/XMLRPC_GetValueFaultString
 * NAME
 *   XMLRPC_GetValueFaultString
 * SYNOPSIS
 *   const char* XMLRPC_GetValueFaultString (XMLRPC_VALUE value)
 * FUNCTION
 *   returns fault string from a struct, if any
 * INPUTS
 *   value  XMLRPC_VALUE of type xmlrpc_vector_struct.
 * RESULT
 *   fault string, else 0.
 * SEE ALSO
 *   XMLRPC_GetResponseFaultString ()
 * SOURCE
 */
const char* XMLRPC_GetValueFaultString (XMLRPC_VALUE value) {
   return XMLRPC_VectorGetStringWithID(value, "faultString");
}

/*******/

/****f* FAULT/XMLRPC_GetResponseFaultString
 * NAME
 *   XMLRPC_GetResponseFaultString
 * SYNOPSIS
 *   const char* XMLRPC_GetResponseFaultString (XMLRPC_REQUEST response)
 * FUNCTION
 *   returns fault string from a response, if any
 * INPUTS
 *   response  XMLRPC_REQUEST. typically of type xmlrpc_request_response.
 * RESULT
 *   fault string, else 0.
 * SEE ALSO
 *   XMLRPC_GetValueFaultString ()
 * SOURCE
 */
const char* XMLRPC_GetResponseFaultString (XMLRPC_REQUEST response) {
   return XMLRPC_GetValueFaultString( XMLRPC_RequestGetData(response) );
}

/*******/


/*-******************
* Utility API funcs *
********************/


/****f* UTILITY/XMLRPC_Free
 * NAME
 *   XMLRPC_Free
 * SYNOPSIS
 *   void XMLRPC_Free(void* mem)
 * FUNCTION
 *   frees a block of memory allocated by xmlrpc. 
 * INPUTS
 *   mem    memory to free
 * RESULT
 *   void
 * NOTES
 *   Useful for OS's where memory must be free'd
 *   in the same library in which it is allocated.
 * SOURCE
 */
void XMLRPC_Free(void* mem) {
   my_free(mem);
}

/*******/


/****f* UTILITY/XMLRPC_GetVersionString
 * NAME
 *   XMLRPC_GetVersionString
 * SYNOPSIS
 *   const char* XMLRPC_GetVersionString()
 * FUNCTION
 *   returns library version string
 * INPUTS
 *   
 * RESULT
 *   const char* 
 * NOTES
 * SOURCE
 */
const char*  XMLRPC_GetVersionString() {
   return XMLRPC_VERSION_STR;
}

/*******/


/*-**********************
* End Utility API funcs *
************************/
