6 * Note: This plugin is a basic implementation of [RFC4918] WebDAV
8 * Version Control System (VCS) backing WebDAV is recommended instead
9 * and Subversion (svn) is one such VCS supporting WebDAV.
11 * status: *** EXPERIMENTAL *** (and likely insecure encoding/decoding)
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().
40 * RFE: add config option whether or not to include locktoken and ownerinfo
41 * in PROPFIND lockdiscovery
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
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
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).
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
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
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.
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)
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.
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)
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.
154 /* linkat() fstatat() unlinkat() fdopendir() NAME_MAX */
155 #if !defined(_XOPEN_SOURCE) || _XOPEN_SOURCE-0 < 700
157 #define _XOPEN_SOURCE 700
159 /* DT_UNKNOWN DTTOIF() */
164 #include "first.h" /* first */
165 #include "sys-mmap.h"
166 #include <sys/types.h>
167 #include <sys/stat.h>
171 #include <stdio.h> /* rename() */
172 #include <stdlib.h> /* strtol() */
174 #include <strings.h> /* strncasecmp() */
175 #include <unistd.h> /* getpid() linkat() rmdir() unlinkat() */
177 #ifndef _D_EXACT_NAMLEN
178 #ifdef _DIRENT_HAVE_D_NAMLEN
179 #define _D_EXACT_NAMLEN(d) ((d)->d_namlen)
181 #define _D_EXACT_NAMLEN(d) (strlen ((d)->d_name))
185 #if defined(HAVE_LIBXML_H) && defined(HAVE_SQLITE3_H)
187 #define USE_PROPPATCH
188 /* minor: libxml2 includes stdlib.h in headers, too */
189 #include <libxml/tree.h>
190 #include <libxml/parser.h>
193 #if defined(HAVE_LIBUUID) && defined(HAVE_UUID_UUID_H)
195 #include <uuid/uuid.h>
198 #endif /* defined(HAVE_LIBXML_H) && defined(HAVE_SQLITE3_H) */
204 #include "http_header.h"
207 #include "connections.h"/* connection_handle_read_post_state() */
209 #include "response.h" /* http_response_redirect_to_directory() */
210 #include "stat_cache.h" /* stat_cache_mimetype_by_ext() */
212 #include "configfile.h"
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)
223 __attribute_noinline__
224 static int http_status_set_error (connection
*con
, int status
) {
225 return http_status_set_fin(con
, status
);
228 typedef physical physical_st
;
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
);
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");
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
;
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
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
269 #define webdav_xmlstrcmp_fixed(s, fixed) \
270 strncmp((const char *)(s), (fixed), sizeof(fixed))
272 #include <ctype.h> /* isupper() tolower() */
274 webdav_str_len_to_lower (char * const restrict s
, const uint32_t len
)
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
) {
280 s
[i
] = tolower(s
[i
]);
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
;
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
;
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
;
312 /* plugin config for all request/connections */
315 int config_context_idx
;
317 unsigned short enabled
;
318 unsigned short is_readonly
;
319 unsigned short log_xml
;
320 unsigned short deprecated_unsafe_partial_put_compat
;
325 buffer
*sqlite_db_name
; /* not used after worker init */
332 plugin_config
**config_storage
;
336 /* init the plugin data */
337 INIT_FUNC(mod_webdav_init
) {
338 return calloc(1, sizeof(plugin_data
));
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
;
347 if (p
->config_storage
) {
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
);
355 sql_config
* const sql
= s
->sql
;
356 if (!sql
|| !sql
->sqlh
) {
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
);
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
);
384 free(p
->config_storage
);
390 return HANDLER_GO_ON
;
395 static handler_t
mod_webdav_sqlite3_init (plugin_config
* restrict s
, log_error_st
*errh
);
397 /* handle plugin config and check values */
398 SETDEFAULTS_FUNC(mod_webdav_set_defaults
) {
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;*/
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
},
417 { NULL
, NULL
, T_CONFIG_UNSET
, T_CONFIG_SCOPE_UNSET
}
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
);
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
));
430 p
->config_storage
[i
] = s
;
431 s
->sqlite_db_name
= buffer_init();
432 s
->opts
= array_init();
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
;
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
;
444 if (!buffer_is_empty(s
->sqlite_db_name
)) {
445 if (mod_webdav_sqlite3_init(s
, srv
->errh
) == HANDLER_ERROR
)
446 return HANDLER_ERROR
;
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;
457 log_error(srv
->errh
, __FILE__
, __LINE__
,
458 "unrecognized webdav.opts: %.*s",
459 BUFFER_INTLEN_PTR(ds
->key
));
460 return HANDLER_ERROR
;
464 p
->config_storage
[0]->srv
= srv
;
465 p
->config_storage
[0]->tmpb
= srv
->tmp_buf
;
468 return HANDLER_GO_ON
;
472 #define PATCH_OPTION(x) pconf->x = s->x;
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
)
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
;
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 */
489 s
= p
->config_storage
[i
];
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
);
501 } else if (buffer_is_equal_string(du
->key
, CONST_STR_LEN("webdav.sqlite-db-name"))) {
504 } else if (buffer_is_equal_string(du
->key
, CONST_STR_LEN("webdav.opts"))) {
505 PATCH_OPTION(deprecated_unsafe_partial_put_compat
);
512 URIHANDLER_FUNC(mod_webdav_uri_handler
)
515 if (con
->request
.http_method
!= HTTP_METHOD_OPTIONS
)
516 return HANDLER_GO_ON
;
519 mod_webdav_patch_connection(srv
, con
, (plugin_data
*)p_d
, &pconf
);
520 if (!pconf
.enabled
) return HANDLER_GO_ON
;
522 /* [RFC4918] 18 DAV Compliance Classes */
523 http_header_response_set(con
, HTTP_HEADER_OTHER
,
524 CONST_STR_LEN("DAV"),
526 CONST_STR_LEN("1,2,3")
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"));
539 if (pconf
.is_readonly
)
540 http_header_response_append(con
, HTTP_HEADER_OTHER
,
541 CONST_STR_LEN("Allow"),
542 CONST_STR_LEN("PROPFIND"));
544 http_header_response_append(con
, HTTP_HEADER_OTHER
,
545 CONST_STR_LEN("Allow"),
549 "PROPFIND, DELETE, MKCOL, PUT, MOVE, COPY, PROPPATCH, LOCK, UNLOCK")
552 "PROPFIND, DELETE, MKCOL, PUT, MOVE, COPY, PROPPATCH")
556 "PROPFIND, DELETE, MKCOL, PUT, MOVE, COPY")
560 return HANDLER_GO_ON
;
566 typedef struct webdav_lockdata
{
571 const buffer
*lockscope
; /* future: might use enum, store int in db */
572 const buffer
*locktype
; /* future: might use enum, store int in db */
574 int timeout
; /* offset from now, not absolute time_t */
577 typedef struct { const char *ptr
; uint32_t used
; uint32_t size
; } tagb
;
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 };
593 } webdav_property_name
;
596 webdav_property_name
*ptr
;
599 } webdav_property_names
;
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.
628 #define MOD_WEBDAV_XMLNS_NS0 "xmlns:ns0=\"urn:uuid:c2f41010-65b3-11d1-a29f-00aa00c14882/\""
632 webdav_xml_doctype (buffer
* const b
, connection
* const con
)
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\""));
638 buffer_copy_string_len(b
, CONST_STR_LEN(
639 "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"));
644 webdav_xml_prop (buffer
* const b
,
645 const webdav_property_name
* const prop
,
646 const char * const value
, const uint32_t vlen
)
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
);
653 buffer_append_string_len(b
, CONST_STR_LEN("\"/>"));
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(">"));
667 webdav_xml_href_raw (buffer
* const b
, const buffer
* const href
)
669 buffer_append_string_len(b
, CONST_STR_LEN(
671 buffer_append_string_len(b
, CONST_BUF_LEN(href
));
672 buffer_append_string_len(b
, CONST_STR_LEN(
679 webdav_xml_href (buffer
* const b
, const buffer
* const href
)
681 buffer_append_string_len(b
, CONST_STR_LEN(
683 buffer_append_string_encoded(b
, CONST_BUF_LEN(href
), ENCODING_REL_URI
);
684 buffer_append_string_len(b
, CONST_STR_LEN(
690 webdav_xml_status (buffer
* const b
, const int status
)
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(
703 webdav_xml_propstat_protected (buffer
* const b
, const char * const propname
,
704 const uint32_t len
, const int status
)
706 buffer_append_string_len(b
, CONST_STR_LEN(
709 buffer_append_string_len(b
, propname
, len
);
710 buffer_append_string_len(b
, CONST_STR_LEN(
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(
723 webdav_xml_propstat_status (buffer
* const b
, const char * const ns
,
724 const char * const name
, const int status
)
726 buffer_append_string_len(b
, CONST_STR_LEN(
729 buffer_append_string(b
, ns
);
730 buffer_append_string(b
, name
);
731 buffer_append_string_len(b
, CONST_STR_LEN(
733 webdav_xml_status(b
, status
);
734 buffer_append_string_len(b
, CONST_STR_LEN(
741 webdav_xml_propstat (buffer
* const b
, buffer
* const value
, const int status
)
743 buffer_append_string_len(b
, CONST_STR_LEN(
746 buffer_append_string_buffer(b
, value
);
747 buffer_append_string_len(b
, CONST_STR_LEN(
749 webdav_xml_status(b
, status
);
750 buffer_append_string_len(b
, CONST_STR_LEN(
757 webdav_xml_response_status (buffer
* const b
,
758 const buffer
* const href
,
761 buffer_append_string_len(b
, CONST_STR_LEN(
763 webdav_xml_href(b
, href
);
764 webdav_xml_status(b
, status
);
765 buffer_append_string_len(b
, CONST_STR_LEN(
772 webdav_xml_activelock (buffer
* const b
,
773 const webdav_lockdata
* const lockdata
,
774 const char * const tbuf
, uint32_t tbuf_len
)
776 buffer_append_string_len(b
, CONST_STR_LEN(
780 buffer_append_string_buffer(b
, lockdata
->lockscope
);
781 buffer_append_string_len(b
, CONST_STR_LEN(
786 buffer_append_string_buffer(b
, lockdata
->locktype
);
787 buffer_append_string_len(b
, CONST_STR_LEN(
791 if (0 == lockdata
->depth
)
792 buffer_append_string_len(b
, CONST_STR_LEN("0"));
794 buffer_append_string_len(b
, CONST_STR_LEN("infinity"));
795 buffer_append_string_len(b
, CONST_STR_LEN(
799 buffer_append_string_len(b
, tbuf
, tbuf_len
); /* "Second-..." */
801 buffer_append_string_len(b
, CONST_STR_LEN("Second-"));
802 buffer_append_int(b
, lockdata
->timeout
);
804 buffer_append_string_len(b
, CONST_STR_LEN(
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(
812 webdav_xml_href_raw(b
, &lockdata
->locktoken
); /*(as-is; not URL-encoded)*/
813 buffer_append_string_len(b
, CONST_STR_LEN(
816 webdav_xml_href(b
, &lockdata
->lockroot
);
817 buffer_append_string_len(b
, CONST_STR_LEN(
819 "</D:activelock>\n"));
825 webdav_xml_doc_multistatus (connection
* const con
,
826 const plugin_config
* const pconf
,
829 http_status_set_fin(con
, 207); /* Multi-status */
831 buffer
* const b
= /*(optimization; buf extended as needed)*/
832 chunkqueue_append_buffer_open_sz(con
->write_queue
, 128 + ms
->used
);
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"));
842 log_error(con
->errh
, __FILE__
, __LINE__
,
843 "XML-response-body: %.*s", BUFFER_INTLEN_PTR(b
));
845 chunkqueue_append_buffer_commit(con
->write_queue
);
851 webdav_xml_doc_multistatus_response (connection
* const con
,
852 const plugin_config
* const pconf
,
855 http_status_set_fin(con
, 207); /* Multi-status */
857 buffer
* const b
= /*(optimization; buf extended as needed)*/
858 chunkqueue_append_buffer_open_sz(con
->write_queue
, 128 + ms
->used
);
860 webdav_xml_doctype(b
, con
);
861 buffer_append_string_len(b
, CONST_STR_LEN(
862 "<D:multistatus xmlns:D=\"DAV:\">\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(
868 "</D:multistatus>\n"));
871 log_error(con
->errh
, __FILE__
, __LINE__
,
872 "XML-response-body: %.*s", BUFFER_INTLEN_PTR(b
));
874 chunkqueue_append_buffer_commit(con
->write_queue
);
881 webdav_xml_doc_lock_acquired (connection
* const con
,
882 const plugin_config
* const pconf
,
883 const webdav_lockdata
* const lockdata
)
885 /*(http_status is set by caller to 200 OK or 201 Created)*/
887 char tbuf
[32] = "Second-";
888 li_itostrn(tbuf
+sizeof("Second-")-1, sizeof(tbuf
)-(sizeof("Second-")-1),
890 const uint32_t tbuf_len
= strlen(tbuf
);
891 http_header_response_set(con
, HTTP_HEADER_OTHER
,
892 CONST_STR_LEN("Timeout"),
896 chunkqueue_append_buffer_open_sz(con
->write_queue
, 1024);
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"
908 log_error(con
->errh
, __FILE__
, __LINE__
,
909 "XML-response-body: %.*s", BUFFER_INTLEN_PTR(b
));
911 chunkqueue_append_buffer_commit(con
->write_queue
);
917 * [RFC4918] 16 Precondition/Postcondition XML Elements
923 * "<D:error><DAV:cannot-modify-protected-property/></D:error>"
926 * "<D:error><DAV:no-external-entities/></D:error>"
929 * "<D:error><DAV:preserved-live-properties/></D:error>"
935 webdav_xml_doc_error_propfind_finite_depth (connection
* const con
)
937 http_status_set(con
, 403); /* Forbidden */
938 con
->file_finished
= 1;
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
);
952 webdav_xml_doc_error_lock_token_matches_request_uri (connection
* const con
)
954 http_status_set(con
, 409); /* Conflict */
955 con
->file_finished
= 1;
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
);
970 webdav_xml_doc_423_locked (connection
* const con
, buffer
* const hrefs
,
971 const char * const errtag
, const uint32_t errtaglen
)
973 http_status_set(con
, 423); /* Locked */
974 con
->file_finished
= 1;
976 buffer
* const b
= /*(optimization; buf extended as needed)*/
977 chunkqueue_append_buffer_open_sz(con
->write_queue
, 256 + hrefs
->used
);
979 webdav_xml_doctype(b
, con
);
980 buffer_append_string_len(b
, CONST_STR_LEN(
981 "<D:error xmlns:D=\"DAV:\">\n"
983 buffer_append_string_len(b
, errtag
, errtaglen
);
984 buffer_append_string_len(b
, CONST_STR_LEN(
986 buffer_append_string_buffer(b
, hrefs
);
987 buffer_append_string_len(b
, CONST_STR_LEN(
989 buffer_append_string_len(b
, errtag
, errtaglen
);
990 buffer_append_string_len(b
, CONST_STR_LEN(
994 chunkqueue_append_buffer_commit(con
->write_queue
);
1002 webdav_xml_doc_error_lock_token_submitted (connection
* const con
,
1003 buffer
* const hrefs
)
1005 webdav_xml_doc_423_locked(con
, hrefs
,
1006 CONST_STR_LEN("lock-token-submitted"));
1014 webdav_xml_doc_error_no_conflicting_lock (connection
* const con
,
1015 buffer
* const hrefs
)
1017 webdav_xml_doc_423_locked(con
, hrefs
,
1018 CONST_STR_LEN("no-conflicting-lock"));
1023 #ifdef USE_PROPPATCH
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))"
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))"
1045 #define MOD_WEBDAV_SQLITE_PROPS_SELECT_PROPNAMES \
1046 "SELECT prop, ns FROM properties WHERE resource = ?"
1048 #define MOD_WEBDAV_SQLITE_PROPS_SELECT_PROP \
1049 "SELECT value FROM properties WHERE resource = ? AND prop = ? AND ns = ?"
1051 #define MOD_WEBDAV_SQLITE_PROPS_SELECT_PROPS \
1052 "SELECT prop, ns, value FROM properties WHERE resource = ?"
1054 #define MOD_WEBDAV_SQLITE_PROPS_UPDATE_PROP \
1055 "REPLACE INTO properties (resource, prop, ns, value) VALUES (?, ?, ?, ?)"
1057 #define MOD_WEBDAV_SQLITE_PROPS_DELETE_PROP \
1058 "DELETE FROM properties WHERE resource = ? AND prop = ? AND ns = ?"
1060 #define MOD_WEBDAV_SQLITE_PROPS_DELETE \
1061 "DELETE FROM properties WHERE resource = ?"
1063 #define MOD_WEBDAV_SQLITE_PROPS_COPY \
1064 "INSERT INTO properties" \
1065 " SELECT ?, prop, ns, value FROM properties WHERE resource = ?"
1067 #define MOD_WEBDAV_SQLITE_PROPS_MOVE \
1068 "UPDATE OR REPLACE properties SET resource = ? WHERE resource = ?"
1070 #define MOD_WEBDAV_SQLITE_PROPS_MOVE_COL \
1071 "UPDATE OR REPLACE properties SET resource = ? || SUBSTR(resource, ?)" \
1072 " WHERE SUBSTR(resource, 1, ?) = ?"
1074 #define MOD_WEBDAV_SQLITE_LOCKS_ACQUIRE \
1075 "INSERT INTO locks" \
1076 " (locktoken,resource,lockscope,locktype,owner,ownerinfo,depth,timeout)" \
1077 " VALUES (?,?,?,?,?,?,?, CURRENT_TIME + ?)"
1079 #define MOD_WEBDAV_SQLITE_LOCKS_REFRESH \
1080 "UPDATE locks SET timeout = CURRENT_TIME + ? WHERE locktoken = ?"
1082 #define MOD_WEBDAV_SQLITE_LOCKS_RELEASE \
1083 "DELETE FROM locks WHERE locktoken = ?"
1085 #define MOD_WEBDAV_SQLITE_LOCKS_READ \
1086 "SELECT resource, owner, depth" \
1087 " FROM locks WHERE locktoken = ?"
1089 #define MOD_WEBDAV_SQLITE_LOCKS_READ_URI \
1091 " locktoken,resource,lockscope,locktype,owner,ownerinfo,depth," \
1092 "timeout - CURRENT_TIME" \
1093 " FROM locks WHERE resource = ?"
1095 #define MOD_WEBDAV_SQLITE_LOCKS_READ_URI_INFINITY \
1097 " locktoken,resource,lockscope,locktype,owner,ownerinfo,depth," \
1098 "timeout - CURRENT_TIME" \
1100 " WHERE depth = -1 AND resource = SUBSTR(?, 1, LENGTH(resource))"
1102 #define MOD_WEBDAV_SQLITE_LOCKS_READ_URI_MEMBERS \
1104 " locktoken,resource,lockscope,locktype,owner,ownerinfo,depth," \
1105 "timeout - CURRENT_TIME" \
1106 " FROM locks WHERE SUBSTR(resource, 1, ?) = ?"
1108 #define MOD_WEBDAV_SQLITE_LOCKS_DELETE_URI \
1109 "DELETE FROM locks WHERE resource = ?"
1111 #define MOD_WEBDAV_SQLITE_LOCKS_DELETE_URI_COL \
1112 "DELETE FROM locks WHERE SUBSTR(resource, 1, ?) = ?"
1113 /*"DELETE FROM locks WHERE locktoken LIKE ? || '%'"*/
1115 /*(not currently used)*/
1116 #define MOD_WEBDAV_SQLITE_LOCKS_DELETE_EXPIRED \
1117 "DELETE FROM locks WHERE timeout < CURRENT_TIME"
1119 #endif /* USE_PROPPATCH */
1124 mod_webdav_sqlite3_init (plugin_config
* const restrict s
,
1125 log_error_st
* const errh
)
1127 #ifndef USE_PROPPATCH
1129 log_error(errh
, __FILE__
, __LINE__
,
1130 "Sorry, no sqlite3 and libxml2 support include, "
1131 "compile with --with-webdav-props");
1133 return HANDLER_ERROR
;
1135 #else /* USE_PROPPATCH */
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; \
1146 sqlite3_free(err); \
1149 sql_config
* const sql
= s
->sql
= (sql_config
*)calloc(1, sizeof(*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
),
1157 ? sqlite3_errmsg(sql
->sqlh
)
1158 : sqlite3_errstr(sqlrc
));
1159 return HANDLER_ERROR
;
1163 MOD_WEBDAV_SQLITE_CREATE_TABLE( MOD_WEBDAV_SQLITE_CREATE_TABLE_PROPERTIES
,
1165 MOD_WEBDAV_SQLITE_CREATE_TABLE( MOD_WEBDAV_SQLITE_CREATE_TABLE_LOCKS
,
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
);
1181 return HANDLER_ERROR
;
1185 sqlite3_close(sql
->sqlh
);
1188 return HANDLER_GO_ON
;
1190 #endif /* USE_PROPPATCH */
1194 #ifdef USE_PROPPATCH
1197 mod_webdav_sqlite3_prep (sql_config
* const restrict sql
,
1198 const buffer
* const sqlite_db_name
,
1199 log_error_st
* const errh
)
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) \
1205 log_error(errh, __FILE__, __LINE__, "sqlite3_prepare_v2(): %s", \
1206 sqlite3_errmsg(sql->sqlh)); \
1207 return HANDLER_ERROR; \
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
),
1216 ? sqlite3_errmsg(sql
->sqlh
)
1217 : sqlite3_errstr(sqlrc
));
1218 return HANDLER_ERROR
;
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 */
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
);
1262 return HANDLER_GO_ON
;
1265 #endif /* USE_PROPPATCH */
1268 SERVER_FUNC(mod_webdav_worker_init
)
1270 #ifdef USE_PROPPATCH
1271 /* open sqlite databases and prepare SQL statements in each worker process
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.
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
)
1283 return HANDLER_ERROR
;
1288 #endif /* USE_PROPPATCH */
1289 return HANDLER_GO_ON
;
1293 #ifdef USE_PROPPATCH
1295 webdav_db_transaction (const plugin_config
* const pconf
,
1296 const char * const action
)
1301 if (SQLITE_OK
== sqlite3_exec(pconf
->sql
->sqlh
, action
, NULL
, NULL
, &err
))
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
);
1314 #define webdav_db_transaction_begin(pconf) \
1315 webdav_db_transaction(pconf, "BEGIN;")
1317 #define webdav_db_transaction_begin_immediate(pconf) \
1318 webdav_db_transaction(pconf, "BEGIN IMMEDIATE;")
1320 #define webdav_db_transaction_commit(pconf) \
1321 webdav_db_transaction(pconf, "COMMIT;")
1323 #define webdav_db_transaction_rollback(pconf) \
1324 webdav_db_transaction(pconf, "ROLLBACK;")
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
1338 webdav_lock_match (const plugin_config
* const pconf
,
1339 const webdav_lockdata
* const lockdata
)
1343 sqlite3_stmt
* const stmt
= pconf
->sql
->stmt_locks_read
;
1348 stmt
, 1, CONST_BUF_LEN(&lockdata
->locktoken
), SQLITE_STATIC
);
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 */
1364 /*(future: might check if owner is a privileged admin user)*/
1365 status
= -3; /* not lock owner; not authorized */
1369 status
= -2; /* URI is not in scope of lock */
1372 sqlite3_reset(stmt
);
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
1387 webdav_lock_activelocks_lockdata (sqlite3_stmt
* const stmt
,
1388 webdav_lockdata
* const lockdata
)
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);
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
;
1414 void webdav_lock_activelocks_cb(void * const vdata
,
1415 const webdav_lockdata
* const lockdata
);
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
,
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
;
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
)
1440 sqlite3_bind_text(stmt
, 1, CONST_BUF_LEN(uri
), SQLITE_STATIC
);
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*/)
1448 webdav_lock_activelocks_lockdata(stmt
, &lockdata
);
1449 if (lockdata
.timeout
> 0)
1450 lock_cb(vdata
, &lockdata
);
1453 sqlite3_reset(stmt
);
1455 if (0 == expand_checks
)
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
;
1462 sqlite3_bind_text(stmt
, 1, CONST_BUF_LEN(uri
), SQLITE_STATIC
);
1464 while (SQLITE_ROW
== sqlite3_step(stmt
)) {
1465 webdav_lock_activelocks_lockdata(stmt
, &lockdata
);
1466 if (lockdata
.timeout
> 0)
1467 lock_cb(vdata
, &lockdata
);
1470 sqlite3_reset(stmt
);
1472 if (1 == expand_checks
)
1476 force_assert(0 != uri
->used
);
1479 /* check for locks on members within (internal to) collection */
1480 stmt
= pconf
->sql
->stmt_locks_read_uri_members
;
1482 sqlite3_bind_int( stmt
, 1, (int)uri
->used
-1);
1483 sqlite3_bind_text(stmt
, 2, CONST_BUF_LEN(uri
), SQLITE_STATIC
);
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*/)
1490 webdav_lock_activelocks_lockdata(stmt
, &lockdata
);
1491 if (lockdata
.timeout
> 0)
1492 lock_cb(vdata
, &lockdata
);
1495 sqlite3_reset(stmt
);
1501 webdav_lock_delete_uri (const plugin_config
* const pconf
,
1502 const buffer
* const uri
)
1508 sqlite3_stmt
* const stmt
= pconf
->sql
->stmt_locks_delete_uri
;
1512 sqlite3_bind_text(stmt
, 1, CONST_BUF_LEN(uri
), SQLITE_STATIC
);
1515 while (SQLITE_DONE
!= sqlite3_step(stmt
)) {
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
));
1524 sqlite3_reset(stmt
);
1537 webdav_lock_delete_uri_col (const plugin_config
* const pconf
,
1538 const buffer
* const uri
)
1544 sqlite3_stmt
* const stmt
= pconf
->sql
->stmt_locks_delete_uri_col
;
1549 force_assert(0 != uri
->used
);
1552 sqlite3_bind_int( stmt
, 1, (int)uri
->used
-1);
1553 sqlite3_bind_text(stmt
, 2, CONST_BUF_LEN(uri
), SQLITE_STATIC
);
1556 while (SQLITE_DONE
!= sqlite3_step(stmt
)) {
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
));
1565 sqlite3_reset(stmt
);
1579 webdav_lock_acquire (const plugin_config
* const pconf
,
1580 const webdav_lockdata
* const lockdata
)
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
1591 sqlite3_stmt
* const stmt
= pconf
->sql
->stmt_locks_acquire
;
1596 stmt
, 1, CONST_BUF_LEN(&lockdata
->locktoken
), SQLITE_STATIC
);
1598 stmt
, 2, CONST_BUF_LEN(&lockdata
->lockroot
), SQLITE_STATIC
);
1600 stmt
, 3, CONST_BUF_LEN(lockdata
->lockscope
), SQLITE_STATIC
);
1602 stmt
, 4, CONST_BUF_LEN(lockdata
->locktype
), SQLITE_STATIC
);
1603 if (lockdata
->owner
->used
)
1605 stmt
, 5, CONST_BUF_LEN(lockdata
->owner
), SQLITE_STATIC
);
1608 stmt
, 5, CONST_STR_LEN(""), SQLITE_STATIC
);
1609 if (lockdata
->ownerinfo
.used
)
1611 stmt
, 6, CONST_BUF_LEN(&lockdata
->ownerinfo
), SQLITE_STATIC
);
1614 stmt
, 6, CONST_STR_LEN(""), SQLITE_STATIC
);
1616 stmt
, 7, lockdata
->depth
);
1618 stmt
, 8, lockdata
->timeout
);
1621 if (SQLITE_DONE
!= sqlite3_step(stmt
)) {
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
));
1630 sqlite3_reset(stmt
);
1639 webdav_lock_refresh (const plugin_config
* const pconf
,
1640 webdav_lockdata
* const lockdata
)
1644 sqlite3_stmt
* const stmt
= pconf
->sql
->stmt_locks_refresh
;
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
);
1653 if (SQLITE_DONE
!= sqlite3_step(stmt
)) {
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
));
1662 sqlite3_reset(stmt
);
1664 /*(future: fill in lockscope, locktype, depth from database)*/
1673 webdav_lock_release (const plugin_config
* const pconf
,
1674 const webdav_lockdata
* const lockdata
)
1678 sqlite3_stmt
* const stmt
= pconf
->sql
->stmt_locks_release
;
1683 stmt
, 1, CONST_BUF_LEN(&lockdata
->locktoken
), SQLITE_STATIC
);
1686 if (SQLITE_DONE
== sqlite3_step(stmt
))
1687 status
= (0 != sqlite3_changes(pconf
->sql
->sqlh
));
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
));
1696 sqlite3_reset(stmt
);
1704 webdav_prop_move_uri (const plugin_config
* const pconf
,
1705 const buffer
* const src
,
1706 const buffer
* const dst
)
1708 #ifdef USE_PROPPATCH
1711 sqlite3_stmt
* const stmt
= pconf
->sql
->stmt_props_move
;
1715 sqlite3_bind_text(stmt
, 1, CONST_BUF_LEN(dst
), SQLITE_STATIC
);
1716 sqlite3_bind_text(stmt
, 2, CONST_BUF_LEN(src
), SQLITE_STATIC
);
1718 if (SQLITE_DONE
!= sqlite3_step(stmt
)) {
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
));
1726 sqlite3_reset(stmt
);
1739 webdav_prop_move_uri_col (const plugin_config
* const pconf
,
1740 const buffer
* const src
,
1741 const buffer
* const dst
)
1743 #ifdef USE_PROPPATCH
1746 sqlite3_stmt
* const stmt
= pconf
->sql
->stmt_props_move_col
;
1751 force_assert(0 != src
->used
);
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
);
1759 if (SQLITE_DONE
!= sqlite3_step(stmt
)) {
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
));
1767 sqlite3_reset(stmt
);
1780 webdav_prop_delete_uri (const plugin_config
* const pconf
,
1781 const buffer
* const uri
)
1783 #ifdef USE_PROPPATCH
1786 sqlite3_stmt
* const stmt
= pconf
->sql
->stmt_props_delete
;
1790 sqlite3_bind_text(stmt
, 1, CONST_BUF_LEN(uri
), SQLITE_STATIC
);
1792 if (SQLITE_DONE
!= sqlite3_step(stmt
)) {
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
));
1800 sqlite3_reset(stmt
);
1812 webdav_prop_copy_uri (const plugin_config
* const pconf
,
1813 const buffer
* const src
,
1814 const buffer
* const dst
)
1816 #ifdef USE_PROPPATCH
1819 sqlite3_stmt
* const stmt
= pconf
->sql
->stmt_props_copy
;
1823 sqlite3_bind_text(stmt
, 1, CONST_BUF_LEN(dst
), SQLITE_STATIC
);
1824 sqlite3_bind_text(stmt
, 2, CONST_BUF_LEN(src
), SQLITE_STATIC
);
1826 if (SQLITE_DONE
!= sqlite3_step(stmt
)) {
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
));
1834 sqlite3_reset(stmt
);
1846 #ifdef USE_PROPPATCH
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
)
1855 sqlite3_stmt
* const stmt
= pconf
->sql
->stmt_props_delete_prop
;
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
);
1863 if (SQLITE_DONE
!= sqlite3_step(stmt
)) {
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
));
1871 sqlite3_reset(stmt
);
1878 #ifdef USE_PROPPATCH
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
)
1888 sqlite3_stmt
* const stmt
= pconf
->sql
->stmt_props_update_prop
;
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
);
1897 if (SQLITE_DONE
!= sqlite3_step(stmt
)) {
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
));
1905 sqlite3_reset(stmt
);
1913 webdav_prop_select_prop (const plugin_config
* const pconf
,
1914 const buffer
* const uri
,
1915 const webdav_property_name
* const prop
,
1918 #ifdef USE_PROPPATCH
1921 sqlite3_stmt
* const stmt
= pconf
->sql
->stmt_props_select_prop
;
1923 return -1; /* not found */
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
);
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 */
1935 sqlite3_reset(stmt
);
1942 return -1; /* not found */
1947 webdav_prop_select_props (const plugin_config
* const pconf
,
1948 const buffer
* const uri
,
1951 #ifdef USE_PROPPATCH
1954 sqlite3_stmt
* const stmt
= pconf
->sql
->stmt_props_select_props
;
1958 sqlite3_bind_text(stmt
, 1, CONST_BUF_LEN(uri
), SQLITE_STATIC
);
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));
1970 sqlite3_reset(stmt
);
1980 webdav_prop_select_propnames (const plugin_config
* const pconf
,
1981 const buffer
* const uri
,
1984 #ifdef USE_PROPPATCH
1987 sqlite3_stmt
* const stmt
= pconf
->sql
->stmt_props_select_propnames
;
1991 /* get all property names (EMPTY) */
1992 sqlite3_bind_text(stmt
, 1, CONST_BUF_LEN(uri
), SQLITE_STATIC
);
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);
2003 sqlite3_reset(stmt
);
2015 #if defined(__APPLE__) && defined(__MACH__)
2016 #include <copyfile.h> /* fcopyfile() *//* OS X 10.5+ */
2018 #ifdef HAVE_ELFTC_COPYFILE/* __FreeBSD__ */
2019 #include <libelftc.h> /* elftc_copyfile() */
2022 #include <sys/sendfile.h> /* sendfile() */
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
2033 __attribute_noinline__
2035 webdav_fcopyfile_sz (int ifd
, int ofd
, off_t isz
)
2041 /* Windows CopyFile() not usable here; operates on filenames, not fds */
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);*/
2048 #if defined(__APPLE__) && defined(__MACH__)
2049 if (0 == fcopyfile(ifd
, ofd
, NULL
, COPYFILE_ALL
))
2052 if (0 != lseek(ifd
, 0, SEEK_SET
)) return -1;
2053 if (0 != lseek(ofd
, 0, SEEK_SET
)) return -1;
2056 #ifdef HAVE_ELFTC_COPYFILE /* __FreeBSD__ */
2057 if (0 == elftc_copyfile(ifd
, ofd
))
2060 if (0 != lseek(ifd
, 0, SEEK_SET
)) return -1;
2061 if (0 != lseek(ofd
, 0, SEEK_SET
)) return -1;
2064 #ifdef __linux__ /* Linux 2.6.33+ sendfile() supports file-to-file copy */
2066 while (offset
< isz
&& sendfile(ifd
,ofd
,&offset
,(size_t)(isz
-offset
)) >= 0);
2070 /*lseek(ifd, 0, SEEK_SET);*/ /*(ifd offset not modified due to &offset arg)*/
2071 if (0 != lseek(ofd
, 0, SEEK_SET
)) return -1;
2074 ssize_t rd
, wr
, off
;
2078 rd
= read(ifd
, buf
, sizeof(buf
));
2079 } while (-1 == rd
&& errno
== EINTR
);
2080 if (rd
< 0) return rd
;
2084 wr
= write(ofd
, buf
+off
, (size_t)(rd
-off
));
2085 } while (wr
>= 0 ? (off
+= wr
) != rd
: errno
== EINTR
);
2086 if (wr
< 0) return -1;
2093 webdav_if_match_or_unmodified_since (connection
* const con
, struct stat
*st
)
2095 buffer
*im
= (0 != con
->etag_flags
)
2096 ? http_header_request_get(con
, HTTP_HEADER_OTHER
,
2097 CONST_STR_LEN("If-Match"))
2100 buffer
*inm
= (0 != con
->etag_flags
)
2101 ? http_header_request_get(con
, HTTP_HEADER_IF_NONE_MATCH
,
2102 CONST_STR_LEN("If-None-Match"))
2106 http_header_request_get(con
, HTTP_HEADER_OTHER
,
2107 CONST_STR_LEN("If-Unmodified-Since"));
2109 if (NULL
== im
&& NULL
== inm
&& NULL
== ius
) return 0;
2113 st
= (0 == lstat(con
->physical
.path
->ptr
, &stp
)) ? &stp
: NULL
;
2115 buffer
*etagb
= con
->physical
.etag
;
2116 if (NULL
!= st
&& (NULL
!= im
|| NULL
!= inm
)) {
2117 etag_create(etagb
, st
, con
->etag_flags
);
2118 etag_mutate(etagb
, etagb
);
2122 if (NULL
== st
|| !etag_is_equal(etagb
, im
->ptr
, 0))
2123 return 412; /* Precondition Failed */
2128 ? !buffer_is_equal_string(inm
,CONST_STR_LEN("*"))
2129 || (errno
!= ENOENT
&& errno
!= ENOTDIR
)
2130 : etag_is_equal(etagb
, inm
->ptr
, 1))
2131 return 412; /* Precondition Failed */
2136 return 412; /* Precondition Failed */
2137 struct tm itm
, *ftm
= gmtime(&st
->st_mtime
);
2138 if (NULL
== strptime(ius
->ptr
, "%a, %d %b %Y %H:%M:%S GMT", &itm
)
2139 || mktime(ftm
) > mktime(&itm
)) { /* timegm() not standard */
2140 return 412; /* Precondition Failed */
2149 webdav_response_etag (server
*srv
, connection
* const con
, struct stat
*st
)
2151 if (0 != con
->etag_flags
) {
2152 buffer
*etagb
= con
->physical
.etag
;
2153 etag_create(etagb
, st
, con
->etag_flags
);
2154 stat_cache_update_entry(srv
,CONST_BUF_LEN(con
->physical
.path
),st
,etagb
);
2155 etag_mutate(etagb
, etagb
);
2156 http_header_response_set(con
, HTTP_HEADER_ETAG
,
2157 CONST_STR_LEN("ETag"),
2158 CONST_BUF_LEN(etagb
));
2164 webdav_parse_Depth (connection
* const con
)
2166 /* Depth = "Depth" ":" ("0" | "1" | "infinity") */
2167 const buffer
* const h
=
2168 http_header_request_get(con
, HTTP_HEADER_OTHER
, CONST_STR_LEN("Depth"));
2170 /* (leading LWS is removed during header parsing in request.c) */
2174 /*case 'i':*/ /* e.g. "infinity" */
2175 /*case 'I':*/ /* e.g. "Infinity" */
2176 default: return -1;/* treat not-'0' and not-'1' as "infinity" */
2180 return -1; /* default value is -1 to represent "infinity" */
2185 webdav_unlinkat (const plugin_config
* const pconf
, const buffer
* const uri
,
2186 const int dfd
, const char * const d_name
, size_t len
)
2188 if (0 == unlinkat(dfd
, d_name
, 0)) {
2189 stat_cache_delete_entry(pconf
->srv
, d_name
, len
);
2190 return webdav_prop_delete_uri(pconf
, uri
);
2194 case EACCES
: case EPERM
: return 403; /* Forbidden */
2195 case ENOENT
: return 404; /* Not Found */
2196 default: return 501; /* Not Implemented */
2202 webdav_delete_file (const plugin_config
* const pconf
,
2203 const physical_st
* const dst
)
2205 if (0 == unlink(dst
->path
->ptr
)) {
2206 stat_cache_delete_entry(pconf
->srv
, CONST_BUF_LEN(dst
->path
));
2207 return webdav_prop_delete_uri(pconf
, dst
->rel_path
);
2211 case EACCES
: case EPERM
: return 403; /* Forbidden */
2212 case ENOENT
: return 404; /* Not Found */
2213 default: return 501; /* Not Implemented */
2219 webdav_delete_dir (const plugin_config
* const pconf
,
2220 physical_st
* const dst
,
2224 int multi_status
= 0;
2225 const int dfd
= fdevent_open_dirname(dst
->path
->ptr
, 0);
2226 DIR * const dir
= (dfd
>= 0) ? fdopendir(dfd
) : NULL
;
2228 if (dfd
>= 0) close(dfd
);
2229 webdav_xml_response_status(b
, dst
->rel_path
, 403);
2233 /* dst is modified in place to extend path,
2234 * so be sure to restore to base each loop iter */
2235 const uint32_t dst_path_used
= dst
->path
->used
;
2236 const uint32_t dst_rel_path_used
= dst
->rel_path
->used
;
2239 while (NULL
!= (de
= readdir(dir
))) {
2240 if (de
->d_name
[0] == '.'
2241 && (de
->d_name
[1] == '\0'
2242 || (de
->d_name
[1] == '.' && de
->d_name
[2] == '\0')))
2243 continue; /* ignore "." and ".." */
2245 #ifdef _DIRENT_HAVE_D_TYPE
2246 if (de
->d_type
!= DT_UNKNOWN
)
2247 s_isdir
= (de
->d_type
== DT_DIR
);
2252 if (0 != fstatat(dfd
, de
->d_name
, &st
, AT_SYMLINK_NOFOLLOW
))
2253 continue; /* file *just* disappeared? */
2254 /* parent rmdir() will fail later if file still exists
2255 * and fstatat() failed for other reasons */
2256 s_isdir
= S_ISDIR(st
.st_mode
);
2259 const uint32_t len
= (uint32_t) _D_EXACT_NAMLEN(de
);
2260 if (flags
& WEBDAV_FLAG_LC_NAMES
) /*(needed at least for rel_path)*/
2261 webdav_str_len_to_lower(de
->d_name
, len
);
2262 buffer_append_string_len(dst
->path
, de
->d_name
, len
);
2263 buffer_append_string_len(dst
->rel_path
, de
->d_name
, len
);
2266 buffer_append_string_len(dst
->path
, CONST_STR_LEN("/"));
2267 buffer_append_string_len(dst
->rel_path
, CONST_STR_LEN("/"));
2268 multi_status
|= webdav_delete_dir(pconf
, dst
, b
, flags
);
2272 webdav_unlinkat(pconf
, dst
->rel_path
, dfd
, de
->d_name
, len
);
2274 webdav_xml_response_status(b
, dst
->rel_path
, status
);
2279 dst
->path
->ptr
[ (dst
->path
->used
= dst_path_used
) -1] = '\0';
2280 dst
->rel_path
->ptr
[(dst
->rel_path
->used
= dst_rel_path_used
)-1] = '\0';
2284 if (0 == multi_status
) {
2286 if (0 == rmdir(dst
->path
->ptr
))
2287 rmdir_status
= webdav_prop_delete_uri(pconf
, dst
->rel_path
);
2291 case EPERM
: rmdir_status
= 403; break; /* Forbidden */
2292 case ENOENT
: rmdir_status
= 404; break; /* Not Found */
2293 default: rmdir_status
= 501; break; /* Not Implemented */
2296 if (0 != rmdir_status
) {
2297 webdav_xml_response_status(b
, dst
->rel_path
, rmdir_status
);
2302 return multi_status
;
2307 webdav_linktmp_rename (const plugin_config
* const pconf
,
2308 const buffer
* const src
,
2309 const buffer
* const dst
)
2311 buffer
* const tmpb
= pconf
->tmpb
;
2312 int rc
= -1; /*(not zero)*/
2314 buffer_copy_buffer(tmpb
, dst
);
2315 buffer_append_string_len(tmpb
, CONST_STR_LEN("."));
2316 buffer_append_int(tmpb
, (long)getpid());
2317 buffer_append_string_len(tmpb
, CONST_STR_LEN("."));
2318 buffer_append_uint_hex_lc(tmpb
, (uintptr_t)pconf
); /*(stack/heap addr)*/
2319 buffer_append_string_len(tmpb
, CONST_STR_LEN("~"));
2320 if (buffer_string_length(tmpb
) < PATH_MAX
2321 && 0 == linkat(AT_FDCWD
, src
->ptr
, AT_FDCWD
, tmpb
->ptr
, 0)) {
2323 rc
= rename(tmpb
->ptr
, dst
->ptr
);
2325 /* unconditionally unlink() src if rename() succeeds, just in case
2326 * dst previously existed and was already hard-linked to src. From
2327 * 'man -s 2 rename':
2328 * If oldpath and newpath are existing hard links referring to the
2329 * same file, then rename() does nothing, and returns a success
2331 * This introduces a small race condition between the rename() and
2332 * unlink() should new file have been created at src in the middle,
2333 * though unlikely if locks are used since locks have not yet been
2342 webdav_copytmp_rename (const plugin_config
* const pconf
,
2343 const physical_st
* const src
,
2344 const physical_st
* const dst
,
2345 const int overwrite
)
2347 buffer
* const tmpb
= pconf
->tmpb
;
2348 buffer_copy_buffer(tmpb
, dst
->path
);
2349 buffer_append_string_len(tmpb
, CONST_STR_LEN("."));
2350 buffer_append_int(tmpb
, (long)getpid());
2351 buffer_append_string_len(tmpb
, CONST_STR_LEN("."));
2352 buffer_append_uint_hex_lc(tmpb
, (uintptr_t)pconf
); /*(stack/heap addr)*/
2353 buffer_append_string_len(tmpb
, CONST_STR_LEN("~"));
2354 if (buffer_string_length(tmpb
) >= PATH_MAX
)
2355 return 414; /* URI Too Long */
2357 /* code does not currently support symlinks in webdav collections;
2358 * disallow symlinks as target when opening src and dst */
2360 const int ifd
= fdevent_open_cloexec(src
->path
->ptr
, 0, O_RDONLY
, 0);
2362 return 403; /* Forbidden */
2363 if (0 != fstat(ifd
, &st
) || !S_ISREG(st
.st_mode
)) {
2365 return 403; /* Forbidden */
2367 const int ofd
= fdevent_open_cloexec(tmpb
->ptr
, 0,
2368 O_WRONLY
| O_CREAT
| O_EXCL
| O_TRUNC
,
2372 return 403; /* Forbidden */
2375 /* perform *blocking* copy (not O_NONBLOCK);
2376 * blocks server from doing any other work until after copy completes
2377 * (should reach here only if unable to use link() and rename()
2378 * due to copy/move crossing device boundaries within the workspace) */
2379 int rc
= webdav_fcopyfile_sz(ifd
, ofd
, st
.st_size
);
2382 const int wc
= close(ofd
);
2384 if (0 != rc
|| 0 != wc
) {
2385 /* error reading or writing files */
2386 rc
= (0 != wc
&& wc
== ENOSPC
) ? 507 : 403;
2391 #ifndef RENAME_NOREPLACE /*(renameat2() not well-supported yet)*/
2394 if (0 == lstat(dst
->path
->ptr
, &stb
) || errno
!= ENOENT
)
2395 return 412; /* Precondition Failed */
2396 /* TOC-TOU race between lstat() and rename(),
2397 * but this is reasonable attempt to not overwrite existing entity */
2399 if (0 == rename(tmpb
->ptr
, dst
->path
->ptr
))
2401 if (0 == renameat2(AT_FDCWD
, tmpb
->ptr
,
2402 AT_FDCWD
, dst
->path
->ptr
,
2403 overwrite
? 0 : RENAME_NOREPLACE
))
2406 /* unconditional stat cache deletion
2407 * (not worth extra syscall/race to detect overwritten or not) */
2408 stat_cache_delete_entry(pconf
->srv
, CONST_BUF_LEN(dst
->path
));
2412 const int errnum
= errno
;
2417 case EISDIR
: return 409; /* Conflict */
2418 case EEXIST
: return 412; /* Precondition Failed */
2419 default: return 403; /* Forbidden */
2426 webdav_copymove_file (const plugin_config
* const pconf
,
2427 const physical_st
* const src
,
2428 const physical_st
* const dst
,
2431 const int overwrite
= (*flags
& WEBDAV_FLAG_OVERWRITE
);
2432 if (*flags
& WEBDAV_FLAG_MOVE_RENAME
) {
2433 #ifndef RENAME_NOREPLACE /*(renameat2() not well-supported yet)*/
2436 if (0 == lstat(dst
->path
->ptr
, &st
) || errno
!= ENOENT
)
2437 return 412; /* Precondition Failed */
2438 /* TOC-TOU race between lstat() and rename(),
2439 * but this is reasonable attempt to not overwrite existing entity*/
2441 if (0 == rename(src
->path
->ptr
, dst
->path
->ptr
))
2443 if (0 == renameat2(AT_FDCWD
, src
->path
->ptr
,
2444 AT_FDCWD
, dst
->path
->ptr
,
2445 overwrite
? 0 : RENAME_NOREPLACE
))
2448 /* unconditionally unlink() src if rename() succeeds, just in case
2449 * dst previously existed and was already hard-linked to src. From
2450 * 'man -s 2 rename':
2451 * If oldpath and newpath are existing hard links referring to the
2452 * same file, then rename() does nothing, and returns a success
2454 * This introduces a small race condition between the rename() and
2455 * unlink() should new file have been created at src in the middle,
2456 * though unlikely if locks are used since locks have not yet been
2458 if (overwrite
) unlink(src
->path
->ptr
);
2459 /* unconditional stat cache deletion
2460 * (not worth extra syscall/race to detect overwritten or not) */
2461 stat_cache_delete_entry(pconf
->srv
, CONST_BUF_LEN(dst
->path
));
2462 stat_cache_delete_entry(pconf
->srv
, CONST_BUF_LEN(src
->path
));
2463 webdav_prop_move_uri(pconf
, src
->rel_path
, dst
->rel_path
);
2466 else if (errno
== EEXIST
)
2467 return 412; /* Precondition Failed */
2469 else if (*flags
& WEBDAV_FLAG_COPY_LINK
) {
2470 if (0 == linkat(AT_FDCWD
, src
->path
->ptr
, AT_FDCWD
, dst
->path
->ptr
, 0)){
2471 webdav_prop_copy_uri(pconf
, src
->rel_path
, dst
->rel_path
);
2474 else if (errno
== EEXIST
) {
2476 return 412; /* Precondition Failed */
2477 if (0 == webdav_linktmp_rename(pconf
, src
->path
, dst
->path
)) {
2478 webdav_prop_copy_uri(pconf
, src
->rel_path
, dst
->rel_path
);
2482 else if (errno
== EXDEV
) {
2483 *flags
&= ~WEBDAV_FLAG_COPY_LINK
;
2484 *flags
|= WEBDAV_FLAG_COPY_XDEV
;
2488 /* link() or rename() failed; fall back to copy to tempfile and rename() */
2489 int status
= webdav_copytmp_rename(pconf
, src
, dst
, overwrite
);
2491 webdav_prop_copy_uri(pconf
, src
->rel_path
, dst
->rel_path
);
2492 if (*flags
& (WEBDAV_FLAG_MOVE_RENAME
|WEBDAV_FLAG_MOVE_XDEV
))
2493 webdav_delete_file(pconf
, src
);
2494 /*(copy successful, but how should we report if delete fails?)*/
2501 webdav_mkdir (const plugin_config
* const pconf
,
2502 const physical_st
* const dst
,
2503 const int overwrite
)
2505 if (0 == mkdir(dst
->path
->ptr
, WEBDAV_DIR_MODE
))
2510 case ENOTDIR
: break;
2511 case ENOENT
: return 409; /* Conflict */
2513 default: return 403; /* Forbidden */
2516 /* [RFC4918] 9.3.1 MKCOL Status Codes
2517 * 405 (Method Not Allowed) -
2518 * MKCOL can only be executed on an unmapped URL.
2520 if (overwrite
< 0) /*(mod_webdav_mkcol() passes overwrite = -1)*/
2521 return (errno
!= ENOTDIR
)
2522 ? 405 /* Method Not Allowed */
2523 : 409; /* Conflict */
2526 force_assert(2 <= dst
->path
->used
);
2527 force_assert(2 <= dst
->rel_path
->used
);
2532 dst
->path
->ptr
[dst
->path
->used
-2] = '\0'; /*(trailing slash)*/
2533 status
= lstat(dst
->path
->ptr
, &st
);
2534 dst
->path
->ptr
[dst
->path
->used
-2] = '/'; /*(restore slash)*/
2535 if (0 != status
) /* still ENOTDIR or *just* disappeared */
2536 return 409; /* Conflict */
2538 if (!overwrite
) /* copying into a non-dir ? */
2539 return 409; /* Conflict */
2541 if (S_ISDIR(st
.st_mode
))
2544 dst
->path
->ptr
[dst
->path
->used
-2] = '\0'; /*(trailing slash)*/
2545 dst
->rel_path
->ptr
[dst
->rel_path
->used
-2] = '\0';
2546 status
= webdav_delete_file(pconf
, dst
);
2547 dst
->path
->ptr
[dst
->path
->used
-2] = '/'; /*(restore slash)*/
2548 dst
->rel_path
->ptr
[dst
->rel_path
->used
-2] = '/';
2552 return (0 == mkdir(dst
->path
->ptr
, WEBDAV_DIR_MODE
))
2554 : 409; /* Conflict */
2559 webdav_copymove_dir (const plugin_config
* const pconf
,
2560 physical_st
* const src
,
2561 physical_st
* const dst
,
2565 /* NOTE: merging collections is NON-CONFORMANT behavior
2566 * (specified in [RFC4918])
2568 * However, merging collections during COPY/MOVE might be expected behavior
2569 * by client, as merging is the behavior of unix cp -r (recursive copy) as
2570 * well as how Microsoft Windows Explorer performs folder copies.
2572 * [RFC4918] 9.8.4 COPY and Overwriting Destination Resources
2573 * When a collection is overwritten, the membership of the destination
2574 * collection after the successful COPY request MUST be the same
2575 * membership as the source collection immediately before the COPY. Thus,
2576 * merging the membership of the source and destination collections
2577 * together in the destination is not a compliant behavior.
2578 * [Ed: strange how non-compliance statement is immediately followed by:]
2579 * In general, if clients require the state of the destination URL to be
2580 * wiped out prior to a COPY (e.g., to force live properties to be reset),
2581 * then the client could send a DELETE to the destination before the COPY
2582 * request to ensure this reset.
2583 * [Ed: if non-compliant merge behavior is the default here, and were it to
2584 * not be desired by client, client could send a DELETE to the destination
2585 * before issuing COPY. There is no easy way to obtain merge behavior
2586 * (were it not the non-compliant default here) unless the client recurses
2587 * into the source and destination, and creates a list of objects that need
2588 * to be copied. This could fail or miss files due to racing with other
2589 * clients. All of this might forget to emphasize that wiping out an
2590 * existing destination collection (a recursive operation) is dangerous and
2591 * would happen if the client set Overwrite: T or omitted setting Overwrite
2592 * since Overwrite: T is default (client must explicitly set Overwrite: F)]
2593 * [RFC4918] 9.9.3 MOVE and the Overwrite Header
2594 * If a resource exists at the destination and the Overwrite header is
2595 * "T", then prior to performing the move, the server MUST perform a
2596 * DELETE with "Depth: infinity" on the destination resource. If the
2597 * Overwrite header is set to "F", then the operation will fail.
2600 /* NOTE: aborting if 507 Insufficient Storage is NON-CONFORMANT behavior
2601 * [RFC4918] specifies that as much as possible of COPY or MOVE
2602 * should be completed.
2605 /* ??? upon encountering errors, should src->rel_path or dst->rel_path
2606 * be used in XML error ??? */
2612 int make_destdir
= 1;
2613 const int overwrite
= (flags
& WEBDAV_FLAG_OVERWRITE
);
2614 if (flags
& WEBDAV_FLAG_MOVE_RENAME
) {
2615 #ifndef RENAME_NOREPLACE /*(renameat2() not well-supported yet)*/
2617 if (0 == lstat(dst
->path
->ptr
, &st
) || errno
!= ENOENT
) {
2618 webdav_xml_response_status(b
, src
->rel_path
, 412);
2619 return 412; /* Precondition Failed */
2621 /* TOC-TOU race between lstat() and rename(),
2622 * but this is reasonable attempt to not overwrite existing entity*/
2624 if (0 == rename(src
->path
->ptr
, dst
->path
->ptr
))
2626 if (0 == renameat2(AT_FDCWD
, src
->path
->ptr
,
2627 AT_FDCWD
, dst
->path
->ptr
,
2628 overwrite
? 0 : RENAME_NOREPLACE
))
2631 webdav_prop_move_uri_col(pconf
, src
->rel_path
, dst
->rel_path
);
2639 webdav_xml_response_status(b
, src
->rel_path
, 412);
2640 return 412; /* Precondition Failed */
2646 webdav_xml_response_status(b
, src
->rel_path
, 409);
2647 return 409; /* Conflict */
2651 force_assert(2 <= dst
->path
->used
);
2654 dst
->path
->ptr
[dst
->path
->used
-2] = '\0'; /*(trailing slash)*/
2655 status
= lstat(dst
->path
->ptr
, &st
);
2656 dst
->path
->ptr
[dst
->path
->used
-2] = '/'; /*(restore slash)*/
2658 if (S_ISDIR(st
.st_mode
)) {
2664 force_assert(2 <= dst
->rel_path
->used
);
2667 dst
->path
->ptr
[dst
->path
->used
-2] = '\0'; /*(remove slash)*/
2668 dst
->rel_path
->ptr
[dst
->rel_path
->used
-2] = '\0';
2669 status
= webdav_delete_file(pconf
, dst
);
2670 dst
->path
->ptr
[dst
->path
->used
-2] = '/'; /*(restore slash)*/
2671 dst
->rel_path
->ptr
[dst
->rel_path
->used
-2] = '/';
2673 webdav_xml_response_status(b
, src
->rel_path
, status
);
2677 if (0 == rename(src
->path
->ptr
, dst
->path
->ptr
)) {
2678 webdav_prop_move_uri_col(pconf
, src
->rel_path
,
2685 flags
&= ~WEBDAV_FLAG_MOVE_RENAME
;
2686 flags
|= WEBDAV_FLAG_MOVE_XDEV
;
2687 /* (if overwrite, then could switch to WEBDAV_FLAG_COPY_XDEV
2688 * and set a flag so that before returning from this routine,
2689 * directory is deleted recursively, instead of deleting each
2690 * file after each copy. Only reliable if overwrite is set
2691 * since if it is not set, an error would leave file copies in
2692 * two places and would be difficult to recover if !overwrite)
2693 * (collections typically do not cross devices, so this is not
2694 * expected to be a common case) */
2703 if (0 != (status
= webdav_mkdir(pconf
, dst
, overwrite
))) {
2704 webdav_xml_response_status(b
, src
->rel_path
, status
);
2709 webdav_prop_copy_uri(pconf
, src
->rel_path
, dst
->rel_path
);
2711 /* copy from src to dst (and, if move, then delete src)
2712 * src and dst are modified in place to extend path,
2713 * so be sure to restore to base each loop iter */
2715 const uint32_t src_path_used
= src
->path
->used
;
2716 const uint32_t src_rel_path_used
= src
->rel_path
->used
;
2717 const uint32_t dst_path_used
= dst
->path
->used
;
2718 const uint32_t dst_rel_path_used
= dst
->rel_path
->used
;
2720 dfd
= fdevent_open_dirname(src
->path
->ptr
, 0);
2721 DIR * const srcdir
= (dfd
>= 0) ? fdopendir(dfd
) : NULL
;
2722 if (NULL
== srcdir
) {
2723 if (dfd
>= 0) close(dfd
);
2724 webdav_xml_response_status(b
, src
->rel_path
, 403);
2725 return 403; /* Forbidden */
2728 int multi_status
= 0;
2730 while (NULL
!= (de
= readdir(srcdir
))) {
2731 if (de
->d_name
[0] == '.'
2732 && (de
->d_name
[1] == '\0'
2733 || (de
->d_name
[1] == '.' && de
->d_name
[2] == '\0')))
2734 continue; /* ignore "." and ".." */
2736 #ifdef _DIRENT_HAVE_D_TYPE
2737 if (de
->d_type
!= DT_UNKNOWN
)
2738 d_type
= DTTOIF(de
->d_type
);
2742 if (0 != fstatat(dfd
, de
->d_name
, &st
, AT_SYMLINK_NOFOLLOW
))
2743 continue; /* file *just* disappeared? */
2744 d_type
= st
.st_mode
;
2747 const uint32_t len
= (uint32_t) _D_EXACT_NAMLEN(de
);
2748 if (flags
& WEBDAV_FLAG_LC_NAMES
) /*(needed at least for rel_path)*/
2749 webdav_str_len_to_lower(de
->d_name
, len
);
2751 buffer_append_string_len(src
->path
, de
->d_name
, len
);
2752 buffer_append_string_len(dst
->path
, de
->d_name
, len
);
2753 buffer_append_string_len(src
->rel_path
, de
->d_name
, len
);
2754 buffer_append_string_len(dst
->rel_path
, de
->d_name
, len
);
2756 if (S_ISDIR(d_type
)) { /* recursive call; depth first */
2757 buffer_append_string_len(src
->path
, CONST_STR_LEN("/"));
2758 buffer_append_string_len(dst
->path
, CONST_STR_LEN("/"));
2759 buffer_append_string_len(src
->rel_path
, CONST_STR_LEN("/"));
2760 buffer_append_string_len(dst
->rel_path
, CONST_STR_LEN("/"));
2761 status
= webdav_copymove_dir(pconf
, src
, dst
, b
, flags
);
2765 else if (S_ISREG(d_type
)) {
2766 status
= webdav_copymove_file(pconf
, src
, dst
, &flags
);
2768 webdav_xml_response_status(b
, src
->rel_path
, status
);
2771 else if (S_ISLNK(d_type
)) {
2772 /*(might entertain support in future, including readlink()
2773 * and changing dst symlink to be relative to new location.
2774 * (or, if absolute to the old location, then absolute to new)
2775 * Be sure to hard-link using linkat() w/o AT_SYMLINK_FOLLOW)*/
2782 src
->path
->ptr
[ (src
->path
->used
= src_path_used
) -1] = '\0';
2783 src
->rel_path
->ptr
[(src
->rel_path
->used
= src_rel_path_used
)-1] = '\0';
2784 dst
->path
->ptr
[ (dst
->path
->used
= dst_path_used
) -1] = '\0';
2785 dst
->rel_path
->ptr
[(dst
->rel_path
->used
= dst_rel_path_used
)-1] = '\0';
2787 if (507 == status
) {
2788 multi_status
= 507; /* Insufficient Storage */
2794 if (0 == multi_status
) {
2795 if (flags
& (WEBDAV_FLAG_MOVE_RENAME
|WEBDAV_FLAG_MOVE_XDEV
)) {
2796 status
= webdav_delete_dir(pconf
, src
, b
, flags
); /* content */
2798 webdav_xml_response_status(b
, src
->rel_path
, status
);
2804 return multi_status
;
2808 typedef struct webdav_propfind_bufs
{
2809 connection
* restrict con
;
2810 const plugin_config
* restrict pconf
;
2811 physical_st
* restrict dst
;
2812 buffer
* restrict b
;
2813 buffer
* restrict b_200
;
2814 buffer
* restrict b_404
;
2815 webdav_property_names proplist
;
2821 } webdav_propfind_bufs
;
2824 enum webdav_live_props_e
{
2825 WEBDAV_PROP_UNSET
= -1 /* (enum value to avoid compiler warning)*/
2826 ,WEBDAV_PROP_ALL
= 0 /* (ALL not really a prop; internal use) */
2827 /*,WEBDAV_PROP_CREATIONDATE*/ /* (located in database, if present) */
2828 /*,WEBDAV_PROP_DISPLAYNAME*/ /* (located in database, if present) */
2829 /*,WEBDAV_PROP_GETCONTENTLANGUAGE*/ /* (located in database, if present) */
2830 ,WEBDAV_PROP_GETCONTENTLENGTH
2831 ,WEBDAV_PROP_GETCONTENTTYPE
2832 ,WEBDAV_PROP_GETETAG
2833 ,WEBDAV_PROP_GETLASTMODIFIED
2834 /*,WEBDAV_PROP_LOCKDISCOVERY*/ /* (located in database, if present) */
2835 ,WEBDAV_PROP_RESOURCETYPE
2836 /*,WEBDAV_PROP_SOURCE*/ /* not implemented; removed in RFC4918 */
2837 ,WEBDAV_PROP_SUPPORTEDLOCK
2841 #ifdef USE_PROPPATCH
2843 struct live_prop_list
{
2846 enum webdav_live_props_e pnum
;
2849 static const struct live_prop_list live_properties
[] = { /*(namespace "DAV:")*/
2850 /* { CONST_STR_LEN("creationdate"), WEBDAV_PROP_CREATIONDATE }*/
2851 /*,{ CONST_STR_LEN("displayname"), WEBDAV_PROP_DISPLAYNAME }*/
2852 /*,{ CONST_STR_LEN("getcontentlanguage"), WEBDAV_PROP_GETCONTENTLANGUAGE}*/
2853 { CONST_STR_LEN("getcontentlength"), WEBDAV_PROP_GETCONTENTLENGTH
}
2854 ,{ CONST_STR_LEN("getcontenttype"), WEBDAV_PROP_GETCONTENTTYPE
}
2855 ,{ CONST_STR_LEN("getetag"), WEBDAV_PROP_GETETAG
}
2856 ,{ CONST_STR_LEN("getlastmodified"), WEBDAV_PROP_GETLASTMODIFIED
}
2858 /*,{ CONST_STR_LEN("lockdiscovery"), WEBDAV_PROP_LOCKDISCOVERY }*/
2860 ,{ CONST_STR_LEN("resourcetype"), WEBDAV_PROP_RESOURCETYPE
}
2861 /*,{ CONST_STR_LEN("source"), WEBDAV_PROP_SOURCE }*/
2863 ,{ CONST_STR_LEN("supportedlock"), WEBDAV_PROP_SUPPORTEDLOCK
}
2866 ,{ NULL
, 0, WEBDAV_PROP_UNSET
}
2869 /* protected live properties
2870 * (must also protect creationdate and lockdiscovery in database) */
2871 static const struct live_prop_list protected_props
[] = { /*(namespace "DAV:")*/
2872 { CONST_STR_LEN("creationdate"), WEBDAV_PROP_UNSET
2873 /*WEBDAV_PROP_CREATIONDATE*/ }
2874 /*,{ CONST_STR_LEN("displayname"), WEBDAV_PROP_DISPLAYNAME }*/
2875 /*,{ CONST_STR_LEN("getcontentlanguage"), WEBDAV_PROP_GETCONTENTLANGUAGE}*/
2876 ,{ CONST_STR_LEN("getcontentlength"), WEBDAV_PROP_GETCONTENTLENGTH
}
2877 ,{ CONST_STR_LEN("getcontenttype"), WEBDAV_PROP_GETCONTENTTYPE
}
2878 ,{ CONST_STR_LEN("getetag"), WEBDAV_PROP_GETETAG
}
2879 ,{ CONST_STR_LEN("getlastmodified"), WEBDAV_PROP_GETLASTMODIFIED
}
2880 ,{ CONST_STR_LEN("lockdiscovery"), WEBDAV_PROP_UNSET
2881 /*WEBDAV_PROP_LOCKDISCOVERY*/ }
2882 ,{ CONST_STR_LEN("resourcetype"), WEBDAV_PROP_RESOURCETYPE
}
2883 /*,{ CONST_STR_LEN("source"), WEBDAV_PROP_SOURCE }*/
2884 ,{ CONST_STR_LEN("supportedlock"), WEBDAV_PROP_SUPPORTEDLOCK
}
2886 ,{ NULL
, 0, WEBDAV_PROP_UNSET
}
2893 webdav_propfind_live_props (const webdav_propfind_bufs
* const restrict pb
,
2894 const enum webdav_live_props_e pnum
)
2896 buffer
* const restrict b
= pb
->b_200
;
2898 case WEBDAV_PROP_ALL
:
2900 /*case WEBDAV_PROP_CREATIONDATE:*/ /* (located in database, if present)*/
2902 case WEBDAV_PROP_CREATIONDATE
: {
2904 * defined by POSIX.1-2008 as last file status change timestamp
2905 * and is no long create-time (as it may have been on older filesystems)
2906 * Therefore, this should return Not Found.
2907 * [RFC4918] 15.1 creationdate Property
2908 * The DAV:creationdate property SHOULD be defined on all DAV
2909 * compliant resources. If present, it contains a timestamp of the
2910 * moment when the resource was created. Servers that are incapable
2911 * of persistently recording the creation date SHOULD instead leave
2912 * it undefined (i.e. report "Not Found").
2913 * (future: might store creationdate in database when PUT creates file
2914 * or LOCK creates empty file, or MKCOL creates collection (dir),
2915 * i.e. wherever the status is 201 Created)
2918 char ctime_buf
[sizeof("2005-08-18T07:27:16Z")];
2919 if (__builtin_expect( (NULL
!= gmtime_r(&pb
->st
.st_ctime
, &tm
)), 1)) {
2920 buffer_append_string_len(b
, CONST_STR_LEN(
2921 "<D:creationdate ns0:dt=\"dateTime.tz\">"));
2922 buffer_append_string_len(b
, ctime_buf
,
2923 strftime(ctime_buf
, sizeof(ctime_buf
),
2924 "%Y-%m-%dT%TZ", &tm
));
2925 buffer_append_string_len(b
, CONST_STR_LEN(
2926 "</D:creationdate>"));
2928 else if (pnum
!= WEBDAV_PROP_ALL
)
2929 return -1; /* invalid; report 'not found' */
2930 if (pnum
!= WEBDAV_PROP_ALL
) return 0;/* found *//*(else fall through)*/
2931 __attribute_fallthrough__
2934 /*case WEBDAV_PROP_DISPLAYNAME:*/ /* (located in database, if present)*/
2935 /*case WEBDAV_PROP_GETCONTENTLANGUAGE:*/ /* (located in db, if present)*/
2937 case WEBDAV_PROP_GETCONTENTLANGUAGE
:
2938 /* [RFC4918] 15.3 getcontentlanguage Property
2939 * SHOULD NOT be protected, so that clients can reset the language.
2941 * The DAV:getcontentlanguage property MUST be defined on any
2942 * DAV-compliant resource that returns the Content-Language header on
2944 * (future: server does not currently set Content-Language and this
2945 * module would need to somehow find out if another module set it)
2947 buffer_append_string_len(b
, CONST_STR_LEN(
2948 "<D:getcontentlanguage>en</D:getcontentlanguage>"));
2949 if (pnum
!= WEBDAV_PROP_ALL
) return 0;/* found *//*(else fall through)*/
2950 __attribute_fallthrough__
2952 case WEBDAV_PROP_GETCONTENTLENGTH
:
2953 buffer_append_string_len(b
, CONST_STR_LEN(
2954 "<D:getcontentlength>"));
2955 buffer_append_int(b
, pb
->st
.st_size
);
2956 buffer_append_string_len(b
, CONST_STR_LEN(
2957 "</D:getcontentlength>"));
2958 if (pnum
!= WEBDAV_PROP_ALL
) return 0;/* found *//*(else fall through)*/
2959 __attribute_fallthrough__
2960 case WEBDAV_PROP_GETCONTENTTYPE
:
2961 /* [RFC4918] 15.5 getcontenttype Property
2962 * Potentially protected if the server prefers to assign content types
2963 * on its own (see also discussion in Section 9.7.1).
2964 * (server currently assigns content types)
2966 * [RFC4918] 15 DAV Properties
2967 * For properties defined based on HTTP GET response headers
2968 * (DAV:get*), the header value could include LWS as defined
2969 * in [RFC2616], Section 4.2. Server implementors SHOULD strip
2970 * LWS from these values before using as WebDAV property
2972 * e.g. application/xml;charset="utf-8"
2973 * instead of: application/xml; charset="utf-8"
2974 * (documentation-only; no check is done here to remove LWS)
2976 if (S_ISDIR(pb
->st
.st_mode
)) {
2977 buffer_append_string_len(b
, CONST_STR_LEN(
2978 "<D:getcontenttype>httpd/unix-directory</D:getcontenttype>"));
2981 /* provide content type by extension
2982 * Note: not currently supporting filesystem xattr */
2984 stat_cache_mimetype_by_ext(pb
->con
, CONST_BUF_LEN(pb
->dst
->path
));
2986 buffer_append_string_len(b
, CONST_STR_LEN(
2987 "<D:getcontenttype>"));
2988 buffer_append_string_buffer(b
, ct
);
2989 buffer_append_string_len(b
, CONST_STR_LEN(
2990 "</D:getcontenttype>"));
2993 if (pnum
!= WEBDAV_PROP_ALL
)
2994 return -1; /* invalid; report 'not found' */
2997 if (pnum
!= WEBDAV_PROP_ALL
) return 0;/* found *//*(else fall through)*/
2998 __attribute_fallthrough__
2999 case WEBDAV_PROP_GETETAG
:
3000 if (0 != pb
->con
->etag_flags
) {
3001 buffer
*etagb
= pb
->con
->physical
.etag
;
3002 etag_create(etagb
, &pb
->st
, pb
->con
->etag_flags
);
3003 etag_mutate(etagb
, etagb
);
3004 buffer_append_string_len(b
, CONST_STR_LEN(
3006 buffer_append_string_buffer(b
, etagb
);
3007 buffer_append_string_len(b
, CONST_STR_LEN(
3009 buffer_clear(etagb
);
3011 else if (pnum
!= WEBDAV_PROP_ALL
)
3012 return -1; /* invalid; report 'not found' */
3013 if (pnum
!= WEBDAV_PROP_ALL
) return 0;/* found *//*(else fall through)*/
3014 __attribute_fallthrough__
3015 case WEBDAV_PROP_GETLASTMODIFIED
:
3017 buffer_append_string_len(b
, CONST_STR_LEN(
3018 "<D:getlastmodified ns0:dt=\"dateTime.rfc1123\">"));
3019 buffer_append_strftime(b
, "%a, %d %b %Y %H:%M:%S GMT",
3020 gmtime(&pb
->st
.st_mtime
));
3021 buffer_append_string_len(b
, CONST_STR_LEN(
3022 "</D:getlastmodified>"));
3024 if (pnum
!= WEBDAV_PROP_ALL
) return 0;/* found *//*(else fall through)*/
3025 __attribute_fallthrough__
3028 case WEBDAV_PROP_LOCKDISCOVERY
:
3029 /* database query for locks occurs in webdav_propfind_resource_props()*/
3030 if (pnum
!= WEBDAV_PROP_ALL
) return 0;/* found *//*(else fall through)*/
3031 __attribute_fallthrough__
3034 case WEBDAV_PROP_RESOURCETYPE
:
3035 if (S_ISDIR(pb
->st
.st_mode
))
3036 buffer_append_string_len(b
, CONST_STR_LEN(
3037 "<D:resourcetype><D:collection/></D:resourcetype>"));
3039 buffer_append_string_len(b
, CONST_STR_LEN(
3040 "<D:resourcetype/>"));
3041 if (pnum
!= WEBDAV_PROP_ALL
) return 0;/* found *//*(else fall through)*/
3042 __attribute_fallthrough__
3043 /*case WEBDAV_PROP_SOURCE:*/ /* not impl; removed in RFC4918 */
3045 case WEBDAV_PROP_SUPPORTEDLOCK
:
3046 buffer_append_string_len(b
, CONST_STR_LEN(
3049 "<D:lockscope><D:exclusive/></D:lockscope>"
3050 "<D:locktype><D:write/></D:locktype>"
3053 "<D:lockscope><D:shared/></D:lockscope>"
3054 "<D:locktype><D:write/></D:locktype>"
3056 "</D:supportedlock>"));
3057 if (pnum
!= WEBDAV_PROP_ALL
) return 0;/* found *//*(else fall through)*/
3058 __attribute_fallthrough__
3060 default: /* WEBDAV_PROP_UNSET */
3061 return -1; /* not found */
3063 return 0; /* found (WEBDAV_PROP_ALL) */
3069 webdav_propfind_lockdiscovery_cb (void * const vdata
,
3070 const webdav_lockdata
* const lockdata
)
3072 webdav_xml_activelock((buffer
*)vdata
, lockdata
, NULL
, 0);
3078 webdav_propfind_resource_props (const webdav_propfind_bufs
* const restrict pb
)
3080 const webdav_property_names
* const props
= &pb
->proplist
;
3081 if (props
->used
) { /* "props" or "allprop"+"include" */
3082 const webdav_property_name
*prop
= props
->ptr
;
3083 for (int i
= 0; i
< props
->used
; ++i
, ++prop
) {
3084 if (NULL
== prop
->name
/*(flag indicating prop is live prop enum)*/
3085 ? 0 == webdav_propfind_live_props(pb
, (enum webdav_live_props_e
)
3087 : 0 == webdav_prop_select_prop(pb
->pconf
, pb
->dst
->rel_path
,
3091 /*(error obtaining prop if reached)*/
3092 webdav_xml_prop(pb
->b_404
, prop
, NULL
, 0);
3097 webdav_propfind_live_props(pb
, WEBDAV_PROP_ALL
);
3098 webdav_prop_select_props(pb
->pconf
, pb
->dst
->rel_path
, pb
->b_200
);
3102 if (pb
->lockdiscovery
) {
3103 /* pb->lockdiscovery > 0:
3104 * report locks resource or containing (parent) collections
3105 * pb->lockdiscovery < 0:
3106 * report only those locks on specific resource
3107 * While this is not compliant with RFC, it may reduces quite a bit of
3108 * redundancy for propfind on Depth: 1 and Depth: infinity when there
3109 * are locks on parent collections. The client receiving this propfind
3110 * XML response should easily know that locks on collections apply to
3111 * the members of those collections and to further nested collections
3113 * future: might be many, many fewer database queries if make a single
3114 * query for the locks in the collection directory tree and parse the
3115 * results, rather than querying the database for each resource */
3116 buffer_append_string_len(pb
->b_200
, CONST_STR_LEN(
3117 "<D:lockdiscovery>"));
3118 webdav_lock_activelocks(pb
->pconf
, pb
->dst
->rel_path
,
3119 (pb
->lockdiscovery
> 0),
3120 webdav_propfind_lockdiscovery_cb
, pb
->b_200
);
3121 buffer_append_string_len(pb
->b_200
, CONST_STR_LEN(
3122 "</D:lockdiscovery>"));
3129 webdav_propfind_resource_propnames (const webdav_propfind_bufs
*
3132 static const char live_propnames
[] =
3133 "<getcontentlength/>\n"
3134 "<getcontenttype/>\n"
3136 "<getlastmodified/>\n"
3139 "<supportedlock/>\n"
3140 "<lockdiscovery/>\n"
3143 /* list live_properties which are not in database, plus "lockdiscovery" */
3144 buffer_append_string_len(pb
->b_200
,live_propnames
,sizeof(live_propnames
)-1);
3146 /* list properties in database 'properties' table for resource */
3147 webdav_prop_select_propnames(pb
->pconf
, pb
->dst
->rel_path
, pb
->b_200
);
3153 webdav_propfind_resource_403 (const webdav_propfind_bufs
* const restrict pb
)
3155 buffer
* const restrict b
= pb
->b
;
3156 buffer_append_string_len(b
, CONST_STR_LEN(
3158 webdav_xml_href(b
, pb
->dst
->rel_path
);
3159 buffer_append_string_len(b
, CONST_STR_LEN(
3161 webdav_xml_status(b
, 403); /* Forbidden */
3162 buffer_append_string_len(b
, CONST_STR_LEN(
3164 "</D:response>\n"));
3169 webdav_propfind_resource (const webdav_propfind_bufs
* const restrict pb
)
3171 buffer_clear(pb
->b_200
);
3172 buffer_clear(pb
->b_404
);
3175 webdav_propfind_resource_props(pb
);
3177 webdav_propfind_resource_propnames(pb
);
3179 /* buffer could get very large for large directory (or Depth: infinity)
3180 * attempt to allocate in 8K chunks, rather than default realloc in
3181 * 64-byte chunks (see buffer.h) which will lead to exponentially more
3182 * expensive copy behavior as buffer is resized over and over and over
3184 * future: avoid (potential) excessive memory usage by accumulating output
3187 buffer
* const restrict b
= pb
->b
;
3188 buffer
* const restrict b_200
= pb
->b_200
;
3189 buffer
* const restrict b_404
= pb
->b_404
;
3190 if (b
->size
- b
->used
< b_200
->used
+ b_404
->used
+ 1024) {
3191 size_t sz
= b
->used
+ BUFFER_MAX_REUSE_SIZE
3192 + b_200
->used
+ b_404
->used
+ 1024;
3193 /*(optimization; buffer is extended as needed)*/
3194 buffer_string_prepare_append(b
, sz
& (BUFFER_MAX_REUSE_SIZE
-1));
3197 buffer_append_string_len(b
, CONST_STR_LEN(
3199 webdav_xml_href(b
, pb
->dst
->rel_path
);
3200 if (b_200
->used
> 1) /* !unset and !blank */
3201 webdav_xml_propstat(b
, b_200
, 200);
3202 if (b_404
->used
> 1) /* !unset and !blank */
3203 webdav_xml_propstat(b
, b_404
, 404);
3204 buffer_append_string_len(b
, CONST_STR_LEN(
3205 "</D:response>\n"));
3210 webdav_propfind_dir (webdav_propfind_bufs
* const restrict pb
)
3212 const physical_st
* const dst
= pb
->dst
;
3213 const int dfd
= fdevent_open_dirname(dst
->path
->ptr
, 0);
3214 DIR * const dir
= (dfd
>= 0) ? fdopendir(dfd
) : NULL
;
3217 if (dfd
>= 0) close(dfd
);
3218 if (errnum
!= ENOENT
)
3219 webdav_propfind_resource_403(pb
); /* Forbidden */
3223 webdav_propfind_resource(pb
);
3225 if (pb
->lockdiscovery
> 0)
3226 pb
->lockdiscovery
= -pb
->lockdiscovery
; /*(check locks on node only)*/
3228 /* dst is modified in place to extend path,
3229 * so be sure to restore to base each loop iter */
3230 const uint32_t dst_path_used
= dst
->path
->used
;
3231 const uint32_t dst_rel_path_used
= dst
->rel_path
->used
;
3233 (pb
->con
->conf
.force_lowercase_filenames
? WEBDAV_FLAG_LC_NAMES
: 0);
3235 while (NULL
!= (de
= readdir(dir
))) {
3236 if (de
->d_name
[0] == '.'
3237 && (de
->d_name
[1] == '\0'
3238 || (de
->d_name
[1] == '.' && de
->d_name
[2] == '\0')))
3239 continue; /* ignore "." and ".." */
3241 if (0 != fstatat(dfd
, de
->d_name
, &pb
->st
, AT_SYMLINK_NOFOLLOW
))
3242 continue; /* file *just* disappeared? */
3244 const uint32_t len
= (uint32_t) _D_EXACT_NAMLEN(de
);
3245 if (flags
& WEBDAV_FLAG_LC_NAMES
) /*(needed by rel_path)*/
3246 webdav_str_len_to_lower(de
->d_name
, len
);
3247 buffer_append_string_len(dst
->path
, de
->d_name
, len
);
3248 buffer_append_string_len(dst
->rel_path
, de
->d_name
, len
);
3249 if (S_ISDIR(pb
->st
.st_mode
)) {
3250 buffer_append_string_len(dst
->path
, CONST_STR_LEN("/"));
3251 buffer_append_string_len(dst
->rel_path
, CONST_STR_LEN("/"));
3254 if (S_ISDIR(pb
->st
.st_mode
) && -1 == pb
->depth
)
3255 webdav_propfind_dir(pb
); /* recurse */
3257 webdav_propfind_resource(pb
);
3259 dst
->path
->ptr
[ (dst
->path
->used
= dst_path_used
) -1] = '\0';
3260 dst
->rel_path
->ptr
[(dst
->rel_path
->used
= dst_rel_path_used
)-1] = '\0';
3267 webdav_open_chunk_file_rd (chunk
* const c
)
3269 if (c
->file
.fd
< 0) /* open file if not already open *//*permit symlink*/
3270 c
->file
.fd
= fdevent_open_cloexec(c
->mem
->ptr
, 1, O_RDONLY
, 0);
3276 webdav_mmap_file_rd (void ** const addr
, const size_t length
,
3277 const int fd
, const off_t offset
)
3279 /*(caller must ensure offset is properly aligned to mmap requirements)*/
3282 *addr
= NULL
; /*(something other than MAP_FAILED)*/
3288 *addr
= mmap(NULL
, length
, PROT_READ
, MAP_SHARED
, fd
, offset
);
3289 if (*addr
== MAP_FAILED
&& errno
== EINVAL
)
3290 *addr
= mmap(NULL
, length
, PROT_READ
, MAP_PRIVATE
, fd
, offset
);
3291 return (*addr
!= MAP_FAILED
? 0 : -1);
3302 webdav_mmap_file_chunk (chunk
* const c
)
3304 /*(request body provided in temporary file, so ok to mmap().
3305 * Otherwise, must check defined(ENABLE_MMAP)) */
3306 /* chunk_reset() or chunk_free() will clean up mmap'd chunk */
3307 /* close c->file.fd only faster mmap() succeeds, since it will not
3308 * be able to be re-opened if it was a tmpfile that was unlinked */
3309 /*assert(c->type == FILE_CHUNK);*/
3310 if (MAP_FAILED
!= c
->file
.mmap
.start
)
3311 return c
->file
.mmap
.start
+ c
->offset
;
3313 if (webdav_open_chunk_file_rd(c
) < 0)
3316 webdav_mmap_file_rd((void **)&c
->file
.mmap
.start
, (size_t)c
->file
.length
,
3319 if (MAP_FAILED
== c
->file
.mmap
.start
)
3324 c
->file
.mmap
.length
= c
->file
.length
;
3325 return c
->file
.mmap
.start
+ c
->offset
;
3329 #if defined(USE_PROPPATCH) || defined(USE_LOCKS)
3330 __attribute_noinline__
3332 webdav_parse_chunkqueue (connection
* const con
,
3333 const plugin_config
* const pconf
)
3335 /* read the chunks in to the XML document */
3336 xmlParserCtxtPtr ctxt
= xmlCreatePushParserCtxt(NULL
, NULL
, NULL
, 0, NULL
);
3337 /* XXX: evaluate adding more xmlParserOptions */
3338 xmlCtxtUseOptions(ctxt
, XML_PARSE_NOERROR
| XML_PARSE_NOWARNING
3339 | XML_PARSE_PEDANTIC
| XML_PARSE_NONET
);
3341 chunkqueue
* const cq
= con
->request_content_queue
;
3342 size_t weWant
= cq
->bytes_in
- cq
->bytes_out
;
3343 int err
= XML_ERR_OK
;
3347 chunk
*c
= cq
->first
;
3350 force_assert(0 == weWant
|| c
!= NULL
);
3353 if (c
->type
== MEM_CHUNK
) {
3354 xmlstr
= c
->mem
->ptr
+ c
->offset
;
3355 weHave
= buffer_string_length(c
->mem
) - c
->offset
;
3357 else if (c
->type
== FILE_CHUNK
) {
3358 xmlstr
= webdav_mmap_file_chunk(c
);
3359 /*xmlstr = c->file.mmap.start + c->offset;*/
3360 if (NULL
!= xmlstr
) {
3361 weHave
= c
->file
.length
- c
->offset
;
3365 case ENOSYS
: case ENODEV
: case EINVAL
: break;
3367 log_perror(con
->errh
, __FILE__
, __LINE__
,
3368 "open() or mmap() '%*.s'",
3369 BUFFER_INTLEN_PTR(c
->mem
));
3371 if (webdav_open_chunk_file_rd(c
) < 0) {
3372 log_perror(con
->errh
, __FILE__
, __LINE__
,
3374 BUFFER_INTLEN_PTR(c
->mem
));
3375 err
= XML_IO_UNKNOWN
;
3380 if (-1 ==lseek(c
->file
.fd
,c
->file
.start
+c
->offset
,SEEK_SET
))
3382 off_t len
= c
->file
.length
- c
->offset
;
3383 if (len
> (off_t
)sizeof(buf
)) len
= (off_t
)sizeof(buf
);
3384 rd
= read(c
->file
.fd
, buf
, (size_t)len
);
3385 } while (-1 == rd
&& errno
== EINTR
);
3388 weHave
= (size_t)rd
;
3391 log_perror(con
->errh
, __FILE__
, __LINE__
,
3393 BUFFER_INTLEN_PTR(c
->mem
));
3394 err
= XML_IO_UNKNOWN
;
3400 log_error(con
->errh
, __FILE__
, __LINE__
,
3401 "unrecognized chunk type: %d", c
->type
);
3402 err
= XML_IO_UNKNOWN
;
3406 if (weHave
> weWant
) weHave
= weWant
;
3409 log_error(con
->errh
, __FILE__
, __LINE__
,
3410 "XML-request-body: %.*s", (int)weHave
, xmlstr
);
3412 if (XML_ERR_OK
!= (err
= xmlParseChunk(ctxt
, xmlstr
, weHave
, 0))) {
3413 log_error(con
->errh
, __FILE__
, __LINE__
,
3414 "xmlParseChunk failed at: %lld %zu %d",
3415 (long long int)cq
->bytes_out
, weHave
, err
);
3420 chunkqueue_mark_written(cq
, weHave
);
3421 chunkqueue_remove_finished_chunks(cq
);
3424 if (XML_ERR_OK
== err
) {
3425 switch ((err
= xmlParseChunk(ctxt
, 0, 0, 1))) {
3426 case XML_ERR_DOCUMENT_END
:
3428 if (ctxt
->wellFormed
) {
3429 xmlDoc
* const xml
= ctxt
->myDoc
;
3430 xmlFreeParserCtxt(ctxt
);
3435 log_error(con
->errh
, __FILE__
, __LINE__
,
3436 "xmlParseChunk failed at final packet: %d", err
);
3441 xmlFreeDoc(ctxt
->myDoc
);
3442 xmlFreeParserCtxt(ctxt
);
3450 struct webdav_lock_token_submitted_st
{
3454 const buffer
*authn_user
;
3463 webdav_lock_token_submitted_cb (void * const vdata
,
3464 const webdav_lockdata
* const lockdata
)
3466 /* RFE: improve support for shared locks
3467 * (instead of treating match of any shared lock as sufficient,
3468 * even when there are different lockroots)
3469 * keep track of matched shared locks and unmatched shared locks and
3470 * ensure that each lockroot with shared locks has at least one match
3471 * (Will need to allocate strings for each URI with shared lock and keep
3472 * track whether or not a shared lock has been matched for that URI.
3473 * After walking all locks, must walk looking for unmatched URIs,
3474 * and must free these strings) */
3476 /* [RFC4918] 6.4 Lock Creator and Privileges
3477 * When a locked resource is modified, a server MUST check that the
3478 * authenticated principal matches the lock creator (in addition to
3479 * checking for valid lock token submission).
3482 struct webdav_lock_token_submitted_st
* const cbdata
=
3483 (struct webdav_lock_token_submitted_st
*)vdata
;
3484 const buffer
* const locktoken
= &lockdata
->locktoken
;
3485 const int shared
= (lockdata
->lockscope
->used
!= sizeof("exclusive"));
3488 if (shared
) ++cbdata
->slocks
;
3490 for (int i
= 0; i
< cbdata
->used
; ++i
) {
3491 const buffer
* const token
= &cbdata
->tokens
[i
];
3492 /* locktoken match (locktoken not '\0' terminated) */
3493 if (buffer_is_equal_string(token
, CONST_BUF_LEN(locktoken
))) {
3494 /*(0 length owner if no auth required to lock; not recommended)*/
3495 if (buffer_string_is_empty(lockdata
->owner
)/*no lock owner;match*/
3496 || buffer_is_equal_string(cbdata
->authn_user
,
3497 CONST_BUF_LEN(lockdata
->owner
))) {
3498 if (shared
) ++cbdata
->smatch
;
3499 return; /* authenticated lock owner match */
3504 /* no match with lock tokens in request */
3506 webdav_xml_href(cbdata
->b
, &lockdata
->lockroot
);
3511 * check if request provides necessary locks to access the resource
3514 webdav_has_lock (connection
* const con
,
3515 const plugin_config
* const pconf
,
3516 const buffer
* const uri
)
3518 /* Note with regard to exclusive locks on collections: client should not be
3519 * able to obtain an exclusive lock on a collection if there are existing
3520 * locks on resource members inside the collection. Therefore, there is no
3521 * need to check here for locks on resource members inside collections.
3522 * (This ignores the possibility that an admin or some other privileged
3523 * or out-of-band process has added locks in spite of lock on collection.)
3524 * Revisit to properly support shared locks. */
3526 struct webdav_lock_token_submitted_st cbdata
;
3527 cbdata
.b
= buffer_init();
3528 cbdata
.tokens
= NULL
;
3535 /* XXX: maybe add config switch to require that authentication occurred? */
3536 buffer owner
= { NULL
, 0, 0 };/*owner (not authenticated)(auth_user unset)*/
3537 data_string
* const authn_user
= (data_string
*)
3538 array_get_element_klen(con
->environment
,
3539 CONST_STR_LEN("REMOTE_USER"));
3540 cbdata
.authn_user
= authn_user
? authn_user
->value
: &owner
;
3542 const buffer
* const h
=
3543 http_header_request_get(con
, HTTP_HEADER_OTHER
, CONST_STR_LEN("If"));
3545 if (!buffer_is_empty(h
)) {
3546 /* parse "If" request header for submitted lock tokens
3547 * While the below not a pedantic, validating parse, if the header
3548 * is non-conformant or contains unencoded characters, the result
3549 * will be misidentified or ignored lock tokens, which will result
3550 * in fail closed -- secure default behavior -- if those lock
3551 * tokens are required. It is highly unlikely that misparsing the "If"
3552 * request header will result in a valid lock token since lock tokens
3553 * should be unique, and opaquelocktoken should be globally unique */
3557 while (*p
== ' ' || *p
== '\t') ++p
;
3558 if (*p
== '<') { /* Resource-Tag */
3559 do { ++p
; } while (*p
!= '>' && *p
!= '\0');
3560 if (*p
== '\0') break;
3561 do { ++p
; } while (*p
== ' ' || *p
== '\t');
3565 while (*p
!= '(' && *p
!= '\0') ++p
;
3567 /* begin List in No-tag-list or Tagged-list
3568 * List = "(" 1*Condition ")"
3569 * Condition = ["Not"] (State-token | "[" entity-tag "]")
3572 while (*p
!= '\0' && *++p
!= ')') {
3573 while (*p
== ' ' || *p
== '\t') ++p
;
3574 if ( (p
[0] & 0xdf) == 'N'
3575 && (p
[1] & 0xdf) == 'O'
3576 && (p
[2] & 0xdf) == 'T') {
3579 while (*p
== ' ' || *p
== '\t') ++p
;
3581 if (*p
!= '<') { /* '<' begins State-token (Coded-URL) */
3582 if (*p
!= '[') break; /* invalid syntax */
3583 /* '[' and ']' wrap entity-tag */
3585 do { ++p
; } while (*p
!= ']' && *p
!= '\0');
3586 if (*p
!= ']') break; /* invalid syntax */
3587 if (p
== etag
) continue; /* ignore entity-tag if empty */
3588 if (notflag
) continue;/* ignore entity-tag in NOT context */
3589 if (0 == con
->etag_flags
) continue; /* ignore entity-tag */
3591 if (0 != lstat(con
->physical
.path
->ptr
, &st
)) {
3592 http_status_set_error(con
,412);/* Precondition Failed */
3595 if (S_ISDIR(st
.st_mode
)) continue;/*we ignore etag if dir*/
3596 buffer
*etagb
= con
->physical
.etag
;
3597 etag_create(etagb
, &st
, con
->etag_flags
);
3598 etag_mutate(etagb
, etagb
);
3600 int ematch
= etag_is_equal(etagb
, etag
, 0);
3603 http_status_set_error(con
,412);/* Precondition Failed */
3610 && 0 == strncmp(p
, "<DAV:no-lock>",
3611 sizeof("<DAV:no-lock>")-1)) {
3613 http_status_set_error(con
,412);/* Precondition Failed */
3616 p
+= sizeof("<DAV:no-lock>")-2; /* point p to '>' */
3620 /* State-token (Coded-URL)
3621 * Coded-URL = "<" absolute-URI ">"
3622 * ; No linear whitespace (LWS) allowed in Coded-URL
3623 * ; absolute-URI defined in RFC 3986, Section 4.3
3625 if (cbdata
.size
== cbdata
.used
) {
3626 if (cbdata
.size
== 16) { /* arbitrary limit */
3627 http_status_set_error(con
, 400); /* Bad Request */
3631 realloc(cbdata
.tokens
, sizeof(*(cbdata
.tokens
)) * 16);
3632 force_assert(cbdata
.tokens
); /*(see above limit)*/
3634 cbdata
.tokens
[cbdata
.used
].ptr
= p
+1;
3636 do { ++p
; } while (*p
!= '>' && *p
!= '\0');
3637 if (*p
== '\0') break; /* (*p != '>') */
3639 cbdata
.tokens
[cbdata
.used
].used
=
3640 (uint32_t)(p
- cbdata
.tokens
[cbdata
.used
].ptr
+ 1);
3643 } while (*p
++ == ')'); /* end of List in No-tag-list or Tagged-list */
3646 webdav_lock_activelocks(pconf
, uri
, 1,
3647 webdav_lock_token_submitted_cb
, &cbdata
);
3649 if (NULL
!= cbdata
.tokens
)
3650 free(cbdata
.tokens
);
3654 if (0 != cbdata
.b
->used
)
3656 else if (0 == cbdata
.nlocks
) { /* resource is not locked at all */
3657 /* error if lock provided on source and no locks present on source;
3658 * not error if no locks on Destination, but "If" provided for source */
3659 if (cbdata
.used
&& uri
== con
->physical
.rel_path
) {
3661 http_status_set_error(con
, 412); /* Precondition Failed */
3663 #if 0 /*(treat no locks as if caller is holding an appropriate lock)*/
3666 webdav_xml_href(cbdata
.b
, uri
);
3671 /*(XXX: overly simplistic shared lock matching allows any match of shared
3672 * locks even when there are shared locks on multiple different lockroots.
3673 * Failure is misreported since unmatched shared locks are not added to
3675 if (cbdata
.slocks
&& !cbdata
.smatch
)
3679 webdav_xml_doc_error_lock_token_submitted(con
, cbdata
.b
);
3681 buffer_free(cbdata
.b
);
3683 return (has_lock
> 0);
3686 #else /* ! defined(USE_LOCKS) */
3688 #define webdav_has_lock(con, pconf, uri) 1
3690 #endif /* ! defined(USE_LOCKS) */
3694 mod_webdav_propfind (connection
* const con
, const plugin_config
* const pconf
)
3696 if (con
->request
.content_length
) {
3697 #ifdef USE_PROPPATCH
3698 if (con
->state
== CON_STATE_READ_POST
) {
3699 handler_t rc
= connection_handle_read_post_state(pconf
->srv
, con
);
3700 if (rc
!= HANDLER_GO_ON
) return rc
;
3703 /* PROPFIND is idempotent and safe, so even if parsing XML input is not
3704 * supported, live properties can still be produced, so treat as allprop
3705 * request. NOTE: this behavior is NOT RFC CONFORMANT (and, well, if
3706 * compiled without XML support, this WebDAV implementation is already
3707 * non-compliant since it is missing support for XML request body).
3708 * RFC-compliant behavior would reject an ignored request body with
3709 * 415 Unsupported Media Type */
3711 http_status_set_error(con
, 415); /* Unsupported Media Type */
3712 return HANDLER_FINISHED
;
3717 webdav_propfind_bufs pb
;
3719 /* [RFC4918] 9.1 PROPFIND Method
3720 * Servers MUST support "0" and "1" depth requests on WebDAV-compliant
3721 * resources and SHOULD support "infinity" requests. In practice, support
3722 * for infinite-depth requests MAY be disabled, due to the performance and
3723 * security concerns associated with this behavior. Servers SHOULD treat
3724 * a request without a Depth header as if a "Depth: infinity" header was
3729 pb
.lockdiscovery
= 0;
3730 pb
.depth
= webdav_parse_Depth(con
);
3732 /* future: might add config option to enable Depth: infinity
3733 * (Depth: infinity is supported if this rejection is removed) */
3734 if (-1 == pb
.depth
) {
3735 webdav_xml_doc_error_propfind_finite_depth(con
);
3736 return HANDLER_FINISHED
;
3739 if (0 != lstat(con
->physical
.path
->ptr
, &pb
.st
)) {
3740 http_status_set_error(con
, (errno
== ENOENT
) ? 404 : 403);
3741 return HANDLER_FINISHED
;
3743 else if (S_ISDIR(pb
.st
.st_mode
)) {
3744 if (con
->physical
.path
->ptr
[con
->physical
.path
->used
- 2] != '/') {
3745 buffer
*vb
= http_header_request_get(con
, HTTP_HEADER_OTHER
,
3746 CONST_STR_LEN("User-Agent"));
3747 if (vb
&& 0 == strncmp(vb
->ptr
, "Microsoft-WebDAV-MiniRedir/",
3748 sizeof("Microsoft-WebDAV-MiniRedir/")-1)) {
3749 /* workaround Microsoft-WebDAV-MiniRedir bug */
3750 http_response_redirect_to_directory(pconf
->srv
, con
, 308);
3751 return HANDLER_FINISHED
;
3753 /* set "Content-Location" instead of sending 308 redirect to dir */
3754 if (!http_response_redirect_to_directory(pconf
->srv
, con
, 0))
3755 return HANDLER_FINISHED
;
3756 buffer_append_string_len(con
->physical
.path
, CONST_STR_LEN("/"));
3757 buffer_append_string_len(con
->physical
.rel_path
,CONST_STR_LEN("/"));
3760 else if (con
->physical
.path
->ptr
[con
->physical
.path
->used
- 2] == '/') {
3761 http_status_set_error(con
, 403);
3762 return HANDLER_FINISHED
;
3764 else if (0 != pb
.depth
) {
3765 http_status_set_error(con
, 403);
3766 return HANDLER_FINISHED
;
3769 pb
.proplist
.ptr
= NULL
;
3770 pb
.proplist
.used
= 0;
3771 pb
.proplist
.size
= 0;
3773 #ifdef USE_PROPPATCH
3774 xmlDocPtr xml
= NULL
;
3775 const xmlNode
*rootnode
= NULL
;
3776 if (con
->request
.content_length
) {
3777 if (NULL
== (xml
= webdav_parse_chunkqueue(con
, pconf
))) {
3778 http_status_set_error(con
, 400); /* Bad Request */
3779 return HANDLER_FINISHED
;
3781 rootnode
= xmlDocGetRootElement(xml
);
3784 if (NULL
!= rootnode
3785 && 0 == webdav_xmlstrcmp_fixed(rootnode
->name
, "propfind")) {
3786 for (const xmlNode
*cmd
= rootnode
->children
; cmd
; cmd
= cmd
->next
) {
3787 if (0 == webdav_xmlstrcmp_fixed(cmd
->name
, "allprop"))
3788 pb
.allprop
= pb
.lockdiscovery
= 1;
3789 else if (0 == webdav_xmlstrcmp_fixed(cmd
->name
, "propname"))
3791 else if (0 != webdav_xmlstrcmp_fixed(cmd
->name
, "prop")
3792 && 0 != webdav_xmlstrcmp_fixed(cmd
->name
, "include"))
3795 /* "prop" or "include": get prop by name */
3796 for (const xmlNode
*prop
= cmd
->children
; prop
; prop
= prop
->next
) {
3797 if (prop
->type
== XML_TEXT_NODE
)
3798 continue; /* ignore WS */
3800 if (prop
->ns
&& '\0' == *(char *)prop
->ns
->href
3801 && '\0' != *(char *)prop
->ns
->prefix
) {
3802 log_error(con
->errh
, __FILE__
, __LINE__
,
3803 "no name space for: %s", prop
->name
);
3804 /* 422 Unprocessable Entity */
3805 http_status_set_error(con
, 422);
3806 free(pb
.proplist
.ptr
);
3808 return HANDLER_FINISHED
;
3811 /* add property to requested list */
3812 if (pb
.proplist
.size
== pb
.proplist
.used
) {
3813 if (pb
.proplist
.size
== 32) {
3814 /* arbitrarily chosen limit of 32 */
3815 log_error(con
->errh
, __FILE__
, __LINE__
,
3816 "too many properties in request (> 32)");
3817 http_status_set_error(con
, 400); /* Bad Request */
3818 free(pb
.proplist
.ptr
);
3820 return HANDLER_FINISHED
;
3823 realloc(pb
.proplist
.ptr
, sizeof(*(pb
.proplist
.ptr
)) * 32);
3824 force_assert(pb
.proplist
.ptr
); /*(see above limit)*/
3827 const size_t namelen
= strlen((char *)prop
->name
);
3828 if (prop
->ns
&& 0 == strcmp((char *)prop
->ns
->href
, "DAV:")) {
3829 if (namelen
== sizeof("lockdiscovery")-1
3830 && 0 == memcmp(prop
->name
,
3831 CONST_STR_LEN("lockdiscovery"))) {
3832 pb
.lockdiscovery
= 1;
3835 const struct live_prop_list
*list
= live_properties
;
3836 while (0 != list
->len
3837 && (list
->len
!= namelen
3838 || 0 != memcmp(prop
->name
,list
->prop
,list
->len
)))
3840 if (NULL
!= list
->prop
) {
3841 if (cmd
->name
[0] == 'p') { /* "prop", not "include" */
3842 pb
.proplist
.ptr
[pb
.proplist
.used
].name
= NULL
;
3843 pb
.proplist
.ptr
[pb
.proplist
.used
].namelen
=
3846 } /* (else skip; will already be part of allprop) */
3849 if (cmd
->name
[0] == 'i') /* allprop "include", not "prop" */
3850 continue; /*(all props in db returned with allprop)*/
3851 /* dead props or props in "DAV:" ns not handed above */
3854 /* save pointers directly into parsed xmlDoc
3855 * Therefore, MUST NOT call xmlFreeDoc(xml)
3856 * until also done with pb.proplist */
3857 webdav_property_name
* const propname
=
3858 pb
.proplist
.ptr
+ pb
.proplist
.used
++;
3860 propname
->ns
= (char *)prop
->ns
->href
;
3861 propname
->nslen
= strlen(propname
->ns
);
3865 propname
->nslen
= 0;
3867 propname
->name
= (char *)prop
->name
;
3868 propname
->namelen
= namelen
;
3874 if (NULL
== pb
.proplist
.ptr
&& !pb
.propname
)
3875 pb
.allprop
= pb
.lockdiscovery
= 1;
3879 pb
.dst
= &con
->physical
;
3880 pb
.b
= /*(optimization; buf extended as needed)*/
3881 chunkqueue_append_buffer_open_sz(con
->write_queue
, BUFFER_MAX_REUSE_SIZE
);
3882 pb
.b_200
= buffer_init();
3883 pb
.b_404
= buffer_init();
3885 buffer_string_prepare_copy(pb
.b_200
, BUFFER_MAX_REUSE_SIZE
-1);
3886 buffer_string_prepare_copy(pb
.b_404
, BUFFER_MAX_REUSE_SIZE
-1);
3888 webdav_xml_doctype(pb
.b
, con
);
3889 buffer_append_string_len(pb
.b
, CONST_STR_LEN(
3890 "<D:multistatus xmlns:D=\"DAV:\" " MOD_WEBDAV_XMLNS_NS0
">\n"));
3892 if (0 != pb
.depth
) /*(must be collection or else error returned above)*/
3893 webdav_propfind_dir(&pb
);
3895 webdav_propfind_resource(&pb
);
3897 buffer_append_string_len(pb
.b
, CONST_STR_LEN(
3898 "</D:multistatus>\n"));
3901 log_error(con
->errh
, __FILE__
, __LINE__
, "XML-response-body: %.*s",
3902 BUFFER_INTLEN_PTR(pb
.b
));
3904 http_status_set_fin(con
, 207); /* Multi-status */
3906 buffer_free(pb
.b_404
);
3907 buffer_free(pb
.b_200
);
3908 #ifdef USE_PROPPATCH
3909 if (pb
.proplist
.ptr
)
3910 free(pb
.proplist
.ptr
);
3915 return HANDLER_FINISHED
;
3920 mod_webdav_mkcol (connection
* const con
, const plugin_config
* const pconf
)
3922 const int status
= webdav_mkdir(pconf
, &con
->physical
, -1);
3924 http_status_set_fin(con
, 201); /* Created */
3926 http_status_set_error(con
, status
);
3928 return HANDLER_FINISHED
;
3933 mod_webdav_delete (connection
* const con
, const plugin_config
* const pconf
)
3935 /* reject DELETE if original URI sent with fragment ('litmus' warning) */
3936 if (NULL
!= strchr(con
->request
.orig_uri
->ptr
, '#')) {
3937 http_status_set_error(con
, 403);
3938 return HANDLER_FINISHED
;
3942 if (-1 == lstat(con
->physical
.path
->ptr
, &st
)) {
3943 http_status_set_error(con
, (errno
== ENOENT
) ? 404 : 403);
3944 return HANDLER_FINISHED
;
3947 if (0 != webdav_if_match_or_unmodified_since(con
, &st
)) {
3948 http_status_set_error(con
, 412); /* Precondition Failed */
3949 return HANDLER_FINISHED
;
3952 if (S_ISDIR(st
.st_mode
)) {
3953 if (con
->physical
.path
->ptr
[con
->physical
.path
->used
- 2] != '/') {
3954 #if 0 /*(issues warning for /usr/bin/litmus copymove test)*/
3955 http_response_redirect_to_directory(pconf
->srv
, con
, 308);
3956 return HANDLER_FINISHED
; /* 308 Permanent Redirect */
3957 /* Alternatively, could append '/' to con->physical.path
3958 * and con->physical.rel_path, set Content-Location in
3959 * response headers, and continue to serve the request */
3961 buffer_append_string_len(con
->physical
.path
, CONST_STR_LEN("/"));
3962 buffer_append_string_len(con
->physical
.rel_path
,CONST_STR_LEN("/"));
3963 #if 0 /*(Content-Location not very useful to client after DELETE)*/
3964 /*(? should it be request.uri or orig_uri ?)*/
3965 /*(should be url-encoded path)*/
3966 buffer_append_string_len(con
->request
.uri
, CONST_STR_LEN("/"));
3967 http_header_response_set(con
, HTTP_HEADER_CONTENT_LOCATION
,
3968 CONST_STR_LEN("Content-Location"),
3969 CONST_BUF_LEN(con
->request
.uri
));
3973 /* require "infinity" if Depth request header provided */
3974 if (-1 != webdav_parse_Depth(con
)) {
3975 /* [RFC4918] 9.6.1 DELETE for Collections
3976 * The DELETE method on a collection MUST act as if a
3977 * "Depth: infinity" header was used on it. A client MUST NOT
3978 * submit a Depth header with a DELETE on a collection with any
3979 * value but infinity.
3981 http_status_set_error(con
, 400); /* Bad Request */
3982 return HANDLER_FINISHED
;
3985 buffer
* const ms
= buffer_init(); /* multi-status */
3987 const int flags
= (con
->conf
.force_lowercase_filenames
)
3988 ? WEBDAV_FLAG_LC_NAMES
3990 if (0 == webdav_delete_dir(pconf
, &con
->physical
, ms
, flags
)) {
3991 /* Note: this does not destroy locks if an error occurs,
3992 * which is not a problem if lock is only on the collection
3993 * being moved, but might need finer updates if there are
3994 * locks on internal elements that are successfully deleted */
3995 webdav_lock_delete_uri_col(pconf
, con
->physical
.rel_path
);
3996 http_status_set_fin(con
, 204); /* No Content */
3999 webdav_xml_doc_multistatus(con
, pconf
, ms
); /* 207 Multi-status */
4003 /* invalidate stat cache of src if DELETE, whether or not successful */
4004 stat_cache_delete_dir(pconf
->srv
, CONST_BUF_LEN(con
->physical
.path
));
4006 else if (con
->physical
.path
->ptr
[con
->physical
.path
->used
- 2] == '/')
4007 http_status_set_error(con
, 403);
4009 const int status
= webdav_delete_file(pconf
, &con
->physical
);
4011 webdav_lock_delete_uri(pconf
, con
->physical
.rel_path
);
4012 http_status_set_fin(con
, 204); /* No Content */
4015 http_status_set_error(con
, status
);
4018 return HANDLER_FINISHED
;
4023 mod_webdav_write_cq_first_chunk (connection
* const con
, chunkqueue
* const cq
,
4026 /* (Note: copying might take some time, temporarily pausing server) */
4027 chunk
*c
= cq
->first
;
4032 if (NULL
!= webdav_mmap_file_chunk(c
)) {
4034 wr
= write(fd
, c
->file
.mmap
.start
+c
->offset
,
4035 c
->file
.length
- c
->offset
);
4036 } while (-1 == wr
&& errno
== EINTR
);
4041 case ENOSYS
: case ENODEV
: case EINVAL
: break;
4043 log_perror(con
->errh
, __FILE__
, __LINE__
,
4044 "open() or mmap() '%*.s'",
4045 BUFFER_INTLEN_PTR(c
->mem
));
4048 if (webdav_open_chunk_file_rd(c
) < 0) {
4049 http_status_set_error(con
, 500); /* Internal Server Error */
4055 if (-1 == lseek(c
->file
.fd
, c
->file
.start
+c
->offset
, SEEK_SET
))
4057 off_t len
= c
->file
.length
- c
->offset
;
4058 if (len
> (off_t
)sizeof(buf
)) len
= (off_t
)sizeof(buf
);
4059 rd
= read(c
->file
.fd
, buf
, (size_t)len
);
4060 } while (-1 == rd
&& errno
== EINTR
);
4063 wr
= write(fd
, buf
, (size_t)rd
);
4064 } while (-1 == wr
&& errno
== EINTR
);
4068 log_perror(con
->errh
, __FILE__
, __LINE__
,
4070 BUFFER_INTLEN_PTR(c
->mem
));
4071 http_status_set_error(con
, 500); /* Internal Server Error */
4077 wr
= write(fd
, c
->mem
->ptr
+ c
->offset
,
4078 buffer_string_length(c
->mem
) - c
->offset
);
4079 } while (-1 == wr
&& errno
== EINTR
);
4084 chunkqueue_mark_written(cq
, wr
);
4085 chunkqueue_remove_finished_chunks(cq
);
4088 http_status_set_error(con
, (errno
== ENOSPC
) ? 507 : 403);
4094 __attribute_noinline__
4096 mod_webdav_write_cq (connection
* const con
, chunkqueue
* const cq
, const int fd
)
4098 chunkqueue_remove_finished_chunks(cq
);
4099 while (!chunkqueue_is_empty(cq
)) {
4100 if (mod_webdav_write_cq_first_chunk(con
, cq
, fd
) < 0) return 0;
4106 #if (defined(__linux__) || defined(__CYGWIN__)) && defined(O_TMPFILE)
4108 mod_webdav_write_single_file_chunk (connection
* const con
, chunkqueue
* const cq
)
4110 /* cq might have mem chunks after initial tempfile chunk
4111 * due to chunkqueue_steal() if request body is small */
4112 /*assert(cq->first->type == FILE_CHUNK);*/
4113 /*assert(cq->first->next != NULL);*/
4114 chunk
* const c
= cq
->first
;
4115 cq
->first
= c
->next
;
4116 if (mod_webdav_write_cq(con
, cq
, c
->file
.fd
)) {
4117 /*assert(cq->first == NULL);*/
4119 cq
->first
= cq
->last
= c
;
4123 /*assert(cq->first != NULL);*/
4124 c
->next
= cq
->first
;
4133 mod_webdav_put_0 (connection
* const con
, const plugin_config
* const pconf
)
4135 if (0 != webdav_if_match_or_unmodified_since(con
, NULL
)) {
4136 http_status_set_error(con
, 412); /* Precondition Failed */
4137 return HANDLER_FINISHED
;
4140 /* special-case PUT 0-length file */
4142 fd
= fdevent_open_cloexec(con
->physical
.path
->ptr
, 0,
4143 O_WRONLY
| O_CREAT
| O_EXCL
| O_TRUNC
,
4146 if (0 != con
->etag_flags
) {
4147 /*(skip sending etag if fstat() error; not expected)*/
4149 if (0 == fstat(fd
, &st
)) webdav_response_etag(pconf
->srv
, con
, &st
);
4152 http_status_set_fin(con
, 201); /* Created */
4153 return HANDLER_FINISHED
;
4155 else if (errno
== EISDIR
) {
4156 http_status_set_error(con
, 405); /* Method Not Allowed */
4157 return HANDLER_FINISHED
;
4161 webdav_delete_file(pconf
, &con
->physical
); /*(ignore result)*/
4162 /*(attempt unlink(); target might be symlink
4163 * and above O_NOFOLLOW resulted in ELOOP)*/
4165 fd
= fdevent_open_cloexec(con
->physical
.path
->ptr
, 0,
4166 O_WRONLY
| O_CREAT
| O_TRUNC
,
4170 http_status_set_fin(con
, 204); /* No Content */
4171 return HANDLER_FINISHED
;
4174 http_status_set_error(con
, 500); /* Internal Server Error */
4175 return HANDLER_FINISHED
;
4180 mod_webdav_put_prep (connection
* const con
, const plugin_config
* const pconf
)
4182 if (NULL
!= http_header_request_get(con
, HTTP_HEADER_OTHER
,
4183 CONST_STR_LEN("Content-Range"))) {
4184 if (pconf
->deprecated_unsafe_partial_put_compat
) return HANDLER_GO_ON
;
4185 /* [RFC7231] 4.3.4 PUT
4186 * An origin server that allows PUT on a given target resource MUST
4187 * send a 400 (Bad Request) response to a PUT request that contains a
4188 * Content-Range header field (Section 4.2 of [RFC7233]), since the
4189 * payload is likely to be partial content that has been mistakenly
4190 * PUT as a full representation.
4192 http_status_set_error(con
, 400); /* Bad Request */
4193 return HANDLER_FINISHED
;
4196 const uint32_t used
= con
->physical
.path
->used
;
4197 char *slash
= con
->physical
.path
->ptr
+ used
- 2;
4198 if (*slash
== '/') { /* disallow PUT on a collection (path ends in '/') */
4199 http_status_set_error(con
, 400); /* Bad Request */
4200 return HANDLER_FINISHED
;
4203 /* special-case PUT 0-length file */
4204 if (0 == con
->request
.content_length
)
4205 return mod_webdav_put_0(con
, pconf
);
4207 /* Create temporary file in target directory (to store reqbody as received)
4208 * Temporary file is unlinked so that if receiving reqbody fails,
4209 * temp file is automatically cleaned up when fd is closed.
4210 * While being received, temporary file is not part of directory listings.
4211 * While this might result in extra copying, it is simple and robust. */
4213 size_t len
= buffer_string_length(con
->physical
.path
);
4214 #if (defined(__linux__) || defined(__CYGWIN__)) && defined(O_TMPFILE)
4215 slash
= memrchr(con
->physical
.path
->ptr
, '/', len
);
4216 if (slash
== con
->physical
.path
->ptr
) slash
= NULL
;
4217 if (slash
) *slash
= '\0';
4218 fd
= fdevent_open_cloexec(con
->physical
.path
->ptr
, 1,
4219 O_RDWR
| O_TMPFILE
| O_APPEND
, WEBDAV_FILE_MODE
);
4220 if (slash
) *slash
= '/';
4224 buffer_append_string_len(con
->physical
.path
, CONST_STR_LEN("-XXXXXX"));
4225 fd
= fdevent_mkstemp_append(con
->physical
.path
->ptr
);
4226 if (fd
>= 0) unlink(con
->physical
.path
->ptr
);
4227 buffer_string_set_length(con
->physical
.path
, len
);
4230 http_status_set_error(con
, 500); /* Internal Server Error */
4231 return HANDLER_FINISHED
;
4234 /* copy all chunks even though expecting (at most) single MEM_CHUNK chunk
4235 * (still, loop on partial writes)
4236 * (Note: copying might take some time, temporarily pausing server)
4237 * (error status is set if error occurs) */
4238 chunkqueue
* const cq
= con
->request_content_queue
;
4239 off_t cqlen
= chunkqueue_length(cq
);
4240 if (!mod_webdav_write_cq(con
, cq
, fd
)) {
4242 return HANDLER_FINISHED
;
4245 chunkqueue_reset(cq
);
4246 if (0 != cqlen
) /*(con->physical.path copied, then c->mem cleared below)*/
4247 chunkqueue_append_file_fd(cq
, con
->physical
.path
, fd
, 0, cqlen
);
4249 /*(must be non-zero for fd to be appended, then reset to 0-length)*/
4250 chunkqueue_append_file_fd(cq
, con
->physical
.path
, fd
, 0, 1);
4251 cq
->last
->file
.length
= 0;
4254 buffer_clear(cq
->last
->mem
); /* file already unlink()ed */
4255 chunkqueue_set_tempdirs(cq
, cq
->tempdirs
, INTMAX_MAX
);
4256 cq
->last
->file
.is_temp
= 1;
4258 return HANDLER_GO_ON
;
4262 #if (defined(__linux__) || defined(__CYGWIN__)) && defined(O_TMPFILE)
4264 mod_webdav_put_linkat_rename (connection
* const con
,
4265 const plugin_config
* const pconf
,
4266 const char * const pathtemp
)
4268 chunkqueue
* const cq
= con
->request_content_queue
;
4269 chunk
*c
= cq
->first
;
4271 char pathproc
[32] = "/proc/self/fd/";
4272 li_itostrn(pathproc
+sizeof("/proc/self/fd/")-1,
4273 sizeof(pathproc
)-(sizeof("/proc/self/fd/")-1), (long)c
->file
.fd
);
4274 if (0 == linkat(AT_FDCWD
, pathproc
, AT_FDCWD
, pathtemp
, AT_SYMLINK_FOLLOW
)){
4276 #ifdef RENAME_NOREPLACE /*(renameat2() not well-supported yet)*/
4277 if (0 == renameat2(AT_FDCWD
, pathtemp
,
4278 AT_FDCWD
, con
->physical
.path
->ptr
, RENAME_NOREPLACE
))
4279 http_status_set_fin(con
, 201); /* Created */
4280 else if (0 == rename(pathtemp
, con
->physical
.path
->ptr
))
4281 http_status_set_fin(con
, 204); /* No Content */ /*(replaced)*/
4284 http_status_set_fin(con
, 0 == lstat(con
->physical
.path
->ptr
, &st
)
4285 ? 204 /* No Content */
4286 : 201); /* Created */
4287 if (0 != rename(pathtemp
, con
->physical
.path
->ptr
))
4290 if (errno
== EISDIR
)
4291 http_status_set_error(con
, 405); /* Method Not Allowed */
4293 http_status_set_error(con
, 403); /* Forbidden */
4297 if (0 != con
->etag_flags
&& http_status_get(con
) < 300) { /*(201, 204)*/
4298 /*(skip sending etag if fstat() error; not expected)*/
4299 if (0 == fstat(c
->file
.fd
, &st
))
4300 webdav_response_etag(pconf
->srv
, con
, &st
);
4303 chunkqueue_mark_written(cq
, c
->file
.length
);
4304 chunkqueue_remove_finished_chunks(cq
);
4315 mod_webdav_put_deprecated_unsafe_partial_put_compat (connection
* const con
,
4316 const plugin_config
*pconf
,
4317 const buffer
* const h
)
4319 /* historical code performed very limited range parse (repeated here) */
4320 /* we only support <num>- ... */
4321 const char *num
= h
->ptr
;
4324 if (0 != strncmp(num
, "bytes", sizeof("bytes")-1)) {
4325 http_status_set_error(con
, 501); /* Not Implemented */
4326 return HANDLER_FINISHED
;
4328 num
+= 5; /* +5 for "bytes" */
4329 offset
= strtoll(num
, &err
, 10); /*(strtoll() ignores leading whitespace)*/
4330 if (num
== err
|| *err
!= '-' || offset
< 0) {
4331 http_status_set_error(con
, 501); /* Not Implemented */
4332 return HANDLER_FINISHED
;
4335 const int fd
= fdevent_open_cloexec(con
->physical
.path
->ptr
, 0,
4336 O_WRONLY
, WEBDAV_FILE_MODE
);
4338 http_status_set_error(con
, (errno
== ENOENT
) ? 404 : 403);
4339 return HANDLER_FINISHED
;
4342 if (-1 == lseek(fd
, offset
, SEEK_SET
)) {
4344 http_status_set_error(con
, 500); /* Internal Server Error */
4345 return HANDLER_FINISHED
;
4348 /* copy all chunks even though expecting single chunk
4349 * (still, loop on partial writes)
4350 * (Note: copying might take some time, temporarily pausing server)
4351 * (error status is set if error occurs) */
4352 mod_webdav_write_cq(con
, con
->request_content_queue
, fd
);
4355 if (0 != con
->etag_flags
&& !http_status_is_set(con
)) {
4356 /*(skip sending etag if fstat() error; not expected)*/
4357 if (0 != fstat(fd
, &st
)) con
->etag_flags
= 0;
4360 const int wc
= close(fd
);
4361 if (0 != wc
&& !http_status_is_set(con
))
4362 http_status_set_error(con
, (errno
== ENOSPC
) ? 507 : 403);
4364 if (!http_status_is_set(con
)) {
4365 http_status_set_fin(con
, 204); /* No Content */
4366 if (0 != con
->etag_flags
) webdav_response_etag(pconf
->srv
, con
, &st
);
4369 return HANDLER_FINISHED
;
4374 mod_webdav_put (connection
* const con
, const plugin_config
* const pconf
)
4376 if (con
->state
== CON_STATE_READ_POST
) {
4377 int first_read
= chunkqueue_is_empty(con
->request_content_queue
);
4378 handler_t rc
= connection_handle_read_post_state(pconf
->srv
, con
);
4379 if (rc
!= HANDLER_GO_ON
) {
4380 if (first_read
&& rc
== HANDLER_WAIT_FOR_EVENT
4381 && 0 != webdav_if_match_or_unmodified_since(con
, NULL
)) {
4382 http_status_set_error(con
, 412); /* Precondition Failed */
4383 return HANDLER_FINISHED
;
4389 if (0 != webdav_if_match_or_unmodified_since(con
, NULL
)) {
4390 http_status_set_error(con
, 412); /* Precondition Failed */
4391 return HANDLER_FINISHED
;
4394 if (pconf
->deprecated_unsafe_partial_put_compat
) {
4395 const buffer
* const h
=
4396 http_header_request_get(con
, HTTP_HEADER_OTHER
,
4397 CONST_STR_LEN("Content-Range"));
4400 mod_webdav_put_deprecated_unsafe_partial_put_compat(con
,pconf
,h
);
4403 /* construct temporary filename in same directory as target
4404 * (expect cq contains exactly one chunk:
4405 * the temporary FILE_CHUNK created in mod_webdav_put_prep())
4406 * (do not reuse c->mem buffer; if tmpfile was unlinked, c->mem is blank)
4407 * (since temporary file was unlinked, no guarantee of unique name,
4408 * so add pid and fd to avoid conflict with an unlikely parallel
4409 * PUT request being handled by same server pid (presumably by
4410 * same client using same lock token)) */
4411 chunkqueue
* const cq
= con
->request_content_queue
;
4412 chunk
*c
= cq
->first
;
4414 /* future: might support client specifying getcontenttype property
4415 * using Content-Type request header. However, [RFC4918] 9.7.1 notes:
4416 * Many servers do not allow configuring the Content-Type on a
4417 * per-resource basis in the first place. Thus, clients can't always
4418 * rely on the ability to directly influence the content type by
4419 * including a Content-Type request header
4422 /*(similar to beginning of webdav_linktmp_rename())*/
4423 buffer
* const tmpb
= pconf
->tmpb
;
4424 buffer_copy_buffer(tmpb
, con
->physical
.path
);
4425 buffer_append_string_len(tmpb
, CONST_STR_LEN("."));
4426 buffer_append_int(tmpb
, (long)getpid());
4427 buffer_append_string_len(tmpb
, CONST_STR_LEN("."));
4428 if (c
->type
== MEM_CHUNK
)
4429 buffer_append_uint_hex_lc(tmpb
, (uintptr_t)pconf
); /*(stack/heap addr)*/
4431 buffer_append_int(tmpb
, (long)c
->file
.fd
);
4432 buffer_append_string_len(tmpb
, CONST_STR_LEN("~"));
4434 if (buffer_string_length(tmpb
) >= PATH_MAX
) { /*(temp file path too long)*/
4435 http_status_set_error(con
, 500); /* Internal Server Error */
4436 return HANDLER_FINISHED
;
4439 const char *pathtemp
= tmpb
->ptr
;
4441 #if (defined(__linux__) || defined(__CYGWIN__)) && defined(O_TMPFILE)
4442 if (c
->type
== FILE_CHUNK
) { /*(reqbody contained in single tempfile)*/
4443 if (NULL
!= c
->next
) {
4444 /* if request body <= 64k, in-memory chunks might have been
4445 * moved to cq instead of appended to first chunk FILE_CHUNK */
4446 if (!mod_webdav_write_single_file_chunk(con
, cq
))
4447 return HANDLER_FINISHED
;
4449 if (mod_webdav_put_linkat_rename(con
, pconf
, pathtemp
))
4450 return HANDLER_FINISHED
;
4451 /* attempt traditional copy (below) if linkat() failed for any reason */
4455 const int fd
= fdevent_open_cloexec(pathtemp
, 0,
4456 O_WRONLY
| O_CREAT
| O_EXCL
| O_TRUNC
,
4459 http_status_set_error(con
, 500); /* Internal Server Error */
4460 return HANDLER_FINISHED
;
4463 /* copy all chunks even though expecting single chunk
4464 * (still, loop on partial writes)
4465 * (Note: copying might take some time, temporarily pausing server)
4466 * (error status is set if error occurs) */
4467 mod_webdav_write_cq(con
, cq
, fd
);
4470 if (0 != con
->etag_flags
&& !http_status_is_set(con
)) {
4471 /*(skip sending etag if fstat() error; not expected)*/
4472 if (0 != fstat(fd
, &st
)) con
->etag_flags
= 0;
4475 const int wc
= close(fd
);
4476 if (0 != wc
&& !http_status_is_set(con
))
4477 http_status_set_error(con
, (errno
== ENOSPC
) ? 507 : 403);
4479 if (!http_status_is_set(con
)) {
4481 http_status_set_fin(con
, 0 == lstat(con
->physical
.path
->ptr
, &ste
)
4482 ? 204 /* No Content */
4483 : 201); /* Created */
4484 if (0 == rename(pathtemp
, con
->physical
.path
->ptr
)) {
4485 if (0 != con
->etag_flags
) webdav_response_etag(pconf
->srv
,con
,&st
);
4488 if (errno
== EISDIR
)
4489 http_status_set_error(con
, 405); /* Method Not Allowed */
4491 http_status_set_error(con
, 500); /* Internal Server Error */
4498 return HANDLER_FINISHED
;
4503 mod_webdav_copymove_b (connection
* const con
, const plugin_config
* const pconf
, buffer
* const dst_path
, buffer
* const dst_rel_path
)
4505 int flags
= WEBDAV_FLAG_OVERWRITE
/*(default)*/
4506 | (con
->conf
.force_lowercase_filenames
4507 ? WEBDAV_FLAG_LC_NAMES
4509 | (con
->request
.http_method
== HTTP_METHOD_MOVE
4510 ? WEBDAV_FLAG_MOVE_RENAME
4511 : WEBDAV_FLAG_COPY_LINK
);
4513 const buffer
* const h
=
4514 http_header_request_get(con
,HTTP_HEADER_OTHER
,CONST_STR_LEN("Overwrite"));
4517 || ((h
->ptr
[0] & 0xdf) != 'F' && (h
->ptr
[0] & 0xdf) != 'T')) {
4518 http_status_set_error(con
, 400); /* Bad Request */
4519 return HANDLER_FINISHED
;
4521 if ((h
->ptr
[0] & 0xdf) == 'F')
4522 flags
&= ~WEBDAV_FLAG_OVERWRITE
;
4525 /* parse Destination
4527 * http://127.0.0.1:1025/dav/litmus/copydest
4529 * - host has to match Host: header in request
4530 * (or else would need to check that Destination is reachable from server
4531 * and authentication credentials grant privileges on Destination)
4532 * - query string on Destination, if present, is discarded
4534 * NOTE: Destination path is relative to document root and IS NOT re-run
4535 * through other modules on server (such as aliasing or rewrite or userdir)
4537 const buffer
* const destination
=
4538 http_header_request_get(con
, HTTP_HEADER_OTHER
,
4539 CONST_STR_LEN("Destination"));
4540 if (NULL
== destination
) {
4541 http_status_set_error(con
, 400); /* Bad Request */
4542 return HANDLER_FINISHED
;
4545 force_assert(2 <= destination
->used
);
4548 const char *sep
= destination
->ptr
, *start
;
4549 if (*sep
!= '/') { /* path-absolute or absolute-URI form */
4551 sep
= start
+ buffer_string_length(con
->uri
.scheme
);
4552 if (0 != strncmp(start
, con
->uri
.scheme
->ptr
, sep
- start
)
4553 || sep
[0] != ':' || sep
[1] != '/' || sep
[2] != '/') {
4554 http_status_set_error(con
, 400); /* Bad Request */
4555 return HANDLER_FINISHED
;
4559 if (NULL
== (sep
= strchr(start
, '/'))) {
4560 http_status_set_error(con
, 400); /* Bad Request */
4561 return HANDLER_FINISHED
;
4563 if (!buffer_is_equal_string(con
->uri
.authority
, start
, sep
- start
)
4564 /* skip login info (even though it should not be present) */
4565 && (NULL
== (start
= (char *)memchr(start
, '@', sep
- start
))
4566 || (++start
, !buffer_is_equal_string(con
->uri
.authority
,
4567 start
, sep
- start
)))) {
4568 /* not the same host */
4569 http_status_set_error(con
, 502); /* Bad Gateway */
4570 return HANDLER_FINISHED
;
4573 start
= sep
; /* starts with '/' */
4576 dst
.path
= dst_path
;
4577 dst
.rel_path
= dst_rel_path
;
4579 /* destination: remove query string, urldecode, path_simplify
4580 * and (maybe) lowercase for consistent destination URI path */
4581 buffer_copy_string_len(dst_rel_path
, start
,
4582 NULL
== (sep
= strchr(start
, '?'))
4583 ? destination
->ptr
+ destination
->used
-1 - start
4585 if (buffer_string_length(dst_rel_path
) >= PATH_MAX
) {
4586 http_status_set_error(con
, 403); /* Forbidden */
4587 return HANDLER_FINISHED
;
4589 buffer_urldecode_path(dst_rel_path
);
4590 if (!buffer_is_valid_UTF8(dst_rel_path
)) {
4591 /* invalid UTF-8 after url-decode */
4592 http_status_set_error(con
, 400);
4593 return HANDLER_FINISHED
;
4595 buffer_path_simplify(dst_rel_path
, dst_rel_path
);
4596 if (buffer_string_is_empty(dst_rel_path
) || dst_rel_path
->ptr
[0] != '/') {
4597 http_status_set_error(con
, 400);
4598 return HANDLER_FINISHED
;
4601 if (flags
& WEBDAV_FLAG_LC_NAMES
)
4602 buffer_to_lower(dst_rel_path
);
4604 /* Destination physical path
4605 * src con->physical.path might have been remapped with mod_alias.
4606 * (but mod_alias does not modify con->physical.rel_path)
4607 * Find matching prefix to support use of mod_alias to remap webdav root.
4608 * Aliasing of paths underneath the webdav root might not work.
4609 * Likewise, mod_rewrite URL rewriting might thwart this comparison.
4610 * Use mod_redirect instead of mod_alias to remap paths *under* webdav root.
4611 * Use mod_redirect instead of mod_rewrite on *any* parts of path to webdav.
4612 * (Related, use mod_auth to protect webdav root, but avoid attempting to
4613 * use mod_auth on paths underneath webdav root, as Destination is not
4614 * validated with mod_auth)
4616 * tl;dr: webdav paths and webdav properties are managed by mod_webdav,
4617 * so do not modify paths externally or else undefined behavior
4618 * or corruption may occur
4620 * find matching URI prefix (lowercased if WEBDAV_FLAG_LC_NAMES)
4621 * (con->physical.rel_path and dst_rel_path will always match leading '/')
4622 * check if remaining con->physical.rel_path matches suffix of
4623 * con->physical.path so that we can use the prefix to remap
4624 * Destination physical path */
4626 force_assert(0 != con
->physical
.rel_path
->used
);
4630 const char * const p1
= con
->physical
.rel_path
->ptr
;
4631 const char * const p2
= dst_rel_path
->ptr
;
4632 for (i
= 0; p1
[i
] && p1
[i
] == p2
[i
]; ++i
) ;
4633 while (i
!= 0 && p1
[--i
] != '/') ; /* find matching directory path */
4635 remain
= con
->physical
.rel_path
->used
- 1 - i
;
4636 if (con
->physical
.path
->used
- 1 <= remain
) { /*(should not happen)*/
4637 http_status_set_error(con
, 403); /* Forbidden */
4638 return HANDLER_FINISHED
;
4640 if (0 == memcmp(con
->physical
.rel_path
->ptr
+i
, /*(suffix match)*/
4641 con
->physical
.path
->ptr
+ con
->physical
.path
->used
-1-remain
,
4642 remain
)) { /*(suffix match)*/
4644 force_assert(2 <= dst_rel_path
->used
);
4646 buffer_copy_string_len(dst_path
, con
->physical
.path
->ptr
,
4647 con
->physical
.path
->used
- 1 - remain
);
4648 buffer_append_string_len(dst_path
,
4649 dst_rel_path
->ptr
+i
,
4650 dst_rel_path
->used
- 1 - i
);
4651 if (buffer_string_length(dst_path
) >= PATH_MAX
) {
4652 http_status_set_error(con
, 403); /* Forbidden */
4653 return HANDLER_FINISHED
;
4656 else { /*(not expected; some other module mucked with path or rel_path)*/
4657 /* unable to perform physical path remap here;
4658 * assume doc_root/rel_path and no remapping */
4660 force_assert(2 <= con
->physical
.doc_root
->used
);
4661 force_assert(2 <= dst_path
->used
);
4663 buffer_copy_buffer(dst_path
, con
->physical
.doc_root
);
4664 if (dst_path
->ptr
[dst_path
->used
-2] == '/')
4665 --dst_path
->used
; /* since dst_rel_path begins with '/' */
4666 buffer_append_string_buffer(dst_path
, dst_rel_path
);
4667 if (buffer_string_length(dst_rel_path
) >= PATH_MAX
) {
4668 http_status_set_error(con
, 403); /* Forbidden */
4669 return HANDLER_FINISHED
;
4673 if (con
->physical
.path
->used
<= dst_path
->used
4674 && 0 == memcmp(con
->physical
.path
->ptr
, dst_path
->ptr
,
4675 con
->physical
.path
->used
-1)
4676 && (con
->physical
.path
->ptr
[con
->physical
.path
->used
-2] == '/'
4677 || dst_path
->ptr
[con
->physical
.path
->used
-1] == '/'
4678 || dst_path
->ptr
[con
->physical
.path
->used
-1] == '\0')) {
4679 /* dst must not be nested under (or same as) src */
4680 http_status_set_error(con
, 403); /* Forbidden */
4681 return HANDLER_FINISHED
;
4685 if (-1 == lstat(con
->physical
.path
->ptr
, &st
)) {
4686 /* don't known about it yet, unlink will fail too */
4687 http_status_set_error(con
, (errno
== ENOENT
) ? 404 : 403);
4688 return HANDLER_FINISHED
;
4691 if (0 != webdav_if_match_or_unmodified_since(con
, &st
)) {
4692 http_status_set_error(con
, 412); /* Precondition Failed */
4693 return HANDLER_FINISHED
;
4696 if (S_ISDIR(st
.st_mode
)) {
4697 if (con
->physical
.path
->ptr
[con
->physical
.path
->used
- 2] != '/') {
4698 http_response_redirect_to_directory(pconf
->srv
, con
, 308);
4699 return HANDLER_FINISHED
; /* 308 Permanent Redirect */
4700 /* Alternatively, could append '/' to con->physical.path
4701 * and con->physical.rel_path, set Content-Location in
4702 * response headers, and continue to serve the request. */
4705 /* ensure Destination paths end with '/' since dst is a collection */
4707 force_assert(2 <= dst_rel_path
->used
);
4709 if (dst_rel_path
->ptr
[dst_rel_path
->used
- 2] != '/') {
4710 buffer_append_slash(dst_rel_path
);
4711 buffer_append_slash(dst_path
);
4714 /* check for lock on destination (after ensuring dst ends in '/') */
4715 if (!webdav_has_lock(con
, pconf
, dst_rel_path
))
4716 return HANDLER_FINISHED
; /* 423 Locked */
4718 const int depth
= webdav_parse_Depth(con
);
4720 http_status_set_error(con
, 400); /* Bad Request */
4721 return HANDLER_FINISHED
;
4724 if (con
->request
.http_method
== HTTP_METHOD_MOVE
) {
4725 http_status_set_error(con
, 400); /* Bad Request */
4726 return HANDLER_FINISHED
;
4728 /* optionally create collection, then copy properties */
4730 if (0 == lstat(dst_path
->ptr
, &st
)) {
4731 if (S_ISDIR(st
.st_mode
))
4732 status
= 204; /* No Content */
4733 else if (flags
& WEBDAV_FLAG_OVERWRITE
) {
4734 status
= webdav_mkdir(pconf
, &dst
, 1);
4735 if (0 == status
) status
= 204; /* No Content */
4738 status
= 412; /* Precondition Failed */
4740 else if (errno
== ENOENT
) {
4741 status
= webdav_mkdir(pconf
, &dst
,
4742 !!(flags
& WEBDAV_FLAG_OVERWRITE
));
4743 if (0 == status
) status
= 201; /* Created */
4746 status
= 403; /* Forbidden */
4748 http_status_set_fin(con
, status
);
4749 webdav_prop_copy_uri(pconf
,con
->physical
.rel_path
,dst
.rel_path
);
4752 http_status_set_error(con
, status
);
4753 return HANDLER_FINISHED
;
4756 /* ensure destination is not nested in source */
4757 if (dst_rel_path
->ptr
[dst_rel_path
->used
- 2] != '/') {
4758 buffer_append_slash(dst_rel_path
);
4759 buffer_append_slash(dst_path
);
4762 buffer
* const ms
= buffer_init(); /* multi-status */
4763 if (0 == webdav_copymove_dir(pconf
, &con
->physical
, &dst
, ms
, flags
)) {
4764 if (con
->request
.http_method
== HTTP_METHOD_MOVE
)
4765 webdav_lock_delete_uri_col(pconf
, con
->physical
.rel_path
);
4766 /*(requiring lock on destination requires MKCOL create dst first)
4767 *(if no lock support, return 200 OK unconditionally
4768 * instead of 200 OK or 201 Created; not fully RFC-conformant)*/
4769 http_status_set_fin(con
, 200); /* OK */
4772 /* Note: this does not destroy any locks if any error occurs,
4773 * which is not a problem if lock is only on the collection
4774 * being moved, but might need finer updates if there are
4775 * locks on internal elements that are successfully moved */
4776 webdav_xml_doc_multistatus(con
, pconf
, ms
); /* 207 Multi-status */
4779 /* invalidate stat cache of src if MOVE, whether or not successful */
4780 if (con
->request
.http_method
== HTTP_METHOD_MOVE
)
4781 stat_cache_delete_dir(pconf
->srv
,CONST_BUF_LEN(con
->physical
.path
));
4782 return HANDLER_FINISHED
;
4784 else if (con
->physical
.path
->ptr
[con
->physical
.path
->used
- 2] == '/') {
4785 http_status_set_error(con
, 403); /* Forbidden */
4786 return HANDLER_FINISHED
;
4789 /* check if client has lock for destination
4790 * Note: requiring a lock on non-collection means that destination
4791 * should always exist since the issuance of the lock creates the
4792 * resource, so client will always have to provide Overwrite: T
4793 * for direct operations on non-collections (files) */
4794 if (!webdav_has_lock(con
, pconf
, dst_rel_path
))
4795 return HANDLER_FINISHED
; /* 423 Locked */
4797 /* check if destination exists
4798 * (Destination should exist since lock is required,
4799 * and obtaining a lock will create the resource) */
4800 int rc
= lstat(dst_path
->ptr
, &st
);
4801 if (0 == rc
&& S_ISDIR(st
.st_mode
)) {
4803 * append basename to physical path
4804 * future: might set Content-Location if dst_path does not end '/'*/
4805 if (NULL
!= (sep
= strrchr(con
->physical
.path
->ptr
, '/'))) {
4807 force_assert(0 != dst_path
->used
);
4809 size_t len
= con
->physical
.path
->used
- 1
4810 - (sep
- con
->physical
.path
->ptr
);
4811 if (dst_path
->ptr
[dst_path
->used
-1] == '/') {
4812 ++sep
; /*(avoid double-slash in path)*/
4815 buffer_append_string_len(dst_path
, sep
, len
);
4816 buffer_append_string_len(dst_rel_path
, sep
, len
);
4817 if (buffer_string_length(dst_path
) >= PATH_MAX
) {
4818 http_status_set_error(con
, 403); /* Forbidden */
4819 return HANDLER_FINISHED
;
4821 rc
= lstat(dst_path
->ptr
, &st
);
4822 /* target (parent collection) already exists */
4823 http_status_set_fin(con
, 204); /* No Content */
4831 if (http_status_is_set(con
)) break;
4832 /* check that parent collection exists */
4833 if ((slash
= strrchr(dst_path
->ptr
, '/'))) {
4835 if (0 == lstat(dst_path
->ptr
, &st
) && S_ISDIR(st
.st_mode
)) {
4837 /* new entity will be created */
4838 if (!http_status_is_set(con
))
4839 http_status_set_fin(con
, 201); /* Created */
4846 http_status_set_error(con
, 409); /* Conflict */
4847 return HANDLER_FINISHED
;
4850 else if (!(flags
& WEBDAV_FLAG_OVERWRITE
)) {
4851 /* destination exists, but overwrite is not set */
4852 http_status_set_error(con
, 412); /* Precondition Failed */
4853 return HANDLER_FINISHED
;
4855 else if (S_ISDIR(st
.st_mode
)) {
4856 /* destination exists, but is a dir, not a file */
4857 http_status_set_error(con
, 409); /* Conflict */
4858 return HANDLER_FINISHED
;
4860 else { /* resource already exists */
4861 http_status_set_fin(con
, 204); /* No Content */
4864 rc
= webdav_copymove_file(pconf
, &con
->physical
, &dst
, &flags
);
4866 if (con
->request
.http_method
== HTTP_METHOD_MOVE
)
4867 webdav_lock_delete_uri(pconf
, con
->physical
.rel_path
);
4870 http_status_set_error(con
, rc
);
4872 return HANDLER_FINISHED
;
4878 mod_webdav_copymove (connection
* const con
, const plugin_config
* const pconf
)
4880 buffer
*dst_path
= buffer_init();
4881 buffer
*dst_rel_path
= buffer_init();
4882 handler_t rc
= mod_webdav_copymove_b(con
, pconf
, dst_path
, dst_rel_path
);
4883 buffer_free(dst_rel_path
);
4884 buffer_free(dst_path
);
4889 #ifdef USE_PROPPATCH
4891 mod_webdav_proppatch (connection
* const con
, const plugin_config
* const pconf
)
4894 http_header_response_set(con
, HTTP_HEADER_OTHER
,
4895 CONST_STR_LEN("Allow"),
4896 CONST_STR_LEN("GET, HEAD, PROPFIND, DELETE, "
4897 "MKCOL, PUT, MOVE, COPY"));
4898 http_status_set_error(con
, 405); /* Method Not Allowed */
4899 return HANDLER_FINISHED
;
4902 if (0 == con
->request
.content_length
) {
4903 http_status_set_error(con
, 400); /* Bad Request */
4904 return HANDLER_FINISHED
;
4907 if (con
->state
== CON_STATE_READ_POST
) {
4908 handler_t rc
= connection_handle_read_post_state(pconf
->srv
, con
);
4909 if (rc
!= HANDLER_GO_ON
) return rc
;
4913 if (0 != lstat(con
->physical
.path
->ptr
, &st
)) {
4914 http_status_set_error(con
, (errno
== ENOENT
) ? 404 : 403);
4915 return HANDLER_FINISHED
;
4918 if (0 != webdav_if_match_or_unmodified_since(con
, &st
)) {
4919 http_status_set_error(con
, 412); /* Precondition Failed */
4920 return HANDLER_FINISHED
;
4923 if (S_ISDIR(st
.st_mode
)) {
4924 if (con
->physical
.path
->ptr
[con
->physical
.path
->used
- 2] != '/') {
4925 buffer
*vb
= http_header_request_get(con
, HTTP_HEADER_OTHER
,
4926 CONST_STR_LEN("User-Agent"));
4927 if (vb
&& 0 == strncmp(vb
->ptr
, "Microsoft-WebDAV-MiniRedir/",
4928 sizeof("Microsoft-WebDAV-MiniRedir/")-1)) {
4929 /* workaround Microsoft-WebDAV-MiniRedir bug */
4930 /* (might not be necessary for PROPPATCH here,
4931 * but match behavior in mod_webdav_propfind() for PROPFIND) */
4932 http_response_redirect_to_directory(pconf
->srv
, con
, 308);
4933 return HANDLER_FINISHED
;
4935 /* set "Content-Location" instead of sending 308 redirect to dir */
4936 if (!http_response_redirect_to_directory(pconf
->srv
, con
, 0))
4937 return HANDLER_FINISHED
;
4938 buffer_append_string_len(con
->physical
.path
, CONST_STR_LEN("/"));
4939 buffer_append_string_len(con
->physical
.rel_path
,CONST_STR_LEN("/"));
4942 else if (con
->physical
.path
->ptr
[con
->physical
.path
->used
- 2] == '/') {
4943 http_status_set_error(con
, 403);
4944 return HANDLER_FINISHED
;
4947 xmlDocPtr
const xml
= webdav_parse_chunkqueue(con
, pconf
);
4949 http_status_set_error(con
, 400); /* Bad Request */
4950 return HANDLER_FINISHED
;
4953 const xmlNode
* const rootnode
= xmlDocGetRootElement(xml
);
4954 if (NULL
== rootnode
4955 || 0 != webdav_xmlstrcmp_fixed(rootnode
->name
, "propertyupdate")) {
4956 http_status_set_error(con
, 422); /* Unprocessable Entity */
4958 return HANDLER_FINISHED
;
4961 if (!webdav_db_transaction_begin_immediate(pconf
)) {
4962 http_status_set_error(con
, 500); /* Internal Server Error */
4964 return HANDLER_FINISHED
;
4967 /* NOTE: selectively providing multi-status response is NON-CONFORMANT
4968 * (specified in [RFC4918])
4969 * However, PROPPATCH is all-or-nothing, so client should be able to
4970 * unequivocably know that all items in PROPPATCH succeeded if it receives
4971 * 204 No Content, or that items that are not listed with a failure status
4972 * in a multi-status response have the status of 424 Failed Dependency,
4973 * without the server having to be explicit. */
4975 /* UPDATE request, we know 'set' and 'remove' */
4976 buffer
*ms
= NULL
; /*(multi-status)*/
4978 for (const xmlNode
*cmd
= rootnode
->children
; cmd
; cmd
= cmd
->next
) {
4979 if (!(update
= (0 == webdav_xmlstrcmp_fixed(cmd
->name
, "set")))) {
4980 if (0 != webdav_xmlstrcmp_fixed(cmd
->name
, "remove"))
4981 continue; /* skip; not "set" or "remove" */
4984 for (const xmlNode
*props
= cmd
->children
; props
; props
= props
->next
) {
4985 if (0 != webdav_xmlstrcmp_fixed(props
->name
, "prop"))
4988 const xmlNode
*prop
= props
->children
;
4989 /* libxml2 will keep those blank (whitespace only) nodes */
4990 while (NULL
!= prop
&& xmlIsBlankNode(prop
))
4994 if (prop
->ns
&& '\0' == *(char *)prop
->ns
->href
4995 && '\0' != *(char *)prop
->ns
->prefix
) {
4996 /* error: missing namespace for property */
4997 log_error(con
->errh
, __FILE__
, __LINE__
,
4998 "no namespace for: %s", prop
->name
);
4999 if (!ms
) ms
= buffer_init(); /* Unprocessable Entity */
5000 webdav_xml_propstat_status(ms
, "", (char *)prop
->name
, 422);
5004 /* XXX: ??? should blank namespace be normalized to "DAV:" ???
5005 * ??? should this also be done in propfind requests ??? */
5007 if (prop
->ns
&& 0 == strcmp((char *)prop
->ns
->href
, "DAV:")) {
5008 const size_t namelen
= strlen((char *)prop
->name
);
5009 const struct live_prop_list
*list
= protected_props
;
5010 while (0 != list
->len
5011 && (list
->len
!= namelen
5012 || 0 != memcmp(prop
->name
, list
->prop
, list
->len
)))
5014 if (NULL
!= list
->prop
) {
5015 /* error <DAV:cannot-modify-protected-property/> */
5016 if (!ms
) ms
= buffer_init();
5017 webdav_xml_propstat_protected(ms
, (char *)prop
->name
,
5018 namelen
, 403); /* Forbidden */
5024 if (!prop
->children
) continue;
5025 char * const propval
= prop
->children
5026 ? (char *)xmlNodeListGetString(xml
, prop
->children
, 0)
5028 webdav_prop_update(pconf
, con
->physical
.rel_path
,
5030 prop
->ns
? (char *)prop
->ns
->href
: "",
5031 propval
? propval
: "");
5035 webdav_prop_delete(pconf
, con
->physical
.rel_path
,
5037 prop
->ns
? (char *)prop
->ns
->href
: "");
5042 ? webdav_db_transaction_commit(pconf
)
5043 : webdav_db_transaction_rollback(pconf
)) {
5045 buffer
*vb
= http_header_request_get(con
, HTTP_HEADER_OTHER
,
5046 CONST_STR_LEN("User-Agent"));
5047 if (vb
&& 0 == strncmp(vb
->ptr
, "Microsoft-WebDAV-MiniRedir/",
5048 sizeof("Microsoft-WebDAV-MiniRedir/")-1)) {
5049 /* workaround Microsoft-WebDAV-MiniRedir bug; 204 not handled */
5050 ms
= buffer_init(); /* 207 Multi-status */
5051 webdav_xml_response_status(ms
, con
->physical
.path
, 200);
5055 http_status_set_fin(con
, 204); /* No Content */
5056 else /* 207 Multi-status */
5057 webdav_xml_doc_multistatus_response(con
, pconf
, ms
);
5060 http_status_set_error(con
, 500); /* Internal Server Error */
5066 return HANDLER_FINISHED
;
5072 struct webdav_conflicting_lock_st
{
5073 webdav_lockdata
*lockdata
;
5079 webdav_conflicting_lock_cb (void * const vdata
,
5080 const webdav_lockdata
* const lockdata
)
5082 /* lock is not available if someone else has exclusive lock or if
5083 * client requested exclusive lock and others have shared locks */
5084 struct webdav_conflicting_lock_st
* const cbdata
=
5085 (struct webdav_conflicting_lock_st
*)vdata
;
5086 if (lockdata
->lockscope
->used
== sizeof("exclusive")
5087 || cbdata
->lockdata
->lockscope
->used
== sizeof("exclusive"))
5088 webdav_xml_href(cbdata
->b
, &lockdata
->lockroot
);
5093 mod_webdav_lock (connection
* const con
, const plugin_config
* const pconf
)
5096 * a mac wants to write
5098 * LOCK /dav/expire.txt HTTP/1.1\r\n
5099 * User-Agent: WebDAVFS/1.3 (01308000) Darwin/8.1.0 (Power Macintosh)\r\n
5102 * Timeout: Second-600\r\n
5103 * Content-Type: text/xml; charset=\"utf-8\"\r\n
5104 * Content-Length: 229\r\n
5105 * Connection: keep-alive\r\n
5106 * Host: 192.168.178.23:1025\r\n
5108 * <?xml version=\"1.0\" encoding=\"utf-8\"?>\n
5109 * <D:lockinfo xmlns:D=\"DAV:\">\n
5110 * <D:lockscope><D:exclusive/></D:lockscope>\n
5111 * <D:locktype><D:write/></D:locktype>\n
5113 * <D:href>http://www.apple.com/webdav_fs/</D:href>\n
5118 if (con
->request
.content_length
) {
5119 if (con
->state
== CON_STATE_READ_POST
) {
5120 handler_t rc
= connection_handle_read_post_state(pconf
->srv
, con
);
5121 if (rc
!= HANDLER_GO_ON
) return rc
;
5125 /* XXX: maybe add config switch to require that authentication occurred? */
5126 buffer owner
= { NULL
, 0, 0 };/*owner (not authenticated)(auth_user unset)*/
5127 data_string
* const authn_user
= (data_string
*)
5128 array_get_element_klen(con
->environment
, CONST_STR_LEN("REMOTE_USER"));
5130 /* future: make max timeout configurable (e.g. pconf->lock_timeout_max)
5132 * [RFC4918] 10.7 Timeout Request Header
5133 * The "Second" TimeType specifies the number of seconds that will elapse
5134 * between granting of the lock at the server, and the automatic removal
5135 * of the lock. The timeout value for TimeType "Second" MUST NOT be
5136 * greater than 2^32-1.
5139 webdav_lockdata lockdata
= {
5140 { NULL
, 0, 0 }, /* locktoken */
5141 { con
->physical
.rel_path
->ptr
,con
->physical
.rel_path
->used
,0},/*lockroot*/
5142 { NULL
, 0, 0 }, /* ownerinfo */
5143 (authn_user
? authn_user
->value
: &owner
), /* owner */
5144 NULL
, /* lockscope */
5145 NULL
, /* locktype */
5147 600 /* timeout (arbitrary default lock timeout: 10 minutes) */
5151 http_header_request_get(con
, HTTP_HEADER_OTHER
, CONST_STR_LEN("Timeout"));
5152 if (!buffer_is_empty(h
)) {
5153 /* loosely parse Timeout request header and ignore "infinity" timeout */
5154 /* future: might implement config param for upper limit for timeout */
5155 const char *p
= h
->ptr
;
5157 if ((*p
| 0x20) == 's'
5158 && 0 == strncasecmp(p
, CONST_STR_LEN("second-"))) {
5159 long t
= strtol(p
+sizeof("second-")-1, NULL
, 10);
5160 if (0 < t
&& t
< lockdata
.timeout
)
5161 lockdata
.timeout
= t
> 5 ? t
: 5;
5162 /*(arbitrary min timeout: 5 secs)*/
5163 else if (sizeof(long) != sizeof(int) && t
> INT32_MAX
)
5164 lockdata
.timeout
= INT32_MAX
;
5165 /* while UINT32_MAX is actual limit in RFC4918,
5166 * sqlite more easily supports int, though could be
5167 * changed to use int64 to for the timeout param.
5168 * The "limitation" between timeouts that are many
5169 * *years* long does not really matter in reality. */
5173 else if ((*p
| 0x20) == 'i'
5174 && 0 == strncasecmp(p
, CONST_STR_LEN("infinity"))) {
5175 lockdata
.timeout
= INT32_MAX
;
5179 while (*p
!= ',' && *p
!= '\0') ++p
;
5180 while (*p
== ' ' || *p
== '\t') ++p
;
5181 } while (*p
!= '\0');
5184 if (con
->request
.content_length
) {
5185 lockdata
.depth
= webdav_parse_Depth(con
);
5186 if (1 == lockdata
.depth
) {
5187 /* [RFC4918] 9.10.3 Depth and Locking
5188 * Values other than 0 or infinity MUST NOT be used
5189 * with the Depth header on a LOCK method.
5191 http_status_set_error(con
, 400); /* Bad Request */
5192 return HANDLER_FINISHED
;
5195 xmlDocPtr
const xml
= webdav_parse_chunkqueue(con
, pconf
);
5197 http_status_set_error(con
, 400); /* Bad Request */
5198 return HANDLER_FINISHED
;
5201 const xmlNode
* const rootnode
= xmlDocGetRootElement(xml
);
5202 if (NULL
== rootnode
5203 || 0 != webdav_xmlstrcmp_fixed(rootnode
->name
, "lockinfo")) {
5204 http_status_set_error(con
, 422); /* Unprocessable Entity */
5206 return HANDLER_FINISHED
;
5209 const xmlNode
*lockinfo
= rootnode
->children
;
5210 for (; lockinfo
; lockinfo
= lockinfo
->next
) {
5211 if (0 == webdav_xmlstrcmp_fixed(lockinfo
->name
, "lockscope")) {
5212 const xmlNode
*value
= lockinfo
->children
;
5213 for (; value
; value
= value
->next
) {
5214 if (0 == webdav_xmlstrcmp_fixed(value
->name
, "exclusive"))
5215 lockdata
.lockscope
=(const buffer
*)&lockscope_exclusive
;
5216 else if (0 == webdav_xmlstrcmp_fixed(value
->name
, "shared"))
5217 lockdata
.lockscope
=(const buffer
*)&lockscope_shared
;
5219 lockdata
.lockscope
=NULL
; /* trigger error below loop */
5224 else if (0 == webdav_xmlstrcmp_fixed(lockinfo
->name
, "locktype")) {
5225 const xmlNode
*value
= lockinfo
->children
;
5226 for (; value
; value
= value
->next
) {
5227 if (0 == webdav_xmlstrcmp_fixed(value
->name
, "write"))
5228 lockdata
.locktype
= (const buffer
*)&locktype_write
;
5230 lockdata
.locktype
= NULL
;/* trigger error below loop */
5235 else if (0 == webdav_xmlstrcmp_fixed(lockinfo
->name
, "owner")) {
5236 if (lockinfo
->children
)
5237 lockdata
.ownerinfo
.ptr
=
5238 (char *)xmlNodeListGetString(xml
, lockinfo
->children
, 0);
5239 if (lockdata
.ownerinfo
.ptr
)
5240 lockdata
.ownerinfo
.used
= strlen(lockdata
.ownerinfo
.ptr
)+1;
5244 do { /*(resources are cleaned up after code block)*/
5246 if (NULL
== lockdata
.lockscope
|| NULL
== lockdata
.locktype
) {
5247 /*(missing lockscope and locktype in lock request)*/
5248 http_status_set_error(con
, 422); /* Unprocessable Entity */
5249 break; /* clean up resources and return HANDLER_FINISHED */
5252 /* check lock prior to potentially creating new resource,
5253 * and prior to using entropy to create uuid */
5254 struct webdav_conflicting_lock_st cbdata
;
5255 cbdata
.lockdata
= &lockdata
;
5256 cbdata
.b
= buffer_init();
5257 webdav_lock_activelocks(pconf
, &lockdata
.lockroot
,
5258 (0 == lockdata
.depth
? 1 : -1),
5259 webdav_conflicting_lock_cb
, &cbdata
);
5260 if (0 != cbdata
.b
->used
) {
5262 webdav_xml_doc_error_no_conflicting_lock(con
, cbdata
.b
);
5263 buffer_free(cbdata
.b
);
5264 break; /* clean up resources and return HANDLER_FINISHED */
5266 buffer_free(cbdata
.b
);
5270 if (0 != lstat(con
->physical
.path
->ptr
, &st
)) {
5271 /* [RFC4918] 7.3 Write Locks and Unmapped URLs
5272 * A successful lock request to an unmapped URL MUST result in
5273 * the creation of a locked (non-collection) resource with empty
5276 * The response MUST indicate that a resource was created, by
5277 * use of the "201 Created" response code (a LOCK request to an
5278 * existing resource instead will result in 200 OK).
5279 * [RFC4918] 9.10.4 Locking Unmapped URLs
5280 * A successful LOCK method MUST result in the creation of an
5281 * empty resource that is locked (and that is not a collection)
5282 * when a resource did not previously exist at that URL. Later on,
5283 * the lock may go away but the empty resource remains. Empty
5284 * resources MUST then appear in PROPFIND responses including that
5285 * URL in the response scope. A server MUST respond successfully
5286 * to a GET request to an empty resource, either by using a 204
5287 * No Content response, or by using 200 OK with a Content-Length
5288 * header indicating zero length
5290 * unmapped resource; create empty file
5291 * (open() should fail if path ends in '/', but does not on some OS.
5292 * This is desired behavior since collection should be created
5293 * with MKCOL, and not via LOCK on an unmapped resource) */
5296 && con
->physical
.path
->ptr
[con
->physical
.path
->used
-2] != '/')
5297 ? fdevent_open_cloexec(con
->physical
.path
->ptr
, 0,
5298 O_WRONLY
| O_CREAT
| O_EXCL
| O_TRUNC
,
5302 /*(skip sending etag if fstat() error; not expected)*/
5303 if (0 != fstat(fd
, &st
)) con
->etag_flags
= 0;
5307 else if (errno
!= EEXIST
) {
5308 http_status_set_error(con
, 403); /* Forbidden */
5309 break; /* clean up resources and return HANDLER_FINISHED */
5311 else if (0 != lstat(con
->physical
.path
->ptr
, &st
)) {
5312 http_status_set_error(con
, 403); /* Forbidden */
5313 break; /* clean up resources and return HANDLER_FINISHED */
5315 lockdata
.depth
= 0; /* force Depth: 0 on non-collections */
5319 if (0 != webdav_if_match_or_unmodified_since(con
, &st
)) {
5320 http_status_set_error(con
, 412); /* Precondition Failed */
5321 break; /* clean up resources and return HANDLER_FINISHED */
5327 else if (S_ISDIR(st
.st_mode
)) {
5328 if (con
->physical
.path
->ptr
[con
->physical
.path
->used
- 2] != '/') {
5329 /* 308 Permanent Redirect */
5330 http_response_redirect_to_directory(pconf
->srv
, con
, 308);
5331 break; /* clean up resources and return HANDLER_FINISHED */
5332 /* Alternatively, could append '/' to con->physical.path
5333 * and con->physical.rel_path, set Content-Location in
5334 * response headers, and continue to serve the request */
5337 else if (con
->physical
.path
->ptr
[con
->physical
.path
->used
- 2] == '/') {
5338 http_status_set_error(con
, 403); /* Forbidden */
5339 break; /* clean up resources and return HANDLER_FINISHED */
5341 else if (0 != lockdata
.depth
)
5342 lockdata
.depth
= 0; /* force Depth: 0 on non-collections */
5345 * (uuid_unparse() output is 36 chars + '\0') */
5347 char lockstr
[sizeof("<urn:uuid:>") + 36] = "<urn:uuid:";
5348 lockdata
.locktoken
.ptr
= lockstr
+1; /*(without surrounding <>)*/
5349 lockdata
.locktoken
.used
= sizeof(lockstr
)-2;/*(without surrounding <>)*/
5351 uuid_unparse(id
, lockstr
+sizeof("<urn:uuid:")-1);
5353 /* XXX: consider fix TOC-TOU race condition by starting transaction
5354 * and re-running webdav_lock_activelocks() check before running
5355 * webdav_lock_acquire() (but both routines would need to be modified
5356 * to defer calling sqlite3_reset(stmt) to be part of transaction) */
5357 if (webdav_lock_acquire(pconf
, &lockdata
)) {
5358 lockstr
[sizeof(lockstr
)-2] = '>';
5359 http_header_response_set(con
, HTTP_HEADER_OTHER
,
5360 CONST_STR_LEN("Lock-Token"),
5361 lockstr
, sizeof(lockstr
)-1);
5362 webdav_xml_doc_lock_acquired(con
, pconf
, &lockdata
);
5363 if (0 != con
->etag_flags
&& !S_ISDIR(st
.st_mode
))
5364 webdav_response_etag(pconf
->srv
, con
, &st
);
5365 http_status_set_fin(con
, created
? 201 : 200); /* Created | OK */
5367 else /*(database error obtaining lock)*/
5368 http_status_set_error(con
, 500); /* Internal Server Error */
5370 } while (0); /*(resources are cleaned up after code block)*/
5372 xmlFree(lockdata
.ownerinfo
.ptr
);
5374 return HANDLER_FINISHED
;
5377 h
= http_header_request_get(con
,HTTP_HEADER_OTHER
,CONST_STR_LEN("If"));
5379 || h
->used
< 6 || h
->ptr
[1] != '<' || h
->ptr
[h
->used
-3] != '>') {
5380 /*(rejects value with trailing LWS, even though RFC-permitted)*/
5381 http_status_set_error(con
, 400); /* Bad Request */
5382 return HANDLER_FINISHED
;
5384 /* remove (< >) around token */
5385 lockdata
.locktoken
.ptr
= h
->ptr
+2;
5386 lockdata
.locktoken
.used
= h
->used
-4;
5387 /*(future: fill in from database, though exclusive write lock is the
5388 * only lock supported at the moment)*/
5389 lockdata
.lockscope
= (const buffer
*)&lockscope_exclusive
;
5390 lockdata
.locktype
= (const buffer
*)&locktype_write
;
5393 if (webdav_lock_refresh(pconf
, &lockdata
)) {
5394 webdav_xml_doc_lock_acquired(con
, pconf
, &lockdata
);
5395 http_status_set_fin(con
, 200); /* OK */
5398 http_status_set_error(con
, 412); /* Precondition Failed */
5400 return HANDLER_FINISHED
;
5408 mod_webdav_unlock (connection
* const con
, const plugin_config
* const pconf
)
5410 const buffer
* const h
=
5411 http_header_request_get(con
, HTTP_HEADER_OTHER
,
5412 CONST_STR_LEN("Lock-Token"));
5414 || h
->used
< 4 || h
->ptr
[0] != '<' || h
->ptr
[h
->used
-2] != '>') {
5415 /*(rejects value with trailing LWS, even though RFC-permitted)*/
5416 http_status_set_error(con
, 400); /* Bad Request */
5417 return HANDLER_FINISHED
;
5420 buffer owner
= { NULL
, 0, 0 };/*owner (not authenticated)(auth_user unset)*/
5421 data_string
* const authn_user
= (data_string
*)
5422 array_get_element_klen(con
->environment
, CONST_STR_LEN("REMOTE_USER"));
5424 webdav_lockdata lockdata
= {
5425 { h
->ptr
+1, h
->used
-2, 0 }, /* locktoken (remove < > around token) */
5426 { con
->physical
.rel_path
->ptr
,con
->physical
.rel_path
->used
,0},/*lockroot*/
5427 { NULL
, 0, 0 }, /* ownerinfo (unused for unlock) */
5428 (authn_user
? authn_user
->value
: &owner
), /* owner */
5429 NULL
, /* lockscope (unused for unlock) */
5430 NULL
, /* locktype (unused for unlock) */
5431 0, /* depth (unused for unlock) */
5432 0 /* timeout (unused for unlock) */
5435 /* check URI (lockroot) and depth in scope for locktoken and authorized */
5436 switch (webdav_lock_match(pconf
, &lockdata
)) {
5438 if (webdav_lock_release(pconf
, &lockdata
)) {
5439 http_status_set_fin(con
, 204); /* No Content */
5440 return HANDLER_FINISHED
;
5444 case -1: /* lock does not exist */
5445 case -2: /* URI not in scope of locktoken and depth */
5447 webdav_xml_doc_error_lock_token_matches_request_uri(con
);
5448 return HANDLER_FINISHED
;
5449 case -3: /* not owner/not authorized to remove lock */
5450 http_status_set_error(con
, 403); /* Forbidden */
5451 return HANDLER_FINISHED
;
5457 SUBREQUEST_FUNC(mod_webdav_subrequest_handler
)
5459 const plugin_config
* const pconf
=
5460 (plugin_config
*)con
->plugin_ctx
[((plugin_data
*)p_d
)->id
];
5461 if (NULL
== pconf
) return HANDLER_GO_ON
; /*(should not happen)*/
5464 switch (con
->request
.http_method
) {
5465 case HTTP_METHOD_PROPFIND
:
5466 return mod_webdav_propfind(con
, pconf
);
5467 case HTTP_METHOD_MKCOL
:
5468 return mod_webdav_mkcol(con
, pconf
);
5469 case HTTP_METHOD_DELETE
:
5470 return mod_webdav_delete(con
, pconf
);
5471 case HTTP_METHOD_PUT
:
5472 return mod_webdav_put(con
, pconf
);
5473 case HTTP_METHOD_MOVE
:
5474 case HTTP_METHOD_COPY
:
5475 return mod_webdav_copymove(con
, pconf
);
5476 #ifdef USE_PROPPATCH
5477 case HTTP_METHOD_PROPPATCH
:
5478 return mod_webdav_proppatch(con
, pconf
);
5481 case HTTP_METHOD_LOCK
:
5482 return mod_webdav_lock(con
, pconf
);
5483 case HTTP_METHOD_UNLOCK
:
5484 return mod_webdav_unlock(con
, pconf
);
5487 http_status_set_error(con
, 501); /* Not Implemented */
5488 return HANDLER_FINISHED
;
5493 PHYSICALPATH_FUNC(mod_webdav_physical_handler
)
5495 /* physical path is set up */
5496 /*assert(0 != con->physical.path->used);*/
5498 force_assert(2 <= con
->physical
.path
->used
);
5501 int check_readonly
= 0;
5502 int check_lock_src
= 0;
5503 int reject_reqbody
= 0;
5505 /* check for WebDAV request methods handled by this module */
5506 switch (con
->request
.http_method
) {
5507 case HTTP_METHOD_GET
:
5508 case HTTP_METHOD_HEAD
:
5509 case HTTP_METHOD_POST
:
5511 return HANDLER_GO_ON
;
5512 case HTTP_METHOD_PROPFIND
:
5513 case HTTP_METHOD_LOCK
:
5515 case HTTP_METHOD_UNLOCK
:
5518 case HTTP_METHOD_DELETE
:
5519 case HTTP_METHOD_MOVE
:
5520 reject_reqbody
= 1; /*(fall through)*/ __attribute_fallthrough__
5521 case HTTP_METHOD_PROPPATCH
:
5522 case HTTP_METHOD_PUT
:
5523 check_readonly
= check_lock_src
= 1;
5525 case HTTP_METHOD_COPY
:
5526 case HTTP_METHOD_MKCOL
:
5527 check_readonly
= reject_reqbody
= 1;
5531 plugin_config pconf
;
5532 mod_webdav_patch_connection(srv
, con
, (plugin_data
*)p_d
, &pconf
);
5533 if (!pconf
.enabled
) return HANDLER_GO_ON
;
5535 if (check_readonly
&& pconf
.is_readonly
) {
5536 http_status_set_error(con
, 403); /* Forbidden */
5537 return HANDLER_FINISHED
;
5540 if (reject_reqbody
&& con
->request
.content_length
) {
5541 /* [RFC4918] 8.4 Required Bodies in Requests
5542 * Servers MUST examine all requests for a body, even when a
5543 * body was not expected. In cases where a request body is
5544 * present but would be ignored by a server, the server MUST
5545 * reject the request with 415 (Unsupported Media Type).
5547 http_status_set_error(con
, 415); /* Unsupported Media Type */
5548 return HANDLER_FINISHED
;
5551 if (check_lock_src
&& !webdav_has_lock(con
, &pconf
, con
->physical
.rel_path
))
5552 return HANDLER_FINISHED
; /* 423 Locked */
5554 /* initial setup for methods */
5555 switch (con
->request
.http_method
) {
5556 case HTTP_METHOD_PUT
:
5557 if (mod_webdav_put_prep(con
, &pconf
) == HANDLER_FINISHED
)
5558 return HANDLER_FINISHED
;
5564 con
->mode
= ((plugin_data
*)p_d
)->id
;
5565 con
->conf
.stream_request_body
= 0;
5566 con
->plugin_ctx
[((plugin_data
*)p_d
)->id
] = &pconf
;
5567 const handler_t rc
=
5568 mod_webdav_subrequest_handler(srv
, con
, p_d
); /*p->handle_subrequest()*/
5569 if (rc
== HANDLER_FINISHED
|| rc
== HANDLER_ERROR
)
5570 con
->plugin_ctx
[((plugin_data
*)p_d
)->id
] = NULL
;
5571 else { /* e.g. HANDLER_WAIT_FOR_RD */
5572 plugin_config
* const save_pconf
=
5573 (plugin_config
*)malloc(sizeof(pconf
));
5574 force_assert(save_pconf
);
5575 memcpy(save_pconf
, &pconf
, sizeof(pconf
));
5576 con
->plugin_ctx
[((plugin_data
*)p_d
)->id
] = save_pconf
;
5582 CONNECTION_FUNC(mod_webdav_handle_reset
) {
5583 /* free plugin_config if allocated and saved to per-request storage */
5584 void ** const restrict dptr
= &con
->plugin_ctx
[((plugin_data
*)p_d
)->id
];
5588 chunkqueue_set_tempdirs(con
->request_content_queue
, /* reset sz */
5589 con
->request_content_queue
->tempdirs
, 0);
5592 return HANDLER_GO_ON
;