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