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