summaryrefslogtreecommitdiff
path: root/subversion/libsvn_ra_serf/xml.c
diff options
context:
space:
mode:
Diffstat (limited to 'subversion/libsvn_ra_serf/xml.c')
-rw-r--r--subversion/libsvn_ra_serf/xml.c587
1 files changed, 539 insertions, 48 deletions
diff --git a/subversion/libsvn_ra_serf/xml.c b/subversion/libsvn_ra_serf/xml.c
index ad8a1a1..a95eacc 100644
--- a/subversion/libsvn_ra_serf/xml.c
+++ b/subversion/libsvn_ra_serf/xml.c
@@ -24,11 +24,9 @@
#include <apr_uri.h>
-
-#include <expat.h>
-
#include <serf.h>
+#include "svn_hash.h"
#include "svn_pools.h"
#include "svn_ra.h"
#include "svn_dav.h"
@@ -37,57 +35,158 @@
#include "svn_config.h"
#include "svn_delta.h"
#include "svn_path.h"
+
#include "svn_private_config.h"
+#include "private/svn_string_private.h"
#include "ra_serf.h"
-void
-svn_ra_serf__define_ns(svn_ra_serf__ns_t **ns_list,
- const char **attrs,
- apr_pool_t *pool)
+struct svn_ra_serf__xml_context_t {
+ /* Current state information. */
+ svn_ra_serf__xml_estate_t *current;
+
+ /* If WAITING.NAMESPACE != NULL, wait for NAMESPACE:NAME element to be
+ closed before looking for transitions from CURRENT->STATE. */
+ svn_ra_serf__dav_props_t waiting;
+
+ /* The transition table. */
+ const svn_ra_serf__xml_transition_t *ttable;
+
+ /* The callback information. */
+ svn_ra_serf__xml_opened_t opened_cb;
+ svn_ra_serf__xml_closed_t closed_cb;
+ svn_ra_serf__xml_cdata_t cdata_cb;
+ void *baton;
+
+ /* Linked list of free states. */
+ svn_ra_serf__xml_estate_t *free_states;
+
+#ifdef SVN_DEBUG
+ /* Used to verify we are not re-entering a callback, specifically to
+ ensure SCRATCH_POOL is not cleared while an outer callback is
+ trying to use it. */
+ svn_boolean_t within_callback;
+#define START_CALLBACK(xmlctx) \
+ do { \
+ svn_ra_serf__xml_context_t *xmlctx__tmp = (xmlctx); \
+ SVN_ERR_ASSERT(!xmlctx__tmp->within_callback); \
+ xmlctx__tmp->within_callback = TRUE; \
+ } while (0)
+#define END_CALLBACK(xmlctx) ((xmlctx)->within_callback = FALSE)
+#else
+#define START_CALLBACK(xmlctx) /* empty */
+#define END_CALLBACK(xmlctx) /* empty */
+#endif /* SVN_DEBUG */
+
+ apr_pool_t *scratch_pool;
+
+};
+
+struct svn_ra_serf__xml_estate_t {
+ /* The current state value. */
+ int state;
+
+ /* The xml tag that opened this state. Waiting for the tag to close. */
+ svn_ra_serf__dav_props_t tag;
+
+ /* Should the CLOSED_CB function be called for custom processing when
+ this tag is closed? */
+ svn_boolean_t custom_close;
+
+ /* A pool may be constructed for this state. */
+ apr_pool_t *state_pool;
+
+ /* The namespaces extent for this state/element. This will start with
+ the parent's NS_LIST, and we will push new namespaces into our
+ local list. The parent will be unaffected by our locally-scoped data. */
+ svn_ra_serf__ns_t *ns_list;
+
+ /* Any collected attribute values. char * -> svn_string_t *. May be NULL
+ if no attributes have been collected. */
+ apr_hash_t *attrs;
+
+ /* Any collected cdata. May be NULL if no cdata is being collected. */
+ svn_stringbuf_t *cdata;
+
+ /* Previous/outer state. */
+ svn_ra_serf__xml_estate_t *prev;
+
+};
+
+
+static void
+define_namespaces(svn_ra_serf__ns_t **ns_list,
+ const char *const *attrs,
+ apr_pool_t *(*get_pool)(void *baton),
+ void *baton)
{
- const char **tmp_attrs = attrs;
+ const char *const *tmp_attrs = attrs;
- while (*tmp_attrs)
+ for (tmp_attrs = attrs; *tmp_attrs != NULL; tmp_attrs += 2)
{
if (strncmp(*tmp_attrs, "xmlns", 5) == 0)
{
- svn_ra_serf__ns_t *new_ns, *cur_ns;
- int found = 0;
+ const svn_ra_serf__ns_t *cur_ns;
+ svn_boolean_t found = FALSE;
+ const char *prefix;
+
+ /* The empty prefix, or a named-prefix. */
+ if (tmp_attrs[0][5] == ':')
+ prefix = &tmp_attrs[0][6];
+ else
+ prefix = "";
/* Have we already defined this ns previously? */
for (cur_ns = *ns_list; cur_ns; cur_ns = cur_ns->next)
{
- if (strcmp(cur_ns->namespace, tmp_attrs[0] + 6) == 0)
+ if (strcmp(cur_ns->namespace, prefix) == 0)
{
- found = 1;
+ found = TRUE;
break;
}
}
if (!found)
{
+ apr_pool_t *pool;
+ svn_ra_serf__ns_t *new_ns;
+
+ if (get_pool)
+ pool = get_pool(baton);
+ else
+ pool = baton;
new_ns = apr_palloc(pool, sizeof(*new_ns));
- new_ns->namespace = apr_pstrdup(pool, tmp_attrs[0] + 6);
+ new_ns->namespace = apr_pstrdup(pool, prefix);
new_ns->url = apr_pstrdup(pool, tmp_attrs[1]);
+ /* Push into the front of NS_LIST. Parent states will point
+ to later in the chain, so will be unaffected by
+ shadowing/other namespaces pushed onto NS_LIST. */
new_ns->next = *ns_list;
-
*ns_list = new_ns;
}
}
- tmp_attrs += 2;
}
}
+
+void
+svn_ra_serf__define_ns(svn_ra_serf__ns_t **ns_list,
+ const char *const *attrs,
+ apr_pool_t *result_pool)
+{
+ define_namespaces(ns_list, attrs, NULL /* get_pool */, result_pool);
+}
+
+
/*
* Look up NAME in the NS_LIST list for previously declared namespace
* definitions and return a DAV_PROPS_T-tuple that has values.
*/
void
svn_ra_serf__expand_ns(svn_ra_serf__dav_props_t *returned_prop_name,
- svn_ra_serf__ns_t *ns_list,
+ const svn_ra_serf__ns_t *ns_list,
const char *name)
{
const char *colon;
@@ -95,7 +194,7 @@ svn_ra_serf__expand_ns(svn_ra_serf__dav_props_t *returned_prop_name,
colon = strchr(name, ':');
if (colon)
{
- svn_ra_serf__ns_t *ns;
+ const svn_ra_serf__ns_t *ns;
for (ns = ns_list; ns; ns = ns->next)
{
@@ -107,42 +206,28 @@ svn_ra_serf__expand_ns(svn_ra_serf__dav_props_t *returned_prop_name,
}
}
}
-
- /* If there is no prefix, or if the prefix is not found, then the
- name is NOT within a namespace. */
- returned_prop_name->namespace = "";
- returned_prop_name->name = name;
-}
-
-void
-svn_ra_serf__expand_string(const char **cur, apr_size_t *cur_len,
- const char *new, apr_size_t new_len,
- apr_pool_t *pool)
-{
- if (!*cur)
- {
- *cur = apr_pstrmemdup(pool, new, new_len);
- *cur_len = new_len;
- }
else
{
- char *new_cur;
-
- /* append the data we received before. */
- new_cur = apr_palloc(pool, *cur_len + new_len + 1);
+ const svn_ra_serf__ns_t *ns;
- memcpy(new_cur, *cur, *cur_len);
- memcpy(new_cur + *cur_len, new, new_len);
-
- /* NULL-term our new string */
- new_cur[*cur_len + new_len] = '\0';
-
- /* update our length */
- *cur_len += new_len;
- *cur = new_cur;
+ for (ns = ns_list; ns; ns = ns->next)
+ {
+ if (! ns->namespace[0])
+ {
+ returned_prop_name->namespace = ns->url;
+ returned_prop_name->name = name;
+ return;
+ }
+ }
}
+
+ /* If the prefix is not found, then the name is NOT within a
+ namespace. */
+ returned_prop_name->namespace = "";
+ returned_prop_name->name = name;
}
+
#define XML_HEADER "<?xml version=\"1.0\" encoding=\"utf-8\"?>"
void
@@ -340,3 +425,409 @@ void svn_ra_serf__xml_pop_state(svn_ra_serf__xml_parser_t *parser)
cur_state->prev = parser->free_state;
parser->free_state = cur_state;
}
+
+
+/* Return a pool for XES to use for self-alloc (and other specifics). */
+static apr_pool_t *
+xes_pool(const svn_ra_serf__xml_estate_t *xes)
+{
+ /* Move up through parent states looking for one with a pool. This
+ will always terminate since the initial state has a pool. */
+ while (xes->state_pool == NULL)
+ xes = xes->prev;
+ return xes->state_pool;
+}
+
+
+static void
+ensure_pool(svn_ra_serf__xml_estate_t *xes)
+{
+ if (xes->state_pool == NULL)
+ xes->state_pool = svn_pool_create(xes_pool(xes));
+}
+
+
+/* This callback is used by define_namespaces() to wait until a pool is
+ required before constructing it. */
+static apr_pool_t *
+lazy_create_pool(void *baton)
+{
+ svn_ra_serf__xml_estate_t *xes = baton;
+
+ ensure_pool(xes);
+ return xes->state_pool;
+}
+
+void
+svn_ra_serf__xml_context_destroy(
+ svn_ra_serf__xml_context_t *xmlctx)
+{
+ svn_pool_destroy(xmlctx->scratch_pool);
+}
+
+svn_ra_serf__xml_context_t *
+svn_ra_serf__xml_context_create(
+ const svn_ra_serf__xml_transition_t *ttable,
+ svn_ra_serf__xml_opened_t opened_cb,
+ svn_ra_serf__xml_closed_t closed_cb,
+ svn_ra_serf__xml_cdata_t cdata_cb,
+ void *baton,
+ apr_pool_t *result_pool)
+{
+ svn_ra_serf__xml_context_t *xmlctx;
+ svn_ra_serf__xml_estate_t *xes;
+
+ xmlctx = apr_pcalloc(result_pool, sizeof(*xmlctx));
+ xmlctx->ttable = ttable;
+ xmlctx->opened_cb = opened_cb;
+ xmlctx->closed_cb = closed_cb;
+ xmlctx->cdata_cb = cdata_cb;
+ xmlctx->baton = baton;
+ xmlctx->scratch_pool = svn_pool_create(result_pool);
+
+ xes = apr_pcalloc(result_pool, sizeof(*xes));
+ /* XES->STATE == 0 */
+
+ /* Child states may use this pool to allocate themselves. If a child
+ needs to collect information, then it will construct a subpool and
+ will use that to allocate itself and its collected data. */
+ xes->state_pool = result_pool;
+
+ xmlctx->current = xes;
+
+ return xmlctx;
+}
+
+
+apr_hash_t *
+svn_ra_serf__xml_gather_since(svn_ra_serf__xml_estate_t *xes,
+ int stop_state)
+{
+ apr_hash_t *data;
+ apr_pool_t *pool;
+
+ ensure_pool(xes);
+ pool = xes->state_pool;
+
+ data = apr_hash_make(pool);
+
+ for (; xes != NULL; xes = xes->prev)
+ {
+ if (xes->attrs != NULL)
+ {
+ apr_hash_index_t *hi;
+
+ for (hi = apr_hash_first(pool, xes->attrs); hi;
+ hi = apr_hash_next(hi))
+ {
+ const void *key;
+ apr_ssize_t klen;
+ void *val;
+
+ /* Parent name/value lifetimes are at least as long as POOL. */
+ apr_hash_this(hi, &key, &klen, &val);
+ apr_hash_set(data, key, klen, val);
+ }
+ }
+
+ if (xes->state == stop_state)
+ break;
+ }
+
+ return data;
+}
+
+
+void
+svn_ra_serf__xml_note(svn_ra_serf__xml_estate_t *xes,
+ int state,
+ const char *name,
+ const char *value)
+{
+ svn_ra_serf__xml_estate_t *scan;
+
+ for (scan = xes; scan != NULL && scan->state != state; scan = scan->prev)
+ /* pass */ ;
+
+ SVN_ERR_ASSERT_NO_RETURN(scan != NULL);
+
+ /* Make sure the target state has a pool. */
+ ensure_pool(scan);
+
+ /* ... and attribute storage. */
+ if (scan->attrs == NULL)
+ scan->attrs = apr_hash_make(scan->state_pool);
+
+ /* In all likelihood, NAME is a string constant. But we can't really
+ be sure. And it isn't like we're storing a billion of these into
+ the state pool. */
+ svn_hash_sets(scan->attrs,
+ apr_pstrdup(scan->state_pool, name),
+ apr_pstrdup(scan->state_pool, value));
+}
+
+
+apr_pool_t *
+svn_ra_serf__xml_state_pool(svn_ra_serf__xml_estate_t *xes)
+{
+ /* If they asked for a pool, then ensure that we have one to provide. */
+ ensure_pool(xes);
+
+ return xes->state_pool;
+}
+
+
+svn_error_t *
+svn_ra_serf__xml_cb_start(svn_ra_serf__xml_context_t *xmlctx,
+ const char *raw_name,
+ const char *const *attrs)
+{
+ svn_ra_serf__xml_estate_t *current = xmlctx->current;
+ svn_ra_serf__dav_props_t elemname;
+ const svn_ra_serf__xml_transition_t *scan;
+ apr_pool_t *new_pool;
+ svn_ra_serf__xml_estate_t *new_xes;
+
+ /* If we're waiting for an element to close, then just ignore all
+ other element-opens. */
+ if (xmlctx->waiting.namespace != NULL)
+ return SVN_NO_ERROR;
+
+ /* Look for xmlns: attributes. Lazily create the state pool if any
+ were found. */
+ define_namespaces(&current->ns_list, attrs, lazy_create_pool, current);
+
+ svn_ra_serf__expand_ns(&elemname, current->ns_list, raw_name);
+
+ for (scan = xmlctx->ttable; scan->ns != NULL; ++scan)
+ {
+ if (scan->from_state != current->state)
+ continue;
+
+ /* Wildcard tag match. */
+ if (*scan->name == '*')
+ break;
+
+ /* Found a specific transition. */
+ if (strcmp(elemname.name, scan->name) == 0
+ && strcmp(elemname.namespace, scan->ns) == 0)
+ break;
+ }
+ if (scan->ns == NULL)
+ {
+ if (current->state == 0)
+ {
+ return svn_error_createf(
+ SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
+ _("XML Parsing failed: Unexpected root element '%s'"),
+ elemname.name);
+ }
+
+ xmlctx->waiting = elemname;
+ /* ### return? */
+ return SVN_NO_ERROR;
+ }
+
+ /* We should not be told to collect cdata if the closed_cb will not
+ be called. */
+ SVN_ERR_ASSERT(!scan->collect_cdata || scan->custom_close);
+
+ /* Found a transition. Make it happen. */
+
+ /* ### todo. push state */
+
+ /* ### how to use free states? */
+ /* This state should be allocated in the extent pool. If we will be
+ collecting information for this state, then construct a subpool.
+
+ ### potentially optimize away the subpool if none of the
+ ### attributes are present. subpools are cheap, tho... */
+ new_pool = xes_pool(current);
+ if (scan->collect_cdata || scan->collect_attrs[0])
+ {
+ new_pool = svn_pool_create(new_pool);
+
+ /* Prep the new state. */
+ new_xes = apr_pcalloc(new_pool, sizeof(*new_xes));
+ new_xes->state_pool = new_pool;
+
+ /* If we're supposed to collect cdata, then set up a buffer for
+ this. The existence of this buffer will instruct our cdata
+ callback to collect the cdata. */
+ if (scan->collect_cdata)
+ new_xes->cdata = svn_stringbuf_create_empty(new_pool);
+
+ if (scan->collect_attrs[0] != NULL)
+ {
+ const char *const *saveattr = &scan->collect_attrs[0];
+
+ new_xes->attrs = apr_hash_make(new_pool);
+ for (; *saveattr != NULL; ++saveattr)
+ {
+ const char *name;
+ const char *value;
+
+ if (**saveattr == '?')
+ {
+ name = *saveattr + 1;
+ value = svn_xml_get_attr_value(name, attrs);
+ }
+ else
+ {
+ name = *saveattr;
+ value = svn_xml_get_attr_value(name, attrs);
+ if (value == NULL)
+ return svn_error_createf(SVN_ERR_XML_ATTRIB_NOT_FOUND,
+ NULL,
+ _("Missing XML attribute: '%s'"),
+ name);
+ }
+
+ if (value)
+ svn_hash_sets(new_xes->attrs, name,
+ apr_pstrdup(new_pool, value));
+ }
+ }
+ }
+ else
+ {
+ /* Prep the new state. */
+ new_xes = apr_pcalloc(new_pool, sizeof(*new_xes));
+ /* STATE_POOL remains NULL. */
+ }
+
+ /* Some basic copies to set up the new estate. */
+ new_xes->state = scan->to_state;
+ new_xes->tag.name = apr_pstrdup(new_pool, elemname.name);
+ new_xes->tag.namespace = apr_pstrdup(new_pool, elemname.namespace);
+ new_xes->custom_close = scan->custom_close;
+
+ /* Start with the parent's namespace set. */
+ new_xes->ns_list = current->ns_list;
+
+ /* The new state is prepared. Make it current. */
+ new_xes->prev = current;
+ xmlctx->current = new_xes;
+
+ if (xmlctx->opened_cb)
+ {
+ START_CALLBACK(xmlctx);
+ SVN_ERR(xmlctx->opened_cb(new_xes, xmlctx->baton,
+ new_xes->state, &new_xes->tag,
+ xmlctx->scratch_pool));
+ END_CALLBACK(xmlctx);
+ svn_pool_clear(xmlctx->scratch_pool);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_ra_serf__xml_cb_end(svn_ra_serf__xml_context_t *xmlctx,
+ const char *raw_name)
+{
+ svn_ra_serf__xml_estate_t *xes = xmlctx->current;
+ svn_ra_serf__dav_props_t elemname;
+
+ svn_ra_serf__expand_ns(&elemname, xes->ns_list, raw_name);
+
+ if (xmlctx->waiting.namespace != NULL)
+ {
+ /* If this element is not the closer, then keep waiting... */
+ if (strcmp(elemname.name, xmlctx->waiting.name) != 0
+ || strcmp(elemname.namespace, xmlctx->waiting.namespace) != 0)
+ return SVN_NO_ERROR;
+
+ /* Found it. Stop waiting, and go back for more. */
+ xmlctx->waiting.namespace = NULL;
+ return SVN_NO_ERROR;
+ }
+
+ /* We should be looking at the same tag that opened the current state.
+
+ Unknown elements are simply skipped, so we wouldn't reach this check.
+
+ Known elements push a new state for a given tag. Some other elemname
+ would imply closing an ancestor tag (where did ours go?) or a spurious
+ tag closure. */
+ if (strcmp(elemname.name, xes->tag.name) != 0
+ || strcmp(elemname.namespace, xes->tag.namespace) != 0)
+ return svn_error_create(SVN_ERR_XML_MALFORMED, NULL,
+ _("The response contains invalid XML"));
+
+ if (xes->custom_close)
+ {
+ const svn_string_t *cdata;
+
+ if (xes->cdata)
+ {
+ cdata = svn_stringbuf__morph_into_string(xes->cdata);
+#ifdef SVN_DEBUG
+ /* We might toss the pool holding this structure, but it could also
+ be within a parent pool. In any case, for safety's sake, disable
+ the stringbuf against future Badness. */
+ xes->cdata->pool = NULL;
+#endif
+ }
+ else
+ cdata = NULL;
+
+ START_CALLBACK(xmlctx);
+ SVN_ERR(xmlctx->closed_cb(xes, xmlctx->baton, xes->state,
+ cdata, xes->attrs,
+ xmlctx->scratch_pool));
+ END_CALLBACK(xmlctx);
+ svn_pool_clear(xmlctx->scratch_pool);
+ }
+
+ /* Pop the state. */
+ xmlctx->current = xes->prev;
+
+ /* ### not everything should go on the free state list. XES may go
+ ### away with the state pool. */
+ xes->prev = xmlctx->free_states;
+ xmlctx->free_states = xes;
+
+ /* If there is a STATE_POOL, then toss it. This will get rid of as much
+ memory as possible. Potentially the XES (if we didn't create a pool
+ right away, then XES may be in a parent pool). */
+ if (xes->state_pool)
+ svn_pool_destroy(xes->state_pool);
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_ra_serf__xml_cb_cdata(svn_ra_serf__xml_context_t *xmlctx,
+ const char *data,
+ apr_size_t len)
+{
+ /* If we are waiting for a closing tag, then we are uninterested in
+ the cdata. Just return. */
+ if (xmlctx->waiting.namespace != NULL)
+ return SVN_NO_ERROR;
+
+ /* If the current state is collecting cdata, then copy the cdata. */
+ if (xmlctx->current->cdata != NULL)
+ {
+ svn_stringbuf_appendbytes(xmlctx->current->cdata, data, len);
+ }
+ /* ... else if a CDATA_CB has been supplied, then invoke it for
+ all states. */
+ else if (xmlctx->cdata_cb != NULL)
+ {
+ START_CALLBACK(xmlctx);
+ SVN_ERR(xmlctx->cdata_cb(xmlctx->current,
+ xmlctx->baton,
+ xmlctx->current->state,
+ data, len,
+ xmlctx->scratch_pool));
+ END_CALLBACK(xmlctx);
+ svn_pool_clear(xmlctx->scratch_pool);
+ }
+
+ return SVN_NO_ERROR;
+}
+