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