mirror of
https://github.com/espressif/esp-idf.git
synced 2024-10-05 20:47:46 -04:00
ab784bb53a
Add files required for DPP feature from upstream. These file expose the functionality to create DPP packets. Ported crypto layer from openssl to mbedtls. Interfacing to use these API will be added in seperate commit
655 lines
14 KiB
C
655 lines
14 KiB
C
/*
|
|
* JavaScript Object Notation (JSON) parser (RFC7159)
|
|
* Copyright (c) 2017, Qualcomm Atheros, Inc.
|
|
*
|
|
* This software may be distributed under the terms of the BSD license.
|
|
* See README for more details.
|
|
*/
|
|
|
|
#include "includes.h"
|
|
|
|
#include "common.h"
|
|
#include "base64.h"
|
|
#include "json.h"
|
|
|
|
#define JSON_MAX_DEPTH 10
|
|
#define JSON_MAX_TOKENS 500
|
|
|
|
|
|
void json_escape_string(char *txt, size_t maxlen, const char *data, size_t len)
|
|
{
|
|
char *end = txt + maxlen;
|
|
size_t i;
|
|
|
|
for (i = 0; i < len; i++) {
|
|
if (txt + 4 >= end)
|
|
break;
|
|
|
|
switch (data[i]) {
|
|
case '\"':
|
|
*txt++ = '\\';
|
|
*txt++ = '\"';
|
|
break;
|
|
case '\\':
|
|
*txt++ = '\\';
|
|
*txt++ = '\\';
|
|
break;
|
|
case '\n':
|
|
*txt++ = '\\';
|
|
*txt++ = 'n';
|
|
break;
|
|
case '\r':
|
|
*txt++ = '\\';
|
|
*txt++ = 'r';
|
|
break;
|
|
case '\t':
|
|
*txt++ = '\\';
|
|
*txt++ = 't';
|
|
break;
|
|
default:
|
|
if (data[i] >= 32 && data[i] <= 126) {
|
|
*txt++ = data[i];
|
|
} else {
|
|
txt += os_snprintf(txt, end - txt, "\\u%04x",
|
|
(unsigned char) data[i]);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
*txt = '\0';
|
|
}
|
|
|
|
|
|
static char * json_parse_string(const char **json_pos, const char *end)
|
|
{
|
|
const char *pos = *json_pos;
|
|
char *str, *spos, *s_end;
|
|
size_t max_len, buf_len;
|
|
u8 bin[2];
|
|
|
|
pos++; /* skip starting quote */
|
|
|
|
max_len = end - pos + 1;
|
|
buf_len = max_len > 10 ? 10 : max_len;
|
|
str = os_malloc(buf_len);
|
|
if (!str)
|
|
return NULL;
|
|
spos = str;
|
|
s_end = str + buf_len;
|
|
|
|
for (; pos < end; pos++) {
|
|
if (buf_len < max_len && s_end - spos < 3) {
|
|
char *tmp;
|
|
int idx;
|
|
|
|
idx = spos - str;
|
|
buf_len *= 2;
|
|
if (buf_len > max_len)
|
|
buf_len = max_len;
|
|
tmp = os_realloc(str, buf_len);
|
|
if (!tmp)
|
|
goto fail;
|
|
str = tmp;
|
|
spos = str + idx;
|
|
s_end = str + buf_len;
|
|
}
|
|
|
|
switch (*pos) {
|
|
case '\"': /* end string */
|
|
*spos = '\0';
|
|
/* caller will move to the next position */
|
|
*json_pos = pos;
|
|
return str;
|
|
case '\\':
|
|
pos++;
|
|
if (pos >= end) {
|
|
wpa_printf(MSG_DEBUG,
|
|
"JSON: Truncated \\ escape");
|
|
goto fail;
|
|
}
|
|
switch (*pos) {
|
|
case '"':
|
|
case '\\':
|
|
case '/':
|
|
*spos++ = *pos;
|
|
break;
|
|
case 'n':
|
|
*spos++ = '\n';
|
|
break;
|
|
case 'r':
|
|
*spos++ = '\r';
|
|
break;
|
|
case 't':
|
|
*spos++ = '\t';
|
|
break;
|
|
case 'u':
|
|
if (end - pos < 5 ||
|
|
hexstr2bin(pos + 1, bin, 2) < 0 ||
|
|
bin[1] == 0x00) {
|
|
wpa_printf(MSG_DEBUG,
|
|
"JSON: Invalid \\u escape");
|
|
goto fail;
|
|
}
|
|
if (bin[0] == 0x00) {
|
|
*spos++ = bin[1];
|
|
} else {
|
|
*spos++ = bin[0];
|
|
*spos++ = bin[1];
|
|
}
|
|
pos += 4;
|
|
break;
|
|
default:
|
|
wpa_printf(MSG_DEBUG,
|
|
"JSON: Unknown escape '%c'", *pos);
|
|
goto fail;
|
|
}
|
|
break;
|
|
default:
|
|
*spos++ = *pos;
|
|
break;
|
|
}
|
|
}
|
|
|
|
fail:
|
|
os_free(str);
|
|
return NULL;
|
|
}
|
|
|
|
|
|
static int json_parse_number(const char **json_pos, const char *end,
|
|
int *ret_val)
|
|
{
|
|
const char *pos = *json_pos;
|
|
size_t len;
|
|
char *str;
|
|
|
|
for (; pos < end; pos++) {
|
|
if (*pos != '-' && (*pos < '0' || *pos > '9')) {
|
|
pos--;
|
|
break;
|
|
}
|
|
}
|
|
if (pos == end)
|
|
pos--;
|
|
if (pos < *json_pos)
|
|
return -1;
|
|
len = pos - *json_pos + 1;
|
|
str = os_malloc(len + 1);
|
|
if (!str)
|
|
return -1;
|
|
os_memcpy(str, *json_pos, len);
|
|
str[len] = '\0';
|
|
|
|
*ret_val = atoi(str);
|
|
os_free(str);
|
|
*json_pos = pos;
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int json_check_tree_state(struct json_token *token)
|
|
{
|
|
if (!token)
|
|
return 0;
|
|
if (json_check_tree_state(token->child) < 0 ||
|
|
json_check_tree_state(token->sibling) < 0)
|
|
return -1;
|
|
if (token->state != JSON_COMPLETED) {
|
|
wpa_printf(MSG_DEBUG,
|
|
"JSON: Unexpected token state %d (name=%s type=%d)",
|
|
token->state, token->name ? token->name : "N/A",
|
|
token->type);
|
|
return -1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
|
|
static struct json_token * json_alloc_token(unsigned int *tokens)
|
|
{
|
|
(*tokens)++;
|
|
if (*tokens > JSON_MAX_TOKENS) {
|
|
wpa_printf(MSG_DEBUG, "JSON: Maximum token limit exceeded");
|
|
return NULL;
|
|
}
|
|
return os_zalloc(sizeof(struct json_token));
|
|
}
|
|
|
|
|
|
struct json_token * json_parse(const char *data, size_t data_len)
|
|
{
|
|
struct json_token *root = NULL, *curr_token = NULL, *token = NULL;
|
|
const char *pos, *end;
|
|
char *str;
|
|
int num;
|
|
unsigned int depth = 0;
|
|
unsigned int tokens = 0;
|
|
|
|
pos = data;
|
|
end = data + data_len;
|
|
|
|
for (; pos < end; pos++) {
|
|
switch (*pos) {
|
|
case '[': /* start array */
|
|
case '{': /* start object */
|
|
if (!curr_token) {
|
|
token = json_alloc_token(&tokens);
|
|
if (!token)
|
|
goto fail;
|
|
if (!root)
|
|
root = token;
|
|
} else if (curr_token->state == JSON_WAITING_VALUE) {
|
|
token = curr_token;
|
|
} else if (curr_token->parent &&
|
|
curr_token->parent->type == JSON_ARRAY &&
|
|
curr_token->parent->state == JSON_STARTED &&
|
|
curr_token->state == JSON_EMPTY) {
|
|
token = curr_token;
|
|
} else {
|
|
wpa_printf(MSG_DEBUG,
|
|
"JSON: Invalid state for start array/object");
|
|
goto fail;
|
|
}
|
|
depth++;
|
|
if (depth > JSON_MAX_DEPTH) {
|
|
wpa_printf(MSG_DEBUG,
|
|
"JSON: Max depth exceeded");
|
|
goto fail;
|
|
}
|
|
token->type = *pos == '[' ? JSON_ARRAY : JSON_OBJECT;
|
|
token->state = JSON_STARTED;
|
|
token->child = json_alloc_token(&tokens);
|
|
if (!token->child)
|
|
goto fail;
|
|
curr_token = token->child;
|
|
curr_token->parent = token;
|
|
curr_token->state = JSON_EMPTY;
|
|
break;
|
|
case ']': /* end array */
|
|
case '}': /* end object */
|
|
if (!curr_token || !curr_token->parent ||
|
|
curr_token->parent->state != JSON_STARTED) {
|
|
wpa_printf(MSG_DEBUG,
|
|
"JSON: Invalid state for end array/object");
|
|
goto fail;
|
|
}
|
|
depth--;
|
|
curr_token = curr_token->parent;
|
|
if ((*pos == ']' &&
|
|
curr_token->type != JSON_ARRAY) ||
|
|
(*pos == '}' &&
|
|
curr_token->type != JSON_OBJECT)) {
|
|
wpa_printf(MSG_DEBUG,
|
|
"JSON: Array/Object mismatch");
|
|
goto fail;
|
|
}
|
|
if (curr_token->child->state == JSON_EMPTY &&
|
|
!curr_token->child->child &&
|
|
!curr_token->child->sibling) {
|
|
/* Remove pending child token since the
|
|
* array/object was empty. */
|
|
json_free(curr_token->child);
|
|
curr_token->child = NULL;
|
|
}
|
|
curr_token->state = JSON_COMPLETED;
|
|
break;
|
|
case '\"': /* string */
|
|
str = json_parse_string(&pos, end);
|
|
if (!str)
|
|
goto fail;
|
|
if (!curr_token) {
|
|
token = json_alloc_token(&tokens);
|
|
if (!token) {
|
|
os_free(str);
|
|
goto fail;
|
|
}
|
|
token->type = JSON_STRING;
|
|
token->string = str;
|
|
token->state = JSON_COMPLETED;
|
|
} else if (curr_token->parent &&
|
|
curr_token->parent->type == JSON_ARRAY &&
|
|
curr_token->parent->state == JSON_STARTED &&
|
|
curr_token->state == JSON_EMPTY) {
|
|
curr_token->string = str;
|
|
curr_token->state = JSON_COMPLETED;
|
|
curr_token->type = JSON_STRING;
|
|
wpa_printf(MSG_MSGDUMP,
|
|
"JSON: String value: '%s'",
|
|
curr_token->string);
|
|
} else if (curr_token->state == JSON_EMPTY) {
|
|
curr_token->type = JSON_VALUE;
|
|
curr_token->name = str;
|
|
curr_token->state = JSON_STARTED;
|
|
} else if (curr_token->state == JSON_WAITING_VALUE) {
|
|
curr_token->string = str;
|
|
curr_token->state = JSON_COMPLETED;
|
|
curr_token->type = JSON_STRING;
|
|
wpa_printf(MSG_MSGDUMP,
|
|
"JSON: String value: '%s' = '%s'",
|
|
curr_token->name,
|
|
curr_token->string);
|
|
} else {
|
|
wpa_printf(MSG_DEBUG,
|
|
"JSON: Invalid state for a string");
|
|
os_free(str);
|
|
goto fail;
|
|
}
|
|
break;
|
|
case ' ':
|
|
case '\t':
|
|
case '\r':
|
|
case '\n':
|
|
/* ignore whitespace */
|
|
break;
|
|
case ':': /* name/value separator */
|
|
if (!curr_token || curr_token->state != JSON_STARTED)
|
|
goto fail;
|
|
curr_token->state = JSON_WAITING_VALUE;
|
|
break;
|
|
case ',': /* member separator */
|
|
if (!curr_token)
|
|
goto fail;
|
|
curr_token->sibling = json_alloc_token(&tokens);
|
|
if (!curr_token->sibling)
|
|
goto fail;
|
|
curr_token->sibling->parent = curr_token->parent;
|
|
curr_token = curr_token->sibling;
|
|
curr_token->state = JSON_EMPTY;
|
|
break;
|
|
case 't': /* true */
|
|
case 'f': /* false */
|
|
case 'n': /* null */
|
|
if (!((end - pos >= 4 &&
|
|
os_strncmp(pos, "true", 4) == 0) ||
|
|
(end - pos >= 5 &&
|
|
os_strncmp(pos, "false", 5) == 0) ||
|
|
(end - pos >= 4 &&
|
|
os_strncmp(pos, "null", 4) == 0))) {
|
|
wpa_printf(MSG_DEBUG,
|
|
"JSON: Invalid literal name");
|
|
goto fail;
|
|
}
|
|
if (!curr_token) {
|
|
token = json_alloc_token(&tokens);
|
|
if (!token)
|
|
goto fail;
|
|
curr_token = token;
|
|
} else if (curr_token->state == JSON_WAITING_VALUE) {
|
|
wpa_printf(MSG_MSGDUMP,
|
|
"JSON: Literal name: '%s' = %c",
|
|
curr_token->name, *pos);
|
|
} else if (curr_token->parent &&
|
|
curr_token->parent->type == JSON_ARRAY &&
|
|
curr_token->parent->state == JSON_STARTED &&
|
|
curr_token->state == JSON_EMPTY) {
|
|
wpa_printf(MSG_MSGDUMP,
|
|
"JSON: Literal name: %c", *pos);
|
|
} else {
|
|
wpa_printf(MSG_DEBUG,
|
|
"JSON: Invalid state for a literal name");
|
|
goto fail;
|
|
}
|
|
switch (*pos) {
|
|
case 't':
|
|
curr_token->type = JSON_BOOLEAN;
|
|
curr_token->number = 1;
|
|
pos += 3;
|
|
break;
|
|
case 'f':
|
|
curr_token->type = JSON_BOOLEAN;
|
|
curr_token->number = 0;
|
|
pos += 4;
|
|
break;
|
|
case 'n':
|
|
curr_token->type = JSON_NULL;
|
|
pos += 3;
|
|
break;
|
|
}
|
|
curr_token->state = JSON_COMPLETED;
|
|
break;
|
|
case '-':
|
|
case '0':
|
|
case '1':
|
|
case '2':
|
|
case '3':
|
|
case '4':
|
|
case '5':
|
|
case '6':
|
|
case '7':
|
|
case '8':
|
|
case '9':
|
|
/* number */
|
|
if (json_parse_number(&pos, end, &num) < 0)
|
|
goto fail;
|
|
if (!curr_token) {
|
|
token = json_alloc_token(&tokens);
|
|
if (!token)
|
|
goto fail;
|
|
token->type = JSON_NUMBER;
|
|
token->number = num;
|
|
token->state = JSON_COMPLETED;
|
|
} else if (curr_token->state == JSON_WAITING_VALUE) {
|
|
curr_token->number = num;
|
|
curr_token->state = JSON_COMPLETED;
|
|
curr_token->type = JSON_NUMBER;
|
|
wpa_printf(MSG_MSGDUMP,
|
|
"JSON: Number value: '%s' = '%d'",
|
|
curr_token->name,
|
|
curr_token->number);
|
|
} else if (curr_token->parent &&
|
|
curr_token->parent->type == JSON_ARRAY &&
|
|
curr_token->parent->state == JSON_STARTED &&
|
|
curr_token->state == JSON_EMPTY) {
|
|
curr_token->number = num;
|
|
curr_token->state = JSON_COMPLETED;
|
|
curr_token->type = JSON_NUMBER;
|
|
wpa_printf(MSG_MSGDUMP,
|
|
"JSON: Number value: %d",
|
|
curr_token->number);
|
|
} else {
|
|
wpa_printf(MSG_DEBUG,
|
|
"JSON: Invalid state for a number");
|
|
goto fail;
|
|
}
|
|
break;
|
|
default:
|
|
wpa_printf(MSG_DEBUG,
|
|
"JSON: Unexpected JSON character: %c", *pos);
|
|
goto fail;
|
|
}
|
|
|
|
if (!root)
|
|
root = token;
|
|
if (!curr_token)
|
|
curr_token = token;
|
|
}
|
|
|
|
if (json_check_tree_state(root) < 0) {
|
|
wpa_printf(MSG_DEBUG, "JSON: Incomplete token in the tree");
|
|
goto fail;
|
|
}
|
|
|
|
return root;
|
|
fail:
|
|
wpa_printf(MSG_DEBUG, "JSON: Parsing failed");
|
|
json_free(root);
|
|
return NULL;
|
|
}
|
|
|
|
|
|
void json_free(struct json_token *json)
|
|
{
|
|
if (!json)
|
|
return;
|
|
json_free(json->child);
|
|
json_free(json->sibling);
|
|
os_free(json->name);
|
|
os_free(json->string);
|
|
os_free(json);
|
|
}
|
|
|
|
|
|
struct json_token * json_get_member(struct json_token *json, const char *name)
|
|
{
|
|
struct json_token *token, *ret = NULL;
|
|
|
|
if (!json || json->type != JSON_OBJECT)
|
|
return NULL;
|
|
/* Return last matching entry */
|
|
for (token = json->child; token; token = token->sibling) {
|
|
if (token->name && os_strcmp(token->name, name) == 0)
|
|
ret = token;
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
|
|
struct wpabuf * json_get_member_base64url(struct json_token *json,
|
|
const char *name)
|
|
{
|
|
struct json_token *token;
|
|
unsigned char *buf;
|
|
size_t buflen;
|
|
struct wpabuf *ret;
|
|
|
|
token = json_get_member(json, name);
|
|
if (!token || token->type != JSON_STRING)
|
|
return NULL;
|
|
buf = base64_url_decode(token->string, os_strlen(token->string),
|
|
&buflen);
|
|
if (!buf)
|
|
return NULL;
|
|
ret = wpabuf_alloc_ext_data(buf, buflen);
|
|
if (!ret)
|
|
os_free(buf);
|
|
|
|
return ret;
|
|
}
|
|
|
|
|
|
static const char * json_type_str(enum json_type type)
|
|
{
|
|
switch (type) {
|
|
case JSON_VALUE:
|
|
return "VALUE";
|
|
case JSON_OBJECT:
|
|
return "OBJECT";
|
|
case JSON_ARRAY:
|
|
return "ARRAY";
|
|
case JSON_STRING:
|
|
return "STRING";
|
|
case JSON_NUMBER:
|
|
return "NUMBER";
|
|
case JSON_BOOLEAN:
|
|
return "BOOLEAN";
|
|
case JSON_NULL:
|
|
return "NULL";
|
|
}
|
|
return "??";
|
|
}
|
|
|
|
|
|
static void json_print_token(struct json_token *token, int depth,
|
|
char *buf, size_t buflen)
|
|
{
|
|
size_t len;
|
|
int ret;
|
|
|
|
if (!token)
|
|
return;
|
|
len = os_strlen(buf);
|
|
ret = os_snprintf(buf + len, buflen - len, "[%d:%s:%s]",
|
|
depth, json_type_str(token->type),
|
|
token->name ? token->name : "");
|
|
if (os_snprintf_error(buflen - len, ret)) {
|
|
buf[len] = '\0';
|
|
return;
|
|
}
|
|
json_print_token(token->child, depth + 1, buf, buflen);
|
|
json_print_token(token->sibling, depth, buf, buflen);
|
|
}
|
|
|
|
|
|
void json_print_tree(struct json_token *root, char *buf, size_t buflen)
|
|
{
|
|
buf[0] = '\0';
|
|
json_print_token(root, 1, buf, buflen);
|
|
}
|
|
|
|
|
|
void json_add_int(struct wpabuf *json, const char *name, int val)
|
|
{
|
|
wpabuf_printf(json, "\"%s\":%d", name, val);
|
|
}
|
|
|
|
|
|
void json_add_string(struct wpabuf *json, const char *name, const char *val)
|
|
{
|
|
wpabuf_printf(json, "\"%s\":\"%s\"", name, val);
|
|
}
|
|
|
|
|
|
int json_add_string_escape(struct wpabuf *json, const char *name,
|
|
const void *val, size_t len)
|
|
{
|
|
char *tmp;
|
|
size_t tmp_len = 6 * len + 1;
|
|
|
|
tmp = os_malloc(tmp_len);
|
|
if (!tmp)
|
|
return -1;
|
|
json_escape_string(tmp, tmp_len, val, len);
|
|
json_add_string(json, name, tmp);
|
|
bin_clear_free(tmp, tmp_len);
|
|
return 0;
|
|
}
|
|
|
|
|
|
int json_add_base64url(struct wpabuf *json, const char *name, const void *val,
|
|
size_t len)
|
|
{
|
|
char *b64;
|
|
|
|
b64 = base64_url_encode(val, len, NULL);
|
|
if (!b64)
|
|
return -1;
|
|
json_add_string(json, name, b64);
|
|
os_free(b64);
|
|
return 0;
|
|
}
|
|
|
|
|
|
void json_start_object(struct wpabuf *json, const char *name)
|
|
{
|
|
if (name)
|
|
wpabuf_printf(json, "\"%s\":", name);
|
|
wpabuf_put_u8(json, '{');
|
|
}
|
|
|
|
|
|
void json_end_object(struct wpabuf *json)
|
|
{
|
|
wpabuf_put_u8(json, '}');
|
|
}
|
|
|
|
|
|
void json_start_array(struct wpabuf *json, const char *name)
|
|
{
|
|
if (name)
|
|
wpabuf_printf(json, "\"%s\":", name);
|
|
wpabuf_put_u8(json, '[');
|
|
}
|
|
|
|
|
|
void json_end_array(struct wpabuf *json)
|
|
{
|
|
wpabuf_put_u8(json, ']');
|
|
}
|
|
|
|
|
|
void json_value_sep(struct wpabuf *json)
|
|
{
|
|
wpabuf_put_u8(json, ',');
|
|
}
|