aboutsummaryrefslogtreecommitdiffstats
path: root/src/output/httpd_output_plugin.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/output/httpd_output_plugin.c')
-rw-r--r--src/output/httpd_output_plugin.c239
1 files changed, 178 insertions, 61 deletions
diff --git a/src/output/httpd_output_plugin.c b/src/output/httpd_output_plugin.c
index 026e8d9d8..6650d89e3 100644
--- a/src/output/httpd_output_plugin.c
+++ b/src/output/httpd_output_plugin.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * Copyright (C) 2003-2010 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -17,6 +17,7 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
+#include "config.h"
#include "httpd_internal.h"
#include "httpd_client.h"
#include "output_api.h"
@@ -25,15 +26,20 @@
#include "socket_util.h"
#include "page.h"
#include "icy_server.h"
+#include "fd_util.h"
+#include "server_socket.h"
#include <assert.h>
#include <sys/types.h>
-#include <netinet/in.h>
-#include <netdb.h>
#include <unistd.h>
#include <errno.h>
+#ifdef HAVE_LIBWRAP
+#include <sys/socket.h> /* needed for AF_UNIX */
+#include <tcpd.h>
+#endif
+
#undef G_LOG_DOMAIN
#define G_LOG_DOMAIN "httpd_output"
@@ -46,18 +52,49 @@ httpd_output_quark(void)
return g_quark_from_static_string("httpd_output");
}
+static void
+httpd_listen_in_event(int fd, const struct sockaddr *address,
+ size_t address_length, int uid, void *ctx);
+
+static bool
+httpd_output_bind(struct httpd_output *httpd, GError **error_r)
+{
+ httpd->open = false;
+
+ g_mutex_lock(httpd->mutex);
+ bool success = server_socket_open(httpd->server_socket, error_r);
+ g_mutex_unlock(httpd->mutex);
+
+ return success;
+}
+
+static void
+httpd_output_unbind(struct httpd_output *httpd)
+{
+ assert(!httpd->open);
+
+ g_mutex_lock(httpd->mutex);
+ server_socket_close(httpd->server_socket);
+ g_mutex_unlock(httpd->mutex);
+}
+
static void *
httpd_output_init(G_GNUC_UNUSED const struct audio_format *audio_format,
const struct config_param *param,
GError **error)
{
struct httpd_output *httpd = g_new(struct httpd_output, 1);
- const char *encoder_name;
+ const char *encoder_name, *bind_to_address;
const struct encoder_plugin *encoder_plugin;
guint port;
- struct sockaddr_in *sin;
/* read configuration */
+ httpd->name =
+ config_get_block_string(param, "name", "Set name in config");
+ httpd->genre =
+ config_get_block_string(param, "genre", "Set genre in config");
+ httpd->website =
+ config_get_block_string(param, "website", "Set website in config");
port = config_get_block_unsigned(param, "port", 8000);
@@ -69,24 +106,25 @@ httpd_output_init(G_GNUC_UNUSED const struct audio_format *audio_format,
return NULL;
}
- if (strcmp(encoder_name, "vorbis") == 0)
- httpd->content_type = "audio/ogg";
- else if (strcmp(encoder_name, "lame") == 0)
- httpd->content_type = "audio/mpeg";
- else
- httpd->content_type = "application/octet-stream";
+ httpd->clients_max = config_get_block_unsigned(param,"max_clients", 0);
+
+ /* set up bind_to_address */
- /* initialize listen address */
+ httpd->server_socket = server_socket_new(httpd_listen_in_event, httpd);
- sin = (struct sockaddr_in *)&httpd->address;
- memset(sin, 0, sizeof(sin));
- sin->sin_port = htons(port);
- sin->sin_family = AF_INET;
- sin->sin_addr.s_addr = INADDR_ANY;
- httpd->address_size = sizeof(*sin);
+ bind_to_address =
+ config_get_block_string(param, "bind_to_address", NULL);
+ bool success = bind_to_address != NULL &&
+ strcmp(bind_to_address, "any") != 0
+ ? server_socket_add_host(httpd->server_socket, bind_to_address,
+ port, error)
+ : server_socket_add_port(httpd->server_socket, port, error);
+ if (!success)
+ return NULL;
/* initialize metadata */
httpd->metadata = NULL;
+ httpd->unflushed_input = 0;
/* initialize encoder */
@@ -94,6 +132,12 @@ httpd_output_init(G_GNUC_UNUSED const struct audio_format *audio_format,
if (httpd->encoder == NULL)
return NULL;
+ /* determine content type */
+ httpd->content_type = encoder_get_mime_type(httpd->encoder);
+ if (httpd->content_type == NULL) {
+ httpd->content_type = "application/octet-stream";
+ }
+
httpd->mutex = g_mutex_new();
return httpd;
@@ -108,6 +152,7 @@ httpd_output_finish(void *data)
page_unref(httpd->metadata);
encoder_finish(httpd->encoder);
+ server_socket_free(httpd->server_socket);
g_mutex_free(httpd->mutex);
g_free(httpd);
}
@@ -124,36 +169,64 @@ httpd_client_add(struct httpd_output *httpd, int fd)
httpd->encoder->plugin->tag == NULL);
httpd->clients = g_list_prepend(httpd->clients, client);
+ httpd->clients_cnt++;
/* pass metadata to client */
if (httpd->metadata)
httpd_client_send_metadata(client, httpd->metadata);
}
-static gboolean
-httpd_listen_in_event(G_GNUC_UNUSED GIOChannel *source,
- G_GNUC_UNUSED GIOCondition condition,
- gpointer data)
+static void
+httpd_listen_in_event(int fd, const struct sockaddr *address,
+ size_t address_length, G_GNUC_UNUSED int uid, void *ctx)
{
- struct httpd_output *httpd = data;
- int fd;
- struct sockaddr_storage sa;
- socklen_t sa_length = sizeof(sa);
-
- g_mutex_lock(httpd->mutex);
+ struct httpd_output *httpd = ctx;
/* the listener socket has become readable - a client has
connected */
- fd = accept(httpd->fd, (struct sockaddr*)&sa, &sa_length);
- if (fd >= 0)
- httpd_client_add(httpd, fd);
- else if (fd < 0 && errno != EINTR)
+#ifdef HAVE_LIBWRAP
+ if (address->sa_family != AF_UNIX) {
+ char *hostaddr = sockaddr_to_string(address, address_length, NULL);
+ const char *progname = g_get_prgname();
+
+ struct request_info req;
+ request_init(&req, RQ_FILE, fd, RQ_DAEMON, progname, 0);
+
+ fromhost(&req);
+
+ if (!hosts_access(&req)) {
+ /* tcp wrappers says no */
+ g_warning("libwrap refused connection (libwrap=%s) from %s",
+ progname, hostaddr);
+ g_free(hostaddr);
+ close(fd);
+ g_mutex_unlock(httpd->mutex);
+ return;
+ }
+
+ g_free(hostaddr);
+ }
+#else
+ (void)address;
+ (void)address_length;
+#endif /* HAVE_WRAP */
+
+ g_mutex_lock(httpd->mutex);
+
+ if (fd >= 0) {
+ /* can we allow additional client */
+ if (httpd->open &&
+ (httpd->clients_max == 0 ||
+ httpd->clients_cnt < httpd->clients_max))
+ httpd_client_add(httpd, fd);
+ else
+ close(fd);
+ } else if (fd < 0 && errno != EINTR) {
g_warning("accept() failed: %s", g_strerror(errno));
+ }
g_mutex_unlock(httpd->mutex);
-
- return true;
}
/**
@@ -165,12 +238,22 @@ httpd_output_read_page(struct httpd_output *httpd)
{
size_t size = 0, nbytes;
+ if (httpd->unflushed_input >= 65536) {
+ /* we have fed a lot of input into the encoder, but it
+ didn't give anything back yet - flush now to avoid
+ buffer underruns */
+ encoder_flush(httpd->encoder, NULL);
+ httpd->unflushed_input = 0;
+ }
+
do {
nbytes = encoder_read(httpd->encoder, httpd->buffer + size,
sizeof(httpd->buffer) - size);
if (nbytes == 0)
break;
+ httpd->unflushed_input = 0;
+
size += nbytes;
} while (size < sizeof(httpd->buffer));
@@ -195,41 +278,41 @@ httpd_output_encoder_open(struct httpd_output *httpd,
bytes of encoder output after opening it, because it has to
be sent to every new client */
httpd->header = httpd_output_read_page(httpd);
+
+ httpd->unflushed_input = 0;
+
return true;
}
static bool
+httpd_output_enable(void *data, GError **error_r)
+{
+ struct httpd_output *httpd = data;
+
+ return httpd_output_bind(httpd, error_r);
+}
+
+static void
+httpd_output_disable(void *data)
+{
+ struct httpd_output *httpd = data;
+
+ httpd_output_unbind(httpd);
+}
+
+static bool
httpd_output_open(void *data, struct audio_format *audio_format,
GError **error)
{
struct httpd_output *httpd = data;
bool success;
- GIOChannel *channel;
g_mutex_lock(httpd->mutex);
- /* create and set up listener socket */
-
- httpd->fd = socket_bind_listen(PF_INET, SOCK_STREAM, 0,
- (struct sockaddr *)&httpd->address,
- httpd->address_size,
- 16, error);
- if (httpd->fd < 0) {
- g_mutex_unlock(httpd->mutex);
- return false;
- }
-
- channel = g_io_channel_unix_new(httpd->fd);
- httpd->source_id = g_io_add_watch(channel, G_IO_IN,
- httpd_listen_in_event, httpd);
- g_io_channel_unref(channel);
-
/* open the encoder */
success = httpd_output_encoder_open(httpd, audio_format, error);
if (!success) {
- g_source_remove(httpd->source_id);
- close(httpd->fd);
g_mutex_unlock(httpd->mutex);
return false;
}
@@ -237,8 +320,11 @@ httpd_output_open(void *data, struct audio_format *audio_format,
/* initialize other attributes */
httpd->clients = NULL;
+ httpd->clients_cnt = 0;
httpd->timer = timer_new(audio_format);
+ httpd->open = true;
+
g_mutex_unlock(httpd->mutex);
return true;
}
@@ -257,6 +343,8 @@ static void httpd_output_close(void *data)
g_mutex_lock(httpd->mutex);
+ httpd->open = false;
+
timer_free(httpd->timer);
g_list_foreach(httpd->clients, httpd_client_delete, NULL);
@@ -267,9 +355,6 @@ static void httpd_output_close(void *data)
encoder_close(httpd->encoder);
- g_source_remove(httpd->source_id);
- close(httpd->fd);
-
g_mutex_unlock(httpd->mutex);
}
@@ -281,6 +366,7 @@ httpd_output_remove_client(struct httpd_output *httpd,
assert(client != NULL);
httpd->clients = g_list_remove(httpd->clients, client);
+ httpd->clients_cnt--;
}
void
@@ -291,6 +377,16 @@ httpd_output_send_header(struct httpd_output *httpd,
httpd_client_send(client, httpd->header);
}
+static unsigned
+httpd_output_delay(void *data)
+{
+ struct httpd_output *httpd = data;
+
+ return httpd->timer->started
+ ? timer_delay(httpd->timer)
+ : 0;
+}
+
static void
httpd_client_check_queue(gpointer data, G_GNUC_UNUSED gpointer user_data)
{
@@ -352,6 +448,8 @@ httpd_output_encode_and_play(struct httpd_output *httpd,
if (!success)
return false;
+ httpd->unflushed_input += size;
+
httpd_output_encoder_to_clients(httpd);
return true;
@@ -378,13 +476,29 @@ httpd_output_play(void *data, const void *chunk, size_t size, GError **error)
if (!httpd->timer->started)
timer_start(httpd->timer);
- else
- timer_sync(httpd->timer);
timer_add(httpd->timer, size);
return size;
}
+static bool
+httpd_output_pause(void *data)
+{
+ struct httpd_output *httpd = data;
+
+ g_mutex_lock(httpd->mutex);
+ bool has_clients = httpd->clients != NULL;
+ g_mutex_unlock(httpd->mutex);
+
+ if (has_clients) {
+ static const char silence[1020];
+ return httpd_output_play(data, silence, sizeof(silence), NULL);
+ } else {
+ g_usleep(100000);
+ return true;
+ }
+}
+
static void
httpd_send_metadata(gpointer data, gpointer user_data)
{
@@ -433,9 +547,8 @@ httpd_output_tag(void *data, const struct tag *tag)
page_unref (httpd->metadata);
httpd->metadata =
- icy_server_metadata_page(tag, TAG_ITEM_ALBUM,
- TAG_ITEM_ARTIST,
- TAG_ITEM_TITLE,
+ icy_server_metadata_page(tag, TAG_ALBUM,
+ TAG_ARTIST, TAG_TITLE,
TAG_NUM_OF_ITEM_TYPES);
if (httpd->metadata != NULL) {
g_mutex_lock(httpd->mutex);
@@ -468,9 +581,13 @@ const struct audio_output_plugin httpd_output_plugin = {
.name = "httpd",
.init = httpd_output_init,
.finish = httpd_output_finish,
+ .enable = httpd_output_enable,
+ .disable = httpd_output_disable,
.open = httpd_output_open,
.close = httpd_output_close,
+ .delay = httpd_output_delay,
.send_tag = httpd_output_tag,
.play = httpd_output_play,
+ .pause = httpd_output_pause,
.cancel = httpd_output_cancel,
};