]> CyberLeo.Net >> Repos - FreeBSD/releng/10.0.git/blob - contrib/subversion/subversion/libsvn_subr/skel.c
- Copy stable/10 (r259064) to releng/10.0 as part of the
[FreeBSD/releng/10.0.git] / contrib / subversion / subversion / libsvn_subr / skel.c
1 /* skel.c --- parsing and unparsing skeletons
2  *
3  * ====================================================================
4  *    Licensed to the Apache Software Foundation (ASF) under one
5  *    or more contributor license agreements.  See the NOTICE file
6  *    distributed with this work for additional information
7  *    regarding copyright ownership.  The ASF licenses this file
8  *    to you under the Apache License, Version 2.0 (the
9  *    "License"); you may not use this file except in compliance
10  *    with the License.  You may obtain a copy of the License at
11  *
12  *      http://www.apache.org/licenses/LICENSE-2.0
13  *
14  *    Unless required by applicable law or agreed to in writing,
15  *    software distributed under the License is distributed on an
16  *    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17  *    KIND, either express or implied.  See the License for the
18  *    specific language governing permissions and limitations
19  *    under the License.
20  * ====================================================================
21  */
22
23 #include <string.h>
24 #include "svn_string.h"
25 #include "svn_error.h"
26 #include "svn_props.h"
27 #include "svn_pools.h"
28 #include "private/svn_skel.h"
29 #include "private/svn_string_private.h"
30
31 \f
32 /* Parsing skeletons.  */
33
34 enum char_type {
35   type_nothing = 0,
36   type_space = 1,
37   type_digit = 2,
38   type_paren = 3,
39   type_name = 4
40 };
41
42
43 /* We can't use the <ctype.h> macros here, because they are locale-
44    dependent.  The syntax of a skel is specified directly in terms of
45    byte values, and is independent of locale.  */
46
47 static const enum char_type skel_char_type[256] = {
48   0, 0, 0, 0, 0, 0, 0, 0,   0, 1, 1, 0, 1, 1, 0, 0,
49   0, 0, 0, 0, 0, 0, 0, 0,   0, 0, 0, 0, 0, 0, 0, 0,
50   1, 0, 0, 0, 0, 0, 0, 0,   3, 3, 0, 0, 0, 0, 0, 0,
51   2, 2, 2, 2, 2, 2, 2, 2,   2, 2, 0, 0, 0, 0, 0, 0,
52
53   /* 64 */
54   0, 4, 4, 4, 4, 4, 4, 4,   4, 4, 4, 4, 4, 4, 4, 4,
55   4, 4, 4, 4, 4, 4, 4, 4,   4, 4, 4, 3, 0, 3, 0, 0,
56   0, 4, 4, 4, 4, 4, 4, 4,   4, 4, 4, 4, 4, 4, 4, 4,
57   4, 4, 4, 4, 4, 4, 4, 4,   4, 4, 4, 0, 0, 0, 0, 0,
58
59   /* 128 */
60   0, 0, 0, 0, 0, 0, 0, 0,   0, 0, 0, 0, 0, 0, 0, 0,
61   0, 0, 0, 0, 0, 0, 0, 0,   0, 0, 0, 0, 0, 0, 0, 0,
62   0, 0, 0, 0, 0, 0, 0, 0,   0, 0, 0, 0, 0, 0, 0, 0,
63   0, 0, 0, 0, 0, 0, 0, 0,   0, 0, 0, 0, 0, 0, 0, 0,
64
65   /* 192 */
66   0, 0, 0, 0, 0, 0, 0, 0,   0, 0, 0, 0, 0, 0, 0, 0,
67   0, 0, 0, 0, 0, 0, 0, 0,   0, 0, 0, 0, 0, 0, 0, 0,
68   0, 0, 0, 0, 0, 0, 0, 0,   0, 0, 0, 0, 0, 0, 0, 0,
69   0, 0, 0, 0, 0, 0, 0, 0,   0, 0, 0, 0, 0, 0, 0, 0,
70 };
71
72
73 \f
74 /* ### WTF? since when is number conversion LOCALE DEPENDENT? */
75 /* stsp: In C99, various numerical string properties such as decimal point,
76  * thousands separator, and the plus/minus sign are locale dependent. */
77
78 /* Converting text to numbers.  */
79
80 /* Return the value of the string of digits at DATA as an ASCII
81    decimal number.  The string is at most LEN bytes long.  The value
82    of the number is at most MAX.  Set *END to the address of the first
83    byte after the number, or zero if an error occurred while
84    converting the number (overflow, for example).
85
86    We would like to use strtoul, but that family of functions is
87    locale-dependent, whereas we're trying to parse data in a
88    locale-independent format.  */
89 static apr_size_t
90 getsize(const char *data, apr_size_t len,
91         const char **endptr, apr_size_t max)
92 {
93   /* We can't detect overflow by simply comparing value against max,
94      since multiplying value by ten can overflow in strange ways if
95      max is close to the limits of apr_size_t.  For example, suppose
96      that max is 54, and apr_size_t is six bits long; its range is
97      0..63.  If we're parsing the number "502", then value will be 50
98      after parsing the first two digits.  50 * 10 = 500.  But 500
99      doesn't fit in an apr_size_t, so it'll be truncated to 500 mod 64
100      = 52, which is less than max, so we'd fail to recognize the
101      overflow.  Furthermore, it *is* greater than 50, so you can't
102      detect overflow by checking whether value actually increased
103      after each multiplication --- sometimes it does increase, but
104      it's still wrong.
105
106      So we do the check for overflow before we multiply value and add
107      in the new digit.  */
108   apr_size_t max_prefix = max / 10;
109   apr_size_t max_digit = max % 10;
110   apr_size_t i;
111   apr_size_t value = 0;
112
113   for (i = 0; i < len && '0' <= data[i] && data[i] <= '9'; i++)
114     {
115       apr_size_t digit = data[i] - '0';
116
117       /* Check for overflow.  */
118       if (value > max_prefix
119           || (value == max_prefix && digit > max_digit))
120         {
121           *endptr = 0;
122           return 0;
123         }
124
125       value = (value * 10) + digit;
126     }
127
128   /* There must be at least one digit there.  */
129   if (i == 0)
130     {
131       *endptr = 0;
132       return 0;
133     }
134   else
135     {
136       *endptr = data + i;
137       return value;
138     }
139 }
140
141 \f
142 /* Checking validity of skels. */
143 static svn_error_t *
144 skel_err(const char *skel_type)
145 {
146   return svn_error_createf(SVN_ERR_FS_MALFORMED_SKEL, NULL,
147                            "Malformed%s%s skeleton",
148                            skel_type ? " " : "",
149                            skel_type ? skel_type : "");
150 }
151
152
153 static svn_boolean_t
154 is_valid_proplist_skel(const svn_skel_t *skel)
155 {
156   int len = svn_skel__list_length(skel);
157
158   if ((len >= 0) && (len & 1) == 0)
159     {
160       svn_skel_t *elt;
161
162       for (elt = skel->children; elt; elt = elt->next)
163         if (! elt->is_atom)
164           return FALSE;
165
166       return TRUE;
167     }
168
169   return FALSE;
170 }
171
172 static svn_boolean_t
173 is_valid_iproplist_skel(const svn_skel_t *skel)
174 {
175   int len = svn_skel__list_length(skel);
176
177   if ((len >= 0) && (len & 1) == 0)
178     {
179       svn_skel_t *elt;
180
181       for (elt = skel->children; elt; elt = elt->next)
182         {
183           if (!elt->is_atom)
184             return FALSE;
185
186           if (elt->next == NULL)
187             return FALSE;
188
189           elt = elt->next;
190
191           if (! is_valid_proplist_skel(elt))
192             return FALSE;
193         }
194
195       return TRUE;
196     }
197
198   return FALSE;
199 }
200
201 \f
202 static svn_skel_t *parse(const char *data, apr_size_t len,
203                          apr_pool_t *pool);
204 static svn_skel_t *list(const char *data, apr_size_t len,
205                         apr_pool_t *pool);
206 static svn_skel_t *implicit_atom(const char *data, apr_size_t len,
207                                  apr_pool_t *pool);
208 static svn_skel_t *explicit_atom(const char *data, apr_size_t len,
209                                  apr_pool_t *pool);
210
211
212 svn_skel_t *
213 svn_skel__parse(const char *data,
214                 apr_size_t len,
215                 apr_pool_t *pool)
216 {
217   return parse(data, len, pool);
218 }
219
220
221 /* Parse any kind of skel object --- atom, or list.  */
222 static svn_skel_t *
223 parse(const char *data,
224       apr_size_t len,
225       apr_pool_t *pool)
226 {
227   char c;
228
229   /* The empty string isn't a valid skel.  */
230   if (len <= 0)
231     return NULL;
232
233   c = *data;
234
235   /* Is it a list, or an atom?  */
236   if (c == '(')
237     return list(data, len, pool);
238
239   /* Is it a string with an implicit length?  */
240   if (skel_char_type[(unsigned char) c] == type_name)
241     return implicit_atom(data, len, pool);
242
243   /* Otherwise, we assume it's a string with an explicit length;
244      svn_skel__getsize will catch the error.  */
245   else
246     return explicit_atom(data, len, pool);
247 }
248
249
250 static svn_skel_t *
251 list(const char *data,
252      apr_size_t len,
253      apr_pool_t *pool)
254 {
255   const char *end = data + len;
256   const char *list_start;
257
258   /* Verify that the list starts with an opening paren.  At the
259      moment, all callers have checked this already, but it's more
260      robust this way.  */
261   if (data >= end || *data != '(')
262     return NULL;
263
264   /* Mark where the list starts.  */
265   list_start = data;
266
267   /* Skip the opening paren.  */
268   data++;
269
270   /* Parse the children.  */
271   {
272     svn_skel_t *children = NULL;
273     svn_skel_t **tail = &children;
274
275     for (;;)
276       {
277         svn_skel_t *element;
278
279         /* Skip any whitespace.  */
280         while (data < end
281                && skel_char_type[(unsigned char) *data] == type_space)
282           data++;
283
284         /* End of data, but no closing paren?  */
285         if (data >= end)
286           return NULL;
287
288         /* End of list?  */
289         if (*data == ')')
290           {
291             data++;
292             break;
293           }
294
295         /* Parse the next element in the list.  */
296         element = parse(data, end - data, pool);
297         if (! element)
298           return NULL;
299
300         /* Link that element into our list.  */
301         element->next = NULL;
302         *tail = element;
303         tail = &element->next;
304
305         /* Advance past that element.  */
306         data = element->data + element->len;
307       }
308
309     /* Construct the return value.  */
310     {
311       svn_skel_t *s = apr_pcalloc(pool, sizeof(*s));
312
313       s->is_atom = FALSE;
314       s->data = list_start;
315       s->len = data - list_start;
316       s->children = children;
317
318       return s;
319     }
320   }
321 }
322
323
324 /* Parse an atom with implicit length --- one that starts with a name
325    character, terminated by whitespace, '(', ')', or end-of-data.  */
326 static svn_skel_t *
327 implicit_atom(const char *data,
328               apr_size_t len,
329               apr_pool_t *pool)
330 {
331   const char *start = data;
332   const char *end = data + len;
333   svn_skel_t *s;
334
335   /* Verify that the atom starts with a name character.  At the
336      moment, all callers have checked this already, but it's more
337      robust this way.  */
338   if (data >= end || skel_char_type[(unsigned char) *data] != type_name)
339     return NULL;
340
341   /* Find the end of the string.  */
342   while (++data < end
343          && skel_char_type[(unsigned char) *data] != type_space
344          && skel_char_type[(unsigned char) *data] != type_paren)
345     ;
346
347   /* Allocate the skel representing this string.  */
348   s = apr_pcalloc(pool, sizeof(*s));
349   s->is_atom = TRUE;
350   s->data = start;
351   s->len = data - start;
352
353   return s;
354 }
355
356
357 /* Parse an atom with explicit length --- one that starts with a byte
358    length, as a decimal ASCII number.  */
359 static svn_skel_t *
360 explicit_atom(const char *data,
361               apr_size_t len,
362               apr_pool_t *pool)
363 {
364   const char *end = data + len;
365   const char *next;
366   apr_size_t size;
367   svn_skel_t *s;
368
369   /* Parse the length.  */
370   size = getsize(data, end - data, &next, end - data);
371   data = next;
372
373   /* Exit if we overflowed, or there wasn't a valid number there.  */
374   if (! data)
375     return NULL;
376
377   /* Skip the whitespace character after the length.  */
378   if (data >= end || skel_char_type[(unsigned char) *data] != type_space)
379     return NULL;
380   data++;
381
382   /* Check the length.  */
383   if (data + size > end)
384     return NULL;
385
386   /* Allocate the skel representing this string.  */
387   s = apr_pcalloc(pool, sizeof(*s));
388   s->is_atom = TRUE;
389   s->data = data;
390   s->len = size;
391
392   return s;
393 }
394
395
396 \f
397 /* Unparsing skeletons.  */
398
399 static apr_size_t estimate_unparsed_size(const svn_skel_t *skel);
400 static svn_stringbuf_t *unparse(const svn_skel_t *skel,
401                                 svn_stringbuf_t *str);
402
403
404 svn_stringbuf_t *
405 svn_skel__unparse(const svn_skel_t *skel, apr_pool_t *pool)
406 {
407   svn_stringbuf_t *str
408     = svn_stringbuf_create_ensure(estimate_unparsed_size(skel) + 200, pool);
409
410   return unparse(skel, str);
411 }
412
413
414 /* Return an estimate of the number of bytes that the external
415    representation of SKEL will occupy.  Since reallocing is expensive
416    in pools, it's worth trying to get the buffer size right the first
417    time.  */
418 static apr_size_t
419 estimate_unparsed_size(const svn_skel_t *skel)
420 {
421   if (skel->is_atom)
422     {
423       if (skel->len < 100)
424         /* If we have to use the explicit-length form, that'll be
425            two bytes for the length, one byte for the space, and
426            the contents.  */
427         return skel->len + 3;
428       else
429         return skel->len + 30;
430     }
431   else
432     {
433       apr_size_t total_len;
434       svn_skel_t *child;
435
436       /* Allow space for opening and closing parens, and a space
437          between each pair of elements.  */
438       total_len = 2;
439       for (child = skel->children; child; child = child->next)
440         total_len += estimate_unparsed_size(child) + 1;
441
442       return total_len;
443     }
444 }
445
446
447 /* Return non-zero iff we should use the implicit-length form for SKEL.
448    Assume that SKEL is an atom.  */
449 static svn_boolean_t
450 use_implicit(const svn_skel_t *skel)
451 {
452   /* If it's null, or long, we should use explicit-length form.  */
453   if (skel->len == 0
454       || skel->len >= 100)
455     return FALSE;
456
457   /* If it doesn't start with a name character, we must use
458      explicit-length form.  */
459   if (skel_char_type[(unsigned char) skel->data[0]] != type_name)
460     return FALSE;
461
462   /* If it contains any whitespace or parens, then we must use
463      explicit-length form.  */
464   {
465     apr_size_t i;
466
467     for (i = 1; i < skel->len; i++)
468       if (skel_char_type[(unsigned char) skel->data[i]] == type_space
469           || skel_char_type[(unsigned char) skel->data[i]] == type_paren)
470         return FALSE;
471   }
472
473   /* If we can't reject it for any of the above reasons, then we can
474      use implicit-length form.  */
475   return TRUE;
476 }
477
478
479 /* Append the concrete representation of SKEL to the string STR. */
480 static svn_stringbuf_t *
481 unparse(const svn_skel_t *skel, svn_stringbuf_t *str)
482 {
483   if (skel->is_atom)
484     {
485       /* Append an atom to STR.  */
486       if (use_implicit(skel))
487         svn_stringbuf_appendbytes(str, skel->data, skel->len);
488       else
489         {
490           /* Append the length to STR.  Ensure enough space for at least
491            * one 64 bit int. */
492           char buf[200 + SVN_INT64_BUFFER_SIZE];
493           apr_size_t length_len;
494
495           length_len = svn__ui64toa(buf, skel->len);
496
497           SVN_ERR_ASSERT_NO_RETURN(length_len > 0);
498
499           /* Make sure we have room for the length, the space, and the
500              atom's contents.  */
501           svn_stringbuf_ensure(str, str->len + length_len + 1 + skel->len);
502           svn_stringbuf_appendbytes(str, buf, length_len);
503           svn_stringbuf_appendbyte(str, ' ');
504           svn_stringbuf_appendbytes(str, skel->data, skel->len);
505         }
506     }
507   else
508     {
509       /* Append a list to STR: an opening parenthesis, the list elements
510        * separated by a space, and a closing parenthesis.  */
511       svn_skel_t *child;
512
513       svn_stringbuf_appendbyte(str, '(');
514
515       for (child = skel->children; child; child = child->next)
516         {
517           unparse(child, str);
518           if (child->next)
519             svn_stringbuf_appendbyte(str, ' ');
520         }
521
522       svn_stringbuf_appendbyte(str, ')');
523     }
524
525   return str;
526 }
527
528
529 \f
530 /* Building skels.  */
531
532
533 svn_skel_t *
534 svn_skel__str_atom(const char *str, apr_pool_t *pool)
535 {
536   svn_skel_t *skel = apr_pcalloc(pool, sizeof(*skel));
537   skel->is_atom = TRUE;
538   skel->data = str;
539   skel->len = strlen(str);
540
541   return skel;
542 }
543
544
545 svn_skel_t *
546 svn_skel__mem_atom(const void *addr,
547                    apr_size_t len,
548                    apr_pool_t *pool)
549 {
550   svn_skel_t *skel = apr_pcalloc(pool, sizeof(*skel));
551   skel->is_atom = TRUE;
552   skel->data = addr;
553   skel->len = len;
554
555   return skel;
556 }
557
558
559 svn_skel_t *
560 svn_skel__make_empty_list(apr_pool_t *pool)
561 {
562   svn_skel_t *skel = apr_pcalloc(pool, sizeof(*skel));
563   return skel;
564 }
565
566 svn_skel_t *svn_skel__dup(const svn_skel_t *src_skel, svn_boolean_t dup_data,
567                           apr_pool_t *result_pool)
568 {
569   svn_skel_t *skel = apr_pmemdup(result_pool, src_skel, sizeof(svn_skel_t));
570
571   if (dup_data && skel->data)
572     {
573       if (skel->is_atom)
574         skel->data = apr_pmemdup(result_pool, skel->data, skel->len);
575       else
576         {
577           /* When creating a skel this would be NULL, 0 for a list.
578              When parsing a string to a skel this might point to real data
579              delimiting the sublist. We don't copy that from here. */
580           skel->data = NULL;
581           skel->len = 0;
582         }
583     }
584
585   if (skel->children)
586     skel->children = svn_skel__dup(skel->children, dup_data, result_pool);
587
588   if (skel->next)
589     skel->next = svn_skel__dup(skel->next, dup_data, result_pool);
590
591   return skel;
592 }
593
594 void
595 svn_skel__prepend(svn_skel_t *skel, svn_skel_t *list_skel)
596 {
597   /* If list_skel isn't even a list, somebody's not using this
598      function properly. */
599   SVN_ERR_ASSERT_NO_RETURN(! list_skel->is_atom);
600
601   skel->next = list_skel->children;
602   list_skel->children = skel;
603 }
604
605
606 void svn_skel__prepend_int(apr_int64_t value,
607                            svn_skel_t *skel,
608                            apr_pool_t *result_pool)
609 {
610   char *val_string = apr_palloc(result_pool, SVN_INT64_BUFFER_SIZE);
611   svn__i64toa(val_string, value);
612
613   svn_skel__prepend_str(val_string, skel, result_pool);
614 }
615
616
617 void svn_skel__prepend_str(const char *value,
618                            svn_skel_t *skel,
619                            apr_pool_t *result_pool)
620 {
621   svn_skel_t *atom = svn_skel__str_atom(value, result_pool);
622
623   svn_skel__prepend(atom, skel);
624 }
625
626
627 void svn_skel__append(svn_skel_t *list_skel, svn_skel_t *skel)
628 {
629   SVN_ERR_ASSERT_NO_RETURN(list_skel != NULL && !list_skel->is_atom);
630
631   if (list_skel->children == NULL)
632     {
633       list_skel->children = skel;
634     }
635   else
636     {
637       list_skel = list_skel->children;
638       while (list_skel->next != NULL)
639         list_skel = list_skel->next;
640       list_skel->next = skel;
641     }
642 }
643
644 \f
645 /* Examining skels.  */
646
647
648 svn_boolean_t
649 svn_skel__matches_atom(const svn_skel_t *skel, const char *str)
650 {
651   if (skel && skel->is_atom)
652     {
653       apr_size_t len = strlen(str);
654
655       return (skel->len == len
656               && ! memcmp(skel->data, str, len));
657     }
658   return FALSE;
659 }
660
661
662 int
663 svn_skel__list_length(const svn_skel_t *skel)
664 {
665   int len = 0;
666   const svn_skel_t *child;
667
668   if ((! skel) || skel->is_atom)
669     return -1;
670
671   for (child = skel->children; child; child = child->next)
672     len++;
673
674   return len;
675 }
676
677
678 \f
679 /* Parsing and unparsing into high-level types. */
680
681 svn_error_t *
682 svn_skel__parse_int(apr_int64_t *n, const svn_skel_t *skel,
683                     apr_pool_t *scratch_pool)
684 {
685   const char *str;
686
687   /* We need to duplicate the SKEL contents in order to get a NUL-terminated
688      version of it. The SKEL may not have valid memory at DATA[LEN].  */
689   str = apr_pstrmemdup(scratch_pool, skel->data, skel->len);
690   return svn_error_trace(svn_cstring_atoi64(n, str));
691 }
692
693
694 svn_error_t *
695 svn_skel__parse_proplist(apr_hash_t **proplist_p,
696                          const svn_skel_t *skel,
697                          apr_pool_t *pool /* result_pool */)
698 {
699   apr_hash_t *proplist = NULL;
700   svn_skel_t *elt;
701
702   /* Validate the skel. */
703   if (! is_valid_proplist_skel(skel))
704     return skel_err("proplist");
705
706   /* Create the returned structure */
707   proplist = apr_hash_make(pool);
708   for (elt = skel->children; elt; elt = elt->next->next)
709     {
710       svn_string_t *value = svn_string_ncreate(elt->next->data,
711                                                elt->next->len, pool);
712       apr_hash_set(proplist,
713                    apr_pstrmemdup(pool, elt->data, elt->len),
714                    elt->len,
715                    value);
716     }
717
718   /* Return the structure. */
719   *proplist_p = proplist;
720   return SVN_NO_ERROR;
721 }
722
723 svn_error_t *
724 svn_skel__parse_iprops(apr_array_header_t **iprops,
725                        const svn_skel_t *skel,
726                        apr_pool_t *result_pool)
727 {
728   svn_skel_t *elt;
729
730   /* Validate the skel. */
731   if (! is_valid_iproplist_skel(skel))
732     return skel_err("iprops");
733
734   /* Create the returned structure */
735   *iprops = apr_array_make(result_pool, 1,
736                            sizeof(svn_prop_inherited_item_t *));
737
738   for (elt = skel->children; elt; elt = elt->next->next)
739     {
740       svn_prop_inherited_item_t *new_iprop = apr_palloc(result_pool,
741                                                         sizeof(*new_iprop));
742       svn_string_t *repos_parent = svn_string_ncreate(elt->data, elt->len,
743                                                       result_pool);
744       SVN_ERR(svn_skel__parse_proplist(&(new_iprop->prop_hash), elt->next,
745                                        result_pool));
746       new_iprop->path_or_url = repos_parent->data;
747       APR_ARRAY_PUSH(*iprops, svn_prop_inherited_item_t *) = new_iprop;
748     }
749   return SVN_NO_ERROR;
750 }
751
752 svn_error_t *
753 svn_skel__parse_prop(svn_string_t **propval,
754                      const svn_skel_t *skel,
755                      const char *propname,
756                      apr_pool_t *pool /* result_pool */)
757 {
758   svn_skel_t *elt;
759
760   *propval = NULL;
761
762   /* Validate the skel. */
763   if (! is_valid_proplist_skel(skel))
764     return skel_err("proplist");
765
766   /* Look for PROPNAME in SKEL. */
767   for (elt = skel->children; elt; elt = elt->next->next)
768     {
769       if (elt->len == strlen(propname)
770           && strncmp(propname, elt->data, elt->len) == 0)
771         {
772           *propval = svn_string_ncreate(elt->next->data, elt->next->len,
773                                         pool);
774           break;
775         }
776       else
777         {
778           continue;
779         }
780     }
781   return SVN_NO_ERROR;
782 }
783
784
785 svn_error_t *
786 svn_skel__unparse_proplist(svn_skel_t **skel_p,
787                            const apr_hash_t *proplist,
788                            apr_pool_t *pool)
789 {
790   svn_skel_t *skel = svn_skel__make_empty_list(pool);
791   apr_hash_index_t *hi;
792
793   /* Create the skel. */
794   if (proplist)
795     {
796       /* Loop over hash entries */
797       for (hi = apr_hash_first(pool, (apr_hash_t *)proplist); hi;
798            hi = apr_hash_next(hi))
799         {
800           const void *key;
801           void *val;
802           apr_ssize_t klen;
803           svn_string_t *value;
804
805           apr_hash_this(hi, &key, &klen, &val);
806           value = val;
807
808           /* VALUE */
809           svn_skel__prepend(svn_skel__mem_atom(value->data, value->len, pool),
810                             skel);
811
812           /* NAME */
813           svn_skel__prepend(svn_skel__mem_atom(key, klen, pool), skel);
814         }
815     }
816
817   /* Validate and return the skel. */
818   if (! is_valid_proplist_skel(skel))
819     return skel_err("proplist");
820   *skel_p = skel;
821   return SVN_NO_ERROR;
822 }
823
824 svn_error_t *
825 svn_skel__unparse_iproplist(svn_skel_t **skel_p,
826                             const apr_array_header_t *inherited_props,
827                             apr_pool_t *result_pool,
828                             apr_pool_t *scratch_pool)
829 {
830   svn_skel_t *skel = svn_skel__make_empty_list(result_pool);
831
832   /* Create the skel. */
833   if (inherited_props)
834     {
835       int i;
836       apr_hash_index_t *hi;
837
838       for (i = 0; i < inherited_props->nelts; i++)
839         {
840           svn_prop_inherited_item_t *iprop =
841             APR_ARRAY_IDX(inherited_props, i, svn_prop_inherited_item_t *);
842
843           svn_skel_t *skel_list = svn_skel__make_empty_list(result_pool);
844           svn_skel_t *skel_atom;
845
846           /* Loop over hash entries */
847           for (hi = apr_hash_first(scratch_pool, iprop->prop_hash);
848                hi;
849                hi = apr_hash_next(hi))
850             {
851               const void *key;
852               void *val;
853               apr_ssize_t klen;
854               svn_string_t *value;
855
856               apr_hash_this(hi, &key, &klen, &val);
857               value = val;
858
859               /* VALUE */
860               svn_skel__prepend(svn_skel__mem_atom(value->data, value->len,
861                                                    result_pool), skel_list);
862
863               /* NAME */
864               svn_skel__prepend(svn_skel__mem_atom(key, klen, result_pool),
865                                 skel_list);
866             }
867
868           skel_atom = svn_skel__str_atom(
869             apr_pstrdup(result_pool, iprop->path_or_url), result_pool);
870           svn_skel__append(skel, skel_atom);
871           svn_skel__append(skel, skel_list);
872         }
873     }
874
875   /* Validate and return the skel. */
876   if (! is_valid_iproplist_skel(skel))
877     return skel_err("iproplist");
878
879   *skel_p = skel;
880   return SVN_NO_ERROR;
881 }