| /*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ |
| |
| /*** |
| This file is part of systemd. |
| |
| Copyright 2014 Lennart Poettering |
| |
| systemd is free software; you can redistribute it and/or modify it |
| under the terms of the GNU Lesser General Public License as published by |
| the Free Software Foundation; either version 2.1 of the License, or |
| (at your option) any later version. |
| |
| systemd is distributed in the hope that it will be useful, but |
| WITHOUT ANY WARRANTY; without even the implied warranty of |
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| Lesser General Public License for more details. |
| |
| You should have received a copy of the GNU Lesser General Public License |
| along with systemd; If not, see <http://www.gnu.org/licenses/>. |
| ***/ |
| |
| #include <sys/types.h> |
| #include <math.h> |
| |
| #include "macro.h" |
| #include "log.h" |
| #include "util.h" |
| #include "utf8.h" |
| #include "json.h" |
| |
| enum { |
| STATE_NULL, |
| STATE_VALUE, |
| STATE_VALUE_POST, |
| }; |
| |
| static void inc_lines(unsigned *line, const char *s, size_t n) { |
| const char *p = s; |
| |
| if (!line) |
| return; |
| |
| for (;;) { |
| const char *f; |
| |
| f = memchr(p, '\n', n); |
| if (!f) |
| return; |
| |
| n -= (f - p) + 1; |
| p = f + 1; |
| (*line)++; |
| } |
| } |
| |
| static int unhex_ucs2(const char *c, uint16_t *ret) { |
| int aa, bb, cc, dd; |
| uint16_t x; |
| |
| assert(c); |
| assert(ret); |
| |
| aa = unhexchar(c[0]); |
| if (aa < 0) |
| return -EINVAL; |
| |
| bb = unhexchar(c[1]); |
| if (bb < 0) |
| return -EINVAL; |
| |
| cc = unhexchar(c[2]); |
| if (cc < 0) |
| return -EINVAL; |
| |
| dd = unhexchar(c[3]); |
| if (dd < 0) |
| return -EINVAL; |
| |
| x = ((uint16_t) aa << 12) | |
| ((uint16_t) bb << 8) | |
| ((uint16_t) cc << 4) | |
| ((uint16_t) dd); |
| |
| if (x <= 0) |
| return -EINVAL; |
| |
| *ret = x; |
| |
| return 0; |
| } |
| |
| static int json_parse_string(const char **p, char **ret) { |
| _cleanup_free_ char *s = NULL; |
| size_t n = 0, allocated = 0; |
| const char *c; |
| |
| assert(p); |
| assert(*p); |
| assert(ret); |
| |
| c = *p; |
| |
| if (*c != '"') |
| return -EINVAL; |
| |
| c++; |
| |
| for (;;) { |
| int len; |
| |
| /* Check for EOF */ |
| if (*c == 0) |
| return -EINVAL; |
| |
| /* Check for control characters 0x00..0x1f */ |
| if (*c > 0 && *c < ' ') |
| return -EINVAL; |
| |
| /* Check for control character 0x7f */ |
| if (*c == 0x7f) |
| return -EINVAL; |
| |
| if (*c == '"') { |
| if (!s) { |
| s = strdup(""); |
| if (!s) |
| return -ENOMEM; |
| } else |
| s[n] = 0; |
| |
| *p = c + 1; |
| |
| *ret = s; |
| s = NULL; |
| return JSON_STRING; |
| } |
| |
| if (*c == '\\') { |
| char ch = 0; |
| c++; |
| |
| if (*c == 0) |
| return -EINVAL; |
| |
| if (IN_SET(*c, '"', '\\', '/')) |
| ch = *c; |
| else if (*c == 'b') |
| ch = '\b'; |
| else if (*c == 'f') |
| ch = '\f'; |
| else if (*c == 'n') |
| ch = '\n'; |
| else if (*c == 'r') |
| ch = '\r'; |
| else if (*c == 't') |
| ch = '\t'; |
| else if (*c == 'u') { |
| uint16_t x; |
| int r; |
| |
| r = unhex_ucs2(c + 1, &x); |
| if (r < 0) |
| return r; |
| |
| c += 5; |
| |
| if (!GREEDY_REALLOC(s, allocated, n + 4)) |
| return -ENOMEM; |
| |
| if (!utf16_is_surrogate(x)) |
| n += utf8_encode_unichar(s + n, x); |
| else if (utf16_is_trailing_surrogate(x)) |
| return -EINVAL; |
| else { |
| uint16_t y; |
| |
| if (c[0] != '\\' || c[1] != 'u') |
| return -EINVAL; |
| |
| r = unhex_ucs2(c + 2, &y); |
| if (r < 0) |
| return r; |
| |
| c += 6; |
| |
| if (!utf16_is_trailing_surrogate(y)) |
| return -EINVAL; |
| |
| n += utf8_encode_unichar(s + n, utf16_surrogate_pair_to_unichar(x, y)); |
| } |
| |
| continue; |
| } else |
| return -EINVAL; |
| |
| if (!GREEDY_REALLOC(s, allocated, n + 2)) |
| return -ENOMEM; |
| |
| s[n++] = ch; |
| c ++; |
| continue; |
| } |
| |
| len = utf8_encoded_valid_unichar(c); |
| if (len < 0) |
| return len; |
| |
| if (!GREEDY_REALLOC(s, allocated, n + len + 1)) |
| return -ENOMEM; |
| |
| memcpy(s + n, c, len); |
| n += len; |
| c += len; |
| } |
| } |
| |
| static int json_parse_number(const char **p, union json_value *ret) { |
| bool negative = false, exponent_negative = false, is_double = false; |
| double x = 0.0, y = 0.0, exponent = 0.0, shift = 1.0; |
| intmax_t i = 0; |
| const char *c; |
| |
| assert(p); |
| assert(*p); |
| assert(ret); |
| |
| c = *p; |
| |
| if (*c == '-') { |
| negative = true; |
| c++; |
| } |
| |
| if (*c == '0') |
| c++; |
| else { |
| if (!strchr("123456789", *c) || *c == 0) |
| return -EINVAL; |
| |
| do { |
| if (!is_double) { |
| int64_t t; |
| |
| t = 10 * i + (*c - '0'); |
| if (t < i) /* overflow */ |
| is_double = false; |
| else |
| i = t; |
| } |
| |
| x = 10.0 * x + (*c - '0'); |
| c++; |
| } while (strchr("0123456789", *c) && *c != 0); |
| } |
| |
| if (*c == '.') { |
| is_double = true; |
| c++; |
| |
| if (!strchr("0123456789", *c) || *c == 0) |
| return -EINVAL; |
| |
| do { |
| y = 10.0 * y + (*c - '0'); |
| shift = 10.0 * shift; |
| c++; |
| } while (strchr("0123456789", *c) && *c != 0); |
| } |
| |
| if (*c == 'e' || *c == 'E') { |
| is_double = true; |
| c++; |
| |
| if (*c == '-') { |
| exponent_negative = true; |
| c++; |
| } else if (*c == '+') |
| c++; |
| |
| if (!strchr("0123456789", *c) || *c == 0) |
| return -EINVAL; |
| |
| do { |
| exponent = 10.0 * exponent + (*c - '0'); |
| c++; |
| } while (strchr("0123456789", *c) && *c != 0); |
| } |
| |
| if (*c != 0) |
| return -EINVAL; |
| |
| *p = c; |
| |
| if (is_double) { |
| ret->real = ((negative ? -1.0 : 1.0) * (x + (y / shift))) * exp10((exponent_negative ? -1.0 : 1.0) * exponent); |
| return JSON_REAL; |
| } else { |
| ret->integer = negative ? -i : i; |
| return JSON_INTEGER; |
| } |
| } |
| |
| int json_tokenize( |
| const char **p, |
| char **ret_string, |
| union json_value *ret_value, |
| void **state, |
| unsigned *line) { |
| |
| const char *c; |
| int t; |
| int r; |
| |
| assert(p); |
| assert(*p); |
| assert(ret_string); |
| assert(ret_value); |
| assert(state); |
| |
| t = PTR_TO_INT(*state); |
| c = *p; |
| |
| if (t == STATE_NULL) { |
| if (line) |
| *line = 1; |
| t = STATE_VALUE; |
| } |
| |
| for (;;) { |
| const char *b; |
| |
| b = c + strspn(c, WHITESPACE); |
| if (*b == 0) |
| return JSON_END; |
| |
| inc_lines(line, c, b - c); |
| c = b; |
| |
| switch (t) { |
| |
| case STATE_VALUE: |
| |
| if (*c == '{') { |
| *ret_string = NULL; |
| *ret_value = JSON_VALUE_NULL; |
| *p = c + 1; |
| *state = INT_TO_PTR(STATE_VALUE); |
| return JSON_OBJECT_OPEN; |
| |
| } else if (*c == '}') { |
| *ret_string = NULL; |
| *ret_value = JSON_VALUE_NULL; |
| *p = c + 1; |
| *state = INT_TO_PTR(STATE_VALUE_POST); |
| return JSON_OBJECT_CLOSE; |
| |
| } else if (*c == '[') { |
| *ret_string = NULL; |
| *ret_value = JSON_VALUE_NULL; |
| *p = c + 1; |
| *state = INT_TO_PTR(STATE_VALUE); |
| return JSON_ARRAY_OPEN; |
| |
| } else if (*c == ']') { |
| *ret_string = NULL; |
| *ret_value = JSON_VALUE_NULL; |
| *p = c + 1; |
| *state = INT_TO_PTR(STATE_VALUE_POST); |
| return JSON_ARRAY_CLOSE; |
| |
| } else if (*c == '"') { |
| r = json_parse_string(&c, ret_string); |
| if (r < 0) |
| return r; |
| |
| *ret_value = JSON_VALUE_NULL; |
| *p = c; |
| *state = INT_TO_PTR(STATE_VALUE_POST); |
| return r; |
| |
| } else if (strchr("-0123456789", *c)) { |
| r = json_parse_number(&c, ret_value); |
| if (r < 0) |
| return r; |
| |
| *ret_string = NULL; |
| *p = c; |
| *state = INT_TO_PTR(STATE_VALUE_POST); |
| return r; |
| |
| } else if (startswith(c, "true")) { |
| *ret_string = NULL; |
| ret_value->boolean = true; |
| *p = c + 4; |
| *state = INT_TO_PTR(STATE_VALUE_POST); |
| return JSON_BOOLEAN; |
| |
| } else if (startswith(c, "false")) { |
| *ret_string = NULL; |
| ret_value->boolean = false; |
| *p = c + 5; |
| *state = INT_TO_PTR(STATE_VALUE_POST); |
| return JSON_BOOLEAN; |
| |
| } else if (startswith(c, "null")) { |
| *ret_string = NULL; |
| *ret_value = JSON_VALUE_NULL; |
| *p = c + 4; |
| *state = INT_TO_PTR(STATE_VALUE_POST); |
| return JSON_NULL; |
| |
| } else |
| return -EINVAL; |
| |
| case STATE_VALUE_POST: |
| |
| if (*c == ':') { |
| *ret_string = NULL; |
| *ret_value = JSON_VALUE_NULL; |
| *p = c + 1; |
| *state = INT_TO_PTR(STATE_VALUE); |
| return JSON_COLON; |
| } else if (*c == ',') { |
| *ret_string = NULL; |
| *ret_value = JSON_VALUE_NULL; |
| *p = c + 1; |
| *state = INT_TO_PTR(STATE_VALUE); |
| return JSON_COMMA; |
| } else if (*c == '}') { |
| *ret_string = NULL; |
| *ret_value = JSON_VALUE_NULL; |
| *p = c + 1; |
| *state = INT_TO_PTR(STATE_VALUE_POST); |
| return JSON_OBJECT_CLOSE; |
| } else if (*c == ']') { |
| *ret_string = NULL; |
| *ret_value = JSON_VALUE_NULL; |
| *p = c + 1; |
| *state = INT_TO_PTR(STATE_VALUE_POST); |
| return JSON_ARRAY_CLOSE; |
| } else |
| return -EINVAL; |
| } |
| |
| } |
| } |