diff options
155 files changed, 8517 insertions, 1260 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 new file mode 100644 index 0000000..68948af --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,162 @@ +name: build + +on: + push: + schedule: + - cron: '0 0 1 * *' + +jobs: + build-linux-ubuntu: + runs-on: ubuntu-latest + steps: + - name: install dependencies + run: | + sudo apt-get update + pip install cython + - name: prepare environment + run: | + echo "target_triplet=`gcc -dumpmachine`" >> $GITHUB_ENV + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + - name: autogen + run: | + export PYTHON=python3 + export LDFLAGS="-Wl,-rpath=/usr/local/lib" + ./autogen.sh --enable-debug + - name: make + run: make + - name: make check + run: make check + - name: make install + run: sudo make install + - name: prepare artifact + run: | + mkdir -p dest + DESTDIR=`pwd`/dest make install + tar -C dest -cf libplist.tar usr + - name: publish artifact + uses: actions/upload-artifact@v4 + with: + name: libplist-latest_${{env.target_triplet}} + path: libplist.tar + build-macOS: + runs-on: macOS-latest + steps: + - name: install dependencies + run: | + if test -x "`which port`"; then + sudo port install libtool autoconf automake + else + brew install libtool autoconf automake + fi + pip3 install cython + shell: bash + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + - name: autogen + run: | + SDKDIR=`xcrun --sdk macosx --show-sdk-path` + TESTARCHS="arm64 x86_64" + USEARCHS= + for ARCH in $TESTARCHS; do + if echo "int main(int argc, char **argv) { return 0; }" |clang -arch $ARCH -o /dev/null -isysroot $SDKDIR -x c - 2>/dev/null; then + USEARCHS="$USEARCHS -arch $ARCH" + fi + done + export CFLAGS="$USEARCHS -isysroot $SDKDIR" + echo "Using CFLAGS: $CFLAGS" + PYTHON3_BIN=`xcrun -f python3` + if test -x $PYTHON3_BIN; then + export PYTHON=$PYTHON3_BIN + PYTHON_VER=`$PYTHON3_BIN -c "import distutils.sysconfig; print(distutils.sysconfig.get_config_var('VERSION'))"` + PYTHON_EXEC_PREFIX=`$PYTHON3_BIN -c "import distutils.sysconfig; print(distutils.sysconfig.get_config_var('exec_prefix'))"` + PYTHON_LIBS_PATH=$PYTHON_EXEC_PREFIX/lib + PYTHON_FRAMEWORK_PATH=$PYTHON_EXEC_PREFIX/Python3 + export PYTHON_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-2019 + defaults: + run: + shell: msys2 {0} + strategy: + fail-fast: false + matrix: + include: [ + { msystem: MINGW64, arch: x86_64 }, + { msystem: MINGW32, arch: i686 } + ] + steps: + - uses: msys2/setup-msys2@v2 + with: + msystem: ${{ matrix.msystem }} + release: false + update: false + install: >- + base-devel + git + mingw-w64-${{ matrix.arch }}-gcc + 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: ./autogen.sh CC=gcc CXX=g++ --enable-debug + - name: make + run: make + - name: make check + run: make check + - name: print test logs + run: | + for I in test/*.trs; do + RES=`grep ":test-result" $I |cut -d ":" -f 3` + if test $RES != PASS; then + TESTNAME=`basename $I .trs` + echo $TESTNAME: + cat test/$TESTNAME.log + echo + fi + done + shell: bash + - name: make install + run: make install + - name: prepare artifact + run: | + mkdir -p dest + DESTDIR=`pwd`/dest make install + tar -C dest -cf libplist.tar ${{ env.dest }} + - name: publish artifact + uses: actions/upload-artifact@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 @@ -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 @@ -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 ~~~~~~~~~~~~~ @@ -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 @@ -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 a3da3e7..fe6592b 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.0.0], [https://github.com/libimobiledevice/libplist/issues],, [http://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=5:0:2 +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]) +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,44 +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) +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]) @@ -112,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([3.0.0]) + if [test "x$CYTHON" != "xfalse"]; then + AM_PATH_PYTHON([3.0], [CYTHON_PYTHON]) + fi else CYTHON=false fi if [test "x$CYTHON" != "xfalse"]; then PKG_PROG_PKG_CONFIG AC_MSG_CHECKING([for libplist Cython bindings]) - 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 @@ -136,12 +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" 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 ;; @@ -150,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 @@ -174,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.]) @@ -244,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" @@ -255,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]) @@ -262,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 0149ffa..b5f4ef6 100644 --- a/cython/plist.pyx +++ b/cython/plist.pyx @@ -2,14 +2,10 @@ cimport cpython cimport libc.stdlib from libc.stdint cimport * -# https://groups.google.com/forum/#!topic/cython-users/xoKNFTRagvk -cdef _from_string_and_size(char *s, size_t length): - return s[:length].encode('utf-8') - cdef extern from *: ctypedef enum plist_type: PLIST_BOOLEAN, - PLIST_UINT, + PLIST_INT, PLIST_REAL, PLIST_STRING, PLIST_ARRAY, @@ -18,6 +14,7 @@ cdef extern from *: PLIST_DATA, PLIST_KEY, PLIST_UID, + PLIST_NULL, PLIST_NONE plist_t plist_new_bool(uint8_t val) @@ -28,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) @@ -51,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) @@ -81,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 @@ -181,19 +186,22 @@ cdef Bool Bool_factory(plist_t c_node, bint managed=True): cdef class Integer(Node): def __cinit__(self, object value=None, *args, **kwargs): if value is None: - self._c_node = plist_new_uint(0) + self._c_node = plist_new_int(0) else: - self._c_node = plist_new_uint(int(value)) + if value < 0 or value <= INT64_MAX: + self._c_node = plist_new_int(int(value)) + else: + self._c_node = plist_new_uint(int(value)) def __repr__(self): - cdef 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() @@ -213,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) @@ -285,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() @@ -316,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): @@ -457,6 +488,8 @@ cdef String String_factory(plist_t c_node, bint managed=True): instance._c_node = c_node return instance +MAC_EPOCH = 978307200 + cdef extern from "plist_util.h": void datetime_to_ints(object obj, int32_t* sec, int32_t* usec) object ints_to_datetime(int32_t sec, int32_t usec) @@ -470,6 +503,7 @@ cdef plist_t create_date_plist(value=None): node = plist_new_date(0, 0) elif check_datetime(value): datetime_to_ints(value, &secs, &usecs) + secs -= MAC_EPOCH node = plist_new_date(secs, usecs) return node @@ -500,6 +534,7 @@ cdef class Date(Node): cdef int32_t secs = 0 cdef int32_t usecs = 0 plist_get_date_val(self._c_node, &secs, &usecs) + secs += MAC_EPOCH return ints_to_datetime(secs, usecs) cpdef set_value(self, object value): @@ -549,7 +584,7 @@ cdef class Data(Node): plist_get_data_val(self._c_node, &val, &length) try: - return _from_string_and_size(val, length) + return bytes(val[:length]) finally: libc.stdlib.free(val) @@ -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 Binary files differnew file mode 100644 index 0000000..10c37ec --- /dev/null +++ b/fuzz/jplist-crashes/clusterfuzz-testcase-minimized-jplist_fuzzer-5161359598288896 diff --git a/fuzz/jplist-crashes/clusterfuzz-testcase-minimized-jplist_fuzzer-5486807695884288 b/fuzz/jplist-crashes/clusterfuzz-testcase-minimized-jplist_fuzzer-5486807695884288 new file mode 100644 index 0000000..e440e5c --- /dev/null +++ b/fuzz/jplist-crashes/clusterfuzz-testcase-minimized-jplist_fuzzer-5486807695884288 @@ -0,0 +1 @@ +3
\ No newline at end of file diff --git a/fuzz/jplist-crashes/clusterfuzz-testcase-minimized-jplist_fuzzer-5576833398079488 b/fuzz/jplist-crashes/clusterfuzz-testcase-minimized-jplist_fuzzer-5576833398079488 new file mode 100644 index 0000000..734562b --- /dev/null +++ b/fuzz/jplist-crashes/clusterfuzz-testcase-minimized-jplist_fuzzer-5576833398079488 @@ -0,0 +1 @@ +{""f ""}{""[""""""]""}{""[""""""]""}{""[""""""]""[""""""]""}{""[""""""]""}{""[""""""]""}{""[""""""]""{""}{""[""""""]""}{""[""""""]""}{""[""""]""}{""[""""""]""}{""[""""""]""}{""[""""""{""}{""[""""""]""}{""[""""""]""}{""[""""""]""}{""[""""]""}{""[""""{""[""]""}{""[""""""]""}{""[""""""]""}{""}{""}{""}{""}{""[""""""]""}{""[""""""]}{""[""""]""}{""[""""]""}{""}{""[""""""]""}""]}{""[""""""]""}{""}{""}{""[""""""]""}{""[""""]}]}{""[""""""]""}{""[""""""]""}{""}{""[""""""]""}}
\ No newline at end of file diff --git a/fuzz/jplist-crashes/clusterfuzz-testcase-minimized-jplist_fuzzer-5704016686874624 b/fuzz/jplist-crashes/clusterfuzz-testcase-minimized-jplist_fuzzer-5704016686874624 new file mode 100644 index 0000000..7853ee8 --- /dev/null +++ b/fuzz/jplist-crashes/clusterfuzz-testcase-minimized-jplist_fuzzer-5704016686874624 @@ -0,0 +1 @@ +{"Some Ak this: \u-170141183460469231731687303715884040192 !!!"}
\ No newline at end of file diff --git a/fuzz/jplist-crashes/clusterfuzz-testcase-minimized-jplist_fuzzer-6639076466360320 b/fuzz/jplist-crashes/clusterfuzz-testcase-minimized-jplist_fuzzer-6639076466360320 new file mode 100644 index 0000000..3cf20d5 --- /dev/null +++ b/fuzz/jplist-crashes/clusterfuzz-testcase-minimized-jplist_fuzzer-6639076466360320 @@ -0,0 +1 @@ +-
\ No newline at end of file diff --git a/fuzz/jplist-leaks/clusterfuzz-testcase-minimized-jplist_fuzzer-4881933237092352 b/fuzz/jplist-leaks/clusterfuzz-testcase-minimized-jplist_fuzzer-4881933237092352 new file mode 100644 index 0000000..d8ae3d3 --- /dev/null +++ b/fuzz/jplist-leaks/clusterfuzz-testcase-minimized-jplist_fuzzer-4881933237092352 @@ -0,0 +1 @@ +{"Some ASCII stringtring":"Test ASCII String","Some UTF8 strnngs":["à éèçù","日本語","汉è¯/漢語","í•œêµì–´/ì¡°ì„ ë§","руÑÑкий Ñзык","الْعَرَبيّة","עִבְרִית","jÄ™zyk polski¹à¤¿à¤¨à¥à¤¦à¥€"],"Keys & \"entities\"":"hellow world & others <nodes> are \"fun!?'","Boolean":false,"Anothe\uD800\uDC00rue,"Some Int":32434543632,"Some String with Unicode entity":"Yeah check this: \u1234 !!!"}
\ No newline at end of file diff --git a/fuzz/jplist-leaks/clusterfuzz-testcase-minimized-jplist_fuzzer-5069883912617984 b/fuzz/jplist-leaks/clusterfuzz-testcase-minimized-jplist_fuzzer-5069883912617984 new file mode 100644 index 0000000..a1f85f7 --- /dev/null +++ b/fuzz/jplist-leaks/clusterfuzz-testcase-minimized-jplist_fuzzer-5069883912617984 @@ -0,0 +1 @@ +{""A}
\ No newline at end of file diff --git a/fuzz/jplist-leaks/clusterfuzz-testcase-minimized-jplist_fuzzer-5816111696838656 b/fuzz/jplist-leaks/clusterfuzz-testcase-minimized-jplist_fuzzer-5816111696838656 new file mode 100644 index 0000000..f19d601 --- /dev/null +++ b/fuzz/jplist-leaks/clusterfuzz-testcase-minimized-jplist_fuzzer-5816111696838656 @@ -0,0 +1 @@ +[[][[][][][][][]{"ÿ222ÀÀÀÀÀÀÀÀÀÀÀÀ\uDBFF\uDFFFÀÀÀÀeÀÀ2ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿßÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ2221Ø2222222ÀÀÀÀÀÀÀÀÀÀÀ\uDBFF\uDFFFÀÀÀÀeÀÀ2ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ[]\r[][][][]ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿßÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ2221Ø2222222222h che[][][][][][][][][][][][][][][][][][][][][][][[][][][][][][][][][][][][][][][][][][][][][][][][][]22222h che22#"}[][][][][][][][][][][][][][][][][][][][][][][][][][][][][][]][]]
\ No newline at end of file diff --git a/fuzz/jplist.dict b/fuzz/jplist.dict new file mode 100644 index 0000000..e08245a --- /dev/null +++ b/fuzz/jplist.dict @@ -0,0 +1,52 @@ +# +# AFL dictionary for JSON +# ----------------------- +# +# Just the very basics. +# +# Inspired by a dictionary by Jakub Wilk <jwilk@jwilk.net> +# + +"0" +",0" +":0" +"0:" +"-1.2e+3" + +"true" +"false" +"null" + +"\"\"" +",\"\"" +":\"\"" +"\"\":" + +"{}" +",{}" +":{}" +"{\"\":0}" +"{{}}" + +"[]" +",[]" +":[]" +"[0]" +"[[]]" + +"''" +"\\" +"\\b" +"\\f" +"\\n" +"\\r" +"\\t" +"\\u0000" +"\\x00" +"\\0" +"\\uD800\\uDC00" +"\\uDBFF\\uDFFF" + +"\"\":0" +"//" +"/**/" diff --git a/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 Binary files differnew file mode 100644 index 0000000..25d9ed9 --- /dev/null +++ b/fuzz/oplist-crashes/clusterfuzz-testcase-minimized-oplist_fuzzer-4683683569467392 diff --git a/fuzz/oplist-crashes/clusterfuzz-testcase-minimized-oplist_fuzzer-4716194114699264 b/fuzz/oplist-crashes/clusterfuzz-testcase-minimized-oplist_fuzzer-4716194114699264 new file mode 100644 index 0000000..2fa08dc --- /dev/null +++ b/fuzz/oplist-crashes/clusterfuzz-testcase-minimized-oplist_fuzzer-4716194114699264 @@ -0,0 +1 @@ +(<
\ No newline at end of file diff --git a/fuzz/oplist-crashes/clusterfuzz-testcase-minimized-oplist_fuzzer-4789915626110976 b/fuzz/oplist-crashes/clusterfuzz-testcase-minimized-oplist_fuzzer-4789915626110976 Binary files differnew file mode 100644 index 0000000..a36e0a2 --- /dev/null +++ b/fuzz/oplist-crashes/clusterfuzz-testcase-minimized-oplist_fuzzer-4789915626110976 diff --git a/fuzz/oplist-leaks/clusterfuzz-testcase-minimized-oplist_fuzzer-6043548602728448 b/fuzz/oplist-leaks/clusterfuzz-testcase-minimized-oplist_fuzzer-6043548602728448 new file mode 100644 index 0000000..6598aac --- /dev/null +++ b/fuzz/oplist-leaks/clusterfuzz-testcase-minimized-oplist_fuzzer-6043548602728448 @@ -0,0 +1,8 @@ +{JJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJtrings" = ( + "à éèçù", + "æi¥æœ¬èªž"Ä, + "汉è¯/漢語", + "í•œêµì–´/ì¡!?'"; + "Somš Int" = 32434543632; + "Some String with Unicode entity" = "Yea—ßœ—eck this: \U1234 !!!"; +} diff --git a/fuzz/oplist-leaks/clusterfuzz-testcase-minimized-oplist_fuzzer-6497436988473344 b/fuzz/oplist-leaks/clusterfuzz-testcase-minimized-oplist_fuzzer-6497436988473344 new file mode 100644 index 0000000..9d68933 --- /dev/null +++ b/fuzz/oplist-leaks/clusterfuzz-testcase-minimized-oplist_fuzzer-6497436988473344 @@ -0,0 +1 @@ +"
\ No newline at end of file diff --git a/fuzz/oplist.dict b/fuzz/oplist.dict new file mode 100644 index 0000000..1408c4a --- /dev/null +++ b/fuzz/oplist.dict @@ -0,0 +1,51 @@ +# +# AFL dictionary for OpenStep plist format +# ---------------------------------------- + +"0" +",0" +"=0" +"0=" + +"\"\"" +",\"\"" +"=\"\"" +"\"\"=" + +"=" +";" + +"{}" +",{}" +"={}" +"{\"\"=0}" +"{{}}" + +"()" +",()" +"=()" +"(0)" +"(())" + +"''" +"\\" +"\\b" +"\\f" +"\\n" +"\\r" +"\\t" +"\\U0000" +"\\a" +"\\b" +"\\f" +"\\n" +"\\r" +"\\t" +"\\v" +"\\0" +"\\uD800\\uDC00" +"\\uDBFF\\uDFFF" + +"\"\"=0" +"//" +"/**/" diff --git a/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 29b1fce..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_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_t plist_new_uint(uint64_t val); + 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 + */ + PLIST_API plist_err_t plist_from_json(const char *json, uint32_t length, plist_t * plist); + + /** + * Import the #plist_t structure from OpenStep plist format. + * + * @param openstep a pointer to the OpenStep plist buffer. * @param length length of the buffer to read. * @param plist a pointer to the imported plist. + * @return PLIST_ERR_SUCCESS on success or a #plist_err_t on failure + */ + PLIST_API plist_err_t plist_from_openstep(const char *openstep, uint32_t length, plist_t * plist); + + /** + * Import the #plist_t structure from memory data. + * + * This function will look at the first bytes of plist_data + * to determine if plist_data contains a binary, JSON, OpenStep, or XML plist + * and tries to parse the data in the appropriate format. + * @note This is just a convenience function and the format detection is + * very basic. It checks with plist_is_binary() if the data supposedly + * contains binary plist data, if not it checks if the first bytes have + * either '{' or '[' and assumes JSON format, and XML tags will result + * in parsing as XML, otherwise it will try to parse as OpenStep. + * + * @param plist_data A pointer to the memory buffer containing plist data. + * @param length Length of the buffer to read. + * @param plist A pointer to the imported plist. + * @param format If non-NULL, the #plist_format_t value pointed to will be set to the parsed format. + * @return PLIST_ERR_SUCCESS on success or a #plist_err_t on failure + */ + PLIST_API plist_err_t plist_from_memory(const char *plist_data, uint32_t length, plist_t *plist, plist_format_t *format); + + /** + * Import the #plist_t structure directly from file. + * + * This function will look at the first bytes of the file data + * to determine if it contains a binary, JSON, OpenStep, or XML plist + * and tries to parse the data in the appropriate format. + * Uses plist_from_memory() internally. + * + * @param filename The name of the file to parse. + * @param plist A pointer to the imported plist. + * @param format If non-NULL, the #plist_format_t value pointed to will be set to the parsed format. + * @return PLIST_ERR_SUCCESS on success or a #plist_err_t on failure + */ + PLIST_API 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. */ - void plist_from_memory(const char *plist_data, uint32_t length, plist_t * plist); + PLIST_API void plist_print(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. + * 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,21 +953,274 @@ 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 or 0 if FALSE. + */ + PLIST_API int plist_bool_val_is_true(plist_t boolnode); + + /** + * Helper function to test if a given #PLIST_INT node's value is negative + * + * @param intnode node of type PLIST_INT + * @return 1 if the node's value is negative, or 0 if positive. + */ + PLIST_API int plist_int_val_is_negative(plist_t intnode); + + /** + * Helper function to compare the value of a PLIST_INT node against + * a given signed integer value. + * + * @param uintnode node of type PLIST_INT + * @param cmpval value to compare against + * @return 0 if the node's value and cmpval are equal, + * 1 if the node's value is greater than cmpval, + * or -1 if the node's value is less than cmpval. + */ + PLIST_API int plist_int_val_compare(plist_t uintnode, int64_t cmpval); + + /** + * Helper function to compare the value of a PLIST_INT node against + * a given unsigned integer value. + * + * @param uintnode node of type PLIST_INT + * @param cmpval value to compare against + * @return 0 if the node's value and cmpval are equal, + * 1 if the node's value is greater than cmpval, + * or -1 if the node's value is less than cmpval. + */ + PLIST_API int plist_uint_val_compare(plist_t uintnode, uint64_t cmpval); + + /** + * Helper function to compare the value of a PLIST_UID node against + * a given value. + * + * @param uidnode node of type PLIST_UID + * @param cmpval value to compare against + * @return 0 if the node's value and cmpval are equal, + * 1 if the node's value is greater than cmpval, + * or -1 if the node's value is less than cmpval. + */ + PLIST_API int plist_uid_val_compare(plist_t uidnode, uint64_t cmpval); + + /** + * Helper function to compare the value of a PLIST_REAL node against + * a given value. + * + * @note WARNING: Comparing floating point values can give inaccurate + * results because of the nature of floating point values on computer + * systems. While this function is designed to be as accurate as + * possible, please don't rely on it too much. + * + * @param realnode node of type PLIST_REAL + * @param cmpval value to compare against + * @return 0 if the node's value and cmpval are (almost) equal, + * 1 if the node's value is greater than cmpval, + * or -1 if the node's value is less than cmpval. + */ + PLIST_API int plist_real_val_compare(plist_t realnode, double cmpval); + + /** + * Helper function to compare the value of a PLIST_DATE node against + * a given set of seconds and fraction of a second since epoch. + * + * @param datenode node of type PLIST_DATE + * @param cmpsec number of seconds since epoch to compare against + * @param cmpusec fraction of a second in microseconds to compare against + * @return 0 if the node's date is equal to the supplied values, + * 1 if the node's date is greater than the supplied values, + * or -1 if the node's date is less than the supplied values. + */ + PLIST_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 + * a given value. + * This function basically behaves like strcmp. + * + * @param strnode node of type PLIST_STRING + * @param cmpval value to compare against + * @return 0 if the node's value and cmpval are equal, + * > 0 if the node's value is lexicographically greater than cmpval, + * or < 0 if the node's value is lexicographically less than cmpval. + */ + PLIST_API int plist_string_val_compare(plist_t strnode, const char* cmpval); + + /** + * Helper function to compare the value of a PLIST_STRING node against + * a given value, while not comparing more than n characters. + * This function basically behaves like strncmp. + * + * @param strnode node of type PLIST_STRING + * @param cmpval value to compare against + * @param n maximum number of characters to compare + * @return 0 if the node's value and cmpval are equal, + * > 0 if the node's value is lexicographically greater than cmpval, + * or < 0 if the node's value is lexicographically less than cmpval. + */ + PLIST_API int plist_string_val_compare_with_size(plist_t strnode, const char* cmpval, size_t n); + + /** + * Helper function to match a given substring in the value of a + * PLIST_STRING node. + * + * @param strnode node of type PLIST_STRING + * @param substr value to match + * @return 1 if the node's value contains the given substring, + * or 0 if not. + */ + PLIST_API int plist_string_val_contains(plist_t strnode, const char* substr); + + /** + * Helper function to compare the value of a PLIST_KEY node against + * a given value. + * This function basically behaves like strcmp. + * + * @param keynode node of type PLIST_KEY + * @param cmpval value to compare against + * @return 0 if the node's value and cmpval are equal, + * > 0 if the node's value is lexicographically greater than cmpval, + * or < 0 if the node's value is lexicographically less than cmpval. + */ + PLIST_API int plist_key_val_compare(plist_t keynode, const char* cmpval); + + /** + * Helper function to compare the value of a PLIST_KEY node against + * a given value, while not comparing more than n characters. + * This function basically behaves like strncmp. + * + * @param keynode node of type PLIST_KEY + * @param cmpval value to compare against + * @param n maximum number of characters to compare + * @return 0 if the node's value and cmpval are equal, + * > 0 if the node's value is lexicographically greater than cmpval, + * or < 0 if the node's value is lexicographically less than cmpval. + */ + PLIST_API int plist_key_val_compare_with_size(plist_t keynode, const char* cmpval, size_t n); + + /** + * Helper function to match a given substring in the value of a + * PLIST_KEY node. + * + * @param keynode node of type PLIST_KEY + * @param substr value to match + * @return 1 if the node's value contains the given substring, + * or 0 if not. + */ + PLIST_API int plist_key_val_contains(plist_t keynode, const char* substr); + + /** + * Helper function to compare the data of a PLIST_DATA node against + * a given blob and size. + * This function basically behaves like memcmp after making sure the + * size of the node's data value is equal to the size of cmpval (n), + * making this a "full match" comparison. + * + * @param datanode node of type PLIST_DATA + * @param cmpval data blob to compare against + * @param n size of data blob passed in cmpval + * @return 0 if the node's data blob and cmpval are equal, + * > 0 if the node's value is lexicographically greater than cmpval, + * or < 0 if the node's value is lexicographically less than cmpval. + */ + PLIST_API int plist_data_val_compare(plist_t datanode, const uint8_t* cmpval, size_t n); + + /** + * Helper function to compare the data of a PLIST_DATA node against + * a given blob and size, while no more than n bytes are compared. + * This function basically behaves like memcmp after making sure the + * size of the node's data value is at least n, making this a + * "starts with" comparison. + * + * @param datanode node of type PLIST_DATA + * @param cmpval data blob to compare against + * @param n size of data blob passed in cmpval + * @return 0 if the node's value and cmpval are equal, + * > 0 if the node's value is lexicographically greater than cmpval, + * or < 0 if the node's value is lexicographically less than cmpval. + */ + PLIST_API int plist_data_val_compare_with_size(plist_t datanode, const uint8_t* cmpval, size_t n); + + /** + * Helper function to match a given data blob within the value of a + * PLIST_DATA node. + * + * @param datanode node of type PLIST_KEY + * @param cmpval data blob to match + * @param n size of data blob passed in cmpval + * @return 1 if the node's value contains the given data blob + * or 0 if not. + */ + PLIST_API int plist_data_val_contains(plist_t datanode, const uint8_t* cmpval, size_t n); + + /** + * Sort all PLIST_DICT key/value pairs in a property list lexicographically + * by key. Recurses into the child nodes if necessary. + * + * @param plist The property list to perform the sorting operation on. + */ + PLIST_API void plist_sort(plist_t plist); + + /** + * Free memory allocated by relevant libplist API calls: + * - plist_to_xml() + * - plist_to_bin() + * - plist_get_key_val() + * - plist_get_string_val() + * - plist_get_data_val() + * + * @param ptr pointer to the memory to free + * + * @note Do not use this function to free plist_t nodes, use plist_free() + * instead. + */ + PLIST_API void plist_mem_free(void* ptr); + + /** + * Set debug level for the format parsers. + * @note This function does nothing if libplist was not configured with --enable-debug . + * + * @param debug Debug level. Currently, only 0 (off) and 1 (enabled) are supported. + */ + PLIST_API void plist_set_debug(int debug); + + /** + * Returns a static string of the libplist version. + * + * @return The libplist version as static ascii string + */ + PLIST_API const char* libplist_version(); /*@}*/ 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 f0b8f0e..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; @@ -201,7 +203,7 @@ struct bplist_data { uint8_t offset_size; const char* offset_table; uint32_t level; - plist_t used_indexes; + ptrarray_t* used_indexes; }; #ifdef DEBUG @@ -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,26 +751,26 @@ 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; } /* store node_index for current recursion level */ - if (plist_array_get_size(bplist->used_indexes) < bplist->level+1) { - while (plist_array_get_size(bplist->used_indexes) < bplist->level+1) { - plist_array_append_item(bplist->used_indexes, plist_new_uint(node_index)); + if ((uint32_t)ptr_array_size(bplist->used_indexes) < bplist->level+1) { + while ((uint32_t)ptr_array_size(bplist->used_indexes) < bplist->level+1) { + ptr_array_add(bplist->used_indexes, (void*)(uintptr_t)node_index); } } else { - plist_array_set_item(bplist->used_indexes, plist_new_uint(node_index), bplist->level); + ptr_array_set(bplist->used_indexes, (void*)(uintptr_t)node_index, bplist->level); } /* recursion check */ if (bplist->level > 0) { for (i = bplist->level-1; i >= 0; i--) { - plist_t node_i = plist_array_get_item(bplist->used_indexes, i); - plist_t node_level = plist_array_get_item(bplist->used_indexes, bplist->level); - if (plist_compare_node_value(node_i, node_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; @@ -850,16 +875,22 @@ PLIST_API void plist_from_bin(const char *plist_bin, uint32_t length, plist_t * bplist.offset_size = offset_size; bplist.offset_table = offset_table; bplist.level = 0; - bplist.used_indexes = plist_new_array(); + bplist.used_indexes = ptr_array_new(16); 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); - plist_free(bplist.used_indexes); + 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; @@ -1170,25 +1206,32 @@ PLIST_API void plist_to_bin(plist_t plist, char **plist_bin, uint32_t * length) uint64_t offset_table_index = 0; bytearray_t *bplist_buff = NULL; uint64_t i = 0; - uint8_t *buff = NULL; + uint64_t buff_len = 0; uint64_t *offsets = NULL; bplist_trailer_t trailer; uint64_t objects_len = 0; - uint64_t buff_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 @@ -1202,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; @@ -1282,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); @@ -1298,14 +1347,17 @@ PLIST_API void plist_to_bin(plist_t plist, char **plist_bin, uint32_t * length) switch (data->type) { - case PLIST_BOOLEAN: - buff = (uint8_t *) malloc(sizeof(uint8_t)); - buff[0] = data->boolval ? BPLIST_TRUE : BPLIST_FALSE; - byte_array_append(bplist_buff, buff, sizeof(uint8_t)); - free(buff); + case PLIST_NULL: { + uint8_t b = 0; + byte_array_append(bplist_buff, &b, 1); break; - - case PLIST_UINT: + } + case PLIST_BOOLEAN: { + uint8_t b = data->boolval ? BPLIST_TRUE : BPLIST_FALSE; + byte_array_append(bplist_buff, &b, 1); + break; + } + case PLIST_INT: if (data->length == 16) { write_uint(bplist_buff, data->intval); } else { @@ -1332,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); @@ -1373,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 87be488..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. * @@ -21,7 +21,11 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif +#define _GNU_SOURCE 1 #include <string.h> #include "plist.h" #include <stdlib.h> @@ -29,6 +33,8 @@ #include <math.h> #include <assert.h> #include <limits.h> +#include <float.h> +#include <ctype.h> #ifdef WIN32 #include <windows.h> @@ -37,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; @@ -67,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); @@ -78,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) { @@ -93,26 +133,61 @@ BOOL WINAPI DllMain(HINSTANCE hModule, DWORD dwReason, LPVOID lpReserved) } return 1; } - #else +#warning No compiler support for constructor/destructor attributes, some features might not be available. +#endif -static pthread_once_t init_once = PTHREAD_ONCE_INIT; -static pthread_once_t deinit_once = PTHREAD_ONCE_INIT; +#ifndef HAVE_MEMMEM +// see https://sourceware.org/legacy-ml/libc-alpha/2007-12/msg00000.html -static void __attribute__((constructor)) libplist_initialize(void) -{ - pthread_once(&init_once, internal_plist_init); -} +#ifndef _LIBC +# define __builtin_expect(expr, val) (expr) +#endif -static void __attribute__((destructor)) libplist_deinitialize(void) +#undef memmem + +/* Return the first occurrence of NEEDLE in HAYSTACK. */ +void* memmem(const void* haystack, size_t haystack_len, const void* needle, size_t needle_len) { - pthread_once(&deinit_once, internal_plist_deinit); -} + /* not really Rabin-Karp, just using additive hashing */ + char* haystack_ = (char*)haystack; + char* needle_ = (char*)needle; + int hash = 0; /* this is the static hash value of the needle */ + int hay_hash = 0; /* rolling hash over the haystack */ + char* last; + size_t i; -#endif + if (haystack_len < needle_len) + return NULL; + if (!needle_len) + return haystack_; -PLIST_API int plist_is_binary(const char *plist_data, uint32_t length) + /* initialize hashes */ + for (i = needle_len; i; --i) { + hash += *needle_++; + hay_hash += *haystack_++; + } + + /* iterate over the haystack */ + haystack_ = (char*)haystack; + needle_ = (char*)needle; + last = haystack_+(haystack_len - needle_len + 1); + for (; haystack_ < last; ++haystack_) { + if (__builtin_expect(hash == hay_hash, 0) + && *haystack_ == *needle_ /* prevent calling memcmp, was a optimization from existing glibc */ + && !memcmp (haystack_, needle_, needle_len)) { + return haystack_; + } + /* roll the hash */ + hay_hash -= *haystack_; + hay_hash += *(haystack_+needle_len); + } + return NULL; +} +#endif + +int plist_is_binary(const char *plist_data, uint32_t length) { if (length < 8) { return 0; @@ -121,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) @@ -141,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) @@ -193,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; @@ -205,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); @@ -213,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; } @@ -225,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; @@ -249,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; @@ -258,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; @@ -267,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_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_UINT; + 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; @@ -285,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; @@ -294,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; @@ -304,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; @@ -313,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; @@ -341,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) { @@ -362,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: @@ -378,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: @@ -389,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 plist_copy_node(node); + 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); } @@ -526,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) { @@ -553,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) { @@ -602,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; @@ -640,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; } } @@ -658,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; @@ -746,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; @@ -773,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; @@ -801,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; @@ -824,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) { @@ -840,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; @@ -854,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; @@ -868,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; @@ -881,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; @@ -893,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; @@ -917,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; @@ -929,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; @@ -939,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; @@ -951,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; @@ -965,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) @@ -976,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); @@ -988,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); } @@ -1063,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; @@ -1086,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); @@ -1096,39 +1302,448 @@ 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_UINT, &val, sizeof(uint64_t)); + plist_set_element_val(node, PLIST_INT, &val, (val > INT64_MAX) ? sizeof(uint64_t)*2 : sizeof(uint64_t)); } -PLIST_API void plist_set_uid_val(plist_t node, uint64_t val) +void plist_set_int_val(plist_t node, int64_t val) +{ + plist_set_element_val(node, PLIST_INT, &val, sizeof(uint64_t)); +} + +void plist_set_uid_val(plist_t node, uint64_t val) { plist_set_element_val(node, PLIST_UID, &val, sizeof(uint64_t)); } -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)); } +int plist_bool_val_is_true(plist_t boolnode) +{ + if (!PLIST_IS_BOOLEAN(boolnode)) { + return 0; + } + uint8_t bv = 0; + plist_get_bool_val(boolnode, &bv); + return (bv == 1); +} + +int plist_int_val_is_negative(plist_t intnode) +{ + if (!PLIST_IS_INT(intnode)) { + return 0; + } + plist_data_t data = plist_get_data(intnode); + if (data->length == 16) { + return 0; + } + if ((int64_t)data->intval < 0) { + return 1; + } + return 0; +} + +int plist_int_val_compare(plist_t uintnode, int64_t cmpval) +{ + if (!PLIST_IS_INT(uintnode)) { + return -1; + } + int64_t uintval = 0; + plist_get_int_val(uintnode, &uintval); + if (uintval == cmpval) { + return 0; + } + + if (uintval < cmpval) { + return -1; + } + + return 1; +} + +int plist_uint_val_compare(plist_t uintnode, uint64_t cmpval) +{ + if (!PLIST_IS_INT(uintnode)) { + return -1; + } + uint64_t uintval = 0; + plist_get_uint_val(uintnode, &uintval); + if (uintval == cmpval) { + return 0; + } + + if (uintval < cmpval) { + return -1; + } + + return 1; +} + +int plist_uid_val_compare(plist_t uidnode, uint64_t cmpval) +{ + if (!PLIST_IS_UID(uidnode)) { + return -1; + } + uint64_t uidval = 0; + plist_get_uid_val(uidnode, &uidval); + if (uidval == cmpval) { + return 0; + } + + if (uidval < cmpval) { + return -1; + } + + return 1; +} + +int plist_real_val_compare(plist_t realnode, double cmpval) +{ + if (!PLIST_IS_REAL(realnode)) { + return -1; + } + double a = 0; + double b = cmpval; + plist_get_real_val(realnode, &a); + double abs_a = fabs(a); + double abs_b = fabs(b); + double diff = fabs(a - b); + if (a == b) { + return 0; + } + + if (a == 0 || b == 0 || (abs_a + abs_b < DBL_MIN)) { + if (diff < (DBL_EPSILON * DBL_MIN)) { + return 0; + } + + if (a < b) { + return -1; + } + } else { + if ((diff / fmin(abs_a + abs_b, DBL_MAX)) < DBL_EPSILON) { + return 0; + } + + if (a < b) { + return -1; + } + } + return 1; +} + +int plist_date_val_compare(plist_t datenode, int32_t cmpsec, int32_t cmpusec) +{ + if (!PLIST_IS_DATE(datenode)) { + return -1; + } + int32_t sec = 0; + int32_t usec = 0; + plist_get_date_val(datenode, &sec, &usec); + uint64_t dateval = ((int64_t)sec << 32) | usec; + uint64_t cmpval = ((int64_t)cmpsec << 32) | cmpusec; + if (dateval == cmpval) { + return 0; + } + + if (dateval < cmpval) { + return -1; + } + + return 1; +} + +int plist_string_val_compare(plist_t strnode, const char* cmpval) +{ + if (!PLIST_IS_STRING(strnode)) { + return -1; + } + plist_data_t data = plist_get_data(strnode); + return strcmp(data->strval, cmpval); +} + +int plist_string_val_compare_with_size(plist_t strnode, const char* cmpval, size_t n) +{ + if (!PLIST_IS_STRING(strnode)) { + return -1; + } + plist_data_t data = plist_get_data(strnode); + return strncmp(data->strval, cmpval, n); +} + +int plist_string_val_contains(plist_t strnode, const char* substr) +{ + if (!PLIST_IS_STRING(strnode)) { + return 0; + } + plist_data_t data = plist_get_data(strnode); + return (strstr(data->strval, substr) != NULL); +} + +int plist_key_val_compare(plist_t keynode, const char* cmpval) +{ + if (!PLIST_IS_KEY(keynode)) { + return -1; + } + plist_data_t data = plist_get_data(keynode); + return strcmp(data->strval, cmpval); +} + +int plist_key_val_compare_with_size(plist_t keynode, const char* cmpval, size_t n) +{ + if (!PLIST_IS_KEY(keynode)) { + return -1; + } + plist_data_t data = plist_get_data(keynode); + return strncmp(data->strval, cmpval, n); +} + +int plist_key_val_contains(plist_t keynode, const char* substr) +{ + if (!PLIST_IS_KEY(keynode)) { + return 0; + } + plist_data_t data = plist_get_data(keynode); + return (strstr(data->strval, substr) != NULL); +} + +int plist_data_val_compare(plist_t datanode, const uint8_t* cmpval, size_t n) +{ + if (!PLIST_IS_DATA(datanode)) { + return -1; + } + plist_data_t data = plist_get_data(datanode); + if (data->length < n) { + return -1; + } + + if (data->length > n) { + return 1; + } + + return memcmp(data->buff, cmpval, n); +} + +int plist_data_val_compare_with_size(plist_t datanode, const uint8_t* cmpval, size_t n) +{ + if (!PLIST_IS_DATA(datanode)) { + return -1; + } + plist_data_t data = plist_get_data(datanode); + if (data->length < n) { + return -1; + } + return memcmp(data->buff, cmpval, n); +} + +int plist_data_val_contains(plist_t datanode, const uint8_t* cmpval, size_t n) +{ + if (!PLIST_IS_DATA(datanode)) { + return -1; + } + plist_data_t data = plist_get_data(datanode); + return (memmem(data->buff, data->length, cmpval, n) != NULL); +} + +extern void plist_xml_set_debug(int debug); +extern void plist_bin_set_debug(int debug); +extern void plist_json_set_debug(int debug); +extern void plist_ostep_set_debug(int debug); + +void plist_set_debug(int debug) +{ + 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 bcffb77..3a11031 100644 --- a/src/ptrarray.c +++ b/src/ptrarray.c @@ -42,10 +42,10 @@ void ptr_array_free(ptrarray_t *pa) void ptr_array_insert(ptrarray_t *pa, void *data, long array_index) { - if (!pa || !pa->pdata || !data) return; + 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) { @@ -89,3 +89,8 @@ void* ptr_array_index(ptrarray_t *pa, long array_index) } return pa->pdata[array_index]; } + +long ptr_array_size(ptrarray_t *pa) +{ + return pa->len; +} diff --git a/src/ptrarray.h b/src/ptrarray.h index 2c6136a..ed67351 100644 --- a/src/ptrarray.h +++ b/src/ptrarray.h @@ -36,4 +36,5 @@ void ptr_array_insert(ptrarray_t *pa, void *data, long index); void ptr_array_remove(ptrarray_t *pa, long index); void ptr_array_set(ptrarray_t *pa, void *data, long index); void* ptr_array_index(ptrarray_t *pa, long index); +long ptr_array_size(ptrarray_t *pa); #endif diff --git a/src/strbuf.h b/src/strbuf.h 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 8c08caf..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)]); @@ -346,11 +335,11 @@ static Year cycle_offset(Year year) */ static int safe_year(const Year year) { - int safe_year= (int)year; + int _safe_year = (int)year; Year year_cycle; if( year >= MIN_SAFE_YEAR && year <= MAX_SAFE_YEAR ) { - return safe_year; + return _safe_year; } year_cycle = year + cycle_offset(year); @@ -368,24 +357,24 @@ 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 ); assert( year_cycle < SOLAR_CYCLE_LENGTH ); if( year < MIN_SAFE_YEAR ) - safe_year = safe_years_low[year_cycle]; + _safe_year = safe_years_low[year_cycle]; else if( year > MAX_SAFE_YEAR ) - safe_year = safe_years_high[year_cycle]; + _safe_year = safe_years_high[year_cycle]; else assert(0); TIME64_TRACE3("# year: %lld, year_cycle: %lld, safe_year: %d\n", - year, year_cycle, safe_year); + year, year_cycle, _safe_year); - assert(safe_year <= MAX_SAFE_YEAR && safe_year >= MIN_SAFE_YEAR); + assert(_safe_year <= MAX_SAFE_YEAR && _safe_year >= MIN_SAFE_YEAR); - return safe_year; + return _safe_year; } @@ -519,17 +508,17 @@ static Time64_T seconds_between_years(Year left_year, Year right_year) { Time64_T mktime64(struct TM *input_date) { struct tm safe_date; struct TM date; - Time64_T time; + Time64_T timev; Year year = input_date->tm_year + 1900; if( date_in_safe_range(input_date, &SYSTEM_MKTIME_MIN, &SYSTEM_MKTIME_MAX) ) { copy_TM64_to_tm(input_date, &safe_date); - time = (Time64_T)mktime(&safe_date); + timev = (Time64_T)mktime(&safe_date); /* Correct the possibly out of bound input date */ copy_tm_to_TM64(&safe_date, input_date); - return time; + return timev; } /* Have to make the year safe in date else it won't fit in safe_date */ @@ -537,14 +526,14 @@ Time64_T mktime64(struct TM *input_date) { date.tm_year = safe_year(year) - 1900; copy_TM64_to_tm(&date, &safe_date); - time = (Time64_T)mktime(&safe_date); + timev = (Time64_T)mktime(&safe_date); /* Correct the user's possibly out of bound input date */ copy_tm_to_TM64(&safe_date, input_date); - time += seconds_between_years(year, (Year)(safe_date.tm_year + 1900)); + timev += seconds_between_years(year, (Year)(safe_date.tm_year) + 1900); - return time; + return timev; } @@ -560,7 +549,7 @@ struct TM *gmtime64_r (const Time64_T *in_time, struct TM *p) Time64_T v_tm_tday; int leap; Time64_T m; - Time64_T time = *in_time; + Time64_T timev = *in_time; Year year = 70; int cycles = 0; @@ -587,13 +576,13 @@ struct TM *gmtime64_r (const Time64_T *in_time, struct TM *p) p->tm_zone = (char*)"UTC"; #endif - v_tm_sec = (int)(time % 60); - time /= 60; - v_tm_min = (int)(time % 60); - time /= 60; - v_tm_hour = (int)(time % 24); - time /= 24; - v_tm_tday = time; + v_tm_sec = (int)(timev % 60); + timev /= 60; + v_tm_min = (int)(timev % 60); + timev /= 60; + v_tm_hour = (int)(timev % 24); + timev /= 24; + v_tm_tday = timev; WRAP (v_tm_sec, v_tm_min, 60); WRAP (v_tm_min, v_tm_hour, 60); @@ -674,14 +663,14 @@ 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; } -struct TM *localtime64_r (const Time64_T *time, struct TM *local_tm) +struct TM *localtime64_r (const Time64_T *timev, struct TM *local_tm) { time_t safe_time; struct tm safe_date; @@ -692,10 +681,10 @@ struct TM *localtime64_r (const Time64_T *time, struct TM *local_tm) assert(local_tm != NULL); /* Use the system localtime() if time_t is small enough */ - if( SHOULD_USE_SYSTEM_LOCALTIME(*time) ) { - safe_time = (time_t)*time; + if( SHOULD_USE_SYSTEM_LOCALTIME(*timev) ) { + safe_time = (time_t)*timev; - TIME64_TRACE1("Using system localtime for %lld\n", *time); + TIME64_TRACE1("Using system localtime for %lld\n", *timev); LOCALTIME_R(&safe_time, &safe_date); @@ -705,8 +694,8 @@ struct TM *localtime64_r (const Time64_T *time, struct TM *local_tm) return local_tm; } - if( gmtime64_r(time, &gm_tm) == NULL ) { - TIME64_TRACE1("gmtime64_r returned null for %lld\n", *time); + if( gmtime64_r(timev, &gm_tm) == NULL ) { + TIME64_TRACE1("gmtime64_r returned null for %lld\n", *timev); return NULL; } @@ -717,7 +706,7 @@ struct TM *localtime64_r (const Time64_T *time, 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 *time, 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 *time, 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 *time, 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; } @@ -803,10 +792,12 @@ char *asctime64_r( const struct TM* date, char *result ) { } -char *ctime64_r( const Time64_T* time, char* result ) { +char *ctime64_r( const Time64_T* timev, char* result ) { struct TM date; - localtime64_r( time, &date ); + if (!localtime64_r( timev, &date )) + return NULL; + return asctime64_r( &date, result ); } 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 Binary files differnew file mode 100644 index 0000000..955993f --- /dev/null +++ b/test/data/data.bplist 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 Binary files differnew file mode 100644 index 0000000..e1fc6f8 --- /dev/null +++ b/test/data/uid.bplist 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 dc752df..52f7080 100755 --- a/test/signedunsigned1.test +++ b/test/signedunsigned1.test @@ -13,11 +13,11 @@ 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 $DATACMP0 $DATAOUT0 -diff $DATACMP1 $DATAOUT1 +diff --strip-trailing-cr $DATACMP0 $DATAOUT0 +diff --strip-trailing-cr $DATACMP1 $DATAOUT1 diff --git a/test/signedunsigned2.test b/test/signedunsigned2.test index d51219f..192f1a6 100755 --- a/test/signedunsigned2.test +++ b/test/signedunsigned2.test @@ -13,11 +13,11 @@ 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 $DATACMP0 $DATAOUT0 -diff $DATACMP1 $DATAOUT1 +diff --strip-trailing-cr $DATACMP0 $DATAOUT0 +diff --strip-trailing-cr $DATACMP1 $DATAOUT1 diff --git a/test/signedunsigned3.test b/test/signedunsigned3.test index 9bada3e..87bdcbc 100755 --- a/test/signedunsigned3.test +++ b/test/signedunsigned3.test @@ -13,11 +13,11 @@ 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 $DATACMP0 $DATAOUT0 -diff $DATACMP1 $DATAOUT1 +diff --strip-trailing-cr $DATACMP0 $DATAOUT0 +diff --strip-trailing-cr $DATACMP1 $DATAOUT1 diff --git a/test/small++.test b/test/small++.test new file mode 100755 index 0000000..2ceace7 --- /dev/null +++ b/test/small++.test @@ -0,0 +1,15 @@ +## -*- sh -*- + +DATASRC=$top_srcdir/test/data +DATAOUT=$top_builddir/test/data +TESTFILE=2.plist + +if ! test -d "$DATAOUT"; then + mkdir -p $DATAOUT +fi + +echo "Converting" +$top_builddir/test/plist_test++ $DATASRC/$TESTFILE $DATAOUT/small++.test.out + +echo "Comparing" +$top_builddir/test/plist_cmp $DATASRC/$TESTFILE $DATAOUT/small++.test.out diff --git a/test/small.test b/test/small.test 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 a1a8c9c..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" @@ -28,6 +32,7 @@ #include <string.h> #include <sys/stat.h> #include <errno.h> +#include <unistd.h> #ifdef _MSC_VER #pragma warning(disable:4996) @@ -36,27 +41,48 @@ typedef struct _options { char *in_file, *out_file; - uint8_t debug, in_fmt, out_fmt; + 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(" -i, --infile FILE\tThe FILE to convert from\n"); - printf(" -o, --outfile FILE\tOptional FILE to convert to or stdout if not used\n"); - printf(" -d, --debug\t\tEnable extended debug output\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 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++) { @@ -71,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) { @@ -83,23 +108,77 @@ static options_t *parse_arguments(int argc, char *argv[]) i++; continue; } - - if (!strcmp(argv[i], "--debug") || !strcmp(argv[i], "-d")) + else if (!strcmp(argv[i], "--format") || !strcmp(argv[i], "-f")) { - options->debug = 1; + if ((i + 1) == argc) + { + free(options); + return NULL; + } + if (!strncmp(argv[i+1], "bin", 3)) { + options->out_fmt = PLIST_FORMAT_BINARY; + } else if (!strncmp(argv[i+1], "xml", 3)) { + 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 { + fprintf(stderr, "ERROR: Unsupported output format\n"); + free(options); + return NULL; + } + i++; + continue; } - - if (!strcmp(argv[i], "--help") || !strcmp(argv[i], "-h")) + else if (!strcmp(argv[i], "--compact") || !strcmp(argv[i], "-c")) + { + options->flags |= OPT_COMPACT; + } + 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; } - } - - if (!options->in_file) - { - 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; @@ -107,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); @@ -122,49 +205,138 @@ int main(int argc, char *argv[]) return 0; } - // 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)); - free(options); - return 1; + if (options->flags & OPT_DEBUG) + { + plist_set_debug(1); } - memset(&filestats, '\0', sizeof(struct stat)); - fstat(fileno(iplist), &filestats); + if (!options->in_file || !strcmp(options->in_file, "-")) + { + read_size = 0; + plist_entire = malloc(sizeof(char) * read_capacity); + if(plist_entire == NULL) + { + fprintf(stderr, "ERROR: Failed to allocate buffer to read from stdin"); + free(options); + return 1; + } + plist_entire[read_size] = '\0'; + char ch; + while(read(STDIN_FILENO, &ch, 1) > 0) + { + if (read_size >= read_capacity) { + char *old = plist_entire; + read_capacity += 4096; + plist_entire = realloc(plist_entire, sizeof(char) * read_capacity); + if (plist_entire == NULL) + { + fprintf(stderr, "ERROR: Failed to reallocate stdin buffer\n"); + free(old); + free(options); + return 1; + } + } + 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'; - if (filestats.st_size < 8) { - printf("ERROR: Input file is too small to contain valid plist data.\n"); - free(options); - fclose(iplist); - return -1; + // Not positive we need this, but it doesnt seem to hurt lol + if(ferror(stdin)) + { + fprintf(stderr, "ERROR: reading from stdin.\n"); + free(plist_entire); + free(options); + return 1; + } } + else + { + // read input file + iplist = fopen(options->in_file, "rb"); + if (!iplist) { + fprintf(stderr, "ERROR: Could not open input file '%s': %s\n", options->in_file, strerror(errno)); + free(options); + return 1; + } - plist_entire = (char *) malloc(sizeof(char) * (filestats.st_size + 1)); - read_size = fread(plist_entire, sizeof(char), filestats.st_size, iplist); - fclose(iplist); + memset(&filestats, '\0', sizeof(struct stat)); + fstat(fileno(iplist), &filestats); - // 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); + 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 + if (plist_is_binary(plist_entire, read_size)) + { + input_res = plist_from_bin(plist_entire, read_size, &root_node); + if (input_res == PLIST_ERR_SUCCESS) { + if (options->flags & OPT_SORT) { + plist_sort(root_node); + } + output_res = plist_to_xml(root_node, &plist_out, &size); + } + } + else + { + input_res = plist_from_xml(plist_entire, read_size, &root_node); + if (input_res == PLIST_ERR_SUCCESS) { + if (options->flags & OPT_SORT) { + plist_sort(root_node); + } + output_res = plist_to_bin(root_node, &plist_out, &size); + } + } } else { - plist_from_xml(plist_entire, read_size, &root_node); - plist_to_bin(root_node, &plist_out, &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); + } + 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; + } + } } plist_free(root_node); free(plist_entire); if (plist_out) { - if (options->out_file != NULL) + 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; } @@ -177,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; } |