json_escape: new module to escape JSON strings.
authorRusty Russell <rusty@rustcorp.com.au>
Tue, 21 May 2019 04:21:12 +0000 (13:51 +0930)
committerRusty Russell <rusty@rustcorp.com.au>
Tue, 21 May 2019 04:21:12 +0000 (13:51 +0930)
Signed-off-by: Rusty Russell <rusty@rustcorp.com.au>
ccan/json_escape/LICENSE [new symlink]
ccan/json_escape/_info [new file with mode: 0644]
ccan/json_escape/json_escape.c [new file with mode: 0644]
ccan/json_escape/json_escape.h [new file with mode: 0644]
ccan/json_escape/test/run-partial.c [new file with mode: 0644]
ccan/json_escape/test/run.c [new file with mode: 0644]

diff --git a/ccan/json_escape/LICENSE b/ccan/json_escape/LICENSE
new file mode 120000 (symlink)
index 0000000..2354d12
--- /dev/null
@@ -0,0 +1 @@
+../../licenses/BSD-MIT
\ No newline at end of file
diff --git a/ccan/json_escape/_info b/ccan/json_escape/_info
new file mode 100644 (file)
index 0000000..934857f
--- /dev/null
@@ -0,0 +1,39 @@
+#include "config.h"
+#include <stdio.h>
+#include <string.h>
+
+/**
+ * json_escape - Escape sequences for JSON strings
+ *
+ * This code helps you format strings into forms useful for JSON.
+ *
+ * Author: Rusty Russell <rusty@rustcorp.com.au>
+ * License: BSD-MIT
+ * Example:
+ *     // Print arguments as a JSON array.
+ *     #include <ccan/json_escape/json_escape.h>
+ *
+ *     int main(int argc, char *argv[])
+ *     {
+ *             printf("[");
+ *             for (int i = 1; i < argc; i++) {
+ *                     struct json_escape *e = json_escape(NULL, argv[i]);
+ *                     printf("%s\"%s\"", i == 1 ? "" : ",", e->s);
+ *             }
+ *             printf("]\n");
+ *             return 0;
+ *     }
+ */
+int main(int argc, char *argv[])
+{
+       /* Expect exactly one argument */
+       if (argc != 2)
+               return 1;
+
+       if (strcmp(argv[1], "depends") == 0) {
+               printf("ccan/tal\n");
+               return 0;
+       }
+
+       return 1;
+}
diff --git a/ccan/json_escape/json_escape.c b/ccan/json_escape/json_escape.c
new file mode 100644 (file)
index 0000000..d344940
--- /dev/null
@@ -0,0 +1,157 @@
+/* MIT (BSD) license - see LICENSE file for details */
+#include <ccan/json_escape/json_escape.h>
+#include <stdio.h>
+
+struct json_escape *json_escape_string_(const tal_t *ctx,
+                                       const void *bytes, size_t len)
+{
+       struct json_escape *esc;
+
+       esc = (void *)tal_arr_label(ctx, char, len + 1,
+                                   TAL_LABEL(struct json_escape, ""));
+       memcpy(esc->s, bytes, len);
+       esc->s[len] = '\0';
+       return esc;
+}
+
+bool json_escape_eq(const struct json_escape *a, const struct json_escape *b)
+{
+       return streq(a->s, b->s);
+}
+
+static struct json_escape *escape(const tal_t *ctx,
+                                 const char *str TAKES,
+                                 size_t len,
+                                 bool partial)
+{
+       struct json_escape *esc;
+       size_t i, n;
+
+       /* Worst case: all \uXXXX */
+       esc = (struct json_escape *)tal_arr(ctx, char, len * 6 + 1);
+
+       for (i = n = 0; i < len; i++, n++) {
+               char escape = 0;
+               switch (str[i]) {
+               case '\n':
+                       escape = 'n';
+                       break;
+               case '\b':
+                       escape = 'b';
+                       break;
+               case '\f':
+                       escape = 'f';
+                       break;
+               case '\t':
+                       escape = 't';
+                       break;
+               case '\r':
+                       escape = 'r';
+                       break;
+               case '\\':
+                       if (partial) {
+                               /* Don't double-escape standard escapes. */
+                               if (str[i+1] == 'n'
+                                   || str[i+1] == 'b'
+                                   || str[i+1] == 'f'
+                                   || str[i+1] == 't'
+                                   || str[i+1] == 'r'
+                                   || str[i+1] == '/'
+                                   || str[i+1] == '\\'
+                                   || str[i+1] == '"') {
+                                       escape = str[i+1];
+                                       i++;
+                                       break;
+                               }
+                               if (str[i+1] == 'u'
+                                   && cisxdigit(str[i+2])
+                                   && cisxdigit(str[i+3])
+                                   && cisxdigit(str[i+4])
+                                   && cisxdigit(str[i+5])) {
+                                           memcpy(esc->s + n, str + i, 6);
+                                           n += 5;
+                                           i += 5;
+                                           continue;
+                               }
+                       } /* fall thru */
+               case '"':
+                       escape = str[i];
+                       break;
+               default:
+                       if ((unsigned)str[i] < ' ' || str[i] == 127) {
+                               snprintf(esc->s + n, 7, "\\u%04X", str[i]);
+                               n += 5;
+                               continue;
+                       }
+               }
+               if (escape) {
+                       esc->s[n++] = '\\';
+                       esc->s[n] = escape;
+               } else
+                       esc->s[n] = str[i];
+       }
+
+       esc->s[n] = '\0';
+       if (taken(str))
+               tal_free(str);
+       return esc;
+}
+
+struct json_escape *json_partial_escape(const tal_t *ctx, const char *str TAKES)
+{
+       return escape(ctx, str, strlen(str), true);
+}
+
+struct json_escape *json_escape(const tal_t *ctx, const char *str TAKES)
+{
+       return escape(ctx, str, strlen(str), false);
+}
+
+struct json_escape *json_escape_len(const tal_t *ctx, const char *str TAKES,
+                                   size_t len)
+{
+       return escape(ctx, str, len, false);
+}
+
+/* By policy, we don't handle \u.  Use UTF-8. */
+const char *json_escape_unescape(const tal_t *ctx, const struct json_escape *esc)
+{
+       char *unesc = tal_arr(ctx, char, strlen(esc->s) + 1);
+       size_t i, n;
+
+       for (i = n = 0; esc->s[i]; i++, n++) {
+               if (esc->s[i] != '\\') {
+                       unesc[n] = esc->s[i];
+                       continue;
+               }
+
+               i++;
+               switch (esc->s[i]) {
+               case 'n':
+                       unesc[n] = '\n';
+                       break;
+               case 'b':
+                       unesc[n] = '\b';
+                       break;
+               case 'f':
+                       unesc[n] = '\f';
+                       break;
+               case 't':
+                       unesc[n] = '\t';
+                       break;
+               case 'r':
+                       unesc[n] = '\r';
+                       break;
+               case '/':
+               case '\\':
+               case '"':
+                       unesc[n] = esc->s[i];
+                       break;
+               default:
+                       return tal_free(unesc);
+               }
+       }
+
+       unesc[n] = '\0';
+       return unesc;
+}
diff --git a/ccan/json_escape/json_escape.h b/ccan/json_escape/json_escape.h
new file mode 100644 (file)
index 0000000..b8e0dfc
--- /dev/null
@@ -0,0 +1,43 @@
+/* MIT (BSD) license - see LICENSE file for details */
+#ifndef CCAN_JSON_ESCAPE_H
+#define CCAN_JSON_ESCAPE_H
+#include "config.h"
+#include <ccan/tal/tal.h>
+
+/* Type differentiation for a correctly-escaped JSON string */
+struct json_escape {
+       /* NUL terminated string. */
+       char s[1];
+};
+
+/**
+ * json_escape - escape a valid UTF-8 string.
+ * @ctx: tal context to allocate from.
+ * @str: the string to escape.
+ *
+ * Allocates and returns a valid JSON string (without surrounding quotes).
+ */
+struct json_escape *json_escape(const tal_t *ctx, const char *str TAKES);
+
+/* Version with @len */
+struct json_escape *json_escape_len(const tal_t *ctx,
+                                   const char *str TAKES, size_t len);
+
+/* @str is a valid UTF-8 string which may already contain escapes. */
+struct json_escape *json_partial_escape(const tal_t *ctx,
+                                        const char *str TAKES);
+
+/* Extract a JSON-escaped string. */
+
+/* Are two escape json strings identical? */
+bool json_escape_eq(const struct json_escape *a,
+                    const struct json_escape *b);
+
+/* Internal routine for creating json_escape from bytes. */
+struct json_escape *json_escape_string_(const tal_t *ctx,
+                                       const void *bytes, size_t len);
+
+/* 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);
+#endif /* CCAN_JSON_ESCAPE_H */
diff --git a/ccan/json_escape/test/run-partial.c b/ccan/json_escape/test/run-partial.c
new file mode 100644 (file)
index 0000000..25764df
--- /dev/null
@@ -0,0 +1,41 @@
+#include <ccan/json_escape/json_escape.h>
+/* Include the C files directly. */
+#include <ccan/json_escape/json_escape.c>
+#include <ccan/tap/tap.h>
+
+int main(void)
+{
+       const tal_t *ctx = tal(NULL, char);
+
+       /* This is how many tests you plan to run */
+       plan_tests(21);
+
+       ok1(!strcmp(json_partial_escape(ctx, "\\")->s, "\\\\"));
+       ok1(!strcmp(json_partial_escape(ctx, "\\\\")->s, "\\\\"));
+       ok1(!strcmp(json_partial_escape(ctx, "\\\\\\")->s, "\\\\\\\\"));
+       ok1(!strcmp(json_partial_escape(ctx, "\\\\\\\\")->s, "\\\\\\\\"));
+       ok1(!strcmp(json_partial_escape(ctx, "\\n")->s, "\\n"));
+       ok1(!strcmp(json_partial_escape(ctx, "\n")->s, "\\n"));
+       ok1(!strcmp(json_partial_escape(ctx, "\\\"")->s, "\\\""));
+       ok1(!strcmp(json_partial_escape(ctx, "\"")->s, "\\\""));
+       ok1(!strcmp(json_partial_escape(ctx, "\\t")->s, "\\t"));
+       ok1(!strcmp(json_partial_escape(ctx, "\t")->s, "\\t"));
+       ok1(!strcmp(json_partial_escape(ctx, "\\b")->s, "\\b"));
+       ok1(!strcmp(json_partial_escape(ctx, "\b")->s, "\\b"));
+       ok1(!strcmp(json_partial_escape(ctx, "\\r")->s, "\\r"));
+       ok1(!strcmp(json_partial_escape(ctx, "\r")->s, "\\r"));
+       ok1(!strcmp(json_partial_escape(ctx, "\\f")->s, "\\f"));
+       ok1(!strcmp(json_partial_escape(ctx, "\f")->s, "\\f"));
+       /* You're allowed to escape / according to json.org. */
+       ok1(!strcmp(json_partial_escape(ctx, "\\/")->s, "\\/"));
+       ok1(!strcmp(json_partial_escape(ctx, "/")->s, "/"));
+
+       ok1(!strcmp(json_partial_escape(ctx, "\\u0FFF")->s, "\\u0FFF"));
+       ok1(!strcmp(json_partial_escape(ctx, "\\u0FFFx")->s, "\\u0FFFx"));
+
+       /* Unknown escapes should be escaped. */
+       ok1(!strcmp(json_partial_escape(ctx, "\\x")->s, "\\\\x"));
+       tal_free(ctx);
+
+       return 0;
+}
diff --git a/ccan/json_escape/test/run.c b/ccan/json_escape/test/run.c
new file mode 100644 (file)
index 0000000..9d8b049
--- /dev/null
@@ -0,0 +1,44 @@
+#include <ccan/json_escape/json_escape.h>
+/* Include the C files directly. */
+#include <ccan/json_escape/json_escape.c>
+#include <ccan/tap/tap.h>
+
+int main(void)
+{
+       const tal_t *ctx = tal(NULL, char);
+       struct json_escape *e;
+
+       /* This is how many tests you plan to run */
+       plan_tests(6);
+
+       e = json_escape(ctx, "Hello");
+       ok1(!strcmp(e->s, "Hello"));
+       ok1(!strcmp(json_escape_unescape(ctx, e),
+                   "Hello"));
+
+       e = json_escape(ctx,
+                       "\\\b\f\n\r\t\""
+                       "\\\\\\b\\f\\n\\r\\t\\\"");
+       ok1(!strcmp(e->s,
+                   "\\\\\\b\\f\\n\\r\\t\\\""
+                   "\\\\\\\\\\\\b\\\\f\\\\n\\\\r\\\\t\\\\\\\""));
+       ok1(!strcmp(json_escape_unescape(ctx, e),
+                   "\\\b\f\n\r\t\""
+                   "\\\\\\b\\f\\n\\r\\t\\\""));
+
+       /* This one doesn't escape the already-escaped chars */
+       e = json_partial_escape(ctx,
+                               "\\\b\f\n\r\t\""
+                               "\\\\\\b\\f\\n\\r\\t\\\"");
+       ok1(!strcmp(e->s,
+                   "\\\\\\b\\f\\n\\r\\t\\\""
+                   "\\\\\\b\\f\\n\\r\\t\\\""));
+       ok1(!strcmp(json_escape_unescape(ctx, e),
+                   "\\\b\f\n\r\t\""
+                   "\\\b\f\n\r\t\""));
+
+       tal_free(ctx);
+
+       /* This exits depending on whether all tests passed */
+       return exit_status();
+}