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