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