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