diff --git a/Makefile b/Makefile index b92954e4..3aadc9d6 100644 --- a/Makefile +++ b/Makefile @@ -152,7 +152,7 @@ mongoose.c: Makefile $(wildcard src/*) (cat src/license.h; echo; echo '#include "mongoose.h"' ; (for F in src/*.c ; do echo; echo '#ifdef MG_ENABLE_LINES'; echo "#line 1 \"$$F\""; echo '#endif'; cat $$F | sed -e 's,#include ".*,,'; done))> $@ mongoose.h: $(HDRS) Makefile - (cat src/license.h; echo; echo '#ifndef MONGOOSE_H'; echo '#define MONGOOSE_H'; echo; cat src/version.h ; echo; echo '#ifdef __cplusplus'; echo 'extern "C" {'; echo '#endif'; cat src/arch.h src/arch_*.h src/config.h src/str.h src/log.h src/timer.h src/fs.h src/util.h src/url.h src/iobuf.h src/base64.h src/md5.h src/sha1.h src/event.h src/net.h src/http.h src/ssi.h src/tls.h src/tls_mbed.h src/tls_openssl.h src/ws.h src/sntp.h src/mqtt.h src/dns.h src/mip.h | sed -e 's,#include ".*,,' -e 's,^#pragma once,,'; echo; echo '#ifdef __cplusplus'; echo '}'; echo '#endif'; echo '#endif // MONGOOSE_H')> $@ + (cat src/license.h; echo; echo '#ifndef MONGOOSE_H'; echo '#define MONGOOSE_H'; echo; cat src/version.h ; echo; echo '#ifdef __cplusplus'; echo 'extern "C" {'; echo '#endif'; cat src/arch.h src/arch_*.h src/config.h src/str.h src/log.h src/timer.h src/fs.h src/util.h src/url.h src/iobuf.h src/base64.h src/md5.h src/sha1.h src/event.h src/net.h src/http.h src/ssi.h src/tls.h src/tls_mbed.h src/tls_openssl.h src/ws.h src/sntp.h src/mqtt.h src/dns.h src/mip.h src/json.h | sed -e 's,#include ".*,,' -e 's,^#pragma once,,'; echo; echo '#ifdef __cplusplus'; echo '}'; echo '#endif'; echo '#endif // MONGOOSE_H')> $@ clean: rm -rf $(PROG) *.exe *.o *.dSYM unit_test* valgrind_unit_test* ut fuzzer *.gcov *.gcno *.gcda *.obj *.exe *.ilk *.pdb slow-unit* _CL_* infer-out data.txt crash-* test/packed_fs.c pack unpacked diff --git a/mongoose.c b/mongoose.c index f1fb6996..cd8ef04d 100644 --- a/mongoose.c +++ b/mongoose.c @@ -2019,6 +2019,231 @@ void mg_iobuf_free(struct mg_iobuf *io) { mg_iobuf_resize(io, 0); } +#ifdef MG_ENABLE_LINES +#line 1 "src/json.c" +#endif + + +static const char *escapeseq(int esc) { + return esc ? "\b\f\n\r\t\\\"" : "bfnrt\\\""; +} + +static char json_esc(int c, int esc) { + const char *p, *esc1 = escapeseq(esc), *esc2 = escapeseq(!esc); + for (p = esc1; *p != '\0'; p++) { + if (*p == c) return esc2[p - esc1]; + } + return 0; +} + +static int mg_pass_string(const char *s, int len) { + int i; + for (i = 0; i < len; i++) { + if (s[i] == '\\' && i + 1 < len && json_esc(s[i + 1], 1)) { + i++; + } else if (s[i] == '\0') { + return MG_JSON_INVALID; + } else if (s[i] == '"') { + return i; + } + } + return MG_JSON_INVALID; +} + +int mg_json_get(const char *s, int len, const char *path, int *toklen) { + enum { S_VALUE, S_KEY, S_COLON, S_COMMA_OR_EOO } expecting = S_VALUE; + unsigned char nesting[MG_JSON_MAX_DEPTH]; + int i, j = 0, depth = 0; + int pos = 1; // Current position in path + int ed = 0; // Expected depth + int ci = -1, ei = -1; // Current and expected index in array + + if (path[0] != '$') return MG_JSON_INVALID; + +#if 0 +#define MG_DBGP(x) \ + do { \ + printf("%c %.*s j=%d i=%d pos=%d depth=%d ed=%d ci=%d ei=%d\n", x, len, s, \ + j, i, pos, depth, ed, ci, ei); \ + } while (0) +#else +#define MG_DBGP(x) +#endif + +#define MG_CHECKRET(x) \ + do { \ + MG_DBGP(x); \ + if (depth == ed && path[pos] == '\0' && ci == ei) { \ + if (toklen) *toklen = i - j + 1; \ + return j; \ + } \ + } while (0) + +// In the ascii table, the distance between `[` and `]` is 2. +// Ditto for `{` and `}`. Hence +2 in the code below. +#define MG_EOO(x) \ + do { \ + if (depth == ed && ci != ei) return MG_JSON_NOT_FOUND; \ + if (c != nesting[depth - 1] + 2) return MG_JSON_INVALID; \ + depth--; \ + MG_CHECKRET(x); \ + } while (0) + + for (i = 0; i < len; i++) { + unsigned char c = ((unsigned char *) s)[i]; + if (c == ' ' || c == '\t' || c == '\n' || c == '\r') continue; + MG_DBGP('-'); + switch (expecting) { + case S_VALUE: + if (depth == ed) j = i; + if (c == '{') { + if (depth >= (int) sizeof(nesting)) return MG_JSON_TOO_DEEP; + if (depth == ed && path[pos] == '.') ed++, pos++; + nesting[depth++] = c; + expecting = S_KEY; + break; + } else if (c == '[') { + if (depth >= (int) sizeof(nesting)) return MG_JSON_TOO_DEEP; + if (depth == ed && path[pos] == '[' && ei == ci) { + ed++, pos++, ci = 0; + for (ei = 0; path[pos] != ']' && path[pos] != '\0'; pos++) { + ei *= 10; + ei += path[pos] - '0'; + } + if (path[pos] != 0) pos++; + } + nesting[depth++] = c; + break; + } else if (c == ']' && depth > 0) { // Empty array + MG_EOO(']'); + } else if (c == 't' && i + 3 < len && memcmp(&s[i], "true", 4) == 0) { + i += 3; + } else if (c == 'n' && i + 3 < len && memcmp(&s[i], "null", 4) == 0) { + i += 3; + } else if (c == 'f' && i + 4 < len && memcmp(&s[i], "false", 5) == 0) { + i += 4; + } else if (c == '-' || ((c >= '0' && c <= '9'))) { + int numlen = 0; + mg_atod(&s[i], len - i, &numlen); + i += numlen - 1; + } else if (c == '"') { + int n = mg_pass_string(&s[i + 1], len - i - 1); + if (n < 0) return n; + i += n + 1; + } else { + return MG_JSON_INVALID; + } + MG_CHECKRET('V'); + if (depth == ed && ei >= 0) ci++; + expecting = S_COMMA_OR_EOO; + break; + + case S_KEY: + if (c == '"') { + int n = mg_pass_string(&s[i + 1], len - i - 1); + if (n < 0) return n; + // printf("K[%.*s] %d %d\n", n, &s[i + 1], depth, ed); + if (depth == ed && path[pos - 1] == '.' && + memcmp(&s[i + 1], &path[pos], (size_t) n) == 0) { + pos += n; + } + i += n + 1; + expecting = S_COLON; + } else if (c == '}') { // Empty object + MG_EOO('}'); + expecting = S_COMMA_OR_EOO; + } else { + return MG_JSON_INVALID; + } + break; + + case S_COLON: + if (c == ':') { + expecting = S_VALUE; + } else { + return MG_JSON_INVALID; + } + break; + + case S_COMMA_OR_EOO: + if (depth <= 0) { + return MG_JSON_INVALID; + } else if (c == ',') { + expecting = (nesting[depth - 1] == '{') ? S_KEY : S_VALUE; + // MG_CHECKRET('C'); + } else if (c == ']' || c == '}') { + MG_EOO('O'); + if (depth == ed && ei >= 0) ci++; + } else { + return MG_JSON_INVALID; + } + break; + } + MG_DBGP('E'); + } + return MG_JSON_NOT_FOUND; +} + +bool mg_json_get_num(struct mg_str json, const char *path, double *v) { + int n, toklen, found = 0; + if ((n = mg_json_get(json.ptr, (int) json.len, path, &toklen)) >= 0 && + (json.ptr[n] == '-' || (json.ptr[n] >= '0' && json.ptr[n] <= '9'))) { + if (v != NULL) *v = mg_atod(json.ptr + n, toklen, NULL); + found = 1; + } + return found; +} + +bool mg_json_get_bool(struct mg_str json, const char *path, bool *v) { + int n, toklen, found = 0; + if ((n = mg_json_get(json.ptr, (int) json.len, path, &toklen)) >= 0 && + (json.ptr[n] == 't' || json.ptr[n] == 'f')) { + if (v != NULL) *v = json.ptr[n] == 't'; + found = 1; + } + return found; +} + +static bool json_unescape(const char *s, size_t len, char *to, size_t n) { + size_t i, j; + for (i = 0, j = 0; i < len && j < n; i++, j++) { + if (s[i] == '\\' && i + 5 < len && s[i + 1] == 'u') { + // \uXXXX escape. We could process a simple one-byte chars + // \u00xx from the ASCII range. More complex chars would require + // dragging in a UTF8 library, which is too much for us + if (s[i + 2] != '0' || s[i + 3] != '0') return false; // Give up + ((unsigned char *) to)[j] = (unsigned char) mg_unhexn(s + i + 4, 2); + + i += 5; + } else if (s[i] == '\\' && i + 1 < len) { + char c = json_esc(s[i + 1], 0); + if (c == 0) return false; + to[j] = c; + i++; + } else { + to[j] = s[i]; + } + } + if (j >= n) return false; + if (n > 0) to[j] = '\0'; + return true; +} + +char *mg_json_get_str(struct mg_str json, const char *path) { + int n, toklen; + char *result = NULL; + if ((n = mg_json_get(json.ptr, (int) json.len, path, &toklen)) >= 0 && + json.ptr[n] == '"') { + if ((result = (char *) calloc(1, (size_t) toklen)) != NULL && + !json_unescape(json.ptr + n + 1, (size_t) (toklen - 2), result, + (size_t) toklen)) { + free(result); + result = NULL; + } + } + return result; +} + #ifdef MG_ENABLE_LINES #line 1 "src/log.c" #endif @@ -5075,6 +5300,52 @@ size_t mg_vsnprintf(char *buf, size_t len, const char *fmt, va_list ap) { return n; } +double mg_atod(const char *p, int len, int *numlen) { + double d = 0.0; + int i = 0, sign = 1; + + // Sign + if (i < len && *p == '-') { + sign = -1, i++; + } else if (i < len && *p == '+') { + i++; + } + + // Decimal + for (; i < len && is_digit(p[i]); i++) { + d *= 10.0; + d += p[i] - '0'; + } + d *= sign; + + // Fractional + if (i < len && p[i] == '.') { + double frac = 0.0, base = 0.1; + i++; + for (; i < len && is_digit(p[i]); i++) { + frac += base * (p[i] - '0'); + base /= 10.0; + } + d += frac * sign; + } + + // Exponential + if (i < len && (p[i] == 'e' || p[i] == 'E')) { + int j, exp = 0, minus = 0; + i++; + if (i < len && p[i] == '-') minus = 1, i++; + if (i < len && p[i] == '+') i++; + while (i < len && is_digit(p[i]) && exp < 308) + exp = exp * 10 + (p[i++] - '0'); + if (minus) exp = -exp; + for (j = 0; j < exp; j++) d *= 10.0; + for (j = 0; j < -exp; j++) d /= 10.0; + } + + if (numlen != NULL) *numlen = i; + return d; +} + #ifdef MG_ENABLE_LINES #line 1 "src/timer.c" #endif diff --git a/mongoose.h b/mongoose.h index c0262b12..899bf504 100644 --- a/mongoose.h +++ b/mongoose.h @@ -726,6 +726,7 @@ int mg_check_ip_acl(struct mg_str acl, uint32_t remote_ip); int64_t mg_to64(struct mg_str str); uint64_t mg_tou64(struct mg_str str); size_t mg_lld(char *buf, int64_t val, bool is_signed, bool is_hex); +double mg_atod(const char *buf, int len, int *numlen); @@ -1303,6 +1304,23 @@ struct mip_ipcfg { void mip_init(struct mg_mgr *, struct mip_ipcfg *, struct mip_driver *); + + + + +#ifndef MG_JSON_MAX_DEPTH +#define MG_JSON_MAX_DEPTH 30 +#endif + +// Error return values - negative. Successful returns are >= 0 +enum { MG_JSON_TOO_DEEP = -1, MG_JSON_INVALID = -2, MG_JSON_NOT_FOUND = -3 }; +int mg_json_get(const char *buf, int len, const char *path, int *toklen); + +bool mg_json_get_num(struct mg_str json, const char *path, double *v); +bool mg_json_get_bool(struct mg_str json, const char *path, bool *v); +char *mg_json_get_str(struct mg_str json, const char *path); +char *mg_json_get_hex(struct mg_str json, const char *path); + #ifdef __cplusplus } #endif diff --git a/src/json.c b/src/json.c new file mode 100644 index 00000000..21ece7ef --- /dev/null +++ b/src/json.c @@ -0,0 +1,221 @@ +#include "json.h" + +static const char *escapeseq(int esc) { + return esc ? "\b\f\n\r\t\\\"" : "bfnrt\\\""; +} + +static char json_esc(int c, int esc) { + const char *p, *esc1 = escapeseq(esc), *esc2 = escapeseq(!esc); + for (p = esc1; *p != '\0'; p++) { + if (*p == c) return esc2[p - esc1]; + } + return 0; +} + +static int mg_pass_string(const char *s, int len) { + int i; + for (i = 0; i < len; i++) { + if (s[i] == '\\' && i + 1 < len && json_esc(s[i + 1], 1)) { + i++; + } else if (s[i] == '\0') { + return MG_JSON_INVALID; + } else if (s[i] == '"') { + return i; + } + } + return MG_JSON_INVALID; +} + +int mg_json_get(const char *s, int len, const char *path, int *toklen) { + enum { S_VALUE, S_KEY, S_COLON, S_COMMA_OR_EOO } expecting = S_VALUE; + unsigned char nesting[MG_JSON_MAX_DEPTH]; + int i, j = 0, depth = 0; + int pos = 1; // Current position in path + int ed = 0; // Expected depth + int ci = -1, ei = -1; // Current and expected index in array + + if (path[0] != '$') return MG_JSON_INVALID; + +#if 0 +#define MG_DBGP(x) \ + do { \ + printf("%c %.*s j=%d i=%d pos=%d depth=%d ed=%d ci=%d ei=%d\n", x, len, s, \ + j, i, pos, depth, ed, ci, ei); \ + } while (0) +#else +#define MG_DBGP(x) +#endif + +#define MG_CHECKRET(x) \ + do { \ + MG_DBGP(x); \ + if (depth == ed && path[pos] == '\0' && ci == ei) { \ + if (toklen) *toklen = i - j + 1; \ + return j; \ + } \ + } while (0) + +// In the ascii table, the distance between `[` and `]` is 2. +// Ditto for `{` and `}`. Hence +2 in the code below. +#define MG_EOO(x) \ + do { \ + if (depth == ed && ci != ei) return MG_JSON_NOT_FOUND; \ + if (c != nesting[depth - 1] + 2) return MG_JSON_INVALID; \ + depth--; \ + MG_CHECKRET(x); \ + } while (0) + + for (i = 0; i < len; i++) { + unsigned char c = ((unsigned char *) s)[i]; + if (c == ' ' || c == '\t' || c == '\n' || c == '\r') continue; + MG_DBGP('-'); + switch (expecting) { + case S_VALUE: + if (depth == ed) j = i; + if (c == '{') { + if (depth >= (int) sizeof(nesting)) return MG_JSON_TOO_DEEP; + if (depth == ed && path[pos] == '.') ed++, pos++; + nesting[depth++] = c; + expecting = S_KEY; + break; + } else if (c == '[') { + if (depth >= (int) sizeof(nesting)) return MG_JSON_TOO_DEEP; + if (depth == ed && path[pos] == '[' && ei == ci) { + ed++, pos++, ci = 0; + for (ei = 0; path[pos] != ']' && path[pos] != '\0'; pos++) { + ei *= 10; + ei += path[pos] - '0'; + } + if (path[pos] != 0) pos++; + } + nesting[depth++] = c; + break; + } else if (c == ']' && depth > 0) { // Empty array + MG_EOO(']'); + } else if (c == 't' && i + 3 < len && memcmp(&s[i], "true", 4) == 0) { + i += 3; + } else if (c == 'n' && i + 3 < len && memcmp(&s[i], "null", 4) == 0) { + i += 3; + } else if (c == 'f' && i + 4 < len && memcmp(&s[i], "false", 5) == 0) { + i += 4; + } else if (c == '-' || ((c >= '0' && c <= '9'))) { + int numlen = 0; + mg_atod(&s[i], len - i, &numlen); + i += numlen - 1; + } else if (c == '"') { + int n = mg_pass_string(&s[i + 1], len - i - 1); + if (n < 0) return n; + i += n + 1; + } else { + return MG_JSON_INVALID; + } + MG_CHECKRET('V'); + if (depth == ed && ei >= 0) ci++; + expecting = S_COMMA_OR_EOO; + break; + + case S_KEY: + if (c == '"') { + int n = mg_pass_string(&s[i + 1], len - i - 1); + if (n < 0) return n; + // printf("K[%.*s] %d %d\n", n, &s[i + 1], depth, ed); + if (depth == ed && path[pos - 1] == '.' && + memcmp(&s[i + 1], &path[pos], (size_t) n) == 0) { + pos += n; + } + i += n + 1; + expecting = S_COLON; + } else if (c == '}') { // Empty object + MG_EOO('}'); + expecting = S_COMMA_OR_EOO; + } else { + return MG_JSON_INVALID; + } + break; + + case S_COLON: + if (c == ':') { + expecting = S_VALUE; + } else { + return MG_JSON_INVALID; + } + break; + + case S_COMMA_OR_EOO: + if (depth <= 0) { + return MG_JSON_INVALID; + } else if (c == ',') { + expecting = (nesting[depth - 1] == '{') ? S_KEY : S_VALUE; + // MG_CHECKRET('C'); + } else if (c == ']' || c == '}') { + MG_EOO('O'); + if (depth == ed && ei >= 0) ci++; + } else { + return MG_JSON_INVALID; + } + break; + } + MG_DBGP('E'); + } + return MG_JSON_NOT_FOUND; +} + +bool mg_json_get_num(struct mg_str json, const char *path, double *v) { + int n, toklen, found = 0; + if ((n = mg_json_get(json.ptr, (int) json.len, path, &toklen)) >= 0 && + (json.ptr[n] == '-' || (json.ptr[n] >= '0' && json.ptr[n] <= '9'))) { + if (v != NULL) *v = mg_atod(json.ptr + n, toklen, NULL); + found = 1; + } + return found; +} + +bool mg_json_get_bool(struct mg_str json, const char *path, bool *v) { + int n, toklen, found = 0; + if ((n = mg_json_get(json.ptr, (int) json.len, path, &toklen)) >= 0 && + (json.ptr[n] == 't' || json.ptr[n] == 'f')) { + if (v != NULL) *v = json.ptr[n] == 't'; + found = 1; + } + return found; +} + +static bool json_unescape(const char *s, size_t len, char *to, size_t n) { + size_t i, j; + for (i = 0, j = 0; i < len && j < n; i++, j++) { + if (s[i] == '\\' && i + 5 < len && s[i + 1] == 'u') { + // \uXXXX escape. We could process a simple one-byte chars + // \u00xx from the ASCII range. More complex chars would require + // dragging in a UTF8 library, which is too much for us + if (s[i + 2] != '0' || s[i + 3] != '0') return false; // Give up + ((unsigned char *) to)[j] = (unsigned char) mg_unhexn(s + i + 4, 2); + + i += 5; + } else if (s[i] == '\\' && i + 1 < len) { + char c = json_esc(s[i + 1], 0); + if (c == 0) return false; + to[j] = c; + i++; + } else { + to[j] = s[i]; + } + } + if (j >= n) return false; + if (n > 0) to[j] = '\0'; + return true; +} + +char *mg_json_get_str(struct mg_str json, const char *path) { + int n, toklen; + char *result = NULL; + if ((n = mg_json_get(json.ptr, (int) json.len, path, &toklen)) >= 0 && + json.ptr[n] == '"') { + if ((result = (char *) calloc(1, (size_t) toklen)) != NULL && + !json_unescape(json.ptr + n + 1, (size_t) (toklen - 2), result, + (size_t) toklen)) { + free(result); + result = NULL; + } + } + return result; +} diff --git a/src/json.h b/src/json.h new file mode 100644 index 00000000..454153cf --- /dev/null +++ b/src/json.h @@ -0,0 +1,17 @@ +#pragma once + +#include "arch.h" +#include "str.h" + +#ifndef MG_JSON_MAX_DEPTH +#define MG_JSON_MAX_DEPTH 30 +#endif + +// Error return values - negative. Successful returns are >= 0 +enum { MG_JSON_TOO_DEEP = -1, MG_JSON_INVALID = -2, MG_JSON_NOT_FOUND = -3 }; +int mg_json_get(const char *buf, int len, const char *path, int *toklen); + +bool mg_json_get_num(struct mg_str json, const char *path, double *v); +bool mg_json_get_bool(struct mg_str json, const char *path, bool *v); +char *mg_json_get_str(struct mg_str json, const char *path); +char *mg_json_get_hex(struct mg_str json, const char *path); diff --git a/src/str.c b/src/str.c index 73776dce..84a03e48 100644 --- a/src/str.c +++ b/src/str.c @@ -397,3 +397,49 @@ size_t mg_vsnprintf(char *buf, size_t len, const char *fmt, va_list ap) { if (n < len) buf[n] = '\0'; return n; } + +double mg_atod(const char *p, int len, int *numlen) { + double d = 0.0; + int i = 0, sign = 1; + + // Sign + if (i < len && *p == '-') { + sign = -1, i++; + } else if (i < len && *p == '+') { + i++; + } + + // Decimal + for (; i < len && is_digit(p[i]); i++) { + d *= 10.0; + d += p[i] - '0'; + } + d *= sign; + + // Fractional + if (i < len && p[i] == '.') { + double frac = 0.0, base = 0.1; + i++; + for (; i < len && is_digit(p[i]); i++) { + frac += base * (p[i] - '0'); + base /= 10.0; + } + d += frac * sign; + } + + // Exponential + if (i < len && (p[i] == 'e' || p[i] == 'E')) { + int j, exp = 0, minus = 0; + i++; + if (i < len && p[i] == '-') minus = 1, i++; + if (i < len && p[i] == '+') i++; + while (i < len && is_digit(p[i]) && exp < 308) + exp = exp * 10 + (p[i++] - '0'); + if (minus) exp = -exp; + for (j = 0; j < exp; j++) d *= 10.0; + for (j = 0; j < -exp; j++) d /= 10.0; + } + + if (numlen != NULL) *numlen = i; + return d; +} diff --git a/src/str.h b/src/str.h index bdf7633c..a90fce6a 100644 --- a/src/str.h +++ b/src/str.h @@ -42,3 +42,4 @@ int mg_check_ip_acl(struct mg_str acl, uint32_t remote_ip); int64_t mg_to64(struct mg_str str); uint64_t mg_tou64(struct mg_str str); size_t mg_lld(char *buf, int64_t val, bool is_signed, bool is_hex); +double mg_atod(const char *buf, int len, int *numlen); diff --git a/test/fuzz.c b/test/fuzz.c index 123e3816..2af58e07 100644 --- a/test/fuzz.c +++ b/test/fuzz.c @@ -43,5 +43,8 @@ int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) { struct mg_str k, v, s = mg_str_n((char *) data, size); while (mg_commalist(&s, &k, &v)) k.len = v.len = 0; + int n; + mg_json_get((char *) data, (int) size, "$", &n); + return 0; } diff --git a/test/unit_test.c b/test/unit_test.c index f3b2dbbf..f69f51ec 100644 --- a/test/unit_test.c +++ b/test/unit_test.c @@ -1389,6 +1389,18 @@ static void test_str(void) { ASSERT(sccmp("a1", "A", 49)); ASSERT(sccmp("a", "A1", -49)); + { + int n; + double tolerance = 1e-14; + ASSERT(mg_atod("1.23", 4, &n) == 1.23 && n == 4); + ASSERT(mg_atod("1.23", 3, &n) == 1.2 && n == 3); + ASSERT(mg_atod("1.23", 2, &n) == 1 && n == 2); + ASSERT(mg_atod("1.23 ", 5, &n) - 1.23 < tolerance && n == 4); + ASSERT(mg_atod("-0.01 ", 6, &n) + 0.01 < tolerance); + ASSERT(mg_atod("-0.5e2", 6, &n) + 50 < tolerance); + ASSERT(mg_atod("123e-3", 6, &n) - 0.123 < tolerance); + } + ASSERT(sn("%d", 0)); ASSERT(sn("%d", 1)); ASSERT(sn("%d", -1)); @@ -2171,11 +2183,108 @@ static void test_get_header_var(void) { ASSERT(mg_strcmp(yy, mg_http_get_header_var(header, mg_str("x"))) == 0); } +static void test_json(void) { + const char *s; + const char *s1 = "{\"a\":{},\"b\":7,\"c\":[[],2]}"; + const char *s2 = "{\"a\":{\"b1\":{}},\"c\":7}"; + int n, n1 = (int) strlen(s1), n2 = (int) strlen(s2); + + ASSERT(mg_json_get(" true ", 6, "", &n) == MG_JSON_INVALID); + ASSERT(mg_json_get(" true ", 6, "$", &n) == 1 && n == 4); + ASSERT(mg_json_get("null ", 5, "$", &n) == 0 && n == 4); + s = " \"hi\\nthere\""; + ASSERT(mg_json_get(s, (int) strlen(s), "$", &n) == 2 && n == 11); + ASSERT(mg_json_get(" { } ", 5, "$", &n) == 1); + ASSERT(mg_json_get(" [[]]", 5, "$", &n) == 1); + ASSERT(mg_json_get(" [ ] ", 5, "$", &n) == 1); + + ASSERT(mg_json_get("[1,2]", 5, "$", &n) == 0 && n == 5); + ASSERT(mg_json_get("[1,2]", 5, "$[0]", &n) == 1 && n == 1); + ASSERT(mg_json_get("[1,2]", 5, "$[1]", &n) == 3 && n == 1); + ASSERT(mg_json_get("[1,2]", 5, "$[3]", &n) == MG_JSON_NOT_FOUND); + + s = "{\"a\":[]}"; + ASSERT(mg_json_get(s, (int) strlen(s), "$.a", &n) == 5); + s = "{\"a\":[1,2]}"; + ASSERT(mg_json_get(s, (int) strlen(s), "$.a", &n) == 5); + s = "{\"a\":[1,[1]]}"; + ASSERT(mg_json_get(s, (int) strlen(s), "$.a", &n) == 5); + s = "{\"a\":[[]]}"; + ASSERT(mg_json_get(s, (int) strlen(s), "$.a", &n) == 5); + s = "{\"a\":[[1,2]]}"; + ASSERT(mg_json_get(s, (int) strlen(s), "$.a", &n) == 5); + s = "{\"a\":{}}"; + ASSERT(mg_json_get(s, (int) strlen(s), "$.a", &n) == 5); + s = "{\"a\":{\"a\":{}}}"; + ASSERT(mg_json_get(s, (int) strlen(s), "$.a", &n) == 5); + s = "{\"a\":{\"a\":[]}}"; + ASSERT(mg_json_get(s, (int) strlen(s), "$.a", &n) == 5); + + ASSERT(mg_json_get("[[1,[2,3]],4]", 13, "$", &n) == 0 && n == 13); + ASSERT(mg_json_get("[[1,[2,3]],4]", 13, "$[0]", &n) == 1 && n == 9); + ASSERT(mg_json_get("[[1,[2,3]],4]", 13, "$[1]", &n) == 11); + ASSERT(mg_json_get("[[1,[2,3]],4]", 13, "$[1]", &n) == 11 && n == 1); + ASSERT(mg_json_get("[[1,[2,3]],4]", 13, "$[2]", &n) == MG_JSON_NOT_FOUND); + ASSERT(mg_json_get("[[1,[2,3]],4]", 13, "$[0][0]", &n) == 2 && n == 1); + ASSERT(mg_json_get("[[1,[2,3]],4]", 13, "$[0][1]", &n) == 4 && n == 5); + ASSERT(mg_json_get("[[1,[2,3]],4]", 13, "$[0][2]", &n) == MG_JSON_NOT_FOUND); + ASSERT(mg_json_get("[[1,[2,3]],4]", 13, "$[0][1][0]", &n) == 5 && n == 1); + ASSERT(mg_json_get("[[1,[2,3]],4]", 13, "$[0][1][1]", &n) == 7 && n == 1); + + ASSERT(mg_json_get("[[1,2],3]", 9, "$", &n) == 0 && n == 9); + ASSERT(mg_json_get("[[1,2],3]", 9, "$[0][0]", &n) == 2 && n == 1); + ASSERT(mg_json_get("[[1,2],3]", 9, "$[0][1]", &n) == 4 && n == 1); + ASSERT(mg_json_get("[[1,2],3]", 9, "$[0][2]", &n) == MG_JSON_NOT_FOUND); + ASSERT(mg_json_get("[[1,2],3]", 9, "$[1][0]", &n) == MG_JSON_NOT_FOUND); + ASSERT(mg_json_get("[[1,2],3]", 9, "$[1]", &n) == 7 && n == 1); + ASSERT(mg_json_get("[[1,2],3]", 9, "$[1][0]", &n) == MG_JSON_NOT_FOUND); + + ASSERT(mg_json_get("[1,[2,3]]", 9, "$", &n) == 0 && n == 9); + ASSERT(mg_json_get("[1,[2,3]]", 9, "$[0][1]", &n) == MG_JSON_NOT_FOUND); + ASSERT(mg_json_get("[1,[2,3]]", 9, "$[1][0]", &n) == 4 && n == 1); + + ASSERT(mg_json_get(s1, n1, "$.a", &n) == 5 && n == 2); + ASSERT(mg_json_get(s1, n1, "$.b", &n) == 12 && n == 1); + ASSERT(mg_json_get(s1, n1, "$.c", &n) == 18 && n == 6); + ASSERT(mg_json_get(s1, n1, "$.c[0]", &n) == 19 && n == 2); + ASSERT(mg_json_get(s1, n1, "$.c[1]", &n) == 22 && n == 1); + ASSERT(mg_json_get(s1, n1, "$.c[3]", &n) == MG_JSON_NOT_FOUND); + + ASSERT(mg_json_get(s2, n2, "$.a", &n) == 5 && n == 9); + ASSERT(mg_json_get(s2, n2, "$.a.b1", &n) == 11 && n == 2); + ASSERT(mg_json_get(s2, n2, "$.a.b2", &n) == MG_JSON_NOT_FOUND); + ASSERT(mg_json_get(s2, n2, "$.a.b", &n) == MG_JSON_NOT_FOUND); + ASSERT(mg_json_get(s2, n2, "$.a1", &n) == MG_JSON_NOT_FOUND); + ASSERT(mg_json_get(s2, n2, "$.c", &n) == 19 && n == 1); + + { + double d = 0; + bool b = false; + const char *json = "{\"a\": \"hi\\nthere\",\"b\": [12345, true]}"; + char *str = mg_json_get_str(mg_str(json), "$.a"); + + ASSERT(str != NULL); + ASSERT(strcmp(str, "hi\nthere") == 0); + free(str); + + ASSERT(mg_json_get_num(mg_str(json), "$.a", &d) == false); + ASSERT(mg_json_get_num(mg_str(json), "$.c", &d) == false); + ASSERT(mg_json_get_num(mg_str(json), "$.b[0]", &d) == true); + ASSERT(d == 12345); + + ASSERT(mg_json_get_bool(mg_str(json), "$.b", &b) == false); + ASSERT(mg_json_get_bool(mg_str(json), "$.b[0]", &b) == false); + ASSERT(mg_json_get_bool(mg_str(json), "$.b[1]", &b) == true); + ASSERT(b == true); + } +} + int main(void) { const char *debug_level = getenv("V"); if (debug_level == NULL) debug_level = "3"; mg_log_set(debug_level); + test_json(); test_str(); test_globmatch(); test_get_header_var();