#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 "hashtable.h"
#include "common.h"
static plist_err_t node_to_string(node_t node, bytearray_t **outbuf, uint32_t depth)
{
plist_data_t node_data = NULL;
char *val = NULL;
int slen = 0;
size_t val_len = 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, "1", 1);
} else {
str_buf_append(*outbuf, "0", 1);
}
}
break;
case PLIST_NULL:
str_buf_append(*outbuf, "<null>", 6);
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 = 0;
size_t start = 0;
size_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;
uint32_t i;
for (ch = node_first_child(node); ch; ch = node_next_sibling(ch)) {
str_buf_append(*outbuf, "\n", 1);
for (i = 0; i <= depth; i++) {
str_buf_append(*outbuf, " ", 2);
}
char indexbuf[16];
slen = snprintf(indexbuf, sizeof(indexbuf), "%u => ", cnt);
if (slen < 0) {
return PLIST_ERR_UNKNOWN;
}
str_buf_append(*outbuf, indexbuf, (size_t)slen);
plist_err_t res = node_to_string(ch, outbuf, depth+1);
if (res < 0) {
return res;
}
cnt++;
}
if (cnt > 0) {
str_buf_append(*outbuf, "\n", 1);
for (i = 0; i < depth; 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;
uint32_t i;
for (ch = node_first_child(node); ch; ch = node_next_sibling(ch)) {
if (cnt % 2 == 0) {
str_buf_append(*outbuf, "\n", 1);
for (i = 0; i <= depth; i++) {
str_buf_append(*outbuf, " ", 2);
}
}
plist_err_t res = node_to_string(ch, outbuf, depth+1);
if (res < 0) {
return res;
}
if (cnt % 2 == 0) {
str_buf_append(*outbuf, " => ", 4);
}
cnt++;
}
if (cnt > 0) {
str_buf_append(*outbuf, "\n", 1);
for (i = 0; i < depth; i++) {
str_buf_append(*outbuf, " ", 2);
}
}
str_buf_append(*outbuf, "}", 1);
} break;
case PLIST_DATA:
{
if (!node_data->buff && node_data->length > 0) {
return PLIST_ERR_INVALID_ARG;
}
val = (char*)calloc(1, 48);
if (!val) return PLIST_ERR_NO_MEM;
size_t len = node_data->length;
slen = snprintf(val, 48, "{length = %" PRIu64 ", bytes = 0x", (uint64_t)len);
if (slen < 0) {
free(val);
return PLIST_ERR_UNKNOWN;
}
str_buf_append(*outbuf, val, (size_t)slen);
size_t j;
if (len <= 24) {
for (j = 0; j < len; j++) {
snprintf(val, 4, "%02x", (unsigned char)node_data->buff[j]);
str_buf_append(*outbuf, val, 2);
}
} else {
for (j = 0; j < 16; j++) {
if (j > 0 && (j % 4 == 0))
str_buf_append(*outbuf, " ", 1);
snprintf(val, 4, "%02x", (unsigned char)node_data->buff[j]);
str_buf_append(*outbuf, val, 2);
}
str_buf_append(*outbuf, " ... ", 5);
for (j = len - 8; j < len; j++) {
snprintf(val, 4, "%02x", (unsigned char)node_data->buff[j]);
str_buf_append(*outbuf, val, 2);
if (j > 0 && (j % 4 == 0))
str_buf_append(*outbuf, " ", 1);
}
}
free(val);
val = NULL;
str_buf_append(*outbuf, "}", 1);
}
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, 26);
if (!val) return PLIST_ERR_NO_MEM;
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:
{
#define UID_FMT "<CFKeyedArchiverUID %p [%p]>{value = %" PRIu64 "}"
slen = snprintf(NULL, 0, UID_FMT, node, node_data, node_data->intval);
if (slen < 0) {
return PLIST_ERR_UNKNOWN;
}
val_len = (size_t)slen;
val = (char*)malloc(val_len + 1);
if (!val) return PLIST_ERR_NO_MEM;
slen = snprintf(val, val_len+1, UID_FMT, node, node_data, node_data->intval);
if (slen < 0 || (size_t)slen > val_len) {
free(val);
return PLIST_ERR_UNKNOWN;
}
val_len = (size_t)slen;
str_buf_append(*outbuf, val, val_len);
free(val);
val = NULL;
#undef UID_FMT
}
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, 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;
}
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, visited);
if (res != PLIST_ERR_SUCCESS) {
return res;
}
}
switch (data->type) {
case PLIST_DICT:
*size += 2; *size += n_children-1; *size += n_children; *size += (uint64_t)n_children * (depth+1); *size += 1; break;
case PLIST_ARRAY:
*size += 2; *size += n_children-1; *size += n_children; *size += (uint64_t)n_children * ((depth+1)<<1); *size += 1; break;
default:
break;
}
*size += (depth << 1); } 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 += 1;
break;
case PLIST_NULL:
*size += 6;
break;
case PLIST_DICT:
case PLIST_ARRAY:
*size += 2;
break;
case PLIST_DATA:
*size = (data->length <= 24) ? 73 : 100;
break;
case PLIST_DATE:
*size += 25;
break;
case PLIST_UID:
*size += 88;
break;
default:
#ifdef DEBUG
fprintf(stderr, "invalid node type encountered\n");
#endif
return PLIST_ERR_UNKNOWN;
}
}
if (depth == 0) {
*size += 1; }
return PLIST_ERR_SUCCESS;
}
static plist_err_t node_estimate_size(node_t node, uint64_t *size, uint32_t depth)
{
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, 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)
{
plist_err_t res = node_to_string((node_t)plist, &outbuf, 0);
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_plutil(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;
}
res = node_estimate_size((node_t)plist, &size, 0);
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_plutil(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;
}