mirror of https://gitee.com/openkylin/gvfs.git
4201 lines
117 KiB
C
4201 lines
117 KiB
C
/* GIO - GLib Input, Output and Streaming Library
|
||
*
|
||
* Copyright (C) 2008 Red Hat, Inc.
|
||
* Copyright (C) 2021 Igalia S.L.
|
||
*
|
||
* 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
|
||
|
||
static gboolean
|
||
soup_authenticate (SoupMessage *msg,
|
||
SoupAuth *auth,
|
||
gboolean retrying,
|
||
gpointer user_data);
|
||
|
||
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;
|
||
gboolean interactive;
|
||
|
||
AuthInfo server_auth;
|
||
AuthInfo proxy_auth;
|
||
|
||
};
|
||
|
||
struct _GVfsBackendDav
|
||
{
|
||
GVfsBackendHttp parent_instance;
|
||
|
||
MountAuthData auth_info;
|
||
gchar *last_good_path;
|
||
|
||
/* 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)
|
||
{
|
||
return soup_message_headers_get_one (soup_message_get_response_headers (msg),
|
||
header) != NULL;
|
||
}
|
||
|
||
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,
|
||
GUri *uri)
|
||
{
|
||
char *string;
|
||
|
||
string = g_uri_to_string_partial (uri, G_URI_HIDE_PASSWORD);
|
||
soup_message_headers_append (soup_message_get_request_headers (msg),
|
||
"Destination",
|
||
string);
|
||
g_free (string);
|
||
}
|
||
static void
|
||
message_add_overwrite_header (SoupMessage *msg,
|
||
gboolean overwrite)
|
||
{
|
||
soup_message_headers_append (soup_message_get_request_headers (msg),
|
||
"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";
|
||
|
||
soup_message_headers_append (soup_message_get_request_headers (msg),
|
||
"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
|
||
dav_uri_match (GUri *a, GUri *b, gboolean relax)
|
||
{
|
||
gboolean diff;
|
||
char *ua, *ub;
|
||
|
||
ua = g_uri_unescape_string (g_uri_get_path (a), "/");
|
||
ub = g_uri_unescape_string (g_uri_get_path (b), "/");
|
||
|
||
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);
|
||
|
||
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;
|
||
|
||
header = soup_message_headers_get_one (soup_message_get_request_headers (msg),
|
||
"Apply-To-Redirect-Ref");
|
||
|
||
if (header == NULL || g_ascii_strcasecmp (header, "T"))
|
||
return FALSE;
|
||
|
||
return TRUE;
|
||
}
|
||
|
||
static GUri *
|
||
g_vfs_backend_dav_uri_for_path (GVfsBackend *backend,
|
||
const char *path,
|
||
gboolean is_dir)
|
||
{
|
||
GUri *mount_base;
|
||
GUri *uri;
|
||
char *fn_encoded;
|
||
char *new_path;
|
||
|
||
mount_base = http_backend_get_mount_base (backend);
|
||
|
||
/* "/" means "whatever mount_base is" */
|
||
if (!strcmp (path, "/"))
|
||
return g_uri_ref (mount_base);
|
||
|
||
/* 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, "/"))
|
||
new_path = g_build_path ("/", g_uri_get_path (mount_base), fn_encoded, NULL);
|
||
else
|
||
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);
|
||
|
||
g_free (fn_encoded);
|
||
g_free (new_path);
|
||
|
||
return uri;
|
||
}
|
||
|
||
static gboolean
|
||
g_vfs_backend_dav_stream_skip (GInputStream *stream, GError **error)
|
||
{
|
||
for (;;)
|
||
{
|
||
gssize skipped = g_input_stream_skip (stream, 4096, NULL, error);
|
||
if (skipped < 0)
|
||
{
|
||
return FALSE;
|
||
}
|
||
else if (!skipped)
|
||
break;
|
||
}
|
||
|
||
g_input_stream_close (stream, NULL, NULL);
|
||
return TRUE;
|
||
}
|
||
|
||
static void
|
||
g_vfs_backend_dav_setup_display_name (GVfsBackend *backend)
|
||
{
|
||
GUri *mount_base;
|
||
char *display_name;
|
||
char port[7] = {0, };
|
||
gint gport;
|
||
|
||
#ifdef HAVE_AVAHI
|
||
GVfsBackendDav *dav_backend = G_VFS_BACKEND_DAV (backend);
|
||
|
||
if (dav_backend->resolver != NULL)
|
||
{
|
||
const char *name;
|
||
name = g_vfs_dns_sd_resolver_get_service_name (dav_backend->resolver);
|
||
g_vfs_backend_set_display_name (backend, name);
|
||
return;
|
||
}
|
||
#endif
|
||
|
||
mount_base = http_backend_get_mount_base (backend);
|
||
|
||
gport = g_uri_get_port (mount_base);
|
||
if ((gport > 0) && (gport != 80) && (gport != 443))
|
||
g_snprintf (port, sizeof (port), ":%u", g_uri_get_port (mount_base));
|
||
|
||
if (g_uri_get_user (mount_base) != NULL)
|
||
/* Translators: This is the name of the WebDAV share constructed as
|
||
"WebDAV as <username> on <hostname>:<port>"; the ":<port>" part is
|
||
the second %s and only shown if it is not the default http(s) port. */
|
||
display_name = g_strdup_printf (_("%s on %s%s"),
|
||
g_uri_get_user (mount_base),
|
||
g_uri_get_host (mount_base),
|
||
port);
|
||
else
|
||
display_name = g_strdup_printf ("%s%s",
|
||
g_uri_get_host (mount_base),
|
||
port);
|
||
|
||
g_vfs_backend_set_display_name (backend, display_name);
|
||
g_free (display_name);
|
||
}
|
||
|
||
static gboolean
|
||
accept_certificate (SoupMessage *msg,
|
||
GTlsCertificate *certificate,
|
||
GTlsCertificateFlags errors,
|
||
gpointer user_data)
|
||
{
|
||
GVfsBackendDav *dav = G_VFS_BACKEND_DAV (user_data);
|
||
|
||
return (errors == dav->certificate_errors &&
|
||
g_tls_certificate_is_same (certificate, dav->certificate));
|
||
}
|
||
|
||
static void
|
||
dav_message_connect_signals (SoupMessage *message, GVfsBackend *backend)
|
||
{
|
||
GVfsBackendDav *dav_backend = G_VFS_BACKEND_DAV (backend);
|
||
|
||
/* we always have to connect this as the message can be
|
||
* re-sent with a differently set certificate_errors field
|
||
*/
|
||
g_signal_connect (message, "accept-certificate",
|
||
G_CALLBACK (accept_certificate), backend);
|
||
|
||
g_signal_connect (message, "authenticate",
|
||
G_CALLBACK (soup_authenticate),
|
||
&dav_backend->auth_info);
|
||
}
|
||
|
||
static void
|
||
dav_send_async_with_redir_cb (GObject *source, GAsyncResult *ret, gpointer user_data)
|
||
{
|
||
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))
|
||
{
|
||
if (status == SOUP_STATUS_MOVED_PERMANENTLY ||
|
||
status == SOUP_STATUS_TEMPORARY_REDIRECT)
|
||
{
|
||
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;
|
||
}
|
||
|
||
/* 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;
|
||
}
|
||
|
||
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);
|
||
}
|
||
|
||
static void
|
||
g_vfs_backend_dav_send_async (GVfsBackend *backend,
|
||
SoupMessage *message,
|
||
GAsyncReadyCallback callback,
|
||
gpointer user_data)
|
||
{
|
||
SoupSession *session = G_VFS_BACKEND_HTTP (backend)->session;
|
||
GTask *task = g_task_new (backend, NULL, callback, user_data);
|
||
|
||
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);
|
||
|
||
soup_message_set_flags (message, SOUP_MESSAGE_NO_REDIRECT);
|
||
|
||
soup_session_send_async (session, message, G_PRIORITY_DEFAULT, NULL,
|
||
dav_send_async_with_redir_cb, task);
|
||
}
|
||
|
||
static GInputStream *
|
||
g_vfs_backend_dav_send_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);
|
||
|
||
return g_task_propagate_pointer (G_TASK (result), error);
|
||
}
|
||
|
||
static SoupMessage *
|
||
g_vfs_backend_dav_get_async_result_message (GVfsBackend *backend,
|
||
GAsyncResult *result)
|
||
{
|
||
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);
|
||
|
||
return g_task_get_task_data (G_TASK (result));
|
||
}
|
||
|
||
/* ************************************************************************* */
|
||
/* 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;
|
||
}
|
||
|
||
static int
|
||
xml_read_cb (void *ctx, char *buf, int len)
|
||
{
|
||
return g_input_stream_read (ctx, buf, len, NULL, NULL);
|
||
}
|
||
|
||
static xmlDocPtr
|
||
parse_xml (SoupMessage *msg,
|
||
GInputStream *body,
|
||
xmlNodePtr *root,
|
||
const char *name,
|
||
GError **error)
|
||
{
|
||
xmlDocPtr doc;
|
||
|
||
if (!SOUP_STATUS_IS_SUCCESSFUL (soup_message_get_status (msg)))
|
||
{
|
||
g_set_error (error,
|
||
G_IO_ERROR,
|
||
http_error_code_from_status (soup_message_get_status (msg)),
|
||
_("HTTP Error: %s"), soup_message_get_reason_phrase (msg));
|
||
return NULL;
|
||
}
|
||
|
||
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);
|
||
|
||
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;
|
||
|
||
GUri *target;
|
||
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
|
||
multistatus_parse (SoupMessage *msg,
|
||
GInputStream *body,
|
||
Multistatus *multistatus,
|
||
GError **error)
|
||
{
|
||
xmlDocPtr doc;
|
||
xmlNodePtr root;
|
||
GUri *uri;
|
||
|
||
doc = parse_xml (msg, body, &root, "multistatus", error);
|
||
|
||
if (doc == NULL)
|
||
return FALSE;
|
||
|
||
uri = soup_message_get_uri (msg);
|
||
|
||
multistatus->doc = doc;
|
||
multistatus->root = root;
|
||
multistatus->target = uri;
|
||
multistatus->path = g_uri_unescape_string (g_uri_get_path (uri), "/");
|
||
|
||
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;
|
||
GUri *uri;
|
||
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;
|
||
|
||
uri = g_uri_parse_relative (multistatus->target, text, SOUP_HTTP_URI_FLAGS, NULL);
|
||
if (uri == NULL)
|
||
return FALSE;
|
||
|
||
path = g_uri_unescape_string (g_uri_get_path (uri), "/");
|
||
g_uri_unref (uri);
|
||
|
||
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"))
|
||
{
|
||
GDateTime *gd;
|
||
|
||
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));
|
||
g_file_info_set_attribute_uint32 (info, G_FILE_ATTRIBUTE_TIME_MODIFIED_USEC, 0);
|
||
g_date_time_unref (gd);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
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;
|
||
|
||
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);
|
||
}
|
||
|
||
|
||
static SoupMessage *
|
||
propfind_request_new (GVfsBackend *backend,
|
||
const char *filename,
|
||
guint depth,
|
||
const PropName *properties)
|
||
{
|
||
SoupMessage *msg;
|
||
GUri *uri;
|
||
const char *header_depth;
|
||
GString *body;
|
||
GBytes *bytes;
|
||
|
||
uri = g_vfs_backend_dav_uri_for_path (backend, filename, depth > 0);
|
||
msg = soup_message_new_from_uri (SOUP_METHOD_PROPFIND, uri);
|
||
g_uri_unref (uri);
|
||
|
||
if (msg == NULL)
|
||
return NULL;
|
||
|
||
if (depth == 0)
|
||
header_depth = "0";
|
||
else if (depth == 1)
|
||
header_depth = "1";
|
||
else
|
||
header_depth = "infinity";
|
||
|
||
soup_message_headers_append (soup_message_get_request_headers (msg),
|
||
"Depth", header_depth);
|
||
|
||
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);
|
||
|
||
bytes = g_string_free_to_bytes (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);
|
||
|
||
return msg;
|
||
}
|
||
|
||
static SoupMessage *
|
||
stat_location_start (GUri *uri,
|
||
gboolean count_children)
|
||
{
|
||
SoupMessage *msg;
|
||
const char *depth;
|
||
GBytes *bytes;
|
||
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";
|
||
|
||
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);
|
||
|
||
return msg;
|
||
}
|
||
|
||
static gboolean
|
||
stat_location_end (SoupMessage *msg,
|
||
GInputStream *body,
|
||
GFileType *target_type,
|
||
gint64 *target_size,
|
||
guint *num_children)
|
||
{
|
||
Multistatus ms;
|
||
xmlNodeIter iter;
|
||
gboolean res;
|
||
guint child_count;
|
||
GFileInfo *file_info;
|
||
|
||
if (!body || soup_message_get_status (msg) != SOUP_STATUS_MULTI_STATUS)
|
||
return FALSE;
|
||
|
||
res = multistatus_parse (msg, body, &ms, NULL);
|
||
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;
|
||
}
|
||
|
||
typedef struct _StatLocationData {
|
||
GUri *uri;
|
||
GFileType target_type;
|
||
gint64 target_size;
|
||
guint num_children;
|
||
} StatLocationData;
|
||
|
||
static void
|
||
stat_location_data_free (gpointer p)
|
||
{
|
||
g_uri_unref (((StatLocationData *)p)->uri);
|
||
g_slice_free (StatLocationData, p);
|
||
}
|
||
|
||
static void
|
||
stat_location_cb (GObject *source, GAsyncResult *result, gpointer user_data)
|
||
{
|
||
GVfsBackend *backend = G_VFS_BACKEND (source);
|
||
SoupMessage *msg = g_vfs_backend_dav_get_async_result_message (backend, result);
|
||
GTask *task = user_data;
|
||
StatLocationData *data = g_task_get_task_data (task);
|
||
GInputStream *body;
|
||
GError *error = NULL;
|
||
guint status;
|
||
gboolean res;
|
||
|
||
body = g_vfs_backend_dav_send_finish (backend, result, &error);
|
||
|
||
if (!body)
|
||
{
|
||
g_object_unref (msg);
|
||
g_task_return_error (task, error);
|
||
g_object_unref (task);
|
||
return;
|
||
}
|
||
|
||
status = soup_message_get_status (msg);
|
||
if (status != SOUP_STATUS_MULTI_STATUS)
|
||
{
|
||
error = g_error_new (G_IO_ERROR,
|
||
http_error_code_from_status (status),
|
||
_("HTTP Error: %s"),
|
||
soup_message_get_reason_phrase (msg));
|
||
|
||
g_object_unref (msg);
|
||
g_task_return_error (task, error);
|
||
g_object_unref (body);
|
||
g_object_unref (task);
|
||
return;
|
||
}
|
||
|
||
res = stat_location_end (msg, body, &data->target_type,
|
||
&data->target_size, &data->num_children);
|
||
g_object_unref (msg);
|
||
g_object_unref (body);
|
||
|
||
if (res == FALSE)
|
||
{
|
||
error = g_error_new (G_IO_ERROR, G_IO_ERROR_FAILED,
|
||
_("Response invalid"));
|
||
g_task_return_error (task, error);
|
||
}
|
||
else
|
||
g_task_return_boolean (task, TRUE);
|
||
|
||
g_object_unref (task);
|
||
}
|
||
|
||
static void
|
||
stat_location_async (GVfsBackend *backend,
|
||
GUri *uri,
|
||
gboolean count_children,
|
||
GAsyncReadyCallback callback,
|
||
gpointer user_data)
|
||
{
|
||
GTask *task;
|
||
SoupMessage *msg;
|
||
StatLocationData *data;
|
||
|
||
task = g_task_new (backend, NULL, callback, user_data);
|
||
data = g_slice_new (StatLocationData);
|
||
data->uri = g_uri_ref (uri);
|
||
|
||
g_task_set_source_tag (task, stat_location_async);
|
||
g_task_set_task_data (task, data, stat_location_data_free);
|
||
|
||
msg = stat_location_start (uri, count_children);
|
||
if (msg == NULL)
|
||
{
|
||
g_task_return_boolean (task, FALSE);
|
||
g_object_unref (task);
|
||
return;
|
||
}
|
||
|
||
dav_message_connect_signals (msg, backend);
|
||
|
||
g_vfs_backend_dav_send_async (backend, msg, stat_location_cb, task);
|
||
}
|
||
|
||
static gboolean
|
||
stat_location_finish (GVfsBackend *backend,
|
||
GFileType *target_type,
|
||
gint64 *target_size,
|
||
guint *num_children,
|
||
GAsyncResult *result,
|
||
GError **error)
|
||
{
|
||
g_return_val_if_fail (G_VFS_IS_BACKEND (backend), FALSE);
|
||
g_return_val_if_fail (G_IS_ASYNC_RESULT (result), FALSE);
|
||
g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
|
||
g_return_val_if_fail (g_task_is_valid (result, backend), FALSE);
|
||
g_return_val_if_fail (g_async_result_is_tagged (result, stat_location_async), FALSE);
|
||
|
||
if (g_task_propagate_boolean (G_TASK (result), error))
|
||
{
|
||
StatLocationData *data = g_task_get_task_data (G_TASK (result));
|
||
if (target_type) *target_type = data->target_type;
|
||
if (target_size) *target_size = data->target_size;
|
||
if (num_children) *num_children = data->num_children;
|
||
return TRUE;
|
||
}
|
||
|
||
return FALSE;
|
||
}
|
||
|
||
static GUri *
|
||
stat_location_async_get_uri (GVfsBackend *backend, GAsyncResult *result)
|
||
{
|
||
StatLocationData *data;
|
||
|
||
g_return_val_if_fail (G_VFS_IS_BACKEND (backend), NULL);
|
||
g_return_val_if_fail (G_IS_ASYNC_RESULT (result), NULL);
|
||
g_return_val_if_fail (g_task_is_valid (result, backend), NULL);
|
||
|
||
data = g_task_get_task_data (G_TASK (result));
|
||
return data->uri;
|
||
|
||
}
|
||
|
||
/* ************************************************************************* */
|
||
/* 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);
|
||
|
||
}
|
||
|
||
static gboolean
|
||
soup_authenticate (SoupMessage *msg,
|
||
SoupAuth *auth,
|
||
gboolean retrying,
|
||
gpointer user_data)
|
||
{
|
||
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;
|
||
|
||
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;
|
||
|
||
g_debug ("+ soup_authenticate (interactive, %s) \n",
|
||
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))
|
||
return FALSE;
|
||
|
||
have_auth = info->username && info->password;
|
||
|
||
if (have_auth == FALSE && g_vfs_keyring_is_available ())
|
||
{
|
||
const char *host;
|
||
gchar *hostp = NULL;
|
||
gint port;
|
||
|
||
pw_ask_flags |= G_ASK_PASSWORD_SAVING_SUPPORTED;
|
||
|
||
if (is_proxy)
|
||
{
|
||
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;
|
||
}
|
||
else
|
||
{
|
||
GUri *uri = soup_message_get_uri (msg);
|
||
|
||
host = g_uri_get_host (uri);
|
||
port = g_uri_get_port (uri);
|
||
}
|
||
|
||
res = g_vfs_keyring_lookup_password (info->username,
|
||
host,
|
||
NULL,
|
||
"http",
|
||
realm,
|
||
is_proxy ? "proxy" : "basic",
|
||
port,
|
||
&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;
|
||
}
|
||
|
||
if (hostp)
|
||
g_free (hostp);
|
||
}
|
||
|
||
if (retrying == FALSE && have_auth)
|
||
{
|
||
soup_auth_authenticate (auth, info->username, info->password);
|
||
return FALSE;
|
||
}
|
||
|
||
if (is_proxy == FALSE)
|
||
{
|
||
if (realm == NULL)
|
||
realm = _("WebDAV share");
|
||
|
||
/* Translators: %s is the name of the WebDAV share */
|
||
prompt = g_strdup_printf (_("Authentication Required\nEnter password for “%s”:"), realm);
|
||
}
|
||
else
|
||
prompt = g_strdup (_("Authentication Required\nEnter proxy password:"));
|
||
|
||
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
|
||
soup_auth_cancel (auth);
|
||
|
||
g_debug ("- soup_authenticate \n");
|
||
g_free (prompt);
|
||
|
||
return !res || aborted;
|
||
}
|
||
|
||
static void
|
||
keyring_save_authinfo (AuthInfo *info,
|
||
GUri *uri,
|
||
gboolean is_proxy)
|
||
{
|
||
const char *type = is_proxy ? "proxy" : "basic";
|
||
|
||
g_vfs_keyring_save_password (info->username,
|
||
g_uri_get_host (uri),
|
||
NULL,
|
||
"http",
|
||
info->realm,
|
||
type,
|
||
g_uri_get_port (uri),
|
||
info->password,
|
||
info->pw_save);
|
||
}
|
||
|
||
/* ************************************************************************* */
|
||
|
||
static GUri *
|
||
g_mount_spec_to_dav_uri (GMountSpec *spec)
|
||
{
|
||
GUri *uri;
|
||
const char *host;
|
||
const char *port;
|
||
const char *ssl;
|
||
const char *path;
|
||
const char *scheme;
|
||
char *host_str;
|
||
char *path_str;
|
||
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))
|
||
scheme = "https";
|
||
else
|
||
scheme = "http";
|
||
|
||
port_num = -1;
|
||
|
||
/* always a valid number if set */
|
||
if (port != NULL)
|
||
port_num = atoi (port);
|
||
|
||
/* IPv6 host does not include brackets in SoupURI, but GMountSpec host does */
|
||
if (gvfs_is_ipv6 (host))
|
||
host_str = g_strndup (host + 1, strlen (host) - 2);
|
||
else
|
||
host_str = g_strdup (host);
|
||
|
||
path_str = dav_uri_encode (path);
|
||
|
||
/* 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);
|
||
|
||
return uri;
|
||
}
|
||
|
||
static GMountSpec *
|
||
g_mount_spec_from_dav_uri (GVfsBackendDav *dav_backend,
|
||
GUri *uri)
|
||
{
|
||
GMountSpec *spec;
|
||
char *local_path;
|
||
gboolean ssl;
|
||
gint port_num;
|
||
|
||
#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");
|
||
|
||
/* IPv6 host does not include brackets in GUri, but GMountSpec host does */
|
||
if (strchr (g_uri_get_host (uri), ':'))
|
||
{
|
||
char *host = g_strdup_printf ("[%s]", g_uri_get_host (uri));
|
||
g_mount_spec_set (spec, "host", host);
|
||
g_free (host);
|
||
}
|
||
else
|
||
g_mount_spec_set (spec, "host", g_uri_get_host (uri));
|
||
|
||
ssl = !strcmp (g_uri_get_scheme (uri), "https");
|
||
|
||
g_mount_spec_set (spec, "ssl", ssl ? "true" : "false");
|
||
|
||
port_num = g_uri_get_port (uri);
|
||
if (port_num > 0 && port_num != (ssl ? 443 : 80))
|
||
{
|
||
char *port = g_strdup_printf ("%d", port_num);
|
||
g_mount_spec_set (spec, "port", port);
|
||
g_free (port);
|
||
}
|
||
|
||
/* There must not be any illegal characters in the
|
||
URL at this point */
|
||
local_path = g_uri_unescape_string (g_uri_get_path (uri), "/");
|
||
g_mount_spec_set_mount_prefix (spec, local_path);
|
||
g_free (local_path);
|
||
|
||
return spec;
|
||
}
|
||
|
||
#ifdef HAVE_AVAHI
|
||
static GUri *
|
||
dav_uri_from_dns_sd_resolver (GVfsBackendDav *dav_backend)
|
||
{
|
||
GUri *uri;
|
||
char *user;
|
||
char *path;
|
||
char *address;
|
||
char *host;
|
||
gchar *interface;
|
||
const char *service_type;
|
||
const char *scheme;
|
||
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)
|
||
scheme = "https";
|
||
else
|
||
scheme = "http";
|
||
|
||
/* IPv6 host does not include brackets in GUri, but GVfsDnsSdResolver host does */
|
||
if (gvfs_is_ipv6 (address))
|
||
{
|
||
/* Link-local addresses require interface to be specified. */
|
||
if (g_str_has_prefix (address, "[fe80:") && interface != NULL)
|
||
{
|
||
host = g_strconcat (address + 1, interface, NULL);
|
||
host[strlen (address) - 2] = '%';
|
||
}
|
||
else
|
||
host = g_strndup (address + 1, strlen (address) - 2);
|
||
}
|
||
else
|
||
host = g_strdup (address);
|
||
|
||
uri = g_uri_build_with_user (SOUP_HTTP_URI_FLAGS,
|
||
scheme,
|
||
user,
|
||
NULL,
|
||
NULL,
|
||
host,
|
||
port,
|
||
path,
|
||
NULL,
|
||
NULL);
|
||
|
||
g_free (address);
|
||
g_free (interface);
|
||
g_free (user);
|
||
g_free (path);
|
||
g_free (host);
|
||
|
||
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 */
|
||
|
||
static void
|
||
mount_success (GVfsBackend *backend, GVfsJobMount *job)
|
||
{
|
||
GVfsBackendDav *dav_backend = G_VFS_BACKEND_DAV (backend);
|
||
GVfsBackendHttp *http_backend = G_VFS_BACKEND_HTTP (backend);
|
||
GMountSpec *mount_spec;
|
||
GUri *tmp;
|
||
const gchar *user;
|
||
|
||
/* 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 */
|
||
|
||
/* 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);
|
||
|
||
/* dup the mountspec, but only copy known fields */
|
||
mount_spec = g_mount_spec_from_dav_uri (dav_backend, http_backend->mount_base);
|
||
|
||
/* 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);
|
||
|
||
g_vfs_backend_set_mount_spec (backend, mount_spec);
|
||
g_vfs_backend_set_icon_name (backend, "folder-remote");
|
||
g_vfs_backend_set_symbolic_icon_name (backend, "folder-remote-symbolic");
|
||
|
||
g_vfs_backend_dav_setup_display_name (backend);
|
||
|
||
/* cleanup */
|
||
g_mount_spec_unref (mount_spec);
|
||
|
||
g_vfs_job_succeeded (G_VFS_JOB (job));
|
||
g_debug ("- mount\n");
|
||
}
|
||
|
||
static void try_mount_opts_cb (GObject *source, GAsyncResult *result, gpointer user_data);
|
||
|
||
static void
|
||
try_mount_stat_cb (GObject *source, GAsyncResult *result, gpointer user_data)
|
||
{
|
||
GVfsBackend *backend = G_VFS_BACKEND (source);
|
||
GVfsBackendDav *dav_backend = G_VFS_BACKEND_DAV (backend);
|
||
GVfsBackendHttp *http_backend = G_VFS_BACKEND_HTTP (backend);
|
||
GVfsJobMount *job = user_data;
|
||
SoupMessage *msg_stat = g_vfs_backend_dav_get_async_result_message (backend, result);
|
||
SoupMessage *msg_opts;
|
||
GInputStream *body;
|
||
GError *error = NULL;
|
||
GFileType file_type;
|
||
gboolean res;
|
||
gboolean is_collection;
|
||
GUri *tmp;
|
||
char *new_path;
|
||
|
||
body = g_vfs_backend_dav_send_finish (backend, result, &error);
|
||
|
||
if (body == NULL)
|
||
{
|
||
g_vfs_job_failed_from_error (G_VFS_JOB (job), error);
|
||
g_error_free (error);
|
||
goto clear_msg;
|
||
}
|
||
|
||
res = stat_location_end (msg_stat, body, &file_type, NULL, NULL);
|
||
is_collection = res && file_type == G_FILE_TYPE_DIRECTORY;
|
||
|
||
if (!g_vfs_backend_dav_stream_skip (body, &error))
|
||
{
|
||
g_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;
|
||
}
|
||
|
||
g_object_unref (body);
|
||
|
||
soup_message_headers_clear (soup_message_get_response_headers (msg_stat));
|
||
|
||
g_debug (" [%s] webdav: %d, collection %d [res: %d]\n",
|
||
g_uri_get_path (http_backend->mount_base), TRUE, is_collection, res);
|
||
|
||
if ((is_collection == FALSE) && (dav_backend->last_good_path != NULL))
|
||
{
|
||
mount_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;
|
||
}
|
||
|
||
/* we have found a new good root, try the parent ... */
|
||
g_free (dav_backend->last_good_path);
|
||
dav_backend->last_good_path = g_strdup (g_uri_get_path (http_backend->mount_base));
|
||
new_path = path_get_parent_dir (dav_backend->last_good_path);
|
||
|
||
tmp = http_backend->mount_base;
|
||
http_backend->mount_base = soup_uri_copy (tmp, SOUP_URI_PATH, new_path, SOUP_URI_NONE);
|
||
g_uri_unref (tmp);
|
||
|
||
g_free (new_path);
|
||
|
||
/* if we have found a root that is good then we assume
|
||
that we also have obtained to correct credentials
|
||
and we switch the auth handler. This will prevent us
|
||
from asking for *different* credentials *again* if the
|
||
server should response with 401 for some of the parent
|
||
collections. See also bug #677753 */
|
||
dav_backend->auth_info.interactive = FALSE;
|
||
|
||
/* break out */
|
||
if (g_strcmp0 (dav_backend->last_good_path, "/") == 0)
|
||
{
|
||
mount_success (backend, job);
|
||
goto clear_msg;
|
||
}
|
||
|
||
/* else loop the whole thing from options */
|
||
|
||
msg_opts = soup_message_new_from_uri (SOUP_METHOD_OPTIONS,
|
||
http_backend->mount_base);
|
||
|
||
dav_message_connect_signals (msg_opts, backend);
|
||
|
||
g_vfs_backend_dav_send_async (backend, msg_opts, try_mount_opts_cb, job);
|
||
|
||
clear_msg:
|
||
g_object_unref (msg_stat);
|
||
}
|
||
|
||
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;
|
||
|
||
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;
|
||
|
||
/* 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);
|
||
|
||
dav_message_connect_signals (msg_opts, backend);
|
||
|
||
g_vfs_backend_dav_send_async (backend, msg_opts,
|
||
try_mount_opts_cb, job);
|
||
return;
|
||
}
|
||
}
|
||
|
||
/* 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;
|
||
}
|
||
|
||
status = soup_message_get_status (msg_opts);
|
||
|
||
/* 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;
|
||
|
||
dav_backend->auth_info.retrying_after_403 = TRUE;
|
||
|
||
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));
|
||
|
||
/* 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);
|
||
|
||
dav_message_connect_signals (msg_opts, backend);
|
||
|
||
g_vfs_backend_dav_send_async (backend, msg_opts, try_mount_opts_cb, job);
|
||
return;
|
||
}
|
||
|
||
is_success = SOUP_STATUS_IS_SUCCESSFUL (status);
|
||
is_webdav = sm_has_header (msg_opts, "DAV");
|
||
|
||
if ((is_success && !is_webdav) || (status == SOUP_STATUS_METHOD_NOT_ALLOWED))
|
||
{
|
||
if (dav_backend->last_good_path == NULL)
|
||
g_vfs_job_failed (G_VFS_JOB (job),
|
||
G_IO_ERROR, G_IO_ERROR_FAILED,
|
||
_("Not a WebDAV enabled share"));
|
||
else
|
||
mount_success (backend, job);
|
||
goto clear_msgs;
|
||
}
|
||
else if (!is_success)
|
||
{
|
||
int error_code = http_error_code_from_status (soup_message_get_status (msg_opts));
|
||
|
||
if (dav_backend->last_good_path == NULL)
|
||
g_vfs_job_failed (G_VFS_JOB (job),
|
||
G_IO_ERROR, error_code,
|
||
_("HTTP Error: %s"),
|
||
soup_message_get_reason_phrase (msg_opts));
|
||
else
|
||
mount_success (backend, job);
|
||
goto clear_msgs;
|
||
}
|
||
|
||
if (!g_vfs_backend_dav_stream_skip (body, &error))
|
||
{
|
||
if (dav_backend->last_good_path == NULL)
|
||
g_vfs_job_failed_from_error (G_VFS_JOB (job), error);
|
||
else
|
||
mount_success (backend, job);
|
||
g_error_free (error);
|
||
goto clear_msgs;
|
||
}
|
||
|
||
g_object_unref (body);
|
||
|
||
cur_uri = soup_message_get_uri (msg_opts);
|
||
|
||
/* The count_children parameter is intentionally set to TRUE to be sure that
|
||
enumeration is possible: https://gitlab.gnome.org/GNOME/gvfs/-/issues/468 */
|
||
msg_stat = stat_location_start (cur_uri, TRUE);
|
||
|
||
dav_message_connect_signals (msg_stat, backend);
|
||
|
||
g_vfs_backend_dav_send_async (backend, msg_stat, try_mount_stat_cb, job);
|
||
|
||
clear_msgs:
|
||
g_object_unref (msg_opts);
|
||
}
|
||
|
||
static 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;
|
||
}
|
||
|
||
static PropName ls_propnames[] = {
|
||
{"creationdate", NULL},
|
||
{"displayname", NULL},
|
||
{"getcontentlength", NULL},
|
||
{"getcontenttype", NULL},
|
||
{"getetag", NULL},
|
||
{"getlastmodified", NULL},
|
||
{"resourcetype", NULL},
|
||
{NULL, NULL}
|
||
};
|
||
|
||
/* *** query_info () *** */
|
||
static void
|
||
try_query_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);
|
||
GVfsJobQueryInfo *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;
|
||
|
||
res = multistatus_parse (msg, body, &ms, &error);
|
||
g_object_unref (body);
|
||
|
||
if (res == FALSE)
|
||
goto error;
|
||
|
||
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"));
|
||
return;
|
||
|
||
error:
|
||
g_vfs_job_failed_from_error (G_VFS_JOB (job), error);
|
||
g_error_free (error);
|
||
g_object_unref (msg);
|
||
}
|
||
|
||
static gboolean
|
||
try_query_info (GVfsBackend *backend,
|
||
GVfsJobQueryInfo *job,
|
||
const char *filename,
|
||
GFileQueryInfoFlags flags,
|
||
GFileInfo *info,
|
||
GFileAttributeMatcher *matcher)
|
||
{
|
||
SoupMessage *msg;
|
||
|
||
g_debug ("Query info %s\n", filename);
|
||
|
||
msg = propfind_request_new (backend, filename, 0, ls_propnames);
|
||
if (msg == NULL)
|
||
{
|
||
g_vfs_job_failed (G_VFS_JOB (job),
|
||
G_IO_ERROR, G_IO_ERROR_FAILED,
|
||
_("Could not create request"));
|
||
return TRUE;
|
||
}
|
||
|
||
message_add_redirect_header (msg, flags);
|
||
|
||
dav_message_connect_signals (msg, backend);
|
||
|
||
g_vfs_backend_dav_send_async (backend, msg, try_query_info_cb, job);
|
||
|
||
return TRUE;
|
||
}
|
||
|
||
static PropName fs_info_propnames[] = {
|
||
{"quota-available-bytes", NULL},
|
||
{"quota-used-bytes", NULL},
|
||
{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;
|
||
|
||
res = multistatus_parse (msg, body, &ms, &error);
|
||
g_object_unref (body);
|
||
|
||
if (res == FALSE)
|
||
goto error;
|
||
|
||
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_fs_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"));
|
||
return;
|
||
|
||
error:
|
||
g_vfs_job_failed_from_error (G_VFS_JOB (job), error);
|
||
g_error_free (error);
|
||
g_object_unref (msg);
|
||
}
|
||
|
||
static gboolean
|
||
try_query_fs_info (GVfsBackend *backend,
|
||
GVfsJobQueryFsInfo *job,
|
||
const char *filename,
|
||
GFileInfo *info,
|
||
GFileAttributeMatcher *attribute_matcher)
|
||
{
|
||
SoupMessage *msg;
|
||
|
||
g_file_info_set_attribute_string (info,
|
||
G_FILE_ATTRIBUTE_FILESYSTEM_TYPE,
|
||
"webdav");
|
||
g_file_info_set_attribute_boolean (info,
|
||
G_FILE_ATTRIBUTE_FILESYSTEM_REMOTE,
|
||
TRUE);
|
||
g_file_info_set_attribute_uint32 (info,
|
||
G_FILE_ATTRIBUTE_FILESYSTEM_USE_PREVIEW,
|
||
G_FILESYSTEM_PREVIEW_TYPE_IF_ALWAYS);
|
||
|
||
if (! (g_file_attribute_matcher_matches (attribute_matcher,
|
||
G_FILE_ATTRIBUTE_FILESYSTEM_SIZE) ||
|
||
g_file_attribute_matcher_matches (attribute_matcher,
|
||
G_FILE_ATTRIBUTE_FILESYSTEM_USED) ||
|
||
g_file_attribute_matcher_matches (attribute_matcher,
|
||
G_FILE_ATTRIBUTE_FILESYSTEM_FREE)))
|
||
{
|
||
g_vfs_job_succeeded (G_VFS_JOB (job));
|
||
return TRUE;
|
||
}
|
||
|
||
msg = propfind_request_new (backend, filename, 0, fs_info_propnames);
|
||
if (msg == NULL)
|
||
{
|
||
g_vfs_job_failed (G_VFS_JOB (job),
|
||
G_IO_ERROR, G_IO_ERROR_FAILED,
|
||
_("Could not create request"));
|
||
return TRUE;
|
||
}
|
||
|
||
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);
|
||
|
||
if (!body)
|
||
goto error;
|
||
|
||
res = multistatus_parse (msg, body, &ms, &error);
|
||
g_object_unref (body);
|
||
|
||
if (res == FALSE)
|
||
goto error;
|
||
|
||
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));
|
||
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;
|
||
}
|
||
|
||
/* ************************************************************************* */
|
||
/* */
|
||
|
||
/* *** open () *** */
|
||
static void
|
||
try_open_stat_done (GObject *source,
|
||
GAsyncResult *result,
|
||
gpointer user_data)
|
||
{
|
||
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;
|
||
|
||
msg = soup_session_get_async_result_message (session, result);
|
||
if (soup_message_get_status (msg) != SOUP_STATUS_MULTI_STATUS)
|
||
{
|
||
http_job_failed (job, msg);
|
||
return;
|
||
}
|
||
|
||
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);
|
||
|
||
if (res == FALSE)
|
||
{
|
||
g_vfs_job_failed (job,
|
||
G_IO_ERROR, G_IO_ERROR_FAILED,
|
||
_("Response invalid"));
|
||
return;
|
||
}
|
||
|
||
if (target_type == G_FILE_TYPE_DIRECTORY)
|
||
{
|
||
g_vfs_job_failed (job,
|
||
G_IO_ERROR, G_IO_ERROR_IS_DIRECTORY,
|
||
_("File is directory"));
|
||
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)
|
||
{
|
||
SoupMessage *msg;
|
||
GUri *uri;
|
||
|
||
uri = g_vfs_backend_dav_uri_for_path (backend, filename, FALSE);
|
||
msg = stat_location_start (uri, FALSE);
|
||
g_uri_unref (uri);
|
||
|
||
if (msg == NULL)
|
||
{
|
||
g_vfs_job_failed (G_VFS_JOB (job),
|
||
G_IO_ERROR, G_IO_ERROR_FAILED,
|
||
_("Could not create request"));
|
||
|
||
return FALSE;
|
||
}
|
||
|
||
dav_message_connect_signals (msg, backend);
|
||
|
||
g_vfs_job_set_backend_data (G_VFS_JOB (job), backend, NULL);
|
||
soup_session_send_async (G_VFS_BACKEND_HTTP (backend)->session, msg,
|
||
G_PRIORITY_DEFAULT, NULL, try_open_stat_done, job);
|
||
|
||
return TRUE;
|
||
}
|
||
|
||
|
||
|
||
|
||
/* *** create () *** */
|
||
static void
|
||
try_create_tested_existence (GObject *source,
|
||
GAsyncResult *result,
|
||
gpointer user_data)
|
||
{
|
||
GVfsJob *job = G_VFS_JOB (user_data);
|
||
GOutputStream *stream;
|
||
GInputStream *body;
|
||
SoupMessage *msg = job->backend_data;
|
||
SoupMessage *put_msg;
|
||
GError *error = NULL;
|
||
GUri *uri;
|
||
|
||
body = soup_session_send_finish (SOUP_SESSION (source), result, &error);
|
||
if (!body)
|
||
{
|
||
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);
|
||
g_vfs_job_failed (job,
|
||
G_IO_ERROR,
|
||
G_IO_ERROR_EXISTS,
|
||
_("Target file already exists"));
|
||
return;
|
||
}
|
||
/* TODO: other errors */
|
||
|
||
g_object_unref (body);
|
||
|
||
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
|
||
* soup_message_headers_append (soup_message_get_request_headers (put_msg), "If-None-Match", "*");
|
||
*/
|
||
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;
|
||
GUri *uri;
|
||
|
||
/* 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);
|
||
g_uri_unref (uri);
|
||
|
||
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);
|
||
|
||
return TRUE;
|
||
}
|
||
|
||
/* *** replace () *** */
|
||
static void
|
||
open_for_replace_succeeded (GVfsBackendHttp *op_backend,
|
||
GVfsJob *job,
|
||
GUri *uri,
|
||
const char *etag)
|
||
{
|
||
SoupMessage *put_msg;
|
||
GOutputStream *stream;
|
||
|
||
put_msg = soup_message_new_from_uri (SOUP_METHOD_PUT, uri);
|
||
|
||
if (etag)
|
||
soup_message_headers_append (soup_message_get_request_headers (put_msg),
|
||
"If-Match",
|
||
etag);
|
||
|
||
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
|
||
try_replace_checked_etag (GObject *source,
|
||
GAsyncResult *result,
|
||
gpointer user_data)
|
||
{
|
||
GVfsJob *job = G_VFS_JOB (user_data);
|
||
GVfsBackendHttp *op_backend = job->backend_data;
|
||
GInputStream *body;
|
||
SoupSession *session = SOUP_SESSION (source);
|
||
SoupMessage *msg;
|
||
GError *error;
|
||
|
||
msg = soup_session_get_async_result_message (session, result);
|
||
body = soup_session_send_finish (session, result, &error);
|
||
if (!body)
|
||
{
|
||
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);
|
||
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 */
|
||
|
||
g_object_unref (body);
|
||
|
||
open_for_replace_succeeded (op_backend, job, soup_message_get_uri (msg),
|
||
soup_message_headers_get_one (soup_message_get_request_headers (msg),
|
||
"If-Match"));
|
||
}
|
||
|
||
static gboolean
|
||
try_replace (GVfsBackend *backend,
|
||
GVfsJobOpenForWrite *job,
|
||
const char *filename,
|
||
const char *etag,
|
||
gboolean make_backup,
|
||
GFileCreateFlags flags)
|
||
{
|
||
GVfsBackendHttp *op_backend;
|
||
GUri *uri;
|
||
|
||
/* 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);
|
||
g_uri_unref (uri);
|
||
|
||
dav_message_connect_signals (msg, backend);
|
||
|
||
soup_message_headers_append (soup_message_get_request_headers (msg),
|
||
"If-Match", etag);
|
||
|
||
g_vfs_job_set_backend_data (G_VFS_JOB (job), op_backend, NULL);
|
||
soup_session_send_async (op_backend->session, msg, G_PRIORITY_DEFAULT,
|
||
NULL, try_replace_checked_etag, job);
|
||
|
||
return TRUE;
|
||
}
|
||
|
||
open_for_replace_succeeded (op_backend, G_VFS_JOB (job), uri, NULL);
|
||
g_uri_unref (uri);
|
||
|
||
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;
|
||
}
|
||
|
||
/* this does not invoke libsoup API, so it can be synchronous/threaded */
|
||
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);
|
||
}
|
||
}
|
||
|
||
/* this does not invoke libsoup API, so it can be synchronous/threaded */
|
||
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
|
||
try_close_write_sent (GObject *source,
|
||
GAsyncResult *result,
|
||
gpointer user_data)
|
||
{
|
||
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;
|
||
}
|
||
|
||
g_object_unref (body);
|
||
|
||
if (!SOUP_STATUS_IS_SUCCESSFUL (soup_message_get_status (msg)))
|
||
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;
|
||
GBytes *bytes;
|
||
|
||
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);
|
||
|
||
dav_message_connect_signals (msg, backend);
|
||
|
||
g_output_stream_close (stream, NULL, NULL);
|
||
bytes = g_memory_output_stream_steal_as_bytes (G_MEMORY_OUTPUT_STREAM (stream));
|
||
g_object_unref (stream);
|
||
|
||
soup_message_set_request_body_from_bytes (msg, NULL, bytes);
|
||
soup_session_send_async (G_VFS_BACKEND_HTTP (backend)->session, msg,
|
||
G_PRIORITY_DEFAULT, NULL, try_close_write_sent, job);
|
||
g_bytes_unref (bytes);
|
||
|
||
return TRUE;
|
||
}
|
||
|
||
static void
|
||
make_directory_cb (GObject *source, GAsyncResult *result, gpointer user_data)
|
||
{
|
||
GVfsBackend *backend = G_VFS_BACKEND (source);
|
||
GVfsJobMakeDirectory *job = user_data;
|
||
SoupMessage *msg = g_vfs_backend_dav_get_async_result_message (backend, result);
|
||
GInputStream *body;
|
||
GError *error = NULL;
|
||
guint status;
|
||
|
||
body = g_vfs_backend_dav_send_finish (backend, result, &error);
|
||
|
||
if (!body)
|
||
{
|
||
g_vfs_job_failed_from_error (G_VFS_JOB (job), error);
|
||
g_error_free (error);
|
||
g_object_unref (msg);
|
||
return;
|
||
}
|
||
|
||
status = soup_message_get_status (msg);
|
||
|
||
if (! SOUP_STATUS_IS_SUCCESSFUL (status))
|
||
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));
|
||
|
||
g_object_unref (body);
|
||
g_object_unref (msg);
|
||
}
|
||
|
||
static gboolean
|
||
try_make_directory (GVfsBackend *backend,
|
||
GVfsJobMakeDirectory *job,
|
||
const char *filename)
|
||
{
|
||
SoupMessage *msg;
|
||
GUri *uri;
|
||
|
||
uri = g_vfs_backend_dav_uri_for_path (backend, filename, TRUE);
|
||
msg = soup_message_new_from_uri (SOUP_METHOD_MKCOL, uri);
|
||
g_uri_unref (uri);
|
||
|
||
dav_message_connect_signals (msg, backend);
|
||
|
||
g_vfs_backend_dav_send_async (backend, msg, make_directory_cb, job);
|
||
return TRUE;
|
||
}
|
||
|
||
static void
|
||
try_delete_send_cb (GObject *source, GAsyncResult *result, gpointer user_data)
|
||
{
|
||
GVfsBackend *backend = G_VFS_BACKEND (source);
|
||
GVfsJobDelete *job = user_data;
|
||
SoupMessage *msg = g_vfs_backend_dav_get_async_result_message (backend, result);
|
||
GInputStream *body;
|
||
GError *error = NULL;
|
||
guint status;
|
||
|
||
body = g_vfs_backend_dav_send_finish (backend, result, &error);
|
||
|
||
if (!body)
|
||
{
|
||
g_vfs_job_failed_from_error (G_VFS_JOB (job), error);
|
||
g_error_free (error);
|
||
g_object_unref (msg);
|
||
return;
|
||
}
|
||
|
||
status = soup_message_get_status (msg);
|
||
if (!SOUP_STATUS_IS_SUCCESSFUL (status))
|
||
http_job_failed (G_VFS_JOB (job), msg);
|
||
else
|
||
g_vfs_job_succeeded (G_VFS_JOB (job));
|
||
|
||
g_object_unref (msg);
|
||
g_object_unref (body);
|
||
}
|
||
|
||
static void
|
||
try_delete_cb (GObject *source, GAsyncResult *result, gpointer user_data)
|
||
{
|
||
GVfsBackend *backend = G_VFS_BACKEND (source);
|
||
GVfsJobDelete *job = user_data;
|
||
SoupMessage *msg = NULL;
|
||
GFileType file_type;
|
||
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);
|
||
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);
|
||
|
||
dav_message_connect_signals (msg, backend);
|
||
|
||
g_vfs_backend_dav_send_async (backend, msg, try_delete_send_cb, job);
|
||
}
|
||
|
||
static gboolean
|
||
try_delete (GVfsBackend *backend, GVfsJobDelete *job, const char *filename)
|
||
{
|
||
GUri *uri;
|
||
|
||
uri = g_vfs_backend_dav_uri_for_path (backend, filename, FALSE);
|
||
stat_location_async (backend, uri, TRUE, try_delete_cb, job);
|
||
return TRUE;
|
||
}
|
||
|
||
typedef struct _TrySetDisplayNameData {
|
||
GVfsJobSetDisplayName *job;
|
||
char *target_path;
|
||
} TrySetDisplayNameData;
|
||
|
||
static void
|
||
try_set_display_name_cb (GObject *source, GAsyncResult *result,
|
||
gpointer user_data)
|
||
{
|
||
GVfsBackend *backend = G_VFS_BACKEND (source);
|
||
TrySetDisplayNameData *data = user_data;
|
||
SoupMessage *msg = g_vfs_backend_dav_get_async_result_message (backend, result);
|
||
GInputStream *body;
|
||
GError *error = NULL;
|
||
guint status;
|
||
|
||
body = g_vfs_backend_dav_send_finish (backend, result, &error);
|
||
|
||
if (!body)
|
||
{
|
||
http_job_failed (G_VFS_JOB (data->job), msg);
|
||
goto error;
|
||
}
|
||
|
||
status = soup_message_get_status (msg);
|
||
|
||
/*
|
||
* The precondition of SOUP_STATUS_PRECONDITION_FAILED (412) in
|
||
* this case was triggered by the "Overwrite: F" header which
|
||
* means that the target already exists.
|
||
* Also if we get a REDIRECTION it means that there was no
|
||
* "Location" header, since otherwise that would have triggered
|
||
* our redirection handler. This probably means we are dealing
|
||
* with an web dav implementation (like mod_dav) that also sends
|
||
* redirects for the destionaion (i.e. "Destination: /foo" header)
|
||
* which very likely means that the target also exists (and is a
|
||
* directory). That or the webdav server is broken.
|
||
* We could find out by doing another stat and but I think this is
|
||
* such a corner case that we are totally fine with returning
|
||
* G_IO_ERROR_EXISTS.
|
||
* */
|
||
|
||
if (SOUP_STATUS_IS_SUCCESSFUL (status))
|
||
{
|
||
g_debug ("new target_path: %s\n", data->target_path);
|
||
g_vfs_job_set_display_name_set_new_path (data->job, data->target_path);
|
||
g_vfs_job_succeeded (G_VFS_JOB (data->job));
|
||
}
|
||
else if (status == SOUP_STATUS_PRECONDITION_FAILED ||
|
||
SOUP_STATUS_IS_REDIRECTION (status))
|
||
g_vfs_job_failed (G_VFS_JOB (data->job), G_IO_ERROR,
|
||
G_IO_ERROR_EXISTS,
|
||
_("Target file already exists"));
|
||
else
|
||
http_job_failed (G_VFS_JOB (data->job), msg);
|
||
|
||
g_object_unref (body);
|
||
|
||
error:
|
||
g_clear_error (&error);
|
||
g_object_unref (msg);
|
||
g_free (data->target_path);
|
||
g_slice_free (TrySetDisplayNameData, data);
|
||
}
|
||
|
||
static gboolean
|
||
try_set_display_name (GVfsBackend *backend,
|
||
GVfsJobSetDisplayName *job,
|
||
const char *filename,
|
||
const char *display_name)
|
||
{
|
||
SoupMessage *msg;
|
||
GUri *source;
|
||
GUri *target;
|
||
TrySetDisplayNameData *data = g_slice_new (TrySetDisplayNameData);
|
||
char *dirname;
|
||
|
||
source = g_vfs_backend_dav_uri_for_path (backend, filename, FALSE);
|
||
msg = soup_message_new_from_uri (SOUP_METHOD_MOVE, source);
|
||
|
||
dirname = g_path_get_dirname (filename);
|
||
data->target_path = g_build_filename (dirname, display_name, NULL);
|
||
target = g_vfs_backend_dav_uri_for_path (backend, data->target_path, FALSE);
|
||
|
||
message_add_destination_header (msg, target);
|
||
message_add_overwrite_header (msg, FALSE);
|
||
|
||
data->job = job;
|
||
g_free (dirname);
|
||
g_uri_unref (target);
|
||
g_uri_unref (source);
|
||
|
||
dav_message_connect_signals (msg, backend);
|
||
|
||
g_vfs_backend_dav_send_async (backend, msg, try_set_display_name_cb, data);
|
||
|
||
return TRUE;
|
||
}
|
||
|
||
typedef struct _CopyData {
|
||
GVfsJob *job;
|
||
SoupMessage *msg;
|
||
GUri *source_uri;
|
||
GUri *target_uri;
|
||
GFileProgressCallback progress_callback;
|
||
gpointer progress_callback_data;
|
||
gint64 file_size;
|
||
GFileType source_ft;
|
||
GFileType target_ft;
|
||
GFileCopyFlags flags;
|
||
gboolean source_res;
|
||
gboolean target_res;
|
||
} CopyData;
|
||
|
||
static void
|
||
copy_data_free (gpointer data)
|
||
{
|
||
CopyData *p = data;
|
||
g_clear_pointer (&p->source_uri, g_uri_unref);
|
||
g_clear_pointer (&p->target_uri, g_uri_unref);
|
||
g_clear_object (&p->msg);
|
||
g_slice_free (CopyData, p);
|
||
}
|
||
|
||
static void
|
||
try_move_do_cb (GObject *source, GAsyncResult *result, gpointer user_data)
|
||
{
|
||
GVfsBackend *backend = G_VFS_BACKEND (source);
|
||
CopyData *data = user_data;
|
||
GInputStream *body;
|
||
GError *error = NULL;
|
||
guint status;
|
||
|
||
body = g_vfs_backend_dav_send_finish (backend, result, &error);
|
||
|
||
if (!body)
|
||
{
|
||
g_vfs_job_failed_from_error (data->job, error);
|
||
copy_data_free (data);
|
||
g_clear_error (&error);
|
||
return;
|
||
}
|
||
|
||
/* 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);
|
||
|
||
if (!body)
|
||
{
|
||
g_vfs_job_failed_from_error (data->job, error);
|
||
copy_data_free (data);
|
||
g_object_unref (msg);
|
||
g_clear_error (&error);
|
||
return;
|
||
}
|
||
|
||
status = soup_message_get_status (msg);
|
||
if (!SOUP_STATUS_IS_SUCCESSFUL (status))
|
||
{
|
||
http_job_failed (data->job, msg);
|
||
g_object_unref (msg);
|
||
copy_data_free (data);
|
||
return;
|
||
}
|
||
|
||
g_object_unref (body);
|
||
g_object_unref (msg);
|
||
|
||
try_move_do (backend, data);
|
||
}
|
||
|
||
static void
|
||
try_move_source_stat_cb (GObject *source, GAsyncResult *result,
|
||
gpointer user_data)
|
||
{
|
||
GVfsBackend *backend = G_VFS_BACKEND (source);
|
||
CopyData *data = user_data;
|
||
GError *error = NULL;
|
||
|
||
data->source_res = stat_location_finish (backend, &data->source_ft,
|
||
&data->file_size, NULL,
|
||
result, &error);
|
||
|
||
if (data->target_res)
|
||
{
|
||
if (data->flags & G_FILE_COPY_OVERWRITE)
|
||
{
|
||
if (data->source_res)
|
||
{
|
||
if (data->target_ft == G_FILE_TYPE_DIRECTORY)
|
||
{
|
||
if (data->source_ft == G_FILE_TYPE_DIRECTORY)
|
||
g_vfs_job_failed_literal (G_VFS_JOB(data->job),
|
||
G_IO_ERROR,
|
||
G_IO_ERROR_WOULD_MERGE,
|
||
_("Can’t move directory over directory"));
|
||
else
|
||
g_vfs_job_failed_literal (G_VFS_JOB(data->job),
|
||
G_IO_ERROR,
|
||
G_IO_ERROR_IS_DIRECTORY,
|
||
_("Can’t move over directory"));
|
||
copy_data_free (data);
|
||
return;
|
||
}
|
||
else if (data->source_ft == G_FILE_TYPE_DIRECTORY)
|
||
{
|
||
/* Overwriting a file with a directory, first remove the
|
||
* file */
|
||
SoupMessage *msg;
|
||
|
||
msg = soup_message_new_from_uri (SOUP_METHOD_DELETE,
|
||
data->target_uri);
|
||
|
||
dav_message_connect_signals (msg, backend);
|
||
|
||
g_vfs_backend_dav_send_async (backend, msg,
|
||
try_move_target_delete_cb,
|
||
data);
|
||
return;
|
||
}
|
||
}
|
||
else
|
||
{
|
||
g_vfs_job_failed_from_error (data->job, error);
|
||
copy_data_free (data);
|
||
g_clear_error (&error);
|
||
return;
|
||
}
|
||
}
|
||
else
|
||
{
|
||
g_vfs_job_failed_literal (G_VFS_JOB(data->job),
|
||
G_IO_ERROR,
|
||
G_IO_ERROR_EXISTS,
|
||
_("Target file exists"));
|
||
copy_data_free (data);
|
||
g_clear_error (&error);
|
||
return;
|
||
}
|
||
}
|
||
|
||
try_move_do (backend, data);
|
||
}
|
||
|
||
static void
|
||
try_move_target_stat_cb (GObject *source, GAsyncResult *result,
|
||
gpointer user_data)
|
||
{
|
||
GVfsBackend *backend = G_VFS_BACKEND (source);
|
||
CopyData *data = user_data;
|
||
gboolean res;
|
||
GError *error = NULL;
|
||
|
||
res = stat_location_finish (backend, &data->target_ft, NULL, NULL,
|
||
result, &error);
|
||
|
||
if (!res && !g_error_matches (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND))
|
||
{
|
||
g_vfs_job_failed_from_error (data->job, error);
|
||
g_clear_error (&error);
|
||
copy_data_free (data);
|
||
return;
|
||
}
|
||
|
||
g_clear_error (&error);
|
||
|
||
data->target_res = res;
|
||
|
||
stat_location_async (backend, data->source_uri, FALSE,
|
||
try_move_source_stat_cb, data);
|
||
}
|
||
|
||
static gboolean
|
||
try_move (GVfsBackend *backend,
|
||
GVfsJobMove *job,
|
||
const char *source,
|
||
const char *destination,
|
||
GFileCopyFlags flags,
|
||
GFileProgressCallback progress_callback,
|
||
gpointer progress_callback_data)
|
||
{
|
||
CopyData *data = g_slice_new0 (CopyData);
|
||
data->job = G_VFS_JOB (job);
|
||
data->flags = flags;
|
||
data->progress_callback = progress_callback;
|
||
data->progress_callback_data = progress_callback_data;
|
||
|
||
if (flags & G_FILE_COPY_BACKUP)
|
||
{
|
||
if (flags & G_FILE_COPY_NO_FALLBACK_FOR_MOVE)
|
||
{
|
||
g_vfs_job_failed_literal (G_VFS_JOB (job),
|
||
G_IO_ERROR,
|
||
G_IO_ERROR_CANT_CREATE_BACKUP,
|
||
_("Backups not supported"));
|
||
}
|
||
else
|
||
{
|
||
/* Return G_IO_ERROR_NOT_SUPPORTED instead of G_IO_ERROR_CANT_CREATE_BACKUP
|
||
* to be proceeded with copy and delete fallback (see g_file_move). */
|
||
g_vfs_job_failed_literal (G_VFS_JOB (job),
|
||
G_IO_ERROR,
|
||
G_IO_ERROR_NOT_SUPPORTED,
|
||
"Operation not supported");
|
||
}
|
||
|
||
copy_data_free (data);
|
||
return TRUE;
|
||
}
|
||
|
||
data->source_uri = g_vfs_backend_dav_uri_for_path (backend, source, FALSE);
|
||
data->msg = soup_message_new_from_uri (SOUP_METHOD_MOVE, data->source_uri);
|
||
data->target_uri = g_vfs_backend_dav_uri_for_path (backend, destination, FALSE);
|
||
|
||
dav_message_connect_signals (data->msg, backend);
|
||
|
||
stat_location_async (backend, data->target_uri, FALSE,
|
||
try_move_target_stat_cb, data);
|
||
return TRUE;
|
||
}
|
||
|
||
static void
|
||
try_copy_do_cb (GObject *source, GAsyncResult *result, gpointer user_data)
|
||
{
|
||
GVfsBackend *backend = G_VFS_BACKEND (source);
|
||
CopyData *data = user_data;
|
||
GInputStream *body;
|
||
GError *error = NULL;
|
||
guint status;
|
||
|
||
body = g_vfs_backend_dav_send_finish (backend, result, &error);
|
||
|
||
if (!body)
|
||
{
|
||
g_vfs_job_failed_from_error (data->job, error);
|
||
copy_data_free (data);
|
||
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->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);
|
||
|
||
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)
|
||
{
|
||
if (data->flags & G_FILE_COPY_OVERWRITE)
|
||
{
|
||
if (data->target_ft == G_FILE_TYPE_DIRECTORY)
|
||
{
|
||
if (data->source_ft == G_FILE_TYPE_DIRECTORY)
|
||
g_vfs_job_failed_literal (G_VFS_JOB(data->job),
|
||
G_IO_ERROR,
|
||
G_IO_ERROR_WOULD_MERGE,
|
||
_("Can’t copy directory over directory"));
|
||
else
|
||
g_vfs_job_failed_literal (G_VFS_JOB(data->job),
|
||
G_IO_ERROR,
|
||
G_IO_ERROR_IS_DIRECTORY,
|
||
_("File is directory"));
|
||
copy_data_free (data);
|
||
return;
|
||
}
|
||
}
|
||
else
|
||
{
|
||
g_vfs_job_failed_literal (data->job,
|
||
G_IO_ERROR,
|
||
G_IO_ERROR_EXISTS,
|
||
_("Target file already exists"));
|
||
copy_data_free (data);
|
||
return;
|
||
}
|
||
}
|
||
else if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND))
|
||
{
|
||
g_vfs_job_failed_from_error (data->job, error);
|
||
g_clear_error (&error);
|
||
copy_data_free (data);
|
||
return;
|
||
}
|
||
|
||
g_clear_error (&error);
|
||
|
||
if (data->source_ft == G_FILE_TYPE_DIRECTORY)
|
||
{
|
||
g_vfs_job_failed_literal (data->job,
|
||
G_IO_ERROR,
|
||
G_IO_ERROR_WOULD_RECURSE,
|
||
_("Can’t recursively copy directory"));
|
||
copy_data_free (data);
|
||
return;
|
||
}
|
||
|
||
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);
|
||
|
||
dav_message_connect_signals (data->msg, backend);
|
||
|
||
g_vfs_backend_dav_send_async (backend, data->msg, try_copy_do_cb, data);
|
||
}
|
||
|
||
static void
|
||
try_copy_source_stat_cb (GObject *source, GAsyncResult *result,
|
||
gpointer user_data)
|
||
{
|
||
GVfsBackend *backend = G_VFS_BACKEND (source);
|
||
CopyData *data = user_data;
|
||
gboolean res;
|
||
GError *error = NULL;
|
||
|
||
res = stat_location_finish (backend, &data->source_ft, &data->file_size,
|
||
NULL, result, &error);
|
||
|
||
if (!res)
|
||
{
|
||
g_vfs_job_failed_from_error (data->job, error);
|
||
g_clear_error (&error);
|
||
copy_data_free (data);
|
||
return;
|
||
}
|
||
|
||
g_clear_error (&error);
|
||
|
||
data->source_res = res;
|
||
|
||
stat_location_async (backend, data->target_uri, FALSE,
|
||
try_copy_target_stat_cb, data);
|
||
}
|
||
|
||
static gboolean
|
||
try_copy (GVfsBackend *backend,
|
||
GVfsJobCopy *job,
|
||
const char *source,
|
||
const char *destination,
|
||
GFileCopyFlags flags,
|
||
GFileProgressCallback progress_callback,
|
||
gpointer progress_callback_data)
|
||
{
|
||
CopyData *data = g_slice_new0 (CopyData);
|
||
data->job = G_VFS_JOB (job);
|
||
data->flags = flags;
|
||
data->progress_callback = progress_callback;
|
||
data->progress_callback_data = progress_callback_data;
|
||
|
||
if (flags & G_FILE_COPY_BACKUP)
|
||
{
|
||
/* 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;
|
||
}
|
||
|
||
#define CHUNK_SIZE 65536
|
||
|
||
typedef struct {
|
||
/* Job details */
|
||
GVfsBackend *backend;
|
||
GVfsJob *job;
|
||
GVfsJobPush *op_job;
|
||
|
||
/* Local file */
|
||
GInputStream *in;
|
||
goffset size;
|
||
|
||
/* Remote file */
|
||
GUri *uri;
|
||
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);
|
||
g_object_unref (handle->msg);
|
||
g_uri_unref (handle->uri);
|
||
|
||
g_slice_free (PushHandle, handle);
|
||
}
|
||
|
||
static void
|
||
push_setup_message (PushHandle *handle)
|
||
{
|
||
message_add_overwrite_header (handle->msg,
|
||
handle->op_job->flags & G_FILE_COPY_OVERWRITE);
|
||
soup_message_headers_set_encoding (soup_message_get_request_headers (handle->msg),
|
||
SOUP_ENCODING_CONTENT_LENGTH);
|
||
soup_message_headers_set_content_length (soup_message_get_request_headers (handle->msg),
|
||
handle->size);
|
||
}
|
||
|
||
static void
|
||
push_restarted (SoupMessage *msg, gpointer user_data)
|
||
{
|
||
PushHandle *handle = user_data;
|
||
|
||
handle->n_written = 0;
|
||
|
||
g_object_set (msg, "method", SOUP_METHOD_PUT, NULL);
|
||
push_setup_message (handle);
|
||
|
||
soup_message_set_request_body (handle->msg, NULL, handle->in, handle->size);
|
||
}
|
||
|
||
static void
|
||
push_wrote_body_data (SoupMessage *msg, guint chunk_size, gpointer user_data)
|
||
{
|
||
PushHandle *handle = user_data;
|
||
|
||
handle->n_written += chunk_size;
|
||
g_vfs_job_progress_callback (handle->n_written, handle->size, handle->job);
|
||
}
|
||
|
||
static void
|
||
push_finished (SoupMessage *msg, gpointer user_data)
|
||
{
|
||
PushHandle *handle = user_data;
|
||
|
||
if (g_vfs_job_is_finished (handle->job))
|
||
; /* We got an error so we finished the job and cancelled msg. */
|
||
else if (!SOUP_STATUS_IS_SUCCESSFUL (soup_message_get_status (msg)))
|
||
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
|
||
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)
|
||
{
|
||
GInputStream *body;
|
||
PushHandle *handle = user_data;
|
||
GFileType type;
|
||
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;
|
||
}
|
||
|
||
if (stat_location_end (handle->msg, body, &type, NULL, NULL))
|
||
{
|
||
if (!(handle->op_job->flags & G_FILE_COPY_OVERWRITE))
|
||
{
|
||
g_object_unref (body);
|
||
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)
|
||
{
|
||
g_object_unref (body);
|
||
g_vfs_job_failed (handle->job,
|
||
G_IO_ERROR,
|
||
G_IO_ERROR_IS_DIRECTORY,
|
||
_("File is directory"));
|
||
push_handle_free (handle);
|
||
return;
|
||
}
|
||
}
|
||
|
||
g_object_unref (body);
|
||
g_object_unref (handle->msg);
|
||
|
||
handle->msg = soup_message_new_from_uri (SOUP_METHOD_PUT, handle->uri);
|
||
push_setup_message (handle);
|
||
|
||
soup_message_set_request_body (handle->msg, NULL, handle->in, handle->size);
|
||
|
||
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);
|
||
g_signal_connect (handle->msg, "finished",
|
||
G_CALLBACK (push_finished), handle);
|
||
|
||
dav_message_connect_signals (handle->msg, handle->backend);
|
||
|
||
soup_session_send_async (G_VFS_BACKEND_HTTP (handle->backend)->session,
|
||
handle->msg, G_PRIORITY_DEFAULT,
|
||
NULL, push_done, handle->job);
|
||
}
|
||
|
||
static void
|
||
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);
|
||
|
||
handle->msg = stat_location_start (handle->uri, FALSE);
|
||
|
||
dav_message_connect_signals (handle->msg, handle->backend);
|
||
|
||
soup_session_send_async (G_VFS_BACKEND_HTTP (handle->backend)->session,
|
||
handle->msg, G_PRIORITY_DEFAULT, NULL,
|
||
push_stat_dest_cb, handle);
|
||
}
|
||
else
|
||
{
|
||
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;
|
||
|
||
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;
|
||
}
|
||
|
||
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);
|
||
|
||
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);
|
||
}
|