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)