From 3aa5f6a3a663a5f2694ec6fc8cdf9744b616e15e Mon Sep 17 00:00:00 2001 From: Nikias Bassen Date: Sun, 16 Apr 2023 16:06:11 +0200 Subject: Add new output-only formats and Define constants for the different plist formats This commit introduces constants for the different plist formats, and adds 3 new human-readable output-only formats: - PLIST_FORMAT_PRINT: the default human-readable format - PLIST_FORMAT_LIMD: "libimobiledevice" format (used in ideviceinfo) - PLIST_FORMAT_PLUTIL: plutil-style format Also, a new set of write functions has been added: - plist_write_to_string - plist_write_to_stream - plist_write_to_file Plus a simple "dump" function: - plist_print See documentation for details. --- src/out-default.c | 491 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 491 insertions(+) create mode 100644 src/out-default.c (limited to 'src/out-default.c') diff --git a/src/out-default.c b/src/out-default.c new file mode 100644 index 0000000..5747097 --- /dev/null +++ b/src/out-default.c @@ -0,0 +1,491 @@ +/* + * out-default.c + * libplist default *output-only* format - NOT for machine parsing + * + * Copyright (c) 2022-2023 Nikias Bassen All Rights Reserved. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include +#include +#include + +#include +#include +#include +#include + +#include + +#include "plist.h" +#include "strbuf.h" +#include "time64.h" + +#define MAC_EPOCH 978307200 + +static size_t dtostr(char *buf, size_t bufsize, double realval) +{ + size_t len = 0; + if (isnan(realval)) { + len = snprintf(buf, bufsize, "nan"); + } else if (isinf(realval)) { + len = snprintf(buf, bufsize, "%cinfinity", (realval > 0.0) ? '+' : '-'); + } else if (realval == 0.0f) { + len = snprintf(buf, bufsize, "0.0"); + } else { + size_t i = 0; + len = snprintf(buf, bufsize, "%.*g", 17, realval); + for (i = 0; buf && i < len; i++) { + if (buf[i] == ',') { + buf[i] = '.'; + break; + } else if (buf[i] == '.') { + break; + } + } + } + return len; +} + +static int node_to_string(node_t node, bytearray_t **outbuf, uint32_t depth, uint32_t indent, int partial_data) +{ + plist_data_t node_data = NULL; + + char *val = NULL; + size_t val_len = 0; + + uint32_t i = 0; + + if (!node) + return PLIST_ERR_INVALID_ARG; + + node_data = plist_get_data(node); + + switch (node_data->type) + { + case PLIST_BOOLEAN: + { + if (node_data->boolval) { + str_buf_append(*outbuf, "true", 4); + } else { + str_buf_append(*outbuf, "false", 5); + } + } + break; + + case PLIST_NULL: + str_buf_append(*outbuf, "null", 4); + break; + + case PLIST_INT: + val = (char*)malloc(64); + if (node_data->length == 16) { + val_len = snprintf(val, 64, "%"PRIu64, node_data->intval); + } else { + val_len = snprintf(val, 64, "%"PRIi64, node_data->intval); + } + str_buf_append(*outbuf, val, val_len); + free(val); + break; + + case PLIST_REAL: + val = (char*)malloc(64); + val_len = dtostr(val, 64, node_data->realval); + str_buf_append(*outbuf, val, val_len); + free(val); + break; + + case PLIST_STRING: + case PLIST_KEY: { + const char *charmap[32] = { + "\\u0000", "\\u0001", "\\u0002", "\\u0003", "\\u0004", "\\u0005", "\\u0006", "\\u0007", + "\\b", "\\t", "\\n", "\\u000b", "\\f", "\\r", "\\u000e", "\\u000f", + "\\u0010", "\\u0011", "\\u0012", "\\u0013", "\\u0014", "\\u0015", "\\u0016", "\\u0017", + "\\u0018", "\\u0019", "\\u001a", "\\u001b", "\\u001c", "\\u001d", "\\u001e", "\\u001f", + }; + size_t j = 0; + size_t len = 0; + off_t start = 0; + off_t cur = 0; + + str_buf_append(*outbuf, "\"", 1); + + len = node_data->length; + for (j = 0; j < len; j++) { + unsigned char ch = (unsigned char)node_data->strval[j]; + if (ch < 0x20) { + str_buf_append(*outbuf, node_data->strval + start, cur - start); + str_buf_append(*outbuf, charmap[ch], (charmap[ch][1] == 'u') ? 6 : 2); + start = cur+1; + } else if (ch == '"') { + str_buf_append(*outbuf, node_data->strval + start, cur - start); + str_buf_append(*outbuf, "\\\"", 2); + start = cur+1; + } + cur++; + } + str_buf_append(*outbuf, node_data->strval + start, cur - start); + + str_buf_append(*outbuf, "\"", 1); + } break; + + case PLIST_ARRAY: { + str_buf_append(*outbuf, "[", 1); + node_t ch; + uint32_t cnt = 0; + for (ch = node_first_child(node); ch; ch = node_next_sibling(ch)) { + if (cnt > 0) { + str_buf_append(*outbuf, ",", 1); + } + str_buf_append(*outbuf, "\n", 1); + for (i = 0; i <= depth+indent; i++) { + str_buf_append(*outbuf, " ", 2); + } + int res = node_to_string(ch, outbuf, depth+1, indent, partial_data); + if (res < 0) { + return res; + } + cnt++; + } + if (cnt > 0) { + str_buf_append(*outbuf, "\n", 1); + for (i = 0; i < depth+indent; i++) { + str_buf_append(*outbuf, " ", 2); + } + } + str_buf_append(*outbuf, "]", 1); + } break; + case PLIST_DICT: { + str_buf_append(*outbuf, "{", 1); + node_t ch; + uint32_t cnt = 0; + for (ch = node_first_child(node); ch; ch = node_next_sibling(ch)) { + if (cnt > 0 && cnt % 2 == 0) { + str_buf_append(*outbuf, ",", 1); + } + if (cnt % 2 == 0) { + str_buf_append(*outbuf, "\n", 1); + for (i = 0; i <= depth+indent; i++) { + str_buf_append(*outbuf, " ", 2); + } + } + int res = node_to_string(ch, outbuf, depth+1, indent, partial_data); + if (res < 0) { + return res; + } + if (cnt % 2 == 0) { + str_buf_append(*outbuf, ": ", 2); + } + cnt++; + } + if (cnt > 0) { + str_buf_append(*outbuf, "\n", 1); + for (i = 0; i < depth+indent; i++) { + str_buf_append(*outbuf, " ", 2); + } + } + str_buf_append(*outbuf, "}", 1); + } break; + case PLIST_DATA: + { + str_buf_append(*outbuf, "<", 1); + size_t len = node_data->length; + char charb[4]; + if (!partial_data || len <= 24) { + for (i = 0; i < len; i++) { + if (i > 0 && (i % 4 == 0)) + str_buf_append(*outbuf, " ", 1); + sprintf(charb, "%02x", (unsigned char)node_data->buff[i]); + str_buf_append(*outbuf, charb, 2); + } + } else { + for (i = 0; i < 16; i++) { + if (i > 0 && (i % 4 == 0)) + str_buf_append(*outbuf, " ", 1); + sprintf(charb, "%02x", (unsigned char)node_data->buff[i]); + str_buf_append(*outbuf, charb, 2); + } + str_buf_append(*outbuf, " ... ", 5); + for (i = len - 8; i < len; i++) { + sprintf(charb, "%02x", (unsigned char)node_data->buff[i]); + str_buf_append(*outbuf, charb, 2); + if (i > 0 && i < len-1 && (i % 4 == 0)) + str_buf_append(*outbuf, " ", 1); + } + } + str_buf_append(*outbuf, ">", 1); + } + break; + case PLIST_DATE: + { + Time64_T timev = (Time64_T)node_data->realval + MAC_EPOCH; + struct TM _btime; + struct TM *btime = gmtime64_r(&timev, &_btime); + if (btime) { + val = (char*)calloc(1, 26); + struct tm _tmcopy; + copy_TM64_to_tm(btime, &_tmcopy); + val_len = strftime(val, 26, "%Y-%m-%d %H:%M:%S +0000", &_tmcopy); + if (val_len > 0) { + str_buf_append(*outbuf, val, val_len); + } + free(val); + val = NULL; + } + } + break; + case PLIST_UID: + { + str_buf_append(*outbuf, "CF$UID:", 7); + val = (char*)malloc(64); + if (node_data->length == 16) { + val_len = snprintf(val, 64, "%"PRIu64, node_data->intval); + } else { + val_len = snprintf(val, 64, "%"PRIi64, node_data->intval); + } + str_buf_append(*outbuf, val, val_len); + free(val); + } + break; + default: + return PLIST_ERR_UNKNOWN; + } + + return PLIST_ERR_SUCCESS; +} + +#define PO10i_LIMIT (INT64_MAX/10) + +/* based on https://stackoverflow.com/a/4143288 */ +static int num_digits_i(int64_t i) +{ + int n; + int64_t po10; + n=1; + if (i < 0) { + i = (i == INT64_MIN) ? INT64_MAX : -i; + n++; + } + po10=10; + while (i>=po10) { + n++; + if (po10 > PO10i_LIMIT) break; + po10*=10; + } + return n; +} + +#define PO10u_LIMIT (UINT64_MAX/10) + +/* based on https://stackoverflow.com/a/4143288 */ +static int num_digits_u(uint64_t i) +{ + int n; + uint64_t po10; + n=1; + po10=10; + while (i>=po10) { + n++; + if (po10 > PO10u_LIMIT) break; + po10*=10; + } + return n; +} + +static int node_estimate_size(node_t node, uint64_t *size, uint32_t depth, uint32_t indent, int partial_data) +{ + plist_data_t data; + if (!node) { + return PLIST_ERR_INVALID_ARG; + } + data = plist_get_data(node); + if (node->children) { + node_t ch; + unsigned int n_children = node_n_children(node); + for (ch = node_first_child(node); ch; ch = node_next_sibling(ch)) { + int res = node_estimate_size(ch, size, depth + 1, indent, partial_data); + if (res < 0) { + return res; + } + } + switch (data->type) { + case PLIST_DICT: + *size += 2; // '{' and '}' + *size += n_children-1; // number of ':' and ',' + *size += n_children; // number of '\n' and extra space + *size += (uint64_t)n_children * (depth+indent+1); // indent for every 2nd child + *size += indent+1; // additional '\n' + break; + case PLIST_ARRAY: + *size += 2; // '[' and ']' + *size += n_children-1; // number of ',' + *size += n_children; // number of '\n' + *size += (uint64_t)n_children * ((depth+indent+1)<<1); // indent for every child + *size += indent+1; // additional '\n' + break; + default: + break; + } + *size += ((depth+indent) << 1); // indent for {} and [] + } else { + switch (data->type) { + case PLIST_STRING: + case PLIST_KEY: + *size += data->length; + *size += 2; + break; + case PLIST_INT: + if (data->length == 16) { + *size += num_digits_u(data->intval); + } else { + *size += num_digits_i((int64_t)data->intval); + } + break; + case PLIST_REAL: + *size += dtostr(NULL, 0, data->realval); + break; + case PLIST_BOOLEAN: + *size += ((data->boolval) ? 4 : 5); + break; + case PLIST_NULL: + *size += 4; + break; + case PLIST_DICT: + case PLIST_ARRAY: + *size += 2; + break; + case PLIST_DATA: + *size += 2; // < and > + if (partial_data) { + *size += 58; + } else { + *size += data->length * 2; + *size += data->length / 4; // space between 4 byte groups + } + break; + case PLIST_DATE: + *size += 25; + break; + case PLIST_UID: + *size += 7; // "CF$UID:" + *size += num_digits_u(data->intval); + break; + default: +#ifdef DEBUG + fprintf(stderr, "%s: invalid node type encountered\n", __func__); +#endif + return PLIST_ERR_UNKNOWN; + } + } + if (depth == 0) { + *size += 1; // final newline + } + return PLIST_ERR_SUCCESS; +} + +static plist_err_t _plist_write_to_strbuf(plist_t plist, strbuf_t *outbuf, plist_write_options_t options) +{ + uint8_t indent = 0; + if (options & PLIST_OPT_INDENT) { + indent = (options >> 24) & 0xFF; + } + uint8_t i; + for (i = 0; i < indent; i++) { + str_buf_append(outbuf, " ", 2); + } + int res = node_to_string(plist, &outbuf, 0, indent, options & PLIST_OPT_PARTIAL_DATA); + if (res < 0) { + return res; + } + if (!(options & PLIST_OPT_NO_NEWLINE)) { + str_buf_append(outbuf, "\n", 1); + } + return res; +} + +plist_err_t plist_write_to_string_default(plist_t plist, char **output, uint32_t* length, plist_write_options_t options) +{ + uint64_t size = 0; + int res; + + if (!plist || !output || !length) { + return PLIST_ERR_INVALID_ARG; + } + + uint8_t indent = 0; + if (options & PLIST_OPT_INDENT) { + indent = (options >> 24) & 0xFF; + } + + res = node_estimate_size(plist, &size, 0, indent, options & PLIST_OPT_PARTIAL_DATA); + if (res < 0) { + return res; + } + + strbuf_t *outbuf = str_buf_new(size); + if (!outbuf) { +#if DEBUG + fprintf(stderr, "%s: Could not allocate output buffer\n", __func__); +#endif + return PLIST_ERR_NO_MEM; + } + + res = _plist_write_to_strbuf(plist, outbuf, options); + if (res < 0) { + str_buf_free(outbuf); + *output = NULL; + *length = 0; + return res; + } + str_buf_append(outbuf, "\0", 1); + + *output = outbuf->data; + *length = outbuf->len - 1; + + outbuf->data = NULL; + str_buf_free(outbuf); + + return PLIST_ERR_SUCCESS; +} + +plist_err_t plist_write_to_stream_default(plist_t plist, FILE *stream, plist_write_options_t options) +{ + if (!plist || !stream) { + return PLIST_ERR_INVALID_ARG; + } + strbuf_t *outbuf = str_buf_new_for_stream(stream); + if (!outbuf) { +#if DEBUG + fprintf(stderr, "%s: Could not allocate output buffer\n", __func__); +#endif + return PLIST_ERR_NO_MEM; + } + + int res = _plist_write_to_strbuf(plist, outbuf, options); + if (res < 0) { + str_buf_free(outbuf); + return res; + } + + str_buf_free(outbuf); + + return PLIST_ERR_SUCCESS; +} -- cgit v1.1-32-gdbae