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
76enum state {
77    STATE_REQUEST_LINE,
78    STATE_MIME_HEADER,
79    STATE_DATA
80};
81
82enum method {
83    METHOD_GET,
84    METHOD_HEAD
85};
86
87struct 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
100struct pa_http_protocol {
101    PA_REFCNT_DECLARE;
102
103    pa_core *core;
104    pa_idxset *connections;
105
106    pa_strlist *servers;
107};
108
109enum {
110    SOURCE_OUTPUT_MESSAGE_POST_DATA = PA_SOURCE_OUTPUT_MESSAGE_MAX
111};
112
113/* Called from main context */
114static 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 */
143static 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 */
173static 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
189fail:
190    connection_unlink(c);
191}
192
193/* Called from thread context, except when it is not */
194static 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 */
220static 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 */
231static 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 */
241static 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 ***/
251static 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 ***/
261static 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
270static 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
300static 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
324static 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
355static 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
369static 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
415static 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
437static 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
456static 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
517static 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
534static 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
613static 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
632static 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
679fail:
680    html_response(c, 500, "Internal Server Error", NULL);
681}
682
683void 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
725fail:
726    if (c)
727        connection_unlink(c);
728}
729
730void 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
742static 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
757pa_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
766pa_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
775void 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
796void 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
804void 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
812pa_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}
818