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