commit 2161d48

uint  ·  2026-04-28 20:50:53 +0000 UTC
parent 3093334
add struct response, remove reply_...

unify all responses to reply() and reply using struct response
1 files changed,  +111, -120
+111, -120
  1@@ -21,6 +21,17 @@
  2 #include "users.h"
  3 #include "util.h"
  4 
  5+struct response {
  6+	const char* status;
  7+	const char* ctype;
  8+	const char* extra;
  9+	const void* body;
 10+
 11+	size_t      len;
 12+	int         send_body;
 13+	int         preflight;
 14+};
 15+
 16 static void auth_delay_sleep(void);
 17 static int cors_build(char* out, size_t outsz, const char* hdr, int preflight);
 18 static const struct item* find_item(uint64_t id);
 19@@ -30,11 +41,7 @@ static int parse_hex64(const char* s, uint64_t* out);
 20 static int path_item(struct item* it, const char* path, const char* prefix, const struct user* u, int c, const char* hdr);
 21 static int parse_range(const char* hdr, size_t total, size_t* start, size_t* end);
 22 static int read_request(int c, char* method, size_t msz, char* path, size_t psz, char* hdr, size_t hsz);
 23-static void reply(int c, const char* hdr, const char* status, const char* ctype, const char* extra, const void* body, size_t len, int send_body, int preflight);
 24-static void reply_json(int c, const char* hdr, const char* status, const char* body, size_t len, int send_body);
 25-static void reply_preflight(int c, const char* hdr);
 26-static void reply_text(int c, const char* hdr, const char* status, const char* body);
 27-static void reply_unauth(int c, const char* hdr, int send_body);
 28+static void reply(int c, const char* hdr, const struct response* res);
 29 static int stat_item(const struct item* it, struct stat* st);
 30 static int stream_file(int c, const struct item* it, const char* hdr, int head_only);
 31 
 32@@ -336,17 +343,41 @@ static int path_item(struct item* it, const char* path, const char* prefix, cons
 33 	int fr;
 34 
 35 	if (parse_hex64(path + strlen(prefix), &id) < 0) {
 36-		reply_text(c, hdr, HTTP_400, "Bad Request\n");
 37+		reply(c, hdr, &(struct response){
 38+			.status = HTTP_400,
 39+			.ctype = HTTP_TEXT,
 40+			.extra = NULL,
 41+			.body = "Bad Request\n",
 42+			.len = sizeof("Bad Request\n") - 1,
 43+			.send_body = 1,
 44+			.preflight = 0,
 45+		});
 46 		return 1;
 47 	}
 48 
 49 	fr = item_path_for_id(it->path, id, u);
 50 	if (fr == 1) {
 51-		reply_text(c, hdr, HTTP_404, "Not Found\n");
 52+		reply(c, hdr, &(struct response){
 53+			.status = HTTP_404,
 54+			.ctype = HTTP_TEXT,
 55+			.extra = NULL,
 56+			.body = "Not Found\n",
 57+			.len = sizeof("Not Found\n") - 1,
 58+			.send_body = 1,
 59+			.preflight = 0,
 60+		});
 61 		return 1;
 62 	}
 63 	if (fr < 0) {
 64-		reply_text(c, hdr, HTTP_500, "Server Error\n");
 65+		reply(c, hdr, &(struct response){
 66+			.status = HTTP_500,
 67+			.ctype = HTTP_TEXT,
 68+			.extra = NULL,
 69+			.body = "Server Error\n",
 70+			.len = sizeof("Server Error\n") - 1,
 71+			.send_body = 1,
 72+			.preflight = 0,
 73+		});
 74 		return -1;
 75 	}
 76 
 77@@ -507,20 +538,14 @@ static int read_request(int c, char* method, size_t msz, char* path, size_t psz,
 78  *
 79  * @param c Connected client socket file descriptor
 80  * @param hdr Request header block
 81- * @param status HTTP status line
 82- * @param ctype Content-Type header string
 83- * @param extra Extra response headers
 84- * @param body Response body
 85- * @param len Response body length
 86- * @param send_body Whether to send body bytes
 87- * @param preflight Whether this is a preflight response
 88+ * @param res Response descriptor
 89  */
 90-static void reply(int c, const char* hdr, const char* status, const char* ctype, const char* extra, const void* body, size_t len, int send_body, int preflight)
 91+static void reply(int c, const char* hdr, const struct response* res)
 92 {
 93 	char resp[HTTP_RESP_MAX];
 94 	char cors[512];
 95 
 96-	(void)cors_build(cors, sizeof(cors), hdr, preflight);
 97+	(void)cors_build(cors, sizeof(cors), hdr, res->preflight);
 98 
 99 	int n = snprintf(
100 		resp, sizeof(resp),
101@@ -533,92 +558,34 @@ static void reply(int c, const char* hdr, const char* status, const char* ctype,
102 		HTTP_CLOSE
103 		"\r\n",
104 
105-		status,
106+		res->status,
107 		cors,
108-		(ctype && ctype[0]) ? ctype : "",
109-		(extra && extra[0]) ? extra : "",
110-		len
111+		(res->ctype && res->ctype[0]) ? res->ctype : "",
112+		(res->extra && res->extra[0]) ? res->extra : "",
113+		res->len
114 	);
115 
116 	if (n < 0)
117 		return;
118 
119 	if ((size_t)n >= sizeof(resp)) {
120-		/* fail if header too large */
121-		reply_text(c, hdr, HTTP_500, "Server Error\n");
122+		struct response err = {
123+			.status = HTTP_500,
124+			.ctype = HTTP_TEXT,
125+			.extra = NULL,
126+			.body = "Server Error\n",
127+			.len = sizeof("Server Error\n") - 1,
128+			.send_body = 1,
129+			.preflight = 0,
130+		};
131+		reply(c, hdr, &err);
132 		return;
133 	}
134 
135 	(void)write_all(c, resp, (size_t)n);
136 
137-	if (send_body && body && len > 0)
138-		(void)write_all(c, body, len);
139-}
140-
141-/**
142- * @brief Send JSON HTTP response
143- *
144- * @param c Connected client socket file descriptor
145- * @param hdr Request header block
146- * @param status HTTP status line
147- * @param body Response body
148- * @param len Response body length
149- * @param send_body Whether to send body bytes
150- */
151-static void reply_json(int c, const char* hdr, const char* status, const char* body, size_t len, int send_body)
152-{
153-	reply(c, hdr, status, HTTP_JSON, NULL, body, len, send_body, 0);
154-}
155-
156-/**
157- * @brief Send CORS preflight response
158- *
159- * @param c Connected client socket file descriptor
160- * @param hdr Request header block
161- */
162-static void reply_preflight(int c, const char* hdr)
163-{
164-	reply(c, hdr, HTTP_204, NULL, NULL, NULL, (size_t)0, 0, 1);
165-}
166-
167-/**
168- * @brief Send text/plain HTTP response
169- *
170- * @param c Connected client socket file descriptor
171- * @param hdr Request header block
172- * @param status HTTP status line
173- * @param body Response body
174- */
175-static void reply_text(int c, const char* hdr, const char* status, const char* body)
176-{
177-	size_t len = strlen(body);
178-	reply(c, hdr, status, HTTP_TEXT, NULL, body, len, 1, 0);
179-}
180-
181-/**
182- * @brief Send 401 Unauthorized response
183- *
184- * @param c Connected client socket file descriptor
185- * @param hdr Request header block
186- * @param send_body Whether to send body bytes
187- */
188-static void reply_unauth(int c, const char* hdr, int send_body)
189-{
190-	const char* body = "unauthorized\n";
191-	size_t blen = strlen(body);
192-
193-	auth_delay_sleep();
194-
195-	reply(
196-		c, hdr,
197-		"HTTP/1.1 401 Unauthorized\r\n",
198-		HTTP_TEXT,
199-		"WWW-Authenticate: Basic realm=\"parados\"\r\n",
200-		body,
201-		blen,
202-		send_body,
203-		0
204-	);
205+	if (res->send_body && res->body && res->len > 0)
206+		(void)write_all(c, res->body, res->len);
207 }
208 
209 /**
210@@ -662,7 +629,7 @@ static int stream_file(int c, const struct item* it, const char* hdr, int head_o
211 	/* build absolute path */
212 	if (join_path(full, sizeof(full), media_dir, it->path) < 0) {
213 		LOG(verbose_log, "HTTP", "Stream FAILED      Path too long");
214-		reply_text(c, hdr, HTTP_500, "Server Error\n");
215+		reply(c, hdr, &(struct response){ HTTP_500, HTTP_TEXT, NULL, "Server Error\n", sizeof("Server Error\n") - 1, 1, 0 });
216 		return -1;
217 	}
218 
219@@ -670,7 +637,7 @@ static int stream_file(int c, const struct item* it, const char* hdr, int head_o
220 	int fd = open(full, O_RDONLY);
221 	if (fd < 0) {
222 		LOG(verbose_log, "HTTP", "Open FAILED        %s", it->path);
223-		reply_text(c, hdr, HTTP_404, "Not Found\n");
224+		reply(c, hdr, &(struct response){ HTTP_404, HTTP_TEXT, NULL, "Not Found\n", sizeof("Not Found\n") - 1, 1, 0 });
225 		return 0;
226 	}
227 
228@@ -678,14 +645,14 @@ static int stream_file(int c, const struct item* it, const char* hdr, int head_o
229 	struct stat st;
230 	if (fstat(fd, &st) < 0) {
231 		close(fd);
232-		reply_text(c, hdr, HTTP_500, "Server Error\n");
233+		reply(c, hdr, &(struct response){ HTTP_500, HTTP_TEXT, NULL, "Server Error\n", sizeof("Server Error\n") - 1, 1, 0 });
234 		return -1;
235 	}
236 
237 	/* reject non-regular files */
238 	if (!S_ISREG(st.st_mode)) {
239 		close(fd);
240-		reply_text(c, hdr, HTTP_404, "Not Found\n");
241+		reply(c, hdr, &(struct response){ HTTP_404, HTTP_TEXT, NULL, "Not Found\n", sizeof("Not Found\n") - 1, 1, 0 });
242 		return 0;
243 	}
244 
245@@ -697,20 +664,20 @@ static int stream_file(int c, const struct item* it, const char* hdr, int head_o
246 	int partial = parse_range(hdr, total, &start, &end);
247 	if (partial == RANGE_BAD) {
248 		close(fd);
249-		reply_text(c, hdr, HTTP_400, "Bad Range\n");
250+		reply(c, hdr, &(struct response){ HTTP_400, HTTP_TEXT, NULL, "Bad Range\n", sizeof("Bad Range\n") - 1, 1, 0 });
251 		return 0;
252 	}
253 
254 	if (partial == RANGE_UNSAT) {
255 		close(fd);
256-		reply_text(c, hdr, HTTP_416, "Range Not Satisfiable\n");
257+		reply(c, hdr, &(struct response){ HTTP_416, HTTP_TEXT, NULL, "Range Not Satisfiable\n", sizeof("Range Not Satisfiable\n") - 1, 1, 0 });
258 		return 0;
259 	}
260 
261 	if (partial == RANGE_OK) {
262 		if (lseek(fd, (off_t)start, SEEK_SET) < 0) {
263 			close(fd);
264-			reply_text(c, hdr, HTTP_500, "Server Error\n");
265+			reply(c, hdr, &(struct response){ HTTP_500, HTTP_TEXT, NULL, "Server Error\n", sizeof("Server Error\n") - 1, 1, 0 });
266 			return -1;
267 		}
268 	}
269@@ -723,7 +690,7 @@ static int stream_file(int c, const struct item* it, const char* hdr, int head_o
270 
271 	if (snprintf(ctype, sizeof(ctype), HTTP_CTYPE, type) >= (int)sizeof(ctype)) {
272 		close(fd);
273-		reply_text(c, hdr, HTTP_500, "Server Error\n");
274+		reply(c, hdr, &(struct response){ HTTP_500, HTTP_TEXT, NULL, "Server Error\n", sizeof("Server Error\n") - 1, 1, 0 });
275 		return -1;
276 	}
277 
278@@ -736,21 +703,37 @@ static int stream_file(int c, const struct item* it, const char* hdr, int head_o
279 			start, end, total
280 		) >= (int)sizeof(extra)) {
281 			close(fd);
282-			reply_text(c, hdr, HTTP_500, "Server Error\n");
283+			reply(c, hdr, &(struct response){ HTTP_500, HTTP_TEXT, NULL, "Server Error\n", sizeof("Server Error\n") - 1, 1, 0 });
284 			return -1;
285 		}
286 
287-		reply(c, hdr, HTTP_206, ctype, extra, NULL, len, 0, 0);
288+		reply(c, hdr, &(struct response){
289+			.status = HTTP_206,
290+			.ctype = ctype,
291+			.extra = extra,
292+			.body = NULL,
293+			.len = len,
294+			.send_body = 0,
295+			.preflight = 0,
296+		});
297 	}
298 	else {
299 		/* non-partial */
300 		if (snprintf(extra, sizeof(extra), HTTP_RANGE) >= (int)sizeof(extra)) {
301 			close(fd);
302-			reply_text(c, hdr, HTTP_500, "Server Error\n");
303+			reply(c, hdr, &(struct response){ HTTP_500, HTTP_TEXT, NULL, "Server Error\n", sizeof("Server Error\n") - 1, 1, 0 });
304 			return -1;
305 		}
306 
307-		reply(c, hdr, HTTP_200, ctype, extra, NULL, len, 0, 0);
308+		reply(c, hdr, &(struct response){
309+			.status = HTTP_200,
310+			.ctype = ctype,
311+			.extra = extra,
312+			.body = NULL,
313+			.len = len,
314+			.send_body = 0,
315+			.preflight = 0,
316+		});
317 	}
318 
319 	/* HEAD: no body */
320@@ -805,14 +788,13 @@ int http_handle(int c)
321 	char method[16];
322 	char path[1024];
323 	char hdr[HTTP_REQ_MAX];
324-
325 	const struct user* u = NULL;
326 	method[0] = '\0';
327 	path[0] = '\0';
328 	hdr[0] = '\0';
329 
330 	if (read_request(c, method, sizeof(method), path, sizeof(path), hdr, sizeof(hdr)) < 0) {
331-		reply_text(c, hdr, HTTP_400, "Bad Request\n");
332+		reply(c, hdr, &(struct response){ HTTP_400, HTTP_TEXT, NULL, "Bad Request\n", sizeof("Bad Request\n") - 1, 1, 0 });
333 		return -1;
334 	}
335 	LOG(verbose_log, "HTTP", "Request            %s %s", method, path);
336@@ -825,25 +807,34 @@ int http_handle(int c)
337 		head_only = 1;
338 	}
339 	else if (strcmp(method, "OPTIONS") == 0) {
340-		reply_preflight(c, hdr);
341+		reply(c, hdr, &(struct response){ HTTP_204, NULL, NULL, NULL, 0, 0, 1 });
342 		return 0;
343 	}
344 	else {
345 		LOG(verbose_log, "HTTP", "Method forbidden   %s", method);
346-		reply_text(c, hdr, HTTP_405, "Method Not Allowed\n");
347+		reply(c, hdr, &(struct response){ HTTP_405, HTTP_TEXT, NULL, "Method Not Allowed\n", sizeof("Method Not Allowed\n") - 1, 1, 0 });
348 		return 0;
349 	}
350 
351 	if (strcmp(path, "/ping") == 0) {
352 		LOG(verbose_log, "HTTP", "Route              /ping");
353-		reply_text(c, hdr, HTTP_200, "OK\n");
354+		reply(c, hdr, &(struct response){ HTTP_200, HTTP_TEXT, NULL, "OK\n", sizeof("OK\n") - 1, 1, 0 });
355 		return 0;
356 	}
357 
358 	if (users.len > 0) {
359 		u = users_auth_from_hdr(hdr);
360 		if (!u) {
361-			reply_unauth(c, hdr, !head_only);
362+			auth_delay_sleep();
363+			reply(c, hdr, &(struct response){
364+				.status = "HTTP/1.1 401 Unauthorized\r\n",
365+				.ctype = HTTP_TEXT,
366+				.extra = "WWW-Authenticate: Basic realm=\"parados\"\r\n",
367+				.body = "unauthorized\n",
368+				.len = sizeof("unauthorized\n") - 1,
369+				.send_body = !head_only,
370+				.preflight = 0,
371+			});
372 			return 0;
373 		}
374 	}
375@@ -865,7 +856,7 @@ int http_handle(int c)
376 				view.items = calloc(lib.len, sizeof(*view.items));
377 				if (!view.items) {
378 					mtx_unlock(&lib_lock);
379-					reply_text(c, hdr, HTTP_500, "Server Error\n");
380+					reply(c, hdr, &(struct response){ HTTP_500, HTTP_TEXT, NULL, "Server Error\n", sizeof("Server Error\n") - 1, 1, 0 });
381 					return -1;
382 				}
383 			}
384@@ -885,15 +876,14 @@ int http_handle(int c)
385 			mtx_unlock(&lib_lock);
386 
387 			LOG(verbose_log, "JSON", "Encode     FAILED");
388-			reply_text(c, hdr, HTTP_500, "JSON Encode Failed\n");
389+			reply(c, hdr, &(struct response){ HTTP_500, HTTP_TEXT, NULL, "JSON Encode Failed\n", sizeof("JSON Encode Failed\n") - 1, 1, 0 });
390 			return -1;
391 		}
392 
393 		mtx_unlock(&lib_lock);
394 
395 		LOG(verbose_log, "JSON", "Encoded bytes      %zu bytes", j.len);
396-
397-		reply_json(c, hdr, HTTP_200, j.buf, j.len, !head_only);
398+		reply(c, hdr, &(struct response){ HTTP_200, HTTP_JSON, NULL, j.buf, j.len, !head_only, 0 });
399 		json_free(&j);
400 
401 		if (u)
402@@ -907,7 +897,7 @@ int http_handle(int c)
403 
404 		if (users.len == 0) {
405 			LOG(true, "HTTP", "Rescan forbidden   Auth disabled");
406-			reply_text(c, hdr, HTTP_403, "Forbidden\n");
407+			reply(c, hdr, &(struct response){ HTTP_403, HTTP_TEXT, NULL, "Forbidden\n", sizeof("Forbidden\n") - 1, 1, 0 });
408 			return 0;
409 		}
410 
411@@ -938,13 +928,13 @@ int http_handle(int c)
412 
413 		if (ok < 0) {
414 			LOG(true, "SCAN", "Rescan FAILED      %s (%ld ms)", media_dir, ms);
415-			reply_text(c, hdr, HTTP_500, "Rescan Failed\n");
416+			reply(c, hdr, &(struct response){ HTTP_500, HTTP_TEXT, NULL, "Rescan Failed\n", sizeof("Rescan Failed\n") - 1, 1, 0 });
417 			return -1;
418 		}
419 
420 		LOG(true, "SCAN", "Rescan OK          %zu -> %zu (%ld ms)", before, after, ms);
421 
422-		reply_text(c, hdr, HTTP_200, "OK\n");
423+		reply(c, hdr, &(struct response){ HTTP_200, HTTP_TEXT, NULL, "OK\n", sizeof("OK\n") - 1, 1, 0 });
424 		return 0;
425 	}
426 
427@@ -975,7 +965,7 @@ int http_handle(int c)
428 
429 		struct stat st;
430 		if (stat_item(&tmp, &st) < 0) {
431-			reply_text(c, hdr, HTTP_404, "Not Found\n");
432+			reply(c, hdr, &(struct response){ HTTP_404, HTTP_TEXT, NULL, "Not Found\n", sizeof("Not Found\n") - 1, 1, 0 });
433 			return 0;
434 		}
435 
436@@ -983,18 +973,19 @@ int http_handle(int c)
437 
438 		struct json j;
439 		if (json_meta(&j, &tmp, (size_t)st.st_size, (long)st.st_mtime, type) < 0) {
440-			reply_text(c, hdr, HTTP_500, "JSON Failed\n");
441+			reply(c, hdr, &(struct response){ HTTP_500, HTTP_TEXT, NULL, "JSON Failed\n", sizeof("JSON Failed\n") - 1, 1, 0 });
442 			return -1;
443 		}
444 
445-		reply_json(c, hdr, HTTP_200, j.buf, j.len, !head_only);
446+		reply(c, hdr, &(struct response){ HTTP_200, HTTP_JSON, NULL, j.buf, j.len, !head_only, 0 });
447 		json_free(&j);
448 
449 		return 0;
450 	}
451 
452 	LOG(verbose_log, "HTTP", "Route not found    %s", path);
453-	reply_text(c, hdr, HTTP_404, "Not Found\n");
454+	reply(c, hdr, &(struct response){ HTTP_404, HTTP_TEXT, NULL, "Not Found\n", sizeof("Not Found\n") - 1, 1, 0 });
455 
456 	return 0;
457 }
458+