| 1 | // SPDX-License-Identifier: GPL-3.0-or-later |
| 2 | |
| 3 | #include "log2journal.h" |
| 4 | |
| 5 | #define JSON_ERROR_LINE_MAX 1024 |
| 6 | #define JSON_KEY_MAX 1024 |
| 7 | #define JSON_DEPTH_MAX 100 |
| 8 | |
| 9 | struct log_json_state { |
| 10 | LOG_JOB *jb; |
| 11 | |
| 12 | const char *line; |
| 13 | uint32_t pos; |
| 14 | uint32_t depth; |
| 15 | char *stack[JSON_DEPTH_MAX]; |
| 16 | |
| 17 | char key[JSON_KEY_MAX]; |
| 18 | char msg[JSON_ERROR_LINE_MAX]; |
| 19 | }; |
| 20 | |
| 21 | static inline bool json_parse_object(LOG_JSON_STATE *js); |
| 22 | static inline bool json_parse_array(LOG_JSON_STATE *js); |
| 23 | |
| 24 | #define json_current_pos(js) &(js)->line[(js)->pos] |
| 25 | #define json_consume_char(js) ++(js)->pos |
| 26 | |
| 27 | static inline void json_process_key_value(LOG_JSON_STATE *js, const char *value, size_t len) { |
| 28 | log_job_send_extracted_key_value(js->jb, js->key, value, len); |
| 29 | } |
| 30 | |
| 31 | static inline void json_skip_spaces(LOG_JSON_STATE *js) { |
| 32 | const char *s = json_current_pos(js); |
| 33 | const char *start = s; |
| 34 | |
| 35 | while(isspace(*s)) s++; |
| 36 | |
| 37 | js->pos += s - start; |
| 38 | } |
| 39 | |
| 40 | static inline bool json_expect_char_after_white_space(LOG_JSON_STATE *js, const char *expected) { |
| 41 | json_skip_spaces(js); |
| 42 | |
| 43 | const char *s = json_current_pos(js); |
| 44 | for(const char *e = expected; *e ;e++) { |
| 45 | if (*s == *e) |
| 46 | return true; |
| 47 | } |
| 48 | |
| 49 | snprintf(js->msg, sizeof(js->msg), |
| 50 | "JSON PARSER: character '%c' is not one of the expected characters (%s), at pos %u", |
| 51 | *s ? *s : '?', expected, js->pos); |
| 52 | |
| 53 | return false; |
| 54 | } |
| 55 | |
| 56 | static inline bool json_parse_null(LOG_JSON_STATE *js) { |
| 57 | const char *s = json_current_pos(js); |
| 58 | if (strncmp(s, "null", 4) == 0) { |
| 59 | json_process_key_value(js, "null", 4); |
| 60 | js->pos += 4; |
| 61 | return true; |
| 62 | } |
| 63 | else { |
| 64 | snprintf(js->msg, sizeof(js->msg), |
| 65 | "JSON PARSER: expected 'null', found '%.4s' at position %u", s, js->pos); |
| 66 | return false; |
| 67 | } |
| 68 | } |
| 69 | |
| 70 | static inline bool json_parse_true(LOG_JSON_STATE *js) { |
| 71 | const char *s = json_current_pos(js); |
| 72 | if (strncmp(s, "true", 4) == 0) { |
| 73 | json_process_key_value(js, "true", 4); |
| 74 | js->pos += 4; |
| 75 | return true; |
| 76 | } |
| 77 | else { |
| 78 | snprintf(js->msg, sizeof(js->msg), |
| 79 | "JSON PARSER: expected 'true', found '%.4s' at position %u", s, js->pos); |
| 80 | return false; |
| 81 | } |
| 82 | } |
| 83 | |
| 84 | static inline bool json_parse_false(LOG_JSON_STATE *js) { |
| 85 | const char *s = json_current_pos(js); |
| 86 | if (strncmp(s, "false", 5) == 0) { |
| 87 | json_process_key_value(js, "false", 5); |
| 88 | js->pos += 5; |
| 89 | return true; |
| 90 | } |
| 91 | else { |
| 92 | snprintf(js->msg, sizeof(js->msg), |
| 93 | "JSON PARSER: expected 'false', found '%.4s' at position %u", s, js->pos); |
| 94 | return false; |
| 95 | } |
| 96 | } |
| 97 | |
| 98 | static inline bool json_parse_number(LOG_JSON_STATE *js) { |
| 99 | static __thread char value[8192]; |
| 100 | |
| 101 | value[0] = '\0'; |
| 102 | char *d = value; |
| 103 | const char *s = json_current_pos(js); |
| 104 | size_t remaining = sizeof(value) - 1; // Reserve space for null terminator |
| 105 | |
| 106 | // Optional minus sign |
| 107 | if (*s == '-') { |
| 108 | *d++ = *s++; |
| 109 | remaining--; |
| 110 | } |
| 111 | |
| 112 | // Digits before decimal point |
| 113 | while (*s >= '0' && *s <= '9') { |
| 114 | if (remaining < 2) { |
| 115 | snprintf(js->msg, sizeof(js->msg), "JSON PARSER: truncated number value at position %u", js->pos); |
| 116 | return false; |
| 117 | } |
| 118 | *d++ = *s++; |
| 119 | remaining--; |
| 120 | } |
| 121 | |
| 122 | // Decimal point and fractional part |
| 123 | if (*s == '.') { |
| 124 | *d++ = *s++; |
| 125 | remaining--; |
| 126 | |
| 127 | while (*s >= '0' && *s <= '9') { |
| 128 | if (remaining < 2) { |
| 129 | snprintf(js->msg, sizeof(js->msg), "JSON PARSER: truncated fractional part at position %u", js->pos); |
| 130 | return false; |
| 131 | } |
| 132 | *d++ = *s++; |
| 133 | remaining--; |
| 134 | } |
| 135 | } |
| 136 | |
| 137 | // Exponent part |
| 138 | if (*s == 'e' || *s == 'E') { |
| 139 | *d++ = *s++; |
| 140 | remaining--; |
| 141 | |
| 142 | // Optional sign in exponent |
| 143 | if (*s == '+' || *s == '-') { |
| 144 | *d++ = *s++; |
| 145 | remaining--; |
| 146 | } |
| 147 | |
| 148 | while (*s >= '0' && *s <= '9') { |
| 149 | if (remaining < 2) { |
| 150 | snprintf(js->msg, sizeof(js->msg), "JSON PARSER: truncated exponent at position %u", js->pos); |
| 151 | return false; |
| 152 | } |
| 153 | *d++ = *s++; |
| 154 | remaining--; |
| 155 | } |
| 156 | } |
| 157 | |
| 158 | *d = '\0'; |
| 159 | js->pos += d - value; |
| 160 | |
| 161 | if (d > value) { |
| 162 | json_process_key_value(js, value, d - value); |
| 163 | return true; |
| 164 | } else { |
| 165 | snprintf(js->msg, sizeof(js->msg), "JSON PARSER: invalid number format at position %u", js->pos); |
| 166 | return false; |
| 167 | } |
| 168 | } |
| 169 | |
| 170 | static inline void copy_newline(LOG_JSON_STATE *js __maybe_unused, char **d, size_t *remaining) { |
| 171 | if(*remaining > 3) { |
| 172 | *(*d)++ = '\\'; |
| 173 | *(*d)++ = 'n'; |
| 174 | (*remaining) -= 2; |
| 175 | } |
| 176 | } |
| 177 | |
| 178 | static inline void copy_tab(LOG_JSON_STATE *js __maybe_unused, char **d, size_t *remaining) { |
| 179 | if(*remaining > 3) { |
| 180 | *(*d)++ = '\\'; |
| 181 | *(*d)++ = 't'; |
| 182 | (*remaining) -= 2; |
| 183 | } |
| 184 | } |
| 185 | |
| 186 | static inline bool encode_utf8(unsigned codepoint, char **d, size_t *remaining) { |
| 187 | if (codepoint <= 0x7F) { |
| 188 | // 1-byte sequence |
| 189 | if (*remaining < 2) return false; // +1 for the null |
| 190 | if(codepoint == '\n') |
| 191 | copy_newline(NULL, d, remaining); |
| 192 | else if(codepoint == '\t') |
| 193 | copy_tab(NULL, d, remaining); |
| 194 | else |
| 195 | *(*d)++ = (char)codepoint; |
| 196 | (*remaining)--; |
| 197 | } |
| 198 | else if (codepoint <= 0x7FF) { |
| 199 | // 2-byte sequence |
| 200 | if (*remaining < 3) return false; // +1 for the null |
| 201 | *(*d)++ = (char)(0xC0 | ((codepoint >> 6) & 0x1F)); |
| 202 | *(*d)++ = (char)(0x80 | (codepoint & 0x3F)); |
| 203 | (*remaining) -= 2; |
| 204 | } |
| 205 | else if (codepoint <= 0xFFFF) { |
| 206 | // 3-byte sequence |
| 207 | if (*remaining < 4) return false; // +1 for the null |
| 208 | *(*d)++ = (char)(0xE0 | ((codepoint >> 12) & 0x0F)); |
| 209 | *(*d)++ = (char)(0x80 | ((codepoint >> 6) & 0x3F)); |
| 210 | *(*d)++ = (char)(0x80 | (codepoint & 0x3F)); |
| 211 | (*remaining) -= 3; |
| 212 | } |
| 213 | else if (codepoint <= 0x10FFFF) { |
| 214 | // 4-byte sequence |
| 215 | if (*remaining < 5) return false; // +1 for the null |
| 216 | *(*d)++ = (char)(0xF0 | ((codepoint >> 18) & 0x07)); |
| 217 | *(*d)++ = (char)(0x80 | ((codepoint >> 12) & 0x3F)); |
| 218 | *(*d)++ = (char)(0x80 | ((codepoint >> 6) & 0x3F)); |
| 219 | *(*d)++ = (char)(0x80 | (codepoint & 0x3F)); |
| 220 | (*remaining) -= 4; |
| 221 | } |
| 222 | else |
| 223 | // Invalid code point |
| 224 | return false; |
| 225 | |
| 226 | return true; |
| 227 | } |
| 228 | |
| 229 | size_t parse_surrogate(const char *s, char *d, size_t *remaining) { |
| 230 | if (s[0] != '\\' || (s[1] != 'u' && s[1] != 'U')) { |
| 231 | return 0; // Not a valid Unicode escape sequence |
| 232 | } |
| 233 | |
| 234 | char hex[9] = {0}; // Buffer for the hexadecimal value |
| 235 | unsigned codepoint; |
| 236 | |
| 237 | if (s[1] == 'u') { |
| 238 | // Handle \uXXXX |
| 239 | if (!isxdigit(s[2]) || !isxdigit(s[3]) || !isxdigit(s[4]) || !isxdigit(s[5])) { |
| 240 | return 0; // Not a valid \uXXXX sequence |
| 241 | } |
| 242 | |
| 243 | hex[0] = s[2]; |
| 244 | hex[1] = s[3]; |
| 245 | hex[2] = s[4]; |
| 246 | hex[3] = s[5]; |
| 247 | codepoint = (unsigned)strtoul(hex, NULL, 16); |
| 248 | |
| 249 | if (codepoint >= 0xD800 && codepoint <= 0xDBFF) { |
| 250 | // Possible start of surrogate pair |
| 251 | if (s[6] == '\\' && s[7] == 'u' && isxdigit(s[8]) && isxdigit(s[9]) && |
| 252 | isxdigit(s[10]) && isxdigit(s[11])) { |
| 253 | // Valid low surrogate |
| 254 | unsigned low_surrogate = strtoul(&s[8], NULL, 16); |
| 255 | if (low_surrogate < 0xDC00 || low_surrogate > 0xDFFF) { |
| 256 | return 0; // Invalid low surrogate |
| 257 | } |
| 258 | codepoint = 0x10000 + ((codepoint - 0xD800) << 10) + (low_surrogate - 0xDC00); |
| 259 | return encode_utf8(codepoint, &d, remaining) ? 12 : 0; // \uXXXX\uXXXX |
| 260 | } |
| 261 | } |
| 262 | |
| 263 | // Single \uXXXX |
| 264 | return encode_utf8(codepoint, &d, remaining) ? 6 : 0; |
| 265 | } |
| 266 | else { |
| 267 | // Handle \UXXXXXXXX |
| 268 | for (int i = 2; i < 10; i++) { |
| 269 | if (!isxdigit(s[i])) { |
| 270 | return 0; // Not a valid \UXXXXXXXX sequence |
| 271 | } |
| 272 | hex[i - 2] = s[i]; |
| 273 | } |
| 274 | codepoint = (unsigned)strtoul(hex, NULL, 16); |
| 275 | return encode_utf8(codepoint, &d, remaining) ? 10 : 0; // \UXXXXXXXX |
| 276 | } |
| 277 | } |
| 278 | |
| 279 | static inline bool json_parse_string(LOG_JSON_STATE *js) { |
| 280 | static __thread char value[JOURNAL_MAX_VALUE_LEN]; |
| 281 | |
| 282 | if(!json_expect_char_after_white_space(js, "\"")) |
| 283 | return false; |
| 284 | |
| 285 | json_consume_char(js); |
| 286 | |
| 287 | value[0] = '\0'; |
| 288 | char *d = value; |
| 289 | const char *s = json_current_pos(js); |
| 290 | size_t remaining = sizeof(value); |
| 291 | |
| 292 | while (*s && *s != '"') { |
| 293 | char c; |
| 294 | |
| 295 | if (*s == '\\') { |
| 296 | s++; |
| 297 | |
| 298 | switch (*s) { |
| 299 | case 'n': |
| 300 | copy_newline(js, &d, &remaining); |
| 301 | s++; |
| 302 | continue; |
| 303 | |
| 304 | case 't': |
| 305 | copy_tab(js, &d, &remaining); |
| 306 | s++; |
| 307 | continue; |
| 308 | |
| 309 | case 'f': |
| 310 | case 'b': |
| 311 | case 'r': |
| 312 | c = ' '; |
| 313 | s++; |
| 314 | break; |
| 315 | |
| 316 | case 'u': { |
| 317 | size_t old_remaining = remaining; |
| 318 | size_t consumed = parse_surrogate(s - 1, d, &remaining); |
| 319 | if (consumed > 0) { |
| 320 | s += consumed - 1; // -1 because we already incremented s after '\\' |
| 321 | d += old_remaining - remaining; |
| 322 | continue; |
| 323 | } |
| 324 | else { |
| 325 | *d++ = '\\'; |
| 326 | remaining--; |
| 327 | c = *s++; |
| 328 | } |
| 329 | } |
| 330 | break; |
| 331 | |
| 332 | default: |
| 333 | c = *s++; |
| 334 | break; |
| 335 | } |
| 336 | } |
| 337 | else |
| 338 | c = *s++; |
| 339 | |
| 340 | if(remaining < 2) { |
| 341 | snprintf(js->msg, sizeof(js->msg), |
| 342 | "JSON PARSER: truncated string value at position %u", js->pos); |
| 343 | return false; |
| 344 | } |
| 345 | else { |
| 346 | *d++ = c; |
| 347 | remaining--; |
| 348 | } |
| 349 | } |
| 350 | *d = '\0'; |
| 351 | js->pos += s - json_current_pos(js); |
| 352 | |
| 353 | if(!json_expect_char_after_white_space(js, "\"")) |
| 354 | return false; |
| 355 | |
| 356 | json_consume_char(js); |
| 357 | |
| 358 | if(d > value) |
| 359 | json_process_key_value(js, value, d - value); |
| 360 | |
| 361 | return true; |
| 362 | } |
| 363 | |
| 364 | static inline bool json_parse_key_and_push(LOG_JSON_STATE *js) { |
| 365 | if (!json_expect_char_after_white_space(js, "\"")) |
| 366 | return false; |
| 367 | |
| 368 | if(js->depth >= JSON_DEPTH_MAX - 1) { |
| 369 | snprintf(js->msg, sizeof(js->msg), |
| 370 | "JSON PARSER: object too deep, at position %u", js->pos); |
| 371 | return false; |
| 372 | } |
| 373 | |
| 374 | json_consume_char(js); |
| 375 | |
| 376 | char *d = js->stack[js->depth]; |
| 377 | if(js->depth) |
| 378 | *d++ = '_'; |
| 379 | |
| 380 | size_t remaining = sizeof(js->key) - (d - js->key); |
| 381 | |
| 382 | const char *s = json_current_pos(js); |
| 383 | char last_c = '\0'; |
| 384 | while(*s && *s != '\"') { |
| 385 | char c; |
| 386 | |
| 387 | if (*s == '\\') { |
| 388 | s++; |
| 389 | c = (char)((*s == 'u') ? '_' : journal_key_characters_map[(unsigned char)*s]); |
| 390 | s += (*s == 'u') ? 5 : 1; |
| 391 | } |
| 392 | else |
| 393 | c = journal_key_characters_map[(unsigned char)*s++]; |
| 394 | |
| 395 | if(c == '_' && last_c == '_') |
| 396 | continue; |
| 397 | else { |
| 398 | if(remaining < 2) { |
| 399 | snprintf(js->msg, sizeof(js->msg), |
| 400 | "JSON PARSER: key buffer full - keys are too long, at position %u", js->pos); |
| 401 | return false; |
| 402 | } |
| 403 | *d++ = c; |
| 404 | remaining--; |
| 405 | } |
| 406 | |
| 407 | last_c = c; |
| 408 | } |
| 409 | *d = '\0'; |
| 410 | js->pos += s - json_current_pos(js); |
| 411 | |
| 412 | if (!json_expect_char_after_white_space(js, "\"")) |
| 413 | return false; |
| 414 | |
| 415 | json_consume_char(js); |
| 416 | |
| 417 | js->stack[++js->depth] = d; |
| 418 | |
| 419 | return true; |
| 420 | } |
| 421 | |
| 422 | static inline bool json_key_pop(LOG_JSON_STATE *js) { |
| 423 | if(js->depth <= 0) { |
| 424 | snprintf(js->msg, sizeof(js->msg), |
| 425 | "JSON PARSER: cannot pop a key at depth %u, at position %u", js->depth, js->pos); |
| 426 | return false; |
| 427 | } |
| 428 | |
| 429 | char *k = js->stack[js->depth--]; |
| 430 | *k = '\0'; |
| 431 | return true; |
| 432 | } |
| 433 | |
| 434 | static inline bool json_parse_value(LOG_JSON_STATE *js) { |
| 435 | if(!json_expect_char_after_white_space(js, "-.0123456789tfn\"{[")) |
| 436 | return false; |
| 437 | |
| 438 | const char *s = json_current_pos(js); |
| 439 | switch(*s) { |
| 440 | case '-': |
| 441 | case '0': |
| 442 | case '1': |
| 443 | case '2': |
| 444 | case '3': |
| 445 | case '4': |
| 446 | case '5': |
| 447 | case '6': |
| 448 | case '7': |
| 449 | case '8': |
| 450 | case '9': |
| 451 | return json_parse_number(js); |
| 452 | |
| 453 | case 't': |
| 454 | return json_parse_true(js); |
| 455 | |
| 456 | case 'f': |
| 457 | return json_parse_false(js); |
| 458 | |
| 459 | case 'n': |
| 460 | return json_parse_null(js); |
| 461 | |
| 462 | case '"': |
| 463 | return json_parse_string(js); |
| 464 | |
| 465 | case '{': |
| 466 | return json_parse_object(js); |
| 467 | |
| 468 | case '[': |
| 469 | return json_parse_array(js); |
| 470 | } |
| 471 | |
| 472 | snprintf(js->msg, sizeof(js->msg), |
| 473 | "JSON PARSER: unexpected character at position %u", js->pos); |
| 474 | return false; |
| 475 | } |
| 476 | |
| 477 | static inline bool json_key_index_and_push(LOG_JSON_STATE *js, size_t index) { |
| 478 | char *d = js->stack[js->depth]; |
| 479 | if(js->depth > 0) { |
| 480 | *d++ = '_'; |
| 481 | } |
| 482 | |
| 483 | // Convert index to string manually |
| 484 | char temp[32]; |
| 485 | char *t = temp + sizeof(temp) - 1; // Start at the end of the buffer |
| 486 | *t = '\0'; |
| 487 | |
| 488 | do { |
| 489 | *--t = (char)((index % 10) + '0'); |
| 490 | index /= 10; |
| 491 | } while (index > 0); |
| 492 | |
| 493 | size_t remaining = sizeof(js->key) - (d - js->key); |
| 494 | |
| 495 | // Append the index to the key |
| 496 | while (*t) { |
| 497 | if(remaining < 2) { |
| 498 | snprintf(js->msg, sizeof(js->msg), |
| 499 | "JSON PARSER: key buffer full - keys are too long, at position %u", js->pos); |
| 500 | return false; |
| 501 | } |
| 502 | |
| 503 | *d++ = *t++; |
| 504 | remaining--; |
| 505 | } |
| 506 | |
| 507 | *d = '\0'; // Null-terminate the key |
| 508 | js->stack[++js->depth] = d; |
| 509 | |
| 510 | return true; |
| 511 | } |
| 512 | |
| 513 | static inline bool json_parse_array(LOG_JSON_STATE *js) { |
| 514 | if(!json_expect_char_after_white_space(js, "[")) |
| 515 | return false; |
| 516 | |
| 517 | json_consume_char(js); |
| 518 | |
| 519 | const char *s = json_current_pos(js); |
| 520 | if(*s == ']') |
| 521 | json_consume_char(js); |
| 522 | else { |
| 523 | size_t index = 0; |
| 524 | do { |
| 525 | if (!json_key_index_and_push(js, index)) |
| 526 | return false; |
| 527 | |
| 528 | if (!json_parse_value(js)) |
| 529 | return false; |
| 530 | |
| 531 | json_key_pop(js); |
| 532 | |
| 533 | if (!json_expect_char_after_white_space(js, ",]")) |
| 534 | return false; |
| 535 | |
| 536 | s = json_current_pos(js); |
| 537 | json_consume_char(js); |
| 538 | if (*s == ',') { |
| 539 | index++; |
| 540 | continue; |
| 541 | } else // ']' |
| 542 | break; |
| 543 | |
| 544 | } while (true); |
| 545 | } |
| 546 | |
| 547 | return true; |
| 548 | } |
| 549 | |
| 550 | static inline bool json_parse_object(LOG_JSON_STATE *js) { |
| 551 | if(!json_expect_char_after_white_space(js, "{")) |
| 552 | return false; |
| 553 | |
| 554 | json_consume_char(js); |
| 555 | |
| 556 | const char *s = json_current_pos(js); |
| 557 | if(*s == '}') |
| 558 | json_consume_char(js); |
| 559 | else { |
| 560 | do { |
| 561 | if (!json_expect_char_after_white_space(js, "\"")) |
| 562 | return false; |
| 563 | |
| 564 | if (!json_parse_key_and_push(js)) |
| 565 | return false; |
| 566 | |
| 567 | if (!json_expect_char_after_white_space(js, ":")) |
| 568 | return false; |
| 569 | |
| 570 | json_consume_char(js); |
| 571 | |
| 572 | if (!json_parse_value(js)) |
| 573 | return false; |
| 574 | |
| 575 | json_key_pop(js); |
| 576 | |
| 577 | if (!json_expect_char_after_white_space(js, ",}")) |
| 578 | return false; |
| 579 | |
| 580 | s = json_current_pos(js); |
| 581 | json_consume_char(js); |
| 582 | if (*s == ',') |
| 583 | continue; |
| 584 | else // '}' |
| 585 | break; |
| 586 | |
| 587 | } while (true); |
| 588 | } |
| 589 | |
| 590 | return true; |
| 591 | } |
| 592 | |
| 593 | LOG_JSON_STATE *json_parser_create(LOG_JOB *jb) { |
| 594 | LOG_JSON_STATE *js = mallocz(sizeof(LOG_JSON_STATE)); |
| 595 | memset(js, 0, sizeof(LOG_JSON_STATE)); |
| 596 | js->jb = jb; |
| 597 | |
| 598 | if(jb->prefix) |
| 599 | copy_to_buffer(js->key, sizeof(js->key), js->jb->prefix, strlen(js->jb->prefix)); |
| 600 | |
| 601 | js->stack[0] = &js->key[strlen(js->key)]; |
| 602 | |
| 603 | return js; |
| 604 | } |
| 605 | |
| 606 | void json_parser_destroy(LOG_JSON_STATE *js) { |
| 607 | if(js) |
| 608 | freez(js); |
| 609 | } |
| 610 | |
| 611 | const char *json_parser_error(LOG_JSON_STATE *js) { |
| 612 | return js->msg; |
| 613 | } |
| 614 | |
| 615 | bool json_parse_document(LOG_JSON_STATE *js, const char *txt) { |
| 616 | js->line = txt; |
| 617 | js->pos = 0; |
| 618 | js->msg[0] = '\0'; |
| 619 | js->stack[0][0] = '\0'; |
| 620 | js->depth = 0; |
| 621 | |
| 622 | if(!json_parse_object(js)) |
| 623 | return false; |
| 624 | |
| 625 | json_skip_spaces(js); |
| 626 | const char *s = json_current_pos(js); |
| 627 | |
| 628 | if(*s) { |
| 629 | snprintf(js->msg, sizeof(js->msg), |
| 630 | "JSON PARSER: excess characters found after document is finished, at position %u", js->pos); |
| 631 | return false; |
| 632 | } |
| 633 | |
| 634 | return true; |
| 635 | } |
| 636 | |
| 637 | void json_test(void) { |
| 638 | LOG_JOB jb = { 0 }; |
| 639 | log_job_init(&jb); |
| 640 | log_job_key_prefix_set(&jb, "NGINX_", 6); |
| 641 | LOG_JSON_STATE *json = json_parser_create(&jb); |
| 642 | |
| 643 | json_parse_document(json, "{\"value\":\"\\u\\u039A\\u03B1\\u03BB\\u03B7\\u03BC\\u03AD\\u03C1\\u03B1\"}"); |
| 644 | |
| 645 | json_parser_destroy(json); |
| 646 | log_job_cleanup(&jb); |
| 647 | } |