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