/* Copyright (C) 2001 by First Peer, Inc. All rights reserved. ** ** Redistribution and use in source and binary forms, with or without ** modification, are permitted provided that the following conditions ** are met: ** 1. Redistributions of source code must retain the above copyright ** notice, this list of conditions and the following disclaimer. ** 2. Redistributions in binary form must reproduce the above copyright ** notice, this list of conditions and the following disclaimer in the ** documentation and/or other materials provided with the distribution. ** 3. The name of the author may not be used to endorse or promote products ** derived from this software without specific prior written permission. ** ** THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND ** ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE ** IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ** ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE ** FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL ** DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS ** OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) ** HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT ** LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY ** OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF ** SUCH DAMAGE. */ #include "xmlrpc_config.h" #include #include #include #include #include "xmlrpc.h" #include "xmlrpc_int.h" #include "xmlrpc_xmlparser.h" /* Define the contents of our internal structure. */ struct _xml_element { struct _xml_element *_parent; char *_name; xmlrpc_mem_block _cdata; /* char */ xmlrpc_mem_block _children; /* xml_element* */ }; /* Check that we're using expat in UTF-8 mode, not wchar_t mode. ** If you need to use expat in wchar_t mode, write a subroutine to ** copy a wchar_t string to a char string & return an error for ** any non-ASCII characters. Then call this subroutine on all ** XML_Char strings passed to our event handlers before using the ** data. */ /* #if sizeof(char) != sizeof(XML_Char) ** #error expat must define XML_Char to be a regular char. ** #endif */ #define XMLRPC_ASSERT_ELEM_OK(elem) \ XMLRPC_ASSERT((elem) != NULL && (elem)->_name != XMLRPC_BAD_POINTER) /*========================================================================= ** xml_element_new **========================================================================= ** Create a new xml_element. This routine isn't exported, because the ** arguments are implementation-dependent. */ static xml_element *xml_element_new (xmlrpc_env *env, char *name) { xml_element *retval; int name_valid, cdata_valid, children_valid; XMLRPC_ASSERT_ENV_OK(env); XMLRPC_ASSERT(name != NULL); /* Set up our error-handling preconditions. */ retval = NULL; name_valid = cdata_valid = children_valid = 0; /* Allocate our xml_element structure. */ retval = (xml_element*) malloc(sizeof(xml_element)); XMLRPC_FAIL_IF_NULL(retval, env, XMLRPC_INTERNAL_ERROR, "Couldn't allocate memory for XML element"); /* Set our parent field to NULL. */ retval->_parent = NULL; /* Copy over the element name. */ retval->_name = (char*) malloc(strlen(name) + 1); XMLRPC_FAIL_IF_NULL(retval->_name, env, XMLRPC_INTERNAL_ERROR, "Couldn't allocate memory for XML element"); name_valid = 1; strcpy(retval->_name, name); /* Initialize a block to hold our CDATA. */ XMLRPC_TYPED_MEM_BLOCK_INIT(char, env, &retval->_cdata, 0); XMLRPC_FAIL_IF_FAULT(env); cdata_valid = 1; /* Initialize a block to hold our child elements. */ XMLRPC_TYPED_MEM_BLOCK_INIT(xml_element*, env, &retval->_children, 0); XMLRPC_FAIL_IF_FAULT(env); children_valid = 1; cleanup: if (env->fault_occurred) { if (retval) { if (name_valid) free(retval->_name); if (cdata_valid) xmlrpc_mem_block_clean(&retval->_cdata); if (children_valid) xmlrpc_mem_block_clean(&retval->_children); free(retval); } return NULL; } else { return retval; } } /*========================================================================= ** xml_element_free **========================================================================= ** Blow away an existing element & all of its child elements. */ void xml_element_free (xml_element *elem) { xmlrpc_mem_block *children; int size, i; xml_element **contents; XMLRPC_ASSERT_ELEM_OK(elem); free(elem->_name); elem->_name = XMLRPC_BAD_POINTER; xmlrpc_mem_block_clean(&elem->_cdata); /* Deallocate all of our children recursively. */ children = &elem->_children; contents = XMLRPC_TYPED_MEM_BLOCK_CONTENTS(xml_element*, children); size = XMLRPC_TYPED_MEM_BLOCK_SIZE(xml_element*, children); for (i = 0; i < size; i++) xml_element_free(contents[i]); xmlrpc_mem_block_clean(&elem->_children); free(elem); } /*========================================================================= ** Miscellaneous Accessors **========================================================================= ** Return the fields of the xml_element. See the header for more ** documentation on each function works. */ char *xml_element_name (xml_element *elem) { XMLRPC_ASSERT_ELEM_OK(elem); return elem->_name; } /* The result of this function is NOT VALID until the end_element handler ** has been called! */ size_t xml_element_cdata_size (xml_element *elem) { XMLRPC_ASSERT_ELEM_OK(elem); return XMLRPC_TYPED_MEM_BLOCK_SIZE(char, &elem->_cdata) - 1; } char *xml_element_cdata (xml_element *elem) { XMLRPC_ASSERT_ELEM_OK(elem); return XMLRPC_TYPED_MEM_BLOCK_CONTENTS(char, &elem->_cdata); } size_t xml_element_children_size (xml_element *elem) { XMLRPC_ASSERT_ELEM_OK(elem); return XMLRPC_TYPED_MEM_BLOCK_SIZE(xml_element*, &elem->_children); } xml_element **xml_element_children (xml_element *elem) { XMLRPC_ASSERT_ELEM_OK(elem); return XMLRPC_TYPED_MEM_BLOCK_CONTENTS(xml_element*, &elem->_children); } /*========================================================================= ** Internal xml_element Utility Functions **========================================================================= */ static void xml_element_append_cdata (xmlrpc_env *env, xml_element *elem, char *cdata, size_t size) { XMLRPC_ASSERT_ENV_OK(env); XMLRPC_ASSERT_ELEM_OK(elem); XMLRPC_TYPED_MEM_BLOCK_APPEND(char, env, &elem->_cdata, cdata, size); } /* Whether or not this function succeeds, it takes ownership of the 'child' ** argument. ** WARNING - This is the exact opposite of the usual memory ownership ** rules for xmlrpc_value! So please pay attention. */ static void xml_element_append_child (xmlrpc_env *env, xml_element *elem, xml_element *child) { XMLRPC_ASSERT_ENV_OK(env); XMLRPC_ASSERT_ELEM_OK(elem); XMLRPC_ASSERT_ELEM_OK(child); XMLRPC_ASSERT(child->_parent == NULL); XMLRPC_TYPED_MEM_BLOCK_APPEND(xml_element*, env, &elem->_children, &child, 1); if (!env->fault_occurred) child->_parent = elem; else xml_element_free(child); } /*========================================================================= ** Our parse context. We pass this around as expat user data. **========================================================================= */ typedef struct { xmlrpc_env *env; xml_element *root; xml_element *current; } parse_context; /*========================================================================= ** Expat Event Handler Functions **========================================================================= */ static void start_element (void *user_data, XML_Char *name, XML_Char **atts ATTR_UNUSED) { parse_context *context; xml_element *elem, *new_current; XMLRPC_ASSERT(user_data != NULL && name != NULL); /* Get our context and see if an error has already occured. */ context = (parse_context*) user_data; if (!context->env->fault_occurred) { /* Set up our error-handling preconditions. */ elem = NULL; /* Build a new element. */ elem = xml_element_new(context->env, name); XMLRPC_FAIL_IF_FAULT(context->env); /* Insert it in the appropriate place. */ if (!context->root) { context->root = elem; context->current = elem; elem = NULL; } else { XMLRPC_ASSERT(context->current != NULL); /* (We need to watch our error handling invariants very carefully ** here. Read the docs for xml_element_append_child. */ new_current = elem; xml_element_append_child(context->env, context->current, elem); elem = NULL; XMLRPC_FAIL_IF_FAULT(context->env); context->current = new_current; } cleanup: if (elem) xml_element_free(elem); } } static void end_element (void *user_data, XML_Char *name) { parse_context *context; XMLRPC_ASSERT(user_data != NULL && name != NULL); /* Get our context and see if an error has already occured. */ context = (parse_context*) user_data; if (!context->env->fault_occurred) { /* XXX - I think expat enforces these facts, but I want to be sure. ** If one of these assertion ever fails, it should be replaced by a ** non-assertion runtime error check. */ XMLRPC_ASSERT(strcmp(name, context->current->_name) == 0); XMLRPC_ASSERT(context->current->_parent != NULL || context->current == context->root); /* Add a trailing '\0' to our cdata. */ xml_element_append_cdata(context->env, context->current, "\0", 1); XMLRPC_FAIL_IF_FAULT(context->env); /* Pop our "stack" of elements. */ context->current = context->current->_parent; cleanup: return; } } static void character_data (void *user_data, XML_Char *s, int len) { parse_context *context; XMLRPC_ASSERT(user_data != NULL && s != NULL && len >= 0); /* Get our context and see if an error has already occured. */ context = (parse_context*) user_data; if (!context->env->fault_occurred) { XMLRPC_ASSERT(context->current != NULL); xml_element_append_cdata(context->env, context->current, s, len); XMLRPC_FAIL_IF_FAULT(context->env); cleanup: return; } } /*========================================================================= ** Expat Driver **========================================================================= ** XXX - We should allow the user to specify the encoding of our xml_data. */ xml_element *xml_parse (xmlrpc_env *env, const char *xml_data, int xml_len) { parse_context context; XML_Parser parser; int ok; XMLRPC_ASSERT_ENV_OK(env); XMLRPC_ASSERT(xml_data != NULL && xml_len >= 0); /* Set up our error-handling preconditions. */ parser = NULL; context.root = NULL; /* Set up the rest of our parse context. */ context.env = env; context.current = NULL; /* Set up our XML parser. */ parser = XML_ParserCreate(NULL); XMLRPC_FAIL_IF_NULL(parser, env, XMLRPC_INTERNAL_ERROR, "Could not create expat parser"); XML_SetUserData(parser, &context); XML_SetElementHandler(parser, (XML_StartElementHandler) start_element, (XML_EndElementHandler) end_element); XML_SetCharacterDataHandler(parser, (XML_CharacterDataHandler) character_data); /* Parse our data. */ ok = XML_Parse(parser, xml_data, xml_len, 1); if (!ok) XMLRPC_FAIL(env, XMLRPC_PARSE_ERROR, (char*) XML_ErrorString(XML_GetErrorCode(parser))); XMLRPC_FAIL_IF_FAULT(env); /* Perform some sanity checks. */ XMLRPC_ASSERT(context.root != NULL); XMLRPC_ASSERT(context.current == NULL); cleanup: if (parser) XML_ParserFree(parser); if (env->fault_occurred) { if (context.root) xml_element_free(context.root); return NULL; } else { return context.root; } }