mirror of
https://github.com/nginx/nginx.git
synced 2026-10-21 03:21:08 +00:00
Core: added json parser
An event-driven JSON parser implementing the grammar of RFC 8259. The full grammar is recognized: objects, arrays, strings with \uXXXX escapes (including UTF-16 surrogate pairs), numbers, and the true/false/null literals. Tokens are reported to a caller-supplied handler, with nesting bounded by a configurable maximum depth. Based on previous work by Maxim Dounin. Co-authored-by: Dmitry Volyntsev <d.volyntsev@f5.com>
This commit is contained in:
parent
bc1cb5686b
commit
746609903e
3 changed files with 944 additions and 0 deletions
|
|
@ -43,6 +43,7 @@ CORE_DEPS="src/core/nginx.h \
|
|||
src/core/ngx_crypt.h \
|
||||
src/core/ngx_data.h \
|
||||
src/core/ngx_json.h \
|
||||
src/core/ngx_json_parse.h \
|
||||
src/core/ngx_json_unescape.h \
|
||||
src/core/ngx_proxy_protocol.h \
|
||||
src/core/ngx_syslog.h"
|
||||
|
|
@ -84,6 +85,7 @@ CORE_SRCS="src/core/nginx.c \
|
|||
src/core/ngx_crypt.c \
|
||||
src/core/ngx_data.c \
|
||||
src/core/ngx_json.c \
|
||||
src/core/ngx_json_parse.c \
|
||||
src/core/ngx_json_unescape.c \
|
||||
src/core/ngx_proxy_protocol.c \
|
||||
src/core/ngx_syslog.c"
|
||||
|
|
|
|||
831
src/core/ngx_json_parse.c
Normal file
831
src/core/ngx_json_parse.c
Normal file
|
|
@ -0,0 +1,831 @@
|
|||
|
||||
/*
|
||||
* Copyright (C) Maxim Dounin
|
||||
* Copyright (C) Nginx, Inc.
|
||||
*/
|
||||
|
||||
|
||||
/*
|
||||
* JSON parser. RFC 8259 for the JSON grammar.
|
||||
|
||||
* Site http://json.org/ may be useful too.
|
||||
*/
|
||||
|
||||
|
||||
#include <ngx_config.h>
|
||||
#include <ngx_core.h>
|
||||
#include <ngx_json_parse.h>
|
||||
|
||||
|
||||
#define NGX_JSON_NOT_SKIPPING (ngx_uint_t) -1
|
||||
#define ngx_json_skipping(d) ((d) != NGX_JSON_NOT_SKIPPING)
|
||||
#define ngx_json_ws(c) ((c) == ' ' || (c) == '\t' || (c) == CR || (c) == LF)
|
||||
|
||||
|
||||
static ngx_inline ngx_int_t ngx_json_end_number(ngx_json_ctx_t *ctx,
|
||||
u_char *start, u_char *end, ngx_json_state_e *state);
|
||||
static ngx_inline ngx_int_t ngx_json_emit(ngx_json_ctx_t *ctx,
|
||||
ngx_json_event_e event, u_char *data, size_t len);
|
||||
static ngx_inline ngx_json_state_e ngx_json_after_value(ngx_json_ctx_t *ctx);
|
||||
static ngx_inline ngx_int_t ngx_json_close(ngx_json_ctx_t *ctx,
|
||||
ngx_json_event_e event, ngx_json_state_e *state, u_char *p);
|
||||
static ngx_int_t ngx_json_push_state(ngx_json_ctx_t *ctx,
|
||||
ngx_json_container_e container);
|
||||
|
||||
|
||||
void
|
||||
ngx_json_ctx_init(ngx_json_ctx_t *ctx, ngx_pool_t *pool)
|
||||
{
|
||||
ctx->state = ngx_json_start;
|
||||
ctx->pool = pool;
|
||||
ctx->handler = NULL;
|
||||
ctx->data = NULL;
|
||||
ctx->codepoint = 0;
|
||||
ctx->hex_left = 0;
|
||||
|
||||
ctx->depth = 0;
|
||||
ctx->skip_until_depth = NGX_JSON_NOT_SKIPPING;
|
||||
ctx->max_depth = NGX_JSON_DEFAULT_MAX_DEPTH;
|
||||
|
||||
/* stack is allocated lazily on first push, sized to max_depth */
|
||||
|
||||
ctx->stack = NULL;
|
||||
|
||||
ctx->in_key = 0;
|
||||
}
|
||||
|
||||
|
||||
ngx_int_t
|
||||
ngx_json_parse_ctx(ngx_json_ctx_t *ctx, u_char *data, size_t len)
|
||||
{
|
||||
u_char *p, *last, *start;
|
||||
ngx_int_t rc, hd;
|
||||
ngx_uint_t skip_depth;
|
||||
ngx_json_state_e state;
|
||||
|
||||
if (len == 0) {
|
||||
return NGX_DECLINED;
|
||||
}
|
||||
|
||||
if (data == NULL) {
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
if (ctx->state == ngx_json_done) {
|
||||
return NGX_DECLINED;
|
||||
}
|
||||
|
||||
last = data + len;
|
||||
state = ctx->state;
|
||||
skip_depth = ctx->skip_until_depth;
|
||||
start = NULL;
|
||||
|
||||
for (p = data; p < last; p++) {
|
||||
|
||||
/* skipping suppresses emission only; the machine still validates */
|
||||
|
||||
if (ngx_json_skipping(skip_depth) && ctx->depth <= skip_depth
|
||||
&& (state == ngx_json_object_value_separator
|
||||
|| state == ngx_json_array_value_separator
|
||||
|| state == ngx_json_done))
|
||||
{
|
||||
skip_depth = NGX_JSON_NOT_SKIPPING;
|
||||
ctx->skip_until_depth = NGX_JSON_NOT_SKIPPING;
|
||||
p--;
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
switch (state) {
|
||||
|
||||
case ngx_json_start:
|
||||
if (ngx_json_ws(*p)) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (*p == '{') {
|
||||
rc = ngx_json_push_state(ctx, ngx_json_ctx_object);
|
||||
if (rc != NGX_OK) {
|
||||
return rc;
|
||||
}
|
||||
|
||||
if (ngx_json_emit(ctx, NGX_JSON_OBJECT_OPEN, p, 1) != NGX_OK) {
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
state = ngx_json_object;
|
||||
break;
|
||||
}
|
||||
|
||||
if (*p == '[') {
|
||||
rc = ngx_json_push_state(ctx, ngx_json_ctx_array);
|
||||
if (rc != NGX_OK) {
|
||||
return rc;
|
||||
}
|
||||
|
||||
if (ngx_json_emit(ctx, NGX_JSON_ARRAY_OPEN, p, 1) != NGX_OK) {
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
state = ngx_json_array;
|
||||
break;
|
||||
}
|
||||
|
||||
return NGX_DECLINED;
|
||||
|
||||
case ngx_json_object:
|
||||
if (ngx_json_ws(*p)) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (*p == '}') {
|
||||
rc = ngx_json_close(ctx, NGX_JSON_OBJECT_CLOSE, &state, p);
|
||||
if (rc != NGX_OK) {
|
||||
return rc;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
/* fall through */
|
||||
|
||||
case ngx_json_object_next:
|
||||
if (ngx_json_ws(*p)) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (*p == '"') {
|
||||
ctx->in_key = 1;
|
||||
start = p + 1;
|
||||
state = ngx_json_string;
|
||||
break;
|
||||
}
|
||||
|
||||
return NGX_DECLINED;
|
||||
|
||||
case ngx_json_object_name_separator:
|
||||
if (ngx_json_ws(*p)) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (*p == ':') {
|
||||
state = ngx_json_value;
|
||||
break;
|
||||
}
|
||||
|
||||
return NGX_DECLINED;
|
||||
|
||||
case ngx_json_object_value_separator:
|
||||
if (ngx_json_ws(*p)) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (*p == ',') {
|
||||
state = ngx_json_object_next;
|
||||
break;
|
||||
}
|
||||
|
||||
if (*p == '}') {
|
||||
rc = ngx_json_close(ctx, NGX_JSON_OBJECT_CLOSE, &state, p);
|
||||
if (rc != NGX_OK) {
|
||||
return rc;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
return NGX_DECLINED;
|
||||
|
||||
case ngx_json_array_value_separator:
|
||||
if (ngx_json_ws(*p)) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (*p == ',') {
|
||||
state = ngx_json_value;
|
||||
break;
|
||||
}
|
||||
|
||||
if (*p == ']') {
|
||||
rc = ngx_json_close(ctx, NGX_JSON_ARRAY_CLOSE, &state, p);
|
||||
if (rc != NGX_OK) {
|
||||
return rc;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
return NGX_DECLINED;
|
||||
|
||||
case ngx_json_array:
|
||||
if (ngx_json_ws(*p)) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (*p == ']') {
|
||||
rc = ngx_json_close(ctx, NGX_JSON_ARRAY_CLOSE, &state, p);
|
||||
if (rc != NGX_OK) {
|
||||
return rc;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
/* fall through */
|
||||
|
||||
case ngx_json_value:
|
||||
if (ngx_json_ws(*p)) {
|
||||
break;
|
||||
}
|
||||
|
||||
start = p;
|
||||
|
||||
if (*p == '"') {
|
||||
ctx->in_key = 0;
|
||||
start = p + 1;
|
||||
state = ngx_json_string;
|
||||
break;
|
||||
}
|
||||
|
||||
if (*p == '{') {
|
||||
rc = ngx_json_emit(ctx, NGX_JSON_OBJECT_OPEN, p, 1);
|
||||
if (rc != NGX_OK) {
|
||||
return rc;
|
||||
}
|
||||
|
||||
rc = ngx_json_push_state(ctx, ngx_json_ctx_object);
|
||||
if (rc != NGX_OK) {
|
||||
return rc;
|
||||
}
|
||||
|
||||
state = ngx_json_object;
|
||||
break;
|
||||
}
|
||||
|
||||
if (*p == '[') {
|
||||
rc = ngx_json_emit(ctx, NGX_JSON_ARRAY_OPEN, p, 1);
|
||||
if (rc != NGX_OK) {
|
||||
return rc;
|
||||
}
|
||||
|
||||
rc = ngx_json_push_state(ctx, ngx_json_ctx_array);
|
||||
if (rc != NGX_OK) {
|
||||
return rc;
|
||||
}
|
||||
|
||||
state = ngx_json_array;
|
||||
break;
|
||||
}
|
||||
|
||||
if (*p == '-') {
|
||||
state = ngx_json_number_minus;
|
||||
break;
|
||||
}
|
||||
|
||||
if (*p == '0') {
|
||||
state = ngx_json_number_int_zero;
|
||||
break;
|
||||
}
|
||||
|
||||
if (*p >= '1' && *p <= '9') {
|
||||
state = ngx_json_number_int;
|
||||
break;
|
||||
}
|
||||
|
||||
if (*p == 't') {
|
||||
state = ngx_json_true_t;
|
||||
break;
|
||||
}
|
||||
|
||||
if (*p == 'f') {
|
||||
state = ngx_json_false_f;
|
||||
break;
|
||||
}
|
||||
|
||||
if (*p == 'n') {
|
||||
state = ngx_json_null_n;
|
||||
break;
|
||||
}
|
||||
|
||||
return NGX_DECLINED;
|
||||
|
||||
case ngx_json_string:
|
||||
if (*p == '"') {
|
||||
|
||||
if (ctx->in_key) {
|
||||
rc = ngx_json_emit(ctx, NGX_JSON_KEY, start, p - start);
|
||||
if (rc != NGX_OK) {
|
||||
return rc;
|
||||
}
|
||||
|
||||
ctx->in_key = 0;
|
||||
state = ngx_json_object_name_separator;
|
||||
|
||||
} else {
|
||||
rc = ngx_json_emit(ctx, NGX_JSON_VALUE_STRING, start,
|
||||
p - start);
|
||||
if (rc != NGX_OK) {
|
||||
return rc;
|
||||
}
|
||||
|
||||
state = ngx_json_after_value(ctx);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
if (*p == '\\') {
|
||||
state = ngx_json_escaped;
|
||||
break;
|
||||
}
|
||||
|
||||
if (*p < ' ') {
|
||||
return NGX_DECLINED;
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case ngx_json_escaped:
|
||||
if (*p == '"' || *p == '\\' || *p == '/' || *p == 'b'
|
||||
|| *p == 'f' || *p == 'n' || *p == 'r' || *p == 't')
|
||||
{
|
||||
state = ngx_json_string;
|
||||
break;
|
||||
}
|
||||
|
||||
if (*p == 'u') {
|
||||
ctx->codepoint = 0;
|
||||
ctx->hex_left = 4;
|
||||
state = ngx_json_escaped_hex;
|
||||
break;
|
||||
}
|
||||
|
||||
return NGX_DECLINED;
|
||||
|
||||
case ngx_json_escaped_hex:
|
||||
|
||||
/* accumulate the four hex digits of the first \uXXXX escape */
|
||||
|
||||
hd = ngx_json_hex_digit(*p);
|
||||
if (hd == NGX_ERROR) {
|
||||
return NGX_DECLINED;
|
||||
}
|
||||
|
||||
ctx->codepoint = (ctx->codepoint << 4) | (uint32_t) hd;
|
||||
|
||||
if (--ctx->hex_left > 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (ctx->codepoint >= 0xD800 && ctx->codepoint <= 0xDBFF) {
|
||||
/* high surrogate - must be followed by \uDC00..\uDFFF */
|
||||
state = ngx_json_surrogate_start;
|
||||
break;
|
||||
}
|
||||
|
||||
if (ctx->codepoint >= 0xDC00 && ctx->codepoint <= 0xDFFF) {
|
||||
/* lone low surrogate */
|
||||
return NGX_DECLINED;
|
||||
}
|
||||
|
||||
state = ngx_json_string;
|
||||
break;
|
||||
|
||||
case ngx_json_surrogate_start:
|
||||
if (*p == '\\') {
|
||||
state = ngx_json_surrogate_u;
|
||||
break;
|
||||
}
|
||||
|
||||
/* high surrogate not followed by \uXXXX */
|
||||
return NGX_DECLINED;
|
||||
|
||||
case ngx_json_surrogate_u:
|
||||
if (*p == 'u') {
|
||||
ctx->codepoint = 0;
|
||||
ctx->hex_left = 4;
|
||||
state = ngx_json_surrogate_hex;
|
||||
break;
|
||||
}
|
||||
|
||||
/* high surrogate followed by non-\u escape */
|
||||
return NGX_DECLINED;
|
||||
|
||||
case ngx_json_surrogate_hex:
|
||||
|
||||
/* accumulate the four hex digits of the low \uXXXX escape */
|
||||
|
||||
hd = ngx_json_hex_digit(*p);
|
||||
if (hd == NGX_ERROR) {
|
||||
return NGX_DECLINED;
|
||||
}
|
||||
|
||||
ctx->codepoint = (ctx->codepoint << 4) | (uint32_t) hd;
|
||||
|
||||
if (--ctx->hex_left > 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (ctx->codepoint >= 0xDC00 && ctx->codepoint <= 0xDFFF) {
|
||||
/* valid surrogate pair */
|
||||
state = ngx_json_string;
|
||||
break;
|
||||
}
|
||||
|
||||
/* high surrogate not followed by low surrogate */
|
||||
return NGX_DECLINED;
|
||||
|
||||
case ngx_json_number_minus:
|
||||
if (*p >= '1' && *p <= '9') {
|
||||
state = ngx_json_number_int;
|
||||
break;
|
||||
}
|
||||
|
||||
if (*p == '0') {
|
||||
state = ngx_json_number_int_zero;
|
||||
break;
|
||||
}
|
||||
|
||||
return NGX_DECLINED;
|
||||
|
||||
case ngx_json_number_int_zero:
|
||||
if (*p == '.') {
|
||||
state = ngx_json_number_frac;
|
||||
break;
|
||||
}
|
||||
|
||||
if (*p == 'e' || *p == 'E') {
|
||||
/* No sense, but permitted by RFC */
|
||||
state = ngx_json_number_exp;
|
||||
break;
|
||||
}
|
||||
|
||||
p--;
|
||||
|
||||
rc = ngx_json_end_number(ctx, start, p + 1, &state);
|
||||
if (rc != NGX_OK) {
|
||||
return rc;
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case ngx_json_number_int:
|
||||
if (*p >= '0' && *p <= '9') {
|
||||
break;
|
||||
}
|
||||
|
||||
if (*p == '.') {
|
||||
state = ngx_json_number_frac;
|
||||
break;
|
||||
}
|
||||
|
||||
if (*p == 'e' || *p == 'E') {
|
||||
/* No sense, but permitted by RFC */
|
||||
state = ngx_json_number_exp;
|
||||
break;
|
||||
}
|
||||
|
||||
p--;
|
||||
|
||||
rc = ngx_json_end_number(ctx, start, p + 1, &state);
|
||||
if (rc!= NGX_OK) {
|
||||
return rc;
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case ngx_json_number_frac:
|
||||
if (*p >= '0' && *p <= '9') {
|
||||
state = ngx_json_number_frac_digit;
|
||||
break;
|
||||
}
|
||||
|
||||
return NGX_DECLINED;
|
||||
|
||||
case ngx_json_number_frac_digit:
|
||||
if (*p >= '0' && *p <= '9') {
|
||||
break;
|
||||
}
|
||||
|
||||
if (*p == 'e' || *p == 'E') {
|
||||
state = ngx_json_number_exp;
|
||||
break;
|
||||
}
|
||||
|
||||
p--;
|
||||
|
||||
rc = ngx_json_end_number(ctx, start, p + 1, &state);
|
||||
if (rc != NGX_OK) {
|
||||
return rc;
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case ngx_json_number_exp:
|
||||
if (*p == '-' || *p == '+') {
|
||||
state = ngx_json_number_exp_plusminus;
|
||||
break;
|
||||
}
|
||||
|
||||
if (*p >= '0' && *p <= '9') {
|
||||
state = ngx_json_number_exp_digit;
|
||||
break;
|
||||
}
|
||||
|
||||
return NGX_DECLINED;
|
||||
|
||||
case ngx_json_number_exp_plusminus:
|
||||
if (*p >= '0' && *p <= '9') {
|
||||
state = ngx_json_number_exp_digit;
|
||||
break;
|
||||
}
|
||||
|
||||
return NGX_DECLINED;
|
||||
|
||||
case ngx_json_number_exp_digit:
|
||||
if (*p >= '0' && *p <= '9') {
|
||||
break;
|
||||
}
|
||||
|
||||
p--;
|
||||
|
||||
rc = ngx_json_end_number(ctx, start, p + 1, &state);
|
||||
if (rc != NGX_OK) {
|
||||
return rc;
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case ngx_json_true_t:
|
||||
if (*p == 'r') {
|
||||
state = ngx_json_true_tr;
|
||||
break;
|
||||
}
|
||||
|
||||
return NGX_DECLINED;
|
||||
|
||||
case ngx_json_true_tr:
|
||||
if (*p == 'u') {
|
||||
state = ngx_json_true_tru;
|
||||
break;
|
||||
}
|
||||
|
||||
return NGX_DECLINED;
|
||||
|
||||
case ngx_json_true_tru:
|
||||
if (*p == 'e') {
|
||||
rc = ngx_json_emit(ctx, NGX_JSON_VALUE_BOOL, start,
|
||||
p + 1 - start);
|
||||
if (rc != NGX_OK) {
|
||||
return rc;
|
||||
}
|
||||
|
||||
state = ngx_json_after_value(ctx);
|
||||
break;
|
||||
}
|
||||
|
||||
return NGX_DECLINED;
|
||||
|
||||
case ngx_json_false_f:
|
||||
if (*p == 'a') {
|
||||
state = ngx_json_false_fa;
|
||||
break;
|
||||
}
|
||||
|
||||
return NGX_DECLINED;
|
||||
|
||||
case ngx_json_false_fa:
|
||||
if (*p == 'l') {
|
||||
state = ngx_json_false_fal;
|
||||
break;
|
||||
}
|
||||
|
||||
return NGX_DECLINED;
|
||||
|
||||
case ngx_json_false_fal:
|
||||
if (*p == 's') {
|
||||
state = ngx_json_false_fals;
|
||||
break;
|
||||
}
|
||||
|
||||
return NGX_DECLINED;
|
||||
|
||||
case ngx_json_false_fals:
|
||||
if (*p == 'e') {
|
||||
rc = ngx_json_emit(ctx, NGX_JSON_VALUE_BOOL, start,
|
||||
p + 1 - start);
|
||||
if (rc != NGX_OK) {
|
||||
return rc;
|
||||
}
|
||||
|
||||
state = ngx_json_after_value(ctx);
|
||||
break;
|
||||
}
|
||||
|
||||
return NGX_DECLINED;
|
||||
|
||||
case ngx_json_null_n:
|
||||
if (*p == 'u') {
|
||||
state = ngx_json_null_nu;
|
||||
break;
|
||||
}
|
||||
|
||||
return NGX_DECLINED;
|
||||
|
||||
case ngx_json_null_nu:
|
||||
if (*p == 'l') {
|
||||
state = ngx_json_null_nul;
|
||||
break;
|
||||
}
|
||||
|
||||
return NGX_DECLINED;
|
||||
|
||||
case ngx_json_null_nul:
|
||||
if (*p == 'l') {
|
||||
rc = ngx_json_emit(ctx, NGX_JSON_VALUE_NULL, start,
|
||||
p + 1 - start);
|
||||
if (rc != NGX_OK) {
|
||||
return rc;
|
||||
}
|
||||
|
||||
state = ngx_json_after_value(ctx);
|
||||
break;
|
||||
}
|
||||
|
||||
return NGX_DECLINED;
|
||||
|
||||
case ngx_json_done:
|
||||
if (ngx_json_ws(*p)) {
|
||||
break;
|
||||
}
|
||||
|
||||
return NGX_DECLINED;
|
||||
}
|
||||
|
||||
skip_depth = ctx->skip_until_depth;
|
||||
}
|
||||
|
||||
ctx->state = state;
|
||||
|
||||
if (state == ngx_json_done) {
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
return NGX_DECLINED;
|
||||
}
|
||||
|
||||
|
||||
ngx_int_t
|
||||
ngx_json_parse(ngx_pool_t *pool, ngx_str_t *json, ngx_json_handler_pt handler,
|
||||
void *data)
|
||||
{
|
||||
ngx_json_ctx_t jctx;
|
||||
|
||||
ngx_json_ctx_init(&jctx, pool);
|
||||
|
||||
jctx.data = data;
|
||||
jctx.handler = handler;
|
||||
|
||||
return ngx_json_parse_ctx(&jctx, json->data, json->len);
|
||||
}
|
||||
|
||||
|
||||
static ngx_inline ngx_int_t
|
||||
ngx_json_end_number(ngx_json_ctx_t *ctx, u_char *start, u_char *end,
|
||||
ngx_json_state_e *state)
|
||||
{
|
||||
ngx_int_t rc;
|
||||
|
||||
rc = ngx_json_emit(ctx, NGX_JSON_VALUE_NUMBER, start, end - start);
|
||||
if (rc != NGX_OK) {
|
||||
return rc;
|
||||
}
|
||||
|
||||
*state = ngx_json_after_value(ctx);
|
||||
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
|
||||
static ngx_inline ngx_int_t
|
||||
ngx_json_emit(ngx_json_ctx_t *ctx, ngx_json_event_e event, u_char *data,
|
||||
size_t len)
|
||||
{
|
||||
ngx_int_t rc;
|
||||
ngx_str_t token;
|
||||
|
||||
if (ngx_json_skipping(ctx->skip_until_depth)) {
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
if (ctx->handler == NULL) {
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
token.data = data;
|
||||
token.len = len;
|
||||
rc = ctx->handler(ctx, event, &token);
|
||||
|
||||
if (rc == NGX_JSON_SKIP) {
|
||||
|
||||
/*
|
||||
* NGX_JSON_SKIP prunes the rest of the current container. It is
|
||||
* honoured only where it is meaningful:
|
||||
*
|
||||
* - OBJECT_OPEN / ARRAY_OPEN: skip the whole container;
|
||||
* - a scalar VALUE_* that is an array element: skip the rest of
|
||||
* that array.
|
||||
*
|
||||
* A skip requested on a CLOSE, or on an object member value, is
|
||||
* ignored: object members are navigated by KEY (use a KEY skip),
|
||||
* and a CLOSE fires after the container is already complete.
|
||||
*/
|
||||
|
||||
switch (event) {
|
||||
|
||||
case NGX_JSON_KEY:
|
||||
case NGX_JSON_OBJECT_OPEN:
|
||||
case NGX_JSON_ARRAY_OPEN:
|
||||
ctx->skip_until_depth = ctx->depth;
|
||||
break;
|
||||
|
||||
case NGX_JSON_VALUE_STRING:
|
||||
case NGX_JSON_VALUE_NUMBER:
|
||||
case NGX_JSON_VALUE_BOOL:
|
||||
case NGX_JSON_VALUE_NULL:
|
||||
if (ctx->depth > 0
|
||||
&& ctx->stack[ctx->depth - 1] == ngx_json_ctx_array
|
||||
) {
|
||||
ctx->skip_until_depth = ctx->depth - 1;
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
|
||||
static ngx_inline ngx_json_state_e
|
||||
ngx_json_after_value(ngx_json_ctx_t *ctx)
|
||||
{
|
||||
if (ctx->depth == 0) {
|
||||
return ngx_json_done;
|
||||
}
|
||||
|
||||
if (ctx->stack[ctx->depth - 1] == ngx_json_ctx_object) {
|
||||
return ngx_json_object_value_separator;
|
||||
}
|
||||
|
||||
return ngx_json_array_value_separator;
|
||||
}
|
||||
|
||||
|
||||
static ngx_inline ngx_int_t
|
||||
ngx_json_close(ngx_json_ctx_t *ctx, ngx_json_event_e event,
|
||||
ngx_json_state_e *state, u_char *p)
|
||||
{
|
||||
ngx_int_t rc;
|
||||
|
||||
ctx->depth--;
|
||||
|
||||
rc = ngx_json_emit(ctx, event, p, 1);
|
||||
if (rc != NGX_OK) {
|
||||
return rc;
|
||||
}
|
||||
|
||||
*state = ngx_json_after_value(ctx);
|
||||
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
|
||||
static ngx_int_t
|
||||
ngx_json_push_state(ngx_json_ctx_t *ctx, ngx_json_container_e container)
|
||||
{
|
||||
ngx_json_container_e *stack;
|
||||
|
||||
if (ctx->depth >= ctx->max_depth) {
|
||||
return NGX_DECLINED;
|
||||
}
|
||||
|
||||
if (ctx->stack == NULL) {
|
||||
stack = ngx_palloc(ctx->pool,
|
||||
ctx->max_depth * sizeof(ngx_json_container_e));
|
||||
if (stack == NULL) {
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
ctx->stack = stack;
|
||||
}
|
||||
|
||||
ctx->stack[ctx->depth] = container;
|
||||
ctx->depth++;
|
||||
|
||||
return NGX_OK;
|
||||
}
|
||||
111
src/core/ngx_json_parse.h
Normal file
111
src/core/ngx_json_parse.h
Normal file
|
|
@ -0,0 +1,111 @@
|
|||
|
||||
/*
|
||||
* Copyright (C) Maxim Dounin
|
||||
* Copyright (C) Nginx, Inc.
|
||||
*/
|
||||
|
||||
|
||||
#ifndef _NGX_JSON_PARSE_H_INCLUDED_
|
||||
#define _NGX_JSON_PARSE_H_INCLUDED_
|
||||
|
||||
|
||||
#include <ngx_config.h>
|
||||
#include <ngx_core.h>
|
||||
#include <ngx_json_unescape.h>
|
||||
|
||||
|
||||
#define NGX_JSON_SKIP -7
|
||||
#define NGX_JSON_DEFAULT_MAX_DEPTH 64
|
||||
|
||||
|
||||
typedef enum {
|
||||
NGX_JSON_OBJECT_OPEN = 0,
|
||||
NGX_JSON_OBJECT_CLOSE,
|
||||
NGX_JSON_ARRAY_OPEN,
|
||||
NGX_JSON_ARRAY_CLOSE,
|
||||
NGX_JSON_KEY,
|
||||
NGX_JSON_VALUE_STRING,
|
||||
NGX_JSON_VALUE_NUMBER,
|
||||
NGX_JSON_VALUE_BOOL,
|
||||
NGX_JSON_VALUE_NULL
|
||||
} ngx_json_event_e;
|
||||
|
||||
|
||||
typedef enum {
|
||||
ngx_json_start = 0,
|
||||
ngx_json_done,
|
||||
ngx_json_object,
|
||||
ngx_json_object_next,
|
||||
ngx_json_object_name_separator,
|
||||
ngx_json_value,
|
||||
ngx_json_object_value_separator,
|
||||
ngx_json_array_value_separator,
|
||||
ngx_json_array,
|
||||
ngx_json_string,
|
||||
ngx_json_number_minus,
|
||||
ngx_json_number_int,
|
||||
ngx_json_number_int_zero,
|
||||
ngx_json_number_frac,
|
||||
ngx_json_number_frac_digit,
|
||||
ngx_json_number_exp,
|
||||
ngx_json_number_exp_plusminus,
|
||||
ngx_json_number_exp_digit,
|
||||
ngx_json_escaped,
|
||||
ngx_json_escaped_hex, /* first \uXXXX: reading the four hex digits */
|
||||
ngx_json_surrogate_start, /* expect '\' beginning the low surrogate */
|
||||
ngx_json_surrogate_u, /* expect 'u' of the low surrogate */
|
||||
ngx_json_surrogate_hex, /* low \uXXXX: reading the four hex digits */
|
||||
ngx_json_true_t,
|
||||
ngx_json_true_tr,
|
||||
ngx_json_true_tru,
|
||||
ngx_json_false_f,
|
||||
ngx_json_false_fa,
|
||||
ngx_json_false_fal,
|
||||
ngx_json_false_fals,
|
||||
ngx_json_null_n,
|
||||
ngx_json_null_nu,
|
||||
ngx_json_null_nul
|
||||
} ngx_json_state_e;
|
||||
|
||||
|
||||
typedef enum {
|
||||
ngx_json_ctx_none = 0,
|
||||
ngx_json_ctx_object,
|
||||
ngx_json_ctx_array
|
||||
} ngx_json_container_e;
|
||||
|
||||
|
||||
typedef struct ngx_json_ctx_s ngx_json_ctx_t;
|
||||
|
||||
|
||||
typedef ngx_int_t (*ngx_json_handler_pt)(ngx_json_ctx_t *ctx,
|
||||
ngx_json_event_e event, ngx_str_t *token);
|
||||
|
||||
|
||||
struct ngx_json_ctx_s {
|
||||
ngx_json_state_e state;
|
||||
|
||||
ngx_json_container_e *stack;
|
||||
ngx_uint_t depth;
|
||||
ngx_uint_t max_depth;
|
||||
|
||||
uint32_t codepoint;
|
||||
ngx_uint_t hex_left; /* hex digits still expected */
|
||||
|
||||
ngx_uint_t skip_until_depth;
|
||||
|
||||
ngx_pool_t *pool;
|
||||
ngx_json_handler_pt handler;
|
||||
void *data;
|
||||
|
||||
unsigned in_key; /* unsigned in_key:1 */
|
||||
};
|
||||
|
||||
|
||||
void ngx_json_ctx_init(ngx_json_ctx_t *ctx, ngx_pool_t *pool);
|
||||
ngx_int_t ngx_json_parse_ctx(ngx_json_ctx_t *ctx, u_char *data, size_t len);
|
||||
ngx_int_t ngx_json_parse(ngx_pool_t *pool, ngx_str_t *json,
|
||||
ngx_json_handler_pt handler, void *data);
|
||||
|
||||
|
||||
#endif /* _NGX_JSON_PARSE_H_INCLUDED_ */
|
||||
Loading…
Add table
Add a link
Reference in a new issue