]> CyberLeo.Net >> Repos - FreeBSD/stable/10.git/blob - contrib/subversion/subversion/libsvn_subr/xml.c
MFC r309356: svn 1.9.4 -> 1.9.5
[FreeBSD/stable/10.git] / contrib / subversion / subversion / libsvn_subr / xml.c
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 \f
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 *
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 *
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 \f
102 /*** XML character validation ***/
103
104 svn_boolean_t
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 \f
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
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
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
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
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
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
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
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
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 *
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 \f
347 /*** Map from the Expat callback types to the SVN XML types. ***/
348
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
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
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)
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. */
390 static void expat_default_handler(void *userData, const XML_Char *s, int len)
391 {
392 }
393 #endif
394 \f
395 /*** Making a parser. ***/
396
397 svn_xml_parser_t *
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
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 *
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
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 \f
518 /*** Attribute walking. ***/
519
520 const char *
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 \f
537 /*** Printing XML ***/
538
539 void
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 \f
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
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 *
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 *
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
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
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 \f
634 /*** Making XML tags. ***/
635
636
637 void
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
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
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
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 }