1 /*
2    sitecopy WebDAV protocol driver module
3    Copyright (C) 2000-2008, Joe Orton <joe@manyfish.co.uk>
4 
5    This program is free software; you can redistribute it and/or modify
6    it under the terms of the GNU General Public License as published by
7    the Free Software Foundation; either version 2 of the License, or
8    (at your option) any later version.
9 
10    This program 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
13    GNU General Public License for more details.
14 
15    You should have received a copy of the GNU General Public License
16    along with this program; if not, write to the Free Software
17    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
18 
19 */
20 
21 #include "config.h"
22 
23 #include <sys/types.h>
24 
25 #include <sys/stat.h> /* For S_IXUSR */
26 
27 #ifdef HAVE_STRING_H
28 #include <string.h>
29 #endif
30 #ifdef HAVE_STDLIB_H
31 #include <stdlib.h>
32 #endif
33 #ifdef HAVE_FCNTL_H
34 #include <fcntl.h>
35 #endif
36 #ifdef HAVE_UNISTD_H
37 #include <unistd.h>
38 #endif
39 
40 #include <errno.h>
41 
42 #include "common.h" /* for strerror */
43 
44 #include <ne_request.h>
45 #include <ne_basic.h>
46 #include <ne_basic.h>
47 #include <ne_props.h>
48 #include <ne_alloc.h>
49 #include <ne_uri.h>
50 #include <ne_auth.h>
51 #include <ne_dates.h>
52 #include <ne_socket.h>
53 
54 #include "protocol.h"
55 #include "frontend.h"
56 #include "i18n.h"
57 #include "common.h"
58 
59 struct fetch_context {
60     struct proto_file **files;
61     struct proto_file *tail;
62     const char *root;
63 };
64 
65 #define ENABLE_PROGRESS do { ne_set_progress(sess, site_sock_progress_cb, NULL); } while (0)
66 
67 #if NE_VERSION_MAJOR == 0 && NE_VERSION_MINOR > 26
68 #define DISABLE_PROGRESS do { ne_set_notifier(sess, NULL, NULL); } while (0)
69 #else
70 #define DISABLE_PROGRESS do { ne_set_progress(sess, NULL, NULL); } while (0)
71 #endif
72 
73 /* TODO:
74  * not really sure whether we should be using an enum here... what
75  * should the client do with resourcetypes it doesn't understand?
76  * ignore them, or presume they have the same semantics as "normal"
77  * resources. I really don't know.
78  */
79 struct private {
80     int iscollection;
81 };
82 
83 #define ELM_resourcetype (NE_PROPS_STATE_TOP + 1)
84 #define ELM_collection (NE_PROPS_STATE_TOP + 2)
85 
86 /* The element definitions for the complex prop handler. */
87 static const struct ne_xml_idmap fetch_elms[] = {
88     { "DAV:", "resourcetype", ELM_resourcetype },
89     { "DAV:", "collection", ELM_collection },
90 };
91 
92 static const ne_propname props[] = {
93     { "DAV:", "getcontentlength" },
94     { "DAV:", "getlastmodified" },
95     { "http://apache.org/dav/props/", "executable" },
96     { "DAV:", "resourcetype" },
97     { NULL }
98 };
99 
100 /* Set session error string to 'msg: strerror(errnum)'. */
syserr(ne_session * sess,const char * msg,int errnum)101 static void syserr(ne_session *sess, const char *msg, int errnum)
102 {
103     char err[256];
104     ne_set_error(sess, "%s: %s", msg, ne_strerror(errnum, err, sizeof err));
105 }
106 
get_server_port(struct site * site)107 static int get_server_port(struct site *site)
108 {
109     return site->http_secure ? 443 : 80;
110 }
111 
get_proxy_port(struct site * site)112 static int get_proxy_port(struct site *site)
113 {
114     return 8080;
115 }
116 
auth_common(void * userdata,fe_login_context ctx,const char * realm,int attempt,char * username,char * password)117 static int auth_common(void *userdata, fe_login_context ctx,
118 		       const char *realm, int attempt,
119 		       char *username, char *password)
120 {
121     struct site_host *host = userdata;
122     if (host->username && host->password) {
123 	strcpy(username, host->username);
124 	strcpy(password, host->password);
125 	return attempt;
126     } else {
127 	return fe_login(ctx, realm, host->hostname, username, password);
128     }
129 }
130 
131 static int
server_auth_cb(void * userdata,const char * realm,int attempt,char * username,char * password)132 server_auth_cb(void *userdata, const char *realm, int attempt,
133 	       char *username, char *password)
134 {
135     return auth_common(userdata, fe_login_server, realm, attempt,
136 		       username, password);
137 }
138 
139 static int
proxy_auth_cb(void * userdata,const char * realm,int attempt,char * username,char * password)140 proxy_auth_cb(void *userdata, const char *realm, int attempt,
141 	      char *username, char *password)
142 {
143     return auth_common(userdata, fe_login_proxy, realm, attempt,
144 		       username, password);
145 }
146 
147 #if NE_VERSION_MINOR < 27
notify_cb(void * userdata,ne_conn_status status,const char * info)148 static void notify_cb(void *userdata, ne_conn_status status, const char *info)
149 {
150 
151 #define MAP(a) case ne_conn_##a: fe_connection(fe_##a, info); break
152 
153     switch (status) {
154 	MAP(namelookup);
155 	MAP(connecting);
156 	MAP(connected);
157     default:
158 	break;
159     }
160 
161 #undef MAP
162 }
163 #else
notify_status(void * userdata,ne_session_status status,const ne_session_status_info * info)164 static void notify_status(void *userdata, ne_session_status status,
165                           const ne_session_status_info *info)
166 {
167     switch (status) {
168     case ne_status_lookup:
169         fe_connection(fe_namelookup, info->lu.hostname);
170         break;
171     case ne_status_connecting:
172         fe_connection(fe_connecting, NULL);
173         break;
174     case ne_status_connected:
175         fe_connection(fe_connected, NULL);
176         break;
177     default:
178         break;
179     }
180 }
181 #endif
182 
h2s(ne_session * sess,int errcode)183 static int h2s(ne_session *sess, int errcode)
184 {
185     switch (errcode) {
186     case NE_OK:
187 	return SITE_OK;
188     case NE_AUTH:
189 	return SITE_AUTH;
190     case NE_PROXYAUTH:
191 	return SITE_PROXYAUTH;
192     case NE_FAILED:
193 	return SITE_FAILED;
194     case NE_CONNECT:
195 	return SITE_CONNECT;
196     case NE_LOOKUP:
197 	return SITE_LOOKUP;
198     case NE_TIMEOUT:
199 	ne_set_error(sess, _("The connection timed out."));
200 	return SITE_ERRORS;
201     case NE_ERROR:
202     default:
203 	return SITE_ERRORS;
204     }
205 }
206 
207 /* Callback invoked when SSL server cert verification fails. */
verify_certificate(void * userdata,int failures,const ne_ssl_certificate * cert)208 static int verify_certificate(void *userdata, int failures,
209                               const ne_ssl_certificate *cert)
210 {
211     struct site *site = userdata;
212 
213     /* If the server cert has not changed since the user accepted it,
214      * trust the cert, unless it has expired, in which case the user
215      * should get a warning. */
216     if (site->server_cert
217         && ne_ssl_cert_cmp(cert, site->server_cert) == 0
218         && (failures & NE_SSL_EXPIRED) == 0) {
219         return 0;
220     }
221 
222     if (fe_accept_cert(cert, failures)) {
223         /* Not accepted by user => fail verification. */
224         return -1;
225     }
226 
227     if (ne_ssl_cert_write(cert, site->certfile)) {
228         fe_warning(_("Could not write SSL certificate"),
229                    NULL, site->certfile);
230     }
231 
232     return 0;
233 }
234 
init(void ** session,struct site * site)235 static int init(void **session, struct site *site)
236 {
237     ne_session *sess;
238     ne_server_capabilities caps = {0};
239     int ret;
240     char *root;
241 
242     sess = ne_session_create(site->http_secure?"https":"http",
243 			     site->server.hostname, site->server.port);
244 
245     *session = sess;
246 
247     if (site->http_secure && !ne_has_support(NE_FEATURE_SSL)) {
248 	ne_set_error(sess, _("SSL support has not be compiled in."));
249 	return SITE_FAILED;
250     }
251 
252     if (site->http_secure) {
253         if (access(site->certfile, R_OK) == 0) {
254             site->server_cert = ne_ssl_cert_read(site->certfile);
255             if (site->server_cert == NULL) {
256                 ne_set_error(sess, _("Could not load certificate `%s'."),
257                              site->certfile);
258                 return SITE_FAILED;
259             }
260         }
261         ne_ssl_set_verify(sess, verify_certificate, site);
262     }
263 
264 #if NE_VERSION_MINOR < 27
265     ne_set_status(sess, notify_cb, NULL);
266 #else
267     ne_set_notifier(sess, notify_status, NULL);
268 #endif
269 
270     if (site->http_limit) {
271 #if NE_VERSION_MINOR > 25
272         ne_set_session_flag(sess, NE_SESSFLAG_PERSIST, 0);
273 #else
274         ne_set_persist(sess, 0);
275 #endif
276     }
277 
278     /* Note, this won't differentiate between xsitecopy and
279      * sitecopy... maybe we should put a comment in as well. */
280     ne_set_useragent(sess, PACKAGE_NAME "/" PACKAGE_VERSION);
281 
282     if (site->proxy.hostname) {
283 	ne_set_proxy_auth(sess, proxy_auth_cb, &site->proxy);
284 	ne_session_proxy(sess, site->proxy.hostname, site->proxy.port);
285     }
286 
287     ne_set_server_auth(sess, server_auth_cb, &site->server);
288 
289     if (site->http_secure && site->client_cert) {
290         ne_ssl_client_cert *cc;
291 
292         cc = ne_ssl_clicert_read(site->client_cert);
293         if (!cc) {
294             ne_set_error(sess, _("Could not read SSL client certificate '%s'."),
295                          site->client_cert);
296             return SITE_FAILED;
297         }
298 
299         if (ne_ssl_clicert_encrypted(cc)) {
300             char password[FE_LBUFSIZ];
301 
302             if (fe_decrypt_clicert(cc, password)) {
303                 return SITE_FAILED;
304             }
305 
306             if (ne_ssl_clicert_decrypt(cc, password) != 0) {
307                 ne_set_error(sess, _("Could not decrypt SSL client "
308                                      "certificate '%s'."), site->client_cert);
309                 return SITE_FAILED;
310             }
311         }
312 
313         ne_ssl_set_clicert(sess, cc);
314 
315         ne_ssl_clicert_free(cc);
316     }
317 
318     if (site->http_tolerant) {
319 	/* Skip the OPTIONS, since we ignore failure anyway. */
320 	return SITE_OK;
321     }
322 
323     root = ne_path_escape(site->remote_root);
324     ret = ne_options(sess, root, &caps);
325     ne_free(root);
326     if (ret == NE_OK) {
327 	if (!caps.dav_class1) {
328 	    ne_set_error(sess,
329 			    _("The server does not appear to be a WebDAV server."));
330 	    return SITE_FAILED;
331 	} else if (site->perms != sitep_ignore && !caps.dav_executable) {
332 	    /* Need to set permissions, but the server can't do that */
333 	    ne_set_error(sess,
334 			    _("The server does not support the executable live property."));
335 	    return SITE_FAILED;
336 	}
337     } else {
338         ret = h2s(sess, ret);
339         if (ret == SITE_ERRORS)
340             ret = SITE_FAILED;
341         return ret;
342     }
343 
344 
345     return SITE_OK;
346 }
347 
finish(void * session)348 static void finish(void *session)
349 {
350     ne_session *sess = session;
351     ne_session_destroy(sess);
352 }
353 
file_move(void * session,const char * from,const char * to)354 static int file_move(void *session, const char *from, const char *to)
355 {
356     ne_session *sess = session;
357     char *efrom, *eto;
358     int ret;
359 
360     efrom = ne_path_escape(from);
361     eto = ne_path_escape(to);
362 
363     /* Always overwrite destination. */
364     ret = ne_move(sess, 1, efrom, eto);
365 
366     free(efrom);
367     free(eto);
368 
369     return h2s(sess, ret);
370 }
371 
file_upload(void * session,const char * local,const char * remote,int ascii)372 static int file_upload(void *session, const char *local, const char *remote,
373 		       int ascii)
374 {
375     int ret, fd = open(local, O_RDONLY | OPEN_BINARY_FLAGS);
376     ne_session *sess = session;
377     char *eremote;
378 
379     if (fd < 0) {
380 	syserr(sess, _("Could not open file"), errno);
381 	return SITE_ERRORS;
382     }
383 
384     eremote = ne_path_escape(remote);
385     ENABLE_PROGRESS;
386     ret = ne_put(sess, eremote, fd);
387     DISABLE_PROGRESS;
388     free(eremote);
389 
390     (void) close(fd);
391 
392     return h2s(sess, ret);
393 }
394 
395 /* conditional PUT using If-Unmodified-Since. */
put_if_unmodified(ne_session * sess,const char * uri,int fd,time_t since)396 static int put_if_unmodified(ne_session *sess, const char *uri, int fd,
397                              time_t since)
398 {
399     ne_request *req = ne_request_create(sess, "PUT", uri);
400     char *date = ne_rfc1123_date(since);
401     int ret;
402 
403     /* Add in the conditional header */
404     ne_add_request_header(req, "If-Unmodified-Since", date);
405     ne_free(date);
406 
407 #if NE_VERSION_MINOR == 24
408     ne_set_request_body_fd(req, fd);
409 #else
410     {
411         struct stat st;
412 
413         if (fstat(fd, &st) < 0) {
414             int errnum = errno;
415             ne_set_error(sess, _("Could not stat file: %s"), strerror(errnum));
416             return NE_ERROR;
417         }
418 
419         ne_set_request_body_fd(req, fd, 0, st.st_size);
420     }
421 #endif
422 
423     ret = ne_request_dispatch(req);
424 
425     if (ret == NE_OK) {
426 	if (ne_get_status(req)->code == 412) {
427 	    ret = NE_FAILED;
428 	} else if (ne_get_status(req)->klass != 2) {
429 	    ret = NE_ERROR;
430 	}
431     }
432 
433     ne_request_destroy(req);
434 
435     return ret;
436 }
437 
438 static int
file_upload_cond(void * session,const char * local,const char * remote,int ascii,time_t t)439 file_upload_cond(void *session, const char *local, const char *remote,
440 		 int ascii, time_t t)
441 {
442     ne_session *sess = session;
443     int ret, fd = open(local, O_RDONLY | OPEN_BINARY_FLAGS);
444     char *eremote;
445 
446     if (fd < 0) {
447 	syserr(sess, _("Could not open file"), errno);
448 	return SITE_ERRORS;
449     }
450 
451     eremote = ne_path_escape(remote);
452     ENABLE_PROGRESS;
453     ret = h2s(sess, put_if_unmodified(sess, eremote, fd, t));
454     DISABLE_PROGRESS;
455     free(eremote);
456 
457     (void) close(fd);
458 
459     return ret;
460 }
461 
file_get_modtime(void * session,const char * remote,time_t * modtime)462 static int file_get_modtime(void *session, const char *remote, time_t *modtime)
463 {
464     ne_session *sess = session;
465     int ret;
466     char *eremote;
467 
468     eremote = ne_path_escape(remote);
469     ret = ne_getmodtime(sess,eremote,modtime);
470     free(eremote);
471 
472     return h2s(sess, ret);
473 }
474 
file_download(void * session,const char * local,const char * remote,int ascii)475 static int file_download(void *session, const char *local, const char *remote,
476 			 int ascii)
477 {
478     ne_session *sess = session;
479     int ret, fd = open(local,
480 		       O_TRUNC | O_CREAT | O_WRONLY | OPEN_BINARY_FLAGS, 0644);
481     char *eremote;
482 
483     if (fd < 0) {
484 	syserr(sess, _("Could not open file"), errno);
485 	return SITE_ERRORS;
486     }
487 
488     eremote = ne_path_escape(remote);
489     ENABLE_PROGRESS;
490     ret = h2s(sess, ne_get(sess, eremote, fd));
491     DISABLE_PROGRESS;
492     free(eremote);
493 
494     if (close(fd)) {
495 	ret = SITE_ERRORS;
496     }
497 
498     return ret;
499 }
500 
file_read(void * session,const char * remote,ne_block_reader reader,void * userdata)501 static int file_read(void *session, const char *remote,
502 		     ne_block_reader reader, void *userdata)
503 {
504     ne_session *sess = session;
505     int ret;
506     char *eremote;
507     ssize_t bytes;
508     ne_request *req;
509 
510     eremote = ne_path_escape(remote);
511     req = ne_request_create(sess, "GET", eremote);
512     ENABLE_PROGRESS;
513     do {
514 	char buf[BUFSIZ];
515 	ret = ne_begin_request(req);
516 	if (ret != NE_OK) break;
517 	while ((bytes = ne_read_response_block(req, buf, sizeof buf)) > 0)
518 	    reader(userdata, buf, bytes);
519 	if (bytes < 0)
520 	    ret = NE_ERROR;
521 	else
522 	    ret = ne_end_request(req);
523     } while (ret == NE_RETRY);
524     DISABLE_PROGRESS;
525     free(eremote);
526 
527     return h2s(sess, ret);
528 }
529 
file_delete(void * session,const char * remote)530 static int file_delete(void *session, const char *remote)
531 {
532     ne_session *sess = session;
533     char *eremote = ne_path_escape(remote);
534     int ret = ne_delete(sess, eremote);
535 
536     free(eremote);
537     return h2s(sess, ret);
538 }
539 
file_chmod(void * session,const char * remote,mode_t mode)540 static int file_chmod(void *session, const char *remote, mode_t mode)
541 {
542     ne_session *sess = session;
543     static const ne_propname execprop =
544     { "http://apache.org/dav/props/", "executable" };
545     /* Use a single operation; set the executable property to... */
546     ne_proppatch_operation ops[] = {
547 	{ &execprop, ne_propset, NULL }, { NULL }
548     };
549     char *eremote = ne_path_escape(remote);
550     int ret;
551 
552     /* True or false, depending... */
553     if (mode & S_IXUSR) {
554 	ops[0].value = "T";
555     } else {
556 	ops[0].value = "F";
557     }
558 
559     ret = ne_proppatch(sess, eremote, ops);
560     free(eremote);
561 
562     return h2s(sess, ret);
563 }
564 
565 /* Returns escaped path string for a directory. */
coll_escape(const char * dirname)566 static char *coll_escape(const char *dirname)
567 {
568     char *ret = ne_path_escape(dirname);
569     if (!ne_path_has_trailing_slash(ret)) {
570 	ret = ne_realloc(ret, strlen(ret) + 2);
571 	strcat(ret, "/");
572     }
573     return ret;
574 }
575 
dir_create(void * session,const char * dirname)576 static int dir_create(void *session, const char *dirname)
577 {
578     ne_session *sess = session;
579     char *edirname = coll_escape(dirname);
580     int ret = ne_mkcol(sess, edirname);
581     free(edirname);
582     return h2s(sess, ret);
583 }
584 
585 /* TODO: check whether it is empty first */
dir_remove(void * session,const char * dirname)586 static int dir_remove(void *session, const char *dirname)
587 {
588     ne_session *sess = session;
589     char *edirname = coll_escape(dirname);
590     int ret = ne_delete(sess, edirname);
591     free(edirname);
592     return h2s(sess, ret);
593 }
594 
595 /* Insert the file in the list in the appropriate position (keeping it
596  * sorted). */
insert_file(struct fetch_context * ctx,struct proto_file * file)597 static void insert_file(struct fetch_context *ctx, struct proto_file *file)
598 {
599     if (ctx->tail) {
600         ctx->tail->next = file;
601     } else {
602         (*ctx->files) = file;
603     }
604     ctx->tail = file;
605     file->next = NULL;
606 }
607 
608 #if NE_VERSION_MINOR > 25
pfind_results(void * userdata,const ne_uri * uri,const ne_prop_result_set * set)609 static void pfind_results(void *userdata, const ne_uri *uri,
610 			  const ne_prop_result_set *set)
611 #else
612 static void pfind_results(void *userdata, const char *href,
613 			  const ne_prop_result_set *set)
614 #endif
615 {
616     struct fetch_context *ctx = userdata;
617     struct private *private = ne_propset_private(set);
618     const char *clength = NULL, *modtime = NULL, *isexec = NULL;
619     struct proto_file *file;
620     int iscoll;
621     char *uhref;
622 
623     iscoll = private->iscollection;
624 
625 #if NE_VERSION_MINOR < 26
626     /* For >= 0.26, this is handled by the destroy_private
627      * callback. */
628     ne_free(private);
629 #endif
630 
631 #if NE_VERSION_MINOR < 26
632     /* Strip down to the abspath segment */
633     if (strncmp(href, "http://", 7) == 0)
634 	href = strchr(href+7, '/');
635 
636     if (strncmp(href, "https://", 8) == 0)
637 	href = strchr(href+8, '/');
638 
639     if (href == NULL) {
640 	NE_DEBUG(NE_DBG_HTTP, "invalid!\n");
641 	return;
642     }
643 
644     uhref = ne_path_unescape(href);
645 #else
646     uhref = ne_path_unescape(uri->path);
647 
648 #endif
649 
650     NE_DEBUG(NE_DBG_HTTP, "URI: [%s]: ", uhref);
651 
652     if (!ne_path_childof(ctx->root, uhref)) {
653 	/* URI not a child of the root collection...  ignore this
654 	 * resource */
655 	NE_DEBUG(NE_DBG_HTTP, "not child of root collection!\n");
656 	return;
657     }
658 
659     NE_DEBUG(NE_DBG_HTTP, "okay.\n");
660 
661     if (!iscoll) {
662 	const ne_status *status = NULL;
663 
664 	clength = ne_propset_value(set, &props[0]);
665 	modtime = ne_propset_value(set, &props[1]);
666 	isexec = ne_propset_value(set, &props[2]);
667 
668 	if (clength == NULL)
669 	    status = ne_propset_status(set, &props[0]);
670 	if (modtime == NULL)
671 	    status = ne_propset_status(set, &props[1]);
672 
673 	if (clength == NULL || modtime == NULL) {
674 	    fe_warning(_("Could not access resource"), uhref,
675 		       status?status->reason_phrase:NULL);
676 	    return;
677 	}
678 
679     }
680 
681     file = ne_calloc(sizeof(struct proto_file));
682     file->filename = ne_strdup(uhref+strlen(ctx->root));
683 
684     if (iscoll) {
685 	file->type = proto_dir;
686 
687 	/* Strip the trailing slash if it has one. */
688 	if (ne_path_has_trailing_slash(file->filename)) {
689 	    file->filename[strlen(file->filename) - 1] = '\0';
690 	}
691 
692     } else {
693 	file->type = proto_file;
694 	file->size = atoi(clength);
695 	file->modtime = modtime?ne_httpdate_parse(modtime):0;
696 	if (isexec && strcasecmp(isexec, "T") == 0) {
697 	    file->mode = 0755;
698 	} else {
699 	    file->mode = 0644;
700 	}
701     }
702 
703     /* Insert the file into the files list. */
704     insert_file(ctx, file);
705 }
706 
start_element(void * userdata,int parent,const char * nspace,const char * name,const char ** atts)707 static int start_element(void *userdata, int parent,
708                          const char *nspace, const char *name,
709                          const char **atts)
710 {
711     ne_propfind_handler *handler = userdata;
712     struct private *priv = ne_propfind_current_private(handler);
713     int state;
714 
715     state = ne_xml_mapid(fetch_elms, NE_XML_MAPLEN(fetch_elms), nspace, name);
716 
717     if (parent == NE_207_STATE_PROP && state == ELM_resourcetype)
718         return ELM_resourcetype;
719 
720     if (parent == ELM_resourcetype && state == ELM_collection)
721         priv->iscollection = 1;
722 
723     return NE_XML_DECLINE;
724 }
725 
726 /* Creates the private structure. */
727 #if NE_VERSION_MINOR > 25
create_private(void * userdata,const ne_uri * uri)728 static void *create_private(void *userdata, const ne_uri *uri)
729 #else
730 static void *create_private(void *userdata, const char *uri)
731 #endif
732 {
733     return ne_calloc(sizeof(struct private));
734 }
735 
736 #if NE_VERSION_MINOR > 25
destroy_private(void * userdata,void * private)737 static void destroy_private(void *userdata, void *private)
738 {
739     struct private *priv = private;
740 
741     ne_free(priv);
742 }
743 #endif
744 
745 /* TODO: optimize: only ask for lastmod + executable when we really
746  * need them: it does waste bandwidth and time to ask for executable
747  * when we don't want it, since it forces a 404 propstat for each
748  * non-collection resource if it is not defined.  */
fetch_list(void * session,const char * dirname,int need_modtimes,struct proto_file ** files)749 static int fetch_list(void *session, const char *dirname, int need_modtimes,
750                       struct proto_file **files)
751 {
752     ne_session *sess = session;
753     int ret;
754     struct fetch_context ctx;
755     ne_propfind_handler *ph;
756     char *edirname = ne_path_escape(dirname);
757 
758     ctx.root = dirname;
759     ctx.files = files;
760     ctx.tail = NULL;
761     ph = ne_propfind_create(sess, edirname, NE_DEPTH_ONE);
762 
763     /* The complex props. */
764     ne_propfind_set_private(ph, create_private,
765 #if NE_VERSION_MINOR > 25
766                             destroy_private,
767 #endif
768                             NULL);
769 
770     /* Register the handler for the complex props. */
771     ne_xml_push_handler(ne_propfind_get_parser(ph), start_element, NULL, NULL, ph);
772 
773     ret = ne_propfind_named(ph, props, pfind_results, &ctx);
774 
775     free(edirname);
776 
777     return h2s(sess,ret);
778 }
779 
unimp_link2(void * session,const char * l,const char * target)780 static int unimp_link2(void *session, const char *l, const char *target)
781 {
782     ne_session *sess = session;
783     ne_set_error(sess, "Operation not supported");
784     return SITE_UNSUPPORTED;
785 }
786 
unimp_link1(void * session,const char * l)787 static int unimp_link1(void *session, const char *l)
788 {
789     ne_session *sess = session;
790     ne_set_error(sess, "Operation not supported");
791     return SITE_UNSUPPORTED;
792 }
793 
794 
error(void * session)795 static const char *error(void *session)
796 {
797     ne_session *sess = session;
798     return ne_get_error(sess);
799 }
800 
801 /* The WebDAV protocol driver */
802 const struct proto_driver dav_driver = {
803     init,
804     finish,
805     file_move,
806     file_upload,
807     file_upload_cond,
808     file_get_modtime,
809     file_download,
810     file_read,
811     file_delete,
812     file_chmod,
813     dir_create,
814     dir_remove,
815     unimp_link2, /* create link */
816     unimp_link2, /* change link target */
817     unimp_link1, /* delete link */
818     fetch_list,
819     error,
820     get_server_port,
821     get_proxy_port,
822     "WebDAV"
823 };
824