66 Commits

Author SHA1 Message Date
Eric Hawicz
6bfab90c87 Issue #867: disallow control characters in strict mode. 2024-09-15 10:37:45 -04:00
Eric Hawicz
ecb7a4c9a3 Merge pull request #864 from pavelzw/patch-1
Build pkg-config for msvc as well
2024-08-29 21:08:52 -04:00
Pavel Zwerschke
906d1581de Update CMakeLists.txt 2024-08-29 10:55:02 +02:00
Pavel Zwerschke
9b8d170d1f Build pkg-config for msvc as well 2024-06-18 00:11:13 +02:00
Eric Hawicz
6e481aa061 Merge pull request #861 from bhaible/handle-another-oom-condition
Handle yet another out-of-memory condition.
2024-05-05 09:49:26 -04:00
Eric Haszlakiewicz
781675758f Although CMAKE_C_COMPILER_FRONTEND_VARIANT is defined, it's not always set to some value. Fix builds for cases when it's blank. 2024-04-28 17:15:42 +00:00
Bruno Haible
833233faa8 Handle yet another out-of-memory condition.
duplocale() can return NULL, with errno set to ENOMEM.
In this case, bail out and set the current error code to
json_tokener_error_memory.
2024-04-22 01:54:10 +02:00
Eric Hawicz
31a22fb2da Issue #857: fix a few places where json_tokener should have been returning json_tokener_error_memory but wasn't. 2024-04-21 10:37:16 -04:00
Eric Hawicz
0051f2dbe0 Merge pull request #858 from alurm/master
json_tokener.h: json_tokener_parse_verbose: fix typo
2024-04-16 11:23:09 -04:00
Alan Urmancheev
0741ce3918 json_tokener.h: json_tokener_parse_verbose: fix typo 2024-04-16 14:56:00 +00:00
Eric Hawicz
e3464a2ab4 Issue #829: attempt to detect clang-cl.exe and pass MSVC-compatile command line arguments. 2024-03-29 22:40:45 -04:00
Eric Hawicz
61016412fb Issue #850: fill in & adjust the "@returns" docs for several functions. 2024-03-29 18:46:05 -04:00
Eric Hawicz
f2eac07129 doxygen can't handle html on heading, split that to separate lines
https://github.com/doxygen/doxygen/issues/10466
2024-03-29 18:45:22 -04:00
Eric Hawicz
e93ae70417 Fix issue #854: Set error=json_tokener_error_memory in json_tokener_parser_verbose() when allocating the tokener fails. 2024-03-29 18:09:12 -04:00
Eric Hawicz
0bc2dd5e60 Merge pull request #852 from fedefrancescon/docs/micro-typo-fix
docs: micro typo in json_object_put doc
2024-03-02 17:31:24 -05:00
Federico Francescon
9c44c7ed52 docs: micro typo in json_object_get doc 2024-03-02 22:41:10 +01:00
Eric Hawicz
401249cbdf Merge pull request #831 from neheb/1
rename WIN32 to _WIN32
2024-02-08 08:11:57 -05:00
Rosen Penev
66f7869219 rename WIN32 to _WIN32
The latter is the proper macro defined by Windows headers.

Fixes compilation under at least clang-cl which mandates function
declarations.

Signed-off-by: Rosen Penev <rosenp@gmail.com>
2024-02-07 18:06:55 -08:00
Eric Hawicz
dabed80523 Merge pull request #849 from rouault/random_seed_toctou
random_seed.c: add a Coverity Scan suppression
2024-01-08 12:45:39 -05:00
Even Rouault
f6c8eba84e random_seed.c: add a Coverity Scan suppression
Coverity Scan warns about the use of open() after stat() being a
potential TOCTOU (Time of check time of use) issue. But here there is no
such issue.
2024-01-07 20:55:38 +01:00
Eric Hawicz
76f819e51d Merge pull request #846 from evo-i/CMP0153
Use execute_process instead of exec_program
2023-12-17 07:23:07 -05:00
evo
eef983b1df Update CMakeLists.txt
Replace with `execute_process` available on v3.0+
2023-12-17 11:19:20 +03:00
evo
c0cf4bc23b Update CMakeLists.txt
Now saved to variable.
2023-12-17 00:32:21 +03:00
evo
e85c8ada54 Fix ECHO_OUTPUT_VARIABLE 2023-12-17 00:23:51 +03:00
evo
2c84476c77 [CMP0153](https://cmake.org/cmake/help/latest/policy/CMP0153.html) 2023-12-12 16:28:41 +03:00
Eric Haszlakiewicz
407ddb4350 Issue #842 - fix one particular sign conversion warning.
There are many others that show up if we were to add -Wsign-conversions,
but this is the only one using a literal constant.
2023-11-30 13:59:13 +00:00
Eric Hawicz
33337523f5 Merge pull request #839 from ffontaine/master
json_pointer.c: initialize idx
2023-11-08 18:11:13 -05:00
Fabrice Fontaine
7ff7eab603 json_pointer.c: initialize idx
Fix the following build failure with gcc 5:

/home/thomas/autobuild/instance-2/output-1/build/json-c-0.17/json_pointer.c: In function 'json_pointer_result_get_recursive':
/home/thomas/autobuild/instance-2/output-1/build/json-c-0.17/json_pointer.c:193:25: error: 'idx' may be used uninitialized in this function [-Werror=maybe-uninitialized]
    res->index_in_parent = idx;
                         ^

Fixes:
 - http://autobuild.buildroot.org/results/523b35a979d59121fe4e18c38171792b06233940/

Signed-off-by: Fabrice Fontaine <fontaine.fabrice@gmail.com>
2023-11-08 22:42:48 +01:00
Eric Hawicz
88bce2b38e Merge pull request #837 from HansolChoe/fix-tokener-parse-ex-fuzzer
Add type checking for parsed objects in tokener_parse_ex_fuzzer
2023-10-19 07:49:32 -04:00
HansolChoe
a907f7dd1b tokener_parse_ex_fuzzer: add type checking for parsed object 2023-10-19 16:55:15 +09:00
Eric Hawicz
ad8b8afa7d Take 2 fixing the placement of json_tokener_error_memory in the enum. (json_tokener_error_size is an actual error, *not* a measure of the size of the enum!) 2023-09-22 22:26:21 -04:00
Eric Hawicz
4186537557 Merge pull request #827 from robert-pang/master
Resolve build issue in tokener_parse_ex_fuzzer.c
2023-09-11 00:13:45 +02:00
Eric Hawicz
9ff6b68baf Merge pull request #826 from emlix/cmake-policies
CMake: remove needless policy setting
2023-09-11 00:10:55 +02:00
Robert Pang
1a7defaa69 Fix variable and API names in tokener_parse_ex_fuzzer.cc to resolve build issue. 2023-09-05 13:58:51 -07:00
Rolf Eike Beer
c4a615a052 CMake: request all policies up to CMake 3.12 being set to NEW 2023-09-05 14:31:24 +02:00
Rolf Eike Beer
159822dd8a CMake: request consistent minimum version 2023-09-05 14:31:14 +02:00
Rolf Eike Beer
990acb4e19 CMake: remove needless policy setting
These policies were all introduced before CMake 3.9, so they will automatically
be initialized to the new behavior when requesting a minimum version of 3.9.
2023-09-05 12:14:46 +02:00
Eric Haszlakiewicz
502522a93d Update the master branch to version 0.17.99 2023-08-12 19:08:59 +00:00
Eric Hawicz
12ea386989 Add an abi-check.sh script that shows how to run the abi-compliance-checker, update release creation steps slightly. 2023-08-12 14:56:51 -04:00
Eric Haszlakiewicz
6dd8618170 Also fix messages returned from json_tokener_error_desc() (broke due to the ordering change of enum json_tokener_error) 2023-08-12 18:52:32 +00:00
Eric Hawicz
3dad6941da Move the json_tokener_error_memory entry to the end of enum json_tokener_error to restore binary compatibility with 0.16 2023-08-12 13:54:50 -04:00
Eric Hawicz
f7e9d8e216 Update the AUTHORS file and add issues_closed_for_0.17.md for the upcoming 0.17 release 2023-08-12 13:53:27 -04:00
Eric Hawicz
85ed501a3d Update the ChangeLog for 0.17 based on notable commits since 0.16 2023-08-08 17:31:38 -04:00
Eric Hawicz
077661f3d1 Fix issue #823: add back json_number_chars, but only because it's part of the public API. 2023-08-06 15:36:19 -04:00
Eric Hawicz
87127d31c6 Merge pull request #679 from GitMensch/patch-1
Let json-c be used with obsolete compilers
2023-08-05 22:32:37 -04:00
Eric Hawicz
1ee12100b6 PR #679: add workaround for old compilers w/o stdint.h (i.e. VS2008 and earlier) 2023-08-05 22:11:30 -04:00
Eric Hawicz
0bcfb6bc84 Merge pull request #822 from SSharshunov/master
Added option to disable app build
2023-08-04 11:47:23 -04:00
Sergey Sharshunov
bef40a342e Added option to disable app build 2023-08-04 19:46:50 +05:00
Eric Hawicz
2316dbae85 Merge pull request #702 from commodo/json_patch
json_patch: add first implementation only with patch application
2023-07-31 22:19:30 -04:00
Eric Hawicz
612ba56f06 Don't export json_pointer_get_internal, move json_object_array_insert_idx and json_patch_apply to the JSONC_0.17 section in json-c.sym 2023-07-31 22:18:04 -04:00
Eric Hawicz
469bc0e4bb Work around a somewhat misleading warning about "a function declaration without a prototype is deprecated in all versions of C" in test1.c 2023-07-31 22:18:04 -04:00
Eric Hawicz
ce3184243a Fix json_patch_apply handling of removing the whole document (i.e. "path":"").
Enable all disabled tests, add a few more including some with null documents.
2023-07-31 22:18:04 -04:00
Eric Hawicz
9dbf2880cc Adjust the behavior of the args passed to json_patch_apply to make it easier to do in place modifications, and add a struct json_patch_error to report more details on failures. 2023-07-31 22:18:04 -04:00
Eric Hawicz
a14a3a680c Fix an uninitialized memory access in json_pointer.
Add comments describing when the fields of the internal struct json_pointer_get_result are valid.
2023-07-31 22:18:03 -04:00
Eric Hawicz
efc530594b Create a json_pointer_private.h and move a few things there, fix test warnings, note array_list_insert_idx is private. 2023-07-31 22:18:03 -04:00
Alexandru Ardelean
3b8363fcdc tests: test_json_patch: add test suite for JSON patch
Essentially, this change adds the test cases from this repo:
   https://github.com/json-patch/json-patch-tests

Specifically:
   https://github.com/json-patch/json-patch-tests/blob/master/spec_tests.json
   https://github.com/json-patch/json-patch-tests/blob/master/tests.json

The files were taken at the date of this commit, at git hash
  ea3af85790cb72893d0676597814b7532019c24e

Some tests may not have an 'expected' or 'error' field. Those are ignored.
One test was disabled manually via "disabled_in_json_c", because it tries
an impossible test, i.e. to add 2 "op" fields in the same patch entry,
something which is impossible in a JSON object.

For the 'error' cases, right now we only test that an error happens.
Later, we can extend this to check the error codes.

Signed-off-by: Alexandru Ardelean <ardeleanalex@gmail.com>
2023-07-31 22:18:03 -04:00
Alexandru Ardelean
538b046884 json_patch: add first implementation only with patch application
Initially I wanted to also do a function that generates the JSON patch from
two JSON documents, but even just applying the JSON patch was a bit of
work, especially when needing to satisfy all the test-cases.

This change defines all the operation in the RFC6902. The addition isn't
too big (for the json_patch_apply() function), as part of the heavy lifting
is also done by JSON pointer logic.

All the ops were tested with the test-cases defined at:
  https://github.com/json-patch/json-patch-tests

RFC6902: https://tools.ietf.org/html/rfc6902

Signed-off-by: Alexandru Ardelean <ardeleanalex@gmail.com>
2023-07-31 22:18:01 -04:00
Alexandru Ardelean
e4d9fbd52a json_pointer: split json_pointer_set_with_array_cb()
JSON patch is a bit more clear on how some array operations should be
handled. Unfortunately, handling them on a case-by-case is a bit tricky
because it's difficult to satisfy properly an 'add' operating with a 'move'
operation and the basic json_pointer_set().

With json_pointer_set{f}() we use json_object_array_put_idx() to insert a
value at a certain index.

With JSON patch:
* for the 'add' operation, we need to insert a value at a given index,
  which means shifting existing values by one to the right
  - also, we cannot allow values to be inserted/added outside the bounds of
    the array
* a 'move' operation, is described as a 'remove' and then an 'add';
  for arrays this complicates things, because when we want to a move a
  value within the array, we have to remove it first (during which the size
  of the array is reduced by one); when the size of the array is reduced by
  one, we can't add it to the last position in the array (before the
  remove)

The only sane method to handle this (after a few considerations) is to
provide a callback to the function that does the final put/insert into
the array. That way, we can do some final checks where these are needed to
handle each corner-case.

Signed-off-by: Alexandru Ardelean <ardeleanalex@gmail.com>
2023-07-31 22:17:30 -04:00
Alexandru Ardelean
1c38dea651 json_pointer: move array out-of-bounds check outside of is_valid_index()
The out-of-bounds check is useful when trying to index/obtain a value from
an array.
However, when we set a value to a specific JSON pointer, we can allow
values that are outside the length of the current array.
The RFC6901 doc isn't clear on that aspect, and doing so is a bit more
in-line with how json_object_array_{put,insert}_idx() functions behave.

This changes the behavior of json_pointer_set{f}() because now a value can
be set anywhere in the array.

Also, added a test-case for this behavior change.

Signed-off-by: Alexandru Ardelean <ardeleanalex@gmail.com>
2023-07-31 22:17:30 -04:00
Alexandru Ardelean
5a46a3b76d json_pointer: introduce json_pointer_get_internal() for internal usage
For JSON patch, we require that we get access to the parent of a JSON
object as well in order to do some operations via the API.

For example, given the object:
{
  "foo": "bar",
  "array", [ 1, 2, 3]
}

Using JSON pointer with the path
 * '/foo' will return 'bar' of type string
 * '/array/0' will return '1', of type integer

The problem is, that if we do 'json_object_put()' on any of the objects
above, this will not detach them from the parent, because there is no
information back to the parent.

One way to fix this, is to introduce links back to the parent, and have
these links be made by 'json_object_array_{put,insert}_idx()' and
'json_object_object_add{_ex}()'[1].

[1] For json_object_object_add_ex() we would need to de-constify the second
parameter, as we need to change it's internal state when being added to a
parent object. It may break some applications, but who knows.

But, since this information is needed mostly for JSON patch, another way to
address this, is to also retrieve the parent of an object via JSON pointer
and use json_object_object_del() and json_object_array_del_idx() on the
object's parent.

Signed-off-by: Alexandru Ardelean <ardeleanalex@gmail.com>
2023-07-31 22:17:30 -04:00
Alexandru Ardelean
43d3118935 json_pointer: convert index to size_t type
The index cannot be negative when parsing in is_valid_index(), because we
don't allow the '-' character in a string before we get to the strtol()
function.

So, might as well remove the negative check (for idx) in is_valid_index()
and convert it to size_t. That may allow for higher values for the index
(which can be insane, but some people may want to try it).

Signed-off-by: Alexandru Ardelean <ardeleanalex@gmail.com>
2023-07-31 22:17:30 -04:00
Alexandru Ardelean
d5c5b2caec tests: test1: add test cases for json_object_array_insert_idx()
This change adds a few test cases to test the behavior of the new
json_object_array_insert_idx() function, to make sure it behaves according
to specification in doc-string.

This test uses assert() vs the old method of comparing outputs.
This will cause the test to fail because the outputs won't match, since the
assert() will kick in.

Signed-off-by: Alexandru Ardelean <ardeleanalex@gmail.com>
2023-07-31 22:17:30 -04:00
Alexandru Ardelean
a86d7a8f5a json_object: introduce json_object_array_insert_idx() API function
The behavior of the json_object_array_put_idx() is that, if a user wants to
insert an element inside a JSON array, the element will be replaced.

For some cases, a user would want to insert an element into the JSON array
and shift the elements to the right.

For indexes that are outside the length of the current array this behaves
like json_object_array_put_idx().
If a user wants to enforce that the JSON array is not expanded, then the
json_object_array_length() function can be used to guard against that.

The main driver for this change is JSON patch, where the 'add' operation in
an array means inserting a value at a certain index and shifting everything
by one.

Signed-off-by: Alexandru Ardelean <ardeleanalex@gmail.com>
2023-07-31 22:17:30 -04:00
Alexandru Ardelean
5568916eb1 json_pointer: fix comments about printf() variants of set/get()
These were wrong. Some details about the json_pointer_setf() &
json_pointer_getf() were added in the json_pointer_set() &
json_pointer_get() doc-strings.

This change removes them.

Signed-off-by: Alexandru Ardelean <ardeleanalex@gmail.com>
2023-07-31 22:17:30 -04:00
Eric Hawicz
71d845e819 Issue #668: add the option to specify "cmake -DUSELOCALE_NEEDS_FREELOCALE=1" to work around a bug in older versions of FreeBSD (<12.4). 2023-07-30 13:38:15 -04:00
Eric Hawicz
c8b0a90e95 Issue #808: turn off -Wshorten-64-to-32 for clang builds (aka iOS builds) to workaround "implicit conversion loses integer precision" warnings. 2023-07-30 11:38:01 -04:00
40 changed files with 2233 additions and 153 deletions

View File

@@ -2,22 +2,146 @@ language: cpp
matrix: matrix:
include: include:
# ubuntu xenial 16.04
# gcc 5 is the default on xenial
- os: linux
dist: xenial
compiler: gcc
addons:
apt:
packages:
- valgrind
- cppcheck
- doxygen
- cmake
env: CHECK="true"
# ubuntu bionic 18.04
# gcc 7 is the default on bionic
- os: linux
dist: bionic
compiler: gcc
addons:
apt:
packages:
- valgrind
- cppcheck
- doxygen
- cmake
env: CHECK="true"
# ubuntu focal fossa 20.04
# gcc 9 is the default on bionic
- os: linux
dist: focal
compiler: gcc
addons:
apt:
packages:
- valgrind
- cppcheck
- doxygen
- cmake
env: CHECK="true"
# clang
# xenial
- os: linux
dist: xenial
compiler: clang
addons:
apt:
sources:
- llvm-toolchain-xenial-6.0
packages:
- clang-6.0
- cmake
env: MATRIX_EVAL="CC=clang-6.0 && CXX=clang++-6.0"
# clang-7 is the default on focal, xenial and bionic
- os: linux
dist: focal
compiler: clang
addons:
apt:
packages:
- valgrind
- cppcheck
- doxygen
- cmake
env: CHECK="true"
# osx # osx
- os: osx - os: osx
osx_image: xcode13.4 osx_image: xcode13.4
env: XCODE="true" CHECK="true" env: XCODE="true" CHECK="true"
# run coveralls
- os: linux
dist: xenial
compiler: gcc
addons:
apt:
packages:
- lcov
env: CHECK="true"
before_install:
- sudo gem install coveralls-lcov
- echo $CC
- echo $LANG
- echo $LC_ALL
- set -e
- if [ "$TRAVIS_OS_NAME" = "linux" ]; then
eval "${MATRIX_EVAL}";
if [ -n "$MATRIX_EVAL" ] && [ "$TRAVIS_COMPILER" != "clang" ]; then
sudo apt-get install -y $CC;
fi;
fi
before_script:
- export CFLAGS="-fprofile-arcs -ftest-coverage"
- mkdir build && cd build && cmake ..
script:
- make
- make test
after_success:
- cd ..
- lcov -d build/ -b . -c -o build/all_coverage.info
- lcov -r build/all_coverage.info '/usr/*' '*CMakeFiles*' '*fuzz*' '*test*' -o build/coverage.info
- coveralls-lcov --verbose build/coverage.info
# allow_failures:
# - os: osx
before_install: before_install:
- echo $CC - echo $CC
- echo $LANG - echo $LANG
- echo $LC_ALL - echo $LC_ALL
- set -e
- if [ "$TRAVIS_OS_NAME" = "linux" ]; then
eval "${MATRIX_EVAL}";
if [ -n "$MATRIX_EVAL" ] && [ "$TRAVIS_COMPILER" != "clang" ]; then
sudo apt-get install -y $CC;
fi;
fi
before_script: before_script:
# XXX osx on travis doesn't work w/ set -e, so turn it off :( # XXX osx on travis doesn't work w/ set -e, so turn it off :(
- set +e - set +e
- mkdir -p build || echo "Failed to mkdir build" - mkdir -p build || echo "Failed to mkdir build"
- cd build || echo "Failed to cd build" - cd build || echo "Failed to cd build"
- cmake -B_builds -GXcode -DCMAKE_SYSTEM_NAME=iOS -DCMAKE_OSX_DEPLOYMENT_TARGET=13.0 -DCMAKE_XCODE_ATTRIBUTE_ONLY_ACTIVE_ARCH=NO -DCMAKE_IOS_INSTALL_COMBINED=YES .. || { echo "Failed to run cmake" ; exit 1 ; } - cmake .. || echo "Failed to run cmake"
script: script:
- cmake --build _builds --config Release --target json-c-static -- -sdk iphonesimulator - make
# when using bionic, Travis seems to ignore the "addons" section, so installing the packages with apt-get...
- if [ -n "$CHECK" ]; then
if [ "$TRAVIS_OS_NAME" = "osx" ]; then
brew install doxygen;
else
if [ "$TRAVIS_DIST" = "bionic" ]; then
sudo apt-get install -y valgrind cppcheck doxygen;
fi;
fi;
make distcheck;
if type cppcheck &> /dev/null ; then cppcheck --error-exitcode=1 --quiet *.h *.c tests/ ; fi;
fi

10
AUTHORS
View File

@@ -1,6 +1,7 @@
Alan Coopersmith <alan.coopersmith@oracle.com> Alan Coopersmith <alan.coopersmith@oracle.com>
Alexander Dahl <post@lespocky.de> Alexander Dahl <post@lespocky.de>
Alexandru Ardelean <ardeleanalex@gmail.com> Alexandru Ardelean <ardeleanalex@gmail.com>
An7ar35 <eadavison@protonmail.com>
andy5995 <andy400-dev@yahoo.com> andy5995 <andy400-dev@yahoo.com>
Aram Poghosyan <Aram.Poghosyan@teamviewer.com> Aram Poghosyan <Aram.Poghosyan@teamviewer.com>
Björn Esser <besser82@fedoraproject.org> Björn Esser <besser82@fedoraproject.org>
@@ -11,13 +12,16 @@ Chris Lamb <lamby@debian.org>
Christopher Head <chead@chead.ca> Christopher Head <chead@chead.ca>
Chris Wolfe <chriswwolfe@gmail.com> Chris Wolfe <chriswwolfe@gmail.com>
C. Watford (christopher.watford@gmail.com) C. Watford (christopher.watford@gmail.com)
Daniel Danzberger <daniel@dd-wrt.com>
Darjan Krijan <darjan_krijan@gmx.de> Darjan Krijan <darjan_krijan@gmx.de>
David McCann <mccannd@uk.ibm.com> David McCann <mccannd@uk.ibm.com>
DeX77 <dex@dragonslave.de> DeX77 <dex@dragonslave.de>
Dimitri Papadopoulos <3234522+DimitriPapadopoulos@users.noreply.github.com>
dota17 <chenguopingdota@163.com> dota17 <chenguopingdota@163.com>
Eric Haszlakiewicz <erh+git@nimenees.com> Eric Haszlakiewicz <erh+git@nimenees.com>
Eric Hawicz <erh+git@nimenees.com> Eric Hawicz <erh+git@nimenees.com>
Even Rouault <even.rouault@spatialys.com> Even Rouault <even.rouault@spatialys.com>
Federico Francescon <federico.francescon@higeco.com>
Gianluigi Tiesi <sherpya@netfarm.it> Gianluigi Tiesi <sherpya@netfarm.it>
grdowns <grdowns@microsoft.com> grdowns <grdowns@microsoft.com>
Hex052 <elijahiff@gmail.com> Hex052 <elijahiff@gmail.com>
@@ -27,6 +31,7 @@ Ivan Romanov <drizt@land.ru>
Jaap Keuter <jaap.keuter@xs4all.nl> Jaap Keuter <jaap.keuter@xs4all.nl>
Jakov Smolic <jakov.smolic@sartura.hr> Jakov Smolic <jakov.smolic@sartura.hr>
janczer <menshikov.ivn@gmail.com> janczer <menshikov.ivn@gmail.com>
JC (Jonathan Chen) <jc@dijonkitchen.org>
Jehan <jehan@girinstud.io> Jehan <jehan@girinstud.io>
Jehiah Czebotar <jehiah@gmail.com> Jehiah Czebotar <jehiah@gmail.com>
Jonathan Wiens <j.wiens@teles.com> Jonathan Wiens <j.wiens@teles.com>
@@ -34,10 +39,13 @@ Jose Bollo <jose.bollo@iot.bzh>
José Bollo <jose.bollo@iot.bzh> José Bollo <jose.bollo@iot.bzh>
Juuso Alasuutari <juuso.alasuutari@gmail.com> Juuso Alasuutari <juuso.alasuutari@gmail.com>
Keith Holman <keith.holman@windriver.com> Keith Holman <keith.holman@windriver.com>
Khem Raj <raj.khem@gmail.com>
Kizuna-Meraki <z9@kizunameraki.de> Kizuna-Meraki <z9@kizunameraki.de>
Leon Gross <leon.gross@rub.de> Leon Gross <leon.gross@rub.de>
Liang, Gao <liang.gao@intel.com> Liang, Gao <liang.gao@intel.com>
Luca Mannella <luca.mannella@studenti.polito.it>
Marc <34656315+MarcT512@users.noreply.github.com> Marc <34656315+MarcT512@users.noreply.github.com>
Matthias Gatto <matthias.gatto@protonmail.com>
max <mpano91@gmail.com> max <mpano91@gmail.com>
Micah Snyder <micasnyd@cisco.com> Micah Snyder <micasnyd@cisco.com>
Michael Clark <michael@metaparadigm.com> Michael Clark <michael@metaparadigm.com>
@@ -53,9 +61,11 @@ Robert Bielik <robert.bielik@dirac.com>
Robert <roby_p97@yahoo.com> Robert <roby_p97@yahoo.com>
Rosen Penev <rosenp@gmail.com> Rosen Penev <rosenp@gmail.com>
Rubasri Kalidas <rubasri.kalidas@intel.com> Rubasri Kalidas <rubasri.kalidas@intel.com>
Sergey Sharshunov <s.sharshunov@gmail.com>
Simon McVittie <smcv@collabora.com> Simon McVittie <smcv@collabora.com>
ssrlive <30760636+ssrlive@users.noreply.github.com> ssrlive <30760636+ssrlive@users.noreply.github.com>
Tobias Nießen <tniessen@users.noreply.github.com> Tobias Nießen <tniessen@users.noreply.github.com>
Tobias Stoeckmann <tobias@stoeckmann.org> Tobias Stoeckmann <tobias@stoeckmann.org>
Tudor Brindus <me@tbrindus.ca> Tudor Brindus <me@tbrindus.ca>
Unmanned Player <36690541+unmanned-player@users.noreply.github.com> Unmanned Player <36690541+unmanned-player@users.noreply.github.com>
Yurii Rashkovskii <yrashk@gmail.com>

View File

@@ -3,24 +3,11 @@
# and many OpenWRT packages require, much newer CMake packages. # and many OpenWRT packages require, much newer CMake packages.
# We're stopping before 3.10 because that version starts requiring # We're stopping before 3.10 because that version starts requiring
# c++11, which isn't available on e.g HPUX. # c++11, which isn't available on e.g HPUX.
cmake_minimum_required(VERSION 3.9) cmake_minimum_required(VERSION 3.9...3.12)
# The project() command manages VERSION variables.
cmake_policy(SET CMP0048 NEW)
# JSON-C library is C only project. # JSON-C library is C only project.
# PROJECT_VERSION{,_MAJOR,_MINOR,_PATCH} set by project(): # PROJECT_VERSION{,_MAJOR,_MINOR,_PATCH} set by project():
project(json-c LANGUAGES C VERSION 0.16.99) project(json-c LANGUAGES C VERSION 0.17.99)
# Targets may not link directly to themselves.
cmake_policy(SET CMP0038 NEW)
# MACOSX_RPATH is enabled by default.
# We set it explicitly to avoid the warning
cmake_policy(SET CMP0042 NEW)
# Only interpret if() arguments as variables or keywords when unquoted.
cmake_policy(SET CMP0054 NEW)
# set default build type if not specified by user # set default build type if not specified by user
if(NOT CMAKE_BUILD_TYPE) if(NOT CMAKE_BUILD_TYPE)
@@ -29,13 +16,6 @@ endif()
set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} -O2") set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} -O2")
# Include file check macros honor CMAKE_REQUIRED_LIBRARIES
# i.e. the check_include_file() calls will include -lm when checking.
# New in version 3.12.
if(POLICY CMP0075)
cmake_policy(SET CMP0075 NEW)
endif()
include(CTest) include(CTest)
# Set some packaging variables. # Set some packaging variables.
@@ -88,7 +68,10 @@ option(ENABLE_RDRAND "Enable RDRAND Hardware RNG Hash Seed."
option(ENABLE_THREADING "Enable partial threading support." OFF) option(ENABLE_THREADING "Enable partial threading support." OFF)
option(OVERRIDE_GET_RANDOM_SEED "Override json_c_get_random_seed() with custom code." OFF) option(OVERRIDE_GET_RANDOM_SEED "Override json_c_get_random_seed() with custom code." OFF)
option(DISABLE_EXTRA_LIBS "Avoid linking against extra libraries, such as libbsd." OFF) option(DISABLE_EXTRA_LIBS "Avoid linking against extra libraries, such as libbsd." OFF)
option(DISABLE_JSON_POINTER "Disable JSON pointer (RFC6901) support." OFF) option(DISABLE_JSON_POINTER "Disable JSON pointer (RFC6901) and JSON patch support." OFF)
option(DISABLE_JSON_PATCH "Disable JSON patch (RFC6902) support." OFF)
option(NEWLOCALE_NEEDS_FREELOCALE "Work around newlocale bugs in old FreeBSD by calling freelocale" OFF)
option(BUILD_APPS "Default to building apps" ON)
if (UNIX OR MINGW OR CYGWIN) if (UNIX OR MINGW OR CYGWIN)
@@ -140,11 +123,14 @@ check_include_file(sys/random.h HAVE_SYS_RANDOM_H)
check_include_file(sys/stat.h HAVE_SYS_STAT_H) check_include_file(sys/stat.h HAVE_SYS_STAT_H)
check_include_file(xlocale.h HAVE_XLOCALE_H) check_include_file(xlocale.h HAVE_XLOCALE_H)
# Set json-c specific vars to stamp into json_config.h
# in a way that hopefully won't conflict with other
# projects that use json-c.
if (HAVE_INTTYPES_H) if (HAVE_INTTYPES_H)
# Set a json-c specific var to stamp into json_config.h set(JSON_C_HAVE_INTTYPES_H 1)
# in a way that hopefully won't conflict with other endif()
# projects that use json-c. if (HAVE_STDINT_H)
set(JSON_C_HAVE_INTTYPES_H 1) set(JSON_C_HAVE_STDINT_H 1)
endif() endif()
check_symbol_exists(_isnan "float.h" HAVE_DECL__ISNAN) check_symbol_exists(_isnan "float.h" HAVE_DECL__ISNAN)
@@ -191,9 +177,10 @@ endif()
# uClibc *intentionally* crashes in duplocale(), at least as of: # uClibc *intentionally* crashes in duplocale(), at least as of:
# https://github.com/ffainelli/uClibc/blob/266bdc1/libc/misc/locale/locale.c#L1322 # https://github.com/ffainelli/uClibc/blob/266bdc1/libc/misc/locale/locale.c#L1322
# So, if it looks like we're compiling for a system like that just disable # So, if it looks like we're compiling for a system like that just disable
# locale handling entirely. # locale handling entirely.
exec_program(${CMAKE_C_COMPILER} ARGS -dumpmachine OUTPUT_VARIABLE CMAKE_GNU_C_MACHINE) execute_process (COMMAND ${CMAKE_C_COMPILER} -dumpmachine ERROR_QUIET OUTPUT_VARIABLE CMAKE_GNU_C_MACHINE)
if (CMAKE_GNU_C_MACHINE MATCHES "uclibc") if (CMAKE_GNU_C_MACHINE MATCHES "uclibc")
message(STATUS "Detected uClibc compiler, disabling locale handling") message(STATUS "Detected uClibc compiler, disabling locale handling")
set(HAVE_SETLOCALE 0) set(HAVE_SETLOCALE 0)
@@ -289,7 +276,24 @@ message(STATUS "Wrote ${PROJECT_BINARY_DIR}/config.h")
configure_file(${PROJECT_SOURCE_DIR}/cmake/json_config.h.in ${PROJECT_BINARY_DIR}/json_config.h) configure_file(${PROJECT_SOURCE_DIR}/cmake/json_config.h.in ${PROJECT_BINARY_DIR}/json_config.h)
message(STATUS "Wrote ${PROJECT_BINARY_DIR}/json_config.h") message(STATUS "Wrote ${PROJECT_BINARY_DIR}/json_config.h")
if ("${CMAKE_C_COMPILER_ID}" STREQUAL "GNU" OR "${CMAKE_C_COMPILER_ID}" STREQUAL "Clang") if (NOT DEFINED CMAKE_C_COMPILER_FRONTEND_VARIANT OR "${CMAKE_C_COMPILER_FRONTEND_VARIANT}" STREQUAL "") # only available in cmake 3.14+
if ("${CMAKE_C_COMPILER_ID}" STREQUAL "MSVC")
set(CMAKE_C_COMPILER_FRONTEND_VARIANT "MSVC")
elseif ("${CMAKE_C_COMPILER_ID}" STREQUAL "GNU")
set(CMAKE_C_COMPILER_FRONTEND_VARIANT "GNU")
elseif ("${CMAKE_C_COMPILER_ID}" STREQUAL "Clang")
# Detect clang-cl.exe, it is Clang but with MSVC compatible command line arguments
execute_process (COMMAND ${CMAKE_C_COMPILER} -? ERROR_QUIET OUTPUT_QUIET RESULT_VARIABLE _clang_result)
if (_clang_result EQUAL 0)
set(CMAKE_C_COMPILER_FRONTEND_VARIANT "MSVC")
else()
set(CMAKE_C_COMPILER_FRONTEND_VARIANT "GNU")
message(STATUS, "clang result is NOT 0")
endif()
endif()
endif()
if ("${CMAKE_C_COMPILER_FRONTEND_VARIANT}" STREQUAL "GNU" OR "${CMAKE_C_COMPILER_FRONTEND_VARIANT}" STREQUAL "Clang")
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -ffunction-sections -fdata-sections") set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -ffunction-sections -fdata-sections")
if ("${DISABLE_WERROR}" STREQUAL "OFF") if ("${DISABLE_WERROR}" STREQUAL "OFF")
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Werror") set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Werror")
@@ -305,7 +309,12 @@ if ("${CMAKE_C_COMPILER_ID}" STREQUAL "GNU" OR "${CMAKE_C_COMPILER_ID}" STREQUAL
endif() endif()
add_definitions(-D_GNU_SOURCE) add_definitions(-D_GNU_SOURCE)
elseif ("${CMAKE_C_COMPILER_ID}" STREQUAL "MSVC")
if ("${CMAKE_C_COMPILER_FRONTEND_VARIANT}" STREQUAL "Clang")
# Remove this for 1.0 when we can bump the ABI and actually fix these warnings.
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wno-shorten-64-to-32")
endif()
elseif ("${CMAKE_C_COMPILER_FRONTEND_VARIANT}" STREQUAL "MSVC")
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} /DEBUG") set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} /DEBUG")
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} /wd4100") set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} /wd4100")
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} /wd4996") set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} /wd4996")
@@ -316,7 +325,7 @@ elseif ("${CMAKE_C_COMPILER_ID}" STREQUAL "MSVC")
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} /wd4701") set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} /wd4701")
endif() endif()
if (NOT ("${CMAKE_C_COMPILER_ID}" STREQUAL "MSVC")) if (NOT ("${CMAKE_C_COMPILER_FRONTEND_VARIANT}" STREQUAL "MSVC"))
check_c_source_compiles( check_c_source_compiles(
" "
/* uClibc toolchains without threading barf when _REENTRANT is defined */ /* uClibc toolchains without threading barf when _REENTRANT is defined */
@@ -396,6 +405,7 @@ set(JSON_C_PUBLIC_HEADERS
set(JSON_C_HEADERS set(JSON_C_HEADERS
${JSON_C_PUBLIC_HEADERS} ${JSON_C_PUBLIC_HEADERS}
${PROJECT_SOURCE_DIR}/json_object_private.h ${PROJECT_SOURCE_DIR}/json_object_private.h
${PROJECT_SOURCE_DIR}/json_pointer_private.h
${PROJECT_SOURCE_DIR}/random_seed.h ${PROJECT_SOURCE_DIR}/random_seed.h
${PROJECT_SOURCE_DIR}/strerror_override.h ${PROJECT_SOURCE_DIR}/strerror_override.h
${PROJECT_SOURCE_DIR}/math_compat.h ${PROJECT_SOURCE_DIR}/math_compat.h
@@ -423,8 +433,15 @@ if (NOT DISABLE_JSON_POINTER)
set(JSON_C_PUBLIC_HEADERS ${JSON_C_PUBLIC_HEADERS} ${PROJECT_SOURCE_DIR}/json_pointer.h) set(JSON_C_PUBLIC_HEADERS ${JSON_C_PUBLIC_HEADERS} ${PROJECT_SOURCE_DIR}/json_pointer.h)
set(JSON_C_SOURCES ${JSON_C_SOURCES} ${PROJECT_SOURCE_DIR}/json_pointer.c) set(JSON_C_SOURCES ${JSON_C_SOURCES} ${PROJECT_SOURCE_DIR}/json_pointer.c)
set(JSON_H_JSON_POINTER "#include \"json_pointer.h\"") set(JSON_H_JSON_POINTER "#include \"json_pointer.h\"")
if (NOT DISABLE_JSON_PATCH)
set(JSON_C_PUBLIC_HEADERS ${JSON_C_PUBLIC_HEADERS} ${PROJECT_SOURCE_DIR}/json_patch.h)
set(JSON_C_SOURCES ${JSON_C_SOURCES} ${PROJECT_SOURCE_DIR}/json_patch.c)
set(JSON_H_JSON_PATCH "#include \"json_patch.h\"")
endif()
else() else()
set(JSON_H_JSON_POINTER "") set(JSON_H_JSON_POINTER "")
set(JSON_H_JSON_PATCH "")
endif() endif()
configure_file(json.h.cmakein ${PROJECT_BINARY_DIR}/json.h @ONLY) configure_file(json.h.cmakein ${PROJECT_BINARY_DIR}/json.h @ONLY)
@@ -453,7 +470,7 @@ add_library(${PROJECT_NAME}
${JSON_C_HEADERS} ${JSON_C_HEADERS}
) )
set_target_properties(${PROJECT_NAME} PROPERTIES set_target_properties(${PROJECT_NAME} PROPERTIES
VERSION 5.2.0 VERSION 5.3.0
SOVERSION 5) SOVERSION 5)
list(APPEND CMAKE_TARGETS ${PROJECT_NAME}) list(APPEND CMAKE_TARGETS ${PROJECT_NAME})
# If json-c is used as subroject it set to target correct interface -I flags and allow # If json-c is used as subroject it set to target correct interface -I flags and allow
@@ -526,7 +543,6 @@ install(
DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME} DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME}
) )
if (UNIX OR MINGW OR CYGWIN)
SET(prefix ${CMAKE_INSTALL_PREFIX}) SET(prefix ${CMAKE_INSTALL_PREFIX})
# exec_prefix is prefix by default and CMake does not have the # exec_prefix is prefix by default and CMake does not have the
# concept. # concept.
@@ -554,7 +570,6 @@ if (UNIX OR MINGW OR CYGWIN)
configure_file(json-c.pc.in json-c.pc @ONLY) configure_file(json-c.pc.in json-c.pc @ONLY)
set(INSTALL_PKGCONFIG_DIR "${CMAKE_INSTALL_LIBDIR}/pkgconfig" CACHE PATH "Installation directory for pkgconfig (.pc) files") set(INSTALL_PKGCONFIG_DIR "${CMAKE_INSTALL_LIBDIR}/pkgconfig" CACHE PATH "Installation directory for pkgconfig (.pc) files")
install(FILES ${PROJECT_BINARY_DIR}/json-c.pc DESTINATION "${INSTALL_PKGCONFIG_DIR}") install(FILES ${PROJECT_BINARY_DIR}/json-c.pc DESTINATION "${INSTALL_PKGCONFIG_DIR}")
endif ()
install(FILES ${JSON_C_PUBLIC_HEADERS} DESTINATION ${CMAKE_INSTALL_FULL_INCLUDEDIR}/json-c) install(FILES ${JSON_C_PUBLIC_HEADERS} DESTINATION ${CMAKE_INSTALL_FULL_INCLUDEDIR}/json-c)
@@ -564,10 +579,9 @@ if (CMAKE_PROJECT_NAME STREQUAL PROJECT_NAME AND BUILD_TESTING AND
add_subdirectory(tests) add_subdirectory(tests)
endif() endif()
if (CMAKE_PROJECT_NAME STREQUAL PROJECT_NAME) if (CMAKE_PROJECT_NAME STREQUAL PROJECT_NAME AND BUILD_APPS)
# skip apps when we're included in someone else's build # skip apps when we're included in someone else's build
if (NOT MSVC) # cmd line apps don't built on Windows currently. if (NOT MSVC) # cmd line apps don't built on Windows currently.
add_subdirectory(apps) add_subdirectory(apps)
endif() endif()
endif() endif()

View File

@@ -1,14 +1,17 @@
0.17 (future release) 0.17 (up to commit 077661f, 2023-08-08)
======================================== ========================================
Deprecated and removed features: Deprecated and removed features:
-------------------------------- --------------------------------
* ... * None
New features New features
------------ ------------
* ... * json_patch: add first implementation only with patch application
* Add --disable-static and --disable-dynamic options to the cmake-configure script.
* Add -DBUILD_APPS=NO option to disable app build
* Minimum cmake version is now 3.9
Significant changes and bug fixes Significant changes and bug fixes
--------------------------------- ---------------------------------
@@ -19,7 +22,17 @@ Significant changes and bug fixes
* When parsing with JSON_TOKENER_STRICT set, integer overflow/underflow * When parsing with JSON_TOKENER_STRICT set, integer overflow/underflow
now result in a json_tokener_error_parse_number. Without that flag now result in a json_tokener_error_parse_number. Without that flag
values are capped at INT64_MIN/UINT64_MAX. values are capped at INT64_MIN/UINT64_MAX.
* Fix memory leak with emtpy strings in json_object_set_string
* json_object_from_fd_ex: fail if file is too large (>=INT_MAX bytes)
* Add back json_number_chars, but only because it's part of the public API.
* Entirely drop mode bits from open(O_RDONLY) to avoid warnings on certain
platforms.
* Specify dependent libraries, including -lbsd, in a more consistent way so
linking against a static json-c works better
* Fix a variety of build problems and add & improve tests
* Update RFC reference to https://www.rfc-editor.org/rfc/rfc8259
***
0.16 (up to commit 66dcdf5, 2022-04-13) 0.16 (up to commit 66dcdf5, 2022-04-13)
======================================== ========================================

View File

@@ -1,6 +1,6 @@
\mainpage \mainpage
`json-c` json-c
======== ========
1. [Overview and Build Status](#overview) 1. [Overview and Build Status](#overview)
@@ -15,7 +15,8 @@
7. [Linking to libjson-c](#linking) 7. [Linking to libjson-c](#linking)
8. [Using json-c](#using) 8. [Using json-c](#using)
JSON-C - A JSON implementation in C <a name="overview"></a> <a name="overview"></a>
JSON-C - A JSON implementation in C
----------------------------------- -----------------------------------
JSON-C implements a reference counting object model that allows you to easily JSON-C implements a reference counting object model that allows you to easily
@@ -29,7 +30,8 @@ if you already have json-c installed and ready to use.
Home page for json-c: https://github.com/json-c/json-c/wiki Home page for json-c: https://github.com/json-c/json-c/wiki
Getting Help <a name="gettinghelp"></a> <a name="gettinghelp"></a>
Getting Help
------------ ------------
If you have questions about using json-c, please start a thread on If you have questions about using json-c, please start a thread on
@@ -47,7 +49,8 @@ features or large changes of any kind, please first start a discussion
on the [forums](https://groups.google.com/forum/#!forum/json-c). on the [forums](https://groups.google.com/forum/#!forum/json-c).
Building on Unix with `git`, `gcc` and `cmake` <a name="buildunix"></a> <a name="buildunix"></a>
Building on Unix with `git`, `gcc` and `cmake`
-------------------------------------------------- --------------------------------------------------
If you already have json-c installed, see [Linking to `libjson-c`](#linking) If you already have json-c installed, see [Linking to `libjson-c`](#linking)
@@ -60,7 +63,8 @@ Build Status
Test Status Test Status
* [Coveralls](https://coveralls.io/github/json-c/json-c?branch=master) [![Coverage Status](https://coveralls.io/repos/github/json-c/json-c/badge.svg?branch=master)](https://coveralls.io/github/json-c/json-c?branch=master) * [Coveralls](https://coveralls.io/github/json-c/json-c?branch=master) [![Coverage Status](https://coveralls.io/repos/github/json-c/json-c/badge.svg?branch=master)](https://coveralls.io/github/json-c/json-c?branch=master)
### Prerequisites: <a name="installprereq"></a> <a name="installprereq"></a>
### Prerequisites:
- `gcc`, `clang`, or another C compiler - `gcc`, `clang`, or another C compiler
@@ -80,7 +84,8 @@ sudo apt install doxygen # optional
sudo apt install valgrind # optional sudo apt install valgrind # optional
``` ```
### Build instructions: <a name="buildcmds"></a> <a name="buildcmds"></a>
### Build instructions:
`json-c` GitHub repo: https://github.com/json-c/json-c `json-c` GitHub repo: https://github.com/json-c/json-c
@@ -116,7 +121,8 @@ google-chrome doc/html/index.html
``` ```
CMake Options <a name="CMake"></a> <a name="CMake"></a>
CMake Options
-------------------- --------------------
The json-c library is built with [CMake](https://cmake.org/cmake-tutorial/), The json-c library is built with [CMake](https://cmake.org/cmake-tutorial/),
@@ -188,7 +194,8 @@ cmake-configure can take a few options.
| disable-werror | Avoid treating compiler warnings as fatal errors | | disable-werror | Avoid treating compiler warnings as fatal errors |
Testing: <a name="testing"></a> <a name="testing"></a>
Testing:
---------- ----------
By default, if valgrind is available running tests uses it. By default, if valgrind is available running tests uses it.
@@ -224,7 +231,8 @@ JSONC_TEST_TRACE=1 make test
and check the log files again. and check the log files again.
Building on Unix and Windows with `vcpkg` <a name="buildvcpkg"></a> <a name="buildvcpkg"></a>
Building on Unix and Windows with `vcpkg`
-------------------------------------------------- --------------------------------------------------
You can download and install JSON-C using the [vcpkg](https://github.com/Microsoft/vcpkg/) dependency manager: You can download and install JSON-C using the [vcpkg](https://github.com/Microsoft/vcpkg/) dependency manager:
@@ -237,7 +245,8 @@ You can download and install JSON-C using the [vcpkg](https://github.com/Microso
The JSON-C port in vcpkg is kept up to date by Microsoft team members and community contributors. If the version is out of date, please [create an issue or pull request](https://github.com/Microsoft/vcpkg) on the vcpkg repository. The JSON-C port in vcpkg is kept up to date by Microsoft team members and community contributors. If the version is out of date, please [create an issue or pull request](https://github.com/Microsoft/vcpkg) on the vcpkg repository.
Building for Android <a name="android"> <a name="android">
Building for Android
---------------------- ----------------------
Building on Android is now particularly well supported, but there Building on Android is now particularly well supported, but there
@@ -261,7 +270,8 @@ cmake \
make install make install
``` ```
Linking to `libjson-c` <a name="linking"> <a name="linking">
Linking to `libjson-c`
---------------------- ----------------------
If your system has `pkgconfig`, If your system has `pkgconfig`,
@@ -298,7 +308,8 @@ cd build
cmake -DCMAKE_PREFIX_PATH=/path/to/json_c/install/lib64/cmake .. cmake -DCMAKE_PREFIX_PATH=/path/to/json_c/install/lib64/cmake ..
``` ```
Using json-c <a name="using"> <a name="using">
Using json-c
------------ ------------
To use json-c you can either include json.h, or preferably, one of the To use json-c you can either include json.h, or preferably, one of the

View File

@@ -16,6 +16,7 @@
changes (added/removed/updated funcs, etc...), and detect backwards compat changes (added/removed/updated funcs, etc...), and detect backwards compat
issues. issues.
* https://github.com/lvc/abi-compliance-checker * https://github.com/lvc/abi-compliance-checker
* See also `abi-check.sh`
* If the new release is not backwards compatible, then this is a MAJOR release. * If the new release is not backwards compatible, then this is a MAJOR release.
* Mention removed features in ChangeLog * Mention removed features in ChangeLog
* Consider re-adding backwards compatible support, through symbol * Consider re-adding backwards compatible support, through symbol
@@ -40,9 +41,15 @@
## Release creation ## Release creation
Start creating the new release: Start creating the new release:
release=0.16
PREV=$(git tag | tail -1)
PREV=${PREV#json-c-}
PREV=${PREV%-*}
release=0.$((${PREV#*.} + 1))
cd ~
git clone https://github.com/json-c/json-c json-c-${release} git clone https://github.com/json-c/json-c json-c-${release}
rm -rf distcheck
mkdir distcheck mkdir distcheck
cd distcheck cd distcheck
# Note, the build directory *must* be entirely separate from # Note, the build directory *must* be entirely separate from
@@ -54,8 +61,7 @@ Start creating the new release:
Make any fixes/changes *before* branching. Make any fixes/changes *before* branching.
cd json-c-${release} cd json-c-${release}
git branch json-c-${release} git checkout -b json-c-${release}
git checkout json-c-${release}
------------ ------------
@@ -135,12 +141,10 @@ Use ${release}.99 to indicate a version "newer" than anything on the branch:
Update the version in json_c_version.h Update the version in json_c_version.h
Update the version in CMakeLists.txt Update the version in CMakeLists.txt
Update RELEASE_CHECKLIST.txt, set release=${release}+1 Update the set_target_properties() line in CmakeLists.txt to match the release branch.
Add a new empty section to the json-c.sym file, for ${release}+1 Add a new empty section to the json-c.sym file, for ${release}+1
Update the set_target_properties() line in CmakeLists.txt to match the release branch.
git commit -a -m "Update the master branch to version ${release}.99" git commit -a -m "Update the master branch to version ${release}.99"
git push git push
@@ -177,7 +181,8 @@ Update checksums on wiki page.
openssl md5 json-c*gz openssl md5 json-c*gz
Copy and paste this output into the wiki page at: Copy and paste this output into the wiki page at:
https://github.com/json-c/json-c/wiki * https://github.com/json-c/json-c/wiki
* https://github.com/json-c/json-c/wiki/Old-Releases
------------ ------------

42
abi-check.sh Normal file
View File

@@ -0,0 +1,42 @@
#!/bin/sh
prev=0.16
release=0.17
# ... clone json-c, abi-compliance-checker, abi-dumper
mkdir build
cd build
CFLAGS=-Og cmake -DCMAKE_INSTALL_PREFIX=~/json-c-installs/json-c-${release} ..
make && make test && make install
# Assume the old version has already been built
cd ~/abi-compliance-checker
mkxml()
{
ver="$1"
cat <<EOF > json-c-${ver}.xml
<foo>
<version>
${ver}
</version>
<headers>
../json-c-installs/json-c-${ver}/include/json-c
</headers>
<libs>
../json-c-installs/json-c-${ver}/lib64/libjson-c.so
</libs>
</foo>
EOF
}
mkxml ${release}
mkxml ${prev}
perl abi-compliance-checker.pl -lib json-c -dump json-c-${prev}.xml -dump-path ./ABI-${prev}.dump
perl abi-compliance-checker.pl -lib json-c -dump json-c-${release}.xml -dump-path ./ABI-${release}.dump
perl abi-compliance-checker.pl -l json-c -old ABI-${prev}.dump -new ABI-${release}.dump
echo "look in compat_reports/json-c/..."

View File

@@ -125,6 +125,27 @@ int array_list_shrink(struct array_list *arr, size_t empty_slots)
return 0; return 0;
} }
int array_list_insert_idx(struct array_list *arr, size_t idx, void *data)
{
size_t move_amount;
if (idx >= arr->length)
return array_list_put_idx(arr, idx, data);
/* we're at full size, what size_t can support */
if (arr->length == SIZE_T_MAX)
return -1;
if (array_list_expand_internal(arr, arr->length + 1))
return -1;
move_amount = (arr->length - idx) * sizeof(void *);
memmove(arr->array + idx + 1, arr->array + idx, move_amount);
arr->array[idx] = data;
arr->length++;
return 0;
}
//static inline int _array_list_put_idx(struct array_list *arr, size_t idx, void *data) //static inline int _array_list_put_idx(struct array_list *arr, size_t idx, void *data)
int array_list_put_idx(struct array_list *arr, size_t idx, void *data) int array_list_put_idx(struct array_list *arr, size_t idx, void *data)
{ {

View File

@@ -62,6 +62,8 @@ extern void array_list_free(struct array_list *al);
extern void *array_list_get_idx(struct array_list *al, size_t i); extern void *array_list_get_idx(struct array_list *al, size_t i);
extern int array_list_insert_idx(struct array_list *al, size_t i, void *data);
extern int array_list_put_idx(struct array_list *al, size_t i, void *data); extern int array_list_put_idx(struct array_list *al, size_t i, void *data);
extern int array_list_add(struct array_list *al, void *data); extern int array_list_add(struct array_list *al, void *data);

View File

@@ -137,6 +137,9 @@
/* Define to 1 if you have the `uselocale' function. */ /* Define to 1 if you have the `uselocale' function. */
#cmakedefine HAVE_USELOCALE #cmakedefine HAVE_USELOCALE
/* Define to 1 if newlocale() needs freelocale() called on it's `base` argument */
#cmakedefine NEWLOCALE_NEEDS_FREELOCALE
/* Define to 1 if you have the `vasprintf' function. */ /* Define to 1 if you have the `vasprintf' function. */
#cmakedefine HAVE_VASPRINTF #cmakedefine HAVE_VASPRINTF

View File

@@ -1,2 +1,5 @@
/* Define to 1 if you have the <inttypes.h> header file. */ /* Define to 1 if you have the <inttypes.h> header file. */
#cmakedefine JSON_C_HAVE_INTTYPES_H @JSON_C_HAVE_INTTYPES_H@ #cmakedefine JSON_C_HAVE_INTTYPES_H @JSON_C_HAVE_INTTYPES_H@
/* Define to 1 if you have the <stdint.h> header file. */
#cmakedefine JSON_C_HAVE_STDINT_H @JSON_C_HAVE_STDINT_H@

View File

@@ -8,11 +8,13 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size)
json_tokener *tok = json_tokener_new(); json_tokener *tok = json_tokener_new();
json_object *obj = json_tokener_parse_ex(tok, data1, size); json_object *obj = json_tokener_parse_ex(tok, data1, size);
json_object_object_foreach(jobj, key, val) { if (json_object_is_type(obj, json_type_object)) {
(void)json_object_get_type(val); json_object_object_foreach(obj, key, val) {
(void)json_object_get_string(val); (void)json_object_get_type(val);
(void)json_object_get_string(val);
}
} }
(void)json_object_to_json_string(obj, JSON_C_TO_STRING_PRETTY | JSON_C_TO_STRING_SPACED); (void)json_object_to_json_string_ext(obj, JSON_C_TO_STRING_PRETTY | JSON_C_TO_STRING_SPACED);
json_object_put(obj); json_object_put(obj);
json_tokener_free(tok); json_tokener_free(tok);

88
issues_closed_for_0.17.md Normal file
View File

@@ -0,0 +1,88 @@
This list was created with:
```
PREV=2022-04-13
NOW=2023-08-12
curl "https://api.github.com/search/issues?q=repo%3Ajson-c%2Fjson-c+closed%3A>${PREV}+created%3A<${NOW}&sort=created&order=asc&per_page=100&page=1" > issues1.out
jq -r '.items[] | "[" + .title + "](" + .url + ")" | tostring' issues?.out > issues.md
sed -e's,^\[ *\(.*\)\](https://api.github.com/.*/\([0-9].*\)),* [Issue #\2](https://github.com/json-c/json-c/issues/\2) - \1,' -i issues.md
cat issues.md >> issues_closed_for_0.17.md
```
* [Issue #191](https://github.com/json-c/json-c/issues/191) - Override int64 to only display uint64 strings
* [Issue #537](https://github.com/json-c/json-c/issues/537) - Replace '\0' only when parsing key, not change data in value.
* [Issue #570](https://github.com/json-c/json-c/issues/570) - Figure out what needs to be done with Android.configure.mk
* [Issue #587](https://github.com/json-c/json-c/issues/587) - Store the hashValue to avoid repeating the hash calculation during the hash resize.
* [Issue #612](https://github.com/json-c/json-c/issues/612) - json-c-0.11: Fix CVE-2020-12762 - json-c through 0.14 has an integer overflow and out-of-bounds write ...
* [Issue #620](https://github.com/json-c/json-c/issues/620) - Introduce json_object_new_string_{ext,noalloc}().
* [Issue #624](https://github.com/json-c/json-c/issues/624) - json-c-0.14: Detect broken RDRAND during initialization.
* [Issue #625](https://github.com/json-c/json-c/issues/625) - json-c-0.13.x: Detect broken RDRAND during initialization.
* [Issue #668](https://github.com/json-c/json-c/issues/668) - Memory usage regression due to newlocal() on older FreeBSD releases
* [Issue #676](https://github.com/json-c/json-c/issues/676) - dereferencing type-punned pointer might break strict-aliasing rules [-Werror=strict-aliasing]
* [Issue #677](https://github.com/json-c/json-c/issues/677) - Naming conflict when using both json-c and jansson
* [Issue #679](https://github.com/json-c/json-c/issues/679) - Let json-c be used with obsolete compilers
* [Issue #681](https://github.com/json-c/json-c/issues/681) - json_tokener_parse_ex: `null` (4 bytes) only parses as valid JSON when passed with null terminator (5 bytes). Documentation issue?
* [Issue #686](https://github.com/json-c/json-c/issues/686) - Remove dependency on libM::getrandom
* [Issue #687](https://github.com/json-c/json-c/issues/687) - Does not build on Apple Silicon M1
* [Issue #688](https://github.com/json-c/json-c/issues/688) - json-c-0.15-nodoc.tar.gz build fails
* [Issue #702](https://github.com/json-c/json-c/issues/702) - json_patch: add first implementation only with patch application
* [Issue #704](https://github.com/json-c/json-c/issues/704) - add json_object_array_insert_idx() + test-cases + fix json_pointer doc-strings
* [Issue #705](https://github.com/json-c/json-c/issues/705) - segmentation fault on json-c parsing methods in cross compiled target
* [Issue #721](https://github.com/json-c/json-c/issues/721) - cmake test fails with building json-c with icc
* [Issue #730](https://github.com/json-c/json-c/issues/730) - Need a comparison with other JSON libraries in C
* [Issue #733](https://github.com/json-c/json-c/issues/733) - Official release? 1.0?
* [Issue #756](https://github.com/json-c/json-c/issues/756) - Question: Is there any way to build this with Gnu Make?
* [Issue #757](https://github.com/json-c/json-c/issues/757) - json_object_from_fd_ex: fail if file is too large
* [Issue #759](https://github.com/json-c/json-c/issues/759) - json_tokener_parse_ex: handle out of memory errors
* [Issue #766](https://github.com/json-c/json-c/issues/766) - Some people have trouble with undefined references to arc4random
* [Issue #767](https://github.com/json-c/json-c/issues/767) - How to create a character array using json-c
* [Issue #768](https://github.com/json-c/json-c/issues/768) - commits from May 30, 2022 killed my docker build process
* [Issue #769](https://github.com/json-c/json-c/issues/769) - Issue #768
* [Issue #770](https://github.com/json-c/json-c/issues/770) - json_parse.c:170:13: error: this statement may fall through
* [Issue #771](https://github.com/json-c/json-c/issues/771) - fix fallthough warning
* [Issue #772](https://github.com/json-c/json-c/issues/772) - add JSON_C_TO_STRING_COLOR option
* [Issue #773](https://github.com/json-c/json-c/issues/773) - problem with u_int64_t
* [Issue #774](https://github.com/json-c/json-c/issues/774) - The function add_compile_options was added to CMake version 2.8.12 and later but your minimum is 2.8 which will not work
* [Issue #775](https://github.com/json-c/json-c/issues/775) - list(TRANSFORM ...) is not available prior to CMake 3.12.
* [Issue #776](https://github.com/json-c/json-c/issues/776) - Fix typo
* [Issue #777](https://github.com/json-c/json-c/issues/777) - Don't try to change locale when libc only supports the C locale
* [Issue #778](https://github.com/json-c/json-c/issues/778) - Do not insert newlines when converting empty arrays to json string and JSON_C_TO_STRING_PRETTY is used
* [Issue #779](https://github.com/json-c/json-c/issues/779) - Fix compiling for Android
* [Issue #780](https://github.com/json-c/json-c/issues/780) - Memory Leak when setting empty strings when c_string.pdata is used
* [Issue #781](https://github.com/json-c/json-c/issues/781) - Fix memory leak with emtpy strings in json_object_set_string
* [Issue #782](https://github.com/json-c/json-c/issues/782) - Fix typos found by codespell
* [Issue #783](https://github.com/json-c/json-c/issues/783) - Fix build with clang-15+
* [Issue #784](https://github.com/json-c/json-c/issues/784) - get_time_seed(): silence warning emitted by Coverity Scan static analyzer
* [Issue #786](https://github.com/json-c/json-c/issues/786) - ghpages update was not published for json-c-0.16
* [Issue #787](https://github.com/json-c/json-c/issues/787) - -static linker flag result in building failed
* [Issue #788](https://github.com/json-c/json-c/issues/788) - Clear sensitive information.
* [Issue #789](https://github.com/json-c/json-c/issues/789) - Unnecessary struct declaration and unsafe function usage
* [Issue #790](https://github.com/json-c/json-c/issues/790) - Small update to README file
* [Issue #791](https://github.com/json-c/json-c/issues/791) - json_object_object_foreach not ISO-C compliant
* [Issue #792](https://github.com/json-c/json-c/issues/792) - ` json_object_get_int` does not set `EINVAL` on invalid string
* [Issue #794](https://github.com/json-c/json-c/issues/794) - replaced
* [Issue #796](https://github.com/json-c/json-c/issues/796) - Added Test for get int functions
* [Issue #797](https://github.com/json-c/json-c/issues/797) - make uninstall
* [Issue #798](https://github.com/json-c/json-c/issues/798) - API to deal with enums is missing
* [Issue #799](https://github.com/json-c/json-c/issues/799) - json_object_put: Assertion `jso->_ref_count > 0' failed.
* [Issue #800](https://github.com/json-c/json-c/issues/800) - String converted to scientific notation
* [Issue #801](https://github.com/json-c/json-c/issues/801) - #error You do not have strncasecmp on your system.
* [Issue #802](https://github.com/json-c/json-c/issues/802) - Problem: modern CMake warns about version 2.8
* [Issue #803](https://github.com/json-c/json-c/issues/803) - Problem: confusing error message in snprintf_compat.h
* [Issue #804](https://github.com/json-c/json-c/issues/804) - Problem: cmake 3.25.1 warns about CMP0042 not being set
* [Issue #806](https://github.com/json-c/json-c/issues/806) - The problem is libjson-c.dylib incompatible with OS version
* [Issue #807](https://github.com/json-c/json-c/issues/807) - json simple parse syntax
* [Issue #808](https://github.com/json-c/json-c/issues/808) - iOS Build using cmake fails due to 64 to 32bits conversion precision loss
* [Issue #809](https://github.com/json-c/json-c/issues/809) - Feature request json_object_new_uint()
* [Issue #810](https://github.com/json-c/json-c/issues/810) - docs: update to Internet Standard reference
* [Issue #811](https://github.com/json-c/json-c/issues/811) - dependence on execution character set
* [Issue #812](https://github.com/json-c/json-c/issues/812) - Duplicate symbol when compiling with clang-cl
* [Issue #813](https://github.com/json-c/json-c/issues/813) - Build apps only in project itself.
* [Issue #814](https://github.com/json-c/json-c/issues/814) - Code execution order
* [Issue #816](https://github.com/json-c/json-c/issues/816) - Hi I need to generate libjson-c.so.3 and libjson-c.so.3.0.1, please help with steps
* [Issue #818](https://github.com/json-c/json-c/issues/818) - error: a function declaration without a prototype is deprecated in all versions of C
* [Issue #819](https://github.com/json-c/json-c/issues/819) - build with intel 2023 fails on vasprintf
* [Issue #820](https://github.com/json-c/json-c/issues/820) - ISO C forbids in
* [Issue #821](https://github.com/json-c/json-c/issues/821) - Any release planing for 0.17?
* [Issue #822](https://github.com/json-c/json-c/issues/822) - Added option to disable app build
* [Issue #823](https://github.com/json-c/json-c/issues/823) - Symbol not found during linking stage of libjson-c.so

View File

@@ -167,11 +167,16 @@ JSONC_0.15 {
} JSONC_0.14; } JSONC_0.14;
JSONC_0.16 { JSONC_0.16 {
# global: # No new symbols in 0.16
# ...new symbols here...
} JSONC_0.15; } JSONC_0.15;
JSONC_0.17 { JSONC_0.17 {
# global: global:
# ...new symbols here... json_object_array_insert_idx;
json_patch_apply;
# array_list_insert_idx is intentionally not exported
} JSONC_0.16; } JSONC_0.16;
JSONC_0.18 {
# global:
} JSONC_0.17;

View File

@@ -26,6 +26,7 @@ extern "C" {
#include "json_c_version.h" #include "json_c_version.h"
#include "json_object.h" #include "json_object.h"
#include "json_object_iterator.h" #include "json_object_iterator.h"
@JSON_H_JSON_PATCH@
@JSON_H_JSON_POINTER@ @JSON_H_JSON_POINTER@
#include "json_tokener.h" #include "json_tokener.h"
#include "json_util.h" #include "json_util.h"

View File

@@ -17,11 +17,11 @@ extern "C" {
#endif #endif
#define JSON_C_MAJOR_VERSION 0 #define JSON_C_MAJOR_VERSION 0
#define JSON_C_MINOR_VERSION 16 #define JSON_C_MINOR_VERSION 17
#define JSON_C_MICRO_VERSION 99 #define JSON_C_MICRO_VERSION 99
#define JSON_C_VERSION_NUM \ #define JSON_C_VERSION_NUM \
((JSON_C_MAJOR_VERSION << 16) | (JSON_C_MINOR_VERSION << 8) | JSON_C_MICRO_VERSION) ((JSON_C_MAJOR_VERSION << 16) | (JSON_C_MINOR_VERSION << 8) | JSON_C_MICRO_VERSION)
#define JSON_C_VERSION "0.16.99" #define JSON_C_VERSION "0.17.99"
#ifndef JSON_EXPORT #ifndef JSON_EXPORT
#if defined(_MSC_VER) && defined(JSON_C_DLL) #if defined(_MSC_VER) && defined(JSON_C_DLL)

View File

@@ -13,7 +13,15 @@
#include <inttypes.h> #include <inttypes.h>
#else #else
#ifdef JSON_C_HAVE_STDINT_H
#include <stdint.h> #include <stdint.h>
#else
/* Really only valid for old MS compilers, VS2008 and earlier: */
typedef __int32 int32_t;
typedef unsigned __int32 uint32_t;
typedef __int64 int64_t;
typedef unsigned __int64 uint64_t;
#endif
#define PRId64 "I64d" #define PRId64 "I64d"
#define SCNd64 "I64d" #define SCNd64 "I64d"

View File

@@ -53,6 +53,7 @@
#endif #endif
#endif #endif
const char *json_number_chars = "0123456789.+-eE"; /* Unused, but part of public API, drop for 1.0 */
const char *json_hex_chars = "0123456789abcdefABCDEF"; const char *json_hex_chars = "0123456789abcdefABCDEF";
static void json_object_generic_delete(struct json_object *jso); static void json_object_generic_delete(struct json_object *jso);
@@ -1519,6 +1520,12 @@ int json_object_array_add(struct json_object *jso, struct json_object *val)
return array_list_add(JC_ARRAY(jso)->c_array, val); return array_list_add(JC_ARRAY(jso)->c_array, val);
} }
int json_object_array_insert_idx(struct json_object *jso, size_t idx, struct json_object *val)
{
assert(json_object_get_type(jso) == json_type_array);
return array_list_insert_idx(JC_ARRAY(jso)->c_array, idx, val);
}
int json_object_array_put_idx(struct json_object *jso, size_t idx, struct json_object *val) int json_object_array_put_idx(struct json_object *jso, size_t idx, struct json_object *val)
{ {
assert(json_object_get_type(jso) == json_type_array); assert(json_object_get_type(jso) == json_type_array);

View File

@@ -172,10 +172,10 @@ JSON_EXPORT struct json_object *json_object_get(struct json_object *obj);
* is a member of (unless you know you've called `json_object_get(obj)` to * is a member of (unless you know you've called `json_object_get(obj)` to
* explicitly increment the refcount). * explicitly increment the refcount).
* *
* NULL may be passed, which which case this is a no-op. * NULL may be passed, in which case this is a no-op.
* *
* @param obj the json_object instance * @param obj the json_object instance
* @returns 1 if the object was freed. * @returns 1 if the object was freed, 0 if only the refcount was decremented
* @see json_object_get() * @see json_object_get()
*/ */
JSON_EXPORT int json_object_put(struct json_object *obj); JSON_EXPORT int json_object_put(struct json_object *obj);
@@ -191,6 +191,7 @@ JSON_EXPORT int json_object_put(struct json_object *obj);
json_type_object, json_type_object,
json_type_array, json_type_array,
json_type_string json_type_string
* @returns 1 if the object is of the specified type, 0 otherwise
*/ */
JSON_EXPORT int json_object_is_type(const struct json_object *obj, enum json_type type); JSON_EXPORT int json_object_is_type(const struct json_object *obj, enum json_type type);
@@ -458,9 +459,9 @@ JSON_EXPORT struct json_object *json_object_object_get(const struct json_object
* associated with the given field name. * associated with the given field name.
* *
* It is safe to pass a NULL value. * It is safe to pass a NULL value.
* @returns whether or not the key exists * @returns 1 if the key exists, 0 otherwise
*/ */
JSON_EXPORT json_bool json_object_object_get_ex(const struct json_object *obj, const char *key, JSON_EXPORT int json_object_object_get_ex(const struct json_object *obj, const char *key,
struct json_object **value); struct json_object **value);
/** Delete the given json_object field /** Delete the given json_object field
@@ -560,7 +561,7 @@ JSON_EXPORT struct array_list *json_object_get_array(const struct json_object *o
/** Get the length of a json_object of type json_type_array /** Get the length of a json_object of type json_type_array
* @param obj the json_object instance * @param obj the json_object instance
* @returns an int * @returns the length of the array
*/ */
JSON_EXPORT size_t json_object_array_length(const struct json_object *obj); JSON_EXPORT size_t json_object_array_length(const struct json_object *obj);
@@ -622,6 +623,25 @@ JSON_EXPORT int json_object_array_add(struct json_object *obj, struct json_objec
JSON_EXPORT int json_object_array_put_idx(struct json_object *obj, size_t idx, JSON_EXPORT int json_object_array_put_idx(struct json_object *obj, size_t idx,
struct json_object *val); struct json_object *val);
/** Insert an element at a specified index in an array (a json_object of type json_type_array)
*
* The reference count will *not* be incremented. This is to make adding
* fields to objects in code more compact. If you want to retain a reference
* to an added object you must wrap the passed object with json_object_get
*
* The array size will be automatically be expanded to the size of the
* index if the index is larger than the current size.
* If the index is within the existing array limits, then the element will be
* inserted and all elements will be shifted. This is the only difference between
* this function and json_object_array_put_idx().
*
* @param obj the json_object instance
* @param idx the index to insert the element at
* @param val the json_object to be added
*/
JSON_EXPORT int json_object_array_insert_idx(struct json_object *obj, size_t idx,
struct json_object *val);
/** Get the element at specified index of array `obj` (which must be a json_object of type json_type_array) /** Get the element at specified index of array `obj` (which must be a json_object of type json_type_array)
* *
* *No* reference counts will be changed, and ownership of the returned * *No* reference counts will be changed, and ownership of the returned
@@ -1023,7 +1043,7 @@ JSON_EXPORT struct json_object *json_object_new_null(void);
* *
* @param obj1 the first json_object instance * @param obj1 the first json_object instance
* @param obj2 the second json_object instance * @param obj2 the second json_object instance
* @returns whether both objects are equal or not * @returns 1 if both objects are equal, 0 otherwise
*/ */
JSON_EXPORT int json_object_equal(struct json_object *obj1, struct json_object *obj2); JSON_EXPORT int json_object_equal(struct json_object *obj1, struct json_object *obj2);

332
json_patch.c Normal file
View File

@@ -0,0 +1,332 @@
/*
* Copyright (c) 2021 Alexandru Ardelean.
* Copyright (c) 2023 Eric Hawicz
*
* This is free software; you can redistribute it and/or modify
* it under the terms of the MIT license. See COPYING for details.
*/
#include "config.h"
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include "json_patch.h"
#include "json_object_private.h"
#include "json_pointer_private.h"
#include <limits.h>
#ifndef SIZE_T_MAX
#if SIZEOF_SIZE_T == SIZEOF_INT
#define SIZE_T_MAX UINT_MAX
#elif SIZEOF_SIZE_T == SIZEOF_LONG
#define SIZE_T_MAX ULONG_MAX
#elif SIZEOF_SIZE_T == SIZEOF_LONG_LONG
#define SIZE_T_MAX ULLONG_MAX
#else
#error Unable to determine size of size_t
#endif
#endif
#define _set_err(_errval, _errmsg) do { \
patch_error->errno_code = (_errval); \
patch_error->errmsg = (_errmsg); \
errno = 0; /* To avoid confusion */ \
} while (0)
#define _set_err_from_ptrget(_errval, _fieldname) do { \
patch_error->errno_code = (_errval); \
patch_error->errmsg = (_errval) == ENOENT ? \
"Did not find element referenced by " _fieldname " field" : \
"Invalid " _fieldname " field"; \
errno = 0; /* To avoid confusion */ \
} while(0)
/**
* JavaScript Object Notation (JSON) Patch
* RFC 6902 - https://tools.ietf.org/html/rfc6902
*/
static int json_patch_apply_test(struct json_object **res,
struct json_object *patch_elem,
const char *path, struct json_patch_error *patch_error)
{
struct json_object *value1, *value2;
if (!json_object_object_get_ex(patch_elem, "value", &value1)) {
_set_err(EINVAL, "Patch object does not contain a 'value' field");
return -1;
}
if (json_pointer_get(*res, path, &value2))
{
_set_err_from_ptrget(errno, "path");
return -1;
}
if (!json_object_equal(value1, value2)) {
_set_err(ENOENT, "Value of element referenced by 'path' field did not match 'value' field");
return -1;
}
return 0;
}
static int __json_patch_apply_remove(struct json_pointer_get_result *jpres)
{
if (json_object_is_type(jpres->parent, json_type_array)) {
return json_object_array_del_idx(jpres->parent, jpres->index_in_parent, 1);
} else if (jpres->parent && jpres->key_in_parent) {
json_object_object_del(jpres->parent, jpres->key_in_parent);
return 0;
} else {
// We're removing the root object
(void)json_object_put(jpres->obj);
jpres->obj = NULL;
return 0;
}
}
static int json_patch_apply_remove(struct json_object **res, const char *path, struct json_patch_error *patch_error)
{
struct json_pointer_get_result jpres;
int rc;
if (json_pointer_get_internal(*res, path, &jpres))
{
_set_err_from_ptrget(errno, "path");
return -1;
}
rc = __json_patch_apply_remove(&jpres);
if (rc < 0)
_set_err(EINVAL, "Unable to remove path referenced by 'path' field");
// This means we removed and freed the root object, i.e. *res
if (jpres.parent == NULL)
*res = NULL;
return rc;
}
// callback for json_pointer_set_with_array_cb()
static int json_object_array_insert_idx_cb(struct json_object *parent, size_t idx,
struct json_object *value, void *priv)
{
int rc;
int *add = priv;
if (idx > json_object_array_length(parent))
{
// Note: will propagate back out through json_pointer_set_with_array_cb()
errno = EINVAL;
return -1;
}
if (*add)
rc = json_object_array_insert_idx(parent, idx, value);
else
rc = json_object_array_put_idx(parent, idx, value);
if (rc < 0)
errno = EINVAL;
return rc;
}
static int json_patch_apply_add_replace(struct json_object **res,
struct json_object *patch_elem,
const char *path, int add, struct json_patch_error *patch_error)
{
struct json_object *value;
int rc;
if (!json_object_object_get_ex(patch_elem, "value", &value)) {
_set_err(EINVAL, "Patch object does not contain a 'value' field");
return -1;
}
/* if this is a replace op, then we need to make sure it exists before replacing */
if (!add && json_pointer_get(*res, path, NULL)) {
_set_err_from_ptrget(errno, "path");
return -1;
}
rc = json_pointer_set_with_array_cb(res, path, json_object_get(value),
json_object_array_insert_idx_cb, &add);
if (rc)
{
_set_err(errno, "Failed to set value at path referenced by 'path' field");
json_object_put(value);
}
return rc;
}
// callback for json_pointer_set_with_array_cb()
static int json_object_array_move_cb(struct json_object *parent, size_t idx,
struct json_object *value, void *priv)
{
int rc;
struct json_pointer_get_result *from = priv;
size_t len = json_object_array_length(parent);
/**
* If it's the same array parent, it means that we removed
* and element from it, so the length is temporarily reduced
* by 1, which means that if we try to move an element to
* the last position, we need to check the current length + 1
*/
if (parent == from->parent)
len++;
if (idx > len)
{
// Note: will propagate back out through json_pointer_set_with_array_cb()
errno = EINVAL;
return -1;
}
rc = json_object_array_insert_idx(parent, idx, value);
if (rc < 0)
errno = EINVAL;
return rc;
}
static int json_patch_apply_move_copy(struct json_object **res,
struct json_object *patch_elem,
const char *path, int move, struct json_patch_error *patch_error)
{
json_pointer_array_set_cb array_set_cb;
struct json_pointer_get_result from;
struct json_object *jfrom;
const char *from_s;
size_t from_s_len;
int rc;
if (!json_object_object_get_ex(patch_elem, "from", &jfrom)) {
_set_err(EINVAL, "Patch does not contain a 'from' field");
return -1;
}
from_s = json_object_get_string(jfrom);
from_s_len = strlen(from_s);
if (strncmp(from_s, path, from_s_len) == 0) {
/**
* If lengths match, it's a noop, if they don't,
* then we're trying to move a parent under a child
* which is not allowed as per RFC 6902 section 4.4
* The "from" location MUST NOT be a proper prefix of the "path"
* location; i.e., a location cannot be moved into one of its children.
*/
if (from_s_len == strlen(path))
return 0;
_set_err(EINVAL, "Invalid attempt to move parent under a child");
return -1;
}
rc = json_pointer_get_internal(*res, from_s, &from);
if (rc)
{
_set_err_from_ptrget(errno, "from");
return rc;
}
// Note: it's impossible for json_pointer to find the root obj, due
// to the path check above, so from.parent is guaranteed non-NULL
json_object_get(from.obj);
if (!move) {
array_set_cb = json_object_array_insert_idx_cb;
} else {
rc = __json_patch_apply_remove(&from);
if (rc < 0) {
json_object_put(from.obj);
return rc;
}
array_set_cb = json_object_array_move_cb;
}
rc = json_pointer_set_with_array_cb(res, path, from.obj, array_set_cb, &from);
if (rc)
{
_set_err(errno, "Failed to set value at path referenced by 'path' field");
json_object_put(from.obj);
}
return rc;
}
int json_patch_apply(struct json_object *copy_from, struct json_object *patch,
struct json_object **base, struct json_patch_error *patch_error)
{
size_t ii;
int rc = 0;
struct json_patch_error placeholder;
if (!patch_error)
patch_error = &placeholder;
patch_error->patch_failure_idx = SIZE_T_MAX;
patch_error->errno_code = 0;
if (base == NULL||
(*base == NULL && copy_from == NULL) ||
(*base != NULL && copy_from != NULL))
{
_set_err(EFAULT, "Exactly one of *base or copy_from must be non-NULL");
return -1;
}
if (!json_object_is_type(patch, json_type_array)) {
_set_err(EFAULT, "Patch object is not of type json_type_array");
return -1;
}
if (copy_from != NULL)
{
if (json_object_deep_copy(copy_from, base, NULL) < 0)
{
_set_err(ENOMEM, "Unable to copy copy_from using json_object_deep_copy()");
return -1;
}
}
/* Go through all operations ; apply them on res */
for (ii = 0; ii < json_object_array_length(patch); ii++) {
struct json_object *jop, *jpath;
struct json_object *patch_elem = json_object_array_get_idx(patch, ii);
const char *op, *path;
patch_error->patch_failure_idx = ii;
if (!json_object_object_get_ex(patch_elem, "op", &jop)) {
_set_err(EINVAL, "Patch object does not contain 'op' field");
return -1;
}
op = json_object_get_string(jop);
if (!json_object_object_get_ex(patch_elem, "path", &jpath)) {
_set_err(EINVAL, "Patch object does not contain 'path' field");
return -1;
}
path = json_object_get_string(jpath); // Note: empty string is ok!
if (!strcmp(op, "test"))
rc = json_patch_apply_test(base, patch_elem, path, patch_error);
else if (!strcmp(op, "remove"))
rc = json_patch_apply_remove(base, path, patch_error);
else if (!strcmp(op, "add"))
rc = json_patch_apply_add_replace(base, patch_elem, path, 1, patch_error);
else if (!strcmp(op, "replace"))
rc = json_patch_apply_add_replace(base, patch_elem, path, 0, patch_error);
else if (!strcmp(op, "move"))
rc = json_patch_apply_move_copy(base, patch_elem, path, 1, patch_error);
else if (!strcmp(op, "copy"))
rc = json_patch_apply_move_copy(base, patch_elem, path, 0, patch_error);
else {
_set_err(EINVAL, "Patch object has invalid 'op' field");
return -1;
}
if (rc < 0)
break;
}
return rc;
}

80
json_patch.h Normal file
View File

@@ -0,0 +1,80 @@
/*
* Copyright (c) 2021 Alexadru Ardelean.
*
* This is free software; you can redistribute it and/or modify
* it under the terms of the MIT license. See COPYING for details.
*
*/
/**
* @file
* @brief JSON Patch (RFC 6902) implementation for manipulating JSON objects
*/
#ifndef _json_patch_h_
#define _json_patch_h_
#include "json_pointer.h"
#ifdef __cplusplus
extern "C" {
#endif
/**
* Details of an error that occurred during json_patch_apply()
*/
struct json_patch_error {
/**
* An errno value indicating what kind of error occurred.
* Possible values include:
* - ENOENT - A path referenced in the operation does not exist.
* - EINVAL - An invalid operation or with invalid path was attempted
* - ENOMEM - Unable to allocate memory
* - EFAULT - Invalid arguments were passed to json_patch_apply()
* (i.e. a C API error, vs. a data error like EINVAL)
*/
int errno_code;
/**
* The index into the patch array of the operation that failed,
* or SIZE_T_MAX for overall errors.
*/
size_t patch_failure_idx;
/**
* A human readable error message.
* Allocated from static storage, does not need to be freed.
*/
const char *errmsg;
};
/**
* Apply the JSON patch to the base object.
* The patch object must be formatted as per RFC 6902, i.e.
* a json_type_array containing patch operations.
* If the patch is not correctly formatted, an error will
* be returned.
*
* The json_object at *base will be modified in place.
* Exactly one of *base or copy_from must be non-NULL.
* If *base is NULL, a new copy of copy_from will allocated and populated
* using json_object_deep_copy(). In this case json_object_put() _must_ be
* used to free *base even if the overall patching operation fails.
*
* If anything fails during patching a negative value will be returned,
* and patch_error (if non-NULL) will be populated with error details.
*
* @param base a pointer to the JSON object which to patch
* @param patch the JSON object that describes the patch to be applied
* @param copy_from a JSON object to copy to *base
* @param patch_error optional, details about errors
*
* @return negative if an error (or not found), or 0 if patch completely applied
*/
JSON_EXPORT int json_patch_apply(struct json_object *copy_from, struct json_object *patch,
struct json_object **base, struct json_patch_error *patch_error);
#ifdef __cplusplus
}
#endif
#endif

View File

@@ -15,7 +15,9 @@
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
#include "json_object_private.h"
#include "json_pointer.h" #include "json_pointer.h"
#include "json_pointer_private.h"
#include "strdup_compat.h" #include "strdup_compat.h"
#include "vasprintf_compat.h" #include "vasprintf_compat.h"
@@ -41,10 +43,9 @@ static void string_replace_all_occurrences_with_char(char *s, const char *occur,
} }
} }
static int is_valid_index(struct json_object *jo, const char *path, size_t *idx) static int is_valid_index(const char *path, size_t *idx)
{ {
size_t i, len = strlen(path); size_t i, len = strlen(path);
long int idx_val = -1;
/* this code-path optimizes a bit, for when we reference the 0-9 index range /* this code-path optimizes a bit, for when we reference the 0-9 index range
* in a JSON array and because leading zeros not allowed * in a JSON array and because leading zeros not allowed
*/ */
@@ -53,7 +54,7 @@ static int is_valid_index(struct json_object *jo, const char *path, size_t *idx)
if (is_plain_digit(path[0])) if (is_plain_digit(path[0]))
{ {
*idx = (path[0] - '0'); *idx = (path[0] - '0');
goto check_oob; return 1;
} }
errno = EINVAL; errno = EINVAL;
return 0; return 0;
@@ -74,34 +75,27 @@ static int is_valid_index(struct json_object *jo, const char *path, size_t *idx)
} }
} }
idx_val = strtol(path, NULL, 10); // We know it's all digits, so the only error case here is overflow,
if (idx_val < 0) // but ULLONG_MAX will be longer than any array length so that's ok.
{ *idx = strtoull(path, NULL, 10);
errno = EINVAL;
return 0;
}
*idx = idx_val;
check_oob:
len = json_object_array_length(jo);
if (*idx >= len)
{
errno = ENOENT;
return 0;
}
return 1; return 1;
} }
static int json_pointer_get_single_path(struct json_object *obj, char *path, static int json_pointer_get_single_path(struct json_object *obj, char *path,
struct json_object **value) struct json_object **value, size_t *idx)
{ {
if (json_object_is_type(obj, json_type_array)) if (json_object_is_type(obj, json_type_array))
{ {
size_t idx; if (!is_valid_index(path, idx))
if (!is_valid_index(obj, path, &idx))
return -1; return -1;
obj = json_object_array_get_idx(obj, idx); if (*idx >= json_object_array_length(obj))
{
errno = ENOENT;
return -1;
}
obj = json_object_array_get_idx(obj, *idx);
if (obj) if (obj)
{ {
if (value) if (value)
@@ -126,8 +120,15 @@ static int json_pointer_get_single_path(struct json_object *obj, char *path,
return 0; return 0;
} }
static int json_object_array_put_idx_cb(struct json_object *parent, size_t idx,
struct json_object *value, void *priv)
{
return json_object_array_put_idx(parent, idx, value);
}
static int json_pointer_set_single_path(struct json_object *parent, const char *path, static int json_pointer_set_single_path(struct json_object *parent, const char *path,
struct json_object *value) struct json_object *value,
json_pointer_array_set_cb array_set_cb, void *priv)
{ {
if (json_object_is_type(parent, json_type_array)) if (json_object_is_type(parent, json_type_array))
{ {
@@ -135,9 +136,9 @@ static int json_pointer_set_single_path(struct json_object *parent, const char *
/* RFC (Chapter 4) states that '-' may be used to add new elements to an array */ /* RFC (Chapter 4) states that '-' may be used to add new elements to an array */
if (path[0] == '-' && path[1] == '\0') if (path[0] == '-' && path[1] == '\0')
return json_object_array_add(parent, value); return json_object_array_add(parent, value);
if (!is_valid_index(parent, path, &idx)) if (!is_valid_index(path, &idx))
return -1; return -1;
return json_object_array_put_idx(parent, idx, value); return array_set_cb(parent, idx, value, priv);
} }
/* path replacements should have been done in json_pointer_get_single_path(), /* path replacements should have been done in json_pointer_get_single_path(),
@@ -153,9 +154,11 @@ static int json_pointer_set_single_path(struct json_object *parent, const char *
return -1; return -1;
} }
static int json_pointer_get_recursive(struct json_object *obj, char *path, static int json_pointer_result_get_recursive(struct json_object *obj, char *path,
struct json_object **value) struct json_pointer_get_result *res)
{ {
struct json_object *parent_obj = obj;
size_t idx = 0;
char *endp; char *endp;
int rc; int rc;
@@ -172,24 +175,47 @@ static int json_pointer_get_recursive(struct json_object *obj, char *path,
*endp = '\0'; *endp = '\0';
/* If we err-ed here, return here */ /* If we err-ed here, return here */
if ((rc = json_pointer_get_single_path(obj, path, &obj))) if ((rc = json_pointer_get_single_path(obj, path, &obj, &idx)))
return rc; return rc;
if (endp) if (endp)
{ {
/* Put the slash back, so that the sanity check passes on next recursion level */ /* Put the slash back, so that the sanity check passes on next recursion level */
*endp = '/'; *endp = '/';
return json_pointer_get_recursive(obj, endp, value); return json_pointer_result_get_recursive(obj, endp, res);
} }
/* We should be at the end of the recursion here */ /* We should be at the end of the recursion here */
if (value) if (res) {
*value = obj; res->parent = parent_obj;
res->obj = obj;
if (json_object_is_type(res->parent, json_type_array))
res->index_in_parent = idx;
else
res->key_in_parent = path;
}
return 0; return 0;
} }
int json_pointer_get(struct json_object *obj, const char *path, struct json_object **res) static int json_pointer_object_get_recursive(struct json_object *obj, char *path,
struct json_object **value)
{
struct json_pointer_get_result res;
int rc;
rc = json_pointer_result_get_recursive(obj, path, &res);
if (rc)
return rc;
if (value)
*value = res.obj;
return 0;
}
int json_pointer_get_internal(struct json_object *obj, const char *path,
struct json_pointer_get_result *res)
{ {
char *path_copy = NULL; char *path_copy = NULL;
int rc; int rc;
@@ -202,8 +228,10 @@ int json_pointer_get(struct json_object *obj, const char *path, struct json_obje
if (path[0] == '\0') if (path[0] == '\0')
{ {
if (res) res->parent = NULL;
*res = obj; res->obj = obj;
res->key_in_parent = NULL;
res->index_in_parent = UINT32_MAX;
return 0; return 0;
} }
@@ -213,12 +241,30 @@ int json_pointer_get(struct json_object *obj, const char *path, struct json_obje
errno = ENOMEM; errno = ENOMEM;
return -1; return -1;
} }
rc = json_pointer_get_recursive(obj, path_copy, res); rc = json_pointer_result_get_recursive(obj, path_copy, res);
/* re-map the path string to the const-path string */
if (rc == 0 && json_object_is_type(res->parent, json_type_object) && res->key_in_parent)
res->key_in_parent = path + (res->key_in_parent - path_copy);
free(path_copy); free(path_copy);
return rc; return rc;
} }
int json_pointer_get(struct json_object *obj, const char *path, struct json_object **res)
{
struct json_pointer_get_result jpres;
int rc;
rc = json_pointer_get_internal(obj, path, &jpres);
if (rc)
return rc;
if (res)
*res = jpres.obj;
return 0;
}
int json_pointer_getf(struct json_object *obj, struct json_object **res, const char *path_fmt, ...) int json_pointer_getf(struct json_object *obj, struct json_object **res, const char *path_fmt, ...)
{ {
char *path_copy = NULL; char *path_copy = NULL;
@@ -245,14 +291,16 @@ int json_pointer_getf(struct json_object *obj, struct json_object **res, const c
goto out; goto out;
} }
rc = json_pointer_get_recursive(obj, path_copy, res); rc = json_pointer_object_get_recursive(obj, path_copy, res);
out: out:
free(path_copy); free(path_copy);
return rc; return rc;
} }
int json_pointer_set(struct json_object **obj, const char *path, struct json_object *value) int json_pointer_set_with_array_cb(struct json_object **obj, const char *path,
struct json_object *value,
json_pointer_array_set_cb array_set_cb, void *priv)
{ {
const char *endp; const char *endp;
char *path_copy = NULL; char *path_copy = NULL;
@@ -282,7 +330,7 @@ int json_pointer_set(struct json_object **obj, const char *path, struct json_obj
if ((endp = strrchr(path, '/')) == path) if ((endp = strrchr(path, '/')) == path)
{ {
path++; path++;
return json_pointer_set_single_path(*obj, path, value); return json_pointer_set_single_path(*obj, path, value, array_set_cb, priv);
} }
/* pass a working copy to the recursive call */ /* pass a working copy to the recursive call */
@@ -292,14 +340,19 @@ int json_pointer_set(struct json_object **obj, const char *path, struct json_obj
return -1; return -1;
} }
path_copy[endp - path] = '\0'; path_copy[endp - path] = '\0';
rc = json_pointer_get_recursive(*obj, path_copy, &set); rc = json_pointer_object_get_recursive(*obj, path_copy, &set);
free(path_copy); free(path_copy);
if (rc) if (rc)
return rc; return rc;
endp++; endp++;
return json_pointer_set_single_path(set, endp, value); return json_pointer_set_single_path(set, endp, value, array_set_cb, priv);
}
int json_pointer_set(struct json_object **obj, const char *path, struct json_object *value)
{
return json_pointer_set_with_array_cb(obj, path, value, json_object_array_put_idx_cb, NULL);
} }
int json_pointer_setf(struct json_object **obj, struct json_object *value, const char *path_fmt, int json_pointer_setf(struct json_object **obj, struct json_object *value, const char *path_fmt,
@@ -347,14 +400,15 @@ int json_pointer_setf(struct json_object **obj, struct json_object *value, const
} }
*endp = '\0'; *endp = '\0';
rc = json_pointer_get_recursive(*obj, path_copy, &set); rc = json_pointer_object_get_recursive(*obj, path_copy, &set);
if (rc) if (rc)
goto out; goto out;
set_single_path: set_single_path:
endp++; endp++;
rc = json_pointer_set_single_path(set, endp, value); rc = json_pointer_set_single_path(set, endp, value,
json_object_array_put_idx_cb, NULL);
out: out:
free(path_copy); free(path_copy);
return rc; return rc;

View File

@@ -32,11 +32,6 @@ extern "C" {
* Internally, this is equivalent to doing a series of 'json_object_object_get()' * Internally, this is equivalent to doing a series of 'json_object_object_get()'
* and 'json_object_array_get_idx()' along the given 'path'. * and 'json_object_array_get_idx()' along the given 'path'.
* *
* Note that the 'path' string supports 'printf()' type arguments, so, whatever
* is added after the 'res' param will be treated as an argument for 'path'
* Example: json_pointer_get(obj, "/foo/%d/%s", &res, 0, bar)
* This means, that you need to escape '%' with '%%' (just like in printf())
*
* @param obj the json_object instance/tree from where to retrieve sub-objects * @param obj the json_object instance/tree from where to retrieve sub-objects
* @param path a (RFC6901) string notation for the sub-object to retrieve * @param path a (RFC6901) string notation for the sub-object to retrieve
* @param res a pointer that stores a reference to the json_object * @param res a pointer that stores a reference to the json_object
@@ -50,7 +45,9 @@ JSON_EXPORT int json_pointer_get(struct json_object *obj, const char *path,
/** /**
* This is a variant of 'json_pointer_get()' that supports printf() style arguments. * This is a variant of 'json_pointer_get()' that supports printf() style arguments.
* *
* Example: json_pointer_getf(obj, res, "/foo/%d/%s", 0, bak) * Variable arguments go after the 'path_fmt' parameter.
*
* Example: json_pointer_getf(obj, res, "/foo/%d/%s", 0, "bar")
* This also means that you need to escape '%' with '%%' (just like in printf()) * This also means that you need to escape '%' with '%%' (just like in printf())
* *
* Please take into consideration all recommended 'printf()' format security * Please take into consideration all recommended 'printf()' format security
@@ -84,11 +81,6 @@ JSON_EXPORT int json_pointer_getf(struct json_object *obj, struct json_object **
* That also implies that 'json_pointer_set()' does not do any refcount incrementing. * That also implies that 'json_pointer_set()' does not do any refcount incrementing.
* (Just that single decrement that was mentioned above). * (Just that single decrement that was mentioned above).
* *
* Note that the 'path' string supports 'printf()' type arguments, so, whatever
* is added after the 'value' param will be treated as an argument for 'path'
* Example: json_pointer_set(obj, "/foo/%d/%s", value, 0, bak)
* This means, that you need to escape '%' with '%%' (just like in printf())
*
* @param obj the json_object instance/tree to which to add a sub-object * @param obj the json_object instance/tree to which to add a sub-object
* @param path a (RFC6901) string notation for the sub-object to set in the tree * @param path a (RFC6901) string notation for the sub-object to set in the tree
* @param value object to set at path * @param value object to set at path
@@ -101,7 +93,9 @@ JSON_EXPORT int json_pointer_set(struct json_object **obj, const char *path,
/** /**
* This is a variant of 'json_pointer_set()' that supports printf() style arguments. * This is a variant of 'json_pointer_set()' that supports printf() style arguments.
* *
* Example: json_pointer_setf(obj, value, "/foo/%d/%s", 0, bak) * Variable arguments go after the 'path_fmt' parameter.
*
* Example: json_pointer_setf(obj, value, "/foo/%d/%s", 0, "bar")
* This also means that you need to escape '%' with '%%' (just like in printf()) * This also means that you need to escape '%' with '%%' (just like in printf())
* *
* Please take into consideration all recommended 'printf()' format security * Please take into consideration all recommended 'printf()' format security

43
json_pointer_private.h Normal file
View File

@@ -0,0 +1,43 @@
/*
* Copyright (c) 2023 Eric Hawicz
*
* This library is free software; you can redistribute it and/or modify
* it under the terms of the MIT license. See COPYING for details.
*/
/**
* @file
* @brief Do not use, json-c internal, may be changed or removed at any time.
*/
#ifndef _json_pointer_private_h_
#define _json_pointer_private_h_
#ifdef __cplusplus
extern "C" {
#endif
struct json_pointer_get_result {
struct json_object *parent;
struct json_object *obj;
// The key of the found object; only valid when parent is json_type_object
// Caution: re-uses tail end of the `path` argument to json_pointer_get_internal
const char *key_in_parent;
// the index of the found object; only valid when parent is json_type_array
uint32_t index_in_parent;
};
int json_pointer_get_internal(struct json_object *obj, const char *path,
struct json_pointer_get_result *res);
typedef int(*json_pointer_array_set_cb)(json_object *parent, size_t idx,
json_object *value, void *priv);
int json_pointer_set_with_array_cb(struct json_object **obj, const char *path,
struct json_object *value,
json_pointer_array_set_cb array_set_cb, void *priv);
#ifdef __cplusplus
}
#endif
#endif

View File

@@ -104,7 +104,6 @@ static const char *json_tokener_errors[] = {
"success", "success",
"continue", "continue",
"nesting too deep", "nesting too deep",
"out of memory",
"unexpected end of data", "unexpected end of data",
"unexpected character", "unexpected character",
"null expected", "null expected",
@@ -117,7 +116,8 @@ static const char *json_tokener_errors[] = {
"invalid string sequence", "invalid string sequence",
"expected comment", "expected comment",
"invalid utf-8 string", "invalid utf-8 string",
"buffer size overflow" "buffer size overflow",
"out of memory"
}; };
/* clang-format on */ /* clang-format on */
@@ -226,7 +226,10 @@ struct json_object *json_tokener_parse_verbose(const char *str, enum json_tokene
tok = json_tokener_new(); tok = json_tokener_new();
if (!tok) if (!tok)
{
*error = json_tokener_error_memory;
return NULL; return NULL;
}
obj = json_tokener_parse_ex(tok, str, -1); obj = json_tokener_parse_ex(tok, str, -1);
*error = tok->err; *error = tok->err;
if (tok->err != json_tokener_success if (tok->err != json_tokener_success
@@ -338,12 +341,23 @@ struct json_object *json_tokener_parse_ex(struct json_tokener *tok, const char *
#ifdef HAVE_USELOCALE #ifdef HAVE_USELOCALE
{ {
locale_t duploc = duplocale(oldlocale); locale_t duploc = duplocale(oldlocale);
if (duploc == NULL && errno == ENOMEM)
{
tok->err = json_tokener_error_memory;
return NULL;
}
newloc = newlocale(LC_NUMERIC_MASK, "C", duploc); newloc = newlocale(LC_NUMERIC_MASK, "C", duploc);
if (newloc == NULL) if (newloc == NULL)
{ {
tok->err = json_tokener_error_memory;
freelocale(duploc); freelocale(duploc);
return NULL; return NULL;
} }
#ifdef NEWLOCALE_NEEDS_FREELOCALE
// Older versions of FreeBSD (<12.4) don't free the locale
// passed to newlocale(), so do it here
freelocale(duploc);
#endif
uselocale(newloc); uselocale(newloc);
} }
#elif defined(HAVE_SETLOCALE) #elif defined(HAVE_SETLOCALE)
@@ -354,7 +368,10 @@ struct json_object *json_tokener_parse_ex(struct json_tokener *tok, const char *
{ {
oldlocale = strdup(tmplocale); oldlocale = strdup(tmplocale);
if (oldlocale == NULL) if (oldlocale == NULL)
{
tok->err = json_tokener_error_memory;
return NULL; return NULL;
}
} }
setlocale(LC_NUMERIC, "C"); setlocale(LC_NUMERIC, "C");
} }
@@ -661,6 +678,12 @@ struct json_object *json_tokener_parse_ex(struct json_tokener *tok, const char *
state = json_tokener_state_string_escape; state = json_tokener_state_string_escape;
break; break;
} }
else if ((tok->flags & JSON_TOKENER_STRICT) && c <= 0x1f)
{
// Disallow control characters in strict mode
tok->err = json_tokener_error_parse_string;
goto out;
}
if (!ADVANCE_CHAR(str, tok) || !PEEK_CHAR(c, tok)) if (!ADVANCE_CHAR(str, tok) || !PEEK_CHAR(c, tok))
{ {
printbuf_memappend_checked(tok->pb, case_start, printbuf_memappend_checked(tok->pb, case_start,
@@ -1249,7 +1272,11 @@ struct json_object *json_tokener_parse_ex(struct json_tokener *tok, const char *
goto redo_char; goto redo_char;
case json_tokener_state_object_value_add: case json_tokener_state_object_value_add:
json_object_object_add(current, obj_field_name, obj); if (json_object_object_add(current, obj_field_name, obj) != 0)
{
tok->err = json_tokener_error_memory;
goto out;
}
free(obj_field_name); free(obj_field_name);
obj_field_name = NULL; obj_field_name = NULL;
saved_state = json_tokener_state_object_sep; saved_state = json_tokener_state_object_sep;

View File

@@ -28,7 +28,6 @@ enum json_tokener_error
json_tokener_success, json_tokener_success,
json_tokener_continue, json_tokener_continue,
json_tokener_error_depth, json_tokener_error_depth,
json_tokener_error_memory,
json_tokener_error_parse_eof, json_tokener_error_parse_eof,
json_tokener_error_parse_unexpected, json_tokener_error_parse_unexpected,
json_tokener_error_parse_null, json_tokener_error_parse_null,
@@ -41,7 +40,8 @@ enum json_tokener_error
json_tokener_error_parse_string, json_tokener_error_parse_string,
json_tokener_error_parse_comment, json_tokener_error_parse_comment,
json_tokener_error_parse_utf8_string, json_tokener_error_parse_utf8_string,
json_tokener_error_size json_tokener_error_size, /* A string longer than INT32_MAX was passed as input */
json_tokener_error_memory /* Failed to allocate memory */
}; };
/** /**
@@ -230,7 +230,7 @@ JSON_EXPORT void json_tokener_reset(struct json_tokener *tok);
JSON_EXPORT struct json_object *json_tokener_parse(const char *str); JSON_EXPORT struct json_object *json_tokener_parse(const char *str);
/** /**
* Parser a json_object out of the string `str`, but if it fails * Parse a json_object out of the string `str`, but if it fails
* return the error in `*error`. * return the error in `*error`.
* @see json_tokener_parse() * @see json_tokener_parse()
* @see json_tokener_parse_ex() * @see json_tokener_parse_ex()

View File

@@ -37,13 +37,13 @@
#include <unistd.h> #include <unistd.h>
#endif /* HAVE_UNISTD_H */ #endif /* HAVE_UNISTD_H */
#ifdef WIN32 #ifdef _WIN32
#define WIN32_LEAN_AND_MEAN #define WIN32_LEAN_AND_MEAN
#include <io.h> #include <io.h>
#include <windows.h> #include <windows.h>
#endif /* defined(WIN32) */ #endif /* defined(_WIN32) */
#if !defined(HAVE_OPEN) && defined(WIN32) #if !defined(HAVE_OPEN) && defined(_WIN32)
#define open _open #define open _open
#endif #endif

View File

@@ -229,6 +229,7 @@ static int get_dev_random_seed(int *seed)
if ((buf.st_mode & S_IFCHR) == 0) if ((buf.st_mode & S_IFCHR) == 0)
return -1; return -1;
/* coverity[toctou] */
int fd = open(dev_random_file, O_RDONLY); int fd = open(dev_random_file, O_RDONLY);
if (fd < 0) if (fd < 0)
{ {
@@ -253,7 +254,7 @@ static int get_dev_random_seed(int *seed)
/* get_cryptgenrandom_seed */ /* get_cryptgenrandom_seed */
#ifdef WIN32 #ifdef _WIN32
#define HAVE_CRYPTGENRANDOM 1 #define HAVE_CRYPTGENRANDOM 1

View File

@@ -36,6 +36,6 @@ static int json_c_snprintf(char *str, size_t size, const char *format, ...)
#elif !defined(HAVE_SNPRINTF) /* !HAVE_SNPRINTF */ #elif !defined(HAVE_SNPRINTF) /* !HAVE_SNPRINTF */
#error snprintf is required but was not found #error snprintf is required but was not found
#endif /* !HAVE_SNPRINTF && defined(WIN32) */ #endif /* !HAVE_SNPRINTF */
#endif /* __snprintf_compat_h */ #endif /* __snprintf_compat_h */

View File

@@ -1,4 +1,4 @@
cmake_minimum_required(VERSION 3.1) cmake_minimum_required(VERSION 3.9)
add_executable(test1Formatted test1.c parse_flags.c parse_flags.h) add_executable(test1Formatted test1.c parse_flags.c parse_flags.h)
target_compile_definitions(test1Formatted PRIVATE TEST_FORMATTED=1) target_compile_definitions(test1Formatted PRIVATE TEST_FORMATTED=1)
target_link_libraries(test1Formatted PRIVATE ${PROJECT_NAME}) target_link_libraries(test1Formatted PRIVATE ${PROJECT_NAME})
@@ -39,6 +39,9 @@ set(ALL_TEST_NAMES
if (NOT DISABLE_JSON_POINTER) if (NOT DISABLE_JSON_POINTER)
set(ALL_TEST_NAMES ${ALL_TEST_NAMES} test_json_pointer) set(ALL_TEST_NAMES ${ALL_TEST_NAMES} test_json_pointer)
if (NOT DISABLE_JSON_PATCH)
set(ALL_TEST_NAMES ${ALL_TEST_NAMES} test_json_patch)
endif()
endif() endif()
foreach(TESTNAME ${ALL_TEST_NAMES}) foreach(TESTNAME ${ALL_TEST_NAMES})

View File

@@ -0,0 +1,233 @@
[
{
"comment": "4.1. add with missing object",
"doc": { "q": { "bar": 2 } },
"patch": [ {"op": "add", "path": "/a/b", "value": 1} ],
"error":
"path /a does not exist -- missing objects are not created recursively"
},
{
"comment": "A.1. Adding an Object Member",
"doc": {
"foo": "bar"
},
"patch": [
{ "op": "add", "path": "/baz", "value": "qux" }
],
"expected": {
"baz": "qux",
"foo": "bar"
}
},
{
"comment": "A.2. Adding an Array Element",
"doc": {
"foo": [ "bar", "baz" ]
},
"patch": [
{ "op": "add", "path": "/foo/1", "value": "qux" }
],
"expected": {
"foo": [ "bar", "qux", "baz" ]
}
},
{
"comment": "A.3. Removing an Object Member",
"doc": {
"baz": "qux",
"foo": "bar"
},
"patch": [
{ "op": "remove", "path": "/baz" }
],
"expected": {
"foo": "bar"
}
},
{
"comment": "A.4. Removing an Array Element",
"doc": {
"foo": [ "bar", "qux", "baz" ]
},
"patch": [
{ "op": "remove", "path": "/foo/1" }
],
"expected": {
"foo": [ "bar", "baz" ]
}
},
{
"comment": "A.5. Replacing a Value",
"doc": {
"baz": "qux",
"foo": "bar"
},
"patch": [
{ "op": "replace", "path": "/baz", "value": "boo" }
],
"expected": {
"baz": "boo",
"foo": "bar"
}
},
{
"comment": "A.6. Moving a Value",
"doc": {
"foo": {
"bar": "baz",
"waldo": "fred"
},
"qux": {
"corge": "grault"
}
},
"patch": [
{ "op": "move", "from": "/foo/waldo", "path": "/qux/thud" }
],
"expected": {
"foo": {
"bar": "baz"
},
"qux": {
"corge": "grault",
"thud": "fred"
}
}
},
{
"comment": "A.7. Moving an Array Element",
"doc": {
"foo": [ "all", "grass", "cows", "eat" ]
},
"patch": [
{ "op": "move", "from": "/foo/1", "path": "/foo/3" }
],
"expected": {
"foo": [ "all", "cows", "eat", "grass" ]
}
},
{
"comment": "A.8. Testing a Value: Success",
"doc": {
"baz": "qux",
"foo": [ "a", 2, "c" ]
},
"patch": [
{ "op": "test", "path": "/baz", "value": "qux" },
{ "op": "test", "path": "/foo/1", "value": 2 }
],
"expected": {
"baz": "qux",
"foo": [ "a", 2, "c" ]
}
},
{
"comment": "A.9. Testing a Value: Error",
"doc": {
"baz": "qux"
},
"patch": [
{ "op": "test", "path": "/baz", "value": "bar" }
],
"error": "string not equivalent"
},
{
"comment": "A.10. Adding a nested Member Object",
"doc": {
"foo": "bar"
},
"patch": [
{ "op": "add", "path": "/child", "value": { "grandchild": { } } }
],
"expected": {
"foo": "bar",
"child": {
"grandchild": {
}
}
}
},
{
"comment": "A.11. Ignoring Unrecognized Elements",
"doc": {
"foo":"bar"
},
"patch": [
{ "op": "add", "path": "/baz", "value": "qux", "xyz": 123 }
],
"expected": {
"foo":"bar",
"baz":"qux"
}
},
{
"comment": "A.12. Adding to a Non-existent Target",
"doc": {
"foo": "bar"
},
"patch": [
{ "op": "add", "path": "/baz/bat", "value": "qux" }
],
"error": "add to a non-existent target"
},
{
"comment": "A.13 Invalid JSON Patch Document",
"doc": {
"foo": "bar"
},
"patch": [
{ "op": "add", "path": "/baz", "value": "qux", "op": "remove" }
],
"error_wont_happen_in_jsonc": "operation has two 'op' members",
"error": "Did not find element referenced by path field"
},
{
"comment": "A.14. ~ Escape Ordering",
"doc": {
"/": 9,
"~1": 10
},
"patch": [{"op": "test", "path": "/~01", "value": 10}],
"expected": {
"/": 9,
"~1": 10
}
},
{
"comment": "A.15. Comparing Strings and Numbers",
"doc": {
"/": 9,
"~1": 10
},
"patch": [{"op": "test", "path": "/~01", "value": "10"}],
"error": "number is not equal to string"
},
{
"comment": "A.16. Adding an Array Value",
"doc": {
"foo": ["bar"]
},
"patch": [{ "op": "add", "path": "/foo/-", "value": ["abc", "def"] }],
"expected": {
"foo": ["bar", ["abc", "def"]]
}
}
]

540
tests/json_patch_tests.json Normal file
View File

@@ -0,0 +1,540 @@
[
{ "comment": "empty list, empty docs",
"doc": {},
"patch": [],
"expected": {} },
{ "comment": "empty patch list",
"doc": {"foo": 1},
"patch": [],
"expected": {"foo": 1} },
{ "comment": "rearrangements OK?",
"doc": {"foo": 1, "bar": 2},
"patch": [],
"expected": {"bar":2, "foo": 1} },
{ "comment": "rearrangements OK? How about one level down ... array",
"doc": [{"foo": 1, "bar": 2}],
"patch": [],
"expected": [{"bar":2, "foo": 1}] },
{ "comment": "rearrangements OK? How about one level down...",
"doc": {"foo":{"foo": 1, "bar": 2}},
"patch": [],
"expected": {"foo":{"bar":2, "foo": 1}} },
{ "comment": "add replaces any existing field",
"doc": {"foo": null},
"patch": [{"op": "add", "path": "/foo", "value":1}],
"expected": {"foo": 1} },
{ "comment": "toplevel array",
"doc": [],
"patch": [{"op": "add", "path": "/0", "value": "foo"}],
"expected": ["foo"] },
{ "comment": "toplevel array, no change",
"doc": ["foo"],
"patch": [],
"expected": ["foo"] },
{ "comment": "toplevel object, numeric string",
"doc": {},
"patch": [{"op": "add", "path": "/foo", "value": "1"}],
"expected": {"foo":"1"} },
{ "comment": "toplevel object, integer",
"doc": {},
"patch": [{"op": "add", "path": "/foo", "value": 1}],
"expected": {"foo":1} },
{ "comment": "Toplevel scalar values OK?",
"doc": "foo",
"patch": [{"op": "replace", "path": "", "value": "bar"}],
"expected": "bar"
},
{ "comment": "replace object document with array document?",
"doc": {},
"patch": [{"op": "add", "path": "", "value": []}],
"expected": [] },
{ "comment": "replace array document with object document?",
"doc": [],
"patch": [{"op": "add", "path": "", "value": {}}],
"expected": {} },
{ "comment": "append to root array document?",
"doc": [],
"patch": [{"op": "add", "path": "/-", "value": "hi"}],
"expected": ["hi"] },
{ "comment": "Add, / target",
"doc": {},
"patch": [ {"op": "add", "path": "/", "value":1 } ],
"expected": {"":1} },
{ "comment": "Add, /foo/ deep target (trailing slash)",
"doc": {"foo": {}},
"patch": [ {"op": "add", "path": "/foo/", "value":1 } ],
"expected": {"foo":{"": 1}} },
{ "comment": "Add composite value at top level",
"doc": {"foo": 1},
"patch": [{"op": "add", "path": "/bar", "value": [1, 2]}],
"expected": {"foo": 1, "bar": [1, 2]} },
{ "comment": "Add into composite value",
"doc": {"foo": 1, "baz": [{"qux": "hello"}]},
"patch": [{"op": "add", "path": "/baz/0/foo", "value": "world"}],
"expected": {"foo": 1, "baz": [{"qux": "hello", "foo": "world"}]} },
{ "doc": {"bar": [1, 2]},
"patch": [{"op": "add", "path": "/bar/8", "value": "5"}],
"error": "Out of bounds (upper)" },
{ "doc": {"bar": [1, 2]},
"patch": [{"op": "add", "path": "/bar/-1", "value": "5"}],
"error": "Out of bounds (lower)" },
{ "doc": {"foo": 1},
"patch": [{"op": "add", "path": "/bar", "value": true}],
"expected": {"foo": 1, "bar": true} },
{ "doc": {"foo": 1},
"patch": [{"op": "add", "path": "/bar", "value": false}],
"expected": {"foo": 1, "bar": false} },
{ "doc": {"foo": 1},
"patch": [{"op": "add", "path": "/bar", "value": null}],
"expected": {"foo": 1, "bar": null} },
{ "comment": "0 can be an array index or object element name",
"doc": {"foo": 1},
"patch": [{"op": "add", "path": "/0", "value": "bar"}],
"expected": {"foo": 1, "0": "bar" } },
{ "doc": ["foo"],
"patch": [{"op": "add", "path": "/1", "value": "bar"}],
"expected": ["foo", "bar"] },
{ "doc": ["foo", "sil"],
"patch": [{"op": "add", "path": "/1", "value": "bar"}],
"expected": ["foo", "bar", "sil"] },
{ "doc": ["foo", "sil"],
"patch": [{"op": "add", "path": "/0", "value": "bar"}],
"expected": ["bar", "foo", "sil"] },
{ "comment": "push item to array via last index + 1",
"doc": ["foo", "sil"],
"patch": [{"op":"add", "path": "/2", "value": "bar"}],
"expected": ["foo", "sil", "bar"] },
{ "comment": "add item to array at index > length should fail",
"doc": ["foo", "sil"],
"patch": [{"op":"add", "path": "/3", "value": "bar"}],
"error": "index is greater than number of items in array" },
{ "comment": "test against implementation-specific numeric parsing",
"doc": {"1e0": "foo"},
"patch": [{"op": "test", "path": "/1e0", "value": "foo"}],
"expected": {"1e0": "foo"} },
{ "comment": "test with bad number should fail",
"doc": ["foo", "bar"],
"patch": [{"op": "test", "path": "/1e0", "value": "bar"}],
"error": "test op shouldn't get array element 1" },
{ "doc": ["foo", "sil"],
"patch": [{"op": "add", "path": "/bar", "value": 42}],
"error": "Object operation on array target" },
{ "doc": ["foo", "sil"],
"patch": [{"op": "add", "path": "/1", "value": ["bar", "baz"]}],
"expected": ["foo", ["bar", "baz"], "sil"],
"comment": "value in array add not flattened" },
{ "doc": {"foo": 1, "bar": [1, 2, 3, 4]},
"patch": [{"op": "remove", "path": "/bar"}],
"expected": {"foo": 1} },
{ "doc": {"foo": 1, "baz": [{"qux": "hello"}]},
"patch": [{"op": "remove", "path": "/baz/0/qux"}],
"expected": {"foo": 1, "baz": [{}]} },
{ "doc": {"foo": 1, "baz": [{"qux": "hello"}]},
"patch": [{"op": "replace", "path": "/foo", "value": [1, 2, 3, 4]}],
"expected": {"foo": [1, 2, 3, 4], "baz": [{"qux": "hello"}]} },
{ "doc": {"foo": [1, 2, 3, 4], "baz": [{"qux": "hello"}]},
"patch": [{"op": "replace", "path": "/baz/0/qux", "value": "world"}],
"expected": {"foo": [1, 2, 3, 4], "baz": [{"qux": "world"}]} },
{ "doc": ["foo"],
"patch": [{"op": "replace", "path": "/0", "value": "bar"}],
"expected": ["bar"] },
{ "doc": [""],
"patch": [{"op": "replace", "path": "/0", "value": 0}],
"expected": [0] },
{ "doc": [""],
"patch": [{"op": "replace", "path": "/0", "value": true}],
"expected": [true] },
{ "doc": [""],
"patch": [{"op": "replace", "path": "/0", "value": false}],
"expected": [false] },
{ "doc": [""],
"patch": [{"op": "replace", "path": "/0", "value": null}],
"expected": [null] },
{ "doc": ["foo", "sil"],
"patch": [{"op": "replace", "path": "/1", "value": ["bar", "baz"]}],
"expected": ["foo", ["bar", "baz"]],
"comment": "value in array replace not flattened" },
{ "comment": "replace whole document",
"doc": {"foo": "bar"},
"patch": [{"op": "replace", "path": "", "value": {"baz": "qux"}}],
"expected": {"baz": "qux"} },
{ "comment": "add whole document, null",
"doc": {},
"Note1": "We can't pass null in to json_patch_apply, so start with _something_ and remove it",
"patch": [
{"op": "remove", "path": ""},
{"op": "add", "path": "", "value": {"baz": "qux"}}
],
"expected": {"baz": "qux"} },
{ "comment": "replace whole document, null",
"doc": {},
"Note1": "We can't pass null in to json_patch_apply, so start with _something_ and remove it",
"patch": [
{"op": "remove", "path": ""},
{"op": "replace", "path": "", "value": {"baz": "qux"}}
],
"error": "The spec says the target location must exist, so replacing a null document fails"
},
{ "comment": "remove whole document",
"doc": {"foo": "bar"},
"patch": [{"op": "remove", "path": ""}],
"expected": null },
{ "comment": "remove whole document",
"doc": {"foo": "bar"},
"patch": [{"op": "remove", "path": ""}],
"expected": null },
{ "comment": "remove whole document, array",
"doc": ["foo", "bar"],
"patch": [{"op": "remove", "path": ""}],
"expected": null },
{ "comment": "remove whole document, string",
"doc": "foo",
"patch": [{"op": "remove", "path": ""}],
"expected": null },
{ "comment": "remove whole document, null",
"doc": {},
"Note1": "We can't pass null in to json_patch_apply, so start with _something_ and remove it",
"patch": [
{"op": "remove", "path": ""},
{"op": "remove", "path": ""},
],
"error": "The spec says the target location must exist, so removing a null document fails"
},
{ "comment": "test replace with missing parent key should fail",
"doc": {"bar": "baz"},
"patch": [{"op": "replace", "path": "/foo/bar", "value": false}],
"error": "replace op should fail with missing parent key" },
{ "comment": "spurious patch properties",
"doc": {"foo": 1},
"patch": [{"op": "test", "path": "/foo", "value": 1, "spurious": 1}],
"expected": {"foo": 1} },
{ "doc": {"foo": null},
"patch": [{"op": "test", "path": "/foo", "value": null}],
"expected": {"foo": null},
"comment": "null value should be valid obj property" },
{ "doc": {"foo": null},
"patch": [{"op": "replace", "path": "/foo", "value": "truthy"}],
"expected": {"foo": "truthy"},
"comment": "null value should be valid obj property to be replaced with something truthy" },
{ "doc": {"foo": null},
"patch": [{"op": "move", "from": "/foo", "path": "/bar"}],
"expected": {"bar": null},
"comment": "null value should be valid obj property to be moved" },
{ "doc": {"foo": null},
"patch": [{"op": "copy", "from": "/foo", "path": "/bar"}],
"expected": {"foo": null, "bar": null},
"comment": "null value should be valid obj property to be copied" },
{ "doc": {"foo": null},
"patch": [{"op": "remove", "path": "/foo"}],
"expected": {},
"comment": "null value should be valid obj property to be removed" },
{ "doc": {"foo": "bar"},
"patch": [{"op": "replace", "path": "/foo", "value": null}],
"expected": {"foo": null},
"comment": "null value should still be valid obj property replace other value" },
{ "doc": {"foo": {"foo": 1, "bar": 2}},
"patch": [{"op": "test", "path": "/foo", "value": {"bar": 2, "foo": 1}}],
"expected": {"foo": {"foo": 1, "bar": 2}},
"comment": "test should pass despite rearrangement" },
{ "doc": {"foo": [{"foo": 1, "bar": 2}]},
"patch": [{"op": "test", "path": "/foo", "value": [{"bar": 2, "foo": 1}]}],
"expected": {"foo": [{"foo": 1, "bar": 2}]},
"comment": "test should pass despite (nested) rearrangement" },
{ "doc": {"foo": {"bar": [1, 2, 5, 4]}},
"patch": [{"op": "test", "path": "/foo", "value": {"bar": [1, 2, 5, 4]}}],
"expected": {"foo": {"bar": [1, 2, 5, 4]}},
"comment": "test should pass - no error" },
{ "doc": {"foo": {"bar": [1, 2, 5, 4]}},
"patch": [{"op": "test", "path": "/foo", "value": [1, 2]}],
"error": "test op should fail" },
{ "comment": "Test the whole document",
"doc": { "foo": 1 },
"patch": [{"op": "test", "path": "", "value": {"foo": 1}}],
"expected": { "foo": 1 } },
{ "comment": "Test the whole document, no match",
"doc": { "foo": 1 },
"patch": [{"op": "test", "path": "", "value": {"foo": 2}}],
"expected": { "foo": 1 },
"error": "Tested value does not match original doc" },
{ "comment": "Empty-string element",
"doc": { "": 1 },
"patch": [{"op": "test", "path": "/", "value": 1}],
"expected": { "": 1 } },
{ "doc": {
"foo": ["bar", "baz"],
"": 0,
"a/b": 1,
"c%d": 2,
"e^f": 3,
"g|h": 4,
"i\\j": 5,
"k\"l": 6,
" ": 7,
"m~n": 8
},
"patch": [{"op": "test", "path": "/foo", "value": ["bar", "baz"]},
{"op": "test", "path": "/foo/0", "value": "bar"},
{"op": "test", "path": "/", "value": 0},
{"op": "test", "path": "/a~1b", "value": 1},
{"op": "test", "path": "/c%d", "value": 2},
{"op": "test", "path": "/e^f", "value": 3},
{"op": "test", "path": "/g|h", "value": 4},
{"op": "test", "path": "/i\\j", "value": 5},
{"op": "test", "path": "/k\"l", "value": 6},
{"op": "test", "path": "/ ", "value": 7},
{"op": "test", "path": "/m~0n", "value": 8}],
"expected": {
"": 0,
" ": 7,
"a/b": 1,
"c%d": 2,
"e^f": 3,
"foo": [
"bar",
"baz"
],
"g|h": 4,
"i\\j": 5,
"k\"l": 6,
"m~n": 8
}
},
{ "comment": "Move to same location has no effect",
"doc": {"foo": 1},
"patch": [{"op": "move", "from": "/foo", "path": "/foo"}],
"expected": {"foo": 1} },
{ "doc": {"foo": 1, "baz": [{"qux": "hello"}]},
"patch": [{"op": "move", "from": "/foo", "path": "/bar"}],
"expected": {"baz": [{"qux": "hello"}], "bar": 1} },
{ "doc": {"baz": [{"qux": "hello"}], "bar": 1},
"patch": [{"op": "move", "from": "/baz/0/qux", "path": "/baz/1"}],
"expected": {"baz": [{}, "hello"], "bar": 1} },
{ "doc": {"baz": [{"qux": "hello"}], "bar": 1},
"patch": [{"op": "copy", "from": "/baz/0", "path": "/boo"}],
"expected": {"baz":[{"qux":"hello"}],"bar":1,"boo":{"qux":"hello"}} },
{ "comment": "replacing the root of the document is possible with add",
"doc": {"foo": "bar"},
"patch": [{"op": "add", "path": "", "value": {"baz": "qux"}}],
"expected": {"baz":"qux"}},
{ "comment": "Adding to \"/-\" adds to the end of the array",
"doc": [ 1, 2 ],
"patch": [ { "op": "add", "path": "/-", "value": { "foo": [ "bar", "baz" ] } } ],
"expected": [ 1, 2, { "foo": [ "bar", "baz" ] } ]},
{ "comment": "Adding to \"/-\" adds to the end of the array, even n levels down",
"doc": [ 1, 2, [ 3, [ 4, 5 ] ] ],
"patch": [ { "op": "add", "path": "/2/1/-", "value": { "foo": [ "bar", "baz" ] } } ],
"expected": [ 1, 2, [ 3, [ 4, 5, { "foo": [ "bar", "baz" ] } ] ] ]},
{ "comment": "test remove with bad number should fail",
"doc": {"foo": 1, "baz": [{"qux": "hello"}]},
"patch": [{"op": "remove", "path": "/baz/1e0/qux"}],
"error": "remove op shouldn't remove from array with bad number" },
{ "comment": "test remove on array",
"doc": [1, 2, 3, 4],
"patch": [{"op": "remove", "path": "/0"}],
"expected": [2, 3, 4] },
{ "comment": "test repeated removes",
"doc": [1, 2, 3, 4],
"patch": [{ "op": "remove", "path": "/1" },
{ "op": "remove", "path": "/2" }],
"expected": [1, 3] },
{ "comment": "test remove with bad index should fail",
"doc": [1, 2, 3, 4],
"patch": [{"op": "remove", "path": "/1e0"}],
"error": "remove op shouldn't remove from array with bad number" },
{ "comment": "test replace with bad number should fail",
"doc": [""],
"patch": [{"op": "replace", "path": "/1e0", "value": false}],
"error": "replace op shouldn't replace in array with bad number" },
{ "comment": "test copy with bad number should fail",
"doc": {"baz": [1,2,3], "bar": 1},
"patch": [{"op": "copy", "from": "/baz/1e0", "path": "/boo"}],
"error": "copy op shouldn't work with bad number" },
{ "comment": "test move with bad number should fail",
"doc": {"foo": 1, "baz": [1,2,3,4]},
"patch": [{"op": "move", "from": "/baz/1e0", "path": "/foo"}],
"error": "move op shouldn't work with bad number" },
{ "comment": "test add with bad number should fail",
"doc": ["foo", "sil"],
"patch": [{"op": "add", "path": "/1e0", "value": "bar"}],
"error": "add op shouldn't add to array with bad number" },
{ "comment": "missing 'path' parameter",
"doc": {},
"patch": [ { "op": "add", "value": "bar" } ],
"error": "missing 'path' parameter" },
{ "comment": "'path' parameter with null value",
"doc": {},
"patch": [ { "op": "add", "path": null, "value": "bar" } ],
"error": "null is not valid value for 'path'" },
{ "comment": "invalid JSON Pointer token",
"doc": {},
"patch": [ { "op": "add", "path": "foo", "value": "bar" } ],
"error": "JSON Pointer should start with a slash" },
{ "comment": "missing 'value' parameter to add",
"doc": [ 1 ],
"patch": [ { "op": "add", "path": "/-" } ],
"error": "missing 'value' parameter" },
{ "comment": "missing 'value' parameter to replace",
"doc": [ 1 ],
"patch": [ { "op": "replace", "path": "/0" } ],
"error": "missing 'value' parameter" },
{ "comment": "missing 'value' parameter to test",
"doc": [ null ],
"patch": [ { "op": "test", "path": "/0" } ],
"error": "missing 'value' parameter" },
{ "comment": "missing value parameter to test - where undef is falsy",
"doc": [ false ],
"patch": [ { "op": "test", "path": "/0" } ],
"error": "missing 'value' parameter" },
{ "comment": "missing from parameter to copy",
"doc": [ 1 ],
"patch": [ { "op": "copy", "path": "/-" } ],
"error": "missing 'from' parameter" },
{ "comment": "missing from location to copy",
"doc": { "foo": 1 },
"patch": [ { "op": "copy", "from": "/bar", "path": "/foo" } ],
"error": "missing 'from' location" },
{ "comment": "missing from parameter to move",
"doc": { "foo": 1 },
"patch": [ { "op": "move", "path": "" } ],
"error": "missing 'from' parameter" },
{ "comment": "missing from location to move",
"doc": { "foo": 1 },
"patch": [ { "op": "move", "from": "/bar", "path": "/foo" } ],
"error": "missing 'from' location" },
{ "comment": "duplicate ops, json-c parses this as op:move",
"doc": { "foo": "bar" },
"patch": [ { "op": "add", "path": "/baz", "value": "qux",
"op": "move", "from":"/foo" } ],
"error_wont_happen_in_jsonc": "patch has two 'op' members",
"expected": { "baz": "bar" }
},
{ "comment": "unrecognized op should fail",
"doc": {"foo": 1},
"patch": [{"op": "spam", "path": "/foo", "value": 1}],
"error": "Unrecognized op 'spam'" },
{ "comment": "test with bad array number that has leading zeros",
"doc": ["foo", "bar"],
"patch": [{"op": "test", "path": "/00", "value": "foo"}],
"error": "test op should reject the array value, it has leading zeros" },
{ "comment": "test with bad array number that has leading zeros",
"doc": ["foo", "bar"],
"patch": [{"op": "test", "path": "/01", "value": "bar"}],
"error": "test op should reject the array value, it has leading zeros" },
{ "comment": "Removing nonexistent field",
"doc": {"foo" : "bar"},
"patch": [{"op": "remove", "path": "/baz"}],
"error": "removing a nonexistent field should fail" },
{ "comment": "Removing deep nonexistent path",
"doc": {"foo" : "bar"},
"patch": [{"op": "remove", "path": "/missing1/missing2"}],
"error": "removing a nonexistent field should fail" },
{ "comment": "Removing nonexistent index",
"doc": ["foo", "bar"],
"patch": [{"op": "remove", "path": "/2"}],
"error": "removing a nonexistent index should fail" },
{ "comment": "Patch with different capitalisation than doc",
"doc": {"foo":"bar"},
"patch": [{"op": "add", "path": "/FOO", "value": "BAR"}],
"expected": {"foo": "bar", "FOO": "BAR"}
}
]

View File

@@ -189,6 +189,42 @@ void test_array_list_expand_internal(void)
json_object_put(my_array); json_object_put(my_array);
} }
void test_array_insert_idx(void);
void test_array_insert_idx(void)
{
json_object *my_array;
struct json_object *jo1;
my_array = json_object_new_array();
json_object_array_add(my_array, json_object_new_int(1));
json_object_array_add(my_array, json_object_new_int(2));
json_object_array_add(my_array, json_object_new_int(5));
json_object_array_insert_idx(my_array, 2, json_object_new_int(4));
jo1 = json_tokener_parse("[1, 2, 4, 5]");
assert(1 == json_object_equal(my_array, jo1));
json_object_put(jo1);
json_object_array_insert_idx(my_array, 2, json_object_new_int(3));
jo1 = json_tokener_parse("[1, 2, 3, 4, 5]");
assert(1 == json_object_equal(my_array, jo1));
json_object_put(jo1);
json_object_array_insert_idx(my_array, 5, json_object_new_int(6));
jo1 = json_tokener_parse("[1, 2, 3, 4, 5, 6]");
assert(1 == json_object_equal(my_array, jo1));
json_object_put(jo1);
json_object_array_insert_idx(my_array, 7, json_object_new_int(8));
jo1 = json_tokener_parse("[1, 2, 3, 4, 5, 6, null, 8]");
assert(1 == json_object_equal(my_array, jo1));
json_object_put(jo1);
json_object_put(my_array);
}
int main(int argc, char **argv) int main(int argc, char **argv)
{ {
json_object *my_string, *my_int, *my_null, *my_object, *my_array; json_object *my_string, *my_int, *my_null, *my_object, *my_array;
@@ -253,6 +289,8 @@ int main(int argc, char **argv)
json_object_put(my_array); json_object_put(my_array);
test_array_insert_idx();
test_array_del_idx(); test_array_del_idx();
test_array_list_expand_internal(); test_array_list_expand_internal();

127
tests/test_json_patch.c Normal file
View File

@@ -0,0 +1,127 @@
#ifdef NDEBUG
#undef NDEBUG
#endif
#include "strerror_override.h"
#include <assert.h>
#include <errno.h>
#include <limits.h>
#include <stdio.h>
#include <string.h>
#include "config.h"
#include "json.h"
#include "snprintf_compat.h"
#ifndef PATH_MAX
#define PATH_MAX 256
#endif
void test_json_patch_op(struct json_object *jo)
{
const char *comment = json_object_get_string(json_object_object_get(jo, "comment"));
struct json_object *doc = json_object_object_get(jo, "doc");
struct json_object *patch = json_object_object_get(jo, "patch");
struct json_object *expected = NULL;
json_bool have_expected = json_object_object_get_ex(jo, "expected", &expected);
struct json_object *error = json_object_object_get(jo, "error");
const char *error_s = json_object_get_string(error);
struct json_object *res = NULL;
int ret;
printf("Testing '%s', doc '%s' patch '%s' : ",
comment ? comment : error_s,
json_object_get_string(doc),
json_object_get_string(patch));
if (!error && !have_expected) {
printf("BAD TEST - no expected or error conditions in test: %s\n", json_object_to_json_string(jo));
assert(0);
}
fflush(stdout);
struct json_patch_error jperr;
if (error) {
assert(-1 == json_patch_apply(doc, patch, &res, &jperr));
assert(jperr.errno_code != 0);
printf("OK\n");
printf(" => json_patch_apply failed as expected: %s at patch idx %zu: %s\n",
strerror(jperr.errno_code), jperr.patch_failure_idx, jperr.errmsg);
json_object_put(res);
} else {
ret = json_patch_apply(doc, patch, &res, &jperr);
if (ret) {
fprintf(stderr, "json_patch_apply() returned '%d'\n", ret);
fprintf(stderr, "Expected: %s\n", json_object_get_string(expected));
fprintf(stderr, "Got: %s\n", res ? json_object_get_string(res) : "(null)");
fprintf(stderr, "json_patch_apply failed: %s at patch idx %zu: %s\n",
strerror(jperr.errno_code), jperr.patch_failure_idx, jperr.errmsg);
fflush(stderr);
assert(0);
}
// Note: res might be NULL if the whole document was removed
assert(jperr.errno_code == 0);
ret = json_object_equal(expected, res);
if (ret == 0) {
fprintf(stderr, "json_object_equal() returned '%d'\n", ret);
fprintf(stderr, "Expected: %s\n", json_object_get_string(expected));
fprintf(stderr, "Got: %s\n", json_object_get_string(res));
fflush(stderr);
assert(0);
}
json_object_put(res);
res = NULL;
printf("OK\n");
}
}
void test_json_patch_using_file(const char *testdir, const char *filename)
{
char full_filename[PATH_MAX];
(void)snprintf(full_filename, sizeof(full_filename), "%s/%s", testdir, filename);
size_t ii;
printf("Testing using file %s\n", filename);
json_object *jo = json_object_from_file(full_filename);
if (!jo) {
fprintf(stderr, "FAIL: unable to open %s: %s\n", full_filename, strerror(errno));
exit(EXIT_FAILURE);
}
for (ii = 0; ii < json_object_array_length(jo); ii++) {
struct json_object *jo1 = json_object_array_get_idx(jo, ii);
test_json_patch_op(jo1);
}
json_object_put(jo);
}
int main(int argc, char **argv)
{
const char *testdir;
if (argc < 2)
{
fprintf(stderr,
"Usage: %s <testdir>\n"
" <testdir> is the location of input files\n",
argv[0]);
return EXIT_FAILURE;
}
testdir = argv[1];
// Test json_c_version.c
if (strncmp(json_c_version(), JSON_C_VERSION, sizeof(JSON_C_VERSION)))
{
printf("FAIL: Output from json_c_version(): %s does not match %s",
json_c_version(), JSON_C_VERSION);
return EXIT_FAILURE;
}
if (json_c_version_num() != JSON_C_VERSION_NUM)
{
printf("FAIL: Output from json_c_version_num(): %d does not match %d",
json_c_version_num(), JSON_C_VERSION_NUM);
return EXIT_FAILURE;
}
test_json_patch_using_file(testdir, "json_patch_spec_tests.json");
test_json_patch_using_file(testdir, "json_patch_tests.json");
return 0;
}

View File

@@ -0,0 +1,158 @@
Testing using file json_patch_spec_tests.json
Testing '4.1. add with missing object', doc '{ "q": { "bar": 2 } }' patch '[ { "op": "add", "path": "\/a\/b", "value": 1 } ]' : OK
=> json_patch_apply failed as expected: ERRNO=ENOENT at patch idx 0: Failed to set value at path referenced by 'path' field
Testing 'A.1. Adding an Object Member', doc '{ "foo": "bar" }' patch '[ { "op": "add", "path": "\/baz", "value": "qux" } ]' : OK
Testing 'A.2. Adding an Array Element', doc '{ "foo": [ "bar", "baz" ] }' patch '[ { "op": "add", "path": "\/foo\/1", "value": "qux" } ]' : OK
Testing 'A.3. Removing an Object Member', doc '{ "baz": "qux", "foo": "bar" }' patch '[ { "op": "remove", "path": "\/baz" } ]' : OK
Testing 'A.4. Removing an Array Element', doc '{ "foo": [ "bar", "qux", "baz" ] }' patch '[ { "op": "remove", "path": "\/foo\/1" } ]' : OK
Testing 'A.5. Replacing a Value', doc '{ "baz": "qux", "foo": "bar" }' patch '[ { "op": "replace", "path": "\/baz", "value": "boo" } ]' : OK
Testing 'A.6. Moving a Value', doc '{ "foo": { "bar": "baz", "waldo": "fred" }, "qux": { "corge": "grault" } }' patch '[ { "op": "move", "from": "\/foo\/waldo", "path": "\/qux\/thud" } ]' : OK
Testing 'A.7. Moving an Array Element', doc '{ "foo": [ "all", "grass", "cows", "eat" ] }' patch '[ { "op": "move", "from": "\/foo\/1", "path": "\/foo\/3" } ]' : OK
Testing 'A.8. Testing a Value: Success', doc '{ "baz": "qux", "foo": [ "a", 2, "c" ] }' patch '[ { "op": "test", "path": "\/baz", "value": "qux" }, { "op": "test", "path": "\/foo\/1", "value": 2 } ]' : OK
Testing 'A.9. Testing a Value: Error', doc '{ "baz": "qux" }' patch '[ { "op": "test", "path": "\/baz", "value": "bar" } ]' : OK
=> json_patch_apply failed as expected: ERRNO=ENOENT at patch idx 0: Value of element referenced by 'path' field did not match 'value' field
Testing 'A.10. Adding a nested Member Object', doc '{ "foo": "bar" }' patch '[ { "op": "add", "path": "\/child", "value": { "grandchild": { } } } ]' : OK
Testing 'A.11. Ignoring Unrecognized Elements', doc '{ "foo": "bar" }' patch '[ { "op": "add", "path": "\/baz", "value": "qux", "xyz": 123 } ]' : OK
Testing 'A.12. Adding to a Non-existent Target', doc '{ "foo": "bar" }' patch '[ { "op": "add", "path": "\/baz\/bat", "value": "qux" } ]' : OK
=> json_patch_apply failed as expected: ERRNO=ENOENT at patch idx 0: Failed to set value at path referenced by 'path' field
Testing 'A.13 Invalid JSON Patch Document', doc '{ "foo": "bar" }' patch '[ { "op": "remove", "path": "\/baz", "value": "qux" } ]' : OK
=> json_patch_apply failed as expected: ERRNO=ENOENT at patch idx 0: Did not find element referenced by path field
Testing 'A.14. ~ Escape Ordering', doc '{ "\/": 9, "~1": 10 }' patch '[ { "op": "test", "path": "\/~01", "value": 10 } ]' : OK
Testing 'A.15. Comparing Strings and Numbers', doc '{ "\/": 9, "~1": 10 }' patch '[ { "op": "test", "path": "\/~01", "value": "10" } ]' : OK
=> json_patch_apply failed as expected: ERRNO=ENOENT at patch idx 0: Value of element referenced by 'path' field did not match 'value' field
Testing 'A.16. Adding an Array Value', doc '{ "foo": [ "bar" ] }' patch '[ { "op": "add", "path": "\/foo\/-", "value": [ "abc", "def" ] } ]' : OK
Testing using file json_patch_tests.json
Testing 'empty list, empty docs', doc '{ }' patch '[ ]' : OK
Testing 'empty patch list', doc '{ "foo": 1 }' patch '[ ]' : OK
Testing 'rearrangements OK?', doc '{ "foo": 1, "bar": 2 }' patch '[ ]' : OK
Testing 'rearrangements OK? How about one level down ... array', doc '[ { "foo": 1, "bar": 2 } ]' patch '[ ]' : OK
Testing 'rearrangements OK? How about one level down...', doc '{ "foo": { "foo": 1, "bar": 2 } }' patch '[ ]' : OK
Testing 'add replaces any existing field', doc '{ "foo": null }' patch '[ { "op": "add", "path": "\/foo", "value": 1 } ]' : OK
Testing 'toplevel array', doc '[ ]' patch '[ { "op": "add", "path": "\/0", "value": "foo" } ]' : OK
Testing 'toplevel array, no change', doc '[ "foo" ]' patch '[ ]' : OK
Testing 'toplevel object, numeric string', doc '{ }' patch '[ { "op": "add", "path": "\/foo", "value": "1" } ]' : OK
Testing 'toplevel object, integer', doc '{ }' patch '[ { "op": "add", "path": "\/foo", "value": 1 } ]' : OK
Testing 'Toplevel scalar values OK?', doc 'foo' patch '[ { "op": "replace", "path": "", "value": "bar" } ]' : OK
Testing 'replace object document with array document?', doc '{ }' patch '[ { "op": "add", "path": "", "value": [ ] } ]' : OK
Testing 'replace array document with object document?', doc '[ ]' patch '[ { "op": "add", "path": "", "value": { } } ]' : OK
Testing 'append to root array document?', doc '[ ]' patch '[ { "op": "add", "path": "\/-", "value": "hi" } ]' : OK
Testing 'Add, / target', doc '{ }' patch '[ { "op": "add", "path": "\/", "value": 1 } ]' : OK
Testing 'Add, /foo/ deep target (trailing slash)', doc '{ "foo": { } }' patch '[ { "op": "add", "path": "\/foo\/", "value": 1 } ]' : OK
Testing 'Add composite value at top level', doc '{ "foo": 1 }' patch '[ { "op": "add", "path": "\/bar", "value": [ 1, 2 ] } ]' : OK
Testing 'Add into composite value', doc '{ "foo": 1, "baz": [ { "qux": "hello" } ] }' patch '[ { "op": "add", "path": "\/baz\/0\/foo", "value": "world" } ]' : OK
Testing 'Out of bounds (upper)', doc '{ "bar": [ 1, 2 ] }' patch '[ { "op": "add", "path": "\/bar\/8", "value": "5" } ]' : OK
=> json_patch_apply failed as expected: ERRNO=EINVAL at patch idx 0: Failed to set value at path referenced by 'path' field
Testing 'Out of bounds (lower)', doc '{ "bar": [ 1, 2 ] }' patch '[ { "op": "add", "path": "\/bar\/-1", "value": "5" } ]' : OK
=> json_patch_apply failed as expected: ERRNO=EINVAL at patch idx 0: Failed to set value at path referenced by 'path' field
Testing '(null)', doc '{ "foo": 1 }' patch '[ { "op": "add", "path": "\/bar", "value": true } ]' : OK
Testing '(null)', doc '{ "foo": 1 }' patch '[ { "op": "add", "path": "\/bar", "value": false } ]' : OK
Testing '(null)', doc '{ "foo": 1 }' patch '[ { "op": "add", "path": "\/bar", "value": null } ]' : OK
Testing '0 can be an array index or object element name', doc '{ "foo": 1 }' patch '[ { "op": "add", "path": "\/0", "value": "bar" } ]' : OK
Testing '(null)', doc '[ "foo" ]' patch '[ { "op": "add", "path": "\/1", "value": "bar" } ]' : OK
Testing '(null)', doc '[ "foo", "sil" ]' patch '[ { "op": "add", "path": "\/1", "value": "bar" } ]' : OK
Testing '(null)', doc '[ "foo", "sil" ]' patch '[ { "op": "add", "path": "\/0", "value": "bar" } ]' : OK
Testing 'push item to array via last index + 1', doc '[ "foo", "sil" ]' patch '[ { "op": "add", "path": "\/2", "value": "bar" } ]' : OK
Testing 'add item to array at index > length should fail', doc '[ "foo", "sil" ]' patch '[ { "op": "add", "path": "\/3", "value": "bar" } ]' : OK
=> json_patch_apply failed as expected: ERRNO=EINVAL at patch idx 0: Failed to set value at path referenced by 'path' field
Testing 'test against implementation-specific numeric parsing', doc '{ "1e0": "foo" }' patch '[ { "op": "test", "path": "\/1e0", "value": "foo" } ]' : OK
Testing 'test with bad number should fail', doc '[ "foo", "bar" ]' patch '[ { "op": "test", "path": "\/1e0", "value": "bar" } ]' : OK
=> json_patch_apply failed as expected: ERRNO=EINVAL at patch idx 0: Invalid path field
Testing 'Object operation on array target', doc '[ "foo", "sil" ]' patch '[ { "op": "add", "path": "\/bar", "value": 42 } ]' : OK
=> json_patch_apply failed as expected: ERRNO=EINVAL at patch idx 0: Failed to set value at path referenced by 'path' field
Testing 'value in array add not flattened', doc '[ "foo", "sil" ]' patch '[ { "op": "add", "path": "\/1", "value": [ "bar", "baz" ] } ]' : OK
Testing '(null)', doc '{ "foo": 1, "bar": [ 1, 2, 3, 4 ] }' patch '[ { "op": "remove", "path": "\/bar" } ]' : OK
Testing '(null)', doc '{ "foo": 1, "baz": [ { "qux": "hello" } ] }' patch '[ { "op": "remove", "path": "\/baz\/0\/qux" } ]' : OK
Testing '(null)', doc '{ "foo": 1, "baz": [ { "qux": "hello" } ] }' patch '[ { "op": "replace", "path": "\/foo", "value": [ 1, 2, 3, 4 ] } ]' : OK
Testing '(null)', doc '{ "foo": [ 1, 2, 3, 4 ], "baz": [ { "qux": "hello" } ] }' patch '[ { "op": "replace", "path": "\/baz\/0\/qux", "value": "world" } ]' : OK
Testing '(null)', doc '[ "foo" ]' patch '[ { "op": "replace", "path": "\/0", "value": "bar" } ]' : OK
Testing '(null)', doc '[ "" ]' patch '[ { "op": "replace", "path": "\/0", "value": 0 } ]' : OK
Testing '(null)', doc '[ "" ]' patch '[ { "op": "replace", "path": "\/0", "value": true } ]' : OK
Testing '(null)', doc '[ "" ]' patch '[ { "op": "replace", "path": "\/0", "value": false } ]' : OK
Testing '(null)', doc '[ "" ]' patch '[ { "op": "replace", "path": "\/0", "value": null } ]' : OK
Testing 'value in array replace not flattened', doc '[ "foo", "sil" ]' patch '[ { "op": "replace", "path": "\/1", "value": [ "bar", "baz" ] } ]' : OK
Testing 'replace whole document', doc '{ "foo": "bar" }' patch '[ { "op": "replace", "path": "", "value": { "baz": "qux" } } ]' : OK
Testing 'add whole document, null', doc '{ }' patch '[ { "op": "remove", "path": "" }, { "op": "add", "path": "", "value": { "baz": "qux" } } ]' : OK
Testing 'replace whole document, null', doc '{ }' patch '[ { "op": "remove", "path": "" }, { "op": "replace", "path": "", "value": { "baz": "qux" } } ]' : OK
=> json_patch_apply failed as expected: ERRNO=EINVAL at patch idx 1: Invalid path field
Testing 'remove whole document', doc '{ "foo": "bar" }' patch '[ { "op": "remove", "path": "" } ]' : OK
Testing 'remove whole document', doc '{ "foo": "bar" }' patch '[ { "op": "remove", "path": "" } ]' : OK
Testing 'remove whole document, array', doc '[ "foo", "bar" ]' patch '[ { "op": "remove", "path": "" } ]' : OK
Testing 'remove whole document, string', doc 'foo' patch '[ { "op": "remove", "path": "" } ]' : OK
Testing 'remove whole document, null', doc '{ }' patch '[ { "op": "remove", "path": "" }, { "op": "remove", "path": "" } ]' : OK
=> json_patch_apply failed as expected: ERRNO=EINVAL at patch idx 1: Invalid path field
Testing 'test replace with missing parent key should fail', doc '{ "bar": "baz" }' patch '[ { "op": "replace", "path": "\/foo\/bar", "value": false } ]' : OK
=> json_patch_apply failed as expected: ERRNO=ENOENT at patch idx 0: Did not find element referenced by path field
Testing 'spurious patch properties', doc '{ "foo": 1 }' patch '[ { "op": "test", "path": "\/foo", "value": 1, "spurious": 1 } ]' : OK
Testing 'null value should be valid obj property', doc '{ "foo": null }' patch '[ { "op": "test", "path": "\/foo", "value": null } ]' : OK
Testing 'null value should be valid obj property to be replaced with something truthy', doc '{ "foo": null }' patch '[ { "op": "replace", "path": "\/foo", "value": "truthy" } ]' : OK
Testing 'null value should be valid obj property to be moved', doc '{ "foo": null }' patch '[ { "op": "move", "from": "\/foo", "path": "\/bar" } ]' : OK
Testing 'null value should be valid obj property to be copied', doc '{ "foo": null }' patch '[ { "op": "copy", "from": "\/foo", "path": "\/bar" } ]' : OK
Testing 'null value should be valid obj property to be removed', doc '{ "foo": null }' patch '[ { "op": "remove", "path": "\/foo" } ]' : OK
Testing 'null value should still be valid obj property replace other value', doc '{ "foo": "bar" }' patch '[ { "op": "replace", "path": "\/foo", "value": null } ]' : OK
Testing 'test should pass despite rearrangement', doc '{ "foo": { "foo": 1, "bar": 2 } }' patch '[ { "op": "test", "path": "\/foo", "value": { "bar": 2, "foo": 1 } } ]' : OK
Testing 'test should pass despite (nested) rearrangement', doc '{ "foo": [ { "foo": 1, "bar": 2 } ] }' patch '[ { "op": "test", "path": "\/foo", "value": [ { "bar": 2, "foo": 1 } ] } ]' : OK
Testing 'test should pass - no error', doc '{ "foo": { "bar": [ 1, 2, 5, 4 ] } }' patch '[ { "op": "test", "path": "\/foo", "value": { "bar": [ 1, 2, 5, 4 ] } } ]' : OK
Testing 'test op should fail', doc '{ "foo": { "bar": [ 1, 2, 5, 4 ] } }' patch '[ { "op": "test", "path": "\/foo", "value": [ 1, 2 ] } ]' : OK
=> json_patch_apply failed as expected: ERRNO=ENOENT at patch idx 0: Value of element referenced by 'path' field did not match 'value' field
Testing 'Test the whole document', doc '{ "foo": 1 }' patch '[ { "op": "test", "path": "", "value": { "foo": 1 } } ]' : OK
Testing 'Test the whole document, no match', doc '{ "foo": 1 }' patch '[ { "op": "test", "path": "", "value": { "foo": 2 } } ]' : OK
=> json_patch_apply failed as expected: ERRNO=ENOENT at patch idx 0: Value of element referenced by 'path' field did not match 'value' field
Testing 'Empty-string element', doc '{ "": 1 }' patch '[ { "op": "test", "path": "\/", "value": 1 } ]' : OK
Testing '(null)', doc '{ "foo": [ "bar", "baz" ], "": 0, "a\/b": 1, "c%d": 2, "e^f": 3, "g|h": 4, "i\\j": 5, "k\"l": 6, " ": 7, "m~n": 8 }' patch '[ { "op": "test", "path": "\/foo", "value": [ "bar", "baz" ] }, { "op": "test", "path": "\/foo\/0", "value": "bar" }, { "op": "test", "path": "\/", "value": 0 }, { "op": "test", "path": "\/a~1b", "value": 1 }, { "op": "test", "path": "\/c%d", "value": 2 }, { "op": "test", "path": "\/e^f", "value": 3 }, { "op": "test", "path": "\/g|h", "value": 4 }, { "op": "test", "path": "\/i\\j", "value": 5 }, { "op": "test", "path": "\/k\"l", "value": 6 }, { "op": "test", "path": "\/ ", "value": 7 }, { "op": "test", "path": "\/m~0n", "value": 8 } ]' : OK
Testing 'Move to same location has no effect', doc '{ "foo": 1 }' patch '[ { "op": "move", "from": "\/foo", "path": "\/foo" } ]' : OK
Testing '(null)', doc '{ "foo": 1, "baz": [ { "qux": "hello" } ] }' patch '[ { "op": "move", "from": "\/foo", "path": "\/bar" } ]' : OK
Testing '(null)', doc '{ "baz": [ { "qux": "hello" } ], "bar": 1 }' patch '[ { "op": "move", "from": "\/baz\/0\/qux", "path": "\/baz\/1" } ]' : OK
Testing '(null)', doc '{ "baz": [ { "qux": "hello" } ], "bar": 1 }' patch '[ { "op": "copy", "from": "\/baz\/0", "path": "\/boo" } ]' : OK
Testing 'replacing the root of the document is possible with add', doc '{ "foo": "bar" }' patch '[ { "op": "add", "path": "", "value": { "baz": "qux" } } ]' : OK
Testing 'Adding to "/-" adds to the end of the array', doc '[ 1, 2 ]' patch '[ { "op": "add", "path": "\/-", "value": { "foo": [ "bar", "baz" ] } } ]' : OK
Testing 'Adding to "/-" adds to the end of the array, even n levels down', doc '[ 1, 2, [ 3, [ 4, 5 ] ] ]' patch '[ { "op": "add", "path": "\/2\/1\/-", "value": { "foo": [ "bar", "baz" ] } } ]' : OK
Testing 'test remove with bad number should fail', doc '{ "foo": 1, "baz": [ { "qux": "hello" } ] }' patch '[ { "op": "remove", "path": "\/baz\/1e0\/qux" } ]' : OK
=> json_patch_apply failed as expected: ERRNO=EINVAL at patch idx 0: Invalid path field
Testing 'test remove on array', doc '[ 1, 2, 3, 4 ]' patch '[ { "op": "remove", "path": "\/0" } ]' : OK
Testing 'test repeated removes', doc '[ 1, 2, 3, 4 ]' patch '[ { "op": "remove", "path": "\/1" }, { "op": "remove", "path": "\/2" } ]' : OK
Testing 'test remove with bad index should fail', doc '[ 1, 2, 3, 4 ]' patch '[ { "op": "remove", "path": "\/1e0" } ]' : OK
=> json_patch_apply failed as expected: ERRNO=EINVAL at patch idx 0: Invalid path field
Testing 'test replace with bad number should fail', doc '[ "" ]' patch '[ { "op": "replace", "path": "\/1e0", "value": false } ]' : OK
=> json_patch_apply failed as expected: ERRNO=EINVAL at patch idx 0: Invalid path field
Testing 'test copy with bad number should fail', doc '{ "baz": [ 1, 2, 3 ], "bar": 1 }' patch '[ { "op": "copy", "from": "\/baz\/1e0", "path": "\/boo" } ]' : OK
=> json_patch_apply failed as expected: ERRNO=EINVAL at patch idx 0: Invalid from field
Testing 'test move with bad number should fail', doc '{ "foo": 1, "baz": [ 1, 2, 3, 4 ] }' patch '[ { "op": "move", "from": "\/baz\/1e0", "path": "\/foo" } ]' : OK
=> json_patch_apply failed as expected: ERRNO=EINVAL at patch idx 0: Invalid from field
Testing 'test add with bad number should fail', doc '[ "foo", "sil" ]' patch '[ { "op": "add", "path": "\/1e0", "value": "bar" } ]' : OK
=> json_patch_apply failed as expected: ERRNO=EINVAL at patch idx 0: Failed to set value at path referenced by 'path' field
Testing 'missing 'path' parameter', doc '{ }' patch '[ { "op": "add", "value": "bar" } ]' : OK
=> json_patch_apply failed as expected: ERRNO=EINVAL at patch idx 0: Patch object does not contain 'path' field
Testing ''path' parameter with null value', doc '{ }' patch '[ { "op": "add", "path": null, "value": "bar" } ]' : OK
=> json_patch_apply failed as expected: ERRNO=EINVAL at patch idx 0: Failed to set value at path referenced by 'path' field
Testing 'invalid JSON Pointer token', doc '{ }' patch '[ { "op": "add", "path": "foo", "value": "bar" } ]' : OK
=> json_patch_apply failed as expected: ERRNO=EINVAL at patch idx 0: Failed to set value at path referenced by 'path' field
Testing 'missing 'value' parameter to add', doc '[ 1 ]' patch '[ { "op": "add", "path": "\/-" } ]' : OK
=> json_patch_apply failed as expected: ERRNO=EINVAL at patch idx 0: Patch object does not contain a 'value' field
Testing 'missing 'value' parameter to replace', doc '[ 1 ]' patch '[ { "op": "replace", "path": "\/0" } ]' : OK
=> json_patch_apply failed as expected: ERRNO=EINVAL at patch idx 0: Patch object does not contain a 'value' field
Testing 'missing 'value' parameter to test', doc '[ null ]' patch '[ { "op": "test", "path": "\/0" } ]' : OK
=> json_patch_apply failed as expected: ERRNO=EINVAL at patch idx 0: Patch object does not contain a 'value' field
Testing 'missing value parameter to test - where undef is falsy', doc '[ false ]' patch '[ { "op": "test", "path": "\/0" } ]' : OK
=> json_patch_apply failed as expected: ERRNO=EINVAL at patch idx 0: Patch object does not contain a 'value' field
Testing 'missing from parameter to copy', doc '[ 1 ]' patch '[ { "op": "copy", "path": "\/-" } ]' : OK
=> json_patch_apply failed as expected: ERRNO=EINVAL at patch idx 0: Patch does not contain a 'from' field
Testing 'missing from location to copy', doc '{ "foo": 1 }' patch '[ { "op": "copy", "from": "\/bar", "path": "\/foo" } ]' : OK
=> json_patch_apply failed as expected: ERRNO=ENOENT at patch idx 0: Did not find element referenced by from field
Testing 'missing from parameter to move', doc '{ "foo": 1 }' patch '[ { "op": "move", "path": "" } ]' : OK
=> json_patch_apply failed as expected: ERRNO=EINVAL at patch idx 0: Patch does not contain a 'from' field
Testing 'missing from location to move', doc '{ "foo": 1 }' patch '[ { "op": "move", "from": "\/bar", "path": "\/foo" } ]' : OK
=> json_patch_apply failed as expected: ERRNO=ENOENT at patch idx 0: Did not find element referenced by from field
Testing 'duplicate ops, json-c parses this as op:move', doc '{ "foo": "bar" }' patch '[ { "op": "move", "path": "\/baz", "value": "qux", "from": "\/foo" } ]' : OK
Testing 'unrecognized op should fail', doc '{ "foo": 1 }' patch '[ { "op": "spam", "path": "\/foo", "value": 1 } ]' : OK
=> json_patch_apply failed as expected: ERRNO=EINVAL at patch idx 0: Patch object has invalid 'op' field
Testing 'test with bad array number that has leading zeros', doc '[ "foo", "bar" ]' patch '[ { "op": "test", "path": "\/00", "value": "foo" } ]' : OK
=> json_patch_apply failed as expected: ERRNO=EINVAL at patch idx 0: Invalid path field
Testing 'test with bad array number that has leading zeros', doc '[ "foo", "bar" ]' patch '[ { "op": "test", "path": "\/01", "value": "bar" } ]' : OK
=> json_patch_apply failed as expected: ERRNO=EINVAL at patch idx 0: Invalid path field
Testing 'Removing nonexistent field', doc '{ "foo": "bar" }' patch '[ { "op": "remove", "path": "\/baz" } ]' : OK
=> json_patch_apply failed as expected: ERRNO=ENOENT at patch idx 0: Did not find element referenced by path field
Testing 'Removing deep nonexistent path', doc '{ "foo": "bar" }' patch '[ { "op": "remove", "path": "\/missing1\/missing2" } ]' : OK
=> json_patch_apply failed as expected: ERRNO=ENOENT at patch idx 0: Did not find element referenced by path field
Testing 'Removing nonexistent index', doc '[ "foo", "bar" ]' patch '[ { "op": "remove", "path": "\/2" } ]' : OK
=> json_patch_apply failed as expected: ERRNO=ENOENT at patch idx 0: Did not find element referenced by path field
Testing 'Patch with different capitalisation than doc', doc '{ "foo": "bar" }' patch '[ { "op": "add", "path": "\/FOO", "value": "BAR" } ]' : OK

17
tests/test_json_patch.test Executable file
View File

@@ -0,0 +1,17 @@
#!/bin/sh
export _JSON_C_STRERROR_ENABLE=1
# Common definitions
if test -z "$srcdir"; then
srcdir="${0%/*}"
test "$srcdir" = "$0" && srcdir=.
test -z "$srcdir" && srcdir=.
fi
. "$srcdir/test-defs.sh"
filename=$(basename "$0")
filename="${filename%.*}"
run_output_test $filename "$srcdir"
exit $?

View File

@@ -269,6 +269,22 @@ static void test_example_set(void)
printf("%s\n", json_object_get_string(jo1)); printf("%s\n", json_object_get_string(jo1));
json_object_put(jo1); json_object_put(jo1);
jo1 = json_tokener_parse("[0, 1, 2, 3]");
jo2 = json_tokener_parse("[0, 1, 2, 3, null, null, null, 7]");
assert(0 == json_pointer_set(&jo1, "/7", json_object_new_int(7)));
assert(1 == json_object_equal(jo1, jo2));
json_object_put(jo1);
jo1 = json_tokener_parse("[0, 1, 2, 3]");
assert(0 == json_pointer_setf(&jo1, json_object_new_int(7), "/%u", 7));
assert(1 == json_object_equal(jo1, jo2));
json_object_put(jo1);
json_object_put(jo2);
} }
static void test_wrong_inputs_set(void) static void test_wrong_inputs_set(void)

View File

@@ -535,7 +535,7 @@ struct incremental_step
{"{\"a\":}", -1, 5, json_tokener_error_parse_unexpected, 1, 0}, {"{\"a\":}", -1, 5, json_tokener_error_parse_unexpected, 1, 0},
{"{\"a\":1,\"a\":2}", -1, -1, json_tokener_success, 1, 0}, {"{\"a\":1,\"a\":2}", -1, -1, json_tokener_success, 1, 0},
{"\"a\":1}", -1, 3, json_tokener_success, 1, 0}, {"\"a\":1}", -1, 3, json_tokener_success, 1, 0},
{"{\"a\":1", -1, -1, json_tokener_continue, 1, 0}, {"{\"a\":1", -1, -1, json_tokener_continue, 1, 0}, //}
{"[,]", -1, 1, json_tokener_error_parse_unexpected, 1, 0}, {"[,]", -1, 1, json_tokener_error_parse_unexpected, 1, 0},
{"[,1]", -1, 1, json_tokener_error_parse_unexpected, 1, 0}, {"[,1]", -1, 1, json_tokener_error_parse_unexpected, 1, 0},
@@ -595,6 +595,44 @@ struct incremental_step
{"\x7b\x22\x31\x81\x22\x3a\x31\x7d", -1, 3, json_tokener_error_parse_utf8_string, 1, {"\x7b\x22\x31\x81\x22\x3a\x31\x7d", -1, 3, json_tokener_error_parse_utf8_string, 1,
JSON_TOKENER_VALIDATE_UTF8}, JSON_TOKENER_VALIDATE_UTF8},
// Note, current asciiz APIs can't parse \x00, skip it
{ "\"0\x01\x02\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f" \
"\x10\x11\x12\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f\"",
-1, -1, json_tokener_success, 1, 0 },
// Test control chars again, this time in strict mode, which should fail
{ "\"\x01\"", -1, 1, json_tokener_error_parse_string, 1, JSON_TOKENER_STRICT },
{ "\"\x02\"", -1, 1, json_tokener_error_parse_string, 1, JSON_TOKENER_STRICT },
{ "\"\x03\"", -1, 1, json_tokener_error_parse_string, 1, JSON_TOKENER_STRICT },
{ "\"\x04\"", -1, 1, json_tokener_error_parse_string, 1, JSON_TOKENER_STRICT },
{ "\"\x05\"", -1, 1, json_tokener_error_parse_string, 1, JSON_TOKENER_STRICT },
{ "\"\x06\"", -1, 1, json_tokener_error_parse_string, 1, JSON_TOKENER_STRICT },
{ "\"\x07\"", -1, 1, json_tokener_error_parse_string, 1, JSON_TOKENER_STRICT },
{ "\"\x08\"", -1, 1, json_tokener_error_parse_string, 1, JSON_TOKENER_STRICT },
{ "\"\x09\"", -1, 1, json_tokener_error_parse_string, 1, JSON_TOKENER_STRICT },
{ "\"\x0a\"", -1, 1, json_tokener_error_parse_string, 1, JSON_TOKENER_STRICT },
{ "\"\x0b\"", -1, 1, json_tokener_error_parse_string, 1, JSON_TOKENER_STRICT },
{ "\"\x0c\"", -1, 1, json_tokener_error_parse_string, 1, JSON_TOKENER_STRICT },
{ "\"\x0d\"", -1, 1, json_tokener_error_parse_string, 1, JSON_TOKENER_STRICT },
{ "\"\x0e\"", -1, 1, json_tokener_error_parse_string, 1, JSON_TOKENER_STRICT },
{ "\"\x0f\"", -1, 1, json_tokener_error_parse_string, 1, JSON_TOKENER_STRICT },
{ "\"\x10\"", -1, 1, json_tokener_error_parse_string, 1, JSON_TOKENER_STRICT },
{ "\"\x11\"", -1, 1, json_tokener_error_parse_string, 1, JSON_TOKENER_STRICT },
{ "\"\x12\"", -1, 1, json_tokener_error_parse_string, 1, JSON_TOKENER_STRICT },
{ "\"\x13\"", -1, 1, json_tokener_error_parse_string, 1, JSON_TOKENER_STRICT },
{ "\"\x14\"", -1, 1, json_tokener_error_parse_string, 1, JSON_TOKENER_STRICT },
{ "\"\x15\"", -1, 1, json_tokener_error_parse_string, 1, JSON_TOKENER_STRICT },
{ "\"\x16\"", -1, 1, json_tokener_error_parse_string, 1, JSON_TOKENER_STRICT },
{ "\"\x17\"", -1, 1, json_tokener_error_parse_string, 1, JSON_TOKENER_STRICT },
{ "\"\x18\"", -1, 1, json_tokener_error_parse_string, 1, JSON_TOKENER_STRICT },
{ "\"\x19\"", -1, 1, json_tokener_error_parse_string, 1, JSON_TOKENER_STRICT },
{ "\"\x1a\"", -1, 1, json_tokener_error_parse_string, 1, JSON_TOKENER_STRICT },
{ "\"\x1b\"", -1, 1, json_tokener_error_parse_string, 1, JSON_TOKENER_STRICT },
{ "\"\x1c\"", -1, 1, json_tokener_error_parse_string, 1, JSON_TOKENER_STRICT },
{ "\"\x1d\"", -1, 1, json_tokener_error_parse_string, 1, JSON_TOKENER_STRICT },
{ "\"\x1e\"", -1, 1, json_tokener_error_parse_string, 1, JSON_TOKENER_STRICT },
{ "\"\x1f\"", -1, 1, json_tokener_error_parse_string, 1, JSON_TOKENER_STRICT },
{NULL, -1, -1, json_tokener_success, 0, 0}, {NULL, -1, -1, json_tokener_success, 0, 0},
}; };

View File

@@ -2,11 +2,11 @@
#undef NDEBUG #undef NDEBUG
#endif #endif
#include "strerror_override.h" #include "strerror_override.h"
#ifdef WIN32 #ifdef _WIN32
#define WIN32_LEAN_AND_MEAN #define WIN32_LEAN_AND_MEAN
#include <io.h> #include <io.h>
#include <windows.h> #include <windows.h>
#endif /* defined(WIN32) */ #endif /* defined(_WIN32) */
#include <fcntl.h> #include <fcntl.h>
#include <limits.h> #include <limits.h>
#include <stddef.h> #include <stddef.h>

View File

@@ -8,9 +8,9 @@
#include "snprintf_compat.h" #include "snprintf_compat.h"
#ifndef WIN32 #ifndef _WIN32
#include <stdarg.h> #include <stdarg.h>
#endif /* !defined(WIN32) */ #endif /* !defined(_WIN32) */
#include <stdint.h> #include <stdint.h>
#include <stdlib.h> #include <stdlib.h>
@@ -18,10 +18,10 @@
/* CAW: compliant version of vasprintf */ /* CAW: compliant version of vasprintf */
static int vasprintf(char **buf, const char *fmt, va_list ap) static int vasprintf(char **buf, const char *fmt, va_list ap)
{ {
#ifndef WIN32 #ifndef _WIN32
static char _T_emptybuffer = '\0'; static char _T_emptybuffer = '\0';
va_list ap2; va_list ap2;
#endif /* !defined(WIN32) */ #endif /* !defined(_WIN32) */
int chars; int chars;
char *b; char *b;
@@ -30,16 +30,16 @@ static int vasprintf(char **buf, const char *fmt, va_list ap)
return -1; return -1;
} }
#ifdef WIN32 #ifdef _WIN32
chars = _vscprintf(fmt, ap); chars = _vscprintf(fmt, ap);
#else /* !defined(WIN32) */ #else /* !defined(_WIN32) */
/* CAW: RAWR! We have to hope to god here that vsnprintf doesn't overwrite /* CAW: RAWR! We have to hope to god here that vsnprintf doesn't overwrite
* our buffer like on some 64bit sun systems... but hey, it's time to move on * our buffer like on some 64bit sun systems... but hey, it's time to move on
*/ */
va_copy(ap2, ap); va_copy(ap2, ap);
chars = vsnprintf(&_T_emptybuffer, 0, fmt, ap2); chars = vsnprintf(&_T_emptybuffer, 0, fmt, ap2);
va_end(ap2); va_end(ap2);
#endif /* defined(WIN32) */ #endif /* defined(_WIN32) */
if (chars < 0 || (size_t)chars + 1 > SIZE_MAX / sizeof(char)) if (chars < 0 || (size_t)chars + 1 > SIZE_MAX / sizeof(char))
{ {
return -1; return -1;