summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorGravatar Calil Khalil2026-02-21 10:39:24 -0300
committerGravatar Nikias Bassen2026-03-20 17:12:47 +0100
commit3edac28498d883f1f768699ee15ce85a82bb2a7b (patch)
treeb350517a84a671fc500d943168f973e7c97aa93e /src
parent6e03a1df6d1aa87c8f9e2b35f1a2ca60feca1c0e (diff)
downloadlibplist-3edac28498d883f1f768699ee15ce85a82bb2a7b.tar.gz
libplist-3edac28498d883f1f768699ee15ce85a82bb2a7b.tar.bz2
Add JSON coercion support for non-JSON plist typesHEADmaster
- Add PLIST_OPT_COERCE option to coerce PLIST_DATE, PLIST_DATA, and PLIST_UID to JSON-compatible types (ISO 8601 strings, Base64 strings, and integers) - Add plist_to_json_with_options() function to allow passing coercion options (and others) - Update plist_write_to_string() and plist_write_to_stream() to support coercion option - Add --coerce flag to plistutil for JSON output - Create plist2json symlink that automatically enables coercion when invoked
Diffstat (limited to 'src')
-rw-r--r--src/jplist.c129
-rw-r--r--src/plist.c4
2 files changed, 104 insertions, 29 deletions
diff --git a/src/jplist.c b/src/jplist.c
index 0ac1e0b..410d4b3 100644
--- a/src/jplist.c
+++ b/src/jplist.c
@@ -39,6 +39,10 @@
#include "strbuf.h"
#include "jsmn.h"
#include "hashtable.h"
+#include "base64.h"
+#include "time64.h"
+
+#define MAC_EPOCH 978307200
#ifdef DEBUG
static int plist_json_debug = 0;
@@ -115,7 +119,7 @@ static size_t dtostr(char *buf, size_t bufsize, double realval)
return len;
}
-static plist_err_t node_to_json(node_t node, bytearray_t **outbuf, uint32_t depth, int prettify)
+static plist_err_t node_to_json(node_t node, bytearray_t **outbuf, uint32_t depth, int prettify, int coerce)
{
plist_data_t node_data = NULL;
@@ -211,7 +215,7 @@ static plist_err_t node_to_json(node_t node, bytearray_t **outbuf, uint32_t dept
str_buf_append(*outbuf, " ", 2);
}
}
- plist_err_t res = node_to_json(ch, outbuf, depth+1, prettify);
+ plist_err_t res = node_to_json(ch, outbuf, depth+1, prettify, coerce);
if (res < 0) {
return res;
}
@@ -239,7 +243,7 @@ static plist_err_t node_to_json(node_t node, bytearray_t **outbuf, uint32_t dept
str_buf_append(*outbuf, " ", 2);
}
}
- plist_err_t res = node_to_json(ch, outbuf, depth+1, prettify);
+ plist_err_t res = node_to_json(ch, outbuf, depth+1, prettify, coerce);
if (res < 0) {
return res;
}
@@ -260,17 +264,60 @@ static plist_err_t node_to_json(node_t node, bytearray_t **outbuf, uint32_t dept
str_buf_append(*outbuf, "}", 1);
} break;
case PLIST_DATA:
- // NOT VALID FOR JSON
- PLIST_JSON_WRITE_ERR("PLIST_DATA type is not valid for JSON format\n");
- return PLIST_ERR_FORMAT;
+ if (coerce) {
+ size_t b64_len = ((node_data->length + 2) / 3) * 4;
+ char *b64_buf = (char*)malloc(b64_len + 1);
+ if (!b64_buf) {
+ return PLIST_ERR_NO_MEM;
+ }
+ size_t actual_len = base64encode(b64_buf, node_data->buff, node_data->length);
+ str_buf_append(*outbuf, "\"", 1);
+ str_buf_append(*outbuf, b64_buf, actual_len);
+ str_buf_append(*outbuf, "\"", 1);
+ free(b64_buf);
+ } else {
+ PLIST_JSON_WRITE_ERR("PLIST_DATA type is not valid for JSON format\n");
+ return PLIST_ERR_FORMAT;
+ }
+ break;
case PLIST_DATE:
- // NOT VALID FOR JSON
- PLIST_JSON_WRITE_ERR("PLIST_DATE type is not valid for JSON format\n");
- return PLIST_ERR_FORMAT;
+ if (coerce) {
+ Time64_T timev = (Time64_T)node_data->realval + MAC_EPOCH;
+ struct TM _btime;
+ struct TM *btime = gmtime64_r(&timev, &_btime);
+ char datebuf[32];
+ size_t datelen = 0;
+ if (btime) {
+ struct tm _tmcopy;
+ copy_TM64_to_tm(btime, &_tmcopy);
+ datelen = strftime(datebuf, sizeof(datebuf), "%Y-%m-%dT%H:%M:%SZ", &_tmcopy);
+ }
+ if (datelen <= 0) {
+ datelen = snprintf(datebuf, sizeof(datebuf), "1970-01-01T00:00:00Z");
+ }
+ str_buf_append(*outbuf, "\"", 1);
+ str_buf_append(*outbuf, datebuf, datelen);
+ str_buf_append(*outbuf, "\"", 1);
+ } else {
+ PLIST_JSON_WRITE_ERR("PLIST_DATE type is not valid for JSON format\n");
+ return PLIST_ERR_FORMAT;
+ }
+ break;
case PLIST_UID:
- // NOT VALID FOR JSON
- PLIST_JSON_WRITE_ERR("PLIST_UID type is not valid for JSON format\n");
- return PLIST_ERR_FORMAT;
+ if (coerce) {
+ 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);
+ } else {
+ PLIST_JSON_WRITE_ERR("PLIST_UID type is not valid for JSON format\n");
+ return PLIST_ERR_FORMAT;
+ }
+ break;
default:
return PLIST_ERR_UNKNOWN;
}
@@ -316,7 +363,7 @@ static int num_digits_u(uint64_t i)
return n;
}
-static plist_err_t _node_estimate_size(node_t node, uint64_t *size, uint32_t depth, int prettify, hashtable_t *visited)
+static plist_err_t _node_estimate_size(node_t node, uint64_t *size, uint32_t depth, int prettify, int coerce, hashtable_t *visited)
{
plist_data_t data;
if (!node) {
@@ -341,7 +388,7 @@ static plist_err_t _node_estimate_size(node_t node, uint64_t *size, uint32_t dep
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, prettify, visited);
+ plist_err_t res = _node_estimate_size(ch, size, depth + 1, prettify, coerce, visited);
if (res < 0) {
return res;
}
@@ -398,17 +445,36 @@ static plist_err_t _node_estimate_size(node_t node, uint64_t *size, uint32_t dep
*size += 2;
break;
case PLIST_DATA:
- // NOT VALID FOR JSON
- PLIST_JSON_WRITE_ERR("PLIST_DATA type is not valid for JSON format\n");
- return PLIST_ERR_FORMAT;
+ if (coerce) {
+ // base64 encoded string: 2 quotes + ((len+2)/3)*4 base64 chars
+ *size += 2 + ((data->length + 2) / 3) * 4;
+ } else {
+ PLIST_JSON_WRITE_ERR("PLIST_DATA type is not valid for JSON format\n");
+ return PLIST_ERR_FORMAT;
+ }
+ break;
case PLIST_DATE:
- // NOT VALID FOR JSON
- PLIST_JSON_WRITE_ERR("PLIST_DATE type is not valid for JSON format\n");
- return PLIST_ERR_FORMAT;
+ if (coerce) {
+ // ISO 8601 string: "YYYY-MM-DDTHH:MM:SSZ" = 22 chars max
+ *size += 24;
+ } else {
+ PLIST_JSON_WRITE_ERR("PLIST_DATE type is not valid for JSON format\n");
+ return PLIST_ERR_FORMAT;
+ }
+ break;
case PLIST_UID:
- // NOT VALID FOR JSON
- PLIST_JSON_WRITE_ERR("PLIST_UID type is not valid for JSON format\n");
- return PLIST_ERR_FORMAT;
+ if (coerce) {
+ // integer representation
+ if (data->length == 16) {
+ *size += num_digits_u(data->intval);
+ } else {
+ *size += num_digits_i((int64_t)data->intval);
+ }
+ } else {
+ PLIST_JSON_WRITE_ERR("PLIST_UID type is not valid for JSON format\n");
+ return PLIST_ERR_FORMAT;
+ }
+ break;
default:
PLIST_JSON_WRITE_ERR("invalid node type encountered\n");
return PLIST_ERR_UNKNOWN;
@@ -417,17 +483,23 @@ static plist_err_t _node_estimate_size(node_t node, uint64_t *size, uint32_t dep
return PLIST_ERR_SUCCESS;
}
-static plist_err_t node_estimate_size(node_t node, uint64_t *size, uint32_t depth, int prettify)
+static plist_err_t node_estimate_size(node_t node, uint64_t *size, uint32_t depth, int prettify, int coerce)
{
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, prettify, visited);
+ plist_err_t err = _node_estimate_size(node, size, depth, prettify, coerce, visited);
hash_table_destroy(visited);
return err;
}
plist_err_t plist_to_json(plist_t plist, char **plist_json, uint32_t* length, int prettify)
{
+ plist_write_options_t opts = prettify ? PLIST_OPT_NONE : PLIST_OPT_COMPACT;
+ return plist_to_json_with_options(plist, plist_json, length, opts);
+}
+
+plist_err_t plist_to_json_with_options(plist_t plist, char **plist_json, uint32_t* length, plist_write_options_t options)
+{
uint64_t size = 0;
plist_err_t res;
@@ -440,7 +512,10 @@ plist_err_t plist_to_json(plist_t plist, char **plist_json, uint32_t* length, in
return PLIST_ERR_FORMAT;
}
- res = node_estimate_size((node_t)plist, &size, 0, prettify);
+ int prettify = !(options & PLIST_OPT_COMPACT);
+ int coerce = options & PLIST_OPT_COERCE;
+
+ res = node_estimate_size((node_t)plist, &size, 0, prettify, coerce);
if (res < 0) {
return res;
}
@@ -451,7 +526,7 @@ plist_err_t plist_to_json(plist_t plist, char **plist_json, uint32_t* length, in
return PLIST_ERR_NO_MEM;
}
- res = node_to_json((node_t)plist, &outbuf, 0, prettify);
+ res = node_to_json((node_t)plist, &outbuf, 0, prettify, coerce);
if (res < 0) {
str_buf_free(outbuf);
*plist_json = NULL;
diff --git a/src/plist.c b/src/plist.c
index 7697a75..a6d3547 100644
--- a/src/plist.c
+++ b/src/plist.c
@@ -2404,7 +2404,7 @@ plist_err_t plist_write_to_string(plist_t plist, char **output, uint32_t* length
err = plist_to_xml(plist, output, length);
break;
case PLIST_FORMAT_JSON:
- err = plist_to_json(plist, output, length, ((options & PLIST_OPT_COMPACT) == 0));
+ err = plist_to_json_with_options(plist, output, length, options);
break;
case PLIST_FORMAT_OSTEP:
err = plist_to_openstep(plist, output, length, ((options & PLIST_OPT_COMPACT) == 0));
@@ -2442,7 +2442,7 @@ plist_err_t plist_write_to_stream(plist_t plist, FILE *stream, plist_format_t fo
err = plist_to_xml(plist, &output, &length);
break;
case PLIST_FORMAT_JSON:
- err = plist_to_json(plist, &output, &length, ((options & PLIST_OPT_COMPACT) == 0));
+ err = plist_to_json_with_options(plist, &output, &length, options);
break;
case PLIST_FORMAT_OSTEP:
err = plist_to_openstep(plist, &output, &length, ((options & PLIST_OPT_COMPACT) == 0));