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("&", sizeof("&") - 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("<", sizeof("<") - 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(">", sizeof(">") - 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(" ", sizeof(" ") - 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(¤t->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