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