1 /* $NetBSD: prop_extern.c,v 1.2 2025/05/14 03:25:46 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 #if !defined(_KERNEL) && !defined(_STANDALONE) 36 #include <sys/stat.h> 37 #include <errno.h> 38 #include <fcntl.h> 39 #include <limits.h> 40 #include <unistd.h> 41 #endif /* !_KERNEL && !_STANDALONE */ 42 43 #define BUF_EXPAND 256 44 #define PLISTTMP "/.plistXXXXXX" 45 46 static prop_format_t _prop_format_default = PROP_FORMAT_XML; 47 48 /* 49 * _prop_extern_append_char -- 50 * Append a single character to the externalize buffer. 51 */ 52 bool 53 _prop_extern_append_char( 54 struct _prop_object_externalize_context *ctx, unsigned char c) 55 { 56 57 _PROP_ASSERT(ctx->poec_capacity != 0); 58 _PROP_ASSERT(ctx->poec_buf != NULL); 59 _PROP_ASSERT(ctx->poec_len <= ctx->poec_capacity); 60 61 if (ctx->poec_len == ctx->poec_capacity) { 62 char *cp = _PROP_REALLOC(ctx->poec_buf, 63 ctx->poec_capacity + BUF_EXPAND, 64 M_TEMP); 65 if (cp == NULL) { 66 return false; 67 } 68 ctx->poec_capacity = ctx->poec_capacity + BUF_EXPAND; 69 ctx->poec_buf = cp; 70 } 71 72 ctx->poec_buf[ctx->poec_len++] = c; 73 74 return true; 75 } 76 77 /* 78 * _prop_extern_append_cstring -- 79 * Append a C string to the externalize buffer. 80 */ 81 bool 82 _prop_extern_append_cstring( 83 struct _prop_object_externalize_context *ctx, const char *cp) 84 { 85 86 while (*cp != '\0') { 87 if (_prop_extern_append_char(ctx, 88 (unsigned char)*cp) == false) { 89 return false; 90 } 91 cp++; 92 } 93 return true; 94 } 95 96 /* 97 * _prop_json_extern_append_encoded_cstring -- 98 * Append a C string to the externalize buffer, JSON-encoded. 99 */ 100 static bool 101 _prop_json_extern_append_escu( 102 struct _prop_object_externalize_context *ctx, uint16_t val) 103 { 104 char tmpstr[sizeof("\\uXXXX")]; 105 106 snprintf(tmpstr, sizeof(tmpstr), "\\u%04X", val); 107 return _prop_extern_append_cstring(ctx, tmpstr); 108 } 109 110 static bool 111 _prop_json_extern_append_encoded_cstring( 112 struct _prop_object_externalize_context *ctx, const char *cp) 113 { 114 bool esc; 115 unsigned char ch; 116 117 for (; (ch = *cp) != '\0'; cp++) { 118 esc = true; 119 switch (ch) { 120 /* 121 * First, the two explicit exclusions. They must be 122 * escaped. 123 */ 124 case '"': /* U+0022 quotation mark */ 125 goto emit; 126 127 case '\\': /* U+005C reverse solidus */ 128 goto emit; 129 130 /* 131 * And some special cases that are explcit in the grammar. 132 */ 133 case '/': /* U+002F solidus (XXX this one seems silly) */ 134 goto emit; 135 136 case 0x08: /* U+0008 backspace */ 137 ch = 'b'; 138 goto emit; 139 140 case 0x0c: /* U+000C form feed */ 141 ch = 'f'; 142 goto emit; 143 144 case 0x0a: /* U+000A line feed */ 145 ch = 'n'; 146 goto emit; 147 148 case 0x0d: /* U+000D carriage return */ 149 ch = 'r'; 150 goto emit; 151 152 case 0x09: /* U+0009 tab */ 153 ch = 't'; 154 goto emit; 155 156 default: 157 /* 158 * \u-escape all other single-byte ASCII control 159 * characters, per RFC 8259: 160 * 161 * <quote> 162 * All Unicode characters may be placed within the 163 * quotation marks, except for the characters that 164 * MUST be escaped: quotation mark, reverse solidus, 165 * and the control characters (U+0000 through U+001F). 166 * </quote> 167 */ 168 if (ch < 0x20) { 169 if (_prop_json_extern_append_escu(ctx, 170 ch) == false) { 171 return false; 172 } 173 break; 174 } 175 /* 176 * We're going to just treat everything else like 177 * UTF-8 (we've been handed a C-string, after all) 178 * and pretend it will be OK. 179 */ 180 esc = false; 181 emit: 182 if ((esc && _prop_extern_append_char(ctx, 183 '\\') == false) || 184 _prop_extern_append_char(ctx, ch) == false) { 185 return false; 186 } 187 break; 188 } 189 } 190 191 return true; 192 } 193 194 /* 195 * _prop_xml_extern_append_encoded_cstring -- 196 * Append a C string to the externalize buffer, XML-encoded. 197 */ 198 static bool 199 _prop_xml_extern_append_encoded_cstring( 200 struct _prop_object_externalize_context *ctx, const char *cp) 201 { 202 bool rv; 203 unsigned char ch; 204 205 for (rv = true; rv && (ch = *cp) != '\0'; cp++) { 206 switch (ch) { 207 case '<': 208 rv = _prop_extern_append_cstring(ctx, "<"); 209 break; 210 case '>': 211 rv = _prop_extern_append_cstring(ctx, ">"); 212 break; 213 case '&': 214 rv = _prop_extern_append_cstring(ctx, "&"); 215 break; 216 default: 217 rv = _prop_extern_append_char(ctx, ch); 218 break; 219 } 220 } 221 222 return rv; 223 } 224 225 /* 226 * _prop_extern_append_encoded_cstring -- 227 * Append a C string to the externalize buffer, encoding it for 228 * the selected format. 229 */ 230 bool 231 _prop_extern_append_encoded_cstring( 232 struct _prop_object_externalize_context *ctx, const char *cp) 233 { 234 _PROP_ASSERT(ctx->poec_format == PROP_FORMAT_XML || 235 ctx->poec_format == PROP_FORMAT_JSON); 236 237 switch (ctx->poec_format) { 238 case PROP_FORMAT_JSON: 239 return _prop_json_extern_append_encoded_cstring(ctx, cp); 240 241 default: /* PROP_FORMAT_XML */ 242 return _prop_xml_extern_append_encoded_cstring(ctx, cp); 243 } 244 } 245 246 /* 247 * _prop_extern_start_line -- 248 * Append the start-of-line character sequence. 249 */ 250 bool 251 _prop_extern_start_line( 252 struct _prop_object_externalize_context *ctx) 253 { 254 unsigned int i; 255 256 for (i = 0; i < ctx->poec_depth; i++) { 257 if (_prop_extern_append_char(ctx, '\t') == false) { 258 return false; 259 } 260 } 261 return true; 262 } 263 264 /* 265 * _prop_extern_end_line -- 266 * Append the end-of-line character sequence. 267 */ 268 bool 269 _prop_extern_end_line( 270 struct _prop_object_externalize_context *ctx, const char *trailer) 271 { 272 if (trailer != NULL && 273 _prop_extern_append_cstring(ctx, trailer) == false) { 274 return false; 275 } 276 return _prop_extern_append_char(ctx, '\n'); 277 } 278 279 /* 280 * _prop_extern_append_start_tag -- 281 * Append an item's start tag to the externalize buffer. 282 */ 283 bool 284 _prop_extern_append_start_tag( 285 struct _prop_object_externalize_context *ctx, 286 const struct _prop_object_type_tags *tags, 287 const char *tagattrs) 288 { 289 bool rv; 290 291 _PROP_ASSERT(ctx->poec_format == PROP_FORMAT_XML || 292 ctx->poec_format == PROP_FORMAT_JSON); 293 294 switch (ctx->poec_format) { 295 case PROP_FORMAT_JSON: 296 rv = tags->json_open_tag == NULL || 297 _prop_extern_append_cstring(ctx, tags->json_open_tag); 298 break; 299 300 default: /* PROP_FORMAT_XML */ 301 rv = _prop_extern_append_char(ctx, '<') && 302 _prop_extern_append_cstring(ctx, tags->xml_tag) && 303 (tagattrs == NULL || 304 (_prop_extern_append_char(ctx, ' ') && 305 _prop_extern_append_cstring(ctx, tagattrs))) && 306 _prop_extern_append_char(ctx, '>'); 307 break; 308 } 309 310 return rv; 311 } 312 313 /* 314 * _prop_extern_append_end_tag -- 315 * Append an item's end tag to the externalize buffer. 316 */ 317 bool 318 _prop_extern_append_end_tag( 319 struct _prop_object_externalize_context *ctx, 320 const struct _prop_object_type_tags *tags) 321 { 322 bool rv; 323 324 _PROP_ASSERT(ctx->poec_format == PROP_FORMAT_XML || 325 ctx->poec_format == PROP_FORMAT_JSON); 326 327 switch (ctx->poec_format) { 328 case PROP_FORMAT_JSON: 329 rv = tags->json_close_tag == NULL || 330 _prop_extern_append_cstring(ctx, tags->json_close_tag); 331 break; 332 333 default: /* PROP_FORMAT_XML */ 334 rv = _prop_extern_append_char(ctx, '<') && 335 _prop_extern_append_char(ctx, '/') && 336 _prop_extern_append_cstring(ctx, tags->xml_tag) && 337 _prop_extern_append_char(ctx, '>'); 338 break; 339 } 340 341 return rv; 342 } 343 344 /* 345 * _prop_extern_append_empty_tag -- 346 * Append an item's empty tag to the externalize buffer. 347 */ 348 bool 349 _prop_extern_append_empty_tag( 350 struct _prop_object_externalize_context *ctx, 351 const struct _prop_object_type_tags *tags) 352 { 353 bool rv; 354 355 _PROP_ASSERT(ctx->poec_format == PROP_FORMAT_XML || 356 ctx->poec_format == PROP_FORMAT_JSON); 357 358 switch (ctx->poec_format) { 359 case PROP_FORMAT_JSON: 360 if (tags->json_open_tag == NULL || 361 _prop_extern_append_cstring(ctx, 362 tags->json_open_tag) == false) { 363 return false; 364 } 365 if (tags->json_empty_sep != NULL && 366 _prop_extern_append_cstring(ctx, 367 tags->json_empty_sep) == false) { 368 return false; 369 } 370 if (tags->json_close_tag != NULL) { 371 rv = _prop_extern_append_cstring(ctx, 372 tags->json_close_tag); 373 } else { 374 rv = true; 375 } 376 break; 377 378 default: /* PROP_FORMAT_XML */ 379 rv = _prop_extern_append_char(ctx, '<') && 380 _prop_extern_append_cstring(ctx, tags->xml_tag) && 381 _prop_extern_append_char(ctx, '/') && 382 _prop_extern_append_char(ctx, '>'); 383 break; 384 } 385 386 return rv; 387 } 388 389 static const struct _prop_object_type_tags _plist_type_tags = { 390 .xml_tag = "plist", 391 }; 392 393 /* 394 * _prop_extern_append_header -- 395 * Append the header to the externalize buffer. 396 */ 397 static bool 398 _prop_extern_append_header(struct _prop_object_externalize_context *ctx) 399 { 400 static const char _plist_xml_header[] = 401 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" 402 "<!DOCTYPE plist PUBLIC \"-//Apple Computer//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n"; 403 404 if (ctx->poec_format != PROP_FORMAT_XML) { 405 return true; 406 } 407 408 if (_prop_extern_append_cstring(ctx, _plist_xml_header) == false || 409 _prop_extern_append_start_tag(ctx, 410 &_plist_type_tags, 411 "version=\"1.0\"") == false || 412 _prop_extern_append_char(ctx, '\n') == false) { 413 return false; 414 } 415 416 return true; 417 } 418 419 /* 420 * _prop_extern_append_footer -- 421 * Append the footer to the externalize buffer. This also 422 * NUL-terminates the buffer. 423 */ 424 static bool 425 _prop_extern_append_footer(struct _prop_object_externalize_context *ctx) 426 { 427 if (_prop_extern_end_line(ctx, NULL) == false) { 428 return false; 429 } 430 431 if (ctx->poec_format == PROP_FORMAT_XML) { 432 if (_prop_extern_append_end_tag(ctx, 433 &_plist_type_tags) == false || 434 _prop_extern_end_line(ctx, NULL) == false) { 435 return false; 436 } 437 } 438 439 return _prop_extern_append_char(ctx, '\0'); 440 } 441 442 /* 443 * _prop_extern_context_alloc -- 444 * Allocate an externalize context. 445 */ 446 static struct _prop_object_externalize_context * 447 _prop_extern_context_alloc(prop_format_t fmt) 448 { 449 struct _prop_object_externalize_context *ctx; 450 451 ctx = _PROP_MALLOC(sizeof(*ctx), M_TEMP); 452 if (ctx != NULL) { 453 ctx->poec_buf = _PROP_MALLOC(BUF_EXPAND, M_TEMP); 454 if (ctx->poec_buf == NULL) { 455 _PROP_FREE(ctx, M_TEMP); 456 return NULL; 457 } 458 ctx->poec_len = 0; 459 ctx->poec_capacity = BUF_EXPAND; 460 ctx->poec_depth = 0; 461 ctx->poec_format = fmt; 462 } 463 return ctx; 464 } 465 466 /* 467 * _prop_extern_context_free -- 468 * Free an externalize context. 469 */ 470 static void 471 _prop_extern_context_free(struct _prop_object_externalize_context *ctx) 472 { 473 /* Buffer is always freed by the caller. */ 474 _PROP_FREE(ctx, M_TEMP); 475 } 476 477 /* 478 * _prop_object_externalize -- 479 * Externalize an object, returning a NUL-terminated buffer 480 * containing the serialized data in either XML or JSON format. 481 * The buffer is allocated with the M_TEMP memory type. 482 */ 483 char * 484 _prop_object_externalize(struct _prop_object *obj, prop_format_t fmt) 485 { 486 struct _prop_object_externalize_context *ctx; 487 char *cp = NULL; 488 489 if (obj == NULL || obj->po_type->pot_extern == NULL) { 490 return NULL; 491 } 492 if (fmt != PROP_FORMAT_XML && fmt != PROP_FORMAT_JSON) { 493 return NULL; 494 } 495 496 ctx = _prop_extern_context_alloc(fmt); 497 if (ctx == NULL) { 498 return NULL; 499 } 500 501 if (_prop_extern_append_header(ctx) == false || 502 obj->po_type->pot_extern(ctx, obj) == false || 503 _prop_extern_append_footer(ctx) == false) { 504 /* We are responsible for releasing the buffer. */ 505 _PROP_FREE(ctx->poec_buf, M_TEMP); 506 goto bad; 507 } 508 509 cp = ctx->poec_buf; 510 bad: 511 _prop_extern_context_free(ctx); 512 return cp; 513 } 514 515 #if !defined(_KERNEL) && !defined(_STANDALONE) 516 /* 517 * _prop_extern_file_dirname -- 518 * dirname(3), basically. We have to roll our own because the 519 * system dirname(3) isn't reentrant. 520 */ 521 static void 522 _prop_extern_file_dirname(const char *path, char *result) 523 { 524 const char *lastp; 525 size_t len; 526 527 /* 528 * If `path' is a NULL pointer or points to an empty string, 529 * return ".". 530 */ 531 if (path == NULL || *path == '\0') { 532 goto singledot; 533 } 534 535 /* Strip trailing slashes, if any. */ 536 lastp = path + strlen(path) - 1; 537 while (lastp != path && *lastp == '/') { 538 lastp--; 539 } 540 541 /* Terminate path at the last occurrence of '/'. */ 542 do { 543 if (*lastp == '/') { 544 /* Strip trailing slashes, if any. */ 545 while (lastp != path && *lastp == '/') { 546 lastp--; 547 } 548 549 /* ...and copy the result into the result buffer. */ 550 len = (lastp - path) + 1 /* last char */; 551 if (len > (PATH_MAX - 1)) { 552 len = PATH_MAX - 1; 553 } 554 555 memcpy(result, path, len); 556 result[len] = '\0'; 557 return; 558 } 559 } while (--lastp >= path); 560 561 /* No /'s found, return ".". */ 562 singledot: 563 strcpy(result, "."); 564 } 565 566 /* 567 * _prop_extern_write_file -- 568 * Write an externalized object to the specified file. 569 * The file is written atomically from the caller's perspective, 570 * and the mode set to 0666 modified by the caller's umask. 571 */ 572 static bool 573 _prop_extern_write_file(const char *fname, const char *data, size_t len) 574 { 575 char tname_store[PATH_MAX]; 576 char *tname = NULL; 577 int fd = -1; 578 int save_errno; 579 mode_t myumask; 580 bool rv = false; 581 582 if (len > SSIZE_MAX) { 583 errno = EFBIG; 584 return false; 585 } 586 587 /* 588 * Get the directory name where the file is to be written 589 * and create the temporary file. 590 */ 591 _prop_extern_file_dirname(fname, tname_store); 592 if (strlen(tname_store) + strlen(PLISTTMP) >= sizeof(tname_store)) { 593 errno = ENAMETOOLONG; 594 return false; 595 } 596 strcat(tname_store, PLISTTMP); 597 598 if ((fd = mkstemp(tname_store)) == -1) { 599 return false; 600 } 601 tname = tname_store; 602 603 if (write(fd, data, len) != (ssize_t)len) { 604 goto bad; 605 } 606 607 if (fsync(fd) == -1) { 608 goto bad; 609 } 610 611 myumask = umask(0); 612 (void)umask(myumask); 613 if (fchmod(fd, 0666 & ~myumask) == -1) { 614 goto bad; 615 } 616 617 if (rename(tname, fname) == -1) { 618 goto bad; 619 } 620 tname = NULL; 621 622 rv = true; 623 624 bad: 625 save_errno = errno; 626 if (fd != -1) { 627 (void) close(fd); 628 } 629 if (tname != NULL) { 630 (void) unlink(tname); 631 } 632 errno = save_errno; 633 return rv; 634 } 635 636 /* 637 * _prop_object_externalize_to_file -- 638 * Externalize an object to the specified file. 639 */ 640 bool 641 _prop_object_externalize_to_file(struct _prop_object *obj, const char *fname, 642 prop_format_t fmt) 643 { 644 char *data = _prop_object_externalize(obj, fmt); 645 if (data == NULL) { 646 return false; 647 } 648 bool rv = _prop_extern_write_file(fname, data, strlen(data)); 649 int save_errno = errno; 650 _PROP_FREE(data, M_TEMP); 651 errno = save_errno; 652 653 return rv; 654 } 655 656 /* 657 * prop_object_externalize_to_file -- 658 * Externalize an object to the specifed file in the default format. 659 */ 660 _PROP_EXPORT bool 661 prop_object_externalize_to_file(prop_object_t po, const char *fname) 662 { 663 return _prop_object_externalize_to_file((struct _prop_object *)po, 664 fname, _prop_format_default); 665 } 666 667 /* 668 * prop_object_externalize_to_file_with_format -- 669 * Externalize an object to the specifed file in the specified format. 670 */ 671 _PROP_EXPORT bool 672 prop_object_externalize_to_file_with_format(prop_object_t po, 673 const char *fname, prop_format_t fmt) 674 { 675 return _prop_object_externalize_to_file((struct _prop_object *)po, 676 fname, fmt); 677 } 678 #endif /* !_KERNEL && !_STANDALONE */ 679 680 /* 681 * prop_object_externalize -- 682 * Externalize an object in the default format. 683 */ 684 _PROP_EXPORT char * 685 prop_object_externalize(prop_object_t po) 686 { 687 return _prop_object_externalize((struct _prop_object *)po, 688 _prop_format_default); 689 } 690 691 /* 692 * prop_object_externalize_with_format -- 693 * Externalize an object in the specified format. 694 */ 695 _PROP_EXPORT char * 696 prop_object_externalize_with_format(prop_object_t po, prop_format_t fmt) 697 { 698 return _prop_object_externalize((struct _prop_object *)po, fmt); 699 } 700