1 /*
2 * mod_webdav - WEBDAV support for lighttpd
3 *
4 * Fully-rewritten from original
5 * Copyright(c) 2019 Glenn Strauss gstrauss()gluelogic.com All rights reserved
6 * License: BSD 3-clause (same as lighttpd)
7 */
8
9 /*
10 * Note: This plugin is a basic implementation of [RFC4918] WebDAV
11 *
12 * Version Control System (VCS) backing WebDAV is recommended instead
13 * and Subversion (svn) is one such VCS supporting WebDAV.
14 *
15 * status: *** EXPERIMENTAL *** (and likely insecure encoding/decoding)
16 *
17 * future:
18 *
19 * TODO: moving props should delete any existing props instead of
20 * preserving any that are not overwritten with UPDATE OR REPLACE
21 * (and, if merging directories, be careful when doing so)
22 * TODO: add proper support for locks with "shared" lockscope
23 * (instead of treating match of any shared lock as sufficient,
24 * even when there are different lockroots)
25 * TODO: does libxml2 xml-decode (html-decode),
26 * or must I do so to normalize input?
27 * TODO: add strict enforcement of property names to be valid XML tags
28 * (or encode as such before putting into database)
29 * & " < >
30 * TODO: should we be using xmlNodeListGetString() or xmlBufNodeDump()
31 * and how does it handle encoding and entity-inlining of external refs?
32 * Must xml decode/encode (normalize) before storing user data in database
33 * if going to add that data verbatim to xml doc returned in queries
34 * TODO: when walking xml nodes, should add checks for "DAV:" namespace
35 * TODO: consider where it might be useful/informative to check
36 * SQLITE_OK != sqlite3_reset() or SQLITE_OK != sqlite3_bind_...() or ...
37 * (in some cases no rows returned is ok, while in other cases it is not)
38 * TODO: Unsupported: !r->conf.follow_symlink is not currently honored;
39 * symlinks are followed. Supporting !r->conf.follow_symlinks would
40 * require operating system support for POSIX.1-2008 *at() commands,
41 * and reworking the mod_webdav code to exclusively operate with *at()
42 * commands, for example, replacing unlink() with unlinkat().
43 *
44 * RFE: add config option whether or not to include locktoken and ownerinfo
45 * in PROPFIND lockdiscovery
46 *
47 * deficiencies
48 * - incomplete "shared" lock support
49 * - review code for proper decoding/encoding of elements from/to XML and db
50 * - preserve XML info in scope on dead properties, e.g. xml:lang
51 *
52 * [RFC4918] 4.3 Property Values
53 * Servers MUST preserve the following XML Information Items (using the
54 * terminology from [REC-XML-INFOSET]) in storage and transmission of dead
55 * properties: ...
56 * [RFC4918] 14.26 set XML Element
57 * The 'set' element MUST contain only a 'prop' element. The elements
58 * contained by the 'prop' element inside the 'set' element MUST specify the
59 * name and value of properties that are set on the resource identified by
60 * Request-URI. If a property already exists, then its value is replaced.
61 * Language tagging information appearing in the scope of the 'prop' element
62 * (in the "xml:lang" attribute, if present) MUST be persistently stored along
63 * with the property, and MUST be subsequently retrievable using PROPFIND.
64 * [RFC4918] F.2 Changes for Server Implementations
65 * Strengthened server requirements for storage of property values, in
66 * particular persistence of language information (xml:lang), whitespace, and
67 * XML namespace information (see Section 4.3).
68 *
69 * resource usage containment
70 * - filesystem I/O operations might take a non-trivial amount of time,
71 * blocking the server from responding to other requests during this time.
72 * Potential solution: have a thread dedicated to handling webdav requests
73 * and serialize such requests in each thread dedicated to handling webdav.
74 * (Limit number of such dedicated threads.) Remove write interest from
75 * connection during this period so that server will not trigger any timeout
76 * on the connection.
77 * - recursive directory operations are depth-first and may consume a large
78 * number of file descriptors if the directory hierarchy is deep.
79 * Potential solution: serialize webdav requests into dedicated thread (above)
80 * Potential solution: perform breadth-first directory traversal and pwrite()
81 * directory paths into a temporary file. After reading each directory,
82 * close() the dirhandle and pread() next directory from temporary file.
83 * (Keeping list of directories in memory might result in large memory usage)
84 * - flush response to client (or to intermediate temporary file) at regular
85 * intervals or triggers to avoid response consume large amount of memory
86 * during operations on large collection hierarchies (with lots of nested
87 * directories)
88 *
89 * beware of security concerns involved in enabling WebDAV
90 * on publicly accessible servers
91 * - (general) [RFC4918] 20 Security Considersations
92 * - (specifically) [RFC4918] 20.6 Implications of XML Entities
93 * - TODO review usage of xml libs for security, resource usage, containment
94 * libxml2 vs expat vs ...
95 * http://xmlbench.sourceforge.net/
96 * http://stackoverflow.com/questions/399704/xml-parser-for-c
97 * http://tibleiz.net/asm-xml/index.html
98 * http://dev.yorhel.nl/yxml
99 * - how might mod_webdav be affected by mod_openssl setting REMOTE_USER?
100 * - when encoding href in responses, also ensure proper XML encoding
101 * (do we need to ENCODING_REL_URI and then ENCODING_MINIMAL_XML?)
102 * - TODO: any (non-numeric) data that goes into database should be encoded
103 * before being sent back to user, not just href. Perhaps anything that
104 * is not an href should be stored in database in XML-encoded form.
105 *
106 * consider implementing a set of (reasonable) limits on such things as
107 * - max number of collections
108 * - max number of objects in a collection
109 * - max number of properties per object
110 * - max length of property name
111 * - max length of property value
112 * - max length of locktoken, lockroot, ownerinfo
113 * - max number of locks held by a client, or by an owner
114 * - max number of locks on a resource (shared locks)
115 * - ...
116 *
117 * robustness
118 * - should check return value from sqlite3_reset(stmt) for REPLACE, UPDATE,
119 * DELETE statements (which is when commit occurs and locks are obtained/fail)
120 * - handle SQLITE_BUSY (e.g. other processes modifying db and hold locks)
121 * https://www.sqlite.org/lang_transaction.html
122 * https://www.sqlite.org/rescode.html#busy
123 * https://www.sqlite.org/c3ref/busy_handler.html
124 * https://www.sqlite.org/c3ref/busy_timeout.html
125 * - periodically execute query to delete expired locks
126 * (MOD_WEBDAV_SQLITE_LOCKS_DELETE_EXPIRED)
127 * (should defend against removing locks protecting long-running operations
128 * that are in progress on the server)
129 * - having all requests go through database, including GET and HEAD would allow
130 * for better transactional semantics, instead of the current race conditions
131 * inherent in multiple (and many) filesystem operations. All queries would
132 * go through database, which would map to objects on disk, and copy and move
133 * would simply be database entries to objects with reference counts and
134 * copy-on-write semantics (instead of potential hard-links on disk).
135 * lstat() information could also be stored in database. Right now, if a file
136 * is copied or moved or deleted, the status of the property update in the db
137 * is discarded, whether it succeeds or not, since file operation succeeded.
138 * (Then again, it might also be okay if props do not exist on a given file.)
139 * On the other hand, if everything went through database, then etag could be
140 * stored in database and could be updated upon PUT (or MOVE/COPY/DELETE).
141 * There would also need to be a way to trigger a rescan of filesystem to
142 * bring the database into sync with any out-of-band changes.
143 *
144 *
145 * notes:
146 *
147 * - lstat() used instead of stat_cache_*() since the stat_cache might have
148 * expired data, as stat_cache is not invalidated by outside modifications,
149 * such as WebDAV PUT method (unless FAM is used)
150 *
151 * - SQLite database can be run in WAL mode (https://sqlite.org/wal.html)
152 * though mod_webdav does not provide a mechanism to configure WAL.
153 * Instead, once lighttpd starts up mod_webdav and creates the database,
154 * set WAL mode on the database from the command and then restart lighttpd.
155 */
156
157
158 /* linkat() fstatat() unlinkat() fdopendir() NAME_MAX */
159 #if !defined(_XOPEN_SOURCE) || _XOPEN_SOURCE-0 < 700
160 #undef _XOPEN_SOURCE
161 #define _XOPEN_SOURCE 700
162 /* NetBSD dirent.h improperly hides fdopendir() (POSIX.1-2008) declaration
163 * which should be visible with _XOPEN_SOURCE 700 or _POSIX_C_SOURCE 200809L */
164 #ifdef __NetBSD__
165 #define _NETBSD_SOURCE
166 #endif
167 #endif
168 /* DT_UNKNOWN DTTOIF() */
169 #ifndef _GNU_SOURCE
170 #define _GNU_SOURCE
171 #endif
172 #ifdef __FreeBSD__ /* FreeBSD strict compliance hides libc extensions */
173 /*#define _XOPEN_SOURCE 700*//*(defined above)*/
174 #define _ISOC11_SOURCE 1
175 #define __BSD_VISIBLE 1
176 #endif
177
178 #include "first.h" /* first */
179 #include "sys-mmap.h"
180 #include <sys/types.h>
181 #include <sys/stat.h>
182 #include "sys-time.h"
183 #include <dirent.h>
184 #include <errno.h>
185 #include <fcntl.h>
186 #include <stdio.h> /* rename() */
187 #include <stdlib.h> /* strtol() */
188 #include <string.h>
189 #include <unistd.h> /* getpid() linkat() rmdir() unlinkat() */
190
191 #ifdef RENAME_NOREPLACE /*(renameat2() not well-supported yet)*/
192 #ifndef __ANDROID_API__ /*(not yet Android?)*/
193 #define HAVE_RENAMEAT2
194 #endif
195 #endif
196
197 #ifdef _DIRENT_HAVE_D_TYPE
198 #ifndef DTTOIF /* missing on Android? */
199 #define DTTOIF(dirtype) ((dirtype) << 12)
200 #endif
201 #endif
202
203 #ifdef HAVE_COPY_FILE_RANGE
204 #ifdef __FreeBSD__
205 typedef off_t loff_t;
206 #endif
207 #else
208 #ifdef __linux__
209 #include <sys/ioctl.h>
210 #include <linux/fs.h> /* ioctl(..., FICLONE, ...) */
211 #endif
212 #endif
213
214 #ifdef _WIN32
215 #define VC_EXTRALEAN
216 #define WIN32_LEAN_AND_MEAN
217 #include <windows.h> /* CopyFile() */
218 #endif
219
220 #ifdef AT_FDCWD
221 #ifndef _ATFILE_SOURCE
222 #define _ATFILE_SOURCE
223 #endif
224 #endif
225
226 #ifndef AT_SYMLINK_NOFOLLOW
227 #define AT_SYMLINK_NOFOLLOW 0
228 #endif
229
230 /* Note: filesystem access race conditions exist without _ATFILE_SOURCE */
231 #ifndef _ATFILE_SOURCE
232 /*(trigger linkat() fail to fallback logic in mod_webdav.c)*/
233 #define linkat(odfd,opath,ndfd,npath,flags) -1
234 #endif
235
236 #ifndef _D_EXACT_NAMLEN
237 #ifdef _DIRENT_HAVE_D_NAMLEN
238 #define _D_EXACT_NAMLEN(d) ((d)->d_namlen)
239 #else
240 #define _D_EXACT_NAMLEN(d) (strlen ((d)->d_name))
241 #endif
242 #endif
243
244 #ifndef PATH_MAX
245 #define PATH_MAX 4096
246 #endif
247
248 #ifndef MOD_WEBDAV_BUILD_MINIMAL
249 #if defined(HAVE_LIBXML_H) && defined(HAVE_SQLITE3_H)
250
251 #define USE_PROPPATCH
252 /* minor: libxml2 includes stdlib.h in headers, too */
253 #include <libxml/tree.h>
254 #include <libxml/parser.h>
255 #include <sqlite3.h>
256
257 #if defined(HAVE_LIBUUID) && defined(HAVE_UUID_UUID_H)
258 #define USE_LOCKS
259 #include <uuid/uuid.h>
260 #endif
261
262 #endif /* defined(HAVE_LIBXML_H) && defined(HAVE_SQLITE3_H) */
263 #endif /* MOD_WEBDAV_BUILD_MINIMAL */
264
265 #include "base.h"
266 #include "buffer.h"
267 #include "chunk.h"
268 #include "fdevent.h"
269 #include "http_chunk.h"
270 #include "http_date.h"
271 #include "http_etag.h"
272 #include "http_header.h"
273 #include "log.h"
274 #include "request.h"
275 #include "response.h" /* http_response_redirect_to_directory() */
276 #include "stat_cache.h" /* stat_cache_mimetype_by_ext() */
277
278 #include "plugin.h"
279
280 #if (defined(__linux__) || defined(__CYGWIN__)) && defined(O_TMPFILE)
281 static int has_proc_self_fd;
282 #endif
283
284 #define http_status_get(r) ((r)->http_status)
285 #define http_status_set_fin(r, code) ((r)->resp_body_finished = 1,\
286 (r)->handler_module = NULL, \
287 (r)->http_status = (code))
288 #define http_status_set(r, code) ((r)->http_status = (code))
289 #define http_status_unset(r) ((r)->http_status = 0)
290 #define http_status_is_set(r) (0 != (r)->http_status)
291 __attribute_cold__
292 __attribute_noinline__
http_status_set_error(request_st * const r,int status)293 static int http_status_set_error (request_st * const r, int status) {
294 return http_status_set_fin(r, status);
295 }
296
297 typedef physical physical_st;
298
299 INIT_FUNC(mod_webdav_init);
300 FREE_FUNC(mod_webdav_free);
301 SETDEFAULTS_FUNC(mod_webdav_set_defaults);
302 SERVER_FUNC(mod_webdav_worker_init);
303 URIHANDLER_FUNC(mod_webdav_uri_handler);
304 PHYSICALPATH_FUNC(mod_webdav_physical_handler);
305 SUBREQUEST_FUNC(mod_webdav_subrequest_handler);
306 REQUEST_FUNC(mod_webdav_handle_reset);
307
308 __attribute_cold__
309 int mod_webdav_plugin_init(plugin *p);
mod_webdav_plugin_init(plugin * p)310 int mod_webdav_plugin_init(plugin *p) {
311 p->version = LIGHTTPD_VERSION_ID;
312 p->name = "webdav";
313
314 p->init = mod_webdav_init;
315 p->cleanup = mod_webdav_free;
316 p->set_defaults = mod_webdav_set_defaults;
317 p->worker_init = mod_webdav_worker_init;
318 p->handle_uri_clean = mod_webdav_uri_handler;
319 p->handle_physical = mod_webdav_physical_handler;
320 p->handle_subrequest = mod_webdav_subrequest_handler;
321 p->handle_request_reset = mod_webdav_handle_reset;
322
323 return 0;
324 }
325
326
327 #define WEBDAV_FILE_MODE S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH|S_IWOTH
328 #define WEBDAV_DIR_MODE S_IRWXU|S_IRWXG|S_IRWXO
329
330 #define WEBDAV_FLAG_LC_NAMES 0x01
331 #define WEBDAV_FLAG_OVERWRITE 0x02
332 #define WEBDAV_FLAG_MOVE_RENAME 0x04
333 #define WEBDAV_FLAG_COPY_LINK 0x08
334 #define WEBDAV_FLAG_MOVE_XDEV 0x10
335 #define WEBDAV_FLAG_COPY_XDEV 0x20
336 #define WEBDAV_FLAG_NO_CLONE 0x40
337
338 #define webdav_xmlstrcmp_fixed(s, fixed) \
339 strncmp((const char *)(s), (fixed), sizeof(fixed))
340
341 #include <ctype.h> /* isupper() tolower() */
342 __attribute_noinline__
343 static void
webdav_str_len_to_lower(char * const ss,const uint32_t len)344 webdav_str_len_to_lower (char * const ss, const uint32_t len)
345 {
346 /*(caller must ensure that len not truncated to (int);
347 * for current intended use, NAME_MAX typically <= 255)*/
348 unsigned char * const restrict s = (unsigned char *)ss;
349 for (int i = 0; i < (int)len; ++i) {
350 if (isupper(s[i]))
351 s[i] = tolower(s[i]);
352 }
353 }
354
355 typedef struct {
356 #ifdef USE_PROPPATCH
357 sqlite3 *sqlh;
358 sqlite3_stmt *stmt_props_select_propnames;
359 sqlite3_stmt *stmt_props_select_props;
360 sqlite3_stmt *stmt_props_select_prop;
361 sqlite3_stmt *stmt_props_update_prop;
362 sqlite3_stmt *stmt_props_delete_prop;
363
364 sqlite3_stmt *stmt_props_copy;
365 sqlite3_stmt *stmt_props_move;
366 sqlite3_stmt *stmt_props_move_col;
367 sqlite3_stmt *stmt_props_delete;
368
369 sqlite3_stmt *stmt_locks_acquire;
370 sqlite3_stmt *stmt_locks_refresh;
371 sqlite3_stmt *stmt_locks_release;
372 sqlite3_stmt *stmt_locks_read;
373 sqlite3_stmt *stmt_locks_read_uri;
374 sqlite3_stmt *stmt_locks_read_uri_infinity;
375 sqlite3_stmt *stmt_locks_read_uri_members;
376 sqlite3_stmt *stmt_locks_delete_uri;
377 sqlite3_stmt *stmt_locks_delete_uri_col;
378 #else
379 int dummy;
380 #endif
381 } sql_config;
382
383 enum { /* opts bitflags */
384 MOD_WEBDAV_UNSAFE_PARTIAL_PUT_COMPAT = 0x1
385 ,MOD_WEBDAV_UNSAFE_PROPFIND_FOLLOW_SYMLINK = 0x2
386 ,MOD_WEBDAV_PROPFIND_DEPTH_INFINITY = 0x4
387 ,MOD_WEBDAV_CPYTMP_PARTIAL_PUT = 0x8
388 };
389
390 typedef struct {
391 unsigned short enabled;
392 unsigned short is_readonly;
393 unsigned short log_xml;
394 unsigned short opts;
395
396 sql_config *sql;
397 buffer *tmpb;
398 buffer *sqlite_db_name; /* not used after worker init */
399 } plugin_config;
400
401 typedef struct {
402 PLUGIN_DATA;
403 plugin_config defaults;
404 } plugin_data;
405
406
INIT_FUNC(mod_webdav_init)407 INIT_FUNC(mod_webdav_init) {
408 return ck_calloc(1, sizeof(plugin_data));
409 }
410
411
FREE_FUNC(mod_webdav_free)412 FREE_FUNC(mod_webdav_free) {
413 plugin_data * const p = (plugin_data *)p_d;
414 if (NULL == p->cvlist) return;
415 /* (init i to 0 if global context; to 1 to skip empty global context) */
416 for (int i = !p->cvlist[0].v.u2[1], used = p->nconfig; i < used; ++i) {
417 config_plugin_value_t *cpv = p->cvlist + p->cvlist[i].v.u2[0];
418 for (; -1 != cpv->k_id; ++cpv) {
419 switch (cpv->k_id) {
420 #ifdef USE_PROPPATCH
421 case 0: /* webdav.sqlite-db-name */
422 if (cpv->vtype == T_CONFIG_LOCAL) {
423 sql_config * const sql = cpv->v.v;
424 if (!sql->sqlh) {
425 free(sql);
426 continue;
427 }
428
429 sqlite3_finalize(sql->stmt_props_select_propnames);
430 sqlite3_finalize(sql->stmt_props_select_props);
431 sqlite3_finalize(sql->stmt_props_select_prop);
432 sqlite3_finalize(sql->stmt_props_update_prop);
433 sqlite3_finalize(sql->stmt_props_delete_prop);
434 sqlite3_finalize(sql->stmt_props_copy);
435 sqlite3_finalize(sql->stmt_props_move);
436 sqlite3_finalize(sql->stmt_props_move_col);
437 sqlite3_finalize(sql->stmt_props_delete);
438
439 sqlite3_finalize(sql->stmt_locks_acquire);
440 sqlite3_finalize(sql->stmt_locks_refresh);
441 sqlite3_finalize(sql->stmt_locks_release);
442 sqlite3_finalize(sql->stmt_locks_read);
443 sqlite3_finalize(sql->stmt_locks_read_uri);
444 sqlite3_finalize(sql->stmt_locks_read_uri_infinity);
445 sqlite3_finalize(sql->stmt_locks_read_uri_members);
446 sqlite3_finalize(sql->stmt_locks_delete_uri);
447 sqlite3_finalize(sql->stmt_locks_delete_uri_col);
448 sqlite3_close(sql->sqlh);
449 free(sql);
450 }
451 break;
452 #endif
453 default:
454 break;
455 }
456 }
457 }
458 }
459
460
mod_webdav_merge_config_cpv(plugin_config * const pconf,const config_plugin_value_t * const cpv)461 static void mod_webdav_merge_config_cpv(plugin_config * const pconf, const config_plugin_value_t * const cpv) {
462 switch (cpv->k_id) { /* index into static config_plugin_keys_t cpk[] */
463 case 0: /* webdav.sqlite-db-name */
464 if (cpv->vtype == T_CONFIG_LOCAL)
465 pconf->sql = cpv->v.v;
466 break;
467 case 1: /* webdav.activate */
468 pconf->enabled = (unsigned short)cpv->v.u;
469 break;
470 case 2: /* webdav.is-readonly */
471 pconf->is_readonly = (unsigned short)cpv->v.u;
472 break;
473 case 3: /* webdav.log-xml */
474 pconf->log_xml = (unsigned short)cpv->v.u;
475 break;
476 case 4: /* webdav.opts */
477 if (cpv->vtype == T_CONFIG_LOCAL)
478 pconf->opts = (unsigned short)cpv->v.u;
479 break;
480 default:/* should not happen */
481 return;
482 }
483 }
484
485
mod_webdav_merge_config(plugin_config * const pconf,const config_plugin_value_t * cpv)486 static void mod_webdav_merge_config(plugin_config * const pconf, const config_plugin_value_t *cpv) {
487 do {
488 mod_webdav_merge_config_cpv(pconf, cpv);
489 } while ((++cpv)->k_id != -1);
490 }
491
492
mod_webdav_patch_config(request_st * const r,plugin_data * const p,plugin_config * const pconf)493 static void mod_webdav_patch_config(request_st * const r, plugin_data * const p, plugin_config * const pconf) {
494 memcpy(pconf, &p->defaults, sizeof(plugin_config));
495 for (int i = 1, used = p->nconfig; i < used; ++i) {
496 if (config_check_cond(r, (uint32_t)p->cvlist[i].k_id))
497 mod_webdav_merge_config(pconf, p->cvlist + p->cvlist[i].v.u2[0]);
498 }
499 }
500
501
502 __attribute_cold__
503 static int mod_webdav_sqlite3_init (const char * restrict s, log_error_st *errh);
504
SETDEFAULTS_FUNC(mod_webdav_set_defaults)505 SETDEFAULTS_FUNC(mod_webdav_set_defaults) {
506 static const config_plugin_keys_t cpk[] = {
507 { CONST_STR_LEN("webdav.sqlite-db-name"),
508 T_CONFIG_STRING,
509 T_CONFIG_SCOPE_CONNECTION }
510 ,{ CONST_STR_LEN("webdav.activate"),
511 T_CONFIG_BOOL,
512 T_CONFIG_SCOPE_CONNECTION }
513 ,{ CONST_STR_LEN("webdav.is-readonly"),
514 T_CONFIG_BOOL,
515 T_CONFIG_SCOPE_CONNECTION }
516 ,{ CONST_STR_LEN("webdav.log-xml"),
517 T_CONFIG_BOOL,
518 T_CONFIG_SCOPE_CONNECTION }
519 ,{ CONST_STR_LEN("webdav.opts"),
520 T_CONFIG_ARRAY_KVANY,
521 T_CONFIG_SCOPE_CONNECTION }
522 ,{ NULL, 0,
523 T_CONFIG_UNSET,
524 T_CONFIG_SCOPE_UNSET }
525 };
526
527 plugin_data * const p = p_d;
528 if (!config_plugin_values_init(srv, p, cpk, "mod_webdav"))
529 return HANDLER_ERROR;
530
531 #ifdef USE_PROPPATCH
532 int sqlrc = sqlite3_config(SQLITE_CONFIG_SINGLETHREAD);
533 if (sqlrc != SQLITE_OK) {
534 log_error(srv->errh, __FILE__, __LINE__, "sqlite3_config(): %s",
535 sqlite3_errstr(sqlrc));
536 /*(performance option since our use is not threaded; not fatal)*/
537 /*return HANDLER_ERROR;*/
538 }
539 #endif
540
541 /* process and validate config directives
542 * (init i to 0 if global context; to 1 to skip empty global context) */
543 for (int i = !p->cvlist[0].v.u2[1]; i < p->nconfig; ++i) {
544 config_plugin_value_t *cpv = p->cvlist + p->cvlist[i].v.u2[0];
545 for (; -1 != cpv->k_id; ++cpv) {
546 switch (cpv->k_id) {
547 case 0: /* webdav.sqlite-db-name */
548 if (!buffer_is_blank(cpv->v.b)) {
549 if (!mod_webdav_sqlite3_init(cpv->v.b->ptr, srv->errh))
550 return HANDLER_ERROR;
551 }
552 break;
553 case 1: /* webdav.activate */
554 case 2: /* webdav.is-readonly */
555 case 3: /* webdav.log-xml */
556 break;
557 case 4: /* webdav.opts */
558 if (cpv->v.a->used) {
559 unsigned short opts = 0;
560 for (uint32_t j = 0, used = cpv->v.a->used; j < used; ++j) {
561 data_string *ds = (data_string *)cpv->v.a->data[j];
562 if (buffer_eq_slen(&ds->key,
563 CONST_STR_LEN("deprecated-unsafe-partial-put"))
564 && config_plugin_value_tobool((data_unset *)ds,0)) {
565 opts |= MOD_WEBDAV_UNSAFE_PARTIAL_PUT_COMPAT;
566 continue;
567 }
568 if (buffer_eq_slen(&ds->key,
569 CONST_STR_LEN("propfind-depth-infinity"))
570 && config_plugin_value_tobool((data_unset *)ds,0)) {
571 opts |= MOD_WEBDAV_PROPFIND_DEPTH_INFINITY;
572 continue;
573 }
574 if (buffer_eq_slen(&ds->key,
575 CONST_STR_LEN("unsafe-propfind-follow-symlink"))
576 && config_plugin_value_tobool((data_unset *)ds,0)) {
577 opts |= MOD_WEBDAV_UNSAFE_PROPFIND_FOLLOW_SYMLINK;
578 continue;
579 }
580 if (buffer_eq_slen(&ds->key,
581 CONST_STR_LEN("partial-put-copy-modify"))
582 && config_plugin_value_tobool((data_unset *)ds,0)) {
583 opts |= MOD_WEBDAV_CPYTMP_PARTIAL_PUT;
584 continue;
585 }
586 log_error(srv->errh, __FILE__, __LINE__,
587 "unrecognized webdav.opts: %s", ds->key.ptr);
588 return HANDLER_ERROR;
589 }
590 cpv->v.u = opts;
591 cpv->vtype = T_CONFIG_LOCAL;
592 }
593 break;
594 default:/* should not happen */
595 break;
596 }
597 }
598 }
599
600 p->defaults.tmpb = srv->tmp_buf;
601
602 /* initialize p->defaults from global config context */
603 if (p->nconfig > 0 && p->cvlist->v.u2[1]) {
604 const config_plugin_value_t *cpv = p->cvlist + p->cvlist->v.u2[0];
605 if (-1 != cpv->k_id)
606 mod_webdav_merge_config(&p->defaults, cpv);
607 }
608
609 #if (defined(__linux__) || defined(__CYGWIN__)) && defined(O_TMPFILE)
610 struct stat st;
611 has_proc_self_fd = (0 == stat("/proc/self/fd", &st));
612 #endif
613
614 return HANDLER_GO_ON;
615 }
616
617
URIHANDLER_FUNC(mod_webdav_uri_handler)618 URIHANDLER_FUNC(mod_webdav_uri_handler)
619 {
620 if (r->http_method != HTTP_METHOD_OPTIONS)
621 return HANDLER_GO_ON;
622
623 plugin_config pconf;
624 mod_webdav_patch_config(r, (plugin_data *)p_d, &pconf);
625 if (!pconf.enabled) return HANDLER_GO_ON;
626
627 /* [RFC4918] 18 DAV Compliance Classes */
628 #ifdef USE_LOCKS
629 if (pconf.sql)
630 http_header_response_set(r, HTTP_HEADER_OTHER,
631 CONST_STR_LEN("DAV"),
632 CONST_STR_LEN("1,2,3"));
633 else
634 #endif
635 http_header_response_set(r, HTTP_HEADER_OTHER,
636 CONST_STR_LEN("DAV"),
637 CONST_STR_LEN("1,3"));
638
639 /* instruct MS Office Web Folders to use DAV
640 * (instead of MS FrontPage Extensions)
641 * http://www.zorched.net/2006/03/01/more-webdav-tips-tricks-and-bugs/ */
642 http_header_response_set(r, HTTP_HEADER_OTHER,
643 CONST_STR_LEN("MS-Author-Via"),
644 CONST_STR_LEN("DAV"));
645
646 if (pconf.is_readonly)
647 http_header_response_append(r, HTTP_HEADER_ALLOW,
648 CONST_STR_LEN("Allow"),
649 CONST_STR_LEN("PROPFIND"));
650 #ifdef USE_PROPPATCH
651 else if (pconf.sql)
652 http_header_response_append(r, HTTP_HEADER_ALLOW,
653 CONST_STR_LEN("Allow"),
654 #ifdef USE_LOCKS
655 CONST_STR_LEN(
656 "PROPFIND, DELETE, MKCOL, PUT, MOVE, COPY, PROPPATCH, LOCK, UNLOCK")
657 #else
658 CONST_STR_LEN(
659 "PROPFIND, DELETE, MKCOL, PUT, MOVE, COPY, PROPPATCH")
660 #endif
661 );
662 #endif
663 else
664 http_header_response_append(r, HTTP_HEADER_ALLOW,
665 CONST_STR_LEN("Allow"),
666 CONST_STR_LEN(
667 "PROPFIND, DELETE, MKCOL, PUT, MOVE, COPY")
668 );
669
670 return HANDLER_GO_ON;
671 }
672
673
674 static void
webdav_double_buffer(request_st * const r,buffer * const b)675 webdav_double_buffer (request_st * const r, buffer * const b)
676 {
677 /* send parts of XML to r->write_queue; surrounding XML tags added later.
678 * http_chunk_append_buffer() is safe to use here since r->resp_body_started
679 * has not been set, so r->resp_send_chunked can not be set yet */
680 if (buffer_clen(b) > 60000) {
681 http_chunk_append_buffer(r, b); /*(might move/steal/reset buffer)*/
682 /*buffer_clear(b);*//*http_chunk_append_buffer() clears*/
683 }
684 }
685
686
687 #ifdef USE_LOCKS
688
689 typedef struct webdav_lockdata_wr {
690 buffer locktoken;
691 buffer lockroot;
692 buffer ownerinfo;
693 buffer *owner; /* NB: caller must provide writable storage */
694 const buffer *lockscope; /* future: might use enum, store int in db */
695 const buffer *locktype; /* future: might use enum, store int in db */
696 int depth;
697 int timeout; /* offset from now, not absolute time_t */
698 } webdav_lockdata_wr;
699
700 typedef struct webdav_lockdata {
701 buffer locktoken;
702 buffer lockroot;
703 buffer ownerinfo;
704 const buffer *owner;
705 const buffer *lockscope; /* future: might use enum, store int in db */
706 const buffer *locktype; /* future: might use enum, store int in db */
707 int depth;
708 int timeout; /* offset from now, not absolute time_t */
709 } webdav_lockdata;
710
711 typedef struct { const char *ptr; uint32_t used; uint32_t size; } tagb;
712
713 static const tagb lockscope_exclusive =
714 { "exclusive", sizeof("exclusive"), 0 };
715 static const tagb lockscope_shared =
716 { "shared", sizeof("shared"), 0 };
717 static const tagb locktype_write =
718 { "write", sizeof("write"), 0 };
719
720 #endif
721
722 typedef struct {
723 const char *ns;
724 const char *name;
725 uint32_t nslen;
726 uint32_t namelen;
727 } webdav_property_name;
728
729 typedef struct {
730 webdav_property_name *ptr;
731 int used;
732 } webdav_property_names;
733
734 /*
735 * http://www.w3.org/TR/1998/NOTE-XML-data-0105/
736 * The datatype attribute "dt" is defined in the namespace named
737 * "urn:uuid:C2F41010-65B3-11d1-A29F-00AA00C14882/".
738 * (See the XML Namespaces Note at the W3C site for details of namespaces.)
739 * The full URN of the attribute is
740 * "urn:uuid:C2F41010-65B3-11d1-A29F-00AA00C14882/dt".
741 * http://www.w3.org/TR/1998/NOTE-xml-names-0119
742 * http://www.w3.org/TR/1998/WD-xml-names-19980327
743 * http://lists.xml.org/archives/xml-dev/200101/msg00924.html
744 * http://lists.xml.org/archives/xml-dev/200101/msg00929.html
745 * http://lists.xml.org/archives/xml-dev/200101/msg00930.html
746 * (Microsoft) Namespace Guidelines
747 * https://msdn.microsoft.com/en-us/library/ms879470%28v=exchg.65%29.aspx
748 * (Microsoft) XML Persistence Format
749 * https://msdn.microsoft.com/en-us/library/ms676547%28v=vs.85%29.aspx
750 * http://www.xml.com/pub/a/2002/06/26/vocabularies.html
751 * The "Uuid" namespaces is the namespace
752 * "uuid:C2F41010-65B3-11d1-A29F-00AA00C14882",
753 * mainly found in association with the MS Office
754 * namespace on the http://www.omg.org website.
755 * http://www.data2type.de/en/xml-xslt-xslfo/wordml/wordml-introduction/the-root-element/
756 * xmlns:dt="uuid:C2F41010-65B3-11d1-A29F-00AA00C14882"
757 * By using the prefix dt, the namespace declares an attribute which
758 * determines the data type of a value. The name of the underlying schema
759 * is dt.xsd and it can be found in the folder for Excel schemas.
760 */
761 #define MOD_WEBDAV_XMLNS_NS0 "xmlns:ns0=\"urn:uuid:c2f41010-65b3-11d1-a29f-00aa00c14882/\""
762
763
764 __attribute_cold__
765 __attribute_noinline__
766 static void
webdav_xml_log_response(request_st * const r)767 webdav_xml_log_response (request_st * const r)
768 {
769 chunkqueue * const cq = &r->write_queue;
770 log_error_st * const errh = r->conf.errh;
771 if (chunkqueue_length(cq) <= 65536)
772 chunkqueue_read_squash(cq, errh);
773 char *s;
774 uint32_t len;
775 for (chunk *c = cq->first; c; c = c->next) {
776 switch (c->type) {
777 case MEM_CHUNK:
778 s = c->mem->ptr + c->offset;
779 len = buffer_clen(c->mem) - (uint32_t)c->offset;
780 break;
781 case FILE_CHUNK:
782 /*(safe to mmap tempfiles from response XML)*/
783 /*(response body provided in temporary file, so ok to mmap().
784 * Otherwise, must access through sys_setjmp_eval3()) */
785 /*(tempfiles (and xml response) should easily fit in uint32_t
786 * and are not expected to already be mmap'd. Avoid >= 128k
787 * requirement of chunkqueue_chunk_file_view() by using viewadj)*/
788 len = (uint32_t)(c->file.length - c->offset);
789 {
790 const chunk_file_view * const restrict cfv =
791 chunkqueue_chunk_file_viewadj(c, (off_t)len, r->conf.errh);
792 s = (cfv && chunk_file_view_dlen(cfv, c->offset) >= (off_t)len)
793 ? chunk_file_view_dptr(cfv, c->offset)
794 : NULL;
795 }
796 if (s == NULL) continue;
797 break;
798 default:
799 continue;
800 }
801 log_error(errh, __FILE__, __LINE__, "XML-response-body: %.*s",
802 (int)len, s);
803 }
804 }
805
806
807 static void
webdav_xml_doctype(buffer * const b,request_st * const r)808 webdav_xml_doctype (buffer * const b, request_st * const r)
809 {
810 http_header_response_set(r, HTTP_HEADER_CONTENT_TYPE,
811 CONST_STR_LEN("Content-Type"),
812 CONST_STR_LEN("application/xml;charset=utf-8"));
813
814 buffer_copy_string_len(b, CONST_STR_LEN(
815 "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"));
816 }
817
818
819 static void
webdav_xml_prop(buffer * const b,const webdav_property_name * const prop,const char * const value,const uint32_t vlen)820 webdav_xml_prop (buffer * const b,
821 const webdav_property_name * const prop,
822 const char * const value, const uint32_t vlen)
823 {
824 if (0 == vlen) {
825 struct const_iovec iov[] = {
826 { CONST_STR_LEN("<") }
827 ,{ prop->name, prop->namelen }
828 ,{ CONST_STR_LEN(" xmlns=\"") }
829 ,{ prop->ns, prop->nslen }
830 ,{ CONST_STR_LEN("\"/>") }
831 };
832 buffer_append_iovec(b, iov, sizeof(iov)/sizeof(*iov));
833 }
834 else {
835 struct const_iovec iov[] = {
836 { CONST_STR_LEN("<") }
837 ,{ prop->name, prop->namelen }
838 ,{ CONST_STR_LEN(" xmlns=\"") }
839 ,{ prop->ns, prop->nslen }
840 ,{ CONST_STR_LEN("\">") }
841 ,{ value, vlen }
842 ,{ CONST_STR_LEN("</") }
843 ,{ prop->name, prop->namelen }
844 ,{ CONST_STR_LEN(">") }
845 };
846 buffer_append_iovec(b, iov, sizeof(iov)/sizeof(*iov));
847 }
848 }
849
850
851 static void
webdav_xml_href(buffer * const b,const buffer * const href)852 webdav_xml_href (buffer * const b, const buffer * const href)
853 {
854 buffer_append_string_len(b, CONST_STR_LEN(
855 "<D:href>"));
856 buffer_append_string_encoded(b, BUF_PTR_LEN(href), ENCODING_REL_URI);
857 buffer_append_string_len(b, CONST_STR_LEN(
858 "</D:href>\n"));
859 }
860
861
862 static void
webdav_xml_status(buffer * const b,const int status)863 webdav_xml_status (buffer * const b, const int status)
864 {
865 buffer_append_string_len(b, CONST_STR_LEN(
866 "<D:status>HTTP/1.1 "));
867 http_status_append(b, status);
868 buffer_append_string_len(b, CONST_STR_LEN(
869 "</D:status>\n"));
870 }
871
872
873 #ifdef USE_PROPPATCH
874 __attribute_cold__
875 static void
webdav_xml_propstat_protected(buffer * const b,const char * const propname,const uint32_t len,const int status)876 webdav_xml_propstat_protected (buffer * const b, const char * const propname,
877 const uint32_t len, const int status)
878 {
879 buffer_append_str3(b, CONST_STR_LEN(
880 "<D:propstat>\n"
881 "<D:prop><DAV:"),
882 propname, len, CONST_STR_LEN(
883 "/></D:prop>\n"
884 "<D:error><DAV:cannot-modify-protected-property/></D:error>\n"));
885 webdav_xml_status(b, status); /* 403 */
886 buffer_append_string_len(b, CONST_STR_LEN(
887 "</D:propstat>\n"));
888 }
889 #endif
890
891
892 #ifdef USE_PROPPATCH
893 __attribute_cold__
894 static void
webdav_xml_propstat_status(buffer * const b,const char * const ns,const char * const name,const int status)895 webdav_xml_propstat_status (buffer * const b, const char * const ns,
896 const char * const name, const int status)
897 {
898 struct const_iovec iov[] = {
899 { CONST_STR_LEN(
900 "<D:propstat>\n"
901 "<D:prop><") }
902 ,{ ns, strlen(ns) }
903 ,{ name, strlen(name) }
904 ,{ CONST_STR_LEN(
905 "/></D:prop>\n") }
906 };
907 buffer_append_iovec(b, iov, sizeof(iov)/sizeof(*iov));
908 webdav_xml_status(b, status);
909 buffer_append_string_len(b, CONST_STR_LEN(
910 "</D:propstat>\n"));
911 }
912 #endif
913
914
915 static void
webdav_xml_propstat(buffer * const b,buffer * const value,const int status)916 webdav_xml_propstat (buffer * const b, buffer * const value, const int status)
917 {
918 buffer_append_str3(b, CONST_STR_LEN(
919 "<D:propstat>\n"
920 "<D:prop>\n"),
921 BUF_PTR_LEN(value), CONST_STR_LEN(
922 "</D:prop>\n"));
923 webdav_xml_status(b, status);
924 buffer_append_string_len(b, CONST_STR_LEN(
925 "</D:propstat>\n"));
926 }
927
928
929 __attribute_cold__
930 static void
webdav_xml_response_status(request_st * const r,const buffer * const href,const int status)931 webdav_xml_response_status (request_st * const r,
932 const buffer * const href,
933 const int status)
934 {
935 buffer * const b = chunk_buffer_acquire();
936 buffer_append_string_len(b, CONST_STR_LEN(
937 "<D:response>\n"));
938 webdav_xml_href(b, href);
939 webdav_xml_status(b, status);
940 buffer_append_string_len(b, CONST_STR_LEN(
941 "</D:response>\n"));
942 /*(under extreme error conditions, write() to tempfile for each error)*/
943 http_chunk_append_buffer(r, b); /*(might move/steal/reset buffer)*/
944 chunk_buffer_release(b);
945 }
946
947
948 #ifdef USE_LOCKS
949 static void
webdav_xml_activelock(buffer * const b,const webdav_lockdata * const lockdata,const char * const tbuf,uint32_t tbuf_len)950 webdav_xml_activelock (buffer * const b,
951 const webdav_lockdata * const lockdata,
952 const char * const tbuf, uint32_t tbuf_len)
953 {
954 struct const_iovec iov[] = {
955 { CONST_STR_LEN(
956 "<D:activelock>\n"
957 "<D:lockscope>"
958 "<D:") }
959 ,{ BUF_PTR_LEN(lockdata->lockscope) }
960 ,{ CONST_STR_LEN(
961 "/>"
962 "</D:lockscope>\n"
963 "<D:locktype>"
964 "<D:") }
965 ,{ BUF_PTR_LEN(lockdata->locktype) }
966 ,{ CONST_STR_LEN(
967 "/>"
968 "</D:locktype>\n"
969 "<D:depth>") }
970 ,{ CONST_STR_LEN(
971 "infinity") } /*(iov[5] might be changed in below)*/
972 ,{ CONST_STR_LEN(
973 "</D:depth>\n"
974 "<D:timeout>") }
975 };
976 if (0 == lockdata->depth) {
977 iov[5].iov_base = "0";
978 iov[5].iov_len = sizeof("0")-1;
979 }
980 buffer_append_iovec(b, iov, sizeof(iov)/sizeof(*iov));
981 if (0 != tbuf_len)
982 buffer_append_string_len(b, tbuf, tbuf_len); /* "Second-..." */
983 else {
984 buffer_append_string_len(b, CONST_STR_LEN("Second-"));
985 buffer_append_int(b, lockdata->timeout);
986 }
987 struct const_iovec iovb[] = {
988 { CONST_STR_LEN(
989 "</D:timeout>\n"
990 "<D:owner>") }
991 ,{ "", 0 } /*(iov[1] filled in below)*/
992 ,{ CONST_STR_LEN(
993 "</D:owner>\n"
994 "<D:locktoken>\n"
995 "<D:href>") } /*webdav_xml_href_raw();*/
996 ,{ BUF_PTR_LEN(&lockdata->locktoken) } /*(as-is; not URL-encoded)*/
997 ,{ CONST_STR_LEN(
998 "</D:href>\n"
999 "</D:locktoken>\n"
1000 "<D:lockroot>\n") }
1001 };
1002 if (!buffer_is_blank(&lockdata->ownerinfo)) {
1003 iov[1].iov_base = lockdata->ownerinfo.ptr;
1004 iov[1].iov_len = buffer_clen(&lockdata->ownerinfo);
1005 }
1006 buffer_append_iovec(b, iovb, sizeof(iovb)/sizeof(*iovb));
1007 webdav_xml_href(b, &lockdata->lockroot);
1008 buffer_append_string_len(b, CONST_STR_LEN(
1009 "</D:lockroot>\n"
1010 "</D:activelock>\n"));
1011 }
1012 #endif
1013
1014
1015 static void
webdav_xml_doc_multistatus(request_st * const r,const plugin_config * const pconf)1016 webdav_xml_doc_multistatus (request_st * const r,
1017 const plugin_config * const pconf)
1018 {
1019 http_status_set_fin(r, 207); /* Multi-status */
1020
1021 chunkqueue * const cq = &r->write_queue;
1022 buffer * const b = chunkqueue_prepend_buffer_open(cq);
1023 webdav_xml_doctype(b, r);
1024 buffer_append_string_len(b, CONST_STR_LEN(
1025 "<D:multistatus xmlns:D=\"DAV:\">\n"));
1026 chunkqueue_prepend_buffer_commit(cq);
1027
1028 chunkqueue_append_mem(cq, CONST_STR_LEN(
1029 "</D:multistatus>\n"));
1030
1031 if (pconf->log_xml)
1032 webdav_xml_log_response(r);
1033 }
1034
1035
1036 #ifdef USE_PROPPATCH
1037 static void
webdav_xml_doc_multistatus_response(request_st * const r,const plugin_config * const pconf,buffer * const ms)1038 webdav_xml_doc_multistatus_response (request_st * const r,
1039 const plugin_config * const pconf,
1040 buffer * const ms)
1041 {
1042 http_status_set_fin(r, 207); /* Multi-status */
1043
1044 chunkqueue * const cq = &r->write_queue;
1045 buffer * const b = chunkqueue_prepend_buffer_open(cq);
1046 webdav_xml_doctype(b, r);
1047 buffer_append_string_len(b, CONST_STR_LEN(
1048 "<D:multistatus xmlns:D=\"DAV:\">\n"
1049 "<D:response>\n"));
1050 webdav_xml_href(b, &r->physical.rel_path);
1051 chunkqueue_prepend_buffer_commit(cq);
1052 chunkqueue_append_buffer(cq, ms); /*(might move/steal/reset buffer)*/
1053 chunkqueue_append_mem(cq, CONST_STR_LEN(
1054 "</D:response>\n"
1055 "</D:multistatus>\n"));
1056
1057 if (pconf->log_xml)
1058 webdav_xml_log_response(r);
1059 }
1060 #endif
1061
1062
1063 #ifdef USE_LOCKS
1064 static void
webdav_xml_doc_lock_acquired(request_st * const r,const plugin_config * const pconf,const webdav_lockdata * const lockdata)1065 webdav_xml_doc_lock_acquired (request_st * const r,
1066 const plugin_config * const pconf,
1067 const webdav_lockdata * const lockdata)
1068 {
1069 /*(http_status is set by caller to 200 OK or 201 Created)*/
1070
1071 char tbuf[32] = "Second-";
1072 const uint32_t tbuf_len = sizeof("Second-")-1 +
1073 li_itostrn(tbuf+sizeof("Second-")-1, sizeof(tbuf)-(sizeof("Second-")-1),
1074 lockdata->timeout);
1075 http_header_response_set(r, HTTP_HEADER_OTHER,
1076 CONST_STR_LEN("Timeout"),
1077 tbuf, tbuf_len);
1078
1079 buffer * const b =
1080 chunkqueue_append_buffer_open_sz(&r->write_queue, 1024);
1081
1082 webdav_xml_doctype(b, r);
1083 buffer_append_string_len(b, CONST_STR_LEN(
1084 "<D:prop xmlns:D=\"DAV:\">\n"
1085 "<D:lockdiscovery>\n"));
1086 webdav_xml_activelock(b, lockdata, tbuf, tbuf_len);
1087 buffer_append_string_len(b, CONST_STR_LEN(
1088 "</D:lockdiscovery>\n"
1089 "</D:prop>\n"));
1090
1091 chunkqueue_append_buffer_commit(&r->write_queue);
1092
1093 if (pconf->log_xml)
1094 webdav_xml_log_response(r);
1095 }
1096 #endif
1097
1098
1099 /*
1100 * [RFC4918] 16 Precondition/Postcondition XML Elements
1101 */
1102
1103
1104 /*
1105 * 403 Forbidden
1106 * "<D:error><DAV:cannot-modify-protected-property/></D:error>"
1107 *
1108 * 403 Forbidden
1109 * "<D:error><DAV:no-external-entities/></D:error>"
1110 *
1111 * 409 Conflict
1112 * "<D:error><DAV:preserved-live-properties/></D:error>"
1113 */
1114
1115
1116 __attribute_cold__
1117 static void
webdav_xml_doc_error_propfind_finite_depth(request_st * const r)1118 webdav_xml_doc_error_propfind_finite_depth (request_st * const r)
1119 {
1120 http_status_set(r, 403); /* Forbidden */
1121 r->resp_body_finished = 1;
1122
1123 buffer * const b =
1124 chunkqueue_append_buffer_open_sz(&r->write_queue, 256);
1125 webdav_xml_doctype(b, r);
1126 buffer_append_string_len(b, CONST_STR_LEN(
1127 "<D:error><DAV:propfind-finite-depth/></D:error>\n"));
1128 chunkqueue_append_buffer_commit(&r->write_queue);
1129 }
1130
1131
1132 #ifdef USE_LOCKS
1133 __attribute_cold__
1134 static void
webdav_xml_doc_error_lock_token_matches_request_uri(request_st * const r)1135 webdav_xml_doc_error_lock_token_matches_request_uri (request_st * const r)
1136 {
1137 http_status_set(r, 409); /* Conflict */
1138 r->resp_body_finished = 1;
1139
1140 buffer * const b =
1141 chunkqueue_append_buffer_open_sz(&r->write_queue, 256);
1142 webdav_xml_doctype(b, r);
1143 buffer_append_string_len(b, CONST_STR_LEN(
1144 "<D:error><DAV:lock-token-matches-request-uri/></D:error>\n"));
1145 chunkqueue_append_buffer_commit(&r->write_queue);
1146 }
1147 #endif
1148
1149
1150 #ifdef USE_LOCKS
1151 __attribute_cold__
1152 static void
webdav_xml_doc_423_locked(request_st * const r,buffer * const hrefs,const char * const errtag,const uint32_t errtaglen)1153 webdav_xml_doc_423_locked (request_st * const r, buffer * const hrefs,
1154 const char * const errtag, const uint32_t errtaglen)
1155 {
1156 http_status_set(r, 423); /* Locked */
1157 r->resp_body_finished = 1;
1158
1159 chunkqueue * const cq = &r->write_queue;
1160 buffer * const b = chunkqueue_prepend_buffer_open(cq);
1161 webdav_xml_doctype(b, r);
1162 buffer_append_str3(b,
1163 CONST_STR_LEN(
1164 "<D:error xmlns:D=\"DAV:\">\n"
1165 "<D:"),
1166 errtag, errtaglen,
1167 CONST_STR_LEN(
1168 ">\n"));
1169 chunkqueue_prepend_buffer_commit(cq);
1170 buffer_append_str3(hrefs,
1171 CONST_STR_LEN(
1172 "</D:"),
1173 errtag, errtaglen,
1174 CONST_STR_LEN(
1175 ">\n"
1176 "</D:error>\n"));
1177 chunkqueue_append_buffer(cq, hrefs); /*(might move/steal/reset buffer)*/
1178 }
1179 #endif
1180
1181
1182 #ifdef USE_LOCKS
1183 __attribute_cold__
1184 static void
webdav_xml_doc_error_lock_token_submitted(request_st * const r,buffer * const hrefs)1185 webdav_xml_doc_error_lock_token_submitted (request_st * const r,
1186 buffer * const hrefs)
1187 {
1188 webdav_xml_doc_423_locked(r, hrefs,
1189 CONST_STR_LEN("lock-token-submitted"));
1190 }
1191 #endif
1192
1193
1194 #ifdef USE_LOCKS
1195 __attribute_cold__
1196 static void
webdav_xml_doc_error_no_conflicting_lock(request_st * const r,buffer * const hrefs)1197 webdav_xml_doc_error_no_conflicting_lock (request_st * const r,
1198 buffer * const hrefs)
1199 {
1200 webdav_xml_doc_423_locked(r, hrefs,
1201 CONST_STR_LEN("no-conflicting-lock"));
1202 }
1203 #endif
1204
1205
1206 #ifdef USE_PROPPATCH
1207
1208 #define MOD_WEBDAV_SQLITE_CREATE_TABLE_PROPERTIES \
1209 "CREATE TABLE IF NOT EXISTS properties (" \
1210 " resource TEXT NOT NULL," \
1211 " prop TEXT NOT NULL," \
1212 " ns TEXT NOT NULL," \
1213 " value TEXT NOT NULL," \
1214 " PRIMARY KEY(resource, prop, ns))"
1215
1216 #define MOD_WEBDAV_SQLITE_CREATE_TABLE_LOCKS \
1217 "CREATE TABLE IF NOT EXISTS locks (" \
1218 " locktoken TEXT NOT NULL," \
1219 " resource TEXT NOT NULL," \
1220 " lockscope TEXT NOT NULL," \
1221 " locktype TEXT NOT NULL," \
1222 " owner TEXT NOT NULL," \
1223 " ownerinfo TEXT NOT NULL," \
1224 " depth INT NOT NULL," \
1225 " timeout TIMESTAMP NOT NULL," \
1226 " PRIMARY KEY(locktoken))"
1227
1228 #define MOD_WEBDAV_SQLITE_PROPS_SELECT_PROPNAMES \
1229 "SELECT prop, ns FROM properties WHERE resource = ?"
1230
1231 #define MOD_WEBDAV_SQLITE_PROPS_SELECT_PROP \
1232 "SELECT value FROM properties WHERE resource = ? AND prop = ? AND ns = ?"
1233
1234 #define MOD_WEBDAV_SQLITE_PROPS_SELECT_PROPS \
1235 "SELECT prop, ns, value FROM properties WHERE resource = ?"
1236
1237 #define MOD_WEBDAV_SQLITE_PROPS_UPDATE_PROP \
1238 "REPLACE INTO properties (resource, prop, ns, value) VALUES (?, ?, ?, ?)"
1239
1240 #define MOD_WEBDAV_SQLITE_PROPS_DELETE_PROP \
1241 "DELETE FROM properties WHERE resource = ? AND prop = ? AND ns = ?"
1242
1243 #define MOD_WEBDAV_SQLITE_PROPS_DELETE \
1244 "DELETE FROM properties WHERE resource = ?"
1245
1246 #define MOD_WEBDAV_SQLITE_PROPS_COPY \
1247 "INSERT INTO properties" \
1248 " SELECT ?, prop, ns, value FROM properties WHERE resource = ?"
1249
1250 #define MOD_WEBDAV_SQLITE_PROPS_MOVE \
1251 "UPDATE OR REPLACE properties SET resource = ? WHERE resource = ?"
1252
1253 #define MOD_WEBDAV_SQLITE_PROPS_MOVE_COL \
1254 "UPDATE OR REPLACE properties SET resource = ? || SUBSTR(resource, ?)" \
1255 " WHERE SUBSTR(resource, 1, ?) = ?"
1256
1257 #define MOD_WEBDAV_SQLITE_LOCKS_ACQUIRE \
1258 "INSERT INTO locks" \
1259 " (locktoken,resource,lockscope,locktype,owner,ownerinfo,depth,timeout)" \
1260 " VALUES (?,?,?,?,?,?,?, CURRENT_TIME + ?)"
1261
1262 #define MOD_WEBDAV_SQLITE_LOCKS_REFRESH \
1263 "UPDATE locks SET timeout = CURRENT_TIME + ? WHERE locktoken = ?"
1264
1265 #define MOD_WEBDAV_SQLITE_LOCKS_RELEASE \
1266 "DELETE FROM locks WHERE locktoken = ?"
1267
1268 #define MOD_WEBDAV_SQLITE_LOCKS_READ \
1269 "SELECT resource, owner, depth" \
1270 " FROM locks WHERE locktoken = ?"
1271
1272 #define MOD_WEBDAV_SQLITE_LOCKS_READ_URI \
1273 "SELECT" \
1274 " locktoken,resource,lockscope,locktype,owner,ownerinfo,depth," \
1275 "timeout - CURRENT_TIME" \
1276 " FROM locks WHERE resource = ?"
1277
1278 #define MOD_WEBDAV_SQLITE_LOCKS_READ_URI_INFINITY \
1279 "SELECT" \
1280 " locktoken,resource,lockscope,locktype,owner,ownerinfo,depth," \
1281 "timeout - CURRENT_TIME" \
1282 " FROM locks" \
1283 " WHERE depth = -1 AND resource = SUBSTR(?, 1, LENGTH(resource))"
1284
1285 #define MOD_WEBDAV_SQLITE_LOCKS_READ_URI_MEMBERS \
1286 "SELECT" \
1287 " locktoken,resource,lockscope,locktype,owner,ownerinfo,depth," \
1288 "timeout - CURRENT_TIME" \
1289 " FROM locks WHERE SUBSTR(resource, 1, ?) = ?"
1290
1291 #define MOD_WEBDAV_SQLITE_LOCKS_DELETE_URI \
1292 "DELETE FROM locks WHERE resource = ?"
1293
1294 #define MOD_WEBDAV_SQLITE_LOCKS_DELETE_URI_COL \
1295 "DELETE FROM locks WHERE SUBSTR(resource, 1, ?) = ?"
1296 /*"DELETE FROM locks WHERE locktoken LIKE ? || '%'"*/
1297
1298 /*(not currently used)*/
1299 #define MOD_WEBDAV_SQLITE_LOCKS_DELETE_EXPIRED \
1300 "DELETE FROM locks WHERE timeout < CURRENT_TIME"
1301
1302 #endif /* USE_PROPPATCH */
1303
1304
1305 __attribute_cold__
1306 static int
mod_webdav_sqlite3_init(const char * const restrict dbname,log_error_st * const errh)1307 mod_webdav_sqlite3_init (const char * const restrict dbname,
1308 log_error_st * const errh)
1309 {
1310 #ifndef USE_PROPPATCH
1311
1312 log_error(errh, __FILE__, __LINE__,
1313 "Sorry, no sqlite3 and libxml2 support include, "
1314 "compile with --with-webdav-props");
1315 UNUSED(dbname);
1316 return 0;
1317
1318 #else /* USE_PROPPATCH */
1319
1320 /*(expects (plugin_config *s) (log_error_st *errh) (char *err))*/
1321 #define MOD_WEBDAV_SQLITE_CREATE_TABLE(query, label) \
1322 if (sqlite3_exec(sqlh, query, NULL, NULL, &err) != SQLITE_OK) { \
1323 if (0 != strcmp(err, "table " label " already exists")) { \
1324 log_error(errh, __FILE__, __LINE__, \
1325 "create table " label ": %s", err); \
1326 sqlite3_free(err); \
1327 sqlite3_close(sqlh); \
1328 return 0; \
1329 } \
1330 sqlite3_free(err); \
1331 }
1332
1333 sqlite3 *sqlh;
1334 int sqlrc = sqlite3_open_v2(dbname, &sqlh,
1335 SQLITE_OPEN_READWRITE|SQLITE_OPEN_CREATE, NULL);
1336 if (sqlrc != SQLITE_OK) {
1337 log_error(errh, __FILE__, __LINE__, "sqlite3_open() '%s': %s",
1338 dbname, sqlh ? sqlite3_errmsg(sqlh) : sqlite3_errstr(sqlrc));
1339 if (sqlh) sqlite3_close(sqlh);
1340 return 0;
1341 }
1342
1343 char *err = NULL;
1344 MOD_WEBDAV_SQLITE_CREATE_TABLE( MOD_WEBDAV_SQLITE_CREATE_TABLE_PROPERTIES,
1345 "properties");
1346 MOD_WEBDAV_SQLITE_CREATE_TABLE( MOD_WEBDAV_SQLITE_CREATE_TABLE_LOCKS,
1347 "locks");
1348
1349 /* add ownerinfo column to locks table (update older mod_webdav sqlite db)
1350 * (could check if 'PRAGMA user_version;' is 0, add column, and increment)*/
1351 #define MOD_WEBDAV_SQLITE_SELECT_LOCKS_OWNERINFO_TEST \
1352 "SELECT COUNT(*) FROM locks WHERE ownerinfo = \"\""
1353 #define MOD_WEBDAV_SQLITE_ALTER_TABLE_LOCKS \
1354 "ALTER TABLE locks ADD COLUMN ownerinfo TEXT NOT NULL DEFAULT \"\""
1355 if (sqlite3_exec(sqlh, MOD_WEBDAV_SQLITE_SELECT_LOCKS_OWNERINFO_TEST,
1356 NULL, NULL, &err) != SQLITE_OK) {
1357 sqlite3_free(err); /* "no such column: ownerinfo" */
1358 if (sqlite3_exec(sqlh, MOD_WEBDAV_SQLITE_ALTER_TABLE_LOCKS,
1359 NULL, NULL, &err) != SQLITE_OK) {
1360 log_error(errh, __FILE__, __LINE__, "alter table locks: %s", err);
1361 sqlite3_free(err);
1362 sqlite3_close(sqlh);
1363 return 0;
1364 }
1365 }
1366
1367 sqlite3_close(sqlh);
1368 return 1;
1369
1370 #endif /* USE_PROPPATCH */
1371 }
1372
1373
1374 #ifdef USE_PROPPATCH
1375 __attribute_cold__
1376 static int
mod_webdav_sqlite3_prep(sql_config * const restrict sql,const char * const sqlite_db_name,log_error_st * const errh)1377 mod_webdav_sqlite3_prep (sql_config * const restrict sql,
1378 const char * const sqlite_db_name,
1379 log_error_st * const errh)
1380 {
1381 /*(expects (plugin_config *s) (log_error_st *errh))*/
1382 #define MOD_WEBDAV_SQLITE_PREPARE_STMT(query, stmt) \
1383 if (sqlite3_prepare_v2(sql->sqlh, query, sizeof(query)-1, &stmt, NULL) \
1384 != SQLITE_OK) { \
1385 log_error(errh, __FILE__, __LINE__, "sqlite3_prepare_v2(): %s", \
1386 sqlite3_errmsg(sql->sqlh)); \
1387 return 0; \
1388 }
1389
1390 int sqlrc = sqlite3_open_v2(sqlite_db_name, &sql->sqlh,
1391 SQLITE_OPEN_READWRITE, NULL);
1392 if (sqlrc != SQLITE_OK) {
1393 log_error(errh, __FILE__, __LINE__, "sqlite3_open() '%s': %s",
1394 sqlite_db_name,
1395 sql->sqlh
1396 ? sqlite3_errmsg(sql->sqlh)
1397 : sqlite3_errstr(sqlrc));
1398 return 0;
1399 }
1400
1401 /* future: perhaps not all statements should be prepared;
1402 * infrequently executed statements could be run with sqlite3_exec(),
1403 * or prepared and finalized on each use, as needed */
1404
1405 MOD_WEBDAV_SQLITE_PREPARE_STMT( MOD_WEBDAV_SQLITE_PROPS_SELECT_PROPNAMES,
1406 sql->stmt_props_select_propnames);
1407 MOD_WEBDAV_SQLITE_PREPARE_STMT( MOD_WEBDAV_SQLITE_PROPS_SELECT_PROPS,
1408 sql->stmt_props_select_props);
1409 MOD_WEBDAV_SQLITE_PREPARE_STMT( MOD_WEBDAV_SQLITE_PROPS_SELECT_PROP,
1410 sql->stmt_props_select_prop);
1411 MOD_WEBDAV_SQLITE_PREPARE_STMT( MOD_WEBDAV_SQLITE_PROPS_UPDATE_PROP,
1412 sql->stmt_props_update_prop);
1413 MOD_WEBDAV_SQLITE_PREPARE_STMT( MOD_WEBDAV_SQLITE_PROPS_DELETE_PROP,
1414 sql->stmt_props_delete_prop);
1415 MOD_WEBDAV_SQLITE_PREPARE_STMT( MOD_WEBDAV_SQLITE_PROPS_COPY,
1416 sql->stmt_props_copy);
1417 MOD_WEBDAV_SQLITE_PREPARE_STMT( MOD_WEBDAV_SQLITE_PROPS_MOVE,
1418 sql->stmt_props_move);
1419 MOD_WEBDAV_SQLITE_PREPARE_STMT( MOD_WEBDAV_SQLITE_PROPS_MOVE_COL,
1420 sql->stmt_props_move_col);
1421 MOD_WEBDAV_SQLITE_PREPARE_STMT( MOD_WEBDAV_SQLITE_PROPS_DELETE,
1422 sql->stmt_props_delete);
1423 MOD_WEBDAV_SQLITE_PREPARE_STMT( MOD_WEBDAV_SQLITE_LOCKS_ACQUIRE,
1424 sql->stmt_locks_acquire);
1425 MOD_WEBDAV_SQLITE_PREPARE_STMT( MOD_WEBDAV_SQLITE_LOCKS_REFRESH,
1426 sql->stmt_locks_refresh);
1427 MOD_WEBDAV_SQLITE_PREPARE_STMT( MOD_WEBDAV_SQLITE_LOCKS_RELEASE,
1428 sql->stmt_locks_release);
1429 MOD_WEBDAV_SQLITE_PREPARE_STMT( MOD_WEBDAV_SQLITE_LOCKS_READ,
1430 sql->stmt_locks_read);
1431 MOD_WEBDAV_SQLITE_PREPARE_STMT( MOD_WEBDAV_SQLITE_LOCKS_READ_URI,
1432 sql->stmt_locks_read_uri);
1433 MOD_WEBDAV_SQLITE_PREPARE_STMT( MOD_WEBDAV_SQLITE_LOCKS_READ_URI_INFINITY,
1434 sql->stmt_locks_read_uri_infinity);
1435 MOD_WEBDAV_SQLITE_PREPARE_STMT( MOD_WEBDAV_SQLITE_LOCKS_READ_URI_MEMBERS,
1436 sql->stmt_locks_read_uri_members);
1437 MOD_WEBDAV_SQLITE_PREPARE_STMT( MOD_WEBDAV_SQLITE_LOCKS_DELETE_URI,
1438 sql->stmt_locks_delete_uri);
1439 MOD_WEBDAV_SQLITE_PREPARE_STMT( MOD_WEBDAV_SQLITE_LOCKS_DELETE_URI_COL,
1440 sql->stmt_locks_delete_uri_col);
1441
1442 return 1;
1443
1444 }
1445 #endif /* USE_PROPPATCH */
1446
1447
1448 __attribute_cold__
SERVER_FUNC(mod_webdav_worker_init)1449 SERVER_FUNC(mod_webdav_worker_init)
1450 {
1451 #ifdef USE_PROPPATCH
1452 /* open sqlite databases and prepare SQL statements in each worker process
1453 *
1454 * https://www.sqlite.org/faq.html
1455 * Under Unix, you should not carry an open SQLite database
1456 * across a fork() system call into the child process.
1457 */
1458 plugin_data * const p = (plugin_data *)p_d;
1459 /* (init i to 0 if global context; to 1 to skip empty global context) */
1460 for (int i = !p->cvlist[0].v.u2[1], used = p->nconfig; i < used; ++i) {
1461 config_plugin_value_t *cpv = p->cvlist + p->cvlist[i].v.u2[0];
1462 for (; -1 != cpv->k_id; ++cpv) {
1463 switch (cpv->k_id) {
1464 #ifdef USE_PROPPATCH
1465 case 0: /* webdav.sqlite-db-name */
1466 if (!buffer_is_blank(cpv->v.b)) {
1467 const char * const dbname = cpv->v.b->ptr;
1468 cpv->v.v = ck_calloc(1, sizeof(sql_config));
1469 cpv->vtype = T_CONFIG_LOCAL;
1470 if (!mod_webdav_sqlite3_prep(cpv->v.v, dbname, srv->errh))
1471 return HANDLER_ERROR;
1472 /*(update p->defaults after init)*/
1473 if (0 == i) p->defaults.sql = cpv->v.v;
1474 }
1475 break;
1476 #endif
1477 default:
1478 break;
1479 }
1480 }
1481 }
1482 #else
1483 UNUSED(srv);
1484 UNUSED(p_d);
1485 #endif /* USE_PROPPATCH */
1486 return HANDLER_GO_ON;
1487 }
1488
1489
1490 #ifdef USE_PROPPATCH
1491 static int
webdav_db_transaction(const plugin_config * const pconf,const char * const action)1492 webdav_db_transaction (const plugin_config * const pconf,
1493 const char * const action)
1494 {
1495 if (!pconf->sql)
1496 return 1;
1497 char *err = NULL;
1498 if (SQLITE_OK == sqlite3_exec(pconf->sql->sqlh, action, NULL, NULL, &err))
1499 return 1;
1500 else {
1501 #if 0
1502 fprintf(stderr, "%s: %s: %s\n", __func__, action, err);
1503 log_error(pconf->errh, __FILE__, __LINE__,
1504 "%s: %s: %s\n", __func__, action, err);
1505 #endif
1506 sqlite3_free(err);
1507 return 0;
1508 }
1509 }
1510
1511 #define webdav_db_transaction_begin(pconf) \
1512 webdav_db_transaction(pconf, "BEGIN;")
1513
1514 #define webdav_db_transaction_begin_immediate(pconf) \
1515 webdav_db_transaction(pconf, "BEGIN IMMEDIATE;")
1516
1517 #define webdav_db_transaction_commit(pconf) \
1518 webdav_db_transaction(pconf, "COMMIT;")
1519
1520 #define webdav_db_transaction_rollback(pconf) \
1521 webdav_db_transaction(pconf, "ROLLBACK;")
1522
1523 #else
1524
1525 #define webdav_db_transaction_begin(pconf) 1
1526 #define webdav_db_transaction_begin_immediate(pconf) 1
1527 #define webdav_db_transaction_commit(pconf) 1
1528 #define webdav_db_transaction_rollback(pconf) 1
1529
1530 #endif
1531
1532
1533 #ifdef USE_LOCKS
1534 static int
webdav_lock_match(const plugin_config * const pconf,const webdav_lockdata * const lockdata)1535 webdav_lock_match (const plugin_config * const pconf,
1536 const webdav_lockdata * const lockdata)
1537 {
1538 if (!pconf->sql)
1539 return 0;
1540 sqlite3_stmt * const stmt = pconf->sql->stmt_locks_read;
1541 if (!stmt)
1542 return 0;
1543
1544 sqlite3_bind_text(
1545 stmt, 1, BUF_PTR_LEN(&lockdata->locktoken), SQLITE_STATIC);
1546
1547 int status = -1; /* if lock does not exist */
1548 if (SQLITE_ROW == sqlite3_step(stmt)) {
1549 const char *text = (char *)sqlite3_column_text(stmt, 0); /* resource */
1550 uint32_t text_len = (uint32_t) sqlite3_column_bytes(stmt, 0);
1551 if (text_len < lockdata->lockroot.used
1552 && 0 == memcmp(lockdata->lockroot.ptr, text, text_len)
1553 && (text_len == lockdata->lockroot.used-1
1554 || -1 == sqlite3_column_int(stmt, 2))) { /* depth */
1555 text = (char *)sqlite3_column_text(stmt, 1); /* owner */
1556 text_len = (uint32_t)sqlite3_column_bytes(stmt, 1);
1557 if (0 == text_len /*(if no auth required to lock; not recommended)*/
1558 || buffer_eq_slen(lockdata->owner, text, text_len))
1559 status = 0; /* success; lock match */
1560 else {
1561 /*(future: might check if owner is a privileged admin user)*/
1562 status = -3; /* not lock owner; not authorized */
1563 }
1564 }
1565 else
1566 status = -2; /* URI is not in scope of lock */
1567 }
1568
1569 sqlite3_reset(stmt);
1570
1571 /* status
1572 * 0 lock exists and uri in scope and owner is privileged/owns lock
1573 * -1 lock does not exist
1574 * -2 URI is not in scope of lock
1575 * -3 owner does not own lock/is not privileged
1576 */
1577 return status;
1578 }
1579 #endif
1580
1581
1582 #ifdef USE_LOCKS
1583 static void
webdav_lock_activelocks_lockdata(sqlite3_stmt * const stmt,webdav_lockdata_wr * const lockdata)1584 webdav_lock_activelocks_lockdata (sqlite3_stmt * const stmt,
1585 webdav_lockdata_wr * const lockdata)
1586 {
1587 lockdata->locktoken.ptr = (char *)sqlite3_column_text(stmt, 0);
1588 lockdata->locktoken.used = sqlite3_column_bytes(stmt, 0);
1589 lockdata->lockroot.ptr = (char *)sqlite3_column_text(stmt, 1);
1590 lockdata->lockroot.used = sqlite3_column_bytes(stmt, 1);
1591 lockdata->lockscope =
1592 (sqlite3_column_bytes(stmt, 2) == (int)sizeof("exclusive")-1)
1593 ? (const buffer *)&lockscope_exclusive
1594 : (const buffer *)&lockscope_shared;
1595 lockdata->locktype = (const buffer *)&locktype_write;
1596 lockdata->owner->ptr = (char *)sqlite3_column_text(stmt, 4);
1597 lockdata->owner->used = sqlite3_column_bytes(stmt, 4);
1598 lockdata->ownerinfo.ptr = (char *)sqlite3_column_text(stmt, 5);
1599 lockdata->ownerinfo.used = sqlite3_column_bytes(stmt, 5);
1600 lockdata->depth = sqlite3_column_int(stmt, 6);
1601 lockdata->timeout = sqlite3_column_int(stmt, 7);
1602
1603 if (lockdata->locktoken.used) ++lockdata->locktoken.used;
1604 if (lockdata->lockroot.used) ++lockdata->lockroot.used;
1605 if (lockdata->owner->used) ++lockdata->owner->used;
1606 if (lockdata->ownerinfo.used) ++lockdata->ownerinfo.used;
1607 }
1608
1609
1610 typedef
1611 void webdav_lock_activelocks_cb(void * const vdata,
1612 const webdav_lockdata * const lockdata);
1613
1614 static void
webdav_lock_activelocks(const plugin_config * const pconf,const buffer * const uri,const int expand_checks,webdav_lock_activelocks_cb * const lock_cb,void * const vdata)1615 webdav_lock_activelocks (const plugin_config * const pconf,
1616 const buffer * const uri,
1617 const int expand_checks,
1618 webdav_lock_activelocks_cb * const lock_cb,
1619 void * const vdata)
1620 {
1621 webdav_lockdata lockdata;
1622 buffer owner = { NULL, 0, 0 };
1623 lockdata.locktoken.size = 0;
1624 lockdata.lockroot.size = 0;
1625 lockdata.ownerinfo.size = 0;
1626 lockdata.owner = &owner;
1627
1628 if (!pconf->sql)
1629 return;
1630
1631 /* check for locks with Depth: 0 (and Depth: infinity if 0==expand_checks)*/
1632 sqlite3_stmt *stmt = pconf->sql->stmt_locks_read_uri;
1633 if (!stmt || !pconf->sql->stmt_locks_read_uri_infinity
1634 || !pconf->sql->stmt_locks_read_uri_members)
1635 return;
1636
1637 sqlite3_bind_text(stmt, 1, BUF_PTR_LEN(uri), SQLITE_STATIC);
1638
1639 while (SQLITE_ROW == sqlite3_step(stmt)) {
1640 /* (avoid duplication with query below if infinity lock on collection)
1641 * (infinity locks are rejected on non-collections elsewhere) */
1642 if (0 != expand_checks && -1 == sqlite3_column_int(stmt, 6) /*depth*/)
1643 continue;
1644
1645 webdav_lock_activelocks_lockdata(stmt, (webdav_lockdata_wr *)&lockdata);
1646 if (lockdata.timeout > 0)
1647 lock_cb(vdata, &lockdata);
1648 }
1649
1650 sqlite3_reset(stmt);
1651
1652 if (0 == expand_checks)
1653 return;
1654
1655 /* check for locks with Depth: infinity
1656 * (i.e. collections: self (if collection) or containing collections) */
1657 stmt = pconf->sql->stmt_locks_read_uri_infinity;
1658
1659 sqlite3_bind_text(stmt, 1, BUF_PTR_LEN(uri), SQLITE_STATIC);
1660
1661 while (SQLITE_ROW == sqlite3_step(stmt)) {
1662 webdav_lock_activelocks_lockdata(stmt, (webdav_lockdata_wr *)&lockdata);
1663 if (lockdata.timeout > 0)
1664 lock_cb(vdata, &lockdata);
1665 }
1666
1667 sqlite3_reset(stmt);
1668
1669 if (1 == expand_checks)
1670 return;
1671
1672 #ifdef __COVERITY__
1673 force_assert(0 != uri->used);
1674 #endif
1675
1676 /* check for locks on members within (internal to) collection */
1677 stmt = pconf->sql->stmt_locks_read_uri_members;
1678
1679 sqlite3_bind_int( stmt, 1, (int)uri->used-1);
1680 sqlite3_bind_text(stmt, 2, BUF_PTR_LEN(uri), SQLITE_STATIC);
1681
1682 while (SQLITE_ROW == sqlite3_step(stmt)) {
1683 /* (avoid duplication with query above for exact resource match) */
1684 if (uri->used-1 == (uint32_t)sqlite3_column_bytes(stmt, 1) /*resource*/)
1685 continue;
1686
1687 webdav_lock_activelocks_lockdata(stmt, (webdav_lockdata_wr *)&lockdata);
1688 if (lockdata.timeout > 0)
1689 lock_cb(vdata, &lockdata);
1690 }
1691
1692 sqlite3_reset(stmt);
1693 }
1694 #endif
1695
1696
1697 static int
webdav_lock_delete_uri(const plugin_config * const pconf,const buffer * const uri)1698 webdav_lock_delete_uri (const plugin_config * const pconf,
1699 const buffer * const uri)
1700 {
1701 #ifdef USE_LOCKS
1702
1703 if (!pconf->sql)
1704 return 0;
1705 sqlite3_stmt * const stmt = pconf->sql->stmt_locks_delete_uri;
1706 if (!stmt)
1707 return 0;
1708
1709 sqlite3_bind_text(stmt, 1, BUF_PTR_LEN(uri), SQLITE_STATIC);
1710
1711 int status = 1;
1712 while (SQLITE_DONE != sqlite3_step(stmt)) {
1713 status = 0;
1714 #if 0
1715 fprintf(stderr, "%s: %s\n", __func__, sqlite3_errmsg(pconf->sql->sqlh));
1716 log_error(pconf->errh, __FILE__, __LINE__,
1717 "%s: %s", __func__, sqlite3_errmsg(pconf->sql->sqlh));
1718 #endif
1719 }
1720
1721 sqlite3_reset(stmt);
1722
1723 return status;
1724
1725 #else
1726 UNUSED(pconf);
1727 UNUSED(uri);
1728 return 1;
1729 #endif
1730 }
1731
1732
1733 static int
webdav_lock_delete_uri_col(const plugin_config * const pconf,const buffer * const uri)1734 webdav_lock_delete_uri_col (const plugin_config * const pconf,
1735 const buffer * const uri)
1736 {
1737 #ifdef USE_LOCKS
1738
1739 if (!pconf->sql)
1740 return 0;
1741 sqlite3_stmt * const stmt = pconf->sql->stmt_locks_delete_uri_col;
1742 if (!stmt)
1743 return 0;
1744
1745 #ifdef __COVERITY__
1746 force_assert(0 != uri->used);
1747 #endif
1748
1749 sqlite3_bind_int( stmt, 1, (int)uri->used-1);
1750 sqlite3_bind_text(stmt, 2, BUF_PTR_LEN(uri), SQLITE_STATIC);
1751
1752 int status = 1;
1753 while (SQLITE_DONE != sqlite3_step(stmt)) {
1754 status = 0;
1755 #if 0
1756 fprintf(stderr, "%s: %s\n", __func__, sqlite3_errmsg(pconf->sql->sqlh));
1757 log_error(pconf->errh, __FILE__, __LINE__,
1758 "%s: %s", __func__, sqlite3_errmsg(pconf->sql->sqlh));
1759 #endif
1760 }
1761
1762 sqlite3_reset(stmt);
1763
1764 return status;
1765
1766 #else
1767 UNUSED(pconf);
1768 UNUSED(uri);
1769 return 1;
1770 #endif
1771 }
1772
1773
1774 #ifdef USE_LOCKS
1775 static int
webdav_lock_acquire(const plugin_config * const pconf,const webdav_lockdata * const lockdata)1776 webdav_lock_acquire (const plugin_config * const pconf,
1777 const webdav_lockdata * const lockdata)
1778 {
1779 /*
1780 * future:
1781 * only lockscope:"exclusive" and locktype:"write" currently supported,
1782 * so inserting strings into database is extraneous, and anyway should
1783 * be enums instead of strings, since there are limited supported values
1784 */
1785
1786 if (!pconf->sql)
1787 return 0;
1788 sqlite3_stmt * const stmt = pconf->sql->stmt_locks_acquire;
1789 if (!stmt)
1790 return 0;
1791
1792 sqlite3_bind_text(
1793 stmt, 1, BUF_PTR_LEN(&lockdata->locktoken), SQLITE_STATIC);
1794 sqlite3_bind_text(
1795 stmt, 2, BUF_PTR_LEN(&lockdata->lockroot), SQLITE_STATIC);
1796 sqlite3_bind_text(
1797 stmt, 3, BUF_PTR_LEN(lockdata->lockscope), SQLITE_STATIC);
1798 sqlite3_bind_text(
1799 stmt, 4, BUF_PTR_LEN(lockdata->locktype), SQLITE_STATIC);
1800 if (lockdata->owner->used)
1801 sqlite3_bind_text(
1802 stmt, 5, BUF_PTR_LEN(lockdata->owner), SQLITE_STATIC);
1803 else
1804 sqlite3_bind_text(
1805 stmt, 5, CONST_STR_LEN(""), SQLITE_STATIC);
1806 if (lockdata->ownerinfo.used)
1807 sqlite3_bind_text(
1808 stmt, 6, BUF_PTR_LEN(&lockdata->ownerinfo), SQLITE_STATIC);
1809 else
1810 sqlite3_bind_text(
1811 stmt, 6, CONST_STR_LEN(""), SQLITE_STATIC);
1812 sqlite3_bind_int(
1813 stmt, 7, lockdata->depth);
1814 sqlite3_bind_int(
1815 stmt, 8, lockdata->timeout);
1816
1817 int status = 1;
1818 if (SQLITE_DONE != sqlite3_step(stmt)) {
1819 status = 0;
1820 #if 0
1821 fprintf(stderr, "%s: %s\n", __func__, sqlite3_errmsg(pconf->sql->sqlh));
1822 log_error(pconf->errh, __FILE__, __LINE__,
1823 "%s: %s", __func__, sqlite3_errmsg(pconf->sql->sqlh));
1824 #endif
1825 }
1826
1827 sqlite3_reset(stmt);
1828
1829 return status;
1830 }
1831 #endif
1832
1833
1834 #ifdef USE_LOCKS
1835 static int
webdav_lock_refresh(const plugin_config * const pconf,webdav_lockdata * const lockdata)1836 webdav_lock_refresh (const plugin_config * const pconf,
1837 webdav_lockdata * const lockdata)
1838 {
1839 if (!pconf->sql)
1840 return 0;
1841 sqlite3_stmt * const stmt = pconf->sql->stmt_locks_refresh;
1842 if (!stmt)
1843 return 0;
1844
1845 const buffer * const locktoken = &lockdata->locktoken;
1846 sqlite3_bind_text(stmt, 1, BUF_PTR_LEN(locktoken), SQLITE_STATIC);
1847 sqlite3_bind_int( stmt, 2, lockdata->timeout);
1848
1849 int status = 1;
1850 if (SQLITE_DONE != sqlite3_step(stmt)) {
1851 status = 0;
1852 #if 0
1853 fprintf(stderr, "%s: %s\n", __func__, sqlite3_errmsg(pconf->sql->sqlh));
1854 log_error(pconf->errh, __FILE__, __LINE__,
1855 "%s: %s", __func__, sqlite3_errmsg(pconf->sql->sqlh));
1856 #endif
1857 }
1858
1859 sqlite3_reset(stmt);
1860
1861 /*(future: fill in lockscope, locktype, depth from database)*/
1862
1863 return status;
1864 }
1865 #endif
1866
1867
1868 #ifdef USE_LOCKS
1869 static int
webdav_lock_release(const plugin_config * const pconf,const webdav_lockdata * const lockdata)1870 webdav_lock_release (const plugin_config * const pconf,
1871 const webdav_lockdata * const lockdata)
1872 {
1873 if (!pconf->sql)
1874 return 0;
1875 sqlite3_stmt * const stmt = pconf->sql->stmt_locks_release;
1876 if (!stmt)
1877 return 0;
1878
1879 sqlite3_bind_text(
1880 stmt, 1, BUF_PTR_LEN(&lockdata->locktoken), SQLITE_STATIC);
1881
1882 int status = 0;
1883 if (SQLITE_DONE == sqlite3_step(stmt))
1884 status = (0 != sqlite3_changes(pconf->sql->sqlh));
1885 else {
1886 #if 0
1887 fprintf(stderr, "%s: %s\n", __func__, sqlite3_errmsg(pconf->sql->sqlh));
1888 log_error(pconf->errh, __FILE__, __LINE__,
1889 "%s: %s", __func__, sqlite3_errmsg(pconf->sql->sqlh));
1890 #endif
1891 }
1892
1893 sqlite3_reset(stmt);
1894
1895 return status;
1896 }
1897 #endif
1898
1899
1900 static int
webdav_prop_move_uri(const plugin_config * const pconf,const buffer * const src,const buffer * const dst)1901 webdav_prop_move_uri (const plugin_config * const pconf,
1902 const buffer * const src,
1903 const buffer * const dst)
1904 {
1905 #ifdef USE_PROPPATCH
1906 if (!pconf->sql)
1907 return 0;
1908 sqlite3_stmt * const stmt = pconf->sql->stmt_props_move;
1909 if (!stmt)
1910 return 0;
1911
1912 sqlite3_bind_text(stmt, 1, BUF_PTR_LEN(dst), SQLITE_STATIC);
1913 sqlite3_bind_text(stmt, 2, BUF_PTR_LEN(src), SQLITE_STATIC);
1914
1915 if (SQLITE_DONE != sqlite3_step(stmt)) {
1916 #if 0
1917 fprintf(stderr, "%s: %s\n", __func__, sqlite3_errmsg(pconf->sql->sqlh));
1918 log_error(pconf->errh, __FILE__, __LINE__,
1919 "%s: %s", __func__, sqlite3_errmsg(pconf->sql->sqlh));
1920 #endif
1921 }
1922
1923 sqlite3_reset(stmt);
1924
1925 #else
1926 UNUSED(pconf);
1927 UNUSED(src);
1928 UNUSED(dst);
1929 #endif
1930
1931 return 0;
1932 }
1933
1934
1935 static int
webdav_prop_move_uri_col(const plugin_config * const pconf,const buffer * const src,const buffer * const dst)1936 webdav_prop_move_uri_col (const plugin_config * const pconf,
1937 const buffer * const src,
1938 const buffer * const dst)
1939 {
1940 #ifdef USE_PROPPATCH
1941 if (!pconf->sql)
1942 return 0;
1943 sqlite3_stmt * const stmt = pconf->sql->stmt_props_move_col;
1944 if (!stmt)
1945 return 0;
1946
1947 #ifdef __COVERITY__
1948 force_assert(0 != src->used);
1949 #endif
1950
1951 sqlite3_bind_text(stmt, 1, BUF_PTR_LEN(dst), SQLITE_STATIC);
1952 sqlite3_bind_int( stmt, 2, (int)src->used);
1953 sqlite3_bind_int( stmt, 3, (int)src->used-1);
1954 sqlite3_bind_text(stmt, 4, BUF_PTR_LEN(src), SQLITE_STATIC);
1955
1956 if (SQLITE_DONE != sqlite3_step(stmt)) {
1957 #if 0
1958 fprintf(stderr, "%s: %s\n", __func__, sqlite3_errmsg(pconf->sql->sqlh));
1959 log_error(pconf->errh, __FILE__, __LINE__,
1960 "%s: %s", __func__, sqlite3_errmsg(pconf->sql->sqlh));
1961 #endif
1962 }
1963
1964 sqlite3_reset(stmt);
1965
1966 #else
1967 UNUSED(pconf);
1968 UNUSED(src);
1969 UNUSED(dst);
1970 #endif
1971
1972 return 0;
1973 }
1974
1975
1976 static int
webdav_prop_delete_uri(const plugin_config * const pconf,const buffer * const uri)1977 webdav_prop_delete_uri (const plugin_config * const pconf,
1978 const buffer * const uri)
1979 {
1980 #ifdef USE_PROPPATCH
1981 if (!pconf->sql)
1982 return 0;
1983 sqlite3_stmt * const stmt = pconf->sql->stmt_props_delete;
1984 if (!stmt)
1985 return 0;
1986
1987 sqlite3_bind_text(stmt, 1, BUF_PTR_LEN(uri), SQLITE_STATIC);
1988
1989 if (SQLITE_DONE != sqlite3_step(stmt)) {
1990 #if 0
1991 fprintf(stderr, "%s: %s\n", __func__, sqlite3_errmsg(pconf->sql->sqlh));
1992 log_error(pconf->errh, __FILE__, __LINE__,
1993 "%s: %s", __func__, sqlite3_errmsg(pconf->sql->sqlh));
1994 #endif
1995 }
1996
1997 sqlite3_reset(stmt);
1998
1999 #else
2000 UNUSED(pconf);
2001 UNUSED(uri);
2002 #endif
2003
2004 return 0;
2005 }
2006
2007
2008 static int
webdav_prop_copy_uri(const plugin_config * const pconf,const buffer * const src,const buffer * const dst)2009 webdav_prop_copy_uri (const plugin_config * const pconf,
2010 const buffer * const src,
2011 const buffer * const dst)
2012 {
2013 #ifdef USE_PROPPATCH
2014 if (!pconf->sql)
2015 return 0;
2016 sqlite3_stmt * const stmt = pconf->sql->stmt_props_copy;
2017 if (!stmt)
2018 return 0;
2019
2020 sqlite3_bind_text(stmt, 1, BUF_PTR_LEN(dst), SQLITE_STATIC);
2021 sqlite3_bind_text(stmt, 2, BUF_PTR_LEN(src), SQLITE_STATIC);
2022
2023 if (SQLITE_DONE != sqlite3_step(stmt)) {
2024 #if 0
2025 fprintf(stderr, "%s: %s\n", __func__, sqlite3_errmsg(pconf->sql->sqlh));
2026 log_error(pconf->errh, __FILE__, __LINE__,
2027 "%s: %s", __func__, sqlite3_errmsg(pconf->sql->sqlh));
2028 #endif
2029 }
2030
2031 sqlite3_reset(stmt);
2032
2033 #else
2034 UNUSED(pconf);
2035 UNUSED(dst);
2036 UNUSED(src);
2037 #endif
2038
2039 return 0;
2040 }
2041
2042
2043 #ifdef USE_PROPPATCH
2044 static int
webdav_prop_delete(const plugin_config * const pconf,const buffer * const uri,const char * const prop_name,const char * const prop_ns)2045 webdav_prop_delete (const plugin_config * const pconf,
2046 const buffer * const uri,
2047 const char * const prop_name,
2048 const char * const prop_ns)
2049 {
2050 if (!pconf->sql)
2051 return 0;
2052 sqlite3_stmt * const stmt = pconf->sql->stmt_props_delete_prop;
2053 if (!stmt)
2054 return 0;
2055
2056 sqlite3_bind_text(stmt, 1, BUF_PTR_LEN(uri), SQLITE_STATIC);
2057 sqlite3_bind_text(stmt, 2, prop_name, strlen(prop_name), SQLITE_STATIC);
2058 sqlite3_bind_text(stmt, 3, prop_ns, strlen(prop_ns), SQLITE_STATIC);
2059
2060 if (SQLITE_DONE != sqlite3_step(stmt)) {
2061 #if 0
2062 fprintf(stderr, "%s: %s\n", __func__, sqlite3_errmsg(pconf->sql->sqlh));
2063 log_error(pconf->errh, __FILE__, __LINE__,
2064 "%s: %s", __func__, sqlite3_errmsg(pconf->sql->sqlh));
2065 #endif
2066 }
2067
2068 sqlite3_reset(stmt);
2069
2070 return 0;
2071 }
2072 #endif
2073
2074
2075 #ifdef USE_PROPPATCH
2076 static int
webdav_prop_update(const plugin_config * const pconf,const buffer * const uri,const char * const prop_name,const char * const prop_ns,const char * const prop_value)2077 webdav_prop_update (const plugin_config * const pconf,
2078 const buffer * const uri,
2079 const char * const prop_name,
2080 const char * const prop_ns,
2081 const char * const prop_value)
2082 {
2083 if (!pconf->sql)
2084 return 0;
2085 sqlite3_stmt * const stmt = pconf->sql->stmt_props_update_prop;
2086 if (!stmt)
2087 return 0;
2088
2089 sqlite3_bind_text(stmt, 1, BUF_PTR_LEN(uri), SQLITE_STATIC);
2090 sqlite3_bind_text(stmt, 2, prop_name, strlen(prop_name), SQLITE_STATIC);
2091 sqlite3_bind_text(stmt, 3, prop_ns, strlen(prop_ns), SQLITE_STATIC);
2092 sqlite3_bind_text(stmt, 4, prop_value, strlen(prop_value), SQLITE_STATIC);
2093
2094 if (SQLITE_DONE != sqlite3_step(stmt)) {
2095 #if 0
2096 fprintf(stderr, "%s: %s\n", __func__, sqlite3_errmsg(pconf->sql->sqlh));
2097 log_error(pconf->errh, __FILE__, __LINE__,
2098 "%s: %s", __func__, sqlite3_errmsg(pconf->sql->sqlh));
2099 #endif
2100 }
2101
2102 sqlite3_reset(stmt);
2103
2104 return 0;
2105 }
2106 #endif
2107
2108
2109 static int
webdav_prop_select_prop(const plugin_config * const pconf,const buffer * const uri,const webdav_property_name * const prop,buffer * const b)2110 webdav_prop_select_prop (const plugin_config * const pconf,
2111 const buffer * const uri,
2112 const webdav_property_name * const prop,
2113 buffer * const b)
2114 {
2115 #ifdef USE_PROPPATCH
2116 if (!pconf->sql)
2117 return -1;
2118 sqlite3_stmt * const stmt = pconf->sql->stmt_props_select_prop;
2119 if (!stmt)
2120 return -1; /* not found */
2121
2122 sqlite3_bind_text(stmt, 1, BUF_PTR_LEN(uri), SQLITE_STATIC);
2123 sqlite3_bind_text(stmt, 2, prop->name, prop->namelen, SQLITE_STATIC);
2124 sqlite3_bind_text(stmt, 3, prop->ns, prop->nslen, SQLITE_STATIC);
2125
2126 if (SQLITE_ROW == sqlite3_step(stmt)) {
2127 webdav_xml_prop(b, prop, (char *)sqlite3_column_text(stmt, 0),
2128 (uint32_t)sqlite3_column_bytes(stmt, 0));
2129 sqlite3_reset(stmt);
2130 return 0; /* found */
2131 }
2132 sqlite3_reset(stmt);
2133 #else
2134 UNUSED(pconf);
2135 UNUSED(uri);
2136 UNUSED(prop);
2137 UNUSED(b);
2138 #endif
2139 return -1; /* not found */
2140 }
2141
2142
2143 static void
webdav_prop_select_props(const plugin_config * const pconf,const buffer * const uri,buffer * const b)2144 webdav_prop_select_props (const plugin_config * const pconf,
2145 const buffer * const uri,
2146 buffer * const b)
2147 {
2148 #ifdef USE_PROPPATCH
2149 if (!pconf->sql)
2150 return;
2151 sqlite3_stmt * const stmt = pconf->sql->stmt_props_select_props;
2152 if (!stmt)
2153 return;
2154
2155 sqlite3_bind_text(stmt, 1, BUF_PTR_LEN(uri), SQLITE_STATIC);
2156
2157 while (SQLITE_ROW == sqlite3_step(stmt)) {
2158 webdav_property_name prop;
2159 prop.ns = (char *)sqlite3_column_text(stmt, 1);
2160 prop.name = (char *)sqlite3_column_text(stmt, 0);
2161 prop.nslen = (uint32_t)sqlite3_column_bytes(stmt, 1);
2162 prop.namelen = (uint32_t)sqlite3_column_bytes(stmt, 0);
2163 webdav_xml_prop(b, &prop, (char *)sqlite3_column_text(stmt, 2),
2164 (uint32_t)sqlite3_column_bytes(stmt, 2));
2165 }
2166
2167 sqlite3_reset(stmt);
2168 #else
2169 UNUSED(pconf);
2170 UNUSED(uri);
2171 UNUSED(b);
2172 #endif
2173 }
2174
2175
2176 static int
webdav_prop_select_propnames(const plugin_config * const pconf,const buffer * const uri,buffer * const b)2177 webdav_prop_select_propnames (const plugin_config * const pconf,
2178 const buffer * const uri,
2179 buffer * const b)
2180 {
2181 #ifdef USE_PROPPATCH
2182 if (!pconf->sql)
2183 return 0;
2184 sqlite3_stmt * const stmt = pconf->sql->stmt_props_select_propnames;
2185 if (!stmt)
2186 return 0;
2187
2188 /* get all property names (EMPTY) */
2189 sqlite3_bind_text(stmt, 1, BUF_PTR_LEN(uri), SQLITE_STATIC);
2190
2191 while (SQLITE_ROW == sqlite3_step(stmt)) {
2192 webdav_property_name prop;
2193 prop.ns = (char *)sqlite3_column_text(stmt, 1);
2194 prop.name = (char *)sqlite3_column_text(stmt, 0);
2195 prop.nslen = (uint32_t)sqlite3_column_bytes(stmt, 1);
2196 prop.namelen = (uint32_t)sqlite3_column_bytes(stmt, 0);
2197 webdav_xml_prop(b, &prop, NULL, 0);
2198 }
2199
2200 sqlite3_reset(stmt);
2201
2202 #else
2203 UNUSED(pconf);
2204 UNUSED(uri);
2205 UNUSED(b);
2206 #endif
2207
2208 return 0;
2209 }
2210
2211
2212 #if defined(__APPLE__) && defined(__MACH__)
2213 #if __ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__ >= 101200
2214 #include <sys/attr.h>
2215 #include <sys/clonefile.h>/* clonefile() *//* OS X 10.12+ */
2216 #endif
2217 #if __ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__ >= 1050
2218 #include <copyfile.h> /* fcopyfile() *//* OS X 10.5+ */
2219 #endif
2220 #endif
2221 #ifdef HAVE_ELFTC_COPYFILE/* __FreeBSD__ */
2222 #include <libelftc.h> /* elftc_copyfile() */
2223 #endif
2224 #ifdef __linux__
2225 #include <sys/sendfile.h> /* sendfile() */
2226 #endif
2227
2228 /* file copy (blocking)
2229 * fds should point to regular files (S_ISREG()) (not dir, symlink, or other)
2230 * fds should not have O_NONBLOCK flag set
2231 * (unless O_NONBLOCK not relevant for files on a given operating system)
2232 * isz should be size of input file, and is a param to avoid extra fstat()
2233 * since size is needed for Linux sendfile(), as well as posix_fadvise().
2234 * caller should handle fchmod() and copying extended attribute, if desired
2235 */
2236 __attribute_noinline__
2237 static int
webdav_fcopyfile_sz(int ifd,int ofd,off_t isz)2238 webdav_fcopyfile_sz (int ifd, int ofd, off_t isz)
2239 {
2240 if (0 == isz)
2241 return 0;
2242
2243 /* Note: copy acceleration does not handle if ifd is extended during copy
2244 * (file should not be modified during copy with proper WebDAV locking) */
2245
2246 #ifndef _WIN32
2247 /*(file descriptors to *regular files* on most OS ignore O_NONBLOCK)*/
2248 /*fcntl(ifd, F_SETFL, fcntl(ifd, F_GETFL, 0) & ~O_NONBLOCK);*/
2249 /*fcntl(ofd, F_SETFL, fcntl(ofd, F_GETFL, 0) & ~O_NONBLOCK);*/
2250 #endif
2251
2252 #if defined(__APPLE__) && defined(__MACH__)
2253 #if __ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__ >= 1050
2254 if (0 == fcopyfile(ifd, ofd, NULL, COPYFILE_ALL))
2255 return 0;
2256
2257 if (0 != lseek(ifd, 0, SEEK_SET)) return -1;
2258 if (0 != lseek(ofd, 0, SEEK_SET)) return -1;
2259 #endif
2260 #endif
2261
2262 #ifdef HAVE_ELFTC_COPYFILE /* __FreeBSD__ */
2263 if (0 == elftc_copyfile(ifd, ofd))
2264 return 0;
2265
2266 if (0 != lseek(ifd, 0, SEEK_SET)) return -1;
2267 if (0 != lseek(ofd, 0, SEEK_SET)) return -1;
2268 #endif
2269
2270 #ifdef __linux__ /* Linux 2.6.33+ sendfile() supports file-to-file copy */
2271 #if defined HAVE_SYS_SENDFILE_H && defined HAVE_SENDFILE \
2272 && (!defined _LARGEFILE_SOURCE || defined HAVE_SENDFILE64) \
2273 && defined(__linux__) && !defined HAVE_SENDFILE_BROKEN
2274 off_t offset = 0;
2275 #if defined(_LP64) || defined(__LP64__) || defined(_WIN64)
2276 while (offset < isz && sendfile(ifd,ofd,&offset,(size_t)(isz-offset)) >= 0);
2277 #else
2278 while (offset < isz
2279 && sendfile(ifd, ofd, &offset,
2280 (size_t)(isz-offset < INT32_MAX
2281 ? isz-offset
2282 : (INT32_MAX & ~(131072-1)))) >= 0)
2283 ;
2284 #endif
2285 if (offset == isz)
2286 return 0;
2287
2288 /*lseek(ifd, 0, SEEK_SET);*/ /*(ifd offset not modified due to &offset arg)*/
2289 if (0 != lseek(ofd, 0, SEEK_SET)) return -1;
2290 #endif
2291 #endif
2292
2293 ssize_t rd, wr, off;
2294 char buf[16384];
2295 isz = 0;
2296 do {
2297 do {
2298 rd = read(ifd, buf, sizeof(buf));
2299 } while (-1 == rd && errno == EINTR);
2300 if (rd <= 0) break;
2301
2302 off = 0;
2303 do {
2304 wr = write(ofd, buf+off, (size_t)(rd-off));
2305 } while (wr >= 0 ? (off += wr) != rd : errno == EINTR);
2306 if (wr < 0) return -1;
2307 } while ((isz += rd)); /*(always true when reached w/ largefile support)*/
2308 #if (defined(__APPLE__) && defined(__MACH__) \
2309 && __ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__ >= 1050) \
2310 || defined(HAVE_ELFTC_COPYFILE) /* __FreeBSD__ */ \
2311 || (defined HAVE_SYS_SENDFILE_H && defined HAVE_SENDFILE \
2312 && (!defined _LARGEFILE_SOURCE || defined HAVE_SENDFILE64) \
2313 && defined(__linux__) && !defined HAVE_SENDFILE_BROKEN)
2314 /*(file may have been truncated during prior copy acceleration attempt)*/
2315 if (0 == rd)
2316 return ftruncate(ofd, isz);
2317 #endif
2318 return (int)rd;
2319 }
2320
2321
2322 #ifdef USE_PROPPATCH
2323 __attribute_cold__
2324 __attribute_noinline__
2325 static handler_t
webdav_405_no_db(request_st * const r)2326 webdav_405_no_db (request_st * const r)
2327 {
2328 http_header_response_set(r, HTTP_HEADER_ALLOW,
2329 CONST_STR_LEN("Allow"),
2330 CONST_STR_LEN("GET, HEAD, PROPFIND, DELETE, MKCOL, PUT, MOVE, COPY"));
2331 http_status_set_error(r, 405); /* Method Not Allowed */
2332 return HANDLER_FINISHED;
2333 }
2334 #endif
2335
2336
2337 #ifdef USE_PROPPATCH
2338 __attribute_pure__
2339 static int
webdav_reqbody_type_xml(request_st * const r)2340 webdav_reqbody_type_xml (request_st * const r)
2341 {
2342 const buffer * const vb =
2343 http_header_request_get(r, HTTP_HEADER_CONTENT_TYPE,
2344 CONST_STR_LEN("Content-Type"));
2345 if (!vb) return 0;
2346
2347 const char * const semi = strchr(vb->ptr, ';');
2348 uint32_t len = semi ? (uint32_t)(semi - vb->ptr) : buffer_clen(vb);
2349 return ((len==15 && 0==memcmp(vb->ptr, "application/xml", 15))
2350 || (len==8 && 0==memcmp(vb->ptr, "text/xml", 8)));
2351 }
2352 #endif
2353
2354
2355 static int
webdav_if_match_or_unmodified_since(request_st * const r,struct stat * st)2356 webdav_if_match_or_unmodified_since (request_st * const r, struct stat *st)
2357 {
2358 const buffer *im = (0 != r->conf.etag_flags)
2359 ? http_header_request_get(r, HTTP_HEADER_IF_MATCH,
2360 CONST_STR_LEN("If-Match"))
2361 : NULL;
2362
2363 const buffer *inm = (0 != r->conf.etag_flags)
2364 ? http_header_request_get(r, HTTP_HEADER_IF_NONE_MATCH,
2365 CONST_STR_LEN("If-None-Match"))
2366 : NULL;
2367
2368 const buffer *ius =
2369 http_header_request_get(r, HTTP_HEADER_IF_UNMODIFIED_SINCE,
2370 CONST_STR_LEN("If-Unmodified-Since"));
2371
2372 if (NULL == im && NULL == inm && NULL == ius) return 0;
2373
2374 struct stat stp;
2375 if (NULL == st)
2376 st = (0 == lstat(r->physical.path.ptr, &stp)) ? &stp : NULL;
2377
2378 buffer * const etagb = r->tmp_buf;
2379 buffer_clear(etagb);
2380 if (NULL != st && (NULL != im || NULL != inm)) {
2381 http_etag_create(etagb, st, r->conf.etag_flags);
2382 }
2383
2384 if (NULL != im) {
2385 if (NULL == st || !http_etag_matches(etagb, im->ptr, 0))
2386 return 412; /* Precondition Failed */
2387 }
2388
2389 if (NULL != inm) {
2390 if (NULL == st
2391 ? (errno != ENOENT && errno != ENOTDIR)
2392 : http_etag_matches(etagb, inm->ptr, 1))
2393 return 412; /* Precondition Failed */
2394 }
2395
2396 if (NULL != ius) {
2397 if (NULL == st)
2398 return 412; /* Precondition Failed */
2399 if (http_date_if_modified_since(BUF_PTR_LEN(ius), st->st_mtime))
2400 return 412; /* Precondition Failed */
2401 }
2402
2403 return 0;
2404 }
2405
2406
2407 static void
webdav_response_etag(request_st * const r,struct stat * st)2408 webdav_response_etag (request_st * const r, struct stat *st)
2409 {
2410 buffer *etagb = NULL;
2411 if (0 != r->conf.etag_flags) {
2412 etagb = http_header_response_set_ptr(r, HTTP_HEADER_ETAG,
2413 CONST_STR_LEN("ETag"));
2414 http_etag_create(etagb, st, r->conf.etag_flags);
2415 }
2416 stat_cache_update_entry(BUF_PTR_LEN(&r->physical.path), st, etagb);
2417 }
2418
2419
2420 static void
webdav_parent_modified(const buffer * path)2421 webdav_parent_modified (const buffer *path)
2422 {
2423 uint32_t dirlen = buffer_clen(path);
2424 const char *fn = path->ptr;
2425 /*force_assert(0 != dirlen);*/
2426 /*force_assert(fn[0] == '/');*/
2427 if (fn[dirlen-1] == '/') --dirlen;
2428 if (0 != dirlen) while (fn[--dirlen] != '/') ;
2429 if (0 == dirlen) dirlen = 1; /* root dir ("/") */
2430 stat_cache_invalidate_entry(fn, dirlen);
2431 }
2432
2433
2434 __attribute_pure__
2435 static int
webdav_parse_Depth(const request_st * const r)2436 webdav_parse_Depth (const request_st * const r)
2437 {
2438 /* Depth = "Depth" ":" ("0" | "1" | "infinity") */
2439 /* check first char only;
2440 * ignore MS-WDVSE "noroot" extensions "1,noroot" and "infinity,noroot" */
2441 const buffer * const h =
2442 http_header_request_get(r, HTTP_HEADER_OTHER, CONST_STR_LEN("Depth"));
2443 if (NULL != h) {
2444 /* (leading LWS is removed during header parsing in request.c) */
2445 switch (*h->ptr) {
2446 case '0': return 0;
2447 case '1': return 1;
2448 /*case 'i':*/ /* e.g. "infinity" */
2449 /*case 'I':*/ /* e.g. "Infinity" */
2450 default: return -1;/* treat not-'0' and not-'1' as "infinity" */
2451 }
2452 }
2453
2454 return -1; /* default value is -1 to represent "infinity" */
2455 }
2456
2457
2458 #ifndef _ATFILE_SOURCE
2459 #define webdav_unlinkat(pconf,dst,dfd,d_name) webdav_delete_file((pconf),(dst))
2460 #else
2461 static int
webdav_unlinkat(const plugin_config * const pconf,const physical_st * const dst,const int dfd,const char * const d_name)2462 webdav_unlinkat (const plugin_config * const pconf,
2463 const physical_st * const dst,
2464 const int dfd, const char * const d_name)
2465 {
2466 if (0 == unlinkat(dfd, d_name, 0)) {
2467 stat_cache_delete_entry(BUF_PTR_LEN(&dst->path));
2468 return webdav_prop_delete_uri(pconf, &dst->rel_path);
2469 }
2470
2471 switch(errno) {
2472 case EACCES: case EPERM: return 403; /* Forbidden */
2473 case ENOENT: return 404; /* Not Found */
2474 default: return 501; /* Not Implemented */
2475 }
2476 }
2477 #endif
2478
2479
2480 static int
webdav_delete_file(const plugin_config * const pconf,const physical_st * const dst)2481 webdav_delete_file (const plugin_config * const pconf,
2482 const physical_st * const dst)
2483 {
2484 if (0 == unlink(dst->path.ptr)) {
2485 stat_cache_delete_entry(BUF_PTR_LEN(&dst->path));
2486 return webdav_prop_delete_uri(pconf, &dst->rel_path);
2487 }
2488
2489 switch(errno) {
2490 case EACCES: case EPERM: return 403; /* Forbidden */
2491 case ENOENT: return 404; /* Not Found */
2492 default: return 501; /* Not Implemented */
2493 }
2494 }
2495
2496
2497 static int
webdav_delete_dir(const plugin_config * const pconf,physical_st * const dst,request_st * const r,const int flags)2498 webdav_delete_dir (const plugin_config * const pconf,
2499 physical_st * const dst,
2500 request_st * const r,
2501 const int flags)
2502 {
2503 int multi_status = 0;
2504 #ifndef _ATFILE_SOURCE /*(not using fdopendir unless _ATFILE_SOURCE)*/
2505 const int dfd = -1;
2506 DIR * const dir = opendir(dst->path.ptr);
2507 #else
2508 const int dfd = fdevent_open_dirname(dst->path.ptr, 0);
2509 DIR * const dir = (dfd >= 0) ? fdopendir(dfd) : NULL;
2510 #endif
2511 if (NULL == dir) {
2512 if (dfd >= 0) close(dfd);
2513 webdav_xml_response_status(r, &dst->rel_path, 403);
2514 return 1;
2515 }
2516
2517 /* dst is modified in place to extend path,
2518 * so be sure to restore to base each loop iter */
2519 const uint32_t dst_path_used = dst->path.used;
2520 const uint32_t dst_rel_path_used = dst->rel_path.used;
2521 int s_isdir;
2522 struct dirent *de;
2523 while (NULL != (de = readdir(dir))) {
2524 if (de->d_name[0] == '.'
2525 && (de->d_name[1] == '\0'
2526 || (de->d_name[1] == '.' && de->d_name[2] == '\0')))
2527 continue; /* ignore "." and ".." */
2528
2529 #ifdef _DIRENT_HAVE_D_TYPE
2530 if (de->d_type != DT_UNKNOWN)
2531 s_isdir = (de->d_type == DT_DIR);
2532 else
2533 #endif
2534 {
2535 #ifdef _ATFILE_SOURCE
2536 struct stat st;
2537 if (0 != fstatat(dfd, de->d_name, &st, AT_SYMLINK_NOFOLLOW))
2538 continue; /* file *just* disappeared? */
2539 /* parent rmdir() will fail later if file still exists
2540 * and fstatat() failed for other reasons */
2541 s_isdir = S_ISDIR(st.st_mode);
2542 #endif
2543 }
2544
2545 const uint32_t len = (uint32_t) _D_EXACT_NAMLEN(de);
2546 if (flags & WEBDAV_FLAG_LC_NAMES) /*(needed at least for rel_path)*/
2547 webdav_str_len_to_lower(de->d_name, len);
2548 buffer_append_string_len(&dst->path, de->d_name, len);
2549 buffer_append_string_len(&dst->rel_path, de->d_name, len);
2550
2551 #ifndef _ATFILE_SOURCE
2552 #ifdef _DIRENT_HAVE_D_TYPE
2553 if (de->d_type == DT_UNKNOWN)
2554 #endif
2555 {
2556 struct stat st;
2557 if (0 != stat(dst->path.ptr, &st)) {
2558 dst->path.ptr[ (dst->path.used = dst_path_used) -1]='\0';
2559 dst->rel_path.ptr[(dst->rel_path.used = dst_rel_path_used)-1]='\0';
2560 continue; /* file *just* disappeared? */
2561 }
2562 s_isdir = S_ISDIR(st.st_mode);
2563 }
2564 #endif
2565
2566 if (s_isdir) {
2567 buffer_append_char(&dst->path, '/');
2568 buffer_append_char(&dst->rel_path, '/');
2569 multi_status |= webdav_delete_dir(pconf, dst, r, flags);
2570 }
2571 else {
2572 int status =
2573 webdav_unlinkat(pconf, dst, dfd, de->d_name);
2574 if (0 != status) {
2575 webdav_xml_response_status(r, &dst->rel_path, status);
2576 multi_status = 1;
2577 }
2578 }
2579
2580 dst->path.ptr[ (dst->path.used = dst_path_used) -1] = '\0';
2581 dst->rel_path.ptr[(dst->rel_path.used = dst_rel_path_used)-1] = '\0';
2582 }
2583 closedir(dir);
2584
2585 if (0 == multi_status) {
2586 int rmdir_status;
2587 if (0 == rmdir(dst->path.ptr))
2588 rmdir_status = webdav_prop_delete_uri(pconf, &dst->rel_path);
2589 else {
2590 switch(errno) {
2591 case EACCES:
2592 case EPERM: rmdir_status = 403; break; /* Forbidden */
2593 case ENOENT: rmdir_status = 404; break; /* Not Found */
2594 default: rmdir_status = 501; break; /* Not Implemented */
2595 }
2596 }
2597 if (0 != rmdir_status) {
2598 webdav_xml_response_status(r, &dst->rel_path, rmdir_status);
2599 multi_status = 1;
2600 }
2601 }
2602
2603 return multi_status;
2604 }
2605
2606
2607 #ifndef _ATFILE_SOURCE
2608 #define webdav_linktmp_rename(pconf, src, dst) -1
2609 #else
2610 static int
webdav_linktmp_rename(const plugin_config * const pconf,const buffer * const src,const buffer * const dst)2611 webdav_linktmp_rename (const plugin_config * const pconf,
2612 const buffer * const src,
2613 const buffer * const dst)
2614 {
2615 buffer * const tmpb = pconf->tmpb;
2616 int rc = -1; /*(not zero)*/
2617
2618 buffer_clear(tmpb);
2619 buffer_append_str2(tmpb, BUF_PTR_LEN(dst),
2620 CONST_STR_LEN("."));
2621 buffer_append_int(tmpb, (long)getpid());
2622 buffer_append_char(tmpb, '.');
2623 buffer_append_uint_hex_lc(tmpb, (uintptr_t)pconf); /*(stack/heap addr)*/
2624 buffer_append_char(tmpb, '~');
2625 if (buffer_clen(tmpb) < PATH_MAX
2626 && 0 == linkat(AT_FDCWD, src->ptr, AT_FDCWD, tmpb->ptr, 0)) {
2627
2628 rc = rename(tmpb->ptr, dst->ptr);
2629
2630 /* unconditionally unlink() src if rename() succeeds, just in case
2631 * dst previously existed and was already hard-linked to src. From
2632 * 'man -s 2 rename':
2633 * If oldpath and newpath are existing hard links referring to the
2634 * same file, then rename() does nothing, and returns a success
2635 * status.
2636 * This introduces a small race condition between the rename() and
2637 * unlink() should new file have been created at src in the middle,
2638 * though unlikely if locks are used since locks have not yet been
2639 * released. */
2640 unlink(tmpb->ptr);
2641 }
2642 return rc;
2643 }
2644 #endif
2645
2646
2647 static int
webdav_copytmp_rename(const plugin_config * const pconf,const physical_st * const src,const physical_st * const dst,int * const flags)2648 webdav_copytmp_rename (const plugin_config * const pconf,
2649 const physical_st * const src,
2650 const physical_st * const dst,
2651 int * const flags)
2652 {
2653 #if defined(__APPLE__) && defined(__MACH__)
2654 #if __ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__ >= 101200 /* 10.12+ */
2655 if (!(*flags & (WEBDAV_FLAG_COPY_XDEV
2656 |WEBDAV_FLAG_MOVE_XDEV
2657 |WEBDAV_FLAG_NO_CLONE)) && src != dst) {
2658 if (0==clonefile(src->path.ptr,dst->path.ptr,CLONE_NOFOLLOW))
2659 /* target did not exist; skip stat_cache_delete_entry() */
2660 return 0; /* copied */
2661 else {
2662 switch (errno) {
2663 case ENOTSUP:
2664 *flags |= WEBDAV_FLAG_NO_CLONE;
2665 break;
2666 case EXDEV:
2667 if (*flags & WEBDAV_FLAG_COPY_LINK) {
2668 *flags &= ~WEBDAV_FLAG_COPY_LINK;
2669 *flags |= WEBDAV_FLAG_COPY_XDEV;
2670 }
2671 break;
2672 case EEXIST:
2673 if (!(*flags & WEBDAV_FLAG_OVERWRITE))
2674 return 412; /* Precondition Failed */
2675 break;
2676 default:
2677 break;
2678 }
2679 }
2680 }
2681 #endif
2682 #endif
2683
2684 buffer * const tmpb = pconf->tmpb;
2685 buffer_clear(tmpb);
2686 buffer_append_str2(tmpb, BUF_PTR_LEN(&dst->path),
2687 CONST_STR_LEN("."));
2688 buffer_append_int(tmpb, (long)getpid());
2689 buffer_append_char(tmpb, '.');
2690 buffer_append_uint_hex_lc(tmpb, (uintptr_t)pconf); /*(stack/heap addr)*/
2691 buffer_append_char(tmpb, '~');
2692 if (buffer_clen(tmpb) >= PATH_MAX)
2693 return 414; /* URI Too Long */
2694
2695 do {
2696
2697 #if defined(__APPLE__) && defined(__MACH__)
2698 #if __ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__ >= 101200 /* 10.12+ */
2699 if (!(*flags & (WEBDAV_FLAG_COPY_XDEV
2700 |WEBDAV_FLAG_MOVE_XDEV
2701 |WEBDAV_FLAG_NO_CLONE))) {
2702 if (0 == clonefile(src->path.ptr, tmpb->ptr, CLONE_NOFOLLOW))
2703 break; /* copied */
2704 else {
2705 switch (errno) {
2706 case ENOTSUP:
2707 *flags |= WEBDAV_FLAG_NO_CLONE;
2708 break;
2709 case EXDEV:
2710 if (*flags & WEBDAV_FLAG_COPY_LINK) {
2711 *flags &= ~WEBDAV_FLAG_COPY_LINK;
2712 *flags |= WEBDAV_FLAG_COPY_XDEV;
2713 }
2714 break;
2715 default:
2716 break;
2717 }
2718 }
2719 }
2720 #endif
2721 #endif
2722
2723 /* code does not currently support symlinks in webdav collections;
2724 * disallow symlinks as target when opening src and dst */
2725 struct stat st;
2726 const int ifd = fdevent_open_cloexec(src->path.ptr, 0, O_RDONLY, 0);
2727 if (ifd < 0)
2728 return 403; /* Forbidden */
2729 if (0 != fstat(ifd, &st) || !S_ISREG(st.st_mode)) {
2730 close(ifd);
2731 return 403; /* Forbidden */
2732 }
2733
2734 #ifdef _WIN32
2735 /* Windows is frequently incompatible with similar functions from other OS.
2736 * https://docs.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-copyfile
2737 * Symbolic link behavior—If the source file is a symbolic link,
2738 * the actual file copied is the target of the symbolic link.
2739 * If the destination file already exists and is a symbolic link,
2740 * the target of the symbolic link is overwritten by the source file.
2741 * Therefore, open and check src file above, and keep fd open during copy.
2742 * (and pass flag to CopyFile() to fail if target exists)
2743 * (assumes typical windows filesystem behavior where an opened file can not
2744 * be replaced while it is held open. XXX: is this true?)
2745 * Aside: WebDAV does not support symlinks, so there is already the
2746 * assumption that the collection does not contain symlinks unless
2747 * there is some alternate means to access the containing volume.
2748 */
2749 if (CopyFile((LPTSTR)src->path.ptr, (LPTSTR)tmpb->ptr, TRUE)) {
2750 close(ifd);
2751 break; /* copied */
2752 }
2753 #endif
2754
2755 const int ofd = fdevent_open_cloexec(tmpb->ptr, 0,
2756 O_WRONLY | O_CREAT | O_EXCL | O_TRUNC,
2757 WEBDAV_FILE_MODE);
2758 if (ofd < 0) {
2759 close(ifd);
2760 return 403; /* Forbidden */
2761 }
2762
2763 /* perform *blocking* copy (not O_NONBLOCK);
2764 * blocks server from doing any other work until after copy completes
2765 * (should reach here only if unable to use link() and rename()
2766 * due to copy/move crossing device boundaries within the workspace) */
2767 int rc = 0;
2768 do {
2769 if (0 == st.st_size)
2770 break; /* copied */
2771
2772 #ifdef HAVE_COPY_FILE_RANGE
2773 if (!(*flags & WEBDAV_FLAG_NO_CLONE)) {
2774 loff_t ioff = 0; /*(provide offset ptr so ifd offset not changed)*/
2775 loff_t ooff = 0; /*(provide offset ptr so ofd offset not changed)*/
2776 off_t ilen = st.st_size;
2777 ssize_t wr;
2778 do {
2779 #if defined(_LP64) || defined(__LP64__) || defined(_WIN64)
2780 wr = copy_file_range(ifd, &ioff, ofd, &ooff, (size_t)ilen, 0);
2781 #else
2782 wr = copy_file_range(ifd, &ioff, ofd, &ooff,
2783 (size_t)(ilen < INT32_MAX
2784 ? ilen
2785 : (INT32_MAX & ~(131072-1))),
2786 0);
2787 #endif
2788 } while (wr > 0 && (ilen -= wr));
2789 if (__builtin_expect( (0 == ilen), 1))
2790 break; /* copied */
2791
2792 if (-1 == wr) {
2793 rc = errno;
2794 if (rc == ENOSPC)
2795 break;
2796 if (rc == EXDEV) {
2797 /*(cross-filesystem copies introduced in Linux 5.3)
2798 *(overload WEBDAV_FLAG_NO_CLONE to indicate
2799 * no cross-filesystem copy_file_range() support) */
2800 *flags |= WEBDAV_FLAG_NO_CLONE;
2801 if (*flags & WEBDAV_FLAG_COPY_LINK) {
2802 *flags &= ~WEBDAV_FLAG_COPY_LINK;
2803 *flags |= WEBDAV_FLAG_COPY_XDEV;
2804 }
2805 }
2806 }
2807 /*(ifd truncated if (0 == wr && ilen != 0))*/
2808 if (0 != ooff && 0 != ftruncate(ofd, 0)) {
2809 if (0 == rc) rc = errno;
2810 break;
2811 }
2812 /* fallback, retry if copy_file_range() did not finish */
2813 }
2814 #elif defined(FICLONE) /* defined(__linux__) */
2815 /*(redundant if copy_file_range() available)*/
2816 if (!(*flags & (WEBDAV_FLAG_COPY_XDEV
2817 |WEBDAV_FLAG_MOVE_XDEV
2818 |WEBDAV_FLAG_NO_CLONE))) {
2819 rc = ioctl(ofd, FICLONE, ifd);
2820 if (__builtin_expect( (0 == rc), 1))
2821 break; /* copied */
2822
2823 /*(reached if filesystem does not support reflinks or fds not on
2824 * same mounted filesystem. If this code is reached, link() was
2825 * not used, e.g. due to enabling "deprecated-unsafe-partial-put")*/
2826 if (errno == EXDEV) {
2827 if (*flags & WEBDAV_FLAG_COPY_LINK) {
2828 *flags &= ~WEBDAV_FLAG_COPY_LINK;
2829 *flags |= WEBDAV_FLAG_COPY_XDEV;
2830 }
2831 }
2832 else
2833 *flags |= WEBDAV_FLAG_NO_CLONE;
2834 }
2835 #endif
2836
2837 rc = webdav_fcopyfile_sz(ifd, ofd, st.st_size);
2838 if (__builtin_expect( (0 != rc), 0))
2839 rc = errno;
2840 } while (0);
2841
2842 close(ifd);
2843
2844 if (src == dst && 0 == rc) {
2845 /*(note: special-case (src == dst) to copy into tempfile w/o rename,
2846 * expecting input flags = 0, returning open tmpfile fd in *flags
2847 * (or -1 if not opened), and returning tmpfile name in pconf->tmpb) */
2848 *flags = ofd;
2849 return 0;
2850 }
2851
2852 const int wc = close(ofd);
2853 if (__builtin_expect( (0 != wc), 0) && 0 == rc)
2854 rc = errno;
2855
2856 if (__builtin_expect( (0 != rc), 0)) {
2857 /* error reading or writing files */
2858 rc = (rc == ENOSPC) ? 507 : 403;
2859 unlink(tmpb->ptr);
2860 return rc;
2861 }
2862
2863 } while (0);
2864
2865 if (src == dst) {
2866 /*(note: special-case (src == dst) to copy into tempfile w/o rename,
2867 * expecting input flags = 0, returning open tmpfile fd in *flags
2868 * (or -1 if not opened), and returning tmpfile name in pconf->tmpb) */
2869 *flags = -1;
2870 return 0;
2871 }
2872
2873 const int overwrite = (*flags & WEBDAV_FLAG_OVERWRITE);
2874 #ifndef HAVE_RENAMEAT2
2875 if (!overwrite) {
2876 struct stat stb;
2877 if (0 == lstat(dst->path.ptr, &stb) || errno != ENOENT) {
2878 unlink(tmpb->ptr);
2879 return 412; /* Precondition Failed */
2880 }
2881 /* TOC-TOU race between lstat() and rename(),
2882 * but this is reasonable attempt to not overwrite existing entity */
2883 }
2884 if (0 == rename(tmpb->ptr, dst->path.ptr))
2885 #else
2886 if (0 == renameat2(AT_FDCWD, tmpb->ptr,
2887 AT_FDCWD, dst->path.ptr,
2888 overwrite ? 0 : RENAME_NOREPLACE))
2889 #endif
2890 {
2891 /* unconditional stat cache deletion
2892 * (not worth extra syscall/race to detect overwritten or not) */
2893 stat_cache_delete_entry(BUF_PTR_LEN(&dst->path));
2894 return 0;
2895 }
2896 else {
2897 const int errnum = errno;
2898 unlink(tmpb->ptr);
2899 switch (errnum) {
2900 case ENOENT:
2901 case ENOTDIR:
2902 case EISDIR: return 409; /* Conflict */
2903 case EEXIST: return 412; /* Precondition Failed */
2904 default: return 403; /* Forbidden */
2905 }
2906 }
2907 }
2908
2909
2910 static int
webdav_copymove_file(const plugin_config * const pconf,const physical_st * const src,const physical_st * const dst,int * const flags)2911 webdav_copymove_file (const plugin_config * const pconf,
2912 const physical_st * const src,
2913 const physical_st * const dst,
2914 int * const flags)
2915 {
2916 const int overwrite = (*flags & WEBDAV_FLAG_OVERWRITE);
2917 if (*flags & WEBDAV_FLAG_MOVE_RENAME) {
2918 #ifndef HAVE_RENAMEAT2
2919 if (!overwrite) {
2920 struct stat st;
2921 if (0 == lstat(dst->path.ptr, &st) || errno != ENOENT)
2922 return 412; /* Precondition Failed */
2923 /* TOC-TOU race between lstat() and rename(),
2924 * but this is reasonable attempt to not overwrite existing entity*/
2925 }
2926 if (0 == rename(src->path.ptr, dst->path.ptr))
2927 #else
2928 if (0 == renameat2(AT_FDCWD, src->path.ptr,
2929 AT_FDCWD, dst->path.ptr,
2930 overwrite ? 0 : RENAME_NOREPLACE))
2931 #endif
2932 {
2933 /* unconditionally unlink() src if rename() succeeds, just in case
2934 * dst previously existed and was already hard-linked to src. From
2935 * 'man -s 2 rename':
2936 * If oldpath and newpath are existing hard links referring to the
2937 * same file, then rename() does nothing, and returns a success
2938 * status.
2939 * This introduces a small race condition between the rename() and
2940 * unlink() should new file have been created at src in the middle,
2941 * though unlikely if locks are used since locks have not yet been
2942 * released. */
2943 if (overwrite) unlink(src->path.ptr);
2944 /* unconditional stat cache deletion
2945 * (not worth extra syscall/race to detect overwritten or not) */
2946 stat_cache_delete_entry(BUF_PTR_LEN(&dst->path));
2947 stat_cache_delete_entry(BUF_PTR_LEN(&src->path));
2948 webdav_prop_move_uri(pconf, &src->rel_path, &dst->rel_path);
2949 return 0;
2950 }
2951 else if (errno == EEXIST)
2952 return 412; /* Precondition Failed */
2953 }
2954 else if (*flags & WEBDAV_FLAG_COPY_LINK) {
2955 if (0 == linkat(AT_FDCWD, src->path.ptr, AT_FDCWD, dst->path.ptr, 0)){
2956 webdav_prop_copy_uri(pconf, &src->rel_path, &dst->rel_path);
2957 return 0;
2958 }
2959 else if (errno == EEXIST) {
2960 if (!overwrite)
2961 return 412; /* Precondition Failed */
2962 if (0 == webdav_linktmp_rename(pconf, &src->path, &dst->path)) {
2963 webdav_prop_copy_uri(pconf, &src->rel_path, &dst->rel_path);
2964 return 0;
2965 }
2966 }
2967 else if (errno == EXDEV) {
2968 *flags &= ~WEBDAV_FLAG_COPY_LINK;
2969 *flags |= WEBDAV_FLAG_COPY_XDEV;
2970 }
2971 }
2972
2973 /* link() or rename() failed; fall back to copy to tempfile and rename() */
2974 int status = webdav_copytmp_rename(pconf, src, dst, flags);
2975 if (0 == status) {
2976 webdav_prop_copy_uri(pconf, &src->rel_path, &dst->rel_path);
2977 if (*flags & (WEBDAV_FLAG_MOVE_RENAME|WEBDAV_FLAG_MOVE_XDEV))
2978 webdav_delete_file(pconf, src);
2979 /*(copy successful, but how should we report if delete fails?)*/
2980 }
2981 return status;
2982 }
2983
2984
2985 static int
webdav_mkdir(const plugin_config * const pconf,const physical_st * const dst,const int overwrite)2986 webdav_mkdir (const plugin_config * const pconf,
2987 const physical_st * const dst,
2988 const int overwrite)
2989 {
2990 if (0 == mkdir(dst->path.ptr, WEBDAV_DIR_MODE)) {
2991 webdav_parent_modified(&dst->path);
2992 return 0;
2993 }
2994
2995 switch (errno) {
2996 case EEXIST:
2997 case ENOTDIR: break;
2998 case ENOENT: return 409; /* Conflict */
2999 case EPERM:
3000 default: return 403; /* Forbidden */
3001 }
3002
3003 /* [RFC4918] 9.3.1 MKCOL Status Codes
3004 * 405 (Method Not Allowed) -
3005 * MKCOL can only be executed on an unmapped URL.
3006 */
3007 if (overwrite < 0) /*(mod_webdav_mkcol() passes overwrite = -1)*/
3008 return (errno != ENOTDIR)
3009 ? 405 /* Method Not Allowed */
3010 : 409; /* Conflict */
3011
3012 #ifdef __COVERITY__
3013 force_assert(2 <= dst->path.used);
3014 force_assert(2 <= dst->rel_path.used);
3015 #endif
3016
3017 struct stat st;
3018 int status;
3019 dst->path.ptr[dst->path.used-2] = '\0'; /*(trailing slash)*/
3020 status = lstat(dst->path.ptr, &st);
3021 dst->path.ptr[dst->path.used-2] = '/'; /*(restore slash)*/
3022 if (0 != status) /* still ENOTDIR or *just* disappeared */
3023 return 409; /* Conflict */
3024
3025 if (!overwrite) /* copying into a non-dir ? */
3026 return 409; /* Conflict */
3027
3028 if (S_ISDIR(st.st_mode))
3029 return 0;
3030
3031 dst->path.ptr[dst->path.used-2] = '\0'; /*(trailing slash)*/
3032 dst->rel_path.ptr[dst->rel_path.used-2] = '\0';
3033 status = webdav_delete_file(pconf, dst);
3034 dst->path.ptr[dst->path.used-2] = '/'; /*(restore slash)*/
3035 dst->rel_path.ptr[dst->rel_path.used-2] = '/';
3036 if (0 != status)
3037 return status;
3038
3039 webdav_parent_modified(&dst->path);
3040 return (0 == mkdir(dst->path.ptr, WEBDAV_DIR_MODE))
3041 ? 0
3042 : 409; /* Conflict */
3043 }
3044
3045
3046 static int
webdav_copymove_dir(const plugin_config * const pconf,physical_st * const src,physical_st * const dst,request_st * const r,int flags)3047 webdav_copymove_dir (const plugin_config * const pconf,
3048 physical_st * const src,
3049 physical_st * const dst,
3050 request_st * const r,
3051 int flags)
3052 {
3053 /* NOTE: merging collections is NON-CONFORMANT behavior
3054 * (specified in [RFC4918])
3055 *
3056 * However, merging collections during COPY/MOVE might be expected behavior
3057 * by client, as merging is the behavior of unix cp -r (recursive copy) as
3058 * well as how Microsoft Windows Explorer performs folder copies.
3059 *
3060 * [RFC4918] 9.8.4 COPY and Overwriting Destination Resources
3061 * When a collection is overwritten, the membership of the destination
3062 * collection after the successful COPY request MUST be the same
3063 * membership as the source collection immediately before the COPY. Thus,
3064 * merging the membership of the source and destination collections
3065 * together in the destination is not a compliant behavior.
3066 * [Ed: strange how non-compliance statement is immediately followed by:]
3067 * In general, if clients require the state of the destination URL to be
3068 * wiped out prior to a COPY (e.g., to force live properties to be reset),
3069 * then the client could send a DELETE to the destination before the COPY
3070 * request to ensure this reset.
3071 * [Ed: if non-compliant merge behavior is the default here, and were it to
3072 * not be desired by client, client could send a DELETE to the destination
3073 * before issuing COPY. There is no easy way to obtain merge behavior
3074 * (were it not the non-compliant default here) unless the client recurses
3075 * into the source and destination, and creates a list of objects that need
3076 * to be copied. This could fail or miss files due to racing with other
3077 * clients. All of this might forget to emphasize that wiping out an
3078 * existing destination collection (a recursive operation) is dangerous and
3079 * would happen if the client set Overwrite: T or omitted setting Overwrite
3080 * since Overwrite: T is default (client must explicitly set Overwrite: F)]
3081 * [RFC4918] 9.9.3 MOVE and the Overwrite Header
3082 * If a resource exists at the destination and the Overwrite header is
3083 * "T", then prior to performing the move, the server MUST perform a
3084 * DELETE with "Depth: infinity" on the destination resource. If the
3085 * Overwrite header is set to "F", then the operation will fail.
3086 */
3087
3088 /* NOTE: aborting if 507 Insufficient Storage is NON-CONFORMANT behavior
3089 * [RFC4918] specifies that as much as possible of COPY or MOVE
3090 * should be completed.
3091 */
3092
3093 /* ??? upon encountering errors, should src->rel_path or dst->rel_path
3094 * be used in XML error ??? */
3095
3096 struct stat st;
3097 int status;
3098 int dfd;
3099
3100 int make_destdir = 1;
3101 const int overwrite = (flags & WEBDAV_FLAG_OVERWRITE);
3102 if (flags & WEBDAV_FLAG_MOVE_RENAME) {
3103 #ifndef HAVE_RENAMEAT2
3104 if (!overwrite) {
3105 if (0 == lstat(dst->path.ptr, &st) || errno != ENOENT) {
3106 webdav_xml_response_status(r, &src->rel_path, 412);
3107 return 412; /* Precondition Failed */
3108 }
3109 /* TOC-TOU race between lstat() and rename(),
3110 * but this is reasonable attempt to not overwrite existing entity*/
3111 }
3112 if (0 == rename(src->path.ptr, dst->path.ptr))
3113 #else
3114 if (0 == renameat2(AT_FDCWD, src->path.ptr,
3115 AT_FDCWD, dst->path.ptr,
3116 overwrite ? 0 : RENAME_NOREPLACE))
3117 #endif
3118 {
3119 webdav_prop_move_uri_col(pconf, &src->rel_path, &dst->rel_path);
3120 return 0;
3121 }
3122 else {
3123 switch (errno) {
3124 case EEXIST:
3125 #if EEXIST != ENOTEMPTY
3126 case ENOTEMPTY:
3127 #endif
3128 if (!overwrite) {
3129 webdav_xml_response_status(r, &src->rel_path, 412);
3130 return 412; /* Precondition Failed */
3131 }
3132 make_destdir = 0;
3133 break;
3134 case ENOTDIR:
3135 if (!overwrite) {
3136 webdav_xml_response_status(r, &src->rel_path, 409);
3137 return 409; /* Conflict */
3138 }
3139
3140 #ifdef __COVERITY__
3141 force_assert(2 <= dst->path.used);
3142 #endif
3143
3144 dst->path.ptr[dst->path.used-2] = '\0'; /*(trailing slash)*/
3145 status = lstat(dst->path.ptr, &st);
3146 dst->path.ptr[dst->path.used-2] = '/'; /*(restore slash)*/
3147 if (0 == status) {
3148 if (S_ISDIR(st.st_mode)) {
3149 make_destdir = 0;
3150 break;
3151 }
3152
3153 #ifdef __COVERITY__
3154 force_assert(2 <= dst->rel_path.used);
3155 #endif
3156
3157 dst->path.ptr[dst->path.used-2] = '\0'; /*(remove slash)*/
3158 dst->rel_path.ptr[dst->rel_path.used-2] = '\0';
3159 status = webdav_delete_file(pconf, dst);
3160 dst->path.ptr[dst->path.used-2] = '/'; /*(restore slash)*/
3161 dst->rel_path.ptr[dst->rel_path.used-2] = '/';
3162 if (0 != status) {
3163 webdav_xml_response_status(r, &src->rel_path, status);
3164 return status;
3165 }
3166
3167 if (0 == rename(src->path.ptr, dst->path.ptr)) {
3168 webdav_prop_move_uri_col(pconf, &src->rel_path,
3169 &dst->rel_path);
3170 return 0;
3171 }
3172 }
3173 break;
3174 case EXDEV:
3175 flags &= ~WEBDAV_FLAG_MOVE_RENAME;
3176 flags |= WEBDAV_FLAG_MOVE_XDEV;
3177 /* (if overwrite, then could switch to WEBDAV_FLAG_COPY_XDEV
3178 * and set a flag so that before returning from this routine,
3179 * directory is deleted recursively, instead of deleting each
3180 * file after each copy. Only reliable if overwrite is set
3181 * since if it is not set, an error would leave file copies in
3182 * two places and would be difficult to recover if !overwrite)
3183 * (collections typically do not cross devices, so this is not
3184 * expected to be a common case) */
3185 break;
3186 default:
3187 break;
3188 }
3189 }
3190 }
3191
3192 if (make_destdir) {
3193 if (0 != (status = webdav_mkdir(pconf, dst, overwrite))) {
3194 webdav_xml_response_status(r, &src->rel_path, status);
3195 return status;
3196 }
3197 }
3198
3199 webdav_prop_copy_uri(pconf, &src->rel_path, &dst->rel_path);
3200
3201 /* copy from src to dst (and, if move, then delete src)
3202 * src and dst are modified in place to extend path,
3203 * so be sure to restore to base each loop iter */
3204
3205 const uint32_t src_path_used = src->path.used;
3206 const uint32_t src_rel_path_used = src->rel_path.used;
3207 const uint32_t dst_path_used = dst->path.used;
3208 const uint32_t dst_rel_path_used = dst->rel_path.used;
3209
3210 #ifndef _ATFILE_SOURCE /*(not using fdopendir unless _ATFILE_SOURCE)*/
3211 dfd = -1;
3212 DIR * const srcdir = opendir(src->path.ptr);
3213 #else
3214 dfd = fdevent_open_dirname(src->path.ptr, 0);
3215 DIR * const srcdir = (dfd >= 0) ? fdopendir(dfd) : NULL;
3216 #endif
3217 if (NULL == srcdir) {
3218 if (dfd >= 0) close(dfd);
3219 webdav_xml_response_status(r, &src->rel_path, 403);
3220 return 403; /* Forbidden */
3221 }
3222 mode_t d_type;
3223 int multi_status = 0;
3224 struct dirent *de;
3225 while (NULL != (de = readdir(srcdir))) {
3226 if (de->d_name[0] == '.'
3227 && (de->d_name[1] == '\0'
3228 || (de->d_name[1] == '.' && de->d_name[2] == '\0')))
3229 continue; /* ignore "." and ".." */
3230
3231 #ifdef _DIRENT_HAVE_D_TYPE
3232 if (de->d_type != DT_UNKNOWN)
3233 d_type = DTTOIF(de->d_type);
3234 else
3235 #endif
3236 {
3237 #ifdef _ATFILE_SOURCE
3238 if (0 != fstatat(dfd, de->d_name, &st, AT_SYMLINK_NOFOLLOW))
3239 continue; /* file *just* disappeared? */
3240 d_type = st.st_mode;
3241 #endif
3242 }
3243
3244 const uint32_t len = (uint32_t) _D_EXACT_NAMLEN(de);
3245 if (flags & WEBDAV_FLAG_LC_NAMES) /*(needed at least for rel_path)*/
3246 webdav_str_len_to_lower(de->d_name, len);
3247
3248 buffer_append_string_len(&src->path, de->d_name, len);
3249 buffer_append_string_len(&dst->path, de->d_name, len);
3250 buffer_append_string_len(&src->rel_path, de->d_name, len);
3251 buffer_append_string_len(&dst->rel_path, de->d_name, len);
3252
3253 #ifndef _ATFILE_SOURCE
3254 #ifdef _DIRENT_HAVE_D_TYPE
3255 if (de->d_type == DT_UNKNOWN)
3256 #endif
3257 {
3258 if (0 != stat(src->path.ptr, &st)) {
3259 src->path.ptr[ (src->path.used = src_path_used) -1]='\0';
3260 src->rel_path.ptr[(src->rel_path.used = src_rel_path_used)-1]='\0';
3261 dst->path.ptr[ (dst->path.used = dst_path_used) -1]='\0';
3262 dst->rel_path.ptr[(dst->rel_path.used = dst_rel_path_used)-1]='\0';
3263 continue; /* file *just* disappeared? */
3264 }
3265 d_type = st.st_mode;
3266 }
3267 #endif
3268
3269 if (S_ISDIR(d_type)) { /* recursive call; depth first */
3270 buffer_append_char(&src->path, '/');
3271 buffer_append_char(&dst->path, '/');
3272 buffer_append_char(&src->rel_path, '/');
3273 buffer_append_char(&dst->rel_path, '/');
3274 status = webdav_copymove_dir(pconf, src, dst, r, flags);
3275 if (0 != status)
3276 multi_status = 1;
3277 }
3278 else if (S_ISREG(d_type)) {
3279 status = webdav_copymove_file(pconf, src, dst, &flags);
3280 if (0 != status)
3281 webdav_xml_response_status(r, &src->rel_path, status);
3282 }
3283 #if 0
3284 else if (S_ISLNK(d_type)) {
3285 /*(might entertain support in future, including readlink()
3286 * and changing dst symlink to be relative to new location.
3287 * (or, if absolute to the old location, then absolute to new)
3288 * Be sure to hard-link using linkat() w/o AT_SYMLINK_FOLLOW)*/
3289 }
3290 #endif
3291 else {
3292 status = 0;
3293 }
3294
3295 src->path.ptr[ (src->path.used = src_path_used) -1] = '\0';
3296 src->rel_path.ptr[(src->rel_path.used = src_rel_path_used)-1] = '\0';
3297 dst->path.ptr[ (dst->path.used = dst_path_used) -1] = '\0';
3298 dst->rel_path.ptr[(dst->rel_path.used = dst_rel_path_used)-1] = '\0';
3299
3300 if (507 == status) {
3301 multi_status = 507; /* Insufficient Storage */
3302 break;
3303 }
3304 }
3305 closedir(srcdir);
3306
3307 if (0 == multi_status) {
3308 if (flags & (WEBDAV_FLAG_MOVE_RENAME|WEBDAV_FLAG_MOVE_XDEV)) {
3309 status = webdav_delete_dir(pconf, src, r, flags); /* content */
3310 if (0 != status) {
3311 webdav_xml_response_status(r, &src->rel_path, status);
3312 multi_status = 1;
3313 }
3314 }
3315 }
3316
3317 return multi_status;
3318 }
3319
3320
3321 typedef struct webdav_propfind_bufs {
3322 request_st * restrict r;
3323 const plugin_config * restrict pconf;
3324 physical_st * restrict dst;
3325 buffer * restrict b;
3326 buffer * restrict b_200;
3327 buffer * restrict b_404;
3328 webdav_property_names proplist;
3329 int allprop;
3330 int propname;
3331 int lockdiscovery;
3332 int depth;
3333 int recursed;
3334 int atflags;
3335 struct stat st;
3336 } webdav_propfind_bufs;
3337
3338
3339 enum webdav_live_props_e {
3340 WEBDAV_PROP_UNSET = -1 /* (enum value to avoid compiler warning)*/
3341 ,WEBDAV_PROP_ALL = 0 /* (ALL not really a prop; internal use) */
3342 /*,WEBDAV_PROP_CREATIONDATE*/ /* (located in database, if present) */
3343 /*,WEBDAV_PROP_DISPLAYNAME*/ /* (located in database, if present) */
3344 /*,WEBDAV_PROP_GETCONTENTLANGUAGE*/ /* (located in database, if present) */
3345 ,WEBDAV_PROP_GETCONTENTLENGTH
3346 ,WEBDAV_PROP_GETCONTENTTYPE
3347 ,WEBDAV_PROP_GETETAG
3348 ,WEBDAV_PROP_GETLASTMODIFIED
3349 /*,WEBDAV_PROP_LOCKDISCOVERY*/ /* (located in database, if present) */
3350 ,WEBDAV_PROP_RESOURCETYPE
3351 /*,WEBDAV_PROP_SOURCE*/ /* not implemented; removed in RFC4918 */
3352 ,WEBDAV_PROP_SUPPORTEDLOCK
3353 };
3354
3355
3356 #ifdef USE_PROPPATCH
3357
3358 struct live_prop_list {
3359 const char *prop;
3360 const uint32_t len;
3361 enum webdav_live_props_e pnum;
3362 };
3363
3364 static const struct live_prop_list live_properties[] = { /*(namespace "DAV:")*/
3365 /* { CONST_STR_LEN("creationdate"), WEBDAV_PROP_CREATIONDATE }*/
3366 /*,{ CONST_STR_LEN("displayname"), WEBDAV_PROP_DISPLAYNAME }*/
3367 /*,{ CONST_STR_LEN("getcontentlanguage"), WEBDAV_PROP_GETCONTENTLANGUAGE}*/
3368 { CONST_STR_LEN("getcontentlength"), WEBDAV_PROP_GETCONTENTLENGTH }
3369 ,{ CONST_STR_LEN("getcontenttype"), WEBDAV_PROP_GETCONTENTTYPE }
3370 ,{ CONST_STR_LEN("getetag"), WEBDAV_PROP_GETETAG }
3371 ,{ CONST_STR_LEN("getlastmodified"), WEBDAV_PROP_GETLASTMODIFIED }
3372 #ifdef USE_LOCKS
3373 /*,{ CONST_STR_LEN("lockdiscovery"), WEBDAV_PROP_LOCKDISCOVERY }*/
3374 #endif
3375 ,{ CONST_STR_LEN("resourcetype"), WEBDAV_PROP_RESOURCETYPE }
3376 /*,{ CONST_STR_LEN("source"), WEBDAV_PROP_SOURCE }*/
3377 #ifdef USE_LOCKS
3378 ,{ CONST_STR_LEN("supportedlock"), WEBDAV_PROP_SUPPORTEDLOCK }
3379 #endif
3380
3381 ,{ NULL, 0, WEBDAV_PROP_UNSET }
3382 };
3383
3384 /* protected live properties
3385 * (must also protect creationdate and lockdiscovery in database) */
3386 static const struct live_prop_list protected_props[] = { /*(namespace "DAV:")*/
3387 { CONST_STR_LEN("creationdate"), WEBDAV_PROP_UNSET
3388 /*WEBDAV_PROP_CREATIONDATE*/ }
3389 /*,{ CONST_STR_LEN("displayname"), WEBDAV_PROP_DISPLAYNAME }*/
3390 /*,{ CONST_STR_LEN("getcontentlanguage"), WEBDAV_PROP_GETCONTENTLANGUAGE}*/
3391 ,{ CONST_STR_LEN("getcontentlength"), WEBDAV_PROP_GETCONTENTLENGTH }
3392 ,{ CONST_STR_LEN("getcontenttype"), WEBDAV_PROP_GETCONTENTTYPE }
3393 ,{ CONST_STR_LEN("getetag"), WEBDAV_PROP_GETETAG }
3394 ,{ CONST_STR_LEN("getlastmodified"), WEBDAV_PROP_GETLASTMODIFIED }
3395 ,{ CONST_STR_LEN("lockdiscovery"), WEBDAV_PROP_UNSET
3396 /*WEBDAV_PROP_LOCKDISCOVERY*/ }
3397 ,{ CONST_STR_LEN("resourcetype"), WEBDAV_PROP_RESOURCETYPE }
3398 /*,{ CONST_STR_LEN("source"), WEBDAV_PROP_SOURCE }*/
3399 ,{ CONST_STR_LEN("supportedlock"), WEBDAV_PROP_SUPPORTEDLOCK }
3400
3401 ,{ NULL, 0, WEBDAV_PROP_UNSET }
3402 };
3403
3404 #endif
3405
3406
3407 static int
webdav_propfind_live_props(const webdav_propfind_bufs * const restrict pb,const enum webdav_live_props_e pnum)3408 webdav_propfind_live_props (const webdav_propfind_bufs * const restrict pb,
3409 const enum webdav_live_props_e pnum)
3410 {
3411 buffer * const restrict b = pb->b_200;
3412 switch (pnum) {
3413 case WEBDAV_PROP_ALL:
3414 /*__attribute_fallthrough__*/
3415 /*case WEBDAV_PROP_CREATIONDATE:*/ /* (located in database, if present)*/
3416 #if 0
3417 case WEBDAV_PROP_CREATIONDATE: {
3418 /* st->st_ctim
3419 * defined by POSIX.1-2008 as last file status change timestamp
3420 * and is no long create-time (as it may have been on older filesystems)
3421 * Therefore, this should return Not Found.
3422 * [RFC4918] 15.1 creationdate Property
3423 * The DAV:creationdate property SHOULD be defined on all DAV
3424 * compliant resources. If present, it contains a timestamp of the
3425 * moment when the resource was created. Servers that are incapable
3426 * of persistently recording the creation date SHOULD instead leave
3427 * it undefined (i.e. report "Not Found").
3428 * (future: might store creationdate in database when PUT creates file
3429 * or LOCK creates empty file, or MKCOL creates collection (dir),
3430 * i.e. wherever the status is 201 Created)
3431 */
3432 struct tm tm;
3433 if (__builtin_expect( (NULL != gmtime64_r(&pb->st.st_ctime, &tm)), 1)) {
3434 buffer_append_string_len(b, CONST_STR_LEN(
3435 "<D:creationdate ns0:dt=\"dateTime.tz\">"));
3436 buffer_append_strftime(b, "%FT%TZ", &tm));
3437 buffer_append_string_len(b, CONST_STR_LEN(
3438 "</D:creationdate>"));
3439 }
3440 else if (pnum != WEBDAV_PROP_ALL)
3441 return -1; /* invalid; report 'not found' */
3442 if (pnum != WEBDAV_PROP_ALL) return 0;/* found *//*(else fall through)*/
3443 __attribute_fallthrough__
3444 }
3445 #endif
3446 /*case WEBDAV_PROP_DISPLAYNAME:*/ /* (located in database, if present)*/
3447 /*case WEBDAV_PROP_GETCONTENTLANGUAGE:*/ /* (located in db, if present)*/
3448 #if 0
3449 case WEBDAV_PROP_GETCONTENTLANGUAGE:
3450 /* [RFC4918] 15.3 getcontentlanguage Property
3451 * SHOULD NOT be protected, so that clients can reset the language.
3452 * [...]
3453 * The DAV:getcontentlanguage property MUST be defined on any
3454 * DAV-compliant resource that returns the Content-Language header on
3455 * a GET.
3456 * (future: server does not currently set Content-Language and this
3457 * module would need to somehow find out if another module set it)
3458 */
3459 buffer_append_string_len(b, CONST_STR_LEN(
3460 "<D:getcontentlanguage>en</D:getcontentlanguage>"));
3461 if (pnum != WEBDAV_PROP_ALL) return 0;/* found *//*(else fall through)*/
3462 __attribute_fallthrough__
3463 #endif
3464 case WEBDAV_PROP_GETCONTENTLENGTH:
3465 buffer_append_string_len(b, CONST_STR_LEN(
3466 "<D:getcontentlength>"));
3467 buffer_append_int(b, pb->st.st_size);
3468 buffer_append_string_len(b, CONST_STR_LEN(
3469 "</D:getcontentlength>"));
3470 if (pnum != WEBDAV_PROP_ALL) return 0;/* found *//*(else fall through)*/
3471 __attribute_fallthrough__
3472 case WEBDAV_PROP_GETCONTENTTYPE:
3473 /* [RFC4918] 15.5 getcontenttype Property
3474 * Potentially protected if the server prefers to assign content types
3475 * on its own (see also discussion in Section 9.7.1).
3476 * (server currently assigns content types)
3477 *
3478 * [RFC4918] 15 DAV Properties
3479 * For properties defined based on HTTP GET response headers
3480 * (DAV:get*), the header value could include LWS as defined
3481 * in [RFC2616], Section 4.2. Server implementors SHOULD strip
3482 * LWS from these values before using as WebDAV property
3483 * values.
3484 * e.g. application/xml;charset=utf-8
3485 * instead of: application/xml; charset="utf-8"
3486 * (documentation-only; no check is done here to remove LWS)
3487 */
3488 if (S_ISDIR(pb->st.st_mode)) {
3489 buffer_append_string_len(b, CONST_STR_LEN(
3490 "<D:getcontenttype>httpd/unix-directory</D:getcontenttype>"));
3491 }
3492 else {
3493 /* provide content type by extension
3494 * Note: not currently supporting filesystem xattr */
3495 const array * const mtypes = pb->r->conf.mimetypes;
3496 const buffer *ct =
3497 stat_cache_mimetype_by_ext(mtypes, BUF_PTR_LEN(&pb->dst->path));
3498 if (NULL != ct) {
3499 buffer_append_str3(b,
3500 CONST_STR_LEN(
3501 "<D:getcontenttype>"),
3502 BUF_PTR_LEN(ct),
3503 CONST_STR_LEN(
3504 "</D:getcontenttype>"));
3505 }
3506 else {
3507 if (pnum != WEBDAV_PROP_ALL)
3508 return -1; /* invalid; report 'not found' */
3509 }
3510 }
3511 if (pnum != WEBDAV_PROP_ALL) return 0;/* found *//*(else fall through)*/
3512 __attribute_fallthrough__
3513 case WEBDAV_PROP_GETETAG:
3514 if (0 != pb->r->conf.etag_flags) {
3515 buffer * const etagb = pb->r->tmp_buf;
3516 http_etag_create(etagb, &pb->st, pb->r->conf.etag_flags);
3517 buffer_append_str3(b,
3518 CONST_STR_LEN(
3519 "<D:getetag>"),
3520 BUF_PTR_LEN(etagb),
3521 CONST_STR_LEN(
3522 "</D:getetag>"));
3523 }
3524 else if (pnum != WEBDAV_PROP_ALL)
3525 return -1; /* invalid; report 'not found' */
3526 if (pnum != WEBDAV_PROP_ALL) return 0;/* found *//*(else fall through)*/
3527 __attribute_fallthrough__
3528 case WEBDAV_PROP_GETLASTMODIFIED:
3529 buffer_append_string_len(b, CONST_STR_LEN(
3530 "<D:getlastmodified ns0:dt=\"dateTime.rfc1123\">"));
3531 http_date_time_append(b, pb->st.st_mtime);
3532 buffer_append_string_len(b, CONST_STR_LEN(
3533 "</D:getlastmodified>"));
3534 if (pnum != WEBDAV_PROP_ALL) return 0;/* found *//*(else fall through)*/
3535 __attribute_fallthrough__
3536 #if 0
3537 #ifdef USE_LOCKS
3538 case WEBDAV_PROP_LOCKDISCOVERY:
3539 /* database query for locks occurs in webdav_propfind_resource_props()*/
3540 if (pnum != WEBDAV_PROP_ALL) return 0;/* found *//*(else fall through)*/
3541 __attribute_fallthrough__
3542 #endif
3543 #endif
3544 case WEBDAV_PROP_RESOURCETYPE:
3545 if (S_ISDIR(pb->st.st_mode))
3546 buffer_append_string_len(b, CONST_STR_LEN(
3547 "<D:resourcetype><D:collection/></D:resourcetype>"));
3548 else
3549 buffer_append_string_len(b, CONST_STR_LEN(
3550 "<D:resourcetype/>"));
3551 if (pnum != WEBDAV_PROP_ALL) return 0;/* found *//*(else fall through)*/
3552 __attribute_fallthrough__
3553 /*case WEBDAV_PROP_SOURCE:*/ /* not impl; removed in RFC4918 */
3554 #ifdef USE_LOCKS
3555 case WEBDAV_PROP_SUPPORTEDLOCK:
3556 buffer_append_string_len(b, CONST_STR_LEN(
3557 "<D:supportedlock>"
3558 "<D:lockentry>"
3559 "<D:lockscope><D:exclusive/></D:lockscope>"
3560 "<D:locktype><D:write/></D:locktype>"
3561 "</D:lockentry>"
3562 "<D:lockentry>"
3563 "<D:lockscope><D:shared/></D:lockscope>"
3564 "<D:locktype><D:write/></D:locktype>"
3565 "</D:lockentry>"
3566 "</D:supportedlock>"));
3567 if (pnum != WEBDAV_PROP_ALL) return 0;/* found *//*(else fall through)*/
3568 __attribute_fallthrough__
3569 #endif
3570 default: /* WEBDAV_PROP_UNSET */
3571 if (pnum == WEBDAV_PROP_ALL) break;
3572 return -1; /* not found */
3573 }
3574 return 0; /* found (WEBDAV_PROP_ALL) */
3575 }
3576
3577
3578 #ifdef USE_LOCKS
3579 static void
webdav_propfind_lockdiscovery_cb(void * const vdata,const webdav_lockdata * const lockdata)3580 webdav_propfind_lockdiscovery_cb (void * const vdata,
3581 const webdav_lockdata * const lockdata)
3582 {
3583 webdav_xml_activelock((buffer *)vdata, lockdata, NULL, 0);
3584 }
3585 #endif
3586
3587
3588 static void
webdav_propfind_resource_props(const webdav_propfind_bufs * const restrict pb)3589 webdav_propfind_resource_props (const webdav_propfind_bufs * const restrict pb)
3590 {
3591 const webdav_property_names * const props = &pb->proplist;
3592 if (props->used) { /* "props" or "allprop"+"include" */
3593 const webdav_property_name *prop = props->ptr;
3594 for (int i = 0; i < props->used; ++i, ++prop) {
3595 if (NULL == prop->name /*(flag indicating prop is live prop enum)*/
3596 ? 0 == webdav_propfind_live_props(pb, (enum webdav_live_props_e)
3597 prop->namelen)
3598 : 0 == webdav_prop_select_prop(pb->pconf, &pb->dst->rel_path,
3599 prop, pb->b_200))
3600 continue;
3601
3602 /*(error obtaining prop if reached)*/
3603 if (prop->name)
3604 webdav_xml_prop(pb->b_404, prop, NULL, 0);
3605 else {
3606 #ifdef USE_PROPPATCH
3607 const struct live_prop_list *list = live_properties;
3608 while (0 != list->len && (uint32_t)list->pnum != prop->namelen)
3609 ++list;
3610 if (0 != list->len) { /*(list->pnum == prop->namelen)*/
3611 webdav_property_name lprop =
3612 { prop->ns, list->prop, prop->nslen, list->len };
3613 webdav_xml_prop(pb->b_404, &lprop, NULL, 0);
3614 }
3615 #endif
3616 }
3617 }
3618 }
3619
3620 if (pb->allprop) {
3621 webdav_propfind_live_props(pb, WEBDAV_PROP_ALL);
3622 webdav_prop_select_props(pb->pconf, &pb->dst->rel_path, pb->b_200);
3623 }
3624
3625 #ifdef USE_LOCKS
3626 if (pb->lockdiscovery) {
3627 /* pb->lockdiscovery > 0:
3628 * report locks resource or containing (parent) collections
3629 * pb->lockdiscovery < 0:
3630 * report only those locks on specific resource
3631 * While this is not compliant with RFC, it may reduces quite a bit of
3632 * redundancy for propfind on Depth: 1 and Depth: infinity when there
3633 * are locks on parent collections. The client receiving this propfind
3634 * XML response should easily know that locks on collections apply to
3635 * the members of those collections and to further nested collections
3636 *
3637 * future: might be many, many fewer database queries if make a single
3638 * query for the locks in the collection directory tree and parse the
3639 * results, rather than querying the database for each resource */
3640 buffer_append_string_len(pb->b_200, CONST_STR_LEN(
3641 "<D:lockdiscovery>"));
3642 webdav_lock_activelocks(pb->pconf, &pb->dst->rel_path,
3643 (pb->lockdiscovery > 0),
3644 webdav_propfind_lockdiscovery_cb, pb->b_200);
3645 buffer_append_string_len(pb->b_200, CONST_STR_LEN(
3646 "</D:lockdiscovery>"));
3647 }
3648 #endif
3649 }
3650
3651
3652 static void
webdav_propfind_resource_propnames(const webdav_propfind_bufs * const restrict pb)3653 webdav_propfind_resource_propnames (const webdav_propfind_bufs *
3654 const restrict pb)
3655 {
3656 static const char live_propnames[] =
3657 "<getcontentlength/>\n"
3658 "<getcontenttype/>\n"
3659 "<getetag/>\n"
3660 "<getlastmodified/>\n"
3661 "<resourcetype/>\n"
3662 #ifdef USE_LOCKS
3663 "<supportedlock/>\n"
3664 "<lockdiscovery/>\n"
3665 #endif
3666 ;
3667 /* list live_properties which are not in database, plus "lockdiscovery" */
3668 buffer_append_string_len(pb->b_200,live_propnames,sizeof(live_propnames)-1);
3669
3670 /* list properties in database 'properties' table for resource */
3671 webdav_prop_select_propnames(pb->pconf, &pb->dst->rel_path, pb->b_200);
3672 }
3673
3674
3675 __attribute_cold__
3676 static void
webdav_propfind_resource_403(const webdav_propfind_bufs * const restrict pb)3677 webdav_propfind_resource_403 (const webdav_propfind_bufs * const restrict pb)
3678 {
3679 buffer * const restrict b = pb->b;
3680 buffer_append_string_len(b, CONST_STR_LEN(
3681 "<D:response>\n"));
3682 webdav_xml_href(b, &pb->dst->rel_path);
3683 buffer_append_string_len(b, CONST_STR_LEN(
3684 "<D:propstat>\n"));
3685 webdav_xml_status(b, 403); /* Forbidden */
3686 buffer_append_string_len(b, CONST_STR_LEN(
3687 "</D:propstat>\n"
3688 "</D:response>\n"));
3689
3690 webdav_double_buffer(pb->r, b);
3691 }
3692
3693
3694 static void
webdav_propfind_resource(const webdav_propfind_bufs * const restrict pb)3695 webdav_propfind_resource (const webdav_propfind_bufs * const restrict pb)
3696 {
3697 buffer_clear(pb->b_200);
3698 buffer_clear(pb->b_404);
3699
3700 if (!pb->propname)
3701 webdav_propfind_resource_props(pb);
3702 else
3703 webdav_propfind_resource_propnames(pb);
3704
3705 /* buffer could get very large for large directory (or Depth: infinity)
3706 * attempt to allocate in 8K chunks, rather than default realloc in
3707 * 64-byte chunks (see buffer.h) which will lead to exponentially more
3708 * expensive copy behavior as buffer is resized over and over and over
3709 *
3710 * future: avoid (potential) excessive memory usage by accumulating output
3711 * in temporary file
3712 */
3713 buffer * const restrict b = pb->b;
3714 buffer * const restrict b_200 = pb->b_200;
3715 buffer * const restrict b_404 = pb->b_404;
3716 if (b->size - b->used < b_200->used + b_404->used + 1024) {
3717 size_t sz = b->used + 8192-1 + b_200->used + b_404->used + 1024 - 1;
3718 /*(optimization; buffer is extended as needed)*/
3719 buffer_string_prepare_append(b, sz & (8192-1));
3720 }
3721
3722 buffer_append_string_len(b, CONST_STR_LEN(
3723 "<D:response>\n"));
3724 webdav_xml_href(b, &pb->dst->rel_path);
3725 if (!buffer_is_blank(b_200))
3726 webdav_xml_propstat(b, b_200, 200);
3727 if (!buffer_is_blank(b_404))
3728 webdav_xml_propstat(b, b_404, 404);
3729 buffer_append_string_len(b, CONST_STR_LEN(
3730 "</D:response>\n"));
3731
3732 webdav_double_buffer(pb->r, b);
3733 }
3734
3735
3736 static void
webdav_propfind_dir(webdav_propfind_bufs * const restrict pb)3737 webdav_propfind_dir (webdav_propfind_bufs * const restrict pb)
3738 {
3739 /* arbitrary recursion limit to prevent infinite loops,
3740 * e.g. due to symlink loops, or excessive resource usage */
3741 if (++pb->recursed > 100) return;
3742
3743 physical_st * const dst = pb->dst;
3744 #ifndef _ATFILE_SOURCE /*(not using fdopendir unless _ATFILE_SOURCE)*/
3745 const int dfd = -1;
3746 DIR * const dir = opendir(dst->path.ptr);
3747 #else
3748 const int dfd = fdevent_open_dirname(dst->path.ptr, 0);
3749 DIR * const dir = (dfd >= 0) ? fdopendir(dfd) : NULL;
3750 #endif
3751 if (NULL == dir) {
3752 int errnum = errno;
3753 if (dfd >= 0) close(dfd);
3754 if (errnum != ENOENT)
3755 webdav_propfind_resource_403(pb); /* Forbidden */
3756 return;
3757 }
3758
3759 webdav_propfind_resource(pb);
3760
3761 if (pb->lockdiscovery > 0)
3762 pb->lockdiscovery = -pb->lockdiscovery; /*(check locks on node only)*/
3763
3764 /* dst is modified in place to extend path,
3765 * so be sure to restore to base each loop iter */
3766 const uint32_t dst_path_used = dst->path.used;
3767 const uint32_t dst_rel_path_used = dst->rel_path.used;
3768 const int flags =
3769 (pb->r->conf.force_lowercase_filenames ? WEBDAV_FLAG_LC_NAMES : 0);
3770 struct dirent *de;
3771 while (NULL != (de = readdir(dir))) {
3772 if (de->d_name[0] == '.'
3773 && (de->d_name[1] == '\0'
3774 || (de->d_name[1] == '.' && de->d_name[2] == '\0')))
3775 continue; /* ignore "." and ".." */
3776
3777 #ifdef _ATFILE_SOURCE
3778 if (0 != fstatat(dfd, de->d_name, &pb->st, pb->atflags))
3779 continue; /* file *just* disappeared? */
3780 #endif
3781
3782 const uint32_t len = (uint32_t) _D_EXACT_NAMLEN(de);
3783 if (flags & WEBDAV_FLAG_LC_NAMES) /*(needed by rel_path)*/
3784 webdav_str_len_to_lower(de->d_name, len);
3785 buffer_append_string_len(&dst->path, de->d_name, len);
3786 buffer_append_string_len(&dst->rel_path, de->d_name, len);
3787 #ifndef _ATFILE_SOURCE
3788 if (0 != stat(dst->path.ptr, &pb->st)) {
3789 dst->path.ptr[ (dst->path.used = dst_path_used) -1]='\0';
3790 dst->rel_path.ptr[(dst->rel_path.used = dst_rel_path_used)-1]='\0';
3791 continue; /* file *just* disappeared? */
3792 }
3793 #endif
3794 if (S_ISDIR(pb->st.st_mode)) {
3795 buffer_append_char(&dst->path, '/');
3796 buffer_append_char(&dst->rel_path, '/');
3797 }
3798
3799 if (S_ISDIR(pb->st.st_mode) && -1 == pb->depth)
3800 webdav_propfind_dir(pb); /* recurse */
3801 else
3802 webdav_propfind_resource(pb);
3803
3804 dst->path.ptr[ (dst->path.used = dst_path_used) -1] = '\0';
3805 dst->rel_path.ptr[(dst->rel_path.used = dst_rel_path_used)-1] = '\0';
3806 }
3807 closedir(dir);
3808 }
3809
3810
3811 #if defined(USE_PROPPATCH) || defined(USE_LOCKS)
3812
3813 static char *
webdav_mmap_file_chunk(chunk * const c,log_error_st * const errh)3814 webdav_mmap_file_chunk (chunk * const c, log_error_st * const errh)
3815 {
3816 #ifdef HAVE_MMAP
3817 /*(request body provided in temporary file, so ok to mmap().
3818 * Otherwise, must access through sys_setjmp_eval3()) */
3819 /*assert(c->type == FILE_CHUNK);*/
3820 const off_t len = c->file.length - c->offset;
3821 const chunk_file_view * const restrict cfv =
3822 chunkqueue_chunk_file_view(c, len, errh);
3823 return (cfv && chunk_file_view_dlen(cfv, c->offset) >= len)
3824 ? chunk_file_view_dptr(cfv, c->offset)
3825 : NULL;
3826 #else
3827 UNUSED(c);
3828 UNUSED(errh);
3829 return NULL;
3830 #endif
3831 }
3832
3833
3834 __attribute_noinline__
3835 static xmlDoc *
webdav_parse_chunkqueue(request_st * const r,const plugin_config * const pconf)3836 webdav_parse_chunkqueue (request_st * const r,
3837 const plugin_config * const pconf)
3838 {
3839 /* parse the XML document */
3840 xmlParserCtxtPtr ctxt = xmlCreatePushParserCtxt(NULL, NULL, NULL, 0, NULL);
3841 /* XXX: evaluate adding more xmlParserOptions */
3842 xmlCtxtUseOptions(ctxt, XML_PARSE_NOERROR | XML_PARSE_NOWARNING
3843 | XML_PARSE_PEDANTIC| XML_PARSE_NONET);
3844 char *xmlstr;
3845 chunkqueue * const cq = &r->reqbody_queue;
3846 size_t weWant = chunkqueue_length(cq);
3847 int err = XML_ERR_OK;
3848
3849 while (weWant) {
3850 size_t weHave = 0;
3851 chunk *c = cq->first;
3852 char buf[16384];
3853 #ifdef __COVERITY__
3854 force_assert(0 == weWant || c != NULL);
3855 #endif
3856
3857 if (c->type == MEM_CHUNK) {
3858 xmlstr = c->mem->ptr + c->offset;
3859 weHave = buffer_clen(c->mem) - c->offset;
3860 }
3861 else if (c->type == FILE_CHUNK) {
3862 xmlstr = webdav_mmap_file_chunk(c, r->conf.errh);
3863 if (NULL != xmlstr) {
3864 weHave = c->file.length - c->offset;
3865 }
3866 else {
3867 char *data = buf;
3868 uint32_t dlen = sizeof(buf);
3869 if (0 == chunkqueue_peek_data(cq, &data, &dlen, r->conf.errh)) {
3870 xmlstr = data;
3871 weHave = dlen;
3872 }
3873 else {
3874 err = XML_IO_UNKNOWN;
3875 break;
3876 }
3877 }
3878 }
3879 else {
3880 log_error(r->conf.errh, __FILE__, __LINE__,
3881 "unrecognized chunk type: %d", c->type);
3882 err = XML_IO_UNKNOWN;
3883 break;
3884 }
3885
3886 if (weHave > weWant) weHave = weWant;
3887
3888 if (pconf->log_xml)
3889 log_error(r->conf.errh, __FILE__, __LINE__,
3890 "XML-request-body: %.*s", (int)weHave, xmlstr);
3891
3892 if (XML_ERR_OK != (err = xmlParseChunk(ctxt, xmlstr, weHave, 0))) {
3893 log_error(r->conf.errh, __FILE__, __LINE__,
3894 "xmlParseChunk failed at: %lld %zu %d",
3895 (long long int)cq->bytes_out, weHave, err);
3896 break;
3897 }
3898
3899 weWant -= weHave;
3900 chunkqueue_mark_written(cq, weHave);
3901 }
3902
3903 if (XML_ERR_OK == err) {
3904 switch ((err = xmlParseChunk(ctxt, 0, 0, 1))) {
3905 case XML_ERR_DOCUMENT_END:
3906 case XML_ERR_OK:
3907 if (ctxt->wellFormed) {
3908 xmlDoc * const xml = ctxt->myDoc;
3909 xmlFreeParserCtxt(ctxt);
3910 return xml;
3911 }
3912 break;
3913 default:
3914 log_error(r->conf.errh, __FILE__, __LINE__,
3915 "xmlParseChunk failed at final packet: %d", err);
3916 break;
3917 }
3918 }
3919
3920 xmlFreeDoc(ctxt->myDoc);
3921 xmlFreeParserCtxt(ctxt);
3922 return NULL;
3923 }
3924
3925 #endif
3926
3927
3928 #ifdef USE_LOCKS
3929
3930 struct webdav_lock_token_submitted_st {
3931 buffer *tokens;
3932 int used;
3933 const buffer *authn_user;
3934 buffer *b;
3935 request_st *r;
3936 int nlocks;
3937 int slocks;
3938 int smatch;
3939 };
3940
3941
3942 static void
webdav_lock_token_submitted_cb(void * const vdata,const webdav_lockdata * const lockdata)3943 webdav_lock_token_submitted_cb (void * const vdata,
3944 const webdav_lockdata * const lockdata)
3945 {
3946 /* RFE: improve support for shared locks
3947 * (instead of treating match of any shared lock as sufficient,
3948 * even when there are different lockroots)
3949 * keep track of matched shared locks and unmatched shared locks and
3950 * ensure that each lockroot with shared locks has at least one match
3951 * (Will need to allocate strings for each URI with shared lock and keep
3952 * track whether or not a shared lock has been matched for that URI.
3953 * After walking all locks, must walk looking for unmatched URIs,
3954 * and must free these strings) */
3955
3956 /* [RFC4918] 6.4 Lock Creator and Privileges
3957 * When a locked resource is modified, a server MUST check that the
3958 * authenticated principal matches the lock creator (in addition to
3959 * checking for valid lock token submission).
3960 */
3961
3962 struct webdav_lock_token_submitted_st * const cbdata =
3963 (struct webdav_lock_token_submitted_st *)vdata;
3964 const buffer * const locktoken = &lockdata->locktoken;
3965 const int shared = (lockdata->lockscope->used != sizeof("exclusive"));
3966
3967 ++cbdata->nlocks;
3968 if (shared) ++cbdata->slocks;
3969
3970 for (int i = 0; i < cbdata->used; ++i) {
3971 const buffer * const token = &cbdata->tokens[i];
3972 /* locktoken match (locktoken not '\0' terminated) */
3973 if (buffer_eq_slen(token, BUF_PTR_LEN(locktoken))) {
3974 /*(0 length owner if no auth required to lock; not recommended)*/
3975 if (buffer_is_blank(lockdata->owner)/*no lock owner;match*/
3976 || buffer_eq_slen(cbdata->authn_user,
3977 BUF_PTR_LEN(lockdata->owner))) {
3978 if (shared) ++cbdata->smatch;
3979 return; /* authenticated lock owner match */
3980 }
3981 }
3982 }
3983
3984 /* no match with lock tokens in request */
3985 if (!shared) {
3986 webdav_xml_href(cbdata->b, &lockdata->lockroot);
3987 webdav_double_buffer(cbdata->r, cbdata->b);
3988 }
3989 }
3990
3991
3992 /**
3993 * check if request provides necessary locks to access the resource
3994 */
3995 static int
webdav_has_lock(request_st * const r,const plugin_config * const pconf,const buffer * const uri)3996 webdav_has_lock (request_st * const r,
3997 const plugin_config * const pconf,
3998 const buffer * const uri)
3999 {
4000 /* Note with regard to exclusive locks on collections: client should not be
4001 * able to obtain an exclusive lock on a collection if there are existing
4002 * locks on resource members inside the collection. Therefore, there is no
4003 * need to check here for locks on resource members inside collections.
4004 * (This ignores the possibility that an admin or some other privileged
4005 * or out-of-band process has added locks in spite of lock on collection.)
4006 * Revisit to properly support shared locks. */
4007
4008 struct webdav_lock_token_submitted_st cbdata;
4009 cbdata.b = chunk_buffer_acquire();
4010 cbdata.r = r;
4011 cbdata.tokens = NULL;
4012 cbdata.used = 0;
4013 cbdata.nlocks = 0;
4014 cbdata.slocks = 0;
4015 cbdata.smatch = 0;
4016
4017 /* XXX: maybe add config switch to require that authentication occurred? */
4018 buffer owner = { NULL, 0, 0 };/*owner (not authenticated)(auth_user unset)*/
4019 const data_string * const authn_user = (const data_string *)
4020 array_get_element_klen(&r->env, CONST_STR_LEN("REMOTE_USER"));
4021 cbdata.authn_user = authn_user ? &authn_user->value : &owner;
4022
4023 const buffer * const h =
4024 http_header_request_get(r, HTTP_HEADER_OTHER, CONST_STR_LEN("If"));
4025
4026 if (h) {
4027 /* parse "If" request header for submitted lock tokens
4028 * While the below not a pedantic, validating parse, if the header
4029 * is non-conformant or contains unencoded characters, the result
4030 * will be misidentified or ignored lock tokens, which will result
4031 * in fail closed -- secure default behavior -- if those lock
4032 * tokens are required. It is highly unlikely that misparsing the "If"
4033 * request header will result in a valid lock token since lock tokens
4034 * should be unique, and opaquelocktoken should be globally unique */
4035 char *p = h->ptr;
4036 do {
4037 #if 0
4038 while (*p == ' ' || *p == '\t') ++p;
4039 if (*p == '<') { /* Resource-Tag */
4040 do { ++p; } while (*p != '>' && *p != '\0');
4041 if (*p == '\0') break;
4042 do { ++p; } while (*p == ' ' || *p == '\t');
4043 }
4044 #endif
4045
4046 while (*p != '(' && *p != '\0') ++p;
4047
4048 /* begin List in No-tag-list or Tagged-list
4049 * List = "(" 1*Condition ")"
4050 * Condition = ["Not"] (State-token | "[" entity-tag "]")
4051 */
4052 int notflag = 0;
4053 while (*p != '\0' && *++p != ')') {
4054 while (*p == ' ' || *p == '\t') ++p;
4055 if ( (p[0] & 0xdf) == 'N'
4056 && (p[1] & 0xdf) == 'O'
4057 && (p[2] & 0xdf) == 'T') {
4058 notflag = 1;
4059 p += 3;
4060 while (*p == ' ' || *p == '\t') ++p;
4061 }
4062 if (*p != '<') { /* '<' begins State-token (Coded-URL) */
4063 if (*p != '[') break; /* invalid syntax */
4064 /* '[' and ']' wrap entity-tag */
4065 char *etag = p+1;
4066 do { ++p; } while (*p != ']' && *p != '\0');
4067 if (*p != ']') break; /* invalid syntax */
4068 if (p == etag) continue; /* ignore entity-tag if empty */
4069 if (notflag) continue;/* ignore entity-tag in NOT context */
4070 if (0 == r->conf.etag_flags) continue; /*ignore entity-tag*/
4071 struct stat st;
4072 if (0 != lstat(r->physical.path.ptr, &st)) {
4073 http_status_set_error(r, 412); /* Precondition Failed */
4074 chunk_buffer_release(cbdata.b);
4075 return 0;
4076 }
4077 if (S_ISDIR(st.st_mode)) continue;/*we ignore etag if dir*/
4078 buffer * const etagb = r->tmp_buf;
4079 http_etag_create(etagb, &st, r->conf.etag_flags);
4080 *p = '\0';
4081 int ematch = http_etag_matches(etagb, etag, 0);
4082 *p = ']';
4083 if (!ematch) {
4084 http_status_set_error(r, 412); /* Precondition Failed */
4085 chunk_buffer_release(cbdata.b);
4086 return 0;
4087 }
4088 continue;
4089 }
4090
4091 if (p[1] == 'D'
4092 && 0 == strncmp(p, "<DAV:no-lock>",
4093 sizeof("<DAV:no-lock>")-1)) {
4094 if (0 == notflag) {
4095 http_status_set_error(r, 412); /* Precondition Failed */
4096 chunk_buffer_release(cbdata.b);
4097 return 0;
4098 }
4099 p += sizeof("<DAV:no-lock>")-2; /* point p to '>' */
4100 continue;
4101 }
4102
4103 /* State-token (Coded-URL)
4104 * Coded-URL = "<" absolute-URI ">"
4105 * ; No linear whitespace (LWS) allowed in Coded-URL
4106 * ; absolute-URI defined in RFC 3986, Section 4.3
4107 */
4108 if (!(cbdata.used & (16-1))) {
4109 if (cbdata.used == 16) { /* arbitrary limit */
4110 http_status_set_error(r, 400); /* Bad Request */
4111 chunk_buffer_release(cbdata.b);
4112 return 0;
4113 }
4114 ck_realloc_u32((void **)&cbdata.tokens, (uint32_t)cbdata.used,
4115 16, sizeof(*cbdata.tokens));
4116 }
4117 cbdata.tokens[cbdata.used].ptr = p+1;
4118
4119 do { ++p; } while (*p != '>' && *p != '\0');
4120 if (*p == '\0') break; /* (*p != '>') */
4121
4122 cbdata.tokens[cbdata.used].used =
4123 (uint32_t)(p - cbdata.tokens[cbdata.used].ptr + 1);
4124 ++cbdata.used;
4125 }
4126 } while (*p++ == ')'); /* end of List in No-tag-list or Tagged-list */
4127 }
4128
4129 webdav_lock_activelocks(pconf, uri, 1,
4130 webdav_lock_token_submitted_cb, &cbdata);
4131
4132 if (NULL != cbdata.tokens)
4133 free(cbdata.tokens);
4134
4135 int has_lock = 1;
4136
4137 if (0 != cbdata.b->used || !chunkqueue_is_empty(&r->write_queue))
4138 has_lock = 0;
4139 else if (0 == cbdata.nlocks) { /* resource is not locked at all */
4140 /* error if lock provided on source and no locks present on source;
4141 * not error if no locks on Destination, but "If" provided for source */
4142 if (cbdata.used && uri == &r->physical.rel_path) {
4143 has_lock = -1;
4144 http_status_set_error(r, 412); /* Precondition Failed */
4145 }
4146 #if 0 /*(treat no locks as if caller is holding an appropriate lock)*/
4147 else {
4148 has_lock = 0;
4149 webdav_xml_href(cbdata.b, uri);
4150 }
4151 #endif
4152 }
4153
4154 /*(XXX: overly simplistic shared lock matching allows any match of shared
4155 * locks even when there are shared locks on multiple different lockroots.
4156 * Failure is misreported since unmatched shared locks are not added to
4157 * cbdata.b) */
4158 if (cbdata.slocks && !cbdata.smatch)
4159 has_lock = 0;
4160
4161 if (!has_lock)
4162 webdav_xml_doc_error_lock_token_submitted(r, cbdata.b);
4163
4164 chunk_buffer_release(cbdata.b);
4165
4166 return (has_lock > 0);
4167 }
4168
4169 #else /* ! defined(USE_LOCKS) */
4170
4171 #define webdav_has_lock(r, pconf, uri) 1
4172
4173 #endif /* ! defined(USE_LOCKS) */
4174
4175
4176 static handler_t
mod_webdav_propfind(request_st * const r,const plugin_config * const pconf)4177 mod_webdav_propfind (request_st * const r, const plugin_config * const pconf)
4178 {
4179 if (r->reqbody_length) {
4180 #ifdef USE_PROPPATCH
4181 if (r->state == CON_STATE_READ_POST) {
4182 handler_t rc = r->con->reqbody_read(r);
4183 if (rc != HANDLER_GO_ON) return rc;
4184 }
4185 if (!webdav_reqbody_type_xml(r)) {
4186 http_status_set_error(r, 415); /* Unsupported Media Type */
4187 return HANDLER_FINISHED;
4188 }
4189 #else
4190 /* PROPFIND is idempotent and safe, so even if parsing XML input is not
4191 * supported, live properties can still be produced, so treat as allprop
4192 * request. NOTE: this behavior is NOT RFC CONFORMANT (and, well, if
4193 * compiled without XML support, this WebDAV implementation is already
4194 * non-compliant since it is missing support for XML request body).
4195 * RFC-compliant behavior would reject an ignored request body with
4196 * 415 Unsupported Media Type */
4197 #if 0
4198 http_status_set_error(r, 415); /* Unsupported Media Type */
4199 return HANDLER_FINISHED;
4200 #endif
4201 #endif
4202 }
4203
4204 webdav_propfind_bufs pb;
4205
4206 /* [RFC4918] 9.1 PROPFIND Method
4207 * Servers MUST support "0" and "1" depth requests on WebDAV-compliant
4208 * resources and SHOULD support "infinity" requests. In practice, support
4209 * for infinite-depth requests MAY be disabled, due to the performance and
4210 * security concerns associated with this behavior. Servers SHOULD treat
4211 * a request without a Depth header as if a "Depth: infinity" header was
4212 * included.
4213 */
4214 pb.allprop = 0;
4215 pb.propname = 0;
4216 pb.lockdiscovery= 0;
4217 pb.recursed = 0;
4218 pb.depth = webdav_parse_Depth(r);
4219
4220 if (-1 == pb.depth && !(pconf->opts & MOD_WEBDAV_PROPFIND_DEPTH_INFINITY)) {
4221 webdav_xml_doc_error_propfind_finite_depth(r);
4222 return HANDLER_FINISHED;
4223 }
4224
4225 pb.atflags =
4226 ((pconf->opts & MOD_WEBDAV_UNSAFE_PROPFIND_FOLLOW_SYMLINK)
4227 && pconf->is_readonly)
4228 ? 0 /* non-standard */
4229 : AT_SYMLINK_NOFOLLOW; /* WebDAV does not have symlink concept */
4230
4231 if (pb.atflags == AT_SYMLINK_NOFOLLOW
4232 ? 0 != lstat(r->physical.path.ptr, &pb.st)
4233 : 0 != stat(r->physical.path.ptr, &pb.st)) { /* non-standard */
4234 http_status_set_error(r, (errno == ENOENT) ? 404 : 403);
4235 return HANDLER_FINISHED;
4236 }
4237 else if (S_ISDIR(pb.st.st_mode)) {
4238 if (!buffer_has_pathsep_suffix(&r->physical.path)) {
4239 /* set "Content-Location" instead of sending 308 redirect to dir */
4240 if (0 != http_response_redirect_to_directory(r, 0))
4241 return HANDLER_FINISHED;
4242 buffer_append_char(&r->physical.path, '/');
4243 buffer_append_char(&r->physical.rel_path, '/');
4244 }
4245 }
4246 else if (buffer_has_pathsep_suffix(&r->physical.path)) {
4247 http_status_set_error(r, 403);
4248 return HANDLER_FINISHED;
4249 }
4250 else {
4251 pb.depth = 0;
4252 }
4253
4254 pb.proplist.ptr = NULL;
4255 pb.proplist.used = 0;
4256
4257 #ifdef USE_PROPPATCH
4258 xmlDocPtr xml = NULL;
4259 const xmlNode *rootnode = NULL;
4260 if (r->reqbody_length) {
4261 if (NULL == (xml = webdav_parse_chunkqueue(r, pconf))) {
4262 http_status_set_error(r, 400); /* Bad Request */
4263 return HANDLER_FINISHED;
4264 }
4265 rootnode = xmlDocGetRootElement(xml);
4266 }
4267
4268 if (NULL != rootnode
4269 && 0 == webdav_xmlstrcmp_fixed(rootnode->name, "propfind")) {
4270 for (const xmlNode *cmd = rootnode->children; cmd; cmd = cmd->next) {
4271 if (0 == webdav_xmlstrcmp_fixed(cmd->name, "allprop"))
4272 pb.allprop = pb.lockdiscovery = 1;
4273 else if (0 == webdav_xmlstrcmp_fixed(cmd->name, "propname"))
4274 pb.propname = 1;
4275 else if (0 != webdav_xmlstrcmp_fixed(cmd->name, "prop")
4276 && 0 != webdav_xmlstrcmp_fixed(cmd->name, "include"))
4277 continue;
4278
4279 /* "prop" or "include": get prop by name */
4280 for (const xmlNode *prop = cmd->children; prop; prop = prop->next) {
4281 if (prop->type == XML_TEXT_NODE)
4282 continue; /* ignore WS */
4283
4284 if (prop->ns && '\0' == *(char *)prop->ns->href
4285 && '\0' != *(char *)prop->ns->prefix) {
4286 log_error(r->conf.errh, __FILE__, __LINE__,
4287 "no name space for: %s", prop->name);
4288 /* 422 Unprocessable Entity */
4289 http_status_set_error(r, 422);
4290 free(pb.proplist.ptr);
4291 xmlFreeDoc(xml);
4292 return HANDLER_FINISHED;
4293 }
4294
4295 /* add property to requested list */
4296 if (!(pb.proplist.used & (32-1))) {
4297 if (pb.proplist.used == 32) {
4298 /* arbitrarily chosen limit of 32 */
4299 log_error(r->conf.errh, __FILE__, __LINE__,
4300 "too many properties in request (> 32)");
4301 http_status_set_error(r, 400); /* Bad Request */
4302 free(pb.proplist.ptr);
4303 xmlFreeDoc(xml);
4304 return HANDLER_FINISHED;
4305 }
4306 ck_realloc_u32((void **)&pb.proplist.ptr,
4307 (uint32_t)pb.proplist.used,
4308 32, sizeof(*pb.proplist.ptr));
4309 }
4310
4311 const size_t namelen = strlen((char *)prop->name);
4312 if (prop->ns && 0 == strcmp((char *)prop->ns->href, "DAV:")) {
4313 if (namelen == sizeof("lockdiscovery")-1
4314 && 0 == memcmp(prop->name,
4315 CONST_STR_LEN("lockdiscovery"))) {
4316 pb.lockdiscovery = 1;
4317 continue;
4318 }
4319 const struct live_prop_list *list = live_properties;
4320 while (0 != list->len
4321 && (list->len != namelen
4322 || 0 != memcmp(prop->name,list->prop,list->len)))
4323 ++list;
4324 if (NULL != list->prop) {
4325 if (cmd->name[0] == 'p') { /* "prop", not "include" */
4326 pb.proplist.ptr[pb.proplist.used].ns = "";
4327 pb.proplist.ptr[pb.proplist.used].nslen = 0;
4328 pb.proplist.ptr[pb.proplist.used].name = NULL;
4329 pb.proplist.ptr[pb.proplist.used].namelen =
4330 list->pnum;
4331 pb.proplist.used++;
4332 } /* (else skip; will already be part of allprop) */
4333 continue;
4334 }
4335 if (cmd->name[0] == 'i') /* allprop "include", not "prop" */
4336 continue; /*(all props in db returned with allprop)*/
4337 /* dead props or props in "DAV:" ns not handed above */
4338 }
4339
4340 /* save pointers directly into parsed xmlDoc
4341 * Therefore, MUST NOT call xmlFreeDoc(xml)
4342 * until also done with pb.proplist */
4343 webdav_property_name * const propname =
4344 pb.proplist.ptr + pb.proplist.used++;
4345 if (prop->ns) {
4346 propname->ns = (char *)prop->ns->href;
4347 propname->nslen = strlen(propname->ns);
4348 }
4349 else {
4350 propname->ns = "";
4351 propname->nslen = 0;
4352 }
4353 propname->name = (char *)prop->name;
4354 propname->namelen = namelen;
4355 }
4356 }
4357 }
4358 #endif
4359
4360 if (NULL == pb.proplist.ptr && !pb.propname)
4361 pb.allprop = pb.lockdiscovery = 1;
4362
4363 pb.r = r;
4364 pb.pconf = pconf;
4365 pb.dst = &r->physical;
4366 pb.b = chunk_buffer_acquire();
4367 pb.b_200 = chunk_buffer_acquire();
4368 pb.b_404 = chunk_buffer_acquire();
4369 /*(optimization; buf extended as needed)*/
4370 chunk_buffer_prepare_append(pb.b, 8192);
4371
4372 webdav_xml_doctype(pb.b, r);
4373 buffer_append_string_len(pb.b, CONST_STR_LEN(
4374 "<D:multistatus xmlns:D=\"DAV:\" " MOD_WEBDAV_XMLNS_NS0 ">\n"));
4375
4376 if (0 != pb.depth) /*(must be collection or else error returned above)*/
4377 webdav_propfind_dir(&pb);
4378 else
4379 webdav_propfind_resource(&pb);
4380
4381 buffer_append_string_len(pb.b, CONST_STR_LEN(
4382 "</D:multistatus>\n"));
4383
4384 http_chunk_append_buffer(r, pb.b); /*(might move/steal/reset buffer)*/
4385 chunk_buffer_release(pb.b);
4386 http_status_set_fin(r, 207); /* Multi-status */
4387
4388 chunk_buffer_release(pb.b_404);
4389 chunk_buffer_release(pb.b_200);
4390 #ifdef USE_PROPPATCH
4391 if (pb.proplist.ptr)
4392 free(pb.proplist.ptr);
4393 if (NULL != xml)
4394 xmlFreeDoc(xml);
4395 #endif
4396
4397 if (pconf->log_xml)
4398 webdav_xml_log_response(r);
4399
4400 return HANDLER_FINISHED;
4401 }
4402
4403
4404 static handler_t
mod_webdav_mkcol(request_st * const r,const plugin_config * const pconf)4405 mod_webdav_mkcol (request_st * const r, const plugin_config * const pconf)
4406 {
4407 const int status = webdav_mkdir(pconf, &r->physical, -1);
4408 if (0 == status)
4409 http_status_set_fin(r, 201); /* Created */
4410 else
4411 http_status_set_error(r, status);
4412
4413 return HANDLER_FINISHED;
4414 }
4415
4416
4417 static handler_t
mod_webdav_delete(request_st * const r,const plugin_config * const pconf)4418 mod_webdav_delete (request_st * const r, const plugin_config * const pconf)
4419 {
4420 /* reject DELETE if original URI sent with fragment ('litmus' warning) */
4421 if (NULL != strchr(r->target_orig.ptr, '#')) {
4422 http_status_set_error(r, 403);
4423 return HANDLER_FINISHED;
4424 }
4425
4426 struct stat st;
4427 if (-1 == lstat(r->physical.path.ptr, &st)) {
4428 http_status_set_error(r, (errno == ENOENT) ? 404 : 403);
4429 return HANDLER_FINISHED;
4430 }
4431
4432 if (0 != webdav_if_match_or_unmodified_since(r, &st)) {
4433 http_status_set_error(r, 412); /* Precondition Failed */
4434 return HANDLER_FINISHED;
4435 }
4436
4437 if (S_ISDIR(st.st_mode)) {
4438 if (!buffer_has_pathsep_suffix(&r->physical.path)) {
4439 #if 0 /*(issues warning for /usr/bin/litmus copymove test)*/
4440 http_response_redirect_to_directory(r, 308);
4441 return HANDLER_FINISHED; /* 308 Permanent Redirect */
4442 /* Alternatively, could append '/' to r->physical.path
4443 * and r->physical.rel_path, set Content-Location in
4444 * response headers, and continue to serve the request */
4445 #else
4446 buffer_append_char(&r->physical.path, '/');
4447 buffer_append_char(&r->physical.rel_path, '/');
4448 #if 0 /*(Content-Location not very useful to client after DELETE)*/
4449 /*(? should it be target or target_orig ?)*/
4450 /*(should be url-encoded path)*/
4451 buffer_append_char(&r->target, '/');
4452 http_header_response_set(r, HTTP_HEADER_CONTENT_LOCATION,
4453 CONST_STR_LEN("Content-Location"),
4454 BUF_PTR_LEN(&r->target));
4455 #endif
4456 #endif
4457 }
4458 /* require "infinity" if Depth request header provided */
4459 if (-1 != webdav_parse_Depth(r)) {
4460 /* [RFC4918] 9.6.1 DELETE for Collections
4461 * The DELETE method on a collection MUST act as if a
4462 * "Depth: infinity" header was used on it. A client MUST NOT
4463 * submit a Depth header with a DELETE on a collection with any
4464 * value but infinity.
4465 */
4466 http_status_set_error(r, 400); /* Bad Request */
4467 return HANDLER_FINISHED;
4468 }
4469
4470 const int flags = (r->conf.force_lowercase_filenames)
4471 ? WEBDAV_FLAG_LC_NAMES
4472 : 0;
4473 if (0 == webdav_delete_dir(pconf, &r->physical, r, flags)) {
4474 /* Note: this does not destroy locks if an error occurs,
4475 * which is not a problem if lock is only on the collection
4476 * being moved, but might need finer updates if there are
4477 * locks on internal elements that are successfully deleted */
4478 webdav_lock_delete_uri_col(pconf, &r->physical.rel_path);
4479 http_status_set_fin(r, 204); /* No Content */
4480 }
4481 else {
4482 webdav_xml_doc_multistatus(r, pconf); /* 207 Multi-status */
4483 }
4484
4485 /* invalidate stat cache of src if DELETE, whether or not successful */
4486 stat_cache_delete_dir(BUF_PTR_LEN(&r->physical.path));
4487 }
4488 else if (buffer_has_pathsep_suffix(&r->physical.path))
4489 http_status_set_error(r, 403);
4490 else {
4491 const int status = webdav_delete_file(pconf, &r->physical);
4492 if (0 == status) {
4493 webdav_lock_delete_uri(pconf, &r->physical.rel_path);
4494 http_status_set_fin(r, 204); /* No Content */
4495 }
4496 else
4497 http_status_set_error(r, status);
4498 }
4499
4500 return HANDLER_FINISHED;
4501 }
4502
4503
4504 __attribute_noinline__
4505 static int
mod_webdav_write_cq(request_st * const r,chunkqueue * const cq,const int fd)4506 mod_webdav_write_cq (request_st * const r, chunkqueue * const cq, const int fd)
4507 {
4508 /* (Note: copying might take some time, temporarily pausing server) */
4509 while (!chunkqueue_is_empty(cq)) {
4510 ssize_t wr = chunkqueue_write_chunk(fd, cq, r->conf.errh);
4511 if (__builtin_expect( (wr > 0), 1))
4512 chunkqueue_mark_written(cq, wr);
4513 else if (wr < 0) {
4514 http_status_set_error(r, (errno == ENOSPC) ? 507 : 403);
4515 return 0;
4516 }
4517 else /*(wr == 0)*/
4518 chunkqueue_remove_finished_chunks(cq);
4519 }
4520 return 1;
4521 }
4522
4523
4524 #if (defined(__linux__) || defined(__CYGWIN__)) && defined(O_TMPFILE)
4525 static int
mod_webdav_write_single_file_chunk(request_st * const r,chunkqueue * const cq)4526 mod_webdav_write_single_file_chunk (request_st * const r, chunkqueue * const cq)
4527 {
4528 /* cq might have mem chunks after initial tempfile chunk
4529 * due to chunkqueue_steal() if request body is small */
4530 /*assert(cq->first->type == FILE_CHUNK);*/
4531 /*assert(cq->first->next != NULL);*/
4532 chunk * const c = cq->first;
4533 cq->first = c->next;
4534 const off_t len = chunkqueue_length(cq);
4535 const off_t bytes_out = cq->bytes_out;
4536 if (mod_webdav_write_cq(r, cq, c->file.fd)) {
4537 /*assert(cq->first == NULL);*/
4538 /* chunks merged; chunkqueue length did not change,
4539 * so restore cq->bytes_out instead of chunkqueue_file_update() */
4540 cq->bytes_out = bytes_out;
4541 c->file.length = len;
4542 c->next = NULL;
4543 cq->first = cq->last = c;
4544 return 1;
4545 }
4546 else {
4547 /*assert(cq->first != NULL);*/
4548 c->next = cq->first;
4549 cq->first = c;
4550 return 0;
4551 }
4552 }
4553 #endif
4554
4555
4556 static handler_t
mod_webdav_put_0(request_st * const r,const plugin_config * const pconf)4557 mod_webdav_put_0 (request_st * const r, const plugin_config * const pconf)
4558 {
4559 if (0 != webdav_if_match_or_unmodified_since(r, NULL)) {
4560 http_status_set_error(r, 412); /* Precondition Failed */
4561 return HANDLER_FINISHED;
4562 }
4563
4564 /* special-case PUT 0-length file */
4565 int fd;
4566 fd = fdevent_open_cloexec(r->physical.path.ptr, 0,
4567 O_WRONLY | O_CREAT | O_EXCL | O_TRUNC,
4568 WEBDAV_FILE_MODE);
4569 if (fd >= 0) {
4570 if (0 != r->conf.etag_flags) {
4571 /*(skip sending etag if fstat() error; not expected)*/
4572 struct stat st;
4573 if (0 == fstat(fd, &st)) webdav_response_etag(r, &st);
4574 }
4575 close(fd);
4576 webdav_parent_modified(&r->physical.path);
4577 http_status_set_fin(r, 201); /* Created */
4578 return HANDLER_FINISHED;
4579 }
4580 else if (errno == EISDIR) {
4581 http_status_set_error(r, 405); /* Method Not Allowed */
4582 return HANDLER_FINISHED;
4583 }
4584
4585 if (errno == ELOOP)
4586 webdav_delete_file(pconf, &r->physical); /*(ignore result)*/
4587 /*(attempt unlink(); target might be symlink
4588 * and above O_NOFOLLOW resulted in ELOOP)*/
4589
4590 fd = fdevent_open_cloexec(r->physical.path.ptr, 0,
4591 O_WRONLY | O_CREAT | O_TRUNC,
4592 WEBDAV_FILE_MODE);
4593 if (fd >= 0) {
4594 close(fd);
4595 http_status_set_fin(r, 204); /* No Content */
4596 return HANDLER_FINISHED;
4597 }
4598
4599 http_status_set_error(r, 500); /* Internal Server Error */
4600 return HANDLER_FINISHED;
4601 }
4602
4603
4604 static handler_t
mod_webdav_put_prep(request_st * const r,const plugin_config * const pconf)4605 mod_webdav_put_prep (request_st * const r, const plugin_config * const pconf)
4606 {
4607 if (buffer_has_pathsep_suffix(&r->physical.path)) {
4608 /* disallow PUT on a collection (path ends in '/') */
4609 http_status_set_error(r, 400); /* Bad Request */
4610 return HANDLER_FINISHED;
4611 }
4612
4613 if (light_btst(r->rqst_htags, HTTP_HEADER_CONTENT_RANGE)) {
4614 if (pconf->opts & (MOD_WEBDAV_UNSAFE_PARTIAL_PUT_COMPAT
4615 |MOD_WEBDAV_CPYTMP_PARTIAL_PUT))
4616 return HANDLER_GO_ON;
4617 /* [RFC7231] 4.3.4 PUT
4618 * An origin server that allows PUT on a given target resource MUST
4619 * send a 400 (Bad Request) response to a PUT request that contains a
4620 * Content-Range header field (Section 4.2 of [RFC7233]), since the
4621 * payload is likely to be partial content that has been mistakenly
4622 * PUT as a full representation.
4623 */
4624 http_status_set_error(r, 400); /* Bad Request */
4625 return HANDLER_FINISHED;
4626 }
4627
4628 /* special-case PUT 0-length file */
4629 if (0 == r->reqbody_length)
4630 return mod_webdav_put_0(r, pconf);
4631
4632 /* Create temporary file in target directory (to store reqbody as received)
4633 * Temporary file is unlinked so that if receiving reqbody fails,
4634 * temp file is automatically cleaned up when fd is closed.
4635 * While being received, temporary file is not part of directory listings.
4636 * While this might result in extra copying, it is simple and robust. */
4637 int fd;
4638 size_t len = buffer_clen(&r->physical.path);
4639 #if (defined(__linux__) || defined(__CYGWIN__)) && defined(O_TMPFILE)
4640 char *slash = memrchr(r->physical.path.ptr, '/', len);
4641 if (slash == r->physical.path.ptr) slash = NULL;
4642 if (slash) *slash = '\0';
4643 fd = fdevent_open_cloexec(r->physical.path.ptr, 1,
4644 O_RDWR | O_TMPFILE | O_APPEND, WEBDAV_FILE_MODE);
4645 if (slash) *slash = '/';
4646 if (fd < 0)
4647 #endif
4648 {
4649 buffer_append_string_len(&r->physical.path, CONST_STR_LEN("-XXXXXX"));
4650 fd = fdevent_mkostemp(r->physical.path.ptr, 0);
4651 if (fd >= 0) unlink(r->physical.path.ptr);
4652 buffer_truncate(&r->physical.path, len);
4653 }
4654 if (fd < 0) {
4655 switch (errno) {
4656 case ENOENT: /* parent collection does not exist */
4657 case ENOTDIR:
4658 http_status_set_error(r, 409); /* Conflict */
4659 break;
4660 default:
4661 http_status_set_error(r, 500); /* Internal Server Error */
4662 break;
4663 }
4664 return HANDLER_FINISHED;
4665 }
4666
4667 /* copy all chunks even though expecting (at most) single MEM_CHUNK chunk
4668 * (still, loop on partial writes)
4669 * (Note: copying might take some time, temporarily pausing server)
4670 * (error status is set if error occurs) */
4671 chunkqueue * const cq = &r->reqbody_queue;
4672 off_t cqlen = chunkqueue_length(cq);
4673 if (!mod_webdav_write_cq(r, cq, fd)) {
4674 close(fd);
4675 return HANDLER_FINISHED;
4676 }
4677
4678 chunkqueue_reset(cq);
4679 if (0 != cqlen) /*(r->physical.path copied, then c->mem cleared below)*/
4680 chunkqueue_append_file_fd(cq, &r->physical.path, fd, 0, cqlen);
4681 else {
4682 /*(must be non-zero for fd to be appended, then reset to 0-length)*/
4683 chunkqueue_append_file_fd(cq, &r->physical.path, fd, 0, 1);
4684 cq->last->file.length = 0;
4685 cq->bytes_in = 0;
4686 }
4687 #ifdef __COVERITY__
4688 /* chunkqueue_append_file_fd() does not update cq->last when 0 == cqlen,
4689 * and that is handled above, so cq->last is never NULL here */
4690 force_assert(cq->last);
4691 #endif
4692 buffer_clear(cq->last->mem); /* file already unlink()ed */
4693 cq->upload_temp_file_size = (off_t)((1uLL << (sizeof(off_t)*8-1))-1);
4694 cq->last->file.is_temp = 1;
4695
4696 return HANDLER_GO_ON;
4697 }
4698
4699
4700 #if (defined(__linux__) || defined(__CYGWIN__)) && defined(O_TMPFILE)
4701 static int
mod_webdav_put_linkat_rename(request_st * const r,const char * const pathtemp)4702 mod_webdav_put_linkat_rename (request_st * const r,
4703 const char * const pathtemp)
4704 {
4705 if (!has_proc_self_fd) return 0;
4706 chunkqueue * const cq = &r->reqbody_queue;
4707 chunk *c = cq->first;
4708
4709 char pathproc[32] = "/proc/self/fd/";
4710 size_t plen =
4711 li_itostrn(pathproc+sizeof("/proc/self/fd/")-1,
4712 sizeof(pathproc)-(sizeof("/proc/self/fd/")-1), c->file.fd);
4713 pathproc[sizeof("/proc/self/fd/")-1+plen] = '\0';
4714 if (0 == linkat(AT_FDCWD, pathproc, AT_FDCWD, pathtemp, AT_SYMLINK_FOLLOW)){
4715 struct stat st;
4716 #ifdef HAVE_RENAMEAT2
4717 if (0 == renameat2(AT_FDCWD, pathtemp,
4718 AT_FDCWD, r->physical.path.ptr, RENAME_NOREPLACE))
4719 http_status_set_fin(r, 201); /* Created */
4720 else if (0 == rename(pathtemp, r->physical.path.ptr))
4721 http_status_set_fin(r, 204); /* No Content */ /*(replaced)*/
4722 else
4723 #else
4724 http_status_set_fin(r, 0 == lstat(r->physical.path.ptr, &st)
4725 ? 204 /* No Content */
4726 : 201); /* Created */
4727 if (201 == http_status_get(r))
4728 webdav_parent_modified(&r->physical.path);
4729 if (0 != rename(pathtemp, r->physical.path.ptr))
4730 #endif
4731 {
4732 if (errno == EISDIR)
4733 http_status_set_error(r, 405); /* Method Not Allowed */
4734 else
4735 http_status_set_error(r, 403); /* Forbidden */
4736 unlink(pathtemp);
4737 }
4738
4739 if (0 != r->conf.etag_flags
4740 && http_status_get(r) < 300) { /*(201, 204)*/
4741 /*(skip sending etag if fstat() error; not expected)*/
4742 if (0 == fstat(c->file.fd, &st))
4743 webdav_response_etag(r, &st);
4744 }
4745
4746 chunkqueue_mark_written(cq, c->file.length); /*(c->offset == 0)*/
4747 return 1;
4748 }
4749
4750 return 0;
4751 }
4752 #endif
4753
4754
4755 static handler_t
mod_webdav_put_range(request_st * const r,const buffer * const h,const plugin_config * const pconf)4756 mod_webdav_put_range (request_st * const r, const buffer * const h,
4757 const plugin_config * const pconf)
4758 {
4759 /* historical code performed very limited range parse (repeated here) */
4760 /* we only support <num>- ... */
4761 const char *num = h->ptr;
4762 off_t offset;
4763 char *err;
4764 if (0 != strncmp(num, "bytes ", sizeof("bytes ")-1)) {
4765 http_status_set_error(r, 501); /* Not Implemented */
4766 return HANDLER_FINISHED;
4767 }
4768 num += sizeof("bytes ")-1; /* +6 for "bytes " */
4769 offset = strtoll(num, &err, 10); /*(strtoll() ignores leading whitespace)*/
4770 if (num == err || *err != '-' || offset < 0) {
4771 http_status_set_error(r, 501); /* Not Implemented */
4772 return HANDLER_FINISHED;
4773 }
4774
4775 const int ifd = fdevent_open_cloexec(r->physical.path.ptr, 0,
4776 O_WRONLY, WEBDAV_FILE_MODE);
4777 if (ifd < 0) {
4778 http_status_set_error(r, (errno == ENOENT) ? 404 : 403);
4779 return HANDLER_FINISHED;
4780 }
4781 int fd = ifd;
4782 struct stat st;
4783
4784 if ((pconf->opts & MOD_WEBDAV_CPYTMP_PARTIAL_PUT)
4785 && (!(pconf->opts & MOD_WEBDAV_UNSAFE_PARTIAL_PUT_COMPAT)
4786 || 0 != fstat(ifd,&st) || st.st_size != offset || st.st_nlink > 1)){
4787 /* open tmpfile and copy source for modify and rename (below this block)
4788 * if cpytmp mode enabled and if unsafe partial put compat not enabled
4789 * or if not appending or if nlink == 1 (modifying in-place is safe when
4790 * appending and nlink == 1 since lighttpd is single-threaded) */
4791 /* future: might rework for reuse since src is already open here in ifd,
4792 * and might be opened again (and closed!) in webdav_copytmp_rename() */
4793 fd = 0; /*(overloaded as input 'flags' to webdav_copytmp_rename())*/
4794 int rc = webdav_copytmp_rename(pconf, &r->physical, &r->physical, &fd);
4795 /*(fd may now be open to temporary file whose name is in pconf->tmpb
4796 * since we passed webdav_copytmp_rename() with (src == dst))*/
4797 if (0 != rc) {
4798 close(ifd);
4799 http_status_set_error(r, rc);
4800 return HANDLER_FINISHED;
4801 }
4802 if (-1 == fd) {
4803 /* open tmp file; file clone in webdav_copytmp_rename() did not */
4804 fd = fdevent_open_cloexec(pconf->tmpb->ptr, 0,
4805 O_WRONLY, WEBDAV_FILE_MODE);
4806 if (fd < 0) {
4807 close(ifd);
4808 unlink(pconf->tmpb->ptr);
4809 http_status_set_error(r, 403);
4810 return HANDLER_FINISHED;
4811 }
4812 }
4813 close(ifd); /*(close ifd after opening temporary file so (fd != ifd))*/
4814 }
4815
4816 #ifdef HAVE_COPY_FILE_RANGE
4817 /* use Linux copy_file_range() if available
4818 * (Linux 4.5, but glibc 2.27 provides a user-space emulation)
4819 * fd_in and fd_out must be on same mount (handled in mod_webdav_put_prep())
4820 * before Linux 5.3
4821 * check that reqbody is contained in single tempfile and open fd (expected)
4822 * (Note: copying might take some time, temporarily pausing server)
4823 */
4824 chunkqueue * const cq = &r->reqbody_queue;
4825 chunk *c = cq->first;
4826 off_t cqlen = chunkqueue_length(cq);
4827 if (c->type == FILE_CHUNK && NULL == c->next && c->file.fd >= 0) {
4828 loff_t zoff = 0;
4829 loff_t ooff = offset;
4830 ssize_t wr;
4831 do {
4832 #if defined(_LP64) || defined(__LP64__) || defined(_WIN64)
4833 wr = copy_file_range(c->file.fd,&zoff,fd,&ooff,(size_t)cqlen, 0);
4834 #else
4835 wr = copy_file_range(c->file.fd,&zoff,fd,&ooff,
4836 (size_t)(cqlen < INT32_MAX
4837 ? cqlen
4838 : (INT32_MAX & ~(131072-1))),
4839 0);
4840 #endif
4841 } while (wr > 0 && (cqlen -= wr));
4842 /*(ignore if c->file.fd truncated (wr == 0 && cqlen != 0); fail below)*/
4843 }
4844 off_t wrote = chunkqueue_length(cq) - cqlen;
4845 offset += wrote;
4846 chunkqueue_mark_written(cq, wrote);
4847 if (0 != cqlen) /* fallback, retry if copy_file_range() did not finish */
4848 #endif
4849 {
4850 if (-1 == lseek(fd, offset, SEEK_SET)) {
4851 close(fd);
4852 if (fd != ifd)
4853 unlink(pconf->tmpb->ptr);
4854 http_status_set_error(r, 500); /* Internal Server Error */
4855 return HANDLER_FINISHED;
4856 }
4857
4858 /* copy all chunks even though expecting single chunk
4859 * (still, loop on partial writes)
4860 * (Note: copying might take some time, temporarily pausing server)
4861 * (error status is set if error occurs) */
4862 mod_webdav_write_cq(r, &r->reqbody_queue, fd);
4863 }
4864
4865 if (fd != ifd) {
4866 #ifndef HAVE_RENAMEAT2
4867 if (0 == rename(pconf->tmpb->ptr, r->physical.path.ptr))
4868 #else
4869 if (0 == renameat2(AT_FDCWD, pconf->tmpb->ptr,
4870 AT_FDCWD, r->physical.path.ptr, 0))
4871 #endif
4872 {
4873 /* unconditional stat cache deletion */
4874 stat_cache_delete_entry(BUF_PTR_LEN(&r->physical.path));
4875 }
4876 else {
4877 switch (errno) {
4878 case ENOENT:
4879 case ENOTDIR:
4880 case EISDIR: http_status_set_error(r, 409); break; /* Conflict */
4881 default: http_status_set_error(r, 403); break; /* Forbidden */
4882 }
4883 unlink(pconf->tmpb->ptr);
4884 }
4885 }
4886
4887 if (0 != r->conf.etag_flags && !http_status_is_set(r)) {
4888 /*(skip sending etag if fstat() error; not expected)*/
4889 if (0 != fstat(fd, &st)) r->conf.etag_flags = 0;
4890 }
4891
4892 const int wc = close(fd);
4893 if (0 != wc && !http_status_is_set(r))
4894 http_status_set_error(r, (errno == ENOSPC) ? 507 : 403);
4895
4896 if (!http_status_is_set(r)) {
4897 http_status_set_fin(r, 204); /* No Content */
4898 if (0 != r->conf.etag_flags) webdav_response_etag(r, &st);
4899 }
4900
4901 return HANDLER_FINISHED;
4902 }
4903
4904
4905 static handler_t
mod_webdav_put(request_st * const r,const plugin_config * const pconf)4906 mod_webdav_put (request_st * const r, const plugin_config * const pconf)
4907 {
4908 if (r->state == CON_STATE_READ_POST) {
4909 int first_read = chunkqueue_is_empty(&r->reqbody_queue);
4910 handler_t rc = r->con->reqbody_read(r);
4911 if (rc != HANDLER_GO_ON) {
4912 if (first_read && rc == HANDLER_WAIT_FOR_EVENT
4913 && 0 != webdav_if_match_or_unmodified_since(r, NULL)) {
4914 http_status_set_error(r, 412); /* Precondition Failed */
4915 return HANDLER_FINISHED;
4916 }
4917 return rc;
4918 }
4919 }
4920
4921 if (0 != webdav_if_match_or_unmodified_since(r, NULL)) {
4922 http_status_set_error(r, 412); /* Precondition Failed */
4923 return HANDLER_FINISHED;
4924 }
4925
4926 if (pconf->opts & (MOD_WEBDAV_UNSAFE_PARTIAL_PUT_COMPAT
4927 |MOD_WEBDAV_CPYTMP_PARTIAL_PUT)) {
4928 const buffer * const h =
4929 http_header_request_get(r, HTTP_HEADER_CONTENT_RANGE,
4930 CONST_STR_LEN("Content-Range"));
4931 if (NULL != h)
4932 return mod_webdav_put_range(r, h, pconf);
4933 }
4934
4935 /* construct temporary filename in same directory as target
4936 * (expect cq contains exactly one chunk:
4937 * the temporary FILE_CHUNK created in mod_webdav_put_prep())
4938 * (do not reuse c->mem buffer; if tmpfile was unlinked, c->mem is blank)
4939 * (since temporary file was unlinked, no guarantee of unique name,
4940 * so add pid and fd to avoid conflict with an unlikely parallel
4941 * PUT request being handled by same server pid (presumably by
4942 * same client using same lock token)) */
4943 chunkqueue * const cq = &r->reqbody_queue;
4944 chunk *c = cq->first;
4945
4946 /* future: might support client specifying getcontenttype property
4947 * using Content-Type request header. However, [RFC4918] 9.7.1 notes:
4948 * Many servers do not allow configuring the Content-Type on a
4949 * per-resource basis in the first place. Thus, clients can't always
4950 * rely on the ability to directly influence the content type by
4951 * including a Content-Type request header
4952 */
4953
4954 /*(similar to beginning of webdav_linktmp_rename())*/
4955 buffer * const tmpb = pconf->tmpb;
4956 buffer_clear(tmpb);
4957 buffer_append_str2(tmpb, BUF_PTR_LEN(&r->physical.path),
4958 CONST_STR_LEN("."));
4959 buffer_append_int(tmpb, (long)getpid());
4960 buffer_append_char(tmpb, '.');
4961 if (c->type == MEM_CHUNK)
4962 buffer_append_uint_hex_lc(tmpb, (uintptr_t)pconf); /*(stack/heap addr)*/
4963 else
4964 buffer_append_int(tmpb, (long)c->file.fd);
4965 buffer_append_char(tmpb, '~');
4966
4967 if (buffer_clen(tmpb) >= PATH_MAX) { /*(temp file path too long)*/
4968 http_status_set_error(r, 500); /* Internal Server Error */
4969 return HANDLER_FINISHED;
4970 }
4971
4972 const char *pathtemp = tmpb->ptr;
4973
4974 #if (defined(__linux__) || defined(__CYGWIN__)) && defined(O_TMPFILE)
4975 if (c->type == FILE_CHUNK) { /*(reqbody contained in single tempfile)*/
4976 if (NULL != c->next) {
4977 /* if request body <= 64k, in-memory chunks might have been
4978 * moved to cq instead of appended to first chunk FILE_CHUNK */
4979 if (!mod_webdav_write_single_file_chunk(r, cq))
4980 return HANDLER_FINISHED;
4981 }
4982 if (mod_webdav_put_linkat_rename(r, pathtemp))
4983 return HANDLER_FINISHED;
4984 /* attempt traditional copy (below) if linkat() failed for any reason */
4985 }
4986 #endif
4987
4988 const int fd = fdevent_open_cloexec(pathtemp, 0,
4989 O_WRONLY | O_CREAT | O_EXCL | O_TRUNC,
4990 WEBDAV_FILE_MODE);
4991 if (fd < 0) {
4992 http_status_set_error(r, 500); /* Internal Server Error */
4993 return HANDLER_FINISHED;
4994 }
4995
4996 /* copy all chunks even though expecting single chunk
4997 * (still, loop on partial writes)
4998 * (Note: copying might take some time, temporarily pausing server)
4999 * (error status is set if error occurs) */
5000 mod_webdav_write_cq(r, cq, fd);
5001
5002 struct stat st;
5003 if (0 != r->conf.etag_flags && !http_status_is_set(r)) {
5004 /*(skip sending etag if fstat() error; not expected)*/
5005 if (0 != fstat(fd, &st)) r->conf.etag_flags = 0;
5006 }
5007
5008 const int wc = close(fd);
5009 if (0 != wc && !http_status_is_set(r))
5010 http_status_set_error(r, (errno == ENOSPC) ? 507 : 403);
5011
5012 if (!http_status_is_set(r)) {
5013 struct stat ste;
5014 http_status_set_fin(r, 0 == lstat(r->physical.path.ptr, &ste)
5015 ? 204 /* No Content */
5016 : 201); /* Created */
5017 if (201 == http_status_get(r))
5018 webdav_parent_modified(&r->physical.path);
5019 if (0 == rename(pathtemp, r->physical.path.ptr)) {
5020 if (0 != r->conf.etag_flags) webdav_response_etag(r, &st);
5021 }
5022 else {
5023 if (errno == EISDIR)
5024 http_status_set_error(r, 405); /* Method Not Allowed */
5025 else
5026 http_status_set_error(r, 500); /* Internal Server Error */
5027 unlink(pathtemp);
5028 }
5029 }
5030 else
5031 unlink(pathtemp);
5032
5033 return HANDLER_FINISHED;
5034 }
5035
5036
5037 static handler_t
mod_webdav_copymove_b(request_st * const r,const plugin_config * const pconf,physical_st * const dst)5038 mod_webdav_copymove_b (request_st * const r, const plugin_config * const pconf, physical_st * const dst)
5039 {
5040 buffer * const dst_path = &dst->path;
5041 buffer * const dst_rel_path = &dst->rel_path;
5042
5043 int flags = WEBDAV_FLAG_OVERWRITE /*(default)*/
5044 | (r->conf.force_lowercase_filenames
5045 ? WEBDAV_FLAG_LC_NAMES
5046 : 0)
5047 | (r->http_method == HTTP_METHOD_MOVE
5048 ? WEBDAV_FLAG_MOVE_RENAME
5049 : ((pconf->opts & MOD_WEBDAV_UNSAFE_PARTIAL_PUT_COMPAT)
5050 && !(pconf->opts & MOD_WEBDAV_CPYTMP_PARTIAL_PUT))
5051 ? 0
5052 : WEBDAV_FLAG_COPY_LINK);
5053
5054 const buffer * const h =
5055 http_header_request_get(r,HTTP_HEADER_OTHER,CONST_STR_LEN("Overwrite"));
5056 if (NULL != h) {
5057 if (h->used != 2
5058 || ((h->ptr[0] & 0xdf) != 'F' && (h->ptr[0] & 0xdf) != 'T')) {
5059 http_status_set_error(r, 400); /* Bad Request */
5060 return HANDLER_FINISHED;
5061 }
5062 if ((h->ptr[0] & 0xdf) == 'F')
5063 flags &= ~WEBDAV_FLAG_OVERWRITE;
5064 }
5065
5066 /* parse Destination
5067 *
5068 * http://127.0.0.1:1025/dav/litmus/copydest
5069 *
5070 * - host has to match Host: header in request
5071 * (or else would need to check that Destination is reachable from server
5072 * and authentication credentials grant privileges on Destination)
5073 * - query string on Destination, if present, is discarded
5074 *
5075 * NOTE: Destination path is relative to document root and IS NOT re-run
5076 * through other modules on server (such as aliasing or rewrite or userdir)
5077 */
5078 const buffer * const destination =
5079 http_header_request_get(r,HTTP_HEADER_OTHER,CONST_STR_LEN("Destination"));
5080 if (NULL == destination) {
5081 http_status_set_error(r, 400); /* Bad Request */
5082 return HANDLER_FINISHED;
5083 }
5084 #ifdef __COVERITY__
5085 force_assert(2 <= destination->used);
5086 #endif
5087
5088 const char *sep = destination->ptr, *start;
5089 if (*sep != '/') { /* path-absolute or absolute-URI form */
5090 start = sep;
5091 sep = start + buffer_clen(&r->uri.scheme);
5092 if (0 != strncmp(start, r->uri.scheme.ptr, sep - start)
5093 || sep[0] != ':' || sep[1] != '/' || sep[2] != '/') {
5094 http_status_set_error(r, 400); /* Bad Request */
5095 return HANDLER_FINISHED;
5096 }
5097 start = sep + 3;
5098
5099 if (NULL == (sep = strchr(start, '/'))) {
5100 http_status_set_error(r, 400); /* Bad Request */
5101 return HANDLER_FINISHED;
5102 }
5103 if (!buffer_eq_slen(&r->uri.authority, start, sep - start)
5104 /* skip login info (even though it should not be present) */
5105 && (NULL == (start = (char *)memchr(start, '@', sep - start))
5106 || (++start, !buffer_eq_slen(&r->uri.authority,
5107 start, sep - start)))) {
5108 /* not the same host */
5109 http_status_set_error(r, 502); /* Bad Gateway */
5110 return HANDLER_FINISHED;
5111 }
5112 }
5113 start = sep; /* starts with '/' */
5114
5115 /* destination: remove query string, urldecode, path_simplify
5116 * and (maybe) lowercase for consistent destination URI path */
5117 buffer_copy_string_len(dst_rel_path, start,
5118 NULL == (sep = strchr(start, '?'))
5119 ? destination->ptr + destination->used-1 - start
5120 : sep - start);
5121 if (buffer_clen(dst_rel_path) >= PATH_MAX) {
5122 http_status_set_error(r, 403); /* Forbidden */
5123 return HANDLER_FINISHED;
5124 }
5125 buffer_urldecode_path(dst_rel_path);
5126 if (!buffer_is_valid_UTF8(dst_rel_path)) {
5127 /* invalid UTF-8 after url-decode */
5128 http_status_set_error(r, 400);
5129 return HANDLER_FINISHED;
5130 }
5131 buffer_path_simplify(dst_rel_path);
5132 if (buffer_is_blank(dst_rel_path) || dst_rel_path->ptr[0] != '/') {
5133 http_status_set_error(r, 400);
5134 return HANDLER_FINISHED;
5135 }
5136
5137 if (flags & WEBDAV_FLAG_LC_NAMES)
5138 buffer_to_lower(dst_rel_path);
5139
5140 /* Destination physical path
5141 * src r->physical.path might have been remapped with mod_alias.
5142 * (but mod_alias does not modify r->physical.rel_path)
5143 * Find matching prefix to support use of mod_alias to remap webdav root.
5144 * Aliasing of paths underneath the webdav root might not work.
5145 * Likewise, mod_rewrite URL rewriting might thwart this comparison.
5146 * Use mod_redirect instead of mod_alias to remap paths *under* webdav root.
5147 * Use mod_redirect instead of mod_rewrite on *any* parts of path to webdav.
5148 * (Related, use mod_auth to protect webdav root, but avoid attempting to
5149 * use mod_auth on paths underneath webdav root, as Destination is not
5150 * validated with mod_auth)
5151 *
5152 * tl;dr: webdav paths and webdav properties are managed by mod_webdav,
5153 * so do not modify paths externally or else undefined behavior
5154 * or corruption may occur
5155 *
5156 * find matching URI prefix (lowercased if WEBDAV_FLAG_LC_NAMES)
5157 * (r->physical.rel_path and dst_rel_path will always match leading '/')
5158 * check if remaining r->physical.rel_path matches suffix of
5159 * r->physical.path so that we can use the prefix to remap
5160 * Destination physical path */
5161 #ifdef __COVERITY__
5162 force_assert(0 != r->physical.rel_path.used);
5163 #endif
5164 uint32_t i, remain;
5165 {
5166 const char * const p1 = r->physical.rel_path.ptr;
5167 const char * const p2 = dst_rel_path->ptr;
5168 for (i = 0; p1[i] && p1[i] == p2[i]; ++i) ;
5169 while (i != 0 && p1[--i] != '/') ; /* find matching directory path */
5170 }
5171 remain = r->physical.rel_path.used - 1 - i;
5172 if (r->physical.path.used - 1 <= remain) { /*(should not happen)*/
5173 http_status_set_error(r, 403); /* Forbidden */
5174 return HANDLER_FINISHED;
5175 }
5176 if (0 == memcmp(r->physical.rel_path.ptr+i, /*(suffix match)*/
5177 r->physical.path.ptr + r->physical.path.used-1-remain,
5178 remain)) { /*(suffix match)*/
5179 #ifdef __COVERITY__
5180 force_assert(2 <= dst_rel_path->used);
5181 #endif
5182 buffer_copy_path_len2(dst_path,
5183 r->physical.path.ptr,
5184 r->physical.path.used - 1 - remain,
5185 dst_rel_path->ptr+i,
5186 dst_rel_path->used - 1 - i);
5187 if (buffer_clen(dst_path) >= PATH_MAX) {
5188 http_status_set_error(r, 403); /* Forbidden */
5189 return HANDLER_FINISHED;
5190 }
5191 }
5192 else { /*(not expected; some other module mucked with path or rel_path)*/
5193 /* unable to perform physical path remap here;
5194 * assume doc_root/rel_path and no remapping */
5195 buffer_copy_path_len2(dst_path, BUF_PTR_LEN(&r->physical.doc_root),
5196 BUF_PTR_LEN(dst_rel_path));
5197 if (buffer_clen(dst_path) >= PATH_MAX) {
5198 http_status_set_error(r, 403); /* Forbidden */
5199 return HANDLER_FINISHED;
5200 }
5201 }
5202
5203 if (r->physical.path.used <= dst_path->used
5204 && 0 == memcmp(r->physical.path.ptr, dst_path->ptr,
5205 r->physical.path.used-1)
5206 && (buffer_has_pathsep_suffix(&r->physical.path)
5207 || dst_path->ptr[r->physical.path.used-1] == '/'
5208 || dst_path->ptr[r->physical.path.used-1] == '\0')) {
5209 /* dst must not be nested under (or same as) src */
5210 http_status_set_error(r, 403); /* Forbidden */
5211 return HANDLER_FINISHED;
5212 }
5213
5214 struct stat st;
5215 if (-1 == lstat(r->physical.path.ptr, &st)) {
5216 /* don't known about it yet, unlink will fail too */
5217 http_status_set_error(r, (errno == ENOENT) ? 404 : 403);
5218 return HANDLER_FINISHED;
5219 }
5220
5221 if (0 != webdav_if_match_or_unmodified_since(r, &st)) {
5222 http_status_set_error(r, 412); /* Precondition Failed */
5223 return HANDLER_FINISHED;
5224 }
5225
5226 if (S_ISDIR(st.st_mode)) {
5227 if (!buffer_has_pathsep_suffix(&r->physical.path)) {
5228 http_response_redirect_to_directory(r, 308);
5229 return HANDLER_FINISHED; /* 308 Permanent Redirect */
5230 /* Alternatively, could append '/' to r->physical.path
5231 * and r->physical.rel_path, set Content-Location in
5232 * response headers, and continue to serve the request. */
5233 }
5234
5235 /* ensure Destination paths end with '/' since dst is a collection */
5236 if (!buffer_has_slash_suffix(dst_rel_path)) {
5237 buffer_append_slash(dst_rel_path);
5238 buffer_append_slash(dst_path);
5239 }
5240
5241 /* check for lock on destination (after ensuring dst ends in '/') */
5242 if (!webdav_has_lock(r, pconf, dst_rel_path))
5243 return HANDLER_FINISHED; /* 423 Locked */
5244
5245 const int depth = webdav_parse_Depth(r);
5246 if (1 == depth) {
5247 http_status_set_error(r, 400); /* Bad Request */
5248 return HANDLER_FINISHED;
5249 }
5250 if (0 == depth) {
5251 if (r->http_method == HTTP_METHOD_MOVE) {
5252 http_status_set_error(r, 400); /* Bad Request */
5253 return HANDLER_FINISHED;
5254 }
5255 /* optionally create collection, then copy properties */
5256 int status;
5257 if (0 == lstat(dst_path->ptr, &st)) {
5258 if (S_ISDIR(st.st_mode))
5259 status = 204; /* No Content */
5260 else if (flags & WEBDAV_FLAG_OVERWRITE) {
5261 status = webdav_mkdir(pconf, dst, 1);
5262 if (0 == status) status = 204; /* No Content */
5263 }
5264 else
5265 status = 412; /* Precondition Failed */
5266 }
5267 else if (errno == ENOENT) {
5268 status = webdav_mkdir(pconf, dst,
5269 !!(flags & WEBDAV_FLAG_OVERWRITE));
5270 if (0 == status) status = 201; /* Created */
5271 }
5272 else
5273 status = 403; /* Forbidden */
5274 if (status < 300) {
5275 http_status_set_fin(r, status);
5276 webdav_prop_copy_uri(pconf, &r->physical.rel_path,
5277 dst_rel_path);
5278 }
5279 else
5280 http_status_set_error(r, status);
5281 return HANDLER_FINISHED;
5282 }
5283
5284 if (0 == webdav_copymove_dir(pconf, &r->physical, dst, r, flags)) {
5285 if (r->http_method == HTTP_METHOD_MOVE)
5286 webdav_lock_delete_uri_col(pconf, &r->physical.rel_path);
5287 /*(requiring lock on destination requires MKCOL create dst first)
5288 *(if no lock support, return 200 OK unconditionally
5289 * instead of 200 OK or 201 Created; not fully RFC-conformant)*/
5290 http_status_set_fin(r, 200); /* OK */
5291 }
5292 else {
5293 /* Note: this does not destroy any locks if any error occurs,
5294 * which is not a problem if lock is only on the collection
5295 * being moved, but might need finer updates if there are
5296 * locks on internal elements that are successfully moved */
5297 webdav_xml_doc_multistatus(r, pconf); /* 207 Multi-status */
5298 }
5299 /* invalidate stat cache of src if MOVE, whether or not successful */
5300 if (r->http_method == HTTP_METHOD_MOVE)
5301 stat_cache_delete_dir(BUF_PTR_LEN(&r->physical.path));
5302 return HANDLER_FINISHED;
5303 }
5304 else if (buffer_has_pathsep_suffix(&r->physical.path)) {
5305 http_status_set_error(r, 403); /* Forbidden */
5306 return HANDLER_FINISHED;
5307 }
5308 else {
5309 /* check if client has lock for destination
5310 * Note: requiring a lock on non-collection means that destination
5311 * should always exist since the issuance of the lock creates the
5312 * resource, so client will always have to provide Overwrite: T
5313 * for direct operations on non-collections (files) */
5314 if (!webdav_has_lock(r, pconf, dst_rel_path))
5315 return HANDLER_FINISHED; /* 423 Locked */
5316
5317 /* check if destination exists
5318 * (Destination should exist since lock is required,
5319 * and obtaining a lock will create the resource) */
5320 int rc = lstat(dst_path->ptr, &st);
5321 if (0 == rc && S_ISDIR(st.st_mode)) {
5322 /* file to dir/
5323 * append basename to physical path
5324 * future: might set Content-Location if dst_path does not end '/'*/
5325 if (NULL != (sep = strrchr(r->physical.path.ptr, '/'))) {
5326 size_t len = r->physical.path.used - 1
5327 - (sep - r->physical.path.ptr);
5328 if (buffer_has_pathsep_suffix(dst_path)) {
5329 ++sep; /*(avoid double-slash in path)*/
5330 --len;
5331 }
5332 buffer_append_string_len(dst_path, sep, len);
5333 buffer_append_string_len(dst_rel_path, sep, len);
5334 if (buffer_clen(dst_path) >= PATH_MAX) {
5335 http_status_set_error(r, 403); /* Forbidden */
5336 return HANDLER_FINISHED;
5337 }
5338 rc = lstat(dst_path->ptr, &st);
5339 /* target (parent collection) already exists */
5340 http_status_set_fin(r, 204); /* No Content */
5341 }
5342 }
5343
5344 if (-1 == rc) {
5345 char *slash;
5346 switch (errno) {
5347 case ENOENT:
5348 if (http_status_is_set(r)) break;
5349 /* check that parent collection exists */
5350 if ((slash = strrchr(dst_path->ptr, '/'))) {
5351 *slash = '\0';
5352 if (0 == lstat(dst_path->ptr, &st) && S_ISDIR(st.st_mode)) {
5353 *slash = '/';
5354 /* new entity will be created */
5355 if (!http_status_is_set(r)) {
5356 webdav_parent_modified(dst_path);
5357 http_status_set_fin(r, 201); /* Created */
5358 }
5359 break;
5360 }
5361 }
5362 __attribute_fallthrough__
5363 /*case ENOTDIR:*/
5364 default:
5365 http_status_set_error(r, 409); /* Conflict */
5366 return HANDLER_FINISHED;
5367 }
5368 }
5369 else if (!(flags & WEBDAV_FLAG_OVERWRITE)) {
5370 /* destination exists, but overwrite is not set */
5371 http_status_set_error(r, 412); /* Precondition Failed */
5372 return HANDLER_FINISHED;
5373 }
5374 else if (S_ISDIR(st.st_mode)) {
5375 /* destination exists, but is a dir, not a file */
5376 http_status_set_error(r, 409); /* Conflict */
5377 return HANDLER_FINISHED;
5378 }
5379 else { /* resource already exists */
5380 http_status_set_fin(r, 204); /* No Content */
5381 }
5382
5383 rc = webdav_copymove_file(pconf, &r->physical, dst, &flags);
5384 if (0 == rc) {
5385 if (r->http_method == HTTP_METHOD_MOVE)
5386 webdav_lock_delete_uri(pconf, &r->physical.rel_path);
5387 }
5388 else
5389 http_status_set_error(r, rc);
5390
5391 return HANDLER_FINISHED;
5392 }
5393 }
5394
5395
5396 static handler_t
mod_webdav_copymove(request_st * const r,const plugin_config * const pconf)5397 mod_webdav_copymove (request_st * const r, const plugin_config * const pconf)
5398 {
5399 buffer *dst_path = chunk_buffer_acquire();
5400 buffer *dst_rel_path = chunk_buffer_acquire();
5401 physical_st dst;
5402 dst.path = *dst_path;
5403 dst.rel_path = *dst_rel_path;
5404 handler_t rc = mod_webdav_copymove_b(r, pconf, &dst);
5405 *dst_path = dst.path;
5406 *dst_rel_path = dst.rel_path;
5407 chunk_buffer_release(dst_rel_path);
5408 chunk_buffer_release(dst_path);
5409 return rc;
5410 }
5411
5412
5413 #ifdef USE_PROPPATCH
5414 static handler_t
mod_webdav_proppatch(request_st * const r,const plugin_config * const pconf)5415 mod_webdav_proppatch (request_st * const r, const plugin_config * const pconf)
5416 {
5417 if (!pconf->sql)
5418 return webdav_405_no_db(r);
5419
5420 if (r->state == CON_STATE_READ_POST) {
5421 handler_t rc = r->con->reqbody_read(r);
5422 if (rc != HANDLER_GO_ON) return rc;
5423 }
5424
5425 if (0 == r->reqbody_length) {
5426 http_status_set_error(r, 400); /* Bad Request */
5427 return HANDLER_FINISHED;
5428 }
5429
5430 if (!webdav_reqbody_type_xml(r)) {
5431 http_status_set_error(r, 415); /* Unsupported Media Type */
5432 return HANDLER_FINISHED;
5433 }
5434
5435 struct stat st;
5436 if (0 != lstat(r->physical.path.ptr, &st)) {
5437 http_status_set_error(r, (errno == ENOENT) ? 404 : 403);
5438 return HANDLER_FINISHED;
5439 }
5440
5441 if (0 != webdav_if_match_or_unmodified_since(r, &st)) {
5442 http_status_set_error(r, 412); /* Precondition Failed */
5443 return HANDLER_FINISHED;
5444 }
5445
5446 if (S_ISDIR(st.st_mode)) {
5447 if (!buffer_has_pathsep_suffix(&r->physical.path)) {
5448 /* set "Content-Location" instead of sending 308 redirect to dir */
5449 if (0 != http_response_redirect_to_directory(r, 0))
5450 return HANDLER_FINISHED;
5451 buffer_append_char(&r->physical.path, '/');
5452 buffer_append_char(&r->physical.rel_path, '/');
5453 }
5454 }
5455 else if (buffer_has_pathsep_suffix(&r->physical.path)) {
5456 http_status_set_error(r, 403);
5457 return HANDLER_FINISHED;
5458 }
5459
5460 xmlDocPtr const xml = webdav_parse_chunkqueue(r, pconf);
5461 if (NULL == xml) {
5462 http_status_set_error(r, 400); /* Bad Request */
5463 return HANDLER_FINISHED;
5464 }
5465
5466 const xmlNode * const rootnode = xmlDocGetRootElement(xml);
5467 if (NULL == rootnode
5468 || 0 != webdav_xmlstrcmp_fixed(rootnode->name, "propertyupdate")) {
5469 http_status_set_error(r, 422); /* Unprocessable Entity */
5470 xmlFreeDoc(xml);
5471 return HANDLER_FINISHED;
5472 }
5473
5474 if (!webdav_db_transaction_begin_immediate(pconf)) {
5475 http_status_set_error(r, 500); /* Internal Server Error */
5476 xmlFreeDoc(xml);
5477 return HANDLER_FINISHED;
5478 }
5479
5480 /* NOTE: selectively providing multi-status response is NON-CONFORMANT
5481 * (specified in [RFC4918])
5482 * However, PROPPATCH is all-or-nothing, so client should be able to
5483 * unequivocably know that all items in PROPPATCH succeeded if it receives
5484 * 204 No Content, or that items that are not listed with a failure status
5485 * in a multi-status response have the status of 424 Failed Dependency,
5486 * without the server having to be explicit. */
5487
5488 /* UPDATE request, we know 'set' and 'remove' */
5489 buffer *ms = NULL; /*(multi-status)*/
5490 int update;
5491 for (const xmlNode *cmd = rootnode->children; cmd; cmd = cmd->next) {
5492 if (!(update = (0 == webdav_xmlstrcmp_fixed(cmd->name, "set")))) {
5493 if (0 != webdav_xmlstrcmp_fixed(cmd->name, "remove"))
5494 continue; /* skip; not "set" or "remove" */
5495 }
5496
5497 for (const xmlNode *props = cmd->children; props; props = props->next) {
5498 if (0 != webdav_xmlstrcmp_fixed(props->name, "prop"))
5499 continue;
5500
5501 const xmlNode *prop = props->children;
5502 /* libxml2 will keep those blank (whitespace only) nodes */
5503 while (NULL != prop && xmlIsBlankNode(prop))
5504 prop = prop->next;
5505 if (NULL == prop)
5506 continue;
5507 if (prop->ns && '\0' == *(char *)prop->ns->href
5508 && '\0' != *(char *)prop->ns->prefix) {
5509 /* error: missing namespace for property */
5510 log_error(r->conf.errh, __FILE__, __LINE__,
5511 "no namespace for: %s", prop->name);
5512 if (!ms) ms = chunk_buffer_acquire(); /* Unprocessable Entity */
5513 webdav_xml_propstat_status(ms, "", (char *)prop->name, 422);
5514 webdav_double_buffer(r, ms);
5515 continue;
5516 }
5517
5518 /* XXX: ??? should blank namespace be normalized to "DAV:" ???
5519 * ??? should this also be done in propfind requests ??? */
5520
5521 if (prop->ns && 0 == strcmp((char *)prop->ns->href, "DAV:")) {
5522 const size_t namelen = strlen((char *)prop->name);
5523 const struct live_prop_list *list = protected_props;
5524 while (0 != list->len
5525 && (list->len != namelen
5526 || 0 != memcmp(prop->name, list->prop, list->len)))
5527 ++list;
5528 if (NULL != list->prop) {
5529 /* error <DAV:cannot-modify-protected-property/> */
5530 if (!ms) ms = chunk_buffer_acquire();
5531 webdav_xml_propstat_protected(ms, (char *)prop->name,
5532 namelen, 403); /* Forbidden */
5533 webdav_double_buffer(r, ms);
5534 continue;
5535 }
5536 }
5537
5538 if (update) {
5539 if (!prop->children) continue;
5540 char * const propval = prop->children
5541 ? (char *)xmlNodeListGetString(xml, prop->children, 0)
5542 : NULL;
5543 webdav_prop_update(pconf, &r->physical.rel_path,
5544 (char *)prop->name,
5545 prop->ns ? (char *)prop->ns->href : "",
5546 propval ? propval : "");
5547 xmlFree(propval);
5548 }
5549 else
5550 webdav_prop_delete(pconf, &r->physical.rel_path,
5551 (char *)prop->name,
5552 prop->ns ? (char *)prop->ns->href : "");
5553 }
5554 }
5555
5556 if (NULL == ms
5557 ? webdav_db_transaction_commit(pconf)
5558 : webdav_db_transaction_rollback(pconf)) {
5559 if (NULL == ms) {
5560 const buffer *vb =
5561 http_header_request_get(r, HTTP_HEADER_USER_AGENT,
5562 CONST_STR_LEN("User-Agent"));
5563 if (vb && 0 == strncmp(vb->ptr, "Microsoft-WebDAV-MiniRedir/",
5564 sizeof("Microsoft-WebDAV-MiniRedir/")-1)) {
5565 /* workaround Microsoft-WebDAV-MiniRedir bug; 204 not handled */
5566 /* 200 without response body or 204 both incorrectly interpreted
5567 * as 507 Insufficient Storage by Microsoft-WebDAV-MiniRedir. */
5568 ms = chunk_buffer_acquire(); /* 207 Multi-status */ /*(flag)*/
5569 webdav_xml_response_status(r, &r->physical.path, 200);
5570 }
5571 }
5572 if (NULL == ms)
5573 http_status_set_fin(r, 204); /* No Content */
5574 else /* 207 Multi-status */
5575 webdav_xml_doc_multistatus_response(r, pconf, ms);
5576 }
5577 else
5578 http_status_set_error(r, 500); /* Internal Server Error */
5579
5580 if (NULL != ms)
5581 chunk_buffer_release(ms);
5582
5583 xmlFreeDoc(xml);
5584 return HANDLER_FINISHED;
5585 }
5586 #endif
5587
5588
5589 #ifdef USE_LOCKS
5590 struct webdav_conflicting_lock_st {
5591 webdav_lockdata *lockdata;
5592 buffer *b;
5593 request_st *r;
5594 };
5595
5596
5597 static void
webdav_conflicting_lock_cb(void * const vdata,const webdav_lockdata * const lockdata)5598 webdav_conflicting_lock_cb (void * const vdata,
5599 const webdav_lockdata * const lockdata)
5600 {
5601 /* lock is not available if someone else has exclusive lock or if
5602 * client requested exclusive lock and others have shared locks */
5603 struct webdav_conflicting_lock_st * const cbdata =
5604 (struct webdav_conflicting_lock_st *)vdata;
5605 if (lockdata->lockscope->used == sizeof("exclusive")
5606 || cbdata->lockdata->lockscope->used == sizeof("exclusive")) {
5607 webdav_xml_href(cbdata->b, &lockdata->lockroot);
5608 webdav_double_buffer(cbdata->r, cbdata->b);
5609 }
5610 }
5611
5612
5613 static handler_t
mod_webdav_lock(request_st * const r,const plugin_config * const pconf)5614 mod_webdav_lock (request_st * const r, const plugin_config * const pconf)
5615 {
5616 /**
5617 * a mac wants to write
5618 *
5619 * LOCK /dav/expire.txt HTTP/1.1\r\n
5620 * User-Agent: WebDAVFS/1.3 (01308000) Darwin/8.1.0 (Power Macintosh)\r\n
5621 * Accept: * / *\r\n
5622 * Depth: 0\r\n
5623 * Timeout: Second-600\r\n
5624 * Content-Type: text/xml;charset=utf-8\r\n
5625 * Content-Length: 229\r\n
5626 * Connection: keep-alive\r\n
5627 * Host: 192.168.178.23:1025\r\n
5628 * \r\n
5629 * <?xml version=\"1.0\" encoding=\"utf-8\"?>\n
5630 * <D:lockinfo xmlns:D=\"DAV:\">\n
5631 * <D:lockscope><D:exclusive/></D:lockscope>\n
5632 * <D:locktype><D:write/></D:locktype>\n
5633 * <D:owner>\n
5634 * <D:href>http://www.apple.com/webdav_fs/</D:href>\n
5635 * </D:owner>\n
5636 * </D:lockinfo>\n
5637 */
5638
5639 if (r->reqbody_length) {
5640 if (r->state == CON_STATE_READ_POST) {
5641 handler_t rc = r->con->reqbody_read(r);
5642 if (rc != HANDLER_GO_ON) return rc;
5643 }
5644 }
5645
5646 /* XXX: maybe add config switch to require that authentication occurred? */
5647 buffer owner = { NULL, 0, 0 };/*owner (not authenticated)(auth_user unset)*/
5648 const data_string * const authn_user = (const data_string *)
5649 array_get_element_klen(&r->env, CONST_STR_LEN("REMOTE_USER"));
5650
5651 /* future: make max timeout configurable (e.g. pconf->lock_timeout_max)
5652 *
5653 * [RFC4918] 10.7 Timeout Request Header
5654 * The "Second" TimeType specifies the number of seconds that will elapse
5655 * between granting of the lock at the server, and the automatic removal
5656 * of the lock. The timeout value for TimeType "Second" MUST NOT be
5657 * greater than 2^32-1.
5658 */
5659
5660 webdav_lockdata lockdata = {
5661 { NULL, 0, 0 }, /* locktoken */
5662 { r->physical.rel_path.ptr, r->physical.rel_path.used, 0}, /*lockroot*/
5663 { NULL, 0, 0 }, /* ownerinfo */
5664 (authn_user ? &authn_user->value : &owner), /* owner */
5665 NULL, /* lockscope */
5666 NULL, /* locktype */
5667 -1, /* depth */
5668 600 /* timeout (arbitrary default lock timeout: 10 minutes) */
5669 };
5670
5671 const buffer *h =
5672 http_header_request_get(r, HTTP_HEADER_OTHER, CONST_STR_LEN("Timeout"));
5673 if (h) {
5674 /* loosely parse Timeout request header and ignore "infinity" timeout */
5675 /* future: might implement config param for upper limit for timeout */
5676 const char *p = h->ptr;
5677 do {
5678 if ((*p | 0x20) == 's'
5679 && buffer_eq_icase_ssn(p, CONST_STR_LEN("second-"))) {
5680 long t = strtol(p+sizeof("second-")-1, NULL, 10);
5681 if (0 < t && t < lockdata.timeout)
5682 lockdata.timeout = t > 5 ? t : 5;
5683 /*(arbitrary min timeout: 5 secs)*/
5684 else if (sizeof(long) != sizeof(int) && t > INT32_MAX)
5685 lockdata.timeout = INT32_MAX;
5686 /* while UINT32_MAX is actual limit in RFC4918,
5687 * sqlite more easily supports int, though could be
5688 * changed to use int64 to for the timeout param.
5689 * The "limitation" between timeouts that are many
5690 * *years* long does not really matter in reality. */
5691 break;
5692 }
5693 #if 0
5694 else if ((*p | 0x20) == 'i'
5695 && buffer_eq_icase_ssn(p, CONST_STR_LEN("infinity"))) {
5696 lockdata.timeout = INT32_MAX;
5697 break;
5698 }
5699 #endif
5700 while (*p != ',' && *p != '\0') ++p;
5701 while (*p == ' ' || *p == '\t') ++p;
5702 } while (*p != '\0');
5703 }
5704
5705 if (r->reqbody_length) {
5706 lockdata.depth = webdav_parse_Depth(r);
5707 if (1 == lockdata.depth) {
5708 /* [RFC4918] 9.10.3 Depth and Locking
5709 * Values other than 0 or infinity MUST NOT be used
5710 * with the Depth header on a LOCK method.
5711 */
5712 http_status_set_error(r, 400); /* Bad Request */
5713 return HANDLER_FINISHED;
5714 }
5715
5716 xmlDocPtr const xml = webdav_parse_chunkqueue(r, pconf);
5717 if (NULL == xml) {
5718 http_status_set_error(r, 400); /* Bad Request */
5719 return HANDLER_FINISHED;
5720 }
5721
5722 const xmlNode * const rootnode = xmlDocGetRootElement(xml);
5723 if (NULL == rootnode
5724 || 0 != webdav_xmlstrcmp_fixed(rootnode->name, "lockinfo")) {
5725 http_status_set_error(r, 422); /* Unprocessable Entity */
5726 xmlFreeDoc(xml);
5727 return HANDLER_FINISHED;
5728 }
5729
5730 const xmlNode *lockinfo = rootnode->children;
5731 for (; lockinfo; lockinfo = lockinfo->next) {
5732 if (0 == webdav_xmlstrcmp_fixed(lockinfo->name, "lockscope")) {
5733 const xmlNode *value = lockinfo->children;
5734 for (; value; value = value->next) {
5735 if (0 == webdav_xmlstrcmp_fixed(value->name, "exclusive"))
5736 lockdata.lockscope=(const buffer *)&lockscope_exclusive;
5737 else if (0 == webdav_xmlstrcmp_fixed(value->name, "shared"))
5738 lockdata.lockscope=(const buffer *)&lockscope_shared;
5739 else {
5740 lockdata.lockscope=NULL; /* trigger error below loop */
5741 break;
5742 }
5743 }
5744 }
5745 else if (0 == webdav_xmlstrcmp_fixed(lockinfo->name, "locktype")) {
5746 const xmlNode *value = lockinfo->children;
5747 for (; value; value = value->next) {
5748 if (0 == webdav_xmlstrcmp_fixed(value->name, "write"))
5749 lockdata.locktype = (const buffer *)&locktype_write;
5750 else {
5751 lockdata.locktype = NULL;/* trigger error below loop */
5752 break;
5753 }
5754 }
5755 }
5756 else if (0 == webdav_xmlstrcmp_fixed(lockinfo->name, "owner")) {
5757 if (lockinfo->children)
5758 lockdata.ownerinfo.ptr =
5759 (char *)xmlNodeListGetString(xml, lockinfo->children, 0);
5760 if (lockdata.ownerinfo.ptr)
5761 lockdata.ownerinfo.used = strlen(lockdata.ownerinfo.ptr)+1;
5762 }
5763 }
5764
5765 do { /*(resources are cleaned up after code block)*/
5766
5767 if (NULL == lockdata.lockscope || NULL == lockdata.locktype) {
5768 /*(missing lockscope and locktype in lock request)*/
5769 http_status_set_error(r, 422); /* Unprocessable Entity */
5770 break; /* clean up resources and return HANDLER_FINISHED */
5771 }
5772
5773 /* check lock prior to potentially creating new resource,
5774 * and prior to using entropy to create uuid */
5775 struct webdav_conflicting_lock_st cbdata;
5776 cbdata.lockdata = &lockdata;
5777 cbdata.b = chunk_buffer_acquire();
5778 cbdata.r = r;
5779 webdav_lock_activelocks(pconf, &lockdata.lockroot,
5780 (0 == lockdata.depth ? 1 : -1),
5781 webdav_conflicting_lock_cb, &cbdata);
5782 if (0 != cbdata.b->used || !chunkqueue_is_empty(&r->write_queue)) {
5783 /* 423 Locked */
5784 webdav_xml_doc_error_no_conflicting_lock(r, cbdata.b);
5785 chunk_buffer_release(cbdata.b);
5786 break; /* clean up resources and return HANDLER_FINISHED */
5787 }
5788 chunk_buffer_release(cbdata.b);
5789
5790 int created = 0;
5791 struct stat st;
5792 if (0 != lstat(r->physical.path.ptr, &st)) {
5793 /* [RFC4918] 7.3 Write Locks and Unmapped URLs
5794 * A successful lock request to an unmapped URL MUST result in
5795 * the creation of a locked (non-collection) resource with empty
5796 * content.
5797 * [...]
5798 * The response MUST indicate that a resource was created, by
5799 * use of the "201 Created" response code (a LOCK request to an
5800 * existing resource instead will result in 200 OK).
5801 * [RFC4918] 9.10.4 Locking Unmapped URLs
5802 * A successful LOCK method MUST result in the creation of an
5803 * empty resource that is locked (and that is not a collection)
5804 * when a resource did not previously exist at that URL. Later on,
5805 * the lock may go away but the empty resource remains. Empty
5806 * resources MUST then appear in PROPFIND responses including that
5807 * URL in the response scope. A server MUST respond successfully
5808 * to a GET request to an empty resource, either by using a 204
5809 * No Content response, or by using 200 OK with a Content-Length
5810 * header indicating zero length
5811 *
5812 * unmapped resource; create empty file
5813 * (open() should fail if path ends in '/', but does not on some OS.
5814 * This is desired behavior since collection should be created
5815 * with MKCOL, and not via LOCK on an unmapped resource) */
5816 const int fd =
5817 (errno == ENOENT && !buffer_has_pathsep_suffix(&r->physical.path))
5818 ? fdevent_open_cloexec(r->physical.path.ptr, 0,
5819 O_WRONLY | O_CREAT | O_EXCL | O_TRUNC,
5820 WEBDAV_FILE_MODE)
5821 : -1;
5822 if (fd >= 0) {
5823 /*(skip sending etag if fstat() error; not expected)*/
5824 if (0 != fstat(fd, &st)) r->conf.etag_flags = 0;
5825 close(fd);
5826 created = 1;
5827 webdav_parent_modified(&r->physical.path);
5828 }
5829 else if (errno != EEXIST) {
5830 http_status_set_error(r, 403); /* Forbidden */
5831 break; /* clean up resources and return HANDLER_FINISHED */
5832 }
5833 else if (0 != lstat(r->physical.path.ptr, &st)) {
5834 http_status_set_error(r, 403); /* Forbidden */
5835 break; /* clean up resources and return HANDLER_FINISHED */
5836 }
5837 lockdata.depth = 0; /* force Depth: 0 on non-collections */
5838 }
5839
5840 if (!created) {
5841 if (0 != webdav_if_match_or_unmodified_since(r, &st)) {
5842 http_status_set_error(r, 412); /* Precondition Failed */
5843 break; /* clean up resources and return HANDLER_FINISHED */
5844 }
5845 }
5846
5847 if (created) {
5848 }
5849 else if (S_ISDIR(st.st_mode)) {
5850 if (!buffer_has_pathsep_suffix(&r->physical.path)) {
5851 /* 308 Permanent Redirect */
5852 http_response_redirect_to_directory(r, 308);
5853 break; /* clean up resources and return HANDLER_FINISHED */
5854 /* Alternatively, could append '/' to r->physical.path
5855 * and r->physical.rel_path, set Content-Location in
5856 * response headers, and continue to serve the request */
5857 }
5858 }
5859 else if (buffer_has_pathsep_suffix(&r->physical.path)) {
5860 http_status_set_error(r, 403); /* Forbidden */
5861 break; /* clean up resources and return HANDLER_FINISHED */
5862 }
5863 else if (0 != lockdata.depth)
5864 lockdata.depth = 0; /* force Depth: 0 on non-collections */
5865
5866 /* create locktoken
5867 * (uuid_unparse() output is 36 chars + '\0') */
5868 uuid_t id;
5869 char lockstr[sizeof("<urn:uuid:>") + 36] = "<urn:uuid:";
5870 lockdata.locktoken.ptr = lockstr+1; /*(without surrounding <>)*/
5871 lockdata.locktoken.used = sizeof(lockstr)-2;/*(without surrounding <>)*/
5872 uuid_generate(id);
5873 uuid_unparse(id, lockstr+sizeof("<urn:uuid:")-1);
5874
5875 /* XXX: consider fix TOC-TOU race condition by starting transaction
5876 * and re-running webdav_lock_activelocks() check before running
5877 * webdav_lock_acquire() (but both routines would need to be modified
5878 * to defer calling sqlite3_reset(stmt) to be part of transaction) */
5879 if (webdav_lock_acquire(pconf, &lockdata)) {
5880 lockstr[sizeof(lockstr)-2] = '>';
5881 http_header_response_set(r, HTTP_HEADER_OTHER,
5882 CONST_STR_LEN("Lock-Token"),
5883 lockstr, sizeof(lockstr)-1);
5884 webdav_xml_doc_lock_acquired(r, pconf, &lockdata);
5885 if (0 != r->conf.etag_flags && !S_ISDIR(st.st_mode))
5886 webdav_response_etag(r, &st);
5887 http_status_set_fin(r, created ? 201 : 200); /* Created | OK */
5888 }
5889 else /*(database error obtaining lock)*/
5890 http_status_set_error(r, 500); /* Internal Server Error */
5891
5892 } while (0); /*(resources are cleaned up after code block)*/
5893
5894 xmlFree(lockdata.ownerinfo.ptr);
5895 xmlFreeDoc(xml);
5896 return HANDLER_FINISHED;
5897 }
5898 else {
5899 h = http_header_request_get(r, HTTP_HEADER_OTHER, CONST_STR_LEN("If"));
5900 if (NULL == h
5901 || h->used < 6 || h->ptr[1] != '<' || h->ptr[h->used-3] != '>') {
5902 /*(rejects value with trailing LWS, even though RFC-permitted)*/
5903 http_status_set_error(r, 400); /* Bad Request */
5904 return HANDLER_FINISHED;
5905 }
5906 /* remove (< >) around token */
5907 lockdata.locktoken.ptr = h->ptr+2;
5908 lockdata.locktoken.used = h->used-4;
5909 /*(future: fill in from database, though exclusive write lock is the
5910 * only lock supported at the moment)*/
5911 lockdata.lockscope = (const buffer *)&lockscope_exclusive;
5912 lockdata.locktype = (const buffer *)&locktype_write;
5913 lockdata.depth = 0;
5914
5915 if (webdav_lock_refresh(pconf, &lockdata)) {
5916 webdav_xml_doc_lock_acquired(r, pconf, &lockdata);
5917 http_status_set_fin(r, 200); /* OK */
5918 }
5919 else
5920 http_status_set_error(r, 412); /* Precondition Failed */
5921
5922 return HANDLER_FINISHED;
5923 }
5924 }
5925 #endif
5926
5927
5928 #ifdef USE_LOCKS
5929 static handler_t
mod_webdav_unlock(request_st * const r,const plugin_config * const pconf)5930 mod_webdav_unlock (request_st * const r, const plugin_config * const pconf)
5931 {
5932 const buffer * const h =
5933 http_header_request_get(r, HTTP_HEADER_OTHER,
5934 CONST_STR_LEN("Lock-Token"));
5935 if (NULL == h
5936 || h->used < 4 || h->ptr[0] != '<' || h->ptr[h->used-2] != '>') {
5937 /*(rejects value with trailing LWS, even though RFC-permitted)*/
5938 http_status_set_error(r, 400); /* Bad Request */
5939 return HANDLER_FINISHED;
5940 }
5941
5942 buffer owner = { NULL, 0, 0 };/*owner (not authenticated)(auth_user unset)*/
5943 const data_string * const authn_user = (const data_string *)
5944 array_get_element_klen(&r->env, CONST_STR_LEN("REMOTE_USER"));
5945
5946 webdav_lockdata lockdata = {
5947 { h->ptr+1, h->used-2, 0 }, /* locktoken (remove < > around token) */
5948 { r->physical.rel_path.ptr, r->physical.rel_path.used, 0}, /*lockroot*/
5949 { NULL, 0, 0 }, /* ownerinfo (unused for unlock) */
5950 (authn_user ? &authn_user->value : &owner), /* owner */
5951 NULL, /* lockscope (unused for unlock) */
5952 NULL, /* locktype (unused for unlock) */
5953 0, /* depth (unused for unlock) */
5954 0 /* timeout (unused for unlock) */
5955 };
5956
5957 /* check URI (lockroot) and depth in scope for locktoken and authorized */
5958 switch (webdav_lock_match(pconf, &lockdata)) {
5959 case 0:
5960 if (webdav_lock_release(pconf, &lockdata)) {
5961 http_status_set_fin(r, 204); /* No Content */
5962 return HANDLER_FINISHED;
5963 }
5964 __attribute_fallthrough__
5965 default:
5966 case -1: /* lock does not exist */
5967 case -2: /* URI not in scope of locktoken and depth */
5968 /* 409 Conflict */
5969 webdav_xml_doc_error_lock_token_matches_request_uri(r);
5970 return HANDLER_FINISHED;
5971 case -3: /* not owner/not authorized to remove lock */
5972 http_status_set_error(r, 403); /* Forbidden */
5973 return HANDLER_FINISHED;
5974 }
5975 }
5976 #endif
5977
5978
SUBREQUEST_FUNC(mod_webdav_subrequest_handler)5979 SUBREQUEST_FUNC(mod_webdav_subrequest_handler)
5980 {
5981 const plugin_config * const pconf =
5982 (plugin_config *)r->plugin_ctx[((plugin_data *)p_d)->id];
5983 if (NULL == pconf) return HANDLER_GO_ON; /*(should not happen)*/
5984
5985 switch (r->http_method) {
5986 case HTTP_METHOD_PROPFIND:
5987 return mod_webdav_propfind(r, pconf);
5988 case HTTP_METHOD_MKCOL:
5989 return mod_webdav_mkcol(r, pconf);
5990 case HTTP_METHOD_DELETE:
5991 return mod_webdav_delete(r, pconf);
5992 case HTTP_METHOD_PUT:
5993 return mod_webdav_put(r, pconf);
5994 case HTTP_METHOD_MOVE:
5995 case HTTP_METHOD_COPY:
5996 return mod_webdav_copymove(r, pconf);
5997 #ifdef USE_PROPPATCH
5998 case HTTP_METHOD_PROPPATCH:
5999 return mod_webdav_proppatch(r, pconf);
6000 #endif
6001 #ifdef USE_LOCKS
6002 case HTTP_METHOD_LOCK:
6003 return mod_webdav_lock(r, pconf);
6004 case HTTP_METHOD_UNLOCK:
6005 return mod_webdav_unlock(r, pconf);
6006 #endif
6007 default:
6008 http_status_set_error(r, 501); /* Not Implemented */
6009 return HANDLER_FINISHED;
6010 }
6011 }
6012
6013
PHYSICALPATH_FUNC(mod_webdav_physical_handler)6014 PHYSICALPATH_FUNC(mod_webdav_physical_handler)
6015 {
6016 /* physical path is set up */
6017 /*assert(0 != r->physical.path.used);*/
6018 #ifdef __COVERITY__
6019 force_assert(2 <= r->physical.path.used);
6020 #endif
6021
6022 int check_readonly = 0;
6023 int check_lock_src = 0;
6024 int reject_reqbody = 0;
6025
6026 /* check for WebDAV request methods handled by this module */
6027 switch (r->http_method) {
6028 case HTTP_METHOD_GET:
6029 case HTTP_METHOD_HEAD:
6030 case HTTP_METHOD_POST:
6031 default:
6032 return HANDLER_GO_ON;
6033 case HTTP_METHOD_PROPFIND:
6034 case HTTP_METHOD_LOCK:
6035 break;
6036 case HTTP_METHOD_UNLOCK:
6037 reject_reqbody = 1;
6038 break;
6039 case HTTP_METHOD_DELETE:
6040 case HTTP_METHOD_MOVE:
6041 reject_reqbody = 1;
6042 __attribute_fallthrough__
6043 case HTTP_METHOD_PROPPATCH:
6044 case HTTP_METHOD_PUT:
6045 check_readonly = check_lock_src = 1;
6046 break;
6047 case HTTP_METHOD_COPY:
6048 case HTTP_METHOD_MKCOL:
6049 check_readonly = reject_reqbody = 1;
6050 break;
6051 }
6052
6053 plugin_config pconf;
6054 mod_webdav_patch_config(r, (plugin_data *)p_d, &pconf);
6055 if (!pconf.enabled) return HANDLER_GO_ON;
6056
6057 if (check_readonly && pconf.is_readonly) {
6058 http_status_set_error(r, 403); /* Forbidden */
6059 return HANDLER_FINISHED;
6060 }
6061
6062 if (reject_reqbody && r->reqbody_length) {
6063 /* [RFC4918] 8.4 Required Bodies in Requests
6064 * Servers MUST examine all requests for a body, even when a
6065 * body was not expected. In cases where a request body is
6066 * present but would be ignored by a server, the server MUST
6067 * reject the request with 415 (Unsupported Media Type).
6068 */
6069 http_status_set_error(r, 415); /* Unsupported Media Type */
6070 return HANDLER_FINISHED;
6071 }
6072
6073 if (check_lock_src && !webdav_has_lock(r, &pconf, &r->physical.rel_path))
6074 return HANDLER_FINISHED; /* 423 Locked */
6075
6076 /* initial setup for methods */
6077 switch (r->http_method) {
6078 case HTTP_METHOD_PUT:
6079 if (mod_webdav_put_prep(r, &pconf) == HANDLER_FINISHED)
6080 return HANDLER_FINISHED;
6081 break;
6082 default:
6083 break;
6084 }
6085
6086 r->handler_module = ((plugin_data *)p_d)->self;
6087 r->conf.stream_request_body &=
6088 ~(FDEVENT_STREAM_REQUEST | FDEVENT_STREAM_REQUEST_BUFMIN);
6089 r->plugin_ctx[((plugin_data *)p_d)->id] = &pconf;
6090 const handler_t rc =
6091 mod_webdav_subrequest_handler(r, p_d); /*p->handle_subrequest()*/
6092 if (rc == HANDLER_FINISHED || rc == HANDLER_ERROR)
6093 r->plugin_ctx[((plugin_data *)p_d)->id] = NULL;
6094 else /* e.g. HANDLER_WAIT_FOR_EVENT */
6095 r->plugin_ctx[((plugin_data *)p_d)->id] = /* save pconf */
6096 memcpy(ck_malloc(sizeof(pconf)), &pconf, sizeof(pconf));
6097 return rc;
6098 }
6099
6100
REQUEST_FUNC(mod_webdav_handle_reset)6101 REQUEST_FUNC(mod_webdav_handle_reset) {
6102 /* free plugin_config if allocated and saved to per-request storage */
6103 void ** const restrict dptr =
6104 &r->plugin_ctx[((plugin_data *)p_d)->id];
6105 if (*dptr) {
6106 free(*dptr);
6107 *dptr = NULL;
6108 chunkqueue_set_tempdirs(&r->reqbody_queue, /* reset sz */
6109 r->reqbody_queue.tempdirs, 0);
6110 }
6111 return HANDLER_GO_ON;
6112 }
6113