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