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