mirror of
https://github.com/DarkFlippers/unleashed-firmware
synced 2024-11-27 15:00:46 +00:00
0154018363
* FBT: cdefines to env, libs order * API: strtod, modf, itoa, calloc * Apps: elk js * Apps: mjs * JS: scripts as assets * mjs: composite resolver * mjs: stack trace * ELK JS example removed * MJS thread, MJS lib modified to support script interruption * JS console UI * Module system, BadUSB bindings rework * JS notifications, simple dialog, BadUSB demo * Custom dialogs, dialog demo * MJS as system library, some dirty hacks to make it compile * Plugin-based js modules * js_uart(BadUART) module * js_uart: support for byte array arguments * Script icon and various fixes * File browser: multiple extensions filter, running js scripts from app loader * Running js scripts from archive browser * JS Runner as system app * Example scripts moved to /ext/apps/Scripts * JS bytecode listing generation * MJS builtin printf cleanup * JS examples cleanup * mbedtls version fix * Unused lib cleanup * Making PVS happy & TODOs cleanup * TODOs cleanup #2 * MJS: initial typed arrays support * JS: fix mem leak in uart destructor Co-authored-by: SG <who.just.the.doctor@gmail.com> Co-authored-by: Aleksandr Kutuzov <alleteam@gmail.com>
1528 lines
48 KiB
C
1528 lines
48 KiB
C
/*
|
|
* Copyright (c) 2004-2013 Sergey Lyubka <valenok@gmail.com>
|
|
* Copyright (c) 2018 Cesanta Software Limited
|
|
* All rights reserved
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the ""License"");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an ""AS IS"" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
#define _CRT_SECURE_NO_WARNINGS /* Disable deprecation warning in VS2005+ */
|
|
|
|
#include "frozen.h"
|
|
|
|
#include <ctype.h>
|
|
#include <stdarg.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
#if !defined(WEAK)
|
|
#if(defined(__GNUC__) || defined(__TI_COMPILER_VERSION__)) && !defined(_WIN32)
|
|
#define WEAK __attribute__((weak))
|
|
#else
|
|
#define WEAK
|
|
#endif
|
|
#endif
|
|
|
|
#ifdef _WIN32
|
|
#undef snprintf
|
|
#undef vsnprintf
|
|
#define snprintf cs_win_snprintf
|
|
#define vsnprintf cs_win_vsnprintf
|
|
int cs_win_snprintf(char* str, size_t size, const char* format, ...);
|
|
int cs_win_vsnprintf(char* str, size_t size, const char* format, va_list ap);
|
|
#if _MSC_VER >= 1700
|
|
#include <stdint.h>
|
|
#else
|
|
typedef _int64 int64_t;
|
|
typedef unsigned _int64 uint64_t;
|
|
#endif
|
|
#define PRId64 "I64d"
|
|
#define PRIu64 "I64u"
|
|
#else /* _WIN32 */
|
|
/* <inttypes.h> wants this for C++ */
|
|
#ifndef __STDC_FORMAT_MACROS
|
|
#define __STDC_FORMAT_MACROS
|
|
#endif
|
|
#include <inttypes.h>
|
|
#endif /* _WIN32 */
|
|
|
|
#ifndef INT64_FMT
|
|
#define INT64_FMT PRId64
|
|
#endif
|
|
#ifndef UINT64_FMT
|
|
#define UINT64_FMT PRIu64
|
|
#endif
|
|
|
|
#ifndef va_copy
|
|
#define va_copy(x, y) x = y
|
|
#endif
|
|
|
|
#ifndef JSON_ENABLE_ARRAY
|
|
#define JSON_ENABLE_ARRAY 1
|
|
#endif
|
|
|
|
struct frozen {
|
|
const char* end;
|
|
const char* cur;
|
|
|
|
const char* cur_name;
|
|
size_t cur_name_len;
|
|
|
|
/* For callback API */
|
|
char path[JSON_MAX_PATH_LEN];
|
|
size_t path_len;
|
|
void* callback_data;
|
|
json_walk_callback_t callback;
|
|
};
|
|
|
|
struct fstate {
|
|
const char* ptr;
|
|
size_t path_len;
|
|
};
|
|
|
|
#define SET_STATE(fr, ptr, str, len) \
|
|
struct fstate fstate = {(ptr), (fr)->path_len}; \
|
|
json_append_to_path((fr), (str), (len));
|
|
|
|
#define CALL_BACK(fr, tok, value, len) \
|
|
do { \
|
|
if((fr)->callback && ((fr)->path_len == 0 || (fr)->path[(fr)->path_len - 1] != '.')) { \
|
|
struct json_token t = {(value), (int)(len), (tok)}; \
|
|
\
|
|
/* Call the callback with the given value and current name */ \
|
|
(fr)->callback( \
|
|
(fr)->callback_data, (fr)->cur_name, (fr)->cur_name_len, (fr)->path, &t); \
|
|
\
|
|
/* Reset the name */ \
|
|
(fr)->cur_name = NULL; \
|
|
(fr)->cur_name_len = 0; \
|
|
} \
|
|
} while(0)
|
|
|
|
static int json_append_to_path(struct frozen* f, const char* str, int size) {
|
|
int n = f->path_len;
|
|
int left = sizeof(f->path) - n - 1;
|
|
if(size > left) size = left;
|
|
memcpy(f->path + n, str, size);
|
|
f->path[n + size] = '\0';
|
|
f->path_len += size;
|
|
return n;
|
|
}
|
|
|
|
static void json_truncate_path(struct frozen* f, size_t len) {
|
|
f->path_len = len;
|
|
f->path[len] = '\0';
|
|
}
|
|
|
|
static int json_parse_object(struct frozen* f);
|
|
static int json_parse_value(struct frozen* f);
|
|
|
|
#define EXPECT(cond, err_code) \
|
|
do { \
|
|
if(!(cond)) return (err_code); \
|
|
} while(0)
|
|
|
|
#define TRY(expr) \
|
|
do { \
|
|
int _n = expr; \
|
|
if(_n < 0) return _n; \
|
|
} while(0)
|
|
|
|
#define END_OF_STRING (-1)
|
|
|
|
static int json_left(const struct frozen* f) {
|
|
return f->end - f->cur;
|
|
}
|
|
|
|
static int json_isspace(int ch) {
|
|
return ch == ' ' || ch == '\t' || ch == '\r' || ch == '\n';
|
|
}
|
|
|
|
static void json_skip_whitespaces(struct frozen* f) {
|
|
while(f->cur < f->end && json_isspace(*f->cur)) f->cur++;
|
|
}
|
|
|
|
static int json_cur(struct frozen* f) {
|
|
json_skip_whitespaces(f);
|
|
return f->cur >= f->end ? END_OF_STRING : *(unsigned char*)f->cur;
|
|
}
|
|
|
|
static int json_test_and_skip(struct frozen* f, int expected) {
|
|
int ch = json_cur(f);
|
|
if(ch == expected) {
|
|
f->cur++;
|
|
return 0;
|
|
}
|
|
return ch == END_OF_STRING ? JSON_STRING_INCOMPLETE : JSON_STRING_INVALID;
|
|
}
|
|
|
|
static int json_isalpha(int ch) {
|
|
return (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z');
|
|
}
|
|
|
|
static int json_isdigit(int ch) {
|
|
return ch >= '0' && ch <= '9';
|
|
}
|
|
|
|
static int json_isxdigit(int ch) {
|
|
return json_isdigit(ch) || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F');
|
|
}
|
|
|
|
static int json_get_escape_len(const char* s, int len) {
|
|
switch(*s) {
|
|
case 'u':
|
|
return len < 6 ? JSON_STRING_INCOMPLETE :
|
|
json_isxdigit(s[1]) && json_isxdigit(s[2]) && json_isxdigit(s[3]) &&
|
|
json_isxdigit(s[4]) ?
|
|
5 :
|
|
JSON_STRING_INVALID;
|
|
case '"':
|
|
case '\\':
|
|
case '/':
|
|
case 'b':
|
|
case 'f':
|
|
case 'n':
|
|
case 'r':
|
|
case 't':
|
|
return len < 2 ? JSON_STRING_INCOMPLETE : 1;
|
|
default:
|
|
return JSON_STRING_INVALID;
|
|
}
|
|
}
|
|
|
|
/* identifier = letter { letter | digit | '_' } */
|
|
static int json_parse_identifier(struct frozen* f) {
|
|
EXPECT(json_isalpha(json_cur(f)), JSON_STRING_INVALID);
|
|
{
|
|
SET_STATE(f, f->cur, "", 0);
|
|
while(f->cur < f->end &&
|
|
(*f->cur == '_' || json_isalpha(*f->cur) || json_isdigit(*f->cur))) {
|
|
f->cur++;
|
|
}
|
|
json_truncate_path(f, fstate.path_len);
|
|
CALL_BACK(f, JSON_TYPE_STRING, fstate.ptr, f->cur - fstate.ptr);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int json_get_utf8_char_len(unsigned char ch) {
|
|
if((ch & 0x80) == 0) return 1;
|
|
switch(ch & 0xf0) {
|
|
case 0xf0:
|
|
return 4;
|
|
case 0xe0:
|
|
return 3;
|
|
default:
|
|
return 2;
|
|
}
|
|
}
|
|
|
|
/* string = '"' { quoted_printable_chars } '"' */
|
|
static int json_parse_string(struct frozen* f) {
|
|
int n, ch = 0, len = 0;
|
|
TRY(json_test_and_skip(f, '"'));
|
|
{
|
|
SET_STATE(f, f->cur, "", 0);
|
|
for(; f->cur < f->end; f->cur += len) {
|
|
ch = *(unsigned char*)f->cur;
|
|
len = json_get_utf8_char_len((unsigned char)ch);
|
|
EXPECT(ch >= 32 && len > 0, JSON_STRING_INVALID); /* No control chars */
|
|
EXPECT(len <= json_left(f), JSON_STRING_INCOMPLETE);
|
|
if(ch == '\\') {
|
|
EXPECT((n = json_get_escape_len(f->cur + 1, json_left(f))) > 0, n);
|
|
len += n;
|
|
} else if(ch == '"') {
|
|
json_truncate_path(f, fstate.path_len);
|
|
CALL_BACK(f, JSON_TYPE_STRING, fstate.ptr, f->cur - fstate.ptr);
|
|
f->cur++;
|
|
break;
|
|
};
|
|
}
|
|
}
|
|
return ch == '"' ? 0 : JSON_STRING_INCOMPLETE;
|
|
}
|
|
|
|
/* number = [ '-' ] digit+ [ '.' digit+ ] [ ['e'|'E'] ['+'|'-'] digit+ ] */
|
|
static int json_parse_number(struct frozen* f) {
|
|
int ch = json_cur(f);
|
|
SET_STATE(f, f->cur, "", 0);
|
|
if(ch == '-') f->cur++;
|
|
EXPECT(f->cur < f->end, JSON_STRING_INCOMPLETE);
|
|
if(f->cur + 1 < f->end && f->cur[0] == '0' && f->cur[1] == 'x') {
|
|
f->cur += 2;
|
|
EXPECT(f->cur < f->end, JSON_STRING_INCOMPLETE);
|
|
EXPECT(json_isxdigit(f->cur[0]), JSON_STRING_INVALID);
|
|
while(f->cur < f->end && json_isxdigit(f->cur[0])) f->cur++;
|
|
} else {
|
|
EXPECT(json_isdigit(f->cur[0]), JSON_STRING_INVALID);
|
|
while(f->cur < f->end && json_isdigit(f->cur[0])) f->cur++;
|
|
if(f->cur < f->end && f->cur[0] == '.') {
|
|
f->cur++;
|
|
EXPECT(f->cur < f->end, JSON_STRING_INCOMPLETE);
|
|
EXPECT(json_isdigit(f->cur[0]), JSON_STRING_INVALID);
|
|
while(f->cur < f->end && json_isdigit(f->cur[0])) f->cur++;
|
|
}
|
|
if(f->cur < f->end && (f->cur[0] == 'e' || f->cur[0] == 'E')) {
|
|
f->cur++;
|
|
EXPECT(f->cur < f->end, JSON_STRING_INCOMPLETE);
|
|
if((f->cur[0] == '+' || f->cur[0] == '-')) f->cur++;
|
|
EXPECT(f->cur < f->end, JSON_STRING_INCOMPLETE);
|
|
EXPECT(json_isdigit(f->cur[0]), JSON_STRING_INVALID);
|
|
while(f->cur < f->end && json_isdigit(f->cur[0])) f->cur++;
|
|
}
|
|
}
|
|
json_truncate_path(f, fstate.path_len);
|
|
CALL_BACK(f, JSON_TYPE_NUMBER, fstate.ptr, f->cur - fstate.ptr);
|
|
return 0;
|
|
}
|
|
|
|
#if JSON_ENABLE_ARRAY
|
|
/* array = '[' [ value { ',' value } ] ']' */
|
|
static int json_parse_array(struct frozen* f) {
|
|
int i = 0, current_path_len;
|
|
char buf[20];
|
|
CALL_BACK(f, JSON_TYPE_ARRAY_START, NULL, 0);
|
|
TRY(json_test_and_skip(f, '['));
|
|
{
|
|
{
|
|
SET_STATE(f, f->cur - 1, "", 0);
|
|
while(json_cur(f) != ']') {
|
|
snprintf(buf, sizeof(buf), "[%d]", i);
|
|
i++;
|
|
current_path_len = json_append_to_path(f, buf, strlen(buf));
|
|
f->cur_name = f->path + strlen(f->path) - strlen(buf) + 1 /*opening brace*/;
|
|
f->cur_name_len = strlen(buf) - 2 /*braces*/;
|
|
TRY(json_parse_value(f));
|
|
json_truncate_path(f, current_path_len);
|
|
if(json_cur(f) == ',') f->cur++;
|
|
}
|
|
TRY(json_test_and_skip(f, ']'));
|
|
json_truncate_path(f, fstate.path_len);
|
|
CALL_BACK(f, JSON_TYPE_ARRAY_END, fstate.ptr, f->cur - fstate.ptr);
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
#endif /* JSON_ENABLE_ARRAY */
|
|
|
|
static int json_expect(struct frozen* f, const char* s, int len, enum json_token_type tok_type) {
|
|
int i, n = json_left(f);
|
|
SET_STATE(f, f->cur, "", 0);
|
|
for(i = 0; i < len; i++) {
|
|
if(i >= n) return JSON_STRING_INCOMPLETE;
|
|
if(f->cur[i] != s[i]) return JSON_STRING_INVALID;
|
|
}
|
|
f->cur += len;
|
|
json_truncate_path(f, fstate.path_len);
|
|
|
|
CALL_BACK(f, tok_type, fstate.ptr, f->cur - fstate.ptr);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* value = 'null' | 'true' | 'false' | number | string | array | object */
|
|
static int json_parse_value(struct frozen* f) {
|
|
int ch = json_cur(f);
|
|
|
|
switch(ch) {
|
|
case '"':
|
|
TRY(json_parse_string(f));
|
|
break;
|
|
case '{':
|
|
TRY(json_parse_object(f));
|
|
break;
|
|
#if JSON_ENABLE_ARRAY
|
|
case '[':
|
|
TRY(json_parse_array(f));
|
|
break;
|
|
#endif
|
|
case 'n':
|
|
TRY(json_expect(f, "null", 4, JSON_TYPE_NULL));
|
|
break;
|
|
case 't':
|
|
TRY(json_expect(f, "true", 4, JSON_TYPE_TRUE));
|
|
break;
|
|
case 'f':
|
|
TRY(json_expect(f, "false", 5, JSON_TYPE_FALSE));
|
|
break;
|
|
case '-':
|
|
case '0':
|
|
case '1':
|
|
case '2':
|
|
case '3':
|
|
case '4':
|
|
case '5':
|
|
case '6':
|
|
case '7':
|
|
case '8':
|
|
case '9':
|
|
TRY(json_parse_number(f));
|
|
break;
|
|
default:
|
|
return ch == END_OF_STRING ? JSON_STRING_INCOMPLETE : JSON_STRING_INVALID;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* key = identifier | string */
|
|
static int json_parse_key(struct frozen* f) {
|
|
int ch = json_cur(f);
|
|
if(json_isalpha(ch)) {
|
|
TRY(json_parse_identifier(f));
|
|
} else if(ch == '"') {
|
|
TRY(json_parse_string(f));
|
|
} else {
|
|
return ch == END_OF_STRING ? JSON_STRING_INCOMPLETE : JSON_STRING_INVALID;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/* pair = key ':' value */
|
|
static int json_parse_pair(struct frozen* f) {
|
|
int current_path_len;
|
|
const char* tok;
|
|
json_skip_whitespaces(f);
|
|
tok = f->cur;
|
|
TRY(json_parse_key(f));
|
|
{
|
|
f->cur_name = *tok == '"' ? tok + 1 : tok;
|
|
f->cur_name_len = *tok == '"' ? f->cur - tok - 2 : f->cur - tok;
|
|
current_path_len = json_append_to_path(f, f->cur_name, f->cur_name_len);
|
|
}
|
|
TRY(json_test_and_skip(f, ':'));
|
|
TRY(json_parse_value(f));
|
|
json_truncate_path(f, current_path_len);
|
|
return 0;
|
|
}
|
|
|
|
/* object = '{' pair { ',' pair } '}' */
|
|
static int json_parse_object(struct frozen* f) {
|
|
CALL_BACK(f, JSON_TYPE_OBJECT_START, NULL, 0);
|
|
TRY(json_test_and_skip(f, '{'));
|
|
{
|
|
SET_STATE(f, f->cur - 1, ".", 1);
|
|
while(json_cur(f) != '}') {
|
|
TRY(json_parse_pair(f));
|
|
if(json_cur(f) == ',') f->cur++;
|
|
}
|
|
TRY(json_test_and_skip(f, '}'));
|
|
json_truncate_path(f, fstate.path_len);
|
|
CALL_BACK(f, JSON_TYPE_OBJECT_END, fstate.ptr, f->cur - fstate.ptr);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int json_doit(struct frozen* f) {
|
|
if(f->cur == 0 || f->end < f->cur) return JSON_STRING_INVALID;
|
|
if(f->end == f->cur) return JSON_STRING_INCOMPLETE;
|
|
return json_parse_value(f);
|
|
}
|
|
|
|
int json_escape(struct json_out* out, const char* p, size_t len) WEAK;
|
|
int json_escape(struct json_out* out, const char* p, size_t len) {
|
|
size_t i, cl, n = 0;
|
|
const char* hex_digits = "0123456789abcdef";
|
|
const char* specials = "btnvfr";
|
|
|
|
for(i = 0; i < len; i++) {
|
|
unsigned char ch = ((unsigned char*)p)[i];
|
|
if(ch == '"' || ch == '\\') {
|
|
n += out->printer(out, "\\", 1);
|
|
n += out->printer(out, p + i, 1);
|
|
} else if(ch >= '\b' && ch <= '\r') {
|
|
n += out->printer(out, "\\", 1);
|
|
n += out->printer(out, &specials[ch - '\b'], 1);
|
|
} else if(isprint(ch)) {
|
|
n += out->printer(out, p + i, 1);
|
|
} else if((cl = json_get_utf8_char_len(ch)) == 1) {
|
|
n += out->printer(out, "\\u00", 4);
|
|
n += out->printer(out, &hex_digits[(ch >> 4) % 0xf], 1);
|
|
n += out->printer(out, &hex_digits[ch % 0xf], 1);
|
|
} else {
|
|
n += out->printer(out, p + i, cl);
|
|
i += cl - 1;
|
|
}
|
|
}
|
|
|
|
return n;
|
|
}
|
|
|
|
int json_printer_buf(struct json_out* out, const char* buf, size_t len) WEAK;
|
|
int json_printer_buf(struct json_out* out, const char* buf, size_t len) {
|
|
size_t avail = out->u.buf.size - out->u.buf.len;
|
|
size_t n = len < avail ? len : avail;
|
|
memcpy(out->u.buf.buf + out->u.buf.len, buf, n);
|
|
out->u.buf.len += n;
|
|
if(out->u.buf.size > 0) {
|
|
size_t idx = out->u.buf.len;
|
|
if(idx >= out->u.buf.size) idx = out->u.buf.size - 1;
|
|
out->u.buf.buf[idx] = '\0';
|
|
}
|
|
return len;
|
|
}
|
|
|
|
int json_printer_file(struct json_out* out, const char* buf, size_t len) WEAK;
|
|
int json_printer_file(struct json_out* out, const char* buf, size_t len) {
|
|
return fwrite(buf, 1, len, out->u.fp);
|
|
}
|
|
|
|
#if JSON_ENABLE_BASE64
|
|
static int b64idx(int c) {
|
|
if(c < 26) {
|
|
return c + 'A';
|
|
} else if(c < 52) {
|
|
return c - 26 + 'a';
|
|
} else if(c < 62) {
|
|
return c - 52 + '0';
|
|
} else {
|
|
return c == 62 ? '+' : '/';
|
|
}
|
|
}
|
|
|
|
static int b64rev(int c) {
|
|
if(c >= 'A' && c <= 'Z') {
|
|
return c - 'A';
|
|
} else if(c >= 'a' && c <= 'z') {
|
|
return c + 26 - 'a';
|
|
} else if(c >= '0' && c <= '9') {
|
|
return c + 52 - '0';
|
|
} else if(c == '+') {
|
|
return 62;
|
|
} else if(c == '/') {
|
|
return 63;
|
|
} else {
|
|
return 64;
|
|
}
|
|
}
|
|
|
|
static int b64enc(struct json_out* out, const unsigned char* p, int n) {
|
|
char buf[4];
|
|
int i, len = 0;
|
|
for(i = 0; i < n; i += 3) {
|
|
int a = p[i], b = i + 1 < n ? p[i + 1] : 0, c = i + 2 < n ? p[i + 2] : 0;
|
|
buf[0] = b64idx(a >> 2);
|
|
buf[1] = b64idx((a & 3) << 4 | (b >> 4));
|
|
buf[2] = b64idx((b & 15) << 2 | (c >> 6));
|
|
buf[3] = b64idx(c & 63);
|
|
if(i + 1 >= n) buf[2] = '=';
|
|
if(i + 2 >= n) buf[3] = '=';
|
|
len += out->printer(out, buf, sizeof(buf));
|
|
}
|
|
return len;
|
|
}
|
|
|
|
static int b64dec(const char* src, int n, char* dst) {
|
|
const char* end = src + n;
|
|
int len = 0;
|
|
while(src + 3 < end) {
|
|
int a = b64rev(src[0]), b = b64rev(src[1]), c = b64rev(src[2]), d = b64rev(src[3]);
|
|
dst[len++] = (a << 2) | (b >> 4);
|
|
if(src[2] != '=') {
|
|
dst[len++] = (b << 4) | (c >> 2);
|
|
if(src[3] != '=') {
|
|
dst[len++] = (c << 6) | d;
|
|
}
|
|
}
|
|
src += 4;
|
|
}
|
|
return len;
|
|
}
|
|
#endif /* JSON_ENABLE_BASE64 */
|
|
|
|
static unsigned char hexdec(const char* s) {
|
|
#define HEXTOI(x) (x >= '0' && x <= '9' ? x - '0' : x - 'W')
|
|
int a = tolower(*(const unsigned char*)s);
|
|
int b = tolower(*(const unsigned char*)(s + 1));
|
|
return (HEXTOI(a) << 4) | HEXTOI(b);
|
|
}
|
|
|
|
int json_vprintf(struct json_out* out, const char* fmt, va_list xap) WEAK;
|
|
int json_vprintf(struct json_out* out, const char* fmt, va_list xap) {
|
|
int len = 0;
|
|
const char *quote = "\"", *null = "null";
|
|
va_list ap;
|
|
va_copy(ap, xap);
|
|
|
|
while(*fmt != '\0') {
|
|
if(strchr(":, \r\n\t[]{}\"", *fmt) != NULL) {
|
|
len += out->printer(out, fmt, 1);
|
|
fmt++;
|
|
} else if(fmt[0] == '%') {
|
|
char buf[21];
|
|
size_t skip = 2;
|
|
|
|
if(fmt[1] == 'l' && fmt[2] == 'l' && (fmt[3] == 'd' || fmt[3] == 'u')) {
|
|
int64_t val = va_arg(ap, int64_t);
|
|
const char* fmt2 = fmt[3] == 'u' ? "%" UINT64_FMT : "%" INT64_FMT;
|
|
snprintf(buf, sizeof(buf), fmt2, val);
|
|
len += out->printer(out, buf, strlen(buf));
|
|
skip += 2;
|
|
} else if(fmt[1] == 'z' && fmt[2] == 'u') {
|
|
size_t val = va_arg(ap, size_t);
|
|
snprintf(buf, sizeof(buf), "%lu", (unsigned long)val);
|
|
len += out->printer(out, buf, strlen(buf));
|
|
skip += 1;
|
|
} else if(fmt[1] == 'M') {
|
|
json_printf_callback_t f = va_arg(ap, json_printf_callback_t);
|
|
len += f(out, &ap);
|
|
} else if(fmt[1] == 'B') {
|
|
int val = va_arg(ap, int);
|
|
const char* str = val ? "true" : "false";
|
|
len += out->printer(out, str, strlen(str));
|
|
} else if(fmt[1] == 'H') {
|
|
#if JSON_ENABLE_HEX
|
|
const char* hex = "0123456789abcdef";
|
|
int i, n = va_arg(ap, int);
|
|
const unsigned char* p = va_arg(ap, const unsigned char*);
|
|
len += out->printer(out, quote, 1);
|
|
for(i = 0; i < n; i++) {
|
|
len += out->printer(out, &hex[(p[i] >> 4) & 0xf], 1);
|
|
len += out->printer(out, &hex[p[i] & 0xf], 1);
|
|
}
|
|
len += out->printer(out, quote, 1);
|
|
#endif /* JSON_ENABLE_HEX */
|
|
} else if(fmt[1] == 'V') {
|
|
#if JSON_ENABLE_BASE64
|
|
const unsigned char* p = va_arg(ap, const unsigned char*);
|
|
int n = va_arg(ap, int);
|
|
len += out->printer(out, quote, 1);
|
|
len += b64enc(out, p, n);
|
|
len += out->printer(out, quote, 1);
|
|
#endif /* JSON_ENABLE_BASE64 */
|
|
} else if(fmt[1] == 'Q' || (fmt[1] == '.' && fmt[2] == '*' && fmt[3] == 'Q')) {
|
|
size_t l = 0;
|
|
const char* p;
|
|
|
|
if(fmt[1] == '.') {
|
|
l = (size_t)va_arg(ap, int);
|
|
skip += 2;
|
|
}
|
|
p = va_arg(ap, char*);
|
|
|
|
if(p == NULL) {
|
|
len += out->printer(out, null, 4);
|
|
} else {
|
|
if(fmt[1] == 'Q') {
|
|
l = strlen(p);
|
|
}
|
|
len += out->printer(out, quote, 1);
|
|
len += json_escape(out, p, l);
|
|
len += out->printer(out, quote, 1);
|
|
}
|
|
} else {
|
|
/*
|
|
* we delegate printing to the system printf.
|
|
* The goal here is to delegate all modifiers parsing to the system
|
|
* printf, as you can see below we still have to parse the format
|
|
* types.
|
|
*
|
|
* Currently, %s with strings longer than 20 chars will require
|
|
* double-buffering (an auxiliary buffer will be allocated from heap).
|
|
* TODO(dfrank): reimplement %s and %.*s in order to avoid that.
|
|
*/
|
|
|
|
const char* end_of_format_specifier = "sdfFeEgGlhuIcx.*-0123456789";
|
|
int n = strspn(fmt + 1, end_of_format_specifier);
|
|
char* pbuf = buf;
|
|
int need_len, size = sizeof(buf);
|
|
char fmt2[20];
|
|
va_list ap_copy;
|
|
strncpy(fmt2, fmt, n + 1 > (int)sizeof(fmt2) ? sizeof(fmt2) : (size_t)n + 1);
|
|
fmt2[n + 1] = '\0';
|
|
|
|
va_copy(ap_copy, ap);
|
|
need_len = vsnprintf(pbuf, size, fmt2, ap_copy);
|
|
va_end(ap_copy);
|
|
|
|
if(need_len < 0) {
|
|
/*
|
|
* Windows & eCos vsnprintf implementation return -1 on overflow
|
|
* instead of needed size.
|
|
*/
|
|
pbuf = NULL;
|
|
while(need_len < 0) {
|
|
free(pbuf);
|
|
size *= 2;
|
|
if((pbuf = (char*)malloc(size)) == NULL) break;
|
|
va_copy(ap_copy, ap);
|
|
need_len = vsnprintf(pbuf, size, fmt2, ap_copy);
|
|
va_end(ap_copy);
|
|
}
|
|
} else if(need_len >= (int)sizeof(buf)) {
|
|
/*
|
|
* resulting string doesn't fit into a stack-allocated buffer `buf`,
|
|
* so we need to allocate a new buffer from heap and use it
|
|
*/
|
|
if((pbuf = (char*)malloc(need_len + 1)) != NULL) {
|
|
va_copy(ap_copy, ap);
|
|
vsnprintf(pbuf, need_len + 1, fmt2, ap_copy);
|
|
va_end(ap_copy);
|
|
}
|
|
}
|
|
if(pbuf == NULL) {
|
|
buf[0] = '\0';
|
|
pbuf = buf;
|
|
}
|
|
|
|
/*
|
|
* however we need to parse the type ourselves in order to advance
|
|
* the va_list by the correct amount; there is no portable way to
|
|
* inherit the advancement made by vprintf.
|
|
* 32-bit (linux or windows) passes va_list by value.
|
|
*/
|
|
if((n + 1 == strlen("%" PRId64) && strcmp(fmt2, "%" PRId64) == 0) ||
|
|
(n + 1 == strlen("%" PRIu64) && strcmp(fmt2, "%" PRIu64) == 0)) {
|
|
(void)va_arg(ap, int64_t);
|
|
} else if(strcmp(fmt2, "%.*s") == 0) {
|
|
(void)va_arg(ap, int);
|
|
(void)va_arg(ap, char*);
|
|
} else {
|
|
switch(fmt2[n]) {
|
|
case 'u':
|
|
case 'd':
|
|
(void)va_arg(ap, int);
|
|
break;
|
|
case 'g':
|
|
case 'f':
|
|
(void)va_arg(ap, double);
|
|
break;
|
|
case 'p':
|
|
(void)va_arg(ap, void*);
|
|
break;
|
|
default:
|
|
/* many types are promoted to int */
|
|
(void)va_arg(ap, int);
|
|
}
|
|
}
|
|
|
|
len += out->printer(out, pbuf, strlen(pbuf));
|
|
skip = n + 1;
|
|
|
|
/* If buffer was allocated from heap, free it */
|
|
if(pbuf != buf) {
|
|
free(pbuf);
|
|
pbuf = NULL;
|
|
}
|
|
}
|
|
fmt += skip;
|
|
} else if(*fmt == '_' || json_isalpha(*fmt)) {
|
|
len += out->printer(out, quote, 1);
|
|
while(*fmt == '_' || json_isalpha(*fmt) || json_isdigit(*fmt)) {
|
|
len += out->printer(out, fmt, 1);
|
|
fmt++;
|
|
}
|
|
len += out->printer(out, quote, 1);
|
|
} else {
|
|
len += out->printer(out, fmt, 1);
|
|
fmt++;
|
|
}
|
|
}
|
|
va_end(ap);
|
|
|
|
return len;
|
|
}
|
|
|
|
int json_printf(struct json_out* out, const char* fmt, ...) WEAK;
|
|
int json_printf(struct json_out* out, const char* fmt, ...) {
|
|
int n;
|
|
va_list ap;
|
|
va_start(ap, fmt);
|
|
n = json_vprintf(out, fmt, ap);
|
|
va_end(ap);
|
|
return n;
|
|
}
|
|
|
|
int json_printf_array(struct json_out* out, va_list* ap) WEAK;
|
|
int json_printf_array(struct json_out* out, va_list* ap) {
|
|
int len = 0;
|
|
char* arr = va_arg(*ap, char*);
|
|
size_t i, arr_size = va_arg(*ap, size_t);
|
|
size_t elem_size = va_arg(*ap, size_t);
|
|
const char* fmt = va_arg(*ap, char*);
|
|
len += json_printf(out, "[", 1);
|
|
for(i = 0; arr != NULL && i < arr_size / elem_size; i++) {
|
|
union {
|
|
int64_t i;
|
|
double d;
|
|
} val;
|
|
memcpy(&val, arr + i * elem_size, elem_size > sizeof(val) ? sizeof(val) : elem_size);
|
|
if(i > 0) len += json_printf(out, ", ");
|
|
if(strpbrk(fmt, "efg") != NULL) {
|
|
len += json_printf(out, fmt, val.d);
|
|
} else {
|
|
len += json_printf(out, fmt, val.i);
|
|
}
|
|
}
|
|
len += json_printf(out, "]", 1);
|
|
return len;
|
|
}
|
|
|
|
#ifdef _WIN32
|
|
int cs_win_vsnprintf(char* str, size_t size, const char* format, va_list ap) WEAK;
|
|
int cs_win_vsnprintf(char* str, size_t size, const char* format, va_list ap) {
|
|
int res = _vsnprintf(str, size, format, ap);
|
|
va_end(ap);
|
|
if(res >= size) {
|
|
str[size - 1] = '\0';
|
|
}
|
|
return res;
|
|
}
|
|
|
|
int cs_win_snprintf(char* str, size_t size, const char* format, ...) WEAK;
|
|
int cs_win_snprintf(char* str, size_t size, const char* format, ...) {
|
|
int res;
|
|
va_list ap;
|
|
va_start(ap, format);
|
|
res = vsnprintf(str, size, format, ap);
|
|
va_end(ap);
|
|
return res;
|
|
}
|
|
#endif /* _WIN32 */
|
|
|
|
int json_walk(
|
|
const char* json_string,
|
|
int json_string_length,
|
|
json_walk_callback_t callback,
|
|
void* callback_data) WEAK;
|
|
int json_walk(
|
|
const char* json_string,
|
|
int json_string_length,
|
|
json_walk_callback_t callback,
|
|
void* callback_data) {
|
|
struct frozen frozen;
|
|
|
|
memset(&frozen, 0, sizeof(frozen));
|
|
frozen.end = json_string + json_string_length;
|
|
frozen.cur = json_string;
|
|
frozen.callback_data = callback_data;
|
|
frozen.callback = callback;
|
|
|
|
TRY(json_doit(&frozen));
|
|
|
|
return frozen.cur - json_string;
|
|
}
|
|
|
|
struct scan_array_info {
|
|
int found;
|
|
char path[JSON_MAX_PATH_LEN];
|
|
struct json_token* token;
|
|
};
|
|
|
|
static void json_scanf_array_elem_cb(
|
|
void* callback_data,
|
|
const char* name,
|
|
size_t name_len,
|
|
const char* path,
|
|
const struct json_token* token) {
|
|
struct scan_array_info* info = (struct scan_array_info*)callback_data;
|
|
|
|
(void)name;
|
|
(void)name_len;
|
|
|
|
if(strcmp(path, info->path) == 0) {
|
|
*info->token = *token;
|
|
info->found = 1;
|
|
}
|
|
}
|
|
|
|
int json_scanf_array_elem(
|
|
const char* s,
|
|
int len,
|
|
const char* path,
|
|
int idx,
|
|
struct json_token* token) WEAK;
|
|
int json_scanf_array_elem(
|
|
const char* s,
|
|
int len,
|
|
const char* path,
|
|
int idx,
|
|
struct json_token* token) {
|
|
struct scan_array_info info;
|
|
info.token = token;
|
|
info.found = 0;
|
|
memset(token, 0, sizeof(*token));
|
|
snprintf(info.path, sizeof(info.path), "%s[%d]", path, idx);
|
|
json_walk(s, len, json_scanf_array_elem_cb, &info);
|
|
return info.found ? token->len : -1;
|
|
}
|
|
|
|
struct json_scanf_info {
|
|
int num_conversions;
|
|
char* path;
|
|
const char* fmt;
|
|
void* target;
|
|
void* user_data;
|
|
int type;
|
|
};
|
|
|
|
int json_unescape(const char* src, int slen, char* dst, int dlen) WEAK;
|
|
int json_unescape(const char* src, int slen, char* dst, int dlen) {
|
|
char *send = (char*)src + slen, *dend = dst + dlen, *orig_dst = dst, *p;
|
|
const char *esc1 = "\"\\/bfnrt", *esc2 = "\"\\/\b\f\n\r\t";
|
|
|
|
while(src < send) {
|
|
if(*src == '\\') {
|
|
if(++src >= send) return JSON_STRING_INCOMPLETE;
|
|
if(*src == 'u') {
|
|
if(send - src < 5) return JSON_STRING_INCOMPLETE;
|
|
/* Here we go: this is a \u.... escape. Process simple one-byte chars */
|
|
if(src[1] == '0' && src[2] == '0') {
|
|
/* This is \u00xx character from the ASCII range */
|
|
if(dst < dend) *dst = hexdec(src + 3);
|
|
src += 4;
|
|
} else {
|
|
/* Complex \uXX XX escapes drag utf8 lib... Do it at some stage */
|
|
return JSON_STRING_INVALID;
|
|
}
|
|
} else if((p = (char*)strchr(esc1, *src)) != NULL) {
|
|
if(dst < dend) *dst = esc2[p - esc1];
|
|
} else {
|
|
return JSON_STRING_INVALID;
|
|
}
|
|
} else {
|
|
if(dst < dend) *dst = *src;
|
|
}
|
|
dst++;
|
|
src++;
|
|
}
|
|
|
|
return dst - orig_dst;
|
|
}
|
|
|
|
static void json_scanf_cb(
|
|
void* callback_data,
|
|
const char* name,
|
|
size_t name_len,
|
|
const char* path,
|
|
const struct json_token* token) {
|
|
struct json_scanf_info* info = (struct json_scanf_info*)callback_data;
|
|
char buf[32]; /* Must be enough to hold numbers */
|
|
|
|
(void)name;
|
|
(void)name_len;
|
|
|
|
if(token->ptr == NULL) {
|
|
/*
|
|
* We're not interested here in the events for which we have no value;
|
|
* namely, JSON_TYPE_OBJECT_START and JSON_TYPE_ARRAY_START
|
|
*/
|
|
return;
|
|
}
|
|
|
|
if(strcmp(path, info->path) != 0) {
|
|
/* It's not the path we're looking for, so, just ignore this callback */
|
|
return;
|
|
}
|
|
|
|
switch(info->type) {
|
|
case 'B':
|
|
info->num_conversions++;
|
|
switch(sizeof(bool)) {
|
|
case sizeof(char):
|
|
*(char*)info->target = (token->type == JSON_TYPE_TRUE ? 1 : 0);
|
|
break;
|
|
case sizeof(int):
|
|
*(int*)info->target = (token->type == JSON_TYPE_TRUE ? 1 : 0);
|
|
break;
|
|
default:
|
|
/* should never be here */
|
|
abort();
|
|
}
|
|
break;
|
|
case 'M': {
|
|
union {
|
|
void* p;
|
|
json_scanner_t f;
|
|
} u = {info->target};
|
|
info->num_conversions++;
|
|
u.f(token->ptr, token->len, info->user_data);
|
|
break;
|
|
}
|
|
case 'Q': {
|
|
char** dst = (char**)info->target;
|
|
if(token->type == JSON_TYPE_NULL) {
|
|
*dst = NULL;
|
|
} else {
|
|
int unescaped_len = json_unescape(token->ptr, token->len, NULL, 0);
|
|
if(unescaped_len >= 0 && (*dst = (char*)malloc(unescaped_len + 1)) != NULL) {
|
|
info->num_conversions++;
|
|
if(json_unescape(token->ptr, token->len, *dst, unescaped_len) == unescaped_len) {
|
|
(*dst)[unescaped_len] = '\0';
|
|
} else {
|
|
free(*dst);
|
|
*dst = NULL;
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
case 'H': {
|
|
#if JSON_ENABLE_HEX
|
|
char** dst = (char**)info->user_data;
|
|
int i, len = token->len / 2;
|
|
*(int*)info->target = len;
|
|
if((*dst = (char*)malloc(len + 1)) != NULL) {
|
|
for(i = 0; i < len; i++) {
|
|
(*dst)[i] = hexdec(token->ptr + 2 * i);
|
|
}
|
|
(*dst)[len] = '\0';
|
|
info->num_conversions++;
|
|
}
|
|
#endif /* JSON_ENABLE_HEX */
|
|
break;
|
|
}
|
|
case 'V': {
|
|
#if JSON_ENABLE_BASE64
|
|
char** dst = (char**)info->target;
|
|
int len = token->len * 4 / 3 + 2;
|
|
if((*dst = (char*)malloc(len + 1)) != NULL) {
|
|
int n = b64dec(token->ptr, token->len, *dst);
|
|
(*dst)[n] = '\0';
|
|
*(int*)info->user_data = n;
|
|
info->num_conversions++;
|
|
}
|
|
#endif /* JSON_ENABLE_BASE64 */
|
|
break;
|
|
}
|
|
case 'T':
|
|
info->num_conversions++;
|
|
*(struct json_token*)info->target = *token;
|
|
break;
|
|
default:
|
|
if(token->len >= (int)sizeof(buf)) break;
|
|
/* Before converting, copy into tmp buffer in order to 0-terminate it */
|
|
memcpy(buf, token->ptr, token->len);
|
|
buf[token->len] = '\0';
|
|
/* NB: Use of base 0 for %d, %ld, %u and %lu is intentional. */
|
|
if(info->fmt[1] == 'd' || (info->fmt[1] == 'l' && info->fmt[2] == 'd') ||
|
|
info->fmt[1] == 'i') {
|
|
char* endptr = NULL;
|
|
long r = strtol(buf, &endptr, 0 /* base */);
|
|
if(*endptr == '\0') {
|
|
if(info->fmt[1] == 'l') {
|
|
*((long*)info->target) = r;
|
|
} else {
|
|
*((int*)info->target) = (int)r;
|
|
}
|
|
info->num_conversions++;
|
|
}
|
|
} else if(info->fmt[1] == 'u' || (info->fmt[1] == 'l' && info->fmt[2] == 'u')) {
|
|
char* endptr = NULL;
|
|
unsigned long r = strtoul(buf, &endptr, 0 /* base */);
|
|
if(*endptr == '\0') {
|
|
if(info->fmt[1] == 'l') {
|
|
*((unsigned long*)info->target) = r;
|
|
} else {
|
|
*((unsigned int*)info->target) = (unsigned int)r;
|
|
}
|
|
info->num_conversions++;
|
|
}
|
|
} else {
|
|
#if !JSON_MINIMAL
|
|
info->num_conversions += sscanf(buf, info->fmt, info->target);
|
|
#endif
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
int json_vscanf(const char* s, int len, const char* fmt, va_list ap) WEAK;
|
|
int json_vscanf(const char* s, int len, const char* fmt, va_list ap) {
|
|
char path[JSON_MAX_PATH_LEN] = "", fmtbuf[20];
|
|
int i = 0;
|
|
char* p = NULL;
|
|
struct json_scanf_info info = {0, path, fmtbuf, NULL, NULL, 0};
|
|
|
|
while(fmt[i] != '\0') {
|
|
if(fmt[i] == '{') {
|
|
strcat(path, ".");
|
|
i++;
|
|
} else if(fmt[i] == '}') {
|
|
if((p = strrchr(path, '.')) != NULL) *p = '\0';
|
|
i++;
|
|
} else if(fmt[i] == '%') {
|
|
info.target = va_arg(ap, void*);
|
|
info.type = fmt[i + 1];
|
|
switch(fmt[i + 1]) {
|
|
case 'M':
|
|
case 'V':
|
|
case 'H':
|
|
info.user_data = va_arg(ap, void*);
|
|
/* FALLTHROUGH */
|
|
case 'B':
|
|
case 'Q':
|
|
case 'T':
|
|
i += 2;
|
|
break;
|
|
default: {
|
|
const char* delims = ", \t\r\n]}";
|
|
int conv_len = strcspn(fmt + i + 1, delims) + 1;
|
|
memcpy(fmtbuf, fmt + i, conv_len);
|
|
fmtbuf[conv_len] = '\0';
|
|
i += conv_len;
|
|
i += strspn(fmt + i, delims);
|
|
break;
|
|
}
|
|
}
|
|
json_walk(s, len, json_scanf_cb, &info);
|
|
} else if(json_isalpha(fmt[i]) || json_get_utf8_char_len(fmt[i]) > 1) {
|
|
char* pe;
|
|
const char* delims = ": \r\n\t";
|
|
int key_len = strcspn(&fmt[i], delims);
|
|
if((p = strrchr(path, '.')) != NULL) p[1] = '\0';
|
|
pe = path + strlen(path);
|
|
memcpy(pe, fmt + i, key_len);
|
|
pe[key_len] = '\0';
|
|
i += key_len + strspn(fmt + i + key_len, delims);
|
|
} else {
|
|
i++;
|
|
}
|
|
}
|
|
return info.num_conversions;
|
|
}
|
|
|
|
int json_scanf(const char* str, int len, const char* fmt, ...) WEAK;
|
|
int json_scanf(const char* str, int len, const char* fmt, ...) {
|
|
int result;
|
|
va_list ap;
|
|
va_start(ap, fmt);
|
|
result = json_vscanf(str, len, fmt, ap);
|
|
va_end(ap);
|
|
return result;
|
|
}
|
|
|
|
int json_vfprintf(const char* file_name, const char* fmt, va_list ap) WEAK;
|
|
int json_vfprintf(const char* file_name, const char* fmt, va_list ap) {
|
|
int res = -1;
|
|
FILE* fp = fopen(file_name, "wb");
|
|
if(fp != NULL) {
|
|
struct json_out out = JSON_OUT_FILE(fp);
|
|
res = json_vprintf(&out, fmt, ap);
|
|
fputc('\n', fp);
|
|
fclose(fp);
|
|
}
|
|
return res;
|
|
}
|
|
|
|
int json_fprintf(const char* file_name, const char* fmt, ...) WEAK;
|
|
int json_fprintf(const char* file_name, const char* fmt, ...) {
|
|
int result;
|
|
va_list ap;
|
|
va_start(ap, fmt);
|
|
result = json_vfprintf(file_name, fmt, ap);
|
|
va_end(ap);
|
|
return result;
|
|
}
|
|
|
|
char* json_fread(const char* path) WEAK;
|
|
char* json_fread(const char* path) {
|
|
FILE* fp;
|
|
char* data = NULL;
|
|
if((fp = fopen(path, "rb")) == NULL) {
|
|
} else if(fseek(fp, 0, SEEK_END) != 0) {
|
|
fclose(fp);
|
|
} else {
|
|
long size = ftell(fp);
|
|
if(size > 0 && (data = (char*)malloc(size + 1)) != NULL) {
|
|
fseek(fp, 0, SEEK_SET); /* Some platforms might not have rewind(), Oo */
|
|
if(fread(data, 1, size, fp) != (size_t)size) {
|
|
free(data);
|
|
data = NULL;
|
|
} else {
|
|
data[size] = '\0';
|
|
}
|
|
}
|
|
fclose(fp);
|
|
}
|
|
return data;
|
|
}
|
|
|
|
struct json_setf_data {
|
|
const char* json_path;
|
|
const char* base; /* Pointer to the source JSON string */
|
|
int matched; /* Matched part of json_path */
|
|
int pos; /* Offset of the mutated value begin */
|
|
int end; /* Offset of the mutated value end */
|
|
int prev; /* Offset of the previous token end */
|
|
};
|
|
|
|
static int get_matched_prefix_len(const char* s1, const char* s2) {
|
|
int i = 0;
|
|
while(s1[i] && s2[i] && s1[i] == s2[i]) i++;
|
|
return i;
|
|
}
|
|
|
|
static void json_vsetf_cb(
|
|
void* userdata,
|
|
const char* name,
|
|
size_t name_len,
|
|
const char* path,
|
|
const struct json_token* t) {
|
|
struct json_setf_data* data = (struct json_setf_data*)userdata;
|
|
int off, len = get_matched_prefix_len(path, data->json_path);
|
|
if(t->ptr == NULL) return;
|
|
off = t->ptr - data->base;
|
|
if(len > data->matched) data->matched = len;
|
|
|
|
/*
|
|
* If there is no exact path match, set the mutation position to tbe end
|
|
* of the object or array
|
|
*/
|
|
if(len < data->matched && data->pos == 0 &&
|
|
(t->type == JSON_TYPE_OBJECT_END || t->type == JSON_TYPE_ARRAY_END)) {
|
|
data->pos = data->end = data->prev;
|
|
}
|
|
|
|
/* Exact path match. Set mutation position to the value of this token */
|
|
if(strcmp(path, data->json_path) == 0 && t->type != JSON_TYPE_OBJECT_START &&
|
|
t->type != JSON_TYPE_ARRAY_START) {
|
|
data->pos = off;
|
|
data->end = off + t->len;
|
|
}
|
|
|
|
/*
|
|
* For deletion, we need to know where the previous value ends, because
|
|
* we don't know where matched value key starts.
|
|
* When the mutation position is not yet set, remember each value end.
|
|
* When the mutation position is already set, but it is at the beginning
|
|
* of the object/array, we catch the end of the object/array and see
|
|
* whether the object/array start is closer then previously stored prev.
|
|
*/
|
|
if(data->pos == 0) {
|
|
data->prev = off + t->len; /* pos is not yet set */
|
|
} else if((t->ptr[0] == '[' || t->ptr[0] == '{') && off + 1 < data->pos && off + 1 > data->prev) {
|
|
data->prev = off + 1;
|
|
}
|
|
(void)name;
|
|
(void)name_len;
|
|
}
|
|
|
|
int json_vsetf(
|
|
const char* s,
|
|
int len,
|
|
struct json_out* out,
|
|
const char* json_path,
|
|
const char* json_fmt,
|
|
va_list ap) WEAK;
|
|
int json_vsetf(
|
|
const char* s,
|
|
int len,
|
|
struct json_out* out,
|
|
const char* json_path,
|
|
const char* json_fmt,
|
|
va_list ap) {
|
|
struct json_setf_data data;
|
|
memset(&data, 0, sizeof(data));
|
|
data.json_path = json_path;
|
|
data.base = s;
|
|
data.end = len;
|
|
json_walk(s, len, json_vsetf_cb, &data);
|
|
if(json_fmt == NULL) {
|
|
/* Deletion codepath */
|
|
json_printf(out, "%.*s", data.prev, s);
|
|
/* Trim comma after the value that begins at object/array start */
|
|
if(s[data.prev - 1] == '{' || s[data.prev - 1] == '[') {
|
|
int i = data.end;
|
|
while(i < len && json_isspace(s[i])) i++;
|
|
if(s[i] == ',') data.end = i + 1; /* Point after comma */
|
|
}
|
|
json_printf(out, "%.*s", len - data.end, s + data.end);
|
|
} else {
|
|
/* Modification codepath */
|
|
int n, off = data.matched, depth = 0;
|
|
|
|
/* Print the unchanged beginning */
|
|
json_printf(out, "%.*s", data.pos, s);
|
|
|
|
/* Add missing keys */
|
|
while((n = strcspn(&json_path[off], ".[")) > 0) {
|
|
if(s[data.prev - 1] != '{' && s[data.prev - 1] != '[' && depth == 0) {
|
|
json_printf(out, ",");
|
|
}
|
|
if(off > 0 && json_path[off - 1] != '.') break;
|
|
json_printf(out, "%.*Q:", n, json_path + off);
|
|
off += n;
|
|
if(json_path[off] != '\0') {
|
|
json_printf(out, "%c", json_path[off] == '.' ? '{' : '[');
|
|
depth++;
|
|
off++;
|
|
}
|
|
}
|
|
/* Print the new value */
|
|
json_vprintf(out, json_fmt, ap);
|
|
|
|
/* Close brackets/braces of the added missing keys */
|
|
for(; off > data.matched; off--) {
|
|
int ch = json_path[off];
|
|
const char* p = ch == '.' ? "}" : ch == '[' ? "]" : "";
|
|
json_printf(out, "%s", p);
|
|
}
|
|
|
|
/* Print the rest of the unchanged string */
|
|
json_printf(out, "%.*s", len - data.end, s + data.end);
|
|
}
|
|
return data.end > data.pos ? 1 : 0;
|
|
}
|
|
|
|
int json_setf(
|
|
const char* s,
|
|
int len,
|
|
struct json_out* out,
|
|
const char* json_path,
|
|
const char* json_fmt,
|
|
...) WEAK;
|
|
int json_setf(
|
|
const char* s,
|
|
int len,
|
|
struct json_out* out,
|
|
const char* json_path,
|
|
const char* json_fmt,
|
|
...) {
|
|
int result;
|
|
va_list ap;
|
|
va_start(ap, json_fmt);
|
|
result = json_vsetf(s, len, out, json_path, json_fmt, ap);
|
|
va_end(ap);
|
|
return result;
|
|
}
|
|
|
|
struct prettify_data {
|
|
struct json_out* out;
|
|
int level;
|
|
int last_token;
|
|
};
|
|
|
|
static void indent(struct json_out* out, int level) {
|
|
while(level-- > 0) out->printer(out, " ", 2);
|
|
}
|
|
|
|
static void print_key(struct prettify_data* pd, const char* path, const char* name, int name_len) {
|
|
if(pd->last_token != JSON_TYPE_INVALID && pd->last_token != JSON_TYPE_ARRAY_START &&
|
|
pd->last_token != JSON_TYPE_OBJECT_START) {
|
|
pd->out->printer(pd->out, ",", 1);
|
|
}
|
|
if(path[0] != '\0') pd->out->printer(pd->out, "\n", 1);
|
|
indent(pd->out, pd->level);
|
|
if(path[0] != '\0' && path[strlen(path) - 1] != ']') {
|
|
pd->out->printer(pd->out, "\"", 1);
|
|
pd->out->printer(pd->out, name, (int)name_len);
|
|
pd->out->printer(pd->out, "\"", 1);
|
|
pd->out->printer(pd->out, ": ", 2);
|
|
}
|
|
}
|
|
|
|
static void prettify_cb(
|
|
void* userdata,
|
|
const char* name,
|
|
size_t name_len,
|
|
const char* path,
|
|
const struct json_token* t) {
|
|
struct prettify_data* pd = (struct prettify_data*)userdata;
|
|
switch(t->type) {
|
|
case JSON_TYPE_OBJECT_START:
|
|
case JSON_TYPE_ARRAY_START:
|
|
print_key(pd, path, name, name_len);
|
|
pd->out->printer(pd->out, t->type == JSON_TYPE_ARRAY_START ? "[" : "{", 1);
|
|
pd->level++;
|
|
break;
|
|
case JSON_TYPE_OBJECT_END:
|
|
case JSON_TYPE_ARRAY_END:
|
|
pd->level--;
|
|
if(pd->last_token != JSON_TYPE_INVALID && pd->last_token != JSON_TYPE_ARRAY_START &&
|
|
pd->last_token != JSON_TYPE_OBJECT_START) {
|
|
pd->out->printer(pd->out, "\n", 1);
|
|
indent(pd->out, pd->level);
|
|
}
|
|
pd->out->printer(pd->out, t->type == JSON_TYPE_ARRAY_END ? "]" : "}", 1);
|
|
break;
|
|
case JSON_TYPE_NUMBER:
|
|
case JSON_TYPE_NULL:
|
|
case JSON_TYPE_TRUE:
|
|
case JSON_TYPE_FALSE:
|
|
case JSON_TYPE_STRING:
|
|
print_key(pd, path, name, name_len);
|
|
if(t->type == JSON_TYPE_STRING) pd->out->printer(pd->out, "\"", 1);
|
|
pd->out->printer(pd->out, t->ptr, t->len);
|
|
if(t->type == JSON_TYPE_STRING) pd->out->printer(pd->out, "\"", 1);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
pd->last_token = t->type;
|
|
}
|
|
|
|
int json_prettify(const char* s, int len, struct json_out* out) WEAK;
|
|
int json_prettify(const char* s, int len, struct json_out* out) {
|
|
struct prettify_data pd = {out, 0, JSON_TYPE_INVALID};
|
|
return json_walk(s, len, prettify_cb, &pd);
|
|
}
|
|
|
|
int json_prettify_file(const char* file_name) WEAK;
|
|
int json_prettify_file(const char* file_name) {
|
|
int res = -1;
|
|
char* s = json_fread(file_name);
|
|
FILE* fp;
|
|
if(s != NULL && (fp = fopen(file_name, "wb")) != NULL) {
|
|
struct json_out out = JSON_OUT_FILE(fp);
|
|
res = json_prettify(s, strlen(s), &out);
|
|
if(res < 0) {
|
|
/* On error, restore the old content */
|
|
fclose(fp);
|
|
fp = fopen(file_name, "wb");
|
|
fseek(fp, 0, SEEK_SET);
|
|
fwrite(s, 1, strlen(s), fp);
|
|
} else {
|
|
fputc('\n', fp);
|
|
}
|
|
fclose(fp);
|
|
}
|
|
free(s);
|
|
return res;
|
|
}
|
|
|
|
struct next_data {
|
|
void* handle; // Passed handle. Changed if a next entry is found
|
|
const char* path; // Path to the iterated object/array
|
|
int path_len; // Path length - optimisation
|
|
int found; // Non-0 if found the next entry
|
|
struct json_token* key; // Object's key
|
|
struct json_token* val; // Object's value
|
|
int* idx; // Array index
|
|
};
|
|
|
|
static void next_set_key(struct next_data* d, const char* name, int name_len, int is_array) {
|
|
if(is_array) {
|
|
/* Array. Set index and reset key */
|
|
if(d->key != NULL) {
|
|
d->key->len = 0;
|
|
d->key->ptr = NULL;
|
|
}
|
|
if(d->idx != NULL) *d->idx = atoi(name);
|
|
} else {
|
|
/* Object. Set key and make index -1 */
|
|
if(d->key != NULL) {
|
|
d->key->ptr = name;
|
|
d->key->len = name_len;
|
|
}
|
|
if(d->idx != NULL) *d->idx = -1;
|
|
}
|
|
}
|
|
|
|
static void json_next_cb(
|
|
void* userdata,
|
|
const char* name,
|
|
size_t name_len,
|
|
const char* path,
|
|
const struct json_token* t) {
|
|
struct next_data* d = (struct next_data*)userdata;
|
|
const char* p = path + d->path_len;
|
|
if(d->found) return;
|
|
if(d->path_len >= (int)strlen(path)) return;
|
|
if(strncmp(d->path, path, d->path_len) != 0) return;
|
|
if(strchr(p + 1, '.') != NULL) return; /* More nested objects - skip */
|
|
if(strchr(p + 1, '[') != NULL) return; /* Ditto for arrays */
|
|
// {OBJECT,ARRAY}_END types do not pass name, _START does. Save key.
|
|
if(t->type == JSON_TYPE_OBJECT_START || t->type == JSON_TYPE_ARRAY_START) {
|
|
next_set_key(d, name, name_len, p[0] == '[');
|
|
} else if(d->handle == NULL || d->handle < (void*)t->ptr) {
|
|
if(t->type != JSON_TYPE_OBJECT_END && t->type != JSON_TYPE_ARRAY_END) {
|
|
next_set_key(d, name, name_len, p[0] == '[');
|
|
}
|
|
if(d->val != NULL) *d->val = *t;
|
|
d->handle = (void*)t->ptr;
|
|
d->found = 1;
|
|
}
|
|
}
|
|
|
|
static void* json_next(
|
|
const char* s,
|
|
int len,
|
|
void* handle,
|
|
const char* path,
|
|
struct json_token* key,
|
|
struct json_token* val,
|
|
int* i) {
|
|
struct json_token tmpval, *v = val == NULL ? &tmpval : val;
|
|
struct json_token tmpkey, *k = key == NULL ? &tmpkey : key;
|
|
int tmpidx, *pidx = i == NULL ? &tmpidx : i;
|
|
struct next_data data = {handle, path, (int)strlen(path), 0, k, v, pidx};
|
|
json_walk(s, len, json_next_cb, &data);
|
|
return data.found ? data.handle : NULL;
|
|
}
|
|
|
|
void* json_next_key(
|
|
const char* s,
|
|
int len,
|
|
void* handle,
|
|
const char* path,
|
|
struct json_token* key,
|
|
struct json_token* val) WEAK;
|
|
void* json_next_key(
|
|
const char* s,
|
|
int len,
|
|
void* handle,
|
|
const char* path,
|
|
struct json_token* key,
|
|
struct json_token* val) {
|
|
return json_next(s, len, handle, path, key, val, NULL);
|
|
}
|
|
|
|
void* json_next_elem(
|
|
const char* s,
|
|
int len,
|
|
void* handle,
|
|
const char* path,
|
|
int* idx,
|
|
struct json_token* val) WEAK;
|
|
void* json_next_elem(
|
|
const char* s,
|
|
int len,
|
|
void* handle,
|
|
const char* path,
|
|
int* idx,
|
|
struct json_token* val) {
|
|
return json_next(s, len, handle, path, NULL, val, idx);
|
|
}
|
|
|
|
static int json_sprinter(struct json_out* out, const char* str, size_t len) {
|
|
size_t old_len = out->u.buf.buf == NULL ? 0 : strlen(out->u.buf.buf);
|
|
size_t new_len = len + old_len;
|
|
char* p = (char*)realloc(out->u.buf.buf, new_len + 1);
|
|
if(p != NULL) {
|
|
memcpy(p + old_len, str, len);
|
|
p[new_len] = '\0';
|
|
out->u.buf.buf = p;
|
|
}
|
|
return len;
|
|
}
|
|
|
|
char* json_vasprintf(const char* fmt, va_list ap) WEAK;
|
|
char* json_vasprintf(const char* fmt, va_list ap) {
|
|
struct json_out out;
|
|
memset(&out, 0, sizeof(out));
|
|
out.printer = json_sprinter;
|
|
json_vprintf(&out, fmt, ap);
|
|
return out.u.buf.buf;
|
|
}
|
|
|
|
char* json_asprintf(const char* fmt, ...) WEAK;
|
|
char* json_asprintf(const char* fmt, ...) {
|
|
char* result = NULL;
|
|
va_list ap;
|
|
va_start(ap, fmt);
|
|
result = json_vasprintf(fmt, ap);
|
|
va_end(ap);
|
|
return result;
|
|
}
|