|
@@ -0,0 +1,541 @@
|
|
|
+/*
|
|
|
+ * 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, "\\ux%04x",
|
|
|
+ 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++;
|
|
|
+ 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 < *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;
|
|
|
+ } 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)
|
|
|
+ goto fail;
|
|
|
+ token->type = JSON_STRING;
|
|
|
+ token->string = str;
|
|
|
+ token->state = JSON_COMPLETED;
|
|
|
+ } 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 {
|
|
|
+ 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 {
|
|
|
+ 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((const unsigned char *) 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);
|
|
|
+}
|