summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGraham Leggett <minfrin@apache.org>2018-07-08 11:26:00 +0000
committerGraham Leggett <minfrin@apache.org>2018-07-08 11:26:00 +0000
commit624aef9cef65fb242ea2ecde7006a79b5d3106cc (patch)
tree8484d07ccaed8f5e7b5a0cd4e0af015e7163e3fe
parentf33d4e6dd99a8d8fde900b816575108f8e4bd9e8 (diff)
downloadapr-624aef9cef65fb242ea2ecde7006a79b5d3106cc.tar.gz
apr_json: Add support for encoding and decoding RFC8259 JSON.
Submitted by: Moriyoshi Koizumi <mozo mozo jp> git-svn-id: https://svn.apache.org/repos/asf/apr/apr/trunk@1835348 13f79535-47bb-0310-9956-ffa450edef68
-rw-r--r--CHANGES3
-rw-r--r--build.conf1
-rw-r--r--include/apr_json.h248
-rw-r--r--json/apr_json.c74
-rw-r--r--json/apr_json_decode.c819
-rw-r--r--json/apr_json_encode.c300
-rw-r--r--test/Makefile.in2
-rw-r--r--test/abts_tests.h3
-rw-r--r--test/testjson.c139
-rw-r--r--test/testutil.h1
10 files changed, 1588 insertions, 2 deletions
diff --git a/CHANGES b/CHANGES
index ef2890855..d510cb342 100644
--- a/CHANGES
+++ b/CHANGES
@@ -1,6 +1,9 @@
-*- coding: utf-8 -*-
Changes for APR 2.0.0
+ *) apr_json: Add support for encoding and decoding RFC8259 JSON.
+ [Moriyoshi Koizumi <mozo mozo jp>]
+
*) Add the apr_encode_* API that implements RFC4648 and RFC7515
compliant BASE64, BASE64URL, BASE32, BASE32HEX and BASE16
encode/decode functions. [Graham Leggett]
diff --git a/build.conf b/build.conf
index af1e99894..cea616aaa 100644
--- a/build.conf
+++ b/build.conf
@@ -26,6 +26,7 @@ paths =
dbm/sdbm/*.c
encoding/*.c
hooks/*.c
+ json/*.c
misc/*.c
memcache/*.c
redis/*.c
diff --git a/include/apr_json.h b/include/apr_json.h
new file mode 100644
index 000000000..a1342c836
--- /dev/null
+++ b/include/apr_json.h
@@ -0,0 +1,248 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+/**
+ * @file apr_json.h
+ * @brief APR-UTIL JSON Library
+ */
+#ifndef APR_JSON_H
+#define APR_JSON_H
+
+/**
+ * @defgroup APR_Util_JSON JSON Encoding and Decoding
+ * @ingroup APR_Util
+ * @{
+ */
+#include "apr.h"
+#include "apr_pools.h"
+#include "apr_tables.h"
+#include "apr_hash.h"
+#include "apr_strings.h"
+#include "apr_buckets.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * @package Apache JSON library
+ *
+ * RFC8259 compliant JSON encoding and decoding library.
+ *
+ * https://tools.ietf.org/html/rfc8259
+ *
+ * This API generates UTF-8 encoded JSON, and writes it to the
+ * bucket brigade specified. All strings are verified as valid UTF-8
+ * before processing, with invalid UTF-8 characters replaced.
+ *
+ * This API parses UTF-8 encoded JSON, and returns the result as
+ * a set of structures. All JSON strings are unescaped. Any bad
+ * characters or formatting will cause parsing to be terminated
+ * and an error returned, along with the offset of the error.
+ *
+ * Whitespace may be optionally preserved or ignored as required
+ * during generation and parsing.
+ *
+ * The ordering of object keys is preserved, allowing the decode and
+ * encode process to reproduce an identical result. This maintains
+ * stable behaviour during unit tests.
+ */
+
+/**
+ * When passing a string to one of the encode functions, this value can be
+ * passed to indicate a string-valued key, and have the length computed
+ * automatically.
+ */
+#define APR_JSON_VALUE_STRING (-1)
+
+/**
+ * Flag indicating no special processing.
+ */
+#define APR_JSON_FLAGS_NONE 0
+
+/**
+ * Flag indicating include whitespace.
+ */
+#define APR_JSON_FLAGS_WHITESPACE 1
+
+/**
+ * A structure to hold a JSON object.
+ */
+typedef struct apr_json_object_t apr_json_object_t;
+
+/**
+ * Enum that represents the type of the given JSON value.
+ */
+typedef enum apr_json_type_e {
+ APR_JSON_OBJECT,
+ APR_JSON_ARRAY,
+ APR_JSON_STRING,
+ APR_JSON_LONG,
+ APR_JSON_DOUBLE,
+ APR_JSON_BOOLEAN,
+ APR_JSON_NULL
+} apr_json_type_e;
+
+/**
+ * A structure to hold a UTF-8 encoded JSON string.
+ */
+typedef struct apr_json_string_t {
+ /** pointer to the string */
+ const char *p;
+ /** string length */
+ apr_size_t len;
+} apr_json_string_t;
+
+/**
+ * A structure that holds a JSON value.
+ *
+ * Use apr_json_value_create() to allocate.
+ */
+typedef struct apr_json_value_t {
+ /** preceding whitespace, if any */
+ const char *pre;
+ /** trailing whitespace, if any */
+ const char *post;
+ /** type of the value */
+ apr_json_type_e type;
+ /** actual value. which member is valid depends on type. */
+ union {
+ /** JSON object */
+ apr_json_object_t *object;
+ /** JSON array */
+ apr_array_header_t *array;
+ /** JSON floating point value */
+ double dnumber;
+ /** JSON long integer value */
+ apr_int64_t lnumber;
+ /** JSON UTF-8 encoded string value */
+ apr_json_string_t string;
+ /** JSON boolean value */
+ int boolean;
+ } value;
+} apr_json_value_t;
+
+/**
+ * A structure to hold a JSON object key value pair.
+ *
+ * Use apr_json_object_set() to allocate.
+ */
+typedef struct apr_json_kv_t {
+ /** Links to the rest of the kv pairs */
+ APR_RING_ENTRY(apr_json_kv_t) link;
+ /** the key */
+ apr_json_value_t *k;
+ /** the value */
+ apr_json_value_t *v;
+} apr_json_kv_t;
+
+/**
+ * A structure to hold a JSON object.
+ *
+ * Use apr_json_object_create() to allocate.
+ */
+typedef struct apr_json_object_t {
+ /** The key value pairs in the object are in this list */
+ APR_RING_HEAD(apr_json_object_list_t, apr_json_kv_t) list;
+ /** JSON object */
+ apr_hash_t *hash;
+} apr_json_object_t;
+
+/**
+ * Allocate and return a apr_json_value_t structure.
+ *
+ * @param pool The pool to allocate from.
+ * @return The apr_json_value_t structure.
+ */
+APR_DECLARE(apr_json_value_t *) apr_json_value_create(apr_pool_t *pool)
+ __attribute__((nonnull(1)));
+
+/**
+ * Allocate and return a apr_json_object_t structure.
+ *
+ * @param pool The pool to allocate from.
+ * @return The apr_json_object_t structure.
+ */
+APR_DECLARE(apr_json_object_t *) apr_json_object_create(apr_pool_t *pool)
+ __attribute__((nonnull(1)));
+
+/**
+ * Associate a value with a key in a JSON object.
+ * @param obj The JSON object.
+ * @param key Pointer to the key.
+ * @param val Value to associate with the key.
+ * @remark If the value is NULL the key value pair is deleted.
+ */
+APR_DECLARE(void) apr_json_object_set(apr_json_object_t *obj,
+ apr_json_value_t *key, apr_json_value_t *val,
+ apr_pool_t *pool) __attribute__((nonnull(1, 2, 4)));
+
+/**
+ * Look up the value associated with a key in a JSON object.
+ * @param ht The hash table
+ * @param key Pointer to the key
+ * @return Returns NULL if the key is not present.
+ */
+APR_DECLARE(apr_json_kv_t *)
+ apr_json_object_get(apr_json_object_t *obj, const char *key)
+ __attribute__((nonnull(1, 2)));
+
+/**
+ * Decode utf8-encoded JSON string into apr_json_value_t.
+ * @param retval the result
+ * @param injson utf8-encoded JSON string.
+ * @param size length of the input string.
+ * @param offset number of characters processed.
+ * @param flags set to APR_JSON_FLAGS_WHITESPACE to preserve whitespace,
+ * or APR_JSON_FLAGS_NONE to filter whitespace.
+ * @param level maximum nesting level we are prepared to decode.
+ * @param pool pool used to allocate the result from.
+ * @return APR_SUCCESS on success, APR_EOF if the JSON text is truncated.
+ * APR_BADCH when a decoding error has occurred (the location of the error
+ * is at offset), APR_EINVAL if the level has been exceeded, or
+ * APR_ENOTIMPL on platforms where not implemented.
+ */
+APR_DECLARE(apr_status_t) apr_json_decode(apr_json_value_t ** retval,
+ const char *injson, apr_ssize_t size, apr_off_t * offset,
+ int flags, int level, apr_pool_t * pool)
+ __attribute__((nonnull(1, 2, 7)));
+
+/**
+ * Encode data represented as apr_json_value_t to utf8-encoded JSON string
+ * and append it to the specified brigade.
+ *
+ * All JSON strings are checked for invalid UTF-8 character sequences,
+ * and if found invalid sequences are replaced with the replacement
+ * character "�" (U+FFFD).
+ *
+ * @param brigade brigade the result will be appended to.
+ * @param flush optional flush function for the brigade. Can be NULL.
+ * @param ctx optional contaxt for the flush function. Can be NULL.
+ * @param json the JSON data.
+ * @param flags set to APR_JSON_FLAGS_WHITESPACE to preserve whitespace,
+ * or APR_JSON_FLAGS_NONE to filter whitespace.
+ * @param pool pool used to allocate the buckets from.
+ * @return APR_SUCCESS on success, or APR_ENOTIMPL on platforms where not
+ * implemented.
+ */
+APR_DECLARE(apr_status_t) apr_json_encode(apr_bucket_brigade * brigade,
+ apr_brigade_flush flush, void *ctx, const apr_json_value_t * json,
+ int flags, apr_pool_t * pool) __attribute__((nonnull(1, 4, 6)));
+
+#ifdef __cplusplus
+}
+#endif
+/** @} */
+#endif /* APR_JSON_H */
diff --git a/json/apr_json.c b/json/apr_json.c
new file mode 100644
index 000000000..98966659f
--- /dev/null
+++ b/json/apr_json.c
@@ -0,0 +1,74 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <ctype.h>
+#include <stdlib.h>
+
+#include "apr_json.h"
+
+#define APR_JSON_OBJECT_INSERT_TAIL(o, e) do { \
+ apr_json_kv_t *ap__b = (e); \
+ APR_RING_INSERT_TAIL(&(o)->list, ap__b, apr_json_kv_t, link); \
+ APR_RING_CHECK_CONSISTENCY(&(o)->list, apr_json_kv_t, link); \
+ } while (0)
+
+apr_json_value_t *apr_json_value_create(apr_pool_t *pool)
+{
+ return apr_pcalloc(pool, sizeof(apr_json_value_t));
+}
+
+apr_json_object_t *apr_json_object_create(apr_pool_t *pool)
+{
+ apr_json_object_t *object = apr_pcalloc(pool,
+ sizeof(apr_json_object_t));
+ APR_RING_INIT(&object->list, apr_json_kv_t, link);
+ object->hash = apr_hash_make(pool);
+
+ return object;
+}
+
+void apr_json_object_set(apr_json_object_t *object, apr_json_value_t *key,
+ apr_json_value_t *val, apr_pool_t *pool)
+{
+ apr_json_kv_t *kv;
+
+ kv = apr_hash_get(object->hash, key->value.string.p, key->value.string.len);
+
+ if (!val) {
+ if (kv) {
+ apr_hash_set(object->hash, key->value.string.p, key->value.string.len,
+ NULL);
+ APR_RING_REMOVE((kv), link);
+ }
+ return;
+ }
+
+ if (!kv) {
+ kv = apr_palloc(pool, sizeof(apr_json_kv_t));
+ APR_RING_ELEM_INIT(kv, link);
+ APR_JSON_OBJECT_INSERT_TAIL(object, kv);
+ apr_hash_set(object->hash, key->value.string.p, key->value.string.len,
+ kv);
+ }
+
+ kv->k = key;
+ kv->v = val;
+}
+
+apr_json_kv_t *apr_json_object_get(apr_json_object_t *object, const char *key)
+{
+ return apr_hash_get(object->hash, key, APR_HASH_KEY_STRING);
+}
diff --git a/json/apr_json_decode.c b/json/apr_json_decode.c
new file mode 100644
index 000000000..ecb60c022
--- /dev/null
+++ b/json/apr_json_decode.c
@@ -0,0 +1,819 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <ctype.h>
+#include <stdlib.h>
+
+#include "apr_json.h"
+
+#if !APR_CHARSET_EBCDIC
+
+typedef struct _json_link_t {
+ apr_json_value_t *value;
+ struct _json_link_t *next;
+} json_link_t;
+
+typedef struct apr_json_scanner_t {
+ apr_pool_t *pool;
+ const char *p;
+ const char *e;
+ int flags;
+ int level;
+} apr_json_scanner_t;
+
+static apr_status_t apr_json_decode_value(apr_json_scanner_t * self, apr_json_value_t ** retval);
+
+/* stolen from mod_mime_magic.c :) */
+/* Single hex char to int; -1 if not a hex char. */
+static int hex_to_int(int c)
+{
+ if (isdigit(c))
+ return c - '0';
+ if ((c >= 'a') && (c <= 'f'))
+ return c + 10 - 'a';
+ if ((c >= 'A') && (c <= 'F'))
+ return c + 10 - 'A';
+ return -1;
+}
+
+static apr_ssize_t ucs4_to_utf8(char *out, int code)
+{
+ if (code < 0x00000080) {
+ out[0] = code;
+ return 1;
+ }
+ else if (code < 0x00000800) {
+ out[0] = 0xc0 + (code >> 6);
+ out[1] = 0x80 + (code & 0x3f);
+ return 2;
+ }
+ else if (code < 0x00010000) {
+ out[0] = 0xe0 + (code >> 12);
+ out[1] = 0x80 + ((code >> 6) & 0x3f);
+ out[2] = 0x80 + (code & 0x3f);
+ return 3;
+ }
+ else if (code < 0x00200000) {
+ out[0] = 0xd0 + (code >> 18);
+ out[1] = 0x80 + ((code >> 12) & 0x3f);
+ out[2] = 0x80 + ((code >> 6) & 0x3f);
+ out[3] = 0x80 + (code & 0x3F);
+ return 4;
+ }
+ return 0;
+}
+
+static apr_status_t apr_json_decode_string(apr_json_scanner_t * self, apr_json_string_t * retval)
+{
+ apr_status_t status = APR_SUCCESS;
+ apr_json_string_t string;
+ const char *p = self->p;
+ const char *e;
+ char *q;
+
+ if (self->p >= self->e) {
+ status = APR_EOF;
+ goto out;
+ }
+
+ self->p++; /* eat the leading '"' */
+
+ /* advance past the \ " */
+ string.len = 0;
+ for (p = self->p, e = self->e; p < e;) {
+ if (*p == '"')
+ break;
+ else if (*p == '\\') {
+ p++;
+ if (p >= e) {
+ status = APR_EOF;
+ goto out;
+ }
+ if (*p == 'u') {
+ if (p + 4 >= e) {
+ status = APR_EOF;
+ goto out;
+ }
+ p += 5;
+ string.len += 4;/* an UTF-8 character spans at most 4 bytes */
+ break;
+ }
+ else {
+ string.len++;
+ p++;
+ }
+ }
+ else {
+ string.len++;
+ p++;
+ }
+ }
+
+ string.p = q = apr_pcalloc(self->pool, string.len + 1);
+ e = p;
+
+#define VALIDATE_UTF8_SUCCEEDING_BYTE(p) \
+ if (*(unsigned char *)(p) < 0x80 || *(unsigned char *)(p) >= 0xc0) { \
+ status = APR_BADCH; \
+ goto out; \
+ }
+
+ for (p = self->p; p < e;) {
+ switch (*(unsigned char *)p) {
+ case '\\':
+ p++;
+ switch (*p) {
+ case 'u':
+ /* THIS IS REQUIRED TO BE A 4 DIGIT HEX NUMBER */
+ {
+ int cp = 0;
+ while (p < e) {
+ int d = hex_to_int(*p);
+ if (d < 0) {
+ status = APR_BADCH;
+ goto out;
+ }
+ cp = (cp << 4) | d;
+ p++;
+ }
+ if (cp >= 0xd800 && cp < 0xdc00) {
+ /* surrogate pair */
+ int sc = 0;
+ if (p + 6 > e) {
+ status = APR_EOF;
+ goto out;
+ }
+ if (p[0] != '\\' && p[1] != 'u') {
+ status = APR_BADCH;
+ goto out;
+ }
+ while (p < e) {
+ int d = hex_to_int(*p);
+ if (d < 0) {
+ status = APR_BADCH;
+ goto out;
+ }
+ sc = (sc << 4) | d;
+ p++;
+ }
+ cp = ((cp & 0x3ff) << 10) | (sc & 0x3ff);
+ if ((cp >= 0xd800 && cp < 0xe000) || (cp >= 0x110000)) {
+ status = APR_BADCH;
+ goto out;
+ }
+ }
+ else if (cp >= 0xdc00 && cp < 0xe000) {
+ status = APR_BADCH;
+ goto out;
+ }
+ q += ucs4_to_utf8(q, cp);
+ }
+ break;
+ case '\\':
+ *q++ = '\\';
+ p++;
+ break;
+ case '/':
+ *q++ = '/';
+ p++;
+ break;
+ case 'n':
+ *q++ = '\n';
+ p++;
+ break;
+ case 'r':
+ *q++ = '\r';
+ p++;
+ break;
+ case 't':
+ *q++ = '\t';
+ p++;
+ break;
+ case 'f':
+ *q++ = '\f';
+ p++;
+ break;
+ case 'b':
+ *q++ = '\b';
+ p++;
+ break;
+ case '"':
+ *q++ = '"';
+ p++;
+ break;
+ default:
+ status = APR_BADCH;
+ goto out;
+ }
+ break;
+
+ case 0xc0:
+ case 0xc1:
+ case 0xc2:
+ case 0xc3:
+ case 0xc4:
+ case 0xc5:
+ case 0xc6:
+ case 0xc7:
+ case 0xc8:
+ case 0xc9:
+ case 0xca:
+ case 0xcb:
+ case 0xcc:
+ case 0xcd:
+ case 0xce:
+ case 0xcf:
+ case 0xd0:
+ case 0xd1:
+ case 0xd2:
+ case 0xd3:
+ case 0xd4:
+ case 0xd5:
+ case 0xd6:
+ case 0xd7:
+ case 0xd8:
+ case 0xd9:
+ case 0xda:
+ case 0xdb:
+ case 0xdc:
+ case 0xdd:
+ case 0xde:
+ case 0xdf:
+ if (p + 1 >= e) {
+ status = APR_EOF;
+ goto out;
+ }
+ *q++ = *p++;
+ VALIDATE_UTF8_SUCCEEDING_BYTE(p);
+ *q++ = *p++;
+ break;
+
+ case 0xe0:
+ case 0xe1:
+ case 0xe2:
+ case 0xe3:
+ case 0xe4:
+ case 0xe5:
+ case 0xe6:
+ case 0xe7:
+ case 0xe8:
+ case 0xe9:
+ case 0xea:
+ case 0xeb:
+ case 0xec:
+ case 0xed:
+ case 0xee:
+ case 0xef:
+ if (p + 2 >= e) {
+ status = APR_EOF;
+ goto out;
+ }
+ *q++ = *p++;
+ VALIDATE_UTF8_SUCCEEDING_BYTE(p);
+ *q++ = *p++;
+ VALIDATE_UTF8_SUCCEEDING_BYTE(p);
+ *q++ = *p++;
+ break;
+
+ case 0xf0:
+ case 0xf1:
+ case 0xf2:
+ case 0xf3:
+ case 0xf4:
+ case 0xf5:
+ case 0xf6:
+ case 0xf7:
+ if (p + 3 >= e) {
+ status = APR_EOF;
+ goto out;
+ }
+ if (((unsigned char *)p)[0] >= 0xf5 || ((unsigned char *)p)[1] >= 0x90) {
+ status = APR_BADCH;
+ goto out;
+ }
+ *q++ = *p++;
+ VALIDATE_UTF8_SUCCEEDING_BYTE(p);
+ *q++ = *p++;
+ VALIDATE_UTF8_SUCCEEDING_BYTE(p);
+ *q++ = *p++;
+ VALIDATE_UTF8_SUCCEEDING_BYTE(p);
+ *q++ = *p++;
+ break;
+
+ case 0xf8:
+ case 0xf9:
+ case 0xfa:
+ case 0xfb:
+ if (p + 4 >= e) {
+ status = APR_EOF;
+ goto out;
+ }
+ status = APR_BADCH;
+ goto out;
+
+ case 0xfc:
+ case 0xfd:
+ if (p + 5 >= e) {
+ status = APR_EOF;
+ goto out;
+ }
+ status = APR_BADCH;
+ goto out;
+
+ default:
+ *q++ = *p++;
+ break;
+ }
+ }
+#undef VALIDATE_UTF8_SUCCEEDING_BYTE
+ p++; /* eat the trailing '"' */
+ *retval = string;
+out:
+ self->p = p;
+ return status;
+}
+
+static apr_status_t apr_json_decode_array(apr_json_scanner_t * self,
+ apr_array_header_t ** retval)
+{
+ apr_status_t status = APR_SUCCESS;
+ apr_pool_t *link_pool = NULL;
+ json_link_t *head = NULL, *tail = NULL;
+ apr_size_t count = 0;
+
+ if ((status = apr_pool_create(&link_pool, self->pool)))
+ return status;
+
+ if (self->p >= self->e) {
+ status = APR_EOF;
+ goto out;
+ }
+
+ self->level--;
+ if (self->level < 0) {
+ return APR_EINVAL;
+ }
+
+ self->p++; /* toss of the leading [ */
+
+ for (;;) {
+ apr_json_value_t *element;
+ json_link_t *new_node;
+
+ if (self->p == self->e) {
+ status = APR_EOF;
+ goto out;
+ }
+
+ if (*self->p == ']') {
+ self->p++;
+ break;
+ }
+
+ if (APR_SUCCESS != (status = apr_json_decode_value(self, &element))) {
+ goto out;
+ }
+
+ new_node = apr_pcalloc(link_pool, sizeof(json_link_t));
+ new_node->value = element;
+ if (tail) {
+ tail->next = new_node;
+ }
+ else {
+ head = new_node;
+ }
+ tail = new_node;
+ count++;
+
+ if (self->p == self->e) {
+ status = APR_EOF;
+ goto out;
+ }
+
+ if (*self->p == ',') {
+ self->p++;
+ }
+ else if (*self->p != ']') {
+ status = APR_BADCH;
+ goto out;
+ }
+ }
+
+ {
+ json_link_t *node;
+ apr_array_header_t *array = apr_array_make(self->pool, count, sizeof(apr_json_value_t *));
+ for (node = head; node; node = node->next) {
+ *((apr_json_value_t **) (apr_array_push(array))) = node->value;
+ }
+ *retval = array;
+ }
+
+ self->level++;
+
+out:
+ if (link_pool) {
+ apr_pool_destroy(link_pool);
+ }
+ return status;
+}
+
+static apr_status_t apr_json_decode_object(apr_json_scanner_t * self,
+ apr_json_object_t ** retval)
+{
+ apr_status_t status = APR_SUCCESS;
+
+ apr_json_object_t *object = apr_json_object_create(self->pool);
+
+ if (self->p >= self->e) {
+ return APR_EOF;
+ }
+
+ self->level--;
+ if (self->level < 0) {
+ return APR_EINVAL;
+ }
+
+ self->p++; /* toss of the leading { */
+
+ for (;;) {
+ apr_json_value_t *key;
+ apr_json_value_t *value;
+
+ if (self->p == self->e) {
+ status = APR_EOF;
+ goto out;
+ }
+
+ if (*self->p == '}') {
+ self->p++;
+ break;
+ }
+
+ if ((status = apr_json_decode_value(self, &key)))
+ goto out;
+
+ if (key->type != APR_JSON_STRING) {
+ status = APR_BADCH;
+ goto out;
+ }
+
+ if (self->p == self->e) {
+ status = APR_EOF;
+ goto out;
+ }
+
+ if (*self->p != ':') {
+ status = APR_BADCH;
+ goto out;
+ }
+
+ self->p++; /* eat the ':' */
+
+ if (self->p == self->e) {
+ status = APR_EOF;
+ goto out;
+ }
+
+ if ((status = apr_json_decode_value(self, &value)))
+ goto out;
+
+ apr_json_object_set(object, key, value, self->pool);
+
+ if (self->p == self->e) {
+ status = APR_EOF;
+ goto out;
+ }
+
+ if (*self->p == ',') {
+ self->p++;
+ }
+ else if (*self->p != '}') {
+ status = APR_BADCH;
+ goto out;
+ }
+ }
+
+ self->level++;
+
+ *retval = object;
+out:
+ return status;
+}
+
+static apr_status_t apr_json_decode_boolean(apr_json_scanner_t * self, int *retval)
+{
+ if (self->p >= self->e)
+ return APR_EOF;
+
+ if (self->e - self->p >= 4 && strncmp("true", self->p, 4) == 0 &&
+ (self->p == self->e ||
+ (!isalnum(((unsigned char *)self->p)[4]) &&
+ ((unsigned char *)self->p)[4] != '_'))) {
+ self->p += 4;
+ *retval = 1;
+ return APR_SUCCESS;
+ }
+ else if (self->e - self->p >= 5 && strncmp("false", self->p, 5) == 0 &&
+ (self->p == self->e ||
+ (!isalnum(((unsigned char *)self->p)[5]) &&
+ ((unsigned char *)self->p)[5] != '_'))) {
+ self->p += 5;
+ *retval = 0;
+ return APR_SUCCESS;
+ }
+ return APR_BADCH;
+}
+
+static apr_status_t apr_json_decode_number(apr_json_scanner_t * self, apr_json_value_t * retval)
+{
+ apr_status_t status = APR_SUCCESS;
+ int treat_as_float = 0, exp_occurred = 0;
+ const char *p = self->p, *e = self->e;
+
+ if (p >= e)
+ return APR_EOF;
+
+ {
+ unsigned char c = *(unsigned char *)p;
+ if (c == '-') {
+ p++;
+ if (p >= e)
+ return APR_EOF;
+ c = *(unsigned char *)p;
+ }
+ if (c == '.') {
+ p++;
+ if (p >= e)
+ return APR_EOF;
+ c = *(unsigned char *)p;
+ treat_as_float = 1;
+ }
+ if (!isdigit(c)) {
+ status = APR_BADCH;
+ goto out;
+ }
+ p++;
+ }
+
+ if (!treat_as_float) {
+ while (p < e) {
+ unsigned char c = *(unsigned char *)p;
+ if (c == 'e' || c == 'E') {
+ p++;
+ if (p >= e)
+ return APR_EOF;
+ c = *(unsigned char *)p;
+ if (c == '-') {
+ p++;
+ if (p >= e)
+ return APR_EOF;
+ c = *(unsigned char *)p;
+ }
+ if (!isdigit(c)) {
+ status = APR_BADCH;
+ goto out;
+ }
+ treat_as_float = 1;
+ exp_occurred = 1;
+ break;
+ }
+ else if (c == '.') {
+ p++;
+ treat_as_float = 1;
+ break;
+ }
+ else if (!isdigit(c))
+ break;
+ p++;
+ }
+ }
+ else {
+ while (p < e) {
+ unsigned char c = *(unsigned char *)p;
+ if (c == 'e' || c == 'E') {
+ p++;
+ if (p >= e)
+ return APR_EOF;
+ c = *(unsigned char *)p;
+ if (c == '-') {
+ p++;
+ if (p >= e)
+ return APR_EOF;
+ c = *(unsigned char *)p;
+ }
+ if (!isdigit(c)) {
+ status = APR_BADCH;
+ goto out;
+ }
+ exp_occurred = 1;
+ break;
+ }
+ else if (!isdigit(c))
+ break;
+ p++;
+ }
+ }
+
+ if (treat_as_float) {
+ if (!exp_occurred) {
+ while (p < e) {
+ unsigned char c = *(unsigned char *)p;
+ if (c == 'e' || c == 'E') {
+ p++;
+ if (p >= e)
+ return APR_EOF;
+ c = *(unsigned char *)p;
+ if (c == '-') {
+ p++;
+ if (p >= e)
+ return APR_EOF;
+ c = *(unsigned char *)p;
+ }
+ if (!isdigit(c)) {
+ status = APR_BADCH;
+ goto out;
+ }
+ exp_occurred = 1;
+ break;
+ }
+ else if (!isdigit(c))
+ break;
+ p++;
+ }
+ }
+ if (exp_occurred) {
+ if (p >= e || !isdigit(*(unsigned char *)p))
+ return APR_EOF;
+ while (++p < e && isdigit(*(unsigned char *)p));
+ }
+ }
+
+ if (treat_as_float) {
+ retval->type = APR_JSON_DOUBLE;
+ retval->value.dnumber = strtod(self->p, NULL);
+ }
+ else {
+ retval->type = APR_JSON_LONG;
+ retval->value.lnumber = strtol(self->p, NULL, 10);
+ }
+
+out:
+ self->p = p;
+ return status;
+}
+
+static apr_status_t apr_json_decode_null(apr_json_scanner_t * self)
+{
+ if (self->e - self->p >= 4 && strncmp("null", self->p, 4) == 0 &&
+ (self->p == self->e ||
+ (!isalnum(((unsigned char *)self->p)[4]) &&
+ ((unsigned char *)self->p)[4] != '_'))) {
+ self->p += 4;
+ return APR_SUCCESS;
+ }
+ return APR_BADCH;
+}
+
+static apr_status_t apr_json_decode_space(apr_json_scanner_t * self,
+ const char **space)
+{
+ const char *p = self->p;
+ char *s;
+ int len = 0;
+
+ *space = NULL;
+
+ if (self->p >= self->e) {
+ return APR_SUCCESS;
+ }
+
+ while (p < self->e && isspace(*(unsigned char *)p)) {
+ p++;
+ len++;
+ }
+
+ if (self->flags & APR_JSON_FLAGS_WHITESPACE) {
+ if (len) {
+ *space = s = apr_palloc(self->pool, len + 1);
+
+ while (self->p < self->e && isspace(*(unsigned char *) self->p)) {
+ *s++ = *self->p++;
+ }
+ *s = 0;
+
+ }
+ } else {
+ self->p = p;
+ }
+
+ return APR_SUCCESS;
+}
+
+static apr_status_t apr_json_decode_value(apr_json_scanner_t * self, apr_json_value_t ** retval)
+{
+ apr_json_value_t value;
+ apr_status_t status = APR_SUCCESS;
+
+ status = apr_json_decode_space(self, &value.pre);
+
+ if (status == APR_SUCCESS) {
+ switch (*(unsigned char *) self->p) {
+ case '"':
+ value.type = APR_JSON_STRING;
+ status = apr_json_decode_string(self, &value.value.string);
+ break;
+ case '[':
+ value.type = APR_JSON_ARRAY;
+ status = apr_json_decode_array(self, &value.value.array);
+ break;
+ case '{':
+ value.type = APR_JSON_OBJECT;
+ status = apr_json_decode_object(self, &value.value.object);
+ break;
+ case 'n':
+ value.type = APR_JSON_NULL;
+ status = apr_json_decode_null(self);
+ break;
+ case 't':
+ case 'f':
+ value.type = APR_JSON_BOOLEAN;
+ status = apr_json_decode_boolean(self, &value.value.boolean);
+ break;
+ case '-':
+ case '0':
+ case '1':
+ case '2':
+ case '3':
+ case '4':
+ case '5':
+ case '6':
+ case '7':
+ case '8':
+ case '9':
+ status = apr_json_decode_number(self, &value);
+ break;
+ default:
+ status = APR_BADCH;
+ }
+ }
+
+ if (status == APR_SUCCESS) {
+ status = apr_json_decode_space(self, &value.post);
+ }
+
+ if (status == APR_SUCCESS) {
+ *retval = apr_json_value_create(self->pool);
+ **retval = value;
+ }
+ return status;
+}
+
+apr_status_t apr_json_decode(apr_json_value_t ** retval, const char *injson,
+ apr_ssize_t injson_size, apr_off_t * offset, int flags, int level,
+ apr_pool_t * pool)
+{
+ apr_status_t status;
+ apr_json_scanner_t scanner;
+
+ scanner.p = injson;
+ scanner.e = injson
+ + (injson_size == APR_JSON_VALUE_STRING ? strlen(injson) : injson_size);
+ scanner.pool = pool;
+ scanner.flags = flags;
+ scanner.level = level;
+
+ if (APR_SUCCESS == (status = apr_json_decode_value(&scanner, retval))) {
+ if (scanner.p != scanner.e) {
+ /* trailing craft */
+ status = APR_BADCH;
+ }
+ }
+
+ if (offset) {
+ *offset = scanner.p - injson;
+ }
+
+ return status;
+}
+
+#else
+/* we do not yet support JSON on EBCDIC platforms, but will do in future */
+apr_status_t apr_json_decode(apr_json_value_t ** retval, const char *injson,
+ apr_size_t injson_size, apr_pool_t * pool)
+{
+ return APR_ENOTIMPL;
+}
+#endif
diff --git a/json/apr_json_encode.c b/json/apr_json_encode.c
new file mode 100644
index 000000000..444851173
--- /dev/null
+++ b/json/apr_json_encode.c
@@ -0,0 +1,300 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "apr_json.h"
+
+#if !APR_CHARSET_EBCDIC
+
+typedef struct apr_json_serializer_t {
+ apr_pool_t *pool;
+ apr_bucket_brigade *brigade;
+ apr_brigade_flush flush;
+ void *ctx;
+ int flags;
+} apr_json_serializer_t;
+
+static apr_status_t apr_json_encode_value(apr_json_serializer_t * self,
+ const apr_json_value_t * value);
+
+static apr_status_t apr_json_brigade_write(apr_json_serializer_t * self,
+ const char *chunk, apr_size_t chunk_len, const char *escaped)
+{
+ apr_status_t status;
+
+ status = apr_brigade_write(self->brigade, self->flush, self->ctx, chunk, chunk_len);
+ if (APR_SUCCESS == status) {
+ status = apr_brigade_puts(self->brigade, self->flush, self->ctx, escaped);
+ }
+
+ return status;
+}
+
+static apr_status_t apr_json_brigade_printf(apr_json_serializer_t * self,
+ const char *chunk, apr_size_t chunk_len, const char *fmt, ...)
+{
+ va_list ap;
+ apr_status_t status;
+
+ status = apr_brigade_write(self->brigade, self->flush, self->ctx, chunk,
+ chunk_len);
+ if (APR_SUCCESS == status) {
+ va_start(ap, fmt);
+ status = apr_brigade_vprintf(self->brigade, self->flush, self->ctx, fmt,
+ ap);
+ va_end(ap);
+ }
+
+ return status;
+}
+
+static apr_status_t apr_json_encode_string(apr_json_serializer_t * self,
+ const apr_json_string_t * string)
+{
+ apr_status_t status;
+ const char *p, *e, *chunk;
+ const char invalid[4] = { 0xEF, 0xBF, 0xBD, 0x00 };
+ unsigned char c;
+
+ status = apr_brigade_putc(self->brigade, self->flush, self->ctx, '\"');
+ if (APR_SUCCESS != status) {
+ return status;
+ }
+
+ for (p = chunk = string->p, e = string->p + string->len; p < e; p++) {
+ switch (*p) {
+ case '\n':
+ status = apr_json_brigade_write(self, chunk, p - chunk, "\\n");
+ chunk = p + 1;
+ break;
+ case '\r':
+ status = apr_json_brigade_write(self, chunk, p - chunk, "\\r");
+ chunk = p + 1;
+ break;
+ case '\t':
+ status = apr_json_brigade_write(self, chunk, p - chunk, "\\t");
+ chunk = p + 1;
+ break;
+ case '\b':
+ status = apr_json_brigade_write(self, chunk, p - chunk, "\\b");
+ chunk = p + 1;
+ break;
+ case '\f':
+ status = apr_json_brigade_write(self, chunk, p - chunk, "\\f");
+ chunk = p + 1;
+ break;
+ case '\\':
+ status = apr_json_brigade_write(self, chunk, p - chunk, "\\\\");
+ chunk = p + 1;
+ break;
+ case '"':
+ status = apr_json_brigade_write(self, chunk, p - chunk, "\\\"");
+ chunk = p + 1;
+ break;
+ default:
+ c = (unsigned char)(*p);
+ apr_size_t left = e - p;
+ if (c < 0x20) {
+ status = apr_json_brigade_printf(self, chunk, p - chunk,
+ "\\u%04x", c);
+ chunk = p + 1;
+ }
+ else if (((c >> 7) == 0x00)) {
+ /* 1 byte */
+ }
+ else if (left > 1 && ((c >> 5) == 0x06) && p[1]) {
+ /* 2 bytes */
+ if (left < 2 || (p[1] >> 6) != 0x02) {
+ status = apr_json_brigade_write(self, chunk, p - chunk,
+ invalid);
+ chunk = p + 1;
+ }
+ }
+ else if (((c >> 4) == 0x0E)) {
+ /* 3 bytes */
+ if (left < 3 || (p[1] >> 6) != 0x02 || (p[2] >> 6) != 0x02) {
+ status = apr_json_brigade_write(self, chunk, p - chunk,
+ invalid);
+ chunk = p + 1;
+ }
+ }
+ else if ((c >> 3) == 0x1E) {
+ /* 4 bytes */
+ if (left < 4 || (p[1] >> 6) != 0x02 || (p[2] >> 6) != 0x02 || (p[3] >> 6) != 0x02) {
+ status = apr_json_brigade_write(self, chunk, p - chunk,
+ invalid);
+ chunk = p + 1;
+ }
+ }
+ else {
+ status = apr_json_brigade_write(self, chunk, p - chunk,
+ invalid);
+ chunk = p + 1;
+ }
+ break;
+ }
+
+ if (APR_SUCCESS != status) {
+ return status;
+ }
+ }
+
+ if (chunk < p) {
+ status = apr_brigade_write(self->brigade, self->flush, self->ctx, chunk, p - chunk);
+ if (APR_SUCCESS != status) {
+ return status;
+ }
+ }
+
+ return apr_brigade_putc(self->brigade, self->flush, self->ctx, '\"');
+}
+
+
+static apr_status_t apr_json_encode_array(apr_json_serializer_t * self, apr_array_header_t * array)
+{
+ apr_status_t status;
+ apr_size_t i;
+
+ status = apr_brigade_putc(self->brigade, self->flush, self->ctx, '[');
+ if (APR_SUCCESS != status) {
+ return status;
+ }
+
+ for (i = 0; i < array->nelts; i++) {
+
+ if (i > 0) {
+ status = apr_brigade_putc(self->brigade, self->flush, self->ctx, ',');
+ if (APR_SUCCESS != status) {
+ return status;
+ }
+ }
+
+ status = apr_json_encode_value(self, ((apr_json_value_t **) array->elts)[i]);
+ if (APR_SUCCESS != status) {
+ return status;
+ }
+
+ }
+
+ return apr_brigade_putc(self->brigade, self->flush, self->ctx, ']');
+}
+
+static apr_status_t apr_json_encode_object(apr_json_serializer_t * self, apr_json_object_t * object)
+{
+ apr_status_t status;
+ apr_json_kv_t *kv;
+ int first = 1;
+ status = apr_brigade_putc(self->brigade, self->flush, self->ctx, '{');
+ if (APR_SUCCESS != status) {
+ return status;
+ }
+
+ for (kv = APR_RING_FIRST(&(object)->list);
+ kv != APR_RING_SENTINEL(&(object)->list, apr_json_kv_t, link);
+ kv = APR_RING_NEXT((kv), link)) {
+
+ if (!first) {
+ status = apr_brigade_putc(self->brigade, self->flush, self->ctx, ',');
+ if (APR_SUCCESS != status) {
+ return status;
+ }
+ }
+
+ {
+ status = apr_json_encode_value(self, kv->k);
+ if (APR_SUCCESS != status) {
+ return status;
+ }
+
+ status = apr_brigade_putc(self->brigade, self->flush, self->ctx, ':');
+ if (APR_SUCCESS != status) {
+ return status;
+ }
+
+ status = apr_json_encode_value(self, kv->v);
+ if (APR_SUCCESS != status) {
+ return status;
+ }
+ }
+ first = 0;
+ }
+ return apr_brigade_putc(self->brigade, self->flush, self->ctx, '}');
+}
+
+static apr_status_t apr_json_encode_value(apr_json_serializer_t * self, const apr_json_value_t * value)
+{
+ apr_status_t status = APR_SUCCESS;
+
+ if (value->pre && (self->flags & APR_JSON_FLAGS_WHITESPACE)) {
+ status = apr_brigade_puts(self->brigade, self->flush, self->ctx,
+ value->pre);
+ }
+
+ if (APR_SUCCESS == status) {
+ switch (value->type) {
+ case APR_JSON_STRING:
+ status = apr_json_encode_string(self, &value->value.string);
+ break;
+ case APR_JSON_LONG:
+ status = apr_brigade_printf(self->brigade, self->flush, self->ctx,
+ "%" APR_INT64_T_FMT, value->value.lnumber);
+ break;
+ case APR_JSON_DOUBLE:
+ status = apr_brigade_printf(self->brigade, self->flush, self->ctx,
+ "%lf", value->value.dnumber);
+ break;
+ case APR_JSON_BOOLEAN:
+ status = apr_brigade_puts(self->brigade, self->flush, self->ctx,
+ value->value.boolean ? "true" : "false");
+ break;
+ case APR_JSON_NULL:
+ status = apr_brigade_puts(self->brigade, self->flush, self->ctx,
+ "null");
+ break;
+ case APR_JSON_OBJECT:
+ status = apr_json_encode_object(self, value->value.object);
+ break;
+ case APR_JSON_ARRAY:
+ status = apr_json_encode_array(self, value->value.array);
+ break;
+ default:
+ return APR_EINVAL;
+ }
+ }
+
+ if (APR_SUCCESS == status && value->post
+ && (self->flags & APR_JSON_FLAGS_WHITESPACE)) {
+ status = apr_brigade_puts(self->brigade, self->flush, self->ctx,
+ value->post);
+ }
+
+ return status;
+}
+
+apr_status_t apr_json_encode(apr_bucket_brigade * brigade, apr_brigade_flush flush,
+ void *ctx, const apr_json_value_t * json, int flags, apr_pool_t * pool)
+{
+ apr_json_serializer_t serializer = {pool, brigade, flush, ctx, flags};
+ return apr_json_encode_value(&serializer, json);
+}
+
+#else
+ /* we do not yet support JSON on EBCDIC platforms, but will do in future */
+apr_status_t apr_json_encode(apr_bucket_brigade * brigade, apr_brigade_flush flush,
+ void *ctx, const apr_json_value_t * json, apr_pool_t * pool)
+{
+ return APR_ENOTIMPL;
+}
+#endif
diff --git a/test/Makefile.in b/test/Makefile.in
index be2d65c4e..291a76e71 100644
--- a/test/Makefile.in
+++ b/test/Makefile.in
@@ -37,7 +37,7 @@ TESTS = testtime.lo teststr.lo testvsn.lo testipsub.lo testshm.lo \
testbuckets.lo testxml.lo testdbm.lo testuuid.lo testmd5.lo \
testreslist.lo testbase64.lo testhooks.lo testlfsabi.lo \
testlfsabi32.lo testlfsabi64.lo testescape.lo testskiplist.lo \
- testsiphash.lo testredis.lo testencode.lo
+ testsiphash.lo testredis.lo testencode.lo testjson.lo
OTHER_PROGRAMS = \
echod@EXEEXT@ \
diff --git a/test/abts_tests.h b/test/abts_tests.h
index 312098668..82cce0ce2 100644
--- a/test/abts_tests.h
+++ b/test/abts_tests.h
@@ -91,7 +91,8 @@ const struct testlist {
{testreslist},
{testlfsabi},
{testskiplist},
- {testsiphash}
+ {testsiphash},
+ {testjson}
};
#endif /* APR_TEST_INCLUDES */
diff --git a/test/testjson.c b/test/testjson.c
new file mode 100644
index 000000000..338149d4f
--- /dev/null
+++ b/test/testjson.c
@@ -0,0 +1,139 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <assert.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "apr_json.h"
+
+#include "abts.h"
+#include "testutil.h"
+
+static void test_json_identity(abts_case * tc, void *data)
+{
+ apr_json_value_t *json = NULL;
+ apr_json_kv_t *image, *width, *ids, *title,
+ *animated, *thumbnail, *height;
+ apr_bucket_alloc_t *ba;
+ apr_bucket_brigade *bb;
+ const char *src;
+ char buf[1024];
+ apr_size_t len = sizeof(buf);
+ apr_off_t offset = 0;
+
+ ba = apr_bucket_alloc_create(p);
+ bb = apr_brigade_create(p, ba);
+
+ src = "{"
+ " \"Image\" : {"
+ " \"Width\" : 800 ,"
+ " \"IDs\" : [116, 943, 234, 38793],"
+ " \"Title\" : \"View from 15th Floor\","
+ " \"Animated\" : false,"
+ " \"Thumbnail\" : {"
+ " \"Height\" : 125,"
+ " \"Width\" : 100,"
+ " \"Url\" : \"http://www.example.com/image/481989943\""
+ " },"
+ " \"Height\" : 600 "
+ " }"
+ "}";
+
+ apr_json_decode(&json, src, APR_JSON_VALUE_STRING, &offset, APR_JSON_FLAGS_WHITESPACE,
+ 10, p);
+ apr_json_encode(bb, NULL, NULL, json, APR_JSON_FLAGS_WHITESPACE, p);
+ apr_brigade_flatten(bb, buf, &len);
+ apr_json_decode(&json, buf, len, &offset, APR_JSON_FLAGS_WHITESPACE, 10, p);
+
+ ABTS_STR_NEQUAL(tc, src, buf, len);
+
+ ABTS_INT_EQUAL(tc, len, offset);
+ ABTS_INT_EQUAL(tc, APR_JSON_OBJECT, json->type);
+ image = apr_hash_get(json->value.object->hash, "Image", 5);
+ ABTS_PTR_NOTNULL(tc, image);
+ width = apr_hash_get(image->v->value.object->hash, "Width", 5);
+ ABTS_PTR_NOTNULL(tc, width);
+ ABTS_INT_EQUAL(tc, APR_JSON_LONG, width->v->type);
+ ABTS_INT_EQUAL(tc, 800, width->v->value.lnumber);
+ ids = apr_hash_get(image->v->value.object->hash, "IDs", 3);
+ ABTS_PTR_NOTNULL(tc, ids);
+ ABTS_INT_EQUAL(tc, APR_JSON_ARRAY, ids->v->type);
+ title = apr_hash_get(image->v->value.object->hash, "Title", 5);
+ ABTS_PTR_NOTNULL(tc, title);
+ ABTS_INT_EQUAL(tc, APR_JSON_STRING, title->v->type);
+ animated = apr_hash_get(image->v->value.object->hash, "Animated", 8);
+ ABTS_PTR_NOTNULL(tc, animated);
+ ABTS_INT_EQUAL(tc, APR_JSON_BOOLEAN, animated->v->type);
+ ABTS_TRUE(tc, !animated->v->value.boolean);
+ thumbnail = apr_hash_get(image->v->value.object->hash, "Thumbnail", 9);
+ ABTS_PTR_NOTNULL(tc, thumbnail);
+ ABTS_INT_EQUAL(tc, APR_JSON_OBJECT, thumbnail->v->type);
+ height = apr_hash_get(image->v->value.object->hash, "Height", 6);
+ ABTS_PTR_NOTNULL(tc, height);
+ ABTS_INT_EQUAL(tc, APR_JSON_LONG, height->v->type);
+ ABTS_INT_EQUAL(tc, 600, height->v->value.lnumber);
+
+}
+
+static void test_json_level(abts_case * tc, void *data)
+{
+ apr_json_value_t *json = NULL;
+ apr_status_t status;
+ const char *src;
+ apr_off_t offset = 0;
+
+ src = "{"
+ "\"One\":{"
+ "\"Two\":{"
+ "\"Three\":{";
+
+ status = apr_json_decode(&json, src, APR_JSON_VALUE_STRING, &offset,
+ APR_JSON_FLAGS_WHITESPACE, 2, p);
+
+ ABTS_INT_EQUAL(tc, APR_EINVAL, status);
+
+}
+
+static void test_json_eof(abts_case * tc, void *data)
+{
+ apr_json_value_t *json = NULL;
+ apr_status_t status;
+ const char *src;
+ apr_off_t offset = 0;
+
+ src = "{"
+ "\"One\":{"
+ "\"Two\":{"
+ "\"Three\":{";
+
+ status = apr_json_decode(&json, src, APR_JSON_VALUE_STRING, &offset,
+ APR_JSON_FLAGS_WHITESPACE, 10, p);
+
+ ABTS_INT_EQUAL(tc, APR_EOF, status);
+
+}
+
+abts_suite *testjson(abts_suite * suite)
+{
+ suite = ADD_SUITE(suite);
+
+ abts_run_test(suite, test_json_identity, NULL);
+ abts_run_test(suite, test_json_level, NULL);
+ abts_run_test(suite, test_json_eof, NULL);
+
+ return suite;
+}
diff --git a/test/testutil.h b/test/testutil.h
index 3a3ad2416..261dfd250 100644
--- a/test/testutil.h
+++ b/test/testutil.h
@@ -134,5 +134,6 @@ abts_suite *testdbm(abts_suite *suite);
abts_suite *testlfsabi(abts_suite *suite);
abts_suite *testskiplist(abts_suite *suite);
abts_suite *testsiphash(abts_suite *suite);
+abts_suite *testjson(abts_suite *suite);
#endif /* APR_TEST_INCLUDES */