summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.gitattributes1
-rw-r--r--.github/workflows/build.yml134
-rw-r--r--.github/workflows/cifuzz.yml26
-rw-r--r--.github/workflows/codeql-analysis.yml53
-rw-r--r--.gitignore6
-rw-r--r--Makefile.am23
-rw-r--r--NEWS63
-rw-r--r--README.md296
-rwxr-xr-xautogen.sh28
-rw-r--r--configure.ac156
-rw-r--r--cython/Makefile.am44
-rw-r--r--cython/plist.pxd3
-rw-r--r--cython/plist.pyx67
-rw-r--r--docs/Makefile.am3
-rw-r--r--docs/plistutil.178
-rw-r--r--doxygen.cfg.in101
-rw-r--r--fuzz/Makefile.am27
-rwxr-xr-xfuzz/init-fuzzers.sh16
-rw-r--r--fuzz/jplist-crashes/clusterfuzz-testcase-minimized-jplist_fuzzer-499761467896627265
-rw-r--r--fuzz/jplist-crashes/clusterfuzz-testcase-minimized-jplist_fuzzer-51494554630881281
-rw-r--r--fuzz/jplist-crashes/clusterfuzz-testcase-minimized-jplist_fuzzer-5161359598288896bin0 -> 725 bytes
-rw-r--r--fuzz/jplist-crashes/clusterfuzz-testcase-minimized-jplist_fuzzer-54868076958842881
-rw-r--r--fuzz/jplist-crashes/clusterfuzz-testcase-minimized-jplist_fuzzer-55768333980794881
-rw-r--r--fuzz/jplist-crashes/clusterfuzz-testcase-minimized-jplist_fuzzer-57040166868746241
-rw-r--r--fuzz/jplist-crashes/clusterfuzz-testcase-minimized-jplist_fuzzer-66390764663603201
-rw-r--r--fuzz/jplist-leaks/clusterfuzz-testcase-minimized-jplist_fuzzer-48819332370923521
-rw-r--r--fuzz/jplist-leaks/clusterfuzz-testcase-minimized-jplist_fuzzer-50698839126179841
-rw-r--r--fuzz/jplist-leaks/clusterfuzz-testcase-minimized-jplist_fuzzer-58161116968386561
-rw-r--r--fuzz/jplist.dict52
-rw-r--r--fuzz/jplist_fuzzer.cc32
-rw-r--r--fuzz/jplist_fuzzer.options3
-rw-r--r--fuzz/oplist-crashes/clusterfuzz-testcase-minimized-oplist_fuzzer-45038154058301441
-rw-r--r--fuzz/oplist-crashes/clusterfuzz-testcase-minimized-oplist_fuzzer-4683683569467392bin0 -> 164 bytes
-rw-r--r--fuzz/oplist-crashes/clusterfuzz-testcase-minimized-oplist_fuzzer-47161941146992641
-rw-r--r--fuzz/oplist-crashes/clusterfuzz-testcase-minimized-oplist_fuzzer-4789915626110976bin0 -> 224 bytes
-rw-r--r--fuzz/oplist-leaks/clusterfuzz-testcase-minimized-oplist_fuzzer-60435486027284488
-rw-r--r--fuzz/oplist-leaks/clusterfuzz-testcase-minimized-oplist_fuzzer-64974369884733441
-rw-r--r--fuzz/oplist.dict51
-rw-r--r--fuzz/oplist_fuzzer.cc32
-rw-r--r--fuzz/oplist_fuzzer.options3
-rwxr-xr-xfuzz/test-fuzzers.sh16
-rwxr-xr-xgit-version-gen20
-rw-r--r--include/Makefile.am29
-rw-r--r--include/plist/Array.h14
-rw-r--r--include/plist/Boolean.h2
-rw-r--r--include/plist/Data.h2
-rw-r--r--include/plist/Date.h6
-rw-r--r--include/plist/Dictionary.h10
-rw-r--r--include/plist/Integer.h10
-rw-r--r--include/plist/Key.h4
-rw-r--r--include/plist/Real.h2
-rw-r--r--include/plist/String.h2
-rw-r--r--include/plist/Uid.h2
-rw-r--r--include/plist/plist.h529
-rw-r--r--libcnary/Makefile.am17
-rw-r--r--libcnary/include/node.h42
-rw-r--r--libcnary/include/node_list.h23
-rw-r--r--libcnary/node.c52
-rw-r--r--libcnary/node_list.c33
-rw-r--r--m4/ac_pkg_cython.m46
-rw-r--r--m4/as-compiler-flag.m44
-rw-r--r--m4/ax_pthread.m4251
-rw-r--r--m4/cython_python.m42
-rw-r--r--src/Array.cpp66
-rw-r--r--src/Boolean.cpp7
-rw-r--r--src/Data.cpp8
-rw-r--r--src/Date.cpp6
-rw-r--r--src/Dictionary.cpp42
-rw-r--r--src/Integer.cpp42
-rw-r--r--src/Key.cpp18
-rw-r--r--src/Makefile.am102
-rw-r--r--src/Node.cpp9
-rw-r--r--src/Real.cpp8
-rw-r--r--src/String.cpp18
-rw-r--r--src/Structure.cpp9
-rw-r--r--src/Uid.cpp7
-rw-r--r--src/bplist.c216
-rw-r--r--src/bytearray.c34
-rw-r--r--src/bytearray.h3
-rw-r--r--src/hashtable.c10
-rw-r--r--src/jplist.c856
-rw-r--r--src/jsmn.c289
-rw-r--r--src/jsmn.h92
-rw-r--r--src/libplist++-2.0.pc.in (renamed from src/libplist++.pc.in)4
-rw-r--r--src/libplist-2.0.pc.in (renamed from src/libplist.pc.in)2
-rw-r--r--src/oplist.c933
-rw-r--r--src/out-default.c491
-rw-r--r--src/out-limd.c449
-rw-r--r--src/out-plutil.c465
-rw-r--r--src/plist.c809
-rw-r--r--src/plist.h22
-rw-r--r--src/ptrarray.c2
-rw-r--r--src/strbuf.h1
-rw-r--r--src/time64.c55
-rw-r--r--src/time64.h2
-rw-r--r--src/xplist.c190
-rw-r--r--test/Makefile.am72
-rwxr-xr-xtest/amp.test10
-rwxr-xr-xtest/bigarray++.test15
-rwxr-xr-xtest/bigarray.test4
-rwxr-xr-xtest/cdata.test2
-rw-r--r--test/data/data.bplistbin0 -> 3718 bytes
-rw-r--r--test/data/int64_min_max.json1
-rw-r--r--test/data/j1.json1
-rw-r--r--test/data/j2.json1
-rw-r--r--test/data/o1.ostep45
-rw-r--r--test/data/o2.ostep17
-rw-r--r--test/data/o3.ostep16
-rw-r--r--test/data/test.strings12
-rw-r--r--test/data/uid.bplistbin0 -> 43 bytes
-rwxr-xr-xtest/dates.test4
-rwxr-xr-xtest/empty++.test15
-rwxr-xr-xtest/empty.test4
-rwxr-xr-xtest/empty_keys.test2
-rwxr-xr-xtest/entities.test2
-rwxr-xr-xtest/hex.test2
-rwxr-xr-xtest/huge++.test15
-rwxr-xr-xtest/huge.test4
-rw-r--r--test/integer_set.c130
-rwxr-xr-xtest/integer_set.test5
-rwxr-xr-xtest/invalid_tag.test10
-rwxr-xr-xtest/json-int64-min-max.test19
-rwxr-xr-xtest/json-invalid-types.test33
-rwxr-xr-xtest/json1.test19
-rwxr-xr-xtest/json2.test19
-rwxr-xr-xtest/json3.test24
-rwxr-xr-xtest/large++.test15
-rwxr-xr-xtest/large.test4
-rwxr-xr-xtest/malformed_dict.test10
-rwxr-xr-xtest/medium++.test15
-rwxr-xr-xtest/medium.test4
-rwxr-xr-xtest/order.test2
-rwxr-xr-xtest/ostep-comments.test20
-rwxr-xr-xtest/ostep-invalid-types.test33
-rwxr-xr-xtest/ostep-strings.test20
-rwxr-xr-xtest/ostep1.test20
-rwxr-xr-xtest/ostep2.test19
-rw-r--r--test/plist_btest.c131
-rw-r--r--test/plist_cmp.c58
-rw-r--r--test/plist_jtest.c131
-rw-r--r--test/plist_otest.c130
-rw-r--r--test/plist_test++.cpp103
-rw-r--r--test/plist_test.c12
-rwxr-xr-xtest/recursion.test10
-rwxr-xr-xtest/signedunsigned1.test4
-rwxr-xr-xtest/signedunsigned2.test4
-rwxr-xr-xtest/signedunsigned3.test4
-rwxr-xr-xtest/small++.test15
-rwxr-xr-xtest/small.test4
-rwxr-xr-xtest/timezone1.test6
-rwxr-xr-xtest/timezone2.test8
-rwxr-xr-xtest/uid.test15
-rw-r--r--tools/Makefile.am7
-rw-r--r--tools/plistutil.c264
154 files changed, 7978 insertions, 1320 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/workflows/build.yml b/.github/workflows/build.yml
index 2036484..f34d56f 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -1,22 +1,45 @@
name: build
-on: [push]
+on:
+ push:
+ schedule:
+ - cron: '0 0 1 * *'
jobs:
build-linux-ubuntu:
runs-on: ubuntu-latest
steps:
- name: install dependencies
- run: sudo apt-get install cython
- - uses: actions/checkout@v1
+ run: |
+ sudo apt-get update
+ sudo apt-get install cython3
+ - name: prepare environment
+ run: |
+ echo "target_triplet=`gcc -dumpmachine`" >> $GITHUB_ENV
+ - uses: actions/checkout@v4
+ with:
+ fetch-depth: 0
- name: autogen
- run: ./autogen.sh
+ 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 distcheck
- run: make distcheck
+ - 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@v4
+ with:
+ name: libplist-latest_${{env.target_triplet}}
+ path: libplist.tar
build-macOS:
runs-on: macOS-latest
steps:
@@ -27,28 +50,92 @@ jobs:
else
brew install libtool autoconf automake
fi
- pip install cython
+ pip3 install cython
shell: bash
- - uses: actions/checkout@v1
+ - uses: actions/checkout@v4
+ with:
+ fetch-depth: 0
- name: autogen
- run: ./autogen.sh
+ 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_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@v4
+ with:
+ name: libplist-latest_macOS
+ path: libplist.tar
build-windows:
- runs-on: windows-latest
+ runs-on: windows-2019
+ defaults:
+ run:
+ shell: msys2 {0}
+ strategy:
+ fail-fast: false
+ matrix:
+ include: [
+ { msystem: MINGW64, arch: x86_64 },
+ { msystem: MINGW32, arch: i686 }
+ ]
steps:
- - uses: numworks/setup-msys2@v1
- - name: install dependencies
- run: msys2do pacman -S --noconfirm mingw-w64-x86_64-gcc make libtool autoconf automake-wrapper
- - uses: actions/checkout@v1
+ - uses: msys2/setup-msys2@v2
+ with:
+ msystem: ${{ matrix.msystem }}
+ release: false
+ update: false
+ install: >-
+ base-devel
+ git
+ mingw-w64-${{ matrix.arch }}-gcc
+ 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@v4
+ with:
+ fetch-depth: 0
- name: autogen
- run: msys2do ./autogen.sh --without-cython
+ run: ./autogen.sh CC=gcc CXX=g++ --enable-debug
- name: make
- run: msys2do make
+ run: make
- name: make check
- run: msys2do make check
+ run: make check
- name: print test logs
run: |
for I in test/*.trs; do
@@ -61,4 +148,15 @@ jobs:
fi
done
shell: bash
- if: always()
+ - 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@v4
+ 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..1a69794
--- /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@v3
+ 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..9e02074
--- /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@v3
+ 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@v2
+ 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@v2
diff --git a/.gitignore b/.gitignore
index 74ad8ba..e63b0b6 100644
--- a/.gitignore
+++ b/.gitignore
@@ -49,7 +49,13 @@ 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/data/*.out
+test/*.trs
cython/Makefile
cython/Makefile.in
cython/.deps
diff --git a/Makefile.am b/Makefile.am
index 5f36fd7..f44a6bc 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -1,18 +1,31 @@
AUTOMAKE_OPTIONS = foreign
ACLOCAL_AMFLAGS = -I m4
-SUBDIRS = libcnary src include tools test
+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:
+ echo $(VERSION) > $(distdir)/.tarball-version
+
docs/html: $(top_builddir)/doxygen.cfg $(top_srcdir)/include/plist/*.h
- rm -rf docs
+ rm -rf docs/html
doxygen doxygen.cfg
-docs: doxygen.cfg docs/html
+docs/README.doxygen.md: README.md
+ awk '/\#\# Table of Contents/{while(getline && $$0 != ""){}{print "[TOC]"}}1' README.md > $@
-clean-local:
- rm -rf docs
+docs: doxygen.cfg docs/README.doxygen.md docs/html
diff --git a/NEWS b/NEWS
index 0238e7e..760f4e4 100644
--- a/NEWS
+++ b/NEWS
@@ -1,3 +1,66 @@
+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
~~~~~~~~~~~~~
diff --git a/README.md b/README.md
index 7a4e0d1..3d36250 100644
--- a/README.md
+++ b/README.md
@@ -1,55 +1,283 @@
# libplist
-## About
+*A small portable C library to handle Apple Property List files in binary, XML,
+JSON, or OpenStep format.*
-A small portable C library to handle Apple Property List files in binary or XML.
+![](https://github.com/libimobiledevice/libplist/workflows/build/badge.svg)
+![](https://github.com/libimobiledevice/libplist/workflows/CodeQL/badge.svg)
-## Requirements
+## 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)
-Software:
-* make
-* autoheader
-* automake
-* autoconf
-* libtool
-* pkg-config
-* gcc or clang
+## Features
-Optional:
-* cython (Python bindings)
-* doxygen (Documentation)
+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`.
-## Installation
+Some key features are:
-To compile run:
-```bash
-./autogen.sh
-make
-sudo make install
+- **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 require a custom prefix or other option being passed to `./configure`
-you can pass them directly to `./autogen.sh` like this:
-```bash
-./autogen.sh --prefix=/opt/local --without-cython
+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
```
-## Who/What/Where?
+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.
-* Home: https://www.libimobiledevice.org/
-* Code: `git clone https://git.libimobiledevice.org/libplist.git`
-* Code (Mirror): `git clone https://github.com/libimobiledevice/libplist.git`
-* Tickets: https://github.com/libimobiledevice/libplist/issues
+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://git.libimobiledevice.org/libplist.git
+* Repository (Mirror): https://github.com/libimobiledevice/libplist.git
+* Issue Tracker: https://github.com/libimobiledevice/libplist/issues
* Mailing List: https://lists.libimobiledevice.org/mailman/listinfo/libimobiledevice-devel
-* IRC: irc://irc.freenode.net#libimobiledevice
* 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, iPod, and iPod Touch are trademarks of Apple Inc.
-libimobiledevice is an independent software library and has not been
-authorized, sponsored, or otherwise approved by Apple Inc.
+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-02-21
-README Updated on: 2019-05-16
diff --git a/autogen.sh b/autogen.sh
index 9164d13..5a0ec43 100755
--- a/autogen.sh
+++ b/autogen.sh
@@ -1,22 +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 "$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" "$@"
+ $srcdir/configure "$@"
fi
diff --git a/configure.ac b/configure.ac
index a2edbc4..757ea95 100644
--- a/configure.ac
+++ b/configure.ac
@@ -1,8 +1,8 @@
# -*- Autoconf -*-
# Process this file with autoconf to produce a configure script.
-AC_PREREQ(2.64)
-AC_INIT([libplist], [2.2.0], [https://github.com/libimobiledevice/libplist/issues],, [https://libimobiledevice.org])
+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/])
@@ -15,14 +15,13 @@ dnl libtool versioning
# changes to the signature and the semantic)
# ? :+1 : ? == just internal changes
# CURRENT : REVISION : AGE
-LIBPLIST_SO_VERSION=6:0:3
+LIBPLIST_SO_VERSION=8:0:4
AC_SUBST(LIBPLIST_SO_VERSION)
-# prefer clang if it is available and CC is not set
-if test -z "$CC" && test -z "$CXX" && test -x "`which clang`"; then
- CC=clang
- CXX=clang++
+# 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.
@@ -39,10 +38,9 @@ AC_COMPILE_IFELSE([AC_LANG_PROGRAM([],[])],
AC_LANG_POP
AM_PROG_CC_C_O
-AC_PROG_LIBTOOL
+LT_INIT
# Checks for header files.
-AC_HEADER_STDC
AC_CHECK_HEADERS([stdint.h stdlib.h string.h])
# Checks for typedefs, structures, and compiler characteristics.
@@ -54,7 +52,7 @@ AC_TYPE_UINT32_T
AC_TYPE_UINT8_T
# Checks for library functions.
-AC_CHECK_FUNCS([asprintf strcasecmp strdup strerror strndup stpcpy vasprintf gmtime_r localtime_r timegm strptime memmem])
+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])],
@@ -62,58 +60,81 @@ AC_C_BIGENDIAN([AC_DEFINE([__BIG_ENDIAN__], [1], [big endian])],
# Check for operating system
-AC_MSG_CHECKING([wether we need platform-specific build settings for ${host_os}])
+AC_MSG_CHECKING([for platform-specific build settings])
case ${host_os} in
*mingw32*|*cygwin*)
- AC_MSG_RESULT([yes])
+ AC_MSG_RESULT([${host_os}])
win32=true
;;
darwin*|*android*)
- AC_MSG_RESULT([no])
+ AC_MSG_RESULT([${host_os}])
;;
*)
- AC_MSG_RESULT([yes])
+ AC_MSG_RESULT([${host_os}])
AX_PTHREAD([], [AC_MSG_ERROR([pthread is required to build libplist])])
AC_CHECK_LIB(pthread, [pthread_once], [], [AC_MSG_ERROR([pthread with pthread_once required to build libplist])])
;;
esac
AM_CONDITIONAL(WIN32, test x$win32 = xtrue)
-# Check if we need libm for fmin
-CACHED_CFLAGS="$CFLAGS"
-CFLAGS="-O0"
-AC_CACHE_CHECK(if fmin is a builtin function, ac_cv_fmin_builtin,
- AC_TRY_LINK([
- #include <math.h>
- #include <float.h>
- ], [
- double val = 3.1415f * 0.55555f;
- double diff = fmin(val, DBL_MAX);
- if (diff > 0) return 1;
- ], ac_cv_fmin_builtin=yes, ac_cv_fmin_builtin=no))
-CFLAGS="$CACHED_CFLAGS"
+AC_SEARCH_LIBS([fmin],[m])
+
+# Check if the C compiler supports __attribute__((constructor))
+AC_CACHE_CHECK([wether the C compiler supports constructor/destructor attributes],
+ ac_cv_attribute_constructor, [
+ ac_cv_attribute_constructor=no
+ AC_COMPILE_IFELSE([AC_LANG_PROGRAM(
+ [[
+ #ifndef __has_attribute
+ #define __has_attribute(x) 0
+ #endif
+ #if !__has_attribute(constructor)
+ #error No constructor attribute
+ #endif
+ #if !__has_attribute(destructor)
+ #error No destructor attribute
+ #endif
+ static void __attribute__((constructor)) test_constructor(void) {
+ }
+ static void __attribute__((destructor)) test_destructor(void) {
+ }
+ ]], [])],
+ [ac_cv_attribute_constructor=yes]
+ )]
+)
+if test "$ac_cv_attribute_constructor" = "yes"; then
+ AC_DEFINE(HAVE_ATTRIBUTE_CONSTRUCTOR, 1, [Define if the C compiler supports constructor/destructor attributes])
+fi
# Check if struct tm has a tm_gmtoff member
AC_CACHE_CHECK(for tm_gmtoff in struct tm, ac_cv_struct_tm_gmtoff,
- AC_TRY_COMPILE([
- #include <time.h>
- ], [
- struct tm tm;
- tm.tm_gmtoff = 1;
- ], ac_cv_struct_tm_gmtoff=yes, ac_cv_struct_tm_gmtoff=no))
+ 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])
+ 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_TRY_COMPILE([
- #include <time.h>
- ], [
- struct tm tm;
- tm.tm_zone = 1;
- ], ac_cv_struct_tm_zone=yes, ac_cv_struct_tm_zone=no))
+ 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])
@@ -126,16 +147,19 @@ AC_ARG_WITH([cython],
[build_cython=false],
[build_cython=true])
if test "$build_cython" = "true"; then
- AM_PATH_PYTHON(2.3)
- AC_PROG_CYTHON(0.17.0)
- CYTHON_PYTHON
+ AC_PROG_CYTHON([0.17.0])
+ if [test "x$CYTHON" != "xfalse"]; then
+ AM_PATH_PYTHON([2.3], [CYTHON_PYTHON])
+ fi
else
CYTHON=false
fi
if [test "x$CYTHON" != "xfalse"]; then
PKG_PROG_PKG_CONFIG
AC_MSG_CHECKING([for libplist Cython bindings])
- CYTHON_PLIST_INCLUDE_DIR=$($PKG_CONFIG --variable=includedir libplist)/plist/cython
+ 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
@@ -150,16 +174,12 @@ else
fi
AM_CONDITIONAL([HAVE_CYTHON],[test "x$cython_python_bindings" = "xyes"])
-AS_COMPILER_FLAGS(GLOBAL_CFLAGS, "-Wall -Wextra -Wredundant-decls -Wshadow -Wpointer-arith -Wwrite-strings -Wswitch-default -Wno-unused-parameter -Wno-strict-aliasing -fvisibility=hidden $PTHREAD_CFLAGS")
+AS_COMPILER_FLAGS(GLOBAL_CFLAGS, "-Wall -Wextra -Wredundant-decls -Wshadow -Wpointer-arith -Wwrite-strings -Wswitch-default -Wno-unused-parameter -Wno-strict-aliasing $PTHREAD_CFLAGS")
GLOBAL_LDFLAGS="$PTHREAD_LIBS"
-if test "x$ac_cv_fmin_builtin" != "xyes"; then
- GLOBAL_LDFLAGS+=" -lm"
-fi
-
AC_ARG_ENABLE(debug,
AS_HELP_STRING([--enable-debug],
- [enable debugging, default: no]),
+ [build debug message output code (default is no)]),
[case "${enableval}" in
yes) debug=yes ;;
no) debug=no ;;
@@ -168,11 +188,21 @@ esac],
[debug=no])
if (test "x$debug" = "xyes"); then
- AC_DEFINE(DEBUG, 1, [Define if debug code should be enabled.])
+ 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
@@ -192,6 +222,12 @@ AC_ARG_WITH([fuzzers],
[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=yes])
+
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.])
@@ -262,9 +298,14 @@ if test "x$build_sanitizers" = "xyes"; then
fi
if test "x$build_fuzzers" = "xyes"; then
- if test "$CXX" != "clang++"; 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)])
- fi
+ ;;
+ esac
CFLAGS+=" -DFUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION"
@@ -273,6 +314,7 @@ if test "x$build_fuzzers" = "xyes"; then
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])
@@ -280,19 +322,21 @@ fi
m4_ifdef([AM_SILENT_RULES],[AM_SILENT_RULES([yes])])
-AC_OUTPUT([
+AC_CONFIG_FILES([
Makefile
libcnary/Makefile
src/Makefile
-src/libplist.pc
-src/libplist++.pc
+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:
diff --git a/cython/Makefile.am b/cython/Makefile.am
index bce8121..bbc51c9 100644
--- a/cython/Makefile.am
+++ b/cython/Makefile.am
@@ -3,24 +3,48 @@ AM_CPPFLAGS = -I$(top_srcdir)/include
AM_CFLAGS = $(GLOBAL_CFLAGS)
AM_LDFLAGS = $(GLOBAL_LDFLAGS)
-EXTRA_DIST = plist.pyx plist.pxd
+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
+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)
-plist_la_LIBADD = $(top_builddir)/src/libplist.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)
diff --git a/cython/plist.pxd b/cython/plist.pxd
index b11d80d..5a41bf8 100644
--- a/cython/plist.pxd
+++ b/cython/plist.pxd
@@ -19,7 +19,8 @@ cdef class Bool(Node):
cdef class Integer(Node):
cpdef set_value(self, object value)
- cpdef uint64_t get_value(self)
+ cpdef get_value(self)
+ cpdef bint is_negative(self)
cdef class Uid(Node):
cpdef set_value(self, object value)
diff --git a/cython/plist.pyx b/cython/plist.pyx
index bfecf85..b5f4ef6 100644
--- a/cython/plist.pyx
+++ b/cython/plist.pyx
@@ -5,7 +5,7 @@ from libc.stdint cimport *
cdef extern from *:
ctypedef enum plist_type:
PLIST_BOOLEAN,
- PLIST_UINT,
+ PLIST_INT,
PLIST_REAL,
PLIST_STRING,
PLIST_ARRAY,
@@ -14,6 +14,7 @@ cdef extern from *:
PLIST_DATA,
PLIST_KEY,
PLIST_UID,
+ PLIST_NULL,
PLIST_NONE
plist_t plist_new_bool(uint8_t val)
@@ -24,6 +25,10 @@ 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)
@@ -47,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)
@@ -77,6 +84,8 @@ cdef extern from *:
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
@@ -177,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 uint64_t 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()
@@ -209,10 +221,18 @@ cdef class Integer(Node):
cpdef set_value(self, object value):
plist_set_uint_val(self._c_node, int(value))
- cpdef uint64_t 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)
@@ -281,7 +301,8 @@ cdef class Uid(Node):
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()
@@ -312,6 +333,20 @@ cdef Uid Uid_factory(plist_t c_node, bint managed=True):
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):
@@ -683,7 +718,7 @@ cdef class Dict(Node):
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)
@@ -831,7 +866,7 @@ 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)
@@ -849,6 +884,8 @@ cdef object plist_t_to_node(plist_t c_plist, bint managed=True):
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
@@ -940,8 +977,10 @@ cpdef object dumps(value, fmt=FMT_XML, sort_keys=True, skipkeys=False):
node = Dict(value)
elif type(value) in (list, set, tuple):
node = Array(value)
+ else:
+ node = value
if fmt == FMT_XML:
- return node.to_xml()
+ return node.to_xml().encode('utf-8')
return node.to_bin()
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/plistutil.1 b/docs/plistutil.1
new file mode 100644
index 0000000..b1f7773
--- /dev/null
+++ b/docs/plistutil.1
@@ -0,0 +1,78 @@
+.TH "plistutil" 1
+.SH NAME
+plistutil \- Convert a plist FILE between binary, XML, and JSON format
+.SH SYNOPSIS
+.B plistutil
+[OPTIONS]
+[-i FILE]
+[-o FILE]
+.SH DESCRIPTION
+plistutil allows converting a Property List file between binary, XML, and JSON format.
+.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 \-c, \-\-compact
+JSON and OpenStep only: Print output in compact form. By default, the output
+will be pretty-printed.
+.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.in b/doxygen.cfg.in
index 9ed1b14..7aa54e8 100644
--- a/doxygen.cfg.in
+++ b/doxygen.cfg.in
@@ -155,13 +155,6 @@ QT_AUTOBRIEF = NO
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.
@@ -446,12 +439,6 @@ MAX_INITIALIZER_LINES = 30
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.
@@ -534,7 +521,8 @@ WARN_LOGFILE =
# directories like "/usr/src/myproject". Separate the files or directories
# with spaces.
-INPUT = include/plist/plist.h
+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, which is
@@ -585,7 +573,7 @@ EXCLUDE_PATTERNS =
# wildcard * is used, a substring. Examples: ANamespace, AClass,
# AClass::ANamespace, ANamespace::*Test
-EXCLUDE_SYMBOLS =
+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
@@ -638,6 +626,13 @@ FILTER_PATTERNS =
FILTER_SOURCE_FILES = NO
+# 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
+
#---------------------------------------------------------------------------
# configuration options related to source browsing
#---------------------------------------------------------------------------
@@ -703,12 +698,6 @@ VERBATIM_HEADERS = NO
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
@@ -758,12 +747,6 @@ HTML_FOOTER =
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)
@@ -916,10 +899,10 @@ MAKEINDEX_CMD_NAME = makeindex
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.
+# by the printer. Possible values are: a4, letter, legal and executive.
+# If left blank a4 will be used.
-PAPER_TYPE = a4wide
+PAPER_TYPE = a4
# The EXTRA_PACKAGES tag can be to specify one or more names of LaTeX
# packages that should be included in the LaTeX output.
@@ -1045,18 +1028,6 @@ GENERATE_XML = NO
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
@@ -1124,13 +1095,13 @@ ENABLE_PREPROCESSING = YES
# compilation will be performed. Macro expansion can be done in a controlled
# way by setting EXPAND_ONLY_PREDEF to YES.
-MACRO_EXPANSION = NO
+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.
-EXPAND_ONLY_PREDEF = NO
+EXPAND_ONLY_PREDEF = YES
# 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.
@@ -1165,7 +1136,7 @@ PREDEFINED =
# 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 =
+EXPAND_AS_DEFINED = PLIST_API
# 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
@@ -1213,33 +1184,10 @@ ALLEXTERNALS = NO
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.
@@ -1253,21 +1201,8 @@ HIDE_UNDOC_RELATIONS = YES
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.
+# 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 =
diff --git a/fuzz/Makefile.am b/fuzz/Makefile.am
index b9798f9..8ea3fb0 100644
--- a/fuzz/Makefile.am
+++ b/fuzz/Makefile.am
@@ -19,18 +19,37 @@ LIBFUZZER_SRC:
CLEANFILES = libFuzzer.a
-noinst_PROGRAMS = xplist_fuzzer bplist_fuzzer
+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.la libFuzzer.a
+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.la libFuzzer.a
+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 init-fuzzers.sh test-fuzzers.sh 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/init-fuzzers.sh b/fuzz/init-fuzzers.sh
index 4d28016..c9b1955 100755
--- a/fuzz/init-fuzzers.sh
+++ b/fuzz/init-fuzzers.sh
@@ -5,7 +5,7 @@ FUZZDIR=`dirname $0`
cd ${FUZZDIR}
-if ! test -x xplist_fuzzer || ! test -x bplist_fuzzer; then
+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
@@ -19,5 +19,19 @@ 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
new file mode 100644
index 0000000..10c37ec
--- /dev/null
+++ b/fuzz/jplist-crashes/clusterfuzz-testcase-minimized-jplist_fuzzer-5161359598288896
Binary files differ
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/fuzz/jplist_fuzzer.cc b/fuzz/jplist_fuzzer.cc
new file mode 100644
index 0000000..a10da59
--- /dev/null
+++ b/fuzz/jplist_fuzzer.cc
@@ -0,0 +1,32 @@
+/*
+ * jplist_fuzzer.cc
+ * JSON plist fuzz target for libFuzzer
+ *
+ * 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
+ * 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>
+
+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);
+
+ 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
new file mode 100644
index 0000000..25d9ed9
--- /dev/null
+++ b/fuzz/oplist-crashes/clusterfuzz-testcase-minimized-oplist_fuzzer-4683683569467392
Binary files differ
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
new file mode 100644
index 0000000..a36e0a2
--- /dev/null
+++ b/fuzz/oplist-crashes/clusterfuzz-testcase-minimized-oplist_fuzzer-4789915626110976
Binary files differ
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/fuzz/oplist_fuzzer.cc b/fuzz/oplist_fuzzer.cc
new file mode 100644
index 0000000..0fabed8
--- /dev/null
+++ b/fuzz/oplist_fuzzer.cc
@@ -0,0 +1,32 @@
+/*
+ * oplist_fuzzer.cc
+ * OpenStep plist fuzz target for libFuzzer
+ *
+ * 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
+ */
+
+#include <plist/plist.h>
+#include <stdio.h>
+
+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);
+
+ 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
index b0a8367..4fdf82b 100755
--- a/fuzz/test-fuzzers.sh
+++ b/fuzz/test-fuzzers.sh
@@ -5,13 +5,13 @@ FUZZDIR=`dirname $0`
cd ${FUZZDIR}
-if ! test -x xplist_fuzzer || ! test -x bplist_fuzzer; then
+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; then
+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
@@ -29,5 +29,17 @@ if ! ./bplist_fuzzer bplist-input -dict=bplist.dict -max_len=4096 -runs=10000; t
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/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/Makefile.am b/include/Makefile.am
index 4e4db2e..2fa500e 100644
--- a/include/Makefile.am
+++ b/include/Makefile.am
@@ -1,14 +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
+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 745b750..0239c78 100644
--- a/include/plist/Array.h
+++ b/include/plist/Array.h
@@ -34,12 +34,24 @@ public :
Array(Node* parent = NULL);
Array(plist_t node, Node* parent = NULL);
Array(const Array& a);
- Array& operator=(Array& a);
+ Array& operator=(const Array& a);
virtual ~Array();
Node* Clone() const;
+ typedef std::vector<Node*>::iterator iterator;
+ typedef std::vector<Node*>::const_iterator const_iterator;
+
Node* operator[](unsigned int index);
+ 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(Node* node);
void Insert(Node* node, unsigned int pos);
void Remove(Node* node);
diff --git a/include/plist/Boolean.h b/include/plist/Boolean.h
index 307a1ff..9ecbc48 100644
--- a/include/plist/Boolean.h
+++ b/include/plist/Boolean.h
@@ -33,7 +33,7 @@ public :
Boolean(Node* parent = NULL);
Boolean(plist_t node, Node* parent = NULL);
Boolean(const Boolean& b);
- Boolean& operator=(Boolean& b);
+ Boolean& operator=(const Boolean& b);
Boolean(bool b);
virtual ~Boolean();
diff --git a/include/plist/Data.h b/include/plist/Data.h
index c9c089b..b566a6c 100644
--- a/include/plist/Data.h
+++ b/include/plist/Data.h
@@ -34,7 +34,7 @@ public :
Data(Node* parent = NULL);
Data(plist_t node, Node* parent = NULL);
Data(const Data& d);
- Data& operator=(Data& d);
+ Data& operator=(const Data& b);
Data(const std::vector<char>& buff);
virtual ~Data();
diff --git a/include/plist/Date.h b/include/plist/Date.h
index 510a349..5113cf3 100644
--- a/include/plist/Date.h
+++ b/include/plist/Date.h
@@ -24,7 +24,11 @@
#include <plist/Node.h>
#include <ctime>
+#ifdef _MSC_VER
+#include <winsock2.h>
+#else
#include <sys/time.h>
+#endif
namespace PList
{
@@ -35,7 +39,7 @@ public :
Date(Node* parent = NULL);
Date(plist_t node, Node* parent = NULL);
Date(const Date& d);
- Date& operator=(Date& d);
+ Date& operator=(const Date& d);
Date(timeval t);
virtual ~Date();
diff --git a/include/plist/Dictionary.h b/include/plist/Dictionary.h
index 71d40ac..583a430 100644
--- a/include/plist/Dictionary.h
+++ b/include/plist/Dictionary.h
@@ -35,7 +35,7 @@ public :
Dictionary(Node* parent = NULL);
Dictionary(plist_t node, Node* parent = NULL);
Dictionary(const Dictionary& d);
- Dictionary& operator=(Dictionary& d);
+ Dictionary& operator=(const Dictionary& d);
virtual ~Dictionary();
Node* Clone() const;
@@ -45,17 +45,21 @@ public :
Node* operator[](const std::string& key);
iterator Begin();
+ iterator begin();
iterator End();
+ iterator end();
iterator Find(const std::string& key);
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);
- iterator Insert(const std::string& key, Node* node) PLIST_WARN_DEPRECATED("use Set() instead");
void Remove(Node* node);
void Remove(const std::string& key);
- std::string GetNodeKey(Node* key);
+ std::string GetNodeKey(Node* node);
private :
std::map<std::string,Node*> _map;
diff --git a/include/plist/Integer.h b/include/plist/Integer.h
index adbc39a..1a4d980 100644
--- a/include/plist/Integer.h
+++ b/include/plist/Integer.h
@@ -33,14 +33,20 @@ public :
Integer(Node* parent = NULL);
Integer(plist_t node, Node* parent = NULL);
Integer(const Integer& i);
- Integer& operator=(Integer& i);
+ Integer& operator=(const Integer& i);
Integer(uint64_t i);
+ Integer(int64_t i);
virtual ~Integer();
Node* Clone() const;
+ void SetValue(int64_t i);
void SetValue(uint64_t i);
- uint64_t GetValue() const;
+ void SetUnsignedValue(uint64_t i);
+ int64_t GetValue() const;
+ uint64_t GetUnsignedValue() const;
+
+ bool isNegative() const;
};
};
diff --git a/include/plist/Key.h b/include/plist/Key.h
index c680f1c..bd5f7bc 100644
--- a/include/plist/Key.h
+++ b/include/plist/Key.h
@@ -33,8 +33,8 @@ class Key : public Node
public :
Key(Node* parent = NULL);
Key(plist_t node, Node* parent = NULL);
- Key(const Key& s);
- Key& operator=(Key& s);
+ Key(const Key& k);
+ Key& operator=(const Key& k);
Key(const std::string& s);
virtual ~Key();
diff --git a/include/plist/Real.h b/include/plist/Real.h
index c2d55f8..5afb0c0 100644
--- a/include/plist/Real.h
+++ b/include/plist/Real.h
@@ -33,7 +33,7 @@ public :
Real(Node* parent = NULL);
Real(plist_t node, Node* parent = NULL);
Real(const Real& d);
- Real& operator=(Real& d);
+ Real& operator=(const Real& d);
Real(double d);
virtual ~Real();
diff --git a/include/plist/String.h b/include/plist/String.h
index 80290b3..9aba16b 100644
--- a/include/plist/String.h
+++ b/include/plist/String.h
@@ -34,7 +34,7 @@ public :
String(Node* parent = NULL);
String(plist_t node, Node* parent = NULL);
String(const String& s);
- String& operator=(String& s);
+ String& operator=(const String& s);
String(const std::string& s);
virtual ~String();
diff --git a/include/plist/Uid.h b/include/plist/Uid.h
index 2d8375b..af6e51d 100644
--- a/include/plist/Uid.h
+++ b/include/plist/Uid.h
@@ -33,7 +33,7 @@ public :
Uid(Node* parent = NULL);
Uid(plist_t node, Node* parent = NULL);
Uid(const Uid& i);
- Uid& operator=(Uid& i);
+ Uid& operator=(const Uid& i);
Uid(uint64_t i);
virtual ~Uid();
diff --git a/include/plist/plist.h b/include/plist/plist.h
index ab91612..46aca16 100644
--- a/include/plist/plist.h
+++ b/include/plist/plist.h
@@ -3,7 +3,7 @@
* @brief Main include of libplist
* \internal
*
- * Copyright (c) 2012-2019 Nikias Bassen, 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
@@ -44,6 +44,7 @@ extern "C"
#include <stdint.h>
#endif
+/*{{{ deprecation macros */
#ifdef __llvm__
#if defined(__has_extension)
#if (__has_extension(attribute_deprecated_with_message))
@@ -72,12 +73,24 @@ extern "C"
#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
*/
/*@{*/
@@ -103,19 +116,68 @@ extern "C"
*/
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 =-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_NONE /**< No type */
+ 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_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_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)
+
/********************************************
* *
@@ -129,7 +191,7 @@ extern "C"
* @return the created plist
* @sa #plist_type
*/
- plist_t plist_new_dict(void);
+ PLIST_API plist_t plist_new_dict(void);
/**
* Create a new root plist_t type #PLIST_ARRAY
@@ -137,7 +199,7 @@ extern "C"
* @return the created plist
* @sa #plist_type
*/
- plist_t plist_new_array(void);
+ PLIST_API plist_t plist_new_array(void);
/**
* Create a new plist_t type #PLIST_STRING
@@ -146,7 +208,7 @@ extern "C"
* @return the created item
* @sa #plist_type
*/
- plist_t plist_new_string(const char *val);
+ PLIST_API plist_t plist_new_string(const char *val);
/**
* Create a new plist_t type #PLIST_BOOLEAN
@@ -155,16 +217,29 @@ extern "C"
* @return the created item
* @sa #plist_type
*/
- plist_t plist_new_bool(uint8_t val);
+ 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_t plist_new_uint(uint64_t val);
+ 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
@@ -173,7 +248,7 @@ extern "C"
* @return the created item
* @sa #plist_type
*/
- plist_t plist_new_real(double val);
+ PLIST_API plist_t plist_new_real(double val);
/**
* Create a new plist_t type #PLIST_DATA
@@ -183,7 +258,7 @@ extern "C"
* @return the created item
* @sa #plist_type
*/
- plist_t plist_new_data(const char *val, uint64_t length);
+ PLIST_API plist_t plist_new_data(const char *val, uint64_t length);
/**
* Create a new plist_t type #PLIST_DATE
@@ -193,7 +268,7 @@ extern "C"
* @return the created item
* @sa #plist_type
*/
- plist_t plist_new_date(int32_t sec, int32_t usec);
+ PLIST_API plist_t plist_new_date(int32_t sec, int32_t usec);
/**
* Create a new plist_t type #PLIST_UID
@@ -202,14 +277,23 @@ extern "C"
* @return the created item
* @sa #plist_type
*/
- plist_t plist_new_uid(uint64_t val);
+ 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
*
* @param plist the plist to free
*/
- void plist_free(plist_t plist);
+ PLIST_API void plist_free(plist_t plist);
/**
* Return a copy of passed node and it's children
@@ -217,7 +301,7 @@ extern "C"
* @param node the plist to copy
* @return copied plist
*/
- plist_t plist_copy(plist_t node);
+ PLIST_API plist_t plist_copy(plist_t node);
/********************************************
@@ -232,7 +316,7 @@ extern "C"
* @param node the node of type #PLIST_ARRAY
* @return size of the #PLIST_ARRAY node
*/
- uint32_t plist_array_get_size(plist_t node);
+ PLIST_API uint32_t plist_array_get_size(plist_t node);
/**
* Get the nth item in a #PLIST_ARRAY node.
@@ -241,7 +325,7 @@ extern "C"
* @param n the index of the item to get. Range is [0, array_size[
* @return the nth item or NULL if node is not of type #PLIST_ARRAY
*/
- plist_t plist_array_get_item(plist_t node, uint32_t n);
+ PLIST_API plist_t plist_array_get_item(plist_t node, uint32_t n);
/**
* Get the index of an item. item must be a member of a #PLIST_ARRAY node.
@@ -249,7 +333,7 @@ extern "C"
* @param node the node
* @return the node index or UINT_MAX if node index can't be determined
*/
- uint32_t plist_array_get_item_index(plist_t node);
+ PLIST_API uint32_t plist_array_get_item_index(plist_t node);
/**
* Set the nth item in a #PLIST_ARRAY node.
@@ -259,7 +343,7 @@ extern "C"
* @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[. Assert if n is not in range.
*/
- void plist_array_set_item(plist_t node, plist_t item, uint32_t n);
+ PLIST_API void 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.
@@ -267,7 +351,7 @@ extern "C"
* @param node the node of type #PLIST_ARRAY
* @param item the new item. The array is responsible for freeing item when it is no longer needed.
*/
- void plist_array_append_item(plist_t node, plist_t item);
+ PLIST_API void plist_array_append_item(plist_t node, plist_t item);
/**
* Insert a new item at position n in a #PLIST_ARRAY node.
@@ -276,7 +360,7 @@ extern "C"
* @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[. Assert if n is not in range.
*/
- void plist_array_insert_item(plist_t node, plist_t item, uint32_t n);
+ PLIST_API void plist_array_insert_item(plist_t node, plist_t item, uint32_t n);
/**
* Remove an existing position in a #PLIST_ARRAY node.
@@ -285,7 +369,7 @@ extern "C"
* @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.
*/
- void plist_array_remove_item(plist_t node, uint32_t n);
+ PLIST_API void plist_array_remove_item(plist_t node, uint32_t n);
/**
* Remove a node that is a child node of a #PLIST_ARRAY node.
@@ -293,7 +377,7 @@ extern "C"
*
* @param node The node to be removed from its #PLIST_ARRAY parent.
*/
- void plist_array_item_remove(plist_t node);
+ PLIST_API void plist_array_item_remove(plist_t node);
/**
* Create an iterator of a #PLIST_ARRAY node.
@@ -302,7 +386,7 @@ extern "C"
* @param node The node of type #PLIST_ARRAY
* @param iter Location to store the iterator for the array.
*/
- void plist_array_new_iter(plist_t node, plist_array_iter *iter);
+ PLIST_API void plist_array_new_iter(plist_t node, plist_array_iter *iter);
/**
* Increment iterator of a #PLIST_ARRAY node.
@@ -313,7 +397,7 @@ extern "C"
* returned item. Will be set to NULL when no more items are left
* to iterate.
*/
- void plist_array_next_item(plist_t node, plist_array_iter iter, plist_t *item);
+ PLIST_API void plist_array_next_item(plist_t node, plist_array_iter iter, plist_t *item);
/********************************************
@@ -328,7 +412,7 @@ extern "C"
* @param node the node of type #PLIST_DICT
* @return size of the #PLIST_DICT node
*/
- uint32_t plist_dict_get_size(plist_t node);
+ PLIST_API uint32_t plist_dict_get_size(plist_t node);
/**
* Create an iterator of a #PLIST_DICT node.
@@ -337,7 +421,7 @@ extern "C"
* @param node The node of type #PLIST_DICT.
* @param iter Location to store the iterator for the dictionary.
*/
- void plist_dict_new_iter(plist_t node, plist_dict_iter *iter);
+ PLIST_API void plist_dict_new_iter(plist_t node, plist_dict_iter *iter);
/**
* Increment iterator of a #PLIST_DICT node.
@@ -350,7 +434,7 @@ extern "C"
* free the returned value. Will be set to NULL when no more
* key/value pairs are left to iterate.
*/
- void plist_dict_next_item(plist_t node, plist_dict_iter iter, char **key, plist_t *val);
+ PLIST_API void plist_dict_next_item(plist_t node, plist_dict_iter iter, char **key, plist_t *val);
/**
* Get key associated key to an item. Item must be member of a dictionary.
@@ -358,7 +442,7 @@ extern "C"
* @param node the item
* @param key a location to store the key. The caller is responsible for freeing the returned string.
*/
- void plist_dict_get_item_key(plist_t node, char **key);
+ PLIST_API void plist_dict_get_item_key(plist_t node, char **key);
/**
* Get the nth item in a #PLIST_DICT node.
@@ -368,7 +452,7 @@ extern "C"
* @return the item or NULL if node is not of type #PLIST_DICT. The caller should not free
* the returned node.
*/
- plist_t plist_dict_get_item(plist_t node, const char* key);
+ PLIST_API plist_t plist_dict_get_item(plist_t node, const char* key);
/**
* Get key node associated to an item. Item must be member of a dictionary.
@@ -376,7 +460,7 @@ extern "C"
* @param node the item
* @return the key node of the given item, or NULL.
*/
- plist_t plist_dict_item_get_key(plist_t node);
+ PLIST_API plist_t plist_dict_item_get_key(plist_t node);
/**
* Set item identified by key in a #PLIST_DICT node.
@@ -387,19 +471,7 @@ extern "C"
* @param item the new item associated to key
* @param key the identifier of the item to set.
*/
- void plist_dict_set_item(plist_t node, const char* key, plist_t item);
-
- /**
- * Insert a new item into a #PLIST_DICT node.
- *
- * @deprecated Deprecated. Use plist_dict_set_item instead.
- *
- * @param node the node of type #PLIST_DICT
- * @param item the new item to insert
- * @param key The identifier of the item to insert.
- */
- PLIST_WARN_DEPRECATED("use plist_dict_set_item instead")
- void plist_dict_insert_item(plist_t node, const char* key, plist_t item);
+ PLIST_API void plist_dict_set_item(plist_t node, const char* key, plist_t item);
/**
* Remove an existing position in a #PLIST_DICT node.
@@ -408,7 +480,7 @@ extern "C"
* @param node the node of type #PLIST_DICT
* @param key The identifier of the item to remove. Assert if identifier is not present.
*/
- void plist_dict_remove_item(plist_t node, const char* key);
+ PLIST_API void plist_dict_remove_item(plist_t node, const char* key);
/**
* Merge a dictionary into another. This will add all key/value pairs
@@ -418,7 +490,7 @@ extern "C"
* @param target pointer to an existing node of type #PLIST_DICT
* @param source node of type #PLIST_DICT that should be merged into target
*/
- void plist_dict_merge(plist_t *target, plist_t source);
+ PLIST_API void plist_dict_merge(plist_t *target, plist_t source);
/********************************************
@@ -432,7 +504,7 @@ extern "C"
*
* @param node the parent (NULL if node is root)
*/
- plist_t plist_get_parent(plist_t node);
+ PLIST_API plist_t plist_get_parent(plist_t node);
/**
* Get the #plist_type of a node.
@@ -440,7 +512,7 @@ extern "C"
* @param node the node
* @return the type of the node
*/
- plist_type plist_get_node_type(plist_t node);
+ PLIST_API plist_type plist_get_node_type(plist_t node);
/**
* Get the value of a #PLIST_KEY node.
@@ -449,8 +521,9 @@ 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.
*/
- void plist_get_key_val(plist_t node, char **val);
+ PLIST_API void plist_get_key_val(plist_t node, char **val);
/**
* Get the value of a #PLIST_STRING node.
@@ -459,8 +532,9 @@ 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.
*/
- void plist_get_string_val(plist_t node, char **val);
+ PLIST_API void plist_get_string_val(plist_t node, char **val);
/**
* Get a pointer to the buffer of a #PLIST_STRING node.
@@ -473,7 +547,7 @@ extern "C"
*
* @return Pointer to the NULL-terminated buffer.
*/
- const char* plist_get_string_ptr(plist_t node, uint64_t* length);
+ PLIST_API const char* plist_get_string_ptr(plist_t node, uint64_t* length);
/**
* Get the value of a #PLIST_BOOLEAN node.
@@ -482,16 +556,25 @@ extern "C"
* @param node the node
* @param val a pointer to a uint8_t variable.
*/
- void plist_get_bool_val(plist_t node, uint8_t * val);
+ 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.
*/
- void plist_get_uint_val(plist_t node, uint64_t * val);
+ 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.
@@ -500,7 +583,7 @@ extern "C"
* @param node the node
* @param val a pointer to a double variable.
*/
- void plist_get_real_val(plist_t node, double *val);
+ PLIST_API void plist_get_real_val(plist_t node, double *val);
/**
* Get the value of a #PLIST_DATA node.
@@ -510,8 +593,9 @@ 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.
*/
- void plist_get_data_val(plist_t node, char **val, uint64_t * length);
+ 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.
@@ -524,7 +608,7 @@ extern "C"
*
* @return Pointer to the buffer
*/
- const char* plist_get_data_ptr(plist_t node, uint64_t* length);
+ PLIST_API const char* plist_get_data_ptr(plist_t node, uint64_t* length);
/**
* Get the value of a #PLIST_DATE node.
@@ -534,7 +618,7 @@ extern "C"
* @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
*/
- void plist_get_date_val(plist_t node, int32_t * sec, int32_t * usec);
+ PLIST_API void plist_get_date_val(plist_t node, int32_t * sec, int32_t * usec);
/**
* Get the value of a #PLIST_UID node.
@@ -543,7 +627,7 @@ extern "C"
* @param node the node
* @param val a pointer to a uint64_t variable.
*/
- void plist_get_uid_val(plist_t node, uint64_t * val);
+ PLIST_API void plist_get_uid_val(plist_t node, uint64_t * val);
/********************************************
@@ -559,7 +643,7 @@ extern "C"
* @param node the node
* @param val the key value
*/
- void plist_set_key_val(plist_t node, const char *val);
+ PLIST_API void plist_set_key_val(plist_t node, const char *val);
/**
* Set the value of a node.
@@ -569,7 +653,7 @@ extern "C"
* @param val the string value. The string is copied when set and will be
* freed by the node.
*/
- void plist_set_string_val(plist_t node, const char *val);
+ PLIST_API void plist_set_string_val(plist_t node, const char *val);
/**
* Set the value of a node.
@@ -578,16 +662,25 @@ extern "C"
* @param node the node
* @param val the boolean value
*/
- void plist_set_bool_val(plist_t node, uint8_t val);
+ PLIST_API void plist_set_bool_val(plist_t node, uint8_t val);
/**
* 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
*/
- void plist_set_uint_val(plist_t node, uint64_t val);
+ PLIST_API void plist_set_uint_val(plist_t node, uint64_t val);
+
+ /**
+ * 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.
@@ -596,7 +689,7 @@ extern "C"
* @param node the node
* @param val the real value
*/
- void plist_set_real_val(plist_t node, double val);
+ PLIST_API void plist_set_real_val(plist_t node, double val);
/**
* Set the value of a node.
@@ -607,7 +700,7 @@ extern "C"
* be freed by the node.
* @param length the length of the buffer
*/
- void plist_set_data_val(plist_t node, const char *val, uint64_t length);
+ PLIST_API void plist_set_data_val(plist_t node, const char *val, uint64_t length);
/**
* Set the value of a node.
@@ -617,7 +710,7 @@ extern "C"
* @param sec the number of seconds since 01/01/2001
* @param usec the number of microseconds
*/
- void plist_set_date_val(plist_t node, int32_t sec, int32_t usec);
+ PLIST_API void plist_set_date_val(plist_t node, int32_t sec, int32_t usec);
/**
* Set the value of a node.
@@ -626,7 +719,7 @@ extern "C"
* @param node the node
* @param val the unsigned integer value
*/
- void plist_set_uid_val(plist_t node, uint64_t val);
+ PLIST_API void plist_set_uid_val(plist_t node, uint64_t val);
/********************************************
@@ -642,32 +735,49 @@ 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.
*/
- 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);
/**
- * Frees the memory allocated by plist_to_xml().
+ * Export the #plist_t structure to binary format.
*
- * @param plist_xml The buffer allocated by plist_to_xml().
+ * @param plist the root node to export
+ * @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.
*/
- void plist_to_xml_free(char *plist_xml);
+ PLIST_API plist_err_t plist_to_bin(plist_t plist, char **plist_bin, uint32_t * length);
/**
- * Export the #plist_t structure to binary format.
+ * Export the #plist_t structure to JSON format.
*
* @param plist the root node to export
- * @param plist_bin a pointer to a char* buffer. This function allocates the memory,
- * caller is responsible for freeing it.
+ * @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.
*/
- void plist_to_bin(plist_t plist, char **plist_bin, uint32_t * length);
+ PLIST_API plist_err_t plist_to_json(plist_t plist, char **plist_json, uint32_t* length, int prettify);
/**
- * Frees the memory allocated by plist_to_bin().
+ * Export the #plist_t structure to OpenStep format.
*
- * @param plist_bin The buffer allocated by plist_to_bin().
+ * @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.
*/
- void plist_to_bin_free(char *plist_bin);
+ PLIST_API plist_err_t plist_to_openstep(plist_t plist, char **plist_openstep, uint32_t* length, int prettify);
+
/**
* Import the #plist_t structure from XML format.
@@ -675,8 +785,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
*/
- 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.
@@ -684,33 +795,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
*/
- void plist_from_bin(const char *plist_bin, uint32_t length, plist_t * plist);
+ PLIST_API plist_err_t plist_from_bin(const char *plist_bin, uint32_t length, plist_t * plist);
/**
- * Import the #plist_t structure from memory data.
- * This method will look at the first bytes of plist_data
- * to determine if plist_data contains a binary or XML plist.
+ * Import the #plist_t structure from JSON format.
*
- * @param plist_data a pointer to the memory buffer containing plist data.
+ * @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
*/
- void plist_from_memory(const char *plist_data, uint32_t length, plist_t * plist);
+ PLIST_API plist_err_t plist_from_json(const char *json, uint32_t length, plist_t * plist);
/**
- * Test if in-memory plist data is binary or XML
- * This method will look at the first bytes of plist_data
- * to determine if plist_data contains a binary or XML plist.
- * This method is not validating the whole memory buffer to check if the
- * content is truly a plist, it's only using some heuristic on the first few
- * bytes of plist_data.
+ * 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 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.
*/
- int plist_is_binary(const char *plist_data, uint32_t length);
+ PLIST_API int plist_is_binary(const char *plist_data, uint32_t length);
/********************************************
* *
@@ -727,7 +934,7 @@ extern "C"
* @param length length of the path to access
* @return the value to access.
*/
- plist_t plist_access_path(plist_t plist, uint32_t length, ...);
+ PLIST_API plist_t plist_access_path(plist_t plist, uint32_t length, ...);
/**
* Variadic version of #plist_access_path.
@@ -737,7 +944,7 @@ extern "C"
* @param v list of array's index and dic'st key
* @return the value to access.
*/
- plist_t plist_access_pathv(plist_t plist, uint32_t length, va_list v);
+ PLIST_API plist_t plist_access_pathv(plist_t plist, uint32_t length, va_list v);
/**
* Compare two node values
@@ -746,42 +953,74 @@ extern "C"
* @param node_r rigth node to compare
* @return TRUE is type and value match, FALSE otherwise.
*/
- char plist_compare_node_value(plist_t node_l, plist_t node_r);
+ 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)
- #define PLIST_IS_UINT(__plist) _PLIST_IS_TYPE(__plist, UINT)
+ /** 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, 0 if FALSE,
- * or -1 if the node is not of type PLIST_BOOL
+ * @return 1 if the boolean node has a value of TRUE or 0 if FALSE.
*/
- int plist_bool_val_is_true(plist_t boolnode);
+ PLIST_API int plist_bool_val_is_true(plist_t boolnode);
/**
- * Helper function to compare the value of a PLIST_UINT node against
- * a given value.
+ * 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_UINT
+ * @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.
*/
- int plist_uint_val_compare(plist_t uintnode, uint64_t 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
@@ -793,7 +1032,7 @@ extern "C"
* 1 if the node's value is greater than cmpval,
* or -1 if the node's value is less than cmpval.
*/
- int plist_uid_val_compare(plist_t uidnode, uint64_t 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
@@ -810,7 +1049,7 @@ extern "C"
* 1 if the node's value is greater than cmpval,
* or -1 if the node's value is less than cmpval.
*/
- int plist_real_val_compare(plist_t realnode, double 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
@@ -823,7 +1062,7 @@ extern "C"
* 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.
*/
- int plist_date_val_compare(plist_t datenode, int32_t cmpsec, int32_t cmpusec);
+ PLIST_API int plist_date_val_compare(plist_t datenode, int32_t cmpsec, int32_t cmpusec);
/**
* Helper function to compare the value of a PLIST_STRING node against
@@ -836,7 +1075,7 @@ extern "C"
* > 0 if the node's value is lexicographically greater than cmpval,
* or < 0 if the node's value is lexicographically less than cmpval.
*/
- int plist_string_val_compare(plist_t strnode, const char* 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
@@ -850,7 +1089,7 @@ extern "C"
* > 0 if the node's value is lexicographically greater than cmpval,
* or < 0 if the node's value is lexicographically less than cmpval.
*/
- int plist_string_val_compare_with_size(plist_t strnode, const char* cmpval, size_t n);
+ 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
@@ -861,7 +1100,7 @@ extern "C"
* @return 1 if the node's value contains the given substring,
* or 0 if not.
*/
- int plist_string_val_contains(plist_t strnode, const char* substr);
+ 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
@@ -874,7 +1113,7 @@ extern "C"
* > 0 if the node's value is lexicographically greater than cmpval,
* or < 0 if the node's value is lexicographically less than cmpval.
*/
- int plist_key_val_compare(plist_t keynode, const char* 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
@@ -888,7 +1127,7 @@ extern "C"
* > 0 if the node's value is lexicographically greater than cmpval,
* or < 0 if the node's value is lexicographically less than cmpval.
*/
- int plist_key_val_compare_with_size(plist_t keynode, const char* cmpval, size_t n);
+ 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
@@ -899,7 +1138,7 @@ extern "C"
* @return 1 if the node's value contains the given substring,
* or 0 if not.
*/
- int plist_key_val_contains(plist_t keynode, const char* substr);
+ 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
@@ -915,7 +1154,7 @@ extern "C"
* > 0 if the node's value is lexicographically greater than cmpval,
* or < 0 if the node's value is lexicographically less than cmpval.
*/
- int plist_data_val_compare(plist_t datanode, const uint8_t* cmpval, size_t n);
+ 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
@@ -931,7 +1170,7 @@ extern "C"
* > 0 if the node's value is lexicographically greater than cmpval,
* or < 0 if the node's value is lexicographically less than cmpval.
*/
- int plist_data_val_compare_with_size(plist_t datanode, const uint8_t* cmpval, size_t n);
+ 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
@@ -943,7 +1182,45 @@ extern "C"
* @return 1 if the node's value contains the given data blob
* or 0 if not.
*/
- int plist_data_val_contains(plist_t datanode, const uint8_t* cmpval, size_t n);
+ 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();
/*@}*/
diff --git a/libcnary/Makefile.am b/libcnary/Makefile.am
index e2187ec..f5c7bc9 100644
--- a/libcnary/Makefile.am
+++ b/libcnary/Makefile.am
@@ -1,12 +1,15 @@
-AM_CFLAGS = $(GLOBAL_CFLAGS) -I$(top_srcdir)/libcnary/include
+AM_CFLAGS = \
+ $(GLOBAL_CFLAGS) \
+ -I$(top_srcdir)/libcnary/include
+
AM_LDFLAGS =
noinst_LTLIBRARIES = libcnary.la
-libcnary_la_LIBADD =
+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
+ node.c \
+ node_list.c \
+ include/node.h \
+ include/node_list.h \
+ include/object.h
diff --git a/libcnary/include/node.h b/libcnary/include/node.h
index 7e9da50..123241a 100644
--- a/libcnary/include/node.h
+++ b/libcnary/include/node.h
@@ -24,42 +24,42 @@
#ifndef NODE_H_
#define NODE_H_
+#include "node_list.h"
#include "object.h"
#define NODE_TYPE 1;
-struct node_list_t;
-
// 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 Members
void *data;
- struct node_t* parent;
- struct node_list_t* children;
-} 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_list.h b/libcnary/include/node_list.h
index 380916e..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();
+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 c24ca7a..8d3708b 100644
--- a/libcnary/node.c
+++ b/libcnary/node.c
@@ -27,11 +27,12 @@
#include "node.h"
#include "node_list.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);
@@ -43,14 +44,14 @@ 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->next = NULL;
@@ -74,7 +75,8 @@ node_t* node_create(node_t* parent, void* data) {
return node;
}
-int node_attach(node_t* parent, node_t* child) {
+int node_attach(node_t parent, node_t child)
+{
if (!parent || !child) return -1;
child->parent = parent;
if(!parent->children) {
@@ -87,7 +89,8 @@ int node_attach(node_t* parent, node_t* child) {
return res;
}
-int node_detach(node_t* parent, node_t* child) {
+int node_detach(node_t parent, node_t child)
+{
if (!parent || !child) return -1;
int node_index = node_list_remove(parent->children, child);
if (node_index >= 0) {
@@ -96,7 +99,7 @@ int node_detach(node_t* parent, node_t* child) {
return node_index;
}
-int node_insert(node_t* parent, unsigned int node_index, node_t* child)
+int node_insert(node_t parent, unsigned int node_index, node_t child)
{
if (!parent || !child) return -1;
child->parent = parent;
@@ -110,9 +113,10 @@ int node_insert(node_t* parent, unsigned int node_index, node_t* child)
return res;
}
-static void _node_debug(node_t* node, unsigned int depth) {
+static void _node_debug(node_t node, unsigned int depth)
+{
unsigned int i = 0;
- node_t* current = NULL;
+ node_t current = NULL;
for(i = 0; i < depth; i++) {
printf("\t");
}
@@ -133,23 +137,23 @@ static void _node_debug(node_t* node, unsigned int depth) {
}
-void node_debug(node_t* node)
+void node_debug(node_t node)
{
_node_debug(node, 0);
}
-unsigned int node_n_children(struct node_t* node)
+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;
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 (node_index++ == n) {
found = 1;
@@ -162,30 +166,30 @@ 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 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;
@@ -199,17 +203,17 @@ int node_child_position(struct node_t* parent, node_t* child)
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);
+ 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_t cc = node_copy_deep(ch, copy_func);
node_attach(copy, cc);
}
return copy;
diff --git a/libcnary/node_list.c b/libcnary/node_list.c
index b0dca0a..f6c2c70 100644
--- a/libcnary/node_list.c
+++ b/libcnary/node_list.c
@@ -28,16 +28,17 @@
#include "node.h"
#include "node_list.h"
-void node_list_destroy(node_list_t* list) {
+void node_list_destroy(node_list_t list)
+{
free(list);
}
-node_list_t* node_list_create() {
- 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->begin = NULL;
@@ -46,11 +47,12 @@ node_list_t* node_list_create() {
return list;
}
-int node_list_add(node_list_t* list, node_t* node) {
+int node_list_add(node_list_t list, node_t node)
+{
if (!list || !node) return -1;
// 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;
@@ -73,17 +75,18 @@ int node_list_add(node_list_t* list, node_t* node) {
return 0;
}
-int node_list_insert(node_list_t* list, unsigned int node_index, node_t* node) {
+int node_list_insert(node_list_t list, unsigned int node_index, node_t node)
+{
if (!list || !node) return -1;
if (node_index >= list->count) {
return node_list_add(list, node);
}
// Get the first element in the list
- node_t* cur = list->begin;
+ node_t cur = list->begin;
unsigned int pos = 0;
- node_t* prev = NULL;
+ node_t prev = NULL;
if (node_index > 0) {
while (pos < node_index) {
@@ -121,15 +124,16 @@ int node_list_insert(node_list_t* list, unsigned int node_index, node_t* node) {
return 0;
}
-int node_list_remove(node_list_t* list, node_t* node) {
+int node_list_remove(node_list_t list, node_t node)
+{
if (!list || !node) return -1;
if (list->count == 0) return -1;
int node_index = 0;
- node_t* n;
+ node_t n;
for (n = list->begin; n; n = n->next) {
if (node == n) {
- node_t* newnode = node->next;
+ node_t newnode = node->next;
if (node->prev) {
node->prev->next = newnode;
if (newnode) {
@@ -151,7 +155,6 @@ int node_list_remove(node_list_t* list, node_t* node) {
return node_index;
}
node_index++;
- }
+ }
return -1;
}
-
diff --git a/m4/ac_pkg_cython.m4 b/m4/ac_pkg_cython.m4
index 3b4c9a7..e0af96a 100644
--- a/m4/ac_pkg_cython.m4
+++ b/m4/ac_pkg_cython.m4
@@ -1,8 +1,8 @@
AC_DEFUN([AC_PROG_CYTHON],[
- AC_PATH_PROG([CYTHON],[cython])
+ AC_PATH_PROGS([CYTHON],[cython cython3])
if test -z "$CYTHON" ; then
- AC_MSG_WARN([cannot find 'cython' program. You should look at http://www.cython.org] or install your distribution specific cython package.)
+ 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])
@@ -59,7 +59,7 @@ AC_DEFUN([AC_PROG_CYTHON],[
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([cannot determine Cython version])
+ AC_MSG_WARN([Unable to determine Cython version])
CYTHON=false
fi
fi
diff --git a/m4/as-compiler-flag.m4 b/m4/as-compiler-flag.m4
index 0f660cf..baab5d9 100644
--- a/m4/as-compiler-flag.m4
+++ b/m4/as-compiler-flag.m4
@@ -18,7 +18,7 @@ AC_DEFUN([AS_COMPILER_FLAG],
save_CFLAGS="$CFLAGS"
CFLAGS="$CFLAGS $1"
- AC_TRY_COMPILE([ ], [], [flag_ok=yes], [flag_ok=no])
+ AC_COMPILE_IFELSE([AC_LANG_PROGRAM([],[])], [flag_ok=yes], [flag_ok=no])
CFLAGS="$save_CFLAGS"
if test "X$flag_ok" = Xyes ; then
@@ -44,7 +44,7 @@ AC_DEFUN([AS_COMPILER_FLAGS],
do
save_CFLAGS="$CFLAGS"
CFLAGS="$CFLAGS $each"
- AC_TRY_COMPILE([ ], [], [flag_ok=yes], [flag_ok=no])
+ AC_COMPILE_IFELSE([AC_LANG_PROGRAM([],[])], [flag_ok=yes], [flag_ok=no])
CFLAGS="$save_CFLAGS"
if test "X$flag_ok" = Xyes ; then
diff --git a/m4/ax_pthread.m4 b/m4/ax_pthread.m4
index 5fbf9fe..9f35d13 100644
--- a/m4/ax_pthread.m4
+++ b/m4/ax_pthread.m4
@@ -14,20 +14,24 @@
# 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 to any special C compiler that is needed for
-# multi-threaded programs (defaults to the value of CC otherwise). (This
-# is necessary on AIX to use the special cc_r compiler alias.)
+# 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
@@ -55,6 +59,7 @@
#
# 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
@@ -82,7 +87,7 @@
# modified version of the Autoconf Macro, you may extend this special
# exception to the GPL to apply to your modified version as well.
-#serial 24
+#serial 31
AU_ALIAS([ACX_PTHREAD], [AX_PTHREAD])
AC_DEFUN([AX_PTHREAD], [
@@ -104,6 +109,7 @@ if test "x$PTHREAD_CFLAGS$PTHREAD_LIBS" != "x"; then
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])
@@ -123,10 +129,12 @@ fi
# (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 starting with a "-" are
-# C compiler flags, and other 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.
+# 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"
@@ -194,14 +202,47 @@ case $host_os in
# 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,pthread pthread $ax_pthread_flags"
+ 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 -pthreads $ax_pthread_flags"])
+ [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
@@ -224,25 +265,86 @@ AS_IF([test "x$ax_pthread_check_macro" = "x--"],
[ax_pthread_check_cond=0],
[ax_pthread_check_cond="!defined($ax_pthread_check_macro)"])
-# 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"
+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
-ax_pthread_clang_warning=no
# Clang needs special handling, because older versions handle the -pthread
# option in a rather... idiosyncratic way
@@ -261,11 +363,6 @@ if test "x$ax_pthread_clang" = "xyes"; then
# -pthread does define _REENTRANT, and while the Darwin headers
# ignore this macro, third-party headers might not.)
- PTHREAD_CFLAGS="-pthread"
- PTHREAD_LIBS=
-
- ax_pthread_ok=yes
-
# 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
@@ -294,7 +391,7 @@ if test "x$ax_pthread_clang" = "xyes"; then
# 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_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
@@ -320,78 +417,7 @@ if test "x$ax_pthread_clang" = "xyes"; then
fi # $ax_pthread_clang = yes
-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])
- ;;
-
- -mt,pthread)
- AC_MSG_CHECKING([whether pthreads work with -mt -lpthread])
- PTHREAD_CFLAGS="-mt"
- PTHREAD_LIBS="-lpthread"
- ;;
-
- -*)
- 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 routine(void *a) { a = 0; }
- 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
# Various other checks:
if test "x$ax_pthread_ok" = "xyes"; then
@@ -438,7 +464,8 @@ if test "x$ax_pthread_ok" = "xyes"; then
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;]])],
+ [[int i = PTHREAD_PRIO_INHERIT;
+ return i;]])],
[ax_cv_PTHREAD_PRIO_INHERIT=yes],
[ax_cv_PTHREAD_PRIO_INHERIT=no])
])
@@ -460,18 +487,28 @@ if test "x$ax_pthread_ok" = "xyes"; then
[#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"])],
- [AC_CHECK_PROGS([PTHREAD_CC],[${CC}_r],[$CC])])])
+ [
+ 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
diff --git a/m4/cython_python.m4 b/m4/cython_python.m4
index ba730d5..5b4a041 100644
--- a/m4/cython_python.m4
+++ b/m4/cython_python.m4
@@ -1,6 +1,6 @@
AC_DEFUN([CYTHON_PYTHON],[
AC_REQUIRE([AC_PROG_CYTHON])
- AC_REQUIRE([AC_PYTHON_DEVEL])
+ 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/src/Array.cpp b/src/Array.cpp
index d5d9d7c..bc448d3 100644
--- a/src/Array.cpp
+++ b/src/Array.cpp
@@ -18,11 +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 <limits.h>
+#include <climits>
+#include "plist.h"
+#include <plist/Array.h>
namespace PList
{
@@ -32,7 +32,7 @@ Array::Array(Node* parent) : Structure(PLIST_ARRAY, parent)
_array.clear();
}
-static void array_fill(Array *_this, std::vector<Node*> array, plist_t node)
+static void array_fill(Array *_this, std::vector<Node*> &array, plist_t node)
{
plist_array_iter iter = NULL;
plist_array_new_iter(node, &iter);
@@ -51,18 +51,17 @@ Array::Array(plist_t node, Node* parent) : Structure(parent)
array_fill(this, _array, _node);
}
-Array::Array(const PList::Array& a) : Structure()
+Array::Array(const PList::Array& a)
{
_array.clear();
_node = plist_copy(a.GetPlist());
array_fill(this, _array, _node);
}
-Array& Array::operator=(PList::Array& a)
+Array& Array::operator=(const PList::Array& a)
{
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();
@@ -73,8 +72,7 @@ Array& Array::operator=(PList::Array& a)
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();
@@ -90,6 +88,50 @@ Node* Array::operator[](unsigned int array_index)
return _array.at(array_index);
}
+Array::iterator Array::Begin()
+{
+ return _array.begin();
+}
+
+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(Node* node)
{
if (node)
@@ -145,4 +187,4 @@ unsigned int Array::GetNodeIndex(Node* node) const
return std::distance (_array.begin(), it);
}
-};
+} // namespace PList
diff --git a/src/Boolean.cpp b/src/Boolean.cpp
index 4608eaf..9ec1a63 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
@@ -37,7 +38,7 @@ 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)
{
plist_free(_node);
_node = plist_copy(b.GetPlist());
@@ -70,4 +71,4 @@ bool Boolean::GetValue() const
return b != 0 ;
}
-};
+} // namespace PList
diff --git a/src/Data.cpp b/src/Data.cpp
index 2e93007..a96fc50 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
@@ -38,7 +38,7 @@ Data::Data(const PList::Data& d) : Node(PLIST_DATA)
plist_set_data_val(_node, &b[0], b.size());
}
-Data& Data::operator=(PList::Data& b)
+Data& Data::operator=(const PList::Data& b)
{
plist_free(_node);
_node = plist_copy(b.GetPlist());
@@ -70,10 +70,10 @@ std::vector<char> Data::GetValue() const
uint64_t length = 0;
plist_get_data_val(_node, &buff, &length);
std::vector<char> ret(buff, buff + length);
- free(buff);
+ delete buff;
return ret;
}
-};
+} // namespace PList
diff --git a/src/Date.cpp b/src/Date.cpp
index 4b5e0a1..8b8e650 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
@@ -38,7 +38,7 @@ Date::Date(const PList::Date& d) : Node(PLIST_DATE)
plist_set_date_val(_node, t.tv_sec, t.tv_usec);
}
-Date& Date::operator=(PList::Date& d)
+Date& Date::operator=(const PList::Date& d)
{
plist_free(_node);
_node = plist_copy(d.GetPlist());
@@ -73,4 +73,4 @@ timeval Date::GetValue() const
return t;
}
-};
+} // namespace PList
diff --git a/src/Dictionary.cpp b/src/Dictionary.cpp
index 59908c6..30c20b6 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
@@ -39,7 +40,7 @@ static void dictionary_fill(Dictionary *_this, std::map<std::string,Node*> &map,
plist_dict_next_item(node, it, &key, &subnode);
if (key && subnode)
map[std::string(key)] = Node::FromPlist(subnode, _this);
- free(key);
+ delete key;
} while (subnode);
free(it);
}
@@ -50,7 +51,7 @@ Dictionary::Dictionary(plist_t node, Node* parent) : Structure(parent)
dictionary_fill(this, _map, _node);
}
-Dictionary::Dictionary(const PList::Dictionary& d) : Structure()
+Dictionary::Dictionary(const PList::Dictionary& d)
{
for (Dictionary::iterator it = _map.begin(); it != _map.end(); it++)
{
@@ -62,7 +63,7 @@ Dictionary::Dictionary(const PList::Dictionary& d) : Structure()
dictionary_fill(this, _map, _node);
}
-Dictionary& Dictionary::operator=(PList::Dictionary& d)
+Dictionary& Dictionary::operator=(const PList::Dictionary& d)
{
for (Dictionary::iterator it = _map.begin(); it != _map.end(); it++)
{
@@ -99,21 +100,45 @@ 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);
@@ -143,11 +168,6 @@ Dictionary::iterator Dictionary::Set(const std::string& key, const Node& node)
return Set(key, &node);
}
-Dictionary::iterator Dictionary::Insert(const std::string& key, Node* node)
-{
- return this->Set(key, node);
-}
-
void Dictionary::Remove(Node* node)
{
if (node)
@@ -156,7 +176,7 @@ void Dictionary::Remove(Node* node)
plist_dict_get_item_key(node->GetPlist(), &key);
plist_dict_remove_item(_node, key);
std::string skey = key;
- free(key);
+ delete key;
_map.erase(skey);
delete node;
}
@@ -179,4 +199,4 @@ std::string Dictionary::GetNodeKey(Node* node)
return "";
}
-};
+} // namespace PList
diff --git a/src/Integer.cpp b/src/Integer.cpp
index 04315d7..30a5405 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,23 +33,28 @@ Integer::Integer(plist_t node, Node* parent) : Node(node, parent)
{
}
-Integer::Integer(const PList::Integer& i) : Node(PLIST_UINT)
+Integer::Integer(const PList::Integer& i) : Node(PLIST_INT)
{
plist_set_uint_val(_node, i.GetValue());
}
-Integer& Integer::operator=(PList::Integer& i)
+Integer& Integer::operator=(const PList::Integer& i)
{
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()
{
}
@@ -58,16 +64,38 @@ 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() const
+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
index ed0c0c6..79265d5 100644
--- a/src/Key.cpp
+++ b/src/Key.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/Key.h>
namespace PList
@@ -32,12 +33,12 @@ Key::Key(plist_t node, Node* parent) : Node(node, parent)
{
}
-Key::Key(const PList::Key& k) : Node(PLIST_UINT)
+Key::Key(const PList::Key& k) : Node(PLIST_INT)
{
plist_set_key_val(_node, k.GetValue().c_str());
}
-Key& Key::operator=(PList::Key& k)
+Key& Key::operator=(const PList::Key& k)
{
plist_free(_node);
_node = plist_copy(k.GetPlist());
@@ -67,14 +68,9 @@ std::string Key::GetValue() const
{
char* s = NULL;
plist_get_key_val(_node, &s);
- std::string ret;
- if (s) {
- ret = s;
- free(s);
- } else {
- ret = "";
- }
+ std::string ret = s ? s : "";
+ delete s;
return ret;
}
-};
+} // namespace PList
diff --git a/src/Makefile.am b/src/Makefile.am
index 995bddd..1a416ad 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -1,52 +1,70 @@
-AM_CPPFLAGS = -I$(top_srcdir)/include -I$(top_srcdir) -I$(top_srcdir)/libcnary/include
+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.la libplist++.la
-libplist_la_LIBADD = $(top_builddir)/libcnary/libcnary.la
-libplist_la_LDFLAGS = $(AM_LDFLAGS) -version-info $(LIBPLIST_SO_VERSION) -no-undefined
-libplist_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 \
- xplist.c \
- bplist.c \
- plist.c plist.h
+lib_LTLIBRARIES = \
+ libplist-2.0.la \
+ libplist++-2.0.la
-libplist___la_LIBADD = libplist.la
-libplist___la_LDFLAGS = $(AM_LDFLAGS) -version-info $(LIBPLIST_SO_VERSION) -no-undefined
-libplist___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
+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 \
+ 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_la_LDFLAGS += -avoid-version -static-libgcc
-libplist___la_LDFLAGS += -avoid-version -static-libgcc
+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.pc libplist++.pc
+pkgconfig_DATA = \
+ libplist-2.0.pc \
+ libplist++-2.0.pc
diff --git a/src/Node.cpp b/src/Node.cpp
index 3da401e..0bd428a 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>
@@ -52,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:
@@ -134,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:
@@ -163,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 ec300c9..02d1d9b 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,12 +32,12 @@ Real::Real(plist_t node, Node* parent) : Node(node, parent)
{
}
-Real::Real(const 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)
{
plist_free(_node);
_node = plist_copy(d.GetPlist());
@@ -70,4 +70,4 @@ double Real::GetValue() const
return d;
}
-};
+} // namespace PList
diff --git a/src/String.cpp b/src/String.cpp
index 0965349..2ddc28b 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,12 +33,12 @@ String::String(plist_t node, Node* parent) : Node(node, parent)
{
}
-String::String(const 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)
{
plist_free(_node);
_node = plist_copy(s.GetPlist());
@@ -67,14 +68,9 @@ std::string String::GetValue() const
{
char* s = NULL;
plist_get_string_val(_node, &s);
- std::string ret;
- if (s) {
- ret = s;
- free(s);
- } else {
- ret = "";
- }
+ std::string ret = s ? s : "";
+ delete s;
return ret;
}
-};
+} // namespace PList
diff --git a/src/Structure.cpp b/src/Structure.cpp
index 70150c2..670cce6 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
@@ -56,7 +57,7 @@ std::string Structure::ToXml() const
uint32_t length = 0;
plist_to_xml(_node, &xml, &length);
std::string ret(xml, xml+length);
- free(xml);
+ delete xml;
return ret;
}
@@ -66,7 +67,7 @@ std::vector<char> Structure::ToBin() const
uint32_t length = 0;
plist_to_bin(_node, &bin, &length);
std::vector<char> ret(bin, bin+length);
- free(bin);
+ delete bin;
return ret;
}
@@ -119,5 +120,5 @@ Structure* Structure::FromBin(const std::vector<char>& bin)
}
-};
+} // namespace PList
diff --git a/src/Uid.cpp b/src/Uid.cpp
index 440dec4..8c73c80 100644
--- a/src/Uid.cpp
+++ b/src/Uid.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/Uid.h>
namespace PList
@@ -37,7 +38,7 @@ Uid::Uid(const PList::Uid& i) : Node(PLIST_UID)
plist_set_uid_val(_node, i.GetValue());
}
-Uid& Uid::operator=(PList::Uid& i)
+Uid& Uid::operator=(const PList::Uid& i)
{
plist_free(_node);
_node = plist_copy(i.GetPlist());
@@ -70,4 +71,4 @@ uint64_t Uid::GetValue() const
return i;
}
-};
+} // namespace PList
diff --git a/src/bplist.c b/src/bplist.c
index 14db755..93f0bc6 100644
--- a/src/bplist.c
+++ b/src/bplist.c
@@ -32,11 +32,11 @@
#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>
@@ -47,7 +47,8 @@
#define BPLIST_VERSION ((uint8_t*)"00")
#define BPLIST_VERSION_SIZE 2
-typedef struct __attribute__((packed)) {
+#pragma pack(push,1)
+typedef struct {
uint8_t unused[6];
uint8_t offset_size;
uint8_t ref_size;
@@ -55,6 +56,7 @@ typedef struct __attribute__((packed)) {
uint64_t root_object_index;
uint64_t offset_table_offset;
} bplist_trailer_t;
+#pragma pack(pop)
enum
{
@@ -62,7 +64,7 @@ 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,
@@ -143,28 +145,28 @@ union plist_uint_ptr
#ifdef __BIG_ENDIAN__
#define beNtoh(x,n) (x >> ((8-n) << 3))
#else
-#define beNtoh(x,n) be64toh(x << ((8-n) << 3))
+#define beNtoh(x,n) be64toh((x) << ((8-(n)) << 3))
#endif
#define UINT_TO_HOST(x, n) \
({ \
union plist_uint_ptr __up; \
- __up.src = (n > 8) ? x + (n - 8) : 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 : \
+ __up.src = ((n) > 8) ? (x) + ((n) - 8) : (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) \
)))); \
})
#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 ? sizeof(float) : sizeof(double))
+#define get_real_bytes(x) ((x) == (float) (x) ? sizeof(float) : sizeof(double))
#if (defined(__LITTLE_ENDIAN__) \
&& !defined(__FLOAT_WORD_ORDER__)) \
@@ -182,7 +184,7 @@ union plist_uint_ptr
#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)
+#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)
{
@@ -191,7 +193,7 @@ static int uint64_mul_overflow(uint64_t a, uint64_t b, uint64_t *res)
}
#endif
-#define NODE_IS_ROOT(x) (((node_t*)x)->isRoot)
+#define NODE_IS_ROOT(x) (((node_t)(x))->isRoot)
struct bplist_data {
const char* data;
@@ -227,9 +229,16 @@ 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_uint_node(const char **bnode, uint8_t size)
+static plist_t parse_int_node(const char **bnode, uint8_t size)
{
plist_data_t data = plist_new_plist_data();
@@ -254,7 +263,7 @@ static plist_t parse_uint_node(const char **bnode, uint8_t size)
data->intval = UINT_TO_HOST(*bnode, size);
(*bnode) += size;
- data->type = PLIST_UINT;
+ data->type = PLIST_INT;
return node_create(NULL, data);
}
@@ -317,7 +326,8 @@ static plist_t parse_string_node(const char **bnode, uint64_t size)
static char *plist_utf16be_to_utf8(uint16_t *unistr, long len, long *items_read, long *items_written)
{
if (!unistr || (len <= 0)) return NULL;
- char *outbuf;
+ char* outbuf;
+ char* outbuf_new;
int p = 0;
long i = 0;
@@ -325,6 +335,7 @@ static char *plist_utf16be_to_utf8(uint16_t *unistr, long len, long *items_read,
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)));
@@ -339,7 +350,7 @@ static char *plist_utf16be_to_utf8(uint16_t *unistr, long len, long *items_read,
read_lead_surrogate = 1;
w = 0x010000 + ((wc & 0x3FF) << 10);
} else {
- // This is invalid, the next 16 bit char should be a trail surrogate.
+ // This is invalid, the next 16 bit char should be a trail surrogate.
// Handling error by skipping.
read_lead_surrogate = 0;
}
@@ -374,30 +385,29 @@ static char *plist_utf16be_to_utf8(uint16_t *unistr, long len, long *items_read,
}
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(const char **bnode, uint64_t size)
{
plist_data_t data = plist_new_plist_data();
- char *tmpstr = NULL;
long items_read = 0;
long items_written = 0;
data->type = PLIST_STRING;
-
- tmpstr = plist_utf16be_to_utf8((uint16_t*)(*bnode), size, &items_read, &items_written);
- if (!tmpstr) {
+ data->strval = plist_utf16be_to_utf8((uint16_t*)(*bnode), size, &items_read, &items_written);
+ if (!data->strval) {
plist_free_data(data);
return NULL;
}
- tmpstr[items_written] = '\0';
-
- data->type = PLIST_STRING;
- data->strval = realloc(tmpstr, items_written+1);
- if (!data->strval)
- data->strval = tmpstr;
data->length = items_written;
+
return node_create(NULL, data);
}
@@ -490,8 +500,8 @@ static plist_t parse_dict_node(struct bplist_data *bplist, const char** bnode, u
return NULL;
}
- node_attach(node, key);
- node_attach(node, val);
+ node_attach((node_t)node, (node_t)key);
+ node_attach((node_t)node, (node_t)val);
}
return node;
@@ -535,7 +545,7 @@ static plist_t parse_array_node(struct bplist_data *bplist, const char** bnode,
return NULL;
}
- node_attach(node, val);
+ node_attach((node_t)node, (node_t)val);
}
return node;
@@ -583,8 +593,8 @@ static plist_t parse_bin_node(struct bplist_data *bplist, const char** object)
case BPLIST_DICT:
{
uint16_t next_size = **object & BPLIST_FILL;
- if ((**object & BPLIST_MASK) != BPLIST_UINT) {
- 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_UINT);
+ 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)++;
@@ -630,16 +640,23 @@ static plist_t parse_bin_node(struct bplist_data *bplist, const char** object)
}
case BPLIST_NULL:
+ {
+ plist_data_t data = plist_new_plist_data();
+ data->type = PLIST_NULL;
+ data->length = 0;
+ return node_create(NULL, data);
+ }
+
default:
return NULL;
}
- case BPLIST_UINT:
+ case BPLIST_INT:
if (pobject + (uint64_t)(1 << size) > poffset_table) {
- PLIST_BIN_ERR("%s: BPLIST_UINT data bytes point outside of valid range\n", __func__);
+ PLIST_BIN_ERR("%s: BPLIST_INT data bytes point outside of valid range\n", __func__);
return NULL;
}
- return parse_uint_node(object, size);
+ return parse_int_node(object, size);
case BPLIST_REAL:
if (pobject + (uint64_t)(1 << size) > poffset_table) {
@@ -734,7 +751,7 @@ static plist_t parse_bin_node_at_index(struct bplist_data *bplist, uint32_t node
ptr = bplist->data + UINT_TO_HOST(idx_ptr, bplist->offset_size);
/* make sure the node offset is in a sane range */
- if ((ptr < bplist->data) || (ptr >= bplist->offset_table)) {
+ 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);
return NULL;
}
@@ -751,8 +768,8 @@ static plist_t parse_bin_node_at_index(struct bplist_data *bplist, uint32_t node
/* recursion check */
if (bplist->level > 0) {
for (i = bplist->level-1; i >= 0; i--) {
- uint32_t node_i = (uint32_t)(uintptr_t)ptr_array_index(bplist->used_indexes, i);
- uint32_t node_level = (uint32_t)(uintptr_t)ptr_array_index(bplist->used_indexes, bplist->level);
+ 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");
return NULL;
@@ -767,7 +784,7 @@ static plist_t parse_bin_node_at_index(struct bplist_data *bplist, uint32_t node
return plist;
}
-PLIST_API 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)
{
bplist_trailer_t *trailer = NULL;
uint8_t offset_size = 0;
@@ -779,20 +796,28 @@ PLIST_API void plist_from_bin(const char *plist_bin, uint32_t length, plist_t *
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 + sizeof(bplist_trailer_t))) {
PLIST_BIN_ERR("plist data is to small to hold a binary plist\n");
- return;
+ return PLIST_ERR_PARSE;
}
//check that plist_bin in actually a plist
if (memcmp(plist_bin, BPLIST_MAGIC, BPLIST_MAGIC_SIZE) != 0) {
PLIST_BIN_ERR("bplist magic mismatch\n");
- return;
+ return PLIST_ERR_PARSE;
}
//check for known version
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;
+ return PLIST_ERR_PARSE;
}
start_data = plist_bin + BPLIST_MAGIC_SIZE + BPLIST_VERSION_SIZE;
@@ -809,37 +834,37 @@ PLIST_API void plist_from_bin(const char *plist_bin, uint32_t length, plist_t *
if (num_objects == 0) {
PLIST_BIN_ERR("number of objects must be larger than 0\n");
- return;
+ return PLIST_ERR_PARSE;
}
if (offset_size == 0) {
PLIST_BIN_ERR("offset size in trailer must be larger than 0\n");
- return;
+ return PLIST_ERR_PARSE;
}
if (ref_size == 0) {
PLIST_BIN_ERR("object reference size in trailer must be larger than 0\n");
- return;
+ return PLIST_ERR_PARSE;
}
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;
+ return PLIST_ERR_PARSE;
}
if (offset_table < start_data || offset_table >= end_data) {
PLIST_BIN_ERR("offset table offset points outside of valid range\n");
- return;
+ return PLIST_ERR_PARSE;
}
if (uint64_mul_overflow(num_objects, offset_size, &offset_table_size)) {
PLIST_BIN_ERR("integer overflow when calculating offset table size\n");
- return;
+ return PLIST_ERR_PARSE;
}
- if ((offset_table + offset_table_size < offset_table) || (offset_table + offset_table_size > end_data)) {
+ if (offset_table_size > (uint64_t)(end_data - offset_table)) {
PLIST_BIN_ERR("offset table points outside of valid range\n");
- return;
+ return PLIST_ERR_PARSE;
}
struct bplist_data bplist;
@@ -854,12 +879,18 @@ PLIST_API void plist_from_bin(const char *plist_bin, uint32_t length, plist_t *
if (!bplist.used_indexes) {
PLIST_BIN_ERR("failed to create array to hold used node indexes. Out of memory?\n");
- return;
+ return PLIST_ERR_NO_MEM;
}
*plist = parse_bin_node_at_index(&bplist, root_object);
ptr_array_free(bplist.used_indexes);
+
+ if (!*plist) {
+ return PLIST_ERR_PARSE;
+ }
+
+ return PLIST_ERR_SUCCESS;
}
static unsigned int plist_data_hash(const void* key)
@@ -875,7 +906,8 @@ 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:
@@ -914,7 +946,7 @@ struct serialize_s
hashtable_t* ref_table;
};
-static void serialize_plist(node_t* node, void* data)
+static void serialize_plist(node_t node, void* data)
{
uint64_t *index_val = NULL;
struct serialize_s *ser = (struct serialize_s *) data;
@@ -937,15 +969,13 @@ static void serialize_plist(node_t* node, void* data)
ptr_array_add(ser->objects, node);
//now recurse on children
- node_t *ch;
+ node_t ch;
for (ch = node_first_child(node); ch; ch = node_next_sibling(ch)) {
serialize_plist(ch, data);
}
-
- return;
}
-#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)
{
@@ -954,7 +984,7 @@ static void write_int(bytearray_t * bplist, uint64_t val)
//do not write 3bytes int node
if (size == 3)
size++;
- sz = BPLIST_UINT | Log2(size);
+ sz = BPLIST_INT | Log2(size);
val = be64toh(val);
byte_array_append(bplist, &sz, 1);
@@ -963,7 +993,7 @@ static void write_int(bytearray_t * bplist, uint64_t val)
static void write_uint(bytearray_t * bplist, uint64_t val)
{
- uint8_t sz = BPLIST_UINT | 4;
+ uint8_t sz = BPLIST_INT | 4;
uint64_t zero = 0;
val = be64toh(val);
@@ -979,18 +1009,24 @@ static void write_real(bytearray_t * bplist, double val)
buff[7] = BPLIST_REAL | Log2(size);
if (size == sizeof(float)) {
float floatval = (float)val;
- *(uint32_t*)(buff+8) = float_bswap32(*(uint32_t*)&floatval);
+ uint32_t intval;
+ memcpy(&intval, &floatval, sizeof(float));
+ *(uint32_t*)(buff+8) = float_bswap32(intval);
} else {
- *(uint64_t*)(buff+8) = float_bswap64(*(uint64_t*)&val);
+ uint64_t intval;
+ memcpy(&intval, &val, sizeof(double));
+ *(uint64_t*)(buff+8) = float_bswap64(intval);
}
byte_array_append(bplist, buff+7, size+1);
}
static void write_date(bytearray_t * bplist, double val)
{
+ uint64_t intval;
+ memcpy(&intval, &val, sizeof(double));
uint8_t buff[16];
buff[7] = BPLIST_DATE | 3;
- *(uint64_t*)(buff+8) = float_bswap64(*(uint64_t*)&val);
+ *(uint64_t*)(buff+8) = float_bswap64(intval);
byte_array_append(bplist, buff+7, 9);
}
@@ -1085,9 +1121,9 @@ static void write_unicode(bytearray_t * bplist, char *val, uint64_t size)
free(unicodestr);
}
-static void write_array(bytearray_t * bplist, node_t* node, hashtable_t* ref_table, uint8_t ref_size)
+static void write_array(bytearray_t * bplist, node_t node, hashtable_t* ref_table, uint8_t ref_size)
{
- node_t* cur = NULL;
+ node_t cur = NULL;
uint64_t i = 0;
uint64_t size = node_n_children(node);
@@ -1104,9 +1140,9 @@ static void write_array(bytearray_t * bplist, node_t* node, hashtable_t* ref_tab
}
}
-static void write_dict(bytearray_t * bplist, node_t* node, hashtable_t* ref_table, uint8_t ref_size)
+static void write_dict(bytearray_t * bplist, node_t node, hashtable_t* ref_table, uint8_t ref_size)
{
- node_t* cur = NULL;
+ node_t cur = NULL;
uint64_t i = 0;
uint64_t size = node_n_children(node) / 2;
@@ -1158,7 +1194,7 @@ static int is_ascii_string(char* s, int len)
return ret;
}
-PLIST_API 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;
@@ -1176,18 +1212,26 @@ PLIST_API void plist_to_bin(plist_t plist, char **plist_bin, uint32_t * length)
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(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, free);
+ if (!ref_table) {
+ ptr_array_free(objects);
+ return PLIST_ERR_NO_MEM;
+ }
//serialize plist
ser_s.objects = objects;
ser_s.ref_table = ref_table;
- serialize_plist(plist, &ser_s);
+ serialize_plist((node_t)plist, &ser_s);
//now stream to output buffer
offset_size = 0; //unknown yet
@@ -1201,12 +1245,13 @@ PLIST_API void plist_to_bin(plist_t plist, char **plist_bin, uint32_t * length)
uint64_t req = 0;
for (i = 0; i < num_objects; i++)
{
- node_t* node = ptr_array_index(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;
@@ -1281,6 +1326,11 @@ PLIST_API void plist_to_bin(plist_t plist, char **plist_bin, uint32_t * length)
//setup a dynamic bytes array to store bplist in
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);
@@ -1297,12 +1347,17 @@ PLIST_API void plist_to_bin(plist_t plist, char **plist_bin, uint32_t * length)
switch (data->type)
{
+ case PLIST_NULL: {
+ uint8_t b = 0;
+ byte_array_append(bplist_buff, &b, 1);
+ break;
+ }
case PLIST_BOOLEAN: {
uint8_t b = data->boolval ? BPLIST_TRUE : BPLIST_FALSE;
byte_array_append(bplist_buff, &b, 1);
break;
}
- case PLIST_UINT:
+ case PLIST_INT:
if (data->length == 16) {
write_uint(bplist_buff, data->intval);
} else {
@@ -1329,10 +1384,10 @@ PLIST_API void plist_to_bin(plist_t plist, char **plist_bin, uint32_t * length)
write_data(bplist_buff, data->buff, data->length);
break;
case PLIST_ARRAY:
- write_array(bplist_buff, ptr_array_index(objects, i), ref_table, ref_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, ref_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->realval);
@@ -1370,14 +1425,11 @@ PLIST_API void plist_to_bin(plist_t plist, char **plist_bin, uint32_t * length)
byte_array_append(bplist_buff, &trailer, sizeof(bplist_trailer_t));
//set output buffer and size
- *plist_bin = bplist_buff->data;
+ *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);
-}
-PLIST_API void plist_to_bin_free(char *plist_bin)
-{
- free(plist_bin);
+ return PLIST_ERR_SUCCESS;
}
diff --git a/src/bytearray.c b/src/bytearray.c
index 7d0549b..39fad5f 100644
--- a/src/bytearray.c
+++ b/src/bytearray.c
@@ -29,6 +29,17 @@ bytearray_t *byte_array_new(size_t initial)
a->capacity = (initial > PAGE_SIZE) ? (initial+(PAGE_SIZE-1)) & (~(PAGE_SIZE-1)) : PAGE_SIZE;
a->data = malloc(a->capacity);
a->len = 0;
+ 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;
}
@@ -43,6 +54,9 @@ void byte_array_free(bytearray_t *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;
@@ -50,12 +64,20 @@ void byte_array_grow(bytearray_t *ba, size_t amount)
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) {
- size_t needed = len - remaining;
- byte_array_grow(ba, needed);
+ 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(((char*)ba->data) + ba->len, buf, len);
ba->len += len;
}
diff --git a/src/bytearray.h b/src/bytearray.h
index 312e2aa..b53e006 100644
--- a/src/bytearray.h
+++ b/src/bytearray.h
@@ -21,14 +21,17 @@
#ifndef BYTEARRAY_H
#define BYTEARRAY_H
#include <stdlib.h>
+#include <stdio.h>
typedef struct bytearray_t {
void *data;
size_t len;
size_t capacity;
+ FILE *stream;
} bytearray_t;
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);
diff --git a/src/hashtable.c b/src/hashtable.c
index dd6dbfc..86dae82 100644
--- a/src/hashtable.c
+++ b/src/hashtable.c
@@ -47,7 +47,7 @@ void hash_table_destroy(hashtable_t *ht)
ht->free_func(e->value);
}
hashentry_t* old = e;
- e = e->next;
+ e = (hashentry_t*)e->next;
free(old);
}
}
@@ -71,7 +71,7 @@ void hash_table_insert(hashtable_t* ht, void *key, void *value)
e->value = value;
return;
}
- e = e->next;
+ e = (hashentry_t*)e->next;
}
// if we get here, the element is not yet in the list.
@@ -103,7 +103,7 @@ void* hash_table_lookup(hashtable_t* ht, void *key)
if (ht->compare_func(e->key, key)) {
return e->value;
}
- e = e->next;
+ e = (hashentry_t*)e->next;
}
return NULL;
}
@@ -124,7 +124,7 @@ void hash_table_remove(hashtable_t* ht, void *key)
// found element, remove it from the list
hashentry_t* old = e;
if (e == ht->entries[idx0]) {
- ht->entries[idx0] = e->next;
+ ht->entries[idx0] = (hashentry_t*)e->next;
} else {
last->next = e->next;
}
@@ -135,6 +135,6 @@ void hash_table_remove(hashtable_t* ht, void *key)
return;
}
last = e;
- e = e->next;
+ e = (hashentry_t*)e->next;
}
}
diff --git a/src/jplist.c b/src/jplist.c
new file mode 100644
index 0000000..782d2b3
--- /dev/null
+++ b/src/jplist.c
@@ -0,0 +1,856 @@
+/*
+ * 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"
+
+#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
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wshadow"
+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;
+}
+#pragma GCC diagnostic pop
+#endif
+
+static size_t dtostr(char *buf, size_t bufsize, double realval)
+{
+ size_t len = 0;
+ if (isnan(realval)) {
+ len = snprintf(buf, bufsize, "nan");
+ } else if (isinf(realval)) {
+ len = snprintf(buf, bufsize, "%cinfinity", (realval > 0.0) ? '+' : '-');
+ } else if (realval == 0.0f) {
+ len = snprintf(buf, bufsize, "0.0");
+ } else {
+ size_t i = 0;
+ len = snprintf(buf, bufsize, "%.*g", 17, realval);
+ for (i = 0; buf && i < len; i++) {
+ if (buf[i] == ',') {
+ buf[i] = '.';
+ break;
+ } else if (buf[i] == '.') {
+ break;
+ }
+ }
+ }
+ return len;
+}
+
+static plist_err_t node_to_json(node_t node, bytearray_t **outbuf, uint32_t depth, int prettify)
+{
+ 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);
+ 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);
+ 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:
+ // NOT VALID FOR JSON
+ PLIST_JSON_WRITE_ERR("PLIST_DATA type is not valid for JSON format\n");
+ return PLIST_ERR_FORMAT;
+ case PLIST_DATE:
+ // NOT VALID FOR JSON
+ PLIST_JSON_WRITE_ERR("PLIST_DATE type is not valid for JSON format\n");
+ return PLIST_ERR_FORMAT;
+ case PLIST_UID:
+ // NOT VALID FOR JSON
+ PLIST_JSON_WRITE_ERR("PLIST_UID type is not valid for JSON format\n");
+ return PLIST_ERR_FORMAT;
+ default:
+ return PLIST_ERR_UNKNOWN;
+ }
+
+ return PLIST_ERR_SUCCESS;
+}
+
+#define PO10i_LIMIT (INT64_MAX/10)
+
+/* based on https://stackoverflow.com/a/4143288 */
+static int num_digits_i(int64_t i)
+{
+ int n;
+ int64_t po10;
+ n=1;
+ if (i < 0) {
+ i = (i == INT64_MIN) ? INT64_MAX : -i;
+ n++;
+ }
+ po10=10;
+ while (i>=po10) {
+ n++;
+ if (po10 > PO10i_LIMIT) break;
+ po10*=10;
+ }
+ return n;
+}
+
+#define PO10u_LIMIT (UINT64_MAX/10)
+
+/* based on https://stackoverflow.com/a/4143288 */
+static int num_digits_u(uint64_t i)
+{
+ int n;
+ uint64_t po10;
+ n=1;
+ po10=10;
+ while (i>=po10) {
+ n++;
+ if (po10 > PO10u_LIMIT) break;
+ po10*=10;
+ }
+ return n;
+}
+
+static plist_err_t node_estimate_size(node_t node, uint64_t *size, uint32_t depth, int prettify)
+{
+ plist_data_t data;
+ if (!node) {
+ return PLIST_ERR_INVALID_ARG;
+ }
+ 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);
+ 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:
+ // NOT VALID FOR JSON
+ PLIST_JSON_WRITE_ERR("PLIST_DATA type is not valid for JSON format\n");
+ return PLIST_ERR_FORMAT;
+ case PLIST_DATE:
+ // NOT VALID FOR JSON
+ PLIST_JSON_WRITE_ERR("PLIST_DATE type is not valid for JSON format\n");
+ return PLIST_ERR_FORMAT;
+ case PLIST_UID:
+ // NOT VALID FOR JSON
+ PLIST_JSON_WRITE_ERR("PLIST_UID type is not valid for JSON format\n");
+ return PLIST_ERR_FORMAT;
+ default:
+ PLIST_JSON_WRITE_ERR("invalid node type encountered\n");
+ return PLIST_ERR_UNKNOWN;
+ }
+ }
+ return PLIST_ERR_SUCCESS;
+}
+
+plist_err_t plist_to_json(plist_t plist, char **plist_json, uint32_t* length, int prettify)
+{
+ 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;
+ }
+
+ res = node_estimate_size((node_t)plist, &size, 0, prettify);
+ 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);
+ 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;
+} jsmntok_info_t;
+
+static int64_t parse_decimal(const char* str, const char* str_end, char** endp)
+{
+ uint64_t MAX = INT64_MAX;
+ uint64_t x = 0;
+ int is_neg = 0;
+ *endp = (char*)str;
+
+ if (str[0] == '-') {
+ is_neg = 1;
+ (*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();
+ 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+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+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);
+ }
+ (*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);
+ 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();
+ data->type = PLIST_STRING;
+ data->strval = strval;
+ data->length = str_len;
+ node = plist_new_node(data);
+
+ (*index)++;
+ return node;
+}
+
+static plist_t parse_object(const char* js, jsmntok_info_t* ti, int* index);
+
+static plist_t parse_array(const char* js, jsmntok_info_t* ti, int* index)
+{
+ if (ti->tokens[*index].type != JSMN_ARRAY) {
+ PLIST_JSON_ERR("%s: token type != JSMN_ARRAY\n", __func__);
+ return NULL;
+ }
+ plist_t arr = plist_new_array();
+ int num_tokens = ti->tokens[*index].size;
+ int 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);
+ return NULL;
+ }
+ plist_t val = NULL;
+ switch (ti->tokens[j].type) {
+ case JSMN_OBJECT:
+ val = parse_object(js, ti, &j);
+ break;
+ case JSMN_ARRAY:
+ val = parse_array(js, ti, &j);
+ 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);
+ } else {
+ plist_free(arr);
+ return NULL;
+ }
+ }
+ *(index) = j;
+ return arr;
+}
+
+static plist_t parse_object(const char* js, jsmntok_info_t* ti, int* index)
+{
+ if (ti->tokens[*index].type != JSMN_OBJECT) {
+ PLIST_JSON_ERR("%s: token type != JSMN_OBJECT\n", __func__);
+ return NULL;
+ }
+ int num_tokens = ti->tokens[*index].size;
+ int num;
+ int j = (*index)+1;
+ if (num_tokens % 2 != 0) {
+ PLIST_JSON_ERR("%s: number of children must be even\n", __func__);
+ return NULL;
+ }
+ plist_t obj = plist_new_dict();
+ 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);
+ 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);
+ return NULL;
+ }
+ plist_t val = NULL;
+ j++;
+ num++;
+ switch (ti->tokens[j].type) {
+ case JSMN_OBJECT:
+ val = parse_object(js, ti, &j);
+ break;
+ case JSMN_ARRAY:
+ val = parse_array(js, ti, &j);
+ 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);
+ } else {
+ free(key);
+ plist_free(obj);
+ return NULL;
+ }
+ free(key);
+ } else {
+ PLIST_JSON_ERR("%s: keys must be of type STRING\n", __func__);
+ plist_free(obj);
+ 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);
+ int maxtoks = 256;
+ int curtoks = 0;
+ int r = 0;
+ jsmntok_t *tokens = NULL;
+
+ do {
+ jsmntok_t* newtokens = (jsmntok_t*)realloc(tokens, sizeof(jsmntok_t)*maxtoks);
+ if (!newtokens) {
+ 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) {
+ maxtoks+=16;
+ continue;
+ }
+ } 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;
+ default:
+ break;
+ }
+
+ int startindex = 0;
+ jsmntok_info_t ti = { tokens, parser.toknext };
+ 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);
+ break;
+ case JSMN_OBJECT:
+ *plist = parse_object(json, &ti, &startindex);
+ break;
+ default:
+ break;
+ }
+ free(tokens);
+ return PLIST_ERR_SUCCESS;
+}
diff --git a/src/jsmn.c b/src/jsmn.c
new file mode 100644
index 0000000..f190312
--- /dev/null
+++ b/src/jsmn.c
@@ -0,0 +1,289 @@
+/*
+ * jsmn.c
+ * Simple JSON parser
+ *
+ * Copyright (c) 2010 Serge A. Zaitsev
+ *
+ * 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 "jsmn.h"
+
+/**
+ * Allocates a fresh unused token from the token pull.
+ */
+static jsmntok_t *jsmn_alloc_token(jsmn_parser *parser,
+ jsmntok_t *tokens, int num_tokens) {
+ jsmntok_t *tok;
+ if (parser->toknext >= num_tokens) {
+ return NULL;
+ }
+ tok = &tokens[parser->toknext++];
+ tok->start = tok->end = -1;
+ 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,
+ int start, int 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, int num_tokens) {
+ jsmntok_t *token;
+ int 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, int num_tokens) {
+ jsmntok_t *token;
+
+ int 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, unsigned int length, jsmntok_t *tokens,
+ unsigned int num_tokens) {
+ jsmnerr_t r;
+ int i;
+ jsmntok_t *token;
+
+ parser->end = length;
+
+ 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 != -1 && token->end == -1) {
+ if (token->type != type) {
+ 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 != -1 && token->end == -1) {
+ 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 != -1 && token->end == -1) {
+ 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 != -1 && tokens[i].end == -1) {
+ 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..380744d
--- /dev/null
+++ b/src/jsmn.h
@@ -0,0 +1,92 @@
+/*
+ * jsmn.h
+ * Simple JSON parser (header file)
+ *
+ * Copyright (c) 2010 Serge A. Zaitsev
+ *
+ * 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_
+
+/**
+ * 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,
+ /* 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;
+ int start;
+ int end;
+ int 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 {
+ unsigned int pos; /* offset in the JSON string */
+ unsigned int 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, unsigned int length,
+ jsmntok_t *tokens, unsigned int num_tokens);
+
+#endif /* __JSMN_H_ */
diff --git a/src/libplist++.pc.in b/src/libplist++-2.0.pc.in
index 0fce9d1..79dc315 100644
--- a/src/libplist++.pc.in
+++ b/src/libplist++-2.0.pc.in
@@ -6,6 +6,6 @@ includedir=@includedir@
Name: @PACKAGE_NAME@++
Description: C++ binding for @PACKAGE_NAME@
Version: @PACKAGE_VERSION@
-Libs: -L${libdir} -lplist++
+Libs: -L${libdir} -lplist++-2.0
Cflags: -I${includedir}
-Requires.private: libplist >= @PACKAGE_VERSION@
+Requires.private: libplist-2.0 >= @PACKAGE_VERSION@
diff --git a/src/libplist.pc.in b/src/libplist-2.0.pc.in
index 0bd6932..43d9f57 100644
--- a/src/libplist.pc.in
+++ b/src/libplist-2.0.pc.in
@@ -6,5 +6,5 @@ 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
+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..6ab6603
--- /dev/null
+++ b/src/oplist.c
@@ -0,0 +1,933 @@
+/*
+ * 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"
+
+#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
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wshadow"
+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;
+}
+#pragma GCC diagnostic pop
+#endif
+
+static size_t dtostr(char *buf, size_t bufsize, double realval)
+{
+ size_t len = 0;
+ if (isnan(realval)) {
+ len = snprintf(buf, bufsize, "nan");
+ } else if (isinf(realval)) {
+ len = snprintf(buf, bufsize, "%cinfinity", (realval > 0.0) ? '+' : '-');
+ } else if (realval == 0.0f) {
+ len = snprintf(buf, bufsize, "0.0");
+ } else {
+ size_t i = 0;
+ len = snprintf(buf, bufsize, "%.*g", 17, realval);
+ for (i = 0; buf && i < len; i++) {
+ if (buf[i] == ',') {
+ buf[i] = '.';
+ break;
+ } else if (buf[i] == '.') {
+ break;
+ }
+ }
+ }
+ return len;
+}
+
+static const char allowed_unquoted_chars[256] = {
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 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)
+{
+ 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);
+ 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);
+ 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:
+ PLIST_OSTEP_WRITE_ERR("PLIST_BOOLEAN type is not valid for OpenStep format\n");
+ return PLIST_ERR_FORMAT;
+ case PLIST_NULL:
+ PLIST_OSTEP_WRITE_ERR("PLIST_NULL type is not valid for OpenStep format\n");
+ return PLIST_ERR_FORMAT;
+ case PLIST_DATE:
+ // NOT VALID FOR OPENSTEP
+ PLIST_OSTEP_WRITE_ERR("PLIST_DATE type is not valid for OpenStep format\n");
+ return PLIST_ERR_FORMAT;
+ case PLIST_UID:
+ // NOT VALID FOR OPENSTEP
+ PLIST_OSTEP_WRITE_ERR("PLIST_UID type is not valid for OpenStep format\n");
+ return PLIST_ERR_FORMAT;
+ default:
+ return PLIST_ERR_UNKNOWN;
+ }
+
+ return PLIST_ERR_SUCCESS;
+}
+
+#define PO10i_LIMIT (INT64_MAX/10)
+
+/* based on https://stackoverflow.com/a/4143288 */
+static int num_digits_i(int64_t i)
+{
+ int n;
+ int64_t po10;
+ n=1;
+ if (i < 0) {
+ i = (i == INT64_MIN) ? INT64_MAX : -i;
+ n++;
+ }
+ po10=10;
+ while (i>=po10) {
+ n++;
+ if (po10 > PO10i_LIMIT) break;
+ po10*=10;
+ }
+ return n;
+}
+
+#define PO10u_LIMIT (UINT64_MAX/10)
+
+/* based on https://stackoverflow.com/a/4143288 */
+static int num_digits_u(uint64_t i)
+{
+ int n;
+ uint64_t po10;
+ n=1;
+ po10=10;
+ while (i>=po10) {
+ n++;
+ if (po10 > PO10u_LIMIT) break;
+ po10*=10;
+ }
+ return n;
+}
+
+static plist_err_t node_estimate_size(node_t node, uint64_t *size, uint32_t depth, int prettify)
+{
+ plist_data_t data;
+ if (!node) {
+ return PLIST_ERR_INVALID_ARG;
+ }
+ 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);
+ 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:
+ // NOT VALID FOR OPENSTEP
+ PLIST_OSTEP_WRITE_ERR("PLIST_BOOLEAN type is not valid for OpenStep format\n");
+ return PLIST_ERR_FORMAT;
+ case PLIST_DATE:
+ // NOT VALID FOR OPENSTEP
+ PLIST_OSTEP_WRITE_ERR("PLIST_DATE type is not valid for OpenStep format\n");
+ return PLIST_ERR_FORMAT;
+ case PLIST_UID:
+ // NOT VALID FOR OPENSTEP
+ PLIST_OSTEP_WRITE_ERR("PLIST_UID type is not valid for OpenStep format\n");
+ return PLIST_ERR_FORMAT;
+ default:
+ PLIST_OSTEP_WRITE_ERR("invalid node type encountered\n");
+ return PLIST_ERR_UNKNOWN;
+ }
+ }
+ return PLIST_ERR_SUCCESS;
+}
+
+plist_err_t plist_to_openstep(plist_t plist, char **openstep, uint32_t* length, int prettify)
+{
+ uint64_t size = 0;
+ plist_err_t res;
+
+ if (!plist || !openstep || !length) {
+ return PLIST_ERR_INVALID_ARG;
+ }
+
+ res = node_estimate_size((node_t)plist, &size, 0, prettify);
+ 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);
+ 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;
+ int 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 != 0) {
+ 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++;
+ 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++;
+ break;
+ }
+ if (*ctx->pos != '=') {
+ PLIST_OSTEP_ERR("Missing '=' while parsing dictionary item at offset %ld\n", (long int)(ctx->pos - ctx->start));
+ ctx->err++;
+ 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++;
+ break;
+ }
+ val = NULL;
+ ctx->err = node_from_openstep(ctx, &val);
+ if (ctx->err != 0) {
+ break;
+ }
+ if (!val) {
+ PLIST_OSTEP_ERR("Missing value for dictionary item at offset %ld\n", (long int)(ctx->pos - ctx->start));
+ ctx->err++;
+ 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++;
+ 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++;
+ 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 > 1000) {
+ PLIST_OSTEP_ERR("Too many levels of recursion (%u) at offset %ld\n", ctx->depth, (long int)(ctx->pos - ctx->start));
+ ctx->err++;
+ return PLIST_ERR_PARSE;
+ }
+ 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++;
+ break;
+ }
+ if (*ctx->pos != '}') {
+ PLIST_OSTEP_ERR("Missing terminating '}' at offset %ld\n", (long int)(ctx->pos - ctx->start));
+ ctx->err++;
+ 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 != 0) {
+ break;
+ }
+ if (!tmp) {
+ ctx->err++;
+ 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++;
+ break;
+ }
+ if (*ctx->pos != ',') {
+ break;
+ }
+ ctx->pos++;
+ }
+ plist_free(tmp);
+ tmp = NULL;
+ if (ctx->err) {
+ 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++;
+ break;
+ }
+ if (*ctx->pos != ')') {
+ PLIST_OSTEP_ERR("Missing terminating ')' at offset %ld\n", (long int)(ctx->pos - ctx->start));
+ ctx->err++;
+ 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++;
+ 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++;
+ 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++;
+ 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++;
+ 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++;
+ 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++;
+ 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;
+ int 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++;
+ 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++;
+ 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++;
+ break;
+ }
+ }
+ ctx->pos++;
+ }
+ ctx->depth--;
+
+err_out:
+ if (ctx->err) {
+ plist_free(subnode);
+ plist_free(*plist);
+ *plist = NULL;
+ return PLIST_ERR_PARSE;
+ }
+ 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..266070b
--- /dev/null
+++ b/src/out-default.c
@@ -0,0 +1,491 @@
+/*
+ * 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"
+
+#define MAC_EPOCH 978307200
+
+static size_t dtostr(char *buf, size_t bufsize, double realval)
+{
+ size_t len = 0;
+ if (isnan(realval)) {
+ len = snprintf(buf, bufsize, "nan");
+ } else if (isinf(realval)) {
+ len = snprintf(buf, bufsize, "%cinfinity", (realval > 0.0) ? '+' : '-');
+ } else if (realval == 0.0f) {
+ len = snprintf(buf, bufsize, "0.0");
+ } else {
+ size_t i = 0;
+ len = snprintf(buf, bufsize, "%.*g", 17, realval);
+ for (i = 0; buf && i < len; i++) {
+ if (buf[i] == ',') {
+ buf[i] = '.';
+ break;
+ } else if (buf[i] == '.') {
+ break;
+ }
+ }
+ }
+ return len;
+}
+
+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;
+ 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);
+ }
+ 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:
+ {
+ str_buf_append(*outbuf, "<", 1);
+ size_t len = node_data->length;
+ char charb[4];
+ if (!partial_data || len <= 24) {
+ for (i = 0; i < len; i++) {
+ if (i > 0 && (i % 4 == 0))
+ str_buf_append(*outbuf, " ", 1);
+ sprintf(charb, "%02x", (unsigned char)node_data->buff[i]);
+ str_buf_append(*outbuf, charb, 2);
+ }
+ } else {
+ for (i = 0; i < 16; i++) {
+ if (i > 0 && (i % 4 == 0))
+ str_buf_append(*outbuf, " ", 1);
+ sprintf(charb, "%02x", (unsigned char)node_data->buff[i]);
+ str_buf_append(*outbuf, charb, 2);
+ }
+ str_buf_append(*outbuf, " ... ", 5);
+ for (i = len - 8; i < len; i++) {
+ sprintf(charb, "%02x", (unsigned char)node_data->buff[i]);
+ str_buf_append(*outbuf, charb, 2);
+ if (i > 0 && i < len-1 && (i % 4 == 0))
+ str_buf_append(*outbuf, " ", 1);
+ }
+ }
+ str_buf_append(*outbuf, ">", 1);
+ }
+ break;
+ case PLIST_DATE:
+ {
+ Time64_T timev = (Time64_T)node_data->realval + MAC_EPOCH;
+ struct TM _btime;
+ struct TM *btime = gmtime64_r(&timev, &_btime);
+ if (btime) {
+ val = (char*)calloc(1, 26);
+ 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 (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;
+ default:
+ return PLIST_ERR_UNKNOWN;
+ }
+
+ return PLIST_ERR_SUCCESS;
+}
+
+#define PO10i_LIMIT (INT64_MAX/10)
+
+/* based on https://stackoverflow.com/a/4143288 */
+static int num_digits_i(int64_t i)
+{
+ int n;
+ int64_t po10;
+ n=1;
+ if (i < 0) {
+ i = (i == INT64_MIN) ? INT64_MAX : -i;
+ n++;
+ }
+ po10=10;
+ while (i>=po10) {
+ n++;
+ if (po10 > PO10i_LIMIT) break;
+ po10*=10;
+ }
+ return n;
+}
+
+#define PO10u_LIMIT (UINT64_MAX/10)
+
+/* based on https://stackoverflow.com/a/4143288 */
+static int num_digits_u(uint64_t i)
+{
+ int n;
+ uint64_t po10;
+ n=1;
+ po10=10;
+ while (i>=po10) {
+ n++;
+ if (po10 > PO10u_LIMIT) break;
+ po10*=10;
+ }
+ return n;
+}
+
+static plist_err_t node_estimate_size(node_t node, uint64_t *size, uint32_t depth, uint32_t indent, int partial_data)
+{
+ plist_data_t data;
+ if (!node) {
+ return PLIST_ERR_INVALID_ARG;
+ }
+ 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);
+ if (res < 0) {
+ 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 _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..7d861f8
--- /dev/null
+++ b/src/out-limd.c
@@ -0,0 +1,449 @@
+/*
+ * 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"
+
+#define MAC_EPOCH 978307200
+
+static size_t dtostr(char *buf, size_t bufsize, double realval)
+{
+ size_t len = 0;
+ if (isnan(realval)) {
+ len = snprintf(buf, bufsize, "nan");
+ } else if (isinf(realval)) {
+ len = snprintf(buf, bufsize, "%cinfinity", (realval > 0.0) ? '+' : '-');
+ } else if (realval == 0.0f) {
+ len = snprintf(buf, bufsize, "0.0");
+ } else {
+ size_t i = 0;
+ len = snprintf(buf, bufsize, "%.*g", 17, realval);
+ for (i = 0; buf && i < len; i++) {
+ if (buf[i] == ',') {
+ buf[i] = '.';
+ break;
+ } else if (buf[i] == '.') {
+ break;
+ }
+ }
+ }
+ return len;
+}
+
+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;
+ size_t val_len = 0;
+ char buf[16];
+
+ 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;
+
+ 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;
+ }
+ 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);
+ }
+ }
+ size_t sl = sprintf(buf, "%u: ", cnt);
+ str_buf_append(*outbuf, buf, sl);
+ 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)) {
+ size_t sl = sprintf(buf, "[%u]:", plist_array_get_size(valnode));
+ str_buf_append(*outbuf, buf, sl);
+ } else {
+ str_buf_append(*outbuf, ": ", 2);
+ }
+ }
+ cnt++;
+ }
+ } break;
+ case PLIST_DATA:
+ {
+ val = (char*)malloc(4096);
+ size_t done = 0;
+ while (done < node_data->length) {
+ size_t amount = node_data->length - done;
+ if (amount > 3072) {
+ amount = 3072;
+ }
+ size_t bsize = base64encode(val, node_data->buff + done, amount);
+ str_buf_append(*outbuf, val, bsize);
+ done += amount;
+ }
+ }
+ break;
+ case PLIST_DATE:
+ {
+ Time64_T timev = (Time64_T)node_data->realval + MAC_EPOCH;
+ struct TM _btime;
+ struct TM *btime = gmtime64_r(&timev, &_btime);
+ if (btime) {
+ val = (char*)calloc(1, 24);
+ 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);
+ val = NULL;
+ }
+ }
+ break;
+ case PLIST_UID:
+ {
+ str_buf_append(*outbuf, "CF$UID:", 7);
+ 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;
+ default:
+ return PLIST_ERR_UNKNOWN;
+ }
+
+ return PLIST_ERR_SUCCESS;
+}
+
+#define PO10i_LIMIT (INT64_MAX/10)
+
+/* based on https://stackoverflow.com/a/4143288 */
+static int num_digits_i(int64_t i)
+{
+ int n;
+ int64_t po10;
+ n=1;
+ if (i < 0) {
+ i = (i == INT64_MIN) ? INT64_MAX : -i;
+ n++;
+ }
+ po10=10;
+ while (i>=po10) {
+ n++;
+ if (po10 > PO10i_LIMIT) break;
+ po10*=10;
+ }
+ return n;
+}
+
+#define PO10u_LIMIT (UINT64_MAX/10)
+
+/* based on https://stackoverflow.com/a/4143288 */
+static int num_digits_u(uint64_t i)
+{
+ int n;
+ uint64_t po10;
+ n=1;
+ po10=10;
+ while (i>=po10) {
+ n++;
+ if (po10 > PO10u_LIMIT) break;
+ po10*=10;
+ }
+ return n;
+}
+
+static plist_err_t node_estimate_size(node_t node, uint64_t *size, uint32_t depth, uint32_t indent)
+{
+ plist_data_t data;
+ if (!node) {
+ return PLIST_ERR_INVALID_ARG;
+ }
+ 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);
+ if (res < 0) {
+ 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 _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..d85f22c
--- /dev/null
+++ b/src/out-plutil.c
@@ -0,0 +1,465 @@
+/*
+ * 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"
+
+#define MAC_EPOCH 978307200
+
+static size_t dtostr(char *buf, size_t bufsize, double realval)
+{
+ size_t len = 0;
+ if (isnan(realval)) {
+ len = snprintf(buf, bufsize, "nan");
+ } else if (isinf(realval)) {
+ len = snprintf(buf, bufsize, "%cinfinity", (realval > 0.0) ? '+' : '-');
+ } else if (realval == 0.0f) {
+ len = snprintf(buf, bufsize, "0.0");
+ } else {
+ size_t i = 0;
+ len = snprintf(buf, bufsize, "%.*g", 17, realval);
+ for (i = 0; buf && i < len; i++) {
+ if (buf[i] == ',') {
+ buf[i] = '.';
+ break;
+ } else if (buf[i] == '.') {
+ break;
+ }
+ }
+ }
+ return len;
+}
+
+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;
+ 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, "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 (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)) {
+ str_buf_append(*outbuf, "\n", 1);
+ for (i = 0; i <= depth; i++) {
+ str_buf_append(*outbuf, " ", 2);
+ }
+ char indexbuf[16];
+ int l = sprintf(indexbuf, "%u => ", cnt);
+ str_buf_append(*outbuf, indexbuf, l);
+ 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;
+ 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:
+ {
+ val = (char*)calloc(1, 48);
+ size_t len = node_data->length;
+ size_t slen = snprintf(val, 48, "{length = %" PRIu64 ", bytes = 0x", (uint64_t)len);
+ str_buf_append(*outbuf, val, slen);
+ if (len <= 24) {
+ for (i = 0; i < len; i++) {
+ sprintf(val, "%02x", (unsigned char)node_data->buff[i]);
+ str_buf_append(*outbuf, val, 2);
+ }
+ } else {
+ for (i = 0; i < 16; i++) {
+ if (i > 0 && (i % 4 == 0))
+ str_buf_append(*outbuf, " ", 1);
+ sprintf(val, "%02x", (unsigned char)node_data->buff[i]);
+ str_buf_append(*outbuf, val, 2);
+ }
+ str_buf_append(*outbuf, " ... ", 5);
+ for (i = len - 8; i < len; i++) {
+ sprintf(val, "%02x", (unsigned char)node_data->buff[i]);
+ str_buf_append(*outbuf, val, 2);
+ if (i > 0 && (i % 4 == 0))
+ str_buf_append(*outbuf, " ", 1);
+ }
+ }
+ free(val);
+ val = NULL;
+ str_buf_append(*outbuf, "}", 1);
+ }
+ break;
+ case PLIST_DATE:
+ {
+ Time64_T timev = (Time64_T)node_data->realval + MAC_EPOCH;
+ struct TM _btime;
+ struct TM *btime = gmtime64_r(&timev, &_btime);
+ if (btime) {
+ val = (char*)calloc(1, 26);
+ 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:
+ {
+ val = (char*)malloc(88);
+ val_len = sprintf(val, "<CFKeyedArchiverUID %p [%p]>{value = %" PRIu64 "}", node, node_data, node_data->intval);
+ str_buf_append(*outbuf, val, val_len);
+ free(val);
+ val = NULL;
+ }
+ break;
+ default:
+ return PLIST_ERR_UNKNOWN;
+ }
+
+ return PLIST_ERR_SUCCESS;
+}
+
+#define PO10i_LIMIT (INT64_MAX/10)
+
+/* based on https://stackoverflow.com/a/4143288 */
+static int num_digits_i(int64_t i)
+{
+ int n;
+ int64_t po10;
+ n=1;
+ if (i < 0) {
+ i = (i == INT64_MIN) ? INT64_MAX : -i;
+ n++;
+ }
+ po10=10;
+ while (i>=po10) {
+ n++;
+ if (po10 > PO10i_LIMIT) break;
+ po10*=10;
+ }
+ return n;
+}
+
+#define PO10u_LIMIT (UINT64_MAX/10)
+
+/* based on https://stackoverflow.com/a/4143288 */
+static int num_digits_u(uint64_t i)
+{
+ int n;
+ uint64_t po10;
+ n=1;
+ po10=10;
+ while (i>=po10) {
+ n++;
+ if (po10 > PO10u_LIMIT) break;
+ po10*=10;
+ }
+ return n;
+}
+
+static plist_err_t node_estimate_size(node_t node, uint64_t *size, uint32_t depth)
+{
+ plist_data_t data;
+ if (!node) {
+ return PLIST_ERR_INVALID_ARG;
+ }
+ 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);
+ if (res < 0) {
+ 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 _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 27f90b1..2078520 100644
--- a/src/plist.c
+++ b/src/plist.c
@@ -2,7 +2,7 @@
* plist.c
* Builds plist XML structures
*
- * Copyright (c) 2009-2019 Nikias Bassen, 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.
*
@@ -34,6 +34,7 @@
#include <assert.h>
#include <limits.h>
#include <float.h>
+#include <ctype.h>
#ifdef WIN32
#include <windows.h>
@@ -42,28 +43,40 @@
#endif
#include <node.h>
+#include <node_list.h>
#include <hashtable.h>
#include <ptrarray.h>
+#ifdef _MSC_VER
+typedef SSIZE_T ssize_t;
+#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_init(void)
{
plist_bin_init();
plist_xml_init();
+ plist_json_init();
+ plist_ostep_init();
}
static void internal_plist_deinit(void)
{
plist_bin_deinit();
plist_xml_deinit();
+ plist_json_deinit();
+ plist_ostep_deinit();
}
#ifdef WIN32
-
typedef volatile struct {
LONG lock;
int state;
@@ -72,7 +85,7 @@ typedef volatile struct {
static thread_once_t init_once = {0, 0};
static thread_once_t deinit_once = {0, 0};
-void thread_once(thread_once_t *once_control, void (*init_routine)(void))
+static void thread_once(thread_once_t *once_control, void (*init_routine)(void))
{
while (InterlockedExchange(&(once_control->lock), 1) != 0) {
Sleep(1);
@@ -83,7 +96,29 @@ void thread_once(thread_once_t *once_control, void (*init_routine)(void))
}
InterlockedExchange(&(once_control->lock), 0);
}
+#else
+static pthread_once_t init_once = PTHREAD_ONCE_INIT;
+static pthread_once_t deinit_once = PTHREAD_ONCE_INIT;
+#define thread_once pthread_once
+#endif
+
+#ifndef HAVE_ATTRIBUTE_CONSTRUCTOR
+ #if defined(__llvm__) || defined(__GNUC__)
+ #define HAVE_ATTRIBUTE_CONSTRUCTOR
+ #endif
+#endif
+
+#ifdef HAVE_ATTRIBUTE_CONSTRUCTOR
+static void __attribute__((constructor)) libplist_initialize(void)
+{
+ thread_once(&init_once, internal_plist_init);
+}
+static void __attribute__((destructor)) libplist_deinitialize(void)
+{
+ thread_once(&deinit_once, internal_plist_deinit);
+}
+#elif defined(WIN32)
BOOL WINAPI DllMain(HINSTANCE hModule, DWORD dwReason, LPVOID lpReserved)
{
switch (dwReason) {
@@ -98,22 +133,8 @@ BOOL WINAPI DllMain(HINSTANCE hModule, DWORD dwReason, LPVOID lpReserved)
}
return 1;
}
-
#else
-
-static pthread_once_t init_once = PTHREAD_ONCE_INIT;
-static pthread_once_t deinit_once = PTHREAD_ONCE_INIT;
-
-static void __attribute__((constructor)) libplist_initialize(void)
-{
- pthread_once(&init_once, internal_plist_init);
-}
-
-static void __attribute__((destructor)) libplist_deinitialize(void)
-{
- pthread_once(&deinit_once, internal_plist_deinit);
-}
-
+#warning No compiler support for constructor/destructor attributes, some features might not be available.
#endif
#ifndef HAVE_MEMMEM
@@ -166,7 +187,7 @@ void* memmem(const void* haystack, size_t haystack_len, const void* needle, size
}
#endif
-PLIST_API int plist_is_binary(const char *plist_data, uint32_t length)
+int plist_is_binary(const char *plist_data, uint32_t length)
{
if (length < 8) {
return 0;
@@ -175,19 +196,131 @@ PLIST_API int plist_is_binary(const char *plist_data, uint32_t length)
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_API void plist_from_memory(const char *plist_data, uint32_t length, plist_t * plist)
+plist_err_t plist_from_memory(const char *plist_data, uint32_t length, plist_t *plist, plist_format_t *format)
{
- if (length < 8) {
- *plist = NULL;
- return;
+ 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)) {
- plist_from_bin(plist_data, length, plist);
+ res = plist_from_bin(plist_data, length, plist);
+ fmt = PLIST_FORMAT_BINARY;
} else {
- plist_from_xml(plist_data, length, plist);
+ 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)
@@ -195,11 +328,11 @@ 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)
@@ -247,10 +380,10 @@ void plist_free_data(plist_data_t data)
free(data->buff);
break;
case PLIST_ARRAY:
- ptr_array_free(data->hashtable);
+ ptr_array_free((ptrarray_t*)data->hashtable);
break;
case PLIST_DICT:
- hash_table_destroy(data->hashtable);
+ hash_table_destroy((hashtable_t*)data->hashtable);
break;
default:
break;
@@ -259,7 +392,7 @@ void plist_free_data(plist_data_t data)
}
}
-static int plist_free_node(node_t* node)
+static int plist_free_node(node_t node)
{
plist_data_t data = NULL;
int node_index = node_detach(node->parent, node);
@@ -267,9 +400,9 @@ static int plist_free_node(node_t* node)
plist_free_data(data);
node->data = NULL;
- node_t *ch;
+ node_t ch;
for (ch = node_first_child(node); ch; ) {
- node_t *next = node_next_sibling(ch);
+ node_t next = node_next_sibling(ch);
plist_free_node(ch);
ch = next;
}
@@ -279,14 +412,14 @@ static int plist_free_node(node_t* node)
return node_index;
}
-PLIST_API plist_t plist_new_dict(void)
+plist_t plist_new_dict(void)
{
plist_data_t data = plist_new_plist_data();
data->type = PLIST_DICT;
return plist_new_node(data);
}
-PLIST_API plist_t plist_new_array(void)
+plist_t plist_new_array(void)
{
plist_data_t data = plist_new_plist_data();
data->type = PLIST_ARRAY;
@@ -303,7 +436,7 @@ static plist_t plist_new_key(const char *val)
return plist_new_node(data);
}
-PLIST_API plist_t plist_new_string(const char *val)
+plist_t plist_new_string(const char *val)
{
plist_data_t data = plist_new_plist_data();
data->type = PLIST_STRING;
@@ -312,7 +445,7 @@ PLIST_API plist_t plist_new_string(const char *val)
return plist_new_node(data);
}
-PLIST_API plist_t plist_new_bool(uint8_t val)
+plist_t plist_new_bool(uint8_t val)
{
plist_data_t data = plist_new_plist_data();
data->type = PLIST_BOOLEAN;
@@ -321,16 +454,25 @@ PLIST_API plist_t plist_new_bool(uint8_t val)
return plist_new_node(data);
}
-PLIST_API plist_t plist_new_uint(uint64_t val)
+plist_t plist_new_uint(uint64_t val)
{
plist_data_t data = plist_new_plist_data();
- data->type = PLIST_UINT;
+ 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();
+ data->type = PLIST_INT;
data->intval = val;
data->length = sizeof(uint64_t);
return plist_new_node(data);
}
-PLIST_API plist_t plist_new_uid(uint64_t val)
+plist_t plist_new_uid(uint64_t val)
{
plist_data_t data = plist_new_plist_data();
data->type = PLIST_UID;
@@ -339,7 +481,7 @@ PLIST_API plist_t plist_new_uid(uint64_t val)
return plist_new_node(data);
}
-PLIST_API plist_t plist_new_real(double val)
+plist_t plist_new_real(double val)
{
plist_data_t data = plist_new_plist_data();
data->type = PLIST_REAL;
@@ -348,7 +490,7 @@ PLIST_API plist_t plist_new_real(double val)
return plist_new_node(data);
}
-PLIST_API plist_t plist_new_data(const char *val, uint64_t length)
+plist_t plist_new_data(const char *val, uint64_t length)
{
plist_data_t data = plist_new_plist_data();
data->type = PLIST_DATA;
@@ -358,7 +500,7 @@ PLIST_API plist_t plist_new_data(const char *val, uint64_t length)
return plist_new_node(data);
}
-PLIST_API plist_t plist_new_date(int32_t sec, int32_t usec)
+plist_t plist_new_date(int32_t sec, int32_t usec)
{
plist_data_t data = plist_new_plist_data();
data->type = PLIST_DATE;
@@ -367,15 +509,32 @@ PLIST_API plist_t plist_new_date(int32_t sec, int32_t usec)
return plist_new_node(data);
}
-PLIST_API void plist_free(plist_t plist)
+plist_t plist_new_null(void)
+{
+ plist_data_t data = plist_new_plist_data();
+ data->type = PLIST_NULL;
+ data->intval = 0;
+ data->length = 0;
+ return plist_new_node(data);
+}
+
+void plist_free(plist_t plist)
{
if (plist)
{
- plist_free_node(plist);
+ plist_free_node((node_t)plist);
+ }
+}
+
+void plist_mem_free(void* ptr)
+{
+ if (ptr)
+ {
+ free(ptr);
}
}
-static plist_t plist_copy_node(node_t *node)
+static plist_t plist_copy_node(node_t node)
{
plist_type node_type = PLIST_NONE;
plist_t newnode = NULL;
@@ -395,7 +554,7 @@ static plist_t plist_copy_node(node_t *node)
break;
case PLIST_KEY:
case PLIST_STRING:
- newdata->strval = strdup((char *) data->strval);
+ newdata->strval = strdup(data->strval);
break;
case PLIST_ARRAY:
if (data->hashtable) {
@@ -416,13 +575,13 @@ static plist_t plist_copy_node(node_t *node)
}
newnode = plist_new_node(newdata);
- node_t *ch;
+ node_t ch;
unsigned int node_index = 0;
for (ch = node_first_child(node); ch; ch = node_next_sibling(ch)) {
/* copy child node */
plist_t newch = plist_copy_node(ch);
/* attach to new parent node */
- node_attach(newnode, newch);
+ node_attach((node_t)newnode, (node_t)newch);
/* if needed, add child node to lookup table of parent node */
switch (node_type) {
case PLIST_ARRAY:
@@ -432,7 +591,7 @@ static plist_t plist_copy_node(node_t *node)
break;
case PLIST_DICT:
if (newdata->hashtable && (node_index % 2 != 0)) {
- hash_table_insert((hashtable_t*)newdata->hashtable, (node_prev_sibling((node_t*)newch))->data, newch);
+ hash_table_insert((hashtable_t*)newdata->hashtable, (node_prev_sibling((node_t)newch))->data, newch);
}
break;
default:
@@ -443,136 +602,140 @@ static plist_t plist_copy_node(node_t *node)
return newnode;
}
-PLIST_API plist_t plist_copy(plist_t node)
+plist_t plist_copy(plist_t node)
{
- return node ? plist_copy_node(node) : NULL;
+ return node ? plist_copy_node((node_t)node) : NULL;
}
-PLIST_API uint32_t plist_array_get_size(plist_t node)
+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);
+ ret = node_n_children((node_t)node);
}
return ret;
}
-PLIST_API plist_t plist_array_get_item(plist_t node, uint32_t n)
+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) && n < INT_MAX)
{
- ptrarray_t *pa = ((plist_data_t)((node_t*)node)->data)->hashtable;
+ 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, n);
+ ret = (plist_t)node_nth_child((node_t)node, n);
}
}
return ret;
}
-PLIST_API uint32_t plist_array_get_item_index(plist_t node)
+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);
+ return node_child_position((node_t)father, (node_t)node);
}
return UINT_MAX;
}
static void _plist_array_post_insert(plist_t node, plist_t item, long n)
{
- ptrarray_t *pa = ((plist_data_t)((node_t*)node)->data)->hashtable;
+ 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);
} else {
- if (((node_t*)node)->count > 100) {
+ 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);
+ for (current = (plist_t)node_first_child((node_t)node);
pa && current;
- current = (plist_t)node_next_sibling(current))
+ current = (plist_t)node_next_sibling((node_t)current))
{
ptr_array_add(pa, current);
}
- ((plist_data_t)((node_t*)node)->data)->hashtable = pa;
+ ((plist_data_t)((node_t)node)->data)->hashtable = pa;
}
}
}
-PLIST_API void plist_array_set_item(plist_t node, plist_t item, uint32_t n)
+void plist_array_set_item(plist_t node, plist_t item, uint32_t n)
{
+ if (!item) {
+ return;
+ }
if (node && PLIST_ARRAY == plist_get_node_type(node) && n < INT_MAX)
{
plist_t old_item = plist_array_get_item(node, n);
if (old_item)
{
- int idx = plist_free_node(old_item);
+ int idx = plist_free_node((node_t)old_item);
assert(idx >= 0);
if (idx < 0) {
return;
- } else {
- node_insert(node, idx, item);
- ptrarray_t* pa = ((plist_data_t)((node_t*)node)->data)->hashtable;
- if (pa) {
- ptr_array_set(pa, item, idx);
- }
+ }
+ node_insert((node_t)node, idx, (node_t)item);
+ ptrarray_t* pa = (ptrarray_t*)((plist_data_t)((node_t)node)->data)->hashtable;
+ if (pa) {
+ ptr_array_set(pa, item, idx);
}
}
}
- return;
}
-PLIST_API void plist_array_append_item(plist_t node, plist_t item)
+void plist_array_append_item(plist_t node, plist_t item)
{
+ if (!item) {
+ return;
+ }
if (node && PLIST_ARRAY == plist_get_node_type(node))
{
- node_attach(node, item);
+ node_attach((node_t)node, (node_t)item);
_plist_array_post_insert(node, item, -1);
}
- return;
}
-PLIST_API void plist_array_insert_item(plist_t node, plist_t item, uint32_t n)
+void plist_array_insert_item(plist_t node, plist_t item, uint32_t n)
{
+ if (!item) {
+ return;
+ }
if (node && PLIST_ARRAY == plist_get_node_type(node) && n < INT_MAX)
{
- node_insert(node, n, item);
+ node_insert((node_t)node, n, (node_t)item);
_plist_array_post_insert(node, item, (long)n);
}
- return;
}
-PLIST_API void plist_array_remove_item(plist_t node, uint32_t n)
+void plist_array_remove_item(plist_t node, uint32_t n)
{
if (node && PLIST_ARRAY == plist_get_node_type(node) && n < INT_MAX)
{
plist_t old_item = plist_array_get_item(node, n);
if (old_item)
{
- ptrarray_t* pa = ((plist_data_t)((node_t*)node)->data)->hashtable;
+ 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_API void plist_array_item_remove(plist_t node)
+void plist_array_item_remove(plist_t node)
{
plist_t father = plist_get_parent(node);
if (PLIST_ARRAY == plist_get_node_type(father))
{
- int n = node_child_position(father, node);
+ int n = node_child_position((node_t)father, (node_t)node);
if (n < 0) return;
- ptrarray_t* pa = ((plist_data_t)((node_t*)father)->data)->hashtable;
+ ptrarray_t* pa = (ptrarray_t*)((plist_data_t)((node_t)father)->data)->hashtable;
if (pa) {
ptr_array_remove(pa, n);
}
@@ -580,19 +743,18 @@ PLIST_API void plist_array_item_remove(plist_t node)
}
}
-PLIST_API void plist_array_new_iter(plist_t node, plist_array_iter *iter)
+void plist_array_new_iter(plist_t node, plist_array_iter *iter)
{
if (iter)
{
- *iter = malloc(sizeof(node_t*));
- *((node_t**)(*iter)) = node_first_child(node);
+ *iter = malloc(sizeof(node_t));
+ *((node_t*)(*iter)) = node_first_child((node_t)node);
}
- return;
}
-PLIST_API void plist_array_next_item(plist_t node, plist_array_iter iter, plist_t *item)
+void plist_array_next_item(plist_t node, plist_array_iter iter, plist_t *item)
{
- node_t** iter_node = (node_t**)iter;
+ node_t* iter_node = (node_t*)iter;
if (item)
{
@@ -607,32 +769,30 @@ PLIST_API void plist_array_next_item(plist_t node, plist_array_iter iter, plist_
}
*iter_node = node_next_sibling(*iter_node);
}
- return;
}
-PLIST_API uint32_t plist_dict_get_size(plist_t node)
+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;
+ ret = node_n_children((node_t)node) / 2;
}
return ret;
}
-PLIST_API void plist_dict_new_iter(plist_t node, plist_dict_iter *iter)
+void plist_dict_new_iter(plist_t node, plist_dict_iter *iter)
{
if (iter)
{
- *iter = malloc(sizeof(node_t*));
- *((node_t**)(*iter)) = node_first_child(node);
+ *iter = malloc(sizeof(node_t));
+ *((node_t*)(*iter)) = node_first_child((node_t)node);
}
- return;
}
-PLIST_API void plist_dict_next_item(plist_t node, plist_dict_iter iter, char **key, plist_t *val)
+void plist_dict_next_item(plist_t node, plist_dict_iter iter, char **key, plist_t *val)
{
- node_t** iter_node = (node_t**)iter;
+ node_t* iter_node = (node_t*)iter;
if (key)
{
@@ -656,30 +816,29 @@ PLIST_API void plist_dict_next_item(plist_t node, plist_dict_iter iter, char **k
}
*iter_node = node_next_sibling(*iter_node);
}
- return;
}
-PLIST_API void plist_dict_get_item_key(plist_t node, char **key)
+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_get_key_val( (plist_t) node_prev_sibling((node_t)node), key);
}
}
-PLIST_API plist_t plist_dict_item_get_key(plist_t node)
+plist_t plist_dict_item_get_key(plist_t node)
{
plist_t ret = NULL;
plist_t father = plist_get_parent(node);
if (PLIST_DICT == plist_get_node_type(father))
{
- ret = (plist_t)node_prev_sibling(node);
+ ret = (plist_t)node_prev_sibling((node_t)node);
}
return ret;
}
-PLIST_API plist_t plist_dict_get_item(plist_t node, const char* key)
+plist_t plist_dict_get_item(plist_t node, const char* key)
{
plist_t ret = NULL;
@@ -694,16 +853,16 @@ PLIST_API plist_t plist_dict_get_item(plist_t node, const char* key)
ret = (plist_t)hash_table_lookup(ht, &sdata);
} else {
plist_t current = NULL;
- for (current = (plist_t)node_first_child(node);
+ for (current = (plist_t)node_first_child((node_t)node);
current;
- current = (plist_t)node_next_sibling(node_next_sibling(current)))
+ current = (plist_t)node_next_sibling(node_next_sibling((node_t)current)))
{
data = plist_get_data(current);
assert( PLIST_KEY == plist_get_node_type(current) );
if (data && !strcmp(key, data->strval))
{
- ret = (plist_t)node_next_sibling(current);
+ ret = (plist_t)node_next_sibling((node_t)current);
break;
}
}
@@ -712,74 +871,69 @@ PLIST_API plist_t plist_dict_get_item(plist_t node, const char* key)
return ret;
}
-PLIST_API void plist_dict_set_item(plist_t node, const char* key, plist_t item)
+void plist_dict_set_item(plist_t node, const char* key, plist_t item)
{
+ if (!item) {
+ return;
+ }
if (node && PLIST_DICT == plist_get_node_type(node)) {
- node_t* old_item = plist_dict_get_item(node, key);
+ plist_t old_item = plist_dict_get_item(node, key);
plist_t key_node = NULL;
if (old_item) {
- int idx = plist_free_node(old_item);
+ int idx = plist_free_node((node_t)old_item);
assert(idx >= 0);
if (idx < 0) {
return;
- } else {
- node_insert(node, idx, item);
}
- key_node = node_prev_sibling(item);
+ node_insert((node_t)node, idx, (node_t)item);
+ key_node = node_prev_sibling((node_t)item);
} else {
key_node = plist_new_key(key);
- node_attach(node, key_node);
- node_attach(node, item);
+ node_attach((node_t)node, (node_t)key_node);
+ node_attach((node_t)node, (node_t)item);
}
- hashtable_t *ht = ((plist_data_t)((node_t*)node)->data)->hashtable;
+ hashtable_t *ht = (hashtable_t*)((plist_data_t)((node_t)node)->data)->hashtable;
if (ht) {
/* store pointer to item in hash table */
- hash_table_insert(ht, (plist_data_t)((node_t*)key_node)->data, item);
+ hash_table_insert(ht, (plist_data_t)((node_t)key_node)->data, item);
} else {
- if (((node_t*)node)->count > 500) {
+ 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);
+ for (current = (plist_t)node_first_child((node_t)node);
ht && current;
- current = (plist_t)node_next_sibling(node_next_sibling(current)))
+ current = (plist_t)node_next_sibling(node_next_sibling((node_t)current)))
{
- hash_table_insert(ht, ((node_t*)current)->data, node_next_sibling(current));
+ hash_table_insert(ht, ((node_t)current)->data, node_next_sibling((node_t)current));
}
- ((plist_data_t)((node_t*)node)->data)->hashtable = ht;
+ ((plist_data_t)((node_t)node)->data)->hashtable = ht;
}
}
}
- return;
}
-PLIST_API void plist_dict_insert_item(plist_t node, const char* key, plist_t item)
-{
- plist_dict_set_item(node, key, item);
-}
-
-PLIST_API void plist_dict_remove_item(plist_t node, const char* key)
+void plist_dict_remove_item(plist_t node, 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);
- hashtable_t* ht = ((plist_data_t)((node_t*)node)->data)->hashtable;
+ 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);
+ hash_table_remove(ht, ((node_t)key_node)->data);
}
plist_free(key_node);
plist_free(old_item);
}
}
- return;
}
-PLIST_API void plist_dict_merge(plist_t *target, plist_t source)
+void plist_dict_merge(plist_t *target, plist_t source)
{
if (!target || !*target || (plist_get_node_type(*target) != PLIST_DICT) || !source || (plist_get_node_type(source) != PLIST_DICT))
return;
@@ -800,10 +954,10 @@ PLIST_API void plist_dict_merge(plist_t *target, plist_t source)
free(key);
key = NULL;
} while (1);
- free(it);
+ free(it);
}
-PLIST_API plist_t plist_access_pathv(plist_t plist, uint32_t length, va_list v)
+plist_t plist_access_pathv(plist_t plist, uint32_t length, va_list v)
{
plist_t current = plist;
plist_type type = PLIST_NONE;
@@ -827,7 +981,7 @@ PLIST_API plist_t plist_access_pathv(plist_t plist, uint32_t length, va_list v)
return current;
}
-PLIST_API plist_t plist_access_path(plist_t plist, uint32_t length, ...)
+plist_t plist_access_path(plist_t plist, uint32_t length, ...)
{
plist_t ret = NULL;
va_list v;
@@ -855,7 +1009,7 @@ 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;
@@ -878,12 +1032,12 @@ static void plist_get_type_and_value(plist_t node, plist_type * type, void *valu
}
}
-PLIST_API plist_t plist_get_parent(plist_t node)
+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_API plist_type plist_get_node_type(plist_t node)
+plist_type plist_get_node_type(plist_t node)
{
if (node)
{
@@ -894,7 +1048,7 @@ PLIST_API plist_type plist_get_node_type(plist_t node)
return PLIST_NONE;
}
-PLIST_API void plist_get_key_val(plist_t node, char **val)
+void plist_get_key_val(plist_t node, char **val)
{
if (!node || !val)
return;
@@ -908,7 +1062,7 @@ PLIST_API void plist_get_key_val(plist_t node, char **val)
assert(length == strlen(*val));
}
-PLIST_API void plist_get_string_val(plist_t node, char **val)
+void plist_get_string_val(plist_t node, char **val)
{
if (!node || !val)
return;
@@ -922,7 +1076,7 @@ PLIST_API void plist_get_string_val(plist_t node, char **val)
assert(length == strlen(*val));
}
-PLIST_API const char* plist_get_string_ptr(plist_t node, uint64_t* length)
+const char* plist_get_string_ptr(plist_t node, uint64_t* length)
{
if (!node)
return NULL;
@@ -935,7 +1089,7 @@ PLIST_API const char* plist_get_string_ptr(plist_t node, uint64_t* length)
return (const char*)data->strval;
}
-PLIST_API void plist_get_bool_val(plist_t node, uint8_t * val)
+void plist_get_bool_val(plist_t node, uint8_t * val)
{
if (!node || !val)
return;
@@ -947,19 +1101,24 @@ PLIST_API void plist_get_bool_val(plist_t node, uint8_t * val)
assert(length == sizeof(uint8_t));
}
-PLIST_API void plist_get_uint_val(plist_t node, uint64_t * val)
+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_UINT != type)
+ if (PLIST_INT != type)
return;
plist_get_type_and_value(node, &type, (void *) val, &length);
assert(length == sizeof(uint64_t) || length == 16);
}
-PLIST_API void plist_get_uid_val(plist_t node, uint64_t * val)
+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;
@@ -971,7 +1130,7 @@ PLIST_API void plist_get_uid_val(plist_t node, uint64_t * val)
assert(length == sizeof(uint64_t));
}
-PLIST_API void plist_get_real_val(plist_t node, double *val)
+void plist_get_real_val(plist_t node, double *val)
{
if (!node || !val)
return;
@@ -983,7 +1142,7 @@ PLIST_API void plist_get_real_val(plist_t node, double *val)
assert(length == sizeof(double));
}
-PLIST_API void plist_get_data_val(plist_t node, char **val, uint64_t * length)
+void plist_get_data_val(plist_t node, char **val, uint64_t * length)
{
if (!node || !val || !length)
return;
@@ -993,7 +1152,7 @@ PLIST_API void plist_get_data_val(plist_t node, char **val, uint64_t * length)
plist_get_type_and_value(node, &type, (void *) val, length);
}
-PLIST_API const char* plist_get_data_ptr(plist_t node, uint64_t* length)
+const char* plist_get_data_ptr(plist_t node, uint64_t* length)
{
if (!node || !length)
return NULL;
@@ -1005,7 +1164,7 @@ PLIST_API const char* plist_get_data_ptr(plist_t node, uint64_t* length)
return (const char*)data->buff;
}
-PLIST_API void plist_get_date_val(plist_t node, int32_t * sec, int32_t * usec)
+void plist_get_date_val(plist_t node, int32_t * sec, int32_t * usec)
{
if (!node)
return;
@@ -1019,7 +1178,10 @@ PLIST_API void plist_get_date_val(plist_t node, int32_t * sec, int32_t * usec)
if (sec)
*sec = (int32_t)val;
if (usec)
- *usec = (int32_t)fabs((val - (int64_t)val) * 1000000);
+ {
+ val = fabs((val - (int64_t)val) * 1000000);
+ *usec = (int32_t)val;
+ }
}
int plist_data_compare(const void *a, const void *b)
@@ -1030,7 +1192,7 @@ int plist_data_compare(const void *a, const void *b)
if (!a || !b)
return FALSE;
- if (!((node_t*) a)->data || !((node_t*) b)->data)
+ if (!((node_t) a)->data || !((node_t) b)->data)
return FALSE;
val_a = plist_get_data((plist_t) a);
@@ -1042,46 +1204,36 @@ int plist_data_compare(const void *a, const void *b)
switch (val_a->type)
{
case PLIST_BOOLEAN:
- case PLIST_UINT:
+ case PLIST_NULL:
+ case PLIST_INT:
case PLIST_REAL:
case PLIST_DATE:
case PLIST_UID:
if (val_a->length != val_b->length)
return FALSE;
- if (val_a->intval == val_b->intval) //it is an union so this is sufficient
- return TRUE;
- else
- return FALSE;
+ return val_a->intval == val_b->intval; //it is an union so this is sufficient
case PLIST_KEY:
case PLIST_STRING:
- if (!strcmp(val_a->strval, val_b->strval))
- return TRUE;
- else
- return FALSE;
+ return strcmp(val_a->strval, val_b->strval) == 0;
case PLIST_DATA:
if (val_a->length != val_b->length)
return FALSE;
- if (!memcmp(val_a->buff, val_b->buff, val_a->length))
- return TRUE;
- else
- return FALSE;
+ 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;
+ return a == b;
+
default:
break;
}
return FALSE;
}
-PLIST_API char plist_compare_node_value(plist_t node_l, plist_t node_r)
+char plist_compare_node_value(plist_t node_l, plist_t node_r)
{
return plist_data_compare(node_l, node_r);
}
@@ -1117,7 +1269,7 @@ 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;
@@ -1140,7 +1292,7 @@ static void plist_set_element_val(plist_t node, plist_type type, const void *val
}
}
-PLIST_API void plist_set_key_val(plist_t node, const char *val)
+void plist_set_key_val(plist_t node, const char *val)
{
plist_t father = plist_get_parent(node);
plist_t item = plist_dict_get_item(father, val);
@@ -1150,43 +1302,48 @@ PLIST_API void plist_set_key_val(plist_t node, const char *val)
plist_set_element_val(node, PLIST_KEY, val, strlen(val));
}
-PLIST_API void plist_set_string_val(plist_t node, const char *val)
+void plist_set_string_val(plist_t node, const char *val)
{
plist_set_element_val(node, PLIST_STRING, val, strlen(val));
}
-PLIST_API void plist_set_bool_val(plist_t node, uint8_t val)
+void plist_set_bool_val(plist_t node, uint8_t val)
{
plist_set_element_val(node, PLIST_BOOLEAN, &val, sizeof(uint8_t));
}
-PLIST_API void plist_set_uint_val(plist_t node, uint64_t val)
+void plist_set_uint_val(plist_t node, uint64_t val)
+{
+ 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_UINT, &val, sizeof(uint64_t));
+ plist_set_element_val(node, PLIST_INT, &val, sizeof(uint64_t));
}
-PLIST_API void plist_set_uid_val(plist_t node, uint64_t val)
+void plist_set_uid_val(plist_t node, uint64_t val)
{
plist_set_element_val(node, PLIST_UID, &val, sizeof(uint64_t));
}
-PLIST_API void plist_set_real_val(plist_t node, double val)
+void plist_set_real_val(plist_t node, double val)
{
plist_set_element_val(node, PLIST_REAL, &val, sizeof(double));
}
-PLIST_API void plist_set_data_val(plist_t node, const char *val, uint64_t length)
+void plist_set_data_val(plist_t node, const char *val, uint64_t length)
{
plist_set_element_val(node, PLIST_DATA, val, length);
}
-PLIST_API void plist_set_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)
{
double val = (double)sec + (double)usec / 1000000;
plist_set_element_val(node, PLIST_DATE, &val, sizeof(struct timeval));
}
-PLIST_API int plist_bool_val_is_true(plist_t boolnode)
+int plist_bool_val_is_true(plist_t boolnode)
{
if (!PLIST_IS_BOOLEAN(boolnode)) {
return 0;
@@ -1196,23 +1353,58 @@ PLIST_API int plist_bool_val_is_true(plist_t boolnode)
return (bv == 1);
}
-PLIST_API int plist_uint_val_compare(plist_t uintnode, uint64_t cmpval)
+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_UINT(uintnode)) {
+ if (!PLIST_IS_INT(uintnode)) {
return -1;
}
uint64_t uintval = 0;
plist_get_uint_val(uintnode, &uintval);
if (uintval == cmpval) {
return 0;
- } else if (uintval < cmpval) {
+ }
+
+ if (uintval < cmpval) {
return -1;
- } else {
- return 1;
}
+
+ return 1;
}
-PLIST_API int plist_uid_val_compare(plist_t uidnode, uint64_t cmpval)
+int plist_uid_val_compare(plist_t uidnode, uint64_t cmpval)
{
if (!PLIST_IS_UID(uidnode)) {
return -1;
@@ -1221,14 +1413,16 @@ PLIST_API int plist_uid_val_compare(plist_t uidnode, uint64_t cmpval)
plist_get_uid_val(uidnode, &uidval);
if (uidval == cmpval) {
return 0;
- } else if (uidval < cmpval) {
+ }
+
+ if (uidval < cmpval) {
return -1;
- } else {
- return 1;
}
+
+ return 1;
}
-PLIST_API int plist_real_val_compare(plist_t realnode, double cmpval)
+int plist_real_val_compare(plist_t realnode, double cmpval)
{
if (!PLIST_IS_REAL(realnode)) {
return -1;
@@ -1241,23 +1435,29 @@ PLIST_API int plist_real_val_compare(plist_t realnode, double cmpval)
double diff = fabs(a - b);
if (a == b) {
return 0;
- } else if (a == 0 || b == 0 || (abs_a + abs_b < DBL_MIN)) {
+ }
+
+ if (a == 0 || b == 0 || (abs_a + abs_b < DBL_MIN)) {
if (diff < (DBL_EPSILON * DBL_MIN)) {
return 0;
- } else if (a < b) {
+ }
+
+ if (a < b) {
return -1;
}
} else {
if ((diff / fmin(abs_a + abs_b, DBL_MAX)) < DBL_EPSILON) {
return 0;
- } else if (a < b) {
+ }
+
+ if (a < b) {
return -1;
}
}
return 1;
}
-PLIST_API int plist_date_val_compare(plist_t datenode, int32_t cmpsec, int32_t cmpusec)
+int plist_date_val_compare(plist_t datenode, int32_t cmpsec, int32_t cmpusec)
{
if (!PLIST_IS_DATE(datenode)) {
return -1;
@@ -1269,14 +1469,16 @@ PLIST_API int plist_date_val_compare(plist_t datenode, int32_t cmpsec, int32_t c
uint64_t cmpval = ((int64_t)cmpsec << 32) | cmpusec;
if (dateval == cmpval) {
return 0;
- } else if (dateval < cmpval) {
+ }
+
+ if (dateval < cmpval) {
return -1;
- } else {
- return 1;
}
+
+ return 1;
}
-PLIST_API int plist_string_val_compare(plist_t strnode, const char* cmpval)
+int plist_string_val_compare(plist_t strnode, const char* cmpval)
{
if (!PLIST_IS_STRING(strnode)) {
return -1;
@@ -1285,7 +1487,7 @@ PLIST_API int plist_string_val_compare(plist_t strnode, const char* cmpval)
return strcmp(data->strval, cmpval);
}
-PLIST_API int plist_string_val_compare_with_size(plist_t strnode, const char* cmpval, size_t n)
+int plist_string_val_compare_with_size(plist_t strnode, const char* cmpval, size_t n)
{
if (!PLIST_IS_STRING(strnode)) {
return -1;
@@ -1294,7 +1496,7 @@ PLIST_API int plist_string_val_compare_with_size(plist_t strnode, const char* cm
return strncmp(data->strval, cmpval, n);
}
-PLIST_API int plist_string_val_contains(plist_t strnode, const char* substr)
+int plist_string_val_contains(plist_t strnode, const char* substr)
{
if (!PLIST_IS_STRING(strnode)) {
return 0;
@@ -1303,7 +1505,7 @@ PLIST_API int plist_string_val_contains(plist_t strnode, const char* substr)
return (strstr(data->strval, substr) != NULL);
}
-PLIST_API int plist_key_val_compare(plist_t keynode, const char* cmpval)
+int plist_key_val_compare(plist_t keynode, const char* cmpval)
{
if (!PLIST_IS_KEY(keynode)) {
return -1;
@@ -1312,7 +1514,7 @@ PLIST_API int plist_key_val_compare(plist_t keynode, const char* cmpval)
return strcmp(data->strval, cmpval);
}
-PLIST_API int plist_key_val_compare_with_size(plist_t keynode, const char* cmpval, size_t n)
+int plist_key_val_compare_with_size(plist_t keynode, const char* cmpval, size_t n)
{
if (!PLIST_IS_KEY(keynode)) {
return -1;
@@ -1321,7 +1523,7 @@ PLIST_API int plist_key_val_compare_with_size(plist_t keynode, const char* cmpva
return strncmp(data->strval, cmpval, n);
}
-PLIST_API int plist_key_val_contains(plist_t keynode, const char* substr)
+int plist_key_val_contains(plist_t keynode, const char* substr)
{
if (!PLIST_IS_KEY(keynode)) {
return 0;
@@ -1330,7 +1532,7 @@ PLIST_API int plist_key_val_contains(plist_t keynode, const char* substr)
return (strstr(data->strval, substr) != NULL);
}
-PLIST_API int plist_data_val_compare(plist_t datanode, const uint8_t* cmpval, size_t n)
+int plist_data_val_compare(plist_t datanode, const uint8_t* cmpval, size_t n)
{
if (!PLIST_IS_DATA(datanode)) {
return -1;
@@ -1338,13 +1540,16 @@ PLIST_API int plist_data_val_compare(plist_t datanode, const uint8_t* cmpval, si
plist_data_t data = plist_get_data(datanode);
if (data->length < n) {
return -1;
- } else if (data->length > n) {
+ }
+
+ if (data->length > n) {
return 1;
}
+
return memcmp(data->buff, cmpval, n);
}
-PLIST_API int plist_data_val_compare_with_size(plist_t datanode, const uint8_t* cmpval, size_t 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;
@@ -1356,7 +1561,7 @@ PLIST_API int plist_data_val_compare_with_size(plist_t datanode, const uint8_t*
return memcmp(data->buff, cmpval, n);
}
-PLIST_API int plist_data_val_contains(plist_t datanode, const uint8_t* cmpval, size_t n)
+int plist_data_val_contains(plist_t datanode, const uint8_t* cmpval, size_t n)
{
if (!PLIST_IS_DATA(datanode)) {
return -1;
@@ -1364,3 +1569,181 @@ PLIST_API int plist_data_val_contains(plist_t datanode, const uint8_t* cmpval, s
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)
+{
+ 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;
+ 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(plist, output, length, ((options & PLIST_OPT_COMPACT) == 0));
+ break;
+ case PLIST_FORMAT_OSTEP:
+ err = plist_to_openstep(plist, output, length, ((options & PLIST_OPT_COMPACT) == 0));
+ 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(plist, &output, &length, ((options & PLIST_OPT_COMPACT) == 0));
+ break;
+ case PLIST_FORMAT_OSTEP:
+ err = plist_to_openstep(plist, &output, &length, ((options & PLIST_OPT_COMPACT) == 0));
+ 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 7bf62a8..a993e3a 100644
--- a/src/plist.h
+++ b/src/plist.h
@@ -26,27 +26,31 @@
#include <config.h>
#endif
-#include "plist/plist.h"
-
#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 WIN32
+#ifdef LIBPLIST_STATIC
+ #define PLIST_API
+#elif defined(_WIN32)
#define PLIST_API __declspec( dllexport )
#else
- #ifdef HAVE_FVISIBILITY
+ #if __GNUC__ >= 4
#define PLIST_API __attribute__((visibility("default")))
#else
#define PLIST_API
#endif
#endif
+#include "plist/plist.h"
+
struct plist_data_s
{
union
@@ -65,10 +69,16 @@ struct plist_data_s
typedef struct plist_data_s *plist_data_t;
plist_t plist_new_node(plist_data_t data);
-plist_data_t plist_get_data(const plist_t node);
+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);
#endif
diff --git a/src/ptrarray.c b/src/ptrarray.c
index c499773..3a11031 100644
--- a/src/ptrarray.c
+++ b/src/ptrarray.c
@@ -45,7 +45,7 @@ void ptr_array_insert(ptrarray_t *pa, void *data, long array_index)
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;
}
if (array_index < 0 || array_index >= pa->len) {
diff --git a/src/strbuf.h b/src/strbuf.h
index 0d28edf..2fbfe93 100644
--- a/src/strbuf.h
+++ b/src/strbuf.h
@@ -27,6 +27,7 @@
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)
diff --git a/src/time64.c b/src/time64.c
index f1cffd4..07639df 100644
--- a/src/time64.c
+++ b/src/time64.c
@@ -1,4 +1,4 @@
-/*
+/*
Copyright (c) 2007-2010 Michael G Schwern
@@ -30,7 +30,7 @@ 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
+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().
@@ -59,11 +59,11 @@ static const short julian_days_by_month[2][12] = {
{0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335},
};
-static char wday_name[7][4] = {
+static const char wday_name[7][4] = {
"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"
};
-static char mon_name[12][4] = {
+static const char mon_name[12][4] = {
"Jan", "Feb", "Mar", "Apr", "May", "Jun",
"Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
};
@@ -176,34 +176,28 @@ static int is_exception_century(Year year)
static int cmp_date( const struct TM* left, const struct tm* right ) {
if( left->tm_year > right->tm_year )
return 1;
- else if( left->tm_year < right->tm_year )
+ if( left->tm_year < right->tm_year )
return -1;
-
if( left->tm_mon > right->tm_mon )
return 1;
- else if( left->tm_mon < right->tm_mon )
+ if( left->tm_mon < right->tm_mon )
return -1;
-
if( left->tm_mday > right->tm_mday )
return 1;
- else if( left->tm_mday < right->tm_mday )
+ if( left->tm_mday < right->tm_mday )
return -1;
-
if( left->tm_hour > right->tm_hour )
return 1;
- else if( left->tm_hour < right->tm_hour )
+ if( left->tm_hour < right->tm_hour )
return -1;
-
if( left->tm_min > right->tm_min )
return 1;
- else if( left->tm_min < right->tm_min )
+ if( left->tm_min < right->tm_min )
return -1;
-
if( left->tm_sec > right->tm_sec )
return 1;
- else if( left->tm_sec < right->tm_sec )
+ if( left->tm_sec < right->tm_sec )
return -1;
-
return 0;
}
@@ -233,12 +227,7 @@ Time64_T timegm64(const struct TM *date) {
Year orig_year = (Year)date->tm_year;
int cycles = 0;
- if( orig_year > 100 ) {
- cycles = (orig_year - 100) / 400;
- orig_year -= cycles * 400;
- days += (Time64_T)cycles * days_in_gregorian_cycle;
- }
- else if( orig_year < -300 ) {
+ if( (orig_year > 100) || (orig_year < -300) ) {
cycles = (orig_year - 100) / 400;
orig_year -= cycles * 400;
days += (Time64_T)cycles * days_in_gregorian_cycle;
@@ -293,7 +282,7 @@ static int check_tm(struct TM *tm)
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)]);
@@ -368,7 +357,7 @@ static int safe_year(const Year year)
year_cycle += 17;
year_cycle %= SOLAR_CYCLE_LENGTH;
- if( year_cycle < 0 )
+ if( year_cycle < 0 )
year_cycle = SOLAR_CYCLE_LENGTH + year_cycle;
assert( year_cycle >= 0 );
@@ -542,7 +531,7 @@ Time64_T mktime64(struct TM *input_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));
+ timev += seconds_between_years(year, (Year)(safe_date.tm_year) + 1900);
return timev;
}
@@ -674,7 +663,7 @@ struct TM *gmtime64_r (const Time64_T *in_time, struct TM *p)
p->tm_hour = v_tm_hour;
p->tm_mon = v_tm_mon;
p->tm_wday = v_tm_wday;
-
+
assert(check_tm(p));
return p;
@@ -717,7 +706,7 @@ struct TM *localtime64_r (const Time64_T *timev, struct TM *local_tm)
)
{
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;
+ gm_tm.tm_year = safe_year((Year)(gm_tm.tm_year) + 1900) - 1900;
}
safe_time = (time_t)timegm64(&gm_tm);
@@ -756,7 +745,7 @@ struct TM *localtime64_r (const Time64_T *timev, struct TM *local_tm)
local_tm->tm_year++;
}
- /* GMT is Jan 1st, xx01 year, but localtime is still Dec 31st
+ /* 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
@@ -766,7 +755,7 @@ struct TM *localtime64_r (const Time64_T *timev, struct TM *local_tm)
local_tm->tm_yday--;
assert(check_tm(local_tm));
-
+
return local_tm;
}
@@ -774,15 +763,15 @@ struct TM *localtime64_r (const Time64_T *timev, struct TM *local_tm)
static int valid_tm_wday( const struct TM* date ) {
if( 0 <= date->tm_wday && date->tm_wday <= 6 )
return 1;
- else
- return 0;
+
+ return 0;
}
static int valid_tm_mon( const struct TM* date ) {
if( 0 <= date->tm_mon && date->tm_mon <= 11 )
return 1;
- else
- return 0;
+
+ return 0;
}
diff --git a/src/time64.h b/src/time64.h
index bf174c6..efdc716 100644
--- a/src/time64.h
+++ b/src/time64.h
@@ -39,7 +39,7 @@ struct TM64 {
#define TM TM64
#else
#define TM tm
-#endif
+#endif
/* Declare public functions */
diff --git a/src/xplist.c b/src/xplist.c
index a7d52e5..66e1dba 100644
--- a/src/xplist.c
+++ b/src/xplist.c
@@ -41,7 +41,6 @@
#include <limits.h>
#include <node.h>
-#include <node_list.h>
#include "plist.h"
#include "base64.h"
@@ -71,7 +70,7 @@
#define MAC_EPOCH 978307200
-#define MAX_DATA_BYTES_PER_LINE(__i) (((76 - (__i << 3)) >> 2) * 3)
+#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\
@@ -81,8 +80,10 @@ 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
void plist_xml_init(void)
@@ -101,6 +102,13 @@ void plist_xml_deinit(void)
/* deinit XML stuff */
}
+void plist_xml_set_debug(int debug)
+{
+#if DEBUG
+ plist_xml_debug = debug;
+#endif
+}
+
static size_t dtostr(char *buf, size_t bufsize, double realval)
{
size_t len = 0;
@@ -113,7 +121,7 @@ static size_t dtostr(char *buf, size_t bufsize, double realval)
} else {
size_t i = 0;
len = snprintf(buf, bufsize, "%.*g", 17, realval);
- for (i = 0; i < len; i++) {
+ for (i = 0; buf && i < len; i++) {
if (buf[i] == ',') {
buf[i] = '.';
break;
@@ -125,7 +133,7 @@ static size_t dtostr(char *buf, size_t bufsize, double realval)
return len;
}
-static void node_to_xml(node_t* node, bytearray_t **outbuf, uint32_t depth)
+static plist_err_t node_to_xml(node_t node, bytearray_t **outbuf, uint32_t depth)
{
plist_data_t node_data = NULL;
@@ -139,8 +147,10 @@ static void node_to_xml(node_t* node, bytearray_t **outbuf, uint32_t depth)
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;
+ }
node_data = plist_get_data(node);
@@ -158,14 +168,14 @@ static void node_to_xml(node_t* node, bytearray_t **outbuf, uint32_t depth)
}
break;
- case PLIST_UINT:
+ case PLIST_INT:
tag = XPLIST_INT;
tag_len = XPLIST_INT_LEN;
val = (char*)malloc(64);
if (node_data->length == 16) {
- val_len = snprintf(val, 64, "%"PRIu64, node_data->intval);
+ val_len = snprintf(val, 64, "%" PRIu64, node_data->intval);
} else {
- val_len = snprintf(val, 64, "%"PRIi64, node_data->intval);
+ val_len = snprintf(val, 64, "%" PRIi64, node_data->intval);
}
break;
@@ -211,8 +221,7 @@ static void node_to_xml(node_t* node, bytearray_t **outbuf, uint32_t depth)
struct TM _btime;
struct TM *btime = gmtime64_r(&timev, &_btime);
if (btime) {
- val = (char*)malloc(24);
- memset(val, 0, 24);
+ val = (char*)calloc(1, 24);
struct tm _tmcopy;
copy_TM64_to_tm(btime, &_tmcopy);
val_len = strftime(val, 24, "%Y-%m-%dT%H:%M:%SZ", &_tmcopy);
@@ -228,13 +237,16 @@ static void node_to_xml(node_t* node, bytearray_t **outbuf, uint32_t depth)
tag_len = XPLIST_DICT_LEN;
val = (char*)malloc(64);
if (node_data->length == 16) {
- val_len = snprintf(val, 64, "%"PRIu64, node_data->intval);
+ val_len = snprintf(val, 64, "%" PRIu64, node_data->intval);
} else {
- val_len = snprintf(val, 64, "%"PRIi64, node_data->intval);
+ val_len = snprintf(val, 64, "%" 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:
- break;
+ return PLIST_ERR_UNKNOWN;
}
for (i = 0; i < depth; i++) {
@@ -352,9 +364,10 @@ static void node_to_xml(node_t* node, bytearray_t **outbuf, uint32_t depth)
if (node_data->type == PLIST_DICT && node->children) {
assert((node->children->count % 2) == 0);
}
- node_t *ch;
+ node_t ch;
for (ch = node_first_child(node); ch; ch = node_next_sibling(ch)) {
- node_to_xml(ch, outbuf, depth+1);
+ plist_err_t res = node_to_xml(ch, outbuf, depth+1);
+ if (res < 0) return res;
}
/* fix indent for structured types */
@@ -370,8 +383,7 @@ static void node_to_xml(node_t* node, bytearray_t **outbuf, uint32_t depth)
str_buf_append(*outbuf, ">", 1);
}
str_buf_append(*outbuf, "\n", 1);
-
- return;
+ return PLIST_ERR_SUCCESS;
}
static void parse_date(const char *strval, struct TM *btime)
@@ -403,7 +415,7 @@ static int num_digits_i(int64_t i)
int64_t po10;
n=1;
if (i < 0) {
- i = -i;
+ i = (i == INT64_MIN) ? INT64_MAX : -i;
n++;
}
po10=10;
@@ -432,15 +444,15 @@ static int num_digits_u(uint64_t i)
return n;
}
-static void node_estimate_size(node_t *node, uint64_t *size, uint32_t depth)
+static plist_err_t node_estimate_size(node_t node, uint64_t *size, uint32_t depth)
{
plist_data_t data;
if (!node) {
- return;
+ return PLIST_ERR_INVALID_ARG;
}
data = plist_get_data(node);
if (node->children) {
- node_t *ch;
+ node_t ch;
for (ch = node_first_child(node); ch; ch = node_next_sibling(ch)) {
node_estimate_size(ch, size, depth + 1);
}
@@ -473,7 +485,7 @@ static void node_estimate_size(node_t *node, uint64_t *size, uint32_t depth)
*size += data->length;
*size += (XPLIST_KEY_LEN << 1) + 6;
break;
- case PLIST_UINT:
+ case PLIST_INT:
if (data->length == 16) {
*size += num_digits_u(data->intval);
} else {
@@ -482,7 +494,7 @@ static void node_estimate_size(node_t *node, uint64_t *size, uint32_t depth)
*size += (XPLIST_INT_LEN << 1) + 6;
break;
case PLIST_REAL:
- *size += num_digits_i((int64_t)data->realval) + 7;
+ *size += dtostr(NULL, 0, data->realval);
*size += (XPLIST_REAL_LEN << 1) + 6;
break;
case PLIST_DATE:
@@ -505,37 +517,58 @@ static void node_estimate_size(node_t *node, uint64_t *size, uint32_t depth)
*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:
- break;
+ PLIST_XML_WRITE_ERR("invalid node type encountered\n");
+ return PLIST_ERR_UNKNOWN;
}
*size += indent;
}
+ return PLIST_ERR_SUCCESS;
}
-PLIST_API void plist_to_xml(plist_t plist, char **plist_xml, uint32_t * length)
+plist_err_t plist_to_xml(plist_t plist, char **plist_xml, uint32_t * length)
{
uint64_t size = 0;
- node_estimate_size(plist, &size, 0);
+ plist_err_t res;
+
+ if (!plist || !plist_xml || !length) {
+ return PLIST_ERR_INVALID_ARG;
+ }
+
+ 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);
- node_to_xml(plist, &outbuf, 0);
+ 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 = outbuf->data;
+ *plist_xml = (char*)outbuf->data;
*length = outbuf->len - 1;
outbuf->data = NULL;
str_buf_free(outbuf);
-}
-PLIST_API void plist_to_xml_free(char *plist_xml)
-{
- free(plist_xml);
+ return PLIST_ERR_SUCCESS;
}
struct _parse_ctx {
@@ -638,14 +671,14 @@ static void text_parts_free(text_part_t *tp)
{
while (tp) {
text_part_t *tmp = tp;
- tp = tp->next;
+ 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 = malloc(sizeof(text_part_t));
+ 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;
@@ -751,7 +784,7 @@ static text_part_t* get_text_parts(parse_ctx ctx, const char* tag, size_t tag_le
}
} while (1);
ctx->pos++;
- if (ctx->pos >= ctx->end-tag_len || strncmp(ctx->pos, tag, tag_len)) {
+ 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++;
return NULL;
@@ -897,9 +930,9 @@ static char* text_parts_get_content(text_part_t *tp, int unesc_entities, size_t
text_part_t *tmp = tp;
while (tp && tp->begin) {
total_length += tp->length;
- tp = tp->next;
+ tp = (text_part_t*)tp->next;
}
- str = malloc(total_length + 1);
+ str = (char*)malloc(total_length + 1);
assert(str);
p = str;
tp = tmp;
@@ -914,7 +947,7 @@ static char* text_parts_get_content(text_part_t *tp, int unesc_entities, size_t
}
}
p += len;
- tp = tp->next;
+ tp = (text_part_t*)tp->next;
}
*p = '\0';
if (length) {
@@ -926,7 +959,7 @@ static char* text_parts_get_content(text_part_t *tp, int unesc_entities, size_t
return str;
}
-static void node_from_xml(parse_ctx ctx, plist_t *plist)
+static plist_err_t node_from_xml(parse_ctx ctx, plist_t *plist)
{
char *tag = NULL;
char *keyname = NULL;
@@ -967,7 +1000,7 @@ static void node_from_xml(parse_ctx ctx, plist_t *plist)
ctx->err++;
goto err_out;
}
- if (strncmp(ctx->pos, "?>", 2)) {
+ if (strncmp(ctx->pos, "?>", 2) != 0) {
PLIST_XML_ERR("Couldn't find <? tag closing marker\n");
ctx->err++;
goto err_out;
@@ -979,7 +1012,7 @@ static void node_from_xml(parse_ctx ctx, plist_t *plist)
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)) {
+ if (ctx->pos > ctx->end-3 || strncmp(ctx->pos, "-->", 3) != 0) {
PLIST_XML_ERR("Couldn't find end of comment\n");
ctx->err++;
goto err_out;
@@ -1008,7 +1041,7 @@ static void node_from_xml(parse_ctx ctx, plist_t *plist)
}
if (embedded_dtd) {
find_str(ctx, "]>", 2, 1);
- if (ctx->pos > ctx->end-2 || strncmp(ctx->pos, "]>", 2)) {
+ if (ctx->pos > ctx->end-2 || strncmp(ctx->pos, "]>", 2) != 0) {
PLIST_XML_ERR("Couldn't find end of DOCTYPE\n");
ctx->err++;
goto err_out;
@@ -1034,7 +1067,7 @@ static void node_from_xml(parse_ctx ctx, plist_t *plist)
goto err_out;
}
int taglen = ctx->pos - p;
- tag = malloc(taglen + 1);
+ tag = (char*)malloc(taglen + 1);
strncpy(tag, p, taglen);
tag[taglen] = '\0';
if (*ctx->pos != '>') {
@@ -1072,7 +1105,7 @@ static void node_from_xml(parse_ctx ctx, plist_t *plist)
goto err_out;
}
- struct node_path_item *path_item = malloc(sizeof(struct node_path_item));
+ 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++;
@@ -1100,7 +1133,7 @@ static void node_from_xml(parse_ctx ctx, plist_t *plist)
goto err_out;
}
struct node_path_item *path_item = node_path;
- node_path = node_path->prev;
+ node_path = (struct node_path_item*)node_path->prev;
free(path_item);
free(tag);
@@ -1123,7 +1156,7 @@ static void node_from_xml(parse_ctx ctx, plist_t *plist)
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(first_part.next);
+ text_parts_free((text_part_t*)first_part.next);
ctx->err++;
goto err_out;
}
@@ -1132,7 +1165,7 @@ static void node_from_xml(parse_ctx ctx, plist_t *plist)
char *str_content = text_parts_get_content(tp, 0, NULL, &requires_free);
if (!str_content) {
PLIST_XML_ERR("Could not get text content for '%s' node\n", tag);
- text_parts_free(first_part.next);
+ text_parts_free((text_part_t*)first_part.next);
ctx->err++;
goto err_out;
}
@@ -1144,7 +1177,7 @@ static void node_from_xml(parse_ctx ctx, plist_t *plist)
}
str++;
}
- data->intval = strtoull((char*)str, NULL, 0);
+ data->intval = strtoull(str, NULL, 0);
if (is_negative || (data->intval <= INT64_MAX)) {
uint64_t v = data->intval;
if (is_negative) {
@@ -1161,20 +1194,20 @@ static void node_from_xml(parse_ctx ctx, plist_t *plist)
} else {
is_empty = 1;
}
- text_parts_free(tp->next);
+ text_parts_free((text_part_t*)tp->next);
}
if (is_empty) {
data->intval = 0;
data->length = 8;
}
- data->type = PLIST_UINT;
+ 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(first_part.next);
+ text_parts_free((text_part_t*)first_part.next);
ctx->err++;
goto err_out;
}
@@ -1183,7 +1216,7 @@ static void node_from_xml(parse_ctx ctx, plist_t *plist)
char *str_content = text_parts_get_content(tp, 0, NULL, &requires_free);
if (!str_content) {
PLIST_XML_ERR("Could not get text content for '%s' node\n", tag);
- text_parts_free(first_part.next);
+ text_parts_free((text_part_t*)first_part.next);
ctx->err++;
goto err_out;
}
@@ -1192,7 +1225,7 @@ static void node_from_xml(parse_ctx ctx, plist_t *plist)
free(str_content);
}
}
- text_parts_free(tp->next);
+ text_parts_free((text_part_t*)tp->next);
}
data->type = PLIST_REAL;
data->length = 8;
@@ -1218,12 +1251,12 @@ static void node_from_xml(parse_ctx ctx, plist_t *plist)
size_t length = 0;
if (!tp) {
PLIST_XML_ERR("Could not parse text content for '%s' node\n", tag);
- text_parts_free(first_part.next);
+ text_parts_free((text_part_t*)first_part.next);
ctx->err++;
goto err_out;
}
str = text_parts_get_content(tp, 1, &length, NULL);
- text_parts_free(first_part.next);
+ 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++;
@@ -1251,7 +1284,7 @@ static void node_from_xml(parse_ctx ctx, plist_t *plist)
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(first_part.next);
+ text_parts_free((text_part_t*)first_part.next);
ctx->err++;
goto err_out;
}
@@ -1260,7 +1293,7 @@ static void node_from_xml(parse_ctx ctx, plist_t *plist)
char *str_content = text_parts_get_content(tp, 0, NULL, &requires_free);
if (!str_content) {
PLIST_XML_ERR("Could not get text content for '%s' node\n", tag);
- text_parts_free(first_part.next);
+ text_parts_free((text_part_t*)first_part.next);
ctx->err++;
goto err_out;
}
@@ -1274,7 +1307,7 @@ static void node_from_xml(parse_ctx ctx, plist_t *plist)
free(str_content);
}
}
- text_parts_free(tp->next);
+ text_parts_free((text_part_t*)tp->next);
}
data->type = PLIST_DATA;
} else if (!strcmp(tag, XPLIST_DATE)) {
@@ -1283,7 +1316,7 @@ static void node_from_xml(parse_ctx ctx, plist_t *plist)
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(first_part.next);
+ text_parts_free((text_part_t*)first_part.next);
ctx->err++;
goto err_out;
}
@@ -1294,7 +1327,7 @@ static void node_from_xml(parse_ctx ctx, plist_t *plist)
char *str_content = text_parts_get_content(tp, 0, &length, &requires_free);
if (!str_content) {
PLIST_XML_ERR("Could not get text content for '%s' node\n", tag);
- text_parts_free(first_part.next);
+ text_parts_free((text_part_t*)first_part.next);
ctx->err++;
goto err_out;
}
@@ -1314,7 +1347,7 @@ static void node_from_xml(parse_ctx ctx, plist_t *plist)
free(str_content);
}
}
- text_parts_free(tp->next);
+ text_parts_free((text_part_t*)tp->next);
data->realval = (double)(timev - MAC_EPOCH);
}
data->length = sizeof(double);
@@ -1358,7 +1391,7 @@ static void node_from_xml(parse_ctx ctx, plist_t *plist)
}
}
if (!is_empty && (data->type == PLIST_DICT || data->type == PLIST_ARRAY)) {
- struct node_path_item *path_item = malloc(sizeof(struct node_path_item));
+ 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++;
@@ -1383,10 +1416,10 @@ static void node_from_xml(parse_ctx ctx, plist_t *plist)
goto err_out;
}
struct node_path_item *path_item = node_path;
- node_path = node_path->prev;
+ node_path = (struct node_path_item*)node_path->prev;
free(path_item);
- parent = ((node_t*)parent)->parent;
+ parent = ((node_t)parent)->parent;
if (!parent) {
goto err_out;
}
@@ -1414,24 +1447,41 @@ err_out:
/* clean up node_path if required */
while (node_path) {
struct node_path_item *path_item = node_path;
- node_path = path_item->prev;
+ node_path = (struct node_path_item*)path_item->prev;
free(path_item);
}
if (ctx->err) {
plist_free(*plist);
*plist = NULL;
+ return PLIST_ERR_PARSE;
}
+
+ /* 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_UINT(value)) {
+ uint64_t u64val = 0;
+ plist_get_uint_val(value, &u64val);
+ plist_free(*plist);
+ *plist = plist_new_uid(u64val);
+ }
+ }
+
+ return PLIST_ERR_SUCCESS;
}
-PLIST_API 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)
{
+ if (!plist) {
+ return PLIST_ERR_INVALID_ARG;
+ }
+ *plist = NULL;
if (!plist_xml || (length == 0)) {
- *plist = NULL;
- return;
+ return PLIST_ERR_INVALID_ARG;
}
struct _parse_ctx ctx = { plist_xml, plist_xml + length, 0 };
- node_from_xml(&ctx, plist);
+ return node_from_xml(&ctx, plist);
}
diff --git a/test/Makefile.am b/test/Makefile.am
index 4519de0..a4191c4 100644
--- a/test/Makefile.am
+++ b/test/Makefile.am
@@ -1,13 +1,42 @@
-AM_CFLAGS = $(GLOBAL_CFLAGS) -I$(top_srcdir)/include -I$(top_srcdir)/libcnary/include
+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
+noinst_PROGRAMS = \
+ plist_cmp \
+ plist_test \
+ plist_test++ \
+ integer_set_test \
+ plist_btest \
+ plist_jtest \
+ plist_otest
plist_cmp_SOURCES = plist_cmp.c
-plist_cmp_LDADD = $(top_builddir)/src/libplist.la $(top_builddir)/libcnary/libcnary.la
+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.la
+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
TESTS = \
empty.test \
@@ -16,6 +45,12 @@ TESTS = \
large.test \
huge.test \
bigarray.test \
+ empty++.test \
+ small++.test \
+ medium++.test \
+ large++.test \
+ huge++.test \
+ bigarray++.test \
dates.test \
timezone1.test \
timezone2.test \
@@ -32,7 +67,19 @@ TESTS = \
cdata.test \
offsetsize.test \
refsize.test \
- malformed_dict.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
EXTRA_DIST = \
$(TESTS) \
@@ -75,9 +122,20 @@ EXTRA_DIST = \
data/signedunsigned.bplist \
data/signedunsigned.plist \
data/unsigned.bplist \
- data/unsigned.plist
+ 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)
+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
index 0815391..3678f27 100755
--- a/test/amp.test
+++ b/test/amp.test
@@ -1,14 +1,16 @@
## -*- sh -*-
-set -e
-
DATASRC=$top_srcdir/test/data
TESTFILE=amp.plist
DATAIN0=$DATASRC/$TESTFILE
-DATAOUT0=$top_builddir/test/data/$TESTFILE.out
+DATAOUT0=$top_builddir/test/data/amp.test.out
rm -rf $DATAOUT0
$top_builddir/tools/plistutil -i $DATAIN0 -o $DATAOUT0
-if test -f $DATAOUT0; then
+
+# 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
index 4b819f4..1b88d43 100755
--- a/test/bigarray.test
+++ b/test/bigarray.test
@@ -9,7 +9,7 @@ if ! test -d "$DATAOUT"; then
fi
echo "Converting"
-$top_builddir/test/plist_test $DATASRC/$TESTFILE $DATAOUT/$TESTFILE.out
+$top_builddir/test/plist_test $DATASRC/$TESTFILE $DATAOUT/bigarray.test.out
echo "Comparing"
-$top_builddir/test/plist_cmp $DATASRC/$TESTFILE $DATAOUT/$TESTFILE.out
+$top_builddir/test/plist_cmp $DATASRC/$TESTFILE $DATAOUT/bigarray.test.out
diff --git a/test/cdata.test b/test/cdata.test
index 77e7e45..b4f3ed2 100755
--- a/test/cdata.test
+++ b/test/cdata.test
@@ -5,7 +5,7 @@ set -e
DATASRC=$top_srcdir/test/data
TESTFILE=cdata.plist
DATAIN0=$DATASRC/$TESTFILE
-DATAOUT0=$top_builddir/test/data/$TESTFILE.bin
+DATAOUT0=$top_builddir/test/data/cdata.test.bin
$top_builddir/tools/plistutil -i $DATAIN0 -o $DATAOUT0
diff --git a/test/data/data.bplist b/test/data/data.bplist
new file mode 100644
index 0000000..955993f
--- /dev/null
+++ b/test/data/data.bplist
Binary files differ
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/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/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/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
new file mode 100644
index 0000000..e1fc6f8
--- /dev/null
+++ b/test/data/uid.bplist
Binary files differ
diff --git a/test/dates.test b/test/dates.test
index 2865717..06bf034 100755
--- a/test/dates.test
+++ b/test/dates.test
@@ -5,8 +5,8 @@ set -e
DATASRC=$top_srcdir/test/data
TESTFILE=7.plist
DATAIN0=$DATASRC/$TESTFILE
-DATAOUT0=$top_builddir/test/data/$TESTFILE.bin
-DATAOUT1=$top_builddir/test/data/$TESTFILE.xml
+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
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
index 0a21f33..bc71562 100755
--- a/test/empty.test
+++ b/test/empty.test
@@ -9,7 +9,7 @@ if ! test -d "$DATAOUT"; then
fi
echo "Converting"
-$top_builddir/test/plist_test $DATASRC/$TESTFILE $DATAOUT/$TESTFILE.out
+$top_builddir/test/plist_test $DATASRC/$TESTFILE $DATAOUT/empty.test.out
echo "Comparing"
-$top_builddir/test/plist_cmp $DATASRC/$TESTFILE $DATAOUT/$TESTFILE.out
+$top_builddir/test/plist_cmp $DATASRC/$TESTFILE $DATAOUT/empty.test.out
diff --git a/test/empty_keys.test b/test/empty_keys.test
index 4291e8c..19bbb08 100755
--- a/test/empty_keys.test
+++ b/test/empty_keys.test
@@ -5,7 +5,7 @@ set -e
DATASRC=$top_srcdir/test/data
TESTFILE=empty_keys.plist
DATAIN0=$DATASRC/$TESTFILE
-DATAOUT0=$top_builddir/test/data/$TESTFILE.bin
+DATAOUT0=$top_builddir/test/data/empty_keys.test.bin
$top_builddir/tools/plistutil -i $DATAIN0 -o $DATAOUT0
diff --git a/test/entities.test b/test/entities.test
index b87e4c7..a828e15 100755
--- a/test/entities.test
+++ b/test/entities.test
@@ -5,7 +5,7 @@ set -e
DATASRC=$top_srcdir/test/data
TESTFILE=entities.plist
DATAIN0=$DATASRC/$TESTFILE
-DATAOUT0=$top_builddir/test/data/$TESTFILE.bin
+DATAOUT0=$top_builddir/test/data/entities.test.bin
$top_builddir/tools/plistutil -i $DATAIN0 -o $DATAOUT0
diff --git a/test/hex.test b/test/hex.test
index 99b88bc..414cdbc 100755
--- a/test/hex.test
+++ b/test/hex.test
@@ -5,7 +5,7 @@ set -e
DATASRC=$top_srcdir/test/data
TESTFILE=hex.plist
DATAIN0=$DATASRC/$TESTFILE
-DATAOUT0=$top_builddir/test/data/$TESTFILE.bin
+DATAOUT0=$top_builddir/test/data/hex.test.bin
$top_builddir/tools/plistutil -i $DATAIN0 -o $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
index 8585ed5..560c21f 100755
--- a/test/huge.test
+++ b/test/huge.test
@@ -9,7 +9,7 @@ if ! test -d "$DATAOUT"; then
fi
echo "Converting"
-$top_builddir/test/plist_test $DATASRC/$TESTFILE $DATAOUT/$TESTFILE.out
+$top_builddir/test/plist_test $DATASRC/$TESTFILE $DATAOUT/huge.test.out
echo "Comparing"
-$top_builddir/test/plist_cmp $DATASRC/$TESTFILE $DATAOUT/$TESTFILE.out
+$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
index 079bcdd..5d4da93 100755
--- a/test/invalid_tag.test
+++ b/test/invalid_tag.test
@@ -1,14 +1,16 @@
## -*- sh -*-
-set -e
-
DATASRC=$top_srcdir/test/data
TESTFILE=invalid_tag.plist
DATAIN0=$DATASRC/$TESTFILE
-DATAOUT0=$top_builddir/test/data/$TESTFILE.out
+DATAOUT0=$top_builddir/test/data/invalid_tag.test.out
rm -rf $DATAOUT0
$top_builddir/tools/plistutil -i $DATAIN0 -o $DATAOUT0
-if test -f $DATAOUT0; then
+
+# 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..c532316
--- /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 [ $? -neq 2 ]; then
+ exit 1
+fi
+
+echo "Converting (failure expected)"
+$top_builddir/tools/plistutil -f json -i $DATASRC/$TESTFILE1 -o /dev/null
+if [ $? -neq 2 ]; then
+ exit 2
+fi
+
+echo "Converting (failure expected)"
+$top_builddir/tools/plistutil -f json -i $DATASRC/$TESTFILE2 -o /dev/null
+if [ $? -neq 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
index b46743f..d3cce7a 100755
--- a/test/large.test
+++ b/test/large.test
@@ -9,7 +9,7 @@ if ! test -d "$DATAOUT"; then
fi
echo "Converting"
-$top_builddir/test/plist_test $DATASRC/$TESTFILE $DATAOUT/$TESTFILE.out
+$top_builddir/test/plist_test $DATASRC/$TESTFILE $DATAOUT/large.test.out
echo "Comparing"
-$top_builddir/test/plist_cmp $DATASRC/$TESTFILE $DATAOUT/$TESTFILE.out
+$top_builddir/test/plist_cmp $DATASRC/$TESTFILE $DATAOUT/large.test.out
diff --git a/test/malformed_dict.test b/test/malformed_dict.test
index f45ae7e..1d3ba2b 100755
--- a/test/malformed_dict.test
+++ b/test/malformed_dict.test
@@ -1,11 +1,15 @@
## -*- sh -*-
-set -e
-
DATASRC=$top_srcdir/test/data
TESTFILE=malformed_dict.bplist
DATAIN0=$DATASRC/$TESTFILE
-DATAOUT0=$top_builddir/test/data/$TESTFILE.out
+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
index 39213b4..300c016 100755
--- a/test/medium.test
+++ b/test/medium.test
@@ -9,7 +9,7 @@ if ! test -d "$DATAOUT"; then
fi
echo "Converting"
-$top_builddir/test/plist_test $DATASRC/$TESTFILE $DATAOUT/$TESTFILE.out
+$top_builddir/test/plist_test $DATASRC/$TESTFILE $DATAOUT/medium.test.out
echo "Comparing"
-$top_builddir/test/plist_cmp $DATASRC/$TESTFILE $DATAOUT/$TESTFILE.out
+$top_builddir/test/plist_cmp $DATASRC/$TESTFILE $DATAOUT/medium.test.out
diff --git a/test/order.test b/test/order.test
index f8a7702..bd89b9d 100755
--- a/test/order.test
+++ b/test/order.test
@@ -6,7 +6,7 @@ DATASRC=$top_srcdir/test/data
TESTFILE=order.bplist
DATAIN0=$DATASRC/$TESTFILE
DATAIN1=$DATASRC/order.plist
-DATAOUT0=$top_builddir/test/data/$TESTFILE.out
+DATAOUT0=$top_builddir/test/data/order.test.out
$top_builddir/tools/plistutil -i $DATAIN0 -o $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..9222394
--- /dev/null
+++ b/test/ostep-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_OSTEP_DEBUG=1
+
+echo "Converting (failure expected)"
+$top_builddir/tools/plistutil -f openstep -i $DATASRC/$TESTFILE0 -o /dev/null
+if [ $? -neq 2 ]; then
+ exit 1
+fi
+
+echo "Converting (failure expected)"
+$top_builddir/tools/plistutil -f openstepn -i $DATASRC/$TESTFILE1 -o /dev/null
+if [ $? -neq 2 ]; then
+ exit 2
+fi
+
+echo "Converting (failure expected)"
+$top_builddir/tools/plistutil -f openstep -i $DATASRC/$TESTFILE2 -o /dev/null
+if [ $? -neq 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..0f2c1c8
--- /dev/null
+++ b/test/plist_btest.c
@@ -0,0 +1,131 @@
+/*
+ * 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));
+ fread(plist_bin, sizeof(char), size_in, iplist);
+ 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..130e3c7
--- /dev/null
+++ b/test/plist_jtest.c
@@ -0,0 +1,131 @@
+/*
+ * 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));
+ fread(plist_json, sizeof(char), size_in, iplist);
+ 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..14168f8
--- /dev/null
+++ b/test/plist_otest.c
@@ -0,0 +1,130 @@
+/*
+ * 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));
+ fread(plist_ostep, sizeof(char), size_in, iplist);
+ 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 b498e1d..6e3947a 100644
--- a/test/plist_test.c
+++ b/test/plist_test.c
@@ -77,36 +77,32 @@ 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;
diff --git a/test/recursion.test b/test/recursion.test
index 0120a12..97bfb45 100755
--- a/test/recursion.test
+++ b/test/recursion.test
@@ -1,11 +1,15 @@
## -*- sh -*-
-set -e
-
DATASRC=$top_srcdir/test/data
TESTFILE=recursion.bplist
DATAIN0=$DATASRC/$TESTFILE
-DATAOUT0=$top_builddir/test/data/$TESTFILE.out
+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/signedunsigned1.test b/test/signedunsigned1.test
index 798de86..52f7080 100755
--- a/test/signedunsigned1.test
+++ b/test/signedunsigned1.test
@@ -13,8 +13,8 @@ CMPFILE1=unsigned.bplist
DATACMP0=$DATASRC/$CMPFILE0
DATACMP1=$DATASRC/$CMPFILE1
-DATAOUT0=$top_builddir/test/data/$TESTFILE0.bin
-DATAOUT1=$top_builddir/test/data/$TESTFILE1.bin
+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 --git a/test/signedunsigned2.test b/test/signedunsigned2.test
index 2ea8b49..192f1a6 100755
--- a/test/signedunsigned2.test
+++ b/test/signedunsigned2.test
@@ -13,8 +13,8 @@ CMPFILE1=unsigned.plist
DATACMP0=$DATASRC/$CMPFILE0
DATACMP1=$DATASRC/$CMPFILE1
-DATAOUT0=$top_builddir/test/data/$TESTFILE0.bin
-DATAOUT1=$top_builddir/test/data/$TESTFILE1.bin
+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 --git a/test/signedunsigned3.test b/test/signedunsigned3.test
index d732e72..87bdcbc 100755
--- a/test/signedunsigned3.test
+++ b/test/signedunsigned3.test
@@ -13,8 +13,8 @@ CMPFILE1=signedunsigned.plist
DATACMP0=$DATASRC/$CMPFILE0
DATACMP1=$DATASRC/$CMPFILE1
-DATAOUT0=$top_builddir/test/data/$TESTFILE0.bin
-DATAOUT1=$top_builddir/test/data/$TESTFILE1.xml
+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 --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
index 8c25e70..dd2faf8 100755
--- a/test/small.test
+++ b/test/small.test
@@ -9,7 +9,7 @@ if ! test -d "$DATAOUT"; then
fi
echo "Converting"
-$top_builddir/test/plist_test $DATASRC/$TESTFILE $DATAOUT/$TESTFILE.out
+$top_builddir/test/plist_test $DATASRC/$TESTFILE $DATAOUT/small.test.out
echo "Comparing"
-$top_builddir/test/plist_cmp $DATASRC/$TESTFILE $DATAOUT/$TESTFILE.out
+$top_builddir/test/plist_cmp $DATASRC/$TESTFILE $DATAOUT/small.test.out
diff --git a/test/timezone1.test b/test/timezone1.test
index 843b166..5de7cea 100755
--- a/test/timezone1.test
+++ b/test/timezone1.test
@@ -5,9 +5,9 @@ set -e
DATASRC=$top_srcdir/test/data
TESTFILE=7.plist
DATAIN0=$DATASRC/$TESTFILE
-DATAOUT0=$top_builddir/test/data/$TESTFILE.tz0.bin
-DATAOUT1=$top_builddir/test/data/$TESTFILE.tz1.bin
-DATAOUT2=$top_builddir/test/data/$TESTFILE.tz2.bin
+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
diff --git a/test/timezone2.test b/test/timezone2.test
index 57db720..79db523 100755
--- a/test/timezone2.test
+++ b/test/timezone2.test
@@ -5,10 +5,10 @@ set -e
DATASRC=$top_srcdir/test/data
TESTFILE=7.plist
DATAIN0=$DATASRC/$TESTFILE
-DATAOUT0=$top_builddir/test/data/$TESTFILE.bin
-DATAOUT1=$top_builddir/test/data/$TESTFILE.tz0.xml
-DATAOUT2=$top_builddir/test/data/$TESTFILE.tz1.xml
-DATAOUT3=$top_builddir/test/data/$TESTFILE.tz2.xml
+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
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/tools/Makefile.am b/tools/Makefile.am
index 67b7dd7..9f3214d 100644
--- a/tools/Makefile.am
+++ b/tools/Makefile.am
@@ -1,7 +1,10 @@
-AM_CFLAGS = $(GLOBAL_CFLAGS) -I$(top_srcdir)/include
+AM_CFLAGS = \
+ $(GLOBAL_CFLAGS) \
+ -I$(top_srcdir)/include
+
AM_LDFLAGS =
bin_PROGRAMS = plistutil
plistutil_SOURCES = plistutil.c
-plistutil_LDADD = $(top_builddir)/src/libplist.la
+plistutil_LDADD = $(top_builddir)/src/libplist-2.0.la
diff --git a/tools/plistutil.c b/tools/plistutil.c
index c7f1a3f..8121a7d 100644
--- a/tools/plistutil.c
+++ b/tools/plistutil.c
@@ -2,8 +2,9 @@
* plistutil.c
* Simple tool to convert a plist into different formats
*
- * Copyright (c) 2009-2015 Martin Szulecki All Rights Reserved.
- * Copyright (c) 2008 Zach C. All Rights Reserved.
+ * 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
@@ -20,6 +21,9 @@
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
#include "plist/plist.h"
@@ -30,8 +34,6 @@
#include <errno.h>
#include <unistd.h>
-#define BUF_SIZE 2048 // Seems to be a decent start to cover most stdin files
-
#ifdef _MSC_VER
#pragma warning(disable:4996)
#endif
@@ -39,28 +41,47 @@
typedef struct _options
{
char *in_file, *out_file;
- uint8_t debug, in_fmt, out_fmt; // fmts 0 = undef, 1 = bin, 2 = xml, 3 = json someday
+ 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)
static void print_usage(int argc, char *argv[])
{
char *name = NULL;
name = strrchr(argv[0], '/');
- printf("Usage: %s -i|--infile FILE [-o|--outfile FILE] [-d|--debug]\n", (name ? name + 1: argv[0]));
- printf("Convert a plist FILE from binary to XML format or vice-versa.\n\n");
+ 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 [bin|xml] Force output format, regardless of input type\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(" -c, --compact JSON and OpenStep only: Print output in compact form.\n");
+ printf(" By default, the output will be pretty-printed.\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[])
{
int i = 0;
- options_t *options = (options_t *) malloc(sizeof(options_t));
- memset(options, 0, sizeof(options_t));
+ options_t *options = (options_t*)calloc(1, sizeof(options_t));
options->out_fmt = 0;
for (i = 1; i < argc; i++)
@@ -76,8 +97,7 @@ static options_t *parse_arguments(int argc, char *argv[])
i++;
continue;
}
-
- if (!strcmp(argv[i], "--outfile") || !strcmp(argv[i], "-o"))
+ else if (!strcmp(argv[i], "--outfile") || !strcmp(argv[i], "-o"))
{
if ((i + 1) == argc)
{
@@ -88,8 +108,7 @@ static options_t *parse_arguments(int argc, char *argv[])
i++;
continue;
}
-
- if (!strcmp(argv[i], "--format") || !strcmp(argv[i], "-f"))
+ else if (!strcmp(argv[i], "--format") || !strcmp(argv[i], "-f"))
{
if ((i + 1) == argc)
{
@@ -97,28 +116,69 @@ static options_t *parse_arguments(int argc, char *argv[])
return NULL;
}
if (!strncmp(argv[i+1], "bin", 3)) {
- options->out_fmt = 1;
+ options->out_fmt = PLIST_FORMAT_BINARY;
} else if (!strncmp(argv[i+1], "xml", 3)) {
- options->out_fmt = 2;
+ options->out_fmt = PLIST_FORMAT_XML;
+ } else if (!strncmp(argv[i+1], "json", 4)) {
+ options->out_fmt = PLIST_FORMAT_JSON;
+ } else if (!strncmp(argv[i+1], "openstep", 8) || !strncmp(argv[i+1], "ostep", 5)) {
+ options->out_fmt = PLIST_FORMAT_OSTEP;
} else {
- printf("ERROR: Unsupported output format\n");
+ fprintf(stderr, "ERROR: Unsupported output format\n");
free(options);
return NULL;
}
i++;
continue;
}
-
- if (!strcmp(argv[i], "--debug") || !strcmp(argv[i], "-d"))
+ else if (!strcmp(argv[i], "--compact") || !strcmp(argv[i], "-c"))
{
- options->debug = 1;
+ options->flags |= OPT_COMPACT;
}
-
- if (!strcmp(argv[i], "--help") || !strcmp(argv[i], "-h"))
+ else if (!strcmp(argv[i], "--sort") || !strcmp(argv[i], "-s"))
+ {
+ options->flags |= OPT_SORT;
+ }
+ else if (!strcmp(argv[i], "--print") || !strcmp(argv[i], "-p"))
+ {
+ if ((i + 1) == argc)
+ {
+ free(options);
+ return NULL;
+ }
+ options->in_file = argv[i + 1];
+ 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;
+ }
+ }
+ i++;
+ continue;
+ }
+ else if (!strcmp(argv[i], "--debug") || !strcmp(argv[i], "-d"))
+ {
+ options->flags |= OPT_DEBUG;
+ }
+ else if (!strcmp(argv[i], "--help") || !strcmp(argv[i], "-h"))
{
free(options);
return NULL;
}
+ else if (!strcmp(argv[i], "--version") || !strcmp(argv[i], "-v"))
+ {
+ printf("plistutil %s\n", PACKAGE_VERSION);
+ exit(EXIT_SUCCESS);
+ }
+ else
+ {
+ fprintf(stderr, "ERROR: Invalid option '%s'\n", argv[i]);
+ free(options);
+ return NULL;
+ }
}
return options;
@@ -126,11 +186,15 @@ static options_t *parse_arguments(int argc, char *argv[])
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;
int read_size = 0;
+ int read_capacity = 4096;
char *plist_entire = NULL;
struct stat filestats;
options_t *options = parse_arguments(argc, argv);
@@ -141,26 +205,32 @@ int main(int argc, char *argv[])
return 0;
}
+ 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) * BUF_SIZE);
+ read_size = 0;
+ plist_entire = malloc(sizeof(char) * read_capacity);
if(plist_entire == NULL)
{
- printf("ERROR: Failed to allocate buffer to read from stdin");
- free(options);
+ fprintf(stderr, "ERROR: Failed to allocate buffer to read from stdin");
+ free(options);
return 1;
}
- plist_entire[read_size] = '\0';
+ plist_entire[read_size] = '\0';
char ch;
- while(read(STDIN_FILENO, &ch, 1) > 0)
+ while(read(STDIN_FILENO, &ch, 1) > 0)
{
- if (read_size >= BUF_SIZE) {
+ if (read_size >= read_capacity) {
char *old = plist_entire;
- plist_entire = realloc(plist_entire, sizeof(char) * (read_size + 1));
+ read_capacity += 4096;
+ plist_entire = realloc(plist_entire, sizeof(char) * read_capacity);
if (plist_entire == NULL)
{
- printf("ERROR: Failed to reallocate stdin buffer\n");
+ fprintf(stderr, "ERROR: Failed to reallocate stdin buffer\n");
free(old);
free(options);
return 1;
@@ -169,30 +239,34 @@ int main(int argc, char *argv[])
plist_entire[read_size] = ch;
read_size++;
}
+ if (read_size >= read_capacity) {
+ char *old = plist_entire;
+ plist_entire = realloc(plist_entire, sizeof(char) * (read_capacity+1));
+ if (plist_entire == NULL)
+ {
+ fprintf(stderr, "ERROR: Failed to reallocate stdin buffer\n");
+ free(old);
+ free(options);
+ return 1;
+ }
+ }
plist_entire[read_size] = '\0';
- // Not positive we need this, but it doesnt seem to hurt lol
+ // Not positive we need this, but it doesnt seem to hurt lol
if(ferror(stdin))
{
- printf("ERROR: reading from stdin.\n");
+ fprintf(stderr, "ERROR: reading from stdin.\n");
free(plist_entire);
free(options);
return 1;
}
-
- if (read_size < 8) {
- printf("ERROR: Input file is too small to contain valid plist data.\n");
- free(plist_entire);
- free(options);
- return 1;
- }
}
else
{
- // read input file
- iplist = fopen(options->in_file, "rb");
+ // read input file
+ iplist = fopen(options->in_file, "rb");
if (!iplist) {
- printf("ERROR: Could not open input file '%s': %s\n", options->in_file, strerror(errno));
+ fprintf(stderr, "ERROR: Could not open input file '%s': %s\n", options->in_file, strerror(errno));
free(options);
return 1;
}
@@ -200,55 +274,56 @@ int main(int argc, char *argv[])
memset(&filestats, '\0', sizeof(struct stat));
fstat(fileno(iplist), &filestats);
- if (filestats.st_size < 8) {
- printf("ERROR: Input file is too small to contain valid plist data.\n");
- free(options);
- fclose(iplist);
- return -1;
- }
-
plist_entire = (char *) malloc(sizeof(char) * (filestats.st_size + 1));
read_size = fread(plist_entire, sizeof(char), filestats.st_size, iplist);
+ plist_entire[read_size] = '\0';
fclose(iplist);
}
if (options->out_fmt == 0) {
- // convert from binary to xml or vice-versa<br>
+ // convert from binary to xml or vice-versa
if (plist_is_binary(plist_entire, read_size))
{
- plist_from_bin(plist_entire, read_size, &root_node);
- plist_to_xml(root_node, &plist_out, &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
{
- plist_from_xml(plist_entire, read_size, &root_node);
- plist_to_bin(root_node, &plist_out, &size);
+ 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
{
- if (options->out_fmt == 1) {
- if (plist_is_binary(plist_entire, read_size))
- {
- plist_out = malloc(sizeof(char) * read_size);
- memcpy(plist_out, plist_entire, read_size);
- size = read_size;
+ input_res = plist_from_memory(plist_entire, read_size, &root_node, NULL);
+ if (input_res == PLIST_ERR_SUCCESS) {
+ if (options->flags & OPT_SORT) {
+ plist_sort(root_node);
}
- else
- {
- plist_from_xml(plist_entire, read_size, &root_node);
- plist_to_bin(root_node, &plist_out, &size);
- }
- } else if (options->out_fmt == 2) {
- if (plist_is_binary(plist_entire, read_size)) {
- plist_from_bin(plist_entire, read_size, &root_node);
- plist_to_xml(root_node, &plist_out, &size);
- }
- else
- {
- plist_out = malloc(sizeof(char) * read_size);
- memcpy(plist_out, plist_entire, read_size);
- size = read_size;
+ 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) {
+ output_res = plist_to_json(root_node, &plist_out, &size, !(options->flags & OPT_COMPACT));
+ } else if (options->out_fmt == PLIST_FORMAT_OSTEP) {
+ output_res = plist_to_openstep(root_node, &plist_out, &size, !(options->flags & OPT_COMPACT));
+ } 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;
}
}
}
@@ -257,11 +332,11 @@ int main(int argc, char *argv[])
if (plist_out)
{
- if (options->out_file != NULL && strcmp(options->out_file, "-"))
+ if (options->out_file != NULL && strcmp(options->out_file, "-") != 0)
{
FILE *oplist = fopen(options->out_file, "wb");
if (!oplist) {
- printf("ERROR: Could not open output file '%s': %s\n", options->out_file, strerror(errno));
+ fprintf(stderr, "ERROR: Could not open output file '%s': %s\n", options->out_file, strerror(errno));
free(options);
return 1;
}
@@ -274,9 +349,36 @@ int main(int argc, char *argv[])
free(plist_out);
}
- else
- printf("ERROR: Failed to convert input file.\n");
+
+ if (input_res == PLIST_ERR_SUCCESS) {
+ switch (output_res) {
+ case PLIST_ERR_SUCCESS:
+ 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;
+ }
+ } 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;
+ default:
+ fprintf(stderr, "ERROR: Could not parse plist data (%d)\n", input_res);
+ ret = 1;
+ break;
+ }
+ }
free(options);
- return 0;
+ return ret;
}