json_escape: new module to escape JSON strings.
[ccan] / ccan / json_escape / json_escape.c
1 /* MIT (BSD) license - see LICENSE file for details */
2 #include <ccan/json_escape/json_escape.h>
3 #include <stdio.h>
4
5 struct json_escape *json_escape_string_(const tal_t *ctx,
6                                         const void *bytes, size_t len)
7 {
8         struct json_escape *esc;
9
10         esc = (void *)tal_arr_label(ctx, char, len + 1,
11                                     TAL_LABEL(struct json_escape, ""));
12         memcpy(esc->s, bytes, len);
13         esc->s[len] = '\0';
14         return esc;
15 }
16
17 bool json_escape_eq(const struct json_escape *a, const struct json_escape *b)
18 {
19         return streq(a->s, b->s);
20 }
21
22 static struct json_escape *escape(const tal_t *ctx,
23                                   const char *str TAKES,
24                                   size_t len,
25                                   bool partial)
26 {
27         struct json_escape *esc;
28         size_t i, n;
29
30         /* Worst case: all \uXXXX */
31         esc = (struct json_escape *)tal_arr(ctx, char, len * 6 + 1);
32
33         for (i = n = 0; i < len; i++, n++) {
34                 char escape = 0;
35                 switch (str[i]) {
36                 case '\n':
37                         escape = 'n';
38                         break;
39                 case '\b':
40                         escape = 'b';
41                         break;
42                 case '\f':
43                         escape = 'f';
44                         break;
45                 case '\t':
46                         escape = 't';
47                         break;
48                 case '\r':
49                         escape = 'r';
50                         break;
51                 case '\\':
52                         if (partial) {
53                                 /* Don't double-escape standard escapes. */
54                                 if (str[i+1] == 'n'
55                                     || str[i+1] == 'b'
56                                     || str[i+1] == 'f'
57                                     || str[i+1] == 't'
58                                     || str[i+1] == 'r'
59                                     || str[i+1] == '/'
60                                     || str[i+1] == '\\'
61                                     || str[i+1] == '"') {
62                                         escape = str[i+1];
63                                         i++;
64                                         break;
65                                 }
66                                 if (str[i+1] == 'u'
67                                     && cisxdigit(str[i+2])
68                                     && cisxdigit(str[i+3])
69                                     && cisxdigit(str[i+4])
70                                     && cisxdigit(str[i+5])) {
71                                             memcpy(esc->s + n, str + i, 6);
72                                             n += 5;
73                                             i += 5;
74                                             continue;
75                                 }
76                         } /* fall thru */
77                 case '"':
78                         escape = str[i];
79                         break;
80                 default:
81                         if ((unsigned)str[i] < ' ' || str[i] == 127) {
82                                 snprintf(esc->s + n, 7, "\\u%04X", str[i]);
83                                 n += 5;
84                                 continue;
85                         }
86                 }
87                 if (escape) {
88                         esc->s[n++] = '\\';
89                         esc->s[n] = escape;
90                 } else
91                         esc->s[n] = str[i];
92         }
93
94         esc->s[n] = '\0';
95         if (taken(str))
96                 tal_free(str);
97         return esc;
98 }
99
100 struct json_escape *json_partial_escape(const tal_t *ctx, const char *str TAKES)
101 {
102         return escape(ctx, str, strlen(str), true);
103 }
104
105 struct json_escape *json_escape(const tal_t *ctx, const char *str TAKES)
106 {
107         return escape(ctx, str, strlen(str), false);
108 }
109
110 struct json_escape *json_escape_len(const tal_t *ctx, const char *str TAKES,
111                                     size_t len)
112 {
113         return escape(ctx, str, len, false);
114 }
115
116 /* By policy, we don't handle \u.  Use UTF-8. */
117 const char *json_escape_unescape(const tal_t *ctx, const struct json_escape *esc)
118 {
119         char *unesc = tal_arr(ctx, char, strlen(esc->s) + 1);
120         size_t i, n;
121
122         for (i = n = 0; esc->s[i]; i++, n++) {
123                 if (esc->s[i] != '\\') {
124                         unesc[n] = esc->s[i];
125                         continue;
126                 }
127
128                 i++;
129                 switch (esc->s[i]) {
130                 case 'n':
131                         unesc[n] = '\n';
132                         break;
133                 case 'b':
134                         unesc[n] = '\b';
135                         break;
136                 case 'f':
137                         unesc[n] = '\f';
138                         break;
139                 case 't':
140                         unesc[n] = '\t';
141                         break;
142                 case 'r':
143                         unesc[n] = '\r';
144                         break;
145                 case '/':
146                 case '\\':
147                 case '"':
148                         unesc[n] = esc->s[i];
149                         break;
150                 default:
151                         return tal_free(unesc);
152                 }
153         }
154
155         unesc[n] = '\0';
156         return unesc;
157 }