commit 16f2b2a
uint
·
2026-01-26 19:50:10 +0000 UTC
parent 6bb5302
add CORS header for more access
3 files changed,
+192,
-27
+190,
-27
1@@ -1,6 +1,7 @@
2 #include <ctype.h>
3 #include <errno.h>
4 #include <fcntl.h>
5+#include <limits.h>
6 #include <stddef.h>
7 #include <stdint.h>
8 #include <stdio.h>
9@@ -18,14 +19,16 @@
10 #include "scan.h"
11
12 static const char* cistrstr(const char* hay, const char* nee);
13+static int cors_build(char* out, size_t outsz, const char* hdr, int preflight);
14 static const struct item* find_item(uint64_t id);
15 static int join_path(char* out, size_t outsz, const char* a, const char* b);
16 static const char* mime_from_path(const char* path);
17 static int parse_hex64(const char* s, uint64_t* out);
18 static int parse_range(const char* hdr, size_t total, size_t* start, size_t* end);
19 static int read_request(int c, char* method, size_t msz, char* path, size_t psz, char* hdr, size_t hsz);
20-static void reply_json(int c, const char* status, const char* body, size_t len, int send_body);
21-static void reply_text(int c, const char* status, const char* body);
22+static void reply_json(int c, const char* hdr, const char* status, const char* body, size_t len, int send_body);
23+static void reply_preflight(int c, const char* hdr);
24+static void reply_text(int c, const char* hdr, const char* status, const char* body);
25 static int stat_item(const struct item* it, struct stat* st);
26 static int stream_file(int c, const struct item* it, const char* hdr, int head_only);
27 static int write_all(int fd, const void* buf, size_t n);
28@@ -62,6 +65,121 @@ static const char* cistrstr(const char* hay, const char* nee)
29 return NULL;
30 }
31
32+static int cors_origin_allowed(const char* origin)
33+{
34+ if (!cors_origin || cors_origin[0] == '\0')
35+ return 0;
36+
37+ if (strcmp(cors_origin, "*") == 0)
38+ return 1;
39+
40+ /* comma/space separated allowlist */
41+ const char* p = cors_origin;
42+ while (*p) {
43+ while (*p == ' ' || *p == '\t' || *p == ',')
44+ p++;
45+
46+ const char* s = p;
47+ while (*p && *p != ',')
48+ p++;
49+
50+ const char* e = p;
51+ while (e > s && (e[-1] == ' ' || e[-1] == '\t'))
52+ e--;
53+
54+ size_t n = (size_t)(e - s);
55+ if (n > 0 && strlen(origin) == n && strncmp(origin, s, n) == 0)
56+ return 1;
57+
58+ if (*p == ',')
59+ p++;
60+ }
61+
62+ return 0;
63+}
64+
65+static int cors_get_origin(char out[512], const char* hdr)
66+{
67+ const char* p = cistrstr(hdr, "\norigin:");
68+ if (!p)
69+ p = cistrstr(hdr, "\rorigin:");
70+ if (!p)
71+ p = cistrstr(hdr, "origin:");
72+ if (!p)
73+ return -1;
74+
75+ p = strchr(p, ':');
76+ if (!p)
77+ return -1;
78+ p++;
79+
80+ while (*p == ' ' || *p == '\t')
81+ p++;
82+
83+ size_t n = 0;
84+ while (*p && *p != '\r' && *p != '\n') {
85+ if (n + 1 >= 512)
86+ return -1;
87+ out[n++] = *p++;
88+ }
89+ out[n] = '\0';
90+
91+ if (n == 0)
92+ return -1;
93+
94+ return 0;
95+}
96+
97+static int cors_build(char* out, size_t outsz, const char* hdr, int preflight)
98+{
99+ char origin[512];
100+
101+ if (!out || outsz == 0)
102+ return 0;
103+
104+ out[0] = '\0';
105+
106+ if (!cors_origin || cors_origin[0] == '\0')
107+ return 0;
108+
109+ if (cors_get_origin(origin, hdr) < 0)
110+ return 0;
111+
112+ if (!cors_origin_allowed(origin))
113+ return 0;
114+
115+ int n;
116+ const char* ao = (strcmp(cors_origin, "*") == 0) ? "*" : origin;
117+ if (preflight) {
118+ n = snprintf(
119+ out, outsz,
120+ "Access-Control-Allow-Origin: %s\r\n"
121+ "Vary: Origin\r\n"
122+ "Access-Control-Allow-Methods: GET, HEAD, OPTIONS\r\n"
123+ "Access-Control-Allow-Headers: Range, Content-Type\r\n"
124+ "Access-Control-Max-Age: 600\r\n"
125+ "Access-Control-Expose-Headers: Content-Length, Content-Range, Accept-Ranges, Content-Type\r\n",
126+ ao
127+ );
128+ }
129+ else {
130+ n = snprintf(
131+ out, outsz,
132+ "Access-Control-Allow-Origin: %s\r\n"
133+ "Vary: Origin\r\n"
134+ "Access-Control-Expose-Headers: Content-Length, Content-Range, Accept-Ranges, Content-Type\r\n",
135+ ao
136+ );
137+ }
138+
139+ if (n < 0)
140+ return 0;
141+ if ((size_t)n >= outsz)
142+ out[outsz - 1] = '\0';
143+
144+ return 1;
145+}
146+
147 static const struct item* find_item(uint64_t id)
148 {
149 for (size_t i = 0; i < lib.len; i++)
150@@ -279,19 +397,23 @@ static int read_request(int c, char* method, size_t msz, char* path, size_t psz,
151 return 0;
152 }
153
154-static void reply_json(int c, const char* status, const char* body, size_t len, int send_body)
155+static void reply_json(int c, const char* hdr, const char* status, const char* body, size_t len, int send_body)
156 {
157 char resp[HTTP_RESP_MAX];
158+ char cors[512];
159+
160+ (void)cors_build(cors, sizeof(cors), hdr, 0);
161 int n = snprintf(
162 resp, sizeof(resp),
163
164+ "%s"
165 "%s"
166 HTTP_JSON
167 HTTP_LENGTH
168 HTTP_CLOSE
169 "\r\n",
170
171- status, len
172+ status, cors, len
173 );
174
175 if (n < 0)
176@@ -306,12 +428,43 @@ static void reply_json(int c, const char* status, const char* body, size_t len,
177 (void)write_all(c, body, len);
178 }
179
180-static void reply_text(int c, const char* status, const char* body)
181+static void reply_preflight(int c, const char* hdr)
182 {
183 char resp[HTTP_RESP_MAX];
184+ char cors[512];
185+
186+ (void)cors_build(cors, sizeof(cors), hdr, 1);
187+
188 int n = snprintf(
189 resp, sizeof(resp),
190+ "%s"
191+ "%s"
192+ HTTP_LENGTH
193+ HTTP_CLOSE
194+ "\r\n",
195+ HTTP_204,
196+ cors,
197+ (size_t)0
198+ );
199+
200+ if (n < 0)
201+ return;
202+ if ((size_t)n >= sizeof(resp))
203+ n = (int)(sizeof(resp) - 1);
204
205+ (void)write_all(c, resp, (size_t)n);
206+}
207+
208+static void reply_text(int c, const char* hdr, const char* status, const char* body)
209+{
210+ char resp[HTTP_RESP_MAX];
211+ char cors[512];
212+
213+ (void)cors_build(cors, sizeof(cors), hdr, 0);
214+ int n = snprintf(
215+ resp, sizeof(resp),
216+
217+ "%s"
218 "%s"
219 HTTP_TEXT
220 HTTP_LENGTH
221@@ -320,6 +473,7 @@ static void reply_text(int c, const char* status, const char* body)
222 "%s",
223
224 status,
225+ cors,
226 strlen(body), body
227 );
228
229@@ -355,7 +509,7 @@ static int stream_file(int c, const struct item* it, const char* hdr, int head_o
230 /* build absolute path */
231 if (join_path(full, sizeof(full), media_dir, it->path) < 0) {
232 LOG(verbose_log, "HTTP", "path too long");
233- reply_text(c, HTTP_500, "server error\n");
234+ reply_text(c, hdr, HTTP_500, "server error\n");
235 return -1;
236 }
237
238@@ -363,7 +517,7 @@ static int stream_file(int c, const struct item* it, const char* hdr, int head_o
239 int fd = open(full, O_RDONLY);
240 if (fd < 0) {
241 LOG(verbose_log, "HTTP", "open failed: %s", it->path);
242- reply_text(c, HTTP_404, "not found\n");
243+ reply_text(c, hdr, HTTP_404, "not found\n");
244 return 0;
245 }
246
247@@ -371,14 +525,14 @@ static int stream_file(int c, const struct item* it, const char* hdr, int head_o
248 struct stat st;
249 if (fstat(fd, &st) < 0) {
250 close(fd);
251- reply_text(c, HTTP_500, "server error\n");
252+ reply_text(c, hdr, HTTP_500, "server error\n");
253 return -1;
254 }
255
256 /* reject non-regular files */
257 if (!S_ISREG(st.st_mode)) {
258 close(fd);
259- reply_text(c, HTTP_404, "not found\n");
260+ reply_text(c, hdr, HTTP_404, "not found\n");
261 return 0;
262 }
263
264@@ -386,24 +540,27 @@ static int stream_file(int c, const struct item* it, const char* hdr, int head_o
265 size_t start = 0;
266 size_t end = total ? (total - 1) : 0;
267 const char* type = mime_from_path(it->path);
268+ char cors[512];
269+
270+ (void)cors_build(cors, sizeof(cors), hdr, 0);
271
272 int partial = parse_range(hdr, total, &start, &end);
273 if (partial == RANGE_BAD) {
274 close(fd);
275- reply_text(c, HTTP_400, "bad range\n");
276+ reply_text(c, hdr, HTTP_400, "bad range\n");
277 return 0;
278 }
279
280 if (partial == RANGE_UNSAT) {
281 close(fd);
282- reply_text(c, HTTP_416, "range not satisfiable\n");
283+ reply_text(c, hdr, HTTP_416, "range not satisfiable\n");
284 return 0;
285 }
286
287 if (partial == RANGE_OK) {
288 if (lseek(fd, (off_t)start, SEEK_SET) < 0) {
289 close(fd);
290- reply_text(c, HTTP_500, "server error\n");
291+ reply_text(c, hdr, HTTP_500, "server error\n");
292 return -1;
293 }
294 }
295@@ -420,12 +577,13 @@ static int stream_file(int c, const struct item* it, const char* hdr, int head_o
296 resp, sizeof(resp),
297 "%s"
298 HTTP_CTYPE
299+ "%s"
300 HTTP_RANGE
301 HTTP_CRG
302 HTTP_LENGTH
303 HTTP_CLOSE
304 "\r\n",
305- HTTP_206, type, start, end, total, len
306+ HTTP_206, type, cors, start, end, total, len
307 );
308 }
309 else {
310@@ -434,11 +592,12 @@ static int stream_file(int c, const struct item* it, const char* hdr, int head_o
311 resp, sizeof(resp),
312 "%s"
313 HTTP_CTYPE
314+ "%s"
315 HTTP_RANGE
316 HTTP_LENGTH
317 HTTP_CLOSE
318 "\r\n",
319- HTTP_200, type, len
320+ HTTP_200, type, cors, len
321 );
322 }
323
324@@ -526,7 +685,7 @@ int http_handle(int c)
325 char hdr[HTTP_REQ_MAX];
326
327 if (read_request(c, method, sizeof(method), path, sizeof(path), hdr, sizeof(hdr)) < 0) {
328- reply_text(c, HTTP_400, "bad request\n");
329+ reply_text(c, hdr, HTTP_400, "bad request\n");
330 return -1;
331 }
332 LOG(verbose_log, "HTTP", "request: %s %s", method, path);
333@@ -538,15 +697,19 @@ int http_handle(int c)
334 else if (strcmp(method, "HEAD") == 0) {
335 head_only = 1;
336 }
337+ else if (strcmp(method, "OPTIONS") == 0) {
338+ reply_preflight(c, hdr);
339+ return 0;
340+ }
341 else {
342 LOG(verbose_log, "HTTP", "method not allowed: %s", method);
343- reply_text(c, HTTP_405, "method not allowed\n");
344+ reply_text(c, hdr, HTTP_405, "method not allowed\n");
345 return 0;
346 }
347
348 if (strcmp(path, "/ping") == 0) {
349 LOG(verbose_log, "HTTP", "route /ping");
350- reply_text(c, HTTP_200, "ok\n");
351+ reply_text(c, hdr, HTTP_200, "ok\n");
352 return 0;
353 }
354
355@@ -556,12 +719,12 @@ int http_handle(int c)
356
357 if (json_library(&j, &lib) < 0) {
358 LOG(verbose_log, "JSON", "encode failed");
359- reply_text(c, HTTP_500, "json failed\n");
360+ reply_text(c, hdr, HTTP_500, "json failed\n");
361 return -1;
362 }
363 LOG(verbose_log, "JSON", "encoded %zu bytes", j.len);
364
365- reply_json(c, HTTP_200, j.buf, j.len, !head_only);
366+ reply_json(c, hdr, HTTP_200, j.buf, j.len, !head_only);
367 json_free(&j);
368
369 return 0;
370@@ -572,13 +735,13 @@ int http_handle(int c)
371
372 uint64_t id;
373 if (parse_hex64(path + 8, &id) < 0) {
374- reply_text(c, HTTP_400, "bad request\n");
375+ reply_text(c, hdr, HTTP_400, "bad request\n");
376 return 0;
377 }
378
379 const struct item* it = find_item(id);
380 if (!it) {
381- reply_text(c, HTTP_404, "not found\n");
382+ reply_text(c, hdr, HTTP_404, "not found\n");
383 return 0;
384 }
385
386@@ -590,19 +753,19 @@ int http_handle(int c)
387
388 uint64_t id;
389 if (parse_hex64(path + 6, &id) < 0) {
390- reply_text(c, HTTP_400, "bad request\n");
391+ reply_text(c, hdr, HTTP_400, "bad request\n");
392 return 0;
393 }
394
395 const struct item* it = find_item(id);
396 if (!it) {
397- reply_text(c, HTTP_404, "not found\n");
398+ reply_text(c, hdr, HTTP_404, "not found\n");
399 return 0;
400 }
401
402 struct stat st;
403 if (stat_item(it, &st) < 0) {
404- reply_text(c, HTTP_404, "not found\n");
405+ reply_text(c, hdr, HTTP_404, "not found\n");
406 return 0;
407 }
408
409@@ -610,18 +773,18 @@ int http_handle(int c)
410
411 struct json j;
412 if (json_meta(&j, it, (size_t)st.st_size, type) < 0) {
413- reply_text(c, HTTP_500, "json failed\n");
414+ reply_text(c, hdr, HTTP_500, "json failed\n");
415 return -1;
416 }
417
418- reply_json(c, HTTP_200, j.buf, j.len, !head_only);
419+ reply_json(c, hdr, HTTP_200, j.buf, j.len, !head_only);
420 json_free(&j);
421
422 return 0;
423 }
424
425 LOG(verbose_log, "HTTP", "route not found: %s", path);
426- reply_text(c, HTTP_404, "not found\n");
427+ reply_text(c, hdr, HTTP_404, "not found\n");
428
429 return 0;
430 }
+1,
-0
1@@ -7,6 +7,7 @@ static const char* media_dir = "/path/to/media";
2 static const char* server_addr = "0.0.0.0";
3 static const int server_port = 8088;
4 static const bool verbose_log = true;
5+static const char* cors_origin = "";
6
7 enum {
8 HTTP_REQ_MAX = 2048,
+1,
-0
1@@ -2,6 +2,7 @@
2 #define HTTP_H
3
4 #define HTTP_200 "HTTP/1.1 200 OK\r\n"
5+#define HTTP_204 "HTTP/1.1 204 No Content\r\n"
6 #define HTTP_206 "HTTP/1.1 206 Partial Content\r\n"
7 #define HTTP_400 "HTTP/1.1 400 Bad Request\r\n"
8 #define HTTP_404 "HTTP/1.1 404 Not Found\r\n"