master
c 647 lines 16.8 KB
Raw
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 }