]> CyberLeo.Net >> Repos - FreeBSD/stable/10.git/blob - contrib/subversion/subversion/libsvn_ra_serf/xml.c
MFC r275385 (by bapt):
[FreeBSD/stable/10.git] / contrib / subversion / subversion / libsvn_ra_serf / xml.c
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 \f
26 #include <apr_uri.h>
27 #include <expat.h>
28 #include <serf.h>
29
30 #include "svn_hash.h"
31 #include "svn_pools.h"
32 #include "svn_ra.h"
33 #include "svn_dav.h"
34 #include "svn_xml.h"
35 #include "../libsvn_ra/ra_loader.h"
36 #include "svn_config.h"
37 #include "svn_delta.h"
38 #include "svn_path.h"
39
40 #include "svn_private_config.h"
41 #include "private/svn_string_private.h"
42
43 #include "ra_serf.h"
44 \f
45
46 /* Fix for older expat 1.95.x's that do not define
47  * XML_STATUS_OK/XML_STATUS_ERROR
48  */
49 #ifndef XML_STATUS_OK
50 #define XML_STATUS_OK    1
51 #define XML_STATUS_ERROR 0
52 #endif
53
54 #ifndef XML_VERSION_AT_LEAST
55 #define XML_VERSION_AT_LEAST(major,minor,patch)                  \
56 (((major) < XML_MAJOR_VERSION)                                       \
57  || ((major) == XML_MAJOR_VERSION && (minor) < XML_MINOR_VERSION)    \
58  || ((major) == XML_MAJOR_VERSION && (minor) == XML_MINOR_VERSION && \
59      (patch) <= XML_MICRO_VERSION))
60 #endif /* XML_VERSION_AT_LEAST */
61
62 /* Read/write chunks of this size into the spillbuf.  */
63 #define PARSE_CHUNK_SIZE 8000
64
65
66 struct svn_ra_serf__xml_context_t {
67   /* Current state information.  */
68   svn_ra_serf__xml_estate_t *current;
69
70   /* If WAITING >= then we are waiting for an element to close before
71      resuming events. The number stored here is the amount of nested
72      elements open. The Xml parser will make sure the document is well
73      formed. */
74   int waiting;
75
76   /* The transition table.  */
77   const svn_ra_serf__xml_transition_t *ttable;
78
79   /* The callback information.  */
80   svn_ra_serf__xml_opened_t opened_cb;
81   svn_ra_serf__xml_closed_t closed_cb;
82   svn_ra_serf__xml_cdata_t cdata_cb;
83   void *baton;
84
85   /* Linked list of free states.  */
86   svn_ra_serf__xml_estate_t *free_states;
87
88 #ifdef SVN_DEBUG
89   /* Used to verify we are not re-entering a callback, specifically to
90      ensure SCRATCH_POOL is not cleared while an outer callback is
91      trying to use it.  */
92   svn_boolean_t within_callback;
93 #define START_CALLBACK(xmlctx) \
94   do {                                                    \
95     svn_ra_serf__xml_context_t *xmlctx__tmp = (xmlctx);   \
96     SVN_ERR_ASSERT(!xmlctx__tmp->within_callback);        \
97     xmlctx__tmp->within_callback = TRUE;                  \
98   } while (0)
99 #define END_CALLBACK(xmlctx) ((xmlctx)->within_callback = FALSE)
100 #else
101 #define START_CALLBACK(xmlctx)  /* empty */
102 #define END_CALLBACK(xmlctx)  /* empty */
103 #endif /* SVN_DEBUG  */
104
105   apr_pool_t *scratch_pool;
106
107 };
108
109 /* Structure which represents an XML namespace. */
110 typedef struct svn_ra_serf__ns_t {
111   /* The assigned name. */
112   const char *xmlns;
113   /* The full URL for this namespace. */
114   const char *url;
115   /* The next namespace in our list. */
116   struct svn_ra_serf__ns_t *next;
117 } svn_ra_serf__ns_t;
118
119 struct svn_ra_serf__xml_estate_t {
120   /* The current state value.  */
121   int state;
122
123   /* The xml tag that opened this state. Waiting for the tag to close.  */
124   svn_ra_serf__dav_props_t tag;
125
126   /* Should the CLOSED_CB function be called for custom processing when
127      this tag is closed?  */
128   svn_boolean_t custom_close;
129
130   /* A pool may be constructed for this state.  */
131   apr_pool_t *state_pool;
132
133   /* The namespaces extent for this state/element. This will start with
134      the parent's NS_LIST, and we will push new namespaces into our
135      local list. The parent will be unaffected by our locally-scoped data. */
136   svn_ra_serf__ns_t *ns_list;
137
138   /* Any collected attribute values. char * -> svn_string_t *. May be NULL
139      if no attributes have been collected.  */
140   apr_hash_t *attrs;
141
142   /* Any collected cdata. May be NULL if no cdata is being collected.  */
143   svn_stringbuf_t *cdata;
144
145   /* Previous/outer state.  */
146   svn_ra_serf__xml_estate_t *prev;
147
148 };
149
150 struct expat_ctx_t {
151   svn_ra_serf__xml_context_t *xmlctx;
152   XML_Parser parser;
153   svn_ra_serf__handler_t *handler;
154   const int *expected_status;
155
156   svn_error_t *inner_error;
157
158   /* Do not use this pool for allocation. It is merely recorded for running
159      the cleanup handler.  */
160   apr_pool_t *cleanup_pool;
161 };
162
163
164 static void
165 define_namespaces(svn_ra_serf__ns_t **ns_list,
166                   const char *const *attrs,
167                   apr_pool_t *(*get_pool)(void *baton),
168                   void *baton)
169 {
170   const char *const *tmp_attrs = attrs;
171
172   for (tmp_attrs = attrs; *tmp_attrs != NULL; tmp_attrs += 2)
173     {
174       if (strncmp(*tmp_attrs, "xmlns", 5) == 0)
175         {
176           const svn_ra_serf__ns_t *cur_ns;
177           svn_boolean_t found = FALSE;
178           const char *prefix;
179
180           /* The empty prefix, or a named-prefix.  */
181           if (tmp_attrs[0][5] == ':')
182             prefix = &tmp_attrs[0][6];
183           else
184             prefix = "";
185
186           /* Have we already defined this ns previously? */
187           for (cur_ns = *ns_list; cur_ns; cur_ns = cur_ns->next)
188             {
189               if (strcmp(cur_ns->xmlns, prefix) == 0)
190                 {
191                   found = TRUE;
192                   break;
193                 }
194             }
195
196           if (!found)
197             {
198               apr_pool_t *pool;
199               svn_ra_serf__ns_t *new_ns;
200
201               if (get_pool)
202                 pool = get_pool(baton);
203               else
204                 pool = baton;
205               new_ns = apr_palloc(pool, sizeof(*new_ns));
206               new_ns->xmlns = apr_pstrdup(pool, prefix);
207               new_ns->url = apr_pstrdup(pool, tmp_attrs[1]);
208
209               /* Push into the front of NS_LIST. Parent states will point
210                  to later in the chain, so will be unaffected by
211                  shadowing/other namespaces pushed onto NS_LIST.  */
212               new_ns->next = *ns_list;
213               *ns_list = new_ns;
214             }
215         }
216     }
217 }
218
219 /*
220  * Look up @a name in the @a ns_list list for previously declared namespace
221  * definitions.
222  *
223  * Return (in @a *returned_prop_name) a #svn_ra_serf__dav_props_t tuple
224  * representing the expanded name.
225  */
226 static void
227 expand_ns(svn_ra_serf__dav_props_t *returned_prop_name,
228                        const svn_ra_serf__ns_t *ns_list,
229                        const char *name)
230 {
231   const char *colon;
232
233   colon = strchr(name, ':');
234   if (colon)
235     {
236       const svn_ra_serf__ns_t *ns;
237
238       for (ns = ns_list; ns; ns = ns->next)
239         {
240           if (strncmp(ns->xmlns, name, colon - name) == 0)
241             {
242               returned_prop_name->xmlns = ns->url;
243               returned_prop_name->name = colon + 1;
244               return;
245             }
246         }
247     }
248   else
249     {
250       const svn_ra_serf__ns_t *ns;
251
252       for (ns = ns_list; ns; ns = ns->next)
253         {
254           if (! ns->xmlns[0])
255             {
256               returned_prop_name->xmlns = ns->url;
257               returned_prop_name->name = name;
258               return;
259             }
260         }
261     }
262
263   /* If the prefix is not found, then the name is NOT within a
264      namespace.  */
265   returned_prop_name->xmlns = "";
266   returned_prop_name->name = name;
267 }
268
269
270 #define XML_HEADER "<?xml version=\"1.0\" encoding=\"utf-8\"?>"
271
272 void
273 svn_ra_serf__add_xml_header_buckets(serf_bucket_t *agg_bucket,
274                                     serf_bucket_alloc_t *bkt_alloc)
275 {
276   serf_bucket_t *tmp;
277
278   tmp = SERF_BUCKET_SIMPLE_STRING_LEN(XML_HEADER, sizeof(XML_HEADER) - 1,
279                                       bkt_alloc);
280   serf_bucket_aggregate_append(agg_bucket, tmp);
281 }
282
283 void
284 svn_ra_serf__add_open_tag_buckets(serf_bucket_t *agg_bucket,
285                                   serf_bucket_alloc_t *bkt_alloc,
286                                   const char *tag, ...)
287 {
288   va_list ap;
289   const char *key;
290   serf_bucket_t *tmp;
291
292   tmp = SERF_BUCKET_SIMPLE_STRING_LEN("<", 1, bkt_alloc);
293   serf_bucket_aggregate_append(agg_bucket, tmp);
294
295   tmp = SERF_BUCKET_SIMPLE_STRING(tag, bkt_alloc);
296   serf_bucket_aggregate_append(agg_bucket, tmp);
297
298   va_start(ap, tag);
299   while ((key = va_arg(ap, char *)) != NULL)
300     {
301       const char *val = va_arg(ap, const char *);
302       if (val)
303         {
304           tmp = SERF_BUCKET_SIMPLE_STRING_LEN(" ", 1, bkt_alloc);
305           serf_bucket_aggregate_append(agg_bucket, tmp);
306
307           tmp = SERF_BUCKET_SIMPLE_STRING(key, bkt_alloc);
308           serf_bucket_aggregate_append(agg_bucket, tmp);
309
310           tmp = SERF_BUCKET_SIMPLE_STRING_LEN("=\"", 2, bkt_alloc);
311           serf_bucket_aggregate_append(agg_bucket, tmp);
312
313           tmp = SERF_BUCKET_SIMPLE_STRING(val, bkt_alloc);
314           serf_bucket_aggregate_append(agg_bucket, tmp);
315
316           tmp = SERF_BUCKET_SIMPLE_STRING_LEN("\"", 1, bkt_alloc);
317           serf_bucket_aggregate_append(agg_bucket, tmp);
318         }
319     }
320   va_end(ap);
321
322   tmp = SERF_BUCKET_SIMPLE_STRING_LEN(">", 1, bkt_alloc);
323   serf_bucket_aggregate_append(agg_bucket, tmp);
324 }
325
326 void
327 svn_ra_serf__add_empty_tag_buckets(serf_bucket_t *agg_bucket,
328                                    serf_bucket_alloc_t *bkt_alloc,
329                                    const char *tag, ...)
330 {
331   va_list ap;
332   const char *key;
333   serf_bucket_t *tmp;
334
335   tmp = SERF_BUCKET_SIMPLE_STRING_LEN("<", 1, bkt_alloc);
336   serf_bucket_aggregate_append(agg_bucket, tmp);
337
338   tmp = SERF_BUCKET_SIMPLE_STRING(tag, bkt_alloc);
339   serf_bucket_aggregate_append(agg_bucket, tmp);
340
341   va_start(ap, tag);
342   while ((key = va_arg(ap, char *)) != NULL)
343     {
344       const char *val = va_arg(ap, const char *);
345       if (val)
346         {
347           tmp = SERF_BUCKET_SIMPLE_STRING_LEN(" ", 1, bkt_alloc);
348           serf_bucket_aggregate_append(agg_bucket, tmp);
349
350           tmp = SERF_BUCKET_SIMPLE_STRING(key, bkt_alloc);
351           serf_bucket_aggregate_append(agg_bucket, tmp);
352
353           tmp = SERF_BUCKET_SIMPLE_STRING_LEN("=\"", 2, bkt_alloc);
354           serf_bucket_aggregate_append(agg_bucket, tmp);
355
356           tmp = SERF_BUCKET_SIMPLE_STRING(val, bkt_alloc);
357           serf_bucket_aggregate_append(agg_bucket, tmp);
358
359           tmp = SERF_BUCKET_SIMPLE_STRING_LEN("\"", 1, bkt_alloc);
360           serf_bucket_aggregate_append(agg_bucket, tmp);
361         }
362     }
363   va_end(ap);
364
365   tmp = SERF_BUCKET_SIMPLE_STRING_LEN("/>", 2, bkt_alloc);
366   serf_bucket_aggregate_append(agg_bucket, tmp);
367 }
368
369 void
370 svn_ra_serf__add_close_tag_buckets(serf_bucket_t *agg_bucket,
371                                    serf_bucket_alloc_t *bkt_alloc,
372                                    const char *tag)
373 {
374   serf_bucket_t *tmp;
375
376   tmp = SERF_BUCKET_SIMPLE_STRING_LEN("</", 2, bkt_alloc);
377   serf_bucket_aggregate_append(agg_bucket, tmp);
378
379   tmp = SERF_BUCKET_SIMPLE_STRING(tag, bkt_alloc);
380   serf_bucket_aggregate_append(agg_bucket, tmp);
381
382   tmp = SERF_BUCKET_SIMPLE_STRING_LEN(">", 1, bkt_alloc);
383   serf_bucket_aggregate_append(agg_bucket, tmp);
384 }
385
386 void
387 svn_ra_serf__add_cdata_len_buckets(serf_bucket_t *agg_bucket,
388                                    serf_bucket_alloc_t *bkt_alloc,
389                                    const char *data, apr_size_t len)
390 {
391   const char *end = data + len;
392   const char *p = data, *q;
393   serf_bucket_t *tmp_bkt;
394
395   while (1)
396     {
397       /* Find a character which needs to be quoted and append bytes up
398          to that point.  Strictly speaking, '>' only needs to be
399          quoted if it follows "]]", but it's easier to quote it all
400          the time.
401
402          So, why are we escaping '\r' here?  Well, according to the
403          XML spec, '\r\n' gets converted to '\n' during XML parsing.
404          Also, any '\r' not followed by '\n' is converted to '\n'.  By
405          golly, if we say we want to escape a '\r', we want to make
406          sure it remains a '\r'!  */
407       q = p;
408       while (q < end && *q != '&' && *q != '<' && *q != '>' && *q != '\r')
409         q++;
410
411
412       tmp_bkt = SERF_BUCKET_SIMPLE_STRING_LEN(p, q - p, bkt_alloc);
413       serf_bucket_aggregate_append(agg_bucket, tmp_bkt);
414
415       /* We may already be a winner.  */
416       if (q == end)
417         break;
418
419       /* Append the entity reference for the character.  */
420       if (*q == '&')
421         {
422           tmp_bkt = SERF_BUCKET_SIMPLE_STRING_LEN("&amp;", sizeof("&amp;") - 1,
423                                                   bkt_alloc);
424           serf_bucket_aggregate_append(agg_bucket, tmp_bkt);
425         }
426       else if (*q == '<')
427         {
428           tmp_bkt = SERF_BUCKET_SIMPLE_STRING_LEN("&lt;", sizeof("&lt;") - 1,
429                                                   bkt_alloc);
430           serf_bucket_aggregate_append(agg_bucket, tmp_bkt);
431         }
432       else if (*q == '>')
433         {
434           tmp_bkt = SERF_BUCKET_SIMPLE_STRING_LEN("&gt;", sizeof("&gt;") - 1,
435                                                   bkt_alloc);
436           serf_bucket_aggregate_append(agg_bucket, tmp_bkt);
437         }
438       else if (*q == '\r')
439         {
440           tmp_bkt = SERF_BUCKET_SIMPLE_STRING_LEN("&#13;", sizeof("&#13;") - 1,
441                                                   bkt_alloc);
442           serf_bucket_aggregate_append(agg_bucket, tmp_bkt);
443         }
444
445       p = q + 1;
446     }
447 }
448
449 void svn_ra_serf__add_tag_buckets(serf_bucket_t *agg_bucket, const char *tag,
450                                   const char *value,
451                                   serf_bucket_alloc_t *bkt_alloc)
452 {
453   svn_ra_serf__add_open_tag_buckets(agg_bucket, bkt_alloc, tag, SVN_VA_NULL);
454
455   if (value)
456     {
457       svn_ra_serf__add_cdata_len_buckets(agg_bucket, bkt_alloc,
458                                          value, strlen(value));
459     }
460
461   svn_ra_serf__add_close_tag_buckets(agg_bucket, bkt_alloc, tag);
462 }
463
464 /* Return a pool for XES to use for self-alloc (and other specifics).  */
465 static apr_pool_t *
466 xes_pool(const svn_ra_serf__xml_estate_t *xes)
467 {
468   /* Move up through parent states looking for one with a pool. This
469      will always terminate since the initial state has a pool.  */
470   while (xes->state_pool == NULL)
471     xes = xes->prev;
472   return xes->state_pool;
473 }
474
475
476 static void
477 ensure_pool(svn_ra_serf__xml_estate_t *xes)
478 {
479   if (xes->state_pool == NULL)
480     xes->state_pool = svn_pool_create(xes_pool(xes));
481 }
482
483
484 /* This callback is used by define_namespaces() to wait until a pool is
485    required before constructing it.  */
486 static apr_pool_t *
487 lazy_create_pool(void *baton)
488 {
489   svn_ra_serf__xml_estate_t *xes = baton;
490
491   ensure_pool(xes);
492   return xes->state_pool;
493 }
494
495 svn_error_t *
496 svn_ra_serf__xml_context_done(svn_ra_serf__xml_context_t *xmlctx)
497 {
498   if (xmlctx->current->prev)
499     {
500       /* Probably unreachable as this would be an xml parser error */
501       return svn_error_createf(SVN_ERR_XML_MALFORMED, NULL,
502                                _("XML stream truncated: closing '%s' missing"),
503                                xmlctx->current->tag.name);
504     }
505   else if (! xmlctx->free_states)
506     {
507       /* If we have no items on the free_states list, we didn't push anything,
508          which tells us that we found an empty xml body */
509       const svn_ra_serf__xml_transition_t *scan;
510       const svn_ra_serf__xml_transition_t *document = NULL;
511       const char *msg;
512
513       for (scan = xmlctx->ttable; scan->ns != NULL; ++scan)
514         {
515           if (scan->from_state == XML_STATE_INITIAL)
516             {
517               if (document != NULL)
518                 {
519                   document = NULL; /* Multiple document elements defined */
520                   break;
521                 }
522               document = scan;
523             }
524         }
525
526       if (document)
527         msg = apr_psprintf(xmlctx->scratch_pool, "'%s' element not found",
528                             document->name);
529       else
530         msg = _("document element not found");
531
532       return svn_error_createf(SVN_ERR_XML_MALFORMED, NULL,
533                                _("XML stream truncated: %s"),
534                                msg);
535     }
536
537   svn_pool_destroy(xmlctx->scratch_pool);
538   return SVN_NO_ERROR;
539 }
540
541 svn_ra_serf__xml_context_t *
542 svn_ra_serf__xml_context_create(
543   const svn_ra_serf__xml_transition_t *ttable,
544   svn_ra_serf__xml_opened_t opened_cb,
545   svn_ra_serf__xml_closed_t closed_cb,
546   svn_ra_serf__xml_cdata_t cdata_cb,
547   void *baton,
548   apr_pool_t *result_pool)
549 {
550   svn_ra_serf__xml_context_t *xmlctx;
551   svn_ra_serf__xml_estate_t *xes;
552
553   xmlctx = apr_pcalloc(result_pool, sizeof(*xmlctx));
554   xmlctx->ttable = ttable;
555   xmlctx->opened_cb = opened_cb;
556   xmlctx->closed_cb = closed_cb;
557   xmlctx->cdata_cb = cdata_cb;
558   xmlctx->baton = baton;
559   xmlctx->scratch_pool = svn_pool_create(result_pool);
560
561   xes = apr_pcalloc(result_pool, sizeof(*xes));
562   /* XES->STATE == 0  */
563
564   /* Child states may use this pool to allocate themselves. If a child
565      needs to collect information, then it will construct a subpool and
566      will use that to allocate itself and its collected data.  */
567   xes->state_pool = result_pool;
568
569   xmlctx->current = xes;
570
571   return xmlctx;
572 }
573
574
575 apr_hash_t *
576 svn_ra_serf__xml_gather_since(svn_ra_serf__xml_estate_t *xes,
577                               int stop_state)
578 {
579   apr_hash_t *data;
580   apr_pool_t *pool;
581
582   ensure_pool(xes);
583   pool = xes->state_pool;
584
585   data = apr_hash_make(pool);
586
587   for (; xes != NULL; xes = xes->prev)
588     {
589       if (xes->attrs != NULL)
590         {
591           apr_hash_index_t *hi;
592
593           for (hi = apr_hash_first(pool, xes->attrs); hi;
594                hi = apr_hash_next(hi))
595             {
596               const void *key;
597               apr_ssize_t klen;
598               void *val;
599
600               /* Parent name/value lifetimes are at least as long as POOL.  */
601               apr_hash_this(hi, &key, &klen, &val);
602               apr_hash_set(data, key, klen, val);
603             }
604         }
605
606       if (xes->state == stop_state)
607         break;
608     }
609
610   return data;
611 }
612
613
614 void
615 svn_ra_serf__xml_note(svn_ra_serf__xml_estate_t *xes,
616                       int state,
617                       const char *name,
618                       const char *value)
619 {
620   svn_ra_serf__xml_estate_t *scan;
621
622   for (scan = xes; scan != NULL && scan->state != state; scan = scan->prev)
623     /* pass */ ;
624
625   SVN_ERR_ASSERT_NO_RETURN(scan != NULL);
626
627   /* Make sure the target state has a pool.  */
628   ensure_pool(scan);
629
630   /* ... and attribute storage.  */
631   if (scan->attrs == NULL)
632     scan->attrs = apr_hash_make(scan->state_pool);
633
634   /* In all likelihood, NAME is a string constant. But we can't really
635      be sure. And it isn't like we're storing a billion of these into
636      the state pool.  */
637   svn_hash_sets(scan->attrs,
638                 apr_pstrdup(scan->state_pool, name),
639                 apr_pstrdup(scan->state_pool, value));
640 }
641
642
643 apr_pool_t *
644 svn_ra_serf__xml_state_pool(svn_ra_serf__xml_estate_t *xes)
645 {
646   /* If they asked for a pool, then ensure that we have one to provide.  */
647   ensure_pool(xes);
648
649   return xes->state_pool;
650 }
651
652
653 static svn_error_t *
654 xml_cb_start(svn_ra_serf__xml_context_t *xmlctx,
655              const char *raw_name,
656              const char *const *attrs)
657 {
658   svn_ra_serf__xml_estate_t *current = xmlctx->current;
659   svn_ra_serf__dav_props_t elemname;
660   const svn_ra_serf__xml_transition_t *scan;
661   apr_pool_t *new_pool;
662   svn_ra_serf__xml_estate_t *new_xes;
663
664   /* If we're waiting for an element to close, then just ignore all
665      other element-opens.  */
666   if (xmlctx->waiting > 0)
667     {
668       xmlctx->waiting++;
669       return SVN_NO_ERROR;
670     }
671
672   /* Look for xmlns: attributes. Lazily create the state pool if any
673      were found.  */
674   define_namespaces(&current->ns_list, attrs, lazy_create_pool, current);
675
676   expand_ns(&elemname, current->ns_list, raw_name);
677
678   for (scan = xmlctx->ttable; scan->ns != NULL; ++scan)
679     {
680       if (scan->from_state != current->state)
681         continue;
682
683       /* Wildcard tag match.  */
684       if (*scan->name == '*')
685         break;
686
687       /* Found a specific transition.  */
688       if (strcmp(elemname.name, scan->name) == 0
689           && strcmp(elemname.xmlns, scan->ns) == 0)
690         break;
691     }
692   if (scan->ns == NULL)
693     {
694       if (current->state == XML_STATE_INITIAL)
695         {
696           return svn_error_createf(
697                         SVN_ERR_XML_UNEXPECTED_ELEMENT, NULL,
698                         _("XML Parsing failed: Unexpected root element '%s'"),
699                         elemname.name);
700         }
701
702       xmlctx->waiting++; /* Start waiting for the close tag */
703       return SVN_NO_ERROR;
704     }
705
706   /* We should not be told to collect cdata if the closed_cb will not
707      be called.  */
708   SVN_ERR_ASSERT(!scan->collect_cdata || scan->custom_close);
709
710   /* Found a transition. Make it happen.  */
711
712   /* ### todo. push state  */
713
714   /* ### how to use free states?  */
715   /* This state should be allocated in the extent pool. If we will be
716      collecting information for this state, then construct a subpool.
717
718      ### potentially optimize away the subpool if none of the
719      ### attributes are present. subpools are cheap, tho...  */
720   new_pool = xes_pool(current);
721   if (scan->collect_cdata || scan->collect_attrs[0])
722     {
723       new_pool = svn_pool_create(new_pool);
724
725       /* Prep the new state.  */
726       new_xes = apr_pcalloc(new_pool, sizeof(*new_xes));
727       new_xes->state_pool = new_pool;
728
729       /* If we're supposed to collect cdata, then set up a buffer for
730          this. The existence of this buffer will instruct our cdata
731          callback to collect the cdata.  */
732       if (scan->collect_cdata)
733         new_xes->cdata = svn_stringbuf_create_empty(new_pool);
734
735       if (scan->collect_attrs[0] != NULL)
736         {
737           const char *const *saveattr = &scan->collect_attrs[0];
738
739           new_xes->attrs = apr_hash_make(new_pool);
740           for (; *saveattr != NULL; ++saveattr)
741             {
742               const char *name;
743               const char *value;
744
745               if (**saveattr == '?')
746                 {
747                   name = *saveattr + 1;
748                   value = svn_xml_get_attr_value(name, attrs);
749                 }
750               else
751                 {
752                   name = *saveattr;
753                   value = svn_xml_get_attr_value(name, attrs);
754                   if (value == NULL)
755                     return svn_error_createf(
756                                 SVN_ERR_XML_ATTRIB_NOT_FOUND,
757                                 NULL,
758                                 _("Missing XML attribute '%s' on '%s' element"),
759                                 name, scan->name);
760                 }
761
762               if (value)
763                 svn_hash_sets(new_xes->attrs, name,
764                               apr_pstrdup(new_pool, value));
765             }
766         }
767     }
768   else
769     {
770       /* Prep the new state.  */
771       new_xes = apr_pcalloc(new_pool, sizeof(*new_xes));
772       /* STATE_POOL remains NULL.  */
773     }
774
775   /* Some basic copies to set up the new estate.  */
776   new_xes->state = scan->to_state;
777   new_xes->tag.name = apr_pstrdup(new_pool, elemname.name);
778   new_xes->tag.xmlns = apr_pstrdup(new_pool, elemname.xmlns);
779   new_xes->custom_close = scan->custom_close;
780
781   /* Start with the parent's namespace set.  */
782   new_xes->ns_list = current->ns_list;
783
784   /* The new state is prepared. Make it current.  */
785   new_xes->prev = current;
786   xmlctx->current = new_xes;
787
788   if (xmlctx->opened_cb)
789     {
790       START_CALLBACK(xmlctx);
791       SVN_ERR(xmlctx->opened_cb(new_xes, xmlctx->baton,
792                                 new_xes->state, &new_xes->tag,
793                                 xmlctx->scratch_pool));
794       END_CALLBACK(xmlctx);
795       svn_pool_clear(xmlctx->scratch_pool);
796     }
797
798   return SVN_NO_ERROR;
799 }
800
801
802 static svn_error_t *
803 xml_cb_end(svn_ra_serf__xml_context_t *xmlctx,
804            const char *raw_name)
805 {
806   svn_ra_serf__xml_estate_t *xes = xmlctx->current;
807
808   if (xmlctx->waiting > 0)
809     {
810       xmlctx->waiting--;
811       return SVN_NO_ERROR;
812     }
813
814   if (xes->custom_close)
815     {
816       const svn_string_t *cdata;
817
818       if (xes->cdata)
819         {
820           cdata = svn_stringbuf__morph_into_string(xes->cdata);
821 #ifdef SVN_DEBUG
822           /* We might toss the pool holding this structure, but it could also
823              be within a parent pool. In any case, for safety's sake, disable
824              the stringbuf against future Badness.  */
825           xes->cdata->pool = NULL;
826 #endif
827         }
828       else
829         cdata = NULL;
830
831       START_CALLBACK(xmlctx);
832       SVN_ERR(xmlctx->closed_cb(xes, xmlctx->baton, xes->state,
833                                 cdata, xes->attrs,
834                                 xmlctx->scratch_pool));
835       END_CALLBACK(xmlctx);
836       svn_pool_clear(xmlctx->scratch_pool);
837     }
838
839   /* Pop the state.  */
840   xmlctx->current = xes->prev;
841
842   /* ### not everything should go on the free state list. XES may go
843      ### away with the state pool.  */
844   xes->prev = xmlctx->free_states;
845   xmlctx->free_states = xes;
846
847   /* If there is a STATE_POOL, then toss it. This will get rid of as much
848      memory as possible. Potentially the XES (if we didn't create a pool
849      right away, then XES may be in a parent pool).  */
850   if (xes->state_pool)
851     svn_pool_destroy(xes->state_pool);
852
853   return SVN_NO_ERROR;
854 }
855
856
857 static svn_error_t *
858 xml_cb_cdata(svn_ra_serf__xml_context_t *xmlctx,
859              const char *data,
860              apr_size_t len)
861 {
862   /* If we are waiting for a closing tag, then we are uninterested in
863      the cdata. Just return.  */
864   if (xmlctx->waiting > 0)
865     return SVN_NO_ERROR;
866
867   /* If the current state is collecting cdata, then copy the cdata.  */
868   if (xmlctx->current->cdata != NULL)
869     {
870       svn_stringbuf_appendbytes(xmlctx->current->cdata, data, len);
871     }
872   /* ... else if a CDATA_CB has been supplied, then invoke it for
873      all states.  */
874   else if (xmlctx->cdata_cb != NULL)
875     {
876       START_CALLBACK(xmlctx);
877       SVN_ERR(xmlctx->cdata_cb(xmlctx->current,
878                                xmlctx->baton,
879                                xmlctx->current->state,
880                                data, len,
881                                xmlctx->scratch_pool));
882       END_CALLBACK(xmlctx);
883       svn_pool_clear(xmlctx->scratch_pool);
884     }
885
886   return SVN_NO_ERROR;
887 }
888
889 /* svn_error_t * wrapper around XML_Parse */
890 static APR_INLINE svn_error_t *
891 parse_xml(struct expat_ctx_t *ectx, const char *data, apr_size_t len, svn_boolean_t is_final)
892 {
893   int xml_status = XML_Parse(ectx->parser, data, (int)len, is_final);
894   const char *msg;
895   int xml_code;
896
897   if (xml_status == XML_STATUS_OK)
898     return ectx->inner_error;
899
900   xml_code = XML_GetErrorCode(ectx->parser);
901
902 #if XML_VERSION_AT_LEAST(1, 95, 8)
903   /* If we called XML_StopParser() expat will return an abort error. If we
904      have a better error stored we should ignore it as it will not help
905      the end-user to store it in the error chain. */
906   if (xml_code == XML_ERROR_ABORTED && ectx->inner_error)
907     return ectx->inner_error;
908 #endif
909
910   msg = XML_ErrorString(xml_code);
911
912   return svn_error_compose_create(
913             ectx->inner_error,
914             svn_error_create(SVN_ERR_RA_DAV_MALFORMED_DATA,
915                              svn_error_createf(SVN_ERR_XML_MALFORMED, NULL,
916                                                _("Malformed XML: %s"),
917                                                msg),
918                              _("The XML response contains invalid XML")));
919 }
920
921 /* Apr pool cleanup handler to release an XML_Parser in success and error
922    conditions */
923 static apr_status_t
924 xml_parser_cleanup(void *baton)
925 {
926   XML_Parser *xmlp = baton;
927
928   if (*xmlp)
929     {
930       (void) XML_ParserFree(*xmlp);
931       *xmlp = NULL;
932     }
933
934   return APR_SUCCESS;
935 }
936
937 /* Conforms to Expat's XML_StartElementHandler  */
938 static void
939 expat_start(void *userData, const char *raw_name, const char **attrs)
940 {
941   struct expat_ctx_t *ectx = userData;
942
943   if (ectx->inner_error != NULL)
944     return;
945
946   ectx->inner_error = svn_error_trace(xml_cb_start(ectx->xmlctx,
947                                                    raw_name, attrs));
948
949 #if XML_VERSION_AT_LEAST(1, 95, 8)
950   if (ectx->inner_error)
951     (void) XML_StopParser(ectx->parser, 0 /* resumable */);
952 #endif
953 }
954
955
956 /* Conforms to Expat's XML_EndElementHandler  */
957 static void
958 expat_end(void *userData, const char *raw_name)
959 {
960   struct expat_ctx_t *ectx = userData;
961
962   if (ectx->inner_error != NULL)
963     return;
964
965   ectx->inner_error = svn_error_trace(xml_cb_end(ectx->xmlctx, raw_name));
966
967 #if XML_VERSION_AT_LEAST(1, 95, 8)
968   if (ectx->inner_error)
969     (void) XML_StopParser(ectx->parser, 0 /* resumable */);
970 #endif
971 }
972
973
974 /* Conforms to Expat's XML_CharacterDataHandler  */
975 static void
976 expat_cdata(void *userData, const char *data, int len)
977 {
978   struct expat_ctx_t *ectx = userData;
979
980   if (ectx->inner_error != NULL)
981     return;
982
983   ectx->inner_error = svn_error_trace(xml_cb_cdata(ectx->xmlctx, data, len));
984
985 #if XML_VERSION_AT_LEAST(1, 95, 8)
986   if (ectx->inner_error)
987     (void) XML_StopParser(ectx->parser, 0 /* resumable */);
988 #endif
989 }
990
991
992 /* Implements svn_ra_serf__response_handler_t */
993 static svn_error_t *
994 expat_response_handler(serf_request_t *request,
995                        serf_bucket_t *response,
996                        void *baton,
997                        apr_pool_t *scratch_pool)
998 {
999   struct expat_ctx_t *ectx = baton;
1000   svn_boolean_t got_expected_status;
1001
1002   if (ectx->expected_status)
1003     {
1004       const int *status = ectx->expected_status;
1005       got_expected_status = FALSE;
1006
1007       while (*status && ectx->handler->sline.code != *status)
1008         status++;
1009
1010       got_expected_status = (*status) != 0;
1011     }
1012   else
1013     got_expected_status = (ectx->handler->sline.code == 200);
1014
1015   if (!ectx->handler->server_error
1016       && ((ectx->handler->sline.code < 200) || (ectx->handler->sline.code >= 300)
1017           || ! got_expected_status))
1018     {
1019       /* By deferring to expect_empty_body(), it will make a choice on
1020          how to handle the body. Whatever the decision, the core handler
1021          will take over, and we will not be called again.  */
1022
1023       /* ### This handles xml bodies as svn-errors (returned via serf context
1024          ### loop), but ignores non-xml errors.
1025
1026          Current code depends on this behavior and checks itself while other
1027          continues, and then verifies if work has been performed.
1028
1029          ### TODO: Make error checking consistent */
1030
1031       /* ### If !GOT_EXPECTED_STATUS, this should always produce an error */
1032       return svn_error_trace(svn_ra_serf__expect_empty_body(
1033                                request, response, ectx->handler,
1034                                scratch_pool));
1035     }
1036
1037   if (!ectx->parser)
1038     {
1039       ectx->parser = XML_ParserCreate(NULL);
1040       apr_pool_cleanup_register(ectx->cleanup_pool, &ectx->parser,
1041                                 xml_parser_cleanup, apr_pool_cleanup_null);
1042       XML_SetUserData(ectx->parser, ectx);
1043       XML_SetElementHandler(ectx->parser, expat_start, expat_end);
1044       XML_SetCharacterDataHandler(ectx->parser, expat_cdata);
1045     }
1046
1047   while (1)
1048     {
1049       apr_status_t status;
1050       const char *data;
1051       apr_size_t len;
1052       svn_error_t *err;
1053       svn_boolean_t at_eof = FALSE;
1054
1055       status = serf_bucket_read(response, PARSE_CHUNK_SIZE, &data, &len);
1056       if (SERF_BUCKET_READ_ERROR(status))
1057         return svn_ra_serf__wrap_err(status, NULL);
1058       else if (APR_STATUS_IS_EOF(status))
1059         at_eof = TRUE;
1060
1061       err = parse_xml(ectx, data, len, at_eof /* isFinal */);
1062
1063       if (at_eof || err)
1064         {
1065           /* Release xml parser state/tables. */
1066           apr_pool_cleanup_run(ectx->cleanup_pool, &ectx->parser,
1067                                xml_parser_cleanup);
1068         }
1069
1070       SVN_ERR(err);
1071
1072       /* The parsing went fine. What has the bucket told us?  */
1073       if (at_eof)
1074         {
1075           /* Make sure we actually got xml and clean up after parsing */
1076           SVN_ERR(svn_ra_serf__xml_context_done(ectx->xmlctx));
1077         }
1078
1079       if (status && !SERF_BUCKET_READ_ERROR(status))
1080         {
1081           return svn_ra_serf__wrap_err(status, NULL);
1082         }
1083     }
1084
1085   /* NOTREACHED */
1086 }
1087
1088
1089 svn_ra_serf__handler_t *
1090 svn_ra_serf__create_expat_handler(svn_ra_serf__session_t *session,
1091                                   svn_ra_serf__xml_context_t *xmlctx,
1092                                   const int *expected_status,
1093                                   apr_pool_t *result_pool)
1094 {
1095   svn_ra_serf__handler_t *handler;
1096   struct expat_ctx_t *ectx;
1097
1098   ectx = apr_pcalloc(result_pool, sizeof(*ectx));
1099   ectx->xmlctx = xmlctx;
1100   ectx->parser = NULL;
1101   ectx->expected_status = expected_status;
1102   ectx->cleanup_pool = result_pool;
1103
1104   handler = svn_ra_serf__create_handler(session, result_pool);
1105   handler->response_handler = expat_response_handler;
1106   handler->response_baton = ectx;
1107
1108   ectx->handler = handler;
1109
1110   return handler;
1111 }