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

Alon Levy alevy at redhat.com
Mon Mar 17 00:54:05 PDT 2014


On 02/28/2014 02:16 PM, Marc-André Lureau wrote:
> 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.

I didn't understand everything (I'm confused by the soup part), but it
works, and looks good to me.

ACK series

> ---
>  .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;

Perhaps comment / change name to reflect purpose - callback after data
has been sent (g_output_queue_flush_async)

> +    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);

Warning on idle_id == 0 but you check for it to be non zero below -
should the warning be removed?

> +
> +    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);

Would be nice to use an actual constant for this instead of relying on
G_MAXUINT16 here and above.

> +    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
> 



More information about the Spice-devel mailing list