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 #ifndef XML_VERSION_AT_LEAST
46 #define XML_VERSION_AT_LEAST(major,minor,patch)                  \
47 (((major) < XML_MAJOR_VERSION)                                       \
48  || ((major) == XML_MAJOR_VERSION && (minor) < XML_MINOR_VERSION)    \
49  || ((major) == XML_MAJOR_VERSION && (minor) == XML_MINOR_VERSION && \
50      (patch) <= XML_MICRO_VERSION))
51 #endif /* XML_VERSION_AT_LEAST */
52 
53 #ifdef XML_UNICODE
54 #error Expat is unusable -- it has been compiled for wide characters
55 #endif
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 #if XML_VERSION_AT_LEAST(1, 95, 8)
358   /* Stop XML parsing if svn_xml_signal_bailout() was called.
359      We cannot do this in svn_xml_signal_bailout() because Expat
360      documentation states that XML_StopParser() must be called only from
361      callbacks. */
362   if (svn_parser->error)
363     (void) XML_StopParser(svn_parser->parser, 0 /* resumable */);
364 #endif
365 }
366 
expat_end_handler(void * userData,const XML_Char * name)367 static void expat_end_handler(void *userData, const XML_Char *name)
368 {
369   svn_xml_parser_t *svn_parser = userData;
370 
371   (*svn_parser->end_handler)(svn_parser->baton, name);
372 
373 #if XML_VERSION_AT_LEAST(1, 95, 8)
374   /* Stop XML parsing if svn_xml_signal_bailout() was called.
375      We cannot do this in svn_xml_signal_bailout() because Expat
376      documentation states that XML_StopParser() must be called only from
377      callbacks. */
378   if (svn_parser->error)
379     (void) XML_StopParser(svn_parser->parser, 0 /* resumable */);
380 #endif
381 }
382 
expat_data_handler(void * userData,const XML_Char * s,int len)383 static void expat_data_handler(void *userData, const XML_Char *s, int len)
384 {
385   svn_xml_parser_t *svn_parser = userData;
386 
387   (*svn_parser->data_handler)(svn_parser->baton, s, (apr_size_t)len);
388 
389 #if XML_VERSION_AT_LEAST(1, 95, 8)
390   /* Stop XML parsing if svn_xml_signal_bailout() was called.
391      We cannot do this in svn_xml_signal_bailout() because Expat
392      documentation states that XML_StopParser() must be called only from
393      callbacks. */
394   if (svn_parser->error)
395     (void) XML_StopParser(svn_parser->parser, 0 /* resumable */);
396 #endif
397 }
398 
399 #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)400 static void expat_entity_declaration(void *userData,
401                                      const XML_Char *entityName,
402                                      int is_parameter_entity,
403                                      const XML_Char *value,
404                                      int value_length,
405                                      const XML_Char *base,
406                                      const XML_Char *systemId,
407                                      const XML_Char *publicId,
408                                      const XML_Char *notationName)
409 {
410   svn_xml_parser_t *svn_parser = userData;
411 
412   /* Stop the parser if an entity declaration is hit. */
413   XML_StopParser(svn_parser->parser, 0 /* resumable */);
414 }
415 #else
416 /* A noop default_handler. */
expat_default_handler(void * userData,const XML_Char * s,int len)417 static void expat_default_handler(void *userData, const XML_Char *s, int len)
418 {
419 }
420 #endif
421 
422 /*** Making a parser. ***/
423 
parser_cleanup(void * data)424 static apr_status_t parser_cleanup(void *data)
425 {
426   svn_xml_parser_t *svn_parser = data;
427 
428   /* Free Expat parser. */
429   if (svn_parser->parser)
430     {
431       XML_ParserFree(svn_parser->parser);
432       svn_parser->parser = NULL;
433     }
434   return APR_SUCCESS;
435 }
436 
437 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)438 svn_xml_make_parser(void *baton,
439                     svn_xml_start_elem start_handler,
440                     svn_xml_end_elem end_handler,
441                     svn_xml_char_data data_handler,
442                     apr_pool_t *pool)
443 {
444   svn_xml_parser_t *svn_parser;
445   XML_Parser parser = XML_ParserCreate(NULL);
446 
447   XML_SetElementHandler(parser,
448                         start_handler ? expat_start_handler : NULL,
449                         end_handler ? expat_end_handler : NULL);
450   XML_SetCharacterDataHandler(parser,
451                               data_handler ? expat_data_handler : NULL);
452 
453 #if XML_VERSION_AT_LEAST(1, 95, 8)
454   XML_SetEntityDeclHandler(parser, expat_entity_declaration);
455 #else
456   XML_SetDefaultHandler(parser, expat_default_handler);
457 #endif
458 
459   svn_parser = apr_pcalloc(pool, sizeof(*svn_parser));
460 
461   svn_parser->parser = parser;
462   svn_parser->start_handler = start_handler;
463   svn_parser->end_handler = end_handler;
464   svn_parser->data_handler = data_handler;
465   svn_parser->baton = baton;
466   svn_parser->pool = pool;
467 
468   /* store our parser info as the UserData in the Expat parser */
469   XML_SetUserData(parser, svn_parser);
470 
471   /* Register pool cleanup handler to free Expat XML parser on cleanup,
472      if svn_xml_free_parser() was not called explicitly. */
473   apr_pool_cleanup_register(svn_parser->pool, svn_parser,
474                             parser_cleanup, apr_pool_cleanup_null);
475 
476   return svn_parser;
477 }
478 
479 
480 /* Free a parser */
481 void
svn_xml_free_parser(svn_xml_parser_t * svn_parser)482 svn_xml_free_parser(svn_xml_parser_t *svn_parser)
483 {
484   apr_pool_cleanup_run(svn_parser->pool, svn_parser, parser_cleanup);
485 }
486 
487 
488 
489 
490 svn_error_t *
svn_xml_parse(svn_xml_parser_t * svn_parser,const char * buf,apr_size_t len,svn_boolean_t is_final)491 svn_xml_parse(svn_xml_parser_t *svn_parser,
492               const char *buf,
493               apr_size_t len,
494               svn_boolean_t is_final)
495 {
496   svn_error_t *err;
497   int success;
498 
499   /* Parse some xml data */
500   success = XML_Parse(svn_parser->parser, buf, (int) len, is_final);
501 
502   /* Did an error occur somewhere *inside* the expat callbacks? */
503   if (svn_parser->error)
504     {
505       /* Kill all parsers and return the error */
506       svn_xml_free_parser(svn_parser);
507       return svn_parser->error;
508     }
509 
510   /* If expat choked internally, return its error. */
511   if (! success)
512     {
513       /* Line num is "int" in Expat v1, "long" in v2; hide the difference. */
514       long line = XML_GetCurrentLineNumber(svn_parser->parser);
515 
516       err = svn_error_createf
517         (SVN_ERR_XML_MALFORMED, NULL,
518          _("Malformed XML: %s at line %ld"),
519          XML_ErrorString(XML_GetErrorCode(svn_parser->parser)), line);
520 
521       /* Kill all parsers and return the expat error */
522       svn_xml_free_parser(svn_parser);
523       return err;
524     }
525 
526   return SVN_NO_ERROR;
527 }
528 
529 
530 
svn_xml_signal_bailout(svn_error_t * error,svn_xml_parser_t * svn_parser)531 void svn_xml_signal_bailout(svn_error_t *error,
532                             svn_xml_parser_t *svn_parser)
533 {
534   /* This will cause the current XML_Parse() call to finish quickly! */
535   XML_SetElementHandler(svn_parser->parser, NULL, NULL);
536   XML_SetCharacterDataHandler(svn_parser->parser, NULL);
537 #if XML_VERSION_AT_LEAST(1, 95, 8)
538   XML_SetEntityDeclHandler(svn_parser->parser, NULL);
539 #endif
540   /* Once outside of XML_Parse(), the existence of this field will
541      cause svn_delta_parse()'s main read-loop to return error. */
542   svn_parser->error = error;
543 }
544 
545 
546 
547 
548 
549 
550 
551 
552 /*** Attribute walking. ***/
553 
554 const char *
svn_xml_get_attr_value(const char * name,const char * const * atts)555 svn_xml_get_attr_value(const char *name, const char *const *atts)
556 {
557   while (atts && (*atts))
558     {
559       if (strcmp(atts[0], name) == 0)
560         return atts[1];
561       else
562         atts += 2; /* continue looping */
563     }
564 
565   /* Else no such attribute name seen. */
566   return NULL;
567 }
568 
569 
570 
571 /*** Printing XML ***/
572 
573 void
svn_xml_make_header2(svn_stringbuf_t ** str,const char * encoding,apr_pool_t * pool)574 svn_xml_make_header2(svn_stringbuf_t **str, const char *encoding,
575                      apr_pool_t *pool)
576 {
577 
578   if (*str == NULL)
579     *str = svn_stringbuf_create_empty(pool);
580   svn_stringbuf_appendcstr(*str, "<?xml version=\"1.0\"");
581   if (encoding)
582     {
583       encoding = apr_psprintf(pool, " encoding=\"%s\"", encoding);
584       svn_stringbuf_appendcstr(*str, encoding);
585     }
586   svn_stringbuf_appendcstr(*str, "?>\n");
587 }
588 
589 
590 
591 /*** Creating attribute hashes. ***/
592 
593 /* Combine an existing attribute list ATTS with a HASH that itself
594    represents an attribute list.  Iff PRESERVE is true, then no value
595    already in HASH will be changed, else values from ATTS will
596    override previous values in HASH. */
597 static void
amalgamate(const char ** atts,apr_hash_t * ht,svn_boolean_t preserve,apr_pool_t * pool)598 amalgamate(const char **atts,
599            apr_hash_t *ht,
600            svn_boolean_t preserve,
601            apr_pool_t *pool)
602 {
603   const char *key;
604 
605   if (atts)
606     for (key = *atts; key; key = *(++atts))
607       {
608         const char *val = *(++atts);
609         size_t keylen;
610         assert(key != NULL);
611         /* kff todo: should we also insist that val be non-null here?
612            Probably. */
613 
614         keylen = strlen(key);
615         if (preserve && ((apr_hash_get(ht, key, keylen)) != NULL))
616           continue;
617         else
618           apr_hash_set(ht, apr_pstrndup(pool, key, keylen), keylen,
619                        val ? apr_pstrdup(pool, val) : NULL);
620       }
621 }
622 
623 
624 apr_hash_t *
svn_xml_ap_to_hash(va_list ap,apr_pool_t * pool)625 svn_xml_ap_to_hash(va_list ap, apr_pool_t *pool)
626 {
627   apr_hash_t *ht = apr_hash_make(pool);
628   const char *key;
629 
630   while ((key = va_arg(ap, char *)) != NULL)
631     {
632       const char *val = va_arg(ap, const char *);
633       svn_hash_sets(ht, key, val);
634     }
635 
636   return ht;
637 }
638 
639 
640 apr_hash_t *
svn_xml_make_att_hash(const char ** atts,apr_pool_t * pool)641 svn_xml_make_att_hash(const char **atts, apr_pool_t *pool)
642 {
643   apr_hash_t *ht = apr_hash_make(pool);
644   amalgamate(atts, ht, 0, pool);  /* third arg irrelevant in this case */
645   return ht;
646 }
647 
648 
649 void
svn_xml_hash_atts_overlaying(const char ** atts,apr_hash_t * ht,apr_pool_t * pool)650 svn_xml_hash_atts_overlaying(const char **atts,
651                              apr_hash_t *ht,
652                              apr_pool_t *pool)
653 {
654   amalgamate(atts, ht, 0, pool);
655 }
656 
657 
658 void
svn_xml_hash_atts_preserving(const char ** atts,apr_hash_t * ht,apr_pool_t * pool)659 svn_xml_hash_atts_preserving(const char **atts,
660                              apr_hash_t *ht,
661                              apr_pool_t *pool)
662 {
663   amalgamate(atts, ht, 1, pool);
664 }
665 
666 
667 
668 /*** Making XML tags. ***/
669 
670 
671 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)672 svn_xml_make_open_tag_hash(svn_stringbuf_t **str,
673                            apr_pool_t *pool,
674                            enum svn_xml_open_tag_style style,
675                            const char *tagname,
676                            apr_hash_t *attributes)
677 {
678   apr_hash_index_t *hi;
679   apr_size_t est_size = strlen(tagname) + 4 + apr_hash_count(attributes) * 30;
680 
681   if (*str == NULL)
682     *str = svn_stringbuf_create_ensure(est_size, pool);
683 
684   svn_stringbuf_appendcstr(*str, "<");
685   svn_stringbuf_appendcstr(*str, tagname);
686 
687   for (hi = apr_hash_first(pool, attributes); hi; hi = apr_hash_next(hi))
688     {
689       const void *key;
690       void *val;
691 
692       apr_hash_this(hi, &key, NULL, &val);
693       assert(val != NULL);
694 
695       svn_stringbuf_appendcstr(*str, "\n   ");
696       svn_stringbuf_appendcstr(*str, key);
697       svn_stringbuf_appendcstr(*str, "=\"");
698       svn_xml_escape_attr_cstring(str, val, pool);
699       svn_stringbuf_appendcstr(*str, "\"");
700     }
701 
702   if (style == svn_xml_self_closing)
703     svn_stringbuf_appendcstr(*str, "/");
704   svn_stringbuf_appendcstr(*str, ">");
705   if (style != svn_xml_protect_pcdata)
706     svn_stringbuf_appendcstr(*str, "\n");
707 }
708 
709 
710 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)711 svn_xml_make_open_tag_v(svn_stringbuf_t **str,
712                         apr_pool_t *pool,
713                         enum svn_xml_open_tag_style style,
714                         const char *tagname,
715                         va_list ap)
716 {
717   apr_pool_t *subpool = svn_pool_create(pool);
718   apr_hash_t *ht = svn_xml_ap_to_hash(ap, subpool);
719 
720   svn_xml_make_open_tag_hash(str, pool, style, tagname, ht);
721   svn_pool_destroy(subpool);
722 }
723 
724 
725 
726 void
svn_xml_make_open_tag(svn_stringbuf_t ** str,apr_pool_t * pool,enum svn_xml_open_tag_style style,const char * tagname,...)727 svn_xml_make_open_tag(svn_stringbuf_t **str,
728                       apr_pool_t *pool,
729                       enum svn_xml_open_tag_style style,
730                       const char *tagname,
731                       ...)
732 {
733   va_list ap;
734 
735   va_start(ap, tagname);
736   svn_xml_make_open_tag_v(str, pool, style, tagname, ap);
737   va_end(ap);
738 }
739 
740 
svn_xml_make_close_tag(svn_stringbuf_t ** str,apr_pool_t * pool,const char * tagname)741 void svn_xml_make_close_tag(svn_stringbuf_t **str,
742                             apr_pool_t *pool,
743                             const char *tagname)
744 {
745   if (*str == NULL)
746     *str = svn_stringbuf_create_empty(pool);
747 
748   svn_stringbuf_appendcstr(*str, "</");
749   svn_stringbuf_appendcstr(*str, tagname);
750   svn_stringbuf_appendcstr(*str, ">\n");
751 }
752