diff options
Diffstat (limited to 'src/util/quote_for_json.c')
-rw-r--r-- | src/util/quote_for_json.c | 218 |
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 |