master uint/parados / server / http.c
  1#include <errno.h>
  2#include <fcntl.h>
  3#include <limits.h>
  4#include <stddef.h>
  5#include <stdint.h>
  6#include <pthread.h>
  7#include <stdio.h>
  8#include <stdlib.h>
  9#include <string.h>
 10#include <strings.h>
 11#include <sys/stat.h>
 12#include <sys/types.h>
 13#include <time.h>
 14#include <unistd.h>
 15
 16#include "config.h"
 17#include "http.h"
 18#include "json.h"
 19#include "log.h"
 20#include "scan.h"
 21#include "users.h"
 22#include "utils.h"
 23
 24struct response {
 25	const char* status;
 26	const char* ctype;
 27	const char* extra;
 28	const void* body;
 29
 30	size_t      len;
 31	bool        send_body;
 32	bool        preflight;
 33};
 34
 35static void auth_delay_sleep(void);
 36static bool cors_build(char* out, size_t outsz, const char* hdr, bool preflight);
 37static bool cors_origin_allowed(const char* origin);
 38static const struct item* find_item(uint64_t id);
 39static int item_path_for_id(char out[4096], uint64_t id, const struct user* u);
 40static const char* mime_from_path(const char* path);
 41static int parse_hex64(const char* s, uint64_t* out);
 42static int parse_range(const char* hdr, size_t total, size_t* start, size_t* end);
 43static int read_request(int c, char* method, size_t msz, char* path, size_t psz, char* hdr, size_t hsz);
 44static void reply(int c, const char* hdr, const struct response* res);
 45static int route_dispatch(int c, const char* hdr, const char* path, const struct user* u, bool head_only);
 46static int route_item_path(struct item* it, const char* path, const char* prefix, const struct user* u, int c, const char* hdr);
 47static int route_library(int c, const char* hdr, const struct user* u, bool head_only);
 48static bool route_method(int c, const char* hdr, const char* method, bool* head_only);
 49static int route_rescan(int c, const char* hdr, const struct user* u);
 50static int stat_item(const struct item* it, struct stat* st);
 51static int stream_file(int c, const struct item* it, const char* hdr, bool head_only);
 52
 53extern struct library lib;
 54extern pthread_mutex_t lib_lock;
 55
 56/* Sleep for configured auth delay
 57   Retries on EINTR. */
 58static void auth_delay_sleep(void)
 59{
 60	if (auth_delay <= 0)
 61		return;
 62
 63	struct timespec req;
 64	struct timespec rem;
 65
 66	/* convert ms to timespec */
 67	req.tv_sec = auth_delay / 1000;
 68	req.tv_nsec = (long)(auth_delay % 1000) * 1000000L;
 69
 70	while (nanosleep(&req, &rem) < 0) {
 71		if (errno != EINTR)
 72			break;
 73		req = rem;
 74	}
 75}
 76
 77/* Check whether origin is allowed by CORS config
 78   Returns true=Allowed, false=Not allowed */
 79static bool cors_origin_allowed(const char* origin)
 80{
 81	if (cors_origin[0] == '\0')
 82		return false;
 83
 84	if (strcmp(cors_origin, "*") == 0)
 85		return true;
 86
 87	/* comma/space separated allowlist */
 88	const char* p = cors_origin;
 89	while (*p) {
 90		while (*p == ' ' || *p == '\t' || *p == ',')
 91			p++;
 92
 93		const char* s = p;
 94		while (*p && *p != ',')
 95			p++;
 96
 97		const char* e = p;
 98		while (e > s && (e[-1] == ' ' || e[-1] == '\t'))
 99			e--;
100
101		size_t n = (size_t)(e - s);
102		if (n > 0 && strlen(origin) == n && strncmp(origin, s, n) == 0)
103			return true;
104
105		if (*p == ',')
106			p++;
107	}
108
109	return false;
110}
111
112/* Build CORS response headers
113   Returns true=Built, false=Not emitted */
114static bool cors_build(char* out, size_t outsz, const char* hdr, bool preflight)
115{
116	char origin[512];
117
118	if (!out || outsz == 0)
119		return false;
120
121	out[0] = '\0';
122
123	if (cors_origin[0] == '\0')
124		return false;
125
126	if (hdr_get_value(origin, hdr, "origin") < 0)
127		return false;
128
129	if (!cors_origin_allowed(origin))
130		return false;
131
132	int n;
133	const char* ao = (strcmp(cors_origin, "*") == 0) ? "*" : origin;
134	if (preflight) {
135		n = snprintf(
136			out, outsz,
137
138			"Access-Control-Allow-Origin: %s\r\n"
139			"Vary: Origin\r\n"
140			"Access-Control-Allow-Methods: GET, HEAD, OPTIONS\r\n"
141			"Access-Control-Allow-Headers: Range, Content-Type, Authorization\r\n"
142			"Access-Control-Max-Age: 600\r\n",
143
144			ao
145		);
146	}
147	else {
148		n = snprintf(
149			out, outsz,
150			"Access-Control-Allow-Origin: %s\r\n"
151			"Vary: Origin\r\n"
152			"Access-Control-Expose-Headers: Content-Length, Content-Range, Accept-Ranges, Content-Type\r\n",
153			ao
154		);
155	}
156
157	if (n < 0)
158		return false;
159	if ((size_t)n >= outsz) {
160		out[0] = '\0';
161		return false;
162	}
163	return true;
164}
165
166/* Find library item by id
167   Returns Item Ptr=Found, NULL=Not found */
168static const struct item* find_item(uint64_t id)
169{
170	for (size_t i = 0; i < lib.len; i++)
171		if (lib.items[i].id == id)
172			return &lib.items[i];
173
174	return NULL;
175}
176
177/* Resolve relative item path for a given id
178   Returns 0=Success, 1=Not found, -1=Failure */
179static int item_path_for_id(char out[4096], uint64_t id, const struct user* u)
180{
181	int ret = -1;
182
183	if (!out)
184		return -1;
185
186	out[0] = '\0';
187
188	(void)pthread_mutex_lock(&lib_lock);
189
190	const struct item* it = find_item(id);
191	if (!it) {
192		ret = 1; /* not found */
193		goto out;
194	}
195
196	if (u && !user_allows_path(u, it->path)) {
197		ret = 1; /* not found */
198		goto out;
199	}
200
201	if (snprintf(out, 4096, "%s", it->path) >= 4096) {
202		ret = -1;
203		goto out;
204	}
205
206	ret = 0;
207
208out:
209	(void)pthread_mutex_unlock(&lib_lock);
210	return ret; /* 0 ok, 1 not found, -1 error */
211}
212
213/* Infer MIME type from file path extension
214   Returns MIME type string */
215static const char* mime_from_path(const char* path)
216{
217	static const struct {
218		const char* ext;
219		const char* type;
220	} types[] = {
221		/* audio */
222		{ "mp3",  "audio/mpeg" }, { "m4a",  "audio/mp4" },
223		{ "aac",  "audio/aac" }, { "flac", "audio/flac" },
224		{ "wav",  "audio/wav" }, { "ogg",  "audio/ogg" },
225		{ "opus", "audio/opus" },
226
227		/* video */
228		{ "mp4",  "video/mp4" }, { "mkv",  "video/x-matroska" },
229		{ "webm", "video/webm" }, { "mov",  "video/quicktime" },
230
231		/* images */
232		{ "jpg",  "image/jpeg" }, { "jpeg", "image/jpeg" },
233		{ "png",  "image/png" }, { "gif",  "image/gif" },
234		{ "webp", "image/webp" },
235
236		/* subtitles */
237		{ "vtt", "text/vtt" }, { "srt", "application/x-subrip" },
238
239		/* misc */
240		{ "pdf", "application/pdf" },
241	};
242	const char* dot = strrchr(path, '.');
243
244	if (!dot || dot[1] == '\0')
245		return "application/octet-stream";
246
247	dot++;
248
249	for (size_t i = 0; i < sizeof(types) / sizeof(types[0]); i++)
250		if (strcasecmp(dot, types[i].ext) == 0)
251			return types[i].type;
252
253	return "application/octet-stream";
254}
255
256/* Parse 16-digit hex string to uint64
257   Returns 0=Success, -1=Failure */
258static int parse_hex64(const char* s, uint64_t* out)
259{
260	if (!s || !out)
261		return -1;
262
263	if (strlen(s) != 16)
264		return -1;
265
266	for (size_t i = 0; i < 16; i++) {
267		unsigned char c = (unsigned char)s[i];
268		if (!((c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F')))
269			return -1;
270	}
271
272	char* end = NULL;
273	errno = 0;
274	unsigned long long v = strtoull(s, &end, 16);
275	if (errno != 0 || !end || *end != '\0')
276		return -1;
277
278	*out = (uint64_t)v;
279	return 0;
280}
281
282/* Parse HTTP Range header
283   Returns RANGE_NONE=No header, RANGE_OK=Valid range, RANGE_BAD=Malformed, RANGE_UNSAT=Unsatisfiable */
284static int parse_range(const char* hdr, size_t total, size_t* start, size_t* end)
285{
286	const char* p = cistrstr(hdr, "\nrange:");
287	if (!p)
288		p = cistrstr(hdr, "\rrange:");
289	if (!p)
290		p = cistrstr(hdr, "range:");
291	if (!p)
292		return RANGE_NONE;
293
294	const char* q = cistrstr(p, "bytes=");
295	if (!q)
296		return RANGE_BAD;
297	q += 6;
298
299	while (*q == ' ')
300		q++;
301
302	if (*q == '-') {
303		/* bytes=-SUFFIX */
304		const char* r = q + 1;
305		char* e2 = NULL;
306
307		errno = 0;
308		unsigned long long suf = strtoull(r, &e2, 10);
309		if (errno != 0 || e2 == r)
310			return RANGE_BAD;
311		if (suf == 0)
312			return RANGE_BAD;
313
314		if (total == 0)
315			return RANGE_UNSAT;
316
317		if ((size_t)suf >= total) {
318			*start = 0;
319			*end = total - 1;
320			return RANGE_OK;
321		}
322
323		*start = total - (size_t)suf;
324		*end = total - 1;
325		return RANGE_OK;
326	}
327
328	/* bytes=START- or bytes=START-END */
329	char* e1 = NULL;
330
331	errno = 0;
332	unsigned long long a = strtoull(q, &e1, 10);
333	if (errno != 0 || e1 == q)
334		return RANGE_BAD;
335
336	if (*e1 != '-')
337		return RANGE_BAD;
338
339	const char* r = e1 + 1;
340
341	if (*r == '\r' || *r == '\n' || *r == '\0') {
342		/* bytes=START- */
343		if ((size_t)a >= total)
344			return RANGE_UNSAT;
345		*start = (size_t)a;
346		*end = total - 1;
347		return RANGE_OK;
348	}
349
350	char* e2 = NULL;
351
352	errno = 0;
353	unsigned long long b = strtoull(r, &e2, 10);
354	if (errno != 0 || e2 == r)
355		return RANGE_BAD;
356
357	if ((size_t)a >= total)
358		return RANGE_UNSAT;
359	if ((size_t)b >= total)
360		b = total - 1;
361	if (b < a)
362		return RANGE_BAD;
363
364	*start = (size_t)a;
365	*end = (size_t)b;
366	return RANGE_OK;
367}
368
369/* Read and parse a single HTTP request
370   Returns 0=Success, -1=Failure */
371static int read_request(int c, char* method, size_t msz, char* path, size_t psz, char* hdr, size_t hsz)
372{
373	size_t used = 0;
374
375	for (;;) {
376		if (used + 1 >= hsz)
377			return -1;
378
379		ssize_t r = read(c, hdr + used, hsz - 1 - used);
380		if (r < 0) {
381			if (errno == EINTR)
382				continue;
383			return -1;
384		}
385		if (r == 0)
386			return -1;
387
388		used += (size_t)r;
389		hdr[used] = '\0';
390
391		if (strstr(hdr, "\r\n\r\n"))
392			break;
393	}
394
395	char* eol = strstr(hdr, "\r\n");
396	if (!eol)
397		return -1;
398	*eol = '\0';
399
400	if (sscanf(hdr, "%15s %1023s", method, path) != 2)
401		return -1;
402
403	method[msz-1] = '\0';
404	path[psz-1] = '\0';
405
406	*eol = '\r';
407
408	return 0;
409}
410
411/* Send HTTP response header and optional body */
412static void reply(int c, const char* hdr, const struct response* res)
413{
414	char resp[HTTP_RESP_MAX];
415	char cors[512];
416
417	(void)cors_build(cors, sizeof(cors), hdr, res->preflight);
418
419	int n = snprintf(
420		resp, sizeof(resp),
421
422		"%s"
423		"%s"
424		"%s"
425		"%s"
426		HTTP_LENGTH
427		HTTP_CLOSE
428		"\r\n",
429
430		res->status,
431		cors,
432		(res->ctype && res->ctype[0]) ? res->ctype : "",
433		(res->extra && res->extra[0]) ? res->extra : "",
434		res->len
435	);
436
437	if (n < 0)
438		return;
439
440	if ((size_t)n >= sizeof(resp)) {
441		struct response err = {
442			.status = HTTP_500,
443			.ctype = HTTP_TEXT,
444			.extra = NULL,
445			.body = "Server Error\n",
446			.len = sizeof("Server Error\n") - 1,
447			.send_body = true,
448			.preflight = false,
449		};
450		reply(c, hdr, &err);
451		return;
452	}
453
454	(void)write_all(c, resp, (size_t)n);
455
456	if (res->send_body && res->body && res->len > 0)
457		(void)write_all(c, res->body, res->len);
458}
459
460/* Dispatch request path to route handler
461   Returns 0=Handled, -1=Failure */
462static int route_dispatch(int c, const char* hdr, const char* path, const struct user* u, bool head_only)
463{
464	if (strcmp(path, "/ping") == 0) {
465		LOG_FIELD(verbose_log, "HTTP", "Route", "/ping");
466		reply(c, hdr, &(struct response){ HTTP_200, HTTP_TEXT, NULL, "OK\n", sizeof("OK\n") - 1, true, false });
467		return 0;
468	}
469
470	if (strcmp(path, "/library") == 0)
471		return route_library(c, hdr, u, head_only);
472
473	if (strcmp(path, "/rescan") == 0)
474		return route_rescan(c, hdr, u);
475
476	if (strncmp(path, "/stream/", 8) == 0) {
477		struct item tmp;
478		char rel[4096];
479
480		LOG_FIELD(verbose_log, "HTTP", "Route", "/stream");
481		tmp.path = rel;
482
483		int rc = route_item_path(&tmp, path, "/stream/", u, c, hdr);
484		if (rc != 0)
485			return rc < 0 ? -1 : 0;
486
487		return stream_file(c, &tmp, hdr, head_only);
488	}
489
490	if (strncmp(path, "/meta/", 6) == 0) {
491		struct item tmp;
492		char rel[4096];
493		struct stat st;
494		const char* type;
495		struct json j;
496
497		LOG_FIELD(verbose_log, "HTTP", "Route", "/meta");
498		tmp.path = rel;
499
500		int rc = route_item_path(&tmp, path, "/meta/", u, c, hdr);
501		if (rc != 0)
502			return rc < 0 ? -1 : 0;
503
504		if (stat_item(&tmp, &st) < 0) {
505			reply(c, hdr, &(struct response){ HTTP_404, HTTP_TEXT, NULL, "Not Found\n", sizeof("Not Found\n") - 1, true, false });
506			return 0;
507		}
508
509		type = mime_from_path(tmp.path);
510		if (json_meta(&j, &tmp, (size_t)st.st_size, (long)st.st_mtime, type) < 0) {
511			reply(c, hdr, &(struct response){ HTTP_500, HTTP_TEXT, NULL, "JSON Failed\n", sizeof("JSON Failed\n") - 1, true, false });
512			return -1;
513		}
514
515		reply(c, hdr, &(struct response){ HTTP_200, HTTP_JSON, NULL, j.buf, j.len, !head_only, 0 });
516		json_free(&j);
517		return 0;
518	}
519
520	LOG_FIELD(verbose_log, "HTTP", "Route not found", "%s", path);
521	reply(c, hdr, &(struct response){ HTTP_404, HTTP_TEXT, NULL, "Not Found\n", sizeof("Not Found\n") - 1, true, false });
522	return 0;
523}
524
525/* Parse route id and resolve to an item
526   Returns 0=Success, 1=Response sent, -1=Failure */
527static int route_item_path(struct item* it, const char* path, const char* prefix, const struct user* u, int c, const char* hdr)
528{
529	uint64_t id;
530	int fr;
531
532	if (parse_hex64(path + strlen(prefix), &id) < 0) {
533		reply(c, hdr, &(struct response){
534			.status = HTTP_400,
535			.ctype = HTTP_TEXT,
536			.extra = NULL,
537			.body = "Bad Request\n",
538			.len = sizeof("Bad Request\n") - 1,
539			.send_body = true,
540			.preflight = false,
541		});
542		return 1;
543	}
544
545	fr = item_path_for_id(it->path, id, u);
546	if (fr == 1) {
547		reply(c, hdr, &(struct response){
548			.status = HTTP_404,
549			.ctype = HTTP_TEXT,
550			.extra = NULL,
551			.body = "Not Found\n",
552			.len = sizeof("Not Found\n") - 1,
553			.send_body = true,
554			.preflight = false,
555		});
556		return 1;
557	}
558	if (fr < 0) {
559		reply(c, hdr, &(struct response){
560			.status = HTTP_500,
561			.ctype = HTTP_TEXT,
562			.extra = NULL,
563			.body = "Server Error\n",
564			.len = sizeof("Server Error\n") - 1,
565			.send_body = true,
566			.preflight = false,
567		});
568		return -1;
569	}
570
571	it->id = id;
572	return 0;
573}
574
575/* Handle /library route
576   Returns 0=Handled, -1=Failure */
577static int route_library(int c, const char* hdr, const struct user* u, bool head_only)
578{
579	struct json j;
580	struct library view;
581
582	LOG_FIELD(verbose_log, "HTTP", "Route", "/library");
583	(void)pthread_mutex_lock(&lib_lock);
584	memset(&view, 0, sizeof(view));
585
586	if (!u) {
587		view = lib;
588	}
589	else {
590		size_t i;
591
592		if (lib.len > 0) {
593			view.items = calloc(lib.len, sizeof(*view.items));
594			if (!view.items) {
595				(void)pthread_mutex_unlock(&lib_lock);
596				reply(c, hdr, &(struct response){ HTTP_500, HTTP_TEXT, NULL, "Server Error\n", sizeof("Server Error\n") - 1, true, false });
597				return -1;
598			}
599		}
600
601		for (i = 0; i < lib.len; i++) {
602			if (!user_allows_path(u, lib.items[i].path))
603				continue;
604			view.items[view.len++] = lib.items[i];
605		}
606		view.cap = view.len;
607	}
608
609	if (json_library(&j, &view) < 0) {
610		if (u)
611			free(view.items);
612
613		(void)pthread_mutex_unlock(&lib_lock);
614		LOG_FIELD(verbose_log, "JSON", "Encode", "FAILED");
615		reply(c, hdr, &(struct response){ HTTP_500, HTTP_TEXT, NULL, "JSON Encode Failed\n", sizeof("JSON Encode Failed\n") - 1, true, false });
616		return -1;
617	}
618
619	(void)pthread_mutex_unlock(&lib_lock);
620	LOG_FIELD(verbose_log, "JSON", "Encoded bytes", "%zu bytes", j.len);
621	reply(c, hdr, &(struct response){ HTTP_200, HTTP_JSON, NULL, j.buf, j.len, !head_only, 0 });
622	json_free(&j);
623
624	if (u)
625		free(view.items);
626	return 0;
627}
628
629/* Handle request method and OPTIONS
630   Returns false=Continue, true=Response sent */
631static bool route_method(int c, const char* hdr, const char* method, bool* head_only)
632{
633	if (strcmp(method, "GET") == 0) {
634		*head_only = false;
635		return false;
636	}
637	if (strcmp(method, "HEAD") == 0) {
638		*head_only = true;
639		return false;
640	}
641	if (strcmp(method, "OPTIONS") == 0) {
642		reply(c, hdr, &(struct response){ HTTP_204, NULL, NULL, NULL, 0, false, true });
643		return true;
644	}
645
646	LOG_FIELD(verbose_log, "HTTP", "Method forbidden", "%s", method);
647	reply(c, hdr, &(struct response){ HTTP_405, HTTP_TEXT, NULL, "Method Not Allowed\n", sizeof("Method Not Allowed\n") - 1, true, false });
648	return true;
649}
650
651/* Handle /rescan route
652   Returns 0=Handled, -1=Failure */
653static int route_rescan(int c, const char* hdr, const struct user* u)
654{
655	struct timespec t0;
656	struct timespec t1;
657	size_t before = 0;
658	size_t after = 0;
659
660	LOG_FIELD(verbose_log, "HTTP", "Route", "/rescan");
661	if (users.len == 0) {
662		LOG_FIELD(true, "HTTP", "Rescan forbidden", "Auth disabled");
663		reply(c, hdr, &(struct response){ HTTP_403, HTTP_TEXT, NULL, "Forbidden\n", sizeof("Forbidden\n") - 1, true, false });
664		return 0;
665	}
666
667	LOG_FIELD(true, "SCAN", "Rescan requested", "%s", u->name);
668	clock_gettime(CLOCK_MONOTONIC, &t0);
669
670	(void)pthread_mutex_lock(&lib_lock);
671	before = lib.len;
672	LOG_FIELD(verbose_log, "SCAN", "Rescan begin", "%s (%zu items)", media_dir, before);
673	int ok = scan_library_rescan(&lib, media_dir);
674	after = lib.len;
675	(void)pthread_mutex_unlock(&lib_lock);
676
677	clock_gettime(CLOCK_MONOTONIC, &t1);
678	long ms = (t1.tv_sec - t0.tv_sec) * 1000L + (t1.tv_nsec - t0.tv_nsec) / 1000000L;
679
680	if (ok < 0) {
681		LOG_FIELD(true, "SCAN", "Rescan FAILED", "%s (%ld ms)", media_dir, ms);
682		reply(c, hdr, &(struct response){ HTTP_500, HTTP_TEXT, NULL, "Rescan Failed\n", sizeof("Rescan Failed\n") - 1, true, false });
683		return -1;
684	}
685
686	LOG_FIELD(true, "SCAN", "Rescan OK", "%zu -> %zu (%ld ms)", before, after, ms);
687	reply(c, hdr, &(struct response){ HTTP_200, HTTP_TEXT, NULL, "OK\n", sizeof("OK\n") - 1, true, false });
688	return 0;
689}
690
691/* Stat media item on disk
692   Returns 0=Success, -1=Failure */
693static int stat_item(const struct item* it, struct stat* st)
694{
695	char full[4096];
696
697	if (join_path(full, sizeof(full), media_dir, it->path) < 0)
698		return -1;
699
700	if (stat(full, st) < 0)
701		return -1;
702
703	if (!S_ISREG(st->st_mode))
704		return -1;
705
706	return 0;
707}
708
709/* Stream item file to HTTP client
710   Returns 0=Handled, -1=Failure */
711static int stream_file(int c, const struct item* it, const char* hdr, bool head_only)
712{
713	char full[4096];
714
715	/* build absolute path */
716	if (join_path(full, sizeof(full), media_dir, it->path) < 0) {
717		LOG_FIELD(verbose_log, "HTTP", "Stream FAILED", "Path too long");
718		reply(c, hdr, &(struct response){ HTTP_500, HTTP_TEXT, NULL, "Server Error\n", sizeof("Server Error\n") - 1, true, false });
719		return -1;
720	}
721
722	/* open file */
723	int fd = open(full, O_RDONLY);
724	if (fd < 0) {
725		LOG_FIELD(verbose_log, "HTTP", "Open FAILED", "%s", it->path);
726		reply(c, hdr, &(struct response){ HTTP_404, HTTP_TEXT, NULL, "Not Found\n", sizeof("Not Found\n") - 1, true, false });
727		return 0;
728	}
729
730	/* stat file */
731	struct stat st;
732	if (fstat(fd, &st) < 0) {
733		close(fd);
734		reply(c, hdr, &(struct response){ HTTP_500, HTTP_TEXT, NULL, "Server Error\n", sizeof("Server Error\n") - 1, true, false });
735		return -1;
736	}
737
738	/* reject non-regular files */
739	if (!S_ISREG(st.st_mode)) {
740		close(fd);
741		reply(c, hdr, &(struct response){ HTTP_404, HTTP_TEXT, NULL, "Not Found\n", sizeof("Not Found\n") - 1, true, false });
742		return 0;
743	}
744
745	size_t total = (size_t)st.st_size;
746	size_t start = 0;
747	size_t end = total ? (total - 1) : 0;
748	const char* type = mime_from_path(it->path);
749
750	int partial = parse_range(hdr, total, &start, &end);
751	if (partial == RANGE_BAD) {
752		close(fd);
753		reply(c, hdr, &(struct response){ HTTP_400, HTTP_TEXT, NULL, "Bad Range\n", sizeof("Bad Range\n") - 1, true, false });
754		return 0;
755	}
756
757	if (partial == RANGE_UNSAT) {
758		close(fd);
759		reply(c, hdr, &(struct response){ HTTP_416, HTTP_TEXT, NULL, "Range Not Satisfiable\n", sizeof("Range Not Satisfiable\n") - 1, true, false });
760		return 0;
761	}
762
763	if (partial == RANGE_OK) {
764		if (lseek(fd, (off_t)start, SEEK_SET) < 0) {
765			close(fd);
766			reply(c, hdr, &(struct response){ HTTP_500, HTTP_TEXT, NULL, "Server Error\n", sizeof("Server Error\n") - 1, true, false });
767			return -1;
768		}
769	}
770
771	size_t len = (total == 0) ? 0 : (end - start + 1);
772
773	/* build response header */
774	char ctype[128];
775	char extra[256];
776
777	if (snprintf(ctype, sizeof(ctype), HTTP_CTYPE, type) >= (int)sizeof(ctype)) {
778		close(fd);
779		reply(c, hdr, &(struct response){ HTTP_500, HTTP_TEXT, NULL, "Server Error\n", sizeof("Server Error\n") - 1, true, false });
780		return -1;
781	}
782
783	if (partial == RANGE_OK) {
784		/* partial */
785		if (snprintf(
786			extra, sizeof(extra),
787			HTTP_RANGE
788			HTTP_CRG,
789			start, end, total
790		) >= (int)sizeof(extra)) {
791			close(fd);
792			reply(c, hdr, &(struct response){ HTTP_500, HTTP_TEXT, NULL, "Server Error\n", sizeof("Server Error\n") - 1, true, false });
793			return -1;
794		}
795
796		reply(c, hdr, &(struct response){
797			.status = HTTP_206,
798			.ctype = ctype,
799			.extra = extra,
800			.body = NULL,
801			.len = len,
802			.send_body = false,
803			.preflight = false,
804		});
805	}
806	else {
807		/* non-partial */
808		if (snprintf(extra, sizeof(extra), HTTP_RANGE) >= (int)sizeof(extra)) {
809			close(fd);
810			reply(c, hdr, &(struct response){ HTTP_500, HTTP_TEXT, NULL, "Server Error\n", sizeof("Server Error\n") - 1, true, false });
811			return -1;
812		}
813
814		reply(c, hdr, &(struct response){
815			.status = HTTP_200,
816			.ctype = ctype,
817			.extra = extra,
818			.body = NULL,
819			.len = len,
820			.send_body = false,
821			.preflight = false,
822		});
823	}
824
825	/* HEAD: no body */
826	if (head_only) {
827		close(fd);
828
829		if (partial == RANGE_OK)
830			LOG_FIELD(verbose_log, "HTTP", "Header", "%s [%zu-%zu/%zu]", it->path, start, end, total);
831		else
832			LOG_FIELD(verbose_log, "HTTP", "Header", "%s (%zu bytes)", it->path, total);
833
834		return 0;
835	}
836
837	/* stream file */
838	char buf[8192];
839	size_t left = len;
840
841	while (left > 0) {
842		size_t want = sizeof(buf);
843		if (want > left)
844			want = left;
845
846		ssize_t r = read(fd, buf, want);
847		if (r < 0) {
848			if (errno == EINTR)
849				continue;
850			break;
851		}
852
853		if (r == 0)
854			break;
855
856		if (write_all(c, buf, (size_t)r) < 0)
857			break;
858
859		left -= (size_t)r;
860	}
861
862	close(fd);
863
864	if (partial == RANGE_OK)
865		LOG_FIELD(verbose_log, "HTTP", "Streamed", "%s [%zu-%zu/%zu]", it->path, start, end, total);
866	else
867		LOG_FIELD(verbose_log, "HTTP", "Streamed", "%s (%zu bytes)", it->path, total);
868
869	return 0;
870}
871
872int http_handle(int c)
873{
874	char method[16];
875	char path[1024];
876	char hdr[HTTP_REQ_MAX];
877	const struct user* u = NULL;
878	method[0] = '\0';
879	path[0] = '\0';
880	hdr[0] = '\0';
881
882	if (read_request(c, method, sizeof(method), path, sizeof(path), hdr, sizeof(hdr)) < 0) {
883		reply(c, hdr, &(struct response){ HTTP_400, HTTP_TEXT, NULL, "Bad Request\n", sizeof("Bad Request\n") - 1, true, false });
884		return -1;
885	}
886	LOG_FIELD(verbose_log, "HTTP", "Request", "%s %s", method, path);
887
888	bool head_only = false;
889	if (route_method(c, hdr, method, &head_only))
890		return 0;
891
892	if (users.len > 0) {
893		u = users_auth_from_hdr(hdr);
894		if (!u) {
895			auth_delay_sleep();
896			reply(c, hdr, &(struct response){
897				.status = "HTTP/1.1 401 Unauthorized\r\n",
898				.ctype = HTTP_TEXT,
899				.extra = "WWW-Authenticate: Basic realm=\"parados\"\r\n",
900				.body = "unauthorized\n",
901				.len = sizeof("unauthorized\n") - 1,
902				.send_body = !head_only,
903				.preflight = false,
904			});
905			return 0;
906		}
907	}
908
909	return route_dispatch(c, hdr, path, u, head_only);
910}