1/*
2 * xml.c :  standard XML parsing routines for ra_serf
3 *
4 * ====================================================================
5 *    Licensed to the Apache Software Foundation (ASF) under one
6 *    or more contributor license agreements.  See the NOTICE file
7 *    distributed with this work for additional information
8 *    regarding copyright ownership.  The ASF licenses this file
9 *    to you under the Apache License, Version 2.0 (the
10 *    "License"); you may not use this file except in compliance
11 *    with the License.  You may obtain a copy of the License at
12 *
13 *      http://www.apache.org/licenses/LICENSE-2.0
14 *
15 *    Unless required by applicable law or agreed to in writing,
16 *    software distributed under the License is distributed on an
17 *    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
18 *    KIND, either express or implied.  See the License for the
19 *    specific language governing permissions and limitations
20 *    under the License.
21 * ====================================================================
22 */
23
24
25
26#include <apr_uri.h>
27#include <serf.h>
28
29#include "svn_hash.h"
30#include "svn_pools.h"
31#include "svn_ra.h"
32#include "svn_dav.h"
33#include "svn_xml.h"
34#include "../libsvn_ra/ra_loader.h"
35#include "svn_config.h"
36#include "svn_delta.h"
37#include "svn_path.h"
38
39#include "svn_private_config.h"
40#include "private/svn_string_private.h"
41
42#include "ra_serf.h"
43
44
45struct svn_ra_serf__xml_context_t {
46  /* Current state information.  */
47  svn_ra_serf__xml_estate_t *current;
48
49  /* If WAITING.NAMESPACE != NULL, wait for NAMESPACE:NAME element to be
50     closed before looking for transitions from CURRENT->STATE.  */
51  svn_ra_serf__dav_props_t waiting;
52
53  /* The transition table.  */
54  const svn_ra_serf__xml_transition_t *ttable;
55
56  /* The callback information.  */
57  svn_ra_serf__xml_opened_t opened_cb;
58  svn_ra_serf__xml_closed_t closed_cb;
59  svn_ra_serf__xml_cdata_t cdata_cb;
60  void *baton;
61
62  /* Linked list of free states.  */
63  svn_ra_serf__xml_estate_t *free_states;
64
65#ifdef SVN_DEBUG
66  /* Used to verify we are not re-entering a callback, specifically to
67     ensure SCRATCH_POOL is not cleared while an outer callback is
68     trying to use it.  */
69  svn_boolean_t within_callback;
70#define START_CALLBACK(xmlctx) \
71  do {                                                    \
72    svn_ra_serf__xml_context_t *xmlctx__tmp = (xmlctx);   \
73    SVN_ERR_ASSERT(!xmlctx__tmp->within_callback);        \
74    xmlctx__tmp->within_callback = TRUE;                  \
75  } while (0)
76#define END_CALLBACK(xmlctx) ((xmlctx)->within_callback = FALSE)
77#else
78#define START_CALLBACK(xmlctx)  /* empty */
79#define END_CALLBACK(xmlctx)  /* empty */
80#endif /* SVN_DEBUG  */
81
82  apr_pool_t *scratch_pool;
83
84};
85
86struct svn_ra_serf__xml_estate_t {
87  /* The current state value.  */
88  int state;
89
90  /* The xml tag that opened this state. Waiting for the tag to close.  */
91  svn_ra_serf__dav_props_t tag;
92
93  /* Should the CLOSED_CB function be called for custom processing when
94     this tag is closed?  */
95  svn_boolean_t custom_close;
96
97  /* A pool may be constructed for this state.  */
98  apr_pool_t *state_pool;
99
100  /* The namespaces extent for this state/element. This will start with
101     the parent's NS_LIST, and we will push new namespaces into our
102     local list. The parent will be unaffected by our locally-scoped data. */
103  svn_ra_serf__ns_t *ns_list;
104
105  /* Any collected attribute values. char * -> svn_string_t *. May be NULL
106     if no attributes have been collected.  */
107  apr_hash_t *attrs;
108
109  /* Any collected cdata. May be NULL if no cdata is being collected.  */
110  svn_stringbuf_t *cdata;
111
112  /* Previous/outer state.  */
113  svn_ra_serf__xml_estate_t *prev;
114
115};
116
117
118static void
119define_namespaces(svn_ra_serf__ns_t **ns_list,
120                  const char *const *attrs,
121                  apr_pool_t *(*get_pool)(void *baton),
122                  void *baton)
123{
124  const char *const *tmp_attrs = attrs;
125
126  for (tmp_attrs = attrs; *tmp_attrs != NULL; tmp_attrs += 2)
127    {
128      if (strncmp(*tmp_attrs, "xmlns", 5) == 0)
129        {
130          const svn_ra_serf__ns_t *cur_ns;
131          svn_boolean_t found = FALSE;
132          const char *prefix;
133
134          /* The empty prefix, or a named-prefix.  */
135          if (tmp_attrs[0][5] == ':')
136            prefix = &tmp_attrs[0][6];
137          else
138            prefix = "";
139
140          /* Have we already defined this ns previously? */
141          for (cur_ns = *ns_list; cur_ns; cur_ns = cur_ns->next)
142            {
143              if (strcmp(cur_ns->namespace, prefix) == 0)
144                {
145                  found = TRUE;
146                  break;
147                }
148            }
149
150          if (!found)
151            {
152              apr_pool_t *pool;
153              svn_ra_serf__ns_t *new_ns;
154
155              if (get_pool)
156                pool = get_pool(baton);
157              else
158                pool = baton;
159              new_ns = apr_palloc(pool, sizeof(*new_ns));
160              new_ns->namespace = apr_pstrdup(pool, prefix);
161              new_ns->url = apr_pstrdup(pool, tmp_attrs[1]);
162
163              /* Push into the front of NS_LIST. Parent states will point
164                 to later in the chain, so will be unaffected by
165                 shadowing/other namespaces pushed onto NS_LIST.  */
166              new_ns->next = *ns_list;
167              *ns_list = new_ns;
168            }
169        }
170    }
171}
172
173
174void
175svn_ra_serf__define_ns(svn_ra_serf__ns_t **ns_list,
176                       const char *const *attrs,
177                       apr_pool_t *result_pool)
178{
179  define_namespaces(ns_list, attrs, NULL /* get_pool */, result_pool);
180}
181
182
183/*
184 * Look up NAME in the NS_LIST list for previously declared namespace
185 * definitions and return a DAV_PROPS_T-tuple that has values.
186 */
187void
188svn_ra_serf__expand_ns(svn_ra_serf__dav_props_t *returned_prop_name,
189                       const svn_ra_serf__ns_t *ns_list,
190                       const char *name)
191{
192  const char *colon;
193
194  colon = strchr(name, ':');
195  if (colon)
196    {
197      const svn_ra_serf__ns_t *ns;
198
199      for (ns = ns_list; ns; ns = ns->next)
200        {
201          if (strncmp(ns->namespace, name, colon - name) == 0)
202            {
203              returned_prop_name->namespace = ns->url;
204              returned_prop_name->name = colon + 1;
205              return;
206            }
207        }
208    }
209  else
210    {
211      const svn_ra_serf__ns_t *ns;
212
213      for (ns = ns_list; ns; ns = ns->next)
214        {
215          if (! ns->namespace[0])
216            {
217              returned_prop_name->namespace = ns->url;
218              returned_prop_name->name = name;
219              return;
220            }
221        }
222    }
223
224  /* If the prefix is not found, then the name is NOT within a
225     namespace.  */
226  returned_prop_name->namespace = "";
227  returned_prop_name->name = name;
228}
229
230
231#define XML_HEADER "<?xml version=\"1.0\" encoding=\"utf-8\"?>"
232
233void
234svn_ra_serf__add_xml_header_buckets(serf_bucket_t *agg_bucket,
235                                    serf_bucket_alloc_t *bkt_alloc)
236{
237  serf_bucket_t *tmp;
238
239  tmp = SERF_BUCKET_SIMPLE_STRING_LEN(XML_HEADER, sizeof(XML_HEADER) - 1,
240                                      bkt_alloc);
241  serf_bucket_aggregate_append(agg_bucket, tmp);
242}
243
244void
245svn_ra_serf__add_open_tag_buckets(serf_bucket_t *agg_bucket,
246                                  serf_bucket_alloc_t *bkt_alloc,
247                                  const char *tag, ...)
248{
249  va_list ap;
250  const char *key;
251  serf_bucket_t *tmp;
252
253  tmp = SERF_BUCKET_SIMPLE_STRING_LEN("<", 1, bkt_alloc);
254  serf_bucket_aggregate_append(agg_bucket, tmp);
255
256  tmp = SERF_BUCKET_SIMPLE_STRING(tag, bkt_alloc);
257  serf_bucket_aggregate_append(agg_bucket, tmp);
258
259  va_start(ap, tag);
260  while ((key = va_arg(ap, char *)) != NULL)
261    {
262      const char *val = va_arg(ap, const char *);
263      if (val)
264        {
265          tmp = SERF_BUCKET_SIMPLE_STRING_LEN(" ", 1, bkt_alloc);
266          serf_bucket_aggregate_append(agg_bucket, tmp);
267
268          tmp = SERF_BUCKET_SIMPLE_STRING(key, bkt_alloc);
269          serf_bucket_aggregate_append(agg_bucket, tmp);
270
271          tmp = SERF_BUCKET_SIMPLE_STRING_LEN("=\"", 2, bkt_alloc);
272          serf_bucket_aggregate_append(agg_bucket, tmp);
273
274          tmp = SERF_BUCKET_SIMPLE_STRING(val, bkt_alloc);
275          serf_bucket_aggregate_append(agg_bucket, tmp);
276
277          tmp = SERF_BUCKET_SIMPLE_STRING_LEN("\"", 1, bkt_alloc);
278          serf_bucket_aggregate_append(agg_bucket, tmp);
279        }
280    }
281  va_end(ap);
282
283  tmp = SERF_BUCKET_SIMPLE_STRING_LEN(">", 1, bkt_alloc);
284  serf_bucket_aggregate_append(agg_bucket, tmp);
285}
286
287void
288svn_ra_serf__add_close_tag_buckets(serf_bucket_t *agg_bucket,
289                                   serf_bucket_alloc_t *bkt_alloc,
290                                   const char *tag)
291{
292  serf_bucket_t *tmp;
293
294  tmp = SERF_BUCKET_SIMPLE_STRING_LEN("</", 2, bkt_alloc);
295  serf_bucket_aggregate_append(agg_bucket, tmp);
296
297  tmp = SERF_BUCKET_SIMPLE_STRING(tag, bkt_alloc);
298  serf_bucket_aggregate_append(agg_bucket, tmp);
299
300  tmp = SERF_BUCKET_SIMPLE_STRING_LEN(">", 1, bkt_alloc);
301  serf_bucket_aggregate_append(agg_bucket, tmp);
302}
303
304void
305svn_ra_serf__add_cdata_len_buckets(serf_bucket_t *agg_bucket,
306                                   serf_bucket_alloc_t *bkt_alloc,
307                                   const char *data, apr_size_t len)
308{
309  const char *end = data + len;
310  const char *p = data, *q;
311  serf_bucket_t *tmp_bkt;
312
313  while (1)
314    {
315      /* Find a character which needs to be quoted and append bytes up
316         to that point.  Strictly speaking, '>' only needs to be
317         quoted if it follows "]]", but it's easier to quote it all
318         the time.
319
320         So, why are we escaping '\r' here?  Well, according to the
321         XML spec, '\r\n' gets converted to '\n' during XML parsing.
322         Also, any '\r' not followed by '\n' is converted to '\n'.  By
323         golly, if we say we want to escape a '\r', we want to make
324         sure it remains a '\r'!  */
325      q = p;
326      while (q < end && *q != '&' && *q != '<' && *q != '>' && *q != '\r')
327        q++;
328
329
330      tmp_bkt = SERF_BUCKET_SIMPLE_STRING_LEN(p, q - p, bkt_alloc);
331      serf_bucket_aggregate_append(agg_bucket, tmp_bkt);
332
333      /* We may already be a winner.  */
334      if (q == end)
335        break;
336
337      /* Append the entity reference for the character.  */
338      if (*q == '&')
339        {
340          tmp_bkt = SERF_BUCKET_SIMPLE_STRING_LEN("&amp;", sizeof("&amp;") - 1,
341                                                  bkt_alloc);
342          serf_bucket_aggregate_append(agg_bucket, tmp_bkt);
343        }
344      else if (*q == '<')
345        {
346          tmp_bkt = SERF_BUCKET_SIMPLE_STRING_LEN("&lt;", sizeof("&lt;") - 1,
347                                                  bkt_alloc);
348          serf_bucket_aggregate_append(agg_bucket, tmp_bkt);
349        }
350      else if (*q == '>')
351        {
352          tmp_bkt = SERF_BUCKET_SIMPLE_STRING_LEN("&gt;", sizeof("&gt;") - 1,
353                                                  bkt_alloc);
354          serf_bucket_aggregate_append(agg_bucket, tmp_bkt);
355        }
356      else if (*q == '\r')
357        {
358          tmp_bkt = SERF_BUCKET_SIMPLE_STRING_LEN("&#13;", sizeof("&#13;") - 1,
359                                                  bkt_alloc);
360          serf_bucket_aggregate_append(agg_bucket, tmp_bkt);
361        }
362
363      p = q + 1;
364    }
365}
366
367void svn_ra_serf__add_tag_buckets(serf_bucket_t *agg_bucket, const char *tag,
368                                  const char *value,
369                                  serf_bucket_alloc_t *bkt_alloc)
370{
371  svn_ra_serf__add_open_tag_buckets(agg_bucket, bkt_alloc, tag, NULL);
372
373  if (value)
374    {
375      svn_ra_serf__add_cdata_len_buckets(agg_bucket, bkt_alloc,
376                                         value, strlen(value));
377    }
378
379  svn_ra_serf__add_close_tag_buckets(agg_bucket, bkt_alloc, tag);
380}
381
382void
383svn_ra_serf__xml_push_state(svn_ra_serf__xml_parser_t *parser,
384                            int state)
385{
386  svn_ra_serf__xml_state_t *new_state;
387
388  if (!parser->free_state)
389    {
390      new_state = apr_palloc(parser->pool, sizeof(*new_state));
391      new_state->pool = svn_pool_create(parser->pool);
392    }
393  else
394    {
395      new_state = parser->free_state;
396      parser->free_state = parser->free_state->prev;
397
398      svn_pool_clear(new_state->pool);
399    }
400
401  if (parser->state)
402    {
403      new_state->private = parser->state->private;
404      new_state->ns_list = parser->state->ns_list;
405    }
406  else
407    {
408      new_state->private = NULL;
409      new_state->ns_list = NULL;
410    }
411
412  new_state->current_state = state;
413
414  /* Add it to the state chain. */
415  new_state->prev = parser->state;
416  parser->state = new_state;
417}
418
419void svn_ra_serf__xml_pop_state(svn_ra_serf__xml_parser_t *parser)
420{
421  svn_ra_serf__xml_state_t *cur_state;
422
423  cur_state = parser->state;
424  parser->state = cur_state->prev;
425  cur_state->prev = parser->free_state;
426  parser->free_state = cur_state;
427}
428
429
430/* Return a pool for XES to use for self-alloc (and other specifics).  */
431static apr_pool_t *
432xes_pool(const svn_ra_serf__xml_estate_t *xes)
433{
434  /* Move up through parent states looking for one with a pool. This
435     will always terminate since the initial state has a pool.  */
436  while (xes->state_pool == NULL)
437    xes = xes->prev;
438  return xes->state_pool;
439}
440
441
442static void
443ensure_pool(svn_ra_serf__xml_estate_t *xes)
444{
445  if (xes->state_pool == NULL)
446    xes->state_pool = svn_pool_create(xes_pool(xes));
447}
448
449
450/* This callback is used by define_namespaces() to wait until a pool is
451   required before constructing it.  */
452static apr_pool_t *
453lazy_create_pool(void *baton)
454{
455  svn_ra_serf__xml_estate_t *xes = baton;
456
457  ensure_pool(xes);
458  return xes->state_pool;
459}
460
461void
462svn_ra_serf__xml_context_destroy(
463  svn_ra_serf__xml_context_t *xmlctx)
464{
465  svn_pool_destroy(xmlctx->scratch_pool);
466}
467
468svn_ra_serf__xml_context_t *
469svn_ra_serf__xml_context_create(
470  const svn_ra_serf__xml_transition_t *ttable,
471  svn_ra_serf__xml_opened_t opened_cb,
472  svn_ra_serf__xml_closed_t closed_cb,
473  svn_ra_serf__xml_cdata_t cdata_cb,
474  void *baton,
475  apr_pool_t *result_pool)
476{
477  svn_ra_serf__xml_context_t *xmlctx;
478  svn_ra_serf__xml_estate_t *xes;
479
480  xmlctx = apr_pcalloc(result_pool, sizeof(*xmlctx));
481  xmlctx->ttable = ttable;
482  xmlctx->opened_cb = opened_cb;
483  xmlctx->closed_cb = closed_cb;
484  xmlctx->cdata_cb = cdata_cb;
485  xmlctx->baton = baton;
486  xmlctx->scratch_pool = svn_pool_create(result_pool);
487
488  xes = apr_pcalloc(result_pool, sizeof(*xes));
489  /* XES->STATE == 0  */
490
491  /* Child states may use this pool to allocate themselves. If a child
492     needs to collect information, then it will construct a subpool and
493     will use that to allocate itself and its collected data.  */
494  xes->state_pool = result_pool;
495
496  xmlctx->current = xes;
497
498  return xmlctx;
499}
500
501
502apr_hash_t *
503svn_ra_serf__xml_gather_since(svn_ra_serf__xml_estate_t *xes,
504                              int stop_state)
505{
506  apr_hash_t *data;
507  apr_pool_t *pool;
508
509  ensure_pool(xes);
510  pool = xes->state_pool;
511
512  data = apr_hash_make(pool);
513
514  for (; xes != NULL; xes = xes->prev)
515    {
516      if (xes->attrs != NULL)
517        {
518          apr_hash_index_t *hi;
519
520          for (hi = apr_hash_first(pool, xes->attrs); hi;
521               hi = apr_hash_next(hi))
522            {
523              const void *key;
524              apr_ssize_t klen;
525              void *val;
526
527              /* Parent name/value lifetimes are at least as long as POOL.  */
528              apr_hash_this(hi, &key, &klen, &val);
529              apr_hash_set(data, key, klen, val);
530            }
531        }
532
533      if (xes->state == stop_state)
534        break;
535    }
536
537  return data;
538}
539
540
541void
542svn_ra_serf__xml_note(svn_ra_serf__xml_estate_t *xes,
543                      int state,
544                      const char *name,
545                      const char *value)
546{
547  svn_ra_serf__xml_estate_t *scan;
548
549  for (scan = xes; scan != NULL && scan->state != state; scan = scan->prev)
550    /* pass */ ;
551
552  SVN_ERR_ASSERT_NO_RETURN(scan != NULL);
553
554  /* Make sure the target state has a pool.  */
555  ensure_pool(scan);
556
557  /* ... and attribute storage.  */
558  if (scan->attrs == NULL)
559    scan->attrs = apr_hash_make(scan->state_pool);
560
561  /* In all likelihood, NAME is a string constant. But we can't really
562     be sure. And it isn't like we're storing a billion of these into
563     the state pool.  */
564  svn_hash_sets(scan->attrs,
565                apr_pstrdup(scan->state_pool, name),
566                apr_pstrdup(scan->state_pool, value));
567}
568
569
570apr_pool_t *
571svn_ra_serf__xml_state_pool(svn_ra_serf__xml_estate_t *xes)
572{
573  /* If they asked for a pool, then ensure that we have one to provide.  */
574  ensure_pool(xes);
575
576  return xes->state_pool;
577}
578
579
580svn_error_t *
581svn_ra_serf__xml_cb_start(svn_ra_serf__xml_context_t *xmlctx,
582                          const char *raw_name,
583                          const char *const *attrs)
584{
585  svn_ra_serf__xml_estate_t *current = xmlctx->current;
586  svn_ra_serf__dav_props_t elemname;
587  const svn_ra_serf__xml_transition_t *scan;
588  apr_pool_t *new_pool;
589  svn_ra_serf__xml_estate_t *new_xes;
590
591  /* If we're waiting for an element to close, then just ignore all
592     other element-opens.  */
593  if (xmlctx->waiting.namespace != NULL)
594    return SVN_NO_ERROR;
595
596  /* Look for xmlns: attributes. Lazily create the state pool if any
597     were found.  */
598  define_namespaces(&current->ns_list, attrs, lazy_create_pool, current);
599
600  svn_ra_serf__expand_ns(&elemname, current->ns_list, raw_name);
601
602  for (scan = xmlctx->ttable; scan->ns != NULL; ++scan)
603    {
604      if (scan->from_state != current->state)
605        continue;
606
607      /* Wildcard tag match.  */
608      if (*scan->name == '*')
609        break;
610
611      /* Found a specific transition.  */
612      if (strcmp(elemname.name, scan->name) == 0
613          && strcmp(elemname.namespace, scan->ns) == 0)
614        break;
615    }
616  if (scan->ns == NULL)
617    {
618      if (current->state == 0)
619        {
620          return svn_error_createf(
621                        SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
622                        _("XML Parsing failed: Unexpected root element '%s'"),
623                        elemname.name);
624        }
625
626      xmlctx->waiting = elemname;
627      /* ### return?  */
628      return SVN_NO_ERROR;
629    }
630
631  /* We should not be told to collect cdata if the closed_cb will not
632     be called.  */
633  SVN_ERR_ASSERT(!scan->collect_cdata || scan->custom_close);
634
635  /* Found a transition. Make it happen.  */
636
637  /* ### todo. push state  */
638
639  /* ### how to use free states?  */
640  /* This state should be allocated in the extent pool. If we will be
641     collecting information for this state, then construct a subpool.
642
643     ### potentially optimize away the subpool if none of the
644     ### attributes are present. subpools are cheap, tho...  */
645  new_pool = xes_pool(current);
646  if (scan->collect_cdata || scan->collect_attrs[0])
647    {
648      new_pool = svn_pool_create(new_pool);
649
650      /* Prep the new state.  */
651      new_xes = apr_pcalloc(new_pool, sizeof(*new_xes));
652      new_xes->state_pool = new_pool;
653
654      /* If we're supposed to collect cdata, then set up a buffer for
655         this. The existence of this buffer will instruct our cdata
656         callback to collect the cdata.  */
657      if (scan->collect_cdata)
658        new_xes->cdata = svn_stringbuf_create_empty(new_pool);
659
660      if (scan->collect_attrs[0] != NULL)
661        {
662          const char *const *saveattr = &scan->collect_attrs[0];
663
664          new_xes->attrs = apr_hash_make(new_pool);
665          for (; *saveattr != NULL; ++saveattr)
666            {
667              const char *name;
668              const char *value;
669
670              if (**saveattr == '?')
671                {
672                  name = *saveattr + 1;
673                  value = svn_xml_get_attr_value(name, attrs);
674                }
675              else
676                {
677                  name = *saveattr;
678                  value = svn_xml_get_attr_value(name, attrs);
679                  if (value == NULL)
680                    return svn_error_createf(SVN_ERR_XML_ATTRIB_NOT_FOUND,
681                                             NULL,
682                                             _("Missing XML attribute: '%s'"),
683                                             name);
684                }
685
686              if (value)
687                svn_hash_sets(new_xes->attrs, name,
688                              apr_pstrdup(new_pool, value));
689            }
690        }
691    }
692  else
693    {
694      /* Prep the new state.  */
695      new_xes = apr_pcalloc(new_pool, sizeof(*new_xes));
696      /* STATE_POOL remains NULL.  */
697    }
698
699  /* Some basic copies to set up the new estate.  */
700  new_xes->state = scan->to_state;
701  new_xes->tag.name = apr_pstrdup(new_pool, elemname.name);
702  new_xes->tag.namespace = apr_pstrdup(new_pool, elemname.namespace);
703  new_xes->custom_close = scan->custom_close;
704
705  /* Start with the parent's namespace set.  */
706  new_xes->ns_list = current->ns_list;
707
708  /* The new state is prepared. Make it current.  */
709  new_xes->prev = current;
710  xmlctx->current = new_xes;
711
712  if (xmlctx->opened_cb)
713    {
714      START_CALLBACK(xmlctx);
715      SVN_ERR(xmlctx->opened_cb(new_xes, xmlctx->baton,
716                                new_xes->state, &new_xes->tag,
717                                xmlctx->scratch_pool));
718      END_CALLBACK(xmlctx);
719      svn_pool_clear(xmlctx->scratch_pool);
720    }
721
722  return SVN_NO_ERROR;
723}
724
725
726svn_error_t *
727svn_ra_serf__xml_cb_end(svn_ra_serf__xml_context_t *xmlctx,
728                        const char *raw_name)
729{
730  svn_ra_serf__xml_estate_t *xes = xmlctx->current;
731  svn_ra_serf__dav_props_t elemname;
732
733  svn_ra_serf__expand_ns(&elemname, xes->ns_list, raw_name);
734
735  if (xmlctx->waiting.namespace != NULL)
736    {
737      /* If this element is not the closer, then keep waiting... */
738      if (strcmp(elemname.name, xmlctx->waiting.name) != 0
739          || strcmp(elemname.namespace, xmlctx->waiting.namespace) != 0)
740        return SVN_NO_ERROR;
741
742      /* Found it. Stop waiting, and go back for more.  */
743      xmlctx->waiting.namespace = NULL;
744      return SVN_NO_ERROR;
745    }
746
747  /* We should be looking at the same tag that opened the current state.
748
749     Unknown elements are simply skipped, so we wouldn't reach this check.
750
751     Known elements push a new state for a given tag. Some other elemname
752     would imply closing an ancestor tag (where did ours go?) or a spurious
753     tag closure.  */
754  if (strcmp(elemname.name, xes->tag.name) != 0
755      || strcmp(elemname.namespace, xes->tag.namespace) != 0)
756    return svn_error_create(SVN_ERR_XML_MALFORMED, NULL,
757                            _("The response contains invalid XML"));
758
759  if (xes->custom_close)
760    {
761      const svn_string_t *cdata;
762
763      if (xes->cdata)
764        {
765          cdata = svn_stringbuf__morph_into_string(xes->cdata);
766#ifdef SVN_DEBUG
767          /* We might toss the pool holding this structure, but it could also
768             be within a parent pool. In any case, for safety's sake, disable
769             the stringbuf against future Badness.  */
770          xes->cdata->pool = NULL;
771#endif
772        }
773      else
774        cdata = NULL;
775
776      START_CALLBACK(xmlctx);
777      SVN_ERR(xmlctx->closed_cb(xes, xmlctx->baton, xes->state,
778                                cdata, xes->attrs,
779                                xmlctx->scratch_pool));
780      END_CALLBACK(xmlctx);
781      svn_pool_clear(xmlctx->scratch_pool);
782    }
783
784  /* Pop the state.  */
785  xmlctx->current = xes->prev;
786
787  /* ### not everything should go on the free state list. XES may go
788     ### away with the state pool.  */
789  xes->prev = xmlctx->free_states;
790  xmlctx->free_states = xes;
791
792  /* If there is a STATE_POOL, then toss it. This will get rid of as much
793     memory as possible. Potentially the XES (if we didn't create a pool
794     right away, then XES may be in a parent pool).  */
795  if (xes->state_pool)
796    svn_pool_destroy(xes->state_pool);
797
798  return SVN_NO_ERROR;
799}
800
801
802svn_error_t *
803svn_ra_serf__xml_cb_cdata(svn_ra_serf__xml_context_t *xmlctx,
804                          const char *data,
805                          apr_size_t len)
806{
807  /* If we are waiting for a closing tag, then we are uninterested in
808     the cdata. Just return.  */
809  if (xmlctx->waiting.namespace != NULL)
810    return SVN_NO_ERROR;
811
812  /* If the current state is collecting cdata, then copy the cdata.  */
813  if (xmlctx->current->cdata != NULL)
814    {
815      svn_stringbuf_appendbytes(xmlctx->current->cdata, data, len);
816    }
817  /* ... else if a CDATA_CB has been supplied, then invoke it for
818     all states.  */
819  else if (xmlctx->cdata_cb != NULL)
820    {
821      START_CALLBACK(xmlctx);
822      SVN_ERR(xmlctx->cdata_cb(xmlctx->current,
823                               xmlctx->baton,
824                               xmlctx->current->state,
825                               data, len,
826                               xmlctx->scratch_pool));
827      END_CALLBACK(xmlctx);
828      svn_pool_clear(xmlctx->scratch_pool);
829    }
830
831  return SVN_NO_ERROR;
832}
833
834