summaryrefslogtreecommitdiffstats
path: root/src/util/quote_for_json.c
blob: f54af3fcc651213c26183afef6862a24a9fd7685 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
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