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