Commit a764aefa authored by Jussi Kivilinna's avatar Jussi Kivilinna Committed by Pekka Niemimaa
Browse files

netutils/cJSON: add input stream JSON parser

Patch provides new stream parsing engine. Benefits are: parsing input
string using user supplied getc function, which allows reading input
directly from files or socket streams (thus avoid temporary buffer
allocation). Parser has also been fixed to pass all tests in 'json_gtest'
folder.

From github:thingsee/thingsee-sdk pull request:

 "Improve cJSON to allow parse/print from/to stream (caller supplied getc/putc functions) for reducing peak memory usage"

 https://github.com/thingsee/thingsee-sdk/pull/6

Signed-off-by: default avatarJussi Kivilinna <jussi.kivilinna@haltian.com>
(cherry picked from commit ed7c67771fcc97ffc9726241d6db70b24b0c0995)
parent 32f21661
......@@ -99,26 +99,22 @@ typedef struct cJSON
char *string;
} cJSON;
typedef struct cJSON_Hooks
{
void *(*malloc_fn)(size_t sz);
void (*free_fn)(void *ptr);
} cJSON_Hooks;
/****************************************************************************
* Public Functions
****************************************************************************/
/* Supply malloc, realloc and free functions to cJSON */
void cJSON_InitHooks(cJSON_Hooks* hooks);
/* Supply a block of JSON, and this returns a cJSON object you can
* interrogate. Call cJSON_Delete when finished.
*/
cJSON *cJSON_Parse(const char *value);
/* Supply a stream of JSON, and this returns a cJSON object you can
* interrogate. Call cJSON_Delete when finished.
*/
cJSON *cJSON_Parse_Stream(char (*getc_fn)(void *priv), void *priv);
/* Render a cJSON entity to text for transfer/storage. Free the char* when
* finished.
*/
......@@ -166,6 +162,7 @@ cJSON *cJSON_CreateNumber(double num);
cJSON *cJSON_CreateString(const char *string);
cJSON *cJSON_CreateArray();
cJSON *cJSON_CreateObject();
cJSON *cJSON_New_Item(void);
/* These utilities create an Array of count items. */
......
......@@ -38,7 +38,7 @@
include $(APPDIR)/Make.defs
ASRCS =
CSRCS = cJSON.c
CSRCS = cJSON.c cJSON_stream_parse.c
AOBJS = $(ASRCS:.S=$(OBJEXT))
COBJS = $(CSRCS:.c=$(OBJEXT))
......
......@@ -49,6 +49,11 @@
* Pre-processor Definitions
****************************************************************************/
#define cJSON_malloc malloc
#define cJSON_free free
#define cJSON_realloc realloc
#define cJSON_strdup strdup
/****************************************************************************
* Private Data
****************************************************************************/
......@@ -58,9 +63,6 @@ static const char *ep;
static const unsigned char firstByteMark[7] =
{ 0x00, 0x00, 0xc0, 0xe0, 0xf0, 0xf8, 0xfc };
static void *(*cJSON_malloc)(size_t sz) = malloc;
static void (*cJSON_free)(void *ptr) = free;
/****************************************************************************
* Private Prototypes
****************************************************************************/
......@@ -76,34 +78,6 @@ static char *print_object(cJSON *item, int depth, int fmt);
* Private Functions
****************************************************************************/
static char *cJSON_strdup(const char *str)
{
size_t len;
char *copy;
len = strlen(str) + 1;
if (!(copy = (char *)cJSON_malloc(len)))
{
return 0;
}
memcpy(copy, str, len);
return copy;
}
/* Internal constructor. */
static cJSON *cJSON_New_Item(void)
{
cJSON *node = (cJSON *) cJSON_malloc(sizeof(cJSON));
if (node)
{
memset(node, 0, sizeof(cJSON));
}
return node;
}
static int cJSON_strcasecmp(const char *s1, const char *s2)
{
if (!s1)
......@@ -1130,24 +1104,14 @@ static cJSON *create_reference(cJSON *item)
* Public Functions
****************************************************************************/
const char *cJSON_GetErrorPtr(void)
cJSON *cJSON_New_Item(void)
{
return ep;
return calloc(1, sizeof(cJSON));
}
void cJSON_InitHooks(cJSON_Hooks *hooks)
const char *cJSON_GetErrorPtr(void)
{
if (!hooks)
{
/* Reset hooks */
cJSON_malloc = malloc;
cJSON_free = free;
return;
}
cJSON_malloc = (hooks->malloc_fn) ? hooks->malloc_fn : malloc;
cJSON_free = (hooks->free_fn) ? hooks->free_fn : free;
return ep;
}
/* Delete a cJSON structure. */
......@@ -1180,7 +1144,7 @@ void cJSON_Delete(cJSON *c)
/* Parse an object - create a new root, and populate. */
cJSON *cJSON_Parse(const char *value)
cJSON *cJSON_Parse_Old(const char *value)
{
cJSON *c = cJSON_New_Item();
ep = 0;
......
/****************************************************************************
* apps/netutils/json/cJSON_stream_parse.c
*
* This file is a part of NuttX:
*
* Copyright (c) 2011 Gregory Nutt. All rights reserved.
* Copyright (c) 2015 Haltian Ltd.
* Author: Jussi Kivilinna <jussi.kivilinna@haltian.com>
*
* And derives from the cJSON Project which has an MIT license:
*
* Copyright (c) 2009 Dave Gamble
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
****************************************************************************/
/****************************************************************************
* Included Files
****************************************************************************/
#include <stdint.h>
#include <stdbool.h>
#include <string.h>
#include <stdio.h>
#include <math.h>
#include <stdlib.h>
#include <float.h>
#include <limits.h>
#include <ctype.h>
#include <unistd.h>
#include <errno.h>
#include <apps/netutils/cJSON.h>
/****************************************************************************
* Pre-processor Definitions
****************************************************************************/
#define FD_PARSER_CACHE_SIZE 256
#define cJSON_malloc malloc
#define cJSON_free free
#define cJSON_realloc realloc
#define cJSON_strdup strdup
/****************************************************************************
* Private Types
****************************************************************************/
typedef struct cJSON_instream_s
{
char (*getc_fn)(void *priv);
void *fn_priv;
int curr;
} cJSON_instream;
struct parse_fd_priv_s
{
int fd;
ssize_t max_readlen;
size_t nread;
char *buf;
size_t buflen;
size_t bufpos;
};
/****************************************************************************
* Private Data
****************************************************************************/
static const unsigned char firstByteMark[7] =
{ 0x00, 0x00, 0xc0, 0xe0, 0xf0, 0xf8, 0xfc };
/****************************************************************************
* Private Prototypes
****************************************************************************/
static cJSON_instream *stream_parse_value(cJSON *item, cJSON_instream *in);
static cJSON_instream *stream_parse_array(cJSON *item, cJSON_instream *in);
static cJSON_instream *stream_parse_object(cJSON *item, cJSON_instream *in);
/****************************************************************************
* Private Functions
****************************************************************************/
static inline char stream_get(cJSON_instream *in)
{
if (in->curr >= 0)
{
char curr = in->curr;
in->curr = -1;
return curr;
}
return in->getc_fn(in->fn_priv);
}
static inline char stream_peek(cJSON_instream *in)
{
if (in->curr >= 0)
{
return in->curr;
}
in->curr = (unsigned int)in->getc_fn(in->fn_priv) & 0xFF;
return in->curr;
}
/* Parse the input text to generate a number, and populate the result into item. */
static cJSON_instream *stream_parse_number(cJSON *item, cJSON_instream *in)
{
double n = 0, sign = 1, scale = 0;
int subscale = 0, signsubscale = 1;
/* Has sign? */
if (stream_peek(in) == '-')
{
sign = -1;
(void)stream_get(in);
}
/* is zero */
if (stream_peek(in) == '0')
{
(void)stream_get(in);
if (stream_peek(in) != '.' && stream_peek(in) != 'e' &&
stream_peek(in) != 'E')
{
n = sign * 0.0;
item->valuedouble = n;
item->valueint = (int)n;
item->type = cJSON_Number;
return in;
}
}
/* Number? */
if (stream_peek(in) >= '1' && stream_peek(in) <= '9')
{
do
{
n = (n * 10.0) + (stream_get(in) - '0');
}
while (stream_peek(in) >= '0' && stream_peek(in) <= '9');
}
/* Fractional part? */
if (stream_peek(in) == '.')
{
(void)stream_get(in);
if (stream_peek(in) >= '0' && stream_peek(in) <= '9')
{
do
{
n = (n * 10.0) + (stream_get(in) - '0'), scale--;
}
while (stream_peek(in)>= '0' && stream_peek(in)<= '9');
}
}
/* Exponent? */
if (stream_peek(in) == 'e' || stream_peek(in) == 'E')
{
(void)stream_get(in);
if (stream_peek(in) == '+')
{
(void)stream_get(in);
}
/* With sign? */
else if (stream_peek(in) == '-')
{
signsubscale = -1;
(void)stream_get(in);
}
/* Number? */
while (stream_peek(in) >= '0' && stream_peek(in) <= '9')
{
subscale = (subscale * 10) + (stream_get(in) - '0');
}
}
/* number = +/- number.fraction * 10^+/-exponent */
n = sign * n * pow(10.0, (scale + subscale * signsubscale));
item->valuedouble = n;
item->valueint = (int)n;
item->type = cJSON_Number;
return in;
}
static bool parse_realloc(char **out, int *outlen, char **out2, int inclen)
{
char *newp;
*outlen += inclen;
newp = cJSON_realloc(*out, *outlen);
if (!newp)
{
free(*out);
*out = NULL;
*out2 = NULL;
*outlen = 0;
return false;
}
*out2 += newp - *out;
*out = newp;
return true;
}
static int stream_parse_hex(cJSON_instream *in)
{
char ascii = stream_peek(in);
unsigned h;
if (ascii >= '0' && ascii <= '9')
{
h = ascii - '0';
}
else if (ascii >= 'A' && ascii <= 'F')
{
h = 0xA + ascii - 'A';
}
else if (ascii >= 'a' && ascii <= 'f')
{
h = 0xa + ascii - 'a';
}
else
{
return -1;
}
(void)stream_get(in);
return h;
}
static unsigned stream_parse_hex4(cJSON_instream *in)
{
int val;
unsigned h = 0;
val = stream_parse_hex(in);
if (val < 0)
return 0;
h += val;
h <<= 4;
val = stream_parse_hex(in);
if (val < 0)
return 0;
h += val;
h <<= 4;
val = stream_parse_hex(in);
if (val < 0)
return 0;
h += val;
h <<= 4;
val = stream_parse_hex(in);
if (val < 0)
return 0;
h += val;
return h;
}
/* Parse the input text into an unescaped cstring, and populate item. */
static cJSON_instream *stream_parse_string(cJSON *item, cJSON_instream *in)
{
char *ptr2;
char *out;
int len;
int outlen;
unsigned uc;
unsigned uc2;
if (stream_peek(in) != '\"')
{
/* not a string! */
return NULL;
}
outlen = 1;
out = (char *)cJSON_malloc(outlen + 7);
if (!out)
{
return NULL;
}
(void)stream_get(in);
ptr2 = out;
while (stream_peek(in) != '\"' && stream_peek(in))
{
if (stream_peek(in) != '\\')
{
if (!parse_realloc(&out, &outlen, &ptr2, 1))
{
/* not enough memory */
return NULL;
}
*ptr2++ = stream_get(in);
}
else
{
char val;
(void)stream_get(in);
next_escape:
val = stream_peek(in);
switch (val)
{
case 'b':
val = '\b';
break;
case 'f':
val = '\f';
break;
case 'n':
val = '\n';
break;
case 'r':
val = '\r';
break;
case 't':
val = '\t';
break;
}
(void)stream_get(in);
switch (val)
{
default:
if (!parse_realloc(&out, &outlen, &ptr2, 1))
{
/* not enough memory */
return NULL;
}
*ptr2++ = val;
break;
case 'u':
/* Transcode utf16 to utf8. */
/* Get the unicode char. */
uc = stream_parse_hex4(in);
/* Check for invalid. */
if ((uc >= 0xdc00 && uc <= 0xdfff) || uc == 0)
{
break;
}
if (uc >= 0xd800 && uc <= 0xdbff) /* UTF16 surrogate pairs. */
{
/* missing second-half of surrogate. */
if (stream_peek(in) != '\\')
{
break;
}
(void)stream_get(in);
if (stream_peek(in) != 'u')
{
goto next_escape;
}
(void)stream_get(in);
uc2 = stream_parse_hex4(in);
if (uc2 < 0xdc00 || uc2 > 0xdfff)
{
/* Invalid second-half of surrogate. */
break;
}
uc = 0x10000 | ((uc & 0x3ff) << 10) | (uc2 & 0x3ff);
}
len = 4;
if (uc < 0x80)
{
len = 1;
}
else if (uc < 0x800)
{
len = 2;
}
else if (uc < 0x10000)
{
len = 3;
}
if (!parse_realloc(&out, &outlen, &ptr2, len))
{
/* not enough memory */
return NULL;
}
ptr2 += len;
switch (len)
{
case 4:
*--ptr2 = ((uc | 0x80) & 0xbf);
uc >>= 6;
/* no break */
case 3:
*--ptr2 = ((uc | 0x80) & 0xbf);
uc >>= 6;
/* no break */
case 2:
*--ptr2 = ((uc | 0x80) & 0xbf);
uc >>= 6;
/* no break */
case 1:
*--ptr2 = (uc | firstByteMark[len]);
break;
}
ptr2 += len;
break;
}
}
}