From 4bdf3da7b08e21df273bdcc12ca6dd54dfdb8e0e Mon Sep 17 00:00:00 2001 From: Ondrej Holy Date: Fri, 8 Apr 2022 14:02:50 +0200 Subject: [PATCH] Rewrite DAV backend to libsoup async API to fix crashes Resolves: #2062465 --- ...-not-lose-userinfo-when-copying-URIs.patch | 86 + ...-to-libsoup-async-API-to-fix-crashes.patch | 2504 +++++++++++++++++ gvfs.spec | 9 +- 3 files changed, 2598 insertions(+), 1 deletion(-) create mode 100644 dav-Do-not-lose-userinfo-when-copying-URIs.patch create mode 100644 dav-Rewrite-to-libsoup-async-API-to-fix-crashes.patch diff --git a/dav-Do-not-lose-userinfo-when-copying-URIs.patch b/dav-Do-not-lose-userinfo-when-copying-URIs.patch new file mode 100644 index 0000000..60d915e --- /dev/null +++ b/dav-Do-not-lose-userinfo-when-copying-URIs.patch @@ -0,0 +1,86 @@ +From 4b9d9442f4b465e9403c4da399ccf6d7cc652eae Mon Sep 17 00:00:00 2001 +From: Daniel Kolesa +Date: Sun, 27 Mar 2022 16:01:55 +0200 +Subject: [PATCH] dav: Do not lose userinfo when copying URIs + +The use of g_uri_build was also bad, since otherwise we would +run into this: https://gitlab.gnome.org/GNOME/glib/-/issues/2619 + +While this should be fixed in glib, we need to work around this +behavior for existing installations. + +Fixes: https://gitlab.gnome.org/GNOME/gvfs/-/issues/614 +--- + daemon/gvfsbackenddav.c | 28 +++++++--------------------- + 1 file changed, 7 insertions(+), 21 deletions(-) + +diff --git a/daemon/gvfsbackenddav.c b/daemon/gvfsbackenddav.c +index f9d10a40..3ece9a5f 100644 +--- a/daemon/gvfsbackenddav.c ++++ b/daemon/gvfsbackenddav.c +@@ -321,23 +321,6 @@ message_should_apply_redir_ref (SoupMessage *msg) + return TRUE; + } + +-static GUri * +-dav_uri_dup_with (GUri *uri, const char *path, const char *userinfo) +-{ +- if (!path && !userinfo) +- return g_uri_ref (uri); +- +- return g_uri_build (g_uri_get_flags (uri), +- g_uri_get_scheme (uri), +- userinfo ? userinfo : g_uri_get_userinfo (uri), +- g_uri_get_host (uri), +- g_uri_get_port (uri), +- path ? path : g_uri_get_path (uri), +- g_uri_get_query (uri), +- g_uri_get_fragment (uri)); +-} +- +- + static GUri * + g_vfs_backend_dav_uri_for_path (GVfsBackend *backend, + const char *path, +@@ -369,7 +352,7 @@ g_vfs_backend_dav_uri_for_path (GVfsBackend *backend, + else + new_path = g_build_path ("/", g_uri_get_path (mount_base), fn_encoded, "/", NULL); + +- uri = dav_uri_dup_with (mount_base, new_path, NULL); ++ uri = soup_uri_copy (mount_base, SOUP_URI_PATH, new_path, SOUP_URI_NONE); + + g_free (fn_encoded); + g_free (new_path); +@@ -432,7 +415,10 @@ g_vfs_backend_dav_redirect (SoupSession *session, + } + + tmp = new_uri; +- new_uri = dav_uri_dup_with (new_uri, NULL, g_uri_get_userinfo (old_uri)); ++ new_uri = soup_uri_copy (new_uri, ++ SOUP_URI_USER, g_uri_get_user (old_uri), ++ SOUP_URI_AUTH_PARAMS, g_uri_get_auth_params (old_uri), ++ SOUP_URI_NONE); + g_uri_unref (tmp); + + /* Check if this is a trailing slash redirect (i.e. /a/b to /a/b/), +@@ -2141,7 +2127,7 @@ do_mount (GVfsBackend *backend, + new_path = path_get_parent_dir (last_good_path); + + tmp = mount_base; +- mount_base = dav_uri_dup_with (mount_base, new_path, NULL); ++ mount_base = soup_uri_copy (mount_base, SOUP_URI_PATH, new_path, SOUP_URI_NONE); + g_uri_unref (tmp); + G_VFS_BACKEND_HTTP (backend)->mount_base = mount_base; + +@@ -2222,7 +2208,7 @@ do_mount (GVfsBackend *backend, + + /* Set the working path in mount path */ + tmp = mount_base; +- mount_base = dav_uri_dup_with (mount_base, last_good_path, NULL); ++ mount_base = soup_uri_copy (mount_base, SOUP_URI_PATH, last_good_path, SOUP_URI_NONE); + g_uri_unref (tmp); + G_VFS_BACKEND_HTTP (backend)->mount_base = mount_base; + g_free (last_good_path); +-- +2.35.1 + diff --git a/dav-Rewrite-to-libsoup-async-API-to-fix-crashes.patch b/dav-Rewrite-to-libsoup-async-API-to-fix-crashes.patch new file mode 100644 index 0000000..e498aeb --- /dev/null +++ b/dav-Rewrite-to-libsoup-async-API-to-fix-crashes.patch @@ -0,0 +1,2504 @@ +From 9203fad575515fba715fcfc0cc8b08d01ef11737 Mon Sep 17 00:00:00 2001 +From: Daniel Kolesa +Date: Sun, 27 Mar 2022 16:33:55 +0200 +Subject: [PATCH] dav: Rewrite to libsoup async API to fix crashes + +Since libsoup3 cannot deal with threads, we cannot use the do_ +methods which execute in a thread pool. However, we can implement +these in an async manner, which will bypass the thread pool and +get rid of the issue. + +The write methods are left synchronous as they deal with a memory +output stream and do not actually call libsoup. Therefore, we do +not have to care whether they are threaded or not. + +Fixes: https://gitlab.gnome.org/GNOME/gvfs/-/issues/609 +--- + daemon/gvfsbackenddav.c | 1958 ++++++++++++++++++++++++--------------- + 1 file changed, 1211 insertions(+), 747 deletions(-) + +diff --git a/daemon/gvfsbackenddav.c b/daemon/gvfsbackenddav.c +index 3ece9a5f..fd58b2c9 100644 +--- a/daemon/gvfsbackenddav.c ++++ b/daemon/gvfsbackenddav.c +@@ -110,6 +110,7 @@ struct _GVfsBackendDav + GVfsBackendHttp parent_instance; + + MountAuthData auth_info; ++ gchar *last_good_path; + + /* Used for user-verified secure connections. */ + GTlsCertificate *certificate; +@@ -378,13 +379,86 @@ g_vfs_backend_dav_stream_skip (GInputStream *stream, GError **error) + return TRUE; + } + +-/* redirection */ +-static GInputStream * +-g_vfs_backend_dav_redirect (SoupSession *session, +- SoupMessage *msg, +- GError **error) ++static void ++g_vfs_backend_dav_setup_display_name (GVfsBackend *backend) ++{ ++ GVfsBackendDav *dav_backend; ++ GUri *mount_base; ++ char *display_name; ++ char port[7] = {0, }; ++ gint gport; ++ ++ dav_backend = G_VFS_BACKEND_DAV (backend); ++ ++#ifdef HAVE_AVAHI ++ if (dav_backend->resolver != NULL) ++ { ++ const char *name; ++ name = g_vfs_dns_sd_resolver_get_service_name (dav_backend->resolver); ++ g_vfs_backend_set_display_name (backend, name); ++ return; ++ } ++#endif ++ ++ mount_base = http_backend_get_mount_base (backend); ++ ++ gport = g_uri_get_port (mount_base); ++ if ((gport > 0) && (gport != 80) && (gport != 443)) ++ g_snprintf (port, sizeof (port), ":%u", g_uri_get_port (mount_base)); ++ ++ if (g_uri_get_user (mount_base) != NULL) ++ /* Translators: This is the name of the WebDAV share constructed as ++ "WebDAV as on :"; the ":" part is ++ the second %s and only shown if it is not the default http(s) port. */ ++ display_name = g_strdup_printf (_("%s on %s%s"), ++ g_uri_get_user (mount_base), ++ g_uri_get_host (mount_base), ++ port); ++ else ++ display_name = g_strdup_printf ("%s%s", ++ g_uri_get_host (mount_base), ++ port); ++ ++ g_vfs_backend_set_display_name (backend, display_name); ++ g_free (display_name); ++} ++ ++static gboolean ++accept_certificate (SoupMessage *msg, ++ GTlsCertificate *certificate, ++ GTlsCertificateFlags errors, ++ gpointer user_data) ++{ ++ GVfsBackendDav *dav = G_VFS_BACKEND_DAV (user_data); ++ ++ return (errors == dav->certificate_errors && ++ g_tls_certificate_is_same (certificate, dav->certificate)); ++} ++ ++static void ++dav_message_connect_signals (SoupMessage *message, GVfsBackend *backend) ++{ ++ GVfsBackendDav *dav_backend = G_VFS_BACKEND_DAV (backend); ++ ++ /* we always have to connect this as the message can be ++ * re-sent with a differently set certificate_errors field ++ */ ++ g_signal_connect (message, "accept-certificate", ++ G_CALLBACK (accept_certificate), backend); ++ ++ g_signal_connect (message, "authenticate", ++ G_CALLBACK (soup_authenticate), ++ &dav_backend->auth_info); ++} ++ ++static void ++dav_send_async_with_redir_cb (GObject *source, GAsyncResult *ret, gpointer user_data) + { +- GInputStream *res; ++ SoupSession *session = SOUP_SESSION (source); ++ SoupMessage *msg = soup_session_get_async_result_message (session, ret); ++ GInputStream *body; ++ GError *error = NULL; ++ GTask *task = user_data; + const char *new_loc; + GUri *new_uri; + GUri *old_uri; +@@ -392,26 +466,28 @@ g_vfs_backend_dav_redirect (SoupSession *session, + guint status; + gboolean redirect; + +- res = soup_session_send (session, msg, NULL, error); +- if (!res) +- return NULL; ++ body = soup_session_send_finish (session, ret, &error); ++ ++ if (!body) ++ goto return_error; + + status = soup_message_get_status (msg); ++ + if (!SOUP_STATUS_IS_REDIRECTION (status)) +- return res; ++ goto return_body; + + new_loc = soup_message_headers_get_one (soup_message_get_response_headers (msg), + "Location"); + if (new_loc == NULL) +- return res; ++ goto return_body; + + old_uri = soup_message_get_uri (msg); + new_uri = g_uri_parse_relative (old_uri, new_loc, +- SOUP_HTTP_URI_FLAGS, error); ++ SOUP_HTTP_URI_FLAGS, &error); + if (new_uri == NULL) + { +- g_object_unref (res); +- return NULL; ++ g_object_unref (body); ++ goto return_error; + } + + tmp = new_uri; +@@ -475,125 +551,77 @@ g_vfs_backend_dav_redirect (SoupSession *session, + if (!redirect) + { + g_uri_unref (new_uri); +- return res; ++ goto return_body; + } + +- if (!g_vfs_backend_dav_stream_skip (res, error)) ++ if (!g_vfs_backend_dav_stream_skip (body, &error)) + { +- g_object_unref (res); +- return NULL; ++ g_object_unref (body); ++ goto return_error; + } + +- g_object_unref (res); ++ g_object_unref (body); + + soup_message_set_uri (msg, new_uri); ++ g_uri_unref (new_uri); + +- return g_vfs_backend_dav_redirect (session, msg, error); +-} +- +-static void +-g_vfs_backend_dav_setup_display_name (GVfsBackend *backend) +-{ +- GVfsBackendDav *dav_backend; +- GUri *mount_base; +- char *display_name; +- char port[7] = {0, }; +- gint gport; +- +- dav_backend = G_VFS_BACKEND_DAV (backend); +- +-#ifdef HAVE_AVAHI +- if (dav_backend->resolver != NULL) +- { +- const char *name; +- name = g_vfs_dns_sd_resolver_get_service_name (dav_backend->resolver); +- g_vfs_backend_set_display_name (backend, name); +- return; +- } +-#endif +- +- mount_base = http_backend_get_mount_base (backend); +- +- gport = g_uri_get_port (mount_base); +- if ((gport > 0) && (gport != 80) && (gport != 443)) +- g_snprintf (port, sizeof (port), ":%u", g_uri_get_port (mount_base)); +- +- if (g_uri_get_user (mount_base) != NULL) +- /* Translators: This is the name of the WebDAV share constructed as +- "WebDAV as on :"; the ":" part is +- the second %s and only shown if it is not the default http(s) port. */ +- display_name = g_strdup_printf (_("%s on %s%s"), +- g_uri_get_user (mount_base), +- g_uri_get_host (mount_base), +- port); +- else +- display_name = g_strdup_printf ("%s%s", +- g_uri_get_host (mount_base), +- port); ++ /* recurse */ ++ soup_session_send_async (session, msg, G_PRIORITY_DEFAULT, NULL, ++ dav_send_async_with_redir_cb, g_object_ref (task)); ++ goto return_done; + +- g_vfs_backend_set_display_name (backend, display_name); +- g_free (display_name); +-} ++return_body: ++ g_task_return_pointer (task, body, g_object_unref); ++ goto return_done; + +-static gboolean +-accept_certificate (SoupMessage *msg, +- GTlsCertificate *certificate, +- GTlsCertificateFlags errors, +- gpointer user_data) +-{ +- GVfsBackendDav *dav = G_VFS_BACKEND_DAV (user_data); ++return_error: ++ g_task_return_error (task, error); + +- return (errors == dav->certificate_errors && +- g_tls_certificate_is_same (certificate, dav->certificate)); ++return_done: ++ g_object_unref (task); + } + + static void +-dav_message_connect_signals (SoupMessage *message, GVfsBackend *backend) ++g_vfs_backend_dav_send_async (GVfsBackend *backend, ++ SoupMessage *message, ++ GAsyncReadyCallback callback, ++ gpointer user_data) + { +- GVfsBackendDav *dav_backend = G_VFS_BACKEND_DAV (backend); ++ SoupSession *session = G_VFS_BACKEND_HTTP (backend)->session; ++ GTask *task = g_task_new (backend, NULL, callback, user_data); + +- /* we always have to connect this as the message can be +- * re-sent with a differently set certificate_errors field +- */ +- g_signal_connect (message, "accept-certificate", +- G_CALLBACK (accept_certificate), backend); ++ g_task_set_source_tag (task, g_vfs_backend_dav_send_async); ++ g_task_set_task_data (task, g_object_ref (message), g_object_unref); + +- g_signal_connect (message, "authenticate", +- G_CALLBACK (soup_authenticate), +- &dav_backend->auth_info); ++ soup_message_set_flags (message, SOUP_MESSAGE_NO_REDIRECT); ++ ++ soup_session_send_async (session, message, G_PRIORITY_DEFAULT, NULL, ++ dav_send_async_with_redir_cb, task); + } + + static GInputStream * +-g_vfs_backend_dav_send (GVfsBackend *backend, +- SoupMessage *message, +- gboolean cb_connect, +- GError **error) ++g_vfs_backend_dav_send_finish (GVfsBackend *backend, ++ GAsyncResult *result, ++ GError **error) + { +- SoupSession *session = G_VFS_BACKEND_HTTP (backend)->session; +- +- /* We have our own custom redirect handler */ +- soup_message_set_flags (message, SOUP_MESSAGE_NO_REDIRECT); +- +- if (cb_connect) +- dav_message_connect_signals (message, backend); ++ g_return_val_if_fail (G_VFS_IS_BACKEND (backend), NULL); ++ g_return_val_if_fail (G_IS_ASYNC_RESULT (result), NULL); ++ g_return_val_if_fail (error == NULL || *error == NULL, NULL); ++ g_return_val_if_fail (g_task_is_valid (result, backend), NULL); ++ g_return_val_if_fail (g_async_result_is_tagged (result, g_vfs_backend_dav_send_async), NULL); + +- return g_vfs_backend_dav_redirect (session, message, error); ++ return g_task_propagate_pointer (G_TASK (result), error); + } + +-static void +-g_vfs_backend_dav_send_async (GVfsBackend *backend, +- SoupMessage *message, +- gboolean cb_connect, +- GAsyncReadyCallback callback, +- gpointer user_data) ++static SoupMessage * ++g_vfs_backend_dav_get_async_result_message (GVfsBackend *backend, ++ GAsyncResult *result) + { +- SoupSession *session = G_VFS_BACKEND_HTTP (backend)->session; +- +- if (cb_connect) +- dav_message_connect_signals (message, backend); ++ g_return_val_if_fail (G_VFS_IS_BACKEND (backend), NULL); ++ g_return_val_if_fail (G_IS_ASYNC_RESULT (result), NULL); ++ g_return_val_if_fail (g_task_is_valid (result, backend), NULL); + +- soup_session_send_async (session, message, G_PRIORITY_DEFAULT, NULL, +- callback, user_data); ++ return g_task_get_task_data (G_TASK (result)); + } + + /* ************************************************************************* */ +@@ -1365,8 +1393,8 @@ propfind_request_new (GVfsBackend *backend, + } + + static SoupMessage * +-stat_location_begin (GUri *uri, +- gboolean count_children) ++stat_location_start (GUri *uri, ++ gboolean count_children) + { + SoupMessage *msg; + const char *depth; +@@ -1406,11 +1434,11 @@ stat_location_begin (GUri *uri, + } + + static gboolean +-stat_location_finish (SoupMessage *msg, +- GInputStream *body, +- GFileType *target_type, +- gint64 *target_size, +- guint *num_children) ++stat_location_end (SoupMessage *msg, ++ GInputStream *body, ++ GFileType *target_type, ++ gint64 *target_size, ++ guint *num_children) + { + Multistatus ms; + xmlNodeIter iter; +@@ -1465,56 +1493,143 @@ stat_location_finish (SoupMessage *msg, + return res; + } + +-static gboolean +-stat_location (GVfsBackend *backend, +- GUri *uri, +- GFileType *target_type, +- gint64 *target_size, +- guint *num_children, +- GError **error) ++typedef struct _StatLocationData { ++ GUri *uri; ++ GFileType target_type; ++ gint64 target_size; ++ guint num_children; ++} StatLocationData; ++ ++static void ++stat_location_data_free (gpointer p) + { +- SoupMessage *msg; ++ g_uri_unref (((StatLocationData *)p)->uri); ++ g_slice_free (StatLocationData, p); ++} ++ ++static void ++stat_location_cb (GObject *source, GAsyncResult *result, gpointer user_data) ++{ ++ GVfsBackend *backend = G_VFS_BACKEND (source); ++ SoupMessage *msg = g_vfs_backend_dav_get_async_result_message (backend, result); ++ GTask *task = user_data; ++ StatLocationData *data = g_task_get_task_data (task); + GInputStream *body; ++ GError *error = NULL; + guint status; +- gboolean count_children; + gboolean res; + +- count_children = num_children != NULL; +- msg = stat_location_begin (uri, count_children); +- if (msg == NULL) +- return FALSE; +- +- body = g_vfs_backend_dav_send (backend, msg, TRUE, error); ++ body = g_vfs_backend_dav_send_finish (backend, result, &error); + + if (!body) + { + g_object_unref (msg); +- return FALSE; ++ g_task_return_error (task, error); ++ g_object_unref (task); ++ return; + } + + status = soup_message_get_status (msg); + if (status != SOUP_STATUS_MULTI_STATUS) + { +- g_set_error_literal (error, +- G_IO_ERROR, ++ error = g_error_new (G_IO_ERROR, + http_error_code_from_status (status), ++ _("HTTP Error: %s"), + soup_message_get_reason_phrase (msg)); + + g_object_unref (msg); ++ g_task_return_error (task, error); + g_object_unref (body); +- return FALSE; ++ g_object_unref (task); ++ return; + } + +- res = stat_location_finish (msg, body, target_type, target_size, num_children); ++ res = stat_location_end (msg, body, &data->target_type, ++ &data->target_size, &data->num_children); + g_object_unref (msg); + g_object_unref (body); + + if (res == FALSE) +- g_set_error_literal (error, +- G_IO_ERROR, G_IO_ERROR_FAILED, +- _("Response invalid")); ++ { ++ error = g_error_new (G_IO_ERROR, G_IO_ERROR_FAILED, ++ _("Response invalid")); ++ g_task_return_error (task, error); ++ } ++ else ++ g_task_return_boolean (task, TRUE); ++ ++ g_object_unref (task); ++} ++ ++static void ++stat_location_async (GVfsBackend *backend, ++ GUri *uri, ++ gboolean count_children, ++ GAsyncReadyCallback callback, ++ gpointer user_data) ++{ ++ GTask *task; ++ SoupMessage *msg; ++ StatLocationData *data; ++ ++ task = g_task_new (backend, NULL, callback, user_data); ++ data = g_slice_new (StatLocationData); ++ data->uri = g_uri_ref (uri); ++ ++ g_task_set_source_tag (task, stat_location_async); ++ g_task_set_task_data (task, data, stat_location_data_free); ++ ++ msg = stat_location_start (uri, count_children); ++ if (msg == NULL) ++ { ++ g_task_return_boolean (task, FALSE); ++ g_object_unref (task); ++ return; ++ } ++ ++ dav_message_connect_signals (msg, backend); ++ ++ g_vfs_backend_dav_send_async (backend, msg, stat_location_cb, task); ++} ++ ++static gboolean ++stat_location_finish (GVfsBackend *backend, ++ GFileType *target_type, ++ gint64 *target_size, ++ guint *num_children, ++ GAsyncResult *result, ++ GError **error) ++{ ++ g_return_val_if_fail (G_VFS_IS_BACKEND (backend), FALSE); ++ g_return_val_if_fail (G_IS_ASYNC_RESULT (result), FALSE); ++ g_return_val_if_fail (error == NULL || *error == NULL, FALSE); ++ g_return_val_if_fail (g_task_is_valid (result, backend), FALSE); ++ g_return_val_if_fail (g_async_result_is_tagged (result, stat_location_async), FALSE); ++ ++ if (g_task_propagate_boolean (G_TASK (result), error)) ++ { ++ StatLocationData *data = g_task_get_task_data (G_TASK (result)); ++ if (target_type) *target_type = data->target_type; ++ if (target_size) *target_size = data->target_size; ++ if (num_children) *num_children = data->num_children; ++ return TRUE; ++ } ++ ++ return FALSE; ++} ++ ++static GUri * ++stat_location_async_get_uri (GVfsBackend *backend, GAsyncResult *result) ++{ ++ StatLocationData *data; ++ ++ g_return_val_if_fail (G_VFS_IS_BACKEND (backend), NULL); ++ g_return_val_if_fail (G_IS_ASYNC_RESULT (result), NULL); ++ g_return_val_if_fail (g_task_is_valid (result, backend), NULL); ++ ++ data = g_task_get_task_data (G_TASK (result)); ++ return data->uri; + +- return res; + } + + /* ************************************************************************* */ +@@ -1930,305 +2045,374 @@ dns_sd_resolver_changed (GVfsDnsSdResolver *resolver, + + /* ************************************************************************* */ + /* Backend Functions */ ++ + static void +-do_mount (GVfsBackend *backend, +- GVfsJobMount *job, +- GMountSpec *mount_spec, +- GMountSource *mount_source, +- gboolean is_automount) ++mount_success (GVfsBackend *backend, GVfsJob *job) + { + GVfsBackendDav *dav_backend = G_VFS_BACKEND_DAV (backend); +- MountAuthData *data; +- SoupSession *session; +- SoupMessage *msg_opts; +- SoupMessage *msg_stat; +- GUri *mount_base; +- GUri *tmp; +- GError *error = NULL; +- guint status; +- gboolean is_success; +- gboolean is_webdav; +- gboolean is_collection; +- gboolean sig_opts = TRUE; +- gboolean sig_stat = TRUE; +- gboolean res; +- char *last_good_path; +- const char *host; +- const char *type; ++ GVfsBackendHttp *http_backend = G_VFS_BACKEND_HTTP (backend); ++ GMountSpec *mount_spec; ++ GUri *tmp; + +- g_debug ("+ mount\n"); ++ /* Save the auth info in the keyring */ ++ keyring_save_authinfo (&(dav_backend->auth_info.server_auth), http_backend->mount_base, FALSE); ++ /* TODO: save proxy auth */ + +- host = g_mount_spec_get (mount_spec, "host"); +- type = g_mount_spec_get (mount_spec, "type"); ++ /* Set the working path in mount path */ ++ tmp = http_backend->mount_base; ++ http_backend->mount_base = soup_uri_copy (tmp, SOUP_URI_PATH, ++ dav_backend->last_good_path, ++ SOUP_URI_NONE); ++ g_uri_unref (tmp); ++ g_clear_pointer (&dav_backend->last_good_path, g_free); + +-#ifdef HAVE_AVAHI +- /* resolve DNS-SD style URIs */ +- if ((strcmp (type, "dav+sd") == 0 || strcmp (type, "davs+sd") == 0) && host != NULL) +- { +- dav_backend->resolver = g_vfs_dns_sd_resolver_new_for_encoded_triple (host, "u"); ++ /* dup the mountspec, but only copy known fields */ ++ mount_spec = g_mount_spec_from_dav_uri (dav_backend, http_backend->mount_base); + +- if (!g_vfs_dns_sd_resolver_resolve_sync (dav_backend->resolver, +- NULL, +- &error)) +- { +- g_vfs_job_failed_from_error (G_VFS_JOB (job), error); +- g_error_free (error); +- return; +- } +- g_signal_connect (dav_backend->resolver, +- "changed", +- (GCallback) dns_sd_resolver_changed, +- dav_backend); ++ g_vfs_backend_set_mount_spec (backend, mount_spec); ++ g_vfs_backend_set_icon_name (backend, "folder-remote"); ++ g_vfs_backend_set_symbolic_icon_name (backend, "folder-remote-symbolic"); ++ ++ g_vfs_backend_dav_setup_display_name (backend); ++ ++ /* cleanup */ ++ g_mount_spec_unref (mount_spec); + +- mount_base = dav_uri_from_dns_sd_resolver (dav_backend); +- } +- else +-#endif ++ g_vfs_job_succeeded (G_VFS_JOB (job)); ++ g_debug ("- mount\n"); ++} ++ ++static void try_mount_opts_cb (GObject *source, GAsyncResult *result, gpointer user_data); ++ ++static void ++try_mount_stat_cb (GObject *source, GAsyncResult *result, gpointer user_data) ++{ ++ GVfsBackend *backend = G_VFS_BACKEND (source); ++ GVfsBackendDav *dav_backend = G_VFS_BACKEND_DAV (backend); ++ GVfsBackendHttp *http_backend = G_VFS_BACKEND_HTTP (backend); ++ GVfsJobMount *job = user_data; ++ SoupMessage *msg_stat = g_vfs_backend_dav_get_async_result_message (backend, result); ++ SoupMessage *msg_opts; ++ GInputStream *body; ++ GError *error = NULL; ++ GFileType file_type; ++ gboolean res; ++ gboolean is_collection; ++ GUri *tmp; ++ char *new_path; ++ ++ body = g_vfs_backend_dav_send_finish (backend, result, &error); ++ ++ if (body == NULL) + { +- mount_base = g_mount_spec_to_dav_uri (mount_spec); ++ g_vfs_job_failed_from_error (G_VFS_JOB (job), error); ++ g_error_free (error); ++ goto clear_msg; + } + +- if (mount_base == NULL) ++ res = stat_location_end (msg_stat, body, &file_type, NULL, NULL); ++ is_collection = res && file_type == G_FILE_TYPE_DIRECTORY; ++ ++ if (!g_vfs_backend_dav_stream_skip (body, &error)) + { +- g_vfs_job_failed (G_VFS_JOB (job), G_IO_ERROR, +- G_IO_ERROR_INVALID_ARGUMENT, +- _("Invalid mount spec")); +- return; ++ g_object_unref (body); ++ if (dav_backend->last_good_path == NULL) ++ g_vfs_job_failed_from_error (G_VFS_JOB (job), error); ++ else ++ mount_success (backend, G_VFS_JOB (job)); ++ g_error_free (error); ++ goto clear_msg; + } + +- session = G_VFS_BACKEND_HTTP (backend)->session; +- G_VFS_BACKEND_HTTP (backend)->mount_base = mount_base; ++ g_object_unref (body); + +- soup_session_add_feature_by_type (session, SOUP_TYPE_AUTH_NEGOTIATE); +- soup_session_add_feature_by_type (session, SOUP_TYPE_AUTH_NTLM); ++ soup_message_headers_clear (soup_message_get_response_headers (msg_stat)); + +- data = &(G_VFS_BACKEND_DAV (backend)->auth_info); +- data->mount_source = g_object_ref (mount_source); +- data->server_auth.username = g_strdup (g_uri_get_user (mount_base)); +- data->server_auth.pw_save = G_PASSWORD_SAVE_NEVER; +- data->proxy_auth.pw_save = G_PASSWORD_SAVE_NEVER; +- data->interactive = TRUE; ++ g_debug (" [%s] webdav: %d, collection %d [res: %d]\n", ++ g_uri_get_path (http_backend->mount_base), TRUE, is_collection, res); + +- last_good_path = NULL; +- msg_opts = soup_message_new_from_uri (SOUP_METHOD_OPTIONS, mount_base); ++ if ((is_collection == FALSE) && (dav_backend->last_good_path != NULL)) ++ { ++ mount_success (backend, G_VFS_JOB (job)); ++ goto clear_msg; ++ } ++ else if (res == FALSE) ++ { ++ int error_code = http_error_code_from_status (soup_message_get_status (msg_stat)); ++ g_vfs_job_failed (G_VFS_JOB (job), ++ G_IO_ERROR, error_code, ++ _("HTTP Error: %s"), ++ soup_message_get_reason_phrase (msg_stat)); ++ goto clear_msg; ++ } ++ else if (is_collection == FALSE) ++ { ++ g_vfs_job_failed (G_VFS_JOB (job), ++ G_IO_ERROR, G_IO_ERROR_FAILED, ++ _("Could not find an enclosing directory")); ++ goto clear_msg; ++ } + +- /* The count_children parameter is intentionally set to TRUE to be sure that +- enumeration is possible: https://gitlab.gnome.org/GNOME/gvfs/-/issues/468 */ +- msg_stat = stat_location_begin (mount_base, TRUE); +- +- do { +- GInputStream *body; +- GFileType file_type; +- GUri *cur_uri; +- char *new_path; +- +- res = TRUE; +- body = g_vfs_backend_dav_send (backend, msg_opts, sig_opts, &error); +- sig_opts = FALSE; +- status = body ? soup_message_get_status (msg_opts) : SOUP_STATUS_NONE; +- is_success = body && SOUP_STATUS_IS_SUCCESSFUL (status); +- is_webdav = sm_has_header (msg_opts, "DAV"); +- +- /* Workaround for servers which response with 403 instead of 401 in case of +- * wrong credentials to let the user specify its credentials again. */ +- if (status == SOUP_STATUS_FORBIDDEN && +- last_good_path == NULL && +- (data->server_auth.password != NULL || +- data->proxy_auth.password != NULL)) +- { +- SoupSessionFeature *auth_manager; ++ /* we have found a new good root, try the parent ... */ ++ g_free (dav_backend->last_good_path); ++ dav_backend->last_good_path = g_strdup (g_uri_get_path (http_backend->mount_base)); ++ new_path = path_get_parent_dir (dav_backend->last_good_path); + +- data->retrying_after_403 = TRUE; ++ tmp = http_backend->mount_base; ++ http_backend->mount_base = soup_uri_copy (tmp, SOUP_URI_PATH, new_path, SOUP_URI_NONE); ++ g_uri_unref (tmp); + +- g_clear_pointer (&data->server_auth.username, g_free); +- data->server_auth.username = g_strdup (g_uri_get_user (mount_base)); +- g_clear_pointer (&data->server_auth.password, g_free); +- g_clear_pointer (&data->proxy_auth.password, g_free); ++ g_free (new_path); + +- auth_manager = soup_session_get_feature (session, SOUP_TYPE_AUTH_MANAGER); +- soup_auth_manager_clear_cached_credentials (SOUP_AUTH_MANAGER (auth_manager)); ++ /* if we have found a root that is good then we assume ++ that we also have obtained to correct credentials ++ and we switch the auth handler. This will prevent us ++ from asking for *different* credentials *again* if the ++ server should response with 401 for some of the parent ++ collections. See also bug #677753 */ ++ dav_backend->auth_info.interactive = FALSE; + +- g_object_unref (msg_opts); +- msg_opts = soup_message_new_from_uri (SOUP_METHOD_OPTIONS, mount_base); ++ /* break out */ ++ if (g_strcmp0 (dav_backend->last_good_path, "/") == 0) ++ { ++ mount_success (backend, G_VFS_JOB (job)); ++ goto clear_msg; ++ } + +- continue; +- } ++ /* else loop the whole thing from options */ + +- /* If SSL is used and the certificate verifies OK, then ssl-strict remains +- * on for all further connections. +- * If SSL is used and the certificate does not verify OK, then the user +- * gets a chance to override it. If they do, ssl-strict is disabled but +- * the certificate is stored, and checked on each subsequent connection to +- * ensure that it hasn't changed. */ +- if (g_error_matches (error, G_TLS_ERROR, G_TLS_ERROR_BAD_CERTIFICATE) && +- !dav_backend->certificate_errors) +- { +- GTlsCertificate *certificate; +- GTlsCertificateFlags errors; +- +- certificate = soup_message_get_tls_peer_certificate (msg_opts); +- errors = soup_message_get_tls_peer_certificate_errors (msg_opts); +- if (gvfs_accept_certificate (mount_source, certificate, errors)) +- { +- g_clear_error (&error); +- dav_backend->certificate = g_object_ref (certificate); +- dav_backend->certificate_errors = errors; +- continue; +- } +- else +- { +- /* break the loop, if last_good_path is NULL then the error is +- * propagated, otherwise it is cleared later by the else branch +- */ +- break; +- } +- } ++ msg_opts = soup_message_new_from_uri (SOUP_METHOD_OPTIONS, ++ http_backend->mount_base); + +- if (!is_success || !is_webdav) +- break; ++ dav_message_connect_signals (msg_opts, backend); + +- if (!g_vfs_backend_dav_stream_skip (body, &error)) +- { +- g_object_unref (body); +- break; +- } ++ g_vfs_backend_dav_send_async (backend, msg_opts, try_mount_opts_cb, job); + +- g_object_unref (body); ++clear_msg: ++ g_object_unref (msg_stat); ++} + +- soup_message_headers_clear (soup_message_get_response_headers (msg_opts)); ++static void ++try_mount_opts_cb (GObject *source, GAsyncResult *result, gpointer user_data) ++{ ++ GVfsBackend *backend = G_VFS_BACKEND (source); ++ GVfsBackendDav *dav_backend = G_VFS_BACKEND_DAV (backend); ++ GVfsBackendHttp *http_backend = G_VFS_BACKEND_HTTP (backend); ++ GVfsJobMount *job = user_data; ++ SoupMessage *msg_opts = g_vfs_backend_dav_get_async_result_message (backend, result); ++ SoupMessage *msg_stat; ++ GInputStream *body; ++ GError *error = NULL; ++ GUri *cur_uri; ++ guint status; ++ gboolean is_success, is_webdav; ++ ++ body = g_vfs_backend_dav_send_finish (backend, result, &error); ++ ++ /* If SSL is used and the certificate verifies OK, then ssl-strict remains ++ * on for all further connections. ++ * If SSL is used and the certificate does not verify OK, then the user ++ * gets a chance to override it. If they do, ssl-strict is disabled but ++ * the certificate is stored, and checked on each subsequent connection to ++ * ensure that it hasn't changed. */ ++ if (g_error_matches (error, G_TLS_ERROR, G_TLS_ERROR_BAD_CERTIFICATE) && ++ !dav_backend->certificate_errors) ++ { ++ GTlsCertificate *certificate; ++ GTlsCertificateFlags errors; + +- cur_uri = soup_message_get_uri (msg_opts); +- soup_message_set_uri (msg_stat, cur_uri); ++ certificate = soup_message_get_tls_peer_certificate (msg_opts); ++ errors = soup_message_get_tls_peer_certificate_errors (msg_opts); ++ if (gvfs_accept_certificate (dav_backend->auth_info.mount_source, certificate, errors)) ++ { ++ g_clear_error (&error); ++ dav_backend->certificate = g_object_ref (certificate); ++ dav_backend->certificate_errors = errors; + +- body = g_vfs_backend_dav_send (backend, msg_stat, sig_stat, NULL); +- sig_stat = FALSE; +- res = stat_location_finish (msg_stat, body, &file_type, NULL, NULL); +- is_collection = res && file_type == G_FILE_TYPE_DIRECTORY; ++ /* re-send the opts message; re-create since we're still in its cb */ ++ g_object_unref (msg_opts); ++ msg_opts = soup_message_new_from_uri (SOUP_METHOD_OPTIONS, ++ http_backend->mount_base); + +- if (body && !g_vfs_backend_dav_stream_skip (body, &error)) +- { +- g_object_unref (body); +- break; +- } ++ dav_message_connect_signals (msg_opts, backend); + +- g_clear_object (&body); ++ g_vfs_backend_dav_send_async (backend, msg_opts, ++ try_mount_opts_cb, job); ++ return; ++ } ++ } + +- soup_message_headers_clear (soup_message_get_response_headers (msg_stat)); ++ /* message failed, propagate the error */ ++ if (!body) ++ { ++ if (dav_backend->last_good_path == NULL) ++ g_vfs_job_failed_from_error (G_VFS_JOB (job), error); ++ else ++ mount_success (backend, G_VFS_JOB (job)); ++ g_error_free (error); ++ goto clear_msgs; ++ } + +- g_debug (" [%s] webdav: %d, collection %d [res: %d]\n", +- g_uri_get_path (mount_base), is_webdav, is_collection, res); ++ status = soup_message_get_status (msg_opts); + +- if (is_collection == FALSE) +- break; ++ /* Workaround for servers which response with 403 instead of 401 in case of ++ * wrong credentials to let the user specify its credentials again. */ ++ if (status == SOUP_STATUS_FORBIDDEN && ++ dav_backend->last_good_path == NULL && ++ (dav_backend->auth_info.server_auth.password != NULL || ++ dav_backend->auth_info.proxy_auth.password != NULL)) ++ { ++ SoupSessionFeature *auth_manager; + +- /* we have found a new good root, try the parent ... */ +- g_free (last_good_path); +- last_good_path = g_strdup (g_uri_get_path (mount_base)); +- new_path = path_get_parent_dir (last_good_path); ++ dav_backend->auth_info.retrying_after_403 = TRUE; + +- tmp = mount_base; +- mount_base = soup_uri_copy (mount_base, SOUP_URI_PATH, new_path, SOUP_URI_NONE); +- g_uri_unref (tmp); +- G_VFS_BACKEND_HTTP (backend)->mount_base = mount_base; ++ g_clear_pointer (&dav_backend->auth_info.server_auth.username, g_free); ++ dav_backend->auth_info.server_auth.username = g_strdup (g_uri_get_user (http_backend->mount_base)); ++ g_clear_pointer (&dav_backend->auth_info.server_auth.password, g_free); ++ g_clear_pointer (&dav_backend->auth_info.proxy_auth.password, g_free); + +- g_free (new_path); ++ auth_manager = soup_session_get_feature (http_backend->session, SOUP_TYPE_AUTH_MANAGER); ++ soup_auth_manager_clear_cached_credentials (SOUP_AUTH_MANAGER (auth_manager)); + +- soup_message_set_uri (msg_opts, mount_base); ++ /* re-send the opts message; re-create since we're still in its cb */ ++ g_object_unref (msg_opts); ++ msg_opts = soup_message_new_from_uri (SOUP_METHOD_OPTIONS, ++ http_backend->mount_base); + +- /* if we have found a root that is good then we assume +- that we also have obtained to correct credentials +- and we switch the auth handler. This will prevent us +- from asking for *different* credentials *again* if the +- server should response with 401 for some of the parent +- collections. See also bug #677753 */ +- data->interactive = FALSE; ++ dav_message_connect_signals (msg_opts, backend); + +- } while (g_strcmp0 (last_good_path, "/") != 0); ++ g_vfs_backend_dav_send_async (backend, msg_opts, try_mount_opts_cb, job); ++ return; ++ } + +- /* we either encountered an error or we have +- reached the end of paths we are allowed to +- chdir up to (or couldn't chdir up at all) */ ++ is_success = SOUP_STATUS_IS_SUCCESSFUL (status); ++ is_webdav = sm_has_header (msg_opts, "DAV"); + +- /* check if we at all have a good path */ +- if (last_good_path == NULL) ++ if ((is_success && !is_webdav) || (status == SOUP_STATUS_METHOD_NOT_ALLOWED)) ++ { ++ if (dav_backend->last_good_path == NULL) ++ g_vfs_job_failed (G_VFS_JOB (job), ++ G_IO_ERROR, G_IO_ERROR_FAILED, ++ _("Not a WebDAV enabled share")); ++ else ++ mount_success (backend, G_VFS_JOB (job)); ++ goto clear_msgs; ++ } ++ else if (!is_success) + { +- if (error) ++ int error_code = http_error_code_from_status (soup_message_get_status (msg_opts)); ++ ++ if (dav_backend->last_good_path == NULL) ++ g_vfs_job_failed (G_VFS_JOB (job), ++ G_IO_ERROR, error_code, ++ _("HTTP Error: %s"), ++ soup_message_get_reason_phrase (msg_opts)); ++ else ++ mount_success (backend, G_VFS_JOB (job)); ++ goto clear_msgs; ++ } ++ ++ if (!g_vfs_backend_dav_stream_skip (body, &error)) ++ { ++ if (dav_backend->last_good_path == NULL) ++ g_vfs_job_failed_from_error (G_VFS_JOB (job), error); ++ else ++ mount_success (backend, G_VFS_JOB (job)); ++ g_error_free (error); ++ goto clear_msgs; ++ } ++ ++ g_object_unref (body); ++ ++ cur_uri = soup_message_get_uri (msg_opts); ++ ++ /* The count_children parameter is intentionally set to TRUE to be sure that ++ enumeration is possible: https://gitlab.gnome.org/GNOME/gvfs/-/issues/468 */ ++ msg_stat = stat_location_start (cur_uri, TRUE); ++ ++ dav_message_connect_signals (msg_stat, backend); ++ ++ g_vfs_backend_dav_send_async (backend, msg_stat, try_mount_stat_cb, job); ++ ++clear_msgs: ++ g_object_unref (msg_opts); ++} ++ ++static gboolean ++try_mount (GVfsBackend *backend, ++ GVfsJobMount *job, ++ GMountSpec *mount_spec, ++ GMountSource *mount_source, ++ gboolean is_automount) ++{ ++ GVfsBackendDav *dav_backend = G_VFS_BACKEND_DAV (backend); ++ GVfsBackendHttp *http_backend = G_VFS_BACKEND_HTTP (backend); ++ SoupMessage *msg_opts; ++ GUri *mount_base; ++ const char *host; ++ const char *type; ++ ++ g_debug ("+ mount\n"); ++ ++ host = g_mount_spec_get (mount_spec, "host"); ++ type = g_mount_spec_get (mount_spec, "type"); ++ ++#ifdef HAVE_AVAHI ++ /* resolve DNS-SD style URIs */ ++ if ((strcmp (type, "dav+sd") == 0 || strcmp (type, "davs+sd") == 0) && host != NULL) ++ { ++ GError *error = NULL; ++ dav_backend->resolver = g_vfs_dns_sd_resolver_new_for_encoded_triple (host, "u"); ++ ++ if (!g_vfs_dns_sd_resolver_resolve_sync (dav_backend->resolver, ++ NULL, ++ &error)) + { + g_vfs_job_failed_from_error (G_VFS_JOB (job), error); + g_error_free (error); ++ return TRUE; + } +- else if ((is_success && !is_webdav) || +- soup_message_get_status (msg_opts) == SOUP_STATUS_METHOD_NOT_ALLOWED) +- { +- /* This means the either: a) OPTIONS request succeeded +- (which should be the case even for non-existent +- resources on a webdav enabled share) but we did not +- get the DAV header. Or b) the OPTIONS request was a +- METHOD_NOT_ALLOWED (405). +- Prioritize this error messages, because it seems most +- useful to the user. */ +- g_vfs_job_failed (G_VFS_JOB (job), +- G_IO_ERROR, G_IO_ERROR_FAILED, +- _("Not a WebDAV enabled share")); +- } +- else if (!is_success || !res) +- { +- /* Either the OPTIONS request (is_success) or the PROPFIND +- request (res) failed. */ +- SoupMessage *target = !is_success ? msg_opts : msg_stat; +- int error_code = http_error_code_from_status (soup_message_get_status (target)); +- +- g_vfs_job_failed (G_VFS_JOB (job), +- G_IO_ERROR, error_code, +- _("HTTP Error: %s"), +- soup_message_get_reason_phrase (target)); +- } +- else +- { +- /* This means, we have a valid DAV header, PROPFIND worked, +- but it is not a collection! */ +- g_vfs_job_failed (G_VFS_JOB (job), +- G_IO_ERROR, G_IO_ERROR_FAILED, +- _("Could not find an enclosing directory")); +- } ++ g_signal_connect (dav_backend->resolver, ++ "changed", ++ (GCallback) dns_sd_resolver_changed, ++ dav_backend); + +- g_object_unref (msg_opts); +- g_object_unref (msg_stat); ++ mount_base = dav_uri_from_dns_sd_resolver (dav_backend); ++ } ++ else ++#endif ++ { ++ mount_base = g_mount_spec_to_dav_uri (mount_spec); ++ } + +- return; ++ if (mount_base == NULL) ++ { ++ g_vfs_job_failed (G_VFS_JOB (job), G_IO_ERROR, ++ G_IO_ERROR_INVALID_ARGUMENT, ++ _("Invalid mount spec")); ++ return TRUE; + } +- else if (error) +- g_error_free (error); + +- /* Success! We are mounted */ +- /* Save the auth info in the keyring */ ++ http_backend->mount_base = mount_base; + +- keyring_save_authinfo (&(data->server_auth), mount_base, FALSE); +- /* TODO: save proxy auth */ ++ soup_session_add_feature_by_type (http_backend->session, ++ SOUP_TYPE_AUTH_NEGOTIATE); ++ soup_session_add_feature_by_type (http_backend->session, ++ SOUP_TYPE_AUTH_NTLM); + +- /* Set the working path in mount path */ +- tmp = mount_base; +- mount_base = soup_uri_copy (mount_base, SOUP_URI_PATH, last_good_path, SOUP_URI_NONE); +- g_uri_unref (tmp); +- G_VFS_BACKEND_HTTP (backend)->mount_base = mount_base; +- g_free (last_good_path); ++ dav_backend->auth_info.mount_source = g_object_ref (mount_source); ++ dav_backend->auth_info.server_auth.username = g_strdup (g_uri_get_user (mount_base)); ++ dav_backend->auth_info.server_auth.pw_save = G_PASSWORD_SAVE_NEVER; ++ dav_backend->auth_info.proxy_auth.pw_save = G_PASSWORD_SAVE_NEVER; ++ dav_backend->auth_info.interactive = TRUE; + +- /* dup the mountspec, but only copy known fields */ +- mount_spec = g_mount_spec_from_dav_uri (dav_backend, mount_base); ++ dav_backend->last_good_path = NULL; + +- g_vfs_backend_set_mount_spec (backend, mount_spec); +- g_vfs_backend_set_icon_name (backend, "folder-remote"); +- g_vfs_backend_set_symbolic_icon_name (backend, "folder-remote-symbolic"); +- +- g_vfs_backend_dav_setup_display_name (backend); +- +- /* cleanup */ +- g_mount_spec_unref (mount_spec); +- g_object_unref (msg_opts); +- g_object_unref (msg_stat); ++ msg_opts = soup_message_new_from_uri (SOUP_METHOD_OPTIONS, mount_base); ++ dav_message_connect_signals (msg_opts, backend); + +- g_vfs_job_succeeded (G_VFS_JOB (job)); +- g_debug ("- mount\n"); ++ g_vfs_backend_dav_send_async (backend, msg_opts, try_mount_opts_cb, job); ++ return TRUE; + } + + static PropName ls_propnames[] = { +@@ -2244,34 +2428,18 @@ static PropName ls_propnames[] = { + + /* *** query_info () *** */ + static void +-do_query_info (GVfsBackend *backend, +- GVfsJobQueryInfo *job, +- const char *filename, +- GFileQueryInfoFlags flags, +- GFileInfo *info, +- GFileAttributeMatcher *matcher) ++try_query_info_cb (GObject *source, GAsyncResult *result, gpointer user_data) + { +- SoupMessage *msg; ++ GVfsBackend *backend = G_VFS_BACKEND (source); ++ SoupMessage *msg = g_vfs_backend_dav_get_async_result_message (backend, result); ++ GVfsJobQueryInfo *job = user_data; + GInputStream *body; ++ GError *error = NULL; + Multistatus ms; + xmlNodeIter iter; + gboolean res; +- GError *error = NULL; + +- g_debug ("Query info %s\n", filename); +- +- msg = propfind_request_new (backend, filename, 0, ls_propnames); +- if (msg == NULL) +- { +- g_vfs_job_failed (G_VFS_JOB (job), +- G_IO_ERROR, G_IO_ERROR_FAILED, +- _("Could not create request")); +- return; +- } +- +- message_add_redirect_header (msg, flags); +- +- body = g_vfs_backend_dav_send (backend, msg, TRUE, &error); ++ body = g_vfs_backend_dav_send_finish (backend, result, &error); + + if (!body) + goto error; +@@ -2318,6 +2486,36 @@ do_query_info (GVfsBackend *backend, + g_object_unref (msg); + } + ++static gboolean ++try_query_info (GVfsBackend *backend, ++ GVfsJobQueryInfo *job, ++ const char *filename, ++ GFileQueryInfoFlags flags, ++ GFileInfo *info, ++ GFileAttributeMatcher *matcher) ++{ ++ SoupMessage *msg; ++ ++ g_debug ("Query info %s\n", filename); ++ ++ msg = propfind_request_new (backend, filename, 0, ls_propnames); ++ if (msg == NULL) ++ { ++ g_vfs_job_failed (G_VFS_JOB (job), ++ G_IO_ERROR, G_IO_ERROR_FAILED, ++ _("Could not create request")); ++ return TRUE; ++ } ++ ++ message_add_redirect_header (msg, flags); ++ ++ dav_message_connect_signals (msg, backend); ++ ++ g_vfs_backend_dav_send_async (backend, msg, try_query_info_cb, job); ++ ++ return TRUE; ++} ++ + static PropName fs_info_propnames[] = { + {"quota-available-bytes", NULL}, + {"quota-used-bytes", NULL}, +@@ -2325,51 +2523,18 @@ static PropName fs_info_propnames[] = { + }; + + static void +-do_query_fs_info (GVfsBackend *backend, +- GVfsJobQueryFsInfo *job, +- const char *filename, +- GFileInfo *info, +- GFileAttributeMatcher *attribute_matcher) ++try_query_fs_info_cb (GObject *source, GAsyncResult *result, gpointer user_data) + { +- SoupMessage *msg; ++ GVfsBackend *backend = G_VFS_BACKEND (source); ++ SoupMessage *msg = g_vfs_backend_dav_get_async_result_message (backend, result); ++ GVfsJobQueryFsInfo *job = user_data; + GInputStream *body; ++ GError *error = NULL; + Multistatus ms; + xmlNodeIter iter; + gboolean res; +- GError *error = NULL; +- +- g_file_info_set_attribute_string (info, +- G_FILE_ATTRIBUTE_FILESYSTEM_TYPE, +- "webdav"); +- g_file_info_set_attribute_boolean (info, +- G_FILE_ATTRIBUTE_FILESYSTEM_REMOTE, +- TRUE); +- g_file_info_set_attribute_uint32 (info, +- G_FILE_ATTRIBUTE_FILESYSTEM_USE_PREVIEW, +- G_FILESYSTEM_PREVIEW_TYPE_IF_ALWAYS); + +- if (! (g_file_attribute_matcher_matches (attribute_matcher, +- G_FILE_ATTRIBUTE_FILESYSTEM_SIZE) || +- g_file_attribute_matcher_matches (attribute_matcher, +- G_FILE_ATTRIBUTE_FILESYSTEM_USED) || +- g_file_attribute_matcher_matches (attribute_matcher, +- G_FILE_ATTRIBUTE_FILESYSTEM_FREE))) +- { +- g_vfs_job_succeeded (G_VFS_JOB (job)); +- return; +- } +- +- msg = propfind_request_new (backend, filename, 0, fs_info_propnames); +- if (msg == NULL) +- { +- g_vfs_job_failed (G_VFS_JOB (job), +- G_IO_ERROR, G_IO_ERROR_FAILED, +- _("Could not create request")); +- +- return; +- } +- +- body = g_vfs_backend_dav_send (backend, msg, TRUE, &error); ++ body = g_vfs_backend_dav_send_finish (backend, result, &error); + + if (!body) + goto error; +@@ -2392,7 +2557,7 @@ do_query_fs_info (GVfsBackend *backend, + + if (response.is_target) + { +- ms_response_to_fs_info (&response, info); ++ ms_response_to_fs_info (&response, job->file_info); + res = TRUE; + } + +@@ -2416,36 +2581,67 @@ do_query_fs_info (GVfsBackend *backend, + g_object_unref (msg); + } + +-/* *** enumerate *** */ +-static void +-do_enumerate (GVfsBackend *backend, +- GVfsJobEnumerate *job, +- const char *filename, +- GFileAttributeMatcher *matcher, +- GFileQueryInfoFlags flags) ++static gboolean ++try_query_fs_info (GVfsBackend *backend, ++ GVfsJobQueryFsInfo *job, ++ const char *filename, ++ GFileInfo *info, ++ GFileAttributeMatcher *attribute_matcher) + { + SoupMessage *msg; +- GInputStream *body; +- Multistatus ms; +- xmlNodeIter iter; +- gboolean res; +- GError *error = NULL; + +- g_debug ("+ do_enumerate: %s\n", filename); ++ g_file_info_set_attribute_string (info, ++ G_FILE_ATTRIBUTE_FILESYSTEM_TYPE, ++ "webdav"); ++ g_file_info_set_attribute_boolean (info, ++ G_FILE_ATTRIBUTE_FILESYSTEM_REMOTE, ++ TRUE); ++ g_file_info_set_attribute_uint32 (info, ++ G_FILE_ATTRIBUTE_FILESYSTEM_USE_PREVIEW, ++ G_FILESYSTEM_PREVIEW_TYPE_IF_ALWAYS); + +- msg = propfind_request_new (backend, filename, 1, ls_propnames); ++ if (! (g_file_attribute_matcher_matches (attribute_matcher, ++ G_FILE_ATTRIBUTE_FILESYSTEM_SIZE) || ++ g_file_attribute_matcher_matches (attribute_matcher, ++ G_FILE_ATTRIBUTE_FILESYSTEM_USED) || ++ g_file_attribute_matcher_matches (attribute_matcher, ++ G_FILE_ATTRIBUTE_FILESYSTEM_FREE))) ++ { ++ g_vfs_job_succeeded (G_VFS_JOB (job)); ++ return TRUE; ++ } ++ ++ msg = propfind_request_new (backend, filename, 0, fs_info_propnames); + if (msg == NULL) + { + g_vfs_job_failed (G_VFS_JOB (job), + G_IO_ERROR, G_IO_ERROR_FAILED, + _("Could not create request")); +- +- return; ++ return TRUE; + } + +- message_add_redirect_header (msg, flags); ++ dav_message_connect_signals (msg, backend); ++ ++ g_vfs_backend_dav_send_async (backend, msg, try_query_fs_info_cb, job); ++ ++ return TRUE; ++} ++ ++/* *** enumerate *** */ ++ ++static void ++try_enumerate_cb (GObject *source, GAsyncResult *result, gpointer user_data) ++{ ++ GVfsBackend *backend = G_VFS_BACKEND (source); ++ SoupMessage *msg = g_vfs_backend_dav_get_async_result_message (backend, result); ++ GVfsJobEnumerate *job = user_data; ++ GInputStream *body; ++ GError *error = NULL; ++ Multistatus ms; ++ xmlNodeIter iter; ++ gboolean res; + +- body = g_vfs_backend_dav_send (backend, msg, TRUE, &error); ++ body = g_vfs_backend_dav_send_finish (backend, result, &error); + + if (!body) + goto error; +@@ -2491,6 +2687,34 @@ do_enumerate (GVfsBackend *backend, + g_object_unref (msg); + } + ++static gboolean ++try_enumerate (GVfsBackend *backend, ++ GVfsJobEnumerate *job, ++ const char *filename, ++ GFileAttributeMatcher *matcher, ++ GFileQueryInfoFlags flags) ++{ ++ SoupMessage *msg; ++ ++ g_debug ("+ try_enumerate: %s\n", filename); ++ ++ msg = propfind_request_new (backend, filename, 1, ls_propnames); ++ if (msg == NULL) ++ { ++ g_vfs_job_failed (G_VFS_JOB (job), ++ G_IO_ERROR, G_IO_ERROR_FAILED, ++ _("Could not create request")); ++ return TRUE; ++ } ++ ++ message_add_redirect_header (msg, flags); ++ dav_message_connect_signals (msg, backend); ++ ++ g_vfs_backend_dav_send_async (backend, msg, try_enumerate_cb, job); ++ ++ return TRUE; ++} ++ + /* ************************************************************************* */ + /* */ + +@@ -2525,7 +2749,7 @@ try_open_stat_done (GObject *source, + return; + } + +- res = stat_location_finish (msg, body, &target_type, NULL, NULL); ++ res = stat_location_end (msg, body, &target_type, NULL, NULL); + g_object_unref (body); + + if (res == FALSE) +@@ -2559,7 +2783,7 @@ try_open_for_read (GVfsBackend *backend, + GUri *uri; + + uri = g_vfs_backend_dav_uri_for_path (backend, filename, FALSE); +- msg = stat_location_begin (uri, FALSE); ++ msg = stat_location_start (uri, FALSE); + g_uri_unref (uri); + + if (msg == NULL) +@@ -2571,8 +2795,11 @@ try_open_for_read (GVfsBackend *backend, + return FALSE; + } + ++ dav_message_connect_signals (msg, backend); ++ + g_vfs_job_set_backend_data (G_VFS_JOB (job), backend, NULL); +- g_vfs_backend_dav_send_async (backend, msg, TRUE, try_open_stat_done, job); ++ soup_session_send_async (G_VFS_BACKEND_HTTP (backend)->session, msg, ++ G_PRIORITY_DEFAULT, NULL, try_open_stat_done, job); + + return TRUE; + } +@@ -2651,7 +2878,11 @@ try_create (GVfsBackend *backend, + + g_vfs_job_set_backend_data (G_VFS_JOB (job), msg, NULL); + +- g_vfs_backend_dav_send_async (backend, msg, TRUE, try_create_tested_existence, job); ++ dav_message_connect_signals (msg, backend); ++ ++ soup_session_send_async (G_VFS_BACKEND_HTTP (backend)->session, msg, ++ G_PRIORITY_DEFAULT, NULL, ++ try_create_tested_existence, job); + + return TRUE; + } +@@ -2760,12 +2991,14 @@ try_replace (GVfsBackend *backend, + msg = soup_message_new_from_uri (SOUP_METHOD_HEAD, uri); + g_uri_unref (uri); + ++ dav_message_connect_signals (msg, backend); ++ + soup_message_headers_append (soup_message_get_request_headers (msg), + "If-Match", etag); + + g_vfs_job_set_backend_data (G_VFS_JOB (job), op_backend, NULL); +- g_vfs_backend_dav_send_async (backend, msg, TRUE, +- try_replace_checked_etag, job); ++ soup_session_send_async (op_backend->session, msg, G_PRIORITY_DEFAULT, ++ NULL, try_replace_checked_etag, job); + + return TRUE; + } +@@ -2829,6 +3062,7 @@ try_write (GVfsBackend *backend, + return TRUE; + } + ++/* this does not invoke libsoup API, so it can be synchronous/threaded */ + static void + do_seek_on_write (GVfsBackend *backend, + GVfsJobSeekWrite *job, +@@ -2851,6 +3085,7 @@ do_seek_on_write (GVfsBackend *backend, + } + } + ++/* this does not invoke libsoup API, so it can be synchronous/threaded */ + static void + do_truncate (GVfsBackend *backend, + GVfsJobTruncate *job, +@@ -2914,34 +3149,32 @@ try_close_write (GVfsBackend *backend, + g_object_ref (msg); + g_object_set_data (G_OBJECT (stream), "-gvfs-stream-msg", NULL); + ++ dav_message_connect_signals (msg, backend); ++ + g_output_stream_close (stream, NULL, NULL); + bytes = g_memory_output_stream_steal_as_bytes (G_MEMORY_OUTPUT_STREAM (stream)); + g_object_unref (stream); + + soup_message_set_request_body_from_bytes (msg, NULL, bytes); +- g_vfs_backend_dav_send_async (backend, msg, TRUE, +- try_close_write_sent, job); ++ soup_session_send_async (G_VFS_BACKEND_HTTP (backend)->session, msg, ++ G_PRIORITY_DEFAULT, NULL, try_close_write_sent, job); + g_bytes_unref (bytes); + + return TRUE; + } + + static void +-do_make_directory (GVfsBackend *backend, +- GVfsJobMakeDirectory *job, +- const char *filename) ++make_directory_cb (GObject *source, GAsyncResult *result, gpointer user_data) + { ++ GVfsBackend *backend = G_VFS_BACKEND (source); ++ GVfsJobMakeDirectory *job = user_data; ++ SoupMessage *msg = g_vfs_backend_dav_get_async_result_message (backend, result); + GInputStream *body; +- SoupMessage *msg; +- GUri *uri; + GError *error = NULL; + guint status; + +- uri = g_vfs_backend_dav_uri_for_path (backend, filename, TRUE); +- msg = soup_message_new_from_uri (SOUP_METHOD_MKCOL, uri); +- g_uri_unref (uri); ++ body = g_vfs_backend_dav_send_finish (backend, result, &error); + +- body = g_vfs_backend_dav_send (backend, msg, TRUE, &error); + if (!body) + { + g_vfs_job_failed_from_error (G_VFS_JOB (job), error); +@@ -2966,27 +3199,72 @@ do_make_directory (GVfsBackend *backend, + g_object_unref (msg); + } + +-static void +-do_delete (GVfsBackend *backend, +- GVfsJobDelete *job, +- const char *filename) ++static gboolean ++try_make_directory (GVfsBackend *backend, ++ GVfsJobMakeDirectory *job, ++ const char *filename) + { +- GInputStream *body; + SoupMessage *msg; + GUri *uri; ++ ++ uri = g_vfs_backend_dav_uri_for_path (backend, filename, TRUE); ++ msg = soup_message_new_from_uri (SOUP_METHOD_MKCOL, uri); ++ g_uri_unref (uri); ++ ++ dav_message_connect_signals (msg, backend); ++ ++ g_vfs_backend_dav_send_async (backend, msg, make_directory_cb, job); ++ return TRUE; ++} ++ ++static void ++try_delete_send_cb (GObject *source, GAsyncResult *result, gpointer user_data) ++{ ++ GVfsBackend *backend = G_VFS_BACKEND (source); ++ GVfsJobDelete *job = user_data; ++ SoupMessage *msg = g_vfs_backend_dav_get_async_result_message (backend, result); ++ GInputStream *body; ++ GError *error = NULL; ++ guint status; ++ ++ body = g_vfs_backend_dav_send_finish (backend, result, &error); ++ ++ if (!body) ++ { ++ g_vfs_job_failed_from_error (G_VFS_JOB (job), error); ++ g_error_free (error); ++ g_object_unref (msg); ++ return; ++ } ++ ++ status = soup_message_get_status (msg); ++ if (!SOUP_STATUS_IS_SUCCESSFUL (status)) ++ http_job_failed (G_VFS_JOB (job), msg); ++ else ++ g_vfs_job_succeeded (G_VFS_JOB (job)); ++ ++ g_object_unref (msg); ++ g_object_unref (body); ++} ++ ++static void ++try_delete_cb (GObject *source, GAsyncResult *result, gpointer user_data) ++{ ++ GVfsBackend *backend = G_VFS_BACKEND (source); ++ GVfsJobDelete *job = user_data; ++ SoupMessage *msg = NULL; + GFileType file_type; +- gboolean res; + guint num_children; +- guint status; + GError *error = NULL; ++ gboolean res; ++ GUri *uri = stat_location_async_get_uri (backend, result); + +- uri = g_vfs_backend_dav_uri_for_path (backend, filename, FALSE); +- res = stat_location (backend, uri, &file_type, NULL, &num_children, &error); ++ res = stat_location_finish (backend, &file_type, NULL, ++ &num_children, result, &error); + if (res == FALSE) + { + g_vfs_job_failed_from_error (G_VFS_JOB (job), error); +- g_error_free (error); +- g_uri_unref (uri); ++ g_clear_error (&error); + return; + } + +@@ -2995,377 +3273,559 @@ do_delete (GVfsBackend *backend, + g_vfs_job_failed (G_VFS_JOB (job), + G_IO_ERROR, G_IO_ERROR_NOT_EMPTY, + _("Directory not empty")); +- g_uri_unref (uri); + return; + } + + msg = soup_message_new_from_uri (SOUP_METHOD_DELETE, uri); +- body = g_vfs_backend_dav_send (backend, msg, TRUE, &error); ++ ++ dav_message_connect_signals (msg, backend); ++ ++ g_vfs_backend_dav_send_async (backend, msg, try_delete_send_cb, job); ++} ++ ++static gboolean ++try_delete (GVfsBackend *backend, GVfsJobDelete *job, const char *filename) ++{ ++ GUri *uri; ++ ++ uri = g_vfs_backend_dav_uri_for_path (backend, filename, FALSE); ++ stat_location_async (backend, uri, TRUE, try_delete_cb, job); ++ return TRUE; ++} ++ ++typedef struct _TrySetDisplayNameData { ++ GVfsJobSetDisplayName *job; ++ char *target_path; ++} TrySetDisplayNameData; ++ ++static void ++try_set_display_name_cb (GObject *source, GAsyncResult *result, ++ gpointer user_data) ++{ ++ GVfsBackend *backend = G_VFS_BACKEND (source); ++ TrySetDisplayNameData *data = user_data; ++ SoupMessage *msg = g_vfs_backend_dav_get_async_result_message (backend, result); ++ GInputStream *body; ++ GError *error = NULL; ++ guint status; ++ ++ body = g_vfs_backend_dav_send_finish (backend, result, &error); ++ + if (!body) + { +- g_vfs_job_failed_from_error (G_VFS_JOB (job), error); +- g_error_free (error); +- g_object_unref (msg); +- g_uri_unref (uri); +- return; ++ http_job_failed (G_VFS_JOB (data->job), msg); ++ goto error; + } + + status = soup_message_get_status (msg); +- if (!SOUP_STATUS_IS_SUCCESSFUL (status)) +- http_job_failed (G_VFS_JOB (job), msg); ++ ++ /* ++ * The precondition of SOUP_STATUS_PRECONDITION_FAILED (412) in ++ * this case was triggered by the "Overwrite: F" header which ++ * means that the target already exists. ++ * Also if we get a REDIRECTION it means that there was no ++ * "Location" header, since otherwise that would have triggered ++ * our redirection handler. This probably means we are dealing ++ * with an web dav implementation (like mod_dav) that also sends ++ * redirects for the destionaion (i.e. "Destination: /foo" header) ++ * which very likely means that the target also exists (and is a ++ * directory). That or the webdav server is broken. ++ * We could find out by doing another stat and but I think this is ++ * such a corner case that we are totally fine with returning ++ * G_IO_ERROR_EXISTS. ++ * */ ++ ++ if (SOUP_STATUS_IS_SUCCESSFUL (status)) ++ { ++ g_debug ("new target_path: %s\n", data->target_path); ++ g_vfs_job_set_display_name_set_new_path (data->job, data->target_path); ++ g_vfs_job_succeeded (G_VFS_JOB (data->job)); ++ } ++ else if (status == SOUP_STATUS_PRECONDITION_FAILED || ++ SOUP_STATUS_IS_REDIRECTION (status)) ++ g_vfs_job_failed (G_VFS_JOB (data->job), G_IO_ERROR, ++ G_IO_ERROR_EXISTS, ++ _("Target file already exists")); + else +- g_vfs_job_succeeded (G_VFS_JOB (job)); ++ http_job_failed (G_VFS_JOB (data->job), msg); + +- g_uri_unref (uri); +- g_object_unref (msg); + g_object_unref (body); ++ ++error: ++ g_clear_error (&error); ++ g_object_unref (msg); ++ g_free (data->target_path); ++ g_slice_free (TrySetDisplayNameData, data); ++} ++ ++static gboolean ++try_set_display_name (GVfsBackend *backend, ++ GVfsJobSetDisplayName *job, ++ const char *filename, ++ const char *display_name) ++{ ++ SoupMessage *msg; ++ GUri *source; ++ GUri *target; ++ TrySetDisplayNameData *data = g_slice_new (TrySetDisplayNameData); ++ char *dirname; ++ ++ source = g_vfs_backend_dav_uri_for_path (backend, filename, FALSE); ++ msg = soup_message_new_from_uri (SOUP_METHOD_MOVE, source); ++ ++ dirname = g_path_get_dirname (filename); ++ data->target_path = g_build_filename (dirname, display_name, NULL); ++ target = g_vfs_backend_dav_uri_for_path (backend, data->target_path, FALSE); ++ ++ message_add_destination_header (msg, target); ++ message_add_overwrite_header (msg, FALSE); ++ ++ data->job = job; ++ g_free (dirname); ++ g_uri_unref (target); ++ g_uri_unref (source); ++ ++ dav_message_connect_signals (msg, backend); ++ ++ g_vfs_backend_dav_send_async (backend, msg, try_set_display_name_cb, data); ++ ++ return TRUE; ++} ++ ++typedef struct _CopyData { ++ GVfsJob *job; ++ SoupMessage *msg; ++ GUri *source_uri; ++ GUri *target_uri; ++ GFileProgressCallback progress_callback; ++ gpointer progress_callback_data; ++ gint64 file_size; ++ GFileType source_ft; ++ GFileType target_ft; ++ GFileCopyFlags flags; ++ gboolean source_res; ++ gboolean target_res; ++} CopyData; ++ ++static void ++copy_data_free (gpointer data) ++{ ++ CopyData *p = data; ++ g_clear_pointer (&p->source_uri, g_uri_unref); ++ g_clear_pointer (&p->target_uri, g_uri_unref); ++ g_clear_object (&p->msg); ++ g_slice_free (CopyData, p); + } + + static void +-do_set_display_name (GVfsBackend *backend, +- GVfsJobSetDisplayName *job, +- const char *filename, +- const char *display_name) ++try_move_do_cb (GObject *source, GAsyncResult *result, gpointer user_data) + { ++ GVfsBackend *backend = G_VFS_BACKEND (source); ++ CopyData *data = user_data; + GInputStream *body; +- SoupMessage *msg; +- GUri *source; +- GUri *target; +- char *target_path; +- char *dirname; + GError *error = NULL; + guint status; + +- source = g_vfs_backend_dav_uri_for_path (backend, filename, FALSE); +- msg = soup_message_new_from_uri (SOUP_METHOD_MOVE, source); +- +- dirname = g_path_get_dirname (filename); +- target_path = g_build_filename (dirname, display_name, NULL); +- target = g_vfs_backend_dav_uri_for_path (backend, target_path, FALSE); +- +- message_add_destination_header (msg, target); +- message_add_overwrite_header (msg, FALSE); ++ body = g_vfs_backend_dav_send_finish (backend, result, &error); + +- body = g_vfs_backend_dav_send (backend, msg, TRUE, &error); + if (!body) + { +- http_job_failed (G_VFS_JOB (job), msg); +- goto error; ++ g_vfs_job_failed_from_error (data->job, error); ++ copy_data_free (data); ++ g_clear_error (&error); ++ return; + } + +- status = soup_message_get_status (msg); +- +- /* +- * The precondition of SOUP_STATUS_PRECONDITION_FAILED (412) in +- * this case was triggered by the "Overwrite: F" header which +- * means that the target already exists. +- * Also if we get a REDIRECTION it means that there was no +- * "Location" header, since otherwise that would have triggered +- * our redirection handler. This probably means we are dealing +- * with an web dav implementation (like mod_dav) that also sends +- * redirects for the destionaion (i.e. "Destination: /foo" header) +- * which very likely means that the target also exists (and is a +- * directory). That or the webdav server is broken. +- * We could find out by doing another stat and but I think this is +- * such a corner case that we are totally fine with returning +- * G_IO_ERROR_EXISTS. +- * */ ++ /* See try_set_display_name () for the explanation of the PRECONDITION_FAILED ++ * and IS_REDIRECTION handling below. */ ++ status = soup_message_get_status (data->msg); + + if (SOUP_STATUS_IS_SUCCESSFUL (status)) + { +- g_debug ("new target_path: %s\n", target_path); +- g_vfs_job_set_display_name_set_new_path (job, target_path); +- g_vfs_job_succeeded (G_VFS_JOB (job)); ++ if (data->source_res && data->progress_callback) ++ data->progress_callback (data->file_size, data->file_size, ++ data->progress_callback_data); ++ g_vfs_job_succeeded (data->job); + } + else if (status == SOUP_STATUS_PRECONDITION_FAILED || + SOUP_STATUS_IS_REDIRECTION (status)) +- g_vfs_job_failed (G_VFS_JOB (job), G_IO_ERROR, ++ g_vfs_job_failed (data->job, G_IO_ERROR, + G_IO_ERROR_EXISTS, + _("Target file already exists")); + else +- http_job_failed (G_VFS_JOB (job), msg); ++ http_job_failed (data->job, data->msg); + +- g_object_unref (body); ++ copy_data_free (data); ++} + +- error: +- g_object_unref (msg); +- g_free (dirname); +- g_free (target_path); +- g_uri_unref (target); +- g_uri_unref (source); ++static void ++try_move_do (GVfsBackend *backend, CopyData *data) ++{ ++ message_add_destination_header (data->msg, data->target_uri); ++ message_add_overwrite_header (data->msg, data->flags & G_FILE_COPY_OVERWRITE); ++ g_vfs_backend_dav_send_async (backend, data->msg, try_move_do_cb, data); + } + + static void +-do_move (GVfsBackend *backend, +- GVfsJobMove *job, +- const char *source, +- const char *destination, +- GFileCopyFlags flags, +- GFileProgressCallback progress_callback, +- gpointer progress_callback_data) +-{ +- GInputStream *body = NULL; +- SoupMessage *msg; +- GUri *source_uri; +- GUri *target_uri; +- guint status; +- GFileType source_ft, target_ft; ++try_move_target_delete_cb (GObject *source, GAsyncResult *result, ++ gpointer user_data) ++{ ++ GVfsBackend *backend = G_VFS_BACKEND (source); ++ SoupMessage *msg = g_vfs_backend_dav_get_async_result_message (backend, result); ++ CopyData *data = user_data; ++ GInputStream *body; + GError *error = NULL; +- gboolean res, stat_res; +- gint64 file_size; ++ guint status; + +- if (flags & G_FILE_COPY_BACKUP) +- { +- if (flags & G_FILE_COPY_NO_FALLBACK_FOR_MOVE) +- { +- g_vfs_job_failed_literal (G_VFS_JOB (job), +- G_IO_ERROR, +- G_IO_ERROR_CANT_CREATE_BACKUP, +- _("Backups not supported")); +- } +- else +- { +- /* Return G_IO_ERROR_NOT_SUPPORTED instead of G_IO_ERROR_CANT_CREATE_BACKUP +- * to be proceeded with copy and delete fallback (see g_file_move). */ +- g_vfs_job_failed_literal (G_VFS_JOB (job), +- G_IO_ERROR, +- G_IO_ERROR_NOT_SUPPORTED, +- "Operation not supported"); +- } ++ body = g_vfs_backend_dav_send_finish (backend, result, &error); + ++ if (!body) ++ { ++ g_vfs_job_failed_from_error (data->job, error); ++ copy_data_free (data); ++ g_object_unref (msg); ++ g_clear_error (&error); + return; + } + +- source_uri = g_vfs_backend_dav_uri_for_path (backend, source, FALSE); +- msg = soup_message_new_from_uri (SOUP_METHOD_MOVE, source_uri); +- target_uri = g_vfs_backend_dav_uri_for_path (backend, destination, FALSE); +- +- res = stat_location (backend, target_uri, &target_ft, NULL, NULL, &error); +- if (!res && !g_error_matches (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND)) ++ status = soup_message_get_status (msg); ++ if (!SOUP_STATUS_IS_SUCCESSFUL (status)) + { +- g_vfs_job_failed_from_error (G_VFS_JOB (job), error); +- goto error; ++ http_job_failed (data->job, msg); ++ g_object_unref (msg); ++ copy_data_free (data); ++ return; + } +- g_clear_error (&error); + +- stat_res = stat_location (backend, source_uri, &source_ft, &file_size, NULL, &error); +- if (res) ++ g_object_unref (body); ++ g_object_unref (msg); ++ ++ try_move_do (backend, data); ++} ++ ++static void ++try_move_source_stat_cb (GObject *source, GAsyncResult *result, ++ gpointer user_data) ++{ ++ GVfsBackend *backend = G_VFS_BACKEND (source); ++ CopyData *data = user_data; ++ GError *error = NULL; ++ ++ data->source_res = stat_location_finish (backend, &data->source_ft, ++ &data->file_size, NULL, ++ result, &error); ++ ++ if (data->target_res) + { +- if (flags & G_FILE_COPY_OVERWRITE) ++ if (data->flags & G_FILE_COPY_OVERWRITE) + { +- if (stat_res) ++ if (data->source_res) + { +- if (target_ft == G_FILE_TYPE_DIRECTORY) ++ if (data->target_ft == G_FILE_TYPE_DIRECTORY) + { +- if (source_ft == G_FILE_TYPE_DIRECTORY) +- g_vfs_job_failed_literal (G_VFS_JOB(job), ++ if (data->source_ft == G_FILE_TYPE_DIRECTORY) ++ g_vfs_job_failed_literal (G_VFS_JOB(data->job), + G_IO_ERROR, + G_IO_ERROR_WOULD_MERGE, + _("Can’t move directory over directory")); + else +- g_vfs_job_failed_literal (G_VFS_JOB(job), ++ g_vfs_job_failed_literal (G_VFS_JOB(data->job), + G_IO_ERROR, + G_IO_ERROR_IS_DIRECTORY, + _("Can’t move over directory")); +- goto error; ++ copy_data_free (data); ++ return; + } +- else if (source_ft == G_FILE_TYPE_DIRECTORY) ++ else if (data->source_ft == G_FILE_TYPE_DIRECTORY) + { + /* Overwriting a file with a directory, first remove the + * file */ + SoupMessage *msg; + + msg = soup_message_new_from_uri (SOUP_METHOD_DELETE, +- target_uri); +- body = g_vfs_backend_dav_send (backend, msg, TRUE, &error); +- if (!body) +- { +- g_vfs_job_failed_from_error (G_VFS_JOB (job), error); +- goto error; +- } +- +- status = soup_message_get_status (msg); +- if (!SOUP_STATUS_IS_SUCCESSFUL (status)) +- { +- http_job_failed (G_VFS_JOB (job), msg); +- goto error; +- } +- g_object_unref (body); +- g_object_unref (msg); ++ data->target_uri); ++ ++ dav_message_connect_signals (msg, backend); ++ ++ g_vfs_backend_dav_send_async (backend, msg, ++ try_move_target_delete_cb, ++ data); ++ return; + } + } + else + { +- g_vfs_job_failed_from_error (G_VFS_JOB (job), error); +- goto error; ++ g_vfs_job_failed_from_error (data->job, error); ++ copy_data_free (data); ++ g_clear_error (&error); ++ return; + } + } + else + { +- g_vfs_job_failed_literal (G_VFS_JOB(job), ++ g_vfs_job_failed_literal (G_VFS_JOB(data->job), + G_IO_ERROR, + G_IO_ERROR_EXISTS, + _("Target file exists")); +- goto error; ++ copy_data_free (data); ++ g_clear_error (&error); ++ return; ++ } ++ } ++ ++ try_move_do (backend, data); ++} ++ ++static void ++try_move_target_stat_cb (GObject *source, GAsyncResult *result, ++ gpointer user_data) ++{ ++ GVfsBackend *backend = G_VFS_BACKEND (source); ++ CopyData *data = user_data; ++ gboolean res; ++ GError *error = NULL; ++ ++ res = stat_location_finish (backend, &data->target_ft, NULL, NULL, ++ result, &error); ++ ++ if (!res && !g_error_matches (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND)) ++ { ++ g_vfs_job_failed_from_error (data->job, error); ++ g_clear_error (&error); ++ copy_data_free (data); ++ return; ++ } ++ ++ g_clear_error (&error); ++ ++ data->target_res = res; ++ ++ stat_location_async (backend, data->source_uri, FALSE, ++ try_move_source_stat_cb, data); ++} ++ ++static gboolean ++try_move (GVfsBackend *backend, ++ GVfsJobMove *job, ++ const char *source, ++ const char *destination, ++ GFileCopyFlags flags, ++ GFileProgressCallback progress_callback, ++ gpointer progress_callback_data) ++{ ++ CopyData *data = g_slice_new0 (CopyData); ++ data->job = G_VFS_JOB (job); ++ data->flags = flags; ++ data->progress_callback = progress_callback; ++ data->progress_callback_data = progress_callback_data; ++ ++ if (flags & G_FILE_COPY_BACKUP) ++ { ++ if (flags & G_FILE_COPY_NO_FALLBACK_FOR_MOVE) ++ { ++ g_vfs_job_failed_literal (G_VFS_JOB (job), ++ G_IO_ERROR, ++ G_IO_ERROR_CANT_CREATE_BACKUP, ++ _("Backups not supported")); ++ } ++ else ++ { ++ /* Return G_IO_ERROR_NOT_SUPPORTED instead of G_IO_ERROR_CANT_CREATE_BACKUP ++ * to be proceeded with copy and delete fallback (see g_file_move). */ ++ g_vfs_job_failed_literal (G_VFS_JOB (job), ++ G_IO_ERROR, ++ G_IO_ERROR_NOT_SUPPORTED, ++ "Operation not supported"); + } ++ ++ copy_data_free (data); ++ return TRUE; + } + +- message_add_destination_header (msg, target_uri); +- message_add_overwrite_header (msg, flags & G_FILE_COPY_OVERWRITE); ++ data->source_uri = g_vfs_backend_dav_uri_for_path (backend, source, FALSE); ++ data->msg = soup_message_new_from_uri (SOUP_METHOD_MOVE, data->source_uri); ++ data->target_uri = g_vfs_backend_dav_uri_for_path (backend, destination, FALSE); ++ ++ dav_message_connect_signals (data->msg, backend); ++ ++ stat_location_async (backend, data->target_uri, FALSE, ++ try_move_target_stat_cb, data); ++ return TRUE; ++} ++ ++static void ++try_copy_do_cb (GObject *source, GAsyncResult *result, gpointer user_data) ++{ ++ GVfsBackend *backend = G_VFS_BACKEND (source); ++ CopyData *data = user_data; ++ GInputStream *body; ++ GError *error = NULL; ++ guint status; ++ ++ body = g_vfs_backend_dav_send_finish (backend, result, &error); + +- body = g_vfs_backend_dav_send (backend, msg, TRUE, &error); + if (!body) + { +- g_vfs_job_failed_from_error (G_VFS_JOB (job), error); +- goto error; ++ g_vfs_job_failed_from_error (data->job, error); ++ copy_data_free (data); ++ return; + } + +- /* See do_set_display_name () for the explanation of the PRECONDITION_FAILED ++ /* See try_set_display_name () for the explanation of the PRECONDITION_FAILED + * and IS_REDIRECTION handling below. */ +- status = soup_message_get_status (msg); ++ status = soup_message_get_status (data->msg); + if (SOUP_STATUS_IS_SUCCESSFUL (status)) + { +- if (stat_res && progress_callback) +- progress_callback (file_size, file_size, progress_callback_data); +- g_vfs_job_succeeded (G_VFS_JOB (job)); ++ if (data->progress_callback) ++ data->progress_callback (data->file_size, data->file_size, ++ data->progress_callback_data); ++ g_vfs_job_succeeded (data->job); + } + else if (status == SOUP_STATUS_PRECONDITION_FAILED || + SOUP_STATUS_IS_REDIRECTION (status)) +- g_vfs_job_failed (G_VFS_JOB (job), G_IO_ERROR, ++ g_vfs_job_failed (data->job, G_IO_ERROR, + G_IO_ERROR_EXISTS, + _("Target file already exists")); + else +- http_job_failed (G_VFS_JOB (job), msg); ++ http_job_failed (data->job, data->msg); + +- error: +- g_clear_object (&body); +- g_object_unref (msg); +- g_clear_error (&error); +- g_uri_unref (source_uri); +- g_uri_unref (target_uri); ++ g_object_unref (body); ++ copy_data_free (data); + } + + static void +-do_copy (GVfsBackend *backend, +- GVfsJobCopy *job, +- const char *source, +- const char *destination, +- GFileCopyFlags flags, +- GFileProgressCallback progress_callback, +- gpointer progress_callback_data) ++try_copy_target_stat_cb (GObject *source, GAsyncResult *result, ++ gpointer user_data) + { +- GInputStream *body; +- SoupMessage *msg; +- GUri *source_uri; +- GUri *target_uri; +- guint status; +- GFileType source_ft, target_ft; ++ GVfsBackend *backend = G_VFS_BACKEND (source); ++ CopyData *data = user_data; + GError *error = NULL; +- gboolean res; +- gint64 file_size; +- +- if (flags & G_FILE_COPY_BACKUP) +- { +- /* Return G_IO_ERROR_NOT_SUPPORTED instead of +- * G_IO_ERROR_CANT_CREATE_BACKUP to proceed with the GIO fallback +- * copy. */ +- g_vfs_job_failed_literal (G_VFS_JOB (job), +- G_IO_ERROR, +- G_IO_ERROR_NOT_SUPPORTED, +- "Operation not supported"); +- return; +- } +- +- source_uri = g_vfs_backend_dav_uri_for_path (backend, source, FALSE); +- target_uri = g_vfs_backend_dav_uri_for_path (backend, destination, FALSE); + +- res = stat_location (backend, source_uri, &source_ft, &file_size, NULL, &error); +- if (!res) +- { +- g_vfs_job_failed_from_error (G_VFS_JOB (job), error); +- goto error; +- } ++ data->target_res = stat_location_finish (backend, &data->target_ft, ++ NULL, NULL, result, &error); + +- res = stat_location (backend, target_uri, &target_ft, NULL, NULL, &error); +- if (res) ++ if (data->target_res) + { +- if (flags & G_FILE_COPY_OVERWRITE) ++ if (data->flags & G_FILE_COPY_OVERWRITE) + { +- if (target_ft == G_FILE_TYPE_DIRECTORY) ++ if (data->target_ft == G_FILE_TYPE_DIRECTORY) + { +- if (source_ft == G_FILE_TYPE_DIRECTORY) +- g_vfs_job_failed_literal (G_VFS_JOB(job), ++ if (data->source_ft == G_FILE_TYPE_DIRECTORY) ++ g_vfs_job_failed_literal (G_VFS_JOB(data->job), + G_IO_ERROR, + G_IO_ERROR_WOULD_MERGE, + _("Can’t copy directory over directory")); + else +- g_vfs_job_failed_literal (G_VFS_JOB(job), ++ g_vfs_job_failed_literal (G_VFS_JOB(data->job), + G_IO_ERROR, + G_IO_ERROR_IS_DIRECTORY, + _("File is directory")); +- goto error; ++ copy_data_free (data); ++ return; + } + } + else + { +- g_vfs_job_failed_literal (G_VFS_JOB (job), ++ g_vfs_job_failed_literal (data->job, + G_IO_ERROR, + G_IO_ERROR_EXISTS, + _("Target file already exists")); +- goto error; ++ copy_data_free (data); ++ return; + } + } + else if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND)) + { +- g_vfs_job_failed_from_error (G_VFS_JOB (job), error); +- goto error; ++ g_vfs_job_failed_from_error (data->job, error); ++ g_clear_error (&error); ++ copy_data_free (data); ++ return; + } + +- if (source_ft == G_FILE_TYPE_DIRECTORY) ++ g_clear_error (&error); ++ ++ if (data->source_ft == G_FILE_TYPE_DIRECTORY) + { +- g_vfs_job_failed_literal (G_VFS_JOB (job), ++ g_vfs_job_failed_literal (data->job, + G_IO_ERROR, + G_IO_ERROR_WOULD_RECURSE, + _("Can’t recursively copy directory")); +- goto error; ++ copy_data_free (data); ++ return; + } + +- msg = soup_message_new_from_uri (SOUP_METHOD_COPY, source_uri); +- message_add_destination_header (msg, target_uri); +- message_add_overwrite_header (msg, flags & G_FILE_COPY_OVERWRITE); ++ data->msg = soup_message_new_from_uri (SOUP_METHOD_COPY, data->source_uri); ++ message_add_destination_header (data->msg, data->target_uri); ++ message_add_overwrite_header (data->msg, data->flags & G_FILE_COPY_OVERWRITE); + +- body = g_vfs_backend_dav_send (backend, msg, TRUE, &error); +- if (!body) ++ dav_message_connect_signals (data->msg, backend); ++ ++ g_vfs_backend_dav_send_async (backend, data->msg, try_copy_do_cb, data); ++} ++ ++static void ++try_copy_source_stat_cb (GObject *source, GAsyncResult *result, ++ gpointer user_data) ++{ ++ GVfsBackend *backend = G_VFS_BACKEND (source); ++ CopyData *data = user_data; ++ gboolean res; ++ GError *error = NULL; ++ ++ res = stat_location_finish (backend, &data->source_ft, &data->file_size, ++ NULL, result, &error); ++ ++ if (!res) + { +- g_vfs_job_failed_from_error (G_VFS_JOB (job), error); +- g_object_unref (msg); +- goto error; ++ g_vfs_job_failed_from_error (data->job, error); ++ g_clear_error (&error); ++ copy_data_free (data); ++ return; + } + +- /* See do_set_display_name () for the explanation of the PRECONDITION_FAILED +- * and IS_REDIRECTION handling below. */ +- status = soup_message_get_status (msg); +- if (SOUP_STATUS_IS_SUCCESSFUL (status)) ++ g_clear_error (&error); ++ ++ data->source_res = res; ++ ++ stat_location_async (backend, data->target_uri, FALSE, ++ try_copy_target_stat_cb, data); ++} ++ ++static gboolean ++try_copy (GVfsBackend *backend, ++ GVfsJobCopy *job, ++ const char *source, ++ const char *destination, ++ GFileCopyFlags flags, ++ GFileProgressCallback progress_callback, ++ gpointer progress_callback_data) ++{ ++ CopyData *data = g_slice_new0 (CopyData); ++ data->job = G_VFS_JOB (job); ++ data->flags = flags; ++ data->progress_callback = progress_callback; ++ data->progress_callback_data = progress_callback_data; ++ ++ if (flags & G_FILE_COPY_BACKUP) + { +- if (progress_callback) +- progress_callback (file_size, file_size, progress_callback_data); +- g_vfs_job_succeeded (G_VFS_JOB (job)); ++ /* Return G_IO_ERROR_NOT_SUPPORTED instead of ++ * G_IO_ERROR_CANT_CREATE_BACKUP to proceed with the GIO fallback ++ * copy. */ ++ g_vfs_job_failed_literal (G_VFS_JOB (job), ++ G_IO_ERROR, ++ G_IO_ERROR_NOT_SUPPORTED, ++ "Operation not supported"); ++ ++ copy_data_free (data); ++ return TRUE; + } +- else if (status == SOUP_STATUS_PRECONDITION_FAILED || +- SOUP_STATUS_IS_REDIRECTION (status)) +- g_vfs_job_failed (G_VFS_JOB (job), G_IO_ERROR, +- G_IO_ERROR_EXISTS, +- _("Target file already exists")); +- else +- http_job_failed (G_VFS_JOB (job), msg); + +- g_object_unref (body); +- g_object_unref (msg); ++ data->source_uri = g_vfs_backend_dav_uri_for_path (backend, source, FALSE); ++ data->target_uri = g_vfs_backend_dav_uri_for_path (backend, destination, FALSE); + +-error: +- g_clear_error (&error); +- g_uri_unref (source_uri); +- g_uri_unref (target_uri); ++ stat_location_async (backend, data->source_uri, FALSE, ++ try_copy_source_stat_cb, data); ++ return TRUE; + } + + #define CHUNK_SIZE 65536 +@@ -3459,7 +3919,7 @@ static void + push_done (GObject *source, GAsyncResult *result, gpointer user_data) + { + GInputStream *body; +- GVfsJob *job = G_VFS_JOB (user_data);; ++ GVfsJob *job = G_VFS_JOB (user_data); + GError *error = NULL; + + body = soup_session_send_finish (SOUP_SESSION (source), result, &error); +@@ -3488,7 +3948,7 @@ push_stat_dest_cb (GObject *source, GAsyncResult *result, gpointer user_data) + return; + } + +- if (stat_location_finish (handle->msg, body, &type, NULL, NULL)) ++ if (stat_location_end (handle->msg, body, &type, NULL, NULL)) + { + if (!(handle->op_job->flags & G_FILE_COPY_OVERWRITE)) + { +@@ -3527,8 +3987,11 @@ push_stat_dest_cb (GObject *source, GAsyncResult *result, gpointer user_data) + g_signal_connect (handle->msg, "finished", + G_CALLBACK (push_finished), handle); + +- g_vfs_backend_dav_send_async (handle->backend, handle->msg, TRUE, +- push_done, handle->job); ++ dav_message_connect_signals (handle->msg, handle->backend); ++ ++ soup_session_send_async (G_VFS_BACKEND_HTTP (handle->backend)->session, ++ handle->msg, G_PRIORITY_DEFAULT, ++ NULL, push_done, handle->job); + } + + static void +@@ -3545,9 +4008,13 @@ push_source_fstat_cb (GObject *source, GAsyncResult *res, gpointer user_data) + handle->size = g_file_info_get_size (info); + g_object_unref (info); + +- handle->msg = stat_location_begin (handle->uri, FALSE); +- g_vfs_backend_dav_send_async (handle->backend, handle->msg, TRUE, +- push_stat_dest_cb, handle); ++ handle->msg = stat_location_start (handle->uri, FALSE); ++ ++ dav_message_connect_signals (handle->msg, handle->backend); ++ ++ soup_session_send_async (G_VFS_BACKEND_HTTP (handle->backend)->session, ++ handle->msg, G_PRIORITY_DEFAULT, NULL, ++ push_stat_dest_cb, handle); + } + else + { +@@ -3675,26 +4142,23 @@ g_vfs_backend_dav_class_init (GVfsBackendDavClass *klass) + + backend_class = G_VFS_BACKEND_CLASS (klass); + +- backend_class->try_mount = NULL; +- backend_class->mount = do_mount; +- backend_class->try_query_info = NULL; +- backend_class->query_info = do_query_info; +- backend_class->try_query_fs_info = NULL; +- backend_class->query_fs_info = do_query_fs_info; +- backend_class->enumerate = do_enumerate; +- backend_class->try_open_for_read = try_open_for_read; +- backend_class->try_create = try_create; +- backend_class->try_replace = try_replace; +- backend_class->try_write = try_write; +- backend_class->seek_on_write = do_seek_on_write; +- backend_class->truncate = do_truncate; +- backend_class->try_close_write = try_close_write; +- backend_class->make_directory = do_make_directory; +- backend_class->delete = do_delete; +- backend_class->set_display_name = do_set_display_name; +- backend_class->move = do_move; +- backend_class->copy = do_copy; +- backend_class->try_push = try_push; ++ backend_class->try_mount = try_mount; ++ backend_class->try_query_info = try_query_info; ++ backend_class->try_query_fs_info = try_query_fs_info; ++ backend_class->try_enumerate = try_enumerate; ++ backend_class->try_open_for_read = try_open_for_read; ++ backend_class->try_create = try_create; ++ backend_class->try_replace = try_replace; ++ backend_class->try_write = try_write; ++ backend_class->seek_on_write = do_seek_on_write; ++ backend_class->truncate = do_truncate; ++ backend_class->try_close_write = try_close_write; ++ backend_class->try_make_directory = try_make_directory; ++ backend_class->try_delete = try_delete; ++ backend_class->try_set_display_name = try_set_display_name; ++ backend_class->try_move = try_move; ++ backend_class->try_copy = try_copy; ++ backend_class->try_push = try_push; + + /* override the maximum number of connections, since the libsoup defaults + * of 10 and 2 respectively are too low and may cause backend lockups when +-- +2.35.1 + diff --git a/gvfs.spec b/gvfs.spec index 498902d..66c42ab 100644 --- a/gvfs.spec +++ b/gvfs.spec @@ -22,7 +22,7 @@ Name: gvfs Version: 1.50.0 -Release: 2%{?dist} +Release: 3%{?dist} Summary: Backends for the gio framework in GLib License: GPLv3 and LGPLv2+ and BSD and MPLv2.0 @@ -32,6 +32,10 @@ Source0: https://download.gnome.org/sources/gvfs/1.50/gvfs-%{version}.tar.xz # https://bugzilla.redhat.com/show_bug.cgi?id=2066717 Patch0: dav-Fix-crashes-caused-by-extra-unref.patch +# https://bugzilla.redhat.com/show_bug.cgi?id=2062465 +Patch1: dav-Do-not-lose-userinfo-when-copying-URIs.patch +Patch2: dav-Rewrite-to-libsoup-async-API-to-fix-crashes.patch + BuildRequires: meson BuildRequires: gcc BuildRequires: pkgconfig(glib-2.0) >= %{glib2_version} @@ -424,6 +428,9 @@ killall -USR1 gvfsd >&/dev/null || : %{_datadir}/installed-tests %changelog +* Fri Apr 8 2022 Ondrej Holy - 1.50.0-3 +- Rewrite DAV backend to libsoup async API to fix crashes (#2062465) + * Thu Mar 24 2022 Ondrej Holy - 1.50.0-2 - Fix DAV backend crashes caused by extra unref (#2066717)