diff options
Diffstat (limited to 'src/out-limd.c')
| -rw-r--r-- | src/out-limd.c | 469 |
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; +} |
