summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/Array.cpp159
-rw-r--r--src/Boolean.cpp15
-rw-r--r--src/CMakeLists.txt57
-rw-r--r--src/Data.cpp25
-rw-r--r--src/Date.cpp34
-rw-r--r--src/Dictionary.cpp128
-rw-r--r--src/Integer.cpp49
-rw-r--r--src/Key.cpp78
-rw-r--r--src/Makefile.am71
-rw-r--r--src/Node.cpp40
-rw-r--r--src/Real.cpp14
-rw-r--r--src/String.cpp40
-rw-r--r--src/Structure.cpp32
-rw-r--r--src/Uid.cpp76
-rw-r--r--src/base64.c101
-rw-r--r--src/base64.h5
-rw-r--r--src/bplist.c1684
-rw-r--r--src/bytearray.c49
-rw-r--r--src/bytearray.h11
-rw-r--r--src/common.c115
-rw-r--r--src/common.h44
-rw-r--r--src/hashtable.c48
-rw-r--r--src/hashtable.h18
-rw-r--r--src/jplist.c981
-rw-r--r--src/jsmn.c306
-rw-r--r--src/jsmn.h98
-rw-r--r--src/libplist++-2.0.pc.in11
-rw-r--r--src/libplist-2.0.pc.in10
-rw-r--r--src/oplist.c989
-rw-r--r--src/out-default.c488
-rw-r--r--src/out-limd.c469
-rw-r--r--src/out-plutil.c477
-rw-r--r--src/plist.c2317
-rw-r--r--src/plist.h62
-rw-r--r--src/ptrarray.c55
-rw-r--r--src/ptrarray.h21
-rw-r--r--src/strbuf.h35
-rw-r--r--src/time64.c804
-rw-r--r--src/time64.h92
-rw-r--r--src/time64_limits.h97
-rw-r--r--src/xplist.c1740
41 files changed, 10394 insertions, 1551 deletions
diff --git a/src/Array.cpp b/src/Array.cpp
index 419249f..784df7c 100644
--- a/src/Array.cpp
+++ b/src/Array.cpp
@@ -18,10 +18,11 @@
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
-#include <stdlib.h>
-#include <plist/Array.h>
-
+#include <cstdlib>
#include <algorithm>
+#include <climits>
+#include "plist.h"
+#include <plist/Array.h>
namespace PList
{
@@ -31,71 +32,131 @@ Array::Array(Node* parent) : Structure(PLIST_ARRAY, parent)
_array.clear();
}
+static void array_fill(Array *_this, std::vector<Node*> &array, plist_t node)
+{
+ plist_array_iter iter = NULL;
+ plist_array_new_iter(node, &iter);
+ plist_t subnode;
+ do {
+ subnode = NULL;
+ plist_array_next_item(node, iter, &subnode);
+ if (subnode) {
+ array.push_back( Node::FromPlist(subnode, _this) );
+ }
+ } while (subnode);
+ free(iter);
+}
+
Array::Array(plist_t node, Node* parent) : Structure(parent)
{
_node = node;
- uint32_t size = plist_array_get_size(_node);
-
- for (uint32_t i = 0; i < size; i++)
- {
- plist_t subnode = plist_array_get_item(_node, i);
- _array.push_back( Node::FromPlist(subnode, this) );
- }
+ array_fill(this, _array, _node);
}
-Array::Array(PList::Array& a) : Structure()
+Array::Array(const PList::Array& a) : Structure(a.GetParent())
{
_array.clear();
_node = plist_copy(a.GetPlist());
- uint32_t size = plist_array_get_size(_node);
-
- for (uint32_t i = 0; i < size; i++)
- {
- plist_t subnode = plist_array_get_item(_node, i);
- _array.push_back( Node::FromPlist(subnode, this) );
- }
+ array_fill(this, _array, _node);
}
-Array& Array::operator=(PList::Array& a)
+Array& Array::operator=(const PList::Array& a)
{
+ if (this == &a) return *this;
+
plist_free(_node);
- for (unsigned int it = 0; it < _array.size(); it++)
- {
+ for (size_t it = 0; it < _array.size(); it++) {
delete _array.at(it);
}
_array.clear();
-
_node = plist_copy(a.GetPlist());
- uint32_t size = plist_array_get_size(_node);
-
- for (uint32_t i = 0; i < size; i++)
- {
- plist_t subnode = plist_array_get_item(_node, i);
- _array.push_back( Node::FromPlist(subnode, this) );
- }
+ array_fill(this, _array, _node);
return *this;
}
Array::~Array()
{
- for (unsigned int it = 0; it < _array.size(); it++)
- {
+ for (size_t it = 0; it < _array.size(); it++) {
delete (_array.at(it));
}
_array.clear();
}
-Node* Array::Clone()
+Node* Array::Clone() const
{
return new Array(*this);
}
-Node* Array::operator[](unsigned int index)
+Node* Array::operator[](unsigned int array_index)
+{
+ return _array.at(array_index);
+}
+
+Node* Array::Back()
+{
+ return _array.back();
+}
+
+Node* Array::back()
+{
+ return _array.back();
+}
+
+Node* Array::Front()
+{
+ return _array.front();
+}
+
+Node* Array::front()
+{
+ return _array.front();
+}
+
+Array::iterator Array::Begin()
{
- return _array.at(index);
+ return _array.begin();
}
-void Array::Append(Node* node)
+Array::iterator Array::begin()
+{
+ return _array.begin();
+}
+
+Array::iterator Array::End()
+{
+ return _array.end();
+}
+
+Array::iterator Array::end()
+{
+ return _array.end();
+}
+
+Array::const_iterator Array::Begin() const
+{
+ return _array.begin();
+}
+
+Array::const_iterator Array::begin() const
+{
+ return _array.begin();
+}
+
+Array::const_iterator Array::End() const
+{
+ return _array.end();
+}
+
+Array::const_iterator Array::end() const
+{
+ return _array.end();
+}
+
+size_t Array::size() const {
+ return _array.size();
+}
+
+void Array::Append(const Node* node)
{
if (node)
{
@@ -106,7 +167,12 @@ void Array::Append(Node* node)
}
}
-void Array::Insert(Node* node, unsigned int pos)
+void Array::Append(const Node& node)
+{
+ Append(&node);
+}
+
+void Array::Insert(const Node* node, unsigned int pos)
{
if (node)
{
@@ -119,16 +185,24 @@ void Array::Insert(Node* node, unsigned int pos)
}
}
+void Array::Insert(const Node &node, unsigned int pos)
+{
+ Insert(&node, pos);
+}
+
void Array::Remove(Node* node)
{
if (node)
{
uint32_t pos = plist_array_get_item_index(node->GetPlist());
+ if (pos == UINT_MAX) {
+ return;
+ }
plist_array_remove_item(_node, pos);
std::vector<Node*>::iterator it = _array.begin();
it += pos;
_array.erase(it);
- delete node;
+ free(node);
}
}
@@ -141,10 +215,15 @@ void Array::Remove(unsigned int pos)
_array.erase(it);
}
-unsigned int Array::GetNodeIndex(Node* node)
+unsigned int Array::GetNodeIndex(const Node* node) const
{
- std::vector<Node*>::iterator it = std::find(_array.begin(), _array.end(), node);
+ std::vector<Node*>::const_iterator it = std::find(_array.begin(), _array.end(), node);
return std::distance (_array.begin(), it);
}
-};
+unsigned int Array::GetNodeIndex(const Node& node) const
+{
+ return GetNodeIndex(&node);
+}
+
+} // namespace PList
diff --git a/src/Boolean.cpp b/src/Boolean.cpp
index e58472f..2a5e303 100644
--- a/src/Boolean.cpp
+++ b/src/Boolean.cpp
@@ -18,7 +18,8 @@
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
-#include <stdlib.h>
+#include <cstdlib>
+#include "plist.h"
#include <plist/Boolean.h>
namespace PList
@@ -32,13 +33,15 @@ Boolean::Boolean(plist_t node, Node* parent) : Node(node, parent)
{
}
-Boolean::Boolean(PList::Boolean& b) : Node(PLIST_BOOLEAN)
+Boolean::Boolean(const PList::Boolean& b) : Node(PLIST_BOOLEAN)
{
plist_set_bool_val(_node, b.GetValue());
}
-Boolean& Boolean::operator=(PList::Boolean& b)
+Boolean& Boolean::operator=(const PList::Boolean& b)
{
+ if (this == &b) return *this;
+
plist_free(_node);
_node = plist_copy(b.GetPlist());
return *this;
@@ -53,7 +56,7 @@ Boolean::~Boolean()
{
}
-Node* Boolean::Clone()
+Node* Boolean::Clone() const
{
return new Boolean(*this);
}
@@ -63,11 +66,11 @@ void Boolean::SetValue(bool b)
plist_set_bool_val(_node, b);
}
-bool Boolean::GetValue()
+bool Boolean::GetValue() const
{
uint8_t b = 0;
plist_get_bool_val(_node, &b);
return b != 0 ;
}
-};
+} // namespace PList
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
deleted file mode 100644
index 0de4c8c..0000000
--- a/src/CMakeLists.txt
+++ /dev/null
@@ -1,57 +0,0 @@
-
-INCLUDE_DIRECTORIES( ${LIBXML2_INCLUDE_DIR} ${CMAKE_SOURCE_DIR}/libcnary/include )
-
-
-SET(libplist_SRC
- plist.c
- hashtable.c
- bytearray.c
- ptrarray.c
- bplist.c
- base64.c
- xplist.c )
-
-SET(libplist++_SRC
- Node.cpp
- Boolean.cpp
- Integer.cpp
- Real.cpp
- String.cpp
- Date.cpp
- Data.cpp
- Structure.cpp
- Array.cpp
- Dictionary.cpp
- )
-
-SET(libcnary_object_files
- ${CMAKE_BINARY_DIR}/libcnary/CMakeFiles/libcnary.dir/iterator.c${CMAKE_C_OUTPUT_EXTENSION}
- ${CMAKE_BINARY_DIR}/libcnary/CMakeFiles/libcnary.dir/list.c${CMAKE_C_OUTPUT_EXTENSION}
- ${CMAKE_BINARY_DIR}/libcnary/CMakeFiles/libcnary.dir/node.c${CMAKE_C_OUTPUT_EXTENSION}
- ${CMAKE_BINARY_DIR}/libcnary/CMakeFiles/libcnary.dir/node_iterator.c${CMAKE_C_OUTPUT_EXTENSION}
- ${CMAKE_BINARY_DIR}/libcnary/CMakeFiles/libcnary.dir/node_list.c${CMAKE_C_OUTPUT_EXTENSION}
-)
-
-SET_SOURCE_FILES_PROPERTIES(${libcnary_object_files} PROPERTIES EXTERNAL_OBJECT true GENERATED true)
-
-SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -DPLIST_BYTE_ORDER=${PLIST_BYTE_ORDER}")
-ADD_LIBRARY( plist SHARED ${libplist_SRC} ${libcnary_object_files} )
-TARGET_LINK_LIBRARIES( plist ${LIBXML2_LIBRARIES} )
-SET_TARGET_PROPERTIES( plist PROPERTIES VERSION ${LIBPLIST_LIBVERSION} )
-SET_TARGET_PROPERTIES( plist PROPERTIES SOVERSION ${LIBPLIST_SOVERSION} )
-if(APPLE)
- SET_TARGET_PROPERTIES( plist PROPERTIES INSTALL_NAME_DIR "${CMAKE_INSTALL_PREFIX}/lib")
-endif()
-
-ADD_LIBRARY( plist++ SHARED ${libplist++_SRC} )
-TARGET_LINK_LIBRARIES( plist++ plist )
-SET_TARGET_PROPERTIES( plist++ PROPERTIES VERSION ${LIBPLIST_LIBVERSION} )
-SET_TARGET_PROPERTIES( plist++ PROPERTIES SOVERSION ${LIBPLIST_SOVERSION} )
-if(APPLE)
- SET_TARGET_PROPERTIES( plist++ PROPERTIES INSTALL_NAME_DIR "${CMAKE_INSTALL_PREFIX}/lib")
-endif()
-
-INSTALL(TARGETS plist plist++
- RUNTIME DESTINATION bin COMPONENT lib
- LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} COMPONENT dev
- ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} COMPONENT dev)
diff --git a/src/Data.cpp b/src/Data.cpp
index df5c1c7..94767c9 100644
--- a/src/Data.cpp
+++ b/src/Data.cpp
@@ -18,7 +18,7 @@
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
-#include <stdlib.h>
+#include <cstdlib>
#include <plist/Data.h>
namespace PList
@@ -32,14 +32,16 @@ Data::Data(plist_t node, Node* parent) : Node(node, parent)
{
}
-Data::Data(PList::Data& d) : Node(PLIST_DATA)
+Data::Data(const PList::Data& d) : Node(PLIST_DATA)
{
std::vector<char> b = d.GetValue();
plist_set_data_val(_node, &b[0], b.size());
}
-Data& Data::operator=(PList::Data& b)
+Data& Data::operator=(const PList::Data& b)
{
+ if (this == &b) return *this;
+
plist_free(_node);
_node = plist_copy(b.GetPlist());
return *this;
@@ -50,11 +52,16 @@ Data::Data(const std::vector<char>& buff) : Node(PLIST_DATA)
plist_set_data_val(_node, &buff[0], buff.size());
}
+Data::Data(const char* buff, uint64_t size) : Node(PLIST_DATA)
+{
+ plist_set_data_val(_node, buff, size);
+}
+
Data::~Data()
{
}
-Node* Data::Clone()
+Node* Data::Clone() const
{
return new Data(*this);
}
@@ -64,16 +71,12 @@ void Data::SetValue(const std::vector<char>& buff)
plist_set_data_val(_node, &buff[0], buff.size());
}
-std::vector<char> Data::GetValue()
+std::vector<char> Data::GetValue() const
{
- char* buff = NULL;
uint64_t length = 0;
- plist_get_data_val(_node, &buff, &length);
+ const char* buff = plist_get_data_ptr(_node, &length);
std::vector<char> ret(buff, buff + length);
- free(buff);
return ret;
}
-
-
-};
+} // namespace PList
diff --git a/src/Date.cpp b/src/Date.cpp
index 2430184..214220b 100644
--- a/src/Date.cpp
+++ b/src/Date.cpp
@@ -18,7 +18,7 @@
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
-#include <stdlib.h>
+#include <cstdlib>
#include <plist/Date.h>
namespace PList
@@ -32,45 +32,45 @@ Date::Date(plist_t node, Node* parent) : Node(node, parent)
{
}
-Date::Date(PList::Date& d) : Node(PLIST_DATE)
+Date::Date(const PList::Date& d) : Node(PLIST_DATE)
{
- timeval t = d.GetValue();
- plist_set_date_val(_node, t.tv_sec, t.tv_usec);
+ int64_t t = d.GetValue();
+ plist_set_unix_date_val(_node, t);
}
-Date& Date::operator=(PList::Date& d)
+Date& Date::operator=(const PList::Date& d)
{
+ if (this == &d) return *this;
+
plist_free(_node);
_node = plist_copy(d.GetPlist());
return *this;
}
-Date::Date(timeval t) : Node(PLIST_DATE)
+Date::Date(int64_t t) : Node(PLIST_DATE)
{
- plist_set_date_val(_node, t.tv_sec, t.tv_usec);
+ plist_set_unix_date_val(_node, t);
}
Date::~Date()
{
}
-Node* Date::Clone()
+Node* Date::Clone() const
{
return new Date(*this);
}
-void Date::SetValue(timeval t)
+void Date::SetValue(int64_t t)
{
- plist_set_date_val(_node, t.tv_sec, t.tv_usec);
+ plist_set_unix_date_val(_node, t);
}
-timeval Date::GetValue()
+int64_t Date::GetValue() const
{
- int32_t tv_sec = 0;
- int32_t tv_usec = 0;
- plist_get_date_val(_node, &tv_sec, &tv_usec);
- timeval t = {tv_sec, tv_usec};
- return t;
+ int64_t sec = 0;
+ plist_get_unix_date_val(_node, &sec);
+ return sec;
}
-};
+} // namespace PList
diff --git a/src/Dictionary.cpp b/src/Dictionary.cpp
index 0030df6..b354945 100644
--- a/src/Dictionary.cpp
+++ b/src/Dictionary.cpp
@@ -18,7 +18,8 @@
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
-#include <stdlib.h>
+#include <cstdlib>
+#include "plist.h"
#include <plist/Dictionary.h>
namespace PList
@@ -28,28 +29,29 @@ Dictionary::Dictionary(Node* parent) : Structure(PLIST_DICT, parent)
{
}
-Dictionary::Dictionary(plist_t node, Node* parent) : Structure(parent)
+static void dictionary_fill(Dictionary *_this, std::map<std::string,Node*> &map, plist_t node)
{
- _node = node;
plist_dict_iter it = NULL;
-
- char* key = NULL;
+ plist_dict_new_iter(node, &it);
plist_t subnode = NULL;
- plist_dict_new_iter(_node, &it);
- plist_dict_next_item(_node, it, &key, &subnode);
- while (subnode)
- {
- _map[std::string(key)] = Node::FromPlist(subnode, this);
-
+ do {
+ char *key = NULL;
subnode = NULL;
+ plist_dict_next_item(node, it, &key, &subnode);
+ if (key && subnode)
+ map[std::string(key)] = Node::FromPlist(subnode, _this);
free(key);
- key = NULL;
- plist_dict_next_item(_node, it, &key, &subnode);
- }
+ } while (subnode);
free(it);
}
-Dictionary::Dictionary(PList::Dictionary& d) : Structure()
+Dictionary::Dictionary(plist_t node, Node* parent) : Structure(parent)
+{
+ _node = node;
+ dictionary_fill(this, _map, _node);
+}
+
+Dictionary::Dictionary(const PList::Dictionary& d) : Structure(d.GetParent())
{
for (Dictionary::iterator it = _map.begin(); it != _map.end(); it++)
{
@@ -57,52 +59,22 @@ Dictionary::Dictionary(PList::Dictionary& d) : Structure()
delete it->second;
}
_map.clear();
-
_node = plist_copy(d.GetPlist());
- plist_dict_iter it = NULL;
-
- char* key = NULL;
- plist_t subnode = NULL;
- plist_dict_new_iter(_node, &it);
- plist_dict_next_item(_node, it, &key, &subnode);
- while (subnode)
- {
- _map[std::string(key)] = Node::FromPlist(subnode, this);
-
- subnode = NULL;
- free(key);
- key = NULL;
- plist_dict_next_item(_node, it, &key, &subnode);
- }
- free(it);
+ dictionary_fill(this, _map, _node);
}
-Dictionary& Dictionary::operator=(PList::Dictionary& d)
+Dictionary& Dictionary::operator=(const PList::Dictionary& d)
{
+ if (this == &d) return *this;
+
for (Dictionary::iterator it = _map.begin(); it != _map.end(); it++)
{
plist_free(it->second->GetPlist());
delete it->second;
}
_map.clear();
-
_node = plist_copy(d.GetPlist());
- plist_dict_iter it = NULL;
-
- char* key = NULL;
- plist_t subnode = NULL;
- plist_dict_new_iter(_node, &it);
- plist_dict_next_item(_node, it, &key, &subnode);
- while (subnode)
- {
- _map[std::string(key)] = Node::FromPlist(subnode, this);
-
- subnode = NULL;
- free(key);
- key = NULL;
- plist_dict_next_item(_node, it, NULL, &subnode);
- }
- free(it);
+ dictionary_fill(this, _map, _node);
return *this;
}
@@ -115,7 +87,7 @@ Dictionary::~Dictionary()
_map.clear();
}
-Node* Dictionary::Clone()
+Node* Dictionary::Clone() const
{
return new Dictionary(*this);
}
@@ -130,28 +102,72 @@ Dictionary::iterator Dictionary::Begin()
return _map.begin();
}
+Dictionary::iterator Dictionary::begin()
+{
+ return _map.begin();
+}
+
Dictionary::iterator Dictionary::End()
{
return _map.end();
}
+Dictionary::iterator Dictionary::end()
+{
+ return _map.end();
+}
+
+Dictionary::const_iterator Dictionary::Begin() const
+{
+ return _map.begin();
+}
+
+Dictionary::const_iterator Dictionary::begin() const
+{
+ return _map.begin();
+}
+
+Dictionary::const_iterator Dictionary::End() const
+{
+ return _map.end();
+}
+
+Dictionary::const_iterator Dictionary::end() const
+{
+ return _map.end();
+}
+
+size_t Dictionary::size() const {
+ return _map.size();
+}
+
Dictionary::iterator Dictionary::Find(const std::string& key)
{
return _map.find(key);
}
-Dictionary::iterator Dictionary::Insert(const std::string& key, Node* node)
+Dictionary::const_iterator Dictionary::Find(const std::string& key) const
+{
+ return _map.find(key);
+}
+
+Dictionary::iterator Dictionary::Set(const std::string& key, const Node* node)
{
if (node)
{
Node* clone = node->Clone();
UpdateNodeParent(clone);
- plist_dict_insert_item(_node, key.c_str(), clone->GetPlist());
+ plist_dict_set_item(_node, key.c_str(), clone->GetPlist());
delete _map[key];
_map[key] = clone;
return _map.find(key);
}
- return iterator(NULL);
+ return iterator(this->_map.end());
+}
+
+Dictionary::iterator Dictionary::Set(const std::string& key, const Node& node)
+{
+ return Set(key, &node);
}
void Dictionary::Remove(Node* node)
@@ -164,7 +180,7 @@ void Dictionary::Remove(Node* node)
std::string skey = key;
free(key);
_map.erase(skey);
- delete node;
+ free(node);
}
}
@@ -185,4 +201,4 @@ std::string Dictionary::GetNodeKey(Node* node)
return "";
}
-};
+} // namespace PList
diff --git a/src/Integer.cpp b/src/Integer.cpp
index fed03f6..26e7ccf 100644
--- a/src/Integer.cpp
+++ b/src/Integer.cpp
@@ -18,13 +18,14 @@
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
-#include <stdlib.h>
+#include <cstdlib>
+#include "plist.h"
#include <plist/Integer.h>
namespace PList
{
-Integer::Integer(Node* parent) : Node(PLIST_UINT, parent)
+Integer::Integer(Node* parent) : Node(PLIST_INT, parent)
{
}
@@ -32,42 +33,72 @@ Integer::Integer(plist_t node, Node* parent) : Node(node, parent)
{
}
-Integer::Integer(PList::Integer& i) : Node(PLIST_UINT)
+Integer::Integer(const PList::Integer& i) : Node(PLIST_INT)
{
- plist_set_uint_val(_node, i.GetValue());
+ plist_free(_node);
+ _node = plist_copy(i.GetPlist());
}
-Integer& Integer::operator=(PList::Integer& i)
+Integer& Integer::operator=(const PList::Integer& i)
{
+ if (this == &i) return *this;
+
plist_free(_node);
_node = plist_copy(i.GetPlist());
return *this;
}
-Integer::Integer(uint64_t i) : Node(PLIST_UINT)
+Integer::Integer(uint64_t i) : Node(PLIST_INT)
{
plist_set_uint_val(_node, i);
}
+Integer::Integer(int64_t i) : Node(PLIST_INT)
+{
+ plist_set_int_val(_node, i);
+}
+
Integer::~Integer()
{
}
-Node* Integer::Clone()
+Node* Integer::Clone() const
{
return new Integer(*this);
}
+void Integer::SetValue(int64_t i)
+{
+ plist_set_int_val(_node, i);
+}
+
void Integer::SetValue(uint64_t i)
{
plist_set_uint_val(_node, i);
}
-uint64_t Integer::GetValue()
+void Integer::SetUnsignedValue(uint64_t i)
+{
+ plist_set_uint_val(_node, i);
+}
+
+int64_t Integer::GetValue() const
+{
+ int64_t i = 0;
+ plist_get_int_val(_node, &i);
+ return i;
+}
+
+uint64_t Integer::GetUnsignedValue() const
{
uint64_t i = 0;
plist_get_uint_val(_node, &i);
return i;
}
-};
+bool Integer::isNegative() const
+{
+ return plist_int_val_is_negative(_node);
+}
+
+} // namespace PList
diff --git a/src/Key.cpp b/src/Key.cpp
new file mode 100644
index 0000000..459227a
--- /dev/null
+++ b/src/Key.cpp
@@ -0,0 +1,78 @@
+/*
+ * Key.cpp
+ *
+ * Copyright (c) 2012 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 <cstdlib>
+#include "plist.h"
+#include <plist/Key.h>
+
+namespace PList
+{
+
+Key::Key(Node* parent) : Node(PLIST_KEY, parent)
+{
+}
+
+Key::Key(plist_t node, Node* parent) : Node(node, parent)
+{
+}
+
+Key::Key(const PList::Key& k) : Node(PLIST_INT)
+{
+ plist_set_key_val(_node, k.GetValue().c_str());
+}
+
+Key& Key::operator=(const PList::Key& k)
+{
+ if (this == &k) return *this;
+
+ plist_free(_node);
+ _node = plist_copy(k.GetPlist());
+ return *this;
+}
+
+Key::Key(const std::string& s) : Node(PLIST_STRING)
+{
+ plist_set_key_val(_node, s.c_str());
+}
+
+Key::~Key()
+{
+}
+
+Node* Key::Clone() const
+{
+ return new Key(*this);
+}
+
+void Key::SetValue(const std::string& s)
+{
+ plist_set_key_val(_node, s.c_str());
+}
+
+std::string Key::GetValue() const
+{
+ char* s = NULL;
+ plist_get_key_val(_node, &s);
+ std::string ret = s ? s : "";
+ free(s);
+ return ret;
+}
+
+} // namespace PList
diff --git a/src/Makefile.am b/src/Makefile.am
new file mode 100644
index 0000000..ce4f931
--- /dev/null
+++ b/src/Makefile.am
@@ -0,0 +1,71 @@
+AM_CPPFLAGS = \
+ -I$(top_srcdir)/include \
+ -I$(top_srcdir) \
+ -I$(top_srcdir)/libcnary/include
+
+AM_CFLAGS = $(GLOBAL_CFLAGS)
+AM_CXXFLAGS = $(GLOBAL_CXXFLAGS)
+AM_LDFLAGS = $(GLOBAL_LDFLAGS)
+
+lib_LTLIBRARIES = \
+ libplist-2.0.la \
+ libplist++-2.0.la
+
+libplist_2_0_la_LIBADD = $(top_builddir)/libcnary/libcnary.la
+libplist_2_0_la_LDFLAGS = $(AM_LDFLAGS) -version-info $(LIBPLIST_SO_VERSION) -no-undefined
+libplist_2_0_la_SOURCES = \
+ base64.c base64.h \
+ bytearray.c bytearray.h \
+ strbuf.h \
+ hashtable.c hashtable.h \
+ ptrarray.c ptrarray.h \
+ time64.c time64.h \
+ time64_limits.h \
+ common.c common.h \
+ xplist.c \
+ bplist.c \
+ jsmn.c jsmn.h \
+ jplist.c \
+ oplist.c \
+ out-default.c \
+ out-plutil.c \
+ out-limd.c \
+ plist.c plist.h
+
+libplist___2_0_la_LIBADD = libplist-2.0.la
+libplist___2_0_la_LDFLAGS = $(AM_LDFLAGS) -version-info $(LIBPLIST_SO_VERSION) -no-undefined
+libplist___2_0_la_SOURCES = \
+ Node.cpp \
+ Structure.cpp \
+ Array.cpp \
+ Boolean.cpp \
+ Data.cpp \
+ Date.cpp \
+ Dictionary.cpp \
+ Integer.cpp \
+ Key.cpp \
+ Real.cpp \
+ String.cpp \
+ Uid.cpp \
+ $(top_srcdir)/include/plist/Node.h \
+ $(top_srcdir)/include/plist/Structure.h \
+ $(top_srcdir)/include/plist/Array.h \
+ $(top_srcdir)/include/plist/Boolean.h \
+ $(top_srcdir)/include/plist/Data.h \
+ $(top_srcdir)/include/plist/Date.h \
+ $(top_srcdir)/include/plist/Dictionary.h \
+ $(top_srcdir)/include/plist/Integer.h \
+ $(top_srcdir)/include/plist/Key.h \
+ $(top_srcdir)/include/plist/Real.h \
+ $(top_srcdir)/include/plist/String.h \
+ $(top_srcdir)/include/plist/Uid.h
+
+if WIN32
+libplist_2_0_la_LDFLAGS += -avoid-version -static-libgcc
+libplist___2_0_la_LDFLAGS += -avoid-version -static-libgcc
+endif
+
+pkgconfigdir = $(libdir)/pkgconfig
+pkgconfig_DATA = \
+ libplist-2.0.pc \
+ libplist++-2.0.pc
diff --git a/src/Node.cpp b/src/Node.cpp
index 9bf50ee..1043b31 100644
--- a/src/Node.cpp
+++ b/src/Node.cpp
@@ -18,7 +18,8 @@
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
-#include <stdlib.h>
+#include <cstdlib>
+#include "plist.h"
#include <plist/Node.h>
#include <plist/Structure.h>
#include <plist/Dictionary.h>
@@ -27,6 +28,8 @@
#include <plist/Integer.h>
#include <plist/Real.h>
#include <plist/String.h>
+#include <plist/Key.h>
+#include <plist/Uid.h>
#include <plist/Data.h>
#include <plist/Date.h>
@@ -50,7 +53,7 @@ Node::Node(plist_type type, Node* parent) : _parent(parent)
case PLIST_BOOLEAN:
_node = plist_new_bool(0);
break;
- case PLIST_UINT:
+ case PLIST_INT:
_node = plist_new_uint(0);
break;
case PLIST_REAL:
@@ -59,11 +62,18 @@ Node::Node(plist_type type, Node* parent) : _parent(parent)
case PLIST_STRING:
_node = plist_new_string("");
break;
+ case PLIST_KEY:
+ _node = plist_new_string("");
+ plist_set_key_val(_node, "");
+ break;
+ case PLIST_UID:
+ _node = plist_new_uid(0);
+ break;
case PLIST_DATA:
_node = plist_new_data(NULL,0);
break;
case PLIST_DATE:
- _node = plist_new_date(0,0);
+ _node = plist_new_unix_date(0);
break;
case PLIST_ARRAY:
_node = plist_new_array();
@@ -71,7 +81,6 @@ Node::Node(plist_type type, Node* parent) : _parent(parent)
case PLIST_DICT:
_node = plist_new_dict();
break;
- case PLIST_KEY:
case PLIST_NONE:
default:
break;
@@ -80,12 +89,17 @@ Node::Node(plist_type type, Node* parent) : _parent(parent)
Node::~Node()
{
- plist_free(_node);
+ /* If the Node is in a container, let _node be cleaned up by
+ * operations on the parent plist_t. Otherwise, duplicate frees
+ * occur when a Node is removed from or replaced in a Dictionary.
+ */
+ if (_parent == NULL)
+ plist_free(_node);
_node = NULL;
_parent = NULL;
}
-plist_type Node::GetType()
+plist_type Node::GetType() const
{
if (_node)
{
@@ -94,12 +108,12 @@ plist_type Node::GetType()
return PLIST_NONE;
}
-plist_t Node::GetPlist()
+plist_t Node::GetPlist() const
{
return _node;
}
-Node* Node::GetParent()
+Node* Node::GetParent() const
{
return _parent;
}
@@ -121,7 +135,7 @@ Node* Node::FromPlist(plist_t node, Node* parent)
case PLIST_BOOLEAN:
ret = new Boolean(node, parent);
break;
- case PLIST_UINT:
+ case PLIST_INT:
ret = new Integer(node, parent);
break;
case PLIST_REAL:
@@ -130,6 +144,12 @@ Node* Node::FromPlist(plist_t node, Node* parent)
case PLIST_STRING:
ret = new String(node, parent);
break;
+ case PLIST_KEY:
+ ret = new Key(node, parent);
+ break;
+ case PLIST_UID:
+ ret = new Uid(node, parent);
+ break;
case PLIST_DATE:
ret = new Date(node, parent);
break;
@@ -144,4 +164,4 @@ Node* Node::FromPlist(plist_t node, Node* parent)
return ret;
}
-};
+} // namespace PList
diff --git a/src/Real.cpp b/src/Real.cpp
index 768d07c..b743ab5 100644
--- a/src/Real.cpp
+++ b/src/Real.cpp
@@ -18,7 +18,7 @@
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
-#include <stdlib.h>
+#include <cstdlib>
#include <plist/Real.h>
namespace PList
@@ -32,13 +32,15 @@ Real::Real(plist_t node, Node* parent) : Node(node, parent)
{
}
-Real::Real(PList::Real& d) : Node(PLIST_UINT)
+Real::Real(const PList::Real& d) : Node(PLIST_INT)
{
plist_set_real_val(_node, d.GetValue());
}
-Real& Real::operator=(PList::Real& d)
+Real& Real::operator=(const PList::Real& d)
{
+ if (this == &d) return *this;
+
plist_free(_node);
_node = plist_copy(d.GetPlist());
return *this;
@@ -53,7 +55,7 @@ Real::~Real()
{
}
-Node* Real::Clone()
+Node* Real::Clone() const
{
return new Real(*this);
}
@@ -63,11 +65,11 @@ void Real::SetValue(double d)
plist_set_real_val(_node, d);
}
-double Real::GetValue()
+double Real::GetValue() const
{
double d = 0.;
plist_get_real_val(_node, &d);
return d;
}
-};
+} // namespace PList
diff --git a/src/String.cpp b/src/String.cpp
index bf65605..bcd5e2e 100644
--- a/src/String.cpp
+++ b/src/String.cpp
@@ -18,7 +18,8 @@
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
-#include <stdlib.h>
+#include <cstdlib>
+#include "plist.h"
#include <plist/String.h>
namespace PList
@@ -32,28 +33,49 @@ String::String(plist_t node, Node* parent) : Node(node, parent)
{
}
-String::String(PList::String& s) : Node(PLIST_UINT)
+String::String(const PList::String& s) : Node(PLIST_INT)
{
plist_set_string_val(_node, s.GetValue().c_str());
}
-String& String::operator=(PList::String& s)
+String& String::operator=(const PList::String& s)
{
+ if (this == &s) return *this;
+
plist_free(_node);
_node = plist_copy(s.GetPlist());
return *this;
}
+String& String::operator=(const std::string& s)
+{
+ plist_free(_node);
+ _node = plist_new_string(s.c_str());
+ return *this;
+}
+
+String& String::operator=(const char* s)
+{
+ plist_free(_node);
+ _node = plist_new_string(s);
+ return *this;
+}
+
String::String(const std::string& s) : Node(PLIST_STRING)
{
plist_set_string_val(_node, s.c_str());
}
+String::String(const char *s) : Node(PLIST_STRING)
+{
+ plist_set_string_val(_node, s);
+}
+
String::~String()
{
}
-Node* String::Clone()
+Node* String::Clone() const
{
return new String(*this);
}
@@ -63,13 +85,11 @@ void String::SetValue(const std::string& s)
plist_set_string_val(_node, s.c_str());
}
-std::string String::GetValue()
+std::string String::GetValue() const
{
- char* s = NULL;
- plist_get_string_val(_node, &s);
- std::string ret = s;
- free(s);
+ const char* s = plist_get_string_ptr(_node, NULL);
+ std::string ret = s ? s : "";
return ret;
}
-};
+} // namespace PList
diff --git a/src/Structure.cpp b/src/Structure.cpp
index 18b19ef..65e5ca8 100644
--- a/src/Structure.cpp
+++ b/src/Structure.cpp
@@ -18,7 +18,8 @@
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
-#include <stdlib.h>
+#include <cstdlib>
+#include "plist.h"
#include <plist/Structure.h>
namespace PList
@@ -35,7 +36,7 @@ Structure::~Structure()
{
}
-uint32_t Structure::GetSize()
+uint32_t Structure::GetSize() const
{
uint32_t size = 0;
plist_type type = plist_get_node_type(_node);
@@ -50,7 +51,7 @@ uint32_t Structure::GetSize()
return size;
}
-std::string Structure::ToXml()
+std::string Structure::ToXml() const
{
char* xml = NULL;
uint32_t length = 0;
@@ -60,7 +61,7 @@ std::string Structure::ToXml()
return ret;
}
-std::vector<char> Structure::ToBin()
+std::vector<char> Structure::ToBin() const
{
char* bin = NULL;
uint32_t length = 0;
@@ -76,7 +77,7 @@ void Structure::UpdateNodeParent(Node* node)
if ( NULL != node->_parent )
{
plist_type type = plist_get_node_type(node->_parent);
- if (PLIST_ARRAY ==type || PLIST_DICT == type )
+ if (PLIST_ARRAY == type || PLIST_DICT == type)
{
Structure* s = static_cast<Structure*>(node->_parent);
s->Remove(node);
@@ -116,8 +117,27 @@ Structure* Structure::FromBin(const std::vector<char>& bin)
plist_from_bin(&bin[0], bin.size(), &root);
return ImportStruct(root);
+}
+
+Structure* Structure::FromBin(const char* bin, uint64_t size)
+{
+ plist_t root = NULL;
+ plist_from_bin(bin, size, &root);
+
+ return ImportStruct(root);
+}
+Structure* Structure::FromMemory(const std::vector<char>& buf, plist_format_t *format)
+{
+ return Structure::FromMemory(&buf[0], buf.size(), format);
+}
+
+Structure* Structure::FromMemory(const char* buf, uint64_t size, plist_format_t *format)
+{
+ plist_t root = NULL;
+ plist_from_memory(buf, size, &root, format);
+ return ImportStruct(root);
}
-};
+} // namespace PList
diff --git a/src/Uid.cpp b/src/Uid.cpp
new file mode 100644
index 0000000..d73f777
--- /dev/null
+++ b/src/Uid.cpp
@@ -0,0 +1,76 @@
+/*
+ * Uid.cpp
+ *
+ * Copyright (c) 2012 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 <cstdlib>
+#include "plist.h"
+#include <plist/Uid.h>
+
+namespace PList
+{
+
+Uid::Uid(Node* parent) : Node(PLIST_UID, parent)
+{
+}
+
+Uid::Uid(plist_t node, Node* parent) : Node(node, parent)
+{
+}
+
+Uid::Uid(const PList::Uid& i) : Node(PLIST_UID)
+{
+ plist_set_uid_val(_node, i.GetValue());
+}
+
+Uid& Uid::operator=(const PList::Uid& i)
+{
+ if (this == &i) return *this;
+
+ plist_free(_node);
+ _node = plist_copy(i.GetPlist());
+ return *this;
+}
+
+Uid::Uid(uint64_t i) : Node(PLIST_UID)
+{
+ plist_set_uid_val(_node, i);
+}
+
+Uid::~Uid()
+{
+}
+
+Node* Uid::Clone() const
+{
+ return new Uid(*this);
+}
+
+void Uid::SetValue(uint64_t i)
+{
+ plist_set_uid_val(_node, i);
+}
+
+uint64_t Uid::GetValue() const
+{
+ uint64_t i = 0;
+ plist_get_uid_val(_node, &i);
+ return i;
+}
+
+} // namespace PList
diff --git a/src/base64.c b/src/base64.c
index e558d9e..603ab6d 100644
--- a/src/base64.c
+++ b/src/base64.c
@@ -28,7 +28,7 @@ static const signed char base64_table[256] = {
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1, -1, 63,
- 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -1, -1, -1,
+ 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -2, -1, -1,
-1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1,
-1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
@@ -43,82 +43,77 @@ static const signed char base64_table[256] = {
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1
};
-char *base64encode(const unsigned char *buf, size_t *size)
+size_t base64encode(char *outbuf, const unsigned char *buf, size_t size)
{
- if (!buf || !size || !(*size > 0)) return NULL;
- int outlen = (*size / 3) * 4;
- char *outbuf = (char*)malloc(outlen+5); // 4 spare bytes + 1 for '\0'
+ if (!outbuf || !buf || (size <= 0)) {
+ return 0;
+ }
+
size_t n = 0;
size_t m = 0;
unsigned char input[3];
unsigned int output[4];
- while (n < *size) {
+ while (n < size) {
input[0] = buf[n];
- input[1] = (n+1 < *size) ? buf[n+1] : 0;
- input[2] = (n+2 < *size) ? buf[n+2] : 0;
+ input[1] = (n+1 < size) ? buf[n+1] : 0;
+ input[2] = (n+2 < size) ? buf[n+2] : 0;
output[0] = input[0] >> 2;
output[1] = ((input[0] & 3) << 4) + (input[1] >> 4);
output[2] = ((input[1] & 15) << 2) + (input[2] >> 6);
output[3] = input[2] & 63;
outbuf[m++] = base64_str[(int)output[0]];
outbuf[m++] = base64_str[(int)output[1]];
- outbuf[m++] = (n+1 < *size) ? base64_str[(int)output[2]] : base64_pad;
- outbuf[m++] = (n+2 < *size) ? base64_str[(int)output[3]] : base64_pad;
+ outbuf[m++] = (n+1 < size) ? base64_str[(int)output[2]] : base64_pad;
+ outbuf[m++] = (n+2 < size) ? base64_str[(int)output[3]] : base64_pad;
n+=3;
}
outbuf[m] = 0; // 0-termination!
- *size = m;
- return outbuf;
-}
-
-static int base64decode_block(unsigned char *target, const char *data, size_t data_size)
-{
- int w1,w2,w3,w4;
- int n,i;
-
- if (!data || (data_size <= 0)) {
- return 0;
- }
-
- n = 0;
- i = 0;
- while (n < data_size-3) {
- w1 = base64_table[(int)data[n]];
- w2 = base64_table[(int)data[n+1]];
- w3 = base64_table[(int)data[n+2]];
- w4 = base64_table[(int)data[n+3]];
-
- if (w2 >= 0) {
- target[i++] = (char)((w1*4 + (w2 >> 4)) & 255);
- }
- if (w3 >= 0) {
- target[i++] = (char)((w2*16 + (w3 >> 2)) & 255);
- }
- if (w4 >= 0) {
- target[i++] = (char)((w3*64 + w4) & 255);
- }
- n+=4;
- }
- return i;
+ return m;
}
unsigned char *base64decode(const char *buf, size_t *size)
{
- if (!buf) return NULL;
- size_t len = strlen(buf);
+ if (!buf || !size) return NULL;
+ size_t len = (*size > 0) ? *size : strlen(buf);
if (len <= 0) return NULL;
unsigned char *outbuf = (unsigned char*)malloc((len/4)*3+3);
+ if (!outbuf) return NULL;
+ const char *ptr = buf;
+ size_t p = 0;
+ int wv, w1, w2, w3, w4;
+ int tmpval[4];
+ int tmpcnt = 0;
- unsigned char *line;
- int p = 0;
+ do {
+ while (ptr < buf+len && (*ptr == ' ' || *ptr == '\t' || *ptr == '\n' || *ptr == '\r')) {
+ ptr++;
+ }
+ if (*ptr == '\0' || ptr >= buf+len) {
+ break;
+ }
+ if ((wv = base64_table[(int)(unsigned char)*ptr++]) == -1) {
+ continue;
+ }
+ tmpval[tmpcnt++] = wv;
+ if (tmpcnt == 4) {
+ tmpcnt = 0;
+ w1 = tmpval[0];
+ w2 = tmpval[1];
+ w3 = tmpval[2];
+ w4 = tmpval[3];
- line = (unsigned char*)strtok((char*)buf, "\r\n\t ");
- while (line) {
- p+=base64decode_block(outbuf+p, (const char*)line, strlen((char*)line));
+ if (w1 >= 0 && w2 >= 0) {
+ outbuf[p++] = (unsigned char)(((w1 << 2) + (w2 >> 4)) & 0xFF);
+ }
+ if (w2 >= 0 && w3 >= 0) {
+ outbuf[p++] = (unsigned char)(((w2 << 4) + (w3 >> 2)) & 0xFF);
+ }
+ if (w3 >= 0 && w4 >= 0) {
+ outbuf[p++] = (unsigned char)(((w3 << 6) + w4) & 0xFF);
+ }
+ }
+ } while (1);
- // get next line of base64 encoded block
- line = (unsigned char*)strtok(NULL, "\r\n\t ");
- }
outbuf[p] = 0;
*size = p;
return outbuf;
diff --git a/src/base64.h b/src/base64.h
index dbaf104..58b8396 100644
--- a/src/base64.h
+++ b/src/base64.h
@@ -21,9 +21,8 @@
#ifndef BASE64_H
#define BASE64_H
#include <stdlib.h>
-#include "common.h"
-_PLIST_INTERNAL char *base64encode(const unsigned char *buf, size_t *size);
-_PLIST_INTERNAL unsigned char *base64decode(const char *buf, size_t *size);
+size_t base64encode(char *outbuf, const unsigned char *buf, size_t size);
+unsigned char *base64decode(const char *buf, size_t *size);
#endif
diff --git a/src/bplist.c b/src/bplist.c
index eff44fc..7b08532 100644
--- a/src/bplist.c
+++ b/src/bplist.c
@@ -1,8 +1,9 @@
/*
- * plist.c
+ * bplist.c
* Binary plist implementation
*
- * Copyright (c) 2008 Jonathan Beck All Rights Reserved.
+ * Copyright (c) 2011-2017 Nikias Bassen, All Rights Reserved.
+ * Copyright (c) 2008-2010 Jonathan Beck, 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
@@ -19,37 +20,42 @@
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
-#include <libxml/encoding.h>
#include <ctype.h>
+#include <inttypes.h>
-#include <plist/plist.h>
#include "plist.h"
#include "hashtable.h"
#include "bytearray.h"
#include "ptrarray.h"
+#include "plist/plist.h"
#include <node.h>
-#include <node_iterator.h>
/* Magic marker and size. */
-#define BPLIST_MAGIC ((uint8_t*)"bplist")
-#define BPLIST_MAGIC_SIZE 6
-
-#define BPLIST_VERSION ((uint8_t*)"00")
-#define BPLIST_VERSION_SIZE 2
-
-
-#define BPLIST_TRL_SIZE 26
-#define BPLIST_TRL_OFFSIZE_IDX 0
-#define BPLIST_TRL_PARMSIZE_IDX 1
-#define BPLIST_TRL_NUMOBJ_IDX 2
-#define BPLIST_TRL_ROOTOBJ_IDX 10
-#define BPLIST_TRL_OFFTAB_IDX 18
+#define BPLIST_MAGIC ((uint8_t*)"bplist")
+#define BPLIST_MAGIC_SIZE 6
+
+#define BPLIST_VERSION ((uint8_t*)"00")
+#define BPLIST_VERSION_SIZE 2
+
+#pragma pack(push,1)
+typedef struct {
+ uint8_t unused[6];
+ uint8_t offset_size;
+ uint8_t ref_size;
+ uint64_t num_objects;
+ uint64_t root_object_index;
+ uint64_t offset_table_offset;
+} bplist_trailer_t;
+#pragma pack(pop)
enum
{
@@ -57,44 +63,51 @@ enum
BPLIST_FALSE = 0x08,
BPLIST_TRUE = 0x09,
BPLIST_FILL = 0x0F, /* will be used for length grabbing */
- BPLIST_UINT = 0x10,
+ BPLIST_INT = 0x10,
BPLIST_REAL = 0x20,
BPLIST_DATE = 0x30,
BPLIST_DATA = 0x40,
BPLIST_STRING = 0x50,
BPLIST_UNICODE = 0x60,
- BPLIST_UID = 0x70,
+ BPLIST_UNK_0x70 = 0x70,
+ BPLIST_UID = 0x80,
BPLIST_ARRAY = 0xA0,
BPLIST_SET = 0xC0,
BPLIST_DICT = 0xD0,
BPLIST_MASK = 0xF0
};
-static void float_byte_convert(uint8_t * address, size_t size)
-{
-#if PLIST_BYTE_ORDER == PLIST_LITTLE_ENDIAN && !defined (__VFP_FP__)
- uint8_t i = 0, j = 0;
- uint8_t tmp = 0;
-
- for (i = 0; i < (size / 2); i++)
- {
- tmp = address[i];
- j = ((size - 1) + 0) - i;
- address[i] = address[j];
- address[j] = tmp;
- }
-#endif
-}
-
union plist_uint_ptr
{
- void *src;
+ const void *src;
uint8_t *u8ptr;
uint16_t *u16ptr;
uint32_t *u32ptr;
uint64_t *u64ptr;
};
+#ifdef _MSC_VER
+uint64_t get_unaligned_64(uint64_t *ptr)
+{
+ uint64_t temp;
+ memcpy(&temp, ptr, sizeof(temp));
+ return temp;
+}
+
+uint32_t get_unaligned_32(uint32_t *ptr)
+{
+ uint32_t temp;
+ memcpy(&temp, ptr, sizeof(temp));
+ return temp;
+}
+
+uint16_t get_unaligned_16(uint16_t *ptr)
+{
+ uint16_t temp;
+ memcpy(&temp, ptr, sizeof(temp));
+ return temp;
+}
+#else
#define get_unaligned(ptr) \
({ \
struct __attribute__((packed)) { \
@@ -102,102 +115,173 @@ union plist_uint_ptr
} *__p = (void *) (ptr); \
__p->__v; \
})
-
-
-static void byte_convert(uint8_t * address, size_t size)
-{
-#if PLIST_BYTE_ORDER == PLIST_LITTLE_ENDIAN
- uint8_t i = 0, j = 0;
- uint8_t tmp = 0;
-
- for (i = 0; i < (size / 2); i++)
- {
- tmp = address[i];
- j = ((size - 1) + 0) - i;
- address[i] = address[j];
- address[j] = tmp;
- }
#endif
-}
-static uint32_t uint24_from_be(union plist_uint_ptr buf)
-{
- union plist_uint_ptr tmp;
- uint32_t ret = 0;
- tmp.src = &ret;
+#ifndef bswap16
+#define bswap16(x) ((((x) & 0xFF00) >> 8) | (((x) & 0x00FF) << 8))
+#endif
- memcpy(tmp.u8ptr + 1, buf.u8ptr, 3 * sizeof(char));
+#ifndef bswap32
+#define bswap32(x) ((((x) & 0xFF000000) >> 24) \
+ | (((x) & 0x00FF0000) >> 8) \
+ | (((x) & 0x0000FF00) << 8) \
+ | (((x) & 0x000000FF) << 24))
+#endif
- byte_convert(tmp.u8ptr, sizeof(uint32_t));
- return ret;
-}
+#ifndef bswap64
+#define bswap64(x) ((((x) & 0xFF00000000000000ull) >> 56) \
+ | (((x) & 0x00FF000000000000ull) >> 40) \
+ | (((x) & 0x0000FF0000000000ull) >> 24) \
+ | (((x) & 0x000000FF00000000ull) >> 8) \
+ | (((x) & 0x00000000FF000000ull) << 8) \
+ | (((x) & 0x0000000000FF0000ull) << 24) \
+ | (((x) & 0x000000000000FF00ull) << 40) \
+ | (((x) & 0x00000000000000FFull) << 56))
+#endif
#ifndef be16toh
-#if PLIST_BYTE_ORDER == PLIST_BIG_ENDIAN
+#ifdef __BIG_ENDIAN__
#define be16toh(x) (x)
#else
-#define be16toh(x) ((((x) & 0xFF00) >> 8) | (((x) & 0x00FF) << 8))
+#define be16toh(x) bswap16(x)
#endif
#endif
#ifndef be32toh
-#if PLIST_BYTE_ORDER == PLIST_BIG_ENDIAN
+#ifdef __BIG_ENDIAN__
#define be32toh(x) (x)
#else
-#define be32toh(x) ((((x) & 0xFF000000) >> 24) \
- | (((x) & 0x00FF0000) >> 8) \
- | (((x) & 0x0000FF00) << 8) \
- | (((x) & 0x000000FF) << 24))
+#define be32toh(x) bswap32(x)
#endif
#endif
#ifndef be64toh
-#if PLIST_BYTE_ORDER == PLIST_BIG_ENDIAN
+#ifdef __BIG_ENDIAN__
#define be64toh(x) (x)
#else
-#define be64toh(x) ((((x) & 0xFF00000000000000ull) >> 56) \
- | (((x) & 0x00FF000000000000ull) >> 40) \
- | (((x) & 0x0000FF0000000000ull) >> 24) \
- | (((x) & 0x000000FF00000000ull) >> 8) \
- | (((x) & 0x00000000FF000000ull) << 8) \
- | (((x) & 0x0000000000FF0000ull) << 24) \
- | (((x) & 0x000000000000FF00ull) << 40) \
- | (((x) & 0x00000000000000FFull) << 56))
+#define be64toh(x) bswap64(x)
#endif
#endif
-#define UINT_TO_HOST(x, n) \
- ({ \
- union plist_uint_ptr __up; \
- __up.src = x; \
- (n == 8 ? be64toh( get_unaligned(__up.u64ptr) ) : \
- (n == 4 ? be32toh( get_unaligned(__up.u32ptr) ) : \
- (n == 3 ? uint24_from_be( __up ) : \
- (n == 2 ? be16toh( get_unaligned(__up.u16ptr) ) : \
- *__up.u8ptr )))); \
- })
+#ifdef __BIG_ENDIAN__
+#define beNtoh(x,n) (x >> ((8-n) << 3))
+#else
+#define beNtoh(x,n) be64toh((x) << ((8-(n)) << 3))
+#endif
-#define be64dec(x) \
+#ifdef _MSC_VER
+static uint64_t UINT_TO_HOST(const void* x, uint8_t n)
+{
+ union plist_uint_ptr __up;
+ __up.src = (n > 8) ? (const char*)x + (n - 8) : (const char*)x;
+ return (n >= 8 ? be64toh( get_unaligned_64(__up.u64ptr) ) :
+ (n == 4 ? be32toh( get_unaligned_32(__up.u32ptr) ) :
+ (n == 2 ? be16toh( get_unaligned_16(__up.u16ptr) ) :
+ (n == 1 ? *__up.u8ptr :
+ beNtoh( get_unaligned_64(__up.u64ptr), n)
+ ))));
+}
+#else
+#define UINT_TO_HOST(x, n) \
({ \
union plist_uint_ptr __up; \
- __up.src = x; \
- be64toh( get_unaligned(__up.u64ptr) ); \
+ __up.src = ((n) > 8) ? (const char*)(x) + ((n) - 8) : (const char*)(x); \
+ ((n) >= 8 ? be64toh( get_unaligned(__up.u64ptr) ) : \
+ ((n) == 4 ? be32toh( get_unaligned(__up.u32ptr) ) : \
+ ((n) == 2 ? be16toh( get_unaligned(__up.u16ptr) ) : \
+ ((n) == 1 ? *__up.u8ptr : \
+ beNtoh( get_unaligned(__up.u64ptr), n) \
+ )))); \
})
+#endif
#define get_needed_bytes(x) \
- ( ((uint64_t)x) < (1ULL << 8) ? 1 : \
- ( ((uint64_t)x) < (1ULL << 16) ? 2 : \
- ( ((uint64_t)x) < (1ULL << 24) ? 3 : \
- ( ((uint64_t)x) < (1ULL << 32) ? 4 : 8))))
+ ( ((uint64_t)(x)) < (1ULL << 8) ? 1 : \
+ ( ((uint64_t)(x)) < (1ULL << 16) ? 2 : \
+ ( ((uint64_t)(x)) < (1ULL << 24) ? 3 : \
+ ( ((uint64_t)(x)) < (1ULL << 32) ? 4 : 8))))
-#define get_real_bytes(x) (x == (float) x ? 4 : 8)
+#define get_real_bytes(x) ((x) == (float) (x) ? sizeof(float) : sizeof(double))
-#define NODE_IS_ROOT(x) (((node_t*)x)->isRoot)
+#if (defined(__BIG_ENDIAN__) && !defined(__FLOAT_WORD_ORDER__)) \
+ || (defined(__FLOAT_WORD_ORDER__) && __FLOAT_WORD_ORDER__ == __ORDER_BIG_ENDIAN__)
+#define float_bswap64(x) (x)
+#define float_bswap32(x) (x)
+#else
+#define float_bswap64(x) bswap64(x)
+#define float_bswap32(x) bswap32(x)
+#endif
-static plist_t parse_uint_node(char *bnode, uint8_t size, char **next_object)
+#ifndef __has_builtin
+#define __has_builtin(x) 0
+#endif
+
+#if __has_builtin(__builtin_umulll_overflow) || __GNUC__ >= 5
+#define uint64_mul_overflow(a, b, r) __builtin_umulll_overflow(a, b, (unsigned long long*)(r))
+#else
+static int uint64_mul_overflow(uint64_t a, uint64_t b, uint64_t *res)
+{
+ *res = a * b;
+ return (a > UINT64_MAX / b);
+}
+#endif
+
+#define NODE_IS_ROOT(x) (((node_t)(x))->isRoot)
+
+struct bplist_data {
+ const char* data;
+ uint64_t size;
+ uint64_t num_objects;
+ uint8_t ref_size;
+ uint8_t offset_size;
+ const char* offset_table;
+ uint32_t level;
+ ptrarray_t* used_indexes;
+ plist_err_t err;
+};
+
+#ifdef DEBUG
+static int plist_bin_debug = 0;
+#define PLIST_BIN_ERR(...) if (plist_bin_debug) { fprintf(stderr, "libplist[binparser] ERROR: " __VA_ARGS__); }
+#define PLIST_BIN_WRITE_ERR(...) if (plist_bin_debug) { fprintf(stderr, "libplist[binwriter] ERROR: " __VA_ARGS__); }
+#else
+#define PLIST_BIN_ERR(...)
+#define PLIST_BIN_WRITE_ERR(...)
+#endif
+
+void plist_bin_init(void)
+{
+ /* init binary plist stuff */
+#ifdef DEBUG
+ char *env_debug = getenv("PLIST_BIN_DEBUG");
+ if (env_debug && !strcmp(env_debug, "1")) {
+ plist_bin_debug = 1;
+ }
+#endif
+}
+
+void plist_bin_deinit(void)
+{
+ /* deinit binary plist stuff */
+}
+
+void plist_bin_set_debug(int debug)
+{
+#if DEBUG
+ plist_bin_debug = debug;
+#endif
+}
+
+static plist_t parse_bin_node_at_index(struct bplist_data *bplist, uint32_t node_index);
+
+static plist_t parse_int_node(const char **bnode, uint8_t size)
{
plist_data_t data = plist_new_plist_data();
+ if (!data) {
+ PLIST_BIN_ERR("%s: failed to allocate plist data\n", __func__);
+ return NULL;
+ }
size = 1 << size; // make length less misleading
switch (size)
@@ -206,91 +290,143 @@ static plist_t parse_uint_node(char *bnode, uint8_t size, char **next_object)
case sizeof(uint16_t):
case sizeof(uint32_t):
case sizeof(uint64_t):
- memcpy(&data->intval, bnode, size);
- data->intval = UINT_TO_HOST(&data->intval, size);
+ data->length = sizeof(uint64_t);
+ break;
+ case 16:
+ data->length = size;
break;
default:
free(data);
+ PLIST_BIN_ERR("%s: Invalid byte size for integer node\n", __func__);
return NULL;
};
- *next_object = bnode + size;
- data->type = PLIST_UINT;
- data->length = sizeof(uint64_t);
+ data->intval = UINT_TO_HOST(*bnode, size);
+
+ (*bnode) += size;
+ data->type = PLIST_INT;
return node_create(NULL, data);
}
-static plist_t parse_real_node(char *bnode, uint8_t size)
+static plist_t parse_real_node(const char **bnode, uint8_t size)
{
plist_data_t data = plist_new_plist_data();
- float floatval = 0.0;
- uint8_t* buf;
+ if (!data) {
+ PLIST_BIN_ERR("%s: failed to allocate plist data\n", __func__);
+ return NULL;
+ }
size = 1 << size; // make length less misleading
- buf = malloc (size);
- memcpy (buf, bnode, size);
switch (size)
{
- case sizeof(float):
- float_byte_convert(buf, size);
- floatval = *(float *) buf;
- data->realval = floatval;
- break;
- case sizeof(double):
- float_byte_convert(buf, size);
- data->realval = *(double *) buf;
+ case sizeof(uint32_t):
+ {
+ uint32_t ival;
+ memcpy(&ival, *bnode, sizeof(uint32_t));
+ ival = float_bswap32(ival);
+ float fval;
+ memcpy(&fval, &ival, sizeof(float));
+ data->realval = fval;
+ }
+ break;
+
+ case sizeof(uint64_t):
+ {
+ uint64_t ival;
+ memcpy(&ival, *bnode, sizeof(uint64_t));
+ ival = float_bswap64(ival);
+ memcpy(&data->realval, &ival, sizeof(double));
break;
+ }
+
default:
free(data);
+ PLIST_BIN_ERR("%s: Invalid byte size for real node\n", __func__);
return NULL;
}
- free (buf);
data->type = PLIST_REAL;
data->length = sizeof(double);
return node_create(NULL, data);
}
-static plist_t parse_date_node(char *bnode, uint8_t size)
+static plist_t parse_date_node(const char **bnode, uint8_t size)
{
plist_t node = parse_real_node(bnode, size);
plist_data_t data = plist_get_data(node);
- double time_real = data->realval;
- data->timeval.tv_sec = (long) time_real;
- data->timeval.tv_usec = (time_real - (long) time_real) * 1000000;
data->type = PLIST_DATE;
- data->length = sizeof(struct timeval);
return node;
}
-static plist_t parse_string_node(char *bnode, uint64_t size)
+static plist_t parse_string_node(const char **bnode, uint64_t size)
{
plist_data_t data = plist_new_plist_data();
+ if (!data) {
+ PLIST_BIN_ERR("%s: failed to allocate plist data\n", __func__);
+ return NULL;
+ }
data->type = PLIST_STRING;
data->strval = (char *) malloc(sizeof(char) * (size + 1));
- memcpy(data->strval, bnode, size);
+ if (!data->strval) {
+ plist_free_data(data);
+ PLIST_BIN_ERR("%s: Could not allocate %" PRIu64 " bytes\n", __func__, sizeof(char) * (size + 1));
+ return NULL;
+ }
+ memcpy(data->strval, *bnode, size);
data->strval[size] = '\0';
data->length = strlen(data->strval);
return node_create(NULL, data);
}
-static char *plist_utf16_to_utf8(uint16_t *unistr, long len, long *items_read, long *items_written)
+static char *plist_utf16be_to_utf8(uint16_t *unistr, size_t len, size_t *items_read, size_t *items_written)
{
if (!unistr || (len <= 0)) return NULL;
- char *outbuf = (char*)malloc(3*(len+1));
- int p = 0;
- int i = 0;
+ char* outbuf;
+ char* outbuf_new;
+ size_t p = 0;
+ size_t i = 0;
uint16_t wc;
+ uint32_t w;
+ int read_lead_surrogate = 0;
+
+ /* allocate with enough space */
+ outbuf = (char*)malloc(4*(len+1));
+ if (!outbuf) {
+ PLIST_BIN_ERR("%s: Could not allocate %" PRIu64 " bytes\n", __func__, (uint64_t)(4*(len+1)));
+ return NULL;
+ }
while (i < len) {
- wc = unistr[i++];
- if (wc >= 0x800) {
+ wc = UINT_TO_HOST(unistr + i, sizeof(wc));
+ i++;
+ if (wc >= 0xD800 && wc <= 0xDBFF) {
+ if (!read_lead_surrogate) {
+ read_lead_surrogate = 1;
+ w = 0x010000 + ((wc & 0x3FF) << 10);
+ } else {
+ // This is invalid, the next 16 bit char should be a trail surrogate.
+ // Handling error by skipping.
+ read_lead_surrogate = 0;
+ }
+ } else if (wc >= 0xDC00 && wc <= 0xDFFF) {
+ if (read_lead_surrogate) {
+ read_lead_surrogate = 0;
+ w = w | (wc & 0x3FF);
+ outbuf[p++] = (char)(0xF0 + ((w >> 18) & 0x7));
+ outbuf[p++] = (char)(0x80 + ((w >> 12) & 0x3F));
+ outbuf[p++] = (char)(0x80 + ((w >> 6) & 0x3F));
+ outbuf[p++] = (char)(0x80 + (w & 0x3F));
+ } else {
+ // This is invalid. A trail surrogate should always follow a lead surrogate.
+ // Handling error by skipping
+ }
+ } else if (wc >= 0x800) {
outbuf[p++] = (char)(0xE0 + ((wc >> 12) & 0xF));
outbuf[p++] = (char)(0x80 + ((wc >> 6) & 0x3F));
outbuf[p++] = (char)(0x80 + (wc & 0x3F));
@@ -309,85 +445,263 @@ static char *plist_utf16_to_utf8(uint16_t *unistr, long len, long *items_read, l
}
outbuf[p] = 0;
+ /* reduce the size to the actual size */
+ outbuf_new = (char*)realloc(outbuf, p+1);
+ if (outbuf_new) {
+ outbuf = outbuf_new;
+ }
+
return outbuf;
}
-static plist_t parse_unicode_node(char *bnode, uint64_t size)
+static plist_t parse_unicode_node(const char **bnode, uint64_t size)
{
plist_data_t data = plist_new_plist_data();
- uint64_t i = 0;
- uint16_t *unicodestr = NULL;
- char *tmpstr = NULL;
- long items_read = 0;
- long items_written = 0;
+ if (!data) {
+ PLIST_BIN_ERR("%s: failed to allocate plist data\n", __func__);
+ return NULL;
+ }
+ size_t items_read = 0;
+ size_t items_written = 0;
data->type = PLIST_STRING;
- unicodestr = (uint16_t*) malloc(sizeof(uint16_t) * size);
- memcpy(unicodestr, bnode, sizeof(uint16_t) * size);
- for (i = 0; i < size; i++)
- byte_convert((uint8_t *) (unicodestr + i), sizeof(uint16_t));
-
- tmpstr = plist_utf16_to_utf8(unicodestr, size, &items_read, &items_written);
- free(unicodestr);
+ data->strval = plist_utf16be_to_utf8((uint16_t*)(*bnode), size, &items_read, &items_written);
+ if (!data->strval) {
+ plist_free_data(data);
+ return NULL;
+ }
+ data->length = items_written;
- data->type = PLIST_STRING;
- data->strval = (char *) malloc(sizeof(char) * (items_written + 1));
- memcpy(data->strval, tmpstr, items_written);
- data->strval[items_written] = '\0';
- data->length = strlen(data->strval);
- free(tmpstr);
return node_create(NULL, data);
}
-static plist_t parse_data_node(char *bnode, uint64_t size)
+static plist_t parse_data_node(const char **bnode, uint64_t size)
{
plist_data_t data = plist_new_plist_data();
-
+ if (!data) {
+ PLIST_BIN_ERR("%s: failed to allocate plist data\n", __func__);
+ return NULL;
+ }
data->type = PLIST_DATA;
data->length = size;
data->buff = (uint8_t *) malloc(sizeof(uint8_t) * size);
- memcpy(data->buff, bnode, sizeof(uint8_t) * size);
+ if (!data->buff) {
+ plist_free_data(data);
+ PLIST_BIN_ERR("%s: Could not allocate %" PRIu64 " bytes\n", __func__, sizeof(uint8_t) * size);
+ return NULL;
+ }
+ memcpy(data->buff, *bnode, sizeof(uint8_t) * size);
return node_create(NULL, data);
}
-static plist_t parse_dict_node(char *bnode, uint64_t size, uint32_t ref_size)
+static plist_t parse_dict_node(struct bplist_data *bplist, const char** bnode, uint64_t size)
{
+ uint64_t j;
+ uint64_t str_i = 0, str_j = 0;
+ uint64_t index1, index2;
plist_data_t data = plist_new_plist_data();
+ if (!data) {
+ PLIST_BIN_ERR("%s: failed to allocate plist data\n", __func__);
+ return NULL;
+ }
+ const char *index1_ptr = NULL;
+ const char *index2_ptr = NULL;
data->type = PLIST_DICT;
data->length = size;
- data->buff = (uint8_t *) malloc(sizeof(uint8_t) * size * ref_size * 2);
- memcpy(data->buff, bnode, sizeof(uint8_t) * size * ref_size * 2);
- return node_create(NULL, data);
+ plist_t node = node_create(NULL, data);
+ if (!node) {
+ plist_free_data(data);
+ PLIST_BIN_ERR("%s: failed to create node\n", __func__);
+ return NULL;
+ }
+
+ for (j = 0; j < data->length; j++) {
+ str_i = j * bplist->ref_size;
+ str_j = (j + size) * bplist->ref_size;
+ index1_ptr = (*bnode) + str_i;
+ index2_ptr = (*bnode) + str_j;
+
+ if ((index1_ptr < bplist->data || index1_ptr + bplist->ref_size > bplist->offset_table) ||
+ (index2_ptr < bplist->data || index2_ptr + bplist->ref_size > bplist->offset_table)) {
+ plist_free(node);
+ PLIST_BIN_ERR("%s: dict entry %" PRIu64 " is outside of valid range\n", __func__, j);
+ return NULL;
+ }
+
+ index1 = UINT_TO_HOST(index1_ptr, bplist->ref_size);
+ index2 = UINT_TO_HOST(index2_ptr, bplist->ref_size);
+
+ if (index1 >= bplist->num_objects) {
+ plist_free(node);
+ PLIST_BIN_ERR("%s: dict entry %" PRIu64 ": key index (%" PRIu64 ") must be smaller than the number of objects (%" PRIu64 ")\n", __func__, j, index1, bplist->num_objects);
+ return NULL;
+ }
+ if (index2 >= bplist->num_objects) {
+ plist_free(node);
+ PLIST_BIN_ERR("%s: dict entry %" PRIu64 ": value index (%" PRIu64 ") must be smaller than the number of objects (%" PRIu64 ")\n", __func__, j, index1, bplist->num_objects);
+ return NULL;
+ }
+
+ /* process key node */
+ plist_t key = parse_bin_node_at_index(bplist, index1);
+ if (!key) {
+ plist_free(node);
+ return NULL;
+ }
+
+ if (plist_get_data(key)->type != PLIST_STRING) {
+ PLIST_BIN_ERR("%s: dict entry %" PRIu64 ": invalid node type for key\n", __func__, j);
+ plist_free(key);
+ plist_free(node);
+ return NULL;
+ }
+
+ /* enforce key type */
+ plist_get_data(key)->type = PLIST_KEY;
+ if (!plist_get_data(key)->strval) {
+ PLIST_BIN_ERR("%s: dict entry %" PRIu64 ": key must not be NULL\n", __func__, j);
+ plist_free(key);
+ plist_free(node);
+ return NULL;
+ }
+
+ /* process value node */
+ plist_t val = parse_bin_node_at_index(bplist, index2);
+ if (!val) {
+ plist_free(key);
+ plist_free(node);
+ return NULL;
+ }
+
+ node_attach((node_t)node, (node_t)key);
+ node_attach((node_t)node, (node_t)val);
+ }
+
+ return node;
}
-static plist_t parse_array_node(char *bnode, uint64_t size, uint32_t ref_size)
+static plist_t parse_array_node(struct bplist_data *bplist, const char** bnode, uint64_t size)
{
+ uint64_t j;
+ uint64_t str_j = 0;
+ uint64_t index1;
plist_data_t data = plist_new_plist_data();
+ if (!data) {
+ PLIST_BIN_ERR("%s: failed to allocate plist data\n", __func__);
+ return NULL;
+ }
+ const char *index1_ptr = NULL;
data->type = PLIST_ARRAY;
data->length = size;
- data->buff = (uint8_t *) malloc(sizeof(uint8_t) * size * ref_size);
- memcpy(data->buff, bnode, sizeof(uint8_t) * size * ref_size);
- return node_create(NULL, data);
+ plist_t node = node_create(NULL, data);
+ if (!node) {
+ plist_free_data(data);
+ PLIST_BIN_ERR("%s: failed to create node\n", __func__);
+ return NULL;
+ }
+
+ for (j = 0; j < data->length; j++) {
+ str_j = j * bplist->ref_size;
+ index1_ptr = (*bnode) + str_j;
+
+ if (index1_ptr < bplist->data || index1_ptr + bplist->ref_size > bplist->offset_table) {
+ plist_free(node);
+ PLIST_BIN_ERR("%s: array item %" PRIu64 " is outside of valid range\n", __func__, j);
+ return NULL;
+ }
+
+ index1 = UINT_TO_HOST(index1_ptr, bplist->ref_size);
+
+ if (index1 >= bplist->num_objects) {
+ plist_free(node);
+ PLIST_BIN_ERR("%s: array item %" PRIu64 " object index (%" PRIu64 ") must be smaller than the number of objects (%" PRIu64 ")\n", __func__, j, index1, bplist->num_objects);
+ return NULL;
+ }
+
+ /* process value node */
+ plist_t val = parse_bin_node_at_index(bplist, index1);
+ if (!val) {
+ plist_free(node);
+ return NULL;
+ }
+
+ node_attach((node_t)node, (node_t)val);
+ }
+
+ return node;
}
+static plist_t parse_uid_node(const char **bnode, uint8_t size)
+{
+ plist_data_t data = plist_new_plist_data();
+ if (!data) {
+ PLIST_BIN_ERR("%s: failed to allocate plist data\n", __func__);
+ return NULL;
+ }
+ size = size + 1;
+ data->intval = UINT_TO_HOST(*bnode, size);
+ if (data->intval > UINT32_MAX) {
+ PLIST_BIN_ERR("%s: value %" PRIu64 " too large for UID node (must be <= %u)\n", __func__, (uint64_t)data->intval, UINT32_MAX);
+ free(data);
+ return NULL;
+ }
+
+ (*bnode) += size;
+ data->type = PLIST_UID;
+ data->length = sizeof(uint64_t);
+ return node_create(NULL, data);
+}
-static plist_t parse_bin_node(char *object, uint8_t dict_size, char **next_object)
+static plist_t parse_bin_node(struct bplist_data *bplist, const char** object)
{
uint16_t type = 0;
uint64_t size = 0;
+ uint64_t pobject = 0;
+ uint64_t poffset_table = (uint64_t)(uintptr_t)bplist->offset_table;
if (!object)
return NULL;
- type = (*object) & 0xF0;
- size = (*object) & 0x0F;
- object++;
+ type = (**object) & BPLIST_MASK;
+ size = (**object) & BPLIST_FILL;
+ (*object)++;
+
+ if (size == BPLIST_FILL) {
+ switch (type) {
+ case BPLIST_DATA:
+ case BPLIST_STRING:
+ case BPLIST_UNICODE:
+ case BPLIST_ARRAY:
+ case BPLIST_SET:
+ case BPLIST_DICT:
+ {
+ uint16_t next_size = **object & BPLIST_FILL;
+ if ((**object & BPLIST_MASK) != BPLIST_INT) {
+ PLIST_BIN_ERR("%s: invalid size node type for node type 0x%02x: found 0x%02x, expected 0x%02x\n", __func__, type, **object & BPLIST_MASK, BPLIST_INT);
+ return NULL;
+ }
+ (*object)++;
+ next_size = 1 << next_size;
+ if (*object + next_size > bplist->offset_table) {
+ PLIST_BIN_ERR("%s: size node data bytes for node type 0x%02x point outside of valid range\n", __func__, type);
+ return NULL;
+ }
+ size = UINT_TO_HOST(*object, next_size);
+ (*object) += next_size;
+ break;
+ }
+ default:
+ break;
+ }
+ }
+
+ pobject = (uint64_t)(uintptr_t)*object;
switch (type)
{
@@ -399,6 +713,10 @@ static plist_t parse_bin_node(char *object, uint8_t dict_size, char **next_objec
case BPLIST_TRUE:
{
plist_data_t data = plist_new_plist_data();
+ if (!data) {
+ PLIST_BIN_ERR("%s: failed to allocate plist data\n", __func__);
+ return NULL;
+ }
data->type = PLIST_BOOLEAN;
data->boolval = TRUE;
data->length = 1;
@@ -408,6 +726,10 @@ static plist_t parse_bin_node(char *object, uint8_t dict_size, char **next_objec
case BPLIST_FALSE:
{
plist_data_t data = plist_new_plist_data();
+ if (!data) {
+ PLIST_BIN_ERR("%s: failed to allocate plist data\n", __func__);
+ return NULL;
+ }
data->type = PLIST_BOOLEAN;
data->boolval = FALSE;
data->length = 1;
@@ -415,246 +737,286 @@ static plist_t parse_bin_node(char *object, uint8_t dict_size, char **next_objec
}
case BPLIST_NULL:
+ {
+ plist_data_t data = plist_new_plist_data();
+ if (!data) {
+ PLIST_BIN_ERR("%s: failed to allocate plist data\n", __func__);
+ return NULL;
+ }
+ data->type = PLIST_NULL;
+ data->length = 0;
+ return node_create(NULL, data);
+ }
+
default:
return NULL;
}
- case BPLIST_UINT:
- return parse_uint_node(object, size, next_object);
+ case BPLIST_INT:
+ if (pobject + (uint64_t)(1 << size) > poffset_table) {
+ PLIST_BIN_ERR("%s: BPLIST_INT data bytes point outside of valid range\n", __func__);
+ return NULL;
+ }
+ return parse_int_node(object, size);
case BPLIST_REAL:
+ if (pobject + (uint64_t)(1 << size) > poffset_table) {
+ PLIST_BIN_ERR("%s: BPLIST_REAL data bytes point outside of valid range\n", __func__);
+ return NULL;
+ }
return parse_real_node(object, size);
case BPLIST_DATE:
- if (3 != size)
+ if (3 != size) {
+ PLIST_BIN_ERR("%s: invalid data size for BPLIST_DATE node\n", __func__);
return NULL;
- else
- return parse_date_node(object, size);
+ }
+ if (pobject + (uint64_t)(1 << size) > poffset_table) {
+ PLIST_BIN_ERR("%s: BPLIST_DATE data bytes point outside of valid range\n", __func__);
+ return NULL;
+ }
+ return parse_date_node(object, size);
case BPLIST_DATA:
- if (0x0F == size)
- {
- plist_t size_node = parse_bin_node(object, dict_size, &object);
- if (plist_get_node_type(size_node) != PLIST_UINT)
- return NULL;
- plist_get_uint_val(size_node, &size);
- plist_free(size_node);
+ if (pobject + size < pobject || pobject + size > poffset_table) {
+ PLIST_BIN_ERR("%s: BPLIST_DATA data bytes point outside of valid range\n", __func__);
+ return NULL;
}
return parse_data_node(object, size);
case BPLIST_STRING:
- if (0x0F == size)
- {
- plist_t size_node = parse_bin_node(object, dict_size, &object);
- if (plist_get_node_type(size_node) != PLIST_UINT)
- return NULL;
- plist_get_uint_val(size_node, &size);
- plist_free(size_node);
+ if (pobject + size < pobject || pobject + size > poffset_table) {
+ PLIST_BIN_ERR("%s: BPLIST_STRING data bytes point outside of valid range\n", __func__);
+ return NULL;
}
return parse_string_node(object, size);
case BPLIST_UNICODE:
- if (0x0F == size)
- {
- plist_t size_node = parse_bin_node(object, dict_size, &object);
- if (plist_get_node_type(size_node) != PLIST_UINT)
- return NULL;
- plist_get_uint_val(size_node, &size);
- plist_free(size_node);
+ if (size*2 < size) {
+ PLIST_BIN_ERR("%s: Integer overflow when calculating BPLIST_UNICODE data size.\n", __func__);
+ return NULL;
+ }
+ if (pobject + size*2 < pobject || pobject + size*2 > poffset_table) {
+ PLIST_BIN_ERR("%s: BPLIST_UNICODE data bytes point outside of valid range\n", __func__);
+ return NULL;
}
return parse_unicode_node(object, size);
- case BPLIST_UID:
+ case BPLIST_SET:
case BPLIST_ARRAY:
- if (0x0F == size)
- {
- plist_t size_node = parse_bin_node(object, dict_size, &object);
- if (plist_get_node_type(size_node) != PLIST_UINT)
- return NULL;
- plist_get_uint_val(size_node, &size);
- plist_free(size_node);
+ if (pobject + size < pobject || pobject + size > poffset_table) {
+ PLIST_BIN_ERR("%s: BPLIST_ARRAY data bytes point outside of valid range\n", __func__);
+ return NULL;
}
- return parse_array_node(object, size, dict_size);
+ return parse_array_node(bplist, object, size);
+
+ case BPLIST_UID:
+ if (pobject + size+1 > poffset_table) {
+ PLIST_BIN_ERR("%s: BPLIST_UID data bytes point outside of valid range\n", __func__);
+ return NULL;
+ }
+ return parse_uid_node(object, size);
- case BPLIST_SET:
case BPLIST_DICT:
- if (0x0F == size)
- {
- plist_t size_node = parse_bin_node(object, dict_size, &object);
- if (plist_get_node_type(size_node) != PLIST_UINT)
- return NULL;
- plist_get_uint_val(size_node, &size);
- plist_free(size_node);
+ if (pobject + size < pobject || pobject + size > poffset_table) {
+ PLIST_BIN_ERR("%s: BPLIST_DICT data bytes point outside of valid range\n", __func__);
+ return NULL;
}
- return parse_dict_node(object, size, dict_size);
+ return parse_dict_node(bplist, object, size);
+
default:
+ PLIST_BIN_ERR("%s: unexpected node type 0x%02x\n", __func__, type);
return NULL;
}
return NULL;
}
-static void* copy_plist_data(const void* src)
+static plist_t parse_bin_node_at_index(struct bplist_data *bplist, uint32_t node_index)
{
- plist_data_t srcdata = (plist_data_t) src;
- plist_data_t dstdata = plist_new_plist_data();
+ int i = 0;
+ const char* ptr = NULL;
+ plist_t plist = NULL;
+ const char* idx_ptr = NULL;
+
+ if (node_index >= bplist->num_objects) {
+ PLIST_BIN_ERR("node index (%u) must be smaller than the number of objects (%" PRIu64 ")\n", node_index, bplist->num_objects);
+ bplist->err = PLIST_ERR_PARSE;
+ return NULL;
+ }
- dstdata->type = srcdata->type;
- dstdata->length = srcdata->length;
- switch (dstdata->type)
- {
- case PLIST_BOOLEAN:
- dstdata->boolval = srcdata->boolval;
- break;
- case PLIST_UINT:
- dstdata->intval = srcdata->intval;
- break;
- case PLIST_DATE:
- dstdata->timeval.tv_sec = srcdata->timeval.tv_sec;
- dstdata->timeval.tv_usec = srcdata->timeval.tv_usec;
- break;
- case PLIST_REAL:
- dstdata->realval = srcdata->realval;
- break;
- case PLIST_KEY:
- case PLIST_STRING:
- dstdata->strval = strdup(srcdata->strval);
- break;
- case PLIST_DATA:
- case PLIST_ARRAY:
- dstdata->buff = (uint8_t*) malloc(sizeof(uint8_t) * srcdata->length);
- memcpy(dstdata->buff, srcdata->buff, sizeof(uint8_t) * srcdata->length);
- break;
- case PLIST_DICT:
- dstdata->buff = (uint8_t*) malloc(sizeof(uint8_t) * srcdata->length * 2);
- memcpy(dstdata->buff, srcdata->buff, sizeof(uint8_t) * srcdata->length * 2);
- break;
- default:
- break;
+ idx_ptr = bplist->offset_table + node_index * bplist->offset_size;
+ if (idx_ptr < bplist->offset_table ||
+ idx_ptr >= bplist->offset_table + bplist->num_objects * bplist->offset_size) {
+ PLIST_BIN_ERR("node index %u points outside of valid range\n", node_index);
+ bplist->err = PLIST_ERR_PARSE;
+ return NULL;
+ }
+
+ uint64_t node_offset = UINT_TO_HOST(idx_ptr, bplist->offset_size);
+ if (node_offset > (uint64_t)bplist->size) {
+ PLIST_BIN_ERR("node offset overflow (%" PRIu64 ")\n", node_offset);
+ bplist->err = PLIST_ERR_PARSE;
+ return NULL;
+ }
+ ptr = bplist->data + node_offset;
+ /* make sure the node offset is in a sane range */
+ if ((ptr < bplist->data+BPLIST_MAGIC_SIZE+BPLIST_VERSION_SIZE) || (ptr >= bplist->offset_table)) {
+ PLIST_BIN_ERR("offset for node index %u points outside of valid range\n", node_index);
+ bplist->err = PLIST_ERR_PARSE;
+ return NULL;
+ }
+
+ /* check nesting depth */
+ if (bplist->level > PLIST_MAX_NESTING_DEPTH) {
+ PLIST_BIN_ERR("maximum nesting depth (%u) exceeded\n",(unsigned)PLIST_MAX_NESTING_DEPTH);
+ bplist->err = PLIST_ERR_MAX_NESTING;
+ return NULL;
+ }
+
+ /* store node_index for current recursion level */
+ if ((uint32_t)ptr_array_size(bplist->used_indexes) < bplist->level+1) {
+ while ((uint32_t)ptr_array_size(bplist->used_indexes) < bplist->level+1) {
+ ptr_array_add(bplist->used_indexes, (void*)(uintptr_t)node_index);
+ }
+ } else {
+ ptr_array_set(bplist->used_indexes, (void*)(uintptr_t)node_index, bplist->level);
+ }
+
+ /* recursion check */
+ if (bplist->level > 0) {
+ for (i = bplist->level-1; i >= 0; i--) {
+ void *node_i = ptr_array_index(bplist->used_indexes, i);
+ void *node_level = ptr_array_index(bplist->used_indexes, bplist->level);
+ if (node_i == node_level) {
+ PLIST_BIN_ERR("recursion detected in binary plist\n");
+ bplist->err = PLIST_ERR_CIRCULAR_REF;
+ return NULL;
+ }
+ }
}
- return dstdata;
+ /* finally parse node */
+ bplist->level++;
+ plist = parse_bin_node(bplist, &ptr);
+ bplist->level--;
+ return plist;
}
-void plist_from_bin(const char *plist_bin, uint32_t length, plist_t * plist)
+plist_err_t plist_from_bin(const char *plist_bin, uint32_t length, plist_t * plist)
{
- char *trailer = NULL;
-
+ bplist_trailer_t *trailer = NULL;
uint8_t offset_size = 0;
- uint8_t dict_param_size = 0;
+ uint8_t ref_size = 0;
uint64_t num_objects = 0;
uint64_t root_object = 0;
- uint64_t offset_table_index = 0;
-
- plist_t *nodeslist = NULL;
- uint64_t i = 0;
- uint64_t current_offset = 0;
- char *offset_table = NULL;
- uint32_t j = 0, str_i = 0, str_j = 0;
- uint32_t index1 = 0, index2 = 0;
+ const char *offset_table = NULL;
+ uint64_t offset_table_size = 0;
+ const char *start_data = NULL;
+ const char *end_data = NULL;
+ if (!plist) {
+ return PLIST_ERR_INVALID_ARG;
+ }
+ *plist = NULL;
+ if (!plist_bin || length == 0) {
+ return PLIST_ERR_INVALID_ARG;
+ }
//first check we have enough data
- if (!(length >= BPLIST_MAGIC_SIZE + BPLIST_VERSION_SIZE + BPLIST_TRL_SIZE))
- return;
+ if (!(length >= BPLIST_MAGIC_SIZE + BPLIST_VERSION_SIZE + sizeof(bplist_trailer_t))) {
+ PLIST_BIN_ERR("plist data is to small to hold a binary plist\n");
+ return PLIST_ERR_PARSE;
+ }
//check that plist_bin in actually a plist
- if (memcmp(plist_bin, BPLIST_MAGIC, BPLIST_MAGIC_SIZE) != 0)
- return;
+ if (memcmp(plist_bin, BPLIST_MAGIC, BPLIST_MAGIC_SIZE) != 0) {
+ PLIST_BIN_ERR("bplist magic mismatch\n");
+ return PLIST_ERR_PARSE;
+ }
//check for known version
- if (memcmp(plist_bin + BPLIST_MAGIC_SIZE, BPLIST_VERSION, BPLIST_VERSION_SIZE) != 0)
- return;
+ if (memcmp(plist_bin + BPLIST_MAGIC_SIZE, BPLIST_VERSION, BPLIST_VERSION_SIZE) != 0) {
+ PLIST_BIN_ERR("unsupported binary plist version '%.2s\n", plist_bin+BPLIST_MAGIC_SIZE);
+ return PLIST_ERR_PARSE;
+ }
+
+ start_data = plist_bin + BPLIST_MAGIC_SIZE + BPLIST_VERSION_SIZE;
+ end_data = plist_bin + length - sizeof(bplist_trailer_t);
//now parse trailer
- trailer = (char *) (plist_bin + (length - BPLIST_TRL_SIZE));
+ trailer = (bplist_trailer_t*)end_data;
+
+ offset_size = trailer->offset_size;
+ ref_size = trailer->ref_size;
+ num_objects = be64toh(trailer->num_objects);
+ root_object = be64toh(trailer->root_object_index);
+
+ uint64_t offset_table_offset = be64toh(trailer->offset_table_offset);
+ uint64_t max_valid_offset = (uint64_t)length - sizeof(bplist_trailer_t);
+ if (offset_table_offset > max_valid_offset) {
+ PLIST_BIN_ERR("offset table offset outside of valid range\n");
+ return PLIST_ERR_PARSE;
+ }
+ offset_table = (char *)(plist_bin + offset_table_offset);
- offset_size = trailer[BPLIST_TRL_OFFSIZE_IDX];
- dict_param_size = trailer[BPLIST_TRL_PARMSIZE_IDX];
- num_objects = be64dec(trailer + BPLIST_TRL_NUMOBJ_IDX);
- root_object = be64dec(trailer + BPLIST_TRL_ROOTOBJ_IDX);
- offset_table_index = be64dec(trailer + BPLIST_TRL_OFFTAB_IDX);
+ if (num_objects == 0) {
+ PLIST_BIN_ERR("number of objects must be larger than 0\n");
+ return PLIST_ERR_PARSE;
+ }
- if (num_objects == 0)
- return;
+ if (offset_size == 0) {
+ PLIST_BIN_ERR("offset size in trailer must be larger than 0\n");
+ return PLIST_ERR_PARSE;
+ }
- //allocate serialized array of nodes
- nodeslist = (plist_t *) malloc(sizeof(plist_t) * num_objects);
+ if (ref_size == 0) {
+ PLIST_BIN_ERR("object reference size in trailer must be larger than 0\n");
+ return PLIST_ERR_PARSE;
+ }
- if (!nodeslist)
- return;
+ if (root_object >= num_objects) {
+ PLIST_BIN_ERR("root object index (%" PRIu64 ") must be smaller than number of objects (%" PRIu64 ")\n", root_object, num_objects);
+ return PLIST_ERR_PARSE;
+ }
- //parse serialized nodes
- offset_table = (char *) (plist_bin + offset_table_index);
- for (i = 0; i < num_objects; i++)
- {
- char *obj = NULL;
- current_offset = UINT_TO_HOST(offset_table + i * offset_size, offset_size);
+ if (offset_table < start_data || offset_table >= end_data) {
+ PLIST_BIN_ERR("offset table offset points outside of valid range\n");
+ return PLIST_ERR_PARSE;
+ }
- obj = (char *) (plist_bin + current_offset);
- nodeslist[i] = parse_bin_node(obj, dict_param_size, &obj);
+ if (uint64_mul_overflow(num_objects, offset_size, &offset_table_size)) {
+ PLIST_BIN_ERR("integer overflow when calculating offset table size\n");
+ return PLIST_ERR_PARSE;
}
- //setup children for structured types
- for (i = 0; i < num_objects; i++)
- {
+ if (offset_table_size > (uint64_t)(end_data - offset_table)) {
+ PLIST_BIN_ERR("offset table points outside of valid range\n");
+ return PLIST_ERR_PARSE;
+ }
- plist_data_t data = plist_get_data(nodeslist[i]);
+ struct bplist_data bplist;
+ bplist.data = plist_bin;
+ bplist.size = length;
+ bplist.num_objects = num_objects;
+ bplist.ref_size = ref_size;
+ bplist.offset_size = offset_size;
+ bplist.offset_table = offset_table;
+ bplist.level = 0;
+ bplist.used_indexes = ptr_array_new(16);
+ bplist.err = PLIST_ERR_SUCCESS;
+
+ if (!bplist.used_indexes) {
+ PLIST_BIN_ERR("failed to create array to hold used node indexes. Out of memory?\n");
+ return PLIST_ERR_NO_MEM;
+ }
- switch (data->type)
- {
- case PLIST_DICT:
- for (j = 0; j < data->length; j++)
- {
- str_i = j * dict_param_size;
- str_j = (j + data->length) * dict_param_size;
-
- index1 = UINT_TO_HOST(data->buff + str_i, dict_param_size);
- index2 = UINT_TO_HOST(data->buff + str_j, dict_param_size);
-
- //first one is actually a key
- plist_get_data(nodeslist[index1])->type = PLIST_KEY;
-
- if (index1 < num_objects)
- {
- if (NODE_IS_ROOT(nodeslist[index1]))
- node_attach(nodeslist[i], nodeslist[index1]);
- else
- node_attach(nodeslist[i], node_copy_deep(nodeslist[index1], copy_plist_data));
- }
-
- if (index2 < num_objects)
- {
- if (NODE_IS_ROOT(nodeslist[index2]))
- node_attach(nodeslist[i], nodeslist[index2]);
- else
- node_attach(nodeslist[i], node_copy_deep(nodeslist[index2], copy_plist_data));
- }
- }
+ *plist = parse_bin_node_at_index(&bplist, root_object);
- free(data->buff);
- break;
+ ptr_array_free(bplist.used_indexes);
- case PLIST_ARRAY:
- for (j = 0; j < data->length; j++)
- {
- str_j = j * dict_param_size;
- index1 = UINT_TO_HOST(data->buff + str_j, dict_param_size);
-
- if (index1 < num_objects)
- {
- if (NODE_IS_ROOT(nodeslist[index1]))
- node_attach(nodeslist[i], nodeslist[index1]);
- else
- node_attach(nodeslist[i], node_copy_deep(nodeslist[index1], copy_plist_data));
- }
- }
- free(data->buff);
- break;
- default:
- break;
- }
+ if (!*plist) {
+ return (bplist.err != PLIST_ERR_SUCCESS) ? bplist.err : PLIST_ERR_PARSE;
}
- *plist = nodeslist[root_object];
- free(nodeslist);
+ return PLIST_ERR_SUCCESS;
}
static unsigned int plist_data_hash(const void* key)
@@ -670,15 +1032,18 @@ static unsigned int plist_data_hash(const void* key)
switch (data->type)
{
case PLIST_BOOLEAN:
- case PLIST_UINT:
+ case PLIST_NULL:
+ case PLIST_INT:
case PLIST_REAL:
+ case PLIST_DATE:
+ case PLIST_UID:
buff = (char *) &data->intval; //works also for real as we use an union
size = 8;
break;
case PLIST_KEY:
case PLIST_STRING:
buff = data->strval;
- size = strlen(buff);
+ size = data->length;
break;
case PLIST_DATA:
case PLIST_ARRAY:
@@ -687,17 +1052,16 @@ static unsigned int plist_data_hash(const void* key)
buff = (char *) &key;
size = sizeof(const void*);
break;
- case PLIST_DATE:
- buff = (char *) &(data->timeval);
- size = data->length;
- break;
default:
break;
}
- //now perform hash
- for (i = 0; i < size; buff++, i++)
- hash = hash << 7 ^ (*buff);
+ // now perform hash using djb2 hashing algorithm
+ // see: http://www.cse.yorku.ca/~oz/hash.html
+ hash += 5381;
+ for (i = 0; i < size; buff++, i++) {
+ hash = ((hash << 5) + hash) + *buff;
+ }
return hash;
}
@@ -706,110 +1070,124 @@ struct serialize_s
{
ptrarray_t* objects;
hashtable_t* ref_table;
+ hashtable_t* in_stack;
};
-static void serialize_plist(node_t* node, void* data)
+static plist_err_t serialize_plist(node_t node, void* data, uint32_t depth)
{
uint64_t *index_val = NULL;
struct serialize_s *ser = (struct serialize_s *) data;
- uint64_t current_index = ser->objects->len;
- //first check that node is not yet in objects
+ if (depth > PLIST_MAX_NESTING_DEPTH) {
+ PLIST_BIN_WRITE_ERR("maximum nesting depth (%u) exceeded\n", (unsigned)PLIST_MAX_NESTING_DEPTH);
+ return PLIST_ERR_MAX_NESTING;
+ }
+
+ // circular reference check: is node on current recursion stack?
+ if (hash_table_lookup(ser->in_stack, node)) {
+ PLIST_BIN_WRITE_ERR("circular reference detected\n");
+ return PLIST_ERR_CIRCULAR_REF;
+ }
+
+ // first check that node is not yet in objects
void* val = hash_table_lookup(ser->ref_table, node);
- if (val)
- {
- //data is already in table
- return;
+ if (val) {
+ // data is already in table
+ return PLIST_ERR_SUCCESS;
}
- //insert new ref
+
+ // mark as active
+ hash_table_insert(ser->in_stack, node, (void*)1);
+
+ // insert new ref
index_val = (uint64_t *) malloc(sizeof(uint64_t));
- *index_val = current_index;
+ if (!index_val) return PLIST_ERR_NO_MEM;
+ *index_val = ser->objects->len;
hash_table_insert(ser->ref_table, node, index_val);
- //now append current node to object array
+ // now append current node to object array
ptr_array_add(ser->objects, node);
- //now recurse on children
- node_iterator_t *ni = node_iterator_create(node->children);
- node_t *ch;
- while ((ch = node_iterator_next(ni))) {
- serialize_plist(ch, data);
+ // now recurse on children
+ node_t ch;
+ plist_err_t err = PLIST_ERR_SUCCESS;
+ for (ch = node_first_child(node); ch; ch = node_next_sibling(ch)) {
+ err = serialize_plist(ch, data, depth+1);
+ if (err != PLIST_ERR_SUCCESS) {
+ break;
+ }
}
- node_iterator_destroy(ni);
- return;
+ // leave recursion stack
+ hash_table_remove(ser->in_stack, node);
+
+ return err;
}
-#define Log2(x) (x == 8 ? 3 : (x == 4 ? 2 : (x == 2 ? 1 : 0)))
+#define Log2(x) ((x) == 8 ? 3 : ((x) == 4 ? 2 : ((x) == 2 ? 1 : 0)))
static void write_int(bytearray_t * bplist, uint64_t val)
{
- uint64_t size = get_needed_bytes(val);
- uint8_t *buff = NULL;
+ int size = get_needed_bytes(val);
+ uint8_t sz;
//do not write 3bytes int node
if (size == 3)
size++;
+ sz = BPLIST_INT | Log2(size);
-#if PLIST_BYTE_ORDER == PLIST_BIG_ENDIAN
- val = val << ((sizeof(uint64_t) - size) * 8);
-#endif
+ val = be64toh(val);
+ byte_array_append(bplist, &sz, 1);
+ byte_array_append(bplist, (uint8_t*)&val + (8-size), size);
+}
- buff = (uint8_t *) malloc(sizeof(uint8_t) + size);
- buff[0] = BPLIST_UINT | Log2(size);
- memcpy(buff + 1, &val, size);
- byte_convert(buff + 1, size);
- byte_array_append(bplist, buff, sizeof(uint8_t) + size);
- free(buff);
+static void write_uint(bytearray_t * bplist, uint64_t val)
+{
+ uint8_t sz = BPLIST_INT | 4;
+ uint64_t zero = 0;
+
+ val = be64toh(val);
+ byte_array_append(bplist, &sz, 1);
+ byte_array_append(bplist, &zero, sizeof(uint64_t));
+ byte_array_append(bplist, &val, sizeof(uint64_t));
}
static void write_real(bytearray_t * bplist, double val)
{
- uint64_t size = get_real_bytes(val); //cheat to know used space
- uint8_t *buff = (uint8_t *) malloc(sizeof(uint8_t) + size);
- buff[0] = BPLIST_REAL | Log2(size);
- if (size == sizeof(double))
- {
- memcpy(buff + 1, &val, size);
- }
- else if (size == sizeof(float))
- {
- float tmpval = (float) val;
- memcpy(buff + 1, &tmpval, size);
+ int size = get_real_bytes(val); //cheat to know used space
+ uint8_t buff[16];
+ buff[7] = BPLIST_REAL | Log2(size);
+ if (size == sizeof(float)) {
+ float floatval = (float)val;
+ uint32_t intval;
+ memcpy(&intval, &floatval, sizeof(float));
+ *(uint32_t*)(buff+8) = float_bswap32(intval);
+ } else {
+ uint64_t intval;
+ memcpy(&intval, &val, sizeof(double));
+ *(uint64_t*)(buff+8) = float_bswap64(intval);
}
- float_byte_convert(buff + 1, size);
- byte_array_append(bplist, buff, sizeof(uint8_t) + size);
- free(buff);
+ byte_array_append(bplist, buff+7, size+1);
}
static void write_date(bytearray_t * bplist, double val)
{
- uint64_t size = 8; //dates always use 8 bytes
- uint8_t *buff = (uint8_t *) malloc(sizeof(uint8_t) + size);
- buff[0] = BPLIST_DATE | Log2(size);
- memcpy(buff + 1, &val, size);
- float_byte_convert(buff + 1, size);
- byte_array_append(bplist, buff, sizeof(uint8_t) + size);
- free(buff);
+ uint64_t intval;
+ memcpy(&intval, &val, sizeof(double));
+ uint8_t buff[16];
+ buff[7] = BPLIST_DATE | 3;
+ *(uint64_t*)(buff+8) = float_bswap64(intval);
+ byte_array_append(bplist, buff+7, 9);
}
static void write_raw_data(bytearray_t * bplist, uint8_t mark, uint8_t * val, uint64_t size)
{
- uint8_t *buff = NULL;
uint8_t marker = mark | (size < 15 ? size : 0xf);
byte_array_append(bplist, &marker, sizeof(uint8_t));
- if (size >= 15)
- {
- bytearray_t *int_buff = byte_array_new();
- write_int(int_buff, size);
- byte_array_append(bplist, int_buff->data, int_buff->len);
- byte_array_free(int_buff);
- }
- //stupid unicode buffer length
- if (BPLIST_UNICODE==mark) size *= 2;
- buff = (uint8_t *) malloc(size);
- memcpy(buff, val, size);
- byte_array_append(bplist, buff, size);
- free(buff);
+ if (size >= 15) {
+ write_int(bplist, size);
+ }
+ if (BPLIST_UNICODE==mark) size <<= 1;
+ byte_array_append(bplist, val, size);
}
static void write_data(bytearray_t * bplist, uint8_t * val, uint64_t size)
@@ -817,208 +1195,323 @@ static void write_data(bytearray_t * bplist, uint8_t * val, uint64_t size)
write_raw_data(bplist, BPLIST_DATA, val, size);
}
-static void write_string(bytearray_t * bplist, char *val)
+static void write_string(bytearray_t * bplist, char *val, uint64_t size)
{
- uint64_t size = strlen(val);
write_raw_data(bplist, BPLIST_STRING, (uint8_t *) val, size);
}
-static void write_unicode(bytearray_t * bplist, uint16_t * val, uint64_t size)
+static uint16_t *plist_utf8_to_utf16be(const unsigned char *unistr, size_t size, size_t *items_read, size_t *items_written)
{
- uint64_t i = 0;
- uint64_t size2 = size * sizeof(uint16_t);
- uint8_t *buff = (uint8_t *) malloc(size2);
- memcpy(buff, val, size2);
- for (i = 0; i < size; i++)
- byte_convert(buff + i * sizeof(uint16_t), sizeof(uint16_t));
- write_raw_data(bplist, BPLIST_UNICODE, buff, size);
- free(buff);
+ uint16_t *outbuf;
+ size_t p = 0;
+ size_t i = 0;
+
+ unsigned char c0;
+ unsigned char c1;
+ unsigned char c2;
+ unsigned char c3;
+
+ outbuf = (uint16_t*)malloc(((size*2)+1)*sizeof(uint16_t));
+ if (!outbuf) {
+ PLIST_BIN_ERR("%s: Could not allocate %" PRIu64 " bytes\n", __func__, (uint64_t)((size*2)+1)*sizeof(uint16_t));
+ return NULL;
+ }
+
+ while (i < size) {
+ c0 = unistr[i];
+ c1 = (i+1 < size) ? unistr[i+1] : 0;
+ c2 = (i+2 < size) ? unistr[i+2] : 0;
+ c3 = (i+3 < size) ? unistr[i+3] : 0;
+ if ((c0 >= 0xF0 && c0 <= 0xF4) && (i+3 < size) && ((c1 & 0xC0) == 0x80) && ((c2 & 0xC0) == 0x80) && ((c3 & 0xC0) == 0x80)) {
+ // 4 byte sequence. Need to generate UTF-16 surrogate pair
+ /* lead-specific second-byte constraints */
+ if ((c0 == 0xF0 && c1 < 0x90) || /* overlong (< U+10000) */
+ (c0 == 0xF4 && c1 > 0x8F)) /* > U+10FFFF */
+ {
+ break;
+ }
+ uint32_t w = ((uint32_t)(c3 & 0x3F)) | ((uint32_t)(c2 & 0x3F) << 6) | ((uint32_t)(c1 & 0x3F) << 12) | ((uint32_t)(c0 & 0x07) << 18);
+ if (w < 0x10000 || w > 0x10FFFF) break;
+ w -= 0x10000;
+ outbuf[p++] = be16toh((uint16_t)(0xD800 + (w >> 10)));
+ outbuf[p++] = be16toh((uint16_t)(0xDC00 + (w & 0x3FF)));
+ i+=4;
+ } else if (((c0 & 0xF0) == 0xE0) && (i+2 < size) && ((c1 & 0xC0) == 0x80) && ((c2 & 0xC0) == 0x80)) {
+ // 3 byte sequence
+ if ((c0 == 0xE0 && c1 < 0xA0) || /* overlong (< U+0800) */
+ (c0 == 0xED && c1 > 0x9F)) /* UTF-16 surrogate range */
+ {
+ break;
+ }
+ uint32_t w = ((uint32_t)(c2 & 0x3F)) | ((uint32_t)(c1 & 0x3F) << 6) | ((uint32_t)(c0 & 0x0F) << 12);
+ if (w < 0x800) break;
+ if (w >= 0xD800 && w <= 0xDFFF) break; // invalid Unicode scalar values
+ outbuf[p++] = be16toh((uint16_t)w);
+ i+=3;
+ } else if ((c0 >= 0xC2 && c0 <= 0xDF) && (i+1 < size) && ((c1 & 0xC0) == 0x80)) {
+ // 2 byte sequence
+ uint32_t w = ((uint32_t)(c1 & 0x3F)) | ((uint32_t)(c0 & 0x1F) << 6);
+ outbuf[p++] = be16toh((uint16_t)w);
+ i+=2;
+ } else if (c0 < 0x80) {
+ // 1 byte sequence
+ outbuf[p++] = be16toh((uint16_t)c0);
+ i+=1;
+ } else {
+ // invalid character
+ PLIST_BIN_ERR("%s: invalid utf8 sequence in string at index %zu\n", __func__, i);
+ break;
+ }
+ }
+ if (items_read) {
+ *items_read = i;
+ }
+ if (items_written) {
+ *items_written = p;
+ }
+ outbuf[p] = 0;
+
+ return outbuf;
}
-static void write_array(bytearray_t * bplist, node_t* node, hashtable_t* ref_table, uint8_t dict_param_size)
+static void write_unicode(bytearray_t * bplist, char *val, size_t size)
{
- uint64_t idx = 0;
- uint8_t *buff = NULL;
+ size_t items_read = 0;
+ size_t items_written = 0;
+ uint16_t *unicodestr = NULL;
+
+ unicodestr = plist_utf8_to_utf16be((const unsigned char *)val, size, &items_read, &items_written);
+ write_raw_data(bplist, BPLIST_UNICODE, (uint8_t*)unicodestr, items_written);
+ free(unicodestr);
+}
- node_t* cur = NULL;
+static void write_array(bytearray_t * bplist, node_t node, hashtable_t* ref_table, uint8_t ref_size)
+{
+ node_t cur = NULL;
uint64_t i = 0;
uint64_t size = node_n_children(node);
uint8_t marker = BPLIST_ARRAY | (size < 15 ? size : 0xf);
byte_array_append(bplist, &marker, sizeof(uint8_t));
- if (size >= 15)
- {
- bytearray_t *int_buff = byte_array_new();
- write_int(int_buff, size);
- byte_array_append(bplist, int_buff->data, int_buff->len);
- byte_array_free(int_buff);
+ if (size >= 15) {
+ write_int(bplist, size);
}
- buff = (uint8_t *) malloc(size * dict_param_size);
-
- for (i = 0, cur = node_first_child(node); cur && i < size; cur = node_next_sibling(cur), i++)
- {
- idx = *(uint64_t *) (hash_table_lookup(ref_table, cur));
-#if PLIST_BYTE_ORDER == PLIST_BIG_ENDIAN
- idx = idx << ((sizeof(uint64_t) - dict_param_size) * 8);
-#endif
- memcpy(buff + i * dict_param_size, &idx, dict_param_size);
- byte_convert(buff + i * dict_param_size, dict_param_size);
+ for (i = 0, cur = node_first_child(node); cur && i < size; cur = node_next_sibling(cur), i++) {
+ uint64_t idx = *(uint64_t *) (hash_table_lookup(ref_table, cur));
+ idx = be64toh(idx);
+ byte_array_append(bplist, (uint8_t*)&idx + (sizeof(uint64_t) - ref_size), ref_size);
}
-
- //now append to bplist
- byte_array_append(bplist, buff, size * dict_param_size);
- free(buff);
-
}
-static void write_dict(bytearray_t * bplist, node_t* node, hashtable_t* ref_table, uint8_t dict_param_size)
+static void write_dict(bytearray_t * bplist, node_t node, hashtable_t* ref_table, uint8_t ref_size)
{
- uint64_t idx1 = 0;
- uint64_t idx2 = 0;
- uint8_t *buff = NULL;
-
- node_t* cur = NULL;
+ node_t cur = NULL;
uint64_t i = 0;
uint64_t size = node_n_children(node) / 2;
uint8_t marker = BPLIST_DICT | (size < 15 ? size : 0xf);
byte_array_append(bplist, &marker, sizeof(uint8_t));
- if (size >= 15)
- {
- bytearray_t *int_buff = byte_array_new();
- write_int(int_buff, size);
- byte_array_append(bplist, int_buff->data, int_buff->len);
- byte_array_free(int_buff);
+ if (size >= 15) {
+ write_int(bplist, size);
}
- buff = (uint8_t *) malloc(size * 2 * dict_param_size);
- for (i = 0, cur = node_first_child(node); cur && i < size; cur = node_next_sibling(node_next_sibling(cur)), i++)
- {
- idx1 = *(uint64_t *) (hash_table_lookup(ref_table, cur));
-#if PLIST_BYTE_ORDER == PLIST_BIG_ENDIAN
- idx1 = idx1 << ((sizeof(uint64_t) - dict_param_size) * 8);
-#endif
- memcpy(buff + i * dict_param_size, &idx1, dict_param_size);
- byte_convert(buff + i * dict_param_size, dict_param_size);
-
- idx2 = *(uint64_t *) (hash_table_lookup(ref_table, cur->next));
-#if PLIST_BYTE_ORDER == PLIST_BIG_ENDIAN
- idx2 = idx2 << ((sizeof(uint64_t) - dict_param_size) * 8);
-#endif
- memcpy(buff + (i + size) * dict_param_size, &idx2, dict_param_size);
- byte_convert(buff + (i + size) * dict_param_size, dict_param_size);
+ for (i = 0, cur = node_first_child(node); cur && i < size; cur = node_next_sibling(node_next_sibling(cur)), i++) {
+ uint64_t idx1 = *(uint64_t *) (hash_table_lookup(ref_table, cur));
+ idx1 = be64toh(idx1);
+ byte_array_append(bplist, (uint8_t*)&idx1 + (sizeof(uint64_t) - ref_size), ref_size);
}
- //now append to bplist
- byte_array_append(bplist, buff, size * 2 * dict_param_size);
- free(buff);
-
+ for (i = 0, cur = node_first_child(node); cur && i < size; cur = node_next_sibling(node_next_sibling(cur)), i++) {
+ uint64_t idx2 = *(uint64_t *) (hash_table_lookup(ref_table, cur->next));
+ idx2 = be64toh(idx2);
+ byte_array_append(bplist, (uint8_t*)&idx2 + (sizeof(uint64_t) - ref_size), ref_size);
+ }
}
-static int is_ascii_string(char* s, int len)
+static void write_uid(bytearray_t * bplist, uint64_t val)
{
- int ret = 1, i = 0;
- for(i = 0; i < len; i++)
- {
- if ( !isascii( s[i] ) )
- {
- ret = 0;
- break;
- }
- }
- return ret;
+ val = (uint32_t)val;
+ int size = get_needed_bytes(val);
+ uint8_t sz;
+ //do not write 3bytes int node
+ if (size == 3)
+ size++;
+ sz = BPLIST_UID | (size-1); // yes, this is what Apple does...
+
+ val = be64toh(val);
+ byte_array_append(bplist, &sz, 1);
+ byte_array_append(bplist, (uint8_t*)&val + (8-size), size);
}
-uint16_t *plist_utf8_to_utf16(char *unistr, long size, long *items_read, long *items_written)
+static int is_ascii_string(const char* s, size_t len)
{
- uint16_t *outbuf = (uint16_t*)malloc((size+1)*sizeof(uint16_t));
- int p = 0;
- int i = 0;
-
- unsigned char c0;
- unsigned char c1;
- unsigned char c2;
-
- while (i < size) {
- c0 = unistr[i];
- c1 = (i < size-1) ? unistr[i+1] : 0;
- c2 = (i < size-2) ? unistr[i+2] : 0;
- if ((c0 >= 0xE0) && (i < size-2) && (c1 >= 0x80) && (c2 >= 0x80)) {
- // 3 byte sequence
- outbuf[p++] = ((c2 & 0x3F) + ((c1 & 3) << 6)) + (((c1 >> 2) & 15) << 8) + ((c0 & 15) << 12);
- i+=3;
- } else if ((c0 >= 0xC0) && (i < size-1) && (c1 >= 0x80)) {
- // 2 byte sequence
- outbuf[p++] = ((c1 & 0x3F) + ((c0 & 3) << 6)) + (((c0 >> 2) & 7) << 8);
- i+=2;
- } else if (c0 < 0x80) {
- // 1 byte sequence
- outbuf[p++] = c0;
- i+=1;
- } else {
- // invalid character
- fprintf(stderr, "invalid utf8 sequence in string at index %d\n", i);
- break;
- }
- }
- if (items_read) {
- *items_read = i;
- }
- if (items_written) {
- *items_written = p;
- }
- outbuf[p] = 0;
-
- return outbuf;
-
+ int ret = 1;
+ size_t i = 0;
+ for (i = 0; i < len; i++) {
+ if ( !isascii( s[i] ) ) {
+ ret = 0;
+ break;
+ }
+ }
+ return ret;
}
-void plist_to_bin(plist_t plist, char **plist_bin, uint32_t * length)
+plist_err_t plist_to_bin(plist_t plist, char **plist_bin, uint32_t * length)
{
ptrarray_t* objects = NULL;
hashtable_t* ref_table = NULL;
+ hashtable_t* in_stack = NULL;
struct serialize_s ser_s;
uint8_t offset_size = 0;
- uint8_t dict_param_size = 0;
+ uint8_t ref_size = 0;
uint64_t num_objects = 0;
uint64_t root_object = 0;
uint64_t offset_table_index = 0;
bytearray_t *bplist_buff = NULL;
uint64_t i = 0;
- uint8_t *buff = NULL;
+ uint64_t buff_len = 0;
uint64_t *offsets = NULL;
- uint8_t pad[6] = { 0, 0, 0, 0, 0, 0 };
- uint8_t trailer[BPLIST_TRL_SIZE];
- //for string
- long len = 0;
- long items_read = 0;
- long items_written = 0;
- uint16_t *unicodestr = NULL;
+ bplist_trailer_t trailer;
+ uint64_t objects_len = 0;
//check for valid input
- if (!plist || !plist_bin || *plist_bin || !length)
- return;
+ if (!plist || !plist_bin || !length) {
+ return PLIST_ERR_INVALID_ARG;
+ }
//list of objects
- objects = ptr_array_new(256);
+ objects = ptr_array_new(4096);
+ if (!objects) {
+ return PLIST_ERR_NO_MEM;
+ }
//hashtable to write only once same nodes
- ref_table = hash_table_new(plist_data_hash, plist_data_compare);
+ ref_table = hash_table_new(plist_data_hash, plist_data_compare, free);
+ if (!ref_table) {
+ ptr_array_free(objects);
+ return PLIST_ERR_NO_MEM;
+ }
+ //hashtable for circular reference detection
+ in_stack = hash_table_new(plist_node_ptr_hash, plist_node_ptr_compare, NULL);
+ if (!in_stack) {
+ ptr_array_free(objects);
+ hash_table_destroy(ref_table);
+ return PLIST_ERR_NO_MEM;
+ }
//serialize plist
ser_s.objects = objects;
ser_s.ref_table = ref_table;
- serialize_plist(plist, &ser_s);
+ ser_s.in_stack = in_stack;
+ plist_err_t err = serialize_plist((node_t)plist, &ser_s, 0);
+ if (err != PLIST_ERR_SUCCESS) {
+ ptr_array_free(objects);
+ hash_table_destroy(ref_table);
+ hash_table_destroy(in_stack);
+ return err;
+ }
+ //no longer needed
+ hash_table_destroy(in_stack);
+ ser_s.in_stack = NULL;
//now stream to output buffer
offset_size = 0; //unknown yet
- dict_param_size = get_needed_bytes(objects->len);
+ objects_len = objects->len;
+ ref_size = get_needed_bytes(objects_len);
num_objects = objects->len;
root_object = 0; //root is first in list
offset_table_index = 0; //unknown yet
+ //figure out the storage size required
+ uint64_t req = 0;
+ for (i = 0; i < num_objects; i++)
+ {
+ node_t node = (node_t)ptr_array_index(objects, i);
+ plist_data_t data = plist_get_data(node);
+ uint64_t size;
+ uint8_t bsize;
+ switch (data->type)
+ {
+ case PLIST_NULL:
+ case PLIST_BOOLEAN:
+ req += 1;
+ break;
+ case PLIST_KEY:
+ case PLIST_STRING:
+ req += 1;
+ if (data->length >= 15) {
+ bsize = get_needed_bytes(data->length);
+ if (bsize == 3) bsize = 4;
+ req += 1;
+ req += bsize;
+ }
+ if ( is_ascii_string(data->strval, data->length) )
+ {
+ req += data->length;
+ }
+ else
+ {
+ req += data->length * 2;
+ }
+ break;
+ case PLIST_REAL:
+ size = get_real_bytes(data->realval);
+ req += 1;
+ req += size;
+ break;
+ case PLIST_DATE:
+ req += 9;
+ break;
+ case PLIST_ARRAY:
+ size = node_n_children(node);
+ req += 1;
+ if (size >= 15) {
+ bsize = get_needed_bytes(size);
+ if (bsize == 3) bsize = 4;
+ req += 1;
+ req += bsize;
+ }
+ req += size * ref_size;
+ break;
+ case PLIST_DICT:
+ size = node_n_children(node) / 2;
+ req += 1;
+ if (size >= 15) {
+ bsize = get_needed_bytes(size);
+ if (bsize == 3) bsize = 4;
+ req += 1;
+ req += bsize;
+ }
+ req += size * 2 * ref_size;
+ break;
+ default:
+ size = data->length;
+ req += 1;
+ if (size >= 15) {
+ bsize = get_needed_bytes(size);
+ if (bsize == 3) bsize = 4;
+ req += 1;
+ req += bsize;
+ }
+ req += data->length;
+ break;
+ }
+ }
+ // add size of magic
+ req += BPLIST_MAGIC_SIZE;
+ req += BPLIST_VERSION_SIZE;
+ // add size of offset table
+ req += get_needed_bytes(req) * num_objects;
+ // add size of trailer
+ req += sizeof(bplist_trailer_t);
+
//setup a dynamic bytes array to store bplist in
- bplist_buff = byte_array_new();
+ bplist_buff = byte_array_new(req);
+ if (!bplist_buff) {
+ ptr_array_free(objects);
+ hash_table_destroy(ref_table);
+ return PLIST_ERR_NO_MEM;
+ }
//set magic number and version
byte_array_append(bplist_buff, BPLIST_MAGIC, BPLIST_MAGIC_SIZE);
@@ -1026,6 +1519,9 @@ void plist_to_bin(plist_t plist, char **plist_bin, uint32_t * length)
//write objects and table
offsets = (uint64_t *) malloc(num_objects * sizeof(uint64_t));
+ if (!offsets) {
+ return PLIST_ERR_NO_MEM;
+ }
for (i = 0; i < num_objects; i++)
{
@@ -1034,15 +1530,22 @@ void plist_to_bin(plist_t plist, char **plist_bin, uint32_t * length)
switch (data->type)
{
- case PLIST_BOOLEAN:
- buff = (uint8_t *) malloc(sizeof(uint8_t));
- buff[0] = data->boolval ? BPLIST_TRUE : BPLIST_FALSE;
- byte_array_append(bplist_buff, buff, sizeof(uint8_t));
- free(buff);
+ case PLIST_NULL: {
+ uint8_t b = 0;
+ byte_array_append(bplist_buff, &b, 1);
break;
-
- case PLIST_UINT:
- write_int(bplist_buff, data->intval);
+ }
+ case PLIST_BOOLEAN: {
+ uint8_t b = data->boolval ? BPLIST_TRUE : BPLIST_FALSE;
+ byte_array_append(bplist_buff, &b, 1);
+ break;
+ }
+ case PLIST_INT:
+ if (data->length == 16) {
+ write_uint(bplist_buff, data->intval);
+ } else {
+ write_int(bplist_buff, data->intval);
+ }
break;
case PLIST_REAL:
@@ -1051,28 +1554,29 @@ void plist_to_bin(plist_t plist, char **plist_bin, uint32_t * length)
case PLIST_KEY:
case PLIST_STRING:
- len = strlen(data->strval);
- if ( is_ascii_string(data->strval, len) )
+ if ( is_ascii_string(data->strval, data->length) )
{
- write_string(bplist_buff, data->strval);
+ write_string(bplist_buff, data->strval, data->length);
}
else
{
- unicodestr = plist_utf8_to_utf16(data->strval, len, &items_read, &items_written);
- write_unicode(bplist_buff, unicodestr, items_written);
- free(unicodestr);
+ write_unicode(bplist_buff, data->strval, data->length);
}
break;
case PLIST_DATA:
write_data(bplist_buff, data->buff, data->length);
+ break;
case PLIST_ARRAY:
- write_array(bplist_buff, ptr_array_index(objects, i), ref_table, dict_param_size);
+ write_array(bplist_buff, (node_t)ptr_array_index(objects, i), ref_table, ref_size);
break;
case PLIST_DICT:
- write_dict(bplist_buff, ptr_array_index(objects, i), ref_table, dict_param_size);
+ write_dict(bplist_buff, (node_t)ptr_array_index(objects, i), ref_table, ref_size);
break;
case PLIST_DATE:
- write_date(bplist_buff, data->timeval.tv_sec + (double) data->timeval.tv_usec / 1000000);
+ write_date(bplist_buff, data->realval);
+ break;
+ case PLIST_UID:
+ write_uid(bplist_buff, data->intval);
break;
default:
break;
@@ -1084,43 +1588,31 @@ void plist_to_bin(plist_t plist, char **plist_bin, uint32_t * length)
hash_table_destroy(ref_table);
//write offsets
- offset_size = get_needed_bytes(bplist_buff->len);
+ buff_len = bplist_buff->len;
+ offset_size = get_needed_bytes(buff_len);
offset_table_index = bplist_buff->len;
- for (i = 0; i < num_objects; i++)
- {
- uint8_t *offsetbuff = (uint8_t *) malloc(offset_size);
-
-#if PLIST_BYTE_ORDER == PLIST_BIG_ENDIAN
- offsets[i] = offsets[i] << ((sizeof(uint64_t) - offset_size) * 8);
-#endif
-
- memcpy(offsetbuff, &offsets[i], offset_size);
- byte_convert(offsetbuff, offset_size);
- byte_array_append(bplist_buff, offsetbuff, offset_size);
- free(offsetbuff);
+ for (i = 0; i < num_objects; i++) {
+ uint64_t offset = be64toh(offsets[i]);
+ byte_array_append(bplist_buff, (uint8_t*)&offset + (sizeof(uint64_t) - offset_size), offset_size);
}
-
- //experimental pad to reflect apple's files
- byte_array_append(bplist_buff, pad, 6);
+ free(offsets);
//setup trailer
- num_objects = be64toh(num_objects);
- root_object = be64toh(root_object);
- offset_table_index = be64toh(offset_table_index);
+ memset(trailer.unused, '\0', sizeof(trailer.unused));
+ trailer.offset_size = offset_size;
+ trailer.ref_size = ref_size;
+ trailer.num_objects = be64toh(num_objects);
+ trailer.root_object_index = be64toh(root_object);
+ trailer.offset_table_offset = be64toh(offset_table_index);
- memcpy(trailer + BPLIST_TRL_OFFSIZE_IDX, &offset_size, sizeof(uint8_t));
- memcpy(trailer + BPLIST_TRL_PARMSIZE_IDX, &dict_param_size, sizeof(uint8_t));
- memcpy(trailer + BPLIST_TRL_NUMOBJ_IDX, &num_objects, sizeof(uint64_t));
- memcpy(trailer + BPLIST_TRL_ROOTOBJ_IDX, &root_object, sizeof(uint64_t));
- memcpy(trailer + BPLIST_TRL_OFFTAB_IDX, &offset_table_index, sizeof(uint64_t));
+ byte_array_append(bplist_buff, &trailer, sizeof(bplist_trailer_t));
- byte_array_append(bplist_buff, trailer, BPLIST_TRL_SIZE);
-
- //duplicate buffer
- *plist_bin = (char *) malloc(bplist_buff->len);
- memcpy(*plist_bin, bplist_buff->data, bplist_buff->len);
+ //set output buffer and size
+ *plist_bin = (char*)bplist_buff->data;
*length = bplist_buff->len;
+ bplist_buff->data = NULL; // make sure we don't free the output buffer
byte_array_free(bplist_buff);
- free(offsets);
+
+ return PLIST_ERR_SUCCESS;
}
diff --git a/src/bytearray.c b/src/bytearray.c
index 0abfe49..39fad5f 100644
--- a/src/bytearray.c
+++ b/src/bytearray.c
@@ -21,12 +21,25 @@
#include <string.h>
#include "bytearray.h"
-bytearray_t *byte_array_new()
+#define PAGE_SIZE 4096
+
+bytearray_t *byte_array_new(size_t initial)
{
bytearray_t *a = (bytearray_t*)malloc(sizeof(bytearray_t));
- a->data = malloc(256);
+ a->capacity = (initial > PAGE_SIZE) ? (initial+(PAGE_SIZE-1)) & (~(PAGE_SIZE-1)) : PAGE_SIZE;
+ a->data = malloc(a->capacity);
a->len = 0;
- a->capacity = 256;
+ a->stream = NULL;
+ return a;
+}
+
+bytearray_t *byte_array_new_for_stream(FILE *stream)
+{
+ bytearray_t *a = (bytearray_t*)malloc(sizeof(bytearray_t));
+ a->capacity = (size_t)-1;
+ a->data = NULL;
+ a->len = 0;
+ a->stream = stream;
return a;
}
@@ -39,14 +52,32 @@ void byte_array_free(bytearray_t *ba)
free(ba);
}
+void byte_array_grow(bytearray_t *ba, size_t amount)
+{
+ if (ba->stream) {
+ return;
+ }
+ size_t increase = (amount > PAGE_SIZE) ? (amount+(PAGE_SIZE-1)) & (~(PAGE_SIZE-1)) : PAGE_SIZE;
+ ba->data = realloc(ba->data, ba->capacity + increase);
+ ba->capacity += increase;
+}
+
void byte_array_append(bytearray_t *ba, void *buf, size_t len)
{
- if (!ba || !ba->data || (len <= 0)) return;
- size_t remaining = ba->capacity-ba->len;
- if (len > remaining) {
- ba->data = realloc(ba->data, ba->capacity + (len - remaining));
- ba->capacity += (len - remaining);
+ if (!ba || (!ba->stream && !ba->data) || (len <= 0)) return;
+ if (ba->stream) {
+ if (fwrite(buf, 1, len, ba->stream) < len) {
+#if DEBUG
+ fprintf(stderr, "ERROR: Failed to write to stream.\n");
+#endif
+ }
+ } else {
+ size_t remaining = ba->capacity-ba->len;
+ if (len > remaining) {
+ size_t needed = len - remaining;
+ byte_array_grow(ba, needed);
+ }
+ memcpy(((char*)ba->data) + ba->len, buf, len);
}
- memcpy(ba->data+ba->len, buf, len);
ba->len += len;
}
diff --git a/src/bytearray.h b/src/bytearray.h
index 3e67d7e..b53e006 100644
--- a/src/bytearray.h
+++ b/src/bytearray.h
@@ -21,16 +21,19 @@
#ifndef BYTEARRAY_H
#define BYTEARRAY_H
#include <stdlib.h>
-#include "common.h"
+#include <stdio.h>
typedef struct bytearray_t {
void *data;
size_t len;
size_t capacity;
+ FILE *stream;
} bytearray_t;
-_PLIST_INTERNAL bytearray_t *byte_array_new();
-_PLIST_INTERNAL void byte_array_free(bytearray_t *ba);
-_PLIST_INTERNAL void byte_array_append(bytearray_t *ba, void *buf, size_t len);
+bytearray_t *byte_array_new(size_t initial);
+bytearray_t *byte_array_new_for_stream(FILE *stream);
+void byte_array_free(bytearray_t *ba);
+void byte_array_grow(bytearray_t *ba, size_t amount);
+void byte_array_append(bytearray_t *ba, void *buf, size_t len);
#endif
diff --git a/src/common.c b/src/common.c
new file mode 100644
index 0000000..810c2e0
--- /dev/null
+++ b/src/common.c
@@ -0,0 +1,115 @@
+/*
+ * 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
+
+int plist_real_to_time64(double realval, Time64_T *timev)
+{
+ if (!timev || !isfinite(realval)) {
+ return -1;
+ }
+
+ if (realval < (double)TIME64_MIN - (double)MAC_EPOCH ||
+ realval > (double)TIME64_MAX - (double)MAC_EPOCH) {
+ return -1;
+ }
+
+ *timev = (Time64_T)realval + MAC_EPOCH;
+ return 0;
+}
diff --git a/src/common.h b/src/common.h
index beb4a2b..ffaa78c 100644
--- a/src/common.h
+++ b/src/common.h
@@ -1,24 +1,34 @@
+/*
+ * 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
-#define PLIST_LITTLE_ENDIAN 0
-#define PLIST_BIG_ENDIAN 1
+#include <stddef.h>
+#include "time64.h"
-#ifndef PLIST_BYTE_ORDER
-#if __BIG_ENDIAN__ == 1
-#define PLIST_BYTE_ORDER PLIST_BIG_ENDIAN
-#endif
-#if __LITTLE_ENDIAN__ == 1
-#define PLIST_BYTE_ORDER PLIST_LITTLE_ENDIAN
-#endif
-#endif
+#define MAC_EPOCH 978307200
-#if defined(__GNUC__) && (__GNUC__ >= 4) && !defined(__CYGWIN__) && !defined(WIN32)
-# define _PLIST_INTERNAL __attribute__((visibility("hidden")))
-#elif defined(__SUNPRO_C) && (__SUNPRO_C >= 0x550)
-# define _PLIST_INTERNAL __hidden
-#else /* not gcc >= 4 and not Sun Studio >= 8 */
-# define _PLIST_INTERNAL
-#endif /* GNUC >= 4 */
+size_t dtostr(char *buf, size_t bufsize, double realval);
+int num_digits_i(int64_t i);
+int num_digits_u(uint64_t i);
+int plist_real_to_time64(double realval, Time64_T *timev);
#endif
diff --git a/src/hashtable.c b/src/hashtable.c
index 08ff934..dd6dbfc 100644
--- a/src/hashtable.c
+++ b/src/hashtable.c
@@ -2,7 +2,7 @@
* hashtable.c
* really simple hash table implementation
*
- * Copyright (c) 2011 Nikias Bassen, All Rights Reserved.
+ * Copyright (c) 2011-2016 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
@@ -20,16 +20,17 @@
*/
#include "hashtable.h"
-hashtable_t* hash_table_new(hash_func_t hash_func, compare_func_t compare_func)
+hashtable_t* hash_table_new(hash_func_t hash_func, compare_func_t compare_func, free_func_t free_func)
{
hashtable_t* ht = (hashtable_t*)malloc(sizeof(hashtable_t));
int i;
- for (i = 0; i < 256; i++) {
+ for (i = 0; i < 4096; i++) {
ht->entries[i] = NULL;
}
ht->count = 0;
ht->hash_func = hash_func;
ht->compare_func = compare_func;
+ ht->free_func = free_func;
return ht;
}
@@ -38,11 +39,13 @@ void hash_table_destroy(hashtable_t *ht)
if (!ht) return;
int i = 0;
- for (i = 0; i < 256; i++) {
+ for (i = 0; i < 4096; i++) {
if (ht->entries[i]) {
hashentry_t* e = ht->entries[i];
while (e) {
- free(e->value);
+ if (ht->free_func) {
+ ht->free_func(e->value);
+ }
hashentry_t* old = e;
e = e->next;
free(old);
@@ -58,7 +61,7 @@ void hash_table_insert(hashtable_t* ht, void *key, void *value)
unsigned int hash = ht->hash_func(key);
- int idx0 = hash & 0xFF;
+ int idx0 = hash & 0xFFF;
// get the idx0 list
hashentry_t* e = ht->entries[idx0];
@@ -93,7 +96,7 @@ void* hash_table_lookup(hashtable_t* ht, void *key)
if (!ht || !key) return NULL;
unsigned int hash = ht->hash_func(key);
- int idx0 = hash & 0xFF;
+ int idx0 = hash & 0xFFF;
hashentry_t* e = ht->entries[idx0];
while (e) {
@@ -104,3 +107,34 @@ void* hash_table_lookup(hashtable_t* ht, void *key)
}
return NULL;
}
+
+void hash_table_remove(hashtable_t* ht, void *key)
+{
+ if (!ht || !key) return;
+
+ unsigned int hash = ht->hash_func(key);
+
+ int idx0 = hash & 0xFFF;
+
+ // get the idx0 list
+ hashentry_t* e = ht->entries[idx0];
+ hashentry_t* last = e;
+ while (e) {
+ if (ht->compare_func(e->key, key)) {
+ // found element, remove it from the list
+ hashentry_t* old = e;
+ if (e == ht->entries[idx0]) {
+ ht->entries[idx0] = e->next;
+ } else {
+ last->next = e->next;
+ }
+ if (ht->free_func) {
+ ht->free_func(old->value);
+ }
+ free(old);
+ return;
+ }
+ last = e;
+ e = e->next;
+ }
+}
diff --git a/src/hashtable.h b/src/hashtable.h
index 8c72c82..514cfec 100644
--- a/src/hashtable.h
+++ b/src/hashtable.h
@@ -2,7 +2,7 @@
* hashtable.h
* header file for really simple hash table implementation
*
- * Copyright (c) 2011 Nikias Bassen, All Rights Reserved.
+ * Copyright (c) 2011-2016 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
@@ -21,28 +21,30 @@
#ifndef HASHTABLE_H
#define HASHTABLE_H
#include <stdlib.h>
-#include "common.h"
typedef struct hashentry_t {
void *key;
void *value;
- void *next;
+ struct hashentry_t *next;
} hashentry_t;
typedef unsigned int(*hash_func_t)(const void* key);
typedef int (*compare_func_t)(const void *a, const void *b);
+typedef void (*free_func_t)(void *ptr);
typedef struct hashtable_t {
- hashentry_t *entries[256];
+ hashentry_t *entries[4096];
size_t count;
hash_func_t hash_func;
compare_func_t compare_func;
+ free_func_t free_func;
} hashtable_t;
-_PLIST_INTERNAL hashtable_t* hash_table_new(hash_func_t hash_func, compare_func_t compare_func);
-_PLIST_INTERNAL void hash_table_destroy(hashtable_t *ht);
+hashtable_t* hash_table_new(hash_func_t hash_func, compare_func_t compare_func, free_func_t free_func);
+void hash_table_destroy(hashtable_t *ht);
-_PLIST_INTERNAL void hash_table_insert(hashtable_t* ht, void *key, void *value);
-_PLIST_INTERNAL void* hash_table_lookup(hashtable_t* ht, void *key);
+void hash_table_insert(hashtable_t* ht, void *key, void *value);
+void* hash_table_lookup(hashtable_t* ht, void *key);
+void hash_table_remove(hashtable_t* ht, void *key);
#endif
diff --git a/src/jplist.c b/src/jplist.c
new file mode 100644
index 0000000..c29f760
--- /dev/null
+++ b/src/jplist.c
@@ -0,0 +1,981 @@
+/*
+ * jplist.c
+ * JSON plist implementation
+ *
+ * Copyright (c) 2019-2021 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
+ */
+
+#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 "jsmn.h"
+#include "hashtable.h"
+#include "base64.h"
+#include "time64.h"
+#include "common.h"
+
+#ifdef DEBUG
+static int plist_json_debug = 0;
+#define PLIST_JSON_ERR(...) if (plist_json_debug) { fprintf(stderr, "libplist[jsonparser] ERROR: " __VA_ARGS__); }
+#define PLIST_JSON_WRITE_ERR(...) if (plist_json_debug) { fprintf(stderr, "libplist[jsonwriter] ERROR: " __VA_ARGS__); }
+#else
+#define PLIST_JSON_ERR(...)
+#define PLIST_JSON_WRITE_ERR(...)
+#endif
+
+void plist_json_init(void)
+{
+ /* init JSON stuff */
+#ifdef DEBUG
+ char *env_debug = getenv("PLIST_JSON_DEBUG");
+ if (env_debug && !strcmp(env_debug, "1")) {
+ plist_json_debug = 1;
+ }
+#endif
+}
+
+void plist_json_deinit(void)
+{
+ /* deinit JSON stuff */
+}
+
+void plist_json_set_debug(int debug)
+{
+#ifdef DEBUG
+ plist_json_debug = debug;
+#endif
+}
+
+#ifndef HAVE_STRNDUP
+#ifndef _MSC_VER
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wshadow"
+#endif
+static char* strndup(const char* str, size_t len)
+{
+ char *newstr = (char *)malloc(len+1);
+ if (newstr) {
+ strncpy(newstr, str, len);
+ newstr[len]= '\0';
+ }
+ return newstr;
+}
+#ifndef _MSC_VER
+#pragma GCC diagnostic pop
+#endif
+#endif
+
+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;
+
+ char *val = NULL;
+ size_t val_len = 0;
+
+ uint32_t i = 0;
+
+ if (!node)
+ return PLIST_ERR_INVALID_ARG;
+
+ node_data = plist_get_data(node);
+
+ switch (node_data->type)
+ {
+ case PLIST_BOOLEAN:
+ {
+ if (node_data->boolval) {
+ str_buf_append(*outbuf, "true", 4);
+ } else {
+ str_buf_append(*outbuf, "false", 5);
+ }
+ }
+ break;
+
+ case PLIST_NULL:
+ str_buf_append(*outbuf, "null", 4);
+ break;
+
+ case PLIST_INT:
+ 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);
+ break;
+
+ case PLIST_REAL:
+ val = (char*)malloc(64);
+ val_len = dtostr(val, 64, node_data->realval);
+ str_buf_append(*outbuf, val, val_len);
+ free(val);
+ break;
+
+ case PLIST_STRING:
+ case PLIST_KEY: {
+ 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;
+ off_t start = 0;
+ off_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;
+ for (ch = node_first_child(node); ch; ch = node_next_sibling(ch)) {
+ if (cnt > 0) {
+ str_buf_append(*outbuf, ",", 1);
+ }
+ if (prettify) {
+ str_buf_append(*outbuf, "\n", 1);
+ for (i = 0; i <= depth; i++) {
+ str_buf_append(*outbuf, " ", 2);
+ }
+ }
+ plist_err_t res = node_to_json(ch, outbuf, depth+1, prettify, coerce);
+ if (res < 0) {
+ return res;
+ }
+ cnt++;
+ }
+ if (cnt > 0 && prettify) {
+ 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;
+ for (ch = node_first_child(node); ch; ch = node_next_sibling(ch)) {
+ if (cnt > 0 && cnt % 2 == 0) {
+ str_buf_append(*outbuf, ",", 1);
+ }
+ if (cnt % 2 == 0 && prettify) {
+ str_buf_append(*outbuf, "\n", 1);
+ for (i = 0; i <= depth; i++) {
+ str_buf_append(*outbuf, " ", 2);
+ }
+ }
+ plist_err_t res = node_to_json(ch, outbuf, depth+1, prettify, coerce);
+ if (res < 0) {
+ return res;
+ }
+ if (cnt % 2 == 0) {
+ str_buf_append(*outbuf, ":", 1);
+ if (prettify) {
+ str_buf_append(*outbuf, " ", 1);
+ }
+ }
+ cnt++;
+ }
+ if (cnt > 0 && prettify) {
+ 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 (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:
+ if (coerce) {
+ Time64_T timev;
+ if (plist_real_to_time64(node_data->realval, &timev) < 0) {
+ PLIST_JSON_WRITE_ERR("Encountered invalid date value %f\n", node_data->realval);
+ return PLIST_ERR_INVALID_ARG;
+ }
+ 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:
+ 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;
+ }
+
+ return PLIST_ERR_SUCCESS;
+}
+
+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) {
+ return PLIST_ERR_INVALID_ARG;
+ }
+
+ if (depth > PLIST_MAX_NESTING_DEPTH) {
+ PLIST_JSON_WRITE_ERR("maximum nesting depth (%u) exceeded\n", (unsigned)PLIST_MAX_NESTING_DEPTH);
+ return PLIST_ERR_MAX_NESTING;
+ }
+
+ if (hash_table_lookup(visited, node)) {
+ PLIST_JSON_WRITE_ERR("circular reference detected\n");
+ return PLIST_ERR_CIRCULAR_REF;
+ }
+
+ // mark as visited
+ 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, prettify, coerce, visited);
+ if (res < 0) {
+ return res;
+ }
+ }
+ switch (data->type) {
+ case PLIST_DICT:
+ *size += 2; // '{' and '}'
+ *size += n_children-1; // number of ':' and ','
+ if (prettify) {
+ *size += n_children; // number of '\n' and extra space
+ *size += (uint64_t)n_children * (depth+1); // indent for every 2nd child
+ *size += 1; // additional '\n'
+ }
+ break;
+ case PLIST_ARRAY:
+ *size += 2; // '[' and ']'
+ *size += n_children-1; // number of ','
+ if (prettify) {
+ *size += n_children; // number of '\n'
+ *size += (uint64_t)n_children * ((depth+1)<<1); // indent for every child
+ *size += 1; // additional '\n'
+ }
+ break;
+ default:
+ break;
+ }
+ if (prettify)
+ *size += (depth << 1); // indent for {} and []
+ } 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 += ((data->boolval) ? 4 : 5);
+ break;
+ case PLIST_NULL:
+ *size += 4;
+ break;
+ case PLIST_DICT:
+ case PLIST_ARRAY:
+ *size += 2;
+ break;
+ case PLIST_DATA:
+ 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:
+ 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:
+ 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;
+ }
+ }
+ return PLIST_ERR_SUCCESS;
+}
+
+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, 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;
+
+ if (!plist || !plist_json || !length) {
+ return PLIST_ERR_INVALID_ARG;
+ }
+
+ if (!PLIST_IS_DICT(plist) && !PLIST_IS_ARRAY(plist)) {
+ PLIST_JSON_WRITE_ERR("plist data is not valid for JSON format\n");
+ return PLIST_ERR_FORMAT;
+ }
+
+ 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;
+ }
+
+ strbuf_t *outbuf = str_buf_new(size);
+ if (!outbuf) {
+ PLIST_JSON_WRITE_ERR("Could not allocate output buffer\n");
+ return PLIST_ERR_NO_MEM;
+ }
+
+ res = node_to_json((node_t)plist, &outbuf, 0, prettify, coerce);
+ if (res < 0) {
+ str_buf_free(outbuf);
+ *plist_json = NULL;
+ *length = 0;
+ return res;
+ }
+ if (prettify) {
+ str_buf_append(outbuf, "\n", 1);
+ }
+
+ str_buf_append(outbuf, "\0", 1);
+
+ *plist_json = (char*)outbuf->data;
+ *length = outbuf->len - 1;
+
+ outbuf->data = NULL;
+ str_buf_free(outbuf);
+
+ return PLIST_ERR_SUCCESS;
+}
+
+typedef struct {
+ jsmntok_t* tokens;
+ int count;
+ plist_err_t err;
+} jsmntok_info_t;
+
+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;
+ *endp = (char*)str;
+
+ if (str[0] == '-') {
+ is_neg = 1;
+ (*endp)++;
+ } else if (str[0] == '+') {
+ (*endp)++;
+ }
+ if (is_neg) {
+ MAX++;
+ }
+ while (*endp < str_end && isdigit(**endp)) {
+ if (x > po10i_limit) {
+ x = MAX;
+ break;
+ }
+ x = x * 10;
+ unsigned int add = (**endp - '0');
+ if (x + add > MAX) {
+ x = MAX;
+ break;
+ }
+ x += add;
+ (*endp)++;
+ }
+
+ // swallow the rest of the digits in case we dropped out early
+ while (*endp < str_end && isdigit(**endp)) (*endp)++;
+
+ int64_t result = x;
+ if (is_neg) {
+ if (x == MAX) {
+ result = INT64_MIN;
+ } else {
+ result = -(int64_t)x;
+ }
+ }
+ return result;
+}
+
+static plist_t parse_primitive(const char* js, jsmntok_info_t* ti, int* index)
+{
+ if (ti->tokens[*index].type != JSMN_PRIMITIVE) {
+ PLIST_JSON_ERR("%s: token type != JSMN_PRIMITIVE\n", __func__);
+ return NULL;
+ }
+ plist_t val = NULL;
+ const char* str_val = js + ti->tokens[*index].start;
+ const char* str_end = js + ti->tokens[*index].end;
+ size_t str_len = ti->tokens[*index].end - ti->tokens[*index].start;
+ if (!strncmp("false", str_val, str_len)) {
+ val = plist_new_bool(0);
+ } else if (!strncmp("true", str_val, str_len)) {
+ val = plist_new_bool(1);
+ } else if (!strncmp("null", str_val, str_len)) {
+ plist_data_t data = plist_new_plist_data();
+ if (!data) {
+ PLIST_JSON_ERR("%s: failed to allocate plist data\n", __func__);
+ return NULL;
+ }
+ data->type = PLIST_NULL;
+ val = plist_new_node(data);
+ } else if (isdigit(str_val[0]) || (str_val[0] == '-' && str_val+1 < str_end && isdigit(str_val[1]))) {
+ char* endp = (char*)str_val;
+ int is_neg = (str_val[0] == '-');
+ int64_t intpart = parse_decimal(str_val, str_end, &endp);
+ if (endp >= str_end) {
+ /* integer */
+ if (is_neg || intpart <= INT64_MAX) {
+ val = plist_new_int(intpart);
+ } else {
+ val = plist_new_uint((uint64_t)intpart);
+ }
+ } else if ((*endp == '.' && endp+1 < str_end && isdigit(*(endp+1))) || ((*endp == 'e' || *endp == 'E') && endp+1 < str_end && (isdigit(*(endp+1)) || (((*(endp+1) == '-') || (*(endp+1) == '+')) && endp+2 < str_end && isdigit(*(endp+2)))))) {
+ /* floating point */
+ double dval = (double)intpart;
+ char* fendp = endp;
+ int err = 0;
+ do {
+ if (*endp == '.') {
+ fendp++;
+ double frac = 0;
+ double p = 0.1;
+ while (fendp < str_end && isdigit(*fendp)) {
+ frac = frac + (*fendp - '0') * p;
+ p *= 0.1;
+ fendp++;
+ }
+ if (is_neg) {
+ dval -= frac;
+ } else {
+ dval += frac;
+ }
+ }
+ if (fendp >= str_end) {
+ break;
+ }
+ if (fendp+1 < str_end && (*fendp == 'e' || *fendp == 'E') && (isdigit(*(fendp+1)) || (((*(fendp+1) == '-') || (*(fendp+1) == '+')) && fendp+2 < str_end && isdigit(*(fendp+2))))) {
+ int64_t exp = parse_decimal(fendp+1, str_end, &fendp);
+ dval = dval * pow(10, (double)exp);
+ } else {
+ PLIST_JSON_ERR("%s: invalid character at offset %d when parsing floating point value\n", __func__, (int)(fendp - js));
+ err++;
+ }
+ } while (0);
+ if (!err) {
+ if (isinf(dval) || isnan(dval)) {
+ PLIST_JSON_ERR("%s: unrepresentable floating point value at offset %d when parsing numerical value\n", __func__, (int)(str_val - js));
+ } else {
+ val = plist_new_real(dval);
+ }
+ }
+ } else {
+ PLIST_JSON_ERR("%s: invalid character at offset %d when parsing numerical value\n", __func__, (int)(endp - js));
+ }
+ } else {
+ PLIST_JSON_ERR("%s: invalid primitive value '%.*s' encountered\n", __func__, (int)str_len, str_val);
+ }
+ if (!val) {
+ PLIST_JSON_ERR("%s: failed to create node\n", __func__);
+ return NULL;
+ }
+ (*index)++;
+ return val;
+}
+
+static char* unescape_string(const char* str_val, size_t str_len, size_t *new_len)
+{
+ char* strval = strndup(str_val, str_len);
+ if (!strval) return NULL;
+ size_t i = 0;
+ while (i < str_len) {
+ if (strval[i] == '\\' && i < str_len-1) {
+ switch (strval[i+1]) {
+ case '\"': case '/' : case '\\' : case 'b' :
+ case 'f' : case 'r' : case 'n' : case 't' :
+ memmove(strval+i, strval+i+1, str_len - (i+1));
+ str_len--;
+ switch (strval[i]) {
+ case 'b':
+ strval[i] = '\b';
+ break;
+ case 'f':
+ strval[i] = '\f';
+ break;
+ case 'r':
+ strval[i] = '\r';
+ break;
+ case 'n':
+ strval[i] = '\n';
+ break;
+ case 't':
+ strval[i] = '\t';
+ break;
+ default:
+ break;
+ }
+ break;
+ case 'u': {
+ unsigned int val = 0;
+ if (str_len-(i+2) < 4) {
+ PLIST_JSON_ERR("%s: invalid escape sequence '%s' (too short)\n", __func__, strval+i);
+ free(strval);
+ return NULL;
+ }
+ if (!(isxdigit(strval[i+2]) && isxdigit(strval[i+3]) && isxdigit(strval[i+4]) && isxdigit(strval[i+5])) || sscanf(strval+i+2, "%04x", &val) != 1) {
+ PLIST_JSON_ERR("%s: invalid escape sequence '%.*s'\n", __func__, 6, strval+i);
+ free(strval);
+ return NULL;
+ }
+ int bytelen = 0;
+ if (val >= 0x800) {
+ /* three bytes */
+ strval[i] = (char)(0xE0 + ((val >> 12) & 0xF));
+ strval[i+1] = (char)(0x80 + ((val >> 6) & 0x3F));
+ strval[i+2] = (char)(0x80 + (val & 0x3F));
+ bytelen = 3;
+ } else if (val >= 0x80) {
+ /* two bytes */
+ strval[i] = (char)(0xC0 + ((val >> 6) & 0x1F));
+ strval[i+1] = (char)(0x80 + (val & 0x3F));
+ bytelen = 2;
+ } else {
+ /* one byte */
+ strval[i] = (char)(val & 0x7F);
+ bytelen = 1;
+ }
+ memmove(strval+i+bytelen, strval+i+6, str_len - (i+5));
+ str_len -= (6-bytelen);
+ } break;
+ default:
+ PLIST_JSON_ERR("%s: invalid escape sequence '%.*s'\n", __func__, 2, strval+i);
+ free(strval);
+ return NULL;
+ }
+ }
+ i++;
+ }
+ strval[str_len] = '\0';
+ if (new_len) {
+ *new_len = str_len;
+ }
+ return strval;
+}
+
+static plist_t parse_string(const char* js, jsmntok_info_t* ti, int* index)
+{
+ if (ti->tokens[*index].type != JSMN_STRING) {
+ PLIST_JSON_ERR("%s: token type != JSMN_STRING\n", __func__);
+ return NULL;
+ }
+
+ size_t str_len = 0; ;
+ char* strval = unescape_string(js + ti->tokens[*index].start, ti->tokens[*index].end - ti->tokens[*index].start, &str_len);
+ if (!strval) {
+ return NULL;
+ }
+ plist_t node;
+
+ plist_data_t data = plist_new_plist_data();
+ if (!data) {
+ free(strval);
+ PLIST_JSON_ERR("%s: failed to allocate plist data\n", __func__);
+ return NULL;
+ }
+ data->type = PLIST_STRING;
+ data->strval = strval;
+ data->length = str_len;
+ node = plist_new_node(data);
+ if (!node) {
+ plist_free_data(data);
+ PLIST_JSON_ERR("%s: failed to create node\n", __func__);
+ return NULL;
+ }
+
+ (*index)++;
+ return node;
+}
+
+static plist_t parse_object(const char* js, jsmntok_info_t* ti, int* index, uint32_t depth);
+
+static plist_t parse_array(const char* js, jsmntok_info_t* ti, int* index, uint32_t depth)
+{
+ if (ti->tokens[*index].type != JSMN_ARRAY) {
+ PLIST_JSON_ERR("%s: token type != JSMN_ARRAY\n", __func__);
+ ti->err = PLIST_ERR_PARSE;
+ return NULL;
+ }
+ if (depth > PLIST_MAX_NESTING_DEPTH) {
+ PLIST_JSON_ERR("%s: maximum nesting depth (%u) exceeded\n", __func__, (unsigned)PLIST_MAX_NESTING_DEPTH);
+ ti->err = PLIST_ERR_MAX_NESTING;
+ return NULL;
+ }
+ plist_t arr = plist_new_array();
+ if (!arr) {
+ PLIST_JSON_ERR("%s: failed to create array node\n", __func__);
+ ti->err = PLIST_ERR_NO_MEM;
+ return NULL;
+ }
+ size_t num_tokens = ti->tokens[*index].size;
+ size_t num;
+ int j = (*index)+1;
+ for (num = 0; num < num_tokens; num++) {
+ if (j >= ti->count) {
+ PLIST_JSON_ERR("%s: token index out of valid range\n", __func__);
+ plist_free(arr);
+ ti->err = PLIST_ERR_PARSE;
+ return NULL;
+ }
+ plist_t val = NULL;
+ switch (ti->tokens[j].type) {
+ case JSMN_OBJECT:
+ val = parse_object(js, ti, &j, depth+1);
+ break;
+ case JSMN_ARRAY:
+ val = parse_array(js, ti, &j, depth+1);
+ break;
+ case JSMN_STRING:
+ val = parse_string(js, ti, &j);
+ break;
+ case JSMN_PRIMITIVE:
+ val = parse_primitive(js, ti, &j);
+ break;
+ default:
+ break;
+ }
+ if (val) {
+ plist_array_append_item(arr, val);
+ // if append failed, val still has no parent, free it and abort
+ if (((node_t)val)->parent == NULL) {
+ plist_free(val);
+ plist_free(arr);
+ ti->err = PLIST_ERR_NO_MEM;
+ return NULL;
+ }
+ } else {
+ plist_free(arr);
+ ti->err = PLIST_ERR_PARSE;
+ return NULL;
+ }
+ }
+ *(index) = j;
+ return arr;
+}
+
+static plist_t parse_object(const char* js, jsmntok_info_t* ti, int* index, uint32_t depth)
+{
+ if (ti->tokens[*index].type != JSMN_OBJECT) {
+ PLIST_JSON_ERR("%s: token type != JSMN_OBJECT\n", __func__);
+ ti->err = PLIST_ERR_PARSE;
+ return NULL;
+ }
+ if (depth > PLIST_MAX_NESTING_DEPTH) {
+ PLIST_JSON_ERR("%s: maximum nesting depth (%u) exceeded\n", __func__, (unsigned)PLIST_MAX_NESTING_DEPTH);
+ ti->err = PLIST_ERR_MAX_NESTING;
+ return NULL;
+ }
+ size_t num_tokens = ti->tokens[*index].size;
+ size_t num;
+ int j = (*index)+1;
+ if (num_tokens % 2 != 0) {
+ PLIST_JSON_ERR("%s: number of children must be even\n", __func__);
+ ti->err = PLIST_ERR_PARSE;
+ return NULL;
+ }
+ plist_t obj = plist_new_dict();
+ if (!obj) {
+ PLIST_JSON_ERR("%s: failed to create dict node\n", __func__);
+ ti->err = PLIST_ERR_NO_MEM;
+ return NULL;
+ }
+ for (num = 0; num < num_tokens; num++) {
+ if (j+1 >= ti->count) {
+ PLIST_JSON_ERR("%s: token index out of valid range\n", __func__);
+ plist_free(obj);
+ ti->err = PLIST_ERR_PARSE;
+ return NULL;
+ }
+ if (ti->tokens[j].type == JSMN_STRING) {
+ char* key = unescape_string(js + ti->tokens[j].start, ti->tokens[j].end - ti->tokens[j].start, NULL);
+ if (!key) {
+ plist_free(obj);
+ ti->err = PLIST_ERR_PARSE;
+ return NULL;
+ }
+ plist_t val = NULL;
+ j++;
+ num++;
+ switch (ti->tokens[j].type) {
+ case JSMN_OBJECT:
+ val = parse_object(js, ti, &j, depth+1);
+ break;
+ case JSMN_ARRAY:
+ val = parse_array(js, ti, &j, depth+1);
+ break;
+ case JSMN_STRING:
+ val = parse_string(js, ti, &j);
+ break;
+ case JSMN_PRIMITIVE:
+ val = parse_primitive(js, ti, &j);
+ break;
+ default:
+ break;
+ }
+ if (val) {
+ plist_dict_set_item(obj, key, val);
+ // if set failed, val still has no parent, free it and abort
+ if (((node_t)val)->parent == NULL) {
+ plist_free(val);
+ free(key);
+ plist_free(obj);
+ ti->err = PLIST_ERR_NO_MEM;
+ return NULL;
+ }
+ } else {
+ free(key);
+ plist_free(obj);
+ ti->err = PLIST_ERR_PARSE;
+ return NULL;
+ }
+ free(key);
+ } else {
+ PLIST_JSON_ERR("%s: keys must be of type STRING\n", __func__);
+ plist_free(obj);
+ ti->err = PLIST_ERR_PARSE;
+ return NULL;
+ }
+ }
+ (*index) = j;
+ return obj;
+}
+
+plist_err_t plist_from_json(const char *json, uint32_t length, plist_t * plist)
+{
+ if (!plist) {
+ return PLIST_ERR_INVALID_ARG;
+ }
+ *plist = NULL;
+ if (!json || (length == 0)) {
+ return PLIST_ERR_INVALID_ARG;
+ }
+
+ jsmn_parser parser;
+ jsmn_init(&parser);
+ unsigned int maxtoks = 256;
+ unsigned int curtoks = 0;
+ int r = 0;
+ jsmntok_t *tokens = NULL;
+
+ do {
+ jsmntok_t* newtokens = (jsmntok_t*)realloc(tokens, sizeof(jsmntok_t)*maxtoks);
+ if (!newtokens) {
+ free(tokens);
+ PLIST_JSON_ERR("%s: Out of memory\n", __func__);
+ return PLIST_ERR_NO_MEM;
+ }
+ memset((unsigned char*)newtokens + sizeof(jsmntok_t)*curtoks, '\0', sizeof(jsmntok_t)*(maxtoks-curtoks));
+ tokens = newtokens;
+ curtoks = maxtoks;
+
+ r = jsmn_parse(&parser, json, length, tokens, maxtoks);
+ if (r == JSMN_ERROR_NOMEM) {
+ if (maxtoks > (unsigned int)INT_MAX - 16) {
+ free(tokens);
+ return PLIST_ERR_NO_MEM;
+ }
+ maxtoks+=16;
+ continue;
+ } else if (r < 0) {
+ break;
+ }
+ } while (r == JSMN_ERROR_NOMEM);
+
+ switch(r) {
+ case JSMN_ERROR_NOMEM:
+ PLIST_JSON_ERR("%s: Out of memory...\n", __func__);
+ free(tokens);
+ return PLIST_ERR_NO_MEM;
+ case JSMN_ERROR_INVAL:
+ PLIST_JSON_ERR("%s: Invalid character inside JSON string\n", __func__);
+ free(tokens);
+ return PLIST_ERR_PARSE;
+ case JSMN_ERROR_PART:
+ PLIST_JSON_ERR("%s: Incomplete JSON, more bytes expected\n", __func__);
+ free(tokens);
+ return PLIST_ERR_PARSE;
+ case JSMN_ERROR_LIMIT:
+ PLIST_JSON_ERR("%s: Input data too large\n", __func__);
+ free(tokens);
+ return PLIST_ERR_PARSE;
+ default:
+ break;
+ }
+
+ int startindex = 0;
+ jsmntok_info_t ti = { tokens, parser.toknext, PLIST_ERR_SUCCESS };
+ switch (tokens[startindex].type) {
+ case JSMN_PRIMITIVE:
+ *plist = parse_primitive(json, &ti, &startindex);
+ break;
+ case JSMN_STRING:
+ *plist = parse_string(json, &ti, &startindex);
+ break;
+ case JSMN_ARRAY:
+ *plist = parse_array(json, &ti, &startindex, 0);
+ break;
+ case JSMN_OBJECT:
+ *plist = parse_object(json, &ti, &startindex, 0);
+ break;
+ default:
+ break;
+ }
+ free(tokens);
+ if (!*plist) {
+ return (ti.err != PLIST_ERR_SUCCESS) ? ti.err : PLIST_ERR_PARSE;
+ }
+ return PLIST_ERR_SUCCESS;
+}
diff --git a/src/jsmn.c b/src/jsmn.c
new file mode 100644
index 0000000..889b8d7
--- /dev/null
+++ b/src/jsmn.c
@@ -0,0 +1,306 @@
+/*
+ * jsmn.c
+ * Simple JSON parser
+ *
+ * Copyright (c) 2010 Serge A. Zaitsev
+ * Updated to use size_t for token offsets and harden against overflows.
+ * (Nikias Bassen, January 2026)
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include <stdlib.h>
+#include <stdint.h>
+#include <limits.h>
+#include <assert.h>
+
+#include "jsmn.h"
+
+#define JSMN_POS_INVALID ((size_t)SIZE_MAX)
+
+/**
+ * Allocates a fresh unused token from the token pull.
+ */
+static jsmntok_t *jsmn_alloc_token(jsmn_parser *parser,
+ jsmntok_t *tokens, unsigned int num_tokens) {
+ jsmntok_t *tok;
+ if ((unsigned int)parser->toknext >= num_tokens) {
+ return NULL;
+ }
+ tok = &tokens[parser->toknext++];
+ tok->start = tok->end = JSMN_POS_INVALID;
+ tok->size = 0;
+#ifdef JSMN_PARENT_LINKS
+ tok->parent = -1;
+#endif
+ return tok;
+}
+
+/**
+ * Fills token type and boundaries.
+ */
+static void jsmn_fill_token(jsmntok_t *token, jsmntype_t type,
+ size_t start, size_t end) {
+ token->type = type;
+ token->start = start;
+ token->end = end;
+ token->size = 0;
+}
+
+/**
+ * Fills next available token with JSON primitive.
+ */
+static jsmnerr_t jsmn_parse_primitive(jsmn_parser *parser, const char *js,
+ jsmntok_t *tokens, unsigned int num_tokens) {
+ jsmntok_t *token;
+ size_t start;
+
+ start = parser->pos;
+
+ for (; (parser->end > 0 && parser->pos < parser->end) && js[parser->pos] != '\0'; parser->pos++) {
+ switch (js[parser->pos]) {
+#ifndef JSMN_STRICT
+ /* In strict mode primitive must be followed by "," or "}" or "]" */
+ case ':':
+#endif
+ case '\t' : case '\r' : case '\n' : case ' ' :
+ case ',' : case ']' : case '}' :
+ goto found;
+ default:
+ break;
+ }
+ if (js[parser->pos] < 32 || js[parser->pos] >= 127) {
+ parser->pos = start;
+ return JSMN_ERROR_INVAL;
+ }
+ }
+#ifdef JSMN_STRICT
+ /* In strict mode primitive must be followed by a comma/object/array */
+ parser->pos = start;
+ return JSMN_ERROR_PART;
+#endif
+
+found:
+ token = jsmn_alloc_token(parser, tokens, num_tokens);
+ if (token == NULL) {
+ parser->pos = start;
+ return JSMN_ERROR_NOMEM;
+ }
+ jsmn_fill_token(token, JSMN_PRIMITIVE, start, parser->pos);
+#ifdef JSMN_PARENT_LINKS
+ token->parent = parser->toksuper;
+#endif
+ parser->pos--;
+ return JSMN_SUCCESS;
+}
+
+/**
+ * Fills next token with JSON string.
+ */
+static jsmnerr_t jsmn_parse_string(jsmn_parser *parser, const char *js,
+ jsmntok_t *tokens, unsigned int num_tokens) {
+ jsmntok_t *token;
+
+ size_t start = parser->pos;
+
+ parser->pos++;
+
+ /* Skip starting quote */
+ for (; (parser->end > 0 && parser->pos < parser->end) && js[parser->pos] != '\0'; parser->pos++) {
+ char c = js[parser->pos];
+
+ /* Quote: end of string */
+ if (c == '\"') {
+ token = jsmn_alloc_token(parser, tokens, num_tokens);
+ if (token == NULL) {
+ parser->pos = start;
+ return JSMN_ERROR_NOMEM;
+ }
+ jsmn_fill_token(token, JSMN_STRING, start+1, parser->pos);
+#ifdef JSMN_PARENT_LINKS
+ token->parent = parser->toksuper;
+#endif
+ return JSMN_SUCCESS;
+ }
+
+ /* Backslash: Quoted symbol expected */
+ if (c == '\\') {
+ parser->pos++;
+ if (parser->end > 0 && parser->pos >= parser->end) {
+ parser->pos = start;
+ return JSMN_ERROR_INVAL;
+ }
+ switch (js[parser->pos]) {
+ /* Allowed escaped symbols */
+ case '\"': case '/' : case '\\' : case 'b' :
+ case 'f' : case 'r' : case 'n' : case 't' :
+ break;
+ /* Allows escaped symbol \uXXXX */
+ case 'u':
+ /* TODO */
+ break;
+ /* Unexpected symbol */
+ default:
+ parser->pos = start;
+ return JSMN_ERROR_INVAL;
+ }
+ }
+ }
+ parser->pos = start;
+ return JSMN_ERROR_PART;
+}
+
+/**
+ * Parse JSON string and fill tokens.
+ */
+jsmnerr_t jsmn_parse(jsmn_parser *parser, const char *js, size_t length, jsmntok_t *tokens,
+ unsigned int num_tokens) {
+ jsmnerr_t r;
+ int i;
+ jsmntok_t *token;
+
+ parser->end = length;
+
+ if (num_tokens >= INT_MAX) {
+ return JSMN_ERROR_LIMIT;
+ }
+ if (length > SIZE_MAX / 2) {
+ return JSMN_ERROR_LIMIT;
+ }
+
+ for (; (parser->end > 0 && parser->pos < parser->end) && js[parser->pos] != '\0'; parser->pos++) {
+ char c;
+ jsmntype_t type;
+
+ c = js[parser->pos];
+ switch (c) {
+ case '{': case '[':
+ token = jsmn_alloc_token(parser, tokens, num_tokens);
+ if (token == NULL)
+ return JSMN_ERROR_NOMEM;
+ if (parser->toksuper != -1) {
+ tokens[parser->toksuper].size++;
+#ifdef JSMN_PARENT_LINKS
+ token->parent = parser->toksuper;
+#endif
+ }
+ token->type = (c == '{' ? JSMN_OBJECT : JSMN_ARRAY);
+ token->start = parser->pos;
+ parser->toksuper = parser->toknext - 1;
+ break;
+ case '}': case ']':
+ type = (c == '}' ? JSMN_OBJECT : JSMN_ARRAY);
+#ifdef JSMN_PARENT_LINKS
+ if (parser->toknext < 1) {
+ return JSMN_ERROR_INVAL;
+ }
+ token = &tokens[parser->toknext - 1];
+ for (;;) {
+ if (token->start != JSMN_POS_INVALID && token->end == JSMN_POS_INVALID) {
+ if (token->type != type) {
+ return JSMN_ERROR_INVAL;
+ }
+ if (parser->pos == SIZE_MAX) {
+ return JSMN_ERROR_INVAL;
+ }
+ token->end = parser->pos + 1;
+ parser->toksuper = token->parent;
+ break;
+ }
+ if (token->parent == -1) {
+ break;
+ }
+ token = &tokens[token->parent];
+ }
+#else
+ for (i = parser->toknext - 1; i >= 0; i--) {
+ token = &tokens[i];
+ if (token->start != JSMN_POS_INVALID && token->end == JSMN_POS_INVALID) {
+ if (token->type != type) {
+ return JSMN_ERROR_INVAL;
+ }
+ parser->toksuper = -1;
+ token->end = parser->pos + 1;
+ break;
+ }
+ }
+ /* Error if unmatched closing bracket */
+ if (i == -1) return JSMN_ERROR_INVAL;
+ for (; i >= 0; i--) {
+ token = &tokens[i];
+ if (token->start != JSMN_POS_INVALID && token->end == JSMN_POS_INVALID) {
+ parser->toksuper = i;
+ break;
+ }
+ }
+#endif
+ break;
+ case '\"':
+ r = jsmn_parse_string(parser, js, tokens, num_tokens);
+ if (r < 0) return r;
+ if (parser->toksuper != -1)
+ tokens[parser->toksuper].size++;
+ break;
+ case '\t' : case '\r' : case '\n' : case ':' : case ',': case ' ':
+ break;
+#ifdef JSMN_STRICT
+ /* In strict mode primitives are: numbers and booleans */
+ case '-': case '0': case '1' : case '2': case '3' : case '4':
+ case '5': case '6': case '7' : case '8': case '9':
+ case 't': case 'f': case 'n' :
+#else
+ /* In non-strict mode every unquoted value is a primitive */
+ default:
+#endif
+ r = jsmn_parse_primitive(parser, js, tokens, num_tokens);
+ if (r < 0) return r;
+ if (parser->toksuper != -1)
+ tokens[parser->toksuper].size++;
+ break;
+
+#ifdef JSMN_STRICT
+ /* Unexpected char in strict mode */
+ default:
+ return JSMN_ERROR_INVAL;
+#endif
+
+ }
+ }
+
+ for (i = parser->toknext - 1; i >= 0; i--) {
+ /* Unmatched opened object or array */
+ if (tokens[i].start != JSMN_POS_INVALID && tokens[i].end == JSMN_POS_INVALID) {
+ return JSMN_ERROR_PART;
+ }
+ }
+
+ return JSMN_SUCCESS;
+}
+
+/**
+ * Creates a new parser based over a given buffer with an array of tokens
+ * available.
+ */
+void jsmn_init(jsmn_parser *parser) {
+ parser->pos = 0;
+ parser->end = 0;
+ parser->toknext = 0;
+ parser->toksuper = -1;
+}
+
diff --git a/src/jsmn.h b/src/jsmn.h
new file mode 100644
index 0000000..629a0dd
--- /dev/null
+++ b/src/jsmn.h
@@ -0,0 +1,98 @@
+/*
+ * jsmn.h
+ * Simple JSON parser (header file)
+ *
+ * Copyright (c) 2010 Serge A. Zaitsev
+ * Updated to use size_t for token offsets and harden against overflows.
+ * (Nikias Bassen, January 2026)
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+#ifndef __JSMN_H_
+#define __JSMN_H_
+
+#include <stddef.h>
+
+/**
+ * JSON type identifier. Basic types are:
+ * o Object
+ * o Array
+ * o String
+ * o Other primitive: number, boolean (true/false) or null
+ */
+typedef enum {
+ JSMN_PRIMITIVE = 0,
+ JSMN_OBJECT = 1,
+ JSMN_ARRAY = 2,
+ JSMN_STRING = 3
+} jsmntype_t;
+
+typedef enum {
+ /* Not enough tokens were provided */
+ JSMN_ERROR_NOMEM = -1,
+ /* Invalid character inside JSON string */
+ JSMN_ERROR_INVAL = -2,
+ /* The string is not a full JSON packet, more bytes expected */
+ JSMN_ERROR_PART = -3,
+ /* Input exceeds implementation-defined limits */
+ JSMN_ERROR_LIMIT = -4,
+ /* Everything was fine */
+ JSMN_SUCCESS = 0
+} jsmnerr_t;
+
+/**
+ * JSON token description.
+ * @param type type (object, array, string etc.)
+ * @param start start position in JSON data string
+ * @param end end position in JSON data string
+ */
+typedef struct {
+ jsmntype_t type;
+ size_t start;
+ size_t end;
+ size_t size;
+#ifdef JSMN_PARENT_LINKS
+ int parent;
+#endif
+} jsmntok_t;
+
+/**
+ * JSON parser. Contains an array of token blocks available. Also stores
+ * the string being parsed now and current position in that string
+ */
+typedef struct {
+ size_t pos; /* offset in the JSON string */
+ size_t end; /* offset after last character of JSON string */
+ int toknext; /* next token to allocate */
+ int toksuper; /* superior token node, e.g parent object or array */
+} jsmn_parser;
+
+/**
+ * Create JSON parser over an array of tokens
+ */
+void jsmn_init(jsmn_parser *parser);
+
+/**
+ * Run JSON parser. It parses a JSON data string into and array of tokens, each describing
+ * a single JSON object.
+ */
+jsmnerr_t jsmn_parse(jsmn_parser *parser, const char *js, size_t length,
+ jsmntok_t *tokens, unsigned int num_tokens);
+
+#endif /* __JSMN_H_ */
diff --git a/src/libplist++-2.0.pc.in b/src/libplist++-2.0.pc.in
new file mode 100644
index 0000000..79dc315
--- /dev/null
+++ b/src/libplist++-2.0.pc.in
@@ -0,0 +1,11 @@
+prefix=@prefix@
+exec_prefix=@exec_prefix@
+libdir=@libdir@
+includedir=@includedir@
+
+Name: @PACKAGE_NAME@++
+Description: C++ binding for @PACKAGE_NAME@
+Version: @PACKAGE_VERSION@
+Libs: -L${libdir} -lplist++-2.0
+Cflags: -I${includedir}
+Requires.private: libplist-2.0 >= @PACKAGE_VERSION@
diff --git a/src/libplist-2.0.pc.in b/src/libplist-2.0.pc.in
new file mode 100644
index 0000000..43d9f57
--- /dev/null
+++ b/src/libplist-2.0.pc.in
@@ -0,0 +1,10 @@
+prefix=@prefix@
+exec_prefix=@exec_prefix@
+libdir=@libdir@
+includedir=@includedir@
+
+Name: @PACKAGE_NAME@
+Description: A library to handle Apple Property Lists whereas they are binary or XML
+Version: @PACKAGE_VERSION@
+Libs: -L${libdir} -lplist-2.0
+Cflags: -I${includedir}
diff --git a/src/oplist.c b/src/oplist.c
new file mode 100644
index 0000000..77a99cd
--- /dev/null
+++ b/src/oplist.c
@@ -0,0 +1,989 @@
+/*
+ * oplist.c
+ * OpenStep plist implementation
+ *
+ * Copyright (c) 2021-2022 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
+ */
+
+#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"
+
+#ifdef DEBUG
+static int plist_ostep_debug = 0;
+#define PLIST_OSTEP_ERR(...) if (plist_ostep_debug) { fprintf(stderr, "libplist[ostepparser] ERROR: " __VA_ARGS__); }
+#define PLIST_OSTEP_WRITE_ERR(...) if (plist_ostep_debug) { fprintf(stderr, "libplist[ostepwriter] ERROR: " __VA_ARGS__); }
+#else
+#define PLIST_OSTEP_ERR(...)
+#define PLIST_OSTEP_WRITE_ERR(...)
+#endif
+
+void plist_ostep_init(void)
+{
+ /* init OpenStep stuff */
+#ifdef DEBUG
+ char *env_debug = getenv("PLIST_OSTEP_DEBUG");
+ if (env_debug && !strcmp(env_debug, "1")) {
+ plist_ostep_debug = 1;
+ }
+#endif
+}
+
+void plist_ostep_deinit(void)
+{
+ /* deinit OpenStep plist stuff */
+}
+
+void plist_ostep_set_debug(int debug)
+{
+#if DEBUG
+ plist_ostep_debug = debug;
+#endif
+}
+
+#ifndef HAVE_STRNDUP
+#ifndef _MSC_VER
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wshadow"
+#endif
+static char* strndup(const char* str, size_t len)
+{
+ char *newstr = (char *)malloc(len+1);
+ if (newstr) {
+ strncpy(newstr, str, len);
+ newstr[len]= '\0';
+ }
+ return newstr;
+}
+#ifndef _MSC_VER
+#pragma GCC diagnostic pop
+#endif
+#endif
+
+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,
+ 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0,
+ 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1,
+ 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 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, 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, 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, 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, 0, 0, 0, 0, 0,
+};
+
+static int str_needs_quotes(const char* str, size_t len)
+{
+ size_t i;
+ for (i = 0; i < len; i++) {
+ if (!allowed_unquoted_chars[(unsigned char)str[i]]) {
+ return 1;
+ }
+ }
+ return 0;
+}
+
+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;
+
+ char *val = NULL;
+ size_t val_len = 0;
+
+ uint32_t i = 0;
+
+ if (!node)
+ return PLIST_ERR_INVALID_ARG;
+
+ node_data = plist_get_data(node);
+
+ switch (node_data->type)
+ {
+ case PLIST_INT:
+ 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);
+ break;
+
+ case PLIST_REAL:
+ val = (char*)malloc(64);
+ val_len = dtostr(val, 64, node_data->realval);
+ str_buf_append(*outbuf, val, val_len);
+ free(val);
+ break;
+
+ case PLIST_STRING:
+ case PLIST_KEY: {
+ 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;
+ off_t start = 0;
+ off_t cur = 0;
+ int needs_quotes;
+
+ len = node_data->length;
+
+ needs_quotes = str_needs_quotes(node_data->strval, len);
+
+ if (needs_quotes) {
+ str_buf_append(*outbuf, "\"", 1);
+ }
+
+ 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);
+
+ if (needs_quotes) {
+ str_buf_append(*outbuf, "\"", 1);
+ }
+
+ } break;
+
+ case PLIST_ARRAY: {
+ str_buf_append(*outbuf, "(", 1);
+ node_t ch;
+ uint32_t cnt = 0;
+ for (ch = node_first_child(node); ch; ch = node_next_sibling(ch)) {
+ if (cnt > 0) {
+ str_buf_append(*outbuf, ",", 1);
+ }
+ if (prettify) {
+ str_buf_append(*outbuf, "\n", 1);
+ for (i = 0; i <= depth; i++) {
+ str_buf_append(*outbuf, " ", 2);
+ }
+ }
+ plist_err_t res = node_to_openstep(ch, outbuf, depth+1, prettify, coerce);
+ if (res < 0) {
+ return res;
+ }
+ cnt++;
+ }
+ if (cnt > 0 && prettify) {
+ 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;
+ for (ch = node_first_child(node); ch; ch = node_next_sibling(ch)) {
+ if (cnt > 0 && cnt % 2 == 0) {
+ str_buf_append(*outbuf, ";", 1);
+ }
+ if (cnt % 2 == 0 && prettify) {
+ str_buf_append(*outbuf, "\n", 1);
+ for (i = 0; i <= depth; i++) {
+ str_buf_append(*outbuf, " ", 2);
+ }
+ }
+ plist_err_t res = node_to_openstep(ch, outbuf, depth+1, prettify, coerce);
+ if (res < 0) {
+ return res;
+ }
+ if (cnt % 2 == 0) {
+ if (prettify) {
+ str_buf_append(*outbuf, " = ", 3);
+ } else {
+ str_buf_append(*outbuf, "=", 1);
+ }
+ }
+ cnt++;
+ }
+ if (cnt > 0) {
+ str_buf_append(*outbuf, ";", 1);
+ }
+ if (cnt > 0 && prettify) {
+ 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: {
+ size_t j = 0;
+ size_t len = 0;
+ str_buf_append(*outbuf, "<", 1);
+ len = node_data->length;
+ for (j = 0; j < len; j++) {
+ char charb[4];
+ if (prettify && j > 0 && (j % 4 == 0))
+ str_buf_append(*outbuf, " ", 1);
+ sprintf(charb, "%02x", (unsigned char)node_data->buff[j]);
+ str_buf_append(*outbuf, charb, 2);
+ }
+ str_buf_append(*outbuf, ">", 1);
+ } break;
+ case PLIST_BOOLEAN:
+ 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:
+ 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:
+ if (coerce) {
+ Time64_T timev;
+ if (plist_real_to_time64(node_data->realval, &timev) < 0) {
+ PLIST_OSTEP_WRITE_ERR("Encountered invalid date value %f\n", node_data->realval);
+ return PLIST_ERR_INVALID_ARG;
+ }
+ 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:
+ 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;
+ }
+
+ return PLIST_ERR_SUCCESS;
+}
+
+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) {
+ return PLIST_ERR_INVALID_ARG;
+ }
+
+ if (depth > PLIST_MAX_NESTING_DEPTH) {
+ PLIST_OSTEP_WRITE_ERR("node tree is nested too deeply\n");
+ return PLIST_ERR_MAX_NESTING;
+ }
+
+ if (hash_table_lookup(visited, node)) {
+ PLIST_OSTEP_WRITE_ERR("circular reference detected\n");
+ return PLIST_ERR_CIRCULAR_REF;
+ }
+
+ // mark as visited
+ 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, prettify, coerce, visited);
+ if (res < 0) {
+ return res;
+ }
+ }
+ switch (data->type) {
+ case PLIST_DICT:
+ *size += 2; // '{' and '}'
+ *size += n_children; // number of '=' and ';'
+ if (prettify) {
+ *size += n_children*2; // number of '\n' and extra spaces
+ *size += (uint64_t)n_children * (depth+1); // indent for every 2nd child
+ *size += 1; // additional '\n'
+ }
+ break;
+ case PLIST_ARRAY:
+ *size += 2; // '(' and ')'
+ *size += n_children-1; // number of ','
+ if (prettify) {
+ *size += n_children; // number of '\n'
+ *size += (uint64_t)n_children * ((depth+1)<<1); // indent for every child
+ *size += 1; // additional '\n'
+ }
+ break;
+ default:
+ break;
+ }
+ if (prettify)
+ *size += (depth << 1); // indent for {} and ()
+ } 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_DICT:
+ case PLIST_ARRAY:
+ *size += 2;
+ break;
+ case PLIST_DATA:
+ *size += 2; // < and >
+ *size += data->length*2;
+ if (prettify)
+ *size += data->length/4;
+ break;
+ case PLIST_BOOLEAN:
+ 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:
+ 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:
+ 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;
+ }
+ }
+ return PLIST_ERR_SUCCESS;
+}
+
+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, 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;
+
+ if (!plist || !openstep || !length) {
+ return PLIST_ERR_INVALID_ARG;
+ }
+
+ 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;
+ }
+
+ strbuf_t *outbuf = str_buf_new(size);
+ if (!outbuf) {
+ PLIST_OSTEP_WRITE_ERR("Could not allocate output buffer");
+ return PLIST_ERR_NO_MEM;
+ }
+
+ res = node_to_openstep((node_t)plist, &outbuf, 0, prettify, coerce);
+ if (res < 0) {
+ str_buf_free(outbuf);
+ *openstep = NULL;
+ *length = 0;
+ return res;
+ }
+ if (prettify) {
+ str_buf_append(outbuf, "\n", 1);
+ }
+
+ str_buf_append(outbuf, "\0", 1);
+
+ *openstep = (char*)outbuf->data;
+ *length = outbuf->len - 1;
+
+ outbuf->data = NULL;
+ str_buf_free(outbuf);
+
+ return PLIST_ERR_SUCCESS;
+}
+
+struct _parse_ctx {
+ const char *start;
+ const char *pos;
+ const char *end;
+ plist_err_t err;
+ uint32_t depth;
+};
+typedef struct _parse_ctx* parse_ctx;
+
+static void parse_skip_ws(parse_ctx ctx)
+{
+ while (ctx->pos < ctx->end) {
+ // skip comments
+ if (*ctx->pos == '/' && (ctx->end - ctx->pos > 1)) {
+ if (*(ctx->pos+1) == '/') {
+ ctx->pos++;
+ while (ctx->pos < ctx->end) {
+ if ((*ctx->pos == '\n') || (*ctx->pos == '\r')) {
+ break;
+ }
+ ctx->pos++;
+ }
+ } else if (*(ctx->pos+1) == '*') {
+ ctx->pos++;
+ while (ctx->pos < ctx->end) {
+ if (*ctx->pos == '*' && (ctx->end - ctx->pos > 1)) {
+ if (*(ctx->pos+1) == '/') {
+ ctx->pos+=2;
+ break;
+ }
+ }
+ ctx->pos++;
+ }
+ }
+ if (ctx->pos >= ctx->end) {
+ break;
+ }
+ }
+ // break on any char that's not white space
+ if (!(((*(ctx->pos) == ' ') || (*(ctx->pos) == '\t') || (*(ctx->pos) == '\r') || (*(ctx->pos) == '\n')))) {
+ break;
+ }
+ ctx->pos++;
+ }
+}
+
+#define HEX_DIGIT(x) ((x <= '9') ? (x - '0') : ((x <= 'F') ? (x - 'A' + 10) : (x - 'a' + 10)))
+
+static plist_err_t node_from_openstep(parse_ctx ctx, plist_t *plist);
+
+static void parse_dict_data(parse_ctx ctx, plist_t dict)
+{
+ plist_t key = NULL;
+ plist_t val = NULL;
+ while (ctx->pos < ctx->end && !ctx->err) {
+ parse_skip_ws(ctx);
+ if (ctx->pos >= ctx->end || *ctx->pos == '}') {
+ break;
+ }
+ key = NULL;
+ ctx->err = node_from_openstep(ctx, &key);
+ if (ctx->err != PLIST_ERR_SUCCESS) {
+ break;
+ }
+ if (!PLIST_IS_STRING(key)) {
+ PLIST_OSTEP_ERR("Invalid type for dictionary key at offset %ld\n", (long int)(ctx->pos - ctx->start));
+ ctx->err = PLIST_ERR_PARSE;
+ break;
+ }
+ parse_skip_ws(ctx);
+ if (ctx->pos >= ctx->end) {
+ PLIST_OSTEP_ERR("EOF while parsing dictionary '=' delimiter at offset %ld\n", (long int)(ctx->pos - ctx->start));
+ ctx->err = PLIST_ERR_PARSE;
+ break;
+ }
+ if (*ctx->pos != '=') {
+ PLIST_OSTEP_ERR("Missing '=' while parsing dictionary item at offset %ld\n", (long int)(ctx->pos - ctx->start));
+ ctx->err = PLIST_ERR_PARSE;
+ break;
+ }
+ ctx->pos++;
+ if (ctx->pos >= ctx->end) {
+ PLIST_OSTEP_ERR("EOF while parsing dictionary item at offset %ld\n", (long int)(ctx->pos - ctx->start));
+ ctx->err = PLIST_ERR_PARSE;
+ break;
+ }
+ val = NULL;
+ ctx->err = node_from_openstep(ctx, &val);
+ if (ctx->err != PLIST_ERR_SUCCESS) {
+ break;
+ }
+ if (!val) {
+ PLIST_OSTEP_ERR("Missing value for dictionary item at offset %ld\n", (long int)(ctx->pos - ctx->start));
+ ctx->err = PLIST_ERR_PARSE;
+ break;
+ }
+ parse_skip_ws(ctx);
+ if (ctx->pos >= ctx->end) {
+ PLIST_OSTEP_ERR("EOF while parsing dictionary item terminator ';' at offset %ld\n", (long int)(ctx->pos - ctx->start));
+ ctx->err = PLIST_ERR_PARSE;
+ break;
+ }
+ if (*ctx->pos != ';') {
+ PLIST_OSTEP_ERR("Missing terminating ';' while parsing dictionary item at offset %ld\n", (long int)(ctx->pos - ctx->start));
+ ctx->err = PLIST_ERR_PARSE;
+ break;
+ }
+
+ plist_dict_set_item(dict, plist_get_string_ptr(key, NULL), val);
+ plist_free(key);
+ key = NULL;
+ val = NULL;
+
+ ctx->pos++;
+ }
+ plist_free(key);
+ plist_free(val);
+}
+
+static plist_err_t node_from_openstep(parse_ctx ctx, plist_t *plist)
+{
+ plist_t subnode = NULL;
+ const char *p = NULL;
+ ctx->depth++;
+ if (ctx->depth > PLIST_MAX_NESTING_DEPTH) {
+ PLIST_OSTEP_ERR("Too many levels of recursion (%u) at offset %ld\n", ctx->depth, (long int)(ctx->pos - ctx->start));
+ ctx->err = PLIST_ERR_MAX_NESTING;
+ return ctx->err;
+ }
+ while (ctx->pos < ctx->end && !ctx->err) {
+ parse_skip_ws(ctx);
+ if (ctx->pos >= ctx->end) {
+ break;
+ }
+ plist_data_t data = plist_new_plist_data();
+ if (*ctx->pos == '{') {
+ data->type = PLIST_DICT;
+ subnode = plist_new_node(data);
+ ctx->pos++;
+ parse_dict_data(ctx, subnode);
+ if (ctx->err) {
+ goto err_out;
+ }
+ if (ctx->pos >= ctx->end) {
+ PLIST_OSTEP_ERR("EOF while parsing dictionary terminator '}' at offset %ld\n", (long int)(ctx->pos - ctx->start));
+ ctx->err = PLIST_ERR_PARSE;
+ break;
+ }
+ if (*ctx->pos != '}') {
+ PLIST_OSTEP_ERR("Missing terminating '}' at offset %ld\n", (long int)(ctx->pos - ctx->start));
+ ctx->err = PLIST_ERR_PARSE;
+ goto err_out;
+ }
+ ctx->pos++;
+ *plist = subnode;
+ parse_skip_ws(ctx);
+ break;
+ } else if (*ctx->pos == '(') {
+ data->type = PLIST_ARRAY;
+ subnode = plist_new_node(data);
+ ctx->pos++;
+ plist_t tmp = NULL;
+ while (ctx->pos < ctx->end && !ctx->err) {
+ parse_skip_ws(ctx);
+ if (ctx->pos >= ctx->end || *ctx->pos == ')') {
+ break;
+ }
+ ctx->err = node_from_openstep(ctx, &tmp);
+ if (ctx->err != PLIST_ERR_SUCCESS) {
+ break;
+ }
+ if (!tmp) {
+ ctx->err = PLIST_ERR_PARSE;
+ break;
+ }
+ plist_array_append_item(subnode, tmp);
+ tmp = NULL;
+ parse_skip_ws(ctx);
+ if (ctx->pos >= ctx->end) {
+ PLIST_OSTEP_ERR("EOF while parsing array item delimiter ',' at offset %ld\n", (long int)(ctx->pos - ctx->start));
+ ctx->err = PLIST_ERR_PARSE;
+ break;
+ }
+ if (*ctx->pos != ',') {
+ break;
+ }
+ ctx->pos++;
+ }
+ plist_free(tmp);
+ tmp = NULL;
+ if (ctx->err != PLIST_ERR_SUCCESS) {
+ goto err_out;
+ }
+ if (ctx->pos >= ctx->end) {
+ PLIST_OSTEP_ERR("EOF while parsing array terminator ')' at offset %ld\n", (long int)(ctx->pos - ctx->start));
+ ctx->err = PLIST_ERR_PARSE;
+ break;
+ }
+ if (*ctx->pos != ')') {
+ PLIST_OSTEP_ERR("Missing terminating ')' at offset %ld\n", (long int)(ctx->pos - ctx->start));
+ ctx->err = PLIST_ERR_PARSE;
+ goto err_out;
+ }
+ ctx->pos++;
+ *plist = subnode;
+ parse_skip_ws(ctx);
+ break;
+ } else if (*ctx->pos == '<') {
+ data->type = PLIST_DATA;
+ ctx->pos++;
+ bytearray_t *bytes = byte_array_new(256);
+ while (ctx->pos < ctx->end && !ctx->err) {
+ parse_skip_ws(ctx);
+ if (ctx->pos >= ctx->end) {
+ PLIST_OSTEP_ERR("EOF while parsing data terminator '>' at offset %ld\n", (long int)(ctx->pos - ctx->start));
+ ctx->err = PLIST_ERR_PARSE;
+ break;
+ }
+ if (*ctx->pos == '>') {
+ break;
+ }
+ if (!isxdigit(*ctx->pos)) {
+ PLIST_OSTEP_ERR("Invalid byte group in data at offset %ld\n", (long int)(ctx->pos - ctx->start));
+ ctx->err = PLIST_ERR_PARSE;
+ break;
+ }
+ uint8_t b = HEX_DIGIT(*ctx->pos);
+ ctx->pos++;
+ if (ctx->pos >= ctx->end) {
+ PLIST_OSTEP_ERR("Unexpected end of data at offset %ld\n", (long int)(ctx->pos - ctx->start));
+ ctx->err = PLIST_ERR_PARSE;
+ break;
+ }
+ if (!isxdigit(*ctx->pos)) {
+ PLIST_OSTEP_ERR("Invalid byte group in data at offset %ld\n", (long int)(ctx->pos - ctx->start));
+ ctx->err = PLIST_ERR_PARSE;
+ break;
+ }
+ b = (b << 4) + HEX_DIGIT(*ctx->pos);
+ byte_array_append(bytes, &b, 1);
+ ctx->pos++;
+ }
+ if (ctx->err) {
+ byte_array_free(bytes);
+ plist_free_data(data);
+ goto err_out;
+ }
+ if (ctx->pos >= ctx->end) {
+ byte_array_free(bytes);
+ plist_free_data(data);
+ PLIST_OSTEP_ERR("EOF while parsing data terminator '>' at offset %ld\n", (long int)(ctx->pos - ctx->start));
+ ctx->err = PLIST_ERR_PARSE;
+ goto err_out;
+ }
+ if (*ctx->pos != '>') {
+ byte_array_free(bytes);
+ plist_free_data(data);
+ PLIST_OSTEP_ERR("Missing terminating '>' at offset %ld\n", (long int)(ctx->pos - ctx->start));
+ ctx->err = PLIST_ERR_PARSE;
+ goto err_out;
+ }
+ ctx->pos++;
+ data->buff = (uint8_t*)bytes->data;
+ data->length = bytes->len;
+ bytes->data = NULL;
+ byte_array_free(bytes);
+ *plist = plist_new_node(data);
+ parse_skip_ws(ctx);
+ break;
+ } else if (*ctx->pos == '"' || *ctx->pos == '\'') {
+ char c = *ctx->pos;
+ ctx->pos++;
+ p = ctx->pos;
+ size_t num_escapes = 0;
+ while (ctx->pos < ctx->end) {
+ if (*ctx->pos == '\\') {
+ num_escapes++;
+ }
+ if ((*ctx->pos == c) && (*(ctx->pos-1) != '\\')) {
+ break;
+ }
+ ctx->pos++;
+ }
+ if (ctx->pos >= ctx->end) {
+ plist_free_data(data);
+ PLIST_OSTEP_ERR("EOF while parsing quoted string at offset %ld\n", (long int)(ctx->pos - ctx->start));
+ ctx->err = PLIST_ERR_PARSE;
+ goto err_out;
+ }
+ if (*ctx->pos != c) {
+ plist_free_data(data);
+ PLIST_OSTEP_ERR("Missing closing quote (%c) at offset %ld\n", c, (long int)(ctx->pos - ctx->start));
+ ctx->err = PLIST_ERR_PARSE;
+ goto err_out;
+ }
+ size_t slen = ctx->pos - p;
+ ctx->pos++; // skip the closing quote
+ char* strbuf = (char*)malloc(slen+1);
+ if (num_escapes > 0) {
+ size_t i = 0;
+ size_t o = 0;
+ while (i < slen) {
+ if (p[i] == '\\') {
+ /* handle escape sequence */
+ i++;
+ switch (p[i]) {
+ case '0':
+ case '1':
+ case '2':
+ case '3':
+ case '4':
+ case '5':
+ case '6':
+ case '7': {
+ // max 3 digits octal
+ unsigned char chr = 0;
+ int maxd = 3;
+ while ((i < slen) && (p[i] >= '0' && p[i] <= '7') && --maxd) {
+ chr = (chr << 3) + p[i] - '0';
+ i++;
+ }
+ strbuf[o++] = (char)chr;
+ } break;
+ case 'U': {
+ i++;
+ // max 4 digits hex
+ uint16_t wchr = 0;
+ int maxd = 4;
+ while ((i < slen) && isxdigit(p[i]) && maxd--) {
+ wchr = (wchr << 4) + ((p[i] <= '9') ? (p[i] - '0') : ((p[i] <= 'F') ? (p[i] - 'A' + 10) : (p[i] - 'a' + 10)));
+ i++;
+ }
+ if (wchr >= 0x800) {
+ strbuf[o++] = (char)(0xE0 + ((wchr >> 12) & 0xF));
+ strbuf[o++] = (char)(0x80 + ((wchr >> 6) & 0x3F));
+ strbuf[o++] = (char)(0x80 + (wchr & 0x3F));
+ } else if (wchr >= 0x80) {
+ strbuf[o++] = (char)(0xC0 + ((wchr >> 6) & 0x1F));
+ strbuf[o++] = (char)(0x80 + (wchr & 0x3F));
+ } else {
+ strbuf[o++] = (char)(wchr & 0x7F);
+ }
+ } break;
+ case 'a': strbuf[o++] = '\a'; i++; break;
+ case 'b': strbuf[o++] = '\b'; i++; break;
+ case 'f': strbuf[o++] = '\f'; i++; break;
+ case 'n': strbuf[o++] = '\n'; i++; break;
+ case 'r': strbuf[o++] = '\r'; i++; break;
+ case 't': strbuf[o++] = '\t'; i++; break;
+ case 'v': strbuf[o++] = '\v'; i++; break;
+ case '"': strbuf[o++] = '"'; i++; break;
+ case '\'': strbuf[o++] = '\''; i++; break;
+ default:
+ break;
+ }
+ } else {
+ strbuf[o++] = p[i++];
+ }
+ }
+ strbuf[o] = '\0';
+ slen = o;
+ } else {
+ strncpy(strbuf, p, slen);
+ strbuf[slen] = '\0';
+ }
+ data->type = PLIST_STRING;
+ data->strval = strbuf;
+ data->length = slen;
+ *plist = plist_new_node(data);
+ parse_skip_ws(ctx);
+ break;
+ } else {
+ // unquoted string
+ size_t slen = 0;
+ parse_skip_ws(ctx);
+ p = ctx->pos;
+ while (ctx->pos < ctx->end) {
+ if (!allowed_unquoted_chars[(uint8_t)*ctx->pos]) {
+ break;
+ }
+ ctx->pos++;
+ }
+ slen = ctx->pos-p;
+ if (slen > 0) {
+ data->type = PLIST_STRING;
+ data->strval = strndup(p, slen);
+ data->length = slen;
+ *plist = plist_new_node(data);
+ parse_skip_ws(ctx);
+ break;
+ } else {
+ plist_free_data(data);
+ PLIST_OSTEP_ERR("Unexpected character when parsing unquoted string at offset %ld\n", (long int)(ctx->pos - ctx->start));
+ ctx->err = PLIST_ERR_PARSE;
+ break;
+ }
+ }
+ ctx->pos++;
+ }
+ ctx->depth--;
+
+err_out:
+ if (ctx->err != PLIST_ERR_SUCCESS) {
+ plist_free(subnode);
+ plist_free(*plist);
+ *plist = NULL;
+ return ctx->err;
+ }
+ return PLIST_ERR_SUCCESS;
+}
+
+plist_err_t plist_from_openstep(const char *plist_ostep, uint32_t length, plist_t * plist)
+{
+ if (!plist) {
+ return PLIST_ERR_INVALID_ARG;
+ }
+ *plist = NULL;
+ if (!plist_ostep || (length == 0)) {
+ return PLIST_ERR_INVALID_ARG;
+ }
+
+ struct _parse_ctx ctx = { plist_ostep, plist_ostep, plist_ostep + length, 0 , 0 };
+
+ plist_err_t err = node_from_openstep(&ctx, plist);
+ if (err == 0) {
+ if (!*plist) {
+ /* whitespace only file is considered an empty dictionary */
+ *plist = plist_new_dict();
+ } else if (ctx.pos < ctx.end && *ctx.pos == '=') {
+ /* attempt to parse this as 'strings' data */
+ plist_free(*plist);
+ *plist = NULL;
+ plist_t pl = plist_new_dict();
+ ctx.pos = plist_ostep;
+ parse_dict_data(&ctx, pl);
+ if (ctx.err > 0) {
+ plist_free(pl);
+ PLIST_OSTEP_ERR("Failed to parse strings data\n");
+ err = PLIST_ERR_PARSE;
+ } else {
+ *plist = pl;
+ }
+ }
+ }
+
+ return err;
+}
diff --git a/src/out-default.c b/src/out-default.c
new file mode 100644
index 0000000..13b9d9c
--- /dev/null
+++ b/src/out-default.c
@@ -0,0 +1,488 @@
+/*
+ * out-default.c
+ * libplist default *output-only* format - NOT for machine parsing
+ *
+ * Copyright (c) 2022-2023 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
+ */
+
+#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, uint32_t indent, int partial_data)
+{
+ plist_data_t node_data = NULL;
+
+ char *val = NULL;
+ int slen = 0;
+ size_t val_len = 0;
+
+ uint32_t i = 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, "true", 4);
+ } else {
+ str_buf_append(*outbuf, "false", 5);
+ }
+ }
+ break;
+
+ case PLIST_NULL:
+ str_buf_append(*outbuf, "null", 4);
+ 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;
+ for (ch = node_first_child(node); ch; ch = node_next_sibling(ch)) {
+ if (cnt > 0) {
+ str_buf_append(*outbuf, ",", 1);
+ }
+ str_buf_append(*outbuf, "\n", 1);
+ for (i = 0; i <= depth+indent; i++) {
+ str_buf_append(*outbuf, " ", 2);
+ }
+ plist_err_t res = node_to_string(ch, outbuf, depth+1, indent, partial_data);
+ if (res < 0) {
+ return res;
+ }
+ cnt++;
+ }
+ if (cnt > 0) {
+ str_buf_append(*outbuf, "\n", 1);
+ for (i = 0; i < depth+indent; 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;
+ for (ch = node_first_child(node); ch; ch = node_next_sibling(ch)) {
+ if (cnt > 0 && cnt % 2 == 0) {
+ str_buf_append(*outbuf, ",", 1);
+ }
+ if (cnt % 2 == 0) {
+ str_buf_append(*outbuf, "\n", 1);
+ for (i = 0; i <= depth+indent; i++) {
+ str_buf_append(*outbuf, " ", 2);
+ }
+ }
+ plist_err_t res = node_to_string(ch, outbuf, depth+1, indent, partial_data);
+ if (res < 0) {
+ return res;
+ }
+ if (cnt % 2 == 0) {
+ str_buf_append(*outbuf, ": ", 2);
+ }
+ cnt++;
+ }
+ if (cnt > 0) {
+ str_buf_append(*outbuf, "\n", 1);
+ for (i = 0; i < depth+indent; 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;
+ }
+ str_buf_append(*outbuf, "<", 1);
+ size_t len = node_data->length;
+ char charb[4];
+ size_t j;
+ if (!partial_data || len <= 24) {
+ for (j = 0; j < len; j++) {
+ if (j > 0 && (j % 4 == 0))
+ str_buf_append(*outbuf, " ", 1);
+ snprintf(charb, sizeof(charb), "%02x", (unsigned char)node_data->buff[j]);
+ str_buf_append(*outbuf, charb, 2);
+ }
+ } else {
+ for (j = 0; j < 16; j++) {
+ if (j > 0 && (j % 4 == 0))
+ str_buf_append(*outbuf, " ", 1);
+ snprintf(charb, sizeof(charb), "%02x", (unsigned char)node_data->buff[j]);
+ str_buf_append(*outbuf, charb, 2);
+ }
+ str_buf_append(*outbuf, " ... ", 5);
+ for (j = len - 8; j < len; j++) {
+ snprintf(charb, sizeof(charb), "%02x", (unsigned char)node_data->buff[j]);
+ str_buf_append(*outbuf, charb, 2);
+ if (j > 0 && j < len-1 && (j % 4 == 0))
+ str_buf_append(*outbuf, " ", 1);
+ }
+ }
+ 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:
+ {
+ str_buf_append(*outbuf, "CF$UID:", 7);
+ 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;
+ 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, uint32_t indent, int partial_data, 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;
+ }
+
+ // mark as visited
+ 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, indent, partial_data, visited);
+ if (res != PLIST_ERR_SUCCESS) {
+ return res;
+ }
+ }
+ switch (data->type) {
+ case PLIST_DICT:
+ *size += 2; // '{' and '}'
+ *size += n_children-1; // number of ':' and ','
+ *size += n_children; // number of '\n' and extra space
+ *size += (uint64_t)n_children * (depth+indent+1); // indent for every 2nd child
+ *size += indent+1; // additional '\n'
+ break;
+ case PLIST_ARRAY:
+ *size += 2; // '[' and ']'
+ *size += n_children-1; // number of ','
+ *size += n_children; // number of '\n'
+ *size += (uint64_t)n_children * ((depth+indent+1)<<1); // indent for every child
+ *size += indent+1; // additional '\n'
+ break;
+ default:
+ break;
+ }
+ *size += ((depth+indent) << 1); // indent for {} and []
+ } 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 += ((data->boolval) ? 4 : 5);
+ break;
+ case PLIST_NULL:
+ *size += 4;
+ break;
+ case PLIST_DICT:
+ case PLIST_ARRAY:
+ *size += 2;
+ break;
+ case PLIST_DATA:
+ *size += 2; // < and >
+ if (partial_data) {
+ *size += 58;
+ } else {
+ *size += data->length * 2;
+ *size += data->length / 4; // space between 4 byte groups
+ }
+ break;
+ case PLIST_DATE:
+ *size += 25;
+ break;
+ case PLIST_UID:
+ *size += 7; // "CF$UID:"
+ *size += num_digits_u(data->intval);
+ break;
+ default:
+#ifdef DEBUG
+ fprintf(stderr, "%s: invalid node type encountered\n", __func__);
+#endif
+ return PLIST_ERR_UNKNOWN;
+ }
+ }
+ if (depth == 0) {
+ *size += 1; // final newline
+ }
+ return PLIST_ERR_SUCCESS;
+}
+
+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 = 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, indent, partial_data, 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)
+{
+ uint8_t indent = 0;
+ if (options & PLIST_OPT_INDENT) {
+ indent = (options >> 24) & 0xFF;
+ }
+ uint8_t i;
+ for (i = 0; i < indent; i++) {
+ str_buf_append(outbuf, " ", 2);
+ }
+ plist_err_t res = node_to_string((node_t)plist, &outbuf, 0, indent, options & PLIST_OPT_PARTIAL_DATA);
+ 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_default(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;
+ }
+
+ uint8_t indent = 0;
+ if (options & PLIST_OPT_INDENT) {
+ indent = (options >> 24) & 0xFF;
+ }
+
+ res = node_estimate_size((node_t)plist, &size, 0, indent, options & PLIST_OPT_PARTIAL_DATA);
+ 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_default(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;
+}
diff --git a/src/out-limd.c b/src/out-limd.c
new file mode 100644
index 0000000..83a5e26
--- /dev/null
+++ b/src/out-limd.c
@@ -0,0 +1,469 @@
+/*
+ * out-limd.c
+ * libplist *output-only* format introduced by libimobiledevice/ideviceinfo
+ * - NOT for machine parsing
+ *
+ * Copyright (c) 2022-2023 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
+ */
+
+#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 "base64.h"
+#include "hashtable.h"
+#include "common.h"
+
+static plist_err_t node_to_string(node_t node, bytearray_t **outbuf, uint32_t depth, uint32_t indent)
+{
+ plist_data_t node_data = NULL;
+
+ char *val = NULL;
+ int slen = 0;
+ size_t val_len = 0;
+ char buf[16];
+
+ uint32_t i = 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, "true", 4);
+ } else {
+ str_buf_append(*outbuf, "false", 5);
+ }
+ break;
+ }
+
+ case PLIST_NULL:
+ str_buf_append(*outbuf, "null", 4);
+ 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 = node_data->length;
+ size_t start = 0;
+ size_t cur = 0;
+
+ 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;
+ }
+
+ cur++;
+ }
+ str_buf_append(*outbuf, node_data->strval + start, cur - start);
+ break;
+ }
+ case PLIST_ARRAY: {
+ node_t ch;
+ uint32_t cnt = 0;
+ for (ch = node_first_child(node); ch; ch = node_next_sibling(ch)) {
+ if (cnt > 0 || (cnt == 0 && node->parent != NULL)) {
+ str_buf_append(*outbuf, "\n", 1);
+ for (i = 0; i < depth+indent; i++) {
+ str_buf_append(*outbuf, " ", 1);
+ }
+ }
+ slen = snprintf(buf, sizeof(buf), "%u: ", cnt);
+ if (slen < 0) {
+ return PLIST_ERR_UNKNOWN;
+ }
+ str_buf_append(*outbuf, buf, (size_t)slen);
+ plist_err_t res = node_to_string(ch, outbuf, depth+1, indent);
+ if (res < 0) {
+ return res;
+ }
+ cnt++;
+ }
+ break;
+ }
+ case PLIST_DICT: {
+ node_t ch;
+ uint32_t cnt = 0;
+ for (ch = node_first_child(node); ch; ch = node_next_sibling(ch)) {
+ if (cnt > 0 && cnt % 2 == 0) {
+ str_buf_append(*outbuf, "\n", 1);
+ for (i = 0; i < depth+indent; i++) {
+ str_buf_append(*outbuf, " ", 1);
+ }
+ }
+ plist_err_t res = node_to_string(ch, outbuf, depth+1, indent);
+ if (res < 0) {
+ return res;
+ }
+ if (cnt % 2 == 0) {
+ plist_t valnode = (plist_t)node_next_sibling(ch);
+ if (PLIST_IS_ARRAY(valnode)) {
+ slen = snprintf(buf, sizeof(buf), "[%u]:", plist_array_get_size(valnode));
+ if (slen < 0) {
+ return PLIST_ERR_UNKNOWN;
+ }
+ str_buf_append(*outbuf, buf, (size_t)slen);
+ } else {
+ str_buf_append(*outbuf, ": ", 2);
+ }
+ }
+ cnt++;
+ }
+ break;
+ }
+ case PLIST_DATA:
+ {
+ if (!node_data->buff && node_data->length > 0) {
+ return PLIST_ERR_INVALID_ARG;
+ }
+ if (node_data->length == 0) {
+ break;
+ }
+#define BASE64_CHUNK_SIZE 3072
+#define BASE64_BUF_SIZE (4 * ((BASE64_CHUNK_SIZE + 2) / 3) + 4)
+ val = (char*)malloc(BASE64_BUF_SIZE);
+ if (!val) return PLIST_ERR_NO_MEM;
+ size_t done = 0;
+ while (done < node_data->length) {
+ size_t amount = node_data->length - done;
+ if (amount > BASE64_CHUNK_SIZE) {
+ amount = BASE64_CHUNK_SIZE;
+ }
+ size_t bsize = base64encode(val, node_data->buff + done, amount);
+ str_buf_append(*outbuf, val, bsize);
+ done += amount;
+ }
+ free(val);
+#undef BASE64_CHUNK_SIZE
+#undef BASE64_BUF_SIZE
+ }
+ 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, 24);
+ if (!val) return PLIST_ERR_NO_MEM;
+ struct tm _tmcopy;
+ copy_TM64_to_tm(btime, &_tmcopy);
+ val_len = strftime(val, 24, "%Y-%m-%dT%H:%M:%SZ", &_tmcopy);
+ if (val_len > 0) {
+ str_buf_append(*outbuf, val, val_len);
+ }
+ free(val);
+ }
+ }
+ break;
+
+ case PLIST_UID:
+ {
+ str_buf_append(*outbuf, "CF$UID:", 7);
+ 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;
+ 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, uint32_t indent, 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;
+ }
+
+ // mark as visited
+ 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, indent, visited);
+ if (res != PLIST_ERR_SUCCESS) {
+ return res;
+ }
+ }
+ switch (data->type) {
+ case PLIST_DICT:
+ *size += n_children-1; // number of ':' and ' '
+ *size += n_children; // number of '\n' and extra space
+ *size += (uint64_t)n_children * (depth+indent+1); // indent for every 2nd child
+ *size += indent+1; // additional '\n'
+ break;
+ case PLIST_ARRAY:
+ *size += n_children-1; // number of ','
+ *size += n_children; // number of '\n'
+ *size += (uint64_t)n_children * ((depth+indent+1)<<1); // indent for every child
+ *size += indent+1; // additional '\n'
+ break;
+ default:
+ break;
+ }
+ } else {
+ switch (data->type) {
+ case PLIST_STRING:
+ case PLIST_KEY:
+ *size += data->length;
+ 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 += ((data->boolval) ? 4 : 5);
+ break;
+ case PLIST_NULL:
+ *size += 4;
+ break;
+ case PLIST_DICT:
+ case PLIST_ARRAY:
+ *size += 3;
+ break;
+ case PLIST_DATA:
+ *size += (data->length / 3) * 4 + 4;
+ break;
+ case PLIST_DATE:
+ *size += 23;
+ break;
+ case PLIST_UID:
+ *size += 7; // "CF$UID:"
+ *size += num_digits_u(data->intval);
+ break;
+ default:
+#ifdef DEBUG
+ fprintf(stderr, "%s: invalid node type encountered\n", __func__);
+#endif
+ return PLIST_ERR_UNKNOWN;
+ }
+ }
+ if (depth == 0) {
+ *size += 1; // final newline
+ }
+ return PLIST_ERR_SUCCESS;
+}
+
+static plist_err_t node_estimate_size(node_t node, uint64_t *size, uint32_t depth, uint32_t indent)
+{
+ 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, indent, 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)
+{
+ uint8_t indent = 0;
+ if (options & PLIST_OPT_INDENT) {
+ indent = (options >> 24) & 0xFF;
+ }
+ uint8_t i;
+ for (i = 0; i < indent; i++) {
+ str_buf_append(outbuf, " ", 1);
+ }
+ plist_err_t res = node_to_string((node_t)plist, &outbuf, 0, indent);
+ 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_limd(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;
+ }
+
+ uint8_t indent = 0;
+ if (options & PLIST_OPT_INDENT) {
+ indent = (options >> 24) & 0xFF;
+ }
+
+ res = node_estimate_size((node_t)plist, &size, 0, indent);
+ 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_limd(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;
+}
diff --git a/src/out-plutil.c b/src/out-plutil.c
new file mode 100644
index 0000000..e603f31
--- /dev/null
+++ b/src/out-plutil.c
@@ -0,0 +1,477 @@
+/*
+ * out-plutil.c
+ * plutil-like *output-only* format - NOT for machine parsing
+ *
+ * Copyright (c) 2023 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
+ */
+
+#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;
+ }
+
+ // mark as visited
+ 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; // '{' and '}'
+ *size += n_children-1; // number of ':' and ','
+ *size += n_children; // number of '\n' and extra space
+ *size += (uint64_t)n_children * (depth+1); // indent for every 2nd child
+ *size += 1; // additional '\n'
+ break;
+ case PLIST_ARRAY:
+ *size += 2; // '[' and ']'
+ *size += n_children-1; // number of ','
+ *size += n_children; // number of '\n'
+ *size += (uint64_t)n_children * ((depth+1)<<1); // indent for every child
+ *size += 1; // additional '\n'
+ break;
+ default:
+ break;
+ }
+ *size += (depth << 1); // indent for {} and []
+ } 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; // final newline
+ }
+ 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;
+}
diff --git a/src/plist.c b/src/plist.c
index 2b31fdd..05af457 100644
--- a/src/plist.c
+++ b/src/plist.c
@@ -1,8 +1,10 @@
/*
* plist.c
- * Builds plist XML structures.
+ * Builds plist XML structures
*
- * Copyright (c) 2008 Zach C. All Rights Reserved.
+ * Copyright (c) 2009-2023 Nikias Bassen, All Rights Reserved.
+ * Copyright (c) 2010-2015 Martin Szulecki, All Rights Reserved.
+ * Copyright (c) 2008 Zach C., 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
@@ -19,77 +21,516 @@
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+#define _GNU_SOURCE 1
#include <string.h>
-#include <assert.h>
#include "plist.h"
#include <stdlib.h>
#include <stdio.h>
+#include <math.h>
+#include <assert.h>
+#include <limits.h>
+#include <float.h>
+#include <ctype.h>
+#include <inttypes.h>
+
+#ifdef WIN32
+#include <windows.h>
+#endif
#include <node.h>
-#include <node_iterator.h>
+#include <node_list.h>
+#include <hashtable.h>
+#include <ptrarray.h>
+
+#include "common.h"
+
+#ifdef _MSC_VER
+typedef SSIZE_T ssize_t;
+#endif
+
+#ifdef DEBUG
+static int plist_debug = 0;
+#define PLIST_ERR(...) if (plist_debug > 0) { fprintf(stderr, "libplist ERROR: " __VA_ARGS__); }
+#else
+#define PLIST_ERR(...)
+#endif
+
+#ifndef bswap16
+#define bswap16(x) ((((x) & 0xFF00) >> 8) | (((x) & 0x00FF) << 8))
+#endif
+
+#ifndef bswap32
+#define bswap32(x) ((((x) & 0xFF000000) >> 24) \
+ | (((x) & 0x00FF0000) >> 8) \
+ | (((x) & 0x0000FF00) << 8) \
+ | (((x) & 0x000000FF) << 24))
+#endif
+
+#ifndef bswap64
+#define bswap64(x) ((((x) & 0xFF00000000000000ull) >> 56) \
+ | (((x) & 0x00FF000000000000ull) >> 40) \
+ | (((x) & 0x0000FF0000000000ull) >> 24) \
+ | (((x) & 0x000000FF00000000ull) >> 8) \
+ | (((x) & 0x00000000FF000000ull) << 8) \
+ | (((x) & 0x0000000000FF0000ull) << 24) \
+ | (((x) & 0x000000000000FF00ull) << 40) \
+ | (((x) & 0x00000000000000FFull) << 56))
+#endif
+
+#ifndef le16toh
+#ifdef __BIG_ENDIAN__
+#define le16toh(x) bswap16(x)
+#else
+#define le16toh(x) (x)
+#endif
+#endif
+
+#ifndef le32toh
+#ifdef __BIG_ENDIAN__
+#define le32toh(x) bswap32(x)
+#else
+#define le32toh(x) (x)
+#endif
+#endif
+
+#ifndef le64toh
+#ifdef __BIG_ENDIAN__
+#define le64toh(x) bswap64(x)
+#else
+#define le64toh(x) (x)
+#endif
+#endif
+
+// Reference: https://stackoverflow.com/a/2390626/1806760
+// Initializer/finalizer sample for MSVC and GCC/Clang.
+// 2010-2016 Joe Lowe. Released into the public domain.
+
+#ifdef __cplusplus
+ #define INITIALIZER(f) \
+ static void f(void); \
+ struct f##_t_ { f##_t_(void) { f(); } }; static f##_t_ f##_; \
+ static void f(void)
+#elif defined(_MSC_VER)
+ #pragma section(".CRT$XCU",read)
+ #define INITIALIZER2_(f,p) \
+ static void f(void); \
+ __declspec(allocate(".CRT$XCU")) void (*f##_)(void) = f; \
+ __pragma(comment(linker,"/include:" p #f "_")) \
+ static void f(void)
+ #ifdef _WIN64
+ #define INITIALIZER(f) INITIALIZER2_(f,"")
+ #else
+ #define INITIALIZER(f) INITIALIZER2_(f,"_")
+ #endif
+#else
+ #define INITIALIZER(f) \
+ static void f(void) __attribute__((__constructor__)); \
+ static void f(void)
+#endif
+
+extern void plist_xml_init(void);
+extern void plist_xml_deinit(void);
+extern void plist_bin_init(void);
+extern void plist_bin_deinit(void);
+extern void plist_json_init(void);
+extern void plist_json_deinit(void);
+extern void plist_ostep_init(void);
+extern void plist_ostep_deinit(void);
+
+static void internal_plist_deinit(void)
+{
+ plist_bin_deinit();
+ plist_xml_deinit();
+ plist_json_deinit();
+ plist_ostep_deinit();
+}
+
+INITIALIZER(internal_plist_init)
+{
+ plist_bin_init();
+ plist_xml_init();
+ plist_json_init();
+ plist_ostep_init();
+ atexit(internal_plist_deinit);
+}
+
+#ifndef HAVE_MEMMEM
+// see https://sourceware.org/legacy-ml/libc-alpha/2007-12/msg00000.html
+
+#ifndef _LIBC
+# define __builtin_expect(expr, val) (expr)
+#endif
+
+#undef memmem
+
+/* Return the first occurrence of NEEDLE in HAYSTACK. */
+void* memmem(const void* haystack, size_t haystack_len, const void* needle, size_t needle_len)
+{
+ /* not really Rabin-Karp, just using additive hashing */
+ char* haystack_ = (char*)haystack;
+ char* needle_ = (char*)needle;
+ int hash = 0; /* this is the static hash value of the needle */
+ int hay_hash = 0; /* rolling hash over the haystack */
+ char* last;
+ size_t i;
+
+ if (haystack_len < needle_len)
+ return NULL;
+
+ if (!needle_len)
+ return haystack_;
+
+ /* initialize hashes */
+ for (i = needle_len; i; --i) {
+ hash += *needle_++;
+ hay_hash += *haystack_++;
+ }
+
+ /* iterate over the haystack */
+ haystack_ = (char*)haystack;
+ needle_ = (char*)needle;
+ last = haystack_+(haystack_len - needle_len + 1);
+ for (; haystack_ < last; ++haystack_) {
+ if (__builtin_expect(hash == hay_hash, 0)
+ && *haystack_ == *needle_ /* prevent calling memcmp, was a optimization from existing glibc */
+ && !memcmp (haystack_, needle_, needle_len)) {
+ return haystack_;
+ }
+ /* roll the hash */
+ hay_hash -= *haystack_;
+ hay_hash += *(haystack_+needle_len);
+ }
+ return NULL;
+}
+#endif
+
+int plist_is_binary(const char *plist_data, uint32_t length)
+{
+ if (plist_data == NULL || length < 8) {
+ return 0;
+ }
+
+ return (memcmp(plist_data, "bplist00", 8) == 0);
+}
+
+#define SKIP_WS(blob, pos, len) \
+ while (pos < len && ((blob[pos] == ' ') || (blob[pos] == '\t') || (blob[pos] == '\r') || (blob[pos] == '\n'))) pos++;
+#define FIND_NEXT(blob, pos, len, chr) \
+ while (pos < len && (blob[pos] != chr)) pos++;
+
+plist_err_t plist_from_memory(const char *plist_data, uint32_t length, plist_t *plist, plist_format_t *format)
+{
+ plist_err_t res = PLIST_ERR_UNKNOWN;
+ if (!plist) {
+ return PLIST_ERR_INVALID_ARG;
+ }
+ *plist = NULL;
+ if (!plist_data || length == 0) {
+ return PLIST_ERR_INVALID_ARG;
+ }
+ plist_format_t fmt = PLIST_FORMAT_NONE;
+ if (format) *format = PLIST_FORMAT_NONE;
+ if (plist_is_binary(plist_data, length)) {
+ res = plist_from_bin(plist_data, length, plist);
+ fmt = PLIST_FORMAT_BINARY;
+ } else {
+ uint32_t pos = 0;
+ int is_json = 0;
+ int is_xml = 0;
+ /* skip whitespace */
+ SKIP_WS(plist_data, pos, length);
+ if (pos >= length) {
+ return PLIST_ERR_PARSE;
+ }
+ if (plist_data[pos] == '<' && (length-pos > 3) && !isxdigit(plist_data[pos+1]) && !isxdigit(plist_data[pos+2]) && !isxdigit(plist_data[pos+3])) {
+ is_xml = 1;
+ } else if (plist_data[pos] == '[') {
+ /* only valid for json */
+ is_json = 1;
+ } else if (plist_data[pos] == '(') {
+ /* only valid for openstep */
+ } else if (plist_data[pos] == '{') {
+ /* this could be json or openstep */
+ pos++;
+ SKIP_WS(plist_data, pos, length);
+ if (pos >= length) {
+ return PLIST_ERR_PARSE;
+ }
+ if (plist_data[pos] == '"') {
+ /* still could be both */
+ pos++;
+ while (pos < length) {
+ FIND_NEXT(plist_data, pos, length, '"');
+ if (plist_data[pos-1] != '\\') {
+ break;
+ }
+ pos++;
+ }
+ if (pos >= length) {
+ return PLIST_ERR_PARSE;
+ }
+ if (plist_data[pos] == '"') {
+ pos++;
+ SKIP_WS(plist_data, pos, length);
+ if (pos >= length) {
+ return PLIST_ERR_PARSE;
+ }
+ if (plist_data[pos] == ':') {
+ /* this is definitely json */
+ is_json = 1;
+ }
+ }
+ }
+ }
+ if (is_xml) {
+ res = plist_from_xml(plist_data, length, plist);
+ fmt = PLIST_FORMAT_XML;
+ } else if (is_json) {
+ res = plist_from_json(plist_data, length, plist);
+ fmt = PLIST_FORMAT_JSON;
+ } else {
+ res = plist_from_openstep(plist_data, length, plist);
+ fmt = PLIST_FORMAT_OSTEP;
+ }
+ }
+ if (format && res == PLIST_ERR_SUCCESS) {
+ *format = fmt;
+ }
+ return res;
+}
+
+plist_err_t plist_read_from_file(const char *filename, plist_t *plist, plist_format_t *format)
+{
+ if (!filename || !plist) {
+ return PLIST_ERR_INVALID_ARG;
+ }
+ FILE *f = fopen(filename, "rb");
+ if (!f) {
+ return PLIST_ERR_IO;
+ }
+ struct stat fst;
+ fstat(fileno(f), &fst);
+ if ((uint64_t)fst.st_size > UINT32_MAX) {
+ return PLIST_ERR_NO_MEM;
+ }
+ uint32_t total = (uint32_t)fst.st_size;
+ if (total == 0) {
+ return PLIST_ERR_PARSE;
+ }
+ char *buf = (char*)malloc(total);
+ if (!buf) {
+ fclose(f);
+ return PLIST_ERR_NO_MEM;
+ }
+ uint32_t done = 0;
+ while (done < total) {
+ ssize_t r = fread(buf + done, 1, total - done, f);
+ if (r <= 0) {
+ break;
+ }
+ done += r;
+ }
+ fclose(f);
+ if (done < total) {
+ free(buf);
+ return PLIST_ERR_IO;
+ }
+ plist_err_t res = plist_from_memory(buf, total, plist, format);
+ free(buf);
+ return res;
+}
plist_t plist_new_node(plist_data_t data)
{
return (plist_t) node_create(NULL, data);
}
-plist_data_t plist_get_data(const plist_t node)
+plist_data_t plist_get_data(plist_t node)
{
if (!node)
return NULL;
- return ((node_t*)node)->data;
+ return (plist_data_t)((node_t)node)->data;
}
plist_data_t plist_new_plist_data(void)
{
- plist_data_t data = (plist_data_t) calloc(sizeof(struct plist_data_s), 1);
- return data;
+ return (plist_data_t) calloc(1, sizeof(struct plist_data_s));
}
-static void plist_free_data(plist_data_t data)
+static unsigned int dict_key_hash(const void *data)
{
- if (data)
- {
- switch (data->type)
- {
+ plist_data_t keydata = (plist_data_t)data;
+ unsigned int hash = 5381;
+ size_t i;
+ char *str = keydata->strval;
+ for (i = 0; i < keydata->length; str++, i++) {
+ hash = ((hash << 5) + hash) + *str;
+ }
+ return hash;
+}
+
+static int dict_key_compare(const void* a, const void* b)
+{
+ plist_data_t data_a = (plist_data_t)a;
+ plist_data_t data_b = (plist_data_t)b;
+ if (data_a->strval == NULL || data_b->strval == NULL) {
+ return FALSE;
+ }
+ if (data_a->length != data_b->length) {
+ return FALSE;
+ }
+ return (strcmp(data_a->strval, data_b->strval) == 0) ? TRUE : FALSE;
+}
+
+static void _plist_free_data(plist_data_t data)
+{
+ if (!data) return;
+ switch (data->type) {
case PLIST_KEY:
case PLIST_STRING:
free(data->strval);
+ data->strval = NULL;
break;
case PLIST_DATA:
free(data->buff);
+ data->buff = NULL;
+ break;
+ case PLIST_ARRAY:
+ ptr_array_free((ptrarray_t*)data->hashtable);
+ data->hashtable = NULL;
+ break;
+ case PLIST_DICT: {
+ hashtable_t *ht = (hashtable_t*)data->hashtable;
+ // PLIST_DICT hashtables must not own/free values; values are freed via node tree.
+ assert(!ht || ht->free_func == NULL);
+ if (ht) ht->free_func = NULL;
+ hash_table_destroy(ht);
+ data->hashtable = NULL;
break;
+ }
default:
break;
+ }
+}
+
+void plist_free_data(plist_data_t data)
+{
+ if (!data) return;
+ _plist_free_data(data);
+ free(data);
+}
+
+static int plist_free_children(node_t root)
+{
+ if (!root) return NODE_ERR_INVALID_ARG;
+
+ if (!node_first_child(root)) {
+ return NODE_ERR_SUCCESS;
+ }
+
+ size_t cap = 64, sp = 0;
+ node_t *stack = (node_t*)malloc(cap * sizeof(*stack));
+ if (!stack) return NODE_ERR_NO_MEM;
+
+ // Push *direct* children onto the stack, detached from root.
+ for (;;) {
+ node_t ch = node_first_child(root);
+ if (!ch) break;
+
+ int di = node_detach(root, ch);
+ if (di < 0) {
+ free(stack);
+ return di;
}
- free(data);
+
+ if (sp == cap) {
+ cap += 64;
+ node_t *tmp = (node_t*)realloc(stack, cap * sizeof(*stack));
+ if (!tmp) {
+ free(stack);
+ return NODE_ERR_NO_MEM;
+ }
+ stack = tmp;
+ }
+ stack[sp++] = ch;
}
+
+ // Now free the detached subtree nodes (and their descendants).
+ while (sp) {
+ node_t node = stack[sp - 1];
+ node_t ch = node_first_child(node);
+ if (ch) {
+ int di = node_detach(node, ch);
+ if (di < 0) {
+ free(stack);
+ return di;
+ }
+
+ if (sp == cap) {
+ cap += 64;
+ node_t *tmp = (node_t*)realloc(stack, cap * sizeof(*stack));
+ if (!tmp) {
+ free(stack);
+ return NODE_ERR_NO_MEM;
+ }
+ stack = tmp;
+ }
+ stack[sp++] = ch;
+ continue;
+ }
+
+ plist_data_t data = plist_get_data(node);
+ plist_free_data(data);
+ node->data = NULL;
+
+ node_destroy(node);
+
+ sp--;
+ }
+
+ free(stack);
+ return NODE_ERR_SUCCESS;
}
-static int plist_free_node(node_t* node)
+static int plist_free_node(node_t root)
{
- plist_data_t data = NULL;
- int index = node_detach(node->parent, node);
- data = plist_get_data(node);
- plist_free_data(data);
- node->data = NULL;
+ if (!root) return NODE_ERR_INVALID_ARG;
+
+ int root_index = -1;
+
+ if (root->parent) {
+ root_index = node_detach(root->parent, root);
+ if (root_index < 0) {
+ return root_index;
+ }
+ }
- node_iterator_t *ni = node_iterator_create(node->children);
- node_t *ch;
- while ((ch = node_iterator_next(ni))) {
- plist_free_node(ch);
+ int r = plist_free_children(root);
+ if (r < 0) {
+ // root is already detached; caller should treat as error.
+ return r;
}
- node_iterator_destroy(ni);
- node_destroy(node);
+ plist_data_t data = plist_get_data(root);
+ plist_free_data(data);
+ root->data = NULL;
+
+ node_destroy(root);
- return index;
+ return root_index;
}
plist_t plist_new_dict(void)
{
plist_data_t data = plist_new_plist_data();
+ if (!data) {
+ PLIST_ERR("%s: failed to allocate plist data\n", __func__);
+ return NULL;
+ }
data->type = PLIST_DICT;
return plist_new_node(data);
}
@@ -97,6 +538,10 @@ plist_t plist_new_dict(void)
plist_t plist_new_array(void)
{
plist_data_t data = plist_new_plist_data();
+ if (!data) {
+ PLIST_ERR("%s: failed to allocate plist data\n", __func__);
+ return NULL;
+ }
data->type = PLIST_ARRAY;
return plist_new_node(data);
}
@@ -105,24 +550,48 @@ plist_t plist_new_array(void)
static plist_t plist_new_key(const char *val)
{
plist_data_t data = plist_new_plist_data();
+ if (!data) {
+ PLIST_ERR("%s: failed to allocate plist data\n", __func__);
+ return NULL;
+ }
data->type = PLIST_KEY;
data->strval = strdup(val);
- data->length = strlen(val);
+ if (!data->strval) {
+ plist_free_data(data);
+ PLIST_ERR("%s: strdup failed\n", __func__);
+ return NULL;
+ } else {
+ data->length = strlen(val);
+ }
return plist_new_node(data);
}
plist_t plist_new_string(const char *val)
{
plist_data_t data = plist_new_plist_data();
+ if (!data) {
+ PLIST_ERR("%s: failed to allocate plist data\n", __func__);
+ return NULL;
+ }
data->type = PLIST_STRING;
data->strval = strdup(val);
- data->length = strlen(val);
+ if (!data->strval) {
+ plist_free_data(data);
+ PLIST_ERR("%s: strdup failed\n", __func__);
+ return NULL;
+ } else {
+ data->length = strlen(val);
+ }
return plist_new_node(data);
}
plist_t plist_new_bool(uint8_t val)
{
plist_data_t data = plist_new_plist_data();
+ if (!data) {
+ PLIST_ERR("%s: failed to allocate plist data\n", __func__);
+ return NULL;
+ }
data->type = PLIST_BOOLEAN;
data->boolval = val;
data->length = sizeof(uint8_t);
@@ -132,7 +601,37 @@ plist_t plist_new_bool(uint8_t val)
plist_t plist_new_uint(uint64_t val)
{
plist_data_t data = plist_new_plist_data();
- data->type = PLIST_UINT;
+ if (!data) {
+ PLIST_ERR("%s: failed to allocate plist data\n", __func__);
+ return NULL;
+ }
+ data->type = PLIST_INT;
+ data->intval = val;
+ data->length = (val > INT_MAX) ? sizeof(uint64_t)*2 : sizeof(uint64_t);
+ return plist_new_node(data);
+}
+
+plist_t plist_new_int(int64_t val)
+{
+ plist_data_t data = plist_new_plist_data();
+ if (!data) {
+ PLIST_ERR("%s: failed to allocate plist data\n", __func__);
+ return NULL;
+ }
+ data->type = PLIST_INT;
+ data->intval = val;
+ data->length = sizeof(uint64_t);
+ return plist_new_node(data);
+}
+
+plist_t plist_new_uid(uint64_t val)
+{
+ plist_data_t data = plist_new_plist_data();
+ if (!data) {
+ PLIST_ERR("%s: failed to allocate plist data\n", __func__);
+ return NULL;
+ }
+ data->type = PLIST_UID;
data->intval = val;
data->length = sizeof(uint64_t);
return plist_new_node(data);
@@ -141,6 +640,10 @@ plist_t plist_new_uint(uint64_t val)
plist_t plist_new_real(double val)
{
plist_data_t data = plist_new_plist_data();
+ if (!data) {
+ PLIST_ERR("%s: failed to allocate plist data\n", __func__);
+ return NULL;
+ }
data->type = PLIST_REAL;
data->realval = val;
data->length = sizeof(double);
@@ -150,9 +653,19 @@ plist_t plist_new_real(double val)
plist_t plist_new_data(const char *val, uint64_t length)
{
plist_data_t data = plist_new_plist_data();
+ if (!data) {
+ PLIST_ERR("%s: failed to allocate plist data\n", __func__);
+ return NULL;
+ }
data->type = PLIST_DATA;
- data->buff = (uint8_t *) malloc(length);
- memcpy(data->buff, val, length);
+ if (val && length) {
+ data->buff = (uint8_t *) malloc(length);
+ if (!data->buff) {
+ PLIST_ERR("%s: failed to allocate %" PRIu64 " bytes\n", __func__, length);
+ return NULL;
+ }
+ memcpy(data->buff, val, length);
+ }
data->length = length;
return plist_new_node(data);
}
@@ -160,10 +673,39 @@ plist_t plist_new_data(const char *val, uint64_t length)
plist_t plist_new_date(int32_t sec, int32_t usec)
{
plist_data_t data = plist_new_plist_data();
+ if (!data) {
+ PLIST_ERR("%s: failed to allocate plist data\n", __func__);
+ return NULL;
+ }
data->type = PLIST_DATE;
- data->timeval.tv_sec = sec;
- data->timeval.tv_usec = usec;
- data->length = sizeof(struct timeval);
+ data->realval = (double)sec + (double)usec / 1000000;
+ data->length = sizeof(double);
+ return plist_new_node(data);
+}
+
+plist_t plist_new_unix_date(int64_t sec)
+{
+ plist_data_t data = plist_new_plist_data();
+ if (!data) {
+ PLIST_ERR("%s: failed to allocate plist data\n", __func__);
+ return NULL;
+ }
+ data->type = PLIST_DATE;
+ data->realval = (double)sec - MAC_EPOCH;
+ data->length = sizeof(double);
+ return plist_new_node(data);
+}
+
+plist_t plist_new_null(void)
+{
+ plist_data_t data = plist_new_plist_data();
+ if (!data) {
+ PLIST_ERR("%s: failed to allocate plist data\n", __func__);
+ return NULL;
+ }
+ data->type = PLIST_NULL;
+ data->intval = 0;
+ data->length = 0;
return plist_new_node(data);
}
@@ -171,70 +713,248 @@ void plist_free(plist_t plist)
{
if (plist)
{
- plist_free_node(plist);
+ plist_free_node((node_t)plist);
}
}
-static void plist_copy_node(node_t *node, void *parent_node_ptr)
+void plist_mem_free(void* ptr)
{
- plist_type node_type = PLIST_NONE;
- plist_t newnode = NULL;
+ if (ptr)
+ {
+ free(ptr);
+ }
+}
+
+static int plist_copy_node_shallow(node_t node, plist_t *out_newnode, plist_data_t *out_newdata, plist_type *out_type)
+{
+ if (!node || !out_newnode || !out_newdata || !out_type) return NODE_ERR_INVALID_ARG;
+
plist_data_t data = plist_get_data(node);
- plist_data_t newdata = plist_new_plist_data();
+ if (!data) return NODE_ERR_INVALID_ARG;
- assert(data); // plist should always have data
+ plist_data_t newdata = plist_new_plist_data();
+ if (!newdata) return NODE_ERR_NO_MEM;
memcpy(newdata, data, sizeof(struct plist_data_s));
- node_type = plist_get_node_type(node);
- if (node_type == PLIST_DATA || node_type == PLIST_STRING || node_type == PLIST_KEY)
- {
- switch (node_type)
- {
+ plist_type node_type = plist_get_node_type(node);
+ switch (node_type) {
case PLIST_DATA:
- newdata->buff = (uint8_t *) malloc(data->length);
- memcpy(newdata->buff, data->buff, data->length);
+ if (data->buff) {
+ newdata->buff = (uint8_t*)malloc(data->length);
+ if (!newdata->buff) {
+ plist_free_data(newdata);
+ return NODE_ERR_NO_MEM;
+ }
+ memcpy(newdata->buff, data->buff, data->length);
+ } else {
+ newdata->buff = NULL;
+ newdata->length = 0;
+ }
break;
+
case PLIST_KEY:
case PLIST_STRING:
- newdata->strval = strdup((char *) data->strval);
+ if (data->strval) {
+ size_t n = strlen(data->strval);
+ newdata->strval = (char*)malloc(n+1);
+ if (!newdata->strval) {
+ plist_free_data(newdata);
+ return NODE_ERR_NO_MEM;
+ }
+ memcpy(newdata->strval, data->strval, n+1);
+ newdata->length = (uint64_t)n;
+ } else {
+ newdata->strval = NULL;
+ newdata->length = 0;
+ }
break;
+
+ case PLIST_ARRAY:
+ if (data->hashtable) {
+ ptrarray_t* pa = ptr_array_new(((ptrarray_t*)data->hashtable)->capacity);
+ if (!pa) {
+ plist_free_data(newdata);
+ return NODE_ERR_NO_MEM;
+ }
+ newdata->hashtable = pa;
+ }
+ break;
+
+ case PLIST_DICT:
+ if (data->hashtable) {
+ hashtable_t* ht = hash_table_new(dict_key_hash, dict_key_compare, NULL);
+ if (!ht) {
+ plist_free_data(newdata);
+ return NODE_ERR_NO_MEM;
+ }
+ newdata->hashtable = ht;
+ }
+ break;
+
default:
break;
- }
}
- newnode = plist_new_node(newdata);
- if (*(plist_t*)parent_node_ptr)
- {
- node_attach(*(plist_t*)parent_node_ptr, newnode);
+ plist_t newnode = plist_new_node(newdata);
+ if (!newnode) {
+ plist_free_data(newdata);
+ return NODE_ERR_NO_MEM;
}
- else
- {
- *(plist_t*)parent_node_ptr = newnode;
+
+ *out_newnode = newnode;
+ *out_newdata = newdata;
+ *out_type = node_type;
+ return NODE_ERR_SUCCESS;
+}
+
+static plist_t plist_copy_node(node_t root)
+{
+ typedef struct copy_frame {
+ node_t orig; // original node
+ plist_t copy; // copied node
+ plist_data_t copydata; // copied node's data (for cache updates)
+ plist_type type; // copied node type
+ node_t next_child; // next child of orig to process
+ unsigned int node_index; // child index (for dict key/value odd/even)
+ int depth; // optional depth tracking
+ } copy_frame_t;
+
+ if (!root) return NULL;
+
+ // shallow-copy root first
+ plist_t newroot = NULL;
+ plist_data_t newroot_data = NULL;
+ plist_type newroot_type = PLIST_NONE;
+
+ int r = plist_copy_node_shallow(root, &newroot, &newroot_data, &newroot_type);
+ if (r != NODE_ERR_SUCCESS) {
+ PLIST_ERR("%s: shallow node copy failed (%d)\n", __func__, r);
+ return NULL;
+ }
+
+ // stack of frames
+ size_t cap = 64, sp = 0;
+ copy_frame_t *st = (copy_frame_t*)malloc(cap * sizeof(*st));
+ if (!st) {
+ plist_free_node((node_t)newroot);
+ return NULL;
}
- node_iterator_t *ni = node_iterator_create(node->children);
- node_t *ch;
- while ((ch = node_iterator_next(ni))) {
- plist_copy_node(ch, &newnode);
+ copy_frame_t cf;
+ cf.orig = root;
+ cf.copy = newroot;
+ cf.copydata = newroot_data;
+ cf.type = newroot_type;
+ cf.next_child = node_first_child(root);
+ cf.node_index = 0;
+ cf.depth = 0;
+ st[sp++] = cf;
+
+ while (sp) {
+ copy_frame_t *f = &st[sp - 1];
+
+ if (f->depth > NODE_MAX_DEPTH) {
+ plist_free_node((node_t)newroot);
+ free(st);
+ PLIST_ERR("%s: maximum nesting depth exceeded\n", __func__);
+ return NULL;
+ }
+
+ // done with this node?
+ if (!f->next_child) {
+ sp--;
+ continue;
+ }
+
+ // take next child and advance iterator
+ node_t ch = f->next_child;
+ f->next_child = node_next_sibling(ch);
+
+ // shallow copy child
+ plist_t newch = NULL;
+ plist_data_t newch_data = NULL;
+ plist_type newch_type = PLIST_NONE;
+
+ r = plist_copy_node_shallow(ch, &newch, &newch_data, &newch_type);
+ if (r != NODE_ERR_SUCCESS) {
+ plist_free_node((node_t)newroot);
+ free(st);
+ PLIST_ERR("%s: shallow node copy failed (%d)\n", __func__, r);
+ return NULL;
+ }
+
+ // attach child to copied parent
+ r = node_attach((node_t)f->copy, (node_t)newch);
+ if (r != NODE_ERR_SUCCESS) {
+ plist_free_node((node_t)newch);
+ plist_free_node((node_t)newroot);
+ free(st);
+ PLIST_ERR("%s: failed to attach child to copied parent (%d)\n", __func__, r);
+ return NULL;
+ }
+
+ // update lookup cache on the *parent* copy
+ switch (f->type) {
+ case PLIST_ARRAY:
+ if (f->copydata->hashtable) {
+ ptr_array_add((ptrarray_t*)f->copydata->hashtable, newch);
+ }
+ break;
+
+ case PLIST_DICT:
+ if (f->copydata->hashtable && (f->node_index % 2 != 0)) {
+ node_t new_key = node_prev_sibling((node_t)newch);
+ if (new_key) {
+ hash_table_insert((hashtable_t*)f->copydata->hashtable, new_key->data, newch);
+ }
+ }
+ break;
+
+ default:
+ break;
+ }
+
+ f->node_index++;
+
+ // push child frame to process its children
+ if (sp == cap) {
+ cap += 64;
+ copy_frame_t *tmp = (copy_frame_t*)realloc(st, cap * sizeof(*st));
+ if (!tmp) {
+ plist_free_node((node_t)newroot);
+ free(st);
+ PLIST_ERR("%s: out of memory when reallocating\n", __func__);
+ return NULL;
+ }
+ st = tmp;
+ }
+
+ copy_frame_t nf;
+ nf.orig = ch;
+ nf.copy = newch;
+ nf.copydata = newch_data;
+ nf.type = newch_type;
+ nf.next_child = node_first_child(ch);
+ nf.node_index = 0;
+ nf.depth = f->depth + 1;
+ st[sp++] = nf;
}
- node_iterator_destroy(ni);
+
+ free(st);
+ return newroot;
}
plist_t plist_copy(plist_t node)
{
- plist_t copied = NULL;
- plist_copy_node(node, &copied);
- return copied;
+ return node ? plist_copy_node((node_t)node) : NULL;
}
uint32_t plist_array_get_size(plist_t node)
{
uint32_t ret = 0;
- if (node && PLIST_ARRAY == plist_get_node_type(node))
- {
- ret = node_n_children(node);
+ if (PLIST_IS_ARRAY(node)) {
+ ret = node_n_children((node_t)node);
}
return ret;
}
@@ -242,199 +962,695 @@ uint32_t plist_array_get_size(plist_t node)
plist_t plist_array_get_item(plist_t node, uint32_t n)
{
plist_t ret = NULL;
- if (node && PLIST_ARRAY == plist_get_node_type(node))
- {
- ret = (plist_t)node_nth_child(node, n);
+ if (PLIST_IS_ARRAY(node) && n < INT_MAX) {
+ ptrarray_t *pa = (ptrarray_t*)((plist_data_t)((node_t)node)->data)->hashtable;
+ if (pa) {
+ ret = (plist_t)ptr_array_index(pa, n);
+ } else {
+ ret = (plist_t)node_nth_child((node_t)node, n);
+ }
}
return ret;
}
uint32_t plist_array_get_item_index(plist_t node)
{
- plist_t father = plist_get_parent(node);
- if (PLIST_ARRAY == plist_get_node_type(father))
- {
- return node_child_position(father, node);
+ plist_t parent = plist_get_parent(node);
+ if (PLIST_IS_ARRAY(parent)) {
+ return node_child_position((node_t)parent, (node_t)node);
}
- return 0;
+ return UINT_MAX;
}
-void plist_array_set_item(plist_t node, plist_t item, uint32_t n)
+static void _plist_array_post_insert(plist_t node, plist_t item, long n)
{
- if (node && PLIST_ARRAY == plist_get_node_type(node))
- {
- plist_t old_item = plist_array_get_item(node, n);
- if (old_item)
+ ptrarray_t *pa = (ptrarray_t*)((plist_data_t)((node_t)node)->data)->hashtable;
+ if (pa) {
+ /* store pointer to item in array */
+ ptr_array_insert(pa, item, n);
+ return;
+ }
+
+ if (((node_t)node)->count > 100) {
+ /* make new lookup array */
+ pa = ptr_array_new(128);
+ plist_t current = NULL;
+ for (current = (plist_t)node_first_child((node_t)node);
+ pa && current;
+ current = (plist_t)node_next_sibling((node_t)current))
+ {
+ ptr_array_add(pa, current);
+ }
+ ((plist_data_t)((node_t)node)->data)->hashtable = pa;
+ }
+}
+
+static void _plist_array_post_set(plist_t node, plist_t item, long n)
+{
+ ptrarray_t *pa = (ptrarray_t*)((plist_data_t)((node_t)node)->data)->hashtable;
+
+ if (pa) {
+ if (n < 0 || n >= pa->len) {
+ PLIST_ERR("%s: cache index out of range (n=%ld len=%ld)\n", __func__, n, pa->len);
+ return;
+ }
+ ptr_array_set(pa, item, n);
+ return;
+ }
+
+ if (((node_t)node)->count > 100) {
+ pa = ptr_array_new(128);
+ plist_t current = NULL;
+ for (current = (plist_t)node_first_child((node_t)node);
+ pa && current;
+ current = (plist_t)node_next_sibling((node_t)current))
{
- int idx = plist_free_node(old_item);
- if (idx < 0) {
- node_attach(node, item);
- } else {
- node_insert(node, idx, item);
- }
+ ptr_array_add(pa, current);
+ }
+ ((plist_data_t)((node_t)node)->data)->hashtable = pa;
+
+ // Now that it exists (and is filled), apply the set (will no-op if out of range)
+ if (pa) {
+ ptr_array_set(pa, item, n);
}
}
- return;
}
-void plist_array_append_item(plist_t node, plist_t item)
+plist_err_t plist_array_set_item(plist_t node, plist_t item, uint32_t n)
{
- if (node && PLIST_ARRAY == plist_get_node_type(node))
- {
- node_attach(node, item);
+ if (!PLIST_IS_ARRAY(node) || !item || n >= INT_MAX) {
+ PLIST_ERR("invalid argument passed to %s (node=%p, item=%p, n=%u)\n", __func__, node, item, n);
+ return PLIST_ERR_INVALID_ARG;
+ }
+ node_t it = (node_t)item;
+ if (it->parent != NULL) {
+ assert(it->parent == NULL && "item already has a parent; use plist_copy() or detach first");
+ PLIST_ERR("%s: item already has a parent; use plist_copy() or detach first\n", __func__);
+ return PLIST_ERR_INVALID_ARG;
+ }
+ plist_t old_item = plist_array_get_item(node, n);
+ if (!old_item) return PLIST_ERR_INVALID_ARG;
+
+ int idx = node_detach((node_t)node, (node_t)old_item);
+ if (idx < 0) {
+ PLIST_ERR("%s: Failed to detach old item (err=%d)\n", __func__, idx);
+ return PLIST_ERR_UNKNOWN;
+ }
+
+ int r = node_insert((node_t)node, (unsigned)idx, (node_t)item);
+ if (r != NODE_ERR_SUCCESS) {
+ int rb = node_insert((node_t)node, (unsigned)idx, (node_t)old_item);
+ if (rb == NODE_ERR_SUCCESS) {
+ _plist_array_post_set(node, old_item, idx); // restore cache correctly
+ PLIST_ERR("%s: failed to insert replacement (idx=%d err=%d); rollback succeeded\n", __func__, idx, r);
+ return (r == NODE_ERR_NO_MEM) ? PLIST_ERR_NO_MEM : PLIST_ERR_UNKNOWN;
+ } else {
+ PLIST_ERR("%s: insert failed (err=%d) and rollback failed (err=%d); array now missing element at idx=%d\n", __func__, r, rb, idx);
+ return PLIST_ERR_UNKNOWN;
+ }
}
- return;
+
+ _plist_array_post_set(node, item, idx); // update cache
+ plist_free_node((node_t)old_item);
+
+ return PLIST_ERR_SUCCESS;
}
-void plist_array_insert_item(plist_t node, plist_t item, uint32_t n)
+plist_err_t plist_array_append_item(plist_t node, plist_t item)
{
- if (node && PLIST_ARRAY == plist_get_node_type(node))
- {
- node_insert(node, n, item);
+ if (!PLIST_IS_ARRAY(node) || !item) {
+ PLIST_ERR("invalid argument passed to %s (node=%p, item=%p)\n", __func__, node, item);
+ return PLIST_ERR_INVALID_ARG;
}
- return;
+ node_t it = (node_t)item;
+ if (it->parent != NULL) {
+ assert(it->parent == NULL && "item already has a parent; use plist_copy() or detach first");
+ PLIST_ERR("%s: item already has a parent; use plist_copy() or detach first\n", __func__);
+ return PLIST_ERR_INVALID_ARG;
+ }
+
+ int r = node_attach((node_t)node, (node_t)item);
+ if (r != NODE_ERR_SUCCESS) {
+ PLIST_ERR("%s: failed to append item (err=%d)\n", __func__, r);
+ return PLIST_ERR_UNKNOWN;
+ }
+ _plist_array_post_insert(node, item, -1);
+
+ return PLIST_ERR_SUCCESS;
}
-void plist_array_remove_item(plist_t node, uint32_t n)
+plist_err_t plist_array_insert_item(plist_t node, plist_t item, uint32_t n)
{
- if (node && PLIST_ARRAY == plist_get_node_type(node))
- {
- plist_t old_item = plist_array_get_item(node, n);
- if (old_item)
- {
- plist_free(old_item);
+ if (!PLIST_IS_ARRAY(node) || !item || n >= INT_MAX) {
+ PLIST_ERR("invalid argument passed to %s (node=%p, item=%p, n=%u)\n", __func__, node, item, n);
+ return PLIST_ERR_INVALID_ARG;
+ }
+ node_t it = (node_t)item;
+ if (it->parent != NULL) {
+ assert(it->parent == NULL && "item already has a parent; use plist_copy() or detach first");
+ PLIST_ERR("%s: item already has a parent; use plist_copy() or detach first\n", __func__);
+ return PLIST_ERR_INVALID_ARG;
+ }
+
+ int r = node_insert((node_t)node, n, (node_t)item);
+ if (r != NODE_ERR_SUCCESS) {
+ PLIST_ERR("%s: Failed to insert item at index %u (err=%d)\n", __func__, n, r);
+ return PLIST_ERR_UNKNOWN;
+ }
+ _plist_array_post_insert(node, item, (long)n);
+
+ return PLIST_ERR_SUCCESS;
+}
+
+plist_err_t plist_array_remove_item(plist_t node, uint32_t n)
+{
+ if (!PLIST_IS_ARRAY(node) || n >= INT_MAX || n > plist_array_get_size(node)) {
+ PLIST_ERR("invalid argument passed to %s (node=%p, n=%u)\n", __func__, node, n);
+ return PLIST_ERR_INVALID_ARG;
+ }
+
+ plist_t old_item = plist_array_get_item(node, n);
+ if (!old_item) {
+ PLIST_ERR("item not found at index %u\n", n);
+ return PLIST_ERR_INVALID_ARG;
+ }
+ ptrarray_t* pa = (ptrarray_t*)((plist_data_t)((node_t)node)->data)->hashtable;
+ if (pa) {
+ ptr_array_remove(pa, n);
+ }
+ plist_free(old_item);
+
+ return PLIST_ERR_SUCCESS;
+}
+
+plist_err_t plist_array_item_remove(plist_t item)
+{
+ plist_t parent = plist_get_parent(item);
+ if (PLIST_IS_ARRAY(parent)) {
+ int n = node_child_position((node_t)parent, (node_t)item);
+ if (n < 0) return PLIST_ERR_INVALID_ARG;
+ ptrarray_t* pa = (ptrarray_t*)((plist_data_t)((node_t)parent)->data)->hashtable;
+ if (pa) {
+ ptr_array_remove(pa, n);
}
+ plist_free(item);
}
- return;
+ return PLIST_ERR_SUCCESS;
+}
+
+typedef struct {
+ node_t cur;
+} plist_array_iter_private;
+
+void plist_array_new_iter(plist_t node, plist_array_iter *iter)
+{
+ if (!iter) return;
+ *iter = NULL;
+ if (!PLIST_IS_ARRAY(node)) return;
+
+ plist_array_iter_private* it = (plist_array_iter_private*)malloc(sizeof(*it));
+ if (!it) return;
+ it->cur = node_first_child((node_t)node);
+ *iter = (plist_array_iter)it;
+}
+
+void plist_array_next_item(plist_t node, plist_array_iter iter, plist_t *item)
+{
+ if (item) *item = NULL;
+ if (!iter) return;
+ if (!PLIST_IS_ARRAY(node)) return;
+
+ plist_array_iter_private* it = (plist_array_iter_private*)iter;
+ node_t cur = it->cur;
+ if (!cur) return;
+
+ if (item) {
+ *item = (plist_t)cur;
+ }
+ it->cur = node_next_sibling(cur);
+}
+
+void plist_array_free_iter(plist_array_iter iter)
+{
+ free(iter);
}
uint32_t plist_dict_get_size(plist_t node)
{
uint32_t ret = 0;
- if (node && PLIST_DICT == plist_get_node_type(node))
- {
- ret = node_n_children(node) / 2;
+ if (PLIST_IS_DICT(node)) {
+ ret = node_n_children((node_t)node) / 2;
}
return ret;
}
+typedef struct {
+ node_t cur;
+} plist_dict_iter_private;
+
void plist_dict_new_iter(plist_t node, plist_dict_iter *iter)
{
- if (iter && *iter == NULL)
- {
- *iter = malloc(sizeof(uint32_t));
- *((uint32_t*)(*iter)) = 0;
- }
- return;
+ if (!iter) return;
+ *iter = NULL;
+ if (!PLIST_IS_DICT(node)) return;
+
+ plist_dict_iter_private* it = (plist_dict_iter_private*)malloc(sizeof(*it));
+ if (!it) return;
+ it->cur = node_first_child((node_t)node);
+ *iter = (plist_dict_iter)it;
}
void plist_dict_next_item(plist_t node, plist_dict_iter iter, char **key, plist_t *val)
{
- uint32_t* iter_int = (uint32_t*) iter;
+ if (key) *key = NULL;
+ if (val) *val = NULL;
+ if (!iter) return;
+ if (!PLIST_IS_DICT(node)) return;
- if (key)
- {
- *key = NULL;
- }
- if (val)
- {
- *val = NULL;
- }
+ plist_dict_iter_private* it = (plist_dict_iter_private*)iter;
- if (node && PLIST_DICT == plist_get_node_type(node) && *iter_int < node_n_children(node))
- {
+ node_t k = it->cur;
+ if (!k) return;
- if (key)
- {
- plist_get_key_val((plist_t)node_nth_child(node, *iter_int), key);
- }
+ if (!PLIST_IS_KEY((plist_t)k)) {
+ // malformed dict, terminate iteration
+ it->cur = NULL;
+ return;
+ }
- if (val)
- {
- *val = (plist_t) node_nth_child(node, *iter_int + 1);
- }
+ node_t v = node_next_sibling(k);
+ if (!v) {
+ // key without value, terminate iteration
+ it->cur = NULL;
+ return;
+ }
- *iter_int += 2;
+ if (key) {
+ plist_get_key_val((plist_t)k, key);
}
- return;
+ if (val) {
+ *val = (plist_t)v;
+ }
+ it->cur = node_next_sibling(v);
+}
+
+void plist_dict_free_iter(plist_dict_iter iter)
+{
+ free(iter);
}
void plist_dict_get_item_key(plist_t node, char **key)
{
- plist_t father = plist_get_parent(node);
- if (PLIST_DICT == plist_get_node_type(father))
- {
- plist_get_key_val( (plist_t) node_prev_sibling(node), key);
+ plist_t parent = plist_get_parent(node);
+ if (PLIST_IS_DICT(parent)) {
+ plist_get_key_val( (plist_t) node_prev_sibling((node_t)node), key);
}
}
+plist_t plist_dict_item_get_key(plist_t node)
+{
+ plist_t ret = NULL;
+ plist_t parent = plist_get_parent(node);
+ if (PLIST_IS_DICT(parent)) {
+ ret = (plist_t)node_prev_sibling((node_t)node);
+ }
+ return ret;
+}
+
plist_t plist_dict_get_item(plist_t node, const char* key)
{
plist_t ret = NULL;
+ if (!PLIST_IS_DICT(node) || !key) {
+ PLIST_ERR("invalid argument passed to %s (node=%p, key=%p)\n", __func__, node, key);
+ return NULL;
+ }
+ plist_data_t data = plist_get_data(node);
+ if (!data) {
+ PLIST_ERR("%s: invalid node\n", __func__);
+ return NULL;
+ }
+ size_t keylen = strlen(key);
+ hashtable_t *ht = (hashtable_t*)data->hashtable;
+ if (ht) {
+ struct plist_data_s sdata = { 0 };
+ sdata.strval = (char*)key;
+ sdata.length = keylen;
+ return (plist_t)hash_table_lookup(ht, &sdata);
+ } else {
+ plist_t k = NULL;
+ for (k = (plist_t)node_first_child((node_t)node); k; ) {
+ plist_t v = (plist_t)node_next_sibling(k);
+ if (!v) break;
+ data = plist_get_data(k);
+ assert(PLIST_IS_KEY(k));
+ if (!PLIST_IS_KEY(k) || !data || !data->strval) {
+ PLIST_ERR("invalid key node at %p\n", k);
+ break;
+ }
+ if (data->length == keylen && !memcmp(key, data->strval, keylen+1)) {
+ ret = v;
+ break;
+ }
+ k = node_next_sibling(v);
+ }
+ }
+ return ret;
+}
- if (node && PLIST_DICT == plist_get_node_type(node))
- {
+plist_err_t plist_dict_set_item(plist_t node, const char* key, plist_t item)
+{
+ if (!PLIST_IS_DICT(node) || !key || !item) {
+ PLIST_ERR("invalid argument passed to %s (node=%p, key=%p, item=%p)\n", __func__, node, key, item);
+ return PLIST_ERR_INVALID_ARG;
+ }
+ node_t it = (node_t)item;
+ if (it->parent != NULL) {
+ assert(it->parent == NULL && "item already has a parent");
+ PLIST_ERR("%s: item already has a parent\n", __func__);
+ return PLIST_ERR_INVALID_ARG;
+ }
- plist_t current = NULL;
- for (current = (plist_t)node_first_child(node);
- current;
- current = (plist_t)node_next_sibling(node_next_sibling(current)))
- {
+ hashtable_t *ht = (hashtable_t*)((plist_data_t)((node_t)node)->data)->hashtable;
+
+ plist_t old_item = plist_dict_get_item(node, key);
+ plist_t key_node = NULL;
+
+ if (old_item) {
+ // --- REPLACE EXISTING VALUE ---
+ node_t old_val = (node_t)old_item;
+ node_t old_key = node_prev_sibling(old_val);
+ if (!old_key) {
+ PLIST_ERR("%s: corrupt dict (value without key)\n", __func__);
+ return PLIST_ERR_UNKNOWN;
+ }
+ if (!PLIST_IS_KEY((plist_t)old_key)) {
+ PLIST_ERR("%s: corrupt dict ('key' node is not PLIST_KEY\n", __func__);
+ return PLIST_ERR_UNKNOWN;
+ }
+
+ // detach old value (do NOT free yet)
+ int idx = node_detach((node_t)node, old_val);
+ if (idx < 0) {
+ PLIST_ERR("%s: failed to detach old value (err=%d)\n", __func__, idx);
+ return PLIST_ERR_UNKNOWN;
+ }
+
+ // insert new value at same position
+ int r = node_insert((node_t)node, (unsigned)idx, (node_t)item);
+ if (r != NODE_ERR_SUCCESS) {
+ // rollback: reinsert old value
+ int rb = node_insert((node_t)node, (unsigned)idx, old_val);
+ if (rb == NODE_ERR_SUCCESS && ht) {
+ hash_table_insert(ht, ((node_t)old_key)->data, old_item);
+ }
+ PLIST_ERR("%s: failed to replace dict value (err=%d)\n", __func__, r);
+ return PLIST_ERR_UNKNOWN;
+ }
+ key_node = old_key;
- plist_data_t data = plist_get_data(current);
- assert( PLIST_KEY == plist_get_node_type(current) );
+ // update hash table
+ if (ht) {
+ hash_table_insert(ht, (plist_data_t)((node_t)key_node)->data, item);
+ }
- if (data && !strcmp(key, data->strval))
+ // now it’s safe to free old value
+ plist_free_node(old_val);
+ } else {
+ // --- INSERT NEW KEY/VALUE PAIR ---
+ key_node = plist_new_key(key);
+ if (!key_node) return PLIST_ERR_NO_MEM;
+
+ int r = node_attach((node_t)node, (node_t)key_node);
+ if (r != NODE_ERR_SUCCESS) {
+ plist_free_node((node_t)key_node);
+ PLIST_ERR("%s: failed to attach dict key (err=%d)\n", __func__, r);
+ return PLIST_ERR_UNKNOWN;
+ }
+ r = node_attach((node_t)node, (node_t)item);
+ if (r != NODE_ERR_SUCCESS) {
+ // rollback key insertion
+ node_detach((node_t)node, (node_t)key_node);
+ plist_free_node((node_t)key_node);
+ PLIST_ERR("%s: failed to attach dict value (err=%d)\n", __func__, r);
+ return PLIST_ERR_UNKNOWN;
+ }
+
+ if (ht) {
+ // store pointer to item in hash table
+ hash_table_insert(ht, (plist_data_t)((node_t)key_node)->data, item);
+ } else if (((node_t)node)->count > 500) {
+ // make new hash table
+ ht = hash_table_new(dict_key_hash, dict_key_compare, NULL);
+ // calculate the hashes for all entries we have so far
+ plist_t current = NULL;
+ for (current = (plist_t)node_first_child((node_t)node);
+ ht && current;
+ current = (plist_t)node_next_sibling(node_next_sibling((node_t)current)))
{
- ret = (plist_t)node_next_sibling(current);
- break;
+ hash_table_insert(ht, ((node_t)current)->data, node_next_sibling((node_t)current));
}
+ ((plist_data_t)((node_t)node)->data)->hashtable = ht;
}
}
- return ret;
+ return PLIST_ERR_SUCCESS;
}
-void plist_dict_set_item(plist_t node, const char* key, plist_t item)
+plist_err_t plist_dict_remove_item(plist_t node, const char* key)
{
- if (node && PLIST_DICT == plist_get_node_type(node))
- {
- node_t* old_item = plist_dict_get_item(node, key);
- if (old_item)
- {
- int idx = plist_free_node(old_item);
- if (idx < 0) {
- node_attach(node, item);
- } else {
- node_insert(node, idx, item);
- }
- }
+ if (!PLIST_IS_DICT(node) || !key) {
+ PLIST_ERR("invalid argument passed to %s (node=%p, key=%p)\n", __func__, node, key);
+ return PLIST_ERR_INVALID_ARG;
+ }
+
+ plist_t old_item = plist_dict_get_item(node, key);
+ if (!old_item) {
+ PLIST_ERR("item not found for key '%s'\n", key);
+ return PLIST_ERR_INVALID_ARG;
+ }
+
+ plist_t key_node = node_prev_sibling((node_t)old_item);
+ hashtable_t* ht = (hashtable_t*)((plist_data_t)((node_t)node)->data)->hashtable;
+ if (ht) {
+ hash_table_remove(ht, ((node_t)key_node)->data);
}
- return;
+ plist_free(key_node);
+ plist_free(old_item);
+
+ return PLIST_ERR_SUCCESS;
}
-void plist_dict_insert_item(plist_t node, const char* key, plist_t item)
+plist_err_t plist_dict_merge(plist_t *target, plist_t source)
{
- if (node && PLIST_DICT == plist_get_node_type(node))
- {
- node_attach(node, plist_new_key(key));
- node_attach(node, item);
- }
- return;
+ if (!target || !PLIST_IS_DICT(*target) || !PLIST_IS_DICT(source))
+ return PLIST_ERR_INVALID_ARG;
+
+ char* key = NULL;
+ plist_dict_iter it = NULL;
+ plist_t subnode = NULL;
+ plist_dict_new_iter(source, &it);
+ if (!it)
+ return PLIST_ERR_NO_MEM;
+
+ do {
+ plist_dict_next_item(source, it, &key, &subnode);
+ if (!key)
+ break;
+
+ plist_dict_set_item(*target, key, plist_copy(subnode));
+ free(key);
+ key = NULL;
+ } while (1);
+ free(it);
+ return PLIST_ERR_SUCCESS;
}
-void plist_dict_remove_item(plist_t node, const char* key)
+uint8_t plist_dict_get_bool(plist_t dict, const char *key)
{
- if (node && PLIST_DICT == plist_get_node_type(node))
- {
- plist_t old_item = plist_dict_get_item(node, key);
- if (old_item)
- {
- plist_t key_node = node_prev_sibling(old_item);
- plist_free(key_node);
- plist_free(old_item);
- }
- }
- return;
+ uint8_t bval = 0;
+ uint64_t uintval = 0;
+ const char *strval = NULL;
+ uint64_t strsz = 0;
+ plist_t node = plist_dict_get_item(dict, key);
+ if (!node) {
+ return 0;
+ }
+ switch (plist_get_node_type(node)) {
+ case PLIST_BOOLEAN:
+ plist_get_bool_val(node, &bval);
+ break;
+ case PLIST_INT:
+ plist_get_uint_val(node, &uintval);
+ bval = (uintval) ? 1 : 0;
+ break;
+ case PLIST_STRING:
+ strval = plist_get_string_ptr(node, NULL);
+ if (strval) {
+ if (strcmp(strval, "true")) {
+ bval = 1;
+ } else if (strcmp(strval, "false")) {
+ bval = 0;
+ } else {
+ PLIST_ERR("%s: invalid string '%s' for string to boolean conversion\n", __func__, strval);
+ }
+ }
+ break;
+ case PLIST_DATA:
+ strval = (const char*)plist_get_data_ptr(node, &strsz);
+ if (strval) {
+ if (strsz == 1) {
+ bval = (strval[0]) ? 1 : 0;
+ } else {
+ PLIST_ERR("%s: invalid size %" PRIu64 " for data to boolean conversion\n", __func__, strsz);
+ }
+ }
+ break;
+ default:
+ break;
+ }
+ return bval;
+}
+
+int64_t plist_dict_get_int(plist_t dict, const char *key)
+{
+ int64_t intval = 0;
+ const char *strval = NULL;
+ uint64_t strsz = 0;
+ plist_t node = plist_dict_get_item(dict, key);
+ if (!node) {
+ return intval;
+ }
+ switch (plist_get_node_type(node)) {
+ case PLIST_INT:
+ plist_get_int_val(node, &intval);
+ break;
+ case PLIST_STRING:
+ strval = plist_get_string_ptr(node, NULL);
+ if (strval) {
+ intval = strtoll(strval, NULL, 0);
+ }
+ break;
+ case PLIST_DATA:
+ strval = (const char*)plist_get_data_ptr(node, &strsz);
+ if (strval) {
+ if (strsz == 8) {
+ intval = le64toh(*(int64_t*)strval);
+ } else if (strsz == 4) {
+ intval = le32toh(*(int32_t*)strval);
+ } else if (strsz == 2) {
+ intval = le16toh(*(int16_t*)strval);
+ } else if (strsz == 1) {
+ intval = strval[0];
+ } else {
+ PLIST_ERR("%s: invalid size %" PRIu64 " for data to integer conversion\n", __func__, strsz);
+ }
+ }
+ break;
+ default:
+ break;
+ }
+ return intval;
+}
+
+
+uint64_t plist_dict_get_uint(plist_t dict, const char *key)
+{
+ uint64_t uintval = 0;
+ const char *strval = NULL;
+ uint64_t strsz = 0;
+ plist_t node = plist_dict_get_item(dict, key);
+ if (!node) {
+ return uintval;
+ }
+ switch (plist_get_node_type(node)) {
+ case PLIST_INT:
+ plist_get_uint_val(node, &uintval);
+ break;
+ case PLIST_STRING:
+ strval = plist_get_string_ptr(node, NULL);
+ if (strval) {
+ uintval = strtoull(strval, NULL, 0);
+ }
+ break;
+ case PLIST_DATA:
+ strval = (const char*)plist_get_data_ptr(node, &strsz);
+ if (strval) {
+ if (strsz == 8) {
+ uintval = le64toh(*(uint64_t*)strval);
+ } else if (strsz == 4) {
+ uintval = le32toh(*(uint32_t*)strval);
+ } else if (strsz == 2) {
+ uintval = le16toh(*(uint16_t*)strval);
+ } else if (strsz == 1) {
+ uintval = strval[0];
+ } else {
+ PLIST_ERR("%s: invalid size %" PRIu64 " for data to integer conversion\n", __func__, strsz);
+ }
+ }
+ break;
+ default:
+ break;
+ }
+ return uintval;
+}
+
+plist_err_t plist_dict_copy_item(plist_t target_dict, plist_t source_dict, const char *key, const char *alt_source_key)
+{
+ plist_t node = plist_dict_get_item(source_dict, (alt_source_key) ? alt_source_key : key);
+ if (!node) {
+ return PLIST_ERR_INVALID_ARG;
+ }
+ plist_dict_set_item(target_dict, key, plist_copy(node));
+ return PLIST_ERR_SUCCESS;
+}
+
+plist_err_t plist_dict_copy_bool(plist_t target_dict, plist_t source_dict, const char *key, const char *alt_source_key)
+{
+ if (plist_dict_get_item(source_dict, (alt_source_key) ? alt_source_key : key) == NULL) {
+ return PLIST_ERR_INVALID_ARG;
+ }
+ uint8_t bval = plist_dict_get_bool(source_dict, (alt_source_key) ? alt_source_key : key);
+ plist_dict_set_item(target_dict, key, plist_new_bool(bval));
+ return PLIST_ERR_SUCCESS;
+}
+
+plist_err_t plist_dict_copy_int(plist_t target_dict, plist_t source_dict, const char *key, const char *alt_source_key)
+{
+ if (plist_dict_get_item(source_dict, (alt_source_key) ? alt_source_key : key) == NULL) {
+ return PLIST_ERR_INVALID_ARG;
+ }
+ int64_t i64val = plist_dict_get_int(source_dict, (alt_source_key) ? alt_source_key : key);
+ plist_dict_set_item(target_dict, key, plist_new_int(i64val));
+ return PLIST_ERR_SUCCESS;
+}
+
+plist_err_t plist_dict_copy_uint(plist_t target_dict, plist_t source_dict, const char *key, const char *alt_source_key)
+{
+ if (plist_dict_get_item(source_dict, (alt_source_key) ? alt_source_key : key) == NULL) {
+ return PLIST_ERR_INVALID_ARG;
+ }
+ uint64_t u64val = plist_dict_get_uint(source_dict, (alt_source_key) ? alt_source_key : key);
+ plist_dict_set_item(target_dict, key, plist_new_uint(u64val));
+ return PLIST_ERR_SUCCESS;
+}
+
+plist_err_t plist_dict_copy_data(plist_t target_dict, plist_t source_dict, const char *key, const char *alt_source_key)
+{
+ plist_t node = plist_dict_get_item(source_dict, (alt_source_key) ? alt_source_key : key);
+ if (!PLIST_IS_DATA(node)) {
+ return PLIST_ERR_INVALID_ARG;
+ }
+ plist_dict_set_item(target_dict, key, plist_copy(node));
+ return PLIST_ERR_SUCCESS;
+}
+
+plist_err_t plist_dict_copy_string(plist_t target_dict, plist_t source_dict, const char *key, const char *alt_source_key)
+{
+ plist_t node = plist_dict_get_item(source_dict, (alt_source_key) ? alt_source_key : key);
+ if (!PLIST_IS_STRING(node)) {
+ return PLIST_ERR_INVALID_ARG;
+ }
+ plist_dict_set_item(target_dict, key, plist_copy(node));
+ return PLIST_ERR_SUCCESS;
}
plist_t plist_access_pathv(plist_t plist, uint32_t length, va_list v)
@@ -476,10 +1692,11 @@ static void plist_get_type_and_value(plist_t node, plist_type * type, void *valu
{
plist_data_t data = NULL;
- if (!node)
+ if (!node || !type || !value || !length)
return;
data = plist_get_data(node);
+ if (!data) return;
*type = data->type;
*length = data->length;
@@ -489,25 +1706,30 @@ static void plist_get_type_and_value(plist_t node, plist_type * type, void *valu
case PLIST_BOOLEAN:
*((char *) value) = data->boolval;
break;
- case PLIST_UINT:
+ case PLIST_INT:
+ case PLIST_UID:
*((uint64_t *) value) = data->intval;
break;
case PLIST_REAL:
+ case PLIST_DATE:
*((double *) value) = data->realval;
break;
case PLIST_KEY:
case PLIST_STRING:
*((char **) value) = strdup(data->strval);
+ if (!*((char **) value)) {
+ PLIST_ERR("%s: strdup failed\n", __func__);
+ return;
+ }
break;
case PLIST_DATA:
*((uint8_t **) value) = (uint8_t *) malloc(*length * sizeof(uint8_t));
+ if (!*((uint8_t **) value)) {
+ PLIST_ERR("%s: malloc failed\n", __func__);
+ return;
+ }
memcpy(*((uint8_t **) value), data->buff, *length * sizeof(uint8_t));
break;
- case PLIST_DATE:
- //exception : here we use memory on the stack since it is just a temporary buffer
- ((struct timeval*) value)->tv_sec = data->timeval.tv_sec;
- ((struct timeval*) value)->tv_usec = data->timeval.tv_usec;
- break;
case PLIST_ARRAY:
case PLIST_DICT:
default:
@@ -517,7 +1739,7 @@ static void plist_get_type_and_value(plist_t node, plist_type * type, void *valu
plist_t plist_get_parent(plist_t node)
{
- return node ? (plist_t) ((node_t*) node)->parent : NULL;
+ return node ? (plist_t) ((node_t) node)->parent : NULL;
}
plist_type plist_get_node_type(plist_t node)
@@ -533,66 +1755,152 @@ plist_type plist_get_node_type(plist_t node)
void plist_get_key_val(plist_t node, char **val)
{
+ if (!node || !val)
+ return;
plist_type type = plist_get_node_type(node);
uint64_t length = 0;
- if (PLIST_KEY == type)
- plist_get_type_and_value(node, &type, (void *) val, &length);
+ if (PLIST_KEY != type)
+ return;
+ plist_get_type_and_value(node, &type, (void *) val, &length);
+ if (!*val)
+ return;
assert(length == strlen(*val));
}
void plist_get_string_val(plist_t node, char **val)
{
+ if (!node || !val)
+ return;
plist_type type = plist_get_node_type(node);
uint64_t length = 0;
- if (PLIST_STRING == type)
- plist_get_type_and_value(node, &type, (void *) val, &length);
+ if (PLIST_STRING != type)
+ return;
+ plist_get_type_and_value(node, &type, (void *) val, &length);
+ if (!*val)
+ return;
assert(length == strlen(*val));
}
+const char* plist_get_string_ptr(plist_t node, uint64_t* length)
+{
+ if (!node)
+ return NULL;
+ plist_type type = plist_get_node_type(node);
+ if (PLIST_STRING != type)
+ return NULL;
+ plist_data_t data = plist_get_data(node);
+ if (length)
+ *length = data->length;
+ return (const char*)data->strval;
+}
+
void plist_get_bool_val(plist_t node, uint8_t * val)
{
+ if (!node || !val)
+ return;
plist_type type = plist_get_node_type(node);
uint64_t length = 0;
- if (PLIST_BOOLEAN == type)
- plist_get_type_and_value(node, &type, (void *) val, &length);
+ if (PLIST_BOOLEAN != type)
+ return;
+ plist_get_type_and_value(node, &type, (void *) val, &length);
assert(length == sizeof(uint8_t));
}
void plist_get_uint_val(plist_t node, uint64_t * val)
{
+ if (!node || !val)
+ return;
+ plist_type type = plist_get_node_type(node);
+ uint64_t length = 0;
+ if (PLIST_INT != type)
+ return;
+ plist_get_type_and_value(node, &type, (void *) val, &length);
+ assert(length == sizeof(uint64_t) || length == 16);
+}
+
+void plist_get_int_val(plist_t node, int64_t * val)
+{
+ plist_get_uint_val(node, (uint64_t*)val);
+}
+
+void plist_get_uid_val(plist_t node, uint64_t * val)
+{
+ if (!node || !val)
+ return;
plist_type type = plist_get_node_type(node);
uint64_t length = 0;
- if (PLIST_UINT == type)
- plist_get_type_and_value(node, &type, (void *) val, &length);
+ if (PLIST_UID != type)
+ return;
+ plist_get_type_and_value(node, &type, (void *) val, &length);
assert(length == sizeof(uint64_t));
}
void plist_get_real_val(plist_t node, double *val)
{
+ if (!node || !val)
+ return;
plist_type type = plist_get_node_type(node);
uint64_t length = 0;
- if (PLIST_REAL == type)
- plist_get_type_and_value(node, &type, (void *) val, &length);
+ if (PLIST_REAL != type)
+ return;
+ plist_get_type_and_value(node, &type, (void *) val, &length);
assert(length == sizeof(double));
}
void plist_get_data_val(plist_t node, char **val, uint64_t * length)
{
+ if (!node || !val || !length)
+ return;
plist_type type = plist_get_node_type(node);
- if (PLIST_DATA == type)
- plist_get_type_and_value(node, &type, (void *) val, length);
+ if (PLIST_DATA != type)
+ return;
+ plist_get_type_and_value(node, &type, (void *) val, length);
+}
+
+const char* plist_get_data_ptr(plist_t node, uint64_t* length)
+{
+ if (!node || !length)
+ return NULL;
+ plist_type type = plist_get_node_type(node);
+ if (PLIST_DATA != type)
+ return NULL;
+ plist_data_t data = plist_get_data(node);
+ *length = data->length;
+ return (const char*)data->buff;
}
void plist_get_date_val(plist_t node, int32_t * sec, int32_t * usec)
{
+ if (!node)
+ return;
plist_type type = plist_get_node_type(node);
uint64_t length = 0;
- struct timeval val = { 0, 0 };
- if (PLIST_DATE == type)
- plist_get_type_and_value(node, &type, (void *) &val, &length);
- assert(length == sizeof(struct timeval));
- *sec = val.tv_sec;
- *usec = val.tv_usec;
+ double val = 0;
+ if (PLIST_DATE != type)
+ return;
+ plist_get_type_and_value(node, &type, (void *) &val, &length);
+ assert(length == sizeof(double));
+ if (sec)
+ *sec = (int32_t)val;
+ if (usec)
+ {
+ val = fabs((val - (int64_t)val) * 1000000);
+ *usec = (int32_t)val;
+ }
+}
+
+void plist_get_unix_date_val(plist_t node, int64_t *sec)
+{
+ if (!node || !sec)
+ return;
+ plist_type type = plist_get_node_type(node);
+ uint64_t length = 0;
+ double val = 0;
+ if (PLIST_DATE != type)
+ return;
+ plist_get_type_and_value(node, &type, (void *) &val, &length);
+ assert(length == sizeof(double));
+ *sec = (int64_t)val + MAC_EPOCH;
}
int plist_data_compare(const void *a, const void *b)
@@ -600,57 +1908,56 @@ int plist_data_compare(const void *a, const void *b)
plist_data_t val_a = NULL;
plist_data_t val_b = NULL;
- if (!a || !b)
- return FALSE;
+ if (a == b)
+ return TRUE;
- if (!((node_t*) a)->data || !((node_t*) b)->data)
+ if (!a || !b)
return FALSE;
val_a = plist_get_data((plist_t) a);
val_b = plist_get_data((plist_t) b);
+ if (val_a == NULL && val_b == NULL)
+ return TRUE;
+
+ if (val_a == NULL || val_b == NULL)
+ return FALSE;
+
if (val_a->type != val_b->type)
return FALSE;
switch (val_a->type)
{
case PLIST_BOOLEAN:
- case PLIST_UINT:
+ case PLIST_NULL:
+ case PLIST_INT:
case PLIST_REAL:
- if (val_a->intval == val_b->intval) //it is an union so this is sufficient
- return TRUE;
- else
- return FALSE;
+ case PLIST_DATE:
+ case PLIST_UID:
+ return val_a->length == val_b->length
+ && val_a->intval == val_b->intval; // it is a union so this is sufficient
case PLIST_KEY:
case PLIST_STRING:
- if (!strcmp(val_a->strval, val_b->strval))
- return TRUE;
- else
- return FALSE;
+ if (!val_a->strval || !val_b->strval)
+ return val_a->strval == val_b->strval;
+ return strcmp(val_a->strval, val_b->strval) == 0;
- case PLIST_DATA:
- if (!memcmp(val_a->buff, val_b->buff, val_a->length))
- return TRUE;
- else
+ case PLIST_DATA: {
+ if (val_a->length != val_b->length)
return FALSE;
+ if (val_a->length == 0)
+ return TRUE;
+ return memcmp(val_a->buff, val_b->buff, val_a->length) == 0;
+ }
case PLIST_ARRAY:
case PLIST_DICT:
//compare pointer
- if (a == b)
- return TRUE;
- else
- return FALSE;
- break;
- case PLIST_DATE:
- if (!memcmp(&(val_a->timeval), &(val_b->timeval), sizeof(struct timeval)))
- return TRUE;
- else
- return FALSE;
+ return a == b;
+
default:
- break;
+ return FALSE;
}
- return FALSE;
}
char plist_compare_node_value(plist_t node_l, plist_t node_r)
@@ -658,26 +1965,20 @@ char plist_compare_node_value(plist_t node_l, plist_t node_r)
return plist_data_compare(node_l, node_r);
}
-static void plist_set_element_val(plist_t node, plist_type type, const void *value, uint64_t length)
+static plist_err_t plist_set_element_val(plist_t node, plist_type type, const void *value, uint64_t length)
{
- //free previous allocated buffer
+ //free previous allocated data
plist_data_t data = plist_get_data(node);
- assert(data); // a node should always have data attached
+ if (!data) { // a node should always have data attached
+ PLIST_ERR("%s: Failed to allocate plist data\n", __func__);
+ return PLIST_ERR_NO_MEM;
+ }
- switch (data->type)
- {
- case PLIST_KEY:
- case PLIST_STRING:
- free(data->strval);
- data->strval = NULL;
- break;
- case PLIST_DATA:
- free(data->buff);
- data->buff = NULL;
- break;
- default:
- break;
+ if (node_first_child((node_t)node)) {
+ int r = plist_free_children((node_t)node);
+ if (r < 0) return PLIST_ERR_UNKNOWN;
}
+ _plist_free_data(data);
//now handle value
@@ -689,62 +1990,45 @@ static void plist_set_element_val(plist_t node, plist_type type, const void *val
case PLIST_BOOLEAN:
data->boolval = *((char *) value);
break;
- case PLIST_UINT:
+ case PLIST_INT:
+ case PLIST_UID:
data->intval = *((uint64_t *) value);
break;
case PLIST_REAL:
+ case PLIST_DATE:
data->realval = *((double *) value);
break;
case PLIST_KEY:
case PLIST_STRING:
data->strval = strdup((char *) value);
+ if (!data->strval) {
+ PLIST_ERR("%s: strdup failed\n", __func__);
+ return PLIST_ERR_NO_MEM;
+ }
break;
case PLIST_DATA:
data->buff = (uint8_t *) malloc(length);
+ if (!data->buff) {
+ PLIST_ERR("%s: malloc failed\n", __func__);
+ return PLIST_ERR_NO_MEM;
+ }
memcpy(data->buff, value, length);
break;
- case PLIST_DATE:
- data->timeval.tv_sec = ((struct timeval*) value)->tv_sec;
- data->timeval.tv_usec = ((struct timeval*) value)->tv_usec;
- break;
case PLIST_ARRAY:
case PLIST_DICT:
default:
break;
}
-}
-
-void plist_set_type(plist_t node, plist_type type)
-{
- if ( node_n_children(node) == 0 )
- {
- plist_data_t data = plist_get_data(node);
- plist_free_data( data );
- data = plist_new_plist_data();
- data->type = type;
- switch (type)
- {
- case PLIST_BOOLEAN:
- data->length = sizeof(uint8_t);
- break;
- case PLIST_UINT:
- data->length = sizeof(uint64_t);
- break;
- case PLIST_REAL:
- data->length = sizeof(double);
- break;
- case PLIST_DATE:
- data->length = sizeof(struct timeval);
- break;
- default:
- data->length = 0;
- break;
- }
- }
+ return PLIST_ERR_SUCCESS;
}
void plist_set_key_val(plist_t node, const char *val)
{
+ plist_t parent = plist_get_parent(node);
+ plist_t item = plist_dict_get_item(parent, val);
+ if (item) {
+ return;
+ }
plist_set_element_val(node, PLIST_KEY, val, strlen(val));
}
@@ -760,7 +2044,17 @@ void plist_set_bool_val(plist_t node, uint8_t val)
void plist_set_uint_val(plist_t node, uint64_t val)
{
- plist_set_element_val(node, PLIST_UINT, &val, sizeof(uint64_t));
+ plist_set_element_val(node, PLIST_INT, &val, (val > INT64_MAX) ? sizeof(uint64_t)*2 : sizeof(uint64_t));
+}
+
+void plist_set_int_val(plist_t node, int64_t val)
+{
+ plist_set_element_val(node, PLIST_INT, &val, sizeof(uint64_t));
+}
+
+void plist_set_uid_val(plist_t node, uint64_t val)
+{
+ plist_set_element_val(node, PLIST_UID, &val, sizeof(uint64_t));
}
void plist_set_real_val(plist_t node, double val)
@@ -775,7 +2069,444 @@ void plist_set_data_val(plist_t node, const char *val, uint64_t length)
void plist_set_date_val(plist_t node, int32_t sec, int32_t usec)
{
- struct timeval val = { sec, usec };
- plist_set_element_val(node, PLIST_DATE, &val, sizeof(struct timeval));
+ double val = (double)sec + (double)usec / 1000000;
+ plist_set_element_val(node, PLIST_DATE, &val, sizeof(double));
+}
+
+void plist_set_unix_date_val(plist_t node, int64_t sec)
+{
+ double val = (double)(sec - MAC_EPOCH);
+ plist_set_element_val(node, PLIST_DATE, &val, sizeof(double));
+}
+
+int plist_bool_val_is_true(plist_t boolnode)
+{
+ if (!PLIST_IS_BOOLEAN(boolnode)) {
+ return 0;
+ }
+ uint8_t bv = 0;
+ plist_get_bool_val(boolnode, &bv);
+ return (bv == 1);
+}
+
+int plist_int_val_is_negative(plist_t intnode)
+{
+ if (!PLIST_IS_INT(intnode)) {
+ return 0;
+ }
+ plist_data_t data = plist_get_data(intnode);
+ if (data->length == 16) {
+ return 0;
+ }
+ if ((int64_t)data->intval < 0) {
+ return 1;
+ }
+ return 0;
+}
+
+int plist_int_val_compare(plist_t uintnode, int64_t cmpval)
+{
+ if (!PLIST_IS_INT(uintnode)) {
+ return -1;
+ }
+ int64_t uintval = 0;
+ plist_get_int_val(uintnode, &uintval);
+ if (uintval == cmpval) {
+ return 0;
+ }
+
+ if (uintval < cmpval) {
+ return -1;
+ }
+
+ return 1;
+}
+
+int plist_uint_val_compare(plist_t uintnode, uint64_t cmpval)
+{
+ if (!PLIST_IS_INT(uintnode)) {
+ return -1;
+ }
+ uint64_t uintval = 0;
+ plist_get_uint_val(uintnode, &uintval);
+ if (uintval == cmpval) {
+ return 0;
+ }
+
+ if (uintval < cmpval) {
+ return -1;
+ }
+
+ return 1;
+}
+
+int plist_uid_val_compare(plist_t uidnode, uint64_t cmpval)
+{
+ if (!PLIST_IS_UID(uidnode)) {
+ return -1;
+ }
+ uint64_t uidval = 0;
+ plist_get_uid_val(uidnode, &uidval);
+ if (uidval == cmpval) {
+ return 0;
+ }
+
+ if (uidval < cmpval) {
+ return -1;
+ }
+
+ return 1;
+}
+
+int plist_real_val_compare(plist_t realnode, double cmpval)
+{
+ if (!PLIST_IS_REAL(realnode)) {
+ return -1;
+ }
+ double a = 0;
+ double b = cmpval;
+ plist_get_real_val(realnode, &a);
+ double abs_a = fabs(a);
+ double abs_b = fabs(b);
+ double diff = fabs(a - b);
+ if (a == b) {
+ return 0;
+ }
+
+ if (a == 0 || b == 0 || (abs_a + abs_b < DBL_MIN)) {
+ if (diff < (DBL_EPSILON * DBL_MIN)) {
+ return 0;
+ }
+
+ if (a < b) {
+ return -1;
+ }
+ } else {
+ if ((diff / fmin(abs_a + abs_b, DBL_MAX)) < DBL_EPSILON) {
+ return 0;
+ }
+
+ if (a < b) {
+ return -1;
+ }
+ }
+ return 1;
+}
+
+int plist_date_val_compare(plist_t datenode, int32_t cmpsec, int32_t cmpusec)
+{
+ if (!PLIST_IS_DATE(datenode)) {
+ return -1;
+ }
+ plist_data_t data = plist_get_data(datenode);
+ assert(data->length == sizeof(double));
+ double val = data->realval;
+ int32_t sec = (int32_t)val;
+ val = fabs((val - (int64_t)val) * 1000000);
+ int32_t usec = (int32_t)val;
+ uint64_t dateval = ((int64_t)sec << 32) | usec;
+ uint64_t cmpval = ((int64_t)cmpsec << 32) | cmpusec;
+ if (dateval == cmpval) {
+ return 0;
+ }
+
+ if (dateval < cmpval) {
+ return -1;
+ }
+
+ return 1;
+}
+
+int plist_unix_date_val_compare(plist_t datenode, int64_t cmpval)
+{
+ if (!PLIST_IS_DATE(datenode)) {
+ return -1;
+ }
+ int64_t dateval = 0;
+ plist_get_unix_date_val(datenode, &dateval);
+ if (dateval == cmpval) {
+ return 0;
+ }
+
+ if (dateval < cmpval) {
+ return -1;
+ }
+
+ return 1;
+}
+
+int plist_string_val_compare(plist_t strnode, const char* cmpval)
+{
+ if (!PLIST_IS_STRING(strnode)) {
+ return -1;
+ }
+ plist_data_t data = plist_get_data(strnode);
+ return strcmp(data->strval, cmpval);
+}
+
+int plist_string_val_compare_with_size(plist_t strnode, const char* cmpval, size_t n)
+{
+ if (!PLIST_IS_STRING(strnode)) {
+ return -1;
+ }
+ plist_data_t data = plist_get_data(strnode);
+ return strncmp(data->strval, cmpval, n);
+}
+
+int plist_string_val_contains(plist_t strnode, const char* substr)
+{
+ if (!PLIST_IS_STRING(strnode)) {
+ return 0;
+ }
+ plist_data_t data = plist_get_data(strnode);
+ return (strstr(data->strval, substr) != NULL);
+}
+
+int plist_key_val_compare(plist_t keynode, const char* cmpval)
+{
+ if (!PLIST_IS_KEY(keynode)) {
+ return -1;
+ }
+ plist_data_t data = plist_get_data(keynode);
+ return strcmp(data->strval, cmpval);
+}
+
+int plist_key_val_compare_with_size(plist_t keynode, const char* cmpval, size_t n)
+{
+ if (!PLIST_IS_KEY(keynode)) {
+ return -1;
+ }
+ plist_data_t data = plist_get_data(keynode);
+ return strncmp(data->strval, cmpval, n);
+}
+
+int plist_key_val_contains(plist_t keynode, const char* substr)
+{
+ if (!PLIST_IS_KEY(keynode)) {
+ return 0;
+ }
+ plist_data_t data = plist_get_data(keynode);
+ return (strstr(data->strval, substr) != NULL);
+}
+
+int plist_data_val_compare(plist_t datanode, const uint8_t* cmpval, size_t n)
+{
+ if (!PLIST_IS_DATA(datanode)) {
+ return -1;
+ }
+ plist_data_t data = plist_get_data(datanode);
+ if (data->length < n) {
+ return -1;
+ }
+
+ if (data->length > n) {
+ return 1;
+ }
+
+ return memcmp(data->buff, cmpval, n);
+}
+
+int plist_data_val_compare_with_size(plist_t datanode, const uint8_t* cmpval, size_t n)
+{
+ if (!PLIST_IS_DATA(datanode)) {
+ return -1;
+ }
+ plist_data_t data = plist_get_data(datanode);
+ if (data->length < n) {
+ return -1;
+ }
+ return memcmp(data->buff, cmpval, n);
+}
+
+int plist_data_val_contains(plist_t datanode, const uint8_t* cmpval, size_t n)
+{
+ if (!PLIST_IS_DATA(datanode)) {
+ return -1;
+ }
+ plist_data_t data = plist_get_data(datanode);
+ return (memmem(data->buff, data->length, cmpval, n) != NULL);
+}
+
+extern void plist_xml_set_debug(int debug);
+extern void plist_bin_set_debug(int debug);
+extern void plist_json_set_debug(int debug);
+extern void plist_ostep_set_debug(int debug);
+
+void plist_set_debug(int debug)
+{
+#if DEBUG
+ plist_debug = debug;
+#endif
+ plist_xml_set_debug(debug);
+ plist_bin_set_debug(debug);
+ plist_json_set_debug(debug);
+ plist_ostep_set_debug(debug);
+}
+
+void plist_sort(plist_t plist)
+{
+ if (!plist) {
+ return;
+ }
+ if (PLIST_IS_ARRAY(plist)) {
+ uint32_t n = plist_array_get_size(plist);
+ uint32_t i = 0;
+ for (i = 0; i < n; i++) {
+ plist_sort(plist_array_get_item(plist, i));
+ }
+ } else if (PLIST_IS_DICT(plist)) {
+ node_t node = (node_t)plist;
+ node_t ch;
+ if (!node_first_child(node)) {
+ return;
+ }
+ for (ch = node_first_child(node); ch; ch = node_next_sibling(ch)) {
+ ch = node_next_sibling(ch);
+ plist_sort((plist_t)ch);
+ }
+ #define KEY_DATA(x) (x->data)
+ #define NEXT_KEY(x) (x->next->next)
+ #define KEY_STRVAL(x) ((plist_data_t)(KEY_DATA(x)))->strval
+ int swapped = 0;
+ do {
+ swapped = 0;
+ node_t lptr = NULL;
+ node_t cur_key = node_first_child((node_t)plist);
+
+ while (NEXT_KEY(cur_key) != lptr) {
+ node_t next_key = NEXT_KEY(cur_key);
+ if (strcmp(KEY_STRVAL(cur_key), KEY_STRVAL(next_key)) > 0) {
+ node_t cur_val = cur_key->next;
+ node_t next_val = next_key->next;
+ // we need to swap 2 consecutive nodes with the 2 after them
+ // a -> b -> [c] -> [d] -> [e] -> [f] -> g -> h
+ // cur next
+ // swapped:
+ // a -> b -> [e] -> [f] -> [c] -> [d] -> g -> h
+ // next cur
+ node_t tmp_prev = cur_key->prev;
+ node_t tmp_next = next_val->next;
+ cur_key->prev = next_val;
+ cur_val->next = tmp_next;
+ next_val->next = cur_key;
+ next_key->prev = tmp_prev;
+ if (tmp_prev) {
+ tmp_prev->next = next_key;
+ } else {
+ ((node_t)plist)->children->begin = next_key;
+ }
+ if (tmp_next) {
+ tmp_next->prev = cur_val;
+ } else {
+ ((node_t)plist)->children->end = cur_val;
+ }
+ cur_key = next_key;
+ swapped = 1;
+ }
+ cur_key = NEXT_KEY(cur_key);
+ }
+ lptr = cur_key;
+ } while (swapped);
+ }
}
+plist_err_t plist_write_to_string(plist_t plist, char **output, uint32_t* length, plist_format_t format, plist_write_options_t options)
+{
+ plist_err_t err = PLIST_ERR_UNKNOWN;
+ switch (format) {
+ case PLIST_FORMAT_XML:
+ err = plist_to_xml(plist, output, length);
+ break;
+ case PLIST_FORMAT_JSON:
+ err = plist_to_json_with_options(plist, output, length, options);
+ break;
+ case PLIST_FORMAT_OSTEP:
+ 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);
+ break;
+ case PLIST_FORMAT_LIMD:
+ err = plist_write_to_string_limd(plist, output, length, options);
+ break;
+ case PLIST_FORMAT_PLUTIL:
+ err = plist_write_to_string_plutil(plist, output, length, options);
+ break;
+ default:
+ // unsupported output format
+ err = PLIST_ERR_FORMAT;
+ break;
+ }
+ return err;
+}
+
+plist_err_t plist_write_to_stream(plist_t plist, FILE *stream, plist_format_t format, plist_write_options_t options)
+{
+ if (!plist || !stream) {
+ return PLIST_ERR_INVALID_ARG;
+ }
+ plist_err_t err = PLIST_ERR_UNKNOWN;
+ char *output = NULL;
+ uint32_t length = 0;
+ switch (format) {
+ case PLIST_FORMAT_BINARY:
+ err = plist_to_bin(plist, &output, &length);
+ break;
+ case PLIST_FORMAT_XML:
+ err = plist_to_xml(plist, &output, &length);
+ break;
+ case PLIST_FORMAT_JSON:
+ err = plist_to_json_with_options(plist, &output, &length, options);
+ break;
+ case PLIST_FORMAT_OSTEP:
+ err = plist_to_openstep_with_options(plist, &output, &length, options);
+ break;
+ case PLIST_FORMAT_PRINT:
+ err = plist_write_to_stream_default(plist, stream, options);
+ break;
+ case PLIST_FORMAT_LIMD:
+ err = plist_write_to_stream_limd(plist, stream, options);
+ break;
+ case PLIST_FORMAT_PLUTIL:
+ err = plist_write_to_stream_plutil(plist, stream, options);
+ break;
+ default:
+ // unsupported output format
+ err = PLIST_ERR_FORMAT;
+ break;
+ }
+ if (output && err == PLIST_ERR_SUCCESS) {
+ if (fwrite(output, 1, length, stream) < length) {
+ err = PLIST_ERR_IO;
+ }
+ free(output);
+ }
+ return err;
+}
+
+plist_err_t plist_write_to_file(plist_t plist, const char* filename, plist_format_t format, plist_write_options_t options)
+{
+ if (!plist || !filename) {
+ return PLIST_ERR_INVALID_ARG;
+ }
+ FILE* f = fopen(filename, "wb");
+ if (!f) {
+ return PLIST_ERR_IO;
+ }
+ plist_err_t err = plist_write_to_stream(plist, f, format, options);
+ fclose(f);
+ return err;
+}
+
+void plist_print(plist_t plist)
+{
+ plist_write_to_stream(plist, stdout, PLIST_FORMAT_PRINT, PLIST_OPT_PARTIAL_DATA);
+}
+
+const char* libplist_version()
+{
+#ifndef PACKAGE_VERSION
+#error PACKAGE_VERSION is not defined!
+#endif
+ return PACKAGE_VERSION;
+}
diff --git a/src/plist.h b/src/plist.h
index 2a9a3b5..7228696 100644
--- a/src/plist.h
+++ b/src/plist.h
@@ -22,18 +22,44 @@
#ifndef PLIST_H
#define PLIST_H
-#include "plist/plist.h"
-#include "common.h"
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
#include <sys/types.h>
#include <sys/stat.h>
-#include <sys/time.h>
#ifdef _MSC_VER
#pragma warning(disable:4996)
#pragma warning(disable:4244)
+#include <winsock2.h>
+#else
+#include <sys/time.h>
#endif
+#ifdef LIBPLIST_STATIC
+ #define PLIST_API
+#elif defined(_WIN32)
+ #define PLIST_API __declspec( dllexport )
+#else
+ #if __GNUC__ >= 4
+ #define PLIST_API __attribute__((visibility("default")))
+ #else
+ #define PLIST_API
+ #endif
+#endif
+
+#include "node.h"
+
+#ifndef PLIST_MAX_NESTING_DEPTH
+#ifdef NODE_MAX_DEPTH
+#define PLIST_MAX_NESTING_DEPTH NODE_MAX_DEPTH
+#else
+#define PLIST_MAX_NESTING_DEPTH 512
+#endif
+#endif
+
+#include "plist/plist.h"
struct plist_data_s
{
@@ -44,7 +70,7 @@ struct plist_data_s
double realval;
char *strval;
uint8_t *buff;
- struct timeval timeval;
+ void *hashtable;
};
uint64_t length;
plist_type type;
@@ -52,10 +78,30 @@ struct plist_data_s
typedef struct plist_data_s *plist_data_t;
-_PLIST_INTERNAL plist_t plist_new_node(plist_data_t data);
-_PLIST_INTERNAL plist_data_t plist_get_data(const plist_t node);
-_PLIST_INTERNAL plist_data_t plist_new_plist_data(void);
-_PLIST_INTERNAL int plist_data_compare(const void *a, const void *b);
+plist_t plist_new_node(plist_data_t data);
+plist_data_t plist_get_data(plist_t node);
+plist_data_t plist_new_plist_data(void);
+void plist_free_data(plist_data_t data);
+int plist_data_compare(const void *a, const void *b);
+
+extern plist_err_t plist_write_to_string_default(plist_t plist, char **output, uint32_t* length, plist_write_options_t options);
+extern plist_err_t plist_write_to_string_limd(plist_t plist, char **output, uint32_t* length, plist_write_options_t options);
+extern plist_err_t plist_write_to_string_plutil(plist_t plist, char **output, uint32_t* length, plist_write_options_t options);
+extern plist_err_t plist_write_to_stream_default(plist_t plist, FILE *stream, plist_write_options_t options);
+extern plist_err_t plist_write_to_stream_limd(plist_t plist, FILE *stream, plist_write_options_t options);
+extern plist_err_t plist_write_to_stream_plutil(plist_t plist, FILE *stream, plist_write_options_t options);
+static inline unsigned int plist_node_ptr_hash(const void *ptr)
+{
+ uintptr_t h = (uintptr_t)ptr;
+ h ^= (h >> 16);
+ h *= 0x85ebca6b;
+ return (unsigned int)h;
+}
+
+static inline int plist_node_ptr_compare(const void *a, const void *b)
+{
+ return a == b;
+}
#endif
diff --git a/src/ptrarray.c b/src/ptrarray.c
index 8567752..3a11031 100644
--- a/src/ptrarray.c
+++ b/src/ptrarray.c
@@ -2,7 +2,7 @@
* ptrarray.c
* simple pointer array implementation
*
- * Copyright (c) 2011 Nikias Bassen, All Rights Reserved.
+ * Copyright (c) 2011-2019 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
@@ -19,13 +19,14 @@
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include "ptrarray.h"
+#include <string.h>
ptrarray_t *ptr_array_new(int capacity)
{
ptrarray_t *pa = (ptrarray_t*)malloc(sizeof(ptrarray_t));
pa->pdata = (void**)malloc(sizeof(void*) * capacity);
pa->capacity = capacity;
- pa->capacity_step = (capacity > 64) ? 64 : capacity;
+ pa->capacity_step = (capacity > 4096) ? 4096 : capacity;
pa->len = 0;
return pa;
}
@@ -39,23 +40,57 @@ void ptr_array_free(ptrarray_t *pa)
free(pa);
}
-void ptr_array_add(ptrarray_t *pa, void *data)
+void ptr_array_insert(ptrarray_t *pa, void *data, long array_index)
{
- if (!pa || !pa->pdata || !data) return;
- size_t remaining = pa->capacity-pa->len;
+ if (!pa || !pa->pdata) return;
+ long remaining = pa->capacity-pa->len;
if (remaining == 0) {
- pa->pdata = realloc(pa->pdata, sizeof(void*) * (pa->capacity + pa->capacity_step));
+ pa->pdata = (void**)realloc(pa->pdata, sizeof(void*) * (pa->capacity + pa->capacity_step));
pa->capacity += pa->capacity_step;
}
- pa->pdata[pa->len] = data;
+ if (array_index < 0 || array_index >= pa->len) {
+ pa->pdata[pa->len] = data;
+ } else {
+ memmove(&pa->pdata[array_index+1], &pa->pdata[array_index], (pa->len-array_index) * sizeof(void*));
+ pa->pdata[array_index] = data;
+ }
pa->len++;
}
-void* ptr_array_index(ptrarray_t *pa, size_t index)
+void ptr_array_add(ptrarray_t *pa, void *data)
+{
+ ptr_array_insert(pa, data, -1);
+}
+
+void ptr_array_remove(ptrarray_t *pa, long array_index)
+{
+ if (!pa || !pa->pdata || array_index < 0) return;
+ if (pa->len == 0 || array_index >= pa->len) return;
+ if (pa->len == 1) {
+ pa->pdata[0] = NULL;
+ } else {
+ memmove(&pa->pdata[array_index], &pa->pdata[array_index+1], (pa->len-array_index-1) * sizeof(void*));
+ }
+ pa->len--;
+}
+
+void ptr_array_set(ptrarray_t *pa, void *data, long array_index)
+{
+ if (!pa || !pa->pdata || array_index < 0) return;
+ if (pa->len == 0 || array_index >= pa->len) return;
+ pa->pdata[array_index] = data;
+}
+
+void* ptr_array_index(ptrarray_t *pa, long array_index)
{
if (!pa) return NULL;
- if (index >= pa->len) {
+ if (array_index < 0 || array_index >= pa->len) {
return NULL;
}
- return pa->pdata[index];
+ return pa->pdata[array_index];
+}
+
+long ptr_array_size(ptrarray_t *pa)
+{
+ return pa->len;
}
diff --git a/src/ptrarray.h b/src/ptrarray.h
index 84f9ef0..ed67351 100644
--- a/src/ptrarray.h
+++ b/src/ptrarray.h
@@ -2,7 +2,7 @@
* ptrarray.h
* header file for simple pointer array implementation
*
- * Copyright (c) 2011 Nikias Bassen, All Rights Reserved.
+ * Copyright (c) 2011-2019 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
@@ -21,17 +21,20 @@
#ifndef PTRARRAY_H
#define PTRARRAY_H
#include <stdlib.h>
-#include "common.h"
typedef struct ptrarray_t {
void **pdata;
- size_t len;
- size_t capacity;
- size_t capacity_step;
+ long len;
+ long capacity;
+ long capacity_step;
} ptrarray_t;
-_PLIST_INTERNAL ptrarray_t *ptr_array_new(int capacity);
-_PLIST_INTERNAL void ptr_array_free(ptrarray_t *pa);
-_PLIST_INTERNAL void ptr_array_add(ptrarray_t *pa, void *data);
-_PLIST_INTERNAL void* ptr_array_index(ptrarray_t *pa, size_t index);
+ptrarray_t *ptr_array_new(int capacity);
+void ptr_array_free(ptrarray_t *pa);
+void ptr_array_add(ptrarray_t *pa, void *data);
+void ptr_array_insert(ptrarray_t *pa, void *data, long index);
+void ptr_array_remove(ptrarray_t *pa, long index);
+void ptr_array_set(ptrarray_t *pa, void *data, long index);
+void* ptr_array_index(ptrarray_t *pa, long index);
+long ptr_array_size(ptrarray_t *pa);
#endif
diff --git a/src/strbuf.h b/src/strbuf.h
new file mode 100644
index 0000000..2fbfe93
--- /dev/null
+++ b/src/strbuf.h
@@ -0,0 +1,35 @@
+/*
+ * strbuf.h
+ * header file for simple string buffer, using the bytearray as underlying
+ * structure.
+ *
+ * Copyright (c) 2016 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 STRBUF_H
+#define STRBUF_H
+#include <stdlib.h>
+#include "bytearray.h"
+
+typedef struct bytearray_t strbuf_t;
+
+#define str_buf_new(__sz) byte_array_new(__sz)
+#define str_buf_new_for_stream(__stream) byte_array_new_for_stream(__stream)
+#define str_buf_free(__ba) byte_array_free(__ba)
+#define str_buf_grow(__ba, __am) byte_array_grow(__ba, __am)
+#define str_buf_append(__ba, __str, __len) byte_array_append(__ba, (void*)(__str), __len)
+
+#endif
diff --git a/src/time64.c b/src/time64.c
new file mode 100644
index 0000000..218088e
--- /dev/null
+++ b/src/time64.c
@@ -0,0 +1,804 @@
+/*
+
+Copyright (c) 2007-2010 Michael G Schwern
+
+This software originally derived from Paul Sheer's pivotal_gmtime_r.c.
+
+The MIT License:
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+
+*/
+
+/*
+
+Programmers who have available to them 64-bit time values as a 'long
+long' type can use localtime64_r() and gmtime64_r() which correctly
+converts the time even on 32-bit systems. Whether you have 64-bit time
+values will depend on the operating system.
+
+localtime64_r() is a 64-bit equivalent of localtime_r().
+
+gmtime64_r() is a 64-bit equivalent of gmtime_r().
+
+*/
+
+#include <assert.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <time.h>
+#include <errno.h>
+#include "time64.h"
+#include "time64_limits.h"
+
+
+static const char days_in_month[2][12] = {
+ {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31},
+ {31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31},
+};
+
+static const short julian_days_by_month[2][12] = {
+ {0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334},
+ {0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335},
+};
+
+static const char wday_name[7][4] = {
+ "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"
+};
+
+static const char mon_name[12][4] = {
+ "Jan", "Feb", "Mar", "Apr", "May", "Jun",
+ "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
+};
+
+static const short length_of_year[2] = { 365, 366 };
+
+/* Some numbers relating to the gregorian cycle */
+static const Year years_in_gregorian_cycle = 400;
+#define days_in_gregorian_cycle ((365 * 400) + 100 - 4 + 1)
+static const Time64_T seconds_in_gregorian_cycle = days_in_gregorian_cycle * 60LL * 60LL * 24LL;
+
+/* Year range we can trust the time funcitons with */
+#define MAX_SAFE_YEAR 2037
+#define MIN_SAFE_YEAR 1971
+
+/* 28 year Julian calendar cycle */
+#define SOLAR_CYCLE_LENGTH 28
+
+/* Year cycle from MAX_SAFE_YEAR down. */
+static const short safe_years_high[SOLAR_CYCLE_LENGTH] = {
+ 2016, 2017, 2018, 2019,
+ 2020, 2021, 2022, 2023,
+ 2024, 2025, 2026, 2027,
+ 2028, 2029, 2030, 2031,
+ 2032, 2033, 2034, 2035,
+ 2036, 2037, 2010, 2011,
+ 2012, 2013, 2014, 2015
+};
+
+/* Year cycle from MIN_SAFE_YEAR up */
+static const int safe_years_low[SOLAR_CYCLE_LENGTH] = {
+ 1996, 1997, 1998, 1971,
+ 1972, 1973, 1974, 1975,
+ 1976, 1977, 1978, 1979,
+ 1980, 1981, 1982, 1983,
+ 1984, 1985, 1986, 1987,
+ 1988, 1989, 1990, 1991,
+ 1992, 1993, 1994, 1995,
+};
+
+/* This isn't used, but it's handy to look at */
+#if 0
+static const char dow_year_start[SOLAR_CYCLE_LENGTH] = {
+ 5, 0, 1, 2, /* 0 2016 - 2019 */
+ 3, 5, 6, 0, /* 4 */
+ 1, 3, 4, 5, /* 8 1996 - 1998, 1971*/
+ 6, 1, 2, 3, /* 12 1972 - 1975 */
+ 4, 6, 0, 1, /* 16 */
+ 2, 4, 5, 6, /* 20 2036, 2037, 2010, 2011 */
+ 0, 2, 3, 4 /* 24 2012, 2013, 2014, 2015 */
+};
+#endif
+
+/* Let's assume people are going to be looking for dates in the future.
+ Let's provide some cheats so you can skip ahead.
+ This has a 4x speed boost when near 2008.
+*/
+/* Number of days since epoch on Jan 1st, 2008 GMT */
+#define CHEAT_DAYS (1199145600 / 24 / 60 / 60)
+#define CHEAT_YEARS 108
+
+#define IS_LEAP(n) ((!(((n) + 1900) % 400) || (!(((n) + 1900) % 4) && (((n) + 1900) % 100))) != 0)
+#define WRAP(a,b,m) ((a) = ((a) < 0 ) ? ((b)--, (a) + (m)) : (a))
+
+#ifdef USE_SYSTEM_LOCALTIME
+# define SHOULD_USE_SYSTEM_LOCALTIME(a) ( \
+ (a) <= SYSTEM_LOCALTIME_MAX && \
+ (a) >= SYSTEM_LOCALTIME_MIN \
+)
+#else
+# define SHOULD_USE_SYSTEM_LOCALTIME(a) (0)
+#endif
+
+#ifdef USE_SYSTEM_GMTIME
+# define SHOULD_USE_SYSTEM_GMTIME(a) ( \
+ (a) <= SYSTEM_GMTIME_MAX && \
+ (a) >= SYSTEM_GMTIME_MIN \
+)
+#else
+# define SHOULD_USE_SYSTEM_GMTIME(a) (0)
+#endif
+
+/* Multi varadic macros are a C99 thing, alas */
+#ifdef TIME_64_DEBUG
+# define TIME64_TRACE(format) (fprintf(stderr, format))
+# define TIME64_TRACE1(format, var1) (fprintf(stderr, format, var1))
+# define TIME64_TRACE2(format, var1, var2) (fprintf(stderr, format, var1, var2))
+# define TIME64_TRACE3(format, var1, var2, var3) (fprintf(stderr, format, var1, var2, var3))
+#else
+# define TIME64_TRACE(format) ((void)0)
+# define TIME64_TRACE1(format, var1) ((void)0)
+# define TIME64_TRACE2(format, var1, var2) ((void)0)
+# define TIME64_TRACE3(format, var1, var2, var3) ((void)0)
+#endif
+
+
+static int is_exception_century(Year year)
+{
+ int is_exception = ((year % 100 == 0) && !(year % 400 == 0));
+ TIME64_TRACE1("# is_exception_century: %s\n", is_exception ? "yes" : "no");
+
+ return(is_exception);
+}
+
+
+/* Compare two dates.
+ The result is like cmp.
+ Ignores things like gmtoffset and dst
+*/
+static int cmp_date( const struct TM* left, const struct tm* right ) {
+ if( left->tm_year > right->tm_year )
+ return 1;
+ if( left->tm_year < right->tm_year )
+ return -1;
+ if( left->tm_mon > right->tm_mon )
+ return 1;
+ if( left->tm_mon < right->tm_mon )
+ return -1;
+ if( left->tm_mday > right->tm_mday )
+ return 1;
+ if( left->tm_mday < right->tm_mday )
+ return -1;
+ if( left->tm_hour > right->tm_hour )
+ return 1;
+ if( left->tm_hour < right->tm_hour )
+ return -1;
+ if( left->tm_min > right->tm_min )
+ return 1;
+ if( left->tm_min < right->tm_min )
+ return -1;
+ if( left->tm_sec > right->tm_sec )
+ return 1;
+ if( left->tm_sec < right->tm_sec )
+ return -1;
+ return 0;
+}
+
+
+/* Check if a date is safely inside a range.
+ The intention is to check if its a few days inside.
+*/
+static int date_in_safe_range( const struct TM* date, const struct tm* min, const struct tm* max ) {
+ if( cmp_date(date, min) == -1 )
+ return 0;
+
+ if( cmp_date(date, max) == 1 )
+ return 0;
+
+ return 1;
+}
+
+
+/* timegm() is not in the C or POSIX spec, but it is such a useful
+ extension I would be remiss in leaving it out. Also I need it
+ for localtime64()
+*/
+Time64_T timegm64(const struct TM *date) {
+ Time64_T days = 0;
+ Time64_T seconds = 0;
+ Year year;
+ assert(date != NULL);
+ Year orig_year = (Year)date->tm_year;
+ int cycles = 0;
+
+ if( (orig_year > 100) || (orig_year < -300) ) {
+ cycles = (orig_year - 100) / 400;
+ orig_year -= cycles * 400;
+ days += (Time64_T)cycles * days_in_gregorian_cycle;
+ }
+ TIME64_TRACE3("# timegm/ cycles: %d, days: %lld, orig_year: %lld\n", cycles, days, orig_year);
+
+ if( orig_year > 70 ) {
+ year = 70;
+ while( year < orig_year ) {
+ days += length_of_year[IS_LEAP(year)];
+ year++;
+ }
+ }
+ else if ( orig_year < 70 ) {
+ year = 69;
+ do {
+ days -= length_of_year[IS_LEAP(year)];
+ year--;
+ } while( year >= orig_year );
+ }
+
+ days += julian_days_by_month[IS_LEAP(orig_year)][date->tm_mon];
+ days += date->tm_mday - 1;
+
+ seconds = days * 60 * 60 * 24;
+
+ seconds += date->tm_hour * 60 * 60;
+ seconds += date->tm_min * 60;
+ seconds += date->tm_sec;
+
+ return(seconds);
+}
+
+
+static int check_tm(struct TM *tm)
+{
+ /* Don't forget leap seconds */
+ assert(tm->tm_sec >= 0);
+ assert(tm->tm_sec <= 61);
+
+ assert(tm->tm_min >= 0);
+ assert(tm->tm_min <= 59);
+
+ assert(tm->tm_hour >= 0);
+ assert(tm->tm_hour <= 23);
+
+ assert(tm->tm_mday >= 1);
+ assert(tm->tm_mday <= days_in_month[IS_LEAP(tm->tm_year)][tm->tm_mon]);
+
+ assert(tm->tm_mon >= 0);
+ assert(tm->tm_mon <= 11);
+
+ assert(tm->tm_wday >= 0);
+ assert(tm->tm_wday <= 6);
+
+ assert(tm->tm_yday >= 0);
+ assert(tm->tm_yday <= length_of_year[IS_LEAP(tm->tm_year)]);
+
+#ifdef HAVE_TM_TM_GMTOFF
+ assert(tm->tm_gmtoff >= -24 * 60 * 60);
+ assert(tm->tm_gmtoff <= 24 * 60 * 60);
+#endif
+
+ return 1;
+}
+
+
+/* The exceptional centuries without leap years cause the cycle to
+ shift by 16
+*/
+static Year cycle_offset(Year year)
+{
+ const Year start_year = 2000;
+ Year year_diff = year - start_year;
+ Year exceptions;
+
+ if( year > start_year )
+ year_diff--;
+
+ exceptions = year_diff / 100;
+ exceptions -= year_diff / 400;
+
+ TIME64_TRACE3("# year: %lld, exceptions: %lld, year_diff: %lld\n",
+ year, exceptions, year_diff);
+
+ return exceptions * 16;
+}
+
+/* For a given year after 2038, pick the latest possible matching
+ year in the 28 year calendar cycle.
+
+ A matching year...
+ 1) Starts on the same day of the week.
+ 2) Has the same leap year status.
+
+ This is so the calendars match up.
+
+ Also the previous year must match. When doing Jan 1st you might
+ wind up on Dec 31st the previous year when doing a -UTC time zone.
+
+ Finally, the next year must have the same start day of week. This
+ is for Dec 31st with a +UTC time zone.
+ It doesn't need the same leap year status since we only care about
+ January 1st.
+*/
+static int safe_year(const Year year)
+{
+ int _safe_year = (int)year;
+ Year year_cycle;
+
+ if( year >= MIN_SAFE_YEAR && year <= MAX_SAFE_YEAR ) {
+ return _safe_year;
+ }
+
+ year_cycle = year + cycle_offset(year);
+
+ /* safe_years_low is off from safe_years_high by 8 years */
+ if( year < MIN_SAFE_YEAR )
+ year_cycle -= 8;
+
+ /* Change non-leap xx00 years to an equivalent */
+ if( is_exception_century(year) )
+ year_cycle += 11;
+
+ /* Also xx01 years, since the previous year will be wrong */
+ if( is_exception_century(year - 1) )
+ year_cycle += 17;
+
+ year_cycle %= SOLAR_CYCLE_LENGTH;
+ if( year_cycle < 0 )
+ year_cycle = SOLAR_CYCLE_LENGTH + year_cycle;
+
+ assert( year_cycle >= 0 );
+ assert( year_cycle < SOLAR_CYCLE_LENGTH );
+ if( year < MIN_SAFE_YEAR )
+ _safe_year = safe_years_low[year_cycle];
+ else if( year > MAX_SAFE_YEAR )
+ _safe_year = safe_years_high[year_cycle];
+ else
+ assert(0);
+
+ TIME64_TRACE3("# year: %lld, year_cycle: %lld, safe_year: %d\n",
+ year, year_cycle, _safe_year);
+
+ assert(_safe_year <= MAX_SAFE_YEAR && _safe_year >= MIN_SAFE_YEAR);
+
+ return _safe_year;
+}
+
+
+void copy_tm_to_TM64(const struct tm *src, struct TM *dest) {
+ if( src == NULL ) {
+ memset(dest, 0, sizeof(*dest));
+ }
+ else {
+# ifdef USE_TM64
+ dest->tm_sec = src->tm_sec;
+ dest->tm_min = src->tm_min;
+ dest->tm_hour = src->tm_hour;
+ dest->tm_mday = src->tm_mday;
+ dest->tm_mon = src->tm_mon;
+ dest->tm_year = (Year)src->tm_year;
+ dest->tm_wday = src->tm_wday;
+ dest->tm_yday = src->tm_yday;
+ dest->tm_isdst = src->tm_isdst;
+
+# ifdef HAVE_TM_TM_GMTOFF
+ dest->tm_gmtoff = src->tm_gmtoff;
+# endif
+
+# ifdef HAVE_TM_TM_ZONE
+ dest->tm_zone = src->tm_zone;
+# endif
+
+# else
+ /* They're the same type */
+ memcpy(dest, src, sizeof(*dest));
+# endif
+ }
+}
+
+
+void copy_TM64_to_tm(const struct TM *src, struct tm *dest) {
+ if( src == NULL ) {
+ memset(dest, 0, sizeof(*dest));
+ }
+ else {
+# ifdef USE_TM64
+ dest->tm_sec = src->tm_sec;
+ dest->tm_min = src->tm_min;
+ dest->tm_hour = src->tm_hour;
+ dest->tm_mday = src->tm_mday;
+ dest->tm_mon = src->tm_mon;
+ dest->tm_year = (int)src->tm_year;
+ dest->tm_wday = src->tm_wday;
+ dest->tm_yday = src->tm_yday;
+ dest->tm_isdst = src->tm_isdst;
+
+# ifdef HAVE_TM_TM_GMTOFF
+ dest->tm_gmtoff = src->tm_gmtoff;
+# endif
+
+# ifdef HAVE_TM_TM_ZONE
+ dest->tm_zone = src->tm_zone;
+# endif
+
+# else
+ /* They're the same type */
+ memcpy(dest, src, sizeof(*dest));
+# endif
+ }
+}
+
+
+#if !defined(HAVE_LOCALTIME_R) && !defined(_WIN32)
+/* Simulate localtime_r() to the best of our ability */
+static struct tm * fake_localtime_r(const time_t *time, struct tm *result) {
+ const struct tm *static_result = localtime(time);
+
+ assert(result != NULL);
+
+ if( static_result == NULL ) {
+ memset(result, 0, sizeof(*result));
+ return NULL;
+ }
+ else {
+ memcpy(result, static_result, sizeof(*result));
+ return result;
+ }
+}
+#endif
+
+
+#if !defined(HAVE_GMTIME_R) && !defined(_WIN32)
+/* Simulate gmtime_r() to the best of our ability */
+static struct tm * fake_gmtime_r(const time_t *time, struct tm *result) {
+ const struct tm *static_result = gmtime(time);
+
+ assert(result != NULL);
+
+ if( static_result == NULL ) {
+ memset(result, 0, sizeof(*result));
+ return NULL;
+ }
+ else {
+ memcpy(result, static_result, sizeof(*result));
+ return result;
+ }
+}
+#endif
+
+
+static Time64_T seconds_between_years(Year left_year, Year right_year) {
+ int increment = (left_year > right_year) ? 1 : -1;
+ Time64_T seconds = 0;
+ int cycles;
+
+ if( left_year > 2400 ) {
+ cycles = (left_year - 2400) / 400;
+ left_year -= cycles * 400;
+ seconds += cycles * seconds_in_gregorian_cycle;
+ }
+ else if( left_year < 1600 ) {
+ cycles = (left_year - 1600) / 400;
+ left_year += cycles * 400;
+ seconds += cycles * seconds_in_gregorian_cycle;
+ }
+
+ while( left_year != right_year ) {
+ seconds += length_of_year[IS_LEAP(right_year - 1900)] * 60 * 60 * 24;
+ right_year += increment;
+ }
+
+ return seconds * increment;
+}
+
+
+Time64_T mktime64(struct TM *input_date) {
+ struct tm safe_date;
+ struct TM date;
+ Time64_T timev;
+ Year year = input_date->tm_year + 1900;
+
+ if( date_in_safe_range(input_date, &SYSTEM_MKTIME_MIN, &SYSTEM_MKTIME_MAX) )
+ {
+ copy_TM64_to_tm(input_date, &safe_date);
+ timev = (Time64_T)mktime(&safe_date);
+
+ /* Correct the possibly out of bound input date */
+ copy_tm_to_TM64(&safe_date, input_date);
+ return timev;
+ }
+
+ /* Have to make the year safe in date else it won't fit in safe_date */
+ date = *input_date;
+ date.tm_year = safe_year(year) - 1900;
+ copy_TM64_to_tm(&date, &safe_date);
+
+ timev = (Time64_T)mktime(&safe_date);
+
+ /* Correct the user's possibly out of bound input date */
+ copy_tm_to_TM64(&safe_date, input_date);
+
+ timev += seconds_between_years(year, (Year)(safe_date.tm_year) + 1900);
+
+ return timev;
+}
+
+
+/* Because I think mktime() is a crappy name */
+Time64_T timelocal64(struct TM *date) {
+ return mktime64(date);
+}
+
+
+struct TM *gmtime64_r (const Time64_T *in_time, struct TM *p)
+{
+ int v_tm_sec, v_tm_min, v_tm_hour, v_tm_mon, v_tm_wday;
+ Time64_T v_tm_tday;
+ int leap;
+ Time64_T m;
+ Time64_T timev = *in_time;
+ Year year = 70;
+ int cycles = 0;
+
+ assert(p != NULL);
+
+ /* Use the system gmtime() if time_t is small enough */
+ if( SHOULD_USE_SYSTEM_GMTIME(*in_time) ) {
+ time_t safe_time = (time_t)*in_time;
+ struct tm safe_date;
+ GMTIME_R(&safe_time, &safe_date);
+
+ copy_tm_to_TM64(&safe_date, p);
+ assert(check_tm(p));
+
+ return p;
+ }
+
+#ifdef HAVE_TM_TM_GMTOFF
+ p->tm_gmtoff = 0;
+#endif
+ p->tm_isdst = 0;
+
+#ifdef HAVE_TM_TM_ZONE
+ p->tm_zone = (char*)"UTC";
+#endif
+
+ v_tm_sec = (int)(timev % 60);
+ timev /= 60;
+ v_tm_min = (int)(timev % 60);
+ timev /= 60;
+ v_tm_hour = (int)(timev % 24);
+ timev /= 24;
+ v_tm_tday = timev;
+
+ WRAP (v_tm_sec, v_tm_min, 60);
+ WRAP (v_tm_min, v_tm_hour, 60);
+ WRAP (v_tm_hour, v_tm_tday, 24);
+
+ v_tm_wday = (int)((v_tm_tday + 4) % 7);
+ if (v_tm_wday < 0)
+ v_tm_wday += 7;
+ m = v_tm_tday;
+
+ if (m >= CHEAT_DAYS) {
+ year = CHEAT_YEARS;
+ m -= CHEAT_DAYS;
+ }
+
+ if (m >= 0) {
+ /* Gregorian cycles, this is huge optimization for distant times */
+ cycles = (int)(m / (Time64_T) days_in_gregorian_cycle);
+ if( cycles ) {
+ m -= (cycles * (Time64_T) days_in_gregorian_cycle);
+ year += (cycles * years_in_gregorian_cycle);
+ }
+
+ /* Years */
+ leap = IS_LEAP (year);
+ while (m >= (Time64_T) length_of_year[leap]) {
+ m -= (Time64_T) length_of_year[leap];
+ year++;
+ leap = IS_LEAP (year);
+ }
+
+ /* Months */
+ v_tm_mon = 0;
+ while (m >= (Time64_T) days_in_month[leap][v_tm_mon]) {
+ m -= (Time64_T) days_in_month[leap][v_tm_mon];
+ v_tm_mon++;
+ }
+ } else {
+ year--;
+
+ /* Gregorian cycles */
+ cycles = (int)((m / (Time64_T) days_in_gregorian_cycle) + 1);
+ if( cycles ) {
+ m -= (cycles * (Time64_T) days_in_gregorian_cycle);
+ year += (cycles * years_in_gregorian_cycle);
+ }
+
+ /* Years */
+ leap = IS_LEAP (year);
+ while (m < (Time64_T) -length_of_year[leap]) {
+ m += (Time64_T) length_of_year[leap];
+ year--;
+ leap = IS_LEAP (year);
+ }
+
+ /* Months */
+ v_tm_mon = 11;
+ while (m < (Time64_T) -days_in_month[leap][v_tm_mon]) {
+ m += (Time64_T) days_in_month[leap][v_tm_mon];
+ v_tm_mon--;
+ }
+ m += (Time64_T) days_in_month[leap][v_tm_mon];
+ }
+
+ p->tm_year = year;
+ if( p->tm_year != year ) {
+#ifdef EOVERFLOW
+ errno = EOVERFLOW;
+#endif
+ return NULL;
+ }
+
+ /* At this point m is less than a year so casting to an int is safe */
+ p->tm_mday = (int) m + 1;
+ p->tm_yday = julian_days_by_month[leap][v_tm_mon] + (int)m;
+ p->tm_sec = v_tm_sec;
+ p->tm_min = v_tm_min;
+ p->tm_hour = v_tm_hour;
+ p->tm_mon = v_tm_mon;
+ p->tm_wday = v_tm_wday;
+
+ assert(check_tm(p));
+
+ return p;
+}
+
+
+struct TM *localtime64_r (const Time64_T *timev, struct TM *local_tm)
+{
+ time_t safe_time;
+ struct tm safe_date;
+ struct TM gm_tm;
+ Year orig_year;
+ int month_diff;
+
+ assert(local_tm != NULL);
+
+ /* Use the system localtime() if time_t is small enough */
+ if( SHOULD_USE_SYSTEM_LOCALTIME(*timev) ) {
+ safe_time = (time_t)*timev;
+
+ TIME64_TRACE1("Using system localtime for %lld\n", *timev);
+
+ LOCALTIME_R(&safe_time, &safe_date);
+
+ copy_tm_to_TM64(&safe_date, local_tm);
+ assert(check_tm(local_tm));
+
+ return local_tm;
+ }
+
+ if( gmtime64_r(timev, &gm_tm) == NULL ) {
+ TIME64_TRACE1("gmtime64_r returned null for %lld\n", *timev);
+ return NULL;
+ }
+
+ orig_year = gm_tm.tm_year;
+
+ if (gm_tm.tm_year > (2037 - 1900) ||
+ gm_tm.tm_year < (1970 - 1900)
+ )
+ {
+ TIME64_TRACE1("Mapping tm_year %lld to safe_year\n", (Year)gm_tm.tm_year);
+ gm_tm.tm_year = safe_year((Year)(gm_tm.tm_year) + 1900) - 1900;
+ }
+
+ safe_time = (time_t)timegm64(&gm_tm);
+ if( LOCALTIME_R(&safe_time, &safe_date) == NULL ) {
+ TIME64_TRACE1("localtime_r(%d) returned NULL\n", (int)safe_time);
+ return NULL;
+ }
+
+ copy_tm_to_TM64(&safe_date, local_tm);
+
+ local_tm->tm_year = orig_year;
+ if( local_tm->tm_year != orig_year ) {
+ TIME64_TRACE2("tm_year overflow: tm_year %lld, orig_year %lld\n",
+ (Year)local_tm->tm_year, (Year)orig_year);
+
+#ifdef EOVERFLOW
+ errno = EOVERFLOW;
+#endif
+ return NULL;
+ }
+
+
+ month_diff = local_tm->tm_mon - gm_tm.tm_mon;
+
+ /* When localtime is Dec 31st previous year and
+ gmtime is Jan 1st next year.
+ */
+ if( month_diff == 11 ) {
+ local_tm->tm_year--;
+ }
+
+ /* When localtime is Jan 1st, next year and
+ gmtime is Dec 31st, previous year.
+ */
+ if( month_diff == -11 ) {
+ local_tm->tm_year++;
+ }
+
+ /* GMT is Jan 1st, xx01 year, but localtime is still Dec 31st
+ in a non-leap xx00. There is one point in the cycle
+ we can't account for which the safe xx00 year is a leap
+ year. So we need to correct for Dec 31st comming out as
+ the 366th day of the year.
+ */
+ if( !IS_LEAP(local_tm->tm_year) && local_tm->tm_yday == 365 )
+ local_tm->tm_yday--;
+
+ assert(check_tm(local_tm));
+
+ return local_tm;
+}
+
+
+static int valid_tm_wday( const struct TM* date ) {
+ if( 0 <= date->tm_wday && date->tm_wday <= 6 )
+ return 1;
+
+ return 0;
+}
+
+static int valid_tm_mon( const struct TM* date ) {
+ if( 0 <= date->tm_mon && date->tm_mon <= 11 )
+ return 1;
+
+ return 0;
+}
+
+
+char *asctime64_r( const struct TM* date, char *result ) {
+ /* I figure everything else can be displayed, even hour 25, but if
+ these are out of range we walk off the name arrays */
+ if( !valid_tm_wday(date) || !valid_tm_mon(date) )
+ return NULL;
+
+ sprintf(result, TM64_ASCTIME_FORMAT,
+ wday_name[date->tm_wday],
+ mon_name[date->tm_mon],
+ date->tm_mday, date->tm_hour,
+ date->tm_min, date->tm_sec,
+ 1900 + date->tm_year);
+
+ return result;
+}
+
+
+char *ctime64_r( const Time64_T* timev, char* result ) {
+ struct TM date;
+
+ if (!localtime64_r( timev, &date ))
+ return NULL;
+
+ return asctime64_r( &date, result );
+}
+
diff --git a/src/time64.h b/src/time64.h
new file mode 100644
index 0000000..2c20ffe
--- /dev/null
+++ b/src/time64.h
@@ -0,0 +1,92 @@
+#ifndef TIME64_H
+# define TIME64_H
+
+#include <time.h>
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+/* Set our custom types */
+typedef long long Int64;
+typedef Int64 Time64_T;
+typedef Int64 Year;
+
+#ifndef TIME64_MIN
+#define TIME64_MIN ((Time64_T)INT64_MIN)
+#endif
+
+#ifndef TIME64_MAX
+#define TIME64_MAX ((Time64_T)INT64_MAX)
+#endif
+
+/* A copy of the tm struct but with a 64 bit year */
+struct TM64 {
+ int tm_sec;
+ int tm_min;
+ int tm_hour;
+ int tm_mday;
+ int tm_mon;
+ Year tm_year;
+ int tm_wday;
+ int tm_yday;
+ int tm_isdst;
+
+#ifdef HAVE_TM_TM_GMTOFF
+ long tm_gmtoff;
+#endif
+
+#ifdef HAVE_TM_TM_ZONE
+ char *tm_zone;
+#endif
+};
+
+
+/* Decide which tm struct to use */
+#ifdef USE_TM64
+#define TM TM64
+#else
+#define TM tm
+#endif
+
+
+/* Declare public functions */
+struct TM *gmtime64_r (const Time64_T *, struct TM *);
+struct TM *localtime64_r (const Time64_T *, struct TM *);
+
+char *asctime64_r (const struct TM *, char *);
+
+char *ctime64_r (const Time64_T*, char*);
+
+Time64_T timegm64 (const struct TM *);
+Time64_T mktime64 (struct TM *);
+Time64_T timelocal64 (struct TM *);
+
+
+/* Not everyone has gm/localtime_r(), provide a replacement */
+#ifdef HAVE_LOCALTIME_R
+# define LOCALTIME_R(clock, result) localtime_r(clock, result)
+#elif defined(_WIN32)
+# define LOCALTIME_R(clock, result) (localtime_s(result, clock) ? NULL : result)
+#else
+# define LOCALTIME_R(clock, result) fake_localtime_r(clock, result)
+#endif
+#ifdef HAVE_GMTIME_R
+# define GMTIME_R(clock, result) gmtime_r(clock, result)
+#elif defined (_WIN32)
+# define GMTIME_R(clock, result) (gmtime_s(result, clock) ? NULL : result)
+#else
+# define GMTIME_R(clock, result) fake_gmtime_r(clock, result)
+#endif
+
+
+/* Use a different asctime format depending on how big the year is */
+#ifdef USE_TM64
+ #define TM64_ASCTIME_FORMAT "%.3s %.3s%3d %.2d:%.2d:%.2d %lld\n"
+#else
+ #define TM64_ASCTIME_FORMAT "%.3s %.3s%3d %.2d:%.2d:%.2d %d\n"
+#endif
+
+void copy_tm_to_TM64(const struct tm *src, struct TM *dest);
+void copy_TM64_to_tm(const struct TM *src, struct tm *dest);
+
+#endif
diff --git a/src/time64_limits.h b/src/time64_limits.h
new file mode 100644
index 0000000..1151cf2
--- /dev/null
+++ b/src/time64_limits.h
@@ -0,0 +1,97 @@
+/*
+ Maximum and minimum inputs your system's respective time functions
+ can correctly handle. time64.h will use your system functions if
+ the input falls inside these ranges and corresponding USE_SYSTEM_*
+ constant is defined.
+*/
+
+#ifndef TIME64_LIMITS_H
+#define TIME64_LIMITS_H
+
+#include <time.h>
+
+/* Max/min for localtime() */
+#define SYSTEM_LOCALTIME_MAX 2147483647
+#define SYSTEM_LOCALTIME_MIN -2147483647-1
+
+/* Max/min for gmtime() */
+#define SYSTEM_GMTIME_MAX 2147483647
+#define SYSTEM_GMTIME_MIN -2147483647-1
+
+/* Max/min for mktime() */
+static const struct tm SYSTEM_MKTIME_MAX = {
+ 7,
+ 14,
+ 19,
+ 18,
+ 0,
+ 138,
+ 1,
+ 17,
+ 0
+#ifdef HAVE_TM_TM_GMTOFF
+ ,-28800
+#endif
+#ifdef HAVE_TM_TM_ZONE
+ ,(char*)"PST"
+#endif
+};
+
+static const struct tm SYSTEM_MKTIME_MIN = {
+ 52,
+ 45,
+ 12,
+ 13,
+ 11,
+ 1,
+ 5,
+ 346,
+ 0
+#ifdef HAVE_TM_TM_GMTOFF
+ ,-28800
+#endif
+#ifdef HAVE_TM_TM_ZONE
+ ,(char*)"PST"
+#endif
+};
+
+/* Max/min for timegm() */
+#ifdef HAVE_TIMEGM
+static const struct tm SYSTEM_TIMEGM_MAX = {
+ 7,
+ 14,
+ 3,
+ 19,
+ 0,
+ 138,
+ 2,
+ 18,
+ 0
+ #ifdef HAVE_TM_TM_GMTOFF
+ ,0
+ #endif
+ #ifdef HAVE_TM_TM_ZONE
+ ,(char*)"UTC"
+ #endif
+};
+
+static const struct tm SYSTEM_TIMEGM_MIN = {
+ 52,
+ 45,
+ 20,
+ 13,
+ 11,
+ 1,
+ 5,
+ 346,
+ 0
+ #ifdef HAVE_TM_TM_GMTOFF
+ ,0
+ #endif
+ #ifdef HAVE_TM_TM_ZONE
+ ,(char*)"UTC"
+ #endif
+};
+#endif /* HAVE_TIMEGM */
+
+#endif /* TIME64_LIMITS_H */
diff --git a/src/xplist.c b/src/xplist.c
index ba312a1..b2c134e 100644
--- a/src/xplist.c
+++ b/src/xplist.c
@@ -1,7 +1,9 @@
/*
- * plist.c
+ * xplist.c
* XML plist implementation
*
+ * Copyright (c) 2010-2017 Nikias Bassen All Rights Reserved.
+ * Copyright (c) 2010-2015 Martin Szulecki All Rights Reserved.
* Copyright (c) 2008 Jonathan Beck All Rights Reserved.
*
* This library is free software; you can redistribute it and/or
@@ -19,6 +21,13 @@
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#ifdef HAVE_STRPTIME
+#define _XOPEN_SOURCE 600
+#endif
#include <string.h>
#include <assert.h>
@@ -27,437 +36,1556 @@
#include <time.h>
#include <inttypes.h>
-#include <locale.h>
-
-#include <libxml/parser.h>
-#include <libxml/tree.h>
+#include <float.h>
+#include <math.h>
+#include <limits.h>
+#include <errno.h>
#include <node.h>
-#include <node_list.h>
-#include <node_iterator.h>
#include "plist.h"
#include "base64.h"
-
-#define XPLIST_TEXT BAD_CAST("text")
-#define XPLIST_KEY BAD_CAST("key")
-#define XPLIST_FALSE BAD_CAST("false")
-#define XPLIST_TRUE BAD_CAST("true")
-#define XPLIST_INT BAD_CAST("integer")
-#define XPLIST_REAL BAD_CAST("real")
-#define XPLIST_DATE BAD_CAST("date")
-#define XPLIST_DATA BAD_CAST("data")
-#define XPLIST_STRING BAD_CAST("string")
-#define XPLIST_ARRAY BAD_CAST("array")
-#define XPLIST_DICT BAD_CAST("dict")
-
-static const char *plist_base = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n\
+#include "strbuf.h"
+#include "time64.h"
+#include "hashtable.h"
+#include "common.h"
+
+#define XPLIST_KEY "key"
+#define XPLIST_KEY_LEN 3
+#define XPLIST_FALSE "false"
+#define XPLIST_FALSE_LEN 5
+#define XPLIST_TRUE "true"
+#define XPLIST_TRUE_LEN 4
+#define XPLIST_INT "integer"
+#define XPLIST_INT_LEN 7
+#define XPLIST_REAL "real"
+#define XPLIST_REAL_LEN 4
+#define XPLIST_DATE "date"
+#define XPLIST_DATE_LEN 4
+#define XPLIST_DATA "data"
+#define XPLIST_DATA_LEN 4
+#define XPLIST_STRING "string"
+#define XPLIST_STRING_LEN 6
+#define XPLIST_ARRAY "array"
+#define XPLIST_ARRAY_LEN 5
+#define XPLIST_DICT "dict"
+#define XPLIST_DICT_LEN 4
+
+#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\
<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n\
-<plist version=\"1.0\">\n\
-</plist>\0";
+<plist version=\"1.0\">\n";
+static const char XML_PLIST_EPILOG[] = "</plist>\n";
+#ifdef DEBUG
+static int plist_xml_debug = 0;
+#define PLIST_XML_ERR(...) if (plist_xml_debug) { fprintf(stderr, "libplist[xmlparser] ERROR: " __VA_ARGS__); }
+#define PLIST_XML_WRITE_ERR(...) if (plist_xml_debug) { fprintf(stderr, "libplist[xmlwriter] ERROR: " __VA_ARGS__); }
+#else
+#define PLIST_XML_ERR(...)
+#define PLIST_XML_WRITE_ERR(...)
+#endif
-/** Formats a block of text to be a given indentation and width.
- *
- * The total width of the return string will be depth + cols.
- *
- * @param buf The string to format.
- * @param cols The number of text columns for returned block of text.
- * @param depth The number of tabs to indent the returned block of text.
- *
- * @return The formatted string.
- */
-static char *format_string(const char *buf, size_t len, int cols, int depth)
+void plist_xml_init(void)
{
- if (!buf || !(len > 0)) return NULL;
- int colw = depth + cols + 1;
- int nlines = len / cols + 1;
- char *new_buf = NULL;
- int i = 0;
- int j = 0;
-
- assert(cols >= 0);
- assert(depth >= 0);
-
- new_buf = (char*) malloc(nlines * colw + depth + 1);
- assert(new_buf != 0);
- memset(new_buf, 0, nlines * colw + depth + 1);
-
- // Inserts new lines and tabs at appropriate locations
- for (i = 0; i < nlines; i++)
- {
- new_buf[i * colw] = '\n';
- for (j = 0; j < depth; j++)
- new_buf[i * colw + 1 + j] = '\t';
- memcpy(new_buf + i * colw + 1 + depth, buf + i * cols, (i + 1) * cols <= len ? cols : len - i * cols);
+ /* init XML stuff */
+#ifdef DEBUG
+ char *env_debug = getenv("PLIST_XML_DEBUG");
+ if (env_debug && !strcmp(env_debug, "1")) {
+ plist_xml_debug = 1;
}
- new_buf[len + (1 + depth) * nlines] = '\n';
-
- // Inserts final row of indentation and termination character
- for (j = 0; j < depth; j++)
- new_buf[len + (1 + depth) * nlines + 1 + j] = '\t';
- new_buf[len + (1 + depth) * nlines + depth + 1] = '\0';
-
- return new_buf;
+#endif
}
-
-
-struct xml_node
+void plist_xml_deinit(void)
{
- xmlNodePtr xml;
- uint32_t depth;
-};
+ /* deinit XML stuff */
+}
-/** Creates a new plist XML document.
- *
- * @return The plist XML document.
- */
-static xmlDocPtr new_xml_plist(void)
+void plist_xml_set_debug(int debug)
{
- char *plist = strdup(plist_base);
- xmlDocPtr plist_xml = xmlParseMemory(plist, strlen(plist));
-
- if (!plist_xml)
- return NULL;
-
- free(plist);
-
- return plist_xml;
+#if DEBUG
+ plist_xml_debug = debug;
+#endif
}
-static void node_to_xml(node_t* node, void *xml_struct)
+static plist_err_t node_to_xml(node_t node, bytearray_t **outbuf, uint32_t depth)
{
- struct xml_node *xstruct = NULL;
plist_data_t node_data = NULL;
- xmlNodePtr child_node = NULL;
char isStruct = FALSE;
+ char tagOpen = FALSE;
- const xmlChar *tag = NULL;
- char *val = NULL;
-
- //for base64
- char *valtmp = NULL;
+ const char *tag = NULL;
+ size_t tag_len = 0;
+ char val[64] = { 0 };
+ size_t val_len = 0;
uint32_t i = 0;
- if (!node)
- return;
+ if (!node) {
+ PLIST_XML_WRITE_ERR("Encountered invalid empty node in property list\n");
+ return PLIST_ERR_INVALID_ARG;
+ }
- xstruct = (struct xml_node *) xml_struct;
node_data = plist_get_data(node);
switch (node_data->type)
{
case PLIST_BOOLEAN:
{
- if (node_data->boolval)
+ if (node_data->boolval) {
tag = XPLIST_TRUE;
- else
+ tag_len = XPLIST_TRUE_LEN;
+ } else {
tag = XPLIST_FALSE;
+ tag_len = XPLIST_FALSE_LEN;
+ }
}
break;
- case PLIST_UINT:
+ case PLIST_INT:
tag = XPLIST_INT;
- val = (char*)malloc(64);
- (void)snprintf(val, 64, "%"PRIu64, node_data->intval);
+ tag_len = XPLIST_INT_LEN;
+ if (node_data->length == 16) {
+ val_len = snprintf(val, sizeof(val), "%" PRIu64, node_data->intval);
+ } else {
+ val_len = snprintf(val, sizeof(val), "%" PRIi64, node_data->intval);
+ }
break;
case PLIST_REAL:
tag = XPLIST_REAL;
- val = (char*)malloc(64);
- (void)snprintf(val, 64, "%f", node_data->realval);
+ tag_len = XPLIST_REAL_LEN;
+ val_len = dtostr(val, sizeof(val), node_data->realval);
break;
case PLIST_STRING:
tag = XPLIST_STRING;
- val = strdup(node_data->strval);
+ tag_len = XPLIST_STRING_LEN;
+ /* contents processed directly below */
break;
case PLIST_KEY:
tag = XPLIST_KEY;
- val = strdup((char*) node_data->strval);
+ tag_len = XPLIST_KEY_LEN;
+ /* contents processed directly below */
break;
case PLIST_DATA:
tag = XPLIST_DATA;
- if (node_data->length)
- {
- size_t len = node_data->length;
- valtmp = base64encode(node_data->buff, &len);
- val = format_string(valtmp, len, 68, xstruct->depth);
- free(valtmp);
- }
+ tag_len = XPLIST_DATA_LEN;
+ /* contents processed directly below */
break;
case PLIST_ARRAY:
tag = XPLIST_ARRAY;
- isStruct = TRUE;
+ tag_len = XPLIST_ARRAY_LEN;
+ isStruct = (node->children) ? TRUE : FALSE;
break;
case PLIST_DICT:
tag = XPLIST_DICT;
- isStruct = TRUE;
+ tag_len = XPLIST_DICT_LEN;
+ isStruct = (node->children) ? TRUE : FALSE;
break;
case PLIST_DATE:
tag = XPLIST_DATE;
+ tag_len = XPLIST_DATE_LEN;
{
- time_t time = (time_t)node_data->timeval.tv_sec;
- struct tm *btime = localtime(&time);
+ Time64_T timev;
+ if (plist_real_to_time64(node_data->realval, &timev) < 0) {
+ PLIST_XML_WRITE_ERR("Encountered invalid date value %f\n", node_data->realval);
+ return PLIST_ERR_INVALID_ARG;
+ }
+ struct TM _btime;
+ struct TM *btime = gmtime64_r(&timev, &_btime);
if (btime) {
- val = (char*)malloc(24);
- memset(val, 0, 24);
- if (strftime(val, 24, "%Y-%m-%dT%H:%M:%SZ", btime) <= 0) {
- free (val);
- val = NULL;
+ struct tm _tmcopy;
+ copy_TM64_to_tm(btime, &_tmcopy);
+ val_len = strftime(val, sizeof(val), "%Y-%m-%dT%H:%M:%SZ", &_tmcopy);
+ if (val_len <= 0) {
+ snprintf(val, sizeof(val), "1970-01-01T00:00:00Z");
}
}
}
break;
- default:
+ case PLIST_UID:
+ tag = XPLIST_DICT;
+ tag_len = XPLIST_DICT_LEN;
+ if (node_data->length == 16) {
+ val_len = snprintf(val, sizeof(val), "%" PRIu64, node_data->intval);
+ } else {
+ val_len = snprintf(val, sizeof(val), "%" PRIi64, node_data->intval);
+ }
break;
+ case PLIST_NULL:
+ PLIST_XML_WRITE_ERR("PLIST_NULL type is not valid for XML format\n");
+ return PLIST_ERR_FORMAT;
+ default:
+ return PLIST_ERR_UNKNOWN;
}
- for (i = 0; i < xstruct->depth; i++)
- {
- xmlNodeAddContent(xstruct->xml, BAD_CAST("\t"));
+ for (i = 0; i < depth; i++) {
+ str_buf_append(*outbuf, "\t", 1);
}
- if (node_data->type == PLIST_STRING) {
+
+ /* append tag */
+ str_buf_append(*outbuf, "<", 1);
+ str_buf_append(*outbuf, tag, tag_len);
+ if ((node_data->type == PLIST_STRING || node_data->type == PLIST_KEY) && node_data->length > 0) {
+ size_t j;
+ size_t len;
+ off_t start = 0;
+ off_t cur = 0;
+
+ str_buf_append(*outbuf, ">", 1);
+ tagOpen = TRUE;
+
/* make sure we convert the following predefined xml entities */
- /* < = &lt; > = &gt; ' = &apos; " = &quot; & = &amp; */
- child_node = xmlNewTextChild(xstruct->xml, NULL, tag, BAD_CAST(val));
- } else
- child_node = xmlNewChild(xstruct->xml, NULL, tag, BAD_CAST(val));
- xmlNodeAddContent(xstruct->xml, BAD_CAST("\n"));
- if (val) {
- free(val);
- }
+ /* < = &lt; > = &gt; & = &amp; */
+ len = node_data->length;
+ for (j = 0; j < len; j++) {
+ switch (node_data->strval[j]) {
+ case '<':
+ str_buf_append(*outbuf, node_data->strval + start, cur - start);
+ str_buf_append(*outbuf, "&lt;", 4);
+ start = cur+1;
+ break;
+ case '>':
+ str_buf_append(*outbuf, node_data->strval + start, cur - start);
+ str_buf_append(*outbuf, "&gt;", 4);
+ start = cur+1;
+ break;
+ case '&':
+ str_buf_append(*outbuf, node_data->strval + start, cur - start);
+ str_buf_append(*outbuf, "&amp;", 5);
+ start = cur+1;
+ break;
+ default:
+ break;
+ }
+ cur++;
+ }
+ str_buf_append(*outbuf, node_data->strval + start, cur - start);
+ } else if (node_data->type == PLIST_DATA) {
+ str_buf_append(*outbuf, ">", 1);
+ tagOpen = TRUE;
+ str_buf_append(*outbuf, "\n", 1);
+ if (node_data->length > 0) {
+ uint32_t j = 0;
+ uint32_t indent = (depth > 8) ? 8 : depth;
+ uint32_t maxread = MAX_DATA_BYTES_PER_LINE(indent);
+ size_t count = 0;
+ size_t amount = (node_data->length / 3 * 4) + 4 + (((node_data->length / maxread) + 1) * (indent+1));
+ if ((*outbuf)->len + amount > (*outbuf)->capacity) {
+ str_buf_grow(*outbuf, amount);
+ }
+ while (j < node_data->length) {
+ for (i = 0; i < indent; i++) {
+ str_buf_append(*outbuf, "\t", 1);
+ }
+ count = (node_data->length-j < maxread) ? node_data->length-j : maxread;
+ assert((*outbuf)->len + count < (*outbuf)->capacity);
+ (*outbuf)->len += base64encode((char*)(*outbuf)->data + (*outbuf)->len, node_data->buff + j, count);
+ str_buf_append(*outbuf, "\n", 1);
+ j+=count;
+ }
+ }
+ for (i = 0; i < depth; i++) {
+ str_buf_append(*outbuf, "\t", 1);
+ }
+ } else if (node_data->type == PLIST_UID) {
+ /* special case for UID nodes: create a DICT */
+ str_buf_append(*outbuf, ">", 1);
+ tagOpen = TRUE;
+ str_buf_append(*outbuf, "\n", 1);
+
+ /* add CF$UID key */
+ for (i = 0; i < depth+1; i++) {
+ str_buf_append(*outbuf, "\t", 1);
+ }
+ str_buf_append(*outbuf, "<key>CF$UID</key>", 17);
+ str_buf_append(*outbuf, "\n", 1);
- //add return for structured types
- if (node_data->type == PLIST_ARRAY || node_data->type == PLIST_DICT)
- xmlNodeAddContent(child_node, BAD_CAST("\n"));
+ /* add UID value */
+ for (i = 0; i < depth+1; i++) {
+ str_buf_append(*outbuf, "\t", 1);
+ }
+ str_buf_append(*outbuf, "<integer>", 9);
+ str_buf_append(*outbuf, val, val_len);
+ str_buf_append(*outbuf, "</integer>", 10);
+ str_buf_append(*outbuf, "\n", 1);
- if (isStruct)
- {
- struct xml_node child = { child_node, xstruct->depth + 1 };
- node_iterator_t *ni = node_iterator_create(node->children);
- node_t *ch;
- while ((ch = node_iterator_next(ni))) {
- node_to_xml(ch, &child);
+ for (i = 0; i < depth; i++) {
+ str_buf_append(*outbuf, "\t", 1);
}
- node_iterator_destroy(ni);
+ } else if (val_len > 0) {
+ str_buf_append(*outbuf, ">", 1);
+ tagOpen = TRUE;
+ str_buf_append(*outbuf, val, val_len);
+ } else if (isStruct) {
+ tagOpen = TRUE;
+ str_buf_append(*outbuf, ">", 1);
+ } else {
+ tagOpen = FALSE;
+ str_buf_append(*outbuf, "/>", 2);
}
- //fix indent for structured types
- if (node_data->type == PLIST_ARRAY || node_data->type == PLIST_DICT)
- {
- for (i = 0; i < xstruct->depth; i++)
- {
- xmlNodeAddContent(child_node, BAD_CAST("\t"));
+ if (isStruct) {
+ /* add newline for structured types */
+ str_buf_append(*outbuf, "\n", 1);
+
+ /* add child nodes */
+ if (node_data->type == PLIST_DICT && node->children) {
+ assert((node->children->count % 2) == 0);
+ }
+ node_t ch;
+ for (ch = node_first_child(node); ch; ch = node_next_sibling(ch)) {
+ plist_err_t res = node_to_xml(ch, outbuf, depth+1);
+ if (res < 0) return res;
+ }
+
+ /* fix indent for structured types */
+ for (i = 0; i < depth; i++) {
+ str_buf_append(*outbuf, "\t", 1);
}
}
- return;
+ if (tagOpen) {
+ /* add closing tag */
+ str_buf_append(*outbuf, "</", 2);
+ str_buf_append(*outbuf, tag, tag_len);
+ str_buf_append(*outbuf, ">", 1);
+ }
+ str_buf_append(*outbuf, "\n", 1);
+ return PLIST_ERR_SUCCESS;
}
-static void parse_date(const char *strval, struct tm *btime)
+static int parse_date(const char *strval, struct TM *btime)
{
- if (!btime) return;
- memset(btime, 0, sizeof(struct tm));
- if (!strval) return;
-#ifdef strptime
- strptime((char*)strval, "%Y-%m-%dT%H:%M:%SZ", btime);
+ if (!btime) return -1;
+ memset(btime, 0, sizeof(*btime));
+ if (!strval) return -1;
+#ifdef HAVE_STRPTIME
+#ifdef USE_TM64
+ struct tm t = { 0 };
+ char* r = strptime((char*)strval, "%Y-%m-%dT%H:%M:%SZ", &t);
+ if (!r || *r != '\0') {
+ return -1;
+ }
+ copy_tm_to_TM64(&t, btime);
+#else
+ char* r = strptime((char*)strval, "%Y-%m-%dT%H:%M:%SZ", btime);
+ if (!r || *r != '\0') {
+ return -1;
+ }
+#endif
#else
- sscanf(strval, "%d-%d-%dT%d:%d:%dZ", &btime->tm_year, &btime->tm_mon, &btime->tm_mday, &btime->tm_hour, &btime->tm_min, &btime->tm_sec);
+#ifdef USE_TM64
+ #define PLIST_SSCANF_FORMAT "%lld-%d-%dT%d:%d:%dZ"
+#else
+ #define PLIST_SSCANF_FORMAT "%d-%d-%dT%d:%d:%dZ"
+#endif
+ int n = 0;
+ if (sscanf(strval, PLIST_SSCANF_FORMAT "%n", &btime->tm_year, &btime->tm_mon, &btime->tm_mday, &btime->tm_hour, &btime->tm_min, &btime->tm_sec, &n) != 6) return -1;
+ if (strval[n] != '\0') return -1;
+ if (btime->tm_mon < 1 || btime->tm_mon > 12) return -1;
+ if (btime->tm_mday < 1 || btime->tm_mday > 31) return -1;
+ if (btime->tm_hour < 0 || btime->tm_hour > 23) return -1;
+ if (btime->tm_min < 0 || btime->tm_min > 59) return -1;
+ if (btime->tm_sec < 0 || btime->tm_sec > 59) return -1;
btime->tm_year-=1900;
btime->tm_mon--;
#endif
+ btime->tm_isdst=0;
+ return 0;
}
-static void xml_to_node(xmlNodePtr xml_node, plist_t * plist_node)
+static plist_err_t _node_estimate_size(node_t node, uint64_t *size, uint32_t depth, hashtable_t *visited)
{
- xmlNodePtr node = NULL;
- plist_data_t data = NULL;
- plist_t subnode = NULL;
+ plist_data_t data;
+ if (!node) {
+ return PLIST_ERR_INVALID_ARG;
+ }
- //for string
- long len = 0;
- int type = 0;
+ if (depth > PLIST_MAX_NESTING_DEPTH) {
+ PLIST_XML_WRITE_ERR("maximum nesting depth (%u) exceeded\n", (unsigned)PLIST_MAX_NESTING_DEPTH);
+ return PLIST_ERR_MAX_NESTING;
+ }
- if (!xml_node)
- return;
+ if (hash_table_lookup(visited, node)) {
+ PLIST_XML_WRITE_ERR("circular reference detected\n");
+ return PLIST_ERR_CIRCULAR_REF;
+ }
- for (node = xml_node->children; node; node = node->next)
- {
+ // mark as visited
+ hash_table_insert(visited, node, (void*)1);
- while (node && !xmlStrcmp(node->name, XPLIST_TEXT))
- node = node->next;
- if (!node)
+ data = plist_get_data(node);
+ if (node->children) {
+ node_t ch;
+ for (ch = node_first_child(node); ch; ch = node_next_sibling(ch)) {
+ plist_err_t err = _node_estimate_size(ch, size, depth + 1, visited);
+ if (err != PLIST_ERR_SUCCESS) {
+ return err;
+ }
+ }
+ switch (data->type) {
+ case PLIST_DICT:
+ *size += (XPLIST_DICT_LEN << 1) + 7;
+ break;
+ case PLIST_ARRAY:
+ *size += (XPLIST_ARRAY_LEN << 1) + 7;
+ break;
+ default:
+ break;
+ }
+ *size += (depth << 1);
+ } else {
+ uint32_t indent = (depth > 8) ? 8 : depth;
+ switch (data->type) {
+ case PLIST_DATA: {
+ uint32_t req_lines = (data->length / MAX_DATA_BYTES_PER_LINE(indent)) + 1;
+ uint32_t b64len = data->length + (data->length / 3);
+ b64len += b64len % 4;
+ *size += b64len;
+ *size += (XPLIST_DATA_LEN << 1) + 5 + (indent+1) * (req_lines+1) + 1;
+ } break;
+ case PLIST_STRING:
+ *size += data->length;
+ *size += (XPLIST_STRING_LEN << 1) + 6;
+ break;
+ case PLIST_KEY:
+ *size += data->length;
+ *size += (XPLIST_KEY_LEN << 1) + 6;
+ break;
+ case PLIST_INT:
+ if (data->length == 16) {
+ *size += num_digits_u(data->intval);
+ } else {
+ *size += num_digits_i((int64_t)data->intval);
+ }
+ *size += (XPLIST_INT_LEN << 1) + 6;
+ break;
+ case PLIST_REAL:
+ *size += dtostr(NULL, 0, data->realval);
+ *size += (XPLIST_REAL_LEN << 1) + 6;
+ break;
+ case PLIST_DATE:
+ *size += 20; /* YYYY-MM-DDThh:mm:ssZ */
+ *size += (XPLIST_DATE_LEN << 1) + 6;
+ break;
+ case PLIST_BOOLEAN:
+ *size += ((data->boolval) ? XPLIST_TRUE_LEN : XPLIST_FALSE_LEN) + 4;
+ break;
+ case PLIST_DICT:
+ *size += XPLIST_DICT_LEN + 4; /* <dict/> */
break;
+ case PLIST_ARRAY:
+ *size += XPLIST_ARRAY_LEN + 4; /* <array/> */
+ break;
+ case PLIST_UID:
+ *size += num_digits_i((int64_t)data->intval);
+ *size += (XPLIST_DICT_LEN << 1) + 7;
+ *size += indent + ((indent+1) << 1);
+ *size += 18; /* <key>CF$UID</key> */
+ *size += (XPLIST_INT_LEN << 1) + 6;
+ break;
+ case PLIST_NULL:
+ PLIST_XML_WRITE_ERR("PLIST_NULL type is not valid for XML format\n");
+ return PLIST_ERR_FORMAT;
+ default:
+ PLIST_XML_WRITE_ERR("invalid node type encountered\n");
+ return PLIST_ERR_UNKNOWN;
+ }
+ *size += indent;
+ }
+ return PLIST_ERR_SUCCESS;
+}
- data = plist_new_plist_data();
- subnode = plist_new_node(data);
- if (*plist_node)
- node_attach(*plist_node, subnode);
- else
- *plist_node = subnode;
+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;
+}
- if (!xmlStrcmp(node->name, XPLIST_TRUE))
- {
- data->boolval = TRUE;
- data->type = PLIST_BOOLEAN;
- data->length = 1;
- continue;
- }
+plist_err_t plist_to_xml(plist_t plist, char **plist_xml, uint32_t * length)
+{
+ uint64_t size = 0;
+ plist_err_t res;
- if (!xmlStrcmp(node->name, XPLIST_FALSE))
- {
- data->boolval = FALSE;
- data->type = PLIST_BOOLEAN;
- data->length = 1;
- continue;
- }
+ if (!plist || !plist_xml || !length) {
+ return PLIST_ERR_INVALID_ARG;
+ }
- if (!xmlStrcmp(node->name, XPLIST_INT))
- {
- xmlChar *strval = xmlNodeGetContent(node);
- data->intval = strtoull((char*)strval, NULL, 0);
- data->type = PLIST_UINT;
- data->length = 8;
- xmlFree(strval);
- continue;
+ res = node_estimate_size((node_t)plist, &size, 0);
+ if (res < 0) {
+ return res;
+ }
+ size += sizeof(XML_PLIST_PROLOG) + sizeof(XML_PLIST_EPILOG) - 1;
+
+ strbuf_t *outbuf = str_buf_new(size);
+ if (!outbuf) {
+ PLIST_XML_WRITE_ERR("Could not allocate output buffer\n");
+ return PLIST_ERR_NO_MEM;
+ }
+
+ str_buf_append(outbuf, XML_PLIST_PROLOG, sizeof(XML_PLIST_PROLOG)-1);
+
+ res = node_to_xml((node_t)plist, &outbuf, 0);
+ if (res < 0) {
+ str_buf_free(outbuf);
+ *plist_xml = NULL;
+ *length = 0;
+ return res;
+ }
+
+ str_buf_append(outbuf, XML_PLIST_EPILOG, sizeof(XML_PLIST_EPILOG));
+
+ *plist_xml = (char*)outbuf->data;
+ *length = outbuf->len - 1;
+
+ outbuf->data = NULL;
+ str_buf_free(outbuf);
+
+ return PLIST_ERR_SUCCESS;
+}
+
+struct _parse_ctx {
+ const char *pos;
+ const char *end;
+ plist_err_t err;
+};
+typedef struct _parse_ctx* parse_ctx;
+
+static inline int is_xml_ws(unsigned char c)
+{
+ return (c == ' ' || c == '\t' || c == '\r' || c == '\n');
+}
+
+static void parse_skip_ws(parse_ctx ctx)
+{
+ while (ctx->pos < ctx->end && is_xml_ws(*(ctx->pos))) {
+ ctx->pos++;
+ }
+}
+
+static void find_char(parse_ctx ctx, char c, int skip_quotes)
+{
+ while (ctx->pos < ctx->end && (*(ctx->pos) != c)) {
+ if (skip_quotes && (c != '"') && (*(ctx->pos) == '"')) {
+ ctx->pos++;
+ find_char(ctx, '"', 0);
+ if (ctx->pos >= ctx->end) {
+ PLIST_XML_ERR("EOF while looking for matching double quote\n");
+ return;
+ }
+ if (*(ctx->pos) != '"') {
+ PLIST_XML_ERR("Unmatched double quote\n");
+ return;
+ }
}
+ ctx->pos++;
+ }
+}
- if (!xmlStrcmp(node->name, XPLIST_REAL))
- {
- xmlChar *strval = xmlNodeGetContent(node);
- data->realval = atof((char *) strval);
- data->type = PLIST_REAL;
- data->length = 8;
- xmlFree(strval);
- continue;
+static void find_str(parse_ctx ctx, const char *str, size_t len, int skip_quotes)
+{
+ while (ctx->pos < (ctx->end - len)) {
+ if (!strncmp(ctx->pos, str, len)) {
+ break;
}
+ if (skip_quotes && (*(ctx->pos) == '"')) {
+ ctx->pos++;
+ find_char(ctx, '"', 0);
+ if (ctx->pos >= ctx->end) {
+ PLIST_XML_ERR("EOF while looking for matching double quote\n");
+ return;
+ }
+ if (*(ctx->pos) != '"') {
+ PLIST_XML_ERR("Unmatched double quote\n");
+ return;
+ }
+ }
+ ctx->pos++;
+ }
+}
- if (!xmlStrcmp(node->name, XPLIST_DATE))
- {
- xmlChar *strval = xmlNodeGetContent(node);
- time_t time = 0;
- if (strlen((const char*)strval) >= 11) {
- struct tm btime;
- parse_date((const char*)strval, &btime);
- time = mktime(&btime);
- }
- data->timeval.tv_sec = (long)time;
- data->timeval.tv_usec = 0;
- data->type = PLIST_DATE;
- data->length = sizeof(struct timeval);
- xmlFree(strval);
- continue;
+static void find_next(parse_ctx ctx, const char *nextchars, int numchars, int skip_quotes)
+{
+ int i = 0;
+ while (ctx->pos < ctx->end) {
+ if (skip_quotes && (*(ctx->pos) == '"')) {
+ ctx->pos++;
+ find_char(ctx, '"', 0);
+ if (ctx->pos >= ctx->end) {
+ PLIST_XML_ERR("EOF while looking for matching double quote\n");
+ return;
+ }
+ if (*(ctx->pos) != '"') {
+ PLIST_XML_ERR("Unmatched double quote\n");
+ return;
+ }
+ }
+ for (i = 0; i < numchars; i++) {
+ if (*(ctx->pos) == nextchars[i]) {
+ return;
+ }
}
+ ctx->pos++;
+ }
+}
- if (!xmlStrcmp(node->name, XPLIST_STRING))
- {
- xmlChar *strval = xmlNodeGetContent(node);
- len = strlen((char *) strval);
- type = xmlDetectCharEncoding(strval, len);
+typedef struct {
+ const char *begin;
+ size_t length;
+ int is_cdata;
+ void *next;
+} text_part_t;
- if (XML_CHAR_ENCODING_UTF8 == type || XML_CHAR_ENCODING_ASCII == type || XML_CHAR_ENCODING_NONE == type)
- {
- data->strval = strdup((char *) strval);
- data->type = PLIST_STRING;
- data->length = strlen(data->strval);
+static text_part_t* text_part_init(text_part_t* part, const char *begin, size_t length, int is_cdata)
+{
+ part->begin = begin;
+ part->length = length;
+ part->is_cdata = is_cdata;
+ part->next = NULL;
+ return part;
+}
+
+static void text_parts_free(text_part_t *tp)
+{
+ while (tp) {
+ text_part_t *tmp = tp;
+ tp = (text_part_t*)tp->next;
+ free(tmp);
+ }
+}
+
+static text_part_t* text_part_append(text_part_t* parts, const char *begin, size_t length, int is_cdata)
+{
+ text_part_t* newpart = (text_part_t*)malloc(sizeof(text_part_t));
+ assert(newpart);
+ parts->next = text_part_init(newpart, begin, length, is_cdata);
+ return newpart;
+}
+
+static text_part_t* get_text_parts(parse_ctx ctx, const char* tag, size_t tag_len, int skip_ws, text_part_t *parts)
+{
+ const char *p = NULL;
+ const char *q = NULL;
+ text_part_t *last = NULL;
+
+ if (skip_ws) {
+ parse_skip_ws(ctx);
+ }
+ do {
+ p = ctx->pos;
+ find_char(ctx, '<', 0);
+ if (ctx->pos >= ctx->end || *ctx->pos != '<') {
+ PLIST_XML_ERR("EOF while looking for closing tag\n");
+ ctx->err = PLIST_ERR_PARSE;
+ return NULL;
+ }
+ q = ctx->pos;
+ ctx->pos++;
+ if (ctx->pos >= ctx->end) {
+ PLIST_XML_ERR("EOF while parsing '%s'\n", p);
+ ctx->err = PLIST_ERR_PARSE;
+ return NULL;
+ }
+ if (*ctx->pos == '!') {
+ ctx->pos++;
+ if (ctx->pos >= ctx->end-1) {
+ PLIST_XML_ERR("EOF while parsing <! special tag\n");
+ ctx->err = PLIST_ERR_PARSE;
+ return NULL;
}
- xmlFree(strval);
- continue;
+ if (*ctx->pos == '-' && *(ctx->pos+1) == '-') {
+ if (last) {
+ last = text_part_append(last, p, q-p, 0);
+ } else if (parts) {
+ last = text_part_init(parts, p, q-p, 0);
+ }
+ ctx->pos += 2;
+ find_str(ctx, "-->", 3, 0);
+ if (ctx->pos > ctx->end-3 || strncmp(ctx->pos, "-->", 3) != 0) {
+ PLIST_XML_ERR("EOF while looking for end of comment\n");
+ ctx->err = PLIST_ERR_PARSE;
+ return NULL;
+ }
+ ctx->pos += 3;
+ } else if (*ctx->pos == '[') {
+ ctx->pos++;
+ if (ctx->pos >= ctx->end - 8) {
+ PLIST_XML_ERR("EOF while parsing <[ tag\n");
+ ctx->err = PLIST_ERR_PARSE;
+ return NULL;
+ }
+ if (strncmp(ctx->pos, "CDATA[", 6) == 0) {
+ if (q-p > 0) {
+ if (last) {
+ last = text_part_append(last, p, q-p, 0);
+ } else if (parts) {
+ last = text_part_init(parts, p, q-p, 0);
+ }
+ }
+ ctx->pos+=6;
+ p = ctx->pos;
+ find_str(ctx, "]]>", 3, 0);
+ if (ctx->pos > ctx->end-3 || strncmp(ctx->pos, "]]>", 3) != 0) {
+ PLIST_XML_ERR("EOF while looking for end of CDATA block\n");
+ ctx->err = PLIST_ERR_PARSE;
+ return NULL;
+ }
+ q = ctx->pos;
+ if (last) {
+ last = text_part_append(last, p, q-p, 1);
+ } else if (parts) {
+ last = text_part_init(parts, p, q-p, 1);
+ }
+ ctx->pos += 3;
+ } else {
+ p = ctx->pos;
+ find_next(ctx, " \r\n\t>", 5, 1);
+ PLIST_XML_ERR("Invalid special tag <[%.*s> encountered inside <%s> tag\n", (int)(ctx->pos - p), p, tag);
+ ctx->err = PLIST_ERR_PARSE;
+ return NULL;
+ }
+ } else {
+ p = ctx->pos;
+ find_next(ctx, " \r\n\t>", 5, 1);
+ PLIST_XML_ERR("Invalid special tag <!%.*s> encountered inside <%s> tag\n", (int)(ctx->pos - p), p, tag);
+ ctx->err = PLIST_ERR_PARSE;
+ return NULL;
+ }
+ } else if (*ctx->pos == '/') {
+ break;
+ } else {
+ p = ctx->pos;
+ find_next(ctx, " \r\n\t>", 5, 1);
+ PLIST_XML_ERR("Invalid tag <%.*s> encountered inside <%s> tag\n", (int)(ctx->pos - p), p, tag);
+ ctx->err = PLIST_ERR_PARSE;
+ return NULL;
}
+ } while (1);
+ ctx->pos++;
+ if (ctx->pos >= ctx->end-tag_len || strncmp(ctx->pos, tag, tag_len) != 0) {
+ PLIST_XML_ERR("EOF or end tag mismatch\n");
+ ctx->err = PLIST_ERR_PARSE;
+ return NULL;
+ }
+ ctx->pos+=tag_len;
+ parse_skip_ws(ctx);
+ if (ctx->pos >= ctx->end) {
+ PLIST_XML_ERR("EOF while parsing closing tag\n");
+ ctx->err = PLIST_ERR_PARSE;
+ return NULL;
+ } else if (*ctx->pos != '>') {
+ PLIST_XML_ERR("Invalid closing tag; expected '>', found '%c'\n", *ctx->pos);
+ ctx->err = PLIST_ERR_PARSE;
+ return NULL;
+ }
+ ctx->pos++;
+ if (q > p) {
+ if (last) {
+ last = text_part_append(last, p, q-p, 0);
+ } else if (parts) {
+ last = text_part_init(parts, p, q-p, 0);
+ }
+ }
+ return parts;
+}
- if (!xmlStrcmp(node->name, XPLIST_KEY))
- {
- xmlChar *strval = xmlNodeGetContent(node);
- data->strval = strdup((char *) strval);
- data->type = PLIST_KEY;
- data->length = strlen(data->strval);
- xmlFree(strval);
- continue;
+static int unescape_entities(char *str, size_t *length)
+{
+ size_t i = 0;
+ size_t len = *length;
+ while (len > 0 && i < len-1) {
+ if (str[i] == '&') {
+ char *entp = str + i + 1;
+ while (i < len && str[i] != ';') {
+ i++;
+ }
+ if (i >= len) {
+ PLIST_XML_ERR("Invalid entity sequence encountered (missing terminating ';')\n");
+ return -1;
+ }
+ size_t entlen = (size_t)(str+i - entp);
+ if (entlen > 0) {
+ if (entlen > 256) {
+ PLIST_XML_ERR("Rejecting absurdly large entity at &%.*s...\n", 8, entp);
+ return -1;
+ }
+ size_t bytelen = 1;
+ if (entlen == 3 && memcmp(entp, "amp", 3) == 0) {
+ /* the '&' is already there */
+ } else if (entlen == 4 && memcmp(entp, "apos", 4) == 0) {
+ *(entp-1) = '\'';
+ } else if (entlen == 4 && memcmp(entp, "quot", 4) == 0) {
+ *(entp-1) = '"';
+ } else if (entlen == 2 && memcmp(entp, "lt", 2) == 0) {
+ *(entp-1) = '<';
+ } else if (entlen == 2 && memcmp(entp, "gt", 2) == 0) {
+ *(entp-1) = '>';
+ } else if (*entp == '#') {
+ /* numerical character reference */
+ uint64_t val = 0;
+ char* ep = NULL;
+ if (entlen > 8) {
+ PLIST_XML_ERR("Invalid numerical character reference encountered, sequence too long: &%.*s;\n", (int)entlen, entp);
+ return -1;
+ }
+ if (entlen >= 2 && (entp[1] == 'x' || entp[1] == 'X')) {
+ if (entlen < 3) {
+ PLIST_XML_ERR("Invalid numerical character reference encountered, sequence too short: &%.*s;\n", (int)entlen, entp);
+ return -1;
+ }
+ val = strtoull(entp+2, &ep, 16);
+ } else {
+ if (entlen < 2) {
+ PLIST_XML_ERR("Invalid numerical character reference encountered, sequence too short: &%.*s;\n", (int)entlen, entp);
+ return -1;
+ }
+ val = strtoull(entp+1, &ep, 10);
+ }
+ if (val == 0 || val > 0x10FFFF || (size_t)(ep-entp) != entlen) {
+ PLIST_XML_ERR("Invalid numerical character reference found: &%.*s;\n", (int)entlen, entp);
+ return -1;
+ }
+ if (val >= 0xD800 && val <= 0xDFFF) {
+ PLIST_XML_ERR("Invalid numerical character reference found (surrogate): &%.*s;\n", (int)entlen, entp);
+ return -1;
+ }
+ /* convert to UTF8 */
+ if (val >= 0x10000) {
+ /* four bytes */
+ *(entp-1) = (char)(0xF0 + ((val >> 18) & 0x7));
+ *(entp+0) = (char)(0x80 + ((val >> 12) & 0x3F));
+ *(entp+1) = (char)(0x80 + ((val >> 6) & 0x3F));
+ *(entp+2) = (char)(0x80 + (val & 0x3F));
+ entp+=3;
+ bytelen = 4;
+ } else if (val >= 0x800) {
+ /* three bytes */
+ *(entp-1) = (char)(0xE0 + ((val >> 12) & 0xF));
+ *(entp+0) = (char)(0x80 + ((val >> 6) & 0x3F));
+ *(entp+1) = (char)(0x80 + (val & 0x3F));
+ entp+=2;
+ bytelen = 3;
+ } else if (val >= 0x80) {
+ /* two bytes */
+ *(entp-1) = (char)(0xC0 + ((val >> 6) & 0x1F));
+ *(entp+0) = (char)(0x80 + (val & 0x3F));
+ entp++;
+ bytelen = 2;
+ } else {
+ /* one byte */
+ *(entp-1) = (char)(val & 0x7F);
+ }
+ } else {
+ PLIST_XML_ERR("Invalid entity encountered: &%.*s;\n", (int)entlen, entp);
+ return -1;
+ }
+ memmove(entp, str+i+1, len - i); /* include '\0' */
+ size_t dec = entlen + 1 - bytelen;
+ size_t shrink = entlen + 2 - bytelen;
+ if (i < dec || len < shrink) {
+ PLIST_XML_ERR("Internal error: length underflow?!\n");
+ return -1;
+ }
+ i -= dec;
+ len -= shrink;
+ continue;
+ } else {
+ PLIST_XML_ERR("Invalid empty entity sequence &;\n");
+ return -1;
+ }
}
+ i++;
+ }
+ *length = len;
+ return 0;
+}
- if (!xmlStrcmp(node->name, XPLIST_DATA))
- {
- xmlChar *strval = xmlNodeGetContent(node);
- size_t size = 0;
- unsigned char *dec = base64decode((char*)strval, &size);
- data->buff = (uint8_t *) malloc(size * sizeof(uint8_t));
- memcpy(data->buff, dec, size * sizeof(uint8_t));
- free(dec);
- data->length = size;
- data->type = PLIST_DATA;
- xmlFree(strval);
- continue;
+static char* text_parts_get_content(text_part_t *tp, int unesc_entities, int trim_ws, size_t *length, int *requires_free)
+{
+ char *str = NULL;
+ size_t total_length = 0;
+
+ if (!tp) {
+ return NULL;
+ }
+ char *p;
+ if (requires_free && !tp->next && !unesc_entities && !trim_ws) {
+ *requires_free = 0;
+ if (length) {
+ *length = tp->length;
}
+ return (char*)tp->begin;
+ }
+ text_part_t *tmp = tp;
+ while (tp && tp->begin) {
+ total_length += tp->length;
+ tp = (text_part_t*)tp->next;
+ }
+ str = (char*)malloc(total_length + 1);
+ assert(str);
+ p = str;
+ tp = tmp;
+ while (tp && tp->begin) {
+ size_t len = tp->length;
+ memcpy(p, tp->begin, len);
+ p[len] = '\0';
+ if (!tp->is_cdata && unesc_entities) {
+ if (unescape_entities(p, &len) < 0) {
+ free(str);
+ return NULL;
+ }
+ }
+ p += len;
+ tp = (text_part_t*)tp->next;
+ }
+ *p = '\0';
+ if (trim_ws) {
+ char* start = str;
+ char* end = p;
+ while (start < end && is_xml_ws((unsigned char)start[0])) start++;
+ while (end > start && is_xml_ws((unsigned char)end[-1])) end--;
+ if (start != str) {
+ size_t newlen = (size_t)(end - start);
+ memmove(str, start, newlen);
+ str[newlen] = '\0';
+ p = str + newlen;
+ } else {
+ *end = '\0';
+ p = end;
+ }
+ }
+ if (length) {
+ *length = p - str;
+ }
+ if (requires_free) {
+ *requires_free = 1;
+ }
+ return str;
+}
- if (!xmlStrcmp(node->name, XPLIST_ARRAY))
- {
- data->type = PLIST_ARRAY;
- xml_to_node(node, &subnode);
- continue;
+static plist_err_t node_from_xml(parse_ctx ctx, plist_t *plist)
+{
+ char tag[16] = { 0 };
+ char *keyname = NULL;
+ plist_t subnode = NULL;
+ const char *p = NULL;
+ plist_t parent = NULL;
+
+ struct node_path_item {
+ const char *type;
+ struct node_path_item *prev;
+ };
+ struct node_path_item* node_path = NULL;
+ int depth = 0;
+
+ while (ctx->pos < ctx->end && !ctx->err) {
+ parse_skip_ws(ctx);
+ if (ctx->pos >= ctx->end) {
+ break;
+ }
+ if (*ctx->pos != '<') {
+ p = ctx->pos;
+ find_next(ctx, " \t\r\n", 4, 0);
+ PLIST_XML_ERR("Expected: opening tag, found: %.*s\n", (int)(ctx->pos - p), p);
+ ctx->err = PLIST_ERR_PARSE;
+ goto err_out;
+ }
+ ctx->pos++;
+ if (ctx->pos >= ctx->end) {
+ PLIST_XML_ERR("EOF while parsing tag\n");
+ ctx->err = PLIST_ERR_PARSE;
+ goto err_out;
}
- if (!xmlStrcmp(node->name, XPLIST_DICT))
- {
- data->type = PLIST_DICT;
- xml_to_node(node, &subnode);
+ if (*(ctx->pos) == '?') {
+ find_str(ctx, "?>", 2, 1);
+ if (ctx->pos > ctx->end-2) {
+ PLIST_XML_ERR("EOF while looking for <? tag closing marker\n");
+ ctx->err = PLIST_ERR_PARSE;
+ goto err_out;
+ }
+ if (strncmp(ctx->pos, "?>", 2) != 0) {
+ PLIST_XML_ERR("Couldn't find <? tag closing marker\n");
+ ctx->err = PLIST_ERR_PARSE;
+ goto err_out;
+ }
+ ctx->pos += 2;
+ continue;
+ } else if (*(ctx->pos) == '!') {
+ /* comment or DTD */
+ if (((ctx->end - ctx->pos) > 3) && !strncmp(ctx->pos, "!--", 3)) {
+ ctx->pos += 3;
+ find_str(ctx, "-->", 3, 0);
+ if (ctx->pos > ctx->end-3 || strncmp(ctx->pos, "-->", 3) != 0) {
+ PLIST_XML_ERR("Couldn't find end of comment\n");
+ ctx->err = PLIST_ERR_PARSE;
+ goto err_out;
+ }
+ ctx->pos+=3;
+ } else if (((ctx->end - ctx->pos) > 8) && !strncmp(ctx->pos, "!DOCTYPE", 8)) {
+ int embedded_dtd = 0;
+ ctx->pos+=8;
+ while (ctx->pos < ctx->end) {
+ find_next(ctx, " \t\r\n[>", 6, 1);
+ if (ctx->pos >= ctx->end) {
+ PLIST_XML_ERR("EOF while parsing !DOCTYPE\n");
+ ctx->err = PLIST_ERR_PARSE;
+ goto err_out;
+ }
+ if (*ctx->pos == '[') {
+ embedded_dtd = 1;
+ break;
+ } else if (*ctx->pos == '>') {
+ /* end of DOCTYPE found already */
+ ctx->pos++;
+ break;
+ } else {
+ parse_skip_ws(ctx);
+ }
+ }
+ if (embedded_dtd) {
+ find_str(ctx, "]>", 2, 1);
+ if (ctx->pos > ctx->end-2 || strncmp(ctx->pos, "]>", 2) != 0) {
+ PLIST_XML_ERR("Couldn't find end of DOCTYPE\n");
+ ctx->err = PLIST_ERR_PARSE;
+ goto err_out;
+ }
+ ctx->pos += 2;
+ }
+ } else {
+ p = ctx->pos;
+ find_next(ctx, " \r\n\t>", 5, 1);
+ PLIST_XML_ERR("Invalid or incomplete special tag <%.*s> encountered\n", (int)(ctx->pos - p), p);
+ ctx->err = PLIST_ERR_PARSE;
+ goto err_out;
+ }
continue;
+ } else {
+ int is_empty = 0;
+ int closing_tag = 0;
+ p = ctx->pos;
+ find_next(ctx, " \r\n\t<>", 6, 0);
+ if (ctx->pos >= ctx->end) {
+ PLIST_XML_ERR("Unexpected EOF while parsing XML\n");
+ ctx->err = PLIST_ERR_PARSE;
+ goto err_out;
+ }
+ size_t taglen = ctx->pos - p;
+ if (taglen >= sizeof(tag)) {
+ PLIST_XML_ERR("Unexpected tag <%.*s> encountered\n", (int)taglen, p);
+ ctx->pos = ctx->end;
+ ctx->err = PLIST_ERR_PARSE;
+ goto err_out;
+ }
+ memcpy(tag, p, taglen);
+ tag[taglen] = '\0';
+ if (*ctx->pos != '>') {
+ find_next(ctx, "<>", 2, 1);
+ }
+ if (ctx->pos >= ctx->end) {
+ PLIST_XML_ERR("Unexpected EOF while parsing XML\n");
+ ctx->err = PLIST_ERR_PARSE;
+ goto err_out;
+ }
+ if (*ctx->pos != '>') {
+ PLIST_XML_ERR("Missing '>' for tag <%s\n", tag);
+ ctx->err = PLIST_ERR_PARSE;
+ goto err_out;
+ }
+ if (*(ctx->pos-1) == '/') {
+ size_t idx = ctx->pos - p - 1;
+ if (idx < taglen)
+ tag[idx] = '\0';
+ is_empty = 1;
+ }
+ ctx->pos++;
+ if (!strcmp(tag, "plist")) {
+ if (!node_path && *plist) {
+ PLIST_XML_ERR("Multiple top-level <plist> elements encountered\n");
+ ctx->err = PLIST_ERR_PARSE;
+ goto err_out;
+ }
+ if (is_empty) {
+ PLIST_XML_ERR("Empty plist tag\n");
+ ctx->err = PLIST_ERR_PARSE;
+ goto err_out;
+ }
+
+ struct node_path_item *path_item = (struct node_path_item*)malloc(sizeof(struct node_path_item));
+ if (!path_item) {
+ PLIST_XML_ERR("out of memory when allocating node path item\n");
+ ctx->err = PLIST_ERR_PARSE;
+ goto err_out;
+ }
+ path_item->type = "plist";
+ path_item->prev = node_path;
+ node_path = path_item;
+
+ continue;
+ } else if (!strcmp(tag, "/plist")) {
+ if (!*plist) {
+ PLIST_XML_ERR("encountered empty plist tag\n");
+ ctx->err = PLIST_ERR_PARSE;
+ goto err_out;
+ }
+ if (!node_path) {
+ PLIST_XML_ERR("node path is empty while trying to match closing tag with opening tag\n");
+ ctx->err = PLIST_ERR_PARSE;
+ goto err_out;
+ }
+ if (strcmp(node_path->type, tag+1) != 0) {
+ PLIST_XML_ERR("mismatching closing tag <%s> found for opening tag <%s>\n", tag, node_path->type);
+ ctx->err = PLIST_ERR_PARSE;
+ goto err_out;
+ }
+ struct node_path_item *path_item = node_path;
+ node_path = (struct node_path_item*)node_path->prev;
+ free(path_item);
+ continue;
+ }
+ if (tag[0] == '/') {
+ closing_tag = 1;
+ goto handle_closing;
+ }
+ plist_data_t data = plist_new_plist_data();
+ if (!data) {
+ PLIST_XML_ERR("failed to allocate plist data\n");
+ ctx->err = PLIST_ERR_NO_MEM;
+ goto err_out;
+ }
+ subnode = plist_new_node(data);
+ if (!subnode) {
+ PLIST_XML_ERR("failed to create node\n");
+ ctx->err = PLIST_ERR_NO_MEM;
+ goto err_out;
+ }
+
+ if (!strcmp(tag, XPLIST_DICT)) {
+ data->type = PLIST_DICT;
+ } else if (!strcmp(tag, XPLIST_ARRAY)) {
+ data->type = PLIST_ARRAY;
+ } else if (!strcmp(tag, XPLIST_INT)) {
+ if (!is_empty) {
+ text_part_t first_part = { NULL, 0, 0, NULL };
+ text_part_t *tp = get_text_parts(ctx, tag, taglen, 1, &first_part);
+ if (!tp) {
+ PLIST_XML_ERR("Could not parse text content for '%s' node\n", tag);
+ text_parts_free((text_part_t*)first_part.next);
+ ctx->err = PLIST_ERR_PARSE;
+ goto err_out;
+ }
+ if (tp->begin) {
+ char *str_content = text_parts_get_content(tp, 0, 1, NULL, NULL);
+ if (!str_content) {
+ PLIST_XML_ERR("Could not get text content for '%s' node\n", tag);
+ text_parts_free((text_part_t*)first_part.next);
+ ctx->err = PLIST_ERR_PARSE;
+ goto err_out;
+ }
+ char *str = str_content;
+ int is_negative = 0;
+ if ((str[0] == '-') || (str[0] == '+')) {
+ if (str[0] == '-') {
+ is_negative = 1;
+ }
+ str++;
+ }
+ errno = 0;
+ char* endp = NULL;
+ data->intval = strtoull(str, &endp, 0);
+ if (errno == ERANGE) {
+ PLIST_XML_ERR("Integer overflow detected while parsing '%.20s'\n", str_content);
+ text_parts_free((text_part_t*)first_part.next);
+ ctx->err = PLIST_ERR_PARSE;
+ free(str_content);
+ goto err_out;
+ }
+ if (endp == str || *endp != '\0') {
+ PLIST_XML_ERR("Invalid characters while parsing integer value '%.20s'\n", str_content);
+ text_parts_free((text_part_t*)first_part.next);
+ ctx->err = PLIST_ERR_PARSE;
+ free(str_content);
+ goto err_out;
+ }
+ if (is_negative && data->intval > ((uint64_t)INT64_MAX + 1)) {
+ PLIST_XML_ERR("Signed integer value out of range while parsing '%.20s'\n", str_content);
+ text_parts_free((text_part_t*)first_part.next);
+ ctx->err = PLIST_ERR_PARSE;
+ free(str_content);
+ goto err_out;
+ }
+ if (is_negative || (data->intval <= INT64_MAX)) {
+ uint64_t v = data->intval;
+ if (is_negative) {
+ v = -v;
+ }
+ data->intval = v;
+ data->length = 8;
+ } else {
+ data->length = 16;
+ }
+ free(str_content);
+ } else {
+ is_empty = 1;
+ }
+ text_parts_free((text_part_t*)first_part.next);
+ }
+ if (is_empty) {
+ PLIST_XML_ERR("Encountered empty " XPLIST_INT " tag\n");
+ ctx->err = PLIST_ERR_PARSE;
+ goto err_out;
+ }
+ data->type = PLIST_INT;
+ } else if (!strcmp(tag, XPLIST_REAL)) {
+ if (!is_empty) {
+ text_part_t first_part = { NULL, 0, 0, NULL };
+ text_part_t *tp = get_text_parts(ctx, tag, taglen, 1, &first_part);
+ if (!tp) {
+ PLIST_XML_ERR("Could not parse text content for '%s' node\n", tag);
+ text_parts_free((text_part_t*)first_part.next);
+ ctx->err = PLIST_ERR_PARSE;
+ goto err_out;
+ }
+ if (tp->begin) {
+ char *str_content = text_parts_get_content(tp, 0, 1, NULL, NULL);
+ if (!str_content) {
+ PLIST_XML_ERR("Could not get text content for '%s' node\n", tag);
+ text_parts_free((text_part_t*)first_part.next);
+ ctx->err = PLIST_ERR_PARSE;
+ goto err_out;
+ }
+ errno = 0;
+ char *endp = NULL;
+ data->realval = strtod(str_content, &endp);
+ if (errno == ERANGE) {
+ PLIST_XML_ERR("Invalid range while parsing value for '%s' node\n", tag);
+ text_parts_free((text_part_t*)first_part.next);
+ ctx->err = PLIST_ERR_PARSE;
+ free(str_content);
+ goto err_out;
+ }
+ if (endp == str_content || *endp != '\0') {
+ PLIST_XML_ERR("Could not parse value for '%s' node\n", tag);
+ text_parts_free((text_part_t*)first_part.next);
+ ctx->err = PLIST_ERR_PARSE;
+ free(str_content);
+ goto err_out;
+
+ }
+ if (!isfinite(data->realval)) {
+ PLIST_XML_ERR("Invalid real value while parsing '%.20s'\n", str_content);
+ text_parts_free((text_part_t*)first_part.next);
+ ctx->err = PLIST_ERR_PARSE;
+ free(str_content);
+ goto err_out;
+ }
+ free(str_content);
+ } else {
+ is_empty = 1;
+ }
+ text_parts_free((text_part_t*)first_part.next);
+ }
+ if (is_empty) {
+ PLIST_XML_ERR("Encountered empty " XPLIST_REAL " tag\n");
+ ctx->err = PLIST_ERR_PARSE;
+ goto err_out;
+ }
+ data->type = PLIST_REAL;
+ data->length = 8;
+ } else if (!strcmp(tag, XPLIST_TRUE)) {
+ if (!is_empty) {
+ get_text_parts(ctx, tag, taglen, 1, NULL);
+ }
+ data->type = PLIST_BOOLEAN;
+ data->boolval = 1;
+ data->length = 1;
+ } else if (!strcmp(tag, XPLIST_FALSE)) {
+ if (!is_empty) {
+ get_text_parts(ctx, tag, taglen, 1, NULL);
+ }
+ data->type = PLIST_BOOLEAN;
+ data->boolval = 0;
+ data->length = 1;
+ } else if (!strcmp(tag, XPLIST_STRING) || !strcmp(tag, XPLIST_KEY)) {
+ if (!is_empty) {
+ text_part_t first_part = { NULL, 0, 0, NULL };
+ text_part_t *tp = get_text_parts(ctx, tag, taglen, 0, &first_part);
+ char *str = NULL;
+ size_t length = 0;
+ if (!tp) {
+ PLIST_XML_ERR("Could not parse text content for '%s' node\n", tag);
+ text_parts_free((text_part_t*)first_part.next);
+ ctx->err = PLIST_ERR_PARSE;
+ goto err_out;
+ }
+ str = text_parts_get_content(tp, 1, 0, &length, NULL);
+ text_parts_free((text_part_t*)first_part.next);
+ if (!str) {
+ PLIST_XML_ERR("Could not get text content for '%s' node\n", tag);
+ ctx->err = PLIST_ERR_PARSE;
+ goto err_out;
+ }
+ if (!strcmp(tag, "key") && !keyname && parent && (plist_get_node_type(parent) == PLIST_DICT)) {
+ keyname = str;
+ plist_free(subnode);
+ subnode = NULL;
+ continue;
+ } else {
+ data->strval = str;
+ data->length = length;
+ }
+ } else {
+ data->strval = strdup("");
+ data->length = 0;
+ }
+ data->type = PLIST_STRING;
+ } else if (!strcmp(tag, XPLIST_DATA)) {
+ if (!is_empty) {
+ text_part_t first_part = { NULL, 0, 0, NULL };
+ text_part_t *tp = get_text_parts(ctx, tag, taglen, 1, &first_part);
+ if (!tp) {
+ PLIST_XML_ERR("Could not parse text content for '%s' node\n", tag);
+ text_parts_free((text_part_t*)first_part.next);
+ ctx->err = PLIST_ERR_PARSE;
+ goto err_out;
+ }
+ if (tp->begin) {
+ int requires_free = 0;
+ char *str_content = text_parts_get_content(tp, 0, 0, NULL, &requires_free);
+ if (!str_content) {
+ PLIST_XML_ERR("Could not get text content for '%s' node\n", tag);
+ text_parts_free((text_part_t*)first_part.next);
+ ctx->err = PLIST_ERR_PARSE;
+ goto err_out;
+ }
+ size_t size = tp->length;
+ if (size > 0) {
+ data->buff = base64decode(str_content, &size);
+ if (!data->buff) {
+ text_parts_free((text_part_t*)first_part.next);
+ PLIST_XML_ERR("failed to decode base64 stream\n");
+ ctx->err = PLIST_ERR_NO_MEM;
+ goto err_out;
+ }
+ data->length = size;
+ }
+
+ if (requires_free) {
+ free(str_content);
+ }
+ }
+ text_parts_free((text_part_t*)first_part.next);
+ }
+ data->type = PLIST_DATA;
+ } else if (!strcmp(tag, XPLIST_DATE)) {
+ if (!is_empty) {
+ text_part_t first_part = { NULL, 0, 0, NULL };
+ text_part_t *tp = get_text_parts(ctx, tag, taglen, 1, &first_part);
+ if (!tp) {
+ PLIST_XML_ERR("Could not parse text content for '%s' node\n", tag);
+ text_parts_free((text_part_t*)first_part.next);
+ ctx->err = PLIST_ERR_PARSE;
+ goto err_out;
+ }
+ Time64_T timev = 0;
+ if (tp->begin) {
+ char *str_content = text_parts_get_content(tp, 0, 1, NULL, NULL);
+ if (!str_content) {
+ PLIST_XML_ERR("Could not get text content for '%s' node\n", tag);
+ text_parts_free((text_part_t*)first_part.next);
+ ctx->err = PLIST_ERR_PARSE;
+ goto err_out;
+ }
+ struct TM btime;
+ if (parse_date(str_content, &btime) < 0) {
+ PLIST_XML_ERR("Failed to parse date node\n");
+ text_parts_free((text_part_t*)first_part.next);
+ ctx->err = PLIST_ERR_PARSE;
+ free(str_content);
+ goto err_out;
+ }
+ timev = timegm64(&btime);
+ free(str_content);
+ } else {
+ is_empty = 1;
+ }
+ text_parts_free((text_part_t*)first_part.next);
+ data->realval = (double)(timev - MAC_EPOCH);
+ }
+ if (is_empty) {
+ PLIST_XML_ERR("Encountered empty " XPLIST_DATE " tag\n");
+ ctx->err = PLIST_ERR_PARSE;
+ goto err_out;
+ }
+ data->length = sizeof(double);
+ data->type = PLIST_DATE;
+ } else {
+ PLIST_XML_ERR("Unexpected tag <%s%s> encountered\n", tag, (is_empty) ? "/" : "");
+ ctx->pos = ctx->end;
+ ctx->err = PLIST_ERR_PARSE;
+ goto err_out;
+ }
+ if (subnode && !closing_tag) {
+ if (!*plist) {
+ /* first value node inside <plist> */
+ *plist = subnode;
+
+ if (data->type == PLIST_DICT || data->type == PLIST_ARRAY) {
+ parent = subnode;
+ } else {
+ /* scalar root: keep parsing until </plist> */
+ parent = NULL;
+ }
+ } else if (parent) {
+ switch (plist_get_node_type(parent)) {
+ case PLIST_DICT:
+ if (!keyname) {
+ PLIST_XML_ERR("missing key name while adding dict item\n");
+ ctx->err = PLIST_ERR_PARSE;
+ goto err_out;
+ }
+ plist_dict_set_item(parent, keyname, subnode);
+ break;
+ case PLIST_ARRAY:
+ plist_array_append_item(parent, subnode);
+ break;
+ default:
+ /* should not happen */
+ PLIST_XML_ERR("parent is not a structured node\n");
+ ctx->err = PLIST_ERR_PARSE;
+ goto err_out;
+ }
+ } else {
+ /* We already produced root, and we're not inside a container */
+ PLIST_XML_ERR("Unexpected tag <%s> found while </plist> is expected\n", tag);
+ ctx->err = PLIST_ERR_PARSE;
+ goto err_out;
+ }
+ if (!is_empty && (data->type == PLIST_DICT || data->type == PLIST_ARRAY)) {
+ if (depth >= PLIST_MAX_NESTING_DEPTH) {
+ PLIST_XML_ERR("maximum nesting depth (%u) exceeded\n", (unsigned)PLIST_MAX_NESTING_DEPTH);
+ ctx->err = PLIST_ERR_MAX_NESTING;
+ goto err_out;
+ }
+ struct node_path_item *path_item = (struct node_path_item*)malloc(sizeof(struct node_path_item));
+ if (!path_item) {
+ PLIST_XML_ERR("out of memory when allocating node path item\n");
+ ctx->err = PLIST_ERR_PARSE;
+ goto err_out;
+ }
+ path_item->type = (data->type == PLIST_DICT) ? XPLIST_DICT : XPLIST_ARRAY;
+ path_item->prev = node_path;
+ node_path = path_item;
+
+ depth++;
+ parent = subnode;
+ } else {
+ /* If we inserted a child scalar into a container, nothing to push. */
+ }
+ subnode = NULL;
+ }
+handle_closing:
+ if (closing_tag) {
+ if (!node_path) {
+ PLIST_XML_ERR("node path is empty while trying to match closing tag with opening tag\n");
+ ctx->err = PLIST_ERR_PARSE;
+ goto err_out;
+ }
+ if (strcmp(node_path->type, tag+1) != 0) {
+ PLIST_XML_ERR("unexpected %s found (for opening %s)\n", tag, node_path->type);
+ ctx->err = PLIST_ERR_PARSE;
+ goto err_out;
+ }
+
+ /* When closing a dictionary, convert a single-entry
+ { "CF$UID" : <integer> } dictionary into a PLIST_UID node.
+ Perform the conversion before moving to the parent node. */
+ if (!strcmp(node_path->type, XPLIST_DICT) && parent && plist_get_node_type(parent) == PLIST_DICT) {
+ if (plist_dict_get_size(parent) == 1) {
+ plist_t uid = plist_dict_get_item(parent, "CF$UID");
+ if (uid) {
+ uint64_t val = 0;
+ if (plist_get_node_type(uid) != PLIST_INT) {
+ ctx->err = PLIST_ERR_PARSE;
+ PLIST_XML_ERR("Invalid node type for CF$UID dict entry (must be PLIST_INT)\n");
+ goto err_out;
+ }
+ plist_get_uint_val(uid, &val);
+ plist_set_uid_val(parent, val);
+ }
+ }
+ }
+
+ if (depth > 0) depth--;
+ struct node_path_item *path_item = node_path;
+ node_path = (struct node_path_item*)node_path->prev;
+ free(path_item);
+ parent = (parent) ? ((node_t)parent)->parent : NULL;
+ }
+ free(keyname);
+ keyname = NULL;
+ plist_free(subnode);
+ subnode = NULL;
}
}
-}
-void plist_to_xml(plist_t plist, char **plist_xml, uint32_t * length)
-{
- xmlDocPtr plist_doc = NULL;
- xmlNodePtr root_node = NULL;
- struct xml_node root = { NULL, 0 };
- int size = 0;
-
- if (!plist || !plist_xml || *plist_xml)
- return;
- plist_doc = new_xml_plist();
- root_node = xmlDocGetRootElement(plist_doc);
- root.xml = root_node;
-
- char *current_locale = setlocale(LC_NUMERIC, NULL);
- char *saved_locale = NULL;
- if (current_locale) {
- saved_locale = strdup(current_locale);
- }
- if (saved_locale) {
- setlocale(LC_NUMERIC, "POSIX");
- }
- node_to_xml(plist, &root);
-
- xmlChar* tmp = NULL;
- xmlDocDumpMemory(plist_doc, &tmp, &size);
- if (size >= 0 && tmp)
- {
- /* make sure to copy the terminating 0-byte */
- *plist_xml = (char*)malloc((size+1) * sizeof(char));
- memcpy(*plist_xml, tmp, size+1);
- *length = size;
- xmlFree(tmp);
- tmp = NULL;
+ if (node_path) {
+ PLIST_XML_ERR("EOF encountered while </%s> was expected\n", node_path->type);
+ ctx->err = PLIST_ERR_PARSE;
}
- xmlFreeDoc(plist_doc);
- if (saved_locale) {
- setlocale(LC_NUMERIC, saved_locale);
- free(saved_locale);
+err_out:
+ free(keyname);
+ plist_free(subnode);
+
+ /* clean up node_path if required */
+ while (node_path) {
+ struct node_path_item *path_item = node_path;
+ node_path = (struct node_path_item*)path_item->prev;
+ free(path_item);
}
+
+ if (ctx->err != PLIST_ERR_SUCCESS) {
+ plist_free(*plist);
+ *plist = NULL;
+ return ctx->err;
+ }
+
+ /* check if we have a UID "dict" so we can replace it with a proper UID node */
+ if (PLIST_IS_DICT(*plist) && plist_dict_get_size(*plist) == 1) {
+ plist_t value = plist_dict_get_item(*plist, "CF$UID");
+ if (PLIST_IS_INT(value)) {
+ uint64_t u64val = 0;
+ plist_get_uint_val(value, &u64val);
+ plist_free(*plist);
+ *plist = plist_new_uid(u64val);
+ }
+ }
+
+ return PLIST_ERR_SUCCESS;
}
-void plist_from_xml(const char *plist_xml, uint32_t length, plist_t * plist)
+plist_err_t plist_from_xml(const char *plist_xml, uint32_t length, plist_t * plist)
{
- xmlDocPtr plist_doc = xmlParseMemory(plist_xml, length);
- xmlNodePtr root_node = xmlDocGetRootElement(plist_doc);
+ if (!plist) {
+ return PLIST_ERR_INVALID_ARG;
+ }
+ *plist = NULL;
+ if (!plist_xml || (length == 0)) {
+ return PLIST_ERR_INVALID_ARG;
+ }
+
+ struct _parse_ctx ctx = { plist_xml, plist_xml + length, PLIST_ERR_SUCCESS };
- xml_to_node(root_node, plist);
- xmlFreeDoc(plist_doc);
+ return node_from_xml(&ctx, plist);
}