Commit 45f9ce17 authored by Rusty Russell's avatar Rusty Russell

json_escape: new module to escape JSON strings.

Signed-off-by: default avatarRusty Russell <rusty@rustcorp.com.au>
parent 7623d082
../../licenses/BSD-MIT
\ No newline at end of file
#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;
}
/* 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;
}
/* 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 */
#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;
}
#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();
}
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment