src/server/webdav/operation.c

changeset 385
a1f4cb076d2f
parent 378
0344108db255
child 413
6afaebf003ea
equal deleted inserted replaced
210:21274e5950af 385:a1f4cb076d2f
1 /*
2 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
3 *
4 * Copyright 2019 Olaf Wintermann. All rights reserved.
5 *
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions are met:
8 *
9 * 1. Redistributions of source code must retain the above copyright
10 * notice, this list of conditions and the following disclaimer.
11 *
12 * 2. Redistributions in binary form must reproduce the above copyright
13 * notice, this list of conditions and the following disclaimer in the
14 * documentation and/or other materials provided with the distribution.
15 *
16 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
17 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
20 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
21 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
22 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
23 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
24 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
25 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
26 * POSSIBILITY OF SUCH DAMAGE.
27 */
28
29 #include <stdio.h>
30 #include <stdlib.h>
31 #include <errno.h>
32
33 #include <ucx/list.h>
34
35 #include "../daemon/session.h"
36 #include "../util/pblock.h"
37
38 #include "operation.h"
39
40 #define WEBDAV_PATH_MAX 8192
41
42
43 size_t webdav_num_backends(WebdavBackend *dav) {
44 size_t count = 0;
45 while(dav) {
46 count++;
47 dav = dav->next;
48 }
49 return count;
50 }
51
52 /****************************************************************************
53 *
54 * PROPFIND OPERATION
55 *
56 ****************************************************************************/
57
58 WebdavOperation* webdav_create_propfind_operation(
59 Session *sn,
60 Request *rq,
61 WebdavBackend *dav,
62 WebdavPList *reqprops,
63 UcxList *requests,
64 WebdavResponse *response)
65 {
66 WebdavOperation *op = pool_malloc(sn->pool, sizeof(WebdavOperation));
67 ZERO(op, sizeof(WebdavOperation));
68 op->dav = dav;
69 op->sn = sn;
70 op->rq = rq;
71 op->reqprops = reqprops;
72 op->requests = requests;
73 op->response = response;
74 op->response_close = webdav_op_propfiond_close_resource;
75 response->op = op;
76
77 return op;
78 }
79
80 int webdav_op_propfind_begin(
81 WebdavOperation *op,
82 const char *href,
83 VFS_DIR parent,
84 struct stat *s)
85 {
86 // create WebdavResource object for requested resource
87 WebdavResource *resource = op->response->addresource(op->response, href);
88 if(!resource) {
89 return REQ_ABORTED;
90 }
91
92 // store data that we need when the resource will be closed
93 op->stat = s;
94 op->parent = parent;
95
96 // get first propfind object
97 WebdavPropfindRequest *propfind = op->requests->data;
98
99 // execute propfind_do of the first backend for the first resource
100 int ret = REQ_PROCEED;
101 if(op->dav->propfind_do(propfind, op->response, NULL, resource, s)) {
102 ret = REQ_ABORTED;
103 } else {
104 // propfind_do successful, close resource if needed
105 // closing the resource will execute propfind_do of all remaining
106 // backends
107 if(!resource->isclosed) {
108 ret = resource->close(resource);
109 }
110 }
111
112 return ret;
113 }
114
115 typedef struct PathSearchElm {
116 char *href;
117 char *path;
118 size_t hreflen;
119 size_t pathlen;
120 } PathSearchElm;
121
122 /*
123 * concats base + / + elm
124 * if baseinit is true, only elm is copied
125 */
126 static int path_buf_concat(
127 pool_handle_t *pool,
128 char **buf,
129 size_t * restrict len,
130 WSBool * restrict baseinit,
131 const char *base,
132 size_t baselen,
133 const char *elm,
134 size_t elmlen)
135 {
136 if(base[baselen-1] == '/') {
137 baselen--;
138 }
139
140 size_t newlen = baselen + elmlen + 1;
141 if(newlen > WEBDAV_PATH_MAX) {
142 log_ereport(LOG_FAILURE, "webdav: maximal path length exceeded");
143 return 1;
144 }
145
146 // check if new path + terminator fits in the buffer
147 if(newlen + 1 > *len) {
148 *len = newlen + 128;
149 char *newbuf = pool_realloc(pool, *buf, newlen);
150 if(newbuf) {
151 log_ereport(LOG_FAILURE, "webdav: path memory allocation failed");
152 return 1;
153 }
154 *baseinit = FALSE;
155
156 *buf = newbuf;
157 }
158
159 // if baseinit is true, the parent is already in the buffer
160 // and we don't need to memcpy it again
161 if(!(*baseinit)) {
162 memcpy(*buf, base, baselen);
163 (*buf)[baselen] = '/';
164 *baseinit = TRUE;
165 }
166 // copy child and terminate string
167 memcpy((*buf) + baselen + 1, elm, elmlen);
168 (*buf)[newlen] = '\0';
169
170 return 0;
171 }
172
173 static int propfind_child_cb(
174 VFSContext *vfs,
175 const char *href,
176 const char *path,
177 VFSDir *parent,
178 struct stat *s,
179 void *op)
180 {
181 return webdav_op_propfind_begin(op, href, parent, s);
182 }
183
184 int webdav_op_propfind_children(
185 WebdavOperation *op,
186 VFSContext *vfs,
187 const char *href,
188 const char *path)
189 {
190 WebdavPropfindRequest *request = op->requests->data;
191 return webdav_op_iterate_children(
192 vfs, request->depth, href, path, propfind_child_cb, op);
193 }
194
195 int webdav_op_propfiond_close_resource(
196 WebdavOperation *op,
197 WebdavResource *resource)
198 {
199 // start with second backend and request, because
200 // the first one was already called by webdav_op_propfind_begin
201 WebdavBackend *dav = op->dav->next;
202 UcxList *request = op->requests->next;
203
204 // call propfind_do of all remaining backends
205 int ret = REQ_PROCEED;
206 while(dav && request) {
207 if(dav->propfind_do(
208 request->data,
209 op->response,
210 op->parent,
211 resource,
212 op->stat))
213 {
214 ret = REQ_ABORTED;
215 }
216
217 dav = dav->next;
218 request = request->next;
219 }
220 return ret;
221 }
222
223 /*
224 * Executes propfind_finish for each Backend
225 */
226 int webdav_op_propfind_finish(WebdavOperation *op) {
227 WebdavBackend *dav = op->dav;
228 UcxList *requests = op->requests;
229
230 int ret = REQ_PROCEED;
231 while(dav && requests) {
232 if(dav->propfind_finish(requests->data)) {
233 ret = REQ_ABORTED;
234 }
235
236 dav = dav->next;
237 requests = requests->next;
238 }
239 return ret;
240 }
241
242 /****************************************************************************
243 *
244 * PROPPATCH OPERATION
245 *
246 ****************************************************************************/
247
248 WebdavOperation* webdav_create_proppatch_operation(
249 Session *sn,
250 Request *rq,
251 WebdavBackend *dav,
252 WebdavProppatchRequest *proppatch,
253 WebdavResponse *response)
254 {
255 WebdavOperation *op = pool_malloc(sn->pool, sizeof(WebdavOperation));
256 ZERO(op, sizeof(WebdavOperation));
257 op->dav = dav;
258 op->sn = sn;
259 op->rq = rq;
260 op->reqprops = NULL;
261 op->response = response;
262 op->proppatch = proppatch;
263 op->response_close = webdav_op_proppatch_close_resource;
264 response->op = op;
265
266 return op;
267 }
268
269
270
271 int webdav_op_proppatch(
272 WebdavOperation *op,
273 const char *href,
274 const char *path)
275 {
276 WebdavProppatchRequest *orig_request = op->proppatch;
277 UcxAllocator *a = session_get_allocator(op->sn);
278
279 // create WebdavResource object for the requested resource
280 WebdavResource *resource = op->response->addresource(op->response, href);
281 if(!resource) {
282 return REQ_ABORTED;
283 }
284
285 VFSContext *ctx = NULL;
286 VFSFile *file = NULL;
287
288 // requests for each backends
289 WebdavProppatchRequest **requests = pool_calloc(
290 op->sn->pool,
291 webdav_num_backends(op->dav),
292 sizeof(WebdavProppatchRequest*));
293 if(requests == NULL) {
294 return REQ_ABORTED;
295 }
296
297 WebdavPList *prev_set = orig_request->set;
298 WebdavPList *prev_remove = orig_request->remove;
299 size_t set_count = orig_request->setcount;
300 size_t remove_count = orig_request->removecount;
301
302 int ret = REQ_PROCEED;
303
304 // iterate backends and execute proppatch_do
305 WebdavBackend *dav = op->dav;
306 size_t numrequests = 0;
307 while(dav) {
308 WebdavPList *set = webdav_plist_clone_s(
309 op->sn->pool,
310 prev_set,
311 &set_count);
312 WebdavPList *remove = webdav_plist_clone_s(
313 op->sn->pool,
314 prev_remove,
315 &remove_count);
316 if((prev_set && !set) || (prev_remove && !remove)) {
317 // clone failed, OOM
318 ret = REQ_ABORTED;
319 break;
320 }
321
322 // create new WebdavProppatchRequest object for this backend
323 WebdavProppatchRequest *req = pool_malloc(
324 op->sn->pool,
325 sizeof(WebdavProppatchRequest));
326 memcpy(req, orig_request, sizeof(WebdavProppatchRequest));
327 req->dav = dav;
328 req->set = orig_request->set;
329 req->setcount = orig_request->setcount;
330 req->remove = orig_request->remove;
331 req->removecount = orig_request->removecount;
332 req->userdata = NULL;
333
334 // check if we need to open the file because the backend wants it
335 if(!file && (dav->settings & WS_WEBDAV_PROPPATCH_USE_VFS)
336 == WS_WEBDAV_PROPPATCH_USE_VFS)
337 {
338 ctx = vfs_request_context(op->sn, op->rq);
339 if(!ctx) {
340 ret = REQ_ABORTED;
341 break;
342 }
343
344 file = vfs_open(ctx, path, O_RDONLY);
345 if(!file) {
346 protocol_status(
347 op->sn,
348 op->rq,
349 util_errno2status(ctx->vfs_errno),
350 NULL);
351 ret = REQ_ABORTED;
352 }
353 }
354
355 // execute proppatch_do
356 if(dav->proppatch_do(req, resource, file, &set, &remove)) {
357 // return later, because we need do execute proppatch_finish
358 // for all successfully called backends
359 ret = REQ_ABORTED;
360 break;
361 }
362
363 // proppatch_do should remove all handled props from set and remove
364 // in the next iteration, the backend must use these reduced lists
365 prev_set = set;
366 prev_remove = remove;
367
368 requests[numrequests++] = req;
369
370 // continue with next backend
371 dav = dav->next;
372 }
373
374 WSBool commit = FALSE;
375 if(ret == REQ_PROCEED && resource->err == 0) {
376 // no errors, no properties with errors -> save the changes
377 commit = TRUE;
378 }
379
380 // call proppatch_finish for each successfully called proppatch_do
381 dav = op->dav;
382 int i = 0;
383 while(dav && i < numrequests) {
384 if(dav->proppatch_finish(requests[i], resource, file, commit)) {
385 ret = REQ_ABORTED;
386 }
387 i++;
388 dav = dav->next;
389 }
390
391 if(file) {
392 vfs_close(file);
393 }
394
395 if(resource->close(resource)) {
396 ret = REQ_ABORTED;
397 }
398
399 return ret;
400 }
401
402 int webdav_op_proppatch_close_resource(
403 WebdavOperation *op,
404 WebdavResource *resource)
405 {
406 return 0; // NOP
407 }
408
409
410 /****************************************************************************
411 *
412 * VFS OPERATION
413 *
414 ****************************************************************************/
415
416 WebdavVFSOperation* webdav_vfs_op(
417 Session *sn,
418 Request *rq,
419 WebdavBackend *dav,
420 WSBool precondition)
421 {
422 WebdavVFSOperation *op = pool_malloc(sn->pool, sizeof(WebdavVFSOperation));
423 if(!op) {
424 return NULL;
425 }
426 ZERO(op, sizeof(WebdavVFSOperation));
427
428 op->sn = sn;
429 op->rq = rq;
430 op->dav = dav;
431 op->stat = NULL;
432 op->stat_errno = 0;
433
434 // create VFS context
435 VFSContext *vfs = vfs_request_context(sn, rq);
436 if(!vfs) {
437 pool_free(sn->pool, op);
438 return NULL;
439 }
440 op->vfs = vfs;
441
442 char *path = pblock_findkeyval(pb_key_path, rq->vars);
443 op->path = path;
444
445 return op;
446 }
447
448 WebdavVFSOperation webdav_vfs_sub_op(
449 WebdavVFSOperation *op,
450 char *path,
451 struct stat *s)
452 {
453 WebdavVFSOperation sub;
454 sub.dav = op->dav;
455 sub.path = path;
456 sub.sn = op->sn;
457 sub.vfs = op->vfs;
458 sub.path = path;
459 sub.stat = s;
460 sub.stat_errno = 0;
461 return sub;
462 }
463
464 int webdav_op_iterate_children(
465 VFSContext *vfs,
466 int depth,
467 const char *href,
468 const char *path,
469 vfs_op_child_func func,
470 void *userdata)
471 {
472 UcxAllocator *a = session_get_allocator(vfs->sn);
473 pool_handle_t *pool = vfs->sn->pool;
474
475 PathSearchElm *start_elm = pool_malloc(pool, sizeof(PathSearchElm));
476 start_elm->href = pool_strdup(pool, href ? href : "");
477 start_elm->path = pool_strdup(pool, path ? path : "");
478 start_elm->hreflen = href ? strlen(href) : 0;
479 start_elm->pathlen = path ? strlen(path) : 0;
480
481 UcxList *stack = ucx_list_prepend_a(a, NULL, start_elm);
482 UcxList *stack_end = stack;
483 if(!stack) {
484 return 1;
485 }
486
487 // reusable buffer for full child path and href
488 char *newpath = pool_malloc(pool, 256);
489 size_t newpathlen = 256;
490
491 char *newhref = pool_malloc(pool, 256);
492 size_t newhreflen = 256;
493
494 int err = 0;
495 while(stack && !err) {
496 PathSearchElm *cur_elm = stack->data;
497
498 // when newpath is initialized with the parent path
499 // set path_buf_init to TRUE
500 WSBool href_buf_init = FALSE;
501 WSBool path_buf_init = FALSE;
502
503 VFS_DIR dir = vfs_opendir(vfs, cur_elm->path);
504 if(!dir) {
505 log_ereport(
506 LOG_FAILURE,
507 "webdav: propfind: cannot open directory %d",
508 vfs->vfs_errno);
509 err = 1;
510 break;
511 }
512
513 VFS_ENTRY f;
514 while(vfs_readdir_stat(dir, &f)) {
515 if(f.stat_errno != 0) {
516 continue;
517 }
518
519 size_t child_len = strlen(f.name);
520
521 // create new path and href for the child
522 if(path_buf_concat(
523 pool,
524 &newhref,
525 &newhreflen,
526 &href_buf_init,
527 cur_elm->href,
528 cur_elm->hreflen,
529 f.name,
530 child_len))
531 {
532 err = 1;
533 break;
534 }
535 if(path_buf_concat(
536 pool,
537 &newpath,
538 &newpathlen,
539 &path_buf_init,
540 cur_elm->path,
541 cur_elm->pathlen,
542 f.name,
543 child_len))
544 {
545 err = 1;
546 break;
547 }
548 size_t childhreflen = cur_elm->hreflen + 1 + child_len;
549 size_t childpathlen = cur_elm->pathlen + 1 + child_len;
550
551 // execute callback func for this file
552 if(func(vfs, newhref, newpath, dir, &f.stat, userdata)) {
553 err = 1;
554 break;
555 }
556
557 // depth of -1 means infinity
558 if(depth == -1 && S_ISDIR(f.stat.st_mode)) {
559 char *hrefcp = pool_malloc(pool, childhreflen + 1);
560 memcpy(hrefcp, newhref, childhreflen + 1);
561 hrefcp[childhreflen] = '\0';
562
563 char *pathcp = pool_malloc(pool, childpathlen + 1);
564 memcpy(pathcp, newpath, childpathlen + 1);
565 pathcp[childpathlen] = '\0';
566
567 PathSearchElm *new_elm = pool_malloc(pool,
568 sizeof(PathSearchElm));
569 new_elm->href = hrefcp;
570 new_elm->path = pathcp;
571 new_elm->hreflen = childhreflen;
572 new_elm->pathlen = childpathlen;
573
574 // add the new_elm to the stack
575 // stack_end is always not NULL here, because we remove
576 // the first stack element at the end of the loop
577 UcxList *newlistelm = ucx_list_append_a(a, stack_end, new_elm);
578 if(!newlistelm) {
579 err = 1;
580 break;
581 }
582 stack_end = newlistelm;
583 }
584 }
585
586 vfs_closedir(dir);
587
588 pool_free(pool, cur_elm->path);
589 pool_free(pool, cur_elm->href);
590 pool_free(pool, cur_elm);
591
592 stack = ucx_list_remove_a(a, stack, stack);
593 }
594
595 // in case of an error, we have to free all remaining stack elements
596 UCX_FOREACH(elm, stack) {
597 char *data = elm->data;
598 if(data != path) {
599 pool_free(pool, data);
600 }
601 }
602
603 return err;
604 }
605
606
607 int webdav_vfs_stat(WebdavVFSOperation *op) {
608 if(op->stat) {
609 return 0;
610 } else if(op->stat_errno != 0) {
611 // previous stat failed
612 return 1;
613 }
614
615 // stat file
616 struct stat sbuf;
617 int ret = vfs_stat(op->vfs, op->path, &sbuf);
618 if(!ret) {
619 // save result in op->stat and in s
620 op->stat = pool_malloc(op->sn->pool, sizeof(struct stat));
621 if(op->stat) {
622 memcpy(op->stat, &sbuf, sizeof(struct stat));
623 } else {
624 ret = 1;
625 op->stat_errno = ENOMEM;
626 }
627 } else {
628 op->stat_errno = errno;
629 }
630
631 return ret;
632 }
633
634 int webdav_vfs_op_do(WebdavVFSOperation *op, WebdavVFSOpType type) {
635 WSBool exec_vfs = TRUE;
636
637 // requests for each backends
638 WebdavVFSRequest **requests = pool_calloc(
639 op->sn->pool,
640 webdav_num_backends(op->dav),
641 sizeof(WebdavVFSRequest*));
642 if(requests == NULL) {
643 return REQ_ABORTED;
644 }
645
646 int ret = REQ_PROCEED;
647
648 // call opt_* func for each backend
649 WebdavBackend *dav = op->dav;
650 int called_backends = 0;
651 while(dav) {
652 WebdavVFSRequest *request = NULL;
653
654 // get vfs operation functions
655 vfs_op_func op_func = NULL;
656 vfs_op_finish_func op_finish_func = NULL;
657
658 if(type == WEBDAV_VFS_MKDIR) {
659 op_func = dav->opt_mkcol;
660 op_finish_func = dav->opt_mkcol_finish;
661 } else if(type == WEBDAV_VFS_DELETE) {
662 op_func = dav->opt_delete;
663 op_finish_func = dav->opt_delete_finish;
664 }
665
666 if(op_func || op_finish_func) {
667 // we need a request object
668 request = pool_malloc(op->sn->pool, sizeof(WebdavVFSRequest));
669 if(!request) {
670 exec_vfs = FALSE;
671 ret = REQ_ABORTED;
672 break;
673 }
674 request->sn = op->sn;
675 request->rq = op->rq;
676 request->path = op->path;
677 request->userdata = NULL;
678
679 requests[called_backends] = request;
680 }
681
682 // exec backend func for this operation
683 // this will set 'done' to TRUE, if no further vfs call is required
684 WSBool done = FALSE;
685 called_backends++;
686 if(op_func) {
687 if(op_func(request, &done)) {
688 exec_vfs = FALSE;
689 ret = REQ_ABORTED;
690 break;
691 }
692 }
693 if(done) {
694 exec_vfs = FALSE;
695 }
696
697 dav = dav->next;
698 }
699
700 // if needed, call vfs func for this operation
701 if(exec_vfs) {
702 int r = 0;
703 if(type == WEBDAV_VFS_MKDIR) {
704 r = vfs_mkdir(op->vfs, op->path);
705 } else if(type == WEBDAV_VFS_DELETE) {
706 r = webdav_vfs_unlink(op);
707 }
708
709 if(r) {
710 ret = REQ_ABORTED;
711 }
712 }
713
714 WSBool success = ret == REQ_PROCEED ? TRUE : FALSE;
715
716 // finish mkcol (cleanup) by calling opt_*_finish for each backend
717 dav = op->dav;
718 int i = 0;
719 while(dav && i < called_backends) {
720 // get vfs operation functions
721 vfs_op_finish_func op_finish_func = NULL;
722
723 if(type == WEBDAV_VFS_MKDIR) {
724 op_finish_func = dav->opt_mkcol_finish;
725 } else if(type == WEBDAV_VFS_DELETE) {
726 op_finish_func = dav->opt_delete_finish;
727 }
728
729 if(op_finish_func) {
730 if(op_finish_func(requests[i], success)) {
731 ret = REQ_ABORTED; // don't exit loop
732 }
733 }
734
735 dav = dav->next;
736 i++;
737 }
738
739 return ret;
740 }
741
742 int webdav_vfs_unlink(WebdavVFSOperation *op) {
743 // stat the file first, to check if the file is a directory
744 if(webdav_vfs_stat(op)) {
745 return 1; // error
746 } else {
747 if(!S_ISDIR(op->stat->st_mode)) {
748 return vfs_unlink(op->vfs, op->path);
749 } else {
750 return vfs_rmdir(op->vfs, op->path);
751 }
752 }
753 }

mercurial