1 /* GIO - GLib Input, Output and Streaming Library
2 *
3 * Copyright (C) 2008 Red Hat, Inc.
4 *
5 * This library is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU Lesser General Public
7 * License as published by the Free Software Foundation; either
8 * version 2 of the License, or (at your option) any later version.
9 *
10 * This library is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * Lesser General Public License for more details.
14 *
15 * You should have received a copy of the GNU Lesser General
16 * Public License along with this library; if not, write to the
17 * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
18 * Boston, MA 02110-1301, USA.
19 *
20 * Author: Christian Kellner <gicmo@gnome.org>
21 */
22
23 #include <config.h>
24
25 #include <sys/types.h>
26 #include <sys/stat.h>
27 #include <errno.h>
28 #include <unistd.h>
29 #include <fcntl.h>
30 #include <string.h>
31
32 #include <glib/gstdio.h>
33 #include <glib/gi18n.h>
34 #include <gio/gio.h>
35
36 #include <libsoup/soup.h>
37
38 /* LibXML2 includes */
39 #include <libxml/parser.h>
40 #include <libxml/tree.h>
41 #include <libxml/xpath.h>
42 #include <libxml/xpathInternals.h>
43
44 #include "gvfsbackenddav.h"
45 #include "gvfskeyring.h"
46
47 #include "gvfsjobmount.h"
48 #include "gvfsjobopenforread.h"
49 #include "gvfsjobread.h"
50 #include "gvfsjobseekread.h"
51 #include "gvfsjobopenforwrite.h"
52 #include "gvfsjobwrite.h"
53 #include "gvfsjobseekwrite.h"
54 #include "gvfsjobsetdisplayname.h"
55 #include "gvfsjobqueryinfo.h"
56 #include "gvfsjobqueryfsinfo.h"
57 #include "gvfsjobqueryattributes.h"
58 #include "gvfsjobenumerate.h"
59 #include "gvfsjobpush.h"
60 #include "gvfsdaemonprotocol.h"
61 #include "gvfsdaemonutils.h"
62 #include "gvfsutils.h"
63
64 #ifdef HAVE_AVAHI
65 #include "gvfsdnssdutils.h"
66 #include "gvfsdnssdresolver.h"
67 #endif
68
69 /* Overwrite maximal number of connections that libsoup can open in one time in
70 order to prevent backend lockups when too many files is opened concurrently. */
71 #define MAX_CONNS 32
72
73 typedef struct _MountAuthData MountAuthData;
74
75 static void mount_auth_info_free (MountAuthData *info);
76
77
78 #ifdef HAVE_AVAHI
79 static void dns_sd_resolver_changed (GVfsDnsSdResolver *resolver, GVfsBackendDav *dav_backend);
80 #endif
81
82 typedef struct _AuthInfo {
83
84 /* for server authentication */
85 char *username;
86 char *password;
87 char *realm;
88
89 GPasswordSave pw_save;
90
91 } AuthInfo;
92
93 struct _MountAuthData {
94
95 SoupSession *session;
96 GMountSource *mount_source;
97 gboolean retrying_after_403;
98
99 AuthInfo server_auth;
100 AuthInfo proxy_auth;
101
102 };
103
104 struct _GVfsBackendDav
105 {
106 GVfsBackendHttp parent_instance;
107
108 MountAuthData auth_info;
109
110 /* Used for user-verified secure connections. */
111 GTlsCertificate *certificate;
112 GTlsCertificateFlags certificate_errors;
113
114 #ifdef HAVE_AVAHI
115 /* only set if we're handling a [dav|davs]+sd:// mounts */
116 GVfsDnsSdResolver *resolver;
117 #endif
118 };
119
120 G_DEFINE_TYPE (GVfsBackendDav, g_vfs_backend_dav, G_VFS_TYPE_BACKEND_HTTP);
121
122 static void
g_vfs_backend_dav_finalize(GObject * object)123 g_vfs_backend_dav_finalize (GObject *object)
124 {
125 GVfsBackendDav *dav_backend;
126
127 dav_backend = G_VFS_BACKEND_DAV (object);
128
129 #ifdef HAVE_AVAHI
130 if (dav_backend->resolver != NULL)
131 {
132 g_signal_handlers_disconnect_by_func (dav_backend->resolver, dns_sd_resolver_changed, dav_backend);
133 g_object_unref (dav_backend->resolver);
134 }
135 #endif
136
137 mount_auth_info_free (&(dav_backend->auth_info));
138
139 g_clear_object (&dav_backend->certificate);
140
141 if (G_OBJECT_CLASS (g_vfs_backend_dav_parent_class)->finalize)
142 (*G_OBJECT_CLASS (g_vfs_backend_dav_parent_class)->finalize) (object);
143 }
144
145 static void
g_vfs_backend_dav_init(GVfsBackendDav * backend)146 g_vfs_backend_dav_init (GVfsBackendDav *backend)
147 {
148 g_vfs_backend_set_user_visible (G_VFS_BACKEND (backend), TRUE);
149 }
150
151 /* ************************************************************************* */
152 /* Small utility functions */
153
154 static gboolean
string_to_uint64(const char * str,guint64 * value)155 string_to_uint64 (const char *str, guint64 *value)
156 {
157 char *endptr;
158
159 *value = g_ascii_strtoull (str, &endptr, 10);
160
161 return endptr != str;
162 }
163
164 static inline gboolean
sm_has_header(SoupMessage * msg,const char * header)165 sm_has_header (SoupMessage *msg, const char *header)
166 {
167 return soup_message_headers_get_one (msg->response_headers, header) != NULL;
168 }
169
170 static char *
path_get_parent_dir(const char * path)171 path_get_parent_dir (const char *path)
172 {
173 char *parent;
174 size_t len;
175
176 len = strlen (path);
177
178 while (len > 0 && path[len - 1] == '/')
179 len--;
180
181 if (len == 0)
182 return g_strdup ("/");
183
184 parent = g_strrstr_len (path, len, "/");
185
186 if (parent == NULL)
187 return g_strdup ("/");
188
189 return g_strndup (path, (parent - path) + 1);
190 }
191
192 /* message utility functions */
193
194 static void
message_add_destination_header(SoupMessage * msg,SoupURI * uri)195 message_add_destination_header (SoupMessage *msg,
196 SoupURI *uri)
197 {
198 char *string;
199
200 string = soup_uri_to_string (uri, FALSE);
201 soup_message_headers_append (msg->request_headers,
202 "Destination",
203 string);
204 g_free (string);
205 }
206 static void
message_add_overwrite_header(SoupMessage * msg,gboolean overwrite)207 message_add_overwrite_header (SoupMessage *msg,
208 gboolean overwrite)
209 {
210 soup_message_headers_append (msg->request_headers,
211 "Overwrite",
212 overwrite ? "T" : "F");
213 }
214
215 static void
message_add_redirect_header(SoupMessage * msg,GFileQueryInfoFlags flags)216 message_add_redirect_header (SoupMessage *msg,
217 GFileQueryInfoFlags flags)
218 {
219 const char *header_redirect;
220
221 /* RFC 4437 */
222 if (flags & G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS)
223 header_redirect = "F";
224 else
225 header_redirect = "T";
226
227 soup_message_headers_append (msg->request_headers,
228 "Apply-To-Redirect-Ref",
229 header_redirect);
230 }
231
232 static inline gboolean
str_equal(const char * a,const char * b,gboolean insensitive)233 str_equal (const char *a, const char *b, gboolean insensitive)
234 {
235 if (a == NULL || b == NULL)
236 return a == b;
237
238 return insensitive ? !g_ascii_strcasecmp (a, b) : !strcmp (a, b);
239 }
240
241 static gboolean
path_equal(const char * a,const char * b,gboolean relax)242 path_equal (const char *a, const char *b, gboolean relax)
243 {
244 gboolean res;
245 size_t a_len, b_len;
246
247 if (relax == FALSE)
248 return str_equal (a, b, FALSE);
249
250 if (a == NULL || b == NULL)
251 return a == b;
252
253 a_len = strlen (a);
254 b_len = strlen (b);
255
256 while (a_len > 0 && a[a_len - 1] == '/')
257 a_len--;
258
259 while (b_len > 0 && b[b_len - 1] == '/')
260 b_len--;
261
262 if (a_len == b_len)
263 res = ! strncmp (a, b, a_len);
264 else
265 res = FALSE;
266
267 return res;
268 }
269
270 /* Like soup_uri_equal */
271 static gboolean
dav_uri_match(SoupURI * a,SoupURI * b,gboolean relax)272 dav_uri_match (SoupURI *a, SoupURI *b, gboolean relax)
273 {
274 gboolean diff;
275 char *ua, *ub;
276
277 ua = g_uri_unescape_string (a->path, "/");
278 ub = g_uri_unescape_string (b->path, "/");
279
280 diff = a->scheme != b->scheme || a->port != b->port ||
281 ! str_equal (a->host, b->host, TRUE) ||
282 ! path_equal (ua, ub, relax) ||
283 ! str_equal (a->query, b->query, FALSE) ||
284 ! str_equal (a->fragment, b->fragment, FALSE);
285
286 g_free (ua);
287 g_free (ub);
288
289 return !diff;
290 }
291
292 static char *
dav_uri_encode(const char * path_to_encode)293 dav_uri_encode (const char *path_to_encode)
294 {
295 char *path;
296 static const char *allowed_reserved_chars = "/";
297
298 path = g_uri_escape_string (path_to_encode,
299 allowed_reserved_chars,
300 FALSE);
301
302 return path;
303 }
304
305 static gboolean
message_should_apply_redir_ref(SoupMessage * msg)306 message_should_apply_redir_ref (SoupMessage *msg)
307 {
308 const char *header;
309
310 header = soup_message_headers_get_one (msg->request_headers,
311 "Apply-To-Redirect-Ref");
312
313 if (header == NULL || g_ascii_strcasecmp (header, "T"))
314 return FALSE;
315
316 return TRUE;
317 }
318
319
320 static SoupURI *
g_vfs_backend_dav_uri_for_path(GVfsBackend * backend,const char * path,gboolean is_dir)321 g_vfs_backend_dav_uri_for_path (GVfsBackend *backend,
322 const char *path,
323 gboolean is_dir)
324 {
325 SoupURI *mount_base;
326 SoupURI *uri;
327 char *fn_encoded;
328 char *new_path;
329
330 mount_base = http_backend_get_mount_base (backend);
331 uri = soup_uri_copy (mount_base);
332
333 /* "/" means "whatever mount_base is" */
334 if (!strcmp (path, "/"))
335 return uri;
336
337 /* The mount_base path is escaped already so we need to
338 escape the new path as well */
339 fn_encoded = dav_uri_encode (path);
340
341 /* Otherwise, we append filename to mount_base (which is assumed to
342 * be a directory in this case).
343 *
344 * Add a "/" in cases where it is likely that the url is going
345 * to be a directory to avoid redirections
346 */
347 if (is_dir == FALSE || g_str_has_suffix (path, "/"))
348 new_path = g_build_path ("/", uri->path, fn_encoded, NULL);
349 else
350 new_path = g_build_path ("/", uri->path, fn_encoded, "/", NULL);
351
352 g_free (fn_encoded);
353 g_free (uri->path);
354 uri->path = new_path;
355
356 return uri;
357 }
358
359 /* redirection */
360 static void
redirect_handler(SoupMessage * msg,gpointer user_data)361 redirect_handler (SoupMessage *msg, gpointer user_data)
362 {
363 SoupSession *session = user_data;
364 const char *new_loc;
365 SoupURI *new_uri;
366 SoupURI *old_uri;
367 guint status;
368 gboolean redirect;
369
370 status = msg->status_code;
371 new_loc = soup_message_headers_get_one (msg->response_headers, "Location");
372
373 /* If we don't have a location to redirect to, just fail */
374 if (new_loc == NULL)
375 return;
376
377 if (!SOUP_STATUS_IS_REDIRECTION(status))
378 return;
379
380 new_uri = soup_uri_new_with_base (soup_message_get_uri (msg), new_loc);
381 if (new_uri == NULL)
382 {
383 soup_message_set_status_full (msg,
384 SOUP_STATUS_MALFORMED,
385 "Invalid Redirect URL");
386 return;
387 }
388
389 old_uri = soup_message_get_uri (msg);
390
391 /* copy over username and password to new_uri */
392 soup_uri_set_user(new_uri, old_uri->user);
393 soup_uri_set_password(new_uri, old_uri->password);
394
395 /* Check if this is a trailing slash redirect (i.e. /a/b to /a/b/),
396 * redirect it right away
397 */
398 redirect = dav_uri_match (new_uri, old_uri, TRUE);
399
400 if (redirect == TRUE)
401 {
402 const char *dest;
403
404 dest = soup_message_headers_get_one (msg->request_headers,
405 "Destination");
406
407 if (dest && g_str_has_suffix (dest, "/") == FALSE)
408 {
409 char *new_dest = g_strconcat (dest, "/", NULL);
410 soup_message_headers_replace (msg->request_headers,
411 "Destination",
412 new_dest);
413 g_free (new_dest);
414 }
415 }
416 else if (message_should_apply_redir_ref (msg))
417 {
418
419
420 if (status == SOUP_STATUS_MOVED_PERMANENTLY ||
421 status == SOUP_STATUS_TEMPORARY_REDIRECT)
422 {
423
424 /* Only corss-site redirect safe methods */
425 if (msg->method == SOUP_METHOD_GET &&
426 msg->method == SOUP_METHOD_HEAD &&
427 msg->method == SOUP_METHOD_OPTIONS &&
428 msg->method == SOUP_METHOD_PROPFIND)
429 redirect = TRUE;
430 }
431
432 #if 0
433 else if (msg->status_code == SOUP_STATUS_SEE_OTHER ||
434 msg->status_code == SOUP_STATUS_FOUND)
435 {
436 /* Redirect using a GET */
437 g_object_set (msg,
438 SOUP_MESSAGE_METHOD, SOUP_METHOD_GET,
439 NULL);
440 soup_message_set_request (msg, NULL,
441 SOUP_MEMORY_STATIC, NULL, 0);
442 soup_message_headers_set_encoding (msg->request_headers,
443 SOUP_ENCODING_NONE);
444 }
445 #endif
446 /* ELSE:
447 *
448 * Two possibilities:
449 *
450 * 1) It's a non-redirecty 3xx response (300, 304,
451 * 305, 306)
452 * 2) It's some newly-defined 3xx response (308+)
453 *
454 * We ignore both of these cases. In the first,
455 * redirecting would be explicitly wrong, and in the
456 * last case, we have no clue if the 3xx response is
457 * supposed to be redirecty or non-redirecty. Plus,
458 * 2616 says unrecognized status codes should be
459 * treated as the equivalent to the x00 code, and we
460 * don't redirect on 300, so therefore we shouldn't
461 * redirect on 308+ either.
462 */
463 }
464
465 if (redirect)
466 {
467 soup_message_set_uri (msg, new_uri);
468 soup_session_requeue_message (session, msg);
469 }
470
471 soup_uri_free (new_uri);
472 }
473
474 static void
g_vfs_backend_dav_setup_display_name(GVfsBackend * backend)475 g_vfs_backend_dav_setup_display_name (GVfsBackend *backend)
476 {
477 GVfsBackendDav *dav_backend;
478 SoupURI *mount_base;
479 char *display_name;
480 char port[7] = {0, };
481
482 dav_backend = G_VFS_BACKEND_DAV (backend);
483
484 #ifdef HAVE_AVAHI
485 if (dav_backend->resolver != NULL)
486 {
487 const char *name;
488 name = g_vfs_dns_sd_resolver_get_service_name (dav_backend->resolver);
489 g_vfs_backend_set_display_name (backend, name);
490 return;
491 }
492 #endif
493
494 mount_base = http_backend_get_mount_base (backend);
495
496 if (! soup_uri_uses_default_port (mount_base))
497 g_snprintf (port, sizeof (port), ":%u", mount_base->port);
498
499 if (mount_base->user != NULL)
500 /* Translators: This is the name of the WebDAV share constructed as
501 "WebDAV as <username> on <hostname>:<port>"; the ":<port>" part is
502 the second %s and only shown if it is not the default http(s) port. */
503 display_name = g_strdup_printf (_("%s on %s%s"),
504 mount_base->user,
505 mount_base->host,
506 port);
507 else
508 display_name = g_strdup_printf ("%s%s",
509 mount_base->host,
510 port);
511
512 g_vfs_backend_set_display_name (backend, display_name);
513 g_free (display_name);
514 }
515
516 static void
certificate_error_handler(SoupMessage * msg,GParamSpec * pspec,gpointer user_data)517 certificate_error_handler (SoupMessage *msg,
518 GParamSpec *pspec,
519 gpointer user_data)
520 {
521 GVfsBackendDav *dav = G_VFS_BACKEND_DAV (user_data);
522 GTlsCertificate *certificate;
523 GTlsCertificateFlags errors;
524
525 /* Fail the message if the certificate errors change or the certificate is
526 * different. */
527 if (soup_message_get_https_status (msg, &certificate, &errors))
528 {
529 if (errors != dav->certificate_errors ||
530 !g_tls_certificate_is_same (certificate, dav->certificate))
531 {
532 soup_session_cancel_message (G_VFS_BACKEND_HTTP (dav)->session,
533 msg,
534 SOUP_STATUS_SSL_FAILED);
535 }
536 }
537 }
538
539 static guint
g_vfs_backend_dav_send_message(GVfsBackend * backend,SoupMessage * message)540 g_vfs_backend_dav_send_message (GVfsBackend *backend, SoupMessage *message)
541 {
542 GVfsBackendHttp *http_backend;
543 SoupSession *session;
544
545 http_backend = G_VFS_BACKEND_HTTP (backend);
546 session = http_backend->session;
547
548 /* We have our own custom redirect handler */
549 soup_message_set_flags (message, SOUP_MESSAGE_NO_REDIRECT);
550
551 if (G_VFS_BACKEND_DAV (backend)->certificate_errors)
552 g_signal_connect (message, "notify::tls-errors",
553 G_CALLBACK (certificate_error_handler), backend);
554
555 soup_message_add_header_handler (message, "got_body", "Location",
556 G_CALLBACK (redirect_handler), session);
557
558 return http_backend_send_message (backend, message);
559 }
560
561 static void
g_vfs_backend_dav_queue_message(GVfsBackend * backend,SoupMessage * msg,SoupSessionCallback callback,gpointer user_data)562 g_vfs_backend_dav_queue_message (GVfsBackend *backend,
563 SoupMessage *msg,
564 SoupSessionCallback callback,
565 gpointer user_data)
566 {
567 if (G_VFS_BACKEND_DAV (backend)->certificate_errors)
568 g_signal_connect (msg, "notify::tls-errors",
569 G_CALLBACK (certificate_error_handler), backend);
570
571 soup_session_queue_message (G_VFS_BACKEND_HTTP (backend)->session, msg,
572 callback, user_data);
573 }
574
575 /* ************************************************************************* */
576 /* generic xml parsing functions */
577
578 static inline gboolean
node_has_name(xmlNodePtr node,const char * name)579 node_has_name (xmlNodePtr node, const char *name)
580 {
581 g_return_val_if_fail (node != NULL, FALSE);
582
583 return ! strcmp ((char *) node->name, name);
584 }
585
586 static inline gboolean
node_has_name_ns(xmlNodePtr node,const char * name,const char * ns_href)587 node_has_name_ns (xmlNodePtr node, const char *name, const char *ns_href)
588 {
589 gboolean has_name;
590 gboolean has_ns;
591
592 g_return_val_if_fail (node != NULL, FALSE);
593
594 has_name = has_ns = TRUE;
595
596 if (name)
597 has_name = node->name && ! strcmp ((char *) node->name, name);
598
599 if (ns_href)
600 has_ns = node->ns && node->ns->href &&
601 ! g_ascii_strcasecmp ((char *) node->ns->href, ns_href);
602
603 return has_name && has_ns;
604 }
605
606 static inline gboolean
node_is_element(xmlNodePtr node)607 node_is_element (xmlNodePtr node)
608 {
609 return node->type == XML_ELEMENT_NODE && node->name != NULL;
610 }
611
612
613 static inline gboolean
node_is_element_with_name(xmlNodePtr node,const char * name)614 node_is_element_with_name (xmlNodePtr node, const char *name)
615 {
616 return node->type == XML_ELEMENT_NODE &&
617 node->name != NULL &&
618 ! strcmp ((char *) node->name, name);
619 }
620
621 static const char *
node_get_content(xmlNodePtr node)622 node_get_content (xmlNodePtr node)
623 {
624 if (node == NULL)
625 return NULL;
626
627 switch (node->type)
628 {
629 case XML_ELEMENT_NODE:
630 return node_get_content (node->children);
631 break;
632 case XML_TEXT_NODE:
633 return (const char *) node->content;
634 break;
635 default:
636 return NULL;
637 }
638 }
639
640 static gboolean
node_is_empty(xmlNodePtr node)641 node_is_empty (xmlNodePtr node)
642 {
643 if (node == NULL)
644 return TRUE;
645
646 if (node->type == XML_TEXT_NODE)
647 return node->content == NULL || node->content[0] == '\0';
648
649 return node->children == NULL;
650 }
651
652 typedef struct _xmlNodeIter {
653
654 xmlNodePtr cur_node;
655 xmlNodePtr next_node;
656
657 const char *name;
658 const char *ns_href;
659
660 void *user_data;
661
662 } xmlNodeIter;
663
664 static xmlNodePtr
xml_node_iter_next(xmlNodeIter * iter)665 xml_node_iter_next (xmlNodeIter *iter)
666 {
667 xmlNodePtr node;
668
669 while ((node = iter->next_node))
670 {
671 iter->next_node = node->next;
672
673 if (node->type == XML_ELEMENT_NODE) {
674 if (node_has_name_ns (node, iter->name, iter->ns_href))
675 break;
676 }
677 }
678
679 iter->cur_node = node;
680 return node;
681 }
682
683 static void *
xml_node_iter_get_user_data(xmlNodeIter * iter)684 xml_node_iter_get_user_data (xmlNodeIter *iter)
685 {
686 return iter->user_data;
687 }
688
689 static xmlNodePtr
xml_node_iter_get_current(xmlNodeIter * iter)690 xml_node_iter_get_current (xmlNodeIter *iter)
691 {
692 return iter->cur_node;
693 }
694
695 static xmlDocPtr
parse_xml(SoupMessage * msg,xmlNodePtr * root,const char * name,GError ** error)696 parse_xml (SoupMessage *msg,
697 xmlNodePtr *root,
698 const char *name,
699 GError **error)
700 {
701 xmlDocPtr doc;
702
703 if (!SOUP_STATUS_IS_SUCCESSFUL (msg->status_code))
704 {
705 g_set_error (error,
706 G_IO_ERROR,
707 http_error_code_from_status (msg->status_code),
708 _("HTTP Error: %s"), msg->reason_phrase);
709 return NULL;
710 }
711
712 doc = xmlReadMemory (msg->response_body->data,
713 msg->response_body->length,
714 "response.xml",
715 NULL,
716 XML_PARSE_NONET |
717 XML_PARSE_NOWARNING |
718 XML_PARSE_NOBLANKS |
719 XML_PARSE_NSCLEAN |
720 XML_PARSE_NOCDATA |
721 XML_PARSE_COMPACT);
722 if (doc == NULL)
723 {
724 g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_FAILED,
725 _("Could not parse response"));
726 return NULL;
727 }
728
729 *root = xmlDocGetRootElement (doc);
730
731 if (*root == NULL || (*root)->children == NULL)
732 {
733 g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_FAILED,
734 _("Empty response"));
735 xmlFreeDoc (doc);
736 return NULL;
737 }
738
739 if (strcmp ((char *) (*root)->name, name))
740 {
741 g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_FAILED,
742 _("Unexpected reply from server"));
743 xmlFreeDoc (doc);
744 return NULL;
745 }
746
747 return doc;
748 }
749
750 /* ************************************************************************* */
751 /* Multistatus parsing code */
752
753 typedef struct _Multistatus Multistatus;
754 typedef struct _MsResponse MsResponse;
755 typedef struct _MsPropstat MsPropstat;
756
757 struct _Multistatus {
758
759 xmlDocPtr doc;
760 xmlNodePtr root;
761
762 SoupURI *target;
763 char *path;
764
765 };
766
767 struct _MsResponse {
768
769 Multistatus *multistatus;
770 char *path;
771 gboolean is_target;
772 xmlNodePtr first_propstat;
773
774 };
775
776 struct _MsPropstat {
777
778 Multistatus *multistatus;
779
780 xmlNodePtr prop_node;
781 guint status_code;
782
783 };
784
785
786 static gboolean
multistatus_parse(SoupMessage * msg,Multistatus * multistatus,GError ** error)787 multistatus_parse (SoupMessage *msg, Multistatus *multistatus, GError **error)
788 {
789 xmlDocPtr doc;
790 xmlNodePtr root;
791 SoupURI *uri;
792
793 doc = parse_xml (msg, &root, "multistatus", error);
794
795 if (doc == NULL)
796 return FALSE;
797
798 uri = soup_message_get_uri (msg);
799
800 multistatus->doc = doc;
801 multistatus->root = root;
802 multistatus->target = uri;
803 multistatus->path = g_uri_unescape_string (uri->path, "/");
804
805 return TRUE;
806 }
807
808 static void
multistatus_free(Multistatus * multistatus)809 multistatus_free (Multistatus *multistatus)
810 {
811 xmlFreeDoc (multistatus->doc);
812 g_free (multistatus->path);
813 }
814
815 static void
multistatus_get_response_iter(Multistatus * multistatus,xmlNodeIter * iter)816 multistatus_get_response_iter (Multistatus *multistatus, xmlNodeIter *iter)
817 {
818 iter->cur_node = multistatus->root->children;
819 iter->next_node = multistatus->root->children;
820 iter->name = "response";
821 iter->ns_href = "DAV:";
822 iter->user_data = multistatus;
823 }
824
825 static gboolean
multistatus_get_response(xmlNodeIter * resp_iter,MsResponse * response)826 multistatus_get_response (xmlNodeIter *resp_iter, MsResponse *response)
827 {
828 Multistatus *multistatus;
829 xmlNodePtr resp_node;
830 xmlNodePtr iter;
831 xmlNodePtr href;
832 xmlNodePtr propstat;
833 SoupURI *uri;
834 const char *text;
835 char *path;
836
837 multistatus = xml_node_iter_get_user_data (resp_iter);
838 resp_node = xml_node_iter_get_current (resp_iter);
839
840 if (resp_node == NULL)
841 return FALSE;
842
843 propstat = NULL;
844 href = NULL;
845
846 for (iter = resp_node->children; iter; iter = iter->next)
847 {
848 if (! node_is_element (iter))
849 {
850 continue;
851 }
852 else if (node_has_name_ns (iter, "href", "DAV:"))
853 {
854 href = iter;
855 }
856 else if (node_has_name_ns (iter, "propstat", "DAV:"))
857 {
858 if (propstat == NULL)
859 propstat = iter;
860 }
861
862 if (href && propstat)
863 break;
864 }
865
866 if (href == NULL)
867 return FALSE;
868
869 text = node_get_content (href);
870
871 if (text == NULL)
872 return FALSE;
873
874 uri = soup_uri_new_with_base (multistatus->target, text);
875
876 if (uri == NULL)
877 return FALSE;
878
879 path = g_uri_unescape_string (uri->path, "/");
880 soup_uri_free (uri);
881
882 response->path = path;
883 response->is_target = path_equal (path, multistatus->path, TRUE);
884 response->multistatus = multistatus;
885 response->first_propstat = propstat;
886
887 return resp_node != NULL;
888 }
889
890 static void
ms_response_clear(MsResponse * response)891 ms_response_clear (MsResponse *response)
892 {
893 g_free (response->path);
894 }
895
896 static char *
ms_response_get_basename(MsResponse * response)897 ms_response_get_basename (MsResponse *response)
898 {
899 return http_path_get_basename (response->path);
900 }
901
902 static void
ms_response_get_propstat_iter(MsResponse * response,xmlNodeIter * iter)903 ms_response_get_propstat_iter (MsResponse *response, xmlNodeIter *iter)
904 {
905 iter->cur_node = response->first_propstat;
906 iter->next_node = response->first_propstat;
907 iter->name = "propstat";
908 iter->ns_href = "DAV:";
909 iter->user_data = response;
910 }
911
912 static guint
ms_response_get_propstat(xmlNodeIter * cur_node,MsPropstat * propstat)913 ms_response_get_propstat (xmlNodeIter *cur_node, MsPropstat *propstat)
914 {
915 MsResponse *response;
916 xmlNodePtr pstat_node;
917 xmlNodePtr iter;
918 xmlNodePtr prop;
919 xmlNodePtr status;
920 const char *status_text;
921 gboolean res;
922 guint code;
923
924 response = xml_node_iter_get_user_data (cur_node);
925 pstat_node = xml_node_iter_get_current (cur_node);
926
927 if (pstat_node == NULL)
928 return 0;
929
930 status = NULL;
931 prop = NULL;
932
933 for (iter = pstat_node->children; iter; iter = iter->next)
934 {
935 if (!node_is_element (iter))
936 {
937 continue;
938 }
939 else if (node_has_name_ns (iter, "status", "DAV:"))
940 {
941 status = iter;
942 }
943 else if (node_has_name_ns (iter, "prop", "DAV:"))
944 {
945 prop = iter;
946 }
947
948 if (status && prop)
949 break;
950 }
951
952 status_text = node_get_content (status);
953
954 if (status_text == NULL || prop == NULL)
955 return 0;
956
957 res = soup_headers_parse_status_line ((char *) status_text,
958 NULL,
959 &code,
960 NULL);
961
962 if (res == FALSE)
963 return 0;
964
965 propstat->prop_node = prop;
966 propstat->status_code = code;
967 propstat->multistatus = response->multistatus;
968
969 return code;
970 }
971
972 static GFileType
parse_resourcetype(xmlNodePtr rt)973 parse_resourcetype (xmlNodePtr rt)
974 {
975 xmlNodePtr node;
976 GFileType type;
977
978 for (node = rt->children; node; node = node->next)
979 {
980 if (node_is_element (node))
981 break;
982 }
983
984 if (node == NULL)
985 return G_FILE_TYPE_REGULAR;
986
987 if (! strcmp ((char *) node->name, "collection"))
988 type = G_FILE_TYPE_DIRECTORY;
989 else if (! strcmp ((char *) node->name, "redirectref"))
990 type = G_FILE_TYPE_SYMBOLIC_LINK;
991 else
992 type = G_FILE_TYPE_UNKNOWN;
993
994 return type;
995 }
996
997 static inline void
file_info_set_content_type(GFileInfo * info,const char * type,gboolean uncertain_content_type)998 file_info_set_content_type (GFileInfo *info,
999 const char *type,
1000 gboolean uncertain_content_type)
1001 {
1002 if (!uncertain_content_type)
1003 g_file_info_set_content_type (info, type);
1004 g_file_info_set_attribute_string (info,
1005 G_FILE_ATTRIBUTE_STANDARD_FAST_CONTENT_TYPE,
1006 type);
1007
1008 }
1009
1010 static void
ms_response_to_file_info(MsResponse * response,GFileInfo * info)1011 ms_response_to_file_info (MsResponse *response,
1012 GFileInfo *info)
1013 {
1014 xmlNodeIter iter;
1015 MsPropstat propstat;
1016 xmlNodePtr node;
1017 guint status;
1018 char *basename;
1019 const char *text;
1020 GFileType file_type;
1021 char *mime_type;
1022 gboolean uncertain_content_type;
1023 GIcon *icon;
1024 GIcon *symbolic_icon;
1025 gboolean have_display_name;
1026
1027 basename = ms_response_get_basename (response);
1028 g_file_info_set_name (info, basename);
1029 g_file_info_set_edit_name (info, basename);
1030
1031 if (basename && basename[0] == '.')
1032 g_file_info_set_is_hidden (info, TRUE);
1033
1034 g_file_info_set_attribute_boolean (info, G_FILE_ATTRIBUTE_ACCESS_CAN_TRASH, FALSE);
1035
1036 file_type = G_FILE_TYPE_REGULAR;
1037 mime_type = NULL;
1038 uncertain_content_type = FALSE;
1039
1040 have_display_name = FALSE;
1041 ms_response_get_propstat_iter (response, &iter);
1042 while (xml_node_iter_next (&iter))
1043 {
1044 status = ms_response_get_propstat (&iter, &propstat);
1045
1046 if (! SOUP_STATUS_IS_SUCCESSFUL (status))
1047 continue;
1048
1049 for (node = propstat.prop_node->children; node; node = node->next)
1050 {
1051 if (! node_is_element (node) || node_is_empty (node))
1052 continue; /* TODO: check namespace, parse user data nodes*/
1053
1054 text = node_get_content (node);
1055
1056 if (node_has_name (node, "resourcetype"))
1057 {
1058 file_type = parse_resourcetype (node);
1059 }
1060 else if (node_has_name (node, "displayname") && text)
1061 {
1062 g_file_info_set_display_name (info, text);
1063 have_display_name = TRUE;
1064 }
1065 else if (node_has_name (node, "getetag"))
1066 {
1067 g_file_info_set_attribute_string (info, G_FILE_ATTRIBUTE_ETAG_VALUE,
1068 text);
1069 }
1070 else if (node_has_name (node, "creationdate"))
1071 {
1072 GDateTime *dt;
1073
1074 if ((dt = g_date_time_new_from_iso8601 (text, NULL)) == NULL)
1075 continue;
1076
1077 g_file_info_set_attribute_uint64 (info,
1078 G_FILE_ATTRIBUTE_TIME_CREATED,
1079 g_date_time_to_unix (dt));
1080 g_date_time_unref (dt);
1081 }
1082 else if (node_has_name (node, "getcontenttype"))
1083 {
1084 char *ptr;
1085
1086 mime_type = g_strdup (text);
1087
1088 /* Ignore parameters of the content type */
1089 ptr = strchr (mime_type, ';');
1090 if (ptr)
1091 {
1092 do
1093 *ptr-- = '\0';
1094 while (ptr >= mime_type && g_ascii_isspace (*ptr));
1095 }
1096 }
1097 else if (node_has_name (node, "getcontentlength"))
1098 {
1099 gint64 size;
1100 size = g_ascii_strtoll (text, NULL, 10);
1101 g_file_info_set_size (info, size);
1102 }
1103 else if (node_has_name (node, "getlastmodified"))
1104 {
1105 SoupDate *sd;
1106
1107 sd = soup_date_new_from_string(text);
1108 if (sd)
1109 {
1110 g_file_info_set_attribute_uint64 (info, G_FILE_ATTRIBUTE_TIME_MODIFIED, soup_date_to_time_t (sd));
1111 g_file_info_set_attribute_uint32 (info, G_FILE_ATTRIBUTE_TIME_MODIFIED_USEC, 0);
1112 soup_date_free (sd);
1113 }
1114 }
1115 }
1116 }
1117
1118 g_file_info_set_file_type (info, file_type);
1119 if (file_type == G_FILE_TYPE_DIRECTORY)
1120 {
1121 g_clear_pointer (&mime_type, g_free);
1122 mime_type = g_strdup ("inode/directory");
1123
1124 icon = g_content_type_get_icon (mime_type);
1125 symbolic_icon = g_content_type_get_symbolic_icon (mime_type);
1126 file_info_set_content_type (info, mime_type, FALSE);
1127
1128 /* Ignore file size for directories. Most of the servers don't report it
1129 * for directories anyway. However, some servers report total size of
1130 * files inside the directory, which is not expected and causes issues
1131 * for clients.
1132 */
1133 g_file_info_remove_attribute (info, G_FILE_ATTRIBUTE_STANDARD_SIZE);
1134 }
1135 else
1136 {
1137 if (mime_type == NULL)
1138 mime_type = g_content_type_guess (basename, NULL, 0, &uncertain_content_type);
1139
1140 icon = g_content_type_get_icon (mime_type);
1141 if (G_IS_THEMED_ICON (icon))
1142 {
1143 g_themed_icon_append_name (G_THEMED_ICON (icon), "text-x-generic");
1144 }
1145
1146 symbolic_icon = g_content_type_get_symbolic_icon (mime_type);
1147 if (G_IS_THEMED_ICON (icon))
1148 {
1149 g_themed_icon_append_name (G_THEMED_ICON (symbolic_icon), "text-x-generic-symbolic");
1150 }
1151
1152 file_info_set_content_type (info, mime_type, uncertain_content_type);
1153 }
1154
1155 if (have_display_name == FALSE)
1156 g_file_info_set_display_name (info, basename);
1157
1158 g_file_info_set_icon (info, icon);
1159 g_file_info_set_symbolic_icon (info, symbolic_icon);
1160 g_object_unref (icon);
1161 g_object_unref (symbolic_icon);
1162 g_free (mime_type);
1163 g_free (basename);
1164
1165 }
1166
1167 static void
ms_response_to_fs_info(MsResponse * response,GFileInfo * info)1168 ms_response_to_fs_info (MsResponse *response,
1169 GFileInfo *info)
1170 {
1171 xmlNodeIter iter;
1172 MsPropstat propstat;
1173 xmlNodePtr node;
1174 guint status;
1175 const char *text;
1176 guint64 bytes_avail;
1177 guint64 bytes_used;
1178 gboolean have_bytes_avail;
1179 gboolean have_bytes_used;
1180
1181 bytes_avail = bytes_used = 0;
1182 have_bytes_avail = have_bytes_used = FALSE;
1183
1184 ms_response_get_propstat_iter (response, &iter);
1185 while (xml_node_iter_next (&iter))
1186 {
1187 status = ms_response_get_propstat (&iter, &propstat);
1188
1189 if (! SOUP_STATUS_IS_SUCCESSFUL (status))
1190 continue;
1191
1192 for (node = propstat.prop_node->children; node; node = node->next)
1193 {
1194 if (! node_is_element (node))
1195 continue;
1196
1197 text = node_get_content (node);
1198 if (text == NULL)
1199 continue;
1200
1201 if (node_has_name (node, "quota-available-bytes"))
1202 {
1203 if (! string_to_uint64 (text, &bytes_avail))
1204 continue;
1205
1206 have_bytes_avail = TRUE;
1207 }
1208 else if (node_has_name (node, "quota-used-bytes"))
1209 {
1210 if (! string_to_uint64 (text, &bytes_used))
1211 continue;
1212
1213 have_bytes_used = TRUE;
1214 }
1215 }
1216 }
1217
1218 if (have_bytes_used)
1219 g_file_info_set_attribute_uint64 (info,
1220 G_FILE_ATTRIBUTE_FILESYSTEM_USED,
1221 bytes_used);
1222
1223 if (have_bytes_avail)
1224 {
1225 g_file_info_set_attribute_uint64 (info,
1226 G_FILE_ATTRIBUTE_FILESYSTEM_FREE,
1227 bytes_avail);
1228 if (have_bytes_used && G_MAXUINT64 - bytes_avail >= bytes_used)
1229 g_file_info_set_attribute_uint64 (info,
1230 G_FILE_ATTRIBUTE_FILESYSTEM_SIZE,
1231 bytes_avail + bytes_used);
1232 }
1233 }
1234
1235 #define PROPSTAT_XML_BEGIN \
1236 "<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n" \
1237 " <D:propfind xmlns:D=\"DAV:\">\n"
1238
1239 #define PROPSTAT_XML_ALLPROP " <D:allprop/>\n"
1240 #define PROPSTAT_XML_PROP_BEGIN " <D:prop>\n"
1241 #define PROPSTAT_XML_PROP_END " </D:prop>\n"
1242
1243 #define PROPSTAT_XML_END \
1244 " </D:propfind>"
1245
1246 typedef struct _PropName {
1247
1248 const char *name;
1249 const char *namespace;
1250
1251 } PropName;
1252
1253
1254 static SoupMessage *
propfind_request_new(GVfsBackend * backend,const char * filename,guint depth,const PropName * properties)1255 propfind_request_new (GVfsBackend *backend,
1256 const char *filename,
1257 guint depth,
1258 const PropName *properties)
1259 {
1260 SoupMessage *msg;
1261 SoupURI *uri;
1262 const char *header_depth;
1263 GString *body;
1264
1265 uri = g_vfs_backend_dav_uri_for_path (backend, filename, depth > 0);
1266 msg = soup_message_new_from_uri (SOUP_METHOD_PROPFIND, uri);
1267 soup_uri_free (uri);
1268
1269 if (msg == NULL)
1270 return NULL;
1271
1272 if (depth == 0)
1273 header_depth = "0";
1274 else if (depth == 1)
1275 header_depth = "1";
1276 else
1277 header_depth = "infinity";
1278
1279 soup_message_headers_append (msg->request_headers, "Depth", header_depth);
1280
1281 body = g_string_new (PROPSTAT_XML_BEGIN);
1282
1283 if (properties != NULL)
1284 {
1285 const PropName *prop;
1286 g_string_append (body, PROPSTAT_XML_PROP_BEGIN);
1287
1288 for (prop = properties; prop->name; prop++)
1289 {
1290 if (prop->namespace != NULL)
1291 g_string_append_printf (body, "<%s xmlns=\"%s\"/>\n",
1292 prop->name,
1293 prop->namespace);
1294 else
1295 g_string_append_printf (body, "<D:%s/>\n", prop->name);
1296 }
1297 g_string_append (body, PROPSTAT_XML_PROP_END);
1298 }
1299 else
1300 g_string_append (body, PROPSTAT_XML_ALLPROP);
1301
1302
1303 g_string_append (body, PROPSTAT_XML_END);
1304
1305 soup_message_set_request (msg, "application/xml",
1306 SOUP_MEMORY_TAKE,
1307 body->str,
1308 body->len);
1309
1310 g_string_free (body, FALSE);
1311
1312 return msg;
1313 }
1314
1315 static SoupMessage *
stat_location_begin(SoupURI * uri,gboolean count_children)1316 stat_location_begin (SoupURI *uri,
1317 gboolean count_children)
1318 {
1319 SoupMessage *msg;
1320 const char *depth;
1321 static const char *stat_profind_body =
1322 PROPSTAT_XML_BEGIN
1323 PROPSTAT_XML_PROP_BEGIN
1324 "<D:resourcetype/>\n"
1325 "<D:getcontentlength/>\n"
1326 PROPSTAT_XML_PROP_END
1327 PROPSTAT_XML_END;
1328
1329 msg = soup_message_new_from_uri (SOUP_METHOD_PROPFIND, uri);
1330
1331 if (count_children)
1332 depth = "1";
1333 else
1334 depth = "0";
1335
1336 soup_message_headers_append (msg->request_headers, "Depth", depth);
1337
1338 soup_message_set_request (msg, "application/xml",
1339 SOUP_MEMORY_STATIC,
1340 stat_profind_body,
1341 strlen (stat_profind_body));
1342 return msg;
1343 }
1344
1345 static gboolean
stat_location_finish(SoupMessage * msg,GFileType * target_type,gint64 * target_size,guint * num_children)1346 stat_location_finish (SoupMessage *msg,
1347 GFileType *target_type,
1348 gint64 *target_size,
1349 guint *num_children)
1350 {
1351 Multistatus ms;
1352 xmlNodeIter iter;
1353 gboolean res;
1354 GError *error;
1355 guint child_count;
1356 GFileInfo *file_info;
1357
1358 if (msg->status_code != 207)
1359 return FALSE;
1360
1361 res = multistatus_parse (msg, &ms, &error);
1362
1363 if (res == FALSE)
1364 return FALSE;
1365
1366 res = FALSE;
1367 child_count = 0;
1368 file_info = g_file_info_new ();
1369
1370 multistatus_get_response_iter (&ms, &iter);
1371 while (xml_node_iter_next (&iter))
1372 {
1373 MsResponse response;
1374
1375 if (! multistatus_get_response (&iter, &response))
1376 continue;
1377
1378 if (response.is_target)
1379 {
1380 ms_response_to_file_info (&response, file_info);
1381 res = TRUE;
1382 }
1383 else
1384 child_count++;
1385
1386 ms_response_clear (&response);
1387 }
1388
1389 if (res)
1390 {
1391 if (target_type)
1392 *target_type = g_file_info_get_file_type (file_info);
1393
1394 if (target_size)
1395 *target_size = g_file_info_get_size (file_info);
1396
1397 if (num_children)
1398 *num_children = child_count;
1399 }
1400
1401 multistatus_free (&ms);
1402 g_object_unref (file_info);
1403 return res;
1404 }
1405
1406 static gboolean
stat_location(GVfsBackend * backend,SoupURI * uri,GFileType * target_type,gint64 * target_size,guint * num_children,GError ** error)1407 stat_location (GVfsBackend *backend,
1408 SoupURI *uri,
1409 GFileType *target_type,
1410 gint64 *target_size,
1411 guint *num_children,
1412 GError **error)
1413 {
1414 SoupMessage *msg;
1415 guint status;
1416 gboolean count_children;
1417 gboolean res;
1418
1419 count_children = num_children != NULL;
1420 msg = stat_location_begin (uri, count_children);
1421
1422 if (msg == NULL)
1423 return FALSE;
1424
1425 status = g_vfs_backend_dav_send_message (backend, msg);
1426
1427 if (status != 207)
1428 {
1429 g_set_error_literal (error,
1430 G_IO_ERROR,
1431 http_error_code_from_status (status),
1432 msg->reason_phrase);
1433
1434 g_object_unref (msg);
1435 return FALSE;
1436 }
1437
1438 res = stat_location_finish (msg, target_type, target_size, num_children);
1439 g_object_unref (msg);
1440
1441 if (res == FALSE)
1442 g_set_error_literal (error,
1443 G_IO_ERROR, G_IO_ERROR_FAILED,
1444 _("Response invalid"));
1445
1446 return res;
1447 }
1448
1449 /* ************************************************************************* */
1450 /* Authentication */
1451
1452 static void
mount_auth_info_free(MountAuthData * data)1453 mount_auth_info_free (MountAuthData *data)
1454 {
1455 if (data->mount_source)
1456 g_object_unref (data->mount_source);
1457
1458 g_free (data->server_auth.username);
1459 g_free (data->server_auth.password);
1460 g_free (data->server_auth.realm);
1461
1462 g_free (data->proxy_auth.username);
1463 g_free (data->proxy_auth.password);
1464
1465 }
1466
1467 static void
soup_authenticate_from_data(SoupSession * session,SoupMessage * msg,SoupAuth * auth,gboolean retrying,gpointer user_data)1468 soup_authenticate_from_data (SoupSession *session,
1469 SoupMessage *msg,
1470 SoupAuth *auth,
1471 gboolean retrying,
1472 gpointer user_data)
1473 {
1474 MountAuthData *data;
1475 AuthInfo *info;
1476
1477 g_debug ("+ soup_authenticate_from_data (%s) \n",
1478 retrying ? "retrying" : "first auth");
1479
1480 if (retrying)
1481 return;
1482
1483 data = (MountAuthData *) user_data;
1484
1485 if (soup_auth_is_for_proxy (auth))
1486 info = &data->proxy_auth;
1487 else
1488 info = &data->server_auth;
1489
1490 soup_auth_authenticate (auth, info->username, info->password);
1491 }
1492
1493 static void
soup_authenticate_interactive(SoupSession * session,SoupMessage * msg,SoupAuth * auth,gboolean retrying,gpointer user_data)1494 soup_authenticate_interactive (SoupSession *session,
1495 SoupMessage *msg,
1496 SoupAuth *auth,
1497 gboolean retrying,
1498 gpointer user_data)
1499 {
1500 MountAuthData *data;
1501 AuthInfo *info;
1502 GAskPasswordFlags pw_ask_flags;
1503 GPasswordSave pw_save;
1504 const char *realm;
1505 gboolean res;
1506 gboolean aborted;
1507 gboolean is_proxy;
1508 gboolean have_auth;
1509 char *new_username;
1510 char *new_password;
1511 char *prompt;
1512
1513 data = (MountAuthData *) user_data;
1514
1515 retrying = (retrying || data->retrying_after_403);
1516
1517 g_debug ("+ soup_authenticate_interactive (%s) \n",
1518 retrying ? "retrying" : "first auth");
1519
1520 new_username = NULL;
1521 new_password = NULL;
1522 realm = NULL;
1523 pw_ask_flags = G_ASK_PASSWORD_NEED_PASSWORD;
1524
1525 is_proxy = soup_auth_is_for_proxy (auth);
1526 realm = soup_auth_get_realm (auth);
1527
1528 if (is_proxy)
1529 info = &(data->proxy_auth);
1530 else
1531 info = &(data->server_auth);
1532
1533 if (realm && info->realm == NULL)
1534 info->realm = g_strdup (realm);
1535 else if (realm && info->realm && !g_str_equal (realm, info->realm))
1536 return;
1537
1538 have_auth = info->username && info->password;
1539
1540 if (have_auth == FALSE && g_vfs_keyring_is_available ())
1541 {
1542 SoupURI *uri;
1543 SoupURI *uri_free = NULL;
1544
1545 pw_ask_flags |= G_ASK_PASSWORD_SAVING_SUPPORTED;
1546
1547 if (is_proxy)
1548 {
1549 g_object_get (session, SOUP_SESSION_PROXY_URI, &uri_free, NULL);
1550 uri = uri_free;
1551 }
1552 else
1553 uri = soup_message_get_uri (msg);
1554
1555 res = g_vfs_keyring_lookup_password (info->username,
1556 uri->host,
1557 NULL,
1558 "http",
1559 realm,
1560 is_proxy ? "proxy" : "basic",
1561 uri->port,
1562 &new_username,
1563 NULL,
1564 &new_password);
1565
1566 if (res == TRUE)
1567 {
1568 have_auth = TRUE;
1569 g_free (info->username);
1570 g_free (info->password);
1571 info->username = new_username;
1572 info->password = new_password;
1573 }
1574
1575 if (uri_free)
1576 soup_uri_free (uri_free);
1577 }
1578
1579 if (retrying == FALSE && have_auth)
1580 {
1581 soup_auth_authenticate (auth, info->username, info->password);
1582 return;
1583 }
1584
1585 if (is_proxy == FALSE)
1586 {
1587 if (realm == NULL)
1588 realm = _("WebDAV share");
1589
1590 prompt = g_strdup_printf (_("Enter password for %s"), realm);
1591 }
1592 else
1593 prompt = g_strdup (_("Please enter proxy password"));
1594
1595 if (info->username == NULL)
1596 pw_ask_flags |= G_ASK_PASSWORD_NEED_USERNAME;
1597
1598 res = g_mount_source_ask_password (data->mount_source,
1599 prompt,
1600 info->username,
1601 NULL,
1602 pw_ask_flags,
1603 &aborted,
1604 &new_password,
1605 &new_username,
1606 NULL,
1607 NULL,
1608 &pw_save);
1609
1610 if (res && !aborted)
1611 {
1612 /* it's not safe to assume that we get the username filed in,
1613 in the case that we provied a default username */
1614 if (new_username == NULL)
1615 new_username = g_strdup (info->username);
1616
1617 soup_auth_authenticate (auth, new_username, new_password);
1618
1619 g_free (info->username);
1620 g_free (info->password);
1621 info->username = new_username;
1622 info->password = new_password;
1623 info->pw_save = pw_save;
1624 }
1625 else
1626 soup_session_cancel_message (session, msg, SOUP_STATUS_CANCELLED);
1627
1628 g_debug ("- soup_authenticate \n");
1629 g_free (prompt);
1630 }
1631
1632 static void
keyring_save_authinfo(AuthInfo * info,SoupURI * uri,gboolean is_proxy)1633 keyring_save_authinfo (AuthInfo *info,
1634 SoupURI *uri,
1635 gboolean is_proxy)
1636 {
1637 const char *type = is_proxy ? "proxy" : "basic";
1638
1639 g_vfs_keyring_save_password (info->username,
1640 uri->host,
1641 NULL,
1642 "http",
1643 info->realm,
1644 type,
1645 uri->port,
1646 info->password,
1647 info->pw_save);
1648 }
1649
1650 /* ************************************************************************* */
1651
1652 static SoupURI *
g_mount_spec_to_dav_uri(GMountSpec * spec)1653 g_mount_spec_to_dav_uri (GMountSpec *spec)
1654 {
1655 SoupURI *uri;
1656 const char *host;
1657 const char *user;
1658 const char *port;
1659 const char *ssl;
1660 const char *path;
1661 gint port_num;
1662
1663 host = g_mount_spec_get (spec, "host");
1664 user = g_mount_spec_get (spec, "user");
1665 port = g_mount_spec_get (spec, "port");
1666 ssl = g_mount_spec_get (spec, "ssl");
1667 path = spec->mount_prefix;
1668
1669 if (host == NULL || *host == 0)
1670 return NULL;
1671
1672 uri = soup_uri_new (NULL);
1673
1674 if (ssl != NULL && (strcmp (ssl, "true") == 0))
1675 soup_uri_set_scheme (uri, SOUP_URI_SCHEME_HTTPS);
1676 else
1677 soup_uri_set_scheme (uri, SOUP_URI_SCHEME_HTTP);
1678
1679 soup_uri_set_user (uri, user);
1680
1681 /* IPv6 host does not include brackets in SoupURI, but GMountSpec host does */
1682 if (gvfs_is_ipv6 (host))
1683 uri->host = g_strndup (host + 1, strlen (host) - 2);
1684 else
1685 soup_uri_set_host (uri, host);
1686
1687 if (port && (port_num = atoi (port)))
1688 soup_uri_set_port (uri, port_num);
1689
1690 g_free (uri->path);
1691 uri->path = dav_uri_encode (path);
1692
1693 return uri;
1694 }
1695
1696 static GMountSpec *
g_mount_spec_from_dav_uri(GVfsBackendDav * dav_backend,SoupURI * uri)1697 g_mount_spec_from_dav_uri (GVfsBackendDav *dav_backend,
1698 SoupURI *uri)
1699 {
1700 GMountSpec *spec;
1701 const char *ssl;
1702 char *local_path;
1703
1704 #ifdef HAVE_AVAHI
1705 if (dav_backend->resolver != NULL)
1706 {
1707 const char *type;
1708 const char *service_type;
1709
1710 service_type = g_vfs_dns_sd_resolver_get_service_type (dav_backend->resolver);
1711 if (strcmp (service_type, "_webdavs._tcp") == 0)
1712 type = "davs+sd";
1713 else
1714 type = "dav+sd";
1715
1716 spec = g_mount_spec_new (type);
1717 g_mount_spec_set (spec,
1718 "host",
1719 g_vfs_dns_sd_resolver_get_encoded_triple (dav_backend->resolver));
1720 return spec;
1721 }
1722 #endif
1723
1724 spec = g_mount_spec_new ("dav");
1725
1726 /* IPv6 host does not include brackets in SoupURI, but GMountSpec host does */
1727 if (strchr (uri->host, ':'))
1728 {
1729 char *host = g_strdup_printf ("[%s]", uri->host);
1730 g_mount_spec_set (spec, "host", host);
1731 g_free (host);
1732 }
1733 else
1734 g_mount_spec_set (spec, "host", uri->host);
1735
1736 if (uri->scheme == SOUP_URI_SCHEME_HTTPS)
1737 ssl = "true";
1738 else
1739 ssl = "false";
1740
1741 g_mount_spec_set (spec, "ssl", ssl);
1742
1743 if (uri->user)
1744 g_mount_spec_set (spec, "user", uri->user);
1745
1746 if (! soup_uri_uses_default_port (uri))
1747 {
1748 char *port = g_strdup_printf ("%u", uri->port);
1749 g_mount_spec_set (spec, "port", port);
1750 g_free (port);
1751 }
1752
1753 /* There must not be any illegal characters in the
1754 URL at this point */
1755 local_path = g_uri_unescape_string (uri->path, "/");
1756 g_mount_spec_set_mount_prefix (spec, local_path);
1757 g_free (local_path);
1758
1759 return spec;
1760 }
1761
1762 #ifdef HAVE_AVAHI
1763 static SoupURI *
dav_uri_from_dns_sd_resolver(GVfsBackendDav * dav_backend)1764 dav_uri_from_dns_sd_resolver (GVfsBackendDav *dav_backend)
1765 {
1766 SoupURI *uri;
1767 char *user;
1768 char *path;
1769 char *address;
1770 gchar *interface;
1771 const char *service_type;
1772 guint port;
1773
1774 service_type = g_vfs_dns_sd_resolver_get_service_type (dav_backend->resolver);
1775 address = g_vfs_dns_sd_resolver_get_address (dav_backend->resolver);
1776 interface = g_vfs_dns_sd_resolver_get_interface (dav_backend->resolver);
1777 port = g_vfs_dns_sd_resolver_get_port (dav_backend->resolver);
1778 user = g_vfs_dns_sd_resolver_lookup_txt_record (dav_backend->resolver, "u"); /* mandatory */
1779 path = g_vfs_dns_sd_resolver_lookup_txt_record (dav_backend->resolver, "path"); /* optional */
1780
1781 /* TODO: According to http://www.dns-sd.org/ServiceTypes.html
1782 * there's also a TXT record "p" for password. Handle this.
1783 */
1784
1785 uri = soup_uri_new (NULL);
1786
1787 if (strcmp (service_type, "_webdavs._tcp") == 0)
1788 soup_uri_set_scheme (uri, SOUP_URI_SCHEME_HTTPS);
1789 else
1790 soup_uri_set_scheme (uri, SOUP_URI_SCHEME_HTTP);
1791
1792 soup_uri_set_user (uri, user);
1793
1794 soup_uri_set_port (uri, port);
1795
1796 /* IPv6 host does not include brackets in SoupURI, but GVfsDnsSdResolver host does */
1797 if (gvfs_is_ipv6 (address))
1798 {
1799 /* Link-local addresses require interface to be specified. */
1800 if (g_str_has_prefix (address, "[fe80:") && interface != NULL)
1801 {
1802 uri->host = g_strconcat (address + 1, interface, NULL);
1803 uri->host[strlen (address) - 2] = '%';
1804 }
1805 else
1806 uri->host = g_strndup (address + 1, strlen (address) - 2);
1807 }
1808 else
1809 soup_uri_set_host (uri, address);
1810
1811 if (path != NULL)
1812 soup_uri_set_path (uri, path);
1813 else
1814 soup_uri_set_path (uri, "/");
1815
1816
1817 g_free (address);
1818 g_free (interface);
1819 g_free (user);
1820 g_free (path);
1821
1822 return uri;
1823 }
1824 #endif
1825
1826 #ifdef HAVE_AVAHI
1827 static void
dns_sd_resolver_changed(GVfsDnsSdResolver * resolver,GVfsBackendDav * dav_backend)1828 dns_sd_resolver_changed (GVfsDnsSdResolver *resolver,
1829 GVfsBackendDav *dav_backend)
1830 {
1831 /* If anything has changed (e.g. address, port, txt-records or is-resolved),
1832 * it is safest to just unmount. */
1833 g_vfs_backend_force_unmount (G_VFS_BACKEND (dav_backend));
1834 }
1835 #endif
1836
1837 /* ************************************************************************* */
1838 /* Backend Functions */
1839 static void
do_mount(GVfsBackend * backend,GVfsJobMount * job,GMountSpec * mount_spec,GMountSource * mount_source,gboolean is_automount)1840 do_mount (GVfsBackend *backend,
1841 GVfsJobMount *job,
1842 GMountSpec *mount_spec,
1843 GMountSource *mount_source,
1844 gboolean is_automount)
1845 {
1846 GVfsBackendDav *dav_backend = G_VFS_BACKEND_DAV (backend);
1847 MountAuthData *data;
1848 SoupSession *session;
1849 SoupMessage *msg_opts;
1850 SoupMessage *msg_stat;
1851 SoupURI *mount_base;
1852 gulong signal_id;
1853 guint status;
1854 gboolean is_success;
1855 gboolean is_webdav;
1856 gboolean is_collection;
1857 gboolean auth_interactive;
1858 gboolean res;
1859 char *last_good_path;
1860 const char *host;
1861 const char *type;
1862
1863 g_debug ("+ mount\n");
1864
1865 host = g_mount_spec_get (mount_spec, "host");
1866 type = g_mount_spec_get (mount_spec, "type");
1867
1868 #ifdef HAVE_AVAHI
1869 /* resolve DNS-SD style URIs */
1870 if ((strcmp (type, "dav+sd") == 0 || strcmp (type, "davs+sd") == 0) && host != NULL)
1871 {
1872 GError *error;
1873
1874 dav_backend->resolver = g_vfs_dns_sd_resolver_new_for_encoded_triple (host, "u");
1875
1876 error = NULL;
1877 if (!g_vfs_dns_sd_resolver_resolve_sync (dav_backend->resolver,
1878 NULL,
1879 &error))
1880 {
1881 g_vfs_job_failed_from_error (G_VFS_JOB (job), error);
1882 g_error_free (error);
1883 return;
1884 }
1885 g_signal_connect (dav_backend->resolver,
1886 "changed",
1887 (GCallback) dns_sd_resolver_changed,
1888 dav_backend);
1889
1890 mount_base = dav_uri_from_dns_sd_resolver (dav_backend);
1891 }
1892 else
1893 #endif
1894 {
1895 mount_base = g_mount_spec_to_dav_uri (mount_spec);
1896 }
1897
1898 if (mount_base == NULL)
1899 {
1900 g_vfs_job_failed (G_VFS_JOB (job), G_IO_ERROR,
1901 G_IO_ERROR_INVALID_ARGUMENT,
1902 _("Invalid mount spec"));
1903 return;
1904 }
1905
1906 session = G_VFS_BACKEND_HTTP (backend)->session;
1907 G_VFS_BACKEND_HTTP (backend)->mount_base = mount_base;
1908
1909 soup_session_add_feature_by_type (session, SOUP_TYPE_AUTH_NEGOTIATE);
1910 soup_session_add_feature_by_type (session, SOUP_TYPE_AUTH_NTLM);
1911
1912 /* Override the HTTP backend's default. */
1913 g_object_set (session,
1914 "ssl-strict", TRUE,
1915 SOUP_SESSION_MAX_CONNS_PER_HOST, MAX_CONNS,
1916 SOUP_SESSION_MAX_CONNS, MAX_CONNS,
1917 NULL);
1918
1919 data = &(G_VFS_BACKEND_DAV (backend)->auth_info);
1920 data->mount_source = g_object_ref (mount_source);
1921 data->server_auth.username = g_strdup (mount_base->user);
1922 data->server_auth.pw_save = G_PASSWORD_SAVE_NEVER;
1923 data->proxy_auth.pw_save = G_PASSWORD_SAVE_NEVER;
1924
1925 signal_id = g_signal_connect (session, "authenticate",
1926 G_CALLBACK (soup_authenticate_interactive),
1927 data);
1928 auth_interactive = TRUE;
1929
1930 last_good_path = NULL;
1931 msg_opts = soup_message_new_from_uri (SOUP_METHOD_OPTIONS, mount_base);
1932
1933 /* The count_children parameter is intentionally set to TRUE to be sure that
1934 enumeration is possible: https://gitlab.gnome.org/GNOME/gvfs/-/issues/468 */
1935 msg_stat = stat_location_begin (mount_base, TRUE);
1936
1937 do {
1938 GFileType file_type;
1939 SoupURI *cur_uri;
1940
1941 res = TRUE;
1942 status = g_vfs_backend_dav_send_message (backend, msg_opts);
1943 is_success = SOUP_STATUS_IS_SUCCESSFUL (status);
1944 is_webdav = sm_has_header (msg_opts, "DAV");
1945
1946 /* Workaround for servers which response with 403 instead of 401 in case of
1947 * wrong credentials to let the user specify its credentials again. */
1948 if (status == SOUP_STATUS_FORBIDDEN &&
1949 last_good_path == NULL &&
1950 (data->server_auth.password != NULL ||
1951 data->proxy_auth.password != NULL))
1952 {
1953 SoupSessionFeature *auth_manager;
1954
1955 data->retrying_after_403 = TRUE;
1956
1957 g_clear_pointer (&data->server_auth.username, g_free);
1958 data->server_auth.username = g_strdup (mount_base->user);
1959 g_clear_pointer (&data->server_auth.password, g_free);
1960 g_clear_pointer (&data->proxy_auth.password, g_free);
1961
1962 auth_manager = soup_session_get_feature (session, SOUP_TYPE_AUTH_MANAGER);
1963 soup_auth_manager_clear_cached_credentials (SOUP_AUTH_MANAGER (auth_manager));
1964
1965 g_object_unref (msg_opts);
1966 msg_opts = soup_message_new_from_uri (SOUP_METHOD_OPTIONS, mount_base);
1967
1968 continue;
1969 }
1970
1971 /* If SSL is used and the certificate verifies OK, then ssl-strict remains
1972 * on for all further connections.
1973 * If SSL is used and the certificate does not verify OK, then the user
1974 * gets a chance to override it. If they do, ssl-strict is disabled but
1975 * the certificate is stored, and checked on each subsequent connection to
1976 * ensure that it hasn't changed. */
1977 if (status == SOUP_STATUS_SSL_FAILED &&
1978 !dav_backend->certificate_errors)
1979 {
1980 GTlsCertificate *certificate;
1981 GTlsCertificateFlags errors;
1982
1983 soup_message_get_https_status (msg_opts, &certificate, &errors);
1984
1985 if (gvfs_accept_certificate (mount_source, certificate, errors))
1986 {
1987 g_object_set (session, "ssl-strict", FALSE, NULL);
1988 dav_backend->certificate = g_object_ref (certificate);
1989 dav_backend->certificate_errors = errors;
1990 continue;
1991 }
1992 else
1993 {
1994 break;
1995 }
1996 }
1997
1998 if (!is_success || !is_webdav)
1999 break;
2000
2001 soup_message_headers_clear (msg_opts->response_headers);
2002 soup_message_body_truncate (msg_opts->response_body);
2003
2004 cur_uri = soup_message_get_uri (msg_opts);
2005 soup_message_set_uri (msg_stat, cur_uri);
2006
2007 g_vfs_backend_dav_send_message (backend, msg_stat);
2008 res = stat_location_finish (msg_stat, &file_type, NULL, NULL);
2009 is_collection = res && file_type == G_FILE_TYPE_DIRECTORY;
2010
2011 g_debug (" [%s] webdav: %d, collection %d [res: %d]\n",
2012 mount_base->path, is_webdav, is_collection, res);
2013
2014 if (is_collection == FALSE)
2015 break;
2016
2017 /* we have found a new good root, try the parent ... */
2018 g_free (last_good_path);
2019 last_good_path = mount_base->path;
2020 mount_base->path = path_get_parent_dir (mount_base->path);
2021 soup_message_set_uri (msg_opts, mount_base);
2022
2023 if (auth_interactive)
2024 {
2025 /* if we have found a root that is good then we assume
2026 that we also have obtained to correct credentials
2027 and we switch the auth handler. This will prevent us
2028 from asking for *different* credentials *again* if the
2029 server should response with 401 for some of the parent
2030 collections. See also bug #677753 */
2031
2032 g_signal_handler_disconnect (session, signal_id);
2033 g_signal_connect (session, "authenticate",
2034 G_CALLBACK (soup_authenticate_from_data),
2035 data);
2036 auth_interactive = FALSE;
2037 }
2038
2039 soup_message_headers_clear (msg_stat->response_headers);
2040 soup_message_body_truncate (msg_stat->response_body);
2041
2042 } while (g_strcmp0 (last_good_path, "/") != 0);
2043
2044 /* we either encountered an error or we have
2045 reached the end of paths we are allowed to
2046 chdir up to (or couldn't chdir up at all) */
2047
2048 /* check if we at all have a good path */
2049 if (last_good_path == NULL)
2050 {
2051 if ((is_success && !is_webdav) ||
2052 msg_opts->status_code == SOUP_STATUS_METHOD_NOT_ALLOWED)
2053 {
2054 /* This means the either: a) OPTIONS request succeeded
2055 (which should be the case even for non-existent
2056 resources on a webdav enabled share) but we did not
2057 get the DAV header. Or b) the OPTIONS request was a
2058 METHOD_NOT_ALLOWED (405).
2059 Prioritize this error messages, because it seems most
2060 useful to the user. */
2061 g_vfs_job_failed (G_VFS_JOB (job),
2062 G_IO_ERROR, G_IO_ERROR_FAILED,
2063 _("Not a WebDAV enabled share"));
2064 }
2065 else if (!is_success || !res)
2066 {
2067 /* Either the OPTIONS request (is_success) or the PROPFIND
2068 request (res) failed. */
2069 SoupMessage *target = !is_success ? msg_opts : msg_stat;
2070 int error_code = http_error_code_from_status (target->status_code);
2071
2072 if (error_code == G_IO_ERROR_CANCELLED)
2073 error_code = G_IO_ERROR_FAILED_HANDLED;
2074
2075 g_vfs_job_failed (G_VFS_JOB (job),
2076 G_IO_ERROR, error_code,
2077 _("HTTP Error: %s"), target->reason_phrase);
2078 }
2079 else
2080 {
2081 /* This means, we have a valid DAV header, PROPFIND worked,
2082 but it is not a collection! */
2083 g_vfs_job_failed (G_VFS_JOB (job),
2084 G_IO_ERROR, G_IO_ERROR_FAILED,
2085 _("Could not find an enclosing directory"));
2086 }
2087
2088 g_object_unref (msg_opts);
2089 g_object_unref (msg_stat);
2090
2091 return;
2092 }
2093
2094 /* Success! We are mounted */
2095 /* Save the auth info in the keyring */
2096
2097 keyring_save_authinfo (&(data->server_auth), mount_base, FALSE);
2098 /* TODO: save proxy auth */
2099
2100 /* Set the working path in mount path */
2101 g_free (mount_base->path);
2102 mount_base->path = last_good_path;
2103
2104 /* dup the mountspec, but only copy known fields */
2105 mount_spec = g_mount_spec_from_dav_uri (dav_backend, mount_base);
2106
2107 g_vfs_backend_set_mount_spec (backend, mount_spec);
2108 g_vfs_backend_set_icon_name (backend, "folder-remote");
2109 g_vfs_backend_set_symbolic_icon_name (backend, "folder-remote-symbolic");
2110
2111 g_vfs_backend_dav_setup_display_name (backend);
2112
2113 /* cleanup */
2114 g_mount_spec_unref (mount_spec);
2115 g_object_unref (msg_opts);
2116 g_object_unref (msg_stat);
2117
2118 g_vfs_job_succeeded (G_VFS_JOB (job));
2119 g_debug ("- mount\n");
2120 }
2121
2122 static PropName ls_propnames[] = {
2123 {"creationdate", NULL},
2124 {"displayname", NULL},
2125 {"getcontentlength", NULL},
2126 {"getcontenttype", NULL},
2127 {"getetag", NULL},
2128 {"getlastmodified", NULL},
2129 {"resourcetype", NULL},
2130 {NULL, NULL}
2131 };
2132
2133 /* *** query_info () *** */
2134 static void
do_query_info(GVfsBackend * backend,GVfsJobQueryInfo * job,const char * filename,GFileQueryInfoFlags flags,GFileInfo * info,GFileAttributeMatcher * matcher)2135 do_query_info (GVfsBackend *backend,
2136 GVfsJobQueryInfo *job,
2137 const char *filename,
2138 GFileQueryInfoFlags flags,
2139 GFileInfo *info,
2140 GFileAttributeMatcher *matcher)
2141 {
2142 SoupMessage *msg;
2143 Multistatus ms;
2144 xmlNodeIter iter;
2145 gboolean res;
2146 GError *error;
2147
2148 error = NULL;
2149
2150 g_debug ("Query info %s\n", filename);
2151
2152 msg = propfind_request_new (backend, filename, 0, ls_propnames);
2153
2154 if (msg == NULL)
2155 {
2156 g_vfs_job_failed (G_VFS_JOB (job),
2157 G_IO_ERROR, G_IO_ERROR_FAILED,
2158 _("Could not create request"));
2159
2160 return;
2161 }
2162
2163 message_add_redirect_header (msg, flags);
2164
2165 g_vfs_backend_dav_send_message (backend, msg);
2166
2167 res = multistatus_parse (msg, &ms, &error);
2168
2169 if (res == FALSE)
2170 {
2171 g_vfs_job_failed_from_error (G_VFS_JOB (job), error);
2172 g_error_free (error);
2173 g_object_unref (msg);
2174 return;
2175 }
2176
2177 res = FALSE;
2178 multistatus_get_response_iter (&ms, &iter);
2179
2180 while (xml_node_iter_next (&iter))
2181 {
2182 MsResponse response;
2183
2184 if (! multistatus_get_response (&iter, &response))
2185 continue;
2186
2187 if (response.is_target)
2188 {
2189 ms_response_to_file_info (&response, job->file_info);
2190 res = TRUE;
2191 }
2192
2193 ms_response_clear (&response);
2194 }
2195
2196 multistatus_free (&ms);
2197 g_object_unref (msg);
2198
2199 if (res)
2200 g_vfs_job_succeeded (G_VFS_JOB (job));
2201 else
2202 g_vfs_job_failed (G_VFS_JOB (job),
2203 G_IO_ERROR, G_IO_ERROR_FAILED,
2204 _("Response invalid"));
2205
2206 }
2207
2208 static PropName fs_info_propnames[] = {
2209 {"quota-available-bytes", NULL},
2210 {"quota-used-bytes", NULL},
2211 {NULL, NULL}
2212 };
2213
2214 static void
do_query_fs_info(GVfsBackend * backend,GVfsJobQueryFsInfo * job,const char * filename,GFileInfo * info,GFileAttributeMatcher * attribute_matcher)2215 do_query_fs_info (GVfsBackend *backend,
2216 GVfsJobQueryFsInfo *job,
2217 const char *filename,
2218 GFileInfo *info,
2219 GFileAttributeMatcher *attribute_matcher)
2220 {
2221 SoupMessage *msg;
2222 Multistatus ms;
2223 xmlNodeIter iter;
2224 gboolean res;
2225 GError *error;
2226
2227 g_file_info_set_attribute_string (info,
2228 G_FILE_ATTRIBUTE_FILESYSTEM_TYPE,
2229 "webdav");
2230 g_file_info_set_attribute_boolean (info,
2231 G_FILE_ATTRIBUTE_FILESYSTEM_REMOTE,
2232 TRUE);
2233
2234 if (! (g_file_attribute_matcher_matches (attribute_matcher,
2235 G_FILE_ATTRIBUTE_FILESYSTEM_SIZE) ||
2236 g_file_attribute_matcher_matches (attribute_matcher,
2237 G_FILE_ATTRIBUTE_FILESYSTEM_USED) ||
2238 g_file_attribute_matcher_matches (attribute_matcher,
2239 G_FILE_ATTRIBUTE_FILESYSTEM_FREE)))
2240 {
2241 g_vfs_job_succeeded (G_VFS_JOB (job));
2242 return;
2243 }
2244
2245 msg = propfind_request_new (backend, filename, 0, fs_info_propnames);
2246
2247 if (msg == NULL)
2248 {
2249 g_vfs_job_failed (G_VFS_JOB (job),
2250 G_IO_ERROR, G_IO_ERROR_FAILED,
2251 _("Could not create request"));
2252
2253 return;
2254 }
2255
2256 g_vfs_backend_dav_send_message (backend, msg);
2257
2258 error = NULL;
2259 res = multistatus_parse (msg, &ms, &error);
2260
2261 if (res == FALSE)
2262 {
2263 g_vfs_job_failed_from_error (G_VFS_JOB (job), error);
2264 g_error_free (error);
2265 g_object_unref (msg);
2266 return;
2267 }
2268
2269 res = FALSE;
2270 multistatus_get_response_iter (&ms, &iter);
2271
2272 while (xml_node_iter_next (&iter))
2273 {
2274 MsResponse response;
2275
2276 if (! multistatus_get_response (&iter, &response))
2277 continue;
2278
2279 if (response.is_target)
2280 {
2281 ms_response_to_fs_info (&response, info);
2282 res = TRUE;
2283 }
2284
2285 ms_response_clear (&response);
2286 }
2287
2288 multistatus_free (&ms);
2289 g_object_unref (msg);
2290
2291 if (res)
2292 g_vfs_job_succeeded (G_VFS_JOB (job));
2293 else
2294 g_vfs_job_failed (G_VFS_JOB (job),
2295 G_IO_ERROR, G_IO_ERROR_FAILED,
2296 _("Response invalid"));
2297
2298 }
2299
2300 /* *** enumerate *** */
2301 static void
do_enumerate(GVfsBackend * backend,GVfsJobEnumerate * job,const char * filename,GFileAttributeMatcher * matcher,GFileQueryInfoFlags flags)2302 do_enumerate (GVfsBackend *backend,
2303 GVfsJobEnumerate *job,
2304 const char *filename,
2305 GFileAttributeMatcher *matcher,
2306 GFileQueryInfoFlags flags)
2307 {
2308 SoupMessage *msg;
2309 Multistatus ms;
2310 xmlNodeIter iter;
2311 gboolean res;
2312 GError *error;
2313
2314 error = NULL;
2315
2316 g_debug ("+ do_enumerate: %s\n", filename);
2317
2318 msg = propfind_request_new (backend, filename, 1, ls_propnames);
2319
2320 if (msg == NULL)
2321 {
2322 g_vfs_job_failed (G_VFS_JOB (job),
2323 G_IO_ERROR, G_IO_ERROR_FAILED,
2324 _("Could not create request"));
2325
2326 return;
2327 }
2328
2329 message_add_redirect_header (msg, flags);
2330
2331 g_vfs_backend_dav_send_message (backend, msg);
2332
2333 res = multistatus_parse (msg, &ms, &error);
2334
2335 if (res == FALSE)
2336 {
2337 g_vfs_job_failed_from_error (G_VFS_JOB (job), error);
2338 g_error_free (error);
2339 g_object_unref (msg);
2340 return;
2341 }
2342 g_vfs_job_succeeded (G_VFS_JOB (job));
2343
2344 multistatus_get_response_iter (&ms, &iter);
2345
2346 while (xml_node_iter_next (&iter))
2347 {
2348 MsResponse response;
2349 GFileInfo *info;
2350
2351 if (! multistatus_get_response (&iter, &response))
2352 continue;
2353
2354 if (response.is_target == FALSE)
2355 {
2356 info = g_file_info_new ();
2357 ms_response_to_file_info (&response, info);
2358 g_vfs_job_enumerate_add_info (job, info);
2359 g_object_unref (info);
2360 }
2361
2362 ms_response_clear (&response);
2363 }
2364
2365 multistatus_free (&ms);
2366 g_object_unref (msg);
2367
2368 g_vfs_job_enumerate_done (G_VFS_JOB_ENUMERATE (job));
2369 }
2370
2371 /* ************************************************************************* */
2372 /* */
2373
2374 /* *** open () *** */
2375 static void
try_open_stat_done(SoupSession * session,SoupMessage * msg,gpointer user_data)2376 try_open_stat_done (SoupSession *session,
2377 SoupMessage *msg,
2378 gpointer user_data)
2379 {
2380 GVfsJob *job = G_VFS_JOB (user_data);
2381 GVfsBackend *backend = job->backend_data;
2382 GFileType target_type;
2383 SoupURI *uri;
2384 gboolean res;
2385
2386 if (msg->status_code != 207)
2387 {
2388 http_job_failed (job, msg);
2389 return;
2390 }
2391
2392 res = stat_location_finish (msg, &target_type, NULL, NULL);
2393
2394 if (res == FALSE)
2395 {
2396 g_vfs_job_failed (job,
2397 G_IO_ERROR, G_IO_ERROR_FAILED,
2398 _("Response invalid"));
2399 return;
2400 }
2401
2402 if (target_type == G_FILE_TYPE_DIRECTORY)
2403 {
2404 g_vfs_job_failed (job,
2405 G_IO_ERROR, G_IO_ERROR_IS_DIRECTORY,
2406 _("File is directory"));
2407 return;
2408 }
2409
2410 uri = soup_message_get_uri (msg);
2411
2412 http_backend_open_for_read (backend, job, uri);
2413 }
2414
2415
2416 static gboolean
try_open_for_read(GVfsBackend * backend,GVfsJobOpenForRead * job,const char * filename)2417 try_open_for_read (GVfsBackend *backend,
2418 GVfsJobOpenForRead *job,
2419 const char *filename)
2420 {
2421 SoupMessage *msg;
2422 SoupURI *uri;
2423
2424 uri = g_vfs_backend_dav_uri_for_path (backend, filename, FALSE);
2425 msg = stat_location_begin (uri, FALSE);
2426 soup_uri_free (uri);
2427
2428 if (msg == NULL)
2429 {
2430 g_vfs_job_failed (G_VFS_JOB (job),
2431 G_IO_ERROR, G_IO_ERROR_FAILED,
2432 _("Could not create request"));
2433
2434 return FALSE;
2435 }
2436
2437 g_vfs_job_set_backend_data (G_VFS_JOB (job), backend, NULL);
2438 g_vfs_backend_dav_queue_message (backend, msg, try_open_stat_done, job);
2439
2440 return TRUE;
2441 }
2442
2443
2444
2445
2446 /* *** create () *** */
2447 static void
try_create_tested_existence(SoupSession * session,SoupMessage * msg,gpointer user_data)2448 try_create_tested_existence (SoupSession *session, SoupMessage *msg,
2449 gpointer user_data)
2450 {
2451 GVfsJob *job = G_VFS_JOB (user_data);
2452 GOutputStream *stream;
2453 SoupMessage *put_msg;
2454 SoupURI *uri;
2455
2456 if (SOUP_STATUS_IS_SUCCESSFUL (msg->status_code))
2457 {
2458 g_vfs_job_failed (job,
2459 G_IO_ERROR,
2460 G_IO_ERROR_EXISTS,
2461 _("Target file already exists"));
2462 return;
2463 }
2464 /* TODO: other errors */
2465
2466 uri = soup_message_get_uri (msg);
2467 put_msg = soup_message_new_from_uri (SOUP_METHOD_PUT, uri);
2468
2469 /*
2470 * Doesn't work with apache > 2.2.9
2471 * soup_message_headers_append (put_msg->request_headers, "If-None-Match", "*");
2472 */
2473 stream = g_memory_output_stream_new (NULL, 0, g_try_realloc, g_free);
2474 g_object_set_data_full (G_OBJECT (stream), "-gvfs-stream-msg", put_msg, g_object_unref);
2475
2476 g_vfs_job_open_for_write_set_handle (G_VFS_JOB_OPEN_FOR_WRITE (job), stream);
2477 g_vfs_job_open_for_write_set_can_seek (G_VFS_JOB_OPEN_FOR_WRITE (job),
2478 g_seekable_can_seek (G_SEEKABLE (stream)));
2479 g_vfs_job_open_for_write_set_can_truncate (G_VFS_JOB_OPEN_FOR_WRITE (job),
2480 g_seekable_can_truncate (G_SEEKABLE (stream)));
2481 g_vfs_job_succeeded (job);
2482 }
2483
2484 static gboolean
try_create(GVfsBackend * backend,GVfsJobOpenForWrite * job,const char * filename,GFileCreateFlags flags)2485 try_create (GVfsBackend *backend,
2486 GVfsJobOpenForWrite *job,
2487 const char *filename,
2488 GFileCreateFlags flags)
2489 {
2490 SoupMessage *msg;
2491 SoupURI *uri;
2492
2493 /* TODO: if we supported chunked requests, we could
2494 * use a PUT with "If-None-Match: *" and "Expect: 100-continue"
2495 */
2496 uri = g_vfs_backend_dav_uri_for_path (backend, filename, FALSE);
2497 msg = soup_message_new_from_uri (SOUP_METHOD_HEAD, uri);
2498 soup_uri_free (uri);
2499
2500 g_vfs_job_set_backend_data (G_VFS_JOB (job), backend, NULL);
2501
2502 g_vfs_backend_dav_queue_message (backend, msg, try_create_tested_existence, job);
2503 return TRUE;
2504 }
2505
2506 /* *** replace () *** */
2507 static void
open_for_replace_succeeded(GVfsBackendHttp * op_backend,GVfsJob * job,SoupURI * uri,const char * etag)2508 open_for_replace_succeeded (GVfsBackendHttp *op_backend, GVfsJob *job,
2509 SoupURI *uri, const char *etag)
2510 {
2511 SoupMessage *put_msg;
2512 GOutputStream *stream;
2513
2514 put_msg = soup_message_new_from_uri (SOUP_METHOD_PUT, uri);
2515
2516 if (etag)
2517 soup_message_headers_append (put_msg->request_headers, "If-Match", etag);
2518
2519 stream = g_memory_output_stream_new (NULL, 0, g_try_realloc, g_free);
2520 g_object_set_data_full (G_OBJECT (stream), "-gvfs-stream-msg", put_msg, g_object_unref);
2521
2522 g_vfs_job_open_for_write_set_handle (G_VFS_JOB_OPEN_FOR_WRITE (job), stream);
2523 g_vfs_job_open_for_write_set_can_seek (G_VFS_JOB_OPEN_FOR_WRITE (job),
2524 g_seekable_can_seek (G_SEEKABLE (stream)));
2525 g_vfs_job_open_for_write_set_can_truncate (G_VFS_JOB_OPEN_FOR_WRITE (job),
2526 g_seekable_can_truncate (G_SEEKABLE (stream)));
2527 g_vfs_job_succeeded (job);
2528 }
2529
2530 static void
try_replace_checked_etag(SoupSession * session,SoupMessage * msg,gpointer user_data)2531 try_replace_checked_etag (SoupSession *session, SoupMessage *msg,
2532 gpointer user_data)
2533 {
2534 GVfsJob *job = G_VFS_JOB (user_data);
2535 GVfsBackendHttp *op_backend = job->backend_data;
2536
2537 if (msg->status_code == SOUP_STATUS_PRECONDITION_FAILED)
2538 {
2539 g_vfs_job_failed (G_VFS_JOB (job),
2540 G_IO_ERROR,
2541 G_IO_ERROR_WRONG_ETAG,
2542 _("The file was externally modified"));
2543 return;
2544 }
2545 /* TODO: other errors */
2546
2547 open_for_replace_succeeded (op_backend, job, soup_message_get_uri (msg),
2548 soup_message_headers_get_one (msg->request_headers, "If-Match"));
2549 }
2550
2551 static gboolean
try_replace(GVfsBackend * backend,GVfsJobOpenForWrite * job,const char * filename,const char * etag,gboolean make_backup,GFileCreateFlags flags)2552 try_replace (GVfsBackend *backend,
2553 GVfsJobOpenForWrite *job,
2554 const char *filename,
2555 const char *etag,
2556 gboolean make_backup,
2557 GFileCreateFlags flags)
2558 {
2559 GVfsBackendHttp *op_backend;
2560 SoupURI *uri;
2561
2562 /* TODO: if SoupOutputStream supported chunked requests, we could
2563 * use a PUT with "If-Match: ..." and "Expect: 100-continue"
2564 */
2565
2566 op_backend = G_VFS_BACKEND_HTTP (backend);
2567
2568 if (make_backup)
2569 {
2570 g_vfs_job_failed (G_VFS_JOB (job),
2571 G_IO_ERROR,
2572 G_IO_ERROR_CANT_CREATE_BACKUP,
2573 _("Backup file creation failed"));
2574 return TRUE;
2575 }
2576
2577
2578
2579 uri = g_vfs_backend_dav_uri_for_path (backend, filename, FALSE);
2580
2581 if (etag)
2582 {
2583 SoupMessage *msg;
2584
2585 msg = soup_message_new_from_uri (SOUP_METHOD_HEAD, uri);
2586 soup_uri_free (uri);
2587 soup_message_headers_append (msg->request_headers, "If-Match", etag);
2588
2589 g_vfs_job_set_backend_data (G_VFS_JOB (job), op_backend, NULL);
2590 g_vfs_backend_dav_queue_message (backend, msg,
2591 try_replace_checked_etag, job);
2592 return TRUE;
2593 }
2594
2595 open_for_replace_succeeded (op_backend, G_VFS_JOB (job), uri, NULL);
2596 soup_uri_free (uri);
2597 return TRUE;
2598 }
2599
2600 /* *** write () *** */
2601 static void
write_ready(GObject * source_object,GAsyncResult * result,gpointer user_data)2602 write_ready (GObject *source_object,
2603 GAsyncResult *result,
2604 gpointer user_data)
2605 {
2606 GOutputStream *stream;
2607 GVfsJob *job;
2608 GError *error;
2609 gssize nwrote;
2610
2611 stream = G_OUTPUT_STREAM (source_object);
2612 error = NULL;
2613 job = G_VFS_JOB (user_data);
2614
2615 nwrote = g_output_stream_write_finish (stream, result, &error);
2616
2617 if (nwrote < 0)
2618 {
2619 g_vfs_job_failed_literal (G_VFS_JOB (job),
2620 error->domain,
2621 error->code,
2622 error->message);
2623
2624 g_error_free (error);
2625 return;
2626 }
2627
2628 g_vfs_job_write_set_written_size (G_VFS_JOB_WRITE (job), nwrote);
2629 g_vfs_job_succeeded (job);
2630 }
2631
2632 static gboolean
try_write(GVfsBackend * backend,GVfsJobWrite * job,GVfsBackendHandle handle,char * buffer,gsize buffer_size)2633 try_write (GVfsBackend *backend,
2634 GVfsJobWrite *job,
2635 GVfsBackendHandle handle,
2636 char *buffer,
2637 gsize buffer_size)
2638 {
2639 GOutputStream *stream;
2640
2641 stream = G_OUTPUT_STREAM (handle);
2642
2643 g_output_stream_write_async (stream,
2644 buffer,
2645 buffer_size,
2646 G_PRIORITY_DEFAULT,
2647 G_VFS_JOB (job)->cancellable,
2648 write_ready,
2649 job);
2650 return TRUE;
2651 }
2652
2653 static void
do_seek_on_write(GVfsBackend * backend,GVfsJobSeekWrite * job,GVfsBackendHandle handle,goffset offset,GSeekType type)2654 do_seek_on_write (GVfsBackend *backend,
2655 GVfsJobSeekWrite *job,
2656 GVfsBackendHandle handle,
2657 goffset offset,
2658 GSeekType type)
2659 {
2660 GSeekable *stream = G_SEEKABLE (handle);
2661 GError *error = NULL;
2662
2663 if (g_seekable_seek (stream, offset, type, G_VFS_JOB (job)->cancellable, &error))
2664 {
2665 g_vfs_job_seek_write_set_offset (job, g_seekable_tell (stream));
2666 g_vfs_job_succeeded (G_VFS_JOB (job));
2667 }
2668 else
2669 {
2670 g_vfs_job_failed_from_error (G_VFS_JOB (job), error);
2671 g_error_free (error);
2672 }
2673 }
2674
2675 static void
do_truncate(GVfsBackend * backend,GVfsJobTruncate * job,GVfsBackendHandle handle,goffset size)2676 do_truncate (GVfsBackend *backend,
2677 GVfsJobTruncate *job,
2678 GVfsBackendHandle handle,
2679 goffset size)
2680 {
2681 GSeekable *stream = G_SEEKABLE (handle);
2682 GError *error = NULL;
2683
2684 if (g_seekable_truncate (stream, size, G_VFS_JOB (job)->cancellable, &error))
2685 {
2686 g_vfs_job_succeeded (G_VFS_JOB (job));
2687 }
2688 else
2689 {
2690 g_vfs_job_failed_from_error (G_VFS_JOB (job), error);
2691 g_error_free (error);
2692 }
2693 }
2694
2695 /* *** close_write () *** */
2696 static void
try_close_write_sent(SoupSession * session,SoupMessage * msg,gpointer user_data)2697 try_close_write_sent (SoupSession *session,
2698 SoupMessage *msg,
2699 gpointer user_data)
2700 {
2701 GVfsJob *job;
2702
2703 job = G_VFS_JOB (user_data);
2704 if (!SOUP_STATUS_IS_SUCCESSFUL (msg->status_code))
2705 http_job_failed (job, msg);
2706 else
2707 g_vfs_job_succeeded (job);
2708 }
2709
2710 static gboolean
try_close_write(GVfsBackend * backend,GVfsJobCloseWrite * job,GVfsBackendHandle handle)2711 try_close_write (GVfsBackend *backend,
2712 GVfsJobCloseWrite *job,
2713 GVfsBackendHandle handle)
2714 {
2715 GOutputStream *stream;
2716 SoupMessage *msg;
2717 gsize length;
2718 gchar *data;
2719
2720 stream = G_OUTPUT_STREAM (handle);
2721
2722 msg = g_object_get_data (G_OBJECT (stream), "-gvfs-stream-msg");
2723 g_object_ref (msg);
2724 g_object_set_data (G_OBJECT (stream), "-gvfs-stream-msg", NULL);
2725
2726 g_output_stream_close (stream, NULL, NULL);
2727 length = g_memory_output_stream_get_data_size (G_MEMORY_OUTPUT_STREAM (stream));
2728 data = g_memory_output_stream_steal_data (G_MEMORY_OUTPUT_STREAM (stream));
2729 g_object_unref (stream);
2730
2731 soup_message_body_append (msg->request_body, SOUP_MEMORY_TAKE, data, length);
2732 g_vfs_backend_dav_queue_message (backend, msg,
2733 try_close_write_sent, job);
2734
2735 return TRUE;
2736 }
2737
2738 static void
do_make_directory(GVfsBackend * backend,GVfsJobMakeDirectory * job,const char * filename)2739 do_make_directory (GVfsBackend *backend,
2740 GVfsJobMakeDirectory *job,
2741 const char *filename)
2742 {
2743 SoupMessage *msg;
2744 SoupURI *uri;
2745 guint status;
2746
2747 uri = g_vfs_backend_dav_uri_for_path (backend, filename, TRUE);
2748 msg = soup_message_new_from_uri (SOUP_METHOD_MKCOL, uri);
2749 soup_uri_free (uri);
2750
2751 status = g_vfs_backend_dav_send_message (backend, msg);
2752
2753 if (! SOUP_STATUS_IS_SUCCESSFUL (status))
2754 if (status == SOUP_STATUS_METHOD_NOT_ALLOWED)
2755 g_vfs_job_failed (G_VFS_JOB (job), G_IO_ERROR,
2756 G_IO_ERROR_EXISTS,
2757 _("Target file already exists"));
2758 else
2759 http_job_failed (G_VFS_JOB (job), msg);
2760 else
2761 g_vfs_job_succeeded (G_VFS_JOB (job));
2762
2763 g_object_unref (msg);
2764 }
2765
2766 static void
do_delete(GVfsBackend * backend,GVfsJobDelete * job,const char * filename)2767 do_delete (GVfsBackend *backend,
2768 GVfsJobDelete *job,
2769 const char *filename)
2770 {
2771 SoupMessage *msg;
2772 SoupURI *uri;
2773 GFileType file_type;
2774 gboolean res;
2775 guint num_children;
2776 guint status;
2777 GError *error;
2778
2779 error = NULL;
2780
2781 uri = g_vfs_backend_dav_uri_for_path (backend, filename, FALSE);
2782 res = stat_location (backend, uri, &file_type, NULL, &num_children, &error);
2783
2784 if (res == FALSE)
2785 {
2786 g_vfs_job_failed_from_error (G_VFS_JOB (job), error);
2787 g_error_free (error);
2788 soup_uri_free (uri);
2789 return;
2790 }
2791
2792 if (file_type == G_FILE_TYPE_DIRECTORY && num_children)
2793 {
2794 g_vfs_job_failed (G_VFS_JOB (job),
2795 G_IO_ERROR, G_IO_ERROR_NOT_EMPTY,
2796 _("Directory not empty"));
2797 soup_uri_free (uri);
2798 return;
2799 }
2800
2801 msg = soup_message_new_from_uri (SOUP_METHOD_DELETE, uri);
2802
2803 status = g_vfs_backend_dav_send_message (backend, msg);
2804
2805 if (!SOUP_STATUS_IS_SUCCESSFUL (status))
2806 http_job_failed (G_VFS_JOB (job), msg);
2807 else
2808 g_vfs_job_succeeded (G_VFS_JOB (job));
2809
2810 soup_uri_free (uri);
2811 g_object_unref (msg);
2812 }
2813
2814 static void
do_set_display_name(GVfsBackend * backend,GVfsJobSetDisplayName * job,const char * filename,const char * display_name)2815 do_set_display_name (GVfsBackend *backend,
2816 GVfsJobSetDisplayName *job,
2817 const char *filename,
2818 const char *display_name)
2819 {
2820 SoupMessage *msg;
2821 SoupURI *source;
2822 SoupURI *target;
2823 char *target_path;
2824 char *dirname;
2825 guint status;
2826
2827 source = g_vfs_backend_dav_uri_for_path (backend, filename, FALSE);
2828 msg = soup_message_new_from_uri (SOUP_METHOD_MOVE, source);
2829
2830 dirname = g_path_get_dirname (filename);
2831 target_path = g_build_filename (dirname, display_name, NULL);
2832 target = g_vfs_backend_dav_uri_for_path (backend, target_path, FALSE);
2833
2834 message_add_destination_header (msg, target);
2835 message_add_overwrite_header (msg, FALSE);
2836
2837 status = g_vfs_backend_dav_send_message (backend, msg);
2838
2839 /*
2840 * The precondition of SOUP_STATUS_PRECONDITION_FAILED (412) in
2841 * this case was triggered by the "Overwrite: F" header which
2842 * means that the target already exists.
2843 * Also if we get a REDIRECTION it means that there was no
2844 * "Location" header, since otherwise that would have triggered
2845 * our redirection handler. This probably means we are dealing
2846 * with an web dav implementation (like mod_dav) that also sends
2847 * redirects for the destionaion (i.e. "Destination: /foo" header)
2848 * which very likely means that the target also exists (and is a
2849 * directory). That or the webdav server is broken.
2850 * We could find out by doing another stat and but I think this is
2851 * such a corner case that we are totally fine with returning
2852 * G_IO_ERROR_EXISTS.
2853 * */
2854
2855 if (SOUP_STATUS_IS_SUCCESSFUL (status))
2856 {
2857 g_debug ("new target_path: %s\n", target_path);
2858 g_vfs_job_set_display_name_set_new_path (job, target_path);
2859 g_vfs_job_succeeded (G_VFS_JOB (job));
2860 }
2861 else if (status == SOUP_STATUS_PRECONDITION_FAILED ||
2862 SOUP_STATUS_IS_REDIRECTION (status))
2863 g_vfs_job_failed (G_VFS_JOB (job), G_IO_ERROR,
2864 G_IO_ERROR_EXISTS,
2865 _("Target file already exists"));
2866 else
2867 http_job_failed (G_VFS_JOB (job), msg);
2868
2869 g_object_unref (msg);
2870 g_free (dirname);
2871 g_free (target_path);
2872 soup_uri_free (target);
2873 soup_uri_free (source);
2874 }
2875
2876 static void
do_move(GVfsBackend * backend,GVfsJobMove * job,const char * source,const char * destination,GFileCopyFlags flags,GFileProgressCallback progress_callback,gpointer progress_callback_data)2877 do_move (GVfsBackend *backend,
2878 GVfsJobMove *job,
2879 const char *source,
2880 const char *destination,
2881 GFileCopyFlags flags,
2882 GFileProgressCallback progress_callback,
2883 gpointer progress_callback_data)
2884 {
2885 SoupMessage *msg;
2886 SoupURI *source_uri;
2887 SoupURI *target_uri;
2888 guint status;
2889 GFileType source_ft, target_ft;
2890 GError *error = NULL;
2891 gboolean res, stat_res;
2892 gint64 file_size;
2893
2894 if (flags & G_FILE_COPY_BACKUP)
2895 {
2896 if (flags & G_FILE_COPY_NO_FALLBACK_FOR_MOVE)
2897 {
2898 g_vfs_job_failed_literal (G_VFS_JOB (job),
2899 G_IO_ERROR,
2900 G_IO_ERROR_CANT_CREATE_BACKUP,
2901 _("Backups not supported"));
2902 }
2903 else
2904 {
2905 /* Return G_IO_ERROR_NOT_SUPPORTED instead of G_IO_ERROR_CANT_CREATE_BACKUP
2906 * to be proceeded with copy and delete fallback (see g_file_move). */
2907 g_vfs_job_failed_literal (G_VFS_JOB (job),
2908 G_IO_ERROR,
2909 G_IO_ERROR_NOT_SUPPORTED,
2910 "Operation not supported");
2911 }
2912
2913 return;
2914 }
2915
2916 source_uri = g_vfs_backend_dav_uri_for_path (backend, source, FALSE);
2917 msg = soup_message_new_from_uri (SOUP_METHOD_MOVE, source_uri);
2918 target_uri = g_vfs_backend_dav_uri_for_path (backend, destination, FALSE);
2919
2920 res = stat_location (backend, target_uri, &target_ft, NULL, NULL, &error);
2921 if (!res && !g_error_matches (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND))
2922 {
2923 g_vfs_job_failed_from_error (G_VFS_JOB (job), error);
2924 goto error;
2925 }
2926 g_clear_error (&error);
2927
2928 stat_res = stat_location (backend, source_uri, &source_ft, &file_size, NULL, &error);
2929 if (res)
2930 {
2931 if (flags & G_FILE_COPY_OVERWRITE)
2932 {
2933 if (stat_res)
2934 {
2935 if (target_ft == G_FILE_TYPE_DIRECTORY)
2936 {
2937 if (source_ft == G_FILE_TYPE_DIRECTORY)
2938 g_vfs_job_failed_literal (G_VFS_JOB(job),
2939 G_IO_ERROR,
2940 G_IO_ERROR_WOULD_MERGE,
2941 _("Can’t move directory over directory"));
2942 else
2943 g_vfs_job_failed_literal (G_VFS_JOB(job),
2944 G_IO_ERROR,
2945 G_IO_ERROR_IS_DIRECTORY,
2946 _("Can’t move over directory"));
2947 goto error;
2948 }
2949 else if (source_ft == G_FILE_TYPE_DIRECTORY)
2950 {
2951 /* Overwriting a file with a directory, first remove the
2952 * file */
2953 SoupMessage *msg;
2954
2955 msg = soup_message_new_from_uri (SOUP_METHOD_DELETE,
2956 target_uri);
2957 status = g_vfs_backend_dav_send_message (backend, msg);
2958
2959 if (!SOUP_STATUS_IS_SUCCESSFUL (status))
2960 {
2961 http_job_failed (G_VFS_JOB (job), msg);
2962 g_object_unref (msg);
2963 goto error;
2964 }
2965 g_object_unref (msg);
2966 }
2967 }
2968 else
2969 {
2970 g_vfs_job_failed_from_error (G_VFS_JOB (job), error);
2971 goto error;
2972 }
2973 }
2974 else
2975 {
2976 g_vfs_job_failed_literal (G_VFS_JOB(job),
2977 G_IO_ERROR,
2978 G_IO_ERROR_EXISTS,
2979 _("Target file exists"));
2980 goto error;
2981 }
2982 }
2983
2984 message_add_destination_header (msg, target_uri);
2985 message_add_overwrite_header (msg, flags & G_FILE_COPY_OVERWRITE);
2986
2987 status = g_vfs_backend_dav_send_message (backend, msg);
2988
2989 /* See do_set_display_name () for the explanation of the PRECONDITION_FAILED
2990 * and IS_REDIRECTION handling below. */
2991
2992 if (SOUP_STATUS_IS_SUCCESSFUL (status))
2993 {
2994 if (stat_res && progress_callback)
2995 progress_callback (file_size, file_size, progress_callback_data);
2996 g_vfs_job_succeeded (G_VFS_JOB (job));
2997 }
2998 else if (status == SOUP_STATUS_PRECONDITION_FAILED ||
2999 SOUP_STATUS_IS_REDIRECTION (status))
3000 g_vfs_job_failed (G_VFS_JOB (job), G_IO_ERROR,
3001 G_IO_ERROR_EXISTS,
3002 _("Target file already exists"));
3003 else
3004 http_job_failed (G_VFS_JOB (job), msg);
3005
3006 error:
3007 g_object_unref (msg);
3008 g_clear_error (&error);
3009 soup_uri_free (source_uri);
3010 soup_uri_free (target_uri);
3011 }
3012
3013 static void
do_copy(GVfsBackend * backend,GVfsJobCopy * job,const char * source,const char * destination,GFileCopyFlags flags,GFileProgressCallback progress_callback,gpointer progress_callback_data)3014 do_copy (GVfsBackend *backend,
3015 GVfsJobCopy *job,
3016 const char *source,
3017 const char *destination,
3018 GFileCopyFlags flags,
3019 GFileProgressCallback progress_callback,
3020 gpointer progress_callback_data)
3021 {
3022 SoupMessage *msg;
3023 SoupURI *source_uri;
3024 SoupURI *target_uri;
3025 guint status;
3026 GFileType source_ft, target_ft;
3027 GError *error = NULL;
3028 gboolean res;
3029 gint64 file_size;
3030
3031 if (flags & G_FILE_COPY_BACKUP)
3032 {
3033 /* Return G_IO_ERROR_NOT_SUPPORTED instead of
3034 * G_IO_ERROR_CANT_CREATE_BACKUP to proceed with the GIO fallback
3035 * copy. */
3036 g_vfs_job_failed_literal (G_VFS_JOB (job),
3037 G_IO_ERROR,
3038 G_IO_ERROR_NOT_SUPPORTED,
3039 "Operation not supported");
3040 return;
3041 }
3042
3043 source_uri = g_vfs_backend_dav_uri_for_path (backend, source, FALSE);
3044 target_uri = g_vfs_backend_dav_uri_for_path (backend, destination, FALSE);
3045
3046 res = stat_location (backend, source_uri, &source_ft, &file_size, NULL, &error);
3047 if (!res)
3048 {
3049 g_vfs_job_failed_from_error (G_VFS_JOB (job), error);
3050 goto error;
3051 }
3052
3053 res = stat_location (backend, target_uri, &target_ft, NULL, NULL, &error);
3054 if (res)
3055 {
3056 if (flags & G_FILE_COPY_OVERWRITE)
3057 {
3058 if (target_ft == G_FILE_TYPE_DIRECTORY)
3059 {
3060 if (source_ft == G_FILE_TYPE_DIRECTORY)
3061 g_vfs_job_failed_literal (G_VFS_JOB(job),
3062 G_IO_ERROR,
3063 G_IO_ERROR_WOULD_MERGE,
3064 _("Can’t copy directory over directory"));
3065 else
3066 g_vfs_job_failed_literal (G_VFS_JOB(job),
3067 G_IO_ERROR,
3068 G_IO_ERROR_IS_DIRECTORY,
3069 _("File is directory"));
3070 goto error;
3071 }
3072 }
3073 else
3074 {
3075 g_vfs_job_failed_literal (G_VFS_JOB (job),
3076 G_IO_ERROR,
3077 G_IO_ERROR_EXISTS,
3078 _("Target file already exists"));
3079 goto error;
3080 }
3081 }
3082 else if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND))
3083 {
3084 g_vfs_job_failed_from_error (G_VFS_JOB (job), error);
3085 goto error;
3086 }
3087
3088 if (source_ft == G_FILE_TYPE_DIRECTORY)
3089 {
3090 g_vfs_job_failed_literal (G_VFS_JOB (job),
3091 G_IO_ERROR,
3092 G_IO_ERROR_WOULD_RECURSE,
3093 _("Can’t recursively copy directory"));
3094 goto error;
3095 }
3096
3097 msg = soup_message_new_from_uri (SOUP_METHOD_COPY, source_uri);
3098 message_add_destination_header (msg, target_uri);
3099 message_add_overwrite_header (msg, flags & G_FILE_COPY_OVERWRITE);
3100
3101 status = g_vfs_backend_dav_send_message (backend, msg);
3102
3103 /* See do_set_display_name () for the explanation of the PRECONDITION_FAILED
3104 * and IS_REDIRECTION handling below. */
3105
3106 if (SOUP_STATUS_IS_SUCCESSFUL (status))
3107 {
3108 if (progress_callback)
3109 progress_callback (file_size, file_size, progress_callback_data);
3110 g_vfs_job_succeeded (G_VFS_JOB (job));
3111 }
3112 else if (status == SOUP_STATUS_PRECONDITION_FAILED ||
3113 SOUP_STATUS_IS_REDIRECTION (status))
3114 g_vfs_job_failed (G_VFS_JOB (job), G_IO_ERROR,
3115 G_IO_ERROR_EXISTS,
3116 _("Target file already exists"));
3117 else
3118 http_job_failed (G_VFS_JOB (job), msg);
3119
3120 g_object_unref (msg);
3121
3122 error:
3123 g_clear_error (&error);
3124 soup_uri_free (source_uri);
3125 soup_uri_free (target_uri);
3126 }
3127
3128 #define CHUNK_SIZE 65536
3129
3130 /* Used to keep track of the state of reads in flight when the restarted signal
3131 * is received. */
3132 typedef enum {
3133 PUSH_READ_STATUS_NONE,
3134 PUSH_READ_STATUS_RESET,
3135 PUSH_READ_STATUS_DEFERRED,
3136 } PushReadStatus;
3137
3138 typedef struct {
3139 /* Job details */
3140 GVfsBackend *backend;
3141 GVfsJob *job;
3142 GVfsJobPush *op_job;
3143
3144 /* Local file */
3145 GInputStream *in;
3146 unsigned char *buf;
3147 goffset size;
3148 goffset n_read;
3149 PushReadStatus read_status;
3150
3151 /* Remote file */
3152 SoupURI *uri;
3153 SoupMessage *msg;
3154 goffset n_written;
3155 } PushHandle;
3156
3157 static void
3158 push_write_next_chunk (SoupMessage *msg, gpointer user_data);
3159
3160 static void
push_handle_free(PushHandle * handle)3161 push_handle_free (PushHandle *handle)
3162 {
3163 if (handle->in)
3164 {
3165 g_input_stream_close_async (handle->in, 0, NULL, NULL, NULL);
3166 g_object_unref (handle->in);
3167 }
3168 g_object_unref (handle->backend);
3169 g_object_unref (handle->job);
3170 soup_uri_free (handle->uri);
3171 g_slice_free (PushHandle, handle);
3172 }
3173
3174 static void
push_read_cb(GObject * source,GAsyncResult * res,gpointer user_data)3175 push_read_cb (GObject *source, GAsyncResult *res, gpointer user_data)
3176 {
3177 PushHandle *handle = user_data;
3178 GError *error = NULL;
3179 gssize n;
3180
3181 n = g_input_stream_read_finish (handle->in, res, &error);
3182
3183 /* Ignore this read if we've subsequently been restarted. */
3184 if (handle->read_status != PUSH_READ_STATUS_NONE)
3185 {
3186 g_free (handle->buf);
3187 handle->buf = NULL;
3188
3189 /* Queue another read if we've been subsequently restarted and
3190 * push_write_next_chunk () was called in the meantime. */
3191 if (handle->read_status == PUSH_READ_STATUS_DEFERRED)
3192 push_write_next_chunk (handle->msg, handle);
3193
3194 return;
3195 }
3196
3197 if (n > 0)
3198 {
3199 soup_message_body_append_take (handle->msg->request_body, handle->buf, n);
3200 handle->buf = NULL;
3201 handle->n_read += n;
3202 soup_session_unpause_message (G_VFS_BACKEND_HTTP (handle->backend)->session,
3203 handle->msg);
3204 }
3205 else if (n == 0)
3206 {
3207 g_free (handle->buf);
3208 handle->buf = NULL;
3209
3210 if (handle->n_read != handle->size)
3211 {
3212 g_vfs_job_failed_literal (handle->job,
3213 G_IO_ERROR,
3214 G_IO_ERROR_FAILED,
3215 _("File length changed during transfer"));
3216
3217 soup_session_cancel_message (G_VFS_BACKEND_HTTP (handle->backend)->session,
3218 handle->msg,
3219 SOUP_STATUS_CANCELLED);
3220 }
3221 }
3222 else
3223 {
3224 g_free (handle->buf);
3225 handle->buf = NULL;
3226 g_vfs_job_failed_from_error (handle->job, error);
3227 g_error_free (error);
3228 soup_session_cancel_message (G_VFS_BACKEND_HTTP (handle->backend)->session,
3229 handle->msg,
3230 SOUP_STATUS_CANCELLED);
3231 }
3232 }
3233
3234 static void
push_write_next_chunk(SoupMessage * msg,gpointer user_data)3235 push_write_next_chunk (SoupMessage *msg, gpointer user_data)
3236 {
3237 PushHandle *handle = user_data;
3238
3239 /* If we've been restarted, seek to the beginning of the file. */
3240 if (handle->read_status == PUSH_READ_STATUS_RESET)
3241 {
3242 GError *error = NULL;
3243
3244 /* We've been restarted but there's still a read in flight, so defer. */
3245 if (handle->buf)
3246 {
3247 handle->read_status = PUSH_READ_STATUS_DEFERRED;
3248 return;
3249 }
3250
3251 handle->n_read = 0;
3252 handle->n_written = 0;
3253 handle->read_status = PUSH_READ_STATUS_NONE;
3254
3255 if (!g_seekable_seek (G_SEEKABLE (handle->in),
3256 0, G_SEEK_SET,
3257 handle->job->cancellable, &error))
3258 {
3259 g_vfs_job_failed_from_error (handle->job, error);
3260 g_error_free (error);
3261 soup_session_cancel_message (G_VFS_BACKEND_HTTP (handle->backend)->session,
3262 handle->msg,
3263 SOUP_STATUS_CANCELLED);
3264 return;
3265 }
3266 }
3267
3268 handle->buf = g_malloc (CHUNK_SIZE);
3269 g_input_stream_read_async (handle->in,
3270 handle->buf, CHUNK_SIZE,
3271 0, handle->job->cancellable,
3272 push_read_cb, handle);
3273 }
3274
3275 static void
push_setup_message(PushHandle * handle)3276 push_setup_message (PushHandle *handle)
3277 {
3278 soup_message_set_flags (handle->msg, SOUP_MESSAGE_CAN_REBUILD);
3279 soup_message_body_set_accumulate (handle->msg->request_body, FALSE);
3280 message_add_overwrite_header (handle->msg,
3281 handle->op_job->flags & G_FILE_COPY_OVERWRITE);
3282 soup_message_headers_set_encoding (handle->msg->request_headers,
3283 SOUP_ENCODING_CONTENT_LENGTH);
3284 soup_message_headers_set_content_length (handle->msg->request_headers,
3285 handle->size);
3286 }
3287
3288 static void
push_restarted(SoupMessage * msg,gpointer user_data)3289 push_restarted (SoupMessage *msg, gpointer user_data)
3290 {
3291 PushHandle *handle = user_data;
3292
3293 handle->read_status = PUSH_READ_STATUS_RESET;
3294 msg->method = SOUP_METHOD_PUT;
3295 push_setup_message (handle);
3296 }
3297
3298 static void
push_wrote_body_data(SoupMessage * msg,SoupBuffer * chunk,gpointer user_data)3299 push_wrote_body_data (SoupMessage *msg, SoupBuffer *chunk, gpointer user_data)
3300 {
3301 PushHandle *handle = user_data;
3302
3303 handle->n_written += chunk->length;
3304 g_vfs_job_progress_callback (handle->n_written, handle->size, handle->job);
3305 }
3306
3307 static void
push_done(SoupSession * session,SoupMessage * msg,gpointer user_data)3308 push_done (SoupSession *session, SoupMessage *msg, gpointer user_data)
3309 {
3310 PushHandle *handle = user_data;
3311
3312 if (g_vfs_job_is_finished (handle->job))
3313 ; /* We got an error so we finished the job and cancelled msg. */
3314 else if (!SOUP_STATUS_IS_SUCCESSFUL (msg->status_code))
3315 http_job_failed (handle->job, msg);
3316 else
3317 {
3318 if (handle->op_job->remove_source)
3319 g_unlink (handle->op_job->local_path);
3320
3321 g_vfs_job_succeeded (handle->job);
3322 }
3323
3324 push_handle_free (handle);
3325 }
3326
3327 static void
push_stat_dest_cb(SoupSession * session,SoupMessage * msg,gpointer user_data)3328 push_stat_dest_cb (SoupSession *session, SoupMessage *msg, gpointer user_data)
3329 {
3330 PushHandle *handle = user_data;
3331 GFileType type;
3332
3333 if (stat_location_finish (msg, &type, NULL, NULL))
3334 {
3335 if (!(handle->op_job->flags & G_FILE_COPY_OVERWRITE))
3336 {
3337 g_vfs_job_failed (handle->job,
3338 G_IO_ERROR,
3339 G_IO_ERROR_EXISTS,
3340 _("Target file already exists"));
3341 push_handle_free (handle);
3342 return;
3343 }
3344 if (type == G_FILE_TYPE_DIRECTORY)
3345 {
3346 g_vfs_job_failed (handle->job,
3347 G_IO_ERROR,
3348 G_IO_ERROR_IS_DIRECTORY,
3349 _("File is directory"));
3350 push_handle_free (handle);
3351 return;
3352 }
3353 }
3354
3355 handle->msg = soup_message_new_from_uri (SOUP_METHOD_PUT, handle->uri);
3356 push_setup_message (handle);
3357
3358 g_signal_connect (handle->msg, "restarted",
3359 G_CALLBACK (push_restarted), handle);
3360 g_signal_connect (handle->msg, "wrote_headers",
3361 G_CALLBACK (push_write_next_chunk), handle);
3362 g_signal_connect (handle->msg, "wrote_chunk",
3363 G_CALLBACK (push_write_next_chunk), handle);
3364 g_signal_connect (handle->msg, "wrote-body-data",
3365 G_CALLBACK (push_wrote_body_data), handle);
3366
3367 g_vfs_backend_dav_queue_message (handle->backend, handle->msg,
3368 push_done, handle);
3369 }
3370
3371 static void
push_source_fstat_cb(GObject * source,GAsyncResult * res,gpointer user_data)3372 push_source_fstat_cb (GObject *source, GAsyncResult *res, gpointer user_data)
3373 {
3374 GFileInputStream *fin = G_FILE_INPUT_STREAM (source);
3375 PushHandle *handle = user_data;
3376 GError *error = NULL;
3377 GFileInfo *info;
3378
3379 info = g_file_input_stream_query_info_finish (fin, res, &error);
3380 if (info)
3381 {
3382 SoupMessage *msg;
3383
3384 handle->size = g_file_info_get_size (info);
3385 g_object_unref (info);
3386
3387 msg = stat_location_begin (handle->uri, FALSE);
3388 g_vfs_backend_dav_queue_message (handle->backend, msg,
3389 push_stat_dest_cb, handle);
3390 }
3391 else
3392 {
3393 g_vfs_job_failed_from_error (handle->job, error);
3394 g_error_free (error);
3395 push_handle_free (handle);
3396 }
3397 }
3398
3399 static void
push_source_open_cb(GObject * source,GAsyncResult * res,gpointer user_data)3400 push_source_open_cb (GObject *source, GAsyncResult *res, gpointer user_data)
3401 {
3402 GFile *source_file = G_FILE (source);
3403 PushHandle *handle = user_data;
3404 GError *error = NULL;
3405 GFileInputStream *fin;
3406
3407 fin = g_file_read_finish (source_file, res, &error);
3408 if (fin)
3409 {
3410 handle->in = G_INPUT_STREAM (fin);
3411
3412 g_file_input_stream_query_info_async (fin,
3413 G_FILE_ATTRIBUTE_STANDARD_SIZE,
3414 0, handle->job->cancellable,
3415 push_source_fstat_cb, handle);
3416 }
3417 else
3418 {
3419 if (error->domain == G_IO_ERROR && error->code == G_IO_ERROR_IS_DIRECTORY)
3420 {
3421 /* Fall back to default implementation to improve the error message */
3422 g_vfs_job_failed (handle->job, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED,
3423 _("Operation not supported"));
3424 }
3425 else
3426 g_vfs_job_failed_from_error (handle->job, error);
3427
3428 g_error_free (error);
3429 push_handle_free (handle);
3430 }
3431 }
3432
3433 static void
push_source_lstat_cb(GObject * source,GAsyncResult * res,gpointer user_data)3434 push_source_lstat_cb (GObject *source, GAsyncResult *res, gpointer user_data)
3435 {
3436 GFile *source_file = G_FILE (source);
3437 PushHandle *handle = user_data;
3438 GError *error = NULL;
3439 GFileInfo *info;
3440
3441 info = g_file_query_info_finish (source_file, res, &error);
3442 if (!info)
3443 {
3444 g_vfs_job_failed_from_error (handle->job, error);
3445 g_error_free (error);
3446 push_handle_free (handle);
3447 return;
3448 }
3449
3450 if ((handle->op_job->flags & G_FILE_COPY_NOFOLLOW_SYMLINKS) &&
3451 g_file_info_get_file_type (info) == G_FILE_TYPE_SYMBOLIC_LINK)
3452 {
3453 /* Fall back to default implementation to copy symlink */
3454 g_vfs_job_failed (handle->job, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED,
3455 _("Operation not supported"));
3456 push_handle_free (handle);
3457 g_object_unref (info);
3458 return;
3459 }
3460
3461 g_file_read_async (source_file,
3462 0, handle->job->cancellable,
3463 push_source_open_cb, handle);
3464 g_object_unref (info);
3465 }
3466
3467 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)3468 try_push (GVfsBackend *backend,
3469 GVfsJobPush *job,
3470 const char *destination,
3471 const char *local_path,
3472 GFileCopyFlags flags,
3473 gboolean remove_source,
3474 GFileProgressCallback progress_callback,
3475 gpointer progress_callback_data)
3476 {
3477 GFile *source;
3478 PushHandle *handle;
3479
3480 if (remove_source && (flags & G_FILE_COPY_NO_FALLBACK_FOR_MOVE))
3481 {
3482 g_vfs_job_failed (G_VFS_JOB (job), G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED,
3483 _("Operation not supported"));
3484 return TRUE;
3485 }
3486
3487 handle = g_slice_new0 (PushHandle);
3488 handle->backend = g_object_ref (backend);
3489 handle->job = g_object_ref (G_VFS_JOB (job));
3490 handle->op_job = job;
3491 handle->uri = g_vfs_backend_dav_uri_for_path (backend, destination, FALSE);
3492
3493 source = g_file_new_for_path (local_path);
3494 g_file_query_info_async (source,
3495 G_FILE_ATTRIBUTE_STANDARD_TYPE,
3496 G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
3497 0, handle->job->cancellable,
3498 push_source_lstat_cb, handle);
3499 g_object_unref (source);
3500
3501 return TRUE;
3502 }
3503
3504 /* ************************************************************************* */
3505 /* */
3506 static void
g_vfs_backend_dav_class_init(GVfsBackendDavClass * klass)3507 g_vfs_backend_dav_class_init (GVfsBackendDavClass *klass)
3508 {
3509 GObjectClass *gobject_class;
3510 GVfsBackendClass *backend_class;
3511
3512 gobject_class = G_OBJECT_CLASS (klass);
3513 gobject_class->finalize = g_vfs_backend_dav_finalize;
3514
3515 backend_class = G_VFS_BACKEND_CLASS (klass);
3516
3517 backend_class->try_mount = NULL;
3518 backend_class->mount = do_mount;
3519 backend_class->try_query_info = NULL;
3520 backend_class->query_info = do_query_info;
3521 backend_class->try_query_fs_info = NULL;
3522 backend_class->query_fs_info = do_query_fs_info;
3523 backend_class->enumerate = do_enumerate;
3524 backend_class->try_open_for_read = try_open_for_read;
3525 backend_class->try_create = try_create;
3526 backend_class->try_replace = try_replace;
3527 backend_class->try_write = try_write;
3528 backend_class->seek_on_write = do_seek_on_write;
3529 backend_class->truncate = do_truncate;
3530 backend_class->try_close_write = try_close_write;
3531 backend_class->make_directory = do_make_directory;
3532 backend_class->delete = do_delete;
3533 backend_class->set_display_name = do_set_display_name;
3534 backend_class->move = do_move;
3535 backend_class->copy = do_copy;
3536 backend_class->try_push = try_push;
3537 }
3538