commit d97f7f0
uint
·
2026-01-27 17:07:14 +0000 UTC
parent d237710
add queue endpoint
2 files changed,
+138,
-0
+137,
-0
1@@ -20,6 +20,7 @@
2
3 static const char* cistrstr(const char* hay, const char* nee);
4 static int cors_build(char* out, size_t outsz, const char* hdr, int preflight);
5+static int get_host(char out[512], const char* hdr);
6 static const struct item* find_item(uint64_t id);
7 static int join_path(char* out, size_t outsz, const char* a, const char* b);
8 static const char* mime_from_path(const char* path);
9@@ -27,8 +28,10 @@ static int parse_hex64(const char* s, uint64_t* out);
10 static int parse_range(const char* hdr, size_t total, size_t* start, size_t* end);
11 static int read_request(int c, char* method, size_t msz, char* path, size_t psz, char* hdr, size_t hsz);
12 static void reply_json(int c, const char* hdr, const char* status, const char* body, size_t len, int send_body);
13+static void reply_m3u(int c, const char* hdr, const char* status, size_t len);
14 static void reply_preflight(int c, const char* hdr);
15 static void reply_text(int c, const char* hdr, const char* status, const char* body);
16+static int queue_write(int c, const char* hdr, int head_only);
17 static int stat_item(const struct item* it, struct stat* st);
18 static int stream_file(int c, const struct item* it, const char* hdr, int head_only);
19 static int write_all(int fd, const void* buf, size_t n);
20@@ -180,6 +183,38 @@ static int cors_build(char* out, size_t outsz, const char* hdr, int preflight)
21 return 1;
22 }
23
24+static int get_host(char out[512], const char* hdr)
25+{
26+ const char* p = cistrstr(hdr, "\nhost:");
27+ if (!p)
28+ p = cistrstr(hdr, "\rhost:");
29+ if (!p)
30+ p = cistrstr(hdr, "host:");
31+ if (!p)
32+ return -1;
33+
34+ p = strchr(p, ':');
35+ if (!p)
36+ return -1;
37+ p++;
38+
39+ while (*p == ' ' || *p == '\t')
40+ p++;
41+
42+ size_t n = 0;
43+ while (*p && *p != '\r' && *p != '\n') {
44+ if (n + 1 >= 512)
45+ return -1;
46+ out[n++] = *p++;
47+ }
48+ out[n] = '\0';
49+
50+ if (n == 0)
51+ return -1;
52+
53+ return 0;
54+}
55+
56 static const struct item* find_item(uint64_t id)
57 {
58 for (size_t i = 0; i < lib.len; i++)
59@@ -428,6 +463,35 @@ static void reply_json(int c, const char* hdr, const char* status, const char* b
60 (void)write_all(c, body, len);
61 }
62
63+
64+static void reply_m3u(int c, const char* hdr, const char* status, size_t len)
65+{
66+ char resp[HTTP_RESP_MAX];
67+ char cors[512];
68+
69+ (void)cors_build(cors, sizeof(cors), hdr, 0);
70+ int n = snprintf(
71+ resp, sizeof(resp),
72+
73+ "%s"
74+ "%s"
75+ HTTP_M3U
76+ HTTP_LENGTH
77+ HTTP_CLOSE
78+ "\r\n",
79+
80+ status, cors, len
81+ );
82+
83+ if (n < 0)
84+ return;
85+
86+ if ((size_t)n >= sizeof(resp))
87+ n = (int)(sizeof(resp) - 1);
88+
89+ (void)write_all(c, resp, (size_t)n);
90+}
91+
92 static void reply_preflight(int c, const char* hdr)
93 {
94 char resp[HTTP_RESP_MAX];
95@@ -486,6 +550,68 @@ static void reply_text(int c, const char* hdr, const char* status, const char* b
96 (void)write_all(c, resp, (size_t)n);
97 }
98
99+static int queue_write(int c, const char* hdr, int head_only)
100+{
101+ char host[512];
102+ char base[768];
103+
104+ if (get_host(host, hdr) == 0) {
105+ if (snprintf(base, sizeof(base), "http://%s", host) >= (int)sizeof(base))
106+ return -1;
107+ }
108+ else {
109+ if (snprintf(base, sizeof(base), "http://%s:%d", server_addr, server_port) >= (int)sizeof(base))
110+ return -1;
111+ }
112+
113+ /* Content-Length */
114+ size_t len = 0;
115+ len += 8; /* "#EXTM3U\n" */
116+
117+ for (size_t i = 0; i < lib.len; i++) {
118+ int n = snprintf(
119+ NULL, 0,
120+ "%s/stream/%016llx\n",
121+ base,
122+ (unsigned long long)lib.items[i].id
123+ );
124+
125+ if (n < 0)
126+ return -1;
127+
128+ len += (size_t)n;
129+ }
130+
131+ reply_m3u(c, hdr, HTTP_200, len);
132+
133+ if (head_only)
134+ return 0;
135+
136+ if (write_all(c, "#EXTM3U\n", 8) < 0)
137+ return -1;
138+
139+ for (size_t i = 0; i < lib.len; i++) {
140+ char line[2048];
141+
142+ int n = snprintf(
143+ line, sizeof(line),
144+ "%s/stream/%016llx\n",
145+ base,
146+ (unsigned long long)lib.items[i].id
147+ );
148+
149+ if (n < 0)
150+ return -1;
151+ if ((size_t)n >= sizeof(line))
152+ return -1;
153+
154+ if (write_all(c, line, (size_t)n) < 0)
155+ return -1;
156+ }
157+
158+ return 0;
159+}
160+
161 static int stat_item(const struct item* it, struct stat* st)
162 {
163 char full[4096];
164@@ -783,6 +909,17 @@ int http_handle(int c)
165 return 0;
166 }
167
168+ if (strcmp(path, "/queue") == 0) {
169+ LOG(verbose_log, "HTTP", "route /queue");
170+
171+ if (queue_write(c, hdr, head_only) < 0) {
172+ reply_text(c, hdr, HTTP_500, "server error\n");
173+ return -1;
174+ }
175+
176+ return 0;
177+ }
178+
179 LOG(verbose_log, "HTTP", "route not found: %s", path);
180 reply_text(c, hdr, HTTP_404, "not found\n");
181
+1,
-0
1@@ -16,6 +16,7 @@
2 #define HTTP_CTYPE "Content-Type: %s\r\n"
3 #define HTTP_JSON "Content-Type: application/json\r\n"
4 #define HTTP_LENGTH "Content-Length: %zu\r\n"
5+#define HTTP_M3U "Content-Type: audio/x-mpegurl\r\n"
6 #define HTTP_RANGE "Accept-Ranges: bytes\r\n"
7 #define HTTP_TEXT "Content-Type: text/plain\r\n"
8