On 02/28/2014 02:16 PM, Marc-André Lureau wrote: > 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. 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 > _______________________________________________ Spice-devel mailing list Spice-devel@xxxxxxxxxxxxxxxxxxxxx http://lists.freedesktop.org/mailman/listinfo/spice-devel