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