]> code.delx.au - pulseaudio/blob - src/pulsecore/protocol-http.c
1de04345a92531bfe773ef6802500a6142a30aef
[pulseaudio] / src / pulsecore / protocol-http.c
1 /***
2 This file is part of PulseAudio.
3
4 Copyright 2005-2009 Lennart Poettering
5
6 PulseAudio is free software; you can redistribute it and/or modify
7 it under the terms of the GNU Lesser General Public License as published
8 by the Free Software Foundation; either version 2.1 of the License,
9 or (at your option) any later version.
10
11 PulseAudio is distributed in the hope that it will be useful, but
12 WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 General Public License for more details.
15
16 You should have received a copy of the GNU Lesser General Public License
17 along with PulseAudio; if not, write to the Free Software
18 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
19 USA.
20 ***/
21
22 #ifdef HAVE_CONFIG_H
23 #include <config.h>
24 #endif
25
26 #include <stdlib.h>
27 #include <stdio.h>
28 #include <string.h>
29 #include <errno.h>
30
31 #include <pulse/util.h>
32 #include <pulse/xmalloc.h>
33 #include <pulse/timeval.h>
34
35 #include <pulsecore/ioline.h>
36 #include <pulsecore/thread-mq.h>
37 #include <pulsecore/macro.h>
38 #include <pulsecore/log.h>
39 #include <pulsecore/namereg.h>
40 #include <pulsecore/cli-text.h>
41 #include <pulsecore/shared.h>
42 #include <pulsecore/core-error.h>
43 #include <pulsecore/mime-type.h>
44
45 #include "protocol-http.h"
46
47 /* Don't allow more than this many concurrent connections */
48 #define MAX_CONNECTIONS 10
49
50 #define URL_ROOT "/"
51 #define URL_CSS "/style"
52 #define URL_STATUS "/status"
53 #define URL_LISTEN "/listen"
54 #define URL_LISTEN_SOURCE "/listen/source/"
55
56 #define MIME_HTML "text/html; charset=utf-8"
57 #define MIME_TEXT "text/plain; charset=utf-8"
58 #define MIME_CSS "text/css"
59
60 #define HTML_HEADER(t) \
61 "<?xml version=\"1.0\"?>\n" \
62 "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Strict//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd\">\n" \
63 "<html xmlns=\"http://www.w3.org/1999/xhtml\">\n" \
64 " <head>\n" \
65 " <title>"t"</title>\n" \
66 " <link rel=\"stylesheet\" type=\"text/css\" href=\"style\"/>\n" \
67 " </head>\n" \
68 " <body>\n"
69
70 #define HTML_FOOTER \
71 " </body>\n" \
72 "</html>\n"
73
74 #define RECORD_BUFFER_SECONDS (5)
75 #define DEFAULT_SOURCE_LATENCY (300*PA_USEC_PER_MSEC)
76
77 enum state {
78 STATE_REQUEST_LINE,
79 STATE_MIME_HEADER,
80 STATE_DATA
81 };
82
83 enum method {
84 METHOD_GET,
85 METHOD_HEAD
86 };
87
88 struct connection {
89 pa_http_protocol *protocol;
90 pa_iochannel *io;
91 pa_ioline *line;
92 pa_memblockq *output_memblockq;
93 pa_source_output *source_output;
94 pa_client *client;
95 enum state state;
96 char *url;
97 enum method method;
98 pa_module *module;
99 };
100
101 struct pa_http_protocol {
102 PA_REFCNT_DECLARE;
103
104 pa_core *core;
105 pa_idxset *connections;
106
107 pa_strlist *servers;
108 };
109
110 enum {
111 SOURCE_OUTPUT_MESSAGE_POST_DATA = PA_SOURCE_OUTPUT_MESSAGE_MAX
112 };
113
114 /* Called from main context */
115 static void connection_unlink(struct connection *c) {
116 pa_assert(c);
117
118 if (c->source_output) {
119 pa_source_output_unlink(c->source_output);
120 c->source_output->userdata = NULL;
121 pa_source_output_unref(c->source_output);
122 }
123
124 if (c->client)
125 pa_client_free(c->client);
126
127 pa_xfree(c->url);
128
129 if (c->line)
130 pa_ioline_unref(c->line);
131
132 if (c->io)
133 pa_iochannel_free(c->io);
134
135 if (c->output_memblockq)
136 pa_memblockq_free(c->output_memblockq);
137
138 pa_idxset_remove_by_data(c->protocol->connections, c, NULL);
139
140 pa_xfree(c);
141 }
142
143 /* Called from main context */
144 static int do_write(struct connection *c) {
145 pa_memchunk chunk;
146 ssize_t r;
147 void *p;
148
149 pa_assert(c);
150
151 if (pa_memblockq_peek(c->output_memblockq, &chunk) < 0)
152 return 0;
153
154 pa_assert(chunk.memblock);
155 pa_assert(chunk.length > 0);
156
157 p = pa_memblock_acquire(chunk.memblock);
158 r = pa_iochannel_write(c->io, (uint8_t*) p+chunk.index, chunk.length);
159 pa_memblock_release(chunk.memblock);
160
161 pa_memblock_unref(chunk.memblock);
162
163 if (r < 0) {
164
165 if (errno == EINTR || errno == EAGAIN)
166 return 0;
167
168 pa_log("write(): %s", pa_cstrerror(errno));
169 return -1;
170 }
171
172 pa_memblockq_drop(c->output_memblockq, (size_t) r);
173
174 return 0;
175 }
176
177 /* Called from main context */
178 static void do_work(struct connection *c) {
179 pa_assert(c);
180
181 if (pa_iochannel_is_hungup(c->io))
182 goto fail;
183
184 if (pa_iochannel_is_writable(c->io))
185 if (do_write(c) < 0)
186 goto fail;
187
188 return;
189
190 fail:
191 connection_unlink(c);
192 }
193
194 /* Called from thread context, except when it is not */
195 static int source_output_process_msg(pa_msgobject *m, int code, void *userdata, int64_t offset, pa_memchunk *chunk) {
196 pa_source_output *o = PA_SOURCE_OUTPUT(m);
197 struct connection *c;
198
199 pa_source_output_assert_ref(o);
200
201 if (!(c = o->userdata))
202 return -1;
203
204 switch (code) {
205
206 case SOURCE_OUTPUT_MESSAGE_POST_DATA:
207 /* While this function is usually called from IO thread
208 * context, this specific command is not! */
209 pa_memblockq_push_align(c->output_memblockq, chunk);
210 do_work(c);
211 break;
212
213 default:
214 return pa_source_output_process_msg(m, code, userdata, offset, chunk);
215 }
216
217 return 0;
218 }
219
220 /* Called from thread context */
221 static void source_output_push_cb(pa_source_output *o, const pa_memchunk *chunk) {
222 struct connection *c;
223
224 pa_source_output_assert_ref(o);
225 pa_assert_se(c = o->userdata);
226 pa_assert(chunk);
227
228 pa_asyncmsgq_post(pa_thread_mq_get()->outq, PA_MSGOBJECT(o), SOURCE_OUTPUT_MESSAGE_POST_DATA, NULL, 0, chunk, NULL);
229 }
230
231 /* Called from main context */
232 static void source_output_kill_cb(pa_source_output *o) {
233 struct connection*c;
234
235 pa_source_output_assert_ref(o);
236 pa_assert_se(c = o->userdata);
237
238 connection_unlink(c);
239 }
240
241 /* Called from main context */
242 static pa_usec_t source_output_get_latency_cb(pa_source_output *o) {
243 struct connection*c;
244
245 pa_source_output_assert_ref(o);
246 pa_assert_se(c = o->userdata);
247
248 return pa_bytes_to_usec(pa_memblockq_get_length(c->output_memblockq), &c->source_output->sample_spec);
249 }
250
251 /*** client callbacks ***/
252 static void client_kill_cb(pa_client *client) {
253 struct connection*c;
254
255 pa_assert(client);
256 pa_assert_se(c = client->userdata);
257
258 connection_unlink(c);
259 }
260
261 /*** pa_iochannel callbacks ***/
262 static void io_callback(pa_iochannel*io, void *userdata) {
263 struct connection *c = userdata;
264
265 pa_assert(c);
266 pa_assert(io);
267
268 do_work(c);
269 }
270
271 static char *escape_html(const char *t) {
272 pa_strbuf *sb;
273 const char *p, *e;
274
275 sb = pa_strbuf_new();
276
277 for (e = p = t; *p; p++) {
278
279 if (*p == '>' || *p == '<' || *p == '&') {
280
281 if (p > e) {
282 pa_strbuf_putsn(sb, e, p-e);
283 e = p + 1;
284 }
285
286 if (*p == '>')
287 pa_strbuf_puts(sb, "&gt;");
288 else if (*p == '<')
289 pa_strbuf_puts(sb, "&lt;");
290 else
291 pa_strbuf_puts(sb, "&amp;");
292 }
293 }
294
295 if (p > e)
296 pa_strbuf_putsn(sb, e, p-e);
297
298 return pa_strbuf_tostring_free(sb);
299 }
300
301 static void http_response(
302 struct connection *c,
303 int code,
304 const char *msg,
305 const char *mime) {
306
307 char *s;
308
309 pa_assert(c);
310 pa_assert(msg);
311 pa_assert(mime);
312
313 s = pa_sprintf_malloc(
314 "HTTP/1.0 %i %s\n"
315 "Connection: close\n"
316 "Content-Type: %s\n"
317 "Cache-Control: no-cache\n"
318 "Expires: 0\n"
319 "Server: "PACKAGE_NAME"/"PACKAGE_VERSION"\n"
320 "\n", code, msg, mime);
321 pa_ioline_puts(c->line, s);
322 pa_xfree(s);
323 }
324
325 static void html_response(
326 struct connection *c,
327 int code,
328 const char *msg,
329 const char *text) {
330
331 char *s;
332 pa_assert(c);
333
334 http_response(c, code, msg, MIME_HTML);
335
336 if (c->method == METHOD_HEAD) {
337 pa_ioline_defer_close(c->line);
338 return;
339 }
340
341 if (!text)
342 text = msg;
343
344 s = pa_sprintf_malloc(
345 HTML_HEADER("%s")
346 "%s"
347 HTML_FOOTER,
348 text, text);
349
350 pa_ioline_puts(c->line, s);
351 pa_xfree(s);
352
353 pa_ioline_defer_close(c->line);
354 }
355
356 static void html_print_field(pa_ioline *line, const char *left, const char *right) {
357 char *eleft, *eright;
358
359 eleft = escape_html(left);
360 eright = escape_html(right);
361
362 pa_ioline_printf(line,
363 "<tr><td><b>%s</b></td>"
364 "<td>%s</td></tr>\n", eleft, eright);
365
366 pa_xfree(eleft);
367 pa_xfree(eright);
368 }
369
370 static void handle_root(struct connection *c) {
371 char *t;
372
373 pa_assert(c);
374
375 http_response(c, 200, "OK", MIME_HTML);
376
377 if (c->method == METHOD_HEAD) {
378 pa_ioline_defer_close(c->line);
379 return;
380 }
381
382 pa_ioline_puts(c->line,
383 HTML_HEADER(PACKAGE_NAME" "PACKAGE_VERSION)
384 "<h1>"PACKAGE_NAME" "PACKAGE_VERSION"</h1>\n"
385 "<table>\n");
386
387 t = pa_get_user_name_malloc();
388 html_print_field(c->line, "User Name:", t);
389 pa_xfree(t);
390
391 t = pa_get_host_name_malloc();
392 html_print_field(c->line, "Host name:", t);
393 pa_xfree(t);
394
395 t = pa_machine_id();
396 html_print_field(c->line, "Machine ID:", t);
397 pa_xfree(t);
398
399 t = pa_uname_string();
400 html_print_field(c->line, "System:", t);
401 pa_xfree(t);
402
403 t = pa_sprintf_malloc("%lu", (unsigned long) getpid());
404 html_print_field(c->line, "Process ID:", t);
405 pa_xfree(t);
406
407 pa_ioline_puts(c->line,
408 "</table>\n"
409 "<p><a href=\"" URL_STATUS "\">Show an extensive server status report</a></p>\n"
410 "<p><a href=\"" URL_LISTEN "\">Monitor sinks and sources</a></p>\n"
411 HTML_FOOTER);
412
413 pa_ioline_defer_close(c->line);
414 }
415
416 static void handle_css(struct connection *c) {
417 pa_assert(c);
418
419 http_response(c, 200, "OK", MIME_CSS);
420
421 if (c->method == METHOD_HEAD) {
422 pa_ioline_defer_close(c->line);
423 return;
424 }
425
426 pa_ioline_puts(c->line,
427 "body { color: black; background-color: white; }\n"
428 "a:link, a:visited { color: #900000; }\n"
429 "div.news-date { font-size: 80%; font-style: italic; }\n"
430 "pre { background-color: #f0f0f0; padding: 0.4cm; }\n"
431 ".grey { color: #8f8f8f; font-size: 80%; }"
432 "table { margin-left: 1cm; border:1px solid lightgrey; padding: 0.2cm; }\n"
433 "td { padding-left:10px; padding-right:10px; }\n");
434
435 pa_ioline_defer_close(c->line);
436 }
437
438 static void handle_status(struct connection *c) {
439 char *r;
440
441 pa_assert(c);
442
443 http_response(c, 200, "OK", MIME_TEXT);
444
445 if (c->method == METHOD_HEAD) {
446 pa_ioline_defer_close(c->line);
447 return;
448 }
449
450 r = pa_full_status_string(c->protocol->core);
451 pa_ioline_puts(c->line, r);
452 pa_xfree(r);
453
454 pa_ioline_defer_close(c->line);
455 }
456
457 static void handle_listen(struct connection *c) {
458 pa_source *source;
459 pa_sink *sink;
460 uint32_t idx;
461
462 http_response(c, 200, "OK", MIME_HTML);
463
464 pa_ioline_puts(c->line,
465 HTML_HEADER("Listen")
466 "<h2>Sinks</h2>\n"
467 "<p>\n");
468
469 if (c->method == METHOD_HEAD) {
470 pa_ioline_defer_close(c->line);
471 return;
472 }
473
474 PA_IDXSET_FOREACH(sink, c->protocol->core->sinks, idx) {
475 char *t, *m;
476
477 t = escape_html(pa_strna(pa_proplist_gets(sink->proplist, PA_PROP_DEVICE_DESCRIPTION)));
478 m = pa_sample_spec_to_mime_type_mimefy(&sink->sample_spec, &sink->channel_map);
479
480 pa_ioline_printf(c->line,
481 "<a href=\"" URL_LISTEN_SOURCE "%s\" title=\"%s\">%s</a><br/>\n",
482 sink->monitor_source->name, m, t);
483
484 pa_xfree(t);
485 pa_xfree(m);
486 }
487
488 pa_ioline_puts(c->line,
489 "</p>\n"
490 "<h2>Sources</h2>\n"
491 "<p>\n");
492
493 PA_IDXSET_FOREACH(source, c->protocol->core->sources, idx) {
494 char *t, *m;
495
496 if (source->monitor_of)
497 continue;
498
499 t = escape_html(pa_strna(pa_proplist_gets(source->proplist, PA_PROP_DEVICE_DESCRIPTION)));
500 m = pa_sample_spec_to_mime_type_mimefy(&source->sample_spec, &source->channel_map);
501
502 pa_ioline_printf(c->line,
503 "<a href=\"" URL_LISTEN_SOURCE "%s\" title=\"%s\">%s</a><br/>\n",
504 source->name, m, t);
505
506 pa_xfree(m);
507 pa_xfree(t);
508
509 }
510
511 pa_ioline_puts(c->line,
512 "</p>\n"
513 HTML_FOOTER);
514
515 pa_ioline_defer_close(c->line);
516 }
517
518 static void line_drain_callback(pa_ioline *l, void *userdata) {
519 struct connection *c;
520
521 pa_assert(l);
522 pa_assert_se(c = userdata);
523
524 /* We don't need the line reader anymore, instead we need a real
525 * binary io channel */
526 pa_assert_se(c->io = pa_ioline_detach_iochannel(c->line));
527 pa_iochannel_set_callback(c->io, io_callback, c);
528
529 pa_iochannel_socket_set_sndbuf(c->io, pa_memblockq_get_length(c->output_memblockq));
530
531 pa_ioline_unref(c->line);
532 c->line = NULL;
533 }
534
535 static void handle_listen_prefix(struct connection *c, const char *source_name) {
536 pa_source *source;
537 pa_source_output_new_data data;
538 pa_sample_spec ss;
539 pa_channel_map cm;
540 char *t;
541 size_t l;
542
543 pa_assert(c);
544 pa_assert(source_name);
545
546 pa_assert(c->line);
547 pa_assert(!c->io);
548
549 if (!(source = pa_namereg_get(c->protocol->core, source_name, PA_NAMEREG_SOURCE))) {
550 html_response(c, 404, "Source not found", NULL);
551 return;
552 }
553
554 ss = source->sample_spec;
555 cm = source->channel_map;
556
557 pa_sample_spec_mimefy(&ss, &cm);
558
559 pa_source_output_new_data_init(&data);
560 data.driver = __FILE__;
561 data.module = c->module;
562 data.client = c->client;
563 pa_source_output_new_data_set_source(&data, source, FALSE);
564 pa_proplist_update(data.proplist, PA_UPDATE_MERGE, c->client->proplist);
565 pa_source_output_new_data_set_sample_spec(&data, &ss);
566 pa_source_output_new_data_set_channel_map(&data, &cm);
567
568 pa_source_output_new(&c->source_output, c->protocol->core, &data);
569 pa_source_output_new_data_done(&data);
570
571 if (!c->source_output) {
572 html_response(c, 403, "Cannot create source output", NULL);
573 return;
574 }
575
576 c->source_output->parent.process_msg = source_output_process_msg;
577 c->source_output->push = source_output_push_cb;
578 c->source_output->kill = source_output_kill_cb;
579 c->source_output->get_latency = source_output_get_latency_cb;
580 c->source_output->userdata = c;
581
582 pa_source_output_set_requested_latency(c->source_output, DEFAULT_SOURCE_LATENCY);
583
584 l = (size_t) (pa_bytes_per_second(&ss)*RECORD_BUFFER_SECONDS);
585 c->output_memblockq = pa_memblockq_new(
586 0,
587 l,
588 0,
589 pa_frame_size(&ss),
590 1,
591 0,
592 0,
593 NULL);
594
595 pa_source_output_put(c->source_output);
596
597 t = pa_sample_spec_to_mime_type(&ss, &cm);
598 http_response(c, 200, "OK", t);
599 pa_xfree(t);
600
601 if(c->method == METHOD_HEAD) {
602 connection_unlink(c);
603 return;
604 }
605 pa_ioline_set_callback(c->line, NULL, NULL);
606
607 if (pa_ioline_is_drained(c->line))
608 line_drain_callback(c->line, c);
609 else
610 pa_ioline_set_drain_callback(c->line, line_drain_callback, c);
611 }
612
613 static void handle_url(struct connection *c) {
614 pa_assert(c);
615
616 pa_log_debug("Request for %s", c->url);
617
618 if (pa_streq(c->url, URL_ROOT))
619 handle_root(c);
620 else if (pa_streq(c->url, URL_CSS))
621 handle_css(c);
622 else if (pa_streq(c->url, URL_STATUS))
623 handle_status(c);
624 else if (pa_streq(c->url, URL_LISTEN))
625 handle_listen(c);
626 else if (pa_startswith(c->url, URL_LISTEN_SOURCE))
627 handle_listen_prefix(c, c->url + sizeof(URL_LISTEN_SOURCE)-1);
628 else
629 html_response(c, 404, "Not Found", NULL);
630 }
631
632 static void line_callback(pa_ioline *line, const char *s, void *userdata) {
633 struct connection *c = userdata;
634 pa_assert(line);
635 pa_assert(c);
636
637 if (!s) {
638 /* EOF */
639 connection_unlink(c);
640 return;
641 }
642
643 switch (c->state) {
644 case STATE_REQUEST_LINE: {
645 if (pa_startswith(s, "GET ")) {
646 c->method = METHOD_GET;
647 s +=4;
648 } else if (pa_startswith(s, "HEAD ")) {
649 c->method = METHOD_HEAD;
650 s +=5;
651 } else {
652 goto fail;
653 }
654
655 c->url = pa_xstrndup(s, strcspn(s, " \r\n\t?"));
656 c->state = STATE_MIME_HEADER;
657 break;
658 }
659
660 case STATE_MIME_HEADER: {
661
662 /* Ignore MIME headers */
663 if (strcspn(s, " \r\n") != 0)
664 break;
665
666 /* We're done */
667 c->state = STATE_DATA;
668
669 handle_url(c);
670 break;
671 }
672
673 default:
674 ;
675 }
676
677 return;
678
679 fail:
680 html_response(c, 500, "Internal Server Error", NULL);
681 }
682
683 void pa_http_protocol_connect(pa_http_protocol *p, pa_iochannel *io, pa_module *m) {
684 struct connection *c;
685 pa_client_new_data client_data;
686 char pname[128];
687
688 pa_assert(p);
689 pa_assert(io);
690 pa_assert(m);
691
692 if (pa_idxset_size(p->connections)+1 > MAX_CONNECTIONS) {
693 pa_log("Warning! Too many connections (%u), dropping incoming connection.", MAX_CONNECTIONS);
694 pa_iochannel_free(io);
695 return;
696 }
697
698 c = pa_xnew0(struct connection, 1);
699 c->protocol = p;
700 c->state = STATE_REQUEST_LINE;
701 c->module = m;
702
703 c->line = pa_ioline_new(io);
704 pa_ioline_set_callback(c->line, line_callback, c);
705
706 pa_client_new_data_init(&client_data);
707 client_data.module = c->module;
708 client_data.driver = __FILE__;
709 pa_iochannel_socket_peer_to_string(io, pname, sizeof(pname));
710 pa_proplist_setf(client_data.proplist, PA_PROP_APPLICATION_NAME, "HTTP client (%s)", pname);
711 pa_proplist_sets(client_data.proplist, "http-protocol.peer", pname);
712 c->client = pa_client_new(p->core, &client_data);
713 pa_client_new_data_done(&client_data);
714
715 if (!c->client)
716 goto fail;
717
718 c->client->kill = client_kill_cb;
719 c->client->userdata = c;
720
721 pa_idxset_put(p->connections, c, NULL);
722
723 return;
724
725 fail:
726 if (c)
727 connection_unlink(c);
728 }
729
730 void pa_http_protocol_disconnect(pa_http_protocol *p, pa_module *m) {
731 struct connection *c;
732 uint32_t idx;
733
734 pa_assert(p);
735 pa_assert(m);
736
737 PA_IDXSET_FOREACH(c, p->connections, idx)
738 if (c->module == m)
739 connection_unlink(c);
740 }
741
742 static pa_http_protocol* http_protocol_new(pa_core *c) {
743 pa_http_protocol *p;
744
745 pa_assert(c);
746
747 p = pa_xnew0(pa_http_protocol, 1);
748 PA_REFCNT_INIT(p);
749 p->core = c;
750 p->connections = pa_idxset_new(NULL, NULL);
751
752 pa_assert_se(pa_shared_set(c, "http-protocol", p) >= 0);
753
754 return p;
755 }
756
757 pa_http_protocol* pa_http_protocol_get(pa_core *c) {
758 pa_http_protocol *p;
759
760 if ((p = pa_shared_get(c, "http-protocol")))
761 return pa_http_protocol_ref(p);
762
763 return http_protocol_new(c);
764 }
765
766 pa_http_protocol* pa_http_protocol_ref(pa_http_protocol *p) {
767 pa_assert(p);
768 pa_assert(PA_REFCNT_VALUE(p) >= 1);
769
770 PA_REFCNT_INC(p);
771
772 return p;
773 }
774
775 void pa_http_protocol_unref(pa_http_protocol *p) {
776 struct connection *c;
777
778 pa_assert(p);
779 pa_assert(PA_REFCNT_VALUE(p) >= 1);
780
781 if (PA_REFCNT_DEC(p) > 0)
782 return;
783
784 while ((c = pa_idxset_first(p->connections, NULL)))
785 connection_unlink(c);
786
787 pa_idxset_free(p->connections, NULL, NULL);
788
789 pa_strlist_free(p->servers);
790
791 pa_assert_se(pa_shared_remove(p->core, "http-protocol") >= 0);
792
793 pa_xfree(p);
794 }
795
796 void pa_http_protocol_add_server_string(pa_http_protocol *p, const char *name) {
797 pa_assert(p);
798 pa_assert(PA_REFCNT_VALUE(p) >= 1);
799 pa_assert(name);
800
801 p->servers = pa_strlist_prepend(p->servers, name);
802 }
803
804 void pa_http_protocol_remove_server_string(pa_http_protocol *p, const char *name) {
805 pa_assert(p);
806 pa_assert(PA_REFCNT_VALUE(p) >= 1);
807 pa_assert(name);
808
809 p->servers = pa_strlist_remove(p->servers, name);
810 }
811
812 pa_strlist *pa_http_protocol_servers(pa_http_protocol *p) {
813 pa_assert(p);
814 pa_assert(PA_REFCNT_VALUE(p) >= 1);
815
816 return p->servers;
817 }