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}