[Spice-devel] [PATCH spice-gtk 1/2] Add webdav channel

Marc-André Lureau marcandre.lureau at gmail.com
Fri Feb 28 04:16:18 PST 2014


From: Marc-André Lureau <marcandre.lureau at redhat.com>

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



More information about the Spice-devel mailing list