]> git.kaiwu.me - nginx.git/commitdiff
JSON: report escaped strings as separate parser events
authorEvgeny Shirykalov <e.shirykalov@f5.com>
Wed, 30 Sep 2026 16:52:47 +0000 (09:52 -0700)
committerAleksei Bavshin <a.bavshin@f5.com>
Fri, 16 Oct 2026 16:42:01 +0000 (09:42 -0700)
The parser now emits NGX_JSON_KEY_ESCAPED and
NGX_JSON_VALUE_STRING_ESCAPED for strings holding escape sequences, so
consumers no longer rescan tokens.

src/core/ngx_json_parse.c
src/core/ngx_json_parse.h
src/http/modules/ngx_http_json_module.c

index 7eefae0b17a1e0fddc73553af057486b79535bd1..5def8e2a6fa0d661aa505b80e9b8195464769858 100644 (file)
@@ -52,6 +52,7 @@ ngx_json_ctx_init(ngx_json_ctx_t *ctx, ngx_pool_t *pool)
     ctx->stack = NULL;
 
     ctx->in_key = 0;
+    ctx->escaped = 0;
 }
 
 
@@ -61,6 +62,7 @@ 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_event_e   event;
     ngx_json_state_e   state;
 
     if (len == 0) {
@@ -130,6 +132,7 @@ ngx_json_parse_ctx(ngx_json_ctx_t *ctx, u_char *data, size_t len)
 
             if (*p == '"') {
                 ctx->in_key = 1;
+                ctx->escaped = 0;
                 start = p + 1;
                 state = ngx_json_string;
                 break;
@@ -216,6 +219,7 @@ ngx_json_parse_ctx(ngx_json_ctx_t *ctx, u_char *data, size_t len)
 
             if (*p == '"') {
                 ctx->in_key = 0;
+                ctx->escaped = 0;
                 start = p + 1;
                 state = ngx_json_string;
                 break;
@@ -287,7 +291,10 @@ ngx_json_parse_ctx(ngx_json_ctx_t *ctx, u_char *data, size_t len)
             if (*p == '"') {
 
                 if (ctx->in_key) {
-                    rc = ngx_json_emit(ctx, NGX_JSON_KEY, start, p - start);
+                    event = ctx->escaped ? NGX_JSON_KEY_ESCAPED
+                                         : NGX_JSON_KEY;
+
+                    rc = ngx_json_emit(ctx, event, start, p - start);
                     if (rc != NGX_OK) {
                         return rc;
                     }
@@ -296,8 +303,10 @@ ngx_json_parse_ctx(ngx_json_ctx_t *ctx, u_char *data, size_t len)
                     state = ngx_json_object_name_separator;
 
                 } else {
-                    rc = ngx_json_emit(ctx, NGX_JSON_VALUE_STRING, start,
-                                      p - start);
+                    event = ctx->escaped ? NGX_JSON_VALUE_STRING_ESCAPED
+                                         : NGX_JSON_VALUE_STRING;
+
+                    rc = ngx_json_emit(ctx, event, start, p - start);
                     if (rc != NGX_OK) {
                         return rc;
                     }
@@ -309,6 +318,7 @@ ngx_json_parse_ctx(ngx_json_ctx_t *ctx, u_char *data, size_t len)
             }
 
             if (*p == '\\') {
+                ctx->escaped = 1;
                 state = ngx_json_escaped;
                 break;
             }
@@ -734,12 +744,14 @@ ngx_json_emit(ngx_json_ctx_t *ctx, ngx_json_event_e event, u_char *data,
         switch (event) {
 
         case NGX_JSON_KEY:
+        case NGX_JSON_KEY_ESCAPED:
         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_STRING_ESCAPED:
         case NGX_JSON_VALUE_NUMBER:
         case NGX_JSON_VALUE_BOOL:
         case NGX_JSON_VALUE_NULL:
index 2d96aa7744be4fd32268f2dc56f0a0860a10c96c..38ede957e03c5110062485fd9ea0f3f2d3c1c654 100644 (file)
@@ -24,7 +24,9 @@ typedef enum {
     NGX_JSON_ARRAY_OPEN,
     NGX_JSON_ARRAY_CLOSE,
     NGX_JSON_KEY,
+    NGX_JSON_KEY_ESCAPED,
     NGX_JSON_VALUE_STRING,
+    NGX_JSON_VALUE_STRING_ESCAPED,
     NGX_JSON_VALUE_NUMBER,
     NGX_JSON_VALUE_BOOL,
     NGX_JSON_VALUE_NULL
@@ -98,7 +100,8 @@ struct ngx_json_ctx_s {
     ngx_json_handler_pt    handler;
     void                  *data;
 
-    unsigned               in_key; /* unsigned in_key:1 */
+    ngx_uint_t             in_key;           /* unsigned in_key:1; */
+    ngx_uint_t             escaped;          /* unsigned escaped:1; */
 };
 
 
index 57f106c6cdefa69794d697f1e58d538d7e5c64f7..eb0008fa69b6a41ea34590753c4961e3aeaf619d 100644 (file)
@@ -60,7 +60,8 @@ typedef struct {
     ngx_http_request_t            *r;
     ngx_http_variable_value_t     *values;
     ngx_array_t                    stack;
-    ngx_str_t                      current_key;
+    ngx_str_t                      key_buffer;
+    ngx_uint_t                     in_member;
     ngx_http_json_node_t          *current_node;
     ngx_http_json_node_t          *root;
 } ngx_http_json_state_t;
@@ -678,7 +679,9 @@ static ngx_int_t
 ngx_http_json_handler(ngx_json_ctx_t *ctx, ngx_json_event_e event,
     ngx_str_t *token)
 {
-    ngx_str_t               slice, value;
+    size_t                  size;
+    u_char                 *p;
+    ngx_str_t               key, slice, value;
     ngx_uint_t              is_member;
     ngx_http_json_node_t   *node;
     ngx_http_json_state_t  *state;
@@ -699,14 +702,14 @@ ngx_http_json_handler(ngx_json_ctx_t *ctx, ngx_json_event_e event,
 
         if (state->stack.nelts == 0) {
 
-            ngx_str_null(&state->current_key);
+            state->in_member = 0;
             state->current_node = NULL;
 
             return ngx_http_json_push(state, event, token->data, state->root);
         }
 
-        is_member = (state->current_key.data != NULL);
-        ngx_str_null(&state->current_key);
+        is_member = state->in_member;
+        state->in_member = 0;
 
         if (is_member) {
 
@@ -748,48 +751,60 @@ ngx_http_json_handler(ngx_json_ctx_t *ctx, ngx_json_event_e event,
         break;
 
     case NGX_JSON_KEY:
+    case NGX_JSON_KEY_ESCAPED:
 
-        if (ngx_strlchr(token->data, token->data + token->len, '\\') == NULL) {
-            state->current_key = *token;
+        key = *token;
 
-        } else {
-            state->current_key.data = ngx_pnalloc(ctx->pool, token->len);
-            if (state->current_key.data == NULL) {
-                return NGX_ERROR;
+        if (event == NGX_JSON_KEY_ESCAPED) {
+            if (token->len > state->key_buffer.len) {
+                size = token->len;
+
+                if (state->key_buffer.len <= NGX_MAX_SIZE_T_VALUE / 2) {
+                    size = ngx_max(size, state->key_buffer.len * 2);
+                }
+
+                p = ngx_pnalloc(ctx->pool, size);
+                if (p == NULL) {
+                    return NGX_ERROR;
+                }
+
+                state->key_buffer.data = p;
+                state->key_buffer.len = size;
             }
 
-            state->current_key.len = token->len;
+            key = state->key_buffer;
 
-            if (ngx_json_unescape_string(&state->current_key, token) != NGX_OK)
-            {
+            if (ngx_json_unescape_string(&key, token) != NGX_OK) {
                 return NGX_ERROR;
             }
         }
 
-        node = ngx_http_json_lookup_key(top->node, &state->current_key);
+        node = ngx_http_json_lookup_key(top->node, &key);
         if (node == NULL) {
-            ngx_str_null(&state->current_key);
+            state->in_member = 0;
             state->current_node = NULL;
             return NGX_JSON_SKIP;
         }
 
+        state->in_member = 1;
         state->current_node = node;
 
         break;
 
     case NGX_JSON_VALUE_STRING:
+    case NGX_JSON_VALUE_STRING_ESCAPED:
     case NGX_JSON_VALUE_NUMBER:
     case NGX_JSON_VALUE_BOOL:
     case NGX_JSON_VALUE_NULL:
 
         if (top == NULL) {
-            ngx_str_null(&state->current_key);
+            state->in_member = 0;
             state->current_node = NULL;
             break;
         }
 
-        is_member = (state->current_key.data != NULL);
-        ngx_str_null(&state->current_key);
+        is_member = state->in_member;
+        state->in_member = 0;
 
         if (is_member) {
 
@@ -808,7 +823,7 @@ ngx_http_json_handler(ngx_json_ctx_t *ctx, ngx_json_event_e event,
             value = *token;
 
             if (ngx_http_json_store(state, node, &value,
-                                          event == NGX_JSON_VALUE_STRING)
+                                    event == NGX_JSON_VALUE_STRING_ESCAPED)
                 != NGX_OK)
             {
                 return NGX_ERROR;