2 This file is part of PulseAudio.
4 Copyright 2005-2009 Lennart Poettering
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.
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.
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
31 #include <pulse/util.h>
32 #include <pulse/xmalloc.h>
33 #include <pulse/timeval.h>
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>
46 #include "protocol-http.h"
48 /* Don't allow more than this many concurrent connections */
49 #define MAX_CONNECTIONS 10
52 #define URL_CSS "/style"
53 #define URL_STATUS "/status"
54 #define URL_LISTEN "/listen"
55 #define URL_LISTEN_SOURCE "/listen/source/"
57 #define MIME_HTML "text/html; charset=utf-8"
58 #define MIME_TEXT "text/plain; charset=utf-8"
59 #define MIME_CSS "text/css"
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" \
66 " <title>"t"</title>\n" \
67 " <link rel=\"stylesheet\" type=\"text/css\" href=\"style\"/>\n" \
75 #define RECORD_BUFFER_SECONDS (5)
76 #define DEFAULT_SOURCE_LATENCY (300*PA_USEC_PER_MSEC)
90 pa_http_protocol
*protocol
;
93 pa_memblockq
*output_memblockq
;
94 pa_source_output
*source_output
;
102 struct pa_http_protocol
{
106 pa_idxset
*connections
;
112 SOURCE_OUTPUT_MESSAGE_POST_DATA
= PA_SOURCE_OUTPUT_MESSAGE_MAX
115 /* Called from main context */
116 static void connection_unlink(struct connection
*c
) {
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
);
126 pa_client_free(c
->client
);
131 pa_ioline_unref(c
->line
);
134 pa_iochannel_free(c
->io
);
136 if (c
->output_memblockq
)
137 pa_memblockq_free(c
->output_memblockq
);
139 pa_idxset_remove_by_data(c
->protocol
->connections
, c
, NULL
);
144 /* Called from main context */
145 static int do_write(struct connection
*c
) {
152 if (pa_memblockq_peek(c
->output_memblockq
, &chunk
) < 0)
155 pa_assert(chunk
.memblock
);
156 pa_assert(chunk
.length
> 0);
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
);
162 pa_memblock_unref(chunk
.memblock
);
166 if (errno
== EINTR
|| errno
== EAGAIN
)
169 pa_log("write(): %s", pa_cstrerror(errno
));
173 pa_memblockq_drop(c
->output_memblockq
, (size_t) r
);
178 /* Called from main context */
179 static void do_work(struct connection
*c
) {
182 if (pa_iochannel_is_hungup(c
->io
))
185 if (pa_iochannel_is_writable(c
->io
))
192 connection_unlink(c
);
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
;
200 pa_source_output_assert_ref(o
);
202 if (!(c
= o
->userdata
))
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
);
215 return pa_source_output_process_msg(m
, code
, userdata
, offset
, chunk
);
221 /* Called from thread context */
222 static void source_output_push_cb(pa_source_output
*o
, const pa_memchunk
*chunk
) {
223 struct connection
*c
;
225 pa_source_output_assert_ref(o
);
226 pa_assert_se(c
= o
->userdata
);
229 pa_asyncmsgq_post(pa_thread_mq_get()->outq
, PA_MSGOBJECT(o
), SOURCE_OUTPUT_MESSAGE_POST_DATA
, NULL
, 0, chunk
, NULL
);
232 /* Called from main context */
233 static void source_output_kill_cb(pa_source_output
*o
) {
236 pa_source_output_assert_ref(o
);
237 pa_assert_se(c
= o
->userdata
);
239 connection_unlink(c
);
242 /* Called from main context */
243 static pa_usec_t
source_output_get_latency_cb(pa_source_output
*o
) {
246 pa_source_output_assert_ref(o
);
247 pa_assert_se(c
= o
->userdata
);
249 return pa_bytes_to_usec(pa_memblockq_get_length(c
->output_memblockq
), &c
->source_output
->sample_spec
);
252 /*** client callbacks ***/
253 static void client_kill_cb(pa_client
*client
) {
257 pa_assert_se(c
= client
->userdata
);
259 connection_unlink(c
);
262 /*** pa_iochannel callbacks ***/
263 static void io_callback(pa_iochannel
*io
, void *userdata
) {
264 struct connection
*c
= userdata
;
272 static char *escape_html(const char *t
) {
276 sb
= pa_strbuf_new();
278 for (e
= p
= t
; *p
; p
++) {
280 if (*p
== '>' || *p
== '<' || *p
== '&') {
283 pa_strbuf_putsn(sb
, e
, p
-e
);
288 pa_strbuf_puts(sb
, ">");
290 pa_strbuf_puts(sb
, "<");
292 pa_strbuf_puts(sb
, "&");
297 pa_strbuf_putsn(sb
, e
, p
-e
);
299 return pa_strbuf_tostring_free(sb
);
302 static void http_response(
303 struct connection
*c
,
314 s
= pa_sprintf_malloc(
316 "Connection: close\n"
318 "Cache-Control: no-cache\n"
320 "Server: "PACKAGE_NAME
"/"PACKAGE_VERSION
"\n"
321 "\n", code
, msg
, mime
);
322 pa_ioline_puts(c
->line
, s
);
326 static void html_response(
327 struct connection
*c
,
335 http_response(c
, code
, msg
, MIME_HTML
);
337 if (c
->method
== METHOD_HEAD
) {
338 pa_ioline_defer_close(c
->line
);
345 s
= pa_sprintf_malloc(
351 pa_ioline_puts(c
->line
, s
);
354 pa_ioline_defer_close(c
->line
);
357 static void html_print_field(pa_ioline
*line
, const char *left
, const char *right
) {
358 char *eleft
, *eright
;
360 eleft
= escape_html(left
);
361 eright
= escape_html(right
);
363 pa_ioline_printf(line
,
364 "<tr><td><b>%s</b></td>"
365 "<td>%s</td></tr>\n", eleft
, eright
);
371 static void handle_root(struct connection
*c
) {
376 http_response(c
, 200, "OK", MIME_HTML
);
378 if (c
->method
== METHOD_HEAD
) {
379 pa_ioline_defer_close(c
->line
);
383 pa_ioline_puts(c
->line
,
384 HTML_HEADER(PACKAGE_NAME
" "PACKAGE_VERSION
)
385 "<h1>"PACKAGE_NAME
" "PACKAGE_VERSION
"</h1>\n"
388 t
= pa_get_user_name_malloc();
389 html_print_field(c
->line
, "User Name:", t
);
392 t
= pa_get_host_name_malloc();
393 html_print_field(c
->line
, "Host name:", t
);
397 html_print_field(c
->line
, "Machine ID:", t
);
400 t
= pa_uname_string();
401 html_print_field(c
->line
, "System:", t
);
404 t
= pa_sprintf_malloc("%lu", (unsigned long) getpid());
405 html_print_field(c
->line
, "Process ID:", t
);
408 pa_ioline_puts(c
->line
,
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"
414 pa_ioline_defer_close(c
->line
);
417 static void handle_css(struct connection
*c
) {
420 http_response(c
, 200, "OK", MIME_CSS
);
422 if (c
->method
== METHOD_HEAD
) {
423 pa_ioline_defer_close(c
->line
);
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");
436 pa_ioline_defer_close(c
->line
);
439 static void handle_status(struct connection
*c
) {
444 http_response(c
, 200, "OK", MIME_TEXT
);
446 if (c
->method
== METHOD_HEAD
) {
447 pa_ioline_defer_close(c
->line
);
451 r
= pa_full_status_string(c
->protocol
->core
);
452 pa_ioline_puts(c
->line
, r
);
455 pa_ioline_defer_close(c
->line
);
458 static void handle_listen(struct connection
*c
) {
463 http_response(c
, 200, "OK", MIME_HTML
);
465 pa_ioline_puts(c
->line
,
466 HTML_HEADER("Listen")
470 if (c
->method
== METHOD_HEAD
) {
471 pa_ioline_defer_close(c
->line
);
475 PA_IDXSET_FOREACH(sink
, c
->protocol
->core
->sinks
, idx
) {
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
);
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
);
489 pa_ioline_puts(c
->line
,
494 PA_IDXSET_FOREACH(source
, c
->protocol
->core
->sources
, idx
) {
497 if (source
->monitor_of
)
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
);
503 pa_ioline_printf(c
->line
,
504 "<a href=\"" URL_LISTEN_SOURCE
"%s\" title=\"%s\">%s</a><br/>\n",
512 pa_ioline_puts(c
->line
,
516 pa_ioline_defer_close(c
->line
);
519 static void line_drain_callback(pa_ioline
*l
, void *userdata
) {
520 struct connection
*c
;
523 pa_assert_se(c
= userdata
);
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
);
530 pa_iochannel_socket_set_sndbuf(c
->io
, pa_memblockq_get_length(c
->output_memblockq
));
532 pa_ioline_unref(c
->line
);
536 static void handle_listen_prefix(struct connection
*c
, const char *source_name
) {
538 pa_source_output_new_data data
;
545 pa_assert(source_name
);
550 if (!(source
= pa_namereg_get(c
->protocol
->core
, source_name
, PA_NAMEREG_SOURCE
))) {
551 html_response(c
, 404, "Source not found", NULL
);
555 ss
= source
->sample_spec
;
556 cm
= source
->channel_map
;
558 pa_sample_spec_mimefy(&ss
, &cm
);
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
);
569 pa_source_output_new(&c
->source_output
, c
->protocol
->core
, &data
);
570 pa_source_output_new_data_done(&data
);
572 if (!c
->source_output
) {
573 html_response(c
, 403, "Cannot create source output", NULL
);
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
;
583 pa_source_output_set_requested_latency(c
->source_output
, DEFAULT_SOURCE_LATENCY
);
585 l
= (size_t) (pa_bytes_per_second(&ss
)*RECORD_BUFFER_SECONDS
);
586 c
->output_memblockq
= pa_memblockq_new(
596 pa_source_output_put(c
->source_output
);
598 t
= pa_sample_spec_to_mime_type(&ss
, &cm
);
599 http_response(c
, 200, "OK", t
);
602 if(c
->method
== METHOD_HEAD
) {
603 connection_unlink(c
);
606 pa_ioline_set_callback(c
->line
, NULL
, NULL
);
608 if (pa_ioline_is_drained(c
->line
))
609 line_drain_callback(c
->line
, c
);
611 pa_ioline_set_drain_callback(c
->line
, line_drain_callback
, c
);
614 static void handle_url(struct connection
*c
) {
617 pa_log_debug("Request for %s", c
->url
);
619 if (pa_streq(c
->url
, URL_ROOT
))
621 else if (pa_streq(c
->url
, URL_CSS
))
623 else if (pa_streq(c
->url
, URL_STATUS
))
625 else if (pa_streq(c
->url
, URL_LISTEN
))
627 else if (pa_startswith(c
->url
, URL_LISTEN_SOURCE
))
628 handle_listen_prefix(c
, c
->url
+ sizeof(URL_LISTEN_SOURCE
)-1);
630 html_response(c
, 404, "Not Found", NULL
);
633 static void line_callback(pa_ioline
*line
, const char *s
, void *userdata
) {
634 struct connection
*c
= userdata
;
640 connection_unlink(c
);
645 case STATE_REQUEST_LINE
: {
646 if (pa_startswith(s
, "GET ")) {
647 c
->method
= METHOD_GET
;
649 } else if (pa_startswith(s
, "HEAD ")) {
650 c
->method
= METHOD_HEAD
;
656 c
->url
= pa_xstrndup(s
, strcspn(s
, " \r\n\t?"));
657 c
->state
= STATE_MIME_HEADER
;
661 case STATE_MIME_HEADER
: {
663 /* Ignore MIME headers */
664 if (strcspn(s
, " \r\n") != 0)
668 c
->state
= STATE_DATA
;
681 html_response(c
, 500, "Internal Server Error", NULL
);
684 void pa_http_protocol_connect(pa_http_protocol
*p
, pa_iochannel
*io
, pa_module
*m
) {
685 struct connection
*c
;
686 pa_client_new_data client_data
;
693 if (pa_idxset_size(p
->connections
)+1 > MAX_CONNECTIONS
) {
694 pa_log("Warning! Too many connections (%u), dropping incoming connection.", MAX_CONNECTIONS
);
695 pa_iochannel_free(io
);
699 c
= pa_xnew0(struct connection
, 1);
701 c
->state
= STATE_REQUEST_LINE
;
704 c
->line
= pa_ioline_new(io
);
705 pa_ioline_set_callback(c
->line
, line_callback
, c
);
707 pa_client_new_data_init(&client_data
);
708 client_data
.module
= c
->module
;
709 client_data
.driver
= __FILE__
;
710 pa_iochannel_socket_peer_to_string(io
, pname
, sizeof(pname
));
711 pa_proplist_setf(client_data
.proplist
, PA_PROP_APPLICATION_NAME
, "HTTP client (%s)", pname
);
712 pa_proplist_sets(client_data
.proplist
, "http-protocol.peer", pname
);
713 c
->client
= pa_client_new(p
->core
, &client_data
);
714 pa_client_new_data_done(&client_data
);
719 c
->client
->kill
= client_kill_cb
;
720 c
->client
->userdata
= c
;
722 pa_idxset_put(p
->connections
, c
, NULL
);
728 connection_unlink(c
);
731 void pa_http_protocol_disconnect(pa_http_protocol
*p
, pa_module
*m
) {
732 struct connection
*c
;
738 PA_IDXSET_FOREACH(c
, p
->connections
, idx
)
740 connection_unlink(c
);
743 static pa_http_protocol
* http_protocol_new(pa_core
*c
) {
748 p
= pa_xnew0(pa_http_protocol
, 1);
751 p
->connections
= pa_idxset_new(NULL
, NULL
);
753 pa_assert_se(pa_shared_set(c
, "http-protocol", p
) >= 0);
758 pa_http_protocol
* pa_http_protocol_get(pa_core
*c
) {
761 if ((p
= pa_shared_get(c
, "http-protocol")))
762 return pa_http_protocol_ref(p
);
764 return http_protocol_new(c
);
767 pa_http_protocol
* pa_http_protocol_ref(pa_http_protocol
*p
) {
769 pa_assert(PA_REFCNT_VALUE(p
) >= 1);
776 void pa_http_protocol_unref(pa_http_protocol
*p
) {
777 struct connection
*c
;
780 pa_assert(PA_REFCNT_VALUE(p
) >= 1);
782 if (PA_REFCNT_DEC(p
) > 0)
785 while ((c
= pa_idxset_first(p
->connections
, NULL
)))
786 connection_unlink(c
);
788 pa_idxset_free(p
->connections
, NULL
, NULL
);
790 pa_strlist_free(p
->servers
);
792 pa_assert_se(pa_shared_remove(p
->core
, "http-protocol") >= 0);
797 void pa_http_protocol_add_server_string(pa_http_protocol
*p
, const char *name
) {
799 pa_assert(PA_REFCNT_VALUE(p
) >= 1);
802 p
->servers
= pa_strlist_prepend(p
->servers
, name
);
805 void pa_http_protocol_remove_server_string(pa_http_protocol
*p
, const char *name
) {
807 pa_assert(PA_REFCNT_VALUE(p
) >= 1);
810 p
->servers
= pa_strlist_remove(p
->servers
, name
);
813 pa_strlist
*pa_http_protocol_servers(pa_http_protocol
*p
) {
815 pa_assert(PA_REFCNT_VALUE(p
) >= 1);