1 /*
2  * xml.c:  xml helper code shared among the Subversion libraries.
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 <string.h>
27 #include <assert.h>
28 
29 #include "svn_private_config.h"         /* for SVN_HAVE_OLD_EXPAT */
30 #include "svn_hash.h"
31 #include "svn_pools.h"
32 #include "svn_xml.h"
33 #include "svn_error.h"
34 #include "svn_ctype.h"
35 
36 #include "private/svn_utf_private.h"
37 #include "private/svn_subr_private.h"
38 
39 #ifdef SVN_HAVE_OLD_EXPAT
40 #include <xmlparse.h>
41 #else
42 #include <expat.h>
43 #endif
44 
45 #ifdef XML_UNICODE
46 #error Expat is unusable -- it has been compiled for wide characters
47 #endif
48 
49 #ifndef XML_VERSION_AT_LEAST
50 #define XML_VERSION_AT_LEAST(major,minor,patch)                  \
51 (((major) < XML_MAJOR_VERSION)                                       \
52  || ((major) == XML_MAJOR_VERSION && (minor) < XML_MINOR_VERSION)    \
53  || ((major) == XML_MAJOR_VERSION && (minor) == XML_MINOR_VERSION && \
54      (patch) <= XML_MICRO_VERSION))
55 #endif /* XML_VERSION_AT_LEAST */
56 
57 const char *
svn_xml__compiled_version(void)58 svn_xml__compiled_version(void)
59 {
60   static const char xml_version_str[] = APR_STRINGIFY(XML_MAJOR_VERSION)
61                                         "." APR_STRINGIFY(XML_MINOR_VERSION)
62                                         "." APR_STRINGIFY(XML_MICRO_VERSION);
63 
64   return xml_version_str;
65 }
66 
67 const char *
svn_xml__runtime_version(void)68 svn_xml__runtime_version(void)
69 {
70   const char *expat_version = XML_ExpatVersion();
71 
72   if (!strncmp(expat_version, "expat_", 6))
73     expat_version += 6;
74 
75   return expat_version;
76 }
77 
78 
79 /* The private internals for a parser object. */
80 struct svn_xml_parser_t
81 {
82   /** the expat parser */
83   XML_Parser parser;
84 
85   /** the SVN callbacks to call from the Expat callbacks */
86   svn_xml_start_elem start_handler;
87   svn_xml_end_elem end_handler;
88   svn_xml_char_data data_handler;
89 
90   /** the user's baton for private data */
91   void *baton;
92 
93   /** if non-@c NULL, an error happened while parsing */
94   svn_error_t *error;
95 
96   /** where this object is allocated, so we can free it easily */
97   apr_pool_t *pool;
98 
99 };
100 
101 
102 /*** XML character validation ***/
103 
104 svn_boolean_t
svn_xml_is_xml_safe(const char * data,apr_size_t len)105 svn_xml_is_xml_safe(const char *data, apr_size_t len)
106 {
107   const char *end = data + len;
108   const char *p;
109 
110   if (! svn_utf__is_valid(data, len))
111     return FALSE;
112 
113   for (p = data; p < end; p++)
114     {
115       unsigned char c = *p;
116 
117       if (svn_ctype_iscntrl(c))
118         {
119           if ((c != SVN_CTYPE_ASCII_TAB)
120               && (c != SVN_CTYPE_ASCII_LINEFEED)
121               && (c != SVN_CTYPE_ASCII_CARRIAGERETURN)
122               && (c != SVN_CTYPE_ASCII_DELETE))
123             return FALSE;
124         }
125     }
126   return TRUE;
127 }
128 
129 
130 
131 
132 
133 /*** XML escaping. ***/
134 
135 /* ### ...?
136  *
137  * If *OUTSTR is @c NULL, set *OUTSTR to a new stringbuf allocated
138  * in POOL, else append to the existing stringbuf there.
139  */
140 static void
xml_escape_cdata(svn_stringbuf_t ** outstr,const char * data,apr_size_t len,apr_pool_t * pool)141 xml_escape_cdata(svn_stringbuf_t **outstr,
142                  const char *data,
143                  apr_size_t len,
144                  apr_pool_t *pool)
145 {
146   const char *end = data + len;
147   const char *p = data, *q;
148 
149   if (*outstr == NULL)
150     *outstr = svn_stringbuf_create_empty(pool);
151 
152   while (1)
153     {
154       /* Find a character which needs to be quoted and append bytes up
155          to that point.  Strictly speaking, '>' only needs to be
156          quoted if it follows "]]", but it's easier to quote it all
157          the time.
158 
159          So, why are we escaping '\r' here?  Well, according to the
160          XML spec, '\r\n' gets converted to '\n' during XML parsing.
161          Also, any '\r' not followed by '\n' is converted to '\n'.  By
162          golly, if we say we want to escape a '\r', we want to make
163          sure it remains a '\r'!  */
164       q = p;
165       while (q < end && *q != '&' && *q != '<' && *q != '>' && *q != '\r')
166         q++;
167       svn_stringbuf_appendbytes(*outstr, p, q - p);
168 
169       /* We may already be a winner.  */
170       if (q == end)
171         break;
172 
173       /* Append the entity reference for the character.  */
174       if (*q == '&')
175         svn_stringbuf_appendcstr(*outstr, "&amp;");
176       else if (*q == '<')
177         svn_stringbuf_appendcstr(*outstr, "&lt;");
178       else if (*q == '>')
179         svn_stringbuf_appendcstr(*outstr, "&gt;");
180       else if (*q == '\r')
181         svn_stringbuf_appendcstr(*outstr, "&#13;");
182 
183       p = q + 1;
184     }
185 }
186 
187 /* Essentially the same as xml_escape_cdata, with the addition of
188    whitespace and quote characters. */
189 static void
xml_escape_attr(svn_stringbuf_t ** outstr,const char * data,apr_size_t len,apr_pool_t * pool)190 xml_escape_attr(svn_stringbuf_t **outstr,
191                 const char *data,
192                 apr_size_t len,
193                 apr_pool_t *pool)
194 {
195   const char *end = data + len;
196   const char *p = data, *q;
197 
198   if (*outstr == NULL)
199     *outstr = svn_stringbuf_create_ensure(len, pool);
200 
201   while (1)
202     {
203       /* Find a character which needs to be quoted and append bytes up
204          to that point. */
205       q = p;
206       while (q < end && *q != '&' && *q != '<' && *q != '>'
207              && *q != '"' && *q != '\'' && *q != '\r'
208              && *q != '\n' && *q != '\t')
209         q++;
210       svn_stringbuf_appendbytes(*outstr, p, q - p);
211 
212       /* We may already be a winner.  */
213       if (q == end)
214         break;
215 
216       /* Append the entity reference for the character.  */
217       if (*q == '&')
218         svn_stringbuf_appendcstr(*outstr, "&amp;");
219       else if (*q == '<')
220         svn_stringbuf_appendcstr(*outstr, "&lt;");
221       else if (*q == '>')
222         svn_stringbuf_appendcstr(*outstr, "&gt;");
223       else if (*q == '"')
224         svn_stringbuf_appendcstr(*outstr, "&quot;");
225       else if (*q == '\'')
226         svn_stringbuf_appendcstr(*outstr, "&apos;");
227       else if (*q == '\r')
228         svn_stringbuf_appendcstr(*outstr, "&#13;");
229       else if (*q == '\n')
230         svn_stringbuf_appendcstr(*outstr, "&#10;");
231       else if (*q == '\t')
232         svn_stringbuf_appendcstr(*outstr, "&#9;");
233 
234       p = q + 1;
235     }
236 }
237 
238 
239 void
svn_xml_escape_cdata_stringbuf(svn_stringbuf_t ** outstr,const svn_stringbuf_t * string,apr_pool_t * pool)240 svn_xml_escape_cdata_stringbuf(svn_stringbuf_t **outstr,
241                                const svn_stringbuf_t *string,
242                                apr_pool_t *pool)
243 {
244   xml_escape_cdata(outstr, string->data, string->len, pool);
245 }
246 
247 
248 void
svn_xml_escape_cdata_string(svn_stringbuf_t ** outstr,const svn_string_t * string,apr_pool_t * pool)249 svn_xml_escape_cdata_string(svn_stringbuf_t **outstr,
250                             const svn_string_t *string,
251                             apr_pool_t *pool)
252 {
253   xml_escape_cdata(outstr, string->data, string->len, pool);
254 }
255 
256 
257 void
svn_xml_escape_cdata_cstring(svn_stringbuf_t ** outstr,const char * string,apr_pool_t * pool)258 svn_xml_escape_cdata_cstring(svn_stringbuf_t **outstr,
259                              const char *string,
260                              apr_pool_t *pool)
261 {
262   xml_escape_cdata(outstr, string, (apr_size_t) strlen(string), pool);
263 }
264 
265 
266 void
svn_xml_escape_attr_stringbuf(svn_stringbuf_t ** outstr,const svn_stringbuf_t * string,apr_pool_t * pool)267 svn_xml_escape_attr_stringbuf(svn_stringbuf_t **outstr,
268                               const svn_stringbuf_t *string,
269                               apr_pool_t *pool)
270 {
271   xml_escape_attr(outstr, string->data, string->len, pool);
272 }
273 
274 
275 void
svn_xml_escape_attr_string(svn_stringbuf_t ** outstr,const svn_string_t * string,apr_pool_t * pool)276 svn_xml_escape_attr_string(svn_stringbuf_t **outstr,
277                            const svn_string_t *string,
278                            apr_pool_t *pool)
279 {
280   xml_escape_attr(outstr, string->data, string->len, pool);
281 }
282 
283 
284 void
svn_xml_escape_attr_cstring(svn_stringbuf_t ** outstr,const char * string,apr_pool_t * pool)285 svn_xml_escape_attr_cstring(svn_stringbuf_t **outstr,
286                             const char *string,
287                             apr_pool_t *pool)
288 {
289   xml_escape_attr(outstr, string, (apr_size_t) strlen(string), pool);
290 }
291 
292 
293 const char *
svn_xml_fuzzy_escape(const char * string,apr_pool_t * pool)294 svn_xml_fuzzy_escape(const char *string, apr_pool_t *pool)
295 {
296   const char *end = string + strlen(string);
297   const char *p = string, *q;
298   svn_stringbuf_t *outstr;
299   char escaped_char[6];   /* ? \ u u u \0 */
300 
301   for (q = p; q < end; q++)
302     {
303       if (svn_ctype_iscntrl(*q)
304           && ! ((*q == '\n') || (*q == '\r') || (*q == '\t')))
305         break;
306     }
307 
308   /* Return original string if no unsafe characters found. */
309   if (q == end)
310     return string;
311 
312   outstr = svn_stringbuf_create_empty(pool);
313   while (1)
314     {
315       q = p;
316 
317       /* Traverse till either unsafe character or eos. */
318       while ((q < end)
319              && ((! svn_ctype_iscntrl(*q))
320                  || (*q == '\n') || (*q == '\r') || (*q == '\t')))
321         q++;
322 
323       /* copy chunk before marker */
324       svn_stringbuf_appendbytes(outstr, p, q - p);
325 
326       if (q == end)
327         break;
328 
329       /* Append an escaped version of the unsafe character.
330 
331          ### This format was chosen for consistency with
332          ### svn_utf__cstring_from_utf8_fuzzy().  The two functions
333          ### should probably share code, even though they escape
334          ### different characters.
335       */
336       apr_snprintf(escaped_char, sizeof(escaped_char), "?\\%03u",
337                    (unsigned char) *q);
338       svn_stringbuf_appendcstr(outstr, escaped_char);
339 
340       p = q + 1;
341     }
342 
343   return outstr->data;
344 }
345 
346 
347 /*** Map from the Expat callback types to the SVN XML types. ***/
348 
expat_start_handler(void * userData,const XML_Char * name,const XML_Char ** atts)349 static void expat_start_handler(void *userData,
350                                 const XML_Char *name,
351                                 const XML_Char **atts)
352 {
353   svn_xml_parser_t *svn_parser = userData;
354 
355   (*svn_parser->start_handler)(svn_parser->baton, name, atts);
356 }
357 
expat_end_handler(void * userData,const XML_Char * name)358 static void expat_end_handler(void *userData, const XML_Char *name)
359 {
360   svn_xml_parser_t *svn_parser = userData;
361 
362   (*svn_parser->end_handler)(svn_parser->baton, name);
363 }
364 
expat_data_handler(void * userData,const XML_Char * s,int len)365 static void expat_data_handler(void *userData, const XML_Char *s, int len)
366 {
367   svn_xml_parser_t *svn_parser = userData;
368 
369   (*svn_parser->data_handler)(svn_parser->baton, s, (apr_size_t)len);
370 }
371 
372 #if XML_VERSION_AT_LEAST(1, 95, 8)
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)373 static void expat_entity_declaration(void *userData,
374                                      const XML_Char *entityName,
375                                      int is_parameter_entity,
376                                      const XML_Char *value,
377                                      int value_length,
378                                      const XML_Char *base,
379                                      const XML_Char *systemId,
380                                      const XML_Char *publicId,
381                                      const XML_Char *notationName)
382 {
383   svn_xml_parser_t *svn_parser = userData;
384 
385   /* Stop the parser if an entity declaration is hit. */
386   XML_StopParser(svn_parser->parser, 0 /* resumable */);
387 }
388 #else
389 /* A noop default_handler. */
expat_default_handler(void * userData,const XML_Char * s,int len)390 static void expat_default_handler(void *userData, const XML_Char *s, int len)
391 {
392 }
393 #endif
394 
395 /*** Making a parser. ***/
396 
397 svn_xml_parser_t *
svn_xml_make_parser(void * baton,svn_xml_start_elem start_handler,svn_xml_end_elem end_handler,svn_xml_char_data data_handler,apr_pool_t * pool)398 svn_xml_make_parser(void *baton,
399                     svn_xml_start_elem start_handler,
400                     svn_xml_end_elem end_handler,
401                     svn_xml_char_data data_handler,
402                     apr_pool_t *pool)
403 {
404   svn_xml_parser_t *svn_parser;
405   apr_pool_t *subpool;
406 
407   XML_Parser parser = XML_ParserCreate(NULL);
408 
409   XML_SetElementHandler(parser,
410                         start_handler ? expat_start_handler : NULL,
411                         end_handler ? expat_end_handler : NULL);
412   XML_SetCharacterDataHandler(parser,
413                               data_handler ? expat_data_handler : NULL);
414 
415 #if XML_VERSION_AT_LEAST(1, 95, 8)
416   XML_SetEntityDeclHandler(parser, expat_entity_declaration);
417 #else
418   XML_SetDefaultHandler(parser, expat_default_handler);
419 #endif
420 
421   /* ### we probably don't want this pool; or at least we should pass it
422      ### to the callbacks and clear it periodically.  */
423   subpool = svn_pool_create(pool);
424 
425   svn_parser = apr_pcalloc(subpool, sizeof(*svn_parser));
426 
427   svn_parser->parser = parser;
428   svn_parser->start_handler = start_handler;
429   svn_parser->end_handler = end_handler;
430   svn_parser->data_handler = data_handler;
431   svn_parser->baton = baton;
432   svn_parser->pool = subpool;
433 
434   /* store our parser info as the UserData in the Expat parser */
435   XML_SetUserData(parser, svn_parser);
436 
437   return svn_parser;
438 }
439 
440 
441 /* Free a parser */
442 void
svn_xml_free_parser(svn_xml_parser_t * svn_parser)443 svn_xml_free_parser(svn_xml_parser_t *svn_parser)
444 {
445   /* Free the expat parser */
446   XML_ParserFree(svn_parser->parser);
447 
448   /* Free the subversion parser */
449   svn_pool_destroy(svn_parser->pool);
450 }
451 
452 
453 
454 
455 svn_error_t *
svn_xml_parse(svn_xml_parser_t * svn_parser,const char * buf,apr_size_t len,svn_boolean_t is_final)456 svn_xml_parse(svn_xml_parser_t *svn_parser,
457               const char *buf,
458               apr_size_t len,
459               svn_boolean_t is_final)
460 {
461   svn_error_t *err;
462   int success;
463 
464   /* Parse some xml data */
465   success = XML_Parse(svn_parser->parser, buf, (int) len, is_final);
466 
467   /* If expat choked internally, return its error. */
468   if (! success)
469     {
470       /* Line num is "int" in Expat v1, "long" in v2; hide the difference. */
471       long line = XML_GetCurrentLineNumber(svn_parser->parser);
472 
473       err = svn_error_createf
474         (SVN_ERR_XML_MALFORMED, NULL,
475          _("Malformed XML: %s at line %ld"),
476          XML_ErrorString(XML_GetErrorCode(svn_parser->parser)), line);
477 
478       /* Kill all parsers and return the expat error */
479       svn_xml_free_parser(svn_parser);
480       return err;
481     }
482 
483   /* Did an error occur somewhere *inside* the expat callbacks? */
484   if (svn_parser->error)
485     {
486       err = svn_parser->error;
487       svn_xml_free_parser(svn_parser);
488       return err;
489     }
490 
491   return SVN_NO_ERROR;
492 }
493 
494 
495 
svn_xml_signal_bailout(svn_error_t * error,svn_xml_parser_t * svn_parser)496 void svn_xml_signal_bailout(svn_error_t *error,
497                             svn_xml_parser_t *svn_parser)
498 {
499   /* This will cause the current XML_Parse() call to finish quickly! */
500   XML_SetElementHandler(svn_parser->parser, NULL, NULL);
501   XML_SetCharacterDataHandler(svn_parser->parser, NULL);
502 #if XML_VERSION_AT_LEAST(1, 95, 8)
503   XML_SetEntityDeclHandler(svn_parser->parser, NULL);
504 #endif
505 
506   /* Once outside of XML_Parse(), the existence of this field will
507      cause svn_delta_parse()'s main read-loop to return error. */
508   svn_parser->error = error;
509 }
510 
511 
512 
513 
514 
515 
516 
517 
518 /*** Attribute walking. ***/
519 
520 const char *
svn_xml_get_attr_value(const char * name,const char * const * atts)521 svn_xml_get_attr_value(const char *name, const char *const *atts)
522 {
523   while (atts && (*atts))
524     {
525       if (strcmp(atts[0], name) == 0)
526         return atts[1];
527       else
528         atts += 2; /* continue looping */
529     }
530 
531   /* Else no such attribute name seen. */
532   return NULL;
533 }
534 
535 
536 
537 /*** Printing XML ***/
538 
539 void
svn_xml_make_header2(svn_stringbuf_t ** str,const char * encoding,apr_pool_t * pool)540 svn_xml_make_header2(svn_stringbuf_t **str, const char *encoding,
541                      apr_pool_t *pool)
542 {
543 
544   if (*str == NULL)
545     *str = svn_stringbuf_create_empty(pool);
546   svn_stringbuf_appendcstr(*str, "<?xml version=\"1.0\"");
547   if (encoding)
548     {
549       encoding = apr_psprintf(pool, " encoding=\"%s\"", encoding);
550       svn_stringbuf_appendcstr(*str, encoding);
551     }
552   svn_stringbuf_appendcstr(*str, "?>\n");
553 }
554 
555 
556 
557 /*** Creating attribute hashes. ***/
558 
559 /* Combine an existing attribute list ATTS with a HASH that itself
560    represents an attribute list.  Iff PRESERVE is true, then no value
561    already in HASH will be changed, else values from ATTS will
562    override previous values in HASH. */
563 static void
amalgamate(const char ** atts,apr_hash_t * ht,svn_boolean_t preserve,apr_pool_t * pool)564 amalgamate(const char **atts,
565            apr_hash_t *ht,
566            svn_boolean_t preserve,
567            apr_pool_t *pool)
568 {
569   const char *key;
570 
571   if (atts)
572     for (key = *atts; key; key = *(++atts))
573       {
574         const char *val = *(++atts);
575         size_t keylen;
576         assert(key != NULL);
577         /* kff todo: should we also insist that val be non-null here?
578            Probably. */
579 
580         keylen = strlen(key);
581         if (preserve && ((apr_hash_get(ht, key, keylen)) != NULL))
582           continue;
583         else
584           apr_hash_set(ht, apr_pstrndup(pool, key, keylen), keylen,
585                        val ? apr_pstrdup(pool, val) : NULL);
586       }
587 }
588 
589 
590 apr_hash_t *
svn_xml_ap_to_hash(va_list ap,apr_pool_t * pool)591 svn_xml_ap_to_hash(va_list ap, apr_pool_t *pool)
592 {
593   apr_hash_t *ht = apr_hash_make(pool);
594   const char *key;
595 
596   while ((key = va_arg(ap, char *)) != NULL)
597     {
598       const char *val = va_arg(ap, const char *);
599       svn_hash_sets(ht, key, val);
600     }
601 
602   return ht;
603 }
604 
605 
606 apr_hash_t *
svn_xml_make_att_hash(const char ** atts,apr_pool_t * pool)607 svn_xml_make_att_hash(const char **atts, apr_pool_t *pool)
608 {
609   apr_hash_t *ht = apr_hash_make(pool);
610   amalgamate(atts, ht, 0, pool);  /* third arg irrelevant in this case */
611   return ht;
612 }
613 
614 
615 void
svn_xml_hash_atts_overlaying(const char ** atts,apr_hash_t * ht,apr_pool_t * pool)616 svn_xml_hash_atts_overlaying(const char **atts,
617                              apr_hash_t *ht,
618                              apr_pool_t *pool)
619 {
620   amalgamate(atts, ht, 0, pool);
621 }
622 
623 
624 void
svn_xml_hash_atts_preserving(const char ** atts,apr_hash_t * ht,apr_pool_t * pool)625 svn_xml_hash_atts_preserving(const char **atts,
626                              apr_hash_t *ht,
627                              apr_pool_t *pool)
628 {
629   amalgamate(atts, ht, 1, pool);
630 }
631 
632 
633 
634 /*** Making XML tags. ***/
635 
636 
637 void
svn_xml_make_open_tag_hash(svn_stringbuf_t ** str,apr_pool_t * pool,enum svn_xml_open_tag_style style,const char * tagname,apr_hash_t * attributes)638 svn_xml_make_open_tag_hash(svn_stringbuf_t **str,
639                            apr_pool_t *pool,
640                            enum svn_xml_open_tag_style style,
641                            const char *tagname,
642                            apr_hash_t *attributes)
643 {
644   apr_hash_index_t *hi;
645   apr_size_t est_size = strlen(tagname) + 4 + apr_hash_count(attributes) * 30;
646 
647   if (*str == NULL)
648     *str = svn_stringbuf_create_ensure(est_size, pool);
649 
650   svn_stringbuf_appendcstr(*str, "<");
651   svn_stringbuf_appendcstr(*str, tagname);
652 
653   for (hi = apr_hash_first(pool, attributes); hi; hi = apr_hash_next(hi))
654     {
655       const void *key;
656       void *val;
657 
658       apr_hash_this(hi, &key, NULL, &val);
659       assert(val != NULL);
660 
661       svn_stringbuf_appendcstr(*str, "\n   ");
662       svn_stringbuf_appendcstr(*str, key);
663       svn_stringbuf_appendcstr(*str, "=\"");
664       svn_xml_escape_attr_cstring(str, val, pool);
665       svn_stringbuf_appendcstr(*str, "\"");
666     }
667 
668   if (style == svn_xml_self_closing)
669     svn_stringbuf_appendcstr(*str, "/");
670   svn_stringbuf_appendcstr(*str, ">");
671   if (style != svn_xml_protect_pcdata)
672     svn_stringbuf_appendcstr(*str, "\n");
673 }
674 
675 
676 void
svn_xml_make_open_tag_v(svn_stringbuf_t ** str,apr_pool_t * pool,enum svn_xml_open_tag_style style,const char * tagname,va_list ap)677 svn_xml_make_open_tag_v(svn_stringbuf_t **str,
678                         apr_pool_t *pool,
679                         enum svn_xml_open_tag_style style,
680                         const char *tagname,
681                         va_list ap)
682 {
683   apr_pool_t *subpool = svn_pool_create(pool);
684   apr_hash_t *ht = svn_xml_ap_to_hash(ap, subpool);
685 
686   svn_xml_make_open_tag_hash(str, pool, style, tagname, ht);
687   svn_pool_destroy(subpool);
688 }
689 
690 
691 
692 void
svn_xml_make_open_tag(svn_stringbuf_t ** str,apr_pool_t * pool,enum svn_xml_open_tag_style style,const char * tagname,...)693 svn_xml_make_open_tag(svn_stringbuf_t **str,
694                       apr_pool_t *pool,
695                       enum svn_xml_open_tag_style style,
696                       const char *tagname,
697                       ...)
698 {
699   va_list ap;
700 
701   va_start(ap, tagname);
702   svn_xml_make_open_tag_v(str, pool, style, tagname, ap);
703   va_end(ap);
704 }
705 
706 
svn_xml_make_close_tag(svn_stringbuf_t ** str,apr_pool_t * pool,const char * tagname)707 void svn_xml_make_close_tag(svn_stringbuf_t **str,
708                             apr_pool_t *pool,
709                             const char *tagname)
710 {
711   if (*str == NULL)
712     *str = svn_stringbuf_create_empty(pool);
713 
714   svn_stringbuf_appendcstr(*str, "</");
715   svn_stringbuf_appendcstr(*str, tagname);
716   svn_stringbuf_appendcstr(*str, ">\n");
717 }
718