1#include <stdbool.h>
2#include <stdio.h>
3#include <stdlib.h>
4#include <string.h>
5
6#include "config.h"
7#include "json.h"
8#include "log.h"
9
10static const char* json_basename(const char* path);
11static int json_grow(struct json* j, size_t need);
12static int json_hex64(struct json* j, uint64_t v);
13static const char* json_kind_from_type(const char* type);
14static int json_putc(struct json* j, const char c);
15static int json_putn(struct json* j, const char* s, size_t n);
16static int json_puts(struct json* j, const char* s);
17static int json_string(struct json* j, const char* s);
18
19/**
20 * @brief Extract basename from path
21 *
22 * @param path Input path
23 *
24 * @return Ptr to basename
25 */
26static const char* json_basename(const char* path)
27{
28 if (!path)
29 return "";
30
31 const char* p = strrchr(path, '/');
32 return p ? (p + 1) : path;
33}
34
35/**
36 * @brief Grow JSON buffer capacity
37 *
38 * @param j JSON buffer
39 * @param need Additional bytes needed
40 *
41 * @return 0=Success, -1=Failure
42 */
43static int json_grow(struct json* j, size_t need)
44{
45 if (j->len + need <= j->cap)
46 return 0;
47
48 size_t ncap = j->cap ? j->cap : 1024; /* initial buffer */
49 while (ncap < j->len + need)
50 ncap *= 2;
51
52 char* nb = realloc(j->buf, ncap);
53 if (!nb) {
54 LOG(verbose_log, "JSON", "realloc FAILED OOM");
55 return -1;
56 }
57
58 j->buf = nb;
59 j->cap = ncap;
60
61 return 0;
62}
63
64/**
65 * @brief Encode uint64 as 16-digit hex string
66 *
67 * @param j Output JSON buffer
68 * @param v Input value
69 *
70 * @return 0=Success, -1=Failure
71 */
72static int json_hex64(struct json* j, uint64_t v)
73{
74 char hex[17];
75 static const char* b16 = "0123456789abcdef";
76
77 for (int i = 15; i >= 0; i--) {
78 hex[i] = b16[v & 0xf];
79 v >>= 4;
80 }
81 hex[16] = '\0';
82
83 return json_string(j, hex);
84}
85
86/**
87 * @brief Map MIME type to item kind
88 *
89 * @param type MIME type string
90 *
91 * @return Item kind string
92 */
93static const char* json_kind_from_type(const char* type)
94{
95 if (!type || type[0] == '\0')
96 return "other";
97
98 if (strncmp(type, "audio/", 6) == 0)
99 return "audio";
100 if (strncmp(type, "video/", 6) == 0)
101 return "video";
102 if (strncmp(type, "image/", 6) == 0)
103 return "image";
104
105 return "other";
106}
107
108/**
109 * @brief Append single byte to JSON buffer
110 *
111 * @param j Output JSON buffer
112 * @param c Byte to append
113 *
114 * @return 0=Success, -1=Failure
115 */
116static int json_putc(struct json* j, char c)
117{
118 return json_putn(j, &c, 1);
119}
120
121/**
122 * @brief Append byte range to JSON buffer
123 *
124 * @param j Output JSON buffer
125 * @param s Input bytes
126 * @param n Number of bytes to append
127 *
128 * @return 0=Success, -1=Failure
129 */
130static int json_putn(struct json* j, const char* s, size_t n)
131{
132 if (json_grow(j, n + 1) < 0)
133 return -1;
134
135 memcpy(j->buf + j->len, s, n);
136 j->len += n;
137 j->buf[j->len] = '\0';
138
139 return 0;
140}
141
142/**
143 * @brief Append string to JSON buffer
144 *
145 * @param j Output JSON buffer
146 * @param s Input string
147 *
148 * @return 0=Success, -1=Failure
149 */
150static int json_puts(struct json* j, const char* s)
151{
152 return json_putn(j, s, strlen(s));
153}
154
155/**
156 * @brief Append JSON string with escaping
157 *
158 * @param j Output JSON buffer
159 * @param s Input string
160 *
161 * @return 0=Success, -1=Failure
162 */
163static int json_string(struct json* j, const char* s)
164{
165 /* opening quote */
166 if (json_putc(j, '"') < 0)
167 return -1;
168
169 /* emit string body */
170 for (; *s; s++) {
171 unsigned char c = (unsigned char)(*s);
172
173 /* escape quote and backslash */
174 if (c == '"' || c == '\\') {
175 if (json_putc(j, '\\') < 0)
176 return -1;
177 if (json_putc(j, (char)c) < 0)
178 return -1;
179 continue;
180 }
181
182 /* common escapes */
183 if (c == '\n') {
184 if (json_puts(j, "\\n") < 0)
185 return -1;
186 continue;
187 }
188 if (c == '\r') {
189 if (json_puts(j, "\\r") < 0)
190 return -1;
191 continue;
192 }
193 if (c == '\t') {
194 if (json_puts(j, "\\t") < 0)
195 return -1;
196 continue;
197 }
198
199 /* other control chars->\uxxxx */
200 if (c < 0x20) {
201 char esc[7];
202 snprintf(esc, sizeof(esc), "\\u%04x", (unsigned)c);
203 if (json_puts(j, esc) < 0)
204 return -1;
205 continue;
206 }
207
208 /* literal byte */
209 if (json_putc(j, (char)c) < 0)
210 return -1;
211 }
212
213 /* closing quote */
214 if (json_putc(j, '"') < 0)
215 return -1;
216
217 return 0;
218}
219
220void json_free(struct json* j)
221{
222 if (!j)
223 return;
224
225 free(j->buf);
226 j->buf = NULL;
227 j->len = 0;
228 j->cap = 0;
229}
230
231int json_library(struct json* j, const struct library* l)
232{
233 memset(j, 0, sizeof(*j));
234
235 /* header */
236 if (json_puts(j, "{\"proto\":1,\"items\":[") < 0)
237 goto fail;
238
239 /* items[] */
240 for (size_t i = 0; i < l->len; i++) {
241 /* item separator */
242 if (i > 0) {
243 if (json_putc(j, ',') < 0)
244 goto fail;
245 }
246
247 /* object open */
248 if (json_puts(j, "{\"id\":") < 0)
249 goto fail;
250 if (json_hex64(j, l->items[i].id) < 0)
251 goto fail;
252
253 /* path */
254 if (json_puts(j, ",\"path\":") < 0)
255 goto fail;
256 if (json_string(j, l->items[i].path) < 0)
257 goto fail;
258
259 /* object close */
260 if (json_putc(j, '}') < 0)
261 goto fail;
262 }
263
264 /* footer */
265 if (json_puts(j, "]}") < 0)
266 goto fail;
267
268 return 0;
269
270fail:
271 json_free(j);
272 return -1;
273}
274
275int json_meta(struct json* j, const struct item* it, size_t size, long mtime, const char* type)
276{
277 memset(j, 0, sizeof(*j));
278
279 if (json_puts(j, "{\"proto\":1,\"id\":") < 0)
280 goto fail;
281 if (json_hex64(j, it->id) < 0)
282 goto fail;
283
284 if (json_puts(j, ",\"path\":") < 0)
285 goto fail;
286 if (json_string(j, it->path) < 0)
287 goto fail;
288
289 /* basename */
290 if (json_puts(j, ",\"name\":") < 0)
291 goto fail;
292 if (json_string(j, json_basename(it->path)) < 0)
293 goto fail;
294
295 if (json_puts(j, ",\"size\":") < 0)
296 goto fail;
297
298 /* decimal size */
299 char tmp[32];
300 snprintf(tmp, sizeof(tmp), "%zu", size);
301 if (json_puts(j, tmp) < 0)
302 goto fail;
303
304
305 /* mtime (epoch seconds) */
306 if (json_puts(j, ",\"mtime\":") < 0)
307 goto fail;
308 snprintf(tmp, sizeof(tmp), "%ld", mtime);
309 if (json_puts(j, tmp) < 0)
310 goto fail;
311
312 /* formats */
313 if (json_puts(j, ",\"type\":") < 0)
314 goto fail;
315 if (json_string(j, type) < 0)
316 goto fail;
317
318 if (json_puts(j, ",\"kind\":") < 0)
319 goto fail;
320 if (json_string(j, json_kind_from_type(type)) < 0)
321 goto fail;
322
323 if (json_putc(j, '}') < 0)
324 goto fail;
325
326 return 0;
327
328fail:
329 json_free(j);
330 return -1;
331}
332