commit fb50c4d
uint
·
2026-04-28 21:20:23 +0000 UTC
parent 2161d48
reorganise http_handle into route_dispatch
1 files changed,
+281,
-229
+281,
-229
1@@ -34,14 +34,19 @@ struct response {
2
3 static void auth_delay_sleep(void);
4 static int cors_build(char* out, size_t outsz, const char* hdr, int preflight);
5+static int cors_origin_allowed(const char* origin);
6 static const struct item* find_item(uint64_t id);
7 static int item_path_for_id(char out[4096], uint64_t id, const struct user* u);
8 static const char* mime_from_path(const char* path);
9 static int parse_hex64(const char* s, uint64_t* out);
10-static int path_item(struct item* it, const char* path, const char* prefix, const struct user* u, int c, const char* hdr);
11 static int parse_range(const char* hdr, size_t total, size_t* start, size_t* end);
12 static int read_request(int c, char* method, size_t msz, char* path, size_t psz, char* hdr, size_t hsz);
13 static void reply(int c, const char* hdr, const struct response* res);
14+static int route_dispatch(int c, const char* hdr, const char* path, const struct user* u, int head_only);
15+static int route_item_path(struct item* it, const char* path, const char* prefix, const struct user* u, int c, const char* hdr);
16+static int route_library(int c, const char* hdr, const struct user* u, int head_only);
17+static int route_method(int c, const char* hdr, const char* method, int* head_only);
18+static int route_rescan(int c, const char* hdr, const struct user* u);
19 static int stat_item(const struct item* it, struct stat* st);
20 static int stream_file(int c, const struct item* it, const char* hdr, int head_only);
21
22@@ -325,66 +330,6 @@ static int parse_hex64(const char* s, uint64_t* out)
23 return 0;
24 }
25
26-/**
27- * @brief Parse route id and resolve to an item
28- *
29- * @param it Output item
30- * @param path Request path
31- * @param prefix Route prefix
32- * @param u Authenticated user filter
33- * @param c Connected client socket file descriptor
34- * @param hdr Request header block
35- *
36- * @return 0=Success, 1=Response sent, -1=Failure
37- */
38-static int path_item(struct item* it, const char* path, const char* prefix, const struct user* u, int c, const char* hdr)
39-{
40- uint64_t id;
41- int fr;
42-
43- if (parse_hex64(path + strlen(prefix), &id) < 0) {
44- reply(c, hdr, &(struct response){
45- .status = HTTP_400,
46- .ctype = HTTP_TEXT,
47- .extra = NULL,
48- .body = "Bad Request\n",
49- .len = sizeof("Bad Request\n") - 1,
50- .send_body = 1,
51- .preflight = 0,
52- });
53- return 1;
54- }
55-
56- fr = item_path_for_id(it->path, id, u);
57- if (fr == 1) {
58- reply(c, hdr, &(struct response){
59- .status = HTTP_404,
60- .ctype = HTTP_TEXT,
61- .extra = NULL,
62- .body = "Not Found\n",
63- .len = sizeof("Not Found\n") - 1,
64- .send_body = 1,
65- .preflight = 0,
66- });
67- return 1;
68- }
69- if (fr < 0) {
70- reply(c, hdr, &(struct response){
71- .status = HTTP_500,
72- .ctype = HTTP_TEXT,
73- .extra = NULL,
74- .body = "Server Error\n",
75- .len = sizeof("Server Error\n") - 1,
76- .send_body = 1,
77- .preflight = 0,
78- });
79- return -1;
80- }
81-
82- it->id = id;
83- return 0;
84-}
85-
86 /**
87 * @brief Parse HTTP Range header
88 *
89@@ -588,6 +533,279 @@ static void reply(int c, const char* hdr, const struct response* res)
90 (void)write_all(c, res->body, res->len);
91 }
92
93+/**
94+ * @brief Dispatch request path to route handler
95+ *
96+ * @param c Connected client socket file descriptor
97+ * @param hdr Request header block
98+ * @param path Request path
99+ * @param u Authenticated user filter
100+ * @param head_only Whether request method is HEAD
101+ *
102+ * @return 0=Handled, -1=Failure
103+ */
104+static int route_dispatch(int c, const char* hdr, const char* path, const struct user* u, int head_only)
105+{
106+ if (strcmp(path, "/ping") == 0) {
107+ LOG(verbose_log, "HTTP", "Route /ping");
108+ reply(c, hdr, &(struct response){ HTTP_200, HTTP_TEXT, NULL, "OK\n", sizeof("OK\n") - 1, 1, 0 });
109+ return 0;
110+ }
111+
112+ if (strcmp(path, "/library") == 0)
113+ return route_library(c, hdr, u, head_only);
114+
115+ if (strcmp(path, "/rescan") == 0)
116+ return route_rescan(c, hdr, u);
117+
118+ if (strncmp(path, "/stream/", 8) == 0) {
119+ struct item tmp;
120+ char rel[4096];
121+
122+ LOG(verbose_log, "HTTP", "Route /stream");
123+ tmp.path = rel;
124+
125+ int rc = route_item_path(&tmp, path, "/stream/", u, c, hdr);
126+ if (rc != 0)
127+ return rc < 0 ? -1 : 0;
128+
129+ return stream_file(c, &tmp, hdr, head_only);
130+ }
131+
132+ if (strncmp(path, "/meta/", 6) == 0) {
133+ struct item tmp;
134+ char rel[4096];
135+ struct stat st;
136+ const char* type;
137+ struct json j;
138+
139+ LOG(verbose_log, "HTTP", "Route /meta");
140+ tmp.path = rel;
141+
142+ int rc = route_item_path(&tmp, path, "/meta/", u, c, hdr);
143+ if (rc != 0)
144+ return rc < 0 ? -1 : 0;
145+
146+ if (stat_item(&tmp, &st) < 0) {
147+ reply(c, hdr, &(struct response){ HTTP_404, HTTP_TEXT, NULL, "Not Found\n", sizeof("Not Found\n") - 1, 1, 0 });
148+ return 0;
149+ }
150+
151+ type = mime_from_path(tmp.path);
152+ if (json_meta(&j, &tmp, (size_t)st.st_size, (long)st.st_mtime, type) < 0) {
153+ reply(c, hdr, &(struct response){ HTTP_500, HTTP_TEXT, NULL, "JSON Failed\n", sizeof("JSON Failed\n") - 1, 1, 0 });
154+ return -1;
155+ }
156+
157+ reply(c, hdr, &(struct response){ HTTP_200, HTTP_JSON, NULL, j.buf, j.len, !head_only, 0 });
158+ json_free(&j);
159+ return 0;
160+ }
161+
162+ LOG(verbose_log, "HTTP", "Route not found %s", path);
163+ reply(c, hdr, &(struct response){ HTTP_404, HTTP_TEXT, NULL, "Not Found\n", sizeof("Not Found\n") - 1, 1, 0 });
164+ return 0;
165+}
166+
167+/**
168+ * @brief Parse route id and resolve to an item
169+ *
170+ * @param it Output item
171+ * @param path Request path
172+ * @param prefix Route prefix
173+ * @param u Authenticated user filter
174+ * @param c Connected client socket file descriptor
175+ * @param hdr Request header block
176+ *
177+ * @return 0=Success, 1=Response sent, -1=Failure
178+ */
179+static int route_item_path(struct item* it, const char* path, const char* prefix, const struct user* u, int c, const char* hdr)
180+{
181+ uint64_t id;
182+ int fr;
183+
184+ if (parse_hex64(path + strlen(prefix), &id) < 0) {
185+ reply(c, hdr, &(struct response){
186+ .status = HTTP_400,
187+ .ctype = HTTP_TEXT,
188+ .extra = NULL,
189+ .body = "Bad Request\n",
190+ .len = sizeof("Bad Request\n") - 1,
191+ .send_body = 1,
192+ .preflight = 0,
193+ });
194+ return 1;
195+ }
196+
197+ fr = item_path_for_id(it->path, id, u);
198+ if (fr == 1) {
199+ reply(c, hdr, &(struct response){
200+ .status = HTTP_404,
201+ .ctype = HTTP_TEXT,
202+ .extra = NULL,
203+ .body = "Not Found\n",
204+ .len = sizeof("Not Found\n") - 1,
205+ .send_body = 1,
206+ .preflight = 0,
207+ });
208+ return 1;
209+ }
210+ if (fr < 0) {
211+ reply(c, hdr, &(struct response){
212+ .status = HTTP_500,
213+ .ctype = HTTP_TEXT,
214+ .extra = NULL,
215+ .body = "Server Error\n",
216+ .len = sizeof("Server Error\n") - 1,
217+ .send_body = 1,
218+ .preflight = 0,
219+ });
220+ return -1;
221+ }
222+
223+ it->id = id;
224+ return 0;
225+}
226+
227+/**
228+ * @brief Handle /library route
229+ *
230+ * @param c Connected client socket file descriptor
231+ * @param hdr Request header block
232+ * @param u Authenticated user filter
233+ * @param head_only Whether request method is HEAD
234+ *
235+ * @return 0=Handled, -1=Failure
236+ */
237+static int route_library(int c, const char* hdr, const struct user* u, int head_only)
238+{
239+ struct json j;
240+ struct library view;
241+
242+ LOG(verbose_log, "HTTP", "Route /library");
243+ mtx_lock(&lib_lock);
244+ memset(&view, 0, sizeof(view));
245+
246+ if (!u) {
247+ view = lib;
248+ }
249+ else {
250+ size_t i;
251+
252+ if (lib.len > 0) {
253+ view.items = calloc(lib.len, sizeof(*view.items));
254+ if (!view.items) {
255+ mtx_unlock(&lib_lock);
256+ reply(c, hdr, &(struct response){ HTTP_500, HTTP_TEXT, NULL, "Server Error\n", sizeof("Server Error\n") - 1, 1, 0 });
257+ return -1;
258+ }
259+ }
260+
261+ for (i = 0; i < lib.len; i++) {
262+ if (!user_allows_path(u, lib.items[i].path))
263+ continue;
264+ view.items[view.len++] = lib.items[i];
265+ }
266+ view.cap = view.len;
267+ }
268+
269+ if (json_library(&j, &view) < 0) {
270+ if (u)
271+ free(view.items);
272+
273+ mtx_unlock(&lib_lock);
274+ LOG(verbose_log, "JSON", "Encode FAILED");
275+ reply(c, hdr, &(struct response){ HTTP_500, HTTP_TEXT, NULL, "JSON Encode Failed\n", sizeof("JSON Encode Failed\n") - 1, 1, 0 });
276+ return -1;
277+ }
278+
279+ mtx_unlock(&lib_lock);
280+ LOG(verbose_log, "JSON", "Encoded bytes %zu bytes", j.len);
281+ reply(c, hdr, &(struct response){ HTTP_200, HTTP_JSON, NULL, j.buf, j.len, !head_only, 0 });
282+ json_free(&j);
283+
284+ if (u)
285+ free(view.items);
286+ return 0;
287+}
288+
289+/**
290+ * @brief Handle request method and OPTIONS
291+ *
292+ * @param c Connected client socket file descriptor
293+ * @param hdr Request header block
294+ * @param method HTTP method
295+ * @param head_only Output HEAD flag
296+ *
297+ * @return 0=Continue, 1=Response sent
298+ */
299+static int route_method(int c, const char* hdr, const char* method, int* head_only)
300+{
301+ if (strcmp(method, "GET") == 0) {
302+ *head_only = 0;
303+ return 0;
304+ }
305+ if (strcmp(method, "HEAD") == 0) {
306+ *head_only = 1;
307+ return 0;
308+ }
309+ if (strcmp(method, "OPTIONS") == 0) {
310+ reply(c, hdr, &(struct response){ HTTP_204, NULL, NULL, NULL, 0, 0, 1 });
311+ return 1;
312+ }
313+
314+ LOG(verbose_log, "HTTP", "Method forbidden %s", method);
315+ reply(c, hdr, &(struct response){ HTTP_405, HTTP_TEXT, NULL, "Method Not Allowed\n", sizeof("Method Not Allowed\n") - 1, 1, 0 });
316+ return 1;
317+}
318+
319+/**
320+ * @brief Handle /rescan route
321+ *
322+ * @param c Connected client socket file descriptor
323+ * @param hdr Request header block
324+ * @param u Authenticated user
325+ *
326+ * @return 0=Handled, -1=Failure
327+ */
328+static int route_rescan(int c, const char* hdr, const struct user* u)
329+{
330+ struct timespec t0;
331+ struct timespec t1;
332+ size_t before = 0;
333+ size_t after = 0;
334+
335+ LOG(verbose_log, "HTTP", "Route /rescan");
336+ if (users.len == 0) {
337+ LOG(true, "HTTP", "Rescan forbidden Auth disabled");
338+ reply(c, hdr, &(struct response){ HTTP_403, HTTP_TEXT, NULL, "Forbidden\n", sizeof("Forbidden\n") - 1, 1, 0 });
339+ return 0;
340+ }
341+
342+ LOG(true, "SCAN", "Rescan requested %s", u->name);
343+ clock_gettime(CLOCK_MONOTONIC, &t0);
344+
345+ mtx_lock(&lib_lock);
346+ before = lib.len;
347+ LOG(verbose_log, "SCAN", "Rescan begin %s (%zu items)", media_dir, before);
348+ int ok = scan_library_rescan(&lib, media_dir);
349+ after = lib.len;
350+ mtx_unlock(&lib_lock);
351+
352+ clock_gettime(CLOCK_MONOTONIC, &t1);
353+ long ms = (t1.tv_sec - t0.tv_sec) * 1000L + (t1.tv_nsec - t0.tv_nsec) / 1000000L;
354+
355+ if (ok < 0) {
356+ LOG(true, "SCAN", "Rescan FAILED %s (%ld ms)", media_dir, ms);
357+ reply(c, hdr, &(struct response){ HTTP_500, HTTP_TEXT, NULL, "Rescan Failed\n", sizeof("Rescan Failed\n") - 1, 1, 0 });
358+ return -1;
359+ }
360+
361+ LOG(true, "SCAN", "Rescan OK %zu -> %zu (%ld ms)", before, after, ms);
362+ reply(c, hdr, &(struct response){ HTTP_200, HTTP_TEXT, NULL, "OK\n", sizeof("OK\n") - 1, 1, 0 });
363+ return 0;
364+}
365+
366 /**
367 * @brief Stat media item on disk
368 *
369@@ -800,27 +1018,8 @@ int http_handle(int c)
370 LOG(verbose_log, "HTTP", "Request %s %s", method, path);
371
372 int head_only = 0;
373- if (strcmp(method, "GET") == 0) {
374- head_only = 0;
375- }
376- else if (strcmp(method, "HEAD") == 0) {
377- head_only = 1;
378- }
379- else if (strcmp(method, "OPTIONS") == 0) {
380- reply(c, hdr, &(struct response){ HTTP_204, NULL, NULL, NULL, 0, 0, 1 });
381- return 0;
382- }
383- else {
384- LOG(verbose_log, "HTTP", "Method forbidden %s", method);
385- reply(c, hdr, &(struct response){ HTTP_405, HTTP_TEXT, NULL, "Method Not Allowed\n", sizeof("Method Not Allowed\n") - 1, 1, 0 });
386+ if (route_method(c, hdr, method, &head_only) != 0)
387 return 0;
388- }
389-
390- if (strcmp(path, "/ping") == 0) {
391- LOG(verbose_log, "HTTP", "Route /ping");
392- reply(c, hdr, &(struct response){ HTTP_200, HTTP_TEXT, NULL, "OK\n", sizeof("OK\n") - 1, 1, 0 });
393- return 0;
394- }
395
396 if (users.len > 0) {
397 u = users_auth_from_hdr(hdr);
398@@ -839,153 +1038,6 @@ int http_handle(int c)
399 }
400 }
401
402- if (strcmp(path, "/library") == 0) {
403- LOG(verbose_log, "HTTP", "Route /library");
404-
405- mtx_lock(&lib_lock);
406-
407- struct json j;
408- struct library view;
409- memset(&view, 0, sizeof(view));
410-
411- if (!u) {
412- view = lib;
413- }
414- else {
415- if (lib.len > 0) {
416- view.items = calloc(lib.len, sizeof(*view.items));
417- if (!view.items) {
418- mtx_unlock(&lib_lock);
419- reply(c, hdr, &(struct response){ HTTP_500, HTTP_TEXT, NULL, "Server Error\n", sizeof("Server Error\n") - 1, 1, 0 });
420- return -1;
421- }
422- }
423-
424- for (size_t i = 0; i < lib.len; i++) {
425- if (!user_allows_path(u, lib.items[i].path))
426- continue;
427- view.items[view.len++] = lib.items[i];
428- }
429- view.cap = view.len;
430- }
431-
432- if (json_library(&j, &view) < 0) {
433- if (u)
434- free(view.items);
435-
436- mtx_unlock(&lib_lock);
437-
438- LOG(verbose_log, "JSON", "Encode FAILED");
439- reply(c, hdr, &(struct response){ HTTP_500, HTTP_TEXT, NULL, "JSON Encode Failed\n", sizeof("JSON Encode Failed\n") - 1, 1, 0 });
440- return -1;
441- }
442-
443- mtx_unlock(&lib_lock);
444-
445- LOG(verbose_log, "JSON", "Encoded bytes %zu bytes", j.len);
446- reply(c, hdr, &(struct response){ HTTP_200, HTTP_JSON, NULL, j.buf, j.len, !head_only, 0 });
447- json_free(&j);
448-
449- if (u)
450- free(view.items);
451-
452- return 0;
453- }
454-
455- if (strcmp(path, "/rescan") == 0) {
456- LOG(verbose_log, "HTTP", "Route /rescan");
457-
458- if (users.len == 0) {
459- LOG(true, "HTTP", "Rescan forbidden Auth disabled");
460- reply(c, hdr, &(struct response){ HTTP_403, HTTP_TEXT, NULL, "Forbidden\n", sizeof("Forbidden\n") - 1, 1, 0 });
461- return 0;
462- }
463-
464- LOG(true, "SCAN", "Rescan requested %s", u->name);
465-
466- struct timespec t0;
467- struct timespec t1;
468- clock_gettime(CLOCK_MONOTONIC, &t0);
469-
470- size_t before = 0;
471- size_t after = 0;
472-
473- mtx_lock(&lib_lock);
474-
475- before = lib.len;
476- LOG(verbose_log, "SCAN", "Rescan begin %s (%zu items)", media_dir, before);
477-
478- int ok = scan_library_rescan(&lib, media_dir);
479-
480- after = lib.len;
481-
482- mtx_unlock(&lib_lock);
483-
484- clock_gettime(CLOCK_MONOTONIC, &t1);
485-
486- long ms = (t1.tv_sec - t0.tv_sec) * 1000L +
487- (t1.tv_nsec - t0.tv_nsec) / 1000000L;
488-
489- if (ok < 0) {
490- LOG(true, "SCAN", "Rescan FAILED %s (%ld ms)", media_dir, ms);
491- reply(c, hdr, &(struct response){ HTTP_500, HTTP_TEXT, NULL, "Rescan Failed\n", sizeof("Rescan Failed\n") - 1, 1, 0 });
492- return -1;
493- }
494-
495- LOG(true, "SCAN", "Rescan OK %zu -> %zu (%ld ms)", before, after, ms);
496-
497- reply(c, hdr, &(struct response){ HTTP_200, HTTP_TEXT, NULL, "OK\n", sizeof("OK\n") - 1, 1, 0 });
498- return 0;
499- }
500-
501- if (strncmp(path, "/stream/", 8) == 0) {
502- LOG(verbose_log, "HTTP", "Route /stream");
503-
504- struct item tmp;
505- char rel[4096];
506- tmp.path = rel;
507-
508- int rc = path_item(&tmp, path, "/stream/", u, c, hdr);
509- if (rc != 0)
510- return rc < 0 ? -1 : 0;
511-
512- return stream_file(c, &tmp, hdr, head_only);
513- }
514-
515- if (strncmp(path, "/meta/", 6) == 0) {
516- LOG(verbose_log, "HTTP", "Route /meta");
517-
518- struct item tmp;
519- char rel[4096];
520- tmp.path = rel;
521-
522- int rc = path_item(&tmp, path, "/meta/", u, c, hdr);
523- if (rc != 0)
524- return rc < 0 ? -1 : 0;
525-
526- struct stat st;
527- if (stat_item(&tmp, &st) < 0) {
528- reply(c, hdr, &(struct response){ HTTP_404, HTTP_TEXT, NULL, "Not Found\n", sizeof("Not Found\n") - 1, 1, 0 });
529- return 0;
530- }
531-
532- const char* type = mime_from_path(tmp.path);
533-
534- struct json j;
535- if (json_meta(&j, &tmp, (size_t)st.st_size, (long)st.st_mtime, type) < 0) {
536- reply(c, hdr, &(struct response){ HTTP_500, HTTP_TEXT, NULL, "JSON Failed\n", sizeof("JSON Failed\n") - 1, 1, 0 });
537- return -1;
538- }
539-
540- reply(c, hdr, &(struct response){ HTTP_200, HTTP_JSON, NULL, j.buf, j.len, !head_only, 0 });
541- json_free(&j);
542-
543- return 0;
544- }
545-
546- LOG(verbose_log, "HTTP", "Route not found %s", path);
547- reply(c, hdr, &(struct response){ HTTP_404, HTTP_TEXT, NULL, "Not Found\n", sizeof("Not Found\n") - 1, 1, 0 });
548-
549- return 0;
550+ return route_dispatch(c, hdr, path, u, head_only);
551 }
552