diff options
Diffstat (limited to 'src')
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 */ - /* < = < > = > ' = ' " = " & = & */ - 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); - } + /* < = < > = > & = & */ + 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, "<", 4); + start = cur+1; + break; + case '>': + str_buf_append(*outbuf, node_data->strval + start, cur - start); + str_buf_append(*outbuf, ">", 4); + start = cur+1; + break; + case '&': + str_buf_append(*outbuf, node_data->strval + start, cur - start); + str_buf_append(*outbuf, "&", 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); } |
