src/server/webdav/webdav.c

branch
webdav
changeset 211
2160585200ac
parent 107
7e81699d1f77
child 212
d7e7ea9c6bc6
equal deleted inserted replaced
210:21274e5950af 211:2160585200ac
1 /* 1 /*
2 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. 2 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
3 * 3 *
4 * Copyright 2013 Olaf Wintermann. All rights reserved. 4 * Copyright 2019 Olaf Wintermann. All rights reserved.
5 * 5 *
6 * Redistribution and use in source and binary forms, with or without 6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions are met: 7 * modification, are permitted provided that the following conditions are met:
8 * 8 *
9 * 1. Redistributions of source code must retain the above copyright 9 * 1. Redistributions of source code must retain the above copyright
28 28
29 #include <stdio.h> 29 #include <stdio.h>
30 #include <stdlib.h> 30 #include <stdlib.h>
31 #include <string.h> 31 #include <string.h>
32 32
33 #include <ucx/buffer.h>
34 #include <ucx/list.h>
35
33 #include "webdav.h" 36 #include "webdav.h"
34 37
35 38 #include "search.h"
39 #include "versioning.h"
40 #include "multistatus.h"
41 #include "requestparser.h"
42
43 #include "../util/pblock.h"
44 #include "../util/util.h"
45 #include "../daemon/session.h"
46 #include "../daemon/http.h"
47
48 static UcxMap *method_handler_map;
49
50 static WebdavBackend default_backend;
51
52 static WSNamespace dav_namespace;
53
54 static WebdavProperty dav_resourcetype_empty;
55 static WebdavProperty dav_resourcetype_collection;
56 static WSXmlNode dav_resourcetype_collection_value;
57
58 static void init_default_backend(void) {
59 memset(&default_backend, 0, sizeof(WebdavBackend));
60 default_backend.propfind_init = default_propfind_init;
61 default_backend.propfind_do = default_propfind_do;
62 default_backend.propfind_finish = default_propfind_finish;
63 }
64
65 int webdav_init(pblock *pb, Session *sn, Request *rq) {
66 init_default_backend();
67
68 method_handler_map = ucx_map_new(64);
69
70 ucx_map_cstr_put(method_handler_map, "OPTIONS", webdav_options);
71 ucx_map_cstr_put(method_handler_map, "PROPFIND", webdav_propfind);
72 ucx_map_cstr_put(method_handler_map, "PROPPATCH", webdav_proppatch);
73 ucx_map_cstr_put(method_handler_map, "MKCOL", webdav_mkcol);
74 ucx_map_cstr_put(method_handler_map, "POST", webdav_post);
75 ucx_map_cstr_put(method_handler_map, "DELETE", webdav_delete);
76 ucx_map_cstr_put(method_handler_map, "PUT", webdav_put);
77 ucx_map_cstr_put(method_handler_map, "COPY", webdav_copy);
78 ucx_map_cstr_put(method_handler_map, "MOVE", webdav_move);
79 ucx_map_cstr_put(method_handler_map, "LOCK", webdav_lock);
80 ucx_map_cstr_put(method_handler_map, "UNLOCK", webdav_unlock);
81 ucx_map_cstr_put(method_handler_map, "REPORT", webdav_report);
82 ucx_map_cstr_put(method_handler_map, "ACL", webdav_acl);
83
84 ucx_map_cstr_put(method_handler_map, "SEARCH", webdav_search);
85
86 ucx_map_cstr_put(method_handler_map, "VERSION-CONTROL", webdav_version_control);
87 ucx_map_cstr_put(method_handler_map, "CHECKOUT", webdav_checkout);
88 ucx_map_cstr_put(method_handler_map, "CHECKIN", webdav_checkin);
89 ucx_map_cstr_put(method_handler_map, "UNCHECKOUT", webdav_uncheckout);
90 ucx_map_cstr_put(method_handler_map, "MKWORKSPACE", webdav_mkworkspace);
91 ucx_map_cstr_put(method_handler_map, "UPDATE", webdav_update);
92 ucx_map_cstr_put(method_handler_map, "LABEL", webdav_label);
93 ucx_map_cstr_put(method_handler_map, "MERGE", webdav_merge);
94
95 dav_namespace.href = (xmlChar*)"DAV:";
96 dav_namespace.prefix = (xmlChar*)"D";
97
98 dav_resourcetype_empty.namespace = &dav_namespace;
99 dav_resourcetype_empty.name = "resourcetype";
100
101 dav_resourcetype_collection.namespace = &dav_namespace;
102 dav_resourcetype_collection.name = "resourcetype";
103 dav_resourcetype_collection.value = &dav_resourcetype_collection_value;
104 dav_resourcetype_collection_value.content = (xmlChar*)"<D:collection/>";
105 dav_resourcetype_collection_value.type = XML_TEXT_NODE;
106
107
108 return REQ_PROCEED;
109 }
110
111
112 int webdav_service(pblock *pb, Session *sn, Request *rq) {
113 if(!method_handler_map) {
114 log_ereport(LOG_FAILURE, "WebDAV module not initialized");
115 protocol_status(sn, rq, 500, NULL);
116 return REQ_ABORTED;
117 }
118 char *method = pblock_findkeyval(pb_key_method, rq->reqpb);
119
120 FuncPtr saf = (FuncPtr)ucx_map_cstr_get(method_handler_map, method);
121 if(!saf) {
122 return REQ_NOACTION;
123 }
124
125 return saf(pb, sn, rq);
126 }
127
128 UcxBuffer* rqbody2buffer(Session *sn, Request *rq) {
129 if(!sn->inbuf) {
130 protocol_status(sn, rq, 400, NULL);
131 return NULL;
132 }
133
134 UcxBuffer *buf = ucx_buffer_new(
135 NULL,
136 sn->inbuf->maxsize,
137 UCX_BUFFER_AUTOEXTEND);
138 if(!buf) {
139 protocol_status(sn, rq, 500, NULL);
140 return NULL;
141 }
142
143 char in[2048];
144 int r;
145 while((r = netbuf_getbytes(sn->inbuf, in, 2048)) > 0) {
146 if(ucx_buffer_write(in, 1, r, buf) != r) {
147 protocol_status(sn, rq, 500, NULL);
148 ucx_buffer_free(buf);
149 return NULL;
150 }
151 }
152
153 return buf;
154 }
155
156 int webdav_options(pblock *pb, Session *sn, Request *rq) {
157 return REQ_ABORTED;
158 }
159
160 int webdav_propfind(pblock *pb, Session *sn, Request *rq) {
161 UcxBuffer *reqbody = rqbody2buffer(sn, rq);
162 if(!reqbody) {
163 return REQ_ABORTED;
164 }
165
166 int error = 0;
167 WebdavPropfindRequest *propfind = propfind_parse(
168 sn,
169 rq,
170 reqbody->space,
171 reqbody->size,
172 &error);
173 ucx_buffer_free(reqbody);
174 if(!propfind) {
175 switch(error) {
176 // TODO: handle all errors
177 default: return REQ_ABORTED;
178 }
179 }
180
181
182 Multistatus *ms = multistatus_response(sn, rq);
183 if(!ms) {
184 return REQ_ABORTED;
185 }
186 WebdavResponse *response = (WebdavResponse*)ms;
187
188 WebdavBackend *dav =
189 rq->davCollection ? rq->davCollection : &default_backend;
190
191 char *path = pblock_findkeyval(pb_key_path, rq->vars);
192
193 uint32_t settings = dav->settings;
194 if(dav->propfind_init(propfind, path)) {
195 return REQ_ABORTED;
196 }
197
198 WSBool usevfs = (settings & WS_PROPFIND_NO_VFS) != WS_PROPFIND_NO_VFS;
199 struct stat s;
200 struct stat *statptr = NULL;
201
202 VFSContext *vfs = NULL;
203 if(usevfs) {
204 vfs = vfs_request_context(sn, rq);
205
206 if(vfs_stat(vfs, path, &s)) {
207 return REQ_ABORTED;
208 }
209 statptr = &s;
210 if(!S_ISDIR(s.st_mode)) {
211 usevfs = FALSE;
212 }
213 }
214 if(propfind->depth == 0) {
215 usevfs = FALSE;
216 }
217
218 int ret = REQ_ABORTED;
219 if(!dav->propfind_do(propfind, response, NULL, path, statptr)) {
220 // propfind for the requested resource was successful
221
222 // usevfsdir is TRUE if
223 // the webdav backend has not disabled vfs usage
224 // the file is a directory
225 // depth is not 0
226 // in this case we need to execute propfind_do for all children
227 if(usevfs && !propfind_children(dav, propfind, response, vfs, path)) {
228 ret = REQ_PROCEED;
229 }
230 }
231
232 // finish the propfind request
233 // this function should cleanup all resources, therefore we execute it
234 // even if a previous function failed
235 if(dav->propfind_finish(propfind)) {
236 ret = REQ_ABORTED;
237 }
238
239 return ret;
240 }
241
242 int propfind_children(
243 WebdavBackend *dav,
244 WebdavPropfindRequest *request,
245 WebdavResponse *response,
246 VFSContext *vfs,
247 char *path)
248 {
249 UcxAllocator *a = session_get_allocator(request->sn);
250 pool_handle_t *pool = request->sn->pool;
251 UcxList *stack = ucx_list_prepend_a(a, NULL, path);
252 UcxList *stack_end = stack;
253 if(!stack) {
254 return 1;
255 }
256
257 // reusable buffer for full child path
258 char *newpath = NULL;
259 size_t newpathlen = 0;
260
261 int err = 0;
262 while(stack && !err) {
263 char *cur_path = stack->data;
264 size_t parent_len = strlen(cur_path);
265 if(parent_len > WEBDAV_PATH_MAX) {
266 log_ereport(LOG_FAILURE, "webdav: maximal path length exceeded");
267 err = 1;
268 break;
269 }
270 if(cur_path[parent_len-1] == '/') {
271 parent_len--;
272 }
273 size_t max_child_len = WEBDAV_PATH_MAX - parent_len;
274
275 // when newpath is initialized with the parent path
276 // set path_buf_init to TRUE
277 WSBool path_buf_init = FALSE;
278
279 VFS_DIR dir = vfs_opendir(vfs, path);
280 if(!dir) {
281 log_ereport(
282 LOG_FAILURE,
283 "webdav: propfind: cannot open directory %d",
284 vfs->vfs_errno);
285 err = 1;
286 break;
287 }
288
289 VFS_ENTRY f;
290 while(vfs_readdir_stat(dir, &f)) {
291 if(f.stat_errno != 0) {
292 continue;
293 }
294
295 size_t child_len = strlen(f.name);
296 if(child_len > max_child_len) {
297 log_ereport(LOG_FAILURE, "webdav: maximal path length exceeded");
298 err = 1;
299 break;
300 }
301 size_t childpathlen = parent_len + child_len + 1; // +1 '/'
302 if(childpathlen > newpathlen) {
303 // we're gonna need a bigger boa^H^H^Hbuffer
304 if(newpath) {
305 pool_free(pool, newpath);
306 }
307 newpath = pool_malloc(pool, childpathlen + 1);
308 if(!newpath) {
309 err = 1;
310 break;
311 }
312 newpathlen = childpathlen;
313 path_buf_init = FALSE;
314 }
315 // create full path string for this child
316 if(!path_buf_init) {
317 memcpy(newpath, cur_path, parent_len);
318 newpath[parent_len] = '/';
319 }
320 memcpy(newpath+parent_len+1, f.name, child_len);
321 newpath[childpathlen] = 0;
322
323 // propfind for this child
324 if(dav->propfind_do(request, response, dir, newpath, &f.stat)) {
325 err = 1;
326 break;
327 }
328
329 // depth of -1 means infinity
330 if(request->depth == -1 && S_ISDIR(f.stat.st_mode)) {
331 char *pathcp = pool_malloc(pool, childpathlen + 1);
332 memcpy(pathcp, newpath, childpathlen + 1);
333
334 // add the newpath copy to the stack
335 // stack_end is always not NULL here, because we remove
336 // the first stack element at the end of the loop
337 UcxList *newlistelm = ucx_list_append_a(a, stack_end, pathcp);
338 if(!newlistelm) {
339 err = 1;
340 break;
341 }
342 stack_end = newlistelm;
343 }
344 }
345
346 vfs_closedir(dir);
347
348 if(cur_path != path) {
349 pool_free(pool, cur_path);
350 }
351 stack = ucx_list_remove_a(a, stack, stack);
352 }
353
354 // in case of an error, we have to free all remaining stack elements
355 UCX_FOREACH(elm, stack) {
356 char *data = elm->data;
357 if(data != path) {
358 pool_free(pool, data);
359 }
360 }
361
362 return err;
363 }
364
365 int webdav_proppatch(pblock *pb, Session *sn, Request *rq) {
366 return REQ_ABORTED;
367 }
368
369 int webdav_mkcol(pblock *pb, Session *sn, Request *rq) {
370 return REQ_ABORTED;
371 }
372
373 int webdav_post(pblock *pb, Session *sn, Request *rq) {
374 return REQ_ABORTED;
375 }
376
377 int webdav_delete(pblock *pb, Session *sn, Request *rq) {
378 return REQ_ABORTED;
379 }
380
381 int webdav_put(pblock *pb, Session *sn, Request *rq) {
382 return REQ_ABORTED;
383 }
384
385 int webdav_copy(pblock *pb, Session *sn, Request *rq) {
386 return REQ_ABORTED;
387 }
388
389 int webdav_move(pblock *pb, Session *sn, Request *rq) {
390 return REQ_ABORTED;
391 }
392
393 int webdav_lock(pblock *pb, Session *sn, Request *rq) {
394 return REQ_ABORTED;
395 }
396
397 int webdav_unlock(pblock *pb, Session *sn, Request *rq) {
398 return REQ_ABORTED;
399 }
400
401 int webdav_report(pblock *pb, Session *sn, Request *rq) {
402 return REQ_ABORTED;
403 }
404
405 int webdav_acl(pblock *pb, Session *sn, Request *rq) {
406 return REQ_ABORTED;
407 }
408
409
410 /* ------------------------ default webdav backend ------------------------ */
411
412 int default_propfind_init(
413 WebdavPropfindRequest *rq,
414 const char* path)
415 {
416 DefaultWebdavData *data = pool_malloc(
417 rq->sn->pool,
418 sizeof(DefaultWebdavData));
419 if(!data) {
420 return 1;
421 }
422 rq->userdata = data;
423
424 data->vfsproperties = webdav_vfs_properties(rq, TRUE, 0);
425
426 return 0;
427 }
428
429 int default_propfind_do(
430 WebdavPropfindRequest *request,
431 WebdavResponse *response,
432 VFS_DIR parent,
433 const char *path,
434 struct stat *s)
435 {
436 DefaultWebdavData *data = request->userdata;
437
438 // add a resource to the response
439 // usually this will lead to a <response> ... </response> tag in the
440 // multistatus response
441 WebdavResource *resource = response->addresource(response, path);
442 if(!resource) {
443 return 1;
444 }
445
446 // add all requested vfs properties like getcontentlength ...
447 if(webdav_add_vfs_properties(
448 resource,
449 request->sn->pool,
450 data->vfsproperties,
451 s))
452 {
453 return 1;
454 }
455
456 // all remaining properties are not available
457 WebdavPList *p = request->properties;
458 while(p) {
459 resource->addproperty(resource, p->property, 404);
460 p = p->next;
461 }
462
463 return 0;
464 }
465
466 int default_propfind_finish(WebdavPropfindRequest *rq) {
467 return 0;
468 }
469
470
471 /* ------------------------------ public API ------------------------------ */
472
473 int webdav_getdepth(Request *rq) {
474 char *depth_str = pblock_findkeyval(pb_key_depth, rq->headers);
475 int depth = 0;
476 if(depth_str) {
477 size_t dlen = strlen(depth_str);
478 if(!memcmp(depth_str, "infinity", dlen)) {
479 depth = -1;
480 } else if(dlen == 1 && depth_str[0] == '1') {
481 depth = 1;
482 }
483 }
484 return depth;
485 }
486
487 WSNamespace* webdav_dav_namespace(void) {
488 return &dav_namespace;
489 }
490
491 WebdavProperty* webdav_dav_property(
492 pool_handle_t *pool,
493 const char *name)
494 {
495 WebdavProperty *property = pool_malloc(pool, sizeof(WebdavProperty));
496 if(!property) {
497 return NULL;
498 }
499
500 property->namespace = &dav_namespace;
501 property->lang = NULL;
502 property->name = name;
503 property->value = NULL;
504 return property;
505 }
506
507 int webdav_property_set_value(
508 WebdavProperty *p,
509 pool_handle_t *pool,
510 char *value)
511 {
512 WSXmlNode *node = pool_malloc(pool, sizeof(WSXmlNode));
513 if(!node) {
514 return 1;
515 }
516 ZERO(node, sizeof(WSXmlNode));
517
518 node->content = (xmlChar*)value;
519 node->type = XML_TEXT_NODE;
520
521 p->value = node;
522 return 0;
523 }
524
525 WebdavVFSProperties webdav_vfs_properties(
526 WebdavPropfindRequest *rq,
527 WSBool removefromlist,
528 uint32_t flags)
529 {
530 WebdavVFSProperties ret;
531 ZERO(&ret, sizeof(WebdavVFSProperties));
532
533 WSBool etag = 1;
534 WSBool creationdate = 1;
535
536 WebdavPList *property = rq->properties;
537 WebdavPList *prev = NULL;
538 while(property) {
539 WebdavPList *next = property->next;
540 WSNamespace *ns = property->property->namespace;
541 if(ns && !strcmp((char*)ns->href, "DAV:")) {
542 const char *name = property->property->name;
543 WebdavPList *removethis = property;
544 if(!strcmp(name, "getlastmodified")) {
545 ret.getlastmodified = 1;
546 } else if(!strcmp(name, "getcontentlength")) {
547 ret.getcontentlength = 1;
548 } else if(!strcmp(name, "resourcetype")) {
549 ret.getresourcetype = 1;
550 } else if(etag && !strcmp(name, "getetag")) {
551 ret.getetag = 1;
552 } else if(creationdate && !strcmp(name, "creationdate")) {
553 ret.creationdate = 1;
554 } else {
555 removethis = NULL;
556 }
557
558 if(removefromlist && removethis) {
559 if(prev) {
560 prev->next = next;
561 } else {
562 rq->properties = next;
563 }
564 }
565 }
566 prev = property;
567 property = next;
568 }
569
570 return ret;
571 }
572
573 static inline int w_addprop(
574 WebdavResource *res,
575 pool_handle_t *pool,
576 const char *name,
577 char *value)
578 {
579 WebdavProperty *p = webdav_dav_property(pool, name);
580 if(!p) {
581 return 1;
582 }
583 if(webdav_property_set_value(p, pool, value)) {
584 return 1;
585 }
586 return res->addproperty(res, p, 200);
587 }
588
589 int webdav_add_vfs_properties(
590 WebdavResource *res,
591 pool_handle_t *pool,
592 WebdavVFSProperties properties,
593 struct stat *s)
594 {
595 if(properties.getresourcetype) {
596 if(S_ISDIR(s->st_mode)) {
597 res->addproperty(res, &dav_resourcetype_collection, 200);
598 } else {
599 res->addproperty(res, &dav_resourcetype_empty, 200);
600 }
601 }
602 if(properties.getcontentlength) {
603 char *buf = pool_malloc(pool, 64);
604 if(!buf) {
605 return 1;
606 }
607 uint64_t contentlength = s->st_size;
608 snprintf(buf, 64, "%" PRIu64 "\0", contentlength);
609 if(w_addprop(res, pool, "getcontentlength", buf)) {
610 return 1;
611 }
612 }
613 if(properties.getlastmodified) {
614 char *buf = pool_malloc(pool, HTTP_DATE_LEN+1);
615 if(!buf) {
616 return 1;
617 }
618 buf[HTTP_DATE_LEN] = 0;
619
620 struct tm mtms;
621 struct tm *mtm = system_gmtime(&s->st_mtim.tv_sec, &mtms);
622
623 if(mtm) {
624 strftime(buf, HTTP_DATE_LEN, HTTP_DATE_FMT, mtm);
625 if(w_addprop(res, pool, "getlastmodified", buf)) {
626 return 1;
627 }
628 } else {
629 return 1;
630 }
631 }
632 if(properties.creationdate) {
633 // TODO
634 }
635 if(properties.getetag) {
636 char *buf = pool_malloc(pool, 96);
637 if(!buf) {
638 return 1;
639 }
640 snprintf(buf,
641 96,
642 "\"%x-%x\"\0",
643 (int)s->st_size,
644 (int)s->st_mtim.tv_sec);
645 if(w_addprop(res, pool, "getetag", buf)) {
646 return 1;
647 }
648 }
649
650 return 0;
651 }

mercurial