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.
 
 
 
 
 
 

5763 lines
210 KiB

/*
* mod_webdav - WEBDAV support for lighttpd
*
* Fully-rewritten from original
* Copyright(c) 2019 Glenn Strauss gstrauss()gluelogic.com All rights reserved
* License: BSD 3-clause (same as lighttpd)
*/
/*
* 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: !r->conf.follow_symlink is not currently honored;
* symlinks are followed. Supporting !r->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
#ifndef PATH_MAX
#define PATH_MAX 4096
#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 "request.h"
#include "response.h" /* http_response_redirect_to_directory() */
#include "stat_cache.h" /* stat_cache_mimetype_by_ext() */
#include "plugin.h"
#if (defined(__linux__) || defined(__CYGWIN__)) && defined(O_TMPFILE)
static int has_proc_self_fd;
#endif
#define http_status_get(r) ((r)->http_status)
#define http_status_set_fin(r, code) ((r)->resp_body_finished = 1,\
(r)->handler_module = NULL, \
(r)->http_status = (code))
#define http_status_set(r, code) ((r)->http_status = (code))
#define http_status_unset(r) ((r)->http_status = 0)
#define http_status_is_set(r) (0 != (r)->http_status)
__attribute_cold__
__attribute_noinline__
static int http_status_set_error (request_st * const r, int status) {
return http_status_set_fin(r, 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);
REQUEST_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->handle_request_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() */
__attribute_noinline__
static void
webdav_str_len_to_lower (char * const ss, const uint32_t len)
{
/*(caller must ensure that len not truncated to (int);
* for current intended use, NAME_MAX typically <= 255)*/
unsigned char * const restrict s = (unsigned char *)ss;
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;
enum { /* opts bitflags */
MOD_WEBDAV_UNSAFE_PARTIAL_PUT_COMPAT = 0x1
,MOD_WEBDAV_UNSAFE_PROPFIND_FOLLOW_SYMLINK = 0x2
,MOD_WEBDAV_PROPFIND_DEPTH_INFINITY = 0x4
};
typedef struct {
unsigned short enabled;
unsigned short is_readonly;
unsigned short log_xml;
unsigned short opts;
sql_config *sql;
buffer *tmpb;
buffer *sqlite_db_name; /* not used after worker init */
} 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->opts = (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(request_st * const r, 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(r, (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 */
if (cpv->v.a->used) {
unsigned short opts = 0;
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"))
&& buffer_eq_slen(&ds->value,
CONST_STR_LEN("enable"))) {
opts |= MOD_WEBDAV_UNSAFE_PARTIAL_PUT_COMPAT;
continue;
}
if (buffer_is_equal_string(&ds->key,
CONST_STR_LEN("propfind-depth-infinity"))
&& buffer_eq_slen(&ds->value,
CONST_STR_LEN("enable"))) {
opts |= MOD_WEBDAV_PROPFIND_DEPTH_INFINITY;
continue;
}
if (buffer_is_equal_string(&ds->key,
CONST_STR_LEN("unsafe-propfind-follow-symlink"))
&& buffer_eq_slen(&ds->value,
CONST_STR_LEN("enable"))) {
opts |= MOD_WEBDAV_UNSAFE_PROPFIND_FOLLOW_SYMLINK;
continue;
}
log_error(srv->errh, __FILE__, __LINE__,
"unrecognized webdav.opts: %.*s",
BUFFER_INTLEN_PTR(&ds->key));
return HANDLER_ERROR;
}
cpv->v.u = opts;
cpv->vtype = T_CONFIG_LOCAL;
}
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);
}
#if (defined(__linux__) || defined(__CYGWIN__)) && defined(O_TMPFILE)
struct stat st;
has_proc_self_fd = (0 == stat("/proc/self/fd", &st));
#endif
return HANDLER_GO_ON;
}
URIHANDLER_FUNC(mod_webdav_uri_handler)
{
if (r->http_method != HTTP_METHOD_OPTIONS)
return HANDLER_GO_ON;
plugin_config pconf;
mod_webdav_patch_config(r, (plugin_data *)p_d, &pconf);
if (!pconf.enabled) return HANDLER_GO_ON;
/* [RFC4918] 18 DAV Compliance Classes */
http_header_response_set(r, 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(r, HTTP_HEADER_OTHER,
CONST_STR_LEN("MS-Author-Via"),
CONST_STR_LEN("DAV"));
if (pconf.is_readonly)
http_header_response_append(r, HTTP_HEADER_OTHER,
CONST_STR_LEN("Allow"),
CONST_STR_LEN("PROPFIND"));
else
http_header_response_append(r, 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, request_st * const r)
{
http_header_response_set(r, 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 (request_st * const r,
const plugin_config * const pconf,
buffer * const ms)
{
http_status_set_fin(r, 207); /* Multi-status */
buffer * const b = /*(optimization; buf extended as needed)*/
chunkqueue_append_buffer_open_sz(r->write_queue, 128 + ms->used);
webdav_xml_doctype(b, r);
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(r->conf.errh, __FILE__, __LINE__,
"XML-response-body: %.*s", BUFFER_INTLEN_PTR(b));
chunkqueue_append_buffer_commit(r->write_queue);
}
#ifdef USE_PROPPATCH
static void
webdav_xml_doc_multistatus_response (request_st * const r,
const plugin_config * const pconf,
buffer * const ms)
{
http_status_set_fin(r, 207); /* Multi-status */
buffer * const b = /*(optimization; buf extended as needed)*/
chunkqueue_append_buffer_open_sz(r->write_queue, 128 + ms->used);
webdav_xml_doctype(b, r);
buffer_append_string_len(b, CONST_STR_LEN(
"<D:multistatus xmlns:D=\"DAV:\">\n"
"<D:response>\n"));
webdav_xml_href(b, &r->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(r->conf.errh, __FILE__, __LINE__,
"XML-response-body: %.*s", BUFFER_INTLEN_PTR(b));
chunkqueue_append_buffer_commit(r->write_queue);
}
#endif
#ifdef USE_LOCKS
static void
webdav_xml_doc_lock_acquired (request_st * const r,
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 = sizeof("Second-")-1 +
li_itostrn(tbuf+sizeof("Second-")-1, sizeof(tbuf)-(sizeof("Second-")-1),
lockdata->timeout);
http_header_response_set(r, HTTP_HEADER_OTHER,
CONST_STR_LEN("Timeout"),
tbuf, tbuf_len);
buffer * const b =
chunkqueue_append_buffer_open_sz(r->write_queue, 1024);
webdav_xml_doctype(b, r);
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(r->conf.errh, __FILE__, __LINE__,
"XML-response-body: %.*s", BUFFER_INTLEN_PTR(b));
chunkqueue_append_buffer_commit(r->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 (request_st * const r)
{
http_status_set(r, 403); /* Forbidden */
r->resp_body_finished = 1;
buffer * const b =
chunkqueue_append_buffer_open_sz(r->write_queue, 256);
webdav_xml_doctype(b, r);
buffer_append_string_len(b, CONST_STR_LEN(
"<D:error><DAV:propfind-finite-depth/></D:error>\n"));
chunkqueue_append_buffer_commit(r->write_queue);
}
#ifdef USE_LOCKS
__attribute_cold__
static void
webdav_xml_doc_error_lock_token_matches_request_uri (request_st * const r)
{
http_status_set(r, 409); /* Conflict */
r->resp_body_finished = 1;
buffer * const b =
chunkqueue_append_buffer_open_sz(r->write_queue, 256);
webdav_xml_doctype(b, r);
buffer_append_string_len(b, CONST_STR_LEN(
"<D:error><DAV:lock-token-matches-request-uri/></D:error>\n"));
chunkqueue_append_buffer_commit(r->write_queue);
}
#endif
#ifdef USE_LOCKS
__attribute_cold__
static void
webdav_xml_doc_423_locked (request_st * const r, buffer * const hrefs,
const char * const errtag, const uint32_t errtaglen)
{
http_status_set(r, 423); /* Locked */
r->resp_body_finished = 1;
buffer * const b = /*(optimization; buf extended as needed)*/
chunkqueue_append_buffer_open_sz(r->write_queue, 256 + hrefs->used);
webdav_xml_doctype(b, r);
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(r->write_queue);
}
#endif
#ifdef USE_LOCKS
__attribute_cold__
static void
webdav_xml_doc_error_lock_token_submitted (request_st * const r,
buffer * const hrefs)
{
webdav_xml_doc_423_locked(r, hrefs,
CONST_STR_LEN("lock-token-submitted"));
}
#endif
#ifdef USE_LOCKS
__attribute_cold__
static void
webdav_xml_doc_error_no_conflicting_lock (request_st * const r,
buffer * const hrefs)
{
webdav_xml_doc_423_locked(r, 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__)
#if __ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__ >= 1050
#include <copyfile.h> /* fcopyfile() *//* OS X 10.5+ */
#endif
#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 __ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__ >= 1050
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
#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 (request_st * const r, struct stat *st)
{
const buffer *im = (0 != r->conf.etag_flags)
? http_header_request_get(r, HTTP_HEADER_OTHER,
CONST_STR_LEN("If-Match"))
: NULL;
const buffer *inm = (0 != r->conf.etag_flags)
? http_header_request_get(r, HTTP_HEADER_IF_NONE_MATCH,
CONST_STR_LEN("If-None-Match"))
: NULL;
const buffer *ius =
http_header_request_get(r, 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(r->physical.path.ptr, &stp)) ? &stp : NULL;
buffer *etagb = &r->physical.etag;
if (NULL != st && (NULL != im || NULL != inm)) {
etag_create(etagb, st, r->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 (request_st * const r, struct stat *st)
{
if (0 != r->conf.etag_flags) {
buffer *etagb = &r->physical.etag;
etag_create(etagb, st, r->conf.etag_flags);
stat_cache_update_entry(CONST_BUF_LEN(&r->physical.path), st, etagb);
etag_mutate(etagb, etagb);
http_header_response_set(r, HTTP_HEADER_ETAG,
CONST_STR_LEN("ETag"),
CONST_BUF_LEN(etagb));
}
else {
stat_cache_update_entry(CONST_BUF_LEN(&r->physical.path), st, NULL);
}
}
static void
webdav_parent_modified (const buffer *path)
{
uint32_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);
}
__attribute_pure__
static int
webdav_parse_Depth (const request_st * const r)
{
/* Depth = "Depth" ":" ("0" | "1" | "infinity") */
const buffer * const h =
http_header_request_get(r, 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, uint32_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
{
/* unconditional stat cache deletion
* (not worth extra syscall/race to detect overwritten or not) */
stat_cache_delete_entry(CONST_BUF_LEN(&dst->path));
return 0;
}
else {
const int errnum = errno;
unlink(tmpb->ptr);
switch (errnum) {
case ENOENT:
case ENOTDIR:
case EISDIR: return 409; /* Conflict */
case EEXIST: return 412; /* Precondition Failed */
default: return 403; /* Forbidden */
}
}
}
static int
webdav_copymove_file (const plugin_config * const pconf,
const physical_st * const src,
const physical_st * const dst,
int * const flags)
{
const int overwrite = (*flags & WEBDAV_FLAG_OVERWRITE);
if (*flags & WEBDAV_FLAG_MOVE_RENAME) {
#ifndef RENAME_NOREPLACE /*(renameat2() not well-supported yet)*/
if (!overwrite) {
struct stat st;
if (0 == lstat(dst->path.ptr, &st) || 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(src->path.ptr, dst->path.ptr))
#else
if (0 == renameat2(AT_FDCWD, src->path.ptr,
AT_FDCWD, dst->path.ptr,
overwrite ? 0 : RENAME_NOREPLACE))
#endif
{
/* 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. */
if (overwrite) unlink(src->path.ptr);
/* unconditional stat cache deletion
* (not worth extra syscall/race to detect overwritten or not) */
stat_cache_delete_entry(CONST_BUF_LEN(&dst->path));
stat_cache_delete_entry(CONST_BUF_LEN(&src->path));
webdav_prop_move_uri(pconf, &src->rel_path, &dst->rel_path);
return 0;
}
else if (errno == EEXIST)
return 412; /* Precondition Failed */
}
else if (*flags & WEBDAV_FLAG_COPY_LINK) {
if (0 == linkat(AT_FDCWD, src->path.ptr, AT_FDCWD, dst->path.ptr, 0)){
webdav_prop_copy_uri(pconf, &src->rel_path, &dst->rel_path);
return 0;
}
else if (errno == EEXIST) {
if (!overwrite)
return 412; /* Precondition Failed */
if (0 == webdav_linktmp_rename(pconf, &src->path, &dst->path)) {
webdav_prop_copy_uri(pconf, &src->rel_path, &dst->rel_path);
return 0;
}
}
else if (errno == EXDEV) {
*flags &= ~WEBDAV_FLAG_COPY_LINK;
*flags |= WEBDAV_FLAG_COPY_XDEV;
}
}
/* link() or rename() failed; fall back to copy to tempfile and rename() */
int status = webdav_copytmp_rename(pconf, src, dst, overwrite);
if (0 == status) {
webdav_prop_copy_uri(pconf, &src->rel_path, &dst->rel_path);
if (*flags & (WEBDAV_FLAG_MOVE_RENAME|WEBDAV_FLAG_MOVE_XDEV))
webdav_delete_file(pconf, src);
/*(copy successful, but how should we report if delete fails?)*/
}
return status;
}
static int
webdav_mkdir (const plugin_config * const pconf,
const physical_st * const dst,
const int overwrite)
{
if (0 == mkdir(dst->path.ptr, WEBDAV_DIR_MODE)) {
webdav_parent_modified(&dst->path);
return 0;
}
switch (errno) {
case EEXIST:
case ENOTDIR: break;
case ENOENT: return 409; /* Conflict */
case EPERM:
default: return 403; /* Forbidden */
}
/* [RFC4918] 9.3.1 MKCOL Status Codes
* 40