string_t *object_member;
struct json_data content_data;
+ struct json_string_istream *str_stream;
+ size_t str_stream_threshold;
+ size_t str_stream_max_buffer_size;
+
char *error;
bool parsed_nul_char:1;
bool parsed_control_char:1;
bool parsed_float:1;
+ bool streaming_string:1;
bool callback_interrupted:1;
bool callback_running:1;
bool finished_level:1;
bool have_object_member:1;
};
+static struct istream *
+json_string_stream_create(struct json_parser *parser, bool complete);
+
static inline bool json_parser_is_busy(struct json_parser *parser)
{
return (parser->level_stack_pos > 0 || parser->cur < parser->end);
void *list_context)
{
struct json_value value;
+ int ret;
+
+ if (parser->str_stream != NULL)
+ return JSON_PARSE_BOUNDARY;
+ if (parser->streaming_string) {
+ parser->streaming_string = FALSE;
+ return JSON_PARSE_OK;
+ }
i_zero(&value);
+ if (parser->str_stream_max_buffer_size > 0) {
+ if (str_len(parser->buffer) >= parser->str_stream_threshold) {
+ value.content_type = JSON_CONTENT_TYPE_STREAM;
+ value.content.stream =
+ json_string_stream_create(parser, TRUE);
+ ret = json_parser_callback_parse_value(
+ parser, list_context, JSON_TYPE_STRING,
+ &value);
+ i_stream_unref(&value.content.stream);
+ parser->streaming_string = TRUE;
+ if (ret < JSON_PARSE_OK)
+ return ret;
+ return JSON_PARSE_INTERRUPTED;
+ }
+ }
+
if (parser->parsed_nul_char ||
(parser->flags & JSON_PARSER_FLAG_STRINGS_AS_DATA) != 0) {
struct json_data *data = &parser->content_data;
JSON_TYPE_STRING, &value);
}
+static int
+json_parser_callback_string_stream(struct json_parser *parser,
+ void *list_context)
+{
+ struct json_value value;
+ int ret;
+
+ if (parser->streaming_string)
+ return JSON_PARSE_OK;
+ parser->streaming_string = TRUE;
+
+ i_zero(&value);
+ value.content_type = JSON_CONTENT_TYPE_STREAM;
+ value.content.stream = json_string_stream_create(parser, FALSE);
+
+ ret = json_parser_callback_parse_value(parser, list_context,
+ JSON_TYPE_STRING, &value);
+ i_stream_unref(&value.content.stream);
+ return ret;
+}
+
static int
json_parser_callback_true_value(struct json_parser *parser,
- void *list_context)
+ void *list_context)
{
struct json_value value;
json_parser_do_parse_string_value(struct json_parser *parser,
struct json_parser_state *state)
{
- size_t max_size = parser->limits.max_string_size;
+ size_t max_size;
+
+ if (parser->str_stream_max_buffer_size > 0)
+ max_size = parser->str_stream_max_buffer_size;
+ else
+ max_size = parser->limits.max_string_size;
+
+ if (parser->str_stream == NULL &&
+ parser->str_stream_max_buffer_size > 0 &&
+ max_size > parser->str_stream_threshold) {
+ /* Return string stream immediately once the treshold is
+ crossed */
+ max_size = parser->str_stream_threshold;
+ }
return json_parser_do_parse_string(parser, state, max_size);
}
if (ret < JSON_PARSE_OK) {
if (ret != JSON_PARSE_OVERFLOW)
return ret;
- json_parser_error(parser,
- "Excessive string size (> %zu)",
- parser->limits.max_string_size);
- return JSON_PARSE_ERROR;
+ if (parser->str_stream_max_buffer_size == 0) {
+ json_parser_error(parser,
+ "Excessive string size (> %zu)",
+ parser->limits.max_string_size);
+ return JSON_PARSE_ERROR;
+ }
+ ret = json_parser_callback_string_stream(
+ parser, parent_context);
+ if (ret < JSON_PARSE_OK)
+ return ret;
+ return JSON_PARSE_INTERRUPTED;
}
state->state = _VALUE_WS;
ret = json_parser_callback_string_value(
ret = json_parser_skip_ws(parser);
if (ret < JSON_PARSE_OK)
return ret;
+ parser->streaming_string = FALSE;
state->state = _VALUE_END;
return JSON_PARSE_OK;
default:
{
int ret;
+ i_assert(parser->str_stream == NULL);
+
*error_r = NULL;
ret = json_parser_continue(parser);
loc_r->value_line = parser->loc.value_line_number;
loc_r->column = parser->loc.column;
}
+
+/*
+ *
+ */
+
+struct json_string_istream {
+ struct istream_private istream;
+
+ struct json_parser *parser;
+
+ bool buffer_overflowed:1;
+ bool ended:1;
+};
+
+static ssize_t json_string_istream_read(struct istream_private *stream)
+{
+ struct json_string_istream *jstream =
+ (struct json_string_istream *)stream;
+ struct json_parser *parser = jstream->parser;
+ bool read_data;
+ size_t old_pos;
+ int ret;
+
+ if (jstream->ended) {
+ stream->istream.eof = TRUE;
+ return -1;
+ }
+ i_assert(jstream->parser != NULL);
+
+ i_assert(stream->pos == str_len(parser->buffer));
+ i_assert(stream->skip <= stream->pos);
+
+ read_data = FALSE;
+ do {
+ if (jstream->buffer_overflowed) {
+ stream->pos = str_len(parser->buffer);
+ if (stream->skip == stream->pos)
+ str_truncate(parser->buffer, 0);
+ else if (stream->skip > 0)
+ str_delete(parser->buffer, 0, stream->skip);
+ else
+ return -2;
+ stream->skip = 0;
+ jstream->buffer_overflowed = FALSE;
+ }
+
+ old_pos = str_len(parser->buffer);
+ ret = json_parser_continue(parser);
+ i_assert(str_len(parser->buffer) >= old_pos);
+ read_data = str_len(parser->buffer) > old_pos;
+ switch (ret) {
+ case JSON_PARSE_INTERRUPTED:
+ i_assert(stream->skip == 0 ||
+ !jstream->buffer_overflowed);
+ jstream->buffer_overflowed = TRUE;
+ break;
+ case JSON_PARSE_BOUNDARY:
+ jstream->ended = TRUE;
+ read_data = TRUE;
+ if (str_len(parser->buffer) == old_pos) {
+ stream->istream.eof = TRUE;
+ return -1;
+ }
+ break;
+ case JSON_PARSE_NO_DATA:
+ stream->buffer = str_data(parser->buffer);
+ stream->pos = str_len(parser->buffer);
+ return 0;
+ case JSON_PARSE_ERROR:
+ io_stream_set_error(&stream->iostream,
+ "%s", parser->error);
+ stream->istream.stream_errno = EINVAL;
+ return -1;
+ case JSON_PARSE_UNEXPECTED_EOF:
+ io_stream_set_error(&stream->iostream,
+ "%s", parser->error);
+ stream->istream.stream_errno = EPIPE;
+ return -1;
+ default:
+ i_unreached();
+ }
+ } while (jstream->buffer_overflowed && !read_data);
+
+ stream->pos = str_len(parser->buffer);
+ stream->buffer = str_data(parser->buffer);
+ return (ssize_t)(stream->pos - old_pos);
+}
+
+static void
+json_string_istream_set_max_buffer_size(struct iostream_private *stream,
+ size_t max_size)
+{
+ struct json_string_istream *jstream =
+ (struct json_string_istream *)stream;
+
+ i_assert(max_size > 0);
+ jstream->parser->str_stream_max_buffer_size = max_size;
+}
+
+static void
+json_string_istream_close(struct iostream_private *stream,
+ bool close_parent ATTR_UNUSED)
+{
+ struct json_string_istream *jstream =
+ (struct json_string_istream *)stream;
+
+ if (jstream->parser != NULL)
+ jstream->parser->str_stream = NULL;
+}
+
+static struct istream *
+json_string_stream_create(struct json_parser *parser, bool complete)
+{
+ struct json_string_istream *jstream;
+ struct istream *stream;
+ const char *name;
+
+ i_assert(parser->str_stream == NULL);
+
+ jstream = i_new(struct json_string_istream, 1);
+ jstream->parser = parser;
+
+ jstream->ended = complete;
+ jstream->istream.pos = str_len(parser->buffer);
+ jstream->istream.buffer = str_data(parser->buffer);
+
+ jstream->istream.max_buffer_size = parser->str_stream_max_buffer_size;
+ jstream->istream.iostream.set_max_buffer_size =
+ json_string_istream_set_max_buffer_size;
+ jstream->istream.iostream.close =
+ json_string_istream_close;
+ jstream->istream.read = json_string_istream_read;
+
+ jstream->istream.istream.readable_fd = FALSE;
+ jstream->istream.istream.blocking = parser->input->blocking;
+ jstream->istream.istream.seekable = FALSE;
+
+ parser->str_stream = jstream;
+
+ stream = i_stream_create(&jstream->istream, NULL,
+ i_stream_get_fd(parser->input), 0);
+
+ name = i_stream_get_name(parser->input);
+ if (name == NULL || *name == '\0') {
+ i_stream_set_name(stream, "(JSON string)");
+ } else {
+ i_stream_set_name(stream, t_strdup_printf(
+ "(JSON string parsed from %s)", name));
+ }
+ return stream;
+}
+
+void json_parser_enable_string_stream(struct json_parser *parser,
+ size_t threshold, size_t max_buffer_size)
+{
+ i_assert(max_buffer_size > 0);
+ if (threshold > max_buffer_size)
+ threshold = max_buffer_size;
+ parser->str_stream_threshold = threshold;
+ parser->str_stream_max_buffer_size = max_buffer_size;
+}
+
+void json_parser_disable_string_stream(struct json_parser *parser)
+{
+ parser->str_stream_max_buffer_size = 0;
+}
#ifndef JSON_TYPES_H
#define JSON_TYPES_H
+#include "istream.h"
+
struct json_data;
struct json_value;
struct json_node;
JSON_CONTENT_TYPE_STRING,
/* data buffer (for string/text containing \u0000) */
JSON_CONTENT_TYPE_DATA,
+ /* data stream (for potentially very long string/text) */
+ JSON_CONTENT_TYPE_STREAM,
/* integer number */
JSON_CONTENT_TYPE_INTEGER,
};
const char *str;
/* JSON_CONTENT_TYPE_DATA */
struct json_data *data;
+ /* JSON_CONTENT_TYPE_STREAM */
+ struct istream *stream;
/* JSON_CONTENT_TYPE_INTEGER */
intmax_t intnum;
} content;
}
JSON_VALUE_GET_U__TEMPLATE(json_value_get_uint,
- unsigned int, UINT_MAX)
+ unsigned int, UINT_MAX)
JSON_VALUE_GET_U__TEMPLATE(json_value_get_ulong,
- unsigned long, ULONG_MAX)
+ unsigned long, ULONG_MAX)
JSON_VALUE_GET_U__TEMPLATE(json_value_get_ullong,
- unsigned long long, ULLONG_MAX)
+ unsigned long long, ULLONG_MAX)
JSON_VALUE_GET_U__TEMPLATE(json_value_get_uint32,
- uint32_t, UINT32_MAX)
+ uint32_t, UINT32_MAX)
JSON_VALUE_GET_U__TEMPLATE(json_value_get_uint64,
- uint64_t, UINT64_MAX)
+ uint64_t, UINT64_MAX)
#define JSON_VALUE_GET_S__TEMPLATE(name, type, int_min, int_max) \
static inline int \
}
JSON_VALUE_GET_S__TEMPLATE(json_value_get_int,
- int, INT_MIN, INT_MAX)
+ int, INT_MIN, INT_MAX)
JSON_VALUE_GET_S__TEMPLATE(json_value_get_long,
- long, LONG_MIN, LONG_MAX)
+ long, LONG_MIN, LONG_MAX)
JSON_VALUE_GET_S__TEMPLATE(json_value_get_llong,
- long long, LLONG_MIN, LLONG_MAX)
+ long long, LLONG_MIN, LLONG_MAX)
JSON_VALUE_GET_S__TEMPLATE(json_value_get_int32,
- int32_t, INT32_MIN, INT32_MAX)
+ int32_t, INT32_MIN, INT32_MAX)
JSON_VALUE_GET_S__TEMPLATE(json_value_get_int64,
- int64_t, INT64_MIN, INT64_MAX)
+ int64_t, INT64_MIN, INT64_MAX)
#define JSON_VALUE_GET_F__TEMPLATE(name, type) \
static inline int \
i_unreached();
}
+static inline ATTR_PURE int
+json_value_get_stream(const struct json_value *jvalue,
+ struct istream **stream_r)
+{
+ if (jvalue->content_type != JSON_CONTENT_TYPE_STREAM)
+ return -1;
+ *stream_r = jvalue->content.stream;
+ i_stream_ref(*stream_r);
+ return 0;
+}
+
/*
* Node
*/
return json_value_get_data(&jnode->value, size_r);
}
+static inline int
+json_node_get_stream(const struct json_node *jnode,
+ struct istream **stream_r)
+{
+ if (jnode->type != JSON_TYPE_STRING)
+ return -1;
+
+ return json_value_get_stream(&jnode->value, stream_r);
+}
+
/* number */
static inline ATTR_PURE bool
* Limits
*/
+// NOTE: There is currently no support for reading enormous object member names
+// incrementally. This is usually not needed, but it could sometimes be
+// nice to have.
+
#define JSON_DEFAULT_MAX_NAME_SIZE 1024
#define JSON_DEFAULT_MAX_STRING_SIZE 32*1024
#define JSON_DEFAULT_MAX_NESTING 32
} T_END;
}
+/*
+ * Test: stream parse tests
+ */
+
+struct json_stream_parse_test {
+ const char *input, *output;
+ struct json_limits limits;
+ enum json_parser_flags flags;
+};
+
+static const struct json_stream_parse_test
+stream_parse_tests[] = {
+ {
+ .input = "\"AABBCC\"",
+ .output = "AABBCC"
+ },{
+ .input = "\""
+ "AABBCCDDEEFFGGHHIIJJKKLLMMNNOOPPQQRRSSTTUUVVWWXXYYZZ"
+ "AABBCCDDEEFFGGHHIIJJKKLLMMNNOOPPQQRRSSTTUUVVWWXXYYZZ"
+ "AABBCCDDEEFFGGHHIIJJKKLLMMNNOOPPQQRRSSTTUUVVWWXXYYZZ"
+ "AABBCCDDEEFFGGHHIIJJKKLLMMNNOOPPQQRRSSTTUUVVWWXXYYZZ"
+ "AABBCCDDEEFFGGHHIIJJKKLLMMNNOOPPQQRRSSTTUUVVWWXXYYZZ"
+ "AABBCCDDEEFFGGHHIIJJKKLLMMNNOOPPQQRRSSTTUUVVWWXXYYZZ"
+ "AABBCCDDEEFFGGHHIIJJKKLLMMNNOOPPQQRRSSTTUUVVWWXXYYZZ"
+ "AABBCCDDEEFFGGHHIIJJKKLLMMNNOOPPQQRRSSTTUUVVWWXXYYZZ"
+ "AABBCCDDEEFFGGHHIIJJKKLLMMNNOOPPQQRRSSTTUUVVWWXXYYZZ"
+ "\"",
+ .output =
+ "AABBCCDDEEFFGGHHIIJJKKLLMMNNOOPPQQRRSSTTUUVVWWXXYYZZ"
+ "AABBCCDDEEFFGGHHIIJJKKLLMMNNOOPPQQRRSSTTUUVVWWXXYYZZ"
+ "AABBCCDDEEFFGGHHIIJJKKLLMMNNOOPPQQRRSSTTUUVVWWXXYYZZ"
+ "AABBCCDDEEFFGGHHIIJJKKLLMMNNOOPPQQRRSSTTUUVVWWXXYYZZ"
+ "AABBCCDDEEFFGGHHIIJJKKLLMMNNOOPPQQRRSSTTUUVVWWXXYYZZ"
+ "AABBCCDDEEFFGGHHIIJJKKLLMMNNOOPPQQRRSSTTUUVVWWXXYYZZ"
+ "AABBCCDDEEFFGGHHIIJJKKLLMMNNOOPPQQRRSSTTUUVVWWXXYYZZ"
+ "AABBCCDDEEFFGGHHIIJJKKLLMMNNOOPPQQRRSSTTUUVVWWXXYYZZ"
+ "AABBCCDDEEFFGGHHIIJJKKLLMMNNOOPPQQRRSSTTUUVVWWXXYYZZ"
+ },{
+ .input = "[\""
+ "AABBCCDDEEFFGGHHIIJJKKLLMMNNOOPPQQRRSSTTUUVVWWXXYYZZ"
+ "AABBCCDDEEFFGGHHIIJJKKLLMMNNOOPPQQRRSSTTUUVVWWXXYYZZ"
+ "AABBCCDDEEFFGGHHIIJJKKLLMMNNOOPPQQRRSSTTUUVVWWXXYYZZ"
+ "AABBCCDDEEFFGGHHIIJJKKLLMMNNOOPPQQRRSSTTUUVVWWXXYYZZ"
+ "AABBCCDDEEFFGGHHIIJJKKLLMMNNOOPPQQRRSSTTUUVVWWXXYYZZ"
+ "AABBCCDDEEFFGGHHIIJJKKLLMMNNOOPPQQRRSSTTUUVVWWXXYYZZ"
+ "AABBCCDDEEFFGGHHIIJJKKLLMMNNOOPPQQRRSSTTUUVVWWXXYYZZ"
+ "AABBCCDDEEFFGGHHIIJJKKLLMMNNOOPPQQRRSSTTUUVVWWXXYYZZ"
+ "AABBCCDDEEFFGGHHIIJJKKLLMMNNOOPPQQRRSSTTUUVVWWXXYYZZ"
+ "\"]",
+ .output =
+ "AABBCCDDEEFFGGHHIIJJKKLLMMNNOOPPQQRRSSTTUUVVWWXXYYZZ"
+ "AABBCCDDEEFFGGHHIIJJKKLLMMNNOOPPQQRRSSTTUUVVWWXXYYZZ"
+ "AABBCCDDEEFFGGHHIIJJKKLLMMNNOOPPQQRRSSTTUUVVWWXXYYZZ"
+ "AABBCCDDEEFFGGHHIIJJKKLLMMNNOOPPQQRRSSTTUUVVWWXXYYZZ"
+ "AABBCCDDEEFFGGHHIIJJKKLLMMNNOOPPQQRRSSTTUUVVWWXXYYZZ"
+ "AABBCCDDEEFFGGHHIIJJKKLLMMNNOOPPQQRRSSTTUUVVWWXXYYZZ"
+ "AABBCCDDEEFFGGHHIIJJKKLLMMNNOOPPQQRRSSTTUUVVWWXXYYZZ"
+ "AABBCCDDEEFFGGHHIIJJKKLLMMNNOOPPQQRRSSTTUUVVWWXXYYZZ"
+ "AABBCCDDEEFFGGHHIIJJKKLLMMNNOOPPQQRRSSTTUUVVWWXXYYZZ"
+ },{
+ .input = " [ \""
+ "AABBCCDDEEFFGGHHIIJJKKLLMMNNOOPPQQRRSSTTUUVVWWXXYYZZ"
+ "AABBCCDDEEFFGGHHIIJJKKLLMMNNOOPPQQRRSSTTUUVVWWXXYYZZ"
+ "AABBCCDDEEFFGGHHIIJJKKLLMMNNOOPPQQRRSSTTUUVVWWXXYYZZ"
+ "AABBCCDDEEFFGGHHIIJJKKLLMMNNOOPPQQRRSSTTUUVVWWXXYYZZ"
+ "AABBCCDDEEFFGGHHIIJJKKLLMMNNOOPPQQRRSSTTUUVVWWXXYYZZ"
+ "AABBCCDDEEFFGGHHIIJJKKLLMMNNOOPPQQRRSSTTUUVVWWXXYYZZ"
+ "AABBCCDDEEFFGGHHIIJJKKLLMMNNOOPPQQRRSSTTUUVVWWXXYYZZ"
+ "AABBCCDDEEFFGGHHIIJJKKLLMMNNOOPPQQRRSSTTUUVVWWXXYYZZ"
+ "AABBCCDDEEFFGGHHIIJJKKLLMMNNOOPPQQRRSSTTUUVVWWXXYYZZ"
+ "\" ] ",
+ .output =
+ "AABBCCDDEEFFGGHHIIJJKKLLMMNNOOPPQQRRSSTTUUVVWWXXYYZZ"
+ "AABBCCDDEEFFGGHHIIJJKKLLMMNNOOPPQQRRSSTTUUVVWWXXYYZZ"
+ "AABBCCDDEEFFGGHHIIJJKKLLMMNNOOPPQQRRSSTTUUVVWWXXYYZZ"
+ "AABBCCDDEEFFGGHHIIJJKKLLMMNNOOPPQQRRSSTTUUVVWWXXYYZZ"
+ "AABBCCDDEEFFGGHHIIJJKKLLMMNNOOPPQQRRSSTTUUVVWWXXYYZZ"
+ "AABBCCDDEEFFGGHHIIJJKKLLMMNNOOPPQQRRSSTTUUVVWWXXYYZZ"
+ "AABBCCDDEEFFGGHHIIJJKKLLMMNNOOPPQQRRSSTTUUVVWWXXYYZZ"
+ "AABBCCDDEEFFGGHHIIJJKKLLMMNNOOPPQQRRSSTTUUVVWWXXYYZZ"
+ "AABBCCDDEEFFGGHHIIJJKKLLMMNNOOPPQQRRSSTTUUVVWWXXYYZZ"
+ },{
+ .input = "\"foo\\\\\\\"\\b\\f\\n\\r\\t\\u0001\\uffff\"",
+ .output = "foo\\\"\b\f\n\r\t\001\xEF\xBF\xBF"
+ },{
+ .input = "\"\\ud801\\udc37\"",
+ .output = "\xf0\x90\x90\xb7"
+ },{
+ .input = "\"\"",
+ .output = ""
+ }
+};
+
+static const unsigned int stream_parse_test_count =
+ N_ELEMENTS(stream_parse_tests);
+
+static void
+test_parse_stream_parse_value(void *context,
+ void *parent_context ATTR_UNUSED,
+ const char *name ATTR_UNUSED, enum json_type type,
+ const struct json_value *value)
+{
+ struct istream **str_stream_r = (struct istream **)context;
+
+ test_assert(type == JSON_TYPE_STRING);
+ test_assert(value->content_type == JSON_CONTENT_TYPE_STREAM);
+ *str_stream_r = value->content.stream;
+ i_stream_ref(value->content.stream);
+}
+
+static struct json_parser_callbacks parse_stream_callbacks = {
+ .parse_value = test_parse_stream_parse_value
+};
+
+static void test_json_parse_stream(void)
+{
+ static const unsigned int trickle_steps[] = {1,2,3,4,5,10,20};
+ string_t *buffer;
+ unsigned int i, j;
+
+ buffer = str_new(default_pool, 256);
+
+ for (i = 0; i < stream_parse_test_count; i++) T_BEGIN {
+ const struct json_stream_parse_test *test;
+ struct istream *input, *str_input;
+ struct json_parser *parser;
+ const char *text, *error = NULL;
+ unsigned int pos, text_len;
+ int ret = 0;
+
+ test = &stream_parse_tests[i];
+
+ text = test->input;
+ text_len = strlen(text);
+
+ input = test_istream_create_data(text, text_len);
+
+ test_begin(t_strdup_printf("json parse stream [%u]", i));
+
+ /* trickle tests */
+ for (j = 0; j < N_ELEMENTS(trickle_steps); j++) {
+ unsigned int trickle_step = trickle_steps[j];
+
+ i_stream_seek(input, 0);
+ str_input = NULL;
+ str_truncate(buffer, 0);
+
+ parser = json_parser_init(input,
+ NULL, 0, &parse_stream_callbacks, &str_input);
+ json_parser_enable_string_stream(parser, 0, 10);
+
+ ret = 0;
+ for (pos = 0; pos <= text_len+1000 && ret == 0; pos += trickle_step) {
+ test_istream_set_size(input, pos);
+ if (str_input == NULL) {
+ ret = json_parse_more(parser, &error);
+ if (ret < 0)
+ break;
+ }
+ if (str_input != NULL) {
+ const unsigned char *data;
+ size_t size;
+
+ while ((ret = i_stream_read_more(str_input,
+ &data, &size)) > 0) {
+ buffer_append(buffer, data, size);
+ i_stream_skip(str_input, size);
+ }
+ if (ret < 0) {
+ i_assert(!i_stream_have_bytes_left(str_input));
+ i_stream_skip(str_input, size);
+ i_stream_unref(&str_input);
+ ret = 0;
+ }
+ }
+ }
+ test_out_reason_quiet(
+ t_strdup_printf("parse success "
+ "(trickle, step=%u)",
+ trickle_step),
+ ret > 0, error);
+ test_out_quiet("stream output",
+ strcmp(str_c(buffer),
+ test->output) == 0);
+ json_parser_deinit(&parser);
+ }
+
+ /* buffered test */
+ i_stream_seek(input, 0);
+ str_truncate(buffer, 0);
+
+ parser = json_parser_init(input,
+ NULL, 0, &parse_stream_callbacks, &str_input);
+ json_parser_enable_string_stream(parser, 0, 10);
+
+ test_istream_set_size(input, text_len);
+ ret = json_parse_more(parser, &error);
+ test_out_reason_quiet("parse success (buffered) #1",
+ ret == 0, error);
+ if (ret == 0 && str_input != NULL) {
+ const unsigned char *data;
+ size_t size;
+
+ while ((ret = i_stream_read_more(str_input,
+ &data, &size)) > 0) {
+ buffer_append(buffer, data, size);
+ i_stream_skip(str_input, size);
+ }
+ i_assert (ret != 0);
+ if (ret < 0) {
+ i_assert(!i_stream_have_bytes_left(str_input));
+ i_stream_skip(str_input, size);
+ i_stream_unref(&str_input);
+ ret = 0;
+ }
+ }
+ if (ret == 0) {
+ ret = json_parse_more(parser, &error);
+ test_out_reason_quiet("parse success (buffered) #2",
+ ret > 0, error);
+ }
+ test_out_quiet("stream output",
+ strcmp(str_c(buffer), test->output) == 0);
+ json_parser_deinit(&parser);
+
+ test_end();
+
+ i_stream_unref(&input);
+ } T_END;
+
+ str_free(&buffer);
+}
+
+/*
+ * Test: stream parse error tests
+ */
+
+struct json_stream_parse_error_test {
+ const char *input;
+ struct json_limits limits;
+ enum json_parser_flags flags;
+ int stream_errno;
+};
+
+static const struct json_stream_parse_error_test
+stream_parse_error_tests[] = {
+ /* invalid escape */
+ {
+ .input = "\"foo\\?\"",
+ .stream_errno = EINVAL,
+ /* just a DQUOTE */
+ },{
+ .input = "\"",
+ .stream_errno = EPIPE,
+ /* unterminated string, escaped DQUOTE */
+ },{
+ .input = "\"\\\"",
+ .stream_errno = EPIPE,
+ /* unterminated string */
+ },{
+ .input = "\"foo",
+ .stream_errno = EPIPE,
+ /* high surrogate alone, unterminated string */
+ },{
+ .input = "\"\\ud801",
+ .stream_errno = EPIPE,
+ /* high surrogate alone */
+ },{
+ .input = "\"\\ud801\"",
+ .stream_errno = EINVAL,
+ /* low surrogate before high */
+ },{
+ .input = "\"\\udced\\udc37\"",
+ .stream_errno = EINVAL,
+ /* has extra 1 in middle */
+ },{
+ .input = "\"\\ud8011\\udc37\"",
+ .stream_errno = EINVAL,
+ /* has extra TAB in middle */
+ },{
+ .input = "\"\\ud801\\t\\udc37\"",
+ .stream_errno = EINVAL,
+ /* low surrogate before high with valid prefix*/
+ },{
+ .input = "\"hello \\udc37\"",
+ .stream_errno = EINVAL,
+ /* high surrogate alone with valid prefix */
+ },{
+ .input = "\"hello \\ud801",
+ .stream_errno = EPIPE,
+ /* invalid hex value */
+ },{
+ .input = "\"\\uabcg",
+ .stream_errno = EINVAL,
+ /* invalid escape */
+ },{
+ .input = "\"\\xFF\\xFF\\xFF\"",
+ .stream_errno = EINVAL,
+ }
+};
+
+static const unsigned int stream_parse_error_test_count =
+ N_ELEMENTS(stream_parse_error_tests);
+
+static void
+test_parse_stream_parse_error_value(void *context,
+ void *parent_context ATTR_UNUSED,
+ const char *name ATTR_UNUSED,
+ enum json_type type,
+ const struct json_value *value)
+{
+ struct istream **str_stream_r = (struct istream **)context;
+
+ test_assert(type == JSON_TYPE_STRING);
+ test_assert(value->content_type == JSON_CONTENT_TYPE_STREAM);
+ *str_stream_r = value->content.stream;
+ i_stream_ref(value->content.stream);
+}
+
+static struct json_parser_callbacks parse_stream_error_callbacks = {
+ .parse_value = test_parse_stream_parse_error_value
+};
+
+static void test_json_parse_stream_error(void)
+{
+ static const unsigned int trickle_steps[] = {1,2,3,4,5,10,20};
+ string_t *buffer;
+ unsigned int i, j;
+
+ buffer = str_new(default_pool, 256);
+
+ for (i = 0; i < stream_parse_error_test_count; i++) T_BEGIN {
+ const struct json_stream_parse_error_test *test;
+ struct istream *input, *str_input;
+ struct json_parser *parser;
+ const char *text, *error = NULL;
+ unsigned int pos, text_len;
+ int ret = 0;
+
+ test = &stream_parse_error_tests[i];
+
+ text = test->input;
+ text_len = strlen(text);
+
+ input = test_istream_create_data(text, text_len);
+
+ test_begin(t_strdup_printf("json parse stream error [%u]", i));
+
+ /* trickle tests */
+ for (j = 0; j < N_ELEMENTS(trickle_steps); j++) {
+ unsigned int trickle_step = trickle_steps[j];
+
+ i_stream_seek(input, 0);
+ str_input = NULL;
+ str_truncate(buffer, 0);
+
+ parser = json_parser_init(input,
+ NULL, 0, &parse_stream_error_callbacks, &str_input);
+ json_parser_enable_string_stream(parser, 0, 10);
+
+ ret = 0;
+ for (pos = 0; pos <= text_len+1000 && ret == 0; pos += trickle_step) {
+ test_istream_set_size(input, pos);
+ if (str_input == NULL) {
+ ret = json_parse_more(parser, &error);
+ if (ret < 0)
+ break;
+ }
+ if (str_input != NULL) {
+ const unsigned char *data;
+ size_t size;
+
+ while ((ret = i_stream_read_more(str_input,
+ &data, &size)) > 0) {
+ buffer_append(buffer, data, size);
+ i_stream_skip(str_input, size);
+ }
+ if (ret < 0) {
+ test_assert(str_input->stream_errno != 0);
+ test_out_quiet("stream errno",
+ str_input->stream_errno == test->stream_errno);
+ i_stream_skip(str_input, size);
+ i_stream_unref(&str_input);
+ ret = 0;
+ }
+ }
+ }
+ test_out_reason_quiet(
+ t_strdup_printf("parse failure "
+ "(trickle, step=%u)",
+ trickle_step),
+ ret < 0, error);
+ json_parser_deinit(&parser);
+ }
+
+ /* buffered test */
+ i_stream_seek(input, 0);
+ str_truncate(buffer, 0);
+
+ parser = json_parser_init(input,
+ NULL, 0, &parse_stream_error_callbacks, &str_input);
+ json_parser_enable_string_stream(parser, 0, 10);
+
+ test_istream_set_size(input, text_len);
+ ret = json_parse_more(parser, &error);
+ test_out_reason_quiet("parse failure (buffered) #1",
+ ret <= 0, error);
+ if (ret == 0 && str_input != NULL) {
+ const unsigned char *data;
+ size_t size;
+
+ while ((ret = i_stream_read_more(str_input,
+ &data, &size)) > 0) {
+ buffer_append(buffer, data, size);
+ i_stream_skip(str_input, size);
+ }
+ i_assert (ret != 0);
+ if (ret < 0) {
+ test_assert(str_input->stream_errno != 0);
+ test_out_quiet("stream errno",
+ str_input->stream_errno == test->stream_errno);
+ i_stream_skip(str_input, size);
+ i_stream_unref(&str_input);
+ ret = 0;
+ }
+ }
+ if (ret == 0) {
+ ret = json_parse_more(parser, &error);
+ test_out_reason_quiet("parse failure (buffered) #2",
+ ret < 0, error);
+ }
+ json_parser_deinit(&parser);
+
+ test_end();
+
+ i_stream_unref(&input);
+ } T_END;
+
+ str_free(&buffer);
+}
+
int main(int argc, char *argv[])
{
int c;
static void (*test_functions[])(void) = {
test_json_parse_valid,
test_json_parse_invalid,
+ test_json_parse_stream,
+ test_json_parse_stream_error,
NULL
};