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.

5624 lines
205 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 <strings.h> /* strncasecmp() */
#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 "configfile.h"
#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 = buffer_init_string("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;
p->data = NULL;
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;
/* plugin config for all request/connections */
typedef struct {
int config_context_idx;
uint32_t directives;
unsigned short enabled;
unsigned short is_readonly;
unsigned short log_xml;
unsigned short deprecated_unsafe_partial_put_compat;
sql_config *sql;
server *srv;
buffer *tmpb;
buffer *sqlite_db_name; /* not used after worker init */
array *opts;
} plugin_config;
typedef struct {
PLUGIN_DATA;
int nconfig;
plugin_config **config_storage;
} plugin_data;
/* init the plugin data */
INIT_FUNC(mod_webdav_init) {
return calloc(1, sizeof(plugin_data));
}
/* destroy the plugin data */
FREE_FUNC(mod_webdav_free) {
plugin_data *p = (plugin_data *)p_d;
if (!p) return HANDLER_GO_ON;
if (p->config_storage) {
#ifdef USE_PROPPATCH
for (int i = 0; i < p->nconfig; ++i) {
plugin_config * const s = p->config_storage[i];
if (NULL == s) continue;
buffer_free(s->sqlite_db_name);
array_free(s->opts);
sql_config * const sql = s->sql;
if (!sql || !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);
}
#endif
free(p->config_storage);
}
free(p);
UNUSED(srv);
return HANDLER_GO_ON;
}
__attribute_cold__
static handler_t mod_webdav_sqlite3_init (plugin_config * restrict s, log_error_st *errh);
/* handle plugin config and check values */
SETDEFAULTS_FUNC(mod_webdav_set_defaults) {
#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
config_values_t cv[] = {
{ "webdav.activate", NULL, T_CONFIG_BOOLEAN, T_CONFIG_SCOPE_CONNECTION },
{ "webdav.is-readonly", NULL, T_CONFIG_BOOLEAN, T_CONFIG_SCOPE_CONNECTION },
{ "webdav.log-xml", NULL, T_CONFIG_BOOLEAN, T_CONFIG_SCOPE_CONNECTION },
{ "webdav.sqlite-db-name", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION },
{ "webdav.opts", NULL, T_CONFIG_ARRAY, T_CONFIG_SCOPE_CONNECTION },
{ NULL, NULL, T_CONFIG_UNSET, T_CONFIG_SCOPE_UNSET }
};
plugin_data * const p = (plugin_data *)p_d;
p->config_storage = calloc(srv->config_context->used, sizeof(plugin_config *));
force_assert(p->config_storage);
const size_t n_context = p->nconfig = srv->config_context->used;
for (size_t i = 0; i < n_context; ++i) {
data_config const *config =
(data_config const *)srv->config_context->data[i];
plugin_config * const restrict s = calloc(1, sizeof(plugin_config));
force_assert(s);
p->config_storage[i] = s;
s->sqlite_db_name = buffer_init();
s->opts = array_init();
cv[0].destination = &(s->enabled);
cv[1].destination = &(s->is_readonly);
cv[2].destination = &(s->log_xml);
cv[3].destination = s->sqlite_db_name;
cv[4].destination = s->opts;
if (0 != config_insert_values_global(srv, config->value, cv, i == 0 ? T_CONFIG_SCOPE_SERVER : T_CONFIG_SCOPE_CONNECTION)) {
return HANDLER_ERROR;
}
if (!buffer_is_empty(s->sqlite_db_name)) {
if (mod_webdav_sqlite3_init(s, srv->errh) == HANDLER_ERROR)
return HANDLER_ERROR;
}
for (size_t j = 0, used = s->opts->used; j < used; ++j) {
data_string *ds = (data_string *)s->opts->data[j];
if (buffer_is_equal_string(ds->key,
CONST_STR_LEN("deprecated-unsafe-partial-put"))
&& buffer_is_equal_string(ds->value, CONST_STR_LEN("enable"))) {
s->deprecated_unsafe_partial_put_compat = 1;
continue;
}
log_error(srv->errh, __FILE__, __LINE__,
"unrecognized webdav.opts: %.*s",
BUFFER_INTLEN_PTR(ds->key));
return HANDLER_ERROR;
}
}
if (n_context) {
p->config_storage[0]->srv = srv;
p->config_storage[0]->tmpb = srv->tmp_buf;
}
return HANDLER_GO_ON;
}
#define PATCH_OPTION(x) pconf->x = s->x;
static void
mod_webdav_patch_connection (server * const restrict srv,
connection * const restrict con,
const plugin_data * const restrict p,
plugin_config * const restrict pconf)
{
const plugin_config *s = p->config_storage[0];
memcpy(pconf, s, sizeof(*s));
data_config ** const restrict context_data =
(data_config **)srv->config_context->data;
for (size_t i = 1; i < srv->config_context->used; ++i) {
data_config * const dc = context_data[i];
if (!config_check_cond(srv, con, dc))
continue; /* condition did not match */
s = p->config_storage[i];
/* merge config */
for (size_t j = 0; j < dc->value->used; ++j) {
data_unset *du = dc->value->data[j];
if (buffer_is_equal_string(du->key, CONST_STR_LEN("webdav.activate"))) {
PATCH_OPTION(enabled);
} else if (buffer_is_equal_string(du->key, CONST_STR_LEN("webdav.is-readonly"))) {
PATCH_OPTION(is_readonly);
} else if (buffer_is_equal_string(du->key, CONST_STR_LEN("webdav.log-xml"))) {
PATCH_OPTION(log_xml);
#ifdef USE_PROPPATCH
} else if (buffer_is_equal_string(du->key, CONST_STR_LEN("webdav.sqlite-db-name"))) {
PATCH_OPTION(sql);
#endif
} else if (buffer_is_equal_string(du->key, CONST_STR_LEN("webdav.opts"))) {
PATCH_OPTION(deprecated_unsafe_partial_put_compat);
}
}
}
}
URIHANDLER_FUNC(mod_webdav_uri_handler)
{
UNUSED(srv);
if (con->request.http_method != HTTP_METHOD_OPTIONS)
return HANDLER_GO_ON;
plugin_config pconf;
mod_webdav_patch_connection(srv, 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 {
buffer locktoken;
buffer lockroot;
buffer ownerinfo;
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->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->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-";
li_itostrn(tbuf+sizeof("Second-")-1, sizeof(tbuf)-(sizeof("Second-")-1),
lockdata->timeout);
const uint32_t tbuf_len = strlen(tbuf);
http_header_response_set(con, HTTP_HEADER_OTHER,