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