]> git.ozlabs.org Git - ccan/commitdiff
json_escape: add json_escape_unescape_len()
authorMatt Whitlock <ccan@mattwhitlock.name>
Mon, 14 Jul 2025 10:02:58 +0000 (06:02 -0400)
committerRusty Russell <rusty@rustcorp.com.au>
Fri, 15 Aug 2025 00:50:02 +0000 (10:20 +0930)
Lacking a variant of json_escape_unescape() that takes a sized span,
the common case of unescaping a JSON string that is embedded inside a
larger buffer is forced to make a copy of the escaped string so as to
terminate it with a NUL character. This extra copy can be avoided if
we can pass an explicit length to the unescaping function.

Also, a drive-by fix: shrink the returned unescaped string to fit its
contents, which can save the caller a subsequent call to strlen().

ccan/json_escape/json_escape.c
ccan/json_escape/json_escape.h

index daa14abe8c8949e8f9f022a955ae3a787c9a245a..4bbb0032d1b5cfc23b23edf34b46d612ab692a82 100644 (file)
@@ -1,6 +1,7 @@
 /* MIT (BSD) license - see LICENSE file for details */
 #include <ccan/json_escape/json_escape.h>
 #include <stdio.h>
+#include <ccan/tal/str/str.h>
 
 struct json_escape *json_escape_string_(const tal_t *ctx,
                                        const void *bytes, size_t len)
@@ -137,19 +138,24 @@ struct json_escape *json_escape_len(const tal_t *ctx, const char *str TAKES,
 }
 
 /* By policy, we don't handle \u.  Use UTF-8. */
-const char *json_escape_unescape(const tal_t *ctx, const struct json_escape *esc)
+static const char *unescape(const tal_t *ctx, const char *esc TAKES, size_t len)
 {
-       char *unesc = tal_arr(ctx, char, strlen(esc->s) + 1);
+       /* Fast path: can steal, and nothing to unescape. */
+       if (is_taken(esc) && !memchr(esc, '\\', len))
+               return tal_strndup(ctx, esc, len);
+
+       char *unesc = tal_arr(ctx, char, len + 1);
        size_t i, n;
 
-       for (i = n = 0; esc->s[i]; i++, n++) {
-               if (esc->s[i] != '\\') {
-                       unesc[n] = esc->s[i];
+       for (i = n = 0; i < len; i++, n++) {
+               if (esc[i] != '\\') {
+                       unesc[n] = esc[i];
                        continue;
                }
 
-               i++;
-               switch (esc->s[i]) {
+               if (++i == len)
+                       goto error;
+               switch (esc[i]) {
                case 'n':
                        unesc[n] = '\n';
                        break;
@@ -168,13 +174,31 @@ const char *json_escape_unescape(const tal_t *ctx, const struct json_escape *esc
                case '/':
                case '\\':
                case '"':
-                       unesc[n] = esc->s[i];
+                       unesc[n] = esc[i];
                        break;
                default:
+               error:
+                       if (taken(esc))
+                               tal_free(esc);
                        return tal_free(unesc);
                }
        }
 
        unesc[n] = '\0';
+       if (!tal_resize(&unesc, n + 1))
+               goto error;
+       if (taken(esc))
+               tal_free(esc);
        return unesc;
 }
+
+const char *json_escape_unescape(const tal_t *ctx, const struct json_escape *esc)
+{
+       return unescape(ctx, esc->s, strlen(esc->s));
+}
+
+const char *json_escape_unescape_len(const tal_t *ctx,
+                                    const char *esc TAKES, size_t len)
+{
+       return unescape(ctx, esc, len);
+}
index 5b33432f8fce768dc53b77ebeaf068a03d567fc9..88d1eefc1a64711053f5227aa401cf89a786fee7 100644 (file)
@@ -41,4 +41,8 @@ struct json_escape *json_escape_string_(const tal_t *ctx,
 /* Be very careful here!  Can fail!  Doesn't handle \u: use UTF-8 please. */
 const char *json_escape_unescape(const tal_t *ctx,
                                 const struct json_escape *esc);
+
+/* Be very careful here!  Can fail!  Doesn't handle \u: use UTF-8 please. */
+const char *json_escape_unescape_len(const tal_t *ctx,
+                                    const char *esc TAKES, size_t len);
 #endif /* CCAN_JSON_ESCAPE_H */