summaryrefslogtreecommitdiffstats
path: root/src/util/quote_for_json.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/util/quote_for_json.c')
-rw-r--r--src/util/quote_for_json.c218
1 files changed, 218 insertions, 0 deletions
diff --git a/src/util/quote_for_json.c b/src/util/quote_for_json.c
new file mode 100644
index 0000000..f54af3f
--- /dev/null
+++ b/src/util/quote_for_json.c
@@ -0,0 +1,218 @@
+/*++
+/* NAME
+/* quote_for_json 3
+/* SUMMARY
+/* quote UTF-8 string value for JSON
+/* SYNOPSIS
+/* #include <quote_for_json.h>
+/*
+/* char *quote_for_json(
+/* VSTRING *result,
+/* const char *in,
+/* ssize_t len)
+/*
+/* char *quote_for_json_append(
+/* VSTRING *result,
+/* const char *in,
+/* ssize_t len)
+/* DESCRIPTION
+/* quote_for_json() takes well-formed UTF-8 encoded text,
+/* quotes that text compliant with RFC 4627, and returns a
+/* pointer to the resulting text. The input may contain null
+/* bytes, but the output will not.
+/*
+/* quote_for_json() produces short (two-letter) escape sequences
+/* for common control characters, double quote and backslash.
+/* It will not quote "/" (0x2F), and will quote DEL (0x7f) as
+/* \u007F to make it printable. The input byte sequence "\uXXXX"
+/* is quoted like any other text (the "\" is escaped as "\\").
+/*
+/* quote_for_json() does not perform UTF-8 validation. The caller
+/* should use valid_utf8_string() or printable() as appropriate.
+/*
+/* quote_for_json_append() appends the output to the result buffer.
+/*
+/* Arguments:
+/* .IP result
+/* Storage for the result, resized automatically.
+/* .IP in
+/* Pointer to the input byte sequence.
+/* .IP len
+/* The length of the input byte sequence, or a negative number
+/* when the byte sequence is null-terminated.
+/* DIAGNOSTICS
+/* Fatal error: memory allocation error.
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* Google, Inc.
+/* 111 8th Avenue
+/* New York, NY 10011, USA
+/*
+/* Wietse Venema
+/* porcupine.org
+/*--*/
+
+ /*
+ * System library.
+ */
+#include <sys_defs.h>
+#include <ctype.h>
+#include <string.h>
+
+ /*
+ * Utility library.
+ */
+#include <stringops.h>
+#include <vstring.h>
+
+#define STR(x) vstring_str(x)
+
+/* quote_for_json_append - quote JSON string, append result */
+
+char *quote_for_json_append(VSTRING *result, const char *text, ssize_t len)
+{
+ const char *cp;
+ int ch;
+
+ if (len < 0)
+ len = strlen(text);
+
+ for (cp = text; len > 0; len--, cp++) {
+ ch = *(const unsigned char *) cp;
+ if (UNEXPECTED(ISCNTRL(ch))) {
+ switch (ch) {
+ case '\b':
+ VSTRING_ADDCH(result, '\\');
+ VSTRING_ADDCH(result, 'b');
+ break;
+ case '\f':
+ VSTRING_ADDCH(result, '\\');
+ VSTRING_ADDCH(result, 'f');
+ break;
+ case '\n':
+ VSTRING_ADDCH(result, '\\');
+ VSTRING_ADDCH(result, 'n');
+ break;
+ case '\r':
+ VSTRING_ADDCH(result, '\\');
+ VSTRING_ADDCH(result, 'r');
+ break;
+ case '\t':
+ VSTRING_ADDCH(result, '\\');
+ VSTRING_ADDCH(result, 't');
+ break;
+ default:
+ /* All other controls including DEL and NUL. */
+ vstring_sprintf_append(result, "\\u%04X", ch);
+ break;
+ }
+ } else {
+ switch (ch) {
+ case '\\':
+ case '"':
+ VSTRING_ADDCH(result, '\\');
+ /* FALLTHROUGH */
+ default:
+ /* Includes malformed UTF-8. */
+ VSTRING_ADDCH(result, ch);
+ break;
+ }
+ }
+ }
+ VSTRING_TERMINATE(result);
+ return (STR(result));
+}
+
+/* quote_for_json - quote JSON string */
+
+char *quote_for_json(VSTRING *result, const char *text, ssize_t len)
+{
+ VSTRING_RESET(result);
+ return (quote_for_json_append(result, text, len));
+}
+
+#ifdef TEST
+
+ /*
+ * System library.
+ */
+#include <stdlib.h>
+
+ /*
+ * Utility library.
+ */
+#include <msg.h>
+#include <msg_vstream.h>
+
+typedef struct TEST_CASE {
+ const char *label; /* identifies test case */
+ char *(*fn) (VSTRING *, const char *, ssize_t);
+ const char *input; /* input string */
+ ssize_t input_len; /* -1 or input length */
+ const char *exp_res; /* expected result */
+} TEST_CASE;
+
+#define PASS (0)
+#define FAIL (1)
+
+ /*
+ * The test cases.
+ */
+static const TEST_CASE test_cases[] = {
+ {"ordinary ASCII text", quote_for_json,
+ " abcABC012.,[]{}/", -1, " abcABC012.,[]{}/",
+ },
+ {"quote_for_json_append", quote_for_json_append,
+ "foo", -1, " abcABC012.,[]{}/foo",
+ },
+ {"common control characters", quote_for_json,
+ "\b\f\r\n\t", -1, "\\b\\f\\r\\n\\t",
+ },
+ {"uncommon control characters and DEL", quote_for_json,
+ "\0\01\037\040\176\177", 6, "\\u0000\\u0001\\u001F ~\\u007F",
+ },
+ {"malformed UTF-8", quote_for_json,
+ "\\*\\uasd\\u007F\x80", -1, "\\\\*\\\\uasd\\\\u007F\x80",
+ },
+ 0,
+};
+
+int main(int argc, char **argv)
+{
+ const TEST_CASE *tp;
+ int pass = 0;
+ int fail = 0;
+ VSTRING *res_buf = vstring_alloc(100);
+
+ msg_vstream_init(sane_basename((VSTRING *) 0, argv[0]), VSTREAM_ERR);
+
+ for (tp = test_cases; tp->label != 0; tp++) {
+ int test_fail = 0;
+ char *res;
+
+ msg_info("RUN %s", tp->label);
+ res = tp->fn(res_buf, tp->input, tp->input_len);
+ if (strcmp(res, tp->exp_res) != 0) {
+ msg_warn("test case '%s': got '%s', want '%s'",
+ tp->label, res, tp->exp_res);
+ test_fail = 1;
+ }
+ if (test_fail) {
+ fail++;
+ msg_info("FAIL %s", tp->label);
+ test_fail = 1;
+ } else {
+ msg_info("PASS %s", tp->label);
+ pass++;
+ }
+ }
+ msg_info("PASS=%d FAIL=%d", pass, fail);
+ vstring_free(res_buf);
+ exit(fail != 0);
+}
+
+#endif