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