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