diff options
300 files changed, 33140 insertions, 4638 deletions
diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..176a458 --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +* text=auto diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 0000000..e995b30 --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1,3 @@ +github: nikias +patreon: nikias +custom: ["https://www.paypal.me/NikiasBassen"] diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..54273b4 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,166 @@ +name: build + +on: + push: + pull_request: + schedule: + - cron: '0 0 1 * *' + +jobs: + build-linux-ubuntu: + runs-on: ubuntu-latest + steps: + - name: install dependencies + run: | + sudo apt-get update + pip install cython + - name: prepare environment + run: | + echo "target_triplet=`gcc -dumpmachine`" >> $GITHUB_ENV + - uses: actions/checkout@v6 + with: + fetch-depth: 0 + - name: autogen + run: | + export PYTHON=python3 + export LDFLAGS="-Wl,-rpath=/usr/local/lib" + ./autogen.sh --enable-debug + - name: make + run: make + - name: make check + run: make check + - name: make install + run: sudo make install + - name: prepare artifact + run: | + mkdir -p dest + DESTDIR=`pwd`/dest make install + tar -C dest -cf libplist.tar usr + - name: publish artifact + uses: actions/upload-artifact@v6 + with: + name: libplist-latest_${{env.target_triplet}} + path: libplist.tar + build-macOS: + runs-on: macOS-latest + steps: + - name: install dependencies + run: | + if test -x "`which port`"; then + sudo port install libtool autoconf automake + else + brew install libtool autoconf automake + fi + pip3 install --break-system-packages cython + shell: bash + - uses: actions/checkout@v6 + with: + fetch-depth: 0 + - name: autogen + run: | + SDKDIR=`xcrun --sdk macosx --show-sdk-path` + TESTARCHS="arm64 x86_64" + USEARCHS= + for ARCH in $TESTARCHS; do + if echo "int main(int argc, char **argv) { return 0; }" |clang -arch $ARCH -o /dev/null -isysroot $SDKDIR -x c - 2>/dev/null; then + USEARCHS="$USEARCHS -arch $ARCH" + fi + done + export CFLAGS="$USEARCHS -isysroot $SDKDIR" + echo "Using CFLAGS: $CFLAGS" + PYTHON3_BIN=`xcrun -f python3` + if test -x $PYTHON3_BIN; then + export PYTHON=$PYTHON3_BIN + PYTHON_VER=`$PYTHON3_BIN -c "import distutils.sysconfig; print(distutils.sysconfig.get_config_var('VERSION'))"` + PYTHON_EXEC_PREFIX=`$PYTHON3_BIN -c "import distutils.sysconfig; print(distutils.sysconfig.get_config_var('exec_prefix'))"` + PYTHON_LIBS_PATH=$PYTHON_EXEC_PREFIX/lib + PYTHON_FRAMEWORK_PATH=$PYTHON_EXEC_PREFIX/Python3 + export PYTHON_CPPFLAGS="-I$PYTHON_EXEC_PREFIX/Headers" + export PYTHON_LIBS="-L$PYTHON_LIBS_PATH -lpython$PYTHON_VER" + export PYTHON_EXTRA_LDFLAGS="-Wl,-stack_size,1000000 -framework CoreFoundation $PYTHON_FRAMEWORK_PATH" + fi + ./autogen.sh --enable-debug + - name: make + run: make + - name: make check + run: make check + - name: make install + run: sudo make install + - name: prepare artifact + run: | + mkdir -p dest + DESTDIR=`pwd`/dest make install + tar -C dest -cf libplist.tar usr + - name: publish artifact + uses: actions/upload-artifact@v6 + with: + name: libplist-latest_macOS + path: libplist.tar + build-windows: + runs-on: windows-latest + defaults: + run: + shell: msys2 {0} + strategy: + fail-fast: false + matrix: + include: [ + { msystem: MINGW64, arch: x86_64 }, + { msystem: MINGW32, arch: i686 } + ] + steps: + - uses: msys2/setup-msys2@v2 + with: + msystem: ${{ matrix.msystem }} + release: false + update: false + install: >- + base-devel + git + mingw-w64-${{ matrix.arch }}-gcc + mingw-w64-${{ matrix.arch }}-pkg-config + mingw-w64-${{ matrix.arch }}-python + make + libtool + autoconf + automake-wrapper + cython + - name: prepare environment + run: | + dest=`echo ${{ matrix.msystem }} |tr [:upper:] [:lower:]` + echo "dest=$dest" >> $GITHUB_ENV + echo "target_triplet=`gcc -dumpmachine`" >> $GITHUB_ENV + git config --global core.autocrlf false + - uses: actions/checkout@v6 + with: + fetch-depth: 0 + - name: autogen + run: ./autogen.sh CC=gcc CXX=g++ --enable-debug + - name: make + run: make + - name: make check + run: make check + - name: print test logs + run: | + for I in test/*.trs; do + RES=`grep ":test-result" $I |cut -d ":" -f 3` + if test $RES != PASS; then + TESTNAME=`basename $I .trs` + echo $TESTNAME: + cat test/$TESTNAME.log + echo + fi + done + shell: bash + - name: make install + run: make install + - name: prepare artifact + run: | + mkdir -p dest + DESTDIR=`pwd`/dest make install + tar -C dest -cf libplist.tar ${{ env.dest }} + - name: publish artifact + uses: actions/upload-artifact@v6 + with: + name: libplist-latest_${{ matrix.arch }}-${{ env.dest }} + path: libplist.tar diff --git a/.github/workflows/cifuzz.yml b/.github/workflows/cifuzz.yml new file mode 100644 index 0000000..c5d6ec0 --- /dev/null +++ b/.github/workflows/cifuzz.yml @@ -0,0 +1,26 @@ +name: CIFuzz +on: [pull_request] +jobs: + Fuzzing: + runs-on: ubuntu-latest + steps: + - name: Build Fuzzers + id: build + uses: google/oss-fuzz/infra/cifuzz/actions/build_fuzzers@master + with: + oss-fuzz-project-name: 'libplist' + dry-run: false + language: c++ + - name: Run Fuzzers + uses: google/oss-fuzz/infra/cifuzz/actions/run_fuzzers@master + with: + oss-fuzz-project-name: 'libplist' + fuzz-seconds: 300 + dry-run: false + language: c++ + - name: Upload Crash + uses: actions/upload-artifact@v4 + if: failure() && steps.build.outcome == 'success' + with: + name: artifacts + path: ./out/artifacts diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml new file mode 100644 index 0000000..8f2384a --- /dev/null +++ b/.github/workflows/codeql-analysis.yml @@ -0,0 +1,53 @@ +# For most projects, this workflow file will not need changing; you simply need +# to commit it to your repository. +# +# You may wish to alter this file to override the set of languages analyzed, +# or to provide custom queries or build logic. +name: "CodeQL" + +on: + push: + branches: [master] + pull_request: + # The branches below must be a subset of the branches above + branches: [master] + +jobs: + analyze: + name: Analyze + runs-on: ubuntu-latest + + strategy: + fail-fast: false + matrix: + # Override automatic language detection by changing the below list + # Supported options are ['csharp', 'cpp', 'go', 'java', 'javascript', 'python'] + language: ['cpp'] + # Learn more... + # https://docs.github.com/en/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#overriding-automatic-language-detection + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + # We must fetch at least the immediate parents so that if this is + # a pull request then we can checkout the head. + fetch-depth: 0 + + # Initializes the CodeQL tools for scanning. + - name: Initialize CodeQL + uses: github/codeql-action/init@v3 + with: + languages: ${{ matrix.language }} + # If you wish to specify custom queries, you can do so here or in a config file. + # By default, queries listed here will override any specified in a config file. + # Prefix the list here with "+" to use these queries and those in the config file. + # queries: ./path/to/local/query, your-org/your-repo/queries@main + + - name: Build + run: | + ./autogen.sh --enable-debug --without-cython + make + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v3 @@ -1,2 +1,74 @@ *.swp -build/* +*.o +*.lo +*.la +*.pc +*.log +*.exe +*~ +.kateconfig +configure +Makefile +Makefile.in +aclocal.m4 +autom4te.cache +compile +config.* +depcomp +doxygen.cfg +install-sh +libtool +ltmain.sh +missing +stamp-h1 +m4/libtool.m4 +m4/ltoptions.m4 +m4/ltsugar.m4 +m4/ltversion.m4 +m4/lt~obsolete.m4 +src/Makefile +src/Makefile.in +src/.libs +src/.deps +fuzz/.deps +fuzz/.libs +include/Makefile +include/Makefile.in +libcnary/Makefile +libcnary/Makefile.in +libcnary/.libs +libcnary/.deps +tools/Makefile +tools/Makefile.in +tools/.libs +tools/.deps +tools/plistutil +test/Makefile +test/Makefile.in +test/.libs +test/.deps +test/plist_cmp +test/plist_test +test/plist_test++ +test/plist_btest +test/plist_jtest +test/plist_otest +test/integer_set_test +test/xml_behavior_test +test/data/*.out +test/*.trs +cython/Makefile +cython/Makefile.in +cython/.deps +cython/.libs +cython/plist.c +test-driver + +# Generated test output files +test/data/*.test.bin +test/data/*.test.signed.bin +test/data/*.test.unsigned.bin +test/data/*.test.unsigned.xml +test/data/*.test.tz*.bin +test/data/*.test.tz*.xml +test/data/*.test.xml @@ -1,13 +1,28 @@ +Aaron Burghardt Alexander Sack +Andrew Udvare +Bryan Forbes +Chow Loong Jin Christophe Fergeau +Dogbert +Elan Ruusamäe +Filippo Bigarella +Frederik Carlier +Glenn Washburn +Greg Dennis Ingmar Vanhassel +Jim Koning Jonathan Beck Julien Blache Martin Aumueller Martin Szulecki Matt Colyer +Matthias Klose +Nicolás Alvarez Nikias Bassen Patrick von Reth Patrick Walton Paul Sladen +Shane G +Wang Junjie Zach C diff --git a/CMakeLists.txt b/CMakeLists.txt deleted file mode 100644 index 0bad840..0000000 --- a/CMakeLists.txt +++ /dev/null @@ -1,81 +0,0 @@ -PROJECT( libplist ) - -SET( LIBPLIST_VERSION_MAJOR "1" ) -SET( LIBPLIST_VERSION_MINOR "8" ) -SET( LIBPLIST_SOVERSION "1" ) -SET( LIBPLIST_VERSION "${LIBPLIST_VERSION_MAJOR}.${LIBPLIST_VERSION_MINOR}" ) -SET( LIBPLIST_LIBVERSION "${LIBPLIST_SOVERSION}.${LIBPLIST_VERSION}" ) -SET( PLUTIL_VERSION ${LIBPLIST_VERSION} ) - -SET( CMAKE_MODULE_PATH ${CMAKE_SOURCE_DIR}/cmake ${CMAKE_SOURCE_DIR}/cmake/modules ) - -cmake_minimum_required(VERSION 2.6) - -IF(NOT DEFINED CMAKE_INSTALL_LIBDIR) - SET(CMAKE_INSTALL_LIBDIR lib CACHE PATH "Output directory for libraries") -ENDIF(NOT DEFINED CMAKE_INSTALL_LIBDIR) - -FIND_PACKAGE( LibXml2 REQUIRED ) - -OPTION(ENABLE_SWIG "Enable SWIG Python bindings (needs Swig)" ON) -OPTION(ENABLE_CYTHON "Enable Cython Python bindings (needs Cython)" ON) - -IF(ENABLE_SWIG) - FIND_PACKAGE( SWIG ) -ENDIF(ENABLE_SWIG) - -IF(ENABLE_CYTHON) - FIND_PACKAGE( Cython ) -ENDIF(ENABLE_CYTHON) - -IF(ENABLE_SWIG OR ENABLE_CYTHON) - FIND_PACKAGE( PythonInterp ) - FIND_PACKAGE( PythonLibs ) -ENDIF(ENABLE_SWIG OR ENABLE_CYTHON) - -INCLUDE_DIRECTORIES( ${CMAKE_CURRENT_SOURCE_DIR}/include - ${CMAKE_INCLUDE_PATH}) - -ENABLE_TESTING() - -IF (NOT EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/libcnary/CMakeLists.txt") - MESSAGE(FATAL_ERROR " -You need to checkout the 'libcnary' submodule to build libplist. -To do so, perform the following steps in the base directory: - git submodule init - git submodule update -Then, run cmake again.") -ENDIF() - -INCLUDE (TestBigEndian) -TEST_BIG_ENDIAN(endianess) -SET ( PLIST_BYTE_ORDER ${endianess} ) - -ADD_SUBDIRECTORY( libcnary ) -ADD_SUBDIRECTORY( src ) -ADD_SUBDIRECTORY( plutil ) -ADD_SUBDIRECTORY( include ) -ADD_SUBDIRECTORY( test ) - -IF ( SWIG_FOUND AND PYTHONLIBS_FOUND AND PYTHONINTERP_FOUND ) - ADD_SUBDIRECTORY( swig ) -ENDIF ( SWIG_FOUND AND PYTHONLIBS_FOUND AND PYTHONINTERP_FOUND ) -IF ( CYTHON_FOUND AND PYTHONLIBS_FOUND AND PYTHONINTERP_FOUND ) - ADD_SUBDIRECTORY( cython ) -ENDIF ( CYTHON_FOUND AND PYTHONLIBS_FOUND AND PYTHONINTERP_FOUND ) - -# add uninstall target -CONFIGURE_FILE( "${CMAKE_SOURCE_DIR}/cmake/modules/cmake_uninstall.cmake.in" "${CMAKE_CURRENT_BINARY_DIR}/cmake_uninstall.cmake" IMMEDIATE @ONLY) -ADD_CUSTOM_TARGET(uninstall "${CMAKE_COMMAND}" -P "${CMAKE_CURRENT_BINARY_DIR}/cmake_uninstall.cmake") - -########## PkgConfig ############################ -CONFIGURE_FILE( "libplist.pc.in" "${CMAKE_CURRENT_BINARY_DIR}/libplist.pc" ) -CONFIGURE_FILE( "libplist++.pc.in" "${CMAKE_CURRENT_BINARY_DIR}/libplist++.pc" ) - -########## INSTALL ############################## -INSTALL( FILES "${CMAKE_CURRENT_BINARY_DIR}/libplist.pc" DESTINATION ${CMAKE_INSTALL_LIBDIR}/pkgconfig/ ) -INSTALL( FILES "${CMAKE_CURRENT_BINARY_DIR}/libplist++.pc" DESTINATION ${CMAKE_INSTALL_LIBDIR}/pkgconfig/ ) - -########## CPack ################################ -INCLUDE( libplistPackaging ) -LIBPLIST_PACKAGE(LIBPLIST_VERSION_MAJOR LIBPLIST_VERSION_MINOR) diff --git a/Makefile.am b/Makefile.am new file mode 100644 index 0000000..e6923a4 --- /dev/null +++ b/Makefile.am @@ -0,0 +1,32 @@ +AUTOMAKE_OPTIONS = foreign +ACLOCAL_AMFLAGS = -I m4 +SUBDIRS = libcnary src include tools docs + +if HAVE_CYTHON +SUBDIRS += cython +endif + +if BUILD_TESTS +SUBDIRS += test +endif + +if BUILD_FUZZERS +SUBDIRS += fuzz +endif + +EXTRA_DIST = \ + README.md \ + git-version-gen + +dist-hook: + @if ! git diff --quiet; then echo "Uncommitted changes present; not releasing"; exit 1; fi + echo $(VERSION) > $(distdir)/.tarball-version + +docs/html: $(top_builddir)/doxygen.cfg $(top_srcdir)/include/plist/*.h + rm -rf docs/html + doxygen doxygen.cfg + +docs/README.doxygen.md: README.md + awk '/\#\# Table of Contents/{while(getline && $$0 != ""){}{print "[TOC]"}}1' README.md > $@ + +docs: doxygen.cfg docs/README.doxygen.md docs/html @@ -1,3 +1,216 @@ +Version 2.7.0 +~~~~~~~~~~~~~ + +- Changes: + * Add plist_new_unix_date, plist_get_unix_date_val, plist_set_unix_date_val functions + that work with int64_t values representing a UNIX timestamp instead of + using the 'MAC epoch'. + These new functions should be used instead of plist_new_date, + plist_get_date_val, and plist_set_date_val, which are now marked deprecated + and might be removed in a future version of libplist. + * Allow building the library without tool(s) + * Switch to more generic global initializer method + * json: Allow e+/E+ in exponent as per RFC 8259 + * C++: Add more convenience functions to the interface + * C++: Add more type variants to different constructors and operators +- Bugfixes: + * Fix segmentation fault when calling plist_sort() on an empty dictionary + * Fix compilation on MSVC + * C++: Fix bug in internal helper function of Array class + * C++: Fix String::GetValue memory leaking and support assignment of const char* + +Version 2.6.0 +~~~~~~~~~~~~~ + +- Changes: + * Revert back API change around PLIST_DATA to use char* again + +Version 2.5.0 +~~~~~~~~~~~~~ + +- Changes: + * Change API around PLIST_DATA to use uint8_t* instead of char* + * Add PLIST_DICT helper functions for different operations + * Require Cython 3.0 for python bindings + +Version 2.4.0 +~~~~~~~~~~~~~ + +- Changes: + * Add a PLIST_OPT_NONE value to plist_write_options_t + * autoconf: Allow disabling build of test suite + * Update doxygen config and document undocumented macros + * Add an explicit PLIST_FORMAT_NONE value + * Add a libplist_version() function to the interface + * docs: Use README.md to generate mainpage with doxygen +- Bugfixes: + * Several compiler-related fixes and code improvements + * Plug memory leak in plist_write_to_stream() + * Prevent adding NULL items to array/dictionary nodes + * Fix parallel running of test suite + * Fix cython bindings + * Fix OOB read in plist_from_memory() + +Version 2.3.0 +~~~~~~~~~~~~~ + +- Changes: + * Rename PLIST_UINT to PLIST_INT and add plist_new_int() and plist_get_int_val() + * Add support for JSON format + * Add support for OpenStep format + * Introduce error codes and format constants + * Add return value to import/export functions to allow returning error codes + * Add new plist_sort function + * Add several human-readable output-only formats + * Add new plist_write_to_string/_stream/_file functions + * Add new plist_print function + * Add new plist_read_from_file function + * Add new plist_mem_free() function + * Add a few C++ methods + * Add C++ interface test + * Add PLIST_NULL type + * Some code housekeeping (mostly clang-tidy) +- Breaking: + * plist_from_memory() gets additional parameter +- Bugfixes: + * Fix multiple bugs in all of the parsers + * Fix handling of PLIST_UID nodes + +Version 2.2.0 +~~~~~~~~~~~~~ + +- Changes: + * bplist: Improve recursion check performance by at least 30% for large files + * test: Fix test suite on Windows + * cython: Fix handling of Date nodes (MACH_EPOCH) + * Add new plist_*_val_compare(), plist_*_val_contains() helper functions + * Fix/suppress several compiler warnings + * plistutil: Added ability for files to be read from stdin + * plistutil: Added ability to specify output format + * Fix: Return NULL from plist_copy() if passed a NULL pointer instead of asserting + * Add GitHub Actions integration for automatic build tests + * plistutil: Add manual page and usage output + * Fix removal of docs directory on `make clean` + * Improve README.md with project description, installation, contributing and + usage sections + * Rename library and all related files by adding an API version resulting + in "libplist-2.0" and "libplist++-2.0" + +Version 2.1.0 +~~~~~~~~~~~~~ + +- Changes: + * Add new plist_get_data_ptr() and plist_get_string_ptr() for direct access to underlying buffer + * Increase precision when converting PLIST_REAL nodes to XML + * Fix several issues in libcnary (UaF, segfault, memory leak) + * Fix copying of hash table entries when cloning array/dict nodes + * cython: Implement load()/loads() and dump()/dumps() to match up with plistlib (Python 3.4) + * Add new plist_dict_item_get_key() for retrieving key node of a dict item + * Add new plist_array_item_remove() to allow removing an array's child node without relying on the index + * Make plist_array_get_item_index() return UINT_MAX instead of 0 when node can't be found + * Add index lookup table for large PLIST_ARRAY nodes + * Add iterator for array nodes: plist_array_new_iter(), plist_array_next_item() + * Improve performance of plist_dict_next_item() drastically + * Improve performance and memory usage when writing XML plists + * Improve performance and memory usage when writing binary plists + * Allow building with sanitizers (without fuzzers) + * Prevent store to misaligned address when writing real/date nodes + * Work around misaligned reads in binary plist parser + * Integrate fuzzing targets (libFuzzer) into project + * Add sanitizers (ASAN, UBSAN) when building fuzzers (--with-fuzzers) + +Version 2.0.0 +~~~~~~~~~~~~~ + +- Changes: + * New light-weight custom XML parser + * Remove libxml2 dependency + * Refactor binary plist parsing + * Improved malformed XML and binary plist detection and error handling + * Add parser debug/error output (when compiled with --enable-debug), controlled via environment variables + * Fix unicode character handling + * Add PLIST_IS_* helper macros for the different node types + * Extend date/time range and date conversion issues + * Add plist_is_binary() and plist_from_memory() functions to the interface + * Plug several memory leaks + * Speed improvements for handling large plist files + +- Includes security fixes for: + * CVE-2017-6440 + * CVE-2017-6439 + * CVE-2017-6438 + * CVE-2017-6437 + * CVE-2017-6436 + * CVE-2017-6435 + * CVE-2017-5836 + * CVE-2017-5835 + * CVE-2017-5834 + * CVE-2017-5545 + * CVE-2017-5209 + ... and several others that didn't receive any CVE (yet). + +Version 1.12 +~~~~~~~~~~~~ + +- Changes: + * Fix plist_from_bin() changing value nodes to key nodes in dictionaries + * Avoid exporting non-public symbols + * Prevent crash in plist_from_bin() when parsing unusual binary plists + * Fix crash in String|Key::GetValue() and actually make C++ interface work + * Fix memory leaks in new_xml_plist() and parse_real_node() + * Fix header guards to conform to C++ standard + * Update Cython based Python bindings and remove plist_new_key() + * Fix key nodes not being output correctly if they contained XML entities + * Fix handling and storage of signed vs. unsigned integer values + * Fix date handling to respect the "Mac Epoch" instead of "Unix Epoch" + * Remove plist_set_type() as it should not be used + * Fix deprecated macros to work with older LLVM/Clang + * Fix various shadowed declarations + * Add documentation to explicitly describe memory buffer ownership + * Fix memory leak in plist_from_bin() + * Add various test cases based on fixes + * Fix wrong timezone related date/time conversion of date nodes + * Fix endian detection on MIPS architecture + * Fix parallel build for autotools + +Version 1.11 +~~~~~~~~~~~~ + +- Changes: + * Deprecated plist_dict_insert_item() in favor of plist_dict_set_item() + * Updated cython bindings for Python 3.x + * Removed swig python bindings + * Changed build system to autotools + * Added new plist_dict_merge() function + * WIN32 (MinGW) + OSX compilation fixes + * Made base64 decoding thread safe + +Version 1.10 +~~~~~~~~~~~~ + +- Changes: + * Renamed plutil to plistutil to not mask Apple's plutil + * Fixed cython bindings (broken in 1.9) + * Added support for PLIST_UID node types to C++, cython, and swig bindings + +- Important Note: + * Support for swig python bindings will be dropped with future releases. + The bindings will be kept in the source tree for now, but we suggest + to update your python code to use the cython bindings instead. + +Version 1.9 +~~~~~~~~~~~ + +- Changes: + * Add support for handling UID node types + * Fix crash when converting plists containing comments + * Fix Bug in plist_data_compare() + * Fix DST handling for PLIST_DATE + * Fix plist_dict_set_item() and plist_array_set_item() + * Fix cython String plist handling + * Fix invalid memory access in copy_plist_data() + * Fix several compiler warnings + Version 1.8 ~~~~~~~~~~~ @@ -1,47 +0,0 @@ -INSTALLATION -================================================================================ - -For: - Apple Binary and XML Property Lists - -You must have: - libxml2-dev - cmake - make - gcc - -Optional: - swig - python - -To compile run: - mkdir build - cd build - cmake .. - make - sudo make install # (if you want to install it into your system directories) - -On Ubuntu/Debian, you can do: - sudo apt-get install build-essential cmake libxml2-dev - - -== Who/what/where? == - -Home: - http://www.libimobiledevice.org/ - -Code: - git clone http://cgit.sukimashita.com/libplist.git/ - - libplist uses libcnary, see: - http://github.com/Chronic-Dev/libcnary - -Tickets: - http://libiphone.lighthouseapp.com/ - Please tag libplist related bugs with 'libplist' - -Mailing List: - http://lists.libimobiledevice.org/mailman/listinfo/libimobiledevice-devel - -updated: - 2011-06-25 diff --git a/README.md b/README.md new file mode 100644 index 0000000..ad36634 --- /dev/null +++ b/README.md @@ -0,0 +1,283 @@ +# libplist + +*A small portable C library to handle Apple Property List files in binary, XML, +JSON, or OpenStep format.* + + + + +## Table of Contents +- [Features](#features) +- [Building](#building) + - [Prerequisites](#prerequisites) + - [Linux (Debian/Ubuntu based)](#linux-debianubuntu-based) + - [macOS](#macos) + - [Windows](#windows) + - [Configuring the source tree](#configuring-the-source-tree) + - [Building and installation](#building-and-installation) +- [Usage](#usage) +- [Contributing](#contributing) +- [Links](#links) +- [License](#license) +- [Credits](#credits) + +## Features + +The project provides an interface to read and write plist files in binary, +XML, JSON, or OpenStep format alongside a command-line utility named `plistutil`. + +Some key features are: + +- **Formats:** Supports binary, XML, JSON, and OpenStep format +- **Utility:** Provides a `plistutil` utility for the command-line +- **Python:** Provides Cython based bindings for Python +- **Tested:** Uses fuzzing ([OSS-Fuzz](https://github.com/google/oss-fuzz)) and data compliance tests +- **Efficient:** Lean library with performance and resources in mind + +## Building + +### Prerequisites + +You need to have a working compiler (gcc/clang) and development environent +available. This project uses autotools for the build process, allowing to +have common build steps across different platforms. +Only the prerequisites differ and they are described in this section. + +#### Linux (Debian/Ubuntu based) + +* Install all required dependencies and build tools: + ```shell + sudo apt-get install \ + build-essential \ + checkinstall \ + git \ + autoconf \ + automake \ + libtool-bin + ``` + + If you want to optionally build the documentation or Python bindings use: + ```shell + sudo apt-get install \ + doxygen \ + cython3 + ``` + +#### macOS + +* Make sure the Xcode command line tools are installed. Then, use either [MacPorts](https://www.macports.org/) + or [Homebrew](https://brew.sh/) to install `automake`, `autoconf`, and `libtool`. + + Using MacPorts: + ```shell + sudo port install libtool autoconf automake + ``` + + Using Homebrew: + ```shell + brew install libtool autoconf automake + ``` + + In case you want to build the documentation, install `doxygen` using the corresponding install command from above. + + If you want to build Python bindings, you need to install cython: + ```shell + pip3 install cython + ``` + + You might need to set a few environment variables if building of the Python bindings fail. For example, the [automated build via GitHub actions](https://github.com/libimobiledevice/libplist/blob/master/.github/workflows/build.yml) + is setting the following environment variables: + ```shell + PYTHON3_BIN=`xcrun -f python3` + export PYTHON=$PYTHON3_BIN + PYTHON_VER=`$PYTHON3_BIN -c "import distutils.sysconfig; print(distutils.sysconfig.get_config_var('VERSION'))"` + PYTHON_EXEC_PREFIX=`$PYTHON3_BIN -c "import distutils.sysconfig; print(distutils.sysconfig.get_config_var('exec_prefix'))"` + PYTHON_LIBS_PATH=$PYTHON_EXEC_PREFIX/lib + PYTHON_FRAMEWORK_PATH=$PYTHON_EXEC_PREFIX/Python3 + export PYTHON_LIBS="-L$PYTHON_LIBS_PATH -lpython$PYTHON_VER" + export PYTHON_EXTRA_LDFLAGS="-Wl,-stack_size,1000000 -framework CoreFoundation $PYTHON_FRAMEWORK_PATH" + ``` + +#### Windows + +* Using [MSYS2](https://www.msys2.org/) is the official way of compiling this project on Windows. Download the MSYS2 installer + and follow the installation steps. + + It is recommended to use the _MSYS2 MinGW 64-bit_ shell. Run it and make sure the required dependencies are installed: + + ```shell + pacman -S base-devel \ + git \ + mingw-w64-x86_64-gcc \ + make \ + libtool \ + autoconf \ + automake-wrapper + ``` + NOTE: You can use a different shell and different compiler according to your needs. Adapt the above command accordingly. + + If you want to optionally build Python bindings, you need to also install `cython` + and make sure you have a working python environment. + + ```shell + pacman -S cython + ``` + +### Configuring the source tree + +You can build the source code from a git checkout, or from a `.tar.bz2` release tarball from [Releases](https://github.com/libimobiledevice/libplist/releases). +Before we can build it, the source tree has to be configured for building. The steps depend on where you got the source from. + +* **From git** + + If you haven't done already, clone the actual project repository and change into the directory. + ```shell + git clone https://github.com/libimobiledevice/libplist.git + cd libplist + ``` + + Configure the source tree for building: + ```shell + ./autogen.sh + ``` + +* **From release tarball (.tar.bz2)** + + When using an official [release tarball](https://github.com/libimobiledevice/libplist/releases) (`libplist-x.y.z.tar.bz2`) + the procedure is slightly different. + + Extract the tarball: + ```shell + tar xjf libplist-x.y.z.tar.bz2 + cd libplist-x.y.z + ``` + + Configure the source tree for building: + ```shell + ./configure + ``` + +Both `./configure` and `./autogen.sh` (which generates and calls `configure`) accept a few options, for example `--enable-debug` to allow +printing debug messages in the final product, or `--without-cython` to skip building the Python bindings. +You can simply pass them like this: + +```shell +./autogen.sh --prefix=/usr/local --enable-debug --without-cython +``` +or +```shell +./configure --prefix=/usr/local --enable-debug +``` + +Once the command is successful, the last few lines of output will look like this: +``` +[...] +config.status: creating config.h +config.status: executing depfiles commands +config.status: executing libtool commands + +Configuration for libplist 2.3.1: +------------------------------------------- + + Install prefix ..........: /usr/local + Debug code ..............: yes + Python bindings .........: yes + + Now type 'make' to build libplist 2.3.1, + and then 'make install' for installation. +``` + +### Building and installation + +If you followed all the steps successfully, and `autogen.sh` or `configure` did not print any errors, +you are ready to build the project. This is simply done with + +```shell +make +``` + +If no errors are emitted you are ready for installation. Depending on whether +the current user has permissions to write to the destination directory or not, +you would either run +```shell +make install +``` +_OR_ +```shell +sudo make install +``` + +If you are on Linux, you want to run `sudo ldconfig` after installation to +make sure the installed libraries are made available. + +## Usage + +Usage is simple; `libplist` has a straight-forward API. It is used in [libimobiledevice](https://github.com/libimobiledevice/libimobiledevice) +and [corresponding projects](https://github.com/libimobiledevice/). + +Furthermore, it comes with a command line utility `plistutil` that is really easy to use: +```shell +plistutil -i foobar.plist -o output.plist +``` +This converts the `foobar.plist` file to the opposite format, e.g. binary to +XML or vice versa, and outputs it to the `output.plist` file. + +To convert to a specific format - and also to convert from JSON or OpenStep +format - use the `-f` command line switch: +```shell +plistutil -i input.plist -f json +``` +This will convert `input.plist`, regardless of the input format, to JSON. The +code auto-detects the input format and parses it accordingly. + +Please consult the usage information or manual page for a full documentation of +available command line options: +```shell +plistutil --help +``` +or +```shell +man plistutil +``` + +## Contributing + +We welcome contributions from anyone and are grateful for every pull request! + +If you'd like to contribute, please fork the `master` branch, change, commit and +send a pull request for review. Once approved it can be merged into the main +code base. + +If you plan to contribute larger changes or a major refactoring, please create a +ticket first to discuss the idea upfront to ensure less effort for everyone. + +Please make sure your contribution adheres to: +* Try to follow the code style of the project +* Commit messages should describe the change well without being too short +* Try to split larger changes into individual commits of a common domain +* Use your real name and a valid email address for your commits + +## Links + +* Homepage: https://libimobiledevice.org/ +* Repository: https://github.com/libimobiledevice/libplist.git +* Repository (Mirror): https://git.libimobiledevice.org/libplist.git +* Issue Tracker: https://github.com/libimobiledevice/libplist/issues +* Mailing List: https://lists.libimobiledevice.org/mailman/listinfo/libimobiledevice-devel +* Twitter: https://twitter.com/libimobiledev + +## License + +This project is licensed under the [GNU Lesser General Public License v2.1](https://www.gnu.org/licenses/lgpl-2.1.en.html), +also included in the repository in the `COPYING` file. + +## Credits + +Apple, iPhone, iPad, iPod, iPod Touch, Apple TV, Apple Watch, Mac, iOS, +iPadOS, tvOS, watchOS, and macOS are trademarks of Apple Inc. + +This project is an independent software library and has not been authorized, +sponsored, or otherwise approved by Apple Inc. + +README Updated on: 2024-10-22 + diff --git a/autogen.sh b/autogen.sh new file mode 100755 index 0000000..5a0ec43 --- /dev/null +++ b/autogen.sh @@ -0,0 +1,26 @@ +#!/bin/sh + +olddir=`pwd` +srcdir=`dirname $0` +test -z "$srcdir" && srcdir=. + +( + cd "$srcdir" + + gprefix=`which glibtoolize 2>&1 >/dev/null` + if [ $? -eq 0 ]; then + glibtoolize --force + else + libtoolize --force + fi + aclocal -I m4 + autoheader + automake --add-missing + autoconf + + cd "$olddir" +) + +if [ -z "$NOCONFIGURE" ]; then + $srcdir/configure "$@" +fi diff --git a/cmake/libplistPackaging.cmake b/cmake/libplistPackaging.cmake deleted file mode 100644 index 1a5fd92..0000000 --- a/cmake/libplistPackaging.cmake +++ /dev/null @@ -1,15 +0,0 @@ -MACRO( LIBPLIST_PACKAGE _major _minor) - - SET(CPACK_PACKAGE_DESCRIPTION_SUMMARY "Library to parse and generate Apple's binary and XML PList format") - SET(CPACK_PACKAGE_VERSION_MAJOR ${_major}) - SET(CPACK_PACKAGE_VERSION_MINOR ${_minor}) - SET(CPACK_PACKAGE_DESCRIPTION_FILE "${CMAKE_CURRENT_SOURCE_DIR}/README") - SET(CPACK_RESOURCE_FILE_LICENSE "${CMAKE_CURRENT_SOURCE_DIR}/COPYING.LESSER") - SET(CPACK_COMPONENT_LIB_DISPLAY_NAME "PList library") - SET(CPACK_COMPONENT_DEV_DISPLAY_NAME "PList development files") - SET(CPACK_COMPONENT_PLUTIL_DISPLAY_NAME "PList conversion tool") - set(CPACK_COMPONENT_DEV_DEPENDS lib) - set(CPACK_COMPONENT_PLUTIL_DEPENDS lib) - INCLUDE( CPack ) - -ENDMACRO( LIBPLIST_PACKAGE ) diff --git a/cmake/modules/FindCython.cmake b/cmake/modules/FindCython.cmake deleted file mode 100644 index 89005b7..0000000 --- a/cmake/modules/FindCython.cmake +++ /dev/null @@ -1,10 +0,0 @@ -FIND_PROGRAM(CYTHON_EXECUTABLE cython) - -INCLUDE(FindPackageHandleStandardArgs) -FIND_PACKAGE_HANDLE_STANDARD_ARGS(Cython DEFAULT_MSG CYTHON_EXECUTABLE) - -MARK_AS_ADVANCED(CYTHON_EXECUTABLE) - -IF(CYTHON_FOUND) - SET(CYTHON_USE_FILE ${CMAKE_SOURCE_DIR}/cmake/modules/UseCython.cmake) -ENDIF(CYTHON_FOUND) diff --git a/cmake/modules/FindGLIB2.cmake b/cmake/modules/FindGLIB2.cmake deleted file mode 100644 index 226d551..0000000 --- a/cmake/modules/FindGLIB2.cmake +++ /dev/null @@ -1,51 +0,0 @@ -# - Try to find the GLIB2 libraries -# Once done this will define -# -# GLIB2_FOUND - system has glib2 -# GLIB2_INCLUDE_DIR - the glib2 include directory -# GLIB2_LIBRARIES - glib2 library - -# Copyright (c) 2008 Laurent Montel, <montel@kde.org> -# -# Redistribution and use is allowed according to the terms of the BSD license. -# For details see the accompanying COPYING-CMAKE-SCRIPTS file. - - -if(GLIB2_INCLUDE_DIR AND GLIB2_LIBRARIES) - # Already in cache, be silent - set(GLIB2_FIND_QUIETLY TRUE) -endif(GLIB2_INCLUDE_DIR AND GLIB2_LIBRARIES) - -if (NOT WIN32) - FIND_PACKAGE(PkgConfig) - PKG_CHECK_MODULES(PC_GLIB2 glib-2.0) - SET(GLIB2_DEFINITIONS ${PC_GLIB2_CFLAGS_OTHER}) -endif(NOT WIN32) - -find_path(GLIB2_MAIN_INCLUDE_DIR glib.h - PATH_SUFFIXES glib-2.0 - PATHS ${_LibGLIB2IncDir} ) - -# search the glibconfig.h include dir under the same root where the library is found -find_library(GLIB2_LIBRARIES - NAMES glib-2.0 - PATHS ${_LibGLIB2LinkDir} ) - -get_filename_component(glib2LibDir "${GLIB2_LIBRARIES}" PATH) - -find_path(GLIB2_INTERNAL_INCLUDE_DIR glibconfig.h - PATH_SUFFIXES glib-2.0/include - PATHS ${_LibGLIB2IncDir} "${glib2LibDir}" ${CMAKE_SYSTEM_LIBRARY_PATH}) - -set(GLIB2_INCLUDE_DIR "${GLIB2_MAIN_INCLUDE_DIR}") - -# not sure if this include dir is optional or required -# for now it is optional -if(GLIB2_INTERNAL_INCLUDE_DIR) - set(GLIB2_INCLUDE_DIR ${GLIB2_INCLUDE_DIR} "${GLIB2_INTERNAL_INCLUDE_DIR}") -endif(GLIB2_INTERNAL_INCLUDE_DIR) - -include(FindPackageHandleStandardArgs) -find_package_handle_standard_args(GLIB2 DEFAULT_MSG GLIB2_LIBRARIES GLIB2_MAIN_INCLUDE_DIR) - -mark_as_advanced(GLIB2_INCLUDE_DIR GLIB2_LIBRARIES)
\ No newline at end of file diff --git a/cmake/modules/FindLibXml2.cmake b/cmake/modules/FindLibXml2.cmake deleted file mode 100644 index 8d3c77e..0000000 --- a/cmake/modules/FindLibXml2.cmake +++ /dev/null @@ -1,57 +0,0 @@ -# - Try to find LibXml2 -# Once done this will define -# -# LIBXML2_FOUND - System has LibXml2 -# LIBXML2_INCLUDE_DIR - The LibXml2 include directory -# LIBXML2_LIBRARIES - The libraries needed to use LibXml2 -# LIBXML2_DEFINITIONS - Compiler switches required for using LibXml2 -# LIBXML2_XMLLINT_EXECUTABLE - The XML checking tool xmllint coming with LibXml2 - -# Copyright (c) 2006, Alexander Neundorf, <neundorf@kde.org> -# -# Redistribution and use is allowed according to the terms of the BSD license. -# For details see the accompanying COPYING-CMAKE-SCRIPTS file. - - -IF (LIBXML2_INCLUDE_DIR AND LIBXML2_LIBRARIES) - # in cache already - SET(LibXml2_FIND_QUIETLY TRUE) -ENDIF (LIBXML2_INCLUDE_DIR AND LIBXML2_LIBRARIES) - -IF (NOT WIN32) - # use pkg-config to get the directories and then use these values - # in the FIND_PATH() and FIND_LIBRARY() calls - FIND_PACKAGE(PkgConfig) - PKG_CHECK_MODULES(PC_LIBXML libxml-2.0) - SET(LIBXML2_DEFINITIONS ${PC_LIBXML_CFLAGS_OTHER}) -ENDIF (NOT WIN32) - -FIND_PATH(LIBXML2_INCLUDE_DIR libxml/xpath.h - HINTS - ${PC_LIBXML_INCLUDEDIR} - ${PC_LIBXML_INCLUDE_DIRS} - PATH_SUFFIXES libxml2 - ) - -FIND_LIBRARY(LIBXML2_LIBRARIES NAMES xml2 libxml2 - HINTS - ${PC_LIBXML_LIBDIR} - ${PC_LIBXML_LIBRARY_DIRS} - ) - -FIND_PROGRAM(LIBXML2_XMLLINT_EXECUTABLE xmllint) -# for backwards compat. with KDE 4.0.x: -SET(XMLLINT_EXECUTABLE "${LIBXML2_XMLLINT_EXECUTABLE}") - -IF( NOT LIBXML2_XMLLINT_EXECUTABLE ) - MESSAGE(STATUS "xmllint program not found. Install it if you want validate generated doc file.") -ENDIF(NOT LIBXML2_XMLLINT_EXECUTABLE ) - - -INCLUDE(FindPackageHandleStandardArgs) - -# handle the QUIETLY and REQUIRED arguments and set LIBXML2_FOUND to TRUE if -# all listed variables are TRUE -FIND_PACKAGE_HANDLE_STANDARD_ARGS(LibXml2 DEFAULT_MSG LIBXML2_LIBRARIES LIBXML2_INCLUDE_DIR) - -MARK_AS_ADVANCED(LIBXML2_INCLUDE_DIR LIBXML2_LIBRARIES LIBXML2_XMLLINT_EXECUTABLE) diff --git a/cmake/modules/cmake_uninstall.cmake.in b/cmake/modules/cmake_uninstall.cmake.in deleted file mode 100644 index 4bfb0bf..0000000 --- a/cmake/modules/cmake_uninstall.cmake.in +++ /dev/null @@ -1,21 +0,0 @@ -IF(NOT EXISTS "@CMAKE_CURRENT_BINARY_DIR@/install_manifest.txt") - MESSAGE(FATAL_ERROR "Cannot find install manifest: \"@CMAKE_CURRENT_BINARY_DIR@/install_manifest.txt\"") -ENDIF(NOT EXISTS "@CMAKE_CURRENT_BINARY_DIR@/install_manifest.txt") - -FILE(READ "@CMAKE_CURRENT_BINARY_DIR@/install_manifest.txt" files) -STRING(REGEX REPLACE "\n" ";" files "${files}") -FOREACH(file ${files}) - MESSAGE(STATUS "Uninstalling \"$ENV{DESTDIR}${file}\"") - IF(EXISTS "$ENV{DESTDIR}${file}") - EXEC_PROGRAM( - "@CMAKE_COMMAND@" ARGS "-E remove \"$ENV{DESTDIR}${file}\"" - OUTPUT_VARIABLE rm_out - RETURN_VALUE rm_retval - ) - IF(NOT "${rm_retval}" STREQUAL 0) - MESSAGE(FATAL_ERROR "Problem when removing \"$ENV{DESTDIR}${file}\"") - ENDIF(NOT "${rm_retval}" STREQUAL 0) - ELSE(EXISTS "$ENV{DESTDIR}${file}") - MESSAGE(STATUS "File \"$ENV{DESTDIR}${file}\" does not exist.") - ENDIF(EXISTS "$ENV{DESTDIR}${file}") -ENDFOREACH(file) diff --git a/configure.ac b/configure.ac new file mode 100644 index 0000000..6f574d9 --- /dev/null +++ b/configure.ac @@ -0,0 +1,336 @@ +# -*- Autoconf -*- +# Process this file with autoconf to produce a configure script. + +AC_PREREQ(2.68) +AC_INIT([libplist], [m4_esyscmd(./git-version-gen $RELEASE_VERSION)], [https://github.com/libimobiledevice/libplist/issues], [], [https://libimobiledevice.org]) +AM_INIT_AUTOMAKE([dist-bzip2 no-dist-gzip check-news]) +m4_ifdef([AM_SILENT_RULES], [AM_SILENT_RULES]) +AC_CONFIG_SRCDIR([src/]) +AC_CONFIG_HEADERS([config.h]) +AC_CONFIG_MACRO_DIR([m4]) + +dnl libtool versioning +# +1 : 0 : +1 == adds new functions to the interface +# +1 : 0 : 0 == changes or removes functions (changes include both +# changes to the signature and the semantic) +# ? :+1 : ? == just internal changes +# CURRENT : REVISION : AGE +LIBPLIST_SO_VERSION=11:0:7 + +AC_SUBST(LIBPLIST_SO_VERSION) + +# Check if we have a version defined +if test -z $PACKAGE_VERSION; then + AC_MSG_ERROR([PACKAGE_VERSION is not defined. Make sure to configure a source tree checked out from git or that .tarball-version is present.]) +fi + +# Checks for programs. +AC_PROG_CC +AC_PROG_CXX + +# AC_PROG_CXX will return "g++" even if no c++ compiler is installed +AC_LANG_PUSH([C++]) +AC_MSG_CHECKING([whether $CXX is available and compiles a program]) +AC_COMPILE_IFELSE([AC_LANG_PROGRAM([],[])], + [AC_MSG_RESULT([yes])], + [AC_MSG_RESULT([no]) + AC_MSG_ERROR([C++ compiler not available or unable to compile])]) +AC_LANG_POP + +AM_PROG_CC_C_O +LT_INIT + +# Checks for header files. +AC_CHECK_HEADERS([stdint.h stdlib.h string.h]) + +# Checks for typedefs, structures, and compiler characteristics. +AC_C_CONST +AC_TYPE_SIZE_T +AC_TYPE_SSIZE_T +AC_TYPE_UINT16_T +AC_TYPE_UINT32_T +AC_TYPE_UINT8_T + +# Checks for library functions. +AC_CHECK_FUNCS([strdup strndup strerror gmtime_r localtime_r timegm strptime memmem]) + +# Checking endianness +AC_C_BIGENDIAN([AC_DEFINE([__BIG_ENDIAN__], [1], [big endian])], + [AC_DEFINE([__LITTLE_ENDIAN__], [1], [little endian])]) + + +# Check for operating system +AC_MSG_CHECKING([for platform-specific build settings]) +case ${host_os} in + *mingw32*|*cygwin*) + AC_MSG_RESULT([${host_os}]) + win32=true + ;; + darwin*|*android*) + AC_MSG_RESULT([${host_os}]) + ;; + *) + AC_MSG_RESULT([${host_os}]) + ;; +esac +AM_CONDITIONAL(WIN32, test x$win32 = xtrue) + +AC_SEARCH_LIBS([fmin],[m]) + +# Check if struct tm has a tm_gmtoff member +AC_CACHE_CHECK(for tm_gmtoff in struct tm, ac_cv_struct_tm_gmtoff, + AC_COMPILE_IFELSE([AC_LANG_PROGRAM([ + #include <time.h> + ], [ + struct tm tm; + tm.tm_gmtoff = 1; + ])], + [ac_cv_struct_tm_gmtoff=yes], + [ac_cv_struct_tm_gmtoff=no] + ) +) + +if (test "$ac_cv_struct_tm_gmtoff" = "yes"); then + AC_DEFINE(HAVE_TM_TM_GMTOFF, 1, [Define if struct tm has a tm_gmtoff member]) +fi + +# Check if struct tm has a tm_zone member +AC_CACHE_CHECK(for tm_zone in struct tm, ac_cv_struct_tm_zone, + AC_COMPILE_IFELSE([AC_LANG_PROGRAM([ + #include <time.h> + ], [ + struct tm tm; + tm.tm_zone = (char*)"UTC"; + ])], + [ac_cv_struct_tm_zone=yes], + [ac_cv_struct_tm_zone=no] + ) +) + +if (test "$ac_cv_struct_tm_zone" = "yes"); then + AC_DEFINE(HAVE_TM_TM_ZONE, 1, [Define if struct tm has a tm_zone member]) +fi + +# Cython Python Bindings +AC_ARG_WITH([cython], + [AS_HELP_STRING([--without-cython], + [build Python bindings using Cython (default is yes)])], + [build_cython=false], + [build_cython=true]) +if test "$build_cython" = "true"; then + AC_PROG_CYTHON([3.0.0]) + if [test "x$CYTHON" != "xfalse"]; then + AM_PATH_PYTHON([3.0], [CYTHON_PYTHON]) + fi +else + CYTHON=false +fi +if [test "x$CYTHON" != "xfalse"]; then + PKG_PROG_PKG_CONFIG + AC_MSG_CHECKING([for libplist Cython bindings]) + if test -x "$PKG_CONFIG"; then + CYTHON_PLIST_INCLUDE_DIR=$($PKG_CONFIG --variable=includedir libplist-2.0)/plist/cython + fi + if [test ! -d "$CYTHON_PLIST_INCLUDE_DIR"]; then + CYTHON_PLIST_INCLUDE_DIR=. + cython_python_bindings=yes + AC_MSG_RESULT([Using built-in libplist Cython bindings (assuming this is a first build)]) + else + AC_SUBST([CYTHON_PLIST_INCLUDE_DIR]) + AC_MSG_RESULT([$CYTHON_PLIST_INCLUDE_DIR]) + cython_python_bindings=yes + fi +else + cython_python_bindings=no +fi +AM_CONDITIONAL([HAVE_CYTHON],[test "x$cython_python_bindings" = "xyes"]) + +AC_ARG_WITH([tools], + [AS_HELP_STRING([--with-tools], [Build libplist tools. [default=yes]])], + [build_tools=${withval}], + [build_tools=yes] +) +AS_IF([test "x$build_tools" = "xyes"], [AC_DEFINE(BUILD_TOOLS, 1, [Define if we are building plist tools])]) +AM_CONDITIONAL(BUILD_TOOLS, test "x$build_tools" = "xyes") + +AS_COMPILER_FLAGS(GLOBAL_CFLAGS, "-Wall -Wextra -Wredundant-decls -Wshadow -Wpointer-arith -Wwrite-strings -Wswitch-default -Wno-unused-parameter -Wno-strict-aliasing") +GLOBAL_LDFLAGS="" + +AC_ARG_ENABLE(debug, +AS_HELP_STRING([--enable-debug], + [build debug message output code (default is no)]), +[case "${enableval}" in + yes) debug=yes ;; + no) debug=no ;; + *) AC_MSG_ERROR([bad value ${enableval} for --enable-debug]) ;; +esac], +[debug=no]) + +if (test "x$debug" = "xyes"); then + AC_DEFINE(DEBUG, 1, [Define if debug message output code should be built.]) + GLOBAL_CFLAGS+=" -g" +fi + +if test "x$enable_static" = "xyes" -a "x$enable_shared" = "xno"; then + GLOBAL_CFLAGS+=" -DLIBPLIST_STATIC" +fi + +GLOBAL_CXXFLAGS=$GLOBAL_CFLAGS +AS_COMPILER_FLAG([-fvisibility=hidden], [ + GLOBAL_CFLAGS+=" -fvisibility=hidden" +], []) + +AC_SUBST(GLOBAL_CFLAGS) +AC_SUBST(GLOBAL_CXXFLAGS) +AC_SUBST(GLOBAL_LDFLAGS) + +case "$GLOBAL_CFLAGS" in + *-fvisibility=hidden*) + AC_DEFINE([HAVE_FVISIBILITY], [1], [Define if compiled with -fvisibility=hidden]) +esac + +AC_ARG_WITH([sanitizers], + [AS_HELP_STRING([--with-sanitizers], + [build libplist with sanitizers (default is no)])], + [build_sanitizers=${withval}], + []) + +AC_ARG_WITH([fuzzers], + [AS_HELP_STRING([--with-fuzzers], + [build fuzzers (implies --with-sanitizers, default is no)])], + [build_fuzzers=${withval}], + [build_fuzzers=no]) + +AC_ARG_WITH([tests], + [AS_HELP_STRING([--without-tests], + [Do not build libplist test suite (default is yes)])], + [build_tests=${withval}], + [build_tests=${build_tools}]) + +if test "x$build_tests" = "xyes" -a "x$build_tools" != "xyes"; then + AC_MSG_ERROR([Building the test suite requires plistutil. Reconfigure with --with-tools=yes to enable.]) +fi + +if test "x$build_fuzzers" = "xyes"; then + if test "x$build_sanitizers" = "xno"; then + AC_MSG_ERROR([--with-fuzzers implies --with-sanitizers, but --without-sanitizers was given. This does not work.]) + fi + build_sanitizers=yes +fi + +if test "x$build_sanitizers" = "xyes"; then + AS_COMPILER_FLAG([-fsanitize=address], [ + SANITIZER_FLAGS+=" -fsanitize=address" + ASAN_AVAILABLE=yes + ], []) + if test "$ASAN_AVAILABLE" = "yes"; then + AS_COMPILER_FLAG([-fsanitize=address -fsanitize-address-use-after-scope], [ + SANITIZER_FLAGS+=" -fsanitize-address-use-after-scope" + ], []) + SANITIZERS+="ASAN " + fi + + AS_COMPILER_FLAG([-fsanitize=undefined], [ + SANITIZER_FLAGS+=" -fsanitize=undefined" + UBSAN_AVAILABLE=yes + ], []) + + if test "$UBSAN_AVAILABLE" = "yes"; then + SANITIZERS+="UBSAN " + fi + + if test -z "$SANITIZER_FLAGS"; then + AC_MSG_ERROR([compiler doesn't support -fsanitize=address nor -fsanitize=undefined]) + fi + + COVERAGE_CHECKS="trace-pc-guard trace-cmp edge" + for COV_CHECK in $COVERAGE_CHECKS; do + AS_COMPILER_FLAG([-fsanitize-coverage=$COV_CHECK], [ + if test -z "$SAN_COV_FLAGS"; then + SAN_COV_FLAGS="$COV_CHECK" + else + SAN_COV_FLAGS+=",$COV_CHECK" + fi + ], []) + done + if test -n "$SAN_COV_FLAGS"; then + SANITIZER_FLAGS+=" -fsanitize-coverage=$SAN_COV_FLAGS" + SANITIZERS+="+coverage " + else + AC_MSG_WARN([No sanitizer coverage supported by compiler]) + fi + + CFLAGS="-O1" + + AS_COMPILER_FLAG([-fno-omit-frame-pointer], [ + CFLAGS+=" -fno-omit-frame-pointer" + ], []) + + AS_COMPILER_FLAG([-gline-tables-only], [ + CFLAGS+=" -gline-tables-only" + ], + [ + CFLAGS+=" -g" + ]) + + EXTRA_CONF+=" Enabled sanitizers ......: $SANITIZERS +" + + CFLAGS+=" $SANITIZER_FLAGS" + CXXFLAGS="$CFLAGS -std=c++11" +fi + +if test "x$build_fuzzers" = "xyes"; then + IS_CLANG=`$CXX --version 2>/dev/null |grep clang` + case "$IS_CLANG" in + *clang*) + ;; + *) + AC_MSG_WARN([building fuzzers requires clang/clang++ (continuing anyway)]) + ;; + esac + + CFLAGS+=" -DFUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION" + + EXTRA_CONF+=" Build fuzzers ...........: yes +" +fi + +AM_CONDITIONAL([BUILD_FUZZERS],[test "x$build_fuzzers" = "xyes"]) +AM_CONDITIONAL([BUILD_TESTS],[test "x$build_tests" != "xno"]) + +if test "x$build_fuzzers" = "xyes" || test "x$build_sanitizers" = "xyes"; then + AS_COMPILER_FLAGS(TEST_CFLAGS, [$CFLAGS]) +fi + +m4_ifdef([AM_SILENT_RULES],[AM_SILENT_RULES([yes])]) + +AC_CONFIG_FILES([ +Makefile +libcnary/Makefile +src/Makefile +src/libplist-2.0.pc +src/libplist++-2.0.pc +include/Makefile +tools/Makefile +docs/Makefile +cython/Makefile +test/Makefile +fuzz/Makefile +doxygen.cfg +]) +AC_OUTPUT + +echo " +Configuration for $PACKAGE $VERSION: +------------------------------------------- + + Install prefix ..........: $prefix + Debug code ..............: $debug + Python bindings .........: $cython_python_bindings + Build tools .............: $build_tools + Build test suite ........: $build_tests +$EXTRA_CONF + Now type 'make' to build $PACKAGE $VERSION, + and then 'make install' for installation. +" diff --git a/cython/CMakeLists.txt b/cython/CMakeLists.txt deleted file mode 100644 index eac6bee..0000000 --- a/cython/CMakeLists.txt +++ /dev/null @@ -1,30 +0,0 @@ - -INCLUDE_DIRECTORIES( ${PYTHON_INCLUDE_PATH} ${CMAKE_CURRENT_SOURCE_DIR} ) - - -SET(plist_SRC - ${CMAKE_CURRENT_BINARY_DIR}/plist.c ) - -SET(plist_HDR - ${CMAKE_CURRENT_SOURCE_DIR}/plist.pxd ) - -ADD_CUSTOM_COMMAND( - OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/plist.c - COMMAND ${CYTHON_EXECUTABLE} -o ${CMAKE_CURRENT_BINARY_DIR}/plist.c ${CMAKE_CURRENT_SOURCE_DIR}/plist.pyx - DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/plist.pyx ${CMAKE_CURRENT_SOURCE_DIR}/plist.pxd -) - - -EXEC_PROGRAM("${PYTHON_EXECUTABLE}" - ARGS "-c 'try:\n import distutils.sysconfig; print distutils.sysconfig.get_python_lib(1,0,\"${CMAKE_INSTALL_PREFIX}\")\nexcept: pass\n'" - OUTPUT_VARIABLE DISTUTILS_PYTHON_ILIBRARY_PATH - ) - -PYTHON_ADD_MODULE(cython_plist plist.c plist_util.c) -SET_TARGET_PROPERTIES(cython_plist PROPERTIES PREFIX "" OUTPUT_NAME plist) -TARGET_LINK_LIBRARIES(cython_plist plist ${PYTHON_LIBRARIES}) - -INSTALL( FILES ${CMAKE_CURRENT_BINARY_DIR}/plist${CMAKE_SHARED_MODULE_SUFFIX} - DESTINATION ${DISTUTILS_PYTHON_ILIBRARY_PATH} ) -INSTALL( FILES ${CMAKE_CURRENT_SOURCE_DIR}/plist.pxd - DESTINATION include/plist/cython COMPONENT dev) diff --git a/cython/Makefile.am b/cython/Makefile.am new file mode 100644 index 0000000..bbc51c9 --- /dev/null +++ b/cython/Makefile.am @@ -0,0 +1,57 @@ +AM_CPPFLAGS = -I$(top_srcdir)/include + +AM_CFLAGS = $(GLOBAL_CFLAGS) +AM_LDFLAGS = $(GLOBAL_LDFLAGS) + +EXTRA_DIST = \ + plist.pyx \ + plist.pxd + +if HAVE_CYTHON + +BUILT_SOURCES = plist.c + +PXDINCLUDES = \ + plist.pxd \ + $(CYTHON_PLIST_INCLUDE_DIR)/plist.pxd + +CLEANFILES = \ + *.pyc \ + *.pyo \ + plist.c + +plistdir = $(pyexecdir) +plist_LTLIBRARIES = plist.la +plist_la_SOURCES = \ + plist_util.c \ + plist_util.h \ + plist.pyx + +plist_la_CFLAGS = \ + -I$(top_srcdir)/include \ + -I$(top_srcdir)/src \ + $(PYTHON_CPPFLAGS) \ + $(AM_CFLAGS) \ + -Wno-shadow \ + -Wno-redundant-decls \ + -Wno-switch-default \ + -Wno-strict-aliasing \ + -Wno-implicit-function-declaration \ + -fvisibility=default + +plist_la_LDFLAGS = -module -avoid-version $(PYTHON_LIBS) $(AM_LDFLAGS) -shared -export-dynamic +plist_la_LIBADD = $(top_builddir)/src/libplist-2.0.la + +if WIN32 +plist_la_LDFLAGS += -no-undefined +endif + +plist.c: plist.pyx $(PXDINCLUDES) $(PXIINCLUDES) + +.pyx.c: + $(CYTHON) -I$(CYTHON_PLIST_INCLUDE_DIR) -I$(top_srcdir)/src -o $@ $< + +pxddir = $(includedir)/plist/cython +pxd_DATA = plist.pxd + +endif diff --git a/cython/plist.pxd b/cython/plist.pxd index a31419d..5a41bf8 100644 --- a/cython/plist.pxd +++ b/cython/plist.pxd @@ -1,3 +1,5 @@ +from libc.stdint cimport * + cdef extern from "plist/plist.h": ctypedef void *plist_t ctypedef void *plist_dict_iter @@ -14,10 +16,19 @@ cdef class Node: cdef class Bool(Node): cpdef set_value(self, object value) cpdef bint get_value(self) - + cdef class Integer(Node): cpdef set_value(self, object value) - cpdef int get_value(self) + cpdef get_value(self) + cpdef bint is_negative(self) + +cdef class Uid(Node): + cpdef set_value(self, object value) + cpdef uint64_t get_value(self) + +cdef class Key(Node): + cpdef set_value(self, object value) + cpdef unicode get_value(self) cdef class Real(Node): cpdef set_value(self, object value) @@ -59,5 +70,10 @@ cdef class Array(Node): cpdef object from_xml(xml) cpdef object from_bin(bytes bin) +cpdef object load(fp, fmt=*, use_builtin_types=*, dict_type=*) +cpdef object loads(data, fmt=*, use_builtin_types=*, dict_type=*) +cpdef object dump(value, fp, fmt=*, sort_keys=*, skipkeys=*) +cpdef object dumps(value, fmt=*, sort_keys=*, skipkeys=*) + cdef object plist_t_to_node(plist_t c_plist, bint managed=*) cdef plist_t native_to_plist_t(object native) diff --git a/cython/plist.pyx b/cython/plist.pyx index 3716a9c..73912d8 100644 --- a/cython/plist.pyx +++ b/cython/plist.pyx @@ -1,21 +1,11 @@ -cdef extern from *: - ctypedef unsigned char uint8_t - ctypedef short int int16_t - ctypedef unsigned short int uint16_t - ctypedef unsigned int uint32_t - ctypedef int int32_t -IF UNAME_MACHINE == 'x86_64': - ctypedef unsigned long int uint64_t -ELSE: - ctypedef unsigned long long int uint64_t - cimport cpython cimport libc.stdlib +from libc.stdint cimport * cdef extern from *: ctypedef enum plist_type: PLIST_BOOLEAN, - PLIST_UINT, + PLIST_INT, PLIST_REAL, PLIST_STRING, PLIST_ARRAY, @@ -23,6 +13,8 @@ cdef extern from *: PLIST_DATE, PLIST_DATA, PLIST_KEY, + PLIST_UID, + PLIST_NULL, PLIST_NONE plist_t plist_new_bool(uint8_t val) @@ -33,13 +25,24 @@ cdef extern from *: void plist_get_uint_val(plist_t node, uint64_t *val) void plist_set_uint_val(plist_t node, uint64_t val) + plist_t plist_new_int(int64_t val) + void plist_get_int_val(plist_t node, int64_t *val) + void plist_set_int_val(plist_t node, int64_t val) + plist_t plist_new_real(double val) void plist_get_real_val(plist_t node, double *val) void plist_set_real_val(plist_t node, double val) - plist_t plist_new_date(int32_t sec, int32_t usec) - void plist_get_date_val(plist_t node, int32_t * sec, int32_t * usec) - void plist_set_date_val(plist_t node, int32_t sec, int32_t usec) + plist_t plist_new_unix_date(int64_t sec) + void plist_get_unix_date_val(plist_t node, int64_t *sec) + void plist_set_unix_date_val(plist_t node, int64_t sec) + + void plist_get_key_val(plist_t node, char **val) + void plist_set_key_val(plist_t node, char *val) + + plist_t plist_new_uid(uint64_t val) + void plist_get_uid_val(plist_t node, uint64_t *val) + void plist_set_uid_val(plist_t node, uint64_t val) plist_t plist_new_string(char *val) void plist_get_string_val(plist_t node, char **val) @@ -49,6 +52,8 @@ cdef extern from *: void plist_get_data_val(plist_t node, char **val, uint64_t * length) void plist_set_data_val(plist_t node, char *val, uint64_t length) + plist_t plist_new_null(); + plist_t plist_new_dict() int plist_dict_get_size(plist_t node) plist_t plist_dict_get_item(plist_t node, char* key) @@ -76,11 +81,11 @@ cdef extern from *: plist_t plist_get_parent(plist_t node) plist_type plist_get_node_type(plist_t node) - void plist_set_type(plist_t node, plist_type type) - void plist_from_xml(char *plist_xml, uint32_t length, plist_t * plist) void plist_from_bin(char *plist_bin, uint32_t length, plist_t * plist) + int plist_int_val_is_negative(plist_t node); + cdef class Node: def __init__(self, *args, **kwargs): self._c_managed = True @@ -116,7 +121,7 @@ cdef class Node: plist_to_bin(self._c_node, &out, &length) try: - return cpython.PyString_FromStringAndSize(out, length) + return bytes(out[:length]) finally: if out != NULL: libc.stdlib.free(out) @@ -181,19 +186,22 @@ cdef Bool Bool_factory(plist_t c_node, bint managed=True): cdef class Integer(Node): def __cinit__(self, object value=None, *args, **kwargs): if value is None: - self._c_node = plist_new_uint(0) + self._c_node = plist_new_int(0) else: - self._c_node = plist_new_uint(int(value)) + if value < 0 or value <= INT64_MAX: + self._c_node = plist_new_int(int(value)) + else: + self._c_node = plist_new_uint(int(value)) def __repr__(self): - cdef int i = self.get_value() - return '<Integer: %s>' % i + return '<Integer: %s>' % self.get_value() def __int__(self): return self.get_value() def __float__(self): - return float(self.get_value()) + v = self.get_value() + return float(v) def __richcmp__(self, other, op): cdef int i = self.get_value() @@ -213,10 +221,18 @@ cdef class Integer(Node): cpdef set_value(self, object value): plist_set_uint_val(self._c_node, int(value)) - cpdef int get_value(self): - cdef uint64_t value - plist_get_uint_val(self._c_node, &value) - return value + cpdef get_value(self): + cdef int64_t ivalue + cdef uint64_t uvalue + if self.is_negative(): + plist_get_int_val(self._c_node, &ivalue) + return int(ivalue) + else: + plist_get_uint_val(self._c_node, &uvalue) + return int(uvalue) + + cpdef bint is_negative(self): + return plist_int_val_is_negative(self._c_node); cdef Integer Integer_factory(plist_t c_node, bint managed=True): cdef Integer instance = Integer.__new__(Integer) @@ -270,8 +286,139 @@ cdef Real Real_factory(plist_t c_node, bint managed=True): instance._c_node = c_node return instance +cdef class Uid(Node): + def __cinit__(self, object value=None, *args, **kwargs): + if value is None: + self._c_node = plist_new_uid(0) + else: + self._c_node = plist_new_uid(int(value)) + + def __repr__(self): + cdef uint64_t i = self.get_value() + return '<Uid: %s>' % i + + def __int__(self): + return self.get_value() + + def __float__(self): + v = self.get_value() + return float(v) + + def __richcmp__(self, other, op): + cdef int i = self.get_value() + if op == 0: + return i < other + if op == 1: + return i <= other + if op == 2: + return i == other + if op == 3: + return i != other + if op == 4: + return i > other + if op == 5: + return i >= other + + cpdef set_value(self, object value): + plist_set_uid_val(self._c_node, int(value)) + + cpdef uint64_t get_value(self): + cdef uint64_t value + plist_get_uid_val(self._c_node, &value) + return value + +cdef Uid Uid_factory(plist_t c_node, bint managed=True): + cdef Uid instance = Uid.__new__(Uid) + instance._c_managed = managed + instance._c_node = c_node + return instance + +cdef class Null(Node): + def __cinit__(self, object value=None, *args, **kwargs): + self._c_node = plist_new_null() + + def __repr__(self): + cdef uint64_t i = self.get_value() + return '<Null>' + +cdef Null Null_factory(plist_t c_node, bint managed=True): + cdef Null instance = Null.__new__(Null) + instance._c_managed = managed + instance._c_node = c_node + return instance + from cpython cimport PY_MAJOR_VERSION +cdef class Key(Node): + def __cinit__(self, object value=None, *args, **kwargs): + cdef: + char* c_utf8_data = NULL + bytes utf8_data + if value is None: + raise ValueError("Requires a value") + else: + if isinstance(value, unicode): + utf8_data = value.encode('utf-8') + elif (PY_MAJOR_VERSION < 3) and isinstance(value, str): + value.encode('ascii') # trial decode + utf8_data = value.encode('ascii') + else: + raise ValueError("Requires unicode input, got %s" % type(value)) + c_utf8_data = utf8_data + self._c_node = plist_new_string("") + plist_set_key_val(self._c_node, c_utf8_data) + + def __repr__(self): + s = self.get_value() + return '<Key: %s>' % s.encode('utf-8') + + def __richcmp__(self, other, op): + cdef unicode s = self.get_value() + if op == 0: + return s < other + if op == 1: + return s <= other + if op == 2: + return s == other + if op == 3: + return s != other + if op == 4: + return s > other + if op == 5: + return s >= other + + cpdef set_value(self, object value): + cdef: + char* c_utf8_data = NULL + bytes utf8_data + if value is None: + plist_set_key_val(self._c_node, c_utf8_data) + else: + if isinstance(value, unicode): + utf8_data = value.encode('utf-8') + elif (PY_MAJOR_VERSION < 3) and isinstance(value, str): + value.encode('ascii') # trial decode + utf8_data = value.encode('ascii') + else: + raise ValueError("Requires unicode input, got %s" % type(value)) + c_utf8_data = utf8_data + plist_set_key_val(self._c_node, c_utf8_data) + + cpdef unicode get_value(self): + cdef: + char* c_value = NULL + plist_get_key_val(self._c_node, &c_value) + try: + return cpython.PyUnicode_DecodeUTF8(c_value, len(c_value), 'strict') + finally: + libc.stdlib.free(c_value) + +cdef Key Key_factory(plist_t c_node, bint managed=True): + cdef Key instance = Key.__new__(Key) + instance._c_managed = managed + instance._c_node = c_node + return instance + cdef class String(Node): def __cinit__(self, object value=None, *args, **kwargs): cdef: @@ -342,19 +489,20 @@ cdef String String_factory(plist_t c_node, bint managed=True): return instance cdef extern from "plist_util.h": - void datetime_to_ints(object obj, int32_t* sec, int32_t* usec) - object ints_to_datetime(int32_t sec, int32_t usec) + int64_t datetime_to_timestamp(object obj) + object timestamp_to_datetime(int64_t sec) int check_datetime(object obj) cdef plist_t create_date_plist(value=None): cdef plist_t node = NULL - cdef int32_t secs - cdef int32_t usecs if value is None: - node = plist_new_date(0, 0) + node = plist_new_unix_date(0) + elif isinstance(value, int): + node = plist_new_unix_date(value) + elif isinstance(value, float): + node = plist_new_unix_date(int(value)) elif check_datetime(value): - datetime_to_ints(value, &secs, &usecs) - node = plist_new_date(secs, usecs) + node = plist_new_unix_date(datetime_to_timestamp(value)) return node cdef class Date(Node): @@ -381,19 +529,21 @@ cdef class Date(Node): return d >= other cpdef object get_value(self): - cdef int32_t secs = 0 - cdef int32_t usecs = 0 - cdef object result - plist_get_date_val(self._c_node, &secs, &usecs) - return ints_to_datetime(secs, usecs) + cdef int64_t secs = 0 + plist_get_unix_date_val(self._c_node, &secs) + return timestamp_to_datetime(secs) cpdef set_value(self, object value): - cdef int32_t secs - cdef int32_t usecs - if not check_datetime(value): - raise ValueError("Expected a datetime") - datetime_to_ints(value, &secs, &usecs) - plist_set_date_val(self._c_node, secs, usecs) + cdef int64_t secs = 0 + if isinstance(value, int): + secs = value + elif isinstance(value, float): + secs = int(value) + elif check_datetime(value): + secs = datetime_to_timestamp(value) + else: + raise ValueError("Expected int or datetime") + plist_set_unix_date_val(self._c_node, secs) cdef Date Date_factory(plist_t c_node, bint managed=True): cdef Date instance = Date.__new__(Date) @@ -434,7 +584,7 @@ cdef class Data(Node): plist_get_data_val(self._c_node, &val, &length) try: - return cpython.PyString_FromStringAndSize(val, length) + return bytes(val[:length]) finally: libc.stdlib.free(val) @@ -456,7 +606,7 @@ cdef plist_t create_dict_plist(object value=None): if value is not None and isinstance(value, dict): for key, item in value.items(): c_node = native_to_plist_t(item) - plist_dict_insert_item(node, key, c_node) + plist_dict_set_item(node, key, c_node) c_node = NULL return node @@ -480,7 +630,12 @@ cdef class Dict(Node): plist_dict_next_item(self._c_node, it, &key, &subnode); while subnode is not NULL: - cpython.PyDict_SetItem(self._map, key, plist_t_to_node(subnode, False)) + py_key = key + + if PY_MAJOR_VERSION >= 3: + py_key = py_key.decode('utf-8') + + cpython.PyDict_SetItem(self._map, py_key, plist_t_to_node(subnode, False)) subnode = NULL libc.stdlib.free(key) key = NULL @@ -512,9 +667,7 @@ cdef class Dict(Node): return '<Dict: %s>' % self._map cpdef dict get_value(self): - cdef dict result = cpython.PyDict_New() return dict([(key, value.get_value()) for key, value in self.items()]) - return result cpdef set_value(self, dict value): plist_free(self._c_node) @@ -546,7 +699,6 @@ cdef class Dict(Node): cpdef list values(self): return cpython.PyDict_Values(self._map) - return self._map.values() cpdef object itervalues(self): return self._map.itervalues() @@ -561,12 +713,12 @@ cdef class Dict(Node): else: n = plist_t_to_node(native_to_plist_t(value), False) - plist_dict_insert_item(self._c_node, key, n._c_node) + plist_dict_set_item(self._c_node, key, n._c_node) self._map[key] = n def __delitem__(self, key): cpython.PyDict_DelItem(self._map, key) - plist_dict_remove_item(self._c_node, key) + plist_dict_remove_item(self._c_node, key.encode('utf-8')) cdef Dict Dict_factory(plist_t c_node, bint managed=True): cdef Dict instance = Dict.__new__(Dict) @@ -598,7 +750,7 @@ cdef class Array(Node): cdef uint32_t size = plist_array_get_size(self._c_node) cdef plist_t subnode = NULL - for i from 0 <= i < size: + for i in range(size): subnode = plist_array_get_item(self._c_node, i) self._array.append(plist_t_to_node(subnode, False)) @@ -637,7 +789,10 @@ cdef class Array(Node): return self._array.__iter__() def __getitem__(self, index): - return self._array[index] + value = self._array[index] + if isinstance(value, list): + return [item.copy() for item in value] + return value.copy() def __setitem__(self, index, value): cdef Node n @@ -699,7 +854,7 @@ cdef plist_t native_to_plist_t(object native): return plist_new_string(native) if isinstance(native, bool): return plist_new_bool(<bint>native) - if isinstance(native, int) or isinstance(native, long): + if isinstance(native, int): return plist_new_uint(native) if isinstance(native, float): return plist_new_real(native) @@ -714,8 +869,10 @@ cdef object plist_t_to_node(plist_t c_plist, bint managed=True): cdef plist_type t = plist_get_node_type(c_plist) if t == PLIST_BOOLEAN: return Bool_factory(c_plist, managed) - if t == PLIST_UINT: + if t == PLIST_INT: return Integer_factory(c_plist, managed) + if t == PLIST_KEY: + return Key_factory(c_plist, managed) if t == PLIST_REAL: return Real_factory(c_plist, managed) if t == PLIST_STRING: @@ -728,5 +885,105 @@ cdef object plist_t_to_node(plist_t c_plist, bint managed=True): return Date_factory(c_plist, managed) if t == PLIST_DATA: return Data_factory(c_plist, managed) + if t == PLIST_UID: + return Uid_factory(c_plist, managed) + if t == PLIST_NULL: + return Null_factory(c_plist, managed) if t == PLIST_NONE: return None + +# This is to match up with the new plistlib API +# http://docs.python.org/dev/library/plistlib.html +# dump() and dumps() are not yet implemented +FMT_XML = 1 +FMT_BINARY = 2 + +cpdef object load(fp, fmt=None, use_builtin_types=True, dict_type=dict): + is_binary = fp.read(6) == 'bplist' + fp.seek(0) + + cdef object cb = None + + if not fmt: + if is_binary: + if 'b' not in fp.mode: + raise IOError('File handle must be opened in binary (b) mode to read binary property lists') + cb = from_bin + else: + cb = from_xml + else: + if fmt not in (FMT_XML, FMT_BINARY): + raise ValueError('Format must be constant FMT_XML or FMT_BINARY') + if fmt == FMT_BINARY: + cb = from_bin + elif fmt == FMT_XML: + cb = from_xml + + if is_binary and fmt == FMT_XML: + raise ValueError('Cannot parse binary property list as XML') + elif not is_binary and fmt == FMT_BINARY: + raise ValueError('Cannot parse XML property list as binary') + + return cb(fp.read()) + +cpdef object loads(data, fmt=None, use_builtin_types=True, dict_type=dict): + is_binary = data[0:6] == 'bplist' + + cdef object cb = None + + if fmt is not None: + if fmt not in (FMT_XML, FMT_BINARY): + raise ValueError('Format must be constant FMT_XML or FMT_BINARY') + if fmt == FMT_BINARY: + cb = from_bin + else: + cb = from_xml + else: + if is_binary: + cb = from_bin + else: + cb = from_xml + + if is_binary and fmt == FMT_XML: + raise ValueError('Cannot parse binary property list as XML') + elif not is_binary and fmt == FMT_BINARY: + raise ValueError('Cannot parse XML property list as binary') + + return cb(data) + +cpdef object dump(value, fp, fmt=FMT_XML, sort_keys=True, skipkeys=False): + fp.write(dumps(value, fmt=fmt)) + +cpdef object dumps(value, fmt=FMT_XML, sort_keys=True, skipkeys=False): + if fmt not in (FMT_XML, FMT_BINARY): + raise ValueError('Format must be constant FMT_XML or FMT_BINARY') + + if check_datetime(value): + node = Date(value) + elif isinstance(value, unicode): + node = String(value) + elif PY_MAJOR_VERSION >= 3 and isinstance(value, bytes): + node = Data(value) + elif isinstance(value, str): + # See if this is binary + try: + node = String(value) + except ValueError: + node = Data(value) + elif isinstance(value, bool): + node = Bool(value) + elif isinstance(value, int): + node = Integer(value) + elif isinstance(value, float): + node = Real(value) + elif isinstance(value, dict): + node = Dict(value) + elif type(value) in (list, set, tuple): + node = Array(value) + else: + node = value + + if fmt == FMT_XML: + return node.to_xml().encode('utf-8') + + return node.to_bin() diff --git a/cython/plist_util.c b/cython/plist_util.c index 70c5be3..27084fa 100644 --- a/cython/plist_util.c +++ b/cython/plist_util.c @@ -3,39 +3,35 @@ #include <time.h> #include <datetime.h> -void datetime_to_ints(PyObject* obj, int32_t* sec, int32_t* usec) { +int64_t datetime_to_timestamp(PyObject* obj) { PyDateTime_IMPORT; if (!PyDateTime_Check(obj)) { - PyErr_SetString(PyExc_ValueError,"Expected a datetime"); - sec = NULL; - usec = NULL; - return; + PyErr_SetString(PyExc_ValueError,"Expected a datetime"); + return 0; } - struct tm t = { - PyDateTime_DATE_GET_SECOND(obj), - PyDateTime_DATE_GET_MINUTE(obj), - PyDateTime_DATE_GET_HOUR(obj), - PyDateTime_GET_DAY(obj), - PyDateTime_GET_MONTH(obj)-1, - PyDateTime_GET_YEAR(obj)-1900, - 0,0,0 - }; - *sec = (int32_t)mktime(&t); - *usec = PyDateTime_DATE_GET_MICROSECOND(obj); + struct tm t; + memset(&t, 0, sizeof(t)); + t.tm_sec = PyDateTime_DATE_GET_SECOND(obj); + t.tm_min = PyDateTime_DATE_GET_MINUTE(obj); + t.tm_hour = PyDateTime_DATE_GET_HOUR(obj); + t.tm_mday = PyDateTime_GET_DAY(obj); + t.tm_mon = PyDateTime_GET_MONTH(obj)-1; + t.tm_year = PyDateTime_GET_YEAR(obj)-1900; + return mktime(&t); } -PyObject* ints_to_datetime(int32_t sec, int32_t usec) { - time_t sec_tt = sec; +PyObject* timestamp_to_datetime(int64_t sec) { + time_t sec_tt = sec; struct tm* t = gmtime(&sec_tt); if(t){ - PyDateTime_IMPORT; - return PyDateTime_FromDateAndTime(t->tm_year+1900, t->tm_mon+1, t->tm_mday, t->tm_hour, t->tm_min, t->tm_sec, usec); + PyDateTime_IMPORT; + return PyDateTime_FromDateAndTime(t->tm_year+1900, t->tm_mon+1, t->tm_mday, t->tm_hour, t->tm_min, t->tm_sec, 0); } - return NULL; + return NULL; } int check_datetime(PyObject* ob) { - if(ob){ - PyDateTime_IMPORT; - return PyDateTime_Check(ob); - } - return 0; + if(ob){ + PyDateTime_IMPORT; + return PyDateTime_Check(ob); + } + return 0; } diff --git a/cython/plist_util.h b/cython/plist_util.h index fbf56b6..e0a84b5 100644 --- a/cython/plist_util.h +++ b/cython/plist_util.h @@ -1,5 +1,5 @@ #include <Python.h> -void datetime_to_ints(PyObject* obj, int32_t* sec, int32_t* usec); -PyObject* ints_to_datetime(int32_t sec, int32_t usec); +int64_t datetime_to_timestamp(PyObject* obj); +PyObject* timestamp_to_datetime(int64_t sec); int check_datetime(PyObject* obj); diff --git a/docs/Makefile.am b/docs/Makefile.am new file mode 100644 index 0000000..f025791 --- /dev/null +++ b/docs/Makefile.am @@ -0,0 +1,3 @@ +man_MANS = plistutil.1 + +EXTRA_DIST = $(man_MANS) diff --git a/docs/doxygen/custom.css b/docs/doxygen/custom.css new file mode 100644 index 0000000..e9f1ea8 --- /dev/null +++ b/docs/doxygen/custom.css @@ -0,0 +1,1723 @@ +body, table, div, p, dl { + font: 14px -apple-system,BlinkMacSystemFont,Segoe UI,Helvetica,Arial,sans-serif,Apple Color Emoji,Segoe UI Emoji; + line-height: 1.5; +} + +/* @group Heading Levels */ + +h1.groupheader { + font-size: 28px; +} + +.title { + font: inherit; + font-size: 2rem; + font-weight: bold; + margin: 0; + padding-bottom: 0.3rem; + border-bottom: 1px solid #eaecef; +} + +h2.groupheader { + border-bottom: none; + color: rgb(36, 41, 46); + font-size: 1.5rem; + font-weight: bold; + margin: 1.75rem 0px 1rem 0px; + padding: 0 0.3rem 0 0; + border-bottom: 1px solid #eaecef; + width: 100%; +} + +h3.groupheader { + font-size: 100%; +} + +h1, h2, h3, h4, h5, h6 { + -webkit-transition: text-shadow 0.5s linear; + -moz-transition: text-shadow 0.5s linear; + -ms-transition: text-shadow 0.5s linear; + -o-transition: text-shadow 0.5s linear; + transition: text-shadow 0.5s linear; +} + +.textblock h1, +.textblock h2, +.textblock h3, +.textblock h4, +.textblock h5, +.textblock h6 { + margin: 1.75rem 0px 1rem 0px; + padding: 0 0.3rem 0 0; + border-bottom: 1px solid #eaecef; +} + +h1 { + font-size: 1.75rem; +} + +h2 { + color: rgb(36, 41, 46); + font-size: 1.5rem; + font-weight: bold; + margin: 1.75rem 0 1rem 0px; +} + +h1.glow, h2.glow, h3.glow, h4.glow, h5.glow, h6.glow { + text-shadow: 0 0 15px #1fa4a9; +} + +dt { + font-weight: bold; +} + +div.multicol { + -moz-column-gap: 1em; + -webkit-column-gap: 1em; + -moz-column-count: 3; + -webkit-column-count: 3; +} + +p.startli, p.startdd, p.starttd { + margin-top: 2px; +} + +p.endli { + margin-bottom: 0px; +} + +p.enddd { + margin-bottom: 4px; +} + +p.endtd { + margin-bottom: 2px; +} + +/* @end */ + +caption { + font-weight: bold; +} + +span.legend { + font-size: 70%; + text-align: center; +} + +h3.version { + font-size: 90%; + text-align: center; +} + +div.qindex, div.navtab { + background-color: #fff; + border: 1px solid #d1d5da; + padding: 0; + border-radius: 3px; + text-align: left; +} + +div.qindex, div.navpath { + width: auto; + line-height: 140%; +} + +div.navtab { + margin-right: 2rem; +} + +@media (max-width: 576px) { + .contents td.top:not(.mempage), + div.navtab { + display: none; + } +} + +div.navtab table { + border-spacing: 0; +} + +div.navtab table td.navtab { + position: relative; + display: block; + padding: 8px 10px; + border-bottom: 1px solid #e1e4e8; +} + +div.navtab table td.navtab:hover { + background-color: #f6f8fa; +} + +/* @group Link Styling */ + +a { + color: #005082; + font-weight: normal; + text-decoration: none; +} + +.contents a:visited { + color: #005082; +} + +a:hover { + text-decoration: underline; +} + +a.qindex { + font-weight: bold; +} + +a.qindexHL { + font-weight: bold; + background-color: #fff; + color: inherit; + border: none; +} + +a.qindexHL:before { + position: absolute; + top: 0; + bottom: 0; + left: 0; + width: 2px; + content: ""; + background-color: #f7ae1a; +} + +.contents a.qindexHL:visited { + color: inherit; +} + +a.el { + font-weight: normal; +} + +a.elRef { +} + +a.code, a.code:visited { + color: #3465a4; +} + +a.codeRef, a.codeRef:visited { + color: #3465a4; +} + +/* @end */ + +dl.el { + margin-left: -1cm; +} + +pre.fragment { + border: 1px solid rgb(221, 221, 221); + border-radius: 3px; + background-color: rgb(248, 248, 248); + padding: 1rem; + margin: 1rem 0px; + overflow: auto; + word-wrap: break-word; + font-size: inherit; + line-height: inherit; + font-family: SFMono-Regular,Consolas,Liberation Mono,Menlo,monospace; +} + +div.fragment { + padding: 1rem; + margin: 1rem 0px; + border: solid 1px rgb(221, 221, 221); + border-radius: 3px; + background-color: rgb(248, 248, 248); + color: rgb(0,0,0); +} + +div.line { + font-family: SFMono-Regular,Consolas,Liberation Mono,Menlo,monospace; + font-size: inherit; + min-height: 1rem; + line-height: inherit; + text-wrap: unrestricted; + white-space: -moz-pre-wrap; /* Moz */ + white-space: -pre-wrap; /* Opera 4-6 */ + white-space: -o-pre-wrap; /* Opera 7 */ + white-space: pre-wrap; /* CSS3 */ + word-wrap: break-word; /* IE 5.5+ */ + text-indent: initial; + padding-left: initial; + padding-bottom: 0px; + margin: 0px; + -webkit-transition-property: background-color, box-shadow; + -webkit-transition-duration: 0.5s; + -moz-transition-property: background-color, box-shadow; + -moz-transition-duration: 0.5s; + -ms-transition-property: background-color, box-shadow; + -ms-transition-duration: 0.5s; + -o-transition-property: background-color, box-shadow; + -o-transition-duration: 0.5s; + transition-property: background-color, box-shadow; + transition-duration: 0.5s; + display: inline-block; + min-width: 100%; +} + +div.line.glow { + background-color: cyan; + box-shadow: 0 0 10px cyan; +} + + +span.lineno { + padding-right: 4px; + text-align: right; + border-right: 2px solid #0F0; + background-color: #E8E8E8; + white-space: pre; +} +span.lineno a { + background-color: #D8D8D8; +} + +span.lineno a:hover { + background-color: #C8C8C8; +} + +div.ah { + background-color: black; + font-weight: bold; + color: #ffffff; + margin-bottom: 3px; + margin-top: 3px; + padding: 0.2em; + border: solid thin #333; + border-radius: 0.5em; + -webkit-border-radius: .5em; + -moz-border-radius: .5em; + box-shadow: 2px 2px 3px #999; + -webkit-box-shadow: 2px 2px 3px #999; + -moz-box-shadow: rgba(0, 0, 0, 0.15) 2px 2px 2px; + background-image: -webkit-gradient(linear, left top, left bottom, from(#eee), to(#000),color-stop(0.3, #444)); + background-image: -moz-linear-gradient(center top, #eee 0%, #444 40%, #000); +} + +div.groupHeader { + margin-left: 0px; + margin-top: 9px; + margin-bottom: 4.7px; + + font-size: 19px; + font-weight: normal; +} + +div.groupText { + margin-left: 16px; + font-style: italic; +} + +body { + background-color: white; + color: rgb(36, 41, 46); + margin: 0; +} + +div.contents { + padding: 1rem 2rem; + margin: 0; +} + +td.indexkey { + background-color: #EBEFF6; + font-weight: bold; + border: 1px solid #C4CFE5; + margin: 2px 0px 2px 0; + padding: 2px 10px; + white-space: nowrap; + vertical-align: top; +} + +td.indexvalue { + background-color: #EBEFF6; + border: 1px solid #C4CFE5; + padding: 2px 10px; + margin: 2px 0px; +} + +tr.memlist { + background-color: #EEF1F7; +} + +p.formulaDsp { + text-align: center; +} + +img.formulaDsp { +} + +img.formulaInl { + vertical-align: middle; +} + +div.center { + text-align: center; + margin-top: 0px; + margin-bottom: 0px; + padding: 0px; +} + +div.center img { + border: 0px; +} + +address.footer { + text-align: left; + margin-left: 2rem; + margin-right: 2rem; + margin-bottom: 1rem; +} + +img.footer { + border: 0px; + vertical-align: middle; +} + +/* @group Code Colorization */ + +span.keyword { + color: #008000; +} + +span.keywordtype { + color: #604020; +} + +span.keywordflow { + color: #e08000; +} + +span.comment { + color: #800000; +} + +span.preprocessor { + color: #806020; +} + +span.stringliteral { + color: #002080; +} + +span.charliteral { + color: #008080; +} + +span.vhdldigit { + color: #ff00ff; +} + +span.vhdlchar { + color: #000000; +} + +span.vhdlkeyword { + color: #700070; +} + +span.vhdllogic { + color: #ff0000; +} + +blockquote { + background-color: #F7F8FB; + border-left: 2px solid #9CAFD4; + margin: 0 24px 0 4px; + padding: 0 12px 0 16px; +} + +/* @end */ + +td.tiny { + font-size: 75%; +} + +.dirtab { + padding: 4px; + border-collapse: collapse; + border: 1px solid #A3B4D7; +} + +th.dirtab { + background: #EBEFF6; + font-weight: bold; +} + +hr { + height: 0px; + border: none; + border-top: 1px solid #e1e4e8; +} + +hr.footer { + height: 0px; + border-top: 1px solid #e1e4e8; + margin-left: 2rem; + margin-right: 2rem; +} + +/* @group Member Descriptions */ + +table.memberdecls { + border-spacing: inherit; + padding: 0px; + background-color: transparent; + border-color: #c8e1ff; +} + +table.memberdecls tr:not(.heading) { + background-color: #f1f8ff; +} + +table.memberdecls tr:not(.heading) td.ititle { + background-color: #fff; + border: none; +} + +.memberdecls td, .fieldtable tr { + -webkit-transition-property: background-color, box-shadow; + -webkit-transition-duration: 0.5s; + -moz-transition-property: background-color, box-shadow; + -moz-transition-duration: 0.5s; + -ms-transition-property: background-color, box-shadow; + -ms-transition-duration: 0.5s; + -o-transition-property: background-color, box-shadow; + -o-transition-duration: 0.5s; + transition-property: background-color, box-shadow; + transition-duration: 0.5s; +} + +.memberdecls td.glow, .fieldtable tr.glow { + background-color: #1fa4a9; + box-shadow: none; +} + +.mdescLeft, .mdescRight, +.memItemLeft, .memItemRight, +.memTemplItemLeft, .memTemplItemRight, .memTemplParams { + background-color: transparent; + border: none; + margin: 0; + padding: 1rem 1rem 0.5rem 1rem; + font-family: SFMono-Regular,Consolas,Liberation Mono,Menlo,monospace; +} + +.mdescLeft, +.memItemLeft, +.memTemplItemLeft { + border-left: 1px solid #c8e1ff; +} + +.mdescRight, +.memItemRight, +.memTemplItemRight { + border-right: 1px solid #c8e1ff; +} + +.memberdecls tr:nth-child(2) td { + border-top: 1px solid #c8e1ff; + padding-bottom: 1rem; +} + +.memberdecls tr:last-child td { + border-bottom: none; + padding-bottom: 1rem; +} + +.memberdecls tr:nth-last-child(2) td { + padding-bottom: 1rem; +} + +.mdescLeft, .mdescRight { + padding: 0 1rem 1rem 1rem; + color: black; + + /* font-family: "Lucida Grande", "Lucida Sans Unicode", Helvetica, Arial, Verdana, sans-serif; */ + font-style: normal; +} + +.memSeparator { + background-color: #fff; + border-top: 1px solid #c8e1ff; + border-bottom: 1px solid #c8e1ff; + line-height: 0.5rem; + margin: 0px; + padding: 0px; +} + +tr:last-child .memSeparator { + border-bottom: none; +} + +.memItemLeft, .memTemplItemLeft { + white-space: nowrap; +} + +.memItemRight { + width: 100%; +} + +.mdescRight a.el:first-child, +.memItemRight a.el:first-child, +.memTemplItemRight a.el:first-child { + font-weight: bold; +} + +.memTemplParams { + color: #005082; + white-space: nowrap; + font-size: 80%; +} + +/* @end */ + +/* @group Member Details */ + +/* Styles for detailed member documentation */ + +.memtitle { + padding: 0 0.3rem 0 0; + border: none; + border-bottom: 1px solid #eaecef; + margin: 0 0 1.75rem 0; + line-height: inherit; + background: none; + font-weight: bold; + font-size: 2rem; + width: 100%; + float: left; +} + +.permalink { + font-size: 65%; + display: inline-block; + vertical-align: middle; + display: none; +} + +.permalink a:hover { + text-decoration: none; +} + +.memtemplate { + font-size: 100%; + color: black; + font-weight: normal; + margin-left: 1px; +} + +.memnav { + background-color: #EBEFF6; + border: 1px solid #A3B4D7; + text-align: center; + margin: 2px; + margin-right: 15px; + padding: 2px; +} + +.mempage { + width: 100%; +} + +.memitem { + padding: 0; + margin-bottom: 2rem; + margin-right: 0; + -webkit-transition: box-shadow 0.5s linear; + -moz-transition: box-shadow 0.5s linear; + -ms-transition: box-shadow 0.5s linear; + -o-transition: box-shadow 0.5s linear; + transition: box-shadow 0.5s linear; + display: table !important; + width: 100%; + background-color: #f1f8ff; + border-color: #c8e1ff; +} + +.memitem.glow { + box-shadow: 0 0 15px #1fa4a9; +} + +.memname { + font-family: SFMono-Regular,Consolas,Liberation Mono,Menlo,monospace; + margin-left: 0px; + border-spacing: initial; +} + +.memname td { + font-weight: bold; + vertical-align: bottom; +} + +.memproto, dl.reflist dt { + border: none; + border: 1px solid #c8e1ff; + padding: 1rem; + color: black; + font-weight: bold; + text-shadow: none; + background-image: none; + background-color: #f1f8ff; + /* opera specific markup */ + box-shadow: none; + border-top-right-radius: 0px; + border-top-left-radius: 0px; + /* firefox specific markup */ + -moz-box-shadow: none; + -moz-border-radius-topright: 0px; + -moz-border-radius-topleft: 0px; + /* webkit specific markup */ + -webkit-box-shadow: none; + -webkit-border-top-right-radius: 0px; + -webkit-border-top-left-radius: 0px; +} + +.memdoc, dl.reflist dd { + border: none; + padding: 6px; + background-color: #FBFCFD; + border-top-width: 0; + background-image: none; + background-color: #FFFFFF; + /* opera specific markup */ + border-bottom-left-radius: 0px; + border-bottom-right-radius: 0px; + box-shadow: none; + /* firefox specific markup */ + -moz-border-radius-bottomleft: 0px; + -moz-border-radius-bottomright: 0px; + -moz-box-shadow: none; + /* webkit specific markup */ + -webkit-border-bottom-left-radius: 0px; + -webkit-border-bottom-right-radius: 0px; + -webkit-box-shadow: none; +} + +dl.reflist dt { + padding: 5px; +} + +dl.reflist dd { + margin: 0px 0px 10px 0px; + padding: 5px; +} + +.paramkey { + text-align: right; +} + +.paramtype { + white-space: nowrap; +} + +.paramname { + color: #01496d; + white-space: nowrap; +} +.paramname em { + font-style: italic; + font-weight: normal; +} +.paramname code { + line-height: 14px; +} + +.params, .retval, .exception, .tparams { + margin-left: 0px; + padding-left: 0px; +} + +.params .paramname, .retval .paramname { + font-family: SFMono-Regular,Consolas,Liberation Mono,Menlo,monospace; + font-style: italic; + font-weight: normal; + text-shadow: none; + padding-right: 1rem; +} + +.params .paramtype { + font-style: italic; + vertical-align: top; +} + +.params .paramdir { + font-family: SFMono-Regular,Consolas,Liberation Mono,Menlo,monospace; + vertical-align: top; +} + +table.mlabels { + border-spacing: 0px; +} + +td.mlabels-left { + width: 100%; + padding: 0px; +} + +td.mlabels-right { + vertical-align: middle; + padding: 0px; + white-space: nowrap; +} + +span.mlabels { + margin-left: 8px; +} + +span.mlabel { + background-color: rgb(172, 172, 172);; + border: none; + text-shadow: none; + color: white; + margin-right: 4px; + padding: 2px 3px; + border-radius: 4px; + font-size: 9pt; + white-space: nowrap; + vertical-align: middle; +} + +/* @end */ + +/* these are for tree view when not used as main index */ + +div.directory { + border-top: 1px solid #eaecef; + border-left: 1px solid #eaecef; + border-right: 1px solid #eaecef; + border-bottom: 1px solid #eaecef; + margin: 10px 0px; + width: 100%; +} + +.directory table { + border-collapse:collapse; +} + +.directory td { + margin: 0px; + padding: 8px; + vertical-align: middle; + line-height: 1.5rem; +} + +.directory tr { + border-top: 1px solid #eaecef; +} + +.directory tr:first-child { + border-top: none; +} + +.directory td.entry { + white-space: nowrap; + padding: 8px; +} + +@media (max-width: 576px) { + .directory td.entry > span:first-child { + display: none !important; + } +} + +.directory td.entry a { + outline: none; +} + +.directory td.entry a img { + border: none; +} + +.directory td.desc { + width: 100%; + padding: 8px; + border-left: 1px solid rgba(0,0,0,0.05); +} + +.directory tr.even { + padding-left: 0; + background-color: inherit; +} + +.directory tr:not(.even) { + background-color: #f6f8fa; +} + +.directory img { + vertical-align: -30%; +} + +.directory .levels { + display: none; + white-space: nowrap; + width: auto; + text-align: right; + font-size: 0.75rem; + font-weight: bold; + padding: 8px; + border-bottom: 1px solid #eaecef; +} + +.directory .levels span { + cursor: pointer; + padding: 0 0.5rem; + color: #3d578c; +} + +.arrow { + color: #6c757d; + -webkit-user-select: none; + -khtml-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + cursor: pointer; + font-size: inherit; + display: inline-block; + width: 16px; + height: 16px; +} + +.icon { + display: inline-block; + margin: 0; + + font-family: Arial, Helvetica; + font-weight: bold; + font-size: 0.75rem; + text-align: center; + + width: 16px; + height: 16px; + line-height: 16px; + + border-radius: 4px; + background-color: #6194cb; + color: white; +} + +.icona { + width: 16px; + height: 16px; + display: inline-block; + margin-left: 0.25rem; + margin-right: 0.25rem; +} + +.iconfopen { + width: 16px; + height: 16px; + margin-bottom: 0; + background-image:url('folder-open.png'); + background-position: 0px -1px; + background-repeat: no-repeat; + vertical-align: middle; + display: inline-block; + margin-left: 0.25rem; + margin-right: 0.25rem; +} + +.iconfclosed { + width: 16px; + height: 16px; + margin-bottom: 0; + background-image:url('folder.png'); + background-position: 0px 0px; + background-repeat: no-repeat; + vertical-align: middle; + display: inline-block; + margin-left: 0.25rem; + margin-right: 0.25rem; +} + +.icondoc { + width: 16px; + height: 16px; + margin-bottom: 0; + background-image:url('text-x-generic.png'); + background-position: 0px 0px; + background-repeat: no-repeat; + vertical-align: middle; + display: inline-block; + margin-left: 0.25rem; + margin-right: 0.25rem; +} + +/* @end */ + +div.dynheader { + margin-top: 8px; + -webkit-touch-callout: none; + -webkit-user-select: none; + -khtml-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; +} + +address { + font-style: normal; + color: #2A3D61; +} + +table.doxtable { + border-collapse:collapse; + margin-top: 4px; + margin-bottom: 4px; +} + +table.doxtable td, table.doxtable th { + border: 1px solid #2D4068; + padding: 3px 7px 2px; +} + +table.doxtable th { + background-color: #374F7F; + color: #FFFFFF; + font-size: 110%; + padding-bottom: 4px; + padding-top: 5px; +} + +table.fieldtable { + margin-bottom: 10px; + border: 1px solid #c6cbd1; + border-spacing: 0px; + -moz-border-radius: 3px; + -webkit-border-radius: 3px; + border-radius: 3px; + -moz-box-shadow: none; + -webkit-box-shadow: none; + box-shadow: none; + font-size: 1rem; +} + +.fieldtable td, .fieldtable th { + padding: 0.5rem 1rem; +} + +.fieldtable td.fieldtype, .fieldtable td.fieldname { + white-space: nowrap; + border-right: 1px solid #c6cbd1; + border-bottom: 1px solid #c6cbd1; + vertical-align: middle; +} + +.fieldtable td.fieldname { + padding-top: 0.5rem; + font-style: italic; +} + +.fieldtable td.fielddoc { + border-bottom: 1px solid #c6cbd1; +} + +.fieldtable td.fielddoc p { + font-size: 1rem; +} + +.fieldtable td.fielddoc p:first-child { + margin-top: 0px; +} + +.fieldtable td.fielddoc p:last-child { + margin-bottom: 2px; +} + +.fieldtable tr:last-child td { + border-bottom: none; +} + +.fieldtable tbody tr:nth-of-type(odd) { + background-color: #f6f8fa; +} + +.fieldtable th { + background-image: none; + background-repeat:repeat-x; + background-color: #fff; + font-size: inherit; + font-weight: bold; + color: inherit; + padding: 0.5rem 1rem; + text-align: inherit; + -moz-border-radius-topleft: 3px; + -moz-border-radius-topright: 3px; + -webkit-border-top-left-radius: 3px; + -webkit-border-top-right-radius: 3px; + border-top-left-radius: 3px; + border-top-right-radius: 3px; + border-bottom: 1px solid #c6cbd1; +} + + +.tabsearch { + top: 0px; + left: 10px; + height: 36px; + background-image: none; + z-index: 101; + overflow: hidden; + font-size: 13px; +} + +.navpath ul { + font-size: 1rem; + background-image: none; + background-repeat: repeat-x; + background-position: 0 -5px; + height:30px; + line-height:30px; + color: inherit; + border: none; + overflow:hidden; + margin:0px; + padding: 0px; +} + +.navpath li { + list-style-type: none; + float: left; + background-image: none; + background-repeat: no-repeat; + background-position: right; + color: inherit; +} + +#top .navpath li+li { + padding-left: .5rem; +} + +#top .navpath li+li::before { + display: inline-block; + padding-right: .5rem; + color: #6c757d; + content: "/"; +} + +@media (min-width: 576px) { + address.footer .navpath li+li { + padding-left: .5rem; + } + + address.footer .navpath li+li::before { + display: inline-block; + padding-right: .5rem; + color: #6c757d; + content: "/"; + } +} + +.navpath li.navelem a { + height:32px; + display: inline-block; + text-decoration: none; + outline: none; + color: inherit; + font-family: inherit; + text-shadow: none; + text-decoration: none; +} + +.navpath li.navelem a:hover { + color: inherit; +} + +address.footer { + text-align: left; + padding-right: 0; +} + +address.footer ul.navpath { + display: inline-block; + padding: 0; +} + +.navpath li.footer { + list-style-type: none; + float: left; + padding-left: 0.5rem; + padding-right: 0; + background-image: none; + background-repeat: no-repeat; + background-position: right; + color: inherit; + font-size: 0.75rem; +} + +div.summary { + float: none; + font-size: 0.75rem; + padding-right: 5px; + width: 100%; + text-align: left; +} + +div.summary a { + white-space: nowrap; +} + +div.ingroups { + font-size: 0.75rem; + width: 50%; + text-align: left; +} + +div.ingroups a { + white-space: nowrap; +} + +div.header { + background-image: none; + background-color: white; + padding: 1rem 2rem 0 2rem; + margin: 0px; + border: none; +} + +div.headertitle { + margin: 1rem 0px 0rem 0px; + padding: 0 0.3rem 0 0; +} + +dl { + padding: 0 0 0 10px; +} + +/* dl.note, dl.warning, dl.attention, dl.pre, dl.post, dl.invariant, dl.deprecated, dl.todo, dl.test, dl.bug */ +dl.section { + margin-left: 0px; + padding-left: 0px; +} + +dl.note { + margin-left: 0px; + padding: 1rem; + border-left: 6px solid; + border: 1px solid rgba(27,31,35,.15); + background-color: #fffbdd; + color: #735c0f; +} + +dl.section.note dd { + margin-left: 0; + margin-bottom: 0; +} + +dl.warning, dl.attention { + margin-left: 0px; + padding: 1rem; + + border-left: 6px solid; + border-color: #ab2333; +} + +dl.pre, dl.post, dl.invariant { + margin-left:-7px; + padding-left: 3px; + border-left:4px solid; + border-color: #4e9a06; +} + +dl.deprecated { + margin-left: 0px; + padding: 1rem; + border-left: 6px solid; + border-color: #505050; +} + +dl.deprecated dt a.el { +} + +dl.todo { + margin-left: 0px; + padding: 1rem; + border-left:4px solid; + border-color: #04898e; +} + +dl.test { + margin-left:-7px; + padding-left: 3px; + border-left:4px solid; + border-color: #21376d; +} + +dl.bug { + margin-left:-7px; + padding-left: 3px; + border-left:4px solid; + border-color: #8f5902; +} + +dl.section dd { + margin-bottom: 0.5rem; +} + + +#projectlogo { + text-align: center; + vertical-align: bottom; + border-collapse: separate; + padding-right: 1rem; +} + +#projectlogo img { + border: 0px none; + width: 8rem; + height: auto; +} + +#projectname { + font: inherit; + font-size: 300%; + margin: 0px; + padding: 2px 0px; +} + +#projectbrief { + font: inherit; + font-size: 120%; + margin: 0px; + padding: 0px; +} + +#projectnumber { + font: inherit; + font-size: 50%; + margin: 0px; + padding: 0px; +} + +#titlearea { + padding: 1rem; + margin: 0px; + width: auto; + border-bottom: none; + background-color: #fafbfc; +} + +.image { + text-align: center; +} + +.dotgraph { + text-align: center; +} + +.mscgraph { + text-align: center; +} + +.caption { + font-weight: bold; +} + +div.zoom { + border: 1px solid #90A5CE; +} + +dl.citelist { + margin-bottom:50px; +} + +dl.citelist dt { + color:#334975; + float:left; + font-weight:bold; + margin-right:10px; + padding:5px; +} + +dl.citelist dd { + margin:2px 0; + padding:5px 0; +} + +div.toc { + padding: 14px 25px; + background-color: #F4F6FA; + border: 1px solid #D8DFEE; + border-radius: 7px 7px 7px 7px; + float: right; + height: auto; + margin: 0 20px 10px 10px; + width: 200px; +} + +div.toc li { + background: url("bdwn.png") no-repeat scroll 0 5px transparent; + font: 10px/1.2 Verdana,DejaVu Sans,Geneva,sans-serif; + margin-top: 5px; + padding-left: 10px; + padding-top: 2px; +} + +div.toc h3 { + font: bold 12px/1.2 Arial,FreeSans,sans-serif; + color: #4665A2; + border-bottom: 0 none; + margin: 0; +} + +div.toc ul { + list-style: none outside none; + border: medium none; + padding: 0px; +} + +div.toc li.level1 { + margin-left: 0px; +} + +div.toc li.level2 { + margin-left: 15px; +} + +div.toc li.level3 { + margin-left: 30px; +} + +div.toc li.level4 { + margin-left: 45px; +} + +.inherit_header { + font-weight: bold; + color: gray; + cursor: pointer; + -webkit-touch-callout: none; + -webkit-user-select: none; + -khtml-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; +} + +.inherit_header td { + padding: 6px 0px 2px 5px; +} + +.inherit { + display: none; +} + +tr.heading h2 { + padding-top: 1.75rem; + margin-top: 0; + margin-bottom: 1rem; +} + +@media print +{ + #top { display: none; } + #side-nav { display: none; } + #nav-path { display: none; } + body { overflow:visible; } + h1, h2, h3, h4, h5, h6 { page-break-after: avoid; } + .summary { display: none; } + .memitem { page-break-inside: avoid; } + #doc-content { + margin-left:0 !important; + height:auto !important; + width:auto !important; + overflow:inherit; + display:inline; + } +} + + +.tabs, .tabs2, .tabs3 { + background-image: none; + background-color: #fafbfc; + color: #24292e; + padding: 0 1rem; + font-family: inherit; + font-size: inherit; + width: auto; +} + +.tabs { + border-bottom: 1px solid #e1e4e8; +} + +.tablist { + position: relative; + top: 1px; +} + +.tabs2 { + margin-top: 1rem; + border-bottom: 1px solid #e1e4e8; + background-color: #fff; +} + +.tabs3 { + margin-top: 1rem; + border-bottom: 1px solid #e1e4e8; + background-color: #fff; +} + +.tablist li { + background-image: none; +} + +.tablist a { + background-image: none; + text-shadow: none; + color: #586069; + border: 1px solid transparent; + border-top: 3px solid transparent; + border-radius: 3px 3px 0 0; + padding: 0 1rem; +} + +.tablist a:hover { + background-image: none; + text-shadow: none; + color: #24292e; +} + +.tablist li.current a { + text-shadow: none; + background-image: none; + background-color: #fff; + color: #24292e; +} + +#navrow1 .tablist li.current a { + border-color: #f7ae1a #e1e4e8 transparent; +} + +#navrow2 .tablist li.current a { + border-color: #e45e25 #e1e4e8 transparent; +} + +#navrow3 .tablist li.current a { + border-color: #1fa4a9 #e1e4e8 transparent; +} + +#navrow4 .tablist li.current a { + border-color: #01496d #e1e4e8 transparent; +} + +.tabs li.current { + background-color: #fff; +} + +.tabs2 li.current { + background-color: #fff; +} + +.navpath { + border: none; + margin-top: 1rem; + padding: 0 2rem; +} + +.navpath ul { + background-color: transparent; + border: none; +} + +.navpath li { + padding: 0; +} + +.navpath li.navelem a { + color: #005082; + text-shadow: none; + padding: 0 0.25rem; + font-size: 1rem; +} + +.navpath li.navelem a:hover { + color: #005082; + text-shadow: none; + text-decoration: underline; +} + +/* @group Markdown */ + +table.markdownTable td, +table.markdownTable th { + border: 1px solid #c6cbd1; + padding: 0.5rem 1rem; +} + +.markdownTable tbody tr:nth-of-type(odd) { + background-color: #f6f8fa; +} + +th.markdownTableHeadLeft, +th.markdownTableHeadRight, +th.markdownTableHeadCenter, +th.markdownTableHeadNone { + background-color: #fff; + color: inherit; + font-size: inherit; + font-weight: bold; + padding: 0.5rem 1rem; + text-align: inherit; + -moz-border-radius-topleft: 3px; + -moz-border-radius-topright: 3px; + -webkit-border-top-left-radius: 3px; + -webkit-border-top-right-radius: 3px; + border-top-left-radius: 3px; + border-top-right-radius: 3px; + border-bottom: 1px solid #c6cbd1; +} + +/* @end */ + +@media (max-width: 576px) { + .navpath { + padding: 0 1rem; + } + + .navpath li.navelem a { + padding: 0; + } + + div.header { + padding: 1rem 1rem 0 1rem; + } + + div.contents { + padding: 1rem 1rem 0 1rem; + } + + hr.footer { + margin-left: 1rem; + margin-right: 1rem; + } + + address.footer { + margin-left: 1rem; + margin-right: 1rem; + } + + .memtitle { + font-size: 1.5rem; + word-break: break-all; + } + + .memname td { + float: left; + word-break: break-all; + } + + div.memitem { + margin-bottom: 1rem; + } + + .memdoc table.params { + word-break: break-word; + } + + div.memproto { + display: table; + } + + dl.section dt { + margin-bottom: 0.5rem; + } + + dl.section dd { + margin-left: 0; + word-break: break-word; + } + + .contents > table, + .contents > table thead, + .contents > table tbody, + .contents > table th, + .contents > table td, + .contents > table tr { + display: block; + } + + .contents table.directory, + .contents table.directory thead, + .contents table.directory tbody, + .contents table.directory th, + .contents table.directory td { + display: block; + } + + .contents table.directory tr { + } + + div:not(.PageDoc) .contents table, + div:not(.PageDoc) .contents thead, + div:not(.PageDoc) .contents tbody, + div:not(.PageDoc) .contents th, + div:not(.PageDoc) .contents td, + div:not(.PageDoc) .contents tr { + display: block; + } + + .mdescLeft, + .memItemLeft, + .memTemplItemLeft { + text-align: left; + border-left: 1px solid #c8e1ff; + border-right: 1px solid #c8e1ff; + word-break: break-word; + white-space: normal; + padding-bottom: 0; + } + + table td.mdescLeft { + display: none; + } + + table td.mdescRight, + table td.memItemRight, + table td.memTemplItemRight { + border-top: none !important; + border-left: 1px solid #c8e1ff; + border-right: 1px solid #c8e1ff; + display: table-cell; + word-break: break-word; + padding-top: 0; + } + + .directory td.desc { + display: table-cell; + } + + dl.params dd { + margin-left: 0; + } + + table.params td.paramname { + margin-top: 0.5rem; + } + + #projectlogo img { + border: 0px none; + width: 4rem; + height: auto; + } + + #projectname { + font-size: 1rem; + } + + #projectbrief { + font-size: 0.75rem; + } + + #projectnumber { + font-size: 0.75rem; + } +} diff --git a/docs/doxygen/favicon.ico b/docs/doxygen/favicon.ico Binary files differnew file mode 100644 index 0000000..b542c5b --- /dev/null +++ b/docs/doxygen/favicon.ico diff --git a/docs/doxygen/folder-open.png b/docs/doxygen/folder-open.png Binary files differnew file mode 100644 index 0000000..b67403d --- /dev/null +++ b/docs/doxygen/folder-open.png diff --git a/docs/doxygen/folder.png b/docs/doxygen/folder.png Binary files differnew file mode 100644 index 0000000..901edc9 --- /dev/null +++ b/docs/doxygen/folder.png diff --git a/docs/doxygen/footer.html b/docs/doxygen/footer.html new file mode 100644 index 0000000..9eef78d --- /dev/null +++ b/docs/doxygen/footer.html @@ -0,0 +1,9 @@ +<hr class="footer"/> +<address class="footer"> + <ul class="navpath"> + <li class="footer">© 2007-$year <a href="https://libimobiledevice.org">$projectname</a> All rights reserved</li> + <li class="footer">Generated on <em>$datetime</em> by <a href="https://www.doxygen.org">doxygen $doxygenversion</a></li> + <li class="footer">Icons by <a href="http://tango-project.org/">Tango</a></li> + <li class="footer">Design © $year by <a href="https://mirell.com" target="_blank">Mirell</a></li> + </ui> +</address>
\ No newline at end of file diff --git a/docs/doxygen/header.html b/docs/doxygen/header.html new file mode 100644 index 0000000..bc41a11 --- /dev/null +++ b/docs/doxygen/header.html @@ -0,0 +1,64 @@ +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "https://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> +<html xmlns="http://www.w3.org/1999/xhtml"> +<head> +<meta http-equiv="Content-Type" content="text/xhtml;charset=UTF-8"/> +<meta http-equiv="X-UA-Compatible" content="IE=9"/> +<meta name="generator" content="doxygen $doxygenversion"/> +<meta name="viewport" content="width=device-width, initial-scale=1"/> +<!--BEGIN PROJECT_NAME--><title>$title - $projectname $projectnumber</title><!--END PROJECT_NAME--> +<!--BEGIN !PROJECT_NAME--><title>$title</title><!--END !PROJECT_NAME--> + +<link href="$relpath^tabs.css" rel="stylesheet" type="text/css"/> + +<meta property="og:locale" content="en_US" /> +<meta property="og:type" content="website" /> +<meta property="og:title" content="$projectbrief - $projectname" /> +<meta property="og:url" content="https://libimobiledevice.org/docs/$projectname/$projectnumber/" /> +<meta property="og:site_name" content="$projectname - $projectbrief" /> +<meta name="twitter:card" content="summary_large_image" /> + +<script type="text/javascript" src="$relpath^jquery.js"></script> +<script type="text/javascript" src="$relpath^dynsections.js"></script> +$treeview +$search +$mathjax +<link href="$relpath^$stylesheet" rel="stylesheet" type="text/css" /> +$extrastylesheet +</head> +<body> +<div id="top"><!-- do not remove this div, it is closed by doxygen! --> + +<!--BEGIN TITLEAREA--> +<div id="titlearea"> +<table cellspacing="0" cellpadding="0"> + <tbody> + <tr style="height: 56px;"> + <!--BEGIN PROJECT_LOGO--> + <td id="projectlogo"><a href="$relpath^index.html"><img alt="Logo" src="$relpath^$projectlogo"/></a></td> + <!--END PROJECT_LOGO--> + <!--BEGIN PROJECT_NAME--> + <td id="projectalign" style="padding-left: 0.5em;"> + <div id="projectname">$projectname + <!--BEGIN PROJECT_NUMBER--> <span id="projectnumber">$projectnumber</span><!--END PROJECT_NUMBER--> + </div> + <!--BEGIN PROJECT_BRIEF--><div id="projectbrief">$projectbrief - <a href="https://libimobiledevice.org"><small>Return to Homepage</small></a></div><!--END PROJECT_BRIEF--> + </td> + <!--END PROJECT_NAME--> + <!--BEGIN !PROJECT_NAME--> + <!--BEGIN PROJECT_BRIEF--> + <td style="padding-left: 0.5em;"> + <div id="projectbrief">$projectbrief</div> + </td> + <!--END PROJECT_BRIEF--> + <!--END !PROJECT_NAME--> + <!--BEGIN DISABLE_INDEX--> + <!--BEGIN SEARCHENGINE--> + <td>$searchbox</td> + <!--END SEARCHENGINE--> + <!--END DISABLE_INDEX--> + </tr> + </tbody> +</table> +</div> +<!--END TITLEAREA--> +<!-- end header part -->
\ No newline at end of file diff --git a/docs/doxygen/layout.xml b/docs/doxygen/layout.xml new file mode 100644 index 0000000..46f068d --- /dev/null +++ b/docs/doxygen/layout.xml @@ -0,0 +1,226 @@ +<doxygenlayout version="1.0"> + <!-- Generated by doxygen 1.8.16 --> + <!-- Navigation index tabs for HTML output --> + <navindex> + <tab type="mainpage" visible="yes" title="About"/> + <tab type="modules" visible="yes" title="" intro=""/> + <tab type="namespaces" visible="yes" title=""> + <tab type="namespacelist" visible="yes" title="" intro=""/> + <tab type="namespacemembers" visible="yes" title="" intro=""/> + </tab> + <tab type="interfaces" visible="yes" title=""> + <tab type="interfacelist" visible="yes" title="" intro=""/> + <tab type="interfaceindex" visible="$ALPHABETICAL_INDEX" title=""/> + <tab type="interfacehierarchy" visible="yes" title="" intro=""/> + </tab> + <tab type="classes" visible="yes" title=""> + <tab type="classlist" visible="yes" title="" intro=""/> + <tab type="classindex" visible="$ALPHABETICAL_INDEX" title=""/> + <tab type="hierarchy" visible="yes" title="" intro=""/> + <tab type="classmembers" visible="yes" title="" intro=""/> + </tab> + <tab type="structs" visible="yes" title=""> + <tab type="structlist" visible="yes" title="" intro=""/> + <tab type="structindex" visible="$ALPHABETICAL_INDEX" title=""/> + </tab> + <tab type="exceptions" visible="yes" title=""> + <tab type="exceptionlist" visible="yes" title="" intro=""/> + <tab type="exceptionindex" visible="$ALPHABETICAL_INDEX" title=""/> + <tab type="exceptionhierarchy" visible="yes" title="" intro=""/> + </tab> + <tab type="files" visible="yes" title=""> + <tab type="filelist" visible="yes" title="" intro=""/> + <tab type="globals" visible="yes" title="" intro=""/> + </tab> + <tab type="examples" visible="yes" title="" intro=""/> + <tab type="pages" visible="yes" title="" intro=""/> + </navindex> + + <!-- Layout definition for a class page --> + <class> + <briefdescription visible="no"/> + <detaileddescription title="Description"/> + <includes visible="$SHOW_INCLUDE_FILES"/> + <inheritancegraph visible="$CLASS_GRAPH"/> + <collaborationgraph visible="$COLLABORATION_GRAPH"/> + <memberdecl> + <nestedclasses visible="yes" title=""/> + <publictypes title=""/> + <services title=""/> + <interfaces title=""/> + <publicslots title=""/> + <signals title=""/> + <publicmethods title=""/> + <publicstaticmethods title=""/> + <publicattributes title=""/> + <publicstaticattributes title=""/> + <protectedtypes title=""/> + <protectedslots title=""/> + <protectedmethods title=""/> + <protectedstaticmethods title=""/> + <protectedattributes title=""/> + <protectedstaticattributes title=""/> + <packagetypes title=""/> + <packagemethods title=""/> + <packagestaticmethods title=""/> + <packageattributes title=""/> + <packagestaticattributes title=""/> + <properties title=""/> + <events title=""/> + <privatetypes title=""/> + <privateslots title=""/> + <privatemethods title=""/> + <privatestaticmethods title=""/> + <privateattributes title=""/> + <privatestaticattributes title=""/> + <friends title=""/> + <related title="" subtitle=""/> + <membergroups visible="yes"/> + </memberdecl> + <memberdef> + <inlineclasses title=""/> + <typedefs title=""/> + <enums title=""/> + <services title=""/> + <interfaces title=""/> + <constructors title=""/> + <functions title=""/> + <related title=""/> + <variables title=""/> + <properties title=""/> + <events title=""/> + </memberdef> + <allmemberslink visible="yes"/> + <usedfiles visible="$SHOW_USED_FILES"/> + <authorsection visible="yes"/> + </class> + + <!-- Layout definition for a namespace page --> + <namespace> + <briefdescription visible="no"/> + <detaileddescription title="Description"/> + <memberdecl> + <nestednamespaces visible="yes" title=""/> + <constantgroups visible="yes" title=""/> + <interfaces visible="yes" title=""/> + <classes visible="yes" title=""/> + <structs visible="yes" title=""/> + <exceptions visible="yes" title=""/> + <typedefs title=""/> + <sequences title=""/> + <dictionaries title=""/> + <enums title=""/> + <functions title=""/> + <variables title=""/> + <membergroups visible="yes"/> + </memberdecl> + <memberdef> + <inlineclasses title=""/> + <typedefs title=""/> + <sequences title=""/> + <dictionaries title=""/> + <enums title=""/> + <functions title=""/> + <variables title=""/> + </memberdef> + <authorsection visible="yes"/> + </namespace> + + <!-- Layout definition for a file page --> + <file> + <briefdescription visible="no"/> + <detaileddescription title="Description"/> + <includes visible="$SHOW_INCLUDE_FILES"/> + <includegraph visible="$INCLUDE_GRAPH"/> + <includedbygraph visible="$INCLUDED_BY_GRAPH"/> + <memberdecl> + <interfaces visible="yes" title=""/> + <classes visible="yes" title=""/> + <structs visible="yes" title=""/> + <exceptions visible="yes" title=""/> + <namespaces visible="yes" title=""/> + <constantgroups visible="yes" title=""/> + <defines title=""/> + <typedefs title=""/> + <sequences title=""/> + <dictionaries title=""/> + <enums title=""/> + <functions title=""/> + <variables title=""/> + <membergroups visible="yes"/> + </memberdecl> + <memberdef> + <inlineclasses title=""/> + <defines title=""/> + <typedefs title=""/> + <sequences title=""/> + <dictionaries title=""/> + <enums title=""/> + <functions title=""/> + <variables title=""/> + </memberdef> + <authorsection/> + <sourcelink visible="yes"/> + </file> + + <!-- Layout definition for a group page --> + <group> + <briefdescription visible="no"/> + <detaileddescription title="Description"/> + <groupgraph visible="$GROUP_GRAPHS"/> + <memberdecl> + <nestedgroups visible="yes" title=""/> + <dirs visible="yes" title=""/> + <files visible="yes" title=""/> + <namespaces visible="yes" title=""/> + <classes visible="yes" title=""/> + <defines title=""/> + <typedefs title=""/> + <sequences title=""/> + <dictionaries title=""/> + <enums title=""/> + <enumvalues title=""/> + <functions title=""/> + <variables title=""/> + <signals title=""/> + <publicslots title=""/> + <protectedslots title=""/> + <privateslots title=""/> + <events title=""/> + <properties title=""/> + <friends title=""/> + <membergroups visible="yes"/> + </memberdecl> + <memberdef> + <pagedocs/> + <inlineclasses title=""/> + <defines title=""/> + <typedefs title=""/> + <sequences title=""/> + <dictionaries title=""/> + <enums title=""/> + <enumvalues title=""/> + <functions title=""/> + <variables title=""/> + <signals title=""/> + <publicslots title=""/> + <protectedslots title=""/> + <privateslots title=""/> + <events title=""/> + <properties title=""/> + <friends title=""/> + </memberdef> + <authorsection visible="yes"/> + </group> + + <!-- Layout definition for a directory page --> + <directory> + <briefdescription visible="no"/> + <detaileddescription title="Description"/> + <directorygraph visible="yes"/> + <memberdecl> + <dirs visible="yes"/> + <files visible="yes"/> + </memberdecl> + </directory> +</doxygenlayout>
\ No newline at end of file diff --git a/docs/doxygen/logo-vector-clean.svg b/docs/doxygen/logo-vector-clean.svg new file mode 100644 index 0000000..a953115 --- /dev/null +++ b/docs/doxygen/logo-vector-clean.svg @@ -0,0 +1 @@ +<svg enable-background="new 0 0 283.5 283.5" height="793.7" viewBox="0 0 793.7 793.7" width="793.7" xmlns="http://www.w3.org/2000/svg"><g stroke-width=".10005" transform="matrix(13.831 0 0 13.831 -1173.2 -291.34)"><path d="m94.968 35.193v-10.71l9.2751 5.355z" fill="#f7ae1a"/><path d="m94.968 35.193 9.2751-5.355v10.71z" fill="#e45e25"/><path d="m94.968 42.414 9.2751 5.355-9.2751 5.355z" fill="#d2343e"/><path d="m94.968 53.124 9.2751-5.355v10.71z" fill="#bb2033"/><path d="m94.968 53.124 9.2751 5.355-9.2751 5.355z" fill="#ab2333"/></g><g fill="#2e3436" fill-opacity=".19964" transform="matrix(13.831 0 0 13.831 -1361.02498 -319.002)"><path d="m57.472 29.867h-55.452l27.726-48.023z" stroke-width=".518" transform="matrix(-.16726 .09657 -.09657 -.16726 121.04 71.121)"/><path d="m148.27 69.213h-10.71l5.355-9.2751z" stroke-width=".10005" transform="matrix(-.86603 .5 .5 .86603 211.62 -57.046)"/><path d="m142.92-59.938h-10.71l5.355-9.2751z" stroke-width=".10005" transform="matrix(-.8660254 .5 -.5 -.8660254 211.614597 -57.0459)"/><path d="m57.472 29.867h-55.452l27.726-48.023z" stroke-width=".518" transform="matrix(0 .19314 -.19314 0 132.86 60.576)"/><path d="m57.472 29.867h-55.452l27.726-48.023z" stroke-width=".518" transform="matrix(.16726 .09657 -.09657 .16726 129.64 55.775)"/><path d="m57.472 29.867h-55.452l27.726-48.023z" transform="matrix(0 .19314 -.19314 0 142.13 55.221)"/></g><g transform="matrix(13.831 0 0 13.831 -1173.2 -291.34)"><g stroke-width=".10005"><path d="m94.968 63.834 9.2751-5.355v10.71z" fill="#7b2f44"/><path d="m104.24 69.189 9.2751-5.355-9.2751-5.355z" fill="#21376d"/><path d="m104.24 58.479 9.2751-5.355v10.71z" fill="#005082"/><path d="m113.52 63.834v-10.71l9.2751 5.355z" fill="#023e49"/><path d="m122.79 58.479-9.2751-5.355 9.2751-5.355z" fill="#007e96"/></g><path d="m122.79 58.479v-10.71l9.2751 5.355z" fill="#0a9197"/></g><g fill="#ccc" transform="translate(2.808 194.51)"><polygon/><polygon/></g></svg>
\ No newline at end of file diff --git a/docs/doxygen/text-x-generic.png b/docs/doxygen/text-x-generic.png Binary files differnew file mode 100644 index 0000000..2d7f2d6 --- /dev/null +++ b/docs/doxygen/text-x-generic.png diff --git a/docs/plistutil.1 b/docs/plistutil.1 new file mode 100644 index 0000000..b2bf7bd --- /dev/null +++ b/docs/plistutil.1 @@ -0,0 +1,135 @@ +.TH "plistutil" 1 +.SH NAME +plistutil \- Convert a plist FILE between binary, XML, JSON, and OpenStep formats +.SH SYNOPSIS +.B plistutil +[OPTIONS] +[-i FILE] +[-o FILE] +.SH DESCRIPTION +plistutil allows converting a Property List file between binary, XML, JSON, and OpenStep formats. +.SH OPTIONS +.TP +.B \-i, \-\-infile FILE +Input FILE to convert from. If this argument is omitted or - is passed as +filename, plistutil will read from stdin. +.TP +.B \-o, \-\-outfile FILE +Output FILE to convert to. If this argument is omitted or - is passed as +filename, plistutil will write to stdout. +.TP +.B \-f, \-\-format [bin|xml|json|openstep] +Force output format, regardless of input type. This is useful if the input +format is not known, but the output format should always be in a specific +format (like xml or json). + +If omitted, XML plist data will be converted to binary and vice-versa. To +convert to/from JSON or OpenStep the output format needs to specified. +.TP +.B \-p, \-\-print FILE +Print PList in human-readable format. +.TP +.B \-n, \-\-nodepath PATH +Restrict output to nodepath defined by +.I PATH +which selects a specific node within the plist document. +A node path describes traversal through dictionaries and arrays using / to +separate the components. +Traversal begins at the root node and proceeds from left to right. +Each component selects a child of the current node. +If the current node is a dictionary, the component is interpreted as a +dictionary key name. +If the current node is an array, the component must be a non-negative decimal +integer specifying the array index (0-based). + +Given the following structure: +.PP +.RS +.nf +<plist version="1.0"> +<dict> + <key>Users</key> + <array> + <dict> + <key>Name</key> + <string>Alice</string> + </dict> + <dict> + <key>Name</key> + <string>Bob</string> + </dict> + </array> +</dict> +</plist> +.fi + +A nodepath of: +.TP +\f[B]Users\f[] resolves to the entire array. +.TP +\f[B]Users/0\f[] resolves to the first dictionary in the array. +.TP +\f[B]Users/0/Name\f[] resolves to the string value "Alice". +.TP +\f[B]Users/1/Name\f[] resolves to the string value "Bob". +.RE + +.TP +.B \-c, \-\-compact +JSON and OpenStep only: Print output in compact form. By default, the output +will be pretty-printed. +.TP +.B \-C, \-\-coerce +JSON and OpenStep only: Coerce non-compatible plist types to JSON/OpenStep +compatible representations. +Date values become ISO 8601 strings, +data values become Base64-encoded strings (JSON), +UID values become integers, +boolean becomes 1 or 0 (OpenStep), +and NULL becomes a string 'NULL' (OpenStep) + +This options is implied when invoked as plist2json. +.TP +.B \-s, \-\-sort +Sort all dictionary nodes lexicographically by key before converting to the output format. +.TP +.B \-h, \-\-help +Prints usage information. +.TP +.B \-d, \-\-debug +Enabled extended debug output. +.TP +.B \-v, \-\-version +Print version information +.SH EXAMPLES +.TP +.B plistutil -i test.plist -o out.plist +Convert test.plist and write to out.plist. If test.plist is in XML format, +out.plist will be in binary format. If test.plist is in binary format, +out.plist will be in XML format. +.TP +.B plistutil -i test.plist -o out.plist -f bin +Same as before, but the output will always be in binary format. +.TP +.B plistutil -i test.plist -f xml +Print test.plist as XML plist, regardless of the input format. +.TP +.B plistutil -i test.plist -f xml -o - +Same as before. +.TP +.B plistutil -i test.plist -f json +Print test.plist as JSON plist, regardless of the input format. +.TP +.B cat test.plist |plistutil -f xml +Take plist data from stdin - piped via cat - and write the output as XML +to stdout. +.SH AUTHORS +Zach C. + +Martin Szulecki + +Nikias Bassen +.SH ON THE WEB +https://libimobiledevice.org + +https://github.com/libimobiledevice/libplist diff --git a/doxygen.cfg b/doxygen.cfg deleted file mode 100644 index a77c3cd..0000000 --- a/doxygen.cfg +++ /dev/null @@ -1,1418 +0,0 @@ -# Doxyfile 1.5.6 - -# This file describes the settings to be used by the documentation system -# doxygen (www.doxygen.org) for a project -# -# All text after a hash (#) is considered a comment and will be ignored -# The format is: -# TAG = value [value, ...] -# For lists items can also be appended using: -# TAG += value [value, ...] -# Values that contain spaces should be placed between quotes (" ") - -#--------------------------------------------------------------------------- -# Project related configuration options -#--------------------------------------------------------------------------- - -# This tag specifies the encoding used for all characters in the config file -# that follow. The default is UTF-8 which is also the encoding used for all -# text before the first occurrence of this tag. Doxygen uses libiconv (or the -# iconv built into libc) for the transcoding. See -# http://www.gnu.org/software/libiconv for the list of possible encodings. - -DOXYFILE_ENCODING = UTF-8 - -# The PROJECT_NAME tag is a single word (or a sequence of words surrounded -# by quotes) that should identify the project. - -PROJECT_NAME = libplist - -# The PROJECT_NUMBER tag can be used to enter a project or revision number. -# This could be handy for archiving the generated documentation or -# if some version control system is used. - -PROJECT_NUMBER = 1.8 - -# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) -# base path where the generated documentation will be put. -# If a relative path is entered, it will be relative to the location -# where doxygen was started. If left blank the current directory will be used. - -OUTPUT_DIRECTORY = docs - -# If the CREATE_SUBDIRS tag is set to YES, then doxygen will create -# 4096 sub-directories (in 2 levels) under the output directory of each output -# format and will distribute the generated files over these directories. -# Enabling this option can be useful when feeding doxygen a huge amount of -# source files, where putting all generated files in the same directory would -# otherwise cause performance problems for the file system. - -CREATE_SUBDIRS = NO - -# The OUTPUT_LANGUAGE tag is used to specify the language in which all -# documentation generated by doxygen is written. Doxygen will use this -# information to generate all constant output in the proper language. -# The default language is English, other supported languages are: -# Afrikaans, Arabic, Brazilian, Catalan, Chinese, Chinese-Traditional, -# Croatian, Czech, Danish, Dutch, Farsi, Finnish, French, German, Greek, -# Hungarian, Italian, Japanese, Japanese-en (Japanese with English messages), -# Korean, Korean-en, Lithuanian, Norwegian, Macedonian, Persian, Polish, -# Portuguese, Romanian, Russian, Serbian, Slovak, Slovene, Spanish, Swedish, -# and Ukrainian. - -OUTPUT_LANGUAGE = English - -# If the BRIEF_MEMBER_DESC tag is set to YES (the default) Doxygen will -# include brief member descriptions after the members that are listed in -# the file and class documentation (similar to JavaDoc). -# Set to NO to disable this. - -BRIEF_MEMBER_DESC = YES - -# If the REPEAT_BRIEF tag is set to YES (the default) Doxygen will prepend -# the brief description of a member or function before the detailed description. -# Note: if both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the -# brief descriptions will be completely suppressed. - -REPEAT_BRIEF = YES - -# This tag implements a quasi-intelligent brief description abbreviator -# that is used to form the text in various listings. Each string -# in this list, if found as the leading text of the brief description, will be -# stripped from the text and the result after processing the whole list, is -# used as the annotated text. Otherwise, the brief description is used as-is. -# If left blank, the following values are used ("$name" is automatically -# replaced with the name of the entity): "The $name class" "The $name widget" -# "The $name file" "is" "provides" "specifies" "contains" -# "represents" "a" "an" "the" - -ABBREVIATE_BRIEF = - -# If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then -# Doxygen will generate a detailed section even if there is only a brief -# description. - -ALWAYS_DETAILED_SEC = NO - -# If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all -# inherited members of a class in the documentation of that class as if those -# members were ordinary class members. Constructors, destructors and assignment -# operators of the base classes will not be shown. - -INLINE_INHERITED_MEMB = NO - -# If the FULL_PATH_NAMES tag is set to YES then Doxygen will prepend the full -# path before files name in the file list and in the header files. If set -# to NO the shortest path that makes the file name unique will be used. - -FULL_PATH_NAMES = YES - -# If the FULL_PATH_NAMES tag is set to YES then the STRIP_FROM_PATH tag -# can be used to strip a user-defined part of the path. Stripping is -# only done if one of the specified strings matches the left-hand part of -# the path. The tag can be used to show relative paths in the file list. -# If left blank the directory from which doxygen is run is used as the -# path to strip. - -STRIP_FROM_PATH = include - -# The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of -# the path mentioned in the documentation of a class, which tells -# the reader which header file to include in order to use a class. -# If left blank only the name of the header file containing the class -# definition is used. Otherwise one should specify the include paths that -# are normally passed to the compiler using the -I flag. - -STRIP_FROM_INC_PATH = - -# If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter -# (but less readable) file names. This can be useful is your file systems -# doesn't support long names like on DOS, Mac, or CD-ROM. - -SHORT_NAMES = NO - -# If the JAVADOC_AUTOBRIEF tag is set to YES then Doxygen -# will interpret the first line (until the first dot) of a JavaDoc-style -# comment as the brief description. If set to NO, the JavaDoc -# comments will behave just like regular Qt-style comments -# (thus requiring an explicit @brief command for a brief description.) - -JAVADOC_AUTOBRIEF = YES - -# If the QT_AUTOBRIEF tag is set to YES then Doxygen will -# interpret the first line (until the first dot) of a Qt-style -# comment as the brief description. If set to NO, the comments -# will behave just like regular Qt-style comments (thus requiring -# an explicit \brief command for a brief description.) - -QT_AUTOBRIEF = NO - -# The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make Doxygen -# treat a multi-line C++ special comment block (i.e. a block of //! or /// -# comments) as a brief description. This used to be the default behaviour. -# The new default is to treat a multi-line C++ comment block as a detailed -# description. Set this tag to YES if you prefer the old behaviour instead. - -MULTILINE_CPP_IS_BRIEF = NO - -# If the DETAILS_AT_TOP tag is set to YES then Doxygen -# will output the detailed description near the top, like JavaDoc. -# If set to NO, the detailed description appears after the member -# documentation. - -DETAILS_AT_TOP = NO - -# If the INHERIT_DOCS tag is set to YES (the default) then an undocumented -# member inherits the documentation from any documented member that it -# re-implements. - -INHERIT_DOCS = YES - -# If the SEPARATE_MEMBER_PAGES tag is set to YES, then doxygen will produce -# a new page for each member. If set to NO, the documentation of a member will -# be part of the file/class/namespace that contains it. - -SEPARATE_MEMBER_PAGES = NO - -# The TAB_SIZE tag can be used to set the number of spaces in a tab. -# Doxygen uses this value to replace tabs by spaces in code fragments. - -TAB_SIZE = 8 - -# This tag can be used to specify a number of aliases that acts -# as commands in the documentation. An alias has the form "name=value". -# For example adding "sideeffect=\par Side Effects:\n" will allow you to -# put the command \sideeffect (or @sideeffect) in the documentation, which -# will result in a user-defined paragraph with heading "Side Effects:". -# You can put \n's in the value part of an alias to insert newlines. - -ALIASES = - -# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C -# sources only. Doxygen will then generate output that is more tailored for C. -# For instance, some of the names that are used will be different. The list -# of all members will be omitted, etc. - -OPTIMIZE_OUTPUT_FOR_C = YES - -# Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java -# sources only. Doxygen will then generate output that is more tailored for -# Java. For instance, namespaces will be presented as packages, qualified -# scopes will look different, etc. - -OPTIMIZE_OUTPUT_JAVA = NO - -# Set the OPTIMIZE_FOR_FORTRAN tag to YES if your project consists of Fortran -# sources only. Doxygen will then generate output that is more tailored for -# Fortran. - -OPTIMIZE_FOR_FORTRAN = NO - -# Set the OPTIMIZE_OUTPUT_VHDL tag to YES if your project consists of VHDL -# sources. Doxygen will then generate output that is tailored for -# VHDL. - -OPTIMIZE_OUTPUT_VHDL = NO - -# If you use STL classes (i.e. std::string, std::vector, etc.) but do not want -# to include (a tag file for) the STL sources as input, then you should -# set this tag to YES in order to let doxygen match functions declarations and -# definitions whose arguments contain STL classes (e.g. func(std::string); v.s. -# func(std::string) {}). This also make the inheritance and collaboration -# diagrams that involve STL classes more complete and accurate. - -BUILTIN_STL_SUPPORT = NO - -# If you use Microsoft's C++/CLI language, you should set this option to YES to -# enable parsing support. - -CPP_CLI_SUPPORT = NO - -# Set the SIP_SUPPORT tag to YES if your project consists of sip sources only. -# Doxygen will parse them like normal C++ but will assume all classes use public -# instead of private inheritance when no explicit protection keyword is present. - -SIP_SUPPORT = NO - -# For Microsoft's IDL there are propget and propput attributes to indicate getter -# and setter methods for a property. Setting this option to YES (the default) -# will make doxygen to replace the get and set methods by a property in the -# documentation. This will only work if the methods are indeed getting or -# setting a simple type. If this is not the case, or you want to show the -# methods anyway, you should set this option to NO. - -IDL_PROPERTY_SUPPORT = YES - -# If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC -# tag is set to YES, then doxygen will reuse the documentation of the first -# member in the group (if any) for the other members of the group. By default -# all members of a group must be documented explicitly. - -DISTRIBUTE_GROUP_DOC = NO - -# Set the SUBGROUPING tag to YES (the default) to allow class member groups of -# the same type (for instance a group of public functions) to be put as a -# subgroup of that type (e.g. under the Public Functions section). Set it to -# NO to prevent subgrouping. Alternatively, this can be done per class using -# the \nosubgrouping command. - -SUBGROUPING = NO - -# When TYPEDEF_HIDES_STRUCT is enabled, a typedef of a struct, union, or enum -# is documented as struct, union, or enum with the name of the typedef. So -# typedef struct TypeS {} TypeT, will appear in the documentation as a struct -# with name TypeT. When disabled the typedef will appear as a member of a file, -# namespace, or class. And the struct will be named TypeS. This can typically -# be useful for C code in case the coding convention dictates that all compound -# types are typedef'ed and only the typedef is referenced, never the tag name. - -TYPEDEF_HIDES_STRUCT = YES - -#--------------------------------------------------------------------------- -# Build related configuration options -#--------------------------------------------------------------------------- - -# If the EXTRACT_ALL tag is set to YES doxygen will assume all entities in -# documentation are documented, even if no documentation was available. -# Private class members and static file members will be hidden unless -# the EXTRACT_PRIVATE and EXTRACT_STATIC tags are set to YES - -EXTRACT_ALL = NO - -# If the EXTRACT_PRIVATE tag is set to YES all private members of a class -# will be included in the documentation. - -EXTRACT_PRIVATE = NO - -# If the EXTRACT_STATIC tag is set to YES all static members of a file -# will be included in the documentation. - -EXTRACT_STATIC = NO - -# If the EXTRACT_LOCAL_CLASSES tag is set to YES classes (and structs) -# defined locally in source files will be included in the documentation. -# If set to NO only classes defined in header files are included. - -EXTRACT_LOCAL_CLASSES = YES - -# This flag is only useful for Objective-C code. When set to YES local -# methods, which are defined in the implementation section but not in -# the interface are included in the documentation. -# If set to NO (the default) only methods in the interface are included. - -EXTRACT_LOCAL_METHODS = NO - -# If this flag is set to YES, the members of anonymous namespaces will be -# extracted and appear in the documentation as a namespace called -# 'anonymous_namespace{file}', where file will be replaced with the base -# name of the file that contains the anonymous namespace. By default -# anonymous namespace are hidden. - -EXTRACT_ANON_NSPACES = NO - -# If the HIDE_UNDOC_MEMBERS tag is set to YES, Doxygen will hide all -# undocumented members of documented classes, files or namespaces. -# If set to NO (the default) these members will be included in the -# various overviews, but no documentation section is generated. -# This option has no effect if EXTRACT_ALL is enabled. - -HIDE_UNDOC_MEMBERS = NO - -# If the HIDE_UNDOC_CLASSES tag is set to YES, Doxygen will hide all -# undocumented classes that are normally visible in the class hierarchy. -# If set to NO (the default) these classes will be included in the various -# overviews. This option has no effect if EXTRACT_ALL is enabled. - -HIDE_UNDOC_CLASSES = NO - -# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, Doxygen will hide all -# friend (class|struct|union) declarations. -# If set to NO (the default) these declarations will be included in the -# documentation. - -HIDE_FRIEND_COMPOUNDS = NO - -# If the HIDE_IN_BODY_DOCS tag is set to YES, Doxygen will hide any -# documentation blocks found inside the body of a function. -# If set to NO (the default) these blocks will be appended to the -# function's detailed documentation block. - -HIDE_IN_BODY_DOCS = NO - -# The INTERNAL_DOCS tag determines if documentation -# that is typed after a \internal command is included. If the tag is set -# to NO (the default) then the documentation will be excluded. -# Set it to YES to include the internal documentation. - -INTERNAL_DOCS = NO - -# If the CASE_SENSE_NAMES tag is set to NO then Doxygen will only generate -# file names in lower-case letters. If set to YES upper-case letters are also -# allowed. This is useful if you have classes or files whose names only differ -# in case and if your file system supports case sensitive file names. Windows -# and Mac users are advised to set this option to NO. - -CASE_SENSE_NAMES = YES - -# If the HIDE_SCOPE_NAMES tag is set to NO (the default) then Doxygen -# will show members with their full class and namespace scopes in the -# documentation. If set to YES the scope will be hidden. - -HIDE_SCOPE_NAMES = NO - -# If the SHOW_INCLUDE_FILES tag is set to YES (the default) then Doxygen -# will put a list of the files that are included by a file in the documentation -# of that file. - -SHOW_INCLUDE_FILES = YES - -# If the INLINE_INFO tag is set to YES (the default) then a tag [inline] -# is inserted in the documentation for inline members. - -INLINE_INFO = YES - -# If the SORT_MEMBER_DOCS tag is set to YES (the default) then doxygen -# will sort the (detailed) documentation of file and class members -# alphabetically by member name. If set to NO the members will appear in -# declaration order. - -SORT_MEMBER_DOCS = YES - -# If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the -# brief documentation of file, namespace and class members alphabetically -# by member name. If set to NO (the default) the members will appear in -# declaration order. - -SORT_BRIEF_DOCS = NO - -# If the SORT_GROUP_NAMES tag is set to YES then doxygen will sort the -# hierarchy of group names into alphabetical order. If set to NO (the default) -# the group names will appear in their defined order. - -SORT_GROUP_NAMES = NO - -# If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be -# sorted by fully-qualified names, including namespaces. If set to -# NO (the default), the class list will be sorted only by class name, -# not including the namespace part. -# Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES. -# Note: This option applies only to the class list, not to the -# alphabetical list. - -SORT_BY_SCOPE_NAME = NO - -# The GENERATE_TODOLIST tag can be used to enable (YES) or -# disable (NO) the todo list. This list is created by putting \todo -# commands in the documentation. - -GENERATE_TODOLIST = YES - -# The GENERATE_TESTLIST tag can be used to enable (YES) or -# disable (NO) the test list. This list is created by putting \test -# commands in the documentation. - -GENERATE_TESTLIST = YES - -# The GENERATE_BUGLIST tag can be used to enable (YES) or -# disable (NO) the bug list. This list is created by putting \bug -# commands in the documentation. - -GENERATE_BUGLIST = YES - -# The GENERATE_DEPRECATEDLIST tag can be used to enable (YES) or -# disable (NO) the deprecated list. This list is created by putting -# \deprecated commands in the documentation. - -GENERATE_DEPRECATEDLIST= YES - -# The ENABLED_SECTIONS tag can be used to enable conditional -# documentation sections, marked by \if sectionname ... \endif. - -ENABLED_SECTIONS = - -# The MAX_INITIALIZER_LINES tag determines the maximum number of lines -# the initial value of a variable or define consists of for it to appear in -# the documentation. If the initializer consists of more lines than specified -# here it will be hidden. Use a value of 0 to hide initializers completely. -# The appearance of the initializer of individual variables and defines in the -# documentation can be controlled using \showinitializer or \hideinitializer -# command in the documentation regardless of this setting. - -MAX_INITIALIZER_LINES = 30 - -# Set the SHOW_USED_FILES tag to NO to disable the list of files generated -# at the bottom of the documentation of classes and structs. If set to YES the -# list will mention the files that were used to generate the documentation. - -SHOW_USED_FILES = YES - -# If the sources in your project are distributed over multiple directories -# then setting the SHOW_DIRECTORIES tag to YES will show the directory hierarchy -# in the documentation. The default is NO. - -SHOW_DIRECTORIES = NO - -# Set the SHOW_FILES tag to NO to disable the generation of the Files page. -# This will remove the Files entry from the Quick Index and from the -# Folder Tree View (if specified). The default is YES. - -SHOW_FILES = YES - -# Set the SHOW_NAMESPACES tag to NO to disable the generation of the -# Namespaces page. This will remove the Namespaces entry from the Quick Index -# and from the Folder Tree View (if specified). The default is YES. - -SHOW_NAMESPACES = YES - -# The FILE_VERSION_FILTER tag can be used to specify a program or script that -# doxygen should invoke to get the current version for each file (typically from -# the version control system). Doxygen will invoke the program by executing (via -# popen()) the command <command> <input-file>, where <command> is the value of -# the FILE_VERSION_FILTER tag, and <input-file> is the name of an input file -# provided by doxygen. Whatever the program writes to standard output -# is used as the file version. See the manual for examples. - -FILE_VERSION_FILTER = - -#--------------------------------------------------------------------------- -# configuration options related to warning and progress messages -#--------------------------------------------------------------------------- - -# The QUIET tag can be used to turn on/off the messages that are generated -# by doxygen. Possible values are YES and NO. If left blank NO is used. - -QUIET = NO - -# The WARNINGS tag can be used to turn on/off the warning messages that are -# generated by doxygen. Possible values are YES and NO. If left blank -# NO is used. - -WARNINGS = YES - -# If WARN_IF_UNDOCUMENTED is set to YES, then doxygen will generate warnings -# for undocumented members. If EXTRACT_ALL is set to YES then this flag will -# automatically be disabled. - -WARN_IF_UNDOCUMENTED = YES - -# If WARN_IF_DOC_ERROR is set to YES, doxygen will generate warnings for -# potential errors in the documentation, such as not documenting some -# parameters in a documented function, or documenting parameters that -# don't exist or using markup commands wrongly. - -WARN_IF_DOC_ERROR = YES - -# This WARN_NO_PARAMDOC option can be abled to get warnings for -# functions that are documented, but have no documentation for their parameters -# or return value. If set to NO (the default) doxygen will only warn about -# wrong or incomplete parameter documentation, but not about the absence of -# documentation. - -WARN_NO_PARAMDOC = NO - -# The WARN_FORMAT tag determines the format of the warning messages that -# doxygen can produce. The string should contain the $file, $line, and $text -# tags, which will be replaced by the file and line number from which the -# warning originated and the warning text. Optionally the format may contain -# $version, which will be replaced by the version of the file (if it could -# be obtained via FILE_VERSION_FILTER) - -WARN_FORMAT = "$file:$line: $text" - -# The WARN_LOGFILE tag can be used to specify a file to which warning -# and error messages should be written. If left blank the output is written -# to stderr. - -WARN_LOGFILE = - -#--------------------------------------------------------------------------- -# configuration options related to the input files -#--------------------------------------------------------------------------- - -# The INPUT tag can be used to specify the files and/or directories that contain -# documented source files. You may enter file names like "myfile.cpp" or -# directories like "/usr/src/myproject". Separate the files or directories -# with spaces. - -INPUT = src \ - include/plist - -# This tag can be used to specify the character encoding of the source files -# that doxygen parses. Internally doxygen uses the UTF-8 encoding, which is -# also the default input encoding. Doxygen uses libiconv (or the iconv built -# into libc) for the transcoding. See http://www.gnu.org/software/libiconv for -# the list of possible encodings. - -INPUT_ENCODING = UTF-8 - -# If the value of the INPUT tag contains directories, you can use the -# FILE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp -# and *.h) to filter out the source-files in the directories. If left -# blank the following patterns are tested: -# *.c *.cc *.cxx *.cpp *.c++ *.java *.ii *.ixx *.ipp *.i++ *.inl *.h *.hh *.hxx -# *.hpp *.h++ *.idl *.odl *.cs *.php *.php3 *.inc *.m *.mm *.py *.f90 - -FILE_PATTERNS = - -# The RECURSIVE tag can be used to turn specify whether or not subdirectories -# should be searched for input files as well. Possible values are YES and NO. -# If left blank NO is used. - -RECURSIVE = NO - -# The EXCLUDE tag can be used to specify files and/or directories that should -# excluded from the INPUT source files. This way you can easily exclude a -# subdirectory from a directory tree whose root is specified with the INPUT tag. - -EXCLUDE = dev - -# The EXCLUDE_SYMLINKS tag can be used select whether or not files or -# directories that are symbolic links (a Unix filesystem feature) are excluded -# from the input. - -EXCLUDE_SYMLINKS = NO - -# If the value of the INPUT tag contains directories, you can use the -# EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude -# certain files from those directories. Note that the wildcards are matched -# against the file with absolute path, so to exclude all test directories -# for example use the pattern */test/* - -EXCLUDE_PATTERNS = - -# The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names -# (namespaces, classes, functions, etc.) that should be excluded from the -# output. The symbol name can be a fully qualified name, a word, or if the -# wildcard * is used, a substring. Examples: ANamespace, AClass, -# AClass::ANamespace, ANamespace::*Test - -EXCLUDE_SYMBOLS = - -# The EXAMPLE_PATH tag can be used to specify one or more files or -# directories that contain example code fragments that are included (see -# the \include command). - -EXAMPLE_PATH = - -# If the value of the EXAMPLE_PATH tag contains directories, you can use the -# EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp -# and *.h) to filter out the source-files in the directories. If left -# blank all files are included. - -EXAMPLE_PATTERNS = - -# If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be -# searched for input files to be used with the \include or \dontinclude -# commands irrespective of the value of the RECURSIVE tag. -# Possible values are YES and NO. If left blank NO is used. - -EXAMPLE_RECURSIVE = NO - -# The IMAGE_PATH tag can be used to specify one or more files or -# directories that contain image that are included in the documentation (see -# the \image command). - -IMAGE_PATH = - -# The INPUT_FILTER tag can be used to specify a program that doxygen should -# invoke to filter for each input file. Doxygen will invoke the filter program -# by executing (via popen()) the command <filter> <input-file>, where <filter> -# is the value of the INPUT_FILTER tag, and <input-file> is the name of an -# input file. Doxygen will then use the output that the filter program writes -# to standard output. If FILTER_PATTERNS is specified, this tag will be -# ignored. - -INPUT_FILTER = - -# The FILTER_PATTERNS tag can be used to specify filters on a per file pattern -# basis. Doxygen will compare the file name with each pattern and apply the -# filter if there is a match. The filters are a list of the form: -# pattern=filter (like *.cpp=my_cpp_filter). See INPUT_FILTER for further -# info on how filters are used. If FILTER_PATTERNS is empty, INPUT_FILTER -# is applied to all files. - -FILTER_PATTERNS = - -# If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using -# INPUT_FILTER) will be used to filter the input files when producing source -# files to browse (i.e. when SOURCE_BROWSER is set to YES). - -FILTER_SOURCE_FILES = NO - -#--------------------------------------------------------------------------- -# configuration options related to source browsing -#--------------------------------------------------------------------------- - -# If the SOURCE_BROWSER tag is set to YES then a list of source files will -# be generated. Documented entities will be cross-referenced with these sources. -# Note: To get rid of all source code in the generated output, make sure also -# VERBATIM_HEADERS is set to NO. - -SOURCE_BROWSER = NO - -# Setting the INLINE_SOURCES tag to YES will include the body -# of functions and classes directly in the documentation. - -INLINE_SOURCES = NO - -# Setting the STRIP_CODE_COMMENTS tag to YES (the default) will instruct -# doxygen to hide any special comment blocks from generated source code -# fragments. Normal C and C++ comments will always remain visible. - -STRIP_CODE_COMMENTS = YES - -# If the REFERENCED_BY_RELATION tag is set to YES -# then for each documented function all documented -# functions referencing it will be listed. - -REFERENCED_BY_RELATION = YES - -# If the REFERENCES_RELATION tag is set to YES -# then for each documented function all documented entities -# called/used by that function will be listed. - -REFERENCES_RELATION = YES - -# If the REFERENCES_LINK_SOURCE tag is set to YES (the default) -# and SOURCE_BROWSER tag is set to YES, then the hyperlinks from -# functions in REFERENCES_RELATION and REFERENCED_BY_RELATION lists will -# link to the source code. Otherwise they will link to the documentstion. - -REFERENCES_LINK_SOURCE = YES - -# If the USE_HTAGS tag is set to YES then the references to source code -# will point to the HTML generated by the htags(1) tool instead of doxygen -# built-in source browser. The htags tool is part of GNU's global source -# tagging system (see http://www.gnu.org/software/global/global.html). You -# will need version 4.8.6 or higher. - -USE_HTAGS = NO - -# If the VERBATIM_HEADERS tag is set to YES (the default) then Doxygen -# will generate a verbatim copy of the header file for each class for -# which an include is specified. Set to NO to disable this. - -VERBATIM_HEADERS = NO - -#--------------------------------------------------------------------------- -# configuration options related to the alphabetical class index -#--------------------------------------------------------------------------- - -# If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index -# of all compounds will be generated. Enable this if the project -# contains a lot of classes, structs, unions or interfaces. - -ALPHABETICAL_INDEX = NO - -# If the alphabetical index is enabled (see ALPHABETICAL_INDEX) then -# the COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns -# in which this list will be split (can be a number in the range [1..20]) - -COLS_IN_ALPHA_INDEX = 5 - -# In case all classes in a project start with a common prefix, all -# classes will be put under the same header in the alphabetical index. -# The IGNORE_PREFIX tag can be used to specify one or more prefixes that -# should be ignored while generating the index headers. - -IGNORE_PREFIX = - -#--------------------------------------------------------------------------- -# configuration options related to the HTML output -#--------------------------------------------------------------------------- - -# If the GENERATE_HTML tag is set to YES (the default) Doxygen will -# generate HTML output. - -GENERATE_HTML = YES - -# The HTML_OUTPUT tag is used to specify where the HTML docs will be put. -# If a relative path is entered the value of OUTPUT_DIRECTORY will be -# put in front of it. If left blank `html' will be used as the default path. - -HTML_OUTPUT = html - -# The HTML_FILE_EXTENSION tag can be used to specify the file extension for -# each generated HTML page (for example: .htm,.php,.asp). If it is left blank -# doxygen will generate files with .html extension. - -HTML_FILE_EXTENSION = .html - -# The HTML_HEADER tag can be used to specify a personal HTML header for -# each generated HTML page. If it is left blank doxygen will generate a -# standard header. - -HTML_HEADER = - -# The HTML_FOOTER tag can be used to specify a personal HTML footer for -# each generated HTML page. If it is left blank doxygen will generate a -# standard footer. - -HTML_FOOTER = - -# The HTML_STYLESHEET tag can be used to specify a user-defined cascading -# style sheet that is used by each HTML page. It can be used to -# fine-tune the look of the HTML output. If the tag is left blank doxygen -# will generate a default style sheet. Note that doxygen will try to copy -# the style sheet file to the HTML output directory, so don't put your own -# stylesheet in the HTML output directory as well, or it will be erased! - -HTML_STYLESHEET = - -# If the HTML_ALIGN_MEMBERS tag is set to YES, the members of classes, -# files or namespaces will be aligned in HTML using tables. If set to -# NO a bullet list will be used. - -HTML_ALIGN_MEMBERS = YES - -# If the GENERATE_HTMLHELP tag is set to YES, additional index files -# will be generated that can be used as input for tools like the -# Microsoft HTML help workshop to generate a compiled HTML help file (.chm) -# of the generated HTML documentation. - -GENERATE_HTMLHELP = NO - -# If the GENERATE_DOCSET tag is set to YES, additional index files -# will be generated that can be used as input for Apple's Xcode 3 -# integrated development environment, introduced with OSX 10.5 (Leopard). -# To create a documentation set, doxygen will generate a Makefile in the -# HTML output directory. Running make will produce the docset in that -# directory and running "make install" will install the docset in -# ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find -# it at startup. - -GENERATE_DOCSET = NO - -# When GENERATE_DOCSET tag is set to YES, this tag determines the name of the -# feed. A documentation feed provides an umbrella under which multiple -# documentation sets from a single provider (such as a company or product suite) -# can be grouped. - -DOCSET_FEEDNAME = "Doxygen generated docs" - -# When GENERATE_DOCSET tag is set to YES, this tag specifies a string that -# should uniquely identify the documentation set bundle. This should be a -# reverse domain-name style string, e.g. com.mycompany.MyDocSet. Doxygen -# will append .docset to the name. - -DOCSET_BUNDLE_ID = org.doxygen.Project - -# If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML -# documentation will contain sections that can be hidden and shown after the -# page has loaded. For this to work a browser that supports -# JavaScript and DHTML is required (for instance Mozilla 1.0+, Firefox -# Netscape 6.0+, Internet explorer 5.0+, Konqueror, or Safari). - -HTML_DYNAMIC_SECTIONS = NO - -# If the GENERATE_HTMLHELP tag is set to YES, the CHM_FILE tag can -# be used to specify the file name of the resulting .chm file. You -# can add a path in front of the file if the result should not be -# written to the html output directory. - -CHM_FILE = - -# If the GENERATE_HTMLHELP tag is set to YES, the HHC_LOCATION tag can -# be used to specify the location (absolute path including file name) of -# the HTML help compiler (hhc.exe). If non-empty doxygen will try to run -# the HTML help compiler on the generated index.hhp. - -HHC_LOCATION = - -# If the GENERATE_HTMLHELP tag is set to YES, the GENERATE_CHI flag -# controls if a separate .chi index file is generated (YES) or that -# it should be included in the master .chm file (NO). - -GENERATE_CHI = NO - -# If the GENERATE_HTMLHELP tag is set to YES, the CHM_INDEX_ENCODING -# is used to encode HtmlHelp index (hhk), content (hhc) and project file -# content. - -CHM_INDEX_ENCODING = - -# If the GENERATE_HTMLHELP tag is set to YES, the BINARY_TOC flag -# controls whether a binary table of contents is generated (YES) or a -# normal table of contents (NO) in the .chm file. - -BINARY_TOC = NO - -# The TOC_EXPAND flag can be set to YES to add extra items for group members -# to the contents of the HTML help documentation and to the tree view. - -TOC_EXPAND = NO - -# The DISABLE_INDEX tag can be used to turn on/off the condensed index at -# top of each HTML page. The value NO (the default) enables the index and -# the value YES disables it. - -DISABLE_INDEX = NO - -# This tag can be used to set the number of enum values (range [1..20]) -# that doxygen will group on one line in the generated HTML documentation. - -ENUM_VALUES_PER_LINE = 4 - -# The GENERATE_TREEVIEW tag is used to specify whether a tree-like index -# structure should be generated to display hierarchical information. -# If the tag value is set to FRAME, a side panel will be generated -# containing a tree-like index structure (just like the one that -# is generated for HTML Help). For this to work a browser that supports -# JavaScript, DHTML, CSS and frames is required (for instance Mozilla 1.0+, -# Netscape 6.0+, Internet explorer 5.0+, or Konqueror). Windows users are -# probably better off using the HTML help feature. Other possible values -# for this tag are: HIERARCHIES, which will generate the Groups, Directories, -# and Class Hiererachy pages using a tree view instead of an ordered list; -# ALL, which combines the behavior of FRAME and HIERARCHIES; and NONE, which -# disables this behavior completely. For backwards compatibility with previous -# releases of Doxygen, the values YES and NO are equivalent to FRAME and NONE -# respectively. - -GENERATE_TREEVIEW = NO - -# If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be -# used to set the initial width (in pixels) of the frame in which the tree -# is shown. - -TREEVIEW_WIDTH = 250 - -# Use this tag to change the font size of Latex formulas included -# as images in the HTML documentation. The default is 10. Note that -# when you change the font size after a successful doxygen run you need -# to manually remove any form_*.png images from the HTML output directory -# to force them to be regenerated. - -FORMULA_FONTSIZE = 10 - -#--------------------------------------------------------------------------- -# configuration options related to the LaTeX output -#--------------------------------------------------------------------------- - -# If the GENERATE_LATEX tag is set to YES (the default) Doxygen will -# generate Latex output. - -GENERATE_LATEX = NO - -# The LATEX_OUTPUT tag is used to specify where the LaTeX docs will be put. -# If a relative path is entered the value of OUTPUT_DIRECTORY will be -# put in front of it. If left blank `latex' will be used as the default path. - -LATEX_OUTPUT = latex - -# The LATEX_CMD_NAME tag can be used to specify the LaTeX command name to be -# invoked. If left blank `latex' will be used as the default command name. - -LATEX_CMD_NAME = latex - -# The MAKEINDEX_CMD_NAME tag can be used to specify the command name to -# generate index for LaTeX. If left blank `makeindex' will be used as the -# default command name. - -MAKEINDEX_CMD_NAME = makeindex - -# If the COMPACT_LATEX tag is set to YES Doxygen generates more compact -# LaTeX documents. This may be useful for small projects and may help to -# save some trees in general. - -COMPACT_LATEX = NO - -# The PAPER_TYPE tag can be used to set the paper type that is used -# by the printer. Possible values are: a4, a4wide, letter, legal and -# executive. If left blank a4wide will be used. - -PAPER_TYPE = a4wide - -# The EXTRA_PACKAGES tag can be to specify one or more names of LaTeX -# packages that should be included in the LaTeX output. - -EXTRA_PACKAGES = - -# The LATEX_HEADER tag can be used to specify a personal LaTeX header for -# the generated latex document. The header should contain everything until -# the first chapter. If it is left blank doxygen will generate a -# standard header. Notice: only use this tag if you know what you are doing! - -LATEX_HEADER = - -# If the PDF_HYPERLINKS tag is set to YES, the LaTeX that is generated -# is prepared for conversion to pdf (using ps2pdf). The pdf file will -# contain links (just like the HTML output) instead of page references -# This makes the output suitable for online browsing using a pdf viewer. - -PDF_HYPERLINKS = NO - -# If the USE_PDFLATEX tag is set to YES, pdflatex will be used instead of -# plain latex in the generated Makefile. Set this option to YES to get a -# higher quality PDF documentation. - -USE_PDFLATEX = NO - -# If the LATEX_BATCHMODE tag is set to YES, doxygen will add the \\batchmode. -# command to the generated LaTeX files. This will instruct LaTeX to keep -# running if errors occur, instead of asking the user for help. -# This option is also used when generating formulas in HTML. - -LATEX_BATCHMODE = NO - -# If LATEX_HIDE_INDICES is set to YES then doxygen will not -# include the index chapters (such as File Index, Compound Index, etc.) -# in the output. - -LATEX_HIDE_INDICES = NO - -#--------------------------------------------------------------------------- -# configuration options related to the RTF output -#--------------------------------------------------------------------------- - -# If the GENERATE_RTF tag is set to YES Doxygen will generate RTF output -# The RTF output is optimized for Word 97 and may not look very pretty with -# other RTF readers or editors. - -GENERATE_RTF = NO - -# The RTF_OUTPUT tag is used to specify where the RTF docs will be put. -# If a relative path is entered the value of OUTPUT_DIRECTORY will be -# put in front of it. If left blank `rtf' will be used as the default path. - -RTF_OUTPUT = rtf - -# If the COMPACT_RTF tag is set to YES Doxygen generates more compact -# RTF documents. This may be useful for small projects and may help to -# save some trees in general. - -COMPACT_RTF = NO - -# If the RTF_HYPERLINKS tag is set to YES, the RTF that is generated -# will contain hyperlink fields. The RTF file will -# contain links (just like the HTML output) instead of page references. -# This makes the output suitable for online browsing using WORD or other -# programs which support those fields. -# Note: wordpad (write) and others do not support links. - -RTF_HYPERLINKS = NO - -# Load stylesheet definitions from file. Syntax is similar to doxygen's -# config file, i.e. a series of assignments. You only have to provide -# replacements, missing definitions are set to their default value. - -RTF_STYLESHEET_FILE = - -# Set optional variables used in the generation of an rtf document. -# Syntax is similar to doxygen's config file. - -RTF_EXTENSIONS_FILE = - -#--------------------------------------------------------------------------- -# configuration options related to the man page output -#--------------------------------------------------------------------------- - -# If the GENERATE_MAN tag is set to YES (the default) Doxygen will -# generate man pages - -GENERATE_MAN = NO - -# The MAN_OUTPUT tag is used to specify where the man pages will be put. -# If a relative path is entered the value of OUTPUT_DIRECTORY will be -# put in front of it. If left blank `man' will be used as the default path. - -MAN_OUTPUT = man - -# The MAN_EXTENSION tag determines the extension that is added to -# the generated man pages (default is the subroutine's section .3) - -MAN_EXTENSION = .3 - -# If the MAN_LINKS tag is set to YES and Doxygen generates man output, -# then it will generate one additional man file for each entity -# documented in the real man page(s). These additional files -# only source the real man page, but without them the man command -# would be unable to find the correct page. The default is NO. - -MAN_LINKS = NO - -#--------------------------------------------------------------------------- -# configuration options related to the XML output -#--------------------------------------------------------------------------- - -# If the GENERATE_XML tag is set to YES Doxygen will -# generate an XML file that captures the structure of -# the code including all documentation. - -GENERATE_XML = NO - -# The XML_OUTPUT tag is used to specify where the XML pages will be put. -# If a relative path is entered the value of OUTPUT_DIRECTORY will be -# put in front of it. If left blank `xml' will be used as the default path. - -XML_OUTPUT = xml - -# The XML_SCHEMA tag can be used to specify an XML schema, -# which can be used by a validating XML parser to check the -# syntax of the XML files. - -XML_SCHEMA = - -# The XML_DTD tag can be used to specify an XML DTD, -# which can be used by a validating XML parser to check the -# syntax of the XML files. - -XML_DTD = - -# If the XML_PROGRAMLISTING tag is set to YES Doxygen will -# dump the program listings (including syntax highlighting -# and cross-referencing information) to the XML output. Note that -# enabling this will significantly increase the size of the XML output. - -XML_PROGRAMLISTING = YES - -#--------------------------------------------------------------------------- -# configuration options for the AutoGen Definitions output -#--------------------------------------------------------------------------- - -# If the GENERATE_AUTOGEN_DEF tag is set to YES Doxygen will -# generate an AutoGen Definitions (see autogen.sf.net) file -# that captures the structure of the code including all -# documentation. Note that this feature is still experimental -# and incomplete at the moment. - -GENERATE_AUTOGEN_DEF = NO - -#--------------------------------------------------------------------------- -# configuration options related to the Perl module output -#--------------------------------------------------------------------------- - -# If the GENERATE_PERLMOD tag is set to YES Doxygen will -# generate a Perl module file that captures the structure of -# the code including all documentation. Note that this -# feature is still experimental and incomplete at the -# moment. - -GENERATE_PERLMOD = NO - -# If the PERLMOD_LATEX tag is set to YES Doxygen will generate -# the necessary Makefile rules, Perl scripts and LaTeX code to be able -# to generate PDF and DVI output from the Perl module output. - -PERLMOD_LATEX = NO - -# If the PERLMOD_PRETTY tag is set to YES the Perl module output will be -# nicely formatted so it can be parsed by a human reader. This is useful -# if you want to understand what is going on. On the other hand, if this -# tag is set to NO the size of the Perl module output will be much smaller -# and Perl will parse it just the same. - -PERLMOD_PRETTY = YES - -# The names of the make variables in the generated doxyrules.make file -# are prefixed with the string contained in PERLMOD_MAKEVAR_PREFIX. -# This is useful so different doxyrules.make files included by the same -# Makefile don't overwrite each other's variables. - -PERLMOD_MAKEVAR_PREFIX = - -#--------------------------------------------------------------------------- -# Configuration options related to the preprocessor -#--------------------------------------------------------------------------- - -# If the ENABLE_PREPROCESSING tag is set to YES (the default) Doxygen will -# evaluate all C-preprocessor directives found in the sources and include -# files. - -ENABLE_PREPROCESSING = YES - -# If the MACRO_EXPANSION tag is set to YES Doxygen will expand all macro -# names in the source code. If set to NO (the default) only conditional -# compilation will be performed. Macro expansion can be done in a controlled -# way by setting EXPAND_ONLY_PREDEF to YES. - -MACRO_EXPANSION = NO - -# If the EXPAND_ONLY_PREDEF and MACRO_EXPANSION tags are both set to YES -# then the macro expansion is limited to the macros specified with the -# PREDEFINED and EXPAND_AS_DEFINED tags. - -EXPAND_ONLY_PREDEF = NO - -# If the SEARCH_INCLUDES tag is set to YES (the default) the includes files -# in the INCLUDE_PATH (see below) will be search if a #include is found. - -SEARCH_INCLUDES = NO - -# The INCLUDE_PATH tag can be used to specify one or more directories that -# contain include files that are not input files but should be processed by -# the preprocessor. - -INCLUDE_PATH = - -# You can use the INCLUDE_FILE_PATTERNS tag to specify one or more wildcard -# patterns (like *.h and *.hpp) to filter out the header-files in the -# directories. If left blank, the patterns specified with FILE_PATTERNS will -# be used. - -INCLUDE_FILE_PATTERNS = - -# The PREDEFINED tag can be used to specify one or more macro names that -# are defined before the preprocessor is started (similar to the -D option of -# gcc). The argument of the tag is a list of macros of the form: name -# or name=definition (no spaces). If the definition and the = are -# omitted =1 is assumed. To prevent a macro definition from being -# undefined via #undef or recursively expanded use the := operator -# instead of the = operator. - -PREDEFINED = - -# If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then -# this tag can be used to specify a list of macro names that should be expanded. -# The macro definition that is found in the sources will be used. -# Use the PREDEFINED tag if you want to use a different macro definition. - -EXPAND_AS_DEFINED = - -# If the SKIP_FUNCTION_MACROS tag is set to YES (the default) then -# doxygen's preprocessor will remove all function-like macros that are alone -# on a line, have an all uppercase name, and do not end with a semicolon. Such -# function macros are typically used for boiler-plate code, and will confuse -# the parser if not removed. - -SKIP_FUNCTION_MACROS = YES - -#--------------------------------------------------------------------------- -# Configuration::additions related to external references -#--------------------------------------------------------------------------- - -# The TAGFILES option can be used to specify one or more tagfiles. -# Optionally an initial location of the external documentation -# can be added for each tagfile. The format of a tag file without -# this location is as follows: -# TAGFILES = file1 file2 ... -# Adding location for the tag files is done as follows: -# TAGFILES = file1=loc1 "file2 = loc2" ... -# where "loc1" and "loc2" can be relative or absolute paths or -# URLs. If a location is present for each tag, the installdox tool -# does not have to be run to correct the links. -# Note that each tag file must have a unique name -# (where the name does NOT include the path) -# If a tag file is not located in the directory in which doxygen -# is run, you must also specify the path to the tagfile here. - -TAGFILES = - -# When a file name is specified after GENERATE_TAGFILE, doxygen will create -# a tag file that is based on the input files it reads. - -GENERATE_TAGFILE = - -# If the ALLEXTERNALS tag is set to YES all external classes will be listed -# in the class index. If set to NO only the inherited external classes -# will be listed. - -ALLEXTERNALS = NO - -# If the EXTERNAL_GROUPS tag is set to YES all external groups will be listed -# in the modules index. If set to NO, only the current project's groups will -# be listed. - -EXTERNAL_GROUPS = YES - -# The PERL_PATH should be the absolute path and name of the perl script -# interpreter (i.e. the result of `which perl'). - -PERL_PATH = /usr/bin/perl - -#--------------------------------------------------------------------------- -# Configuration options related to the dot tool -#--------------------------------------------------------------------------- - -# If the CLASS_DIAGRAMS tag is set to YES (the default) Doxygen will -# generate a inheritance diagram (in HTML, RTF and LaTeX) for classes with base -# or super classes. Setting the tag to NO turns the diagrams off. Note that -# this option is superseded by the HAVE_DOT option below. This is only a -# fallback. It is recommended to install and use dot, since it yields more -# powerful graphs. - -CLASS_DIAGRAMS = YES - -# You can define message sequence charts within doxygen comments using the \msc -# command. Doxygen will then run the mscgen tool (see -# http://www.mcternan.me.uk/mscgen/) to produce the chart and insert it in the -# documentation. The MSCGEN_PATH tag allows you to specify the directory where -# the mscgen tool resides. If left empty the tool is assumed to be found in the -# default search path. - -MSCGEN_PATH = - -# If set to YES, the inheritance and collaboration graphs will hide -# inheritance and usage relations if the target is undocumented -# or is not a class. - -HIDE_UNDOC_RELATIONS = YES - -# If you set the HAVE_DOT tag to YES then doxygen will assume the dot tool is -# available from the path. This tool is part of Graphviz, a graph visualization -# toolkit from AT&T and Lucent Bell Labs. The other options in this section -# have no effect if this option is set to NO (the default) - -HAVE_DOT = NO - -# By default doxygen will write a font called FreeSans.ttf to the output -# directory and reference it in all dot files that doxygen generates. This -# font does not include all possible unicode characters however, so when you need -# these (or just want a differently looking font) you can specify the font name -# using DOT_FONTNAME. You need need to make sure dot is able to find the font, -# which can be done by putting it in a standard location or by setting the -# DOTFONTPATH environment variable or by setting DOT_FONTPATH to the directory -# containing the font. - -DOT_FONTNAME = FreeSans - -# By default doxygen will tell dot to use the output directory to look for the -# FreeSans.ttf font (which doxygen will put there itself). If you specify a -# different font using DOT_FONTNAME you can set the path where dot -# can find it using this tag. - -DOT_FONTPATH = - -# If the CLASS_GRAPH and HAVE_DOT tags are set to YES then doxygen -# will generate a graph for each documented class showing the direct and -# indirect inheritance relations. Setting this tag to YES will force the -# the CLASS_DIAGRAMS tag to NO. - -CLASS_GRAPH = YES - -# If the COLLABORATION_GRAPH and HAVE_DOT tags are set to YES then doxygen -# will generate a graph for each documented class showing the direct and -# indirect implementation dependencies (inheritance, containment, and -# class references variables) of the class with other documented classes. - -COLLABORATION_GRAPH = YES - -# If the GROUP_GRAPHS and HAVE_DOT tags are set to YES then doxygen -# will generate a graph for groups, showing the direct groups dependencies - -GROUP_GRAPHS = YES - -# If the UML_LOOK tag is set to YES doxygen will generate inheritance and -# collaboration diagrams in a style similar to the OMG's Unified Modeling -# Language. - -UML_LOOK = NO - -# If set to YES, the inheritance and collaboration graphs will show the -# relations between templates and their instances. - -TEMPLATE_RELATIONS = NO - -# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDE_GRAPH, and HAVE_DOT -# tags are set to YES then doxygen will generate a graph for each documented -# file showing the direct and indirect include dependencies of the file with -# other documented files. - -INCLUDE_GRAPH = YES - -# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDED_BY_GRAPH, and -# HAVE_DOT tags are set to YES then doxygen will generate a graph for each -# documented header file showing the documented files that directly or -# indirectly include this file. - -INCLUDED_BY_GRAPH = YES - -# If the CALL_GRAPH and HAVE_DOT options are set to YES then -# doxygen will generate a call dependency graph for every global function -# or class method. Note that enabling this option will significantly increase -# the time of a run. So in most cases it will be better to enable call graphs -# for selected functions only using the \callgraph command. - -CALL_GRAPH = NO - -# If the CALLER_GRAPH and HAVE_DOT tags are set to YES then -# doxygen will generate a caller dependency graph for every global function -# or class method. Note that enabling this option will significantly increase -# the time of a run. So in most cases it will be better to enable caller -# graphs for selected functions only using the \callergraph command. - -CALLER_GRAPH = NO - -# If the GRAPHICAL_HIERARCHY and HAVE_DOT tags are set to YES then doxygen -# will graphical hierarchy of all classes instead of a textual one. - -GRAPHICAL_HIERARCHY = YES - -# If the DIRECTORY_GRAPH, SHOW_DIRECTORIES and HAVE_DOT tags are set to YES -# then doxygen will show the dependencies a directory has on other directories -# in a graphical way. The dependency relations are determined by the #include -# relations between the files in the directories. - -DIRECTORY_GRAPH = YES - -# The DOT_IMAGE_FORMAT tag can be used to set the image format of the images -# generated by dot. Possible values are png, jpg, or gif -# If left blank png will be used. - -DOT_IMAGE_FORMAT = png - -# The tag DOT_PATH can be used to specify the path where the dot tool can be -# found. If left blank, it is assumed the dot tool can be found in the path. - -DOT_PATH = - -# The DOTFILE_DIRS tag can be used to specify one or more directories that -# contain dot files that are included in the documentation (see the -# \dotfile command). - -DOTFILE_DIRS = - -# The DOT_GRAPH_MAX_NODES tag can be used to set the maximum number of -# nodes that will be shown in the graph. If the number of nodes in a graph -# becomes larger than this value, doxygen will truncate the graph, which is -# visualized by representing a node as a red box. Note that doxygen if the -# number of direct children of the root node in a graph is already larger than -# DOT_GRAPH_MAX_NODES then the graph will not be shown at all. Also note -# that the size of a graph can be further restricted by MAX_DOT_GRAPH_DEPTH. - -DOT_GRAPH_MAX_NODES = 50 - -# The MAX_DOT_GRAPH_DEPTH tag can be used to set the maximum depth of the -# graphs generated by dot. A depth value of 3 means that only nodes reachable -# from the root by following a path via at most 3 edges will be shown. Nodes -# that lay further from the root node will be omitted. Note that setting this -# option to 1 or 2 may greatly reduce the computation time needed for large -# code bases. Also note that the size of a graph can be further restricted by -# DOT_GRAPH_MAX_NODES. Using a depth of 0 means no depth restriction. - -MAX_DOT_GRAPH_DEPTH = 0 - -# Set the DOT_TRANSPARENT tag to YES to generate images with a transparent -# background. This is enabled by default, which results in a transparent -# background. Warning: Depending on the platform used, enabling this option -# may lead to badly anti-aliased labels on the edges of a graph (i.e. they -# become hard to read). - -DOT_TRANSPARENT = NO - -# Set the DOT_MULTI_TARGETS tag to YES allow dot to generate multiple output -# files in one run (i.e. multiple -o and -T options on the command line). This -# makes dot run faster, but since only newer versions of dot (>1.8.10) -# support this, this feature is disabled by default. - -DOT_MULTI_TARGETS = NO - -# If the GENERATE_LEGEND tag is set to YES (the default) Doxygen will -# generate a legend page explaining the meaning of the various boxes and -# arrows in the dot generated graphs. - -GENERATE_LEGEND = YES - -# If the DOT_CLEANUP tag is set to YES (the default) Doxygen will -# remove the intermediate dot files that are used to generate -# the various graphs. - -DOT_CLEANUP = YES - -#--------------------------------------------------------------------------- -# Configuration::additions related to the search engine -#--------------------------------------------------------------------------- - -# The SEARCHENGINE tag specifies whether or not a search engine should be -# used. If set to NO the values of all tags below this one will be ignored. - -SEARCHENGINE = NO diff --git a/doxygen.cfg.in b/doxygen.cfg.in new file mode 100644 index 0000000..eb84c85 --- /dev/null +++ b/doxygen.cfg.in @@ -0,0 +1,2806 @@ +# Doxyfile 1.11.0 + +# This file describes the settings to be used by the documentation system +# doxygen (www.doxygen.org) for a project. +# +# All text after a double hash (##) is considered a comment and is placed in +# front of the TAG it is preceding. +# +# All text after a single hash (#) is considered a comment and will be ignored. +# The format is: +# TAG = value [value, ...] +# For lists, items can also be appended using: +# TAG += value [value, ...] +# Values that contain spaces should be placed between quotes (\" \"). +# +# Note: +# +# Use doxygen to compare the used configuration file with the template +# configuration file: +# doxygen -x [configFile] +# Use doxygen to compare the used configuration file with the template +# configuration file without replacing the environment variables or CMake type +# replacement variables: +# doxygen -x_noenv [configFile] + +#--------------------------------------------------------------------------- +# Project related configuration options +#--------------------------------------------------------------------------- + +# This tag specifies the encoding used for all characters in the configuration +# file that follow. The default is UTF-8 which is also the encoding used for all +# text before the first occurrence of this tag. Doxygen uses libiconv (or the +# iconv built into libc) for the transcoding. See +# https://www.gnu.org/software/libiconv/ for the list of possible encodings. +# The default value is: UTF-8. + +DOXYFILE_ENCODING = UTF-8 + +# The PROJECT_NAME tag is a single word (or a sequence of words surrounded by +# double-quotes, unless you are using Doxywizard) that should identify the +# project for which the documentation is generated. This name is used in the +# title of most generated pages and in a few other places. +# The default value is: My Project. + +PROJECT_NAME = @PACKAGE@ + +# The PROJECT_NUMBER tag can be used to enter a project or revision number. This +# could be handy for archiving the generated documentation or if some version +# control system is used. + +PROJECT_NUMBER = @VERSION@ + +# Using the PROJECT_BRIEF tag one can provide an optional one line description +# for a project that appears at the top of each page and should give viewer a +# quick idea about the purpose of the project. Keep the description short. + +PROJECT_BRIEF = "API Documentation" + +# With the PROJECT_LOGO tag one can specify a logo or an icon that is included +# in the documentation. The maximum height of the logo should not exceed 55 +# pixels and the maximum width should not exceed 200 pixels. Doxygen will copy +# the logo to the output directory. + +PROJECT_LOGO = docs/doxygen/logo-vector-clean.svg + +# With the PROJECT_ICON tag one can specify an icon that is included in the tabs +# when the HTML document is shown. Doxygen will copy the logo to the output +# directory. + +PROJECT_ICON = + +# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) path +# into which the generated documentation will be written. If a relative path is +# entered, it will be relative to the location where doxygen was started. If +# left blank the current directory will be used. + +OUTPUT_DIRECTORY = docs + +# If the CREATE_SUBDIRS tag is set to YES then doxygen will create up to 4096 +# sub-directories (in 2 levels) under the output directory of each output format +# and will distribute the generated files over these directories. Enabling this +# option can be useful when feeding doxygen a huge amount of source files, where +# putting all generated files in the same directory would otherwise causes +# performance problems for the file system. Adapt CREATE_SUBDIRS_LEVEL to +# control the number of sub-directories. +# The default value is: NO. + +CREATE_SUBDIRS = NO + +# Controls the number of sub-directories that will be created when +# CREATE_SUBDIRS tag is set to YES. Level 0 represents 16 directories, and every +# level increment doubles the number of directories, resulting in 4096 +# directories at level 8 which is the default and also the maximum value. The +# sub-directories are organized in 2 levels, the first level always has a fixed +# number of 16 directories. +# Minimum value: 0, maximum value: 8, default value: 8. +# This tag requires that the tag CREATE_SUBDIRS is set to YES. + +CREATE_SUBDIRS_LEVEL = 8 + +# If the ALLOW_UNICODE_NAMES tag is set to YES, doxygen will allow non-ASCII +# characters to appear in the names of generated files. If set to NO, non-ASCII +# characters will be escaped, for example _xE3_x81_x84 will be used for Unicode +# U+3044. +# The default value is: NO. + +ALLOW_UNICODE_NAMES = NO + +# The OUTPUT_LANGUAGE tag is used to specify the language in which all +# documentation generated by doxygen is written. Doxygen will use this +# information to generate all constant output in the proper language. +# Possible values are: Afrikaans, Arabic, Armenian, Brazilian, Bulgarian, +# Catalan, Chinese, Chinese-Traditional, Croatian, Czech, Danish, Dutch, English +# (United States), Esperanto, Farsi (Persian), Finnish, French, German, Greek, +# Hindi, Hungarian, Indonesian, Italian, Japanese, Japanese-en (Japanese with +# English messages), Korean, Korean-en (Korean with English messages), Latvian, +# Lithuanian, Macedonian, Norwegian, Persian (Farsi), Polish, Portuguese, +# Romanian, Russian, Serbian, Serbian-Cyrillic, Slovak, Slovene, Spanish, +# Swedish, Turkish, Ukrainian and Vietnamese. +# The default value is: English. + +OUTPUT_LANGUAGE = English + +# If the BRIEF_MEMBER_DESC tag is set to YES, doxygen will include brief member +# descriptions after the members that are listed in the file and class +# documentation (similar to Javadoc). Set to NO to disable this. +# The default value is: YES. + +BRIEF_MEMBER_DESC = YES + +# If the REPEAT_BRIEF tag is set to YES, doxygen will prepend the brief +# description of a member or function before the detailed description +# +# Note: If both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the +# brief descriptions will be completely suppressed. +# The default value is: YES. + +REPEAT_BRIEF = YES + +# This tag implements a quasi-intelligent brief description abbreviator that is +# used to form the text in various listings. Each string in this list, if found +# as the leading text of the brief description, will be stripped from the text +# and the result, after processing the whole list, is used as the annotated +# text. Otherwise, the brief description is used as-is. If left blank, the +# following values are used ($name is automatically replaced with the name of +# the entity):The $name class, The $name widget, The $name file, is, provides, +# specifies, contains, represents, a, an and the. + +ABBREVIATE_BRIEF = + +# If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then +# doxygen will generate a detailed section even if there is only a brief +# description. +# The default value is: NO. + +ALWAYS_DETAILED_SEC = NO + +# If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all +# inherited members of a class in the documentation of that class as if those +# members were ordinary class members. Constructors, destructors and assignment +# operators of the base classes will not be shown. +# The default value is: NO. + +INLINE_INHERITED_MEMB = NO + +# If the FULL_PATH_NAMES tag is set to YES, doxygen will prepend the full path +# before files name in the file list and in the header files. If set to NO the +# shortest path that makes the file name unique will be used +# The default value is: YES. + +FULL_PATH_NAMES = YES + +# The STRIP_FROM_PATH tag can be used to strip a user-defined part of the path. +# Stripping is only done if one of the specified strings matches the left-hand +# part of the path. The tag can be used to show relative paths in the file list. +# If left blank the directory from which doxygen is run is used as the path to +# strip. +# +# Note that you can specify absolute paths here, but also relative paths, which +# will be relative from the directory where doxygen is started. +# This tag requires that the tag FULL_PATH_NAMES is set to YES. + +STRIP_FROM_PATH = include + +# The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of the +# path mentioned in the documentation of a class, which tells the reader which +# header file to include in order to use a class. If left blank only the name of +# the header file containing the class definition is used. Otherwise one should +# specify the list of include paths that are normally passed to the compiler +# using the -I flag. + +STRIP_FROM_INC_PATH = + +# If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter (but +# less readable) file names. This can be useful is your file systems doesn't +# support long names like on DOS, Mac, or CD-ROM. +# The default value is: NO. + +SHORT_NAMES = NO + +# If the JAVADOC_AUTOBRIEF tag is set to YES then doxygen will interpret the +# first line (until the first dot) of a Javadoc-style comment as the brief +# description. If set to NO, the Javadoc-style will behave just like regular Qt- +# style comments (thus requiring an explicit @brief command for a brief +# description.) +# The default value is: NO. + +JAVADOC_AUTOBRIEF = YES + +# If the JAVADOC_BANNER tag is set to YES then doxygen will interpret a line +# such as +# /*************** +# as being the beginning of a Javadoc-style comment "banner". If set to NO, the +# Javadoc-style will behave just like regular comments and it will not be +# interpreted by doxygen. +# The default value is: NO. + +JAVADOC_BANNER = NO + +# If the QT_AUTOBRIEF tag is set to YES then doxygen will interpret the first +# line (until the first dot) of a Qt-style comment as the brief description. If +# set to NO, the Qt-style will behave just like regular Qt-style comments (thus +# requiring an explicit \brief command for a brief description.) +# The default value is: NO. + +QT_AUTOBRIEF = NO + +# The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make doxygen treat a +# multi-line C++ special comment block (i.e. a block of //! or /// comments) as +# a brief description. This used to be the default behavior. The new default is +# to treat a multi-line C++ comment block as a detailed description. Set this +# tag to YES if you prefer the old behavior instead. +# +# Note that setting this tag to YES also means that rational rose comments are +# not recognized any more. +# The default value is: NO. + +MULTILINE_CPP_IS_BRIEF = NO + +# By default Python docstrings are displayed as preformatted text and doxygen's +# special commands cannot be used. By setting PYTHON_DOCSTRING to NO the +# doxygen's special commands can be used and the contents of the docstring +# documentation blocks is shown as doxygen documentation. +# The default value is: YES. + +PYTHON_DOCSTRING = YES + +# If the INHERIT_DOCS tag is set to YES then an undocumented member inherits the +# documentation from any documented member that it re-implements. +# The default value is: YES. + +INHERIT_DOCS = YES + +# If the SEPARATE_MEMBER_PAGES tag is set to YES then doxygen will produce a new +# page for each member. If set to NO, the documentation of a member will be part +# of the file/class/namespace that contains it. +# The default value is: NO. + +SEPARATE_MEMBER_PAGES = YES + +# The TAB_SIZE tag can be used to set the number of spaces in a tab. Doxygen +# uses this value to replace tabs by spaces in code fragments. +# Minimum value: 1, maximum value: 16, default value: 4. + +TAB_SIZE = 8 + +# This tag can be used to specify a number of aliases that act as commands in +# the documentation. An alias has the form: +# name=value +# For example adding +# "sideeffect=@par Side Effects:^^" +# will allow you to put the command \sideeffect (or @sideeffect) in the +# documentation, which will result in a user-defined paragraph with heading +# "Side Effects:". Note that you cannot put \n's in the value part of an alias +# to insert newlines (in the resulting output). You can put ^^ in the value part +# of an alias to insert a newline as if a physical newline was in the original +# file. When you need a literal { or } or , in the value part of an alias you +# have to escape them by means of a backslash (\), this can lead to conflicts +# with the commands \{ and \} for these it is advised to use the version @{ and +# @} or use a double escape (\\{ and \\}) + +ALIASES = + +# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C sources +# only. Doxygen will then generate output that is more tailored for C. For +# instance, some of the names that are used will be different. The list of all +# members will be omitted, etc. +# The default value is: NO. + +OPTIMIZE_OUTPUT_FOR_C = YES + +# Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java or +# Python sources only. Doxygen will then generate output that is more tailored +# for that language. For instance, namespaces will be presented as packages, +# qualified scopes will look different, etc. +# The default value is: NO. + +OPTIMIZE_OUTPUT_JAVA = NO + +# Set the OPTIMIZE_FOR_FORTRAN tag to YES if your project consists of Fortran +# sources. Doxygen will then generate output that is tailored for Fortran. +# The default value is: NO. + +OPTIMIZE_FOR_FORTRAN = NO + +# Set the OPTIMIZE_OUTPUT_VHDL tag to YES if your project consists of VHDL +# sources. Doxygen will then generate output that is tailored for VHDL. +# The default value is: NO. + +OPTIMIZE_OUTPUT_VHDL = NO + +# Set the OPTIMIZE_OUTPUT_SLICE tag to YES if your project consists of Slice +# sources only. Doxygen will then generate output that is more tailored for that +# language. For instance, namespaces will be presented as modules, types will be +# separated into more groups, etc. +# The default value is: NO. + +OPTIMIZE_OUTPUT_SLICE = YES + +# Doxygen selects the parser to use depending on the extension of the files it +# parses. With this tag you can assign which parser to use for a given +# extension. Doxygen has a built-in mapping, but you can override or extend it +# using this tag. The format is ext=language, where ext is a file extension, and +# language is one of the parsers supported by doxygen: IDL, Java, JavaScript, +# Csharp (C#), C, C++, Lex, D, PHP, md (Markdown), Objective-C, Python, Slice, +# VHDL, Fortran (fixed format Fortran: FortranFixed, free formatted Fortran: +# FortranFree, unknown formatted Fortran: Fortran. In the later case the parser +# tries to guess whether the code is fixed or free formatted code, this is the +# default for Fortran type files). For instance to make doxygen treat .inc files +# as Fortran files (default is PHP), and .f files as C (default is Fortran), +# use: inc=Fortran f=C. +# +# Note: For files without extension you can use no_extension as a placeholder. +# +# Note that for custom extensions you also need to set FILE_PATTERNS otherwise +# the files are not read by doxygen. When specifying no_extension you should add +# * to the FILE_PATTERNS. +# +# Note see also the list of default file extension mappings. + +EXTENSION_MAPPING = + +# If the MARKDOWN_SUPPORT tag is enabled then doxygen pre-processes all comments +# according to the Markdown format, which allows for more readable +# documentation. See https://daringfireball.net/projects/markdown/ for details. +# The output of markdown processing is further processed by doxygen, so you can +# mix doxygen, HTML, and XML commands with Markdown formatting. Disable only in +# case of backward compatibilities issues. +# The default value is: YES. + +MARKDOWN_SUPPORT = YES + +# When the TOC_INCLUDE_HEADINGS tag is set to a non-zero value, all headings up +# to that level are automatically included in the table of contents, even if +# they do not have an id attribute. +# Note: This feature currently applies only to Markdown headings. +# Minimum value: 0, maximum value: 99, default value: 6. +# This tag requires that the tag MARKDOWN_SUPPORT is set to YES. + +TOC_INCLUDE_HEADINGS = 5 + +# The MARKDOWN_ID_STYLE tag can be used to specify the algorithm used to +# generate identifiers for the Markdown headings. Note: Every identifier is +# unique. +# Possible values are: DOXYGEN use a fixed 'autotoc_md' string followed by a +# sequence number starting at 0 and GITHUB use the lower case version of title +# with any whitespace replaced by '-' and punctuation characters removed. +# The default value is: DOXYGEN. +# This tag requires that the tag MARKDOWN_SUPPORT is set to YES. + +MARKDOWN_ID_STYLE = DOXYGEN + +# When enabled doxygen tries to link words that correspond to documented +# classes, or namespaces to their corresponding documentation. Such a link can +# be prevented in individual cases by putting a % sign in front of the word or +# globally by setting AUTOLINK_SUPPORT to NO. +# The default value is: YES. + +AUTOLINK_SUPPORT = YES + +# If you use STL classes (i.e. std::string, std::vector, etc.) but do not want +# to include (a tag file for) the STL sources as input, then you should set this +# tag to YES in order to let doxygen match functions declarations and +# definitions whose arguments contain STL classes (e.g. func(std::string); +# versus func(std::string) {}). This also makes the inheritance and +# collaboration diagrams that involve STL classes more complete and accurate. +# The default value is: NO. + +BUILTIN_STL_SUPPORT = NO + +# If you use Microsoft's C++/CLI language, you should set this option to YES to +# enable parsing support. +# The default value is: NO. + +CPP_CLI_SUPPORT = NO + +# Set the SIP_SUPPORT tag to YES if your project consists of sip (see: +# https://www.riverbankcomputing.com/software) sources only. Doxygen will parse +# them like normal C++ but will assume all classes use public instead of private +# inheritance when no explicit protection keyword is present. +# The default value is: NO. + +SIP_SUPPORT = NO + +# For Microsoft's IDL there are propget and propput attributes to indicate +# getter and setter methods for a property. Setting this option to YES will make +# doxygen to replace the get and set methods by a property in the documentation. +# This will only work if the methods are indeed getting or setting a simple +# type. If this is not the case, or you want to show the methods anyway, you +# should set this option to NO. +# The default value is: YES. + +IDL_PROPERTY_SUPPORT = YES + +# If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC +# tag is set to YES then doxygen will reuse the documentation of the first +# member in the group (if any) for the other members of the group. By default +# all members of a group must be documented explicitly. +# The default value is: NO. + +DISTRIBUTE_GROUP_DOC = NO + +# If one adds a struct or class to a group and this option is enabled, then also +# any nested class or struct is added to the same group. By default this option +# is disabled and one has to add nested compounds explicitly via \ingroup. +# The default value is: NO. + +GROUP_NESTED_COMPOUNDS = NO + +# Set the SUBGROUPING tag to YES to allow class member groups of the same type +# (for instance a group of public functions) to be put as a subgroup of that +# type (e.g. under the Public Functions section). Set it to NO to prevent +# subgrouping. Alternatively, this can be done per class using the +# \nosubgrouping command. +# The default value is: YES. + +SUBGROUPING = NO + +# When the INLINE_GROUPED_CLASSES tag is set to YES, classes, structs and unions +# are shown inside the group in which they are included (e.g. using \ingroup) +# instead of on a separate page (for HTML and Man pages) or section (for LaTeX +# and RTF). +# +# Note that this feature does not work in combination with +# SEPARATE_MEMBER_PAGES. +# The default value is: NO. + +INLINE_GROUPED_CLASSES = NO + +# When the INLINE_SIMPLE_STRUCTS tag is set to YES, structs, classes, and unions +# with only public data fields or simple typedef fields will be shown inline in +# the documentation of the scope in which they are defined (i.e. file, +# namespace, or group documentation), provided this scope is documented. If set +# to NO, structs, classes, and unions are shown on a separate page (for HTML and +# Man pages) or section (for LaTeX and RTF). +# The default value is: NO. + +INLINE_SIMPLE_STRUCTS = NO + +# When TYPEDEF_HIDES_STRUCT tag is enabled, a typedef of a struct, union, or +# enum is documented as struct, union, or enum with the name of the typedef. So +# typedef struct TypeS {} TypeT, will appear in the documentation as a struct +# with name TypeT. When disabled the typedef will appear as a member of a file, +# namespace, or class. And the struct will be named TypeS. This can typically be +# useful for C code in case the coding convention dictates that all compound +# types are typedef'ed and only the typedef is referenced, never the tag name. +# The default value is: NO. + +TYPEDEF_HIDES_STRUCT = YES + +# The size of the symbol lookup cache can be set using LOOKUP_CACHE_SIZE. This +# cache is used to resolve symbols given their name and scope. Since this can be +# an expensive process and often the same symbol appears multiple times in the +# code, doxygen keeps a cache of pre-resolved symbols. If the cache is too small +# doxygen will become slower. If the cache is too large, memory is wasted. The +# cache size is given by this formula: 2^(16+LOOKUP_CACHE_SIZE). The valid range +# is 0..9, the default is 0, corresponding to a cache size of 2^16=65536 +# symbols. At the end of a run doxygen will report the cache usage and suggest +# the optimal cache size from a speed point of view. +# Minimum value: 0, maximum value: 9, default value: 0. + +LOOKUP_CACHE_SIZE = 0 + +# The NUM_PROC_THREADS specifies the number of threads doxygen is allowed to use +# during processing. When set to 0 doxygen will based this on the number of +# cores available in the system. You can set it explicitly to a value larger +# than 0 to get more control over the balance between CPU load and processing +# speed. At this moment only the input processing can be done using multiple +# threads. Since this is still an experimental feature the default is set to 1, +# which effectively disables parallel processing. Please report any issues you +# encounter. Generating dot graphs in parallel is controlled by the +# DOT_NUM_THREADS setting. +# Minimum value: 0, maximum value: 32, default value: 1. + +NUM_PROC_THREADS = 1 + +# If the TIMESTAMP tag is set different from NO then each generated page will +# contain the date or date and time when the page was generated. Setting this to +# NO can help when comparing the output of multiple runs. +# Possible values are: YES, NO, DATETIME and DATE. +# The default value is: NO. + +TIMESTAMP = YES + +#--------------------------------------------------------------------------- +# Build related configuration options +#--------------------------------------------------------------------------- + +# If the EXTRACT_ALL tag is set to YES, doxygen will assume all entities in +# documentation are documented, even if no documentation was available. Private +# class members and static file members will be hidden unless the +# EXTRACT_PRIVATE respectively EXTRACT_STATIC tags are set to YES. +# Note: This will also disable the warnings about undocumented members that are +# normally produced when WARNINGS is set to YES. +# The default value is: NO. + +EXTRACT_ALL = NO + +# If the EXTRACT_PRIVATE tag is set to YES, all private members of a class will +# be included in the documentation. +# The default value is: NO. + +EXTRACT_PRIVATE = NO + +# If the EXTRACT_PRIV_VIRTUAL tag is set to YES, documented private virtual +# methods of a class will be included in the documentation. +# The default value is: NO. + +EXTRACT_PRIV_VIRTUAL = NO + +# If the EXTRACT_PACKAGE tag is set to YES, all members with package or internal +# scope will be included in the documentation. +# The default value is: NO. + +EXTRACT_PACKAGE = NO + +# If the EXTRACT_STATIC tag is set to YES, all static members of a file will be +# included in the documentation. +# The default value is: NO. + +EXTRACT_STATIC = NO + +# If the EXTRACT_LOCAL_CLASSES tag is set to YES, classes (and structs) defined +# locally in source files will be included in the documentation. If set to NO, +# only classes defined in header files are included. Does not have any effect +# for Java sources. +# The default value is: YES. + +EXTRACT_LOCAL_CLASSES = NO + +# This flag is only useful for Objective-C code. If set to YES, local methods, +# which are defined in the implementation section but not in the interface are +# included in the documentation. If set to NO, only methods in the interface are +# included. +# The default value is: NO. + +EXTRACT_LOCAL_METHODS = NO + +# If this flag is set to YES, the members of anonymous namespaces will be +# extracted and appear in the documentation as a namespace called +# 'anonymous_namespace{file}', where file will be replaced with the base name of +# the file that contains the anonymous namespace. By default anonymous namespace +# are hidden. +# The default value is: NO. + +EXTRACT_ANON_NSPACES = NO + +# If this flag is set to YES, the name of an unnamed parameter in a declaration +# will be determined by the corresponding definition. By default unnamed +# parameters remain unnamed in the output. +# The default value is: YES. + +RESOLVE_UNNAMED_PARAMS = YES + +# If the HIDE_UNDOC_MEMBERS tag is set to YES, doxygen will hide all +# undocumented members inside documented classes or files. If set to NO these +# members will be included in the various overviews, but no documentation +# section is generated. This option has no effect if EXTRACT_ALL is enabled. +# The default value is: NO. + +HIDE_UNDOC_MEMBERS = NO + +# If the HIDE_UNDOC_CLASSES tag is set to YES, doxygen will hide all +# undocumented classes that are normally visible in the class hierarchy. If set +# to NO, these classes will be included in the various overviews. This option +# will also hide undocumented C++ concepts if enabled. This option has no effect +# if EXTRACT_ALL is enabled. +# The default value is: NO. + +HIDE_UNDOC_CLASSES = NO + +# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, doxygen will hide all friend +# declarations. If set to NO, these declarations will be included in the +# documentation. +# The default value is: NO. + +HIDE_FRIEND_COMPOUNDS = NO + +# If the HIDE_IN_BODY_DOCS tag is set to YES, doxygen will hide any +# documentation blocks found inside the body of a function. If set to NO, these +# blocks will be appended to the function's detailed documentation block. +# The default value is: NO. + +HIDE_IN_BODY_DOCS = NO + +# The INTERNAL_DOCS tag determines if documentation that is typed after a +# \internal command is included. If the tag is set to NO then the documentation +# will be excluded. Set it to YES to include the internal documentation. +# The default value is: NO. + +INTERNAL_DOCS = NO + +# With the correct setting of option CASE_SENSE_NAMES doxygen will better be +# able to match the capabilities of the underlying filesystem. In case the +# filesystem is case sensitive (i.e. it supports files in the same directory +# whose names only differ in casing), the option must be set to YES to properly +# deal with such files in case they appear in the input. For filesystems that +# are not case sensitive the option should be set to NO to properly deal with +# output files written for symbols that only differ in casing, such as for two +# classes, one named CLASS and the other named Class, and to also support +# references to files without having to specify the exact matching casing. On +# Windows (including Cygwin) and MacOS, users should typically set this option +# to NO, whereas on Linux or other Unix flavors it should typically be set to +# YES. +# Possible values are: SYSTEM, NO and YES. +# The default value is: SYSTEM. + +CASE_SENSE_NAMES = NO + +# If the HIDE_SCOPE_NAMES tag is set to NO then doxygen will show members with +# their full class and namespace scopes in the documentation. If set to YES, the +# scope will be hidden. +# The default value is: NO. + +HIDE_SCOPE_NAMES = NO + +# If the HIDE_COMPOUND_REFERENCE tag is set to NO (default) then doxygen will +# append additional text to a page's title, such as Class Reference. If set to +# YES the compound reference will be hidden. +# The default value is: NO. + +HIDE_COMPOUND_REFERENCE= NO + +# If the SHOW_HEADERFILE tag is set to YES then the documentation for a class +# will show which file needs to be included to use the class. +# The default value is: YES. + +SHOW_HEADERFILE = YES + +# If the SHOW_INCLUDE_FILES tag is set to YES then doxygen will put a list of +# the files that are included by a file in the documentation of that file. +# The default value is: YES. + +SHOW_INCLUDE_FILES = NO + +# If the SHOW_GROUPED_MEMB_INC tag is set to YES then Doxygen will add for each +# grouped member an include statement to the documentation, telling the reader +# which file to include in order to use the member. +# The default value is: NO. + +SHOW_GROUPED_MEMB_INC = NO + +# If the FORCE_LOCAL_INCLUDES tag is set to YES then doxygen will list include +# files with double quotes in the documentation rather than with sharp brackets. +# The default value is: NO. + +FORCE_LOCAL_INCLUDES = NO + +# If the INLINE_INFO tag is set to YES then a tag [inline] is inserted in the +# documentation for inline members. +# The default value is: YES. + +INLINE_INFO = YES + +# If the SORT_MEMBER_DOCS tag is set to YES then doxygen will sort the +# (detailed) documentation of file and class members alphabetically by member +# name. If set to NO, the members will appear in declaration order. +# The default value is: YES. + +SORT_MEMBER_DOCS = YES + +# If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the brief +# descriptions of file, namespace and class members alphabetically by member +# name. If set to NO, the members will appear in declaration order. Note that +# this will also influence the order of the classes in the class list. +# The default value is: NO. + +SORT_BRIEF_DOCS = NO + +# If the SORT_MEMBERS_CTORS_1ST tag is set to YES then doxygen will sort the +# (brief and detailed) documentation of class members so that constructors and +# destructors are listed first. If set to NO the constructors will appear in the +# respective orders defined by SORT_BRIEF_DOCS and SORT_MEMBER_DOCS. +# Note: If SORT_BRIEF_DOCS is set to NO this option is ignored for sorting brief +# member documentation. +# Note: If SORT_MEMBER_DOCS is set to NO this option is ignored for sorting +# detailed member documentation. +# The default value is: NO. + +SORT_MEMBERS_CTORS_1ST = NO + +# If the SORT_GROUP_NAMES tag is set to YES then doxygen will sort the hierarchy +# of group names into alphabetical order. If set to NO the group names will +# appear in their defined order. +# The default value is: NO. + +SORT_GROUP_NAMES = NO + +# If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be sorted by +# fully-qualified names, including namespaces. If set to NO, the class list will +# be sorted only by class name, not including the namespace part. +# Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES. +# Note: This option applies only to the class list, not to the alphabetical +# list. +# The default value is: NO. + +SORT_BY_SCOPE_NAME = NO + +# If the STRICT_PROTO_MATCHING option is enabled and doxygen fails to do proper +# type resolution of all parameters of a function it will reject a match between +# the prototype and the implementation of a member function even if there is +# only one candidate or it is obvious which candidate to choose by doing a +# simple string match. By disabling STRICT_PROTO_MATCHING doxygen will still +# accept a match between prototype and implementation in such cases. +# The default value is: NO. + +STRICT_PROTO_MATCHING = NO + +# The GENERATE_TODOLIST tag can be used to enable (YES) or disable (NO) the todo +# list. This list is created by putting \todo commands in the documentation. +# The default value is: YES. + +GENERATE_TODOLIST = YES + +# The GENERATE_TESTLIST tag can be used to enable (YES) or disable (NO) the test +# list. This list is created by putting \test commands in the documentation. +# The default value is: YES. + +GENERATE_TESTLIST = YES + +# The GENERATE_BUGLIST tag can be used to enable (YES) or disable (NO) the bug +# list. This list is created by putting \bug commands in the documentation. +# The default value is: YES. + +GENERATE_BUGLIST = YES + +# The GENERATE_DEPRECATEDLIST tag can be used to enable (YES) or disable (NO) +# the deprecated list. This list is created by putting \deprecated commands in +# the documentation. +# The default value is: YES. + +GENERATE_DEPRECATEDLIST= YES + +# The ENABLED_SECTIONS tag can be used to enable conditional documentation +# sections, marked by \if <section_label> ... \endif and \cond <section_label> +# ... \endcond blocks. + +ENABLED_SECTIONS = + +# The MAX_INITIALIZER_LINES tag determines the maximum number of lines that the +# initial value of a variable or macro / define can have for it to appear in the +# documentation. If the initializer consists of more lines than specified here +# it will be hidden. Use a value of 0 to hide initializers completely. The +# appearance of the value of individual variables and macros / defines can be +# controlled using \showinitializer or \hideinitializer command in the +# documentation regardless of this setting. +# Minimum value: 0, maximum value: 10000, default value: 30. + +MAX_INITIALIZER_LINES = 30 + +# Set the SHOW_USED_FILES tag to NO to disable the list of files generated at +# the bottom of the documentation of classes and structs. If set to YES, the +# list will mention the files that were used to generate the documentation. +# The default value is: YES. + +SHOW_USED_FILES = NO + +# Set the SHOW_FILES tag to NO to disable the generation of the Files page. This +# will remove the Files entry from the Quick Index and from the Folder Tree View +# (if specified). +# The default value is: YES. + +SHOW_FILES = YES + +# Set the SHOW_NAMESPACES tag to NO to disable the generation of the Namespaces +# page. This will remove the Namespaces entry from the Quick Index and from the +# Folder Tree View (if specified). +# The default value is: YES. + +SHOW_NAMESPACES = YES + +# The FILE_VERSION_FILTER tag can be used to specify a program or script that +# doxygen should invoke to get the current version for each file (typically from +# the version control system). Doxygen will invoke the program by executing (via +# popen()) the command command input-file, where command is the value of the +# FILE_VERSION_FILTER tag, and input-file is the name of an input file provided +# by doxygen. Whatever the program writes to standard output is used as the file +# version. For an example see the documentation. + +FILE_VERSION_FILTER = + +# The LAYOUT_FILE tag can be used to specify a layout file which will be parsed +# by doxygen. The layout file controls the global structure of the generated +# output files in an output format independent way. To create the layout file +# that represents doxygen's defaults, run doxygen with the -l option. You can +# optionally specify a file name after the option, if omitted DoxygenLayout.xml +# will be used as the name of the layout file. See also section "Changing the +# layout of pages" for information. +# +# Note that if you run doxygen from a directory containing a file called +# DoxygenLayout.xml, doxygen will parse it automatically even if the LAYOUT_FILE +# tag is left empty. + +LAYOUT_FILE = docs/doxygen/layout.xml + +# The CITE_BIB_FILES tag can be used to specify one or more bib files containing +# the reference definitions. This must be a list of .bib files. The .bib +# extension is automatically appended if omitted. This requires the bibtex tool +# to be installed. See also https://en.wikipedia.org/wiki/BibTeX for more info. +# For LaTeX the style of the bibliography can be controlled using +# LATEX_BIB_STYLE. To use this feature you need bibtex and perl available in the +# search path. See also \cite for info how to create references. + +CITE_BIB_FILES = + +#--------------------------------------------------------------------------- +# Configuration options related to warning and progress messages +#--------------------------------------------------------------------------- + +# The QUIET tag can be used to turn on/off the messages that are generated to +# standard output by doxygen. If QUIET is set to YES this implies that the +# messages are off. +# The default value is: NO. + +QUIET = NO + +# The WARNINGS tag can be used to turn on/off the warning messages that are +# generated to standard error (stderr) by doxygen. If WARNINGS is set to YES +# this implies that the warnings are on. +# +# Tip: Turn warnings on while writing the documentation. +# The default value is: YES. + +WARNINGS = YES + +# If the WARN_IF_UNDOCUMENTED tag is set to YES then doxygen will generate +# warnings for undocumented members. If EXTRACT_ALL is set to YES then this flag +# will automatically be disabled. +# The default value is: YES. + +WARN_IF_UNDOCUMENTED = YES + +# If the WARN_IF_DOC_ERROR tag is set to YES, doxygen will generate warnings for +# potential errors in the documentation, such as documenting some parameters in +# a documented function twice, or documenting parameters that don't exist or +# using markup commands wrongly. +# The default value is: YES. + +WARN_IF_DOC_ERROR = YES + +# If WARN_IF_INCOMPLETE_DOC is set to YES, doxygen will warn about incomplete +# function parameter documentation. If set to NO, doxygen will accept that some +# parameters have no documentation without warning. +# The default value is: YES. + +WARN_IF_INCOMPLETE_DOC = YES + +# This WARN_NO_PARAMDOC option can be enabled to get warnings for functions that +# are documented, but have no documentation for their parameters or return +# value. If set to NO, doxygen will only warn about wrong parameter +# documentation, but not about the absence of documentation. If EXTRACT_ALL is +# set to YES then this flag will automatically be disabled. See also +# WARN_IF_INCOMPLETE_DOC +# The default value is: NO. + +WARN_NO_PARAMDOC = NO + +# If WARN_IF_UNDOC_ENUM_VAL option is set to YES, doxygen will warn about +# undocumented enumeration values. If set to NO, doxygen will accept +# undocumented enumeration values. If EXTRACT_ALL is set to YES then this flag +# will automatically be disabled. +# The default value is: NO. + +WARN_IF_UNDOC_ENUM_VAL = NO + +# If the WARN_AS_ERROR tag is set to YES then doxygen will immediately stop when +# a warning is encountered. If the WARN_AS_ERROR tag is set to FAIL_ON_WARNINGS +# then doxygen will continue running as if WARN_AS_ERROR tag is set to NO, but +# at the end of the doxygen process doxygen will return with a non-zero status. +# If the WARN_AS_ERROR tag is set to FAIL_ON_WARNINGS_PRINT then doxygen behaves +# like FAIL_ON_WARNINGS but in case no WARN_LOGFILE is defined doxygen will not +# write the warning messages in between other messages but write them at the end +# of a run, in case a WARN_LOGFILE is defined the warning messages will be +# besides being in the defined file also be shown at the end of a run, unless +# the WARN_LOGFILE is defined as - i.e. standard output (stdout) in that case +# the behavior will remain as with the setting FAIL_ON_WARNINGS. +# Possible values are: NO, YES, FAIL_ON_WARNINGS and FAIL_ON_WARNINGS_PRINT. +# The default value is: NO. + +WARN_AS_ERROR = NO + +# The WARN_FORMAT tag determines the format of the warning messages that doxygen +# can produce. The string should contain the $file, $line, and $text tags, which +# will be replaced by the file and line number from which the warning originated +# and the warning text. Optionally the format may contain $version, which will +# be replaced by the version of the file (if it could be obtained via +# FILE_VERSION_FILTER) +# See also: WARN_LINE_FORMAT +# The default value is: $file:$line: $text. + +WARN_FORMAT = "$file:$line: $text" + +# In the $text part of the WARN_FORMAT command it is possible that a reference +# to a more specific place is given. To make it easier to jump to this place +# (outside of doxygen) the user can define a custom "cut" / "paste" string. +# Example: +# WARN_LINE_FORMAT = "'vi $file +$line'" +# See also: WARN_FORMAT +# The default value is: at line $line of file $file. + +WARN_LINE_FORMAT = "at line $line of file $file" + +# The WARN_LOGFILE tag can be used to specify a file to which warning and error +# messages should be written. If left blank the output is written to standard +# error (stderr). In case the file specified cannot be opened for writing the +# warning and error messages are written to standard error. When as file - is +# specified the warning and error messages are written to standard output +# (stdout). + +WARN_LOGFILE = + +#--------------------------------------------------------------------------- +# Configuration options related to the input files +#--------------------------------------------------------------------------- + +# The INPUT tag is used to specify the files and/or directories that contain +# documented source files. You may enter file names like myfile.cpp or +# directories like /usr/src/myproject. Separate the files or directories with +# spaces. See also FILE_PATTERNS and EXTENSION_MAPPING +# Note: If this tag is empty the current directory is searched. + +INPUT = include/plist/plist.h \ + docs/README.doxygen.md + +# This tag can be used to specify the character encoding of the source files +# that doxygen parses. Internally doxygen uses the UTF-8 encoding. Doxygen uses +# libiconv (or the iconv built into libc) for the transcoding. See the libiconv +# documentation (see: +# https://www.gnu.org/software/libiconv/) for the list of possible encodings. +# See also: INPUT_FILE_ENCODING +# The default value is: UTF-8. + +INPUT_ENCODING = UTF-8 + +# This tag can be used to specify the character encoding of the source files +# that doxygen parses The INPUT_FILE_ENCODING tag can be used to specify +# character encoding on a per file pattern basis. Doxygen will compare the file +# name with each pattern and apply the encoding instead of the default +# INPUT_ENCODING) if there is a match. The character encodings are a list of the +# form: pattern=encoding (like *.php=ISO-8859-1). +# See also: INPUT_ENCODING for further information on supported encodings. + +INPUT_FILE_ENCODING = + +# If the value of the INPUT tag contains directories, you can use the +# FILE_PATTERNS tag to specify one or more wildcard patterns (like *.cpp and +# *.h) to filter out the source-files in the directories. +# +# Note that for custom extensions or not directly supported extensions you also +# need to set EXTENSION_MAPPING for the extension otherwise the files are not +# read by doxygen. +# +# Note the list of default checked file patterns might differ from the list of +# default file extension mappings. +# +# If left blank the following patterns are tested:*.c, *.cc, *.cxx, *.cxxm, +# *.cpp, *.cppm, *.ccm, *.c++, *.c++m, *.java, *.ii, *.ixx, *.ipp, *.i++, *.inl, +# *.idl, *.ddl, *.odl, *.h, *.hh, *.hxx, *.hpp, *.h++, *.ixx, *.l, *.cs, *.d, +# *.php, *.php4, *.php5, *.phtml, *.inc, *.m, *.markdown, *.md, *.mm, *.dox (to +# be provided as doxygen C comment), *.py, *.pyw, *.f90, *.f95, *.f03, *.f08, +# *.f18, *.f, *.for, *.vhd, *.vhdl, *.ucf, *.qsf and *.ice. + +FILE_PATTERNS = + +# The RECURSIVE tag can be used to specify whether or not subdirectories should +# be searched for input files as well. +# The default value is: NO. + +RECURSIVE = NO + +# The EXCLUDE tag can be used to specify files and/or directories that should be +# excluded from the INPUT source files. This way you can easily exclude a +# subdirectory from a directory tree whose root is specified with the INPUT tag. +# +# Note that relative paths are relative to the directory from which doxygen is +# run. + +EXCLUDE = + +# The EXCLUDE_SYMLINKS tag can be used to select whether or not files or +# directories that are symbolic links (a Unix file system feature) are excluded +# from the input. +# The default value is: NO. + +EXCLUDE_SYMLINKS = NO + +# If the value of the INPUT tag contains directories, you can use the +# EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude +# certain files from those directories. +# +# Note that the wildcards are matched against the file with absolute path, so to +# exclude all test directories for example use the pattern */test/* + +EXCLUDE_PATTERNS = + +# The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names +# (namespaces, classes, functions, etc.) that should be excluded from the +# output. The symbol name can be a fully qualified name, a word, or if the +# wildcard * is used, a substring. Examples: ANamespace, AClass, +# ANamespace::AClass, ANamespace::*Test + +EXCLUDE_SYMBOLS = PLIST_WARN_DEPRECATED PLIST_API PLIST_UINT PLIST_IS_UINT + +# The EXAMPLE_PATH tag can be used to specify one or more files or directories +# that contain example code fragments that are included (see the \include +# command). + +EXAMPLE_PATH = + +# If the value of the EXAMPLE_PATH tag contains directories, you can use the +# EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp and +# *.h) to filter out the source-files in the directories. If left blank all +# files are included. + +EXAMPLE_PATTERNS = + +# If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be +# searched for input files to be used with the \include or \dontinclude commands +# irrespective of the value of the RECURSIVE tag. +# The default value is: NO. + +EXAMPLE_RECURSIVE = NO + +# The IMAGE_PATH tag can be used to specify one or more files or directories +# that contain images that are to be included in the documentation (see the +# \image command). + +IMAGE_PATH = + +# The INPUT_FILTER tag can be used to specify a program that doxygen should +# invoke to filter for each input file. Doxygen will invoke the filter program +# by executing (via popen()) the command: +# +# <filter> <input-file> +# +# where <filter> is the value of the INPUT_FILTER tag, and <input-file> is the +# name of an input file. Doxygen will then use the output that the filter +# program writes to standard output. If FILTER_PATTERNS is specified, this tag +# will be ignored. +# +# Note that the filter must not add or remove lines; it is applied before the +# code is scanned, but not when the output code is generated. If lines are added +# or removed, the anchors will not be placed correctly. +# +# Note that doxygen will use the data processed and written to standard output +# for further processing, therefore nothing else, like debug statements or used +# commands (so in case of a Windows batch file always use @echo OFF), should be +# written to standard output. +# +# Note that for custom extensions or not directly supported extensions you also +# need to set EXTENSION_MAPPING for the extension otherwise the files are not +# properly processed by doxygen. + +INPUT_FILTER = + +# The FILTER_PATTERNS tag can be used to specify filters on a per file pattern +# basis. Doxygen will compare the file name with each pattern and apply the +# filter if there is a match. The filters are a list of the form: pattern=filter +# (like *.cpp=my_cpp_filter). See INPUT_FILTER for further information on how +# filters are used. If the FILTER_PATTERNS tag is empty or if none of the +# patterns match the file name, INPUT_FILTER is applied. +# +# Note that for custom extensions or not directly supported extensions you also +# need to set EXTENSION_MAPPING for the extension otherwise the files are not +# properly processed by doxygen. + +FILTER_PATTERNS = + +# If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using +# INPUT_FILTER) will also be used to filter the input files that are used for +# producing the source files to browse (i.e. when SOURCE_BROWSER is set to YES). +# The default value is: NO. + +FILTER_SOURCE_FILES = NO + +# The FILTER_SOURCE_PATTERNS tag can be used to specify source filters per file +# pattern. A pattern will override the setting for FILTER_PATTERN (if any) and +# it is also possible to disable source filtering for a specific pattern using +# *.ext= (so without naming a filter). +# This tag requires that the tag FILTER_SOURCE_FILES is set to YES. + +FILTER_SOURCE_PATTERNS = + +# If the USE_MDFILE_AS_MAINPAGE tag refers to the name of a markdown file that +# is part of the input, its contents will be placed on the main page +# (index.html). This can be useful if you have a project on for instance GitHub +# and want to reuse the introduction page also for the doxygen output. + +USE_MDFILE_AS_MAINPAGE = docs/README.doxygen.md + +# The Fortran standard specifies that for fixed formatted Fortran code all +# characters from position 72 are to be considered as comment. A common +# extension is to allow longer lines before the automatic comment starts. The +# setting FORTRAN_COMMENT_AFTER will also make it possible that longer lines can +# be processed before the automatic comment starts. +# Minimum value: 7, maximum value: 10000, default value: 72. + +FORTRAN_COMMENT_AFTER = 72 + +#--------------------------------------------------------------------------- +# Configuration options related to source browsing +#--------------------------------------------------------------------------- + +# If the SOURCE_BROWSER tag is set to YES then a list of source files will be +# generated. Documented entities will be cross-referenced with these sources. +# +# Note: To get rid of all source code in the generated output, make sure that +# also VERBATIM_HEADERS is set to NO. +# The default value is: NO. + +SOURCE_BROWSER = NO + +# Setting the INLINE_SOURCES tag to YES will include the body of functions, +# multi-line macros, enums or list initialized variables directly into the +# documentation. +# The default value is: NO. + +INLINE_SOURCES = NO + +# Setting the STRIP_CODE_COMMENTS tag to YES will instruct doxygen to hide any +# special comment blocks from generated source code fragments. Normal C, C++ and +# Fortran comments will always remain visible. +# The default value is: YES. + +STRIP_CODE_COMMENTS = YES + +# If the REFERENCED_BY_RELATION tag is set to YES then for each documented +# entity all documented functions referencing it will be listed. +# The default value is: NO. + +REFERENCED_BY_RELATION = YES + +# If the REFERENCES_RELATION tag is set to YES then for each documented function +# all documented entities called/used by that function will be listed. +# The default value is: NO. + +REFERENCES_RELATION = YES + +# If the REFERENCES_LINK_SOURCE tag is set to YES and SOURCE_BROWSER tag is set +# to YES then the hyperlinks from functions in REFERENCES_RELATION and +# REFERENCED_BY_RELATION lists will link to the source code. Otherwise they will +# link to the documentation. +# The default value is: YES. + +REFERENCES_LINK_SOURCE = YES + +# If SOURCE_TOOLTIPS is enabled (the default) then hovering a hyperlink in the +# source code will show a tooltip with additional information such as prototype, +# brief description and links to the definition and documentation. Since this +# will make the HTML file larger and loading of large files a bit slower, you +# can opt to disable this feature. +# The default value is: YES. +# This tag requires that the tag SOURCE_BROWSER is set to YES. + +SOURCE_TOOLTIPS = YES + +# If the USE_HTAGS tag is set to YES then the references to source code will +# point to the HTML generated by the htags(1) tool instead of doxygen built-in +# source browser. The htags tool is part of GNU's global source tagging system +# (see https://www.gnu.org/software/global/global.html). You will need version +# 4.8.6 or higher. +# +# To use it do the following: +# - Install the latest version of global +# - Enable SOURCE_BROWSER and USE_HTAGS in the configuration file +# - Make sure the INPUT points to the root of the source tree +# - Run doxygen as normal +# +# Doxygen will invoke htags (and that will in turn invoke gtags), so these +# tools must be available from the command line (i.e. in the search path). +# +# The result: instead of the source browser generated by doxygen, the links to +# source code will now point to the output of htags. +# The default value is: NO. +# This tag requires that the tag SOURCE_BROWSER is set to YES. + +USE_HTAGS = NO + +# If the VERBATIM_HEADERS tag is set the YES then doxygen will generate a +# verbatim copy of the header file for each class for which an include is +# specified. Set to NO to disable this. +# See also: Section \class. +# The default value is: YES. + +VERBATIM_HEADERS = NO + +#--------------------------------------------------------------------------- +# Configuration options related to the alphabetical class index +#--------------------------------------------------------------------------- + +# If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index of all +# compounds will be generated. Enable this if the project contains a lot of +# classes, structs, unions or interfaces. +# The default value is: YES. + +ALPHABETICAL_INDEX = NO + +# The IGNORE_PREFIX tag can be used to specify a prefix (or a list of prefixes) +# that should be ignored while generating the index headers. The IGNORE_PREFIX +# tag works for classes, function and member names. The entity will be placed in +# the alphabetical list under the first letter of the entity name that remains +# after removing the prefix. +# This tag requires that the tag ALPHABETICAL_INDEX is set to YES. + +IGNORE_PREFIX = + +#--------------------------------------------------------------------------- +# Configuration options related to the HTML output +#--------------------------------------------------------------------------- + +# If the GENERATE_HTML tag is set to YES, doxygen will generate HTML output +# The default value is: YES. + +GENERATE_HTML = YES + +# The HTML_OUTPUT tag is used to specify where the HTML docs will be put. If a +# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of +# it. +# The default directory is: html. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_OUTPUT = html + +# The HTML_FILE_EXTENSION tag can be used to specify the file extension for each +# generated HTML page (for example: .htm, .php, .asp). +# The default value is: .html. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_FILE_EXTENSION = .html + +# The HTML_HEADER tag can be used to specify a user-defined HTML header file for +# each generated HTML page. If the tag is left blank doxygen will generate a +# standard header. +# +# To get valid HTML the header file that includes any scripts and style sheets +# that doxygen needs, which is dependent on the configuration options used (e.g. +# the setting GENERATE_TREEVIEW). It is highly recommended to start with a +# default header using +# doxygen -w html new_header.html new_footer.html new_stylesheet.css +# YourConfigFile +# and then modify the file new_header.html. See also section "Doxygen usage" +# for information on how to generate the default header that doxygen normally +# uses. +# Note: The header is subject to change so you typically have to regenerate the +# default header when upgrading to a newer version of doxygen. For a description +# of the possible markers and block names see the documentation. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_HEADER = docs/doxygen/header.html + +# The HTML_FOOTER tag can be used to specify a user-defined HTML footer for each +# generated HTML page. If the tag is left blank doxygen will generate a standard +# footer. See HTML_HEADER for more information on how to generate a default +# footer and what special commands can be used inside the footer. See also +# section "Doxygen usage" for information on how to generate the default footer +# that doxygen normally uses. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_FOOTER = docs/doxygen/footer.html + +# The HTML_STYLESHEET tag can be used to specify a user-defined cascading style +# sheet that is used by each HTML page. It can be used to fine-tune the look of +# the HTML output. If left blank doxygen will generate a default style sheet. +# See also section "Doxygen usage" for information on how to generate the style +# sheet that doxygen normally uses. +# Note: It is recommended to use HTML_EXTRA_STYLESHEET instead of this tag, as +# it is more robust and this tag (HTML_STYLESHEET) will in the future become +# obsolete. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_STYLESHEET = + +# The HTML_EXTRA_STYLESHEET tag can be used to specify additional user-defined +# cascading style sheets that are included after the standard style sheets +# created by doxygen. Using this option one can overrule certain style aspects. +# This is preferred over using HTML_STYLESHEET since it does not replace the +# standard style sheet and is therefore more robust against future updates. +# Doxygen will copy the style sheet files to the output directory. +# Note: The order of the extra style sheet files is of importance (e.g. the last +# style sheet in the list overrules the setting of the previous ones in the +# list). +# Note: Since the styling of scrollbars can currently not be overruled in +# Webkit/Chromium, the styling will be left out of the default doxygen.css if +# one or more extra stylesheets have been specified. So if scrollbar +# customization is desired it has to be added explicitly. For an example see the +# documentation. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_EXTRA_STYLESHEET = docs/doxygen/custom.css + +# The HTML_EXTRA_FILES tag can be used to specify one or more extra images or +# other source files which should be copied to the HTML output directory. Note +# that these files will be copied to the base HTML output directory. Use the +# $relpath^ marker in the HTML_HEADER and/or HTML_FOOTER files to load these +# files. In the HTML_STYLESHEET file, use the file name only. Also note that the +# files will be copied as-is; there are no commands or markers available. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_EXTRA_FILES = docs/doxygen/favicon.ico \ + docs/doxygen/folder.png \ + docs/doxygen/folder-open.png \ + docs/doxygen/text-x-generic.png + +# The HTML_COLORSTYLE tag can be used to specify if the generated HTML output +# should be rendered with a dark or light theme. +# Possible values are: LIGHT always generates light mode output, DARK always +# generates dark mode output, AUTO_LIGHT automatically sets the mode according +# to the user preference, uses light mode if no preference is set (the default), +# AUTO_DARK automatically sets the mode according to the user preference, uses +# dark mode if no preference is set and TOGGLE allows a user to switch between +# light and dark mode via a button. +# The default value is: AUTO_LIGHT. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_COLORSTYLE = AUTO_LIGHT + +# The HTML_COLORSTYLE_HUE tag controls the color of the HTML output. Doxygen +# will adjust the colors in the style sheet and background images according to +# this color. Hue is specified as an angle on a color-wheel, see +# https://en.wikipedia.org/wiki/Hue for more information. For instance the value +# 0 represents red, 60 is yellow, 120 is green, 180 is cyan, 240 is blue, 300 +# purple, and 360 is red again. +# Minimum value: 0, maximum value: 359, default value: 220. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_COLORSTYLE_HUE = 220 + +# The HTML_COLORSTYLE_SAT tag controls the purity (or saturation) of the colors +# in the HTML output. For a value of 0 the output will use gray-scales only. A +# value of 255 will produce the most vivid colors. +# Minimum value: 0, maximum value: 255, default value: 100. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_COLORSTYLE_SAT = 100 + +# The HTML_COLORSTYLE_GAMMA tag controls the gamma correction applied to the +# luminance component of the colors in the HTML output. Values below 100 +# gradually make the output lighter, whereas values above 100 make the output +# darker. The value divided by 100 is the actual gamma applied, so 80 represents +# a gamma of 0.8, The value 220 represents a gamma of 2.2, and 100 does not +# change the gamma. +# Minimum value: 40, maximum value: 240, default value: 80. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_COLORSTYLE_GAMMA = 80 + +# If the HTML_DYNAMIC_MENUS tag is set to YES then the generated HTML +# documentation will contain a main index with vertical navigation menus that +# are dynamically created via JavaScript. If disabled, the navigation index will +# consists of multiple levels of tabs that are statically embedded in every HTML +# page. Disable this option to support browsers that do not have JavaScript, +# like the Qt help browser. +# The default value is: YES. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_DYNAMIC_MENUS = NO + +# If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML +# documentation will contain sections that can be hidden and shown after the +# page has loaded. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_DYNAMIC_SECTIONS = NO + +# If the HTML_CODE_FOLDING tag is set to YES then classes and functions can be +# dynamically folded and expanded in the generated HTML source code. +# The default value is: YES. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_CODE_FOLDING = YES + +# If the HTML_COPY_CLIPBOARD tag is set to YES then doxygen will show an icon in +# the top right corner of code and text fragments that allows the user to copy +# its content to the clipboard. Note this only works if supported by the browser +# and the web page is served via a secure context (see: +# https://www.w3.org/TR/secure-contexts/), i.e. using the https: or file: +# protocol. +# The default value is: YES. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_COPY_CLIPBOARD = YES + +# Doxygen stores a couple of settings persistently in the browser (via e.g. +# cookies). By default these settings apply to all HTML pages generated by +# doxygen across all projects. The HTML_PROJECT_COOKIE tag can be used to store +# the settings under a project specific key, such that the user preferences will +# be stored separately. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_PROJECT_COOKIE = + +# With HTML_INDEX_NUM_ENTRIES one can control the preferred number of entries +# shown in the various tree structured indices initially; the user can expand +# and collapse entries dynamically later on. Doxygen will expand the tree to +# such a level that at most the specified number of entries are visible (unless +# a fully collapsed tree already exceeds this amount). So setting the number of +# entries 1 will produce a full collapsed tree by default. 0 is a special value +# representing an infinite number of entries and will result in a full expanded +# tree by default. +# Minimum value: 0, maximum value: 9999, default value: 100. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_INDEX_NUM_ENTRIES = 100 + +# If the GENERATE_DOCSET tag is set to YES, additional index files will be +# generated that can be used as input for Apple's Xcode 3 integrated development +# environment (see: +# https://developer.apple.com/xcode/), introduced with OSX 10.5 (Leopard). To +# create a documentation set, doxygen will generate a Makefile in the HTML +# output directory. Running make will produce the docset in that directory and +# running make install will install the docset in +# ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find it at +# startup. See https://developer.apple.com/library/archive/featuredarticles/Doxy +# genXcode/_index.html for more information. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_DOCSET = NO + +# This tag determines the name of the docset feed. A documentation feed provides +# an umbrella under which multiple documentation sets from a single provider +# (such as a company or product suite) can be grouped. +# The default value is: Doxygen generated docs. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_FEEDNAME = "Doxygen generated docs" + +# This tag determines the URL of the docset feed. A documentation feed provides +# an umbrella under which multiple documentation sets from a single provider +# (such as a company or product suite) can be grouped. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_FEEDURL = + +# This tag specifies a string that should uniquely identify the documentation +# set bundle. This should be a reverse domain-name style string, e.g. +# com.mycompany.MyDocSet. Doxygen will append .docset to the name. +# The default value is: org.doxygen.Project. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_BUNDLE_ID = org.doxygen.Project + +# The DOCSET_PUBLISHER_ID tag specifies a string that should uniquely identify +# the documentation publisher. This should be a reverse domain-name style +# string, e.g. com.mycompany.MyDocSet.documentation. +# The default value is: org.doxygen.Publisher. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_PUBLISHER_ID = org.doxygen.Publisher + +# The DOCSET_PUBLISHER_NAME tag identifies the documentation publisher. +# The default value is: Publisher. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_PUBLISHER_NAME = Publisher + +# If the GENERATE_HTMLHELP tag is set to YES then doxygen generates three +# additional HTML index files: index.hhp, index.hhc, and index.hhk. The +# index.hhp is a project file that can be read by Microsoft's HTML Help Workshop +# on Windows. In the beginning of 2021 Microsoft took the original page, with +# a.o. the download links, offline the HTML help workshop was already many years +# in maintenance mode). You can download the HTML help workshop from the web +# archives at Installation executable (see: +# http://web.archive.org/web/20160201063255/http://download.microsoft.com/downlo +# ad/0/A/9/0A939EF6-E31C-430F-A3DF-DFAE7960D564/htmlhelp.exe). +# +# The HTML Help Workshop contains a compiler that can convert all HTML output +# generated by doxygen into a single compiled HTML file (.chm). Compiled HTML +# files are now used as the Windows 98 help format, and will replace the old +# Windows help format (.hlp) on all Windows platforms in the future. Compressed +# HTML files also contain an index, a table of contents, and you can search for +# words in the documentation. The HTML workshop also contains a viewer for +# compressed HTML files. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_HTMLHELP = NO + +# The CHM_FILE tag can be used to specify the file name of the resulting .chm +# file. You can add a path in front of the file if the result should not be +# written to the html output directory. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +CHM_FILE = + +# The HHC_LOCATION tag can be used to specify the location (absolute path +# including file name) of the HTML help compiler (hhc.exe). If non-empty, +# doxygen will try to run the HTML help compiler on the generated index.hhp. +# The file has to be specified with full path. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +HHC_LOCATION = + +# The GENERATE_CHI flag controls if a separate .chi index file is generated +# (YES) or that it should be included in the main .chm file (NO). +# The default value is: NO. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +GENERATE_CHI = NO + +# The CHM_INDEX_ENCODING is used to encode HtmlHelp index (hhk), content (hhc) +# and project file content. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +CHM_INDEX_ENCODING = + +# The BINARY_TOC flag controls whether a binary table of contents is generated +# (YES) or a normal table of contents (NO) in the .chm file. Furthermore it +# enables the Previous and Next buttons. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +BINARY_TOC = NO + +# The TOC_EXPAND flag can be set to YES to add extra items for group members to +# the table of contents of the HTML help documentation and to the tree view. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +TOC_EXPAND = NO + +# The SITEMAP_URL tag is used to specify the full URL of the place where the +# generated documentation will be placed on the server by the user during the +# deployment of the documentation. The generated sitemap is called sitemap.xml +# and placed on the directory specified by HTML_OUTPUT. In case no SITEMAP_URL +# is specified no sitemap is generated. For information about the sitemap +# protocol see https://www.sitemaps.org +# This tag requires that the tag GENERATE_HTML is set to YES. + +SITEMAP_URL = + +# If the GENERATE_QHP tag is set to YES and both QHP_NAMESPACE and +# QHP_VIRTUAL_FOLDER are set, an additional index file will be generated that +# can be used as input for Qt's qhelpgenerator to generate a Qt Compressed Help +# (.qch) of the generated HTML documentation. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_QHP = NO + +# If the QHG_LOCATION tag is specified, the QCH_FILE tag can be used to specify +# the file name of the resulting .qch file. The path specified is relative to +# the HTML output folder. +# This tag requires that the tag GENERATE_QHP is set to YES. + +QCH_FILE = + +# The QHP_NAMESPACE tag specifies the namespace to use when generating Qt Help +# Project output. For more information please see Qt Help Project / Namespace +# (see: +# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#namespace). +# The default value is: org.doxygen.Project. +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_NAMESPACE = org.doxygen.Project + +# The QHP_VIRTUAL_FOLDER tag specifies the namespace to use when generating Qt +# Help Project output. For more information please see Qt Help Project / Virtual +# Folders (see: +# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#virtual-folders). +# The default value is: doc. +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_VIRTUAL_FOLDER = doc + +# If the QHP_CUST_FILTER_NAME tag is set, it specifies the name of a custom +# filter to add. For more information please see Qt Help Project / Custom +# Filters (see: +# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#custom-filters). +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_CUST_FILTER_NAME = + +# The QHP_CUST_FILTER_ATTRS tag specifies the list of the attributes of the +# custom filter to add. For more information please see Qt Help Project / Custom +# Filters (see: +# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#custom-filters). +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_CUST_FILTER_ATTRS = + +# The QHP_SECT_FILTER_ATTRS tag specifies the list of the attributes this +# project's filter section matches. Qt Help Project / Filter Attributes (see: +# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#filter-attributes). +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_SECT_FILTER_ATTRS = + +# The QHG_LOCATION tag can be used to specify the location (absolute path +# including file name) of Qt's qhelpgenerator. If non-empty doxygen will try to +# run qhelpgenerator on the generated .qhp file. +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHG_LOCATION = + +# If the GENERATE_ECLIPSEHELP tag is set to YES, additional index files will be +# generated, together with the HTML files, they form an Eclipse help plugin. To +# install this plugin and make it available under the help contents menu in +# Eclipse, the contents of the directory containing the HTML and XML files needs +# to be copied into the plugins directory of eclipse. The name of the directory +# within the plugins directory should be the same as the ECLIPSE_DOC_ID value. +# After copying Eclipse needs to be restarted before the help appears. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_ECLIPSEHELP = NO + +# A unique identifier for the Eclipse help plugin. When installing the plugin +# the directory name containing the HTML and XML files should also have this +# name. Each documentation set should have its own identifier. +# The default value is: org.doxygen.Project. +# This tag requires that the tag GENERATE_ECLIPSEHELP is set to YES. + +ECLIPSE_DOC_ID = org.doxygen.Project + +# If you want full control over the layout of the generated HTML pages it might +# be necessary to disable the index and replace it with your own. The +# DISABLE_INDEX tag can be used to turn on/off the condensed index (tabs) at top +# of each HTML page. A value of NO enables the index and the value YES disables +# it. Since the tabs in the index contain the same information as the navigation +# tree, you can set this option to YES if you also set GENERATE_TREEVIEW to YES. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +DISABLE_INDEX = NO + +# The GENERATE_TREEVIEW tag is used to specify whether a tree-like index +# structure should be generated to display hierarchical information. If the tag +# value is set to YES, a side panel will be generated containing a tree-like +# index structure (just like the one that is generated for HTML Help). For this +# to work a browser that supports JavaScript, DHTML, CSS and frames is required +# (i.e. any modern browser). Windows users are probably better off using the +# HTML help feature. Via custom style sheets (see HTML_EXTRA_STYLESHEET) one can +# further fine tune the look of the index (see "Fine-tuning the output"). As an +# example, the default style sheet generated by doxygen has an example that +# shows how to put an image at the root of the tree instead of the PROJECT_NAME. +# Since the tree basically has the same information as the tab index, you could +# consider setting DISABLE_INDEX to YES when enabling this option. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_TREEVIEW = NONE + +# When both GENERATE_TREEVIEW and DISABLE_INDEX are set to YES, then the +# FULL_SIDEBAR option determines if the side bar is limited to only the treeview +# area (value NO) or if it should extend to the full height of the window (value +# YES). Setting this to YES gives a layout similar to +# https://docs.readthedocs.io with more room for contents, but less room for the +# project logo, title, and description. If either GENERATE_TREEVIEW or +# DISABLE_INDEX is set to NO, this option has no effect. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +FULL_SIDEBAR = NO + +# The ENUM_VALUES_PER_LINE tag can be used to set the number of enum values that +# doxygen will group on one line in the generated HTML documentation. +# +# Note that a value of 0 will completely suppress the enum values from appearing +# in the overview section. +# Minimum value: 0, maximum value: 20, default value: 4. +# This tag requires that the tag GENERATE_HTML is set to YES. + +ENUM_VALUES_PER_LINE = 1 + +# If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be used +# to set the initial width (in pixels) of the frame in which the tree is shown. +# Minimum value: 0, maximum value: 1500, default value: 250. +# This tag requires that the tag GENERATE_HTML is set to YES. + +TREEVIEW_WIDTH = 250 + +# If the EXT_LINKS_IN_WINDOW option is set to YES, doxygen will open links to +# external symbols imported via tag files in a separate window. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +EXT_LINKS_IN_WINDOW = NO + +# If the OBFUSCATE_EMAILS tag is set to YES, doxygen will obfuscate email +# addresses. +# The default value is: YES. +# This tag requires that the tag GENERATE_HTML is set to YES. + +OBFUSCATE_EMAILS = YES + +# If the HTML_FORMULA_FORMAT option is set to svg, doxygen will use the pdf2svg +# tool (see https://github.com/dawbarton/pdf2svg) or inkscape (see +# https://inkscape.org) to generate formulas as SVG images instead of PNGs for +# the HTML output. These images will generally look nicer at scaled resolutions. +# Possible values are: png (the default) and svg (looks nicer but requires the +# pdf2svg or inkscape tool). +# The default value is: png. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_FORMULA_FORMAT = png + +# Use this tag to change the font size of LaTeX formulas included as images in +# the HTML documentation. When you change the font size after a successful +# doxygen run you need to manually remove any form_*.png images from the HTML +# output directory to force them to be regenerated. +# Minimum value: 8, maximum value: 50, default value: 10. +# This tag requires that the tag GENERATE_HTML is set to YES. + +FORMULA_FONTSIZE = 10 + +# The FORMULA_MACROFILE can contain LaTeX \newcommand and \renewcommand commands +# to create new LaTeX commands to be used in formulas as building blocks. See +# the section "Including formulas" for details. + +FORMULA_MACROFILE = + +# Enable the USE_MATHJAX option to render LaTeX formulas using MathJax (see +# https://www.mathjax.org) which uses client side JavaScript for the rendering +# instead of using pre-rendered bitmaps. Use this if you do not have LaTeX +# installed or if you want to formulas look prettier in the HTML output. When +# enabled you may also need to install MathJax separately and configure the path +# to it using the MATHJAX_RELPATH option. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +USE_MATHJAX = NO + +# With MATHJAX_VERSION it is possible to specify the MathJax version to be used. +# Note that the different versions of MathJax have different requirements with +# regards to the different settings, so it is possible that also other MathJax +# settings have to be changed when switching between the different MathJax +# versions. +# Possible values are: MathJax_2 and MathJax_3. +# The default value is: MathJax_2. +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_VERSION = MathJax_2 + +# When MathJax is enabled you can set the default output format to be used for +# the MathJax output. For more details about the output format see MathJax +# version 2 (see: +# http://docs.mathjax.org/en/v2.7-latest/output.html) and MathJax version 3 +# (see: +# http://docs.mathjax.org/en/latest/web/components/output.html). +# Possible values are: HTML-CSS (which is slower, but has the best +# compatibility. This is the name for Mathjax version 2, for MathJax version 3 +# this will be translated into chtml), NativeMML (i.e. MathML. Only supported +# for MathJax 2. For MathJax version 3 chtml will be used instead.), chtml (This +# is the name for Mathjax version 3, for MathJax version 2 this will be +# translated into HTML-CSS) and SVG. +# The default value is: HTML-CSS. +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_FORMAT = HTML-CSS + +# When MathJax is enabled you need to specify the location relative to the HTML +# output directory using the MATHJAX_RELPATH option. The destination directory +# should contain the MathJax.js script. For instance, if the mathjax directory +# is located at the same level as the HTML output directory, then +# MATHJAX_RELPATH should be ../mathjax. The default value points to the MathJax +# Content Delivery Network so you can quickly see the result without installing +# MathJax. However, it is strongly recommended to install a local copy of +# MathJax from https://www.mathjax.org before deployment. The default value is: +# - in case of MathJax version 2: https://cdn.jsdelivr.net/npm/mathjax@2 +# - in case of MathJax version 3: https://cdn.jsdelivr.net/npm/mathjax@3 +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_RELPATH = http://cdn.mathjax.org/mathjax/latest + +# The MATHJAX_EXTENSIONS tag can be used to specify one or more MathJax +# extension names that should be enabled during MathJax rendering. For example +# for MathJax version 2 (see +# https://docs.mathjax.org/en/v2.7-latest/tex.html#tex-and-latex-extensions): +# MATHJAX_EXTENSIONS = TeX/AMSmath TeX/AMSsymbols +# For example for MathJax version 3 (see +# http://docs.mathjax.org/en/latest/input/tex/extensions/index.html): +# MATHJAX_EXTENSIONS = ams +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_EXTENSIONS = + +# The MATHJAX_CODEFILE tag can be used to specify a file with javascript pieces +# of code that will be used on startup of the MathJax code. See the MathJax site +# (see: +# http://docs.mathjax.org/en/v2.7-latest/output.html) for more details. For an +# example see the documentation. +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_CODEFILE = + +# When the SEARCHENGINE tag is enabled doxygen will generate a search box for +# the HTML output. The underlying search engine uses javascript and DHTML and +# should work on any modern browser. Note that when using HTML help +# (GENERATE_HTMLHELP), Qt help (GENERATE_QHP), or docsets (GENERATE_DOCSET) +# there is already a search function so this one should typically be disabled. +# For large projects the javascript based search engine can be slow, then +# enabling SERVER_BASED_SEARCH may provide a better solution. It is possible to +# search using the keyboard; to jump to the search box use <access key> + S +# (what the <access key> is depends on the OS and browser, but it is typically +# <CTRL>, <ALT>/<option>, or both). Inside the search box use the <cursor down +# key> to jump into the search results window, the results can be navigated +# using the <cursor keys>. Press <Enter> to select an item or <escape> to cancel +# the search. The filter options can be selected when the cursor is inside the +# search box by pressing <Shift>+<cursor down>. Also here use the <cursor keys> +# to select a filter and <Enter> or <escape> to activate or cancel the filter +# option. +# The default value is: YES. +# This tag requires that the tag GENERATE_HTML is set to YES. + +SEARCHENGINE = NO + +# When the SERVER_BASED_SEARCH tag is enabled the search engine will be +# implemented using a web server instead of a web client using JavaScript. There +# are two flavors of web server based searching depending on the EXTERNAL_SEARCH +# setting. When disabled, doxygen will generate a PHP script for searching and +# an index file used by the script. When EXTERNAL_SEARCH is enabled the indexing +# and searching needs to be provided by external tools. See the section +# "External Indexing and Searching" for details. +# The default value is: NO. +# This tag requires that the tag SEARCHENGINE is set to YES. + +SERVER_BASED_SEARCH = NO + +# When EXTERNAL_SEARCH tag is enabled doxygen will no longer generate the PHP +# script for searching. Instead the search results are written to an XML file +# which needs to be processed by an external indexer. Doxygen will invoke an +# external search engine pointed to by the SEARCHENGINE_URL option to obtain the +# search results. +# +# Doxygen ships with an example indexer (doxyindexer) and search engine +# (doxysearch.cgi) which are based on the open source search engine library +# Xapian (see: +# https://xapian.org/). +# +# See the section "External Indexing and Searching" for details. +# The default value is: NO. +# This tag requires that the tag SEARCHENGINE is set to YES. + +EXTERNAL_SEARCH = NO + +# The SEARCHENGINE_URL should point to a search engine hosted by a web server +# which will return the search results when EXTERNAL_SEARCH is enabled. +# +# Doxygen ships with an example indexer (doxyindexer) and search engine +# (doxysearch.cgi) which are based on the open source search engine library +# Xapian (see: +# https://xapian.org/). See the section "External Indexing and Searching" for +# details. +# This tag requires that the tag SEARCHENGINE is set to YES. + +SEARCHENGINE_URL = + +# When SERVER_BASED_SEARCH and EXTERNAL_SEARCH are both enabled the unindexed +# search data is written to a file for indexing by an external tool. With the +# SEARCHDATA_FILE tag the name of this file can be specified. +# The default file is: searchdata.xml. +# This tag requires that the tag SEARCHENGINE is set to YES. + +SEARCHDATA_FILE = searchdata.xml + +# When SERVER_BASED_SEARCH and EXTERNAL_SEARCH are both enabled the +# EXTERNAL_SEARCH_ID tag can be used as an identifier for the project. This is +# useful in combination with EXTRA_SEARCH_MAPPINGS to search through multiple +# projects and redirect the results back to the right project. +# This tag requires that the tag SEARCHENGINE is set to YES. + +EXTERNAL_SEARCH_ID = + +# The EXTRA_SEARCH_MAPPINGS tag can be used to enable searching through doxygen +# projects other than the one defined by this configuration file, but that are +# all added to the same external search index. Each project needs to have a +# unique id set via EXTERNAL_SEARCH_ID. The search mapping then maps the id of +# to a relative location where the documentation can be found. The format is: +# EXTRA_SEARCH_MAPPINGS = tagname1=loc1 tagname2=loc2 ... +# This tag requires that the tag SEARCHENGINE is set to YES. + +EXTRA_SEARCH_MAPPINGS = + +#--------------------------------------------------------------------------- +# Configuration options related to the LaTeX output +#--------------------------------------------------------------------------- + +# If the GENERATE_LATEX tag is set to YES, doxygen will generate LaTeX output. +# The default value is: YES. + +GENERATE_LATEX = NO + +# The LATEX_OUTPUT tag is used to specify where the LaTeX docs will be put. If a +# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of +# it. +# The default directory is: latex. +# This tag requires that the tag GENERATE_LATEX is set to YES. + +LATEX_OUTPUT = latex + +# The LATEX_CMD_NAME tag can be used to specify the LaTeX command name to be +# invoked. +# +# Note that when not enabling USE_PDFLATEX the default is latex when enabling +# USE_PDFLATEX the default is pdflatex and when in the later case latex is +# chosen this is overwritten by pdflatex. For specific output languages the +# default can have been set differently, this depends on the implementation of +# the output language. +# This tag requires that the tag GENERATE_LATEX is set to YES. + +LATEX_CMD_NAME = latex + +# The MAKEINDEX_CMD_NAME tag can be used to specify the command name to generate +# index for LaTeX. +# Note: This tag is used in the Makefile / make.bat. +# See also: LATEX_MAKEINDEX_CMD for the part in the generated output file +# (.tex). +# The default file is: makeindex. +# This tag requires that the tag GENERATE_LATEX is set to YES. + +MAKEINDEX_CMD_NAME = makeindex + +# The LATEX_MAKEINDEX_CMD tag can be used to specify the command name to +# generate index for LaTeX. In case there is no backslash (\) as first character +# it will be automatically added in the LaTeX code. +# Note: This tag is used in the generated output file (.tex). +# See also: MAKEINDEX_CMD_NAME for the part in the Makefile / make.bat. +# The default value is: makeindex. +# This tag requires that the tag GENERATE_LATEX is set to YES. + +LATEX_MAKEINDEX_CMD = makeindex + +# If the COMPACT_LATEX tag is set to YES, doxygen generates more compact LaTeX +# documents. This may be useful for small projects and may help to save some +# trees in general. +# The default value is: NO. +# This tag requires that the tag GENERATE_LATEX is set to YES. + +COMPACT_LATEX = NO + +# The PAPER_TYPE tag can be used to set the paper type that is used by the +# printer. +# Possible values are: a4 (210 x 297 mm), letter (8.5 x 11 inches), legal (8.5 x +# 14 inches) and executive (7.25 x 10.5 inches). +# The default value is: a4. +# This tag requires that the tag GENERATE_LATEX is set to YES. + +PAPER_TYPE = a4 + +# The EXTRA_PACKAGES tag can be used to specify one or more LaTeX package names +# that should be included in the LaTeX output. The package can be specified just +# by its name or with the correct syntax as to be used with the LaTeX +# \usepackage command. To get the times font for instance you can specify : +# EXTRA_PACKAGES=times or EXTRA_PACKAGES={times} +# To use the option intlimits with the amsmath package you can specify: +# EXTRA_PACKAGES=[intlimits]{amsmath} +# If left blank no extra packages will be included. +# This tag requires that the tag GENERATE_LATEX is set to YES. + +EXTRA_PACKAGES = + +# The LATEX_HEADER tag can be used to specify a user-defined LaTeX header for +# the generated LaTeX document. The header should contain everything until the +# first chapter. If it is left blank doxygen will generate a standard header. It +# is highly recommended to start with a default header using +# doxygen -w latex new_header.tex new_footer.tex new_stylesheet.sty +# and then modify the file new_header.tex. See also section "Doxygen usage" for +# information on how to generate the default header that doxygen normally uses. +# +# Note: Only use a user-defined header if you know what you are doing! +# Note: The header is subject to change so you typically have to regenerate the +# default header when upgrading to a newer version of doxygen. The following +# commands have a special meaning inside the header (and footer): For a +# description of the possible markers and block names see the documentation. +# This tag requires that the tag GENERATE_LATEX is set to YES. + +LATEX_HEADER = + +# The LATEX_FOOTER tag can be used to specify a user-defined LaTeX footer for +# the generated LaTeX document. The footer should contain everything after the +# last chapter. If it is left blank doxygen will generate a standard footer. See +# LATEX_HEADER for more information on how to generate a default footer and what +# special commands can be used inside the footer. See also section "Doxygen +# usage" for information on how to generate the default footer that doxygen +# normally uses. Note: Only use a user-defined footer if you know what you are +# doing! +# This tag requires that the tag GENERATE_LATEX is set to YES. + +LATEX_FOOTER = + +# The LATEX_EXTRA_STYLESHEET tag can be used to specify additional user-defined +# LaTeX style sheets that are included after the standard style sheets created +# by doxygen. Using this option one can overrule certain style aspects. Doxygen +# will copy the style sheet files to the output directory. +# Note: The order of the extra style sheet files is of importance (e.g. the last +# style sheet in the list overrules the setting of the previous ones in the +# list). +# This tag requires that the tag GENERATE_LATEX is set to YES. + +LATEX_EXTRA_STYLESHEET = + +# The LATEX_EXTRA_FILES tag can be used to specify one or more extra images or +# other source files which should be copied to the LATEX_OUTPUT output +# directory. Note that the files will be copied as-is; there are no commands or +# markers available. +# This tag requires that the tag GENERATE_LATEX is set to YES. + +LATEX_EXTRA_FILES = + +# If the PDF_HYPERLINKS tag is set to YES, the LaTeX that is generated is +# prepared for conversion to PDF (using ps2pdf or pdflatex). The PDF file will +# contain links (just like the HTML output) instead of page references. This +# makes the output suitable for online browsing using a PDF viewer. +# The default value is: YES. +# This tag requires that the tag GENERATE_LATEX is set to YES. + +PDF_HYPERLINKS = NO + +# If the USE_PDFLATEX tag is set to YES, doxygen will use the engine as +# specified with LATEX_CMD_NAME to generate the PDF file directly from the LaTeX +# files. Set this option to YES, to get a higher quality PDF documentation. +# +# See also section LATEX_CMD_NAME for selecting the engine. +# The default value is: YES. +# This tag requires that the tag GENERATE_LATEX is set to YES. + +USE_PDFLATEX = NO + +# The LATEX_BATCHMODE tag signals the behavior of LaTeX in case of an error. +# Possible values are: NO same as ERROR_STOP, YES same as BATCH, BATCH In batch +# mode nothing is printed on the terminal, errors are scrolled as if <return> is +# hit at every error; missing files that TeX tries to input or request from +# keyboard input (\read on a not open input stream) cause the job to abort, +# NON_STOP In nonstop mode the diagnostic message will appear on the terminal, +# but there is no possibility of user interaction just like in batch mode, +# SCROLL In scroll mode, TeX will stop only for missing files to input or if +# keyboard input is necessary and ERROR_STOP In errorstop mode, TeX will stop at +# each error, asking for user intervention. +# The default value is: NO. +# This tag requires that the tag GENERATE_LATEX is set to YES. + +LATEX_BATCHMODE = NO + +# If the LATEX_HIDE_INDICES tag is set to YES then doxygen will not include the +# index chapters (such as File Index, Compound Index, etc.) in the output. +# The default value is: NO. +# This tag requires that the tag GENERATE_LATEX is set to YES. + +LATEX_HIDE_INDICES = NO + +# The LATEX_BIB_STYLE tag can be used to specify the style to use for the +# bibliography, e.g. plainnat, or ieeetr. See +# https://en.wikipedia.org/wiki/BibTeX and \cite for more info. +# The default value is: plain. +# This tag requires that the tag GENERATE_LATEX is set to YES. + +LATEX_BIB_STYLE = plain + +# The LATEX_EMOJI_DIRECTORY tag is used to specify the (relative or absolute) +# path from which the emoji images will be read. If a relative path is entered, +# it will be relative to the LATEX_OUTPUT directory. If left blank the +# LATEX_OUTPUT directory will be used. +# This tag requires that the tag GENERATE_LATEX is set to YES. + +LATEX_EMOJI_DIRECTORY = + +#--------------------------------------------------------------------------- +# Configuration options related to the RTF output +#--------------------------------------------------------------------------- + +# If the GENERATE_RTF tag is set to YES, doxygen will generate RTF output. The +# RTF output is optimized for Word 97 and may not look too pretty with other RTF +# readers/editors. +# The default value is: NO. + +GENERATE_RTF = NO + +# The RTF_OUTPUT tag is used to specify where the RTF docs will be put. If a +# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of +# it. +# The default directory is: rtf. +# This tag requires that the tag GENERATE_RTF is set to YES. + +RTF_OUTPUT = rtf + +# If the COMPACT_RTF tag is set to YES, doxygen generates more compact RTF +# documents. This may be useful for small projects and may help to save some +# trees in general. +# The default value is: NO. +# This tag requires that the tag GENERATE_RTF is set to YES. + +COMPACT_RTF = NO + +# If the RTF_HYPERLINKS tag is set to YES, the RTF that is generated will +# contain hyperlink fields. The RTF file will contain links (just like the HTML +# output) instead of page references. This makes the output suitable for online +# browsing using Word or some other Word compatible readers that support those +# fields. +# +# Note: WordPad (write) and others do not support links. +# The default value is: NO. +# This tag requires that the tag GENERATE_RTF is set to YES. + +RTF_HYPERLINKS = NO + +# Load stylesheet definitions from file. Syntax is similar to doxygen's +# configuration file, i.e. a series of assignments. You only have to provide +# replacements, missing definitions are set to their default value. +# +# See also section "Doxygen usage" for information on how to generate the +# default style sheet that doxygen normally uses. +# This tag requires that the tag GENERATE_RTF is set to YES. + +RTF_STYLESHEET_FILE = + +# Set optional variables used in the generation of an RTF document. Syntax is +# similar to doxygen's configuration file. A template extensions file can be +# generated using doxygen -e rtf extensionFile. +# This tag requires that the tag GENERATE_RTF is set to YES. + +RTF_EXTENSIONS_FILE = + +# The RTF_EXTRA_FILES tag can be used to specify one or more extra images or +# other source files which should be copied to the RTF_OUTPUT output directory. +# Note that the files will be copied as-is; there are no commands or markers +# available. +# This tag requires that the tag GENERATE_RTF is set to YES. + +RTF_EXTRA_FILES = + +#--------------------------------------------------------------------------- +# Configuration options related to the man page output +#--------------------------------------------------------------------------- + +# If the GENERATE_MAN tag is set to YES, doxygen will generate man pages for +# classes and files. +# The default value is: NO. + +GENERATE_MAN = NO + +# The MAN_OUTPUT tag is used to specify where the man pages will be put. If a +# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of +# it. A directory man3 will be created inside the directory specified by +# MAN_OUTPUT. +# The default directory is: man. +# This tag requires that the tag GENERATE_MAN is set to YES. + +MAN_OUTPUT = man + +# The MAN_EXTENSION tag determines the extension that is added to the generated +# man pages. In case the manual section does not start with a number, the number +# 3 is prepended. The dot (.) at the beginning of the MAN_EXTENSION tag is +# optional. +# The default value is: .3. +# This tag requires that the tag GENERATE_MAN is set to YES. + +MAN_EXTENSION = .3 + +# The MAN_SUBDIR tag determines the name of the directory created within +# MAN_OUTPUT in which the man pages are placed. If defaults to man followed by +# MAN_EXTENSION with the initial . removed. +# This tag requires that the tag GENERATE_MAN is set to YES. + +MAN_SUBDIR = + +# If the MAN_LINKS tag is set to YES and doxygen generates man output, then it +# will generate one additional man file for each entity documented in the real +# man page(s). These additional files only source the real man page, but without +# them the man command would be unable to find the correct page. +# The default value is: NO. +# This tag requires that the tag GENERATE_MAN is set to YES. + +MAN_LINKS = NO + +#--------------------------------------------------------------------------- +# Configuration options related to the XML output +#--------------------------------------------------------------------------- + +# If the GENERATE_XML tag is set to YES, doxygen will generate an XML file that +# captures the structure of the code including all documentation. +# The default value is: NO. + +GENERATE_XML = NO + +# The XML_OUTPUT tag is used to specify where the XML pages will be put. If a +# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of +# it. +# The default directory is: xml. +# This tag requires that the tag GENERATE_XML is set to YES. + +XML_OUTPUT = xml + +# If the XML_PROGRAMLISTING tag is set to YES, doxygen will dump the program +# listings (including syntax highlighting and cross-referencing information) to +# the XML output. Note that enabling this will significantly increase the size +# of the XML output. +# The default value is: YES. +# This tag requires that the tag GENERATE_XML is set to YES. + +XML_PROGRAMLISTING = YES + +# If the XML_NS_MEMB_FILE_SCOPE tag is set to YES, doxygen will include +# namespace members in file scope as well, matching the HTML output. +# The default value is: NO. +# This tag requires that the tag GENERATE_XML is set to YES. + +XML_NS_MEMB_FILE_SCOPE = NO + +#--------------------------------------------------------------------------- +# Configuration options related to the DOCBOOK output +#--------------------------------------------------------------------------- + +# If the GENERATE_DOCBOOK tag is set to YES, doxygen will generate Docbook files +# that can be used to generate PDF. +# The default value is: NO. + +GENERATE_DOCBOOK = NO + +# The DOCBOOK_OUTPUT tag is used to specify where the Docbook pages will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be put in +# front of it. +# The default directory is: docbook. +# This tag requires that the tag GENERATE_DOCBOOK is set to YES. + +DOCBOOK_OUTPUT = docbook + +#--------------------------------------------------------------------------- +# Configuration options for the AutoGen Definitions output +#--------------------------------------------------------------------------- + +# If the GENERATE_AUTOGEN_DEF tag is set to YES, doxygen will generate an +# AutoGen Definitions (see https://autogen.sourceforge.net/) file that captures +# the structure of the code including all documentation. Note that this feature +# is still experimental and incomplete at the moment. +# The default value is: NO. + +GENERATE_AUTOGEN_DEF = NO + +#--------------------------------------------------------------------------- +# Configuration options related to Sqlite3 output +#--------------------------------------------------------------------------- + +# If the GENERATE_SQLITE3 tag is set to YES doxygen will generate a Sqlite3 +# database with symbols found by doxygen stored in tables. +# The default value is: NO. + +GENERATE_SQLITE3 = NO + +# The SQLITE3_OUTPUT tag is used to specify where the Sqlite3 database will be +# put. If a relative path is entered the value of OUTPUT_DIRECTORY will be put +# in front of it. +# The default directory is: sqlite3. +# This tag requires that the tag GENERATE_SQLITE3 is set to YES. + +SQLITE3_OUTPUT = sqlite3 + +# The SQLITE3_RECREATE_DB tag is set to YES, the existing doxygen_sqlite3.db +# database file will be recreated with each doxygen run. If set to NO, doxygen +# will warn if a database file is already found and not modify it. +# The default value is: YES. +# This tag requires that the tag GENERATE_SQLITE3 is set to YES. + +SQLITE3_RECREATE_DB = YES + +#--------------------------------------------------------------------------- +# Configuration options related to the Perl module output +#--------------------------------------------------------------------------- + +# If the GENERATE_PERLMOD tag is set to YES, doxygen will generate a Perl module +# file that captures the structure of the code including all documentation. +# +# Note that this feature is still experimental and incomplete at the moment. +# The default value is: NO. + +GENERATE_PERLMOD = NO + +# If the PERLMOD_LATEX tag is set to YES, doxygen will generate the necessary +# Makefile rules, Perl scripts and LaTeX code to be able to generate PDF and DVI +# output from the Perl module output. +# The default value is: NO. +# This tag requires that the tag GENERATE_PERLMOD is set to YES. + +PERLMOD_LATEX = NO + +# If the PERLMOD_PRETTY tag is set to YES, the Perl module output will be nicely +# formatted so it can be parsed by a human reader. This is useful if you want to +# understand what is going on. On the other hand, if this tag is set to NO, the +# size of the Perl module output will be much smaller and Perl will parse it +# just the same. +# The default value is: YES. +# This tag requires that the tag GENERATE_PERLMOD is set to YES. + +PERLMOD_PRETTY = YES + +# The names of the make variables in the generated doxyrules.make file are +# prefixed with the string contained in PERLMOD_MAKEVAR_PREFIX. This is useful +# so different doxyrules.make files included by the same Makefile don't +# overwrite each other's variables. +# This tag requires that the tag GENERATE_PERLMOD is set to YES. + +PERLMOD_MAKEVAR_PREFIX = + +#--------------------------------------------------------------------------- +# Configuration options related to the preprocessor +#--------------------------------------------------------------------------- + +# If the ENABLE_PREPROCESSING tag is set to YES, doxygen will evaluate all +# C-preprocessor directives found in the sources and include files. +# The default value is: YES. + +ENABLE_PREPROCESSING = YES + +# If the MACRO_EXPANSION tag is set to YES, doxygen will expand all macro names +# in the source code. If set to NO, only conditional compilation will be +# performed. Macro expansion can be done in a controlled way by setting +# EXPAND_ONLY_PREDEF to YES. +# The default value is: NO. +# This tag requires that the tag ENABLE_PREPROCESSING is set to YES. + +MACRO_EXPANSION = YES + +# If the EXPAND_ONLY_PREDEF and MACRO_EXPANSION tags are both set to YES then +# the macro expansion is limited to the macros specified with the PREDEFINED and +# EXPAND_AS_DEFINED tags. +# The default value is: NO. +# This tag requires that the tag ENABLE_PREPROCESSING is set to YES. + +EXPAND_ONLY_PREDEF = YES + +# If the SEARCH_INCLUDES tag is set to YES, the include files in the +# INCLUDE_PATH will be searched if a #include is found. +# The default value is: YES. +# This tag requires that the tag ENABLE_PREPROCESSING is set to YES. + +SEARCH_INCLUDES = NO + +# The INCLUDE_PATH tag can be used to specify one or more directories that +# contain include files that are not input files but should be processed by the +# preprocessor. Note that the INCLUDE_PATH is not recursive, so the setting of +# RECURSIVE has no effect here. +# This tag requires that the tag SEARCH_INCLUDES is set to YES. + +INCLUDE_PATH = + +# You can use the INCLUDE_FILE_PATTERNS tag to specify one or more wildcard +# patterns (like *.h and *.hpp) to filter out the header-files in the +# directories. If left blank, the patterns specified with FILE_PATTERNS will be +# used. +# This tag requires that the tag ENABLE_PREPROCESSING is set to YES. + +INCLUDE_FILE_PATTERNS = + +# The PREDEFINED tag can be used to specify one or more macro names that are +# defined before the preprocessor is started (similar to the -D option of e.g. +# gcc). The argument of the tag is a list of macros of the form: name or +# name=definition (no spaces). If the definition and the "=" are omitted, "=1" +# is assumed. To prevent a macro definition from being undefined via #undef or +# recursively expanded use the := operator instead of the = operator. +# This tag requires that the tag ENABLE_PREPROCESSING is set to YES. + +PREDEFINED = + +# If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then this +# tag can be used to specify a list of macro names that should be expanded. The +# macro definition that is found in the sources will be used. Use the PREDEFINED +# tag if you want to use a different macro definition that overrules the +# definition found in the source code. +# This tag requires that the tag ENABLE_PREPROCESSING is set to YES. + +EXPAND_AS_DEFINED = LIBIMOBILEDEVICE_API + +# If the SKIP_FUNCTION_MACROS tag is set to YES then doxygen's preprocessor will +# remove all references to function-like macros that are alone on a line, have +# an all uppercase name, and do not end with a semicolon. Such function macros +# are typically used for boiler-plate code, and will confuse the parser if not +# removed. +# The default value is: YES. +# This tag requires that the tag ENABLE_PREPROCESSING is set to YES. + +SKIP_FUNCTION_MACROS = YES + +#--------------------------------------------------------------------------- +# Configuration options related to external references +#--------------------------------------------------------------------------- + +# The TAGFILES tag can be used to specify one or more tag files. For each tag +# file the location of the external documentation should be added. The format of +# a tag file without this location is as follows: +# TAGFILES = file1 file2 ... +# Adding location for the tag files is done as follows: +# TAGFILES = file1=loc1 "file2 = loc2" ... +# where loc1 and loc2 can be relative or absolute paths or URLs. See the +# section "Linking to external documentation" for more information about the use +# of tag files. +# Note: Each tag file must have a unique name (where the name does NOT include +# the path). If a tag file is not located in the directory in which doxygen is +# run, you must also specify the path to the tagfile here. + +TAGFILES = + +# When a file name is specified after GENERATE_TAGFILE, doxygen will create a +# tag file that is based on the input files it reads. See section "Linking to +# external documentation" for more information about the usage of tag files. + +GENERATE_TAGFILE = + +# If the ALLEXTERNALS tag is set to YES, all external classes and namespaces +# will be listed in the class and namespace index. If set to NO, only the +# inherited external classes will be listed. +# The default value is: NO. + +ALLEXTERNALS = NO + +# If the EXTERNAL_GROUPS tag is set to YES, all external groups will be listed +# in the topic index. If set to NO, only the current project's groups will be +# listed. +# The default value is: YES. + +EXTERNAL_GROUPS = YES + +# If the EXTERNAL_PAGES tag is set to YES, all external pages will be listed in +# the related pages index. If set to NO, only the current project's pages will +# be listed. +# The default value is: YES. + +EXTERNAL_PAGES = YES + +#--------------------------------------------------------------------------- +# Configuration options related to diagram generator tools +#--------------------------------------------------------------------------- + +# If set to YES the inheritance and collaboration graphs will hide inheritance +# and usage relations if the target is undocumented or is not a class. +# The default value is: YES. + +HIDE_UNDOC_RELATIONS = YES + +# If you set the HAVE_DOT tag to YES then doxygen will assume the dot tool is +# available from the path. This tool is part of Graphviz (see: +# https://www.graphviz.org/), a graph visualization toolkit from AT&T and Lucent +# Bell Labs. The other options in this section have no effect if this option is +# set to NO +# The default value is: NO. + +HAVE_DOT = NO + +# The DOT_NUM_THREADS specifies the number of dot invocations doxygen is allowed +# to run in parallel. When set to 0 doxygen will base this on the number of +# processors available in the system. You can set it explicitly to a value +# larger than 0 to get control over the balance between CPU load and processing +# speed. +# Minimum value: 0, maximum value: 32, default value: 0. +# This tag requires that the tag HAVE_DOT is set to YES. + +DOT_NUM_THREADS = 0 + +# DOT_COMMON_ATTR is common attributes for nodes, edges and labels of +# subgraphs. When you want a differently looking font in the dot files that +# doxygen generates you can specify fontname, fontcolor and fontsize attributes. +# For details please see <a href=https://graphviz.org/doc/info/attrs.html>Node, +# Edge and Graph Attributes specification</a> You need to make sure dot is able +# to find the font, which can be done by putting it in a standard location or by +# setting the DOTFONTPATH environment variable or by setting DOT_FONTPATH to the +# directory containing the font. Default graphviz fontsize is 14. +# The default value is: fontname=Helvetica,fontsize=10. +# This tag requires that the tag HAVE_DOT is set to YES. + +DOT_COMMON_ATTR = "fontname=Helvetica,fontsize=10" + +# DOT_EDGE_ATTR is concatenated with DOT_COMMON_ATTR. For elegant style you can +# add 'arrowhead=open, arrowtail=open, arrowsize=0.5'. <a +# href=https://graphviz.org/doc/info/arrows.html>Complete documentation about +# arrows shapes.</a> +# The default value is: labelfontname=Helvetica,labelfontsize=10. +# This tag requires that the tag HAVE_DOT is set to YES. + +DOT_EDGE_ATTR = "labelfontname=Helvetica,labelfontsize=10" + +# DOT_NODE_ATTR is concatenated with DOT_COMMON_ATTR. For view without boxes +# around nodes set 'shape=plain' or 'shape=plaintext' <a +# href=https://www.graphviz.org/doc/info/shapes.html>Shapes specification</a> +# The default value is: shape=box,height=0.2,width=0.4. +# This tag requires that the tag HAVE_DOT is set to YES. + +DOT_NODE_ATTR = "shape=box,height=0.2,width=0.4" + +# You can set the path where dot can find font specified with fontname in +# DOT_COMMON_ATTR and others dot attributes. +# This tag requires that the tag HAVE_DOT is set to YES. + +DOT_FONTPATH = + +# If the CLASS_GRAPH tag is set to YES or GRAPH or BUILTIN then doxygen will +# generate a graph for each documented class showing the direct and indirect +# inheritance relations. In case the CLASS_GRAPH tag is set to YES or GRAPH and +# HAVE_DOT is enabled as well, then dot will be used to draw the graph. In case +# the CLASS_GRAPH tag is set to YES and HAVE_DOT is disabled or if the +# CLASS_GRAPH tag is set to BUILTIN, then the built-in generator will be used. +# If the CLASS_GRAPH tag is set to TEXT the direct and indirect inheritance +# relations will be shown as texts / links. Explicit enabling an inheritance +# graph or choosing a different representation for an inheritance graph of a +# specific class, can be accomplished by means of the command \inheritancegraph. +# Disabling an inheritance graph can be accomplished by means of the command +# \hideinheritancegraph. +# Possible values are: NO, YES, TEXT, GRAPH and BUILTIN. +# The default value is: YES. + +CLASS_GRAPH = YES + +# If the COLLABORATION_GRAPH tag is set to YES then doxygen will generate a +# graph for each documented class showing the direct and indirect implementation +# dependencies (inheritance, containment, and class references variables) of the +# class with other documented classes. Explicit enabling a collaboration graph, +# when COLLABORATION_GRAPH is set to NO, can be accomplished by means of the +# command \collaborationgraph. Disabling a collaboration graph can be +# accomplished by means of the command \hidecollaborationgraph. +# The default value is: YES. +# This tag requires that the tag HAVE_DOT is set to YES. + +COLLABORATION_GRAPH = YES + +# If the GROUP_GRAPHS tag is set to YES then doxygen will generate a graph for +# groups, showing the direct groups dependencies. Explicit enabling a group +# dependency graph, when GROUP_GRAPHS is set to NO, can be accomplished by means +# of the command \groupgraph. Disabling a directory graph can be accomplished by +# means of the command \hidegroupgraph. See also the chapter Grouping in the +# manual. +# The default value is: YES. +# This tag requires that the tag HAVE_DOT is set to YES. + +GROUP_GRAPHS = YES + +# If the UML_LOOK tag is set to YES, doxygen will generate inheritance and +# collaboration diagrams in a style similar to the OMG's Unified Modeling +# Language. +# The default value is: NO. +# This tag requires that the tag HAVE_DOT is set to YES. + +UML_LOOK = NO + +# If the UML_LOOK tag is enabled, the fields and methods are shown inside the +# class node. If there are many fields or methods and many nodes the graph may +# become too big to be useful. The UML_LIMIT_NUM_FIELDS threshold limits the +# number of items for each type to make the size more manageable. Set this to 0 +# for no limit. Note that the threshold may be exceeded by 50% before the limit +# is enforced. So when you set the threshold to 10, up to 15 fields may appear, +# but if the number exceeds 15, the total amount of fields shown is limited to +# 10. +# Minimum value: 0, maximum value: 100, default value: 10. +# This tag requires that the tag UML_LOOK is set to YES. + +UML_LIMIT_NUM_FIELDS = 10 + +# If the DOT_UML_DETAILS tag is set to NO, doxygen will show attributes and +# methods without types and arguments in the UML graphs. If the DOT_UML_DETAILS +# tag is set to YES, doxygen will add type and arguments for attributes and +# methods in the UML graphs. If the DOT_UML_DETAILS tag is set to NONE, doxygen +# will not generate fields with class member information in the UML graphs. The +# class diagrams will look similar to the default class diagrams but using UML +# notation for the relationships. +# Possible values are: NO, YES and NONE. +# The default value is: NO. +# This tag requires that the tag UML_LOOK is set to YES. + +DOT_UML_DETAILS = NO + +# The DOT_WRAP_THRESHOLD tag can be used to set the maximum number of characters +# to display on a single line. If the actual line length exceeds this threshold +# significantly it will be wrapped across multiple lines. Some heuristics are +# applied to avoid ugly line breaks. +# Minimum value: 0, maximum value: 1000, default value: 17. +# This tag requires that the tag HAVE_DOT is set to YES. + +DOT_WRAP_THRESHOLD = 17 + +# If the TEMPLATE_RELATIONS tag is set to YES then the inheritance and +# collaboration graphs will show the relations between templates and their +# instances. +# The default value is: NO. +# This tag requires that the tag HAVE_DOT is set to YES. + +TEMPLATE_RELATIONS = NO + +# If the INCLUDE_GRAPH, ENABLE_PREPROCESSING and SEARCH_INCLUDES tags are set to +# YES then doxygen will generate a graph for each documented file showing the +# direct and indirect include dependencies of the file with other documented +# files. Explicit enabling an include graph, when INCLUDE_GRAPH is is set to NO, +# can be accomplished by means of the command \includegraph. Disabling an +# include graph can be accomplished by means of the command \hideincludegraph. +# The default value is: YES. +# This tag requires that the tag HAVE_DOT is set to YES. + +INCLUDE_GRAPH = YES + +# If the INCLUDED_BY_GRAPH, ENABLE_PREPROCESSING and SEARCH_INCLUDES tags are +# set to YES then doxygen will generate a graph for each documented file showing +# the direct and indirect include dependencies of the file with other documented +# files. Explicit enabling an included by graph, when INCLUDED_BY_GRAPH is set +# to NO, can be accomplished by means of the command \includedbygraph. Disabling +# an included by graph can be accomplished by means of the command +# \hideincludedbygraph. +# The default value is: YES. +# This tag requires that the tag HAVE_DOT is set to YES. + +INCLUDED_BY_GRAPH = YES + +# If the CALL_GRAPH tag is set to YES then doxygen will generate a call +# dependency graph for every global function or class method. +# +# Note that enabling this option will significantly increase the time of a run. +# So in most cases it will be better to enable call graphs for selected +# functions only using the \callgraph command. Disabling a call graph can be +# accomplished by means of the command \hidecallgraph. +# The default value is: NO. +# This tag requires that the tag HAVE_DOT is set to YES. + +CALL_GRAPH = NO + +# If the CALLER_GRAPH tag is set to YES then doxygen will generate a caller +# dependency graph for every global function or class method. +# +# Note that enabling this option will significantly increase the time of a run. +# So in most cases it will be better to enable caller graphs for selected +# functions only using the \callergraph command. Disabling a caller graph can be +# accomplished by means of the command \hidecallergraph. +# The default value is: NO. +# This tag requires that the tag HAVE_DOT is set to YES. + +CALLER_GRAPH = NO + +# If the GRAPHICAL_HIERARCHY tag is set to YES then doxygen will graphical +# hierarchy of all classes instead of a textual one. +# The default value is: YES. +# This tag requires that the tag HAVE_DOT is set to YES. + +GRAPHICAL_HIERARCHY = YES + +# If the DIRECTORY_GRAPH tag is set to YES then doxygen will show the +# dependencies a directory has on other directories in a graphical way. The +# dependency relations are determined by the #include relations between the +# files in the directories. Explicit enabling a directory graph, when +# DIRECTORY_GRAPH is set to NO, can be accomplished by means of the command +# \directorygraph. Disabling a directory graph can be accomplished by means of +# the command \hidedirectorygraph. +# The default value is: YES. +# This tag requires that the tag HAVE_DOT is set to YES. + +DIRECTORY_GRAPH = YES + +# The DIR_GRAPH_MAX_DEPTH tag can be used to limit the maximum number of levels +# of child directories generated in directory dependency graphs by dot. +# Minimum value: 1, maximum value: 25, default value: 1. +# This tag requires that the tag DIRECTORY_GRAPH is set to YES. + +DIR_GRAPH_MAX_DEPTH = 1 + +# The DOT_IMAGE_FORMAT tag can be used to set the image format of the images +# generated by dot. For an explanation of the image formats see the section +# output formats in the documentation of the dot tool (Graphviz (see: +# https://www.graphviz.org/)). +# Note: If you choose svg you need to set HTML_FILE_EXTENSION to xhtml in order +# to make the SVG files visible in IE 9+ (other browsers do not have this +# requirement). +# Possible values are: png, jpg, gif, svg, png:gd, png:gd:gd, png:cairo, +# png:cairo:gd, png:cairo:cairo, png:cairo:gdiplus, png:gdiplus and +# png:gdiplus:gdiplus. +# The default value is: png. +# This tag requires that the tag HAVE_DOT is set to YES. + +DOT_IMAGE_FORMAT = png + +# If DOT_IMAGE_FORMAT is set to svg, then this option can be set to YES to +# enable generation of interactive SVG images that allow zooming and panning. +# +# Note that this requires a modern browser other than Internet Explorer. Tested +# and working are Firefox, Chrome, Safari, and Opera. +# Note: For IE 9+ you need to set HTML_FILE_EXTENSION to xhtml in order to make +# the SVG files visible. Older versions of IE do not have SVG support. +# The default value is: NO. +# This tag requires that the tag HAVE_DOT is set to YES. + +INTERACTIVE_SVG = NO + +# The DOT_PATH tag can be used to specify the path where the dot tool can be +# found. If left blank, it is assumed the dot tool can be found in the path. +# This tag requires that the tag HAVE_DOT is set to YES. + +DOT_PATH = + +# The DOTFILE_DIRS tag can be used to specify one or more directories that +# contain dot files that are included in the documentation (see the \dotfile +# command). +# This tag requires that the tag HAVE_DOT is set to YES. + +DOTFILE_DIRS = + +# You can include diagrams made with dia in doxygen documentation. Doxygen will +# then run dia to produce the diagram and insert it in the documentation. The +# DIA_PATH tag allows you to specify the directory where the dia binary resides. +# If left empty dia is assumed to be found in the default search path. + +DIA_PATH = + +# The DIAFILE_DIRS tag can be used to specify one or more directories that +# contain dia files that are included in the documentation (see the \diafile +# command). + +DIAFILE_DIRS = + +# When using plantuml, the PLANTUML_JAR_PATH tag should be used to specify the +# path where java can find the plantuml.jar file or to the filename of jar file +# to be used. If left blank, it is assumed PlantUML is not used or called during +# a preprocessing step. Doxygen will generate a warning when it encounters a +# \startuml command in this case and will not generate output for the diagram. + +PLANTUML_JAR_PATH = + +# When using plantuml, the PLANTUML_CFG_FILE tag can be used to specify a +# configuration file for plantuml. + +PLANTUML_CFG_FILE = + +# When using plantuml, the specified paths are searched for files specified by +# the !include statement in a plantuml block. + +PLANTUML_INCLUDE_PATH = + +# The DOT_GRAPH_MAX_NODES tag can be used to set the maximum number of nodes +# that will be shown in the graph. If the number of nodes in a graph becomes +# larger than this value, doxygen will truncate the graph, which is visualized +# by representing a node as a red box. Note that if the number of direct +# children of the root node in a graph is already larger than +# DOT_GRAPH_MAX_NODES then the graph will not be shown at all. Also note that +# the size of a graph can be further restricted by MAX_DOT_GRAPH_DEPTH. +# Minimum value: 0, maximum value: 10000, default value: 50. +# This tag requires that the tag HAVE_DOT is set to YES. + +DOT_GRAPH_MAX_NODES = 50 + +# The MAX_DOT_GRAPH_DEPTH tag can be used to set the maximum depth of the graphs +# generated by dot. A depth value of 3 means that only nodes reachable from the +# root by following a path via at most 3 edges will be shown. Nodes that lay +# further from the root node will be omitted. Note that setting this option to 1 +# or 2 may greatly reduce the computation time needed for large code bases. Also +# note that the size of a graph can be further restricted by +# DOT_GRAPH_MAX_NODES. Using a depth of 0 means no depth restriction. +# Minimum value: 0, maximum value: 1000, default value: 0. +# This tag requires that the tag HAVE_DOT is set to YES. + +MAX_DOT_GRAPH_DEPTH = 0 + +# Set the DOT_MULTI_TARGETS tag to YES to allow dot to generate multiple output +# files in one run (i.e. multiple -o and -T options on the command line). This +# makes dot run faster, but since only newer versions of dot (>1.8.10) support +# this, this feature is disabled by default. +# The default value is: NO. +# This tag requires that the tag HAVE_DOT is set to YES. + +DOT_MULTI_TARGETS = NO + +# If the GENERATE_LEGEND tag is set to YES doxygen will generate a legend page +# explaining the meaning of the various boxes and arrows in the dot generated +# graphs. +# Note: This tag requires that UML_LOOK isn't set, i.e. the doxygen internal +# graphical representation for inheritance and collaboration diagrams is used. +# The default value is: YES. +# This tag requires that the tag HAVE_DOT is set to YES. + +GENERATE_LEGEND = YES + +# If the DOT_CLEANUP tag is set to YES, doxygen will remove the intermediate +# files that are used to generate the various graphs. +# +# Note: This setting is not only used for dot files but also for msc temporary +# files. +# The default value is: YES. + +DOT_CLEANUP = YES + +# You can define message sequence charts within doxygen comments using the \msc +# command. If the MSCGEN_TOOL tag is left empty (the default), then doxygen will +# use a built-in version of mscgen tool to produce the charts. Alternatively, +# the MSCGEN_TOOL tag can also specify the name an external tool. For instance, +# specifying prog as the value, doxygen will call the tool as prog -T +# <outfile_format> -o <outputfile> <inputfile>. The external tool should support +# output file formats "png", "eps", "svg", and "ismap". + +MSCGEN_TOOL = + +# The MSCFILE_DIRS tag can be used to specify one or more directories that +# contain msc files that are included in the documentation (see the \mscfile +# command). + +MSCFILE_DIRS = diff --git a/fuzz/Makefile.am b/fuzz/Makefile.am new file mode 100644 index 0000000..8ea3fb0 --- /dev/null +++ b/fuzz/Makefile.am @@ -0,0 +1,55 @@ +if BUILD_FUZZERS + +libFuzzer.a: Fuzzer/build.sh + @echo "Building $@" + @./Fuzzer/build.sh + +Fuzzer/build.sh: LIBFUZZER_SRC + +LIBFUZZER_SRC: + @if test -d Fuzzer ; then \ + if test -d Fuzzer/.git ; then \ + echo Making sure libFuzzer source tree is up-to-date... ; \ + cd Fuzzer && git checkout . && git pull && cd .. ; \ + fi \ + else \ + echo Checking out libFuzzer source code... ; \ + git clone https://chromium.googlesource.com/chromium/llvm-project/llvm/lib/Fuzzer ; \ + fi + +CLEANFILES = libFuzzer.a + +noinst_PROGRAMS = \ + xplist_fuzzer \ + bplist_fuzzer \ + jplist_fuzzer \ + oplist_fuzzer + +xplist_fuzzer_SOURCES = xplist_fuzzer.cc +xplist_fuzzer_LDFLAGS = -static +xplist_fuzzer_LDADD = $(top_builddir)/src/libplist-2.0.la libFuzzer.a + +bplist_fuzzer_SOURCES = bplist_fuzzer.cc +bplist_fuzzer_LDFLAGS = -static +bplist_fuzzer_LDADD = $(top_builddir)/src/libplist-2.0.la libFuzzer.a + +jplist_fuzzer_SOURCES = jplist_fuzzer.cc +jplist_fuzzer_LDFLAGS = -static +jplist_fuzzer_LDADD = $(top_builddir)/src/libplist-2.0.la libFuzzer.a + +oplist_fuzzer_SOURCES = oplist_fuzzer.cc +oplist_fuzzer_LDFLAGS = -static +oplist_fuzzer_LDADD = $(top_builddir)/src/libplist-2.0.la libFuzzer.a + +TESTS = fuzzers.test + +EXTRA_DIST = \ + bplist.dict \ + xplist.dict \ + jplist.dict \ + oplist.dict \ + init-fuzzers.sh \ + test-fuzzers.sh \ + fuzzers.test + +endif diff --git a/fuzz/bplist-crashes/clusterfuzz-testcase-4766291097288704 b/fuzz/bplist-crashes/clusterfuzz-testcase-4766291097288704 Binary files differnew file mode 100644 index 0000000..ad24aea --- /dev/null +++ b/fuzz/bplist-crashes/clusterfuzz-testcase-4766291097288704 diff --git a/fuzz/bplist-crashes/clusterfuzz-testcase-4930725262393344 b/fuzz/bplist-crashes/clusterfuzz-testcase-4930725262393344 Binary files differnew file mode 100644 index 0000000..a8f654c --- /dev/null +++ b/fuzz/bplist-crashes/clusterfuzz-testcase-4930725262393344 diff --git a/fuzz/bplist-crashes/clusterfuzz-testcase-5421965105954816 b/fuzz/bplist-crashes/clusterfuzz-testcase-5421965105954816 Binary files differnew file mode 100644 index 0000000..7b4972b --- /dev/null +++ b/fuzz/bplist-crashes/clusterfuzz-testcase-5421965105954816 diff --git a/fuzz/bplist-crashes/clusterfuzz-testcase-5556177727389696 b/fuzz/bplist-crashes/clusterfuzz-testcase-5556177727389696 Binary files differnew file mode 100644 index 0000000..dac8e23 --- /dev/null +++ b/fuzz/bplist-crashes/clusterfuzz-testcase-5556177727389696 diff --git a/fuzz/bplist-crashes/clusterfuzz-testcase-6557963011489792 b/fuzz/bplist-crashes/clusterfuzz-testcase-6557963011489792 Binary files differnew file mode 100644 index 0000000..296e2f7 --- /dev/null +++ b/fuzz/bplist-crashes/clusterfuzz-testcase-6557963011489792 diff --git a/fuzz/bplist-crashes/clusterfuzz-testcase-6605859144597504 b/fuzz/bplist-crashes/clusterfuzz-testcase-6605859144597504 Binary files differnew file mode 100644 index 0000000..06c225a --- /dev/null +++ b/fuzz/bplist-crashes/clusterfuzz-testcase-6605859144597504 diff --git a/fuzz/bplist-crashes/crash-0ce4dd483b645bc05ec45a97d285dcf1933ee840 b/fuzz/bplist-crashes/crash-0ce4dd483b645bc05ec45a97d285dcf1933ee840 Binary files differnew file mode 100644 index 0000000..fb68699 --- /dev/null +++ b/fuzz/bplist-crashes/crash-0ce4dd483b645bc05ec45a97d285dcf1933ee840 diff --git a/fuzz/bplist-crashes/crash-0d59cdcb5c3416c2790612b78a631ce9f5622800 b/fuzz/bplist-crashes/crash-0d59cdcb5c3416c2790612b78a631ce9f5622800 Binary files differnew file mode 100644 index 0000000..ba30b5e --- /dev/null +++ b/fuzz/bplist-crashes/crash-0d59cdcb5c3416c2790612b78a631ce9f5622800 diff --git a/fuzz/bplist-crashes/crash-17d635ab460fef74df1ac7561f1253533a6359c5 b/fuzz/bplist-crashes/crash-17d635ab460fef74df1ac7561f1253533a6359c5 Binary files differnew file mode 100644 index 0000000..31e2388 --- /dev/null +++ b/fuzz/bplist-crashes/crash-17d635ab460fef74df1ac7561f1253533a6359c5 diff --git a/fuzz/bplist-crashes/crash-33a67313fa7864cd627ef92d12017a127d342f28 b/fuzz/bplist-crashes/crash-33a67313fa7864cd627ef92d12017a127d342f28 Binary files differnew file mode 100644 index 0000000..060cf42 --- /dev/null +++ b/fuzz/bplist-crashes/crash-33a67313fa7864cd627ef92d12017a127d342f28 diff --git a/fuzz/bplist-crashes/crash-39f1347115f8fe9ac25cdc9332e3fc5cd32c7f7b b/fuzz/bplist-crashes/crash-39f1347115f8fe9ac25cdc9332e3fc5cd32c7f7b Binary files differnew file mode 100644 index 0000000..02c60bc --- /dev/null +++ b/fuzz/bplist-crashes/crash-39f1347115f8fe9ac25cdc9332e3fc5cd32c7f7b diff --git a/fuzz/bplist-crashes/crash-4bd78175fe01d07c5ee90104fbc68d95dd092210 b/fuzz/bplist-crashes/crash-4bd78175fe01d07c5ee90104fbc68d95dd092210 Binary files differnew file mode 100644 index 0000000..739dabc --- /dev/null +++ b/fuzz/bplist-crashes/crash-4bd78175fe01d07c5ee90104fbc68d95dd092210 diff --git a/fuzz/bplist-crashes/crash-4c5404453bb7425aef9b953a5f6f3f0afcc48392 b/fuzz/bplist-crashes/crash-4c5404453bb7425aef9b953a5f6f3f0afcc48392 Binary files differnew file mode 100644 index 0000000..28ac412 --- /dev/null +++ b/fuzz/bplist-crashes/crash-4c5404453bb7425aef9b953a5f6f3f0afcc48392 diff --git a/fuzz/bplist-crashes/crash-52775e25f6af20af351cd616834e602fc988a032 b/fuzz/bplist-crashes/crash-52775e25f6af20af351cd616834e602fc988a032 Binary files differnew file mode 100644 index 0000000..7a9af5f --- /dev/null +++ b/fuzz/bplist-crashes/crash-52775e25f6af20af351cd616834e602fc988a032 diff --git a/fuzz/bplist-crashes/crash-6d1399685b745d3d49fadc91072fabcdde8d16fa b/fuzz/bplist-crashes/crash-6d1399685b745d3d49fadc91072fabcdde8d16fa Binary files differnew file mode 100644 index 0000000..2846cbb --- /dev/null +++ b/fuzz/bplist-crashes/crash-6d1399685b745d3d49fadc91072fabcdde8d16fa diff --git a/fuzz/bplist-crashes/crash-94b3725900d63c6258448fd757559c81002de9c9 b/fuzz/bplist-crashes/crash-94b3725900d63c6258448fd757559c81002de9c9 Binary files differnew file mode 100644 index 0000000..9337149 --- /dev/null +++ b/fuzz/bplist-crashes/crash-94b3725900d63c6258448fd757559c81002de9c9 diff --git a/fuzz/bplist-crashes/crash-a47195acbabc3f5b2cda9c6ebda42f2afc1cc5af b/fuzz/bplist-crashes/crash-a47195acbabc3f5b2cda9c6ebda42f2afc1cc5af Binary files differnew file mode 100644 index 0000000..ecc9f9a --- /dev/null +++ b/fuzz/bplist-crashes/crash-a47195acbabc3f5b2cda9c6ebda42f2afc1cc5af diff --git a/fuzz/bplist-crashes/crash-ce852bae6aeeffc4698a93660236f1b995ce966e b/fuzz/bplist-crashes/crash-ce852bae6aeeffc4698a93660236f1b995ce966e Binary files differnew file mode 100644 index 0000000..97761e4 --- /dev/null +++ b/fuzz/bplist-crashes/crash-ce852bae6aeeffc4698a93660236f1b995ce966e diff --git a/fuzz/bplist-crashes/crash-daddb6e82e3d1ed76e20e4131462f5247a6fb131 b/fuzz/bplist-crashes/crash-daddb6e82e3d1ed76e20e4131462f5247a6fb131 Binary files differnew file mode 100644 index 0000000..d278fd9 --- /dev/null +++ b/fuzz/bplist-crashes/crash-daddb6e82e3d1ed76e20e4131462f5247a6fb131 diff --git a/fuzz/bplist-crashes/crash-f21be4ebce7f9c337bf96b93cec7e252f5c75a21 b/fuzz/bplist-crashes/crash-f21be4ebce7f9c337bf96b93cec7e252f5c75a21 Binary files differnew file mode 100644 index 0000000..27a6356 --- /dev/null +++ b/fuzz/bplist-crashes/crash-f21be4ebce7f9c337bf96b93cec7e252f5c75a21 diff --git a/fuzz/bplist-crashes/crash-f779dafccc0164684d1a264976e56037188b40e2 b/fuzz/bplist-crashes/crash-f779dafccc0164684d1a264976e56037188b40e2 Binary files differnew file mode 100644 index 0000000..01e3dca --- /dev/null +++ b/fuzz/bplist-crashes/crash-f779dafccc0164684d1a264976e56037188b40e2 diff --git a/fuzz/bplist-leaks/leak-96fb453b3ba18d31e164831795a708183b5a0dac b/fuzz/bplist-leaks/leak-96fb453b3ba18d31e164831795a708183b5a0dac Binary files differnew file mode 100644 index 0000000..d4a1ec3 --- /dev/null +++ b/fuzz/bplist-leaks/leak-96fb453b3ba18d31e164831795a708183b5a0dac diff --git a/fuzz/bplist-leaks/leak-acb3c00a83d7635638a476a4e70fc1f1caa1a5b1 b/fuzz/bplist-leaks/leak-acb3c00a83d7635638a476a4e70fc1f1caa1a5b1 Binary files differnew file mode 100644 index 0000000..d99278b --- /dev/null +++ b/fuzz/bplist-leaks/leak-acb3c00a83d7635638a476a4e70fc1f1caa1a5b1 diff --git a/fuzz/bplist-leaks/leak-fe2cd4b1f67e0c9b21e6225a4fb4ca19eab80074 b/fuzz/bplist-leaks/leak-fe2cd4b1f67e0c9b21e6225a4fb4ca19eab80074 Binary files differnew file mode 100644 index 0000000..b499279 --- /dev/null +++ b/fuzz/bplist-leaks/leak-fe2cd4b1f67e0c9b21e6225a4fb4ca19eab80074 diff --git a/fuzz/bplist.dict b/fuzz/bplist.dict new file mode 100644 index 0000000..bb0ea5d --- /dev/null +++ b/fuzz/bplist.dict @@ -0,0 +1 @@ +header_bplist = "bplist00" diff --git a/libcnary/list.c b/fuzz/bplist_fuzzer.cc index 2f05347..17d0649 100644 --- a/libcnary/list.c +++ b/fuzz/bplist_fuzzer.cc @@ -1,10 +1,8 @@ /* - * list.c + * bplist_fuzzer.cc + * binary plist fuzz target for libFuzzer * - * Created on: Mar 8, 2011 - * Author: posixninja - * - * Copyright (c) 2011 Joshua Hill. All Rights Reserved. + * Copyright (c) 2017 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,27 +19,14 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ +#include <plist/plist.h> #include <stdio.h> -#include <stdlib.h> - -#include "list.h" - -void list_init(list_t* list) { - list->next = NULL; - list->prev = list; -} +extern "C" int LLVMFuzzerTestOneInput(const unsigned char* data, size_t size) +{ + plist_t root_node = NULL; + plist_from_bin(reinterpret_cast<const char*>(data), size, &root_node); + plist_free(root_node); -void list_destroy(list_t* list) { - if(list) { - free(list); - } -} - -int list_add(list_t* list, object_t* object) { - return -1; -} - -int list_remove(list_t* list, object_t* object) { - return -1; + return 0; } diff --git a/fuzz/bplist_fuzzer.options b/fuzz/bplist_fuzzer.options new file mode 100644 index 0000000..c0689b2 --- /dev/null +++ b/fuzz/bplist_fuzzer.options @@ -0,0 +1,3 @@ +[libfuzzer] +max_len = 4096 +dict = bplist.dict diff --git a/fuzz/fuzzers.test b/fuzz/fuzzers.test new file mode 100755 index 0000000..dd3fb08 --- /dev/null +++ b/fuzz/fuzzers.test @@ -0,0 +1,8 @@ +## -*- sh -*- + +set -e + +./init-fuzzers.sh + +./test-fuzzers.sh + diff --git a/fuzz/init-fuzzers.sh b/fuzz/init-fuzzers.sh new file mode 100755 index 0000000..c9b1955 --- /dev/null +++ b/fuzz/init-fuzzers.sh @@ -0,0 +1,37 @@ +#!/bin/sh + +CURDIR=`pwd` +FUZZDIR=`dirname $0` + +cd ${FUZZDIR} + +if ! test -x xplist_fuzzer || ! test -x bplist_fuzzer || ! test -x jplist_fuzzer; then + echo "ERROR: you need to build the fuzzers first." + cd ${CURDIR} + exit 1 +fi + +mkdir -p xplist-input +cp ../test/data/*.plist xplist-input/ +./xplist_fuzzer -merge=1 xplist-input xplist-crashes xplist-leaks -dict=xplist.dict + +mkdir -p bplist-input +cp ../test/data/*.bplist bplist-input/ +./bplist_fuzzer -merge=1 bplist-input bplist-crashes bplist-leaks -dict=bplist.dict + +mkdir -p jplist-input +mkdir -p jplist-crashes +mkdir -p jplist-leaks +cp ../test/data/j1.plist jplist-input/ +cp ../test/data/j2.plist jplist-input/ +./jplist_fuzzer -merge=1 jplist-input jplist-crashes jplist-leaks -dict=jplist.dict + +mkdir -p oplist-input +mkdir -p oplist-crashes +mkdir -p oplist-leaks +cp ../test/data/*.ostep oplist-input/ +cp ../test/data/test.strings oplist-input/ +./oplist_fuzzer -merge=1 oplist-input oplist-crashes oplist-leaks -dict=oplist.dict + +cd ${CURDIR} +exit 0 diff --git a/fuzz/jplist-crashes/clusterfuzz-testcase-minimized-jplist_fuzzer-4997614678966272 b/fuzz/jplist-crashes/clusterfuzz-testcase-minimized-jplist_fuzzer-4997614678966272 new file mode 100644 index 0000000..e9982b8 --- /dev/null +++ b/fuzz/jplist-crashes/clusterfuzz-testcase-minimized-jplist_fuzzer-4997614678966272 @@ -0,0 +1,65 @@ +{"Some AISCI stri,{}ng""e:sTt ASCII Stri{"":tring""e:sTt ASCII Stri{"":5}ome UTF8 stringq":["à éËçù","æ—¥I Stri""""""""""{""{,""""""""""""""""""{""{,""""""""""""""""""{""{,""""""""""""""""""{""{,i,{}ng""e:sT. + + + + + + + + + + + + + + + +[ + + + + + + + + + + + + + + + +Stri:sTt ASCII Stri{"":5}ome UTF8 stringq":["à éËçù","æ—¥I Stri""""""""""{""{,""""""""""""""""""{""{,""""""""""""""""""{""{,""""""""""""""""""{""{,i,{}ng""e:sT. + + + + + + + + + + + + + + + +[ + + + + + + + + + + + + + + + +Stri{"":5}ome UTF8 stringq":["à éËçù","æ—¥I Stri""""""""""{""{,""""""""""""""""""{""{,""""""""""""""""""{""{,""""""""""""""""""{""{,""""""""""""""""""{""{,""""""""""""""""""{""{,""""""""""""""""""{{,""""""""""""""""""{""{,""""""""""""""""""{""{,""""""""""""""""""{""{,""""""""""""""""""{""{,""""""""0:""SCI stri,{}ng""e:sTt ASCII Stri{"":tring""e:sTt ASCII Stri{"":5}ome UTF8 stringq":["à éËçù","æ—¥I Stri""""""""""{""{,""""""""""""""""""{""{,"""""""""",0""""""{""{,""""""""""""""""""{""{,""""""""""""""""""{""{,""""""""""""""""""{""{,""""""""""""""""""{{,""""""""""""""""""{""{,""""""""""""""""""{""{,""""""""""""""""""{""{,""""""""""""""""""{""{,""""""""0:""""""""""{""{,""""""""""""""""""{""{,""""""""""""""""""{""{,""""""""""""""(ZÝÙßtheyk Ѥ¿à¤¨à¥à¤¦à¥€"],"Keys & \"entiti]s\"":"helloִבְרִ*f(ZÝÙßtheyk Ñ\nºÐ¸Ð¹ ѯÐ-0.2e+3yk polÏski","\nàTest A''I{"":0}oee UT0ÑÑÐी"],"Keys & \"ti]s\"":"helloִבְרִ*f(ZÝÙßtheyk Ñ\nºÐ¸Ð¹ ѯзыothe""""""""""""""""""{""{,""""""""""""""{""{,""""""""""""""""""{""{,""""""""""""""""""{""{,""""""""""""""""""{""{,""""""""0:""SCI stri,{}ng""e:sTt ASCII Stri{"":tring""e:sTt ASCII Stri{"":5}ome UTF8 stringq":["à éËçù","æ—¥I Stri""""""""""{""{,""""""""""""""""""{""{,""""""""""""""""""{""{,""""""""""""""""""{""{,""""""""""""""""""{""{,""""""""""""""""""{""{,""""""""""""""""""{{,""""""""""""""""""{""{,""""""""""""""""""{""{,""""""""""""""""""{""{,""""""""""""""""""{""{,""""""""0:""""""""""{""{,""""""""""""""""""{""{,""""""""""""""""""{""{,""""""""""""""(ZÝÙßtheyk Ѥ¿à¤¨à¥à¤¦à¥€"],"Keys & \"entiti]s\"":"helloִבְרִ*f(ZÝÙßtheyk Ñ\nºÐ¸Ð¹ ѯÐ-0.2e+3yk p©Ã‹¸Ã§Ã¹","Test A''I{"":0}oee UT0ÑÑÐी"],"Keys & \"ti]s\"":"helloִבְרִ*f(ZÝÙßtheyk Ñ\nºÐ¸Ð¹ ѯзыotheyk polski","\nहà check this: falSs{"":5}ome UTF8 stringq":["à éËçù","æ—¥I Stri""""""""""{""…ÓÝ×""""""""""""""""{""{,""""""""""""""""""{""{,""""""""""""""""""{""{,""""""""""""""""""{""{,""""""""""""""""""{""{,""""""""""""""""""{{,""""""""""""""""""{""{,""""""""""""""""""{""{,""""""""""""""""""{""{,""""""""""""""""""{""{,""""""""0:""SCI stri,{}ng""e:sTt ASCII Stri{"":tring""e:sTt ASCII Stri{"":5}ome UTF8 stringq":["à éËçù","æ—¥I Stri""""""""""{""{,""""""""""""""""""{""{,"""""""""",0""""""{""{,""""""""""""""""""{""{,""""""""""""""""""{""{,""""""""""""""""""{""{,""""""""""""""""""{{,""""""""""""""""""{""{,""""""""""""""""""{""{,""""""""""""""""""{""{,""""""""""""""""""{""{,""""""""0:""""""""""{""{,""""""""""""""""""{""{,""""""""""""""""""{""{,""""""""""""""(ZÝÙßtheyk Ѥ¿à¤¨à¥à¤¦à¥€"],"Keys & \"entiti]s\"":"helloִבְרִ*f(ZÝÙßtheyk Ñ\nºÐ¸Ð¹ ѯÐ-0.2e+3yk polÏski","\nàTest A''I{"":0}oee UT0ÑÑÐी"],"Keys & \"ti]s\"":"helloִבְרִ*f(ZÝÙßtheyk Ñ\nºÐ¸Ð¹ ѯзыothe""""""""""""""""""{""{,""""""""""""""{""{,""""""""""""""""""{""{,""""""""""""""""""{""{,""""""""""""""""""{""{,""""""""0:""SCI stri,{}ng""e:sTt ASCII Stri{"":tring""e:sTt ASCII Stri{"":5}ome UTF8 stringq":["à éËçù","æ—¥I Stri""""""""""{""{,""""""""""""""""""{""{,""""""""""""""""""{""{,""""""""""""""""""{""{,""""""""""""""""""{""{,""""""""""""""""""{""{,""""""""""""""""""{{,""""""""""""""""""{""{,""""""""""""""""""{""{,""""""""""""""""""{""{,""""""""""""""""""{""{,""""""""0:""""""""""{""{,""""""""""""""""""{""{,""""""""""""""""""{""{,""""""""""""""(ZÝÙßtheyk Ѥ¿à¤¨à¥à¤¦à¥€"],"Keys & \"entiti]s\"":"helloִבְרִ*f(ZÝÙßtheyk Ñ\nºÐ¸Ð¹ ѯÐ-0.2e+3yk p©Ã‹¸Ã§Ã¹","Test A''I{"":0}oee UT0ÑÑÐी"],"Keys & \"ti]s\"":"helloִבְרִ*f(ZÝÙßtheyk Ñ\nºÐ¸Ð¹ ѯзыotheyk polski","\nहà check this: falSse2 !!!"}
\ No newline at end of file diff --git a/fuzz/jplist-crashes/clusterfuzz-testcase-minimized-jplist_fuzzer-5149455463088128 b/fuzz/jplist-crashes/clusterfuzz-testcase-minimized-jplist_fuzzer-5149455463088128 new file mode 100644 index 0000000..653c585 --- /dev/null +++ b/fuzz/jplist-crashes/clusterfuzz-testcase-minimized-jplist_fuzzer-5149455463088128 @@ -0,0 +1 @@ +{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\Xn‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!\\n‰!\n‰!"}{"\n\\\n‰!\n‰!n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}
\ No newline at end of file diff --git a/fuzz/jplist-crashes/clusterfuzz-testcase-minimized-jplist_fuzzer-5161359598288896 b/fuzz/jplist-crashes/clusterfuzz-testcase-minimized-jplist_fuzzer-5161359598288896 Binary files differnew file mode 100644 index 0000000..10c37ec --- /dev/null +++ b/fuzz/jplist-crashes/clusterfuzz-testcase-minimized-jplist_fuzzer-5161359598288896 diff --git a/fuzz/jplist-crashes/clusterfuzz-testcase-minimized-jplist_fuzzer-5486807695884288 b/fuzz/jplist-crashes/clusterfuzz-testcase-minimized-jplist_fuzzer-5486807695884288 new file mode 100644 index 0000000..e440e5c --- /dev/null +++ b/fuzz/jplist-crashes/clusterfuzz-testcase-minimized-jplist_fuzzer-5486807695884288 @@ -0,0 +1 @@ +3
\ No newline at end of file diff --git a/fuzz/jplist-crashes/clusterfuzz-testcase-minimized-jplist_fuzzer-5576833398079488 b/fuzz/jplist-crashes/clusterfuzz-testcase-minimized-jplist_fuzzer-5576833398079488 new file mode 100644 index 0000000..734562b --- /dev/null +++ b/fuzz/jplist-crashes/clusterfuzz-testcase-minimized-jplist_fuzzer-5576833398079488 @@ -0,0 +1 @@ +{""f ""}{""[""""""]""}{""[""""""]""}{""[""""""]""[""""""]""}{""[""""""]""}{""[""""""]""}{""[""""""]""{""}{""[""""""]""}{""[""""""]""}{""[""""]""}{""[""""""]""}{""[""""""]""}{""[""""""{""}{""[""""""]""}{""[""""""]""}{""[""""""]""}{""[""""]""}{""[""""{""[""]""}{""[""""""]""}{""[""""""]""}{""}{""}{""}{""}{""[""""""]""}{""[""""""]}{""[""""]""}{""[""""]""}{""}{""[""""""]""}""]}{""[""""""]""}{""}{""}{""[""""""]""}{""[""""]}]}{""[""""""]""}{""[""""""]""}{""}{""[""""""]""}}
\ No newline at end of file diff --git a/fuzz/jplist-crashes/clusterfuzz-testcase-minimized-jplist_fuzzer-5704016686874624 b/fuzz/jplist-crashes/clusterfuzz-testcase-minimized-jplist_fuzzer-5704016686874624 new file mode 100644 index 0000000..7853ee8 --- /dev/null +++ b/fuzz/jplist-crashes/clusterfuzz-testcase-minimized-jplist_fuzzer-5704016686874624 @@ -0,0 +1 @@ +{"Some Ak this: \u-170141183460469231731687303715884040192 !!!"}
\ No newline at end of file diff --git a/fuzz/jplist-crashes/clusterfuzz-testcase-minimized-jplist_fuzzer-6639076466360320 b/fuzz/jplist-crashes/clusterfuzz-testcase-minimized-jplist_fuzzer-6639076466360320 new file mode 100644 index 0000000..3cf20d5 --- /dev/null +++ b/fuzz/jplist-crashes/clusterfuzz-testcase-minimized-jplist_fuzzer-6639076466360320 @@ -0,0 +1 @@ +-
\ No newline at end of file diff --git a/fuzz/jplist-leaks/clusterfuzz-testcase-minimized-jplist_fuzzer-4881933237092352 b/fuzz/jplist-leaks/clusterfuzz-testcase-minimized-jplist_fuzzer-4881933237092352 new file mode 100644 index 0000000..d8ae3d3 --- /dev/null +++ b/fuzz/jplist-leaks/clusterfuzz-testcase-minimized-jplist_fuzzer-4881933237092352 @@ -0,0 +1 @@ +{"Some ASCII stringtring":"Test ASCII String","Some UTF8 strnngs":["à éèçù","日本語","汉è¯/漢語","한êµì–´/ì¡°ì„ ë§","руÑÑкий Ñзык","الْعَرَبيّة","עִבְרִית","jÄ™zyk polski¹à¤¿à¤¨à¥à¤¦à¥€"],"Keys & \"entities\"":"hellow world & others <nodes> are \"fun!?'","Boolean":false,"Anothe\uD800\uDC00rue,"Some Int":32434543632,"Some String with Unicode entity":"Yeah check this: \u1234 !!!"}
\ No newline at end of file diff --git a/fuzz/jplist-leaks/clusterfuzz-testcase-minimized-jplist_fuzzer-5069883912617984 b/fuzz/jplist-leaks/clusterfuzz-testcase-minimized-jplist_fuzzer-5069883912617984 new file mode 100644 index 0000000..a1f85f7 --- /dev/null +++ b/fuzz/jplist-leaks/clusterfuzz-testcase-minimized-jplist_fuzzer-5069883912617984 @@ -0,0 +1 @@ +{""A}
\ No newline at end of file diff --git a/fuzz/jplist-leaks/clusterfuzz-testcase-minimized-jplist_fuzzer-5816111696838656 b/fuzz/jplist-leaks/clusterfuzz-testcase-minimized-jplist_fuzzer-5816111696838656 new file mode 100644 index 0000000..f19d601 --- /dev/null +++ b/fuzz/jplist-leaks/clusterfuzz-testcase-minimized-jplist_fuzzer-5816111696838656 @@ -0,0 +1 @@ +[[][[][][][][][]{"ÿ222ÀÀÀÀÀÀÀÀÀÀÀÀ\uDBFF\uDFFFÀÀÀÀeÀÀ2ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿßÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ2221Ø2222222ÀÀÀÀÀÀÀÀÀÀÀ\uDBFF\uDFFFÀÀÀÀeÀÀ2ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ[]\r[][][][]ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿßÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ2221Ø2222222222h che[][][][][][][][][][][][][][][][][][][][][][][[][][][][][][][][][][][][][][][][][][][][][][][][][]22222h che22#"}[][][][][][][][][][][][][][][][][][][][][][][][][][][][][][]][]]
\ No newline at end of file diff --git a/fuzz/jplist.dict b/fuzz/jplist.dict new file mode 100644 index 0000000..e08245a --- /dev/null +++ b/fuzz/jplist.dict @@ -0,0 +1,52 @@ +# +# AFL dictionary for JSON +# ----------------------- +# +# Just the very basics. +# +# Inspired by a dictionary by Jakub Wilk <jwilk@jwilk.net> +# + +"0" +",0" +":0" +"0:" +"-1.2e+3" + +"true" +"false" +"null" + +"\"\"" +",\"\"" +":\"\"" +"\"\":" + +"{}" +",{}" +":{}" +"{\"\":0}" +"{{}}" + +"[]" +",[]" +":[]" +"[0]" +"[[]]" + +"''" +"\\" +"\\b" +"\\f" +"\\n" +"\\r" +"\\t" +"\\u0000" +"\\x00" +"\\0" +"\\uD800\\uDC00" +"\\uDBFF\\uDFFF" + +"\"\":0" +"//" +"/**/" diff --git a/libcnary/include/list.h b/fuzz/jplist_fuzzer.cc index 6b18e6f..a10da59 100644 --- a/libcnary/include/list.h +++ b/fuzz/jplist_fuzzer.cc @@ -1,10 +1,8 @@ /* - * list.h + * jplist_fuzzer.cc + * JSON plist fuzz target for libFuzzer * - * Created on: Mar 8, 2011 - * Author: posixninja - * - * Copyright (c) 2011 Joshua Hill. All Rights Reserved. + * Copyright (c) 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 @@ -21,20 +19,14 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ -#ifndef LIST_H_ -#define LIST_H_ - -#include "object.h" - -typedef struct list_t { - void* next; - void* prev; -} list_t; - -void list_init(struct list_t* list); -void list_destroy(struct list_t* list); +#include <plist/plist.h> +#include <stdio.h> -int list_add(struct list_t* list, struct object_t* object); -int list_remove(struct list_t* list, struct object_t* object); +extern "C" int LLVMFuzzerTestOneInput(const unsigned char* data, size_t size) +{ + plist_t root_node = NULL; + plist_from_json(reinterpret_cast<const char*>(data), size, &root_node); + plist_free(root_node); -#endif /* LIST_H_ */ + return 0; +} diff --git a/fuzz/jplist_fuzzer.options b/fuzz/jplist_fuzzer.options new file mode 100644 index 0000000..b22e679 --- /dev/null +++ b/fuzz/jplist_fuzzer.options @@ -0,0 +1,3 @@ +[libfuzzer] +max_len = 4096 +dict = jplist.dict diff --git a/fuzz/oplist-crashes/clusterfuzz-testcase-minimized-oplist_fuzzer-4503815405830144 b/fuzz/oplist-crashes/clusterfuzz-testcase-minimized-oplist_fuzzer-4503815405830144 new file mode 100644 index 0000000..11496c4 --- /dev/null +++ b/fuzz/oplist-crashes/clusterfuzz-testcase-minimized-oplist_fuzzer-4503815405830144 @@ -0,0 +1 @@ +"3ÿÿÿÿ"= /// hÐo/**5/*(*///6/*/#o/,{Å
\ No newline at end of file diff --git a/fuzz/oplist-crashes/clusterfuzz-testcase-minimized-oplist_fuzzer-4683683569467392 b/fuzz/oplist-crashes/clusterfuzz-testcase-minimized-oplist_fuzzer-4683683569467392 Binary files differnew file mode 100644 index 0000000..25d9ed9 --- /dev/null +++ b/fuzz/oplist-crashes/clusterfuzz-testcase-minimized-oplist_fuzzer-4683683569467392 diff --git a/fuzz/oplist-crashes/clusterfuzz-testcase-minimized-oplist_fuzzer-4716194114699264 b/fuzz/oplist-crashes/clusterfuzz-testcase-minimized-oplist_fuzzer-4716194114699264 new file mode 100644 index 0000000..2fa08dc --- /dev/null +++ b/fuzz/oplist-crashes/clusterfuzz-testcase-minimized-oplist_fuzzer-4716194114699264 @@ -0,0 +1 @@ +(<
\ No newline at end of file diff --git a/fuzz/oplist-crashes/clusterfuzz-testcase-minimized-oplist_fuzzer-4789915626110976 b/fuzz/oplist-crashes/clusterfuzz-testcase-minimized-oplist_fuzzer-4789915626110976 Binary files differnew file mode 100644 index 0000000..a36e0a2 --- /dev/null +++ b/fuzz/oplist-crashes/clusterfuzz-testcase-minimized-oplist_fuzzer-4789915626110976 diff --git a/fuzz/oplist-leaks/clusterfuzz-testcase-minimized-oplist_fuzzer-6043548602728448 b/fuzz/oplist-leaks/clusterfuzz-testcase-minimized-oplist_fuzzer-6043548602728448 new file mode 100644 index 0000000..6598aac --- /dev/null +++ b/fuzz/oplist-leaks/clusterfuzz-testcase-minimized-oplist_fuzzer-6043548602728448 @@ -0,0 +1,8 @@ +{JJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJtrings" = ( + "à éèçù", + "æi¥æœ¬èªž"Ä, + "汉è¯/漢語", + "한êµì–´/ì¡!?'"; + "Somš Int" = 32434543632; + "Some String with Unicode entity" = "Yea—ßœ—eck this: \U1234 !!!"; +} diff --git a/fuzz/oplist-leaks/clusterfuzz-testcase-minimized-oplist_fuzzer-6497436988473344 b/fuzz/oplist-leaks/clusterfuzz-testcase-minimized-oplist_fuzzer-6497436988473344 new file mode 100644 index 0000000..9d68933 --- /dev/null +++ b/fuzz/oplist-leaks/clusterfuzz-testcase-minimized-oplist_fuzzer-6497436988473344 @@ -0,0 +1 @@ +"
\ No newline at end of file diff --git a/fuzz/oplist.dict b/fuzz/oplist.dict new file mode 100644 index 0000000..1408c4a --- /dev/null +++ b/fuzz/oplist.dict @@ -0,0 +1,51 @@ +# +# AFL dictionary for OpenStep plist format +# ---------------------------------------- + +"0" +",0" +"=0" +"0=" + +"\"\"" +",\"\"" +"=\"\"" +"\"\"=" + +"=" +";" + +"{}" +",{}" +"={}" +"{\"\"=0}" +"{{}}" + +"()" +",()" +"=()" +"(0)" +"(())" + +"''" +"\\" +"\\b" +"\\f" +"\\n" +"\\r" +"\\t" +"\\U0000" +"\\a" +"\\b" +"\\f" +"\\n" +"\\r" +"\\t" +"\\v" +"\\0" +"\\uD800\\uDC00" +"\\uDBFF\\uDFFF" + +"\"\"=0" +"//" +"/**/" diff --git a/libcnary/iterator.c b/fuzz/oplist_fuzzer.cc index 492a8ae..0fabed8 100644 --- a/libcnary/iterator.c +++ b/fuzz/oplist_fuzzer.cc @@ -1,10 +1,8 @@ /* - * iterator.c + * oplist_fuzzer.cc + * OpenStep plist fuzz target for libFuzzer * - * Created on: Mar 8, 2011 - * Author: posixninja - * - * Copyright (c) 2011 Joshua Hill. All Rights Reserved. + * 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 @@ -21,41 +19,14 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ +#include <plist/plist.h> #include <stdio.h> -#include <stdlib.h> -#include <string.h> - -#include "list.h" -#include "object.h" -#include "iterator.h" - -void iterator_destroy(iterator_t* iterator) { - if(iterator) { - free(iterator); - } -} - -iterator_t* iterator_create(list_t* list) { - iterator_t* iterator = (iterator_t*) malloc(sizeof(iterator_t)); - if(iterator == NULL) { - return NULL; - } - memset(iterator, '\0', sizeof(iterator_t)); - if(list != NULL) { - // Create and bind to list - - } else { - // Empty Iterator - } - - return iterator; -} - -object_t* iterator_next(iterator_t* iterator) { - return NULL; -} +extern "C" int LLVMFuzzerTestOneInput(const unsigned char* data, size_t size) +{ + plist_t root_node = NULL; + plist_from_openstep(reinterpret_cast<const char*>(data), size, &root_node); + plist_free(root_node); -int iterator_bind(iterator_t* iterator, list_t* list) { - return -1; + return 0; } diff --git a/fuzz/oplist_fuzzer.options b/fuzz/oplist_fuzzer.options new file mode 100644 index 0000000..69a63d9 --- /dev/null +++ b/fuzz/oplist_fuzzer.options @@ -0,0 +1,3 @@ +[libfuzzer] +max_len = 4096 +dict = oplist.dict diff --git a/fuzz/test-fuzzers.sh b/fuzz/test-fuzzers.sh new file mode 100755 index 0000000..4fdf82b --- /dev/null +++ b/fuzz/test-fuzzers.sh @@ -0,0 +1,45 @@ +#!/bin/sh + +CURDIR=`pwd` +FUZZDIR=`dirname $0` + +cd ${FUZZDIR} + +if ! test -x xplist_fuzzer || ! test -x bplist_fuzzer || ! test -x jplist_fuzzer || ! test -x oplist_fuzzer; then + echo "ERROR: you need to build the fuzzers first." + cd ${CURDIR} + exit 1 +fi + +if ! test -d xplist-input || ! test -d bplist-input || ! test -d jplist-input || ! test -d oplist-input; then + echo "ERROR: fuzzer corpora directories are not present. Did you run init-fuzzers.sh ?" + cd ${CURDIR} + exit 1 +fi + +echo "### TESTING xplist_fuzzer ###" +if ! ./xplist_fuzzer xplist-input -dict=xplist.dict -max_len=65536 -runs=10000; then + cd ${CURDIR} + exit 1 +fi + +echo "### TESTING bplist_fuzzer ###" +if ! ./bplist_fuzzer bplist-input -dict=bplist.dict -max_len=4096 -runs=10000; then + cd ${CURDIR} + exit 1 +fi + +echo "### TESTING jplist_fuzzer ###" +if ! ./jplist_fuzzer jplist-input -dict=jplist.dict -max_len=65536 -runs=10000; then + cd ${CURDIR} + exit 1 +fi + +echo "### TESTING oplist_fuzzer ###" +if ! ./oplist_fuzzer oplist-input -dict=oplist.dict -max_len=65536 -runs=10000; then + cd ${CURDIR} + exit 1 +fi + +cd ${CURDIR} +exit 0 diff --git a/fuzz/xplist-crashes/clusterfuzz-testcase-4516959125241856 b/fuzz/xplist-crashes/clusterfuzz-testcase-4516959125241856 Binary files differnew file mode 100644 index 0000000..e7b3493 --- /dev/null +++ b/fuzz/xplist-crashes/clusterfuzz-testcase-4516959125241856 diff --git a/fuzz/xplist-crashes/clusterfuzz-testcase-4663876528373760 b/fuzz/xplist-crashes/clusterfuzz-testcase-4663876528373760 new file mode 100644 index 0000000..ec8b44d --- /dev/null +++ b/fuzz/xplist-crashes/clusterfuzz-testcase-4663876528373760 @@ -0,0 +1,5 @@ +<?xml version="1.0" encoding="UTF-8"?> +<plist> + <data> + /EN" "http://www.apple.com/DTDs========================/PropertyList-1.0.</data> +</plist> diff --git a/fuzz/xplist-crashes/clusterfuzz-testcase-5799076113154048 b/fuzz/xplist-crashes/clusterfuzz-testcase-5799076113154048 new file mode 100644 index 0000000..ff3100a --- /dev/null +++ b/fuzz/xplist-crashes/clusterfuzz-testcase-5799076113154048 @@ -0,0 +1,45 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http<key>test<!test></key> +://www.apple.com/D|Ds/Pr<false/>st-1.0.dtd"> +<plist version="1.0"> +<dict> + <key>Time1</key> + <dict> + <key>Time1</key> + <dict> + <key>Time1</key> + <dict> + <key>CF$UID</key> + <dict> + <key>Time1</key> + <dict> + <key>Time1</key> + <dict> + <key>Time1</key> + <dict> + <key>Time1</key> + <dict> + <key>Time1</key> + <dict> + <key>Time1</key> + <dict> + <key>Time1</key> + <dict> + <key>Time1</key> + <dict> + <key>Time1</key> + <dict> + <key>Time1</key> + <dict> + <key>Time1</key> + <dict> + <key>Time1</key> + <dict> + <key>Time1</key> + <date>2010-11-12T13:14:15Z</date> + <key>Time2</key> + <date>2008-07-06T05:04:03Z</date> + <key>Time3</key> + <date>1869-01-03T08:16:32Z</date> + <key>Time4</key> +
\ No newline at end of file diff --git a/fuzz/xplist-crashes/clusterfuzz-testcase-5922404220010496 b/fuzz/xplist-crashes/clusterfuzz-testcase-5922404220010496 Binary files differnew file mode 100644 index 0000000..49efd9d --- /dev/null +++ b/fuzz/xplist-crashes/clusterfuzz-testcase-5922404220010496 diff --git a/fuzz/xplist-crashes/clusterfuzz-testcase-6015098361479168 b/fuzz/xplist-crashes/clusterfuzz-testcase-6015098361479168 Binary files differnew file mode 100644 index 0000000..52c9d0a --- /dev/null +++ b/fuzz/xplist-crashes/clusterfuzz-testcase-6015098361479168 diff --git a/fuzz/xplist-crashes/clusterfuzz-testcase-6043836860071936 b/fuzz/xplist-crashes/clusterfuzz-testcase-6043836860071936 Binary files differnew file mode 100644 index 0000000..6c7ea6f --- /dev/null +++ b/fuzz/xplist-crashes/clusterfuzz-testcase-6043836860071936 diff --git a/fuzz/xplist-crashes/clusterfuzz-testcase-6047509906456576 b/fuzz/xplist-crashes/clusterfuzz-testcase-6047509906456576 new file mode 100644 index 0000000..96a889a --- /dev/null +++ b/fuzz/xplist-crashes/clusterfuzz-testcase-6047509906456576 @@ -0,0 +1,10050 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> +<plist version="1.0"> +<dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + <array> + </array><dict> + <key>test</key> + <array> + <array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <dict> + <key>test</key> + <key>foo</key> + <array> + <array> + <array> + <integer>1</integer> + <dict> + <key>test</key> + <array> + <integer>1</integer> + <key>test</key> + <string>foo</string> + <array> + <array> + </array> + <array> + <array> + <dict> + <array> + <array> + <integer>1</integer> + <array> + <key>test</key> + <array> + <integer>1</integer> + <key>foo</key> + <dict> + <key>foo</key> + <array> + <array> + <integer>1</integer> + <array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + </array><dict> + <key>test</key> + <integer>1</integer> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + </dict> + <array> + <integer>1</integer> + <key>test</key> + <array> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <integer>1</integer> + <integer>1</integer> + <key>test</key> + <integer>1</integer> + <key>foo</key> + <array> + <array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <array> + <key>foo</key> + <array> + <integer>1</integer> + <array> + <integer>1</integer> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + </array><dict> + <key>foo</key> + <array> + <array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + </array> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>test</key> + <array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <integer>1</integer> + <array> + <integer>1</integer> + <integer>1</integer> + <array> + <array> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + <key>test</key> + <array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <array> + <integer>1</integer> + </array> + <string>foo</string> + <array> + <array> + <array> + </array> + <dict> + <key>test</key> + <array> + <array> + <array> + <integer>1</integer> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <array> + <integer>1</integer> + <string>foo</string> + <key>test</key> + <array> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + <integer>1</integer> + <key>test</key> + <array> + <array> + <array> + <dict> + <array> + <integer>1</integer> + <array> + <array> + <array> + <key>test</key> + <array> + </array> + <dict> + <key>test</key> + <integer>1</integer> + <array> + <integer>1</integer> + <array> + </array> + <array> + <array> + <dict> + <key>test</key> + <array> + <array> + </array> + <key>foo</key> + <array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + <key>test</key> + <array> + <integer>1</integer> + <array> + <dict> + <array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + <key>foo</key> + <array> + <array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <integer>1</integer> + <key>foo</key> + <array> + <array> + </array> + <dict> + <key>test</key> + <array> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + <array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <integer>1</integer> + <integer>1</integer> + <key>foo</key> + <array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <array> + <array> + <integer>1</integer> + <key>foo</key> + <array> + <array> + <integer>1</integer> + <key>test</key> + <array> + <array> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + </array> + <array> + <key>test</key> + <array> + <array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + </array> + <array> + <array> + <integer>1</integer> + <array> + <integer>1</integer> + <integer>1</integer> + <array> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>foo</key> + <array> + <integer>1</integer> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>foo</key> + <array> + <array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + <array> + <integer>1</integer> + <key>test</key> + <array> + <integer>1</integer> + <key>test</key> + <integer>1</integer> + <array> + <array> + <dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + <array> + <array> + <integer>1</integer> + <array> + <integer>1</integer> + <integer>1</integer> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + <array> + <key>test</key> + <array> + <array> + <array> + <integer>1</integer> + <key>test</key> + <array> + <array> + <array> + <array> + </array> + <array> + <array> + <array> + <array> + </array> + <dict> + <array> + <array> + <integer>1</integer> + <array> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <key>test</key> + <array> + <array> + <array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + <array> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + </array><dict> + <array> + <array> + <dict> + <key>test</key> + <array> + <array> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <array> + <integer>1</integer> + <integer>1</integer> + <key>test</key> + <array> + <integer>1</integer> + <dict> + <key>test</key> + <array> + <integer>1</integer> + <array> + <array> + <integer>1</integer> + <array> + <array> + <array> + </array><dict> + <array> + <array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <array> + <array> + </array> + <key>foo</key> + <array> + <array> + <key>test</key> + <string>foo</string> + <array> + <array> + <key>test</key> + <array> + <array> + <array> + <array> + <integer>1</integer> + <array> + <array> + <array> + <dict> + <key>test</key> + <array> + <array> + <array> + <array> + <integer>1</integer> + <array> + <array> + <array> + <integer>1</integer> + <array> + <key>test</key> + <array> + <array> + <array> + <array> + <array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <array> + <array> + <array> + <integer>1</integer> + <array> + <array> + <array> + <array> + <integer>1</integer> + <array> + <array> + <array> + <array> + <integer>1</integer> + <dict> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + <array> + <array> + <integer>1</integer> + <dict> + <key>test</key> + <string>foo</string> + <array> + <array> + <dict> + <key>test</key> + <key>test</key> + <array> + <array> + <integer>1</integer> + <dict> + <key>test</key> + <key>test</key> + <array> + <array> + <integer>1</integer> + <key>foo</key> + <array> + <key>test</key> + <array> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <dict> + <key>test</key> + <string>foo</string> + <key>test</key> + <array> + <integer>1</integer> + </array> + <array> + <array> + <key>foo</key> + <array> + <array> + <array> + <array> + <array> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + <integer>1</integer> + <key>foo</key> + <array> + <array> + <array> + <array> + <array> + <array> + <array> + <integer>1</integer> + <dict> + <key>test</key> + <integer>1</integer> + <array> + <array> + <dict> + <key>test</key> + <array> + <array> + </array><dict> + <key>foo</key> + <array> + <array> + <dict> + </dict> + </array><dict> + <array> + <key>test</key> + <array> + <array> + <array> + <dict> + </dict> + <array> + <array> + <integer>1</integer> + <array> + <array> + </array><dict> + <array> + <array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + <integer>1</integer> + <key>foo</key> + <array> + <array> + <integer>1</integer> + <key>test</key> + <array> + <integer>1</integer> + <array> + <integer>1</integer> + <key>foo</key> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + <key>foo</key> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <array> + <integer>1</integer> + <array> + </array> + <array> + <integer>1</integer> + <array> + <array> + <key>test</key> + <array> + <array> + <array> + <array> + <dict> + <array> + <array> + <array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + </array><dict> + <array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + </array><dict> + <key>test</key> + <array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <array> + </array> + <dict> + <array> + <array> + <integer>1</integer> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + <array> + <integer>1</integer> + <array> + <array> + <integer>1</integer> + <dict> + <array> + <integer>1</integer> + <key>foo</key> + <array> + </array> + <dict> + <array> + <array> + <array> + <array> + <array> + <array> + <array> + <array> + <dict> + <array> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + <array> + <array> + <integer>1</integer> + <dict> + <array> + <array> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <array> + <array> + <integer>1</integer> + <dict> + <key>test</key> + <array> + <integer>1</integer> + </array><dict> + <array> + <array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + </array><dict> + <array> + <array> + <integer>1</integer> + <dict> + <key>test</key> + <array> + <array> + <array> + <dict> + <key>test</key> + <array> + <integer>1</integer> + </array><dict> + <dict> + <key>test</key> + <string>foo</string> + <array> + <integer>1</integer> + <key>test</key> + <dict> + </dict> + <array> + <integer>1</integer> + <key>foo</key> + <array> + <dict> + <array> + <key>foo</key> + <array> + <array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + </array><dict> + <key>test</key> + <array> + <array> + <array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <array> + </array><dict> + <array> + <array> + <dict> + <key>test</key> + <array> + <key>test</key> + <array> + <integer>1</integer> + <array> + <array> + </array> + <dict> + <key>test</key> + <string>foo</string> + <array> + <array> + <integer>1</integer> + <integer>1</integer> + <key>foo</key> + <array> + <array> + <integer>1</integer> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + </array> + <array> + <array> + <integer>1</integer> + <dict> + <key>test</key> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <array> + <array> + <integer>1</integer> + <integer>1</integer> + <array> + <array> + <integer>1</integer> + <array> + <array> + <key>test</key> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <array> + <integer>1</integer> + </array><dict> + <array> + <array> + <array> + <dict> + <array> + <integer>1</integer> + <dict> + </dict> + <array> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <dict> + </dict> + <array> + <array> + <array> + <array> + <array> + <array> + <array> + <integer>1</integer> + <array> + </array><dict> + <key>test</key> + <array> + <array> + <integer>1</integer> + <integer>1</integer> + <array> + <dict> + <array> + <key>test</key> + <array> + <dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + <array> + <array> + </array> + <array> + <array> + <integer>1</integer> + <array> + <integer>1</integer> + <array> + <key>test</key> + <array> + <integer>1</integer> + <dict> + <array> + <integer>1</integer> + <array> + <integer>1</integer> + <dict> + </dict> + <array> + <integer>1</integer> + <array> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <array> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + <dict> + <array> + <array> + </array> + <dict> + <key>test</key> + <integer>1</integer> + <array> + <integer>1</integer> + <array> + <array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <key>foo</key> + <array> + <array> + <array> + <array> + <array> + <key>test</key> + <array> + <array> + <array> + <array> + <array> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + </array><dict> + <array> + <key>foo</key> + <array> + <array> + <key>test</key> + <key>test</key> + <array> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <array> + </array><dict> + <key>test</key> + <array> + <key>foo</key> + <dict> + <key>test</key> + <array> + <array> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <array> + <array> + <array> + <integer>1</integer> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <key>test</key> + <array> + <integer>1</integer> + <key>test</key> + <array> + <array> + <string>foo</string> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <array> + <integer>1</integer> + <integer>1</integer> + <key>foo</key> + <array> + <key>test</key> + <string>foo</string> + <array> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + <key>foo</key> + <array> + <dict> + <key>test</key> + <string>foo</string> + <array> + <array> + <array> + <dict> + <key>test</key> + <array> + <integer>1</integer> + <key>foo</key> + <array> + <dict> + <array> + <array> + <array> + <key>test</key> + <dict> + <key>test</key> + <string>foo</string> + <key>test</key> + <array> + <integer>1</integer> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + </array> + <array> + <integer>1</integer> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + <array> + <dict> + <array> + <key>test</key> + <array> + <integer>1</integer> + </array> + <array> + <dict> + <key>test</key> + <string>foo</string> + <array> + <dict> + <key>test</key> + <string>foo</string> + <key>test</key> + <array> + </array><dict> + <array> + <integer>1</integer> + <array> + <array> + <integer>1</integer> + <dict> + <key>foo</key> + <array> + <array> + <integer>1</integer> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + <array> + </array> + <array> + <array> + <integer>1</integer> + <dict> + <dict> + <array> + <array> + <array> + <integer>1</integer> + <dict> + <key>test</key> + <array> + <array> + <key>foo</key> + <array> + <integer>1</integer> + <array> + <integer>1</integer> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + </array><dict> + <array> + <dict> + <key>test</key> + <array> + <integer>1</integer> + <key>test</key> + <array> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <array> + </array> + <array> + <array> + <key>foo</key> + <array> + <key>foo</key> + <array> + <array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + </array><dict> + <key>test</key> + <key>foo</key> + <dict> + <key>foo</key> + <array> + <array> + <array> + <array> + <array> + <key>test</key> + <integer>1</integer> + <dict> + <key>test</key> + <array> + <array> + <array> + <array> + <integer>1</integer> + <array> + <array> + <array> + <array> + <array> + <integer>1</integer> + <array> + <integer>1</integer> + <key>test</key> + <array> + <dict> + <array> + <dict> + <array> + <integer>1</integer> + <array> + <integer>1</integer> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + <key>foo</key> + <dict> + <key>test</key> + <array> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <array> + <array> + <dict> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <dict> + <key>test</key> + <array> + </array><dict> + <key>test</key> + <key>foo</key> + <array> + <array> + <array> + <array> + <key>foo</key> + <array> + <key>foo</key> + <array> + <array> + <dict> + <key>test</key> + <array> + <integer>1</integer> + <array> + <array> + <array> + <dict> + <key>test</key> + <array> + <array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <key>foo</key> + <array> + <key>test</key> + <string>foo</string> + <key>test</key> + <array> + <key>foo</key> + <array> + <array> + <array> + <dict> + <array> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <array> + <key>test</key> + <array> + <dict> + <key>test</key> + <string>foo</string> + <array> + <array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + <array> + <array> + <dict> + <key>foo</key> + <array> + <array> + <key>test</key> + <array> + <integer>1</integer> + <array> + <array> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <key>test</key> + <array> + <key>foo</key> + <array> + <dict> + </dict> + <array> + <array> + </array> + <dict> + <key>test</key> + <array> + <array> + <array> + <array> + <array> + <array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <key>test</key> + <array> + <array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + <dict> + <key>test</key> + <key>foo</key> + <array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + </array> + <array> + <dict> + <array> + <array> + <integer>1</integer> + <dict> + <array> + <array> + <array> + <integer>1</integer> + <dict> + <array> + <key>foo</key> + <array> + <array> + <dict> + <array> + <array> + <array> + <integer>1</integer> + <dict> + <array> + <integer>1</integer> + <integer>1</integer> + <array> + <key>foo</key> + <array> + <key>foo</key> + <array> + <dict> + <key>test</key> + <array> + <array> + <integer>1</integer> + <array> + <array> + <array> + <array> + <integer>1</integer> + <key>foo</key> + <array> + <array> + <array> + <integer>1</integer> + <integer>1</integer> + <dict> + <key>test</key> + <key>foo</key> + <array> + <array> + <array> + <dict> + <array> + <integer>1</integer> + <key>test</key> + <array> + <array> + <integer>1</integer> + <array> + <array> + <integer>1</integer> + <dict> + <array> + <integer>1</integer> + <array> + </array> + <dict> + <key>test</key> + <array> + <integer>1</integer> + <array> + <array> + <dict> + <key>test</key> + <array> + <array> + <array> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + </array> + <key>foo</key> + <array> + <array> + <key>test</key> + <array> + <integer>1</integer> + <array> + <array> + <array> + <integer>1</integer> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <array> + <array> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + </array><dict> + <key>test</key> + <array> + <dict> + <array> + <array> + <integer>1</integer> + <array> + <array> + <integer>1</integer> + </array> + <array> + <array> + </array> + <key>foo</key> + <array> + <dict> + </dict> + <array> + <key>test</key> + <array> + <key>foo</key> + <array> + <array> + <array> + <integer>1</integer> + <array> + <integer>1</integer> + <integer>1</integer> + <array> + <dict> + <key>test</key> + <string>foo</string> + <array> + <array> + <integer>1</integer> + <array> + <array> + <array> + <integer>1</integer> + <array> + <dict> + <key>test</key> + <dict> + <array> + <integer>1</integer> + <key>test</key> + <array> + <array> + <array> + <array> + <dict> + <key>test</key> + <array> + </array><dict> + <array> + <array> + <array> + <array> + <array> + <array> + <array> + <array> + <array> + <integer>1</integer> + <dict> + <array> + <integer>1</integer> + <array> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + <array> + <array> + <integer>1</integer> + <array> + <key>foo</key> + <array> + <integer>1</integer> + <array> + <integer>1</integer> + <array> + <dict> + <key>test</key> + <string>foo</string> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <key>test</key> + <array> + <array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <integer>1</integer> + </array><dict> + <array> + <integer>1</integer> + <array> + <integer>1</integer> + <key>test</key> + <array> + <dict> + <key>test</key> + <array> + <key>test</key> + <array> + </array> + <array> + <array> + <dict> + <key>test</key> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + </array> + <dict> + <key>test</key> + <array> + <integer>1</integer> + <array> + <array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + </array><dict> + <array> + <integer>1</integer> + <integer>1</integer> + <array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + <array> + <array> + <key>test</key> + <array> + <integer>1</integer> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + <array> + </array> + <dict> + </dict> + <array> + </array><dict> + <array> + <array> + <dict> + <array> + <array> + <dict> + <key>test</key> + <string>foo</string> + <array> + <array> + <array> + <dict> + <array> + <array> + <array> + <array> + <integer>1</integer> + <integer>1</integer> + <dict> + <array> + <key>test</key> + <array> + <array> + <array> + <array> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + <array> + <array> + <array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <array> + <array> + <array> + <integer>1</integer> + <array> + <array> + <array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <array> + <integer>1</integer> + <integer>1</integer> + <array> + <array> + <dict> + </dict> + <array> + <integer>1</integer> + <key>test</key> + <array> + <key>foo</key> + <array> + <array> + <array> + <integer>1</integer> + <key>foo</key> + <array> + <array> + <key>foo</key> + <array> + <string>foo</string> + <array> + <integer>1</integer> + <dict> + </dict> + <array> + <integer>1</integer> + <array> + <array> + <key>test</key> + <array> + <array> + <array> + <integer>1</integer> + <array> + <integer>1</integer> + <array> + <integer>1</integer> + <array> + <dict> + <array> + <array> + <key>test</key> + <array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + <array> + <array> + <array> + </array> + <key>test</key> + <string>foo</string> + <array> + <array> + <dict> + <array> + <key>test</key> + <array> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + <array> + <integer>1</integer> + <string>foo</string> + <array> + <array> + <dict> + <array> + <key>foo</key> + <array> + <array> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <key>foo</key> + <array> + <dict> + <key>test</key> + <string>foo</string> + <array> + <array> + <array> + <integer>1</integer> + <key>foo</key> + <array> + <key>test</key> + <array> + <integer>1</integer> + <array> + <array> + <array> + <array> + <integer>1</integer> + <key>foo</key> + <array> + <array> + <integer>1</integer> + <array> + <integer>1</integer> + <array> + <integer>1</integer> + <array> + <array> + <string>foo</string> + <array> + <array> + <array> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <key>test</key> + <array> + <array> + <dict> + <key>test</key> + <dict> + <key>test</key> + <array> + <array> + <integer>1</integer> + <integer>1</integer> + <array> + <array> + <integer>1</integer> + <key>foo</key> + <array> + <array> + <integer>1</integer> + <key>foo</key> + <string>foo</string> + <array> + <integer>1</integer> + <array> + <integer>1</integer> + <array> + <dict> + <array> + <array> + <array> + <array> + <array> + <array> + <dict> + <key>test</key> + <string>foo</string> + <array> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + <array> + <dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + <array> + <key>foo</key> + <array> + <array> + </array><dict> + <key>foo</key> + <array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <array> + <integer>1</integer> + <array> + <array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + <key>test</key> + <array> + <array> + <array> + </array><dict> + <array> + <integer>1</integer> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <array> + <array> + <integer>1</integer> + <key>foo</key> + <array> + <array> + <integer>1</integer> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <array> + <array> + </array><dict> + <key>test</key> + <array> + </array><dict> + <array> + <array> + <array> + <array> + <array> + <integer>1</integer> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <dict> + <key>test</key> + <key>foo</key> + <array> + <key>test</key> + <array> + <integer>1</integer> + </array><dict> + <array> + <array> + <array> + <array> + <array> + <dict> + </dict> + <array> + </array><dict> + <key>test</key> + <integer>1</integer> + <array> + <integer>1</integer> + <array> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <key>foo</key> + <array> + <array> + <array> + <integer>1</integer> + <array> + <array> + <array> + <key>test</key> + <string>foo</string> + <array> + <array> + <dict> + <array> + <array> + <array> + <integer>1</integer> + <dict> + </dict> + <array> + <integer>1</integer> + </array><dict> + <array> + <array> + <array> + <array> + <array> + <array> + <array> + <key>test</key> + <array> + <dict> + <key>test</key> + <array> + <integer>1</integer> + <array> + <array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + </array> + <array> + <integer>1</integer> + <array> + <integer>1</integer> + <array> + <array> + <array> + <integer>1</integer> + <dict> + <key>test</key> + <array> + <integer>1</integer> + </array><dict> + <array> + <key>foo</key> + <array> + <array> + <array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + <array> + <array> + <array> + <dict> + <dict> + <array> + <array> + </array><dict> + <array> + <array> + <integer>1</integer> + <array> + <array> + </array> + <dict> + <array> + <array> + <array> + <array> + <array> + <array> + <dict> + </dict> + <integer>1</integer> + <array> + <array> + <integer>1</integer> + <array> + <integer>1</integer> + <array> + <array> + <integer>1</integer> + <key>foo</key> + <array> + <integer>1</integer> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <key>foo</key> + <array> + <dict> + <array> + <array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <array> + <array> + <array> + <array> + <array> + <integer>1</integer> + <array> + <array> + <key>test</key> + <array> + <integer>1</integer> + <array> + <array> + <array> + <dict> + <array> + <integer>1</integer> + <dict> + <array> + <array> + <key>test</key> + <array> + <array> + <array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <array> + <array> + <key>test</key> + <string>foo</string> + <array> + <integer>1</integer> + <key>foo</key> + <array> + <array> + <integer>1</integer> + <array> + <array> + <array> + <key>foo</key> + <array> + <array> + </array> + <dict> + <key>test</key> + <key>foo</key> + <array> + <array> + <dict> + <array> + <integer>1</integer> + <integer>1</integer> + <array> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + </array> + <array> + <key>foo</key> + <array> + <key>test</key> + <array> + <integer>1</integer> + <array> + <array> + <integer>1</integer> + <dict> + </dict> + <array> + <integer>1</integer> + </array><dict> + <array> + <array> + <array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <array> + <array> + <array> + <array> + <array> + <array> + <dict> + <array> + <integer>1</integer> + <array> + <array> + <array> + <key>foo</key> + <array> + <array> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + <array> + <array> + <array> + </array> + <array> + <array> + <array> + <array> + <integer>1</integer> + <array> + <array> + </array> + <dict> + <array> + <integer>1</integer> + <array> + <array> + <array> + <key>foo</key> + <array> + <dict> + <key>test</key> + <array> + <integer>1</integer> + <array> + <array> + <integer>1</integer> + </array> + <dict> + </dict> + <array> + <integer>1</integer> + </array><dict> + <key>test</key> + <array> + <integer>1</integer> + <array> + <integer>1</integer> + <key>test</key> + <array> + <array> + <array> + <array> + <array> + <dict> + <dict> + <key>test</key> + <string>foo</string> + <key>test</key> + <array> + <integer>1</integer> + </array> + <array> + <array> + <key>test</key> + <array>
\ No newline at end of file diff --git a/fuzz/xplist-crashes/clusterfuzz-testcase-6482601374121984 b/fuzz/xplist-crashes/clusterfuzz-testcase-6482601374121984 Binary files differnew file mode 100644 index 0000000..54111bb --- /dev/null +++ b/fuzz/xplist-crashes/clusterfuzz-testcase-6482601374121984 diff --git a/fuzz/xplist-crashes/crash-09788d7acb46b4b177422f9c4ee54556a3ad1f17 b/fuzz/xplist-crashes/crash-09788d7acb46b4b177422f9c4ee54556a3ad1f17 new file mode 100644 index 0000000..9038147 --- /dev/null +++ b/fuzz/xplist-crashes/crash-09788d7acb46b4b177422f9c4ee54556a3ad1f17 @@ -0,0 +1,53 @@ +<plist version="1.0"> +<dict> + <key>some test entitites</key> + <dict> + <key>copyright</key> + <string>©</string> + <key>multiple entitites</key> + <string>⼠⼡⼢</string> + <key>multiple entitites with characters inbetween</key> + <string>a⼣b⼤c⼥d</string> + </dict> + <key>one byte entity</key> + <dict> + <key>one byte entity</key> + <string>@</string> + <key>one byte entity with text after</key> + <string>@z</string> + <key>one byte entity with text before</key> + <string>a@</string> + <key>one byte entity with text before & after</key> + <string>a@z</string> + </dict> + <key>two-byte utf-8</key> + <dict> + <key>two-byte utf-8</key> + <string>Ä</string> + <key>two-byte utf-8 with text after</key> + <string>Ëyz</string> + <key>two-byte utf-8 with text before</key> + <string>abÖ</string> + <key>two-byte utf-8 with text before & after</key> + <string>abÜyz</string> + </dict> + <key>three byte utf-8</key> + <dict> + <key>three byte utf-8</key> + <string>¢</string> + <key>three byte utf-8 with text after</key> + <string>£xyz</string> + <key>three byte utf-8 with text bef&a;</key> + <string>abc¥</string> + <key>three byte utf-8 with text before & after</key> + (<string>abc₩xyz</string> + </dict> + <key>four byte utf-8</key> + <dict> + <key>four byte utf-8</key> + <string>𝕢</string> + with text before & after</key> + <string>abcd𝕥wxyz</string> + </dict> +</dict> +</plist> diff --git a/fuzz/xplist-crashes/crash-155c60d5c4b2778110fb4a633caa7d64eb9b99c1 b/fuzz/xplist-crashes/crash-155c60d5c4b2778110fb4a633caa7d64eb9b99c1 new file mode 100644 index 0000000..9954475 --- /dev/null +++ b/fuzz/xplist-crashes/crash-155c60d5c4b2778110fb4a633caa7d64eb9b99c1 @@ -0,0 +1,9 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.aPropertyList-1.0.dtd"> +<plist version="1.0teger> + <integer>9223372036854775807</integer> + <integer>-9223372036854775808</integer> + <integer>9223372036854775808</036854775808</in6854775808</integer> + <integer>9223372036854775808</036854775808</integer> +</array> +</plist> diff --git a/fuzz/xplist-crashes/crash-348c909a0b3532977185f551eb5d2ed04e694883 b/fuzz/xplist-crashes/crash-348c909a0b3532977185f551eb5d2ed04e694883 new file mode 100644 index 0000000..2942529 --- /dev/null +++ b/fuzz/xplist-crashes/crash-348c909a0b3532977185f551eb5d2ed04e694883 @@ -0,0 +1,11 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> +<plist version="1.0"> +<array> + <integer>-1</integer> + <integer>18446744073709551615</integer> + <integer>9223372036854775807</integer> + <integer>-9223372036854775808</integer> + <integer>9223372036854775808</integer> +</array> +</plist> diff --git a/fuzz/xplist-crashes/crash-7bc172e27a2b1d4fcaf031f59d1f08fb83081e4a b/fuzz/xplist-crashes/crash-7bc172e27a2b1d4fcaf031f59d1f08fb83081e4a new file mode 100644 index 0000000..cda6a0f --- /dev/null +++ b/fuzz/xplist-crashes/crash-7bc172e27a2b1d4fcaf031f59d1f08fb83081e4a @@ -0,0 +1,8 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> +<plist versiog="1.0"> +<dict> + <key>Time1</key> + <date>2010-11-12T13:14:15Z</date> + <key>Time2</key> + <date>4016-07-0-11<!---
\ No newline at end of file diff --git a/fuzz/xplist-crashes/crash-80c28b0e2a446665e68927dd4441c10c9a68e64e b/fuzz/xplist-crashes/crash-80c28b0e2a446665e68927dd4441c10c9a68e64e Binary files differnew file mode 100644 index 0000000..7a7d8f1 --- /dev/null +++ b/fuzz/xplist-crashes/crash-80c28b0e2a446665e68927dd4441c10c9a68e64e diff --git a/fuzz/xplist-crashes/crash-8acf177da60ecf13c36d0da1b8e25de891980ea6 b/fuzz/xplist-crashes/crash-8acf177da60ecf13c36d0da1b8e25de891980ea6 new file mode 100644 index 0000000..9a64518 --- /dev/null +++ b/fuzz/xplist-crashes/crash-8acf177da60ecf13c36d0da1b8e25de891980ea6 @@ -0,0 +1,5 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"" http://www.apple.com/DTDs/PropertyList-1.0.dtd<integer>-1</integer> + <integnteger> +</array> +</plist> diff --git a/fuzz/xplist-crashes/crash-94276cf69a50e78dd6cdf079c208cb80f357cd33 b/fuzz/xplist-crashes/crash-94276cf69a50e78dd6cdf079c208cb80f357cd33 Binary files differnew file mode 100644 index 0000000..1eed6ac --- /dev/null +++ b/fuzz/xplist-crashes/crash-94276cf69a50e78dd6cdf079c208cb80f357cd33 diff --git a/fuzz/xplist-crashes/crash-9ddb891a0ec42401eb4629e6b99e01aef7909eb9 b/fuzz/xplist-crashes/crash-9ddb891a0ec42401eb4629e6b99e01aef7909eb9 Binary files differnew file mode 100644 index 0000000..fb9a05f --- /dev/null +++ b/fuzz/xplist-crashes/crash-9ddb891a0ec42401eb4629e6b99e01aef7909eb9 diff --git a/fuzz/xplist-crashes/crash-a8b44e1f5be4f75f00a937016ec5405bce77a0f9 b/fuzz/xplist-crashes/crash-a8b44e1f5be4f75f00a937016ec5405bce77a0f9 Binary files differnew file mode 100644 index 0000000..f06d59b --- /dev/null +++ b/fuzz/xplist-crashes/crash-a8b44e1f5be4f75f00a937016ec5405bce77a0f9 diff --git a/fuzz/xplist-crashes/crash-da39a3ee5e6b4b0d3255bfef95601890afd80709 b/fuzz/xplist-crashes/crash-da39a3ee5e6b4b0d3255bfef95601890afd80709 new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/fuzz/xplist-crashes/crash-da39a3ee5e6b4b0d3255bfef95601890afd80709 diff --git a/fuzz/xplist-crashes/crash-f03c680ee1c4d84c28e731752df77a61e213a24d b/fuzz/xplist-crashes/crash-f03c680ee1c4d84c28e731752df77a61e213a24d new file mode 100644 index 0000000..278dbcc --- /dev/null +++ b/fuzz/xplist-crashes/crash-f03c680ee1c4d84c28e731752df77a61e213a24d @@ -0,0 +1,72 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> +<plist version="1.0"> +<dict> + <key>Some ASCII string</key> + <string>Test ASCII String</string> + <key>Some UTF8 strings</key> + <array> + <string>à éèçù</string> + <string>日本語</string> + <string>汉è¯/漢語</string> + <string>한êµì–´/ì¡°ì„ ë§</string> + <string>руÑÑкий Ñзык</string> + <string>الْعَرَبيّة</string> + <string>עִבְרִית</string> + <string>jÄ™zyk polski</string> + <string>हिनà¥à¤¦à¥€</string> + </array> + <key>Keys & "entities"</key> + <string>hellow world & others <nodes> are "fun!?'</string> + <key>Boolean</key> + <false/> + <key>Another Boolean</key> + <true/> + <key>Some Int</key> + <integer>32434543632</integer> + <key>Some Real</key> + <real>58654.347656</real> + <key>Some Date</key> + <date>2009-02-12T22:23:00Z</date> + <key>Some Data</key> + <data> + MDEyMzQ1Njc4OVRFU1QwMTIzNDU2Nzg5dGVzdCbDqSInKC3DqF/Dp8OgKT0rwrAkwqMkXsKoKsK1 + w7klIcKnOi87Liw/Pjx+I3tbfGBcXkBdfcKkCjAxMjM0NTY3ODlURVNUMDEyMzQ1Njc4OXRlc3Qm + w6kiJygtw6hfw6fDoCk9K8KwJMKjJF7CqCrCtcO5JSHCpzovOy4sPz48fiN7W3xgXF5AXX3CpAow + MTIzNDU2Nzg5VEVTVDAxMjM0NTY3ODl0ZXN0JsOpIicoLcOoX8Onw6ApPSvCsCTCoyRewqgqwrXD + uSUhwqc6LzsuLD8+PH4je1t8YFxeQF19wqQKMDEyMzQ1Njc4OVRFU1QwMTIzNDU2Nzg5dGVzdCbD + qSInKC3DqF/Dp8OgKT0rwrAkwqMkXsKoKsK1w7klIcKnOi87Liw/Pjx+I3tbfGBcXkBdfcKkCjAx + MjM0NTY3ODlURVNUMDEyMzQ1Njc4OXRlc3Qmw6kiJygtw6hfw6fDoCk9K8KwJMKjJF7CqCrCtcO5 + JSHCpzovOy4sPz48fiN7W3xgXF5AXX3CpAowMTIzNDU2Nzg5VEVTVDAxMjM0NTY3ODl0ZXN0JsOp + IicoLcOoX8Onw6ApPSvCsCTCoyRewqgqwrXDuSUhwqc6LzsuLD8+PH4je1t8YFxeQF19wqQKMDEy + MzQ1Njc4OVRFU1QwMTIzNDU2Nzg5dGVzdCbDqSInKC3DqF/Dp8OgKT0rwrAkwqMkXsKoKsK1w7kl + IcKnOi87Liw/Pjx+I3tbfGBcXkBdfcKkCjAxMjM0NTY3ODlURVNUMDEyMzQ1Njc4OXRlc3Qmw6ki + Jygtw6hfw6fDoCk9K8KwJMKjJF7CqCrCtcO5JSHCpzovOy4sPz48fiN7W3xgXF5AXX3CpAowMTIz + NDU2Nzg5VEVTVDAxMjM0NTY3ODl0ZXN0JsOpIicoLcOoX8Onw6ApPSvCsCTCoyRewqgqwrXDuSUh + wqc6LzsuLD8+PH4je1t8YFxeQF19wqQKMDEyMzQ1Njc4OVRFU1QwMTIzNDU2Nzg5dGVzdCbDqSIn + KC3DqF/Dp8OgKT0rwrAkwqMkXsKoKsK1w7klIcKnOi87Liw/Pjx+I3tbfGBcXkBdfcKkCjAxMjM0 + NTY3ODlURVNUMDEyMzQ1Njc4OXRlc3Qmw6kiJygtw6hfw6fDoCk9K8KwJMKjJF7CqCrCtcO5JSHC + pzovOy4sPz48fiN7W3xgXF5AXX3CpAowMTIzNDU2Nzg5VEVTVDAxMjM0NTY3ODl0ZXN0JsOpIico + LcOoX8Onw6ApPSvCsCTCoyRewqgqwrXDuSUhwqc6LzsuLD8+PH4je1t8YFxeQF19wqQKMDEyMzQ1 + Njc4OVRFU1QwMTIzNDU2Nzg5dGVzdCbDqSInKC3DqF/Dp8OgKT0rwrAkwqMkXsKoKsK1w7klIcKn + Oi87Liw/Pjx+I3tbfGBcXkBdfcKkCjAxMjM0NTY3ODlURVNUMDEyMzQ1Njc4OXRlc3Qmw6kiJygt + w6hfw6fDoCk9K8KwJMKjJF7CqCrCtcO5JSHCpzovOy4sPz48fiN7W3xgXF5AXX3CpAowMTIzNDU2 + Nzg5VEVTVDAxMjM0NTY3ODl0ZXN0JsOpIicoLcOoX8Onw6ApPSvCsCTCoyRewqgqwrXDuSUhwqc6 + LzsuLD8+PH4je1t8YFxeQF19wqQKMDEyMzQ1Njc4OVRFU1QwMTIzNDU2Nzg5dGVzdCbDqSInKC3D + qF/Dp8OgKT0rwrAkwqMkXsKoKsK1w7klIcKnOi87Liw/Pjx+I3tbfGBcXkBdfcKkCjAxMjM0NTY3 + ODlURVNUMDEyMzQ1Njc4OXRlc3Qmw6kiJygtw6hfw6fDoCk9K8KwJMKjJF7CqCrCtcO5JSHCpzov + Oy4sPz48fiN7W3xgXF5AXX3CpAowMTIzNDU2Nzg5VEVTVDAxMjM0NTY3ODl0ZXN0JsOpIicoLcOo + X8Onw6ApPSvCsCTCoyRewqgqwrXDuSUhwqc6LzsuLD8+PH4je1t8YFxeQF19wqQKMDEyMzQ1Njc4 + OVRFU1QwMTIzNDU2Nzg5dGVzdCbDqSInKC3DqF/Dp8OgKT0rwrAkwqMkXsKoKsK1w7klIcKnOi87 + Liw/Pjx+I3tbfGBcXkBdfcKkCjAxMjM0NTY3ODlURVNUMDEyMzQ1Njc4OXRlc3Qmw6kiJygtw6hf + w6fDoCk9K8KwJMKjJF7CqCrCtcO5JSHCpzovOy4sPz48fiN7W3xgXF5AXX3CpAowMTIzNDU2Nzg5 + VEVTVDAxMjM0NTY3ODl0ZXN0JsOpIicoLcOoX8Onw6ApPSvCsCTCoyRewqgqwrXDuSUhwqc6Lzsu + LD8+PH4je1t8YFxeQF19wqQKMDEyMzQ1Njc4OVRFU1QwMTIzNDU2Nzg5dGVzdCbDqSInKC3DqF/D + p8OgKT0rwrAkwqMkXsKoKsK1w7klIcKnOi87Liw/Pjx+I3tbfGBcXkBdfcKkCjAxMjM0NTY3ODlU + RVNUMDEyMzQ1Njc4OXRlc3Qmw6kiJygtw6hfw6fDoCk9K8KwJMKjJF7CqCrCtcO5JSHCpzovOy4s + Pz48fiN7W3xgXF5AXX3CpAowMTIzNDU2Nzg5VEVTVDAxMjM0NTY3ODl0ZXN0JsOpIicoLcOoX8On + w6ApPSvCsCTCoyRewqgqwrXDuSUhwqc6LzsuLD8+PH4je1t8YFxeQF19wqQKMDEyMzQ1Njc4OVRF + U1QwMTIzNDU2Nzg5dGVzdCbDqSInKC3DqF/Dp8OgKT0rwrAkwqMkXsKoKsK1w7klIcKnOi87Liw/ + Pjx+I3tbfGBcXkBdfcKkCjAxMjM0NTY3ODlURVNUMDEyMzQ1Njc4OXRlc3Qmw6kiJygtw6hfw6fD + oCk9K8KwJMKjJF7CqCrCtcO5JSHCpzovOy4sPz48fiN7W3xgXF5AXX3CpAowMTIzNDU2Nzg5VEVT + VDAxMjM0NTY3ODl0ZXN0JsOpIicoLcOoX8Onw6ApPSvCsCTCoyRewqg
\ No newline at end of file diff --git a/fuzz/xplist-leaks/clusterfuzz-testcase-4634751103139840 b/fuzz/xplist-leaks/clusterfuzz-testcase-4634751103139840 new file mode 100644 index 0000000..aa1ca4e --- /dev/null +++ b/fuzz/xplist-leaks/clusterfuzz-testcase-4634751103139840 @@ -0,0 +1,187 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE pló ©ist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> +<plist version="1.0"> +<plist version="1.0"> +<plist version="1.0"> +<plist version="1.0"> +<plist ve+/v9rsion="1.0"> +<plist version="1.0"> +<dict> + <key>Time1</key> + <date>2008-07-06T05:04:03Z</date> + <key>Time4</key> + <date>1162-08-20T01:10:11Z</date> +</dict><plist version="1.0"> +<plist version="1.0"> +<plist version="1.0"> +<plist version="1.0"> +<plist ve+/v9rsion="1.0"> +<plist version="1.0"> +<dict> + <key>Time1</key> + <date>2008-07-06T05:04:03Z</date> + <key>Time4</key> + <date>1162-08-20T01:10:11Z</date> +</dict><plist version="1.0"> +<plist version="1.0"> +<plist version="1.0"> +<plist version="1.0"> +<plist ve+/v9rsion="1.0"> +<plist version="1.0"> +<dict> + <key>Time1</key> + <date>2008-07-06T05:04:03Z</date> + <key>Time4</key> + <date>1162-08-20T01:10:11Z</date> +</dict><plist version="1.0"> +<plist version="1.0"> +<plist version="1.0"> +<plist version="1.0"> +<plist ve+/v9rsion="1.0"> +<plist version="1.0"> +<dict> + <key>Time1</key> + <date>2008-07-06T05:04:03Z</date> + <key>Time4</key> + <date>1162-08-20T01:10:11Z</date> +</dict> +</plist> +</plist> +</plist> +</plist> +</plist></plist> +</plist> +</plist> +</plist> +</plist></plist> +</plist> +</plist> +</plist> +</plist></plist> +</plist><plist version="1.0"> +<plist ve+/v9rsion="1.0"> +<plist version="1.0"> +<dict> + <key>Time1</key> + <date>2008-07-06T05:04:03Z</date> + <key>Time4</key> + <date>1162-08-20T01:10:11Z</date> +</dict><plist version="1.0"> +<plist version="1.0"> +<plist version="1.0"> +<plist version="1.0"> +<plist ve+/v9rsion="1.0"> +<plist version="1.0"> +<dict> + <key>Time1</key> + <date>2008-07-06T05:04:03Z</date> + <key>Time4</key> + <date>1162-08-20T01:10:11Z</date> +</dict><plist version="1.0"> +<plist version="1.0"> +<plist version="1.0"> +<plist version="1.0"> +<plist ve+/v9rsion="1.0"> +<plist version="1.0"> +<dict> + <key>Time1</key> + <date>2008-07-06T05:04:03Z</date> + <key>Time4</key> + <date>1162-08-20T01:10:11Z</date> +</dict><plist version="1.0"> +<plist version="1.0"> +<plist version="1.0"> +<plist version="1.0"> +<plist ve+/v9rsion="1.0"> +<plist version="1.0"> +<dict> + <key>Time1</key> + <date>2008-07-06T05:04:03Z</date> + <key>Time4</key> + <date>1162-08-20T01:10:11Z</date> +</dict> +</plist> +</plist> +</plist> +</plist> +</plist></plist> +</plist> +</plist> +</plist> +</plist></plist> +</plist> +</plist> +</plist> +</plist></plist> +</plist><plist version="1.0"> +<plist ve+/v9rsion="1.0"> +<plist version="1.0"> +<dict> + <key>Time1</key> + <date>2008-07-06T05:04:03Z</date> + <key>Time4</key> + <date>1162-08-20T01:10:11Z</date> +</dict><plist version="1.0"> +<plist version="1.0"> +<plist version="1.0"> +<plist version="1.0"> +<plist ve+/v9rsion="1.0"> +<plist version="1.0"> +<dict> + <key>Time1</key> + <date>2008-07-06T05:04:03Z</date> + <key>Time4</key> + <date>1162-08-20T01:10:11Z</date> +</dict><plist version="1.0"> +<plist version="1.0"> +<plist version="1.0"> +<plist version="1.0"> +<plist ve+/v9rsion="1.0"> +<plist version="1.0"> +<plist ve+/v9rsion="1.0"> +<plist version="1.0"> +<dict> + <key>Time1</key> + <date>2008-07-06T05:04:03Z</date> + <key>Time4</key> + <date>1162-08-20T01:10:11Z</date> +</dict><plist version="1.0"> +<plist version="1.0"> +<plist version="1.0"> +<plist version="1.0"> +<plist ve+/v9rsion="1.0"> +<plist version="1.0"> +<dict> + <key>Time1</key> + <date>2008-07-06T05:04:03Z</date> + <key>Time4</key> + <date>1162-08-20T01:10:11Z</date> +</dict><plist version="1.0"> +<plist version="1.0"> +<plist version="1.0"> +<plist version="1.0"> +<plist ve+/v9rsion="1.0"> +<plist version="1.0"> +<plist ve+/v9rsion="1.0"> +<plist version="1.0"> +<dict> + <key>Time1</key> + <date>2008-07-06T05:04:03Z</date> + <key>Time4</key> + <date>1162-08-20T01:10:11Z</date> +</dict><plist version="1.0"> +<plist version="1.0"> +<plist version="1.0"> +<plist version="1.0"> +<plist ve+/v9rsion="1.0"> +<plist version="1.0"> +<dict> + <key>Time1</key> + <date>2008-07-06T05:04:03Z</date> + <key>Time4</key> + <date>1162-08-20T01:10:11Z</date> +</dict><plist version="1.0"> +<plist version="1.0"> +<plist version="1.0"> +<plist version="1.0"> +<plist ve+/v9r
\ No newline at end of file diff --git a/fuzz/xplist-leaks/clusterfuzz-testcase-6056412121661440 b/fuzz/xplist-leaks/clusterfuzz-testcase-6056412121661440 new file mode 100644 index 0000000..be5f3f6 --- /dev/null +++ b/fuzz/xplist-leaks/clusterfuzz-testcase-6056412121661440 @@ -0,0 +1,12 @@ +<plist version="9223372036854808575.1"> +<dict> + <key><![CDATA[cdata&key]]></key><key><![CDATA[cdata&key]]></key> + <plist version="32768.1"> +<dict> + <key><![CDATA[cdata&key]]></key><key><![CDATA[cdatadkey]]></key> + <plist version="32768.1"> +<dict> + <key><![CDATA[cdata&key]]></key><key><![CDATA[cdata&key]]></key> + <plist version="32768.1"> +<dict> + <key><![CDATA[cdata&key]]></key><key><![CDA diff --git a/fuzz/xplist-leaks/leak-47b0a9f21d3affab6cfc898e5140bb53265ada07 b/fuzz/xplist-leaks/leak-47b0a9f21d3affab6cfc898e5140bb53265ada07 Binary files differnew file mode 100644 index 0000000..36e09f0 --- /dev/null +++ b/fuzz/xplist-leaks/leak-47b0a9f21d3affab6cfc898e5140bb53265ada07 diff --git a/fuzz/xplist-leaks/leak-4c81aae70b20d9b9408b78d40999a54205bdcfb7 b/fuzz/xplist-leaks/leak-4c81aae70b20d9b9408b78d40999a54205bdcfb7 Binary files differnew file mode 100644 index 0000000..4dec3dd --- /dev/null +++ b/fuzz/xplist-leaks/leak-4c81aae70b20d9b9408b78d40999a54205bdcfb7 diff --git a/fuzz/xplist-leaks/leak-53bc466851eda790bd891733da5db0a7482886bf b/fuzz/xplist-leaks/leak-53bc466851eda790bd891733da5db0a7482886bf new file mode 100644 index 0000000..ce9e7ac --- /dev/null +++ b/fuzz/xplist-leaks/leak-53bc466851eda790bd891733da5db0a7482886bf @@ -0,0 +1,6 @@ +<plist version="1.0"> +<dict> + <key>test&</key> + <string>value</string> +</dict> +</plist> diff --git a/fuzz/xplist.dict b/fuzz/xplist.dict new file mode 100644 index 0000000..48b0367 --- /dev/null +++ b/fuzz/xplist.dict @@ -0,0 +1,51 @@ +################################################################################ +# +# AFL dictionary for XML Property Lists +# ---------------------- +# +# Several basic syntax elements and attributes for libplist. +# +# Created by Nikias Bassen <nikias@gmx.li> +# Adapted from libxml2's dict file (created by Michal Zalewski <lcamtuf@google.com>) +# + +attr_encoding=" encoding=\"1\"" +attr_generic=" a=\"1\"" +attr_version=" version=\"1\"" + +entity_builtin="<" +entity_decimal="" +entity_external="&a;" +entity_hex="" + +string_cdata="CDATA" +string_dashes="--" +string_empty="EMPTY" +string_empty_dblquotes="\"\"" +string_empty_quotes="''" +string_parentheses="()" +string_pcdata="#PCDATA" +string_percent="%a" +string_public="PUBLIC" +string_utf8="UTF-8" + +tag_cdata="<![CDATA[" +tag_close="</plist>" +tag_doctype="<!DOCTYPE" +tag_open="<plist>" +tag_open_close="<plist />" +tag_open_exclamation="<!" +tag_open_q="<?" +tag_sq2_close="]]>" +tag_xml_q="<?xml?>" +tag_array="<array>" +tag_data="<data>" +tag_date="<date>" +tag_dict="<dict>" +tag_false="<false/>" +tag_integer="<integer>" +tag_key="<key>" +tag_plist="<plist>" +tag_real="<real>" +tag_string="<string>" +tag_true="<true/>" diff --git a/plutil/plutil.h b/fuzz/xplist_fuzzer.cc index 4be716e..c477c4d 100644 --- a/plutil/plutil.h +++ b/fuzz/xplist_fuzzer.cc @@ -1,8 +1,8 @@ /* - * plutil.h - * header for plist convertion tool + * xplist_fuzzer.cc + * XML plist fuzz target for libFuzzer * - * Copyright (c) 2008 Zach C. All Rights Reserved. + * Copyright (c) 2017 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,11 +19,14 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ -typedef struct _options +#include <plist/plist.h> +#include <stdio.h> + +extern "C" int LLVMFuzzerTestOneInput(const unsigned char* data, size_t size) { - char *in_file, *out_file; - uint8_t debug, in_fmt, out_fmt; -} Options; + plist_t root_node = NULL; + plist_from_xml(reinterpret_cast<const char*>(data), size, &root_node); + plist_free(root_node); -Options *parse_arguments(int argc, char *argv[]); -void print_usage(); + return 0; +} diff --git a/fuzz/xplist_fuzzer.options b/fuzz/xplist_fuzzer.options new file mode 100644 index 0000000..bad5dac --- /dev/null +++ b/fuzz/xplist_fuzzer.options @@ -0,0 +1,3 @@ +[libfuzzer] +max_len = 4096 +dict = xplist.dict diff --git a/git-version-gen b/git-version-gen new file mode 100755 index 0000000..d868952 --- /dev/null +++ b/git-version-gen @@ -0,0 +1,20 @@ +#!/bin/sh +SRCDIR=`dirname $0` +if test -n "$1"; then + VER=$1 +else + if test -r "${SRCDIR}/.git" && test -x "`which git`" ; then + git update-index -q --refresh + if ! VER=`git describe --tags --dirty 2>/dev/null`; then + COMMIT=`git rev-parse --short HEAD` + DIRTY=`git diff --quiet HEAD || echo "-dirty"` + VER=`sed -n '1,/RE/s/Version \(.*\)/\1/p' ${SRCDIR}/NEWS`-git-${COMMIT}${DIRTY} + fi + else + if test -f "${SRCDIR}/.tarball-version"; then + VER=`cat "${SRCDIR}/.tarball-version"` + fi + fi +fi +VER=`printf %s "$VER" | head -n1` +printf %s "$VER" diff --git a/include/CMakeLists.txt b/include/CMakeLists.txt deleted file mode 100644 index ea77d45..0000000 --- a/include/CMakeLists.txt +++ /dev/null @@ -1,17 +0,0 @@ -SET( libplist_HDR - ${CMAKE_CURRENT_SOURCE_DIR}/plist/plist.h - ${CMAKE_CURRENT_SOURCE_DIR}/plist/plist++.h - ${CMAKE_CURRENT_SOURCE_DIR}/plist/Array.h - ${CMAKE_CURRENT_SOURCE_DIR}/plist/Boolean.h - ${CMAKE_CURRENT_SOURCE_DIR}/plist/Data.h - ${CMAKE_CURRENT_SOURCE_DIR}/plist/Date.h - ${CMAKE_CURRENT_SOURCE_DIR}/plist/Dictionary.h - ${CMAKE_CURRENT_SOURCE_DIR}/plist/Integer.h - ${CMAKE_CURRENT_SOURCE_DIR}/plist/Node.h - ${CMAKE_CURRENT_SOURCE_DIR}/plist/Real.h - ${CMAKE_CURRENT_SOURCE_DIR}/plist/String.h - ${CMAKE_CURRENT_SOURCE_DIR}/plist/Structure.h - ) - -INSTALL( FILES ${libplist_HDR} - DESTINATION include/plist COMPONENT dev) diff --git a/include/Makefile.am b/include/Makefile.am new file mode 100644 index 0000000..2fa500e --- /dev/null +++ b/include/Makefile.am @@ -0,0 +1,15 @@ +nobase_include_HEADERS = \ + plist/plist.h \ + plist/plist++.h \ + plist/Array.h \ + plist/Boolean.h \ + plist/Data.h \ + plist/Date.h \ + plist/Dictionary.h \ + plist/Integer.h \ + plist/Key.h \ + plist/Node.h \ + plist/Real.h \ + plist/String.h \ + plist/Structure.h \ + plist/Uid.h diff --git a/include/plist/Array.h b/include/plist/Array.h index fd4dea8..f4360c5 100644 --- a/include/plist/Array.h +++ b/include/plist/Array.h @@ -19,8 +19,8 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ -#ifndef PLIST__ARRAY_H -#define PLIST__ARRAY_H +#ifndef PLIST_ARRAY_H +#define PLIST_ARRAY_H #include <plist/Structure.h> #include <vector> @@ -33,18 +33,43 @@ class Array : public Structure public : Array(Node* parent = NULL); Array(plist_t node, Node* parent = NULL); - Array(Array& a); - Array& operator=(Array& a); + Array(const Array& a); + Array& operator=(const Array& a); virtual ~Array(); - Node* Clone(); + Node* Clone() const; + + typedef std::vector<Node*>::iterator iterator; + typedef std::vector<Node*>::const_iterator const_iterator; Node* operator[](unsigned int index); - void Append(Node* node); - void Insert(Node* node, unsigned int pos); + Node* Back(); + Node* back(); + Node* Front(); + Node* front(); + iterator Begin(); + iterator begin(); + iterator End(); + iterator end(); + const_iterator Begin() const; + const_iterator begin() const; + const_iterator End() const; + const_iterator end() const; + size_t size() const; + void Append(const Node& node); + void Append(const Node* node); + void Insert(const Node& node, unsigned int pos); + void Insert(const Node* node, unsigned int pos); void Remove(Node* node); void Remove(unsigned int pos); - unsigned int GetNodeIndex(Node* node); + unsigned int GetNodeIndex(const Node& node) const; + unsigned int GetNodeIndex(const Node* node) const; + template <typename T> T* at(unsigned int index) { + return (T*)(_array.at(index)); + } + template <typename T> T* At(unsigned int index) { + return (T*)(_array.at(index)); + } private : std::vector<Node*> _array; @@ -52,4 +77,4 @@ private : }; -#endif // PLIST__ARRAY_H +#endif // PLIST_ARRAY_H diff --git a/include/plist/Boolean.h b/include/plist/Boolean.h index 731eb9e..9ecbc48 100644 --- a/include/plist/Boolean.h +++ b/include/plist/Boolean.h @@ -19,8 +19,8 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ -#ifndef PLIST__BOOLEAN_H -#define PLIST__BOOLEAN_H +#ifndef PLIST_BOOLEAN_H +#define PLIST_BOOLEAN_H #include <plist/Node.h> @@ -32,17 +32,17 @@ class Boolean : public Node public : Boolean(Node* parent = NULL); Boolean(plist_t node, Node* parent = NULL); - Boolean(Boolean& b); - Boolean& operator=(Boolean& b); + Boolean(const Boolean& b); + Boolean& operator=(const Boolean& b); Boolean(bool b); virtual ~Boolean(); - Node* Clone(); + Node* Clone() const; void SetValue(bool b); - bool GetValue(); + bool GetValue() const; }; }; -#endif // PLIST__BOOLEAN_H +#endif // PLIST_BOOLEAN_H diff --git a/include/plist/Data.h b/include/plist/Data.h index f8ae715..1de88aa 100644 --- a/include/plist/Data.h +++ b/include/plist/Data.h @@ -19,8 +19,8 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ -#ifndef PLIST__DATA_H -#define PLIST__DATA_H +#ifndef PLIST_DATA_H +#define PLIST_DATA_H #include <plist/Node.h> #include <vector> @@ -33,17 +33,18 @@ class Data : public Node public : Data(Node* parent = NULL); Data(plist_t node, Node* parent = NULL); - Data(Data& d); - Data& operator=(Data& d); + Data(const Data& d); + Data& operator=(const Data& b); Data(const std::vector<char>& buff); + Data(const char* buff, uint64_t size); virtual ~Data(); - Node* Clone(); + Node* Clone() const; void SetValue(const std::vector<char>& buff); - std::vector<char> GetValue(); + std::vector<char> GetValue() const; }; }; -#endif // PLIST__DATA_H +#endif // PLIST_DATA_H diff --git a/include/plist/Date.h b/include/plist/Date.h index 2ea830b..7026699 100644 --- a/include/plist/Date.h +++ b/include/plist/Date.h @@ -19,14 +19,15 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ -#ifndef PLIST__DATE_H -#define PLIST__DATE_H +#ifndef PLIST_DATE_H +#define PLIST_DATE_H #include <plist/Node.h> #include <ctime> - -#ifdef _WIN32 -#include <Winsock2.h> +#ifdef _MSC_VER +#include <winsock2.h> +#else +#include <sys/time.h> #endif namespace PList @@ -37,17 +38,17 @@ class Date : public Node public : Date(Node* parent = NULL); Date(plist_t node, Node* parent = NULL); - Date(Date& d); - Date& operator=(Date& d); - Date(timeval t); + Date(const Date& d); + Date& operator=(const Date& d); + Date(int64_t t); virtual ~Date(); - Node* Clone(); + Node* Clone() const; - void SetValue(timeval t); - timeval GetValue(); + void SetValue(int64_t t); + int64_t GetValue() const; }; }; -#endif // PLIST__DATE_H +#endif // PLIST_DATE_H diff --git a/include/plist/Dictionary.h b/include/plist/Dictionary.h index a937bde..37ff1c9 100644 --- a/include/plist/Dictionary.h +++ b/include/plist/Dictionary.h @@ -19,8 +19,8 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ -#ifndef PLIST__DICTIONARY_H -#define PLIST__DICTIONARY_H +#ifndef PLIST_DICTIONARY_H +#define PLIST_DICTIONARY_H #include <plist/Structure.h> #include <map> @@ -34,29 +34,40 @@ class Dictionary : public Structure public : Dictionary(Node* parent = NULL); Dictionary(plist_t node, Node* parent = NULL); - Dictionary(Dictionary& d); - Dictionary& operator=(Dictionary& d); + Dictionary(const Dictionary& d); + Dictionary& operator=(const Dictionary& d); virtual ~Dictionary(); - Node* Clone(); + Node* Clone() const; typedef std::map<std::string,Node*>::iterator iterator; + typedef std::map<std::string,Node*>::const_iterator const_iterator; Node* operator[](const std::string& key); iterator Begin(); + iterator begin(); iterator End(); + iterator end(); iterator Find(const std::string& key); - iterator Insert(const std::string& key, Node* node); + const_iterator Begin() const; + const_iterator begin() const; + const_iterator End() const; + const_iterator end() const; + size_t size() const; + const_iterator Find(const std::string& key) const; + iterator Set(const std::string& key, const Node* node); + iterator Set(const std::string& key, const Node& node); void Remove(Node* node); void Remove(const std::string& key); - std::string GetNodeKey(Node* key); + std::string GetNodeKey(Node* node); + template <typename T> T* Get(const std::string& key) { + return (T*)(_map[key]); + } private : std::map<std::string,Node*> _map; - - }; }; -#endif // PLIST__DICTIONARY_H +#endif // PLIST_DICTIONARY_H diff --git a/include/plist/Integer.h b/include/plist/Integer.h index 7cdf0df..1a4d980 100644 --- a/include/plist/Integer.h +++ b/include/plist/Integer.h @@ -19,8 +19,8 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ -#ifndef PLIST__INTEGER_H -#define PLIST__INTEGER_H +#ifndef PLIST_INTEGER_H +#define PLIST_INTEGER_H #include <plist/Node.h> @@ -32,17 +32,23 @@ class Integer : public Node public : Integer(Node* parent = NULL); Integer(plist_t node, Node* parent = NULL); - Integer(Integer& i); - Integer& operator=(Integer& i); + Integer(const Integer& i); + Integer& operator=(const Integer& i); Integer(uint64_t i); + Integer(int64_t i); virtual ~Integer(); - Node* Clone(); + Node* Clone() const; + void SetValue(int64_t i); void SetValue(uint64_t i); - uint64_t GetValue(); + void SetUnsignedValue(uint64_t i); + int64_t GetValue() const; + uint64_t GetUnsignedValue() const; + + bool isNegative() const; }; }; -#endif // PLIST__INTEGER_H +#endif // PLIST_INTEGER_H diff --git a/include/plist/Key.h b/include/plist/Key.h new file mode 100644 index 0000000..bd5f7bc --- /dev/null +++ b/include/plist/Key.h @@ -0,0 +1,49 @@ +/* + * Key.h + * Key node type for C++ binding + * + * 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 + */ + +#ifndef PLIST_KEY_H +#define PLIST_KEY_H + +#include <plist/Node.h> +#include <string> + +namespace PList +{ + +class Key : public Node +{ +public : + Key(Node* parent = NULL); + Key(plist_t node, Node* parent = NULL); + Key(const Key& k); + Key& operator=(const Key& k); + Key(const std::string& s); + virtual ~Key(); + + Node* Clone() const; + + void SetValue(const std::string& s); + std::string GetValue() const; +}; + +}; + +#endif // PLIST_KEY_H diff --git a/include/plist/Node.h b/include/plist/Node.h index 1da9ee1..6c9ae43 100644 --- a/include/plist/Node.h +++ b/include/plist/Node.h @@ -19,10 +19,11 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ -#ifndef PLIST__NODE_H -#define PLIST__NODE_H +#ifndef PLIST_NODE_H +#define PLIST_NODE_H #include <plist/plist.h> +#include <cstddef> namespace PList { @@ -32,11 +33,11 @@ class Node public : virtual ~Node(); - virtual Node* Clone() = 0; + virtual Node* Clone() const = 0; - Node * GetParent(); - plist_type GetType(); - plist_t GetPlist(); + Node * GetParent() const; + plist_type GetType() const; + plist_t GetPlist() const; static Node* FromPlist(plist_t node, Node* parent = NULL); @@ -53,4 +54,4 @@ private: }; -#endif // PLIST__NODE_H +#endif // PLIST_NODE_H diff --git a/include/plist/Real.h b/include/plist/Real.h index 7d6f8e2..5afb0c0 100644 --- a/include/plist/Real.h +++ b/include/plist/Real.h @@ -19,8 +19,8 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ -#ifndef PLIST__REAL_H -#define PLIST__REAL_H +#ifndef PLIST_REAL_H +#define PLIST_REAL_H #include <plist/Node.h> @@ -32,17 +32,17 @@ class Real : public Node public : Real(Node* parent = NULL); Real(plist_t node, Node* parent = NULL); - Real(Real& d); - Real& operator=(Real& d); + Real(const Real& d); + Real& operator=(const Real& d); Real(double d); virtual ~Real(); - Node* Clone(); + Node* Clone() const; void SetValue(double d); - double GetValue(); + double GetValue() const; }; }; -#endif // PLIST__REAL_H +#endif // PLIST_REAL_H diff --git a/include/plist/String.h b/include/plist/String.h index bd8680d..95f8dd1 100644 --- a/include/plist/String.h +++ b/include/plist/String.h @@ -19,8 +19,8 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ -#ifndef PLIST__STRING_H -#define PLIST__STRING_H +#ifndef PLIST_STRING_H +#define PLIST_STRING_H #include <plist/Node.h> #include <string> @@ -33,17 +33,20 @@ class String : public Node public : String(Node* parent = NULL); String(plist_t node, Node* parent = NULL); - String(String& s); - String& operator=(String& s); + String(const String& s); + String& operator=(const String& s); + String& operator=(const std::string& s); + String& operator=(const char* s); String(const std::string& s); + String(const char *s); virtual ~String(); - Node* Clone(); + Node* Clone() const; void SetValue(const std::string& s); - std::string GetValue(); + std::string GetValue() const; }; }; -#endif // PLIST__STRING_H +#endif // PLIST_STRING_H diff --git a/include/plist/Structure.h b/include/plist/Structure.h index ace8b5c..b7fbdb5 100644 --- a/include/plist/Structure.h +++ b/include/plist/Structure.h @@ -19,8 +19,8 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ -#ifndef PLIST__STRUCTURE_H -#define PLIST__STRUCTURE_H +#ifndef PLIST_STRUCTURE_H +#define PLIST_STRUCTURE_H #include <plist/Node.h> #include <string> @@ -34,15 +34,18 @@ class Structure : public Node public : virtual ~Structure(); - uint32_t GetSize(); + uint32_t GetSize() const; - std::string ToXml(); - std::vector<char> ToBin(); + std::string ToXml() const; + std::vector<char> ToBin() const; virtual void Remove(Node* node) = 0; static Structure* FromXml(const std::string& xml); static Structure* FromBin(const std::vector<char>& bin); + static Structure* FromBin(const char* bin, uint64_t size); + static Structure* FromMemory(const std::vector<char>& buf, plist_format_t* format = NULL); + static Structure* FromMemory(const char* buf, uint64_t size, plist_format_t* format = NULL); protected: Structure(Node* parent = NULL); @@ -56,4 +59,4 @@ private: }; -#endif // PLIST__STRUCTURE_H +#endif // PLIST_STRUCTURE_H diff --git a/include/plist/Uid.h b/include/plist/Uid.h new file mode 100644 index 0000000..af6e51d --- /dev/null +++ b/include/plist/Uid.h @@ -0,0 +1,48 @@ +/* + * Uid.h + * Uid node type for C++ binding + * + * 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 + */ + +#ifndef PLIST_UID_H +#define PLIST_UID_H + +#include <plist/Node.h> + +namespace PList +{ + +class Uid : public Node +{ +public : + Uid(Node* parent = NULL); + Uid(plist_t node, Node* parent = NULL); + Uid(const Uid& i); + Uid& operator=(const Uid& i); + Uid(uint64_t i); + virtual ~Uid(); + + Node* Clone() const; + + void SetValue(uint64_t i); + uint64_t GetValue() const; +}; + +}; + +#endif // PLIST_UID_H diff --git a/include/plist/plist++.h b/include/plist/plist++.h index d5eb691..b29fc76 100644 --- a/include/plist/plist++.h +++ b/include/plist/plist++.h @@ -31,6 +31,8 @@ #include "Integer.h" #include "Node.h" #include "Real.h" +#include "Key.h" +#include "Uid.h" #include "String.h" #include "Structure.h" diff --git a/include/plist/plist.h b/include/plist/plist.h index 1b1ac52..e720aac 100644 --- a/include/plist/plist.h +++ b/include/plist/plist.h @@ -3,7 +3,8 @@ * @brief Main include of libplist * \internal * - * Copyright (c) 2008 Jonathan Beck All Rights Reserved. + * Copyright (c) 2012-2023 Nikias Bassen, All Rights Reserved. + * Copyright (c) 2008-2009 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 @@ -28,7 +29,7 @@ extern "C" { #endif -#ifdef _MSC_VER +#if _MSC_VER && _MSC_VER < 1700 typedef __int8 int8_t; typedef __int16 int16_t; typedef __int32 int32_t; @@ -39,21 +40,57 @@ extern "C" typedef unsigned __int32 uint32_t; typedef unsigned __int64 uint64_t; -#ifdef plist_EXPORTS -#define PLIST_API __declspec( dllexport ) #else -#define PLIST_API __declspec( dllimport ) +#include <stdint.h> #endif + +/*{{{ deprecation macros */ +#ifdef __llvm__ + #if defined(__has_extension) + #if (__has_extension(attribute_deprecated_with_message)) + #ifndef PLIST_WARN_DEPRECATED + #define PLIST_WARN_DEPRECATED(x) __attribute__((deprecated(x))) + #endif + #else + #ifndef PLIST_WARN_DEPRECATED + #define PLIST_WARN_DEPRECATED(x) __attribute__((deprecated)) + #endif + #endif + #else + #ifndef PLIST_WARN_DEPRECATED + #define PLIST_WARN_DEPRECATED(x) __attribute__((deprecated)) + #endif + #endif +#elif (__GNUC__ > 4 || (__GNUC__ == 4 && (__GNUC_MINOR__ >= 5))) + #ifndef PLIST_WARN_DEPRECATED + #define PLIST_WARN_DEPRECATED(x) __attribute__((deprecated(x))) + #endif +#elif defined(_MSC_VER) + #ifndef PLIST_WARN_DEPRECATED + #define PLIST_WARN_DEPRECATED(x) __declspec(deprecated(x)) + #endif #else -#include <stdint.h> -#define PLIST_API + #define PLIST_WARN_DEPRECATED(x) + #pragma message("WARNING: You need to implement DEPRECATED for this compiler") +#endif +/*}}}*/ + +#ifndef PLIST_API + #ifdef LIBPLIST_STATIC + #define PLIST_API + #elif defined(_WIN32) + #define PLIST_API __declspec(dllimport) + #else + #define PLIST_API + #endif #endif #include <sys/types.h> #include <stdarg.h> +#include <stdio.h> /** - * \mainpage libplist : A library to handle Apple Property Lists + * libplist : A library to handle Apple Property Lists * \defgroup PublicAPI Public libplist API */ /*@{*/ @@ -67,25 +104,87 @@ extern "C" /** * The plist dictionary iterator. */ - typedef void *plist_dict_iter; + typedef void* plist_dict_iter; + + /** + * The plist array iterator. + */ + typedef void* plist_array_iter; /** * The enumeration of plist node types. */ typedef enum { - PLIST_BOOLEAN, /**< Boolean, scalar type */ - PLIST_UINT, /**< Unsigned integer, scalar type */ - PLIST_REAL, /**< Real, scalar type */ - PLIST_STRING, /**< ASCII string, scalar type */ - PLIST_ARRAY, /**< Ordered array, structured type */ - PLIST_DICT, /**< Unordered dictionary (key/value pair), structured type */ - PLIST_DATE, /**< Date, scalar type */ - PLIST_DATA, /**< Binary data, scalar type */ - PLIST_KEY, /**< Key in dictionaries (ASCII String), scalar type */ - PLIST_NONE /**< No type */ + PLIST_NONE =-1, /**< No type */ + PLIST_BOOLEAN, /**< Boolean, scalar type */ + PLIST_INT, /**< Integer, scalar type */ + PLIST_REAL, /**< Real, scalar type */ + PLIST_STRING, /**< ASCII string, scalar type */ + PLIST_ARRAY, /**< Ordered array, structured type */ + PLIST_DICT, /**< Unordered dictionary (key/value pair), structured type */ + PLIST_DATE, /**< Date, scalar type */ + PLIST_DATA, /**< Binary data, scalar type */ + PLIST_KEY, /**< Key in dictionaries (ASCII String), scalar type */ + PLIST_UID, /**< Special type used for 'keyed encoding' */ + PLIST_NULL, /**< NULL type */ } plist_type; + /* for backwards compatibility */ + #define PLIST_UINT PLIST_INT + + /** + * libplist error values + */ + typedef enum + { + PLIST_ERR_SUCCESS = 0, /**< operation successful */ + PLIST_ERR_INVALID_ARG = -1, /**< one or more of the parameters are invalid */ + PLIST_ERR_FORMAT = -2, /**< the plist contains nodes not compatible with the output format */ + PLIST_ERR_PARSE = -3, /**< parsing of the input format failed */ + PLIST_ERR_NO_MEM = -4, /**< not enough memory to handle the operation */ + PLIST_ERR_IO = -5, /**< I/O error */ + PLIST_ERR_CIRCULAR_REF = -6, /**< circular reference detected */ + PLIST_ERR_MAX_NESTING = -7, /**< maximum nesting depth exceeded */ + PLIST_ERR_UNKNOWN = -255 /**< an unspecified error occurred */ + } plist_err_t; + + /** + * libplist format types + */ + typedef enum + { + PLIST_FORMAT_NONE = 0, /**< No format */ + PLIST_FORMAT_XML = 1, /**< XML format */ + PLIST_FORMAT_BINARY = 2, /**< bplist00 format */ + PLIST_FORMAT_JSON = 3, /**< JSON format */ + PLIST_FORMAT_OSTEP = 4, /**< OpenStep "old-style" plist format */ + /* 5-9 are reserved for possible future use */ + PLIST_FORMAT_PRINT = 10, /**< human-readable output-only format */ + PLIST_FORMAT_LIMD = 11, /**< "libimobiledevice" output-only format (ideviceinfo) */ + PLIST_FORMAT_PLUTIL = 12, /**< plutil-style output-only format */ + } plist_format_t; + + /** + * libplist write options + */ + typedef enum + { + PLIST_OPT_NONE = 0, /**< Default value to use when none of the options is needed. */ + PLIST_OPT_COMPACT = 1 << 0, /**< Use a compact representation (non-prettified). Only valid for #PLIST_FORMAT_JSON and #PLIST_FORMAT_OSTEP. */ + PLIST_OPT_PARTIAL_DATA = 1 << 1, /**< Print 24 bytes maximum of #PLIST_DATA values. If the data is longer than 24 bytes, the first 16 and last 8 bytes will be written. Only valid for #PLIST_FORMAT_PRINT. */ + PLIST_OPT_NO_NEWLINE = 1 << 2, /**< Do not print a final newline character. Only valid for #PLIST_FORMAT_PRINT, #PLIST_FORMAT_LIMD, and #PLIST_FORMAT_PLUTIL. */ + PLIST_OPT_INDENT = 1 << 3, /**< Indent each line of output. Currently only #PLIST_FORMAT_PRINT and #PLIST_FORMAT_LIMD are supported. Use #PLIST_OPT_INDENT_BY() macro to specify the level of indentation. */ + PLIST_OPT_COERCE = 1 << 4, /**< Coerce plist types that have no native JSON representation into JSON-compatible types. + #PLIST_DATE is converted to an ISO 8601 date string, + #PLIST_DATA is converted to a Base64-encoded string, and + #PLIST_UID is converted to an integer. + Only valid for #PLIST_FORMAT_JSON. Without this option, these types cause #PLIST_ERR_FORMAT. */ + } plist_write_options_t; + + /** To be used with #PLIST_OPT_INDENT - encodes the level of indentation for OR'ing it into the #plist_write_options_t bitfield. */ + #define PLIST_OPT_INDENT_BY(x) ((x & 0xFF) << 24) + /******************************************** * * @@ -128,15 +227,28 @@ extern "C" PLIST_API plist_t plist_new_bool(uint8_t val); /** - * Create a new plist_t type #PLIST_UINT + * Create a new plist_t type #PLIST_INT with an unsigned integer value * * @param val the unsigned integer value * @return the created item * @sa #plist_type + * @note The value is always stored as uint64_t internally. + * Use #plist_get_uint_val or #plist_get_int_val to get the unsigned or signed value. */ PLIST_API plist_t plist_new_uint(uint64_t val); /** + * Create a new plist_t type #PLIST_INT with a signed integer value + * + * @param val the signed integer value + * @return the created item + * @sa #plist_type + * @note The value is always stored as uint64_t internally. + * Use #plist_get_uint_val or #plist_get_int_val to get the unsigned or signed value. + */ + PLIST_API plist_t plist_new_int(int64_t val); + + /** * Create a new plist_t type #PLIST_REAL * * @param val the real value @@ -158,12 +270,29 @@ extern "C" /** * Create a new plist_t type #PLIST_DATE * - * @param sec the number of seconds since 01/01/2001 - * @param usec the number of microseconds + * @param sec The number of seconds since 01/01/1970 (UNIX timestamp) * @return the created item * @sa #plist_type */ - PLIST_API plist_t plist_new_date(int32_t sec, int32_t usec); + PLIST_API plist_t plist_new_unix_date(int64_t sec); + + /** + * Create a new plist_t type #PLIST_UID + * + * @param val the unsigned integer value + * @return the created item + * @sa #plist_type + */ + PLIST_API plist_t plist_new_uid(uint64_t val); + + /** + * Create a new plist_t type #PLIST_NULL + * @return the created item + * @sa #plist_type + * @note This type is not valid for all formats, e.g. the XML format + * does not support it. + */ + PLIST_API plist_t plist_new_null(void); /** * Destruct a plist_t node and all its children recursively @@ -208,7 +337,7 @@ extern "C" * Get the index of an item. item must be a member of a #PLIST_ARRAY node. * * @param node the node - * @return the node index + * @return the node index or UINT_MAX if node index can't be determined */ PLIST_API uint32_t plist_array_get_item_index(plist_t node); @@ -217,36 +346,105 @@ extern "C" * The previous item at index n will be freed using #plist_free * * @param node the node of type #PLIST_ARRAY - * @param item the new item at index n - * @param n the index of the item to get. Range is [0, array_size[. Assert if n is not in range. + * @param item the new item at index n. The array is responsible for freeing item when it is no longer needed. + * @param n the index of the item to get. Range is [0, array_size[. + * + * @return + * - PLIST_ERR_SUCCESS on success. + * - PLIST_ERR_INVALID_ARG if node is not an array, item is NULL, item is already + * attached to a parent, or n is outside the valid range. + * - PLIST_ERR_NO_MEM if replacing the item fails due to memory allocation failure + * and the original array element was restored successfully. + * - PLIST_ERR_UNKNOWN if an unexpected internal error occurred, including failure + * to restore the original array element after insertion of the replacement failed. */ - PLIST_API void plist_array_set_item(plist_t node, plist_t item, uint32_t n); + PLIST_API plist_err_t plist_array_set_item(plist_t node, plist_t item, uint32_t n); /** * Append a new item at the end of a #PLIST_ARRAY node. * * @param node the node of type #PLIST_ARRAY - * @param item the new item + * @param item the new item. The array is responsible for freeing item when it is no longer needed. + * + * @return + * - PLIST_ERR_SUCCESS on success. + * - PLIST_ERR_INVALID_ARG if node is not an array, item is NULL, or item is + * already attached to a parent. + * - PLIST_ERR_NO_MEM if memory allocation failed while appending the item. + * - PLIST_ERR_UNKNOWN if an unexpected internal error occurred. */ - PLIST_API void plist_array_append_item(plist_t node, plist_t item); + PLIST_API plist_err_t plist_array_append_item(plist_t node, plist_t item); /** * Insert a new item at position n in a #PLIST_ARRAY node. * * @param node the node of type #PLIST_ARRAY - * @param item the new item to insert - * @param n The position at which the node will be stored. Range is [0, array_size[. Assert if n is not in range. + * @param item the new item to insert. The array is responsible for freeing item when it is no longer needed. + * @param n The position at which the node will be stored. Range is [0, array_size[. + * + * @return + * - PLIST_ERR_SUCCESS on success. + * - PLIST_ERR_INVALID_ARG if node is not an array, item is NULL, item is already + * attached to a parent, or n is outside the valid insertion range. + * - PLIST_ERR_NO_MEM if memory allocation failed while inserting the item. + * - PLIST_ERR_UNKNOWN if an unexpected internal error occurred. */ - PLIST_API void plist_array_insert_item(plist_t node, plist_t item, uint32_t n); + PLIST_API plist_err_t plist_array_insert_item(plist_t node, plist_t item, uint32_t n); /** * Remove an existing position in a #PLIST_ARRAY node. - * Removed position will be freed using #plist_free + * Removed position will be freed using #plist_free. * * @param node the node of type #PLIST_ARRAY - * @param n The position to remove. Range is [0, array_size[. Assert if n is not in range. + * @param n The position to remove. Range is [0, array_size[. + * + * @return + * - PLIST_ERR_SUCCESS on success. + * - PLIST_ERR_INVALID_ARG if node is not an array or n is outside the valid range. + * - PLIST_ERR_UNKNOWN if an unexpected internal error occurred while removing + * the item. + */ + PLIST_API plist_err_t plist_array_remove_item(plist_t node, uint32_t n); + + /** + * Remove a node that is a child node of a #PLIST_ARRAY node. + * node will be freed using #plist_free. + * + * @param node The node to be removed from its #PLIST_ARRAY parent. + * + * @return + * - PLIST_ERR_SUCCESS on success. + * - PLIST_ERR_INVALID_ARG if item is NULL or not a child of a #PLIST_ARRAY + * - PLIST_ERR_UNKNOWN if an unexpected internal error occurred. + */ + PLIST_API plist_err_t plist_array_item_remove(plist_t item); + + /** + * Create an iterator of a #PLIST_ARRAY node. + * The allocated iterator should be freed with the standard free function. + * + * @param node The node of type #PLIST_ARRAY + * @param iter Location to store the iterator for the array. + */ + PLIST_API void plist_array_new_iter(plist_t node, plist_array_iter *iter); + + /** + * Increment iterator of a #PLIST_ARRAY node. + * + * @param node The node of type #PLIST_ARRAY. + * @param iter Iterator of the array + * @param item Location to store the item. The caller must *not* free the + * returned item. Will be set to NULL when no more items are left + * to iterate. + */ + PLIST_API void plist_array_next_item(plist_t node, plist_array_iter iter, plist_t *item); + + /** + * Free #PLIST_ARRAY iterator. + * + * @param iter Iterator to free. */ - PLIST_API void plist_array_remove_item(plist_t node, uint32_t n); + PLIST_API void plist_array_free_iter(plist_array_iter iter); /******************************************** * * @@ -263,29 +461,39 @@ extern "C" PLIST_API uint32_t plist_dict_get_size(plist_t node); /** - * Create iterator of a #PLIST_DICT node. - * The allocated iterator shoult be freed with tandard free function + * Create an iterator of a #PLIST_DICT node. + * The allocated iterator should be freed with the standard free function. * - * @param node the node of type #PLIST_DICT - * @param iter iterator of the #PLIST_DICT node + * @param node The node of type #PLIST_DICT. + * @param iter Location to store the iterator for the dictionary. */ PLIST_API void plist_dict_new_iter(plist_t node, plist_dict_iter *iter); /** * Increment iterator of a #PLIST_DICT node. * - * @param node the node of type #PLIST_DICT - * @param iter iterator of the dictionary - * @param key a location to store the key, or NULL. - * @param val a location to store the value, or NULL. + * @param node The node of type #PLIST_DICT + * @param iter Iterator of the dictionary + * @param key Location to store the key, or NULL. The caller is responsible + * for freeing the the returned string. + * @param val Location to store the value, or NULL. The caller must *not* + * free the returned value. Will be set to NULL when no more + * key/value pairs are left to iterate. */ PLIST_API void plist_dict_next_item(plist_t node, plist_dict_iter iter, char **key, plist_t *val); /** - * Get key associated to an item. Item must be member of a dictionary + * Free #PLIST_DICT iterator. * - * @param node the node - * @param key a location to store the key. + * @param iter Iterator to free. + */ + PLIST_API void plist_dict_free_iter(plist_dict_iter iter); + + /** + * Get key associated key to an item. Item must be member of a dictionary. + * + * @param node the item + * @param key a location to store the key. The caller is responsible for freeing the returned string. */ PLIST_API void plist_dict_get_item_key(plist_t node, char **key); @@ -294,38 +502,232 @@ extern "C" * * @param node the node of type #PLIST_DICT * @param key the identifier of the item to get. - * @return the item or NULL if node is not of type #PLIST_DICT + * @return the item or NULL if node is not of type #PLIST_DICT. The caller should not free + * the returned node. */ PLIST_API plist_t plist_dict_get_item(plist_t node, const char* key); /** - * Set item identified by key in a #PLIST_DICT node. - * The previous item at index n will be freed using #plist_free + * Get key node associated to an item. Item must be member of a dictionary. * - * @param node the node of type #PLIST_DICT - * @param item the new item associated to key - * @param key the identifier of the item to get. Assert if identifier is not present. + * @param node the item + * @return the key node of the given item, or NULL. */ - PLIST_API void plist_dict_set_item(plist_t node, const char* key, plist_t item); + PLIST_API plist_t plist_dict_item_get_key(plist_t node); /** - * Insert a new item at position n in a #PLIST_DICT node. + * Set item identified by key in a #PLIST_DICT node. + * The previous item identified by key will be freed using #plist_free. + * If there is no item for the given key a new item will be inserted. * * @param node the node of type #PLIST_DICT - * @param item the new item to insert - * @param key The identifier of the item to insert. Assert if identifier already present. + * @param item the new item associated to key + * @param key the identifier of the item to set. + * + * @return + * - PLIST_ERR_SUCCESS on success. + * - PLIST_ERR_INVALID_ARG if node is not a dictionary, key is NULL, item is NULL, + * or item is already attached to a parent. + * - PLIST_ERR_NO_MEM if memory allocation failed while creating or inserting the + * key/value pair. + * - PLIST_ERR_UNKNOWN if an unexpected internal error occurred. */ - PLIST_API void plist_dict_insert_item(plist_t node, const char* key, plist_t item); + PLIST_API plist_err_t plist_dict_set_item(plist_t node, const char* key, plist_t item); /** * Remove an existing position in a #PLIST_DICT node. * Removed position will be freed using #plist_free * * @param node the node of type #PLIST_DICT - * @param key The identifier of the item to remove. Assert if identifier is not present. + * @param key The identifier of the item to remove + * + * @return + * - PLIST_ERR_SUCCESS on success. + * - PLIST_ERR_INVALID_ARG if node is not a dictionary, key is NULL, or no item + * with the given key exists. + * - PLIST_ERR_UNKNOWN if an unexpected internal error occurred while removing + * the item. + */ + PLIST_API plist_err_t plist_dict_remove_item(plist_t node, const char* key); + + /** + * Merge a dictionary into another. This will add all key/value pairs + * from the source dictionary to the target dictionary, overwriting + * any existing key/value pairs that are already present in target. + * + * @param target pointer to an existing node of type #PLIST_DICT + * @param source node of type #PLIST_DICT that should be merged into target + * + * @return + * - PLIST_ERR_SUCCESS on success. + * - PLIST_ERR_INVALID_ARG if target is NULL, source is NULL, source is not a + * dictionary, or *target is not NULL and does not point to a dictionary. + * - PLIST_ERR_NO_MEM if memory allocation failed while creating the target + * dictionary or copying items from source. + * - PLIST_ERR_UNKNOWN if an unexpected internal error occurred. + */ + PLIST_API plist_err_t plist_dict_merge(plist_t *target, plist_t source); + + /** + * Get a boolean value from a given #PLIST_DICT entry. + * + * The value node can be of type #PLIST_BOOLEAN, but also + * #PLIST_STRING (either 'true' or 'false'), + * #PLIST_INT with a numerical value of 0 or >= 1, + * or #PLIST_DATA with a single byte with a value of 0 or >= 1. + * + * @note This function returns 0 if the dictionary does not contain an + * entry for the given key, if the value node is of any other than + * the above mentioned type, or has any mismatching value. + * + * @param dict A node of type #PLIST_DICT + * @param key The key to look for in dict + * @return 0 or 1 depending on the value of the node. + */ + PLIST_API uint8_t plist_dict_get_bool(plist_t dict, const char *key); + + /** + * Get a signed integer value from a given #PLIST_DICT entry. + * The value node can be of type #PLIST_INT, but also + * #PLIST_STRING with a numerical value as string (decimal or hexadecimal), + * or #PLIST_DATA with a size of 1, 2, 4, or 8 bytes in little endian byte order. + * + * @note This function returns 0 if the dictionary does not contain an + * entry for the given key, if the value node is of any other than + * the above mentioned type, or has any mismatching value. + * + * @param dict A node of type #PLIST_DICT + * @param key The key to look for in dict + * @return Signed integer value depending on the value of the node. + */ + PLIST_API int64_t plist_dict_get_int(plist_t dict, const char *key); + + /** + * Get an unsigned integer value from a given #PLIST_DICT entry. + * The value node can be of type #PLIST_INT, but also + * #PLIST_STRING with a numerical value as string (decimal or hexadecimal), + * or #PLIST_DATA with a size of 1, 2, 4, or 8 bytes in little endian byte order. + * + * @note This function returns 0 if the dictionary does not contain an + * entry for the given key, if the value node is of any other than + * the above mentioned type, or has any mismatching value. + * + * @param dict A node of type #PLIST_DICT + * @param key The key to look for in dict + * @return Signed integer value depending on the value of the node. + */ + PLIST_API uint64_t plist_dict_get_uint(plist_t dict, const char *key); + + /** + * Copy a node from *source_dict* to *target_dict*. + * The node is looked up in *source_dict* with given *key*, unless *alt_source_key* + * is non-NULL, in which case it is looked up with *alt_source_key*. + * The entry in *target_dict* is **always** created with *key*. + * + * @param target_dict The target dictionary to copy to. + * @param source_dict The source dictionary to copy from. + * @param key The key for the node to copy. + * @param alt_source_key The alternative source key for lookup in *source_dict* or NULL. + * + * @result PLIST_ERR_SUCCESS on success or PLIST_ERR_INVALID_ARG if the source dictionary does not contain + * any entry with given key or alt_source_key. + */ + PLIST_API plist_err_t plist_dict_copy_item(plist_t target_dict, plist_t source_dict, const char *key, const char *alt_source_key); + + /** + * Copy a boolean value from *source_dict* to *target_dict*. + * The node is looked up in *source_dict* with given *key*, unless *alt_source_key* + * is non-NULL, in which case it is looked up with *alt_source_key*. + * The entry in *target_dict* is **always** created with *key*. + * + * @note The boolean value from *source_dict* is retrieved with #plist_dict_get_bool, + * but is **always** created as #PLIST_BOOLEAN in *target_dict*. + * + * @param target_dict The target dictionary to copy to. + * @param source_dict The source dictionary to copy from. + * @param key The key for the node to copy. + * @param alt_source_key The alternative source key for lookup in *source_dict* or NULL. + * + * @result PLIST_ERR_SUCCESS on success or PLIST_ERR_INVALID_ARG if the source dictionary does not contain + * any entry with given key or alt_source_key. + */ + PLIST_API plist_err_t plist_dict_copy_bool(plist_t target_dict, plist_t source_dict, const char *key, const char *alt_source_key); + + /** + * Copy a signed integer value from *source_dict* to *target_dict*. + * The node is looked up in *source_dict* with given *key*, unless *alt_source_key* + * is non-NULL, in which case it is looked up with *alt_source_key*. + * The entry in *target_dict* is **always** created with *key*. + * + * @note The signed integer value from *source_dict* is retrieved with #plist_dict_get_int, + * but is **always** created as #PLIST_INT. + * + * @param target_dict The target dictionary to copy to. + * @param source_dict The source dictionary to copy from. + * @param key The key for the node value to copy. + * @param alt_source_key The alternative source key for lookup in *source_dict* or NULL. + * + * @result PLIST_ERR_SUCCESS on success or PLIST_ERR_INVALID_ARG if the source dictionary does not contain + * any entry with given key or alt_source_key. */ - PLIST_API void plist_dict_remove_item(plist_t node, const char* key); + PLIST_API plist_err_t plist_dict_copy_int(plist_t target_dict, plist_t source_dict, const char *key, const char *alt_source_key); + /** + * Copy an unsigned integer value from *source_dict* to *target_dict*. + * The node is looked up in *source_dict* with given *key*, unless *alt_source_key* + * is non-NULL, in which case it is looked up with *alt_source_key*. + * The entry in *target_dict* is **always** created with *key*. + * + * @note The unsigned integer value from *source_dict* is retrieved with #plist_dict_get_uint, + * but is **always** created as #PLIST_INT. + * + * @param target_dict The target dictionary to copy to. + * @param source_dict The source dictionary to copy from. + * @param key The key for the node value to copy. + * @param alt_source_key The alternative source key for lookup in *source_dict* or NULL. + * + * @result PLIST_ERR_SUCCESS on success or PLIST_ERR_INVALID_ARG if the source dictionary does not contain + * any entry with given key or alt_source_key. + */ + PLIST_API plist_err_t plist_dict_copy_uint(plist_t target_dict, plist_t source_dict, const char *key, const char *alt_source_key); + + /** + * Copy a #PLIST_DATA node from *source_dict* to *target_dict*. + * The node is looked up in *source_dict* with given *key*, unless *alt_source_key* + * is non-NULL, in which case it is looked up with *alt_source_key*. + * The entry in *target_dict* is **always** created with *key*. + * + * @note This function is like #plist_dict_copy_item, except that it fails + * if the source node is not of type #PLIST_DATA. + * + * @param target_dict The target dictionary to copy to. + * @param source_dict The source dictionary to copy from. + * @param key The key for the node value to copy. + * @param alt_source_key The alternative source key for lookup in *source_dict* or NULL. + * + * @result PLIST_ERR_SUCCESS on success or PLIST_ERR_INVALID_ARG if the source dictionary does not contain + * any entry with given key or alt_source_key, or if it is not of type #PLIST_DATA. + */ + PLIST_API plist_err_t plist_dict_copy_data(plist_t target_dict, plist_t source_dict, const char *key, const char *alt_source_key); + + /** + * Copy a #PLIST_STRING node from *source_dict* to *target_dict*. + * The node is looked up in *source_dict* with given *key*, unless *alt_source_key* + * is non-NULL, in which case it is looked up with *alt_source_key*. + * The entry in *target_dict* is **always** created with *key*. + * + * @note This function is like #plist_dict_copy_item, except that it fails + * if the source node is not of type #PLIST_STRING. + * + * @param target_dict The target dictionary to copy to. + * @param source_dict The source dictionary to copy from. + * @param key The key for the node value to copy. + * @param alt_source_key The alternative source key for lookup in *source_dict* or NULL. + * + * @result PLIST_ERR_SUCCESS on success or PLIST_ERR_INVALID_ARG if the source dictionary does not contain + * any entry with given key or alt_source_key, or if it is not of type #PLIST_STRING. + */ + PLIST_API plist_err_t plist_dict_copy_string(plist_t target_dict, plist_t source_dict, const char *key, const char *alt_source_key); /******************************************** * * @@ -355,6 +757,7 @@ extern "C" * @param node the node * @param val a pointer to a C-string. This function allocates the memory, * caller is responsible for freeing it. + * @note Use plist_mem_free() to free the allocated memory. */ PLIST_API void plist_get_key_val(plist_t node, char **val); @@ -365,10 +768,24 @@ extern "C" * @param node the node * @param val a pointer to a C-string. This function allocates the memory, * caller is responsible for freeing it. Data is UTF-8 encoded. + * @note Use plist_mem_free() to free the allocated memory. */ PLIST_API void plist_get_string_val(plist_t node, char **val); /** + * Get a pointer to the buffer of a #PLIST_STRING node. + * + * @note DO NOT MODIFY the buffer. Mind that the buffer is only available + * until the plist node gets freed. Make a copy if needed. + * + * @param node The node + * @param length If non-NULL, will be set to the length of the string + * + * @return Pointer to the NULL-terminated buffer. + */ + PLIST_API const char* plist_get_string_ptr(plist_t node, uint64_t* length); + + /** * Get the value of a #PLIST_BOOLEAN node. * This function does nothing if node is not of type #PLIST_BOOLEAN * @@ -378,8 +795,8 @@ extern "C" PLIST_API void plist_get_bool_val(plist_t node, uint8_t * val); /** - * Get the value of a #PLIST_UINT node. - * This function does nothing if node is not of type #PLIST_UINT + * Get the unsigned integer value of a #PLIST_INT node. + * This function does nothing if node is not of type #PLIST_INT * * @param node the node * @param val a pointer to a uint64_t variable. @@ -387,6 +804,15 @@ extern "C" PLIST_API void plist_get_uint_val(plist_t node, uint64_t * val); /** + * Get the signed integer value of a #PLIST_INT node. + * This function does nothing if node is not of type #PLIST_INT + * + * @param node the node + * @param val a pointer to a int64_t variable. + */ + PLIST_API void plist_get_int_val(plist_t node, int64_t * val); + + /** * Get the value of a #PLIST_REAL node. * This function does nothing if node is not of type #PLIST_REAL * @@ -403,18 +829,40 @@ extern "C" * @param val a pointer to an unallocated char buffer. This function allocates the memory, * caller is responsible for freeing it. * @param length the length of the buffer + * @note Use plist_mem_free() to free the allocated memory. */ PLIST_API void plist_get_data_val(plist_t node, char **val, uint64_t * length); /** + * Get a pointer to the data buffer of a #PLIST_DATA node. + * + * @note DO NOT MODIFY the buffer. Mind that the buffer is only available + * until the plist node gets freed. Make a copy if needed. + * + * @param node The node + * @param length Pointer to a uint64_t that will be set to the length of the buffer + * + * @return Pointer to the buffer + */ + PLIST_API const char* plist_get_data_ptr(plist_t node, uint64_t* length); + + /** * Get the value of a #PLIST_DATE node. * This function does nothing if node is not of type #PLIST_DATE * * @param node the node - * @param sec a pointer to an int32_t variable. Represents the number of seconds since 01/01/2001. - * @param usec a pointer to an int32_t variable. Represents the number of microseconds + * @param sec a pointer to an int64_t variable. Represents the number of seconds since 01/01/1970 (UNIX timestamp). */ - PLIST_API void plist_get_date_val(plist_t node, int32_t * sec, int32_t * usec); + PLIST_API void plist_get_unix_date_val(plist_t node, int64_t *sec); + + /** + * Get the value of a #PLIST_UID node. + * This function does nothing if node is not of type #PLIST_UID + * + * @param node the node + * @param val a pointer to a uint64_t variable. + */ + PLIST_API void plist_get_uid_val(plist_t node, uint64_t * val); /******************************************** @@ -424,14 +872,6 @@ extern "C" ********************************************/ /** - * Forces type of node. Changing type of structured nodes is only allowed if node is empty. - * Reset value of node; - * @param node the node - * @param type the key value - */ - PLIST_API void plist_set_type(plist_t node, plist_type type); - - /** * Set the value of a node. * Forces type of node to #PLIST_KEY * @@ -445,7 +885,8 @@ extern "C" * Forces type of node to #PLIST_STRING * * @param node the node - * @param val the string value + * @param val the string value. The string is copied when set and will be + * freed by the node. */ PLIST_API void plist_set_string_val(plist_t node, const char *val); @@ -460,7 +901,7 @@ extern "C" /** * Set the value of a node. - * Forces type of node to #PLIST_UINT + * Forces type of node to #PLIST_INT * * @param node the node * @param val the unsigned integer value @@ -469,6 +910,15 @@ extern "C" /** * Set the value of a node. + * Forces type of node to #PLIST_INT + * + * @param node the node + * @param val the signed integer value + */ + PLIST_API void plist_set_int_val(plist_t node, int64_t val); + + /** + * Set the value of a node. * Forces type of node to #PLIST_REAL * * @param node the node @@ -481,7 +931,8 @@ extern "C" * Forces type of node to #PLIST_DATA * * @param node the node - * @param val the binary buffer + * @param val the binary buffer. The buffer is copied when set and will + * be freed by the node. * @param length the length of the buffer */ PLIST_API void plist_set_data_val(plist_t node, const char *val, uint64_t length); @@ -491,10 +942,18 @@ extern "C" * Forces type of node to #PLIST_DATE * * @param node the node - * @param sec the number of seconds since 01/01/2001 - * @param usec the number of microseconds + * @param sec the number of seconds since 01/01/1970 (UNIX timestamp) */ - PLIST_API void plist_set_date_val(plist_t node, int32_t sec, int32_t usec); + PLIST_API void plist_set_unix_date_val(plist_t node, int64_t sec); + + /** + * Set the value of a node. + * Forces type of node to #PLIST_UID + * + * @param node the node + * @param val the unsigned integer value + */ + PLIST_API void plist_set_uid_val(plist_t node, uint64_t val); /******************************************** @@ -510,8 +969,10 @@ extern "C" * @param plist_xml a pointer to a C-string. This function allocates the memory, * caller is responsible for freeing it. Data is UTF-8 encoded. * @param length a pointer to an uint32_t variable. Represents the length of the allocated buffer. + * @return PLIST_ERR_SUCCESS on success or a #plist_err_t on failure + * @note Use plist_mem_free() to free the allocated memory. */ - PLIST_API void plist_to_xml(plist_t plist, char **plist_xml, uint32_t * length); + PLIST_API plist_err_t plist_to_xml(plist_t plist, char **plist_xml, uint32_t * length); /** * Export the #plist_t structure to binary format. @@ -520,8 +981,84 @@ extern "C" * @param plist_bin a pointer to a char* buffer. This function allocates the memory, * caller is responsible for freeing it. * @param length a pointer to an uint32_t variable. Represents the length of the allocated buffer. + * @return PLIST_ERR_SUCCESS on success or a #plist_err_t on failure + * @note Use plist_mem_free() to free the allocated memory. + */ + PLIST_API plist_err_t plist_to_bin(plist_t plist, char **plist_bin, uint32_t * length); + + /** + * Export the #plist_t structure to JSON format. + * + * @param plist the root node to export + * @param plist_json a pointer to a char* buffer. This function allocates the memory, + * caller is responsible for freeing it. + * @param length a pointer to an uint32_t variable. Represents the length of the allocated buffer. + * @param prettify pretty print the output if != 0 + * @return PLIST_ERR_SUCCESS on success or a #plist_err_t on failure + * @note Use plist_mem_free() to free the allocated memory. + */ + PLIST_API plist_err_t plist_to_json(plist_t plist, char **plist_json, uint32_t* length, int prettify); + + /** + * Export the #plist_t structure to JSON format with extended options. + * + * When \a PLIST_OPT_COMPACT is set in \a options, the resulting JSON + * will be non-prettified. + * + * When \a PLIST_OPT_COERCE is set in \a options, plist types that have + * no native JSON representation are converted to JSON-compatible types + * instead of returning #PLIST_ERR_FORMAT: + * - #PLIST_DATE is serialized as an ISO 8601 date string + * - #PLIST_DATA is serialized as a Base64-encoded string + * - #PLIST_UID is serialized as an integer + * + * @param plist the root node to export + * @param plist_json a pointer to a char* buffer. This function allocates the memory, + * caller is responsible for freeing it. + * @param length a pointer to an uint32_t variable. Represents the length of the allocated buffer. + * @param options One or more bitwise ORed values of #plist_write_options_t. + * @return PLIST_ERR_SUCCESS on success or a #plist_err_t on failure + * @note Use plist_mem_free() to free the allocated memory. + */ + PLIST_API plist_err_t plist_to_json_with_options(plist_t plist, char **plist_json, uint32_t* length, plist_write_options_t options); + + /** + * Export the #plist_t structure to OpenStep format. + * + * @param plist the root node to export + * @param plist_openstep a pointer to a char* buffer. This function allocates the memory, + * caller is responsible for freeing it. + * @param length a pointer to an uint32_t variable. Represents the length of the allocated buffer. + * @param prettify pretty print the output if != 0 + * @return PLIST_ERR_SUCCESS on success or a #plist_err_t on failure + * @note Use plist_mem_free() to free the allocated memory. + */ + PLIST_API plist_err_t plist_to_openstep(plist_t plist, char **plist_openstep, uint32_t* length, int prettify); + + /** + * Export the #plist_t structure to OpenStep format with extended options. + * + * When \a PLIST_OPT_COMPACT is set in \a options, the resulting OpenStep output + * will be non-prettified. + * + * When \a PLIST_OPT_COERCE is set in \a options, plist types that have + * no native OpenStep representation are converted to OpenStep-compatible types + * instead of returning #PLIST_ERR_FORMAT: + * - #PLIST_BOOLEAN is serialized as a 1 or 0 + * - #PLIST_DATE is serialized as an ISO 8601 date string + * - #PLIST_NULL is serialized as string 'NULL' + * - #PLIST_UID is serialized as an integer + * + * @param plist the root node to export + * @param plist_openstep a pointer to a char* buffer. This function allocates the memory, + * caller is responsible for freeing it. + * @param length a pointer to an uint32_t variable. Represents the length of the allocated buffer. + * @param options One or more bitwise ORed values of #plist_write_options_t. + * @return PLIST_ERR_SUCCESS on success or a #plist_err_t on failure + * @note Use plist_mem_free() to free the allocated memory. */ - PLIST_API void plist_to_bin(plist_t plist, char **plist_bin, uint32_t * length); + PLIST_API plist_err_t plist_to_openstep_with_options(plist_t plist, char **plist_openstep, uint32_t* length, plist_write_options_t options); + /** * Import the #plist_t structure from XML format. @@ -529,8 +1066,9 @@ extern "C" * @param plist_xml a pointer to the xml buffer. * @param length length of the buffer to read. * @param plist a pointer to the imported plist. + * @return PLIST_ERR_SUCCESS on success or a #plist_err_t on failure */ - PLIST_API void plist_from_xml(const char *plist_xml, uint32_t length, plist_t * plist); + PLIST_API plist_err_t plist_from_xml(const char *plist_xml, uint32_t length, plist_t * plist); /** * Import the #plist_t structure from binary format. @@ -538,9 +1076,129 @@ extern "C" * @param plist_bin a pointer to the xml buffer. * @param length length of the buffer to read. * @param plist a pointer to the imported plist. + * @return PLIST_ERR_SUCCESS on success or a #plist_err_t on failure + */ + PLIST_API plist_err_t plist_from_bin(const char *plist_bin, uint32_t length, plist_t * plist); + + /** + * Import the #plist_t structure from JSON format. + * + * @param json a pointer to the JSON buffer. + * @param length length of the buffer to read. + * @param plist a pointer to the imported plist. + * @return PLIST_ERR_SUCCESS on success or a #plist_err_t on failure + */ + PLIST_API plist_err_t plist_from_json(const char *json, uint32_t length, plist_t * plist); + + /** + * Import the #plist_t structure from OpenStep plist format. + * + * @param openstep a pointer to the OpenStep plist buffer. + * @param length length of the buffer to read. + * @param plist a pointer to the imported plist. + * @return PLIST_ERR_SUCCESS on success or a #plist_err_t on failure + */ + PLIST_API plist_err_t plist_from_openstep(const char *openstep, uint32_t length, plist_t * plist); + + /** + * Import the #plist_t structure from memory data. + * + * This function will look at the first bytes of plist_data + * to determine if plist_data contains a binary, JSON, OpenStep, or XML plist + * and tries to parse the data in the appropriate format. + * @note This is just a convenience function and the format detection is + * very basic. It checks with plist_is_binary() if the data supposedly + * contains binary plist data, if not it checks if the first bytes have + * either '{' or '[' and assumes JSON format, and XML tags will result + * in parsing as XML, otherwise it will try to parse as OpenStep. + * + * @param plist_data A pointer to the memory buffer containing plist data. + * @param length Length of the buffer to read. + * @param plist A pointer to the imported plist. + * @param format If non-NULL, the #plist_format_t value pointed to will be set to the parsed format. + * @return PLIST_ERR_SUCCESS on success or a #plist_err_t on failure + */ + PLIST_API plist_err_t plist_from_memory(const char *plist_data, uint32_t length, plist_t *plist, plist_format_t *format); + + /** + * Import the #plist_t structure directly from file. + * + * This function will look at the first bytes of the file data + * to determine if it contains a binary, JSON, OpenStep, or XML plist + * and tries to parse the data in the appropriate format. + * Uses plist_from_memory() internally. + * + * @param filename The name of the file to parse. + * @param plist A pointer to the imported plist. + * @param format If non-NULL, the #plist_format_t value pointed to will be set to the parsed format. + * @return PLIST_ERR_SUCCESS on success or a #plist_err_t on failure */ - PLIST_API void plist_from_bin(const char *plist_bin, uint32_t length, plist_t * plist); + PLIST_API plist_err_t plist_read_from_file(const char *filename, plist_t *plist, plist_format_t *format); + /** + * Write the #plist_t structure to a NULL-terminated string using the given format and options. + * + * @param plist The input plist structure + * @param output Pointer to a char* buffer. This function allocates the memory, + * caller is responsible for freeing it. + * @param length A pointer to a uint32_t value that will receive the lenght of the allocated buffer. + * @param format A #plist_format_t value that specifies the output format to use. + * @param options One or more bitwise ORed values of #plist_write_options_t. + * @return PLIST_ERR_SUCCESS on success or a #plist_err_t on failure. + * @note Use plist_mem_free() to free the allocated memory. + * @note #PLIST_FORMAT_BINARY is not supported by this function. + */ + PLIST_API plist_err_t plist_write_to_string(plist_t plist, char **output, uint32_t* length, plist_format_t format, plist_write_options_t options); + + /** + * Write the #plist_t structure to a FILE* stream using the given format and options. + * + * @param plist The input plist structure + * @param stream A writeable FILE* stream that the data will be written to. + * @param format A #plist_format_t value that specifies the output format to use. + * @param options One or more bitwise ORed values of #plist_write_options_t. + * @return PLIST_ERR_SUCCESS on success or a #plist_err_t on failure. + * @note While this function allows all formats to be written to the given stream, + * only the formats #PLIST_FORMAT_PRINT, #PLIST_FORMAT_LIMD, and #PLIST_FORMAT_PLUTIL + * (basically all output-only formats) are directly and efficiently written to the stream; + * the other formats are written to a memory buffer first. + */ + PLIST_API plist_err_t plist_write_to_stream(plist_t plist, FILE* stream, plist_format_t format, plist_write_options_t options); + + /** + * Write the #plist_t structure to a file at given path using the given format and options. + * + * @param plist The input plist structure + * @param filename The file name of the file to write to. Existing files will be overwritten. + * @param format A #plist_format_t value that specifies the output format to use. + * @param options One or more bitwise ORed values of #plist_write_options_t. + * @return PLIST_ERR_SUCCESS on success or a #plist_err_t on failure. + * @note Use plist_mem_free() to free the allocated memory. + */ + PLIST_API plist_err_t plist_write_to_file(plist_t plist, const char *filename, plist_format_t format, plist_write_options_t options); + + /** + * Print the given plist in human-readable format to standard output. + * This is equivalent to + * <code>plist_write_to_stream(plist, stdout, PLIST_FORMAT_PRINT, PLIST_OPT_PARTIAL_DATA);</code> + * @param plist The #plist_t structure to print + * @note For #PLIST_DATA nodes, only a maximum of 24 bytes (first 16 and last 8) are written. + */ + PLIST_API void plist_print(plist_t plist); + + /** + * Test if in-memory plist data is in binary format. + * This function will look at the first bytes of plist_data to determine + * if it supposedly contains a binary plist. + * @note The function is not validating the whole memory buffer to check + * if the content is truly a plist, it is only using some heuristic on + * the first few bytes of plist_data. + * + * @param plist_data a pointer to the memory buffer containing plist data. + * @param length length of the buffer to read. + * @return 1 if the buffer is a binary plist, 0 otherwise. + */ + PLIST_API int plist_is_binary(const char *plist_data, uint32_t length); /******************************************** * * @@ -578,6 +1236,334 @@ extern "C" */ PLIST_API char plist_compare_node_value(plist_t node_l, plist_t node_r); + /** Helper macro used by PLIST_IS_* macros that will evaluate the type of a plist node. */ + #define _PLIST_IS_TYPE(__plist, __plist_type) (__plist && (plist_get_node_type(__plist) == PLIST_##__plist_type)) + + /* Helper macros for the different plist types */ + /** Evaluates to true if the given plist node is of type PLIST_BOOLEAN */ + #define PLIST_IS_BOOLEAN(__plist) _PLIST_IS_TYPE(__plist, BOOLEAN) + /** Evaluates to true if the given plist node is of type PLIST_INT */ + #define PLIST_IS_INT(__plist) _PLIST_IS_TYPE(__plist, INT) + /** Evaluates to true if the given plist node is of type PLIST_REAL */ + #define PLIST_IS_REAL(__plist) _PLIST_IS_TYPE(__plist, REAL) + /** Evaluates to true if the given plist node is of type PLIST_STRING */ + #define PLIST_IS_STRING(__plist) _PLIST_IS_TYPE(__plist, STRING) + /** Evaluates to true if the given plist node is of type PLIST_ARRAY */ + #define PLIST_IS_ARRAY(__plist) _PLIST_IS_TYPE(__plist, ARRAY) + /** Evaluates to true if the given plist node is of type PLIST_DICT */ + #define PLIST_IS_DICT(__plist) _PLIST_IS_TYPE(__plist, DICT) + /** Evaluates to true if the given plist node is of type PLIST_DATE */ + #define PLIST_IS_DATE(__plist) _PLIST_IS_TYPE(__plist, DATE) + /** Evaluates to true if the given plist node is of type PLIST_DATA */ + #define PLIST_IS_DATA(__plist) _PLIST_IS_TYPE(__plist, DATA) + /** Evaluates to true if the given plist node is of type PLIST_KEY */ + #define PLIST_IS_KEY(__plist) _PLIST_IS_TYPE(__plist, KEY) + /** Evaluates to true if the given plist node is of type PLIST_UID */ + #define PLIST_IS_UID(__plist) _PLIST_IS_TYPE(__plist, UID) + /* for backwards compatibility */ + #define PLIST_IS_UINT PLIST_IS_INT + + /** + * Helper function to check the value of a PLIST_BOOL node. + * + * @param boolnode node of type PLIST_BOOL + * @return 1 if the boolean node has a value of TRUE or 0 if FALSE. + */ + PLIST_API int plist_bool_val_is_true(plist_t boolnode); + + /** + * Helper function to test if a given #PLIST_INT node's value is negative + * + * @param intnode node of type PLIST_INT + * @return 1 if the node's value is negative, or 0 if positive. + */ + PLIST_API int plist_int_val_is_negative(plist_t intnode); + + /** + * Helper function to compare the value of a PLIST_INT node against + * a given signed integer value. + * + * @param uintnode node of type PLIST_INT + * @param cmpval value to compare against + * @return 0 if the node's value and cmpval are equal, + * 1 if the node's value is greater than cmpval, + * or -1 if the node's value is less than cmpval. + */ + PLIST_API int plist_int_val_compare(plist_t uintnode, int64_t cmpval); + + /** + * Helper function to compare the value of a PLIST_INT node against + * a given unsigned integer value. + * + * @param uintnode node of type PLIST_INT + * @param cmpval value to compare against + * @return 0 if the node's value and cmpval are equal, + * 1 if the node's value is greater than cmpval, + * or -1 if the node's value is less than cmpval. + */ + PLIST_API int plist_uint_val_compare(plist_t uintnode, uint64_t cmpval); + + /** + * Helper function to compare the value of a PLIST_UID node against + * a given value. + * + * @param uidnode node of type PLIST_UID + * @param cmpval value to compare against + * @return 0 if the node's value and cmpval are equal, + * 1 if the node's value is greater than cmpval, + * or -1 if the node's value is less than cmpval. + */ + PLIST_API int plist_uid_val_compare(plist_t uidnode, uint64_t cmpval); + + /** + * Helper function to compare the value of a PLIST_REAL node against + * a given value. + * + * @note WARNING: Comparing floating point values can give inaccurate + * results because of the nature of floating point values on computer + * systems. While this function is designed to be as accurate as + * possible, please don't rely on it too much. + * + * @param realnode node of type PLIST_REAL + * @param cmpval value to compare against + * @return 0 if the node's value and cmpval are (almost) equal, + * 1 if the node's value is greater than cmpval, + * or -1 if the node's value is less than cmpval. + */ + PLIST_API int plist_real_val_compare(plist_t realnode, double cmpval); + + /** + * Helper function to compare the value of a PLIST_DATE node against + * a given number of seconds since epoch (UNIX timestamp). + * + * @param datenode node of type PLIST_DATE + * @param cmpval Number of seconds to compare against (UNIX timestamp) + * @return 0 if the node's date is equal to the supplied values, + * 1 if the node's date is greater than the supplied values, + * or -1 if the node's date is less than the supplied values. + */ + PLIST_API int plist_unix_date_val_compare(plist_t datenode, int64_t cmpval); + + /** + * Helper function to compare the value of a PLIST_STRING node against + * a given value. + * This function basically behaves like strcmp. + * + * @param strnode node of type PLIST_STRING + * @param cmpval value to compare against + * @return 0 if the node's value and cmpval are equal, + * > 0 if the node's value is lexicographically greater than cmpval, + * or < 0 if the node's value is lexicographically less than cmpval. + */ + PLIST_API int plist_string_val_compare(plist_t strnode, const char* cmpval); + + /** + * Helper function to compare the value of a PLIST_STRING node against + * a given value, while not comparing more than n characters. + * This function basically behaves like strncmp. + * + * @param strnode node of type PLIST_STRING + * @param cmpval value to compare against + * @param n maximum number of characters to compare + * @return 0 if the node's value and cmpval are equal, + * > 0 if the node's value is lexicographically greater than cmpval, + * or < 0 if the node's value is lexicographically less than cmpval. + */ + PLIST_API int plist_string_val_compare_with_size(plist_t strnode, const char* cmpval, size_t n); + + /** + * Helper function to match a given substring in the value of a + * PLIST_STRING node. + * + * @param strnode node of type PLIST_STRING + * @param substr value to match + * @return 1 if the node's value contains the given substring, + * or 0 if not. + */ + PLIST_API int plist_string_val_contains(plist_t strnode, const char* substr); + + /** + * Helper function to compare the value of a PLIST_KEY node against + * a given value. + * This function basically behaves like strcmp. + * + * @param keynode node of type PLIST_KEY + * @param cmpval value to compare against + * @return 0 if the node's value and cmpval are equal, + * > 0 if the node's value is lexicographically greater than cmpval, + * or < 0 if the node's value is lexicographically less than cmpval. + */ + PLIST_API int plist_key_val_compare(plist_t keynode, const char* cmpval); + + /** + * Helper function to compare the value of a PLIST_KEY node against + * a given value, while not comparing more than n characters. + * This function basically behaves like strncmp. + * + * @param keynode node of type PLIST_KEY + * @param cmpval value to compare against + * @param n maximum number of characters to compare + * @return 0 if the node's value and cmpval are equal, + * > 0 if the node's value is lexicographically greater than cmpval, + * or < 0 if the node's value is lexicographically less than cmpval. + */ + PLIST_API int plist_key_val_compare_with_size(plist_t keynode, const char* cmpval, size_t n); + + /** + * Helper function to match a given substring in the value of a + * PLIST_KEY node. + * + * @param keynode node of type PLIST_KEY + * @param substr value to match + * @return 1 if the node's value contains the given substring, + * or 0 if not. + */ + PLIST_API int plist_key_val_contains(plist_t keynode, const char* substr); + + /** + * Helper function to compare the data of a PLIST_DATA node against + * a given blob and size. + * This function basically behaves like memcmp after making sure the + * size of the node's data value is equal to the size of cmpval (n), + * making this a "full match" comparison. + * + * @param datanode node of type PLIST_DATA + * @param cmpval data blob to compare against + * @param n size of data blob passed in cmpval + * @return 0 if the node's data blob and cmpval are equal, + * > 0 if the node's value is lexicographically greater than cmpval, + * or < 0 if the node's value is lexicographically less than cmpval. + */ + PLIST_API int plist_data_val_compare(plist_t datanode, const uint8_t* cmpval, size_t n); + + /** + * Helper function to compare the data of a PLIST_DATA node against + * a given blob and size, while no more than n bytes are compared. + * This function basically behaves like memcmp after making sure the + * size of the node's data value is at least n, making this a + * "starts with" comparison. + * + * @param datanode node of type PLIST_DATA + * @param cmpval data blob to compare against + * @param n size of data blob passed in cmpval + * @return 0 if the node's value and cmpval are equal, + * > 0 if the node's value is lexicographically greater than cmpval, + * or < 0 if the node's value is lexicographically less than cmpval. + */ + PLIST_API int plist_data_val_compare_with_size(plist_t datanode, const uint8_t* cmpval, size_t n); + + /** + * Helper function to match a given data blob within the value of a + * PLIST_DATA node. + * + * @param datanode node of type PLIST_KEY + * @param cmpval data blob to match + * @param n size of data blob passed in cmpval + * @return 1 if the node's value contains the given data blob + * or 0 if not. + */ + PLIST_API int plist_data_val_contains(plist_t datanode, const uint8_t* cmpval, size_t n); + + /** + * Sort all PLIST_DICT key/value pairs in a property list lexicographically + * by key. Recurses into the child nodes if necessary. + * + * @param plist The property list to perform the sorting operation on. + */ + PLIST_API void plist_sort(plist_t plist); + + /** + * Free memory allocated by relevant libplist API calls: + * - plist_to_xml() + * - plist_to_bin() + * - plist_get_key_val() + * - plist_get_string_val() + * - plist_get_data_val() + * + * @param ptr pointer to the memory to free + * + * @note Do not use this function to free plist_t nodes, use plist_free() + * instead. + */ + PLIST_API void plist_mem_free(void* ptr); + + /** + * Set debug level for the format parsers. + * @note This function does nothing if libplist was not configured with --enable-debug . + * + * @param debug Debug level. Currently, only 0 (off) and 1 (enabled) are supported. + */ + PLIST_API void plist_set_debug(int debug); + + /** + * Returns a static string of the libplist version. + * + * @return The libplist version as static ascii string + */ + PLIST_API const char* libplist_version(); + + + /******************************************** + * * + * Deprecated API * + * * + ********************************************/ + + /** + * Create a new plist_t type #PLIST_DATE + * + * @deprecated Deprecated. Use plist_new_unix_date instead. + * + * @param sec the number of seconds since 01/01/2001 + * @param usec the number of microseconds + * @return the created item + * @sa #plist_type + */ + PLIST_WARN_DEPRECATED("use plist_new_unix_date instead") + PLIST_API plist_t plist_new_date(int32_t sec, int32_t usec); + + /** + * Get the value of a #PLIST_DATE node. + * This function does nothing if node is not of type #PLIST_DATE + * + * @deprecated Deprecated. Use plist_get_unix_date_val instead. + * + * @param node the node + * @param sec a pointer to an int32_t variable. Represents the number of seconds since 01/01/2001. + * @param usec a pointer to an int32_t variable. Represents the number of microseconds + */ + PLIST_WARN_DEPRECATED("use plist_get_unix_date_val instead") + PLIST_API void plist_get_date_val(plist_t node, int32_t * sec, int32_t * usec); + + /** + * Set the value of a node. + * Forces type of node to #PLIST_DATE + * + * @deprecated Deprecated. Use plist_set_unix_date_val instead. + * + * @param node the node + * @param sec the number of seconds since 01/01/2001 + * @param usec the number of microseconds + */ + PLIST_WARN_DEPRECATED("use plist_set_unix_date_val instead") + PLIST_API void plist_set_date_val(plist_t node, int32_t sec, int32_t usec); + + /** + * Helper function to compare the value of a PLIST_DATE node against + * a given set of seconds and fraction of a second since epoch. + * + * @deprecated Deprecated. Use plist_unix_date_val_compare instead. + * + * @param datenode node of type PLIST_DATE + * @param cmpsec number of seconds since epoch to compare against + * @param cmpusec fraction of a second in microseconds to compare against + * @return 0 if the node's date is equal to the supplied values, + * 1 if the node's date is greater than the supplied values, + * or -1 if the node's date is less than the supplied values. + */ + PLIST_WARN_DEPRECATED("use plist_unix_date_val_compare instead") + PLIST_API int plist_date_val_compare(plist_t datenode, int32_t cmpsec, int32_t cmpusec); + /*@}*/ #ifdef __cplusplus diff --git a/libcnary/CMakeLists.txt b/libcnary/CMakeLists.txt deleted file mode 100644 index bbbe8ce..0000000 --- a/libcnary/CMakeLists.txt +++ /dev/null @@ -1,16 +0,0 @@ -cmake_minimum_required(VERSION 2.6) - -SET(libcnary_SRC - iterator.c - list.c - node.c - node_iterator.c - node_list.c ) - -INCLUDE_DIRECTORIES(${CMAKE_CURRENT_SOURCE_DIR}/include) - -SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fPIC") -ADD_LIBRARY(libcnary STATIC ${libcnary_SRC}) - -SET_TARGET_PROPERTIES(libcnary PROPERTIES OUTPUT_NAME cnary) - diff --git a/libcnary/Makefile b/libcnary/Makefile deleted file mode 100644 index bdd165d..0000000 --- a/libcnary/Makefile +++ /dev/null @@ -1,21 +0,0 @@ -TARGET = cnary -LIBRARY = libcnary.a -OBJECTS = cnary.o libcnary.a -LIBRARY_OBJECTS = node.o list.o iterator.o node_list.o node_iterator.o -CFLAGS=-g -I./include -I/opt/local/include -mmacosx-version-min=10.5 -arch i386 -isysroot /Developer/SDKs/MacOSX10.5.sdk -LDFLAGS=-L/opt/local/lib -framework CoreFoundation -mmacosx-version-min=10.5 -arch i386 -isysroot /Developer/SDKs/MacOSX10.5.sdk -Wl,-no_compact_linkedit - - -%.o: %.c - $(CC) -o $(@) -c $(^) $(CFLAGS) - -$(LIBRARY): $(LIBRARY_OBJECTS) - $(AR) rs $(@) $(^) - -$(TARGET): $(OBJECTS) - $(CC) -o $(@) $(^) $(CFLAGS) $(LDFLAGS) - -all: $(TARGET) - -clean: - rm -rf $(TARGET) $(LIBRARY) $(OBJECTS) $(LIBRARY_OBJECTS)
\ No newline at end of file diff --git a/libcnary/Makefile.am b/libcnary/Makefile.am new file mode 100644 index 0000000..f5c7bc9 --- /dev/null +++ b/libcnary/Makefile.am @@ -0,0 +1,15 @@ +AM_CFLAGS = \ + $(GLOBAL_CFLAGS) \ + -I$(top_srcdir)/libcnary/include + +AM_LDFLAGS = + +noinst_LTLIBRARIES = libcnary.la +libcnary_la_LIBADD = +libcnary_la_LDFLAGS = $(AM_LDFLAGS) -no-undefined +libcnary_la_SOURCES = \ + node.c \ + node_list.c \ + include/node.h \ + include/node_list.h \ + include/object.h diff --git a/libcnary/include/iterator.h b/libcnary/include/iterator.h deleted file mode 100644 index a33c4ff..0000000 --- a/libcnary/include/iterator.h +++ /dev/null @@ -1,49 +0,0 @@ -/* - * iterator.h - * - * Created on: Mar 8, 2011 - * Author: posixninja - * - * Copyright (c) 2011 Joshua Hill. 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 ITERATOR_H_ -#define ITERATOR_H_ - -struct list_t; -struct object_t; - -typedef struct iterator_t { - struct object_t*(*next)(struct iterator_t* iterator); - int(*bind)(struct iterator_t* iterator, struct list_t* list); - - unsigned int count; - unsigned int position; - - struct list_t* list; - struct object_t* end; - struct object_t* begin; - struct object_t* value; -} iterator_t; - -void iterator_destroy(struct iterator_t* iterator); -struct iterator_t* iterator_create(struct list_t* list); - -struct object_t* iterator_next(struct iterator_t* iterator); -int iterator_bind(struct iterator_t* iterator, struct list_t* list); - -#endif /* ITERATOR_H_ */ diff --git a/libcnary/include/node.h b/libcnary/include/node.h index f9afdd6..5b3ae37 100644 --- a/libcnary/include/node.h +++ b/libcnary/include/node.h @@ -24,52 +24,54 @@ #ifndef NODE_H_ #define NODE_H_ +#include "node_list.h" #include "object.h" #define NODE_TYPE 1; -struct node_list_t; +#ifndef NODE_MAX_DEPTH +#define NODE_MAX_DEPTH 512 +#endif + +#define NODE_ERR_SUCCESS 0 +#define NODE_ERR_INVALID_ARG -1 +#define NODE_ERR_NO_MEM -2 +#define NODE_ERR_PARENT -3 +#define NODE_ERR_CIRCULAR_REF -4 +#define NODE_ERR_MAX_DEPTH -5 +#define NODE_ERR_NOT_FOUND -6 // This class implements the abstract iterator class -typedef struct node_t { +typedef struct node* node_t; +struct node { // Super class - struct node_t* next; - struct node_t* prev; + node_t next; + node_t prev; unsigned int count; - // Local Properties - int isRoot; - int isLeaf; - // Local Members void *data; - unsigned int depth; - struct node_t* parent; - struct node_list_t* children; - - // Virtual Functions - int(*attach)(struct node_t* parent, struct node_t* child); - int(*detach)(struct node_t* parent, struct node_t* child); - -} node_t; + node_t parent; + node_list_t children; +}; -void node_destroy(struct node_t* node); -struct node_t* node_create(struct node_t* parent, void* data); +void node_destroy(node_t node); +node_t node_create(node_t parent, void* data); -int node_attach(struct node_t* parent, struct node_t* child); -int node_detach(struct node_t* parent, struct node_t* child); -int node_insert(struct node_t* parent, unsigned int index, struct node_t* child); +int node_attach(node_t parent, node_t child); +int node_detach(node_t parent, node_t child); +int node_insert(node_t parent, unsigned int index, node_t child); -unsigned int node_n_children(struct node_t* node); -node_t* node_nth_child(struct node_t* node, unsigned int n); -node_t* node_first_child(struct node_t* node); -node_t* node_prev_sibling(struct node_t* node); -node_t* node_next_sibling(struct node_t* node); -int node_child_position(struct node_t* parent, node_t* child); +unsigned int node_n_children(node_t node); +node_t node_nth_child(node_t node, unsigned int n); +node_t node_first_child(node_t node); +node_t node_prev_sibling(node_t node); +node_t node_next_sibling(node_t node); +int node_child_position(node_t parent, node_t child); typedef void* (*copy_func_t)(const void *src); -node_t* node_copy_deep(node_t* node, copy_func_t copy_func); +node_t node_copy_deep(node_t node, copy_func_t copy_func); -void node_debug(struct node_t* node); +void node_debug(node_t node); #endif /* NODE_H_ */ diff --git a/libcnary/include/node_iterator.h b/libcnary/include/node_iterator.h deleted file mode 100644 index d3bce3a..0000000 --- a/libcnary/include/node_iterator.h +++ /dev/null @@ -1,55 +0,0 @@ -/* - * node_iterator.h - * - * Created on: Mar 8, 2011 - * Author: posixninja - * - * Copyright (c) 2011 Joshua Hill. 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 NODE_ITERATOR_H_ -#define NODE_ITERATOR_H_ - -#include "iterator.h" -#include "node_list.h" - -// This class implements the abstract iterator class -typedef struct node_iterator_t { - // Super class - struct iterator_t super; - - // Local members - struct node_t*(*next)(struct node_iterator_t* iterator); - int(*bind)(struct node_iterator_t* iterator, struct node_list_t* list); - - unsigned int count; - unsigned int position; - - struct node_list_t* list; - struct node_t* end; - struct node_t* begin; - struct node_t* value; - -} node_iterator_t; - -void node_iterator_destroy(node_iterator_t* iterator); -node_iterator_t* node_iterator_create(node_list_t* list); - -struct node_t* node_iterator_next(struct node_iterator_t* iterator); -int node_iterator_bind(struct node_iterator_t* iterator, struct node_list_t* list); - -#endif /* NODE_ITERATOR_H_ */ diff --git a/libcnary/include/node_list.h b/libcnary/include/node_list.h index 7b9e311..d566b00 100644 --- a/libcnary/include/node_list.h +++ b/libcnary/include/node_list.h @@ -24,24 +24,27 @@ #ifndef NODE_LIST_H_ #define NODE_LIST_H_ -struct node_t; +#include "node.h" + +typedef struct node* node_t; // This class implements the list_t abstract class -typedef struct node_list_t { +struct node_list { // list_t members - struct node_t* begin; - struct node_t* end; + node_t begin; + node_t end; // node_list_t members unsigned int count; -} node_list_t; +}; +typedef struct node_list* node_list_t; -void node_list_destroy(struct node_list_t* list); -struct node_list_t* node_list_create(struct node_t* node); +void node_list_destroy(node_list_t list); +node_list_t node_list_create(); -int node_list_add(node_list_t* list, node_t* node); -int node_list_insert(node_list_t* list, unsigned int index, node_t* node); -int node_list_remove(node_list_t* list, node_t* node); +int node_list_add(node_list_t list, node_t node); +int node_list_insert(node_list_t list, unsigned int index, node_t node); +int node_list_remove(node_list_t list, node_t node); #endif /* NODE_LIST_H_ */ diff --git a/libcnary/node.c b/libcnary/node.c index 0a8f414..63b449c 100644 --- a/libcnary/node.c +++ b/libcnary/node.c @@ -26,13 +26,13 @@ #include "node.h" #include "node_list.h" -#include "node_iterator.h" -void node_destroy(node_t* node) { +void node_destroy(node_t node) +{ if(!node) return; if (node->children && node->children->count > 0) { - node_t* ch; + node_t ch; while ((ch = node->children->begin)) { node_list_remove(node->children, ch); node_destroy(ch); @@ -44,32 +44,28 @@ void node_destroy(node_t* node) { free(node); } -node_t* node_create(node_t* parent, void* data) { +node_t node_create(node_t parent, void* data) +{ int error = 0; - node_t* node = (node_t*) malloc(sizeof(node_t)); - if(node == NULL) { + node_t node = (node_t)calloc(1, sizeof(struct node)); + if (node == NULL) { return NULL; } - memset(node, '\0', sizeof(node_t)); node->data = data; - node->depth = 0; node->next = NULL; node->prev = NULL; node->count = 0; - node->isLeaf = TRUE; - node->isRoot = TRUE; node->parent = NULL; - node->children = node_list_create(node); + node->children = NULL; // Pass NULL to create a root node - if(parent != NULL) { + if (parent != NULL) { // This is a child node so attach it to it's parent error = node_attach(parent, node); - if(error < 0) { + if (error < 0) { // Unable to attach nodes - printf("ERROR: %d \"Unable to attach nodes\"\n", error); node_destroy(node); return NULL; } @@ -78,88 +74,180 @@ node_t* node_create(node_t* parent, void* data) { return node; } -int node_attach(node_t* parent, node_t* child) { - if (!parent || !child) return -1; - child->isLeaf = TRUE; - child->isRoot = FALSE; - child->parent = parent; - child->depth = parent->depth + 1; - if(parent->isLeaf == TRUE) { - parent->isLeaf = FALSE; +static int node_depth_from_root(node_t n) +{ + int d = 0; + while (n && n->parent) { + d++; + n = n->parent; + if (d > NODE_MAX_DEPTH) return d; // early out + } + return d; +} + +static int node_subtree_max_depth(node_t root) +{ + if (!root) return 0; + + typedef struct { node_t n; int depth; } frame_t; + size_t cap = 64, sp = 0; + frame_t *st = (frame_t*)malloc(cap * sizeof(*st)); + if (!st) return NODE_MAX_DEPTH + 1; + + st[sp++] = (frame_t){ root, 0 }; + int maxd = 0; + + while (sp) { + frame_t f = st[--sp]; + if (f.depth > maxd) maxd = f.depth; + if (maxd > NODE_MAX_DEPTH) break; + + if (!f.n->children) continue; + + for (node_t ch = node_first_child(f.n); ch; ch = node_next_sibling(ch)) { + if (sp == cap) { + cap *= 2; + frame_t *tmp = (frame_t*)realloc(st, cap * sizeof(*st)); + if (!tmp) { maxd = NODE_MAX_DEPTH + 1; goto out; } + st = tmp; + } + st[sp++] = (frame_t){ ch, f.depth + 1 }; + } + } + +out: + free(st); + return maxd; +} + +static int would_create_cycle(node_t parent, node_t child) +{ + // if parent is anywhere in child's ancestor chain => cycle + for (node_t p = parent; p; p = p->parent) { + if (p == child) return 1; + } + return 0; +} + +int node_attach(node_t parent, node_t child) +{ + if (!parent || !child) return NODE_ERR_INVALID_ARG; + + // already parented? + if (child->parent) return NODE_ERR_PARENT; + + // self/cycle guard + if (parent == child) return NODE_ERR_CIRCULAR_REF; + if (would_create_cycle(parent, child)) return NODE_ERR_CIRCULAR_REF; + + // depth guard: depth(parent)+1+max_depth(child_subtree) <= NODE_MAX_DEPTH + int pd = node_depth_from_root(parent); + int cd = node_subtree_max_depth(child); + if (pd + 1 + cd > NODE_MAX_DEPTH) { + return NODE_ERR_MAX_DEPTH; + } + + if (!parent->children) { + parent->children = node_list_create(); + if (!parent->children) return NODE_ERR_NO_MEM; } int res = node_list_add(parent->children, child); if (res == 0) { + child->parent = parent; parent->count++; } return res; } -int node_detach(node_t* parent, node_t* child) { - if (!parent || !child) return -1; - int index = node_list_remove(parent->children, child); - if (index >= 0) { - parent->count--; +int node_detach(node_t parent, node_t child) +{ + if (!parent || !child) return NODE_ERR_INVALID_ARG; + if (!parent->children) return NODE_ERR_NOT_FOUND; + if (child->parent && child->parent != parent) return NODE_ERR_PARENT; + + int node_index = node_list_remove(parent->children, child); + if (node_index >= 0) { + if (parent->count > 0) parent->count--; + child->parent = NULL; + child->prev = NULL; + child->next = NULL; } - return index; + return node_index; } -int node_insert(node_t* parent, unsigned int index, node_t* child) +int node_insert(node_t parent, unsigned int node_index, node_t child) { - if (!parent || !child) return -1; - child->isLeaf = TRUE; - child->isRoot = FALSE; - child->parent = parent; - child->depth = parent->depth + 1; - if(parent->isLeaf == TRUE) { - parent->isLeaf = FALSE; + if (!parent || !child) return NODE_ERR_INVALID_ARG; + + // already parented? + if (child->parent) return NODE_ERR_PARENT; + + // self/cycle guard + if (parent == child) return NODE_ERR_CIRCULAR_REF; + if (would_create_cycle(parent, child)) return NODE_ERR_CIRCULAR_REF; + + // depth guard: depth(parent)+1+max_depth(child_subtree) <= NODE_MAX_DEPTH + int pd = node_depth_from_root(parent); + int cd = node_subtree_max_depth(child); + if (pd + 1 + cd > NODE_MAX_DEPTH) { + return NODE_ERR_MAX_DEPTH; } - int res = node_list_insert(parent->children, index, child); + + if (!parent->children) { + parent->children = node_list_create(); + if (!parent->children) return NODE_ERR_NO_MEM; + } + int res = node_list_insert(parent->children, node_index, child); if (res == 0) { + child->parent = parent; parent->count++; } return res; } -void node_debug(node_t* node) { - int i = 0; - node_t* current = NULL; - node_iterator_t* iter = NULL; - for(i = 0; i < node->depth; i++) { +static void _node_debug(node_t node, unsigned int depth) +{ + unsigned int i = 0; + node_t current = NULL; + for(i = 0; i < depth; i++) { printf("\t"); } - if(node->isRoot) { + if(!node->parent) { printf("ROOT\n"); } - if(node->isLeaf && !node->isRoot) { + if(!node->children && node->parent) { printf("LEAF\n"); - } else { - if(!node->isRoot) { + if(node->parent) { printf("NODE\n"); } - iter = node_iterator_create(node->children); - for(current = iter->begin; current != NULL; current = iter->next(iter)) { - node_debug(current); + for (current = node_first_child(node); current; current = node_next_sibling(current)) { + _node_debug(current, depth+1); } } } -unsigned int node_n_children(struct node_t* node) +void node_debug(node_t node) +{ + _node_debug(node, 0); +} + +unsigned int node_n_children(node_t node) { if (!node) return 0; return node->count; } -node_t* node_nth_child(struct node_t* node, unsigned int n) +node_t node_nth_child(node_t node, unsigned int n) { if (!node || !node->children || !node->children->begin) return NULL; - int index = 0; + unsigned int node_index = 0; int found = 0; - node_t *ch; + node_t ch; for (ch = node_first_child(node); ch; ch = node_next_sibling(ch)) { - if (index++ == n) { + if (node_index++ == n) { found = 1; break; } @@ -170,55 +258,64 @@ node_t* node_nth_child(struct node_t* node, unsigned int n) return ch; } -node_t* node_first_child(struct node_t* node) +node_t node_first_child(node_t node) { if (!node || !node->children) return NULL; return node->children->begin; } -node_t* node_prev_sibling(struct node_t* node) +node_t node_prev_sibling(node_t node) { if (!node) return NULL; return node->prev; } -node_t* node_next_sibling(struct node_t* node) +node_t node_next_sibling(node_t node) { if (!node) return NULL; return node->next; } -int node_child_position(struct node_t* parent, node_t* child) +int node_child_position(node_t parent, node_t child) { - if (!parent || !parent->children || !parent->children->begin || !child) return -1; - int index = 0; + if (!parent || !parent->children || !parent->children->begin || !child) return NODE_ERR_INVALID_ARG; + int node_index = 0; int found = 0; - node_t *ch; + node_t ch; for (ch = node_first_child(parent); ch; ch = node_next_sibling(ch)) { if (ch == child) { found = 1; break; } - index++; + node_index++; } if (!found) { - return -1; + return NODE_ERR_NOT_FOUND; } - return index; + return node_index; } -node_t* node_copy_deep(node_t* node, copy_func_t copy_func) +node_t node_copy_deep(node_t node, copy_func_t copy_func) { if (!node) return NULL; void *data = NULL; if (copy_func) { data = copy_func(node->data); } - node_t* copy = node_create(NULL, data); - node_t* ch; + node_t copy = node_create(NULL, data); + if (!copy) return NULL; + node_t ch; for (ch = node_first_child(node); ch; ch = node_next_sibling(ch)) { - node_t* cc = node_copy_deep(ch, copy_func); - node_attach(copy, cc); + node_t cc = node_copy_deep(ch, copy_func); + if (!cc) { + node_destroy(copy); + return NULL; + } + if (node_attach(copy, cc) < 0) { + node_destroy(cc); + node_destroy(copy); + return NULL; + } } return copy; } diff --git a/libcnary/node_iterator.c b/libcnary/node_iterator.c deleted file mode 100644 index dedf3b4..0000000 --- a/libcnary/node_iterator.c +++ /dev/null @@ -1,80 +0,0 @@ -/* - * node_iterator.c - * - * Created on: Mar 8, 2011 - * Author: posixninja - * - * Copyright (c) 2011 Joshua Hill. 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 <stdio.h> -#include <stdlib.h> -#include <string.h> - -#include "node.h" -#include "node_list.h" -#include "node_iterator.h" - -void node_iterator_destroy(node_iterator_t* iterator) { - if(iterator) { - free(iterator); - } -} - -node_iterator_t* node_iterator_create(node_list_t* list) { - node_iterator_t* iterator = (node_iterator_t*) malloc(sizeof(node_iterator_t)); - if(iterator == NULL) { - return NULL; - } - memset(iterator, '\0', sizeof(node_iterator_t)); - - iterator->count = 0; - iterator->position = 0; - - iterator->end = NULL; - iterator->begin = NULL; - iterator->value = list->begin; - - iterator->list = NULL; - iterator->next = node_iterator_next; - iterator->bind = node_iterator_bind; - - - if(list != NULL) { - iterator->bind(iterator, list); - } - - return iterator; -} - -node_t* node_iterator_next(node_iterator_t* iterator) { - node_t* node = iterator->value; - if (node) { - iterator->value = node->next; - } - iterator->position++; - return node; -} - -int node_iterator_bind(node_iterator_t* iterator, node_list_t* list) { - iterator->position = 0; - iterator->end = list->end; - iterator->count = list->count; - iterator->begin = list->begin; - iterator->value = list->begin; - return 0; -} diff --git a/libcnary/node_list.c b/libcnary/node_list.c index 5b291e7..218c81d 100644 --- a/libcnary/node_list.c +++ b/libcnary/node_list.c @@ -25,34 +25,34 @@ #include <stdlib.h> #include <string.h> -#include "list.h" #include "node.h" #include "node_list.h" -void node_list_destroy(node_list_t* list) { - if(list != NULL) { - list_destroy((list_t*) list); - } +void node_list_destroy(node_list_t list) +{ + free(list); } -node_list_t* node_list_create(node_t* node) { - node_list_t* list = (node_list_t*) malloc(sizeof(node_list_t)); - if(list == NULL) { +node_list_t node_list_create() +{ + node_list_t list = (node_list_t)calloc(1, sizeof(struct node_list)); + if (list == NULL) { return NULL; } - memset(list, '\0', sizeof(node_list_t)); // Initialize structure - list_init((list_t*) list); + list->begin = NULL; + list->end = NULL; list->count = 0; return list; } -int node_list_add(node_list_t* list, node_t* node) { - if (!list || !node) return -1; +int node_list_add(node_list_t list, node_t node) +{ + if (!list || !node) return NODE_ERR_INVALID_ARG; // Find the last element in the list - node_t* last = list->end; + node_t last = list->end; // Setup our new node as the new last element node->next = NULL; @@ -62,6 +62,9 @@ int node_list_add(node_list_t* list, node_t* node) { if (last) { // but only if the node list is not empty last->next = node; + } else { + // otherwise this is the start of the list + list->begin = node; } // Set the lists prev to the new last element @@ -69,86 +72,80 @@ int node_list_add(node_list_t* list, node_t* node) { // Increment our node count for this list list->count++; - return 0; + return NODE_ERR_SUCCESS; } -int node_list_insert(node_list_t* list, unsigned int index, node_t* node) { - if (!list || !node) return -1; - if (index >= list->count) { +int node_list_insert(node_list_t list, unsigned int node_index, node_t node) +{ + if (!list || !node) return NODE_ERR_INVALID_ARG; + if (node_index > list->count) return NODE_ERR_INVALID_ARG; + if (node_index == list->count) { return node_list_add(list, node); } // Get the first element in the list - node_t* cur = list->begin; - - unsigned int pos = 0; - node_t* prev = NULL; + node_t cur = list->begin; + node_t prev = NULL; - if (index > 0) { - while (pos < index) { - prev = cur; - cur = cur->next; - pos++; - } + for (unsigned int pos = 0; pos < node_index; pos++) { + if (!cur) return NODE_ERR_INVALID_ARG; + prev = cur; + cur = cur->next; } + // insert node before cur + node->prev = prev; + node->next = cur; + if (prev) { - // Set previous node - node->prev = prev; - // Set next node of our new node to next node of the previous node - node->next = prev->next; - // Set next node of previous node to our new node prev->next = node; } else { - node->prev = NULL; - // get old first element in list - node->next = list->begin; - // set new node as first element in list list->begin = node; } - if (node->next == NULL) { - // Set the lists prev to the new last element - list->end = node; + if (cur) { + cur->prev = node; } else { - // set prev of the new next element to our node - node->next->prev = node; + // should not happen with bounds above, but keeps things consistent + list->end = node; } // Increment our node count for this list list->count++; - return 0; + return NODE_ERR_SUCCESS; } -int node_list_remove(node_list_t* list, node_t* node) { - if (!list || !node) return -1; - if (list->count == 0) return -1; - - int index = 0; - node_t* n; - for (n = list->begin; n; n = n->next) { - if (node == n) { - node_t* newnode = node->next; - if (node->prev) { - node->prev->next = newnode; - if (newnode) { - newnode->prev = node->prev; - } else { - // last element in the list - list->end = node->prev; - } - } else { - // we just removed the first element - if (newnode) { - newnode->prev = NULL; - } - list->begin = newnode; - } - list->count--; - return index; +// Returns removed index (>=0) on success, or NODE_ERR_* (<0) on failure. +int node_list_remove(node_list_t list, node_t node) +{ + if (!list || !node) return NODE_ERR_INVALID_ARG; + if (list->count == 0) return NODE_ERR_NOT_FOUND; + + int node_index = 0; + for (node_t n = list->begin; n; n = n->next, node_index++) { + if (node != n) continue; + + node_t newnode = node->next; + if (node->prev) { + node->prev->next = newnode; + } else { + // we just removed the first element + list->begin = newnode; + } + + if (newnode) { + newnode->prev = node->prev; + } else { + // we removed the last element, set new end + list->end = node->prev; } - index++; - } - return -1; -} + // fully detach node from list + node->prev = NULL; + node->next = NULL; + + list->count--; + return node_index; + } + return NODE_ERR_NOT_FOUND; +} diff --git a/libplist++.pc.in b/libplist++.pc.in deleted file mode 100644 index 89e40d7..0000000 --- a/libplist++.pc.in +++ /dev/null @@ -1,12 +0,0 @@ -prefix=${CMAKE_INSTALL_PREFIX} -exec_prefix=${CMAKE_INSTALL_PREFIX} -libdir=${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_LIBDIR} -includedir=${CMAKE_INSTALL_PREFIX}/include - -Name: libplist++ -Description: C++ binding of libplist -Version: ${LIBPLIST_VERSION} -Requires: libplist >= ${LIBPLIST_VERSION} -Libs: -L${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_LIBDIR} -lplist++ -Cflags: -I${CMAKE_INSTALL_PREFIX}/include - diff --git a/libplist.pc.in b/libplist.pc.in deleted file mode 100644 index 971b8b5..0000000 --- a/libplist.pc.in +++ /dev/null @@ -1,12 +0,0 @@ -prefix=${CMAKE_INSTALL_PREFIX} -exec_prefix=${CMAKE_INSTALL_PREFIX} -libdir=${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_LIBDIR} -includedir=${CMAKE_INSTALL_PREFIX}/include - -Name: libplist -Description: A library to handle Apple Property Lists whereas they are binary or XML -Version: ${LIBPLIST_VERSION} -Requires.private: libxml-2.0 >= 2.6.30 -Libs: -L${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_LIBDIR} -lplist -Cflags: -I${CMAKE_INSTALL_PREFIX}/include - diff --git a/m4/ac_pkg_cython.m4 b/m4/ac_pkg_cython.m4 new file mode 100644 index 0000000..e0af96a --- /dev/null +++ b/m4/ac_pkg_cython.m4 @@ -0,0 +1,67 @@ + +AC_DEFUN([AC_PROG_CYTHON],[ + AC_PATH_PROGS([CYTHON],[cython cython3]) + if test -z "$CYTHON" ; then + AC_MSG_WARN([Unable to find 'cython' or 'cython3' program. You should look at https://cython.org or install your distribution specific cython package.]) + CYTHON=false + elif test -n "$1" ; then + AC_MSG_CHECKING([for Cython version]) + [cython_version=`$CYTHON --version 2>&1 | sed 's/Cython version \(.*\)$/\1/g'`] + AC_MSG_RESULT([$cython_version]) + + # Setup extra version string for parsing + [cython_version_stripped=`echo $cython_version | sed 's/\([0-9]\+\)\.\([0-9]\+\)[^\.]*\(\.\([0-9]\+\)\)\?.*/0\1.0\2.0\4/g'`] + if test -n "$cython_version" ; then + # Calculate the required version number components + [required=$1] + [required_major=`echo $required | sed 's/[^0-9].*//'`] + if test -z "$required_major" ; then + [required_major=0] + fi + [required=`echo $required | sed 's/[0-9]*[^0-9]//'`] + [required_minor=`echo $required | sed 's/[^0-9].*//'`] + if test -z "$required_minor" ; then + [required_minor=0] + fi + [required=`echo $required | sed 's/[0-9]*[^0-9]//'`] + [required_patch=`echo $required | sed 's/[^0-9].*//'`] + if test -z "$required_patch" ; then + [required_patch=0] + fi + + # Calculate the available version number components + [available=$cython_version_stripped] + [available_major=`echo $available | sed 's/[^0-9].*//'`] + if test -z "$available_major" ; then + [available_major=0] + fi + [available=`echo $available | sed 's/[0-9]*[^0-9]//'`] + [available_minor=`echo $available | sed 's/[^0-9].*//'`] + if test -z "$available_minor" ; then + [available_minor=0] + fi + [available=`echo $available | sed 's/[0-9]*[^0-9]//'`] + [available_patch=`echo $available | sed 's/[^0-9].*//'`] + if test -z "$available_patch" ; then + [available_patch=0] + fi + + if test $available_major -gt $required_major || \ + ( test $available_major -eq $required_major && \ + test $available_minor -gt $required_minor ) || \ + ( test $available_major -eq $required_major && \ + test $available_minor -eq $required_minor && \ + test $available_patch -ge $required_patch ) ; then + + AC_MSG_NOTICE([Cython executable is '$CYTHON']) + else + AC_MSG_WARN([Cython version >= $1 is required. You have $cython_version. You should look at http://www.cython.org]) + CYTHON='echo "Error: Cython version >= $1 is required. You have '"$cython_version"'. You should look at http://www.cython.org" ; false' + fi + else + AC_MSG_WARN([Unable to determine Cython version]) + CYTHON=false + fi + fi + AC_SUBST([CYTHON_LIB]) +]) diff --git a/m4/as-compiler-flag.m4 b/m4/as-compiler-flag.m4 new file mode 100644 index 0000000..baab5d9 --- /dev/null +++ b/m4/as-compiler-flag.m4 @@ -0,0 +1,62 @@ +dnl as-compiler-flag.m4 0.1.0 + +dnl autostars m4 macro for detection of compiler flags + +dnl David Schleef <ds@schleef.org> + +dnl $Id: as-compiler-flag.m4,v 1.1 2005/12/15 23:35:19 ds Exp $ + +dnl AS_COMPILER_FLAG(CFLAGS, ACTION-IF-ACCEPTED, [ACTION-IF-NOT-ACCEPTED]) +dnl Tries to compile with the given CFLAGS. +dnl Runs ACTION-IF-ACCEPTED if the compiler can compile with the flags, +dnl and ACTION-IF-NOT-ACCEPTED otherwise. + +AC_DEFUN([AS_COMPILER_FLAG], +[ + AC_MSG_CHECKING([to see if compiler understands $1]) + + save_CFLAGS="$CFLAGS" + CFLAGS="$CFLAGS $1" + + AC_COMPILE_IFELSE([AC_LANG_PROGRAM([],[])], [flag_ok=yes], [flag_ok=no]) + CFLAGS="$save_CFLAGS" + + if test "X$flag_ok" = Xyes ; then + m4_ifvaln([$2],[$2]) + true + else + m4_ifvaln([$3],[$3]) + true + fi + AC_MSG_RESULT([$flag_ok]) +]) + +dnl AS_COMPILER_FLAGS(VAR, FLAGS) +dnl Tries to compile with the given CFLAGS. + +AC_DEFUN([AS_COMPILER_FLAGS], +[ + list=$2 + flags_supported="" + flags_unsupported="" + AC_MSG_CHECKING([for supported compiler flags]) + for each in $list + do + save_CFLAGS="$CFLAGS" + CFLAGS="$CFLAGS $each" + AC_COMPILE_IFELSE([AC_LANG_PROGRAM([],[])], [flag_ok=yes], [flag_ok=no]) + CFLAGS="$save_CFLAGS" + + if test "X$flag_ok" = Xyes ; then + flags_supported="$flags_supported $each" + else + flags_unsupported="$flags_unsupported $each" + fi + done + AC_MSG_RESULT([$flags_supported]) + if test "X$flags_unsupported" != X ; then + AC_MSG_WARN([unsupported compiler flags: $flags_unsupported]) + fi + $1="$$1 $flags_supported" +]) + diff --git a/m4/ax_pthread.m4 b/m4/ax_pthread.m4 new file mode 100644 index 0000000..9f35d13 --- /dev/null +++ b/m4/ax_pthread.m4 @@ -0,0 +1,522 @@ +# =========================================================================== +# https://www.gnu.org/software/autoconf-archive/ax_pthread.html +# =========================================================================== +# +# SYNOPSIS +# +# AX_PTHREAD([ACTION-IF-FOUND[, ACTION-IF-NOT-FOUND]]) +# +# DESCRIPTION +# +# This macro figures out how to build C programs using POSIX threads. It +# sets the PTHREAD_LIBS output variable to the threads library and linker +# flags, and the PTHREAD_CFLAGS output variable to any special C compiler +# flags that are needed. (The user can also force certain compiler +# flags/libs to be tested by setting these environment variables.) +# +# Also sets PTHREAD_CC and PTHREAD_CXX to any special C compiler that is +# needed for multi-threaded programs (defaults to the value of CC +# respectively CXX otherwise). (This is necessary on e.g. AIX to use the +# special cc_r/CC_r compiler alias.) +# +# NOTE: You are assumed to not only compile your program with these flags, +# but also to link with them as well. For example, you might link with +# $PTHREAD_CC $CFLAGS $PTHREAD_CFLAGS $LDFLAGS ... $PTHREAD_LIBS $LIBS +# $PTHREAD_CXX $CXXFLAGS $PTHREAD_CFLAGS $LDFLAGS ... $PTHREAD_LIBS $LIBS +# +# If you are only building threaded programs, you may wish to use these +# variables in your default LIBS, CFLAGS, and CC: +# +# LIBS="$PTHREAD_LIBS $LIBS" +# CFLAGS="$CFLAGS $PTHREAD_CFLAGS" +# CXXFLAGS="$CXXFLAGS $PTHREAD_CFLAGS" +# CC="$PTHREAD_CC" +# CXX="$PTHREAD_CXX" +# +# In addition, if the PTHREAD_CREATE_JOINABLE thread-attribute constant +# has a nonstandard name, this macro defines PTHREAD_CREATE_JOINABLE to +# that name (e.g. PTHREAD_CREATE_UNDETACHED on AIX). +# +# Also HAVE_PTHREAD_PRIO_INHERIT is defined if pthread is found and the +# PTHREAD_PRIO_INHERIT symbol is defined when compiling with +# PTHREAD_CFLAGS. +# +# ACTION-IF-FOUND is a list of shell commands to run if a threads library +# is found, and ACTION-IF-NOT-FOUND is a list of commands to run it if it +# is not found. If ACTION-IF-FOUND is not specified, the default action +# will define HAVE_PTHREAD. +# +# Please let the authors know if this macro fails on any platform, or if +# you have any other suggestions or comments. This macro was based on work +# by SGJ on autoconf scripts for FFTW (http://www.fftw.org/) (with help +# from M. Frigo), as well as ac_pthread and hb_pthread macros posted by +# Alejandro Forero Cuervo to the autoconf macro repository. We are also +# grateful for the helpful feedback of numerous users. +# +# Updated for Autoconf 2.68 by Daniel Richard G. +# +# LICENSE +# +# Copyright (c) 2008 Steven G. Johnson <stevenj@alum.mit.edu> +# Copyright (c) 2011 Daniel Richard G. <skunk@iSKUNK.ORG> +# Copyright (c) 2019 Marc Stevens <marc.stevens@cwi.nl> +# +# This program is free software: you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation, either version 3 of the License, or (at your +# option) any later version. +# +# This program 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 General +# Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program. If not, see <https://www.gnu.org/licenses/>. +# +# As a special exception, the respective Autoconf Macro's copyright owner +# gives unlimited permission to copy, distribute and modify the configure +# scripts that are the output of Autoconf when processing the Macro. You +# need not follow the terms of the GNU General Public License when using +# or distributing such scripts, even though portions of the text of the +# Macro appear in them. The GNU General Public License (GPL) does govern +# all other use of the material that constitutes the Autoconf Macro. +# +# This special exception to the GPL applies to versions of the Autoconf +# Macro released by the Autoconf Archive. When you make and distribute a +# modified version of the Autoconf Macro, you may extend this special +# exception to the GPL to apply to your modified version as well. + +#serial 31 + +AU_ALIAS([ACX_PTHREAD], [AX_PTHREAD]) +AC_DEFUN([AX_PTHREAD], [ +AC_REQUIRE([AC_CANONICAL_HOST]) +AC_REQUIRE([AC_PROG_CC]) +AC_REQUIRE([AC_PROG_SED]) +AC_LANG_PUSH([C]) +ax_pthread_ok=no + +# We used to check for pthread.h first, but this fails if pthread.h +# requires special compiler flags (e.g. on Tru64 or Sequent). +# It gets checked for in the link test anyway. + +# First of all, check if the user has set any of the PTHREAD_LIBS, +# etcetera environment variables, and if threads linking works using +# them: +if test "x$PTHREAD_CFLAGS$PTHREAD_LIBS" != "x"; then + ax_pthread_save_CC="$CC" + ax_pthread_save_CFLAGS="$CFLAGS" + ax_pthread_save_LIBS="$LIBS" + AS_IF([test "x$PTHREAD_CC" != "x"], [CC="$PTHREAD_CC"]) + AS_IF([test "x$PTHREAD_CXX" != "x"], [CXX="$PTHREAD_CXX"]) + CFLAGS="$CFLAGS $PTHREAD_CFLAGS" + LIBS="$PTHREAD_LIBS $LIBS" + AC_MSG_CHECKING([for pthread_join using $CC $PTHREAD_CFLAGS $PTHREAD_LIBS]) + AC_LINK_IFELSE([AC_LANG_CALL([], [pthread_join])], [ax_pthread_ok=yes]) + AC_MSG_RESULT([$ax_pthread_ok]) + if test "x$ax_pthread_ok" = "xno"; then + PTHREAD_LIBS="" + PTHREAD_CFLAGS="" + fi + CC="$ax_pthread_save_CC" + CFLAGS="$ax_pthread_save_CFLAGS" + LIBS="$ax_pthread_save_LIBS" +fi + +# We must check for the threads library under a number of different +# names; the ordering is very important because some systems +# (e.g. DEC) have both -lpthread and -lpthreads, where one of the +# libraries is broken (non-POSIX). + +# Create a list of thread flags to try. Items with a "," contain both +# C compiler flags (before ",") and linker flags (after ","). Other items +# starting with a "-" are C compiler flags, and remaining items are +# library names, except for "none" which indicates that we try without +# any flags at all, and "pthread-config" which is a program returning +# the flags for the Pth emulation library. + +ax_pthread_flags="pthreads none -Kthread -pthread -pthreads -mthreads pthread --thread-safe -mt pthread-config" + +# The ordering *is* (sometimes) important. Some notes on the +# individual items follow: + +# pthreads: AIX (must check this before -lpthread) +# none: in case threads are in libc; should be tried before -Kthread and +# other compiler flags to prevent continual compiler warnings +# -Kthread: Sequent (threads in libc, but -Kthread needed for pthread.h) +# -pthread: Linux/gcc (kernel threads), BSD/gcc (userland threads), Tru64 +# (Note: HP C rejects this with "bad form for `-t' option") +# -pthreads: Solaris/gcc (Note: HP C also rejects) +# -mt: Sun Workshop C (may only link SunOS threads [-lthread], but it +# doesn't hurt to check since this sometimes defines pthreads and +# -D_REENTRANT too), HP C (must be checked before -lpthread, which +# is present but should not be used directly; and before -mthreads, +# because the compiler interprets this as "-mt" + "-hreads") +# -mthreads: Mingw32/gcc, Lynx/gcc +# pthread: Linux, etcetera +# --thread-safe: KAI C++ +# pthread-config: use pthread-config program (for GNU Pth library) + +case $host_os in + + freebsd*) + + # -kthread: FreeBSD kernel threads (preferred to -pthread since SMP-able) + # lthread: LinuxThreads port on FreeBSD (also preferred to -pthread) + + ax_pthread_flags="-kthread lthread $ax_pthread_flags" + ;; + + hpux*) + + # From the cc(1) man page: "[-mt] Sets various -D flags to enable + # multi-threading and also sets -lpthread." + + ax_pthread_flags="-mt -pthread pthread $ax_pthread_flags" + ;; + + openedition*) + + # IBM z/OS requires a feature-test macro to be defined in order to + # enable POSIX threads at all, so give the user a hint if this is + # not set. (We don't define these ourselves, as they can affect + # other portions of the system API in unpredictable ways.) + + AC_EGREP_CPP([AX_PTHREAD_ZOS_MISSING], + [ +# if !defined(_OPEN_THREADS) && !defined(_UNIX03_THREADS) + AX_PTHREAD_ZOS_MISSING +# endif + ], + [AC_MSG_WARN([IBM z/OS requires -D_OPEN_THREADS or -D_UNIX03_THREADS to enable pthreads support.])]) + ;; + + solaris*) + + # On Solaris (at least, for some versions), libc contains stubbed + # (non-functional) versions of the pthreads routines, so link-based + # tests will erroneously succeed. (N.B.: The stubs are missing + # pthread_cleanup_push, or rather a function called by this macro, + # so we could check for that, but who knows whether they'll stub + # that too in a future libc.) So we'll check first for the + # standard Solaris way of linking pthreads (-mt -lpthread). + + ax_pthread_flags="-mt,-lpthread pthread $ax_pthread_flags" + ;; +esac + +# Are we compiling with Clang? + +AC_CACHE_CHECK([whether $CC is Clang], + [ax_cv_PTHREAD_CLANG], + [ax_cv_PTHREAD_CLANG=no + # Note that Autoconf sets GCC=yes for Clang as well as GCC + if test "x$GCC" = "xyes"; then + AC_EGREP_CPP([AX_PTHREAD_CC_IS_CLANG], + [/* Note: Clang 2.7 lacks __clang_[a-z]+__ */ +# if defined(__clang__) && defined(__llvm__) + AX_PTHREAD_CC_IS_CLANG +# endif + ], + [ax_cv_PTHREAD_CLANG=yes]) + fi + ]) +ax_pthread_clang="$ax_cv_PTHREAD_CLANG" + + +# GCC generally uses -pthread, or -pthreads on some platforms (e.g. SPARC) + +# Note that for GCC and Clang -pthread generally implies -lpthread, +# except when -nostdlib is passed. +# This is problematic using libtool to build C++ shared libraries with pthread: +# [1] https://gcc.gnu.org/bugzilla/show_bug.cgi?id=25460 +# [2] https://bugzilla.redhat.com/show_bug.cgi?id=661333 +# [3] https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=468555 +# To solve this, first try -pthread together with -lpthread for GCC + +AS_IF([test "x$GCC" = "xyes"], + [ax_pthread_flags="-pthread,-lpthread -pthread -pthreads $ax_pthread_flags"]) + +# Clang takes -pthread (never supported any other flag), but we'll try with -lpthread first + +AS_IF([test "x$ax_pthread_clang" = "xyes"], + [ax_pthread_flags="-pthread,-lpthread -pthread"]) + + +# The presence of a feature test macro requesting re-entrant function +# definitions is, on some systems, a strong hint that pthreads support is +# correctly enabled + +case $host_os in + darwin* | hpux* | linux* | osf* | solaris*) + ax_pthread_check_macro="_REENTRANT" + ;; + + aix*) + ax_pthread_check_macro="_THREAD_SAFE" + ;; + + *) + ax_pthread_check_macro="--" + ;; +esac +AS_IF([test "x$ax_pthread_check_macro" = "x--"], + [ax_pthread_check_cond=0], + [ax_pthread_check_cond="!defined($ax_pthread_check_macro)"]) + + +if test "x$ax_pthread_ok" = "xno"; then +for ax_pthread_try_flag in $ax_pthread_flags; do + + case $ax_pthread_try_flag in + none) + AC_MSG_CHECKING([whether pthreads work without any flags]) + ;; + + *,*) + PTHREAD_CFLAGS=`echo $ax_pthread_try_flag | sed "s/^\(.*\),\(.*\)$/\1/"` + PTHREAD_LIBS=`echo $ax_pthread_try_flag | sed "s/^\(.*\),\(.*\)$/\2/"` + AC_MSG_CHECKING([whether pthreads work with "$PTHREAD_CFLAGS" and "$PTHREAD_LIBS"]) + ;; + + -*) + AC_MSG_CHECKING([whether pthreads work with $ax_pthread_try_flag]) + PTHREAD_CFLAGS="$ax_pthread_try_flag" + ;; + + pthread-config) + AC_CHECK_PROG([ax_pthread_config], [pthread-config], [yes], [no]) + AS_IF([test "x$ax_pthread_config" = "xno"], [continue]) + PTHREAD_CFLAGS="`pthread-config --cflags`" + PTHREAD_LIBS="`pthread-config --ldflags` `pthread-config --libs`" + ;; + + *) + AC_MSG_CHECKING([for the pthreads library -l$ax_pthread_try_flag]) + PTHREAD_LIBS="-l$ax_pthread_try_flag" + ;; + esac + + ax_pthread_save_CFLAGS="$CFLAGS" + ax_pthread_save_LIBS="$LIBS" + CFLAGS="$CFLAGS $PTHREAD_CFLAGS" + LIBS="$PTHREAD_LIBS $LIBS" + + # Check for various functions. We must include pthread.h, + # since some functions may be macros. (On the Sequent, we + # need a special flag -Kthread to make this header compile.) + # We check for pthread_join because it is in -lpthread on IRIX + # while pthread_create is in libc. We check for pthread_attr_init + # due to DEC craziness with -lpthreads. We check for + # pthread_cleanup_push because it is one of the few pthread + # functions on Solaris that doesn't have a non-functional libc stub. + # We try pthread_create on general principles. + + AC_LINK_IFELSE([AC_LANG_PROGRAM([#include <pthread.h> +# if $ax_pthread_check_cond +# error "$ax_pthread_check_macro must be defined" +# endif + static void *some_global = NULL; + static void routine(void *a) + { + /* To avoid any unused-parameter or + unused-but-set-parameter warning. */ + some_global = a; + } + static void *start_routine(void *a) { return a; }], + [pthread_t th; pthread_attr_t attr; + pthread_create(&th, 0, start_routine, 0); + pthread_join(th, 0); + pthread_attr_init(&attr); + pthread_cleanup_push(routine, 0); + pthread_cleanup_pop(0) /* ; */])], + [ax_pthread_ok=yes], + []) + + CFLAGS="$ax_pthread_save_CFLAGS" + LIBS="$ax_pthread_save_LIBS" + + AC_MSG_RESULT([$ax_pthread_ok]) + AS_IF([test "x$ax_pthread_ok" = "xyes"], [break]) + + PTHREAD_LIBS="" + PTHREAD_CFLAGS="" +done +fi + + +# Clang needs special handling, because older versions handle the -pthread +# option in a rather... idiosyncratic way + +if test "x$ax_pthread_clang" = "xyes"; then + + # Clang takes -pthread; it has never supported any other flag + + # (Note 1: This will need to be revisited if a system that Clang + # supports has POSIX threads in a separate library. This tends not + # to be the way of modern systems, but it's conceivable.) + + # (Note 2: On some systems, notably Darwin, -pthread is not needed + # to get POSIX threads support; the API is always present and + # active. We could reasonably leave PTHREAD_CFLAGS empty. But + # -pthread does define _REENTRANT, and while the Darwin headers + # ignore this macro, third-party headers might not.) + + # However, older versions of Clang make a point of warning the user + # that, in an invocation where only linking and no compilation is + # taking place, the -pthread option has no effect ("argument unused + # during compilation"). They expect -pthread to be passed in only + # when source code is being compiled. + # + # Problem is, this is at odds with the way Automake and most other + # C build frameworks function, which is that the same flags used in + # compilation (CFLAGS) are also used in linking. Many systems + # supported by AX_PTHREAD require exactly this for POSIX threads + # support, and in fact it is often not straightforward to specify a + # flag that is used only in the compilation phase and not in + # linking. Such a scenario is extremely rare in practice. + # + # Even though use of the -pthread flag in linking would only print + # a warning, this can be a nuisance for well-run software projects + # that build with -Werror. So if the active version of Clang has + # this misfeature, we search for an option to squash it. + + AC_CACHE_CHECK([whether Clang needs flag to prevent "argument unused" warning when linking with -pthread], + [ax_cv_PTHREAD_CLANG_NO_WARN_FLAG], + [ax_cv_PTHREAD_CLANG_NO_WARN_FLAG=unknown + # Create an alternate version of $ac_link that compiles and + # links in two steps (.c -> .o, .o -> exe) instead of one + # (.c -> exe), because the warning occurs only in the second + # step + ax_pthread_save_ac_link="$ac_link" + ax_pthread_sed='s/conftest\.\$ac_ext/conftest.$ac_objext/g' + ax_pthread_link_step=`AS_ECHO(["$ac_link"]) | sed "$ax_pthread_sed"` + ax_pthread_2step_ac_link="($ac_compile) && (echo ==== >&5) && ($ax_pthread_link_step)" + ax_pthread_save_CFLAGS="$CFLAGS" + for ax_pthread_try in '' -Qunused-arguments -Wno-unused-command-line-argument unknown; do + AS_IF([test "x$ax_pthread_try" = "xunknown"], [break]) + CFLAGS="-Werror -Wunknown-warning-option $ax_pthread_try -pthread $ax_pthread_save_CFLAGS" + ac_link="$ax_pthread_save_ac_link" + AC_LINK_IFELSE([AC_LANG_SOURCE([[int main(void){return 0;}]])], + [ac_link="$ax_pthread_2step_ac_link" + AC_LINK_IFELSE([AC_LANG_SOURCE([[int main(void){return 0;}]])], + [break]) + ]) + done + ac_link="$ax_pthread_save_ac_link" + CFLAGS="$ax_pthread_save_CFLAGS" + AS_IF([test "x$ax_pthread_try" = "x"], [ax_pthread_try=no]) + ax_cv_PTHREAD_CLANG_NO_WARN_FLAG="$ax_pthread_try" + ]) + + case "$ax_cv_PTHREAD_CLANG_NO_WARN_FLAG" in + no | unknown) ;; + *) PTHREAD_CFLAGS="$ax_cv_PTHREAD_CLANG_NO_WARN_FLAG $PTHREAD_CFLAGS" ;; + esac + +fi # $ax_pthread_clang = yes + + + +# Various other checks: +if test "x$ax_pthread_ok" = "xyes"; then + ax_pthread_save_CFLAGS="$CFLAGS" + ax_pthread_save_LIBS="$LIBS" + CFLAGS="$CFLAGS $PTHREAD_CFLAGS" + LIBS="$PTHREAD_LIBS $LIBS" + + # Detect AIX lossage: JOINABLE attribute is called UNDETACHED. + AC_CACHE_CHECK([for joinable pthread attribute], + [ax_cv_PTHREAD_JOINABLE_ATTR], + [ax_cv_PTHREAD_JOINABLE_ATTR=unknown + for ax_pthread_attr in PTHREAD_CREATE_JOINABLE PTHREAD_CREATE_UNDETACHED; do + AC_LINK_IFELSE([AC_LANG_PROGRAM([#include <pthread.h>], + [int attr = $ax_pthread_attr; return attr /* ; */])], + [ax_cv_PTHREAD_JOINABLE_ATTR=$ax_pthread_attr; break], + []) + done + ]) + AS_IF([test "x$ax_cv_PTHREAD_JOINABLE_ATTR" != "xunknown" && \ + test "x$ax_cv_PTHREAD_JOINABLE_ATTR" != "xPTHREAD_CREATE_JOINABLE" && \ + test "x$ax_pthread_joinable_attr_defined" != "xyes"], + [AC_DEFINE_UNQUOTED([PTHREAD_CREATE_JOINABLE], + [$ax_cv_PTHREAD_JOINABLE_ATTR], + [Define to necessary symbol if this constant + uses a non-standard name on your system.]) + ax_pthread_joinable_attr_defined=yes + ]) + + AC_CACHE_CHECK([whether more special flags are required for pthreads], + [ax_cv_PTHREAD_SPECIAL_FLAGS], + [ax_cv_PTHREAD_SPECIAL_FLAGS=no + case $host_os in + solaris*) + ax_cv_PTHREAD_SPECIAL_FLAGS="-D_POSIX_PTHREAD_SEMANTICS" + ;; + esac + ]) + AS_IF([test "x$ax_cv_PTHREAD_SPECIAL_FLAGS" != "xno" && \ + test "x$ax_pthread_special_flags_added" != "xyes"], + [PTHREAD_CFLAGS="$ax_cv_PTHREAD_SPECIAL_FLAGS $PTHREAD_CFLAGS" + ax_pthread_special_flags_added=yes]) + + AC_CACHE_CHECK([for PTHREAD_PRIO_INHERIT], + [ax_cv_PTHREAD_PRIO_INHERIT], + [AC_LINK_IFELSE([AC_LANG_PROGRAM([[#include <pthread.h>]], + [[int i = PTHREAD_PRIO_INHERIT; + return i;]])], + [ax_cv_PTHREAD_PRIO_INHERIT=yes], + [ax_cv_PTHREAD_PRIO_INHERIT=no]) + ]) + AS_IF([test "x$ax_cv_PTHREAD_PRIO_INHERIT" = "xyes" && \ + test "x$ax_pthread_prio_inherit_defined" != "xyes"], + [AC_DEFINE([HAVE_PTHREAD_PRIO_INHERIT], [1], [Have PTHREAD_PRIO_INHERIT.]) + ax_pthread_prio_inherit_defined=yes + ]) + + CFLAGS="$ax_pthread_save_CFLAGS" + LIBS="$ax_pthread_save_LIBS" + + # More AIX lossage: compile with *_r variant + if test "x$GCC" != "xyes"; then + case $host_os in + aix*) + AS_CASE(["x/$CC"], + [x*/c89|x*/c89_128|x*/c99|x*/c99_128|x*/cc|x*/cc128|x*/xlc|x*/xlc_v6|x*/xlc128|x*/xlc128_v6], + [#handle absolute path differently from PATH based program lookup + AS_CASE(["x$CC"], + [x/*], + [ + AS_IF([AS_EXECUTABLE_P([${CC}_r])],[PTHREAD_CC="${CC}_r"]) + AS_IF([test "x${CXX}" != "x"], [AS_IF([AS_EXECUTABLE_P([${CXX}_r])],[PTHREAD_CXX="${CXX}_r"])]) + ], + [ + AC_CHECK_PROGS([PTHREAD_CC],[${CC}_r],[$CC]) + AS_IF([test "x${CXX}" != "x"], [AC_CHECK_PROGS([PTHREAD_CXX],[${CXX}_r],[$CXX])]) + ] + ) + ]) + ;; + esac + fi +fi + +test -n "$PTHREAD_CC" || PTHREAD_CC="$CC" +test -n "$PTHREAD_CXX" || PTHREAD_CXX="$CXX" + +AC_SUBST([PTHREAD_LIBS]) +AC_SUBST([PTHREAD_CFLAGS]) +AC_SUBST([PTHREAD_CC]) +AC_SUBST([PTHREAD_CXX]) + +# Finally, execute ACTION-IF-FOUND/ACTION-IF-NOT-FOUND: +if test "x$ax_pthread_ok" = "xyes"; then + ifelse([$1],,[AC_DEFINE([HAVE_PTHREAD],[1],[Define if you have POSIX threads libraries and header files.])],[$1]) + : +else + ax_pthread_ok=no + $2 +fi +AC_LANG_POP +])dnl AX_PTHREAD diff --git a/m4/ax_python_devel.m4 b/m4/ax_python_devel.m4 new file mode 100644 index 0000000..935056c --- /dev/null +++ b/m4/ax_python_devel.m4 @@ -0,0 +1,468 @@ +# =========================================================================== +# https://www.gnu.org/software/autoconf-archive/ax_python_devel.html +# =========================================================================== +# +# SYNOPSIS +# +# AX_PYTHON_DEVEL([version[,optional]]) +# +# DESCRIPTION +# +# Note: Defines as a precious variable "PYTHON_VERSION". Don't override it +# in your configure.ac. +# +# This macro checks for Python and tries to get the include path to +# 'Python.h'. It provides the $(PYTHON_CPPFLAGS) and $(PYTHON_LIBS) output +# variables. It also exports $(PYTHON_EXTRA_LIBS) and +# $(PYTHON_EXTRA_LDFLAGS) for embedding Python in your code. +# +# You can search for some particular version of Python by passing a +# parameter to this macro, for example ">= '2.3.1'", or "== '2.4'". Please +# note that you *have* to pass also an operator along with the version to +# match, and pay special attention to the single quotes surrounding the +# version number. Don't use "PYTHON_VERSION" for this: that environment +# variable is declared as precious and thus reserved for the end-user. +# +# By default this will fail if it does not detect a development version of +# python. If you want it to continue, set optional to true, like +# AX_PYTHON_DEVEL([], [true]). The ax_python_devel_found variable will be +# "no" if it fails. +# +# This macro should work for all versions of Python >= 2.1.0. As an end +# user, you can disable the check for the python version by setting the +# PYTHON_NOVERSIONCHECK environment variable to something else than the +# empty string. +# +# If you need to use this macro for an older Python version, please +# contact the authors. We're always open for feedback. +# +# LICENSE +# +# Copyright (c) 2009 Sebastian Huber <sebastian-huber@web.de> +# Copyright (c) 2009 Alan W. Irwin +# Copyright (c) 2009 Rafael Laboissiere <rafael@laboissiere.net> +# Copyright (c) 2009 Andrew Collier +# Copyright (c) 2009 Matteo Settenvini <matteo@member.fsf.org> +# Copyright (c) 2009 Horst Knorr <hk_classes@knoda.org> +# Copyright (c) 2013 Daniel Mullner <muellner@math.stanford.edu> +# +# This program is free software: you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation, either version 3 of the License, or (at your +# option) any later version. +# +# This program 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 General +# Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program. If not, see <https://www.gnu.org/licenses/>. +# +# As a special exception, the respective Autoconf Macro's copyright owner +# gives unlimited permission to copy, distribute and modify the configure +# scripts that are the output of Autoconf when processing the Macro. You +# need not follow the terms of the GNU General Public License when using +# or distributing such scripts, even though portions of the text of the +# Macro appear in them. The GNU General Public License (GPL) does govern +# all other use of the material that constitutes the Autoconf Macro. +# +# This special exception to the GPL applies to versions of the Autoconf +# Macro released by the Autoconf Archive. When you make and distribute a +# modified version of the Autoconf Macro, you may extend this special +# exception to the GPL to apply to your modified version as well. + +#serial 37 + +AU_ALIAS([AC_PYTHON_DEVEL], [AX_PYTHON_DEVEL]) +AC_DEFUN([AX_PYTHON_DEVEL],[ + # Get whether it's optional + if test -z "$2"; then + ax_python_devel_optional=false + else + ax_python_devel_optional=$2 + fi + ax_python_devel_found=yes + + # + # Allow the use of a (user set) custom python version + # + AC_ARG_VAR([PYTHON_VERSION],[The installed Python + version to use, for example '2.3'. This string + will be appended to the Python interpreter + canonical name.]) + + AC_PATH_PROG([PYTHON],[python[$PYTHON_VERSION]]) + if test -z "$PYTHON"; then + AC_MSG_WARN([Cannot find python$PYTHON_VERSION in your system path]) + if ! $ax_python_devel_optional; then + AC_MSG_ERROR([Giving up, python development not available]) + fi + ax_python_devel_found=no + PYTHON_VERSION="" + fi + + if test $ax_python_devel_found = yes; then + # + # Check for a version of Python >= 2.1.0 + # + AC_MSG_CHECKING([for a version of Python >= '2.1.0']) + ac_supports_python_ver=`$PYTHON -c "import sys; \ + ver = sys.version.split ()[[0]]; \ + print (ver >= '2.1.0')"` + if test "$ac_supports_python_ver" != "True"; then + if test -z "$PYTHON_NOVERSIONCHECK"; then + AC_MSG_RESULT([no]) + AC_MSG_WARN([ +This version of the AC@&t@_PYTHON_DEVEL macro +doesn't work properly with versions of Python before +2.1.0. You may need to re-run configure, setting the +variables PYTHON_CPPFLAGS, PYTHON_LIBS, PYTHON_SITE_PKG, +PYTHON_EXTRA_LIBS and PYTHON_EXTRA_LDFLAGS by hand. +Moreover, to disable this check, set PYTHON_NOVERSIONCHECK +to something else than an empty string. +]) + if ! $ax_python_devel_optional; then + AC_MSG_FAILURE([Giving up]) + fi + ax_python_devel_found=no + PYTHON_VERSION="" + else + AC_MSG_RESULT([skip at user request]) + fi + else + AC_MSG_RESULT([yes]) + fi + fi + + if test $ax_python_devel_found = yes; then + # + # If the macro parameter ``version'' is set, honour it. + # A Python shim class, VPy, is used to implement correct version comparisons via + # string expressions, since e.g. a naive textual ">= 2.7.3" won't work for + # Python 2.7.10 (the ".1" being evaluated as less than ".3"). + # + if test -n "$1"; then + AC_MSG_CHECKING([for a version of Python $1]) + cat << EOF > ax_python_devel_vpy.py +class VPy: + def vtup(self, s): + return tuple(map(int, s.strip().replace("rc", ".").split("."))) + def __init__(self): + import sys + self.vpy = tuple(sys.version_info)[[:3]] + def __eq__(self, s): + return self.vpy == self.vtup(s) + def __ne__(self, s): + return self.vpy != self.vtup(s) + def __lt__(self, s): + return self.vpy < self.vtup(s) + def __gt__(self, s): + return self.vpy > self.vtup(s) + def __le__(self, s): + return self.vpy <= self.vtup(s) + def __ge__(self, s): + return self.vpy >= self.vtup(s) +EOF + ac_supports_python_ver=`$PYTHON -c "import ax_python_devel_vpy; \ + ver = ax_python_devel_vpy.VPy(); \ + print (ver $1)"` + rm -rf ax_python_devel_vpy*.py* __pycache__/ax_python_devel_vpy*.py* + if test "$ac_supports_python_ver" = "True"; then + AC_MSG_RESULT([yes]) + else + AC_MSG_RESULT([no]) + AC_MSG_WARN([this package requires Python $1. +If you have it installed, but it isn't the default Python +interpreter in your system path, please pass the PYTHON_VERSION +variable to configure. See ``configure --help'' for reference. +]) + if ! $ax_python_devel_optional; then + AC_MSG_ERROR([Giving up]) + fi + ax_python_devel_found=no + PYTHON_VERSION="" + fi + fi + fi + + if test $ax_python_devel_found = yes; then + # + # Check if you have distutils, else fail + # + AC_MSG_CHECKING([for the sysconfig Python package]) + ac_sysconfig_result=`$PYTHON -c "import sysconfig" 2>&1` + if test $? -eq 0; then + AC_MSG_RESULT([yes]) + IMPORT_SYSCONFIG="import sysconfig" + else + AC_MSG_RESULT([no]) + + AC_MSG_CHECKING([for the distutils Python package]) + ac_sysconfig_result=`$PYTHON -c "from distutils import sysconfig" 2>&1` + if test $? -eq 0; then + AC_MSG_RESULT([yes]) + IMPORT_SYSCONFIG="from distutils import sysconfig" + else + AC_MSG_WARN([cannot import Python module "distutils". +Please check your Python installation. The error was: +$ac_sysconfig_result]) + if ! $ax_python_devel_optional; then + AC_MSG_ERROR([Giving up]) + fi + ax_python_devel_found=no + PYTHON_VERSION="" + fi + fi + fi + + if test $ax_python_devel_found = yes; then + # + # Check for Python include path + # + AC_MSG_CHECKING([for Python include path]) + if test -z "$PYTHON_CPPFLAGS"; then + if test "$IMPORT_SYSCONFIG" = "import sysconfig"; then + # sysconfig module has different functions + python_path=`$PYTHON -c "$IMPORT_SYSCONFIG; \ + print (sysconfig.get_path ('include'));"` + plat_python_path=`$PYTHON -c "$IMPORT_SYSCONFIG; \ + print (sysconfig.get_path ('platinclude'));"` + else + # old distutils way + python_path=`$PYTHON -c "$IMPORT_SYSCONFIG; \ + print (sysconfig.get_python_inc ());"` + plat_python_path=`$PYTHON -c "$IMPORT_SYSCONFIG; \ + print (sysconfig.get_python_inc (plat_specific=1));"` + fi + if test -n "${python_path}"; then + if test "${plat_python_path}" != "${python_path}"; then + python_path="-I$python_path -I$plat_python_path" + else + python_path="-I$python_path" + fi + fi + PYTHON_CPPFLAGS=$python_path + fi + AC_MSG_RESULT([$PYTHON_CPPFLAGS]) + AC_SUBST([PYTHON_CPPFLAGS]) + + # + # Check for Python library path + # + AC_MSG_CHECKING([for Python library path]) + if test -z "$PYTHON_LIBS"; then + # (makes two attempts to ensure we've got a version number + # from the interpreter) + ac_python_version=`cat<<EOD | $PYTHON - + +# join all versioning strings, on some systems +# major/minor numbers could be in different list elements +from sysconfig import * +e = get_config_var('VERSION') +if e is not None: + print(e) +EOD` + + if test -z "$ac_python_version"; then + if test -n "$PYTHON_VERSION"; then + ac_python_version=$PYTHON_VERSION + else + ac_python_version=`$PYTHON -c "import sys; \ + print ("%d.%d" % sys.version_info[[:2]])"` + fi + fi + + # Make the versioning information available to the compiler + AC_DEFINE_UNQUOTED([HAVE_PYTHON], ["$ac_python_version"], + [If available, contains the Python version number currently in use.]) + + # First, the library directory: + ac_python_libdir=`cat<<EOD | $PYTHON - + +# There should be only one +$IMPORT_SYSCONFIG +e = sysconfig.get_config_var('LIBDIR') +if e is not None: + print (e) +EOD` + + # Now, for the library: + ac_python_library=`cat<<EOD | $PYTHON - + +$IMPORT_SYSCONFIG +c = sysconfig.get_config_vars() +if 'LDVERSION' in c: + print ('python'+c[['LDVERSION']]) +else: + print ('python'+c[['VERSION']]) +EOD` + + # This small piece shamelessly adapted from PostgreSQL python macro; + # credits goes to momjian, I think. I'd like to put the right name + # in the credits, if someone can point me in the right direction... ? + # + if test -n "$ac_python_libdir" -a -n "$ac_python_library" + then + # use the official shared library + ac_python_library=`echo "$ac_python_library" | sed "s/^lib//"` + PYTHON_LIBS="-L$ac_python_libdir -l$ac_python_library" + else + # old way: use libpython from python_configdir + ac_python_libdir=`$PYTHON -c \ + "from sysconfig import get_python_lib as f; \ + import os; \ + print (os.path.join(f(plat_specific=1, standard_lib=1), 'config'));"` + PYTHON_LIBS="-L$ac_python_libdir -lpython$ac_python_version" + fi + + if test -z "$PYTHON_LIBS"; then + AC_MSG_WARN([ + Cannot determine location of your Python DSO. Please check it was installed with + dynamic libraries enabled, or try setting PYTHON_LIBS by hand. + ]) + if ! $ax_python_devel_optional; then + AC_MSG_ERROR([Giving up]) + fi + ax_python_devel_found=no + PYTHON_VERSION="" + fi + fi + fi + + if test $ax_python_devel_found = yes; then + AC_MSG_RESULT([$PYTHON_LIBS]) + AC_SUBST([PYTHON_LIBS]) + + # + # Check for site packages + # + AC_MSG_CHECKING([for Python site-packages path]) + if test -z "$PYTHON_SITE_PKG"; then + if test "$IMPORT_SYSCONFIG" = "import sysconfig"; then + PYTHON_SITE_PKG=`$PYTHON -c " +$IMPORT_SYSCONFIG; +if hasattr(sysconfig, 'get_default_scheme'): + scheme = sysconfig.get_default_scheme() +else: + scheme = sysconfig._get_default_scheme() +if scheme == 'posix_local': + # Debian's default scheme installs to /usr/local/ but we want to find headers in /usr/ + scheme = 'posix_prefix' +prefix = '$prefix' +if prefix == 'NONE': + prefix = '$ac_default_prefix' +sitedir = sysconfig.get_path('purelib', scheme, vars={'base': prefix}) +print(sitedir)"` + else + # distutils.sysconfig way + PYTHON_SITE_PKG=`$PYTHON -c "$IMPORT_SYSCONFIG; \ + print (sysconfig.get_python_lib(0,0));"` + fi + fi + AC_MSG_RESULT([$PYTHON_SITE_PKG]) + AC_SUBST([PYTHON_SITE_PKG]) + + # + # Check for platform-specific site packages + # + AC_MSG_CHECKING([for Python platform specific site-packages path]) + if test -z "$PYTHON_PLATFORM_SITE_PKG"; then + if test "$IMPORT_SYSCONFIG" = "import sysconfig"; then + PYTHON_PLATFORM_SITE_PKG=`$PYTHON -c " +$IMPORT_SYSCONFIG; +if hasattr(sysconfig, 'get_default_scheme'): + scheme = sysconfig.get_default_scheme() +else: + scheme = sysconfig._get_default_scheme() +if scheme == 'posix_local': + # Debian's default scheme installs to /usr/local/ but we want to find headers in /usr/ + scheme = 'posix_prefix' +prefix = '$prefix' +if prefix == 'NONE': + prefix = '$ac_default_prefix' +sitedir = sysconfig.get_path('platlib', scheme, vars={'platbase': prefix}) +print(sitedir)"` + else + # distutils.sysconfig way + PYTHON_PLATFORM_SITE_PKG=`$PYTHON -c "$IMPORT_SYSCONFIG; \ + print (sysconfig.get_python_lib(1,0));"` + fi + fi + AC_MSG_RESULT([$PYTHON_PLATFORM_SITE_PKG]) + AC_SUBST([PYTHON_PLATFORM_SITE_PKG]) + + # + # libraries which must be linked in when embedding + # + AC_MSG_CHECKING(python extra libraries) + if test -z "$PYTHON_EXTRA_LIBS"; then + PYTHON_EXTRA_LIBS=`$PYTHON -c "$IMPORT_SYSCONFIG; \ + conf = sysconfig.get_config_var; \ + print (conf('LIBS') + ' ' + conf('SYSLIBS'))"` + fi + AC_MSG_RESULT([$PYTHON_EXTRA_LIBS]) + AC_SUBST(PYTHON_EXTRA_LIBS) + + # + # linking flags needed when embedding + # + AC_MSG_CHECKING(python extra linking flags) + if test -z "$PYTHON_EXTRA_LDFLAGS"; then + PYTHON_EXTRA_LDFLAGS=`$PYTHON -c "$IMPORT_SYSCONFIG; \ + conf = sysconfig.get_config_var; \ + print (conf('LINKFORSHARED'))"` + # Hack for macos, it sticks this in here. + PYTHON_EXTRA_LDFLAGS=`echo $PYTHON_EXTRA_LDFLAGS | sed 's/CoreFoundation.*$/CoreFoundation/'` + fi + AC_MSG_RESULT([$PYTHON_EXTRA_LDFLAGS]) + AC_SUBST(PYTHON_EXTRA_LDFLAGS) + + # + # final check to see if everything compiles alright + # + AC_MSG_CHECKING([consistency of all components of python development environment]) + # save current global flags + ac_save_LIBS="$LIBS" + ac_save_LDFLAGS="$LDFLAGS" + ac_save_CPPFLAGS="$CPPFLAGS" + LIBS="$ac_save_LIBS $PYTHON_LIBS $PYTHON_EXTRA_LIBS" + LDFLAGS="$ac_save_LDFLAGS $PYTHON_EXTRA_LDFLAGS" + CPPFLAGS="$ac_save_CPPFLAGS $PYTHON_CPPFLAGS" + AC_LANG_PUSH([C]) + AC_LINK_IFELSE([ + AC_LANG_PROGRAM([[#include <Python.h>]], + [[Py_Initialize();]]) + ],[pythonexists=yes],[pythonexists=no]) + AC_LANG_POP([C]) + # turn back to default flags + CPPFLAGS="$ac_save_CPPFLAGS" + LIBS="$ac_save_LIBS" + LDFLAGS="$ac_save_LDFLAGS" + + AC_MSG_RESULT([$pythonexists]) + + if test ! "x$pythonexists" = "xyes"; then + AC_MSG_WARN([ + Could not link test program to Python. Maybe the main Python library has been + installed in some non-standard library path. If so, pass it to configure, + via the LIBS environment variable. + Example: ./configure LIBS="-L/usr/non-standard-path/python/lib" + ============================================================================ + ERROR! + You probably have to install the development version of the Python package + for your distribution. The exact name of this package varies among them. + ============================================================================ + ]) + if ! $ax_python_devel_optional; then + AC_MSG_ERROR([Giving up]) + fi + ax_python_devel_found=no + PYTHON_VERSION="" + fi + fi + + # + # all done! + # +]) diff --git a/m4/cython_python.m4 b/m4/cython_python.m4 new file mode 100644 index 0000000..5b4a041 --- /dev/null +++ b/m4/cython_python.m4 @@ -0,0 +1,7 @@ +AC_DEFUN([CYTHON_PYTHON],[ + AC_REQUIRE([AC_PROG_CYTHON]) + AC_REQUIRE([AX_PYTHON_DEVEL]) + test "x$1" != "xno" || cython_shadow=" -noproxy" + AC_SUBST([CYTHON_PYTHON_OPT],[-python$cython_shadow]) + AC_SUBST([CYTHON_PYTHON_CPPFLAGS],[$PYTHON_CPPFLAGS]) +]) diff --git a/plutil/CMakeLists.txt b/plutil/CMakeLists.txt deleted file mode 100644 index 149d68f..0000000 --- a/plutil/CMakeLists.txt +++ /dev/null @@ -1,9 +0,0 @@ - -SET(plutil_SRC - plutil.c) - -ADD_EXECUTABLE(plutil ${plutil_SRC}) -TARGET_LINK_LIBRARIES(plutil plist) -SET_TARGET_PROPERTIES( plutil PROPERTIES VERSION ${PLUTIL_VERSION} ) - -INSTALL( TARGETS plutil RUNTIME DESTINATION bin COMPONENT plutil )
\ No newline at end of file diff --git a/plutil/plutil.c b/plutil/plutil.c deleted file mode 100644 index 88df080..0000000 --- a/plutil/plutil.c +++ /dev/null @@ -1,164 +0,0 @@ -/* - * plutil.C - * source for plist convertion tool - * - * 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 - * 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 "plist/plist.h" -#include "plutil.h" - -#include <stdio.h> -#include <stdlib.h> -#include <string.h> -#include <sys/stat.h> - -#ifdef _MSC_VER -#pragma warning(disable:4996) -#endif - - -int main(int argc, char *argv[]) -{ - FILE *iplist = NULL; - plist_t root_node = NULL; - char *plist_out = NULL; - uint32_t size = 0; - int read_size = 0; - char *plist_entire = NULL; - struct stat *filestats = (struct stat *) malloc(sizeof(struct stat)); - Options *options = parse_arguments(argc, argv); - - if (!options) - { - print_usage(); - free(filestats); - return 0; - } - //read input file - iplist = fopen(options->in_file, "rb"); - if (!iplist) - return 1; - stat(options->in_file, filestats); - plist_entire = (char *) malloc(sizeof(char) * (filestats->st_size + 1)); - read_size = fread(plist_entire, sizeof(char), filestats->st_size, iplist); - fclose(iplist); - - - //convert one format to another - - - if (memcmp(plist_entire, "bplist00", 8) == 0) - { - plist_from_bin(plist_entire, read_size, &root_node); - plist_to_xml(root_node, &plist_out, &size); - } - else - { - plist_from_xml(plist_entire, read_size, &root_node); - plist_to_bin(root_node, &plist_out, &size); - } - plist_free(root_node); - free(plist_entire); - free(filestats); - - if (plist_out) - { - if (options->out_file != NULL) - { - FILE *oplist = fopen(options->out_file, "wb"); - if (!oplist) - return 1; - fwrite(plist_out, size, sizeof(char), oplist); - fclose(oplist); - } - //if no output file specified, write to stdout - else - fwrite(plist_out, size, sizeof(char), stdout); - - free(plist_out); - } - else - printf("ERROR\n"); - - free(options); - return 0; -} - -Options *parse_arguments(int argc, char *argv[]) -{ - int i = 0; - - Options *options = (Options *) malloc(sizeof(Options)); - memset(options, 0, sizeof(Options)); - - for (i = 1; i < argc; i++) - { - if (!strcmp(argv[i], "--infile") || !strcmp(argv[i], "-i")) - { - if ((i + 1) == argc) - { - free(options); - return NULL; - } - options->in_file = argv[i + 1]; - i++; - continue; - } - - if (!strcmp(argv[i], "--outfile") || !strcmp(argv[i], "-o")) - { - if ((i + 1) == argc) - { - free(options); - return NULL; - } - options->out_file = argv[i + 1]; - i++; - continue; - } - - if (!strcmp(argv[i], "--debug") || !strcmp(argv[i], "-d") || !strcmp(argv[i], "-v")) - { - options->debug = 1; - } - - if (!strcmp(argv[i], "--help") || !strcmp(argv[i], "-h")) - { - free(options); - return NULL; - } - } - - if (!options->in_file /*|| !options->out_file */ ) - { - free(options); - return NULL; - } - - return options; -} - -void print_usage() -{ - printf("Usage: plistutil -i|--infile in_file.plist -o|--outfile out_file.plist [--debug]\n"); - printf("\n"); - printf("\t-i or --infile: The file to read in.\n"); - printf("\t-o or --outfile: The file to convert to.\n"); - printf("\t-d, -v or --debug: Provide extended debug information.\n\n"); -} 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); } diff --git a/swig/CMakeLists.txt b/swig/CMakeLists.txt deleted file mode 100644 index 379cb95..0000000 --- a/swig/CMakeLists.txt +++ /dev/null @@ -1,24 +0,0 @@ -INCLUDE( ${SWIG_USE_FILE} ) - -SET(CMAKE_SWIG_FLAGS -Werror -Wall -modern) - -INCLUDE_DIRECTORIES( ${PYTHON_INCLUDE_PATH} ) - -SET_SOURCE_FILES_PROPERTIES(plist.i PROPERTIES CPLUSPLUS ON) -SWIG_ADD_MODULE( plist python plist.i ) -SWIG_LINK_LIBRARIES( plist plist plist++ ${PYTHON_LIBRARIES} ) - -EXEC_PROGRAM("${PYTHON_EXECUTABLE}" - ARGS "-c 'try:\n import distutils.sysconfig; print distutils.sysconfig.get_python_lib(plat_specific=1)\nexcept: pass\n'" - OUTPUT_VARIABLE DISTUTILS_PYTHON_ILIBRARY_PATH - ) - -INSTALL( FILES ${CMAKE_CURRENT_BINARY_DIR}/_plist${CMAKE_SHARED_MODULE_SUFFIX} - DESTINATION ${DISTUTILS_PYTHON_ILIBRARY_PATH}/plist/ ) -INSTALL( FILES ${CMAKE_CURRENT_BINARY_DIR}/plist.py - DESTINATION ${DISTUTILS_PYTHON_ILIBRARY_PATH}/plist/ ) -INSTALL( FILES ${CMAKE_CURRENT_SOURCE_DIR}/__init__.py - DESTINATION ${DISTUTILS_PYTHON_ILIBRARY_PATH}/plist/ ) -INSTALL( FILES ${CMAKE_CURRENT_SOURCE_DIR}/plist.i - DESTINATION include/plist/swig COMPONENT dev) - diff --git a/swig/__init__.py b/swig/__init__.py deleted file mode 100644 index f4c0dbb..0000000 --- a/swig/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -# -*- coding: utf-8 -*- - -from plist import * diff --git a/swig/plist.i b/swig/plist.i deleted file mode 100644 index 7755cc5..0000000 --- a/swig/plist.i +++ /dev/null @@ -1,273 +0,0 @@ - /* swig.i */ - %module plist - %feature("autodoc", "1"); - %{ - /* Includes the header in the wrapper code */ - #include <plist/plist++.h> - #include <cstddef> - %} - -%include "std_string.i" -%include "stdint.i" - -%typemap(out) std::vector<char> { - $result = SWIG_FromCharPtrAndSize((const char*)&($1[0]),(size_t)($1.size())); -} - -%typemap(in) (const std::vector<char>&) -{ - char* buffer = NULL; - size_t length = 0; - SWIG_AsCharPtrAndSize($input, &buffer, &length, NULL); - $1 = new std::vector<char>(buffer, buffer + length - 1); -} - -#if SWIGPYTHON -//for datetime in python -%{ -#include <ctime> -#include <datetime.h> -%} - -%typemap(typecheck,precedence=SWIG_TYPECHECK_POINTER) timeval { - PyDateTime_IMPORT; - $1 = PyDateTime_Check($input) ? 1 : 0; -} - -%typemap(out) timeval { - struct tm* t = gmtime ( &$1.tv_sec ); - if (t) - { - PyDateTime_IMPORT; - $result = PyDateTime_FromDateAndTime(t->tm_year+1900, t->tm_mon+1, t->tm_mday, t->tm_hour, t->tm_min, t->tm_sec, $1.tv_usec); - } -} - -%typemap(in) (timeval t) -{ - PyDateTime_IMPORT; - if (!PyDateTime_Check($input)) { - PyErr_SetString(PyExc_ValueError,"Expected a datetime"); - return NULL; - } - struct tm t = { - PyDateTime_DATE_GET_SECOND($input), - PyDateTime_DATE_GET_MINUTE($input), - PyDateTime_DATE_GET_HOUR($input), - PyDateTime_GET_DAY($input), - PyDateTime_GET_MONTH($input)-1, - PyDateTime_GET_YEAR($input)-1900, - 0,0,0 - }; - timeval ret = {(int)mktime(&t), PyDateTime_DATE_GET_MICROSECOND($input)}; - $1 = ret; -} -#endif - -%apply SWIGTYPE *DYNAMIC { PList::Node* }; -%apply SWIGTYPE *DYNAMIC { PList::Structure* }; - -%{ -static swig_type_info *Node_dynamic(void **ptr) -{ - PList::Node* node = dynamic_cast<PList::Node *>((PList::Node *) *ptr); - if (node) - { - plist_type type = node->GetType(); - switch(type) - { - case PLIST_DICT: - *ptr = dynamic_cast<PList::Dictionary *>(node); - return SWIGTYPE_p_PList__Dictionary; - case PLIST_ARRAY: - *ptr = dynamic_cast<PList::Array *>(node); - return SWIGTYPE_p_PList__Array; - case PLIST_BOOLEAN: - *ptr = dynamic_cast<PList::Boolean *>(node); - return SWIGTYPE_p_PList__Boolean; - case PLIST_UINT: - *ptr = dynamic_cast<PList::Integer *>(node); - return SWIGTYPE_p_PList__Integer; - case PLIST_REAL: - *ptr = dynamic_cast<PList::Real *>(node); - return SWIGTYPE_p_PList__Real; - case PLIST_STRING: - *ptr = dynamic_cast<PList::String *>(node); - return SWIGTYPE_p_PList__String; - case PLIST_DATE: - *ptr = dynamic_cast<PList::Date *>(node); - return SWIGTYPE_p_PList__Date; - case PLIST_DATA: - *ptr = dynamic_cast<PList::Data *>(node); - return SWIGTYPE_p_PList__Data; - default: - break; - } - } - return 0; -} -%} - -// Register the above casting function -DYNAMIC_CAST(SWIGTYPE_p_PList__Node, Node_dynamic); -DYNAMIC_CAST(SWIGTYPE_p_PList__Structure, Node_dynamic); - -%include "std_map.i" -// Instantiate templates used by example -namespace std { - %template(PairStringNodePtr) std::pair<string, PList::Node*>; - %template(MapStringNodePtr) map<string,PList::Node*>; -} - -#if SWIGPYTHON -%rename(__assign__) *::operator=; -%rename(__getitem__) *::operator[]; -%rename(__delitem__) *::Remove; -%rename(__setitem__) PList::Dictionary::Insert; -%rename(__deepcopy__) *::Clone; -%rename(__len__) *::GetSize; -%rename(get_type) *::GetType; -%rename(set_value) *::SetValue; -%rename(get_value) *::GetValue; -%rename(to_xml) *::ToXml; -%rename(to_bin) *::ToBin; -%rename(from_xml) *::FromXml; -%rename(from_bin) *::FromBin; -%rename(append) *::Append; -%rename(insert) PList::Array::Insert; -#endif - -%ignore GetPlist(); -%ignore Boolean(plist_t); -%ignore Integer(plist_t); -%ignore Real(plist_t); -%ignore String(plist_t); -%ignore Data(plist_t); -%ignore Date(plist_t); -%ignore Array(plist_t); -%ignore Dictionary(plist_t); -%ignore Begin(); -%ignore End(); -%ignore Find(); - -%include <plist/Node.h> -%include <plist/Boolean.h> -%include <plist/Integer.h> -%include <plist/Real.h> -%include <plist/String.h> -%include <plist/Data.h> -%include <plist/Date.h> -%include <plist/Structure.h> -%include <plist/Array.h> -%include <plist/Dictionary.h> - -typedef enum { - PLIST_BOOLEAN, - PLIST_UINT, - PLIST_REAL, - PLIST_STRING, - PLIST_ARRAY, - PLIST_DICT, - PLIST_DATE, - PLIST_DATA, - PLIST_KEY, - PLIST_NONE -} plist_type; - -#if SWIGPYTHON - -#if SWIG_VERSION <= 0x010336 -#define SwigPyIterator PySwigIterator -#endif - -%extend PList::Dictionary { - - %newobject key_iterator(PyObject **PYTHON_SELF); - swig::SwigPyIterator* key_iterator(PyObject **PYTHON_SELF) { - return swig::make_output_key_iterator(self->Begin(), self->Begin(), self->End(), *PYTHON_SELF); - } - - %newobject value_iterator(PyObject **PYTHON_SELF); - swig::SwigPyIterator* value_iterator(PyObject **PYTHON_SELF) { - return swig::make_output_value_iterator(self->Begin(), self->Begin(), self->End(), *PYTHON_SELF); - } - - iterator iteritems() - { - return self->Begin(); - } - - bool has_key(const std::string& key) const { - PList::Dictionary* dict = const_cast<PList::Dictionary*>(self); - PList::Dictionary::iterator i = dict->Find(key); - return i != dict->End(); - } - - PyObject* keys() { - uint32_t size = self->GetSize(); - int pysize = (size <= (uint32_t) INT_MAX) ? (int) size : -1; - if (pysize < 0) { - SWIG_PYTHON_THREAD_BEGIN_BLOCK; - PyErr_SetString(PyExc_OverflowError, - "map size not valid in python"); - SWIG_PYTHON_THREAD_END_BLOCK; - return NULL; - } - PyObject* keyList = PyList_New(pysize); - PList::Dictionary::iterator i = self->Begin(); - for (int j = 0; j < pysize; ++i, ++j) { - PyList_SET_ITEM(keyList, j, swig::from(i->first)); - } - return keyList; - } - - PyObject* values() { - uint32_t size = self->GetSize(); - int pysize = (size <= (uint32_t) INT_MAX) ? (int) size : -1; - if (pysize < 0) { - SWIG_PYTHON_THREAD_BEGIN_BLOCK; - PyErr_SetString(PyExc_OverflowError, - "map size not valid in python"); - SWIG_PYTHON_THREAD_END_BLOCK; - return NULL; - } - PyObject* valList = PyList_New(pysize); - PList::Dictionary::iterator i = self->Begin(); - for (int j = 0; j < pysize; ++i, ++j) { - PList::Node *second = i->second; - PyObject *down = SWIG_NewPointerObj(SWIG_as_voidptr(second), SWIG_TypeDynamicCast(SWIGTYPE_p_PList__Node, SWIG_as_voidptrptr(&second)), 0 | 0 ); - PyList_SET_ITEM(valList, j, down); - } - return valList; - } - - PyObject* items() { - uint32_t size = self->GetSize(); - int pysize = (size <= (uint32_t) INT_MAX) ? (int) size : -1; - if (pysize < 0) { - SWIG_PYTHON_THREAD_BEGIN_BLOCK; - PyErr_SetString(PyExc_OverflowError, - "map size not valid in python"); - SWIG_PYTHON_THREAD_END_BLOCK; - return NULL; - } - PyObject* itemList = PyList_New(pysize); - PList::Dictionary::iterator i = self->Begin(); - for (int j = 0; j < pysize; ++i, ++j) { - PyObject *item = PyTuple_New(2); - PList::Node *second = i->second; - PyObject *down = SWIG_NewPointerObj(SWIG_as_voidptr(second), SWIG_TypeDynamicCast(SWIGTYPE_p_PList__Node, SWIG_as_voidptrptr(&second)), 0 | 0 ); - PyTuple_SetItem(item, 0, swig::from(i->first)); - PyTuple_SetItem(item, 1, down); - PyList_SET_ITEM(itemList, j, item); - } - return itemList; - } - - %pythoncode {def __iter__(self): return self.key_iterator()} - %pythoncode {def iterkeys(self): return self.key_iterator()} - %pythoncode {def itervalues(self): return self.value_iterator()} -} - -#undef SwigPyIterator -#endif diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt deleted file mode 100644 index 1b3d2f7..0000000 --- a/test/CMakeLists.txt +++ /dev/null @@ -1,29 +0,0 @@ - -#build the test executable -INCLUDE_DIRECTORIES( ${CMAKE_SOURCE_DIR}/libcnary/include ) - -SET(plist_test_SRC - plist_test.c) -SET(plist_cmp_SRC - plist_cmp.c) -ADD_EXECUTABLE(plist_test ${plist_test_SRC}) -TARGET_LINK_LIBRARIES(plist_test plist) -ADD_EXECUTABLE(plist_cmp ${plist_cmp_SRC}) -TARGET_LINK_LIBRARIES(plist_cmp plist) - -INCLUDE(CTest) - -SET(TEST_DATA_DIR ${CMAKE_CURRENT_SOURCE_DIR}) -ADD_TEST(Empty plist_test ${TEST_DATA_DIR}/data/1.plist) -ADD_TEST(Small plist_test ${TEST_DATA_DIR}/data/2.plist) -ADD_TEST(Medium plist_test ${TEST_DATA_DIR}/data/3.plist) -ADD_TEST(Large plist_test ${TEST_DATA_DIR}/data/4.plist) -ADD_TEST(Huge plist_test ${TEST_DATA_DIR}/data/5.plist) -ADD_TEST(Big_Array plist_test ${TEST_DATA_DIR}/data/6.plist) - -ADD_TEST(EmptyCmp plist_cmp ${TEST_DATA_DIR}/data/1.plist ${TEST_DATA_DIR}/data/1.plist.out) -ADD_TEST(SmallCmp plist_cmp ${TEST_DATA_DIR}/data/2.plist ${TEST_DATA_DIR}/data/2.plist.out) -ADD_TEST(MediumCmp plist_cmp ${TEST_DATA_DIR}/data/3.plist ${TEST_DATA_DIR}/data/3.plist.out) -ADD_TEST(LargeCmp plist_cmp ${TEST_DATA_DIR}/data/4.plist ${TEST_DATA_DIR}/data/4.plist.out) -ADD_TEST(HugeCmp plist_cmp ${TEST_DATA_DIR}/data/5.plist ${TEST_DATA_DIR}/data/5.plist.out) -ADD_TEST(Big_ArrayCmp plist_cmp ${TEST_DATA_DIR}/data/6.plist ${TEST_DATA_DIR}/data/6.plist.out) diff --git a/test/Makefile.am b/test/Makefile.am new file mode 100644 index 0000000..f9f21e4 --- /dev/null +++ b/test/Makefile.am @@ -0,0 +1,146 @@ +AM_CFLAGS = \ + $(GLOBAL_CFLAGS) \ + -I$(top_srcdir)/include \ + -I$(top_srcdir)/libcnary/include +AM_CPPFLAGS = $(AM_CFLAGS) + +AM_LDFLAGS = + +noinst_PROGRAMS = \ + plist_cmp \ + plist_test \ + plist_test++ \ + integer_set_test \ + plist_btest \ + plist_jtest \ + plist_otest \ + xml_behavior_test + +plist_cmp_SOURCES = plist_cmp.c +plist_cmp_LDADD = \ + $(top_builddir)/src/libplist-2.0.la \ + $(top_builddir)/libcnary/libcnary.la + +plist_test___SOURCES = plist_test++.cpp +plist_test___LDADD = $(top_builddir)/src/libplist++-2.0.la + +plist_test_SOURCES = plist_test.c +plist_test_LDADD = $(top_builddir)/src/libplist-2.0.la + +integer_set_test_SOURCES = integer_set.c +integer_set_test_LDADD = $(top_builddir)/src/libplist-2.0.la + +plist_btest_SOURCES = plist_btest.c +plist_btest_LDADD = $(top_builddir)/src/libplist-2.0.la + +plist_jtest_SOURCES = plist_jtest.c +plist_jtest_LDADD = $(top_builddir)/src/libplist-2.0.la + +plist_otest_SOURCES = plist_otest.c +plist_otest_LDADD = $(top_builddir)/src/libplist-2.0.la + +xml_behavior_test_SOURCES = xml_behavior_test.c +xml_behavior_test_LDADD = $(top_builddir)/src/libplist-2.0.la + +TESTS = \ + empty.test \ + small.test \ + medium.test \ + large.test \ + huge.test \ + bigarray.test \ + empty++.test \ + small++.test \ + medium++.test \ + large++.test \ + huge++.test \ + bigarray++.test \ + dates.test \ + timezone1.test \ + timezone2.test \ + signedunsigned1.test \ + signedunsigned2.test \ + signedunsigned3.test \ + hex.test \ + order.test \ + recursion.test \ + entities.test \ + empty_keys.test \ + amp.test \ + invalid_tag.test \ + cdata.test \ + offsetsize.test \ + refsize.test \ + malformed_dict.test \ + uid.test \ + integer_set.test \ + json1.test \ + json2.test \ + json3.test \ + json-invalid-types.test \ + json-int64-min-max.test \ + ostep1.test \ + ostep2.test \ + ostep-strings.test \ + ostep-comments.test \ + ostep-invalid-types.test \ + xml_behavior.test + +EXTRA_DIST = \ + $(TESTS) \ + data/1.plist \ + data/2.plist \ + data/3.plist \ + data/4.plist \ + data/5.plist \ + data/6.plist \ + data/7.plist \ + data/amp.plist \ + data/cdata.plist \ + data/dictref1byte.bplist \ + data/dictref2bytes.bplist \ + data/dictref3bytes.bplist \ + data/dictref4bytes.bplist \ + data/dictref5bytes.bplist \ + data/dictref6bytes.bplist \ + data/dictref7bytes.bplist \ + data/dictref8bytes.bplist \ + data/empty_keys.plist \ + data/entities.plist \ + data/hex.plist \ + data/invalid_tag.plist \ + data/malformed_dict.bplist \ + data/off1byte.bplist \ + data/off2bytes.bplist \ + data/off3bytes.bplist \ + data/off4bytes.bplist \ + data/off5bytes.bplist \ + data/off6bytes.bplist \ + data/off7bytes.bplist \ + data/off8bytes.bplist \ + data/offxml.plist \ + data/order.bplist \ + data/order.plist \ + data/recursion.bplist \ + data/signed.bplist \ + data/signed.plist \ + data/signedunsigned.bplist \ + data/signedunsigned.plist \ + data/unsigned.bplist \ + data/unsigned.plist \ + data/uid.bplist \ + data/data.bplist \ + data/j1.json \ + data/j2.json \ + data/int64_min_max.json \ + data/o1.ostep \ + data/o2.ostep \ + data/o3.ostep \ + data/test.strings + +TESTS_ENVIRONMENT = \ + top_srcdir=$(top_srcdir) \ + top_builddir=$(top_builddir) + +clean-local: + if test -d $(top_builddir)/test/data; then cd $(top_builddir)/test/data && rm -f *.out *.bin *.xml; fi diff --git a/test/amp.test b/test/amp.test new file mode 100755 index 0000000..3678f27 --- /dev/null +++ b/test/amp.test @@ -0,0 +1,16 @@ +## -*- sh -*- + +DATASRC=$top_srcdir/test/data +TESTFILE=amp.plist +DATAIN0=$DATASRC/$TESTFILE +DATAOUT0=$top_builddir/test/data/amp.test.out + +rm -rf $DATAOUT0 +$top_builddir/tools/plistutil -i $DATAIN0 -o $DATAOUT0 + +# test succeeds if plistutil fails +if [ $? -eq 0 ]; then + exit 1 +else + exit 0 +fi diff --git a/test/bigarray++.test b/test/bigarray++.test new file mode 100755 index 0000000..78f38ef --- /dev/null +++ b/test/bigarray++.test @@ -0,0 +1,15 @@ +## -*- sh -*- + +DATASRC=$top_srcdir/test/data +DATAOUT=$top_builddir/test/data +TESTFILE=6.plist + +if ! test -d "$DATAOUT"; then + mkdir -p $DATAOUT +fi + +echo "Converting" +$top_builddir/test/plist_test++ $DATASRC/$TESTFILE $DATAOUT/bigarray++.test.out + +echo "Comparing" +$top_builddir/test/plist_cmp $DATASRC/$TESTFILE $DATAOUT/bigarray++.test.out diff --git a/test/bigarray.test b/test/bigarray.test new file mode 100755 index 0000000..1b88d43 --- /dev/null +++ b/test/bigarray.test @@ -0,0 +1,15 @@ +## -*- sh -*- + +DATASRC=$top_srcdir/test/data +DATAOUT=$top_builddir/test/data +TESTFILE=6.plist + +if ! test -d "$DATAOUT"; then + mkdir -p $DATAOUT +fi + +echo "Converting" +$top_builddir/test/plist_test $DATASRC/$TESTFILE $DATAOUT/bigarray.test.out + +echo "Comparing" +$top_builddir/test/plist_cmp $DATASRC/$TESTFILE $DATAOUT/bigarray.test.out diff --git a/test/cdata.test b/test/cdata.test new file mode 100755 index 0000000..b4f3ed2 --- /dev/null +++ b/test/cdata.test @@ -0,0 +1,12 @@ +## -*- sh -*- + +set -e + +DATASRC=$top_srcdir/test/data +TESTFILE=cdata.plist +DATAIN0=$DATASRC/$TESTFILE +DATAOUT0=$top_builddir/test/data/cdata.test.bin + +$top_builddir/tools/plistutil -i $DATAIN0 -o $DATAOUT0 + +$top_builddir/test/plist_cmp $DATAIN0 $DATAOUT0 diff --git a/test/data/1.plist b/test/data/1.plist index c7135e9..82a112b 100644 --- a/test/data/1.plist +++ b/test/data/1.plist @@ -16,20 +16,20 @@ <string></string> <string></string> </array> + <key>Keys & "entities"</key> + <string/> <key>Boolean</key> <false/> <key>Another Boolean</key> <true/> <key>Some Int</key> - <integer></integer> + <integer>0</integer> <key>Some Real</key> - <real></real> + <real>1e4</real> <key>Some Date</key> - <date></date> + <date>1970-01-01T00:00:00Z</date> <key>Some Data</key> <data> </data> - <key>Some Entities</key> - <string>hellow world & others <nodes> are "fun!?'</string> </dict> </plist> diff --git a/test/data/2.plist b/test/data/2.plist index 169b822..7c62067 100644 --- a/test/data/2.plist +++ b/test/data/2.plist @@ -16,6 +16,8 @@ <string>jÄ™zyk polski</string> <string>हिनà¥à¤¦à¥€</string> </array> + <key>Keys & "entities"</key> + <string>hellow world & others <nodes> are "fun!?'</string> <key>Boolean</key> <false/> <key>Another Boolean</key> diff --git a/test/data/3.plist b/test/data/3.plist index 6d16191..249e7ce 100644 --- a/test/data/3.plist +++ b/test/data/3.plist @@ -16,6 +16,8 @@ <string>jÄ™zyk polski</string> <string>हिनà¥à¤¦à¥€</string> </array> + <key>Keys & "entities"</key> + <string>hellow world & others <nodes> are "fun!?'</string> <key>Boolean</key> <false/> <key>Another Boolean</key> diff --git a/test/data/4.plist b/test/data/4.plist index de7c488..a04a292 100644 --- a/test/data/4.plist +++ b/test/data/4.plist @@ -16,6 +16,8 @@ <string>jÄ™zyk polski</string> <string>हिनà¥à¤¦à¥€</string> </array> + <key>Keys & "entities"</key> + <string>hellow world & others <nodes> are "fun!?'</string> <key>Boolean</key> <false/> <key>Another Boolean</key> diff --git a/test/data/5.plist b/test/data/5.plist index 647c648..9dc9109 100644 --- a/test/data/5.plist +++ b/test/data/5.plist @@ -16,6 +16,8 @@ <string>jÄ™zyk polski</string> <string>हिनà¥à¤¦à¥€</string> </array> + <key>Keys & "entities"</key> + <string>hellow world & others <nodes> are "fun!?'</string> <key>Boolean</key> <false/> <key>Another Boolean</key> diff --git a/test/data/7.plist b/test/data/7.plist new file mode 100644 index 0000000..d8b441d --- /dev/null +++ b/test/data/7.plist @@ -0,0 +1,14 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> +<plist version="1.0"> +<dict> + <key>Time1</key> + <date>2010-11-12T13:14:15Z</date> + <key>Time2</key> + <date>2008-07-06T05:04:03Z</date> + <key>Time3</key> + <date>1869-01-03T08:16:32Z</date> + <key>Time4</key> + <date>2199-08-20T01:10:11Z</date> +</dict> +</plist> diff --git a/test/data/amp.plist b/test/data/amp.plist new file mode 100644 index 0000000..ce9e7ac --- /dev/null +++ b/test/data/amp.plist @@ -0,0 +1,6 @@ +<plist version="1.0"> +<dict> + <key>test&</key> + <string>value</string> +</dict> +</plist> diff --git a/test/data/cdata.plist b/test/data/cdata.plist new file mode 100644 index 0000000..dcd015f --- /dev/null +++ b/test/data/cdata.plist @@ -0,0 +1,6 @@ +<plist version="1.0"> +<dict> + <key><![CDATA[cdata&key]]></key> + <string>string with cdata content: <![CDATA[ !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~]]> !!!</string> +</dict> +</plist> diff --git a/test/data/data.bplist b/test/data/data.bplist Binary files differnew file mode 100644 index 0000000..955993f --- /dev/null +++ b/test/data/data.bplist diff --git a/test/data/dictref1byte.bplist b/test/data/dictref1byte.bplist Binary files differnew file mode 100644 index 0000000..c33d70b --- /dev/null +++ b/test/data/dictref1byte.bplist diff --git a/test/data/dictref2bytes.bplist b/test/data/dictref2bytes.bplist Binary files differnew file mode 100644 index 0000000..2a5225f --- /dev/null +++ b/test/data/dictref2bytes.bplist diff --git a/test/data/dictref3bytes.bplist b/test/data/dictref3bytes.bplist Binary files differnew file mode 100644 index 0000000..fb34258 --- /dev/null +++ b/test/data/dictref3bytes.bplist diff --git a/test/data/dictref4bytes.bplist b/test/data/dictref4bytes.bplist Binary files differnew file mode 100644 index 0000000..5845107 --- /dev/null +++ b/test/data/dictref4bytes.bplist diff --git a/test/data/dictref5bytes.bplist b/test/data/dictref5bytes.bplist Binary files differnew file mode 100644 index 0000000..6ced9de --- /dev/null +++ b/test/data/dictref5bytes.bplist diff --git a/test/data/dictref6bytes.bplist b/test/data/dictref6bytes.bplist Binary files differnew file mode 100644 index 0000000..2ed5f34 --- /dev/null +++ b/test/data/dictref6bytes.bplist diff --git a/test/data/dictref7bytes.bplist b/test/data/dictref7bytes.bplist Binary files differnew file mode 100644 index 0000000..cbb9368 --- /dev/null +++ b/test/data/dictref7bytes.bplist diff --git a/test/data/dictref8bytes.bplist b/test/data/dictref8bytes.bplist Binary files differnew file mode 100644 index 0000000..ffb4292 --- /dev/null +++ b/test/data/dictref8bytes.bplist diff --git a/test/data/empty_keys.plist b/test/data/empty_keys.plist new file mode 100644 index 0000000..a0107b4 --- /dev/null +++ b/test/data/empty_keys.plist @@ -0,0 +1,8 @@ +<plist version="1.0"> +<dict> + <key></key> + <string>empty key</string> + <key><!-- comment --></key> + <string>empty key with comment</string> +</dict> +</plist> diff --git a/test/data/entities.plist b/test/data/entities.plist new file mode 100644 index 0000000..d7655d6 --- /dev/null +++ b/test/data/entities.plist @@ -0,0 +1,57 @@ +<plist version="1.0"> +<dict> + <key>some test entitites</key> + <dict> + <key>copyright</key> + <string>©</string> + <key>multiple entitites</key> + <string>⼠⼡⼢</string> + <key>multiple entitites with characters inbetween</key> + <string>a⼣b⼤c⼥d</string> + </dict> + <key>one byte entity</key> + <dict> + <key>one byte entity</key> + <string>@</string> + <key>one byte entity with text after</key> + <string>@z</string> + <key>one byte entity with text before</key> + <string>a@</string> + <key>one byte entity with text before & after</key> + <string>a@z</string> + </dict> + <key>two-byte utf-8</key> + <dict> + <key>two-byte utf-8</key> + <string>Ä</string> + <key>two-byte utf-8 with text after</key> + <string>Ëyz</string> + <key>two-byte utf-8 with text before</key> + <string>abÖ</string> + <key>two-byte utf-8 with text before & after</key> + <string>abÜyz</string> + </dict> + <key>three byte utf-8</key> + <dict> + <key>three byte utf-8</key> + <string>¢</string> + <key>three byte utf-8 with text after</key> + <string>£xyz</string> + <key>three byte utf-8 with text before</key> + <string>abc¥</string> + <key>three byte utf-8 with text before & after</key> + <string>abc₩xyz</string> + </dict> + <key>four byte utf-8</key> + <dict> + <key>four byte utf-8</key> + <string>𝕢</string> + <key>four byte utf-8 with text after</key> + <string>𝕣wxyz</string> + <key>four byte utf-8 with text before</key> + <string>abcd𝕤</string> + <key>four byte utf-8 with text before & after</key> + <string>abcd𝕥wxyz</string> + </dict> +</dict> +</plist> diff --git a/test/data/hex.plist b/test/data/hex.plist new file mode 100644 index 0000000..ae2d2b0 --- /dev/null +++ b/test/data/hex.plist @@ -0,0 +1,5 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> +<plist version="1.0"> +<integer>0x73709551615</integer> +</plist> diff --git a/test/data/int64_min_max.json b/test/data/int64_min_max.json new file mode 100644 index 0000000..7707090 --- /dev/null +++ b/test/data/int64_min_max.json @@ -0,0 +1 @@ +{"INT64_MIN":-9223372036854775808,"INT64_MAX":9223372036854775807}
\ No newline at end of file diff --git a/test/data/invalid_tag.plist b/test/data/invalid_tag.plist new file mode 100644 index 0000000..feb770f --- /dev/null +++ b/test/data/invalid_tag.plist @@ -0,0 +1,6 @@ +<plist version="1.0"> +<dict> + <key>test<!test></key> + <string>value</string> +</dict> +</plist> diff --git a/test/data/j1.json b/test/data/j1.json new file mode 100644 index 0000000..2fe564d --- /dev/null +++ b/test/data/j1.json @@ -0,0 +1 @@ +{"test":[1,1],"foo":[[-1337],[1],[1],[1],[[1],[1],[1],[1],[[1],[1],[1],[1]]]],"more":{"a":"yo","b":[{"c":0.25},{"a":"yo","b":[{"c":0.25},{"a":"yo","b":[{"c":-0.25}]}]}]}} diff --git a/test/data/j2.json b/test/data/j2.json new file mode 100644 index 0000000..9d1210e --- /dev/null +++ b/test/data/j2.json @@ -0,0 +1 @@ +{"Some ASCII string":"Test ASCII String","Some UTF8 strings":["à éèçù","日本語","汉è¯/漢語","한êµì–´/ì¡°ì„ ë§","руÑÑкий Ñзык","الْعَرَبيّة","עִבְרִית","jÄ™zyk polski","हिनà¥à¤¦à¥€"],"Keys & \"entities\"":"hellow world & others <nodes> are \"fun!?'","Boolean":false,"Another Boolean":true,"Some Int":32434543632,"Some String with Unicode entity":"Yeah check this: \u1234 !!!"}
\ No newline at end of file diff --git a/test/data/malformed_dict.bplist b/test/data/malformed_dict.bplist Binary files differnew file mode 100644 index 0000000..0de4cd7 --- /dev/null +++ b/test/data/malformed_dict.bplist diff --git a/test/data/o1.ostep b/test/data/o1.ostep new file mode 100644 index 0000000..074406a --- /dev/null +++ b/test/data/o1.ostep @@ -0,0 +1,45 @@ +{ + "test" = (1,1); + foo = ( + (-1337), + (1), + (1), + (1), + ( + (1), + (1), + (1), + (1), + ( + (1), + (1), + (1), + (1) + ) + ) + ); + more = { + "a" = "yo"; + "b" = ( + { + "c" = 0.25; + }, + { + "a" = "yo"; + "b" = ( + { + "c" = 0.25; + }, + { + "a" = "yo"; + "b" = ( + { + "cd" = -0.25; + } + ); + } + ); + } + ); + }; +} diff --git a/test/data/o2.ostep b/test/data/o2.ostep new file mode 100644 index 0000000..5f5f3c2 --- /dev/null +++ b/test/data/o2.ostep @@ -0,0 +1,17 @@ +{ + "Some ASCII string" = "Test ASCII String"; + "Some UTF8 strings" = ( + "à éèçù", + "日本語", + "汉è¯/漢語", + "한êµì–´/ì¡°ì„ ë§", + "руÑÑкий Ñзык", + "الْعَرَبيّة", + "עִבְרִית", + "jÄ™zyk polski", + "हिनà¥à¤¦à¥€", + ); + "Keys & \"entities\"" = "hello world & others <nodes> are fun!?'"; + "Some Int" = 32434543632; + "Some String with Unicode entity" = "Yeah check this: \U1234 !!!"; +} diff --git a/test/data/o3.ostep b/test/data/o3.ostep new file mode 100644 index 0000000..b80444d --- /dev/null +++ b/test/data/o3.ostep @@ -0,0 +1,16 @@ +( + { + AFirstKey = "A First Value"; + ASecondKey = "A Second Value"; + // this is the last entry + }, + /*{ + BFirstKey = "B First Value"; + BSecondKey = "B Second Value"; + },*/ + { + CFirstKey = "C First Value"; // "C First Unused Value"; + // now here is another comment + CSecondKey = /* "C Second Value";*/ "C Second Corrected Value"; + } +) diff --git a/test/data/off1byte.bplist b/test/data/off1byte.bplist Binary files differnew file mode 100644 index 0000000..c33d70b --- /dev/null +++ b/test/data/off1byte.bplist diff --git a/test/data/off2bytes.bplist b/test/data/off2bytes.bplist Binary files differnew file mode 100644 index 0000000..f080738 --- /dev/null +++ b/test/data/off2bytes.bplist diff --git a/test/data/off3bytes.bplist b/test/data/off3bytes.bplist Binary files differnew file mode 100644 index 0000000..a2f926b --- /dev/null +++ b/test/data/off3bytes.bplist diff --git a/test/data/off4bytes.bplist b/test/data/off4bytes.bplist Binary files differnew file mode 100644 index 0000000..35897be --- /dev/null +++ b/test/data/off4bytes.bplist diff --git a/test/data/off5bytes.bplist b/test/data/off5bytes.bplist Binary files differnew file mode 100644 index 0000000..9df03cd --- /dev/null +++ b/test/data/off5bytes.bplist diff --git a/test/data/off6bytes.bplist b/test/data/off6bytes.bplist Binary files differnew file mode 100644 index 0000000..3d78a47 --- /dev/null +++ b/test/data/off6bytes.bplist diff --git a/test/data/off7bytes.bplist b/test/data/off7bytes.bplist Binary files differnew file mode 100644 index 0000000..150c277 --- /dev/null +++ b/test/data/off7bytes.bplist diff --git a/test/data/off8bytes.bplist b/test/data/off8bytes.bplist Binary files differnew file mode 100644 index 0000000..f01114e --- /dev/null +++ b/test/data/off8bytes.bplist diff --git a/test/data/offxml.plist b/test/data/offxml.plist new file mode 100644 index 0000000..c3c1e10 --- /dev/null +++ b/test/data/offxml.plist @@ -0,0 +1,8 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> +<plist version="1.0"> +<dict> + <key>A</key> + <string>B</string> +</dict> +</plist> diff --git a/test/data/order.bplist b/test/data/order.bplist Binary files differnew file mode 100644 index 0000000..5ea86f8 --- /dev/null +++ b/test/data/order.bplist diff --git a/test/data/order.plist b/test/data/order.plist new file mode 100644 index 0000000..b2cbdcb --- /dev/null +++ b/test/data/order.plist @@ -0,0 +1,27 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> +<plist version="1.0"> +<dict> + <key>test</key> + <array> + <integer>1</integer> + <integer>1</integer> + </array> + <key>foo</key> + <array> + <array> + <integer>1</integer> + </array> + <dict> + <key>test</key> + <string>foo</string> + </dict> + <array> + <integer>1</integer> + </array> + <array> + <integer>1</integer> + </array> + </array> +</dict> +</plist> diff --git a/test/data/recursion.bplist b/test/data/recursion.bplist Binary files differnew file mode 100644 index 0000000..3f4b42a --- /dev/null +++ b/test/data/recursion.bplist diff --git a/test/data/signed.bplist b/test/data/signed.bplist Binary files differnew file mode 100644 index 0000000..89bf362 --- /dev/null +++ b/test/data/signed.bplist diff --git a/test/data/signed.plist b/test/data/signed.plist new file mode 100644 index 0000000..c7d2991 --- /dev/null +++ b/test/data/signed.plist @@ -0,0 +1,5 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> +<plist version="1.0"> +<integer>-1</integer> +</plist> diff --git a/test/data/signedunsigned.bplist b/test/data/signedunsigned.bplist Binary files differnew file mode 100644 index 0000000..f361063 --- /dev/null +++ b/test/data/signedunsigned.bplist diff --git a/test/data/signedunsigned.plist b/test/data/signedunsigned.plist new file mode 100644 index 0000000..2942529 --- /dev/null +++ b/test/data/signedunsigned.plist @@ -0,0 +1,11 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> +<plist version="1.0"> +<array> + <integer>-1</integer> + <integer>18446744073709551615</integer> + <integer>9223372036854775807</integer> + <integer>-9223372036854775808</integer> + <integer>9223372036854775808</integer> +</array> +</plist> diff --git a/test/data/test.strings b/test/data/test.strings new file mode 100644 index 0000000..6d6ee43 --- /dev/null +++ b/test/data/test.strings @@ -0,0 +1,12 @@ +STRINGS_ENTRY = "Whatever"; +FOO = "BAR"; +BAR = Foo; +ENTRY0 = "à éèçù"; +ENTRY1 = "日本語"; +ENTRY2 = "汉è¯/漢語"; +ENTRY3 = "한êµì–´/ì¡°ì„ ë§"; +ENTRY4 = "руÑÑкий Ñзык"; +ENTRY5 = "الْعَرَبيّة"; +ENTRY6 = "עִבְרִית"; +ENTRY7 = "jÄ™zyk polski"; +ENTRY8 = "हिनà¥à¤¦à¥€"; diff --git a/test/data/uid.bplist b/test/data/uid.bplist Binary files differnew file mode 100644 index 0000000..e1fc6f8 --- /dev/null +++ b/test/data/uid.bplist diff --git a/test/data/unsigned.bplist b/test/data/unsigned.bplist Binary files differnew file mode 100644 index 0000000..2b0a807 --- /dev/null +++ b/test/data/unsigned.bplist diff --git a/test/data/unsigned.plist b/test/data/unsigned.plist new file mode 100644 index 0000000..f08d1b7 --- /dev/null +++ b/test/data/unsigned.plist @@ -0,0 +1,5 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> +<plist version="1.0"> +<integer>18446744073709551615</integer> +</plist> diff --git a/test/dates.test b/test/dates.test new file mode 100755 index 0000000..06bf034 --- /dev/null +++ b/test/dates.test @@ -0,0 +1,15 @@ +## -*- sh -*- + +set -e + +DATASRC=$top_srcdir/test/data +TESTFILE=7.plist +DATAIN0=$DATASRC/$TESTFILE +DATAOUT0=$top_builddir/test/data/dates.test.bin +DATAOUT1=$top_builddir/test/data/dates.test.xml + +$top_builddir/tools/plistutil -i $DATAIN0 -o $DATAOUT0 +$top_builddir/tools/plistutil -i $DATAOUT0 -o $DATAOUT1 + +$top_builddir/test/plist_cmp $DATAOUT0 $DATAIN0 +$top_builddir/test/plist_cmp $DATAOUT1 $DATAIN0 diff --git a/test/empty++.test b/test/empty++.test new file mode 100755 index 0000000..7e3695b --- /dev/null +++ b/test/empty++.test @@ -0,0 +1,15 @@ +## -*- sh -*- + +DATASRC=$top_srcdir/test/data +DATAOUT=$top_builddir/test/data +TESTFILE=1.plist + +if ! test -d "$DATAOUT"; then + mkdir -p $DATAOUT +fi + +echo "Converting" +$top_builddir/test/plist_test++ $DATASRC/$TESTFILE $DATAOUT/empty++.test.out + +echo "Comparing" +$top_builddir/test/plist_cmp $DATASRC/$TESTFILE $DATAOUT/empty++.test.out diff --git a/test/empty.test b/test/empty.test new file mode 100755 index 0000000..bc71562 --- /dev/null +++ b/test/empty.test @@ -0,0 +1,15 @@ +## -*- sh -*- + +DATASRC=$top_srcdir/test/data +DATAOUT=$top_builddir/test/data +TESTFILE=1.plist + +if ! test -d "$DATAOUT"; then + mkdir -p $DATAOUT +fi + +echo "Converting" +$top_builddir/test/plist_test $DATASRC/$TESTFILE $DATAOUT/empty.test.out + +echo "Comparing" +$top_builddir/test/plist_cmp $DATASRC/$TESTFILE $DATAOUT/empty.test.out diff --git a/test/empty_keys.test b/test/empty_keys.test new file mode 100755 index 0000000..19bbb08 --- /dev/null +++ b/test/empty_keys.test @@ -0,0 +1,12 @@ +## -*- sh -*- + +set -e + +DATASRC=$top_srcdir/test/data +TESTFILE=empty_keys.plist +DATAIN0=$DATASRC/$TESTFILE +DATAOUT0=$top_builddir/test/data/empty_keys.test.bin + +$top_builddir/tools/plistutil -i $DATAIN0 -o $DATAOUT0 + +$top_builddir/test/plist_cmp $DATAIN0 $DATAOUT0 diff --git a/test/entities.test b/test/entities.test new file mode 100755 index 0000000..a828e15 --- /dev/null +++ b/test/entities.test @@ -0,0 +1,12 @@ +## -*- sh -*- + +set -e + +DATASRC=$top_srcdir/test/data +TESTFILE=entities.plist +DATAIN0=$DATASRC/$TESTFILE +DATAOUT0=$top_builddir/test/data/entities.test.bin + +$top_builddir/tools/plistutil -i $DATAIN0 -o $DATAOUT0 + +$top_builddir/test/plist_cmp $DATAIN0 $DATAOUT0 diff --git a/test/hex.test b/test/hex.test new file mode 100755 index 0000000..414cdbc --- /dev/null +++ b/test/hex.test @@ -0,0 +1,12 @@ +## -*- sh -*- + +set -e + +DATASRC=$top_srcdir/test/data +TESTFILE=hex.plist +DATAIN0=$DATASRC/$TESTFILE +DATAOUT0=$top_builddir/test/data/hex.test.bin + +$top_builddir/tools/plistutil -i $DATAIN0 -o $DATAOUT0 + +$top_builddir/test/plist_cmp $DATAIN0 $DATAOUT0 diff --git a/test/huge++.test b/test/huge++.test new file mode 100755 index 0000000..90d3503 --- /dev/null +++ b/test/huge++.test @@ -0,0 +1,15 @@ +## -*- sh -*- + +DATASRC=$top_srcdir/test/data +DATAOUT=$top_builddir/test/data +TESTFILE=5.plist + +if ! test -d "$DATAOUT"; then + mkdir -p $DATAOUT +fi + +echo "Converting" +$top_builddir/test/plist_test++ $DATASRC/$TESTFILE $DATAOUT/huge++.test.out + +echo "Comparing" +$top_builddir/test/plist_cmp $DATASRC/$TESTFILE $DATAOUT/huge++.test.out diff --git a/test/huge.test b/test/huge.test new file mode 100755 index 0000000..560c21f --- /dev/null +++ b/test/huge.test @@ -0,0 +1,15 @@ +## -*- sh -*- + +DATASRC=$top_srcdir/test/data +DATAOUT=$top_builddir/test/data +TESTFILE=5.plist + +if ! test -d "$DATAOUT"; then + mkdir -p $DATAOUT +fi + +echo "Converting" +$top_builddir/test/plist_test $DATASRC/$TESTFILE $DATAOUT/huge.test.out + +echo "Comparing" +$top_builddir/test/plist_cmp $DATASRC/$TESTFILE $DATAOUT/huge.test.out diff --git a/test/integer_set.c b/test/integer_set.c new file mode 100644 index 0000000..e25648f --- /dev/null +++ b/test/integer_set.c @@ -0,0 +1,130 @@ +#include <stdio.h> +#include <stdint.h> +#include <inttypes.h> +#include <stdlib.h> + +#include <string.h> +#include <plist/plist.h> + +void print_plist(plist_t pl) +{ + char *xml = NULL; + uint32_t xlen = 0; + plist_to_xml(pl, &xml, &xlen); + if (xml) { + printf("%s\n", xml); + } + free(xml); +} + +int main(int argc, char** argv) +{ + int err = 0; + char *xml = NULL; + uint32_t xlen = 0; + plist_t iii = plist_new_int(0); + + /* test 1 */ + plist_set_uint_val(iii, 0x8000000000000000LL); + plist_to_xml(iii, &xml, &xlen); + const char* match1 = "<?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" + "<integer>9223372036854775808</integer>\n" + "</plist>\n"; + if (strcmp(xml, match1) != 0) { + printf("ERROR: plist_set_uint_val with 0x8000000000000000LL failed\n"); + err++; + } else { + printf("SUCCESS: plist_set_uint_val with 0x8000000000000000LL\n"); + } + free(xml); + xml = NULL; + + /* test 2 */ + plist_set_int_val(iii, 0x8000000000000000LL); + plist_to_xml(iii, &xml, &xlen); + const char* match2 = "<?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" + "<integer>-9223372036854775808</integer>\n" + "</plist>\n"; + if (strcmp(xml, match2) != 0) { + printf("ERROR: plist_set_int_val with 0x8000000000000000LL failed\n"); + err++; + } else { + printf("SUCCESS: plist_set_int_val with 0x8000000000000000LL\n"); + } + free(xml); + xml = NULL; + + /* test 3 */ + plist_set_uint_val(iii, (uint64_t)-1LL); + plist_to_xml(iii, &xml, &xlen); + const char* match3 = "<?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" + "<integer>18446744073709551615</integer>\n" + "</plist>\n"; + if (strcmp(xml, match3) != 0) { + printf("ERROR: plist_set_uint_val with (uint64_t)-1LL failed\n"); + err++; + } else { + printf("SUCCESS: plist_set_uint_val with (uint64_t)-1LL\n"); + } + free(xml); + xml = NULL; + + /* test 4 */ + plist_set_int_val(iii, -1LL); + plist_to_xml(iii, &xml, &xlen); + const char* match4 = "<?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" + "<integer>-1</integer>\n" + "</plist>\n"; + if (strcmp(xml, match4) != 0) { + printf("ERROR: plist_set_int_val with -1LL failed\n"); + err++; + } else { + printf("SUCCESS: plist_set_int_val with -1LL\n"); + } + free(xml); + xml = NULL; + + /* test 5 */ + plist_set_uint_val(iii, 0x8000000000000001LL); + plist_to_xml(iii, &xml, &xlen); + const char* match5 = "<?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" + "<integer>9223372036854775809</integer>\n" + "</plist>\n"; + if (strcmp(xml, match5) != 0) { + printf("ERROR: plist_set_uint_val with 0x8000000000000001LL failed\n"); + err++; + } else { + printf("SUCCESS: plist_set_uint_val with 0x8000000000000001LL\n"); + } + free(xml); + xml = NULL; + + /* test 6 */ + plist_set_uint_val(iii, 18446744073709551615uLL); + plist_to_xml(iii, &xml, &xlen); + const char* match6 = "<?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" + "<integer>18446744073709551615</integer>\n" + "</plist>\n"; + if (strcmp(xml, match6) != 0) { + printf("ERROR: plist_set_uint_val with 0x8000000000000001LL failed\n"); + err++; + } else { + printf("SUCCESS: plist_set_uint_val with 0x8000000000000001LL\n"); + } + free(xml); + xml = NULL; + + return (err > 0) ? EXIT_FAILURE : EXIT_SUCCESS; +} diff --git a/test/integer_set.test b/test/integer_set.test new file mode 100755 index 0000000..b917663 --- /dev/null +++ b/test/integer_set.test @@ -0,0 +1,5 @@ +## -*- sh -*- + +set -e + +$top_builddir/test/integer_set_test diff --git a/test/invalid_tag.test b/test/invalid_tag.test new file mode 100755 index 0000000..5d4da93 --- /dev/null +++ b/test/invalid_tag.test @@ -0,0 +1,16 @@ +## -*- sh -*- + +DATASRC=$top_srcdir/test/data +TESTFILE=invalid_tag.plist +DATAIN0=$DATASRC/$TESTFILE +DATAOUT0=$top_builddir/test/data/invalid_tag.test.out + +rm -rf $DATAOUT0 +$top_builddir/tools/plistutil -i $DATAIN0 -o $DATAOUT0 + +# test succeeds if plistutil fails +if [ $? -eq 0 ]; then + exit 1 +else + exit 0 +fi diff --git a/test/json-int64-min-max.test b/test/json-int64-min-max.test new file mode 100755 index 0000000..f3fe61c --- /dev/null +++ b/test/json-int64-min-max.test @@ -0,0 +1,19 @@ +## -*- sh -*- + +set -e + +DATASRC=$top_srcdir/test/data +DATAOUT=$top_builddir/test/data +TESTFILE=int64_min_max.json + +if ! test -d "$DATAOUT"; then + mkdir -p $DATAOUT +fi + +export PLIST_JSON_DEBUG=1 + +echo "Converting" +$top_builddir/test/plist_jtest $DATASRC/$TESTFILE $DATAOUT/json-int64-min-max.test.out + +echo "Comparing" +$top_builddir/test/plist_cmp $DATASRC/$TESTFILE $DATAOUT/json-int64-min-max.test.out diff --git a/test/json-invalid-types.test b/test/json-invalid-types.test new file mode 100755 index 0000000..a21fcd9 --- /dev/null +++ b/test/json-invalid-types.test @@ -0,0 +1,33 @@ +## -*- sh -*- + +DATASRC=$top_srcdir/test/data +DATAOUT=$top_builddir/test/data +TESTFILE0=data.bplist +TESTFILE1=7.plist +TESTFILE2=uid.bplist + +if ! test -d "$DATAOUT"; then + mkdir -p $DATAOUT +fi + +export PLIST_JSON_DEBUG=1 + +echo "Converting (failure expected)" +$top_builddir/tools/plistutil -f json -i $DATASRC/$TESTFILE0 -o /dev/null +if [ $? -ne 2 ]; then + exit 1 +fi + +echo "Converting (failure expected)" +$top_builddir/tools/plistutil -f json -i $DATASRC/$TESTFILE1 -o /dev/null +if [ $? -ne 2 ]; then + exit 2 +fi + +echo "Converting (failure expected)" +$top_builddir/tools/plistutil -f json -i $DATASRC/$TESTFILE2 -o /dev/null +if [ $? -ne 2 ]; then + exit 3 +fi + +exit 0 diff --git a/test/json1.test b/test/json1.test new file mode 100755 index 0000000..cc7440f --- /dev/null +++ b/test/json1.test @@ -0,0 +1,19 @@ +## -*- sh -*- + +set -e + +DATASRC=$top_srcdir/test/data +DATAOUT=$top_builddir/test/data +TESTFILE=j1.json + +if ! test -d "$DATAOUT"; then + mkdir -p $DATAOUT +fi + +export PLIST_JSON_DEBUG=1 + +echo "Converting" +$top_builddir/test/plist_jtest $DATASRC/$TESTFILE $DATAOUT/json1.test.out + +echo "Comparing" +$top_builddir/test/plist_cmp $DATASRC/$TESTFILE $DATAOUT/json1.test.out diff --git a/test/json2.test b/test/json2.test new file mode 100755 index 0000000..199643c --- /dev/null +++ b/test/json2.test @@ -0,0 +1,19 @@ +## -*- sh -*- + +set -e + +DATASRC=$top_srcdir/test/data +DATAOUT=$top_builddir/test/data +TESTFILE=j2.json + +if ! test -d "$DATAOUT"; then + mkdir -p $DATAOUT +fi + +export PLIST_JSON_DEBUG=1 + +echo "Converting" +$top_builddir/test/plist_jtest $DATASRC/$TESTFILE $DATAOUT/json2.test.out + +echo "Comparing" +$top_builddir/test/plist_cmp $DATASRC/$TESTFILE $DATAOUT/json2.test.out diff --git a/test/json3.test b/test/json3.test new file mode 100755 index 0000000..6189e89 --- /dev/null +++ b/test/json3.test @@ -0,0 +1,24 @@ +## -*- sh -*- + +set -e + +DATASRC=$top_srcdir/test/data +DATAOUT=$top_builddir/test/data +TESTFILE=entities.plist + +if ! test -d "$DATAOUT"; then + mkdir -p $DATAOUT +fi + +export PLIST_JSON_DEBUG=1 + +echo "Converting input file to JSON" +$top_builddir/tools/plistutil -f json -i $DATASRC/$TESTFILE -o $DATAOUT/json3.test.json + +echo "Converting to binary and back to JSON" +$top_builddir/test/plist_jtest $DATAOUT/json3.test.json $DATAOUT/json3.test.json.out + +echo "Comparing" +$top_builddir/test/plist_cmp $DATASRC/$TESTFILE $DATAOUT/json3.test.json.out + +rm -f $DATAOUT/json3.test.json diff --git a/test/large++.test b/test/large++.test new file mode 100755 index 0000000..f3f4c23 --- /dev/null +++ b/test/large++.test @@ -0,0 +1,15 @@ +## -*- sh -*- + +DATASRC=$top_srcdir/test/data +DATAOUT=$top_builddir/test/data +TESTFILE=4.plist + +if ! test -d "$DATAOUT"; then + mkdir -p $DATAOUT +fi + +echo "Converting" +$top_builddir/test/plist_test++ $DATASRC/$TESTFILE $DATAOUT/large++.test.out + +echo "Comparing" +$top_builddir/test/plist_cmp $DATASRC/$TESTFILE $DATAOUT/large++.test.out diff --git a/test/large.test b/test/large.test new file mode 100755 index 0000000..d3cce7a --- /dev/null +++ b/test/large.test @@ -0,0 +1,15 @@ +## -*- sh -*- + +DATASRC=$top_srcdir/test/data +DATAOUT=$top_builddir/test/data +TESTFILE=4.plist + +if ! test -d "$DATAOUT"; then + mkdir -p $DATAOUT +fi + +echo "Converting" +$top_builddir/test/plist_test $DATASRC/$TESTFILE $DATAOUT/large.test.out + +echo "Comparing" +$top_builddir/test/plist_cmp $DATASRC/$TESTFILE $DATAOUT/large.test.out diff --git a/test/malformed_dict.test b/test/malformed_dict.test new file mode 100755 index 0000000..1d3ba2b --- /dev/null +++ b/test/malformed_dict.test @@ -0,0 +1,15 @@ +## -*- sh -*- + +DATASRC=$top_srcdir/test/data +TESTFILE=malformed_dict.bplist +DATAIN0=$DATASRC/$TESTFILE +DATAOUT0=$top_builddir/test/data/malformed_dict.test.out + +$top_builddir/tools/plistutil -i $DATAIN0 -o $DATAOUT0 + +# test succeeds if plistutil fails +if [ $? -eq 0 ]; then + exit 1 +else + exit 0 +fi diff --git a/test/medium++.test b/test/medium++.test new file mode 100755 index 0000000..6eb453f --- /dev/null +++ b/test/medium++.test @@ -0,0 +1,15 @@ +## -*- sh -*- + +DATASRC=$top_srcdir/test/data +DATAOUT=$top_builddir/test/data +TESTFILE=3.plist + +if ! test -d "$DATAOUT"; then + mkdir -p $DATAOUT +fi + +echo "Converting" +$top_builddir/test/plist_test++ $DATASRC/$TESTFILE $DATAOUT/medium++.test.out + +echo "Comparing" +$top_builddir/test/plist_cmp $DATASRC/$TESTFILE $DATAOUT/medium++.test.out diff --git a/test/medium.test b/test/medium.test new file mode 100755 index 0000000..300c016 --- /dev/null +++ b/test/medium.test @@ -0,0 +1,15 @@ +## -*- sh -*- + +DATASRC=$top_srcdir/test/data +DATAOUT=$top_builddir/test/data +TESTFILE=3.plist + +if ! test -d "$DATAOUT"; then + mkdir -p $DATAOUT +fi + +echo "Converting" +$top_builddir/test/plist_test $DATASRC/$TESTFILE $DATAOUT/medium.test.out + +echo "Comparing" +$top_builddir/test/plist_cmp $DATASRC/$TESTFILE $DATAOUT/medium.test.out diff --git a/test/offsetsize.test b/test/offsetsize.test new file mode 100755 index 0000000..d548d88 --- /dev/null +++ b/test/offsetsize.test @@ -0,0 +1,12 @@ +## -*- sh -*- + +set -e + +DATASRC=$top_srcdir/test/data +TESTFILES="off1byte.bplist off2bytes.bplist off3bytes.bplist off4bytes.bplist off5bytes.bplist off6bytes.bplist off7bytes.bplist off8bytes.bplist" +CMPFILE=offxml.plist + +for I in $TESTFILES; do + echo "* checking $I" + $top_builddir/test/plist_cmp $DATASRC/$I $DATASRC/$CMPFILE +done diff --git a/test/order.test b/test/order.test new file mode 100755 index 0000000..bd89b9d --- /dev/null +++ b/test/order.test @@ -0,0 +1,13 @@ +## -*- sh -*- + +set -e + +DATASRC=$top_srcdir/test/data +TESTFILE=order.bplist +DATAIN0=$DATASRC/$TESTFILE +DATAIN1=$DATASRC/order.plist +DATAOUT0=$top_builddir/test/data/order.test.out + +$top_builddir/tools/plistutil -i $DATAIN0 -o $DATAOUT0 + +$top_builddir/test/plist_cmp $DATAIN1 $DATAOUT0 diff --git a/test/ostep-comments.test b/test/ostep-comments.test new file mode 100755 index 0000000..68f5242 --- /dev/null +++ b/test/ostep-comments.test @@ -0,0 +1,20 @@ +## -*- sh -*- + +set -e + +DATASRC=$top_srcdir/test/data +DATAOUT=$top_builddir/test/data +TESTFILE=o3.ostep + +if ! test -d "$DATAOUT"; then + mkdir -p $DATAOUT +fi + +export PLIST_OSTEP_DEBUG=1 + +echo "Converting" +$top_builddir/test/plist_otest $DATASRC/$TESTFILE $DATAOUT/ostep-comments.test.out + +echo "Comparing" +export PLIST_OSTEP_DEBUG=1 +$top_builddir/test/plist_cmp $DATASRC/$TESTFILE $DATAOUT/ostep-comments.test.out diff --git a/test/ostep-invalid-types.test b/test/ostep-invalid-types.test new file mode 100755 index 0000000..3ae376c --- /dev/null +++ b/test/ostep-invalid-types.test @@ -0,0 +1,32 @@ +## -*- sh -*- + +DATASRC=$top_srcdir/test/data +DATAOUT=$top_builddir/test/data +TESTFILE1=7.plist +TESTFILE2=uid.bplist + +if ! test -d "$DATAOUT"; then + mkdir -p $DATAOUT +fi + +export PLIST_OSTEP_DEBUG=1 + +echo "Converting (failure expected)" +echo '[true]' |$top_builddir/tools/plistutil -f openstep -i - -o /dev/null +if [ $? -ne 2 ]; then + exit 1 +fi + +echo "Converting (failure expected)" +$top_builddir/tools/plistutil -f openstepn -i $DATASRC/$TESTFILE1 -o /dev/null +if [ $? -ne 2 ]; then + exit 2 +fi + +echo "Converting (failure expected)" +$top_builddir/tools/plistutil -f openstep -i $DATASRC/$TESTFILE2 -o /dev/null +if [ $? -ne 2 ]; then + exit 3 +fi + +exit 0 diff --git a/test/ostep-strings.test b/test/ostep-strings.test new file mode 100755 index 0000000..e3441a3 --- /dev/null +++ b/test/ostep-strings.test @@ -0,0 +1,20 @@ +## -*- sh -*- + +set -e + +DATASRC=$top_srcdir/test/data +DATAOUT=$top_builddir/test/data +TESTFILE=test.strings + +if ! test -d "$DATAOUT"; then + mkdir -p $DATAOUT +fi + +export PLIST_OSTEP_DEBUG=1 + +echo "Converting" +$top_builddir/test/plist_otest $DATASRC/$TESTFILE $DATAOUT/ostep-strings.test.out + +echo "Comparing" +export PLIST_OSTEP_DEBUG=1 +$top_builddir/test/plist_cmp $DATASRC/$TESTFILE $DATAOUT/ostep-strings.test.out diff --git a/test/ostep1.test b/test/ostep1.test new file mode 100755 index 0000000..998fc54 --- /dev/null +++ b/test/ostep1.test @@ -0,0 +1,20 @@ +## -*- sh -*- + +set -e + +DATASRC=$top_srcdir/test/data +DATAOUT=$top_builddir/test/data +TESTFILE=o1.ostep + +if ! test -d "$DATAOUT"; then + mkdir -p $DATAOUT +fi + +export PLIST_OSTEP_DEBUG=1 + +echo "Converting" +$top_builddir/test/plist_otest $DATASRC/$TESTFILE $DATAOUT/ostep1.test.out + +echo "Comparing" +export PLIST_OSTEP_DEBUG=1 +$top_builddir/test/plist_cmp $DATASRC/$TESTFILE $DATAOUT/ostep1.test.out diff --git a/test/ostep2.test b/test/ostep2.test new file mode 100755 index 0000000..a5485f8 --- /dev/null +++ b/test/ostep2.test @@ -0,0 +1,19 @@ +## -*- sh -*- + +set -e + +DATASRC=$top_srcdir/test/data +DATAOUT=$top_builddir/test/data +TESTFILE=o2.ostep + +if ! test -d "$DATAOUT"; then + mkdir -p $DATAOUT +fi + +export PLIST_OTEST_DEBUG=1 + +echo "Converting" +$top_builddir/test/plist_otest $DATASRC/$TESTFILE $DATAOUT/ostep2.test.out + +echo "Comparing" +$top_builddir/test/plist_cmp $DATASRC/$TESTFILE $DATAOUT/ostep2.test.out diff --git a/test/plist_btest.c b/test/plist_btest.c new file mode 100644 index 0000000..32ba364 --- /dev/null +++ b/test/plist_btest.c @@ -0,0 +1,137 @@ +/* + * backup_test.c + * source libplist regression test + * + * Copyright (c) 2009 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 + * 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 "plist/plist.h" + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/stat.h> + +#ifdef _MSC_VER +#pragma warning(disable:4996) +#endif + + +int main(int argc, char *argv[]) +{ + FILE *iplist = NULL; + plist_t root_node1 = NULL; + plist_t root_node2 = NULL; + char *plist_bin = NULL; + char *plist_bin2 = NULL; + char *plist_xml = NULL; + int size_in = 0; + uint32_t size_out = 0; + uint32_t size_out2 = 0; + char *file_in = NULL; + char *file_out = NULL; + struct stat *filestats = (struct stat *) malloc(sizeof(struct stat)); + if (argc != 3) + { + printf("Wrong input\n"); + return 1; + } + + file_in = argv[1]; + file_out = argv[2]; + //read input file + iplist = fopen(file_in, "rb"); + + if (!iplist) + { + printf("File does not exists\n"); + return 2; + } + printf("File %s is open\n", file_in); + stat(file_in, filestats); + size_in = filestats->st_size; + plist_bin = (char *) malloc(sizeof(char) * (size_in + 1)); + if (fread(plist_bin, sizeof(char), size_in, iplist) != (size_t)size_in) { + printf("Failed to read input file\n"); + fclose(iplist); + free(plist_bin); + free(filestats); + return 3; + } + fclose(iplist); + + + //convert one format to another + plist_from_bin(plist_bin, size_in, &root_node1); + if (!root_node1) + { + printf("PList BIN parsing failed\n"); + return 3; + } + + printf("PList BIN parsing succeeded\n"); + plist_to_xml(root_node1, &plist_xml, &size_out); + if (!plist_xml) + { + printf("PList XML writing failed\n"); + return 4; + } + + printf("PList XML writing succeeded\n"); + plist_from_xml(plist_xml, size_out, &root_node2); + if (!root_node2) + { + printf("PList XML parsing failed\n"); + return 5; + } + + printf("PList XML parsing succeeded\n"); + plist_to_bin(root_node2, &plist_bin2, &size_out2); + if (!plist_bin2) + { + printf("PList BIN writing failed\n"); + return 8; + } + + printf("PList BIN writing succeeded\n"); + if (plist_bin2) + { + FILE *oplist = NULL; + oplist = fopen(file_out, "wb"); + fwrite(plist_bin2, size_out2, sizeof(char), oplist); + fclose(oplist); + } + + plist_free(root_node1); + plist_free(root_node2); + free(plist_xml); + free(plist_bin); + free(plist_bin2); + free(filestats); + + if ((uint32_t)size_in != size_out2) + { + printf("Size of input and output is different\n"); + printf("Input size : %i\n", size_in); + printf("Output size : %i\n", size_out2); + } + + //success + return 0; +} + diff --git a/test/plist_cmp.c b/test/plist_cmp.c index a07452b..c452032 100644 --- a/test/plist_cmp.c +++ b/test/plist_cmp.c @@ -35,12 +35,12 @@ static plist_t plist_get_first_child(plist_t node) { - return (plist_t) node_first_child((node_t*) node); + return (plist_t) node_first_child((node_t) node); } static plist_t plist_get_next_sibling(plist_t node) { - return (plist_t) node_next_sibling((node_t*) node); + return (plist_t) node_next_sibling((node_t) node); } static char compare_plist(plist_t node_l, plist_t node_r) @@ -75,21 +75,12 @@ static char compare_plist(plist_t node_l, plist_t node_r) int main(int argc, char *argv[]) { - FILE *iplist1 = NULL; - FILE *iplist2 = NULL; plist_t root_node1 = NULL; plist_t root_node2 = NULL; - char *plist_1 = NULL; - char *plist_2 = NULL; - int size_in1 = 0; - int size_in2 = 0; char *file_in1 = NULL; char *file_in2 = NULL; int res = 0; - struct stat *filestats1 = (struct stat *) malloc(sizeof(struct stat)); - struct stat *filestats2 = (struct stat *) malloc(sizeof(struct stat)); - if (argc!= 3) { printf("Wrong input\n"); @@ -99,60 +90,21 @@ int main(int argc, char *argv[]) file_in1 = argv[1]; file_in2 = argv[2]; - //read input file - iplist1 = fopen(file_in1, "rb"); - iplist2 = fopen(file_in2, "rb"); - - if (!iplist1 || !iplist2) - { - printf("File does not exists\n"); - return 2; - } - - stat(file_in1, filestats1); - stat(file_in2, filestats2); - - size_in1 = filestats1->st_size; - size_in2 = filestats2->st_size; - - plist_1 = (char *) malloc(sizeof(char) * (size_in1 + 1)); - plist_2 = (char *) malloc(sizeof(char) * (size_in2 + 1)); - - fread(plist_1, sizeof(char), size_in1, iplist1); - fread(plist_2, sizeof(char), size_in2, iplist2); - - fclose(iplist1); - fclose(iplist2); - - if (memcmp(plist_1, "bplist00", 8) == 0) - plist_from_bin(plist_1, size_in1, &root_node1); - else - plist_from_xml(plist_1, size_in1, &root_node1); - - if (memcmp(plist_2, "bplist00", 8) == 0) - plist_from_bin(plist_2, size_in2, &root_node2); - else - plist_from_xml(plist_2, size_in2, &root_node2); + plist_read_from_file(file_in1, &root_node1, NULL); + plist_read_from_file(file_in2, &root_node2, NULL); if (!root_node1 || !root_node2) { printf("PList parsing failed\n"); return 3; } - else - printf("PList parsing succeeded\n"); + printf("PList parsing succeeded\n"); res = compare_plist(root_node1, root_node2); - plist_free(root_node1); plist_free(root_node2); - free(plist_1); - free(plist_2); - free(filestats1); - free(filestats2); - return !res; } diff --git a/test/plist_jtest.c b/test/plist_jtest.c new file mode 100644 index 0000000..570886d --- /dev/null +++ b/test/plist_jtest.c @@ -0,0 +1,137 @@ +/* + * backup_test.c + * source libplist regression test + * + * Copyright (c) 2009 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 + * 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 "plist/plist.h" + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/stat.h> + +#ifdef _MSC_VER +#pragma warning(disable:4996) +#endif + + +int main(int argc, char *argv[]) +{ + FILE *iplist = NULL; + plist_t root_node1 = NULL; + plist_t root_node2 = NULL; + char *plist_json = NULL; + char *plist_json2 = NULL; + char *plist_bin = NULL; + int size_in = 0; + uint32_t size_out = 0; + uint32_t size_out2 = 0; + char *file_in = NULL; + char *file_out = NULL; + struct stat *filestats = (struct stat *) malloc(sizeof(struct stat)); + if (argc != 3) + { + printf("Wrong input\n"); + return 1; + } + + file_in = argv[1]; + file_out = argv[2]; + //read input file + iplist = fopen(file_in, "rb"); + + if (!iplist) + { + printf("File does not exists\n"); + return 2; + } + printf("File %s is open\n", file_in); + stat(file_in, filestats); + size_in = filestats->st_size; + plist_json = (char *) malloc(sizeof(char) * (size_in + 1)); + if (fread(plist_json, sizeof(char), size_in, iplist) != (size_t)size_in) { + printf("Failed to read input file\n"); + fclose(iplist); + free(plist_json); + free(filestats); + return 3; + } + fclose(iplist); + plist_json[size_in] = 0; + + //convert one format to another + plist_from_json(plist_json, size_in, &root_node1); + if (!root_node1) + { + printf("PList JSON parsing failed\n"); + return 3; + } + + printf("PList JSON parsing succeeded\n"); + plist_to_bin(root_node1, &plist_bin, &size_out); + if (!plist_bin) + { + printf("PList BIN writing failed\n"); + return 4; + } + + printf("PList BIN writing succeeded\n"); + plist_from_bin(plist_bin, size_out, &root_node2); + if (!root_node2) + { + printf("PList BIN parsing failed\n"); + return 5; + } + + printf("PList BIN parsing succeeded\n"); + plist_to_json(root_node2, &plist_json2, &size_out2, 0); + if (!plist_json2) + { + printf("PList JSON writing failed\n"); + return 8; + } + + printf("PList JSON writing succeeded\n"); + if (plist_json2) + { + FILE *oplist = NULL; + oplist = fopen(file_out, "wb"); + fwrite(plist_json2, size_out2, sizeof(char), oplist); + fclose(oplist); + } + + plist_free(root_node1); + plist_free(root_node2); + free(plist_bin); + free(plist_json); + free(plist_json2); + free(filestats); + + if ((uint32_t)size_in != size_out2) + { + printf("Size of input and output is different\n"); + printf("Input size : %i\n", size_in); + printf("Output size : %i\n", size_out2); + } + + //success + return 0; +} + diff --git a/test/plist_otest.c b/test/plist_otest.c new file mode 100644 index 0000000..b2d1c94 --- /dev/null +++ b/test/plist_otest.c @@ -0,0 +1,135 @@ +/* + * plist_otest.c + * source libplist regression test + * + * Copyright (c) 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 + */ + + +#include "plist/plist.h" + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/stat.h> + +#ifdef _MSC_VER +#pragma warning(disable:4996) +#endif + + +int main(int argc, char *argv[]) +{ + FILE *iplist = NULL; + plist_t root_node1 = NULL; + plist_t root_node2 = NULL; + char *plist_ostep = NULL; + char *plist_ostep2 = NULL; + char *plist_bin = NULL; + int size_in = 0; + uint32_t size_out = 0; + uint32_t size_out2 = 0; + char *file_in = NULL; + char *file_out = NULL; + struct stat filestats; + if (argc != 3) + { + printf("Wrong input\n"); + return 1; + } + + file_in = argv[1]; + file_out = argv[2]; + //read input file + iplist = fopen(file_in, "rb"); + + if (!iplist) + { + printf("File does not exists\n"); + return 2; + } + printf("File %s is open\n", file_in); + stat(file_in, &filestats); + size_in = filestats.st_size; + plist_ostep = (char *) malloc(sizeof(char) * (size_in + 1)); + if (fread(plist_ostep, sizeof(char), size_in, iplist) != (size_t)size_in) { + printf("Failed to read input file\n"); + fclose(iplist); + free(plist_ostep); + return 3; + } + fclose(iplist); + plist_ostep[size_in] = 0; + + //convert one format to another + plist_from_openstep(plist_ostep, size_in, &root_node1); + if (!root_node1) + { + printf("OpenStep PList parsing failed\n"); + return 3; + } + + printf("OpenStep PList parsing succeeded\n"); + plist_to_bin(root_node1, &plist_bin, &size_out); + if (!plist_bin) + { + printf("PList BIN writing failed\n"); + return 4; + } + + printf("PList BIN writing succeeded\n"); + plist_from_bin(plist_bin, size_out, &root_node2); + if (!root_node2) + { + printf("PList BIN parsing failed\n"); + return 5; + } + + printf("PList BIN parsing succeeded\n"); + plist_to_openstep(root_node2, &plist_ostep2, &size_out2, 0); + if (!plist_ostep2) + { + printf("OpenStep PList writing failed\n"); + return 8; + } + + printf("OpenStep PList writing succeeded\n"); + if (plist_ostep2) + { + FILE *oplist = NULL; + oplist = fopen(file_out, "wb"); + fwrite(plist_ostep2, size_out2, sizeof(char), oplist); + fclose(oplist); + } + + plist_free(root_node1); + plist_free(root_node2); + free(plist_bin); + free(plist_ostep); + free(plist_ostep2); + + if ((uint32_t)size_in != size_out2) + { + printf("Size of input and output is different\n"); + printf("Input size : %i\n", size_in); + printf("Output size : %i\n", size_out2); + } + + //success + return 0; +} + diff --git a/test/plist_test++.cpp b/test/plist_test++.cpp new file mode 100644 index 0000000..33205c1 --- /dev/null +++ b/test/plist_test++.cpp @@ -0,0 +1,103 @@ +/* + * source libplist++ regression test + * + * Copyright (c) 2021 Sebastien Gonzalve 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 "plist/plist++.h" +#include <fstream> +#include <sstream> +#include <iostream> + +int main(int argc, char *argv[]) +{ + if (argc != 3) + { + printf("Wrong input\n"); + return 1; + } + + const char* file_in = argv[1]; + const char* file_out = argv[2]; + + //read input file + std::ifstream iplist; + iplist.open(file_in); + + if (!iplist) + { + printf("File does not exists\n"); + return 2; + } + + std::cout << "File " << file_in << " is open\n"; + + std::string plist_xml; + { + std::stringstream buffer; + buffer << iplist.rdbuf(); + plist_xml = buffer.str(); + } + + iplist.close(); + + //convert one format to another + PList::Structure* root_node1 = PList::Structure::FromXml(plist_xml); + if (!root_node1) + { + std::cout << "PList XML parsing failed\n"; + return 3; + } + + std::cout << "PList XML parsing succeeded\n"; + std::vector<char> plist_bin = root_node1->ToBin(); + // FIXME There is no way to test for success of ToBin for now. + + std::cout << "PList BIN writing succeeded\n"; + PList::Structure* root_node2 = PList::Structure::FromBin(plist_bin); + if (!root_node2) + { + std::cout << "PList BIN parsing failed\n"; + return 5; + } + + std::cout << "PList BIN parsing succeeded\n"; + std::string plist_xml2 = root_node2->ToXml(); + if (plist_xml2.empty()) + { + std::cout << "PList XML writing failed\n"; + return 8; + } + + std::cout << "PList XML writing succeeded\n"; + { + std::ofstream oplist; + oplist.open(file_out); + oplist << plist_xml2; + } + + if (plist_xml.size() != plist_xml2.size()) + { + std::cout << "Size of input and output is different\n" + << "Input size : " << plist_xml.size() + << "\nOutput size : " << plist_xml2.size() << '\n'; + } + + return 0; +} + diff --git a/test/plist_test.c b/test/plist_test.c index 17be11a..6e94c57 100644 --- a/test/plist_test.c +++ b/test/plist_test.c @@ -44,14 +44,16 @@ int main(int argc, char *argv[]) uint32_t size_out = 0; uint32_t size_out2 = 0; char *file_in = NULL; + char *file_out = NULL; struct stat *filestats = (struct stat *) malloc(sizeof(struct stat)); - if (argc!= 2) + if (argc != 3) { printf("Wrong input\n"); return 1; } file_in = argv[1]; + file_out = argv[2]; //read input file iplist = fopen(file_in, "rb"); @@ -64,7 +66,13 @@ int main(int argc, char *argv[]) stat(file_in, filestats); size_in = filestats->st_size; plist_xml = (char *) malloc(sizeof(char) * (size_in + 1)); - fread(plist_xml, sizeof(char), size_in, iplist); + if (fread(plist_xml, sizeof(char), size_in, iplist) != (size_t)size_in) { + printf("Failed to read input file\n"); + fclose(iplist); + free(plist_xml); + free(filestats); + return 3; + } fclose(iplist); @@ -75,41 +83,35 @@ int main(int argc, char *argv[]) printf("PList XML parsing failed\n"); return 3; } - else - printf("PList XML parsing succeeded\n"); + printf("PList XML parsing succeeded\n"); plist_to_bin(root_node1, &plist_bin, &size_out); if (!plist_bin) { printf("PList BIN writing failed\n"); return 4; } - else - printf("PList BIN writing succeeded\n"); + printf("PList BIN writing succeeded\n"); plist_from_bin(plist_bin, size_out, &root_node2); if (!root_node2) { printf("PList BIN parsing failed\n"); return 5; } - else - printf("PList BIN parsing succeeded\n"); + printf("PList BIN parsing succeeded\n"); plist_to_xml(root_node2, &plist_xml2, &size_out2); if (!plist_xml2) { printf("PList XML writing failed\n"); return 8; } - else - printf("PList XML writing succeeded\n"); + printf("PList XML writing succeeded\n"); if (plist_xml2) { FILE *oplist = NULL; - char file_out[512]; - sprintf(file_out, "%s.out", file_in); oplist = fopen(file_out, "wb"); fwrite(plist_xml2, size_out2, sizeof(char), oplist); fclose(oplist); diff --git a/test/recursion.test b/test/recursion.test new file mode 100755 index 0000000..97bfb45 --- /dev/null +++ b/test/recursion.test @@ -0,0 +1,15 @@ +## -*- sh -*- + +DATASRC=$top_srcdir/test/data +TESTFILE=recursion.bplist +DATAIN0=$DATASRC/$TESTFILE +DATAOUT0=$top_builddir/test/data/recursion.test.out + +$top_builddir/tools/plistutil -i $DATAIN0 -o $DATAOUT0 + +# test succeeds if plistutil fails +if [ $? -eq 0 ]; then + exit 1 +else + exit 0 +fi diff --git a/test/refsize.test b/test/refsize.test new file mode 100755 index 0000000..379b7fb --- /dev/null +++ b/test/refsize.test @@ -0,0 +1,12 @@ +## -*- sh -*- + +set -e + +DATASRC=$top_srcdir/test/data +TESTFILES="dictref1byte.bplist dictref2bytes.bplist dictref3bytes.bplist dictref4bytes.bplist dictref5bytes.bplist dictref6bytes.bplist dictref7bytes.bplist dictref8bytes.bplist" +CMPFILE=offxml.plist + +for I in $TESTFILES; do + echo "* checking $I" + $top_builddir/test/plist_cmp $DATASRC/$I $DATASRC/$CMPFILE +done diff --git a/test/signedunsigned1.test b/test/signedunsigned1.test new file mode 100755 index 0000000..52f7080 --- /dev/null +++ b/test/signedunsigned1.test @@ -0,0 +1,23 @@ +## -*- sh -*- + +set -e + +DATASRC=$top_srcdir/test/data +TESTFILE0=signed.plist +TESTFILE1=unsigned.plist +DATAIN0=$DATASRC/$TESTFILE0 +DATAIN1=$DATASRC/$TESTFILE1 + +CMPFILE0=signed.bplist +CMPFILE1=unsigned.bplist +DATACMP0=$DATASRC/$CMPFILE0 +DATACMP1=$DATASRC/$CMPFILE1 + +DATAOUT0=$top_builddir/test/data/signedunsigned1.test.signed.bin +DATAOUT1=$top_builddir/test/data/signedunsigned1.test.unsigned.bin + +$top_builddir/tools/plistutil -i $DATAIN0 -o $DATAOUT0 +$top_builddir/tools/plistutil -i $DATAIN1 -o $DATAOUT1 + +diff --strip-trailing-cr $DATACMP0 $DATAOUT0 +diff --strip-trailing-cr $DATACMP1 $DATAOUT1 diff --git a/test/signedunsigned2.test b/test/signedunsigned2.test new file mode 100755 index 0000000..192f1a6 --- /dev/null +++ b/test/signedunsigned2.test @@ -0,0 +1,23 @@ +## -*- sh -*- + +set -e + +DATASRC=$top_srcdir/test/data +TESTFILE0=signed.bplist +TESTFILE1=unsigned.bplist +DATAIN0=$DATASRC/$TESTFILE0 +DATAIN1=$DATASRC/$TESTFILE1 + +CMPFILE0=signed.plist +CMPFILE1=unsigned.plist +DATACMP0=$DATASRC/$CMPFILE0 +DATACMP1=$DATASRC/$CMPFILE1 + +DATAOUT0=$top_builddir/test/data/signedunsigned2.test.signed.bin +DATAOUT1=$top_builddir/test/data/signedunsigned2.test.unsigned.bin + +$top_builddir/tools/plistutil -i $DATAIN0 -o $DATAOUT0 +$top_builddir/tools/plistutil -i $DATAIN1 -o $DATAOUT1 + +diff --strip-trailing-cr $DATACMP0 $DATAOUT0 +diff --strip-trailing-cr $DATACMP1 $DATAOUT1 diff --git a/test/signedunsigned3.test b/test/signedunsigned3.test new file mode 100755 index 0000000..87bdcbc --- /dev/null +++ b/test/signedunsigned3.test @@ -0,0 +1,23 @@ +## -*- sh -*- + +set -e + +DATASRC=$top_srcdir/test/data +TESTFILE0=signedunsigned.plist +TESTFILE1=signedunsigned.bplist +DATAIN0=$DATASRC/$TESTFILE0 +DATAIN1=$DATASRC/$TESTFILE1 + +CMPFILE0=signedunsigned.bplist +CMPFILE1=signedunsigned.plist +DATACMP0=$DATASRC/$CMPFILE0 +DATACMP1=$DATASRC/$CMPFILE1 + +DATAOUT0=$top_builddir/test/data/signedunsigned3.test.signed.bin +DATAOUT1=$top_builddir/test/data/signedunsigned3.test.unsigned.xml + +$top_builddir/tools/plistutil -i $DATAIN0 -o $DATAOUT0 +$top_builddir/tools/plistutil -i $DATAIN1 -o $DATAOUT1 + +diff --strip-trailing-cr $DATACMP0 $DATAOUT0 +diff --strip-trailing-cr $DATACMP1 $DATAOUT1 diff --git a/test/small++.test b/test/small++.test new file mode 100755 index 0000000..2ceace7 --- /dev/null +++ b/test/small++.test @@ -0,0 +1,15 @@ +## -*- sh -*- + +DATASRC=$top_srcdir/test/data +DATAOUT=$top_builddir/test/data +TESTFILE=2.plist + +if ! test -d "$DATAOUT"; then + mkdir -p $DATAOUT +fi + +echo "Converting" +$top_builddir/test/plist_test++ $DATASRC/$TESTFILE $DATAOUT/small++.test.out + +echo "Comparing" +$top_builddir/test/plist_cmp $DATASRC/$TESTFILE $DATAOUT/small++.test.out diff --git a/test/small.test b/test/small.test new file mode 100755 index 0000000..dd2faf8 --- /dev/null +++ b/test/small.test @@ -0,0 +1,15 @@ +## -*- sh -*- + +DATASRC=$top_srcdir/test/data +DATAOUT=$top_builddir/test/data +TESTFILE=2.plist + +if ! test -d "$DATAOUT"; then + mkdir -p $DATAOUT +fi + +echo "Converting" +$top_builddir/test/plist_test $DATASRC/$TESTFILE $DATAOUT/small.test.out + +echo "Comparing" +$top_builddir/test/plist_cmp $DATASRC/$TESTFILE $DATAOUT/small.test.out diff --git a/test/timezone1.test b/test/timezone1.test new file mode 100755 index 0000000..5de7cea --- /dev/null +++ b/test/timezone1.test @@ -0,0 +1,17 @@ +## -*- sh -*- + +set -e + +DATASRC=$top_srcdir/test/data +TESTFILE=7.plist +DATAIN0=$DATASRC/$TESTFILE +DATAOUT0=$top_builddir/test/data/timezone1.test.tz0.bin +DATAOUT1=$top_builddir/test/data/timezone1.test.tz1.bin +DATAOUT2=$top_builddir/test/data/timezone1.test.tz2.bin + +TZ=UTC $top_builddir/tools/plistutil -i $DATAIN0 -o $DATAOUT0 +TZ=Asia/Singapore $top_builddir/tools/plistutil -i $DATAIN0 -o $DATAOUT1 +TZ=US/Pacific $top_builddir/tools/plistutil -i $DATAIN0 -o $DATAOUT2 + +$top_builddir/test/plist_cmp $DATAOUT0 $DATAOUT1 +$top_builddir/test/plist_cmp $DATAOUT0 $DATAOUT2 diff --git a/test/timezone2.test b/test/timezone2.test new file mode 100755 index 0000000..79db523 --- /dev/null +++ b/test/timezone2.test @@ -0,0 +1,21 @@ +## -*- sh -*- + +set -e + +DATASRC=$top_srcdir/test/data +TESTFILE=7.plist +DATAIN0=$DATASRC/$TESTFILE +DATAOUT0=$top_builddir/test/data/timezone2.test.bin +DATAOUT1=$top_builddir/test/data/timezone2.test.tz0.xml +DATAOUT2=$top_builddir/test/data/timezone2.test.tz1.xml +DATAOUT3=$top_builddir/test/data/timezone2.test.tz2.xml + +TZ=UTC $top_builddir/tools/plistutil -i $DATAIN0 -o $DATAOUT0 + +TZ=UTC $top_builddir/tools/plistutil -i $DATAOUT0 -o $DATAOUT1 +TZ=Asia/Singapore $top_builddir/tools/plistutil -i $DATAOUT0 -o $DATAOUT2 +TZ=US/Pacific $top_builddir/tools/plistutil -i $DATAOUT0 -o $DATAOUT3 + +$top_builddir/test/plist_cmp $DATAIN0 $DATAOUT1 +$top_builddir/test/plist_cmp $DATAIN0 $DATAOUT2 +$top_builddir/test/plist_cmp $DATAIN0 $DATAOUT3 diff --git a/test/uid.test b/test/uid.test new file mode 100755 index 0000000..a62756f --- /dev/null +++ b/test/uid.test @@ -0,0 +1,15 @@ +## -*- sh -*- + +DATASRC=$top_srcdir/test/data +DATAOUT=$top_builddir/test/data +TESTFILE=uid.bplist + +if ! test -d "$DATAOUT"; then + mkdir -p $DATAOUT +fi + +echo "Converting" +$top_builddir/test/plist_btest $DATASRC/$TESTFILE $DATAOUT/uid.test.out + +echo "Comparing" +$top_builddir/test/plist_cmp $DATASRC/$TESTFILE $DATAOUT/uid.test.out diff --git a/test/xml_behavior.test b/test/xml_behavior.test new file mode 100755 index 0000000..81d8dd0 --- /dev/null +++ b/test/xml_behavior.test @@ -0,0 +1,2 @@ +## -*- sh -*- +$top_builddir/test/xml_behavior_test diff --git a/test/xml_behavior_test.c b/test/xml_behavior_test.c new file mode 100644 index 0000000..94d8a7f --- /dev/null +++ b/test/xml_behavior_test.c @@ -0,0 +1,167 @@ +/* + * xml_behavior_test.c + * + * Tests XML parser behavior for correctness and specification compliance: + * + * 1) A <plist> element must contain exactly one root value node. + * Any additional value nodes after the first root object must + * cause parsing to fail. + * + * 2) Dictionaries of the form: + * <dict> + * <key>CF$UID</key> + * <integer>...</integer> + * </dict> + * must be converted to PLIST_UID nodes during XML parsing, + * including when they appear nested inside other containers. + * + * These tests ensure proper root handling and UID node conversion + * when parsing XML property lists. + */ + +#include <stdio.h> +#include <string.h> +#include <stdint.h> +#include <inttypes.h> + +#include "plist/plist.h" + +static int test_nested_cfuid_converts_to_uid(void) +{ + const char *xml = + "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" + "<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" " + "\"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">" + "<plist version=\"1.0\">" + " <dict>" + " <key>obj</key>" + " <dict>" + " <key>CF$UID</key>" + " <integer>7</integer>" + " </dict>" + " </dict>" + "</plist>"; + + plist_t root = NULL; + plist_err_t err = plist_from_xml(xml, (uint32_t)strlen(xml), &root); + if (err != PLIST_ERR_SUCCESS || !root) { + fprintf(stderr, "nested CF$UID: plist_from_xml failed (err=%d)\n", err); + plist_free(root); + return 0; + } + + if (plist_get_node_type(root) != PLIST_DICT) { + fprintf(stderr, "nested CF$UID: root is not dict\n"); + plist_free(root); + return 0; + } + + plist_t obj = plist_dict_get_item(root, "obj"); + if (!obj) { + fprintf(stderr, "nested CF$UID: missing key 'obj'\n"); + plist_free(root); + return 0; + } + + if (plist_get_node_type(obj) != PLIST_UID) { + fprintf(stderr, "nested CF$UID: expected PLIST_UID, got %d\n", + plist_get_node_type(obj)); + plist_free(root); + return 0; + } + + uint64_t uid = 0; + plist_get_uid_val(obj, &uid); + if (uid != 7) { + fprintf(stderr, "nested CF$UID: expected uid=7, got %" PRIu64 "\n", uid); + plist_free(root); + return 0; + } + + plist_free(root); + return 1; +} + +static int test_extra_root_value_is_rejected(void) +{ + /* Two root values inside <plist> must be rejected */ + const char *xml = + "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" + "<plist version=\"1.0\">" + " <string>one</string>" + " <string>two</string>" + "</plist>"; + + plist_t root = NULL; + plist_err_t err = plist_from_xml(xml, (uint32_t)strlen(xml), &root); + + /* Must fail, and root must be NULL (consistent with other parsers) */ + if (err == PLIST_ERR_SUCCESS || root != NULL) { + fprintf(stderr, "extra root value: expected failure, got err=%d root=%p\n", + err, (void*)root); + plist_free(root); + return 0; + } + return 1; +} + +static int test_scalar_then_extra_node_is_rejected(void) +{ + /* Scalar root followed by another node must be rejected */ + const char *xml = + "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" + "<plist version=\"1.0\">" + " <true/>" + " <dict><key>A</key><string>x</string></dict>" + "</plist>"; + + plist_t root = NULL; + plist_err_t err = plist_from_xml(xml, (uint32_t)strlen(xml), &root); + + if (err == PLIST_ERR_SUCCESS || root != NULL) { + fprintf(stderr, "scalar then extra node: expected failure, got err=%d root=%p\n", + err, (void*)root); + plist_free(root); + return 0; + } + return 1; +} + +static int test_scalar_with_comment_is_ok(void) +{ + /* Comment after the single root value is not an extra value node */ + const char *xml = + "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" + "<plist version=\"1.0\">" + " <string>ok</string>" + " <!-- trailing comment -->" + "</plist>"; + + plist_t root = NULL; + plist_err_t err = plist_from_xml(xml, (uint32_t)strlen(xml), &root); + if (err != PLIST_ERR_SUCCESS || !root) { + fprintf(stderr, "scalar + comment: expected success, got err=%d\n", err); + plist_free(root); + return 0; + } + if (plist_get_node_type(root) != PLIST_STRING) { + fprintf(stderr, "scalar + comment: expected root string, got %d\n", + plist_get_node_type(root)); + plist_free(root); + return 0; + } + plist_free(root); + return 1; +} + +int main(void) +{ + int ok = 1; + + ok &= test_nested_cfuid_converts_to_uid(); + ok &= test_extra_root_value_is_rejected(); + ok &= test_scalar_then_extra_node_is_rejected(); + ok &= test_scalar_with_comment_is_ok(); + + return ok ? 0 : 1; +} diff --git a/tools/Makefile.am b/tools/Makefile.am new file mode 100644 index 0000000..93b5b9f --- /dev/null +++ b/tools/Makefile.am @@ -0,0 +1,20 @@ +if BUILD_TOOLS + +AM_CFLAGS = \ + $(GLOBAL_CFLAGS) \ + -I$(top_srcdir)/include + +AM_LDFLAGS = + +bin_PROGRAMS = plistutil + +plistutil_SOURCES = plistutil.c +plistutil_LDADD = $(top_builddir)/src/libplist-2.0.la + +install-exec-hook: + cd $(DESTDIR)$(bindir) && ln -sf plistutil plist2json + +uninstall-hook: + rm -f $(DESTDIR)$(bindir)/plist2json + +endif diff --git a/tools/plistutil.c b/tools/plistutil.c new file mode 100644 index 0000000..37bb044 --- /dev/null +++ b/tools/plistutil.c @@ -0,0 +1,529 @@ +/* + * plistutil.c + * Simple tool to convert a plist into different formats + * + * Copyright (c) 2009-2020 Martin Szulecki All Rights Reserved. + * Copyright (c) 2013-2020 Nikias Bassen, 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 + * 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 "plist/plist.h" + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/stat.h> +#include <getopt.h> +#include <errno.h> +#ifndef _MSC_VER +#include <unistd.h> +#endif + +#ifdef _MSC_VER +#pragma warning(disable:4996) +#define STDIN_FILENO _fileno(stdin) +#define strtok_r strtok_s +#endif + +typedef struct _options +{ + char *in_file, *out_file, *nodepath; + uint8_t in_fmt, out_fmt; // fmts 0 = undef, 1 = bin, 2 = xml, 3 = json, 4 = openstep + uint8_t flags; +} options_t; +#define OPT_DEBUG (1 << 0) +#define OPT_COMPACT (1 << 1) +#define OPT_SORT (1 << 2) +#define OPT_COERCE (1 << 3) + +static void print_usage(int argc, char *argv[]) +{ + char *name = NULL; + name = strrchr(argv[0], '/'); + printf("Usage: %s [OPTIONS] [-i FILE] [-o FILE]\n", (name ? name + 1: argv[0])); + printf("\n"); + printf("Convert a plist FILE between binary, XML, JSON, and OpenStep format.\n"); + printf("If -f is omitted, XML plist data will be converted to binary and vice-versa.\n"); + printf("To convert to/from JSON or OpenStep the output format needs to be specified.\n"); + printf("\n"); + printf("OPTIONS:\n"); + printf(" -i, --infile FILE Optional FILE to convert from or stdin if - or not used\n"); + printf(" -o, --outfile FILE Optional FILE to convert to or stdout if - or not used\n"); + printf(" -f, --format FORMAT Force output format, regardless of input type\n"); + printf(" FORMAT is one of xml, bin, json, or openstep\n"); + printf(" If omitted, XML will be converted to binary,\n"); + printf(" and binary to XML.\n"); + printf(" -p, --print FILE Print the PList in human-readable format.\n"); + printf(" -n, --nodepath PATH Restrict output to nodepath defined by PATH.\n"); + printf(" -c, --compact JSON and OpenStep only: Print output in compact form.\n"); + printf(" By default, the output will be pretty-printed.\n"); + printf(" -C, --coerce JSON + OpenStep only: Coerce non-compatible plist types\n"); + printf(" to JSON/OpenStep compatible representations.\n"); + printf(" Date values become ISO 8601 strings,\n"); + printf(" data values become Base64-encoded strings (JSON),\n"); + printf(" UID values become integers,\n"); + printf(" boolean becomes 1 or 0 (OpenStep),\n"); + printf(" and NULL becomes a string 'NULL' (OpenStep)\n"); + printf(" This options is implied when invoked as plist2json.\n"); + printf(" -s, --sort Sort all dictionary nodes lexicographically by key\n"); + printf(" before converting to the output format.\n"); + printf(" -d, --debug Enable extended debug output\n"); + printf(" -v, --version Print version information\n"); + printf("\n"); + printf("Homepage: <" PACKAGE_URL ">\n"); + printf("Bug Reports: <" PACKAGE_BUGREPORT ">\n"); +} + +static options_t *parse_arguments(int argc, char *argv[]) +{ + options_t *options = calloc(1, sizeof(options_t)); + if (!options) + return NULL; + + options->out_fmt = 0; + + static struct option long_options[] = { + { "infile", required_argument, 0, 'i' }, + { "outfile", required_argument, 0, 'o' }, + { "format", required_argument, 0, 'f' }, + { "compact", no_argument, 0, 'c' }, + { "coerce", no_argument, 0, 'C' }, + { "sort", no_argument, 0, 's' }, + { "print", required_argument, 0, 'p' }, + { "nodepath", required_argument, 0, 'n' }, + { "debug", no_argument, 0, 'd' }, + { "help", no_argument, 0, 'h' }, + { "version", no_argument, 0, 'v' }, + { 0, 0, 0, 0 } + }; + + int c; + while ((c = getopt_long(argc, argv, "i:o:f:cCsp:n:dhv", long_options, NULL)) != -1) + { + switch (c) + { + case 'i': + if (!optarg || optarg[0] == '\0') { + fprintf(stderr, "ERROR: --infile requires a filename or '-' for stdin\n"); + free(options); + return NULL; + } + options->in_file = optarg; + break; + + case 'o': + if (!optarg || optarg[0] == '\0') { + fprintf(stderr, "ERROR: --outfile requires a filename or '-' for stdout\n"); + free(options); + return NULL; + } + options->out_file = optarg; + break; + + case 'f': + if (!optarg || optarg[0] == '\0') { + fprintf(stderr, "ERROR: --format requires a format (bin|xml|json|openstep)\n"); + free(options); + return NULL; + } + if (!strncmp(optarg, "bin", 3)) { + options->out_fmt = PLIST_FORMAT_BINARY; + } else if (!strncmp(optarg, "xml", 3)) { + options->out_fmt = PLIST_FORMAT_XML; + } else if (!strncmp(optarg, "json", 4)) { + options->out_fmt = PLIST_FORMAT_JSON; + } else if (!strncmp(optarg, "openstep", 8) || + !strncmp(optarg, "ostep", 5)) { + options->out_fmt = PLIST_FORMAT_OSTEP; + } else { + fprintf(stderr, "ERROR: Unsupported output format\n"); + free(options); + return NULL; + } + break; + + case 'c': + options->flags |= OPT_COMPACT; + break; + + case 'C': + options->flags |= OPT_COERCE; + break; + + case 's': + options->flags |= OPT_SORT; + break; + + case 'p': { + if (!optarg || optarg[0] == '\0') { + fprintf(stderr, "ERROR: --print requires a filename or '-' for stdin\n"); + free(options); + return NULL; + } + options->in_file = optarg; + options->out_fmt = PLIST_FORMAT_PRINT; + + char *env_fmt = getenv("PLIST_OUTPUT_FORMAT"); + if (env_fmt) { + if (!strcmp(env_fmt, "plutil")) { + options->out_fmt = PLIST_FORMAT_PLUTIL; + } else if (!strcmp(env_fmt, "limd")) { + options->out_fmt = PLIST_FORMAT_LIMD; + } + } + break; + } + + case 'n': + if (!optarg || optarg[0] == '\0') { + fprintf(stderr, "ERROR: --extract needs a node path\n"); + free(options); + return NULL; + } + options->nodepath = optarg; + break; + + case 'd': + options->flags |= OPT_DEBUG; + break; + + case 'h': + free(options); + return NULL; + + case 'v': + printf("plistutil %s\n", libplist_version()); + exit(EXIT_SUCCESS); + + default: + fprintf(stderr, "ERROR: Invalid option\n"); + free(options); + return NULL; + } + } + + return options; +} + +int main(int argc, char *argv[]) +{ + int ret = 0; + int input_res = PLIST_ERR_UNKNOWN; + int output_res = PLIST_ERR_UNKNOWN; + FILE *iplist = NULL; + plist_t root_node = NULL; + char *plist_out = NULL; + uint32_t size = 0; + size_t read_size = 0; + size_t read_capacity = 4096; + char *plist_entire = NULL; + struct stat filestats; + options_t *options = parse_arguments(argc, argv); + + if (!options) + { + print_usage(argc, argv); + return 0; + } + + // detect invocation as plist2json symlink + { + char *progname = strrchr(argv[0], '/'); + progname = progname ? progname + 1 : argv[0]; + if (!strcmp(progname, "plist2json")) { + if (options->out_fmt == 0) { + options->out_fmt = PLIST_FORMAT_JSON; + } + options->flags |= OPT_COERCE; + } + } + + if (options->flags & OPT_DEBUG) + { + plist_set_debug(1); + } + + if (!options->in_file || !strcmp(options->in_file, "-")) + { + read_size = 0; + plist_entire = malloc(sizeof(char) * read_capacity); + if(plist_entire == NULL) + { + fprintf(stderr, "ERROR: Failed to allocate buffer to read from stdin\n"); + free(options); + return 1; + } + plist_entire[read_size] = '\0'; + char buf[4096]; + ssize_t n; + + while (1) { + n = read(STDIN_FILENO, buf, sizeof(buf)); + if (n > 0) { + size_t needed = read_size + (size_t)n + 1; + if (needed > read_capacity) { + size_t newcap = read_capacity ? read_capacity : 4096; + while (newcap < needed) { + newcap *= 2; + } + + char *tmp = realloc(plist_entire, newcap); + if (!tmp) { + fprintf(stderr, "ERROR: Failed to reallocate stdin buffer\n"); + free(plist_entire); + free(options); + return 1; + } + plist_entire = tmp; + read_capacity = newcap; + } + + memcpy(plist_entire + read_size, buf, (size_t)n); + read_size += (size_t)n; + continue; + } + + if (n == 0) { // EOF + break; + } + + // n < 0: error + if (errno == EINTR) + continue; + + fprintf(stderr, "ERROR: Failed to read from stdin\n"); + free(plist_entire); + free(options); + return 1; + } + plist_entire[read_size] = '\0'; + } + else + { + // read input file + iplist = fopen(options->in_file, "rb"); + if (!iplist) { + fprintf(stderr, "ERROR: Could not open input file '%s': %s\n", options->in_file, strerror(errno)); + free(options); + return 1; + } + + memset(&filestats, '\0', sizeof(struct stat)); + fstat(fileno(iplist), &filestats); + + plist_entire = (char *) malloc(sizeof(char) * (filestats.st_size + 1)); + if(plist_entire == NULL) + { + fprintf(stderr, "ERROR: Failed to allocate buffer to read from file\n"); + free(options); + return 1; + } + read_size = fread(plist_entire, sizeof(char), filestats.st_size, iplist); + if (read_size != (size_t)filestats.st_size) { + fprintf(stderr, "ERROR: Could not read from input file '%s'\n", options->in_file); + fclose(iplist); + free(plist_entire); + free(options); + return 1; + } + plist_entire[read_size] = '\0'; + fclose(iplist); + } + + if (options->out_fmt == 0) { + // convert from binary to xml or vice-versa + if (plist_is_binary(plist_entire, read_size)) + { + input_res = plist_from_bin(plist_entire, read_size, &root_node); + if (input_res == PLIST_ERR_SUCCESS) { + if (options->flags & OPT_SORT) { + plist_sort(root_node); + } + output_res = plist_to_xml(root_node, &plist_out, &size); + } + } + else + { + input_res = plist_from_xml(plist_entire, read_size, &root_node); + if (input_res == PLIST_ERR_SUCCESS) { + if (options->flags & OPT_SORT) { + plist_sort(root_node); + } + output_res = plist_to_bin(root_node, &plist_out, &size); + } + } + } + else + { + input_res = plist_from_memory(plist_entire, read_size, &root_node, NULL); + if (input_res == PLIST_ERR_SUCCESS) { + + if (options->nodepath) { + char *copy = strdup(options->nodepath); + char *tok, *saveptr = NULL; + if (!copy) { + plist_free(root_node); + free(plist_entire); + free(options); + return 1; + } + + plist_t current = root_node; + for (tok = strtok_r(copy, "/", &saveptr); tok; tok = strtok_r(NULL, "/", &saveptr)) { + if (*tok == '\0') continue; + switch (plist_get_node_type(current)) { + case PLIST_DICT: + current = plist_dict_get_item(current, tok); + break; + case PLIST_ARRAY: { + char* endp = NULL; + uint32_t idx = strtoul(tok, &endp, 10); + if (endp == tok || *endp != '\0') { + current = NULL; + break; + } + if (idx >= plist_array_get_size(current)) { + current = NULL; + break; + } + current = plist_array_get_item(current, idx); + break; + } + default: + current = NULL; + break; + } + if (!current) { + break; + } + } + free(copy); + if (current) { + plist_t destnode = plist_copy(current); + plist_free(root_node); + root_node = destnode; + } else { + fprintf(stderr, "ERROR: nodepath '%s' is invalid\n", options->nodepath); + plist_free(root_node); + free(plist_entire); + free(options); + return 1; + } + } + + if (options->flags & OPT_SORT) { + plist_sort(root_node); + } + if (options->out_fmt == PLIST_FORMAT_BINARY) { + output_res = plist_to_bin(root_node, &plist_out, &size); + } else if (options->out_fmt == PLIST_FORMAT_XML) { + output_res = plist_to_xml(root_node, &plist_out, &size); + } else if (options->out_fmt == PLIST_FORMAT_JSON) { + plist_write_options_t wropts = PLIST_OPT_NONE; + if (options->flags & OPT_COMPACT) wropts |= PLIST_OPT_COMPACT; + if (options->flags & OPT_COERCE) wropts |= PLIST_OPT_COERCE; + output_res = plist_to_json_with_options(root_node, &plist_out, &size, wropts); + } else if (options->out_fmt == PLIST_FORMAT_OSTEP) { + plist_write_options_t wropts = PLIST_OPT_NONE; + if (options->flags & OPT_COMPACT) wropts |= PLIST_OPT_COMPACT; + if (options->flags & OPT_COERCE) wropts |= PLIST_OPT_COERCE; + output_res = plist_to_openstep_with_options(root_node, &plist_out, &size, wropts); + } else { + plist_write_to_stream(root_node, stdout, options->out_fmt, PLIST_OPT_PARTIAL_DATA); + plist_free(root_node); + free(plist_entire); + free(options); + return 0; + } + } + } + plist_free(root_node); + free(plist_entire); + + if (plist_out) + { + if (options->out_file != NULL && strcmp(options->out_file, "-") != 0) + { + FILE *oplist = fopen(options->out_file, "wb"); + if (!oplist) { + fprintf(stderr, "ERROR: Could not open output file '%s': %s\n", options->out_file, strerror(errno)); + free(options); + return 1; + } + fwrite(plist_out, size, sizeof(char), oplist); + fclose(oplist); + } + // if no output file specified, write to stdout + else + fwrite(plist_out, size, sizeof(char), stdout); + + free(plist_out); + } + + if (input_res == PLIST_ERR_SUCCESS) { + switch (output_res) { + case PLIST_ERR_SUCCESS: + break; + case PLIST_ERR_CIRCULAR_REF: + fprintf(stderr, "ERROR: Circular reference detected.\n"); + ret = 5; + break; + case PLIST_ERR_MAX_NESTING: + fprintf(stderr, "ERROR: Output plist data exceeds maximum nesting depth.\n"); + ret = 4; + break; + case PLIST_ERR_FORMAT: + fprintf(stderr, "ERROR: Input plist data is not compatible with output format.\n"); + ret = 2; + break; + default: + fprintf(stderr, "ERROR: Failed to convert plist data (%d)\n", output_res); + ret = 1; + break; + } + } else { + switch (input_res) { + case PLIST_ERR_PARSE: + if (options->out_fmt == 0) { + fprintf(stderr, "ERROR: Could not parse plist data, expected XML or binary plist\n"); + } else { + fprintf(stderr, "ERROR: Could not parse plist data (%d)\n", input_res); + } + ret = 3; + break; + case PLIST_ERR_CIRCULAR_REF: + fprintf(stderr, "ERROR: Circular reference detected in input plist data.\n"); + ret = 5; + break; + case PLIST_ERR_MAX_NESTING: + fprintf(stderr, "ERROR: Input plist data exceeds maximum nesting depth.\n"); + ret = 4; + break; + default: + fprintf(stderr, "ERROR: Could not parse plist data (%d)\n", input_res); + ret = 1; + break; + } + } + + free(options); + return ret; +} |
