<feed xmlns='http://www.w3.org/2005/Atom'>
<title>libplist/src/plist.c, branch 2.8.1</title>
<subtitle>Library to handle Apple Property List format files in binary or XML</subtitle>
<link rel='alternate' type='text/html' href='https://cgit.sukimashita.com/libplist.git/'/>
<entry>
<title>plist: treat NaN date values as 0 in plist_get_date_val</title>
<updated>2026-11-06T09:32:42+00:00</updated>
<author>
<name>Nikias Bassen</name>
</author>
<published>2026-11-06T09:32:42+00:00</published>
<link rel='alternate' type='text/html' href='https://cgit.sukimashita.com/libplist.git/commit/?id=a3215e2a6577df39d944578af671f9dae2770336'/>
<id>a3215e2a6577df39d944578af671f9dae2770336</id>
<content type='text'>
Converting NaN to int32_t is undefined behavior, and NaN can come from
a parsed date node.
</content>
<content type='xhtml'>
<div xmlns='http://www.w3.org/1999/xhtml'>
<pre>
Converting NaN to int32_t is undefined behavior, and NaN can come from
a parsed date node.
</pre>
</div>
</content>
</entry>
<entry>
<title>plist: fix the (sec, usec) date API round trip</title>
<updated>2026-11-06T09:29:06+00:00</updated>
<author>
<name>Flavio Toffalini</name>
</author>
<published>2026-10-26T09:03:57+00:00</published>
<link rel='alternate' type='text/html' href='https://cgit.sukimashita.com/libplist.git/commit/?id=1d3b54c784da260f71d885f5f560d69c211a645e'/>
<id>1d3b54c784da260f71d885f5f560d69c211a645e</id>
<content type='text'>
plist_set_date_val stores val = sec + usec / 1000000, but
plist_get_date_val splits it with a truncating cast and fabs():

    *sec  = (int32_t)val;
    val   = fabs((val - (int64_t)val) * 1000000);
    *usec = (int32_t)val;

Two results are wrong. The product is truncated, so set(5, 6) reads
back as (5, 5) - 29.3% of round trips over sec in [0, 3] come back a
microsecond short. And for a negative value, truncation toward zero
plus fabs() give a pair that does not denote the same instant:
set(-3, 250000) stores -2.75 and reads back as (-2, 750000), which the
setter would store as -1.25. Every round trip with sec &lt; 0 and
usec != 0 is wrong.

(int32_t)val is also undefined when the stored value is outside the
int32_t range.

Round the seconds down, round the fraction to microseconds carrying
1000000 into the seconds, and clamp the seconds before converting, so
sec + usec / 1000000 == val to the microsecond with 0 &lt;= usec &lt;
1000000 - the convention the setter uses.

Add test/date_val.c covering fixed cases and a sweep over sec in
[-2, 2]. It fails on an unmodified tree - set(5, 6), set(-3, 250000)
and set(-1, 1) all come back as different instants - and passes with
this change, with make check at 44/44.

Co-Authored-By: Claude Opus 5 &lt;noreply@anthropic.com&gt;
Claude-Session: https://claude.ai/code/session_01Arn4YaWtcQBDsQuJtyBmFP
</content>
<content type='xhtml'>
<div xmlns='http://www.w3.org/1999/xhtml'>
<pre>
plist_set_date_val stores val = sec + usec / 1000000, but
plist_get_date_val splits it with a truncating cast and fabs():

    *sec  = (int32_t)val;
    val   = fabs((val - (int64_t)val) * 1000000);
    *usec = (int32_t)val;

Two results are wrong. The product is truncated, so set(5, 6) reads
back as (5, 5) - 29.3% of round trips over sec in [0, 3] come back a
microsecond short. And for a negative value, truncation toward zero
plus fabs() give a pair that does not denote the same instant:
set(-3, 250000) stores -2.75 and reads back as (-2, 750000), which the
setter would store as -1.25. Every round trip with sec &lt; 0 and
usec != 0 is wrong.

(int32_t)val is also undefined when the stored value is outside the
int32_t range.

Round the seconds down, round the fraction to microseconds carrying
1000000 into the seconds, and clamp the seconds before converting, so
sec + usec / 1000000 == val to the microsecond with 0 &lt;= usec &lt;
1000000 - the convention the setter uses.

Add test/date_val.c covering fixed cases and a sweep over sec in
[-2, 2]. It fails on an unmodified tree - set(5, 6), set(-3, 250000)
and set(-1, 1) all come back as different instants - and passes with
this change, with make check at 44/44.

Co-Authored-By: Claude Opus 5 &lt;noreply@anthropic.com&gt;
Claude-Session: https://claude.ai/code/session_01Arn4YaWtcQBDsQuJtyBmFP
</pre>
</div>
</content>
</entry>
<entry>
<title>Fix leaks and crashes on allocation failure</title>
<updated>2026-11-05T11:02:05+00:00</updated>
<author>
<name>Nikias Bassen</name>
</author>
<published>2026-11-05T11:02:05+00:00</published>
<link rel='alternate' type='text/html' href='https://cgit.sukimashita.com/libplist.git/commit/?id=9fd3406baec5997d386cdb4c7492117604ba0a70'/>
<id>9fd3406baec5997d386cdb4c7492117604ba0a70</id>
<content type='text'>
Found by failing each allocation in turn under ASan/UBSan/LSan:

- plist_free() leaked the whole tree if its work stack could not be
  allocated; free the children iteratively without extra memory
- plist_copy(): when a shallow copy of a dict/array failed, the original's
  lookup table was freed along with the copy's data (use-after-free)
- bplist parser: fix a NULL dereference in parse_date_node(), check the
  node_attach() results, and don't leak on failure; plist_to_bin() no
  longer leaks on an allocation failure
- free the plist data when node creation fails (plist constructors, XML,
  JSON and binary parsers)
</content>
<content type='xhtml'>
<div xmlns='http://www.w3.org/1999/xhtml'>
<pre>
Found by failing each allocation in turn under ASan/UBSan/LSan:

- plist_free() leaked the whole tree if its work stack could not be
  allocated; free the children iteratively without extra memory
- plist_copy(): when a shallow copy of a dict/array failed, the original's
  lookup table was freed along with the copy's data (use-after-free)
- bplist parser: fix a NULL dereference in parse_date_node(), check the
  node_attach() results, and don't leak on failure; plist_to_bin() no
  longer leaks on an allocation failure
- free the plist data when node creation fails (plist constructors, XML,
  JSON and binary parsers)
</pre>
</div>
</content>
</entry>
<entry>
<title>Harden allocation failure handling and size arithmetic</title>
<updated>2026-11-05T11:01:59+00:00</updated>
<author>
<name>Nikias Bassen</name>
</author>
<published>2026-11-05T11:01:59+00:00</published>
<link rel='alternate' type='text/html' href='https://cgit.sukimashita.com/libplist.git/commit/?id=4dc93bc274867b42aa82fc91ee4d5878d23a3f36'/>
<id>4dc93bc274867b42aa82fc91ee4d5878d23a3f36</id>
<content type='text'>
- check malloc/calloc/realloc results in constructors, the JSON and
  OpenStep writers, the XML text-part helpers and base64 decoding, and
  return PLIST_ERR_NO_MEM instead of asserting or dereferencing NULL
- guard size calculations against overflow in the UTF-8/UTF-16BE
  conversions, ptrarray, and bytearray
- make hash_table_insert() report failure instead of silently dropping
  the entry; writers propagate PLIST_ERR_NO_MEM, and dict lookup caches
  are discarded so lookups fall back to a linear search
- don't overwrite an earlier parse error code in the XML parser, and
  report PLIST_ERR_NO_MEM when reading text content fails for lack of memory
- OpenStep parser: check node/data/string allocations and dict/array
  insertion results

Based on #323 by bg0d-droid.

Co-authored-by: bg0d-droid &lt;81825610+bg0d-droid@users.noreply.github.com&gt;
</content>
<content type='xhtml'>
<div xmlns='http://www.w3.org/1999/xhtml'>
<pre>
- check malloc/calloc/realloc results in constructors, the JSON and
  OpenStep writers, the XML text-part helpers and base64 decoding, and
  return PLIST_ERR_NO_MEM instead of asserting or dereferencing NULL
- guard size calculations against overflow in the UTF-8/UTF-16BE
  conversions, ptrarray, and bytearray
- make hash_table_insert() report failure instead of silently dropping
  the entry; writers propagate PLIST_ERR_NO_MEM, and dict lookup caches
  are discarded so lookups fall back to a linear search
- don't overwrite an earlier parse error code in the XML parser, and
  report PLIST_ERR_NO_MEM when reading text content fails for lack of memory
- OpenStep parser: check node/data/string allocations and dict/array
  insertion results

Based on #323 by bg0d-droid.

Co-authored-by: bg0d-droid &lt;81825610+bg0d-droid@users.noreply.github.com&gt;
</pre>
</div>
</content>
</entry>
<entry>
<title>plist: fix use-after-free in plist_copy_node() on stack realloc</title>
<updated>2026-11-05T08:35:33+00:00</updated>
<author>
<name>Nikias Bassen</name>
</author>
<published>2026-11-05T08:35:33+00:00</published>
<link rel='alternate' type='text/html' href='https://cgit.sukimashita.com/libplist.git/commit/?id=3e9c034c93fa6f59584ec179d3df1333f250c694'/>
<id>3e9c034c93fa6f59584ec179d3df1333f250c694</id>
<content type='text'>
plist_copy_node() kept a pointer `f` to the top frame of its traversal
stack and dereferenced it after the stack was grown with realloc(),
which may move the buffer. Plists nested deeper than 64 levels hit this
path, causing a heap-use-after-free when reading f-&gt;depth.

Read the child depth before the realloc and use it when building the
new frame.

Thanks to @sdnuhfiu for reporting!

Fixes #328
</content>
<content type='xhtml'>
<div xmlns='http://www.w3.org/1999/xhtml'>
<pre>
plist_copy_node() kept a pointer `f` to the top frame of its traversal
stack and dereferenced it after the stack was grown with realloc(),
which may move the buffer. Plists nested deeper than 64 levels hit this
path, causing a heap-use-after-free when reading f-&gt;depth.

Read the child depth before the realloc and use it when building the
new frame.

Thanks to @sdnuhfiu for reporting!

Fixes #328
</pre>
</div>
</content>
</entry>
<entry>
<title>plist: fix inverted strcmp in string to boolean conversion</title>
<updated>2026-09-29T14:43:02+00:00</updated>
<author>
<name>Arpit Jain</name>
</author>
<published>2026-07-29T05:25:18+00:00</published>
<link rel='alternate' type='text/html' href='https://cgit.sukimashita.com/libplist.git/commit/?id=bd68e726e45c10fd223814eaf6129e1bb2f7b022'/>
<id>bd68e726e45c10fd223814eaf6129e1bb2f7b022</id>
<content type='text'>
plist_dict_get_bool() compared the string value with strcmp() but treated a
non-zero return as a match. strcmp() returns 0 on equality, so the conditions
were inverted: "true" produced 0, "false" produced 1, and any other string
also produced 1. The error branch could never be reached, since it required
both comparisons to return 0 at once.

The result is that the API returns the opposite of the stored value for both
valid boolean strings, and returns true for strings that are not booleans at
all, instead of reporting the conversion error.

Compare == 0 in both conditions:

  input          before   after
  true           0        1
  false          1        0
  not-a-bool     1        error
  TRUE           1        error
  (empty)        1        error

Signed-off-by: Arpit Jain &lt;arpitjain099@gmail.com&gt;
</content>
<content type='xhtml'>
<div xmlns='http://www.w3.org/1999/xhtml'>
<pre>
plist_dict_get_bool() compared the string value with strcmp() but treated a
non-zero return as a match. strcmp() returns 0 on equality, so the conditions
were inverted: "true" produced 0, "false" produced 1, and any other string
also produced 1. The error branch could never be reached, since it required
both comparisons to return 0 at once.

The result is that the API returns the opposite of the stored value for both
valid boolean strings, and returns true for strings that are not booleans at
all, instead of reporting the conversion error.

Compare == 0 in both conditions:

  input          before   after
  true           0        1
  false          1        0
  not-a-bool     1        error
  TRUE           1        error
  (empty)        1        error

Signed-off-by: Arpit Jain &lt;arpitjain099@gmail.com&gt;
</pre>
</div>
</content>
</entry>
<entry>
<title>ptrarray: Handle realloc() failure in ptr_array_insert()</title>
<updated>2026-09-27T06:06:22+00:00</updated>
<author>
<name>Comtea04</name>
</author>
<published>2026-09-27T06:06:22+00:00</published>
<link rel='alternate' type='text/html' href='https://cgit.sukimashita.com/libplist.git/commit/?id=8b48fa72d359de89215f50cbf6ac8c77ae5de9b4'/>
<id>8b48fa72d359de89215f50cbf6ac8c77ae5de9b4</id>
<content type='text'>
ptr_array_insert() assigned the result of realloc() directly to
pa-&gt;pdata and bumped the capacity without checking for failure, so on
out-of-memory the old buffer was leaked and the following store or
memmove() wrote through a NULL pointer.

Keep the old buffer on failure and return -1 from ptr_array_insert()
and ptr_array_add(). The callers now handle the error:

- the array lookup cache (plist.c) is dropped instead of silently going
  out of sync with the node list; lookups then fall back to walking
  the children, as they do before the cache exists
- bplist parsing (used_indexes) and serialization (objects) return
  PLIST_ERR_NO_MEM; ignoring the error in parse_bin_node_at_index()
  would otherwise loop forever since the array never grows

Co-Authored-By: Claude Opus 5.5 &lt;noreply@anthropic.com&gt;
</content>
<content type='xhtml'>
<div xmlns='http://www.w3.org/1999/xhtml'>
<pre>
ptr_array_insert() assigned the result of realloc() directly to
pa-&gt;pdata and bumped the capacity without checking for failure, so on
out-of-memory the old buffer was leaked and the following store or
memmove() wrote through a NULL pointer.

Keep the old buffer on failure and return -1 from ptr_array_insert()
and ptr_array_add(). The callers now handle the error:

- the array lookup cache (plist.c) is dropped instead of silently going
  out of sync with the node list; lookups then fall back to walking
  the children, as they do before the cache exists
- bplist parsing (used_indexes) and serialization (objects) return
  PLIST_ERR_NO_MEM; ignoring the error in parse_bin_node_at_index()
  would otherwise loop forever since the array never grows

Co-Authored-By: Claude Opus 5.5 &lt;noreply@anthropic.com&gt;
</pre>
</div>
</content>
</entry>
<entry>
<title>refactor: centralize formatting helpers and harden out-plutil</title>
<updated>2026-05-22T16:46:02+00:00</updated>
<author>
<name>Nikias Bassen</name>
</author>
<published>2026-05-22T16:46:02+00:00</published>
<link rel='alternate' type='text/html' href='https://cgit.sukimashita.com/libplist.git/commit/?id=9711459dbed7d60bb00c7d2c052623e8489c88e1'/>
<id>9711459dbed7d60bb00c7d2c052623e8489c88e1</id>
<content type='text'>
</content>
<content type='xhtml'>
<div xmlns='http://www.w3.org/1999/xhtml'>
<pre>
</pre>
</div>
</content>
</entry>
<entry>
<title>Add error handling to all modification functions</title>
<updated>2026-04-26T23:45:33+00:00</updated>
<author>
<name>Nikias Bassen</name>
</author>
<published>2026-04-26T23:45:33+00:00</published>
<link rel='alternate' type='text/html' href='https://cgit.sukimashita.com/libplist.git/commit/?id=d35b31d0a2661b6346a1592a5eb7b70e66b2a141'/>
<id>d35b31d0a2661b6346a1592a5eb7b70e66b2a141</id>
<content type='text'>
Convert all array/dict modification functions from void to plist_err_t return type:
  - plist_array_set_item: replace at index n
  - plist_array_append_item: append to end
  - plist_array_insert_item: insert at position n
  - plist_array_remove_item: remove item at index n
  - plist_array_item_remove: remove item from its array parent
  - plist_dict_set_item: replace or insert key/value
  - plist_dict_remove_item: remove key/value pair
  - plist_dict_merge: merge source dict into target

Returns:
  - PLIST_ERR_SUCCESS on success
  - PLIST_ERR_INVALID_ARG for invalid arguments (NULL, wrong type, out of range, etc.)
  - PLIST_ERR_NO_MEM on memory allocation failure
  - PLIST_ERR_UNKNOWN on unexpected internal errors

Header documentation updated with full error code semantics for each function.
</content>
<content type='xhtml'>
<div xmlns='http://www.w3.org/1999/xhtml'>
<pre>
Convert all array/dict modification functions from void to plist_err_t return type:
  - plist_array_set_item: replace at index n
  - plist_array_append_item: append to end
  - plist_array_insert_item: insert at position n
  - plist_array_remove_item: remove item at index n
  - plist_array_item_remove: remove item from its array parent
  - plist_dict_set_item: replace or insert key/value
  - plist_dict_remove_item: remove key/value pair
  - plist_dict_merge: merge source dict into target

Returns:
  - PLIST_ERR_SUCCESS on success
  - PLIST_ERR_INVALID_ARG for invalid arguments (NULL, wrong type, out of range, etc.)
  - PLIST_ERR_NO_MEM on memory allocation failure
  - PLIST_ERR_UNKNOWN on unexpected internal errors

Header documentation updated with full error code semantics for each function.
</pre>
</div>
</content>
</entry>
<entry>
<title>Add OpenStep coercion support for non-OpenStep plist types</title>
<updated>2026-03-22T18:16:51+00:00</updated>
<author>
<name>Nikias Bassen</name>
</author>
<published>2026-03-22T18:16:51+00:00</published>
<link rel='alternate' type='text/html' href='https://cgit.sukimashita.com/libplist.git/commit/?id=c8b36a80bad4a1fe488927af4da0ecbcf10079bb'/>
<id>c8b36a80bad4a1fe488927af4da0ecbcf10079bb</id>
<content type='text'>
- 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
</content>
<content type='xhtml'>
<div xmlns='http://www.w3.org/1999/xhtml'>
<pre>
- 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
</pre>
</div>
</content>
</entry>
</feed>
