diff options
Diffstat (limited to 'daemon')
-rw-r--r-- | daemon/gvfsbackenddav.c | 1914 |
1 files changed, 1189 insertions, 725 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 <username> on <hostname>:<port>"; the ":<port>" 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); + /* recurse */ + soup_session_send_async (session, msg, G_PRIORITY_DEFAULT, NULL, + dav_send_async_with_redir_cb, g_object_ref (task)); + goto return_done; -#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 +return_body: + g_task_return_pointer (task, body, g_object_unref); + goto return_done; - 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)); +return_error: + g_task_return_error (task, error); - if (g_uri_get_user (mount_base) != NULL) - /* Translators: This is the name of the WebDAV share constructed as - "WebDAV as <username> on <hostname>:<port>"; the ":<port>" 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)); +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; + 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); - /* 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); - - 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) + /* dup the mountspec, but only copy known fields */ + mount_spec = g_mount_spec_from_dav_uri (dav_backend, http_backend->mount_base); + + 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_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) { - dav_backend->resolver = g_vfs_dns_sd_resolver_new_for_encoded_triple (host, "u"); + g_vfs_job_failed_from_error (G_VFS_JOB (job), error); + g_error_free (error); + goto clear_msg; + } - 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); + res = stat_location_end (msg_stat, body, &file_type, NULL, NULL); + is_collection = res && file_type == G_FILE_TYPE_DIRECTORY; - mount_base = dav_uri_from_dns_sd_resolver (dav_backend); + if (!g_vfs_backend_dav_stream_skip (body, &error)) + { + 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; } - else -#endif + + g_object_unref (body); + + soup_message_headers_clear (soup_message_get_response_headers (msg_stat)); + + g_debug (" [%s] webdav: %d, collection %d [res: %d]\n", + g_uri_get_path (http_backend->mount_base), TRUE, is_collection, res); + + if ((is_collection == FALSE) && (dav_backend->last_good_path != NULL)) { - mount_base = g_mount_spec_to_dav_uri (mount_spec); + 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; } - if (mount_base == NULL) + /* 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); + + 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_free (new_path); + + /* 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; + + /* break out */ + if (g_strcmp0 (dav_backend->last_good_path, "/") == 0) { - g_vfs_job_failed (G_VFS_JOB (job), G_IO_ERROR, - G_IO_ERROR_INVALID_ARGUMENT, - _("Invalid mount spec")); - return; + mount_success (backend, G_VFS_JOB (job)); + goto clear_msg; } - session = G_VFS_BACKEND_HTTP (backend)->session; - G_VFS_BACKEND_HTTP (backend)->mount_base = mount_base; + /* else loop the whole thing from options */ - soup_session_add_feature_by_type (session, SOUP_TYPE_AUTH_NEGOTIATE); - soup_session_add_feature_by_type (session, SOUP_TYPE_AUTH_NTLM); + msg_opts = soup_message_new_from_uri (SOUP_METHOD_OPTIONS, + http_backend->mount_base); - 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; + dav_message_connect_signals (msg_opts, backend); - last_good_path = NULL; - msg_opts = soup_message_new_from_uri (SOUP_METHOD_OPTIONS, mount_base); + g_vfs_backend_dav_send_async (backend, msg_opts, try_mount_opts_cb, job); - /* 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; +clear_msg: + g_object_unref (msg_stat); +} - data->retrying_after_403 = TRUE; +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; - 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); + 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; - auth_manager = soup_session_get_feature (session, SOUP_TYPE_AUTH_MANAGER); - soup_auth_manager_clear_cached_credentials (SOUP_AUTH_MANAGER (auth_manager)); + /* 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); - g_object_unref (msg_opts); - msg_opts = soup_message_new_from_uri (SOUP_METHOD_OPTIONS, mount_base); + dav_message_connect_signals (msg_opts, backend); - continue; - } + g_vfs_backend_dav_send_async (backend, msg_opts, + try_mount_opts_cb, job); + return; + } + } - /* 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; - } - } + /* 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; + } - if (!is_success || !is_webdav) - break; + status = soup_message_get_status (msg_opts); - if (!g_vfs_backend_dav_stream_skip (body, &error)) - { - g_object_unref (body); - 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; - g_object_unref (body); + dav_backend->auth_info.retrying_after_403 = TRUE; - soup_message_headers_clear (soup_message_get_response_headers (msg_opts)); + 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); - cur_uri = soup_message_get_uri (msg_opts); - soup_message_set_uri (msg_stat, cur_uri); + auth_manager = soup_session_get_feature (http_backend->session, SOUP_TYPE_AUTH_MANAGER); + soup_auth_manager_clear_cached_credentials (SOUP_AUTH_MANAGER (auth_manager)); - 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_vfs_backend_dav_send_async (backend, msg_opts, try_mount_opts_cb, job); + return; + } + + is_success = SOUP_STATUS_IS_SUCCESSFUL (status); + is_webdav = sm_has_header (msg_opts, "DAV"); + + 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) + { + int error_code = http_error_code_from_status (soup_message_get_status (msg_opts)); - g_clear_object (&body); + 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; + } - soup_message_headers_clear (soup_message_get_response_headers (msg_stat)); + 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_debug (" [%s] webdav: %d, collection %d [res: %d]\n", - g_uri_get_path (mount_base), is_webdav, is_collection, res); + g_object_unref (body); - if (is_collection == FALSE) - break; + cur_uri = soup_message_get_uri (msg_opts); - /* 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); + /* 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); - 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; + dav_message_connect_signals (msg_stat, backend); - g_free (new_path); + g_vfs_backend_dav_send_async (backend, msg_stat, try_mount_stat_cb, job); - soup_message_set_uri (msg_opts, mount_base); +clear_msgs: + g_object_unref (msg_opts); +} - /* 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; +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; - } while (g_strcmp0 (last_good_path, "/") != 0); + g_debug ("+ mount\n"); - /* 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) */ + host = g_mount_spec_get (mount_spec, "host"); + type = g_mount_spec_get (mount_spec, "type"); - /* check if we at all have a good path */ - if (last_good_path == NULL) +#ifdef HAVE_AVAHI + /* resolve DNS-SD style URIs */ + if ((strcmp (type, "dav+sd") == 0 || strcmp (type, "davs+sd") == 0) && host != NULL) { - if (error) + 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); - body = g_vfs_backend_dav_send (backend, msg, TRUE, &error); + 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_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,61 +3273,47 @@ 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); - 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; - } - 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)); + dav_message_connect_signals (msg, backend); - g_uri_unref (uri); - g_object_unref (msg); - g_object_unref (body); + 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 -do_set_display_name (GVfsBackend *backend, - GVfsJobSetDisplayName *job, - const char *filename, - const char *display_name) +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; - 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); + http_job_failed (G_VFS_JOB (data->job), msg); goto error; } @@ -3073,299 +3337,495 @@ do_set_display_name (GVfsBackend *backend, 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)); + 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 (job), G_IO_ERROR, + g_vfs_job_failed (G_VFS_JOB (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 (G_VFS_JOB (data->job), msg); g_object_unref (body); - error: +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_free (target_path); 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; } -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; +typedef struct _CopyData { + GVfsJob *job; SoupMessage *msg; GUri *source_uri; GUri *target_uri; - guint status; - GFileType source_ft, target_ft; - GError *error = NULL; - gboolean res, stat_res; + 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; - 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"); - } +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 +try_move_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); + + if (!body) + { + g_vfs_job_failed_from_error (data->job, error); + copy_data_free (data); + 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); + /* See try_set_display_name () for the explanation of the PRECONDITION_FAILED + * and IS_REDIRECTION handling below. */ + status = soup_message_get_status (data->msg); - 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)) + if (SOUP_STATUS_IS_SUCCESSFUL (status)) { - g_vfs_job_failed_from_error (G_VFS_JOB (job), error); - goto error; + 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); } - g_clear_error (&error); + else if (status == SOUP_STATUS_PRECONDITION_FAILED || + SOUP_STATUS_IS_REDIRECTION (status)) + g_vfs_job_failed (data->job, G_IO_ERROR, + G_IO_ERROR_EXISTS, + _("Target file already exists")); + else + http_job_failed (data->job, data->msg); - stat_res = stat_location (backend, source_uri, &source_ft, &file_size, NULL, &error); - if (res) + copy_data_free (data); +} + +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 +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; + guint status; + + 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; + } + + status = soup_message_get_status (msg); + if (!SOUP_STATUS_IS_SUCCESSFUL (status)) { - if (flags & G_FILE_COPY_OVERWRITE) + http_job_failed (data->job, msg); + g_object_unref (msg); + copy_data_free (data); + return; + } + + 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 (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; - } + data->target_res = stat_location_finish (backend, &data->target_ft, + NULL, NULL, result, &error); - 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) + if (data->target_res) { - g_vfs_job_failed_from_error (G_VFS_JOB (job), error); - goto error; - } - - res = stat_location (backend, target_uri, &target_ft, NULL, NULL, &error); - if (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 |