refactor: centralize formatting helpers and harden out-plutil
diff --git a/src/Makefile.am b/src/Makefile.am
index 1a416ad..ce4f931 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -21,6 +21,7 @@
 	ptrarray.c ptrarray.h \
 	time64.c time64.h \
 	time64_limits.h \
+	common.c common.h \
 	xplist.c \
 	bplist.c \
 	jsmn.c jsmn.h \
diff --git a/src/common.c b/src/common.c
new file mode 100644
index 0000000..0b11d57
--- /dev/null
+++ b/src/common.c
@@ -0,0 +1,100 @@
+/*
+ * common.c
+ * contains some common functions
+ *
+ * Copyright (c) 2026 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
+ */
+#include <math.h>
+#include <stdio.h>
+#include <stdint.h>
+#include "common.h"
+
+size_t dtostr(char *buf, size_t bufsize, double realval)
+{
+    int slen = 0;
+    if (isnan(realval)) {
+        slen = snprintf(buf, bufsize, "nan");
+    } else if (isinf(realval)) {
+        slen = snprintf(buf, bufsize, "%cinfinity", (realval > 0.0) ? '+' : '-');
+    } else if (realval == 0.0f) {
+        slen = snprintf(buf, bufsize, "0.0");
+    } else {
+        slen = snprintf(buf, bufsize, "%.*g", 17, realval);
+        if (slen < 0) {
+            return 0;
+        }
+        if (!buf || bufsize == 0) {
+            return (size_t)slen;
+        }
+        size_t len = (size_t)slen;
+        if (len >= bufsize) {
+            len = bufsize - 1;
+        }
+        size_t i = 0;
+        for (i = 0; i < len; i++) {
+            if (buf[i] == ',') {
+                buf[i] = '.';
+                break;
+            } else if (buf[i] == '.') {
+                break;
+            }
+        }
+        return len;
+    }
+    if (slen < 0) {
+        return 0;
+    }
+    return (size_t)slen;
+}
+
+/* based on https://stackoverflow.com/a/4143288 */
+#define PO10i_LIMIT (INT64_MAX/10)
+int num_digits_i(int64_t i)
+{
+    int n;
+    int64_t po10;
+    n=1;
+    if (i < 0) {
+        i = (i == INT64_MIN) ? INT64_MAX : -i;
+        n++;
+    }
+    po10=10;
+    while (i>=po10) {
+        n++;
+        if (po10 > PO10i_LIMIT) break;
+        po10*=10;
+    }
+    return n;
+}
+#undef PO10i_LIMIT
+
+/* based on https://stackoverflow.com/a/4143288 */
+#define PO10u_LIMIT (UINT64_MAX/10)
+int num_digits_u(uint64_t i)
+{
+    int n;
+    uint64_t po10;
+    n=1;
+    po10=10;
+    while (i>=po10) {
+        n++;
+        if (po10 > PO10u_LIMIT) break;
+        po10*=10;
+    }
+    return n;
+}
+#undef PO10u_LIMIT
diff --git a/src/common.h b/src/common.h
new file mode 100644
index 0000000..7e5aff7
--- /dev/null
+++ b/src/common.h
@@ -0,0 +1,32 @@
+/*
+ * common.h
+ * contains some common functions
+ *
+ * Copyright (c) 2026 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
+ */
+#ifndef COMMON_H
+#define COMMON_H
+
+#include <stddef.h>
+
+#define MAC_EPOCH 978307200
+
+size_t dtostr(char *buf, size_t bufsize, double realval);
+int num_digits_i(int64_t i);
+int num_digits_u(uint64_t i);
+
+#endif
diff --git a/src/jplist.c b/src/jplist.c
index 410d4b3..cc1cd1b 100644
--- a/src/jplist.c
+++ b/src/jplist.c
@@ -41,8 +41,7 @@
 #include "hashtable.h"
 #include "base64.h"
 #include "time64.h"
-
-#define MAC_EPOCH 978307200
+#include "common.h"
 
 #ifdef DEBUG
 static int plist_json_debug = 0;
@@ -95,30 +94,6 @@
 #endif
 #endif
 
-static size_t dtostr(char *buf, size_t bufsize, double realval)
-{
-    size_t len = 0;
-    if (isnan(realval)) {
-        len = snprintf(buf, bufsize, "nan");
-    } else if (isinf(realval)) {
-        len = snprintf(buf, bufsize, "%cinfinity", (realval > 0.0) ? '+' : '-');
-    } else if (realval == 0.0f) {
-        len = snprintf(buf, bufsize, "0.0");
-    } else {
-        size_t i = 0;
-        len = snprintf(buf, bufsize, "%.*g", 17, realval);
-        for (i = 0; buf && i < len; i++) {
-            if (buf[i] == ',') {
-                buf[i] = '.';
-                break;
-            } else if (buf[i] == '.') {
-                break;
-            }
-        }
-    }
-    return len;
-}
-
 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;
@@ -325,44 +300,6 @@
     return PLIST_ERR_SUCCESS;
 }
 
-#define PO10i_LIMIT (INT64_MAX/10)
-
-/* based on https://stackoverflow.com/a/4143288 */
-static int num_digits_i(int64_t i)
-{
-    int n;
-    int64_t po10;
-    n=1;
-    if (i < 0) {
-        i = (i == INT64_MIN) ? INT64_MAX : -i;
-        n++;
-    }
-    po10=10;
-    while (i>=po10) {
-        n++;
-        if (po10 > PO10i_LIMIT) break;
-        po10*=10;
-    }
-    return n;
-}
-
-#define PO10u_LIMIT (UINT64_MAX/10)
-
-/* based on https://stackoverflow.com/a/4143288 */
-static int num_digits_u(uint64_t i)
-{
-    int n;
-    uint64_t po10;
-    n=1;
-    po10=10;
-    while (i>=po10) {
-        n++;
-        if (po10 > PO10u_LIMIT) break;
-        po10*=10;
-    }
-    return n;
-}
-
 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;
@@ -556,6 +493,7 @@
 
 static int64_t parse_decimal(const char* str, const char* str_end, char** endp)
 {
+    const uint64_t po10i_limit = INT64_MAX / 10;
     uint64_t MAX = INT64_MAX;
     uint64_t x = 0;
     int is_neg = 0;
@@ -571,7 +509,7 @@
         MAX++;
     }
     while (*endp < str_end && isdigit(**endp)) {
-        if (x > PO10i_LIMIT) {
+        if (x > po10i_limit) {
             x = MAX;
             break;
         }
diff --git a/src/oplist.c b/src/oplist.c
index 3c48b3a..292467f 100644
--- a/src/oplist.c
+++ b/src/oplist.c
@@ -39,8 +39,7 @@
 #include "strbuf.h"
 #include "time64.h"
 #include "hashtable.h"
-
-#define MAC_EPOCH 978307200
+#include "common.h"
 
 #ifdef DEBUG
 static int plist_ostep_debug = 0;
@@ -93,30 +92,6 @@
 #endif
 #endif
 
-static size_t dtostr(char *buf, size_t bufsize, double realval)
-{
-    size_t len = 0;
-    if (isnan(realval)) {
-        len = snprintf(buf, bufsize, "nan");
-    } else if (isinf(realval)) {
-        len = snprintf(buf, bufsize, "%cinfinity", (realval > 0.0) ? '+' : '-');
-    } else if (realval == 0.0f) {
-        len = snprintf(buf, bufsize, "0.0");
-    } else {
-        size_t i = 0;
-        len = snprintf(buf, bufsize, "%.*g", 17, realval);
-        for (i = 0; buf && i < len; i++) {
-            if (buf[i] == ',') {
-                buf[i] = '.';
-                break;
-            } else if (buf[i] == '.') {
-                break;
-            }
-        }
-    }
-    return len;
-}
-
 static const char allowed_unquoted_chars[256] = {
     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
@@ -371,44 +346,6 @@
     return PLIST_ERR_SUCCESS;
 }
 
-#define PO10i_LIMIT (INT64_MAX/10)
-
-/* based on https://stackoverflow.com/a/4143288 */
-static int num_digits_i(int64_t i)
-{
-    int n;
-    int64_t po10;
-    n=1;
-    if (i < 0) {
-        i = (i == INT64_MIN) ? INT64_MAX : -i;
-        n++;
-    }
-    po10=10;
-    while (i>=po10) {
-        n++;
-        if (po10 > PO10i_LIMIT) break;
-        po10*=10;
-    }
-    return n;
-}
-
-#define PO10u_LIMIT (UINT64_MAX/10)
-
-/* based on https://stackoverflow.com/a/4143288 */
-static int num_digits_u(uint64_t i)
-{
-    int n;
-    uint64_t po10;
-    n=1;
-    po10=10;
-    while (i>=po10) {
-        n++;
-        if (po10 > PO10u_LIMIT) break;
-        po10*=10;
-    }
-    return n;
-}
-
 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;
diff --git a/src/out-default.c b/src/out-default.c
index 676a677..5d4b6fc 100644
--- a/src/out-default.c
+++ b/src/out-default.c
@@ -39,46 +39,7 @@
 #include "strbuf.h"
 #include "time64.h"
 #include "hashtable.h"
-
-#define MAC_EPOCH 978307200
-
-static size_t dtostr(char *buf, size_t bufsize, double realval)
-{
-    int slen = 0;
-    if (isnan(realval)) {
-        slen = snprintf(buf, bufsize, "nan");
-    } else if (isinf(realval)) {
-        slen = snprintf(buf, bufsize, "%cinfinity", (realval > 0.0) ? '+' : '-');
-    } else if (realval == 0.0f) {
-        slen = snprintf(buf, bufsize, "0.0");
-    } else {
-        slen = snprintf(buf, bufsize, "%.*g", 17, realval);
-        if (slen < 0) {
-            return 0;
-        }
-        if (!buf || bufsize == 0) {
-            return (size_t)slen;
-        }
-        size_t len = (size_t)slen;
-        if (len >= bufsize) {
-            len = bufsize - 1;
-        }
-        size_t i = 0;
-        for (i = 0; i < len; i++) {
-            if (buf[i] == ',') {
-                buf[i] = '.';
-                break;
-            } else if (buf[i] == '.') {
-                break;
-            }
-        }
-        return len;
-    }
-    if (slen < 0) {
-        return 0;
-    }
-    return (size_t)slen;
-}
+#include "common.h"
 
 static plist_err_t node_to_string(node_t node, bytearray_t **outbuf, uint32_t depth, uint32_t indent, int partial_data)
 {
@@ -313,44 +274,6 @@
     return PLIST_ERR_SUCCESS;
 }
 
-#define PO10i_LIMIT (INT64_MAX/10)
-
-/* based on https://stackoverflow.com/a/4143288 */
-static int num_digits_i(int64_t i)
-{
-    int n;
-    int64_t po10;
-    n=1;
-    if (i < 0) {
-        i = (i == INT64_MIN) ? INT64_MAX : -i;
-        n++;
-    }
-    po10=10;
-    while (i>=po10) {
-        n++;
-        if (po10 > PO10i_LIMIT) break;
-        po10*=10;
-    }
-    return n;
-}
-
-#define PO10u_LIMIT (UINT64_MAX/10)
-
-/* based on https://stackoverflow.com/a/4143288 */
-static int num_digits_u(uint64_t i)
-{
-    int n;
-    uint64_t po10;
-    n=1;
-    po10=10;
-    while (i>=po10) {
-        n++;
-        if (po10 > PO10u_LIMIT) break;
-        po10*=10;
-    }
-    return n;
-}
-
 static plist_err_t _node_estimate_size(node_t node, uint64_t *size, uint32_t depth, uint32_t indent, int partial_data, hashtable_t *visited)
 {
     plist_data_t data;
diff --git a/src/out-limd.c b/src/out-limd.c
index 6d864cb..b01f966 100644
--- a/src/out-limd.c
+++ b/src/out-limd.c
@@ -41,46 +41,7 @@
 #include "time64.h"
 #include "base64.h"
 #include "hashtable.h"
-
-#define MAC_EPOCH 978307200
-
-static size_t dtostr(char *buf, size_t bufsize, double realval)
-{
-    int slen = 0;
-    if (isnan(realval)) {
-        slen = snprintf(buf, bufsize, "nan");
-    } else if (isinf(realval)) {
-        slen = snprintf(buf, bufsize, "%cinfinity", (realval > 0.0) ? '+' : '-');
-    } else if (realval == 0.0f) {
-        slen = snprintf(buf, bufsize, "0.0");
-    } else {
-        slen = snprintf(buf, bufsize, "%.*g", 17, realval);
-        if (slen < 0) {
-            return 0;
-        }
-        if (!buf || bufsize == 0) {
-            return (size_t)slen;
-        }
-        size_t len = (size_t)slen;
-        if (len >= bufsize) {
-            len = bufsize - 1;
-        }
-        size_t i = 0;
-        for (i = 0; i < len; i++) {
-            if (buf[i] == ',') {
-                buf[i] = '.';
-                break;
-            } else if (buf[i] == '.') {
-                break;
-            }
-        }
-        return len;
-    }
-    if (slen < 0) {
-        return 0;
-    }
-    return (size_t)slen;
-}
+#include "common.h"
 
 static plist_err_t node_to_string(node_t node, bytearray_t **outbuf, uint32_t depth, uint32_t indent)
 {
@@ -304,44 +265,6 @@
     return PLIST_ERR_SUCCESS;
 }
 
-#define PO10i_LIMIT (INT64_MAX/10)
-
-/* based on https://stackoverflow.com/a/4143288 */
-static int num_digits_i(int64_t i)
-{
-    int n;
-    int64_t po10;
-    n=1;
-    if (i < 0) {
-        i = (i == INT64_MIN) ? INT64_MAX : -i;
-        n++;
-    }
-    po10=10;
-    while (i>=po10) {
-        n++;
-        if (po10 > PO10i_LIMIT) break;
-        po10*=10;
-    }
-    return n;
-}
-
-#define PO10u_LIMIT (UINT64_MAX/10)
-
-/* based on https://stackoverflow.com/a/4143288 */
-static int num_digits_u(uint64_t i)
-{
-    int n;
-    uint64_t po10;
-    n=1;
-    po10=10;
-    while (i>=po10) {
-        n++;
-        if (po10 > PO10u_LIMIT) break;
-        po10*=10;
-    }
-    return n;
-}
-
 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;
diff --git a/src/out-plutil.c b/src/out-plutil.c
index bf9e72e..9f7968e 100644
--- a/src/out-plutil.c
+++ b/src/out-plutil.c
@@ -39,46 +39,24 @@
 #include "strbuf.h"
 #include "time64.h"
 #include "hashtable.h"
-
-#define MAC_EPOCH 978307200
-
-static size_t dtostr(char *buf, size_t bufsize, double realval)
-{
-    size_t len = 0;
-    if (isnan(realval)) {
-        len = snprintf(buf, bufsize, "nan");
-    } else if (isinf(realval)) {
-        len = snprintf(buf, bufsize, "%cinfinity", (realval > 0.0) ? '+' : '-');
-    } else if (realval == 0.0f) {
-        len = snprintf(buf, bufsize, "0.0");
-    } else {
-        size_t i = 0;
-        len = snprintf(buf, bufsize, "%.*g", 17, realval);
-        for (i = 0; buf && i < len; i++) {
-            if (buf[i] == ',') {
-                buf[i] = '.';
-                break;
-            } else if (buf[i] == '.') {
-                break;
-            }
-        }
-    }
-    return len;
-}
+#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;
 
-    uint32_t i = 0;
-
-    if (!node)
+    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)
     {
@@ -98,17 +76,24 @@
 
     case PLIST_INT:
         val = (char*)malloc(64);
+        if (!val) return PLIST_ERR_NO_MEM;
         if (node_data->length == 16) {
-            val_len = snprintf(val, 64, "%" PRIu64, node_data->intval);
+            slen = snprintf(val, 64, "%" PRIu64, node_data->intval);
         } else {
-            val_len = snprintf(val, 64, "%" PRIi64, node_data->intval);
+            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);
@@ -116,6 +101,9 @@
 
     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",
@@ -124,8 +112,8 @@
         };
         size_t j = 0;
         size_t len = 0;
-        off_t start = 0;
-        off_t cur = 0;
+        size_t start = 0;
+        size_t cur = 0;
 
         str_buf_append(*outbuf, "\"", 1);
 
@@ -152,14 +140,18 @@
         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];
-            int l = sprintf(indexbuf, "%u => ", cnt);
-            str_buf_append(*outbuf, indexbuf, l);
+            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;
@@ -178,6 +170,7 @@
         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);
@@ -204,27 +197,36 @@
         } 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;
-            size_t slen = snprintf(val, 48, "{length = %" PRIu64 ", bytes = 0x", (uint64_t)len);
-            str_buf_append(*outbuf, val, slen);
+            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 (i = 0; i < len; i++) {
-                    sprintf(val, "%02x", (unsigned char)node_data->buff[i]);
+                for (j = 0; j < len; j++) {
+                    snprintf(val, 4, "%02x", (unsigned char)node_data->buff[j]);
                     str_buf_append(*outbuf, val, 2);
                 }
             } else {
-                for (i = 0; i < 16; i++) {
-                    if (i > 0 && (i % 4 == 0))
+                for (j = 0; j < 16; j++) {
+                    if (j > 0 && (j % 4 == 0))
                         str_buf_append(*outbuf, " ", 1);
-                    sprintf(val, "%02x", (unsigned char)node_data->buff[i]);
+                    snprintf(val, 4, "%02x", (unsigned char)node_data->buff[j]);
                     str_buf_append(*outbuf, val, 2);
                 }
                 str_buf_append(*outbuf, " ... ", 5);
-                for (i = len - 8; i < len; i++) {
-                    sprintf(val, "%02x", (unsigned char)node_data->buff[i]);
+                for (j = len - 8; j < len; j++) {
+                    snprintf(val, 4, "%02x", (unsigned char)node_data->buff[j]);
                     str_buf_append(*outbuf, val, 2);
-                    if (i > 0 && (i % 4 == 0))
+                    if (j > 0 && (j % 4 == 0))
                         str_buf_append(*outbuf, " ", 1);
                 }
             }
@@ -240,6 +242,7 @@
             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);
@@ -253,11 +256,24 @@
         break;
     case PLIST_UID:
         {
-            val = (char*)malloc(88);
-            val_len = sprintf(val, "<CFKeyedArchiverUID %p [%p]>{value = %" PRIu64 "}", node, node_data, node_data->intval);
+#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:
@@ -267,44 +283,6 @@
     return PLIST_ERR_SUCCESS;
 }
 
-#define PO10i_LIMIT (INT64_MAX/10)
-
-/* based on https://stackoverflow.com/a/4143288 */
-static int num_digits_i(int64_t i)
-{
-    int n;
-    int64_t po10;
-    n=1;
-    if (i < 0) {
-        i = (i == INT64_MIN) ? INT64_MAX : -i;
-        n++;
-    }
-    po10=10;
-    while (i>=po10) {
-        n++;
-        if (po10 > PO10i_LIMIT) break;
-        po10*=10;
-    }
-    return n;
-}
-
-#define PO10u_LIMIT (UINT64_MAX/10)
-
-/* based on https://stackoverflow.com/a/4143288 */
-static int num_digits_u(uint64_t i)
-{
-    int n;
-    uint64_t po10;
-    n=1;
-    po10=10;
-    while (i>=po10) {
-        n++;
-        if (po10 > PO10u_LIMIT) break;
-        po10*=10;
-    }
-    return n;
-}
-
 static plist_err_t _node_estimate_size(node_t node, uint64_t *size, uint32_t depth, hashtable_t *visited)
 {
     plist_data_t data;
diff --git a/src/plist.c b/src/plist.c
index 7def2b6..05af457 100644
--- a/src/plist.c
+++ b/src/plist.c
@@ -46,7 +46,7 @@
 #include <hashtable.h>
 #include <ptrarray.h>
 
-#define MAC_EPOCH 978307200
+#include "common.h"
 
 #ifdef _MSC_VER
 typedef SSIZE_T ssize_t;
diff --git a/src/xplist.c b/src/xplist.c
index de5227a..8f5cb15 100644
--- a/src/xplist.c
+++ b/src/xplist.c
@@ -48,6 +48,7 @@
 #include "strbuf.h"
 #include "time64.h"
 #include "hashtable.h"
+#include "common.h"
 
 #define XPLIST_KEY	"key"
 #define XPLIST_KEY_LEN 3
@@ -70,8 +71,6 @@
 #define XPLIST_DICT	"dict"
 #define XPLIST_DICT_LEN 4
 
-#define MAC_EPOCH 978307200
-
 #define MAX_DATA_BYTES_PER_LINE(__i) (((76 - ((__i) << 3)) >> 2) * 3)
 
 static const char XML_PLIST_PROLOG[] = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n\
@@ -111,30 +110,6 @@
 #endif
 }
 
-static size_t dtostr(char *buf, size_t bufsize, double realval)
-{
-    size_t len = 0;
-    if (isnan(realval)) {
-        len = snprintf(buf, bufsize, "nan");
-    } else if (isinf(realval)) {
-        len = snprintf(buf, bufsize, "%cinfinity", (realval > 0.0) ? '+' : '-');
-    } else if (realval == 0.0f) {
-        len = snprintf(buf, bufsize, "0.0");
-    } else {
-        size_t i = 0;
-        len = snprintf(buf, bufsize, "%.*g", 17, realval);
-        for (i = 0; buf && i < len; i++) {
-            if (buf[i] == ',') {
-                buf[i] = '.';
-                break;
-            } else if (buf[i] == '.') {
-                break;
-            }
-        }
-    }
-    return len;
-}
-
 static plist_err_t node_to_xml(node_t node, bytearray_t **outbuf, uint32_t depth)
 {
     plist_data_t node_data = NULL;
@@ -422,44 +397,6 @@
     return 0;
 }
 
-#define PO10i_LIMIT (INT64_MAX/10)
-
-/* based on https://stackoverflow.com/a/4143288 */
-static int num_digits_i(int64_t i)
-{
-    int n;
-    int64_t po10;
-    n=1;
-    if (i < 0) {
-        i = (i == INT64_MIN) ? INT64_MAX : -i;
-        n++;
-    }
-    po10=10;
-    while (i>=po10) {
-        n++;
-        if (po10 > PO10i_LIMIT) break;
-        po10*=10;
-    }
-    return n;
-}
-
-#define PO10u_LIMIT (UINT64_MAX/10)
-
-/* based on https://stackoverflow.com/a/4143288 */
-static int num_digits_u(uint64_t i)
-{
-    int n;
-    uint64_t po10;
-    n=1;
-    po10=10;
-    while (i>=po10) {
-        n++;
-        if (po10 > PO10u_LIMIT) break;
-        po10*=10;
-    }
-    return n;
-}
-
 static plist_err_t _node_estimate_size(node_t node, uint64_t *size, uint32_t depth, hashtable_t *visited)
 {
     plist_data_t data;