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 <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 
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
define_namespaces(svn_ra_serf__ns_t ** ns_list,const char * const * attrs,apr_pool_t * (* get_pool)(void * baton),void * baton)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
expand_ns(svn_ra_serf__dav_props_t * returned_prop_name,const svn_ra_serf__ns_t * ns_list,const char * name)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
svn_ra_serf__add_xml_header_buckets(serf_bucket_t * agg_bucket,serf_bucket_alloc_t * bkt_alloc)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
svn_ra_serf__add_open_tag_buckets(serf_bucket_t * agg_bucket,serf_bucket_alloc_t * bkt_alloc,const char * tag,...)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
svn_ra_serf__add_empty_tag_buckets(serf_bucket_t * agg_bucket,serf_bucket_alloc_t * bkt_alloc,const char * tag,...)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
svn_ra_serf__add_close_tag_buckets(serf_bucket_t * agg_bucket,serf_bucket_alloc_t * bkt_alloc,const char * tag)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
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)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 
svn_ra_serf__add_tag_buckets(serf_bucket_t * agg_bucket,const char * tag,const char * value,serf_bucket_alloc_t * bkt_alloc)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 *
xes_pool(const svn_ra_serf__xml_estate_t * xes)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
ensure_pool(svn_ra_serf__xml_estate_t * xes)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 *
lazy_create_pool(void * baton)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 *
svn_ra_serf__xml_context_done(svn_ra_serf__xml_context_t * xmlctx)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 *
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)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 *
svn_ra_serf__xml_gather_since(svn_ra_serf__xml_estate_t * xes,int stop_state)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
svn_ra_serf__xml_note(svn_ra_serf__xml_estate_t * xes,int state,const char * name,const char * value)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 *
svn_ra_serf__xml_state_pool(svn_ra_serf__xml_estate_t * xes)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 *
xml_cb_start(svn_ra_serf__xml_context_t * xmlctx,const char * raw_name,const char * const * attrs)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 *
xml_cb_end(svn_ra_serf__xml_context_t * xmlctx,const char * raw_name)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 *
xml_cb_cdata(svn_ra_serf__xml_context_t * xmlctx,const char * data,apr_size_t len)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 *
parse_xml(struct expat_ctx_t * ectx,const char * data,apr_size_t len,svn_boolean_t is_final)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
xml_parser_cleanup(void * baton)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
expat_start(void * userData,const char * raw_name,const char ** attrs)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
expat_end(void * userData,const char * raw_name)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
expat_cdata(void * userData,const char * data,int len)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 #if XML_VERSION_AT_LEAST(1, 95, 8)
992 static void
expat_entity_declaration(void * userData,const XML_Char * entityName,int is_parameter_entity,const XML_Char * value,int value_length,const XML_Char * base,const XML_Char * systemId,const XML_Char * publicId,const XML_Char * notationName)993 expat_entity_declaration(void *userData,
994                          const XML_Char *entityName,
995                          int is_parameter_entity,
996                          const XML_Char *value,
997                          int value_length,
998                          const XML_Char *base,
999                          const XML_Char *systemId,
1000                          const XML_Char *publicId,
1001                          const XML_Char *notationName)
1002 {
1003   struct expat_ctx_t *ectx = userData;
1004 
1005   /* Stop the parser if an entity declaration is hit. */
1006   XML_StopParser(ectx->parser, 0 /* resumable */);
1007 }
1008 #else
1009 /* A noop default_handler. */
1010 static void
expat_default_handler(void * userData,const XML_Char * s,int len)1011 expat_default_handler(void *userData, const XML_Char *s, int len)
1012 {
1013 }
1014 #endif
1015 
1016 /* Implements svn_ra_serf__response_handler_t */
1017 static svn_error_t *
expat_response_handler(serf_request_t * request,serf_bucket_t * response,void * baton,apr_pool_t * scratch_pool)1018 expat_response_handler(serf_request_t *request,
1019                        serf_bucket_t *response,
1020                        void *baton,
1021                        apr_pool_t *scratch_pool)
1022 {
1023   struct expat_ctx_t *ectx = baton;
1024   svn_boolean_t got_expected_status;
1025 
1026   if (ectx->expected_status)
1027     {
1028       const int *status = ectx->expected_status;
1029       got_expected_status = FALSE;
1030 
1031       while (*status && ectx->handler->sline.code != *status)
1032         status++;
1033 
1034       got_expected_status = (*status) != 0;
1035     }
1036   else
1037     got_expected_status = (ectx->handler->sline.code == 200);
1038 
1039   if (!ectx->handler->server_error
1040       && ((ectx->handler->sline.code < 200) || (ectx->handler->sline.code >= 300)
1041           || ! got_expected_status))
1042     {
1043       /* By deferring to expect_empty_body(), it will make a choice on
1044          how to handle the body. Whatever the decision, the core handler
1045          will take over, and we will not be called again.  */
1046 
1047       /* ### This handles xml bodies as svn-errors (returned via serf context
1048          ### loop), but ignores non-xml errors.
1049 
1050          Current code depends on this behavior and checks itself while other
1051          continues, and then verifies if work has been performed.
1052 
1053          ### TODO: Make error checking consistent */
1054 
1055       /* ### If !GOT_EXPECTED_STATUS, this should always produce an error */
1056       return svn_error_trace(svn_ra_serf__expect_empty_body(
1057                                request, response, ectx->handler,
1058                                scratch_pool));
1059     }
1060 
1061   if (!ectx->parser)
1062     {
1063       ectx->parser = XML_ParserCreate(NULL);
1064       apr_pool_cleanup_register(ectx->cleanup_pool, &ectx->parser,
1065                                 xml_parser_cleanup, apr_pool_cleanup_null);
1066       XML_SetUserData(ectx->parser, ectx);
1067       XML_SetElementHandler(ectx->parser, expat_start, expat_end);
1068       XML_SetCharacterDataHandler(ectx->parser, expat_cdata);
1069 
1070 #if XML_VERSION_AT_LEAST(1, 95, 8)
1071       XML_SetEntityDeclHandler(ectx->parser, expat_entity_declaration);
1072 #else
1073       XML_SetDefaultHandler(ectx->parser, expat_default_handler);
1074 #endif
1075     }
1076 
1077   while (1)
1078     {
1079       apr_status_t status;
1080       const char *data;
1081       apr_size_t len;
1082       svn_error_t *err;
1083       svn_boolean_t at_eof = FALSE;
1084 
1085       status = serf_bucket_read(response, PARSE_CHUNK_SIZE, &data, &len);
1086       if (SERF_BUCKET_READ_ERROR(status))
1087         return svn_ra_serf__wrap_err(status, NULL);
1088       else if (APR_STATUS_IS_EOF(status))
1089         at_eof = TRUE;
1090 
1091       err = parse_xml(ectx, data, len, at_eof /* isFinal */);
1092 
1093       if (at_eof || err)
1094         {
1095           /* Release xml parser state/tables. */
1096           apr_pool_cleanup_run(ectx->cleanup_pool, &ectx->parser,
1097                                xml_parser_cleanup);
1098         }
1099 
1100       SVN_ERR(err);
1101 
1102       /* The parsing went fine. What has the bucket told us?  */
1103       if (at_eof)
1104         {
1105           /* Make sure we actually got xml and clean up after parsing */
1106           SVN_ERR(svn_ra_serf__xml_context_done(ectx->xmlctx));
1107         }
1108 
1109       if (status && !SERF_BUCKET_READ_ERROR(status))
1110         {
1111           return svn_ra_serf__wrap_err(status, NULL);
1112         }
1113     }
1114 
1115   /* NOTREACHED */
1116 }
1117 
1118 
1119 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)1120 svn_ra_serf__create_expat_handler(svn_ra_serf__session_t *session,
1121                                   svn_ra_serf__xml_context_t *xmlctx,
1122                                   const int *expected_status,
1123                                   apr_pool_t *result_pool)
1124 {
1125   svn_ra_serf__handler_t *handler;
1126   struct expat_ctx_t *ectx;
1127 
1128   ectx = apr_pcalloc(result_pool, sizeof(*ectx));
1129   ectx->xmlctx = xmlctx;
1130   ectx->parser = NULL;
1131   ectx->expected_status = expected_status;
1132   ectx->cleanup_pool = result_pool;
1133 
1134   handler = svn_ra_serf__create_handler(session, result_pool);
1135   handler->response_handler = expat_response_handler;
1136   handler->response_baton = ectx;
1137 
1138   ectx->handler = handler;
1139 
1140   return handler;
1141 }
1142