From: Marc-André Lureau <marcandre.lureau@xxxxxxxxxx> See spice-common for protocol details. phodav, a webdav server library, is imported thanks to a submodule, until this project has a stable API and releases. The webdav channel is reponsible for handling port events and multiplexing the request streams. Extra care has been made to avoid blocking and to enable some fairness between concurrent streams, however this has been particularly tricky and is likely to have some issues left. The webdav server is run in a seperate thread, using libsoup. The client communication is done via a local tcp socket, but protected to only accept local connection and with a pretty strong password. The home directory is exported for the remote to browse, which seems to be a sensible default atm. --- .gitmodules | 3 + autogen.sh | 2 +- configure.ac | 4 + doc/reference/spice-gtk-docs.xml | 1 + doc/reference/spice-gtk-sections.txt | 17 + doc/reference/spice-gtk.types | 4 +- gtk/Makefile.am | 9 +- gtk/channel-webdav.c | 733 +++++++++++++++++++++++++++++++++++ gtk/channel-webdav.h | 68 ++++ gtk/map-file | 1 + gtk/phodav | 1 + gtk/spice-channel.c | 6 + gtk/spice-client.h | 1 + gtk/spice-glib-sym-file | 1 + gtk/spice-session-priv.h | 3 + gtk/spice-session.c | 1 + spice-common | 2 +- 17 files changed, 852 insertions(+), 5 deletions(-) create mode 100644 gtk/channel-webdav.c create mode 100644 gtk/channel-webdav.h create mode 160000 gtk/phodav diff --git a/.gitmodules b/.gitmodules index 0c618ee..cfce54a 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,6 @@ [submodule "spice-common"] path = spice-common url = ../spice-common +[submodule "gtk/phodav"] + path = gtk/phodav + url = git://git.gnome.org/phodav diff --git a/autogen.sh b/autogen.sh index 0c18272..d71be70 100755 --- a/autogen.sh +++ b/autogen.sh @@ -6,6 +6,7 @@ srcdir=`dirname $0` test -z "$srcdir" && srcdir=. git submodule update --init --recursive +(cd "$srcdir/gtk/phodav/" && intltoolize -f) gtkdocize autoreconf -v --force --install @@ -15,4 +16,3 @@ if [ -z "$NOCONFIGURE" ]; then echo "Running configure with --enable-maintainer-mode --enable-gtk-doc --with-gtk=3.0 --enable-vala ${1+"$@"}" "$srcdir"/configure --enable-maintainer-mode --enable-gtk-doc --with-gtk=3.0 --enable-vala ${1+"$@"} fi - diff --git a/configure.ac b/configure.ac index 192d748..d30ec40 100644 --- a/configure.ac +++ b/configure.ac @@ -75,6 +75,8 @@ AC_CONFIG_SUBDIRS([spice-common]) COMMON_CFLAGS='-I ${top_srcdir}/spice-common/ -I ${top_srcdir}/spice-common/spice-protocol/' AC_SUBST(COMMON_CFLAGS) +AC_CONFIG_SUBDIRS([gtk/phodav]) + SPICE_GTK_MAJOR_VERSION=`echo $PACKAGE_VERSION | cut -d. -f1` SPICE_GTK_MINOR_VERSION=`echo $PACKAGE_VERSION | cut -d. -f2` SPICE_GTK_MICRO_VERSION=`echo $PACKAGE_VERSION | cut -d. -f3 | cut -d- -f1` @@ -267,6 +269,8 @@ PKG_CHECK_MODULES(GTHREAD, gthread-2.0 > 2.0.0) AC_SUBST(GTHREAD_CFLAGS) AC_SUBST(GTHREAD_LIBS) +PKG_CHECK_MODULES(SOUP, libsoup-2.4) + AC_ARG_WITH([audio], AS_HELP_STRING([--with-audio=@<:@gstreamer/pulse/auto/no@:>@], [Select audio backend @<:@default=auto@:>@]), [], diff --git a/doc/reference/spice-gtk-docs.xml b/doc/reference/spice-gtk-docs.xml index d2c1a2b..5faea74 100644 --- a/doc/reference/spice-gtk-docs.xml +++ b/doc/reference/spice-gtk-docs.xml @@ -37,6 +37,7 @@ <xi:include href="xml/channel-smartcard.xml"/> <xi:include href="xml/channel-usbredir.xml"/> <xi:include href="xml/channel-port.xml"/> + <xi:include href="xml/channel-webdav.xml"/> </chapter> <chapter> diff --git a/doc/reference/spice-gtk-sections.txt b/doc/reference/spice-gtk-sections.txt index 9232a23..caaa92c 100644 --- a/doc/reference/spice-gtk-sections.txt +++ b/doc/reference/spice-gtk-sections.txt @@ -461,3 +461,20 @@ spice_uri_get_type <SUBSECTION Private> SpiceURIPrivate </SECTION> + +<SECTION> +<FILE>channel-webdav</FILE> +<TITLE>SpiceWebdavChannel</TITLE> +SpiceWebdavChannel +SpiceWebdavChannelClass +<SUBSECTION Standard> +SPICE_IS_WEBDAV_CHANNEL +SPICE_IS_WEBDAV_CHANNEL_CLASS +SPICE_TYPE_WEBDAV_CHANNEL +SPICE_WEBDAV_CHANNEL +SPICE_WEBDAV_CHANNEL_CLASS +SPICE_WEBDAV_CHANNEL_GET_CLASS +spice_webdav_channel_get_type +<SUBSECTION Private> +SpiceWebdavChannelPrivate +</SECTION> diff --git a/doc/reference/spice-gtk.types b/doc/reference/spice-gtk.types index 2f52845..db0374a 100644 --- a/doc/reference/spice-gtk.types +++ b/doc/reference/spice-gtk.types @@ -13,6 +13,7 @@ #include "channel-record.h" #include "channel-smartcard.h" #include "channel-usbredir.h" +#include "channel-webdav.h" #include "spice-gtk-session.h" #include "spice-widget.h" #include "spice-grabsequence.h" @@ -42,4 +43,5 @@ spice_usbredir_channel_get_type spice_usb_device_get_type spice_usb_device_manager_get_type spice_usb_device_widget_get_type -spice_port_channel_get_type \ No newline at end of file +spice_port_channel_get_type +spice_webdav_channel_get_type \ No newline at end of file diff --git a/gtk/Makefile.am b/gtk/Makefile.am index 8a16120..ebb00b8 100644 --- a/gtk/Makefile.am +++ b/gtk/Makefile.am @@ -1,6 +1,6 @@ NULL = -SUBDIRS = +SUBDIRS = phodav if WITH_CONTROLLER SUBDIRS += controller @@ -96,6 +96,7 @@ SPICE_COMMON_CPPFLAGS = \ $(SMARTCARD_CFLAGS) \ $(USBREDIR_CFLAGS) \ $(GUDEV_CFLAGS) \ + $(SOUP_CFLAGS) \ $(NULL) AM_CPPFLAGS = \ @@ -185,8 +186,9 @@ libspice_client_glib_2_0_la_LDFLAGS = \ libspice_client_glib_2_0_la_LIBADD = \ $(top_builddir)/spice-common/common/libspice-common.la \ $(top_builddir)/spice-common/common/libspice-common-client.la \ + phodav/libphodav.la \ $(GLIB2_LIBS) \ - $(GIO_LIBS) \ + $(SOUP_LIBS) \ $(GOBJECT2_LIBS) \ $(CELT051_LIBS) \ $(OPUS_LIBS) \ @@ -236,6 +238,7 @@ libspice_client_glib_2_0_la_SOURCES = \ gio-coroutine.h \ \ channel-base.c \ + channel-webdav.c \ channel-cursor.c \ channel-display.c \ channel-display-priv.h \ @@ -303,6 +306,7 @@ libspice_client_glibinclude_HEADERS = \ channel-record.h \ channel-smartcard.h \ channel-usbredir.h \ + channel-webdav.h \ usb-device-manager.h \ smartcard-manager.h \ $(NULL) @@ -598,6 +602,7 @@ glib_introspection_files = \ spice-glib-enums.c \ spice-option.c \ spice-util.c \ + channel-webdav.c \ channel-cursor.c \ channel-display.c \ channel-inputs.c \ diff --git a/gtk/channel-webdav.c b/gtk/channel-webdav.c new file mode 100644 index 0000000..28760f5 --- /dev/null +++ b/gtk/channel-webdav.c @@ -0,0 +1,733 @@ +/* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */ +/* + Copyright (C) 2013 Red Hat, Inc. + + This library 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.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, see <http://www.gnu.org/licenses/>. +*/ +#include "spice-client.h" +#include "spice-common.h" +#include "spice-channel-priv.h" +#include "spice-session-priv.h" +#include "spice-marshal.h" +#include "glib-compat.h" +#include "vmcstream.h" + +static PhodavServer* phodav_server_get(SpiceSession *session, gint *port); + +/** + * SECTION:channel-webdav + * @short_description: exports a directory + * @title: WebDAV Channel + * @section_id: + * @see_also: #SpiceChannel + * @stability: Stable + * @include: channel-webdav.h + * + * The "webdav" channel exports a directory to the guest for file + * manipulation (read/write/copy etc). The underlying protocol is + * implemented using WebDAV (RFC 4918) + * + * Since: 0.24 + */ + +#define SPICE_WEBDAV_CHANNEL_GET_PRIVATE(obj) \ + (G_TYPE_INSTANCE_GET_PRIVATE((obj), SPICE_TYPE_WEBDAV_CHANNEL, SpiceWebdavChannelPrivate)) + +typedef struct _OutputQueue OutputQueue; + +struct _SpiceWebdavChannelPrivate { + SpiceVmcStream *stream; + GCancellable *cancellable; + GHashTable *clients; + OutputQueue *queue; + + gboolean demuxing; + struct _demux { + gint64 client; + guint16 size; + guint8 *buf; + } demux; +}; + +G_DEFINE_TYPE(SpiceWebdavChannel, spice_webdav_channel, SPICE_TYPE_PORT_CHANNEL) + +static void spice_webdav_handle_msg(SpiceChannel *channel, SpiceMsgIn *msg); + +struct _OutputQueue { + GOutputStream *output; + gboolean flushing; + guint idle_id; + GQueue *queue; +}; + +typedef struct _OutputQueueElem { + OutputQueue *queue; + const guint8 *buf; + gsize size; + GFunc cb; + gpointer user_data; +} OutputQueueElem; + +static OutputQueue* output_queue_new(GOutputStream *output) +{ + OutputQueue *queue = g_new0(OutputQueue, 1); + + queue->output = g_object_ref(output); + queue->queue = g_queue_new(); + + return queue; +} + +static void output_queue_free(OutputQueue *queue) +{ + g_warn_if_fail(g_queue_get_length(queue->queue) == 0); + g_warn_if_fail(!queue->flushing); + g_warn_if_fail(!queue->idle_id); + + g_queue_free_full(queue->queue, g_free); + g_clear_object(&queue->output); + if (queue->idle_id) + g_source_remove(queue->idle_id); + g_free(queue); +} + +static gboolean output_queue_idle(gpointer user_data); + +static void output_queue_flush_cb(GObject *source_object, + GAsyncResult *res, + gpointer user_data) +{ + GError *error = NULL; + OutputQueueElem *e = user_data; + OutputQueue *q = e->queue; + + q->flushing = FALSE; + g_output_stream_flush_finish(G_OUTPUT_STREAM(source_object), + res, &error); + if (error) + g_warning("error: %s", error->message); + + g_clear_error(&error); + + if (!q->idle_id) + q->idle_id = g_idle_add(output_queue_idle, q); + + g_free(e); +} + +static gboolean output_queue_idle(gpointer user_data) +{ + OutputQueue *q = user_data; + OutputQueueElem *e; + GError *error = NULL; + + if (q->flushing) { + q->idle_id = 0; + return FALSE; + } + + e = g_queue_pop_head(q->queue); + if (!e) { + q->idle_id = 0; + return FALSE; + } + + g_output_stream_write_all(q->output, e->buf, e->size, NULL, NULL, &error); + if (error) + goto err; + else if (e->cb) + e->cb(q, e->user_data); + + q->flushing = TRUE; + g_output_stream_flush_async(q->output, G_PRIORITY_DEFAULT, NULL, output_queue_flush_cb, e); + + return TRUE; + +err: + g_warning("error: %s", error->message); + g_clear_error(&error); + + q->idle_id = 0; + return FALSE; +} + +static void output_queue_push(OutputQueue *q, const guint8 *buf, gsize size, + GFunc pushed_cb, gpointer user_data) +{ + OutputQueueElem *e = g_new(OutputQueueElem, 1); + + e->buf = buf; + e->size = size; + e->cb = pushed_cb; + e->user_data = user_data; + e->queue = q; + g_queue_push_tail(q->queue, e); + + if (!q->idle_id && !q->flushing) + q->idle_id = g_idle_add(output_queue_idle, q); +} + +typedef struct Client +{ + guint refs; + SpiceWebdavChannel *self; + GSocketConnection *conn; + OutputQueue *output; + gint64 id; + GCancellable *cancellable; + + struct _mux { + gint64 id; + guint16 size; + guint8 *buf; + } mux; +} Client; + +static void +client_unref(Client *client) +{ + if (--client->refs > 0) + return; + + g_free(client->mux.buf); + output_queue_free(client->output); + + g_object_unref(client->conn); + g_object_unref(client->cancellable); + + g_free(client); +} + +static Client * +client_ref(Client *client) +{ + client->refs++; + return client; +} + +static void client_start_read(SpiceWebdavChannel *self, Client *client); + +static void remove_client(SpiceWebdavChannel *self, Client *client) +{ + SpiceWebdavChannelPrivate *c; + + if (g_cancellable_is_cancelled(client->cancellable)) + return; + + g_cancellable_cancel(client->cancellable); + + c = self->priv; + g_hash_table_remove(c->clients, &client->id); +} + +static void mux_pushed_cb(OutputQueue *q, gpointer user_data) +{ + Client *client = user_data; + + if (client->mux.size == 0) { + remove_client(client->self, client); + } else { + client_start_read(client->self, client); + } + + client_unref(client); +} + +static void server_reply_cb(GObject *source_object, + GAsyncResult *res, + gpointer user_data) +{ + Client *client = user_data; + SpiceWebdavChannel *self = client->self; + SpiceWebdavChannelPrivate *c = self->priv; + GError *err = NULL; + gssize size; + + size = g_input_stream_read_finish(G_INPUT_STREAM(source_object), res, &err); + if (err || g_cancellable_is_cancelled(client->cancellable)) + goto end; + + g_return_if_fail(size <= G_MAXUINT16); + g_return_if_fail(size >= 0); + client->mux.size = size; + + output_queue_push(c->queue, (guint8 *)&client->mux.id, sizeof(gint64), NULL, NULL); + client->mux.size = GUINT16_TO_LE(client->mux.size); + output_queue_push(c->queue, (guint8 *)&client->mux.size, sizeof(guint16), NULL, NULL); + output_queue_push(c->queue, (guint8 *)client->mux.buf, size, (GFunc)mux_pushed_cb, client); + + return; + +end: + if (err) { + if (!g_cancellable_is_cancelled(client->cancellable)) + g_warning("read error: %s", err->message); + remove_client(self, client); + g_clear_error(&err); + } + + client_unref(client); +} + +static void client_start_read(SpiceWebdavChannel *self, Client *client) +{ + GInputStream *input; + + input = g_io_stream_get_input_stream(G_IO_STREAM(client->conn)); + g_input_stream_read_async(input, client->mux.buf, G_MAXUINT16, + G_PRIORITY_DEFAULT, client->cancellable, server_reply_cb, + client_ref(client)); +} + +static void start_demux(SpiceWebdavChannel *self); + +static void pushed_client_cb(OutputQueue *q, gpointer user_data) +{ + Client *client = user_data; + SpiceWebdavChannel *self = client->self; + SpiceWebdavChannelPrivate *c = self->priv; + + c->demuxing = FALSE; + start_demux(self); +} + +static void demux_to_client(SpiceWebdavChannel *self, + Client *client) +{ + SpiceWebdavChannelPrivate *c = self->priv; + gssize size = c->demux.size; + + CHANNEL_DEBUG(self, "pushing %ld to client %p", size, client); + + if (size != 0) { + output_queue_push(client->output, (guint8 *)c->demux.buf, size, + (GFunc)pushed_client_cb, client); + } else { + remove_client(self, client); + c->demuxing = FALSE; + start_demux(self); + } +} + +static void magic_written(GObject *source_object, + GAsyncResult *res, + gpointer user_data) +{ + Client *client = user_data; + SpiceWebdavChannel *self = client->self; + SpiceWebdavChannelPrivate *c = self->priv; + gssize bytes_written; + GError *err = NULL; + SpiceSession *session; + + session = spice_channel_get_session(SPICE_CHANNEL(self)); + bytes_written = g_output_stream_write_finish(G_OUTPUT_STREAM(source_object), + res, &err); + + if (err || bytes_written != sizeof(session->priv->webdav_magic)) + goto error; + + client_start_read(self, client); + g_hash_table_insert(c->clients, &client->id, client); + + demux_to_client(self, client); + + return; + +error: + if (err) { + g_critical("socket creation failed %s", err->message); + g_clear_error(&err); + } + if (client) { + client_unref(client); + } +} + +static void client_connected(GObject *source_object, + GAsyncResult *res, + gpointer user_data) +{ + SpiceWebdavChannel *self = user_data; + SpiceWebdavChannelPrivate *c = self->priv; + GSocketClient *sclient = G_SOCKET_CLIENT(source_object); + GError *err = NULL; + GSocketConnection *conn; + SpiceSession *session; + Client *client = NULL; + GOutputStream *output; + + session = spice_channel_get_session(SPICE_CHANNEL(self)); + + conn = g_socket_client_connect_to_host_finish(sclient, res, &err); + g_object_unref(sclient); + if (err) + goto error; + + client = g_new0(Client, 1); + client->refs = 1; + client->id = c->demux.client; + client->self = self; + client->conn = conn; + client->mux.id = GINT64_TO_LE(client->id); + client->mux.buf = g_malloc(G_MAXUINT16); + client->cancellable = g_cancellable_new(); + + output = g_buffered_output_stream_new(g_io_stream_get_output_stream(G_IO_STREAM(conn))); + client->output = output_queue_new(output); + g_object_unref(output); + + g_output_stream_write_async(g_io_stream_get_output_stream(G_IO_STREAM(conn)), + session->priv->webdav_magic, sizeof(session->priv->webdav_magic), + G_PRIORITY_DEFAULT, c->cancellable, + magic_written, client); + return; + +error: + if (err) { + g_critical("socket creation failed %s", err->message); + g_clear_error(&err); + } + if (client) { + client_unref(client); + } +} + +static void start_client(SpiceWebdavChannel *self) +{ + SpiceWebdavChannelPrivate *c = self->priv; + GSocketClient *sclient; + gint davport = -1; + SpiceSession *session; + + session = spice_channel_get_session(SPICE_CHANNEL(self)); + phodav_server_get(session, &davport); + CHANNEL_DEBUG(self, "starting client %" G_GINT64_FORMAT, c->demux.client); + + sclient = g_socket_client_new(); + g_socket_client_connect_to_host_async(sclient, "localhost", davport, + c->cancellable, client_connected, self); +} + +static void data_read_cb(GObject *source_object, + GAsyncResult *res, + gpointer user_data) +{ + SpiceWebdavChannel *self = user_data; + SpiceWebdavChannelPrivate *c; + Client *client; + GError *error = NULL; + gssize size; + + size = spice_vmc_input_stream_read_all_finish(G_INPUT_STREAM(source_object), res, &error); + if (error) { + g_warning("error: %s", error->message); + g_clear_error(&error); + return; + } + + c = self->priv; + g_return_if_fail(size == c->demux.size); + + client = g_hash_table_lookup(c->clients, &c->demux.client); + + if (client) + demux_to_client(self, client); + else + start_client(self); +} + + +static void size_read_cb(GObject *source_object, + GAsyncResult *res, + gpointer user_data) +{ + SpiceWebdavChannel *self = user_data; + SpiceWebdavChannelPrivate *c; + GInputStream *istream = G_INPUT_STREAM(source_object); + GError *error = NULL; + gssize size; + + size = spice_vmc_input_stream_read_all_finish(G_INPUT_STREAM(source_object), res, &error); + if (error || size != sizeof(guint16)) + goto end; + + c = self->priv; + c->demux.size = GUINT16_FROM_LE(c->demux.size); + spice_vmc_input_stream_read_all_async(istream, + c->demux.buf, c->demux.size, + G_PRIORITY_DEFAULT, c->cancellable, data_read_cb, self); + return; + +end: + if (error) { + g_warning("error: %s", error->message); + g_clear_error(&error); + } +} + +static void client_read_cb(GObject *source_object, + GAsyncResult *res, + gpointer user_data) +{ + SpiceWebdavChannel *self = user_data; + SpiceWebdavChannelPrivate *c = self->priv; + GInputStream *istream = G_INPUT_STREAM(source_object); + GError *error = NULL; + gssize size; + + size = spice_vmc_input_stream_read_all_finish(G_INPUT_STREAM(source_object), res, &error); + if (error || size != sizeof(gint64)) + goto end; + + c->demux.client = GINT64_FROM_LE(c->demux.client); + spice_vmc_input_stream_read_all_async(istream, + &c->demux.size, sizeof(guint16), + G_PRIORITY_DEFAULT, c->cancellable, size_read_cb, self); + return; + +end: + if (error) { + g_warning("error: %s", error->message); + g_clear_error(&error); + } +} + +static void start_demux(SpiceWebdavChannel *self) +{ + SpiceWebdavChannelPrivate *c = self->priv; + GInputStream *istream = g_io_stream_get_input_stream(G_IO_STREAM(c->stream)); + + if (c->demuxing) + return; + + c->demuxing = TRUE; + + CHANNEL_DEBUG(self, "start demux"); + spice_vmc_input_stream_read_all_async(istream, &c->demux.client, sizeof(gint64), + G_PRIORITY_DEFAULT, c->cancellable, client_read_cb, self); + +} + +static void port_event(SpiceWebdavChannel *self, gint event) +{ + SpiceWebdavChannelPrivate *c = self->priv; + + CHANNEL_DEBUG(self, "port event:%d", event); + if (event == SPICE_PORT_EVENT_OPENED) { + g_cancellable_reset(c->cancellable); + start_demux(self); + } else { + g_cancellable_cancel(c->cancellable); + c->demuxing = FALSE; + g_hash_table_remove_all(c->clients); + } +} + +static void client_remove_unref(gpointer data) +{ + Client *client = data; + + g_cancellable_cancel(client->cancellable); + client_unref(client); +} + +static void spice_webdav_channel_init(SpiceWebdavChannel *channel) +{ + SpiceWebdavChannelPrivate *c = SPICE_WEBDAV_CHANNEL_GET_PRIVATE(channel); + + channel->priv = c; + c->stream = spice_vmc_stream_new(SPICE_CHANNEL(channel)); + c->cancellable = g_cancellable_new(); + c->clients = g_hash_table_new_full(g_int64_hash, g_int64_equal, + NULL, client_remove_unref); + c->demux.buf = g_malloc(G_MAXUINT16); + + GOutputStream *ostream = g_io_stream_get_output_stream(G_IO_STREAM(c->stream)); + c->queue = output_queue_new(ostream); +} + +static void spice_webdav_channel_finalize(GObject *object) +{ + SpiceWebdavChannelPrivate *c = SPICE_WEBDAV_CHANNEL(object)->priv; + + g_free(c->demux.buf); + + G_OBJECT_CLASS(spice_webdav_channel_parent_class)->finalize(object); +} + +static void spice_webdav_channel_dispose(GObject *object) +{ + SpiceWebdavChannelPrivate *c = SPICE_WEBDAV_CHANNEL(object)->priv; + + g_cancellable_cancel(c->cancellable); + g_clear_object(&c->cancellable); + g_clear_pointer(&c->queue, output_queue_free); + g_clear_object(&c->stream); + g_hash_table_unref(c->clients); + + G_OBJECT_CLASS(spice_webdav_channel_parent_class)->dispose(object); +} + +static void spice_webdav_channel_up(SpiceChannel *channel) +{ + CHANNEL_DEBUG(channel, "up"); +} + +static void spice_webdav_channel_class_init(SpiceWebdavChannelClass *klass) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS(klass); + SpiceChannelClass *channel_class = SPICE_CHANNEL_CLASS(klass); + + gobject_class->dispose = spice_webdav_channel_dispose; + gobject_class->finalize = spice_webdav_channel_finalize; + channel_class->handle_msg = spice_webdav_handle_msg; + channel_class->channel_up = spice_webdav_channel_up; + + g_signal_override_class_handler("port-event", + SPICE_TYPE_WEBDAV_CHANNEL, + G_CALLBACK(port_event)); + + g_type_class_add_private(klass, sizeof(SpiceWebdavChannelPrivate)); +} + +/* coroutine context */ +static void webdav_handle_msg(SpiceChannel *channel, SpiceMsgIn *in) +{ + SpiceWebdavChannel *self = SPICE_WEBDAV_CHANNEL(channel); + SpiceWebdavChannelPrivate *c = self->priv; + int size; + uint8_t *buf; + + buf = spice_msg_in_raw(in, &size); + CHANNEL_DEBUG(channel, "len:%d buf:%p", size, buf); + + spice_vmc_input_stream_co_data( + SPICE_VMC_INPUT_STREAM(g_io_stream_get_input_stream(G_IO_STREAM(c->stream))), + buf, size); +} + + +/* coroutine context */ +static void spice_webdav_handle_msg(SpiceChannel *channel, SpiceMsgIn *msg) +{ + int type = spice_msg_in_type(msg); + SpiceChannelClass *parent_class; + + parent_class = SPICE_CHANNEL_CLASS(spice_webdav_channel_parent_class); + + if (type == SPICE_MSG_SPICEVMC_DATA) + webdav_handle_msg(channel, msg); + else if (parent_class->handle_msg) + parent_class->handle_msg(channel, msg); + else + g_return_if_reached(); +} + + + +static void new_connection(SoupSocket *sock, + SoupSocket *new, + gpointer user_data) +{ + SpiceSession *session = user_data; + SoupAddress *addr; + GSocketAddress *gaddr; + GInetAddress *iaddr; + guint port; + guint8 magic[16]; + gsize nread; + gboolean success = FALSE; + SoupSocketIOStatus status; + + /* note: this is sync calls, since webdav server is in a seperate thread */ + addr = soup_socket_get_remote_address(new); + gaddr = soup_address_get_gsockaddr(addr); + iaddr = g_inet_socket_address_get_address(G_INET_SOCKET_ADDRESS(gaddr)); + port = g_inet_socket_address_get_port(G_INET_SOCKET_ADDRESS(gaddr)); + + SPICE_DEBUG("port %d %p", port, iaddr); + if (!g_inet_address_get_is_loopback(iaddr)) { + g_warn_if_reached(); + goto end; + } + + g_object_set(new, "non-blocking", FALSE, NULL); + status = soup_socket_read(new, magic, sizeof(magic), &nread, NULL, NULL); + if (status != SOUP_SOCKET_OK) { + g_warning("bad initial socket read: %d", status); + goto end; + } + g_object_set(new, "non-blocking", TRUE, NULL); + + /* check we got the right magic */ + if (memcmp(session->priv->webdav_magic, magic, sizeof(magic))) { + g_warn_if_reached(); + goto end; + } + + success = TRUE; + +end: + if (!success) { + g_warn_if_reached(); + soup_socket_disconnect(new); + g_signal_stop_emission_by_name(sock, "new_connection"); + } + g_object_unref(gaddr); +} + +static PhodavServer* webdav_server_new(SpiceSession *session) +{ + PhodavServer *dav; + SoupServer *server; + SoupSocket *listener; + int i; + + g_warn_if_fail(!session->priv->webdav); + + dav = phodav_server_new(0, g_get_user_special_dir(G_USER_DIRECTORY_PUBLIC_SHARE)); + session->priv->webdav = dav; + for (i = 0; i < sizeof(session->priv->webdav_magic); i++) + session->priv->webdav_magic[i] = g_random_int_range(0, 255); + + server = phodav_server_get_soup_server(dav); + listener = soup_server_get_listener(server); + spice_g_signal_connect_object(listener, "new_connection", + G_CALLBACK(new_connection), session, + 0); + + return dav; +} + +static PhodavServer* phodav_server_get(SpiceSession *session, gint *port) +{ + g_return_val_if_fail(SPICE_IS_SESSION(session), NULL); + + PhodavServer *self; + static GStaticMutex mutex = G_STATIC_MUTEX_INIT; + + g_static_mutex_lock(&mutex); + self = session->priv->webdav; + if (self == NULL) { + self = webdav_server_new(session); + phodav_server_run(self); + } + g_static_mutex_unlock(&mutex); + + if (port) + *port = phodav_server_get_port(self); + + return self; +} diff --git a/gtk/channel-webdav.h b/gtk/channel-webdav.h new file mode 100644 index 0000000..7940706 --- /dev/null +++ b/gtk/channel-webdav.h @@ -0,0 +1,68 @@ +/* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */ +/* + Copyright (C) 2013 Red Hat, Inc. + + This library 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.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, see <http://www.gnu.org/licenses/>. +*/ +#ifndef __SPICE_WEBDAV_CHANNEL_H__ +#define __SPICE_WEBDAV_CHANNEL_H__ + +#include <gio/gio.h> +#include "spice-client.h" +#include "channel-port.h" + +G_BEGIN_DECLS + +#define SPICE_TYPE_WEBDAV_CHANNEL (spice_webdav_channel_get_type()) +#define SPICE_WEBDAV_CHANNEL(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), SPICE_TYPE_WEBDAV_CHANNEL, SpiceWebdavChannel)) +#define SPICE_WEBDAV_CHANNEL_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), SPICE_TYPE_WEBDAV_CHANNEL, SpiceWebdavChannelClass)) +#define SPICE_IS_WEBDAV_CHANNEL(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), SPICE_TYPE_WEBDAV_CHANNEL)) +#define SPICE_IS_WEBDAV_CHANNEL_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), SPICE_TYPE_WEBDAV_CHANNEL)) +#define SPICE_WEBDAV_CHANNEL_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), SPICE_TYPE_WEBDAV_CHANNEL, SpiceWebdavChannelClass)) + +typedef struct _SpiceWebdavChannel SpiceWebdavChannel; +typedef struct _SpiceWebdavChannelClass SpiceWebdavChannelClass; +typedef struct _SpiceWebdavChannelPrivate SpiceWebdavChannelPrivate; + +/** + * SpiceWebdavChannel: + * + * The #SpiceWebdavChannel struct is opaque and should not be accessed directly. + */ +struct _SpiceWebdavChannel { + SpicePortChannel parent; + + /*< private >*/ + SpiceWebdavChannelPrivate *priv; + /* Do not add fields to this struct */ +}; + +/** + * SpiceWebdavChannelClass: + * @parent_class: Parent class. + * + * Class structure for #SpiceWebdavChannel. + */ +struct _SpiceWebdavChannelClass { + SpicePortChannelClass parent_class; + + /*< private >*/ + /* Do not add fields to this struct */ +}; + +GType spice_webdav_channel_get_type(void); + +G_END_DECLS + +#endif /* __SPICE_WEBDAV_CHANNEL_H__ */ diff --git a/gtk/map-file b/gtk/map-file index f98680c..90f14f1 100644 --- a/gtk/map-file +++ b/gtk/map-file @@ -132,6 +132,7 @@ spice_uri_set_port; spice_uri_set_scheme; spice_uri_set_user; spice_uri_to_string; +spice_webdav_channel_get_type; local: *; }; diff --git a/gtk/phodav b/gtk/phodav new file mode 160000 index 0000000..46082ea --- /dev/null +++ b/gtk/phodav @@ -0,0 +1 @@ +Subproject commit 46082ead2f214c6cbf9acc98fa7ed1c0d8436355 diff --git a/gtk/spice-channel.c b/gtk/spice-channel.c index 632a286..a9c1076 100644 --- a/gtk/spice-channel.c +++ b/gtk/spice-channel.c @@ -1878,6 +1878,7 @@ static const char *to_string[] = { [ SPICE_CHANNEL_SMARTCARD ] = "smartcard", [ SPICE_CHANNEL_USBREDIR ] = "usbredir", [ SPICE_CHANNEL_PORT ] = "port", + [ SPICE_CHANNEL_WEBDAV ] = "webdav", }; /** @@ -1938,6 +1939,7 @@ gchar *spice_channel_supported_string(void) #ifdef USE_USBREDIR spice_channel_type_to_string(SPICE_CHANNEL_USBREDIR), #endif + spice_channel_type_to_string(SPICE_CHANNEL_WEBDAV), NULL); } @@ -2002,6 +2004,10 @@ SpiceChannel *spice_channel_new(SpiceSession *s, int type, int id) break; } #endif + case SPICE_CHANNEL_WEBDAV: { + gtype = SPICE_TYPE_WEBDAV_CHANNEL; + break; + } case SPICE_CHANNEL_PORT: gtype = SPICE_TYPE_PORT_CHANNEL; break; diff --git a/gtk/spice-client.h b/gtk/spice-client.h index 98aaffe..0e1e49d 100644 --- a/gtk/spice-client.h +++ b/gtk/spice-client.h @@ -43,6 +43,7 @@ #include "channel-smartcard.h" #include "channel-usbredir.h" #include "channel-port.h" +#include "channel-webdav.h" #include "smartcard-manager.h" #include "usb-device-manager.h" diff --git a/gtk/spice-glib-sym-file b/gtk/spice-glib-sym-file index 2aa17cb..878dd12 100644 --- a/gtk/spice-glib-sym-file +++ b/gtk/spice-glib-sym-file @@ -106,3 +106,4 @@ spice_uri_set_scheme spice_uri_set_user spice_uri_to_string spice_session_get_proxy_uri +spice_webdav_channel_get_type diff --git a/gtk/spice-session-priv.h b/gtk/spice-session-priv.h index 1aae342..cf9f9d1 100644 --- a/gtk/spice-session-priv.h +++ b/gtk/spice-session-priv.h @@ -23,6 +23,7 @@ #include "desktop-integration.h" #include "spice-session.h" #include "spice-gtk-session.h" +#include "phodav/libphodav/phodav.h" #include "spice-channel-cache.h" #include "decode.h" @@ -107,6 +108,8 @@ struct _SpiceSessionPrivate { SpiceGtkSession *gtk_session; SpiceUsbDeviceManager *usb_manager; SpicePlaybackChannel *playback_channel; + PhodavServer *webdav; + guint8 webdav_magic[16]; }; SpiceSession *spice_session_new_from_session(SpiceSession *session); diff --git a/gtk/spice-session.c b/gtk/spice-session.c index 09556dc..ea32cf7 100644 --- a/gtk/spice-session.c +++ b/gtk/spice-session.c @@ -212,6 +212,7 @@ spice_session_dispose(GObject *gobject) g_clear_object(&s->gtk_session); g_clear_object(&s->usb_manager); g_clear_object(&s->proxy); + g_clear_object(&s->webdav); /* Chain up to the parent class */ if (G_OBJECT_CLASS(spice_session_parent_class)->dispose) diff --git a/spice-common b/spice-common index 96ca358..74e3a04 160000 --- a/spice-common +++ b/spice-common @@ -1 +1 @@ -Subproject commit 96ca358669cd32d17ce51f30de3cdbf0a1c0518c +Subproject commit 74e3a04d5bed51febaa51f41405ca4779f232d6a -- 1.8.5.3 _______________________________________________ Spice-devel mailing list Spice-devel@xxxxxxxxxxxxxxxxxxxxx http://lists.freedesktop.org/mailman/listinfo/spice-devel