1 /*        $NetBSD: prop_object.c,v 1.39 2025/04/26 17:13:23 thorpej Exp $       */
2 
3 /*-
4  * Copyright (c) 2006, 2007, 2025 The NetBSD Foundation, Inc.
5  * All rights reserved.
6  *
7  * This code is derived from software contributed to The NetBSD Foundation
8  * by Jason R. Thorpe.
9  *
10  * Redistribution and use in source and binary forms, with or without
11  * modification, are permitted provided that the following conditions
12  * are met:
13  * 1. Redistributions of source code must retain the above copyright
14  *    notice, this list of conditions and the following disclaimer.
15  * 2. Redistributions in binary form must reproduce the above copyright
16  *    notice, this list of conditions and the following disclaimer in the
17  *    documentation and/or other materials provided with the distribution.
18  *
19  * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
20  * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
21  * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
22  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
23  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
24  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
25  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
26  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
27  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
28  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
29  * POSSIBILITY OF SUCH DAMAGE.
30  */
31 
32 #include "prop_object_impl.h"
33 #include <prop/prop_object.h>
34 
35 #ifdef _PROP_NEED_REFCNT_MTX
36 static pthread_mutex_t _prop_refcnt_mtx = PTHREAD_MUTEX_INITIALIZER;
37 #endif /* _PROP_NEED_REFCNT_MTX */
38 
39 #if !defined(_KERNEL) && !defined(_STANDALONE)
40 #include <sys/mman.h>
41 #include <sys/stat.h>
42 #include <errno.h>
43 #include <fcntl.h>
44 #include <limits.h>
45 #include <unistd.h>
46 #endif
47 
48 #ifdef _STANDALONE
49 void *
_prop_standalone_calloc(size_t size)50 _prop_standalone_calloc(size_t size)
51 {
52           void *rv;
53 
54           rv = alloc(size);
55           if (rv != NULL)
56                     memset(rv, 0, size);
57 
58           return (rv);
59 }
60 
61 void *
_prop_standalone_realloc(void * v,size_t size)62 _prop_standalone_realloc(void *v, size_t size)
63 {
64           void *rv;
65 
66           rv = alloc(size);
67           if (rv != NULL) {
68                     memcpy(rv, v, size);          /* XXX */
69                     dealloc(v, 0);                /* XXX */
70           }
71 
72           return (rv);
73 }
74 #endif /* _STANDALONE */
75 
76 /*
77  * _prop_object_init --
78  *        Initialize an object.  Called when sub-classes create
79  *        an instance.
80  */
81 void
_prop_object_init(struct _prop_object * po,const struct _prop_object_type * pot)82 _prop_object_init(struct _prop_object *po, const struct _prop_object_type *pot)
83 {
84 
85           po->po_type = pot;
86           po->po_refcnt = 1;
87 }
88 
89 /*
90  * _prop_object_fini --
91  *        Finalize an object.  Called when sub-classes destroy
92  *        an instance.
93  */
94 /*ARGSUSED*/
95 void
_prop_object_fini(struct _prop_object * po _PROP_ARG_UNUSED)96 _prop_object_fini(struct _prop_object *po _PROP_ARG_UNUSED)
97 {
98           /* Nothing to do, currently. */
99 }
100 
101 /*
102  * _prop_object_externalize_start_line --
103  *        Append the start-of-line character sequence.
104  */
105 bool
_prop_object_externalize_start_line(struct _prop_object_externalize_context * ctx)106 _prop_object_externalize_start_line(
107     struct _prop_object_externalize_context *ctx)
108 {
109           unsigned int i;
110 
111           for (i = 0; i < ctx->poec_depth; i++) {
112                     if (_prop_object_externalize_append_char(ctx, '\t') == false) {
113                               return false;
114                     }
115           }
116           return true;
117 }
118 
119 /*
120  * _prop_object_externalize_end_line --
121  *        Append the end-of-line character sequence.
122  */
123 bool
_prop_object_externalize_end_line(struct _prop_object_externalize_context * ctx,const char * trailer)124 _prop_object_externalize_end_line(
125     struct _prop_object_externalize_context *ctx, const char *trailer)
126 {
127           if (trailer != NULL &&
128               _prop_object_externalize_append_cstring(ctx, trailer) == false) {
129                     return false;
130           }
131           return _prop_object_externalize_append_char(ctx, '\n');
132 }
133 
134 /*
135  * _prop_object_externalize_start_tag --
136  *        Append an item's start tag to the externalize buffer.
137  */
138 bool
_prop_object_externalize_start_tag(struct _prop_object_externalize_context * ctx,const struct _prop_object_type_tags * tags,const char * tagattrs)139 _prop_object_externalize_start_tag(
140     struct _prop_object_externalize_context *ctx,
141     const struct _prop_object_type_tags *tags,
142     const char *tagattrs)
143 {
144           bool rv;
145 
146           _PROP_ASSERT(ctx->poec_format == PROP_FORMAT_XML ||
147                          ctx->poec_format == PROP_FORMAT_JSON);
148 
149           switch (ctx->poec_format) {
150           case PROP_FORMAT_JSON:
151                     rv = tags->json_open_tag == NULL ||
152                          _prop_object_externalize_append_cstring(ctx,
153                                                                       tags->json_open_tag);
154                     break;
155 
156           default:            /* XML */
157                     rv = _prop_object_externalize_append_char(ctx, '<') &&
158                          _prop_object_externalize_append_cstring(ctx,
159                                                                            tags->xml_tag) &&
160                          (tagattrs == NULL ||
161                           (_prop_object_externalize_append_char(ctx, ' ') &&
162                            _prop_object_externalize_append_cstring(ctx,
163                                                                              tagattrs))) &&
164                          _prop_object_externalize_append_char(ctx, '>');
165                     break;
166           }
167 
168           return rv;
169 }
170 
171 /*
172  * _prop_object_externalize_end_tag --
173  *        Append an item's end tag to the externalize buffer.
174  */
175 bool
_prop_object_externalize_end_tag(struct _prop_object_externalize_context * ctx,const struct _prop_object_type_tags * tags)176 _prop_object_externalize_end_tag(
177     struct _prop_object_externalize_context *ctx,
178     const struct _prop_object_type_tags *tags)
179 {
180           bool rv;
181 
182           _PROP_ASSERT(ctx->poec_format == PROP_FORMAT_XML ||
183                          ctx->poec_format == PROP_FORMAT_JSON);
184 
185           switch (ctx->poec_format) {
186           case PROP_FORMAT_JSON:
187                     rv = tags->json_close_tag == NULL ||
188                          _prop_object_externalize_append_cstring(ctx,
189                                                                       tags->json_close_tag);
190                     break;
191 
192           default:            /* XML */
193                     rv = _prop_object_externalize_append_char(ctx, '<') &&
194                          _prop_object_externalize_append_char(ctx, '/') &&
195                          _prop_object_externalize_append_cstring(ctx,
196                                                                            tags->xml_tag) &&
197                          _prop_object_externalize_append_char(ctx, '>');
198                     break;
199           }
200 
201           return rv;
202 }
203 
204 /*
205  * _prop_object_externalize_empty_tag --
206  *        Append an item's empty tag to the externalize buffer.
207  */
208 bool
_prop_object_externalize_empty_tag(struct _prop_object_externalize_context * ctx,const struct _prop_object_type_tags * tags)209 _prop_object_externalize_empty_tag(
210     struct _prop_object_externalize_context *ctx,
211     const struct _prop_object_type_tags *tags)
212 {
213           bool rv;
214 
215           _PROP_ASSERT(ctx->poec_format == PROP_FORMAT_XML ||
216                          ctx->poec_format == PROP_FORMAT_JSON);
217 
218           switch (ctx->poec_format) {
219           case PROP_FORMAT_JSON:
220                     if (tags->json_open_tag == NULL ||
221                         _prop_object_externalize_append_cstring(ctx,
222                                                   tags->json_open_tag) == false) {
223                               return false;
224                     }
225                     if (tags->json_empty_sep != NULL &&
226                         _prop_object_externalize_append_cstring(ctx,
227                                                   tags->json_empty_sep) == false) {
228                               return false;
229                     }
230                     if (tags->json_close_tag != NULL) {
231                               rv = _prop_object_externalize_append_cstring(ctx,
232                                                                       tags->json_close_tag);
233                     } else {
234                               rv = true;
235                     }
236                     break;
237 
238           default:            /* XML */
239                     rv = _prop_object_externalize_append_char(ctx, '<') &&
240                          _prop_object_externalize_append_cstring(ctx,
241                                                                            tags->xml_tag) &&
242                          _prop_object_externalize_append_char(ctx, '/') &&
243                          _prop_object_externalize_append_char(ctx, '>');
244                     break;
245           }
246 
247           return rv;
248 }
249 
250 /*
251  * _prop_object_externalize_append_cstring --
252  *        Append a C string to the externalize buffer.
253  */
254 bool
_prop_object_externalize_append_cstring(struct _prop_object_externalize_context * ctx,const char * cp)255 _prop_object_externalize_append_cstring(
256     struct _prop_object_externalize_context *ctx, const char *cp)
257 {
258 
259           while (*cp != '\0') {
260                     if (_prop_object_externalize_append_char(ctx,
261                                                             (unsigned char) *cp) == false)
262                               return (false);
263                     cp++;
264           }
265 
266           return (true);
267 }
268 
269 /*
270  * _prop_object_externalize_append_encoded_cstring --
271  *        Append an encoded C string to the externalize buffer.
272  */
273 static bool
_prop_object_externalize_append_encoded_cstring_xml(struct _prop_object_externalize_context * ctx,const char * cp)274 _prop_object_externalize_append_encoded_cstring_xml(
275     struct _prop_object_externalize_context *ctx, const char *cp)
276 {
277 
278           while (*cp != '\0') {
279                     switch (*cp) {
280                     case '<':
281                               if (_prop_object_externalize_append_cstring(ctx,
282                                                   "&lt;") == false)
283                                         return (false);
284                               break;
285                     case '>':
286                               if (_prop_object_externalize_append_cstring(ctx,
287                                                   "&gt;") == false)
288                                         return (false);
289                               break;
290                     case '&':
291                               if (_prop_object_externalize_append_cstring(ctx,
292                                                   "&amp;") == false)
293                                         return (false);
294                               break;
295                     default:
296                               if (_prop_object_externalize_append_char(ctx,
297                                                   (unsigned char) *cp) == false)
298                                         return (false);
299                               break;
300                     }
301                     cp++;
302           }
303 
304           return (true);
305 }
306 
307 static bool
_prop_object_externalize_append_escu(struct _prop_object_externalize_context * ctx,uint16_t val)308 _prop_object_externalize_append_escu(
309     struct _prop_object_externalize_context *ctx, uint16_t val)
310 {
311           char tmpstr[sizeof("\\uXXXX")];
312 
313           snprintf(tmpstr, sizeof(tmpstr), "\\u%04X", val);
314           return _prop_object_externalize_append_cstring(ctx, tmpstr);
315 }
316 
317 static bool
_prop_object_externalize_append_encoded_cstring_json(struct _prop_object_externalize_context * ctx,const char * cp)318 _prop_object_externalize_append_encoded_cstring_json(
319     struct _prop_object_externalize_context *ctx, const char *cp)
320 {
321           bool esc;
322           unsigned char ch;
323 
324           while ((ch = *cp) != '\0') {
325                     esc = true;
326                     switch (ch) {
327                     /*
328                      * First, the two explicit exclusions.  They must be
329                      * escaped.
330                      */
331                     case '"': /* U+0022 quotation mark */
332                               goto emit;
333 
334                     case '\\':          /* U+005C reverse solidus */
335                               goto emit;
336 
337                     /*
338                      * And some special cases that are explcit in the grammar.
339                      */
340                     case '/': /* U+002F solidus (XXX this one seems silly) */
341                               goto emit;
342 
343                     case 0x08:          /* U+0008 backspace */
344                               ch = 'b';
345                               goto emit;
346 
347                     case 0x0c:          /* U+000C form feed */
348                               ch = 'f';
349                               goto emit;
350 
351                     case 0x0a:          /* U+000A line feed */
352                               ch = 'n';
353                               goto emit;
354 
355                     case 0x0d:          /* U+000D carriage return */
356                               ch = 'r';
357                               goto emit;
358 
359                     case 0x09:          /* U+0009 tab */
360                               ch = 't';
361                               goto emit;
362 
363                     default:
364                               /*
365                                * \u-escape all other single-byte ASCII control
366                                * characters, per RFC 8259:
367                                *
368                                * <quote>
369                                * All Unicode characters may be placed within the
370                                * quotation marks, except for the characters that
371                                * MUST be escaped: quotation mark, reverse solidus,
372                                * and the control characters (U+0000 through U+001F).
373                                * </quote>
374                                */
375                               if (ch < 0x20) {
376                                         if (_prop_object_externalize_append_escu(ctx,
377                                                                       ch) == false) {
378                                                   return false;
379                                         }
380                                         break;
381                               }
382                               /*
383                                * We're going to just treat everything else like
384                                * UTF-8 (we've been handed a C-string, after all)
385                                * and pretend it will all be OK.
386                                */
387                               esc = false;
388                     emit:
389                               if ((esc && _prop_object_externalize_append_char(ctx,
390                                                                       '\\') == false) ||
391                                   _prop_object_externalize_append_char(ctx,
392                                                                       ch) == false) {
393                                         return false;
394                               }
395                               break;
396                     }
397                     cp++;
398           }
399 
400           return true;
401 }
402 
403 bool
_prop_object_externalize_append_encoded_cstring(struct _prop_object_externalize_context * ctx,const char * cp)404 _prop_object_externalize_append_encoded_cstring(
405     struct _prop_object_externalize_context *ctx, const char *cp)
406 {
407           _PROP_ASSERT(ctx->poec_format == PROP_FORMAT_XML ||
408                          ctx->poec_format == PROP_FORMAT_JSON);
409 
410           switch (ctx->poec_format) {
411           case PROP_FORMAT_JSON:
412                     return _prop_object_externalize_append_encoded_cstring_json(ctx,
413                                                                                               cp);
414           default:
415                     return _prop_object_externalize_append_encoded_cstring_xml(ctx,
416                                                                                              cp);
417           }
418 }
419 
420 #define   BUF_EXPAND                    256
421 
422 /*
423  * _prop_object_externalize_append_char --
424  *        Append a single character to the externalize buffer.
425  */
426 bool
_prop_object_externalize_append_char(struct _prop_object_externalize_context * ctx,unsigned char c)427 _prop_object_externalize_append_char(
428     struct _prop_object_externalize_context *ctx, unsigned char c)
429 {
430 
431           _PROP_ASSERT(ctx->poec_capacity != 0);
432           _PROP_ASSERT(ctx->poec_buf != NULL);
433           _PROP_ASSERT(ctx->poec_len <= ctx->poec_capacity);
434 
435           if (ctx->poec_len == ctx->poec_capacity) {
436                     char *cp = _PROP_REALLOC(ctx->poec_buf,
437                                                    ctx->poec_capacity + BUF_EXPAND,
438                                                    M_TEMP);
439                     if (cp == NULL)
440                               return (false);
441                     ctx->poec_capacity = ctx->poec_capacity + BUF_EXPAND;
442                     ctx->poec_buf = cp;
443           }
444 
445           ctx->poec_buf[ctx->poec_len++] = c;
446 
447           return (true);
448 }
449 
450 static const struct _prop_object_type_tags _plist_type_tags = {
451           .xml_tag  =         "plist",
452 };
453 
454 /*
455  * _prop_object_externalize_header --
456  *        Append the standard XML header to the externalize buffer.
457  */
458 bool
_prop_object_externalize_header(struct _prop_object_externalize_context * ctx)459 _prop_object_externalize_header(struct _prop_object_externalize_context *ctx)
460 {
461           static const char _plist_xml_header[] =
462 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
463 "<!DOCTYPE plist PUBLIC \"-//Apple Computer//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n";
464 
465           if (ctx->poec_format != PROP_FORMAT_XML) {
466                     return true;
467           }
468 
469           if (_prop_object_externalize_append_cstring(ctx,
470                                                              _plist_xml_header) == false ||
471               _prop_object_externalize_start_tag(ctx,
472                                                   &_plist_type_tags,
473                                                   "version=\"1.0\"") == false ||
474               _prop_object_externalize_append_char(ctx, '\n') == false)
475                     return (false);
476 
477           return (true);
478 }
479 
480 /*
481  * _prop_object_externalize_footer --
482  *        Append the standard XML footer to the externalize buffer.  This
483  *        also NUL-terminates the buffer.
484  */
485 bool
_prop_object_externalize_footer(struct _prop_object_externalize_context * ctx)486 _prop_object_externalize_footer(struct _prop_object_externalize_context *ctx)
487 {
488           if (_prop_object_externalize_end_line(ctx, NULL) == false) {
489                     return false;
490           }
491 
492           if (ctx->poec_format == PROP_FORMAT_XML) {
493                     if (_prop_object_externalize_end_tag(ctx,
494                                                   &_plist_type_tags) == false ||
495                         _prop_object_externalize_end_line(ctx, NULL) == false) {
496                               return false;
497                     }
498           }
499 
500           return _prop_object_externalize_append_char(ctx, '\0');
501 }
502 
503 /*
504  * _prop_object_externalize_context_alloc --
505  *        Allocate an externalize context.
506  */
507 struct _prop_object_externalize_context *
_prop_object_externalize_context_alloc(prop_format_t fmt)508 _prop_object_externalize_context_alloc(prop_format_t fmt)
509 {
510           struct _prop_object_externalize_context *ctx;
511 
512           ctx = _PROP_MALLOC(sizeof(*ctx), M_TEMP);
513           if (ctx != NULL) {
514                     ctx->poec_buf = _PROP_MALLOC(BUF_EXPAND, M_TEMP);
515                     if (ctx->poec_buf == NULL) {
516                               _PROP_FREE(ctx, M_TEMP);
517                               return (NULL);
518                     }
519                     ctx->poec_len = 0;
520                     ctx->poec_capacity = BUF_EXPAND;
521                     ctx->poec_depth = 0;
522                     ctx->poec_format = fmt;
523           }
524           return (ctx);
525 }
526 
527 /*
528  * _prop_object_externalize_context_free --
529  *        Free an externalize context.
530  */
531 void
_prop_object_externalize_context_free(struct _prop_object_externalize_context * ctx)532 _prop_object_externalize_context_free(
533                     struct _prop_object_externalize_context *ctx)
534 {
535 
536           /* Buffer is always freed by the caller. */
537           _PROP_FREE(ctx, M_TEMP);
538 }
539 
540 /*
541  * _prop_object_externalize --
542  *        Externalize an object, returning a NUL-terminated buffer
543  *        containing the serialized data in either XML or JSON format.
544  *        The buffer is allocated with the M_TEMP memory type.
545  */
546 char *
_prop_object_externalize(struct _prop_object * obj,prop_format_t fmt)547 _prop_object_externalize(struct _prop_object *obj, prop_format_t fmt)
548 {
549           struct _prop_object_externalize_context *ctx;
550           char *cp = NULL;
551 
552           if (obj == NULL || obj->po_type->pot_extern == NULL) {
553                     return NULL;
554           }
555           if (fmt != PROP_FORMAT_XML && fmt != PROP_FORMAT_JSON) {
556                     return NULL;
557           }
558 
559           ctx = _prop_object_externalize_context_alloc(fmt);
560           if (ctx == NULL) {
561                     return NULL;
562           }
563 
564           if (_prop_object_externalize_header(ctx) == false ||
565               obj->po_type->pot_extern(ctx, obj) == false ||
566               _prop_object_externalize_footer(ctx) == false) {
567                     /* We are responsible for releasing the buffer. */
568                     _PROP_FREE(ctx->poec_buf, M_TEMP);
569                     goto bad;
570           }
571 
572           cp = ctx->poec_buf;
573  bad:
574           _prop_object_externalize_context_free(ctx);
575           return cp;
576 }
577 
578 /*
579  * _prop_object_internalize_skip_comment --
580  *        Skip the body and end tag of a comment.
581  */
582 static bool
_prop_object_internalize_skip_comment(struct _prop_object_internalize_context * ctx)583 _prop_object_internalize_skip_comment(
584                                         struct _prop_object_internalize_context *ctx)
585 {
586           const char *cp = ctx->poic_cp;
587 
588           while (!_PROP_EOF(*cp)) {
589                     if (cp[0] == '-' &&
590                         cp[1] == '-' &&
591                         cp[2] == '>') {
592                               ctx->poic_cp = cp + 3;
593                               return (true);
594                     }
595                     cp++;
596           }
597 
598           return (false);               /* ran out of buffer */
599 }
600 
601 /*
602  * _prop_object_internalize_find_tag --
603  *        Find the next tag in an XML stream.  Optionally compare the found
604  *        tag to an expected tag name.  State of the context is undefined
605  *        if this routine returns false.  Upon success, the context points
606  *        to the first octet after the tag.
607  */
608 bool
_prop_object_internalize_find_tag(struct _prop_object_internalize_context * ctx,const char * tag,_prop_tag_type_t type)609 _prop_object_internalize_find_tag(struct _prop_object_internalize_context *ctx,
610                           const char *tag, _prop_tag_type_t type)
611 {
612           const char *cp;
613           size_t taglen;
614 
615           if (tag != NULL)
616                     taglen = strlen(tag);
617           else
618                     taglen = 0;
619 
620  start_over:
621           cp = ctx->poic_cp;
622 
623           /*
624            * Find the start of the tag.
625            */
626           cp = _prop_object_internalize_skip_whitespace(cp);
627           if (*cp != '<')
628                     return (false);
629 
630           ctx->poic_tag_start = cp++;
631           if (_PROP_EOF(*cp))
632                     return (false);
633 
634           if (*cp == '!') {
635                     if (cp[1] != '-' || cp[2] != '-')
636                               return (false);
637                     /*
638                      * Comment block -- only allowed if we are allowed to
639                      * return a start tag.
640                      */
641                     if (type == _PROP_TAG_TYPE_END)
642                               return (false);
643                     ctx->poic_cp = cp + 3;
644                     if (_prop_object_internalize_skip_comment(ctx) == false)
645                               return (false);
646                     goto start_over;
647           }
648 
649           if (*cp == '/') {
650                     if (type != _PROP_TAG_TYPE_END &&
651                         type != _PROP_TAG_TYPE_EITHER)
652                               return (false);
653                     cp++;
654                     if (_PROP_EOF(*cp))
655                               return (false);
656                     ctx->poic_tag_type = _PROP_TAG_TYPE_END;
657           } else {
658                     if (type != _PROP_TAG_TYPE_START &&
659                         type != _PROP_TAG_TYPE_EITHER)
660                               return (false);
661                     ctx->poic_tag_type = _PROP_TAG_TYPE_START;
662           }
663 
664           ctx->poic_tagname = cp;
665 
666           while (!_PROP_ISSPACE(*cp) && *cp != '/' && *cp != '>') {
667                     if (_PROP_EOF(*cp))
668                               return (false);
669                     cp++;
670           }
671 
672           ctx->poic_tagname_len = cp - ctx->poic_tagname;
673 
674           /* Make sure this is the tag we're looking for. */
675           if (tag != NULL &&
676               (taglen != ctx->poic_tagname_len ||
677                memcmp(tag, ctx->poic_tagname, taglen) != 0))
678                     return (false);
679 
680           /* Check for empty tag. */
681           if (*cp == '/') {
682                     if (ctx->poic_tag_type != _PROP_TAG_TYPE_START)
683                               return(false);                /* only valid on start tags */
684                     ctx->poic_is_empty_element = true;
685                     cp++;
686                     if (_PROP_EOF(*cp) || *cp != '>')
687                               return (false);
688           } else
689                     ctx->poic_is_empty_element = false;
690 
691           /* Easy case of no arguments. */
692           if (*cp == '>') {
693                     ctx->poic_tagattr = NULL;
694                     ctx->poic_tagattr_len = 0;
695                     ctx->poic_tagattrval = NULL;
696                     ctx->poic_tagattrval_len = 0;
697                     ctx->poic_cp = cp + 1;
698                     return (true);
699           }
700 
701           _PROP_ASSERT(!_PROP_EOF(*cp));
702           cp++;
703           if (_PROP_EOF(*cp))
704                     return (false);
705 
706           cp = _prop_object_internalize_skip_whitespace(cp);
707           if (_PROP_EOF(*cp))
708                     return (false);
709 
710           ctx->poic_tagattr = cp;
711 
712           while (!_PROP_ISSPACE(*cp) && *cp != '=') {
713                     if (_PROP_EOF(*cp))
714                               return (false);
715                     cp++;
716           }
717 
718           ctx->poic_tagattr_len = cp - ctx->poic_tagattr;
719 
720           cp++;
721           if (*cp != '\"')
722                     return (false);
723           cp++;
724           if (_PROP_EOF(*cp))
725                     return (false);
726 
727           ctx->poic_tagattrval = cp;
728           while (*cp != '\"') {
729                     if (_PROP_EOF(*cp))
730                               return (false);
731                     cp++;
732           }
733           ctx->poic_tagattrval_len = cp - ctx->poic_tagattrval;
734 
735           cp++;
736           if (*cp != '>')
737                     return (false);
738 
739           ctx->poic_cp = cp + 1;
740           return (true);
741 }
742 
743 /*
744  * _prop_object_internalize_decode_string --
745  *        Decode an encoded string.
746  */
747 
748 #define   ADDCHAR(x)                                                                      \
749           do {                                                                            \
750                     if (target) {                                                         \
751                               if (tarindex >= targsize) {                       \
752                                         return false;                                     \
753                               }                                                           \
754                               target[tarindex] = (x);                                     \
755                     }                                                                     \
756                     tarindex++;                                                           \
757           } while (/*CONSTCOND*/0)
758 
759 static bool
_prop_object_internalize_decode_string_xml(struct _prop_object_internalize_context * ctx,char * target,size_t targsize,size_t * sizep,const char ** cpp)760 _prop_object_internalize_decode_string_xml(
761                                         struct _prop_object_internalize_context *ctx,
762                                         char *target, size_t targsize, size_t *sizep,
763                                         const char **cpp)
764 {
765           const char *src;
766           size_t tarindex;
767           char c;
768 
769           tarindex = 0;
770           src = ctx->poic_cp;
771 
772           for (;;) {
773                     if (_PROP_EOF(*src))
774                               return (false);
775                     if (*src == '<') {
776                               break;
777                     }
778 
779                     if ((c = *src) == '&') {
780                               if (src[1] == 'a' &&
781                                   src[2] == 'm' &&
782                                   src[3] == 'p' &&
783                                   src[4] == ';') {
784                                         c = '&';
785                                         src += 5;
786                               } else if (src[1] == 'l' &&
787                                            src[2] == 't' &&
788                                            src[3] == ';') {
789                                         c = '<';
790                                         src += 4;
791                               } else if (src[1] == 'g' &&
792                                            src[2] == 't' &&
793                                            src[3] == ';') {
794                                         c = '>';
795                                         src += 4;
796                               } else if (src[1] == 'a' &&
797                                            src[2] == 'p' &&
798                                            src[3] == 'o' &&
799                                            src[4] == 's' &&
800                                            src[5] == ';') {
801                                         c = '\'';
802                                         src += 6;
803                               } else if (src[1] == 'q' &&
804                                            src[2] == 'u' &&
805                                            src[3] == 'o' &&
806                                            src[4] == 't' &&
807                                            src[5] == ';') {
808                                         c = '\"';
809                                         src += 6;
810                               } else
811                                         return (false);
812                     } else
813                               src++;
814                     ADDCHAR(c);
815           }
816 
817           _PROP_ASSERT(*src == '<');
818           if (sizep != NULL)
819                     *sizep = tarindex;
820           if (cpp != NULL)
821                     *cpp = src;
822 
823           return (true);
824 }
825 
826 static unsigned int
_prop_object_decode_string_uesc_getu16(const char * src,unsigned int idx,uint16_t * valp)827 _prop_object_decode_string_uesc_getu16(const char *src, unsigned int idx,
828     uint16_t *valp)
829 {
830           unsigned int i;
831           uint16_t val;
832           unsigned char c;
833 
834           if (src[idx] != '\\' || src[idx + 1] != 'u') {
835                     return 0;
836           }
837 
838           for (val = 0, i = 2; i < 6; i++) {
839                     val <<= 4;
840                     c = src[idx + i];
841                     if (c >= 'A' && c <= 'F') {
842                               val |= 10 + (c - 'A');
843                     } else if (c >= 'a' && c <= 'f') {
844                               val |= 10 + (c - 'a');
845                     } else if (c >= '0' && c <= '9') {
846                               val |= c - '0';
847                     } else {
848                               return 0;
849                     }
850           }
851 
852           *valp = val;
853           return idx + i;
854 }
855 
856 #define   HS_FIRST  0xd800
857 #define   HS_LAST             0xdbff
858 #define   HS_SHIFT  10
859 #define   LS_FIRST  0xdc00
860 #define   LS_LAST             0xdfff
861 
862 #define   HIGH_SURROGAGE_P(x) \
863           ((x) >= HS_FIRST && (x) <= HS_LAST)
864 #define   LOW_SURROGATE_P(x)  \
865           ((x) >= LS_FIRST && (x) <= LS_LAST)
866 #define   SURROGATE_P(x)                \
867           (HIGH_SURROGAGE_P(x) || LOW_SURROGATE_P(x))
868 
869 static int
_prop_object_decode_string_uesc(const char * src,char * c,unsigned int * cszp)870 _prop_object_decode_string_uesc(const char *src, char *c,
871     unsigned int *cszp)
872 {
873           unsigned int idx = 0;
874           uint32_t code;
875           uint16_t code16[2] = { 0, 0 };
876 
877           idx = _prop_object_decode_string_uesc_getu16(src, idx, &code16[0]);
878           if (idx == 0) {
879                     return 0;
880           }
881           if (! SURROGATE_P(code16[0])) {
882                     /* Simple case: not a surrogate pair */
883                     code = code16[0];
884           } else if (HIGH_SURROGAGE_P(code16[0])) {
885                     idx = _prop_object_decode_string_uesc_getu16(src, idx,
886                                                                            &code16[1]);
887                     if (idx == 0) {
888                               return 0;
889                     }
890                     /* Next code must be the low surrogate. */
891                     if (! LOW_SURROGATE_P(code16[1])) {
892                               return 0;
893                     }
894                     code = (((uint32_t)code16[0] - HS_FIRST) << HS_SHIFT) +
895                             (          code16[1] - LS_FIRST)              +
896                            0x10000;
897           } else {
898                     /* Got the low surrogate first; this is an error. */
899                     return 0;
900           }
901 
902           /*
903            * Ok, we have the code point.  Now convert it to UTF-8.
904            * First we'll just split into nybbles.
905            */
906           uint8_t u = (code >> 20) & 0xf;
907           uint8_t v = (code >> 16) & 0xf;
908           uint8_t w = (code >> 12) & 0xf;
909           uint8_t x = (code >>  8) & 0xf;
910           uint8_t y = (code >>  4) & 0xf;
911           uint8_t z = (code      ) & 0xf;
912 
913           /*
914            * ...and swizzle the nybbles accordingly.
915            *
916            * N.B. we expcitly disallow inserting a NUL into the string
917            * by way of a \uXXXX escape.
918            */
919           if (code == 0) {
920                     /* Not allowed. */
921                     return 0;
922           } else if (/*code >= 0x0000 &&*/ code <= 0x007f) {
923                     c[0] = (char)code;  /* == (y << 4) | z */
924                     *cszp = 1;
925           } else if (/*code >= 0x0080 &&*/ code <= 0x07ff) {
926                     c[0] = 0xc0 | (x << 2) | (y >> 2);
927                     c[1] = 0x80 | ((y & 3) << 4) | z;
928                     *cszp = 2;
929           } else if (/*code >= 0x0800 &&*/ code <= 0xffff) {
930                     c[0] = 0xe0 | w;
931                     c[1] = 0x80 | (x << 2) | (y >> 2);
932                     c[2] = 0x80 | ((y & 3) << 4) | z;
933                     *cszp = 3;
934           } else if (/*code >= 0x010000 &&*/ code <= 0x10ffff) {
935                     c[0] = 0xf0 | ((u & 1) << 2) | (v >> 2);
936                     c[1] = 0x80 | ((v & 3) << 4) | w;
937                     c[2] = 0x80 | (x << 2) | (y >> 2);
938                     c[3] = 0x80 | ((y & 3) << 4) | z;
939                     *cszp = 4;
940           } else {
941                     /* Invalid code. */
942                     return 0;
943           }
944 
945           return idx;         /* advance input by this much */
946 }
947 
948 #undef HS_FIRST
949 #undef HS_LAST
950 #undef LS_FIRST
951 #undef LS_LAST
952 #undef HIGH_SURROGAGE_P
953 #undef LOW_SURROGATE_P
954 #undef SURROGATE_P
955 
956 static bool
_prop_object_internalize_decode_string_json(struct _prop_object_internalize_context * ctx,char * target,size_t targsize,size_t * sizep,const char ** cpp)957 _prop_object_internalize_decode_string_json(
958                                         struct _prop_object_internalize_context *ctx,
959                                         char *target, size_t targsize, size_t *sizep,
960                                         const char **cpp)
961 {
962           const char *src;
963           size_t tarindex;
964           char c[4];
965           unsigned int csz;
966 
967           tarindex = 0;
968           src = ctx->poic_cp;
969 
970           for (;;) {
971                     if (_PROP_EOF(*src)) {
972                               return false;
973                     }
974                     if (*src == '"') {
975                               break;
976                     }
977 
978                     csz = 1;
979                     if ((c[0] = *src) == '\\') {
980                               int advance = 2;
981 
982                               switch ((c[0] = src[1])) {
983                               case '"':           /* quotation mark */
984                               case '\\':                    /* reverse solidus */
985                               case '/':           /* solidus */
986                                         /* identity mapping */
987                                         break;
988 
989                               case 'b':           /* backspace */
990                                         c[0] = 0x08;
991                                         break;
992 
993                               case 'f':           /* form feed */
994                                         c[0] = 0x0c;
995                                         break;
996 
997                               case 'n':           /* line feed */
998                                         c[0] = 0x0a;
999                                         break;
1000 
1001                               case 'r':           /* carriage return */
1002                                         c[0] = 0x0d;
1003                                         break;
1004 
1005                               case 't':           /* tab */
1006                                         c[0] = 0x09;
1007                                         break;
1008 
1009                               case 'u':
1010                                         advance = _prop_object_decode_string_uesc(
1011                                             src, c, &csz);
1012                                         if (advance == 0) {
1013                                                   return false;
1014                                         }
1015                                         break;
1016 
1017                               default:
1018                                         /* invalid escape */
1019                                         return false;
1020                               }
1021                               src += advance;
1022                     } else {
1023                               src++;
1024                     }
1025                     for (unsigned int i = 0; i < csz; i++) {
1026                               ADDCHAR(c[i]);
1027                     }
1028           }
1029 
1030           _PROP_ASSERT(*src == '"');
1031           if (sizep != NULL) {
1032                     *sizep = tarindex;
1033           }
1034           if (cpp != NULL) {
1035                     *cpp = src;
1036           }
1037 
1038           return true;
1039 }
1040 
1041 #undef ADDCHAR
1042 
1043 bool
_prop_object_internalize_decode_string(struct _prop_object_internalize_context * ctx,char * target,size_t targsize,size_t * sizep,const char ** cpp)1044 _prop_object_internalize_decode_string(
1045                                         struct _prop_object_internalize_context *ctx,
1046                                         char *target, size_t targsize, size_t *sizep,
1047                                         const char **cpp)
1048 {
1049           _PROP_ASSERT(ctx->poic_format == PROP_FORMAT_XML ||
1050                          ctx->poic_format == PROP_FORMAT_JSON);
1051 
1052           switch (ctx->poic_format) {
1053           case PROP_FORMAT_JSON:
1054                     return _prop_object_internalize_decode_string_json(ctx,
1055                         target, targsize, sizep, cpp);
1056 
1057           default:            /* XML */
1058                     return _prop_object_internalize_decode_string_xml(ctx,
1059                         target, targsize, sizep, cpp);
1060           }
1061 }
1062 
1063 /*
1064  * _prop_object_internalize_skip_whitespace --
1065  *        Skip a span of whitespace.
1066  */
1067 const char *
_prop_object_internalize_skip_whitespace(const char * cp)1068 _prop_object_internalize_skip_whitespace(const char *cp)
1069 {
1070           while (_PROP_ISSPACE(*cp)) {
1071                     cp++;
1072           }
1073           return cp;
1074 }
1075 
1076 /*
1077  * _prop_object_internalize_match --
1078  *        Returns true if the two character streams match.
1079  */
1080 bool
_prop_object_internalize_match(const char * str1,size_t len1,const char * str2,size_t len2)1081 _prop_object_internalize_match(const char *str1, size_t len1,
1082                                      const char *str2, size_t len2)
1083 {
1084 
1085           return (len1 == len2 && memcmp(str1, str2, len1) == 0);
1086 }
1087 
1088 #define   INTERNALIZER(t, f)                      \
1089 {         t,        sizeof(t) - 1,                f         }
1090 
1091 static const struct _prop_object_internalizer {
1092           const char                              *poi_tag;
1093           size_t                                  poi_taglen;
1094           prop_object_internalizer_t    poi_intern;
1095 } _prop_object_internalizer_table[] = {
1096           INTERNALIZER("array", _prop_array_internalize),
1097 
1098           INTERNALIZER("true", _prop_bool_internalize),
1099           INTERNALIZER("false", _prop_bool_internalize),
1100 
1101           INTERNALIZER("data", _prop_data_internalize),
1102 
1103           INTERNALIZER("dict", _prop_dictionary_internalize),
1104 
1105           INTERNALIZER("integer", _prop_number_internalize),
1106 
1107           INTERNALIZER("string", _prop_string_internalize),
1108 
1109           { 0, 0, NULL }
1110 };
1111 
1112 #undef INTERNALIZER
1113 
1114 /*
1115  * _prop_object_internalize_by_tag --
1116  *        Determine the object type from the tag in the context and
1117  *        internalize it.
1118  */
1119 static prop_object_t
_prop_object_internalize_by_tag(struct _prop_object_internalize_context * ctx)1120 _prop_object_internalize_by_tag(struct _prop_object_internalize_context *ctx)
1121 {
1122           const struct _prop_object_internalizer *poi;
1123           prop_object_t obj, parent_obj;
1124           void *data, *iter;
1125           prop_object_internalizer_continue_t iter_func;
1126           struct _prop_stack stack;
1127 
1128           _prop_stack_init(&stack);
1129 
1130  match_start:
1131           for (poi = _prop_object_internalizer_table;
1132                poi->poi_tag != NULL; poi++) {
1133                     if (_prop_object_internalize_match(ctx->poic_tagname,
1134                                                                ctx->poic_tagname_len,
1135                                                                poi->poi_tag,
1136                                                                poi->poi_taglen))
1137                               break;
1138           }
1139           if ((poi == NULL) || (poi->poi_tag == NULL)) {
1140                     while (_prop_stack_pop(&stack, &obj, &iter, &data, NULL)) {
1141                               iter_func = (prop_object_internalizer_continue_t)iter;
1142                               (*iter_func)(&stack, &obj, ctx, data, NULL);
1143                     }
1144 
1145                     return (NULL);
1146           }
1147 
1148           obj = NULL;
1149           if (!(*poi->poi_intern)(&stack, &obj, ctx))
1150                     goto match_start;
1151 
1152           parent_obj = obj;
1153           while (_prop_stack_pop(&stack, &parent_obj, &iter, &data, NULL)) {
1154                     iter_func = (prop_object_internalizer_continue_t)iter;
1155                     if (!(*iter_func)(&stack, &parent_obj, ctx, data, obj))
1156                               goto match_start;
1157                     obj = parent_obj;
1158           }
1159 
1160           return (parent_obj);
1161 }
1162 
1163 /*
1164  * _prop_object_internalize_xml --
1165  *        Internalize a property list from XML data.
1166  */
1167 static prop_object_t
_prop_object_internalize_xml(struct _prop_object_internalize_context * ctx,const struct _prop_object_type_tags * initial_tag)1168 _prop_object_internalize_xml(struct _prop_object_internalize_context *ctx,
1169     const struct _prop_object_type_tags *initial_tag)
1170 {
1171           prop_object_t obj = NULL;
1172 
1173           /* We start with a <plist> tag. */
1174           if (_prop_object_internalize_find_tag(ctx, "plist",
1175                                                         _PROP_TAG_TYPE_START) == false)
1176                     goto out;
1177 
1178           /* Plist elements cannot be empty. */
1179           if (ctx->poic_is_empty_element)
1180                     goto out;
1181 
1182           /*
1183            * We don't understand any plist attributes, but Apple XML
1184            * property lists often have a "version" attribute.  If we
1185            * see that one, we simply ignore it.
1186            */
1187           if (ctx->poic_tagattr != NULL &&
1188               !_PROP_TAGATTR_MATCH(ctx, "version"))
1189                     goto out;
1190 
1191           /* Next we expect to see opening master_tag. */
1192           if (_prop_object_internalize_find_tag(ctx,
1193                                         initial_tag != NULL ? initial_tag->xml_tag
1194                                                                 : NULL,
1195                                         _PROP_TAG_TYPE_START) == false)
1196                     goto out;
1197 
1198           obj = _prop_object_internalize_by_tag(ctx);
1199           if (obj == NULL)
1200                     goto out;
1201 
1202           /*
1203            * We've advanced past the closing main tag.
1204            * Now we want </plist>.
1205            */
1206           if (_prop_object_internalize_find_tag(ctx, "plist",
1207                                                         _PROP_TAG_TYPE_END) == false) {
1208                     prop_object_release(obj);
1209                     obj = NULL;
1210           }
1211 
1212  out:
1213           return (obj);
1214 }
1215 
1216 /*
1217  * _prop_object_internalize_json --
1218  *        Internalize a property list from JSON data.
1219  */
1220 static prop_object_t
_prop_object_internalize_json(struct _prop_object_internalize_context * ctx,const struct _prop_object_type_tags * initial_tag __unused)1221 _prop_object_internalize_json(struct _prop_object_internalize_context *ctx,
1222     const struct _prop_object_type_tags *initial_tag __unused)
1223 {
1224           prop_object_t obj, parent_obj;
1225           void *data, *iter;
1226           prop_object_internalizer_continue_t iter_func;
1227           struct _prop_stack stack;
1228           bool (*intern)(prop_stack_t, prop_object_t *,
1229                            struct _prop_object_internalize_context *);
1230 
1231           _prop_stack_init(&stack);
1232 
1233  match_start:
1234           intern = NULL;
1235           ctx->poic_tagname = ctx->poic_tagattr = ctx->poic_tagattrval = NULL;
1236           ctx->poic_tagname_len = ctx->poic_tagattr_len =
1237               ctx->poic_tagattrval_len = 0;
1238           ctx->poic_is_empty_element = false;
1239           ctx->poic_cp = _prop_object_internalize_skip_whitespace(ctx->poic_cp);
1240           switch (ctx->poic_cp[0]) {
1241           case '{':
1242                     ctx->poic_cp++;
1243                     intern = _prop_dictionary_internalize;
1244                     break;
1245 
1246           case '[':
1247                     ctx->poic_cp++;
1248                     intern = _prop_array_internalize;
1249                     break;
1250 
1251           case '"':
1252                     ctx->poic_cp++;
1253                     /* XXX Slightly gross. */
1254                     if (*ctx->poic_cp == '"') {
1255                               ctx->poic_cp++;
1256                               ctx->poic_is_empty_element = true;
1257                     }
1258                     intern = _prop_string_internalize;
1259                     break;
1260 
1261           case 't':
1262                     if (ctx->poic_cp[1] == 'r' &&
1263                         ctx->poic_cp[2] == 'u' &&
1264                         ctx->poic_cp[3] == 'e') {
1265                               /* XXX Slightly gross. */
1266                               ctx->poic_tagname = ctx->poic_cp;
1267                               ctx->poic_tagname_len = 4;
1268                               ctx->poic_is_empty_element = true;
1269                               intern = _prop_bool_internalize;
1270                               ctx->poic_cp += 4;
1271                     }
1272                     break;
1273 
1274           case 'f':
1275                     if (ctx->poic_cp[1] == 'a' &&
1276                         ctx->poic_cp[2] == 'l' &&
1277                         ctx->poic_cp[3] == 's' &&
1278                         ctx->poic_cp[4] == 'e') {
1279                               /* XXX Slightly gross. */
1280                               ctx->poic_tagname = ctx->poic_cp;
1281                               ctx->poic_tagname_len = 5;
1282                               ctx->poic_is_empty_element = true;
1283                               intern = _prop_bool_internalize;
1284                               ctx->poic_cp += 5;
1285                     }
1286                     break;
1287 
1288           default:
1289                     if (ctx->poic_cp[0] == '+' ||
1290                         ctx->poic_cp[0] == '-' ||
1291                         (ctx->poic_cp[0] >= '0' && ctx->poic_cp[0] <= '9')) {
1292                               intern = _prop_number_internalize;
1293                     }
1294                     break;
1295           }
1296 
1297           if (intern == NULL) {
1298                     while (_prop_stack_pop(&stack, &obj, &iter, &data, NULL)) {
1299                               iter_func = (prop_object_internalizer_continue_t)iter;
1300                               (*iter_func)(&stack, &obj, ctx, data, NULL);
1301                     }
1302                     return NULL;
1303           }
1304 
1305           obj = NULL;
1306           if ((*intern)(&stack, &obj, ctx) == false) {
1307                     goto match_start;
1308           }
1309 
1310           parent_obj = obj;
1311           while (_prop_stack_pop(&stack, &parent_obj, &iter, &data, NULL)) {
1312                     iter_func = (prop_object_internalizer_continue_t)iter;
1313                     if ((*iter_func)(&stack, &parent_obj, ctx, data,
1314                                                                       obj) == false) {
1315                               goto match_start;
1316                     }
1317                     obj = parent_obj;
1318           }
1319 
1320           /* Ensure there's no trailing junk. */
1321           if (parent_obj != NULL) {
1322                     ctx->poic_cp =
1323                         _prop_object_internalize_skip_whitespace(ctx->poic_cp);
1324                     if (!_PROP_EOF(*ctx->poic_cp)) {
1325                               prop_object_release(parent_obj);
1326                               parent_obj = NULL;
1327                     }
1328           }
1329           return parent_obj;
1330 }
1331 
1332 /*
1333  * _prop_object_internalize --
1334  *        Internalize a property list from a NUL-terminated data blob.
1335  */
1336 prop_object_t
_prop_object_internalize(const char * data,const struct _prop_object_type_tags * initial_tag)1337 _prop_object_internalize(const char *data,
1338     const struct _prop_object_type_tags *initial_tag)
1339 {
1340           struct _prop_object_internalize_context *ctx;
1341           prop_object_t obj;
1342           prop_format_t fmt;
1343 
1344           /*
1345            * Skip all whitespace until and look at the first
1346            * non-whitespace character to determine the format:
1347            * An XML plist will always have '<' as the first non-ws
1348            * character.  If we encounter something else, we assume
1349            * it is JSON.
1350            */
1351           data = _prop_object_internalize_skip_whitespace(data);
1352           if (_PROP_EOF(*data)) {
1353                     return NULL;
1354           }
1355 
1356           fmt = *data == '<' ? PROP_FORMAT_XML : PROP_FORMAT_JSON;
1357 
1358           ctx = _prop_object_internalize_context_alloc(data, fmt);
1359           if (ctx == NULL) {
1360                     return NULL;
1361           }
1362 
1363           if (fmt == PROP_FORMAT_XML) {
1364                     obj = _prop_object_internalize_xml(ctx, initial_tag);
1365           } else {
1366                     obj = _prop_object_internalize_json(ctx, initial_tag);
1367           }
1368 
1369           _prop_object_internalize_context_free(ctx);
1370           return (obj);
1371 }
1372 
1373 _PROP_EXPORT prop_object_t
prop_object_internalize(const char * data)1374 prop_object_internalize(const char *data)
1375 {
1376           return _prop_object_internalize(data, NULL);
1377 }
1378 
1379 /*
1380  * _prop_object_internalize_context_alloc --
1381  *        Allocate an internalize context.
1382  */
1383 struct _prop_object_internalize_context *
_prop_object_internalize_context_alloc(const char * data,prop_format_t fmt)1384 _prop_object_internalize_context_alloc(const char *data, prop_format_t fmt)
1385 {
1386           struct _prop_object_internalize_context *ctx;
1387 
1388           ctx = _PROP_MALLOC(sizeof(*ctx), M_TEMP);
1389           if (ctx == NULL)
1390                     return (NULL);
1391 
1392           ctx->poic_format = fmt;
1393           ctx->poic_data = ctx->poic_cp = data;
1394 
1395           /*
1396            * If we're digesting JSON, check for a byte order mark and
1397            * skip it, if present.  We should never see one, but we're
1398            * allowed to detect and ignore it.  (RFC 8259 section 8.1)
1399            */
1400           if (fmt == PROP_FORMAT_JSON) {
1401                     if (((unsigned char)data[0] == 0xff &&
1402                          (unsigned char)data[1] == 0xfe) ||
1403                         ((unsigned char)data[0] == 0xfe &&
1404                          (unsigned char)data[1] == 0xff)) {
1405                               ctx->poic_cp = data + 2;
1406                     }
1407 
1408                     /* No additional processing work to do for JSON. */
1409                     return ctx;
1410           }
1411 
1412           /*
1413            * Skip any whitespace and XML preamble stuff that we don't
1414            * know about / care about.
1415            */
1416           for (;;) {
1417                     data = _prop_object_internalize_skip_whitespace(data);
1418                     if (_PROP_EOF(*data) || *data != '<')
1419                               goto bad;
1420 
1421 #define   MATCH(str)          (strncmp(&data[1], str, strlen(str)) == 0)
1422 
1423                     /*
1424                      * Skip over the XML preamble that Apple XML property
1425                      * lists usually include at the top of the file.
1426                      */
1427                     if (MATCH("?xml ") ||
1428                         MATCH("!DOCTYPE plist")) {
1429                               while (*data != '>' && !_PROP_EOF(*data))
1430                                         data++;
1431                               if (_PROP_EOF(*data))
1432                                         goto bad;
1433                               data++;   /* advance past the '>' */
1434                               continue;
1435                     }
1436 
1437                     if (MATCH("<!--")) {
1438                               ctx->poic_cp = data + 4;
1439                               if (_prop_object_internalize_skip_comment(ctx) == false)
1440                                         goto bad;
1441                               data = ctx->poic_cp;
1442                               continue;
1443                     }
1444 
1445 #undef MATCH
1446 
1447                     /*
1448                      * We don't think we should skip it, so let's hope we can
1449                      * parse it.
1450                      */
1451                     break;
1452           }
1453 
1454           ctx->poic_cp = data;
1455           return (ctx);
1456  bad:
1457           _PROP_FREE(ctx, M_TEMP);
1458           return (NULL);
1459 }
1460 
1461 /*
1462  * _prop_object_internalize_context_free --
1463  *        Free an internalize context.
1464  */
1465 void
_prop_object_internalize_context_free(struct _prop_object_internalize_context * ctx)1466 _prop_object_internalize_context_free(
1467                     struct _prop_object_internalize_context *ctx)
1468 {
1469 
1470           _PROP_FREE(ctx, M_TEMP);
1471 }
1472 
1473 #if !defined(_KERNEL) && !defined(_STANDALONE)
1474 /*
1475  * _prop_object_externalize_file_dirname --
1476  *        dirname(3), basically.  We have to roll our own because the
1477  *        system dirname(3) isn't reentrant.
1478  */
1479 static void
_prop_object_externalize_file_dirname(const char * path,char * result)1480 _prop_object_externalize_file_dirname(const char *path, char *result)
1481 {
1482           const char *lastp;
1483           size_t len;
1484 
1485           /*
1486            * If `path' is a NULL pointer or points to an empty string,
1487            * return ".".
1488            */
1489           if (path == NULL || *path == '\0')
1490                     goto singledot;
1491 
1492           /* String trailing slashes, if any. */
1493           lastp = path + strlen(path) - 1;
1494           while (lastp != path && *lastp == '/')
1495                     lastp--;
1496 
1497           /* Terminate path at the last occurrence of '/'. */
1498           do {
1499                     if (*lastp == '/') {
1500                               /* Strip trailing slashes, if any. */
1501                               while (lastp != path && *lastp == '/')
1502                                         lastp--;
1503 
1504                               /* ...and copy the result into the result buffer. */
1505                               len = (lastp - path) + 1 /* last char */;
1506                               if (len > (PATH_MAX - 1))
1507                                         len = PATH_MAX - 1;
1508 
1509                               memcpy(result, path, len);
1510                               result[len] = '\0';
1511                               return;
1512                     }
1513           } while (--lastp >= path);
1514 
1515           /* No /'s found, return ".". */
1516  singledot:
1517           strcpy(result, ".");
1518 }
1519 
1520 /*
1521  * _prop_object_externalize_write_file --
1522  *        Write an externalized dictionary to the specified file.
1523  *        The file is written atomically from the caller's perspective,
1524  *        and the mode set to 0666 modified by the caller's umask.
1525  */
1526 static bool
_prop_object_externalize_write_file(const char * fname,const char * data,size_t len)1527 _prop_object_externalize_write_file(const char *fname, const char *data,
1528     size_t len)
1529 {
1530           char tname_store[PATH_MAX];
1531           char *tname = NULL;
1532           int fd = -1;
1533           int save_errno;
1534           mode_t myumask;
1535           bool rv = false;
1536 
1537           if (len > SSIZE_MAX) {
1538                     errno = EFBIG;
1539                     return false;
1540           }
1541 
1542           /*
1543            * Get the directory name where the file is to be written
1544            * and create the temporary file.
1545            */
1546           _prop_object_externalize_file_dirname(fname, tname_store);
1547 #define PLISTTMP "/.plistXXXXXX"
1548           if (strlen(tname_store) + strlen(PLISTTMP) >= sizeof(tname_store)) {
1549                     errno = ENAMETOOLONG;
1550                     return false;
1551           }
1552           strcat(tname_store, PLISTTMP);
1553 #undef PLISTTMP
1554 
1555           if ((fd = mkstemp(tname_store)) == -1) {
1556                     return (false);
1557           }
1558           tname = tname_store;
1559 
1560           if (write(fd, data, len) != (ssize_t)len) {
1561                     goto bad;
1562           }
1563 
1564           if (fsync(fd) == -1) {
1565                     goto bad;
1566           }
1567 
1568           myumask = umask(0);
1569           (void)umask(myumask);
1570           if (fchmod(fd, 0666 & ~myumask) == -1) {
1571                     goto bad;
1572           }
1573 
1574           if (rename(tname, fname) == -1) {
1575                     goto bad;
1576           }
1577           tname = NULL;
1578 
1579           rv = true;
1580 
1581  bad:
1582           save_errno = errno;
1583           if (fd != -1) {
1584                     (void) close(fd);
1585           }
1586           if (tname != NULL) {
1587                     (void) unlink(tname);
1588           }
1589           errno = save_errno;
1590           return rv;
1591 }
1592 
1593 /*
1594  * _prop_object_externalize_to_file --
1595  *        Externalize an object to the specified file.
1596  */
1597 bool
_prop_object_externalize_to_file(struct _prop_object * obj,const char * fname,prop_format_t fmt)1598 _prop_object_externalize_to_file(struct _prop_object *obj, const char *fname,
1599     prop_format_t fmt)
1600 {
1601           char *data = _prop_object_externalize(obj, fmt);
1602           if (data == NULL) {
1603                     return false;
1604           }
1605           bool rv = _prop_object_externalize_write_file(fname, data,
1606               strlen(data));
1607           int save_errno = errno;
1608           _PROP_FREE(data, M_TEMP);
1609           errno = save_errno;
1610 
1611           return rv;
1612 }
1613 
1614 struct _prop_object_internalize_mapped_file {
1615           char *    poimf_xml;
1616           size_t    poimf_mapsize;
1617 };
1618 
1619 /*
1620  * _prop_object_internalize_map_file --
1621  *        Map a file for the purpose of internalizing it.
1622  */
1623 static struct _prop_object_internalize_mapped_file *
_prop_object_internalize_map_file(const char * fname)1624 _prop_object_internalize_map_file(const char *fname)
1625 {
1626           struct stat sb;
1627           struct _prop_object_internalize_mapped_file *mf;
1628           size_t pgsize = (size_t)sysconf(_SC_PAGESIZE);
1629           size_t pgmask = pgsize - 1;
1630           bool need_guard = false;
1631           int fd;
1632 
1633           mf = _PROP_MALLOC(sizeof(*mf), M_TEMP);
1634           if (mf == NULL)
1635                     return (NULL);
1636 
1637           fd = open(fname, O_RDONLY, 0400);
1638           if (fd == -1) {
1639                     _PROP_FREE(mf, M_TEMP);
1640                     return (NULL);
1641           }
1642 
1643           if (fstat(fd, &sb) == -1) {
1644                     (void) close(fd);
1645                     _PROP_FREE(mf, M_TEMP);
1646                     return (NULL);
1647           }
1648           mf->poimf_mapsize = ((size_t)sb.st_size + pgmask) & ~pgmask;
1649           if (mf->poimf_mapsize < (size_t)sb.st_size) {
1650                     (void) close(fd);
1651                     _PROP_FREE(mf, M_TEMP);
1652                     return (NULL);
1653           }
1654 
1655           /*
1656            * If the file length is an integral number of pages, then we
1657            * need to map a guard page at the end in order to provide the
1658            * necessary NUL-termination of the buffer.
1659            */
1660           if ((sb.st_size & pgmask) == 0)
1661                     need_guard = true;
1662 
1663           mf->poimf_xml = mmap(NULL, need_guard ? mf->poimf_mapsize + pgsize
1664                                                         : mf->poimf_mapsize,
1665                                   PROT_READ, MAP_FILE|MAP_SHARED, fd, (off_t)0);
1666           (void) close(fd);
1667           if (mf->poimf_xml == MAP_FAILED) {
1668                     _PROP_FREE(mf, M_TEMP);
1669                     return (NULL);
1670           }
1671 #ifdef POSIX_MADV_SEQUENTIAL
1672           (void) posix_madvise(mf->poimf_xml, mf->poimf_mapsize,
1673               POSIX_MADV_SEQUENTIAL);
1674 #endif
1675 
1676           if (need_guard) {
1677                     if (mmap(mf->poimf_xml + mf->poimf_mapsize,
1678                                pgsize, PROT_READ,
1679                                MAP_ANON|MAP_PRIVATE|MAP_FIXED, -1,
1680                                (off_t)0) == MAP_FAILED) {
1681                               (void) munmap(mf->poimf_xml, mf->poimf_mapsize);
1682                               _PROP_FREE(mf, M_TEMP);
1683                               return (NULL);
1684                     }
1685                     mf->poimf_mapsize += pgsize;
1686           }
1687 
1688           return (mf);
1689 }
1690 
1691 /*
1692  * _prop_object_internalize_unmap_file --
1693  *        Unmap a file previously mapped for internalizing.
1694  */
1695 static void
_prop_object_internalize_unmap_file(struct _prop_object_internalize_mapped_file * mf)1696 _prop_object_internalize_unmap_file(
1697     struct _prop_object_internalize_mapped_file *mf)
1698 {
1699 
1700 #ifdef POSIX_MADV_DONTNEED
1701           (void) posix_madvise(mf->poimf_xml, mf->poimf_mapsize,
1702               POSIX_MADV_DONTNEED);
1703 #endif
1704           (void) munmap(mf->poimf_xml, mf->poimf_mapsize);
1705           _PROP_FREE(mf, M_TEMP);
1706 }
1707 
1708 /*
1709  * _prop_object_internalize_from_file --
1710  *        Internalize a property list from a file.
1711  */
1712 prop_object_t
_prop_object_internalize_from_file(const char * fname,const struct _prop_object_type_tags * initial_tag)1713 _prop_object_internalize_from_file(const char *fname,
1714     const struct _prop_object_type_tags *initial_tag)
1715 {
1716           struct _prop_object_internalize_mapped_file *mf;
1717           prop_object_t obj;
1718 
1719           mf = _prop_object_internalize_map_file(fname);
1720           if (mf == NULL) {
1721                     return NULL;
1722           }
1723           obj = _prop_object_internalize(mf->poimf_xml, initial_tag);
1724           _prop_object_internalize_unmap_file(mf);
1725 
1726           return obj;
1727 }
1728 
1729 _PROP_EXPORT prop_object_t
prop_object_internalize_from_file(const char * fname)1730 prop_object_internalize_from_file(const char *fname)
1731 {
1732           return _prop_object_internalize_from_file(fname, NULL);
1733 }
1734 #endif /* !_KERNEL && !_STANDALONE */
1735 
1736 static prop_format_t          _prop_format_default = PROP_FORMAT_XML;
1737 
1738 /*
1739  * prop_object_externalize --
1740  *        Externalize an object in the default format.
1741  */
1742 _PROP_EXPORT char *
prop_object_externalize(prop_object_t po)1743 prop_object_externalize(prop_object_t po)
1744 {
1745           return _prop_object_externalize((struct _prop_object *)po,
1746               _prop_format_default);
1747 }
1748 
1749 /*
1750  * prop_object_externalize_with_format --
1751  *        Externalize an object in the specified format.
1752  */
1753 _PROP_EXPORT char *
prop_object_externalize_with_format(prop_object_t po,prop_format_t fmt)1754 prop_object_externalize_with_format(prop_object_t po, prop_format_t fmt)
1755 {
1756           return _prop_object_externalize((struct _prop_object *)po, fmt);
1757 }
1758 
1759 #if !defined(_KERNEL) && !defined(_STANDALONE)
1760 /*
1761  * prop_object_externalize_to_file --
1762  *        Externalize an object to the specifed file in the default format.
1763  */
1764 _PROP_EXPORT bool
prop_object_externalize_to_file(prop_object_t po,const char * fname)1765 prop_object_externalize_to_file(prop_object_t po, const char *fname)
1766 {
1767           return _prop_object_externalize_to_file((struct _prop_object *)po,
1768               fname, _prop_format_default);
1769 }
1770 
1771 /*
1772  * prop_object_externalize_to_file_with_format --
1773  *        Externalize an object to the specifed file in the specified format.
1774  */
1775 _PROP_EXPORT bool
prop_object_externalize_to_file_with_format(prop_object_t po,const char * fname,prop_format_t fmt)1776 prop_object_externalize_to_file_with_format(prop_object_t po,
1777     const char *fname, prop_format_t fmt)
1778 {
1779           return _prop_object_externalize_to_file((struct _prop_object *)po,
1780               fname, fmt);
1781 }
1782 #endif /* !_KERNEL && !_STANDALONE */
1783 
1784 /*
1785  * prop_object_retain --
1786  *        Increment the reference count on an object.
1787  */
1788 _PROP_EXPORT void
prop_object_retain(prop_object_t obj)1789 prop_object_retain(prop_object_t obj)
1790 {
1791           struct _prop_object *po = obj;
1792           uint32_t ncnt __unused;
1793 
1794           _PROP_ATOMIC_INC32_NV(&po->po_refcnt, ncnt);
1795           _PROP_ASSERT(ncnt != 0);
1796 }
1797 
1798 /*
1799  * prop_object_release_emergency
1800  *        A direct free with prop_object_release failed.
1801  *        Walk down the tree until a leaf is found and
1802  *        free that. Do not recurse to avoid stack overflows.
1803  *
1804  *        This is a slow edge condition, but necessary to
1805  *        guarantee that an object can always be freed.
1806  */
1807 static void
prop_object_release_emergency(prop_object_t obj)1808 prop_object_release_emergency(prop_object_t obj)
1809 {
1810           struct _prop_object *po;
1811           void (*unlock)(void);
1812           prop_object_t parent = NULL;
1813           uint32_t ocnt;
1814 
1815           for (;;) {
1816                     po = obj;
1817                     _PROP_ASSERT(obj);
1818 
1819                     if (po->po_type->pot_lock != NULL)
1820                     po->po_type->pot_lock();
1821 
1822                     /* Save pointerto unlock function */
1823                     unlock = po->po_type->pot_unlock;
1824 
1825                     /* Dance a bit to make sure we always get the non-racy ocnt */
1826                     _PROP_ATOMIC_DEC32_NV(&po->po_refcnt, ocnt);
1827                     ocnt++;
1828                     _PROP_ASSERT(ocnt != 0);
1829 
1830                     if (ocnt != 1) {
1831                               if (unlock != NULL)
1832                                         unlock();
1833                               break;
1834                     }
1835 
1836                     _PROP_ASSERT(po->po_type);
1837                     if ((po->po_type->pot_free)(NULL, &obj) ==
1838                         _PROP_OBJECT_FREE_DONE) {
1839                               if (unlock != NULL)
1840                                         unlock();
1841                               break;
1842                     }
1843 
1844                     if (unlock != NULL)
1845                               unlock();
1846 
1847                     parent = po;
1848                     _PROP_ATOMIC_INC32(&po->po_refcnt);
1849           }
1850           _PROP_ASSERT(parent);
1851           /* One object was just freed. */
1852           po = parent;
1853           (*po->po_type->pot_emergency_free)(parent);
1854 }
1855 
1856 /*
1857  * prop_object_release --
1858  *        Decrement the reference count on an object.
1859  *
1860  *        Free the object if we are releasing the final
1861  *        reference.
1862  */
1863 _PROP_EXPORT void
prop_object_release(prop_object_t obj)1864 prop_object_release(prop_object_t obj)
1865 {
1866           struct _prop_object *po;
1867           struct _prop_stack stack;
1868           void (*unlock)(void);
1869           int ret;
1870           uint32_t ocnt;
1871 
1872           _prop_stack_init(&stack);
1873 
1874           do {
1875                     do {
1876                               po = obj;
1877                               _PROP_ASSERT(obj);
1878 
1879                               if (po->po_type->pot_lock != NULL)
1880                                         po->po_type->pot_lock();
1881 
1882                               /* Save pointer to object unlock function */
1883                               unlock = po->po_type->pot_unlock;
1884 
1885                               _PROP_ATOMIC_DEC32_NV(&po->po_refcnt, ocnt);
1886                               ocnt++;
1887                               _PROP_ASSERT(ocnt != 0);
1888 
1889                               if (ocnt != 1) {
1890                                         ret = 0;
1891                                         if (unlock != NULL)
1892                                                   unlock();
1893                                         break;
1894                               }
1895 
1896                               ret = (po->po_type->pot_free)(&stack, &obj);
1897 
1898                               if (unlock != NULL)
1899                                         unlock();
1900 
1901                               if (ret == _PROP_OBJECT_FREE_DONE)
1902                                         break;
1903 
1904                               _PROP_ATOMIC_INC32(&po->po_refcnt);
1905                     } while (ret == _PROP_OBJECT_FREE_RECURSE);
1906                     if (ret == _PROP_OBJECT_FREE_FAILED)
1907                               prop_object_release_emergency(obj);
1908           } while (_prop_stack_pop(&stack, &obj, NULL, NULL, NULL));
1909 }
1910 
1911 /*
1912  * prop_object_type --
1913  *        Return the type of an object.
1914  */
1915 _PROP_EXPORT prop_type_t
prop_object_type(prop_object_t obj)1916 prop_object_type(prop_object_t obj)
1917 {
1918           struct _prop_object *po = obj;
1919 
1920           if (obj == NULL)
1921                     return (PROP_TYPE_UNKNOWN);
1922 
1923           return (po->po_type->pot_type);
1924 }
1925 
1926 /*
1927  * prop_object_equals --
1928  *        Returns true if thw two objects are equivalent.
1929  */
1930 _PROP_EXPORT bool
prop_object_equals(prop_object_t obj1,prop_object_t obj2)1931 prop_object_equals(prop_object_t obj1, prop_object_t obj2)
1932 {
1933           return (prop_object_equals_with_error(obj1, obj2, NULL));
1934 }
1935 
1936 _PROP_EXPORT bool
prop_object_equals_with_error(prop_object_t obj1,prop_object_t obj2,bool * error_flag)1937 prop_object_equals_with_error(prop_object_t obj1, prop_object_t obj2,
1938     bool *error_flag)
1939 {
1940           struct _prop_object *po1;
1941           struct _prop_object *po2;
1942           void *stored_pointer1, *stored_pointer2;
1943           prop_object_t next_obj1, next_obj2;
1944           struct _prop_stack stack;
1945           _prop_object_equals_rv_t ret;
1946 
1947           _prop_stack_init(&stack);
1948           if (error_flag)
1949                     *error_flag = false;
1950 
1951  start_subtree:
1952           stored_pointer1 = NULL;
1953           stored_pointer2 = NULL;
1954           po1 = obj1;
1955           po2 = obj2;
1956 
1957           if (po1->po_type != po2->po_type)
1958                     return (false);
1959 
1960  continue_subtree:
1961           ret = (*po1->po_type->pot_equals)(obj1, obj2,
1962                                                     &stored_pointer1, &stored_pointer2,
1963                                                     &next_obj1, &next_obj2);
1964           if (ret == _PROP_OBJECT_EQUALS_FALSE)
1965                     goto finish;
1966           if (ret == _PROP_OBJECT_EQUALS_TRUE) {
1967                     if (!_prop_stack_pop(&stack, &obj1, &obj2,
1968                                              &stored_pointer1, &stored_pointer2))
1969                               return true;
1970                     po1 = obj1;
1971                     po2 = obj2;
1972                     goto continue_subtree;
1973           }
1974           _PROP_ASSERT(ret == _PROP_OBJECT_EQUALS_RECURSE);
1975 
1976           if (!_prop_stack_push(&stack, obj1, obj2,
1977                                     stored_pointer1, stored_pointer2)) {
1978                     if (error_flag)
1979                               *error_flag = true;
1980                     goto finish;
1981           }
1982           obj1 = next_obj1;
1983           obj2 = next_obj2;
1984           goto start_subtree;
1985 
1986 finish:
1987           while (_prop_stack_pop(&stack, &obj1, &obj2, NULL, NULL)) {
1988                     po1 = obj1;
1989                     (*po1->po_type->pot_equals_finish)(obj1, obj2);
1990           }
1991           return (false);
1992 }
1993 
1994 /*
1995  * prop_object_iterator_next --
1996  *        Return the next item during an iteration.
1997  */
1998 _PROP_EXPORT prop_object_t
prop_object_iterator_next(prop_object_iterator_t pi)1999 prop_object_iterator_next(prop_object_iterator_t pi)
2000 {
2001 
2002           return ((*pi->pi_next_object)(pi));
2003 }
2004 
2005 /*
2006  * prop_object_iterator_reset --
2007  *        Reset the iterator to the first object so as to restart
2008  *        iteration.
2009  */
2010 _PROP_EXPORT void
prop_object_iterator_reset(prop_object_iterator_t pi)2011 prop_object_iterator_reset(prop_object_iterator_t pi)
2012 {
2013 
2014           (*pi->pi_reset)(pi);
2015 }
2016 
2017 /*
2018  * prop_object_iterator_release --
2019  *        Release the object iterator.
2020  */
2021 _PROP_EXPORT void
prop_object_iterator_release(prop_object_iterator_t pi)2022 prop_object_iterator_release(prop_object_iterator_t pi)
2023 {
2024 
2025           prop_object_release(pi->pi_obj);
2026           _PROP_FREE(pi, M_TEMP);
2027 }
2028