/*
 * 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;
}


struct wpabuf * json_get_member_base64(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_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;
}


int json_add_base64(struct wpabuf *json, const char *name, const void *val,
		    size_t len)
{
	char *b64;

	b64 = base64_encode_no_lf(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, ',');
}
