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}