make escaping slashes optional - fixes #569

Sun, 12 Jan 2025 13:04:32 +0100

author
Mike Becker <universe@uap-core.de>
date
Sun, 12 Jan 2025 13:04:32 +0100
changeset 1123
2b83302d595a
parent 1122
49ab92de9a13
child 1124
fcd5d86c472f

make escaping slashes optional - fixes #569

src/cx/json.h file | annotate | diff | comparison | revisions
src/json.c file | annotate | diff | comparison | revisions
tests/test_json.c file | annotate | diff | comparison | revisions
--- a/src/cx/json.h	Sat Jan 11 12:56:54 2025 +0100
+++ b/src/cx/json.h	Sun Jan 12 13:04:32 2025 +0100
@@ -463,6 +463,10 @@
      * Indentation is only used in pretty output.
      */
     uint8_t indent;
+    /**
+     * Set true to enable escaping of the slash character (solidus).
+     */
+    bool escape_slash;
 };
 
 /**
--- a/src/json.c	Sat Jan 11 12:56:54 2025 +0100
+++ b/src/json.c	Sun Jan 12 13:04:32 2025 +0100
@@ -386,7 +386,7 @@
     return result;
 }
 
-static cxmutstr escape_string(cxmutstr str) {
+static cxmutstr escape_string(cxmutstr str, bool escape_slash) {
     // note: this function produces the string without enclosing quotes
     // the reason is that we don't want to allocate memory just for that
     CxBuffer buf = {0};
@@ -396,8 +396,7 @@
         bool escape = !isprint(str.ptr[i])
             || str.ptr[i] == '\\'
             || str.ptr[i] == '"'
-            // TODO: make escaping slash optional
-            || str.ptr[i] == '/';
+            || (escape_slash && str.ptr[i] == '/');
 
         if (all_printable && escape) {
             size_t capa = str.length + 32;
@@ -1058,7 +1057,8 @@
         true,
         6,
         false,
-        4
+        4,
+        false
     };
 }
 
@@ -1068,7 +1068,8 @@
         true,
         6,
         use_spaces,
-        4
+        4,
+        false
     };
 }
 
@@ -1149,7 +1150,7 @@
 
                 // the name
                 actual += wfunc("\"", 1, 1, target);
-                cxmutstr name = escape_string(member->name);
+                cxmutstr name = escape_string(member->name, settings->escape_slash);
                 actual += wfunc(name.ptr, 1, name.length, target);
                 if (name.ptr != member->name.ptr) {
                     cx_strfree(&name);
@@ -1219,7 +1220,7 @@
         }
         case CX_JSON_STRING: {
             actual += wfunc("\"", 1, 1, target);
-            cxmutstr str = escape_string(value->value.string);
+            cxmutstr str = escape_string(value->value.string, settings->escape_slash);
             actual += wfunc(str.ptr, 1, str.length, target);
             if (str.ptr != value->value.string.ptr) {
                 cx_strfree(&str);
--- a/tests/test_json.c	Sat Jan 11 12:56:54 2025 +0100
+++ b/tests/test_json.c	Sun Jan 12 13:04:32 2025 +0100
@@ -944,7 +944,7 @@
      * According to RFC-8259 we have to test the following characters:
      *    "    quotation mark
      *    \    reverse solidus
-     *    /    solidus
+     *    /    solidus     ---> we make this optional, see test_json_write_solidus
      *    b    backspace
      *    f    form feed
      *    n    line feed
@@ -954,14 +954,14 @@
      * Also, all unicode characters are encoded that way - in our example the 'ö'.
      */
     CxJsonValue* str = cxJsonCreateString(NULL,
-        "hello\twörld\r\nthis/is\\a \"string\"\b in \a string\f");
+        "hello\twörld\r\nthis is\\a \"string\"\b in \a string\f");
     CxJsonWriter writer = cxJsonWriterCompact();
     CxBuffer buf;
     cxBufferInit(&buf, NULL, 128, NULL, 0);
     CX_TEST_DO {
         cxJsonWrite(&buf, str, cxBufferWriteFunc, &writer);
         CX_TEST_ASSERT(0 == cx_strcmp(cx_strn(buf.space, buf.size),
-            CX_STR("\"hello\\tw\\u00c3\\u00b6rld\\r\\nthis\\/is\\\\a \\\"string\\\"\\b in \\u0007 string\\f\"")));
+            CX_STR("\"hello\\tw\\u00c3\\u00b6rld\\r\\nthis is\\\\a \\\"string\\\"\\b in \\u0007 string\\f\"")));
     }
     cxBufferDestroy(&buf);
     cxJsonValueFree(str);
@@ -970,19 +970,39 @@
 CX_TEST(test_json_write_name_escape) {
     CxJsonValue* obj = cxJsonCreateObj(NULL);
     cxJsonObjPutLiteral(obj,
-        CX_STR("hello\twörld\r\nthis/is\\a \"string\"\b in \a string\f"), CX_JSON_TRUE);
+        CX_STR("hello\twörld\r\nthis is\\a \"string\"\b in \a string\f"), CX_JSON_TRUE);
     CxJsonWriter writer = cxJsonWriterCompact();
     CxBuffer buf;
     cxBufferInit(&buf, NULL, 128, NULL, 0);
     CX_TEST_DO {
         cxJsonWrite(&buf, obj, cxBufferWriteFunc, &writer);
         CX_TEST_ASSERT(0 == cx_strcmp(cx_strn(buf.space, buf.size),
-            CX_STR("{\"hello\\tw\\u00c3\\u00b6rld\\r\\nthis\\/is\\\\a \\\"string\\\"\\b in \\u0007 string\\f\":true}")));
+            CX_STR("{\"hello\\tw\\u00c3\\u00b6rld\\r\\nthis is\\\\a \\\"string\\\"\\b in \\u0007 string\\f\":true}")));
     }
     cxBufferDestroy(&buf);
     cxJsonValueFree(obj);
 }
 
+CX_TEST(test_json_write_solidus) {
+    CxJsonValue* str = cxJsonCreateString(NULL,"test/solidus");
+    CxJsonWriter writer = cxJsonWriterCompact();
+    CxBuffer buf;
+    cxBufferInit(&buf, NULL, 16, NULL, 0);
+    CX_TEST_DO {
+        // default: do not escape
+        cxJsonWrite(&buf, str, cxBufferWriteFunc, &writer);
+        CX_TEST_ASSERT(0 == cx_strcmp(cx_strn(buf.space, buf.size), CX_STR("\"test/solidus\"")));
+
+        // enable escaping
+        writer.escape_slash = true;
+        cxBufferReset(&buf);
+        cxJsonWrite(&buf, str, cxBufferWriteFunc, &writer);
+        CX_TEST_ASSERT(0 == cx_strcmp(cx_strn(buf.space, buf.size), CX_STR("\"test\\/solidus\"")));
+    }
+    cxBufferDestroy(&buf);
+    cxJsonValueFree(str);
+}
+
 CxTestSuite *cx_test_suite_json(void) {
     CxTestSuite *suite = cx_test_suite_new("json");
 
@@ -1008,6 +1028,7 @@
     cx_test_register(suite, test_json_write_frac_max_digits);
     cx_test_register(suite, test_json_write_string_escape);
     cx_test_register(suite, test_json_write_name_escape);
+    cx_test_register(suite, test_json_write_solidus);
     
     return suite;
 }

mercurial