diff --git a/hostapd/Android.mk b/hostapd/Android.mk index 50b1e2c5d..d768d0099 100644 --- a/hostapd/Android.mk +++ b/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 diff --git a/hostapd/Makefile b/hostapd/Makefile index b9e89240a..bb4bad337 100644 --- a/hostapd/Makefile +++ b/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 diff --git a/src/utils/json.c b/src/utils/json.c new file mode 100644 index 000000000..3519c4884 --- /dev/null +++ b/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); +} diff --git a/src/utils/json.h b/src/utils/json.h new file mode 100644 index 000000000..8faa95d8b --- /dev/null +++ b/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 */ diff --git a/wpa_supplicant/Android.mk b/wpa_supplicant/Android.mk index 1eb092753..6af5bcf83 100644 --- a/wpa_supplicant/Android.mk +++ b/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 diff --git a/wpa_supplicant/Makefile b/wpa_supplicant/Makefile index f410d925e..f3f9e1f8f 100644 --- a/wpa_supplicant/Makefile +++ b/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