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