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