summaryrefslogtreecommitdiffstats
path: root/src/out-limd.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/out-limd.c')
-rw-r--r--src/out-limd.c469
1 files changed, 469 insertions, 0 deletions
diff --git a/src/out-limd.c b/src/out-limd.c
new file mode 100644
index 0000000..83a5e26
--- /dev/null
+++ b/src/out-limd.c
@@ -0,0 +1,469 @@
+/*
+ * out-limd.c
+ * libplist *output-only* format introduced by libimobiledevice/ideviceinfo
+ * - 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 <config.h>
+#endif
+
+#include <string.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <time.h>
+
+#include <inttypes.h>
+#include <ctype.h>
+#include <math.h>
+#include <limits.h>
+
+#include <node.h>
+
+#include "plist.h"
+#include "strbuf.h"
+#include "time64.h"
+#include "base64.h"
+#include "hashtable.h"
+#include "common.h"
+
+static plist_err_t node_to_string(node_t node, bytearray_t **outbuf, uint32_t depth, uint32_t indent)
+{
+ plist_data_t node_data = NULL;
+
+ char *val = NULL;
+ int slen = 0;
+ size_t val_len = 0;
+ char buf[16];
+
+ uint32_t i = 0;
+
+ if (!node || !outbuf || !*outbuf) {
+ return PLIST_ERR_INVALID_ARG;
+ }
+
+ node_data = plist_get_data(node);
+ if (!node_data) {
+ return PLIST_ERR_INVALID_ARG;
+ }
+
+ 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 (!val) {
+ return PLIST_ERR_NO_MEM;
+ }
+ if (node_data->length == 16) {
+ slen = snprintf(val, 64, "%" PRIu64, node_data->intval);
+ } else {
+ slen = snprintf(val, 64, "%" PRIi64, node_data->intval);
+ }
+ if (slen < 0) {
+ free(val);
+ return PLIST_ERR_UNKNOWN;
+ }
+ val_len = (size_t)slen;
+ str_buf_append(*outbuf, val, val_len);
+ free(val);
+ break;
+
+ case PLIST_REAL:
+ val = (char*)malloc(64);
+ if (!val) {
+ return PLIST_ERR_NO_MEM;
+ }
+ val_len = dtostr(val, 64, node_data->realval);
+ str_buf_append(*outbuf, val, val_len);
+ free(val);
+ break;
+
+ case PLIST_STRING:
+ case PLIST_KEY: {
+ if (!node_data->strval && node_data->length > 0) {
+ return PLIST_ERR_INVALID_ARG;
+ }
+ 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 = node_data->length;
+ size_t start = 0;
+ size_t cur = 0;
+
+ 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;
+ }
+
+ cur++;
+ }
+ str_buf_append(*outbuf, node_data->strval + start, cur - start);
+ break;
+ }
+ case PLIST_ARRAY: {
+ node_t ch;
+ uint32_t cnt = 0;
+ for (ch = node_first_child(node); ch; ch = node_next_sibling(ch)) {
+ if (cnt > 0 || (cnt == 0 && node->parent != NULL)) {
+ str_buf_append(*outbuf, "\n", 1);
+ for (i = 0; i < depth+indent; i++) {
+ str_buf_append(*outbuf, " ", 1);
+ }
+ }
+ slen = snprintf(buf, sizeof(buf), "%u: ", cnt);
+ if (slen < 0) {
+ return PLIST_ERR_UNKNOWN;
+ }
+ str_buf_append(*outbuf, buf, (size_t)slen);
+ plist_err_t res = node_to_string(ch, outbuf, depth+1, indent);
+ if (res < 0) {
+ return res;
+ }
+ cnt++;
+ }
+ break;
+ }
+ case PLIST_DICT: {
+ 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, "\n", 1);
+ for (i = 0; i < depth+indent; i++) {
+ str_buf_append(*outbuf, " ", 1);
+ }
+ }
+ plist_err_t res = node_to_string(ch, outbuf, depth+1, indent);
+ if (res < 0) {
+ return res;
+ }
+ if (cnt % 2 == 0) {
+ plist_t valnode = (plist_t)node_next_sibling(ch);
+ if (PLIST_IS_ARRAY(valnode)) {
+ slen = snprintf(buf, sizeof(buf), "[%u]:", plist_array_get_size(valnode));
+ if (slen < 0) {
+ return PLIST_ERR_UNKNOWN;
+ }
+ str_buf_append(*outbuf, buf, (size_t)slen);
+ } else {
+ str_buf_append(*outbuf, ": ", 2);
+ }
+ }
+ cnt++;
+ }
+ break;
+ }
+ case PLIST_DATA:
+ {
+ if (!node_data->buff && node_data->length > 0) {
+ return PLIST_ERR_INVALID_ARG;
+ }
+ if (node_data->length == 0) {
+ break;
+ }
+#define BASE64_CHUNK_SIZE 3072
+#define BASE64_BUF_SIZE (4 * ((BASE64_CHUNK_SIZE + 2) / 3) + 4)
+ val = (char*)malloc(BASE64_BUF_SIZE);
+ if (!val) return PLIST_ERR_NO_MEM;
+ size_t done = 0;
+ while (done < node_data->length) {
+ size_t amount = node_data->length - done;
+ if (amount > BASE64_CHUNK_SIZE) {
+ amount = BASE64_CHUNK_SIZE;
+ }
+ size_t bsize = base64encode(val, node_data->buff + done, amount);
+ str_buf_append(*outbuf, val, bsize);
+ done += amount;
+ }
+ free(val);
+#undef BASE64_CHUNK_SIZE
+#undef BASE64_BUF_SIZE
+ }
+ break;
+ case PLIST_DATE:
+ {
+ Time64_T timev;
+ if (plist_real_to_time64(node_data->realval, &timev) < 0) {
+#if DEBUG
+ fprintf(stderr, "libplist: ERROR: Encountered invalid date value %f\n", node_data->realval);
+#endif
+ return PLIST_ERR_INVALID_ARG;
+ }
+ struct TM _btime;
+ struct TM *btime = gmtime64_r(&timev, &_btime);
+ if (btime) {
+ val = (char*)calloc(1, 24);
+ if (!val) return PLIST_ERR_NO_MEM;
+ struct tm _tmcopy;
+ copy_TM64_to_tm(btime, &_tmcopy);
+ val_len = strftime(val, 24, "%Y-%m-%dT%H:%M:%SZ", &_tmcopy);
+ if (val_len > 0) {
+ str_buf_append(*outbuf, val, val_len);
+ }
+ free(val);
+ }
+ }
+ break;
+
+ case PLIST_UID:
+ {
+ str_buf_append(*outbuf, "CF$UID:", 7);
+ val = (char*)malloc(64);
+ if (!val) {
+ return PLIST_ERR_NO_MEM;
+ }
+ if (node_data->length == 16) {
+ slen = snprintf(val, 64, "%" PRIu64, node_data->intval);
+ } else {
+ slen = snprintf(val, 64, "%" PRIi64, node_data->intval);
+ }
+ if (slen < 0) {
+ free(val);
+ return PLIST_ERR_UNKNOWN;
+ }
+ val_len = (size_t)slen;
+ str_buf_append(*outbuf, val, val_len);
+ free(val);
+ }
+ break;
+ default:
+ return PLIST_ERR_UNKNOWN;
+ }
+
+ return PLIST_ERR_SUCCESS;
+}
+
+static plist_err_t _node_estimate_size(node_t node, uint64_t *size, uint32_t depth, uint32_t indent, hashtable_t *visited)
+{
+ plist_data_t data;
+ if (!node) {
+ return PLIST_ERR_INVALID_ARG;
+ }
+
+ if (depth > PLIST_MAX_NESTING_DEPTH) {
+#if DEBUG
+ fprintf(stderr, "libplist: ERROR: maximum nesting depth (%u) exceeded\n", (unsigned)PLIST_MAX_NESTING_DEPTH);
+#endif
+ return PLIST_ERR_MAX_NESTING;
+ }
+
+ if (hash_table_lookup(visited, node)) {
+#if DEBUG
+ fprintf(stderr, "libplist: ERROR: circular reference detected\n");
+#endif
+ return PLIST_ERR_CIRCULAR_REF;
+ }
+
+ // mark as visited
+ hash_table_insert(visited, node, (void*)1);
+
+ 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)) {
+ plist_err_t res = _node_estimate_size(ch, size, depth + 1, indent, visited);
+ if (res != PLIST_ERR_SUCCESS) {
+ return res;
+ }
+ }
+ switch (data->type) {
+ case PLIST_DICT:
+ *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 += 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;
+ }
+ } else {
+ switch (data->type) {
+ case PLIST_STRING:
+ case PLIST_KEY:
+ *size += data->length;
+ 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 += 3;
+ break;
+ case PLIST_DATA:
+ *size += (data->length / 3) * 4 + 4;
+ break;
+ case PLIST_DATE:
+ *size += 23;
+ 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 node_estimate_size(node_t node, uint64_t *size, uint32_t depth, uint32_t indent)
+{
+ hashtable_t *visited = hash_table_new(plist_node_ptr_hash, plist_node_ptr_compare, NULL);
+ if (!visited) return PLIST_ERR_NO_MEM;
+ plist_err_t err = _node_estimate_size(node, size, depth, indent, visited);
+ hash_table_destroy(visited);
+ return err;
+}
+
+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, " ", 1);
+ }
+ plist_err_t res = node_to_string((node_t)plist, &outbuf, 0, indent);
+ 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_limd(plist_t plist, char **output, uint32_t* length, plist_write_options_t options)
+{
+ uint64_t size = 0;
+ plist_err_t 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((node_t)plist, &size, 0, indent);
+ 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 = (char*)outbuf->data;
+ *length = outbuf->len - 1;
+
+ outbuf->data = NULL;
+ str_buf_free(outbuf);
+
+ return PLIST_ERR_SUCCESS;
+}
+
+plist_err_t plist_write_to_stream_limd(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;
+ }
+
+ plist_err_t 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;
+}