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