X-Git-Url: https://code.delx.au/pulseaudio/blobdiff_plain/a67c21f093202f142438689d3f7cfbdf4ea82eea..dd9265ac78b56c378bc7ca47920be9d9d93392b7:/src/pulsecore/protocol-http.c
diff --git a/src/pulsecore/protocol-http.c b/src/pulsecore/protocol-http.c
index d91ae142..d085e618 100644
--- a/src/pulsecore/protocol-http.c
+++ b/src/pulsecore/protocol-http.c
@@ -1,13 +1,11 @@
-/* $Id$ */
-
/***
This file is part of PulseAudio.
- Copyright 2005-2006 Lennart Poettering
+ Copyright 2005-2009 Lennart Poettering
PulseAudio is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published
- by the Free Software Foundation; either version 2 of the License,
+ by the Free Software Foundation; either version 2.1 of the License,
or (at your option) any later version.
PulseAudio is distributed in the hope that it will be useful, but
@@ -28,187 +26,648 @@
#include
#include
#include
+#include
#include
#include
+#include
+#include
#include
+#include
#include
#include
#include
#include
+#include
+#include
+#include
#include "protocol-http.h"
/* Don't allow more than this many concurrent connections */
#define MAX_CONNECTIONS 10
-#define internal_server_error(c) http_message((c), 500, "Internal Server Error", NULL)
-
#define URL_ROOT "/"
#define URL_CSS "/style"
#define URL_STATUS "/status"
+#define URL_LISTEN "/listen"
+#define URL_LISTEN_SOURCE "/listen/source/"
+
+#define MIME_HTML "text/html; charset=utf-8"
+#define MIME_TEXT "text/plain; charset=utf-8"
+#define MIME_CSS "text/css"
+
+#define HTML_HEADER(t) \
+ "\n" \
+ "\n" \
+ "\n" \
+ " \n" \
+ " "t"\n" \
+ " \n" \
+ " \n" \
+ " \n"
+
+#define HTML_FOOTER \
+ " \n" \
+ "\n"
+
+#define RECORD_BUFFER_SECONDS (5)
+#define DEFAULT_SOURCE_LATENCY (300*PA_USEC_PER_MSEC)
+
+enum state {
+ STATE_REQUEST_LINE,
+ STATE_MIME_HEADER,
+ STATE_DATA
+};
+
+enum method {
+ METHOD_GET,
+ METHOD_HEAD
+};
struct connection {
- pa_protocol_http *protocol;
+ pa_http_protocol *protocol;
+ pa_iochannel *io;
pa_ioline *line;
- enum { REQUEST_LINE, MIME_HEADER, DATA } state;
+ pa_memblockq *output_memblockq;
+ pa_source_output *source_output;
+ pa_client *client;
+ enum state state;
char *url;
+ enum method method;
+ pa_module *module;
};
-struct pa_protocol_http {
- pa_module *module;
+struct pa_http_protocol {
+ PA_REFCNT_DECLARE;
+
pa_core *core;
- pa_socket_server*server;
pa_idxset *connections;
+
+ pa_strlist *servers;
+};
+
+enum {
+ SOURCE_OUTPUT_MESSAGE_POST_DATA = PA_SOURCE_OUTPUT_MESSAGE_MAX
};
-static void http_response(struct connection *c, int code, const char *msg, const char *mime) {
- char s[256];
+/* Called from main context */
+static void connection_unlink(struct connection *c) {
+ pa_assert(c);
+
+ if (c->source_output) {
+ pa_source_output_unlink(c->source_output);
+ c->source_output->userdata = NULL;
+ pa_source_output_unref(c->source_output);
+ }
+
+ if (c->client)
+ pa_client_free(c->client);
+
+ pa_xfree(c->url);
+
+ if (c->line)
+ pa_ioline_unref(c->line);
+
+ if (c->io)
+ pa_iochannel_free(c->io);
+
+ if (c->output_memblockq)
+ pa_memblockq_free(c->output_memblockq);
+
+ pa_idxset_remove_by_data(c->protocol->connections, c, NULL);
+
+ pa_xfree(c);
+}
+
+/* Called from main context */
+static int do_write(struct connection *c) {
+ pa_memchunk chunk;
+ ssize_t r;
+ void *p;
+
+ pa_assert(c);
+
+ if (pa_memblockq_peek(c->output_memblockq, &chunk) < 0)
+ return 0;
+
+ pa_assert(chunk.memblock);
+ pa_assert(chunk.length > 0);
+
+ p = pa_memblock_acquire(chunk.memblock);
+ r = pa_iochannel_write(c->io, (uint8_t*) p+chunk.index, chunk.length);
+ pa_memblock_release(chunk.memblock);
+
+ pa_memblock_unref(chunk.memblock);
+
+ if (r < 0) {
+
+ if (errno == EINTR || errno == EAGAIN)
+ return 0;
+
+ pa_log("write(): %s", pa_cstrerror(errno));
+ return -1;
+ }
+
+ pa_memblockq_drop(c->output_memblockq, (size_t) r);
+
+ return 0;
+}
+
+/* Called from main context */
+static void do_work(struct connection *c) {
+ pa_assert(c);
+
+ if (pa_iochannel_is_hungup(c->io))
+ goto fail;
+
+ if (pa_iochannel_is_writable(c->io))
+ if (do_write(c) < 0)
+ goto fail;
+
+ return;
+
+fail:
+ connection_unlink(c);
+}
+
+/* Called from thread context, except when it is not */
+static int source_output_process_msg(pa_msgobject *m, int code, void *userdata, int64_t offset, pa_memchunk *chunk) {
+ pa_source_output *o = PA_SOURCE_OUTPUT(m);
+ struct connection *c;
+
+ pa_source_output_assert_ref(o);
+
+ if (!(c = o->userdata))
+ return -1;
+
+ switch (code) {
+
+ case SOURCE_OUTPUT_MESSAGE_POST_DATA:
+ /* While this function is usually called from IO thread
+ * context, this specific command is not! */
+ pa_memblockq_push_align(c->output_memblockq, chunk);
+ do_work(c);
+ break;
+
+ default:
+ return pa_source_output_process_msg(m, code, userdata, offset, chunk);
+ }
+
+ return 0;
+}
+
+/* Called from thread context */
+static void source_output_push_cb(pa_source_output *o, const pa_memchunk *chunk) {
+ struct connection *c;
+
+ pa_source_output_assert_ref(o);
+ pa_assert_se(c = o->userdata);
+ pa_assert(chunk);
+
+ pa_asyncmsgq_post(pa_thread_mq_get()->outq, PA_MSGOBJECT(o), SOURCE_OUTPUT_MESSAGE_POST_DATA, NULL, 0, chunk, NULL);
+}
+
+/* Called from main context */
+static void source_output_kill_cb(pa_source_output *o) {
+ struct connection*c;
+
+ pa_source_output_assert_ref(o);
+ pa_assert_se(c = o->userdata);
+
+ connection_unlink(c);
+}
+
+/* Called from main context */
+static pa_usec_t source_output_get_latency_cb(pa_source_output *o) {
+ struct connection*c;
+
+ pa_source_output_assert_ref(o);
+ pa_assert_se(c = o->userdata);
+
+ return pa_bytes_to_usec(pa_memblockq_get_length(c->output_memblockq), &c->source_output->sample_spec);
+}
+
+/*** client callbacks ***/
+static void client_kill_cb(pa_client *client) {
+ struct connection*c;
+
+ pa_assert(client);
+ pa_assert_se(c = client->userdata);
+
+ connection_unlink(c);
+}
+
+/*** pa_iochannel callbacks ***/
+static void io_callback(pa_iochannel*io, void *userdata) {
+ struct connection *c = userdata;
+
+ pa_assert(c);
+ pa_assert(io);
+
+ do_work(c);
+}
+
+static char *escape_html(const char *t) {
+ pa_strbuf *sb;
+ const char *p, *e;
+
+ sb = pa_strbuf_new();
+
+ for (e = p = t; *p; p++) {
+
+ if (*p == '>' || *p == '<' || *p == '&') {
+
+ if (p > e) {
+ pa_strbuf_putsn(sb, e, p-e);
+ e = p + 1;
+ }
+
+ if (*p == '>')
+ pa_strbuf_puts(sb, ">");
+ else if (*p == '<')
+ pa_strbuf_puts(sb, "<");
+ else
+ pa_strbuf_puts(sb, "&");
+ }
+ }
+
+ if (p > e)
+ pa_strbuf_putsn(sb, e, p-e);
+
+ return pa_strbuf_tostring_free(sb);
+}
+
+static void http_response(
+ struct connection *c,
+ int code,
+ const char *msg,
+ const char *mime) {
+
+ char *s;
pa_assert(c);
pa_assert(msg);
pa_assert(mime);
- pa_snprintf(s, sizeof(s),
- "HTTP/1.0 %i %s\n"
- "Connection: close\n"
- "Content-Type: %s\n"
- "Cache-Control: no-cache\n"
- "Expires: 0\n"
- "Server: "PACKAGE_NAME"/"PACKAGE_VERSION"\n"
- "\n", code, msg, mime);
-
+ s = pa_sprintf_malloc(
+ "HTTP/1.0 %i %s\n"
+ "Connection: close\n"
+ "Content-Type: %s\n"
+ "Cache-Control: no-cache\n"
+ "Expires: 0\n"
+ "Server: "PACKAGE_NAME"/"PACKAGE_VERSION"\n"
+ "\n", code, msg, mime);
pa_ioline_puts(c->line, s);
+ pa_xfree(s);
}
-static void http_message(struct connection *c, int code, const char *msg, const char *text) {
- char s[256];
+static void html_response(
+ struct connection *c,
+ int code,
+ const char *msg,
+ const char *text) {
+
+ char *s;
pa_assert(c);
- http_response(c, code, msg, "text/html");
+ http_response(c, code, msg, MIME_HTML);
+
+ if (c->method == METHOD_HEAD) {
+ pa_ioline_defer_close(c->line);
+ return;
+ }
if (!text)
text = msg;
- pa_snprintf(s, sizeof(s),
- "\n"
- "\n"
- "%s\n"
- "%s\n",
- text, text);
+ s = pa_sprintf_malloc(
+ HTML_HEADER("%s")
+ "%s"
+ HTML_FOOTER,
+ text, text);
pa_ioline_puts(c->line, s);
+ pa_xfree(s);
+
pa_ioline_defer_close(c->line);
}
+static void html_print_field(pa_ioline *line, const char *left, const char *right) {
+ char *eleft, *eright;
+
+ eleft = escape_html(left);
+ eright = escape_html(right);
+
+ pa_ioline_printf(line,
+ "%s | "
+ "%s |
\n", eleft, eright);
+
+ pa_xfree(eleft);
+ pa_xfree(eright);
+}
+
+static void handle_root(struct connection *c) {
+ char *t;
-static void connection_free(struct connection *c, int del) {
pa_assert(c);
- if (c->url)
- pa_xfree(c->url);
+ http_response(c, 200, "OK", MIME_HTML);
+
+ if (c->method == METHOD_HEAD) {
+ pa_ioline_defer_close(c->line);
+ return;
+ }
- if (del)
- pa_idxset_remove_by_data(c->protocol->connections, c, NULL);
+ pa_ioline_puts(c->line,
+ HTML_HEADER(PACKAGE_NAME" "PACKAGE_VERSION)
+ ""PACKAGE_NAME" "PACKAGE_VERSION"
\n"
+ "\n");
- pa_ioline_unref(c->line);
- pa_xfree(c);
+ t = pa_get_user_name_malloc();
+ html_print_field(c->line, "User Name:", t);
+ pa_xfree(t);
+
+ t = pa_get_host_name_malloc();
+ html_print_field(c->line, "Host name:", t);
+ pa_xfree(t);
+
+ t = pa_machine_id();
+ html_print_field(c->line, "Machine ID:", t);
+ pa_xfree(t);
+
+ t = pa_uname_string();
+ html_print_field(c->line, "System:", t);
+ pa_xfree(t);
+
+ t = pa_sprintf_malloc("%lu", (unsigned long) getpid());
+ html_print_field(c->line, "Process ID:", t);
+ pa_xfree(t);
+
+ pa_ioline_puts(c->line,
+ "
\n"
+ "Show an extensive server status report
\n"
+ "Monitor sinks and sources
\n"
+ HTML_FOOTER);
+
+ pa_ioline_defer_close(c->line);
}
-static void line_callback(pa_ioline *line, const char *s, void *userdata) {
- struct connection *c = userdata;
- pa_assert(line);
+static void handle_css(struct connection *c) {
pa_assert(c);
- if (!s) {
- /* EOF */
- connection_free(c, 1);
+ http_response(c, 200, "OK", MIME_CSS);
+
+ if (c->method == METHOD_HEAD) {
+ pa_ioline_defer_close(c->line);
return;
}
- switch (c->state) {
- case REQUEST_LINE: {
- if (memcmp(s, "GET ", 4))
- goto fail;
+ pa_ioline_puts(c->line,
+ "body { color: black; background-color: white; }\n"
+ "a:link, a:visited { color: #900000; }\n"
+ "div.news-date { font-size: 80%; font-style: italic; }\n"
+ "pre { background-color: #f0f0f0; padding: 0.4cm; }\n"
+ ".grey { color: #8f8f8f; font-size: 80%; }"
+ "table { margin-left: 1cm; border:1px solid lightgrey; padding: 0.2cm; }\n"
+ "td { padding-left:10px; padding-right:10px; }\n");
- s +=4;
+ pa_ioline_defer_close(c->line);
+}
- c->url = pa_xstrndup(s, strcspn(s, " \r\n\t?"));
- c->state = MIME_HEADER;
- break;
+static void handle_status(struct connection *c) {
+ char *r;
- }
+ pa_assert(c);
- case MIME_HEADER: {
+ http_response(c, 200, "OK", MIME_TEXT);
- /* Ignore MIME headers */
- if (strcspn(s, " \r\n") != 0)
- break;
+ if (c->method == METHOD_HEAD) {
+ pa_ioline_defer_close(c->line);
+ return;
+ }
- /* We're done */
- c->state = DATA;
+ r = pa_full_status_string(c->protocol->core);
+ pa_ioline_puts(c->line, r);
+ pa_xfree(r);
+
+ pa_ioline_defer_close(c->line);
+}
+
+static void handle_listen(struct connection *c) {
+ pa_source *source;
+ pa_sink *sink;
+ uint32_t idx;
- pa_log_info("request for %s", c->url);
+ http_response(c, 200, "OK", MIME_HTML);
- if (!strcmp(c->url, URL_ROOT)) {
- char txt[256];
- http_response(c, 200, "OK", "text/html");
+ pa_ioline_puts(c->line,
+ HTML_HEADER("Listen")
+ "Sinks
\n"
+ "\n");
- pa_ioline_puts(c->line,
- "\n"
- "\n"
- "
"PACKAGE_NAME" "PACKAGE_VERSION"\n"
- "\n");
+ if (c->method == METHOD_HEAD) {
+ pa_ioline_defer_close(c->line);
+ return;
+ }
+
+ PA_IDXSET_FOREACH(sink, c->protocol->core->sinks, idx) {
+ char *t, *m;
+
+ t = escape_html(pa_strna(pa_proplist_gets(sink->proplist, PA_PROP_DEVICE_DESCRIPTION)));
+ m = pa_sample_spec_to_mime_type_mimefy(&sink->sample_spec, &sink->channel_map);
+
+ pa_ioline_printf(c->line,
+ "%s
\n",
+ sink->monitor_source->name, m, t);
+
+ pa_xfree(t);
+ pa_xfree(m);
+ }
+
+ pa_ioline_puts(c->line,
+ "
\n"
+ "Sources
\n"
+ "\n");
- pa_ioline_puts(c->line,
- "
"PACKAGE_NAME" "PACKAGE_VERSION"
\n"
- "");
+ PA_IDXSET_FOREACH(source, c->protocol->core->sources, idx) {
+ char *t, *m;
-#define PRINTF_FIELD(a,b) pa_ioline_printf(c->line, "%s | %s |
\n",(a),(b))
+ if (source->monitor_of)
+ continue;
- PRINTF_FIELD("User Name:", pa_get_user_name(txt, sizeof(txt)));
- PRINTF_FIELD("Fully Qualified Domain Name:", pa_get_fqdn(txt, sizeof(txt)));
- PRINTF_FIELD("Default Sample Specification:", pa_sample_spec_snprint(txt, sizeof(txt), &c->protocol->core->default_sample_spec));
- PRINTF_FIELD("Default Sink:", pa_namereg_get_default_sink_name(c->protocol->core));
- PRINTF_FIELD("Default Source:", pa_namereg_get_default_source_name(c->protocol->core));
+ t = escape_html(pa_strna(pa_proplist_gets(source->proplist, PA_PROP_DEVICE_DESCRIPTION)));
+ m = pa_sample_spec_to_mime_type_mimefy(&source->sample_spec, &source->channel_map);
- pa_ioline_puts(c->line, "
");
+ pa_ioline_printf(c->line,
+ "%s
\n",
+ source->name, m, t);
- pa_ioline_puts(c->line, "Click here for an extensive server status report.
");
+ pa_xfree(m);
+ pa_xfree(t);
- pa_ioline_puts(c->line, "