commit 9aedfa4
uint
·
2026-01-26 16:52:29 +0000 UTC
parent 769b10a
add /meta endpoint, add Content-Type for streams (update later)
4 files changed,
+162,
-11
+115,
-4
1@@ -6,6 +6,7 @@
2 #include <stdio.h>
3 #include <stdlib.h>
4 #include <string.h>
5+#include <strings.h>
6 #include <sys/stat.h>
7 #include <sys/types.h>
8 #include <unistd.h>
9@@ -19,14 +20,17 @@
10 static const char* cistrstr(const char* hay, const char* nee);
11 static const struct item* find_item(uint64_t id);
12 static int join_path(char* out, size_t outsz, const char* a, const char* b);
13+static const char* mime_from_path(const char* path);
14 static int parse_hex64(const char* s, uint64_t* out);
15 static int parse_range(const char* hdr, size_t total, size_t* start, size_t* end);
16 static int read_request(int c, char* method, size_t msz, char* path, size_t psz, char* hdr, size_t hsz);
17 static void reply_json(int c, const char* status, const char* body, size_t len, int send_body);
18 static void reply_text(int c, const char* status, const char* body);
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 static int write_all(int fd, const void* buf, size_t n);
22
23+
24 extern struct library lib;
25
26 static const char* cistrstr(const char* hay, const char* nee)
27@@ -79,6 +83,59 @@ static int join_path(char* out, size_t outsz, const char* a, const char* b)
28 return 0;
29 }
30
31+static const char* mime_from_path(const char* path)
32+{
33+ const char* dot = strrchr(path, '.');
34+ if (!dot || dot[1] == '\0')
35+ return "application/octet-stream";
36+
37+ dot++;
38+
39+ /* audio */
40+ if (strcasecmp(dot, "mp3") == 0)
41+ return "audio/mpeg";
42+ if (strcasecmp(dot, "m4a") == 0)
43+ return "audio/mp4";
44+ if (strcasecmp(dot, "aac") == 0)
45+ return "audio/aac";
46+ if (strcasecmp(dot, "flac") == 0)
47+ return "audio/flac";
48+ if (strcasecmp(dot, "wav") == 0)
49+ return "audio/wav";
50+ if (strcasecmp(dot, "ogg") == 0)
51+ return "audio/ogg";
52+ if (strcasecmp(dot, "opus") == 0)
53+ return "audio/opus";
54+
55+ /* video */
56+ if (strcasecmp(dot, "mp4") == 0)
57+ return "video/mp4";
58+ if (strcasecmp(dot, "mkv") == 0)
59+ return "video/x-matroska";
60+ if (strcasecmp(dot, "webm") == 0)
61+ return "video/webm";
62+ if (strcasecmp(dot, "mov") == 0)
63+ return "video/quicktime";
64+
65+ /* images */
66+ if (strcasecmp(dot, "jpg") == 0)
67+ return "image/jpeg";
68+ if (strcasecmp(dot, "jpeg") == 0)
69+ return "image/jpeg";
70+ if (strcasecmp(dot, "png") == 0)
71+ return "image/png";
72+ if (strcasecmp(dot, "gif") == 0)
73+ return "image/gif";
74+ if (strcasecmp(dot, "webp") == 0)
75+ return "image/webp";
76+
77+ /* misc */
78+ if (strcasecmp(dot, "pdf") == 0)
79+ return "application/pdf";
80+
81+ return "application/octet-stream";
82+}
83+
84 static int parse_hex64(const char* s, uint64_t* out)
85 {
86 char* end = NULL;
87@@ -275,6 +332,22 @@ static void reply_text(int c, const char* status, const char* body)
88 (void)write_all(c, resp, (size_t)n);
89 }
90
91+static int stat_item(const struct item* it, struct stat* st)
92+{
93+ char full[4096];
94+
95+ if (join_path(full, sizeof(full), media_dir, it->path) < 0)
96+ return -1;
97+
98+ if (stat(full, st) < 0)
99+ return -1;
100+
101+ if (!S_ISREG(st->st_mode))
102+ return -1;
103+
104+ return 0;
105+}
106+
107 static int stream_file(int c, const struct item* it, const char* hdr, int head_only)
108 {
109 char full[4096];
110@@ -312,6 +385,7 @@ static int stream_file(int c, const struct item* it, const char* hdr, int head_o
111 size_t total = (size_t)st.st_size;
112 size_t start = 0;
113 size_t end = total ? (total - 1) : 0;
114+ const char* type = mime_from_path(it->path);
115
116 int partial = parse_range(hdr, total, &start, &end);
117 if (partial == RANGE_BAD) {
118@@ -341,28 +415,30 @@ static int stream_file(int c, const struct item* it, const char* hdr, int head_o
119 int n;
120
121 if (partial == RANGE_OK) {
122+ /* partial */
123 n = snprintf(
124 resp, sizeof(resp),
125 "%s"
126- HTTP_BIN
127+ HTTP_CTYPE
128 HTTP_RANGE
129 HTTP_CRG
130 HTTP_LENGTH
131 HTTP_CLOSE
132 "\r\n",
133- HTTP_206, start, end, total, len
134+ HTTP_206, type, start, end, total, len
135 );
136 }
137 else {
138+ /* non-partial */
139 n = snprintf(
140 resp, sizeof(resp),
141 "%s"
142- HTTP_BIN
143+ HTTP_CTYPE
144 HTTP_RANGE
145 HTTP_LENGTH
146 HTTP_CLOSE
147 "\r\n",
148- HTTP_200, len
149+ HTTP_200, type, len
150 );
151 }
152
153@@ -509,6 +585,41 @@ int http_handle(int c)
154 return stream_file(c, it, hdr, head_only);
155 }
156
157+ if (strncmp(path, "/meta/", 6) == 0) {
158+ LOG(verbose_log, "HTTP", "route /meta");
159+
160+ uint64_t id;
161+ if (parse_hex64(path + 6, &id) < 0) {
162+ reply_text(c, HTTP_400, "bad request\n");
163+ return 0;
164+ }
165+
166+ const struct item* it = find_item(id);
167+ if (!it) {
168+ reply_text(c, HTTP_404, "not found\n");
169+ return 0;
170+ }
171+
172+ struct stat st;
173+ if (stat_item(it, &st) < 0) {
174+ reply_text(c, HTTP_404, "not found\n");
175+ return 0;
176+ }
177+
178+ const char* type = mime_from_path(it->path);
179+
180+ struct json j;
181+ if (json_meta(&j, it, (size_t)st.st_size, type) < 0) {
182+ reply_text(c, HTTP_500, "json failed\n");
183+ return -1;
184+ }
185+
186+ reply_json(c, HTTP_200, j.buf, j.len, !head_only);
187+ json_free(&j);
188+
189+ return 0;
190+ }
191+
192 LOG(verbose_log, "HTTP", "route not found: %s", path);
193 reply_text(c, HTTP_404, "not found\n");
194
+1,
-0
1@@ -12,6 +12,7 @@
2 #define HTTP_BIN "Content-Type: application/octet-stream\r\n"
3 #define HTTP_CLOSE "Connection: close\r\n"
4 #define HTTP_CRG "Content-Range: bytes %zu-%zu/%zu\r\n"
5+#define HTTP_CTYPE "Content-Type: %s\r\n"
6 #define HTTP_JSON "Content-Type: application/json\r\n"
7 #define HTTP_LENGTH "Content-Length: %zu\r\n"
8 #define HTTP_RANGE "Accept-Ranges: bytes\r\n"
+1,
-0
1@@ -14,6 +14,7 @@ struct json {
2
3 void json_free(struct json* j);
4 int json_library(struct json* j, const struct library* l);
5+int json_meta(struct json* j, const struct item* it, size_t size, const char* type);
6
7 #endif /* JSON_H */
8
+45,
-7
1@@ -128,6 +128,17 @@ static int json_string(struct json* j, const char* s)
2 return 0;
3 }
4
5+void json_free(struct json* j)
6+{
7+ if (!j)
8+ return;
9+
10+ free(j->buf);
11+ j->buf = NULL;
12+ j->len = 0;
13+ j->cap = 0;
14+}
15+
16 int json_library(struct json* j, const struct library* l)
17 {
18 memset(j, 0, sizeof(*j));
19@@ -172,14 +183,41 @@ fail:
20 return -1;
21 }
22
23-void json_free(struct json* j)
24+int json_meta(struct json* j, const struct item* it, size_t size, const char* type)
25 {
26- if (!j)
27- return;
28+ memset(j, 0, sizeof(*j));
29
30- free(j->buf);
31- j->buf = NULL;
32- j->len = 0;
33- j->cap = 0;
34+ if (json_puts(j, "{\"proto\":1,\"id\":") < 0)
35+ goto fail;
36+ if (json_hex64(j, it->id) < 0)
37+ goto fail;
38+
39+ if (json_puts(j, ",\"path\":") < 0)
40+ goto fail;
41+ if (json_string(j, it->path) < 0)
42+ goto fail;
43+
44+ if (json_puts(j, ",\"size\":") < 0)
45+ goto fail;
46+
47+ /* decimal size */
48+ char tmp[32];
49+ snprintf(tmp, sizeof(tmp), "%zu", size);
50+ if (json_puts(j, tmp) < 0)
51+ goto fail;
52+
53+ if (json_puts(j, ",\"type\":") < 0)
54+ goto fail;
55+ if (json_string(j, type) < 0)
56+ goto fail;
57+
58+ if (json_putc(j, '}') < 0)
59+ goto fail;
60+
61+ return 0;
62+
63+fail:
64+ json_free(j);
65+ return -1;
66 }
67