skel.c revision 362181
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
32/* Parsing skeletons.  */
33
34enum 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
47static 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
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.  */
89static apr_size_t
90getsize(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
142/* Checking validity of skels. */
143static svn_error_t *
144skel_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
153static svn_boolean_t
154is_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
172static svn_boolean_t
173is_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
202static svn_skel_t *parse(const char *data, apr_size_t len,
203                         apr_pool_t *pool);
204static svn_skel_t *list(const char *data, apr_size_t len,
205                        apr_pool_t *pool);
206static svn_skel_t *implicit_atom(const char *data, apr_size_t len,
207                                 apr_pool_t *pool);
208static svn_skel_t *explicit_atom(const char *data, apr_size_t len,
209                                 apr_pool_t *pool);
210
211
212svn_skel_t *
213svn_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.  */
222static svn_skel_t *
223parse(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
250static svn_skel_t *
251list(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.  */
326static svn_skel_t *
327implicit_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.  */
359static svn_skel_t *
360explicit_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 (end - data < size)
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
397/* Unparsing skeletons.  */
398
399static apr_size_t estimate_unparsed_size(const svn_skel_t *skel);
400static svn_stringbuf_t *unparse(const svn_skel_t *skel,
401                                svn_stringbuf_t *str);
402
403
404svn_stringbuf_t *
405svn_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.  */
418static apr_size_t
419estimate_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.  */
449static svn_boolean_t
450use_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. */
480static svn_stringbuf_t *
481unparse(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
530/* Building skels.  */
531
532
533svn_skel_t *
534svn_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
545svn_skel_t *
546svn_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
559svn_skel_t *
560svn_skel__make_empty_list(apr_pool_t *pool)
561{
562  svn_skel_t *skel = apr_pcalloc(pool, sizeof(*skel));
563  return skel;
564}
565
566svn_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
594void
595svn_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
606void 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
617void 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
627void 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
645/* Examining skels.  */
646
647
648svn_boolean_t
649svn_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
662int
663svn_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
679/* Parsing and unparsing into high-level types. */
680
681svn_error_t *
682svn_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
694svn_error_t *
695svn_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
723svn_error_t *
724svn_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
752svn_error_t *
753svn_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
785svn_error_t *
786svn_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
824svn_error_t *
825svn_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}
882