Browse Source

Add JavaScript Object Notation (JSON) parser (RFC7159)

This is needed for DPP configuration attributes/objects.

Signed-off-by: Jouni Malinen <jouni@qca.qualcomm.com>
Jouni Malinen 7 years ago
parent
commit
005be3daa9
6 changed files with 603 additions and 0 deletions
  1. 5 0
      hostapd/Android.mk
  2. 5 0
      hostapd/Makefile
  3. 541 0
      src/utils/json.c
  4. 42 0
      src/utils/json.h
  5. 5 0
      wpa_supplicant/Android.mk
  6. 5 0
      wpa_supplicant/Makefile

+ 5 - 0
hostapd/Android.mk

@@ -941,6 +941,11 @@ ifdef NEED_BASE64
 OBJS += src/utils/base64.c
 endif
 
+ifdef NEED_JSON
+OBJS += src/utils/json.c
+L_CFLAGS += -DCONFIG_JSON
+endif
+
 ifdef NEED_AP_MLME
 OBJS += src/ap/wmm.c
 OBJS += src/ap/ap_list.c

+ 5 - 0
hostapd/Makefile

@@ -1035,6 +1035,11 @@ ifdef NEED_BASE64
 OBJS += ../src/utils/base64.o
 endif
 
+ifdef NEED_JSON
+OBJS += ../src/utils/json.o
+CFLAGS += -DCONFIG_JSON
+endif
+
 ifdef NEED_AP_MLME
 OBJS += ../src/ap/wmm.o
 OBJS += ../src/ap/ap_list.o

+ 541 - 0
src/utils/json.c

@@ -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);
+}

+ 42 - 0
src/utils/json.h

@@ -0,0 +1,42 @@
+/*
+ * 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.
+ */
+
+#ifndef JSON_H
+#define JSON_H
+
+struct json_token {
+	enum json_type {
+		JSON_VALUE,
+		JSON_OBJECT,
+		JSON_ARRAY,
+		JSON_STRING,
+		JSON_NUMBER,
+		JSON_BOOLEAN,
+		JSON_NULL,
+	} type;
+	enum json_parsing_state {
+		JSON_EMPTY,
+		JSON_STARTED,
+		JSON_WAITING_VALUE,
+		JSON_COMPLETED,
+	} state;
+	char *name;
+	char *string;
+	int number;
+	struct json_token *parent, *child, *sibling;
+};
+
+void json_escape_string(char *txt, size_t maxlen, const char *data, size_t len);
+struct json_token * json_parse(const char *data, size_t data_len);
+void json_free(struct json_token *json);
+struct json_token * json_get_member(struct json_token *json, const char *name);
+struct wpabuf * json_get_member_base64url(struct json_token *json,
+					  const char *name);
+void json_print_tree(struct json_token *root, char *buf, size_t buflen);
+
+#endif /* JSON_H */

+ 5 - 0
wpa_supplicant/Android.mk

@@ -1565,6 +1565,11 @@ OBJS += offchannel.c
 L_CFLAGS += -DCONFIG_OFFCHANNEL
 endif
 
+ifdef NEED_JSON
+OBJS += src/utils/json.c
+L_CFLAGS += -DCONFIG_JSON
+endif
+
 OBJS += src/drivers/driver_common.c
 
 OBJS += wpa_supplicant.c events.c blacklist.c wpas_glue.c scan.c

+ 5 - 0
wpa_supplicant/Makefile

@@ -1691,6 +1691,11 @@ OBJS += offchannel.o
 CFLAGS += -DCONFIG_OFFCHANNEL
 endif
 
+ifdef NEED_JSON
+OBJS += ../src/utils/json.o
+CFLAGS += -DCONFIG_JSON
+endif
+
 ifdef CONFIG_MODULE_TESTS
 CFLAGS += -DCONFIG_MODULE_TESTS
 OBJS += wpas_module_tests.o