From: Matt Whitlock Date: Mon, 14 Jul 2025 10:02:58 +0000 (-0400) Subject: json_escape: add json_escape_unescape_len() X-Git-Url: https://git.ozlabs.org/?a=commitdiff_plain;h=e43c61c4c760aed7fd4ab6b716b019fc170f280d;p=ccan json_escape: add json_escape_unescape_len() 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(). --- diff --git a/ccan/json_escape/json_escape.c b/ccan/json_escape/json_escape.c index daa14abe..4bbb0032 100644 --- a/ccan/json_escape/json_escape.c +++ b/ccan/json_escape/json_escape.c @@ -1,6 +1,7 @@ /* MIT (BSD) license - see LICENSE file for details */ #include #include +#include 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); +} diff --git a/ccan/json_escape/json_escape.h b/ccan/json_escape/json_escape.h index 5b33432f..88d1eefc 100644 --- a/ccan/json_escape/json_escape.h +++ b/ccan/json_escape/json_escape.h @@ -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 */