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