lighttpd 1.4.x
https://www.lighttpd.net/
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
5670 lines
207 KiB
5670 lines
207 KiB
/* |
|
* mod_webdav |
|
*/ |
|
|
|
/* |
|
* Note: This plugin is a basic implementation of [RFC4918] WebDAV |
|
* |
|
* Version Control System (VCS) backing WebDAV is recommended instead |
|
* and Subversion (svn) is one such VCS supporting WebDAV. |
|
* |
|
* status: *** EXPERIMENTAL *** (and likely insecure encoding/decoding) |
|
* |
|
* future: |
|
* |
|
* TODO: moving props should delete any existing props instead of |
|
* preserving any that are not overwritten with UPDATE OR REPLACE |
|
* (and, if merging directories, be careful when doing so) |
|
* TODO: add proper support for locks with "shared" lockscope |
|
* (instead of treating match of any shared lock as sufficient, |
|
* even when there are different lockroots) |
|
* TODO: does libxml2 xml-decode (html-decode), |
|
* or must I do so to normalize input? |
|
* TODO: add strict enforcement of property names to be valid XML tags |
|
* (or encode as such before putting into database) |
|
* & " < > |
|
* TODO: should we be using xmlNodeListGetString() or xmlBufNodeDump() |
|
* and how does it handle encoding and entity-inlining of external refs? |
|
* Must xml decode/encode (normalize) before storing user data in database |
|
* if going to add that data verbatim to xml doc returned in queries |
|
* TODO: when walking xml nodes, should add checks for "DAV:" namespace |
|
* TODO: consider where it might be useful/informative to check |
|
* SQLITE_OK != sqlite3_reset() or SQLITE_OK != sqlite3_bind_...() or ... |
|
* (in some cases no rows returned is ok, while in other cases it is not) |
|
* TODO: Unsupported: !con->conf.follow_symlink is not currently honored; |
|
* symlinks are followed. Supporting !con->conf.follow_symlinks would |
|
* require operating system support for POSIX.1-2008 *at() commands, |
|
* and reworking the mod_webdav code to exclusively operate with *at() |
|
* commands, for example, replacing unlink() with unlinkat(). |
|
* |
|
* RFE: add config option whether or not to include locktoken and ownerinfo |
|
* in PROPFIND lockdiscovery |
|
* |
|
* deficiencies |
|
* - incomplete "shared" lock support |
|
* - review code for proper decoding/encoding of elements from/to XML and db |
|
* - preserve XML info in scope on dead properties, e.g. xml:lang |
|
* |
|
* [RFC4918] 4.3 Property Values |
|
* Servers MUST preserve the following XML Information Items (using the |
|
* terminology from [REC-XML-INFOSET]) in storage and transmission of dead |
|
* properties: ... |
|
* [RFC4918] 14.26 set XML Element |
|
* The 'set' element MUST contain only a 'prop' element. The elements |
|
* contained by the 'prop' element inside the 'set' element MUST specify the |
|
* name and value of properties that are set on the resource identified by |
|
* Request-URI. If a property already exists, then its value is replaced. |
|
* Language tagging information appearing in the scope of the 'prop' element |
|
* (in the "xml:lang" attribute, if present) MUST be persistently stored along |
|
* with the property, and MUST be subsequently retrievable using PROPFIND. |
|
* [RFC4918] F.2 Changes for Server Implementations |
|
* Strengthened server requirements for storage of property values, in |
|
* particular persistence of language information (xml:lang), whitespace, and |
|
* XML namespace information (see Section 4.3). |
|
* |
|
* resource usage containment |
|
* - filesystem I/O operations might take a non-trivial amount of time, |
|
* blocking the server from responding to other requests during this time. |
|
* Potential solution: have a thread dedicated to handling webdav requests |
|
* and serialize such requests in each thread dedicated to handling webdav. |
|
* (Limit number of such dedicated threads.) Remove write interest from |
|
* connection during this period so that server will not trigger any timeout |
|
* on the connection. |
|
* - recursive directory operations are depth-first and may consume a large |
|
* number of file descriptors if the directory hierarchy is deep. |
|
* Potential solution: serialize webdav requests into dedicated thread (above) |
|
* Potential solution: perform breadth-first directory traversal and pwrite() |
|
* directory paths into a temporary file. After reading each directory, |
|
* close() the dirhandle and pread() next directory from temporary file. |
|
* (Keeping list of directories in memory might result in large memory usage) |
|
* - flush response to client (or to intermediate temporary file) at regular |
|
* intervals or triggers to avoid response consume large amount of memory |
|
* during operations on large collection hierarchies (with lots of nested |
|
* directories) |
|
* |
|
* beware of security concerns involved in enabling WebDAV |
|
* on publicly accessible servers |
|
* - (general) [RFC4918] 20 Security Considersations |
|
* - (specifically) [RFC4918] 20.6 Implications of XML Entities |
|
* - TODO review usage of xml libs for security, resource usage, containment |
|
* libxml2 vs expat vs ... |
|
* http://xmlbench.sourceforge.net/ |
|
* http://stackoverflow.com/questions/399704/xml-parser-for-c |
|
* http://tibleiz.net/asm-xml/index.html |
|
* http://dev.yorhel.nl/yxml |
|
* - how might mod_webdav be affected by mod_openssl setting REMOTE_USER? |
|
* - when encoding href in responses, also ensure proper XML encoding |
|
* (do we need to ENCODING_REL_URI and then ENCODING_MINIMAL_XML?) |
|
* - TODO: any (non-numeric) data that goes into database should be encoded |
|
* before being sent back to user, not just href. Perhaps anything that |
|
* is not an href should be stored in database in XML-encoded form. |
|
* |
|
* consider implementing a set of (reasonable) limits on such things as |
|
* - max number of collections |
|
* - max number of objects in a collection |
|
* - max number of properties per object |
|
* - max length of property name |
|
* - max length of property value |
|
* - max length of locktoken, lockroot, ownerinfo |
|
* - max number of locks held by a client, or by an owner |
|
* - max number of locks on a resource (shared locks) |
|
* - ... |
|
* |
|
* robustness |
|
* - should check return value from sqlite3_reset(stmt) for REPLACE, UPDATE, |
|
* DELETE statements (which is when commit occurs and locks are obtained/fail) |
|
* - handle SQLITE_BUSY (e.g. other processes modifying db and hold locks) |
|
* https://www.sqlite.org/lang_transaction.html |
|
* https://www.sqlite.org/rescode.html#busy |
|
* https://www.sqlite.org/c3ref/busy_handler.html |
|
* https://www.sqlite.org/c3ref/busy_timeout.html |
|
* - periodically execute query to delete expired locks |
|
* (MOD_WEBDAV_SQLITE_LOCKS_DELETE_EXPIRED) |
|
* (should defend against removing locks protecting long-running operations |
|
* that are in progress on the server) |
|
* - having all requests go through database, including GET and HEAD would allow |
|
* for better transactional semantics, instead of the current race conditions |
|
* inherent in multiple (and many) filesystem operations. All queries would |
|
* go through database, which would map to objects on disk, and copy and move |
|
* would simply be database entries to objects with reference counts and |
|
* copy-on-write semantics (instead of potential hard-links on disk). |
|
* lstat() information could also be stored in database. Right now, if a file |
|
* is copied or moved or deleted, the status of the property update in the db |
|
* is discarded, whether it succeeds or not, since file operation succeeded. |
|
* (Then again, it might also be okay if props do not exist on a given file.) |
|
* On the other hand, if everything went through database, then etag could be |
|
* stored in database and could be updated upon PUT (or MOVE/COPY/DELETE). |
|
* There would also need to be a way to trigger a rescan of filesystem to |
|
* bring the database into sync with any out-of-band changes. |
|
* |
|
* |
|
* notes: |
|
* |
|
* - lstat() used instead of stat_cache_*() since the stat_cache might have |
|
* expired data, as stat_cache is not invalidated by outside modifications, |
|
* such as WebDAV PUT method (unless FAM is used) |
|
* |
|
* - SQLite database can be run in WAL mode (https://sqlite.org/wal.html) |
|
* though mod_webdav does not provide a mechanism to configure WAL. |
|
* Instead, once lighttpd starts up mod_webdav and creates the database, |
|
* set WAL mode on the database from the command and then restart lighttpd. |
|
*/ |
|
|
|
|
|
/* linkat() fstatat() unlinkat() fdopendir() NAME_MAX */ |
|
#if !defined(_XOPEN_SOURCE) || _XOPEN_SOURCE-0 < 700 |
|
#undef _XOPEN_SOURCE |
|
#define _XOPEN_SOURCE 700 |
|
#endif |
|
/* DT_UNKNOWN DTTOIF() */ |
|
#ifndef _GNU_SOURCE |
|
#define _GNU_SOURCE |
|
#endif |
|
|
|
#include "first.h" /* first */ |
|
#include "sys-mmap.h" |
|
#include <sys/types.h> |
|
#include <sys/stat.h> |
|
#include <dirent.h> |
|
#include <errno.h> |
|
#include <fcntl.h> |
|
#include <stdio.h> /* rename() */ |
|
#include <stdlib.h> /* strtol() */ |
|
#include <string.h> |
|
#include <unistd.h> /* getpid() linkat() rmdir() unlinkat() */ |
|
|
|
#ifndef _D_EXACT_NAMLEN |
|
#ifdef _DIRENT_HAVE_D_NAMLEN |
|
#define _D_EXACT_NAMLEN(d) ((d)->d_namlen) |
|
#else |
|
#define _D_EXACT_NAMLEN(d) (strlen ((d)->d_name)) |
|
#endif |
|
#endif |
|
|
|
#if defined(HAVE_LIBXML_H) && defined(HAVE_SQLITE3_H) |
|
|
|
#define USE_PROPPATCH |
|
/* minor: libxml2 includes stdlib.h in headers, too */ |
|
#include <libxml/tree.h> |
|
#include <libxml/parser.h> |
|
#include <sqlite3.h> |
|
|
|
#if defined(HAVE_LIBUUID) && defined(HAVE_UUID_UUID_H) |
|
#define USE_LOCKS |
|
#include <uuid/uuid.h> |
|
#endif |
|
|
|
#endif /* defined(HAVE_LIBXML_H) && defined(HAVE_SQLITE3_H) */ |
|
|
|
#include "base.h" |
|
#include "buffer.h" |
|
#include "chunk.h" |
|
#include "fdevent.h" |
|
#include "http_header.h" |
|
#include "etag.h" |
|
#include "log.h" |
|
#include "connections.h"/* connection_handle_read_post_state() */ |
|
#include "request.h" |
|
#include "response.h" /* http_response_redirect_to_directory() */ |
|
#include "stat_cache.h" /* stat_cache_mimetype_by_ext() */ |
|
|
|
#include "plugin.h" |
|
|
|
#define http_status_get(con) ((con)->http_status) |
|
#define http_status_set_fin(con, code) ((con)->file_finished = 1, \ |
|
(con)->mode = DIRECT, \ |
|
(con)->http_status = (code)) |
|
#define http_status_set(con, code) ((con)->http_status = (code)) |
|
#define http_status_unset(con) ((con)->http_status = 0) |
|
#define http_status_is_set(con) (0 != (con)->http_status) |
|
__attribute_cold__ |
|
__attribute_noinline__ |
|
static int http_status_set_error (connection *con, int status) { |
|
return http_status_set_fin(con, status); |
|
} |
|
|
|
typedef physical physical_st; |
|
|
|
INIT_FUNC(mod_webdav_init); |
|
FREE_FUNC(mod_webdav_free); |
|
SETDEFAULTS_FUNC(mod_webdav_set_defaults); |
|
SERVER_FUNC(mod_webdav_worker_init); |
|
URIHANDLER_FUNC(mod_webdav_uri_handler); |
|
PHYSICALPATH_FUNC(mod_webdav_physical_handler); |
|
SUBREQUEST_FUNC(mod_webdav_subrequest_handler); |
|
CONNECTION_FUNC(mod_webdav_handle_reset); |
|
|
|
int mod_webdav_plugin_init(plugin *p); |
|
int mod_webdav_plugin_init(plugin *p) { |
|
p->version = LIGHTTPD_VERSION_ID; |
|
p->name = "webdav"; |
|
|
|
p->init = mod_webdav_init; |
|
p->cleanup = mod_webdav_free; |
|
p->set_defaults = mod_webdav_set_defaults; |
|
p->worker_init = mod_webdav_worker_init; |
|
p->handle_uri_clean = mod_webdav_uri_handler; |
|
p->handle_physical = mod_webdav_physical_handler; |
|
p->handle_subrequest = mod_webdav_subrequest_handler; |
|
p->connection_reset = mod_webdav_handle_reset; |
|
|
|
return 0; |
|
} |
|
|
|
|
|
#define WEBDAV_FILE_MODE S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH|S_IWOTH |
|
#define WEBDAV_DIR_MODE S_IRWXU|S_IRWXG|S_IRWXO |
|
|
|
#define WEBDAV_FLAG_LC_NAMES 0x01 |
|
#define WEBDAV_FLAG_OVERWRITE 0x02 |
|
#define WEBDAV_FLAG_MOVE_RENAME 0x04 |
|
#define WEBDAV_FLAG_COPY_LINK 0x08 |
|
#define WEBDAV_FLAG_MOVE_XDEV 0x10 |
|
#define WEBDAV_FLAG_COPY_XDEV 0x20 |
|
|
|
#define webdav_xmlstrcmp_fixed(s, fixed) \ |
|
strncmp((const char *)(s), (fixed), sizeof(fixed)) |
|
|
|
#include <ctype.h> /* isupper() tolower() */ |
|
static void |
|
webdav_str_len_to_lower (char * const restrict s, const uint32_t len) |
|
{ |
|
/*(caller must ensure that len not truncated to (int); |
|
* for current intended use, NAME_MAX typically <= 255)*/ |
|
for (int i = 0; i < (int)len; ++i) { |
|
if (isupper(s[i])) |
|
s[i] = tolower(s[i]); |
|
} |
|
} |
|
|
|
typedef struct { |
|
#ifdef USE_PROPPATCH |
|
sqlite3 *sqlh; |
|
sqlite3_stmt *stmt_props_select_propnames; |
|
sqlite3_stmt *stmt_props_select_props; |
|
sqlite3_stmt *stmt_props_select_prop; |
|
sqlite3_stmt *stmt_props_update_prop; |
|
sqlite3_stmt *stmt_props_delete_prop; |
|
|
|
sqlite3_stmt *stmt_props_copy; |
|
sqlite3_stmt *stmt_props_move; |
|
sqlite3_stmt *stmt_props_move_col; |
|
sqlite3_stmt *stmt_props_delete; |
|
|
|
sqlite3_stmt *stmt_locks_acquire; |
|
sqlite3_stmt *stmt_locks_refresh; |
|
sqlite3_stmt *stmt_locks_release; |
|
sqlite3_stmt *stmt_locks_read; |
|
sqlite3_stmt *stmt_locks_read_uri; |
|
sqlite3_stmt *stmt_locks_read_uri_infinity; |
|
sqlite3_stmt *stmt_locks_read_uri_members; |
|
sqlite3_stmt *stmt_locks_delete_uri; |
|
sqlite3_stmt *stmt_locks_delete_uri_col; |
|
#else |
|
int dummy; |
|
#endif |
|
} sql_config; |
|
|
|
typedef struct { |
|
unsigned short enabled; |
|
unsigned short is_readonly; |
|
unsigned short log_xml; |
|
unsigned short deprecated_unsafe_partial_put_compat; |
|
|
|
sql_config *sql; |
|
buffer *tmpb; |
|
buffer *sqlite_db_name; /* not used after worker init */ |
|
array *opts; |
|
} plugin_config; |
|
|
|
typedef struct { |
|
PLUGIN_DATA; |
|
plugin_config defaults; |
|
} plugin_data; |
|
|
|
|
|
INIT_FUNC(mod_webdav_init) { |
|
return calloc(1, sizeof(plugin_data)); |
|
} |
|
|
|
|
|
FREE_FUNC(mod_webdav_free) { |
|
plugin_data * const p = (plugin_data *)p_d; |
|
if (NULL == p->cvlist) return; |
|
/* (init i to 0 if global context; to 1 to skip empty global context) */ |
|
for (int i = !p->cvlist[0].v.u2[1], used = p->nconfig; i < used; ++i) { |
|
config_plugin_value_t *cpv = p->cvlist + p->cvlist[i].v.u2[0]; |
|
for (; -1 != cpv->k_id; ++cpv) { |
|
switch (cpv->k_id) { |
|
#ifdef USE_PROPPATCH |
|
case 0: /* webdav.sqlite-db-name */ |
|
if (cpv->vtype == T_CONFIG_LOCAL) { |
|
sql_config * const sql = cpv->v.v; |
|
if (!sql->sqlh) { |
|
free(sql); |
|
continue; |
|
} |
|
|
|
sqlite3_finalize(sql->stmt_props_select_propnames); |
|
sqlite3_finalize(sql->stmt_props_select_props); |
|
sqlite3_finalize(sql->stmt_props_select_prop); |
|
sqlite3_finalize(sql->stmt_props_update_prop); |
|
sqlite3_finalize(sql->stmt_props_delete_prop); |
|
sqlite3_finalize(sql->stmt_props_copy); |
|
sqlite3_finalize(sql->stmt_props_move); |
|
sqlite3_finalize(sql->stmt_props_move_col); |
|
sqlite3_finalize(sql->stmt_props_delete); |
|
|
|
sqlite3_finalize(sql->stmt_locks_acquire); |
|
sqlite3_finalize(sql->stmt_locks_refresh); |
|
sqlite3_finalize(sql->stmt_locks_release); |
|
sqlite3_finalize(sql->stmt_locks_read); |
|
sqlite3_finalize(sql->stmt_locks_read_uri); |
|
sqlite3_finalize(sql->stmt_locks_read_uri_infinity); |
|
sqlite3_finalize(sql->stmt_locks_read_uri_members); |
|
sqlite3_finalize(sql->stmt_locks_delete_uri); |
|
sqlite3_finalize(sql->stmt_locks_delete_uri_col); |
|
sqlite3_close(sql->sqlh); |
|
free(sql); |
|
} |
|
break; |
|
#endif |
|
default: |
|
break; |
|
} |
|
} |
|
} |
|
} |
|
|
|
|
|
static void mod_webdav_merge_config_cpv(plugin_config * const pconf, const config_plugin_value_t * const cpv) { |
|
switch (cpv->k_id) { /* index into static config_plugin_keys_t cpk[] */ |
|
case 0: /* webdav.sqlite-db-name */ |
|
if (cpv->vtype == T_CONFIG_LOCAL) |
|
pconf->sql = cpv->v.v; |
|
break; |
|
case 1: /* webdav.activate */ |
|
pconf->enabled = (unsigned short)cpv->v.u; |
|
break; |
|
case 2: /* webdav.is-readonly */ |
|
pconf->is_readonly = (unsigned short)cpv->v.u; |
|
break; |
|
case 3: /* webdav.log-xml */ |
|
pconf->log_xml = (unsigned short)cpv->v.u; |
|
break; |
|
case 4: /* webdav.opts */ |
|
if (cpv->vtype == T_CONFIG_LOCAL) |
|
pconf->deprecated_unsafe_partial_put_compat = |
|
(unsigned short)cpv->v.u; |
|
break; |
|
default:/* should not happen */ |
|
return; |
|
} |
|
} |
|
|
|
|
|
static void mod_webdav_merge_config(plugin_config * const pconf, const config_plugin_value_t *cpv) { |
|
do { |
|
mod_webdav_merge_config_cpv(pconf, cpv); |
|
} while ((++cpv)->k_id != -1); |
|
} |
|
|
|
|
|
static void mod_webdav_patch_config(connection * const con, plugin_data * const p, plugin_config * const pconf) { |
|
memcpy(pconf, &p->defaults, sizeof(plugin_config)); |
|
for (int i = 1, used = p->nconfig; i < used; ++i) { |
|
if (config_check_cond(con, (uint32_t)p->cvlist[i].k_id)) |
|
mod_webdav_merge_config(pconf, p->cvlist + p->cvlist[i].v.u2[0]); |
|
} |
|
} |
|
|
|
|
|
__attribute_cold__ |
|
static int mod_webdav_sqlite3_init (const char * restrict s, log_error_st *errh); |
|
|
|
SETDEFAULTS_FUNC(mod_webdav_set_defaults) { |
|
static const config_plugin_keys_t cpk[] = { |
|
{ CONST_STR_LEN("webdav.sqlite-db-name"), |
|
T_CONFIG_STRING, |
|
T_CONFIG_SCOPE_CONNECTION } |
|
,{ CONST_STR_LEN("webdav.activate"), |
|
T_CONFIG_BOOL, |
|
T_CONFIG_SCOPE_CONNECTION } |
|
,{ CONST_STR_LEN("webdav.is-readonly"), |
|
T_CONFIG_BOOL, |
|
T_CONFIG_SCOPE_CONNECTION } |
|
,{ CONST_STR_LEN("webdav.log-xml"), |
|
T_CONFIG_BOOL, |
|
T_CONFIG_SCOPE_CONNECTION } |
|
,{ CONST_STR_LEN("webdav.opts"), |
|
T_CONFIG_ARRAY_KVSTRING, |
|
T_CONFIG_SCOPE_CONNECTION } |
|
,{ NULL, 0, |
|
T_CONFIG_UNSET, |
|
T_CONFIG_SCOPE_UNSET } |
|
}; |
|
|
|
plugin_data * const p = p_d; |
|
if (!config_plugin_values_init(srv, p, cpk, "mod_webdav")) |
|
return HANDLER_ERROR; |
|
|
|
#ifdef USE_PROPPATCH |
|
int sqlrc = sqlite3_config(SQLITE_CONFIG_SINGLETHREAD); |
|
if (sqlrc != SQLITE_OK) { |
|
log_error(srv->errh, __FILE__, __LINE__, "sqlite3_config(): %s", |
|
sqlite3_errstr(sqlrc)); |
|
/*(performance option since our use is not threaded; not fatal)*/ |
|
/*return HANDLER_ERROR;*/ |
|
} |
|
#endif |
|
|
|
/* process and validate config directives |
|
* (init i to 0 if global context; to 1 to skip empty global context) */ |
|
for (int i = !p->cvlist[0].v.u2[1]; i < p->nconfig; ++i) { |
|
config_plugin_value_t *cpv = p->cvlist + p->cvlist[i].v.u2[0]; |
|
for (; -1 != cpv->k_id; ++cpv) { |
|
switch (cpv->k_id) { |
|
case 0: /* webdav.sqlite-db-name */ |
|
if (!buffer_string_is_empty(cpv->v.b)) { |
|
if (!mod_webdav_sqlite3_init(cpv->v.b->ptr, srv->errh)) |
|
return HANDLER_ERROR; |
|
} |
|
break; |
|
case 1: /* webdav.activate */ |
|
case 2: /* webdav.is-readonly */ |
|
case 3: /* webdav.log-xml */ |
|
break; |
|
case 4: /* webdav.opts */ |
|
for (uint32_t j = 0, used = cpv->v.a->used; j < used; ++j) { |
|
data_string *ds = (data_string *)cpv->v.a->data[j]; |
|
if (buffer_is_equal_string(&ds->key, |
|
CONST_STR_LEN("deprecated-unsafe-partial-put"))) { |
|
cpv->v.u = |
|
buffer_eq_slen(&ds->value,CONST_STR_LEN("enable")); |
|
cpv->vtype = T_CONFIG_LOCAL; |
|
continue; |
|
} |
|
log_error(srv->errh, __FILE__, __LINE__, |
|
"unrecognized webdav.opts: %.*s", |
|
BUFFER_INTLEN_PTR(&ds->key)); |
|
return HANDLER_ERROR; |
|
} |
|
break; |
|
default:/* should not happen */ |
|
break; |
|
} |
|
} |
|
} |
|
|
|
p->defaults.tmpb = srv->tmp_buf; |
|
|
|
/* initialize p->defaults from global config context */ |
|
if (p->nconfig > 0 && p->cvlist->v.u2[1]) { |
|
const config_plugin_value_t *cpv = p->cvlist + p->cvlist->v.u2[0]; |
|
if (-1 != cpv->k_id) |
|
mod_webdav_merge_config(&p->defaults, cpv); |
|
} |
|
|
|
return HANDLER_GO_ON; |
|
} |
|
|
|
|
|
URIHANDLER_FUNC(mod_webdav_uri_handler) |
|
{ |
|
if (con->request.http_method != HTTP_METHOD_OPTIONS) |
|
return HANDLER_GO_ON; |
|
|
|
plugin_config pconf; |
|
mod_webdav_patch_config(con, (plugin_data *)p_d, &pconf); |
|
if (!pconf.enabled) return HANDLER_GO_ON; |
|
|
|
/* [RFC4918] 18 DAV Compliance Classes */ |
|
http_header_response_set(con, HTTP_HEADER_OTHER, |
|
CONST_STR_LEN("DAV"), |
|
#ifdef USE_LOCKS |
|
CONST_STR_LEN("1,2,3") |
|
#else |
|
CONST_STR_LEN("1,3") |
|
#endif |
|
); |
|
|
|
/* instruct MS Office Web Folders to use DAV |
|
* (instead of MS FrontPage Extensions) |
|
* http://www.zorched.net/2006/03/01/more-webdav-tips-tricks-and-bugs/ */ |
|
http_header_response_set(con, HTTP_HEADER_OTHER, |
|
CONST_STR_LEN("MS-Author-Via"), |
|
CONST_STR_LEN("DAV")); |
|
|
|
if (pconf.is_readonly) |
|
http_header_response_append(con, HTTP_HEADER_OTHER, |
|
CONST_STR_LEN("Allow"), |
|
CONST_STR_LEN("PROPFIND")); |
|
else |
|
http_header_response_append(con, HTTP_HEADER_OTHER, |
|
CONST_STR_LEN("Allow"), |
|
#ifdef USE_PROPPATCH |
|
#ifdef USE_LOCKS |
|
CONST_STR_LEN( |
|
"PROPFIND, DELETE, MKCOL, PUT, MOVE, COPY, PROPPATCH, LOCK, UNLOCK") |
|
#else |
|
CONST_STR_LEN( |
|
"PROPFIND, DELETE, MKCOL, PUT, MOVE, COPY, PROPPATCH") |
|
#endif |
|
#else |
|
CONST_STR_LEN( |
|
"PROPFIND, DELETE, MKCOL, PUT, MOVE, COPY") |
|
#endif |
|
); |
|
|
|
return HANDLER_GO_ON; |
|
} |
|
|
|
|
|
#ifdef USE_LOCKS |
|
|
|
typedef struct webdav_lockdata_wr { |
|
buffer locktoken; |
|
buffer lockroot; |
|
buffer ownerinfo; |
|
buffer *owner; /* NB: caller must provide writable storage */ |
|
const buffer *lockscope; /* future: might use enum, store int in db */ |
|
const buffer *locktype; /* future: might use enum, store int in db */ |
|
int depth; |
|
int timeout; /* offset from now, not absolute time_t */ |
|
} webdav_lockdata_wr; |
|
|
|
typedef struct webdav_lockdata { |
|
buffer locktoken; |
|
buffer lockroot; |
|
buffer ownerinfo; |
|
const buffer *owner; |
|
const buffer *lockscope; /* future: might use enum, store int in db */ |
|
const buffer *locktype; /* future: might use enum, store int in db */ |
|
int depth; |
|
int timeout; /* offset from now, not absolute time_t */ |
|
} webdav_lockdata; |
|
|
|
typedef struct { const char *ptr; uint32_t used; uint32_t size; } tagb; |
|
|
|
static const tagb lockscope_exclusive = |
|
{ "exclusive", sizeof("exclusive"), 0 }; |
|
static const tagb lockscope_shared = |
|
{ "shared", sizeof("shared"), 0 }; |
|
static const tagb locktype_write = |
|
{ "write", sizeof("write"), 0 }; |
|
|
|
#endif |
|
|
|
typedef struct { |
|
const char *ns; |
|
const char *name; |
|
uint32_t nslen; |
|
uint32_t namelen; |
|
} webdav_property_name; |
|
|
|
typedef struct { |
|
webdav_property_name *ptr; |
|
int used; |
|
int size; |
|
} webdav_property_names; |
|
|
|
/* |
|
* http://www.w3.org/TR/1998/NOTE-XML-data-0105/ |
|
* The datatype attribute "dt" is defined in the namespace named |
|
* "urn:uuid:C2F41010-65B3-11d1-A29F-00AA00C14882/". |
|
* (See the XML Namespaces Note at the W3C site for details of namespaces.) |
|
* The full URN of the attribute is |
|
* "urn:uuid:C2F41010-65B3-11d1-A29F-00AA00C14882/dt". |
|
* http://www.w3.org/TR/1998/NOTE-xml-names-0119 |
|
* http://www.w3.org/TR/1998/WD-xml-names-19980327 |
|
* http://lists.xml.org/archives/xml-dev/200101/msg00924.html |
|
* http://lists.xml.org/archives/xml-dev/200101/msg00929.html |
|
* http://lists.xml.org/archives/xml-dev/200101/msg00930.html |
|
* (Microsoft) Namespace Guidelines |
|
* https://msdn.microsoft.com/en-us/library/ms879470%28v=exchg.65%29.aspx |
|
* (Microsoft) XML Persistence Format |
|
* https://msdn.microsoft.com/en-us/library/ms676547%28v=vs.85%29.aspx |
|
* http://www.xml.com/pub/a/2002/06/26/vocabularies.html |
|
* The "Uuid" namespaces is the namespace |
|
* "uuid:C2F41010-65B3-11d1-A29F-00AA00C14882", |
|
* mainly found in association with the MS Office |
|
* namespace on the http://www.omg.org website. |
|
* http://www.data2type.de/en/xml-xslt-xslfo/wordml/wordml-introduction/the-root-element/ |
|
* xmlns:dt="uuid:C2F41010-65B3-11d1-A29F-00AA00C14882" |
|
* By using the prefix dt, the namespace declares an attribute which |
|
* determines the data type of a value. The name of the underlying schema |
|
* is dt.xsd and it can be found in the folder for Excel schemas. |
|
*/ |
|
#define MOD_WEBDAV_XMLNS_NS0 "xmlns:ns0=\"urn:uuid:c2f41010-65b3-11d1-a29f-00aa00c14882/\"" |
|
|
|
|
|
static void |
|
webdav_xml_doctype (buffer * const b, connection * const con) |
|
{ |
|
http_header_response_set(con, HTTP_HEADER_CONTENT_TYPE, |
|
CONST_STR_LEN("Content-Type"), |
|
CONST_STR_LEN("application/xml; charset=\"utf-8\"")); |
|
|
|
buffer_copy_string_len(b, CONST_STR_LEN( |
|
"<?xml version=\"1.0\" encoding=\"utf-8\"?>\n")); |
|
} |
|
|
|
|
|
static void |
|
webdav_xml_prop (buffer * const b, |
|
const webdav_property_name * const prop, |
|
const char * const value, const uint32_t vlen) |
|
{ |
|
buffer_append_string_len(b, CONST_STR_LEN("<")); |
|
buffer_append_string_len(b, prop->name, prop->namelen); |
|
buffer_append_string_len(b, CONST_STR_LEN(" xmlns=\"")); |
|
buffer_append_string_len(b, prop->ns, prop->nslen); |
|
if (0 == vlen) { |
|
buffer_append_string_len(b, CONST_STR_LEN("\"/>")); |
|
} |
|
else { |
|
buffer_append_string_len(b, CONST_STR_LEN("\">")); |
|
buffer_append_string_len(b, value, vlen); |
|
buffer_append_string_len(b, CONST_STR_LEN("</")); |
|
buffer_append_string_len(b, prop->name, prop->namelen); |
|
buffer_append_string_len(b, CONST_STR_LEN(">")); |
|
} |
|
} |
|
|
|
|
|
#ifdef USE_LOCKS |
|
static void |
|
webdav_xml_href_raw (buffer * const b, const buffer * const href) |
|
{ |
|
buffer_append_string_len(b, CONST_STR_LEN( |
|
"<D:href>")); |
|
buffer_append_string_len(b, CONST_BUF_LEN(href)); |
|
buffer_append_string_len(b, CONST_STR_LEN( |
|
"</D:href>\n")); |
|
} |
|
#endif |
|
|
|
|
|
static void |
|
webdav_xml_href (buffer * const b, const buffer * const href) |
|
{ |
|
buffer_append_string_len(b, CONST_STR_LEN( |
|
"<D:href>")); |
|
buffer_append_string_encoded(b, CONST_BUF_LEN(href), ENCODING_REL_URI); |
|
buffer_append_string_len(b, CONST_STR_LEN( |
|
"</D:href>\n")); |
|
} |
|
|
|
|
|
static void |
|
webdav_xml_status (buffer * const b, const int status) |
|
{ |
|
buffer_append_string_len(b, CONST_STR_LEN( |
|
"<D:status>HTTP/1.1 ")); |
|
http_status_append(b, status); |
|
buffer_append_string_len(b, CONST_STR_LEN( |
|
"</D:status>\n")); |
|
} |
|
|
|
|
|
#ifdef USE_PROPPATCH |
|
__attribute_cold__ |
|
static void |
|
webdav_xml_propstat_protected (buffer * const b, const char * const propname, |
|
const uint32_t len, const int status) |
|
{ |
|
buffer_append_string_len(b, CONST_STR_LEN( |
|
"<D:propstat>\n" |
|
"<D:prop><DAV:")); |
|
buffer_append_string_len(b, propname, len); |
|
buffer_append_string_len(b, CONST_STR_LEN( |
|
"/></D:prop>\n" |
|
"<D:error><DAV:cannot-modify-protected-property/></D:error>\n")); |
|
webdav_xml_status(b, status); /* 403 */ |
|
buffer_append_string_len(b, CONST_STR_LEN( |
|
"</D:propstat>\n")); |
|
} |
|
#endif |
|
|
|
|
|
#ifdef USE_PROPPATCH |
|
__attribute_cold__ |
|
static void |
|
webdav_xml_propstat_status (buffer * const b, const char * const ns, |
|
const char * const name, const int status) |
|
{ |
|
buffer_append_string_len(b, CONST_STR_LEN( |
|
"<D:propstat>\n" |
|
"<D:prop><")); |
|
buffer_append_string(b, ns); |
|
buffer_append_string(b, name); |
|
buffer_append_string_len(b, CONST_STR_LEN( |
|
"/></D:prop>\n")); |
|
webdav_xml_status(b, status); |
|
buffer_append_string_len(b, CONST_STR_LEN( |
|
"</D:propstat>\n")); |
|
} |
|
#endif |
|
|
|
|
|
static void |
|
webdav_xml_propstat (buffer * const b, buffer * const value, const int status) |
|
{ |
|
buffer_append_string_len(b, CONST_STR_LEN( |
|
"<D:propstat>\n" |
|
"<D:prop>\n")); |
|
buffer_append_string_buffer(b, value); |
|
buffer_append_string_len(b, CONST_STR_LEN( |
|
"</D:prop>\n")); |
|
webdav_xml_status(b, status); |
|
buffer_append_string_len(b, CONST_STR_LEN( |
|
"</D:propstat>\n")); |
|
} |
|
|
|
|
|
__attribute_cold__ |
|
static void |
|
webdav_xml_response_status (buffer * const b, |
|
const buffer * const href, |
|
const int status) |
|
{ |
|
buffer_append_string_len(b, CONST_STR_LEN( |
|
"<D:response>\n")); |
|
webdav_xml_href(b, href); |
|
webdav_xml_status(b, status); |
|
buffer_append_string_len(b, CONST_STR_LEN( |
|
"</D:response>\n")); |
|
} |
|
|
|
|
|
#ifdef USE_LOCKS |
|
static void |
|
webdav_xml_activelock (buffer * const b, |
|
const webdav_lockdata * const lockdata, |
|
const char * const tbuf, uint32_t tbuf_len) |
|
{ |
|
buffer_append_string_len(b, CONST_STR_LEN( |
|
"<D:activelock>\n" |
|
"<D:lockscope>" |
|
"<D:")); |
|
buffer_append_string_buffer(b, lockdata->lockscope); |
|
buffer_append_string_len(b, CONST_STR_LEN( |
|
"/>" |
|
"</D:lockscope>\n" |
|
"<D:locktype>" |
|
"<D:")); |
|
buffer_append_string_buffer(b, lockdata->locktype); |
|
buffer_append_string_len(b, CONST_STR_LEN( |
|
"/>" |
|
"</D:locktype>\n" |
|
"<D:depth>")); |
|
if (0 == lockdata->depth) |
|
buffer_append_string_len(b, CONST_STR_LEN("0")); |
|
else |
|
buffer_append_string_len(b, CONST_STR_LEN("infinity")); |
|
buffer_append_string_len(b, CONST_STR_LEN( |
|
"</D:depth>\n" |
|
"<D:timeout>")); |
|
if (0 != tbuf_len) |
|
buffer_append_string_len(b, tbuf, tbuf_len); /* "Second-..." */ |
|
else { |
|
buffer_append_string_len(b, CONST_STR_LEN("Second-")); |
|
buffer_append_int(b, lockdata->timeout); |
|
} |
|
buffer_append_string_len(b, CONST_STR_LEN( |
|
"</D:timeout>\n" |
|
"<D:owner>")); |
|
if (!buffer_string_is_empty(&lockdata->ownerinfo)) |
|
buffer_append_string_buffer(b, &lockdata->ownerinfo); |
|
buffer_append_string_len(b, CONST_STR_LEN( |
|
"</D:owner>\n" |
|
"<D:locktoken>\n")); |
|
webdav_xml_href_raw(b, &lockdata->locktoken); /*(as-is; not URL-encoded)*/ |
|
buffer_append_string_len(b, CONST_STR_LEN( |
|
"</D:locktoken>\n" |
|
"<D:lockroot>\n")); |
|
webdav_xml_href(b, &lockdata->lockroot); |
|
buffer_append_string_len(b, CONST_STR_LEN( |
|
"</D:lockroot>\n" |
|
"</D:activelock>\n")); |
|
} |
|
#endif |
|
|
|
|
|
static void |
|
webdav_xml_doc_multistatus (connection * const con, |
|
const plugin_config * const pconf, |
|
buffer * const ms) |
|
{ |
|
http_status_set_fin(con, 207); /* Multi-status */ |
|
|
|
buffer * const b = /*(optimization; buf extended as needed)*/ |
|
chunkqueue_append_buffer_open_sz(con->write_queue, 128 + ms->used); |
|
|
|
webdav_xml_doctype(b, con); |
|
buffer_append_string_len(b, CONST_STR_LEN( |
|
"<D:multistatus xmlns:D=\"DAV:\">\n")); |
|
buffer_append_string_buffer(b, ms); |
|
buffer_append_string_len(b, CONST_STR_LEN( |
|
"</D:multistatus>\n")); |
|
|
|
if (pconf->log_xml) |
|
log_error(con->conf.errh, __FILE__, __LINE__, |
|
"XML-response-body: %.*s", BUFFER_INTLEN_PTR(b)); |
|
|
|
chunkqueue_append_buffer_commit(con->write_queue); |
|
} |
|
|
|
|
|
#ifdef USE_PROPPATCH |
|
static void |
|
webdav_xml_doc_multistatus_response (connection * const con, |
|
const plugin_config * const pconf, |
|
buffer * const ms) |
|
{ |
|
http_status_set_fin(con, 207); /* Multi-status */ |
|
|
|
buffer * const b = /*(optimization; buf extended as needed)*/ |
|
chunkqueue_append_buffer_open_sz(con->write_queue, 128 + ms->used); |
|
|
|
webdav_xml_doctype(b, con); |
|
buffer_append_string_len(b, CONST_STR_LEN( |
|
"<D:multistatus xmlns:D=\"DAV:\">\n" |
|
"<D:response>\n")); |
|
webdav_xml_href(b, con->physical.rel_path); |
|
buffer_append_string_buffer(b, ms); |
|
buffer_append_string_len(b, CONST_STR_LEN( |
|
"</D:response>\n" |
|
"</D:multistatus>\n")); |
|
|
|
if (pconf->log_xml) |
|
log_error(con->conf.errh, __FILE__, __LINE__, |
|
"XML-response-body: %.*s", BUFFER_INTLEN_PTR(b)); |
|
|
|
chunkqueue_append_buffer_commit(con->write_queue); |
|
} |
|
#endif |
|
|
|
|
|
#ifdef USE_LOCKS |
|
static void |
|
webdav_xml_doc_lock_acquired (connection * const con, |
|
const plugin_config * const pconf, |
|
const webdav_lockdata * const lockdata) |
|
{ |
|
/*(http_status is set by caller to 200 OK or 201 Created)*/ |
|
|
|
char tbuf[32] = "Second-"; |
|
const uint32_t tbuf_len = |
|
li_itostrn(tbuf+sizeof("Second-")-1, sizeof(tbuf)-(sizeof("Second-")-1), |
|
lockdata->timeout); |
|
http_header_response_set(con, HTTP_HEADER_OTHER, |
|
CONST_STR_LEN("Timeout"), |
|
tbuf, tbuf_len); |
|
|
|
buffer * const b = |
|
chunkqueue_append_buffer_open_sz(con->write_queue, 1024); |
|
|
|
webdav_xml_doctype(b, con); |
|
buffer_append_string_len(b, CONST_STR_LEN( |
|
"<D:prop xmlns:D=\"DAV:\">\n" |
|
"<D:lockdiscovery>\n")); |
|
webdav_xml_activelock(b, lockdata, tbuf, tbuf_len); |
|
buffer_append_string_len(b, CONST_STR_LEN( |
|
"</D:lockdiscovery>\n" |
|
"</D:prop>\n")); |
|
|
|
if (pconf->log_xml) |
|
log_error(con->conf.errh, __FILE__, __LINE__, |
|
"XML-response-body: %.*s", BUFFER_INTLEN_PTR(b)); |
|
|
|
chunkqueue_append_buffer_commit(con->write_queue); |
|
} |
|
#endif |
|
|
|
|
|
/* |
|
* [RFC4918] 16 Precondition/Postcondition XML Elements |
|
*/ |
|
|
|
|
|
/* |
|
* 403 Forbidden |
|
* "<D:error><DAV:cannot-modify-protected-property/></D:error>" |
|
* |
|
* 403 Forbidden |
|
* "<D:error><DAV:no-external-entities/></D:error>" |
|
* |
|
* 409 Conflict |
|
* "<D:error><DAV:preserved-live-properties/></D:error>" |
|
*/ |
|
|
|
|
|
__attribute_cold__ |
|
static void |
|
webdav_xml_doc_error_propfind_finite_depth (connection * const con) |
|
{ |
|
http_status_set(con, 403); /* Forbidden */ |
|
con->file_finished = 1; |
|
|
|
buffer * const b = |
|
chunkqueue_append_buffer_open_sz(con->write_queue, 256); |
|
webdav_xml_doctype(b, con); |
|
buffer_append_string_len(b, CONST_STR_LEN( |
|
"<D:error><DAV:propfind-finite-depth/></D:error>\n")); |
|
chunkqueue_append_buffer_commit(con->write_queue); |
|
} |
|
|
|
|
|
#ifdef USE_LOCKS |
|
__attribute_cold__ |
|
static void |
|
webdav_xml_doc_error_lock_token_matches_request_uri (connection * const con) |
|
{ |
|
http_status_set(con, 409); /* Conflict */ |
|
con->file_finished = 1; |
|
|
|
buffer * const b = |
|
chunkqueue_append_buffer_open_sz(con->write_queue, 256); |
|
webdav_xml_doctype(b, con); |
|
buffer_append_string_len(b, CONST_STR_LEN( |
|
"<D:error><DAV:lock-token-matches-request-uri/></D:error>\n")); |
|
chunkqueue_append_buffer_commit(con->write_queue); |
|
} |
|
#endif |
|
|
|
|
|
#ifdef USE_LOCKS |
|
__attribute_cold__ |
|
static void |
|
webdav_xml_doc_423_locked (connection * const con, buffer * const hrefs, |
|
const char * const errtag, const uint32_t errtaglen) |
|
{ |
|
http_status_set(con, 423); /* Locked */ |
|
con->file_finished = 1; |
|
|
|
buffer * const b = /*(optimization; buf extended as needed)*/ |
|
chunkqueue_append_buffer_open_sz(con->write_queue, 256 + hrefs->used); |
|
|
|
webdav_xml_doctype(b, con); |
|
buffer_append_string_len(b, CONST_STR_LEN( |
|
"<D:error xmlns:D=\"DAV:\">\n" |
|
"<D:")); |
|
buffer_append_string_len(b, errtag, errtaglen); |
|
buffer_append_string_len(b, CONST_STR_LEN( |
|
">\n")); |
|
buffer_append_string_buffer(b, hrefs); |
|
buffer_append_string_len(b, CONST_STR_LEN( |
|
"</D:")); |
|
buffer_append_string_len(b, errtag, errtaglen); |
|
buffer_append_string_len(b, CONST_STR_LEN( |
|
">\n" |
|
"</D:error>\n")); |
|
|
|
chunkqueue_append_buffer_commit(con->write_queue); |
|
} |
|
#endif |
|
|
|
|
|
#ifdef USE_LOCKS |
|
__attribute_cold__ |
|
static void |
|
webdav_xml_doc_error_lock_token_submitted (connection * const con, |
|
buffer * const hrefs) |
|
{ |
|
webdav_xml_doc_423_locked(con, hrefs, |
|
CONST_STR_LEN("lock-token-submitted")); |
|
} |
|
#endif |
|
|
|
|
|
#ifdef USE_LOCKS |
|
__attribute_cold__ |
|
static void |
|
webdav_xml_doc_error_no_conflicting_lock (connection * const con, |
|
buffer * const hrefs) |
|
{ |
|
webdav_xml_doc_423_locked(con, hrefs, |
|
CONST_STR_LEN("no-conflicting-lock")); |
|
} |
|
#endif |
|
|
|
|
|
#ifdef USE_PROPPATCH |
|
|
|
#define MOD_WEBDAV_SQLITE_CREATE_TABLE_PROPERTIES \ |
|
"CREATE TABLE IF NOT EXISTS properties (" \ |
|
" resource TEXT NOT NULL," \ |
|
" prop TEXT NOT NULL," \ |
|
" ns TEXT NOT NULL," \ |
|
" value TEXT NOT NULL," \ |
|
" PRIMARY KEY(resource, prop, ns))" |
|
|
|
#define MOD_WEBDAV_SQLITE_CREATE_TABLE_LOCKS \ |
|
"CREATE TABLE IF NOT EXISTS locks (" \ |
|
" locktoken TEXT NOT NULL," \ |
|
" resource TEXT NOT NULL," \ |
|
" lockscope TEXT NOT NULL," \ |
|
" locktype TEXT NOT NULL," \ |
|
" owner TEXT NOT NULL," \ |
|
" ownerinfo TEXT NOT NULL," \ |
|
" depth INT NOT NULL," \ |
|
" timeout TIMESTAMP NOT NULL," \ |
|
" PRIMARY KEY(locktoken))" |
|
|
|
#define MOD_WEBDAV_SQLITE_PROPS_SELECT_PROPNAMES \ |
|
"SELECT prop, ns FROM properties WHERE resource = ?" |
|
|
|
#define MOD_WEBDAV_SQLITE_PROPS_SELECT_PROP \ |
|
"SELECT value FROM properties WHERE resource = ? AND prop = ? AND ns = ?" |
|
|
|
#define MOD_WEBDAV_SQLITE_PROPS_SELECT_PROPS \ |
|
"SELECT prop, ns, value FROM properties WHERE resource = ?" |
|
|
|
#define MOD_WEBDAV_SQLITE_PROPS_UPDATE_PROP \ |
|
"REPLACE INTO properties (resource, prop, ns, value) VALUES (?, ?, ?, ?)" |
|
|
|
#define MOD_WEBDAV_SQLITE_PROPS_DELETE_PROP \ |
|
"DELETE FROM properties WHERE resource = ? AND prop = ? AND ns = ?" |
|
|
|
#define MOD_WEBDAV_SQLITE_PROPS_DELETE \ |
|
"DELETE FROM properties WHERE resource = ?" |
|
|
|
#define MOD_WEBDAV_SQLITE_PROPS_COPY \ |
|
"INSERT INTO properties" \ |
|
" SELECT ?, prop, ns, value FROM properties WHERE resource = ?" |
|
|
|
#define MOD_WEBDAV_SQLITE_PROPS_MOVE \ |
|
"UPDATE OR REPLACE properties SET resource = ? WHERE resource = ?" |
|
|
|
#define MOD_WEBDAV_SQLITE_PROPS_MOVE_COL \ |
|
"UPDATE OR REPLACE properties SET resource = ? || SUBSTR(resource, ?)" \ |
|
" WHERE SUBSTR(resource, 1, ?) = ?" |
|
|
|
#define MOD_WEBDAV_SQLITE_LOCKS_ACQUIRE \ |
|
"INSERT INTO locks" \ |
|
" (locktoken,resource,lockscope,locktype,owner,ownerinfo,depth,timeout)" \ |
|
" VALUES (?,?,?,?,?,?,?, CURRENT_TIME + ?)" |
|
|
|
#define MOD_WEBDAV_SQLITE_LOCKS_REFRESH \ |
|
"UPDATE locks SET timeout = CURRENT_TIME + ? WHERE locktoken = ?" |
|
|
|
#define MOD_WEBDAV_SQLITE_LOCKS_RELEASE \ |
|
"DELETE FROM locks WHERE locktoken = ?" |
|
|
|
#define MOD_WEBDAV_SQLITE_LOCKS_READ \ |
|
"SELECT resource, owner, depth" \ |
|
" FROM locks WHERE locktoken = ?" |
|
|
|
#define MOD_WEBDAV_SQLITE_LOCKS_READ_URI \ |
|
"SELECT" \ |
|
" locktoken,resource,lockscope,locktype,owner,ownerinfo,depth," \ |
|
"timeout - CURRENT_TIME" \ |
|
" FROM locks WHERE resource = ?" |
|
|
|
#define MOD_WEBDAV_SQLITE_LOCKS_READ_URI_INFINITY \ |
|
"SELECT" \ |
|
" locktoken,resource,lockscope,locktype,owner,ownerinfo,depth," \ |
|
"timeout - CURRENT_TIME" \ |
|
" FROM locks" \ |
|
" WHERE depth = -1 AND resource = SUBSTR(?, 1, LENGTH(resource))" |
|
|
|
#define MOD_WEBDAV_SQLITE_LOCKS_READ_URI_MEMBERS \ |
|
"SELECT" \ |
|
" locktoken,resource,lockscope,locktype,owner,ownerinfo,depth," \ |
|
"timeout - CURRENT_TIME" \ |
|
" FROM locks WHERE SUBSTR(resource, 1, ?) = ?" |
|
|
|
#define MOD_WEBDAV_SQLITE_LOCKS_DELETE_URI \ |
|
"DELETE FROM locks WHERE resource = ?" |
|
|
|
#define MOD_WEBDAV_SQLITE_LOCKS_DELETE_URI_COL \ |
|
"DELETE FROM locks WHERE SUBSTR(resource, 1, ?) = ?" |
|
/*"DELETE FROM locks WHERE locktoken LIKE ? || '%'"*/ |
|
|
|
/*(not currently used)*/ |
|
#define MOD_WEBDAV_SQLITE_LOCKS_DELETE_EXPIRED \ |
|
"DELETE FROM locks WHERE timeout < CURRENT_TIME" |
|
|
|
#endif /* USE_PROPPATCH */ |
|
|
|
|
|
__attribute_cold__ |
|
static int |
|
mod_webdav_sqlite3_init (const char * const restrict dbname, |
|
log_error_st * const errh) |
|
{ |
|
#ifndef USE_PROPPATCH |
|
|
|
log_error(errh, __FILE__, __LINE__, |
|
"Sorry, no sqlite3 and libxml2 support include, " |
|
"compile with --with-webdav-props"); |
|
UNUSED(dbname); |
|
return 0; |
|
|
|
#else /* USE_PROPPATCH */ |
|
|
|
/*(expects (plugin_config *s) (log_error_st *errh) (char *err))*/ |
|
#define MOD_WEBDAV_SQLITE_CREATE_TABLE(query, label) \ |
|
if (sqlite3_exec(sqlh, query, NULL, NULL, &err) != SQLITE_OK) { \ |
|
if (0 != strcmp(err, "table " label " already exists")) { \ |
|
log_error(errh, __FILE__, __LINE__, \ |
|
"create table " label ": %s", err); \ |
|
sqlite3_free(err); \ |
|
sqlite3_close(sqlh); \ |
|
return 0; \ |
|
} \ |
|
sqlite3_free(err); \ |
|
} |
|
|
|
sqlite3 *sqlh; |
|
int sqlrc = sqlite3_open_v2(dbname, &sqlh, |
|
SQLITE_OPEN_READWRITE|SQLITE_OPEN_CREATE, NULL); |
|
if (sqlrc != SQLITE_OK) { |
|
log_error(errh, __FILE__, __LINE__, "sqlite3_open() '%s': %s", |
|
dbname, sqlh ? sqlite3_errmsg(sqlh) : sqlite3_errstr(sqlrc)); |
|
if (sqlh) sqlite3_close(sqlh); |
|
return 0; |
|
} |
|
|
|
char *err = NULL; |
|
MOD_WEBDAV_SQLITE_CREATE_TABLE( MOD_WEBDAV_SQLITE_CREATE_TABLE_PROPERTIES, |
|
"properties"); |
|
MOD_WEBDAV_SQLITE_CREATE_TABLE( MOD_WEBDAV_SQLITE_CREATE_TABLE_LOCKS, |
|
"locks"); |
|
|
|
/* add ownerinfo column to locks table (update older mod_webdav sqlite db) |
|
* (could check if 'PRAGMA user_version;' is 0, add column, and increment)*/ |
|
#define MOD_WEBDAV_SQLITE_SELECT_LOCKS_OWNERINFO_TEST \ |
|
"SELECT COUNT(*) FROM locks WHERE ownerinfo = \"\"" |
|
#define MOD_WEBDAV_SQLITE_ALTER_TABLE_LOCKS \ |
|
"ALTER TABLE locks ADD COLUMN ownerinfo TEXT NOT NULL DEFAULT \"\"" |
|
if (sqlite3_exec(sqlh, MOD_WEBDAV_SQLITE_SELECT_LOCKS_OWNERINFO_TEST, |
|
NULL, NULL, &err) != SQLITE_OK) { |
|
sqlite3_free(err); /* "no such column: ownerinfo" */ |
|
if (sqlite3_exec(sqlh, MOD_WEBDAV_SQLITE_ALTER_TABLE_LOCKS, |
|
NULL, NULL, &err) != SQLITE_OK) { |
|
log_error(errh, __FILE__, __LINE__, "alter table locks: %s", err); |
|
sqlite3_free(err); |
|
sqlite3_close(sqlh); |
|
return 0; |
|
} |
|
} |
|
|
|
sqlite3_close(sqlh); |
|
return 1; |
|
|
|
#endif /* USE_PROPPATCH */ |
|
} |
|
|
|
|
|
#ifdef USE_PROPPATCH |
|
__attribute_cold__ |
|
static int |
|
mod_webdav_sqlite3_prep (sql_config * const restrict sql, |
|
const char * const sqlite_db_name, |
|
log_error_st * const errh) |
|
{ |
|
/*(expects (plugin_config *s) (log_error_st *errh))*/ |
|
#define MOD_WEBDAV_SQLITE_PREPARE_STMT(query, stmt) \ |
|
if (sqlite3_prepare_v2(sql->sqlh, query, sizeof(query)-1, &stmt, NULL) \ |
|
!= SQLITE_OK) { \ |
|
log_error(errh, __FILE__, __LINE__, "sqlite3_prepare_v2(): %s", \ |
|
sqlite3_errmsg(sql->sqlh)); \ |
|
return 0; \ |
|
} |
|
|
|
int sqlrc = sqlite3_open_v2(sqlite_db_name, &sql->sqlh, |
|
SQLITE_OPEN_READWRITE, NULL); |
|
if (sqlrc != SQLITE_OK) { |
|
log_error(errh, __FILE__, __LINE__, "sqlite3_open() '%s': %s", |
|
sqlite_db_name, |
|
sql->sqlh |
|
? sqlite3_errmsg(sql->sqlh) |
|
: sqlite3_errstr(sqlrc)); |
|
return 0; |
|
} |
|
|
|
/* future: perhaps not all statements should be prepared; |
|
* infrequently executed statements could be run with sqlite3_exec(), |
|
* or prepared and finalized on each use, as needed */ |
|
|
|
MOD_WEBDAV_SQLITE_PREPARE_STMT( MOD_WEBDAV_SQLITE_PROPS_SELECT_PROPNAMES, |
|
sql->stmt_props_select_propnames); |
|
MOD_WEBDAV_SQLITE_PREPARE_STMT( MOD_WEBDAV_SQLITE_PROPS_SELECT_PROPS, |
|
sql->stmt_props_select_props); |
|
MOD_WEBDAV_SQLITE_PREPARE_STMT( MOD_WEBDAV_SQLITE_PROPS_SELECT_PROP, |
|
sql->stmt_props_select_prop); |
|
MOD_WEBDAV_SQLITE_PREPARE_STMT( MOD_WEBDAV_SQLITE_PROPS_UPDATE_PROP, |
|
sql->stmt_props_update_prop); |
|
MOD_WEBDAV_SQLITE_PREPARE_STMT( MOD_WEBDAV_SQLITE_PROPS_DELETE_PROP, |
|
sql->stmt_props_delete_prop); |
|
MOD_WEBDAV_SQLITE_PREPARE_STMT( MOD_WEBDAV_SQLITE_PROPS_COPY, |
|
sql->stmt_props_copy); |
|
MOD_WEBDAV_SQLITE_PREPARE_STMT( MOD_WEBDAV_SQLITE_PROPS_MOVE, |
|
sql->stmt_props_move); |
|
MOD_WEBDAV_SQLITE_PREPARE_STMT( MOD_WEBDAV_SQLITE_PROPS_MOVE_COL, |
|
sql->stmt_props_move_col); |
|
MOD_WEBDAV_SQLITE_PREPARE_STMT( MOD_WEBDAV_SQLITE_PROPS_DELETE, |
|
sql->stmt_props_delete); |
|
MOD_WEBDAV_SQLITE_PREPARE_STMT( MOD_WEBDAV_SQLITE_LOCKS_ACQUIRE, |
|
sql->stmt_locks_acquire); |
|
MOD_WEBDAV_SQLITE_PREPARE_STMT( MOD_WEBDAV_SQLITE_LOCKS_REFRESH, |
|
sql->stmt_locks_refresh); |
|
MOD_WEBDAV_SQLITE_PREPARE_STMT( MOD_WEBDAV_SQLITE_LOCKS_RELEASE, |
|
sql->stmt_locks_release); |
|
MOD_WEBDAV_SQLITE_PREPARE_STMT( MOD_WEBDAV_SQLITE_LOCKS_READ, |
|
sql->stmt_locks_read); |
|
MOD_WEBDAV_SQLITE_PREPARE_STMT( MOD_WEBDAV_SQLITE_LOCKS_READ_URI, |
|
sql->stmt_locks_read_uri); |
|
MOD_WEBDAV_SQLITE_PREPARE_STMT( MOD_WEBDAV_SQLITE_LOCKS_READ_URI_INFINITY, |
|
sql->stmt_locks_read_uri_infinity); |
|
MOD_WEBDAV_SQLITE_PREPARE_STMT( MOD_WEBDAV_SQLITE_LOCKS_READ_URI_MEMBERS, |
|
sql->stmt_locks_read_uri_members); |
|
MOD_WEBDAV_SQLITE_PREPARE_STMT( MOD_WEBDAV_SQLITE_LOCKS_DELETE_URI, |
|
sql->stmt_locks_delete_uri); |
|
MOD_WEBDAV_SQLITE_PREPARE_STMT( MOD_WEBDAV_SQLITE_LOCKS_DELETE_URI_COL, |
|
sql->stmt_locks_delete_uri_col); |
|
|
|
return 1; |
|
|
|
} |
|
#endif /* USE_PROPPATCH */ |
|
|
|
|
|
__attribute_cold__ |
|
SERVER_FUNC(mod_webdav_worker_init) |
|
{ |
|
#ifdef USE_PROPPATCH |
|
/* open sqlite databases and prepare SQL statements in each worker process |
|
* |
|
* https://www.sqlite.org/faq.html |
|
* Under Unix, you should not carry an open SQLite database |
|
* across a fork() system call into the child process. |
|
*/ |
|
plugin_data * const p = (plugin_data *)p_d; |
|
/* (init i to 0 if global context; to 1 to skip empty global context) */ |
|
for (int i = !p->cvlist[0].v.u2[1], used = p->nconfig; i < used; ++i) { |
|
config_plugin_value_t *cpv = p->cvlist + p->cvlist[i].v.u2[0]; |
|
for (; -1 != cpv->k_id; ++cpv) { |
|
switch (cpv->k_id) { |
|
#ifdef USE_PROPPATCH |
|
case 0: /* webdav.sqlite-db-name */ |
|
if (!buffer_is_empty(cpv->v.b)) { |
|
const char * const dbname = cpv->v.b->ptr; |
|
cpv->v.v = calloc(1, sizeof(sql_config)); |
|
cpv->vtype = T_CONFIG_LOCAL; |
|
if (!mod_webdav_sqlite3_prep(cpv->v.v, dbname, srv->errh)) |
|
return HANDLER_ERROR; |
|
} |
|
break; |
|
#endif |
|
default: |
|
break; |
|
} |
|
} |
|
} |
|
#else |
|
UNUSED(srv); |
|
UNUSED(p_d); |
|
#endif /* USE_PROPPATCH */ |
|
return HANDLER_GO_ON; |
|
} |
|
|
|
|
|
#ifdef USE_PROPPATCH |
|
static int |
|
webdav_db_transaction (const plugin_config * const pconf, |
|
const char * const action) |
|
{ |
|
if (!pconf->sql) |
|
return 1; |
|
char *err = NULL; |
|
if (SQLITE_OK == sqlite3_exec(pconf->sql->sqlh, action, NULL, NULL, &err)) |
|
return 1; |
|
else { |
|
#if 0 |
|
fprintf(stderr, "%s: %s: %s\n", __func__, action, err); |
|
log_error(pconf->errh, __FILE__, __LINE__, |
|
"%s: %s: %s\n", __func__, action, err); |
|
#endif |
|
sqlite3_free(err); |
|
return 0; |
|
} |
|
} |
|
|
|
#define webdav_db_transaction_begin(pconf) \ |
|
webdav_db_transaction(pconf, "BEGIN;") |
|
|
|
#define webdav_db_transaction_begin_immediate(pconf) \ |
|
webdav_db_transaction(pconf, "BEGIN IMMEDIATE;") |
|
|
|
#define webdav_db_transaction_commit(pconf) \ |
|
webdav_db_transaction(pconf, "COMMIT;") |
|
|
|
#define webdav_db_transaction_rollback(pconf) \ |
|
webdav_db_transaction(pconf, "ROLLBACK;") |
|
|
|
#else |
|
|
|
#define webdav_db_transaction_begin(pconf) 1 |
|
#define webdav_db_transaction_begin_immediate(pconf) 1 |
|
#define webdav_db_transaction_commit(pconf) 1 |
|
#define webdav_db_transaction_rollback(pconf) 1 |
|
|
|
#endif |
|
|
|
|
|
#ifdef USE_LOCKS |
|
static int |
|
webdav_lock_match (const plugin_config * const pconf, |
|
const webdav_lockdata * const lockdata) |
|
{ |
|
if (!pconf->sql) |
|
return 0; |
|
sqlite3_stmt * const stmt = pconf->sql->stmt_locks_read; |
|
if (!stmt) |
|
return 0; |
|
|
|
sqlite3_bind_text( |
|
stmt, 1, CONST_BUF_LEN(&lockdata->locktoken), SQLITE_STATIC); |
|
|
|
int status = -1; /* if lock does not exist */ |
|
if (SQLITE_ROW == sqlite3_step(stmt)) { |
|
const char *text = (char *)sqlite3_column_text(stmt, 0); /* resource */ |
|
uint32_t text_len = (uint32_t) sqlite3_column_bytes(stmt, 0); |
|
if (text_len < lockdata->lockroot.used |
|
&& 0 == memcmp(lockdata->lockroot.ptr, text, text_len) |
|
&& (text_len == lockdata->lockroot.used-1 |
|
|| -1 == sqlite3_column_int(stmt, 2))) { /* depth */ |
|
text = (char *)sqlite3_column_text(stmt, 1); /* owner */ |
|
text_len = (uint32_t)sqlite3_column_bytes(stmt, 1); |
|
if (0 == text_len /*(if no auth required to lock; not recommended)*/ |
|
|| buffer_is_equal_string(lockdata->owner, text, text_len)) |
|
status = 0; /* success; lock match */ |
|
else { |
|
/*(future: might check if owner is a privileged admin user)*/ |
|
status = -3; /* not lock owner; not authorized */ |
|
} |
|
} |
|
else |
|
status = -2; /* URI is not in scope of lock */ |
|
} |
|
|
|
sqlite3_reset(stmt); |
|
|
|
/* status |
|
* 0 lock exists and uri in scope and owner is privileged/owns lock |
|
* -1 lock does not exist |
|
* -2 URI is not in scope of lock |
|
* -3 owner does not own lock/is not privileged |
|
*/ |
|
return status; |
|
} |
|
#endif |
|
|
|
|
|
#ifdef USE_LOCKS |
|
static void |
|
webdav_lock_activelocks_lockdata (sqlite3_stmt * const stmt, |
|
webdav_lockdata_wr * const lockdata) |
|
{ |
|
lockdata->locktoken.ptr = (char *)sqlite3_column_text(stmt, 0); |
|
lockdata->locktoken.used = sqlite3_column_bytes(stmt, 0); |
|
lockdata->lockroot.ptr = (char *)sqlite3_column_text(stmt, 1); |
|
lockdata->lockroot.used = sqlite3_column_bytes(stmt, 1); |
|
lockdata->lockscope = |
|
(sqlite3_column_bytes(stmt, 2) == (int)sizeof("exclusive")-1) |
|
? (const buffer *)&lockscope_exclusive |
|
: (const buffer *)&lockscope_shared; |
|
lockdata->locktype = (const buffer *)&locktype_write; |
|
lockdata->owner->ptr = (char *)sqlite3_column_text(stmt, 4); |
|
lockdata->owner->used = sqlite3_column_bytes(stmt, 4); |
|
lockdata->ownerinfo.ptr = (char *)sqlite3_column_text(stmt, 5); |
|
lockdata->ownerinfo.used = sqlite3_column_bytes(stmt, 5); |
|
lockdata->depth = sqlite3_column_int(stmt, 6); |
|
lockdata->timeout = sqlite3_column_int(stmt, 7); |
|
|
|
if (lockdata->locktoken.used) ++lockdata->locktoken.used; |
|
if (lockdata->lockroot.used) ++lockdata->lockroot.used; |
|
if (lockdata->owner->used) ++lockdata->owner->used; |
|
if (lockdata->ownerinfo.used) ++lockdata->ownerinfo.used; |
|
} |
|
|
|
|
|
typedef |
|
void webdav_lock_activelocks_cb(void * const vdata, |
|
const webdav_lockdata * const lockdata); |
|
|
|
static void |
|
webdav_lock_activelocks (const plugin_config * const pconf, |
|
const buffer * const uri, |
|
const int expand_checks, |
|
webdav_lock_activelocks_cb * const lock_cb, |
|
void * const vdata) |
|
{ |
|
webdav_lockdata lockdata; |
|
buffer owner = { NULL, 0, 0 }; |
|
lockdata.locktoken.size = 0; |
|
lockdata.lockroot.size = 0; |
|
lockdata.ownerinfo.size = 0; |
|
lockdata.owner = &owner; |
|
|
|
if (!pconf->sql) |
|
return; |
|
|
|
/* check for locks with Depth: 0 (and Depth: infinity if 0==expand_checks)*/ |
|
sqlite3_stmt *stmt = pconf->sql->stmt_locks_read_uri; |
|
if (!stmt || !pconf->sql->stmt_locks_read_uri_infinity |
|
|| !pconf->sql->stmt_locks_read_uri_members) |
|
return; |
|
|
|
sqlite3_bind_text(stmt, 1, CONST_BUF_LEN(uri), SQLITE_STATIC); |
|
|
|
while (SQLITE_ROW == sqlite3_step(stmt)) { |
|
/* (avoid duplication with query below if infinity lock on collection) |
|
* (infinity locks are rejected on non-collections elsewhere) */ |
|
if (0 != expand_checks && -1 == sqlite3_column_int(stmt, 6) /*depth*/) |
|
continue; |
|
|
|
webdav_lock_activelocks_lockdata(stmt, (webdav_lockdata_wr *)&lockdata); |
|
if (lockdata.timeout > 0) |
|
lock_cb(vdata, &lockdata); |
|
} |
|
|
|
sqlite3_reset(stmt); |
|
|
|
if (0 == expand_checks) |
|
return; |
|
|
|
/* check for locks with Depth: infinity |
|
* (i.e. collections: self (if collection) or containing collections) */ |
|
stmt = pconf->sql->stmt_locks_read_uri_infinity; |
|
|
|
sqlite3_bind_text(stmt, 1, CONST_BUF_LEN(uri), SQLITE_STATIC); |
|
|
|
while (SQLITE_ROW == sqlite3_step(stmt)) { |
|
webdav_lock_activelocks_lockdata(stmt, (webdav_lockdata_wr *)&lockdata); |
|
if (lockdata.timeout > 0) |
|
lock_cb(vdata, &lockdata); |
|
} |
|
|
|
sqlite3_reset(stmt); |
|
|
|
if (1 == expand_checks) |
|
return; |
|
|
|
#ifdef __COVERITY__ |
|
force_assert(0 != uri->used); |
|
#endif |
|
|
|
/* check for locks on members within (internal to) collection */ |
|
stmt = pconf->sql->stmt_locks_read_uri_members; |
|
|
|
sqlite3_bind_int( stmt, 1, (int)uri->used-1); |
|
sqlite3_bind_text(stmt, 2, CONST_BUF_LEN(uri), SQLITE_STATIC); |
|
|
|
while (SQLITE_ROW == sqlite3_step(stmt)) { |
|
/* (avoid duplication with query above for exact resource match) */ |
|
if (uri->used-1 == (uint32_t)sqlite3_column_bytes(stmt, 1) /*resource*/) |
|
continue; |
|
|
|
webdav_lock_activelocks_lockdata(stmt, (webdav_lockdata_wr *)&lockdata); |
|
if (lockdata.timeout > 0) |
|
lock_cb(vdata, &lockdata); |
|
} |
|
|
|
sqlite3_reset(stmt); |
|
} |
|
#endif |
|
|
|
|
|
static int |
|
webdav_lock_delete_uri (const plugin_config * const pconf, |
|
const buffer * const uri) |
|
{ |
|
#ifdef USE_LOCKS |
|
|
|
if (!pconf->sql) |
|
return 0; |
|
sqlite3_stmt * const stmt = pconf->sql->stmt_locks_delete_uri; |
|
if (!stmt) |
|
return 0; |
|
|
|
sqlite3_bind_text(stmt, 1, CONST_BUF_LEN(uri), SQLITE_STATIC); |
|
|
|
int status = 1; |
|
while (SQLITE_DONE != sqlite3_step(stmt)) { |
|
status = 0; |
|
#if 0 |
|
fprintf(stderr, "%s: %s\n", __func__, sqlite3_errmsg(pconf->sql->sqlh)); |
|
log_error(pconf->errh, __FILE__, __LINE__, |
|
"%s: %s", __func__, sqlite3_errmsg(pconf->sql->sqlh)); |
|
#endif |
|
} |
|
|
|
sqlite3_reset(stmt); |
|
|
|
return status; |
|
|
|
#else |
|
UNUSED(pconf); |
|
UNUSED(uri); |
|
return 1; |
|
#endif |
|
} |
|
|
|
|
|
static int |
|
webdav_lock_delete_uri_col (const plugin_config * const pconf, |
|
const buffer * const uri) |
|
{ |
|
#ifdef USE_LOCKS |
|
|
|
if (!pconf->sql) |
|
return 0; |
|
sqlite3_stmt * const stmt = pconf->sql->stmt_locks_delete_uri_col; |
|
if (!stmt) |
|
return 0; |
|
|
|
#ifdef __COVERITY__ |
|
force_assert(0 != uri->used); |
|
#endif |
|
|
|
sqlite3_bind_int( stmt, 1, (int)uri->used-1); |
|
sqlite3_bind_text(stmt, 2, CONST_BUF_LEN(uri), SQLITE_STATIC); |
|
|
|
int status = 1; |
|
while (SQLITE_DONE != sqlite3_step(stmt)) { |
|
status = 0; |
|
#if 0 |
|
fprintf(stderr, "%s: %s\n", __func__, sqlite3_errmsg(pconf->sql->sqlh)); |
|
log_error(pconf->errh, __FILE__, __LINE__, |
|
"%s: %s", __func__, sqlite3_errmsg(pconf->sql->sqlh)); |
|
#endif |
|
} |
|
|
|
sqlite3_reset(stmt); |
|
|
|
return status; |
|
|
|
#else |
|
UNUSED(pconf); |
|
UNUSED(uri); |
|
return 1; |
|
#endif |
|
} |
|
|
|
|
|
#ifdef USE_LOCKS |
|
static int |
|
webdav_lock_acquire (const plugin_config * const pconf, |
|
const webdav_lockdata * const lockdata) |
|
{ |
|
/* |
|
* future: |
|
* only lockscope:"exclusive" and locktype:"write" currently supported, |
|
* so inserting strings into database is extraneous, and anyway should |
|
* be enums instead of strings, since there are limited supported values |
|
*/ |
|
|
|
if (!pconf->sql) |
|
return 0; |
|
sqlite3_stmt * const stmt = pconf->sql->stmt_locks_acquire; |
|
if (!stmt) |
|
return 0; |
|
|
|
sqlite3_bind_text( |
|
stmt, 1, CONST_BUF_LEN(&lockdata->locktoken), SQLITE_STATIC); |
|
sqlite3_bind_text( |
|
stmt, 2, CONST_BUF_LEN(&lockdata->lockroot), SQLITE_STATIC); |
|
sqlite3_bind_text( |
|
stmt, 3, CONST_BUF_LEN(lockdata->lockscope), SQLITE_STATIC); |
|
sqlite3_bind_text( |
|
stmt, 4, CONST_BUF_LEN(lockdata->locktype), SQLITE_STATIC); |
|
if (lockdata->owner->used) |
|
sqlite3_bind_text( |
|
stmt, 5, CONST_BUF_LEN(lockdata->owner), SQLITE_STATIC); |
|
else |
|
sqlite3_bind_text( |
|
stmt, 5, CONST_STR_LEN(""), SQLITE_STATIC); |
|
if (lockdata->ownerinfo.used) |
|
sqlite3_bind_text( |
|
stmt, 6, CONST_BUF_LEN(&lockdata->ownerinfo), SQLITE_STATIC); |
|
else |
|
sqlite3_bind_text( |
|
stmt, 6, CONST_STR_LEN(""), SQLITE_STATIC); |
|
sqlite3_bind_int( |
|
stmt, 7, lockdata->depth); |
|
sqlite3_bind_int( |
|
stmt, 8, lockdata->timeout); |
|
|
|
int status = 1; |
|
if (SQLITE_DONE != sqlite3_step(stmt)) { |
|
status = 0; |
|
#if 0 |
|
fprintf(stderr, "%s: %s\n", __func__, sqlite3_errmsg(pconf->sql->sqlh)); |
|
log_error(pconf->errh, __FILE__, __LINE__, |
|
"%s: %s", __func__, sqlite3_errmsg(pconf->sql->sqlh)); |
|
#endif |
|
} |
|
|
|
sqlite3_reset(stmt); |
|
|
|
return status; |
|
} |
|
#endif |
|
|
|
|
|
#ifdef USE_LOCKS |
|
static int |
|
webdav_lock_refresh (const plugin_config * const pconf, |
|
webdav_lockdata * const lockdata) |
|
{ |
|
if (!pconf->sql) |
|
return 0; |
|
sqlite3_stmt * const stmt = pconf->sql->stmt_locks_refresh; |
|
if (!stmt) |
|
return 0; |
|
|
|
const buffer * const locktoken = &lockdata->locktoken; |
|
sqlite3_bind_text(stmt, 1, CONST_BUF_LEN(locktoken), SQLITE_STATIC); |
|
sqlite3_bind_int( stmt, 2, lockdata->timeout); |
|
|
|
int status = 1; |
|
if (SQLITE_DONE != sqlite3_step(stmt)) { |
|
status = 0; |
|
#if 0 |
|
fprintf(stderr, "%s: %s\n", __func__, sqlite3_errmsg(pconf->sql->sqlh)); |
|
log_error(pconf->errh, __FILE__, __LINE__, |
|
"%s: %s", __func__, sqlite3_errmsg(pconf->sql->sqlh)); |
|
#endif |
|
} |
|
|
|
sqlite3_reset(stmt); |
|
|
|
/*(future: fill in lockscope, locktype, depth from database)*/ |
|
|
|
return status; |
|
} |
|
#endif |
|
|
|
|
|
#ifdef USE_LOCKS |
|
static int |
|
webdav_lock_release (const plugin_config * const pconf, |
|
const webdav_lockdata * const lockdata) |
|
{ |
|
if (!pconf->sql) |
|
return 0; |
|
sqlite3_stmt * const stmt = pconf->sql->stmt_locks_release; |
|
if (!stmt) |
|
return 0; |
|
|
|
sqlite3_bind_text( |
|
stmt, 1, CONST_BUF_LEN(&lockdata->locktoken), SQLITE_STATIC); |
|
|
|
int status = 0; |
|
if (SQLITE_DONE == sqlite3_step(stmt)) |
|
status = (0 != sqlite3_changes(pconf->sql->sqlh)); |
|
else { |
|
#if 0 |
|
fprintf(stderr, "%s: %s\n", __func__, sqlite3_errmsg(pconf->sql->sqlh)); |
|
log_error(pconf->errh, __FILE__, __LINE__, |
|
"%s: %s", __func__, sqlite3_errmsg(pconf->sql->sqlh)); |
|
#endif |
|
} |
|
|
|
sqlite3_reset(stmt); |
|
|
|
return status; |
|
} |
|
#endif |
|
|
|
|
|
static int |
|
webdav_prop_move_uri (const plugin_config * const pconf, |
|
const buffer * const src, |
|
const buffer * const dst) |
|
{ |
|
#ifdef USE_PROPPATCH |
|
if (!pconf->sql) |
|
return 0; |
|
sqlite3_stmt * const stmt = pconf->sql->stmt_props_move; |
|
if (!stmt) |
|
return 0; |
|
|
|
sqlite3_bind_text(stmt, 1, CONST_BUF_LEN(dst), SQLITE_STATIC); |
|
sqlite3_bind_text(stmt, 2, CONST_BUF_LEN(src), SQLITE_STATIC); |
|
|
|
if (SQLITE_DONE != sqlite3_step(stmt)) { |
|
#if 0 |
|
fprintf(stderr, "%s: %s\n", __func__, sqlite3_errmsg(pconf->sql->sqlh)); |
|
log_error(pconf->errh, __FILE__, __LINE__, |
|
"%s: %s", __func__, sqlite3_errmsg(pconf->sql->sqlh)); |
|
#endif |
|
} |
|
|
|
sqlite3_reset(stmt); |
|
|
|
#else |
|
UNUSED(pconf); |
|
UNUSED(src); |
|
UNUSED(dst); |
|
#endif |
|
|
|
return 0; |
|
} |
|
|
|
|
|
static int |
|
webdav_prop_move_uri_col (const plugin_config * const pconf, |
|
const buffer * const src, |
|
const buffer * const dst) |
|
{ |
|
#ifdef USE_PROPPATCH |
|
if (!pconf->sql) |
|
return 0; |
|
sqlite3_stmt * const stmt = pconf->sql->stmt_props_move_col; |
|
if (!stmt) |
|
return 0; |
|
|
|
#ifdef __COVERITY__ |
|
force_assert(0 != src->used); |
|
#endif |
|
|
|
sqlite3_bind_text(stmt, 1, CONST_BUF_LEN(dst), SQLITE_STATIC); |
|
sqlite3_bind_int( stmt, 2, (int)src->used); |
|
sqlite3_bind_int( stmt, 3, (int)src->used-1); |
|
sqlite3_bind_text(stmt, 4, CONST_BUF_LEN(src), SQLITE_STATIC); |
|
|
|
if (SQLITE_DONE != sqlite3_step(stmt)) { |
|
#if 0 |
|
fprintf(stderr, "%s: %s\n", __func__, sqlite3_errmsg(pconf->sql->sqlh)); |
|
log_error(pconf->errh, __FILE__, __LINE__, |
|
"%s: %s", __func__, sqlite3_errmsg(pconf->sql->sqlh)); |
|
#endif |
|
} |
|
|
|
sqlite3_reset(stmt); |
|
|
|
#else |
|
UNUSED(pconf); |
|
UNUSED(src); |
|
UNUSED(dst); |
|
#endif |
|
|
|
return 0; |
|
} |
|
|
|
|
|
static int |
|
webdav_prop_delete_uri (const plugin_config * const pconf, |
|
const buffer * const uri) |
|
{ |
|
#ifdef USE_PROPPATCH |
|
if (!pconf->sql) |
|
return 0; |
|
sqlite3_stmt * const stmt = pconf->sql->stmt_props_delete; |
|
if (!stmt) |
|
return 0; |
|
|
|
sqlite3_bind_text(stmt, 1, CONST_BUF_LEN(uri), SQLITE_STATIC); |
|
|
|
if (SQLITE_DONE != sqlite3_step(stmt)) { |
|
#if 0 |
|
fprintf(stderr, "%s: %s\n", __func__, sqlite3_errmsg(pconf->sql->sqlh)); |
|
log_error(pconf->errh, __FILE__, __LINE__, |
|
"%s: %s", __func__, sqlite3_errmsg(pconf->sql->sqlh)); |
|
#endif |
|
} |
|
|
|
sqlite3_reset(stmt); |
|
|
|
#else |
|
UNUSED(pconf); |
|
UNUSED(uri); |
|
#endif |
|
|
|
return 0; |
|
} |
|
|
|
|
|
static int |
|
webdav_prop_copy_uri (const plugin_config * const pconf, |
|
const buffer * const src, |
|
const buffer * const dst) |
|
{ |
|
#ifdef USE_PROPPATCH |
|
if (!pconf->sql) |
|
return 0; |
|
sqlite3_stmt * const stmt = pconf->sql->stmt_props_copy; |
|
if (!stmt) |
|
return 0; |
|
|
|
sqlite3_bind_text(stmt, 1, CONST_BUF_LEN(dst), SQLITE_STATIC); |
|
sqlite3_bind_text(stmt, 2, CONST_BUF_LEN(src), SQLITE_STATIC); |
|
|
|
if (SQLITE_DONE != sqlite3_step(stmt)) { |
|
#if 0 |
|
fprintf(stderr, "%s: %s\n", __func__, sqlite3_errmsg(pconf->sql->sqlh)); |
|
log_error(pconf->errh, __FILE__, __LINE__, |
|
"%s: %s", __func__, sqlite3_errmsg(pconf->sql->sqlh)); |
|
#endif |
|
} |
|
|
|
sqlite3_reset(stmt); |
|
|
|
#else |
|
UNUSED(pconf); |
|
UNUSED(dst); |
|
UNUSED(src); |
|
#endif |
|
|
|
return 0; |
|
} |
|
|
|
|
|
#ifdef USE_PROPPATCH |
|
static int |
|
webdav_prop_delete (const plugin_config * const pconf, |
|
const buffer * const uri, |
|
const char * const prop_name, |
|
const char * const prop_ns) |
|
{ |
|
if (!pconf->sql) |
|
return 0; |
|
sqlite3_stmt * const stmt = pconf->sql->stmt_props_delete_prop; |
|
if (!stmt) |
|
return 0; |
|
|
|
sqlite3_bind_text(stmt, 1, CONST_BUF_LEN(uri), SQLITE_STATIC); |
|
sqlite3_bind_text(stmt, 2, prop_name, strlen(prop_name), SQLITE_STATIC); |
|
sqlite3_bind_text(stmt, 3, prop_ns, strlen(prop_ns), SQLITE_STATIC); |
|
|
|
if (SQLITE_DONE != sqlite3_step(stmt)) { |
|
#if 0 |
|
fprintf(stderr, "%s: %s\n", __func__, sqlite3_errmsg(pconf->sql->sqlh)); |
|
log_error(pconf->errh, __FILE__, __LINE__, |
|
"%s: %s", __func__, sqlite3_errmsg(pconf->sql->sqlh)); |
|
#endif |
|
} |
|
|
|
sqlite3_reset(stmt); |
|
|
|
return 0; |
|
} |
|
#endif |
|
|
|
|
|
#ifdef USE_PROPPATCH |
|
static int |
|
webdav_prop_update (const plugin_config * const pconf, |
|
const buffer * const uri, |
|
const char * const prop_name, |
|
const char * const prop_ns, |
|
const char * const prop_value) |
|
{ |
|
if (!pconf->sql) |
|
return 0; |
|
sqlite3_stmt * const stmt = pconf->sql->stmt_props_update_prop; |
|
if (!stmt) |
|
return 0; |
|
|
|
sqlite3_bind_text(stmt, 1, CONST_BUF_LEN(uri), SQLITE_STATIC); |
|
sqlite3_bind_text(stmt, 2, prop_name, strlen(prop_name), SQLITE_STATIC); |
|
sqlite3_bind_text(stmt, 3, prop_ns, strlen(prop_ns), SQLITE_STATIC); |
|
sqlite3_bind_text(stmt, 4, prop_value, strlen(prop_value), SQLITE_STATIC); |
|
|
|
if (SQLITE_DONE != sqlite3_step(stmt)) { |
|
#if 0 |
|
fprintf(stderr, "%s: %s\n", __func__, sqlite3_errmsg(pconf->sql->sqlh)); |
|
log_error(pconf->errh, __FILE__, __LINE__, |
|
"%s: %s", __func__, sqlite3_errmsg(pconf->sql->sqlh)); |
|
#endif |
|
} |
|
|
|
sqlite3_reset(stmt); |
|
|
|
return 0; |
|
} |
|
#endif |
|
|
|
|
|
static int |
|
webdav_prop_select_prop (const plugin_config * const pconf, |
|
const buffer * const uri, |
|
const webdav_property_name * const prop, |
|
buffer * const b) |
|
{ |
|
#ifdef USE_PROPPATCH |
|
if (!pconf->sql) |
|
return -1; |
|
sqlite3_stmt * const stmt = pconf->sql->stmt_props_select_prop; |
|
if (!stmt) |
|
return -1; /* not found */ |
|
|
|
sqlite3_bind_text(stmt, 1, CONST_BUF_LEN(uri), SQLITE_STATIC); |
|
sqlite3_bind_text(stmt, 2, prop->name, prop->namelen, SQLITE_STATIC); |
|
sqlite3_bind_text(stmt, 3, prop->ns, prop->nslen, SQLITE_STATIC); |
|
|
|
if (SQLITE_ROW == sqlite3_step(stmt)) { |
|
webdav_xml_prop(b, prop, (char *)sqlite3_column_text(stmt, 0), |
|
(uint32_t)sqlite3_column_bytes(stmt, 0)); |
|
sqlite3_reset(stmt); |
|
return 0; /* found */ |
|
} |
|
sqlite3_reset(stmt); |
|
#else |
|
UNUSED(pconf); |
|
UNUSED(uri); |
|
UNUSED(prop); |
|
UNUSED(b); |
|
#endif |
|
return -1; /* not found */ |
|
} |
|
|
|
|
|
static void |
|
webdav_prop_select_props (const plugin_config * const pconf, |
|
const buffer * const uri, |
|
buffer * const b) |
|
{ |
|
#ifdef USE_PROPPATCH |
|
if (!pconf->sql) |
|
return; |
|
sqlite3_stmt * const stmt = pconf->sql->stmt_props_select_props; |
|
if (!stmt) |
|
return; |
|
|
|
sqlite3_bind_text(stmt, 1, CONST_BUF_LEN(uri), SQLITE_STATIC); |
|
|
|
while (SQLITE_ROW == sqlite3_step(stmt)) { |
|
webdav_property_name prop; |
|
prop.ns = (char *)sqlite3_column_text(stmt, 1); |
|
prop.name = (char *)sqlite3_column_text(stmt, 0); |
|
prop.nslen = (uint32_t)sqlite3_column_bytes(stmt, 1); |
|
prop.namelen = (uint32_t)sqlite3_column_bytes(stmt, 0); |
|
webdav_xml_prop(b, &prop, (char *)sqlite3_column_text(stmt, 2), |
|
(uint32_t)sqlite3_column_bytes(stmt, 2)); |
|
} |
|
|
|
sqlite3_reset(stmt); |
|
#else |
|
UNUSED(pconf); |
|
UNUSED(uri); |
|
UNUSED(b); |
|
#endif |
|
} |
|
|
|
|
|
static int |
|
webdav_prop_select_propnames (const plugin_config * const pconf, |
|
const buffer * const uri, |
|
buffer * const b) |
|
{ |
|
#ifdef USE_PROPPATCH |
|
if (!pconf->sql) |
|
return 0; |
|
sqlite3_stmt * const stmt = pconf->sql->stmt_props_select_propnames; |
|
if (!stmt) |
|
return 0; |
|
|
|
/* get all property names (EMPTY) */ |
|
sqlite3_bind_text(stmt, 1, CONST_BUF_LEN(uri), SQLITE_STATIC); |
|
|
|
while (SQLITE_ROW == sqlite3_step(stmt)) { |
|
webdav_property_name prop; |
|
prop.ns = (char *)sqlite3_column_text(stmt, 1); |
|
prop.name = (char *)sqlite3_column_text(stmt, 0); |
|
prop.nslen = (uint32_t)sqlite3_column_bytes(stmt, 1); |
|
prop.namelen = (uint32_t)sqlite3_column_bytes(stmt, 0); |
|
webdav_xml_prop(b, &prop, NULL, 0); |
|
} |
|
|
|
sqlite3_reset(stmt); |
|
|
|
#else |
|
UNUSED(pconf); |
|
UNUSED(uri); |
|
UNUSED(b); |
|
#endif |
|
|
|
return 0; |
|
} |
|
|
|
|
|
#if defined(__APPLE__) && defined(__MACH__) |
|
#include <copyfile.h> /* fcopyfile() *//* OS X 10.5+ */ |
|
#endif |
|
#ifdef HAVE_ELFTC_COPYFILE/* __FreeBSD__ */ |
|
#include <libelftc.h> /* elftc_copyfile() */ |
|
#endif |
|
#ifdef __linux__ |
|
#include <sys/sendfile.h> /* sendfile() */ |
|
#endif |
|
|
|
/* file copy (blocking) |
|
* fds should point to regular files (S_ISREG()) (not dir, symlink, or other) |
|
* fds should not have O_NONBLOCK flag set |
|
* (unless O_NONBLOCK not relevant for files on a given operating system) |
|
* isz should be size of input file, and is a param to avoid extra fstat() |
|
* since size is needed for Linux sendfile(), as well as posix_fadvise(). |
|
* caller should handler fchmod() and copying extended attribute, if desired |
|
*/ |
|
__attribute_noinline__ |
|
static int |
|
webdav_fcopyfile_sz (int ifd, int ofd, off_t isz) |
|
{ |
|
if (0 == isz) |
|
return 0; |
|
|
|
#ifdef _WIN32 |
|
/* Windows CopyFile() not usable here; operates on filenames, not fds */ |
|
#else |
|
/*(file descriptors to *regular files* on most OS ignore O_NONBLOCK)*/ |
|
/*fcntl(ifd, F_SETFL, fcntl(ifd, F_GETFL, 0) & ~O_NONBLOCK);*/ |
|
/*fcntl(ofd, F_SETFL, fcntl(ofd, F_GETFL, 0) & ~O_NONBLOCK);*/ |
|
#endif |
|
|
|
#if defined(__APPLE__) && defined(__MACH__) |
|
if (0 == fcopyfile(ifd, ofd, NULL, COPYFILE_ALL)) |
|
return 0; |
|
|
|
if (0 != lseek(ifd, 0, SEEK_SET)) return -1; |
|
if (0 != lseek(ofd, 0, SEEK_SET)) return -1; |
|
#endif |
|
|
|
#ifdef HAVE_ELFTC_COPYFILE /* __FreeBSD__ */ |
|
if (0 == elftc_copyfile(ifd, ofd)) |
|
return 0; |
|
|
|
if (0 != lseek(ifd, 0, SEEK_SET)) return -1; |
|
if (0 != lseek(ofd, 0, SEEK_SET)) return -1; |
|
#endif |
|
|
|
#ifdef __linux__ /* Linux 2.6.33+ sendfile() supports file-to-file copy */ |
|
off_t offset = 0; |
|
while (offset < isz && sendfile(ifd,ofd,&offset,(size_t)(isz-offset)) >= 0); |
|
if (offset == isz) |
|
return 0; |
|
|
|
/*lseek(ifd, 0, SEEK_SET);*/ /*(ifd offset not modified due to &offset arg)*/ |
|
if (0 != lseek(ofd, 0, SEEK_SET)) return -1; |
|
#endif |
|
|
|
ssize_t rd, wr, off; |
|
char buf[16384]; |
|
do { |
|
do { |
|
rd = read(ifd, buf, sizeof(buf)); |
|
} while (-1 == rd && errno == EINTR); |
|
if (rd < 0) return rd; |
|
|
|
off = 0; |
|
do { |
|
wr = write(ofd, buf+off, (size_t)(rd-off)); |
|
} while (wr >= 0 ? (off += wr) != rd : errno == EINTR); |
|
if (wr < 0) return -1; |
|
} while (rd > 0); |
|
return rd; |
|
} |
|
|
|
|
|
static int |
|
webdav_if_match_or_unmodified_since (connection * const con, struct stat *st) |
|
{ |
|
const buffer *im = (0 != con->conf.etag_flags) |
|
? http_header_request_get(con, HTTP_HEADER_OTHER, |
|
CONST_STR_LEN("If-Match")) |
|
: NULL; |
|
|
|
const buffer *inm = (0 != con->conf.etag_flags) |
|
? http_header_request_get(con, HTTP_HEADER_IF_NONE_MATCH, |
|
CONST_STR_LEN("If-None-Match")) |
|
: NULL; |
|
|
|
const buffer *ius = |
|
http_header_request_get(con, HTTP_HEADER_OTHER, |
|
CONST_STR_LEN("If-Unmodified-Since")); |
|
|
|
if (NULL == im && NULL == inm && NULL == ius) return 0; |
|
|
|
struct stat stp; |
|
if (NULL == st) |
|
st = (0 == lstat(con->physical.path->ptr, &stp)) ? &stp : NULL; |
|
|
|
buffer *etagb = con->physical.etag; |
|
if (NULL != st && (NULL != im || NULL != inm)) { |
|
etag_create(etagb, st, con->conf.etag_flags); |
|
etag_mutate(etagb, etagb); |
|
} |
|
|
|
if (NULL != im) { |
|
if (NULL == st || !etag_is_equal(etagb, im->ptr, 0)) |
|
return 412; /* Precondition Failed */ |
|
} |
|
|
|
if (NULL != inm) { |
|
if (NULL == st |
|
? !buffer_is_equal_string(inm,CONST_STR_LEN("*")) |
|
|| (errno != ENOENT && errno != ENOTDIR) |
|
: etag_is_equal(etagb, inm->ptr, 1)) |
|
return 412; /* Precondition Failed */ |
|
} |
|
|
|
if (NULL != ius) { |
|
if (NULL == st) |
|
return 412; /* Precondition Failed */ |
|
struct tm itm, *ftm = gmtime(&st->st_mtime); |
|
if (NULL == strptime(ius->ptr, "%a, %d %b %Y %H:%M:%S GMT", &itm) |
|
|| mktime(ftm) > mktime(&itm)) { /* timegm() not standard */ |
|
return 412; /* Precondition Failed */ |
|
} |
|
} |
|
|
|
return 0; |
|
} |
|
|
|
|
|
static void |
|
webdav_response_etag (connection * const con, struct stat *st) |
|
{ |
|
if (0 != con->conf.etag_flags) { |
|
buffer *etagb = con->physical.etag; |
|
etag_create(etagb, st, con->conf.etag_flags); |
|
stat_cache_update_entry(CONST_BUF_LEN(con->physical.path), st, etagb); |
|
etag_mutate(etagb, etagb); |
|
http_header_response_set(con, HTTP_HEADER_ETAG, |
|
CONST_STR_LEN("ETag"), |
|
CONST_BUF_LEN(etagb)); |
|
} |
|
else { |
|
stat_cache_update_entry(CONST_BUF_LEN(con->physical.path), st, NULL); |
|
} |
|
} |
|
|
|
|
|
static void |
|
webdav_parent_modified (const buffer *path) |
|
{ |
|
size_t dirlen = buffer_string_length(path); |
|
const char *fn = path->ptr; |
|
/*force_assert(0 != dirlen);*/ |
|
/*force_assert(fn[0] == '/');*/ |
|
if (fn[dirlen-1] == '/') --dirlen; |
|
if (0 != dirlen) while (fn[--dirlen] != '/') ; |
|
if (0 == dirlen) dirlen = 1; /* root dir ("/") */ |
|
stat_cache_invalidate_entry(fn, dirlen); |
|
} |
|
|
|
|
|
static int |
|
webdav_parse_Depth (connection * const con) |
|
{ |
|
/* Depth = "Depth" ":" ("0" | "1" | "infinity") */ |
|
const buffer * const h = |
|
http_header_request_get(con, HTTP_HEADER_OTHER, CONST_STR_LEN("Depth")); |
|
if (NULL != h) { |
|
/* (leading LWS is removed during header parsing in request.c) */ |
|
switch (*h->ptr) { |
|
case '0': return 0; |
|
case '1': return 1; |
|
/*case 'i':*/ /* e.g. "infinity" */ |
|
/*case 'I':*/ /* e.g. "Infinity" */ |
|
default: return -1;/* treat not-'0' and not-'1' as "infinity" */ |
|
} |
|
} |
|
|
|
return -1; /* default value is -1 to represent "infinity" */ |
|
} |
|
|
|
|
|
static int |
|
webdav_unlinkat (const plugin_config * const pconf, const buffer * const uri, |
|
const int dfd, const char * const d_name, size_t len) |
|
{ |
|
if (0 == unlinkat(dfd, d_name, 0)) { |
|
stat_cache_delete_entry(d_name, len); |
|
return webdav_prop_delete_uri(pconf, uri); |
|
} |
|
|
|
switch(errno) { |
|
case EACCES: case EPERM: return 403; /* Forbidden */ |
|
case ENOENT: return 404; /* Not Found */ |
|
default: return 501; /* Not Implemented */ |
|
} |
|
} |
|
|
|
|
|
static int |
|
webdav_delete_file (const plugin_config * const pconf, |
|
const physical_st * const dst) |
|
{ |
|
if (0 == unlink(dst->path->ptr)) { |
|
stat_cache_delete_entry(CONST_BUF_LEN(dst->path)); |
|
return webdav_prop_delete_uri(pconf, dst->rel_path); |
|
} |
|
|
|
switch(errno) { |
|
case EACCES: case EPERM: return 403; /* Forbidden */ |
|
case ENOENT: return 404; /* Not Found */ |
|
default: return 501; /* Not Implemented */ |
|
} |
|
} |
|
|
|
|
|
static int |
|
webdav_delete_dir (const plugin_config * const pconf, |
|
physical_st * const dst, |
|
buffer * const b, |
|
const int flags) |
|
{ |
|
int multi_status = 0; |
|
const int dfd = fdevent_open_dirname(dst->path->ptr, 0); |
|
DIR * const dir = (dfd >= 0) ? fdopendir(dfd) : NULL; |
|
if (NULL == dir) { |
|
if (dfd >= 0) close(dfd); |
|
webdav_xml_response_status(b, dst->rel_path, 403); |
|
return 1; |
|
} |
|
|
|
/* dst is modified in place to extend path, |
|
* so be sure to restore to base each loop iter */ |
|
const uint32_t dst_path_used = dst->path->used; |
|
const uint32_t dst_rel_path_used = dst->rel_path->used; |
|
int s_isdir; |
|
struct dirent *de; |
|
while (NULL != (de = readdir(dir))) { |
|
if (de->d_name[0] == '.' |
|
&& (de->d_name[1] == '\0' |
|
|| (de->d_name[1] == '.' && de->d_name[2] == '\0'))) |
|
continue; /* ignore "." and ".." */ |
|
|
|
#ifdef _DIRENT_HAVE_D_TYPE |
|
if (de->d_type != DT_UNKNOWN) |
|
s_isdir = (de->d_type == DT_DIR); |
|
else |
|
#endif |
|
{ |
|
struct stat st; |
|
if (0 != fstatat(dfd, de->d_name, &st, AT_SYMLINK_NOFOLLOW)) |
|
continue; /* file *just* disappeared? */ |
|
/* parent rmdir() will fail later if file still exists |
|
* and fstatat() failed for other reasons */ |
|
s_isdir = S_ISDIR(st.st_mode); |
|
} |
|
|
|
const uint32_t len = (uint32_t) _D_EXACT_NAMLEN(de); |
|
if (flags & WEBDAV_FLAG_LC_NAMES) /*(needed at least for rel_path)*/ |
|
webdav_str_len_to_lower(de->d_name, len); |
|
buffer_append_string_len(dst->path, de->d_name, len); |
|
buffer_append_string_len(dst->rel_path, de->d_name, len); |
|
|
|
if (s_isdir) { |
|
buffer_append_string_len(dst->path, CONST_STR_LEN("/")); |
|
buffer_append_string_len(dst->rel_path, CONST_STR_LEN("/")); |
|
multi_status |= webdav_delete_dir(pconf, dst, b, flags); |
|
} |
|
else { |
|
int status = |
|
webdav_unlinkat(pconf, dst->rel_path, dfd, de->d_name, len); |
|
if (0 != status) { |
|
webdav_xml_response_status(b, dst->rel_path, status); |
|
multi_status = 1; |
|
} |
|
} |
|
|
|
dst->path->ptr[ (dst->path->used = dst_path_used) -1] = '\0'; |
|
dst->rel_path->ptr[(dst->rel_path->used = dst_rel_path_used)-1] = '\0'; |
|
} |
|
closedir(dir); |
|
|
|
if (0 == multi_status) { |
|
int rmdir_status; |
|
if (0 == rmdir(dst->path->ptr)) |
|
rmdir_status = webdav_prop_delete_uri(pconf, dst->rel_path); |
|
else { |
|
switch(errno) { |
|
case EACCES: |
|
case EPERM: rmdir_status = 403; break; /* Forbidden */ |
|
case ENOENT: rmdir_status = 404; break; /* Not Found */ |
|
default: rmdir_status = 501; break; /* Not Implemented */ |
|
} |
|
} |
|
if (0 != rmdir_status) { |
|
webdav_xml_response_status(b, dst->rel_path, rmdir_status); |
|
multi_status = 1; |
|
} |
|
} |
|
|
|
return multi_status; |
|
} |
|
|
|
|
|
static int |
|
webdav_linktmp_rename (const plugin_config * const pconf, |
|
const buffer * const src, |
|
const buffer * const dst) |
|
{ |
|
buffer * const tmpb = pconf->tmpb; |
|
int rc = -1; /*(not zero)*/ |
|
|
|
buffer_copy_buffer(tmpb, dst); |
|
buffer_append_string_len(tmpb, CONST_STR_LEN(".")); |
|
buffer_append_int(tmpb, (long)getpid()); |
|
buffer_append_string_len(tmpb, CONST_STR_LEN(".")); |
|
buffer_append_uint_hex_lc(tmpb, (uintptr_t)pconf); /*(stack/heap addr)*/ |
|
buffer_append_string_len(tmpb, CONST_STR_LEN("~")); |
|
if (buffer_string_length(tmpb) < PATH_MAX |
|
&& 0 == linkat(AT_FDCWD, src->ptr, AT_FDCWD, tmpb->ptr, 0)) { |
|
|
|
rc = rename(tmpb->ptr, dst->ptr); |
|
|
|
/* unconditionally unlink() src if rename() succeeds, just in case |
|
* dst previously existed and was already hard-linked to src. From |
|
* 'man -s 2 rename': |
|
* If oldpath and newpath are existing hard links referring to the |
|
* same file, then rename() does nothing, and returns a success |
|
* status. |
|
* This introduces a small race condition between the rename() and |
|
* unlink() should new file have been created at src in the middle, |
|
* though unlikely if locks are used since locks have not yet been |
|
* released. */ |
|
unlink(tmpb->ptr); |
|
} |
|
return rc; |
|
} |
|
|
|
|
|
static int |
|
webdav_copytmp_rename (const plugin_config * const pconf, |
|
const physical_st * const src, |
|
const physical_st * const dst, |
|
const int overwrite) |
|
{ |
|
buffer * const tmpb = pconf->tmpb; |
|
buffer_copy_buffer(tmpb, dst->path); |
|
buffer_append_string_len(tmpb, CONST_STR_LEN(".")); |
|
buffer_append_int(tmpb, (long)getpid()); |
|
buffer_append_string_len(tmpb, CONST_STR_LEN(".")); |
|
buffer_append_uint_hex_lc(tmpb, (uintptr_t)pconf); /*(stack/heap addr)*/ |
|
buffer_append_string_len(tmpb, CONST_STR_LEN("~")); |
|
if (buffer_string_length(tmpb) >= PATH_MAX) |
|
return 414; /* URI Too Long */ |
|
|
|
/* code does not currently support symlinks in webdav collections; |
|
* disallow symlinks as target when opening src and dst */ |
|
struct stat st; |
|
const int ifd = fdevent_open_cloexec(src->path->ptr, 0, O_RDONLY, 0); |
|
if (ifd < 0) |
|
return 403; /* Forbidden */ |
|
if (0 != fstat(ifd, &st) || !S_ISREG(st.st_mode)) { |
|
close(ifd); |
|
return 403; /* Forbidden */ |
|
} |
|
const int ofd = fdevent_open_cloexec(tmpb->ptr, 0, |
|
O_WRONLY | O_CREAT | O_EXCL | O_TRUNC, |
|
WEBDAV_FILE_MODE); |
|
if (ofd < 0) { |
|
close(ifd); |
|
return 403; /* Forbidden */ |
|
} |
|
|
|
/* perform *blocking* copy (not O_NONBLOCK); |
|
* blocks server from doing any other work until after copy completes |
|
* (should reach here only if unable to use link() and rename() |
|
* due to copy/move crossing device boundaries within the workspace) */ |
|
int rc = webdav_fcopyfile_sz(ifd, ofd, st.st_size); |
|
|
|
close(ifd); |
|
const int wc = close(ofd); |
|
|
|
if (0 != rc || 0 != wc) { |
|
/* error reading or writing files */ |
|
rc = (0 != wc && wc == ENOSPC) ? 507 : 403; |
|
unlink(tmpb->ptr); |
|
return rc; |
|
} |
|
|
|
#ifndef RENAME_NOREPLACE /*(renameat2() not well-supported yet)*/ |
|
if (!overwrite) { |
|
struct stat stb; |
|
if (0 == lstat(dst->path->ptr, &stb) || errno != ENOENT) |
|
return 412; /* Precondition Failed */ |
|
/* TOC-TOU race between lstat() and rename(), |
|
* but this is reasonable attempt to not overwrite existing entity */ |
|
} |
|
if (0 == rename(tmpb->ptr, dst->path->ptr)) |
|
#else |
|
if (0 == renameat2(AT_FDCWD, tmpb->ptr, |
|
AT_FDCWD, dst->path->ptr, |
|
overwrite ? 0 : RENAME_NOREPLACE)) |
|
#endif |
|
{ |
|