Add default-monitor-time-sec
[platform/upstream/pulseaudio.git] / 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, see <http://www.gnu.org/licenses/>.
18 ***/
19
20 #ifdef HAVE_CONFIG_H
21 #include <config.h>
22 #endif
23
24 #include <stdlib.h>
25 #include <stdio.h>
26 #include <string.h>
27 #include <errno.h>
28
29 #include <pulse/util.h>
30 #include <pulse/xmalloc.h>
31 #include <pulse/timeval.h>
32
33 #include <pulsecore/core-util.h>
34 #include <pulsecore/ioline.h>
35 #include <pulsecore/thread-mq.h>
36 #include <pulsecore/macro.h>
37 #include <pulsecore/log.h>
38 #include <pulsecore/namereg.h>
39 #include <pulsecore/cli-text.h>
40 #include <pulsecore/shared.h>
41 #include <pulsecore/core-error.h>
42 #include <pulsecore/mime-type.h>
43
44 #include "protocol-http.h"
45
46 /* Don't allow more than this many concurrent connections */
47 #define MAX_CONNECTIONS 10
48
49 #define URL_ROOT "/"
50 #define URL_CSS "/style"
51 #define URL_STATUS "/status"
52 #define URL_LISTEN "/listen"
53 #define URL_LISTEN_SOURCE "/listen/source/"
54
55 #define MIME_HTML "text/html; charset=utf-8"
56 #define MIME_TEXT "text/plain; charset=utf-8"
57 #define MIME_CSS "text/css"
58
59 #define HTML_HEADER(t)                                                  \
60     "<?xml version=\"1.0\"?>\n"                                         \
61     "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Strict//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd\">\n" \
62     "<html xmlns=\"http://www.w3.org/1999/xhtml\">\n"                   \
63     "        <head>\n"                                                  \
64     "                <title>"t"</title>\n"                              \
65     "                <link rel=\"stylesheet\" type=\"text/css\" href=\"style\"/>\n" \
66     "        </head>\n"                                                 \
67     "        <body>\n"
68
69 #define HTML_FOOTER                                                     \
70     "        </body>\n"                                                 \
71     "</html>\n"
72
73 #define RECORD_BUFFER_SECONDS (5)
74 #define DEFAULT_SOURCE_LATENCY (300*PA_USEC_PER_MSEC)
75
76 enum state {
77     STATE_REQUEST_LINE,
78     STATE_MIME_HEADER,
79     STATE_DATA
80 };
81
82 enum method {
83     METHOD_GET,
84     METHOD_HEAD
85 };
86
87 struct connection {
88     pa_http_protocol *protocol;
89     pa_iochannel *io;
90     pa_ioline *line;
91     pa_memblockq *output_memblockq;
92     pa_source_output *source_output;
93     pa_client *client;
94     enum state state;
95     char *url;
96     enum method method;
97     pa_module *module;
98 };
99
100 struct pa_http_protocol {
101     PA_REFCNT_DECLARE;
102
103     pa_core *core;
104     pa_idxset *connections;
105
106     pa_strlist *servers;
107 };
108
109 enum {
110     SOURCE_OUTPUT_MESSAGE_POST_DATA = PA_SOURCE_OUTPUT_MESSAGE_MAX
111 };
112
113 /* Called from main context */
114 static void connection_unlink(struct connection *c) {
115     pa_assert(c);
116
117     if (c->source_output) {
118         pa_source_output_unlink(c->source_output);
119         c->source_output->userdata = NULL;
120         pa_source_output_unref(c->source_output);
121     }
122
123     if (c->client)
124         pa_client_free(c->client);
125
126     pa_xfree(c->url);
127
128     if (c->line)
129         pa_ioline_unref(c->line);
130
131     if (c->io)
132         pa_iochannel_free(c->io);
133
134     if (c->output_memblockq)
135         pa_memblockq_free(c->output_memblockq);
136
137     pa_idxset_remove_by_data(c->protocol->connections, c, NULL);
138
139     pa_xfree(c);
140 }
141
142 /* Called from main context */
143 static int do_write(struct connection *c) {
144     pa_memchunk chunk;
145     ssize_t r;
146     void *p;
147
148     pa_assert(c);
149
150     if (pa_memblockq_peek(c->output_memblockq, &chunk) < 0)
151         return 0;
152
153     pa_assert(chunk.memblock);
154     pa_assert(chunk.length > 0);
155
156     p = pa_memblock_acquire(chunk.memblock);
157     r = pa_iochannel_write(c->io, (uint8_t*) p+chunk.index, chunk.length);
158     pa_memblock_release(chunk.memblock);
159
160     pa_memblock_unref(chunk.memblock);
161
162     if (r < 0) {
163         pa_log("write(): %s", pa_cstrerror(errno));
164         return -1;
165     }
166
167     pa_memblockq_drop(c->output_memblockq, (size_t) r);
168
169     return 1;
170 }
171
172 /* Called from main context */
173 static void do_work(struct connection *c) {
174     pa_assert(c);
175
176     if (pa_iochannel_is_hungup(c->io))
177         goto fail;
178
179     while (pa_iochannel_is_writable(c->io)) {
180         int r = do_write(c);
181         if (r < 0)
182             goto fail;
183         if (r == 0)
184             break;
185     }
186
187     return;
188
189 fail:
190     connection_unlink(c);
191 }
192
193 /* Called from thread context, except when it is not */
194 static int source_output_process_msg(pa_msgobject *m, int code, void *userdata, int64_t offset, pa_memchunk *chunk) {
195     pa_source_output *o = PA_SOURCE_OUTPUT(m);
196     struct connection *c;
197
198     pa_source_output_assert_ref(o);
199
200     if (!(c = o->userdata))
201         return -1;
202
203     switch (code) {
204
205         case SOURCE_OUTPUT_MESSAGE_POST_DATA:
206             /* While this function is usually called from IO thread
207              * context, this specific command is not! */
208             pa_memblockq_push_align(c->output_memblockq, chunk);
209             do_work(c);
210             break;
211
212         default:
213             return pa_source_output_process_msg(m, code, userdata, offset, chunk);
214     }
215
216     return 0;
217 }
218
219 /* Called from thread context */
220 static void source_output_push_cb(pa_source_output *o, const pa_memchunk *chunk) {
221     struct connection *c;
222
223     pa_source_output_assert_ref(o);
224     pa_assert_se(c = o->userdata);
225     pa_assert(chunk);
226
227     pa_asyncmsgq_post(pa_thread_mq_get()->outq, PA_MSGOBJECT(o), SOURCE_OUTPUT_MESSAGE_POST_DATA, NULL, 0, chunk, NULL);
228 }
229
230 /* Called from main context */
231 static void source_output_kill_cb(pa_source_output *o) {
232     struct connection*c;
233
234     pa_source_output_assert_ref(o);
235     pa_assert_se(c = o->userdata);
236
237     connection_unlink(c);
238 }
239
240 /* Called from main context */
241 static pa_usec_t source_output_get_latency_cb(pa_source_output *o) {
242     struct connection*c;
243
244     pa_source_output_assert_ref(o);
245     pa_assert_se(c = o->userdata);
246
247     return pa_bytes_to_usec(pa_memblockq_get_length(c->output_memblockq), &c->source_output->sample_spec);
248 }
249
250 /*** client callbacks ***/
251 static void client_kill_cb(pa_client *client) {
252     struct connection*c;
253
254     pa_assert(client);
255     pa_assert_se(c = client->userdata);
256
257     connection_unlink(c);
258 }
259
260 /*** pa_iochannel callbacks ***/
261 static void io_callback(pa_iochannel*io, void *userdata) {
262     struct connection *c = userdata;
263
264     pa_assert(c);
265     pa_assert(io);
266
267     do_work(c);
268 }
269
270 static char *escape_html(const char *t) {
271     pa_strbuf *sb;
272     const char *p, *e;
273
274     sb = pa_strbuf_new();
275
276     for (e = p = t; *p; p++) {
277
278         if (*p == '>' || *p == '<' || *p == '&') {
279
280             if (p > e) {
281                 pa_strbuf_putsn(sb, e, p-e);
282                 e = p + 1;
283             }
284
285             if (*p == '>')
286                 pa_strbuf_puts(sb, "&gt;");
287             else if (*p == '<')
288                 pa_strbuf_puts(sb, "&lt;");
289             else
290                 pa_strbuf_puts(sb, "&amp;");
291         }
292     }
293
294     if (p > e)
295         pa_strbuf_putsn(sb, e, p-e);
296
297     return pa_strbuf_to_string_free(sb);
298 }
299
300 static void http_response(
301         struct connection *c,
302         int code,
303         const char *msg,
304         const char *mime) {
305
306     char *s;
307
308     pa_assert(c);
309     pa_assert(msg);
310     pa_assert(mime);
311
312     s = pa_sprintf_malloc(
313             "HTTP/1.0 %i %s\n"
314             "Connection: close\n"
315             "Content-Type: %s\n"
316             "Cache-Control: no-cache\n"
317             "Expires: 0\n"
318             "Server: "PACKAGE_NAME"/"PACKAGE_VERSION"\n"
319             "\n", code, msg, mime);
320     pa_ioline_puts(c->line, s);
321     pa_xfree(s);
322 }
323
324 static void html_response(
325         struct connection *c,
326         int code,
327         const char *msg,
328         const char *text) {
329
330     char *s;
331     pa_assert(c);
332
333     http_response(c, code, msg, MIME_HTML);
334
335     if (c->method == METHOD_HEAD) {
336         pa_ioline_defer_close(c->line);
337         return;
338     }
339
340     if (!text)
341         text = msg;
342
343     s = pa_sprintf_malloc(
344             HTML_HEADER("%s")
345             "%s"
346             HTML_FOOTER,
347             text, text);
348
349     pa_ioline_puts(c->line, s);
350     pa_xfree(s);
351
352     pa_ioline_defer_close(c->line);
353 }
354
355 static void html_print_field(pa_ioline *line, const char *left, const char *right) {
356     char *eleft, *eright;
357
358     eleft = escape_html(left);
359     eright = escape_html(right);
360
361     pa_ioline_printf(line,
362                      "<tr><td><b>%s</b></td>"
363                      "<td>%s</td></tr>\n", eleft, eright);
364
365     pa_xfree(eleft);
366     pa_xfree(eright);
367 }
368
369 static void handle_root(struct connection *c) {
370     char *t;
371
372     pa_assert(c);
373
374     http_response(c, 200, "OK", MIME_HTML);
375
376     if (c->method == METHOD_HEAD) {
377         pa_ioline_defer_close(c->line);
378         return;
379     }
380
381     pa_ioline_puts(c->line,
382                    HTML_HEADER(PACKAGE_NAME" "PACKAGE_VERSION)
383                    "<h1>"PACKAGE_NAME" "PACKAGE_VERSION"</h1>\n"
384                    "<table>\n");
385
386     t = pa_get_user_name_malloc();
387     html_print_field(c->line, "User Name:", t);
388     pa_xfree(t);
389
390     t = pa_get_host_name_malloc();
391     html_print_field(c->line, "Host name:", t);
392     pa_xfree(t);
393
394     t = pa_machine_id();
395     html_print_field(c->line, "Machine ID:", t);
396     pa_xfree(t);
397
398     t = pa_uname_string();
399     html_print_field(c->line, "System:", t);
400     pa_xfree(t);
401
402     t = pa_sprintf_malloc("%lu", (unsigned long) getpid());
403     html_print_field(c->line, "Process ID:", t);
404     pa_xfree(t);
405
406     pa_ioline_puts(c->line,
407                    "</table>\n"
408                    "<p><a href=\"" URL_STATUS "\">Show an extensive server status report</a></p>\n"
409                    "<p><a href=\"" URL_LISTEN "\">Monitor sinks and sources</a></p>\n"
410                    HTML_FOOTER);
411
412     pa_ioline_defer_close(c->line);
413 }
414
415 static void handle_css(struct connection *c) {
416     pa_assert(c);
417
418     http_response(c, 200, "OK", MIME_CSS);
419
420     if (c->method == METHOD_HEAD) {
421         pa_ioline_defer_close(c->line);
422         return;
423     }
424
425     pa_ioline_puts(c->line,
426                    "body { color: black; background-color: white; }\n"
427                    "a:link, a:visited { color: #900000; }\n"
428                    "div.news-date { font-size: 80%; font-style: italic; }\n"
429                    "pre { background-color: #f0f0f0; padding: 0.4cm; }\n"
430                    ".grey { color: #8f8f8f; font-size: 80%; }"
431                    "table {  margin-left: 1cm; border:1px solid lightgrey; padding: 0.2cm; }\n"
432                    "td { padding-left:10px; padding-right:10px; }\n");
433
434     pa_ioline_defer_close(c->line);
435 }
436
437 static void handle_status(struct connection *c) {
438     char *r;
439
440     pa_assert(c);
441
442     http_response(c, 200, "OK", MIME_TEXT);
443
444     if (c->method == METHOD_HEAD) {
445         pa_ioline_defer_close(c->line);
446         return;
447     }
448
449     r = pa_full_status_string(c->protocol->core);
450     pa_ioline_puts(c->line, r);
451     pa_xfree(r);
452
453     pa_ioline_defer_close(c->line);
454 }
455
456 static void handle_listen(struct connection *c) {
457     pa_source *source;
458     pa_sink *sink;
459     uint32_t idx;
460
461     http_response(c, 200, "OK", MIME_HTML);
462
463     pa_ioline_puts(c->line,
464                    HTML_HEADER("Listen")
465                    "<h2>Sinks</h2>\n"
466                    "<p>\n");
467
468     if (c->method == METHOD_HEAD) {
469         pa_ioline_defer_close(c->line);
470         return;
471     }
472
473     PA_IDXSET_FOREACH(sink, c->protocol->core->sinks, idx) {
474         char *t, *m;
475
476         t = escape_html(pa_strna(pa_proplist_gets(sink->proplist, PA_PROP_DEVICE_DESCRIPTION)));
477         m = pa_sample_spec_to_mime_type_mimefy(&sink->sample_spec, &sink->channel_map);
478
479         pa_ioline_printf(c->line,
480                          "<a href=\"" URL_LISTEN_SOURCE "%s\" title=\"%s\">%s</a><br/>\n",
481                          sink->monitor_source->name, m, t);
482
483         pa_xfree(t);
484         pa_xfree(m);
485     }
486
487     pa_ioline_puts(c->line,
488                    "</p>\n"
489                    "<h2>Sources</h2>\n"
490                    "<p>\n");
491
492     PA_IDXSET_FOREACH(source, c->protocol->core->sources, idx) {
493         char *t, *m;
494
495         if (source->monitor_of)
496             continue;
497
498         t = escape_html(pa_strna(pa_proplist_gets(source->proplist, PA_PROP_DEVICE_DESCRIPTION)));
499         m = pa_sample_spec_to_mime_type_mimefy(&source->sample_spec, &source->channel_map);
500
501         pa_ioline_printf(c->line,
502                          "<a href=\"" URL_LISTEN_SOURCE "%s\" title=\"%s\">%s</a><br/>\n",
503                          source->name, m, t);
504
505         pa_xfree(m);
506         pa_xfree(t);
507
508     }
509
510     pa_ioline_puts(c->line,
511                    "</p>\n"
512                    HTML_FOOTER);
513
514     pa_ioline_defer_close(c->line);
515 }
516
517 static void line_drain_callback(pa_ioline *l, void *userdata) {
518     struct connection *c;
519
520     pa_assert(l);
521     pa_assert_se(c = userdata);
522
523     /* We don't need the line reader anymore, instead we need a real
524      * binary io channel */
525     pa_assert_se(c->io = pa_ioline_detach_iochannel(c->line));
526     pa_iochannel_set_callback(c->io, io_callback, c);
527
528     pa_iochannel_socket_set_sndbuf(c->io, pa_memblockq_get_length(c->output_memblockq));
529
530     pa_ioline_unref(c->line);
531     c->line = NULL;
532 }
533
534 static void handle_listen_prefix(struct connection *c, const char *source_name) {
535     pa_source *source;
536     pa_source_output_new_data data;
537     pa_sample_spec ss;
538     pa_channel_map cm;
539     char *t;
540     size_t l;
541
542     pa_assert(c);
543     pa_assert(source_name);
544
545     pa_assert(c->line);
546     pa_assert(!c->io);
547
548     if (!(source = pa_namereg_get(c->protocol->core, source_name, PA_NAMEREG_SOURCE))) {
549         html_response(c, 404, "Source not found", NULL);
550         return;
551     }
552
553     ss = source->sample_spec;
554     cm = source->channel_map;
555
556     pa_sample_spec_mimefy(&ss, &cm);
557
558     pa_source_output_new_data_init(&data);
559     data.driver = __FILE__;
560     data.module = c->module;
561     data.client = c->client;
562     pa_source_output_new_data_set_source(&data, source, false, true);
563     pa_proplist_update(data.proplist, PA_UPDATE_MERGE, c->client->proplist);
564     pa_source_output_new_data_set_sample_spec(&data, &ss);
565     pa_source_output_new_data_set_channel_map(&data, &cm);
566
567     pa_source_output_new(&c->source_output, c->protocol->core, &data);
568     pa_source_output_new_data_done(&data);
569
570     if (!c->source_output) {
571         html_response(c, 403, "Cannot create source output", NULL);
572         return;
573     }
574
575     c->source_output->parent.process_msg = source_output_process_msg;
576     c->source_output->push = source_output_push_cb;
577     c->source_output->kill = source_output_kill_cb;
578     c->source_output->get_latency = source_output_get_latency_cb;
579     c->source_output->userdata = c;
580
581     pa_source_output_set_requested_latency(c->source_output, DEFAULT_SOURCE_LATENCY);
582
583     l = (size_t) (pa_bytes_per_second(&ss)*RECORD_BUFFER_SECONDS);
584     c->output_memblockq = pa_memblockq_new(
585             "http protocol connection output_memblockq",
586             0,
587             l,
588             0,
589             &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);
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 }