diff --git a/doc/apiref.rst b/doc/apiref.rst index 2c57d2e..efafd12 100644 --- a/doc/apiref.rst +++ b/doc/apiref.rst @@ -765,6 +765,26 @@ is in UTF-8. *path* already exists, it is overwritten. *flags* is described above. Returns 0 on success and -1 on error. +.. type:: json_dump_callback_t + + A typedef for a function that's called by + :func:`json_dump_callback()`:: + + typedef int (*json_dump_callback_t)(const char *buffer, size_t size, void *data); + + *buffer* points to a buffer containing a chunk of output, *size* is + the length of the buffer, and *data* is the corresponding + :func:`json_dump_callback()` argument passed through. + + On error, the function should return -1 to stop the encoding + process. On success, it should return 0. + +.. function:: int json_dump_callback(const json_t *json, json_dump_callback_t callback, void *data, size_t flags) + + Call *callback* repeatedly, passing a chunk of the JSON + representation of *root* each time. *flags* is described above. + Returns 0 on success and -1 on error. + .. _apiref-decoding: diff --git a/src/dump.c b/src/dump.c index f32f667..344670a 100644 --- a/src/dump.c +++ b/src/dump.c @@ -19,8 +19,6 @@ #define MAX_INTEGER_STR_LENGTH 100 #define MAX_REAL_STR_LENGTH 100 -typedef int (*dump_func)(const char *buffer, int size, void *data); - struct string { char *buffer; @@ -28,12 +26,12 @@ struct string int size; }; -static int dump_to_strbuffer(const char *buffer, int size, void *data) +static int dump_to_strbuffer(const char *buffer, size_t size, void *data) { return strbuffer_append_bytes((strbuffer_t *)data, buffer, size); } -static int dump_to_file(const char *buffer, int size, void *data) +static int dump_to_file(const char *buffer, size_t size, void *data) { FILE *dest = (FILE *)data; if(fwrite(buffer, size, 1, dest) != 1) @@ -44,7 +42,7 @@ static int dump_to_file(const char *buffer, int size, void *data) /* 32 spaces (the maximum indentation size) */ static char whitespace[] = " "; -static int dump_indent(size_t flags, int depth, int space, dump_func dump, void *data) +static int dump_indent(size_t flags, int depth, int space, json_dump_callback_t dump, void *data) { if(JSON_INDENT(flags) > 0) { @@ -66,7 +64,7 @@ static int dump_indent(size_t flags, int depth, int space, dump_func dump, void return 0; } -static int dump_string(const char *str, int ascii, dump_func dump, void *data) +static int dump_string(const char *str, int ascii, json_dump_callback_t dump, void *data) { const char *pos, *end; int32_t codepoint; @@ -166,7 +164,7 @@ static int object_key_compare_serials(const void *key1, const void *key2) } static int do_dump(const json_t *json, size_t flags, int depth, - dump_func dump, void *data) + json_dump_callback_t dump, void *data) { int ascii = flags & JSON_ENSURE_ASCII ? 1 : 0; @@ -463,3 +461,13 @@ int json_dump_file(const json_t *json, const char *path, size_t flags) fclose(output); return result; } + +int json_dump_callback(const json_t *json, json_dump_callback_t callback, void *data, size_t flags) +{ + if(!(flags & JSON_ENCODE_ANY)) { + if(!json_is_array(json) && !json_is_object(json)) + return -1; + } + + return do_dump(json, flags, 0, callback, data); +} diff --git a/src/jansson.h b/src/jansson.h index 3abb4cf..8275e4f 100644 --- a/src/jansson.h +++ b/src/jansson.h @@ -234,10 +234,12 @@ json_t *json_load_file(const char *path, size_t flags, json_error_t *error); #define JSON_PRESERVE_ORDER 0x100 #define JSON_ENCODE_ANY 0x200 +typedef int (*json_dump_callback_t)(const char *buffer, size_t size, void *data); + char *json_dumps(const json_t *json, size_t flags); int json_dumpf(const json_t *json, FILE *output, size_t flags); int json_dump_file(const json_t *json, const char *path, size_t flags); - +int json_dump_callback(const json_t *json, json_dump_callback_t callback, void *data, size_t flags); /* custom memory allocation */ diff --git a/src/strbuffer.c b/src/strbuffer.c index 1b20e2b..b9190c2 100644 --- a/src/strbuffer.c +++ b/src/strbuffer.c @@ -13,6 +13,7 @@ #define STRBUFFER_MIN_SIZE 16 #define STRBUFFER_FACTOR 2 +#define STRBUFFER_SIZE_MAX ((size_t)-1) int strbuffer_init(strbuffer_t *strbuff) { @@ -64,13 +65,19 @@ int strbuffer_append_byte(strbuffer_t *strbuff, char byte) return strbuffer_append_bytes(strbuff, &byte, 1); } -int strbuffer_append_bytes(strbuffer_t *strbuff, const char *data, int size) +int strbuffer_append_bytes(strbuffer_t *strbuff, const char *data, size_t size) { - if(strbuff->length + size >= strbuff->size) + if(size >= strbuff->size - strbuff->length) { size_t new_size; char *new_value; + /* avoid integer overflow */ + if (strbuff->size > STRBUFFER_SIZE_MAX / STRBUFFER_FACTOR + || size > STRBUFFER_SIZE_MAX - 1 + || strbuff->length > STRBUFFER_SIZE_MAX - 1 - size) + return -1; + new_size = max(strbuff->size * STRBUFFER_FACTOR, strbuff->length + size + 1); diff --git a/src/strbuffer.h b/src/strbuffer.h index b21ef8b..9764251 100644 --- a/src/strbuffer.h +++ b/src/strbuffer.h @@ -10,8 +10,8 @@ typedef struct { char *value; - int length; /* bytes used */ - int size; /* bytes allocated */ + size_t length; /* bytes used */ + size_t size; /* bytes allocated */ } strbuffer_t; int strbuffer_init(strbuffer_t *strbuff); @@ -24,7 +24,7 @@ char *strbuffer_steal_value(strbuffer_t *strbuff); int strbuffer_append(strbuffer_t *strbuff, const char *string); int strbuffer_append_byte(strbuffer_t *strbuff, char byte); -int strbuffer_append_bytes(strbuffer_t *strbuff, const char *data, int size); +int strbuffer_append_bytes(strbuffer_t *strbuff, const char *data, size_t size); char strbuffer_pop(strbuffer_t *strbuff); diff --git a/test/.gitignore b/test/.gitignore index 278e5f5..7b7203d 100644 --- a/test/.gitignore +++ b/test/.gitignore @@ -4,6 +4,7 @@ suites/api/test_array suites/api/test_copy suites/api/test_cpp suites/api/test_dump +suites/api/test_dump_callback suites/api/test_equal suites/api/test_load suites/api/test_loadb diff --git a/test/suites/api/Makefile.am b/test/suites/api/Makefile.am index 58c31c6..5f96505 100644 --- a/test/suites/api/Makefile.am +++ b/test/suites/api/Makefile.am @@ -4,6 +4,7 @@ check_PROGRAMS = \ test_array \ test_copy \ test_dump \ + test_dump_callback \ test_equal \ test_load \ test_loadb \ @@ -17,6 +18,7 @@ check_PROGRAMS = \ test_array_SOURCES = test_array.c util.h test_copy_SOURCES = test_copy.c util.h test_dump_SOURCES = test_dump.c util.h +test_dump_callback_SOURCES = test_dump_callback.c util.h test_load_SOURCES = test_load.c util.h test_loadb_SOURCES = test_loadb.c util.h test_memory_funcs_SOURCES = test_memory_funcs.c util.h diff --git a/test/suites/api/check-exports b/test/suites/api/check-exports index 6df5a48..802d683 100755 --- a/test/suites/api/check-exports +++ b/test/suites/api/check-exports @@ -47,6 +47,7 @@ json_object_iter_set_new json_dumps json_dumpf json_dump_file +json_dump_callback json_loads json_loadf json_load_file diff --git a/test/suites/api/test_dump_callback.c b/test/suites/api/test_dump_callback.c new file mode 100644 index 0000000..cb71e1b --- /dev/null +++ b/test/suites/api/test_dump_callback.c @@ -0,0 +1,83 @@ +/* + * Copyright (c) 2009-2011 Petri Lehtinen + * + * Jansson is free software; you can redistribute it and/or modify + * it under the terms of the MIT license. See LICENSE for details. + */ + +#include +#include +#include +#include "util.h" + +struct my_sink { + char *buf; + size_t off; + size_t cap; +}; + +static int my_writer(const char *buffer, size_t len, void *data) { + struct my_sink *s = data; + if (len > s->cap - s->off) { + return -1; + } + memcpy(s->buf + s->off, buffer, len); + s->off += len; + return 0; +} + +int main(void) +{ + struct my_sink s; + json_t *json; + const char str[] = "[\"A\", {\"B\": \"C\", \"e\": false}, 1, null, \"foo\"]"; + char *dumped_to_string; + + json = json_loads(str, 0, NULL); + if(!json) { + fail("json_loads failed"); + } + + dumped_to_string = json_dumps(json, 0); + if (!dumped_to_string) { + json_decref(json); + fail("json_dumps failed"); + } + + s.off = 0; + s.cap = strlen(dumped_to_string); + s.buf = malloc(s.cap); + if (!s.buf) { + json_decref(json); + free(dumped_to_string); + fail("malloc failed"); + } + + if (json_dump_callback(json, my_writer, &s, 0) == -1) { + json_decref(json); + free(dumped_to_string); + free(s.buf); + fail("json_dump_callback failed on an exact-length sink buffer"); + } + + if (strncmp(dumped_to_string, s.buf, s.off) != 0) { + json_decref(json); + free(dumped_to_string); + free(s.buf); + fail("json_dump_callback and json_dumps did not produce identical output"); + } + + s.off = 1; + if (json_dump_callback(json, my_writer, &s, 0) != -1) { + json_decref(json); + free(dumped_to_string); + free(s.buf); + fail("json_dump_callback succeeded on a short buffer when it should have failed"); + } + + json_decref(json); + free(dumped_to_string); + free(s.buf); + return EXIT_SUCCESS; +} +