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