diff src/json.c @ 7967:45ea5ebf3a98 v7.4.1279

commit https://github.com/vim/vim/commit/595e64e259faefb330866852e1b9f6168544572a Author: Bram Moolenaar <Bram@vim.org> Date: Sun Feb 7 19:19:53 2016 +0100 patch 7.4.1279 Problem: jsonencode() is not producing strict JSON. Solution: Add jsencode() and jsdecode(). Make jsonencode() and jsondecode() strict.
author Christian Brabandt <cb@256bit.org>
date Sun, 07 Feb 2016 19:30:05 +0100
parents 646d5148fee2
children c6443e78cf2d
line wrap: on
line diff
--- a/src/json.c
+++ b/src/json.c
@@ -16,22 +16,23 @@
 #include "vim.h"
 
 #if defined(FEAT_EVAL) || defined(PROTO)
-static int json_encode_item(garray_T *gap, typval_T *val, int copyID, int allow_none);
-static int json_decode_item(js_read_T *reader, typval_T *res);
+static int json_encode_item(garray_T *gap, typval_T *val, int copyID, int options);
+static int json_decode_item(js_read_T *reader, typval_T *res, int options);
 
 /*
  * Encode "val" into a JSON format string.
  * The result is in allocated memory.
  * The result is empty when encoding fails.
+ * "options" can be JSON_JS or zero;
  */
     char_u *
-json_encode(typval_T *val)
+json_encode(typval_T *val, int options)
 {
     garray_T ga;
 
     /* Store bytes in the growarray. */
     ga_init2(&ga, 1, 4000);
-    if (json_encode_item(&ga, val, get_copyID(), TRUE) == FAIL)
+    if (json_encode_item(&ga, val, get_copyID(), options) == FAIL)
     {
 	vim_free(ga.ga_data);
 	return vim_strsave((char_u *)"");
@@ -41,10 +42,11 @@ json_encode(typval_T *val)
 
 /*
  * Encode ["nr", "val"] into a JSON format string in allocated memory.
+ * "options" can be JSON_JS or zero;
  * Returns NULL when out of memory.
  */
     char_u *
-json_encode_nr_expr(int nr, typval_T *val)
+json_encode_nr_expr(int nr, typval_T *val, int options)
 {
     typval_T	listtv;
     typval_T	nrtv;
@@ -61,7 +63,7 @@ json_encode_nr_expr(int nr, typval_T *va
 	return NULL;
     }
 
-    text = json_encode(&listtv);
+    text = json_encode(&listtv, options);
     list_unref(listtv.vval.v_list);
     return text;
 }
@@ -123,11 +125,29 @@ write_string(garray_T *gap, char_u *str)
 }
 
 /*
+ * Return TRUE if "key" can be used without quotes.
+ * That is when it starts with a letter and only contains letters, digits and
+ * underscore.
+ */
+    static int
+is_simple_key(char_u *key)
+{
+    char_u *p;
+
+    if (!ASCII_ISALPHA(*key))
+	return FALSE;
+    for (p = key + 1; *p != NUL; ++p)
+	if (!ASCII_ISALPHA(*p) && *p != '_' && !vim_isdigit(*p))
+	    return FALSE;
+    return TRUE;
+}
+
+/*
  * Encode "val" into "gap".
  * Return FAIL or OK.
  */
     static int
-json_encode_item(garray_T *gap, typval_T *val, int copyID, int allow_none)
+json_encode_item(garray_T *gap, typval_T *val, int copyID, int options)
 {
     char_u	numbuf[NUMBUFLEN];
     char_u	*res;
@@ -141,13 +161,11 @@ json_encode_item(garray_T *gap, typval_T
 	    {
 		case VVAL_FALSE: ga_concat(gap, (char_u *)"false"); break;
 		case VVAL_TRUE: ga_concat(gap, (char_u *)"true"); break;
-		case VVAL_NONE: if (!allow_none)
-				{
-				    /* TODO: better error */
-				    EMSG(_(e_invarg));
-				    return FAIL;
-				}
-				break;
+		case VVAL_NONE: if ((options & JSON_JS) != 0
+					     && (options & JSON_NO_NONE) == 0)
+				    /* empty item */
+				    break;
+				/* FALLTHROUGH */
 		case VVAL_NULL: ga_concat(gap, (char_u *)"null"); break;
 	    }
 	    break;
@@ -185,9 +203,15 @@ json_encode_item(garray_T *gap, typval_T
 		    ga_append(gap, '[');
 		    for (li = l->lv_first; li != NULL && !got_int; )
 		    {
-			if (json_encode_item(gap, &li->li_tv, copyID, TRUE)
-								      == FAIL)
+			if (json_encode_item(gap, &li->li_tv, copyID,
+						   options & JSON_JS) == FAIL)
 			    return FAIL;
+			if ((options & JSON_JS)
+				&& li->li_next == NULL
+				&& li->li_tv.v_type == VAR_SPECIAL
+				&& li->li_tv.vval.v_number == VVAL_NONE)
+			    /* add an extra comma if the last item is v:none */
+			    ga_append(gap, ',');
 			li = li->li_next;
 			if (li != NULL)
 			    ga_append(gap, ',');
@@ -224,10 +248,14 @@ json_encode_item(garray_T *gap, typval_T
 				first = FALSE;
 			    else
 				ga_append(gap, ',');
-			    write_string(gap, hi->hi_key);
+			    if ((options & JSON_JS)
+						 && is_simple_key(hi->hi_key))
+				ga_concat(gap, hi->hi_key);
+			    else
+				write_string(gap, hi->hi_key);
 			    ga_append(gap, ':');
 			    if (json_encode_item(gap, &dict_lookup(hi)->di_tv,
-						       copyID, FALSE) == FAIL)
+				      copyID, options | JSON_NO_NONE) == FAIL)
 				return FAIL;
 			}
 		    ga_append(gap, '}');
@@ -265,7 +293,8 @@ fill_numbuflen(js_read_T *reader)
 }
 
 /*
- * Skip white space in "reader".
+ * Skip white space in "reader".  All characters <= space are considered white
+ * space.
  * Also tops up readahead when needed.
  */
     static void
@@ -282,7 +311,7 @@ json_skip_white(js_read_T *reader)
 		reader->js_end = reader->js_buf + STRLEN(reader->js_buf);
 	    continue;
 	}
-	if (c != ' ' && c != TAB && c != NL && c != CAR)
+	if (c == NUL || c > ' ')
 	    break;
 	++reader->js_used;
     }
@@ -290,7 +319,7 @@ json_skip_white(js_read_T *reader)
 }
 
     static int
-json_decode_array(js_read_T *reader, typval_T *res)
+json_decode_array(js_read_T *reader, typval_T *res, int options)
 {
     char_u	*p;
     typval_T	item;
@@ -317,7 +346,7 @@ json_decode_array(js_read_T *reader, typ
 	    break;
 	}
 
-	ret = json_decode_item(reader, res == NULL ? NULL : &item);
+	ret = json_decode_item(reader, res == NULL ? NULL : &item, options);
 	if (ret != OK)
 	    return ret;
 	if (res != NULL)
@@ -347,7 +376,7 @@ json_decode_array(js_read_T *reader, typ
 }
 
     static int
-json_decode_object(js_read_T *reader, typval_T *res)
+json_decode_object(js_read_T *reader, typval_T *res, int options)
 {
     char_u	*p;
     typval_T	tvkey;
@@ -377,16 +406,31 @@ json_decode_object(js_read_T *reader, ty
 	    break;
 	}
 
-	ret = json_decode_item(reader, res == NULL ? NULL : &tvkey);
-	if (ret != OK)
-	    return ret;
-	if (res != NULL)
+	if ((options & JSON_JS) && reader->js_buf[reader->js_used] != '"')
+	{
+	    /* accept a key that is not in quotes */
+	    key = p = reader->js_buf + reader->js_used;
+	    while (*p != NUL && *p != ':' && *p > ' ')
+		++p;
+	    tvkey.v_type = VAR_STRING;
+	    tvkey.vval.v_string = vim_strnsave(key, (int)(p - key));
+	    reader->js_used += (int)(p - key);
+	    key = tvkey.vval.v_string;
+	}
+	else
 	{
-	    key = get_tv_string_buf_chk(&tvkey, buf);
-	    if (key == NULL || *key == NUL)
+	    ret = json_decode_item(reader, res == NULL ? NULL : &tvkey,
+								     options);
+	    if (ret != OK)
+		return ret;
+	    if (res != NULL)
 	    {
-		clear_tv(&tvkey);
-		return FAIL;
+		key = get_tv_string_buf_chk(&tvkey, buf);
+		if (key == NULL || *key == NUL)
+		{
+		    clear_tv(&tvkey);
+		    return FAIL;
+		}
 	    }
 	}
 
@@ -403,7 +447,7 @@ json_decode_object(js_read_T *reader, ty
 	++reader->js_used;
 	json_skip_white(reader);
 
-	ret = json_decode_item(reader, res == NULL ? NULL : &item);
+	ret = json_decode_item(reader, res == NULL ? NULL : &item, options);
 	if (ret != OK)
 	{
 	    if (res != NULL)
@@ -569,7 +613,7 @@ json_decode_string(js_read_T *reader, ty
  * Return MAYBE for an incomplete message.
  */
     static int
-json_decode_item(js_read_T *reader, typval_T *res)
+json_decode_item(js_read_T *reader, typval_T *res, int options)
 {
     char_u	*p;
     int		len;
@@ -579,15 +623,18 @@ json_decode_item(js_read_T *reader, typv
     switch (*p)
     {
 	case '[': /* array */
-	    return json_decode_array(reader, res);
+	    return json_decode_array(reader, res, options);
 
 	case '{': /* object */
-	    return json_decode_object(reader, res);
+	    return json_decode_object(reader, res, options);
 
 	case '"': /* string */
 	    return json_decode_string(reader, res);
 
 	case ',': /* comma: empty item */
+	    if ((options & JSON_JS) == 0)
+		return FAIL;
+	    /* FALLTHROUGH */
 	case NUL: /* empty */
 	    if (res != NULL)
 	    {
@@ -691,17 +738,18 @@ json_decode_item(js_read_T *reader, typv
 
 /*
  * Decode the JSON from "reader" and store the result in "res".
+ * "options" can be JSON_JS or zero;
  * Return FAIL if not the whole message was consumed.
  */
     int
-json_decode_all(js_read_T *reader, typval_T *res)
+json_decode_all(js_read_T *reader, typval_T *res, int options)
 {
     int ret;
 
-    /* We get the end once, to avoid calling strlen() many times. */
+    /* We find the end once, to avoid calling strlen() many times. */
     reader->js_end = reader->js_buf + STRLEN(reader->js_buf);
     json_skip_white(reader);
-    ret = json_decode_item(reader, res);
+    ret = json_decode_item(reader, res, options);
     if (ret != OK)
 	return FAIL;
     json_skip_white(reader);
@@ -712,18 +760,19 @@ json_decode_all(js_read_T *reader, typva
 
 /*
  * Decode the JSON from "reader" and store the result in "res".
+ * "options" can be JSON_JS or zero;
  * Return FAIL if the message has a decoding error or the message is
  * truncated.  Consumes the message anyway.
  */
     int
-json_decode(js_read_T *reader, typval_T *res)
+json_decode(js_read_T *reader, typval_T *res, int options)
 {
     int ret;
 
-    /* We get the end once, to avoid calling strlen() many times. */
+    /* We find the end once, to avoid calling strlen() many times. */
     reader->js_end = reader->js_buf + STRLEN(reader->js_buf);
     json_skip_white(reader);
-    ret = json_decode_item(reader, res);
+    ret = json_decode_item(reader, res, options);
     json_skip_white(reader);
 
     return ret == OK ? OK : FAIL;
@@ -731,6 +780,7 @@ json_decode(js_read_T *reader, typval_T 
 
 /*
  * Decode the JSON from "reader" to find the end of the message.
+ * "options" can be JSON_JS or zero;
  * Return FAIL if the message has a decoding error.
  * Return MAYBE if the message is truncated, need to read more.
  * This only works reliable if the message contains an object, array or
@@ -738,15 +788,15 @@ json_decode(js_read_T *reader, typval_T 
  * Does not advance the reader.
  */
     int
-json_find_end(js_read_T *reader)
+json_find_end(js_read_T *reader, int options)
 {
     int used_save = reader->js_used;
     int ret;
 
-    /* We get the end once, to avoid calling strlen() many times. */
+    /* We find the end once, to avoid calling strlen() many times. */
     reader->js_end = reader->js_buf + STRLEN(reader->js_buf);
     json_skip_white(reader);
-    ret = json_decode_item(reader, NULL);
+    ret = json_decode_item(reader, NULL, options);
     reader->js_used = used_save;
     return ret;
 }