| /******************************************************************************* |
| * Copyright (c) 2007, 2013 Wind River Systems, Inc. and others. |
| * All rights reserved. This program and the accompanying materials |
| * are made available under the terms of the Eclipse Public License v1.0 |
| * and Eclipse Distribution License v1.0 which accompany this distribution. |
| * The Eclipse Public License is available at |
| * http://www.eclipse.org/legal/epl-v10.html |
| * and the Eclipse Distribution License is available at |
| * http://www.eclipse.org/org/documents/edl-v10.php. |
| * You may elect to redistribute this code under either of these licenses. |
| * |
| * Contributors: |
| * Wind River Systems - initial API and implementation |
| * Michael Sills-Lavoie(École Polytechnique de Montréal) - ZeroCopy support |
| * * * - json_splice_binary |
| *******************************************************************************/ |
| |
| /* |
| * This module provides support for JSON - a computer data interchange format. |
| * It is a text-based, human-readable format for representing simple data structures and |
| * associative arrays (called objects). The JSON format is specified in RFC 4627 by Douglas Crockford. |
| * JSON is TCF preffered marshaling format. |
| */ |
| |
| #if defined(__GNUC__) && !defined(_GNU_SOURCE) |
| /* pread() need _GNU_SOURCE */ |
| # define _GNU_SOURCE |
| #endif |
| |
| #include <tcf/config.h> |
| #ifdef ENABLE_STREAM_MACROS |
| #undef ENABLE_STREAM_MACROS |
| #endif |
| #define ENABLE_STREAM_MACROS 1 |
| |
| #include <stdio.h> |
| #include <assert.h> |
| #include <string.h> |
| #include <ctype.h> |
| #include <tcf/framework/json.h> |
| #include <tcf/framework/myalloc.h> |
| #include <tcf/framework/exceptions.h> |
| #include <tcf/framework/base64.h> |
| |
| #include <math.h> |
| #if defined(isfinite) |
| # define is_nan_or_infinity(x) !isfinite(x) |
| #elif defined(_MSC_VER) |
| # include <float.h> |
| # define is_nan_or_infinity(x) !_finite(x) |
| #elif defined(HUGE_VAL) |
| # define is_nan_or_infinity(x) (!(-HUGE_VAL < (x) && (x) < HUGE_VAL)) |
| #else |
| # define is_nan_or_infinity(x) ((x) != (x)) |
| #endif |
| |
| #define ENCODING_BINARY 0 |
| #define ENCODING_BASE64 1 |
| |
| #define ignore_whitespace(ch, inp) do { while (ch > 0 && isspace(ch)) (ch) = read_stream(inp); } while (0) |
| |
| #define read_whitespace(inp) do { int ch = peek_stream(inp); \ |
| while (ch > 0 && isspace(ch)) { read_stream(inp); ch = peek_stream(inp); } } while (0) |
| |
| static char * buf = NULL; |
| static size_t buf_pos = 0; |
| static size_t buf_size = 0; |
| |
| static void realloc_buf(void) { |
| if (buf == NULL) { |
| buf_size = 0x1000; |
| buf = (char *)loc_alloc(buf_size); |
| } |
| else { |
| buf_size *= 2; |
| buf = (char *)loc_realloc(buf, buf_size); |
| } |
| } |
| |
| #define buf_add(ch) { if (buf_pos >= buf_size) realloc_buf(); buf[buf_pos++] = (char)(ch); } |
| |
| void json_write_ulong(OutputStream * out, unsigned long n) { |
| if (n >= 10) { |
| json_write_ulong(out, n / 10); |
| n = n % 10; |
| } |
| write_stream(out, (unsigned int) n + '0'); |
| } |
| |
| void json_write_long(OutputStream * out, long n) { |
| if (n < 0) { |
| write_stream(out, '-'); |
| n = -n; |
| } |
| json_write_ulong(out, (unsigned long)n); |
| } |
| |
| void json_write_uint64(OutputStream * out, uint64_t n) { |
| if (n >= 10) { |
| json_write_uint64(out, n / 10); |
| n = n % 10; |
| } |
| write_stream(out, (int)n + '0'); |
| } |
| |
| void json_write_int64(OutputStream * out, int64_t n) { |
| if (n < 0) { |
| write_stream(out, '-'); |
| n = -n; |
| } |
| json_write_uint64(out, (uint64_t)n); |
| } |
| |
| void json_write_double(OutputStream * out, double n) { |
| if (is_nan_or_infinity(n)) write_string(out, "null"); |
| else write_string(out, double_to_str(n)); |
| } |
| |
| void json_write_boolean(OutputStream * out, int b) { |
| if (b) write_string(out, "true"); |
| else write_string(out, "false"); |
| } |
| |
| static int hex_digit(unsigned n) { |
| n &= 0xf; |
| if (n < 10) return '0' + n; |
| return 'A' + (n - 10); |
| } |
| |
| void json_write_char(OutputStream * out, char ch) { |
| unsigned n = ch & 0xff; |
| if (n < ' ') { |
| write_stream(out, '\\'); |
| write_stream(out, 'u'); |
| write_stream(out, '0'); |
| write_stream(out, '0'); |
| write_stream(out, hex_digit(n >> 4)); |
| write_stream(out, hex_digit(n)); |
| } |
| else { |
| if (n == '"' || n == '\\') write_stream(out, '\\'); |
| write_stream(out, n); |
| } |
| } |
| |
| void json_write_string(OutputStream * out, const char * str) { |
| if (str == NULL) { |
| write_string(out, "null"); |
| } |
| else { |
| write_stream(out, '"'); |
| for (;;) { |
| unsigned char ch = (unsigned char)*str++; |
| while (ch >= ' ') { |
| if (ch == '"' || ch == '\\') write_stream(out, '\\'); |
| if (out->cur < out->end) *out->cur++ = ch; |
| else out->write(out, ch); |
| ch = (unsigned char)*str++; |
| } |
| if (ch == 0) break; |
| json_write_char(out, ch); |
| } |
| write_stream(out, '"'); |
| } |
| } |
| |
| void json_write_string_len(OutputStream * out, const char * str, size_t len) { |
| if (str == NULL) { |
| write_string(out, "null"); |
| } |
| else { |
| const char * end = str + len; |
| write_stream(out, '"'); |
| while (str < end) { |
| unsigned char ch = (unsigned char)*str++; |
| while (ch >= ' ' && str < end) { |
| if (ch == '"' || ch == '\\') write_stream(out, '\\'); |
| if (out->cur < out->end) *out->cur++ = ch; |
| else out->write(out, ch); |
| ch = (unsigned char)*str++; |
| } |
| json_write_char(out, ch); |
| } |
| write_stream(out, '"'); |
| } |
| } |
| |
| static const char * char2str(int ch, char * buf) { |
| if (ch == MARKER_EOS) return "<eos>"; |
| if (ch == MARKER_EOM) return "<eom>"; |
| if (ch == MARKER_EOA) return "<eoa>"; |
| if (ch >= ' ' && ch < 127) sprintf(buf, "'%c'", ch); |
| else sprintf(buf, "'\\%3o'", ch); |
| return buf; |
| } |
| |
| static void check_char(int ch, int exp) { |
| char s0[16], s1[16]; |
| if (exp == ch) return; |
| str_fmt_exception(ERR_JSON_SYNTAX, "Expected %s, got %s", |
| char2str(exp, s0), char2str(ch, s1)); |
| } |
| |
| static int read_hex_digit(InputStream * inp) { |
| int res = 0; |
| int ch = read_stream(inp); |
| if (ch >= '0' && ch <= '9') res = ch - '0'; |
| else if (ch >= 'A' && ch <= 'F') res = ch - 'A' + 10; |
| else if (ch >= 'a' && ch <= 'f') res = ch - 'a' + 10; |
| else exception(ERR_JSON_SYNTAX); |
| return res; |
| } |
| |
| static int read_hex_char(InputStream * inp) { |
| int n = read_hex_digit(inp) << 12; |
| n |= read_hex_digit(inp) << 8; |
| n |= read_hex_digit(inp) << 4; |
| n |= read_hex_digit(inp); |
| return n; |
| } |
| |
| static unsigned read_esc_char(InputStream * inp, char * utf8) { |
| int ch = read_stream(inp); |
| switch (ch) { |
| case '"': break; |
| case '\\': break; |
| case '/': break; |
| case 'b': ch = '\b'; break; |
| case 'f': ch = '\f'; break; |
| case 'n': ch = '\n'; break; |
| case 'r': ch = '\r'; break; |
| case 't': ch = '\t'; break; |
| case 'u': ch = read_hex_char(inp); break; |
| default: exception(ERR_JSON_SYNTAX); break; |
| } |
| /* 'ch' can be wide character - convert it to UTF-8 sequence */ |
| if (ch < 0x80) { |
| utf8[0] = (char)ch; |
| return 1; |
| } |
| if (ch < 0x800) { |
| utf8[0] = (char)((ch >> 6) | 0xc0); |
| utf8[1] = (char)((ch & 0x3f) | 0x80); |
| return 2; |
| } |
| utf8[0] = (char)((ch >> 12) | 0xe0); |
| utf8[1] = (char)(((ch >> 6) & 0x3f) | 0x80); |
| utf8[2] = (char)((ch & 0x3f) | 0x80); |
| return 3; |
| } |
| |
| int json_read_string(InputStream * inp, char * str, size_t size) { |
| unsigned i = 0; |
| int ch = read_stream(inp); |
| ignore_whitespace(ch, inp); |
| if (ch == 'n') { |
| json_test_char(inp, 'u'); |
| json_test_char(inp, 'l'); |
| json_test_char(inp, 'l'); |
| str[0] = 0; |
| return -1; |
| } |
| if (ch != '"') exception(ERR_PROTOCOL); |
| for (;;) { |
| ch = read_stream(inp); |
| if (ch < 0) exception(ERR_JSON_SYNTAX); |
| if (ch == '"') break; |
| if (ch == '\\') { |
| char utf8[4]; |
| unsigned l = read_esc_char(inp, utf8); |
| unsigned n; |
| for (n = 0; n < l; n++, i++) { |
| if (i < size - 1) str[i] = utf8[n]; |
| } |
| } |
| else { |
| if (i < size - 1) str[i] = (char)ch; |
| i++; |
| } |
| } |
| if (i < size) str[i] = 0; |
| else str[size - 1] = 0; |
| return i; |
| } |
| |
| char * json_read_alloc_string(InputStream * inp) { |
| char * str; |
| int ch = read_stream(inp); |
| ignore_whitespace(ch, inp); |
| if (ch == 'n') { |
| json_test_char(inp, 'u'); |
| json_test_char(inp, 'l'); |
| json_test_char(inp, 'l'); |
| return NULL; |
| } |
| buf_pos = 0; |
| if (ch != '"') exception(ERR_PROTOCOL); |
| for (;;) { |
| ch = read_stream(inp); |
| if (ch < 0) exception(ERR_JSON_SYNTAX); |
| if (ch == '"') break; |
| if (ch == '\\') { |
| char utf8[4]; |
| unsigned l = read_esc_char(inp, utf8); |
| unsigned n; |
| for (n = 0; n < l; n++) buf_add(utf8[n]); |
| } |
| else { |
| buf_add(ch); |
| } |
| } |
| buf_add(0); |
| str = (char *)loc_alloc(buf_pos); |
| memcpy(str, buf, buf_pos); |
| return str; |
| } |
| |
| int json_read_boolean(InputStream * inp) { |
| int ch = read_stream(inp); |
| ignore_whitespace(ch, inp); |
| if (ch == 't') { |
| json_test_char(inp, 'r'); |
| json_test_char(inp, 'u'); |
| json_test_char(inp, 'e'); |
| return 1; |
| } |
| if (ch == 'f') { |
| json_test_char(inp, 'a'); |
| json_test_char(inp, 'l'); |
| json_test_char(inp, 's'); |
| json_test_char(inp, 'e'); |
| } |
| else { |
| exception(ERR_JSON_SYNTAX); |
| } |
| return 0; |
| } |
| |
| long json_read_long(InputStream * inp) { |
| long res; |
| int neg = 0; |
| int ch = read_stream(inp); |
| ignore_whitespace(ch, inp); |
| if (ch == '-') { |
| neg = 1; |
| ch = read_stream(inp); |
| } |
| if (ch < '0' || ch > '9') exception(ERR_JSON_SYNTAX); |
| res = ch - '0'; |
| for (;;) { |
| ch = peek_stream(inp); |
| if (ch < '0' || ch > '9') break; |
| read_stream(inp); |
| res = res * 10 + (ch - '0'); |
| } |
| if (neg) return -res; |
| return res; |
| } |
| |
| unsigned long json_read_ulong(InputStream * inp) { |
| unsigned long res; |
| int neg = 0; |
| int ch = read_stream(inp); |
| ignore_whitespace(ch, inp); |
| if (ch == '-') { |
| neg = 1; |
| ch = read_stream(inp); |
| } |
| if (ch < '0' || ch > '9') exception(ERR_JSON_SYNTAX); |
| res = ch - '0'; |
| for (;;) { |
| ch = peek_stream(inp); |
| if (ch < '0' || ch > '9') break; |
| read_stream(inp); |
| res = res * 10 + (ch - '0'); |
| } |
| if (neg) return ~res + 1; |
| return res; |
| } |
| |
| int64_t json_read_int64(InputStream * inp) { |
| int64_t res; |
| int neg = 0; |
| int ch = read_stream(inp); |
| ignore_whitespace(ch, inp); |
| if (ch == '-') { |
| neg = 1; |
| ch = read_stream(inp); |
| } |
| if (ch < '0' || ch > '9') exception(ERR_JSON_SYNTAX); |
| res = ch - '0'; |
| for (;;) { |
| ch = peek_stream(inp); |
| if (ch < '0' || ch > '9') break; |
| read_stream(inp); |
| res = res * 10 + (ch - '0'); |
| } |
| if (neg) return -res; |
| return res; |
| } |
| |
| uint64_t json_read_uint64(InputStream * inp) { |
| uint64_t res; |
| int neg = 0; |
| int ch = read_stream(inp); |
| ignore_whitespace(ch, inp); |
| if (ch == '-') { |
| neg = 1; |
| ch = read_stream(inp); |
| } |
| if (ch < '0' || ch > '9') exception(ERR_JSON_SYNTAX); |
| res = ch - '0'; |
| for (;;) { |
| ch = peek_stream(inp); |
| if (ch < '0' || ch > '9') break; |
| read_stream(inp); |
| res = res * 10 + (ch - '0'); |
| } |
| if (neg) return ~res + 1; |
| return res; |
| } |
| |
| double json_read_double(InputStream * inp) { |
| char buf[256]; |
| int pos = 0; |
| double n; |
| char * end = buf; |
| |
| read_whitespace(inp); |
| for (;;) { |
| int ch = peek_stream(inp); |
| switch (ch) { |
| case '0': |
| case '1': |
| case '2': |
| case '3': |
| case '4': |
| case '5': |
| case '6': |
| case '7': |
| case '8': |
| case '9': |
| case '-': |
| case '+': |
| case 'e': |
| case 'E': |
| case '.': |
| if (pos >= (int)sizeof(buf) - 1) exception(ERR_BUFFER_OVERFLOW); |
| buf[pos++] = (char)read_stream(inp); |
| continue; |
| } |
| break; |
| } |
| if (pos == 0) exception(ERR_JSON_SYNTAX); |
| buf[pos] = 0; |
| n = str_to_double(buf, &end); |
| if (*end != 0) exception(ERR_JSON_SYNTAX); |
| return n; |
| } |
| |
| int json_read_struct(InputStream * inp, JsonStructCallBack * call_back, void * arg) { |
| int ch = read_stream(inp); |
| ignore_whitespace(ch, inp); |
| if (ch == 'n') { |
| json_test_char(inp, 'u'); |
| json_test_char(inp, 'l'); |
| json_test_char(inp, 'l'); |
| return 0; |
| } |
| if (ch != '{') { |
| exception(ERR_PROTOCOL); |
| } |
| else if (json_peek(inp) == '}') { |
| read_stream(inp); |
| } |
| else { |
| for (;;) { |
| char nm[256]; |
| json_read_string(inp, nm, sizeof(nm)); |
| read_whitespace(inp); |
| json_test_char(inp, ':'); |
| call_back(inp, nm, arg); |
| ch = read_stream(inp); |
| ignore_whitespace(ch, inp); |
| if (ch == ',') continue; |
| check_char(ch, '}'); |
| break; |
| } |
| } |
| return 1; |
| } |
| |
| char ** json_read_alloc_string_array(InputStream * inp, int * cnt) { |
| int ch = read_stream(inp); |
| ignore_whitespace(ch, inp); |
| if (ch == 'n') { |
| json_test_char(inp, 'u'); |
| json_test_char(inp, 'l'); |
| json_test_char(inp, 'l'); |
| if (cnt) *cnt = 0; |
| } |
| else if (ch != '[') { |
| exception(ERR_PROTOCOL); |
| } |
| else { |
| static size_t * len_buf = NULL; |
| static unsigned len_buf_size = 0; |
| unsigned len_pos = 0; |
| |
| unsigned i; |
| size_t j; |
| char * str; |
| char ** arr; |
| |
| buf_pos = 0; |
| |
| if (json_peek(inp) == ']') { |
| read_stream(inp); |
| } |
| else { |
| for (;;) { |
| int ch = read_stream(inp); |
| size_t len = 0; |
| if (len_pos >= len_buf_size) { |
| len_buf_size = len_buf_size == 0 ? 0x100 : len_buf_size * 2; |
| len_buf = (size_t *)loc_realloc(len_buf, len_buf_size * sizeof(size_t)); |
| } |
| ignore_whitespace(ch, inp); |
| if (ch == 'n') { |
| json_test_char(inp, 'u'); |
| json_test_char(inp, 'l'); |
| json_test_char(inp, 'l'); |
| } |
| else { |
| size_t buf_pos0 = buf_pos; |
| if (ch != '"') exception(ERR_PROTOCOL); |
| for (;;) { |
| ch = read_stream(inp); |
| if (ch < 0) exception(ERR_JSON_SYNTAX); |
| if (ch == '"') break; |
| if (ch == '\\') { |
| char utf8[4]; |
| unsigned l = read_esc_char(inp, utf8); |
| unsigned n; |
| for (n = 0; n < l; n++) buf_add(utf8[n]); |
| } |
| else { |
| buf_add(ch); |
| } |
| } |
| len = buf_pos - buf_pos0; |
| } |
| buf_add(0); |
| len_buf[len_pos++] = len; |
| ch = read_stream(inp); |
| ignore_whitespace(ch, inp); |
| if (ch == ',') continue; |
| check_char(ch, ']'); |
| break; |
| } |
| } |
| buf_add(0); |
| arr = (char **)loc_alloc((len_pos + 1) * sizeof(char *) + buf_pos); |
| str = (char *)(arr + len_pos + 1); |
| memcpy(str, buf, buf_pos); |
| j = 0; |
| for (i = 0; i < len_pos; i++) { |
| arr[i] = str + j; |
| j += len_buf[i] + 1; |
| } |
| arr[len_pos] = NULL; |
| if (cnt) *cnt = len_pos; |
| return arr; |
| } |
| return NULL; |
| } |
| |
| /* |
| * json_read_array - generic read array function |
| * |
| * This function will call the call_back with inp and arg as |
| * arguments for each element of the list. |
| * Return 0 if null, 1 otherwise |
| */ |
| int json_read_array(InputStream * inp, JsonArrayCallBack * call_back, void * arg) { |
| int ch = read_stream(inp); |
| ignore_whitespace(ch, inp); |
| if (ch == 'n') { |
| json_test_char(inp, 'u'); |
| json_test_char(inp, 'l'); |
| json_test_char(inp, 'l'); |
| return 0; |
| } |
| if (ch != '[') { |
| exception(ERR_PROTOCOL); |
| } |
| else if (json_peek(inp) == ']') { |
| read_stream(inp); |
| } |
| else { |
| for (;;) { |
| call_back(inp, arg); |
| ch = read_stream(inp); |
| ignore_whitespace(ch, inp); |
| if (ch == ',') continue; |
| check_char(ch, ']'); |
| break; |
| } |
| } |
| return 1; |
| } |
| |
| void json_read_binary_start(JsonReadBinaryState * state, InputStream * inp) { |
| int ch = read_stream(inp); |
| state->inp = inp; |
| state->rem = 0; |
| state->size_start = 0; |
| state->size_done = 0; |
| ignore_whitespace(ch, inp); |
| if (ch == '(') { |
| state->encoding = ENCODING_BINARY; |
| state->size_start = json_read_ulong(inp); |
| json_test_char(inp, ')'); |
| } |
| else if (ch == '"') { |
| state->encoding = ENCODING_BASE64; |
| } |
| else if (ch == 'n') { |
| json_test_char(inp, 'u'); |
| json_test_char(inp, 'l'); |
| json_test_char(inp, 'l'); |
| state->encoding = ENCODING_BINARY; |
| state->size_start = 0; |
| } |
| else { |
| exception(ERR_JSON_SYNTAX); |
| } |
| } |
| |
| size_t json_read_binary_data(JsonReadBinaryState * state, void * buf, size_t len) { |
| size_t res = 0; |
| uint8_t * ptr = (uint8_t *)buf; |
| InputStream * inp = state->inp; |
| |
| if (state->encoding == ENCODING_BINARY) { |
| if (len > (size_t)(state->size_start - state->size_done)) len = state->size_start - state->size_done; |
| while (res < len) { |
| if (inp->cur < inp->end) { |
| size_t n = inp->end - inp->cur; |
| if (n > len - res) n = len - res; |
| memcpy(ptr + res, inp->cur, n); |
| inp->cur += n; |
| res += n; |
| } |
| else { |
| ptr[res++] = (uint8_t)inp->read(inp); |
| } |
| } |
| } |
| else { |
| while (len > 0) { |
| assert(state->rem <= sizeof(state->buf)); |
| if (state->rem > 0) { |
| unsigned i = 0; |
| while (i < state->rem && i < len) *ptr++ = state->buf[i++]; |
| len -= i; |
| res += i; |
| if (i < state->rem) { |
| unsigned j = 0; |
| if (state->rem > sizeof(state->buf)) exception(ERR_JSON_SYNTAX); |
| while (i < state->rem) state->buf[j++] = state->buf[i++]; |
| state->rem = j; |
| return res; |
| } |
| state->rem = 0; |
| } |
| if (len >= 3) { |
| size_t i = read_base64(inp, (char *)ptr, len); |
| if (i == 0) break; |
| ptr += i; |
| len -= i; |
| res += i; |
| } |
| else { |
| state->rem = read_base64(inp, state->buf, 3); |
| if (state->rem == 0) break; |
| } |
| } |
| } |
| state->size_done += res; |
| return res; |
| } |
| |
| void json_read_binary_end(JsonReadBinaryState * state) { |
| if (state->rem != 0) exception(ERR_JSON_SYNTAX); |
| if (state->encoding == ENCODING_BINARY) { |
| assert(state->size_start == state->size_done); |
| } |
| else { |
| json_test_char(state->inp, '"'); |
| } |
| } |
| |
| char * json_read_alloc_binary(InputStream * inp, size_t * size) { |
| char * data = NULL; |
| *size = 0; |
| if (json_peek(inp) == 'n') { |
| read_stream(inp); |
| json_test_char(inp, 'u'); |
| json_test_char(inp, 'l'); |
| json_test_char(inp, 'l'); |
| } |
| else { |
| JsonReadBinaryState state; |
| |
| json_read_binary_start(&state, inp); |
| |
| buf_pos = 0; |
| for (;;) { |
| size_t rd; |
| if (buf_pos >= buf_size) realloc_buf(); |
| rd = json_read_binary_data(&state, buf + buf_pos, buf_size - buf_pos); |
| if (rd == 0) break; |
| buf_pos += rd; |
| } |
| |
| assert(state.size_start <= 0 || buf_pos == state.size_start); |
| |
| json_read_binary_end(&state); |
| data = (char *)loc_alloc(buf_pos); |
| memcpy(data, buf, buf_pos); |
| *size = buf_pos; |
| } |
| return data; |
| } |
| |
| void json_write_binary_start(JsonWriteBinaryState * state, OutputStream * out, size_t size) { |
| state->out = out; |
| state->rem = 0; |
| state->encoding = out->supports_zero_copy && size > 0 ? ENCODING_BINARY : ENCODING_BASE64; |
| state->size_start = size; |
| state->size_done = 0; |
| if (state->encoding == ENCODING_BINARY) { |
| write_stream(state->out, '('); |
| json_write_ulong(out, size); |
| write_stream(state->out, ')'); |
| } |
| else { |
| write_stream(state->out, '"'); |
| } |
| } |
| |
| void json_write_binary_data(JsonWriteBinaryState * state, const void * data, size_t len) { |
| if (len <= 0) return; |
| if (state->encoding == (int)ENCODING_BINARY) { |
| write_block_stream(state->out, (const char *)data, len); |
| } |
| else { |
| const uint8_t * ptr = (uint8_t *)data; |
| size_t rem = state->rem; |
| |
| if (rem > 0) { |
| while (rem < 3 && len > 0) { |
| state->buf[rem++] = *ptr++; |
| len--; |
| } |
| assert(rem <= 3); |
| if (rem >= 3) { |
| write_base64(state->out, state->buf, rem); |
| rem = 0; |
| } |
| } |
| if (len > 0) { |
| assert(rem == 0); |
| rem = len % 3; |
| len -= rem; |
| write_base64(state->out, (char *)ptr, len); |
| if (rem > 0) memcpy(state->buf, ptr + len, rem); |
| } |
| state->rem = rem; |
| } |
| state->size_done += len; |
| } |
| |
| void json_write_binary_end(JsonWriteBinaryState * state) { |
| if (state->encoding == ENCODING_BINARY) { |
| assert(state->size_start == state->size_done); |
| } |
| else { |
| size_t rem = state->rem; |
| if (rem > 0) write_base64(state->out, state->buf, rem); |
| write_stream(state->out, '"'); |
| } |
| } |
| |
| void json_write_binary(OutputStream * out, const void * data, size_t size) { |
| if (data == NULL) { |
| write_string(out, "null"); |
| } |
| else { |
| JsonWriteBinaryState state; |
| json_write_binary_start(&state, out, size); |
| json_write_binary_data(&state, data, size); |
| json_write_binary_end(&state); |
| } |
| } |
| |
| void json_splice_binary(OutputStream * out, int fd, size_t size) { |
| json_splice_binary_offset(out, fd, size, NULL); |
| } |
| |
| void json_splice_binary_offset(OutputStream * out, int fd, size_t size, int64_t * offset) { |
| if (out->supports_zero_copy && size > 0) { |
| write_stream(out, '('); |
| json_write_ulong(out, size); |
| write_stream(out, ')'); |
| while (size > 0) { |
| ssize_t rd = splice_block_stream(out, fd, size, offset); |
| if (rd < 0) exception(errno); |
| if (rd == 0) exception(ERR_EOF); |
| size -= rd; |
| } |
| } |
| else { |
| char buffer[0x1000]; |
| JsonWriteBinaryState state; |
| json_write_binary_start(&state, out, size); |
| |
| while (size > 0) { |
| ssize_t rd; |
| if (offset != NULL) { |
| rd = pread(fd, buffer, size < sizeof(buffer) ? size : sizeof(buffer), (off_t)*offset); |
| if (rd > 0) *offset += rd; |
| } |
| else { |
| rd = read(fd, buffer, size < sizeof(buffer) ? size : sizeof(buffer)); |
| } |
| if (rd < 0) exception(errno); |
| if (rd == 0) exception(ERR_EOF); |
| json_write_binary_data(&state, buffer, rd); |
| size -= rd; |
| } |
| json_write_binary_end(&state); |
| } |
| } |
| |
| static int skip_char(InputStream * inp) { |
| int ch = read_stream(inp); |
| if (ch < 0) exception(ERR_JSON_SYNTAX); |
| buf_add(ch); |
| return ch; |
| } |
| |
| static void skip_object(InputStream * inp) { |
| int ch; |
| read_whitespace(inp); |
| ch = skip_char(inp); |
| switch (ch) { |
| case 'n': |
| check_char(skip_char(inp), 'u'); |
| check_char(skip_char(inp), 'l'); |
| check_char(skip_char(inp), 'l'); |
| return; |
| case 'f': |
| check_char(skip_char(inp), 'a'); |
| check_char(skip_char(inp), 'l'); |
| check_char(skip_char(inp), 's'); |
| check_char(skip_char(inp), 'e'); |
| return; |
| case 't': |
| check_char(skip_char(inp), 'r'); |
| check_char(skip_char(inp), 'u'); |
| check_char(skip_char(inp), 'e'); |
| return; |
| case '"': |
| for (;;) { |
| ch = read_stream(inp); |
| if (ch < 0) exception(ERR_JSON_SYNTAX); |
| buf_add(ch); |
| if (ch == '"') break; |
| if (ch == '\\') skip_char(inp); |
| } |
| return; |
| case '-': |
| case '0': |
| case '1': |
| case '2': |
| case '3': |
| case '4': |
| case '5': |
| case '6': |
| case '7': |
| case '8': |
| case '9': |
| for (;;) { |
| ch = peek_stream(inp); |
| if ((ch < '0' || ch > '9') && ch != '.' |
| && ch != 'e' && ch != 'E' && ch != '-' && ch != '+') break; |
| skip_char(inp); |
| } |
| return; |
| case '[': |
| if (json_peek(inp) == ']') { |
| skip_char(inp); |
| } |
| else { |
| for (;;) { |
| int ch; |
| skip_object(inp); |
| read_whitespace(inp); |
| ch = skip_char(inp); |
| if (ch == ',') continue; |
| check_char(ch, ']'); |
| break; |
| } |
| } |
| return; |
| case '{': |
| if (json_peek(inp) == '}') { |
| skip_char(inp); |
| } |
| else { |
| for (;;) { |
| skip_object(inp); |
| read_whitespace(inp); |
| ch = skip_char(inp); |
| check_char(ch, ':'); |
| skip_object(inp); |
| read_whitespace(inp); |
| ch = skip_char(inp); |
| if (ch == ',') continue; |
| check_char(ch, '}'); |
| break; |
| } |
| } |
| return; |
| case '(': |
| { |
| unsigned long size = json_read_ulong(inp); |
| ch = skip_char(inp); |
| check_char(ch, ')'); |
| while (size) { |
| ch = read_stream(inp); |
| if (ch < 0) exception(ERR_JSON_SYNTAX); |
| buf_add(ch); |
| size--; |
| } |
| } |
| return; |
| default: |
| exception(ERR_JSON_SYNTAX); |
| } |
| } |
| |
| char * json_read_object(InputStream * inp) { |
| char * str; |
| buf_pos = 0; |
| skip_object(inp); |
| buf_add(0); |
| str = (char *)loc_alloc(buf_pos); |
| memcpy(str, buf, buf_pos); |
| return str; |
| } |
| |
| void json_skip_object(InputStream * inp) { |
| buf_pos = 0; |
| skip_object(inp); |
| } |
| |
| void json_test_char(InputStream * inp, int x) { |
| int ch = read_stream(inp); |
| if (x != MARKER_EOM) ignore_whitespace(ch, inp); |
| check_char(ch, x); |
| } |
| |
| int json_peek(InputStream * inp) { |
| int ch = peek_stream(inp); |
| while (ch > 0 && isspace(ch)) { |
| read_stream(inp); |
| ch = peek_stream(inp); |
| } |
| return ch; |
| } |
| |
| static void read_errno_param(InputStream * inp, void * x) { |
| ErrorReport * err = (ErrorReport *)x; |
| if (err->param_cnt >= err->param_max) { |
| err->param_max += 4; |
| err->params = (char **)loc_realloc(err->params, err->param_max * sizeof(char *)); |
| } |
| err->params[err->param_cnt++] = json_read_object(inp); |
| } |
| |
| int read_errno(InputStream * inp) { |
| int no = 0; |
| ErrorReport * err = NULL; |
| int ch = read_stream(inp); |
| ignore_whitespace(ch, inp); |
| if (ch == 0) return 0; |
| if (ch == 'n') { |
| json_test_char(inp, 'u'); |
| json_test_char(inp, 'l'); |
| json_test_char(inp, 'l'); |
| json_test_char(inp, MARKER_EOA); |
| return 0; |
| } |
| check_char(ch, '{'); |
| if (json_peek(inp) == '}') { |
| read_stream(inp); |
| } |
| else { |
| for (;;) { |
| char name[256]; |
| json_read_string(inp, name, sizeof(name)); |
| read_whitespace(inp); |
| json_test_char(inp, ':'); |
| if (err == NULL) err = create_error_report(); |
| if (strcmp(name, "Code") == 0) { |
| err->code = (int)json_read_long(inp); |
| } |
| else if (strcmp(name, "Time") == 0) { |
| err->time_stamp = json_read_uint64(inp); |
| } |
| else if (strcmp(name, "Format") == 0) { |
| err->format = json_read_alloc_string(inp); |
| } |
| else if (strcmp(name, "Params") == 0) { |
| json_read_array(inp, read_errno_param, err); |
| } |
| else { |
| ErrorReportItem * i = (ErrorReportItem *)loc_alloc_zero(sizeof(ErrorReportItem)); |
| i->name = loc_strdup(name); |
| i->value = json_read_object(inp); |
| i->next = err->props; |
| err->props = i; |
| } |
| ch = read_stream(inp); |
| ignore_whitespace(ch, inp); |
| if (ch == ',') continue; |
| check_char(ch, '}'); |
| break; |
| } |
| } |
| json_test_char(inp, MARKER_EOA); |
| if (err == NULL) return 0; |
| if (err->code != 0) no = set_error_report_errno(err); |
| release_error_report(err); |
| return no; |
| } |
| |
| static void write_error_props(OutputStream * out, ErrorReport * rep) { |
| ErrorReportItem * i = rep->props; |
| |
| if (rep->time_stamp != 0) { |
| write_stream(out, ','); |
| json_write_string(out, "Time"); |
| write_stream(out, ':'); |
| json_write_uint64(out, rep->time_stamp); |
| } |
| |
| if (rep->format != NULL) { |
| write_stream(out, ','); |
| json_write_string(out, "Format"); |
| write_stream(out, ':'); |
| json_write_string(out, rep->format); |
| } |
| |
| if (rep->param_cnt > 0) { |
| int n; |
| write_stream(out, ','); |
| json_write_string(out, "Params"); |
| write_stream(out, ':'); |
| write_stream(out, '['); |
| for (n = 0; n < rep->param_cnt; n++) { |
| if (n > 0) write_stream(out, ','); |
| write_string(out, rep->params[n]); |
| } |
| write_stream(out, ']'); |
| } |
| |
| while (i != NULL) { |
| write_stream(out, ','); |
| json_write_string(out, i->name); |
| write_stream(out, ':'); |
| write_string(out, i->value); |
| i = i->next; |
| } |
| } |
| |
| void write_error_object(OutputStream * out, int err) { |
| ErrorReport * rep = get_error_report(err); |
| if (rep == NULL) { |
| write_string(out, "null"); |
| } |
| else { |
| write_stream(out, '{'); |
| |
| json_write_string(out, "Code"); |
| write_stream(out, ':'); |
| json_write_long(out, rep->code); |
| |
| write_error_props(out, rep); |
| release_error_report(rep); |
| |
| write_stream(out, '}'); |
| } |
| } |
| |
| void write_errno(OutputStream * out, int err) { |
| if (err != 0) write_error_object(out, err); |
| write_stream(out, 0); |
| } |
| |
| void write_service_error(OutputStream * out, int err, const char * service_name, int service_error) { |
| ErrorReport * rep = get_error_report(err); |
| if (rep != NULL) { |
| write_stream(out, '{'); |
| |
| json_write_string(out, "Service"); |
| write_stream(out, ':'); |
| json_write_string(out, service_name); |
| |
| write_stream(out, ','); |
| |
| json_write_string(out, "Code"); |
| write_stream(out, ':'); |
| json_write_long(out, service_error); |
| |
| write_error_props(out, rep); |
| release_error_report(rep); |
| |
| write_stream(out, '}'); |
| } |
| write_stream(out, 0); |
| } |