src/json.c

changeset 1119
ff4d7e76f85a
parent 1117
54df904472b0
--- a/src/json.c	Fri Jan 10 15:03:58 2025 +0100
+++ b/src/json.c	Fri Jan 10 23:11:08 2025 +0100
@@ -345,7 +345,7 @@
 
 static cxmutstr unescape_string(const CxAllocator *a, cxmutstr str) {
     // TODO: support more escape sequences
-    // we know that the unescaped string will be shorter by at least 2 chars
+    // TODO: to be consistent with escape_string() we might want to expect that the enclosing quotes were already removed
     cxmutstr result;
     result.length = 0;
     result.ptr = cxMalloc(a, str.length - 1);
@@ -375,6 +375,60 @@
     return result;
 }
 
+static cxmutstr escape_string(cxmutstr str) {
+    CxBuffer buf = {0};
+
+    bool all_printable = true;
+    for (size_t i = 0; i < str.length; i++) {
+        bool escape = !isprint(str.ptr[i])
+            || str.ptr[i] == '\\'
+            || str.ptr[i] == '"'
+            // TODO: make escaping slash optional
+            || str.ptr[i] == '/';
+
+        if (all_printable && escape) {
+            size_t capa = str.length + 32;
+            char *space = malloc(capa);
+            if (space == NULL) return cx_mutstrn(NULL, 0);
+            cxBufferInit(&buf, space, capa, NULL, CX_BUFFER_AUTO_EXTEND);
+            cxBufferWrite(str.ptr, 1, i, &buf);
+            all_printable = false;
+        }
+        if (escape) {
+            cxBufferPut(&buf, '\\');
+            if (str.ptr[i] == '\"') {
+                cxBufferPut(&buf, '\"');
+            } else if (str.ptr[i] == '\n') {
+                cxBufferPut(&buf, 'n');
+            } else if (str.ptr[i] == '\t') {
+                cxBufferPut(&buf, 't');
+            } else if (str.ptr[i] == '\r') {
+                cxBufferPut(&buf, 'r');
+            } else if (str.ptr[i] == '\\') {
+                cxBufferPut(&buf, '\\');
+            } else if (str.ptr[i] == '/') {
+                cxBufferPut(&buf, '/');
+            } else if (str.ptr[i] == '\f') {
+                cxBufferPut(&buf, 'f');
+            } else if (str.ptr[i] == '\b') {
+                cxBufferPut(&buf, 'b');
+            } else {
+                char code[6];
+                snprintf(code, sizeof(code), "u%04x",
+                    (unsigned int)(0xff & str.ptr[i]));
+                cxBufferPutString(&buf, code);
+            }
+        } else if (!all_printable) {
+            cxBufferPut(&buf, str.ptr[i]);
+        }
+    }
+    if (!all_printable) {
+        str = cx_mutstrn(buf.space, buf.size);
+    }
+    cxBufferDestroy(&buf);
+    return str;
+}
+
 static CxJsonValue* create_json_value(CxJson *json, CxJsonValueType type) {
     CxJsonValue *v = cxCalloc(json->allocator, 1, sizeof(CxJsonValue));
     if (v == NULL) return NULL; // LCOV_EXCL_LINE
@@ -1084,9 +1138,11 @@
 
                 // the name
                 actual += wfunc("\"", 1, 1, target);
-                // TODO: escape the string
-                actual += wfunc(member->name.ptr, 1,
-                    member->name.length, target);
+                cxmutstr name = escape_string(member->name);
+                actual += wfunc(name.ptr, 1, name.length, target);
+                if (name.ptr != member->name.ptr) {
+                    cx_strfree(&name);
+                }
                 actual += wfunc("\"", 1, 1, target);
                 const char *obj_name_sep = ": ";
                 if (settings->pretty) {
@@ -1152,9 +1208,11 @@
         }
         case CX_JSON_STRING: {
             actual += wfunc("\"", 1, 1, target);
-            // TODO: escape the string
-            actual += wfunc(value->value.string.ptr, 1,
-                value->value.string.length, target);
+            cxmutstr str = escape_string(value->value.string);
+            actual += wfunc(str.ptr, 1, str.length, target);
+            if (str.ptr != value->value.string.ptr) {
+                cx_strfree(&str);
+            }
             actual += wfunc("\"", 1, 1, target);
             expected += 2 + value->value.string.length;
             break;

mercurial