mirror of
https://github.com/libimobiledevice/libplist
synced 2026-06-08 15:29:34 +00:00
Add OpenStep coercion support for non-OpenStep plist types
- Use PLIST_OPT_COERCE option to coerce PLIST_BOOLEAN, PLIST_DATE, PLIST_UID, and PLIST_NULL to OpenStep-compatible types (1 or 0, ISO 8601 strings, integers, and 'NULL' string) - Add plist_to_openstep_with_options() function to allow passing coercion option (and others) - Update plist_write_to_string() and plist_write_to_stream() accordingly
This commit is contained in:
@@ -79,6 +79,17 @@ A nodepath of:
|
||||
JSON and OpenStep only: Print output in compact form. By default, the output
|
||||
will be pretty-printed.
|
||||
.TP
|
||||
.B \-C, \-\-coerce
|
||||
JSON and OpenStep only: Coerce non-compatible plist types to JSON/OpenStep
|
||||
compatible representations.
|
||||
Date values become ISO 8601 strings,
|
||||
data values become Base64-encoded strings (JSON),
|
||||
UID values become integers,
|
||||
boolean becomes 1 or 0 (OpenStep),
|
||||
and NULL becomes a string 'NULL' (OpenStep)
|
||||
|
||||
This options is implied when invoked as plist2json.
|
||||
.TP
|
||||
.B \-s, \-\-sort
|
||||
Sort all dictionary nodes lexicographically by key before converting to the output format.
|
||||
.TP
|
||||
|
||||
@@ -978,6 +978,30 @@ extern "C"
|
||||
*/
|
||||
PLIST_API plist_err_t plist_to_openstep(plist_t plist, char **plist_openstep, uint32_t* length, int prettify);
|
||||
|
||||
/**
|
||||
* Export the #plist_t structure to OpenStep format with extended options.
|
||||
*
|
||||
* When \a PLIST_OPT_COMPACT is set in \a options, the resulting OpenStep output
|
||||
* will be non-prettified.
|
||||
*
|
||||
* When \a PLIST_OPT_COERCE is set in \a options, plist types that have
|
||||
* no native OpenStep representation are converted to OpenStep-compatible types
|
||||
* instead of returning #PLIST_ERR_FORMAT:
|
||||
* - #PLIST_BOOLEAN is serialized as a 1 or 0
|
||||
* - #PLIST_DATE is serialized as an ISO 8601 date string
|
||||
* - #PLIST_NULL is serialized as string 'NULL'
|
||||
* - #PLIST_UID is serialized as an integer
|
||||
*
|
||||
* @param plist the root node to export
|
||||
* @param plist_openstep a pointer to a char* buffer. This function allocates the memory,
|
||||
* caller is responsible for freeing it.
|
||||
* @param length a pointer to an uint32_t variable. Represents the length of the allocated buffer.
|
||||
* @param options One or more bitwise ORed values of #plist_write_options_t.
|
||||
* @return PLIST_ERR_SUCCESS on success or a #plist_err_t on failure
|
||||
* @note Use plist_mem_free() to free the allocated memory.
|
||||
*/
|
||||
PLIST_API plist_err_t plist_to_openstep_with_options(plist_t plist, char **plist_openstep, uint32_t* length, plist_write_options_t options);
|
||||
|
||||
|
||||
/**
|
||||
* Import the #plist_t structure from XML format.
|
||||
|
||||
+115
-28
@@ -37,8 +37,11 @@
|
||||
|
||||
#include "plist.h"
|
||||
#include "strbuf.h"
|
||||
#include "time64.h"
|
||||
#include "hashtable.h"
|
||||
|
||||
#define MAC_EPOCH 978307200
|
||||
|
||||
#ifdef DEBUG
|
||||
static int plist_ostep_debug = 0;
|
||||
#define PLIST_OSTEP_ERR(...) if (plist_ostep_debug) { fprintf(stderr, "libplist[ostepparser] ERROR: " __VA_ARGS__); }
|
||||
@@ -144,7 +147,7 @@ static int str_needs_quotes(const char* str, size_t len)
|
||||
return 0;
|
||||
}
|
||||
|
||||
static plist_err_t node_to_openstep(node_t node, bytearray_t **outbuf, uint32_t depth, int prettify)
|
||||
static plist_err_t node_to_openstep(node_t node, bytearray_t **outbuf, uint32_t depth, int prettify, int coerce)
|
||||
{
|
||||
plist_data_t node_data = NULL;
|
||||
|
||||
@@ -235,7 +238,7 @@ static plist_err_t node_to_openstep(node_t node, bytearray_t **outbuf, uint32_t
|
||||
str_buf_append(*outbuf, " ", 2);
|
||||
}
|
||||
}
|
||||
plist_err_t res = node_to_openstep(ch, outbuf, depth+1, prettify);
|
||||
plist_err_t res = node_to_openstep(ch, outbuf, depth+1, prettify, coerce);
|
||||
if (res < 0) {
|
||||
return res;
|
||||
}
|
||||
@@ -263,7 +266,7 @@ static plist_err_t node_to_openstep(node_t node, bytearray_t **outbuf, uint32_t
|
||||
str_buf_append(*outbuf, " ", 2);
|
||||
}
|
||||
}
|
||||
plist_err_t res = node_to_openstep(ch, outbuf, depth+1, prettify);
|
||||
plist_err_t res = node_to_openstep(ch, outbuf, depth+1, prettify, coerce);
|
||||
if (res < 0) {
|
||||
return res;
|
||||
}
|
||||
@@ -302,19 +305,65 @@ static plist_err_t node_to_openstep(node_t node, bytearray_t **outbuf, uint32_t
|
||||
str_buf_append(*outbuf, ">", 1);
|
||||
} break;
|
||||
case PLIST_BOOLEAN:
|
||||
PLIST_OSTEP_WRITE_ERR("PLIST_BOOLEAN type is not valid for OpenStep format\n");
|
||||
return PLIST_ERR_FORMAT;
|
||||
if (coerce) {
|
||||
if (node_data->boolval) {
|
||||
str_buf_append(*outbuf, "1", 1);
|
||||
} else {
|
||||
str_buf_append(*outbuf, "0", 1);
|
||||
}
|
||||
} else {
|
||||
PLIST_OSTEP_WRITE_ERR("PLIST_BOOLEAN type is not valid for OpenStep format\n");
|
||||
return PLIST_ERR_FORMAT;
|
||||
}
|
||||
break;
|
||||
case PLIST_NULL:
|
||||
PLIST_OSTEP_WRITE_ERR("PLIST_NULL type is not valid for OpenStep format\n");
|
||||
return PLIST_ERR_FORMAT;
|
||||
if (coerce) {
|
||||
str_buf_append(*outbuf, "NULL", 4);
|
||||
} else {
|
||||
PLIST_OSTEP_WRITE_ERR("PLIST_NULL type is not valid for OpenStep format\n");
|
||||
return PLIST_ERR_FORMAT;
|
||||
}
|
||||
break;
|
||||
case PLIST_DATE:
|
||||
// NOT VALID FOR OPENSTEP
|
||||
PLIST_OSTEP_WRITE_ERR("PLIST_DATE type is not valid for OpenStep 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 {
|
||||
// NOT VALID FOR OPENSTEP
|
||||
PLIST_OSTEP_WRITE_ERR("PLIST_DATE type is not valid for OpenStep format\n");
|
||||
return PLIST_ERR_FORMAT;
|
||||
}
|
||||
break;
|
||||
case PLIST_UID:
|
||||
// NOT VALID FOR OPENSTEP
|
||||
PLIST_OSTEP_WRITE_ERR("PLIST_UID type is not valid for OpenStep 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 {
|
||||
// NOT VALID FOR OPENSTEP
|
||||
PLIST_OSTEP_WRITE_ERR("PLIST_UID type is not valid for OpenStep format\n");
|
||||
return PLIST_ERR_FORMAT;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
return PLIST_ERR_UNKNOWN;
|
||||
}
|
||||
@@ -360,7 +409,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) {
|
||||
@@ -385,7 +434,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;
|
||||
}
|
||||
@@ -442,17 +491,46 @@ static plist_err_t _node_estimate_size(node_t node, uint64_t *size, uint32_t dep
|
||||
*size += data->length/4;
|
||||
break;
|
||||
case PLIST_BOOLEAN:
|
||||
// NOT VALID FOR OPENSTEP
|
||||
PLIST_OSTEP_WRITE_ERR("PLIST_BOOLEAN type is not valid for OpenStep format\n");
|
||||
return PLIST_ERR_FORMAT;
|
||||
if (coerce) {
|
||||
*size += 1;
|
||||
} else {
|
||||
// NOT VALID FOR OPENSTEP
|
||||
PLIST_OSTEP_WRITE_ERR("PLIST_BOOLEAN type is not valid for OpenStep format\n");
|
||||
return PLIST_ERR_FORMAT;
|
||||
}
|
||||
break;
|
||||
case PLIST_DATE:
|
||||
// NOT VALID FOR OPENSTEP
|
||||
PLIST_OSTEP_WRITE_ERR("PLIST_DATE type is not valid for OpenStep format\n");
|
||||
return PLIST_ERR_FORMAT;
|
||||
if (coerce) {
|
||||
// ISO 8601 string: "YYYY-MM-DDTHH:MM:SSZ" = 22 chars max
|
||||
*size += 24;
|
||||
} else {
|
||||
// NOT VALID FOR OPENSTEP
|
||||
PLIST_OSTEP_WRITE_ERR("PLIST_DATE type is not valid for OpenStep format\n");
|
||||
return PLIST_ERR_FORMAT;
|
||||
}
|
||||
break;
|
||||
case PLIST_UID:
|
||||
// NOT VALID FOR OPENSTEP
|
||||
PLIST_OSTEP_WRITE_ERR("PLIST_UID type is not valid for OpenStep format\n");
|
||||
return PLIST_ERR_FORMAT;
|
||||
if (coerce) {
|
||||
if (data->length == 16) {
|
||||
*size += num_digits_u(data->intval);
|
||||
} else {
|
||||
*size += num_digits_i((int64_t)data->intval);
|
||||
}
|
||||
} else {
|
||||
// NOT VALID FOR OPENSTEP
|
||||
PLIST_OSTEP_WRITE_ERR("PLIST_UID type is not valid for OpenStep format\n");
|
||||
return PLIST_ERR_FORMAT;
|
||||
}
|
||||
break;
|
||||
case PLIST_NULL:
|
||||
if (coerce) {
|
||||
*size += 4;
|
||||
} else {
|
||||
// NOT VALID FOR OPENSTEP
|
||||
PLIST_OSTEP_WRITE_ERR("PLIST_NULL type is not valid for OpenStep format\n");
|
||||
return PLIST_ERR_FORMAT;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
PLIST_OSTEP_WRITE_ERR("invalid node type encountered\n");
|
||||
return PLIST_ERR_UNKNOWN;
|
||||
@@ -461,16 +539,22 @@ 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_openstep(plist_t plist, char **openstep, uint32_t* length, int prettify)
|
||||
{
|
||||
plist_write_options_t opts = prettify ? PLIST_OPT_NONE : PLIST_OPT_COMPACT;
|
||||
return plist_to_openstep_with_options(plist, openstep, length, opts);
|
||||
}
|
||||
|
||||
plist_err_t plist_to_openstep_with_options(plist_t plist, char **openstep, uint32_t* length, plist_write_options_t options)
|
||||
{
|
||||
uint64_t size = 0;
|
||||
plist_err_t res;
|
||||
@@ -479,7 +563,10 @@ plist_err_t plist_to_openstep(plist_t plist, char **openstep, uint32_t* length,
|
||||
return PLIST_ERR_INVALID_ARG;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
@@ -490,7 +577,7 @@ plist_err_t plist_to_openstep(plist_t plist, char **openstep, uint32_t* length,
|
||||
return PLIST_ERR_NO_MEM;
|
||||
}
|
||||
|
||||
res = node_to_openstep((node_t)plist, &outbuf, 0, prettify);
|
||||
res = node_to_openstep((node_t)plist, &outbuf, 0, prettify, coerce);
|
||||
if (res < 0) {
|
||||
str_buf_free(outbuf);
|
||||
*openstep = NULL;
|
||||
|
||||
+2
-2
@@ -2407,7 +2407,7 @@ plist_err_t plist_write_to_string(plist_t plist, char **output, uint32_t* length
|
||||
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));
|
||||
err = plist_to_openstep_with_options(plist, output, length, options);
|
||||
break;
|
||||
case PLIST_FORMAT_PRINT:
|
||||
err = plist_write_to_string_default(plist, output, length, options);
|
||||
@@ -2445,7 +2445,7 @@ plist_err_t plist_write_to_stream(plist_t plist, FILE *stream, plist_format_t fo
|
||||
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));
|
||||
err = plist_to_openstep_with_options(plist, &output, &length, options);
|
||||
break;
|
||||
case PLIST_FORMAT_PRINT:
|
||||
err = plist_write_to_stream_default(plist, stream, options);
|
||||
|
||||
+12
-5
@@ -75,10 +75,14 @@ static void print_usage(int argc, char *argv[])
|
||||
printf(" -n, --nodepath PATH Restrict output to nodepath defined by PATH.\n");
|
||||
printf(" -c, --compact JSON and OpenStep only: Print output in compact form.\n");
|
||||
printf(" By default, the output will be pretty-printed.\n");
|
||||
printf(" -C, --coerce JSON only: Coerce non-JSON plist types to JSON-compatible\n");
|
||||
printf(" representations. Date values become ISO 8601 strings,\n");
|
||||
printf(" data values become Base64-encoded strings, and UID values\n");
|
||||
printf(" become integers. Implied when invoked as plist2json.\n");
|
||||
printf(" -C, --coerce JSON + OpenStep only: Coerce non-compatible plist types\n");
|
||||
printf(" to JSON/OpenStep compatible representations.\n");
|
||||
printf(" Date values become ISO 8601 strings,\n");
|
||||
printf(" data values become Base64-encoded strings (JSON),\n");
|
||||
printf(" UID values become integers,\n");
|
||||
printf(" boolean becomes 1 or 0 (OpenStep),\n");
|
||||
printf(" and NULL becomes a string 'NULL' (OpenStep)\n");
|
||||
printf(" This options is implied when invoked as plist2json.\n");
|
||||
printf(" -s, --sort Sort all dictionary nodes lexicographically by key\n");
|
||||
printf(" before converting to the output format.\n");
|
||||
printf(" -d, --debug Enable extended debug output\n");
|
||||
@@ -431,7 +435,10 @@ int main(int argc, char *argv[])
|
||||
if (options->flags & OPT_COERCE) wropts |= PLIST_OPT_COERCE;
|
||||
output_res = plist_to_json_with_options(root_node, &plist_out, &size, wropts);
|
||||
} else if (options->out_fmt == PLIST_FORMAT_OSTEP) {
|
||||
output_res = plist_to_openstep(root_node, &plist_out, &size, !(options->flags & OPT_COMPACT));
|
||||
plist_write_options_t wropts = PLIST_OPT_NONE;
|
||||
if (options->flags & OPT_COMPACT) wropts |= PLIST_OPT_COMPACT;
|
||||
if (options->flags & OPT_COERCE) wropts |= PLIST_OPT_COERCE;
|
||||
output_res = plist_to_openstep_with_options(root_node, &plist_out, &size, wropts);
|
||||
} else {
|
||||
plist_write_to_stream(root_node, stdout, options->out_fmt, PLIST_OPT_PARTIAL_DATA);
|
||||
plist_free(root_node);
|
||||
|
||||
Reference in New Issue
Block a user