gs

Unnamed repository; edit this file 'description' to name the repository.
git clone git://edryd.org/gs
Log | Files | Refs | LICENSE

commit 8be30f8913b1bc963a7a2703eb0fa940849b0912
parent c50effd2e8f2e838951b812a4719d97e512e70e5
Author: Ed van Bruggen <edvb54@gmail.com>
Date:   Mon, 31 Jul 2017 23:42:45 -0700

Create extern/ for arg.h and frozen

Diffstat:
Makefile | 4++--
arg.h | 63---------------------------------------------------------------
config.mk | 6+++---
extern/arg.h | 63+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
extern/frozen.c | 1001+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
extern/frozen.h | 220+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
frozen.c | 1001-------------------------------------------------------------------------------
frozen.h | 220-------------------------------------------------------------------------------
gs.c | 4++--
9 files changed, 1291 insertions(+), 1291 deletions(-)

diff --git a/Makefile b/Makefile @@ -4,7 +4,7 @@ include config.mk EXE = gs -SRC = $(wildcard *.c) +SRC = $(wildcard *.c) $(wildcard */*.c) OBJ = $(SRC:.c=.o) all: options $(EXE) @@ -22,7 +22,7 @@ options: @echo $(CC) $< @$(CC) -c -o $@ $< $(CFLAGS) -${OBJ}: config.h config.mk +$(OBJ): config.h config.mk config.h: @echo creating $@ from config.def.h diff --git a/arg.h b/arg.h @@ -1,63 +0,0 @@ -/* - * Copy me if you can. - * by 20h - */ - -#ifndef ARG_H__ -#define ARG_H__ - -extern char *argv0; - -/* use main(int argc, char *argv[]) */ -#define ARGBEGIN for (argv0 = basename(*argv), argv++, argc--;\ - argv[0] && argv[0][0] == '-'\ - && argv[0][1];\ - argc--, argv++) {\ - char argc_;\ - char **argv_;\ - int brk_;\ - if (argv[0][1] == '-' && argv[0][2] == '\0') {\ - argv++;\ - argc--;\ - break;\ - }\ - for (brk_ = 0, argv[0]++, argv_ = argv;\ - argv[0][0] && !brk_;\ - argv[0]++) {\ - if (argv_ != argv)\ - break;\ - argc_ = argv[0][0];\ - switch (argc_) - -/* Handles -NUM syntax */ -#define ARGNUM case '0':\ - case '1':\ - case '2':\ - case '3':\ - case '4':\ - case '5':\ - case '6':\ - case '7':\ - case '8':\ - case '9' - -#define ARGEND }\ - } - -#define ARGC() argc_ - -#define ARGNUMF() (brk_ = 1, atoi(argv[0])) - -#define EARGF(x) ((argv[0][1] == '\0' && argv[1] == NULL)?\ - ((x), abort(), (char *)0) :\ - (brk_ = 1, (argv[0][1] != '\0')?\ - (&argv[0][1]) :\ - (argc--, argv++, argv[0]))) - -#define ARGF() ((argv[0][1] == '\0' && argv[1] == NULL)?\ - (char *)0 :\ - (brk_ = 1, (argv[0][1] != '\0')?\ - (&argv[0][1]) :\ - (argc--, argv++, argv[0]))) - -#endif diff --git a/config.mk b/config.mk @@ -3,7 +3,7 @@ VERSION = 0.0.0 # paths PREFIX = /usr/local -MANPREFIX = ${PREFIX}/share/man +MANPREFIX = $(PREFIX)/share/man # includes and libraries INCS = -Iinclude @@ -11,8 +11,8 @@ LIBS = -lcurl # flags CPPFLAGS = -DVERSION=\"$(VERSION)\" -D_DEFAULT_SOURCE -D_BSD_SOURCE -D_XOPEN_SOURCE=600 -CFLAGS = -g -std=c99 -pedantic -Wall ${INCS} ${CPPFLAGS} -LDFLAGS = -g ${LIBS} +CFLAGS = -g -std=c99 -pedantic -Wall $(INCS) $(CPPFLAGS) +LDFLAGS = -g $(LIBS) # compiler and linker CC = gcc diff --git a/extern/arg.h b/extern/arg.h @@ -0,0 +1,63 @@ +/* + * Copy me if you can. + * by 20h + */ + +#ifndef ARG_H__ +#define ARG_H__ + +extern char *argv0; + +/* use main(int argc, char *argv[]) */ +#define ARGBEGIN for (argv0 = basename(*argv), argv++, argc--;\ + argv[0] && argv[0][0] == '-'\ + && argv[0][1];\ + argc--, argv++) {\ + char argc_;\ + char **argv_;\ + int brk_;\ + if (argv[0][1] == '-' && argv[0][2] == '\0') {\ + argv++;\ + argc--;\ + break;\ + }\ + for (brk_ = 0, argv[0]++, argv_ = argv;\ + argv[0][0] && !brk_;\ + argv[0]++) {\ + if (argv_ != argv)\ + break;\ + argc_ = argv[0][0];\ + switch (argc_) + +/* Handles -NUM syntax */ +#define ARGNUM case '0':\ + case '1':\ + case '2':\ + case '3':\ + case '4':\ + case '5':\ + case '6':\ + case '7':\ + case '8':\ + case '9' + +#define ARGEND }\ + } + +#define ARGC() argc_ + +#define ARGNUMF() (brk_ = 1, atoi(argv[0])) + +#define EARGF(x) ((argv[0][1] == '\0' && argv[1] == NULL)?\ + ((x), abort(), (char *)0) :\ + (brk_ = 1, (argv[0][1] != '\0')?\ + (&argv[0][1]) :\ + (argc--, argv++, argv[0]))) + +#define ARGF() ((argv[0][1] == '\0' && argv[1] == NULL)?\ + (char *)0 :\ + (brk_ = 1, (argv[0][1] != '\0')?\ + (&argv[0][1]) :\ + (argc--, argv++, argv[0]))) + +#endif diff --git a/extern/frozen.c b/extern/frozen.c @@ -0,0 +1,1001 @@ +/* + * Copyright (c) 2004-2013 Sergey Lyubka <valenok@gmail.com> + * Copyright (c) 2013 Cesanta Software Limited + * All rights reserved + * + * This library is dual-licensed: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. For the terms of this + * license, see <http: *www.gnu.org/licenses/>. + * + * You are free to use this library under the terms of the GNU General + * Public License, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * Alternatively, you can license this library under a commercial + * license, as set out in <http://cesanta.com/products.html>. + */ + +#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 +#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" +#if !defined(SIZE_T_FMT) +#if _MSC_VER >= 1310 +#define SIZE_T_FMT "Iu" +#else +#define SIZE_T_FMT "u" +#endif +#endif +#else /* _WIN32 */ +/* <inttypes.h> wants this for C++ */ +#ifndef __STDC_FORMAT_MACROS +#define __STDC_FORMAT_MACROS +#endif +#include <inttypes.h> +#if !defined(SIZE_T_FMT) +#define SIZE_T_FMT "zu" +#endif +#endif /* _WIN32 */ + +#define INT64_FMT PRId64 +#define UINT64_FMT PRIu64 + +#ifndef va_copy +#define va_copy(x, y) x = y +#endif + +#ifndef JSON_MAX_PATH_LEN +#define JSON_MAX_PATH_LEN 60 +#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}; \ + 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), (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 append_to_path(struct frozen *f, const char *str, int size) { + int n = f->path_len; + f->path_len += + snprintf(f->path + f->path_len, sizeof(f->path) - (f->path_len), "%.*s", size, str); + if (f->path_len > sizeof(f->path) - 1) { + f->path_len = sizeof(f->path) - 1; + } + + return n; +} + +static void truncate_path(struct frozen *f, size_t len) { + f->path_len = len; + f->path[len] = '\0'; +} + +static int parse_object(struct frozen *f); +static int 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 left(const struct frozen *f) { + return f->end - f->cur; +} + +static int is_space(int ch) { + return ch == ' ' || ch == '\t' || ch == '\r' || ch == '\n'; +} + +static void skip_whitespaces(struct frozen *f) { + while (f->cur < f->end && is_space(*f->cur)) f->cur++; +} + +static int cur(struct frozen *f) { + skip_whitespaces(f); + return f->cur >= f->end ? END_OF_STRING : *(unsigned char *) f->cur; +} + +static int test_and_skip(struct frozen *f, int expected) { + int ch = cur(f); + if (ch == expected) { + f->cur++; + return 0; + } + return ch == END_OF_STRING ? JSON_STRING_INCOMPLETE : JSON_STRING_INVALID; +} + +static int is_alpha(int ch) { + return (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z'); +} + +static int is_digit(int ch) { + return ch >= '0' && ch <= '9'; +} + +static int is_hex_digit(int ch) { + return is_digit(ch) || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F'); +} + +static int get_escape_len(const char *s, int len) { + switch (*s) { + case 'u': + return len < 6 ? JSON_STRING_INCOMPLETE + : is_hex_digit(s[1]) && is_hex_digit(s[2]) && + is_hex_digit(s[3]) && is_hex_digit(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 parse_identifier(struct frozen *f) { + EXPECT(is_alpha(cur(f)), JSON_STRING_INVALID); + { + SET_STATE(f, f->cur, "", 0); + while (f->cur < f->end && + (*f->cur == '_' || is_alpha(*f->cur) || is_digit(*f->cur))) { + f->cur++; + } + truncate_path(f, fstate.path_len); + CALL_BACK(f, JSON_TYPE_STRING, fstate.ptr, f->cur - fstate.ptr); + } + return 0; +} + +static int 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 parse_string(struct frozen *f) { + int n, ch = 0, len = 0; + TRY(test_and_skip(f, '"')); + { + SET_STATE(f, f->cur, "", 0); + for (; f->cur < f->end; f->cur += len) { + ch = *(unsigned char *) f->cur; + len = get_utf8_char_len((unsigned char) ch); + EXPECT(ch >= 32 && len > 0, JSON_STRING_INVALID); /* No control chars */ + EXPECT(len < left(f), JSON_STRING_INCOMPLETE); + if (ch == '\\') { + EXPECT((n = get_escape_len(f->cur + 1, left(f))) > 0, n); + len += n; + } else if (ch == '"') { + 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 parse_number(struct frozen *f) { + int ch = cur(f); + SET_STATE(f, f->cur, "", 0); + if (ch == '-') f->cur++; + EXPECT(f->cur < f->end, JSON_STRING_INCOMPLETE); + EXPECT(is_digit(f->cur[0]), JSON_STRING_INVALID); + while (f->cur < f->end && is_digit(f->cur[0])) f->cur++; + if (f->cur < f->end && f->cur[0] == '.') { + f->cur++; + EXPECT(f->cur < f->end, JSON_STRING_INCOMPLETE); + EXPECT(is_digit(f->cur[0]), JSON_STRING_INVALID); + while (f->cur < f->end && is_digit(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(is_digit(f->cur[0]), JSON_STRING_INVALID); + while (f->cur < f->end && is_digit(f->cur[0])) f->cur++; + } + truncate_path(f, fstate.path_len); + CALL_BACK(f, JSON_TYPE_NUMBER, fstate.ptr, f->cur - fstate.ptr); + return 0; +} + +/* array = '[' [ value { ',' value } ] ']' */ +static int parse_array(struct frozen *f) { + int i = 0, current_path_len; + char buf[20]; + TRY(test_and_skip(f, '[')); + { + CALL_BACK(f, JSON_TYPE_ARRAY_START, NULL, 0); + { + SET_STATE(f, f->cur - 1, "", 0); + while (cur(f) != ']') { + snprintf(buf, sizeof(buf), "[%d]", i); + i++; + current_path_len = 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(parse_value(f)); + truncate_path(f, current_path_len); + if (cur(f) == ',') f->cur++; + } + TRY(test_and_skip(f, ']')); + truncate_path(f, fstate.path_len); + CALL_BACK(f, JSON_TYPE_ARRAY_END, fstate.ptr, f->cur - fstate.ptr); + } + } + return 0; +} + +static int expect(struct frozen *f, const char *s, int len, + enum json_token_type tok_type) { + int i, n = 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; + 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 parse_value(struct frozen *f) { + int ch = cur(f); + + switch (ch) { + case '"': + TRY(parse_string(f)); + break; + case '{': + TRY(parse_object(f)); + break; + case '[': + TRY(parse_array(f)); + break; + case 'n': + TRY(expect(f, "null", 4, JSON_TYPE_NULL)); + break; + case 't': + TRY(expect(f, "true", 4, JSON_TYPE_TRUE)); + break; + case 'f': + TRY(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(parse_number(f)); + break; + default: + return ch == END_OF_STRING ? JSON_STRING_INCOMPLETE : JSON_STRING_INVALID; + } + + return 0; +} + +/* key = identifier | string */ +static int parse_key(struct frozen *f) { + int ch = cur(f); +#if 0 + printf("%s [%.*s]\n", __func__, (int) (f->end - f->cur), f->cur); +#endif + if (is_alpha(ch)) { + TRY(parse_identifier(f)); + } else if (ch == '"') { + TRY(parse_string(f)); + } else { + return ch == END_OF_STRING ? JSON_STRING_INCOMPLETE : JSON_STRING_INVALID; + } + return 0; +} + +/* pair = key ':' value */ +static int parse_pair(struct frozen *f) { + int current_path_len; + const char *tok; + skip_whitespaces(f); + tok = f->cur; + TRY(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 = append_to_path(f, f->cur_name, f->cur_name_len); + } + TRY(test_and_skip(f, ':')); + TRY(parse_value(f)); + truncate_path(f, current_path_len); + return 0; +} + +/* object = '{' pair { ',' pair } '}' */ +static int parse_object(struct frozen *f) { + TRY(test_and_skip(f, '{')); + { + CALL_BACK(f, JSON_TYPE_OBJECT_START, NULL, 0); + { + SET_STATE(f, f->cur - 1, ".", 1); + while (cur(f) != '}') { + TRY(parse_pair(f)); + if (cur(f) == ',') f->cur++; + } + TRY(test_and_skip(f, '}')); + truncate_path(f, fstate.path_len); + CALL_BACK(f, JSON_TYPE_OBJECT_END, fstate.ptr, f->cur - fstate.ptr); + } + } + return 0; +} + +static int 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 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 = 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); +} + +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 uint8_t 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); +} + +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; +} + +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), "%" SIZE_T_FMT, 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') { + 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); + } else if (fmt[1] == 'V') { + 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); + } 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 = "sdfFgGlhuI.*-0123456789"; + size_t n = strspn(fmt + 1, end_of_format_specifier); + char *pbuf = buf; + size_t need_len; + char fmt2[20]; + va_list sub_ap; + strncpy(fmt2, fmt, n + 1 > sizeof(fmt2) ? sizeof(fmt2) : n + 1); + fmt2[n + 1] = '\0'; + + va_copy(sub_ap, ap); + need_len = + vsnprintf(buf, sizeof(buf), fmt2, sub_ap) + 1 /* null-term */; + /* + * TODO(lsm): Fix windows & eCos code path here. Their vsnprintf + * implementation returns -1 on overflow rather needed size. + */ + if (need_len > 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 + */ + pbuf = (char *) malloc(need_len); + va_copy(sub_ap, ap); + vsnprintf(pbuf, need_len, fmt2, sub_ap); + } + + /* + * 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); + skip += strlen(PRIu64) - 1; + } 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 == '_' || is_alpha(*fmt)) { + len += out->printer(out, quote, 1); + while (*fmt == '_' || is_alpha(*fmt) || is_digit(*fmt)) { + len += out->printer(out, fmt, 1); + fmt++; + } + len += out->printer(out, quote, 1); + } else { + 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 (strchr(fmt, 'f') != 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(doit(&frozen)); + + return frozen.cur - json_string; +} + +struct scan_array_info { + 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; + } +} + +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; + 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 token->len; +} + +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') { + /* TODO(lsm): \uXXXX 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; + + (void) name; + (void) name_len; + + if (strcmp(path, info->path) != 0) { + /* It's not the path we're looking for, so, just ignore this callback */ + return; + } + + 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; + } + + switch (info->type) { + case 'B': + info->num_conversions++; + *(int *) info->target = (token->type == JSON_TYPE_TRUE ? 1 : 0); + 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++; + json_unescape(token->ptr, token->len, *dst, unescaped_len); + (*dst)[unescaped_len] = '\0'; + } + } + break; + } + case 'H': { + 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++; + } + break; + } + case 'V': { + 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++; + } + break; + } + case 'T': + info->num_conversions++; + *(struct json_token *) info->target = *token; + break; + default: + info->num_conversions += sscanf(token->ptr, info->fmt, info->target); + 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; + snprintf(fmtbuf, sizeof(fmtbuf), "%.*s", conv_len, fmt + i); + i += conv_len; + i += strspn(fmt + i, delims); + break; + } + } + json_walk(s, len, json_scanf_cb, &info); + } else if (is_alpha(fmt[i]) || get_utf8_char_len(fmt[i]) > 1) { + const char *delims = ": \r\n\t"; + int key_len = strcspn(&fmt[i], delims); + if ((p = strrchr(path, '.')) != NULL) p[1] = '\0'; + sprintf(path + strlen(path), "%.*s", key_len, &fmt[i]); + 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; +} diff --git a/extern/frozen.h b/extern/frozen.h @@ -0,0 +1,220 @@ +/* + * Copyright (c) 2004-2013 Sergey Lyubka <valenok@gmail.com> + * Copyright (c) 2013 Cesanta Software Limited + * All rights reserved + * + * This library is dual-licensed: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. For the terms of this + * license, see <http: *www.gnu.org/licenses/>. + * + * You are free to use this library under the terms of the GNU General + * Public License, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * Alternatively, you can license this library under a commercial + * license, as set out in <http://cesanta.com/products.html>. + */ + +#ifndef CS_FROZEN_FROZEN_H_ +#define CS_FROZEN_FROZEN_H_ + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +#include <stdarg.h> +#include <stddef.h> +#include <stdio.h> + +/* JSON token type */ +enum json_token_type { + JSON_TYPE_INVALID = 0, /* memsetting to 0 should create INVALID value */ + JSON_TYPE_STRING, + JSON_TYPE_NUMBER, + JSON_TYPE_TRUE, + JSON_TYPE_FALSE, + JSON_TYPE_NULL, + JSON_TYPE_OBJECT_START, + JSON_TYPE_OBJECT_END, + JSON_TYPE_ARRAY_START, + JSON_TYPE_ARRAY_END, + + JSON_TYPES_CNT +}; + +/* + * Structure containing token type and value. Used in `json_walk()` and + * `json_scanf()` with the format specifier `%T`. + */ +struct json_token { + const char *ptr; /* Points to the beginning of the value */ + int len; /* Value length */ + enum json_token_type type; /* Type of the token, possible values are above */ +}; + +#define JSON_INVALID_TOKEN \ + { 0, 0, JSON_TYPE_INVALID } + +/* Error codes */ +#define JSON_STRING_INVALID -1 +#define JSON_STRING_INCOMPLETE -2 + +/* + * Callback-based SAX-like API. + * + * Property name and length is given only if it's available: i.e. if current + * event is an object's property. In other cases, `name` is `NULL`. For + * example, name is never given: + * - For the first value in the JSON string; + * - For events JSON_TYPE_OBJECT_END and JSON_TYPE_ARRAY_END + * + * E.g. for the input `{ "foo": 123, "bar": [ 1, 2, { "baz": true } ] }`, + * the sequence of callback invocations will be as follows: + * + * - type: JSON_TYPE_OBJECT_START, name: NULL, path: "", value: NULL + * - type: JSON_TYPE_NUMBER, name: "foo", path: ".foo", value: "123" + * - type: JSON_TYPE_ARRAY_START, name: "bar", path: ".bar", value: NULL + * - type: JSON_TYPE_NUMBER, name: "0", path: ".bar[0]", value: "1" + * - type: JSON_TYPE_NUMBER, name: "1", path: ".bar[1]", value: "2" + * - type: JSON_TYPE_OBJECT_START, name: "2", path: ".bar[2]", value: NULL + * - type: JSON_TYPE_TRUE, name: "baz", path: ".bar[2].baz", value: "true" + * - type: JSON_TYPE_OBJECT_END, name: NULL, path: ".bar[2]", value: "{ \"baz\": + *true }" + * - type: JSON_TYPE_ARRAY_END, name: NULL, path: ".bar", value: "[ 1, 2, { + *\"baz\": true } ]" + * - type: JSON_TYPE_OBJECT_END, name: NULL, path: "", value: "{ \"foo\": 123, + *\"bar\": [ 1, 2, { \"baz\": true } ] }" + */ +typedef void (*json_walk_callback_t)(void *callback_data, const char *name, + size_t name_len, const char *path, + const struct json_token *token); + +/* + * Parse `json_string`, invoking `callback` in a way similar to SAX parsers; + * see `json_walk_callback_t`. + */ +int json_walk(const char *json_string, int json_string_length, + json_walk_callback_t callback, void *callback_data); + +/* + * JSON generation API. + * struct json_out abstracts output, allowing alternative printing plugins. + */ +struct json_out { + int (*printer)(struct json_out *, const char *str, size_t len); + union { + struct { + char *buf; + size_t size; + size_t len; + } buf; + void *data; + FILE *fp; + } u; +}; + +extern int json_printer_buf(struct json_out *, const char *, size_t); +extern int json_printer_file(struct json_out *, const char *, size_t); + +#define JSON_OUT_BUF(buf, len) \ + { \ + json_printer_buf, { \ + { buf, len, 0 } \ + } \ + } +#define JSON_OUT_FILE(fp) \ + { \ + json_printer_file, { \ + { (void *) fp, 0, 0 } \ + } \ + } + +typedef int (*json_printf_callback_t)(struct json_out *, va_list *ap); + +/* + * Generate formatted output into a given sting buffer. + * This is a superset of printf() function, with extra format specifiers: + * - `%B` print json boolean, `true` or `false`. Accepts an `int`. + * - `%Q` print quoted escaped string or `null`. Accepts a `const char *`. + * - `%.*Q` same as `%Q`, but with length. Accepts `int`, `const char *` + * - `%V` print quoted base64-encoded string. Accepts a `const char *`, `int`. + * - `%H` print quoted hex-encoded string. Accepts a `int`, `const char *`. + * - `%M` invokes a json_printf_callback_t function. That callback function + * can consume more parameters. + * + * Return number of bytes printed. If the return value is bigger then the + * supplied buffer, that is an indicator of overflow. In the overflow case, + * overflown bytes are not printed. + */ +int json_printf(struct json_out *, const char *fmt, ...); +int json_vprintf(struct json_out *, const char *fmt, va_list ap); + +/* + * Helper %M callback that prints contiguous C arrays. + * Consumes void *array_ptr, size_t array_size, size_t elem_size, char *fmt + * Return number of bytes printed. + */ +int json_printf_array(struct json_out *, va_list *ap); + +/* + * Scan JSON string `str`, performing scanf-like conversions according to `fmt`. + * This is a `scanf()` - like function, with following differences: + * + * 1. Object keys in the format string may be not quoted, e.g. "{key: %d}" + * 2. Order of keys in an object is irrelevant. + * 3. Several extra format specifiers are supported: + * - %B: consumes `int *`, expects boolean `true` or `false`. + * - %Q: consumes `char **`, expects quoted, JSON-encoded string. Scanned + * string is malloc-ed, caller must free() the string. + * - %V: consumes `char **`, `int *`. Expects base64-encoded string. + * Result string is base64-decoded, malloced and NUL-terminated. + * The length of result string is stored in `int *` placeholder. + * Caller must free() the result. + * - %H: consumes `int *`, `char **`. + * Expects a hex-encoded string, e.g. "fa014f". + * Result string is hex-decoded, malloced and NUL-terminated. + * The length of the result string is stored in `int *` placeholder. + * Caller must free() the result. + * - %M: consumes custom scanning function pointer and + * `void *user_data` parameter - see json_scanner_t definition. + * - %T: consumes `struct json_token *`, fills it out with matched token. + * + * Return number of elements successfully scanned & converted. + * Negative number means scan error. + */ +int json_scanf(const char *str, int str_len, const char *fmt, ...); +int json_vscanf(const char *str, int str_len, const char *fmt, va_list ap); + +/* json_scanf's %M handler */ +typedef void (*json_scanner_t)(const char *str, int len, void *user_data); + +/* + * Helper function to scan array item with given path and index. + * Fills `token` with the matched JSON token. + * Return 0 if no array element found, otherwise non-0. + */ +int json_scanf_array_elem(const char *s, int len, const char *path, int index, + struct json_token *token); + +/* + * Unescape JSON-encoded string src,slen into dst, dlen. + * src and dst may overlap. + * If destination buffer is too small (or zero-length), result string is not + * written but the length is counted nevertheless (similar to snprintf). + * Return the length of unescaped string in bytes. + */ +int json_unescape(const char *src, int slen, char *dst, int dlen); + +/* + * Escape a string `str`, `str_len` into the printer `out`. + * Return the number of bytes printed. + */ +int json_escape(struct json_out *out, const char *str, size_t str_len); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* CS_FROZEN_FROZEN_H_ */ diff --git a/frozen.c b/frozen.c @@ -1,1001 +0,0 @@ -/* - * Copyright (c) 2004-2013 Sergey Lyubka <valenok@gmail.com> - * Copyright (c) 2013 Cesanta Software Limited - * All rights reserved - * - * This library is dual-licensed: you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 2 as - * published by the Free Software Foundation. For the terms of this - * license, see <http: *www.gnu.org/licenses/>. - * - * You are free to use this library under the terms of the GNU General - * Public License, but WITHOUT ANY WARRANTY; without even the implied - * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - * See the GNU General Public License for more details. - * - * Alternatively, you can license this library under a commercial - * license, as set out in <http://cesanta.com/products.html>. - */ - -#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 -#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" -#if !defined(SIZE_T_FMT) -#if _MSC_VER >= 1310 -#define SIZE_T_FMT "Iu" -#else -#define SIZE_T_FMT "u" -#endif -#endif -#else /* _WIN32 */ -/* <inttypes.h> wants this for C++ */ -#ifndef __STDC_FORMAT_MACROS -#define __STDC_FORMAT_MACROS -#endif -#include <inttypes.h> -#if !defined(SIZE_T_FMT) -#define SIZE_T_FMT "zu" -#endif -#endif /* _WIN32 */ - -#define INT64_FMT PRId64 -#define UINT64_FMT PRIu64 - -#ifndef va_copy -#define va_copy(x, y) x = y -#endif - -#ifndef JSON_MAX_PATH_LEN -#define JSON_MAX_PATH_LEN 60 -#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}; \ - 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), (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 append_to_path(struct frozen *f, const char *str, int size) { - int n = f->path_len; - f->path_len += - snprintf(f->path + f->path_len, sizeof(f->path) - (f->path_len), "%.*s", size, str); - if (f->path_len > sizeof(f->path) - 1) { - f->path_len = sizeof(f->path) - 1; - } - - return n; -} - -static void truncate_path(struct frozen *f, size_t len) { - f->path_len = len; - f->path[len] = '\0'; -} - -static int parse_object(struct frozen *f); -static int 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 left(const struct frozen *f) { - return f->end - f->cur; -} - -static int is_space(int ch) { - return ch == ' ' || ch == '\t' || ch == '\r' || ch == '\n'; -} - -static void skip_whitespaces(struct frozen *f) { - while (f->cur < f->end && is_space(*f->cur)) f->cur++; -} - -static int cur(struct frozen *f) { - skip_whitespaces(f); - return f->cur >= f->end ? END_OF_STRING : *(unsigned char *) f->cur; -} - -static int test_and_skip(struct frozen *f, int expected) { - int ch = cur(f); - if (ch == expected) { - f->cur++; - return 0; - } - return ch == END_OF_STRING ? JSON_STRING_INCOMPLETE : JSON_STRING_INVALID; -} - -static int is_alpha(int ch) { - return (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z'); -} - -static int is_digit(int ch) { - return ch >= '0' && ch <= '9'; -} - -static int is_hex_digit(int ch) { - return is_digit(ch) || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F'); -} - -static int get_escape_len(const char *s, int len) { - switch (*s) { - case 'u': - return len < 6 ? JSON_STRING_INCOMPLETE - : is_hex_digit(s[1]) && is_hex_digit(s[2]) && - is_hex_digit(s[3]) && is_hex_digit(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 parse_identifier(struct frozen *f) { - EXPECT(is_alpha(cur(f)), JSON_STRING_INVALID); - { - SET_STATE(f, f->cur, "", 0); - while (f->cur < f->end && - (*f->cur == '_' || is_alpha(*f->cur) || is_digit(*f->cur))) { - f->cur++; - } - truncate_path(f, fstate.path_len); - CALL_BACK(f, JSON_TYPE_STRING, fstate.ptr, f->cur - fstate.ptr); - } - return 0; -} - -static int 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 parse_string(struct frozen *f) { - int n, ch = 0, len = 0; - TRY(test_and_skip(f, '"')); - { - SET_STATE(f, f->cur, "", 0); - for (; f->cur < f->end; f->cur += len) { - ch = *(unsigned char *) f->cur; - len = get_utf8_char_len((unsigned char) ch); - EXPECT(ch >= 32 && len > 0, JSON_STRING_INVALID); /* No control chars */ - EXPECT(len < left(f), JSON_STRING_INCOMPLETE); - if (ch == '\\') { - EXPECT((n = get_escape_len(f->cur + 1, left(f))) > 0, n); - len += n; - } else if (ch == '"') { - 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 parse_number(struct frozen *f) { - int ch = cur(f); - SET_STATE(f, f->cur, "", 0); - if (ch == '-') f->cur++; - EXPECT(f->cur < f->end, JSON_STRING_INCOMPLETE); - EXPECT(is_digit(f->cur[0]), JSON_STRING_INVALID); - while (f->cur < f->end && is_digit(f->cur[0])) f->cur++; - if (f->cur < f->end && f->cur[0] == '.') { - f->cur++; - EXPECT(f->cur < f->end, JSON_STRING_INCOMPLETE); - EXPECT(is_digit(f->cur[0]), JSON_STRING_INVALID); - while (f->cur < f->end && is_digit(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(is_digit(f->cur[0]), JSON_STRING_INVALID); - while (f->cur < f->end && is_digit(f->cur[0])) f->cur++; - } - truncate_path(f, fstate.path_len); - CALL_BACK(f, JSON_TYPE_NUMBER, fstate.ptr, f->cur - fstate.ptr); - return 0; -} - -/* array = '[' [ value { ',' value } ] ']' */ -static int parse_array(struct frozen *f) { - int i = 0, current_path_len; - char buf[20]; - TRY(test_and_skip(f, '[')); - { - CALL_BACK(f, JSON_TYPE_ARRAY_START, NULL, 0); - { - SET_STATE(f, f->cur - 1, "", 0); - while (cur(f) != ']') { - snprintf(buf, sizeof(buf), "[%d]", i); - i++; - current_path_len = 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(parse_value(f)); - truncate_path(f, current_path_len); - if (cur(f) == ',') f->cur++; - } - TRY(test_and_skip(f, ']')); - truncate_path(f, fstate.path_len); - CALL_BACK(f, JSON_TYPE_ARRAY_END, fstate.ptr, f->cur - fstate.ptr); - } - } - return 0; -} - -static int expect(struct frozen *f, const char *s, int len, - enum json_token_type tok_type) { - int i, n = 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; - 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 parse_value(struct frozen *f) { - int ch = cur(f); - - switch (ch) { - case '"': - TRY(parse_string(f)); - break; - case '{': - TRY(parse_object(f)); - break; - case '[': - TRY(parse_array(f)); - break; - case 'n': - TRY(expect(f, "null", 4, JSON_TYPE_NULL)); - break; - case 't': - TRY(expect(f, "true", 4, JSON_TYPE_TRUE)); - break; - case 'f': - TRY(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(parse_number(f)); - break; - default: - return ch == END_OF_STRING ? JSON_STRING_INCOMPLETE : JSON_STRING_INVALID; - } - - return 0; -} - -/* key = identifier | string */ -static int parse_key(struct frozen *f) { - int ch = cur(f); -#if 0 - printf("%s [%.*s]\n", __func__, (int) (f->end - f->cur), f->cur); -#endif - if (is_alpha(ch)) { - TRY(parse_identifier(f)); - } else if (ch == '"') { - TRY(parse_string(f)); - } else { - return ch == END_OF_STRING ? JSON_STRING_INCOMPLETE : JSON_STRING_INVALID; - } - return 0; -} - -/* pair = key ':' value */ -static int parse_pair(struct frozen *f) { - int current_path_len; - const char *tok; - skip_whitespaces(f); - tok = f->cur; - TRY(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 = append_to_path(f, f->cur_name, f->cur_name_len); - } - TRY(test_and_skip(f, ':')); - TRY(parse_value(f)); - truncate_path(f, current_path_len); - return 0; -} - -/* object = '{' pair { ',' pair } '}' */ -static int parse_object(struct frozen *f) { - TRY(test_and_skip(f, '{')); - { - CALL_BACK(f, JSON_TYPE_OBJECT_START, NULL, 0); - { - SET_STATE(f, f->cur - 1, ".", 1); - while (cur(f) != '}') { - TRY(parse_pair(f)); - if (cur(f) == ',') f->cur++; - } - TRY(test_and_skip(f, '}')); - truncate_path(f, fstate.path_len); - CALL_BACK(f, JSON_TYPE_OBJECT_END, fstate.ptr, f->cur - fstate.ptr); - } - } - return 0; -} - -static int 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 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 = 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); -} - -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 uint8_t 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); -} - -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; -} - -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), "%" SIZE_T_FMT, 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') { - 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); - } else if (fmt[1] == 'V') { - 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); - } 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 = "sdfFgGlhuI.*-0123456789"; - size_t n = strspn(fmt + 1, end_of_format_specifier); - char *pbuf = buf; - size_t need_len; - char fmt2[20]; - va_list sub_ap; - strncpy(fmt2, fmt, n + 1 > sizeof(fmt2) ? sizeof(fmt2) : n + 1); - fmt2[n + 1] = '\0'; - - va_copy(sub_ap, ap); - need_len = - vsnprintf(buf, sizeof(buf), fmt2, sub_ap) + 1 /* null-term */; - /* - * TODO(lsm): Fix windows & eCos code path here. Their vsnprintf - * implementation returns -1 on overflow rather needed size. - */ - if (need_len > 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 - */ - pbuf = (char *) malloc(need_len); - va_copy(sub_ap, ap); - vsnprintf(pbuf, need_len, fmt2, sub_ap); - } - - /* - * 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); - skip += strlen(PRIu64) - 1; - } 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 == '_' || is_alpha(*fmt)) { - len += out->printer(out, quote, 1); - while (*fmt == '_' || is_alpha(*fmt) || is_digit(*fmt)) { - len += out->printer(out, fmt, 1); - fmt++; - } - len += out->printer(out, quote, 1); - } else { - 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 (strchr(fmt, 'f') != 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(doit(&frozen)); - - return frozen.cur - json_string; -} - -struct scan_array_info { - 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; - } -} - -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; - 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 token->len; -} - -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') { - /* TODO(lsm): \uXXXX 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; - - (void) name; - (void) name_len; - - if (strcmp(path, info->path) != 0) { - /* It's not the path we're looking for, so, just ignore this callback */ - return; - } - - 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; - } - - switch (info->type) { - case 'B': - info->num_conversions++; - *(int *) info->target = (token->type == JSON_TYPE_TRUE ? 1 : 0); - 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++; - json_unescape(token->ptr, token->len, *dst, unescaped_len); - (*dst)[unescaped_len] = '\0'; - } - } - break; - } - case 'H': { - 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++; - } - break; - } - case 'V': { - 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++; - } - break; - } - case 'T': - info->num_conversions++; - *(struct json_token *) info->target = *token; - break; - default: - info->num_conversions += sscanf(token->ptr, info->fmt, info->target); - 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; - snprintf(fmtbuf, sizeof(fmtbuf), "%.*s", conv_len, fmt + i); - i += conv_len; - i += strspn(fmt + i, delims); - break; - } - } - json_walk(s, len, json_scanf_cb, &info); - } else if (is_alpha(fmt[i]) || get_utf8_char_len(fmt[i]) > 1) { - const char *delims = ": \r\n\t"; - int key_len = strcspn(&fmt[i], delims); - if ((p = strrchr(path, '.')) != NULL) p[1] = '\0'; - sprintf(path + strlen(path), "%.*s", key_len, &fmt[i]); - 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; -} diff --git a/frozen.h b/frozen.h @@ -1,220 +0,0 @@ -/* - * Copyright (c) 2004-2013 Sergey Lyubka <valenok@gmail.com> - * Copyright (c) 2013 Cesanta Software Limited - * All rights reserved - * - * This library is dual-licensed: you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 2 as - * published by the Free Software Foundation. For the terms of this - * license, see <http: *www.gnu.org/licenses/>. - * - * You are free to use this library under the terms of the GNU General - * Public License, but WITHOUT ANY WARRANTY; without even the implied - * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - * See the GNU General Public License for more details. - * - * Alternatively, you can license this library under a commercial - * license, as set out in <http://cesanta.com/products.html>. - */ - -#ifndef CS_FROZEN_FROZEN_H_ -#define CS_FROZEN_FROZEN_H_ - -#ifdef __cplusplus -extern "C" { -#endif /* __cplusplus */ - -#include <stdarg.h> -#include <stddef.h> -#include <stdio.h> - -/* JSON token type */ -enum json_token_type { - JSON_TYPE_INVALID = 0, /* memsetting to 0 should create INVALID value */ - JSON_TYPE_STRING, - JSON_TYPE_NUMBER, - JSON_TYPE_TRUE, - JSON_TYPE_FALSE, - JSON_TYPE_NULL, - JSON_TYPE_OBJECT_START, - JSON_TYPE_OBJECT_END, - JSON_TYPE_ARRAY_START, - JSON_TYPE_ARRAY_END, - - JSON_TYPES_CNT -}; - -/* - * Structure containing token type and value. Used in `json_walk()` and - * `json_scanf()` with the format specifier `%T`. - */ -struct json_token { - const char *ptr; /* Points to the beginning of the value */ - int len; /* Value length */ - enum json_token_type type; /* Type of the token, possible values are above */ -}; - -#define JSON_INVALID_TOKEN \ - { 0, 0, JSON_TYPE_INVALID } - -/* Error codes */ -#define JSON_STRING_INVALID -1 -#define JSON_STRING_INCOMPLETE -2 - -/* - * Callback-based SAX-like API. - * - * Property name and length is given only if it's available: i.e. if current - * event is an object's property. In other cases, `name` is `NULL`. For - * example, name is never given: - * - For the first value in the JSON string; - * - For events JSON_TYPE_OBJECT_END and JSON_TYPE_ARRAY_END - * - * E.g. for the input `{ "foo": 123, "bar": [ 1, 2, { "baz": true } ] }`, - * the sequence of callback invocations will be as follows: - * - * - type: JSON_TYPE_OBJECT_START, name: NULL, path: "", value: NULL - * - type: JSON_TYPE_NUMBER, name: "foo", path: ".foo", value: "123" - * - type: JSON_TYPE_ARRAY_START, name: "bar", path: ".bar", value: NULL - * - type: JSON_TYPE_NUMBER, name: "0", path: ".bar[0]", value: "1" - * - type: JSON_TYPE_NUMBER, name: "1", path: ".bar[1]", value: "2" - * - type: JSON_TYPE_OBJECT_START, name: "2", path: ".bar[2]", value: NULL - * - type: JSON_TYPE_TRUE, name: "baz", path: ".bar[2].baz", value: "true" - * - type: JSON_TYPE_OBJECT_END, name: NULL, path: ".bar[2]", value: "{ \"baz\": - *true }" - * - type: JSON_TYPE_ARRAY_END, name: NULL, path: ".bar", value: "[ 1, 2, { - *\"baz\": true } ]" - * - type: JSON_TYPE_OBJECT_END, name: NULL, path: "", value: "{ \"foo\": 123, - *\"bar\": [ 1, 2, { \"baz\": true } ] }" - */ -typedef void (*json_walk_callback_t)(void *callback_data, const char *name, - size_t name_len, const char *path, - const struct json_token *token); - -/* - * Parse `json_string`, invoking `callback` in a way similar to SAX parsers; - * see `json_walk_callback_t`. - */ -int json_walk(const char *json_string, int json_string_length, - json_walk_callback_t callback, void *callback_data); - -/* - * JSON generation API. - * struct json_out abstracts output, allowing alternative printing plugins. - */ -struct json_out { - int (*printer)(struct json_out *, const char *str, size_t len); - union { - struct { - char *buf; - size_t size; - size_t len; - } buf; - void *data; - FILE *fp; - } u; -}; - -extern int json_printer_buf(struct json_out *, const char *, size_t); -extern int json_printer_file(struct json_out *, const char *, size_t); - -#define JSON_OUT_BUF(buf, len) \ - { \ - json_printer_buf, { \ - { buf, len, 0 } \ - } \ - } -#define JSON_OUT_FILE(fp) \ - { \ - json_printer_file, { \ - { (void *) fp, 0, 0 } \ - } \ - } - -typedef int (*json_printf_callback_t)(struct json_out *, va_list *ap); - -/* - * Generate formatted output into a given sting buffer. - * This is a superset of printf() function, with extra format specifiers: - * - `%B` print json boolean, `true` or `false`. Accepts an `int`. - * - `%Q` print quoted escaped string or `null`. Accepts a `const char *`. - * - `%.*Q` same as `%Q`, but with length. Accepts `int`, `const char *` - * - `%V` print quoted base64-encoded string. Accepts a `const char *`, `int`. - * - `%H` print quoted hex-encoded string. Accepts a `int`, `const char *`. - * - `%M` invokes a json_printf_callback_t function. That callback function - * can consume more parameters. - * - * Return number of bytes printed. If the return value is bigger then the - * supplied buffer, that is an indicator of overflow. In the overflow case, - * overflown bytes are not printed. - */ -int json_printf(struct json_out *, const char *fmt, ...); -int json_vprintf(struct json_out *, const char *fmt, va_list ap); - -/* - * Helper %M callback that prints contiguous C arrays. - * Consumes void *array_ptr, size_t array_size, size_t elem_size, char *fmt - * Return number of bytes printed. - */ -int json_printf_array(struct json_out *, va_list *ap); - -/* - * Scan JSON string `str`, performing scanf-like conversions according to `fmt`. - * This is a `scanf()` - like function, with following differences: - * - * 1. Object keys in the format string may be not quoted, e.g. "{key: %d}" - * 2. Order of keys in an object is irrelevant. - * 3. Several extra format specifiers are supported: - * - %B: consumes `int *`, expects boolean `true` or `false`. - * - %Q: consumes `char **`, expects quoted, JSON-encoded string. Scanned - * string is malloc-ed, caller must free() the string. - * - %V: consumes `char **`, `int *`. Expects base64-encoded string. - * Result string is base64-decoded, malloced and NUL-terminated. - * The length of result string is stored in `int *` placeholder. - * Caller must free() the result. - * - %H: consumes `int *`, `char **`. - * Expects a hex-encoded string, e.g. "fa014f". - * Result string is hex-decoded, malloced and NUL-terminated. - * The length of the result string is stored in `int *` placeholder. - * Caller must free() the result. - * - %M: consumes custom scanning function pointer and - * `void *user_data` parameter - see json_scanner_t definition. - * - %T: consumes `struct json_token *`, fills it out with matched token. - * - * Return number of elements successfully scanned & converted. - * Negative number means scan error. - */ -int json_scanf(const char *str, int str_len, const char *fmt, ...); -int json_vscanf(const char *str, int str_len, const char *fmt, va_list ap); - -/* json_scanf's %M handler */ -typedef void (*json_scanner_t)(const char *str, int len, void *user_data); - -/* - * Helper function to scan array item with given path and index. - * Fills `token` with the matched JSON token. - * Return 0 if no array element found, otherwise non-0. - */ -int json_scanf_array_elem(const char *s, int len, const char *path, int index, - struct json_token *token); - -/* - * Unescape JSON-encoded string src,slen into dst, dlen. - * src and dst may overlap. - * If destination buffer is too small (or zero-length), result string is not - * written but the length is counted nevertheless (similar to snprintf). - * Return the length of unescaped string in bytes. - */ -int json_unescape(const char *src, int slen, char *dst, int dlen); - -/* - * Escape a string `str`, `str_len` into the printer `out`. - * Return the number of bytes printed. - */ -int json_escape(struct json_out *out, const char *str, size_t str_len); - -#ifdef __cplusplus -} -#endif /* __cplusplus */ - -#endif /* CS_FROZEN_FROZEN_H_ */ diff --git a/gs.c b/gs.c @@ -6,8 +6,8 @@ #include <string.h> #include <unistd.h> -#include "arg.h" -#include "frozen.h" +#include "extern/arg.h" +#include "extern/frozen.h" #include "util.h" /* defines */