commit 8782705
uint
·
2026-01-25 23:37:27 +0000 UTC
parent 3d7bcf8
add range handling
3 files changed,
+260,
-37
+229,
-33
1@@ -1,3 +1,4 @@
2+#include <ctype.h>
3 #include <errno.h>
4 #include <fcntl.h>
5 #include <stddef.h>
6@@ -6,6 +7,7 @@
7 #include <stdlib.h>
8 #include <string.h>
9 #include <sys/stat.h>
10+#include <sys/types.h>
11 #include <unistd.h>
12
13 #include "config.h"
14@@ -14,18 +16,49 @@
15 #include "log.h"
16 #include "scan.h"
17
18+static const char* cistrstr(const char* hay, const char* nee);
19 static const struct item* find_item(uint64_t id);
20 static int join_path(char* out, size_t outsz, const char* a, const char* b);
21 static int parse_hex64(const char* s, uint64_t* out);
22-static int read_reqline(int c, char* method, size_t msz, char* path, size_t psz);
23-static void reply_json(int c, const char* status, const char* body, size_t len);
24+static int parse_range(const char* hdr, size_t total, size_t* start, size_t* end);
25+static int read_request(int c, char* method, size_t msz, char* path, size_t psz, char* hdr, size_t hsz);
26+static void reply_json(int c, const char* status, const char* body, size_t len, int send_body);
27 static void reply_text(int c, const char* status, const char* body);
28-static int stream_file(int c, const struct item* it);
29+static int stream_file(int c, const struct item* it, const char* hdr, int head_only);
30 static int write_all(int fd, const void* buf, size_t n);
31
32
33 extern struct library lib;
34
35+static const char* cistrstr(const char* hay, const char* nee)
36+{
37+ size_t nl = strlen(nee);
38+ if (nl == 0)
39+ return hay;
40+
41+ for (; *hay; hay++) {
42+ size_t i = 0;
43+
44+ for (;;) {
45+ if (i == nl)
46+ return hay;
47+
48+ unsigned char a = (unsigned char)hay[i];
49+ unsigned char b = (unsigned char)nee[i];
50+
51+ if (a == '\0')
52+ break;
53+
54+ if (tolower(a) != tolower(b))
55+ break;
56+
57+ i++;
58+ }
59+ }
60+
61+ return NULL;
62+}
63+
64 static const struct item* find_item(uint64_t id)
65 {
66 for (size_t i = 0; i < lib.len; i++)
67@@ -65,16 +98,98 @@ static int parse_hex64(const char* s, uint64_t* out)
68 return 0;
69 }
70
71-static int read_reqline(int c, char* method, size_t msz, char* path, size_t psz)
72+static int parse_range(const char* hdr, size_t total, size_t* start, size_t* end)
73+{
74+ const char* p = cistrstr(hdr, "\nrange:");
75+ if (!p)
76+ p = cistrstr(hdr, "\rrange:");
77+ if (!p)
78+ p = cistrstr(hdr, "range:");
79+ if (!p)
80+ return RANGE_NONE;
81+
82+ const char* q = cistrstr(p, "bytes=");
83+ if (!q)
84+ return RANGE_BAD;
85+ q += 6;
86+
87+ while (*q == ' ')
88+ q++;
89+
90+ if (*q == '-') {
91+ /* bytes=-SUFFIX */
92+ const char* r = q + 1;
93+ char* e2 = NULL;
94+
95+ errno = 0;
96+ unsigned long long suf = strtoull(r, &e2, 10);
97+ if (errno != 0 || e2 == r)
98+ return RANGE_BAD;
99+
100+ if (total == 0)
101+ return RANGE_BAD;
102+
103+ if ((size_t)suf >= total) {
104+ *start = 0;
105+ *end = total - 1;
106+ return RANGE_OK;
107+ }
108+
109+ *start = total - (size_t)suf;
110+ *end = total - 1;
111+ return RANGE_OK;
112+ }
113+
114+ /* bytes=START- or bytes=START-END */
115+ char* e1 = NULL;
116+
117+ errno = 0;
118+ unsigned long long a = strtoull(q, &e1, 10);
119+ if (errno != 0 || e1 == q)
120+ return RANGE_BAD;
121+
122+ if (*e1 != '-')
123+ return RANGE_BAD;
124+
125+ const char* r = e1 + 1;
126+
127+ if (*r == '\r' || *r == '\n' || *r == '\0') {
128+ /* bytes=START- */
129+ if ((size_t)a >= total)
130+ return RANGE_BAD;
131+ *start = (size_t)a;
132+ *end = total - 1;
133+ return RANGE_OK;
134+ }
135+
136+ char* e2 = NULL;
137+
138+ errno = 0;
139+ unsigned long long b = strtoull(r, &e2, 10);
140+ if (errno != 0 || e2 == r)
141+ return RANGE_BAD;
142+
143+ if ((size_t)a >= total)
144+ return RANGE_BAD;
145+ if ((size_t)b >= total)
146+ b = total - 1;
147+ if (b < a)
148+ return RANGE_BAD;
149+
150+ *start = (size_t)a;
151+ *end = (size_t)b;
152+ return RANGE_OK;
153+}
154+
155+static int read_request(int c, char* method, size_t msz, char* path, size_t psz, char* hdr, size_t hsz)
156 {
157- char buf[HTTP_REQ_MAX];
158 size_t used = 0;
159
160 for (;;) {
161- if (used + 1 >= sizeof(buf))
162+ if (used + 1 >= hsz)
163 return -1;
164
165- ssize_t r = read(c, buf + used, sizeof(buf) - 1 - used);
166+ ssize_t r = read(c, hdr + used, hsz - 1 - used);
167 if (r < 0) {
168 if (errno == EINTR)
169 continue;
170@@ -84,25 +199,29 @@ static int read_reqline(int c, char* method, size_t msz, char* path, size_t psz)
171 return -1;
172
173 used += (size_t)r;
174- buf[used] = '\0';
175+ hdr[used] = '\0';
176
177- char* eol = strstr(buf, "\r\n");
178- if (eol) {
179- *eol = '\0';
180+ if (strstr(hdr, "\r\n\r\n"))
181 break;
182- }
183 }
184
185- if (sscanf(buf, "%15s %1023s", method, path) != 2)
186+ char* eol = strstr(hdr, "\r\n");
187+ if (!eol)
188+ return -1;
189+ *eol = '\0';
190+
191+ if (sscanf(hdr, "%15s %1023s", method, path) != 2)
192 return -1;
193
194 method[msz-1] = '\0';
195 path[psz-1] = '\0';
196
197+ *eol = '\r';
198+
199 return 0;
200 }
201
202-static void reply_json(int c, const char* status, const char* body, size_t len)
203+static void reply_json(int c, const char* status, const char* body, size_t len, int send_body)
204 {
205 char resp[HTTP_RESP_MAX];
206 int n = snprintf(
207@@ -124,7 +243,9 @@ static void reply_json(int c, const char* status, const char* body, size_t len)
208 n = (int)(sizeof(resp) - 1);
209
210 (void)write_all(c, resp, (size_t)n);
211- (void)write_all(c, body, len);
212+
213+ if (send_body)
214+ (void)write_all(c, body, len);
215 }
216
217 static void reply_text(int c, const char* status, const char* body)
218@@ -153,7 +274,7 @@ static void reply_text(int c, const char* status, const char* body)
219 (void)write_all(c, resp, (size_t)n);
220 }
221
222-static int stream_file(int c, const struct item* it)
223+static int stream_file(int c, const struct item* it, const char* hdr, int head_only)
224 {
225 char full[4096];
226
227@@ -187,19 +308,62 @@ static int stream_file(int c, const struct item* it)
228 return 0;
229 }
230
231- size_t len = (size_t)st.st_size;
232+ size_t total = (size_t)st.st_size;
233+ size_t start = 0;
234+ size_t end = total ? (total - 1) : 0;
235+
236+ int partial = parse_range(hdr, total, &start, &end);
237+ if (partial == RANGE_BAD) {
238+ close(fd);
239+ reply_text(c, HTTP_400, "bad range\n");
240+ return 0;
241+ }
242+
243+ if (partial == RANGE_UNSAT) {
244+ close(fd);
245+ reply_text(c, HTTP_416, "range not satisfiable\n");
246+ return 0;
247+ }
248+
249+ if (partial == RANGE_OK) {
250+ if (lseek(fd, (off_t)start, SEEK_SET) < 0) {
251+ close(fd);
252+ reply_text(c, HTTP_500, "server error\n");
253+ return -1;
254+ }
255+ }
256+
257+ size_t len = (total == 0) ? 0 : (end - start + 1);
258
259 /* build response header */
260 char resp[HTTP_RESP_MAX];
261- int n = snprintf(
262- resp, sizeof(resp),
263- "%s"
264- HTTP_BIN
265- HTTP_LENGTH
266- HTTP_CLOSE
267- "\r\n",
268- HTTP_200, len
269- );
270+ int n;
271+
272+ if (partial == RANGE_OK) {
273+ n = snprintf(
274+ resp, sizeof(resp),
275+ "%s"
276+ HTTP_BIN
277+ HTTP_RANGE
278+ HTTP_CRG
279+ HTTP_LENGTH
280+ HTTP_CLOSE
281+ "\r\n",
282+ HTTP_206, start, end, total, len
283+ );
284+ }
285+ else {
286+ n = snprintf(
287+ resp, sizeof(resp),
288+ "%s"
289+ HTTP_BIN
290+ HTTP_RANGE
291+ HTTP_LENGTH
292+ HTTP_CLOSE
293+ "\r\n",
294+ HTTP_200, len
295+ );
296+ }
297
298 if (n < 0) {
299 close(fd);
300@@ -212,10 +376,28 @@ static int stream_file(int c, const struct item* it)
301 /* send headers */
302 (void)write_all(c, resp, (size_t)n);
303
304+ /* HEAD: no body */
305+ if (head_only) {
306+ close(fd);
307+
308+ if (partial == RANGE_OK)
309+ LOG(verbose_log, "HTTP", "header %s [%zu-%zu/%zu]", it->path, start, end, total);
310+ else
311+ LOG(verbose_log, "HTTP", "header %s (%zu bytes)", it->path, total);
312+
313+ return 0;
314+ }
315+
316 /* stream file */
317 char buf[8192];
318- for (;;) {
319- ssize_t r = read(fd, buf, sizeof(buf));
320+ size_t left = len;
321+
322+ while (left > 0) {
323+ size_t want = sizeof(buf);
324+ if (want > left)
325+ want = left;
326+
327+ ssize_t r = read(fd, buf, want);
328 if (r < 0) {
329 if (errno == EINTR)
330 continue;
331@@ -227,10 +409,16 @@ static int stream_file(int c, const struct item* it)
332
333 if (write_all(c, buf, (size_t)r) < 0)
334 break;
335+
336+ left -= (size_t)r;
337 }
338
339 close(fd);
340- LOG(verbose_log, "HTTP", "streamed %s (%zu bytes)", it->path, len);
341+
342+ if (partial == RANGE_OK)
343+ LOG(verbose_log, "HTTP", "streamed %s [%zu-%zu/%zu]", it->path, start, end, total);
344+ else
345+ LOG(verbose_log, "HTTP", "streamed %s (%zu bytes)", it->path, total);
346
347 return 0;
348 }
349@@ -258,14 +446,22 @@ int http_handle(int c)
350 {
351 char method[16];
352 char path[1024];
353+ char hdr[HTTP_REQ_MAX];
354
355- if (read_reqline(c, method, sizeof(method), path, sizeof(path)) < 0) {
356+ if (read_request(c, method, sizeof(method), path, sizeof(path), hdr, sizeof(hdr)) < 0) {
357 reply_text(c, HTTP_400, "bad request\n");
358 return -1;
359 }
360 LOG(verbose_log, "HTTP", "request: %s %s", method, path);
361
362- if (strcmp(method, "GET") != 0) {
363+ int head_only = 0;
364+ if (strcmp(method, "GET") == 0) {
365+ head_only = 0;
366+ }
367+ else if (strcmp(method, "HEAD") == 0) {
368+ head_only = 1;
369+ }
370+ else {
371 LOG(verbose_log, "HTTP", "method not allowed: %s", method);
372 reply_text(c, HTTP_405, "method not allowed\n");
373 return 0;
374@@ -288,7 +484,7 @@ int http_handle(int c)
375 }
376 LOG(verbose_log, "JSON", "encoded %zu bytes", j.len);
377
378- reply_json(c, HTTP_200, j.buf, j.len);
379+ reply_json(c, HTTP_200, j.buf, j.len, !head_only);
380 json_free(&j);
381
382 return 0;
383@@ -309,7 +505,7 @@ int http_handle(int c)
384 return 0;
385 }
386
387- return stream_file(c, it);
388+ return stream_file(c, it, hdr, head_only);
389 }
390
391 LOG(verbose_log, "HTTP", "route not found: %s", path);
+13,
-2
1@@ -2,16 +2,27 @@
2 #define HTTP_H
3
4 #define HTTP_200 "HTTP/1.1 200 OK\r\n"
5+#define HTTP_206 "HTTP/1.1 206 Partial Content\r\n"
6 #define HTTP_400 "HTTP/1.1 400 Bad Request\r\n"
7 #define HTTP_404 "HTTP/1.1 404 Not Found\r\n"
8 #define HTTP_405 "HTTP/1.1 405 Method Not Allowed\r\n"
9+#define HTTP_416 "HTTP/1.1 416 Range Not Satisfiable\r\n"
10 #define HTTP_500 "HTTP/1.1 500 Internal Server Error\r\n"
11
12-#define HTTP_TEXT "Content-Type: text/plain\r\n"
13-#define HTTP_JSON "Content-Type: application/json\r\n"
14 #define HTTP_BIN "Content-Type: application/octet-stream\r\n"
15 #define HTTP_CLOSE "Connection: close\r\n"
16+#define HTTP_CRG "Content-Range: bytes %zu-%zu/%zu\r\n"
17+#define HTTP_JSON "Content-Type: application/json\r\n"
18 #define HTTP_LENGTH "Content-Length: %zu\r\n"
19+#define HTTP_RANGE "Accept-Ranges: bytes\r\n"
20+#define HTTP_TEXT "Content-Type: text/plain\r\n"
21+
22+enum {
23+ RANGE_NONE = 0, /* no range header */
24+ RANGE_OK = 1, /* valid satisfiable range */
25+ RANGE_BAD = -1, /* malformed (=> 400) */
26+ RANGE_UNSAT = -2, /* unsatisfiable (=> 416) */
27+};
28
29 int http_handle(int c);
30
+18,
-2
1@@ -6,6 +6,7 @@
2 */
3
4 #include <errno.h>
5+#include <signal.h>
6 #include <stdarg.h>
7 #include <stdbool.h>
8 #include <stddef.h>
9@@ -49,14 +50,29 @@ void run(void)
10 }
11 LOG(verbose_log, "CORE", "connection accepted");
12
13- (void)http_handle(c);
14- shutdown(c, SHUT_WR);
15+ pid_t pid = fork();
16+ if (pid < 0) {
17+ close(c);
18+ continue;
19+ }
20+
21+ if (pid == 0) {
22+ (void)http_handle(c);
23+ shutdown(c, SHUT_WR);
24+ close(c);
25+ _exit(EXIT_SUCCESS);
26+ }
27+
28 close(c);
29 }
30 }
31
32 void setup(void)
33 {
34+ signal(SIGPIPE, SIG_IGN); /* ignore SIGPIPE */
35+ signal(SIGCHLD, SIG_IGN); /* reap children to prevent
36+ them to turn into zombies */
37+
38 int ret = 1;
39 sock = socket(AF_INET, SOCK_STREAM, 0);
40 if (sock < 0)