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