From 0b2d96d6c0f586bb8436950080eda11daa170ba3 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Wed, 29 Apr 2009 02:00:19 +0200 Subject: protocol-http: substantial modernizations --- src/pulsecore/protocol-http.c | 497 +++++++++++++++++++++++++++++++++--------- 1 file changed, 394 insertions(+), 103 deletions(-) (limited to 'src/pulsecore/protocol-http.c') diff --git a/src/pulsecore/protocol-http.c b/src/pulsecore/protocol-http.c index f3b93819..08a70e50 100644 --- a/src/pulsecore/protocol-http.c +++ b/src/pulsecore/protocol-http.c @@ -42,20 +42,39 @@ /* 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_PREFIX "/listen/" + +#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" +enum state { + STATE_REQUEST_LINE, + STATE_MIME_HEADER, + STATE_DATA +}; struct connection { pa_http_protocol *protocol; pa_ioline *line; - enum { - REQUEST_LINE, - MIME_HEADER, - DATA - } state; + enum state state; char *url; pa_module *module; }; @@ -67,45 +86,242 @@ struct pa_http_protocol { pa_idxset *connections; }; -static void http_response(struct connection *c, int code, const char *msg, const char *mime) { - char s[256]; + +static pa_bool_t is_mime_sample_spec(const pa_sample_spec *ss, const pa_channel_map *cm) { + + pa_assert(pa_channel_map_compatible(cm, ss)); + + switch (ss->format) { + case PA_SAMPLE_S16BE: + case PA_SAMPLE_S24BE: + case PA_SAMPLE_U8: + + if (ss->rate != 8000 && + ss->rate != 11025 && + ss->rate != 16000 && + ss->rate != 22050 && + ss->rate != 24000 && + ss->rate != 32000 && + ss->rate != 44100 && + ss->rate != 48000) + return FALSE; + + if (ss->channels != 1 && + ss->channels != 2) + return FALSE; + + if ((cm->channels == 1 && cm->map[0] != PA_CHANNEL_POSITION_MONO) || + (cm->channels == 2 && (cm->map[0] != PA_CHANNEL_POSITION_LEFT || cm->map[1] != PA_CHANNEL_POSITION_RIGHT))) + return FALSE; + + return TRUE; + + case PA_SAMPLE_ULAW: + + if (ss->rate != 8000) + return FALSE; + + if (ss->channels != 1) + return FALSE; + + if (cm->map[0] != PA_CHANNEL_POSITION_MONO) + return FALSE; + + return TRUE; + + default: + return FALSE; + } +} + +static void mimefy_sample_spec(pa_sample_spec *ss, pa_channel_map *cm) { + + pa_assert(pa_channel_map_compatible(cm, ss)); + + /* Turns the sample type passed in into the next 'better' one that + * can be encoded for HTTP. If there is no 'better' one we pick + * the 'best' one that is 'worse'. */ + + if (ss->channels > 2) + ss->channels = 2; + + if (ss->rate > 44100) + ss->rate = 48000; + else if (ss->rate > 32000) + ss->rate = 44100; + else if (ss->rate > 24000) + ss->rate = 32000; + else if (ss->rate > 22050) + ss->rate = 24000; + else if (ss->rate > 16000) + ss->rate = 22050; + else if (ss->rate > 11025) + ss->rate = 16000; + else if (ss->rate > 8000) + ss->rate = 11025; + else + ss->rate = 8000; + + switch (ss->format) { + case PA_SAMPLE_S24BE: + case PA_SAMPLE_S24LE: + case PA_SAMPLE_S24_32LE: + case PA_SAMPLE_S24_32BE: + case PA_SAMPLE_S32LE: + case PA_SAMPLE_S32BE: + case PA_SAMPLE_FLOAT32LE: + case PA_SAMPLE_FLOAT32BE: + ss->format = PA_SAMPLE_S24BE; + break; + + case PA_SAMPLE_S16BE: + case PA_SAMPLE_S16LE: + ss->format = PA_SAMPLE_S16BE; + break; + + case PA_SAMPLE_ULAW: + case PA_SAMPLE_ALAW: + + if (ss->rate == 8000 && ss->channels == 1) + ss->format = PA_SAMPLE_ULAW; + else + ss->format = PA_SAMPLE_S16BE; + break; + + case PA_SAMPLE_U8: + ss->format = PA_SAMPLE_U8; + break; + + case PA_SAMPLE_MAX: + case PA_SAMPLE_INVALID: + pa_assert_not_reached(); + } + + pa_channel_map_init_auto(cm, ss->channels, PA_CHANNEL_MAP_DEFAULT); + + pa_assert(is_mime_sample_spec(ss, cm)); +} + +static char *sample_spec_to_mime_type(const pa_sample_spec *ss, const pa_channel_map *cm) { + pa_assert(pa_channel_map_compatible(cm, ss)); + + if (!is_mime_sample_spec(ss, cm)) + return NULL; + + switch (ss->format) { + + case PA_SAMPLE_S16BE: + case PA_SAMPLE_S24BE: + case PA_SAMPLE_U8: + return pa_sprintf_malloc("audio/%s; rate=%u; channels=%u", + ss->format == PA_SAMPLE_S16BE ? "L16" : + (ss->format == PA_SAMPLE_S24BE ? "L24" : "L8"), + ss->rate, ss->channels); + + case PA_SAMPLE_ULAW: + return pa_xstrdup("audio/basic"); + + default: + pa_assert_not_reached(); + } + + pa_assert(pa_sample_spec_valid(ss)); +} + +static char *mimefy_and_stringify_sample_spec(const pa_sample_spec *_ss, const pa_channel_map *_cm) { + pa_sample_spec ss = *_ss; + pa_channel_map cm = *_cm; + + mimefy_sample_spec(&ss, &cm); + + return sample_spec_to_mime_type(&ss, &cm); +} + +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 (!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 internal_server_error(struct connection *c) { + pa_assert(c); + + html_response(c, 500, "Internal Server Error", NULL); +} static void connection_unlink(struct connection *c) { pa_assert(c); @@ -113,12 +329,153 @@ static void connection_unlink(struct connection *c) { if (c->url) pa_xfree(c->url); + if (c->line) + pa_ioline_unref(c->line); + pa_idxset_remove_by_data(c->protocol->connections, c, NULL); - pa_ioline_unref(c->line); pa_xfree(c); } +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_url(struct connection *c) { + pa_assert(c); + + pa_log_debug("Request for %s", c->url); + + if (pa_streq(c->url, URL_ROOT)) { + char *t; + + http_response(c, 200, "OK", MIME_HTML); + + pa_ioline_puts(c->line, + HTML_HEADER(PACKAGE_NAME" "PACKAGE_VERSION) + "

"PACKAGE_NAME" "PACKAGE_VERSION"

\n" + "\n"); + + 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); + + } else if (pa_streq(c->url, URL_CSS)) { + http_response(c, 200, "OK", MIME_CSS); + + 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"); + + pa_ioline_defer_close(c->line); + + } else if (pa_streq(c->url, URL_STATUS)) { + char *r; + + http_response(c, 200, "OK", MIME_TEXT); + r = pa_full_status_string(c->protocol->core); + pa_ioline_puts(c->line, r); + pa_xfree(r); + + pa_ioline_defer_close(c->line); + + } else if (pa_streq(c->url, URL_LISTEN)) { + pa_source *source; + pa_sink *sink; + uint32_t idx; + + http_response(c, 200, "OK", MIME_HTML); + + pa_ioline_puts(c->line, + HTML_HEADER("Listen") + "

Sinks

\n" + "

\n"); + + 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 = mimefy_and_stringify_sample_spec(&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_IDXSET_FOREACH(source, c->protocol->core->sources, idx) { + char *t, *m; + + if (source->monitor_of) + continue; + + t = escape_html(pa_strna(pa_proplist_gets(source->proplist, PA_PROP_DEVICE_DESCRIPTION))); + m = mimefy_and_stringify_sample_spec(&source->sample_spec, &source->channel_map); + + pa_ioline_printf(c->line, + "%s
\n", + source->name, m, t); + + pa_xfree(m); + pa_xfree(t); + + } + + pa_ioline_puts(c->line, + "

\n" + HTML_FOOTER); + + pa_ioline_defer_close(c->line); + } else + html_response(c, 404, "Not Found", NULL); +} + static void line_callback(pa_ioline *line, const char *s, void *userdata) { struct connection *c = userdata; pa_assert(line); @@ -131,93 +488,27 @@ static void line_callback(pa_ioline *line, const char *s, void *userdata) { } switch (c->state) { - case REQUEST_LINE: { - if (memcmp(s, "GET ", 4)) + case STATE_REQUEST_LINE: { + if (!pa_startswith(s, "GET ")) goto fail; s +=4; c->url = pa_xstrndup(s, strcspn(s, " \r\n\t?")); - c->state = MIME_HEADER; + c->state = STATE_MIME_HEADER; break; - } - case MIME_HEADER: { + case STATE_MIME_HEADER: { /* Ignore MIME headers */ if (strcspn(s, " \r\n") != 0) break; /* We're done */ - c->state = DATA; - - pa_log_info("request for %s", c->url); - - if (!strcmp(c->url, URL_ROOT)) { - char txt[256]; - pa_sink *def_sink; - pa_source *def_source; - http_response(c, 200, "OK", "text/html"); - - pa_ioline_puts(c->line, - "\n" - "\n" - ""PACKAGE_NAME" "PACKAGE_VERSION"\n" - "\n"); - - pa_ioline_puts(c->line, - "

"PACKAGE_NAME" "PACKAGE_VERSION"

\n" - ""); - -#define PRINTF_FIELD(a,b) pa_ioline_printf(c->line, "\n",(a),(b)) - - PRINTF_FIELD("User Name:", pa_get_user_name(txt, sizeof(txt))); - PRINTF_FIELD("Host name:", pa_get_host_name(txt, sizeof(txt))); - PRINTF_FIELD("Default Sample Specification:", pa_sample_spec_snprint(txt, sizeof(txt), &c->protocol->core->default_sample_spec)); - - def_sink = pa_namereg_get_default_sink(c->protocol->core); - def_source = pa_namereg_get_default_source(c->protocol->core); - - PRINTF_FIELD("Default Sink:", def_sink ? def_sink->name : "n/a"); - PRINTF_FIELD("Default Source:", def_source ? def_source->name : "n/a"); - - pa_ioline_puts(c->line, "
%s%s
"); - - pa_ioline_puts(c->line, "

Click here for an extensive server status report.

"); - - pa_ioline_puts(c->line, "\n"); - - pa_ioline_defer_close(c->line); - } else if (!strcmp(c->url, URL_CSS)) { - http_response(c, 200, "OK", "text/css"); - - pa_ioline_puts(c->line, - "body { color: black; background-color: white; margin: 0.5cm; }\n" - "a:link, a:visited { color: #900000; }\n" - "p { margin-left: 0.5cm; margin-right: 0.5cm; }\n" - "h1 { color: #00009F; }\n" - "h2 { color: #00009F; }\n" - "ul { margin-left: .5cm; }\n" - "ol { margin-left: .5cm; }\n" - "pre { margin-left: .5cm; background-color: #f0f0f0; padding: 0.4cm;}\n" - ".grey { color: #afafaf; }\n" - "table { margin-left: 1cm; border:1px solid lightgrey; padding: 0.2cm; }\n" - "td { padding-left:10px; padding-right:10px; }\n"); - - pa_ioline_defer_close(c->line); - } else if (!strcmp(c->url, URL_STATUS)) { - char *r; - - http_response(c, 200, "OK", "text/plain"); - r = pa_full_status_string(c->protocol->core); - pa_ioline_puts(c->line, r); - pa_xfree(r); - - pa_ioline_defer_close(c->line); - } else - http_message(c, 404, "Not Found", NULL); + c->state = STATE_DATA; + handle_url(c); break; } @@ -247,7 +538,7 @@ void pa_http_protocol_connect(pa_http_protocol *p, pa_iochannel *io, pa_module * c = pa_xnew(struct connection, 1); c->protocol = p; c->line = pa_ioline_new(io); - c->state = REQUEST_LINE; + c->state = STATE_REQUEST_LINE; c->url = NULL; c->module = m; @@ -258,12 +549,12 @@ void pa_http_protocol_connect(pa_http_protocol *p, pa_iochannel *io, pa_module * void pa_http_protocol_disconnect(pa_http_protocol *p, pa_module *m) { struct connection *c; - void *state = NULL; + uint32_t idx; pa_assert(p); pa_assert(m); - while ((c = pa_idxset_iterate(p->connections, &state, NULL))) + PA_IDXSET_FOREACH(c, p->connections, idx) if (c->module == m) connection_unlink(c); } -- cgit