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