master uint/parados / server / json.c
  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