gvfs/daemon/gvfsbackenddav.c

4201 lines
117 KiB
C
Raw Normal View History

2022-06-29 16:07:13 +08:00
/* GIO - GLib Input, Output and Streaming Library
*
* Copyright (C) 2008 Red Hat, Inc.
2023-02-15 15:41:32 +08:00
* Copyright (C) 2021 Igalia S.L.
2022-06-29 16:07:13 +08:00
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General
* Public License along with this library; if not, write to the
* Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*
* Author: Christian Kellner <gicmo@gnome.org>
*/
#include <config.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <errno.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <glib/gstdio.h>
#include <glib/gi18n.h>
#include <gio/gio.h>
#include <libsoup/soup.h>
/* LibXML2 includes */
#include <libxml/parser.h>
#include <libxml/tree.h>
#include <libxml/xpath.h>
#include <libxml/xpathInternals.h>
#include "gvfsbackenddav.h"
#include "gvfskeyring.h"
#include "gvfsjobmount.h"
#include "gvfsjobopenforread.h"
#include "gvfsjobread.h"
#include "gvfsjobseekread.h"
#include "gvfsjobopenforwrite.h"
#include "gvfsjobwrite.h"
#include "gvfsjobseekwrite.h"
#include "gvfsjobsetdisplayname.h"
#include "gvfsjobqueryinfo.h"
#include "gvfsjobqueryfsinfo.h"
#include "gvfsjobqueryattributes.h"
#include "gvfsjobenumerate.h"
#include "gvfsjobpush.h"
#include "gvfsdaemonprotocol.h"
#include "gvfsdaemonutils.h"
#include "gvfsutils.h"
#ifdef HAVE_AVAHI
#include "gvfsdnssdutils.h"
#include "gvfsdnssdresolver.h"
#endif
typedef struct _MountAuthData MountAuthData;
static void mount_auth_info_free (MountAuthData *info);
#ifdef HAVE_AVAHI
static void dns_sd_resolver_changed (GVfsDnsSdResolver *resolver, GVfsBackendDav *dav_backend);
#endif
2023-02-15 15:41:32 +08:00
static gboolean
soup_authenticate (SoupMessage *msg,
SoupAuth *auth,
gboolean retrying,
gpointer user_data);
2022-06-29 16:07:13 +08:00
typedef struct _AuthInfo {
/* for server authentication */
char *username;
char *password;
char *realm;
GPasswordSave pw_save;
} AuthInfo;
struct _MountAuthData {
SoupSession *session;
GMountSource *mount_source;
gboolean retrying_after_403;
2023-02-15 15:41:32 +08:00
gboolean interactive;
2022-06-29 16:07:13 +08:00
AuthInfo server_auth;
AuthInfo proxy_auth;
};
struct _GVfsBackendDav
{
GVfsBackendHttp parent_instance;
MountAuthData auth_info;
2023-02-15 15:41:32 +08:00
gchar *last_good_path;
2022-06-29 16:07:13 +08:00
/* Used for user-verified secure connections. */
GTlsCertificate *certificate;
GTlsCertificateFlags certificate_errors;
#ifdef HAVE_AVAHI
/* only set if we're handling a [dav|davs]+sd:// mounts */
GVfsDnsSdResolver *resolver;
#endif
};
G_DEFINE_TYPE (GVfsBackendDav, g_vfs_backend_dav, G_VFS_TYPE_BACKEND_HTTP);
static void
g_vfs_backend_dav_finalize (GObject *object)
{
GVfsBackendDav *dav_backend;
dav_backend = G_VFS_BACKEND_DAV (object);
#ifdef HAVE_AVAHI
if (dav_backend->resolver != NULL)
{
g_signal_handlers_disconnect_by_func (dav_backend->resolver, dns_sd_resolver_changed, dav_backend);
g_object_unref (dav_backend->resolver);
}
#endif
mount_auth_info_free (&(dav_backend->auth_info));
g_clear_object (&dav_backend->certificate);
if (G_OBJECT_CLASS (g_vfs_backend_dav_parent_class)->finalize)
(*G_OBJECT_CLASS (g_vfs_backend_dav_parent_class)->finalize) (object);
}
static void
g_vfs_backend_dav_init (GVfsBackendDav *backend)
{
g_vfs_backend_set_user_visible (G_VFS_BACKEND (backend), TRUE);
}
/* ************************************************************************* */
/* Small utility functions */
static gboolean
string_to_uint64 (const char *str, guint64 *value)
{
char *endptr;
*value = g_ascii_strtoull (str, &endptr, 10);
return endptr != str;
}
static inline gboolean
sm_has_header (SoupMessage *msg, const char *header)
{
2023-02-15 15:41:32 +08:00
return soup_message_headers_get_one (soup_message_get_response_headers (msg),
header) != NULL;
2022-06-29 16:07:13 +08:00
}
static char *
path_get_parent_dir (const char *path)
{
char *parent;
size_t len;
len = strlen (path);
while (len > 0 && path[len - 1] == '/')
len--;
if (len == 0)
return g_strdup ("/");
parent = g_strrstr_len (path, len, "/");
if (parent == NULL)
return g_strdup ("/");
return g_strndup (path, (parent - path) + 1);
}
/* message utility functions */
static void
message_add_destination_header (SoupMessage *msg,
2023-02-15 15:41:32 +08:00
GUri *uri)
2022-06-29 16:07:13 +08:00
{
char *string;
2023-02-15 15:41:32 +08:00
string = g_uri_to_string_partial (uri, G_URI_HIDE_PASSWORD);
soup_message_headers_append (soup_message_get_request_headers (msg),
2022-06-29 16:07:13 +08:00
"Destination",
string);
g_free (string);
}
static void
message_add_overwrite_header (SoupMessage *msg,
gboolean overwrite)
{
2023-02-15 15:41:32 +08:00
soup_message_headers_append (soup_message_get_request_headers (msg),
2022-06-29 16:07:13 +08:00
"Overwrite",
overwrite ? "T" : "F");
}
static void
message_add_redirect_header (SoupMessage *msg,
GFileQueryInfoFlags flags)
{
const char *header_redirect;
/* RFC 4437 */
if (flags & G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS)
header_redirect = "F";
else
header_redirect = "T";
2023-02-15 15:41:32 +08:00
soup_message_headers_append (soup_message_get_request_headers (msg),
2022-06-29 16:07:13 +08:00
"Apply-To-Redirect-Ref",
header_redirect);
}
static inline gboolean
str_equal (const char *a, const char *b, gboolean insensitive)
{
if (a == NULL || b == NULL)
return a == b;
return insensitive ? !g_ascii_strcasecmp (a, b) : !strcmp (a, b);
}
static gboolean
path_equal (const char *a, const char *b, gboolean relax)
{
gboolean res;
size_t a_len, b_len;
if (relax == FALSE)
return str_equal (a, b, FALSE);
if (a == NULL || b == NULL)
return a == b;
a_len = strlen (a);
b_len = strlen (b);
while (a_len > 0 && a[a_len - 1] == '/')
a_len--;
while (b_len > 0 && b[b_len - 1] == '/')
b_len--;
if (a_len == b_len)
res = ! strncmp (a, b, a_len);
else
res = FALSE;
return res;
}
static gboolean
2023-02-15 15:41:32 +08:00
dav_uri_match (GUri *a, GUri *b, gboolean relax)
2022-06-29 16:07:13 +08:00
{
gboolean diff;
char *ua, *ub;
2023-02-15 15:41:32 +08:00
ua = g_uri_unescape_string (g_uri_get_path (a), "/");
ub = g_uri_unescape_string (g_uri_get_path (b), "/");
2022-06-29 16:07:13 +08:00
2023-02-15 15:41:32 +08:00
diff = g_uri_get_port (a) != g_uri_get_port (b) ||
!str_equal (g_uri_get_scheme (a), g_uri_get_scheme (b), FALSE) ||
!str_equal (g_uri_get_host (a), g_uri_get_host (b), TRUE) ||
!path_equal (ua, ub, relax) ||
!str_equal (g_uri_get_query (a), g_uri_get_query (b), FALSE) ||
!str_equal (g_uri_get_fragment (a), g_uri_get_fragment (b), FALSE);
2022-06-29 16:07:13 +08:00
g_free (ua);
g_free (ub);
return !diff;
}
static char *
dav_uri_encode (const char *path_to_encode)
{
char *path;
static const char *allowed_reserved_chars = "/";
path = g_uri_escape_string (path_to_encode,
allowed_reserved_chars,
FALSE);
return path;
}
static gboolean
message_should_apply_redir_ref (SoupMessage *msg)
{
const char *header;
2023-02-15 15:41:32 +08:00
header = soup_message_headers_get_one (soup_message_get_request_headers (msg),
2022-06-29 16:07:13 +08:00
"Apply-To-Redirect-Ref");
if (header == NULL || g_ascii_strcasecmp (header, "T"))
return FALSE;
return TRUE;
}
2023-02-15 15:41:32 +08:00
static GUri *
2022-06-29 16:07:13 +08:00
g_vfs_backend_dav_uri_for_path (GVfsBackend *backend,
const char *path,
gboolean is_dir)
{
2023-02-15 15:41:32 +08:00
GUri *mount_base;
GUri *uri;
char *fn_encoded;
char *new_path;
2022-06-29 16:07:13 +08:00
mount_base = http_backend_get_mount_base (backend);
/* "/" means "whatever mount_base is" */
if (!strcmp (path, "/"))
2023-02-15 15:41:32 +08:00
return g_uri_ref (mount_base);
2022-06-29 16:07:13 +08:00
/* The mount_base path is escaped already so we need to
escape the new path as well */
fn_encoded = dav_uri_encode (path);
/* Otherwise, we append filename to mount_base (which is assumed to
* be a directory in this case).
*
* Add a "/" in cases where it is likely that the url is going
* to be a directory to avoid redirections
*/
if (is_dir == FALSE || g_str_has_suffix (path, "/"))
2023-02-15 15:41:32 +08:00
new_path = g_build_path ("/", g_uri_get_path (mount_base), fn_encoded, NULL);
2022-06-29 16:07:13 +08:00
else
2023-02-15 15:41:32 +08:00
new_path = g_build_path ("/", g_uri_get_path (mount_base), fn_encoded, "/", NULL);
uri = soup_uri_copy (mount_base, SOUP_URI_PATH, new_path, SOUP_URI_NONE);
2022-06-29 16:07:13 +08:00
g_free (fn_encoded);
2023-02-15 15:41:32 +08:00
g_free (new_path);
2022-06-29 16:07:13 +08:00
return uri;
}
2023-02-15 15:41:32 +08:00
static gboolean
g_vfs_backend_dav_stream_skip (GInputStream *stream, GError **error)
2022-06-29 16:07:13 +08:00
{
2023-02-15 15:41:32 +08:00
for (;;)
{
gssize skipped = g_input_stream_skip (stream, 4096, NULL, error);
if (skipped < 0)
{
return FALSE;
}
else if (!skipped)
break;
}
2022-06-29 16:07:13 +08:00
2023-02-15 15:41:32 +08:00
g_input_stream_close (stream, NULL, NULL);
return TRUE;
2022-06-29 16:07:13 +08:00
}
static void
g_vfs_backend_dav_setup_display_name (GVfsBackend *backend)
{
2023-02-15 15:41:32 +08:00
GUri *mount_base;
2022-06-29 16:07:13 +08:00
char *display_name;
char port[7] = {0, };
2023-02-15 15:41:32 +08:00
gint gport;
2022-06-29 16:07:13 +08:00
#ifdef HAVE_AVAHI
2023-02-15 15:41:32 +08:00
GVfsBackendDav *dav_backend = G_VFS_BACKEND_DAV (backend);
2022-06-29 16:07:13 +08:00
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);
2023-02-15 15:41:32 +08:00
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));
2022-06-29 16:07:13 +08:00
2023-02-15 15:41:32 +08:00
if (g_uri_get_user (mount_base) != NULL)
2022-06-29 16:07:13 +08:00
/* 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"),
2023-02-15 15:41:32 +08:00
g_uri_get_user (mount_base),
g_uri_get_host (mount_base),
port);
2022-06-29 16:07:13 +08:00
else
display_name = g_strdup_printf ("%s%s",
2023-02-15 15:41:32 +08:00
g_uri_get_host (mount_base),
port);
2022-06-29 16:07:13 +08:00
g_vfs_backend_set_display_name (backend, display_name);
g_free (display_name);
}
2023-02-15 15:41:32 +08:00
static gboolean
accept_certificate (SoupMessage *msg,
GTlsCertificate *certificate,
GTlsCertificateFlags errors,
gpointer user_data)
2022-06-29 16:07:13 +08:00
{
GVfsBackendDav *dav = G_VFS_BACKEND_DAV (user_data);
2023-02-15 15:41:32 +08:00
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)
{
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;
GUri *tmp;
guint status;
gboolean redirect;
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))
goto return_body;
new_loc = soup_message_headers_get_one (soup_message_get_response_headers (msg),
"Location");
if (new_loc == NULL)
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);
if (new_uri == NULL)
{
g_object_unref (body);
goto return_error;
}
tmp = new_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/),
* redirect it right away
*/
redirect = dav_uri_match (new_uri, old_uri, TRUE);
if (redirect)
{
const char *dest;
dest = soup_message_headers_get_one (soup_message_get_request_headers (msg),
"Destination");
if (dest && g_str_has_suffix (dest, "/") == FALSE)
{
char *new_dest = g_strconcat (dest, "/", NULL);
soup_message_headers_replace (soup_message_get_request_headers (msg),
"Destination",
new_dest);
g_free (new_dest);
}
}
else if (message_should_apply_redir_ref (msg))
2022-06-29 16:07:13 +08:00
{
2023-02-15 15:41:32 +08:00
if (status == SOUP_STATUS_MOVED_PERMANENTLY ||
status == SOUP_STATUS_TEMPORARY_REDIRECT)
2022-06-29 16:07:13 +08:00
{
2023-02-15 15:41:32 +08:00
const char *method = soup_message_get_method (msg);
/* Only cross-site redirect safe methods */
if (method == SOUP_METHOD_GET &&
method == SOUP_METHOD_HEAD &&
method == SOUP_METHOD_OPTIONS &&
method == SOUP_METHOD_PROPFIND)
redirect = TRUE;
2022-06-29 16:07:13 +08:00
}
2023-02-15 15:41:32 +08:00
/* Two possibilities:
*
* 1) It's a non-redirecty 3xx response (300, 304,
* 305, 306)
* 2) It's some newly-defined 3xx response (308+)
*
* We ignore both of these cases. In the first,
* redirecting would be explicitly wrong, and in the
* last case, we have no clue if the 3xx response is
* supposed to be redirecty or non-redirecty. Plus,
* 2616 says unrecognized status codes should be
* treated as the equivalent to the x00 code, and we
* don't redirect on 300, so therefore we shouldn't
* redirect on 308+ either.
*/
}
if (!redirect)
{
g_uri_unref (new_uri);
goto return_body;
}
if (!g_vfs_backend_dav_stream_skip (body, &error))
{
g_object_unref (body);
goto return_error;
2022-06-29 16:07:13 +08:00
}
2023-02-15 15:41:32 +08:00
g_object_unref (body);
soup_message_set_uri (msg, new_uri);
g_uri_unref (new_uri);
/* recurse */
soup_session_send_async (session, msg, G_PRIORITY_DEFAULT, NULL,
dav_send_async_with_redir_cb, g_object_ref (task));
goto return_done;
return_body:
g_task_return_pointer (task, body, g_object_unref);
goto return_done;
return_error:
g_task_return_error (task, error);
return_done:
g_object_unref (task);
2022-06-29 16:07:13 +08:00
}
2023-02-15 15:41:32 +08:00
static void
g_vfs_backend_dav_send_async (GVfsBackend *backend,
SoupMessage *message,
GAsyncReadyCallback callback,
gpointer user_data)
2022-06-29 16:07:13 +08:00
{
2023-02-15 15:41:32 +08:00
SoupSession *session = G_VFS_BACKEND_HTTP (backend)->session;
GTask *task = g_task_new (backend, NULL, callback, user_data);
2022-06-29 16:07:13 +08:00
2023-02-15 15:41:32 +08:00
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);
2022-06-29 16:07:13 +08:00
soup_message_set_flags (message, SOUP_MESSAGE_NO_REDIRECT);
2023-02-15 15:41:32 +08:00
soup_session_send_async (session, message, G_PRIORITY_DEFAULT, NULL,
dav_send_async_with_redir_cb, task);
}
2022-06-29 16:07:13 +08:00
2023-02-15 15:41:32 +08:00
static GInputStream *
g_vfs_backend_dav_send_finish (GVfsBackend *backend,
GAsyncResult *result,
GError **error)
{
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);
2022-06-29 16:07:13 +08:00
2023-02-15 15:41:32 +08:00
return g_task_propagate_pointer (G_TASK (result), error);
2022-06-29 16:07:13 +08:00
}
2023-02-15 15:41:32 +08:00
static SoupMessage *
g_vfs_backend_dav_get_async_result_message (GVfsBackend *backend,
GAsyncResult *result)
2022-06-29 16:07:13 +08:00
{
2023-02-15 15:41:32 +08:00
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);
2022-06-29 16:07:13 +08:00
2023-02-15 15:41:32 +08:00
return g_task_get_task_data (G_TASK (result));
2022-06-29 16:07:13 +08:00
}
/* ************************************************************************* */
/* generic xml parsing functions */
static inline gboolean
node_has_name (xmlNodePtr node, const char *name)
{
g_return_val_if_fail (node != NULL, FALSE);
return ! strcmp ((char *) node->name, name);
}
static inline gboolean
node_has_name_ns (xmlNodePtr node, const char *name, const char *ns_href)
{
gboolean has_name;
gboolean has_ns;
g_return_val_if_fail (node != NULL, FALSE);
has_name = has_ns = TRUE;
if (name)
has_name = node->name && ! strcmp ((char *) node->name, name);
if (ns_href)
has_ns = node->ns && node->ns->href &&
! g_ascii_strcasecmp ((char *) node->ns->href, ns_href);
return has_name && has_ns;
}
static inline gboolean
node_is_element (xmlNodePtr node)
{
return node->type == XML_ELEMENT_NODE && node->name != NULL;
}
static inline gboolean
node_is_element_with_name (xmlNodePtr node, const char *name)
{
return node->type == XML_ELEMENT_NODE &&
node->name != NULL &&
! strcmp ((char *) node->name, name);
}
static const char *
node_get_content (xmlNodePtr node)
{
if (node == NULL)
return NULL;
switch (node->type)
{
case XML_ELEMENT_NODE:
return node_get_content (node->children);
break;
case XML_TEXT_NODE:
return (const char *) node->content;
break;
default:
return NULL;
}
}
static gboolean
node_is_empty (xmlNodePtr node)
{
if (node == NULL)
return TRUE;
if (node->type == XML_TEXT_NODE)
return node->content == NULL || node->content[0] == '\0';
return node->children == NULL;
}
typedef struct _xmlNodeIter {
xmlNodePtr cur_node;
xmlNodePtr next_node;
const char *name;
const char *ns_href;
void *user_data;
} xmlNodeIter;
static xmlNodePtr
xml_node_iter_next (xmlNodeIter *iter)
{
xmlNodePtr node;
while ((node = iter->next_node))
{
iter->next_node = node->next;
if (node->type == XML_ELEMENT_NODE) {
if (node_has_name_ns (node, iter->name, iter->ns_href))
break;
}
}
iter->cur_node = node;
return node;
}
static void *
xml_node_iter_get_user_data (xmlNodeIter *iter)
{
return iter->user_data;
}
static xmlNodePtr
xml_node_iter_get_current (xmlNodeIter *iter)
{
return iter->cur_node;
}
2023-02-15 15:41:32 +08:00
static int
xml_read_cb (void *ctx, char *buf, int len)
{
return g_input_stream_read (ctx, buf, len, NULL, NULL);
}
2022-06-29 16:07:13 +08:00
static xmlDocPtr
parse_xml (SoupMessage *msg,
2023-02-15 15:41:32 +08:00
GInputStream *body,
2022-06-29 16:07:13 +08:00
xmlNodePtr *root,
const char *name,
GError **error)
{
2023-02-15 15:41:32 +08:00
xmlDocPtr doc;
2022-06-29 16:07:13 +08:00
2023-02-15 15:41:32 +08:00
if (!SOUP_STATUS_IS_SUCCESSFUL (soup_message_get_status (msg)))
2022-06-29 16:07:13 +08:00
{
g_set_error (error,
G_IO_ERROR,
2023-02-15 15:41:32 +08:00
http_error_code_from_status (soup_message_get_status (msg)),
_("HTTP Error: %s"), soup_message_get_reason_phrase (msg));
2022-06-29 16:07:13 +08:00
return NULL;
}
2023-02-15 15:41:32 +08:00
doc = xmlReadIO (xml_read_cb, NULL, body, "response.xml", NULL,
XML_PARSE_NONET |
XML_PARSE_NOWARNING |
XML_PARSE_NOBLANKS |
XML_PARSE_NSCLEAN |
XML_PARSE_NOCDATA |
XML_PARSE_COMPACT);
2022-06-29 16:07:13 +08:00
if (doc == NULL)
{
g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_FAILED,
_("Could not parse response"));
return NULL;
}
*root = xmlDocGetRootElement (doc);
if (*root == NULL || (*root)->children == NULL)
{
g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_FAILED,
_("Empty response"));
xmlFreeDoc (doc);
return NULL;
}
if (strcmp ((char *) (*root)->name, name))
{
g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_FAILED,
_("Unexpected reply from server"));
xmlFreeDoc (doc);
return NULL;
}
return doc;
}
/* ************************************************************************* */
/* Multistatus parsing code */
typedef struct _Multistatus Multistatus;
typedef struct _MsResponse MsResponse;
typedef struct _MsPropstat MsPropstat;
struct _Multistatus {
xmlDocPtr doc;
xmlNodePtr root;
2023-02-15 15:41:32 +08:00
GUri *target;
2022-06-29 16:07:13 +08:00
char *path;
};
struct _MsResponse {
Multistatus *multistatus;
char *path;
gboolean is_target;
xmlNodePtr first_propstat;
};
struct _MsPropstat {
Multistatus *multistatus;
xmlNodePtr prop_node;
guint status_code;
};
static gboolean
2023-02-15 15:41:32 +08:00
multistatus_parse (SoupMessage *msg,
GInputStream *body,
Multistatus *multistatus,
GError **error)
2022-06-29 16:07:13 +08:00
{
xmlDocPtr doc;
xmlNodePtr root;
2023-02-15 15:41:32 +08:00
GUri *uri;
2022-06-29 16:07:13 +08:00
2023-02-15 15:41:32 +08:00
doc = parse_xml (msg, body, &root, "multistatus", error);
2022-06-29 16:07:13 +08:00
if (doc == NULL)
return FALSE;
uri = soup_message_get_uri (msg);
multistatus->doc = doc;
multistatus->root = root;
multistatus->target = uri;
2023-02-15 15:41:32 +08:00
multistatus->path = g_uri_unescape_string (g_uri_get_path (uri), "/");
2022-06-29 16:07:13 +08:00
return TRUE;
}
static void
multistatus_free (Multistatus *multistatus)
{
xmlFreeDoc (multistatus->doc);
g_free (multistatus->path);
}
static void
multistatus_get_response_iter (Multistatus *multistatus, xmlNodeIter *iter)
{
iter->cur_node = multistatus->root->children;
iter->next_node = multistatus->root->children;
iter->name = "response";
iter->ns_href = "DAV:";
iter->user_data = multistatus;
}
static gboolean
multistatus_get_response (xmlNodeIter *resp_iter, MsResponse *response)
{
Multistatus *multistatus;
xmlNodePtr resp_node;
xmlNodePtr iter;
xmlNodePtr href;
xmlNodePtr propstat;
2023-02-15 15:41:32 +08:00
GUri *uri;
2022-06-29 16:07:13 +08:00
const char *text;
char *path;
multistatus = xml_node_iter_get_user_data (resp_iter);
resp_node = xml_node_iter_get_current (resp_iter);
if (resp_node == NULL)
return FALSE;
propstat = NULL;
href = NULL;
for (iter = resp_node->children; iter; iter = iter->next)
{
if (! node_is_element (iter))
{
continue;
}
else if (node_has_name_ns (iter, "href", "DAV:"))
{
href = iter;
}
else if (node_has_name_ns (iter, "propstat", "DAV:"))
{
if (propstat == NULL)
propstat = iter;
}
if (href && propstat)
break;
}
if (href == NULL)
return FALSE;
text = node_get_content (href);
if (text == NULL)
return FALSE;
2023-02-15 15:41:32 +08:00
uri = g_uri_parse_relative (multistatus->target, text, SOUP_HTTP_URI_FLAGS, NULL);
2022-06-29 16:07:13 +08:00
if (uri == NULL)
return FALSE;
2023-02-15 15:41:32 +08:00
path = g_uri_unescape_string (g_uri_get_path (uri), "/");
g_uri_unref (uri);
2022-06-29 16:07:13 +08:00
response->path = path;
response->is_target = path_equal (path, multistatus->path, TRUE);
response->multistatus = multistatus;
response->first_propstat = propstat;
return resp_node != NULL;
}
static void
ms_response_clear (MsResponse *response)
{
g_free (response->path);
}
static char *
ms_response_get_basename (MsResponse *response)
{
return http_path_get_basename (response->path);
}
static void
ms_response_get_propstat_iter (MsResponse *response, xmlNodeIter *iter)
{
iter->cur_node = response->first_propstat;
iter->next_node = response->first_propstat;
iter->name = "propstat";
iter->ns_href = "DAV:";
iter->user_data = response;
}
static guint
ms_response_get_propstat (xmlNodeIter *cur_node, MsPropstat *propstat)
{
MsResponse *response;
xmlNodePtr pstat_node;
xmlNodePtr iter;
xmlNodePtr prop;
xmlNodePtr status;
const char *status_text;
gboolean res;
guint code;
response = xml_node_iter_get_user_data (cur_node);
pstat_node = xml_node_iter_get_current (cur_node);
if (pstat_node == NULL)
return 0;
status = NULL;
prop = NULL;
for (iter = pstat_node->children; iter; iter = iter->next)
{
if (!node_is_element (iter))
{
continue;
}
else if (node_has_name_ns (iter, "status", "DAV:"))
{
status = iter;
}
else if (node_has_name_ns (iter, "prop", "DAV:"))
{
prop = iter;
}
if (status && prop)
break;
}
status_text = node_get_content (status);
if (status_text == NULL || prop == NULL)
return 0;
res = soup_headers_parse_status_line ((char *) status_text,
NULL,
&code,
NULL);
if (res == FALSE)
return 0;
propstat->prop_node = prop;
propstat->status_code = code;
propstat->multistatus = response->multistatus;
return code;
}
static GFileType
parse_resourcetype (xmlNodePtr rt)
{
xmlNodePtr node;
GFileType type;
for (node = rt->children; node; node = node->next)
{
if (node_is_element (node))
break;
}
if (node == NULL)
return G_FILE_TYPE_REGULAR;
if (! strcmp ((char *) node->name, "collection"))
type = G_FILE_TYPE_DIRECTORY;
else if (! strcmp ((char *) node->name, "redirectref"))
type = G_FILE_TYPE_SYMBOLIC_LINK;
else
type = G_FILE_TYPE_UNKNOWN;
return type;
}
static inline void
file_info_set_content_type (GFileInfo *info,
const char *type,
gboolean uncertain_content_type)
{
if (!uncertain_content_type)
g_file_info_set_content_type (info, type);
g_file_info_set_attribute_string (info,
G_FILE_ATTRIBUTE_STANDARD_FAST_CONTENT_TYPE,
type);
}
static void
ms_response_to_file_info (MsResponse *response,
GFileInfo *info)
{
xmlNodeIter iter;
MsPropstat propstat;
xmlNodePtr node;
guint status;
char *basename;
const char *text;
GFileType file_type;
char *mime_type;
gboolean uncertain_content_type;
GIcon *icon;
GIcon *symbolic_icon;
gboolean have_display_name;
basename = ms_response_get_basename (response);
g_file_info_set_name (info, basename);
g_file_info_set_edit_name (info, basename);
if (basename && basename[0] == '.')
g_file_info_set_is_hidden (info, TRUE);
g_file_info_set_attribute_boolean (info, G_FILE_ATTRIBUTE_ACCESS_CAN_TRASH, FALSE);
file_type = G_FILE_TYPE_REGULAR;
mime_type = NULL;
uncertain_content_type = FALSE;
have_display_name = FALSE;
ms_response_get_propstat_iter (response, &iter);
while (xml_node_iter_next (&iter))
{
status = ms_response_get_propstat (&iter, &propstat);
if (! SOUP_STATUS_IS_SUCCESSFUL (status))
continue;
for (node = propstat.prop_node->children; node; node = node->next)
{
if (! node_is_element (node) || node_is_empty (node))
continue; /* TODO: check namespace, parse user data nodes*/
text = node_get_content (node);
if (node_has_name (node, "resourcetype"))
{
file_type = parse_resourcetype (node);
}
else if (node_has_name (node, "displayname") && text)
{
g_file_info_set_display_name (info, text);
have_display_name = TRUE;
}
else if (node_has_name (node, "getetag"))
{
g_file_info_set_attribute_string (info, G_FILE_ATTRIBUTE_ETAG_VALUE,
text);
}
else if (node_has_name (node, "creationdate"))
{
GDateTime *dt;
if ((dt = g_date_time_new_from_iso8601 (text, NULL)) == NULL)
continue;
g_file_info_set_attribute_uint64 (info,
G_FILE_ATTRIBUTE_TIME_CREATED,
g_date_time_to_unix (dt));
g_date_time_unref (dt);
}
else if (node_has_name (node, "getcontenttype"))
{
char *ptr;
mime_type = g_strdup (text);
/* Ignore parameters of the content type */
ptr = strchr (mime_type, ';');
if (ptr)
{
do
*ptr-- = '\0';
while (ptr >= mime_type && g_ascii_isspace (*ptr));
}
}
else if (node_has_name (node, "getcontentlength"))
{
gint64 size;
size = g_ascii_strtoll (text, NULL, 10);
g_file_info_set_size (info, size);
}
else if (node_has_name (node, "getlastmodified"))
{
2023-02-15 15:41:32 +08:00
GDateTime *gd;
2022-06-29 16:07:13 +08:00
2023-02-15 15:41:32 +08:00
gd = soup_date_time_new_from_http_string (text);
if (gd)
{
g_file_info_set_attribute_uint64 (info, G_FILE_ATTRIBUTE_TIME_MODIFIED, g_date_time_to_unix (gd));
2022-06-29 16:07:13 +08:00
g_file_info_set_attribute_uint32 (info, G_FILE_ATTRIBUTE_TIME_MODIFIED_USEC, 0);
2023-02-15 15:41:32 +08:00
g_date_time_unref (gd);
}
}
2022-06-29 16:07:13 +08:00
}
}
g_file_info_set_file_type (info, file_type);
if (file_type == G_FILE_TYPE_DIRECTORY)
{
g_clear_pointer (&mime_type, g_free);
mime_type = g_strdup ("inode/directory");
icon = g_content_type_get_icon (mime_type);
symbolic_icon = g_content_type_get_symbolic_icon (mime_type);
file_info_set_content_type (info, mime_type, FALSE);
/* Ignore file size for directories. Most of the servers don't report it
* for directories anyway. However, some servers report total size of
* files inside the directory, which is not expected and causes issues
* for clients.
*/
g_file_info_remove_attribute (info, G_FILE_ATTRIBUTE_STANDARD_SIZE);
}
else
{
if (mime_type == NULL)
mime_type = g_content_type_guess (basename, NULL, 0, &uncertain_content_type);
icon = g_content_type_get_icon (mime_type);
if (G_IS_THEMED_ICON (icon))
{
g_themed_icon_append_name (G_THEMED_ICON (icon), "text-x-generic");
}
symbolic_icon = g_content_type_get_symbolic_icon (mime_type);
if (G_IS_THEMED_ICON (icon))
{
g_themed_icon_append_name (G_THEMED_ICON (symbolic_icon), "text-x-generic-symbolic");
}
file_info_set_content_type (info, mime_type, uncertain_content_type);
}
if (have_display_name == FALSE)
g_file_info_set_display_name (info, basename);
g_file_info_set_icon (info, icon);
g_file_info_set_symbolic_icon (info, symbolic_icon);
g_object_unref (icon);
g_object_unref (symbolic_icon);
g_free (mime_type);
g_free (basename);
}
static void
ms_response_to_fs_info (MsResponse *response,
GFileInfo *info)
{
xmlNodeIter iter;
MsPropstat propstat;
xmlNodePtr node;
guint status;
const char *text;
guint64 bytes_avail;
guint64 bytes_used;
gboolean have_bytes_avail;
gboolean have_bytes_used;
bytes_avail = bytes_used = 0;
have_bytes_avail = have_bytes_used = FALSE;
ms_response_get_propstat_iter (response, &iter);
while (xml_node_iter_next (&iter))
{
status = ms_response_get_propstat (&iter, &propstat);
if (! SOUP_STATUS_IS_SUCCESSFUL (status))
continue;
for (node = propstat.prop_node->children; node; node = node->next)
{
if (! node_is_element (node))
continue;
text = node_get_content (node);
if (text == NULL)
continue;
if (node_has_name (node, "quota-available-bytes"))
{
if (! string_to_uint64 (text, &bytes_avail))
continue;
have_bytes_avail = TRUE;
}
else if (node_has_name (node, "quota-used-bytes"))
{
if (! string_to_uint64 (text, &bytes_used))
continue;
have_bytes_used = TRUE;
}
}
}
if (have_bytes_used)
g_file_info_set_attribute_uint64 (info,
G_FILE_ATTRIBUTE_FILESYSTEM_USED,
bytes_used);
if (have_bytes_avail)
{
g_file_info_set_attribute_uint64 (info,
G_FILE_ATTRIBUTE_FILESYSTEM_FREE,
bytes_avail);
if (have_bytes_used && G_MAXUINT64 - bytes_avail >= bytes_used)
g_file_info_set_attribute_uint64 (info,
G_FILE_ATTRIBUTE_FILESYSTEM_SIZE,
bytes_avail + bytes_used);
}
}
#define PROPSTAT_XML_BEGIN \
"<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n" \
" <D:propfind xmlns:D=\"DAV:\">\n"
#define PROPSTAT_XML_ALLPROP " <D:allprop/>\n"
#define PROPSTAT_XML_PROP_BEGIN " <D:prop>\n"
#define PROPSTAT_XML_PROP_END " </D:prop>\n"
#define PROPSTAT_XML_END \
" </D:propfind>"
typedef struct _PropName {
const char *name;
const char *namespace;
} PropName;
2023-02-15 15:41:32 +08:00
static void
propfind_stat_msg_starting_restarted (SoupMessage *msg, gpointer user_data)
{
soup_message_set_request_body_from_bytes (msg, "application/xml", user_data);
}
static void
propfind_stat_bytes_unref (gpointer data, GClosure *closure)
{
(void)closure;
g_bytes_unref (data);
}
2022-06-29 16:07:13 +08:00
static SoupMessage *
propfind_request_new (GVfsBackend *backend,
const char *filename,
guint depth,
const PropName *properties)
{
SoupMessage *msg;
2023-02-15 15:41:32 +08:00
GUri *uri;
2022-06-29 16:07:13 +08:00
const char *header_depth;
GString *body;
2023-02-15 15:41:32 +08:00
GBytes *bytes;
2022-06-29 16:07:13 +08:00
uri = g_vfs_backend_dav_uri_for_path (backend, filename, depth > 0);
msg = soup_message_new_from_uri (SOUP_METHOD_PROPFIND, uri);
2023-02-15 15:41:32 +08:00
g_uri_unref (uri);
2022-06-29 16:07:13 +08:00
if (msg == NULL)
return NULL;
if (depth == 0)
header_depth = "0";
else if (depth == 1)
header_depth = "1";
else
header_depth = "infinity";
2023-02-15 15:41:32 +08:00
soup_message_headers_append (soup_message_get_request_headers (msg),
"Depth", header_depth);
2022-06-29 16:07:13 +08:00
body = g_string_new (PROPSTAT_XML_BEGIN);
if (properties != NULL)
{
const PropName *prop;
g_string_append (body, PROPSTAT_XML_PROP_BEGIN);
for (prop = properties; prop->name; prop++)
{
if (prop->namespace != NULL)
g_string_append_printf (body, "<%s xmlns=\"%s\"/>\n",
prop->name,
prop->namespace);
else
g_string_append_printf (body, "<D:%s/>\n", prop->name);
}
g_string_append (body, PROPSTAT_XML_PROP_END);
}
else
g_string_append (body, PROPSTAT_XML_ALLPROP);
g_string_append (body, PROPSTAT_XML_END);
2023-02-15 15:41:32 +08:00
bytes = g_string_free_to_bytes (body);
2022-06-29 16:07:13 +08:00
2023-02-15 15:41:32 +08:00
g_signal_connect_data (msg, "starting",
G_CALLBACK (propfind_stat_msg_starting_restarted),
g_bytes_ref (bytes), propfind_stat_bytes_unref, 0);
/* only called with implicit redirects enabled */
g_signal_connect_data (msg, "restarted",
G_CALLBACK (propfind_stat_msg_starting_restarted),
g_bytes_ref (bytes), propfind_stat_bytes_unref, 0);
g_bytes_unref (bytes);
2022-06-29 16:07:13 +08:00
return msg;
}
static SoupMessage *
2023-02-15 15:41:32 +08:00
stat_location_start (GUri *uri,
gboolean count_children)
2022-06-29 16:07:13 +08:00
{
SoupMessage *msg;
const char *depth;
2023-02-15 15:41:32 +08:00
GBytes *bytes;
2022-06-29 16:07:13 +08:00
static const char *stat_profind_body =
PROPSTAT_XML_BEGIN
PROPSTAT_XML_PROP_BEGIN
"<D:resourcetype/>\n"
"<D:getcontentlength/>\n"
PROPSTAT_XML_PROP_END
PROPSTAT_XML_END;
msg = soup_message_new_from_uri (SOUP_METHOD_PROPFIND, uri);
if (count_children)
depth = "1";
else
depth = "0";
2023-02-15 15:41:32 +08:00
soup_message_headers_append (soup_message_get_request_headers (msg),
"Depth", depth);
bytes = g_bytes_new (stat_profind_body, strlen (stat_profind_body));
g_signal_connect_data (msg, "starting",
G_CALLBACK (propfind_stat_msg_starting_restarted),
g_bytes_ref (bytes), propfind_stat_bytes_unref, 0);
/* only called with implicit redirects enabled */
g_signal_connect_data (msg, "restarted",
G_CALLBACK (propfind_stat_msg_starting_restarted),
g_bytes_ref (bytes), propfind_stat_bytes_unref, 0);
g_bytes_unref (bytes);
2022-06-29 16:07:13 +08:00
return msg;
}
static gboolean
2023-02-15 15:41:32 +08:00
stat_location_end (SoupMessage *msg,
GInputStream *body,
GFileType *target_type,
gint64 *target_size,
guint *num_children)
2022-06-29 16:07:13 +08:00
{
Multistatus ms;
xmlNodeIter iter;
gboolean res;
guint child_count;
GFileInfo *file_info;
2023-02-15 15:41:32 +08:00
if (!body || soup_message_get_status (msg) != SOUP_STATUS_MULTI_STATUS)
2022-06-29 16:07:13 +08:00
return FALSE;
2023-02-15 15:41:32 +08:00
res = multistatus_parse (msg, body, &ms, NULL);
2022-06-29 16:07:13 +08:00
if (res == FALSE)
return FALSE;
res = FALSE;
child_count = 0;
file_info = g_file_info_new ();
multistatus_get_response_iter (&ms, &iter);
while (xml_node_iter_next (&iter))
{
MsResponse response;
if (! multistatus_get_response (&iter, &response))
continue;
if (response.is_target)
{
ms_response_to_file_info (&response, file_info);
res = TRUE;
}
else
child_count++;
ms_response_clear (&response);
}
if (res)
{
if (target_type)
*target_type = g_file_info_get_file_type (file_info);
if (target_size)
*target_size = g_file_info_get_size (file_info);
if (num_children)
*num_children = child_count;
}
multistatus_free (&ms);
g_object_unref (file_info);
return res;
}
2023-02-15 15:41:32 +08:00
typedef struct _StatLocationData {
GUri *uri;
GFileType target_type;
gint64 target_size;
guint num_children;
} StatLocationData;
static void
stat_location_data_free (gpointer p)
2022-06-29 16:07:13 +08:00
{
2023-02-15 15:41:32 +08:00
g_uri_unref (((StatLocationData *)p)->uri);
g_slice_free (StatLocationData, p);
}
2022-06-29 16:07:13 +08:00
2023-02-15 15:41:32 +08:00
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 res;
2022-06-29 16:07:13 +08:00
2023-02-15 15:41:32 +08:00
body = g_vfs_backend_dav_send_finish (backend, result, &error);
2022-06-29 16:07:13 +08:00
2023-02-15 15:41:32 +08:00
if (!body)
{
g_object_unref (msg);
g_task_return_error (task, error);
g_object_unref (task);
return;
}
2022-06-29 16:07:13 +08:00
2023-02-15 15:41:32 +08:00
status = soup_message_get_status (msg);
if (status != SOUP_STATUS_MULTI_STATUS)
2022-06-29 16:07:13 +08:00
{
2023-02-15 15:41:32 +08:00
error = g_error_new (G_IO_ERROR,
http_error_code_from_status (status),
_("HTTP Error: %s"),
soup_message_get_reason_phrase (msg));
2022-06-29 16:07:13 +08:00
g_object_unref (msg);
2023-02-15 15:41:32 +08:00
g_task_return_error (task, error);
g_object_unref (body);
g_object_unref (task);
return;
2022-06-29 16:07:13 +08:00
}
2023-02-15 15:41:32 +08:00
res = stat_location_end (msg, body, &data->target_type,
&data->target_size, &data->num_children);
2022-06-29 16:07:13 +08:00
g_object_unref (msg);
2023-02-15 15:41:32 +08:00
g_object_unref (body);
2022-06-29 16:07:13 +08:00
if (res == FALSE)
2023-02-15 15:41:32 +08:00
{
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);
2022-06-29 16:07:13 +08:00
2023-02-15 15:41:32 +08:00
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;
2022-06-29 16:07:13 +08:00
}
2023-02-15 15:41:32 +08:00
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;
}
2022-06-29 16:07:13 +08:00
/* ************************************************************************* */
/* Authentication */
static void
mount_auth_info_free (MountAuthData *data)
{
if (data->mount_source)
g_object_unref (data->mount_source);
g_free (data->server_auth.username);
g_free (data->server_auth.password);
g_free (data->server_auth.realm);
g_free (data->proxy_auth.username);
g_free (data->proxy_auth.password);
}
2023-02-15 15:41:32 +08:00
static gboolean
soup_authenticate (SoupMessage *msg,
SoupAuth *auth,
gboolean retrying,
gpointer user_data)
2022-06-29 16:07:13 +08:00
{
MountAuthData *data;
AuthInfo *info;
GAskPasswordFlags pw_ask_flags;
GPasswordSave pw_save;
const char *realm;
gboolean res;
gboolean aborted;
gboolean is_proxy;
gboolean have_auth;
char *new_username;
char *new_password;
char *prompt;
data = (MountAuthData *) user_data;
2023-02-15 15:41:32 +08:00
is_proxy = soup_auth_is_for_proxy (auth);
if (is_proxy)
info = &(data->proxy_auth);
else
info = &(data->server_auth);
if (!data->interactive)
{
g_debug ("+ soup_authenticate (%s) \n",
retrying ? "retrying" : "first auth");
if (!retrying)
soup_auth_authenticate (auth, info->username, info->password);
return FALSE;
}
if (data->retrying_after_403)
retrying = TRUE;
2022-06-29 16:07:13 +08:00
2023-02-15 15:41:32 +08:00
g_debug ("+ soup_authenticate (interactive, %s) \n",
2022-06-29 16:07:13 +08:00
retrying ? "retrying" : "first auth");
new_username = NULL;
new_password = NULL;
realm = NULL;
pw_ask_flags = G_ASK_PASSWORD_NEED_PASSWORD;
realm = soup_auth_get_realm (auth);
if (realm && info->realm == NULL)
info->realm = g_strdup (realm);
else if (realm && info->realm && !g_str_equal (realm, info->realm))
2023-02-15 15:41:32 +08:00
return FALSE;
2022-06-29 16:07:13 +08:00
have_auth = info->username && info->password;
if (have_auth == FALSE && g_vfs_keyring_is_available ())
{
2023-02-15 15:41:32 +08:00
const char *host;
gchar *hostp = NULL;
gint port;
2022-06-29 16:07:13 +08:00
pw_ask_flags |= G_ASK_PASSWORD_SAVING_SUPPORTED;
if (is_proxy)
{
2023-02-15 15:41:32 +08:00
gboolean ret;
gchar *urip;
urip = g_strdup_printf ("http://%s", soup_auth_get_authority (auth));
ret = g_uri_split_network (urip, G_URI_FLAGS_NONE,
NULL, &hostp, &port, NULL);
g_free (urip);
if (!ret)
return FALSE;
host = hostp;
2022-06-29 16:07:13 +08:00
}
else
2023-02-15 15:41:32 +08:00
{
GUri *uri = soup_message_get_uri (msg);
host = g_uri_get_host (uri);
port = g_uri_get_port (uri);
}
2022-06-29 16:07:13 +08:00
res = g_vfs_keyring_lookup_password (info->username,
2023-02-15 15:41:32 +08:00
host,
2022-06-29 16:07:13 +08:00
NULL,
"http",
realm,
is_proxy ? "proxy" : "basic",
2023-02-15 15:41:32 +08:00
port,
2022-06-29 16:07:13 +08:00
&new_username,
NULL,
&new_password);
if (res == TRUE)
{
have_auth = TRUE;
g_free (info->username);
g_free (info->password);
info->username = new_username;
info->password = new_password;
}
2023-02-15 15:41:32 +08:00
if (hostp)
g_free (hostp);
2022-06-29 16:07:13 +08:00
}
if (retrying == FALSE && have_auth)
{
soup_auth_authenticate (auth, info->username, info->password);
2023-02-15 15:41:32 +08:00
return FALSE;
2022-06-29 16:07:13 +08:00
}
if (is_proxy == FALSE)
{
if (realm == NULL)
realm = _("WebDAV share");
2023-02-15 15:41:32 +08:00
/* Translators: %s is the name of the WebDAV share */
prompt = g_strdup_printf (_("Authentication Required\nEnter password for “%s”:"), realm);
2022-06-29 16:07:13 +08:00
}
else
2023-02-15 15:41:32 +08:00
prompt = g_strdup (_("Authentication Required\nEnter proxy password:"));
2022-06-29 16:07:13 +08:00
if (info->username == NULL)
pw_ask_flags |= G_ASK_PASSWORD_NEED_USERNAME;
res = g_mount_source_ask_password (data->mount_source,
prompt,
info->username,
NULL,
pw_ask_flags,
&aborted,
&new_password,
&new_username,
NULL,
NULL,
&pw_save);
if (res && !aborted)
{
/* it's not safe to assume that we get the username filed in,
in the case that we provied a default username */
if (new_username == NULL)
new_username = g_strdup (info->username);
soup_auth_authenticate (auth, new_username, new_password);
g_free (info->username);
g_free (info->password);
info->username = new_username;
info->password = new_password;
info->pw_save = pw_save;
}
else
2023-02-15 15:41:32 +08:00
soup_auth_cancel (auth);
2022-06-29 16:07:13 +08:00
g_debug ("- soup_authenticate \n");
g_free (prompt);
2023-02-15 15:41:32 +08:00
return !res || aborted;
2022-06-29 16:07:13 +08:00
}
static void
keyring_save_authinfo (AuthInfo *info,
2023-02-15 15:41:32 +08:00
GUri *uri,
2022-06-29 16:07:13 +08:00
gboolean is_proxy)
{
const char *type = is_proxy ? "proxy" : "basic";
g_vfs_keyring_save_password (info->username,
2023-02-15 15:41:32 +08:00
g_uri_get_host (uri),
2022-06-29 16:07:13 +08:00
NULL,
"http",
info->realm,
type,
2023-02-15 15:41:32 +08:00
g_uri_get_port (uri),
2022-06-29 16:07:13 +08:00
info->password,
info->pw_save);
}
/* ************************************************************************* */
2023-02-15 15:41:32 +08:00
static GUri *
2022-06-29 16:07:13 +08:00
g_mount_spec_to_dav_uri (GMountSpec *spec)
{
2023-02-15 15:41:32 +08:00
GUri *uri;
2022-06-29 16:07:13 +08:00
const char *host;
const char *port;
const char *ssl;
const char *path;
2023-02-15 15:41:32 +08:00
const char *scheme;
char *host_str;
char *path_str;
2022-06-29 16:07:13 +08:00
gint port_num;
host = g_mount_spec_get (spec, "host");
port = g_mount_spec_get (spec, "port");
ssl = g_mount_spec_get (spec, "ssl");
path = spec->mount_prefix;
if (host == NULL || *host == 0)
return NULL;
if (ssl != NULL && (strcmp (ssl, "true") == 0))
2023-02-15 15:41:32 +08:00
scheme = "https";
2022-06-29 16:07:13 +08:00
else
2023-02-15 15:41:32 +08:00
scheme = "http";
port_num = -1;
2022-06-29 16:07:13 +08:00
2023-02-15 15:41:32 +08:00
/* always a valid number if set */
if (port != NULL)
port_num = atoi (port);
2022-06-29 16:07:13 +08:00
/* IPv6 host does not include brackets in SoupURI, but GMountSpec host does */
if (gvfs_is_ipv6 (host))
2023-02-15 15:41:32 +08:00
host_str = g_strndup (host + 1, strlen (host) - 2);
2022-06-29 16:07:13 +08:00
else
2023-02-15 15:41:32 +08:00
host_str = g_strdup (host);
2022-06-29 16:07:13 +08:00
2023-02-15 15:41:32 +08:00
path_str = dav_uri_encode (path);
2022-06-29 16:07:13 +08:00
2023-02-15 15:41:32 +08:00
/* A username is not part of URI as a workaround for:
* https://gitlab.gnome.org/GNOME/gvfs/-/issues/617.
*/
uri = g_uri_build (SOUP_HTTP_URI_FLAGS,
scheme,
NULL,
host_str,
port_num,
path_str,
NULL,
NULL);
g_free (host_str);
g_free (path_str);
2022-06-29 16:07:13 +08:00
return uri;
}
static GMountSpec *
g_mount_spec_from_dav_uri (GVfsBackendDav *dav_backend,
2023-02-15 15:41:32 +08:00
GUri *uri)
2022-06-29 16:07:13 +08:00
{
GMountSpec *spec;
2023-02-15 15:41:32 +08:00
char *local_path;
gboolean ssl;
gint port_num;
2022-06-29 16:07:13 +08:00
#ifdef HAVE_AVAHI
if (dav_backend->resolver != NULL)
{
const char *type;
const char *service_type;
service_type = g_vfs_dns_sd_resolver_get_service_type (dav_backend->resolver);
if (strcmp (service_type, "_webdavs._tcp") == 0)
type = "davs+sd";
else
type = "dav+sd";
spec = g_mount_spec_new (type);
g_mount_spec_set (spec,
"host",
g_vfs_dns_sd_resolver_get_encoded_triple (dav_backend->resolver));
return spec;
}
#endif
spec = g_mount_spec_new ("dav");
2023-02-15 15:41:32 +08:00
/* IPv6 host does not include brackets in GUri, but GMountSpec host does */
if (strchr (g_uri_get_host (uri), ':'))
2022-06-29 16:07:13 +08:00
{
2023-02-15 15:41:32 +08:00
char *host = g_strdup_printf ("[%s]", g_uri_get_host (uri));
2022-06-29 16:07:13 +08:00
g_mount_spec_set (spec, "host", host);
g_free (host);
}
else
2023-02-15 15:41:32 +08:00
g_mount_spec_set (spec, "host", g_uri_get_host (uri));
2022-06-29 16:07:13 +08:00
2023-02-15 15:41:32 +08:00
ssl = !strcmp (g_uri_get_scheme (uri), "https");
2022-06-29 16:07:13 +08:00
2023-02-15 15:41:32 +08:00
g_mount_spec_set (spec, "ssl", ssl ? "true" : "false");
2022-06-29 16:07:13 +08:00
2023-02-15 15:41:32 +08:00
port_num = g_uri_get_port (uri);
if (port_num > 0 && port_num != (ssl ? 443 : 80))
2022-06-29 16:07:13 +08:00
{
2023-02-15 15:41:32 +08:00
char *port = g_strdup_printf ("%d", port_num);
2022-06-29 16:07:13 +08:00
g_mount_spec_set (spec, "port", port);
g_free (port);
}
/* There must not be any illegal characters in the
URL at this point */
2023-02-15 15:41:32 +08:00
local_path = g_uri_unescape_string (g_uri_get_path (uri), "/");
2022-06-29 16:07:13 +08:00
g_mount_spec_set_mount_prefix (spec, local_path);
g_free (local_path);
return spec;
}
#ifdef HAVE_AVAHI
2023-02-15 15:41:32 +08:00
static GUri *
2022-06-29 16:07:13 +08:00
dav_uri_from_dns_sd_resolver (GVfsBackendDav *dav_backend)
{
2023-02-15 15:41:32 +08:00
GUri *uri;
2022-06-29 16:07:13 +08:00
char *user;
char *path;
char *address;
2023-02-15 15:41:32 +08:00
char *host;
2022-06-29 16:07:13 +08:00
gchar *interface;
const char *service_type;
2023-02-15 15:41:32 +08:00
const char *scheme;
2022-06-29 16:07:13 +08:00
guint port;
service_type = g_vfs_dns_sd_resolver_get_service_type (dav_backend->resolver);
address = g_vfs_dns_sd_resolver_get_address (dav_backend->resolver);
interface = g_vfs_dns_sd_resolver_get_interface (dav_backend->resolver);
port = g_vfs_dns_sd_resolver_get_port (dav_backend->resolver);
user = g_vfs_dns_sd_resolver_lookup_txt_record (dav_backend->resolver, "u"); /* mandatory */
path = g_vfs_dns_sd_resolver_lookup_txt_record (dav_backend->resolver, "path"); /* optional */
/* TODO: According to http://www.dns-sd.org/ServiceTypes.html
* there's also a TXT record "p" for password. Handle this.
*/
if (strcmp (service_type, "_webdavs._tcp") == 0)
2023-02-15 15:41:32 +08:00
scheme = "https";
2022-06-29 16:07:13 +08:00
else
2023-02-15 15:41:32 +08:00
scheme = "http";
2022-06-29 16:07:13 +08:00
2023-02-15 15:41:32 +08:00
/* IPv6 host does not include brackets in GUri, but GVfsDnsSdResolver host does */
2022-06-29 16:07:13 +08:00
if (gvfs_is_ipv6 (address))
{
/* Link-local addresses require interface to be specified. */
if (g_str_has_prefix (address, "[fe80:") && interface != NULL)
{
2023-02-15 15:41:32 +08:00
host = g_strconcat (address + 1, interface, NULL);
host[strlen (address) - 2] = '%';
2022-06-29 16:07:13 +08:00
}
else
2023-02-15 15:41:32 +08:00
host = g_strndup (address + 1, strlen (address) - 2);
2022-06-29 16:07:13 +08:00
}
else
2023-02-15 15:41:32 +08:00
host = g_strdup (address);
2022-06-29 16:07:13 +08:00
2023-02-15 15:41:32 +08:00
uri = g_uri_build_with_user (SOUP_HTTP_URI_FLAGS,
scheme,
user,
NULL,
NULL,
host,
port,
path,
NULL,
NULL);
2022-06-29 16:07:13 +08:00
g_free (address);
g_free (interface);
g_free (user);
g_free (path);
2023-02-15 15:41:32 +08:00
g_free (host);
2022-06-29 16:07:13 +08:00
return uri;
}
#endif
#ifdef HAVE_AVAHI
static void
dns_sd_resolver_changed (GVfsDnsSdResolver *resolver,
GVfsBackendDav *dav_backend)
{
/* If anything has changed (e.g. address, port, txt-records or is-resolved),
* it is safest to just unmount. */
g_vfs_backend_force_unmount (G_VFS_BACKEND (dav_backend));
}
#endif
/* ************************************************************************* */
/* Backend Functions */
2023-02-15 15:41:32 +08:00
2022-06-29 16:07:13 +08:00
static void
2023-02-15 15:41:32 +08:00
mount_success (GVfsBackend *backend, GVfsJobMount *job)
2022-06-29 16:07:13 +08:00
{
GVfsBackendDav *dav_backend = G_VFS_BACKEND_DAV (backend);
2023-02-15 15:41:32 +08:00
GVfsBackendHttp *http_backend = G_VFS_BACKEND_HTTP (backend);
GMountSpec *mount_spec;
GUri *tmp;
const gchar *user;
2022-06-29 16:07:13 +08:00
2023-02-15 15:41:32 +08:00
/* 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 */
2022-06-29 16:07:13 +08:00
2023-02-15 15:41:32 +08:00
/* 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);
2022-06-29 16:07:13 +08:00
2023-02-15 15:41:32 +08:00
/* dup the mountspec, but only copy known fields */
mount_spec = g_mount_spec_from_dav_uri (dav_backend, http_backend->mount_base);
2022-06-29 16:07:13 +08:00
2023-02-15 15:41:32 +08:00
/* A username is not part of URI as a workaround for:
* https://gitlab.gnome.org/GNOME/gvfs/-/issues/617
* So it has to be restored here in order to avoid:
* https://gitlab.gnome.org/GNOME/gvfs/-/issues/614
*/
user = g_mount_spec_get (job->mount_spec, "user");
if (user != NULL)
g_mount_spec_set (mount_spec, "user", user);
2022-06-29 16:07:13 +08:00
2023-02-15 15:41:32 +08:00
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);
2022-06-29 16:07:13 +08:00
2023-02-15 15:41:32 +08:00
g_vfs_job_succeeded (G_VFS_JOB (job));
g_debug ("- mount\n");
}
2022-06-29 16:07:13 +08:00
2023-02-15 15:41:32 +08:00
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)
2022-06-29 16:07:13 +08:00
{
2023-02-15 15:41:32 +08:00
g_vfs_job_failed_from_error (G_VFS_JOB (job), error);
g_error_free (error);
goto clear_msg;
2022-06-29 16:07:13 +08:00
}
2023-02-15 15:41:32 +08:00
res = stat_location_end (msg_stat, body, &file_type, NULL, NULL);
is_collection = res && file_type == G_FILE_TYPE_DIRECTORY;
2022-06-29 16:07:13 +08:00
2023-02-15 15:41:32 +08:00
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, job);
g_error_free (error);
goto clear_msg;
}
2022-06-29 16:07:13 +08:00
2023-02-15 15:41:32 +08:00
g_object_unref (body);
2022-06-29 16:07:13 +08:00
2023-02-15 15:41:32 +08:00
soup_message_headers_clear (soup_message_get_response_headers (msg_stat));
2022-06-29 16:07:13 +08:00
2023-02-15 15:41:32 +08:00
g_debug (" [%s] webdav: %d, collection %d [res: %d]\n",
g_uri_get_path (http_backend->mount_base), TRUE, is_collection, res);
2022-06-29 16:07:13 +08:00
2023-02-15 15:41:32 +08:00
if ((is_collection == FALSE) && (dav_backend->last_good_path != NULL))
{
mount_success (backend, 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;
}
2022-06-29 16:07:13 +08:00
2023-02-15 15:41:32 +08:00
/* 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);
2022-06-29 16:07:13 +08:00
2023-02-15 15:41:32 +08:00
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);
2022-06-29 16:07:13 +08:00
2023-02-15 15:41:32 +08:00
g_free (new_path);
2022-06-29 16:07:13 +08:00
2023-02-15 15:41:32 +08:00
/* 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;
2022-06-29 16:07:13 +08:00
2023-02-15 15:41:32 +08:00
/* break out */
if (g_strcmp0 (dav_backend->last_good_path, "/") == 0)
{
mount_success (backend, job);
goto clear_msg;
}
2022-06-29 16:07:13 +08:00
2023-02-15 15:41:32 +08:00
/* else loop the whole thing from options */
2022-06-29 16:07:13 +08:00
2023-02-15 15:41:32 +08:00
msg_opts = soup_message_new_from_uri (SOUP_METHOD_OPTIONS,
http_backend->mount_base);
2022-06-29 16:07:13 +08:00
2023-02-15 15:41:32 +08:00
dav_message_connect_signals (msg_opts, backend);
2022-06-29 16:07:13 +08:00
2023-02-15 15:41:32 +08:00
g_vfs_backend_dav_send_async (backend, msg_opts, try_mount_opts_cb, job);
2022-06-29 16:07:13 +08:00
2023-02-15 15:41:32 +08:00
clear_msg:
g_object_unref (msg_stat);
}
2022-06-29 16:07:13 +08:00
2023-02-15 15:41:32 +08:00
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;
2022-06-29 16:07:13 +08:00
2023-02-15 15:41:32 +08:00
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;
2022-06-29 16:07:13 +08:00
2023-02-15 15:41:32 +08:00
/* 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);
2022-06-29 16:07:13 +08:00
2023-02-15 15:41:32 +08:00
dav_message_connect_signals (msg_opts, backend);
2022-06-29 16:07:13 +08:00
2023-02-15 15:41:32 +08:00
g_vfs_backend_dav_send_async (backend, msg_opts,
try_mount_opts_cb, job);
return;
}
}
2022-06-29 16:07:13 +08:00
2023-02-15 15:41:32 +08:00
/* 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, job);
g_error_free (error);
goto clear_msgs;
}
2022-06-29 16:07:13 +08:00
2023-02-15 15:41:32 +08:00
status = soup_message_get_status (msg_opts);
2022-06-29 16:07:13 +08:00
2023-02-15 15:41:32 +08:00
/* 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;
2022-06-29 16:07:13 +08:00
2023-02-15 15:41:32 +08:00
dav_backend->auth_info.retrying_after_403 = TRUE;
2022-06-29 16:07:13 +08:00
2023-02-15 15:41:32 +08:00
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);
auth_manager = soup_session_get_feature (http_backend->session, SOUP_TYPE_AUTH_MANAGER);
soup_auth_manager_clear_cached_credentials (SOUP_AUTH_MANAGER (auth_manager));
2022-06-29 16:07:13 +08:00
2023-02-15 15:41:32 +08:00
/* re-send the opts message; re-create since we're still in its cb */
2022-06-29 16:07:13 +08:00
g_object_unref (msg_opts);
2023-02-15 15:41:32 +08:00
msg_opts = soup_message_new_from_uri (SOUP_METHOD_OPTIONS,
http_backend->mount_base);
dav_message_connect_signals (msg_opts, backend);
2022-06-29 16:07:13 +08:00
2023-02-15 15:41:32 +08:00
g_vfs_backend_dav_send_async (backend, msg_opts, try_mount_opts_cb, job);
2022-06-29 16:07:13 +08:00
return;
}
2023-02-15 15:41:32 +08:00
is_success = SOUP_STATUS_IS_SUCCESSFUL (status);
is_webdav = sm_has_header (msg_opts, "DAV");
2022-06-29 16:07:13 +08:00
2023-02-15 15:41:32 +08:00
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, job);
goto clear_msgs;
}
else if (!is_success)
{
int error_code = http_error_code_from_status (soup_message_get_status (msg_opts));
2022-06-29 16:07:13 +08:00
2023-02-15 15:41:32 +08:00
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, job);
goto clear_msgs;
}
2022-06-29 16:07:13 +08:00
2023-02-15 15:41:32 +08:00
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, job);
g_error_free (error);
goto clear_msgs;
}
2022-06-29 16:07:13 +08:00
2023-02-15 15:41:32 +08:00
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:
2022-06-29 16:07:13 +08:00
g_object_unref (msg_opts);
2023-02-15 15:41:32 +08:00
}
2022-06-29 16:07:13 +08:00
2023-02-15 15:41:32 +08:00
static void
try_mount_send_opts (GVfsJobMount *job)
{
GVfsBackendDav *dav_backend = G_VFS_BACKEND_DAV (job->backend);
GVfsBackendHttp *http_backend = G_VFS_BACKEND_HTTP (job->backend);
SoupMessage *msg_opts;
const gchar *user;
if (http_backend->mount_base == NULL)
{
g_vfs_job_failed (G_VFS_JOB (job), G_IO_ERROR,
G_IO_ERROR_INVALID_ARGUMENT,
_("Invalid mount spec"));
return;
}
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);
user = g_mount_spec_get (job->mount_spec, "user");
dav_backend->auth_info.mount_source = g_object_ref (job->mount_source);
dav_backend->auth_info.server_auth.username = g_strdup (user);
dav_backend->auth_info.server_auth.password = NULL;
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;
dav_backend->last_good_path = NULL;
msg_opts = soup_message_new_from_uri (SOUP_METHOD_OPTIONS, http_backend->mount_base);
dav_message_connect_signals (msg_opts, job->backend);
g_vfs_backend_dav_send_async (job->backend, msg_opts, try_mount_opts_cb, job);
}
#ifdef HAVE_AVAHI
static void
try_mount_resolve_cb (GObject *source, GAsyncResult *result, gpointer user_data)
{
GVfsJobMount *job = user_data;
GVfsBackendDav *dav_backend = G_VFS_BACKEND_DAV (job->backend);
GVfsBackendHttp *http_backend = G_VFS_BACKEND_HTTP (job->backend);
GError *error = NULL;
if (!g_vfs_dns_sd_resolver_resolve_finish (dav_backend->resolver,
result,
&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);
http_backend->mount_base = dav_uri_from_dns_sd_resolver (dav_backend);
try_mount_send_opts (job);
}
#endif
static gboolean
try_mount (GVfsBackend *backend,
GVfsJobMount *job,
GMountSpec *mount_spec,
GMountSource *mount_source,
gboolean is_automount)
{
GVfsBackendHttp *http_backend = G_VFS_BACKEND_HTTP (backend);
g_debug ("+ mount\n");
#ifdef HAVE_AVAHI
GVfsBackendDav *dav_backend = G_VFS_BACKEND_DAV (backend);
const char *host;
const char *type;
host = g_mount_spec_get (mount_spec, "host");
type = g_mount_spec_get (mount_spec, "type");
/* 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");
g_vfs_dns_sd_resolver_resolve (dav_backend->resolver,
G_VFS_JOB (job)->cancellable,
try_mount_resolve_cb,
job);
}
else
#endif
{
http_backend->mount_base = g_mount_spec_to_dav_uri (mount_spec);
try_mount_send_opts (job);
}
return TRUE;
2022-06-29 16:07:13 +08:00
}
static PropName ls_propnames[] = {
{"creationdate", NULL},
{"displayname", NULL},
{"getcontentlength", NULL},
{"getcontenttype", NULL},
{"getetag", NULL},
{"getlastmodified", NULL},
{"resourcetype", NULL},
{NULL, NULL}
};
/* *** query_info () *** */
static void
2023-02-15 15:41:32 +08:00
try_query_info_cb (GObject *source, GAsyncResult *result, gpointer user_data)
2022-06-29 16:07:13 +08:00
{
2023-02-15 15:41:32 +08:00
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;
2022-06-29 16:07:13 +08:00
2023-02-15 15:41:32 +08:00
body = g_vfs_backend_dav_send_finish (backend, result, &error);
2022-06-29 16:07:13 +08:00
2023-02-15 15:41:32 +08:00
if (!body)
goto error;
2022-06-29 16:07:13 +08:00
2023-02-15 15:41:32 +08:00
res = multistatus_parse (msg, body, &ms, &error);
g_object_unref (body);
2022-06-29 16:07:13 +08:00
if (res == FALSE)
2023-02-15 15:41:32 +08:00
goto error;
2022-06-29 16:07:13 +08:00
res = FALSE;
multistatus_get_response_iter (&ms, &iter);
while (xml_node_iter_next (&iter))
{
MsResponse response;
if (! multistatus_get_response (&iter, &response))
continue;
if (response.is_target)
{
ms_response_to_file_info (&response, job->file_info);
res = TRUE;
}
ms_response_clear (&response);
}
multistatus_free (&ms);
g_object_unref (msg);
if (res)
g_vfs_job_succeeded (G_VFS_JOB (job));
else
g_vfs_job_failed (G_VFS_JOB (job),
G_IO_ERROR, G_IO_ERROR_FAILED,
_("Response invalid"));
2023-02-15 15:41:32 +08:00
return;
2022-06-29 16:07:13 +08:00
2023-02-15 15:41:32 +08:00
error:
g_vfs_job_failed_from_error (G_VFS_JOB (job), error);
g_error_free (error);
g_object_unref (msg);
2022-06-29 16:07:13 +08:00
}
2023-02-15 15:41:32 +08:00
static gboolean
try_query_info (GVfsBackend *backend,
GVfsJobQueryInfo *job,
const char *filename,
GFileQueryInfoFlags flags,
GFileInfo *info,
GFileAttributeMatcher *matcher)
2022-06-29 16:07:13 +08:00
{
SoupMessage *msg;
2023-02-15 15:41:32 +08:00
g_debug ("Query info %s\n", filename);
2022-06-29 16:07:13 +08:00
2023-02-15 15:41:32 +08:00
msg = propfind_request_new (backend, filename, 0, ls_propnames);
2022-06-29 16:07:13 +08:00
if (msg == NULL)
{
g_vfs_job_failed (G_VFS_JOB (job),
G_IO_ERROR, G_IO_ERROR_FAILED,
_("Could not create request"));
2023-02-15 15:41:32 +08:00
return TRUE;
2022-06-29 16:07:13 +08:00
}
2023-02-15 15:41:32 +08:00
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},
{NULL, NULL}
};
static void
try_query_fs_info_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);
GVfsJobQueryFsInfo *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;
2022-06-29 16:07:13 +08:00
2023-02-15 15:41:32 +08:00
res = multistatus_parse (msg, body, &ms, &error);
g_object_unref (body);
2022-06-29 16:07:13 +08:00
if (res == FALSE)
2023-02-15 15:41:32 +08:00
goto error;
2022-06-29 16:07:13 +08:00
res = FALSE;
multistatus_get_response_iter (&ms, &iter);
while (xml_node_iter_next (&iter))
{
MsResponse response;
if (! multistatus_get_response (&iter, &response))
continue;
if (response.is_target)
{
2023-02-15 15:41:32 +08:00
ms_response_to_fs_info (&response, job->file_info);
2022-06-29 16:07:13 +08:00
res = TRUE;
}
ms_response_clear (&response);
}
multistatus_free (&ms);
g_object_unref (msg);
if (res)
g_vfs_job_succeeded (G_VFS_JOB (job));
else
g_vfs_job_failed (G_VFS_JOB (job),
G_IO_ERROR, G_IO_ERROR_FAILED,
_("Response invalid"));
2023-02-15 15:41:32 +08:00
return;
2022-06-29 16:07:13 +08:00
2023-02-15 15:41:32 +08:00
error:
g_vfs_job_failed_from_error (G_VFS_JOB (job), error);
g_error_free (error);
g_object_unref (msg);
2022-06-29 16:07:13 +08:00
}
2023-02-15 15:41:32 +08:00
static gboolean
try_query_fs_info (GVfsBackend *backend,
GVfsJobQueryFsInfo *job,
const char *filename,
GFileInfo *info,
GFileAttributeMatcher *attribute_matcher)
2022-06-29 16:07:13 +08:00
{
SoupMessage *msg;
2023-02-15 15:41:32 +08:00
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);
2022-06-29 16:07:13 +08:00
2023-02-15 15:41:32 +08:00
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;
}
2022-06-29 16:07:13 +08:00
2023-02-15 15:41:32 +08:00
msg = propfind_request_new (backend, filename, 0, fs_info_propnames);
2022-06-29 16:07:13 +08:00
if (msg == NULL)
{
g_vfs_job_failed (G_VFS_JOB (job),
G_IO_ERROR, G_IO_ERROR_FAILED,
_("Could not create request"));
2023-02-15 15:41:32 +08:00
return TRUE;
2022-06-29 16:07:13 +08:00
}
2023-02-15 15:41:32 +08:00
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_finish (backend, result, &error);
2022-06-29 16:07:13 +08:00
2023-02-15 15:41:32 +08:00
if (!body)
goto error;
2022-06-29 16:07:13 +08:00
2023-02-15 15:41:32 +08:00
res = multistatus_parse (msg, body, &ms, &error);
g_object_unref (body);
2022-06-29 16:07:13 +08:00
if (res == FALSE)
2023-02-15 15:41:32 +08:00
goto error;
2022-06-29 16:07:13 +08:00
g_vfs_job_succeeded (G_VFS_JOB (job));
multistatus_get_response_iter (&ms, &iter);
while (xml_node_iter_next (&iter))
{
MsResponse response;
GFileInfo *info;
if (! multistatus_get_response (&iter, &response))
continue;
if (response.is_target == FALSE)
{
info = g_file_info_new ();
ms_response_to_file_info (&response, info);
g_vfs_job_enumerate_add_info (job, info);
g_object_unref (info);
}
ms_response_clear (&response);
}
multistatus_free (&ms);
g_object_unref (msg);
g_vfs_job_enumerate_done (G_VFS_JOB_ENUMERATE (job));
2023-02-15 15:41:32 +08:00
return;
error:
g_vfs_job_failed_from_error (G_VFS_JOB (job), error);
g_error_free (error);
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;
2022-06-29 16:07:13 +08:00
}
/* ************************************************************************* */
/* */
/* *** open () *** */
static void
2023-02-15 15:41:32 +08:00
try_open_stat_done (GObject *source,
GAsyncResult *result,
gpointer user_data)
2022-06-29 16:07:13 +08:00
{
2023-02-15 15:41:32 +08:00
GInputStream *body;
SoupSession *session = SOUP_SESSION (source);
SoupMessage *msg;
GVfsJob *job = G_VFS_JOB (user_data);
GVfsBackend *backend = job->backend_data;
GError *error = NULL;
GUri *uri;
GFileType target_type;
gboolean res;
2022-06-29 16:07:13 +08:00
2023-02-15 15:41:32 +08:00
msg = soup_session_get_async_result_message (session, result);
if (soup_message_get_status (msg) != SOUP_STATUS_MULTI_STATUS)
2022-06-29 16:07:13 +08:00
{
http_job_failed (job, msg);
return;
}
2023-02-15 15:41:32 +08:00
body = soup_session_send_finish (session, result, &error);
if (!body)
{
g_vfs_job_failed_from_error (job, error);
g_error_free (error);
return;
}
res = stat_location_end (msg, body, &target_type, NULL, NULL);
g_object_unref (body);
2022-06-29 16:07:13 +08:00
if (res == FALSE)
{
g_vfs_job_failed (job,
2023-02-15 15:41:32 +08:00
G_IO_ERROR, G_IO_ERROR_FAILED,
_("Response invalid"));
2022-06-29 16:07:13 +08:00
return;
}
if (target_type == G_FILE_TYPE_DIRECTORY)
{
g_vfs_job_failed (job,
2023-02-15 15:41:32 +08:00
G_IO_ERROR, G_IO_ERROR_IS_DIRECTORY,
_("File is directory"));
2022-06-29 16:07:13 +08:00
return;
}
uri = soup_message_get_uri (msg);
http_backend_open_for_read (backend, job, uri);
}
static gboolean
try_open_for_read (GVfsBackend *backend,
GVfsJobOpenForRead *job,
const char *filename)
{
2023-02-15 15:41:32 +08:00
SoupMessage *msg;
GUri *uri;
2022-06-29 16:07:13 +08:00
uri = g_vfs_backend_dav_uri_for_path (backend, filename, FALSE);
2023-02-15 15:41:32 +08:00
msg = stat_location_start (uri, FALSE);
g_uri_unref (uri);
2022-06-29 16:07:13 +08:00
if (msg == NULL)
{
g_vfs_job_failed (G_VFS_JOB (job),
G_IO_ERROR, G_IO_ERROR_FAILED,
_("Could not create request"));
return FALSE;
}
2023-02-15 15:41:32 +08:00
dav_message_connect_signals (msg, backend);
2022-06-29 16:07:13 +08:00
g_vfs_job_set_backend_data (G_VFS_JOB (job), backend, NULL);
2023-02-15 15:41:32 +08:00
soup_session_send_async (G_VFS_BACKEND_HTTP (backend)->session, msg,
G_PRIORITY_DEFAULT, NULL, try_open_stat_done, job);
2022-06-29 16:07:13 +08:00
return TRUE;
}
/* *** create () *** */
static void
2023-02-15 15:41:32 +08:00
try_create_tested_existence (GObject *source,
GAsyncResult *result,
2022-06-29 16:07:13 +08:00
gpointer user_data)
{
GVfsJob *job = G_VFS_JOB (user_data);
2023-02-15 15:41:32 +08:00
GOutputStream *stream;
GInputStream *body;
SoupMessage *msg = job->backend_data;
SoupMessage *put_msg;
GError *error = NULL;
GUri *uri;
2022-06-29 16:07:13 +08:00
2023-02-15 15:41:32 +08:00
body = soup_session_send_finish (SOUP_SESSION (source), result, &error);
if (!body)
2022-06-29 16:07:13 +08:00
{
2023-02-15 15:41:32 +08:00
g_vfs_job_failed_from_error (job, error);
g_error_free (error);
return;
}
if (SOUP_STATUS_IS_SUCCESSFUL (soup_message_get_status (msg)))
{
g_object_unref (body);
2022-06-29 16:07:13 +08:00
g_vfs_job_failed (job,
G_IO_ERROR,
G_IO_ERROR_EXISTS,
_("Target file already exists"));
return;
}
/* TODO: other errors */
2023-02-15 15:41:32 +08:00
g_object_unref (body);
2022-06-29 16:07:13 +08:00
uri = soup_message_get_uri (msg);
put_msg = soup_message_new_from_uri (SOUP_METHOD_PUT, uri);
/*
* Doesn't work with apache > 2.2.9
2023-02-15 15:41:32 +08:00
* soup_message_headers_append (soup_message_get_request_headers (put_msg), "If-None-Match", "*");
2022-06-29 16:07:13 +08:00
*/
stream = g_memory_output_stream_new (NULL, 0, g_try_realloc, g_free);
g_object_set_data_full (G_OBJECT (stream), "-gvfs-stream-msg", put_msg, g_object_unref);
g_vfs_job_open_for_write_set_handle (G_VFS_JOB_OPEN_FOR_WRITE (job), stream);
g_vfs_job_open_for_write_set_can_seek (G_VFS_JOB_OPEN_FOR_WRITE (job),
g_seekable_can_seek (G_SEEKABLE (stream)));
g_vfs_job_open_for_write_set_can_truncate (G_VFS_JOB_OPEN_FOR_WRITE (job),
g_seekable_can_truncate (G_SEEKABLE (stream)));
g_vfs_job_succeeded (job);
}
static gboolean
try_create (GVfsBackend *backend,
GVfsJobOpenForWrite *job,
const char *filename,
GFileCreateFlags flags)
{
SoupMessage *msg;
2023-02-15 15:41:32 +08:00
GUri *uri;
2022-06-29 16:07:13 +08:00
/* TODO: if we supported chunked requests, we could
* use a PUT with "If-None-Match: *" and "Expect: 100-continue"
*/
uri = g_vfs_backend_dav_uri_for_path (backend, filename, FALSE);
msg = soup_message_new_from_uri (SOUP_METHOD_HEAD, uri);
2023-02-15 15:41:32 +08:00
g_uri_unref (uri);
2022-06-29 16:07:13 +08:00
2023-02-15 15:41:32 +08:00
g_vfs_job_set_backend_data (G_VFS_JOB (job), msg, NULL);
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);
2022-06-29 16:07:13 +08:00
return TRUE;
}
/* *** replace () *** */
static void
2023-02-15 15:41:32 +08:00
open_for_replace_succeeded (GVfsBackendHttp *op_backend,
GVfsJob *job,
GUri *uri,
const char *etag)
2022-06-29 16:07:13 +08:00
{
SoupMessage *put_msg;
GOutputStream *stream;
put_msg = soup_message_new_from_uri (SOUP_METHOD_PUT, uri);
if (etag)
2023-02-15 15:41:32 +08:00
soup_message_headers_append (soup_message_get_request_headers (put_msg),
"If-Match",
etag);
2022-06-29 16:07:13 +08:00
stream = g_memory_output_stream_new (NULL, 0, g_try_realloc, g_free);
g_object_set_data_full (G_OBJECT (stream), "-gvfs-stream-msg", put_msg, g_object_unref);
g_vfs_job_open_for_write_set_handle (G_VFS_JOB_OPEN_FOR_WRITE (job), stream);
g_vfs_job_open_for_write_set_can_seek (G_VFS_JOB_OPEN_FOR_WRITE (job),
g_seekable_can_seek (G_SEEKABLE (stream)));
g_vfs_job_open_for_write_set_can_truncate (G_VFS_JOB_OPEN_FOR_WRITE (job),
g_seekable_can_truncate (G_SEEKABLE (stream)));
g_vfs_job_succeeded (job);
}
static void
2023-02-15 15:41:32 +08:00
try_replace_checked_etag (GObject *source,
GAsyncResult *result,
gpointer user_data)
2022-06-29 16:07:13 +08:00
{
GVfsJob *job = G_VFS_JOB (user_data);
GVfsBackendHttp *op_backend = job->backend_data;
2023-02-15 15:41:32 +08:00
GInputStream *body;
SoupSession *session = SOUP_SESSION (source);
SoupMessage *msg;
GError *error;
2022-06-29 16:07:13 +08:00
2023-02-15 15:41:32 +08:00
msg = soup_session_get_async_result_message (session, result);
body = soup_session_send_finish (session, result, &error);
if (!body)
2022-06-29 16:07:13 +08:00
{
2023-02-15 15:41:32 +08:00
g_vfs_job_failed_from_error (job, error);
g_error_free (error);
return;
}
if (soup_message_get_status (msg) == SOUP_STATUS_PRECONDITION_FAILED)
{
g_object_unref (body);
2022-06-29 16:07:13 +08:00
g_vfs_job_failed (G_VFS_JOB (job),
G_IO_ERROR,
G_IO_ERROR_WRONG_ETAG,
_("The file was externally modified"));
return;
}
/* TODO: other errors */
2023-02-15 15:41:32 +08:00
g_object_unref (body);
2022-06-29 16:07:13 +08:00
open_for_replace_succeeded (op_backend, job, soup_message_get_uri (msg),
2023-02-15 15:41:32 +08:00
soup_message_headers_get_one (soup_message_get_request_headers (msg),
"If-Match"));
2022-06-29 16:07:13 +08:00
}
static gboolean
try_replace (GVfsBackend *backend,
GVfsJobOpenForWrite *job,
const char *filename,
const char *etag,
gboolean make_backup,
GFileCreateFlags flags)
{
GVfsBackendHttp *op_backend;
2023-02-15 15:41:32 +08:00
GUri *uri;
2022-06-29 16:07:13 +08:00
/* TODO: if SoupOutputStream supported chunked requests, we could
* use a PUT with "If-Match: ..." and "Expect: 100-continue"
*/
op_backend = G_VFS_BACKEND_HTTP (backend);
if (make_backup)
{
g_vfs_job_failed (G_VFS_JOB (job),
G_IO_ERROR,
G_IO_ERROR_CANT_CREATE_BACKUP,
_("Backup file creation failed"));
return TRUE;
}
uri = g_vfs_backend_dav_uri_for_path (backend, filename, FALSE);
if (etag)
{
SoupMessage *msg;
msg = soup_message_new_from_uri (SOUP_METHOD_HEAD, uri);
2023-02-15 15:41:32 +08:00
g_uri_unref (uri);
dav_message_connect_signals (msg, backend);
soup_message_headers_append (soup_message_get_request_headers (msg),
"If-Match", etag);
2022-06-29 16:07:13 +08:00
g_vfs_job_set_backend_data (G_VFS_JOB (job), op_backend, NULL);
2023-02-15 15:41:32 +08:00
soup_session_send_async (op_backend->session, msg, G_PRIORITY_DEFAULT,
NULL, try_replace_checked_etag, job);
2022-06-29 16:07:13 +08:00
return TRUE;
}
open_for_replace_succeeded (op_backend, G_VFS_JOB (job), uri, NULL);
2023-02-15 15:41:32 +08:00
g_uri_unref (uri);
2022-06-29 16:07:13 +08:00
return TRUE;
}
/* *** write () *** */
static void
write_ready (GObject *source_object,
GAsyncResult *result,
gpointer user_data)
{
GOutputStream *stream;
GVfsJob *job;
GError *error;
gssize nwrote;
stream = G_OUTPUT_STREAM (source_object);
error = NULL;
job = G_VFS_JOB (user_data);
nwrote = g_output_stream_write_finish (stream, result, &error);
if (nwrote < 0)
{
g_vfs_job_failed_literal (G_VFS_JOB (job),
error->domain,
error->code,
error->message);
g_error_free (error);
return;
}
g_vfs_job_write_set_written_size (G_VFS_JOB_WRITE (job), nwrote);
g_vfs_job_succeeded (job);
}
static gboolean
try_write (GVfsBackend *backend,
GVfsJobWrite *job,
GVfsBackendHandle handle,
char *buffer,
gsize buffer_size)
{
GOutputStream *stream;
stream = G_OUTPUT_STREAM (handle);
g_output_stream_write_async (stream,
buffer,
buffer_size,
G_PRIORITY_DEFAULT,
G_VFS_JOB (job)->cancellable,
write_ready,
job);
return TRUE;
}
2023-02-15 15:41:32 +08:00
/* this does not invoke libsoup API, so it can be synchronous/threaded */
2022-06-29 16:07:13 +08:00
static void
do_seek_on_write (GVfsBackend *backend,
GVfsJobSeekWrite *job,
GVfsBackendHandle handle,
goffset offset,
GSeekType type)
{
GSeekable *stream = G_SEEKABLE (handle);
GError *error = NULL;
if (g_seekable_seek (stream, offset, type, G_VFS_JOB (job)->cancellable, &error))
{
g_vfs_job_seek_write_set_offset (job, g_seekable_tell (stream));
g_vfs_job_succeeded (G_VFS_JOB (job));
}
else
{
g_vfs_job_failed_from_error (G_VFS_JOB (job), error);
g_error_free (error);
}
}
2023-02-15 15:41:32 +08:00
/* this does not invoke libsoup API, so it can be synchronous/threaded */
2022-06-29 16:07:13 +08:00
static void
do_truncate (GVfsBackend *backend,
GVfsJobTruncate *job,
GVfsBackendHandle handle,
goffset size)
{
GSeekable *stream = G_SEEKABLE (handle);
GError *error = NULL;
if (g_seekable_truncate (stream, size, G_VFS_JOB (job)->cancellable, &error))
{
g_vfs_job_succeeded (G_VFS_JOB (job));
}
else
{
g_vfs_job_failed_from_error (G_VFS_JOB (job), error);
g_error_free (error);
}
}
/* *** close_write () *** */
static void
2023-02-15 15:41:32 +08:00
try_close_write_sent (GObject *source,
GAsyncResult *result,
gpointer user_data)
2022-06-29 16:07:13 +08:00
{
2023-02-15 15:41:32 +08:00
GInputStream *body;
GVfsJob *job = G_VFS_JOB (user_data);
SoupSession *session = SOUP_SESSION (source);
SoupMessage *msg = soup_session_get_async_result_message (session, result);
GError *error;
body = soup_session_send_finish (session, result, &error);
if (!body)
{
g_vfs_job_failed_from_error (job, error);
g_error_free (error);
return;
}
2022-06-29 16:07:13 +08:00
2023-02-15 15:41:32 +08:00
g_object_unref (body);
if (!SOUP_STATUS_IS_SUCCESSFUL (soup_message_get_status (msg)))
2022-06-29 16:07:13 +08:00
http_job_failed (job, msg);
else
g_vfs_job_succeeded (job);
}
static gboolean
try_close_write (GVfsBackend *backend,
GVfsJobCloseWrite *job,
GVfsBackendHandle handle)
{
GOutputStream *stream;
SoupMessage *msg;
2023-02-15 15:41:32 +08:00
GBytes *bytes;
2022-06-29 16:07:13 +08:00
stream = G_OUTPUT_STREAM (handle);
msg = g_object_get_data (G_OBJECT (stream), "-gvfs-stream-msg");
g_object_ref (msg);
g_object_set_data (G_OBJECT (stream), "-gvfs-stream-msg", NULL);
2023-02-15 15:41:32 +08:00
dav_message_connect_signals (msg, backend);
2022-06-29 16:07:13 +08:00
g_output_stream_close (stream, NULL, NULL);
2023-02-15 15:41:32 +08:00
bytes = g_memory_output_stream_steal_as_bytes (G_MEMORY_OUTPUT_STREAM (stream));
2022-06-29 16:07:13 +08:00
g_object_unref (stream);
2023-02-15 15:41:32 +08:00
soup_message_set_request_body_from_bytes (msg, NULL, bytes);
soup_session_send_async (G_VFS_BACKEND_HTTP (backend)->session, msg,
G_PRIORITY_DEFAULT, NULL, try_close_write_sent, job);
g_bytes_unref (bytes);
2022-06-29 16:07:13 +08:00
return TRUE;
}
static void
2023-02-15 15:41:32 +08:00
make_directory_cb (GObject *source, GAsyncResult *result, gpointer user_data)
2022-06-29 16:07:13 +08:00
{
2023-02-15 15:41:32 +08:00
GVfsBackend *backend = G_VFS_BACKEND (source);
GVfsJobMakeDirectory *job = user_data;
SoupMessage *msg = g_vfs_backend_dav_get_async_result_message (backend, result);
GInputStream *body;
GError *error = NULL;
guint status;
2022-06-29 16:07:13 +08:00
2023-02-15 15:41:32 +08:00
body = g_vfs_backend_dav_send_finish (backend, result, &error);
2022-06-29 16:07:13 +08:00
2023-02-15 15:41:32 +08:00
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);
2022-06-29 16:07:13 +08:00
if (! SOUP_STATUS_IS_SUCCESSFUL (status))
if (status == SOUP_STATUS_METHOD_NOT_ALLOWED)
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);
else
g_vfs_job_succeeded (G_VFS_JOB (job));
2023-02-15 15:41:32 +08:00
g_object_unref (body);
2022-06-29 16:07:13 +08:00
g_object_unref (msg);
}
2023-02-15 15:41:32 +08:00
static gboolean
try_make_directory (GVfsBackend *backend,
GVfsJobMakeDirectory *job,
const char *filename)
2022-06-29 16:07:13 +08:00
{
SoupMessage *msg;
2023-02-15 15:41:32 +08:00
GUri *uri;
2022-06-29 16:07:13 +08:00
2023-02-15 15:41:32 +08:00
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);
2022-06-29 16:07:13 +08:00
2023-02-15 15:41:32 +08:00
dav_message_connect_signals (msg, backend);
2022-06-29 16:07:13 +08:00
2023-02-15 15:41:32 +08:00
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)
2022-06-29 16:07:13 +08:00
{
g_vfs_job_failed_from_error (G_VFS_JOB (job), error);
g_error_free (error);
2023-02-15 15:41:32 +08:00
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;
guint num_children;
GError *error = NULL;
gboolean res;
GUri *uri = stat_location_async_get_uri (backend, result);
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_clear_error (&error);
2022-06-29 16:07:13 +08:00
return;
}
if (file_type == G_FILE_TYPE_DIRECTORY && num_children)
{
g_vfs_job_failed (G_VFS_JOB (job),
G_IO_ERROR, G_IO_ERROR_NOT_EMPTY,
_("Directory not empty"));
return;
}
msg = soup_message_new_from_uri (SOUP_METHOD_DELETE, uri);
2023-02-15 15:41:32 +08:00
dav_message_connect_signals (msg, backend);
2022-06-29 16:07:13 +08:00
2023-02-15 15:41:32 +08:00
g_vfs_backend_dav_send_async (backend, msg, try_delete_send_cb, job);
}
2022-06-29 16:07:13 +08:00
2023-02-15 15:41:32 +08:00
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;
2022-06-29 16:07:13 +08:00
}
2023-02-15 15:41:32 +08:00
typedef struct _TrySetDisplayNameData {
GVfsJobSetDisplayName *job;
char *target_path;
} TrySetDisplayNameData;
2022-06-29 16:07:13 +08:00
static void
2023-02-15 15:41:32 +08:00
try_set_display_name_cb (GObject *source, GAsyncResult *result,
gpointer user_data)
2022-06-29 16:07:13 +08:00
{
2023-02-15 15:41:32 +08:00
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;
2022-06-29 16:07:13 +08:00
2023-02-15 15:41:32 +08:00
body = g_vfs_backend_dav_send_finish (backend, result, &error);
2022-06-29 16:07:13 +08:00
2023-02-15 15:41:32 +08:00
if (!body)
{
http_job_failed (G_VFS_JOB (data->job), msg);
goto error;
}
2022-06-29 16:07:13 +08:00
2023-02-15 15:41:32 +08:00
status = soup_message_get_status (msg);
2022-06-29 16:07:13 +08:00
/*
* 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))
{
2023-02-15 15:41:32 +08:00
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));
2022-06-29 16:07:13 +08:00
}
else if (status == SOUP_STATUS_PRECONDITION_FAILED ||
SOUP_STATUS_IS_REDIRECTION (status))
2023-02-15 15:41:32 +08:00
g_vfs_job_failed (G_VFS_JOB (data->job), G_IO_ERROR,
2022-06-29 16:07:13 +08:00
G_IO_ERROR_EXISTS,
_("Target file already exists"));
else
2023-02-15 15:41:32 +08:00
http_job_failed (G_VFS_JOB (data->job), msg);
2022-06-29 16:07:13 +08:00
2023-02-15 15:41:32 +08:00
g_object_unref (body);
error:
g_clear_error (&error);
2022-06-29 16:07:13 +08:00
g_object_unref (msg);
2023-02-15 15:41:32 +08:00
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;
2022-06-29 16:07:13 +08:00
g_free (dirname);
2023-02-15 15:41:32 +08:00
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;
2022-06-29 16:07:13 +08:00
}
2023-02-15 15:41:32 +08:00
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;
2022-06-29 16:07:13 +08:00
static void
2023-02-15 15:41:32 +08:00
copy_data_free (gpointer data)
2022-06-29 16:07:13 +08:00
{
2023-02-15 15:41:32 +08:00
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;
2022-06-29 16:07:13 +08:00
GError *error = NULL;
2023-02-15 15:41:32 +08:00
guint status;
2022-06-29 16:07:13 +08:00
2023-02-15 15:41:32 +08:00
body = g_vfs_backend_dav_send_finish (backend, result, &error);
if (!body)
2022-06-29 16:07:13 +08:00
{
2023-02-15 15:41:32 +08:00
g_vfs_job_failed_from_error (data->job, error);
copy_data_free (data);
g_clear_error (&error);
return;
}
/* 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))
{
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 (data->job, G_IO_ERROR,
G_IO_ERROR_EXISTS,
_("Target file already exists"));
else
http_job_failed (data->job, data->msg);
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);
2022-06-29 16:07:13 +08:00
2023-02-15 15:41:32 +08:00
if (!body)
{
g_vfs_job_failed_from_error (data->job, error);
copy_data_free (data);
g_object_unref (msg);
g_clear_error (&error);
2022-06-29 16:07:13 +08:00
return;
}
2023-02-15 15:41:32 +08:00
status = soup_message_get_status (msg);
if (!SOUP_STATUS_IS_SUCCESSFUL (status))
2022-06-29 16:07:13 +08:00
{
2023-02-15 15:41:32 +08:00
http_job_failed (data->job, msg);
g_object_unref (msg);
copy_data_free (data);
return;
2022-06-29 16:07:13 +08:00
}
2023-02-15 15:41:32 +08:00
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)
2022-06-29 16:07:13 +08:00
{
2023-02-15 15:41:32 +08:00
if (data->flags & G_FILE_COPY_OVERWRITE)
2022-06-29 16:07:13 +08:00
{
2023-02-15 15:41:32 +08:00
if (data->source_res)
2022-06-29 16:07:13 +08:00
{
2023-02-15 15:41:32 +08:00
if (data->target_ft == G_FILE_TYPE_DIRECTORY)
2022-06-29 16:07:13 +08:00
{
2023-02-15 15:41:32 +08:00
if (data->source_ft == G_FILE_TYPE_DIRECTORY)
g_vfs_job_failed_literal (G_VFS_JOB(data->job),
2022-06-29 16:07:13 +08:00
G_IO_ERROR,
G_IO_ERROR_WOULD_MERGE,
_("Cant move directory over directory"));
else
2023-02-15 15:41:32 +08:00
g_vfs_job_failed_literal (G_VFS_JOB(data->job),
2022-06-29 16:07:13 +08:00
G_IO_ERROR,
G_IO_ERROR_IS_DIRECTORY,
_("Cant move over directory"));
2023-02-15 15:41:32 +08:00
copy_data_free (data);
return;
2022-06-29 16:07:13 +08:00
}
2023-02-15 15:41:32 +08:00
else if (data->source_ft == G_FILE_TYPE_DIRECTORY)
2022-06-29 16:07:13 +08:00
{
/* Overwriting a file with a directory, first remove the
* file */
SoupMessage *msg;
msg = soup_message_new_from_uri (SOUP_METHOD_DELETE,
2023-02-15 15:41:32 +08:00
data->target_uri);
dav_message_connect_signals (msg, backend);
g_vfs_backend_dav_send_async (backend, msg,
try_move_target_delete_cb,
data);
return;
2022-06-29 16:07:13 +08:00
}
}
else
{
2023-02-15 15:41:32 +08:00
g_vfs_job_failed_from_error (data->job, error);
copy_data_free (data);
g_clear_error (&error);
return;
2022-06-29 16:07:13 +08:00
}
}
else
{
2023-02-15 15:41:32 +08:00
g_vfs_job_failed_literal (G_VFS_JOB(data->job),
2022-06-29 16:07:13 +08:00
G_IO_ERROR,
G_IO_ERROR_EXISTS,
_("Target file exists"));
2023-02-15 15:41:32 +08:00
copy_data_free (data);
g_clear_error (&error);
return;
2022-06-29 16:07:13 +08:00
}
}
2023-02-15 15:41:32 +08:00
try_move_do (backend, data);
}
2022-06-29 16:07:13 +08:00
2023-02-15 15:41:32 +08:00
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;
2022-06-29 16:07:13 +08:00
2023-02-15 15:41:32 +08:00
res = stat_location_finish (backend, &data->target_ft, NULL, NULL,
result, &error);
2022-06-29 16:07:13 +08:00
2023-02-15 15:41:32 +08:00
if (!res && !g_error_matches (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND))
2022-06-29 16:07:13 +08:00
{
2023-02-15 15:41:32 +08:00
g_vfs_job_failed_from_error (data->job, error);
g_clear_error (&error);
copy_data_free (data);
return;
2022-06-29 16:07:13 +08:00
}
g_clear_error (&error);
2023-02-15 15:41:32 +08:00
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;
}
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;
2022-06-29 16:07:13 +08:00
}
static void
2023-02-15 15:41:32 +08:00
try_copy_do_cb (GObject *source, GAsyncResult *result, gpointer user_data)
2022-06-29 16:07:13 +08:00
{
2023-02-15 15:41:32 +08:00
GVfsBackend *backend = G_VFS_BACKEND (source);
CopyData *data = user_data;
GInputStream *body;
2022-06-29 16:07:13 +08:00
GError *error = NULL;
2023-02-15 15:41:32 +08:00
guint status;
2022-06-29 16:07:13 +08:00
2023-02-15 15:41:32 +08:00
body = g_vfs_backend_dav_send_finish (backend, result, &error);
if (!body)
2022-06-29 16:07:13 +08:00
{
2023-02-15 15:41:32 +08:00
g_vfs_job_failed_from_error (data->job, error);
copy_data_free (data);
2022-06-29 16:07:13 +08:00
return;
}
2023-02-15 15:41:32 +08:00
/* 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))
2022-06-29 16:07:13 +08:00
{
2023-02-15 15:41:32 +08:00
if (data->progress_callback)
data->progress_callback (data->file_size, data->file_size,
data->progress_callback_data);
g_vfs_job_succeeded (data->job);
2022-06-29 16:07:13 +08:00
}
2023-02-15 15:41:32 +08:00
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);
2022-06-29 16:07:13 +08:00
2023-02-15 15:41:32 +08:00
g_object_unref (body);
copy_data_free (data);
}
static void
try_copy_target_stat_cb (GObject *source, GAsyncResult *result,
gpointer user_data)
{
GVfsBackend *backend = G_VFS_BACKEND (source);
CopyData *data = user_data;
GError *error = NULL;
data->target_res = stat_location_finish (backend, &data->target_ft,
NULL, NULL, result, &error);
if (data->target_res)
2022-06-29 16:07:13 +08:00
{
2023-02-15 15:41:32 +08:00
if (data->flags & G_FILE_COPY_OVERWRITE)
2022-06-29 16:07:13 +08:00
{
2023-02-15 15:41:32 +08:00
if (data->target_ft == G_FILE_TYPE_DIRECTORY)
2022-06-29 16:07:13 +08:00
{
2023-02-15 15:41:32 +08:00
if (data->source_ft == G_FILE_TYPE_DIRECTORY)
g_vfs_job_failed_literal (G_VFS_JOB(data->job),
2022-06-29 16:07:13 +08:00
G_IO_ERROR,
G_IO_ERROR_WOULD_MERGE,
_("Cant copy directory over directory"));
else
2023-02-15 15:41:32 +08:00
g_vfs_job_failed_literal (G_VFS_JOB(data->job),
2022-06-29 16:07:13 +08:00
G_IO_ERROR,
G_IO_ERROR_IS_DIRECTORY,
_("File is directory"));
2023-02-15 15:41:32 +08:00
copy_data_free (data);
return;
2022-06-29 16:07:13 +08:00
}
}
else
{
2023-02-15 15:41:32 +08:00
g_vfs_job_failed_literal (data->job,
2022-06-29 16:07:13 +08:00
G_IO_ERROR,
G_IO_ERROR_EXISTS,
_("Target file already exists"));
2023-02-15 15:41:32 +08:00
copy_data_free (data);
return;
2022-06-29 16:07:13 +08:00
}
}
else if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND))
{
2023-02-15 15:41:32 +08:00
g_vfs_job_failed_from_error (data->job, error);
g_clear_error (&error);
copy_data_free (data);
return;
2022-06-29 16:07:13 +08:00
}
2023-02-15 15:41:32 +08:00
g_clear_error (&error);
if (data->source_ft == G_FILE_TYPE_DIRECTORY)
2022-06-29 16:07:13 +08:00
{
2023-02-15 15:41:32 +08:00
g_vfs_job_failed_literal (data->job,
2022-06-29 16:07:13 +08:00
G_IO_ERROR,
G_IO_ERROR_WOULD_RECURSE,
_("Cant recursively copy directory"));
2023-02-15 15:41:32 +08:00
copy_data_free (data);
return;
2022-06-29 16:07:13 +08:00
}
2023-02-15 15:41:32 +08:00
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);
2022-06-29 16:07:13 +08:00
2023-02-15 15:41:32 +08:00
dav_message_connect_signals (data->msg, backend);
2022-06-29 16:07:13 +08:00
2023-02-15 15:41:32 +08:00
g_vfs_backend_dav_send_async (backend, data->msg, try_copy_do_cb, data);
}
2022-06-29 16:07:13 +08:00
2023-02-15 15:41:32 +08:00
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)
2022-06-29 16:07:13 +08:00
{
2023-02-15 15:41:32 +08:00
g_vfs_job_failed_from_error (data->job, error);
g_clear_error (&error);
copy_data_free (data);
return;
2022-06-29 16:07:13 +08:00
}
g_clear_error (&error);
2023-02-15 15:41:32 +08:00
data->source_res = res;
stat_location_async (backend, data->target_uri, FALSE,
try_copy_target_stat_cb, data);
2022-06-29 16:07:13 +08:00
}
2023-02-15 15:41:32 +08:00
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)
{
/* 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;
}
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);
stat_location_async (backend, data->source_uri, FALSE,
try_copy_source_stat_cb, data);
return TRUE;
}
2022-06-29 16:07:13 +08:00
2023-02-15 15:41:32 +08:00
#define CHUNK_SIZE 65536
2022-06-29 16:07:13 +08:00
typedef struct {
/* Job details */
GVfsBackend *backend;
GVfsJob *job;
GVfsJobPush *op_job;
/* Local file */
GInputStream *in;
goffset size;
/* Remote file */
2023-02-15 15:41:32 +08:00
GUri *uri;
2022-06-29 16:07:13 +08:00
SoupMessage *msg;
goffset n_written;
} PushHandle;
static void
push_handle_free (PushHandle *handle)
{
if (handle->in)
{
g_input_stream_close_async (handle->in, 0, NULL, NULL, NULL);
g_object_unref (handle->in);
}
g_object_unref (handle->backend);
g_object_unref (handle->job);
2023-02-15 15:41:32 +08:00
g_object_unref (handle->msg);
g_uri_unref (handle->uri);
2022-06-29 16:07:13 +08:00
2023-02-15 15:41:32 +08:00
g_slice_free (PushHandle, handle);
2022-06-29 16:07:13 +08:00
}
static void
push_setup_message (PushHandle *handle)
{
message_add_overwrite_header (handle->msg,
handle->op_job->flags & G_FILE_COPY_OVERWRITE);
2023-02-15 15:41:32 +08:00
soup_message_headers_set_encoding (soup_message_get_request_headers (handle->msg),
2022-06-29 16:07:13 +08:00
SOUP_ENCODING_CONTENT_LENGTH);
2023-02-15 15:41:32 +08:00
soup_message_headers_set_content_length (soup_message_get_request_headers (handle->msg),
2022-06-29 16:07:13 +08:00
handle->size);
}
static void
push_restarted (SoupMessage *msg, gpointer user_data)
{
PushHandle *handle = user_data;
2023-02-15 15:41:32 +08:00
handle->n_written = 0;
g_object_set (msg, "method", SOUP_METHOD_PUT, NULL);
2022-06-29 16:07:13 +08:00
push_setup_message (handle);
2023-02-15 15:41:32 +08:00
soup_message_set_request_body (handle->msg, NULL, handle->in, handle->size);
2022-06-29 16:07:13 +08:00
}
static void
2023-02-15 15:41:32 +08:00
push_wrote_body_data (SoupMessage *msg, guint chunk_size, gpointer user_data)
2022-06-29 16:07:13 +08:00
{
PushHandle *handle = user_data;
2023-02-15 15:41:32 +08:00
handle->n_written += chunk_size;
2022-06-29 16:07:13 +08:00
g_vfs_job_progress_callback (handle->n_written, handle->size, handle->job);
}
static void
2023-02-15 15:41:32 +08:00
push_finished (SoupMessage *msg, gpointer user_data)
2022-06-29 16:07:13 +08:00
{
PushHandle *handle = user_data;
if (g_vfs_job_is_finished (handle->job))
; /* We got an error so we finished the job and cancelled msg. */
2023-02-15 15:41:32 +08:00
else if (!SOUP_STATUS_IS_SUCCESSFUL (soup_message_get_status (msg)))
2022-06-29 16:07:13 +08:00
http_job_failed (handle->job, msg);
else
{
if (handle->op_job->remove_source)
g_unlink (handle->op_job->local_path);
g_vfs_job_succeeded (handle->job);
}
push_handle_free (handle);
}
static void
2023-02-15 15:41:32 +08:00
push_done (GObject *source, GAsyncResult *result, gpointer user_data)
{
GInputStream *body;
GVfsJob *job = G_VFS_JOB (user_data);
GError *error = NULL;
body = soup_session_send_finish (SOUP_SESSION (source), result, &error);
if (!body)
{
g_vfs_job_failed_from_error (job, error);
g_error_free (error);
}
else
g_object_unref (body);
}
static void
push_stat_dest_cb (GObject *source, GAsyncResult *result, gpointer user_data)
2022-06-29 16:07:13 +08:00
{
2023-02-15 15:41:32 +08:00
GInputStream *body;
2022-06-29 16:07:13 +08:00
PushHandle *handle = user_data;
GFileType type;
2023-02-15 15:41:32 +08:00
GError *error = NULL;
body = soup_session_send_finish (SOUP_SESSION (source), result, &error);
if (!body)
{
g_vfs_job_failed_from_error (handle->job, error);
g_error_free (error);
return;
}
2022-06-29 16:07:13 +08:00
2023-02-15 15:41:32 +08:00
if (stat_location_end (handle->msg, body, &type, NULL, NULL))
2022-06-29 16:07:13 +08:00
{
if (!(handle->op_job->flags & G_FILE_COPY_OVERWRITE))
{
2023-02-15 15:41:32 +08:00
g_object_unref (body);
2022-06-29 16:07:13 +08:00
g_vfs_job_failed (handle->job,
G_IO_ERROR,
G_IO_ERROR_EXISTS,
_("Target file already exists"));
push_handle_free (handle);
return;
}
if (type == G_FILE_TYPE_DIRECTORY)
{
2023-02-15 15:41:32 +08:00
g_object_unref (body);
2022-06-29 16:07:13 +08:00
g_vfs_job_failed (handle->job,
G_IO_ERROR,
G_IO_ERROR_IS_DIRECTORY,
_("File is directory"));
push_handle_free (handle);
return;
}
}
2023-02-15 15:41:32 +08:00
g_object_unref (body);
g_object_unref (handle->msg);
2022-06-29 16:07:13 +08:00
handle->msg = soup_message_new_from_uri (SOUP_METHOD_PUT, handle->uri);
push_setup_message (handle);
2023-02-15 15:41:32 +08:00
soup_message_set_request_body (handle->msg, NULL, handle->in, handle->size);
2022-06-29 16:07:13 +08:00
g_signal_connect (handle->msg, "restarted",
G_CALLBACK (push_restarted), handle);
g_signal_connect (handle->msg, "wrote-body-data",
G_CALLBACK (push_wrote_body_data), handle);
2023-02-15 15:41:32 +08:00
g_signal_connect (handle->msg, "finished",
G_CALLBACK (push_finished), handle);
dav_message_connect_signals (handle->msg, handle->backend);
2022-06-29 16:07:13 +08:00
2023-02-15 15:41:32 +08:00
soup_session_send_async (G_VFS_BACKEND_HTTP (handle->backend)->session,
handle->msg, G_PRIORITY_DEFAULT,
NULL, push_done, handle->job);
2022-06-29 16:07:13 +08:00
}
static void
push_source_fstat_cb (GObject *source, GAsyncResult *res, gpointer user_data)
{
GFileInputStream *fin = G_FILE_INPUT_STREAM (source);
PushHandle *handle = user_data;
GError *error = NULL;
GFileInfo *info;
info = g_file_input_stream_query_info_finish (fin, res, &error);
if (info)
{
handle->size = g_file_info_get_size (info);
g_object_unref (info);
2023-02-15 15:41:32 +08:00
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);
2022-06-29 16:07:13 +08:00
}
else
{
g_vfs_job_failed_from_error (handle->job, error);
g_error_free (error);
push_handle_free (handle);
}
}
static void
push_source_open_cb (GObject *source, GAsyncResult *res, gpointer user_data)
{
GFile *source_file = G_FILE (source);
PushHandle *handle = user_data;
GError *error = NULL;
GFileInputStream *fin;
fin = g_file_read_finish (source_file, res, &error);
if (fin)
{
handle->in = G_INPUT_STREAM (fin);
g_file_input_stream_query_info_async (fin,
G_FILE_ATTRIBUTE_STANDARD_SIZE,
0, handle->job->cancellable,
push_source_fstat_cb, handle);
}
else
{
if (error->domain == G_IO_ERROR && error->code == G_IO_ERROR_IS_DIRECTORY)
{
/* Fall back to default implementation to improve the error message */
g_vfs_job_failed (handle->job, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED,
_("Operation not supported"));
}
else
g_vfs_job_failed_from_error (handle->job, error);
g_error_free (error);
push_handle_free (handle);
}
}
static void
push_source_lstat_cb (GObject *source, GAsyncResult *res, gpointer user_data)
{
GFile *source_file = G_FILE (source);
PushHandle *handle = user_data;
GError *error = NULL;
GFileInfo *info;
info = g_file_query_info_finish (source_file, res, &error);
if (!info)
{
g_vfs_job_failed_from_error (handle->job, error);
g_error_free (error);
push_handle_free (handle);
return;
}
if ((handle->op_job->flags & G_FILE_COPY_NOFOLLOW_SYMLINKS) &&
g_file_info_get_file_type (info) == G_FILE_TYPE_SYMBOLIC_LINK)
{
/* Fall back to default implementation to copy symlink */
g_vfs_job_failed (handle->job, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED,
_("Operation not supported"));
push_handle_free (handle);
g_object_unref (info);
return;
}
g_file_read_async (source_file,
0, handle->job->cancellable,
push_source_open_cb, handle);
g_object_unref (info);
}
static gboolean
try_push (GVfsBackend *backend,
GVfsJobPush *job,
const char *destination,
const char *local_path,
GFileCopyFlags flags,
gboolean remove_source,
GFileProgressCallback progress_callback,
gpointer progress_callback_data)
{
GFile *source;
PushHandle *handle;
2023-02-15 15:41:32 +08:00
if (remove_source && (flags & G_FILE_COPY_NO_FALLBACK_FOR_MOVE))
{
g_vfs_job_failed (G_VFS_JOB (job), G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED,
_("Operation not supported"));
return TRUE;
}
2022-06-29 16:07:13 +08:00
handle = g_slice_new0 (PushHandle);
handle->backend = g_object_ref (backend);
handle->job = g_object_ref (G_VFS_JOB (job));
handle->op_job = job;
handle->uri = g_vfs_backend_dav_uri_for_path (backend, destination, FALSE);
source = g_file_new_for_path (local_path);
g_file_query_info_async (source,
G_FILE_ATTRIBUTE_STANDARD_TYPE,
G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
0, handle->job->cancellable,
push_source_lstat_cb, handle);
g_object_unref (source);
return TRUE;
}
/* ************************************************************************* */
/* */
static void
g_vfs_backend_dav_class_init (GVfsBackendDavClass *klass)
{
GObjectClass *gobject_class;
GVfsBackendClass *backend_class;
gobject_class = G_OBJECT_CLASS (klass);
gobject_class->finalize = g_vfs_backend_dav_finalize;
backend_class = G_VFS_BACKEND_CLASS (klass);
2023-02-15 15:41:32 +08:00
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
* a lot of files are opened at the same time
*/
http_try_init_session (32, 32);
2022-06-29 16:07:13 +08:00
}