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