561 lines
14 KiB
C
561 lines
14 KiB
C
#include <math.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
#include "../vendor/jsmn.h"
|
|
|
|
#include "sentry_alloc.h"
|
|
#include "sentry_json.h"
|
|
#include "sentry_string.h"
|
|
#include "sentry_utils.h"
|
|
#include "sentry_value.h"
|
|
|
|
struct sentry_jsonwriter_s {
|
|
sentry_stringbuilder_t *sb;
|
|
uint64_t want_comma;
|
|
uint32_t depth;
|
|
bool last_was_key;
|
|
bool owns_sb;
|
|
};
|
|
|
|
sentry_jsonwriter_t *
|
|
sentry__jsonwriter_new(sentry_stringbuilder_t *sb)
|
|
{
|
|
bool owns_sb = false;
|
|
if (!sb) {
|
|
sb = SENTRY_MAKE(sentry_stringbuilder_t);
|
|
owns_sb = true;
|
|
sentry__stringbuilder_init(sb);
|
|
}
|
|
if (!sb) {
|
|
return NULL;
|
|
}
|
|
sentry_jsonwriter_t *rv = SENTRY_MAKE(sentry_jsonwriter_t);
|
|
if (!rv) {
|
|
return NULL;
|
|
}
|
|
|
|
rv->sb = sb;
|
|
rv->want_comma = 0;
|
|
rv->depth = 0;
|
|
rv->last_was_key = 0;
|
|
rv->owns_sb = owns_sb;
|
|
return rv;
|
|
}
|
|
|
|
void
|
|
sentry__jsonwriter_free(sentry_jsonwriter_t *jw)
|
|
{
|
|
if (!jw) {
|
|
return;
|
|
}
|
|
if (jw->owns_sb) {
|
|
sentry__stringbuilder_cleanup(jw->sb);
|
|
sentry_free(jw->sb);
|
|
}
|
|
sentry_free(jw);
|
|
}
|
|
|
|
char *
|
|
sentry__jsonwriter_into_string(sentry_jsonwriter_t *jw, size_t *len_out)
|
|
{
|
|
char *rv = NULL;
|
|
sentry_stringbuilder_t *sb = jw->sb;
|
|
if (len_out) {
|
|
*len_out = sb->len;
|
|
}
|
|
rv = sentry__stringbuilder_into_string(sb);
|
|
sentry__jsonwriter_free(jw);
|
|
return rv;
|
|
}
|
|
|
|
static bool
|
|
at_max_depth(const sentry_jsonwriter_t *jw)
|
|
{
|
|
return jw->depth >= 64;
|
|
}
|
|
|
|
static void
|
|
set_comma(sentry_jsonwriter_t *jw, bool val)
|
|
{
|
|
if (at_max_depth(jw)) {
|
|
return;
|
|
}
|
|
if (val) {
|
|
jw->want_comma |= 1ULL << jw->depth;
|
|
} else {
|
|
jw->want_comma &= ~(1ULL << jw->depth);
|
|
}
|
|
}
|
|
|
|
static void
|
|
write_char(sentry_jsonwriter_t *jw, char c)
|
|
{
|
|
sentry__stringbuilder_append_char(jw->sb, c);
|
|
}
|
|
|
|
static void
|
|
write_str(sentry_jsonwriter_t *jw, const char *str)
|
|
{
|
|
sentry__stringbuilder_append(jw->sb, str);
|
|
}
|
|
|
|
// The Lookup table and algorithm below are adapted from:
|
|
// https://github.com/serde-rs/json/blob/977975ee650829a1f3c232cd5f641a7011bdce1d/src/ser.rs#L2079-L2145
|
|
|
|
// Lookup table of escape sequences. `0` means no need to escape, and `1` means
|
|
// that escaping is needed.
|
|
static unsigned char needs_escaping[256] = {
|
|
// 1 2 3 4 5 6 7 8 9 A B C D E F
|
|
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0
|
|
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 1
|
|
0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 2
|
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 3
|
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 4
|
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, // 5
|
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 6
|
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 7
|
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 8
|
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 9
|
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // A
|
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // B
|
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // C
|
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // D
|
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // E
|
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // F
|
|
};
|
|
|
|
static void
|
|
write_json_str(sentry_jsonwriter_t *jw, const char *str)
|
|
{
|
|
// using unsigned here because utf-8 is > 127 :-)
|
|
const unsigned char *ptr = (const unsigned char *)str;
|
|
write_char(jw, '"');
|
|
|
|
const unsigned char *start = ptr;
|
|
for (; *ptr; ptr++) {
|
|
if (!needs_escaping[*ptr]) {
|
|
continue;
|
|
}
|
|
|
|
size_t len = ptr - start;
|
|
if (len) {
|
|
sentry__stringbuilder_append_buf(jw->sb, (const char *)start, len);
|
|
}
|
|
|
|
switch (*ptr) {
|
|
case '\\':
|
|
write_str(jw, "\\\\");
|
|
break;
|
|
case '"':
|
|
write_str(jw, "\\\"");
|
|
break;
|
|
case '\b':
|
|
write_str(jw, "\\b");
|
|
break;
|
|
case '\f':
|
|
write_str(jw, "\\f");
|
|
break;
|
|
case '\n':
|
|
write_str(jw, "\\n");
|
|
break;
|
|
case '\r':
|
|
write_str(jw, "\\r");
|
|
break;
|
|
case '\t':
|
|
write_str(jw, "\\t");
|
|
break;
|
|
default:
|
|
// See https://tools.ietf.org/html/rfc8259#section-7
|
|
// We only need to escape the control characters, otherwise we
|
|
// assume that `str` is valid utf-8
|
|
if (*ptr < 32) {
|
|
char buf[10];
|
|
snprintf(buf, sizeof(buf), "\\u%04x", *ptr);
|
|
write_str(jw, buf);
|
|
} else {
|
|
write_char(jw, *ptr);
|
|
}
|
|
}
|
|
|
|
start = ptr + 1;
|
|
}
|
|
|
|
size_t len = ptr - start;
|
|
if (len) {
|
|
sentry__stringbuilder_append_buf(jw->sb, (const char *)start, len);
|
|
}
|
|
|
|
write_char(jw, '"');
|
|
}
|
|
|
|
static bool
|
|
can_write_item(sentry_jsonwriter_t *jw)
|
|
{
|
|
if (at_max_depth(jw)) {
|
|
return false;
|
|
}
|
|
if (jw->last_was_key) {
|
|
jw->last_was_key = false;
|
|
return true;
|
|
}
|
|
if ((jw->want_comma >> jw->depth) & 1) {
|
|
write_char(jw, ',');
|
|
} else {
|
|
set_comma(jw, true);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void
|
|
sentry__jsonwriter_write_null(sentry_jsonwriter_t *jw)
|
|
{
|
|
if (can_write_item(jw)) {
|
|
write_str(jw, "null");
|
|
}
|
|
}
|
|
|
|
void
|
|
sentry__jsonwriter_write_bool(sentry_jsonwriter_t *jw, bool val)
|
|
{
|
|
if (can_write_item(jw)) {
|
|
write_str(jw, val ? "true" : "false");
|
|
}
|
|
}
|
|
|
|
void
|
|
sentry__jsonwriter_write_int32(sentry_jsonwriter_t *jw, int32_t val)
|
|
{
|
|
if (can_write_item(jw)) {
|
|
char buf[16];
|
|
snprintf(buf, sizeof(buf), "%" PRId32, val);
|
|
write_str(jw, buf);
|
|
}
|
|
}
|
|
|
|
void
|
|
sentry__jsonwriter_write_double(sentry_jsonwriter_t *jw, double val)
|
|
{
|
|
if (can_write_item(jw)) {
|
|
char buf[24];
|
|
// The MAX_SAFE_INTEGER is 9007199254740991, which has 16 digits
|
|
int written = sentry__snprintf_c(buf, sizeof(buf), "%.16g", val);
|
|
// print `null` if we have printf issues or a non-finite double, which
|
|
// can't be represented in JSON.
|
|
if (written < 0 || written >= (int)sizeof(buf) || !isfinite(val)) {
|
|
write_str(jw, "null");
|
|
} else {
|
|
buf[written] = '\0';
|
|
write_str(jw, buf);
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
sentry__jsonwriter_write_str(sentry_jsonwriter_t *jw, const char *val)
|
|
{
|
|
if (!val) {
|
|
sentry__jsonwriter_write_null(jw);
|
|
return;
|
|
}
|
|
if (can_write_item(jw)) {
|
|
write_json_str(jw, val);
|
|
}
|
|
}
|
|
|
|
void
|
|
sentry__jsonwriter_write_uuid(
|
|
sentry_jsonwriter_t *jw, const sentry_uuid_t *uuid)
|
|
{
|
|
if (!uuid) {
|
|
sentry__jsonwriter_write_null(jw);
|
|
return;
|
|
}
|
|
char buf[37];
|
|
sentry_uuid_as_string(uuid, buf);
|
|
sentry__jsonwriter_write_str(jw, buf);
|
|
}
|
|
|
|
void
|
|
sentry__jsonwriter_write_msec_timestamp(sentry_jsonwriter_t *jw, uint64_t time)
|
|
{
|
|
char *formatted = sentry__msec_time_to_iso8601(time);
|
|
sentry__jsonwriter_write_str(jw, formatted);
|
|
sentry_free(formatted);
|
|
}
|
|
|
|
void
|
|
sentry__jsonwriter_write_key(sentry_jsonwriter_t *jw, const char *val)
|
|
{
|
|
if (can_write_item(jw)) {
|
|
write_json_str(jw, val);
|
|
write_char(jw, ':');
|
|
jw->last_was_key = true;
|
|
}
|
|
}
|
|
|
|
void
|
|
sentry__jsonwriter_write_list_start(sentry_jsonwriter_t *jw)
|
|
{
|
|
if (can_write_item(jw)) {
|
|
write_char(jw, '[');
|
|
}
|
|
jw->depth += 1;
|
|
set_comma(jw, false);
|
|
}
|
|
|
|
void
|
|
sentry__jsonwriter_write_list_end(sentry_jsonwriter_t *jw)
|
|
{
|
|
jw->depth -= 1;
|
|
if (!at_max_depth(jw)) {
|
|
write_char(jw, ']');
|
|
}
|
|
}
|
|
|
|
void
|
|
sentry__jsonwriter_write_object_start(sentry_jsonwriter_t *jw)
|
|
{
|
|
if (can_write_item(jw)) {
|
|
write_char(jw, '{');
|
|
}
|
|
jw->depth += 1;
|
|
set_comma(jw, false);
|
|
}
|
|
|
|
void
|
|
sentry__jsonwriter_write_object_end(sentry_jsonwriter_t *jw)
|
|
{
|
|
jw->depth -= 1;
|
|
if (!at_max_depth(jw)) {
|
|
write_char(jw, '}');
|
|
}
|
|
}
|
|
|
|
static int32_t
|
|
read_escaped_unicode_char(const char *buf)
|
|
{
|
|
size_t i;
|
|
int32_t uchar = 0;
|
|
|
|
for (i = 0; i < 4; i++) {
|
|
char c = *buf++;
|
|
uchar <<= 4;
|
|
if (c >= '0' && c <= '9') {
|
|
uchar |= c - '0';
|
|
} else if (c >= 'a' && c <= 'f') {
|
|
uchar |= c - 'a' + 10;
|
|
} else if (c >= 'A' && c <= 'F') {
|
|
uchar |= c - 'A' + 10;
|
|
} else {
|
|
return (int32_t)-1;
|
|
}
|
|
}
|
|
|
|
return uchar;
|
|
}
|
|
|
|
static bool
|
|
decode_string_inplace(char *buf)
|
|
{
|
|
const char *input = buf;
|
|
char *output = buf;
|
|
|
|
#define SIMPLE_ESCAPE(Char, Rep) \
|
|
case Char: \
|
|
*output++ = Rep; \
|
|
break
|
|
|
|
while (*input) {
|
|
char c = *input++;
|
|
if (c != '\\') {
|
|
*output++ = c;
|
|
continue;
|
|
}
|
|
switch (*input++) {
|
|
SIMPLE_ESCAPE('"', '"');
|
|
SIMPLE_ESCAPE('\\', '\\');
|
|
SIMPLE_ESCAPE('/', '/');
|
|
SIMPLE_ESCAPE('b', '\b');
|
|
SIMPLE_ESCAPE('f', '\f');
|
|
SIMPLE_ESCAPE('n', '\n');
|
|
SIMPLE_ESCAPE('r', '\r');
|
|
SIMPLE_ESCAPE('t', '\t');
|
|
case 'u': {
|
|
int32_t uchar = read_escaped_unicode_char(input);
|
|
if (uchar == (int32_t)-1) {
|
|
return false;
|
|
}
|
|
input += 4;
|
|
|
|
if (sentry__is_lead_surrogate(uchar)) {
|
|
uint16_t lead = (uint16_t)uchar;
|
|
if (input[0] != '\\' || input[1] != 'u') {
|
|
return false;
|
|
}
|
|
input += 2;
|
|
int32_t trail = read_escaped_unicode_char(input);
|
|
if (trail == (int32_t)-1
|
|
|| !sentry__is_trail_surrogate(trail)) {
|
|
return false;
|
|
}
|
|
input += 4;
|
|
uchar = sentry__surrogate_value(lead, trail);
|
|
} else if (sentry__is_trail_surrogate(uchar)) {
|
|
return false;
|
|
}
|
|
|
|
if (uchar) {
|
|
output += sentry__unichar_to_utf8(uchar, output);
|
|
}
|
|
break;
|
|
}
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
#undef SIMPLE_ESCAPE
|
|
|
|
*output = 0;
|
|
return true;
|
|
}
|
|
|
|
static size_t
|
|
tokens_to_value(jsmntok_t *tokens, size_t token_count, const char *buf,
|
|
sentry_value_t *value_out)
|
|
{
|
|
size_t offset = 0;
|
|
|
|
#define POP() (offset < token_count ? &tokens[offset++] : NULL)
|
|
#define NESTED_PARSE(Target) \
|
|
do { \
|
|
size_t child_consumed = tokens_to_value( \
|
|
tokens + offset, token_count - offset, buf, Target); \
|
|
if (child_consumed == (size_t)-1) { \
|
|
goto error; \
|
|
} \
|
|
offset += child_consumed; \
|
|
} while (0)
|
|
|
|
jsmntok_t *root = POP();
|
|
sentry_value_t rv = sentry_value_new_null();
|
|
|
|
if (!root) {
|
|
goto error;
|
|
}
|
|
|
|
switch (root->type) {
|
|
case JSMN_PRIMITIVE: {
|
|
switch (buf[root->start]) {
|
|
case 't':
|
|
rv = sentry_value_new_bool(true);
|
|
break;
|
|
case 'f':
|
|
rv = sentry_value_new_bool(false);
|
|
break;
|
|
case 'n':
|
|
rv = sentry_value_new_null();
|
|
break;
|
|
default: {
|
|
double val = sentry__strtod_c(buf + root->start, NULL);
|
|
if (val == (double)(int32_t)val) {
|
|
rv = sentry_value_new_int32((int32_t)val);
|
|
} else {
|
|
rv = sentry_value_new_double(val);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
case JSMN_STRING: {
|
|
char *string
|
|
= sentry__string_clonen(buf + root->start, root->end - root->start);
|
|
if (decode_string_inplace(string)) {
|
|
rv = sentry__value_new_string_owned(string);
|
|
} else {
|
|
sentry_free(string);
|
|
rv = sentry_value_new_null();
|
|
}
|
|
break;
|
|
}
|
|
case JSMN_OBJECT: {
|
|
rv = sentry_value_new_object();
|
|
for (int i = 0; i < root->size; i++) {
|
|
jsmntok_t *token = POP();
|
|
if (!token || token->type != JSMN_STRING) {
|
|
goto error;
|
|
}
|
|
|
|
sentry_value_t child;
|
|
NESTED_PARSE(&child);
|
|
|
|
char *key = sentry__string_clonen(
|
|
buf + token->start, token->end - token->start);
|
|
if (decode_string_inplace(key)) {
|
|
sentry_value_set_by_key(rv, key, child);
|
|
} else {
|
|
sentry_value_decref(child);
|
|
}
|
|
sentry_free(key);
|
|
}
|
|
break;
|
|
}
|
|
case JSMN_ARRAY: {
|
|
rv = sentry_value_new_list();
|
|
for (int i = 0; i < root->size; i++) {
|
|
sentry_value_t child;
|
|
NESTED_PARSE(&child);
|
|
sentry_value_append(rv, child);
|
|
}
|
|
break;
|
|
}
|
|
case JSMN_UNDEFINED:
|
|
break;
|
|
}
|
|
|
|
#undef POP
|
|
#undef NESTED_PARSE
|
|
|
|
*value_out = rv;
|
|
return offset;
|
|
|
|
error:
|
|
sentry_value_decref(rv);
|
|
return (size_t)-1;
|
|
}
|
|
|
|
sentry_value_t
|
|
sentry__value_from_json(const char *buf, size_t buflen)
|
|
{
|
|
int token_count;
|
|
jsmn_parser jsmn_p;
|
|
|
|
jsmn_init(&jsmn_p);
|
|
token_count = jsmn_parse(&jsmn_p, buf, buflen, NULL, 0);
|
|
if (token_count <= 0) {
|
|
return sentry_value_new_null();
|
|
}
|
|
|
|
jsmntok_t *tokens = sentry_malloc(sizeof(jsmntok_t) * token_count);
|
|
jsmn_init(&jsmn_p);
|
|
token_count = jsmn_parse(&jsmn_p, buf, buflen, tokens, token_count);
|
|
if (token_count <= 0) {
|
|
sentry_free(tokens);
|
|
return sentry_value_new_null();
|
|
}
|
|
|
|
sentry_value_t value_out;
|
|
size_t tokens_consumed
|
|
= tokens_to_value(tokens, (size_t)token_count, buf, &value_out);
|
|
sentry_free(tokens);
|
|
|
|
if (tokens_consumed == (size_t)token_count) {
|
|
return value_out;
|
|
} else {
|
|
return sentry_value_new_null();
|
|
}
|
|
}
|