mirror of
				https://gitlab.freedesktop.org/pulseaudio/pulseaudio.git
				synced 2025-11-03 09:01:50 -05:00 
			
		
		
		
	protocol-http: allow listening into sinks/sources via HTTP
This commit is contained in:
		
							parent
							
								
									c2150118bf
								
							
						
					
					
						commit
						84a92f2a88
					
				
					 1 changed files with 426 additions and 143 deletions
				
			
		| 
						 | 
					@ -1,7 +1,7 @@
 | 
				
			||||||
/***
 | 
					/***
 | 
				
			||||||
  This file is part of PulseAudio.
 | 
					  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
 | 
					  PulseAudio is free software; you can redistribute it and/or modify
 | 
				
			||||||
  it under the terms of the GNU Lesser General Public License as published
 | 
					  it under the terms of the GNU Lesser General Public License as published
 | 
				
			||||||
| 
						 | 
					@ -26,16 +26,20 @@
 | 
				
			||||||
#include <stdlib.h>
 | 
					#include <stdlib.h>
 | 
				
			||||||
#include <stdio.h>
 | 
					#include <stdio.h>
 | 
				
			||||||
#include <string.h>
 | 
					#include <string.h>
 | 
				
			||||||
 | 
					#include <errno.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#include <pulse/util.h>
 | 
					#include <pulse/util.h>
 | 
				
			||||||
#include <pulse/xmalloc.h>
 | 
					#include <pulse/xmalloc.h>
 | 
				
			||||||
 | 
					#include <pulse/timeval.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#include <pulsecore/ioline.h>
 | 
					#include <pulsecore/ioline.h>
 | 
				
			||||||
 | 
					#include <pulsecore/thread-mq.h>
 | 
				
			||||||
#include <pulsecore/macro.h>
 | 
					#include <pulsecore/macro.h>
 | 
				
			||||||
#include <pulsecore/log.h>
 | 
					#include <pulsecore/log.h>
 | 
				
			||||||
#include <pulsecore/namereg.h>
 | 
					#include <pulsecore/namereg.h>
 | 
				
			||||||
#include <pulsecore/cli-text.h>
 | 
					#include <pulsecore/cli-text.h>
 | 
				
			||||||
#include <pulsecore/shared.h>
 | 
					#include <pulsecore/shared.h>
 | 
				
			||||||
 | 
					#include <pulsecore/core-error.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#include "protocol-http.h"
 | 
					#include "protocol-http.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -46,7 +50,7 @@
 | 
				
			||||||
#define URL_CSS "/style"
 | 
					#define URL_CSS "/style"
 | 
				
			||||||
#define URL_STATUS "/status"
 | 
					#define URL_STATUS "/status"
 | 
				
			||||||
#define URL_LISTEN "/listen"
 | 
					#define URL_LISTEN "/listen"
 | 
				
			||||||
#define URL_LISTEN_PREFIX "/listen/"
 | 
					#define URL_LISTEN_SOURCE "/listen/source/"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#define MIME_HTML "text/html; charset=utf-8"
 | 
					#define MIME_HTML "text/html; charset=utf-8"
 | 
				
			||||||
#define MIME_TEXT "text/plain; charset=utf-8"
 | 
					#define MIME_TEXT "text/plain; charset=utf-8"
 | 
				
			||||||
| 
						 | 
					@ -65,6 +69,10 @@
 | 
				
			||||||
#define HTML_FOOTER                                                     \
 | 
					#define HTML_FOOTER                                                     \
 | 
				
			||||||
    "        </body>\n"                                                 \
 | 
					    "        </body>\n"                                                 \
 | 
				
			||||||
    "</html>\n"
 | 
					    "</html>\n"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#define RECORD_BUFFER_SECONDS (5)
 | 
				
			||||||
 | 
					#define DEFAULT_SOURCE_LATENCY (300*PA_USEC_PER_MSEC)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
enum state {
 | 
					enum state {
 | 
				
			||||||
    STATE_REQUEST_LINE,
 | 
					    STATE_REQUEST_LINE,
 | 
				
			||||||
    STATE_MIME_HEADER,
 | 
					    STATE_MIME_HEADER,
 | 
				
			||||||
| 
						 | 
					@ -73,7 +81,11 @@ enum state {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
struct connection {
 | 
					struct connection {
 | 
				
			||||||
    pa_http_protocol *protocol;
 | 
					    pa_http_protocol *protocol;
 | 
				
			||||||
 | 
					    pa_iochannel *io;
 | 
				
			||||||
    pa_ioline *line;
 | 
					    pa_ioline *line;
 | 
				
			||||||
 | 
					    pa_memblockq *output_memblockq;
 | 
				
			||||||
 | 
					    pa_source_output *source_output;
 | 
				
			||||||
 | 
					    pa_client *client;
 | 
				
			||||||
    enum state state;
 | 
					    enum state state;
 | 
				
			||||||
    char *url;
 | 
					    char *url;
 | 
				
			||||||
    pa_module *module;
 | 
					    pa_module *module;
 | 
				
			||||||
| 
						 | 
					@ -86,6 +98,163 @@ struct pa_http_protocol {
 | 
				
			||||||
    pa_idxset *connections;
 | 
					    pa_idxset *connections;
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					enum {
 | 
				
			||||||
 | 
					    SOURCE_OUTPUT_MESSAGE_POST_DATA = PA_SOURCE_OUTPUT_MESSAGE_MAX
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* 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);
 | 
				
			||||||
 | 
					        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);
 | 
				
			||||||
 | 
					    pa_assert_se(c = o->userdata);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    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 pa_bool_t is_mime_sample_spec(const pa_sample_spec *ss, const pa_channel_map *cm) {
 | 
					static pa_bool_t is_mime_sample_spec(const pa_sample_spec *ss, const pa_channel_map *cm) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -317,26 +486,6 @@ static void html_response(
 | 
				
			||||||
    pa_ioline_defer_close(c->line);
 | 
					    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);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    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_xfree(c);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
static void html_print_field(pa_ioline *line, const char *left, const char *right) {
 | 
					static void html_print_field(pa_ioline *line, const char *left, const char *right) {
 | 
				
			||||||
    char *eleft, *eright;
 | 
					    char *eleft, *eright;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -351,128 +500,240 @@ static void html_print_field(pa_ioline *line, const char *left, const char *righ
 | 
				
			||||||
    pa_xfree(eright);
 | 
					    pa_xfree(eright);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static void handle_root(struct connection *c) {
 | 
				
			||||||
 | 
					    char *t;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pa_assert(c);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    http_response(c, 200, "OK", MIME_HTML);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pa_ioline_puts(c->line,
 | 
				
			||||||
 | 
					                   HTML_HEADER(PACKAGE_NAME" "PACKAGE_VERSION)
 | 
				
			||||||
 | 
					                   "<h1>"PACKAGE_NAME" "PACKAGE_VERSION"</h1>\n"
 | 
				
			||||||
 | 
					                   "<table>\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,
 | 
				
			||||||
 | 
					                   "</table>\n"
 | 
				
			||||||
 | 
					                   "<p><a href=\"" URL_STATUS "\">Show an extensive server status report</a></p>\n"
 | 
				
			||||||
 | 
					                   "<p><a href=\"" URL_LISTEN "\">Monitor sinks and sources</a></p>\n"
 | 
				
			||||||
 | 
					                   HTML_FOOTER);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pa_ioline_defer_close(c->line);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static void handle_css(struct connection *c) {
 | 
				
			||||||
 | 
					    pa_assert(c);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    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);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static void handle_status(struct connection *c) {
 | 
				
			||||||
 | 
					    char *r;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pa_assert(c);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    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);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static void handle_listen(struct connection *c) {
 | 
				
			||||||
 | 
					    pa_source *source;
 | 
				
			||||||
 | 
					    pa_sink *sink;
 | 
				
			||||||
 | 
					    uint32_t idx;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    http_response(c, 200, "OK", MIME_HTML);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pa_ioline_puts(c->line,
 | 
				
			||||||
 | 
					                   HTML_HEADER("Listen")
 | 
				
			||||||
 | 
					                   "<h2>Sinks</h2>\n"
 | 
				
			||||||
 | 
					                   "<p>\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,
 | 
				
			||||||
 | 
					                         "<a href=\"" URL_LISTEN_SOURCE "%s\" title=\"%s\">%s</a><br/>\n",
 | 
				
			||||||
 | 
					                         sink->monitor_source->name, m, t);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        pa_xfree(t);
 | 
				
			||||||
 | 
					        pa_xfree(m);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pa_ioline_puts(c->line,
 | 
				
			||||||
 | 
					                   "</p>\n"
 | 
				
			||||||
 | 
					                   "<h2>Sources</h2>\n"
 | 
				
			||||||
 | 
					                   "<p>\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,
 | 
				
			||||||
 | 
					                         "<a href=\"" URL_LISTEN_SOURCE "%s\" title=\"%s\">%s</a><br/>\n",
 | 
				
			||||||
 | 
					                         source->name, m, t);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        pa_xfree(m);
 | 
				
			||||||
 | 
					        pa_xfree(t);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pa_ioline_puts(c->line,
 | 
				
			||||||
 | 
					                   "</p>\n"
 | 
				
			||||||
 | 
					                   HTML_FOOTER);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pa_ioline_defer_close(c->line);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static void line_drain_callback(pa_ioline *l, void *userdata) {
 | 
				
			||||||
 | 
					    struct connection *c;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pa_assert(l);
 | 
				
			||||||
 | 
					    pa_assert_se(c = userdata);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /* We don't need the line reader anymore, instead we need a real
 | 
				
			||||||
 | 
					     * binary io channel */
 | 
				
			||||||
 | 
					    pa_assert_se(c->io = pa_ioline_detach_iochannel(c->line));
 | 
				
			||||||
 | 
					    pa_iochannel_set_callback(c->io, io_callback, c);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pa_iochannel_socket_set_sndbuf(c->io, pa_memblockq_get_length(c->output_memblockq));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pa_ioline_unref(c->line);
 | 
				
			||||||
 | 
					    c->line = NULL;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static void handle_listen_prefix(struct connection *c, const char *source_name) {
 | 
				
			||||||
 | 
					    pa_source *source;
 | 
				
			||||||
 | 
					    pa_source_output_new_data data;
 | 
				
			||||||
 | 
					    pa_sample_spec ss;
 | 
				
			||||||
 | 
					    pa_channel_map cm;
 | 
				
			||||||
 | 
					    char *t;
 | 
				
			||||||
 | 
					    size_t l;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pa_assert(c);
 | 
				
			||||||
 | 
					    pa_assert(source_name);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pa_assert(c->line);
 | 
				
			||||||
 | 
					    pa_assert(!c->io);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (!(source = pa_namereg_get(c->protocol->core, source_name, PA_NAMEREG_SOURCE))) {
 | 
				
			||||||
 | 
					        html_response(c, 404, "Source not found", NULL);
 | 
				
			||||||
 | 
					        return;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    ss = source->sample_spec;
 | 
				
			||||||
 | 
					    cm = source->channel_map;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    mimefy_sample_spec(&ss, &cm);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pa_source_output_new_data_init(&data);
 | 
				
			||||||
 | 
					    data.driver = __FILE__;
 | 
				
			||||||
 | 
					    data.module = c->module;
 | 
				
			||||||
 | 
					    data.client = c->client;
 | 
				
			||||||
 | 
					    data.source = source;
 | 
				
			||||||
 | 
					    pa_proplist_update(data.proplist, PA_UPDATE_MERGE, c->client->proplist);
 | 
				
			||||||
 | 
					    pa_source_output_new_data_set_sample_spec(&data, &ss);
 | 
				
			||||||
 | 
					    pa_source_output_new_data_set_channel_map(&data, &cm);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pa_source_output_new(&c->source_output, c->protocol->core, &data, 0);
 | 
				
			||||||
 | 
					    pa_source_output_new_data_done(&data);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (!c->source_output) {
 | 
				
			||||||
 | 
					        html_response(c, 403, "Cannot create source output", NULL);
 | 
				
			||||||
 | 
					        return;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    c->source_output->parent.process_msg = source_output_process_msg;
 | 
				
			||||||
 | 
					    c->source_output->push = source_output_push_cb;
 | 
				
			||||||
 | 
					    c->source_output->kill = source_output_kill_cb;
 | 
				
			||||||
 | 
					    c->source_output->get_latency = source_output_get_latency_cb;
 | 
				
			||||||
 | 
					    c->source_output->userdata = c;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pa_source_output_set_requested_latency(c->source_output, DEFAULT_SOURCE_LATENCY);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    l = (size_t) (pa_bytes_per_second(&ss)*RECORD_BUFFER_SECONDS);
 | 
				
			||||||
 | 
					    c->output_memblockq = pa_memblockq_new(
 | 
				
			||||||
 | 
					            0,
 | 
				
			||||||
 | 
					            l,
 | 
				
			||||||
 | 
					            0,
 | 
				
			||||||
 | 
					            pa_frame_size(&ss),
 | 
				
			||||||
 | 
					            1,
 | 
				
			||||||
 | 
					            0,
 | 
				
			||||||
 | 
					            0,
 | 
				
			||||||
 | 
					            NULL);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pa_source_output_put(c->source_output);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    t = sample_spec_to_mime_type(&ss, &cm);
 | 
				
			||||||
 | 
					    http_response(c, 200, "OK", t);
 | 
				
			||||||
 | 
					    pa_xfree(t);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pa_ioline_set_callback(c->line, NULL, NULL);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (pa_ioline_is_drained(c->line))
 | 
				
			||||||
 | 
					        line_drain_callback(c->line, c);
 | 
				
			||||||
 | 
					    else
 | 
				
			||||||
 | 
					        pa_ioline_set_drain_callback(c->line, line_drain_callback, c);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
static void handle_url(struct connection *c) {
 | 
					static void handle_url(struct connection *c) {
 | 
				
			||||||
    pa_assert(c);
 | 
					    pa_assert(c);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    pa_log_debug("Request for %s", c->url);
 | 
					    pa_log_debug("Request for %s", c->url);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (pa_streq(c->url, URL_ROOT)) {
 | 
					    if (pa_streq(c->url, URL_ROOT))
 | 
				
			||||||
        char *t;
 | 
					        handle_root(c);
 | 
				
			||||||
 | 
					    else if (pa_streq(c->url, URL_CSS))
 | 
				
			||||||
        http_response(c, 200, "OK", MIME_HTML);
 | 
					        handle_css(c);
 | 
				
			||||||
 | 
					    else if (pa_streq(c->url, URL_STATUS))
 | 
				
			||||||
        pa_ioline_puts(c->line,
 | 
					        handle_status(c);
 | 
				
			||||||
                       HTML_HEADER(PACKAGE_NAME" "PACKAGE_VERSION)
 | 
					    else if (pa_streq(c->url, URL_LISTEN))
 | 
				
			||||||
                       "<h1>"PACKAGE_NAME" "PACKAGE_VERSION"</h1>\n"
 | 
					        handle_listen(c);
 | 
				
			||||||
                       "<table>\n");
 | 
					    else if (pa_startswith(c->url, URL_LISTEN_SOURCE))
 | 
				
			||||||
 | 
					        handle_listen_prefix(c, c->url + sizeof(URL_LISTEN_SOURCE)-1);
 | 
				
			||||||
        t = pa_get_user_name_malloc();
 | 
					    else
 | 
				
			||||||
        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,
 | 
					 | 
				
			||||||
                       "</table>\n"
 | 
					 | 
				
			||||||
                       "<p><a href=\"/status\">Show an extensive server status report</a></p>\n"
 | 
					 | 
				
			||||||
                       "<p><a href=\"/listen\">Monitor sinks and sources</a></p>\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")
 | 
					 | 
				
			||||||
                       "<h2>Sinks</h2>\n"
 | 
					 | 
				
			||||||
                       "<p>\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,
 | 
					 | 
				
			||||||
                             "<a href=\"/listen/%s\" title=\"%s\">%s</a><br/>\n",
 | 
					 | 
				
			||||||
                             sink->monitor_source->name, m, t);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            pa_xfree(t);
 | 
					 | 
				
			||||||
            pa_xfree(m);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        pa_ioline_puts(c->line,
 | 
					 | 
				
			||||||
                       "</p>\n"
 | 
					 | 
				
			||||||
                       "<h2>Sources</h2>\n"
 | 
					 | 
				
			||||||
                       "<p>\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,
 | 
					 | 
				
			||||||
                             "<a href=\"/listen/%s\" title=\"%s\">%s</a><br/>\n",
 | 
					 | 
				
			||||||
                             source->name, m, t);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            pa_xfree(m);
 | 
					 | 
				
			||||||
            pa_xfree(t);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        pa_ioline_puts(c->line,
 | 
					 | 
				
			||||||
                       "</p>\n"
 | 
					 | 
				
			||||||
                       HTML_FOOTER);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        pa_ioline_defer_close(c->line);
 | 
					 | 
				
			||||||
    } else
 | 
					 | 
				
			||||||
        html_response(c, 404, "Not Found", NULL);
 | 
					        html_response(c, 404, "Not Found", NULL);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -519,11 +780,13 @@ static void line_callback(pa_ioline *line, const char *s, void *userdata) {
 | 
				
			||||||
    return;
 | 
					    return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
fail:
 | 
					fail:
 | 
				
			||||||
    internal_server_error(c);
 | 
					    html_response(c, 500, "Internal Server Error", NULL);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void pa_http_protocol_connect(pa_http_protocol *p, pa_iochannel *io, pa_module *m) {
 | 
					void pa_http_protocol_connect(pa_http_protocol *p, pa_iochannel *io, pa_module *m) {
 | 
				
			||||||
    struct connection *c;
 | 
					    struct connection *c;
 | 
				
			||||||
 | 
					    pa_client_new_data client_data;
 | 
				
			||||||
 | 
					    char pname[128];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    pa_assert(p);
 | 
					    pa_assert(p);
 | 
				
			||||||
    pa_assert(io);
 | 
					    pa_assert(io);
 | 
				
			||||||
| 
						 | 
					@ -535,16 +798,36 @@ void pa_http_protocol_connect(pa_http_protocol *p, pa_iochannel *io, pa_module *
 | 
				
			||||||
        return;
 | 
					        return;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    c = pa_xnew(struct connection, 1);
 | 
					    c = pa_xnew0(struct connection, 1);
 | 
				
			||||||
    c->protocol = p;
 | 
					    c->protocol = p;
 | 
				
			||||||
    c->line = pa_ioline_new(io);
 | 
					 | 
				
			||||||
    c->state = STATE_REQUEST_LINE;
 | 
					    c->state = STATE_REQUEST_LINE;
 | 
				
			||||||
    c->url = NULL;
 | 
					 | 
				
			||||||
    c->module = m;
 | 
					    c->module = m;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    c->line = pa_ioline_new(io);
 | 
				
			||||||
    pa_ioline_set_callback(c->line, line_callback, c);
 | 
					    pa_ioline_set_callback(c->line, line_callback, c);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pa_client_new_data_init(&client_data);
 | 
				
			||||||
 | 
					    client_data.module = c->module;
 | 
				
			||||||
 | 
					    client_data.driver = __FILE__;
 | 
				
			||||||
 | 
					    pa_iochannel_socket_peer_to_string(io, pname, sizeof(pname));
 | 
				
			||||||
 | 
					    pa_proplist_setf(client_data.proplist, PA_PROP_APPLICATION_NAME, "HTTP client (%s)", pname);
 | 
				
			||||||
 | 
					    pa_proplist_sets(client_data.proplist, "http-protocol.peer", pname);
 | 
				
			||||||
 | 
					    c->client = pa_client_new(p->core, &client_data);
 | 
				
			||||||
 | 
					    pa_client_new_data_done(&client_data);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (!c->client)
 | 
				
			||||||
 | 
					        goto fail;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    c->client->kill = client_kill_cb;
 | 
				
			||||||
 | 
					    c->client->userdata = c;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    pa_idxset_put(p->connections, c, NULL);
 | 
					    pa_idxset_put(p->connections, c, NULL);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					fail:
 | 
				
			||||||
 | 
					    if (c)
 | 
				
			||||||
 | 
					        connection_unlink(c);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void pa_http_protocol_disconnect(pa_http_protocol *p, pa_module *m) {
 | 
					void pa_http_protocol_disconnect(pa_http_protocol *p, pa_module *m) {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue