summaryrefslogtreecommitdiffstats
path: root/modules/dav
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--modules/dav/fs/Makefile.in3
-rw-r--r--modules/dav/fs/NWGNUmakefile269
-rw-r--r--modules/dav/fs/config6.m423
-rw-r--r--modules/dav/fs/dbm.c771
-rw-r--r--modules/dav/fs/lock.c1445
-rw-r--r--modules/dav/fs/mod_dav_fs.c108
-rw-r--r--modules/dav/fs/mod_dav_fs.dep203
-rw-r--r--modules/dav/fs/mod_dav_fs.dsp135
-rw-r--r--modules/dav/fs/mod_dav_fs.mak407
-rw-r--r--modules/dav/fs/repos.c2254
-rw-r--r--modules/dav/fs/repos.h84
-rw-r--r--modules/dav/lock/Makefile.in3
-rw-r--r--modules/dav/lock/NWGNUmakefile259
-rw-r--r--modules/dav/lock/config6.m417
-rw-r--r--modules/dav/lock/locks.c1211
-rw-r--r--modules/dav/lock/locks.h33
-rw-r--r--modules/dav/lock/mod_dav_lock.c104
-rw-r--r--modules/dav/lock/mod_dav_lock.dep100
-rw-r--r--modules/dav/lock/mod_dav_lock.dsp127
-rw-r--r--modules/dav/lock/mod_dav_lock.mak389
-rw-r--r--modules/dav/main/Makefile.in3
-rw-r--r--modules/dav/main/NWGNUmakefile268
-rw-r--r--modules/dav/main/config5.m421
-rw-r--r--modules/dav/main/liveprop.c140
-rw-r--r--modules/dav/main/mod_dav.c4946
-rw-r--r--modules/dav/main/mod_dav.dep354
-rw-r--r--modules/dav/main/mod_dav.dsp147
-rw-r--r--modules/dav/main/mod_dav.h2553
-rw-r--r--modules/dav/main/mod_dav.mak406
-rw-r--r--modules/dav/main/props.c1125
-rw-r--r--modules/dav/main/providers.c58
-rw-r--r--modules/dav/main/std_liveprop.c226
-rw-r--r--modules/dav/main/util.c2152
-rw-r--r--modules/dav/main/util_lock.c798
34 files changed, 21142 insertions, 0 deletions
diff --git a/modules/dav/fs/Makefile.in b/modules/dav/fs/Makefile.in
new file mode 100644
index 0000000..7c5c149
--- /dev/null
+++ b/modules/dav/fs/Makefile.in
@@ -0,0 +1,3 @@
+# a modules Makefile has no explicit targets -- they will be defined by
+# whatever modules are enabled. just grab special.mk to deal with this.
+include $(top_srcdir)/build/special.mk
diff --git a/modules/dav/fs/NWGNUmakefile b/modules/dav/fs/NWGNUmakefile
new file mode 100644
index 0000000..e402428
--- /dev/null
+++ b/modules/dav/fs/NWGNUmakefile
@@ -0,0 +1,269 @@
+#
+# Declare the sub-directories to be built here
+#
+
+SUBDIRS = \
+ $(EOLIST)
+
+#
+# Get the 'head' of the build environment. This includes default targets and
+# paths to tools
+#
+
+include $(AP_WORK)/build/NWGNUhead.inc
+
+#
+# build this level's files
+
+#
+# Make sure all needed macro's are defined
+#
+
+#
+# These directories will be at the beginning of the include list, followed by
+# INCDIRS
+#
+XINCDIRS += \
+ $(APR)/include \
+ $(APRUTIL)/include \
+ $(AP_WORK)/include \
+ $(AP_WORK)/server/mpm/netware \
+ $(AP_WORK)/modules/dav/main \
+ $(NWOS) \
+ $(EOLIST)
+
+#
+# These flags will come after CFLAGS
+#
+XCFLAGS += \
+ $(EOLIST)
+
+#
+# These defines will come after DEFINES
+#
+XDEFINES += \
+ $(EOLIST)
+
+#
+# These flags will be added to the link.opt file
+#
+XLFLAGS += \
+ $(EOLIST)
+
+#
+# These values will be appended to the correct variables based on the value of
+# RELEASE
+#
+ifeq "$(RELEASE)" "debug"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+ifeq "$(RELEASE)" "noopt"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+ifeq "$(RELEASE)" "release"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+#
+# These are used by the link target if an NLM is being generated
+# This is used by the link 'name' directive to name the nlm. If left blank
+# TARGET_nlm (see below) will be used.
+#
+NLM_NAME = moddavfs
+
+#
+# This is used by the link '-desc ' directive.
+# If left blank, NLM_NAME will be used.
+#
+NLM_DESCRIPTION = Apache $(VERSION_STR) DAV FileSystem Sub-Module
+
+#
+# This is used by the '-threadname' directive. If left blank,
+# NLM_NAME Thread will be used.
+NLM_THREAD_NAME = $(NLM_NAME) Thread
+
+#
+# If this is specified, it will override VERSION value in
+# $(AP_WORK)/build/NWGNUenvironment.inc
+#
+NLM_VERSION =
+
+#
+# If this is specified, it will override the default of 64K
+#
+NLM_STACK_SIZE = 65536
+
+
+#
+# If this is specified it will be used by the link '-entry' directive
+#
+NLM_ENTRY_SYM =
+
+#
+# If this is specified it will be used by the link '-exit' directive
+#
+NLM_EXIT_SYM =
+
+#
+# If this is specified it will be used by the link '-check' directive
+#
+NLM_CHECK_SYM =
+
+#
+# If this is specified it will be used by the link '-flags' directive
+#
+NLM_FLAGS =
+
+#
+# Declare all target files (you must add your files here)
+#
+
+#
+# If there is an NLM target, put it here
+#
+TARGET_nlm = \
+ $(OBJDIR)/$(NLM_NAME).nlm \
+ $(EOLIST)
+
+#
+# If there is an LIB target, put it here
+#
+TARGET_lib = \
+ $(EOLIST)
+
+#
+# These are the OBJ files needed to create the NLM target above.
+# Paths must all use the '/' character
+#
+FILES_nlm_objs = \
+ $(OBJDIR)/mod_dav_fs.o \
+ $(OBJDIR)/dbm.o \
+ $(OBJDIR)/lock.o \
+ $(OBJDIR)/repos.o \
+ $(OBJDIR)/libprews.o \
+ $(EOLIST)
+
+#
+# These are the LIB files needed to create the NLM target above.
+# These will be added as a library command in the link.opt file.
+#
+FILES_nlm_libs = \
+ $(PRELUDE) \
+ $(EOLIST)
+
+#
+# These are the modules that the above NLM target depends on to load.
+# These will be added as a module command in the link.opt file.
+#
+FILES_nlm_modules = \
+ Apache2 \
+ Libc \
+ mod_dav \
+ $(EOLIST)
+
+#
+# If the nlm has a msg file, put it's path here
+#
+FILE_nlm_msg =
+
+#
+# If the nlm has a hlp file put it's path here
+#
+FILE_nlm_hlp =
+
+#
+# If this is specified, it will override $(NWOS)\copyright.txt.
+#
+FILE_nlm_copyright =
+
+#
+# Any additional imports go here
+#
+FILES_nlm_Ximports = \
+ @libc.imp \
+ @aprlib.imp \
+ @httpd.imp \
+ @../main/dav.imp \
+ $(EOLIST)
+
+# Don't link with Winsock if standard sockets are being used
+ifndef USE_STDSOCKETS
+FILES_nlm_Ximports += @ws2nlm.imp \
+ $(EOLIST)
+endif
+
+#
+# Any symbols exported to here
+#
+FILES_nlm_exports = \
+ dav_fs_module \
+ $(EOLIST)
+
+#
+# These are the OBJ files needed to create the LIB target above.
+# Paths must all use the '/' character
+#
+FILES_lib_objs = \
+ $(EOLIST)
+
+#
+# implement targets and dependancies (leave this section alone)
+#
+
+libs :: $(OBJDIR) $(TARGET_lib)
+
+nlms :: libs $(TARGET_nlm)
+
+#
+# Updated this target to create necessary directories and copy files to the
+# correct place. (See $(AP_WORK)/build/NWGNUhead.inc for examples)
+#
+install :: nlms FORCE
+ $(call COPY,$(OBJDIR)/*.nlm, $(INSTALLBASE)/modules/)
+
+#
+# Any specialized rules here
+#
+
+vpath %.c ../../arch/netware
+
+#
+# Include the 'tail' makefile that has targets that depend on variables defined
+# in this makefile
+#
+
+include $(APBUILD)/NWGNUtail.inc
+
+
diff --git a/modules/dav/fs/config6.m4 b/modules/dav/fs/config6.m4
new file mode 100644
index 0000000..dd26ec8
--- /dev/null
+++ b/modules/dav/fs/config6.m4
@@ -0,0 +1,23 @@
+dnl modules enabled in this directory by default
+
+APACHE_MODPATH_INIT(dav/fs)
+
+dav_fs_objects="mod_dav_fs.lo dbm.lo lock.lo repos.lo"
+
+if test "x$enable_dav" != "x"; then
+ dav_fs_enable=$enable_dav
+else
+ dav_fs_enable=$dav_enable
+fi
+
+case "$host" in
+ *os2*)
+ # OS/2 DLLs must resolve all symbols at build time
+ # and we need some from main DAV module
+ dav_fs_objects="$dav_fs_objects ../main/mod_dav.la"
+ ;;
+esac
+
+APACHE_MODULE(dav_fs, DAV provider for the filesystem. --enable-dav also enables mod_dav_fs., $dav_fs_objects, , $dav_fs_enable,,dav)
+
+APACHE_MODPATH_FINISH
diff --git a/modules/dav/fs/dbm.c b/modules/dav/fs/dbm.c
new file mode 100644
index 0000000..821168e
--- /dev/null
+++ b/modules/dav/fs/dbm.c
@@ -0,0 +1,771 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/*
+** DAV extension module for Apache 2.0.*
+** - Database support using DBM-style databases,
+** part of the filesystem repository implementation
+*/
+
+/*
+** This implementation uses a SDBM database per file and directory to
+** record the properties. These databases are kept in a subdirectory (of
+** the directory in question or the directory that holds the file in
+** question) named by the macro DAV_FS_STATE_DIR (.DAV). The filename of the
+** database is equivalent to the target filename, and is
+** DAV_FS_STATE_FILE_FOR_DIR (.state_for_dir) for the directory itself.
+*/
+
+#include "apr_strings.h"
+#include "apr_file_io.h"
+
+#include "apr_dbm.h"
+
+#define APR_WANT_BYTEFUNC
+#include "apr_want.h" /* for ntohs and htons */
+
+#include "mod_dav.h"
+#include "repos.h"
+#include "http_log.h"
+#include "http_main.h" /* for ap_server_conf */
+
+APLOG_USE_MODULE(dav_fs);
+
+struct dav_db {
+ apr_pool_t *pool;
+ apr_dbm_t *file;
+
+ /* when used as a property database: */
+
+ int version; /* *minor* version of this db */
+
+ dav_buffer ns_table; /* table of namespace URIs */
+ short ns_count; /* number of entries in table */
+ int ns_table_dirty; /* ns_table was modified */
+ apr_hash_t *uri_index; /* map URIs to (1-based) table indices */
+
+ dav_buffer wb_key; /* work buffer for dav_gdbm_key */
+
+ apr_datum_t iter; /* iteration key */
+};
+
+/* -------------------------------------------------------------------------
+ *
+ * GENERIC DBM ACCESS
+ *
+ * For the most part, this just uses the APR DBM functions. They are wrapped
+ * a bit with some error handling (using the mod_dav error functions).
+ */
+
+void dav_dbm_get_statefiles(apr_pool_t *p, const char *fname,
+ const char **state1, const char **state2)
+{
+ if (fname == NULL)
+ fname = DAV_FS_STATE_FILE_FOR_DIR;
+
+ apr_dbm_get_usednames(p, fname, state1, state2);
+}
+
+static dav_error * dav_fs_dbm_error(dav_db *db, apr_pool_t *p,
+ apr_status_t status)
+{
+ int errcode;
+ const char *errstr;
+ dav_error *err;
+ char errbuf[200];
+
+ if (status == APR_SUCCESS)
+ return NULL;
+
+ p = db ? db->pool : p;
+
+ /* There might not be a <db> if we had problems creating it. */
+ if (db == NULL) {
+ errcode = 1;
+ errstr = "Could not open property database.";
+ if (APR_STATUS_IS_EDSOOPEN(status))
+ ap_log_error(APLOG_MARK, APLOG_CRIT, status, ap_server_conf, APLOGNO(00576)
+ "The DBM driver could not be loaded");
+ }
+ else {
+ (void) apr_dbm_geterror(db->file, &errcode, errbuf, sizeof(errbuf));
+ errstr = apr_pstrdup(p, errbuf);
+ }
+
+ err = dav_new_error(p, HTTP_INTERNAL_SERVER_ERROR, errcode, status, errstr);
+ return err;
+}
+
+/* ensure that our state subdirectory is present */
+/* ### does this belong here or in dav_fs_repos.c ?? */
+void dav_fs_ensure_state_dir(apr_pool_t * p, const char *dirname)
+{
+ const char *pathname = apr_pstrcat(p, dirname, "/" DAV_FS_STATE_DIR, NULL);
+
+ /* ### do we need to deal with the umask? */
+
+ /* just try to make it, ignoring any resulting errors */
+ (void) apr_dir_make(pathname, APR_OS_DEFAULT, p);
+}
+
+/* dav_dbm_open_direct: Opens a *dbm database specified by path.
+ * ro = boolean read-only flag.
+ */
+dav_error * dav_dbm_open_direct(apr_pool_t *p, const char *pathname, int ro,
+ dav_db **pdb)
+{
+ apr_status_t status;
+ apr_dbm_t *file = NULL;
+
+ *pdb = NULL;
+
+ if ((status = apr_dbm_open(&file, pathname,
+ ro ? APR_DBM_READONLY : APR_DBM_RWCREATE,
+ APR_OS_DEFAULT, p))
+ != APR_SUCCESS
+ && !ro) {
+ /* ### do something with 'status' */
+
+ /* we can't continue if we couldn't open the file
+ and we need to write */
+ return dav_fs_dbm_error(NULL, p, status);
+ }
+
+ /* may be NULL if we tried to open a non-existent db as read-only */
+ if (file != NULL) {
+ /* we have an open database... return it */
+ *pdb = apr_pcalloc(p, sizeof(**pdb));
+ (*pdb)->pool = p;
+ (*pdb)->file = file;
+ }
+
+ return NULL;
+}
+
+static dav_error * dav_dbm_open(apr_pool_t * p, const dav_resource *resource,
+ int ro, dav_db **pdb)
+{
+ const char *dirpath;
+ const char *fname;
+ const char *pathname;
+
+ /* Get directory and filename for resource */
+ /* ### should test this result value... */
+ (void) dav_fs_dir_file_name(resource, &dirpath, &fname);
+
+ /* If not opening read-only, ensure the state dir exists */
+ if (!ro) {
+ /* ### what are the perf implications of always checking this? */
+ dav_fs_ensure_state_dir(p, dirpath);
+ }
+
+ pathname = apr_pstrcat(p, dirpath, "/" DAV_FS_STATE_DIR "/",
+ fname ? fname : DAV_FS_STATE_FILE_FOR_DIR,
+ NULL);
+
+ /* ### readers cannot open while a writer has this open; we should
+ ### perform a few retries with random pauses. */
+
+ /* ### do we need to deal with the umask? */
+
+ return dav_dbm_open_direct(p, pathname, ro, pdb);
+}
+
+void dav_dbm_close(dav_db *db)
+{
+ apr_dbm_close(db->file);
+}
+
+dav_error * dav_dbm_fetch(dav_db *db, apr_datum_t key, apr_datum_t *pvalue)
+{
+ apr_status_t status;
+
+ if (!key.dptr) {
+ /* no key could be created (namespace not known) => no value */
+ memset(pvalue, 0, sizeof(*pvalue));
+ status = APR_SUCCESS;
+ } else {
+ status = apr_dbm_fetch(db->file, key, pvalue);
+ }
+
+ return dav_fs_dbm_error(db, NULL, status);
+}
+
+dav_error * dav_dbm_store(dav_db *db, apr_datum_t key, apr_datum_t value)
+{
+ apr_status_t status = apr_dbm_store(db->file, key, value);
+
+ return dav_fs_dbm_error(db, NULL, status);
+}
+
+dav_error * dav_dbm_delete(dav_db *db, apr_datum_t key)
+{
+ apr_status_t status = apr_dbm_delete(db->file, key);
+
+ return dav_fs_dbm_error(db, NULL, status);
+}
+
+int dav_dbm_exists(dav_db *db, apr_datum_t key)
+{
+ return apr_dbm_exists(db->file, key);
+}
+
+static dav_error * dav_dbm_firstkey(dav_db *db, apr_datum_t *pkey)
+{
+ apr_status_t status = apr_dbm_firstkey(db->file, pkey);
+
+ return dav_fs_dbm_error(db, NULL, status);
+}
+
+static dav_error * dav_dbm_nextkey(dav_db *db, apr_datum_t *pkey)
+{
+ apr_status_t status = apr_dbm_nextkey(db->file, pkey);
+
+ return dav_fs_dbm_error(db, NULL, status);
+}
+
+void dav_dbm_freedatum(dav_db *db, apr_datum_t data)
+{
+ apr_dbm_freedatum(db->file, data);
+}
+
+/* -------------------------------------------------------------------------
+ *
+ * PROPERTY DATABASE FUNCTIONS
+ */
+
+
+#define DAV_GDBM_NS_KEY "METADATA"
+#define DAV_GDBM_NS_KEY_LEN 8
+
+typedef struct {
+ unsigned char major;
+#define DAV_DBVSN_MAJOR 4
+ /*
+ ** V4 -- 0.9.9 ..
+ ** Prior versions could have keys or values with invalid
+ ** namespace prefixes as a result of the xmlns="" form not
+ ** resetting the default namespace to be "no namespace". The
+ ** namespace would be set to "" which is invalid; it should
+ ** be set to "no namespace".
+ **
+ ** V3 -- 0.9.8
+ ** Prior versions could have values with invalid namespace
+ ** prefixes due to an incorrect mapping of input to propdb
+ ** namespace indices. Version bumped to obsolete the old
+ ** values.
+ **
+ ** V2 -- 0.9.7
+ ** This introduced the xml:lang value into the property value's
+ ** record in the propdb.
+ **
+ ** V1 -- .. 0.9.6
+ ** Initial version.
+ */
+
+
+ unsigned char minor;
+#define DAV_DBVSN_MINOR 0
+
+ short ns_count;
+
+} dav_propdb_metadata;
+
+struct dav_deadprop_rollback {
+ apr_datum_t key;
+ apr_datum_t value;
+};
+
+struct dav_namespace_map {
+ int *ns_map;
+};
+
+/*
+** Internal function to build a key
+**
+** WARNING: returns a pointer to a "static" buffer holding the key. The
+** value must be copied or no longer used if this function is
+** called again.
+*/
+static apr_datum_t dav_build_key(dav_db *db, const dav_prop_name *name)
+{
+ char nsbuf[20];
+ apr_size_t l_ns, l_name = strlen(name->name);
+ apr_datum_t key = { 0 };
+
+ /*
+ * Convert namespace ID to a string. "no namespace" is an empty string,
+ * so the keys will have the form ":name". Otherwise, the keys will
+ * have the form "#:name".
+ */
+ if (*name->ns == '\0') {
+ nsbuf[0] = '\0';
+ l_ns = 0;
+ }
+ else {
+ long ns_id = (long)apr_hash_get(db->uri_index, name->ns,
+ APR_HASH_KEY_STRING);
+
+
+ if (ns_id == 0) {
+ /* the namespace was not found(!) */
+ return key; /* zeroed */
+ }
+
+ l_ns = apr_snprintf(nsbuf, sizeof(nsbuf), "%ld", ns_id - 1);
+ }
+
+ /* assemble: #:name */
+ dav_set_bufsize(db->pool, &db->wb_key, l_ns + 1 + l_name + 1);
+ memcpy(db->wb_key.buf, nsbuf, l_ns);
+ db->wb_key.buf[l_ns] = ':';
+ memcpy(&db->wb_key.buf[l_ns + 1], name->name, l_name + 1);
+
+ /* build the database key */
+ key.dsize = l_ns + 1 + l_name + 1;
+ key.dptr = db->wb_key.buf;
+
+ return key;
+}
+
+static void dav_append_prop(apr_pool_t *pool,
+ const char *name, const char *value,
+ apr_text_header *phdr)
+{
+ const char *s;
+ const char *lang = value;
+
+ /* skip past the xml:lang value */
+ value += strlen(lang) + 1;
+
+ if (*value == '\0') {
+ /* the property is an empty value */
+ if (*name == ':') {
+ /* "no namespace" case */
+ s = apr_psprintf(pool, "<%s/>" DEBUG_CR, name+1);
+ }
+ else {
+ s = apr_psprintf(pool, "<ns%s/>" DEBUG_CR, name);
+ }
+ }
+ else if (*lang != '\0') {
+ if (*name == ':') {
+ /* "no namespace" case */
+ s = apr_psprintf(pool, "<%s xml:lang=\"%s\">%s</%s>" DEBUG_CR,
+ name+1, lang, value, name+1);
+ }
+ else {
+ s = apr_psprintf(pool, "<ns%s xml:lang=\"%s\">%s</ns%s>" DEBUG_CR,
+ name, lang, value, name);
+ }
+ }
+ else if (*name == ':') {
+ /* "no namespace" case */
+ s = apr_psprintf(pool, "<%s>%s</%s>" DEBUG_CR, name+1, value, name+1);
+ }
+ else {
+ s = apr_psprintf(pool, "<ns%s>%s</ns%s>" DEBUG_CR, name, value, name);
+ }
+
+ apr_text_append(pool, phdr, s);
+}
+
+static dav_error * dav_propdb_open(apr_pool_t *pool,
+ const dav_resource *resource, int ro,
+ dav_db **pdb)
+{
+ dav_db *db;
+ dav_error *err;
+ apr_datum_t key;
+ apr_datum_t value = { 0 };
+
+ *pdb = NULL;
+
+ /*
+ ** Return if an error occurred, or there is no database.
+ **
+ ** NOTE: db could be NULL if we attempted to open a readonly
+ ** database that doesn't exist. If we require read/write
+ ** access, then a database was created and opened.
+ */
+ if ((err = dav_dbm_open(pool, resource, ro, &db)) != NULL
+ || db == NULL)
+ return err;
+
+ db->uri_index = apr_hash_make(pool);
+
+ key.dptr = DAV_GDBM_NS_KEY;
+ key.dsize = DAV_GDBM_NS_KEY_LEN;
+ if ((err = dav_dbm_fetch(db, key, &value)) != NULL) {
+ /* ### push a higher-level description? */
+ return err;
+ }
+
+ if (value.dptr == NULL) {
+ dav_propdb_metadata m = {
+ DAV_DBVSN_MAJOR, DAV_DBVSN_MINOR, 0
+ };
+
+ /*
+ ** If there is no METADATA key, then the database may be
+ ** from versions 0.9.0 .. 0.9.4 (which would be incompatible).
+ ** These can be identified by the presence of an NS_TABLE entry.
+ */
+ key.dptr = "NS_TABLE";
+ key.dsize = 8;
+ if (dav_dbm_exists(db, key)) {
+ dav_dbm_close(db);
+
+ /* call it a major version error */
+ return dav_new_error(pool, HTTP_INTERNAL_SERVER_ERROR,
+ DAV_ERR_PROP_BAD_MAJOR, 0,
+ "Prop database has the wrong major "
+ "version number and cannot be used.");
+ }
+
+ /* initialize a new metadata structure */
+ dav_set_bufsize(pool, &db->ns_table, sizeof(m));
+ memcpy(db->ns_table.buf, &m, sizeof(m));
+ }
+ else {
+ dav_propdb_metadata m;
+ long ns;
+ const char *uri;
+
+ dav_set_bufsize(pool, &db->ns_table, value.dsize);
+ memcpy(db->ns_table.buf, value.dptr, value.dsize);
+
+ memcpy(&m, value.dptr, sizeof(m));
+ if (m.major != DAV_DBVSN_MAJOR) {
+ dav_dbm_close(db);
+
+ return dav_new_error(pool, HTTP_INTERNAL_SERVER_ERROR,
+ DAV_ERR_PROP_BAD_MAJOR, 0,
+ "Prop database has the wrong major "
+ "version number and cannot be used.");
+ }
+ db->version = m.minor;
+ db->ns_count = ntohs(m.ns_count);
+
+ dav_dbm_freedatum(db, value);
+
+ /* create db->uri_index */
+ for (ns = 0, uri = db->ns_table.buf + sizeof(dav_propdb_metadata);
+ ns++ < db->ns_count;
+ uri += strlen(uri) + 1) {
+
+ /* we must copy the key, in case ns_table.buf moves */
+ apr_hash_set(db->uri_index,
+ apr_pstrdup(pool, uri), APR_HASH_KEY_STRING,
+ (void *)ns);
+ }
+ }
+
+ *pdb = db;
+ return NULL;
+}
+
+static void dav_propdb_close(dav_db *db)
+{
+
+ if (db->ns_table_dirty) {
+ dav_propdb_metadata m;
+ apr_datum_t key;
+ apr_datum_t value;
+ dav_error *err;
+
+ key.dptr = DAV_GDBM_NS_KEY;
+ key.dsize = DAV_GDBM_NS_KEY_LEN;
+
+ value.dptr = db->ns_table.buf;
+ value.dsize = db->ns_table.cur_len;
+
+ /* fill in the metadata that we store into the prop db. */
+ m.major = DAV_DBVSN_MAJOR;
+ m.minor = db->version; /* ### keep current minor version? */
+ m.ns_count = htons(db->ns_count);
+
+ memcpy(db->ns_table.buf, &m, sizeof(m));
+
+ err = dav_dbm_store(db, key, value);
+ if (err != NULL)
+ ap_log_error(APLOG_MARK, APLOG_WARNING, err->aprerr, ap_server_conf,
+ APLOGNO(00577) "Error writing propdb: %s", err->desc);
+ }
+
+ dav_dbm_close(db);
+}
+
+static dav_error * dav_propdb_define_namespaces(dav_db *db, dav_xmlns_info *xi)
+{
+ int ns;
+ const char *uri = db->ns_table.buf + sizeof(dav_propdb_metadata);
+
+ /* within the prop values, we use "ns%d" for prefixes... register them */
+ for (ns = 0; ns < db->ns_count; ++ns, uri += strlen(uri) + 1) {
+
+ /* Empty URIs signify the empty namespace. These do not get a
+ namespace prefix. when we generate the value, we will simply
+ leave off the prefix, which is defined by mod_dav to be the
+ empty namespace. */
+ if (*uri == '\0')
+ continue;
+
+ /* ns_table.buf can move, so copy its value (we want the values to
+ last as long as the provided dav_xmlns_info). */
+ dav_xmlns_add(xi,
+ apr_psprintf(xi->pool, "ns%d", ns),
+ apr_pstrdup(xi->pool, uri));
+ }
+
+ return NULL;
+}
+
+static dav_error * dav_propdb_output_value(dav_db *db,
+ const dav_prop_name *name,
+ dav_xmlns_info *xi,
+ apr_text_header *phdr,
+ int *found)
+{
+ apr_datum_t key = dav_build_key(db, name);
+ apr_datum_t value;
+ dav_error *err;
+
+ if ((err = dav_dbm_fetch(db, key, &value)) != NULL)
+ return err;
+ if (value.dptr == NULL) {
+ *found = 0;
+ return NULL;
+ }
+ *found = 1;
+
+ dav_append_prop(db->pool, key.dptr, value.dptr, phdr);
+
+ dav_dbm_freedatum(db, value);
+
+ return NULL;
+}
+
+static dav_error * dav_propdb_map_namespaces(
+ dav_db *db,
+ const apr_array_header_t *namespaces,
+ dav_namespace_map **mapping)
+{
+ dav_namespace_map *m = apr_palloc(db->pool, sizeof(*m));
+ int i;
+ int *pmap;
+ const char **puri;
+
+ /*
+ ** Iterate over the provided namespaces. If a namespace already appears
+ ** in our internal map of URI -> ns_id, then store that in the map. If
+ ** we don't know the namespace yet, then add it to the map and to our
+ ** table of known namespaces.
+ */
+ m->ns_map = pmap = apr_palloc(db->pool, namespaces->nelts * sizeof(*pmap));
+ for (i = namespaces->nelts, puri = (const char **)namespaces->elts;
+ i-- > 0;
+ ++puri, ++pmap) {
+
+ const char *uri = *puri;
+ apr_size_t uri_len = strlen(uri);
+ long ns_id = (long)apr_hash_get(db->uri_index, uri, uri_len);
+
+ if (ns_id == 0) {
+ dav_check_bufsize(db->pool, &db->ns_table, uri_len + 1);
+ memcpy(db->ns_table.buf + db->ns_table.cur_len, uri, uri_len + 1);
+ db->ns_table.cur_len += uri_len + 1;
+
+ /* copy the uri in case the passed-in namespaces changes in
+ some way. */
+ apr_hash_set(db->uri_index, apr_pstrdup(db->pool, uri), uri_len,
+ (void *)((long)(db->ns_count + 1)));
+
+ db->ns_table_dirty = 1;
+
+ *pmap = db->ns_count++;
+ }
+ else {
+ *pmap = ns_id - 1;
+ }
+ }
+
+ *mapping = m;
+ return NULL;
+}
+
+static dav_error * dav_propdb_store(dav_db *db, const dav_prop_name *name,
+ const apr_xml_elem *elem,
+ dav_namespace_map *mapping)
+{
+ apr_datum_t key = dav_build_key(db, name);
+ apr_datum_t value;
+
+ /* Note: mapping->ns_map was set up in dav_propdb_map_namespaces() */
+
+ /* ### use a db- subpool for these values? clear on exit? */
+
+ /* quote all the values in the element */
+ /* ### be nice to do this without affecting the element itself */
+ /* ### of course, the cast indicates Badness is occurring here */
+ apr_xml_quote_elem(db->pool, (apr_xml_elem *)elem);
+
+ /* generate a text blob for the xml:lang plus the contents */
+ apr_xml_to_text(db->pool, elem, APR_XML_X2T_LANG_INNER, NULL,
+ mapping->ns_map,
+ (const char **)&value.dptr, &value.dsize);
+
+ return dav_dbm_store(db, key, value);
+}
+
+static dav_error * dav_propdb_remove(dav_db *db, const dav_prop_name *name)
+{
+ apr_datum_t key = dav_build_key(db, name);
+ return dav_dbm_delete(db, key);
+}
+
+static int dav_propdb_exists(dav_db *db, const dav_prop_name *name)
+{
+ apr_datum_t key = dav_build_key(db, name);
+ return dav_dbm_exists(db, key);
+}
+
+static const char *dav_get_ns_table_uri(dav_db *db, int ns_id)
+{
+ const char *p = db->ns_table.buf + sizeof(dav_propdb_metadata);
+
+ while (ns_id--)
+ p += strlen(p) + 1;
+
+ return p;
+}
+
+static void dav_set_name(dav_db *db, dav_prop_name *pname)
+{
+ const char *s = db->iter.dptr;
+
+ if (s == NULL) {
+ pname->ns = pname->name = NULL;
+ }
+ else if (*s == ':') {
+ pname->ns = "";
+ pname->name = s + 1;
+ }
+ else {
+ int id = atoi(s);
+
+ pname->ns = dav_get_ns_table_uri(db, id);
+ if (s[1] == ':') {
+ pname->name = s + 2;
+ }
+ else {
+ pname->name = ap_strchr_c(s + 2, ':') + 1;
+ }
+ }
+}
+
+static dav_error * dav_propdb_next_name(dav_db *db, dav_prop_name *pname)
+{
+ dav_error *err;
+
+ /* free the previous key. note: if the loop is aborted, then the DBM
+ will toss the key (via pool cleanup) */
+ if (db->iter.dptr != NULL)
+ dav_dbm_freedatum(db, db->iter);
+
+ if ((err = dav_dbm_nextkey(db, &db->iter)) != NULL)
+ return err;
+
+ /* skip past the METADATA key */
+ if (db->iter.dptr != NULL && *db->iter.dptr == 'M')
+ return dav_propdb_next_name(db, pname);
+
+ dav_set_name(db, pname);
+ return NULL;
+}
+
+static dav_error * dav_propdb_first_name(dav_db *db, dav_prop_name *pname)
+{
+ dav_error *err;
+
+ if ((err = dav_dbm_firstkey(db, &db->iter)) != NULL)
+ return err;
+
+ /* skip past the METADATA key */
+ if (db->iter.dptr != NULL && *db->iter.dptr == 'M')
+ return dav_propdb_next_name(db, pname);
+
+ dav_set_name(db, pname);
+ return NULL;
+}
+
+static dav_error * dav_propdb_get_rollback(dav_db *db,
+ const dav_prop_name *name,
+ dav_deadprop_rollback **prollback)
+{
+ dav_deadprop_rollback *rb = apr_pcalloc(db->pool, sizeof(*rb));
+ apr_datum_t key;
+ apr_datum_t value;
+ dav_error *err;
+
+ key = dav_build_key(db, name);
+ rb->key.dptr = apr_pstrdup(db->pool, key.dptr);
+ rb->key.dsize = key.dsize;
+
+ if ((err = dav_dbm_fetch(db, key, &value)) != NULL)
+ return err;
+ if (value.dptr != NULL) {
+ rb->value.dptr = apr_pmemdup(db->pool, value.dptr, value.dsize);
+ rb->value.dsize = value.dsize;
+ }
+
+ *prollback = rb;
+ return NULL;
+}
+
+static dav_error * dav_propdb_apply_rollback(dav_db *db,
+ dav_deadprop_rollback *rollback)
+{
+ if (!rollback) {
+ return NULL; /* no rollback, nothing to do */
+ }
+
+ if (rollback->value.dptr == NULL) {
+ /* don't fail if the thing isn't really there. */
+ (void) dav_dbm_delete(db, rollback->key);
+ return NULL;
+ }
+
+ return dav_dbm_store(db, rollback->key, rollback->value);
+}
+
+const dav_hooks_db dav_hooks_db_dbm =
+{
+ dav_propdb_open,
+ dav_propdb_close,
+ dav_propdb_define_namespaces,
+ dav_propdb_output_value,
+ dav_propdb_map_namespaces,
+ dav_propdb_store,
+ dav_propdb_remove,
+ dav_propdb_exists,
+ dav_propdb_first_name,
+ dav_propdb_next_name,
+ dav_propdb_get_rollback,
+ dav_propdb_apply_rollback,
+
+ NULL /* ctx */
+};
diff --git a/modules/dav/fs/lock.c b/modules/dav/fs/lock.c
new file mode 100644
index 0000000..c058e2e
--- /dev/null
+++ b/modules/dav/fs/lock.c
@@ -0,0 +1,1445 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/*
+** DAV filesystem lock implementation
+*/
+
+#include "apr.h"
+#include "apr_strings.h"
+#include "apr_file_io.h"
+#include "apr_uuid.h"
+
+#define APR_WANT_MEMFUNC
+#include "apr_want.h"
+
+#include "httpd.h"
+#include "http_log.h"
+
+#include "mod_dav.h"
+#include "repos.h"
+
+
+/* ---------------------------------------------------------------
+**
+** Lock database primitives
+**
+*/
+
+/*
+** LOCK DATABASES
+**
+** Lockdiscovery information is stored in the single lock database specified
+** by the DAVLockDB directive. Information about this db is stored in the
+** global server configuration.
+**
+** KEY
+**
+** The database is keyed by a key_type unsigned char (DAV_TYPE_FNAME)
+** followed by the full path. The key_type DAV_TYPE_INODE is not used anymore.
+**
+** VALUE
+**
+** The value consists of a list of elements.
+** DIRECT LOCK: [char (DAV_LOCK_DIRECT),
+** char (dav_lock_scope),
+** char (dav_lock_type),
+** int depth,
+** time_t expires,
+** apr_uuid_t locktoken,
+** char[] owner,
+** char[] auth_user]
+**
+** INDIRECT LOCK: [char (DAV_LOCK_INDIRECT),
+** apr_uuid_t locktoken,
+** time_t expires,
+** apr_size_t key_size,
+** char[] key]
+** The key is to the collection lock that resulted in this indirect lock
+*/
+
+#define DAV_TRUE 1
+#define DAV_FALSE 0
+
+#define DAV_CREATE_LIST 23
+#define DAV_APPEND_LIST 24
+
+/* Stored lock_discovery prefix */
+#define DAV_LOCK_DIRECT 1
+#define DAV_LOCK_INDIRECT 2
+
+/*
+ * not used anymore
+ * #define DAV_TYPE_INODE 10
+ */
+#define DAV_TYPE_FNAME 11
+
+
+/* ack. forward declare. */
+static dav_error * dav_fs_remove_locknull_member(apr_pool_t *p,
+ const char *filename,
+ dav_buffer *pbuf);
+
+/*
+** Use the opaquelock scheme for locktokens
+*/
+struct dav_locktoken {
+ apr_uuid_t uuid;
+};
+#define dav_compare_locktoken(plt1, plt2) \
+ memcmp(&(plt1)->uuid, &(plt2)->uuid, sizeof((plt1)->uuid))
+
+
+/* #################################################################
+** ### keep these structures (internal) or move fully to dav_lock?
+*/
+
+/*
+** We need to reliably size the fixed-length portion of
+** dav_lock_discovery; best to separate it into another
+** struct for a convenient sizeof, unless we pack lock_discovery.
+*/
+typedef struct dav_lock_discovery_fixed
+{
+ char scope;
+ char type;
+ int depth;
+ time_t timeout;
+} dav_lock_discovery_fixed;
+
+typedef struct dav_lock_discovery
+{
+ struct dav_lock_discovery_fixed f;
+
+ dav_locktoken *locktoken;
+ const char *owner; /* owner field from activelock */
+ const char *auth_user; /* authenticated user who created the lock */
+ struct dav_lock_discovery *next;
+} dav_lock_discovery;
+
+/* Indirect locks represent locks inherited from containing collections.
+ * They reference the lock token for the collection the lock is
+ * inherited from. A lock provider may also define a key to the
+ * inherited lock, for fast datbase lookup. The key is opaque outside
+ * the lock provider.
+ */
+typedef struct dav_lock_indirect
+{
+ dav_locktoken *locktoken;
+ apr_datum_t key;
+ struct dav_lock_indirect *next;
+ time_t timeout;
+} dav_lock_indirect;
+
+/* ################################################################# */
+
+
+/*
+** Stored direct lock info - full lock_discovery length:
+** prefix + Fixed length + lock token + 2 strings + 2 nulls (one for each string)
+*/
+#define dav_size_direct(a) ( 1 + sizeof(dav_lock_discovery_fixed) \
+ + sizeof(apr_uuid_t) \
+ + ((a)->owner ? strlen((a)->owner) : 0) \
+ + ((a)->auth_user ? strlen((a)->auth_user) : 0) \
+ + 2)
+
+/* Stored indirect lock info - lock token and apr_datum_t */
+#define dav_size_indirect(a) (1 + sizeof(apr_uuid_t) \
+ + sizeof(time_t) \
+ + sizeof((a)->key.dsize) + (a)->key.dsize)
+
+/*
+** The lockdb structure.
+**
+** The <db> field may be NULL, meaning one of two things:
+** 1) That we have not actually opened the underlying database (yet). The
+** <opened> field should be false.
+** 2) We opened it readonly and it wasn't present.
+**
+** The delayed opening (determined by <opened>) makes creating a lockdb
+** quick, while deferring the underlying I/O until it is actually required.
+**
+** We export the notion of a lockdb, but hide the details of it. Most
+** implementations will use a database of some kind, but it is certainly
+** possible that alternatives could be used.
+*/
+struct dav_lockdb_private
+{
+ request_rec *r; /* for accessing the uuid state */
+ apr_pool_t *pool; /* a pool to use */
+ const char *lockdb_path; /* where is the lock database? */
+
+ int opened; /* we opened the database */
+ dav_db *db; /* if non-NULL, the lock database */
+};
+typedef struct
+{
+ dav_lockdb pub;
+ dav_lockdb_private priv;
+} dav_lockdb_combined;
+
+/*
+** The private part of the lock structure.
+*/
+struct dav_lock_private
+{
+ apr_datum_t key; /* key into the lock database */
+};
+typedef struct
+{
+ dav_lock pub;
+ dav_lock_private priv;
+ dav_locktoken token;
+} dav_lock_combined;
+
+/*
+** This must be forward-declared so the open_lockdb function can use it.
+*/
+extern const dav_hooks_locks dav_hooks_locks_fs;
+
+
+/* internal function for creating locks */
+static dav_lock *dav_fs_alloc_lock(dav_lockdb *lockdb, apr_datum_t key,
+ const dav_locktoken *locktoken)
+{
+ dav_lock_combined *comb;
+
+ comb = apr_pcalloc(lockdb->info->pool, sizeof(*comb));
+ comb->pub.rectype = DAV_LOCKREC_DIRECT;
+ comb->pub.info = &comb->priv;
+ comb->priv.key = key;
+
+ if (locktoken == NULL) {
+ comb->pub.locktoken = &comb->token;
+ apr_uuid_get(&comb->token.uuid);
+ }
+ else {
+ comb->pub.locktoken = locktoken;
+ }
+
+ return &comb->pub;
+}
+
+/*
+** dav_fs_parse_locktoken
+**
+** Parse an opaquelocktoken URI into a locktoken.
+*/
+static dav_error * dav_fs_parse_locktoken(
+ apr_pool_t *p,
+ const char *char_token,
+ dav_locktoken **locktoken_p)
+{
+ dav_locktoken *locktoken;
+
+ if (ap_strstr_c(char_token, "opaquelocktoken:") != char_token) {
+ return dav_new_error(p,
+ HTTP_BAD_REQUEST, DAV_ERR_LOCK_UNK_STATE_TOKEN, 0,
+ "The lock token uses an unknown State-token "
+ "format and could not be parsed.");
+ }
+ char_token += 16;
+
+ locktoken = apr_pcalloc(p, sizeof(*locktoken));
+ if (apr_uuid_parse(&locktoken->uuid, char_token)) {
+ return dav_new_error(p, HTTP_BAD_REQUEST, DAV_ERR_LOCK_PARSE_TOKEN, 0,
+ "The opaquelocktoken has an incorrect format "
+ "and could not be parsed.");
+ }
+
+ *locktoken_p = locktoken;
+ return NULL;
+}
+
+/*
+** dav_fs_format_locktoken
+**
+** Generate the URI for a locktoken
+*/
+static const char *dav_fs_format_locktoken(
+ apr_pool_t *p,
+ const dav_locktoken *locktoken)
+{
+ char buf[APR_UUID_FORMATTED_LENGTH + 1];
+
+ apr_uuid_format(buf, &locktoken->uuid);
+ return apr_pstrcat(p, "opaquelocktoken:", buf, NULL);
+}
+
+/*
+** dav_fs_compare_locktoken
+**
+** Determine whether two locktokens are the same
+*/
+static int dav_fs_compare_locktoken(
+ const dav_locktoken *lt1,
+ const dav_locktoken *lt2)
+{
+ return dav_compare_locktoken(lt1, lt2);
+}
+
+/*
+** dav_fs_really_open_lockdb:
+**
+** If the database hasn't been opened yet, then open the thing.
+*/
+static dav_error * dav_fs_really_open_lockdb(dav_lockdb *lockdb)
+{
+ dav_error *err;
+
+ if (lockdb->info->opened)
+ return NULL;
+
+ err = dav_dbm_open_direct(lockdb->info->pool,
+ lockdb->info->lockdb_path,
+ lockdb->ro,
+ &lockdb->info->db);
+ if (err != NULL) {
+ return dav_push_error(lockdb->info->pool,
+ HTTP_INTERNAL_SERVER_ERROR,
+ DAV_ERR_LOCK_OPENDB,
+ "Could not open the lock database.",
+ err);
+ }
+
+ /* all right. it is opened now. */
+ lockdb->info->opened = 1;
+
+ return NULL;
+}
+
+/*
+** dav_fs_open_lockdb:
+**
+** "open" the lock database, as specified in the global server configuration.
+** If force is TRUE, then the database is opened now, rather than lazily.
+**
+** Note that only one can be open read/write.
+*/
+static dav_error * dav_fs_open_lockdb(request_rec *r, int ro, int force,
+ dav_lockdb **lockdb)
+{
+ dav_lockdb_combined *comb;
+
+ comb = apr_pcalloc(r->pool, sizeof(*comb));
+ comb->pub.hooks = &dav_hooks_locks_fs;
+ comb->pub.ro = ro;
+ comb->pub.info = &comb->priv;
+ comb->priv.r = r;
+ comb->priv.pool = r->pool;
+
+ comb->priv.lockdb_path = dav_get_lockdb_path(r);
+ if (comb->priv.lockdb_path == NULL) {
+ return dav_new_error(r->pool, HTTP_INTERNAL_SERVER_ERROR,
+ DAV_ERR_LOCK_NO_DB, 0,
+ "A lock database was not specified with the "
+ "DAVLockDB directive. One must be specified "
+ "to use the locking functionality.");
+ }
+
+ /* done initializing. return it. */
+ *lockdb = &comb->pub;
+
+ if (force) {
+ /* ### add a higher-level comment? */
+ return dav_fs_really_open_lockdb(*lockdb);
+ }
+
+ return NULL;
+}
+
+/*
+** dav_fs_close_lockdb:
+**
+** Close it. Duh.
+*/
+static void dav_fs_close_lockdb(dav_lockdb *lockdb)
+{
+ if (lockdb->info->db != NULL)
+ dav_dbm_close(lockdb->info->db);
+}
+
+/*
+** dav_fs_build_key: Given a resource, return a apr_datum_t key
+** to look up lock information for this file.
+*/
+static apr_datum_t dav_fs_build_key(apr_pool_t *p,
+ const dav_resource *resource)
+{
+ const char *pathname = dav_fs_pathname(resource);
+ apr_datum_t key;
+
+ /* ### does this allocation have a proper lifetime? need to check */
+ /* ### can we use a buffer for this? */
+
+ /* size is TYPE + pathname + null */
+ key.dsize = strlen(pathname) + 2;
+ key.dptr = apr_palloc(p, key.dsize);
+ *key.dptr = DAV_TYPE_FNAME;
+ memcpy(key.dptr + 1, pathname, key.dsize - 1);
+ if (key.dptr[key.dsize - 2] == '/')
+ key.dptr[--key.dsize - 1] = '\0';
+ return key;
+}
+
+/*
+** dav_fs_lock_expired: return 1 (true) if the given timeout is in the past
+** or present (the lock has expired), or 0 (false) if in the future
+** (the lock has not yet expired).
+*/
+static int dav_fs_lock_expired(time_t expires)
+{
+ return expires != DAV_TIMEOUT_INFINITE && time(NULL) >= expires;
+}
+
+/*
+** dav_fs_save_lock_record: Saves the lock information specified in the
+** direct and indirect lock lists about path into the lock database.
+** If direct and indirect == NULL, the key is removed.
+*/
+static dav_error * dav_fs_save_lock_record(dav_lockdb *lockdb, apr_datum_t key,
+ dav_lock_discovery *direct,
+ dav_lock_indirect *indirect)
+{
+ dav_error *err;
+ apr_datum_t val = { 0 };
+ char *ptr;
+ dav_lock_discovery *dp = direct;
+ dav_lock_indirect *ip = indirect;
+
+#if DAV_DEBUG
+ if (lockdb->ro) {
+ return dav_new_error(lockdb->info->pool,
+ HTTP_INTERNAL_SERVER_ERROR, 0, 0,
+ "INTERNAL DESIGN ERROR: the lockdb was opened "
+ "readonly, but an attempt to save locks was "
+ "performed.");
+ }
+#endif
+
+ if ((err = dav_fs_really_open_lockdb(lockdb)) != NULL) {
+ /* ### add a higher-level error? */
+ return err;
+ }
+
+ /* If nothing to save, delete key */
+ if (dp == NULL && ip == NULL) {
+ /* don't fail if the key is not present */
+ /* ### but what about other errors? */
+ (void) dav_dbm_delete(lockdb->info->db, key);
+ return NULL;
+ }
+
+ while(dp) {
+ val.dsize += dav_size_direct(dp);
+ dp = dp->next;
+ }
+ while(ip) {
+ val.dsize += dav_size_indirect(ip);
+ ip = ip->next;
+ }
+
+ /* ### can this be apr_palloc() ? */
+ /* ### hmmm.... investigate the use of a buffer here */
+ ptr = val.dptr = apr_pcalloc(lockdb->info->pool, val.dsize);
+ dp = direct;
+ ip = indirect;
+
+ while(dp) {
+ *ptr++ = DAV_LOCK_DIRECT; /* Direct lock - lock_discovery struct follows */
+ memcpy(ptr, dp, sizeof(dp->f)); /* Fixed portion of struct */
+ ptr += sizeof(dp->f);
+ memcpy(ptr, dp->locktoken, sizeof(*dp->locktoken));
+ ptr += sizeof(*dp->locktoken);
+ if (dp->owner == NULL) {
+ *ptr++ = '\0';
+ }
+ else {
+ memcpy(ptr, dp->owner, strlen(dp->owner) + 1);
+ ptr += strlen(dp->owner) + 1;
+ }
+ if (dp->auth_user == NULL) {
+ *ptr++ = '\0';
+ }
+ else {
+ memcpy(ptr, dp->auth_user, strlen(dp->auth_user) + 1);
+ ptr += strlen(dp->auth_user) + 1;
+ }
+
+ dp = dp->next;
+ }
+
+ while(ip) {
+ *ptr++ = DAV_LOCK_INDIRECT; /* Indirect lock prefix */
+ memcpy(ptr, ip->locktoken, sizeof(*ip->locktoken)); /* Locktoken */
+ ptr += sizeof(*ip->locktoken);
+ memcpy(ptr, &ip->timeout, sizeof(ip->timeout)); /* Expire time */
+ ptr += sizeof(ip->timeout);
+ memcpy(ptr, &ip->key.dsize, sizeof(ip->key.dsize)); /* Size of key */
+ ptr += sizeof(ip->key.dsize);
+ memcpy(ptr, ip->key.dptr, ip->key.dsize); /* Key data */
+ ptr += ip->key.dsize;
+ ip = ip->next;
+ }
+
+ if ((err = dav_dbm_store(lockdb->info->db, key, val)) != NULL) {
+ /* ### more details? add an error_id? */
+ return dav_push_error(lockdb->info->pool,
+ HTTP_INTERNAL_SERVER_ERROR,
+ DAV_ERR_LOCK_SAVE_LOCK,
+ "Could not save lock information.",
+ err);
+ }
+
+ return NULL;
+}
+
+/*
+** dav_load_lock_record: Reads lock information about key from lock db;
+** creates linked lists of the direct and indirect locks.
+**
+** If add_method = DAV_APPEND_LIST, the result will be appended to the
+** head of the direct and indirect lists supplied.
+**
+** Passive lock removal: If lock has timed out, it will not be returned.
+** ### How much "logging" does RFC 2518 require?
+*/
+static dav_error * dav_fs_load_lock_record(dav_lockdb *lockdb, apr_datum_t key,
+ int add_method,
+ dav_lock_discovery **direct,
+ dav_lock_indirect **indirect)
+{
+ apr_pool_t *p = lockdb->info->pool;
+ dav_error *err;
+ apr_size_t offset = 0;
+ int need_save = DAV_FALSE;
+ apr_datum_t val = { 0 };
+ dav_lock_discovery *dp;
+ dav_lock_indirect *ip;
+ dav_buffer buf = { 0 };
+
+ if (add_method != DAV_APPEND_LIST) {
+ *direct = NULL;
+ *indirect = NULL;
+ }
+
+ if ((err = dav_fs_really_open_lockdb(lockdb)) != NULL) {
+ /* ### add a higher-level error? */
+ return err;
+ }
+
+ /*
+ ** If we opened readonly and the db wasn't there, then there are no
+ ** locks for this resource. Just exit.
+ */
+ if (lockdb->info->db == NULL)
+ return NULL;
+
+ if ((err = dav_dbm_fetch(lockdb->info->db, key, &val)) != NULL)
+ return err;
+
+ if (!val.dsize)
+ return NULL;
+
+ while (offset < val.dsize) {
+ switch (*(val.dptr + offset++)) {
+ case DAV_LOCK_DIRECT:
+ /* Create and fill a dav_lock_discovery structure */
+
+ dp = apr_pcalloc(p, sizeof(*dp));
+ memcpy(dp, val.dptr + offset, sizeof(dp->f));
+ offset += sizeof(dp->f);
+ dp->locktoken = apr_pmemdup(p, val.dptr + offset, sizeof(*dp->locktoken));
+ offset += sizeof(*dp->locktoken);
+ if (*(val.dptr + offset) == '\0') {
+ ++offset;
+ }
+ else {
+ dp->owner = apr_pstrdup(p, val.dptr + offset);
+ offset += strlen(dp->owner) + 1;
+ }
+
+ if (*(val.dptr + offset) == '\0') {
+ ++offset;
+ }
+ else {
+ dp->auth_user = apr_pstrdup(p, val.dptr + offset);
+ offset += strlen(dp->auth_user) + 1;
+ }
+
+ if (!dav_fs_lock_expired(dp->f.timeout)) {
+ dp->next = *direct;
+ *direct = dp;
+ }
+ else {
+ need_save = DAV_TRUE;
+
+ /* Remove timed-out locknull fm .locknull list */
+ if (*key.dptr == DAV_TYPE_FNAME) {
+ const char *fname = key.dptr + 1;
+ apr_finfo_t finfo;
+ apr_status_t rv;
+
+ /* if we don't see the file, then it's a locknull */
+ rv = apr_stat(&finfo, fname, APR_FINFO_MIN | APR_FINFO_LINK, p);
+ if (rv != APR_SUCCESS && rv != APR_INCOMPLETE) {
+ if ((err = dav_fs_remove_locknull_member(p, fname, &buf)) != NULL) {
+ /* ### push a higher-level description? */
+ return err;
+ }
+ }
+ }
+ }
+ break;
+
+ case DAV_LOCK_INDIRECT:
+ /* Create and fill a dav_lock_indirect structure */
+
+ ip = apr_pcalloc(p, sizeof(*ip));
+ ip->locktoken = apr_pmemdup(p, val.dptr + offset, sizeof(*ip->locktoken));
+ offset += sizeof(*ip->locktoken);
+ memcpy(&ip->timeout, val.dptr + offset, sizeof(ip->timeout));
+ offset += sizeof(ip->timeout);
+ memcpy(&ip->key.dsize, val.dptr + offset, sizeof(ip->key.dsize)); /* length of datum */
+ offset += sizeof(ip->key.dsize);
+ ip->key.dptr = apr_pmemdup(p, val.dptr + offset, ip->key.dsize);
+ offset += ip->key.dsize;
+
+ if (!dav_fs_lock_expired(ip->timeout)) {
+ ip->next = *indirect;
+ *indirect = ip;
+ }
+ else {
+ need_save = DAV_TRUE;
+ /* A locknull resource will never be locked indirectly */
+ }
+
+ break;
+
+ default:
+ dav_dbm_freedatum(lockdb->info->db, val);
+
+ /* ### should use a computed_desc and insert corrupt token data */
+ --offset;
+ return dav_new_error(p,
+ HTTP_INTERNAL_SERVER_ERROR,
+ DAV_ERR_LOCK_CORRUPT_DB, 0,
+ apr_psprintf(p,
+ "The lock database was found to "
+ "be corrupt. offset %"
+ APR_SIZE_T_FMT ", c=%02x",
+ offset, val.dptr[offset]));
+ }
+ }
+
+ dav_dbm_freedatum(lockdb->info->db, val);
+
+ /* Clean up this record if we found expired locks */
+ /*
+ ** ### shouldn't do this if we've been opened READONLY. elide the
+ ** ### timed-out locks from the response, but don't save that info back
+ */
+ if (need_save == DAV_TRUE) {
+ return dav_fs_save_lock_record(lockdb, key, *direct, *indirect);
+ }
+
+ return NULL;
+}
+
+/* resolve <indirect>, returning <*direct> */
+static dav_error * dav_fs_resolve(dav_lockdb *lockdb,
+ dav_lock_indirect *indirect,
+ dav_lock_discovery **direct,
+ dav_lock_discovery **ref_dp,
+ dav_lock_indirect **ref_ip)
+{
+ dav_error *err;
+ dav_lock_discovery *dir;
+ dav_lock_indirect *ind;
+
+ if ((err = dav_fs_load_lock_record(lockdb, indirect->key,
+ DAV_CREATE_LIST,
+ &dir, &ind)) != NULL) {
+ /* ### insert a higher-level description? */
+ return err;
+ }
+ if (ref_dp != NULL) {
+ *ref_dp = dir;
+ *ref_ip = ind;
+ }
+
+ for (; dir != NULL; dir = dir->next) {
+ if (!dav_compare_locktoken(indirect->locktoken, dir->locktoken)) {
+ *direct = dir;
+ return NULL;
+ }
+ }
+
+ /* No match found (but we should have found one!) */
+
+ /* ### use a different description and/or error ID? */
+ return dav_new_error(lockdb->info->pool,
+ HTTP_INTERNAL_SERVER_ERROR,
+ DAV_ERR_LOCK_CORRUPT_DB, 0,
+ "The lock database was found to be corrupt. "
+ "An indirect lock's direct lock could not "
+ "be found.");
+}
+
+/* ---------------------------------------------------------------
+**
+** Property-related lock functions
+**
+*/
+
+/*
+** dav_fs_get_supportedlock: Returns a static string for all supportedlock
+** properties. I think we save more returning a static string than
+** constructing it every time, though it might look cleaner.
+*/
+static const char *dav_fs_get_supportedlock(const dav_resource *resource)
+{
+ static const char supported[] = DEBUG_CR
+ "<D:lockentry>" DEBUG_CR
+ "<D:lockscope><D:exclusive/></D:lockscope>" DEBUG_CR
+ "<D:locktype><D:write/></D:locktype>" DEBUG_CR
+ "</D:lockentry>" DEBUG_CR
+ "<D:lockentry>" DEBUG_CR
+ "<D:lockscope><D:shared/></D:lockscope>" DEBUG_CR
+ "<D:locktype><D:write/></D:locktype>" DEBUG_CR
+ "</D:lockentry>" DEBUG_CR;
+
+ return supported;
+}
+
+/* ---------------------------------------------------------------
+**
+** General lock functions
+**
+*/
+
+/* ---------------------------------------------------------------
+**
+** Functions dealing with lock-null resources
+**
+*/
+
+/*
+** dav_fs_load_locknull_list: Returns a dav_buffer dump of the locknull file
+** for the given directory.
+*/
+static dav_error * dav_fs_load_locknull_list(apr_pool_t *p, const char *dirpath,
+ dav_buffer *pbuf)
+{
+ apr_finfo_t finfo;
+ apr_file_t *file = NULL;
+ dav_error *err = NULL;
+ apr_size_t amt;
+ apr_status_t rv;
+
+ dav_buffer_init(p, pbuf, dirpath);
+
+ if (pbuf->buf[pbuf->cur_len - 1] == '/')
+ pbuf->buf[--pbuf->cur_len] = '\0';
+
+ dav_buffer_place(p, pbuf, "/" DAV_FS_STATE_DIR "/" DAV_FS_LOCK_NULL_FILE);
+
+ /* reset this in case we leave w/o reading into the buffer */
+ pbuf->cur_len = 0;
+
+ if (apr_file_open(&file, pbuf->buf, APR_READ | APR_BINARY, APR_OS_DEFAULT,
+ p) != APR_SUCCESS) {
+ return NULL;
+ }
+
+ rv = apr_file_info_get(&finfo, APR_FINFO_SIZE, file);
+ if (rv != APR_SUCCESS) {
+ err = dav_new_error(p, HTTP_INTERNAL_SERVER_ERROR, 0, rv,
+ apr_psprintf(p,
+ "Opened but could not stat file %s",
+ pbuf->buf));
+ goto loaderror;
+ }
+
+ if (finfo.size != (apr_size_t)finfo.size) {
+ err = dav_new_error(p, HTTP_INTERNAL_SERVER_ERROR, 0, 0,
+ apr_psprintf(p,
+ "Opened but rejected huge file %s",
+ pbuf->buf));
+ goto loaderror;
+ }
+
+ amt = (apr_size_t)finfo.size;
+ dav_set_bufsize(p, pbuf, amt);
+ if ((rv = apr_file_read(file, pbuf->buf, &amt)) != APR_SUCCESS
+ || amt != finfo.size) {
+ err = dav_new_error(p, HTTP_INTERNAL_SERVER_ERROR, 0, rv,
+ apr_psprintf(p,
+ "Failure reading locknull file "
+ "for %s", dirpath));
+
+ /* just in case the caller disregards the returned error */
+ pbuf->cur_len = 0;
+ goto loaderror;
+ }
+
+ loaderror:
+ apr_file_close(file);
+ return err;
+}
+
+/*
+** dav_fs_save_locknull_list: Saves contents of pbuf into the
+** locknull file for dirpath.
+*/
+static dav_error * dav_fs_save_locknull_list(apr_pool_t *p, const char *dirpath,
+ dav_buffer *pbuf)
+{
+ const char *pathname;
+ apr_file_t *file = NULL;
+ dav_error *err = NULL;
+ apr_size_t amt;
+ apr_status_t rv;
+
+ if (pbuf->buf == NULL)
+ return NULL;
+
+ dav_fs_ensure_state_dir(p, dirpath);
+ pathname = apr_pstrcat(p,
+ dirpath,
+ dirpath[strlen(dirpath) - 1] == '/' ? "" : "/",
+ DAV_FS_STATE_DIR "/" DAV_FS_LOCK_NULL_FILE,
+ NULL);
+
+ if (pbuf->cur_len == 0) {
+ /* delete the file if cur_len == 0 */
+ if ((rv = apr_file_remove(pathname, p)) != APR_SUCCESS) {
+ return dav_new_error(p, HTTP_INTERNAL_SERVER_ERROR, 0, rv,
+ apr_psprintf(p,
+ "Error removing %s", pathname));
+ }
+ return NULL;
+ }
+
+ if ((rv = apr_file_open(&file, pathname,
+ APR_WRITE | APR_CREATE | APR_TRUNCATE | APR_BINARY,
+ APR_OS_DEFAULT, p)) != APR_SUCCESS) {
+ return dav_new_error(p, HTTP_INTERNAL_SERVER_ERROR, 0, rv,
+ apr_psprintf(p,
+ "Error opening %s for writing",
+ pathname));
+ }
+
+ amt = pbuf->cur_len;
+ if ((rv = apr_file_write_full(file, pbuf->buf, amt, &amt)) != APR_SUCCESS
+ || amt != pbuf->cur_len) {
+ err = dav_new_error(p, HTTP_INTERNAL_SERVER_ERROR, 0, rv,
+ apr_psprintf(p,
+ "Error writing %" APR_SIZE_T_FMT
+ " bytes to %s",
+ pbuf->cur_len, pathname));
+ }
+
+ apr_file_close(file);
+ return err;
+}
+
+/*
+** dav_fs_remove_locknull_member: Removes filename from the locknull list
+** for directory path.
+*/
+static dav_error * dav_fs_remove_locknull_member(apr_pool_t *p,
+ const char *filename,
+ dav_buffer *pbuf)
+{
+ dav_error *err;
+ apr_size_t len;
+ apr_size_t scanlen;
+ char *scan;
+ const char *scanend;
+ char *dirpath = apr_pstrdup(p, filename);
+ char *fname = strrchr(dirpath, '/');
+ int dirty = 0;
+
+ if (fname != NULL)
+ *fname++ = '\0';
+ else
+ fname = dirpath;
+ len = strlen(fname) + 1;
+
+ if ((err = dav_fs_load_locknull_list(p, dirpath, pbuf)) != NULL) {
+ /* ### add a higher level description? */
+ return err;
+ }
+
+ for (scan = pbuf->buf, scanend = scan + pbuf->cur_len;
+ scan < scanend;
+ scan += scanlen) {
+ scanlen = strlen(scan) + 1;
+ if (len == scanlen && memcmp(fname, scan, scanlen) == 0) {
+ pbuf->cur_len -= scanlen;
+ memmove(scan, scan + scanlen, scanend - (scan + scanlen));
+ dirty = 1;
+ break;
+ }
+ }
+
+ if (dirty) {
+ if ((err = dav_fs_save_locknull_list(p, dirpath, pbuf)) != NULL) {
+ /* ### add a higher level description? */
+ return err;
+ }
+ }
+
+ return NULL;
+}
+
+/* Note: used by dav_fs_repos.c */
+dav_error * dav_fs_get_locknull_members(
+ const dav_resource *resource,
+ dav_buffer *pbuf)
+{
+ const char *dirpath;
+
+ /* ### should test this result value... */
+ (void) dav_fs_dir_file_name(resource, &dirpath, NULL);
+ return dav_fs_load_locknull_list(dav_fs_pool(resource), dirpath, pbuf);
+}
+
+/* ### fold into append_lock? */
+/* ### take an optional buf parameter? */
+static dav_error * dav_fs_add_locknull_state(
+ dav_lockdb *lockdb,
+ const dav_resource *resource)
+{
+ dav_buffer buf = { 0 };
+ apr_pool_t *p = lockdb->info->pool;
+ const char *dirpath;
+ const char *fname;
+ dav_error *err;
+
+ /* ### should test this result value... */
+ (void) dav_fs_dir_file_name(resource, &dirpath, &fname);
+
+ if ((err = dav_fs_load_locknull_list(p, dirpath, &buf)) != NULL) {
+ return dav_push_error(p, HTTP_INTERNAL_SERVER_ERROR, 0,
+ "Could not load .locknull file.", err);
+ }
+
+ dav_buffer_append(p, &buf, fname);
+ buf.cur_len++; /* we want the null-term here */
+
+ if ((err = dav_fs_save_locknull_list(p, dirpath, &buf)) != NULL) {
+ return dav_push_error(p, HTTP_INTERNAL_SERVER_ERROR, 0,
+ "Could not save .locknull file.", err);
+ }
+
+ return NULL;
+}
+
+/*
+** dav_fs_remove_locknull_state: Given a request, check to see if r->filename
+** is/was a lock-null resource. If so, return it to an existant state, i.e.
+** remove it from the list in the appropriate .DAV/locknull file.
+*/
+static dav_error * dav_fs_remove_locknull_state(
+ dav_lockdb *lockdb,
+ const dav_resource *resource)
+{
+ dav_buffer buf = { 0 };
+ dav_error *err;
+ apr_pool_t *p = lockdb->info->pool;
+ const char *pathname = dav_fs_pathname(resource);
+
+ if ((err = dav_fs_remove_locknull_member(p, pathname, &buf)) != NULL) {
+ /* ### add a higher-level description? */
+ return err;
+ }
+
+ return NULL;
+}
+
+static dav_error * dav_fs_create_lock(dav_lockdb *lockdb,
+ const dav_resource *resource,
+ dav_lock **lock)
+{
+ apr_datum_t key;
+
+ key = dav_fs_build_key(lockdb->info->pool, resource);
+
+ *lock = dav_fs_alloc_lock(lockdb,
+ key,
+ NULL);
+
+ (*lock)->is_locknull = !resource->exists;
+
+ return NULL;
+}
+
+static dav_error * dav_fs_get_locks(dav_lockdb *lockdb,
+ const dav_resource *resource,
+ int calltype,
+ dav_lock **locks)
+{
+ apr_pool_t *p = lockdb->info->pool;
+ apr_datum_t key;
+ dav_error *err;
+ dav_lock *lock = NULL;
+ dav_lock *newlock;
+ dav_lock_discovery *dp;
+ dav_lock_indirect *ip;
+
+#if DAV_DEBUG
+ if (calltype == DAV_GETLOCKS_COMPLETE) {
+ return dav_new_error(lockdb->info->pool,
+ HTTP_INTERNAL_SERVER_ERROR, 0, 0,
+ "INTERNAL DESIGN ERROR: DAV_GETLOCKS_COMPLETE "
+ "is not yet supported");
+ }
+#endif
+
+ key = dav_fs_build_key(p, resource);
+ if ((err = dav_fs_load_lock_record(lockdb, key, DAV_CREATE_LIST,
+ &dp, &ip)) != NULL) {
+ /* ### push a higher-level desc? */
+ return err;
+ }
+
+ /* copy all direct locks to the result list */
+ for (; dp != NULL; dp = dp->next) {
+ newlock = dav_fs_alloc_lock(lockdb, key, dp->locktoken);
+ newlock->is_locknull = !resource->exists;
+ newlock->scope = dp->f.scope;
+ newlock->type = dp->f.type;
+ newlock->depth = dp->f.depth;
+ newlock->timeout = dp->f.timeout;
+ newlock->owner = dp->owner;
+ newlock->auth_user = dp->auth_user;
+
+ /* hook into the result list */
+ newlock->next = lock;
+ lock = newlock;
+ }
+
+ /* copy all the indirect locks to the result list. resolve as needed. */
+ for (; ip != NULL; ip = ip->next) {
+ newlock = dav_fs_alloc_lock(lockdb, ip->key, ip->locktoken);
+ newlock->is_locknull = !resource->exists;
+
+ if (calltype == DAV_GETLOCKS_RESOLVED) {
+ if ((err = dav_fs_resolve(lockdb, ip, &dp, NULL, NULL)) != NULL) {
+ /* ### push a higher-level desc? */
+ return err;
+ }
+
+ newlock->scope = dp->f.scope;
+ newlock->type = dp->f.type;
+ newlock->depth = dp->f.depth;
+ newlock->timeout = dp->f.timeout;
+ newlock->owner = dp->owner;
+ newlock->auth_user = dp->auth_user;
+ }
+ else {
+ /* DAV_GETLOCKS_PARTIAL */
+ newlock->rectype = DAV_LOCKREC_INDIRECT_PARTIAL;
+ }
+
+ /* hook into the result list */
+ newlock->next = lock;
+ lock = newlock;
+ }
+
+ *locks = lock;
+ return NULL;
+}
+
+static dav_error * dav_fs_find_lock(dav_lockdb *lockdb,
+ const dav_resource *resource,
+ const dav_locktoken *locktoken,
+ int partial_ok,
+ dav_lock **lock)
+{
+ dav_error *err;
+ apr_datum_t key;
+ dav_lock_discovery *dp;
+ dav_lock_indirect *ip;
+
+ *lock = NULL;
+
+ key = dav_fs_build_key(lockdb->info->pool, resource);
+ if ((err = dav_fs_load_lock_record(lockdb, key, DAV_CREATE_LIST,
+ &dp, &ip)) != NULL) {
+ /* ### push a higher-level desc? */
+ return err;
+ }
+
+ for (; dp != NULL; dp = dp->next) {
+ if (!dav_compare_locktoken(locktoken, dp->locktoken)) {
+ *lock = dav_fs_alloc_lock(lockdb, key, locktoken);
+ (*lock)->is_locknull = !resource->exists;
+ (*lock)->scope = dp->f.scope;
+ (*lock)->type = dp->f.type;
+ (*lock)->depth = dp->f.depth;
+ (*lock)->timeout = dp->f.timeout;
+ (*lock)->owner = dp->owner;
+ (*lock)->auth_user = dp->auth_user;
+ return NULL;
+ }
+ }
+
+ for (; ip != NULL; ip = ip->next) {
+ if (!dav_compare_locktoken(locktoken, ip->locktoken)) {
+ *lock = dav_fs_alloc_lock(lockdb, ip->key, locktoken);
+ (*lock)->is_locknull = !resource->exists;
+
+ /* ### nobody uses the resolving right now! */
+ if (partial_ok) {
+ (*lock)->rectype = DAV_LOCKREC_INDIRECT_PARTIAL;
+ }
+ else {
+ (*lock)->rectype = DAV_LOCKREC_INDIRECT;
+ if ((err = dav_fs_resolve(lockdb, ip, &dp,
+ NULL, NULL)) != NULL) {
+ /* ### push a higher-level desc? */
+ return err;
+ }
+ (*lock)->scope = dp->f.scope;
+ (*lock)->type = dp->f.type;
+ (*lock)->depth = dp->f.depth;
+ (*lock)->timeout = dp->f.timeout;
+ (*lock)->owner = dp->owner;
+ (*lock)->auth_user = dp->auth_user;
+ }
+ return NULL;
+ }
+ }
+
+ return NULL;
+}
+
+static dav_error * dav_fs_has_locks(dav_lockdb *lockdb,
+ const dav_resource *resource,
+ int *locks_present)
+{
+ dav_error *err;
+ apr_datum_t key;
+
+ *locks_present = 0;
+
+ if ((err = dav_fs_really_open_lockdb(lockdb)) != NULL) {
+ /* ### insert a higher-level error description */
+ return err;
+ }
+
+ /*
+ ** If we opened readonly and the db wasn't there, then there are no
+ ** locks for this resource. Just exit.
+ */
+ if (lockdb->info->db == NULL)
+ return NULL;
+
+ key = dav_fs_build_key(lockdb->info->pool, resource);
+
+ *locks_present = dav_dbm_exists(lockdb->info->db, key);
+
+ return NULL;
+}
+
+static dav_error * dav_fs_append_locks(dav_lockdb *lockdb,
+ const dav_resource *resource,
+ int make_indirect,
+ const dav_lock *lock)
+{
+ apr_pool_t *p = lockdb->info->pool;
+ dav_error *err;
+ dav_lock_indirect *ip;
+ dav_lock_discovery *dp;
+ apr_datum_t key;
+
+ key = dav_fs_build_key(lockdb->info->pool, resource);
+ if ((err = dav_fs_load_lock_record(lockdb, key, 0, &dp, &ip)) != NULL) {
+ /* ### maybe add in a higher-level description */
+ return err;
+ }
+
+ /*
+ ** ### when we store the lock more directly, we need to update
+ ** ### lock->rectype and lock->is_locknull
+ */
+
+ if (make_indirect) {
+ for (; lock != NULL; lock = lock->next) {
+
+ /* ### this works for any <lock> rectype */
+ dav_lock_indirect *newi = apr_pcalloc(p, sizeof(*newi));
+
+ /* ### shut off the const warning for now */
+ newi->locktoken = (dav_locktoken *)lock->locktoken;
+ newi->timeout = lock->timeout;
+ newi->key = lock->info->key;
+ newi->next = ip;
+ ip = newi;
+ }
+ }
+ else {
+ for (; lock != NULL; lock = lock->next) {
+ /* create and link in the right kind of lock */
+
+ if (lock->rectype == DAV_LOCKREC_DIRECT) {
+ dav_lock_discovery *newd = apr_pcalloc(p, sizeof(*newd));
+
+ newd->f.scope = lock->scope;
+ newd->f.type = lock->type;
+ newd->f.depth = lock->depth;
+ newd->f.timeout = lock->timeout;
+ /* ### shut off the const warning for now */
+ newd->locktoken = (dav_locktoken *)lock->locktoken;
+ newd->owner = lock->owner;
+ newd->auth_user = lock->auth_user;
+ newd->next = dp;
+ dp = newd;
+ }
+ else {
+ /* DAV_LOCKREC_INDIRECT(_PARTIAL) */
+
+ dav_lock_indirect *newi = apr_pcalloc(p, sizeof(*newi));
+
+ /* ### shut off the const warning for now */
+ newi->locktoken = (dav_locktoken *)lock->locktoken;
+ newi->key = lock->info->key;
+ newi->next = ip;
+ ip = newi;
+ }
+ }
+ }
+
+ if ((err = dav_fs_save_lock_record(lockdb, key, dp, ip)) != NULL) {
+ /* ### maybe add a higher-level description */
+ return err;
+ }
+
+ /* we have a special list for recording locknull resources */
+ /* ### ack! this can add two copies to the locknull list */
+ if (!resource->exists
+ && (err = dav_fs_add_locknull_state(lockdb, resource)) != NULL) {
+ /* ### maybe add a higher-level description */
+ return err;
+ }
+
+ return NULL;
+}
+
+static dav_error * dav_fs_remove_lock(dav_lockdb *lockdb,
+ const dav_resource *resource,
+ const dav_locktoken *locktoken)
+{
+ dav_error *err;
+ dav_buffer buf = { 0 };
+ dav_lock_discovery *dh = NULL;
+ dav_lock_indirect *ih = NULL;
+ apr_datum_t key;
+
+ key = dav_fs_build_key(lockdb->info->pool, resource);
+
+ if (locktoken != NULL) {
+ dav_lock_discovery *dp;
+ dav_lock_discovery *dprev = NULL;
+ dav_lock_indirect *ip;
+ dav_lock_indirect *iprev = NULL;
+
+ if ((err = dav_fs_load_lock_record(lockdb, key, DAV_CREATE_LIST,
+ &dh, &ih)) != NULL) {
+ /* ### maybe add a higher-level description */
+ return err;
+ }
+
+ for (dp = dh; dp != NULL; dp = dp->next) {
+ if (dav_compare_locktoken(locktoken, dp->locktoken) == 0) {
+ if (dprev)
+ dprev->next = dp->next;
+ else
+ dh = dh->next;
+ }
+ dprev = dp;
+ }
+
+ for (ip = ih; ip != NULL; ip = ip->next) {
+ if (dav_compare_locktoken(locktoken, ip->locktoken) == 0) {
+ if (iprev)
+ iprev->next = ip->next;
+ else
+ ih = ih->next;
+ }
+ iprev = ip;
+ }
+
+ }
+
+ /* save the modified locks, or remove all locks (dh=ih=NULL). */
+ if ((err = dav_fs_save_lock_record(lockdb, key, dh, ih)) != NULL) {
+ /* ### maybe add a higher-level description */
+ return err;
+ }
+
+ /*
+ ** If this resource is a locknull resource AND no more locks exist,
+ ** then remove the locknull member.
+ **
+ ** Note: remove_locknull_state() attempts to convert a locknull member
+ ** to a real member. In this case, all locks are gone, so the
+ ** locknull resource returns to the null state (ie. doesn't exist),
+ ** so there is no need to update the lockdb (and it won't find
+ ** any because a precondition is that none exist).
+ */
+ if (!resource->exists && dh == NULL && ih == NULL
+ && (err = dav_fs_remove_locknull_member(lockdb->info->pool,
+ dav_fs_pathname(resource),
+ &buf)) != NULL) {
+ /* ### maybe add a higher-level description */
+ return err;
+ }
+
+ return NULL;
+}
+
+static int dav_fs_do_refresh(dav_lock_discovery *dp,
+ const dav_locktoken_list *ltl,
+ time_t new_time)
+{
+ int dirty = 0;
+
+ for (; ltl != NULL; ltl = ltl->next) {
+ if (dav_compare_locktoken(dp->locktoken, ltl->locktoken) == 0)
+ {
+ dp->f.timeout = new_time;
+ dirty = 1;
+ break;
+ }
+ }
+
+ return dirty;
+}
+
+static dav_error * dav_fs_refresh_locks(dav_lockdb *lockdb,
+ const dav_resource *resource,
+ const dav_locktoken_list *ltl,
+ time_t new_time,
+ dav_lock **locks)
+{
+ dav_error *err;
+ apr_datum_t key;
+ dav_lock_discovery *dp;
+ dav_lock_discovery *dp_scan;
+ dav_lock_indirect *ip;
+ int dirty = 0;
+ dav_lock *newlock;
+
+ *locks = NULL;
+
+ key = dav_fs_build_key(lockdb->info->pool, resource);
+ if ((err = dav_fs_load_lock_record(lockdb, key, DAV_CREATE_LIST,
+ &dp, &ip)) != NULL) {
+ /* ### maybe add in a higher-level description */
+ return err;
+ }
+
+ /* ### we should be refreshing direct AND (resolved) indirect locks! */
+
+ /* refresh all of the direct locks on this resource */
+ for (dp_scan = dp; dp_scan != NULL; dp_scan = dp_scan->next) {
+ if (dav_fs_do_refresh(dp_scan, ltl, new_time)) {
+ /* the lock was refreshed. return the lock. */
+ newlock = dav_fs_alloc_lock(lockdb, key, dp_scan->locktoken);
+ newlock->is_locknull = !resource->exists;
+ newlock->scope = dp_scan->f.scope;
+ newlock->type = dp_scan->f.type;
+ newlock->depth = dp_scan->f.depth;
+ newlock->timeout = dp_scan->f.timeout;
+ newlock->owner = dp_scan->owner;
+ newlock->auth_user = dp_scan->auth_user;
+
+ newlock->next = *locks;
+ *locks = newlock;
+
+ dirty = 1;
+ }
+ }
+
+ /* if we refreshed any locks, then save them back. */
+ if (dirty
+ && (err = dav_fs_save_lock_record(lockdb, key, dp, ip)) != NULL) {
+ /* ### maybe add in a higher-level description */
+ return err;
+ }
+
+ /* for each indirect lock, find its direct lock and refresh it. */
+ for (; ip != NULL; ip = ip->next) {
+ dav_lock_discovery *ref_dp;
+ dav_lock_indirect *ref_ip;
+
+ if ((err = dav_fs_resolve(lockdb, ip, &dp_scan,
+ &ref_dp, &ref_ip)) != NULL) {
+ /* ### push a higher-level desc? */
+ return err;
+ }
+ if (dav_fs_do_refresh(dp_scan, ltl, new_time)) {
+ /* the lock was refreshed. return the lock. */
+ newlock = dav_fs_alloc_lock(lockdb, ip->key, dp_scan->locktoken);
+ newlock->is_locknull = !resource->exists;
+ newlock->scope = dp_scan->f.scope;
+ newlock->type = dp_scan->f.type;
+ newlock->depth = dp_scan->f.depth;
+ newlock->timeout = dp_scan->f.timeout;
+ newlock->owner = dp_scan->owner;
+ newlock->auth_user = dp_scan->auth_user;
+
+ newlock->next = *locks;
+ *locks = newlock;
+
+ /* save the (resolved) direct lock back */
+ if ((err = dav_fs_save_lock_record(lockdb, ip->key, ref_dp,
+ ref_ip)) != NULL) {
+ /* ### push a higher-level desc? */
+ return err;
+ }
+ }
+ }
+
+ return NULL;
+}
+
+
+const dav_hooks_locks dav_hooks_locks_fs =
+{
+ dav_fs_get_supportedlock,
+ dav_fs_parse_locktoken,
+ dav_fs_format_locktoken,
+ dav_fs_compare_locktoken,
+ dav_fs_open_lockdb,
+ dav_fs_close_lockdb,
+ dav_fs_remove_locknull_state,
+ dav_fs_create_lock,
+ dav_fs_get_locks,
+ dav_fs_find_lock,
+ dav_fs_has_locks,
+ dav_fs_append_locks,
+ dav_fs_remove_lock,
+ dav_fs_refresh_locks,
+ NULL, /* lookup_resource */
+
+ NULL /* ctx */
+};
diff --git a/modules/dav/fs/mod_dav_fs.c b/modules/dav/fs/mod_dav_fs.c
new file mode 100644
index 0000000..addfd7e
--- /dev/null
+++ b/modules/dav/fs/mod_dav_fs.c
@@ -0,0 +1,108 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "httpd.h"
+#include "http_config.h"
+#include "apr_strings.h"
+
+#include "mod_dav.h"
+#include "repos.h"
+
+/* per-server configuration */
+typedef struct {
+ const char *lockdb_path;
+
+} dav_fs_server_conf;
+
+extern module AP_MODULE_DECLARE_DATA dav_fs_module;
+
+const char *dav_get_lockdb_path(const request_rec *r)
+{
+ dav_fs_server_conf *conf;
+
+ conf = ap_get_module_config(r->server->module_config, &dav_fs_module);
+ return conf->lockdb_path;
+}
+
+static void *dav_fs_create_server_config(apr_pool_t *p, server_rec *s)
+{
+ return apr_pcalloc(p, sizeof(dav_fs_server_conf));
+}
+
+static void *dav_fs_merge_server_config(apr_pool_t *p,
+ void *base, void *overrides)
+{
+ dav_fs_server_conf *parent = base;
+ dav_fs_server_conf *child = overrides;
+ dav_fs_server_conf *newconf;
+
+ newconf = apr_pcalloc(p, sizeof(*newconf));
+
+ newconf->lockdb_path =
+ child->lockdb_path ? child->lockdb_path : parent->lockdb_path;
+
+ return newconf;
+}
+
+/*
+ * Command handler for the DAVLockDB directive, which is TAKE1
+ */
+static const char *dav_fs_cmd_davlockdb(cmd_parms *cmd, void *config,
+ const char *arg1)
+{
+ dav_fs_server_conf *conf;
+ conf = ap_get_module_config(cmd->server->module_config,
+ &dav_fs_module);
+ conf->lockdb_path = ap_server_root_relative(cmd->pool, arg1);
+
+ if (!conf->lockdb_path) {
+ return apr_pstrcat(cmd->pool, "Invalid DAVLockDB path ",
+ arg1, NULL);
+ }
+
+ return NULL;
+}
+
+static const command_rec dav_fs_cmds[] =
+{
+ /* per server */
+ AP_INIT_TAKE1("DAVLockDB", dav_fs_cmd_davlockdb, NULL, RSRC_CONF,
+ "specify a lock database"),
+
+ { NULL }
+};
+
+static void register_hooks(apr_pool_t *p)
+{
+ dav_hook_gather_propsets(dav_fs_gather_propsets, NULL, NULL,
+ APR_HOOK_MIDDLE);
+ dav_hook_find_liveprop(dav_fs_find_liveprop, NULL, NULL, APR_HOOK_MIDDLE);
+ dav_hook_insert_all_liveprops(dav_fs_insert_all_liveprops, NULL, NULL,
+ APR_HOOK_MIDDLE);
+
+ dav_fs_register(p);
+}
+
+AP_DECLARE_MODULE(dav_fs) =
+{
+ STANDARD20_MODULE_STUFF,
+ NULL, /* dir config creater */
+ NULL, /* dir merger --- default is to override */
+ dav_fs_create_server_config, /* server config */
+ dav_fs_merge_server_config, /* merge server config */
+ dav_fs_cmds, /* command table */
+ register_hooks, /* register hooks */
+};
diff --git a/modules/dav/fs/mod_dav_fs.dep b/modules/dav/fs/mod_dav_fs.dep
new file mode 100644
index 0000000..0d67f04
--- /dev/null
+++ b/modules/dav/fs/mod_dav_fs.dep
@@ -0,0 +1,203 @@
+# Microsoft Developer Studio Generated Dependency File, included by mod_dav_fs.mak
+
+.\dbm.c : \
+ "..\..\..\include\ap_config.h"\
+ "..\..\..\include\ap_config_layout.h"\
+ "..\..\..\include\ap_hooks.h"\
+ "..\..\..\include\ap_mmn.h"\
+ "..\..\..\include\ap_regex.h"\
+ "..\..\..\include\ap_release.h"\
+ "..\..\..\include\apache_noprobes.h"\
+ "..\..\..\include\http_config.h"\
+ "..\..\..\include\http_log.h"\
+ "..\..\..\include\http_main.h"\
+ "..\..\..\include\httpd.h"\
+ "..\..\..\include\mod_dav.h"\
+ "..\..\..\include\os.h"\
+ "..\..\..\include\util_cfgtree.h"\
+ "..\..\..\include\util_filter.h"\
+ "..\..\..\include\util_xml.h"\
+ "..\..\..\srclib\apr-util\include\apr_buckets.h"\
+ "..\..\..\srclib\apr-util\include\apr_dbm.h"\
+ "..\..\..\srclib\apr-util\include\apr_hooks.h"\
+ "..\..\..\srclib\apr-util\include\apr_optional.h"\
+ "..\..\..\srclib\apr-util\include\apr_optional_hooks.h"\
+ "..\..\..\srclib\apr-util\include\apr_uri.h"\
+ "..\..\..\srclib\apr-util\include\apr_xlate.h"\
+ "..\..\..\srclib\apr-util\include\apr_xml.h"\
+ "..\..\..\srclib\apr-util\include\apu.h"\
+ "..\..\..\srclib\apr\include\apr.h"\
+ "..\..\..\srclib\apr\include\apr_allocator.h"\
+ "..\..\..\srclib\apr\include\apr_errno.h"\
+ "..\..\..\srclib\apr\include\apr_file_info.h"\
+ "..\..\..\srclib\apr\include\apr_file_io.h"\
+ "..\..\..\srclib\apr\include\apr_general.h"\
+ "..\..\..\srclib\apr\include\apr_hash.h"\
+ "..\..\..\srclib\apr\include\apr_inherit.h"\
+ "..\..\..\srclib\apr\include\apr_mmap.h"\
+ "..\..\..\srclib\apr\include\apr_network_io.h"\
+ "..\..\..\srclib\apr\include\apr_poll.h"\
+ "..\..\..\srclib\apr\include\apr_pools.h"\
+ "..\..\..\srclib\apr\include\apr_ring.h"\
+ "..\..\..\srclib\apr\include\apr_strings.h"\
+ "..\..\..\srclib\apr\include\apr_tables.h"\
+ "..\..\..\srclib\apr\include\apr_thread_mutex.h"\
+ "..\..\..\srclib\apr\include\apr_thread_proc.h"\
+ "..\..\..\srclib\apr\include\apr_time.h"\
+ "..\..\..\srclib\apr\include\apr_user.h"\
+ "..\..\..\srclib\apr\include\apr_want.h"\
+ ".\repos.h"\
+
+
+.\lock.c : \
+ "..\..\..\include\ap_config.h"\
+ "..\..\..\include\ap_config_layout.h"\
+ "..\..\..\include\ap_hooks.h"\
+ "..\..\..\include\ap_mmn.h"\
+ "..\..\..\include\ap_regex.h"\
+ "..\..\..\include\ap_release.h"\
+ "..\..\..\include\apache_noprobes.h"\
+ "..\..\..\include\http_config.h"\
+ "..\..\..\include\http_log.h"\
+ "..\..\..\include\httpd.h"\
+ "..\..\..\include\mod_dav.h"\
+ "..\..\..\include\os.h"\
+ "..\..\..\include\util_cfgtree.h"\
+ "..\..\..\include\util_filter.h"\
+ "..\..\..\include\util_xml.h"\
+ "..\..\..\srclib\apr-util\include\apr_buckets.h"\
+ "..\..\..\srclib\apr-util\include\apr_dbm.h"\
+ "..\..\..\srclib\apr-util\include\apr_hooks.h"\
+ "..\..\..\srclib\apr-util\include\apr_optional_hooks.h"\
+ "..\..\..\srclib\apr-util\include\apr_uri.h"\
+ "..\..\..\srclib\apr-util\include\apr_uuid.h"\
+ "..\..\..\srclib\apr-util\include\apr_xlate.h"\
+ "..\..\..\srclib\apr-util\include\apr_xml.h"\
+ "..\..\..\srclib\apr-util\include\apu.h"\
+ "..\..\..\srclib\apr\include\apr.h"\
+ "..\..\..\srclib\apr\include\apr_allocator.h"\
+ "..\..\..\srclib\apr\include\apr_errno.h"\
+ "..\..\..\srclib\apr\include\apr_file_info.h"\
+ "..\..\..\srclib\apr\include\apr_file_io.h"\
+ "..\..\..\srclib\apr\include\apr_general.h"\
+ "..\..\..\srclib\apr\include\apr_hash.h"\
+ "..\..\..\srclib\apr\include\apr_inherit.h"\
+ "..\..\..\srclib\apr\include\apr_mmap.h"\
+ "..\..\..\srclib\apr\include\apr_network_io.h"\
+ "..\..\..\srclib\apr\include\apr_poll.h"\
+ "..\..\..\srclib\apr\include\apr_pools.h"\
+ "..\..\..\srclib\apr\include\apr_ring.h"\
+ "..\..\..\srclib\apr\include\apr_strings.h"\
+ "..\..\..\srclib\apr\include\apr_tables.h"\
+ "..\..\..\srclib\apr\include\apr_thread_mutex.h"\
+ "..\..\..\srclib\apr\include\apr_thread_proc.h"\
+ "..\..\..\srclib\apr\include\apr_time.h"\
+ "..\..\..\srclib\apr\include\apr_user.h"\
+ "..\..\..\srclib\apr\include\apr_want.h"\
+ ".\repos.h"\
+
+
+.\mod_dav_fs.c : \
+ "..\..\..\include\ap_config.h"\
+ "..\..\..\include\ap_config_layout.h"\
+ "..\..\..\include\ap_hooks.h"\
+ "..\..\..\include\ap_mmn.h"\
+ "..\..\..\include\ap_regex.h"\
+ "..\..\..\include\ap_release.h"\
+ "..\..\..\include\apache_noprobes.h"\
+ "..\..\..\include\http_config.h"\
+ "..\..\..\include\httpd.h"\
+ "..\..\..\include\mod_dav.h"\
+ "..\..\..\include\os.h"\
+ "..\..\..\include\util_cfgtree.h"\
+ "..\..\..\include\util_filter.h"\
+ "..\..\..\include\util_xml.h"\
+ "..\..\..\srclib\apr-util\include\apr_buckets.h"\
+ "..\..\..\srclib\apr-util\include\apr_dbm.h"\
+ "..\..\..\srclib\apr-util\include\apr_hooks.h"\
+ "..\..\..\srclib\apr-util\include\apr_optional_hooks.h"\
+ "..\..\..\srclib\apr-util\include\apr_uri.h"\
+ "..\..\..\srclib\apr-util\include\apr_xlate.h"\
+ "..\..\..\srclib\apr-util\include\apr_xml.h"\
+ "..\..\..\srclib\apr-util\include\apu.h"\
+ "..\..\..\srclib\apr\include\apr.h"\
+ "..\..\..\srclib\apr\include\apr_allocator.h"\
+ "..\..\..\srclib\apr\include\apr_errno.h"\
+ "..\..\..\srclib\apr\include\apr_file_info.h"\
+ "..\..\..\srclib\apr\include\apr_file_io.h"\
+ "..\..\..\srclib\apr\include\apr_general.h"\
+ "..\..\..\srclib\apr\include\apr_hash.h"\
+ "..\..\..\srclib\apr\include\apr_inherit.h"\
+ "..\..\..\srclib\apr\include\apr_mmap.h"\
+ "..\..\..\srclib\apr\include\apr_network_io.h"\
+ "..\..\..\srclib\apr\include\apr_poll.h"\
+ "..\..\..\srclib\apr\include\apr_pools.h"\
+ "..\..\..\srclib\apr\include\apr_ring.h"\
+ "..\..\..\srclib\apr\include\apr_strings.h"\
+ "..\..\..\srclib\apr\include\apr_tables.h"\
+ "..\..\..\srclib\apr\include\apr_thread_mutex.h"\
+ "..\..\..\srclib\apr\include\apr_thread_proc.h"\
+ "..\..\..\srclib\apr\include\apr_time.h"\
+ "..\..\..\srclib\apr\include\apr_user.h"\
+ "..\..\..\srclib\apr\include\apr_want.h"\
+ ".\repos.h"\
+
+
+.\repos.c : \
+ "..\..\..\include\ap_config.h"\
+ "..\..\..\include\ap_config_layout.h"\
+ "..\..\..\include\ap_hooks.h"\
+ "..\..\..\include\ap_mmn.h"\
+ "..\..\..\include\ap_regex.h"\
+ "..\..\..\include\ap_release.h"\
+ "..\..\..\include\apache_noprobes.h"\
+ "..\..\..\include\http_config.h"\
+ "..\..\..\include\http_log.h"\
+ "..\..\..\include\http_protocol.h"\
+ "..\..\..\include\http_request.h"\
+ "..\..\..\include\httpd.h"\
+ "..\..\..\include\mod_dav.h"\
+ "..\..\..\include\os.h"\
+ "..\..\..\include\util_cfgtree.h"\
+ "..\..\..\include\util_filter.h"\
+ "..\..\..\include\util_xml.h"\
+ "..\..\..\srclib\apr-util\include\apr_buckets.h"\
+ "..\..\..\srclib\apr-util\include\apr_dbm.h"\
+ "..\..\..\srclib\apr-util\include\apr_hooks.h"\
+ "..\..\..\srclib\apr-util\include\apr_optional.h"\
+ "..\..\..\srclib\apr-util\include\apr_optional_hooks.h"\
+ "..\..\..\srclib\apr-util\include\apr_uri.h"\
+ "..\..\..\srclib\apr-util\include\apr_xlate.h"\
+ "..\..\..\srclib\apr-util\include\apr_xml.h"\
+ "..\..\..\srclib\apr-util\include\apu.h"\
+ "..\..\..\srclib\apr\include\apr.h"\
+ "..\..\..\srclib\apr\include\apr_allocator.h"\
+ "..\..\..\srclib\apr\include\apr_dso.h"\
+ "..\..\..\srclib\apr\include\apr_errno.h"\
+ "..\..\..\srclib\apr\include\apr_file_info.h"\
+ "..\..\..\srclib\apr\include\apr_file_io.h"\
+ "..\..\..\srclib\apr\include\apr_general.h"\
+ "..\..\..\srclib\apr\include\apr_global_mutex.h"\
+ "..\..\..\srclib\apr\include\apr_hash.h"\
+ "..\..\..\srclib\apr\include\apr_inherit.h"\
+ "..\..\..\srclib\apr\include\apr_mmap.h"\
+ "..\..\..\srclib\apr\include\apr_network_io.h"\
+ "..\..\..\srclib\apr\include\apr_poll.h"\
+ "..\..\..\srclib\apr\include\apr_pools.h"\
+ "..\..\..\srclib\apr\include\apr_portable.h"\
+ "..\..\..\srclib\apr\include\apr_proc_mutex.h"\
+ "..\..\..\srclib\apr\include\apr_ring.h"\
+ "..\..\..\srclib\apr\include\apr_shm.h"\
+ "..\..\..\srclib\apr\include\apr_strings.h"\
+ "..\..\..\srclib\apr\include\apr_tables.h"\
+ "..\..\..\srclib\apr\include\apr_thread_mutex.h"\
+ "..\..\..\srclib\apr\include\apr_thread_proc.h"\
+ "..\..\..\srclib\apr\include\apr_time.h"\
+ "..\..\..\srclib\apr\include\apr_user.h"\
+ "..\..\..\srclib\apr\include\apr_want.h"\
+ ".\repos.h"\
+
+
+..\..\..\build\win32\httpd.rc : \
+ "..\..\..\include\ap_release.h"\
+
diff --git a/modules/dav/fs/mod_dav_fs.dsp b/modules/dav/fs/mod_dav_fs.dsp
new file mode 100644
index 0000000..fbfc1e4
--- /dev/null
+++ b/modules/dav/fs/mod_dav_fs.dsp
@@ -0,0 +1,135 @@
+# Microsoft Developer Studio Project File - Name="mod_dav_fs" - Package Owner=<4>
+# Microsoft Developer Studio Generated Build File, Format Version 6.00
+# ** DO NOT EDIT **
+
+# TARGTYPE "Win32 (x86) Dynamic-Link Library" 0x0102
+
+CFG=mod_dav_fs - Win32 Release
+!MESSAGE This is not a valid makefile. To build this project using NMAKE,
+!MESSAGE use the Export Makefile command and run
+!MESSAGE
+!MESSAGE NMAKE /f "mod_dav_fs.mak".
+!MESSAGE
+!MESSAGE You can specify a configuration when running NMAKE
+!MESSAGE by defining the macro CFG on the command line. For example:
+!MESSAGE
+!MESSAGE NMAKE /f "mod_dav_fs.mak" CFG="mod_dav_fs - Win32 Release"
+!MESSAGE
+!MESSAGE Possible choices for configuration are:
+!MESSAGE
+!MESSAGE "mod_dav_fs - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE "mod_dav_fs - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE
+
+# Begin Project
+# PROP AllowPerConfigDependencies 0
+# PROP Scc_ProjName ""
+# PROP Scc_LocalPath ""
+CPP=cl.exe
+MTL=midl.exe
+RSC=rc.exe
+
+!IF "$(CFG)" == "mod_dav_fs - Win32 Release"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 0
+# PROP BASE Output_Dir "Release"
+# PROP BASE Intermediate_Dir "Release"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 0
+# PROP Output_Dir "Release"
+# PROP Intermediate_Dir "Release"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /MD /W3 /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /FD /c
+# ADD CPP /nologo /MD /W3 /O2 /Oy- /Zi /I "../../../include" /I "../../../srclib/apr/include" /I "../../../srclib/apr-util/include" /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /Fd"Release\mod_dav_fs_src" /FD /c
+# ADD BASE MTL /nologo /D "NDEBUG" /win32
+# ADD MTL /nologo /D "NDEBUG" /mktyplib203 /win32
+# ADD BASE RSC /l 0x409 /d "NDEBUG"
+# ADD RSC /l 0x409 /fo"Release/mod_dav_fs.res" /i "../../../include" /i "../../../srclib/apr/include" /d "NDEBUG" /d BIN_NAME="mod_dav_fs.so" /d LONG_NAME="dav_fs_module for Apache"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib ws2_32.lib mswsock.lib /nologo /subsystem:windows /dll /out:".\Release\mod_dav_fs.so" /base:@..\..\..\os\win32\BaseAddr.ref,mod_dav_fs.so
+# ADD LINK32 kernel32.lib ws2_32.lib mswsock.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Release\mod_dav_fs.so" /base:@..\..\..\os\win32\BaseAddr.ref,mod_dav_fs.so /opt:ref
+# Begin Special Build Tool
+TargetPath=.\Release\mod_dav_fs.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+PostBuild_Cmds=if exist $(TargetPath).manifest mt.exe -manifest $(TargetPath).manifest -outputresource:$(TargetPath);2
+# End Special Build Tool
+
+!ELSEIF "$(CFG)" == "mod_dav_fs - Win32 Debug"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 1
+# PROP BASE Output_Dir "Debug"
+# PROP BASE Intermediate_Dir "Debug"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 1
+# PROP Output_Dir "Debug"
+# PROP Intermediate_Dir "Debug"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /MDd /W3 /EHsc /Zi /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /FD /c
+# ADD CPP /nologo /MDd /W3 /EHsc /Zi /Od /I "../../../include" /I "../../../srclib/apr/include" /I "../../../srclib/apr-util/include" /D "_DEBUG" /D "WIN32" /D "_WINDOWS" /Fd"Debug\mod_dav_fs_src" /FD /c
+# ADD BASE MTL /nologo /D "_DEBUG" /win32
+# ADD MTL /nologo /D "_DEBUG" /mktyplib203 /win32
+# ADD BASE RSC /l 0x409 /d "_DEBUG"
+# ADD RSC /l 0x409 /fo"Debug/mod_dav_fs.res" /i "../../../include" /i "../../../srclib/apr/include" /d "_DEBUG" /d BIN_NAME="mod_dav_fs.so" /d LONG_NAME="dav_fs_module for Apache"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib ws2_32.lib mswsock.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Debug\mod_dav_fs.so" /base:@..\..\..\os\win32\BaseAddr.ref,mod_dav_fs.so
+# ADD LINK32 kernel32.lib ws2_32.lib mswsock.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Debug\mod_dav_fs.so" /base:@..\..\..\os\win32\BaseAddr.ref,mod_dav_fs.so
+# Begin Special Build Tool
+TargetPath=.\Debug\mod_dav_fs.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+PostBuild_Cmds=if exist $(TargetPath).manifest mt.exe -manifest $(TargetPath).manifest -outputresource:$(TargetPath);2
+# End Special Build Tool
+
+!ENDIF
+
+# Begin Target
+
+# Name "mod_dav_fs - Win32 Release"
+# Name "mod_dav_fs - Win32 Debug"
+# Begin Group "Source Files"
+
+# PROP Default_Filter "cpp;c;cxx;rc;def;r;odl;hpj;bat;for;f90"
+# Begin Source File
+
+SOURCE=.\dbm.c
+# End Source File
+# Begin Source File
+
+SOURCE=.\lock.c
+# End Source File
+# Begin Source File
+
+SOURCE=.\mod_dav_fs.c
+# End Source File
+# Begin Source File
+
+SOURCE=.\repos.c
+# End Source File
+# End Group
+# Begin Group "Header Files"
+
+# PROP Default_Filter "h;hpp;hxx;hm;inl;fi;fd"
+# Begin Source File
+
+SOURCE=.\repos.h
+# End Source File
+# End Group
+# Begin Source File
+
+SOURCE=..\..\..\build\win32\httpd.rc
+# End Source File
+# End Target
+# End Project
diff --git a/modules/dav/fs/mod_dav_fs.mak b/modules/dav/fs/mod_dav_fs.mak
new file mode 100644
index 0000000..5baff67
--- /dev/null
+++ b/modules/dav/fs/mod_dav_fs.mak
@@ -0,0 +1,407 @@
+# Microsoft Developer Studio Generated NMAKE File, Based on mod_dav_fs.dsp
+!IF "$(CFG)" == ""
+CFG=mod_dav_fs - Win32 Release
+!MESSAGE No configuration specified. Defaulting to mod_dav_fs - Win32 Release.
+!ENDIF
+
+!IF "$(CFG)" != "mod_dav_fs - Win32 Release" && "$(CFG)" != "mod_dav_fs - Win32 Debug"
+!MESSAGE Invalid configuration "$(CFG)" specified.
+!MESSAGE You can specify a configuration when running NMAKE
+!MESSAGE by defining the macro CFG on the command line. For example:
+!MESSAGE
+!MESSAGE NMAKE /f "mod_dav_fs.mak" CFG="mod_dav_fs - Win32 Release"
+!MESSAGE
+!MESSAGE Possible choices for configuration are:
+!MESSAGE
+!MESSAGE "mod_dav_fs - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE "mod_dav_fs - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE
+!ERROR An invalid configuration is specified.
+!ENDIF
+
+!IF "$(OS)" == "Windows_NT"
+NULL=
+!ELSE
+NULL=nul
+!ENDIF
+
+!IF "$(CFG)" == "mod_dav_fs - Win32 Release"
+
+OUTDIR=.\Release
+INTDIR=.\Release
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+# Begin Custom Macros
+OutDir=.\Release
+# End Custom Macros
+
+!IF "$(RECURSE)" == "0"
+
+ALL : "$(OUTDIR)\mod_dav_fs.so" "$(DS_POSTBUILD_DEP)"
+
+!ELSE
+
+ALL : "mod_dav - Win32 Release" "libhttpd - Win32 Release" "libaprutil - Win32 Release" "libapr - Win32 Release" "$(OUTDIR)\mod_dav_fs.so" "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+!IF "$(RECURSE)" == "1"
+CLEAN :"libapr - Win32 ReleaseCLEAN" "libaprutil - Win32 ReleaseCLEAN" "libhttpd - Win32 ReleaseCLEAN" "mod_dav - Win32 ReleaseCLEAN"
+!ELSE
+CLEAN :
+!ENDIF
+ -@erase "$(INTDIR)\dbm.obj"
+ -@erase "$(INTDIR)\lock.obj"
+ -@erase "$(INTDIR)\mod_dav_fs.obj"
+ -@erase "$(INTDIR)\mod_dav_fs.res"
+ -@erase "$(INTDIR)\mod_dav_fs_src.idb"
+ -@erase "$(INTDIR)\mod_dav_fs_src.pdb"
+ -@erase "$(INTDIR)\repos.obj"
+ -@erase "$(OUTDIR)\mod_dav_fs.exp"
+ -@erase "$(OUTDIR)\mod_dav_fs.lib"
+ -@erase "$(OUTDIR)\mod_dav_fs.pdb"
+ -@erase "$(OUTDIR)\mod_dav_fs.so"
+
+"$(OUTDIR)" :
+ if not exist "$(OUTDIR)/$(NULL)" mkdir "$(OUTDIR)"
+
+CPP=cl.exe
+CPP_PROJ=/nologo /MD /W3 /Zi /O2 /Oy- /I "../../../include" /I "../../../srclib/apr/include" /I "../../../srclib/apr-util/include" /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /Fo"$(INTDIR)\\" /Fd"$(INTDIR)\mod_dav_fs_src" /FD /c
+
+.c{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.c{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+MTL=midl.exe
+MTL_PROJ=/nologo /D "NDEBUG" /mktyplib203 /win32
+RSC=rc.exe
+RSC_PROJ=/l 0x409 /fo"$(INTDIR)\mod_dav_fs.res" /i "../../../include" /i "../../../srclib/apr/include" /d "NDEBUG" /d BIN_NAME="mod_dav_fs.so" /d LONG_NAME="dav_fs_module for Apache"
+BSC32=bscmake.exe
+BSC32_FLAGS=/nologo /o"$(OUTDIR)\mod_dav_fs.bsc"
+BSC32_SBRS= \
+
+LINK32=link.exe
+LINK32_FLAGS=kernel32.lib ws2_32.lib mswsock.lib /nologo /subsystem:windows /dll /incremental:no /pdb:"$(OUTDIR)\mod_dav_fs.pdb" /debug /out:"$(OUTDIR)\mod_dav_fs.so" /implib:"$(OUTDIR)\mod_dav_fs.lib" /base:@..\..\..\os\win32\BaseAddr.ref,mod_dav_fs.so /opt:ref
+LINK32_OBJS= \
+ "$(INTDIR)\dbm.obj" \
+ "$(INTDIR)\lock.obj" \
+ "$(INTDIR)\mod_dav_fs.obj" \
+ "$(INTDIR)\repos.obj" \
+ "$(INTDIR)\mod_dav_fs.res" \
+ "..\..\..\srclib\apr\Release\libapr-1.lib" \
+ "..\..\..\srclib\apr-util\Release\libaprutil-1.lib" \
+ "..\..\..\Release\libhttpd.lib" \
+ "..\main\Release\mod_dav.lib"
+
+"$(OUTDIR)\mod_dav_fs.so" : "$(OUTDIR)" $(DEF_FILE) $(LINK32_OBJS)
+ $(LINK32) @<<
+ $(LINK32_FLAGS) $(LINK32_OBJS)
+<<
+
+TargetPath=.\Release\mod_dav_fs.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+
+# Begin Custom Macros
+OutDir=.\Release
+# End Custom Macros
+
+"$(DS_POSTBUILD_DEP)" : "$(OUTDIR)\mod_dav_fs.so"
+ if exist .\Release\mod_dav_fs.so.manifest mt.exe -manifest .\Release\mod_dav_fs.so.manifest -outputresource:.\Release\mod_dav_fs.so;2
+ echo Helper for Post-build step > "$(DS_POSTBUILD_DEP)"
+
+!ELSEIF "$(CFG)" == "mod_dav_fs - Win32 Debug"
+
+OUTDIR=.\Debug
+INTDIR=.\Debug
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+# Begin Custom Macros
+OutDir=.\Debug
+# End Custom Macros
+
+!IF "$(RECURSE)" == "0"
+
+ALL : "$(OUTDIR)\mod_dav_fs.so" "$(DS_POSTBUILD_DEP)"
+
+!ELSE
+
+ALL : "mod_dav - Win32 Debug" "libhttpd - Win32 Debug" "libaprutil - Win32 Debug" "libapr - Win32 Debug" "$(OUTDIR)\mod_dav_fs.so" "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+!IF "$(RECURSE)" == "1"
+CLEAN :"libapr - Win32 DebugCLEAN" "libaprutil - Win32 DebugCLEAN" "libhttpd - Win32 DebugCLEAN" "mod_dav - Win32 DebugCLEAN"
+!ELSE
+CLEAN :
+!ENDIF
+ -@erase "$(INTDIR)\dbm.obj"
+ -@erase "$(INTDIR)\lock.obj"
+ -@erase "$(INTDIR)\mod_dav_fs.obj"
+ -@erase "$(INTDIR)\mod_dav_fs.res"
+ -@erase "$(INTDIR)\mod_dav_fs_src.idb"
+ -@erase "$(INTDIR)\mod_dav_fs_src.pdb"
+ -@erase "$(INTDIR)\repos.obj"
+ -@erase "$(OUTDIR)\mod_dav_fs.exp"
+ -@erase "$(OUTDIR)\mod_dav_fs.lib"
+ -@erase "$(OUTDIR)\mod_dav_fs.pdb"
+ -@erase "$(OUTDIR)\mod_dav_fs.so"
+
+"$(OUTDIR)" :
+ if not exist "$(OUTDIR)/$(NULL)" mkdir "$(OUTDIR)"
+
+CPP=cl.exe
+CPP_PROJ=/nologo /MDd /W3 /Zi /Od /I "../../../include" /I "../../../srclib/apr/include" /I "../../../srclib/apr-util/include" /D "_DEBUG" /D "WIN32" /D "_WINDOWS" /Fo"$(INTDIR)\\" /Fd"$(INTDIR)\mod_dav_fs_src" /FD /EHsc /c
+
+.c{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.c{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+MTL=midl.exe
+MTL_PROJ=/nologo /D "_DEBUG" /mktyplib203 /win32
+RSC=rc.exe
+RSC_PROJ=/l 0x409 /fo"$(INTDIR)\mod_dav_fs.res" /i "../../../include" /i "../../../srclib/apr/include" /d "_DEBUG" /d BIN_NAME="mod_dav_fs.so" /d LONG_NAME="dav_fs_module for Apache"
+BSC32=bscmake.exe
+BSC32_FLAGS=/nologo /o"$(OUTDIR)\mod_dav_fs.bsc"
+BSC32_SBRS= \
+
+LINK32=link.exe
+LINK32_FLAGS=kernel32.lib ws2_32.lib mswsock.lib /nologo /subsystem:windows /dll /incremental:no /pdb:"$(OUTDIR)\mod_dav_fs.pdb" /debug /out:"$(OUTDIR)\mod_dav_fs.so" /implib:"$(OUTDIR)\mod_dav_fs.lib" /base:@..\..\..\os\win32\BaseAddr.ref,mod_dav_fs.so
+LINK32_OBJS= \
+ "$(INTDIR)\dbm.obj" \
+ "$(INTDIR)\lock.obj" \
+ "$(INTDIR)\mod_dav_fs.obj" \
+ "$(INTDIR)\repos.obj" \
+ "$(INTDIR)\mod_dav_fs.res" \
+ "..\..\..\srclib\apr\Debug\libapr-1.lib" \
+ "..\..\..\srclib\apr-util\Debug\libaprutil-1.lib" \
+ "..\..\..\Debug\libhttpd.lib" \
+ "..\main\Debug\mod_dav.lib"
+
+"$(OUTDIR)\mod_dav_fs.so" : "$(OUTDIR)" $(DEF_FILE) $(LINK32_OBJS)
+ $(LINK32) @<<
+ $(LINK32_FLAGS) $(LINK32_OBJS)
+<<
+
+TargetPath=.\Debug\mod_dav_fs.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+
+# Begin Custom Macros
+OutDir=.\Debug
+# End Custom Macros
+
+"$(DS_POSTBUILD_DEP)" : "$(OUTDIR)\mod_dav_fs.so"
+ if exist .\Debug\mod_dav_fs.so.manifest mt.exe -manifest .\Debug\mod_dav_fs.so.manifest -outputresource:.\Debug\mod_dav_fs.so;2
+ echo Helper for Post-build step > "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+
+!IF "$(NO_EXTERNAL_DEPS)" != "1"
+!IF EXISTS("mod_dav_fs.dep")
+!INCLUDE "mod_dav_fs.dep"
+!ELSE
+!MESSAGE Warning: cannot find "mod_dav_fs.dep"
+!ENDIF
+!ENDIF
+
+
+!IF "$(CFG)" == "mod_dav_fs - Win32 Release" || "$(CFG)" == "mod_dav_fs - Win32 Debug"
+SOURCE=.\dbm.c
+
+"$(INTDIR)\dbm.obj" : $(SOURCE) "$(INTDIR)"
+
+
+SOURCE=.\lock.c
+
+"$(INTDIR)\lock.obj" : $(SOURCE) "$(INTDIR)"
+
+
+SOURCE=.\mod_dav_fs.c
+
+"$(INTDIR)\mod_dav_fs.obj" : $(SOURCE) "$(INTDIR)"
+
+
+SOURCE=.\repos.c
+
+"$(INTDIR)\repos.obj" : $(SOURCE) "$(INTDIR)"
+
+
+!IF "$(CFG)" == "mod_dav_fs - Win32 Release"
+
+"libapr - Win32 Release" :
+ cd ".\..\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Release"
+ cd "..\..\modules\dav\fs"
+
+"libapr - Win32 ReleaseCLEAN" :
+ cd ".\..\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Release" RECURSE=1 CLEAN
+ cd "..\..\modules\dav\fs"
+
+!ELSEIF "$(CFG)" == "mod_dav_fs - Win32 Debug"
+
+"libapr - Win32 Debug" :
+ cd ".\..\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Debug"
+ cd "..\..\modules\dav\fs"
+
+"libapr - Win32 DebugCLEAN" :
+ cd ".\..\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Debug" RECURSE=1 CLEAN
+ cd "..\..\modules\dav\fs"
+
+!ENDIF
+
+!IF "$(CFG)" == "mod_dav_fs - Win32 Release"
+
+"libaprutil - Win32 Release" :
+ cd ".\..\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Release"
+ cd "..\..\modules\dav\fs"
+
+"libaprutil - Win32 ReleaseCLEAN" :
+ cd ".\..\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Release" RECURSE=1 CLEAN
+ cd "..\..\modules\dav\fs"
+
+!ELSEIF "$(CFG)" == "mod_dav_fs - Win32 Debug"
+
+"libaprutil - Win32 Debug" :
+ cd ".\..\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Debug"
+ cd "..\..\modules\dav\fs"
+
+"libaprutil - Win32 DebugCLEAN" :
+ cd ".\..\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Debug" RECURSE=1 CLEAN
+ cd "..\..\modules\dav\fs"
+
+!ENDIF
+
+!IF "$(CFG)" == "mod_dav_fs - Win32 Release"
+
+"libhttpd - Win32 Release" :
+ cd ".\..\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Release"
+ cd ".\modules\dav\fs"
+
+"libhttpd - Win32 ReleaseCLEAN" :
+ cd ".\..\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Release" RECURSE=1 CLEAN
+ cd ".\modules\dav\fs"
+
+!ELSEIF "$(CFG)" == "mod_dav_fs - Win32 Debug"
+
+"libhttpd - Win32 Debug" :
+ cd ".\..\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Debug"
+ cd ".\modules\dav\fs"
+
+"libhttpd - Win32 DebugCLEAN" :
+ cd ".\..\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Debug" RECURSE=1 CLEAN
+ cd ".\modules\dav\fs"
+
+!ENDIF
+
+!IF "$(CFG)" == "mod_dav_fs - Win32 Release"
+
+"mod_dav - Win32 Release" :
+ cd ".\..\main"
+ $(MAKE) /$(MAKEFLAGS) /F ".\mod_dav.mak" CFG="mod_dav - Win32 Release"
+ cd "..\fs"
+
+"mod_dav - Win32 ReleaseCLEAN" :
+ cd ".\..\main"
+ $(MAKE) /$(MAKEFLAGS) /F ".\mod_dav.mak" CFG="mod_dav - Win32 Release" RECURSE=1 CLEAN
+ cd "..\fs"
+
+!ELSEIF "$(CFG)" == "mod_dav_fs - Win32 Debug"
+
+"mod_dav - Win32 Debug" :
+ cd ".\..\main"
+ $(MAKE) /$(MAKEFLAGS) /F ".\mod_dav.mak" CFG="mod_dav - Win32 Debug"
+ cd "..\fs"
+
+"mod_dav - Win32 DebugCLEAN" :
+ cd ".\..\main"
+ $(MAKE) /$(MAKEFLAGS) /F ".\mod_dav.mak" CFG="mod_dav - Win32 Debug" RECURSE=1 CLEAN
+ cd "..\fs"
+
+!ENDIF
+
+SOURCE=..\..\..\build\win32\httpd.rc
+
+!IF "$(CFG)" == "mod_dav_fs - Win32 Release"
+
+
+"$(INTDIR)\mod_dav_fs.res" : $(SOURCE) "$(INTDIR)"
+ $(RSC) /l 0x409 /fo"$(INTDIR)\mod_dav_fs.res" /i "../../../include" /i "../../../srclib/apr/include" /i "../../../build\win32" /d "NDEBUG" /d BIN_NAME="mod_dav_fs.so" /d LONG_NAME="dav_fs_module for Apache" $(SOURCE)
+
+
+!ELSEIF "$(CFG)" == "mod_dav_fs - Win32 Debug"
+
+
+"$(INTDIR)\mod_dav_fs.res" : $(SOURCE) "$(INTDIR)"
+ $(RSC) /l 0x409 /fo"$(INTDIR)\mod_dav_fs.res" /i "../../../include" /i "../../../srclib/apr/include" /i "../../../build\win32" /d "_DEBUG" /d BIN_NAME="mod_dav_fs.so" /d LONG_NAME="dav_fs_module for Apache" $(SOURCE)
+
+
+!ENDIF
+
+
+!ENDIF
+
diff --git a/modules/dav/fs/repos.c b/modules/dav/fs/repos.c
new file mode 100644
index 0000000..6a5ff76
--- /dev/null
+++ b/modules/dav/fs/repos.c
@@ -0,0 +1,2254 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/*
+** DAV filesystem-based repository provider
+*/
+
+#include "apr.h"
+#include "apr_file_io.h"
+#include "apr_strings.h"
+#include "apr_buckets.h"
+
+#if APR_HAVE_UNISTD_H
+#include <unistd.h> /* for getpid() */
+#endif
+
+#include "httpd.h"
+#include "http_log.h"
+#include "http_protocol.h" /* for ap_set_* (in dav_fs_set_headers) */
+#include "http_request.h" /* for ap_update_mtime() */
+
+#include "mod_dav.h"
+#include "repos.h"
+
+
+/* to assist in debugging mod_dav's GET handling */
+#define DEBUG_GET_HANDLER 0
+
+#define DAV_FS_COPY_BLOCKSIZE 16384 /* copy 16k at a time */
+
+/* context needed to identify a resource */
+struct dav_resource_private {
+ apr_pool_t *pool; /* memory storage pool associated with request */
+ const char *pathname; /* full pathname to resource */
+ apr_finfo_t finfo; /* filesystem info */
+ request_rec *r;
+};
+
+/* private context for doing a filesystem walk */
+typedef struct {
+ /* the input walk parameters */
+ const dav_walk_params *params;
+
+ /* reused as we walk */
+ dav_walk_resource wres;
+
+ dav_resource res1;
+ dav_resource_private info1;
+ dav_buffer path1;
+ dav_buffer uri_buf;
+
+ /* MOVE/COPY need a secondary path */
+ dav_resource res2;
+ dav_resource_private info2;
+ dav_buffer path2;
+
+ dav_buffer locknull_buf;
+
+} dav_fs_walker_context;
+
+typedef struct {
+ int is_move; /* is this a MOVE? */
+ dav_buffer work_buf; /* handy buffer for copymove_file() */
+
+ /* CALLBACK: this is a secondary resource managed specially for us */
+ const dav_resource *res_dst;
+
+ /* copied from dav_walk_params (they are invariant across the walk) */
+ const dav_resource *root;
+ apr_pool_t *pool;
+
+} dav_fs_copymove_walk_ctx;
+
+/* an internal WALKTYPE to walk hidden files (the .DAV directory) */
+#define DAV_WALKTYPE_HIDDEN 0x4000
+
+/* an internal WALKTYPE to call collections (again) after their contents */
+#define DAV_WALKTYPE_POSTFIX 0x8000
+
+#define DAV_CALLTYPE_POSTFIX 1000 /* a private call type */
+
+
+/* pull this in from the other source file */
+extern const dav_hooks_locks dav_hooks_locks_fs;
+
+/* forward-declare the hook structures */
+static const dav_hooks_repository dav_hooks_repository_fs;
+static const dav_hooks_liveprop dav_hooks_liveprop_fs;
+
+/*
+** The namespace URIs that we use. This list and the enumeration must
+** stay in sync.
+*/
+static const char * const dav_fs_namespace_uris[] =
+{
+ "DAV:",
+ "http://apache.org/dav/props/",
+
+ NULL /* sentinel */
+};
+enum {
+ DAV_FS_URI_DAV, /* the DAV: namespace URI */
+ DAV_FS_URI_MYPROPS /* the namespace URI for our custom props */
+};
+
+/*
+** Does this platform support an executable flag?
+**
+** ### need a way to portably abstract this query
+**
+** DAV_FINFO_MASK gives the appropriate mask to use for the stat call
+** used to get file attributes.
+*/
+#ifndef WIN32
+#define DAV_FS_HAS_EXECUTABLE
+#define DAV_FINFO_MASK (APR_FINFO_LINK | APR_FINFO_TYPE | APR_FINFO_INODE | \
+ APR_FINFO_SIZE | APR_FINFO_CTIME | APR_FINFO_MTIME | \
+ APR_FINFO_PROT)
+#else
+/* as above, but without APR_FINFO_PROT */
+#define DAV_FINFO_MASK (APR_FINFO_LINK | APR_FINFO_TYPE | APR_FINFO_INODE | \
+ APR_FINFO_SIZE | APR_FINFO_CTIME | APR_FINFO_MTIME)
+#endif
+
+/*
+** The single property that we define (in the DAV_FS_URI_MYPROPS namespace)
+*/
+#define DAV_PROPID_FS_executable 1
+
+/*
+ * prefix for temporary files
+ */
+#define DAV_FS_TMP_PREFIX ".davfs.tmp"
+
+static const dav_liveprop_spec dav_fs_props[] =
+{
+ /* standard DAV properties */
+ {
+ DAV_FS_URI_DAV,
+ "creationdate",
+ DAV_PROPID_creationdate,
+ 0
+ },
+ {
+ DAV_FS_URI_DAV,
+ "getcontentlength",
+ DAV_PROPID_getcontentlength,
+ 0
+ },
+ {
+ DAV_FS_URI_DAV,
+ "getetag",
+ DAV_PROPID_getetag,
+ 0
+ },
+ {
+ DAV_FS_URI_DAV,
+ "getlastmodified",
+ DAV_PROPID_getlastmodified,
+ 0
+ },
+
+ /* our custom properties */
+ {
+ DAV_FS_URI_MYPROPS,
+ "executable",
+ DAV_PROPID_FS_executable,
+ 0 /* handled special in dav_fs_is_writable */
+ },
+
+ { 0 } /* sentinel */
+};
+
+static const dav_liveprop_group dav_fs_liveprop_group =
+{
+ dav_fs_props,
+ dav_fs_namespace_uris,
+ &dav_hooks_liveprop_fs
+};
+
+
+/* define the dav_stream structure for our use */
+struct dav_stream {
+ apr_pool_t *p;
+ apr_file_t *f;
+ const char *pathname; /* we may need to remove it at close time */
+ char *temppath;
+ int unlink_on_error;
+};
+
+/* returns an appropriate HTTP status code given an APR status code for a
+ * failed I/O operation. ### use something besides 500? */
+#define MAP_IO2HTTP(e) (APR_STATUS_IS_ENOSPC(e) ? HTTP_INSUFFICIENT_STORAGE : \
+ APR_STATUS_IS_ENOENT(e) ? HTTP_CONFLICT : \
+ HTTP_INTERNAL_SERVER_ERROR)
+
+/* forward declaration for internal treewalkers */
+static dav_error * dav_fs_walk(const dav_walk_params *params, int depth,
+ dav_response **response);
+static dav_error * dav_fs_internal_walk(const dav_walk_params *params,
+ int depth, int is_move,
+ const dav_resource *root_dst,
+ dav_response **response);
+
+/* --------------------------------------------------------------------
+**
+** PRIVATE REPOSITORY FUNCTIONS
+*/
+static request_rec *dav_fs_get_request_rec(const dav_resource *resource)
+{
+ return resource->info->r;
+}
+
+apr_pool_t *dav_fs_pool(const dav_resource *resource)
+{
+ return resource->info->pool;
+}
+
+const char *dav_fs_pathname(const dav_resource *resource)
+{
+ return resource->info->pathname;
+}
+
+dav_error * dav_fs_dir_file_name(
+ const dav_resource *resource,
+ const char **dirpath_p,
+ const char **fname_p)
+{
+ dav_resource_private *ctx = resource->info;
+
+ if (resource->collection) {
+ *dirpath_p = ctx->pathname;
+ if (fname_p != NULL)
+ *fname_p = NULL;
+ }
+ else {
+ const char *testpath, *rootpath;
+ char *dirpath = ap_make_dirstr_parent(ctx->pool, ctx->pathname);
+ apr_size_t dirlen = strlen(dirpath);
+ apr_status_t rv = APR_SUCCESS;
+
+ testpath = dirpath;
+ if (dirlen > 0) {
+ rv = apr_filepath_root(&rootpath, &testpath, 0, ctx->pool);
+ }
+
+ /* remove trailing slash from dirpath, unless it's a root path
+ */
+ if ((rv == APR_SUCCESS && testpath && *testpath)
+ || rv == APR_ERELATIVE) {
+ if (dirpath[dirlen - 1] == '/') {
+ dirpath[dirlen - 1] = '\0';
+ }
+ }
+
+ /* ###: Looks like a response could be appropriate
+ *
+ * APR_SUCCESS here tells us the dir is a root
+ * APR_ERELATIVE told us we had no root (ok)
+ * APR_EINCOMPLETE an incomplete testpath told us
+ * there was no -file- name here!
+ * APR_EBADPATH or other errors tell us this file
+ * path is undecipherable
+ */
+
+ if (rv == APR_SUCCESS || rv == APR_ERELATIVE) {
+ *dirpath_p = dirpath;
+ if (fname_p != NULL)
+ *fname_p = ctx->pathname + dirlen;
+ }
+ else {
+ return dav_new_error(ctx->pool, HTTP_INTERNAL_SERVER_ERROR, 0, rv,
+ "An incomplete/bad path was found in "
+ "dav_fs_dir_file_name.");
+ }
+ }
+
+ return NULL;
+}
+
+/* Note: picked up from ap_gm_timestr_822() */
+/* NOTE: buf must be at least DAV_TIMEBUF_SIZE chars in size */
+static void dav_format_time(int style, apr_time_t sec, char *buf, apr_size_t buflen)
+{
+ apr_time_exp_t tms;
+
+ /* ### what to do if fails? */
+ (void) apr_time_exp_gmt(&tms, sec);
+
+ if (style == DAV_STYLE_ISO8601) {
+ /* ### should we use "-00:00" instead of "Z" ?? */
+
+ /* 20 chars plus null term */
+ apr_snprintf(buf, buflen, "%.4d-%.2d-%.2dT%.2d:%.2d:%.2dZ",
+ tms.tm_year + 1900, tms.tm_mon + 1, tms.tm_mday,
+ tms.tm_hour, tms.tm_min, tms.tm_sec);
+ return;
+ }
+
+ /* RFC 822 date format; as strftime '%a, %d %b %Y %T GMT' */
+
+ /* 29 chars plus null term */
+ apr_snprintf(buf, buflen, "%s, %.2d %s %d %.2d:%.2d:%.2d GMT",
+ apr_day_snames[tms.tm_wday],
+ tms.tm_mday, apr_month_snames[tms.tm_mon],
+ tms.tm_year + 1900,
+ tms.tm_hour, tms.tm_min, tms.tm_sec);
+}
+
+/* Copy or move src to dst; src_finfo is used to propagate permissions
+ * bits across if non-NULL; dst_finfo must be non-NULL iff dst already
+ * exists. */
+static dav_error * dav_fs_copymove_file(
+ int is_move,
+ apr_pool_t * p,
+ const char *src,
+ const char *dst,
+ const apr_finfo_t *src_finfo,
+ const apr_finfo_t *dst_finfo,
+ dav_buffer *pbuf)
+{
+ dav_buffer work_buf = { 0 };
+ apr_file_t *inf = NULL;
+ apr_file_t *outf = NULL;
+ apr_status_t status;
+ apr_fileperms_t perms;
+
+ if (pbuf == NULL)
+ pbuf = &work_buf;
+
+ /* Determine permissions to use for destination */
+ if (src_finfo && src_finfo->valid & APR_FINFO_PROT
+ && src_finfo->protection & APR_UEXECUTE) {
+ perms = src_finfo->protection;
+
+ if (dst_finfo != NULL) {
+ /* chmod it if it already exist */
+ if ((status = apr_file_perms_set(dst, perms)) != APR_SUCCESS) {
+ return dav_new_error(p, HTTP_INTERNAL_SERVER_ERROR, 0, status,
+ "Could not set permissions on destination");
+ }
+ }
+ }
+ else {
+ perms = APR_OS_DEFAULT;
+ }
+
+ dav_set_bufsize(p, pbuf, DAV_FS_COPY_BLOCKSIZE);
+
+ if ((status = apr_file_open(&inf, src, APR_READ | APR_BINARY,
+ APR_OS_DEFAULT, p)) != APR_SUCCESS) {
+ /* ### use something besides 500? */
+ return dav_new_error(p, HTTP_INTERNAL_SERVER_ERROR, 0, status,
+ "Could not open file for reading");
+ }
+
+ /* ### do we need to deal with the umask? */
+ status = apr_file_open(&outf, dst, APR_WRITE | APR_CREATE | APR_TRUNCATE
+ | APR_BINARY, perms, p);
+ if (status != APR_SUCCESS) {
+ apr_file_close(inf);
+
+ return dav_new_error(p, MAP_IO2HTTP(status), 0, status,
+ "Could not open file for writing");
+ }
+
+ while (1) {
+ apr_size_t len = DAV_FS_COPY_BLOCKSIZE;
+
+ status = apr_file_read(inf, pbuf->buf, &len);
+ if (status != APR_SUCCESS && status != APR_EOF) {
+ apr_status_t lcl_status;
+
+ apr_file_close(inf);
+ apr_file_close(outf);
+
+ if ((lcl_status = apr_file_remove(dst, p)) != APR_SUCCESS) {
+ /* ### ACK! Inconsistent state... */
+
+ /* ### use something besides 500? */
+ return dav_new_error(p, HTTP_INTERNAL_SERVER_ERROR, 0,
+ lcl_status,
+ "Could not delete output after read "
+ "failure. Server is now in an "
+ "inconsistent state.");
+ }
+
+ /* ### use something besides 500? */
+ return dav_new_error(p, HTTP_INTERNAL_SERVER_ERROR, 0, status,
+ "Could not read input file");
+ }
+
+ if (status == APR_EOF)
+ break;
+
+ /* write any bytes that were read */
+ status = apr_file_write_full(outf, pbuf->buf, len, NULL);
+ if (status != APR_SUCCESS) {
+ apr_status_t lcl_status;
+
+ apr_file_close(inf);
+ apr_file_close(outf);
+
+ if ((lcl_status = apr_file_remove(dst, p)) != APR_SUCCESS) {
+ /* ### ACK! Inconsistent state... */
+
+ /* ### use something besides 500? */
+ return dav_new_error(p, HTTP_INTERNAL_SERVER_ERROR, 0,
+ lcl_status,
+ "Could not delete output after write "
+ "failure. Server is now in an "
+ "inconsistent state.");
+ }
+
+ return dav_new_error(p, MAP_IO2HTTP(status), 0, status,
+ "Could not write output file");
+ }
+ }
+
+ apr_file_close(inf);
+ apr_file_close(outf);
+
+ if (is_move && (status = apr_file_remove(src, p)) != APR_SUCCESS) {
+ dav_error *err;
+ apr_status_t lcl_status;
+
+ if (APR_STATUS_IS_ENOENT(status)) {
+ /*
+ * Something is wrong here but the result is what we wanted.
+ * We definitely should not remove the destination file.
+ */
+ err = dav_new_error(p, HTTP_INTERNAL_SERVER_ERROR, 0, status,
+ apr_psprintf(p, "Could not remove source "
+ "file %s after move to %s. The "
+ "server may be in an "
+ "inconsistent state.", src, dst));
+ return err;
+ }
+ else if ((lcl_status = apr_file_remove(dst, p)) != APR_SUCCESS) {
+ /* ### ACK. this creates an inconsistency. do more!? */
+
+ /* ### use something besides 500? */
+ return dav_new_error(p, HTTP_INTERNAL_SERVER_ERROR, 0, lcl_status,
+ "Could not remove source or destination "
+ "file. Server is now in an inconsistent "
+ "state.");
+ }
+
+ /* ### use something besides 500? */
+ err = dav_new_error(p, HTTP_INTERNAL_SERVER_ERROR, 0, status,
+ "Could not remove source file after move. "
+ "Destination was removed to ensure consistency.");
+ return err;
+ }
+
+ return NULL;
+}
+
+/* copy/move a file from within a state dir to another state dir */
+/* ### need more buffers to replace the pool argument */
+static dav_error * dav_fs_copymove_state(
+ int is_move,
+ apr_pool_t * p,
+ const char *src_dir, const char *src_file,
+ const char *dst_dir, const char *dst_file,
+ dav_buffer *pbuf)
+{
+ apr_finfo_t src_finfo; /* finfo for source file */
+ apr_finfo_t dst_state_finfo; /* finfo for STATE directory */
+ apr_status_t rv;
+ const char *src;
+ const char *dst;
+
+ /* build the propset pathname for the source file */
+ src = apr_pstrcat(p, src_dir, "/" DAV_FS_STATE_DIR "/", src_file, NULL);
+
+ /* the source file doesn't exist */
+ rv = apr_stat(&src_finfo, src, APR_FINFO_NORM, p);
+ if (rv != APR_SUCCESS && rv != APR_INCOMPLETE) {
+ return NULL;
+ }
+
+ /* build the pathname for the destination state dir */
+ dst = apr_pstrcat(p, dst_dir, "/" DAV_FS_STATE_DIR, NULL);
+
+ /* ### do we need to deal with the umask? */
+
+ /* ensure that it exists */
+ rv = apr_dir_make(dst, APR_OS_DEFAULT, p);
+ if (rv != APR_SUCCESS) {
+ if (!APR_STATUS_IS_EEXIST(rv)) {
+ /* ### use something besides 500? */
+ return dav_new_error(p, HTTP_INTERNAL_SERVER_ERROR, 0, rv,
+ "Could not create internal state directory");
+ }
+ }
+
+ /* get info about the state directory */
+ rv = apr_stat(&dst_state_finfo, dst, APR_FINFO_NORM, p);
+ if (rv != APR_SUCCESS && rv != APR_INCOMPLETE) {
+ /* Ack! Where'd it go? */
+ /* ### use something besides 500? */
+ return dav_new_error(p, HTTP_INTERNAL_SERVER_ERROR, 0, rv,
+ "State directory disappeared");
+ }
+
+ /* The mkdir() may have failed because a *file* exists there already */
+ if (dst_state_finfo.filetype != APR_DIR) {
+ /* ### try to recover by deleting this file? (and mkdir again) */
+ /* ### use something besides 500? */
+ return dav_new_error(p, HTTP_INTERNAL_SERVER_ERROR, 0, 0,
+ "State directory is actually a file");
+ }
+
+ /* append the target file to the state directory pathname */
+ dst = apr_pstrcat(p, dst, "/", dst_file, NULL);
+
+ /* copy/move the file now */
+ if (is_move) {
+ /* try simple rename first */
+ rv = apr_file_rename(src, dst, p);
+ if (APR_STATUS_IS_EXDEV(rv)) {
+ return dav_fs_copymove_file(is_move, p, src, dst, NULL, NULL, pbuf);
+ }
+ if (rv != APR_SUCCESS) {
+ /* ### use something besides 500? */
+ return dav_new_error(p, HTTP_INTERNAL_SERVER_ERROR, 0, rv,
+ "Could not move state file.");
+ }
+ }
+ else
+ {
+ /* gotta copy (and delete) */
+ return dav_fs_copymove_file(is_move, p, src, dst, NULL, NULL, pbuf);
+ }
+
+ return NULL;
+}
+
+static dav_error *dav_fs_copymoveset(int is_move, apr_pool_t *p,
+ const dav_resource *src,
+ const dav_resource *dst,
+ dav_buffer *pbuf)
+{
+ const char *src_dir;
+ const char *src_file;
+ const char *src_state1;
+ const char *src_state2;
+ const char *dst_dir;
+ const char *dst_file;
+ const char *dst_state1;
+ const char *dst_state2;
+ dav_error *err;
+
+ /* Get directory and filename for resources */
+ /* ### should test these result values... */
+ (void) dav_fs_dir_file_name(src, &src_dir, &src_file);
+ (void) dav_fs_dir_file_name(dst, &dst_dir, &dst_file);
+
+ /* Get the corresponding state files for each resource */
+ dav_dbm_get_statefiles(p, src_file, &src_state1, &src_state2);
+ dav_dbm_get_statefiles(p, dst_file, &dst_state1, &dst_state2);
+#if DAV_DEBUG
+ if ((src_state2 != NULL && dst_state2 == NULL) ||
+ (src_state2 == NULL && dst_state2 != NULL)) {
+ return dav_new_error(p, HTTP_INTERNAL_SERVER_ERROR, 0, 0,
+ "DESIGN ERROR: dav_dbm_get_statefiles() "
+ "returned inconsistent results.");
+ }
+#endif
+
+ err = dav_fs_copymove_state(is_move, p,
+ src_dir, src_state1,
+ dst_dir, dst_state1,
+ pbuf);
+
+ if (err == NULL && src_state2 != NULL) {
+ err = dav_fs_copymove_state(is_move, p,
+ src_dir, src_state2,
+ dst_dir, dst_state2,
+ pbuf);
+
+ if (err != NULL) {
+ /* ### CRAP. inconsistency. */
+ /* ### should perform some cleanup at the target if we still
+ ### have the original files */
+
+ /* Change the error to reflect the bad server state. */
+ err->status = HTTP_INTERNAL_SERVER_ERROR;
+ err->desc =
+ "Could not fully copy/move the properties. "
+ "The server is now in an inconsistent state.";
+ }
+ }
+
+ return err;
+}
+
+static dav_error *dav_fs_deleteset(apr_pool_t *p, const dav_resource *resource)
+{
+ const char *dirpath;
+ const char *fname;
+ const char *state1;
+ const char *state2;
+ const char *pathname;
+ apr_status_t status;
+
+ /* Get directory, filename, and state-file names for the resource */
+ /* ### should test this result value... */
+ (void) dav_fs_dir_file_name(resource, &dirpath, &fname);
+ dav_dbm_get_statefiles(p, fname, &state1, &state2);
+
+ /* build the propset pathname for the file */
+ pathname = apr_pstrcat(p,
+ dirpath,
+ "/" DAV_FS_STATE_DIR "/",
+ state1,
+ NULL);
+
+ /* note: we may get ENOENT if the state dir is not present */
+ if ((status = apr_file_remove(pathname, p)) != APR_SUCCESS
+ && !APR_STATUS_IS_ENOENT(status)) {
+ return dav_new_error(p, HTTP_INTERNAL_SERVER_ERROR, 0, status,
+ "Could not remove properties.");
+ }
+
+ if (state2 != NULL) {
+ /* build the propset pathname for the file */
+ pathname = apr_pstrcat(p,
+ dirpath,
+ "/" DAV_FS_STATE_DIR "/",
+ state2,
+ NULL);
+
+ if ((status = apr_file_remove(pathname, p)) != APR_SUCCESS
+ && !APR_STATUS_IS_ENOENT(status)) {
+ /* ### CRAP. only removed half. */
+ return dav_new_error(p, HTTP_INTERNAL_SERVER_ERROR, 0, status,
+ "Could not fully remove properties. "
+ "The server is now in an inconsistent "
+ "state.");
+ }
+ }
+
+ return NULL;
+}
+
+/* --------------------------------------------------------------------
+**
+** REPOSITORY HOOK FUNCTIONS
+*/
+
+static dav_error * dav_fs_get_resource(
+ request_rec *r,
+ const char *root_dir,
+ const char *label,
+ int use_checked_in,
+ dav_resource **result_resource)
+{
+ dav_resource_private *ctx;
+ dav_resource *resource;
+ char *s;
+ char *filename;
+ apr_size_t len;
+
+ /* ### optimize this into a single allocation! */
+
+ /* Create private resource context descriptor */
+ ctx = apr_pcalloc(r->pool, sizeof(*ctx));
+ ctx->finfo = r->finfo;
+ ctx->r = r;
+
+ /* ### this should go away */
+ ctx->pool = r->pool;
+
+ /* Preserve case on OSes which fold canonical filenames */
+#if 0
+ /* ### not available in Apache 2.0 yet */
+ filename = r->case_preserved_filename;
+#else
+ filename = r->filename;
+#endif
+
+ /*
+ ** If there is anything in the path_info, then this indicates that the
+ ** entire path was not used to specify the file/dir. We want to append
+ ** it onto the filename so that we get a "valid" pathname for null
+ ** resources.
+ */
+ s = apr_pstrcat(r->pool, filename, r->path_info, NULL);
+
+ /* make sure the pathname does not have a trailing "/" */
+ len = strlen(s);
+ if (len > 1 && s[len - 1] == '/') {
+ s[len - 1] = '\0';
+ }
+ ctx->pathname = s;
+
+ /* Create resource descriptor */
+ resource = apr_pcalloc(r->pool, sizeof(*resource));
+ resource->type = DAV_RESOURCE_TYPE_REGULAR;
+ resource->info = ctx;
+ resource->hooks = &dav_hooks_repository_fs;
+ resource->pool = r->pool;
+
+ /* make sure the URI does not have a trailing "/" */
+ len = strlen(r->uri);
+ if (len > 1 && r->uri[len - 1] == '/') {
+ s = apr_pstrmemdup(r->pool, r->uri, len-1);
+ resource->uri = s;
+ }
+ else {
+ resource->uri = r->uri;
+ }
+
+ if (r->finfo.filetype != APR_NOFILE) {
+ resource->exists = 1;
+ resource->collection = r->finfo.filetype == APR_DIR;
+
+ /* unused info in the URL will indicate a null resource */
+
+ if (r->path_info != NULL && *r->path_info != '\0') {
+ if (resource->collection) {
+ /* only a trailing "/" is allowed */
+ if (*r->path_info != '/' || r->path_info[1] != '\0') {
+
+ /*
+ ** This URL/filename represents a locknull resource or
+ ** possibly a destination of a MOVE/COPY
+ */
+ resource->exists = 0;
+ resource->collection = 0;
+ }
+ }
+ else
+ {
+ /*
+ ** The base of the path refers to a file -- nothing should
+ ** be in path_info. The resource is simply an error: it
+ ** can't be a null or a locknull resource.
+ */
+ return dav_new_error(r->pool, HTTP_BAD_REQUEST, 0, 0,
+ "The URL contains extraneous path "
+ "components. The resource could not "
+ "be identified.");
+ }
+
+ /* retain proper integrity across the structures */
+ if (!resource->exists) {
+ ctx->finfo.filetype = APR_NOFILE;
+ }
+ }
+ }
+
+ *result_resource = resource;
+ return NULL;
+}
+
+static dav_error * dav_fs_get_parent_resource(const dav_resource *resource,
+ dav_resource **result_parent)
+{
+ dav_resource_private *ctx = resource->info;
+ dav_resource_private *parent_ctx;
+ dav_resource *parent_resource;
+ apr_status_t rv;
+ char *dirpath;
+ const char *testroot;
+ const char *testpath;
+
+ /* If we're at the root of the URL space, then there is no parent. */
+ if (strcmp(resource->uri, "/") == 0) {
+ *result_parent = NULL;
+ return NULL;
+ }
+
+ /* If given resource is root, then there is no parent.
+ * Unless we can retrieve the filepath root, this is
+ * intendend to fail. If we split the root and
+ * no path info remains, then we also fail.
+ */
+ testpath = ctx->pathname;
+ rv = apr_filepath_root(&testroot, &testpath, 0, ctx->pool);
+ if ((rv != APR_SUCCESS && rv != APR_ERELATIVE)
+ || !testpath || !*testpath) {
+ *result_parent = NULL;
+ return NULL;
+ }
+
+ /* ### optimize this into a single allocation! */
+
+ /* Create private resource context descriptor */
+ parent_ctx = apr_pcalloc(ctx->pool, sizeof(*parent_ctx));
+
+ /* ### this should go away */
+ parent_ctx->pool = ctx->pool;
+
+ dirpath = ap_make_dirstr_parent(ctx->pool, ctx->pathname);
+ if (strlen(dirpath) > 1 && dirpath[strlen(dirpath) - 1] == '/')
+ dirpath[strlen(dirpath) - 1] = '\0';
+ parent_ctx->pathname = dirpath;
+
+ parent_resource = apr_pcalloc(ctx->pool, sizeof(*parent_resource));
+ parent_resource->info = parent_ctx;
+ parent_resource->collection = 1;
+ parent_resource->hooks = &dav_hooks_repository_fs;
+ parent_resource->pool = resource->pool;
+
+ if (resource->uri != NULL) {
+ char *uri = ap_make_dirstr_parent(ctx->pool, resource->uri);
+ if (strlen(uri) > 1 && uri[strlen(uri) - 1] == '/')
+ uri[strlen(uri) - 1] = '\0';
+ parent_resource->uri = uri;
+ }
+
+ rv = apr_stat(&parent_ctx->finfo, parent_ctx->pathname,
+ APR_FINFO_NORM, ctx->pool);
+ if (rv == APR_SUCCESS || rv == APR_INCOMPLETE) {
+ parent_resource->exists = 1;
+ }
+
+ *result_parent = parent_resource;
+ return NULL;
+}
+
+static int dav_fs_is_same_resource(
+ const dav_resource *res1,
+ const dav_resource *res2)
+{
+ dav_resource_private *ctx1 = res1->info;
+ dav_resource_private *ctx2 = res2->info;
+
+ if (res1->hooks != res2->hooks)
+ return 0;
+
+ if ((ctx1->finfo.filetype != APR_NOFILE) && (ctx2->finfo.filetype != APR_NOFILE)
+ && (ctx1->finfo.valid & ctx2->finfo.valid & APR_FINFO_INODE)) {
+ return ctx1->finfo.inode == ctx2->finfo.inode;
+ }
+ else {
+ return strcmp(ctx1->pathname, ctx2->pathname) == 0;
+ }
+}
+
+static int dav_fs_is_parent_resource(
+ const dav_resource *res1,
+ const dav_resource *res2)
+{
+ dav_resource_private *ctx1 = res1->info;
+ dav_resource_private *ctx2 = res2->info;
+ apr_size_t len1 = strlen(ctx1->pathname);
+ apr_size_t len2;
+
+ if (res1->hooks != res2->hooks)
+ return 0;
+
+ /* it is safe to use ctx2 now */
+ len2 = strlen(ctx2->pathname);
+
+ return (len2 > len1
+ && memcmp(ctx1->pathname, ctx2->pathname, len1) == 0
+ && ctx2->pathname[len1] == '/');
+}
+
+static apr_status_t tmpfile_cleanup(void *data)
+{
+ dav_stream *ds = data;
+ if (ds->temppath) {
+ apr_file_remove(ds->temppath, ds->p);
+ }
+ return APR_SUCCESS;
+}
+
+/* custom mktemp that creates the file with APR_OS_DEFAULT permissions */
+static apr_status_t dav_fs_mktemp(apr_file_t **fp, char *templ, apr_pool_t *p)
+{
+ apr_status_t rv;
+ int num = ((getpid() << 7) + (apr_uintptr_t)templ % (1 << 16) ) %
+ ( 1 << 23 ) ;
+ char *numstr = templ + strlen(templ) - 6;
+
+ ap_assert(numstr >= templ);
+
+ do {
+ num = (num + 1) % ( 1 << 23 );
+ apr_snprintf(numstr, 7, "%06x", num);
+ rv = apr_file_open(fp, templ,
+ APR_WRITE | APR_CREATE | APR_BINARY | APR_EXCL,
+ APR_OS_DEFAULT, p);
+ } while (APR_STATUS_IS_EEXIST(rv));
+
+ return rv;
+}
+
+static dav_error * dav_fs_open_stream(const dav_resource *resource,
+ dav_stream_mode mode,
+ dav_stream **stream)
+{
+ apr_pool_t *p = resource->info->pool;
+ dav_stream *ds = apr_pcalloc(p, sizeof(*ds));
+ apr_int32_t flags;
+ apr_status_t rv;
+
+ switch (mode) {
+ default:
+ flags = APR_READ | APR_BINARY;
+ break;
+
+ case DAV_MODE_WRITE_TRUNC:
+ flags = APR_WRITE | APR_CREATE | APR_TRUNCATE | APR_BINARY;
+ break;
+ case DAV_MODE_WRITE_SEEKABLE:
+ flags = APR_WRITE | APR_CREATE | APR_BINARY;
+ break;
+ }
+
+ ds->p = p;
+ ds->pathname = resource->info->pathname;
+ ds->temppath = NULL;
+ ds->unlink_on_error = 0;
+
+ if (mode == DAV_MODE_WRITE_TRUNC) {
+ ds->temppath = apr_pstrcat(p, ap_make_dirstr_parent(p, ds->pathname),
+ DAV_FS_TMP_PREFIX "XXXXXX", NULL);
+ rv = dav_fs_mktemp(&ds->f, ds->temppath, ds->p);
+ apr_pool_cleanup_register(p, ds, tmpfile_cleanup,
+ apr_pool_cleanup_null);
+ }
+ else if (mode == DAV_MODE_WRITE_SEEKABLE) {
+ rv = apr_file_open(&ds->f, ds->pathname, flags | APR_FOPEN_EXCL,
+ APR_OS_DEFAULT, ds->p);
+ if (rv == APR_SUCCESS) {
+ /* we have created a new file */
+ ds->unlink_on_error = 1;
+ }
+ else if (APR_STATUS_IS_EEXIST(rv)) {
+ rv = apr_file_open(&ds->f, ds->pathname, flags, APR_OS_DEFAULT,
+ ds->p);
+ }
+ }
+ else {
+ rv = apr_file_open(&ds->f, ds->pathname, flags, APR_OS_DEFAULT, ds->p);
+ }
+
+ if (rv != APR_SUCCESS) {
+ return dav_new_error(p, MAP_IO2HTTP(rv), 0, rv,
+ "An error occurred while opening a resource.");
+ }
+
+ /* (APR registers cleanups for the fd with the pool) */
+
+ *stream = ds;
+ return NULL;
+}
+
+static dav_error * dav_fs_close_stream(dav_stream *stream, int commit)
+{
+ apr_status_t rv;
+
+ apr_file_close(stream->f);
+
+ if (!commit) {
+ if (stream->temppath) {
+ apr_pool_cleanup_run(stream->p, stream, tmpfile_cleanup);
+ }
+ else if (stream->unlink_on_error) {
+ if ((rv = apr_file_remove(stream->pathname, stream->p))
+ != APR_SUCCESS) {
+ /* ### use a better description? */
+ return dav_new_error(stream->p, HTTP_INTERNAL_SERVER_ERROR, 0,
+ rv,
+ "There was a problem removing (rolling "
+ "back) the resource "
+ "when it was being closed.");
+ }
+ }
+ }
+ else if (stream->temppath) {
+ rv = apr_file_rename(stream->temppath, stream->pathname, stream->p);
+ if (rv) {
+ return dav_new_error(stream->p, HTTP_INTERNAL_SERVER_ERROR, 0, rv,
+ "There was a problem writing the file "
+ "atomically after writes.");
+ }
+ apr_pool_cleanup_kill(stream->p, stream, tmpfile_cleanup);
+ }
+
+ return NULL;
+}
+
+static dav_error * dav_fs_write_stream(dav_stream *stream,
+ const void *buf, apr_size_t bufsize)
+{
+ apr_status_t status;
+
+ status = apr_file_write_full(stream->f, buf, bufsize, NULL);
+ if (APR_STATUS_IS_ENOSPC(status)) {
+ return dav_new_error(stream->p, HTTP_INSUFFICIENT_STORAGE, 0, status,
+ "There is not enough storage to write to "
+ "this resource.");
+ }
+ else if (status != APR_SUCCESS) {
+ /* ### use something besides 500? */
+ return dav_new_error(stream->p, HTTP_INTERNAL_SERVER_ERROR, 0, status,
+ "An error occurred while writing to a "
+ "resource.");
+ }
+ return NULL;
+}
+
+static dav_error * dav_fs_seek_stream(dav_stream *stream, apr_off_t abs_pos)
+{
+ apr_status_t status;
+
+ if ((status = apr_file_seek(stream->f, APR_SET, &abs_pos))
+ != APR_SUCCESS) {
+ /* ### should check whether apr_file_seek set abs_pos was set to the
+ * correct position? */
+ /* ### use something besides 500? */
+ return dav_new_error(stream->p, HTTP_INTERNAL_SERVER_ERROR, 0, status,
+ "Could not seek to specified position in the "
+ "resource.");
+ }
+ return NULL;
+}
+
+
+#if DEBUG_GET_HANDLER
+
+/* only define set_headers() and deliver() for debug purposes */
+
+
+static dav_error * dav_fs_set_headers(request_rec *r,
+ const dav_resource *resource)
+{
+ /* ### this function isn't really used since we have a get_pathname */
+ if (!resource->exists)
+ return NULL;
+
+ /* make sure the proper mtime is in the request record */
+ ap_update_mtime(r, resource->info->finfo.mtime);
+
+ /* ### note that these use r->filename rather than <resource> */
+ ap_set_last_modified(r);
+ ap_set_etag(r);
+
+ /* we accept byte-ranges */
+ ap_set_accept_ranges(r);
+
+ /* set up the Content-Length header */
+ ap_set_content_length(r, resource->info->finfo.size);
+
+ /* ### how to set the content type? */
+ /* ### until this is resolved, the Content-Type header is busted */
+
+ return NULL;
+}
+
+static dav_error * dav_fs_deliver(const dav_resource *resource,
+ ap_filter_t *output)
+{
+ apr_pool_t *pool = resource->pool;
+ apr_bucket_brigade *bb;
+ apr_file_t *fd;
+ apr_status_t status;
+ apr_bucket *bkt;
+
+ /* Check resource type */
+ if (resource->type != DAV_RESOURCE_TYPE_REGULAR
+ && resource->type != DAV_RESOURCE_TYPE_VERSION
+ && resource->type != DAV_RESOURCE_TYPE_WORKING) {
+ return dav_new_error(pool, HTTP_CONFLICT, 0, 0,
+ "Cannot GET this type of resource.");
+ }
+ if (resource->collection) {
+ return dav_new_error(pool, HTTP_CONFLICT, 0, 0,
+ "There is no default response to GET for a "
+ "collection.");
+ }
+
+ if ((status = apr_file_open(&fd, resource->info->pathname,
+ APR_READ | APR_BINARY, 0,
+ pool)) != APR_SUCCESS) {
+ return dav_new_error(pool, HTTP_FORBIDDEN, 0, status,
+ "File permissions deny server access.");
+ }
+
+ bb = apr_brigade_create(pool, output->c->bucket_alloc);
+
+ apr_brigade_insert_file(bb, fd, 0, resource->info->finfo.size, pool);
+
+ bkt = apr_bucket_eos_create(output->c->bucket_alloc);
+ APR_BRIGADE_INSERT_TAIL(bb, bkt);
+
+ if ((status = ap_pass_brigade(output, bb)) != APR_SUCCESS) {
+ return dav_new_error(pool, AP_FILTER_ERROR, 0, status,
+ "Could not write contents to filter.");
+ }
+
+ return NULL;
+}
+
+#endif /* DEBUG_GET_HANDLER */
+
+
+static dav_error * dav_fs_create_collection(dav_resource *resource)
+{
+ dav_resource_private *ctx = resource->info;
+ apr_status_t status;
+
+ status = apr_dir_make(ctx->pathname, APR_OS_DEFAULT, ctx->pool);
+ if (APR_STATUS_IS_ENOSPC(status)) {
+ return dav_new_error(ctx->pool, HTTP_INSUFFICIENT_STORAGE, 0, status,
+ "There is not enough storage to create "
+ "this collection.");
+ }
+ else if (APR_STATUS_IS_ENOENT(status)) {
+ return dav_new_error(ctx->pool, HTTP_CONFLICT, 0, status,
+ "Cannot create collection; intermediate "
+ "collection does not exist.");
+ }
+ else if (status != APR_SUCCESS) {
+ /* ### refine this error message? */
+ return dav_new_error(ctx->pool, HTTP_FORBIDDEN, 0, status,
+ "Unable to create collection.");
+ }
+
+ /* update resource state to show it exists as a collection */
+ resource->exists = 1;
+ resource->collection = 1;
+
+ return NULL;
+}
+
+static dav_error * dav_fs_copymove_walker(dav_walk_resource *wres,
+ int calltype)
+{
+ apr_status_t status;
+ dav_fs_copymove_walk_ctx *ctx = wres->walk_ctx;
+ dav_resource_private *srcinfo = wres->resource->info;
+ dav_resource_private *dstinfo = ctx->res_dst->info;
+ dav_error *err = NULL;
+
+ if (wres->resource->collection) {
+ if (calltype == DAV_CALLTYPE_POSTFIX) {
+ /* Postfix call for MOVE. delete the source dir.
+ * Note: when copying, we do not enable the postfix-traversal.
+ */
+ /* ### we are ignoring any error here; what should we do? */
+ (void) apr_dir_remove(srcinfo->pathname, ctx->pool);
+ }
+ else {
+ /* copy/move of a collection. Create the new, target collection */
+ if ((status = apr_dir_make(dstinfo->pathname, APR_OS_DEFAULT,
+ ctx->pool)) != APR_SUCCESS) {
+ /* ### assume it was a permissions problem */
+ /* ### need a description here */
+ err = dav_new_error(ctx->pool, HTTP_FORBIDDEN, 0, status, NULL);
+ }
+ }
+ }
+ else {
+ err = dav_fs_copymove_file(ctx->is_move, ctx->pool,
+ srcinfo->pathname, dstinfo->pathname,
+ &srcinfo->finfo,
+ ctx->res_dst->exists ? &dstinfo->finfo : NULL,
+ &ctx->work_buf);
+ /* ### push a higher-level description? */
+ }
+
+ /*
+ ** If we have a "not so bad" error, then it might need to go into a
+ ** multistatus response.
+ **
+ ** For a MOVE, it will always go into the multistatus. It could be
+ ** that everything has been moved *except* for the root. Using a
+ ** multistatus (with no errors for the other resources) will signify
+ ** this condition.
+ **
+ ** For a COPY, we are traversing in a prefix fashion. If the root fails,
+ ** then we can just bail out now.
+ */
+ if (err != NULL
+ && !ap_is_HTTP_SERVER_ERROR(err->status)
+ && (ctx->is_move
+ || !dav_fs_is_same_resource(wres->resource, ctx->root))) {
+ /* ### use errno to generate DAV:responsedescription? */
+ dav_add_response(wres, err->status, NULL);
+
+ /* the error is in the multistatus now. do not stop the traversal. */
+ return NULL;
+ }
+
+ return err;
+}
+
+static dav_error *dav_fs_copymove_resource(
+ int is_move,
+ const dav_resource *src,
+ const dav_resource *dst,
+ int depth,
+ dav_response **response)
+{
+ dav_error *err = NULL;
+ dav_buffer work_buf = { 0 };
+
+ *response = NULL;
+
+ /* if a collection, recursively copy/move it and its children,
+ * including the state dirs
+ */
+ if (src->collection) {
+ dav_walk_params params = { 0 };
+ dav_response *multi_status;
+
+ params.walk_type = DAV_WALKTYPE_NORMAL | DAV_WALKTYPE_HIDDEN;
+ params.func = dav_fs_copymove_walker;
+ params.pool = src->info->pool;
+ params.root = src;
+
+ /* params.walk_ctx is managed by dav_fs_internal_walk() */
+
+ /* postfix is needed for MOVE to delete source dirs */
+ if (is_move)
+ params.walk_type |= DAV_WALKTYPE_POSTFIX;
+
+ /* note that we return the error OR the multistatus. never both */
+
+ if ((err = dav_fs_internal_walk(&params, depth, is_move, dst,
+ &multi_status)) != NULL) {
+ /* on a "real" error, then just punt. nothing else to do. */
+ return err;
+ }
+
+ if ((*response = multi_status) != NULL) {
+ /* some multistatus responses exist. wrap them in a 207 */
+ return dav_new_error(src->info->pool, HTTP_MULTI_STATUS, 0, 0,
+ "Error(s) occurred on some resources during "
+ "the COPY/MOVE process.");
+ }
+
+ return NULL;
+ }
+
+ /* not a collection */
+ if ((err = dav_fs_copymove_file(is_move, src->info->pool,
+ src->info->pathname, dst->info->pathname,
+ &src->info->finfo,
+ dst->exists ? &dst->info->finfo : NULL,
+ &work_buf)) != NULL) {
+ /* ### push a higher-level description? */
+ return err;
+ }
+
+ /* copy/move properties as well */
+ return dav_fs_copymoveset(is_move, src->info->pool, src, dst, &work_buf);
+}
+
+static dav_error * dav_fs_copy_resource(
+ const dav_resource *src,
+ dav_resource *dst,
+ int depth,
+ dav_response **response)
+{
+ dav_error *err;
+
+#if DAV_DEBUG
+ if (src->hooks != dst->hooks) {
+ /*
+ ** ### strictly speaking, this is a design error; we should not
+ ** ### have reached this point.
+ */
+ return dav_new_error(src->info->pool, HTTP_INTERNAL_SERVER_ERROR, 0, 0,
+ "DESIGN ERROR: a mix of repositories "
+ "was passed to copy_resource.");
+ }
+#endif
+
+ if ((err = dav_fs_copymove_resource(0, src, dst, depth,
+ response)) == NULL) {
+
+ /* update state of destination resource to show it exists */
+ dst->exists = 1;
+ dst->collection = src->collection;
+ }
+
+ return err;
+}
+
+static dav_error * dav_fs_move_resource(
+ dav_resource *src,
+ dav_resource *dst,
+ dav_response **response)
+{
+ dav_resource_private *srcinfo = src->info;
+ dav_resource_private *dstinfo = dst->info;
+ dav_error *err;
+ apr_status_t rv;
+
+#if DAV_DEBUG
+ if (src->hooks != dst->hooks) {
+ /*
+ ** ### strictly speaking, this is a design error; we should not
+ ** ### have reached this point.
+ */
+ return dav_new_error(src->info->pool, HTTP_INTERNAL_SERVER_ERROR, 0, 0,
+ "DESIGN ERROR: a mix of repositories "
+ "was passed to move_resource.");
+ }
+#endif
+
+
+ /* try rename first */
+ rv = apr_file_rename(srcinfo->pathname, dstinfo->pathname, srcinfo->pool);
+
+ /* if we can't simply rename, then do it the hard way... */
+ if (APR_STATUS_IS_EXDEV(rv)) {
+ if ((err = dav_fs_copymove_resource(1, src, dst, DAV_INFINITY,
+ response)) == NULL) {
+ /* update resource states */
+ dst->exists = 1;
+ dst->collection = src->collection;
+ src->exists = 0;
+ src->collection = 0;
+ }
+
+ return err;
+ }
+
+ /* no multistatus response */
+ *response = NULL;
+
+ if (rv != APR_SUCCESS) {
+ /* ### should have a better error than this. */
+ return dav_new_error(srcinfo->pool, HTTP_INTERNAL_SERVER_ERROR, 0, rv,
+ "Could not rename resource.");
+ }
+
+ /* Rename did work. Update resource states and move properties as well */
+ dst->exists = 1;
+ dst->collection = src->collection;
+ src->exists = 0;
+ src->collection = 0;
+
+ if ((err = dav_fs_copymoveset(1, src->info->pool,
+ src, dst, NULL)) == NULL) {
+ /* no error. we're done. go ahead and return now. */
+ return NULL;
+ }
+
+ /* error occurred during properties move; try to put resource back */
+ if (apr_file_rename(dstinfo->pathname, srcinfo->pathname,
+ srcinfo->pool) != APR_SUCCESS) {
+ /* couldn't put it back! */
+ return dav_push_error(srcinfo->pool,
+ HTTP_INTERNAL_SERVER_ERROR, 0,
+ "The resource was moved, but a failure "
+ "occurred during the move of its "
+ "properties. The resource could not be "
+ "restored to its original location. The "
+ "server is now in an inconsistent state.",
+ err);
+ }
+
+ /* update resource states again */
+ src->exists = 1;
+ src->collection = dst->collection;
+ dst->exists = 0;
+ dst->collection = 0;
+
+ /* resource moved back, but properties may be inconsistent */
+ return dav_push_error(srcinfo->pool,
+ HTTP_INTERNAL_SERVER_ERROR, 0,
+ "The resource was moved, but a failure "
+ "occurred during the move of its properties. "
+ "The resource was moved back to its original "
+ "location, but its properties may have been "
+ "partially moved. The server may be in an "
+ "inconsistent state.",
+ err);
+}
+
+static dav_error * dav_fs_delete_walker(dav_walk_resource *wres, int calltype)
+{
+ dav_resource_private *info = wres->resource->info;
+
+ /* do not attempt to remove a null resource,
+ * or a collection with children
+ */
+ if (wres->resource->exists &&
+ (!wres->resource->collection || calltype == DAV_CALLTYPE_POSTFIX)) {
+ /* try to remove the resource */
+ apr_status_t result;
+
+ result = wres->resource->collection
+ ? apr_dir_remove(info->pathname, wres->pool)
+ : apr_file_remove(info->pathname, wres->pool);
+
+ /*
+ ** If an error occurred, then add it to multistatus response.
+ ** Note that we add it for the root resource, too. It is quite
+ ** possible to delete the whole darn tree, yet fail on the root.
+ **
+ ** (also: remember we are deleting via a postfix traversal)
+ */
+ if (result != APR_SUCCESS) {
+ /* ### assume there is a permissions problem */
+
+ /* ### use errno to generate DAV:responsedescription? */
+ dav_add_response(wres, HTTP_FORBIDDEN, NULL);
+ }
+ }
+
+ return NULL;
+}
+
+static dav_error * dav_fs_remove_resource(dav_resource *resource,
+ dav_response **response)
+{
+ apr_status_t status;
+ dav_resource_private *info = resource->info;
+
+ *response = NULL;
+
+ /* if a collection, recursively remove it and its children,
+ * including the state dirs
+ */
+ if (resource->collection) {
+ dav_walk_params params = { 0 };
+ dav_error *err = NULL;
+ dav_response *multi_status;
+
+ params.walk_type = (DAV_WALKTYPE_NORMAL
+ | DAV_WALKTYPE_HIDDEN
+ | DAV_WALKTYPE_POSTFIX);
+ params.func = dav_fs_delete_walker;
+ params.pool = info->pool;
+ params.root = resource;
+
+ if ((err = dav_fs_walk(&params, DAV_INFINITY,
+ &multi_status)) != NULL) {
+ /* on a "real" error, then just punt. nothing else to do. */
+ return err;
+ }
+
+ if ((*response = multi_status) != NULL) {
+ /* some multistatus responses exist. wrap them in a 207 */
+ return dav_new_error(info->pool, HTTP_MULTI_STATUS, 0, 0,
+ "Error(s) occurred on some resources during "
+ "the deletion process.");
+ }
+
+ /* no errors... update resource state */
+ resource->exists = 0;
+ resource->collection = 0;
+
+ return NULL;
+ }
+
+ /* not a collection; remove the file and its properties */
+ if ((status = apr_file_remove(info->pathname, info->pool)) != APR_SUCCESS) {
+ /* ### put a description in here */
+ return dav_new_error(info->pool, HTTP_FORBIDDEN, 0, status, NULL);
+ }
+
+ /* update resource state */
+ resource->exists = 0;
+ resource->collection = 0;
+
+ /* remove properties and return its result */
+ return dav_fs_deleteset(info->pool, resource);
+}
+
+/* ### move this to dav_util? */
+/* Walk recursively down through directories, *
+ * including lock-null resources as we go. */
+static dav_error * dav_fs_walker(dav_fs_walker_context *fsctx, int depth)
+{
+ const dav_walk_params *params = fsctx->params;
+ apr_pool_t *pool = params->pool;
+ apr_status_t status;
+ dav_error *err = NULL;
+ int isdir = fsctx->res1.collection;
+ apr_finfo_t dirent;
+ apr_dir_t *dirp;
+
+ /* ensure the context is prepared properly, then call the func */
+ err = (*params->func)(&fsctx->wres,
+ isdir
+ ? DAV_CALLTYPE_COLLECTION
+ : DAV_CALLTYPE_MEMBER);
+ if (err != NULL) {
+ return err;
+ }
+
+ if (depth == 0 || !isdir) {
+ return NULL;
+ }
+
+ /* put a trailing slash onto the directory, in preparation for appending
+ * files to it as we discovery them within the directory */
+ dav_check_bufsize(pool, &fsctx->path1, DAV_BUFFER_PAD);
+ fsctx->path1.buf[fsctx->path1.cur_len++] = '/';
+ fsctx->path1.buf[fsctx->path1.cur_len] = '\0'; /* in pad area */
+
+ /* if a secondary path is present, then do that, too */
+ if (fsctx->path2.buf != NULL) {
+ dav_check_bufsize(pool, &fsctx->path2, DAV_BUFFER_PAD);
+ fsctx->path2.buf[fsctx->path2.cur_len++] = '/';
+ fsctx->path2.buf[fsctx->path2.cur_len] = '\0'; /* in pad area */
+ }
+
+ /* Note: the URI should ALREADY have a trailing "/" */
+
+ /* for this first pass of files, all resources exist */
+ fsctx->res1.exists = 1;
+
+ /* a file is the default; we'll adjust if we hit a directory */
+ fsctx->res1.collection = 0;
+ fsctx->res2.collection = 0;
+
+ /* open and scan the directory */
+ if ((status = apr_dir_open(&dirp, fsctx->path1.buf, pool)) != APR_SUCCESS) {
+ /* ### need a better error */
+ return dav_new_error(pool, HTTP_NOT_FOUND, 0, status, NULL);
+ }
+ while ((apr_dir_read(&dirent, APR_FINFO_DIRENT, dirp)) == APR_SUCCESS) {
+ apr_size_t len;
+
+ len = strlen(dirent.name);
+
+ /* avoid recursing into our current, parent, or state directories */
+ if (dirent.name[0] == '.'
+ && (len == 1 || (dirent.name[1] == '.' && len == 2))) {
+ continue;
+ }
+
+ if (params->walk_type & DAV_WALKTYPE_AUTH) {
+ /* ### need to authorize each file */
+ /* ### example: .htaccess is normally configured to fail auth */
+
+ /* stuff in the state directory and temp files are never authorized! */
+ if (!strcmp(dirent.name, DAV_FS_STATE_DIR) ||
+ !strncmp(dirent.name, DAV_FS_TMP_PREFIX,
+ strlen(DAV_FS_TMP_PREFIX))) {
+ continue;
+ }
+ }
+ /* skip the state dir and temp files unless a HIDDEN is performed */
+ if (!(params->walk_type & DAV_WALKTYPE_HIDDEN)
+ && (!strcmp(dirent.name, DAV_FS_STATE_DIR) ||
+ !strncmp(dirent.name, DAV_FS_TMP_PREFIX,
+ strlen(DAV_FS_TMP_PREFIX)))) {
+ continue;
+ }
+
+ /* append this file onto the path buffer (copy null term) */
+ dav_buffer_place_mem(pool, &fsctx->path1, dirent.name, len + 1, 0);
+
+ status = apr_stat(&fsctx->info1.finfo, fsctx->path1.buf,
+ DAV_FINFO_MASK, pool);
+ if (status != APR_SUCCESS && status != APR_INCOMPLETE) {
+ /* woah! where'd it go? */
+ /* ### should have a better error here */
+ err = dav_new_error(pool, HTTP_NOT_FOUND, 0, status, NULL);
+ break;
+ }
+
+ /* copy the file to the URI, too. NOTE: we will pad an extra byte
+ for the trailing slash later. */
+ dav_buffer_place_mem(pool, &fsctx->uri_buf, dirent.name, len + 1, 1);
+
+ /* if there is a secondary path, then do that, too */
+ if (fsctx->path2.buf != NULL) {
+ dav_buffer_place_mem(pool, &fsctx->path2, dirent.name, len + 1, 0);
+ }
+
+ /* set up the (internal) pathnames for the two resources */
+ fsctx->info1.pathname = fsctx->path1.buf;
+ fsctx->info2.pathname = fsctx->path2.buf;
+
+ /* set up the URI for the current resource */
+ fsctx->res1.uri = fsctx->uri_buf.buf;
+
+ /* ### for now, only process regular files (e.g. skip symlinks) */
+ if (fsctx->info1.finfo.filetype == APR_REG) {
+ /* call the function for the specified dir + file */
+ if ((err = (*params->func)(&fsctx->wres,
+ DAV_CALLTYPE_MEMBER)) != NULL) {
+ /* ### maybe add a higher-level description? */
+ break;
+ }
+ }
+ else if (fsctx->info1.finfo.filetype == APR_DIR) {
+ apr_size_t save_path_len = fsctx->path1.cur_len;
+ apr_size_t save_uri_len = fsctx->uri_buf.cur_len;
+ apr_size_t save_path2_len = fsctx->path2.cur_len;
+
+ /* adjust length to incorporate the subdir name */
+ fsctx->path1.cur_len += len;
+ fsctx->path2.cur_len += len;
+
+ /* adjust URI length to incorporate subdir and a slash */
+ fsctx->uri_buf.cur_len += len + 1;
+ fsctx->uri_buf.buf[fsctx->uri_buf.cur_len - 1] = '/';
+ fsctx->uri_buf.buf[fsctx->uri_buf.cur_len] = '\0';
+
+ /* switch over to a collection */
+ fsctx->res1.collection = 1;
+ fsctx->res2.collection = 1;
+
+ /* recurse on the subdir */
+ /* ### don't always want to quit on error from single child */
+ if ((err = dav_fs_walker(fsctx, depth - 1)) != NULL) {
+ /* ### maybe add a higher-level description? */
+ break;
+ }
+
+ /* put the various information back */
+ fsctx->path1.cur_len = save_path_len;
+ fsctx->path2.cur_len = save_path2_len;
+ fsctx->uri_buf.cur_len = save_uri_len;
+
+ fsctx->res1.collection = 0;
+ fsctx->res2.collection = 0;
+
+ /* assert: res1.exists == 1 */
+ }
+ }
+
+ /* ### check the return value of this? */
+ apr_dir_close(dirp);
+
+ if (err != NULL)
+ return err;
+
+ if (params->walk_type & DAV_WALKTYPE_LOCKNULL) {
+ apr_size_t offset = 0;
+
+ /* null terminate the directory name */
+ fsctx->path1.buf[fsctx->path1.cur_len - 1] = '\0';
+
+ /* Include any lock null resources found in this collection */
+ fsctx->res1.collection = 1;
+ if ((err = dav_fs_get_locknull_members(&fsctx->res1,
+ &fsctx->locknull_buf)) != NULL) {
+ /* ### maybe add a higher-level description? */
+ return err;
+ }
+
+ /* put a slash back on the end of the directory */
+ fsctx->path1.buf[fsctx->path1.cur_len - 1] = '/';
+
+ /* these are all non-existant (files) */
+ fsctx->res1.exists = 0;
+ fsctx->res1.collection = 0;
+ memset(&fsctx->info1.finfo, 0, sizeof(fsctx->info1.finfo));
+
+ while (offset < fsctx->locknull_buf.cur_len) {
+ apr_size_t len = strlen(fsctx->locknull_buf.buf + offset);
+ dav_lock *locks = NULL;
+
+ /*
+ ** Append the locknull file to the paths and the URI. Note that
+ ** we don't have to pad the URI for a slash since a locknull
+ ** resource is not a collection.
+ */
+ dav_buffer_place_mem(pool, &fsctx->path1,
+ fsctx->locknull_buf.buf + offset, len + 1, 0);
+ dav_buffer_place_mem(pool, &fsctx->uri_buf,
+ fsctx->locknull_buf.buf + offset, len + 1, 0);
+ if (fsctx->path2.buf != NULL) {
+ dav_buffer_place_mem(pool, &fsctx->path2,
+ fsctx->locknull_buf.buf + offset,
+ len + 1, 0);
+ }
+
+ /* set up the (internal) pathnames for the two resources */
+ fsctx->info1.pathname = fsctx->path1.buf;
+ fsctx->info2.pathname = fsctx->path2.buf;
+
+ /* set up the URI for the current resource */
+ fsctx->res1.uri = fsctx->uri_buf.buf;
+
+ /*
+ ** To prevent a PROPFIND showing an expired locknull
+ ** resource, query the lock database to force removal
+ ** of both the lock entry and .locknull, if necessary..
+ ** Sure, the query in PROPFIND would do this.. after
+ ** the locknull resource was already included in the
+ ** return.
+ **
+ ** NOTE: we assume the caller has opened the lock database
+ ** if they have provided DAV_WALKTYPE_LOCKNULL.
+ */
+ /* ### we should also look into opening it read-only and
+ ### eliding timed-out items from the walk, yet leaving
+ ### them in the locknull database until somebody opens
+ ### the thing writable.
+ */
+ /* ### probably ought to use has_locks. note the problem
+ ### mentioned above, though... we would traverse this as
+ ### a locknull, but then a PROPFIND would load the lock
+ ### info, causing a timeout and the locks would not be
+ ### reported. Therefore, a null resource would be returned
+ ### in the PROPFIND.
+ ###
+ ### alternative: just load unresolved locks. any direct
+ ### locks will be timed out (correct). any indirect will
+ ### not (correct; consider if a parent timed out -- the
+ ### timeout routines do not walk and remove indirects;
+ ### even the resolve func would probably fail when it
+ ### tried to find a timed-out direct lock).
+ */
+ if ((err = dav_lock_query(params->lockdb, &fsctx->res1,
+ &locks)) != NULL) {
+ /* ### maybe add a higher-level description? */
+ return err;
+ }
+
+ /* call the function for the specified dir + file */
+ if (locks != NULL &&
+ (err = (*params->func)(&fsctx->wres,
+ DAV_CALLTYPE_LOCKNULL)) != NULL) {
+ /* ### maybe add a higher-level description? */
+ return err;
+ }
+
+ offset += len + 1;
+ }
+
+ /* reset the exists flag */
+ fsctx->res1.exists = 1;
+ }
+
+ if (params->walk_type & DAV_WALKTYPE_POSTFIX) {
+ /* replace the dirs' trailing slashes with null terms */
+ fsctx->path1.buf[--fsctx->path1.cur_len] = '\0';
+ fsctx->uri_buf.buf[--fsctx->uri_buf.cur_len] = '\0';
+ if (fsctx->path2.buf != NULL) {
+ fsctx->path2.buf[--fsctx->path2.cur_len] = '\0';
+ }
+
+ /* this is a collection which exists */
+ fsctx->res1.collection = 1;
+
+ return (*params->func)(&fsctx->wres, DAV_CALLTYPE_POSTFIX);
+ }
+
+ return NULL;
+}
+
+static dav_error * dav_fs_internal_walk(const dav_walk_params *params,
+ int depth, int is_move,
+ const dav_resource *root_dst,
+ dav_response **response)
+{
+ dav_fs_walker_context fsctx = { 0 };
+ dav_error *err;
+ dav_fs_copymove_walk_ctx cm_ctx = { 0 };
+
+#if DAV_DEBUG
+ if ((params->walk_type & DAV_WALKTYPE_LOCKNULL) != 0
+ && params->lockdb == NULL) {
+ return dav_new_error(params->pool, HTTP_INTERNAL_SERVER_ERROR, 0, 0,
+ "DESIGN ERROR: walker called to walk locknull "
+ "resources, but a lockdb was not provided.");
+ }
+#endif
+
+ fsctx.params = params;
+ fsctx.wres.walk_ctx = params->walk_ctx;
+ fsctx.wres.pool = params->pool;
+
+ /* ### zero out versioned, working, baselined? */
+
+ fsctx.res1 = *params->root;
+ fsctx.res1.pool = params->pool;
+
+ fsctx.res1.info = &fsctx.info1;
+ fsctx.info1 = *params->root->info;
+
+ /* the pathname is stored in the path1 buffer */
+ dav_buffer_init(params->pool, &fsctx.path1, fsctx.info1.pathname);
+ fsctx.info1.pathname = fsctx.path1.buf;
+
+ if (root_dst != NULL) {
+ /* internal call from the COPY/MOVE code. set it up. */
+
+ fsctx.wres.walk_ctx = &cm_ctx;
+ cm_ctx.is_move = is_move;
+ cm_ctx.res_dst = &fsctx.res2;
+ cm_ctx.root = params->root;
+ cm_ctx.pool = params->pool;
+
+ fsctx.res2 = *root_dst;
+ fsctx.res2.exists = 0;
+ fsctx.res2.collection = 0;
+ fsctx.res2.uri = NULL; /* we don't track this */
+ fsctx.res2.pool = params->pool;
+
+ fsctx.res2.info = &fsctx.info2;
+ fsctx.info2 = *root_dst->info;
+
+ /* res2 does not exist -- clear its finfo structure */
+ memset(&fsctx.info2.finfo, 0, sizeof(fsctx.info2.finfo));
+
+ /* the pathname is stored in the path2 buffer */
+ dav_buffer_init(params->pool, &fsctx.path2, fsctx.info2.pathname);
+ fsctx.info2.pathname = fsctx.path2.buf;
+ }
+
+ /* prep the URI buffer */
+ dav_buffer_init(params->pool, &fsctx.uri_buf, params->root->uri);
+
+ /* if we have a directory, then ensure the URI has a trailing "/" */
+ if (fsctx.res1.collection
+ && fsctx.uri_buf.buf[fsctx.uri_buf.cur_len - 1] != '/') {
+
+ /* this will fall into the pad area */
+ fsctx.uri_buf.buf[fsctx.uri_buf.cur_len++] = '/';
+ fsctx.uri_buf.buf[fsctx.uri_buf.cur_len] = '\0';
+ }
+
+ /* the current resource's URI is stored in the uri_buf buffer */
+ fsctx.res1.uri = fsctx.uri_buf.buf;
+
+ /* point the callback's resource at our structure */
+ fsctx.wres.resource = &fsctx.res1;
+
+ /* always return the error, and any/all multistatus responses */
+ err = dav_fs_walker(&fsctx, depth);
+ *response = fsctx.wres.response;
+ return err;
+}
+
+static dav_error * dav_fs_walk(const dav_walk_params *params, int depth,
+ dav_response **response)
+{
+ /* always return the error, and any/all multistatus responses */
+ return dav_fs_internal_walk(params, depth, 0, NULL, response);
+}
+
+/* dav_fs_etag: Stolen from ap_make_etag. Creates a strong etag
+ * for file path.
+ * ### do we need to return weak tags sometimes?
+ */
+static const char *dav_fs_getetag(const dav_resource *resource)
+{
+ dav_resource_private *ctx = resource->info;
+ /* XXX: This should really honor the FileETag setting */
+
+ if (!resource->exists)
+ return apr_pstrdup(ctx->pool, "");
+
+ if (ctx->finfo.filetype != APR_NOFILE) {
+ return apr_psprintf(ctx->pool, "\"%" APR_UINT64_T_HEX_FMT "-%"
+ APR_UINT64_T_HEX_FMT "\"",
+ (apr_uint64_t) ctx->finfo.size,
+ (apr_uint64_t) ctx->finfo.mtime);
+ }
+
+ return apr_psprintf(ctx->pool, "\"%" APR_UINT64_T_HEX_FMT "\"",
+ (apr_uint64_t) ctx->finfo.mtime);
+}
+
+static const dav_hooks_repository dav_hooks_repository_fs =
+{
+ DEBUG_GET_HANDLER, /* normally: special GET handling not required */
+ dav_fs_get_resource,
+ dav_fs_get_parent_resource,
+ dav_fs_is_same_resource,
+ dav_fs_is_parent_resource,
+ dav_fs_open_stream,
+ dav_fs_close_stream,
+ dav_fs_write_stream,
+ dav_fs_seek_stream,
+#if DEBUG_GET_HANDLER
+ dav_fs_set_headers,
+ dav_fs_deliver,
+#else
+ NULL,
+ NULL,
+#endif
+ dav_fs_create_collection,
+ dav_fs_copy_resource,
+ dav_fs_move_resource,
+ dav_fs_remove_resource,
+ dav_fs_walk,
+ dav_fs_getetag,
+ NULL,
+ dav_fs_get_request_rec,
+ dav_fs_pathname
+};
+
+static dav_prop_insert dav_fs_insert_prop(const dav_resource *resource,
+ int propid, dav_prop_insert what,
+ apr_text_header *phdr)
+{
+ const char *value;
+ const char *s;
+ apr_pool_t *p = resource->info->pool;
+ const dav_liveprop_spec *info;
+ int global_ns;
+
+ /* an HTTP-date can be 29 chars plus a null term */
+ /* a 64-bit size can be 20 chars plus a null term */
+ char buf[DAV_TIMEBUF_SIZE];
+
+ /*
+ ** None of FS provider properties are defined if the resource does not
+ ** exist. Just bail for this case.
+ **
+ ** Even though we state that the FS properties are not defined, the
+ ** client cannot store dead values -- we deny that thru the is_writable
+ ** hook function.
+ */
+ if (!resource->exists)
+ return DAV_PROP_INSERT_NOTDEF;
+
+ switch (propid) {
+ case DAV_PROPID_creationdate:
+ /*
+ ** Closest thing to a creation date. since we don't actually
+ ** perform the operations that would modify ctime (after we
+ ** create the file), then we should be pretty safe here.
+ */
+ dav_format_time(DAV_STYLE_ISO8601,
+ resource->info->finfo.ctime,
+ buf, sizeof(buf));
+ value = buf;
+ break;
+
+ case DAV_PROPID_getcontentlength:
+ /* our property, but not defined on collection resources */
+ if (resource->collection)
+ return DAV_PROP_INSERT_NOTDEF;
+
+ apr_snprintf(buf, sizeof(buf), "%" APR_OFF_T_FMT, resource->info->finfo.size);
+ value = buf;
+ break;
+
+ case DAV_PROPID_getetag:
+ value = dav_fs_getetag(resource);
+ break;
+
+ case DAV_PROPID_getlastmodified:
+ dav_format_time(DAV_STYLE_RFC822,
+ resource->info->finfo.mtime,
+ buf, sizeof(buf));
+ value = buf;
+ break;
+
+ case DAV_PROPID_FS_executable:
+ /* our property, but not defined on collection resources */
+ if (resource->collection)
+ return DAV_PROP_INSERT_NOTDEF;
+
+ /* our property, but not defined on this platform */
+ if (!(resource->info->finfo.valid & APR_FINFO_UPROT))
+ return DAV_PROP_INSERT_NOTDEF;
+
+ /* the files are "ours" so we only need to check owner exec privs */
+ if (resource->info->finfo.protection & APR_UEXECUTE)
+ value = "T";
+ else
+ value = "F";
+ break;
+
+ default:
+ /* ### what the heck was this property? */
+ return DAV_PROP_INSERT_NOTDEF;
+ }
+
+ /* assert: value != NULL */
+
+ /* get the information and global NS index for the property */
+ global_ns = dav_get_liveprop_info(propid, &dav_fs_liveprop_group, &info);
+
+ /* assert: info != NULL && info->name != NULL */
+
+ /* DBG3("FS: inserting lp%d:%s (local %d)", ns, scan->name, scan->ns); */
+
+ if (what == DAV_PROP_INSERT_VALUE) {
+ s = apr_psprintf(p, "<lp%d:%s>%s</lp%d:%s>" DEBUG_CR,
+ global_ns, info->name, value, global_ns, info->name);
+ }
+ else if (what == DAV_PROP_INSERT_NAME) {
+ s = apr_psprintf(p, "<lp%d:%s/>" DEBUG_CR, global_ns, info->name);
+ }
+ else {
+ /* assert: what == DAV_PROP_INSERT_SUPPORTED */
+ s = apr_psprintf(p,
+ "<D:supported-live-property D:name=\"%s\" "
+ "D:namespace=\"%s\"/>" DEBUG_CR,
+ info->name, dav_fs_namespace_uris[info->ns]);
+ }
+ apr_text_append(p, phdr, s);
+
+ /* we inserted what was asked for */
+ return what;
+}
+
+static int dav_fs_is_writable(const dav_resource *resource, int propid)
+{
+ const dav_liveprop_spec *info;
+
+#ifdef DAV_FS_HAS_EXECUTABLE
+ /* if we have the executable property, and this isn't a collection,
+ then the property is writable. */
+ if (propid == DAV_PROPID_FS_executable && !resource->collection)
+ return 1;
+#endif
+
+ (void) dav_get_liveprop_info(propid, &dav_fs_liveprop_group, &info);
+ return info->is_writable;
+}
+
+static dav_error *dav_fs_patch_validate(const dav_resource *resource,
+ const apr_xml_elem *elem,
+ int operation,
+ void **context,
+ int *defer_to_dead)
+{
+ const apr_text *cdata;
+ const apr_text *f_cdata;
+ char value;
+ dav_elem_private *priv = elem->priv;
+
+ if (priv->propid != DAV_PROPID_FS_executable) {
+ *defer_to_dead = 1;
+ return NULL;
+ }
+
+ if (operation == DAV_PROP_OP_DELETE) {
+ return dav_new_error(resource->info->pool, HTTP_CONFLICT, 0, 0,
+ "The 'executable' property cannot be removed.");
+ }
+
+ cdata = elem->first_cdata.first;
+
+ /* ### hmm. this isn't actually looking at all the possible text items */
+ f_cdata = elem->first_child == NULL
+ ? NULL
+ : elem->first_child->following_cdata.first;
+
+ /* DBG3("name=%s cdata=%s f_cdata=%s",elem->name,cdata ? cdata->text : "[null]",f_cdata ? f_cdata->text : "[null]"); */
+
+ if (cdata == NULL) {
+ if (f_cdata == NULL) {
+ return dav_new_error(resource->info->pool, HTTP_CONFLICT, 0, 0,
+ "The 'executable' property expects a single "
+ "character, valued 'T' or 'F'. There was no "
+ "value submitted.");
+ }
+ cdata = f_cdata;
+ }
+ else if (f_cdata != NULL)
+ goto too_long;
+
+ if (cdata->next != NULL || strlen(cdata->text) != 1)
+ goto too_long;
+
+ value = cdata->text[0];
+ if (value != 'T' && value != 'F') {
+ return dav_new_error(resource->info->pool, HTTP_CONFLICT, 0, 0,
+ "The 'executable' property expects a single "
+ "character, valued 'T' or 'F'. The value "
+ "submitted is invalid.");
+ }
+
+ *context = (void *)((long)(value == 'T'));
+
+ return NULL;
+
+ too_long:
+ return dav_new_error(resource->info->pool, HTTP_CONFLICT, 0, 0,
+ "The 'executable' property expects a single "
+ "character, valued 'T' or 'F'. The value submitted "
+ "has too many characters.");
+
+}
+
+static dav_error *dav_fs_patch_exec(const dav_resource *resource,
+ const apr_xml_elem *elem,
+ int operation,
+ void *context,
+ dav_liveprop_rollback **rollback_ctx)
+{
+ long value = context != NULL;
+ apr_fileperms_t perms = resource->info->finfo.protection;
+ apr_status_t status;
+ long old_value = (perms & APR_UEXECUTE) != 0;
+
+ /* assert: prop == executable. operation == SET. */
+
+ /* don't do anything if there is no change. no rollback info either. */
+ /* DBG2("new value=%d (old=%d)", value, old_value); */
+ if (value == old_value)
+ return NULL;
+
+ perms &= ~APR_UEXECUTE;
+ if (value)
+ perms |= APR_UEXECUTE;
+
+ if ((status = apr_file_perms_set(resource->info->pathname, perms))
+ != APR_SUCCESS) {
+ return dav_new_error(resource->info->pool,
+ HTTP_INTERNAL_SERVER_ERROR, 0, status,
+ "Could not set the executable flag of the "
+ "target resource.");
+ }
+
+ /* update the resource and set up the rollback context */
+ resource->info->finfo.protection = perms;
+ *rollback_ctx = (dav_liveprop_rollback *)old_value;
+
+ return NULL;
+}
+
+static void dav_fs_patch_commit(const dav_resource *resource,
+ int operation,
+ void *context,
+ dav_liveprop_rollback *rollback_ctx)
+{
+ /* nothing to do */
+}
+
+static dav_error *dav_fs_patch_rollback(const dav_resource *resource,
+ int operation,
+ void *context,
+ dav_liveprop_rollback *rollback_ctx)
+{
+ apr_fileperms_t perms = resource->info->finfo.protection & ~APR_UEXECUTE;
+ apr_status_t status;
+ int value = rollback_ctx != NULL;
+
+ /* assert: prop == executable. operation == SET. */
+
+ /* restore the executable bit */
+ if (value)
+ perms |= APR_UEXECUTE;
+
+ if ((status = apr_file_perms_set(resource->info->pathname, perms))
+ != APR_SUCCESS) {
+ return dav_new_error(resource->info->pool,
+ HTTP_INTERNAL_SERVER_ERROR, 0, status,
+ "After a failure occurred, the resource's "
+ "executable flag could not be restored.");
+ }
+
+ /* restore the resource's state */
+ resource->info->finfo.protection = perms;
+
+ return NULL;
+}
+
+
+static const dav_hooks_liveprop dav_hooks_liveprop_fs =
+{
+ dav_fs_insert_prop,
+ dav_fs_is_writable,
+ dav_fs_namespace_uris,
+ dav_fs_patch_validate,
+ dav_fs_patch_exec,
+ dav_fs_patch_commit,
+ dav_fs_patch_rollback
+};
+
+static const dav_provider dav_fs_provider =
+{
+ &dav_hooks_repository_fs,
+ &dav_hooks_db_dbm,
+ &dav_hooks_locks_fs,
+ NULL, /* vsn */
+ NULL, /* binding */
+ NULL, /* search */
+
+ NULL /* ctx */
+};
+
+void dav_fs_gather_propsets(apr_array_header_t *uris)
+{
+#ifdef DAV_FS_HAS_EXECUTABLE
+ *(const char **)apr_array_push(uris) =
+ "<http://apache.org/dav/propset/fs/1>";
+#endif
+}
+
+int dav_fs_find_liveprop(const dav_resource *resource,
+ const char *ns_uri, const char *name,
+ const dav_hooks_liveprop **hooks)
+{
+ /* don't try to find any liveprops if this isn't "our" resource */
+ if (resource->hooks != &dav_hooks_repository_fs)
+ return 0;
+ return dav_do_find_liveprop(ns_uri, name, &dav_fs_liveprop_group, hooks);
+}
+
+void dav_fs_insert_all_liveprops(request_rec *r, const dav_resource *resource,
+ dav_prop_insert what, apr_text_header *phdr)
+{
+ /* don't insert any liveprops if this isn't "our" resource */
+ if (resource->hooks != &dav_hooks_repository_fs)
+ return;
+
+ if (!resource->exists) {
+ /* a lock-null resource */
+ /*
+ ** ### technically, we should insert empty properties. dunno offhand
+ ** ### what part of the spec said this, but it was essentially thus:
+ ** ### "the properties should be defined, but may have no value".
+ */
+ return;
+ }
+
+ (void) dav_fs_insert_prop(resource, DAV_PROPID_creationdate,
+ what, phdr);
+ (void) dav_fs_insert_prop(resource, DAV_PROPID_getcontentlength,
+ what, phdr);
+ (void) dav_fs_insert_prop(resource, DAV_PROPID_getlastmodified,
+ what, phdr);
+ (void) dav_fs_insert_prop(resource, DAV_PROPID_getetag,
+ what, phdr);
+
+#ifdef DAV_FS_HAS_EXECUTABLE
+ /* Only insert this property if it is defined for this platform. */
+ (void) dav_fs_insert_prop(resource, DAV_PROPID_FS_executable,
+ what, phdr);
+#endif
+
+ /* ### we know the others aren't defined as liveprops */
+}
+
+void dav_fs_register(apr_pool_t *p)
+{
+ /* register the namespace URIs */
+ dav_register_liveprop_group(p, &dav_fs_liveprop_group);
+
+ /* register the repository provider */
+ dav_register_provider(p, "filesystem", &dav_fs_provider);
+}
diff --git a/modules/dav/fs/repos.h b/modules/dav/fs/repos.h
new file mode 100644
index 0000000..b164611
--- /dev/null
+++ b/modules/dav/fs/repos.h
@@ -0,0 +1,84 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * @file repos.h
+ * @brief Declarations for the filesystem repository implementation
+ *
+ * @addtogroup MOD_DAV
+ * @{
+ */
+
+#ifndef _DAV_FS_REPOS_H_
+#define _DAV_FS_REPOS_H_
+
+/* the subdirectory to hold all DAV-related information for a directory */
+#define DAV_FS_STATE_DIR ".DAV"
+#define DAV_FS_STATE_FILE_FOR_DIR ".state_for_dir"
+#define DAV_FS_LOCK_NULL_FILE ".locknull"
+
+
+/* ensure that our state subdirectory is present */
+void dav_fs_ensure_state_dir(apr_pool_t *p, const char *dirname);
+
+/* return the storage pool associated with a resource */
+apr_pool_t *dav_fs_pool(const dav_resource *resource);
+
+/* return the full pathname for a resource */
+const char *dav_fs_pathname(const dav_resource *resource);
+
+/* return the directory and filename for a resource */
+dav_error * dav_fs_dir_file_name(const dav_resource *resource,
+ const char **dirpath,
+ const char **fname);
+
+/* return the list of locknull members in this resource's directory */
+dav_error * dav_fs_get_locknull_members(const dav_resource *resource,
+ dav_buffer *pbuf);
+
+
+/* DBM functions used by the repository and locking providers */
+extern const dav_hooks_db dav_hooks_db_dbm;
+
+dav_error * dav_dbm_open_direct(apr_pool_t *p, const char *pathname, int ro,
+ dav_db **pdb);
+void dav_dbm_get_statefiles(apr_pool_t *p, const char *fname,
+ const char **state1, const char **state2);
+dav_error * dav_dbm_delete(dav_db *db, apr_datum_t key);
+dav_error * dav_dbm_store(dav_db *db, apr_datum_t key, apr_datum_t value);
+dav_error * dav_dbm_fetch(dav_db *db, apr_datum_t key, apr_datum_t *pvalue);
+void dav_dbm_freedatum(dav_db *db, apr_datum_t data);
+int dav_dbm_exists(dav_db *db, apr_datum_t key);
+void dav_dbm_close(dav_db *db);
+
+/* where is the lock database located? */
+const char *dav_get_lockdb_path(const request_rec *r);
+
+const dav_hooks_locks *dav_fs_get_lock_hooks(request_rec *r);
+const dav_hooks_propdb *dav_fs_get_propdb_hooks(request_rec *r);
+
+void dav_fs_gather_propsets(apr_array_header_t *uris);
+int dav_fs_find_liveprop(const dav_resource *resource,
+ const char *ns_uri, const char *name,
+ const dav_hooks_liveprop **hooks);
+void dav_fs_insert_all_liveprops(request_rec *r, const dav_resource *resource,
+ dav_prop_insert what, apr_text_header *phdr);
+
+void dav_fs_register(apr_pool_t *p);
+
+#endif /* _DAV_FS_REPOS_H_ */
+/** @} */
+
diff --git a/modules/dav/lock/Makefile.in b/modules/dav/lock/Makefile.in
new file mode 100644
index 0000000..7c5c149
--- /dev/null
+++ b/modules/dav/lock/Makefile.in
@@ -0,0 +1,3 @@
+# a modules Makefile has no explicit targets -- they will be defined by
+# whatever modules are enabled. just grab special.mk to deal with this.
+include $(top_srcdir)/build/special.mk
diff --git a/modules/dav/lock/NWGNUmakefile b/modules/dav/lock/NWGNUmakefile
new file mode 100644
index 0000000..b515d27
--- /dev/null
+++ b/modules/dav/lock/NWGNUmakefile
@@ -0,0 +1,259 @@
+#
+# Declare the sub-directories to be built here
+#
+
+SUBDIRS = \
+ $(EOLIST)
+
+#
+# Get the 'head' of the build environment. This includes default targets and
+# paths to tools
+#
+
+include $(AP_WORK)/build/NWGNUhead.inc
+
+#
+# build this level's files
+
+#
+# Make sure all needed macro's are defined
+#
+
+#
+# These directories will be at the beginning of the include list, followed by
+# INCDIRS
+#
+XINCDIRS += \
+ $(APR)/include \
+ $(APRUTIL)/include \
+ $(AP_WORK)/include \
+ $(AP_WORK)/server/mpm/netware \
+ $(AP_WORK)/modules/dav/main \
+ $(NWOS) \
+ $(EOLIST)
+
+#
+# These flags will come after CFLAGS
+#
+XCFLAGS += \
+ $(EOLIST)
+
+#
+# These defines will come after DEFINES
+#
+XDEFINES += \
+ $(EOLIST)
+
+#
+# These flags will be added to the link.opt file
+#
+XLFLAGS += \
+ $(EOLIST)
+
+#
+# These values will be appended to the correct variables based on the value of
+# RELEASE
+#
+ifeq "$(RELEASE)" "debug"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+ifeq "$(RELEASE)" "noopt"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+ifeq "$(RELEASE)" "release"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+#
+# These are used by the link target if an NLM is being generated
+# This is used by the link 'name' directive to name the nlm. If left blank
+# TARGET_nlm (see below) will be used.
+#
+NLM_NAME = moddavlk
+
+#
+# This is used by the link '-desc ' directive.
+# If left blank, NLM_NAME will be used.
+#
+NLM_DESCRIPTION = Apache $(VERSION_STR) DAV Database Lock Sub-Module
+
+#
+# This is used by the '-threadname' directive. If left blank,
+# NLM_NAME Thread will be used.
+#
+NLM_THREAD_NAME = $(NLM_NAME) Thread
+
+#
+# If this is specified, it will override VERSION value in
+# $(AP_WORK)/build/NWGNUenvironment.inc
+#
+NLM_VERSION =
+
+#
+# If this is specified, it will override the default of 64K
+#
+NLM_STACK_SIZE = 65536
+
+
+#
+# If this is specified it will be used by the link '-entry' directive
+#
+NLM_ENTRY_SYM =
+
+#
+# If this is specified it will be used by the link '-exit' directive
+#
+NLM_EXIT_SYM =
+
+#
+# If this is specified it will be used by the link '-check' directive
+#
+NLM_CHECK_SYM =
+
+#
+# If this is specified it will be used by the link '-flags' directive
+#
+NLM_FLAGS =
+
+#
+# Declare all target files (you must add your files here)
+#
+
+#
+# If there is an NLM target, put it here
+#
+TARGET_nlm = \
+ $(OBJDIR)/$(NLM_NAME).nlm \
+ $(EOLIST)
+
+#
+# If there is an LIB target, put it here
+#
+TARGET_lib = \
+ $(EOLIST)
+
+#
+# These are the OBJ files needed to create the NLM target above.
+# Paths must all use the '/' character
+#
+FILES_nlm_objs = \
+ $(OBJDIR)/mod_dav_lock.o \
+ $(OBJDIR)/locks.o \
+ $(EOLIST)
+
+#
+# These are the LIB files needed to create the NLM target above.
+# These will be added as a library command in the link.opt file.
+#
+FILES_nlm_libs = \
+ $(PRELUDE) \
+ $(EOLIST)
+
+#
+# These are the modules that the above NLM target depends on to load.
+# These will be added as a module command in the link.opt file.
+#
+FILES_nlm_modules = \
+ Apache2 \
+ Libc \
+ mod_dav \
+ $(EOLIST)
+
+#
+# If the nlm has a msg file, put it's path here
+#
+FILE_nlm_msg =
+
+#
+# If the nlm has a hlp file put it's path here
+#
+FILE_nlm_hlp =
+
+#
+# If this is specified, it will override $(NWOS)\copyright.txt.
+#
+FILE_nlm_copyright =
+
+#
+# Any additional imports go here
+#
+FILES_nlm_Ximports = \
+ @libc.imp \
+ @aprlib.imp \
+ @httpd.imp \
+ @../main/dav.imp \
+ $(EOLIST)
+
+#
+# Any symbols exported to here
+#
+FILES_nlm_exports = \
+ dav_lock_module \
+ $(EOLIST)
+
+#
+# These are the OBJ files needed to create the LIB target above.
+# Paths must all use the '/' character
+#
+FILES_lib_objs = \
+ $(EOLIST)
+
+#
+# implement targets and dependancies (leave this section alone)
+#
+
+libs :: $(OBJDIR) $(TARGET_lib)
+
+nlms :: libs $(TARGET_nlm)
+
+#
+# Updated this target to create necessary directories and copy files to the
+# correct place. (See $(AP_WORK)/build/NWGNUhead.inc for examples)
+#
+install :: nlms FORCE
+ $(call COPY,$(OBJDIR)/*.nlm, $(INSTALLBASE)/modules/)
+
+#
+# Any specialized rules here
+#
+
+#
+# Include the 'tail' makefile that has targets that depend on variables defined
+# in this makefile
+#
+
+include $(APBUILD)/NWGNUtail.inc
+
+
diff --git a/modules/dav/lock/config6.m4 b/modules/dav/lock/config6.m4
new file mode 100644
index 0000000..02a05e5
--- /dev/null
+++ b/modules/dav/lock/config6.m4
@@ -0,0 +1,17 @@
+dnl modules enabled in this directory by default
+
+APACHE_MODPATH_INIT(dav/lock)
+
+dav_lock_objects="mod_dav_lock.lo locks.lo"
+
+case "$host" in
+ *os2*)
+ # OS/2 DLLs must resolve all symbols at build time
+ # and we need some from main DAV module
+ dav_lock_objects="$dav_lock_objects ../main/mod_dav.la"
+ ;;
+esac
+
+APACHE_MODULE(dav_lock, DAV provider for generic locking, $dav_lock_objects, ,,,dav)
+
+APACHE_MODPATH_FINISH
diff --git a/modules/dav/lock/locks.c b/modules/dav/lock/locks.c
new file mode 100644
index 0000000..17b9ee6
--- /dev/null
+++ b/modules/dav/lock/locks.c
@@ -0,0 +1,1211 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/*
+ * Generic DAV lock implementation that a DAV provider can use.
+ */
+
+#include "apr.h"
+#include "apr_strings.h"
+#include "apr_file_io.h"
+#include "apr_uuid.h"
+
+#define APR_WANT_MEMFUNC
+#include "apr_want.h"
+
+#include "httpd.h"
+#include "http_log.h"
+
+#include "mod_dav.h"
+
+#include "locks.h"
+
+
+/* ---------------------------------------------------------------
+ *
+ * Lock database primitives
+ *
+ */
+
+/*
+ * LOCK DATABASES
+ *
+ * Lockdiscovery information is stored in the single lock database specified
+ * by the DAVGenericLockDB directive. Information about this db is stored in
+ * the per-dir configuration.
+ *
+ * KEY
+ *
+ * The database is keyed by a key_type unsigned char (DAV_TYPE_FNAME)
+ * followed by full path.
+ *
+ * VALUE
+ *
+ * The value consists of a list of elements.
+ * DIRECT LOCK: [char (DAV_LOCK_DIRECT),
+ * char (dav_lock_scope),
+ * char (dav_lock_type),
+ * int depth,
+ * time_t expires,
+ * apr_uuid_t locktoken,
+ * char[] owner,
+ * char[] auth_user]
+ *
+ * INDIRECT LOCK: [char (DAV_LOCK_INDIRECT),
+ * apr_uuid_t locktoken,
+ * time_t expires,
+ * int key_size,
+ * char[] key]
+ * The key is to the collection lock that resulted in this indirect lock
+ */
+
+#define DAV_TRUE 1
+#define DAV_FALSE 0
+
+#define DAV_CREATE_LIST 23
+#define DAV_APPEND_LIST 24
+
+/* Stored lock_discovery prefix */
+#define DAV_LOCK_DIRECT 1
+#define DAV_LOCK_INDIRECT 2
+
+#define DAV_TYPE_FNAME 11
+
+/* Use the opaquelock scheme for locktokens */
+struct dav_locktoken {
+ apr_uuid_t uuid;
+};
+#define dav_compare_locktoken(plt1, plt2) \
+ memcmp(&(plt1)->uuid, &(plt2)->uuid, sizeof((plt1)->uuid))
+
+
+/* #################################################################
+ * ### keep these structures (internal) or move fully to dav_lock?
+ */
+
+/*
+ * We need to reliably size the fixed-length portion of
+ * dav_lock_discovery; best to separate it into another
+ * struct for a convenient sizeof, unless we pack lock_discovery.
+ */
+typedef struct dav_lock_discovery_fixed
+{
+ char scope;
+ char type;
+ int depth;
+ time_t timeout;
+} dav_lock_discovery_fixed;
+
+typedef struct dav_lock_discovery
+{
+ struct dav_lock_discovery_fixed f;
+
+ dav_locktoken *locktoken;
+ const char *owner; /* owner field from activelock */
+ const char *auth_user; /* authenticated user who created the lock */
+ struct dav_lock_discovery *next;
+} dav_lock_discovery;
+
+/* Indirect locks represent locks inherited from containing collections.
+ * They reference the lock token for the collection the lock is
+ * inherited from. A lock provider may also define a key to the
+ * inherited lock, for fast datbase lookup. The key is opaque outside
+ * the lock provider.
+ */
+typedef struct dav_lock_indirect
+{
+ dav_locktoken *locktoken;
+ apr_datum_t key;
+ struct dav_lock_indirect *next;
+ time_t timeout;
+} dav_lock_indirect;
+
+/* ################################################################# */
+
+/*
+ * Stored direct lock info - full lock_discovery length:
+ * prefix + Fixed length + lock token + 2 strings + 2 nulls (one for each
+ * string)
+ */
+#define dav_size_direct(a) (1 + sizeof(dav_lock_discovery_fixed) \
+ + sizeof(apr_uuid_t) \
+ + ((a)->owner ? strlen((a)->owner) : 0) \
+ + ((a)->auth_user ? strlen((a)->auth_user) : 0) \
+ + 2)
+
+/* Stored indirect lock info - lock token and apr_datum_t */
+#define dav_size_indirect(a) (1 + sizeof(apr_uuid_t) \
+ + sizeof(time_t) \
+ + sizeof(int) + (a)->key.dsize)
+
+/*
+ * The lockdb structure.
+ *
+ * The <db> field may be NULL, meaning one of two things:
+ * 1) That we have not actually opened the underlying database (yet). The
+ * <opened> field should be false.
+ * 2) We opened it readonly and it wasn't present.
+ *
+ * The delayed opening (determined by <opened>) makes creating a lockdb
+ * quick, while deferring the underlying I/O until it is actually required.
+ *
+ * We export the notion of a lockdb, but hide the details of it. Most
+ * implementations will use a database of some kind, but it is certainly
+ * possible that alternatives could be used.
+ */
+struct dav_lockdb_private
+{
+ request_rec *r; /* for accessing the uuid state */
+ apr_pool_t *pool; /* a pool to use */
+ const char *lockdb_path; /* where is the lock database? */
+
+ int opened; /* we opened the database */
+ apr_dbm_t *db; /* if non-NULL, the lock database */
+};
+
+typedef struct
+{
+ dav_lockdb pub;
+ dav_lockdb_private priv;
+} dav_lockdb_combined;
+
+/*
+ * The private part of the lock structure.
+ */
+struct dav_lock_private
+{
+ apr_datum_t key; /* key into the lock database */
+};
+typedef struct
+{
+ dav_lock pub;
+ dav_lock_private priv;
+ dav_locktoken token;
+} dav_lock_combined;
+
+/*
+ * This must be forward-declared so the open_lockdb function can use it.
+ */
+extern const dav_hooks_locks dav_hooks_locks_generic;
+
+static dav_error * dav_generic_dbm_new_error(apr_dbm_t *db, apr_pool_t *p,
+ apr_status_t status)
+{
+ int errcode;
+ const char *errstr;
+ dav_error *err;
+ char errbuf[200];
+
+ if (status == APR_SUCCESS) {
+ return NULL;
+ }
+
+ /* There might not be a <db> if we had problems creating it. */
+ if (db == NULL) {
+ errcode = 1;
+ errstr = "Could not open property database.";
+ }
+ else {
+ (void) apr_dbm_geterror(db, &errcode, errbuf, sizeof(errbuf));
+ errstr = apr_pstrdup(p, errbuf);
+ }
+
+ err = dav_new_error(p, HTTP_INTERNAL_SERVER_ERROR, errcode, status, errstr);
+ return err;
+}
+
+/* internal function for creating locks */
+static dav_lock *dav_generic_alloc_lock(dav_lockdb *lockdb, apr_datum_t key,
+ const dav_locktoken *locktoken)
+{
+ dav_lock_combined *comb;
+
+ comb = apr_pcalloc(lockdb->info->pool, sizeof(*comb));
+ comb->pub.rectype = DAV_LOCKREC_DIRECT;
+ comb->pub.info = &comb->priv;
+ comb->priv.key = key;
+
+ if (locktoken == NULL) {
+ comb->pub.locktoken = &comb->token;
+ apr_uuid_get(&comb->token.uuid);
+ }
+ else {
+ comb->pub.locktoken = locktoken;
+ }
+
+ return &comb->pub;
+}
+
+/*
+ * dav_generic_parse_locktoken
+ *
+ * Parse an opaquelocktoken URI into a locktoken.
+ */
+static dav_error * dav_generic_parse_locktoken(apr_pool_t *p,
+ const char *char_token,
+ dav_locktoken **locktoken_p)
+{
+ dav_locktoken *locktoken;
+
+ if (ap_strstr_c(char_token, "opaquelocktoken:") != char_token) {
+ return dav_new_error(p,
+ HTTP_BAD_REQUEST, DAV_ERR_LOCK_UNK_STATE_TOKEN, 0,
+ "The lock token uses an unknown State-token "
+ "format and could not be parsed.");
+ }
+ char_token += 16;
+
+ locktoken = apr_pcalloc(p, sizeof(*locktoken));
+ if (apr_uuid_parse(&locktoken->uuid, char_token)) {
+ return dav_new_error(p, HTTP_BAD_REQUEST, DAV_ERR_LOCK_PARSE_TOKEN, 0,
+ "The opaquelocktoken has an incorrect format "
+ "and could not be parsed.");
+ }
+
+ *locktoken_p = locktoken;
+ return NULL;
+}
+
+/*
+ * dav_generic_format_locktoken
+ *
+ * Generate the URI for a locktoken
+ */
+static const char *dav_generic_format_locktoken(apr_pool_t *p,
+ const dav_locktoken *locktoken)
+{
+ char buf[APR_UUID_FORMATTED_LENGTH + 1];
+
+ apr_uuid_format(buf, &locktoken->uuid);
+ return apr_pstrcat(p, "opaquelocktoken:", buf, NULL);
+}
+
+/*
+ * dav_generic_compare_locktoken
+ *
+ * Determine whether two locktokens are the same
+ */
+static int dav_generic_compare_locktoken(const dav_locktoken *lt1,
+ const dav_locktoken *lt2)
+{
+ return dav_compare_locktoken(lt1, lt2);
+}
+
+/*
+ * dav_generic_really_open_lockdb:
+ *
+ * If the database hasn't been opened yet, then open the thing.
+ */
+static dav_error * dav_generic_really_open_lockdb(dav_lockdb *lockdb)
+{
+ dav_error *err;
+ apr_status_t status;
+
+ if (lockdb->info->opened) {
+ return NULL;
+ }
+
+ status = apr_dbm_open(&lockdb->info->db, lockdb->info->lockdb_path,
+ lockdb->ro ? APR_DBM_READONLY : APR_DBM_RWCREATE,
+ APR_OS_DEFAULT, lockdb->info->pool);
+
+ if (status) {
+ err = dav_generic_dbm_new_error(lockdb->info->db, lockdb->info->pool,
+ status);
+ return dav_push_error(lockdb->info->pool,
+ HTTP_INTERNAL_SERVER_ERROR,
+ DAV_ERR_LOCK_OPENDB,
+ "Could not open the lock database.",
+ err);
+ }
+
+ /* all right. it is opened now. */
+ lockdb->info->opened = 1;
+
+ return NULL;
+}
+
+/*
+ * dav_generic_open_lockdb:
+ *
+ * "open" the lock database, as specified in the global server configuration.
+ * If force is TRUE, then the database is opened now, rather than lazily.
+ *
+ * Note that only one can be open read/write.
+ */
+static dav_error * dav_generic_open_lockdb(request_rec *r, int ro, int force,
+ dav_lockdb **lockdb)
+{
+ dav_lockdb_combined *comb;
+
+ comb = apr_pcalloc(r->pool, sizeof(*comb));
+ comb->pub.hooks = &dav_hooks_locks_generic;
+ comb->pub.ro = ro;
+ comb->pub.info = &comb->priv;
+ comb->priv.r = r;
+ comb->priv.pool = r->pool;
+
+ comb->priv.lockdb_path = dav_generic_get_lockdb_path(r);
+ if (comb->priv.lockdb_path == NULL) {
+ return dav_new_error(r->pool, HTTP_INTERNAL_SERVER_ERROR,
+ DAV_ERR_LOCK_NO_DB, 0,
+ "A lock database was not specified with the "
+ "DAVGenericLockDB directive. One must be "
+ "specified to use the locking functionality.");
+ }
+
+ /* done initializing. return it. */
+ *lockdb = &comb->pub;
+
+ if (force) {
+ /* ### add a higher-level comment? */
+ return dav_generic_really_open_lockdb(*lockdb);
+ }
+
+ return NULL;
+}
+
+/*
+ * dav_generic_close_lockdb:
+ *
+ * Close it. Duh.
+ */
+static void dav_generic_close_lockdb(dav_lockdb *lockdb)
+{
+ if (lockdb->info->db != NULL) {
+ apr_dbm_close(lockdb->info->db);
+ }
+ lockdb->info->opened = 0;
+}
+
+/*
+ * dav_generic_build_key
+ *
+ * Given a pathname, build a DAV_TYPE_FNAME lock database key.
+ */
+static apr_datum_t dav_generic_build_key(apr_pool_t *p,
+ const dav_resource *resource)
+{
+ apr_datum_t key;
+ const char *pathname = resource->uri;
+
+ /* ### does this allocation have a proper lifetime? need to check */
+ /* ### can we use a buffer for this? */
+
+ /* size is TYPE + pathname + null */
+ key.dsize = strlen(pathname) + 2;
+ key.dptr = apr_palloc(p, key.dsize);
+ *key.dptr = DAV_TYPE_FNAME;
+ memcpy(key.dptr + 1, pathname, key.dsize - 1);
+ if (key.dptr[key.dsize - 2] == '/')
+ key.dptr[--key.dsize - 1] = '\0';
+ return key;
+}
+
+/*
+ * dav_generic_lock_expired: return 1 (true) if the given timeout is in the
+ * past or present (the lock has expired), or 0 (false) if in the future
+ * (the lock has not yet expired).
+ */
+static int dav_generic_lock_expired(time_t expires)
+{
+ return expires != DAV_TIMEOUT_INFINITE && time(NULL) >= expires;
+}
+
+/*
+ * dav_generic_save_lock_record: Saves the lock information specified in the
+ * direct and indirect lock lists about path into the lock database.
+ * If direct and indirect == NULL, the key is removed.
+ */
+static dav_error * dav_generic_save_lock_record(dav_lockdb *lockdb,
+ apr_datum_t key,
+ dav_lock_discovery *direct,
+ dav_lock_indirect *indirect)
+{
+ dav_error *err;
+ apr_status_t status;
+ apr_datum_t val = { 0 };
+ char *ptr;
+ dav_lock_discovery *dp = direct;
+ dav_lock_indirect *ip = indirect;
+
+#if DAV_DEBUG
+ if (lockdb->ro) {
+ return dav_new_error(lockdb->info->pool,
+ HTTP_INTERNAL_SERVER_ERROR, 0, 0,
+ "INTERNAL DESIGN ERROR: the lockdb was opened "
+ "readonly, but an attempt to save locks was "
+ "performed.");
+ }
+#endif
+
+ if ((err = dav_generic_really_open_lockdb(lockdb)) != NULL) {
+ /* ### add a higher-level error? */
+ return err;
+ }
+
+ /* If nothing to save, delete key */
+ if (dp == NULL && ip == NULL) {
+ /* don't fail if the key is not present */
+ /* ### but what about other errors? */
+ apr_dbm_delete(lockdb->info->db, key);
+ return NULL;
+ }
+
+ while (dp) {
+ val.dsize += dav_size_direct(dp);
+ dp = dp->next;
+ }
+ while (ip) {
+ val.dsize += dav_size_indirect(ip);
+ ip = ip->next;
+ }
+
+ /* ### can this be apr_palloc() ? */
+ /* ### hmmm.... investigate the use of a buffer here */
+ ptr = val.dptr = apr_pcalloc(lockdb->info->pool, val.dsize);
+ dp = direct;
+ ip = indirect;
+
+ while (dp) {
+ /* Direct lock - lock_discovery struct follows */
+ *ptr++ = DAV_LOCK_DIRECT;
+ memcpy(ptr, dp, sizeof(dp->f)); /* Fixed portion of struct */
+ ptr += sizeof(dp->f);
+ memcpy(ptr, dp->locktoken, sizeof(*dp->locktoken));
+ ptr += sizeof(*dp->locktoken);
+ if (dp->owner == NULL) {
+ *ptr++ = '\0';
+ }
+ else {
+ memcpy(ptr, dp->owner, strlen(dp->owner) + 1);
+ ptr += strlen(dp->owner) + 1;
+ }
+ if (dp->auth_user == NULL) {
+ *ptr++ = '\0';
+ }
+ else {
+ memcpy(ptr, dp->auth_user, strlen(dp->auth_user) + 1);
+ ptr += strlen(dp->auth_user) + 1;
+ }
+
+ dp = dp->next;
+ }
+
+ while (ip) {
+ /* Indirect lock prefix */
+ *ptr++ = DAV_LOCK_INDIRECT;
+
+ memcpy(ptr, ip->locktoken, sizeof(*ip->locktoken));
+ ptr += sizeof(*ip->locktoken);
+
+ memcpy(ptr, &ip->timeout, sizeof(ip->timeout));
+ ptr += sizeof(ip->timeout);
+
+ memcpy(ptr, &ip->key.dsize, sizeof(ip->key.dsize));
+ ptr += sizeof(ip->key.dsize);
+
+ memcpy(ptr, ip->key.dptr, ip->key.dsize);
+ ptr += ip->key.dsize;
+
+ ip = ip->next;
+ }
+
+ if ((status = apr_dbm_store(lockdb->info->db, key, val)) != APR_SUCCESS) {
+ /* ### more details? add an error_id? */
+ err = dav_generic_dbm_new_error(lockdb->info->db, lockdb->info->pool,
+ status);
+ return dav_push_error(lockdb->info->pool,
+ HTTP_INTERNAL_SERVER_ERROR,
+ DAV_ERR_LOCK_SAVE_LOCK,
+ "Could not save lock information.",
+ err);
+ }
+
+ return NULL;
+}
+
+/*
+ * dav_load_lock_record: Reads lock information about key from lock db;
+ * creates linked lists of the direct and indirect locks.
+ *
+ * If add_method = DAV_APPEND_LIST, the result will be appended to the
+ * head of the direct and indirect lists supplied.
+ *
+ * Passive lock removal: If lock has timed out, it will not be returned.
+ * ### How much "logging" does RFC 2518 require?
+ */
+static dav_error * dav_generic_load_lock_record(dav_lockdb *lockdb,
+ apr_datum_t key,
+ int add_method,
+ dav_lock_discovery **direct,
+ dav_lock_indirect **indirect)
+{
+ apr_pool_t *p = lockdb->info->pool;
+ dav_error *err;
+ apr_status_t status;
+ apr_size_t offset = 0;
+ int need_save = DAV_FALSE;
+ apr_datum_t val = { 0 };
+ dav_lock_discovery *dp;
+ dav_lock_indirect *ip;
+
+ if (add_method != DAV_APPEND_LIST) {
+ *direct = NULL;
+ *indirect = NULL;
+ }
+
+ if ((err = dav_generic_really_open_lockdb(lockdb)) != NULL) {
+ /* ### add a higher-level error? */
+ return err;
+ }
+
+ /*
+ * If we opened readonly and the db wasn't there, then there are no
+ * locks for this resource. Just exit.
+ */
+ if (lockdb->info->db == NULL) {
+ return NULL;
+ }
+
+ if ((status = apr_dbm_fetch(lockdb->info->db, key, &val)) != APR_SUCCESS) {
+ return dav_generic_dbm_new_error(lockdb->info->db, p, status);
+ }
+
+ if (!val.dsize) {
+ return NULL;
+ }
+
+ while (offset < val.dsize) {
+ switch (*(val.dptr + offset++)) {
+ case DAV_LOCK_DIRECT:
+ /* Create and fill a dav_lock_discovery structure */
+
+ dp = apr_pcalloc(p, sizeof(*dp));
+
+ /* Copy the dav_lock_discovery_fixed portion */
+ memcpy(dp, val.dptr + offset, sizeof(dp->f));
+ offset += sizeof(dp->f);
+
+ /* Copy the lock token. */
+ dp->locktoken = apr_pmemdup(p, val.dptr + offset, sizeof(*dp->locktoken));
+ offset += sizeof(*dp->locktoken);
+
+ /* Do we have an owner field? */
+ if (*(val.dptr + offset) == '\0') {
+ ++offset;
+ }
+ else {
+ apr_size_t len = strlen(val.dptr + offset);
+ dp->owner = apr_pstrmemdup(p, val.dptr + offset, len);
+ offset += len + 1;
+ }
+
+ if (*(val.dptr + offset) == '\0') {
+ ++offset;
+ }
+ else {
+ apr_size_t len = strlen(val.dptr + offset);
+ dp->auth_user = apr_pstrmemdup(p, val.dptr + offset, len);
+ offset += len + 1;
+ }
+
+ if (!dav_generic_lock_expired(dp->f.timeout)) {
+ dp->next = *direct;
+ *direct = dp;
+ }
+ else {
+ need_save = DAV_TRUE;
+ }
+ break;
+
+ case DAV_LOCK_INDIRECT:
+ /* Create and fill a dav_lock_indirect structure */
+
+ ip = apr_pcalloc(p, sizeof(*ip));
+ ip->locktoken = apr_pmemdup(p, val.dptr + offset, sizeof(*ip->locktoken));
+ offset += sizeof(*ip->locktoken);
+ memcpy(&ip->timeout, val.dptr + offset, sizeof(ip->timeout));
+ offset += sizeof(ip->timeout);
+ /* length of datum */
+ ip->key.dsize = *((int *) (val.dptr + offset));
+ offset += sizeof(ip->key.dsize);
+ ip->key.dptr = apr_pmemdup(p, val.dptr + offset, ip->key.dsize);
+ offset += ip->key.dsize;
+
+ if (!dav_generic_lock_expired(ip->timeout)) {
+ ip->next = *indirect;
+ *indirect = ip;
+ }
+ else {
+ need_save = DAV_TRUE;
+ }
+
+ break;
+
+ default:
+ apr_dbm_freedatum(lockdb->info->db, val);
+
+ /* ### should use a computed_desc and insert corrupt token data */
+ --offset;
+ return dav_new_error(p,
+ HTTP_INTERNAL_SERVER_ERROR,
+ DAV_ERR_LOCK_CORRUPT_DB, 0,
+ apr_psprintf(p,
+ "The lock database was found to "
+ "be corrupt. offset %"
+ APR_SIZE_T_FMT ", c=%02x",
+ offset, val.dptr[offset]));
+ }
+ }
+
+ apr_dbm_freedatum(lockdb->info->db, val);
+
+ /* Clean up this record if we found expired locks */
+ /*
+ * ### shouldn't do this if we've been opened READONLY. elide the
+ * ### timed-out locks from the response, but don't save that info back
+ */
+ if (need_save == DAV_TRUE) {
+ return dav_generic_save_lock_record(lockdb, key, *direct, *indirect);
+ }
+
+ return NULL;
+}
+
+/* resolve <indirect>, returning <*direct> */
+static dav_error * dav_generic_resolve(dav_lockdb *lockdb,
+ dav_lock_indirect *indirect,
+ dav_lock_discovery **direct,
+ dav_lock_discovery **ref_dp,
+ dav_lock_indirect **ref_ip)
+{
+ dav_error *err;
+ dav_lock_discovery *dir;
+ dav_lock_indirect *ind;
+
+ if ((err = dav_generic_load_lock_record(lockdb, indirect->key,
+ DAV_CREATE_LIST,
+ &dir, &ind)) != NULL) {
+ /* ### insert a higher-level description? */
+ return err;
+ }
+ if (ref_dp != NULL) {
+ *ref_dp = dir;
+ *ref_ip = ind;
+ }
+
+ for (; dir != NULL; dir = dir->next) {
+ if (!dav_compare_locktoken(indirect->locktoken, dir->locktoken)) {
+ *direct = dir;
+ return NULL;
+ }
+ }
+
+ /* No match found (but we should have found one!) */
+
+ /* ### use a different description and/or error ID? */
+ return dav_new_error(lockdb->info->pool,
+ HTTP_INTERNAL_SERVER_ERROR,
+ DAV_ERR_LOCK_CORRUPT_DB, 0,
+ "The lock database was found to be corrupt. "
+ "An indirect lock's direct lock could not "
+ "be found.");
+}
+
+/* ---------------------------------------------------------------
+ *
+ * Property-related lock functions
+ *
+ */
+
+/*
+ * dav_generic_get_supportedlock: Returns a static string for all
+ * supportedlock properties. I think we save more returning a static string
+ * than constructing it every time, though it might look cleaner.
+ */
+static const char *dav_generic_get_supportedlock(const dav_resource *resource)
+{
+ static const char supported[] = DEBUG_CR
+ "<D:lockentry>" DEBUG_CR
+ "<D:lockscope><D:exclusive/></D:lockscope>" DEBUG_CR
+ "<D:locktype><D:write/></D:locktype>" DEBUG_CR
+ "</D:lockentry>" DEBUG_CR
+ "<D:lockentry>" DEBUG_CR
+ "<D:lockscope><D:shared/></D:lockscope>" DEBUG_CR
+ "<D:locktype><D:write/></D:locktype>" DEBUG_CR
+ "</D:lockentry>" DEBUG_CR;
+
+ return supported;
+}
+
+/* ---------------------------------------------------------------
+ *
+ * General lock functions
+ *
+ */
+
+static dav_error * dav_generic_remove_locknull_state(dav_lockdb *lockdb,
+ const dav_resource *resource)
+{
+ /* We don't need to do anything. */
+ return NULL;
+}
+
+static dav_error * dav_generic_create_lock(dav_lockdb *lockdb,
+ const dav_resource *resource,
+ dav_lock **lock)
+{
+ apr_datum_t key;
+
+ key = dav_generic_build_key(lockdb->info->pool, resource);
+
+ *lock = dav_generic_alloc_lock(lockdb, key, NULL);
+
+ (*lock)->is_locknull = !resource->exists;
+
+ return NULL;
+}
+
+static dav_error * dav_generic_get_locks(dav_lockdb *lockdb,
+ const dav_resource *resource,
+ int calltype,
+ dav_lock **locks)
+{
+ apr_pool_t *p = lockdb->info->pool;
+ apr_datum_t key;
+ dav_error *err;
+ dav_lock *lock = NULL;
+ dav_lock *newlock;
+ dav_lock_discovery *dp;
+ dav_lock_indirect *ip;
+
+#if DAV_DEBUG
+ if (calltype == DAV_GETLOCKS_COMPLETE) {
+ return dav_new_error(lockdb->info->pool,
+ HTTP_INTERNAL_SERVER_ERROR, 0, 0,
+ "INTERNAL DESIGN ERROR: DAV_GETLOCKS_COMPLETE "
+ "is not yet supported");
+ }
+#endif
+
+ key = dav_generic_build_key(p, resource);
+ if ((err = dav_generic_load_lock_record(lockdb, key, DAV_CREATE_LIST,
+ &dp, &ip)) != NULL) {
+ /* ### push a higher-level desc? */
+ return err;
+ }
+
+ /* copy all direct locks to the result list */
+ for (; dp != NULL; dp = dp->next) {
+ newlock = dav_generic_alloc_lock(lockdb, key, dp->locktoken);
+ newlock->is_locknull = !resource->exists;
+ newlock->scope = dp->f.scope;
+ newlock->type = dp->f.type;
+ newlock->depth = dp->f.depth;
+ newlock->timeout = dp->f.timeout;
+ newlock->owner = dp->owner;
+ newlock->auth_user = dp->auth_user;
+
+ /* hook into the result list */
+ newlock->next = lock;
+ lock = newlock;
+ }
+
+ /* copy all the indirect locks to the result list. resolve as needed. */
+ for (; ip != NULL; ip = ip->next) {
+ newlock = dav_generic_alloc_lock(lockdb, ip->key, ip->locktoken);
+ newlock->is_locknull = !resource->exists;
+
+ if (calltype == DAV_GETLOCKS_RESOLVED) {
+ err = dav_generic_resolve(lockdb, ip, &dp, NULL, NULL);
+ if (err != NULL) {
+ /* ### push a higher-level desc? */
+ return err;
+ }
+
+ newlock->scope = dp->f.scope;
+ newlock->type = dp->f.type;
+ newlock->depth = dp->f.depth;
+ newlock->timeout = dp->f.timeout;
+ newlock->owner = dp->owner;
+ newlock->auth_user = dp->auth_user;
+ }
+ else {
+ /* DAV_GETLOCKS_PARTIAL */
+ newlock->rectype = DAV_LOCKREC_INDIRECT_PARTIAL;
+ }
+
+ /* hook into the result list */
+ newlock->next = lock;
+ lock = newlock;
+ }
+
+ *locks = lock;
+ return NULL;
+}
+
+static dav_error * dav_generic_find_lock(dav_lockdb *lockdb,
+ const dav_resource *resource,
+ const dav_locktoken *locktoken,
+ int partial_ok,
+ dav_lock **lock)
+{
+ dav_error *err;
+ apr_datum_t key;
+ dav_lock_discovery *dp;
+ dav_lock_indirect *ip;
+
+ *lock = NULL;
+
+ key = dav_generic_build_key(lockdb->info->pool, resource);
+ if ((err = dav_generic_load_lock_record(lockdb, key, DAV_CREATE_LIST,
+ &dp, &ip)) != NULL) {
+ /* ### push a higher-level desc? */
+ return err;
+ }
+
+ for (; dp != NULL; dp = dp->next) {
+ if (!dav_compare_locktoken(locktoken, dp->locktoken)) {
+ *lock = dav_generic_alloc_lock(lockdb, key, locktoken);
+ (*lock)->is_locknull = !resource->exists;
+ (*lock)->scope = dp->f.scope;
+ (*lock)->type = dp->f.type;
+ (*lock)->depth = dp->f.depth;
+ (*lock)->timeout = dp->f.timeout;
+ (*lock)->owner = dp->owner;
+ (*lock)->auth_user = dp->auth_user;
+ return NULL;
+ }
+ }
+
+ for (; ip != NULL; ip = ip->next) {
+ if (!dav_compare_locktoken(locktoken, ip->locktoken)) {
+ *lock = dav_generic_alloc_lock(lockdb, ip->key, locktoken);
+ (*lock)->is_locknull = !resource->exists;
+
+ /* ### nobody uses the resolving right now! */
+ if (partial_ok) {
+ (*lock)->rectype = DAV_LOCKREC_INDIRECT_PARTIAL;
+ }
+ else {
+ (*lock)->rectype = DAV_LOCKREC_INDIRECT;
+ if ((err = dav_generic_resolve(lockdb, ip, &dp,
+ NULL, NULL)) != NULL) {
+ /* ### push a higher-level desc? */
+ return err;
+ }
+ (*lock)->scope = dp->f.scope;
+ (*lock)->type = dp->f.type;
+ (*lock)->depth = dp->f.depth;
+ (*lock)->timeout = dp->f.timeout;
+ (*lock)->owner = dp->owner;
+ (*lock)->auth_user = dp->auth_user;
+ }
+ return NULL;
+ }
+ }
+
+ return NULL;
+}
+
+static dav_error * dav_generic_has_locks(dav_lockdb *lockdb,
+ const dav_resource *resource,
+ int *locks_present)
+{
+ dav_error *err;
+ apr_datum_t key;
+
+ *locks_present = 0;
+
+ if ((err = dav_generic_really_open_lockdb(lockdb)) != NULL) {
+ /* ### insert a higher-level error description */
+ return err;
+ }
+
+ /*
+ * If we opened readonly and the db wasn't there, then there are no
+ * locks for this resource. Just exit.
+ */
+ if (lockdb->info->db == NULL)
+ return NULL;
+
+ key = dav_generic_build_key(lockdb->info->pool, resource);
+
+ *locks_present = apr_dbm_exists(lockdb->info->db, key);
+
+ return NULL;
+}
+
+static dav_error * dav_generic_append_locks(dav_lockdb *lockdb,
+ const dav_resource *resource,
+ int make_indirect,
+ const dav_lock *lock)
+{
+ apr_pool_t *p = lockdb->info->pool;
+ dav_error *err;
+ dav_lock_indirect *ip;
+ dav_lock_discovery *dp;
+ apr_datum_t key;
+
+ key = dav_generic_build_key(lockdb->info->pool, resource);
+
+ err = dav_generic_load_lock_record(lockdb, key, 0, &dp, &ip);
+ if (err != NULL) {
+ /* ### maybe add in a higher-level description */
+ return err;
+ }
+
+ /*
+ * ### when we store the lock more directly, we need to update
+ * ### lock->rectype and lock->is_locknull
+ */
+
+ if (make_indirect) {
+ for (; lock != NULL; lock = lock->next) {
+
+ /* ### this works for any <lock> rectype */
+ dav_lock_indirect *newi = apr_pcalloc(p, sizeof(*newi));
+
+ /* ### shut off the const warning for now */
+ newi->locktoken = (dav_locktoken *)lock->locktoken;
+ newi->timeout = lock->timeout;
+ newi->key = lock->info->key;
+ newi->next = ip;
+ ip = newi;
+ }
+ }
+ else {
+ for (; lock != NULL; lock = lock->next) {
+ /* create and link in the right kind of lock */
+
+ if (lock->rectype == DAV_LOCKREC_DIRECT) {
+ dav_lock_discovery *newd = apr_pcalloc(p, sizeof(*newd));
+
+ newd->f.scope = lock->scope;
+ newd->f.type = lock->type;
+ newd->f.depth = lock->depth;
+ newd->f.timeout = lock->timeout;
+ /* ### shut off the const warning for now */
+ newd->locktoken = (dav_locktoken *)lock->locktoken;
+ newd->owner = lock->owner;
+ newd->auth_user = lock->auth_user;
+ newd->next = dp;
+ dp = newd;
+ }
+ else {
+ /* DAV_LOCKREC_INDIRECT(_PARTIAL) */
+
+ dav_lock_indirect *newi = apr_pcalloc(p, sizeof(*newi));
+
+ /* ### shut off the const warning for now */
+ newi->locktoken = (dav_locktoken *)lock->locktoken;
+ newi->key = lock->info->key;
+ newi->next = ip;
+ ip = newi;
+ }
+ }
+ }
+
+ if ((err = dav_generic_save_lock_record(lockdb, key, dp, ip)) != NULL) {
+ /* ### maybe add a higher-level description */
+ return err;
+ }
+
+ return NULL;
+}
+
+static dav_error * dav_generic_remove_lock(dav_lockdb *lockdb,
+ const dav_resource *resource,
+ const dav_locktoken *locktoken)
+{
+ dav_error *err;
+ dav_lock_discovery *dh = NULL;
+ dav_lock_indirect *ih = NULL;
+ apr_datum_t key;
+
+ key = dav_generic_build_key(lockdb->info->pool, resource);
+
+ if (locktoken != NULL) {
+ dav_lock_discovery *dp;
+ dav_lock_discovery *dprev = NULL;
+ dav_lock_indirect *ip;
+ dav_lock_indirect *iprev = NULL;
+
+ if ((err = dav_generic_load_lock_record(lockdb, key, DAV_CREATE_LIST,
+ &dh, &ih)) != NULL) {
+ /* ### maybe add a higher-level description */
+ return err;
+ }
+
+ for (dp = dh; dp != NULL; dp = dp->next) {
+ if (dav_compare_locktoken(locktoken, dp->locktoken) == 0) {
+ if (dprev)
+ dprev->next = dp->next;
+ else
+ dh = dh->next;
+ }
+ dprev = dp;
+ }
+
+ for (ip = ih; ip != NULL; ip = ip->next) {
+ if (dav_compare_locktoken(locktoken, ip->locktoken) == 0) {
+ if (iprev)
+ iprev->next = ip->next;
+ else
+ ih = ih->next;
+ }
+ iprev = ip;
+ }
+
+ }
+
+ /* save the modified locks, or remove all locks (dh=ih=NULL). */
+ if ((err = dav_generic_save_lock_record(lockdb, key, dh, ih)) != NULL) {
+ /* ### maybe add a higher-level description */
+ return err;
+ }
+
+ return NULL;
+}
+
+static int dav_generic_do_refresh(dav_lock_discovery *dp,
+ const dav_locktoken_list *ltl,
+ time_t new_time)
+{
+ int dirty = 0;
+
+ for (; ltl != NULL; ltl = ltl->next) {
+ if (dav_compare_locktoken(dp->locktoken, ltl->locktoken) == 0)
+ {
+ dp->f.timeout = new_time;
+ dirty = 1;
+ break;
+ }
+ }
+
+ return dirty;
+}
+
+static dav_error * dav_generic_refresh_locks(dav_lockdb *lockdb,
+ const dav_resource *resource,
+ const dav_locktoken_list *ltl,
+ time_t new_time,
+ dav_lock **locks)
+{
+ dav_error *err;
+ apr_datum_t key;
+ dav_lock_discovery *dp;
+ dav_lock_discovery *dp_scan;
+ dav_lock_indirect *ip;
+ int dirty = 0;
+ dav_lock *newlock;
+
+ *locks = NULL;
+
+ key = dav_generic_build_key(lockdb->info->pool, resource);
+ if ((err = dav_generic_load_lock_record(lockdb, key, DAV_CREATE_LIST,
+ &dp, &ip)) != NULL) {
+ /* ### maybe add in a higher-level description */
+ return err;
+ }
+
+ /* ### we should be refreshing direct AND (resolved) indirect locks! */
+
+ /* refresh all of the direct locks on this resource */
+ for (dp_scan = dp; dp_scan != NULL; dp_scan = dp_scan->next) {
+ if (dav_generic_do_refresh(dp_scan, ltl, new_time)) {
+ /* the lock was refreshed. return the lock. */
+ newlock = dav_generic_alloc_lock(lockdb, key, dp_scan->locktoken);
+ newlock->is_locknull = !resource->exists;
+ newlock->scope = dp_scan->f.scope;
+ newlock->type = dp_scan->f.type;
+ newlock->depth = dp_scan->f.depth;
+ newlock->timeout = dp_scan->f.timeout;
+ newlock->owner = dp_scan->owner;
+ newlock->auth_user = dp_scan->auth_user;
+
+ newlock->next = *locks;
+ *locks = newlock;
+
+ dirty = 1;
+ }
+ }
+
+ /* if we refreshed any locks, then save them back. */
+ if (dirty
+ && (err = dav_generic_save_lock_record(lockdb, key, dp, ip)) != NULL) {
+ /* ### maybe add in a higher-level description */
+ return err;
+ }
+
+ /* for each indirect lock, find its direct lock and refresh it. */
+ for (; ip != NULL; ip = ip->next) {
+ dav_lock_discovery *ref_dp;
+ dav_lock_indirect *ref_ip;
+
+ if ((err = dav_generic_resolve(lockdb, ip, &dp_scan,
+ &ref_dp, &ref_ip)) != NULL) {
+ /* ### push a higher-level desc? */
+ return err;
+ }
+ if (dav_generic_do_refresh(dp_scan, ltl, new_time)) {
+ /* the lock was refreshed. return the lock. */
+ newlock = dav_generic_alloc_lock(lockdb, ip->key, dp->locktoken);
+ newlock->is_locknull = !resource->exists;
+ newlock->scope = dp->f.scope;
+ newlock->type = dp->f.type;
+ newlock->depth = dp->f.depth;
+ newlock->timeout = dp->f.timeout;
+ newlock->owner = dp->owner;
+ newlock->auth_user = dp_scan->auth_user;
+
+ newlock->next = *locks;
+ *locks = newlock;
+
+ /* save the (resolved) direct lock back */
+ if ((err = dav_generic_save_lock_record(lockdb, ip->key, ref_dp,
+ ref_ip)) != NULL) {
+ /* ### push a higher-level desc? */
+ return err;
+ }
+ }
+ }
+
+ return NULL;
+}
+
+
+const dav_hooks_locks dav_hooks_locks_generic =
+{
+ dav_generic_get_supportedlock,
+ dav_generic_parse_locktoken,
+ dav_generic_format_locktoken,
+ dav_generic_compare_locktoken,
+ dav_generic_open_lockdb,
+ dav_generic_close_lockdb,
+ dav_generic_remove_locknull_state,
+ dav_generic_create_lock,
+ dav_generic_get_locks,
+ dav_generic_find_lock,
+ dav_generic_has_locks,
+ dav_generic_append_locks,
+ dav_generic_remove_lock,
+ dav_generic_refresh_locks,
+ NULL, /* lookup_resource */
+
+ NULL /* ctx */
+};
diff --git a/modules/dav/lock/locks.h b/modules/dav/lock/locks.h
new file mode 100644
index 0000000..8a78a53
--- /dev/null
+++ b/modules/dav/lock/locks.h
@@ -0,0 +1,33 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * @file locks.h
+ * @brief Declarations for the generic lock implementation
+ *
+ * @addtogroup MOD_DAV
+ * @{
+ */
+
+#ifndef _DAV_LOCK_LOCKS_H_
+#define _DAV_LOCK_LOCKS_H_
+
+/* where is the lock database located? */
+const char *dav_generic_get_lockdb_path(const request_rec *r);
+
+#endif /* _DAV_LOCK_LOCKS_H_ */
+/** @} */
+
diff --git a/modules/dav/lock/mod_dav_lock.c b/modules/dav/lock/mod_dav_lock.c
new file mode 100644
index 0000000..56a3202
--- /dev/null
+++ b/modules/dav/lock/mod_dav_lock.c
@@ -0,0 +1,104 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "httpd.h"
+#include "http_config.h"
+#include "apr_strings.h"
+#include "ap_provider.h"
+
+#include "mod_dav.h"
+#include "locks.h"
+
+/* per-dir configuration */
+typedef struct {
+ const char *lockdb_path;
+} dav_lock_dir_conf;
+
+extern const dav_hooks_locks dav_hooks_locks_generic;
+
+extern module AP_MODULE_DECLARE_DATA dav_lock_module;
+
+const char *dav_generic_get_lockdb_path(const request_rec *r)
+{
+ dav_lock_dir_conf *conf;
+
+ conf = ap_get_module_config(r->per_dir_config, &dav_lock_module);
+ return conf->lockdb_path;
+}
+
+static void *dav_lock_create_dir_config(apr_pool_t *p, char *dir)
+{
+ return apr_pcalloc(p, sizeof(dav_lock_dir_conf));
+}
+
+static void *dav_lock_merge_dir_config(apr_pool_t *p,
+ void *base, void *overrides)
+{
+ dav_lock_dir_conf *parent = base;
+ dav_lock_dir_conf *child = overrides;
+ dav_lock_dir_conf *newconf;
+
+ newconf = apr_pcalloc(p, sizeof(*newconf));
+
+ newconf->lockdb_path =
+ child->lockdb_path ? child->lockdb_path : parent->lockdb_path;
+
+ return newconf;
+}
+
+/*
+ * Command handler for the DAVGenericLockDB directive, which is TAKE1
+ */
+static const char *dav_lock_cmd_davlockdb(cmd_parms *cmd, void *config,
+ const char *arg1)
+{
+ dav_lock_dir_conf *conf = config;
+
+ conf->lockdb_path = ap_server_root_relative(cmd->pool, arg1);
+
+ if (!conf->lockdb_path) {
+ return apr_pstrcat(cmd->pool, "Invalid DAVGenericLockDB path ",
+ arg1, NULL);
+ }
+
+ return NULL;
+}
+
+static const command_rec dav_lock_cmds[] =
+{
+ /* per server */
+ AP_INIT_TAKE1("DAVGenericLockDB", dav_lock_cmd_davlockdb, NULL, ACCESS_CONF,
+ "specify a lock database"),
+
+ { NULL }
+};
+
+static void register_hooks(apr_pool_t *p)
+{
+ ap_register_provider(p, "dav-lock", "generic", "0",
+ &dav_hooks_locks_generic);
+}
+
+AP_DECLARE_MODULE(dav_lock) =
+{
+ STANDARD20_MODULE_STUFF,
+ dav_lock_create_dir_config, /* dir config creater */
+ dav_lock_merge_dir_config, /* dir merger --- default is to override */
+ NULL, /* server config */
+ NULL, /* merge server config */
+ dav_lock_cmds, /* command table */
+ register_hooks, /* register hooks */
+};
diff --git a/modules/dav/lock/mod_dav_lock.dep b/modules/dav/lock/mod_dav_lock.dep
new file mode 100644
index 0000000..e21d71b
--- /dev/null
+++ b/modules/dav/lock/mod_dav_lock.dep
@@ -0,0 +1,100 @@
+# Microsoft Developer Studio Generated Dependency File, included by mod_dav_lock.mak
+
+.\locks.c : \
+ "..\..\..\include\ap_config.h"\
+ "..\..\..\include\ap_config_layout.h"\
+ "..\..\..\include\ap_hooks.h"\
+ "..\..\..\include\ap_mmn.h"\
+ "..\..\..\include\ap_regex.h"\
+ "..\..\..\include\ap_release.h"\
+ "..\..\..\include\apache_noprobes.h"\
+ "..\..\..\include\http_config.h"\
+ "..\..\..\include\http_log.h"\
+ "..\..\..\include\httpd.h"\
+ "..\..\..\include\mod_dav.h"\
+ "..\..\..\include\os.h"\
+ "..\..\..\include\util_cfgtree.h"\
+ "..\..\..\include\util_filter.h"\
+ "..\..\..\include\util_xml.h"\
+ "..\..\..\srclib\apr-util\include\apr_buckets.h"\
+ "..\..\..\srclib\apr-util\include\apr_dbm.h"\
+ "..\..\..\srclib\apr-util\include\apr_hooks.h"\
+ "..\..\..\srclib\apr-util\include\apr_optional_hooks.h"\
+ "..\..\..\srclib\apr-util\include\apr_uri.h"\
+ "..\..\..\srclib\apr-util\include\apr_uuid.h"\
+ "..\..\..\srclib\apr-util\include\apr_xlate.h"\
+ "..\..\..\srclib\apr-util\include\apr_xml.h"\
+ "..\..\..\srclib\apr-util\include\apu.h"\
+ "..\..\..\srclib\apr\include\apr.h"\
+ "..\..\..\srclib\apr\include\apr_allocator.h"\
+ "..\..\..\srclib\apr\include\apr_errno.h"\
+ "..\..\..\srclib\apr\include\apr_file_info.h"\
+ "..\..\..\srclib\apr\include\apr_file_io.h"\
+ "..\..\..\srclib\apr\include\apr_general.h"\
+ "..\..\..\srclib\apr\include\apr_hash.h"\
+ "..\..\..\srclib\apr\include\apr_inherit.h"\
+ "..\..\..\srclib\apr\include\apr_mmap.h"\
+ "..\..\..\srclib\apr\include\apr_network_io.h"\
+ "..\..\..\srclib\apr\include\apr_poll.h"\
+ "..\..\..\srclib\apr\include\apr_pools.h"\
+ "..\..\..\srclib\apr\include\apr_ring.h"\
+ "..\..\..\srclib\apr\include\apr_strings.h"\
+ "..\..\..\srclib\apr\include\apr_tables.h"\
+ "..\..\..\srclib\apr\include\apr_thread_mutex.h"\
+ "..\..\..\srclib\apr\include\apr_thread_proc.h"\
+ "..\..\..\srclib\apr\include\apr_time.h"\
+ "..\..\..\srclib\apr\include\apr_user.h"\
+ "..\..\..\srclib\apr\include\apr_want.h"\
+ ".\locks.h"\
+
+
+.\mod_dav_lock.c : \
+ "..\..\..\include\ap_config.h"\
+ "..\..\..\include\ap_config_layout.h"\
+ "..\..\..\include\ap_hooks.h"\
+ "..\..\..\include\ap_mmn.h"\
+ "..\..\..\include\ap_provider.h"\
+ "..\..\..\include\ap_regex.h"\
+ "..\..\..\include\ap_release.h"\
+ "..\..\..\include\apache_noprobes.h"\
+ "..\..\..\include\http_config.h"\
+ "..\..\..\include\httpd.h"\
+ "..\..\..\include\mod_dav.h"\
+ "..\..\..\include\os.h"\
+ "..\..\..\include\util_cfgtree.h"\
+ "..\..\..\include\util_filter.h"\
+ "..\..\..\include\util_xml.h"\
+ "..\..\..\srclib\apr-util\include\apr_buckets.h"\
+ "..\..\..\srclib\apr-util\include\apr_dbm.h"\
+ "..\..\..\srclib\apr-util\include\apr_hooks.h"\
+ "..\..\..\srclib\apr-util\include\apr_optional_hooks.h"\
+ "..\..\..\srclib\apr-util\include\apr_uri.h"\
+ "..\..\..\srclib\apr-util\include\apr_xlate.h"\
+ "..\..\..\srclib\apr-util\include\apr_xml.h"\
+ "..\..\..\srclib\apr-util\include\apu.h"\
+ "..\..\..\srclib\apr\include\apr.h"\
+ "..\..\..\srclib\apr\include\apr_allocator.h"\
+ "..\..\..\srclib\apr\include\apr_errno.h"\
+ "..\..\..\srclib\apr\include\apr_file_info.h"\
+ "..\..\..\srclib\apr\include\apr_file_io.h"\
+ "..\..\..\srclib\apr\include\apr_general.h"\
+ "..\..\..\srclib\apr\include\apr_hash.h"\
+ "..\..\..\srclib\apr\include\apr_inherit.h"\
+ "..\..\..\srclib\apr\include\apr_mmap.h"\
+ "..\..\..\srclib\apr\include\apr_network_io.h"\
+ "..\..\..\srclib\apr\include\apr_poll.h"\
+ "..\..\..\srclib\apr\include\apr_pools.h"\
+ "..\..\..\srclib\apr\include\apr_ring.h"\
+ "..\..\..\srclib\apr\include\apr_strings.h"\
+ "..\..\..\srclib\apr\include\apr_tables.h"\
+ "..\..\..\srclib\apr\include\apr_thread_mutex.h"\
+ "..\..\..\srclib\apr\include\apr_thread_proc.h"\
+ "..\..\..\srclib\apr\include\apr_time.h"\
+ "..\..\..\srclib\apr\include\apr_user.h"\
+ "..\..\..\srclib\apr\include\apr_want.h"\
+ ".\locks.h"\
+
+
+..\..\..\build\win32\httpd.rc : \
+ "..\..\..\include\ap_release.h"\
+
diff --git a/modules/dav/lock/mod_dav_lock.dsp b/modules/dav/lock/mod_dav_lock.dsp
new file mode 100644
index 0000000..e1c1a24
--- /dev/null
+++ b/modules/dav/lock/mod_dav_lock.dsp
@@ -0,0 +1,127 @@
+# Microsoft Developer Studio Project File - Name="mod_dav_lock" - Package Owner=<4>
+# Microsoft Developer Studio Generated Build File, Format Version 6.00
+# ** DO NOT EDIT **
+
+# TARGTYPE "Win32 (x86) Dynamic-Link Library" 0x0102
+
+CFG=mod_dav_lock - Win32 Release
+!MESSAGE This is not a valid makefile. To build this project using NMAKE,
+!MESSAGE use the Export Makefile command and run
+!MESSAGE
+!MESSAGE NMAKE /f "mod_dav_lock.mak".
+!MESSAGE
+!MESSAGE You can specify a configuration when running NMAKE
+!MESSAGE by defining the macro CFG on the command line. For example:
+!MESSAGE
+!MESSAGE NMAKE /f "mod_dav_lock.mak" CFG="mod_dav_lock - Win32 Release"
+!MESSAGE
+!MESSAGE Possible choices for configuration are:
+!MESSAGE
+!MESSAGE "mod_dav_lock - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE "mod_dav_lock - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE
+
+# Begin Project
+# PROP AllowPerConfigDependencies 0
+# PROP Scc_ProjName ""
+# PROP Scc_LocalPath ""
+CPP=cl.exe
+MTL=midl.exe
+RSC=rc.exe
+
+!IF "$(CFG)" == "mod_dav_lock - Win32 Release"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 0
+# PROP BASE Output_Dir "Release"
+# PROP BASE Intermediate_Dir "Release"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 0
+# PROP Output_Dir "Release"
+# PROP Intermediate_Dir "Release"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /MD /W3 /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /FD /c
+# ADD CPP /nologo /MD /W3 /O2 /Oy- /Zi /I "../../../include" /I "../../../srclib/apr/include" /I "../../../srclib/apr-util/include" /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /Fd"Release\mod_dav_lock_src" /FD /c
+# ADD BASE MTL /nologo /D "NDEBUG" /win32
+# ADD MTL /nologo /D "NDEBUG" /mktyplib203 /win32
+# ADD BASE RSC /l 0x409 /d "NDEBUG"
+# ADD RSC /l 0x409 /fo"Release/mod_dav_lock.res" /i "../../../include" /i "../../../srclib/apr/include" /d "NDEBUG" /d BIN_NAME="mod_dav_lock.so" /d LONG_NAME="dav_lock_module for Apache"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib ws2_32.lib mswsock.lib /nologo /subsystem:windows /dll /out:".\Release\mod_dav_lock.so" /base:@..\..\..\os\win32\BaseAddr.ref,mod_dav_lock.so
+# ADD LINK32 kernel32.lib ws2_32.lib mswsock.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Release\mod_dav_lock.so" /base:@..\..\..\os\win32\BaseAddr.ref,mod_dav_lock.so /opt:ref
+# Begin Special Build Tool
+TargetPath=.\Release\mod_dav_lock.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+PostBuild_Cmds=if exist $(TargetPath).manifest mt.exe -manifest $(TargetPath).manifest -outputresource:$(TargetPath);2
+# End Special Build Tool
+
+!ELSEIF "$(CFG)" == "mod_dav_lock - Win32 Debug"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 1
+# PROP BASE Output_Dir "Debug"
+# PROP BASE Intermediate_Dir "Debug"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 1
+# PROP Output_Dir "Debug"
+# PROP Intermediate_Dir "Debug"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /MDd /W3 /EHsc /Zi /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /FD /c
+# ADD CPP /nologo /MDd /W3 /EHsc /Zi /Od /I "../../../include" /I "../../../srclib/apr/include" /I "../../../srclib/apr-util/include" /D "_DEBUG" /D "WIN32" /D "_WINDOWS" /Fd"Debug\mod_dav_lock_src" /FD /c
+# ADD BASE MTL /nologo /D "_DEBUG" /win32
+# ADD MTL /nologo /D "_DEBUG" /mktyplib203 /win32
+# ADD BASE RSC /l 0x409 /d "_DEBUG"
+# ADD RSC /l 0x409 /fo"Debug/mod_dav_lock.res" /i "../../../include" /i "../../../srclib/apr/include" /d "_DEBUG" /d BIN_NAME="mod_dav_lock.so" /d LONG_NAME="dav_lock_module for Apache"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib ws2_32.lib mswsock.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Debug\mod_dav_lock.so" /base:@..\..\..\os\win32\BaseAddr.ref,mod_dav_lock.so
+# ADD LINK32 kernel32.lib ws2_32.lib mswsock.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Debug\mod_dav_lock.so" /base:@..\..\..\os\win32\BaseAddr.ref,mod_dav_lock.so
+# Begin Special Build Tool
+TargetPath=.\Debug\mod_dav_lock.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+PostBuild_Cmds=if exist $(TargetPath).manifest mt.exe -manifest $(TargetPath).manifest -outputresource:$(TargetPath);2
+# End Special Build Tool
+
+!ENDIF
+
+# Begin Target
+
+# Name "mod_dav_lock - Win32 Release"
+# Name "mod_dav_lock - Win32 Debug"
+# Begin Group "Source Files"
+
+# PROP Default_Filter "cpp;c;cxx;rc;def;r;odl;hpj;bat;for;f90"
+# Begin Source File
+
+SOURCE=.\locks.c
+# End Source File
+# Begin Source File
+
+SOURCE=.\mod_dav_lock.c
+# End Source File
+# End Group
+# Begin Group "Header Files"
+
+# PROP Default_Filter "h;hpp;hxx;hm;inl;fi;fd"
+# Begin Source File
+
+SOURCE=.\locks.h
+# End Source File
+# End Group
+# Begin Source File
+
+SOURCE=..\..\..\build\win32\httpd.rc
+# End Source File
+# End Target
+# End Project
diff --git a/modules/dav/lock/mod_dav_lock.mak b/modules/dav/lock/mod_dav_lock.mak
new file mode 100644
index 0000000..0ae2b6a
--- /dev/null
+++ b/modules/dav/lock/mod_dav_lock.mak
@@ -0,0 +1,389 @@
+# Microsoft Developer Studio Generated NMAKE File, Based on mod_dav_lock.dsp
+!IF "$(CFG)" == ""
+CFG=mod_dav_lock - Win32 Release
+!MESSAGE No configuration specified. Defaulting to mod_dav_lock - Win32 Release.
+!ENDIF
+
+!IF "$(CFG)" != "mod_dav_lock - Win32 Release" && "$(CFG)" != "mod_dav_lock - Win32 Debug"
+!MESSAGE Invalid configuration "$(CFG)" specified.
+!MESSAGE You can specify a configuration when running NMAKE
+!MESSAGE by defining the macro CFG on the command line. For example:
+!MESSAGE
+!MESSAGE NMAKE /f "mod_dav_lock.mak" CFG="mod_dav_lock - Win32 Release"
+!MESSAGE
+!MESSAGE Possible choices for configuration are:
+!MESSAGE
+!MESSAGE "mod_dav_lock - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE "mod_dav_lock - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE
+!ERROR An invalid configuration is specified.
+!ENDIF
+
+!IF "$(OS)" == "Windows_NT"
+NULL=
+!ELSE
+NULL=nul
+!ENDIF
+
+!IF "$(CFG)" == "mod_dav_lock - Win32 Release"
+
+OUTDIR=.\Release
+INTDIR=.\Release
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+# Begin Custom Macros
+OutDir=.\Release
+# End Custom Macros
+
+!IF "$(RECURSE)" == "0"
+
+ALL : "$(OUTDIR)\mod_dav_lock.so" "$(DS_POSTBUILD_DEP)"
+
+!ELSE
+
+ALL : "mod_dav - Win32 Release" "libhttpd - Win32 Release" "libaprutil - Win32 Release" "libapr - Win32 Release" "$(OUTDIR)\mod_dav_lock.so" "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+!IF "$(RECURSE)" == "1"
+CLEAN :"libapr - Win32 ReleaseCLEAN" "libaprutil - Win32 ReleaseCLEAN" "libhttpd - Win32 ReleaseCLEAN" "mod_dav - Win32 ReleaseCLEAN"
+!ELSE
+CLEAN :
+!ENDIF
+ -@erase "$(INTDIR)\locks.obj"
+ -@erase "$(INTDIR)\mod_dav_lock.obj"
+ -@erase "$(INTDIR)\mod_dav_lock.res"
+ -@erase "$(INTDIR)\mod_dav_lock_src.idb"
+ -@erase "$(INTDIR)\mod_dav_lock_src.pdb"
+ -@erase "$(OUTDIR)\mod_dav_lock.exp"
+ -@erase "$(OUTDIR)\mod_dav_lock.lib"
+ -@erase "$(OUTDIR)\mod_dav_lock.pdb"
+ -@erase "$(OUTDIR)\mod_dav_lock.so"
+
+"$(OUTDIR)" :
+ if not exist "$(OUTDIR)/$(NULL)" mkdir "$(OUTDIR)"
+
+CPP=cl.exe
+CPP_PROJ=/nologo /MD /W3 /Zi /O2 /Oy- /I "../../../include" /I "../../../srclib/apr/include" /I "../../../srclib/apr-util/include" /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /Fo"$(INTDIR)\\" /Fd"$(INTDIR)\mod_dav_lock_src" /FD /c
+
+.c{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.c{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+MTL=midl.exe
+MTL_PROJ=/nologo /D "NDEBUG" /mktyplib203 /win32
+RSC=rc.exe
+RSC_PROJ=/l 0x409 /fo"$(INTDIR)\mod_dav_lock.res" /i "../../../include" /i "../../../srclib/apr/include" /d "NDEBUG" /d BIN_NAME="mod_dav_lock.so" /d LONG_NAME="dav_lock_module for Apache"
+BSC32=bscmake.exe
+BSC32_FLAGS=/nologo /o"$(OUTDIR)\mod_dav_lock.bsc"
+BSC32_SBRS= \
+
+LINK32=link.exe
+LINK32_FLAGS=kernel32.lib ws2_32.lib mswsock.lib /nologo /subsystem:windows /dll /incremental:no /pdb:"$(OUTDIR)\mod_dav_lock.pdb" /debug /out:"$(OUTDIR)\mod_dav_lock.so" /implib:"$(OUTDIR)\mod_dav_lock.lib" /base:@..\..\..\os\win32\BaseAddr.ref,mod_dav_lock.so /opt:ref
+LINK32_OBJS= \
+ "$(INTDIR)\locks.obj" \
+ "$(INTDIR)\mod_dav_lock.obj" \
+ "$(INTDIR)\mod_dav_lock.res" \
+ "..\..\..\srclib\apr\Release\libapr-1.lib" \
+ "..\..\..\srclib\apr-util\Release\libaprutil-1.lib" \
+ "..\..\..\Release\libhttpd.lib" \
+ "..\main\Release\mod_dav.lib"
+
+"$(OUTDIR)\mod_dav_lock.so" : "$(OUTDIR)" $(DEF_FILE) $(LINK32_OBJS)
+ $(LINK32) @<<
+ $(LINK32_FLAGS) $(LINK32_OBJS)
+<<
+
+TargetPath=.\Release\mod_dav_lock.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+
+# Begin Custom Macros
+OutDir=.\Release
+# End Custom Macros
+
+"$(DS_POSTBUILD_DEP)" : "$(OUTDIR)\mod_dav_lock.so"
+ if exist .\Release\mod_dav_lock.so.manifest mt.exe -manifest .\Release\mod_dav_lock.so.manifest -outputresource:.\Release\mod_dav_lock.so;2
+ echo Helper for Post-build step > "$(DS_POSTBUILD_DEP)"
+
+!ELSEIF "$(CFG)" == "mod_dav_lock - Win32 Debug"
+
+OUTDIR=.\Debug
+INTDIR=.\Debug
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+# Begin Custom Macros
+OutDir=.\Debug
+# End Custom Macros
+
+!IF "$(RECURSE)" == "0"
+
+ALL : "$(OUTDIR)\mod_dav_lock.so" "$(DS_POSTBUILD_DEP)"
+
+!ELSE
+
+ALL : "mod_dav - Win32 Debug" "libhttpd - Win32 Debug" "libaprutil - Win32 Debug" "libapr - Win32 Debug" "$(OUTDIR)\mod_dav_lock.so" "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+!IF "$(RECURSE)" == "1"
+CLEAN :"libapr - Win32 DebugCLEAN" "libaprutil - Win32 DebugCLEAN" "libhttpd - Win32 DebugCLEAN" "mod_dav - Win32 DebugCLEAN"
+!ELSE
+CLEAN :
+!ENDIF
+ -@erase "$(INTDIR)\locks.obj"
+ -@erase "$(INTDIR)\mod_dav_lock.obj"
+ -@erase "$(INTDIR)\mod_dav_lock.res"
+ -@erase "$(INTDIR)\mod_dav_lock_src.idb"
+ -@erase "$(INTDIR)\mod_dav_lock_src.pdb"
+ -@erase "$(OUTDIR)\mod_dav_lock.exp"
+ -@erase "$(OUTDIR)\mod_dav_lock.lib"
+ -@erase "$(OUTDIR)\mod_dav_lock.pdb"
+ -@erase "$(OUTDIR)\mod_dav_lock.so"
+
+"$(OUTDIR)" :
+ if not exist "$(OUTDIR)/$(NULL)" mkdir "$(OUTDIR)"
+
+CPP=cl.exe
+CPP_PROJ=/nologo /MDd /W3 /Zi /Od /I "../../../include" /I "../../../srclib/apr/include" /I "../../../srclib/apr-util/include" /D "_DEBUG" /D "WIN32" /D "_WINDOWS" /Fo"$(INTDIR)\\" /Fd"$(INTDIR)\mod_dav_lock_src" /FD /EHsc /c
+
+.c{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.c{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+MTL=midl.exe
+MTL_PROJ=/nologo /D "_DEBUG" /mktyplib203 /win32
+RSC=rc.exe
+RSC_PROJ=/l 0x409 /fo"$(INTDIR)\mod_dav_lock.res" /i "../../../include" /i "../../../srclib/apr/include" /d "_DEBUG" /d BIN_NAME="mod_dav_lock.so" /d LONG_NAME="dav_lock_module for Apache"
+BSC32=bscmake.exe
+BSC32_FLAGS=/nologo /o"$(OUTDIR)\mod_dav_lock.bsc"
+BSC32_SBRS= \
+
+LINK32=link.exe
+LINK32_FLAGS=kernel32.lib ws2_32.lib mswsock.lib /nologo /subsystem:windows /dll /incremental:no /pdb:"$(OUTDIR)\mod_dav_lock.pdb" /debug /out:"$(OUTDIR)\mod_dav_lock.so" /implib:"$(OUTDIR)\mod_dav_lock.lib" /base:@..\..\..\os\win32\BaseAddr.ref,mod_dav_lock.so
+LINK32_OBJS= \
+ "$(INTDIR)\locks.obj" \
+ "$(INTDIR)\mod_dav_lock.obj" \
+ "$(INTDIR)\mod_dav_lock.res" \
+ "..\..\..\srclib\apr\Debug\libapr-1.lib" \
+ "..\..\..\srclib\apr-util\Debug\libaprutil-1.lib" \
+ "..\..\..\Debug\libhttpd.lib" \
+ "..\main\Debug\mod_dav.lib"
+
+"$(OUTDIR)\mod_dav_lock.so" : "$(OUTDIR)" $(DEF_FILE) $(LINK32_OBJS)
+ $(LINK32) @<<
+ $(LINK32_FLAGS) $(LINK32_OBJS)
+<<
+
+TargetPath=.\Debug\mod_dav_lock.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+
+# Begin Custom Macros
+OutDir=.\Debug
+# End Custom Macros
+
+"$(DS_POSTBUILD_DEP)" : "$(OUTDIR)\mod_dav_lock.so"
+ if exist .\Debug\mod_dav_lock.so.manifest mt.exe -manifest .\Debug\mod_dav_lock.so.manifest -outputresource:.\Debug\mod_dav_lock.so;2
+ echo Helper for Post-build step > "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+
+!IF "$(NO_EXTERNAL_DEPS)" != "1"
+!IF EXISTS("mod_dav_lock.dep")
+!INCLUDE "mod_dav_lock.dep"
+!ELSE
+!MESSAGE Warning: cannot find "mod_dav_lock.dep"
+!ENDIF
+!ENDIF
+
+
+!IF "$(CFG)" == "mod_dav_lock - Win32 Release" || "$(CFG)" == "mod_dav_lock - Win32 Debug"
+SOURCE=.\locks.c
+
+"$(INTDIR)\locks.obj" : $(SOURCE) "$(INTDIR)"
+
+
+SOURCE=.\mod_dav_lock.c
+
+"$(INTDIR)\mod_dav_lock.obj" : $(SOURCE) "$(INTDIR)"
+
+
+!IF "$(CFG)" == "mod_dav_lock - Win32 Release"
+
+"libapr - Win32 Release" :
+ cd ".\..\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Release"
+ cd "..\..\modules\dav\lock"
+
+"libapr - Win32 ReleaseCLEAN" :
+ cd ".\..\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Release" RECURSE=1 CLEAN
+ cd "..\..\modules\dav\lock"
+
+!ELSEIF "$(CFG)" == "mod_dav_lock - Win32 Debug"
+
+"libapr - Win32 Debug" :
+ cd ".\..\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Debug"
+ cd "..\..\modules\dav\lock"
+
+"libapr - Win32 DebugCLEAN" :
+ cd ".\..\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Debug" RECURSE=1 CLEAN
+ cd "..\..\modules\dav\lock"
+
+!ENDIF
+
+!IF "$(CFG)" == "mod_dav_lock - Win32 Release"
+
+"libaprutil - Win32 Release" :
+ cd ".\..\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Release"
+ cd "..\..\modules\dav\lock"
+
+"libaprutil - Win32 ReleaseCLEAN" :
+ cd ".\..\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Release" RECURSE=1 CLEAN
+ cd "..\..\modules\dav\lock"
+
+!ELSEIF "$(CFG)" == "mod_dav_lock - Win32 Debug"
+
+"libaprutil - Win32 Debug" :
+ cd ".\..\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Debug"
+ cd "..\..\modules\dav\lock"
+
+"libaprutil - Win32 DebugCLEAN" :
+ cd ".\..\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Debug" RECURSE=1 CLEAN
+ cd "..\..\modules\dav\lock"
+
+!ENDIF
+
+!IF "$(CFG)" == "mod_dav_lock - Win32 Release"
+
+"libhttpd - Win32 Release" :
+ cd ".\..\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Release"
+ cd ".\modules\dav\lock"
+
+"libhttpd - Win32 ReleaseCLEAN" :
+ cd ".\..\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Release" RECURSE=1 CLEAN
+ cd ".\modules\dav\lock"
+
+!ELSEIF "$(CFG)" == "mod_dav_lock - Win32 Debug"
+
+"libhttpd - Win32 Debug" :
+ cd ".\..\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Debug"
+ cd ".\modules\dav\lock"
+
+"libhttpd - Win32 DebugCLEAN" :
+ cd ".\..\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Debug" RECURSE=1 CLEAN
+ cd ".\modules\dav\lock"
+
+!ENDIF
+
+!IF "$(CFG)" == "mod_dav_lock - Win32 Release"
+
+"mod_dav - Win32 Release" :
+ cd ".\..\main"
+ $(MAKE) /$(MAKEFLAGS) /F ".\mod_dav.mak" CFG="mod_dav - Win32 Release"
+ cd "..\lock"
+
+"mod_dav - Win32 ReleaseCLEAN" :
+ cd ".\..\main"
+ $(MAKE) /$(MAKEFLAGS) /F ".\mod_dav.mak" CFG="mod_dav - Win32 Release" RECURSE=1 CLEAN
+ cd "..\lock"
+
+!ELSEIF "$(CFG)" == "mod_dav_lock - Win32 Debug"
+
+"mod_dav - Win32 Debug" :
+ cd ".\..\main"
+ $(MAKE) /$(MAKEFLAGS) /F ".\mod_dav.mak" CFG="mod_dav - Win32 Debug"
+ cd "..\lock"
+
+"mod_dav - Win32 DebugCLEAN" :
+ cd ".\..\main"
+ $(MAKE) /$(MAKEFLAGS) /F ".\mod_dav.mak" CFG="mod_dav - Win32 Debug" RECURSE=1 CLEAN
+ cd "..\lock"
+
+!ENDIF
+
+SOURCE=..\..\..\build\win32\httpd.rc
+
+!IF "$(CFG)" == "mod_dav_lock - Win32 Release"
+
+
+"$(INTDIR)\mod_dav_lock.res" : $(SOURCE) "$(INTDIR)"
+ $(RSC) /l 0x409 /fo"$(INTDIR)\mod_dav_lock.res" /i "../../../include" /i "../../../srclib/apr/include" /i "../../../build\win32" /d "NDEBUG" /d BIN_NAME="mod_dav_lock.so" /d LONG_NAME="dav_lock_module for Apache" $(SOURCE)
+
+
+!ELSEIF "$(CFG)" == "mod_dav_lock - Win32 Debug"
+
+
+"$(INTDIR)\mod_dav_lock.res" : $(SOURCE) "$(INTDIR)"
+ $(RSC) /l 0x409 /fo"$(INTDIR)\mod_dav_lock.res" /i "../../../include" /i "../../../srclib/apr/include" /i "../../../build\win32" /d "_DEBUG" /d BIN_NAME="mod_dav_lock.so" /d LONG_NAME="dav_lock_module for Apache" $(SOURCE)
+
+
+!ENDIF
+
+
+!ENDIF
+
diff --git a/modules/dav/main/Makefile.in b/modules/dav/main/Makefile.in
new file mode 100644
index 0000000..7c5c149
--- /dev/null
+++ b/modules/dav/main/Makefile.in
@@ -0,0 +1,3 @@
+# a modules Makefile has no explicit targets -- they will be defined by
+# whatever modules are enabled. just grab special.mk to deal with this.
+include $(top_srcdir)/build/special.mk
diff --git a/modules/dav/main/NWGNUmakefile b/modules/dav/main/NWGNUmakefile
new file mode 100644
index 0000000..bc94a22
--- /dev/null
+++ b/modules/dav/main/NWGNUmakefile
@@ -0,0 +1,268 @@
+#
+# Declare the sub-directories to be built here
+#
+
+SUBDIRS = \
+ $(EOLIST)
+
+#
+# Get the 'head' of the build environment. This includes default targets and
+# paths to tools
+#
+
+include $(AP_WORK)/build/NWGNUhead.inc
+
+#
+# build this level's files
+#
+# Make sure all needed macro's are defined
+#
+
+#
+# These directories will be at the beginning of the include list, followed by
+# INCDIRS
+#
+XINCDIRS += \
+ $(APR)/include \
+ $(APRUTIL)/include \
+ $(AP_WORK)/include \
+ $(AP_WORK)/server/mpm/netware \
+ $(NWOS) \
+ $(EOLIST)
+
+#
+# These flags will come after CFLAGS
+#
+XCFLAGS += \
+ $(EOLIST)
+
+#
+# These defines will come after DEFINES
+#
+XDEFINES += \
+ $(EOLIST)
+
+#
+# These flags will be added to the link.opt file
+#
+XLFLAGS += \
+ $(EOLIST)
+
+#
+# These values will be appended to the correct variables based on the value of
+# RELEASE
+#
+ifeq "$(RELEASE)" "debug"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+ifeq "$(RELEASE)" "noopt"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+ifeq "$(RELEASE)" "release"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+#
+# These are used by the link target if an NLM is being generated
+# This is used by the link 'name' directive to name the nlm. If left blank
+# TARGET_nlm (see below) will be used.
+#
+NLM_NAME = mod_dav
+
+#
+# This is used by the link '-desc ' directive.
+# If left blank, NLM_NAME will be used.
+#
+NLM_DESCRIPTION = Apache $(VERSION_STR) DAV module
+
+#
+# This is used by the '-threadname' directive. If left blank,
+# NLM_NAME Thread will be used.
+#
+NLM_THREAD_NAME = $(NLM_NAME) Thread
+
+#
+# If this is specified, it will override VERSION value in
+# $(AP_WORK)/build/NWGNUenvironment.inc
+#
+NLM_VERSION =
+
+#
+# If this is specified, it will override the default of 64K
+#
+NLM_STACK_SIZE = 65536
+
+
+#
+# If this is specified it will be used by the link '-entry' directive
+#
+NLM_ENTRY_SYM =
+
+#
+# If this is specified it will be used by the link '-exit' directive
+#
+NLM_EXIT_SYM =
+
+#
+# If this is specified it will be used by the link '-check' directive
+#
+NLM_CHECK_SYM =
+
+#
+# If this is specified it will be used by the link '-flags' directive
+#
+NLM_FLAGS =
+
+#
+# If this is specified it will be linked in with the XDCData option in the def
+# file instead of the default of $(NWOS)/apache.xdc. XDCData can be disabled
+# by setting APACHE_UNIPROC in the environment
+#
+XDCDATA =
+
+#
+# Declare all target files (you must add your files here)
+#
+
+#
+# If there is an NLM target, put it here
+#
+TARGET_nlm = \
+ $(OBJDIR)/$(NLM_NAME).nlm \
+ $(EOLIST)
+
+#
+# If there is an LIB target, put it here
+#
+TARGET_lib = \
+ $(EOLIST)
+
+#
+# These are the OBJ files needed to create the NLM target above.
+# Paths must all use the '/' character
+#
+FILES_nlm_objs = \
+ $(OBJDIR)/mod_dav.o \
+ $(OBJDIR)/liveprop.o \
+ $(OBJDIR)/props.o \
+ $(OBJDIR)/providers.o \
+ $(OBJDIR)/std_liveprop.o \
+ $(OBJDIR)/util.o \
+ $(OBJDIR)/util_lock.o \
+ $(EOLIST)
+
+#
+# These are the LIB files needed to create the NLM target above.
+# These will be added as a library command in the link.opt file.
+#
+FILES_nlm_libs = \
+ $(PRELUDE) \
+ $(EOLIST)
+
+#
+# These are the modules that the above NLM target depends on to load.
+# These will be added as a module command in the link.opt file.
+#
+FILES_nlm_modules = \
+ Apache2 \
+ Libc \
+ $(EOLIST)
+
+#
+# If the nlm has a msg file, put it's path here
+#
+FILE_nlm_msg =
+
+#
+# If the nlm has a hlp file put it's path here
+#
+FILE_nlm_hlp =
+
+#
+# If this is specified, it will override $(NWOS)\copyright.txt.
+#
+FILE_nlm_copyright =
+
+#
+# Any additional imports go here
+#
+FILES_nlm_Ximports = \
+ @libc.imp \
+ @aprlib.imp \
+ @httpd.imp \
+ $(EOLIST)
+
+#
+# Any symbols exported to here
+#
+FILES_nlm_exports = \
+ dav_module \
+ @dav.imp \
+ $(EOLIST)
+
+#
+# These are the OBJ files needed to create the LIB target above.
+# Paths must all use the '/' character
+#
+FILES_lib_objs = \
+ $(EOLIST)
+
+#
+# implement targets and dependancies (leave this section alone)
+#
+
+libs :: $(OBJDIR) $(TARGET_lib)
+
+nlms :: libs $(TARGET_nlm)
+
+#
+# Updated this target to create necessary directories and copy files to the
+# correct place. (See $(AP_WORK)/build/NWGNUhead.inc for examples)
+#
+install :: nlms FORCE
+ $(call COPY,$(OBJDIR)/*.nlm, $(INSTALLBASE)/modules/)
+
+#
+# Any specialized rules here
+#
+
+#
+# Include the 'tail' makefile that has targets that depend on variables defined
+# in this makefile
+#
+
+include $(APBUILD)/NWGNUtail.inc
+
+
diff --git a/modules/dav/main/config5.m4 b/modules/dav/main/config5.m4
new file mode 100644
index 0000000..28823c7
--- /dev/null
+++ b/modules/dav/main/config5.m4
@@ -0,0 +1,21 @@
+dnl modules enabled in this directory by default
+
+APACHE_MODPATH_INIT(dav/main)
+
+dav_objects="mod_dav.lo props.lo util.lo util_lock.lo liveprop.lo providers.lo std_liveprop.lo"
+
+if test "$enable_http" = "no"; then
+ dav_enable=no
+else
+ dav_enable=most
+fi
+
+APACHE_MODULE(dav, WebDAV protocol handling. --enable-dav also enables mod_dav_fs, $dav_objects, , $dav_enable)
+
+if test "$dav_enable" != "no" -o "$enable_dav" != "no"; then
+ apache_need_expat=yes
+fi
+
+APR_ADDTO(INCLUDES, [-I\$(top_srcdir)/$modpath_current])
+
+APACHE_MODPATH_FINISH
diff --git a/modules/dav/main/liveprop.c b/modules/dav/main/liveprop.c
new file mode 100644
index 0000000..d170d75
--- /dev/null
+++ b/modules/dav/main/liveprop.c
@@ -0,0 +1,140 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "apr_pools.h"
+#include "apr_hash.h"
+#include "apr_errno.h"
+#include "apr_strings.h"
+#include "util_xml.h" /* for apr_text_header */
+#include "mod_dav.h"
+
+
+static apr_hash_t *dav_liveprop_uris = NULL;
+static long dav_liveprop_count = 0;
+
+
+static apr_status_t dav_cleanup_liveprops(void *ctx)
+{
+ dav_liveprop_uris = NULL;
+ dav_liveprop_count = 0;
+ return APR_SUCCESS;
+}
+
+static void dav_register_liveprop_namespace(apr_pool_t *p, const char *uri)
+{
+ long value;
+
+ if (dav_liveprop_uris == NULL) {
+ dav_liveprop_uris = apr_hash_make(p);
+ apr_pool_cleanup_register(p, NULL, dav_cleanup_liveprops, apr_pool_cleanup_null);
+ }
+
+ value = (long)apr_hash_get(dav_liveprop_uris, uri, APR_HASH_KEY_STRING);
+ if (value != 0) {
+ /* already registered */
+ return;
+ }
+
+ /* start at 1, and count up */
+ apr_hash_set(dav_liveprop_uris, uri, APR_HASH_KEY_STRING,
+ (void *)++dav_liveprop_count);
+}
+
+DAV_DECLARE(long) dav_get_liveprop_ns_index(const char *uri)
+{
+ return (long)apr_hash_get(dav_liveprop_uris, uri, APR_HASH_KEY_STRING);
+}
+
+DAV_DECLARE(long) dav_get_liveprop_ns_count(void)
+{
+ return dav_liveprop_count;
+}
+
+DAV_DECLARE(void) dav_add_all_liveprop_xmlns(apr_pool_t *p,
+ apr_text_header *phdr)
+{
+ apr_hash_index_t *idx = apr_hash_first(p, dav_liveprop_uris);
+
+ for ( ; idx != NULL; idx = apr_hash_next(idx) ) {
+ const void *key;
+ void *val;
+ const char *s;
+
+ apr_hash_this(idx, &key, NULL, &val);
+
+ s = apr_psprintf(p, " xmlns:lp%ld=\"%s\"", (long)val, (const char *)key);
+ apr_text_append(p, phdr, s);
+ }
+}
+
+DAV_DECLARE(int) dav_do_find_liveprop(const char *ns_uri, const char *name,
+ const dav_liveprop_group *group,
+ const dav_hooks_liveprop **hooks)
+{
+ const char * const *uris = group->namespace_uris;
+ const dav_liveprop_spec *scan;
+ int ns;
+
+ /* first: locate the namespace in the namespace table */
+ for (ns = 0; uris[ns] != NULL; ++ns)
+ if (strcmp(ns_uri, uris[ns]) == 0)
+ break;
+ if (uris[ns] == NULL) {
+ /* not our property (the namespace matched none of ours) */
+ return 0;
+ }
+
+ /* second: look for the property in the liveprop specs */
+ for (scan = group->specs; scan->name != NULL; ++scan)
+ if (ns == scan->ns && strcmp(name, scan->name) == 0) {
+ *hooks = group->hooks;
+ return scan->propid;
+ }
+
+ /* not our property (same namespace, but no matching prop name) */
+ return 0;
+}
+
+DAV_DECLARE(long) dav_get_liveprop_info(int propid,
+ const dav_liveprop_group *group,
+ const dav_liveprop_spec **info)
+{
+ const dav_liveprop_spec *scan;
+
+ for (scan = group->specs; scan->name != NULL; ++scan) {
+ if (scan->propid == propid) {
+ *info = scan;
+
+ /* map the provider-local NS into a global NS index */
+ return dav_get_liveprop_ns_index(group->namespace_uris[scan->ns]);
+ }
+ }
+
+ /* assert: should not reach this point */
+ *info = NULL;
+ return 0;
+}
+
+DAV_DECLARE(void) dav_register_liveprop_group(apr_pool_t *p,
+ const dav_liveprop_group *group)
+{
+ /* register the namespace URIs */
+ const char * const * uris = group->namespace_uris;
+
+ for ( ; *uris != NULL; ++uris) {
+ dav_register_liveprop_namespace(p, *uris);
+ }
+}
diff --git a/modules/dav/main/mod_dav.c b/modules/dav/main/mod_dav.c
new file mode 100644
index 0000000..84012f2
--- /dev/null
+++ b/modules/dav/main/mod_dav.c
@@ -0,0 +1,4946 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/*
+ * DAV extension module for Apache 2.0.*
+ *
+ * This module is repository-independent. It depends on hooks provided by a
+ * repository implementation.
+ *
+ * APACHE ISSUES:
+ * - within a DAV hierarchy, if an unknown method is used and we default
+ * to Apache's implementation, it sends back an OPTIONS with the wrong
+ * set of methods -- there is NO HOOK for us.
+ * therefore: we need to manually handle the HTTP_METHOD_NOT_ALLOWED
+ * and HTTP_NOT_IMPLEMENTED responses (not ap_send_error_response).
+ * - process_mkcol_body() had to dup code from ap_setup_client_block().
+ * - it would be nice to get status lines from Apache for arbitrary
+ * status codes
+ * - it would be nice to be able to extend Apache's set of response
+ * codes so that it doesn't return 500 when an unknown code is placed
+ * into r->status.
+ * - http_vhost functions should apply "const" to their params
+ *
+ * DESIGN NOTES:
+ * - For PROPFIND, we batch up the entire response in memory before
+ * sending it. We may want to reorganize around sending the information
+ * as we suck it in from the propdb. Alternatively, we should at least
+ * generate a total Content-Length if we're going to buffer in memory
+ * so that we can keep the connection open.
+ */
+
+#include "apr_strings.h"
+#include "apr_lib.h" /* for apr_is* */
+
+#define APR_WANT_STRFUNC
+#include "apr_want.h"
+
+#include "httpd.h"
+#include "http_config.h"
+#include "http_core.h"
+#include "http_log.h"
+#include "http_main.h"
+#include "http_protocol.h"
+#include "http_request.h"
+#include "util_script.h"
+
+#include "mod_dav.h"
+
+#include "ap_provider.h"
+
+
+/* ### what is the best way to set this? */
+#define DAV_DEFAULT_PROVIDER "filesystem"
+
+/* used to denote that mod_dav will be handling this request */
+#define DAV_HANDLER_NAME "dav-handler"
+
+APLOG_USE_MODULE(dav);
+
+enum {
+ DAV_ENABLED_UNSET = 0,
+ DAV_ENABLED_OFF,
+ DAV_ENABLED_ON
+};
+
+/* per-dir configuration */
+typedef struct {
+ const char *provider_name;
+ const dav_provider *provider;
+ const char *dir;
+ int locktimeout;
+ int allow_depthinfinity;
+
+} dav_dir_conf;
+
+/* per-server configuration */
+typedef struct {
+ int unused;
+
+} dav_server_conf;
+
+#define DAV_INHERIT_VALUE(parent, child, field) \
+ ((child)->field ? (child)->field : (parent)->field)
+
+
+/* forward-declare for use in configuration lookup */
+extern module DAV_DECLARE_DATA dav_module;
+
+/* DAV methods */
+enum {
+ DAV_M_BIND = 0,
+ DAV_M_SEARCH,
+ DAV_M_LAST
+};
+static int dav_methods[DAV_M_LAST];
+
+
+static int dav_init_handler(apr_pool_t *p, apr_pool_t *plog, apr_pool_t *ptemp,
+ server_rec *s)
+{
+ /* DBG0("dav_init_handler"); */
+
+ /* Register DAV methods */
+ dav_methods[DAV_M_BIND] = ap_method_register(p, "BIND");
+ dav_methods[DAV_M_SEARCH] = ap_method_register(p, "SEARCH");
+
+ return OK;
+}
+
+static void *dav_create_server_config(apr_pool_t *p, server_rec *s)
+{
+ dav_server_conf *newconf;
+
+ newconf = (dav_server_conf *)apr_pcalloc(p, sizeof(*newconf));
+
+ /* ### this isn't used at the moment... */
+
+ return newconf;
+}
+
+static void *dav_merge_server_config(apr_pool_t *p, void *base, void *overrides)
+{
+#if 0
+ dav_server_conf *child = overrides;
+#endif
+ dav_server_conf *newconf;
+
+ newconf = (dav_server_conf *)apr_pcalloc(p, sizeof(*newconf));
+
+ /* ### nothing to merge right now... */
+
+ return newconf;
+}
+
+static void *dav_create_dir_config(apr_pool_t *p, char *dir)
+{
+ /* NOTE: dir==NULL creates the default per-dir config */
+
+ dav_dir_conf *conf;
+
+ conf = (dav_dir_conf *)apr_pcalloc(p, sizeof(*conf));
+
+ /* clean up the directory to remove any trailing slash */
+ if (dir != NULL) {
+ char *d;
+ apr_size_t l;
+
+ l = strlen(dir);
+ d = apr_pstrmemdup(p, dir, l);
+ if (l > 1 && d[l - 1] == '/')
+ d[l - 1] = '\0';
+ conf->dir = d;
+ }
+
+ return conf;
+}
+
+static void *dav_merge_dir_config(apr_pool_t *p, void *base, void *overrides)
+{
+ dav_dir_conf *parent = base;
+ dav_dir_conf *child = overrides;
+ dav_dir_conf *newconf = (dav_dir_conf *)apr_pcalloc(p, sizeof(*newconf));
+
+ /* DBG3("dav_merge_dir_config: new=%08lx base=%08lx overrides=%08lx",
+ (long)newconf, (long)base, (long)overrides); */
+
+ newconf->provider_name = DAV_INHERIT_VALUE(parent, child, provider_name);
+ newconf->provider = DAV_INHERIT_VALUE(parent, child, provider);
+ if (parent->provider_name != NULL) {
+ if (child->provider_name == NULL) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, 0, NULL, APLOGNO(00578)
+ "\"DAV Off\" cannot be used to turn off a subtree "
+ "of a DAV-enabled location.");
+ }
+ else if (strcasecmp(child->provider_name,
+ parent->provider_name) != 0) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, 0, NULL, APLOGNO(00579)
+ "A subtree cannot specify a different DAV provider "
+ "than its parent.");
+ }
+ }
+
+ newconf->locktimeout = DAV_INHERIT_VALUE(parent, child, locktimeout);
+ newconf->dir = DAV_INHERIT_VALUE(parent, child, dir);
+ newconf->allow_depthinfinity = DAV_INHERIT_VALUE(parent, child,
+ allow_depthinfinity);
+
+ return newconf;
+}
+
+DAV_DECLARE(const char *) dav_get_provider_name(request_rec *r)
+{
+ dav_dir_conf *conf = ap_get_module_config(r->per_dir_config, &dav_module);
+ return conf ? conf->provider_name : NULL;
+}
+
+static const dav_provider *dav_get_provider(request_rec *r)
+{
+ dav_dir_conf *conf;
+
+ conf = ap_get_module_config(r->per_dir_config, &dav_module);
+ /* assert: conf->provider_name != NULL
+ (otherwise, DAV is disabled, and we wouldn't be here) */
+
+ /* assert: conf->provider != NULL
+ (checked when conf->provider_name is set) */
+ return conf->provider;
+}
+
+DAV_DECLARE(const dav_hooks_locks *) dav_get_lock_hooks(request_rec *r)
+{
+ return dav_get_provider(r)->locks;
+}
+
+DAV_DECLARE(const dav_hooks_propdb *) dav_get_propdb_hooks(request_rec *r)
+{
+ return dav_get_provider(r)->propdb;
+}
+
+DAV_DECLARE(const dav_hooks_vsn *) dav_get_vsn_hooks(request_rec *r)
+{
+ return dav_get_provider(r)->vsn;
+}
+
+DAV_DECLARE(const dav_hooks_binding *) dav_get_binding_hooks(request_rec *r)
+{
+ return dav_get_provider(r)->binding;
+}
+
+DAV_DECLARE(const dav_hooks_search *) dav_get_search_hooks(request_rec *r)
+{
+ return dav_get_provider(r)->search;
+}
+
+/*
+ * Command handler for the DAV directive, which is TAKE1.
+ */
+static const char *dav_cmd_dav(cmd_parms *cmd, void *config, const char *arg1)
+{
+ dav_dir_conf *conf = (dav_dir_conf *)config;
+
+ if (strcasecmp(arg1, "on") == 0) {
+ conf->provider_name = DAV_DEFAULT_PROVIDER;
+ }
+ else if (strcasecmp(arg1, "off") == 0) {
+ conf->provider_name = NULL;
+ conf->provider = NULL;
+ }
+ else {
+ conf->provider_name = arg1;
+ }
+
+ if (conf->provider_name != NULL) {
+ /* lookup and cache the actual provider now */
+ conf->provider = dav_lookup_provider(conf->provider_name);
+
+ if (conf->provider == NULL) {
+ /* by the time they use it, the provider should be loaded and
+ registered with us. */
+ return apr_psprintf(cmd->pool,
+ "Unknown DAV provider: %s",
+ conf->provider_name);
+ }
+ }
+
+ return NULL;
+}
+
+/*
+ * Command handler for the DAVDepthInfinity directive, which is FLAG.
+ */
+static const char *dav_cmd_davdepthinfinity(cmd_parms *cmd, void *config,
+ int arg)
+{
+ dav_dir_conf *conf = (dav_dir_conf *)config;
+
+ if (arg)
+ conf->allow_depthinfinity = DAV_ENABLED_ON;
+ else
+ conf->allow_depthinfinity = DAV_ENABLED_OFF;
+ return NULL;
+}
+
+/*
+ * Command handler for DAVMinTimeout directive, which is TAKE1
+ */
+static const char *dav_cmd_davmintimeout(cmd_parms *cmd, void *config,
+ const char *arg1)
+{
+ dav_dir_conf *conf = (dav_dir_conf *)config;
+
+ conf->locktimeout = atoi(arg1);
+ if (conf->locktimeout < 0)
+ return "DAVMinTimeout requires a non-negative integer.";
+
+ return NULL;
+}
+
+/*
+** dav_error_response()
+**
+** Send a nice response back to the user. In most cases, Apache doesn't
+** allow us to provide details in the body about what happened. This
+** function allows us to completely specify the response body.
+**
+** ### this function is not logging any errors! (e.g. the body)
+*/
+static int dav_error_response(request_rec *r, int status, const char *body)
+{
+ r->status = status;
+ r->status_line = ap_get_status_line(status);
+
+ ap_set_content_type(r, "text/html; charset=ISO-8859-1");
+
+ /* begin the response now... */
+ ap_rvputs(r,
+ DAV_RESPONSE_BODY_1,
+ r->status_line,
+ DAV_RESPONSE_BODY_2,
+ &r->status_line[4],
+ DAV_RESPONSE_BODY_3,
+ body,
+ DAV_RESPONSE_BODY_4,
+ ap_psignature("<hr />\n", r),
+ DAV_RESPONSE_BODY_5,
+ NULL);
+
+ /* the response has been sent. */
+ /*
+ * ### Use of DONE obviates logging..!
+ */
+ return DONE;
+}
+
+
+/*
+ * Send a "standardized" error response based on the error's namespace & tag
+ */
+static int dav_error_response_tag(request_rec *r,
+ dav_error *err)
+{
+ r->status = err->status;
+
+ ap_set_content_type(r, DAV_XML_CONTENT_TYPE);
+
+ ap_rputs(DAV_XML_HEADER DEBUG_CR
+ "<D:error xmlns:D=\"DAV:\"", r);
+
+ if (err->desc != NULL) {
+ /* ### should move this namespace somewhere (with the others!) */
+ ap_rputs(" xmlns:m=\"http://apache.org/dav/xmlns\"", r);
+ }
+
+ if (err->childtags) {
+ if (err->namespace != NULL) {
+ ap_rprintf(r,
+ " xmlns:C=\"%s\">" DEBUG_CR
+ "<C:%s>%s</C:%s>" DEBUG_CR,
+ err->namespace,
+ err->tagname, err->childtags, err->tagname);
+ }
+ else {
+ ap_rprintf(r,
+ ">" DEBUG_CR
+ "<D:%s>%s</D:%s>" DEBUG_CR,
+ err->tagname, err->childtags, err->tagname);
+ }
+ }
+ else {
+ if (err->namespace != NULL) {
+ ap_rprintf(r,
+ " xmlns:C=\"%s\">" DEBUG_CR
+ "<C:%s/>" DEBUG_CR,
+ err->namespace, err->tagname);
+ }
+ else {
+ ap_rprintf(r,
+ ">" DEBUG_CR
+ "<D:%s/>" DEBUG_CR, err->tagname);
+ }
+ }
+
+ /* here's our mod_dav specific tag: */
+ if (err->desc != NULL) {
+ ap_rprintf(r,
+ "<m:human-readable errcode=\"%d\">" DEBUG_CR
+ "%s" DEBUG_CR
+ "</m:human-readable>" DEBUG_CR,
+ err->error_id,
+ apr_xml_quote_string(r->pool, err->desc, 0));
+ }
+
+ ap_rputs("</D:error>" DEBUG_CR, r);
+
+ /* the response has been sent. */
+ /*
+ * ### Use of DONE obviates logging..!
+ */
+ return DONE;
+}
+
+
+/*
+ * Apache's URI escaping does not replace '&' since that is a valid character
+ * in a URI (to form a query section). We must explicitly handle it so that
+ * we can embed the URI into an XML document.
+ */
+static const char *dav_xml_escape_uri(apr_pool_t *p, const char *uri)
+{
+ const char *e_uri = ap_escape_uri(p, uri);
+
+ /* check the easy case... */
+ if (ap_strchr_c(e_uri, '&') == NULL)
+ return e_uri;
+
+ /* there was a '&', so more work is needed... sigh. */
+
+ /*
+ * Note: this is a teeny bit of overkill since we know there are no
+ * '<' or '>' characters, but who cares.
+ */
+ return apr_xml_quote_string(p, e_uri, 0);
+}
+
+
+/* Write a complete RESPONSE object out as a <DAV:repsonse> xml
+ element. Data is sent into brigade BB, which is auto-flushed into
+ the output filter stack for request R. Use POOL for any temporary
+ allocations.
+
+ [Presumably the <multistatus> tag has already been written; this
+ routine is shared by dav_send_multistatus and dav_stream_response.]
+*/
+DAV_DECLARE(void) dav_send_one_response(dav_response *response,
+ apr_bucket_brigade *bb,
+ request_rec *r,
+ apr_pool_t *pool)
+{
+ apr_text *t = NULL;
+
+ if (response->propresult.xmlns == NULL) {
+ ap_fputs(r->output_filters, bb, "<D:response>");
+ }
+ else {
+ ap_fputs(r->output_filters, bb, "<D:response");
+ for (t = response->propresult.xmlns; t; t = t->next) {
+ ap_fputs(r->output_filters, bb, t->text);
+ }
+ ap_fputc(r->output_filters, bb, '>');
+ }
+
+ ap_fputstrs(r->output_filters, bb,
+ DEBUG_CR "<D:href>",
+ dav_xml_escape_uri(pool, response->href),
+ "</D:href>" DEBUG_CR,
+ NULL);
+
+ if (response->propresult.propstats == NULL) {
+ /* use the Status-Line text from Apache. Note, this will
+ * default to 500 Internal Server Error if first->status
+ * is not a known (or valid) status code.
+ */
+ ap_fputstrs(r->output_filters, bb,
+ "<D:status>HTTP/1.1 ",
+ ap_get_status_line(response->status),
+ "</D:status>" DEBUG_CR,
+ NULL);
+ }
+ else {
+ /* assume this includes <propstat> and is quoted properly */
+ for (t = response->propresult.propstats; t; t = t->next) {
+ ap_fputs(r->output_filters, bb, t->text);
+ }
+ }
+
+ if (response->desc != NULL) {
+ /*
+ * We supply the description, so we know it doesn't have to
+ * have any escaping/encoding applied to it.
+ */
+ ap_fputstrs(r->output_filters, bb,
+ "<D:responsedescription>",
+ response->desc,
+ "</D:responsedescription>" DEBUG_CR,
+ NULL);
+ }
+
+ ap_fputs(r->output_filters, bb, "</D:response>" DEBUG_CR);
+}
+
+
+/* Factorized helper function: prep request_rec R for a multistatus
+ response and write <multistatus> tag into BB, destined for
+ R->output_filters. Use xml NAMESPACES in initial tag, if
+ non-NULL. */
+DAV_DECLARE(void) dav_begin_multistatus(apr_bucket_brigade *bb,
+ request_rec *r, int status,
+ apr_array_header_t *namespaces)
+{
+ /* Set the correct status and Content-Type */
+ r->status = status;
+ ap_set_content_type(r, DAV_XML_CONTENT_TYPE);
+
+ /* Send the headers and actual multistatus response now... */
+ ap_fputs(r->output_filters, bb, DAV_XML_HEADER DEBUG_CR
+ "<D:multistatus xmlns:D=\"DAV:\"");
+
+ if (namespaces != NULL) {
+ int i;
+
+ for (i = namespaces->nelts; i--; ) {
+ ap_fprintf(r->output_filters, bb, " xmlns:ns%d=\"%s\"", i,
+ APR_XML_GET_URI_ITEM(namespaces, i));
+ }
+ }
+
+ ap_fputs(r->output_filters, bb, ">" DEBUG_CR);
+}
+
+/* Finish a multistatus response started by dav_begin_multistatus: */
+DAV_DECLARE(apr_status_t) dav_finish_multistatus(request_rec *r,
+ apr_bucket_brigade *bb)
+{
+ apr_bucket *b;
+
+ ap_fputs(r->output_filters, bb, "</D:multistatus>" DEBUG_CR);
+
+ /* indicate the end of the response body */
+ b = apr_bucket_eos_create(r->connection->bucket_alloc);
+ APR_BRIGADE_INSERT_TAIL(bb, b);
+
+ /* deliver whatever might be remaining in the brigade */
+ return ap_pass_brigade(r->output_filters, bb);
+}
+
+DAV_DECLARE(void) dav_send_multistatus(request_rec *r, int status,
+ dav_response *first,
+ apr_array_header_t *namespaces)
+{
+ apr_pool_t *subpool;
+ apr_bucket_brigade *bb = apr_brigade_create(r->pool,
+ r->connection->bucket_alloc);
+
+ dav_begin_multistatus(bb, r, status, namespaces);
+
+ apr_pool_create(&subpool, r->pool);
+
+ for (; first != NULL; first = first->next) {
+ apr_pool_clear(subpool);
+ dav_send_one_response(first, bb, r, subpool);
+ }
+ apr_pool_destroy(subpool);
+
+ dav_finish_multistatus(r, bb);
+}
+
+/*
+ * dav_log_err()
+ *
+ * Write error information to the log.
+ */
+static void dav_log_err(request_rec *r, dav_error *err, int level)
+{
+ dav_error *errscan;
+
+ /* Log the errors */
+ /* ### should have a directive to log the first or all */
+ for (errscan = err; errscan != NULL; errscan = errscan->prev) {
+ if (errscan->desc == NULL)
+ continue;
+
+ /* Intentional no APLOGNO */
+ ap_log_rerror(APLOG_MARK, level, errscan->aprerr, r, "%s [%d, #%d]",
+ errscan->desc, errscan->status, errscan->error_id);
+ }
+}
+
+/*
+ * dav_handle_err()
+ *
+ * Handle the standard error processing. <err> must be non-NULL.
+ *
+ * <response> is set by the following:
+ * - dav_validate_request()
+ * - dav_add_lock()
+ * - repos_hooks->remove_resource
+ * - repos_hooks->move_resource
+ * - repos_hooks->copy_resource
+ * - vsn_hooks->update
+ */
+DAV_DECLARE(int) dav_handle_err(request_rec *r, dav_error *err,
+ dav_response *response)
+{
+ /* log the errors */
+ dav_log_err(r, err, APLOG_ERR);
+
+ if (!ap_is_HTTP_VALID_RESPONSE(err->status)) {
+ /* we have responded already */
+ return AP_FILTER_ERROR;
+ }
+
+ if (response == NULL) {
+ dav_error *stackerr = err;
+
+ /* our error messages are safe; tell Apache this */
+ apr_table_setn(r->notes, "verbose-error-to", "*");
+
+ /* Didn't get a multistatus response passed in, but we still
+ might be able to generate a standard <D:error> response.
+ Search the error stack for an errortag. */
+ while (stackerr != NULL && stackerr->tagname == NULL)
+ stackerr = stackerr->prev;
+
+ if (stackerr != NULL && stackerr->tagname != NULL)
+ return dav_error_response_tag(r, stackerr);
+
+ return err->status;
+ }
+
+ /* send the multistatus and tell Apache the request/response is DONE. */
+ dav_send_multistatus(r, err->status, response, NULL);
+ return DONE;
+}
+
+/* handy function for return values of methods that (may) create things.
+ * locn if provided is assumed to be escaped. */
+static int dav_created(request_rec *r, const char *locn, const char *what,
+ int replaced)
+{
+ const char *body;
+
+ if (locn == NULL) {
+ locn = ap_escape_uri(r->pool, r->uri);
+ }
+
+ /* did the target resource already exist? */
+ if (replaced) {
+ /* Apache will supply a default message */
+ return HTTP_NO_CONTENT;
+ }
+
+ /* Per HTTP/1.1, S10.2.2: add a Location header to contain the
+ * URI that was created. */
+
+ /* Convert locn to an absolute URI, and return in Location header */
+ apr_table_setn(r->headers_out, "Location", ap_construct_url(r->pool, locn, r));
+
+ /* ### insert an ETag header? see HTTP/1.1 S10.2.2 */
+
+ /* Apache doesn't allow us to set a variable body for HTTP_CREATED, so
+ * we must manufacture the entire response. */
+ body = apr_psprintf(r->pool, "%s %s has been created.",
+ what, ap_escape_html(r->pool, locn));
+ return dav_error_response(r, HTTP_CREATED, body);
+}
+
+/* ### move to dav_util? */
+DAV_DECLARE(int) dav_get_depth(request_rec *r, int def_depth)
+{
+ const char *depth = apr_table_get(r->headers_in, "Depth");
+
+ if (depth == NULL) {
+ return def_depth;
+ }
+
+ if (strcasecmp(depth, "infinity") == 0) {
+ return DAV_INFINITY;
+ }
+ else if (strcmp(depth, "0") == 0) {
+ return 0;
+ }
+ else if (strcmp(depth, "1") == 0) {
+ return 1;
+ }
+
+ /* The caller will return an HTTP_BAD_REQUEST. This will augment the
+ * default message that Apache provides. */
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(00580)
+ "An invalid Depth header was specified.");
+ return -1;
+}
+
+static int dav_get_overwrite(request_rec *r)
+{
+ const char *overwrite = apr_table_get(r->headers_in, "Overwrite");
+
+ if (overwrite == NULL) {
+ return 1; /* default is "T" */
+ }
+
+ if ((*overwrite == 'F' || *overwrite == 'f') && overwrite[1] == '\0') {
+ return 0;
+ }
+
+ if ((*overwrite == 'T' || *overwrite == 't') && overwrite[1] == '\0') {
+ return 1;
+ }
+
+ /* The caller will return an HTTP_BAD_REQUEST. This will augment the
+ * default message that Apache provides. */
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(00581)
+ "An invalid Overwrite header was specified.");
+ return -1;
+}
+
+/* resolve a request URI to a resource descriptor.
+ *
+ * If label_allowed != 0, then allow the request target to be altered by
+ * a Label: header.
+ *
+ * If use_checked_in is true, then the repository provider should return
+ * the resource identified by the DAV:checked-in property of the resource
+ * identified by the Request-URI.
+ */
+static dav_error *dav_get_resource(request_rec *r, int label_allowed,
+ int use_checked_in, dav_resource **res_p)
+{
+ dav_dir_conf *conf;
+ const char *label = NULL;
+ dav_error *err;
+
+ /* if the request target can be overridden, get any target selector */
+ if (label_allowed) {
+ label = apr_table_get(r->headers_in, "label");
+ }
+
+ conf = ap_get_module_config(r->per_dir_config, &dav_module);
+ /* assert: conf->provider != NULL */
+ if (conf->provider == NULL) {
+ return dav_new_error(r->pool, HTTP_METHOD_NOT_ALLOWED, 0, 0,
+ apr_psprintf(r->pool,
+ "DAV not enabled for %s",
+ ap_escape_html(r->pool, r->uri)));
+ }
+
+ /* resolve the resource */
+ err = (*conf->provider->repos->get_resource)(r, conf->dir,
+ label, use_checked_in,
+ res_p);
+ if (err != NULL) {
+ err = dav_push_error(r->pool, err->status, 0,
+ "Could not fetch resource information.", err);
+ return err;
+ }
+
+ /* Note: this shouldn't happen, but just be sure... */
+ if (*res_p == NULL) {
+ /* ### maybe use HTTP_INTERNAL_SERVER_ERROR */
+ return dav_new_error(r->pool, HTTP_NOT_FOUND, 0, 0,
+ apr_psprintf(r->pool,
+ "The provider did not define a "
+ "resource for %s.",
+ ap_escape_html(r->pool, r->uri)));
+ }
+
+ /* ### hmm. this doesn't feel like the right place or thing to do */
+ /* if there were any input headers requiring a Vary header in the response,
+ * add it now */
+ dav_add_vary_header(r, r, *res_p);
+
+ return NULL;
+}
+
+static dav_error * dav_open_lockdb(request_rec *r, int ro, dav_lockdb **lockdb)
+{
+ const dav_hooks_locks *hooks = DAV_GET_HOOKS_LOCKS(r);
+
+ if (hooks == NULL) {
+ *lockdb = NULL;
+ return NULL;
+ }
+
+ /* open the thing lazily */
+ return (*hooks->open_lockdb)(r, ro, 0, lockdb);
+}
+
+/**
+ * @return 1 if valid content-range,
+ * 0 if no content-range,
+ * -1 if malformed content-range
+ */
+static int dav_parse_range(request_rec *r,
+ apr_off_t *range_start, apr_off_t *range_end)
+{
+ const char *range_c;
+ char *range;
+ char *dash;
+ char *slash;
+ char *errp;
+
+ range_c = apr_table_get(r->headers_in, "content-range");
+ if (range_c == NULL)
+ return 0;
+
+ range = apr_pstrdup(r->pool, range_c);
+ if (strncasecmp(range, "bytes ", 6) != 0
+ || (dash = ap_strchr(range, '-')) == NULL
+ || (slash = ap_strchr(range, '/')) == NULL) {
+ /* malformed header */
+ return -1;
+ }
+
+ *dash++ = *slash++ = '\0';
+
+ /* detect invalid ranges */
+ if (apr_strtoff(range_start, range + 6, &errp, 10)
+ || *errp || *range_start < 0) {
+ return -1;
+ }
+ if (apr_strtoff(range_end, dash, &errp, 10)
+ || *errp || *range_end < 0 || *range_end < *range_start) {
+ return -1;
+ }
+
+ if (*slash != '*') {
+ apr_off_t dummy;
+
+ if (apr_strtoff(&dummy, slash, &errp, 10)
+ || *errp || dummy <= *range_end) {
+ return -1;
+ }
+ }
+
+ /* we now have a valid range */
+ return 1;
+}
+
+/* handle the GET method */
+static int dav_method_get(request_rec *r)
+{
+ dav_resource *resource;
+ dav_error *err;
+ int status;
+
+ /* This method should only be called when the resource is not
+ * visible to Apache. We will fetch the resource from the repository,
+ * then create a subrequest for Apache to handle.
+ */
+ err = dav_get_resource(r, 1 /* label_allowed */, 0 /* use_checked_in */,
+ &resource);
+ if (err != NULL)
+ return dav_handle_err(r, err, NULL);
+
+ if (!resource->exists) {
+ /* Apache will supply a default error for this. */
+ return HTTP_NOT_FOUND;
+ }
+
+ /* set up the HTTP headers for the response */
+ if ((err = (*resource->hooks->set_headers)(r, resource)) != NULL) {
+ err = dav_push_error(r->pool, err->status, 0,
+ "Unable to set up HTTP headers.",
+ err);
+ return dav_handle_err(r, err, NULL);
+ }
+
+ /* Handle conditional requests */
+ status = ap_meets_conditions(r);
+ if (status) {
+ return status;
+ }
+
+ if (r->header_only) {
+ return DONE;
+ }
+
+ /* okay... time to deliver the content */
+ if ((err = (*resource->hooks->deliver)(resource,
+ r->output_filters)) != NULL) {
+ err = dav_push_error(r->pool, err->status, 0,
+ "Unable to deliver content.",
+ err);
+ return dav_handle_err(r, err, NULL);
+ }
+
+ return DONE;
+}
+
+/* validate resource/locks on POST, then pass to the default handler */
+static int dav_method_post(request_rec *r)
+{
+ dav_resource *resource;
+ dav_error *err;
+
+ /* Ask repository module to resolve the resource */
+ err = dav_get_resource(r, 0 /* label_allowed */, 0 /* use_checked_in */,
+ &resource);
+ if (err != NULL)
+ return dav_handle_err(r, err, NULL);
+
+ /* Note: depth == 0. Implies no need for a multistatus response. */
+ if ((err = dav_validate_request(r, resource, 0, NULL, NULL,
+ DAV_VALIDATE_RESOURCE, NULL)) != NULL) {
+ /* ### add a higher-level description? */
+ return dav_handle_err(r, err, NULL);
+ }
+
+ return DECLINED;
+}
+
+/* handle the PUT method */
+static int dav_method_put(request_rec *r)
+{
+ dav_resource *resource;
+ int resource_state;
+ dav_auto_version_info av_info;
+ const dav_hooks_locks *locks_hooks = DAV_GET_HOOKS_LOCKS(r);
+ const char *body;
+ dav_error *err;
+ dav_error *err2;
+ dav_stream_mode mode;
+ dav_stream *stream;
+ dav_response *multi_response;
+ int has_range;
+ apr_off_t range_start;
+ apr_off_t range_end;
+
+ /* Ask repository module to resolve the resource */
+ err = dav_get_resource(r, 0 /* label_allowed */, 0 /* use_checked_in */,
+ &resource);
+ if (err != NULL)
+ return dav_handle_err(r, err, NULL);
+
+ /* If not a file or collection resource, PUT not allowed */
+ if (resource->type != DAV_RESOURCE_TYPE_REGULAR
+ && resource->type != DAV_RESOURCE_TYPE_WORKING) {
+ body = apr_psprintf(r->pool,
+ "Cannot create resource %s with PUT.",
+ ap_escape_html(r->pool, r->uri));
+ return dav_error_response(r, HTTP_CONFLICT, body);
+ }
+
+ /* Cannot PUT a collection */
+ if (resource->collection) {
+ return dav_error_response(r, HTTP_CONFLICT,
+ "Cannot PUT to a collection.");
+
+ }
+
+ resource_state = dav_get_resource_state(r, resource);
+
+ /*
+ * Note: depth == 0 normally requires no multistatus response. However,
+ * if we pass DAV_VALIDATE_PARENT, then we could get an error on a URI
+ * other than the Request-URI, thereby requiring a multistatus.
+ *
+ * If the resource does not exist (DAV_RESOURCE_NULL), then we must
+ * check the resource *and* its parent. If the resource exists or is
+ * a locknull resource, then we check only the resource.
+ */
+ if ((err = dav_validate_request(r, resource, 0, NULL, &multi_response,
+ resource_state == DAV_RESOURCE_NULL ?
+ DAV_VALIDATE_PARENT :
+ DAV_VALIDATE_RESOURCE, NULL)) != NULL) {
+ /* ### add a higher-level description? */
+ return dav_handle_err(r, err, multi_response);
+ }
+
+ has_range = dav_parse_range(r, &range_start, &range_end);
+ if (has_range < 0) {
+ /* RFC 2616 14.16: If we receive an invalid Content-Range we must
+ * not use the content.
+ */
+ body = apr_psprintf(r->pool,
+ "Malformed Content-Range header for PUT %s.",
+ ap_escape_html(r->pool, r->uri));
+ return dav_error_response(r, HTTP_BAD_REQUEST, body);
+ } else if (has_range) {
+ mode = DAV_MODE_WRITE_SEEKABLE;
+ }
+ else {
+ mode = DAV_MODE_WRITE_TRUNC;
+ }
+
+ /* make sure the resource can be modified (if versioning repository) */
+ if ((err = dav_auto_checkout(r, resource,
+ 0 /* not parent_only */,
+ &av_info)) != NULL) {
+ /* ### add a higher-level description? */
+ return dav_handle_err(r, err, NULL);
+ }
+
+ /* Create the new file in the repository */
+ if ((err = (*resource->hooks->open_stream)(resource, mode,
+ &stream)) != NULL) {
+ /* ### assuming FORBIDDEN is probably not quite right... */
+ err = dav_push_error(r->pool, HTTP_FORBIDDEN, 0,
+ apr_psprintf(r->pool,
+ "Unable to PUT new contents for %s.",
+ ap_escape_html(r->pool, r->uri)),
+ err);
+ }
+
+ if (err == NULL && has_range) {
+ /* a range was provided. seek to the start */
+ err = (*resource->hooks->seek_stream)(stream, range_start);
+ }
+
+ if (err == NULL) {
+ apr_bucket_brigade *bb;
+ apr_bucket *b;
+ int seen_eos = 0;
+
+ bb = apr_brigade_create(r->pool, r->connection->bucket_alloc);
+
+ do {
+ apr_status_t rc;
+
+ rc = ap_get_brigade(r->input_filters, bb, AP_MODE_READBYTES,
+ APR_BLOCK_READ, DAV_READ_BLOCKSIZE);
+
+ if (rc != APR_SUCCESS) {
+ int http_err;
+ char *msg = ap_escape_html(r->pool, r->uri);
+ http_err = ap_map_http_request_error(rc, HTTP_BAD_REQUEST);
+ msg = apr_psprintf(r->pool, "An error occurred while reading "
+ "the request body (URI: %s)",
+ msg);
+ err = dav_new_error(r->pool, http_err, 0, rc, msg);
+ break;
+ }
+
+ for (b = APR_BRIGADE_FIRST(bb);
+ b != APR_BRIGADE_SENTINEL(bb);
+ b = APR_BUCKET_NEXT(b))
+ {
+ const char *data;
+ apr_size_t len;
+
+ if (APR_BUCKET_IS_EOS(b)) {
+ seen_eos = 1;
+ break;
+ }
+
+ if (APR_BUCKET_IS_METADATA(b)) {
+ continue;
+ }
+
+ if (err == NULL) {
+ /* write whatever we read, until we see an error */
+ rc = apr_bucket_read(b, &data, &len, APR_BLOCK_READ);
+ if (rc != APR_SUCCESS) {
+ err = dav_new_error(r->pool, HTTP_BAD_REQUEST, 0, rc,
+ apr_psprintf(r->pool,
+ "An error occurred while"
+ " reading the request body"
+ " from the bucket (URI: %s)",
+ ap_escape_html(r->pool, r->uri)));
+ break;
+ }
+
+ err = (*resource->hooks->write_stream)(stream, data, len);
+ }
+ }
+
+ apr_brigade_cleanup(bb);
+ } while (!seen_eos);
+
+ apr_brigade_destroy(bb);
+
+ err2 = (*resource->hooks->close_stream)(stream,
+ err == NULL /* commit */);
+ err = dav_join_error(err, err2);
+ }
+
+ /*
+ * Ensure that we think the resource exists now.
+ * ### eek. if an error occurred during the write and we did not commit,
+ * ### then the resource might NOT exist (e.g. dav_fs_repos.c)
+ */
+ if (err == NULL) {
+ resource->exists = 1;
+ }
+
+ /* restore modifiability of resources back to what they were */
+ err2 = dav_auto_checkin(r, resource, err != NULL /* undo if error */,
+ 0 /*unlock*/, &av_info);
+
+ /* check for errors now */
+ if (err != NULL) {
+ err = dav_join_error(err, err2); /* don't forget err2 */
+ return dav_handle_err(r, err, NULL);
+ }
+
+ if (err2 != NULL) {
+ /* just log a warning */
+ err2 = dav_push_error(r->pool, err2->status, 0,
+ "The PUT was successful, but there "
+ "was a problem automatically checking in "
+ "the resource or its parent collection.",
+ err2);
+ dav_log_err(r, err2, APLOG_WARNING);
+ }
+
+ /* ### place the Content-Type and Content-Language into the propdb */
+
+ if (locks_hooks != NULL) {
+ dav_lockdb *lockdb;
+
+ if ((err = (*locks_hooks->open_lockdb)(r, 0, 0, &lockdb)) != NULL) {
+ /* The file creation was successful, but the locking failed. */
+ err = dav_push_error(r->pool, err->status, 0,
+ "The file was PUT successfully, but there "
+ "was a problem opening the lock database "
+ "which prevents inheriting locks from the "
+ "parent resources.",
+ err);
+ return dav_handle_err(r, err, NULL);
+ }
+
+ /* notify lock system that we have created/replaced a resource */
+ err = dav_notify_created(r, lockdb, resource, resource_state, 0);
+
+ (*locks_hooks->close_lockdb)(lockdb);
+
+ if (err != NULL) {
+ /* The file creation was successful, but the locking failed. */
+ err = dav_push_error(r->pool, err->status, 0,
+ "The file was PUT successfully, but there "
+ "was a problem updating its lock "
+ "information.",
+ err);
+ return dav_handle_err(r, err, NULL);
+ }
+ }
+
+ /* NOTE: WebDAV spec, S8.7.1 states properties should be unaffected */
+
+ /* return an appropriate response (HTTP_CREATED or HTTP_NO_CONTENT) */
+ return dav_created(r, NULL, "Resource", resource_state == DAV_RESOURCE_EXISTS);
+}
+
+
+/* Use POOL to temporarily construct a dav_response object (from WRES
+ STATUS, and PROPSTATS) and stream it via WRES's ctx->brigade. */
+static void dav_stream_response(dav_walk_resource *wres,
+ int status,
+ dav_get_props_result *propstats,
+ apr_pool_t *pool)
+{
+ dav_response resp = { 0 };
+ dav_walker_ctx *ctx = wres->walk_ctx;
+
+ resp.href = wres->resource->uri;
+ resp.status = status;
+ if (propstats) {
+ resp.propresult = *propstats;
+ }
+
+ dav_send_one_response(&resp, ctx->bb, ctx->r, pool);
+}
+
+
+/* ### move this to dav_util? */
+DAV_DECLARE(void) dav_add_response(dav_walk_resource *wres,
+ int status, dav_get_props_result *propstats)
+{
+ dav_response *resp;
+
+ /* just drop some data into an dav_response */
+ resp = apr_pcalloc(wres->pool, sizeof(*resp));
+ resp->href = apr_pstrdup(wres->pool, wres->resource->uri);
+ resp->status = status;
+ if (propstats) {
+ resp->propresult = *propstats;
+ }
+
+ resp->next = wres->response;
+ wres->response = resp;
+}
+
+
+/* handle the DELETE method */
+static int dav_method_delete(request_rec *r)
+{
+ dav_resource *resource;
+ dav_auto_version_info av_info;
+ dav_error *err;
+ dav_error *err2;
+ dav_response *multi_response;
+ int result;
+ int depth;
+
+ /* We don't use the request body right now, so torch it. */
+ if ((result = ap_discard_request_body(r)) != OK) {
+ return result;
+ }
+
+ /* Ask repository module to resolve the resource */
+ err = dav_get_resource(r, 0 /* label_allowed */, 0 /* use_checked_in */,
+ &resource);
+ if (err != NULL)
+ return dav_handle_err(r, err, NULL);
+ if (!resource->exists) {
+ /* Apache will supply a default error for this. */
+ return HTTP_NOT_FOUND;
+ }
+
+ /* 2518 says that depth must be infinity only for collections.
+ * For non-collections, depth is ignored, unless it is an illegal value (1).
+ */
+ depth = dav_get_depth(r, DAV_INFINITY);
+
+ if (resource->collection && depth != DAV_INFINITY) {
+ /* This supplies additional information for the default message. */
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(00582)
+ "Depth must be \"infinity\" for DELETE of a collection.");
+ return HTTP_BAD_REQUEST;
+ }
+
+ if (!resource->collection && depth == 1) {
+ /* This supplies additional information for the default message. */
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(00583)
+ "Depth of \"1\" is not allowed for DELETE.");
+ return HTTP_BAD_REQUEST;
+ }
+
+ /*
+ ** If any resources fail the lock/If: conditions, then we must fail
+ ** the delete. Each of the failing resources will be listed within
+ ** a DAV:multistatus body, wrapped into a 424 response.
+ **
+ ** Note that a failure on the resource itself does not generate a
+ ** multistatus response -- only internal members/collections.
+ */
+ if ((err = dav_validate_request(r, resource, depth, NULL,
+ &multi_response,
+ DAV_VALIDATE_PARENT
+ | DAV_VALIDATE_USE_424, NULL)) != NULL) {
+ err = dav_push_error(r->pool, err->status, 0,
+ apr_psprintf(r->pool,
+ "Could not DELETE %s due to a failed "
+ "precondition (e.g. locks).",
+ ap_escape_html(r->pool, r->uri)),
+ err);
+ return dav_handle_err(r, err, multi_response);
+ }
+
+ /* ### RFC 2518 s. 8.10.5 says to remove _all_ locks, not just those
+ * locked by the token(s) in the if_header.
+ */
+ if ((result = dav_unlock(r, resource, NULL)) != OK) {
+ return result;
+ }
+
+ /* if versioned resource, make sure parent is checked out */
+ if ((err = dav_auto_checkout(r, resource, 1 /* parent_only */,
+ &av_info)) != NULL) {
+ /* ### add a higher-level description? */
+ return dav_handle_err(r, err, NULL);
+ }
+
+ /* try to remove the resource */
+ err = (*resource->hooks->remove_resource)(resource, &multi_response);
+
+ /* restore writability of parent back to what it was */
+ err2 = dav_auto_checkin(r, NULL, err != NULL /* undo if error */,
+ 0 /*unlock*/, &av_info);
+
+ /* check for errors now */
+ if (err != NULL) {
+ err = dav_push_error(r->pool, err->status, 0,
+ apr_psprintf(r->pool,
+ "Could not DELETE %s.",
+ ap_escape_html(r->pool, r->uri)),
+ err);
+ return dav_handle_err(r, err, multi_response);
+ }
+ if (err2 != NULL) {
+ /* just log a warning */
+ err = dav_push_error(r->pool, err2->status, 0,
+ "The DELETE was successful, but there "
+ "was a problem automatically checking in "
+ "the parent collection.",
+ err2);
+ dav_log_err(r, err, APLOG_WARNING);
+ }
+
+ /* ### HTTP_NO_CONTENT if no body, HTTP_OK if there is a body (some day) */
+
+ /* Apache will supply a default error for this. */
+ return HTTP_NO_CONTENT;
+}
+
+/* generate DAV:supported-method-set OPTIONS response */
+static dav_error *dav_gen_supported_methods(request_rec *r,
+ const apr_xml_elem *elem,
+ const apr_table_t *methods,
+ apr_text_header *body)
+{
+ const apr_array_header_t *arr;
+ const apr_table_entry_t *elts;
+ apr_xml_elem *child;
+ apr_xml_attr *attr;
+ char *s;
+ int i;
+
+ apr_text_append(r->pool, body, "<D:supported-method-set>" DEBUG_CR);
+
+ if (elem->first_child == NULL) {
+ /* show all supported methods */
+ arr = apr_table_elts(methods);
+ elts = (const apr_table_entry_t *)arr->elts;
+
+ for (i = 0; i < arr->nelts; ++i) {
+ if (elts[i].key == NULL)
+ continue;
+
+ s = apr_psprintf(r->pool,
+ "<D:supported-method D:name=\"%s\"/>"
+ DEBUG_CR,
+ elts[i].key);
+ apr_text_append(r->pool, body, s);
+ }
+ }
+ else {
+ /* check for support of specific methods */
+ for (child = elem->first_child; child != NULL; child = child->next) {
+ if (child->ns == APR_XML_NS_DAV_ID
+ && strcmp(child->name, "supported-method") == 0) {
+ const char *name = NULL;
+
+ /* go through attributes to find method name */
+ for (attr = child->attr; attr != NULL; attr = attr->next) {
+ if (attr->ns == APR_XML_NS_DAV_ID
+ && strcmp(attr->name, "name") == 0)
+ name = attr->value;
+ }
+
+ if (name == NULL) {
+ return dav_new_error(r->pool, HTTP_BAD_REQUEST, 0, 0,
+ "A DAV:supported-method element "
+ "does not have a \"name\" attribute");
+ }
+
+ /* see if method is supported */
+ if (apr_table_get(methods, name) != NULL) {
+ s = apr_psprintf(r->pool,
+ "<D:supported-method D:name=\"%s\"/>"
+ DEBUG_CR,
+ name);
+ apr_text_append(r->pool, body, s);
+ }
+ }
+ }
+ }
+
+ apr_text_append(r->pool, body, "</D:supported-method-set>" DEBUG_CR);
+ return NULL;
+}
+
+/* generate DAV:supported-live-property-set OPTIONS response */
+static dav_error *dav_gen_supported_live_props(request_rec *r,
+ const dav_resource *resource,
+ const apr_xml_elem *elem,
+ apr_text_header *body)
+{
+ dav_lockdb *lockdb;
+ dav_propdb *propdb;
+ apr_xml_elem *child;
+ apr_xml_attr *attr;
+ dav_error *err;
+
+ /* open lock database, to report on supported lock properties */
+ /* ### should open read-only */
+ if ((err = dav_open_lockdb(r, 0, &lockdb)) != NULL) {
+ return dav_push_error(r->pool, err->status, 0,
+ "The lock database could not be opened, "
+ "preventing the reporting of supported lock "
+ "properties.",
+ err);
+ }
+
+ /* open the property database (readonly) for the resource */
+ if ((err = dav_open_propdb(r, lockdb, resource, 1, NULL,
+ &propdb)) != NULL) {
+ if (lockdb != NULL)
+ (*lockdb->hooks->close_lockdb)(lockdb);
+
+ return dav_push_error(r->pool, err->status, 0,
+ "The property database could not be opened, "
+ "preventing report of supported properties.",
+ err);
+ }
+
+ apr_text_append(r->pool, body, "<D:supported-live-property-set>" DEBUG_CR);
+
+ if (elem->first_child == NULL) {
+ /* show all supported live properties */
+ dav_get_props_result props = dav_get_allprops(propdb, DAV_PROP_INSERT_SUPPORTED);
+ body->last->next = props.propstats;
+ while (body->last->next != NULL)
+ body->last = body->last->next;
+ }
+ else {
+ /* check for support of specific live property */
+ for (child = elem->first_child; child != NULL; child = child->next) {
+ if (child->ns == APR_XML_NS_DAV_ID
+ && strcmp(child->name, "supported-live-property") == 0) {
+ const char *name = NULL;
+ const char *nmspace = NULL;
+
+ /* go through attributes to find name and namespace */
+ for (attr = child->attr; attr != NULL; attr = attr->next) {
+ if (attr->ns == APR_XML_NS_DAV_ID) {
+ if (strcmp(attr->name, "name") == 0)
+ name = attr->value;
+ else if (strcmp(attr->name, "namespace") == 0)
+ nmspace = attr->value;
+ }
+ }
+
+ if (name == NULL) {
+ err = dav_new_error(r->pool, HTTP_BAD_REQUEST, 0, 0,
+ "A DAV:supported-live-property "
+ "element does not have a \"name\" "
+ "attribute");
+ break;
+ }
+
+ /* default namespace to DAV: */
+ if (nmspace == NULL)
+ nmspace = "DAV:";
+
+ /* check for support of property */
+ dav_get_liveprop_supported(propdb, nmspace, name, body);
+ }
+ }
+ }
+
+ apr_text_append(r->pool, body, "</D:supported-live-property-set>" DEBUG_CR);
+
+ dav_close_propdb(propdb);
+
+ if (lockdb != NULL)
+ (*lockdb->hooks->close_lockdb)(lockdb);
+
+ return err;
+}
+
+/* generate DAV:supported-report-set OPTIONS response */
+static dav_error *dav_gen_supported_reports(request_rec *r,
+ const dav_resource *resource,
+ const apr_xml_elem *elem,
+ const dav_hooks_vsn *vsn_hooks,
+ apr_text_header *body)
+{
+ apr_xml_elem *child;
+ apr_xml_attr *attr;
+ dav_error *err;
+ char *s;
+
+ apr_text_append(r->pool, body, "<D:supported-report-set>" DEBUG_CR);
+
+ if (vsn_hooks != NULL) {
+ const dav_report_elem *reports;
+ const dav_report_elem *rp;
+
+ if ((err = (*vsn_hooks->avail_reports)(resource, &reports)) != NULL) {
+ return dav_push_error(r->pool, err->status, 0,
+ "DAV:supported-report-set could not be "
+ "determined due to a problem fetching the "
+ "available reports for this resource.",
+ err);
+ }
+
+ if (reports != NULL) {
+ if (elem->first_child == NULL) {
+ /* show all supported reports */
+ for (rp = reports; rp->nmspace != NULL; ++rp) {
+ /* Note: we presume reports->namespace is
+ * properly XML/URL quoted */
+ s = apr_psprintf(r->pool,
+ "<D:supported-report D:name=\"%s\" "
+ "D:namespace=\"%s\"/>" DEBUG_CR,
+ rp->name, rp->nmspace);
+ apr_text_append(r->pool, body, s);
+ }
+ }
+ else {
+ /* check for support of specific report */
+ for (child = elem->first_child; child != NULL; child = child->next) {
+ if (child->ns == APR_XML_NS_DAV_ID
+ && strcmp(child->name, "supported-report") == 0) {
+ const char *name = NULL;
+ const char *nmspace = NULL;
+
+ /* go through attributes to find name and namespace */
+ for (attr = child->attr; attr != NULL; attr = attr->next) {
+ if (attr->ns == APR_XML_NS_DAV_ID) {
+ if (strcmp(attr->name, "name") == 0)
+ name = attr->value;
+ else if (strcmp(attr->name, "namespace") == 0)
+ nmspace = attr->value;
+ }
+ }
+
+ if (name == NULL) {
+ return dav_new_error(r->pool, HTTP_BAD_REQUEST, 0, 0,
+ "A DAV:supported-report element "
+ "does not have a \"name\" attribute");
+ }
+
+ /* default namespace to DAV: */
+ if (nmspace == NULL)
+ nmspace = "DAV:";
+
+ for (rp = reports; rp->nmspace != NULL; ++rp) {
+ if (strcmp(name, rp->name) == 0
+ && strcmp(nmspace, rp->nmspace) == 0) {
+ /* Note: we presume reports->nmspace is
+ * properly XML/URL quoted
+ */
+ s = apr_psprintf(r->pool,
+ "<D:supported-report "
+ "D:name=\"%s\" "
+ "D:namespace=\"%s\"/>"
+ DEBUG_CR,
+ rp->name, rp->nmspace);
+ apr_text_append(r->pool, body, s);
+ break;
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ apr_text_append(r->pool, body, "</D:supported-report-set>" DEBUG_CR);
+ return NULL;
+}
+
+
+/* handle the SEARCH method */
+static int dav_method_search(request_rec *r)
+{
+ const dav_hooks_search *search_hooks = DAV_GET_HOOKS_SEARCH(r);
+ dav_resource *resource;
+ dav_error *err;
+ dav_response *multi_status;
+
+ /* If no search provider, decline the request */
+ if (search_hooks == NULL)
+ return DECLINED;
+
+ /* This method should only be called when the resource is not
+ * visible to Apache. We will fetch the resource from the repository,
+ * then create a subrequest for Apache to handle.
+ */
+ err = dav_get_resource(r, 1 /* label_allowed */, 0 /* use_checked_in */,
+ &resource);
+ if (err != NULL)
+ return dav_handle_err(r, err, NULL);
+
+ if (!resource->exists) {
+ /* Apache will supply a default error for this. */
+ return HTTP_NOT_FOUND;
+ }
+
+ /* set up the HTTP headers for the response */
+ if ((err = (*resource->hooks->set_headers)(r, resource)) != NULL) {
+ err = dav_push_error(r->pool, err->status, 0,
+ "Unable to set up HTTP headers.",
+ err);
+ return dav_handle_err(r, err, NULL);
+ }
+
+ if (r->header_only) {
+ return DONE;
+ }
+
+ /* okay... time to search the content */
+ /* Let's validate XML and process walk function
+ * in the hook function
+ */
+ if ((err = (*search_hooks->search_resource)(r, &multi_status)) != NULL) {
+ /* ### add a higher-level description? */
+ return dav_handle_err(r, err, NULL);
+ }
+
+ /* We have results in multi_status */
+ /* Should I pass namespace?? */
+ dav_send_multistatus(r, HTTP_MULTI_STATUS, multi_status, NULL);
+
+ return DONE;
+}
+
+
+/* handle the OPTIONS method */
+static int dav_method_options(request_rec *r)
+{
+ const dav_hooks_locks *locks_hooks = DAV_GET_HOOKS_LOCKS(r);
+ const dav_hooks_vsn *vsn_hooks = DAV_GET_HOOKS_VSN(r);
+ const dav_hooks_binding *binding_hooks = DAV_GET_HOOKS_BINDING(r);
+ const dav_hooks_search *search_hooks = DAV_GET_HOOKS_SEARCH(r);
+ dav_resource *resource;
+ const char *dav_level;
+ char *allow;
+ char *s;
+ const apr_array_header_t *arr;
+ const apr_table_entry_t *elts;
+ apr_table_t *methods = apr_table_make(r->pool, 12);
+ apr_text_header vsn_options = { 0 };
+ apr_text_header body = { 0 };
+ apr_text *t;
+ int text_size;
+ int result;
+ int i;
+ apr_array_header_t *uri_ary;
+ apr_xml_doc *doc;
+ const apr_xml_elem *elem;
+ dav_error *err;
+
+ apr_array_header_t *extensions;
+ ap_list_provider_names_t *entry;
+
+ /* resolve the resource */
+ err = dav_get_resource(r, 0 /* label_allowed */, 0 /* use_checked_in */,
+ &resource);
+ if (err != NULL)
+ return dav_handle_err(r, err, NULL);
+
+ /* parse any request body */
+ if ((result = ap_xml_parse_input(r, &doc)) != OK) {
+ return result;
+ }
+ /* note: doc == NULL if no request body */
+
+ if (doc && !dav_validate_root(doc, "options")) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(00584)
+ "The \"options\" element was not found.");
+ return HTTP_BAD_REQUEST;
+ }
+
+ /* determine which providers are available */
+ dav_level = "1";
+
+ if (locks_hooks != NULL) {
+ dav_level = "1,2";
+ }
+
+ if (binding_hooks != NULL)
+ dav_level = apr_pstrcat(r->pool, dav_level, ",bindings", NULL);
+
+ /* DAV header additions registered by external modules */
+ extensions = ap_list_provider_names(r->pool, DAV_OPTIONS_EXTENSION_GROUP, "0");
+ entry = (ap_list_provider_names_t *)extensions->elts;
+
+ for (i = 0; i < extensions->nelts; i++, entry++) {
+ const dav_options_provider *options =
+ dav_get_options_providers(entry->provider_name);
+
+ if (options && options->dav_header) {
+ apr_text_header hoptions = { 0 };
+
+ options->dav_header(r, resource, &hoptions);
+ for (t = hoptions.first; t && t->text; t = t->next)
+ dav_level = apr_pstrcat(r->pool, dav_level, ",", t->text, NULL);
+ }
+ }
+
+ /* ###
+ * MSFT Web Folders chokes if length of DAV header value > 63 characters!
+ * To workaround that, we use separate DAV headers for versioning and
+ * live prop provider namespace URIs.
+ * ###
+ */
+ apr_table_setn(r->headers_out, "DAV", dav_level);
+
+ /*
+ * If there is a versioning provider, generate DAV headers
+ * for versioning options.
+ */
+ if (vsn_hooks != NULL) {
+ (*vsn_hooks->get_vsn_options)(r->pool, &vsn_options);
+
+ for (t = vsn_options.first; t != NULL; t = t->next)
+ apr_table_addn(r->headers_out, "DAV", t->text);
+ }
+
+ /*
+ * Gather property set URIs from all the liveprop providers,
+ * and generate a separate DAV header for each URI, to avoid
+ * problems with long header lengths.
+ */
+ uri_ary = apr_array_make(r->pool, 5, sizeof(const char *));
+ dav_run_gather_propsets(uri_ary);
+ for (i = 0; i < uri_ary->nelts; ++i) {
+ if (((char **)uri_ary->elts)[i] != NULL)
+ apr_table_addn(r->headers_out, "DAV", ((char **)uri_ary->elts)[i]);
+ }
+
+ /* this tells MSFT products to skip looking for FrontPage extensions */
+ apr_table_setn(r->headers_out, "MS-Author-Via", "DAV");
+
+ /*
+ * Determine which methods are allowed on the resource.
+ * Three cases: resource is null (3), is lock-null (7.4), or exists.
+ *
+ * All cases support OPTIONS, and if there is a lock provider, LOCK.
+ * (Lock-) null resources also support MKCOL and PUT.
+ * Lock-null supports PROPFIND and UNLOCK.
+ * Existing resources support lots of stuff.
+ */
+
+ apr_table_addn(methods, "OPTIONS", "");
+
+ /* ### take into account resource type */
+ switch (dav_get_resource_state(r, resource))
+ {
+ case DAV_RESOURCE_EXISTS:
+ /* resource exists */
+ apr_table_addn(methods, "GET", "");
+ apr_table_addn(methods, "HEAD", "");
+ apr_table_addn(methods, "POST", "");
+ apr_table_addn(methods, "DELETE", "");
+ apr_table_addn(methods, "TRACE", "");
+ apr_table_addn(methods, "PROPFIND", "");
+ apr_table_addn(methods, "PROPPATCH", "");
+ apr_table_addn(methods, "COPY", "");
+ apr_table_addn(methods, "MOVE", "");
+
+ if (!resource->collection)
+ apr_table_addn(methods, "PUT", "");
+
+ if (locks_hooks != NULL) {
+ apr_table_addn(methods, "LOCK", "");
+ apr_table_addn(methods, "UNLOCK", "");
+ }
+
+ break;
+
+ case DAV_RESOURCE_LOCK_NULL:
+ /* resource is lock-null. */
+ apr_table_addn(methods, "MKCOL", "");
+ apr_table_addn(methods, "PROPFIND", "");
+ apr_table_addn(methods, "PUT", "");
+
+ if (locks_hooks != NULL) {
+ apr_table_addn(methods, "LOCK", "");
+ apr_table_addn(methods, "UNLOCK", "");
+ }
+
+ break;
+
+ case DAV_RESOURCE_NULL:
+ /* resource is null. */
+ apr_table_addn(methods, "MKCOL", "");
+ apr_table_addn(methods, "PUT", "");
+
+ if (locks_hooks != NULL)
+ apr_table_addn(methods, "LOCK", "");
+
+ break;
+
+ default:
+ /* ### internal error! */
+ break;
+ }
+
+ /* If there is a versioning provider, add versioning methods */
+ if (vsn_hooks != NULL) {
+ if (!resource->exists) {
+ if ((*vsn_hooks->versionable)(resource))
+ apr_table_addn(methods, "VERSION-CONTROL", "");
+
+ if (vsn_hooks->can_be_workspace != NULL
+ && (*vsn_hooks->can_be_workspace)(resource))
+ apr_table_addn(methods, "MKWORKSPACE", "");
+
+ if (vsn_hooks->can_be_activity != NULL
+ && (*vsn_hooks->can_be_activity)(resource))
+ apr_table_addn(methods, "MKACTIVITY", "");
+ }
+ else if (!resource->versioned) {
+ if ((*vsn_hooks->versionable)(resource))
+ apr_table_addn(methods, "VERSION-CONTROL", "");
+ }
+ else if (resource->working) {
+ apr_table_addn(methods, "CHECKIN", "");
+
+ /* ### we might not support this DeltaV option */
+ apr_table_addn(methods, "UNCHECKOUT", "");
+ }
+ else if (vsn_hooks->add_label != NULL) {
+ apr_table_addn(methods, "CHECKOUT", "");
+ apr_table_addn(methods, "LABEL", "");
+ }
+ else {
+ apr_table_addn(methods, "CHECKOUT", "");
+ }
+ }
+
+ /* If there is a bindings provider, see if resource is bindable */
+ if (binding_hooks != NULL
+ && (*binding_hooks->is_bindable)(resource)) {
+ apr_table_addn(methods, "BIND", "");
+ }
+
+ /* If there is a search provider, set SEARCH in option */
+ if (search_hooks != NULL) {
+ apr_table_addn(methods, "SEARCH", "");
+ }
+
+ /* additional methods registered by external modules */
+ extensions = ap_list_provider_names(r->pool, DAV_OPTIONS_EXTENSION_GROUP, "0");
+ entry = (ap_list_provider_names_t *)extensions->elts;
+
+ for (i = 0; i < extensions->nelts; i++, entry++) {
+ const dav_options_provider *options =
+ dav_get_options_providers(entry->provider_name);
+
+ if (options && options->dav_method) {
+ apr_text_header hoptions = { 0 };
+
+ options->dav_method(r, resource, &hoptions);
+ for (t = hoptions.first; t && t->text; t = t->next)
+ apr_table_addn(methods, t->text, "");
+ }
+ }
+
+ /* Generate the Allow header */
+ arr = apr_table_elts(methods);
+ elts = (const apr_table_entry_t *)arr->elts;
+ text_size = 0;
+
+ /* first, compute total length */
+ for (i = 0; i < arr->nelts; ++i) {
+ if (elts[i].key == NULL)
+ continue;
+
+ /* add 1 for comma or null */
+ text_size += strlen(elts[i].key) + 1;
+ }
+
+ s = allow = apr_palloc(r->pool, text_size);
+
+ for (i = 0; i < arr->nelts; ++i) {
+ if (elts[i].key == NULL)
+ continue;
+
+ if (s != allow)
+ *s++ = ',';
+
+ strcpy(s, elts[i].key);
+ s += strlen(s);
+ }
+
+ apr_table_setn(r->headers_out, "Allow", allow);
+
+
+ /* If there is search set_option_head function, set head */
+ /* DASL: <DAV:basicsearch>
+ * DASL: <http://foo.bar.com/syntax1>
+ * DASL: <http://akuma.com/syntax2>
+ */
+ if (search_hooks != NULL
+ && *search_hooks->set_option_head != NULL) {
+ if ((err = (*search_hooks->set_option_head)(r)) != NULL) {
+ return dav_handle_err(r, err, NULL);
+ }
+ }
+
+ /* if there was no request body, then there is no response body */
+ if (doc == NULL) {
+ ap_set_content_length(r, 0);
+
+ /* ### this sends a Content-Type. the default OPTIONS does not. */
+
+ /* ### the default (ap_send_http_options) returns OK, but I believe
+ * ### that is because it is the default handler and nothing else
+ * ### will run after the thing. */
+ return DONE;
+ }
+
+ /* handle each options request */
+ for (elem = doc->root->first_child; elem != NULL; elem = elem->next) {
+ /* check for something we recognize first */
+ int core_option = 0;
+ dav_error *err = NULL;
+
+ if (elem->ns == APR_XML_NS_DAV_ID) {
+ if (strcmp(elem->name, "supported-method-set") == 0) {
+ err = dav_gen_supported_methods(r, elem, methods, &body);
+ core_option = 1;
+ }
+ else if (strcmp(elem->name, "supported-live-property-set") == 0) {
+ err = dav_gen_supported_live_props(r, resource, elem, &body);
+ core_option = 1;
+ }
+ else if (strcmp(elem->name, "supported-report-set") == 0) {
+ err = dav_gen_supported_reports(r, resource, elem, vsn_hooks, &body);
+ core_option = 1;
+ }
+ }
+
+ if (err != NULL)
+ return dav_handle_err(r, err, NULL);
+
+ /* if unrecognized option, pass to versioning provider */
+ if (!core_option && vsn_hooks != NULL) {
+ if ((err = (*vsn_hooks->get_option)(resource, elem, &body))
+ != NULL) {
+ return dav_handle_err(r, err, NULL);
+ }
+ }
+ }
+
+ /* send the options response */
+ r->status = HTTP_OK;
+ ap_set_content_type(r, DAV_XML_CONTENT_TYPE);
+
+ /* send the headers and response body */
+ ap_rputs(DAV_XML_HEADER DEBUG_CR
+ "<D:options-response xmlns:D=\"DAV:\">" DEBUG_CR, r);
+
+ for (t = body.first; t != NULL; t = t->next)
+ ap_rputs(t->text, r);
+
+ ap_rputs("</D:options-response>" DEBUG_CR, r);
+
+ /* we've sent everything necessary to the client. */
+ return DONE;
+}
+
+static void dav_cache_badprops(dav_walker_ctx *ctx)
+{
+ const apr_xml_elem *elem;
+ apr_text_header hdr = { 0 };
+
+ /* just return if we built the thing already */
+ if (ctx->propstat_404 != NULL) {
+ return;
+ }
+
+ apr_text_append(ctx->w.pool, &hdr,
+ "<D:propstat>" DEBUG_CR
+ "<D:prop>" DEBUG_CR);
+
+ elem = dav_find_child(ctx->doc->root, "prop");
+ for (elem = elem->first_child; elem; elem = elem->next) {
+ apr_text_append(ctx->w.pool, &hdr,
+ apr_xml_empty_elem(ctx->w.pool, elem));
+ }
+
+ apr_text_append(ctx->w.pool, &hdr,
+ "</D:prop>" DEBUG_CR
+ "<D:status>HTTP/1.1 404 Not Found</D:status>" DEBUG_CR
+ "</D:propstat>" DEBUG_CR);
+
+ ctx->propstat_404 = hdr.first;
+}
+
+static dav_error * dav_propfind_walker(dav_walk_resource *wres, int calltype)
+{
+ dav_walker_ctx *ctx = wres->walk_ctx;
+ dav_error *err;
+ dav_propdb *propdb;
+ dav_get_props_result propstats = { 0 };
+
+ /*
+ ** Note: ctx->doc can only be NULL for DAV_PROPFIND_IS_ALLPROP. Since
+ ** dav_get_allprops() does not need to do namespace translation,
+ ** we're okay.
+ **
+ ** Note: we cast to lose the "const". The propdb won't try to change
+ ** the resource, however, since we are opening readonly.
+ */
+ err = dav_open_propdb(ctx->r, ctx->w.lockdb, wres->resource, 1,
+ ctx->doc ? ctx->doc->namespaces : NULL, &propdb);
+ if (err != NULL) {
+ /* ### do something with err! */
+
+ if (ctx->propfind_type == DAV_PROPFIND_IS_PROP) {
+ dav_get_props_result badprops = { 0 };
+
+ /* some props were expected on this collection/resource */
+ dav_cache_badprops(ctx);
+ badprops.propstats = ctx->propstat_404;
+ dav_stream_response(wres, 0, &badprops, ctx->scratchpool);
+ }
+ else {
+ /* no props on this collection/resource */
+ dav_stream_response(wres, HTTP_OK, NULL, ctx->scratchpool);
+ }
+
+ apr_pool_clear(ctx->scratchpool);
+ return NULL;
+ }
+ /* ### what to do about closing the propdb on server failure? */
+
+ if (ctx->propfind_type == DAV_PROPFIND_IS_PROP) {
+ propstats = dav_get_props(propdb, ctx->doc);
+ }
+ else {
+ dav_prop_insert what = ctx->propfind_type == DAV_PROPFIND_IS_ALLPROP
+ ? DAV_PROP_INSERT_VALUE
+ : DAV_PROP_INSERT_NAME;
+ propstats = dav_get_allprops(propdb, what);
+ }
+ dav_close_propdb(propdb);
+
+ dav_stream_response(wres, 0, &propstats, ctx->scratchpool);
+
+ /* at this point, ctx->scratchpool has been used to stream a
+ single response. this function fully controls the pool, and
+ thus has the right to clear it for the next iteration of this
+ callback. */
+ apr_pool_clear(ctx->scratchpool);
+
+ return NULL;
+}
+
+/* handle the PROPFIND method */
+static int dav_method_propfind(request_rec *r)
+{
+ dav_resource *resource;
+ int depth;
+ dav_error *err;
+ int result;
+ apr_xml_doc *doc;
+ dav_walker_ctx ctx = { { 0 } };
+ dav_response *multi_status;
+
+ /* Ask repository module to resolve the resource */
+ err = dav_get_resource(r, 1 /* label_allowed */, 0 /* use_checked_in */,
+ &resource);
+ if (err != NULL)
+ return dav_handle_err(r, err, NULL);
+
+ if (dav_get_resource_state(r, resource) == DAV_RESOURCE_NULL) {
+ /* Apache will supply a default error for this. */
+ return HTTP_NOT_FOUND;
+ }
+
+ if ((depth = dav_get_depth(r, DAV_INFINITY)) < 0) {
+ /* dav_get_depth() supplies additional information for the
+ * default message. */
+ return HTTP_BAD_REQUEST;
+ }
+
+ if (depth == DAV_INFINITY && resource->collection) {
+ dav_dir_conf *conf;
+ conf = (dav_dir_conf *)ap_get_module_config(r->per_dir_config,
+ &dav_module);
+ /* default is to DISALLOW these requests */
+ if (conf->allow_depthinfinity != DAV_ENABLED_ON) {
+ return dav_error_response(r, HTTP_FORBIDDEN,
+ apr_psprintf(r->pool,
+ "PROPFIND requests with a "
+ "Depth of \"infinity\" are "
+ "not allowed for %s.",
+ ap_escape_html(r->pool,
+ r->uri)));
+ }
+ }
+
+ if ((result = ap_xml_parse_input(r, &doc)) != OK) {
+ return result;
+ }
+ /* note: doc == NULL if no request body */
+
+ if (doc && !dav_validate_root(doc, "propfind")) {
+ /* This supplies additional information for the default message. */
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(00585)
+ "The \"propfind\" element was not found.");
+ return HTTP_BAD_REQUEST;
+ }
+
+ /* ### validate that only one of these three elements is present */
+
+ if (doc == NULL || dav_find_child(doc->root, "allprop") != NULL) {
+ /* note: no request body implies allprop */
+ ctx.propfind_type = DAV_PROPFIND_IS_ALLPROP;
+ }
+ else if (dav_find_child(doc->root, "propname") != NULL) {
+ ctx.propfind_type = DAV_PROPFIND_IS_PROPNAME;
+ }
+ else if (dav_find_child(doc->root, "prop") != NULL) {
+ ctx.propfind_type = DAV_PROPFIND_IS_PROP;
+ }
+ else {
+ /* "propfind" element must have one of the above three children */
+
+ /* This supplies additional information for the default message. */
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(00586)
+ "The \"propfind\" element does not contain one of "
+ "the required child elements (the specific command).");
+ return HTTP_BAD_REQUEST;
+ }
+
+ ctx.w.walk_type = DAV_WALKTYPE_NORMAL | DAV_WALKTYPE_AUTH;
+ ctx.w.func = dav_propfind_walker;
+ ctx.w.walk_ctx = &ctx;
+ ctx.w.pool = r->pool;
+ ctx.w.root = resource;
+
+ ctx.doc = doc;
+ ctx.r = r;
+ ctx.bb = apr_brigade_create(r->pool, r->connection->bucket_alloc);
+ apr_pool_create(&ctx.scratchpool, r->pool);
+
+ /* ### should open read-only */
+ if ((err = dav_open_lockdb(r, 0, &ctx.w.lockdb)) != NULL) {
+ err = dav_push_error(r->pool, err->status, 0,
+ "The lock database could not be opened, "
+ "preventing access to the various lock "
+ "properties for the PROPFIND.",
+ err);
+ return dav_handle_err(r, err, NULL);
+ }
+ if (ctx.w.lockdb != NULL) {
+ /* if we have a lock database, then we can walk locknull resources */
+ ctx.w.walk_type |= DAV_WALKTYPE_LOCKNULL;
+ }
+
+ /* send <multistatus> tag, with all doc->namespaces attached. */
+
+ /* NOTE: we *cannot* leave out the doc's namespaces from the
+ initial <multistatus> tag. if a 404 was generated for an HREF,
+ then we need to spit out the doc's namespaces for use by the
+ 404. Note that <response> elements will override these ns0,
+ ns1, etc, but NOT within the <response> scope for the
+ badprops. */
+ dav_begin_multistatus(ctx.bb, r, HTTP_MULTI_STATUS,
+ doc ? doc->namespaces : NULL);
+
+ /* Have the provider walk the resource. */
+ err = (*resource->hooks->walk)(&ctx.w, depth, &multi_status);
+
+ if (ctx.w.lockdb != NULL) {
+ (*ctx.w.lockdb->hooks->close_lockdb)(ctx.w.lockdb);
+ }
+
+ if (err != NULL) {
+ /* If an error occurred during the resource walk, there's
+ basically nothing we can do but abort the connection and
+ log an error. This is one of the limitations of HTTP; it
+ needs to "know" the entire status of the response before
+ generating it, which is just impossible in these streamy
+ response situations. */
+ err = dav_push_error(r->pool, err->status, 0,
+ "Provider encountered an error while streaming"
+ " a multistatus PROPFIND response.", err);
+ dav_log_err(r, err, APLOG_ERR);
+ r->connection->aborted = 1;
+ return DONE;
+ }
+
+ dav_finish_multistatus(r, ctx.bb);
+
+ /* the response has been sent. */
+ return DONE;
+}
+
+DAV_DECLARE(apr_text *) dav_failed_proppatch(apr_pool_t *p,
+ apr_array_header_t *prop_ctx)
+{
+ apr_text_header hdr = { 0 };
+ int i = prop_ctx->nelts;
+ dav_prop_ctx *ctx = (dav_prop_ctx *)prop_ctx->elts;
+ dav_error *err424_set = NULL;
+ dav_error *err424_delete = NULL;
+ const char *s;
+
+ /* ### might be nice to sort by status code and description */
+
+ for ( ; i-- > 0; ++ctx ) {
+ apr_text_append(p, &hdr,
+ "<D:propstat>" DEBUG_CR
+ "<D:prop>");
+ apr_text_append(p, &hdr, apr_xml_empty_elem(p, ctx->prop));
+ apr_text_append(p, &hdr, "</D:prop>" DEBUG_CR);
+
+ if (ctx->err == NULL) {
+ /* nothing was assigned here yet, so make it a 424 */
+
+ if (ctx->operation == DAV_PROP_OP_SET) {
+ if (err424_set == NULL)
+ err424_set = dav_new_error(p, HTTP_FAILED_DEPENDENCY, 0, 0,
+ "Attempted DAV:set operation "
+ "could not be completed due "
+ "to other errors.");
+ ctx->err = err424_set;
+ }
+ else if (ctx->operation == DAV_PROP_OP_DELETE) {
+ if (err424_delete == NULL)
+ err424_delete = dav_new_error(p, HTTP_FAILED_DEPENDENCY, 0, 0,
+ "Attempted DAV:remove "
+ "operation could not be "
+ "completed due to other "
+ "errors.");
+ ctx->err = err424_delete;
+ }
+ }
+
+ s = apr_psprintf(p,
+ "<D:status>"
+ "HTTP/1.1 %d (status)"
+ "</D:status>" DEBUG_CR,
+ ctx->err->status);
+ apr_text_append(p, &hdr, s);
+
+ /* ### we should use compute_desc if necessary... */
+ if (ctx->err->desc != NULL) {
+ apr_text_append(p, &hdr, "<D:responsedescription>" DEBUG_CR);
+ apr_text_append(p, &hdr, ctx->err->desc);
+ apr_text_append(p, &hdr, "</D:responsedescription>" DEBUG_CR);
+ }
+
+ apr_text_append(p, &hdr, "</D:propstat>" DEBUG_CR);
+ }
+
+ return hdr.first;
+}
+
+DAV_DECLARE(apr_text *) dav_success_proppatch(apr_pool_t *p,
+ apr_array_header_t *prop_ctx)
+{
+ apr_text_header hdr = { 0 };
+ int i = prop_ctx->nelts;
+ dav_prop_ctx *ctx = (dav_prop_ctx *)prop_ctx->elts;
+
+ /*
+ * ### we probably need to revise the way we assemble the response...
+ * ### this code assumes everything will return status==200.
+ */
+
+ apr_text_append(p, &hdr,
+ "<D:propstat>" DEBUG_CR
+ "<D:prop>" DEBUG_CR);
+
+ for ( ; i-- > 0; ++ctx ) {
+ apr_text_append(p, &hdr, apr_xml_empty_elem(p, ctx->prop));
+ }
+
+ apr_text_append(p, &hdr,
+ "</D:prop>" DEBUG_CR
+ "<D:status>HTTP/1.1 200 OK</D:status>" DEBUG_CR
+ "</D:propstat>" DEBUG_CR);
+
+ return hdr.first;
+}
+
+static void dav_prop_log_errors(dav_prop_ctx *ctx)
+{
+ dav_log_err(ctx->r, ctx->err, APLOG_ERR);
+}
+
+/*
+ * Call <func> for each context. This can stop when an error occurs, or
+ * simply iterate through the whole list.
+ *
+ * Returns 1 if an error occurs (and the iteration is aborted). Returns 0
+ * if all elements are processed.
+ *
+ * If <reverse> is true (non-zero), then the list is traversed in
+ * reverse order.
+ */
+static int dav_process_ctx_list(void (*func)(dav_prop_ctx *ctx),
+ apr_array_header_t *ctx_list, int stop_on_error,
+ int reverse)
+{
+ int i = ctx_list->nelts;
+ dav_prop_ctx *ctx = (dav_prop_ctx *)ctx_list->elts;
+
+ if (reverse)
+ ctx += i;
+
+ while (i--) {
+ if (reverse)
+ --ctx;
+
+ (*func)(ctx);
+ if (stop_on_error && DAV_PROP_CTX_HAS_ERR(*ctx)) {
+ return 1;
+ }
+
+ if (!reverse)
+ ++ctx;
+ }
+
+ return 0;
+}
+
+/* handle the PROPPATCH method */
+static int dav_method_proppatch(request_rec *r)
+{
+ dav_error *err;
+ dav_resource *resource;
+ int result;
+ apr_xml_doc *doc;
+ apr_xml_elem *child;
+ dav_propdb *propdb;
+ int failure = 0;
+ dav_response resp = { 0 };
+ apr_text *propstat_text;
+ apr_array_header_t *ctx_list;
+ dav_prop_ctx *ctx;
+ dav_auto_version_info av_info;
+
+ /* Ask repository module to resolve the resource */
+ err = dav_get_resource(r, 0 /* label_allowed */, 0 /* use_checked_in */,
+ &resource);
+ if (err != NULL)
+ return dav_handle_err(r, err, NULL);
+ if (!resource->exists) {
+ /* Apache will supply a default error for this. */
+ return HTTP_NOT_FOUND;
+ }
+
+ if ((result = ap_xml_parse_input(r, &doc)) != OK) {
+ return result;
+ }
+ /* note: doc == NULL if no request body */
+
+ if (doc == NULL || !dav_validate_root(doc, "propertyupdate")) {
+ /* This supplies additional information for the default message. */
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(00587)
+ "The request body does not contain "
+ "a \"propertyupdate\" element.");
+ return HTTP_BAD_REQUEST;
+ }
+
+ /* Check If-Headers and existing locks */
+ /* Note: depth == 0. Implies no need for a multistatus response. */
+ if ((err = dav_validate_request(r, resource, 0, NULL, NULL,
+ DAV_VALIDATE_RESOURCE, NULL)) != NULL) {
+ /* ### add a higher-level description? */
+ return dav_handle_err(r, err, NULL);
+ }
+
+ /* make sure the resource can be modified (if versioning repository) */
+ if ((err = dav_auto_checkout(r, resource,
+ 0 /* not parent_only */,
+ &av_info)) != NULL) {
+ /* ### add a higher-level description? */
+ return dav_handle_err(r, err, NULL);
+ }
+
+ if ((err = dav_open_propdb(r, NULL, resource, 0, doc->namespaces,
+ &propdb)) != NULL) {
+ /* undo any auto-checkout */
+ dav_auto_checkin(r, resource, 1 /*undo*/, 0 /*unlock*/, &av_info);
+
+ err = dav_push_error(r->pool, HTTP_INTERNAL_SERVER_ERROR, 0,
+ apr_psprintf(r->pool,
+ "Could not open the property "
+ "database for %s.",
+ ap_escape_html(r->pool, r->uri)),
+ err);
+ return dav_handle_err(r, err, NULL);
+ }
+ /* ### what to do about closing the propdb on server failure? */
+
+ /* ### validate "live" properties */
+
+ /* set up an array to hold property operation contexts */
+ ctx_list = apr_array_make(r->pool, 10, sizeof(dav_prop_ctx));
+
+ /* do a first pass to ensure that all "remove" properties exist */
+ for (child = doc->root->first_child; child; child = child->next) {
+ int is_remove;
+ apr_xml_elem *prop_group;
+ apr_xml_elem *one_prop;
+
+ /* Ignore children that are not set/remove */
+ if (child->ns != APR_XML_NS_DAV_ID
+ || (!(is_remove = (strcmp(child->name, "remove") == 0))
+ && strcmp(child->name, "set") != 0)) {
+ continue;
+ }
+
+ /* make sure that a "prop" child exists for set/remove */
+ if ((prop_group = dav_find_child(child, "prop")) == NULL) {
+ dav_close_propdb(propdb);
+
+ /* undo any auto-checkout */
+ dav_auto_checkin(r, resource, 1 /*undo*/, 0 /*unlock*/, &av_info);
+
+ /* This supplies additional information for the default message. */
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(00588)
+ "A \"prop\" element is missing inside "
+ "the propertyupdate command.");
+ return HTTP_BAD_REQUEST;
+ }
+
+ for (one_prop = prop_group->first_child; one_prop;
+ one_prop = one_prop->next) {
+
+ ctx = (dav_prop_ctx *)apr_array_push(ctx_list);
+ ctx->propdb = propdb;
+ ctx->operation = is_remove ? DAV_PROP_OP_DELETE : DAV_PROP_OP_SET;
+ ctx->prop = one_prop;
+
+ ctx->r = r; /* for later use by dav_prop_log_errors() */
+
+ dav_prop_validate(ctx);
+
+ if ( DAV_PROP_CTX_HAS_ERR(*ctx) ) {
+ failure = 1;
+ }
+ }
+ }
+
+ /* ### should test that we found at least one set/remove */
+
+ /* execute all of the operations */
+ if (!failure && dav_process_ctx_list(dav_prop_exec, ctx_list, 1, 0)) {
+ failure = 1;
+ }
+
+ /* generate a failure/success response */
+ if (failure) {
+ (void)dav_process_ctx_list(dav_prop_rollback, ctx_list, 0, 1);
+ propstat_text = dav_failed_proppatch(r->pool, ctx_list);
+ }
+ else {
+ (void)dav_process_ctx_list(dav_prop_commit, ctx_list, 0, 0);
+ propstat_text = dav_success_proppatch(r->pool, ctx_list);
+ }
+
+ /* make sure this gets closed! */
+ dav_close_propdb(propdb);
+
+ /* complete any auto-versioning */
+ dav_auto_checkin(r, resource, failure, 0 /*unlock*/, &av_info);
+
+ /* log any errors that occurred */
+ (void)dav_process_ctx_list(dav_prop_log_errors, ctx_list, 0, 0);
+
+ resp.href = resource->uri;
+
+ /* ### should probably use something new to pass along this text... */
+ resp.propresult.propstats = propstat_text;
+
+ dav_send_multistatus(r, HTTP_MULTI_STATUS, &resp, doc->namespaces);
+
+ /* the response has been sent. */
+ return DONE;
+}
+
+static int process_mkcol_body(request_rec *r)
+{
+ /* This is snarfed from ap_setup_client_block(). We could get pretty
+ * close to this behavior by passing REQUEST_NO_BODY, but we need to
+ * return HTTP_UNSUPPORTED_MEDIA_TYPE (while ap_setup_client_block
+ * returns HTTP_REQUEST_ENTITY_TOO_LARGE). */
+
+ const char *tenc = apr_table_get(r->headers_in, "Transfer-Encoding");
+ const char *lenp = apr_table_get(r->headers_in, "Content-Length");
+
+ /* make sure to set the Apache request fields properly. */
+ r->read_body = REQUEST_NO_BODY;
+ r->read_chunked = 0;
+ r->remaining = 0;
+
+ if (tenc) {
+ if (strcasecmp(tenc, "chunked")) {
+ /* Use this instead of Apache's default error string */
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(00589)
+ "Unknown Transfer-Encoding %s", tenc);
+ return HTTP_NOT_IMPLEMENTED;
+ }
+
+ r->read_chunked = 1;
+ }
+ else if (lenp) {
+ const char *pos = lenp;
+
+ while (apr_isdigit(*pos) || apr_isspace(*pos)) {
+ ++pos;
+ }
+
+ if (*pos != '\0') {
+ /* This supplies additional information for the default message. */
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(00590)
+ "Invalid Content-Length %s", lenp);
+ return HTTP_BAD_REQUEST;
+ }
+
+ r->remaining = apr_atoi64(lenp);
+ }
+
+ if (r->read_chunked || r->remaining > 0) {
+ /* ### log something? */
+
+ /* Apache will supply a default error for this. */
+ return HTTP_UNSUPPORTED_MEDIA_TYPE;
+ }
+
+ /*
+ * Get rid of the body. this will call ap_setup_client_block(), but
+ * our copy above has already verified its work.
+ */
+ return ap_discard_request_body(r);
+}
+
+/* handle the MKCOL method */
+static int dav_method_mkcol(request_rec *r)
+{
+ dav_resource *resource;
+ int resource_state;
+ dav_auto_version_info av_info;
+ const dav_hooks_locks *locks_hooks = DAV_GET_HOOKS_LOCKS(r);
+ dav_error *err;
+ dav_error *err2;
+ int result;
+ dav_response *multi_status;
+
+ /* handle the request body */
+ /* ### this may move lower once we start processing bodies */
+ if ((result = process_mkcol_body(r)) != OK) {
+ return result;
+ }
+
+ /* Ask repository module to resolve the resource */
+ err = dav_get_resource(r, 0 /* label_allowed */, 0 /* use_checked_in */,
+ &resource);
+ if (err != NULL)
+ return dav_handle_err(r, err, NULL);
+
+ if (resource->exists) {
+ /* oops. something was already there! */
+
+ /* Apache will supply a default error for this. */
+ /* ### we should provide a specific error message! */
+ return HTTP_METHOD_NOT_ALLOWED;
+ }
+
+ resource_state = dav_get_resource_state(r, resource);
+
+ /*
+ * Check If-Headers and existing locks.
+ *
+ * Note: depth == 0 normally requires no multistatus response. However,
+ * if we pass DAV_VALIDATE_PARENT, then we could get an error on a URI
+ * other than the Request-URI, thereby requiring a multistatus.
+ *
+ * If the resource does not exist (DAV_RESOURCE_NULL), then we must
+ * check the resource *and* its parent. If the resource exists or is
+ * a locknull resource, then we check only the resource.
+ */
+ if ((err = dav_validate_request(r, resource, 0, NULL, &multi_status,
+ resource_state == DAV_RESOURCE_NULL ?
+ DAV_VALIDATE_PARENT :
+ DAV_VALIDATE_RESOURCE, NULL)) != NULL) {
+ /* ### add a higher-level description? */
+ return dav_handle_err(r, err, multi_status);
+ }
+
+ /* if versioned resource, make sure parent is checked out */
+ if ((err = dav_auto_checkout(r, resource, 1 /* parent_only */,
+ &av_info)) != NULL) {
+ /* ### add a higher-level description? */
+ return dav_handle_err(r, err, NULL);
+ }
+
+ /* try to create the collection */
+ resource->collection = 1;
+ err = (*resource->hooks->create_collection)(resource);
+
+ /* restore modifiability of parent back to what it was */
+ err2 = dav_auto_checkin(r, NULL, err != NULL /* undo if error */,
+ 0 /*unlock*/, &av_info);
+
+ /* check for errors now */
+ if (err != NULL) {
+ return dav_handle_err(r, err, NULL);
+ }
+ if (err2 != NULL) {
+ /* just log a warning */
+ err = dav_push_error(r->pool, err2->status, 0,
+ "The MKCOL was successful, but there "
+ "was a problem automatically checking in "
+ "the parent collection.",
+ err2);
+ dav_log_err(r, err, APLOG_WARNING);
+ }
+
+ if (locks_hooks != NULL) {
+ dav_lockdb *lockdb;
+
+ if ((err = (*locks_hooks->open_lockdb)(r, 0, 0, &lockdb)) != NULL) {
+ /* The directory creation was successful, but the locking failed. */
+ err = dav_push_error(r->pool, err->status, 0,
+ "The MKCOL was successful, but there "
+ "was a problem opening the lock database "
+ "which prevents inheriting locks from the "
+ "parent resources.",
+ err);
+ return dav_handle_err(r, err, NULL);
+ }
+
+ /* notify lock system that we have created/replaced a resource */
+ err = dav_notify_created(r, lockdb, resource, resource_state, 0);
+
+ (*locks_hooks->close_lockdb)(lockdb);
+
+ if (err != NULL) {
+ /* The dir creation was successful, but the locking failed. */
+ err = dav_push_error(r->pool, err->status, 0,
+ "The MKCOL was successful, but there "
+ "was a problem updating its lock "
+ "information.",
+ err);
+ return dav_handle_err(r, err, NULL);
+ }
+ }
+
+ /* return an appropriate response (HTTP_CREATED) */
+ return dav_created(r, NULL, "Collection", 0);
+}
+
+/* handle the COPY and MOVE methods */
+static int dav_method_copymove(request_rec *r, int is_move)
+{
+ dav_resource *resource;
+ dav_resource *resnew;
+ dav_auto_version_info src_av_info = { 0 };
+ dav_auto_version_info dst_av_info = { 0 };
+ const char *body;
+ const char *dest;
+ dav_error *err;
+ dav_error *err2;
+ dav_error *err3;
+ dav_response *multi_response;
+ dav_lookup_result lookup;
+ int is_dir;
+ int overwrite;
+ int depth;
+ int result;
+ dav_lockdb *lockdb;
+ int replace_dest;
+ int resnew_state;
+
+ /* Ask repository module to resolve the resource */
+ err = dav_get_resource(r, !is_move /* label_allowed */,
+ 0 /* use_checked_in */, &resource);
+ if (err != NULL)
+ return dav_handle_err(r, err, NULL);
+
+ if (!resource->exists) {
+ /* Apache will supply a default error for this. */
+ return HTTP_NOT_FOUND;
+ }
+
+ /* If not a file or collection resource, COPY/MOVE not allowed */
+ /* ### allow COPY/MOVE of DeltaV resource types */
+ if (resource->type != DAV_RESOURCE_TYPE_REGULAR) {
+ body = apr_psprintf(r->pool,
+ "Cannot COPY/MOVE resource %s.",
+ ap_escape_html(r->pool, r->uri));
+ return dav_error_response(r, HTTP_METHOD_NOT_ALLOWED, body);
+ }
+
+ /* get the destination URI */
+ dest = apr_table_get(r->headers_in, "Destination");
+ if (dest == NULL) {
+ /* Look in headers provided by Netscape's Roaming Profiles */
+ const char *nscp_host = apr_table_get(r->headers_in, "Host");
+ const char *nscp_path = apr_table_get(r->headers_in, "New-uri");
+
+ if (nscp_host != NULL && nscp_path != NULL)
+ dest = apr_psprintf(r->pool, "http://%s%s", nscp_host, nscp_path);
+ }
+ if (dest == NULL) {
+ /* This supplies additional information for the default message. */
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(00591)
+ "The request is missing a Destination header.");
+ return HTTP_BAD_REQUEST;
+ }
+
+ lookup = dav_lookup_uri(dest, r, 1 /* must_be_absolute */);
+ if (lookup.rnew == NULL) {
+ if (lookup.err.status == HTTP_BAD_REQUEST) {
+ /* This supplies additional information for the default message. */
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(00592)
+ "%s", lookup.err.desc);
+ return HTTP_BAD_REQUEST;
+ }
+
+ /* ### this assumes that dav_lookup_uri() only generates a status
+ * ### that Apache can provide a status line for!! */
+
+ return dav_error_response(r, lookup.err.status, lookup.err.desc);
+ }
+ if (lookup.rnew->status != HTTP_OK) {
+ const char *auth = apr_table_get(lookup.rnew->err_headers_out,
+ "WWW-Authenticate");
+ if (lookup.rnew->status == HTTP_UNAUTHORIZED && auth != NULL) {
+ /* propagate the WWW-Authorization header up from the
+ * subreq so the client sees it. */
+ apr_table_setn(r->err_headers_out, "WWW-Authenticate",
+ apr_pstrdup(r->pool, auth));
+ }
+
+ /* ### how best to report this... */
+ return dav_error_response(r, lookup.rnew->status,
+ "Destination URI had an error.");
+ }
+
+ /* Resolve destination resource */
+ err = dav_get_resource(lookup.rnew, 0 /* label_allowed */,
+ 0 /* use_checked_in */, &resnew);
+ if (err != NULL)
+ return dav_handle_err(r, err, NULL);
+
+ /* are the two resources handled by the same repository? */
+ if (resource->hooks != resnew->hooks) {
+ /* ### this message exposes some backend config, but screw it... */
+ return dav_error_response(r, HTTP_BAD_GATEWAY,
+ "Destination URI is handled by a "
+ "different repository than the source URI. "
+ "MOVE or COPY between repositories is "
+ "not possible.");
+ }
+
+ /* get and parse the overwrite header value */
+ if ((overwrite = dav_get_overwrite(r)) < 0) {
+ /* dav_get_overwrite() supplies additional information for the
+ * default message. */
+ return HTTP_BAD_REQUEST;
+ }
+
+ /* quick failure test: if dest exists and overwrite is false. */
+ if (resnew->exists && !overwrite) {
+ /* Supply some text for the error response body. */
+ return dav_error_response(r, HTTP_PRECONDITION_FAILED,
+ "Destination is not empty and "
+ "Overwrite is not \"T\"");
+ }
+
+ /* are the source and destination the same? */
+ if ((*resource->hooks->is_same_resource)(resource, resnew)) {
+ /* Supply some text for the error response body. */
+ return dav_error_response(r, HTTP_FORBIDDEN,
+ "Source and Destination URIs are the same.");
+
+ }
+
+ is_dir = resource->collection;
+
+ /* get and parse the Depth header value. "0" and "infinity" are legal. */
+ if ((depth = dav_get_depth(r, DAV_INFINITY)) < 0) {
+ /* dav_get_depth() supplies additional information for the
+ * default message. */
+ return HTTP_BAD_REQUEST;
+ }
+ if (depth == 1) {
+ /* This supplies additional information for the default message. */
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(00593)
+ "Depth must be \"0\" or \"infinity\" for COPY or MOVE.");
+ return HTTP_BAD_REQUEST;
+ }
+ if (is_move && is_dir && depth != DAV_INFINITY) {
+ /* This supplies additional information for the default message. */
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(00594)
+ "Depth must be \"infinity\" when moving a collection.");
+ return HTTP_BAD_REQUEST;
+ }
+
+ /*
+ * Check If-Headers and existing locks for each resource in the source.
+ * We will return a 424 response with a DAV:multistatus body.
+ * The multistatus responses will contain the information about any
+ * resource that fails the validation.
+ *
+ * We check the parent resource, too, if this is a MOVE. Moving the
+ * resource effectively removes it from the parent collection, so we
+ * must ensure that we have met the appropriate conditions.
+ *
+ * If a problem occurs with the Request-URI itself, then a plain error
+ * (rather than a multistatus) will be returned.
+ */
+ if ((err = dav_validate_request(r, resource, depth, NULL,
+ &multi_response,
+ (is_move ? DAV_VALIDATE_PARENT
+ : DAV_VALIDATE_RESOURCE
+ | DAV_VALIDATE_NO_MODIFY)
+ | DAV_VALIDATE_USE_424,
+ NULL)) != NULL) {
+ err = dav_push_error(r->pool, err->status, 0,
+ apr_psprintf(r->pool,
+ "Could not %s %s due to a failed "
+ "precondition on the source "
+ "(e.g. locks).",
+ is_move ? "MOVE" : "COPY",
+ ap_escape_html(r->pool, r->uri)),
+ err);
+ return dav_handle_err(r, err, multi_response);
+ }
+
+ /*
+ * Check If-Headers and existing locks for destination. Note that we
+ * use depth==infinity since the target (hierarchy) will be deleted
+ * before the move/copy is completed.
+ *
+ * Note that we are overwriting the target, which implies a DELETE, so
+ * we are subject to the error/response rules as a DELETE. Namely, we
+ * will return a 424 error if any of the validations fail.
+ * (see dav_method_delete() for more information)
+ */
+ if ((err = dav_validate_request(lookup.rnew, resnew, DAV_INFINITY, NULL,
+ &multi_response,
+ DAV_VALIDATE_PARENT
+ | DAV_VALIDATE_USE_424, NULL)) != NULL) {
+ err = dav_push_error(r->pool, err->status, 0,
+ apr_psprintf(r->pool,
+ "Could not MOVE/COPY %s due to a "
+ "failed precondition on the "
+ "destination (e.g. locks).",
+ ap_escape_html(r->pool, r->uri)),
+ err);
+ return dav_handle_err(r, err, multi_response);
+ }
+
+ if (is_dir
+ && depth == DAV_INFINITY
+ && (*resource->hooks->is_parent_resource)(resource, resnew)) {
+ /* Supply some text for the error response body. */
+ return dav_error_response(r, HTTP_FORBIDDEN,
+ "Source collection contains the "
+ "Destination.");
+
+ }
+ if (is_dir
+ && (*resnew->hooks->is_parent_resource)(resnew, resource)) {
+ /* The destination must exist (since it contains the source), and
+ * a condition above implies Overwrite==T. Obviously, we cannot
+ * delete the Destination before the MOVE/COPY, as that would
+ * delete the Source.
+ */
+
+ /* Supply some text for the error response body. */
+ return dav_error_response(r, HTTP_FORBIDDEN,
+ "Destination collection contains the Source "
+ "and Overwrite has been specified.");
+ }
+
+ /* ### for now, we don't need anything in the body */
+ if ((result = ap_discard_request_body(r)) != OK) {
+ return result;
+ }
+
+ if ((err = dav_open_lockdb(r, 0, &lockdb)) != NULL) {
+ /* ### add a higher-level description? */
+ return dav_handle_err(r, err, NULL);
+ }
+
+ /* remove any locks from the old resources */
+ /*
+ * ### this is Yet Another Traversal. if we do a rename(), then we
+ * ### really don't have to do this in some cases since the inode
+ * ### values will remain constant across the move. but we can't
+ * ### know that fact from outside the provider :-(
+ *
+ * ### note that we now have a problem atomicity in the move/copy
+ * ### since a failure after this would have removed locks (technically,
+ * ### this is okay to do, but really...)
+ */
+ if (is_move && lockdb != NULL) {
+ /* ### this is wrong! it blasts direct locks on parent resources */
+ /* ### pass lockdb! */
+ (void)dav_unlock(r, resource, NULL);
+ }
+
+ /* if this is a move, then the source parent collection will be modified */
+ if (is_move) {
+ if ((err = dav_auto_checkout(r, resource, 1 /* parent_only */,
+ &src_av_info)) != NULL) {
+ if (lockdb != NULL)
+ (*lockdb->hooks->close_lockdb)(lockdb);
+
+ /* ### add a higher-level description? */
+ return dav_handle_err(r, err, NULL);
+ }
+ }
+
+ /*
+ * Remember the initial state of the destination, so the lock system
+ * can be notified as to how it changed.
+ */
+ resnew_state = dav_get_resource_state(lookup.rnew, resnew);
+
+ /* In a MOVE operation, the destination is replaced by the source.
+ * In a COPY operation, if the destination exists, is under version
+ * control, and is the same resource type as the source,
+ * then it should not be replaced, but modified to be a copy of
+ * the source.
+ */
+ if (!resnew->exists)
+ replace_dest = 0;
+ else if (is_move || !resource->versioned)
+ replace_dest = 1;
+ else if (resource->type != resnew->type)
+ replace_dest = 1;
+ else if ((resource->collection == 0) != (resnew->collection == 0))
+ replace_dest = 1;
+ else
+ replace_dest = 0;
+
+ /* If the destination must be created or replaced,
+ * make sure the parent collection is writable
+ */
+ if (!resnew->exists || replace_dest) {
+ if ((err = dav_auto_checkout(r, resnew, 1 /*parent_only*/,
+ &dst_av_info)) != NULL) {
+ /* could not make destination writable:
+ * if move, restore state of source parent
+ */
+ if (is_move) {
+ (void)dav_auto_checkin(r, NULL, 1 /* undo */,
+ 0 /*unlock*/, &src_av_info);
+ }
+
+ if (lockdb != NULL)
+ (*lockdb->hooks->close_lockdb)(lockdb);
+
+ /* ### add a higher-level description? */
+ return dav_handle_err(r, err, NULL);
+ }
+ }
+
+ /* If source and destination parents are the same, then
+ * use the same resource object, so status updates to one are reflected
+ * in the other, when doing auto-versioning. Otherwise,
+ * we may try to checkin the parent twice.
+ */
+ if (src_av_info.parent_resource != NULL
+ && dst_av_info.parent_resource != NULL
+ && (*src_av_info.parent_resource->hooks->is_same_resource)
+ (src_av_info.parent_resource, dst_av_info.parent_resource)) {
+
+ dst_av_info.parent_resource = src_av_info.parent_resource;
+ }
+
+ /* If destination is being replaced, remove it first
+ * (we know Ovewrite must be TRUE). Then try to copy/move the resource.
+ */
+ if (replace_dest)
+ err = (*resnew->hooks->remove_resource)(resnew, &multi_response);
+
+ if (err == NULL) {
+ if (is_move)
+ err = (*resource->hooks->move_resource)(resource, resnew,
+ &multi_response);
+ else
+ err = (*resource->hooks->copy_resource)(resource, resnew, depth,
+ &multi_response);
+ }
+
+ /* perform any auto-versioning cleanup */
+ err2 = dav_auto_checkin(r, NULL, err != NULL /* undo if error */,
+ 0 /*unlock*/, &dst_av_info);
+
+ if (is_move) {
+ err3 = dav_auto_checkin(r, NULL, err != NULL /* undo if error */,
+ 0 /*unlock*/, &src_av_info);
+ }
+ else
+ err3 = NULL;
+
+ /* check for error from remove/copy/move operations */
+ if (err != NULL) {
+ if (lockdb != NULL)
+ (*lockdb->hooks->close_lockdb)(lockdb);
+
+ err = dav_push_error(r->pool, err->status, 0,
+ apr_psprintf(r->pool,
+ "Could not MOVE/COPY %s.",
+ ap_escape_html(r->pool, r->uri)),
+ err);
+ return dav_handle_err(r, err, multi_response);
+ }
+
+ /* check for errors from auto-versioning */
+ if (err2 != NULL) {
+ /* just log a warning */
+ err = dav_push_error(r->pool, err2->status, 0,
+ "The MOVE/COPY was successful, but there was a "
+ "problem automatically checking in the "
+ "source parent collection.",
+ err2);
+ dav_log_err(r, err, APLOG_WARNING);
+ }
+ if (err3 != NULL) {
+ /* just log a warning */
+ err = dav_push_error(r->pool, err3->status, 0,
+ "The MOVE/COPY was successful, but there was a "
+ "problem automatically checking in the "
+ "destination or its parent collection.",
+ err3);
+ dav_log_err(r, err, APLOG_WARNING);
+ }
+
+ /* propagate any indirect locks at the target */
+ if (lockdb != NULL) {
+
+ /* notify lock system that we have created/replaced a resource */
+ err = dav_notify_created(r, lockdb, resnew, resnew_state, depth);
+
+ (*lockdb->hooks->close_lockdb)(lockdb);
+
+ if (err != NULL) {
+ /* The move/copy was successful, but the locking failed. */
+ err = dav_push_error(r->pool, err->status, 0,
+ "The MOVE/COPY was successful, but there "
+ "was a problem updating the lock "
+ "information.",
+ err);
+ return dav_handle_err(r, err, NULL);
+ }
+ }
+
+ /* return an appropriate response (HTTP_CREATED or HTTP_NO_CONTENT) */
+ return dav_created(r, lookup.rnew->unparsed_uri, "Destination",
+ resnew_state == DAV_RESOURCE_EXISTS);
+}
+
+/* dav_method_lock: Handler to implement the DAV LOCK method
+ * Returns appropriate HTTP_* response.
+ */
+static int dav_method_lock(request_rec *r)
+{
+ dav_error *err;
+ dav_resource *resource;
+ dav_resource *parent;
+ const dav_hooks_locks *locks_hooks;
+ int result;
+ int depth;
+ int new_lock_request = 0;
+ apr_xml_doc *doc;
+ dav_lock *lock;
+ dav_response *multi_response = NULL;
+ dav_lockdb *lockdb;
+ int resource_state;
+
+ /* If no locks provider, decline the request */
+ locks_hooks = DAV_GET_HOOKS_LOCKS(r);
+ if (locks_hooks == NULL)
+ return DECLINED;
+
+ if ((result = ap_xml_parse_input(r, &doc)) != OK)
+ return result;
+
+ depth = dav_get_depth(r, DAV_INFINITY);
+ if (depth != 0 && depth != DAV_INFINITY) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(00595)
+ "Depth must be 0 or \"infinity\" for LOCK.");
+ return HTTP_BAD_REQUEST;
+ }
+
+ /* Ask repository module to resolve the resource */
+ err = dav_get_resource(r, 0 /* label_allowed */, 0 /* use_checked_in */,
+ &resource);
+ if (err != NULL)
+ return dav_handle_err(r, err, NULL);
+
+ /* Check if parent collection exists */
+ if ((err = resource->hooks->get_parent_resource(resource, &parent)) != NULL) {
+ /* ### add a higher-level description? */
+ return dav_handle_err(r, err, NULL);
+ }
+ if (parent && (!parent->exists || parent->collection != 1)) {
+ err = dav_new_error(r->pool, HTTP_CONFLICT, 0, 0,
+ apr_psprintf(r->pool,
+ "The parent resource of %s does not "
+ "exist or is not a collection.",
+ ap_escape_html(r->pool, r->uri)));
+ return dav_handle_err(r, err, NULL);
+ }
+
+ /*
+ * Open writable. Unless an error occurs, we'll be
+ * writing into the database.
+ */
+ if ((err = (*locks_hooks->open_lockdb)(r, 0, 0, &lockdb)) != NULL) {
+ /* ### add a higher-level description? */
+ return dav_handle_err(r, err, NULL);
+ }
+
+ if (doc != NULL) {
+ if ((err = dav_lock_parse_lockinfo(r, resource, lockdb, doc,
+ &lock)) != NULL) {
+ /* ### add a higher-level description to err? */
+ goto error;
+ }
+ new_lock_request = 1;
+
+ lock->auth_user = apr_pstrdup(r->pool, r->user);
+ }
+
+ resource_state = dav_get_resource_state(r, resource);
+
+ /*
+ * Check If-Headers and existing locks.
+ *
+ * If this will create a locknull resource, then the LOCK will affect
+ * the parent collection (much like a PUT/MKCOL). For that case, we must
+ * validate the parent resource's conditions.
+ */
+ if ((err = dav_validate_request(r, resource, depth, NULL, &multi_response,
+ (resource_state == DAV_RESOURCE_NULL
+ ? DAV_VALIDATE_PARENT
+ : DAV_VALIDATE_RESOURCE)
+ | (new_lock_request ? lock->scope : 0)
+ | DAV_VALIDATE_ADD_LD,
+ lockdb)) != OK) {
+ err = dav_push_error(r->pool, err->status, 0,
+ apr_psprintf(r->pool,
+ "Could not LOCK %s due to a failed "
+ "precondition (e.g. other locks).",
+ ap_escape_html(r->pool, r->uri)),
+ err);
+ goto error;
+ }
+
+ if (new_lock_request == 0) {
+ dav_locktoken_list *ltl;
+
+ /*
+ * Refresh request
+ * ### Assumption: We can renew multiple locks on the same resource
+ * ### at once. First harvest all the positive lock-tokens given in
+ * ### the If header. Then modify the lock entries for this resource
+ * ### with the new Timeout val.
+ */
+
+ if ((err = dav_get_locktoken_list(r, &ltl)) != NULL) {
+ err = dav_push_error(r->pool, err->status, 0,
+ apr_psprintf(r->pool,
+ "The lock refresh for %s failed "
+ "because no lock tokens were "
+ "specified in an \"If:\" "
+ "header.",
+ ap_escape_html(r->pool, r->uri)),
+ err);
+ goto error;
+ }
+
+ if ((err = (*locks_hooks->refresh_locks)(lockdb, resource, ltl,
+ dav_get_timeout(r),
+ &lock)) != NULL) {
+ /* ### add a higher-level description to err? */
+ goto error;
+ }
+ } else {
+ /* New lock request */
+ char *locktoken_txt;
+ dav_dir_conf *conf;
+
+ conf = (dav_dir_conf *)ap_get_module_config(r->per_dir_config,
+ &dav_module);
+
+ /* apply lower bound (if any) from DAVMinTimeout directive */
+ if (lock->timeout != DAV_TIMEOUT_INFINITE
+ && lock->timeout < time(NULL) + conf->locktimeout)
+ lock->timeout = time(NULL) + conf->locktimeout;
+
+ err = dav_add_lock(r, resource, lockdb, lock, &multi_response);
+ if (err != NULL) {
+ /* ### add a higher-level description to err? */
+ goto error;
+ }
+
+ locktoken_txt = apr_pstrcat(r->pool, "<",
+ (*locks_hooks->format_locktoken)(r->pool,
+ lock->locktoken),
+ ">", NULL);
+
+ apr_table_setn(r->headers_out, "Lock-Token", locktoken_txt);
+ }
+
+ (*locks_hooks->close_lockdb)(lockdb);
+
+ r->status = HTTP_OK;
+ ap_set_content_type(r, DAV_XML_CONTENT_TYPE);
+
+ ap_rputs(DAV_XML_HEADER DEBUG_CR "<D:prop xmlns:D=\"DAV:\">" DEBUG_CR, r);
+ if (lock == NULL)
+ ap_rputs("<D:lockdiscovery/>" DEBUG_CR, r);
+ else {
+ ap_rprintf(r,
+ "<D:lockdiscovery>" DEBUG_CR
+ "%s" DEBUG_CR
+ "</D:lockdiscovery>" DEBUG_CR,
+ dav_lock_get_activelock(r, lock, NULL));
+ }
+ ap_rputs("</D:prop>", r);
+
+ /* the response has been sent. */
+ return DONE;
+
+ error:
+ (*locks_hooks->close_lockdb)(lockdb);
+ return dav_handle_err(r, err, multi_response);
+}
+
+/* dav_method_unlock: Handler to implement the DAV UNLOCK method
+ * Returns appropriate HTTP_* response.
+ */
+static int dav_method_unlock(request_rec *r)
+{
+ dav_error *err;
+ dav_resource *resource;
+ const dav_hooks_locks *locks_hooks;
+ int result;
+ const char *const_locktoken_txt;
+ char *locktoken_txt;
+ dav_locktoken *locktoken = NULL;
+ int resource_state;
+ dav_response *multi_response;
+
+ /* If no locks provider, decline the request */
+ locks_hooks = DAV_GET_HOOKS_LOCKS(r);
+ if (locks_hooks == NULL)
+ return DECLINED;
+
+ if ((const_locktoken_txt = apr_table_get(r->headers_in,
+ "Lock-Token")) == NULL) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(00596)
+ "Unlock failed (%s): "
+ "No Lock-Token specified in header", r->filename);
+ return HTTP_BAD_REQUEST;
+ }
+
+ locktoken_txt = apr_pstrdup(r->pool, const_locktoken_txt);
+ if (locktoken_txt[0] != '<') {
+ /* ### should provide more specifics... */
+ return HTTP_BAD_REQUEST;
+ }
+ locktoken_txt++;
+
+ if (locktoken_txt[strlen(locktoken_txt) - 1] != '>') {
+ /* ### should provide more specifics... */
+ return HTTP_BAD_REQUEST;
+ }
+ locktoken_txt[strlen(locktoken_txt) - 1] = '\0';
+
+ if ((err = (*locks_hooks->parse_locktoken)(r->pool, locktoken_txt,
+ &locktoken)) != NULL) {
+ err = dav_push_error(r->pool, HTTP_BAD_REQUEST, 0,
+ apr_psprintf(r->pool,
+ "The UNLOCK on %s failed -- an "
+ "invalid lock token was specified "
+ "in the \"If:\" header.",
+ ap_escape_html(r->pool, r->uri)),
+ err);
+ return dav_handle_err(r, err, NULL);
+ }
+
+ /* Ask repository module to resolve the resource */
+ err = dav_get_resource(r, 0 /* label_allowed */, 0 /* use_checked_in */,
+ &resource);
+ if (err != NULL)
+ return dav_handle_err(r, err, NULL);
+
+ resource_state = dav_get_resource_state(r, resource);
+
+ /*
+ * Check If-Headers and existing locks.
+ *
+ * Note: depth == 0 normally requires no multistatus response. However,
+ * if we pass DAV_VALIDATE_PARENT, then we could get an error on a URI
+ * other than the Request-URI, thereby requiring a multistatus.
+ *
+ * If the resource is a locknull resource, then the UNLOCK will affect
+ * the parent collection (much like a delete). For that case, we must
+ * validate the parent resource's conditions.
+ */
+ if ((err = dav_validate_request(r, resource, 0, locktoken,
+ &multi_response,
+ resource_state == DAV_RESOURCE_LOCK_NULL
+ ? DAV_VALIDATE_PARENT
+ : DAV_VALIDATE_RESOURCE, NULL)) != NULL) {
+ /* ### add a higher-level description? */
+ return dav_handle_err(r, err, multi_response);
+ }
+
+ /* ### RFC 2518 s. 8.11: If this resource is locked by locktoken,
+ * _all_ resources locked by locktoken are released. It does not say
+ * resource has to be the root of an infinte lock. Thus, an UNLOCK
+ * on any part of an infinte lock will remove the lock on all resources.
+ *
+ * For us, if r->filename represents an indirect lock (part of an infinity lock),
+ * we must actually perform an UNLOCK on the direct lock for this resource.
+ */
+ if ((result = dav_unlock(r, resource, locktoken)) != OK) {
+ return result;
+ }
+
+ return HTTP_NO_CONTENT;
+}
+
+static int dav_method_vsn_control(request_rec *r)
+{
+ dav_resource *resource;
+ int resource_state;
+ dav_auto_version_info av_info;
+ const dav_hooks_locks *locks_hooks = DAV_GET_HOOKS_LOCKS(r);
+ const dav_hooks_vsn *vsn_hooks = DAV_GET_HOOKS_VSN(r);
+ dav_error *err;
+ apr_xml_doc *doc;
+ const char *target = NULL;
+ int result;
+
+ /* if no versioning provider, decline the request */
+ if (vsn_hooks == NULL)
+ return DECLINED;
+
+ /* ask repository module to resolve the resource */
+ err = dav_get_resource(r, 0 /* label_allowed */, 0 /* use_checked_in */,
+ &resource);
+ if (err != NULL)
+ return dav_handle_err(r, err, NULL);
+
+ /* remember the pre-creation resource state */
+ resource_state = dav_get_resource_state(r, resource);
+
+ /* parse the request body (may be a version-control element) */
+ if ((result = ap_xml_parse_input(r, &doc)) != OK) {
+ return result;
+ }
+ /* note: doc == NULL if no request body */
+
+ if (doc != NULL) {
+ const apr_xml_elem *child;
+ apr_size_t tsize;
+
+ if (!dav_validate_root(doc, "version-control")) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(00597)
+ "The request body does not contain "
+ "a \"version-control\" element.");
+ return HTTP_BAD_REQUEST;
+ }
+
+ /* get the version URI */
+ if ((child = dav_find_child(doc->root, "version")) == NULL) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(00598)
+ "The \"version-control\" element does not contain "
+ "a \"version\" element.");
+ return HTTP_BAD_REQUEST;
+ }
+
+ if ((child = dav_find_child(child, "href")) == NULL) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(00599)
+ "The \"version\" element does not contain "
+ "an \"href\" element.");
+ return HTTP_BAD_REQUEST;
+ }
+
+ /* get version URI */
+ apr_xml_to_text(r->pool, child, APR_XML_X2T_INNER, NULL, NULL,
+ &target, &tsize);
+ if (tsize == 0) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(00600)
+ "An \"href\" element does not contain a URI.");
+ return HTTP_BAD_REQUEST;
+ }
+ }
+
+ /* Check request preconditions */
+
+ /* ### need a general mechanism for reporting precondition violations
+ * ### (should be returning XML document for 403/409 responses)
+ */
+
+ /* if not versioning existing resource, must specify version to select */
+ if (!resource->exists && target == NULL) {
+ err = dav_new_error(r->pool, HTTP_CONFLICT, 0, 0,
+ "<DAV:initial-version-required/>");
+ return dav_handle_err(r, err, NULL);
+ }
+ else if (resource->exists) {
+ /* cannot add resource to existing version history */
+ if (target != NULL) {
+ err = dav_new_error(r->pool, HTTP_CONFLICT, 0, 0,
+ "<DAV:cannot-add-to-existing-history/>");
+ return dav_handle_err(r, err, NULL);
+ }
+
+ /* resource must be unversioned and versionable, or version selector */
+ if (resource->type != DAV_RESOURCE_TYPE_REGULAR
+ || (!resource->versioned && !(vsn_hooks->versionable)(resource))) {
+ err = dav_new_error(r->pool, HTTP_CONFLICT, 0, 0,
+ "<DAV:must-be-versionable/>");
+ return dav_handle_err(r, err, NULL);
+ }
+
+ /* the DeltaV spec says if resource is a version selector,
+ * then VERSION-CONTROL is a no-op
+ */
+ if (resource->versioned) {
+ /* set the Cache-Control header, per the spec */
+ apr_table_setn(r->headers_out, "Cache-Control", "no-cache");
+
+ /* no body */
+ ap_set_content_length(r, 0);
+
+ return DONE;
+ }
+ }
+
+ /* Check If-Headers and existing locks */
+ /* Note: depth == 0. Implies no need for a multistatus response. */
+ if ((err = dav_validate_request(r, resource, 0, NULL, NULL,
+ resource_state == DAV_RESOURCE_NULL ?
+ DAV_VALIDATE_PARENT :
+ DAV_VALIDATE_RESOURCE, NULL)) != NULL) {
+ return dav_handle_err(r, err, NULL);
+ }
+
+ /* if in versioned collection, make sure parent is checked out */
+ if ((err = dav_auto_checkout(r, resource, 1 /* parent_only */,
+ &av_info)) != NULL) {
+ return dav_handle_err(r, err, NULL);
+ }
+
+ /* attempt to version-control the resource */
+ if ((err = (*vsn_hooks->vsn_control)(resource, target)) != NULL) {
+ dav_auto_checkin(r, resource, 1 /*undo*/, 0 /*unlock*/, &av_info);
+ err = dav_push_error(r->pool, HTTP_CONFLICT, 0,
+ apr_psprintf(r->pool,
+ "Could not VERSION-CONTROL resource %s.",
+ ap_escape_html(r->pool, r->uri)),
+ err);
+ return dav_handle_err(r, err, NULL);
+ }
+
+ /* revert writability of parent directory */
+ err = dav_auto_checkin(r, resource, 0 /*undo*/, 0 /*unlock*/, &av_info);
+ if (err != NULL) {
+ /* just log a warning */
+ err = dav_push_error(r->pool, err->status, 0,
+ "The VERSION-CONTROL was successful, but there "
+ "was a problem automatically checking in "
+ "the parent collection.",
+ err);
+ dav_log_err(r, err, APLOG_WARNING);
+ }
+
+ /* if the resource is lockable, let lock system know of new resource */
+ if (locks_hooks != NULL
+ && (*locks_hooks->get_supportedlock)(resource) != NULL) {
+ dav_lockdb *lockdb;
+
+ if ((err = (*locks_hooks->open_lockdb)(r, 0, 0, &lockdb)) != NULL) {
+ /* The resource creation was successful, but the locking failed. */
+ err = dav_push_error(r->pool, err->status, 0,
+ "The VERSION-CONTROL was successful, but there "
+ "was a problem opening the lock database "
+ "which prevents inheriting locks from the "
+ "parent resources.",
+ err);
+ return dav_handle_err(r, err, NULL);
+ }
+
+ /* notify lock system that we have created/replaced a resource */
+ err = dav_notify_created(r, lockdb, resource, resource_state, 0);
+
+ (*locks_hooks->close_lockdb)(lockdb);
+
+ if (err != NULL) {
+ /* The dir creation was successful, but the locking failed. */
+ err = dav_push_error(r->pool, err->status, 0,
+ "The VERSION-CONTROL was successful, but there "
+ "was a problem updating its lock "
+ "information.",
+ err);
+ return dav_handle_err(r, err, NULL);
+ }
+ }
+
+ /* set the Cache-Control header, per the spec */
+ apr_table_setn(r->headers_out, "Cache-Control", "no-cache");
+
+ /* return an appropriate response (HTTP_CREATED) */
+ return dav_created(r, resource->uri, "Version selector", 0 /*replaced*/);
+}
+
+/* handle the CHECKOUT method */
+static int dav_method_checkout(request_rec *r)
+{
+ dav_resource *resource;
+ dav_resource *working_resource;
+ const dav_hooks_vsn *vsn_hooks = DAV_GET_HOOKS_VSN(r);
+ dav_error *err;
+ int result;
+ apr_xml_doc *doc;
+ int apply_to_vsn = 0;
+ int is_unreserved = 0;
+ int is_fork_ok = 0;
+ int create_activity = 0;
+ apr_array_header_t *activities = NULL;
+
+ /* If no versioning provider, decline the request */
+ if (vsn_hooks == NULL)
+ return DECLINED;
+
+ if ((result = ap_xml_parse_input(r, &doc)) != OK)
+ return result;
+
+ if (doc != NULL) {
+ const apr_xml_elem *aset;
+
+ if (!dav_validate_root(doc, "checkout")) {
+ /* This supplies additional information for the default msg. */
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(00601)
+ "The request body, if present, must be a "
+ "DAV:checkout element.");
+ return HTTP_BAD_REQUEST;
+ }
+
+ if (dav_find_child(doc->root, "apply-to-version") != NULL) {
+ if (apr_table_get(r->headers_in, "label") != NULL) {
+ /* ### we want generic 403/409 XML reporting here */
+ /* ### DAV:must-not-have-label-and-apply-to-version */
+ return dav_error_response(r, HTTP_CONFLICT,
+ "DAV:apply-to-version cannot be "
+ "used in conjunction with a "
+ "Label header.");
+ }
+ apply_to_vsn = 1;
+ }
+
+ is_unreserved = dav_find_child(doc->root, "unreserved") != NULL;
+ is_fork_ok = dav_find_child(doc->root, "fork-ok") != NULL;
+
+ if ((aset = dav_find_child(doc->root, "activity-set")) != NULL) {
+ if (dav_find_child(aset, "new") != NULL) {
+ create_activity = 1;
+ }
+ else {
+ const apr_xml_elem *child = aset->first_child;
+
+ activities = apr_array_make(r->pool, 1, sizeof(const char *));
+
+ for (; child != NULL; child = child->next) {
+ if (child->ns == APR_XML_NS_DAV_ID
+ && strcmp(child->name, "href") == 0) {
+ const char *href;
+
+ href = dav_xml_get_cdata(child, r->pool,
+ 1 /* strip_white */);
+ *(const char **)apr_array_push(activities) = href;
+ }
+ }
+
+ if (activities->nelts == 0) {
+ /* no href's is a DTD violation:
+ <!ELEMENT activity-set (href+ | new)>
+ */
+
+ /* This supplies additional info for the default msg. */
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(00602)
+ "Within the DAV:activity-set element, the "
+ "DAV:new element must be used, or at least "
+ "one DAV:href must be specified.");
+ return HTTP_BAD_REQUEST;
+ }
+ }
+ }
+ }
+
+ /* Ask repository module to resolve the resource */
+ err = dav_get_resource(r, 1 /*label_allowed*/, apply_to_vsn, &resource);
+ if (err != NULL)
+ return dav_handle_err(r, err, NULL);
+
+ if (!resource->exists) {
+ /* Apache will supply a default error for this. */
+ return HTTP_NOT_FOUND;
+ }
+
+ /* Check the state of the resource: must be a file or collection,
+ * must be versioned, and must not already be checked out.
+ */
+ if (resource->type != DAV_RESOURCE_TYPE_REGULAR
+ && resource->type != DAV_RESOURCE_TYPE_VERSION) {
+ return dav_error_response(r, HTTP_CONFLICT,
+ "Cannot checkout this type of resource.");
+ }
+
+ if (!resource->versioned) {
+ return dav_error_response(r, HTTP_CONFLICT,
+ "Cannot checkout unversioned resource.");
+ }
+
+ if (resource->working) {
+ return dav_error_response(r, HTTP_CONFLICT,
+ "The resource is already checked out to the workspace.");
+ }
+
+ /* ### do lock checks, once behavior is defined */
+
+ /* Do the checkout */
+ if ((err = (*vsn_hooks->checkout)(resource, 0 /*auto_checkout*/,
+ is_unreserved, is_fork_ok,
+ create_activity, activities,
+ &working_resource)) != NULL) {
+ err = dav_push_error(r->pool, HTTP_CONFLICT, 0,
+ apr_psprintf(r->pool,
+ "Could not CHECKOUT resource %s.",
+ ap_escape_html(r->pool, r->uri)),
+ err);
+ return dav_handle_err(r, err, NULL);
+ }
+
+ /* set the Cache-Control header, per the spec */
+ apr_table_setn(r->headers_out, "Cache-Control", "no-cache");
+
+ /* if no working resource created, return OK,
+ * else return CREATED with working resource URL in Location header
+ */
+ if (working_resource == NULL) {
+ /* no body */
+ ap_set_content_length(r, 0);
+ return DONE;
+ }
+
+ return dav_created(r, working_resource->uri, "Checked-out resource", 0);
+}
+
+/* handle the UNCHECKOUT method */
+static int dav_method_uncheckout(request_rec *r)
+{
+ dav_resource *resource;
+ const dav_hooks_vsn *vsn_hooks = DAV_GET_HOOKS_VSN(r);
+ dav_error *err;
+ int result;
+
+ /* If no versioning provider, decline the request */
+ if (vsn_hooks == NULL)
+ return DECLINED;
+
+ if ((result = ap_discard_request_body(r)) != OK) {
+ return result;
+ }
+
+ /* Ask repository module to resolve the resource */
+ err = dav_get_resource(r, 0 /* label_allowed */, 0 /* use_checked_in */,
+ &resource);
+ if (err != NULL)
+ return dav_handle_err(r, err, NULL);
+
+ if (!resource->exists) {
+ /* Apache will supply a default error for this. */
+ return HTTP_NOT_FOUND;
+ }
+
+ /* Check the state of the resource: must be a file or collection,
+ * must be versioned, and must be checked out.
+ */
+ if (resource->type != DAV_RESOURCE_TYPE_REGULAR) {
+ return dav_error_response(r, HTTP_CONFLICT,
+ "Cannot uncheckout this type of resource.");
+ }
+
+ if (!resource->versioned) {
+ return dav_error_response(r, HTTP_CONFLICT,
+ "Cannot uncheckout unversioned resource.");
+ }
+
+ if (!resource->working) {
+ return dav_error_response(r, HTTP_CONFLICT,
+ "The resource is not checked out to the workspace.");
+ }
+
+ /* ### do lock checks, once behavior is defined */
+
+ /* Do the uncheckout */
+ if ((err = (*vsn_hooks->uncheckout)(resource)) != NULL) {
+ err = dav_push_error(r->pool, HTTP_CONFLICT, 0,
+ apr_psprintf(r->pool,
+ "Could not UNCHECKOUT resource %s.",
+ ap_escape_html(r->pool, r->uri)),
+ err);
+ return dav_handle_err(r, err, NULL);
+ }
+
+ /* no body */
+ ap_set_content_length(r, 0);
+
+ return DONE;
+}
+
+/* handle the CHECKIN method */
+static int dav_method_checkin(request_rec *r)
+{
+ dav_resource *resource;
+ dav_resource *new_version;
+ const dav_hooks_vsn *vsn_hooks = DAV_GET_HOOKS_VSN(r);
+ dav_error *err;
+ int result;
+ apr_xml_doc *doc;
+ int keep_checked_out = 0;
+
+ /* If no versioning provider, decline the request */
+ if (vsn_hooks == NULL)
+ return DECLINED;
+
+ if ((result = ap_xml_parse_input(r, &doc)) != OK)
+ return result;
+
+ if (doc != NULL) {
+ if (!dav_validate_root(doc, "checkin")) {
+ /* This supplies additional information for the default msg. */
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(00603)
+ "The request body, if present, must be a "
+ "DAV:checkin element.");
+ return HTTP_BAD_REQUEST;
+ }
+
+ keep_checked_out = dav_find_child(doc->root, "keep-checked-out") != NULL;
+ }
+
+ /* Ask repository module to resolve the resource */
+ err = dav_get_resource(r, 0 /* label_allowed */, 0 /* use_checked_in */,
+ &resource);
+ if (err != NULL)
+ return dav_handle_err(r, err, NULL);
+
+ if (!resource->exists) {
+ /* Apache will supply a default error for this. */
+ return HTTP_NOT_FOUND;
+ }
+
+ /* Check the state of the resource: must be a file or collection,
+ * must be versioned, and must be checked out.
+ */
+ if (resource->type != DAV_RESOURCE_TYPE_REGULAR) {
+ return dav_error_response(r, HTTP_CONFLICT,
+ "Cannot checkin this type of resource.");
+ }
+
+ if (!resource->versioned) {
+ return dav_error_response(r, HTTP_CONFLICT,
+ "Cannot checkin unversioned resource.");
+ }
+
+ if (!resource->working) {
+ return dav_error_response(r, HTTP_CONFLICT,
+ "The resource is not checked out.");
+ }
+
+ /* ### do lock checks, once behavior is defined */
+
+ /* Do the checkin */
+ if ((err = (*vsn_hooks->checkin)(resource, keep_checked_out, &new_version))
+ != NULL) {
+ err = dav_push_error(r->pool, HTTP_CONFLICT, 0,
+ apr_psprintf(r->pool,
+ "Could not CHECKIN resource %s.",
+ ap_escape_html(r->pool, r->uri)),
+ err);
+ return dav_handle_err(r, err, NULL);
+ }
+
+ return dav_created(r, new_version->uri, "Version", 0);
+}
+
+static int dav_method_update(request_rec *r)
+{
+ dav_resource *resource;
+ dav_resource *version = NULL;
+ const dav_hooks_vsn *vsn_hooks = DAV_GET_HOOKS_VSN(r);
+ apr_xml_doc *doc;
+ apr_xml_elem *child;
+ int is_label = 0;
+ int depth;
+ int result;
+ apr_size_t tsize;
+ const char *target;
+ dav_response *multi_response;
+ dav_error *err;
+ dav_lookup_result lookup;
+
+ /* If no versioning provider, or UPDATE not supported,
+ * decline the request */
+ if (vsn_hooks == NULL || vsn_hooks->update == NULL)
+ return DECLINED;
+
+ if ((depth = dav_get_depth(r, 0)) < 0) {
+ /* dav_get_depth() supplies additional information for the
+ * default message. */
+ return HTTP_BAD_REQUEST;
+ }
+
+ /* parse the request body */
+ if ((result = ap_xml_parse_input(r, &doc)) != OK) {
+ return result;
+ }
+
+ if (doc == NULL || !dav_validate_root(doc, "update")) {
+ /* This supplies additional information for the default message. */
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(00604)
+ "The request body does not contain "
+ "an \"update\" element.");
+ return HTTP_BAD_REQUEST;
+ }
+
+ /* check for label-name or version element, but not both */
+ if ((child = dav_find_child(doc->root, "label-name")) != NULL)
+ is_label = 1;
+ else if ((child = dav_find_child(doc->root, "version")) != NULL) {
+ /* get the href element */
+ if ((child = dav_find_child(child, "href")) == NULL) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(00605)
+ "The version element does not contain "
+ "an \"href\" element.");
+ return HTTP_BAD_REQUEST;
+ }
+ }
+ else {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(00606)
+ "The \"update\" element does not contain "
+ "a \"label-name\" or \"version\" element.");
+ return HTTP_BAD_REQUEST;
+ }
+
+ /* a depth greater than zero is only allowed for a label */
+ if (!is_label && depth != 0) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(00607)
+ "Depth must be zero for UPDATE with a version");
+ return HTTP_BAD_REQUEST;
+ }
+
+ /* get the target value (a label or a version URI) */
+ apr_xml_to_text(r->pool, child, APR_XML_X2T_INNER, NULL, NULL,
+ &target, &tsize);
+ if (tsize == 0) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(00608)
+ "A \"label-name\" or \"href\" element does not contain "
+ "any content.");
+ return HTTP_BAD_REQUEST;
+ }
+
+ /* Ask repository module to resolve the resource */
+ err = dav_get_resource(r, 0 /* label_allowed */, 0 /* use_checked_in */,
+ &resource);
+ if (err != NULL)
+ return dav_handle_err(r, err, NULL);
+
+ if (!resource->exists) {
+ /* Apache will supply a default error for this. */
+ return HTTP_NOT_FOUND;
+ }
+
+ /* ### need a general mechanism for reporting precondition violations
+ * ### (should be returning XML document for 403/409 responses)
+ */
+ if (resource->type != DAV_RESOURCE_TYPE_REGULAR
+ || !resource->versioned || resource->working) {
+ return dav_error_response(r, HTTP_CONFLICT,
+ "<DAV:must-be-checked-in-version-controlled-resource>");
+ }
+
+ /* if target is a version, resolve the version resource */
+ /* ### dav_lookup_uri only allows absolute URIs; is that OK? */
+ if (!is_label) {
+ lookup = dav_lookup_uri(target, r, 0 /* must_be_absolute */);
+ if (lookup.rnew == NULL) {
+ if (lookup.err.status == HTTP_BAD_REQUEST) {
+ /* This supplies additional information for the default message. */
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(00609)
+ "%s", lookup.err.desc);
+ return HTTP_BAD_REQUEST;
+ }
+
+ /* ### this assumes that dav_lookup_uri() only generates a status
+ * ### that Apache can provide a status line for!! */
+
+ return dav_error_response(r, lookup.err.status, lookup.err.desc);
+ }
+ if (lookup.rnew->status != HTTP_OK) {
+ /* ### how best to report this... */
+ return dav_error_response(r, lookup.rnew->status,
+ "Version URI had an error.");
+ }
+
+ /* resolve version resource */
+ err = dav_get_resource(lookup.rnew, 0 /* label_allowed */,
+ 0 /* use_checked_in */, &version);
+ if (err != NULL)
+ return dav_handle_err(r, err, NULL);
+
+ /* NULL out target, since we're using a version resource */
+ target = NULL;
+ }
+
+ /* do the UPDATE operation */
+ err = (*vsn_hooks->update)(resource, version, target, depth, &multi_response);
+
+ if (err != NULL) {
+ err = dav_push_error(r->pool, err->status, 0,
+ apr_psprintf(r->pool,
+ "Could not UPDATE %s.",
+ ap_escape_html(r->pool, r->uri)),
+ err);
+ return dav_handle_err(r, err, multi_response);
+ }
+
+ /* set the Cache-Control header, per the spec */
+ apr_table_setn(r->headers_out, "Cache-Control", "no-cache");
+
+ /* no body */
+ ap_set_content_length(r, 0);
+
+ return DONE;
+}
+
+/* context maintained during LABEL treewalk */
+typedef struct dav_label_walker_ctx
+{
+ /* input: */
+ dav_walk_params w;
+
+ /* label being manipulated */
+ const char *label;
+
+ /* label operation */
+ int label_op;
+#define DAV_LABEL_ADD 1
+#define DAV_LABEL_SET 2
+#define DAV_LABEL_REMOVE 3
+
+ /* version provider hooks */
+ const dav_hooks_vsn *vsn_hooks;
+
+} dav_label_walker_ctx;
+
+static dav_error * dav_label_walker(dav_walk_resource *wres, int calltype)
+{
+ dav_label_walker_ctx *ctx = wres->walk_ctx;
+ dav_error *err = NULL;
+
+ /* Check the state of the resource: must be a version or
+ * non-checkedout version selector
+ */
+ /* ### need a general mechanism for reporting precondition violations
+ * ### (should be returning XML document for 403/409 responses)
+ */
+ if (wres->resource->type != DAV_RESOURCE_TYPE_VERSION &&
+ (wres->resource->type != DAV_RESOURCE_TYPE_REGULAR
+ || !wres->resource->versioned)) {
+ err = dav_new_error(ctx->w.pool, HTTP_CONFLICT, 0, 0,
+ "<DAV:must-be-version-or-version-selector/>");
+ }
+ else if (wres->resource->working) {
+ err = dav_new_error(ctx->w.pool, HTTP_CONFLICT, 0, 0,
+ "<DAV:must-not-be-checked-out/>");
+ }
+ else {
+ /* do the label operation */
+ if (ctx->label_op == DAV_LABEL_REMOVE)
+ err = (*ctx->vsn_hooks->remove_label)(wres->resource, ctx->label);
+ else
+ err = (*ctx->vsn_hooks->add_label)(wres->resource, ctx->label,
+ ctx->label_op == DAV_LABEL_SET);
+ }
+
+ if (err != NULL) {
+ /* ### need utility routine to add response with description? */
+ dav_add_response(wres, err->status, NULL);
+ wres->response->desc = err->desc;
+ }
+
+ return NULL;
+}
+
+static int dav_method_label(request_rec *r)
+{
+ dav_resource *resource;
+ const dav_hooks_vsn *vsn_hooks = DAV_GET_HOOKS_VSN(r);
+ apr_xml_doc *doc;
+ apr_xml_elem *child;
+ int depth;
+ int result;
+ apr_size_t tsize;
+ dav_error *err;
+ dav_label_walker_ctx ctx = { { 0 } };
+ dav_response *multi_status;
+
+ /* If no versioning provider, or the provider doesn't support
+ * labels, decline the request */
+ if (vsn_hooks == NULL || vsn_hooks->add_label == NULL)
+ return DECLINED;
+
+ /* Ask repository module to resolve the resource */
+ err = dav_get_resource(r, 1 /* label_allowed */, 0 /* use_checked_in */,
+ &resource);
+ if (err != NULL)
+ return dav_handle_err(r, err, NULL);
+ if (!resource->exists) {
+ /* Apache will supply a default error for this. */
+ return HTTP_NOT_FOUND;
+ }
+
+ if ((depth = dav_get_depth(r, 0)) < 0) {
+ /* dav_get_depth() supplies additional information for the
+ * default message. */
+ return HTTP_BAD_REQUEST;
+ }
+
+ /* parse the request body */
+ if ((result = ap_xml_parse_input(r, &doc)) != OK) {
+ return result;
+ }
+
+ if (doc == NULL || !dav_validate_root(doc, "label")) {
+ /* This supplies additional information for the default message. */
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(00610)
+ "The request body does not contain "
+ "a \"label\" element.");
+ return HTTP_BAD_REQUEST;
+ }
+
+ /* check for add, set, or remove element */
+ if ((child = dav_find_child(doc->root, "add")) != NULL) {
+ ctx.label_op = DAV_LABEL_ADD;
+ }
+ else if ((child = dav_find_child(doc->root, "set")) != NULL) {
+ ctx.label_op = DAV_LABEL_SET;
+ }
+ else if ((child = dav_find_child(doc->root, "remove")) != NULL) {
+ ctx.label_op = DAV_LABEL_REMOVE;
+ }
+ else {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(00611)
+ "The \"label\" element does not contain "
+ "an \"add\", \"set\", or \"remove\" element.");
+ return HTTP_BAD_REQUEST;
+ }
+
+ /* get the label string */
+ if ((child = dav_find_child(child, "label-name")) == NULL) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(00612)
+ "The label command element does not contain "
+ "a \"label-name\" element.");
+ return HTTP_BAD_REQUEST;
+ }
+
+ apr_xml_to_text(r->pool, child, APR_XML_X2T_INNER, NULL, NULL,
+ &ctx.label, &tsize);
+ if (tsize == 0) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(00613)
+ "A \"label-name\" element does not contain "
+ "a label name.");
+ return HTTP_BAD_REQUEST;
+ }
+
+ /* do the label operation walk */
+ ctx.w.walk_type = DAV_WALKTYPE_NORMAL;
+ ctx.w.func = dav_label_walker;
+ ctx.w.walk_ctx = &ctx;
+ ctx.w.pool = r->pool;
+ ctx.w.root = resource;
+ ctx.vsn_hooks = vsn_hooks;
+
+ err = (*resource->hooks->walk)(&ctx.w, depth, &multi_status);
+
+ if (err != NULL) {
+ /* some sort of error occurred which terminated the walk */
+ err = dav_push_error(r->pool, err->status, 0,
+ "The LABEL operation was terminated prematurely.",
+ err);
+ return dav_handle_err(r, err, multi_status);
+ }
+
+ if (multi_status != NULL) {
+ /* One or more resources had errors. If depth was zero, convert
+ * response to simple error, else make sure there is an
+ * overall error to pass to dav_handle_err()
+ */
+ if (depth == 0) {
+ err = dav_new_error(r->pool, multi_status->status, 0, 0,
+ multi_status->desc);
+ multi_status = NULL;
+ }
+ else {
+ err = dav_new_error(r->pool, HTTP_MULTI_STATUS, 0, 0,
+ "Errors occurred during the LABEL operation.");
+ }
+
+ return dav_handle_err(r, err, multi_status);
+ }
+
+ /* set the Cache-Control header, per the spec */
+ apr_table_setn(r->headers_out, "Cache-Control", "no-cache");
+
+ /* no body */
+ ap_set_content_length(r, 0);
+
+ return DONE;
+}
+
+static int dav_method_report(request_rec *r)
+{
+ dav_resource *resource;
+ const dav_hooks_vsn *vsn_hooks = DAV_GET_HOOKS_VSN(r);
+ int result;
+ int label_allowed;
+ apr_xml_doc *doc;
+ dav_error *err;
+
+ /* If no versioning provider, decline the request */
+ if (vsn_hooks == NULL)
+ return DECLINED;
+
+ if ((result = ap_xml_parse_input(r, &doc)) != OK)
+ return result;
+ if (doc == NULL) {
+ /* This supplies additional information for the default msg. */
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(00614)
+ "The request body must specify a report.");
+ return HTTP_BAD_REQUEST;
+ }
+
+ /* Ask repository module to resolve the resource.
+ * First determine whether a Target-Selector header is allowed
+ * for this report.
+ */
+ label_allowed = (*vsn_hooks->report_label_header_allowed)(doc);
+ err = dav_get_resource(r, label_allowed, 0 /* use_checked_in */,
+ &resource);
+ if (err != NULL)
+ return dav_handle_err(r, err, NULL);
+
+ if (!resource->exists) {
+ /* Apache will supply a default error for this. */
+ return HTTP_NOT_FOUND;
+ }
+
+ /* set up defaults for the report response */
+ r->status = HTTP_OK;
+ ap_set_content_type(r, DAV_XML_CONTENT_TYPE);
+
+ /* run report hook */
+ if ((err = (*vsn_hooks->deliver_report)(r, resource, doc,
+ r->output_filters)) != NULL) {
+ if (! r->sent_bodyct)
+ /* No data has been sent to client yet; throw normal error. */
+ return dav_handle_err(r, err, NULL);
+
+ /* If an error occurred during the report delivery, there's
+ basically nothing we can do but abort the connection and
+ log an error. This is one of the limitations of HTTP; it
+ needs to "know" the entire status of the response before
+ generating it, which is just impossible in these streamy
+ response situations. */
+ err = dav_push_error(r->pool, err->status, 0,
+ "Provider encountered an error while streaming"
+ " a REPORT response.", err);
+ dav_log_err(r, err, APLOG_ERR);
+ r->connection->aborted = 1;
+ return DONE;
+ }
+
+ return DONE;
+}
+
+static int dav_method_make_workspace(request_rec *r)
+{
+ dav_resource *resource;
+ const dav_hooks_vsn *vsn_hooks = DAV_GET_HOOKS_VSN(r);
+ dav_error *err;
+ apr_xml_doc *doc;
+ int result;
+
+ /* if no versioning provider, or the provider does not support workspaces,
+ * decline the request
+ */
+ if (vsn_hooks == NULL || vsn_hooks->make_workspace == NULL)
+ return DECLINED;
+
+ /* ask repository module to resolve the resource */
+ err = dav_get_resource(r, 0 /* label_allowed */, 0 /* use_checked_in */,
+ &resource);
+ if (err != NULL)
+ return dav_handle_err(r, err, NULL);
+
+ /* parse the request body (must be a mkworkspace element) */
+ if ((result = ap_xml_parse_input(r, &doc)) != OK) {
+ return result;
+ }
+
+ if (doc == NULL
+ || !dav_validate_root(doc, "mkworkspace")) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(00615)
+ "The request body does not contain "
+ "a \"mkworkspace\" element.");
+ return HTTP_BAD_REQUEST;
+ }
+
+ /* Check request preconditions */
+
+ /* ### need a general mechanism for reporting precondition violations
+ * ### (should be returning XML document for 403/409 responses)
+ */
+
+ /* resource must not already exist */
+ if (resource->exists) {
+ err = dav_new_error(r->pool, HTTP_CONFLICT, 0, 0,
+ "<DAV:resource-must-be-null/>");
+ return dav_handle_err(r, err, NULL);
+ }
+
+ /* ### what about locking? */
+
+ /* attempt to create the workspace */
+ if ((err = (*vsn_hooks->make_workspace)(resource, doc)) != NULL) {
+ err = dav_push_error(r->pool, err->status, 0,
+ apr_psprintf(r->pool,
+ "Could not create workspace %s.",
+ ap_escape_html(r->pool, r->uri)),
+ err);
+ return dav_handle_err(r, err, NULL);
+ }
+
+ /* set the Cache-Control header, per the spec */
+ apr_table_setn(r->headers_out, "Cache-Control", "no-cache");
+
+ /* return an appropriate response (HTTP_CREATED) */
+ return dav_created(r, resource->uri, "Workspace", 0 /*replaced*/);
+}
+
+static int dav_method_make_activity(request_rec *r)
+{
+ dav_resource *resource;
+ const dav_hooks_vsn *vsn_hooks = DAV_GET_HOOKS_VSN(r);
+ dav_error *err;
+ int result;
+
+ /* if no versioning provider, or the provider does not support activities,
+ * decline the request
+ */
+ if (vsn_hooks == NULL || vsn_hooks->make_activity == NULL)
+ return DECLINED;
+
+ /* ask repository module to resolve the resource */
+ err = dav_get_resource(r, 0 /* label_allowed */, 0 /* use_checked_in */,
+ &resource);
+ if (err != NULL)
+ return dav_handle_err(r, err, NULL);
+
+ /* MKACTIVITY does not have a defined request body. */
+ if ((result = ap_discard_request_body(r)) != OK) {
+ return result;
+ }
+
+ /* Check request preconditions */
+
+ /* ### need a general mechanism for reporting precondition violations
+ * ### (should be returning XML document for 403/409 responses)
+ */
+
+ /* resource must not already exist */
+ if (resource->exists) {
+ err = dav_new_error(r->pool, HTTP_CONFLICT, 0, 0,
+ "<DAV:resource-must-be-null/>");
+ return dav_handle_err(r, err, NULL);
+ }
+
+ /* the provider must say whether the resource can be created as
+ an activity, i.e. whether the location is ok. */
+ if (vsn_hooks->can_be_activity != NULL
+ && !(*vsn_hooks->can_be_activity)(resource)) {
+ err = dav_new_error(r->pool, HTTP_FORBIDDEN, 0, 0,
+ "<DAV:activity-location-ok/>");
+ return dav_handle_err(r, err, NULL);
+ }
+
+ /* ### what about locking? */
+
+ /* attempt to create the activity */
+ if ((err = (*vsn_hooks->make_activity)(resource)) != NULL) {
+ err = dav_push_error(r->pool, err->status, 0,
+ apr_psprintf(r->pool,
+ "Could not create activity %s.",
+ ap_escape_html(r->pool, r->uri)),
+ err);
+ return dav_handle_err(r, err, NULL);
+ }
+
+ /* set the Cache-Control header, per the spec */
+ apr_table_setn(r->headers_out, "Cache-Control", "no-cache");
+
+ /* return an appropriate response (HTTP_CREATED) */
+ return dav_created(r, resource->uri, "Activity", 0 /*replaced*/);
+}
+
+static int dav_method_baseline_control(request_rec *r)
+{
+ /* ### */
+ return HTTP_METHOD_NOT_ALLOWED;
+}
+
+static int dav_method_merge(request_rec *r)
+{
+ dav_resource *resource;
+ dav_resource *source_resource;
+ const dav_hooks_vsn *vsn_hooks = DAV_GET_HOOKS_VSN(r);
+ dav_error *err;
+ int result;
+ apr_xml_doc *doc;
+ apr_xml_elem *source_elem;
+ apr_xml_elem *href_elem;
+ apr_xml_elem *prop_elem;
+ const char *source;
+ int no_auto_merge;
+ int no_checkout;
+ dav_lookup_result lookup;
+
+ /* If no versioning provider, decline the request */
+ if (vsn_hooks == NULL)
+ return DECLINED;
+
+ if ((result = ap_xml_parse_input(r, &doc)) != OK)
+ return result;
+
+ if (doc == NULL || !dav_validate_root(doc, "merge")) {
+ /* This supplies additional information for the default msg. */
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(00616)
+ "The request body must be present and must be a "
+ "DAV:merge element.");
+ return HTTP_BAD_REQUEST;
+ }
+
+ if ((source_elem = dav_find_child(doc->root, "source")) == NULL) {
+ /* This supplies additional information for the default msg. */
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(00617)
+ "The DAV:merge element must contain a DAV:source "
+ "element.");
+ return HTTP_BAD_REQUEST;
+ }
+ if ((href_elem = dav_find_child(source_elem, "href")) == NULL) {
+ /* This supplies additional information for the default msg. */
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(00618)
+ "The DAV:source element must contain a DAV:href "
+ "element.");
+ return HTTP_BAD_REQUEST;
+ }
+ source = dav_xml_get_cdata(href_elem, r->pool, 1 /* strip_white */);
+
+ /* get a subrequest for the source, so that we can get a dav_resource
+ for that source. */
+ lookup = dav_lookup_uri(source, r, 0 /* must_be_absolute */);
+ if (lookup.rnew == NULL) {
+ if (lookup.err.status == HTTP_BAD_REQUEST) {
+ /* This supplies additional information for the default message. */
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(00619)
+ "%s", lookup.err.desc);
+ return HTTP_BAD_REQUEST;
+ }
+
+ /* ### this assumes that dav_lookup_uri() only generates a status
+ * ### that Apache can provide a status line for!! */
+
+ return dav_error_response(r, lookup.err.status, lookup.err.desc);
+ }
+ if (lookup.rnew->status != HTTP_OK) {
+ /* ### how best to report this... */
+ return dav_error_response(r, lookup.rnew->status,
+ "Merge source URI had an error.");
+ }
+ err = dav_get_resource(lookup.rnew, 0 /* label_allowed */,
+ 0 /* use_checked_in */, &source_resource);
+ if (err != NULL)
+ return dav_handle_err(r, err, NULL);
+
+ no_auto_merge = dav_find_child(doc->root, "no-auto-merge") != NULL;
+ no_checkout = dav_find_child(doc->root, "no-checkout") != NULL;
+
+ prop_elem = dav_find_child(doc->root, "prop");
+
+ /* ### check RFC. I believe the DAV:merge element may contain any
+ ### element also allowed within DAV:checkout. need to extract them
+ ### here, and pass them along.
+ ### if so, then refactor the CHECKOUT method handling so we can reuse
+ ### the code. maybe create a structure to hold CHECKOUT parameters
+ ### which can be passed to the checkout() and merge() hooks. */
+
+ /* Ask repository module to resolve the resource */
+ err = dav_get_resource(r, 0 /* label_allowed */, 0 /* use_checked_in */,
+ &resource);
+ if (err != NULL)
+ return dav_handle_err(r, err, NULL);
+ if (!resource->exists) {
+ /* Apache will supply a default error for this. */
+ return HTTP_NOT_FOUND;
+ }
+
+ /* ### check the source and target resources flags/types */
+
+ /* ### do lock checks, once behavior is defined */
+
+ /* set the Cache-Control header, per the spec */
+ /* ### correct? */
+ apr_table_setn(r->headers_out, "Cache-Control", "no-cache");
+
+ /* Initialize these values for a standard MERGE response. If the MERGE
+ is going to do something different (i.e. an error), then it must
+ return a dav_error, and we'll reset these values properly. */
+ r->status = HTTP_OK;
+ ap_set_content_type(r, "text/xml");
+
+ /* ### should we do any preliminary response generation? probably not,
+ ### because we may have an error, thus demanding something else in
+ ### the response body. */
+
+ /* Do the merge, including any response generation. */
+ if ((err = (*vsn_hooks->merge)(resource, source_resource,
+ no_auto_merge, no_checkout,
+ prop_elem,
+ r->output_filters)) != NULL) {
+ /* ### is err->status the right error here? */
+ err = dav_push_error(r->pool, err->status, 0,
+ apr_psprintf(r->pool,
+ "Could not MERGE resource \"%s\" "
+ "into \"%s\".",
+ ap_escape_html(r->pool, source),
+ ap_escape_html(r->pool, r->uri)),
+ err);
+ return dav_handle_err(r, err, NULL);
+ }
+
+ /* the response was fully generated by the merge() hook. */
+ /* ### urk. does this prevent logging? need to check... */
+ return DONE;
+}
+
+static int dav_method_bind(request_rec *r)
+{
+ dav_resource *resource;
+ dav_resource *binding;
+ dav_auto_version_info av_info;
+ const dav_hooks_binding *binding_hooks = DAV_GET_HOOKS_BINDING(r);
+ const char *dest;
+ dav_error *err;
+ dav_error *err2;
+ dav_response *multi_response = NULL;
+ dav_lookup_result lookup;
+ int overwrite;
+
+ /* If no bindings provider, decline the request */
+ if (binding_hooks == NULL)
+ return DECLINED;
+
+ /* Ask repository module to resolve the resource */
+ err = dav_get_resource(r, 0 /* label_allowed */, 0 /* use_checked_in */,
+ &resource);
+ if (err != NULL)
+ return dav_handle_err(r, err, NULL);
+
+ if (!resource->exists) {
+ /* Apache will supply a default error for this. */
+ return HTTP_NOT_FOUND;
+ }
+
+ /* get the destination URI */
+ dest = apr_table_get(r->headers_in, "Destination");
+ if (dest == NULL) {
+ /* This supplies additional information for the default message. */
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(00620)
+ "The request is missing a Destination header.");
+ return HTTP_BAD_REQUEST;
+ }
+
+ lookup = dav_lookup_uri(dest, r, 0 /* must_be_absolute */);
+ if (lookup.rnew == NULL) {
+ if (lookup.err.status == HTTP_BAD_REQUEST) {
+ /* This supplies additional information for the default message. */
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(00621)
+ "%s", lookup.err.desc);
+ return HTTP_BAD_REQUEST;
+ }
+ else if (lookup.err.status == HTTP_BAD_GATEWAY) {
+ /* ### Bindings protocol draft 02 says to return 507
+ * ### (Cross Server Binding Forbidden); Apache already defines 507
+ * ### as HTTP_INSUFFICIENT_STORAGE. So, for now, we'll return
+ * ### HTTP_FORBIDDEN
+ */
+ return dav_error_response(r, HTTP_FORBIDDEN,
+ "Cross server bindings are not "
+ "allowed by this server.");
+ }
+
+ /* ### this assumes that dav_lookup_uri() only generates a status
+ * ### that Apache can provide a status line for!! */
+
+ return dav_error_response(r, lookup.err.status, lookup.err.desc);
+ }
+ if (lookup.rnew->status != HTTP_OK) {
+ /* ### how best to report this... */
+ return dav_error_response(r, lookup.rnew->status,
+ "Destination URI had an error.");
+ }
+
+ /* resolve binding resource */
+ err = dav_get_resource(lookup.rnew, 0 /* label_allowed */,
+ 0 /* use_checked_in */, &binding);
+ if (err != NULL)
+ return dav_handle_err(r, err, NULL);
+
+ /* are the two resources handled by the same repository? */
+ if (resource->hooks != binding->hooks) {
+ /* ### this message exposes some backend config, but screw it... */
+ return dav_error_response(r, HTTP_BAD_GATEWAY,
+ "Destination URI is handled by a "
+ "different repository than the source URI. "
+ "BIND between repositories is not possible.");
+ }
+
+ /* get and parse the overwrite header value */
+ if ((overwrite = dav_get_overwrite(r)) < 0) {
+ /* dav_get_overwrite() supplies additional information for the
+ * default message. */
+ return HTTP_BAD_REQUEST;
+ }
+
+ /* quick failure test: if dest exists and overwrite is false. */
+ if (binding->exists && !overwrite) {
+ return dav_error_response(r, HTTP_PRECONDITION_FAILED,
+ "Destination is not empty and "
+ "Overwrite is not \"T\"");
+ }
+
+ /* are the source and destination the same? */
+ if ((*resource->hooks->is_same_resource)(resource, binding)) {
+ return dav_error_response(r, HTTP_FORBIDDEN,
+ "Source and Destination URIs are the same.");
+ }
+
+ /*
+ * Check If-Headers and existing locks for destination. Note that we
+ * use depth==infinity since the target (hierarchy) will be deleted
+ * before the move/copy is completed.
+ *
+ * Note that we are overwriting the target, which implies a DELETE, so
+ * we are subject to the error/response rules as a DELETE. Namely, we
+ * will return a 424 error if any of the validations fail.
+ * (see dav_method_delete() for more information)
+ */
+ if ((err = dav_validate_request(lookup.rnew, binding, DAV_INFINITY, NULL,
+ &multi_response,
+ DAV_VALIDATE_PARENT
+ | DAV_VALIDATE_USE_424, NULL)) != NULL) {
+ err = dav_push_error(r->pool, err->status, 0,
+ apr_psprintf(r->pool,
+ "Could not BIND %s due to a "
+ "failed precondition on the "
+ "destination (e.g. locks).",
+ ap_escape_html(r->pool, r->uri)),
+ err);
+ return dav_handle_err(r, err, multi_response);
+ }
+
+ /* guard against creating circular bindings */
+ if (resource->collection
+ && (*resource->hooks->is_parent_resource)(resource, binding)) {
+ return dav_error_response(r, HTTP_FORBIDDEN,
+ "Source collection contains the Destination.");
+ }
+ if (resource->collection
+ && (*resource->hooks->is_parent_resource)(binding, resource)) {
+ /* The destination must exist (since it contains the source), and
+ * a condition above implies Overwrite==T. Obviously, we cannot
+ * delete the Destination before the BIND, as that would
+ * delete the Source.
+ */
+
+ return dav_error_response(r, HTTP_FORBIDDEN,
+ "Destination collection contains the Source and "
+ "Overwrite has been specified.");
+ }
+
+ /* prepare the destination collection for modification */
+ if ((err = dav_auto_checkout(r, binding, 1 /* parent_only */,
+ &av_info)) != NULL) {
+ /* could not make destination writable */
+ return dav_handle_err(r, err, NULL);
+ }
+
+ /* If target exists, remove it first (we know Ovewrite must be TRUE).
+ * Then try to bind to the resource.
+ */
+ if (binding->exists)
+ err = (*resource->hooks->remove_resource)(binding, &multi_response);
+
+ if (err == NULL) {
+ err = (*binding_hooks->bind_resource)(resource, binding);
+ }
+
+ /* restore parent collection states */
+ err2 = dav_auto_checkin(r, NULL,
+ err != NULL /* undo if error */,
+ 0 /* unlock */, &av_info);
+
+ /* check for error from remove/bind operations */
+ if (err != NULL) {
+ err = dav_push_error(r->pool, err->status, 0,
+ apr_psprintf(r->pool,
+ "Could not BIND %s.",
+ ap_escape_html(r->pool, r->uri)),
+ err);
+ return dav_handle_err(r, err, multi_response);
+ }
+
+ /* check for errors from reverting writability */
+ if (err2 != NULL) {
+ /* just log a warning */
+ err = dav_push_error(r->pool, err2->status, 0,
+ "The BIND was successful, but there was a "
+ "problem automatically checking in the "
+ "source parent collection.",
+ err2);
+ dav_log_err(r, err, APLOG_WARNING);
+ }
+
+ /* return an appropriate response (HTTP_CREATED) */
+ /* ### spec doesn't say what happens when destination was replaced */
+ return dav_created(r, lookup.rnew->unparsed_uri, "Binding", 0);
+}
+
+
+/*
+ * Response handler for DAV resources
+ */
+static int dav_handler(request_rec *r)
+{
+ if (strcmp(r->handler, DAV_HANDLER_NAME) != 0)
+ return DECLINED;
+
+ /* Reject requests with an unescaped hash character, as these may
+ * be more destructive than the user intended. */
+ if (r->parsed_uri.fragment != NULL) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(00622)
+ "buggy client used un-escaped hash in Request-URI");
+ return dav_error_response(r, HTTP_BAD_REQUEST,
+ "The request was invalid: the URI included "
+ "an un-escaped hash character");
+ }
+
+ /* ### do we need to do anything with r->proxyreq ?? */
+
+ /*
+ * ### anything else to do here? could another module and/or
+ * ### config option "take over" the handler here? i.e. how do
+ * ### we lock down this hierarchy so that we are the ultimate
+ * ### arbiter? (or do we simply depend on the administrator
+ * ### to avoid conflicting configurations?)
+ */
+
+ /*
+ * Set up the methods mask, since that's one of the reasons this handler
+ * gets called, and lower-level things may need the info.
+ *
+ * First, set the mask to the methods we handle directly. Since by
+ * definition we own our managed space, we unconditionally set
+ * the r->allowed field rather than ORing our values with anything
+ * any other module may have put in there.
+ *
+ * These are the HTTP-defined methods that we handle directly.
+ */
+ r->allowed = 0
+ | (AP_METHOD_BIT << M_GET)
+ | (AP_METHOD_BIT << M_PUT)
+ | (AP_METHOD_BIT << M_DELETE)
+ | (AP_METHOD_BIT << M_OPTIONS)
+ | (AP_METHOD_BIT << M_INVALID);
+
+ /*
+ * These are the DAV methods we handle.
+ */
+ r->allowed |= 0
+ | (AP_METHOD_BIT << M_COPY)
+ | (AP_METHOD_BIT << M_LOCK)
+ | (AP_METHOD_BIT << M_UNLOCK)
+ | (AP_METHOD_BIT << M_MKCOL)
+ | (AP_METHOD_BIT << M_MOVE)
+ | (AP_METHOD_BIT << M_PROPFIND)
+ | (AP_METHOD_BIT << M_PROPPATCH);
+
+ /*
+ * These are methods that we don't handle directly, but let the
+ * server's default handler do for us as our agent.
+ */
+ r->allowed |= 0
+ | (AP_METHOD_BIT << M_POST);
+
+ /* ### hrm. if we return HTTP_METHOD_NOT_ALLOWED, then an Allow header
+ * ### is sent; it will need the other allowed states; since the default
+ * ### handler is not called on error, then it doesn't add the other
+ * ### allowed states, so we must
+ */
+
+ /* ### we might need to refine this for just where we return the error.
+ * ### also, there is the issue with other methods (see ISSUES)
+ */
+
+ /* dispatch the appropriate method handler */
+ if (r->method_number == M_GET) {
+ return dav_method_get(r);
+ }
+
+ if (r->method_number == M_PUT) {
+ return dav_method_put(r);
+ }
+
+ if (r->method_number == M_POST) {
+ return dav_method_post(r);
+ }
+
+ if (r->method_number == M_DELETE) {
+ return dav_method_delete(r);
+ }
+
+ if (r->method_number == M_OPTIONS) {
+ return dav_method_options(r);
+ }
+
+ if (r->method_number == M_PROPFIND) {
+ return dav_method_propfind(r);
+ }
+
+ if (r->method_number == M_PROPPATCH) {
+ return dav_method_proppatch(r);
+ }
+
+ if (r->method_number == M_MKCOL) {
+ return dav_method_mkcol(r);
+ }
+
+ if (r->method_number == M_COPY) {
+ return dav_method_copymove(r, DAV_DO_COPY);
+ }
+
+ if (r->method_number == M_MOVE) {
+ return dav_method_copymove(r, DAV_DO_MOVE);
+ }
+
+ if (r->method_number == M_LOCK) {
+ return dav_method_lock(r);
+ }
+
+ if (r->method_number == M_UNLOCK) {
+ return dav_method_unlock(r);
+ }
+
+ if (r->method_number == M_VERSION_CONTROL) {
+ return dav_method_vsn_control(r);
+ }
+
+ if (r->method_number == M_CHECKOUT) {
+ return dav_method_checkout(r);
+ }
+
+ if (r->method_number == M_UNCHECKOUT) {
+ return dav_method_uncheckout(r);
+ }
+
+ if (r->method_number == M_CHECKIN) {
+ return dav_method_checkin(r);
+ }
+
+ if (r->method_number == M_UPDATE) {
+ return dav_method_update(r);
+ }
+
+ if (r->method_number == M_LABEL) {
+ return dav_method_label(r);
+ }
+
+ if (r->method_number == M_REPORT) {
+ return dav_method_report(r);
+ }
+
+ if (r->method_number == M_MKWORKSPACE) {
+ return dav_method_make_workspace(r);
+ }
+
+ if (r->method_number == M_MKACTIVITY) {
+ return dav_method_make_activity(r);
+ }
+
+ if (r->method_number == M_BASELINE_CONTROL) {
+ return dav_method_baseline_control(r);
+ }
+
+ if (r->method_number == M_MERGE) {
+ return dav_method_merge(r);
+ }
+
+ /* BIND method */
+ if (r->method_number == dav_methods[DAV_M_BIND]) {
+ return dav_method_bind(r);
+ }
+
+ /* DASL method */
+ if (r->method_number == dav_methods[DAV_M_SEARCH]) {
+ return dav_method_search(r);
+ }
+
+ /* ### add'l methods for Advanced Collections, ACLs */
+
+ return DECLINED;
+}
+
+static int dav_fixups(request_rec *r)
+{
+ dav_dir_conf *conf;
+
+ /* quickly ignore any HTTP/0.9 requests which aren't subreqs. */
+ if (r->assbackwards && !r->main) {
+ return DECLINED;
+ }
+
+ conf = (dav_dir_conf *)ap_get_module_config(r->per_dir_config,
+ &dav_module);
+
+ /* if DAV is not enabled, then we've got nothing to do */
+ if (conf->provider == NULL) {
+ return DECLINED;
+ }
+
+ /* We are going to handle almost every request. In certain cases,
+ the provider maps to the filesystem (thus, handle_get is
+ FALSE), and core Apache will handle it. a For that case, we
+ just return right away. */
+ if (r->method_number == M_GET) {
+ /*
+ * ### need some work to pull Content-Type and Content-Language
+ * ### from the property database.
+ */
+
+ /*
+ * If the repository hasn't indicated that it will handle the
+ * GET method, then just punt.
+ *
+ * ### this isn't quite right... taking over the response can break
+ * ### things like mod_negotiation. need to look into this some more.
+ */
+ if (!conf->provider->repos->handle_get) {
+ return DECLINED;
+ }
+ }
+
+ /* ### this is wrong. We should only be setting the r->handler for the
+ * requests that mod_dav knows about. If we set the handler for M_POST
+ * requests, then CGI scripts that use POST will return the source for the
+ * script. However, mod_dav DOES handle POST, so something else needs
+ * to be fixed.
+ */
+ if (r->method_number != M_POST) {
+
+ /* We are going to be handling the response for this resource. */
+ r->handler = DAV_HANDLER_NAME;
+ return OK;
+ }
+
+ return DECLINED;
+}
+
+static void register_hooks(apr_pool_t *p)
+{
+ ap_hook_handler(dav_handler, NULL, NULL, APR_HOOK_MIDDLE);
+ ap_hook_post_config(dav_init_handler, NULL, NULL, APR_HOOK_MIDDLE);
+ ap_hook_fixups(dav_fixups, NULL, NULL, APR_HOOK_MIDDLE);
+
+ dav_hook_find_liveprop(dav_core_find_liveprop, NULL, NULL, APR_HOOK_LAST);
+ dav_hook_insert_all_liveprops(dav_core_insert_all_liveprops,
+ NULL, NULL, APR_HOOK_MIDDLE);
+
+ dav_core_register_uris(p);
+}
+
+/*---------------------------------------------------------------------------
+ *
+ * Configuration info for the module
+ */
+
+static const command_rec dav_cmds[] =
+{
+ /* per directory/location */
+ AP_INIT_TAKE1("DAV", dav_cmd_dav, NULL, ACCESS_CONF,
+ "specify the DAV provider for a directory or location"),
+
+ /* per directory/location, or per server */
+ AP_INIT_TAKE1("DAVMinTimeout", dav_cmd_davmintimeout, NULL,
+ ACCESS_CONF|RSRC_CONF,
+ "specify minimum allowed timeout"),
+
+ /* per directory/location, or per server */
+ AP_INIT_FLAG("DAVDepthInfinity", dav_cmd_davdepthinfinity, NULL,
+ ACCESS_CONF|RSRC_CONF,
+ "allow Depth infinity PROPFIND requests"),
+
+ { NULL }
+};
+
+module DAV_DECLARE_DATA dav_module =
+{
+ STANDARD20_MODULE_STUFF,
+ dav_create_dir_config, /* dir config creater */
+ dav_merge_dir_config, /* dir merger --- default is to override */
+ dav_create_server_config, /* server config */
+ dav_merge_server_config, /* merge server config */
+ dav_cmds, /* command table */
+ register_hooks, /* register hooks */
+};
+
+APR_HOOK_STRUCT(
+ APR_HOOK_LINK(gather_propsets)
+ APR_HOOK_LINK(find_liveprop)
+ APR_HOOK_LINK(insert_all_liveprops)
+ )
+
+APR_IMPLEMENT_EXTERNAL_HOOK_VOID(dav, DAV, gather_propsets,
+ (apr_array_header_t *uris),
+ (uris))
+
+APR_IMPLEMENT_EXTERNAL_HOOK_RUN_FIRST(dav, DAV, int, find_liveprop,
+ (const dav_resource *resource,
+ const char *ns_uri, const char *name,
+ const dav_hooks_liveprop **hooks),
+ (resource, ns_uri, name, hooks), 0)
+
+APR_IMPLEMENT_EXTERNAL_HOOK_VOID(dav, DAV, insert_all_liveprops,
+ (request_rec *r, const dav_resource *resource,
+ dav_prop_insert what, apr_text_header *phdr),
+ (r, resource, what, phdr))
diff --git a/modules/dav/main/mod_dav.dep b/modules/dav/main/mod_dav.dep
new file mode 100644
index 0000000..5efe2f4
--- /dev/null
+++ b/modules/dav/main/mod_dav.dep
@@ -0,0 +1,354 @@
+# Microsoft Developer Studio Generated Dependency File, included by mod_dav.mak
+
+.\liveprop.c : \
+ "..\..\..\include\ap_config.h"\
+ "..\..\..\include\ap_config_layout.h"\
+ "..\..\..\include\ap_hooks.h"\
+ "..\..\..\include\ap_mmn.h"\
+ "..\..\..\include\ap_regex.h"\
+ "..\..\..\include\ap_release.h"\
+ "..\..\..\include\apache_noprobes.h"\
+ "..\..\..\include\httpd.h"\
+ "..\..\..\include\os.h"\
+ "..\..\..\include\util_filter.h"\
+ "..\..\..\include\util_xml.h"\
+ "..\..\..\srclib\apr-util\include\apr_buckets.h"\
+ "..\..\..\srclib\apr-util\include\apr_dbm.h"\
+ "..\..\..\srclib\apr-util\include\apr_hooks.h"\
+ "..\..\..\srclib\apr-util\include\apr_optional_hooks.h"\
+ "..\..\..\srclib\apr-util\include\apr_uri.h"\
+ "..\..\..\srclib\apr-util\include\apr_xlate.h"\
+ "..\..\..\srclib\apr-util\include\apr_xml.h"\
+ "..\..\..\srclib\apr-util\include\apu.h"\
+ "..\..\..\srclib\apr\include\apr.h"\
+ "..\..\..\srclib\apr\include\apr_allocator.h"\
+ "..\..\..\srclib\apr\include\apr_errno.h"\
+ "..\..\..\srclib\apr\include\apr_file_info.h"\
+ "..\..\..\srclib\apr\include\apr_file_io.h"\
+ "..\..\..\srclib\apr\include\apr_general.h"\
+ "..\..\..\srclib\apr\include\apr_hash.h"\
+ "..\..\..\srclib\apr\include\apr_inherit.h"\
+ "..\..\..\srclib\apr\include\apr_mmap.h"\
+ "..\..\..\srclib\apr\include\apr_network_io.h"\
+ "..\..\..\srclib\apr\include\apr_poll.h"\
+ "..\..\..\srclib\apr\include\apr_pools.h"\
+ "..\..\..\srclib\apr\include\apr_ring.h"\
+ "..\..\..\srclib\apr\include\apr_strings.h"\
+ "..\..\..\srclib\apr\include\apr_tables.h"\
+ "..\..\..\srclib\apr\include\apr_thread_mutex.h"\
+ "..\..\..\srclib\apr\include\apr_thread_proc.h"\
+ "..\..\..\srclib\apr\include\apr_time.h"\
+ "..\..\..\srclib\apr\include\apr_user.h"\
+ "..\..\..\srclib\apr\include\apr_want.h"\
+ ".\mod_dav.h"\
+
+
+.\mod_dav.c : \
+ "..\..\..\include\ap_config.h"\
+ "..\..\..\include\ap_config_layout.h"\
+ "..\..\..\include\ap_expr.h"\
+ "..\..\..\include\ap_hooks.h"\
+ "..\..\..\include\ap_mmn.h"\
+ "..\..\..\include\ap_provider.h"\
+ "..\..\..\include\ap_regex.h"\
+ "..\..\..\include\ap_release.h"\
+ "..\..\..\include\apache_noprobes.h"\
+ "..\..\..\include\http_config.h"\
+ "..\..\..\include\http_core.h"\
+ "..\..\..\include\http_log.h"\
+ "..\..\..\include\http_main.h"\
+ "..\..\..\include\http_protocol.h"\
+ "..\..\..\include\http_request.h"\
+ "..\..\..\include\httpd.h"\
+ "..\..\..\include\os.h"\
+ "..\..\..\include\util_cfgtree.h"\
+ "..\..\..\include\util_filter.h"\
+ "..\..\..\include\util_script.h"\
+ "..\..\..\include\util_xml.h"\
+ "..\..\..\srclib\apr-util\include\apr_buckets.h"\
+ "..\..\..\srclib\apr-util\include\apr_dbm.h"\
+ "..\..\..\srclib\apr-util\include\apr_hooks.h"\
+ "..\..\..\srclib\apr-util\include\apr_optional.h"\
+ "..\..\..\srclib\apr-util\include\apr_optional_hooks.h"\
+ "..\..\..\srclib\apr-util\include\apr_uri.h"\
+ "..\..\..\srclib\apr-util\include\apr_xlate.h"\
+ "..\..\..\srclib\apr-util\include\apr_xml.h"\
+ "..\..\..\srclib\apr-util\include\apu.h"\
+ "..\..\..\srclib\apr\include\apr.h"\
+ "..\..\..\srclib\apr\include\apr_allocator.h"\
+ "..\..\..\srclib\apr\include\apr_dso.h"\
+ "..\..\..\srclib\apr\include\apr_errno.h"\
+ "..\..\..\srclib\apr\include\apr_file_info.h"\
+ "..\..\..\srclib\apr\include\apr_file_io.h"\
+ "..\..\..\srclib\apr\include\apr_general.h"\
+ "..\..\..\srclib\apr\include\apr_global_mutex.h"\
+ "..\..\..\srclib\apr\include\apr_hash.h"\
+ "..\..\..\srclib\apr\include\apr_inherit.h"\
+ "..\..\..\srclib\apr\include\apr_lib.h"\
+ "..\..\..\srclib\apr\include\apr_mmap.h"\
+ "..\..\..\srclib\apr\include\apr_network_io.h"\
+ "..\..\..\srclib\apr\include\apr_poll.h"\
+ "..\..\..\srclib\apr\include\apr_pools.h"\
+ "..\..\..\srclib\apr\include\apr_portable.h"\
+ "..\..\..\srclib\apr\include\apr_proc_mutex.h"\
+ "..\..\..\srclib\apr\include\apr_ring.h"\
+ "..\..\..\srclib\apr\include\apr_shm.h"\
+ "..\..\..\srclib\apr\include\apr_strings.h"\
+ "..\..\..\srclib\apr\include\apr_tables.h"\
+ "..\..\..\srclib\apr\include\apr_thread_mutex.h"\
+ "..\..\..\srclib\apr\include\apr_thread_proc.h"\
+ "..\..\..\srclib\apr\include\apr_time.h"\
+ "..\..\..\srclib\apr\include\apr_user.h"\
+ "..\..\..\srclib\apr\include\apr_want.h"\
+ ".\mod_dav.h"\
+
+
+.\props.c : \
+ "..\..\..\include\ap_config.h"\
+ "..\..\..\include\ap_config_layout.h"\
+ "..\..\..\include\ap_hooks.h"\
+ "..\..\..\include\ap_mmn.h"\
+ "..\..\..\include\ap_regex.h"\
+ "..\..\..\include\ap_release.h"\
+ "..\..\..\include\apache_noprobes.h"\
+ "..\..\..\include\http_config.h"\
+ "..\..\..\include\http_log.h"\
+ "..\..\..\include\http_request.h"\
+ "..\..\..\include\httpd.h"\
+ "..\..\..\include\os.h"\
+ "..\..\..\include\util_cfgtree.h"\
+ "..\..\..\include\util_filter.h"\
+ "..\..\..\include\util_xml.h"\
+ "..\..\..\srclib\apr-util\include\apr_buckets.h"\
+ "..\..\..\srclib\apr-util\include\apr_dbm.h"\
+ "..\..\..\srclib\apr-util\include\apr_hooks.h"\
+ "..\..\..\srclib\apr-util\include\apr_optional.h"\
+ "..\..\..\srclib\apr-util\include\apr_optional_hooks.h"\
+ "..\..\..\srclib\apr-util\include\apr_uri.h"\
+ "..\..\..\srclib\apr-util\include\apr_xlate.h"\
+ "..\..\..\srclib\apr-util\include\apr_xml.h"\
+ "..\..\..\srclib\apr-util\include\apu.h"\
+ "..\..\..\srclib\apr\include\apr.h"\
+ "..\..\..\srclib\apr\include\apr_allocator.h"\
+ "..\..\..\srclib\apr\include\apr_errno.h"\
+ "..\..\..\srclib\apr\include\apr_file_info.h"\
+ "..\..\..\srclib\apr\include\apr_file_io.h"\
+ "..\..\..\srclib\apr\include\apr_general.h"\
+ "..\..\..\srclib\apr\include\apr_hash.h"\
+ "..\..\..\srclib\apr\include\apr_inherit.h"\
+ "..\..\..\srclib\apr\include\apr_mmap.h"\
+ "..\..\..\srclib\apr\include\apr_network_io.h"\
+ "..\..\..\srclib\apr\include\apr_poll.h"\
+ "..\..\..\srclib\apr\include\apr_pools.h"\
+ "..\..\..\srclib\apr\include\apr_ring.h"\
+ "..\..\..\srclib\apr\include\apr_strings.h"\
+ "..\..\..\srclib\apr\include\apr_tables.h"\
+ "..\..\..\srclib\apr\include\apr_thread_mutex.h"\
+ "..\..\..\srclib\apr\include\apr_thread_proc.h"\
+ "..\..\..\srclib\apr\include\apr_time.h"\
+ "..\..\..\srclib\apr\include\apr_user.h"\
+ "..\..\..\srclib\apr\include\apr_want.h"\
+ ".\mod_dav.h"\
+
+
+.\providers.c : \
+ "..\..\..\include\ap_config.h"\
+ "..\..\..\include\ap_config_layout.h"\
+ "..\..\..\include\ap_hooks.h"\
+ "..\..\..\include\ap_mmn.h"\
+ "..\..\..\include\ap_provider.h"\
+ "..\..\..\include\ap_regex.h"\
+ "..\..\..\include\ap_release.h"\
+ "..\..\..\include\apache_noprobes.h"\
+ "..\..\..\include\httpd.h"\
+ "..\..\..\include\os.h"\
+ "..\..\..\include\util_filter.h"\
+ "..\..\..\include\util_xml.h"\
+ "..\..\..\srclib\apr-util\include\apr_buckets.h"\
+ "..\..\..\srclib\apr-util\include\apr_dbm.h"\
+ "..\..\..\srclib\apr-util\include\apr_hooks.h"\
+ "..\..\..\srclib\apr-util\include\apr_optional_hooks.h"\
+ "..\..\..\srclib\apr-util\include\apr_uri.h"\
+ "..\..\..\srclib\apr-util\include\apr_xlate.h"\
+ "..\..\..\srclib\apr-util\include\apr_xml.h"\
+ "..\..\..\srclib\apr-util\include\apu.h"\
+ "..\..\..\srclib\apr\include\apr.h"\
+ "..\..\..\srclib\apr\include\apr_allocator.h"\
+ "..\..\..\srclib\apr\include\apr_errno.h"\
+ "..\..\..\srclib\apr\include\apr_file_info.h"\
+ "..\..\..\srclib\apr\include\apr_file_io.h"\
+ "..\..\..\srclib\apr\include\apr_general.h"\
+ "..\..\..\srclib\apr\include\apr_hash.h"\
+ "..\..\..\srclib\apr\include\apr_inherit.h"\
+ "..\..\..\srclib\apr\include\apr_mmap.h"\
+ "..\..\..\srclib\apr\include\apr_network_io.h"\
+ "..\..\..\srclib\apr\include\apr_poll.h"\
+ "..\..\..\srclib\apr\include\apr_pools.h"\
+ "..\..\..\srclib\apr\include\apr_ring.h"\
+ "..\..\..\srclib\apr\include\apr_tables.h"\
+ "..\..\..\srclib\apr\include\apr_thread_mutex.h"\
+ "..\..\..\srclib\apr\include\apr_thread_proc.h"\
+ "..\..\..\srclib\apr\include\apr_time.h"\
+ "..\..\..\srclib\apr\include\apr_user.h"\
+ "..\..\..\srclib\apr\include\apr_want.h"\
+ ".\mod_dav.h"\
+
+
+.\std_liveprop.c : \
+ "..\..\..\include\ap_config.h"\
+ "..\..\..\include\ap_config_layout.h"\
+ "..\..\..\include\ap_hooks.h"\
+ "..\..\..\include\ap_mmn.h"\
+ "..\..\..\include\ap_provider.h"\
+ "..\..\..\include\ap_regex.h"\
+ "..\..\..\include\ap_release.h"\
+ "..\..\..\include\apache_noprobes.h"\
+ "..\..\..\include\httpd.h"\
+ "..\..\..\include\os.h"\
+ "..\..\..\include\util_filter.h"\
+ "..\..\..\include\util_xml.h"\
+ "..\..\..\srclib\apr-util\include\apr_buckets.h"\
+ "..\..\..\srclib\apr-util\include\apr_dbm.h"\
+ "..\..\..\srclib\apr-util\include\apr_hooks.h"\
+ "..\..\..\srclib\apr-util\include\apr_optional_hooks.h"\
+ "..\..\..\srclib\apr-util\include\apr_uri.h"\
+ "..\..\..\srclib\apr-util\include\apr_xlate.h"\
+ "..\..\..\srclib\apr-util\include\apr_xml.h"\
+ "..\..\..\srclib\apr-util\include\apu.h"\
+ "..\..\..\srclib\apr\include\apr.h"\
+ "..\..\..\srclib\apr\include\apr_allocator.h"\
+ "..\..\..\srclib\apr\include\apr_errno.h"\
+ "..\..\..\srclib\apr\include\apr_file_info.h"\
+ "..\..\..\srclib\apr\include\apr_file_io.h"\
+ "..\..\..\srclib\apr\include\apr_general.h"\
+ "..\..\..\srclib\apr\include\apr_hash.h"\
+ "..\..\..\srclib\apr\include\apr_inherit.h"\
+ "..\..\..\srclib\apr\include\apr_mmap.h"\
+ "..\..\..\srclib\apr\include\apr_network_io.h"\
+ "..\..\..\srclib\apr\include\apr_poll.h"\
+ "..\..\..\srclib\apr\include\apr_pools.h"\
+ "..\..\..\srclib\apr\include\apr_ring.h"\
+ "..\..\..\srclib\apr\include\apr_strings.h"\
+ "..\..\..\srclib\apr\include\apr_tables.h"\
+ "..\..\..\srclib\apr\include\apr_thread_mutex.h"\
+ "..\..\..\srclib\apr\include\apr_thread_proc.h"\
+ "..\..\..\srclib\apr\include\apr_time.h"\
+ "..\..\..\srclib\apr\include\apr_user.h"\
+ "..\..\..\srclib\apr\include\apr_want.h"\
+ ".\mod_dav.h"\
+
+
+.\util.c : \
+ "..\..\..\include\ap_config.h"\
+ "..\..\..\include\ap_config_layout.h"\
+ "..\..\..\include\ap_hooks.h"\
+ "..\..\..\include\ap_mmn.h"\
+ "..\..\..\include\ap_regex.h"\
+ "..\..\..\include\ap_release.h"\
+ "..\..\..\include\apache_noprobes.h"\
+ "..\..\..\include\http_config.h"\
+ "..\..\..\include\http_log.h"\
+ "..\..\..\include\http_protocol.h"\
+ "..\..\..\include\http_request.h"\
+ "..\..\..\include\http_vhost.h"\
+ "..\..\..\include\httpd.h"\
+ "..\..\..\include\os.h"\
+ "..\..\..\include\util_cfgtree.h"\
+ "..\..\..\include\util_filter.h"\
+ "..\..\..\include\util_xml.h"\
+ "..\..\..\srclib\apr-util\include\apr_buckets.h"\
+ "..\..\..\srclib\apr-util\include\apr_dbm.h"\
+ "..\..\..\srclib\apr-util\include\apr_hooks.h"\
+ "..\..\..\srclib\apr-util\include\apr_optional.h"\
+ "..\..\..\srclib\apr-util\include\apr_optional_hooks.h"\
+ "..\..\..\srclib\apr-util\include\apr_uri.h"\
+ "..\..\..\srclib\apr-util\include\apr_xlate.h"\
+ "..\..\..\srclib\apr-util\include\apr_xml.h"\
+ "..\..\..\srclib\apr-util\include\apu.h"\
+ "..\..\..\srclib\apr\include\apr.h"\
+ "..\..\..\srclib\apr\include\apr_allocator.h"\
+ "..\..\..\srclib\apr\include\apr_dso.h"\
+ "..\..\..\srclib\apr\include\apr_errno.h"\
+ "..\..\..\srclib\apr\include\apr_file_info.h"\
+ "..\..\..\srclib\apr\include\apr_file_io.h"\
+ "..\..\..\srclib\apr\include\apr_general.h"\
+ "..\..\..\srclib\apr\include\apr_global_mutex.h"\
+ "..\..\..\srclib\apr\include\apr_hash.h"\
+ "..\..\..\srclib\apr\include\apr_inherit.h"\
+ "..\..\..\srclib\apr\include\apr_lib.h"\
+ "..\..\..\srclib\apr\include\apr_mmap.h"\
+ "..\..\..\srclib\apr\include\apr_network_io.h"\
+ "..\..\..\srclib\apr\include\apr_poll.h"\
+ "..\..\..\srclib\apr\include\apr_pools.h"\
+ "..\..\..\srclib\apr\include\apr_portable.h"\
+ "..\..\..\srclib\apr\include\apr_proc_mutex.h"\
+ "..\..\..\srclib\apr\include\apr_ring.h"\
+ "..\..\..\srclib\apr\include\apr_shm.h"\
+ "..\..\..\srclib\apr\include\apr_strings.h"\
+ "..\..\..\srclib\apr\include\apr_tables.h"\
+ "..\..\..\srclib\apr\include\apr_thread_mutex.h"\
+ "..\..\..\srclib\apr\include\apr_thread_proc.h"\
+ "..\..\..\srclib\apr\include\apr_time.h"\
+ "..\..\..\srclib\apr\include\apr_user.h"\
+ "..\..\..\srclib\apr\include\apr_want.h"\
+ ".\mod_dav.h"\
+
+
+.\util_lock.c : \
+ "..\..\..\include\ap_config.h"\
+ "..\..\..\include\ap_config_layout.h"\
+ "..\..\..\include\ap_expr.h"\
+ "..\..\..\include\ap_hooks.h"\
+ "..\..\..\include\ap_mmn.h"\
+ "..\..\..\include\ap_regex.h"\
+ "..\..\..\include\ap_release.h"\
+ "..\..\..\include\apache_noprobes.h"\
+ "..\..\..\include\http_config.h"\
+ "..\..\..\include\http_core.h"\
+ "..\..\..\include\http_log.h"\
+ "..\..\..\include\http_protocol.h"\
+ "..\..\..\include\httpd.h"\
+ "..\..\..\include\os.h"\
+ "..\..\..\include\util_cfgtree.h"\
+ "..\..\..\include\util_filter.h"\
+ "..\..\..\include\util_xml.h"\
+ "..\..\..\srclib\apr-util\include\apr_buckets.h"\
+ "..\..\..\srclib\apr-util\include\apr_dbm.h"\
+ "..\..\..\srclib\apr-util\include\apr_hooks.h"\
+ "..\..\..\srclib\apr-util\include\apr_optional.h"\
+ "..\..\..\srclib\apr-util\include\apr_optional_hooks.h"\
+ "..\..\..\srclib\apr-util\include\apr_uri.h"\
+ "..\..\..\srclib\apr-util\include\apr_xlate.h"\
+ "..\..\..\srclib\apr-util\include\apr_xml.h"\
+ "..\..\..\srclib\apr-util\include\apu.h"\
+ "..\..\..\srclib\apr\include\apr.h"\
+ "..\..\..\srclib\apr\include\apr_allocator.h"\
+ "..\..\..\srclib\apr\include\apr_dso.h"\
+ "..\..\..\srclib\apr\include\apr_errno.h"\
+ "..\..\..\srclib\apr\include\apr_file_info.h"\
+ "..\..\..\srclib\apr\include\apr_file_io.h"\
+ "..\..\..\srclib\apr\include\apr_general.h"\
+ "..\..\..\srclib\apr\include\apr_global_mutex.h"\
+ "..\..\..\srclib\apr\include\apr_hash.h"\
+ "..\..\..\srclib\apr\include\apr_inherit.h"\
+ "..\..\..\srclib\apr\include\apr_mmap.h"\
+ "..\..\..\srclib\apr\include\apr_network_io.h"\
+ "..\..\..\srclib\apr\include\apr_poll.h"\
+ "..\..\..\srclib\apr\include\apr_pools.h"\
+ "..\..\..\srclib\apr\include\apr_portable.h"\
+ "..\..\..\srclib\apr\include\apr_proc_mutex.h"\
+ "..\..\..\srclib\apr\include\apr_ring.h"\
+ "..\..\..\srclib\apr\include\apr_shm.h"\
+ "..\..\..\srclib\apr\include\apr_strings.h"\
+ "..\..\..\srclib\apr\include\apr_tables.h"\
+ "..\..\..\srclib\apr\include\apr_thread_mutex.h"\
+ "..\..\..\srclib\apr\include\apr_thread_proc.h"\
+ "..\..\..\srclib\apr\include\apr_time.h"\
+ "..\..\..\srclib\apr\include\apr_user.h"\
+ "..\..\..\srclib\apr\include\apr_want.h"\
+ ".\mod_dav.h"\
+
+
+..\..\..\build\win32\httpd.rc : \
+ "..\..\..\include\ap_release.h"\
+
diff --git a/modules/dav/main/mod_dav.dsp b/modules/dav/main/mod_dav.dsp
new file mode 100644
index 0000000..752cea8
--- /dev/null
+++ b/modules/dav/main/mod_dav.dsp
@@ -0,0 +1,147 @@
+# Microsoft Developer Studio Project File - Name="mod_dav" - Package Owner=<4>
+# Microsoft Developer Studio Generated Build File, Format Version 6.00
+# ** DO NOT EDIT **
+
+# TARGTYPE "Win32 (x86) Dynamic-Link Library" 0x0102
+
+CFG=mod_dav - Win32 Release
+!MESSAGE This is not a valid makefile. To build this project using NMAKE,
+!MESSAGE use the Export Makefile command and run
+!MESSAGE
+!MESSAGE NMAKE /f "mod_dav.mak".
+!MESSAGE
+!MESSAGE You can specify a configuration when running NMAKE
+!MESSAGE by defining the macro CFG on the command line. For example:
+!MESSAGE
+!MESSAGE NMAKE /f "mod_dav.mak" CFG="mod_dav - Win32 Release"
+!MESSAGE
+!MESSAGE Possible choices for configuration are:
+!MESSAGE
+!MESSAGE "mod_dav - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE "mod_dav - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE
+
+# Begin Project
+# PROP AllowPerConfigDependencies 0
+# PROP Scc_ProjName ""
+# PROP Scc_LocalPath ""
+CPP=cl.exe
+MTL=midl.exe
+RSC=rc.exe
+
+!IF "$(CFG)" == "mod_dav - Win32 Release"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 0
+# PROP BASE Output_Dir "Release"
+# PROP BASE Intermediate_Dir "Release"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 0
+# PROP Output_Dir "Release"
+# PROP Intermediate_Dir "Release"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /MD /W3 /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /FD /c
+# ADD CPP /nologo /MD /W3 /O2 /Oy- /Zi /I "../../../include" /I "../../../srclib/apr/include" /I "../../../srclib/apr-util/include" /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /D "DAV_DECLARE_EXPORT" /Fd"Release\mod_dav_src" /FD /c
+# ADD BASE MTL /nologo /D "NDEBUG" /win32
+# ADD MTL /nologo /D "NDEBUG" /mktyplib203 /win32
+# ADD BASE RSC /l 0x409 /d "NDEBUG"
+# ADD RSC /l 0x409 /fo"Release/mod_dav.res" /i "../../../include" /i "../../../srclib/apr/include" /d "NDEBUG" /d BIN_NAME="mod_dav.so" /d LONG_NAME="dav_module for Apache"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib ws2_32.lib mswsock.lib /nologo /subsystem:windows /dll /out:".\Release\mod_dav.so" /base:@..\..\..\os\win32\BaseAddr.ref,mod_dav.so
+# ADD LINK32 kernel32.lib ws2_32.lib mswsock.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Release\mod_dav.so" /base:@..\..\..\os\win32\BaseAddr.ref,mod_dav.so /opt:ref
+# Begin Special Build Tool
+TargetPath=.\Release\mod_dav.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+PostBuild_Cmds=if exist $(TargetPath).manifest mt.exe -manifest $(TargetPath).manifest -outputresource:$(TargetPath);2
+# End Special Build Tool
+
+!ELSEIF "$(CFG)" == "mod_dav - Win32 Debug"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 1
+# PROP BASE Output_Dir "Debug"
+# PROP BASE Intermediate_Dir "Debug"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 1
+# PROP Output_Dir "Debug"
+# PROP Intermediate_Dir "Debug"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /MDd /W3 /EHsc /Zi /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /FD /c
+# ADD CPP /nologo /MDd /W3 /EHsc /Zi /Od /I "../../../include" /I "../../../srclib/apr/include" /I "../../../srclib/apr-util/include" /D "_DEBUG" /D "WIN32" /D "_WINDOWS" /D "DAV_DECLARE_EXPORT" /Fd"Debug\mod_dav_src" /FD /c
+# ADD BASE MTL /nologo /D "_DEBUG" /win32
+# ADD MTL /nologo /D "_DEBUG" /mktyplib203 /win32
+# ADD BASE RSC /l 0x409 /d "_DEBUG"
+# ADD RSC /l 0x409 /fo"Debug/mod_dav.res" /i "../../../include" /i "../../../srclib/apr/include" /d "_DEBUG" /d BIN_NAME="mod_dav.so" /d LONG_NAME="dav_module for Apache"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib ws2_32.lib mswsock.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Debug\mod_dav.so" /base:@..\..\..\os\win32\BaseAddr.ref,mod_dav.so
+# ADD LINK32 kernel32.lib ws2_32.lib mswsock.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Debug\mod_dav.so" /base:@..\..\..\os\win32\BaseAddr.ref,mod_dav.so
+# Begin Special Build Tool
+TargetPath=.\Debug\mod_dav.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+PostBuild_Cmds=if exist $(TargetPath).manifest mt.exe -manifest $(TargetPath).manifest -outputresource:$(TargetPath);2
+# End Special Build Tool
+
+!ENDIF
+
+# Begin Target
+
+# Name "mod_dav - Win32 Release"
+# Name "mod_dav - Win32 Debug"
+# Begin Group "Source Files"
+
+# PROP Default_Filter "cpp;c;cxx;rc;def;r;odl;hpj;bat;for;f90"
+# Begin Source File
+
+SOURCE=.\liveprop.c
+# End Source File
+# Begin Source File
+
+SOURCE=.\mod_dav.c
+# End Source File
+# Begin Source File
+
+SOURCE=.\props.c
+# End Source File
+# Begin Source File
+
+SOURCE=.\providers.c
+# End Source File
+# Begin Source File
+
+SOURCE=.\std_liveprop.c
+# End Source File
+# Begin Source File
+
+SOURCE=.\util.c
+# End Source File
+# Begin Source File
+
+SOURCE=.\util_lock.c
+# End Source File
+# End Group
+# Begin Group "Header Files"
+
+# PROP Default_Filter "h;hpp;hxx;hm;inl;fi;fd"
+# Begin Source File
+
+SOURCE=.\mod_dav.h
+# End Source File
+# End Group
+# Begin Source File
+
+SOURCE=..\..\..\build\win32\httpd.rc
+# End Source File
+# End Target
+# End Project
diff --git a/modules/dav/main/mod_dav.h b/modules/dav/main/mod_dav.h
new file mode 100644
index 0000000..80ad117
--- /dev/null
+++ b/modules/dav/main/mod_dav.h
@@ -0,0 +1,2553 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * @file mod_dav.h
+ * @brief DAV extension module for Apache 2.0.*
+ *
+ * @defgroup MOD_DAV mod_dav
+ * @ingroup APACHE_MODS
+ * @{
+ */
+
+#ifndef _MOD_DAV_H_
+#define _MOD_DAV_H_
+
+#include "apr_hooks.h"
+#include "apr_hash.h"
+#include "apr_dbm.h"
+#include "apr_tables.h"
+
+#include "httpd.h"
+#include "util_filter.h"
+#include "util_xml.h"
+
+#include <limits.h> /* for INT_MAX */
+#include <time.h> /* for time_t */
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+
+#define DAV_VERSION AP_SERVER_BASEREVISION
+
+#define DAV_XML_HEADER "<?xml version=\"1.0\" encoding=\"utf-8\"?>"
+#define DAV_XML_CONTENT_TYPE "text/xml; charset=\"utf-8\""
+
+#define DAV_READ_BLOCKSIZE 2048 /* used for reading input blocks */
+
+#define DAV_RESPONSE_BODY_1 "<!DOCTYPE HTML PUBLIC \"-//IETF//DTD HTML 2.0//EN\">\n<html><head>\n<title>"
+#define DAV_RESPONSE_BODY_2 "</title>\n</head><body>\n<h1>"
+#define DAV_RESPONSE_BODY_3 "</h1>\n<p>"
+#define DAV_RESPONSE_BODY_4 "</p>\n"
+#define DAV_RESPONSE_BODY_5 "</body></html>\n"
+
+#define DAV_DO_COPY 0
+#define DAV_DO_MOVE 1
+
+
+#if 1
+#define DAV_DEBUG 1
+#define DEBUG_CR "\n"
+#define DBG0(f) ap_log_error(APLOG_MARK, \
+ APLOG_ERR, 0, NULL, (f))
+#define DBG1(f,a1) ap_log_error(APLOG_MARK, \
+ APLOG_ERR, 0, NULL, f, a1)
+#define DBG2(f,a1,a2) ap_log_error(APLOG_MARK, \
+ APLOG_ERR, 0, NULL, f, a1, a2)
+#define DBG3(f,a1,a2,a3) ap_log_error(APLOG_MARK, \
+ APLOG_ERR, 0, NULL, f, a1, a2, a3)
+#else
+#undef DAV_DEBUG
+#define DEBUG_CR ""
+#endif
+
+#define DAV_INFINITY INT_MAX /* for the Depth: header */
+
+/* Create a set of DAV_DECLARE(type), DAV_DECLARE_NONSTD(type) and
+ * DAV_DECLARE_DATA with appropriate export and import tags for the platform
+ */
+#if !defined(WIN32)
+#define DAV_DECLARE(type) type
+#define DAV_DECLARE_NONSTD(type) type
+#define DAV_DECLARE_DATA
+#elif defined(DAV_DECLARE_STATIC)
+#define DAV_DECLARE(type) type __stdcall
+#define DAV_DECLARE_NONSTD(type) type
+#define DAV_DECLARE_DATA
+#elif defined(DAV_DECLARE_EXPORT)
+#define DAV_DECLARE(type) __declspec(dllexport) type __stdcall
+#define DAV_DECLARE_NONSTD(type) __declspec(dllexport) type
+#define DAV_DECLARE_DATA __declspec(dllexport)
+#else
+#define DAV_DECLARE(type) __declspec(dllimport) type __stdcall
+#define DAV_DECLARE_NONSTD(type) __declspec(dllimport) type
+#define DAV_DECLARE_DATA __declspec(dllimport)
+#endif
+
+/* --------------------------------------------------------------------
+**
+** ERROR MANAGEMENT
+*/
+
+/*
+** dav_error structure.
+**
+** In most cases, mod_dav uses a pointer to a dav_error structure. If the
+** pointer is NULL, then no error has occurred.
+**
+** In certain cases, a dav_error structure is directly used. In these cases,
+** a status value of 0 means that an error has not occurred.
+**
+** Note: this implies that status != 0 whenever an error occurs.
+**
+** The desc field is optional (it may be NULL). When NULL, it typically
+** implies that Apache has a proper description for the specified status.
+*/
+typedef struct dav_error {
+ int status; /* suggested HTTP status (0 for no error) */
+ int error_id; /* DAV-specific error ID */
+ const char *desc; /* DAV:responsedescription and error log */
+
+ apr_status_t aprerr; /* APR error if any, or 0/APR_SUCCESS */
+
+ const char *namespace; /* [optional] namespace of error */
+ const char *tagname; /* name of error-tag */
+
+ struct dav_error *prev; /* previous error (in stack) */
+
+ const char *childtags; /* error-tag may have children */
+
+} dav_error;
+
+/*
+** Create a new error structure. save_errno will be filled with the current
+** errno value.
+*/
+DAV_DECLARE(dav_error*) dav_new_error(apr_pool_t *p, int status,
+ int error_id, apr_status_t aprerr,
+ const char *desc);
+
+
+/*
+** Create a new error structure with tagname and (optional) namespace;
+** namespace may be NULL, which means "DAV:".
+*/
+DAV_DECLARE(dav_error*) dav_new_error_tag(apr_pool_t *p, int status,
+ int error_id, apr_status_t aprerr,
+ const char *desc,
+ const char *namespace,
+ const char *tagname);
+
+
+/*
+** Push a new error description onto the stack of errors.
+**
+** This function is used to provide an additional description to an existing
+** error.
+**
+** <status> should contain the caller's view of what the current status is,
+** given the underlying error. If it doesn't have a better idea, then the
+** caller should pass prev->status.
+**
+** <error_id> can specify a new error_id since the topmost description has
+** changed.
+*/
+DAV_DECLARE(dav_error*) dav_push_error(apr_pool_t *p, int status, int error_id,
+ const char *desc, dav_error *prev);
+
+
+/*
+** Join two errors together.
+**
+** This function is used to add a new error stack onto an existing error so
+** that subsequent errors can be reported after the first error. It returns
+** the correct error stack to use so that the caller can blindly call it
+** without checking that both dest and src are not NULL.
+**
+** <dest> is the error stack that the error will be added to.
+**
+** <src> is the error stack that will be appended.
+*/
+DAV_DECLARE(dav_error*) dav_join_error(dav_error* dest, dav_error* src);
+
+typedef struct dav_response dav_response;
+
+/*
+** dav_handle_err()
+**
+** Handle the standard error processing. <err> must be non-NULL.
+**
+** <response> is set by the following:
+** - dav_validate_request()
+** - dav_add_lock()
+** - repos_hooks->remove_resource
+** - repos_hooks->move_resource
+** - repos_hooks->copy_resource
+** - vsn_hooks->update
+*/
+DAV_DECLARE(int) dav_handle_err(request_rec *r, dav_error *err,
+ dav_response *response);
+
+/* error ID values... */
+
+/* IF: header errors */
+#define DAV_ERR_IF_PARSE 100 /* general parsing error */
+#define DAV_ERR_IF_MULTIPLE_NOT 101 /* multiple "Not" found */
+#define DAV_ERR_IF_UNK_CHAR 102 /* unknown char in header */
+#define DAV_ERR_IF_ABSENT 103 /* no locktokens given */
+#define DAV_ERR_IF_TAGGED 104 /* in parsing tagged-list */
+#define DAV_ERR_IF_UNCLOSED_PAREN 105 /* in no-tagged-list */
+
+/* Prop DB errors */
+#define DAV_ERR_PROP_BAD_MAJOR 200 /* major version was wrong */
+#define DAV_ERR_PROP_READONLY 201 /* prop is read-only */
+#define DAV_ERR_PROP_NO_DATABASE 202 /* writable db not avail */
+#define DAV_ERR_PROP_NOT_FOUND 203 /* prop not found */
+#define DAV_ERR_PROP_BAD_LOCKDB 204 /* could not open lockdb */
+#define DAV_ERR_PROP_OPENING 205 /* problem opening propdb */
+#define DAV_ERR_PROP_EXEC 206 /* problem exec'ing patch */
+
+/* Predefined DB errors */
+/* ### any to define?? */
+
+/* Predefined locking system errors */
+#define DAV_ERR_LOCK_OPENDB 400 /* could not open lockdb */
+#define DAV_ERR_LOCK_NO_DB 401 /* no database defined */
+#define DAV_ERR_LOCK_CORRUPT_DB 402 /* DB is corrupt */
+#define DAV_ERR_LOCK_UNK_STATE_TOKEN 403 /* unknown State-token */
+#define DAV_ERR_LOCK_PARSE_TOKEN 404 /* bad opaquelocktoken */
+#define DAV_ERR_LOCK_SAVE_LOCK 405 /* err saving locks */
+
+/*
+** Some comments on Error ID values:
+**
+** The numbers do not necessarily need to be unique. Uniqueness simply means
+** that two errors that have not been predefined above can be distinguished
+** from each other. At the moment, mod_dav does not use this distinguishing
+** feature, but it could be used in the future to collapse <response> elements
+** into groups based on the error ID (and associated responsedescription).
+**
+** If a compute_desc is provided, then the error ID should be unique within
+** the context of the compute_desc function (so the function can figure out
+** what to filled into the desc).
+**
+** Basically, subsystems can ignore defining new error ID values if they want
+** to. The subsystems *do* need to return the predefined errors when
+** appropriate, so that mod_dav can figure out what to do. Subsystems can
+** simply leave the error ID field unfilled (zero) if there isn't an error
+** that must be placed there.
+*/
+
+
+/* --------------------------------------------------------------------
+**
+** HOOK STRUCTURES
+**
+** These are here for forward-declaration purposes. For more info, see
+** the section title "HOOK HANDLING" for more information, plus each
+** structure definition.
+*/
+
+/* forward-declare this structure */
+typedef struct dav_hooks_propdb dav_hooks_propdb;
+typedef struct dav_hooks_locks dav_hooks_locks;
+typedef struct dav_hooks_vsn dav_hooks_vsn;
+typedef struct dav_hooks_repository dav_hooks_repository;
+typedef struct dav_hooks_liveprop dav_hooks_liveprop;
+typedef struct dav_hooks_binding dav_hooks_binding;
+typedef struct dav_hooks_search dav_hooks_search;
+
+/* ### deprecated name */
+typedef dav_hooks_propdb dav_hooks_db;
+
+
+/* --------------------------------------------------------------------
+**
+** RESOURCE HANDLING
+*/
+
+/*
+** Resource Types:
+** The base protocol defines only file and collection resources.
+** The versioning protocol defines several additional resource types
+** to represent artifacts of a version control system.
+**
+** This enumeration identifies the type of URL used to identify the
+** resource. Since the same resource may have more than one type of
+** URL which can identify it, dav_resource_type cannot be used
+** alone to determine the type of the resource; attributes of the
+** dav_resource object must also be consulted.
+*/
+typedef enum {
+ DAV_RESOURCE_TYPE_UNKNOWN,
+
+ DAV_RESOURCE_TYPE_REGULAR, /* file or collection; could be
+ * unversioned, or version selector,
+ * or baseline selector */
+
+ DAV_RESOURCE_TYPE_VERSION, /* version or baseline URL */
+
+ DAV_RESOURCE_TYPE_HISTORY, /* version or baseline history URL */
+
+ DAV_RESOURCE_TYPE_WORKING, /* working resource URL */
+
+ DAV_RESOURCE_TYPE_WORKSPACE, /* workspace URL */
+
+ DAV_RESOURCE_TYPE_ACTIVITY, /* activity URL */
+
+ DAV_RESOURCE_TYPE_PRIVATE /* repository-private type */
+
+} dav_resource_type;
+
+/*
+** Opaque, repository-specific information for a resource.
+*/
+typedef struct dav_resource_private dav_resource_private;
+
+/*
+** Resource descriptor, generated by a repository provider.
+**
+** Note: the lock-null state is not explicitly represented here,
+** since it may be expensive to compute. Use dav_get_resource_state()
+** to determine whether a non-existent resource is a lock-null resource.
+**
+** A quick explanation of how the flags can apply to different resources:
+**
+** unversioned file or collection:
+** type = DAV_RESOURCE_TYPE_REGULAR
+** exists = ? (1 if exists)
+** collection = ? (1 if collection)
+** versioned = 0
+** baselined = 0
+** working = 0
+**
+** version-controlled resource or configuration:
+** type = DAV_RESOURCE_TYPE_REGULAR
+** exists = 1
+** collection = ? (1 if collection)
+** versioned = 1
+** baselined = ? (1 if configuration)
+** working = ? (1 if checked out)
+**
+** version/baseline history:
+** type = DAV_RESOURCE_TYPE_HISTORY
+** exists = 1
+** collection = 0
+** versioned = 0
+** baselined = 0
+** working = 0
+**
+** version/baseline:
+** type = DAV_RESOURCE_TYPE_VERSION
+** exists = 1
+** collection = ? (1 if collection)
+** versioned = 1
+** baselined = ? (1 if baseline)
+** working = 0
+**
+** working resource:
+** type = DAV_RESOURCE_TYPE_WORKING
+** exists = 1
+** collection = ? (1 if collection)
+** versioned = 1
+** baselined = 0
+** working = 1
+**
+** workspace:
+** type = DAV_RESOURCE_TYPE_WORKSPACE
+** exists = ? (1 if exists)
+** collection = 1
+** versioned = ? (1 if version-controlled)
+** baselined = ? (1 if baseline-controlled)
+** working = ? (1 if checked out)
+**
+** activity:
+** type = DAV_RESOURCE_TYPE_ACTIVITY
+** exists = ? (1 if exists)
+** collection = 0
+** versioned = 0
+** baselined = 0
+** working = 0
+*/
+typedef struct dav_resource {
+ dav_resource_type type;
+
+ int exists; /* 0 => null resource */
+
+ int collection; /* 0 => file; can be 1 for
+ * REGULAR, VERSION, and WORKING resources,
+ * and is always 1 for WORKSPACE */
+
+ int versioned; /* 0 => unversioned; can be 1 for
+ * REGULAR and WORKSPACE resources,
+ * and is always 1 for VERSION and WORKING */
+
+ int baselined; /* 0 => not baselined; can be 1 for
+ * REGULAR, VERSION, and WORKSPACE resources;
+ * versioned == 1 when baselined == 1 */
+
+ int working; /* 0 => not checked out; can be 1 for
+ * REGULAR and WORKSPACE resources,
+ * and is always 1 for WORKING */
+
+ const char *uri; /* the URI for this resource;
+ * currently has an ABI flaw where sometimes it is
+ * assumed to be encoded and sometimes not */
+
+ dav_resource_private *info; /* the provider's private info */
+
+ const dav_hooks_repository *hooks; /* hooks used for this resource */
+
+ /* When allocating items related specifically to this resource, the
+ following pool should be used. Its lifetime will be at least as
+ long as the dav_resource structure. */
+ apr_pool_t *pool;
+
+} dav_resource;
+
+/*
+** Lock token type. Lock providers define the details of a lock token.
+** However, all providers are expected to at least be able to parse
+** the "opaquelocktoken" scheme, which is represented by a uuid_t.
+*/
+typedef struct dav_locktoken dav_locktoken;
+
+
+/* --------------------------------------------------------------------
+**
+** BUFFER HANDLING
+**
+** These buffers are used as a lightweight buffer reuse mechanism. Apache
+** provides sub-pool creation and destruction to much the same effect, but
+** the sub-pools are a bit more general and heavyweight than these buffers.
+*/
+
+/* buffer for reuse; can grow to accommodate needed size */
+typedef struct
+{
+ apr_size_t alloc_len; /* how much has been allocated */
+ apr_size_t cur_len; /* how much is currently being used */
+ char *buf; /* buffer contents */
+} dav_buffer;
+#define DAV_BUFFER_MINSIZE 256 /* minimum size for buffer */
+#define DAV_BUFFER_PAD 64 /* amount of pad when growing */
+
+/* set the cur_len to the given size and ensure space is available */
+DAV_DECLARE(void) dav_set_bufsize(apr_pool_t *p, dav_buffer *pbuf,
+ apr_size_t size);
+
+/* initialize a buffer and copy the specified (null-term'd) string into it */
+DAV_DECLARE(void) dav_buffer_init(apr_pool_t *p, dav_buffer *pbuf,
+ const char *str);
+
+/* check that the buffer can accommodate <extra_needed> more bytes */
+DAV_DECLARE(void) dav_check_bufsize(apr_pool_t *p, dav_buffer *pbuf,
+ apr_size_t extra_needed);
+
+/* append a string to the end of the buffer, adjust length */
+DAV_DECLARE(void) dav_buffer_append(apr_pool_t *p, dav_buffer *pbuf,
+ const char *str);
+
+/* place a string on the end of the buffer, do NOT adjust length */
+DAV_DECLARE(void) dav_buffer_place(apr_pool_t *p, dav_buffer *pbuf,
+ const char *str);
+
+/* place some memory on the end of a buffer; do NOT adjust length */
+DAV_DECLARE(void) dav_buffer_place_mem(apr_pool_t *p, dav_buffer *pbuf,
+ const void *mem, apr_size_t amt,
+ apr_size_t pad);
+
+
+/* --------------------------------------------------------------------
+**
+** HANDY UTILITIES
+*/
+
+/* contains results from one of the getprop functions */
+typedef struct
+{
+ apr_text * propstats; /* <propstat> element text */
+ apr_text * xmlns; /* namespace decls for <response> elem */
+} dav_get_props_result;
+
+/* holds the contents of a <response> element */
+struct dav_response
+{
+ const char *href; /* always */
+ const char *desc; /* optional description at <response> level */
+
+ /* use status if propresult.propstats is NULL. */
+ dav_get_props_result propresult;
+
+ int status;
+
+ struct dav_response *next;
+};
+
+typedef struct
+{
+ request_rec *rnew; /* new subrequest */
+ dav_error err; /* potential error response */
+} dav_lookup_result;
+
+
+DAV_DECLARE(dav_lookup_result) dav_lookup_uri(const char *uri, request_rec *r,
+ int must_be_absolute);
+
+/* defines type of property info a provider is to return */
+typedef enum {
+ DAV_PROP_INSERT_NOTDEF, /* property is defined by this provider,
+ but nothing was inserted because the
+ (live) property is not defined for this
+ resource (it may be present as a dead
+ property). */
+ DAV_PROP_INSERT_NOTSUPP, /* property is recognized by this provider,
+ but it is not supported, and cannot be
+ treated as a dead property */
+ DAV_PROP_INSERT_NAME, /* a property name (empty elem) was
+ inserted into the text block */
+ DAV_PROP_INSERT_VALUE, /* a property name/value pair was inserted
+ into the text block */
+ DAV_PROP_INSERT_SUPPORTED /* a supported live property was added to
+ the text block as a
+ <DAV:supported-live-property> element */
+} dav_prop_insert;
+
+/* ### this stuff is private to dav/fs/repos.c; move it... */
+/* format a time string (buf must be at least DAV_TIMEBUF_SIZE chars) */
+#define DAV_STYLE_ISO8601 1
+#define DAV_STYLE_RFC822 2
+#define DAV_TIMEBUF_SIZE 30
+
+/* Write a complete RESPONSE object out as a <DAV:response> xml
+ * element. Data is sent into brigade BB, which is auto-flushed into
+ * the output filter stack for request R. Use POOL for any temporary
+ * allocations.
+ *
+ * [Presumably the <multistatus> tag has already been written; this
+ * routine is shared by dav_send_multistatus and dav_stream_response.]
+ */
+DAV_DECLARE(void) dav_send_one_response(dav_response *response,
+ apr_bucket_brigade *bb,
+ request_rec *r,
+ apr_pool_t *pool);
+
+/* Factorized helper function: prep request_rec R for a multistatus
+ * response and write <multistatus> tag into BB, destined for
+ * R->output_filters. Use xml NAMESPACES in initial tag, if
+ * non-NULL.
+ */
+DAV_DECLARE(void) dav_begin_multistatus(apr_bucket_brigade *bb,
+ request_rec *r, int status,
+ apr_array_header_t *namespaces);
+
+/* Finish a multistatus response started by dav_begin_multistatus: */
+DAV_DECLARE(apr_status_t) dav_finish_multistatus(request_rec *r,
+ apr_bucket_brigade *bb);
+
+/* Send a multistatus response */
+DAV_DECLARE(void) dav_send_multistatus(request_rec *r, int status,
+ dav_response *first,
+ apr_array_header_t *namespaces);
+
+DAV_DECLARE(apr_text *) dav_failed_proppatch(apr_pool_t *p,
+ apr_array_header_t *prop_ctx);
+DAV_DECLARE(apr_text *) dav_success_proppatch(apr_pool_t *p,
+ apr_array_header_t *prop_ctx);
+
+DAV_DECLARE(int) dav_get_depth(request_rec *r, int def_depth);
+
+DAV_DECLARE(int) dav_validate_root(const apr_xml_doc *doc,
+ const char *tagname);
+DAV_DECLARE(apr_xml_elem *) dav_find_child(const apr_xml_elem *elem,
+ const char *tagname);
+
+/* gather up all the CDATA into a single string */
+DAV_DECLARE(const char *) dav_xml_get_cdata(const apr_xml_elem *elem, apr_pool_t *pool,
+ int strip_white);
+
+/*
+** XML namespace handling
+**
+** This structure tracks namespace declarations (xmlns:prefix="URI").
+** It maintains a one-to-many relationship of URIs-to-prefixes. In other
+** words, one URI may be defined by many prefixes, but any specific
+** prefix will specify only one URI.
+**
+** Prefixes using the "g###" pattern can be generated automatically if
+** the caller does not have specific prefix requirements.
+*/
+typedef struct {
+ apr_pool_t *pool;
+ apr_hash_t *uri_prefix; /* map URIs to an available prefix */
+ apr_hash_t *prefix_uri; /* map all prefixes to their URIs */
+ int count; /* counter for "g###" prefixes */
+} dav_xmlns_info;
+
+/* create an empty dav_xmlns_info structure */
+DAV_DECLARE(dav_xmlns_info *) dav_xmlns_create(apr_pool_t *pool);
+
+/* add a specific prefix/URI pair. the prefix/uri should have a lifetime
+ at least that of xmlns->pool */
+DAV_DECLARE(void) dav_xmlns_add(dav_xmlns_info *xi,
+ const char *prefix, const char *uri);
+
+/* add a URI (if not present); any prefix is acceptable and is returned.
+ the uri should have a lifetime at least that xmlns->pool */
+DAV_DECLARE(const char *) dav_xmlns_add_uri(dav_xmlns_info *xi,
+ const char *uri);
+
+/* return the URI for a specified prefix (or NULL if the prefix is unknown) */
+DAV_DECLARE(const char *) dav_xmlns_get_uri(dav_xmlns_info *xi,
+ const char *prefix);
+
+/* return an available prefix for a specified URI (or NULL if the URI
+ is unknown) */
+DAV_DECLARE(const char *) dav_xmlns_get_prefix(dav_xmlns_info *xi,
+ const char *uri);
+
+/* generate xmlns declarations (appending into the given text) */
+DAV_DECLARE(void) dav_xmlns_generate(dav_xmlns_info *xi,
+ apr_text_header *phdr);
+
+/* --------------------------------------------------------------------
+**
+** DAV PLUGINS
+*/
+
+/* ### docco ... */
+
+/*
+** dav_provider
+**
+** This structure wraps up all of the hooks that a mod_dav provider can
+** supply. The provider MUST supply <repos> and <propdb>. The rest are
+** optional and should contain NULL if that feature is not supplied.
+**
+** Note that a provider cannot pick and choose portions from various
+** underlying implementations (which was theoretically possible in
+** mod_dav 1.0). There are too many dependencies between a dav_resource
+** (defined by <repos>) and the other functionality.
+**
+** Live properties are not part of the dav_provider structure because they
+** are handled through the APR_HOOK interface (to allow for multiple liveprop
+** providers). The core always provides some properties, and then a given
+** provider will add more properties.
+**
+** Some providers may need to associate a context with the dav_provider
+** structure -- the ctx field is available for storing this context. Just
+** leave it NULL if it isn't required.
+*/
+typedef struct {
+ const dav_hooks_repository *repos;
+ const dav_hooks_propdb *propdb;
+ const dav_hooks_locks *locks;
+ const dav_hooks_vsn *vsn;
+ const dav_hooks_binding *binding;
+ const dav_hooks_search *search;
+
+ void *ctx;
+} dav_provider;
+
+/*
+** gather_propsets: gather all live property propset-URIs
+**
+** The hook implementor should push one or more URIs into the specified
+** array. These URIs are returned in the DAV: header to let clients know
+** what sets of live properties are supported by the installation. mod_dav
+** will place open/close angle brackets around each value (much like
+** a Coded-URL); quotes and brackets should not be in the value.
+**
+** Example: http://apache.org/dav/props/
+**
+** (of course, use your own domain to ensure a unique value)
+*/
+APR_DECLARE_EXTERNAL_HOOK(dav, DAV, void, gather_propsets,
+ (apr_array_header_t *uris))
+
+/*
+** find_liveprop: find a live property, returning a non-zero, unique,
+** opaque identifier.
+**
+** If the hook implementor determines the specified URI/name refers to
+** one of its properties, then it should fill in HOOKS and return a
+** non-zero value. The returned value is the "property ID" and will
+** be passed to the various liveprop hook functions.
+**
+** Return 0 if the property is not defined by the hook implementor.
+*/
+APR_DECLARE_EXTERNAL_HOOK(dav, DAV, int, find_liveprop,
+ (const dav_resource *resource,
+ const char *ns_uri, const char *name,
+ const dav_hooks_liveprop **hooks))
+
+/*
+** insert_all_liveprops: insert all (known) live property names/values.
+**
+** The hook implementor should append XML text to PHDR, containing liveprop
+** names. If INSVALUE is true, then the property values should also be
+** inserted into the output XML stream.
+**
+** The liveprop provider should insert *all* known and *defined* live
+** properties on the specified resource. If a particular liveprop is
+** not defined for this resource, then it should not be inserted.
+*/
+APR_DECLARE_EXTERNAL_HOOK(dav, DAV, void, insert_all_liveprops,
+ (request_rec *r, const dav_resource *resource,
+ dav_prop_insert what, apr_text_header *phdr))
+
+DAV_DECLARE(const dav_hooks_locks *) dav_get_lock_hooks(request_rec *r);
+DAV_DECLARE(const dav_hooks_propdb *) dav_get_propdb_hooks(request_rec *r);
+DAV_DECLARE(const dav_hooks_vsn *) dav_get_vsn_hooks(request_rec *r);
+DAV_DECLARE(const dav_hooks_binding *) dav_get_binding_hooks(request_rec *r);
+DAV_DECLARE(const dav_hooks_search *) dav_get_search_hooks(request_rec *r);
+
+DAV_DECLARE(void) dav_register_provider(apr_pool_t *p, const char *name,
+ const dav_provider *hooks);
+DAV_DECLARE(const dav_provider *) dav_lookup_provider(const char *name);
+DAV_DECLARE(const char *) dav_get_provider_name(request_rec *r);
+
+
+/* ### deprecated */
+#define DAV_GET_HOOKS_PROPDB(r) dav_get_propdb_hooks(r)
+#define DAV_GET_HOOKS_LOCKS(r) dav_get_lock_hooks(r)
+#define DAV_GET_HOOKS_VSN(r) dav_get_vsn_hooks(r)
+#define DAV_GET_HOOKS_BINDING(r) dav_get_binding_hooks(r)
+#define DAV_GET_HOOKS_SEARCH(r) dav_get_search_hooks(r)
+
+
+/* --------------------------------------------------------------------
+**
+** IF HEADER PROCESSING
+**
+** Here is the definition of the If: header from RFC 2518, S9.4:
+**
+** If = "If" ":" (1*No-tag-list | 1*Tagged-list)
+** No-tag-list = List
+** Tagged-list = Resource 1*List
+** Resource = Coded-URL
+** List = "(" 1*(["Not"](State-token | "[" entity-tag "]")) ")"
+** State-token = Coded-URL
+** Coded-URL = "<" absoluteURI ">" ; absoluteURI from RFC 2616
+**
+** List corresponds to dav_if_state_list. No-tag-list corresponds to
+** dav_if_header with uri==NULL. Tagged-list corresponds to a sequence of
+** dav_if_header structures with (duplicate) uri==Resource -- one
+** dav_if_header per state_list. A second Tagged-list will start a new
+** sequence of dav_if_header structures with the new URI.
+**
+** A summary of the semantics, mapped into our structures:
+** - Chained dav_if_headers: OR
+** - Chained dav_if_state_lists: AND
+** - NULL uri matches all resources
+*/
+
+typedef enum
+{
+ dav_if_etag,
+ dav_if_opaquelock,
+ dav_if_unknown /* the "unknown" state type; always matches false. */
+} dav_if_state_type;
+
+typedef struct dav_if_state_list
+{
+ dav_if_state_type type;
+
+ int condition;
+#define DAV_IF_COND_NORMAL 0
+#define DAV_IF_COND_NOT 1 /* "Not" was applied */
+
+ const char *etag;
+ dav_locktoken *locktoken;
+
+ struct dav_if_state_list *next;
+} dav_if_state_list;
+
+typedef struct dav_if_header
+{
+ const char *uri;
+ apr_size_t uri_len;
+ struct dav_if_state_list *state;
+ struct dav_if_header *next;
+
+ int dummy_header; /* used internally by the lock/etag validation */
+} dav_if_header;
+
+typedef struct dav_locktoken_list
+{
+ dav_locktoken *locktoken;
+ struct dav_locktoken_list *next;
+} dav_locktoken_list;
+
+DAV_DECLARE(dav_error *) dav_get_locktoken_list(request_rec *r,
+ dav_locktoken_list **ltl);
+
+
+/* --------------------------------------------------------------------
+**
+** LIVE PROPERTY HANDLING
+*/
+
+/* opaque type for PROPPATCH rollback information */
+typedef struct dav_liveprop_rollback dav_liveprop_rollback;
+
+struct dav_hooks_liveprop
+{
+ /*
+ ** Insert property information into a text block. The property to
+ ** insert is identified by the propid value. The information to insert
+ ** is identified by the "what" argument, as follows:
+ ** DAV_PROP_INSERT_NAME
+ ** property name, as an empty XML element
+ ** DAV_PROP_INSERT_VALUE
+ ** property name/value, as an XML element
+ ** DAV_PROP_INSERT_SUPPORTED
+ ** if the property is defined on the resource, then
+ ** a DAV:supported-live-property element, as defined
+ ** by the DeltaV extensions to RFC2518.
+ **
+ ** Providers should return DAV_PROP_INSERT_NOTDEF if the property is
+ ** known and not defined for this resource, so should be handled as a
+ ** dead property. If a provider recognizes, but does not support, a
+ ** property, and does not want it handled as a dead property, it should
+ ** return DAV_PROP_INSERT_NOTSUPP.
+ **
+ ** Returns one of DAV_PROP_INSERT_* based on what happened.
+ **
+ ** ### we may need more context... ie. the lock database
+ */
+ dav_prop_insert (*insert_prop)(const dav_resource *resource,
+ int propid, dav_prop_insert what,
+ apr_text_header *phdr);
+
+ /*
+ ** Determine whether a given property is writable.
+ **
+ ** ### we may want a different semantic. i.e. maybe it should be
+ ** ### "can we write <value> into this property?"
+ **
+ ** Returns 1 if the live property can be written, 0 if read-only.
+ */
+ int (*is_writable)(const dav_resource *resource, int propid);
+
+ /*
+ ** This member defines the set of namespace URIs that the provider
+ ** uses for its properties. When insert_all is called, it will be
+ ** passed a list of integers that map from indices into this list
+ ** to namespace IDs for output generation.
+ **
+ ** The last entry in this list should be a NULL value (sentinel).
+ */
+ const char * const * namespace_uris;
+
+ /*
+ ** ### this is not the final design. we want an open-ended way for
+ ** ### liveprop providers to attach *new* properties. To this end,
+ ** ### we'll have a "give me a list of the props you define", a way
+ ** ### to check for a prop's existence, a way to validate a set/remove
+ ** ### of a prop, and a way to execute/commit/rollback that change.
+ */
+
+ /*
+ ** Validate that the live property can be assigned a value, and that
+ ** the provided value is valid.
+ **
+ ** elem will point to the XML element that names the property. For
+ ** example:
+ ** <lp1:executable>T</lp1:executable>
+ **
+ ** The provider can access the cdata fields and the child elements
+ ** to extract the relevant pieces.
+ **
+ ** operation is one of DAV_PROP_OP_SET or _DELETE.
+ **
+ ** The provider may return a value in *context which will be passed
+ ** to each of the exec/commit/rollback functions. For example, this
+ ** may contain an internal value which has been processed from the
+ ** input element.
+ **
+ ** The provider must set defer_to_dead to true (non-zero) or false.
+ ** If true, then the set/remove is deferred to the dead property
+ ** database. Note: it will be set to zero on entry.
+ */
+ dav_error * (*patch_validate)(const dav_resource *resource,
+ const apr_xml_elem *elem,
+ int operation,
+ void **context,
+ int *defer_to_dead);
+
+ /* ### doc... */
+ dav_error * (*patch_exec)(const dav_resource *resource,
+ const apr_xml_elem *elem,
+ int operation,
+ void *context,
+ dav_liveprop_rollback **rollback_ctx);
+
+ /* ### doc... */
+ void (*patch_commit)(const dav_resource *resource,
+ int operation,
+ void *context,
+ dav_liveprop_rollback *rollback_ctx);
+
+ /* ### doc... */
+ dav_error * (*patch_rollback)(const dav_resource *resource,
+ int operation,
+ void *context,
+ dav_liveprop_rollback *rollback_ctx);
+
+ /*
+ ** If a provider needs a context to associate with this hooks structure,
+ ** then this field may be used. In most cases, it will just be NULL.
+ */
+ void *ctx;
+};
+
+/*
+** dav_liveprop_spec: specify a live property
+**
+** This structure is used as a standard way to determine if a particular
+** property is a live property. Its use is not part of the mandated liveprop
+** interface, but can be used by liveprop providers in conjunction with the
+** utility routines below.
+**
+** spec->name == NULL is the defined end-sentinel for a list of specs.
+*/
+typedef struct {
+ int ns; /* provider-local namespace index */
+ const char *name; /* name of the property */
+
+ int propid; /* provider-local property ID */
+
+ int is_writable; /* is the property writable? */
+
+} dav_liveprop_spec;
+
+/*
+** dav_liveprop_group: specify a group of liveprops
+**
+** This structure specifies a group of live properties, their namespaces,
+** and how to handle them.
+*/
+typedef struct {
+ const dav_liveprop_spec *specs;
+ const char * const *namespace_uris;
+ const dav_hooks_liveprop *hooks;
+
+} dav_liveprop_group;
+
+/* ### docco */
+DAV_DECLARE(int) dav_do_find_liveprop(const char *ns_uri, const char *name,
+ const dav_liveprop_group *group,
+ const dav_hooks_liveprop **hooks);
+
+/* ### docco */
+DAV_DECLARE(long) dav_get_liveprop_info(int propid,
+ const dav_liveprop_group *group,
+ const dav_liveprop_spec **info);
+
+/* ### docco */
+DAV_DECLARE(void) dav_register_liveprop_group(apr_pool_t *pool,
+ const dav_liveprop_group *group);
+
+/* ### docco */
+DAV_DECLARE(long) dav_get_liveprop_ns_index(const char *uri);
+
+/* ### docco */
+DAV_DECLARE(long) dav_get_liveprop_ns_count(void);
+
+/* ### docco */
+DAV_DECLARE(void) dav_add_all_liveprop_xmlns(apr_pool_t *p,
+ apr_text_header *phdr);
+
+/*
+** The following three functions are part of mod_dav's internal handling
+** for the core WebDAV properties. They are not part of mod_dav's API.
+*/
+DAV_DECLARE_NONSTD(int) dav_core_find_liveprop(
+ const dav_resource *resource,
+ const char *ns_uri,
+ const char *name,
+ const dav_hooks_liveprop **hooks);
+DAV_DECLARE_NONSTD(void) dav_core_insert_all_liveprops(
+ request_rec *r,
+ const dav_resource *resource,
+ dav_prop_insert what,
+ apr_text_header *phdr);
+DAV_DECLARE_NONSTD(void) dav_core_register_uris(apr_pool_t *p);
+
+
+/*
+** Standard WebDAV Property Identifiers
+**
+** A live property provider does not need to use these; they are simply
+** provided for convenience.
+**
+** Property identifiers need to be unique within a given provider, but not
+** *across* providers (note: this uniqueness constraint was different in
+** older versions of mod_dav).
+**
+** The identifiers start at 20000 to make it easier for providers to avoid
+** conflicts with the standard properties. The properties are arranged
+** alphabetically, and may be reordered from time to time (as properties
+** are introduced).
+**
+** NOTE: there is no problem with reordering (e.g. binary compat) since the
+** identifiers are only used within a given provider, which would pick up
+** the entire set of changes upon a recompile.
+*/
+enum {
+ DAV_PROPID_BEGIN = 20000,
+
+ /* Standard WebDAV properties (RFC 2518) */
+ DAV_PROPID_creationdate,
+ DAV_PROPID_displayname,
+ DAV_PROPID_getcontentlanguage,
+ DAV_PROPID_getcontentlength,
+ DAV_PROPID_getcontenttype,
+ DAV_PROPID_getetag,
+ DAV_PROPID_getlastmodified,
+ DAV_PROPID_lockdiscovery,
+ DAV_PROPID_resourcetype,
+ DAV_PROPID_source,
+ DAV_PROPID_supportedlock,
+
+ /* DeltaV properties (from the I-D (#14)) */
+ DAV_PROPID_activity_checkout_set,
+ DAV_PROPID_activity_set,
+ DAV_PROPID_activity_version_set,
+ DAV_PROPID_auto_merge_set,
+ DAV_PROPID_auto_version,
+ DAV_PROPID_baseline_collection,
+ DAV_PROPID_baseline_controlled_collection,
+ DAV_PROPID_baseline_controlled_collection_set,
+ DAV_PROPID_checked_in,
+ DAV_PROPID_checked_out,
+ DAV_PROPID_checkin_fork,
+ DAV_PROPID_checkout_fork,
+ DAV_PROPID_checkout_set,
+ DAV_PROPID_comment,
+ DAV_PROPID_creator_displayname,
+ DAV_PROPID_current_activity_set,
+ DAV_PROPID_current_workspace_set,
+ DAV_PROPID_default_variant,
+ DAV_PROPID_eclipsed_set,
+ DAV_PROPID_label_name_set,
+ DAV_PROPID_merge_set,
+ DAV_PROPID_precursor_set,
+ DAV_PROPID_predecessor_set,
+ DAV_PROPID_root_version,
+ DAV_PROPID_subactivity_set,
+ DAV_PROPID_subbaseline_set,
+ DAV_PROPID_successor_set,
+ DAV_PROPID_supported_method_set,
+ DAV_PROPID_supported_live_property_set,
+ DAV_PROPID_supported_report_set,
+ DAV_PROPID_unreserved,
+ DAV_PROPID_variant_set,
+ DAV_PROPID_version_controlled_binding_set,
+ DAV_PROPID_version_controlled_configuration,
+ DAV_PROPID_version_history,
+ DAV_PROPID_version_name,
+ DAV_PROPID_workspace,
+ DAV_PROPID_workspace_checkout_set,
+
+ DAV_PROPID_END
+};
+
+/*
+** Property Identifier Registration
+**
+** At the moment, mod_dav requires live property providers to ensure that
+** each property returned has a unique value. For now, this is done through
+** central registration (there are no known providers other than the default,
+** so this remains manageable).
+**
+** WARNING: the TEST ranges should never be "shipped".
+*/
+#define DAV_PROPID_CORE 10000 /* ..10099. defined by mod_dav */
+#define DAV_PROPID_FS 10100 /* ..10299.
+ mod_dav filesystem provider. */
+#define DAV_PROPID_TEST1 10300 /* ..10399 */
+#define DAV_PROPID_TEST2 10400 /* ..10499 */
+#define DAV_PROPID_TEST3 10500 /* ..10599 */
+/* Next: 10600 */
+
+
+/* --------------------------------------------------------------------
+**
+** DATABASE FUNCTIONS
+*/
+
+typedef struct dav_db dav_db;
+typedef struct dav_namespace_map dav_namespace_map;
+typedef struct dav_deadprop_rollback dav_deadprop_rollback;
+
+typedef struct {
+ const char *ns; /* "" signals "no namespace" */
+ const char *name;
+} dav_prop_name;
+
+/* hook functions to enable pluggable databases */
+struct dav_hooks_propdb
+{
+ dav_error * (*open)(apr_pool_t *p, const dav_resource *resource, int ro,
+ dav_db **pdb);
+ void (*close)(dav_db *db);
+
+ /*
+ ** In bulk, define any namespaces that the values and their name
+ ** elements may need.
+ **
+ ** Note: sometimes mod_dav will defer calling this until output_value
+ ** returns found==1. If the output process needs the dav_xmlns_info
+ ** filled for its work, then it will need to fill it on demand rather
+ ** than depending upon this hook to fill in the structure.
+ **
+ ** Note: this will *always* be called during an output sequence. Thus,
+ ** the provider may rely solely on using this to fill the xmlns info.
+ */
+ dav_error * (*define_namespaces)(dav_db *db, dav_xmlns_info *xi);
+
+ /*
+ ** Output the value from the database (i.e. add an element name and
+ ** the value into *phdr). Set *found based on whether the name/value
+ ** was found in the propdb.
+ **
+ ** Note: it is NOT an error for the key/value pair to not exist.
+ **
+ ** The dav_xmlns_info passed to define_namespaces() is also passed to
+ ** each output_value() call so that namespaces can be added on-demand.
+ ** It can also be used to look up prefixes or URIs during the output
+ ** process.
+ */
+ dav_error * (*output_value)(dav_db *db, const dav_prop_name *name,
+ dav_xmlns_info *xi,
+ apr_text_header *phdr, int *found);
+
+ /*
+ ** Build a mapping from "global" namespaces (stored in apr_xml_*)
+ ** into provider-local namespace identifiers.
+ **
+ ** This mapping should be done once per set of namespaces, and the
+ ** resulting mapping should be passed into the store() hook function.
+ **
+ ** Note: usually, there is just a single document/namespaces for all
+ ** elements passed. However, the generality of creating multiple
+ ** mappings and passing them to store() is provided here.
+ **
+ ** Note: this is only in preparation for a series of store() calls.
+ ** As a result, the propdb must be open for read/write access when
+ ** this function is called.
+ */
+ dav_error * (*map_namespaces)(dav_db *db,
+ const apr_array_header_t *namespaces,
+ dav_namespace_map **mapping);
+
+ /*
+ ** Store a property value for a given name. The value->combined field
+ ** MUST be set for this call.
+ **
+ ** ### WARNING: current providers will quote the text within ELEM.
+ ** ### this implies you can call this function only once with a given
+ ** ### element structure (a second time will quote it again).
+ */
+ dav_error * (*store)(dav_db *db, const dav_prop_name *name,
+ const apr_xml_elem *elem,
+ dav_namespace_map *mapping);
+
+ /* remove a given property */
+ dav_error * (*remove)(dav_db *db, const dav_prop_name *name);
+
+ /* returns 1 if the record specified by "key" exists; 0 otherwise */
+ int (*exists)(dav_db *db, const dav_prop_name *name);
+
+ /*
+ ** Iterate over the property names in the database.
+ **
+ ** iter->name.ns == iter->name.name == NULL when there are no more names.
+ **
+ ** Note: only one iteration may occur over the propdb at a time.
+ */
+ dav_error * (*first_name)(dav_db *db, dav_prop_name *pname);
+ dav_error * (*next_name)(dav_db *db, dav_prop_name *pname);
+
+ /*
+ ** Rollback support: get rollback context, and apply it.
+ **
+ ** struct dav_deadprop_rollback is a provider-private structure; it
+ ** should remember the name, and the name's old value (or the fact that
+ ** the value was not present, and should be deleted if a rollback occurs).
+ */
+ dav_error * (*get_rollback)(dav_db *db, const dav_prop_name *name,
+ dav_deadprop_rollback **prollback);
+ dav_error * (*apply_rollback)(dav_db *db,
+ dav_deadprop_rollback *rollback);
+
+ /*
+ ** If a provider needs a context to associate with this hooks structure,
+ ** then this field may be used. In most cases, it will just be NULL.
+ */
+ void *ctx;
+};
+
+
+/* --------------------------------------------------------------------
+**
+** LOCK FUNCTIONS
+*/
+
+/* Used to represent a Timeout header of "Infinity" */
+#define DAV_TIMEOUT_INFINITE 0
+
+DAV_DECLARE(time_t) dav_get_timeout(request_rec *r);
+
+/*
+** Opaque, provider-specific information for a lock database.
+*/
+typedef struct dav_lockdb_private dav_lockdb_private;
+
+/*
+** Opaque, provider-specific information for a lock record.
+*/
+typedef struct dav_lock_private dav_lock_private;
+
+/*
+** Lock database type. Lock providers are urged to implement a "lazy" open, so
+** doing an "open" is cheap until something is actually needed from the DB.
+*/
+typedef struct
+{
+ const dav_hooks_locks *hooks; /* the hooks used for this lockdb */
+ int ro; /* was it opened readonly? */
+
+ dav_lockdb_private *info;
+
+} dav_lockdb;
+
+typedef enum {
+ DAV_LOCKSCOPE_UNKNOWN,
+ DAV_LOCKSCOPE_EXCLUSIVE,
+ DAV_LOCKSCOPE_SHARED
+} dav_lock_scope;
+
+typedef enum {
+ DAV_LOCKTYPE_UNKNOWN,
+ DAV_LOCKTYPE_WRITE
+} dav_lock_type;
+
+typedef enum {
+ DAV_LOCKREC_DIRECT, /* lock asserted on this resource */
+ DAV_LOCKREC_INDIRECT, /* lock inherited from a parent */
+ DAV_LOCKREC_INDIRECT_PARTIAL /* most info is not filled in */
+} dav_lock_rectype;
+
+/*
+** dav_lock: hold information about a lock on a resource.
+**
+** This structure is used for both direct and indirect locks. A direct lock
+** is a lock applied to a specific resource by the client. An indirect lock
+** is one that is inherited from a parent resource by virtue of a non-zero
+** Depth: header when the lock was applied.
+**
+** mod_dav records both types of locks in the lock database, managing their
+** addition/removal as resources are moved about the namespace.
+**
+** Note that the lockdb is free to marshal this structure in any form that
+** it likes.
+**
+** For a "partial" lock, the <rectype> and <locktoken> fields must be filled
+** in. All other (user) fields should be zeroed. The lock provider will
+** usually fill in the <info> field, and the <next> field may be used to
+** construct a list of partial locks.
+**
+** The lock provider MUST use the info field to store a value such that a
+** dav_lock structure can locate itself in the underlying lock database.
+** This requirement is needed for refreshing: when an indirect dav_lock is
+** refreshed, its reference to the direct lock does not specify the direct's
+** resource, so the only way to locate the (refreshed, direct) lock in the
+** database is to use the info field.
+**
+** Note that <is_locknull> only refers to the resource where this lock was
+** found.
+** ### hrm. that says the abstraction is wrong. is_locknull may disappear.
+*/
+typedef struct dav_lock
+{
+ dav_lock_rectype rectype; /* type of lock record */
+ int is_locknull; /* lock establishes a locknull resource */
+
+ /* ### put the resource in here? */
+
+ dav_lock_scope scope; /* scope of the lock */
+ dav_lock_type type; /* type of lock */
+ int depth; /* depth of the lock */
+ time_t timeout; /* when the lock will timeout */
+
+ const dav_locktoken *locktoken; /* the token that was issued */
+
+ const char *owner; /* (XML) owner of the lock */
+ const char *auth_user; /* auth'd username owning lock */
+
+ dav_lock_private *info; /* private to the lockdb */
+
+ struct dav_lock *next; /* for managing a list of locks */
+} dav_lock;
+
+/* Property-related public lock functions */
+DAV_DECLARE(const char *)dav_lock_get_activelock(request_rec *r,
+ dav_lock *locks,
+ dav_buffer *pbuf);
+
+/* LockDB-related public lock functions */
+DAV_DECLARE(dav_error *) dav_lock_parse_lockinfo(request_rec *r,
+ const dav_resource *resrouce,
+ dav_lockdb *lockdb,
+ const apr_xml_doc *doc,
+ dav_lock **lock_request);
+DAV_DECLARE(int) dav_unlock(request_rec *r,
+ const dav_resource *resource,
+ const dav_locktoken *locktoken);
+DAV_DECLARE(dav_error *) dav_add_lock(request_rec *r,
+ const dav_resource *resource,
+ dav_lockdb *lockdb, dav_lock *request,
+ dav_response **response);
+DAV_DECLARE(dav_error *) dav_notify_created(request_rec *r,
+ dav_lockdb *lockdb,
+ const dav_resource *resource,
+ int resource_state,
+ int depth);
+
+DAV_DECLARE(dav_error*) dav_lock_query(dav_lockdb *lockdb,
+ const dav_resource *resource,
+ dav_lock **locks);
+
+DAV_DECLARE(dav_error *) dav_validate_request(request_rec *r,
+ dav_resource *resource,
+ int depth,
+ dav_locktoken *locktoken,
+ dav_response **response,
+ int flags,
+ dav_lockdb *lockdb);
+/*
+** flags:
+** 0x0F -- reserved for <dav_lock_scope> values
+**
+** other flags, detailed below
+*/
+#define DAV_VALIDATE_RESOURCE 0x0010 /* validate just the resource */
+#define DAV_VALIDATE_PARENT 0x0020 /* validate resource AND its parent */
+#define DAV_VALIDATE_ADD_LD 0x0040 /* add DAV:lockdiscovery into
+ the 424 DAV:response */
+#define DAV_VALIDATE_USE_424 0x0080 /* return 424 status, not 207 */
+#define DAV_VALIDATE_IS_PARENT 0x0100 /* for internal use */
+#define DAV_VALIDATE_NO_MODIFY 0x0200 /* resource is not being modified
+ so allow even if lock token
+ is not provided */
+
+/* Lock-null related public lock functions */
+DAV_DECLARE(int) dav_get_resource_state(request_rec *r,
+ const dav_resource *resource);
+
+/* Lock provider hooks. Locking is optional, so there may be no
+ * lock provider for a given repository.
+ */
+struct dav_hooks_locks
+{
+ /* Return the supportedlock property for a resource */
+ const char * (*get_supportedlock)(
+ const dav_resource *resource
+ );
+
+ /* Parse a lock token URI, returning a lock token object allocated
+ * in the given pool.
+ */
+ dav_error * (*parse_locktoken)(
+ apr_pool_t *p,
+ const char *char_token,
+ dav_locktoken **locktoken_p
+ );
+
+ /* Format a lock token object into a URI string, allocated in
+ * the given pool.
+ *
+ * Always returns non-NULL.
+ */
+ const char * (*format_locktoken)(
+ apr_pool_t *p,
+ const dav_locktoken *locktoken
+ );
+
+ /* Compare two lock tokens.
+ *
+ * Result < 0 => lt1 < lt2
+ * Result == 0 => lt1 == lt2
+ * Result > 0 => lt1 > lt2
+ */
+ int (*compare_locktoken)(
+ const dav_locktoken *lt1,
+ const dav_locktoken *lt2
+ );
+
+ /* Open the provider's lock database.
+ *
+ * The provider may or may not use a "real" database for locks
+ * (a lock could be an attribute on a resource, for example).
+ *
+ * The provider may choose to use the value of the DAVLockDB directive
+ * (as returned by dav_get_lockdb_path()) to decide where to place
+ * any storage it may need.
+ *
+ * The request storage pool should be associated with the lockdb,
+ * so it can be used in subsequent operations.
+ *
+ * If ro != 0, only readonly operations will be performed.
+ * If force == 0, the open can be "lazy"; no subsequent locking operations
+ * may occur.
+ * If force != 0, locking operations will definitely occur.
+ */
+ dav_error * (*open_lockdb)(
+ request_rec *r,
+ int ro,
+ int force,
+ dav_lockdb **lockdb
+ );
+
+ /* Indicates completion of locking operations */
+ void (*close_lockdb)(
+ dav_lockdb *lockdb
+ );
+
+ /* Take a resource out of the lock-null state. */
+ dav_error * (*remove_locknull_state)(
+ dav_lockdb *lockdb,
+ const dav_resource *resource
+ );
+
+ /*
+ ** Create a (direct) lock structure for the given resource. A locktoken
+ ** will be created.
+ **
+ ** The lock provider may store private information into lock->info.
+ */
+ dav_error * (*create_lock)(dav_lockdb *lockdb,
+ const dav_resource *resource,
+ dav_lock **lock);
+
+ /*
+ ** Get the locks associated with the specified resource.
+ **
+ ** If resolve_locks is true (non-zero), then any indirect locks are
+ ** resolved to their actual, direct lock (i.e. the reference to followed
+ ** to the original lock).
+ **
+ ** The locks, if any, are returned as a linked list in no particular
+ ** order. If no locks are present, then *locks will be NULL.
+ */
+ dav_error * (*get_locks)(dav_lockdb *lockdb,
+ const dav_resource *resource,
+ int calltype,
+ dav_lock **locks);
+
+#define DAV_GETLOCKS_RESOLVED 0 /* resolve indirects to directs */
+#define DAV_GETLOCKS_PARTIAL 1 /* leave indirects partially filled */
+#define DAV_GETLOCKS_COMPLETE 2 /* fill out indirect locks */
+
+ /*
+ ** Find a particular lock on a resource (specified by its locktoken).
+ **
+ ** *lock will be set to NULL if the lock is not found.
+ **
+ ** Note that the provider can optimize the unmarshalling -- only one
+ ** lock (or none) must be constructed and returned.
+ **
+ ** If partial_ok is true (non-zero), then an indirect lock can be
+ ** partially filled in. Otherwise, another lookup is done and the
+ ** lock structure will be filled out as a DAV_LOCKREC_INDIRECT.
+ */
+ dav_error * (*find_lock)(dav_lockdb *lockdb,
+ const dav_resource *resource,
+ const dav_locktoken *locktoken,
+ int partial_ok,
+ dav_lock **lock);
+
+ /*
+ ** Quick test to see if the resource has *any* locks on it.
+ **
+ ** This is typically used to determine if a non-existent resource
+ ** has a lock and is (therefore) a locknull resource.
+ **
+ ** WARNING: this function may return TRUE even when timed-out locks
+ ** exist (i.e. it may not perform timeout checks).
+ */
+ dav_error * (*has_locks)(dav_lockdb *lockdb,
+ const dav_resource *resource,
+ int *locks_present);
+
+ /*
+ ** Append the specified lock(s) to the set of locks on this resource.
+ **
+ ** If "make_indirect" is true (non-zero), then the specified lock(s)
+ ** should be converted to an indirect lock (if it is a direct lock)
+ ** before appending. Note that the conversion to an indirect lock does
+ ** not alter the passed-in lock -- the change is internal the
+ ** append_locks function.
+ **
+ ** Multiple locks are specified using the lock->next links.
+ */
+ dav_error * (*append_locks)(dav_lockdb *lockdb,
+ const dav_resource *resource,
+ int make_indirect,
+ const dav_lock *lock);
+
+ /*
+ ** Remove any lock that has the specified locktoken.
+ **
+ ** If locktoken == NULL, then ALL locks are removed.
+ */
+ dav_error * (*remove_lock)(dav_lockdb *lockdb,
+ const dav_resource *resource,
+ const dav_locktoken *locktoken);
+
+ /*
+ ** Refresh all locks, found on the specified resource, which has a
+ ** locktoken in the provided list.
+ **
+ ** If the lock is indirect, then the direct lock is referenced and
+ ** refreshed.
+ **
+ ** Each lock that is updated is returned in the <locks> argument.
+ ** Note that the locks will be fully resolved.
+ */
+ dav_error * (*refresh_locks)(dav_lockdb *lockdb,
+ const dav_resource *resource,
+ const dav_locktoken_list *ltl,
+ time_t new_time,
+ dav_lock **locks);
+
+ /*
+ ** Look up the resource associated with a particular locktoken.
+ **
+ ** The search begins at the specified <start_resource> and the lock
+ ** specified by <locktoken>.
+ **
+ ** If the resource/token specifies an indirect lock, then the direct
+ ** lock will be looked up, and THAT resource will be returned. In other
+ ** words, this function always returns the resource where a particular
+ ** lock (token) was asserted.
+ **
+ ** NOTE: this function pointer is allowed to be NULL, indicating that
+ ** the provider does not support this type of functionality. The
+ ** caller should then traverse up the repository hierarchy looking
+ ** for the resource defining a lock with this locktoken.
+ */
+ dav_error * (*lookup_resource)(dav_lockdb *lockdb,
+ const dav_locktoken *locktoken,
+ const dav_resource *start_resource,
+ const dav_resource **resource);
+
+ /*
+ ** If a provider needs a context to associate with this hooks structure,
+ ** then this field may be used. In most cases, it will just be NULL.
+ */
+ void *ctx;
+};
+
+/* what types of resources can be discovered by dav_get_resource_state() */
+#define DAV_RESOURCE_LOCK_NULL 10 /* resource lock-null */
+#define DAV_RESOURCE_NULL 11 /* resource null */
+#define DAV_RESOURCE_EXISTS 12 /* resource exists */
+#define DAV_RESOURCE_ERROR 13 /* an error occurred */
+
+
+/* --------------------------------------------------------------------
+**
+** PROPERTY HANDLING
+*/
+
+typedef struct dav_propdb dav_propdb;
+
+
+DAV_DECLARE(dav_error *) dav_open_propdb(
+ request_rec *r,
+ dav_lockdb *lockdb,
+ const dav_resource *resource,
+ int ro,
+ apr_array_header_t *ns_xlate,
+ dav_propdb **propdb);
+
+DAV_DECLARE(void) dav_close_propdb(dav_propdb *db);
+
+DAV_DECLARE(dav_get_props_result) dav_get_props(
+ dav_propdb *db,
+ apr_xml_doc *doc);
+
+DAV_DECLARE(dav_get_props_result) dav_get_allprops(
+ dav_propdb *db,
+ dav_prop_insert what);
+
+DAV_DECLARE(void) dav_get_liveprop_supported(
+ dav_propdb *propdb,
+ const char *ns_uri,
+ const char *propname,
+ apr_text_header *body);
+
+/*
+** 3-phase property modification.
+**
+** 1) validate props. readable? unlocked? ACLs allow access?
+** 2) execute operation (set/delete)
+** 3) commit or rollback
+**
+** ### eventually, auth must be available. a ref to the request_rec (which
+** ### contains the auth info) should be in the shared context struct.
+**
+** Each function may alter the error values and information contained within
+** the context record. This should be done as an "increasing" level of
+** error, rather than overwriting any previous error.
+**
+** Note that commit() cannot generate errors. It should simply free the
+** rollback information.
+**
+** rollback() may generate additional errors because the rollback operation
+** can sometimes fail(!).
+**
+** The caller should allocate an array of these, one per operation. It should
+** be zero-initialized, then the db, operation, and prop fields should be
+** filled in before calling dav_prop_validate. Note that the set/delete
+** operations are order-dependent. For a given (logical) context, the same
+** pointer must be passed to each phase.
+**
+** error_type is an internal value, but will have the same numeric value
+** for each possible "desc" value. This allows the caller to group the
+** descriptions via the error_type variable, rather than through string
+** comparisons. Note that "status" does not provide enough granularity to
+** differentiate/group the "desc" values.
+**
+** Note that the propdb will maintain some (global) context across all
+** of the property change contexts. This implies that you can have only
+** one open transaction per propdb.
+*/
+typedef struct dav_prop_ctx
+{
+ dav_propdb *propdb;
+
+ apr_xml_elem *prop; /* property to affect */
+
+ int operation;
+#define DAV_PROP_OP_SET 1 /* set a property value */
+#define DAV_PROP_OP_DELETE 2 /* delete a prop value */
+/* ### add a GET? */
+
+ /* private items to the propdb */
+ int is_liveprop;
+ void *liveprop_ctx;
+ struct dav_rollback_item *rollback; /* optional rollback info */
+
+ dav_error *err; /* error (if any) */
+
+ /* private to mod_dav.c */
+ request_rec *r;
+
+} dav_prop_ctx;
+
+DAV_DECLARE_NONSTD(void) dav_prop_validate(dav_prop_ctx *ctx);
+DAV_DECLARE_NONSTD(void) dav_prop_exec(dav_prop_ctx *ctx);
+DAV_DECLARE_NONSTD(void) dav_prop_commit(dav_prop_ctx *ctx);
+DAV_DECLARE_NONSTD(void) dav_prop_rollback(dav_prop_ctx *ctx);
+
+#define DAV_PROP_CTX_HAS_ERR(dpc) ((dpc).err && (dpc).err->status >= 300)
+
+
+/* --------------------------------------------------------------------
+**
+** WALKER STRUCTURE
+*/
+
+enum {
+ DAV_CALLTYPE_MEMBER = 1, /* called for a member resource */
+ DAV_CALLTYPE_COLLECTION, /* called for a collection */
+ DAV_CALLTYPE_LOCKNULL /* called for a locknull resource */
+};
+
+typedef struct
+{
+ /* the client-provided context */
+ void *walk_ctx;
+
+ /* pool to use for allocations in the callback */
+ apr_pool_t *pool;
+
+ /* the current resource */
+ const dav_resource *resource;
+
+ /* OUTPUT: add responses to this */
+ dav_response *response;
+
+} dav_walk_resource;
+
+typedef struct
+{
+ int walk_type;
+#define DAV_WALKTYPE_AUTH 0x0001 /* limit to authorized files */
+#define DAV_WALKTYPE_NORMAL 0x0002 /* walk normal files */
+#define DAV_WALKTYPE_LOCKNULL 0x0004 /* walk locknull resources */
+
+ /* callback function and a client context for the walk */
+ dav_error * (*func)(dav_walk_resource *wres, int calltype);
+ void *walk_ctx;
+
+ /* what pool to use for allocations needed by walk logic */
+ apr_pool_t *pool;
+
+ /* beginning root of the walk */
+ const dav_resource *root;
+
+ /* lock database to enable walking LOCKNULL resources */
+ dav_lockdb *lockdb;
+
+} dav_walk_params;
+
+/* directory tree walking context */
+typedef struct dav_walker_ctx
+{
+ /* input: */
+ dav_walk_params w;
+
+
+ /* ### client data... phasing out this big glom */
+
+ /* this brigade buffers data being sent to r->output_filters */
+ apr_bucket_brigade *bb;
+
+ /* a scratch pool, used to stream responses and iteratively cleared. */
+ apr_pool_t *scratchpool;
+
+ request_rec *r; /* original request */
+
+ /* for PROPFIND operations */
+ apr_xml_doc *doc;
+ int propfind_type;
+#define DAV_PROPFIND_IS_ALLPROP 1
+#define DAV_PROPFIND_IS_PROPNAME 2
+#define DAV_PROPFIND_IS_PROP 3
+
+ apr_text *propstat_404; /* (cached) propstat giving a 404 error */
+
+ const dav_if_header *if_header; /* for validation */
+ const dav_locktoken *locktoken; /* for UNLOCK */
+ const dav_lock *lock; /* for LOCK */
+ int skip_root; /* for dav_inherit_locks() */
+
+ int flags;
+
+ dav_buffer work_buf; /* for dav_validate_request() */
+
+} dav_walker_ctx;
+
+DAV_DECLARE(void) dav_add_response(dav_walk_resource *wres,
+ int status,
+ dav_get_props_result *propstats);
+
+
+/* --------------------------------------------------------------------
+**
+** "STREAM" STRUCTURE
+**
+** mod_dav uses this abstraction for interacting with the repository
+** while fetching/storing resources. mod_dav views resources as a stream
+** of bytes.
+**
+** Note that the structure is opaque -- it is private to the repository
+** that created the stream in the repository's "open" function.
+**
+** ### THIS STUFF IS GOING AWAY ... GET/read requests are handled by
+** ### having the provider jam stuff straight into the filter stack.
+** ### this is only left for handling PUT/write requests.
+*/
+
+typedef struct dav_stream dav_stream;
+
+typedef enum {
+ DAV_MODE_WRITE_TRUNC, /* truncate and open for writing */
+ DAV_MODE_WRITE_SEEKABLE /* open for writing; random access */
+} dav_stream_mode;
+
+
+/* --------------------------------------------------------------------
+**
+** REPOSITORY FUNCTIONS
+*/
+
+/* Repository provider hooks */
+struct dav_hooks_repository
+{
+ /* Flag for whether repository requires special GET handling.
+ * If resources in the repository are not visible in the
+ * filesystem location which URLs map to, then special handling
+ * is required to first fetch a resource from the repository,
+ * respond to the GET request, then free the resource copy.
+ */
+ int handle_get;
+
+ /* Get a resource descriptor for the URI in a request. A descriptor
+ * should always be returned even if the resource does not exist. This
+ * repository has been identified as handling the resource given by
+ * the URI, so an answer must be given. If there is a problem with the
+ * URI or accessing the resource or whatever, then an error should be
+ * returned.
+ *
+ * root_dir:
+ * the root of the directory for which this repository is configured.
+ *
+ * label:
+ * if a Label: header is present (and allowed), this is the label
+ * to use to identify a version resource from the resource's
+ * corresponding version history. Otherwise, it will be NULL.
+ *
+ * use_checked_in:
+ * use the DAV:checked-in property of the resource identified by the
+ * Request-URI to identify and return a version resource
+ *
+ * The provider may associate the request storage pool with the resource
+ * (in the resource->pool field), to use in other operations on that
+ * resource.
+ */
+ dav_error * (*get_resource)(
+ request_rec *r,
+ const char *root_dir,
+ const char *label,
+ int use_checked_in,
+ dav_resource **resource
+ );
+
+ /* Get a resource descriptor for the parent of the given resource.
+ * The resources need not exist. NULL is returned if the resource
+ * is the root collection.
+ *
+ * An error should be returned only if there is a fatal error in
+ * fetching information about the parent resource.
+ */
+ dav_error * (*get_parent_resource)(
+ const dav_resource *resource,
+ dav_resource **parent_resource
+ );
+
+ /* Determine whether two resource descriptors refer to the same resource.
+ *
+ * Result != 0 => the resources are the same.
+ */
+ int (*is_same_resource)(
+ const dav_resource *res1,
+ const dav_resource *res2
+ );
+
+ /* Determine whether one resource is a parent (immediate or otherwise)
+ * of another.
+ *
+ * Result != 0 => res1 is a parent of res2.
+ */
+ int (*is_parent_resource)(
+ const dav_resource *res1,
+ const dav_resource *res2
+ );
+
+ /*
+ ** Open a stream for this resource, using the specified mode. The
+ ** stream will be returned in *stream.
+ */
+ dav_error * (*open_stream)(const dav_resource *resource,
+ dav_stream_mode mode,
+ dav_stream **stream);
+
+ /*
+ ** Close the specified stream.
+ **
+ ** mod_dav will (ideally) make sure to call this. For safety purposes,
+ ** a provider should (ideally) register a cleanup function with the
+ ** request pool to get this closed and cleaned up.
+ **
+ ** Note the possibility of an error from the close -- it is entirely
+ ** feasible that the close does a "commit" of some kind, which can
+ ** produce an error.
+ **
+ ** commit should be TRUE (non-zero) or FALSE (0) if the stream was
+ ** opened for writing. This flag states whether to retain the file
+ ** or not.
+ ** Note: the commit flag is ignored for streams opened for reading.
+ */
+ dav_error * (*close_stream)(dav_stream *stream, int commit);
+
+ /*
+ ** Write data to the stream.
+ **
+ ** All of the bytes must be written, or an error should be returned.
+ */
+ dav_error * (*write_stream)(dav_stream *stream,
+ const void *buf, apr_size_t bufsize);
+
+ /*
+ ** Seek to an absolute position in the stream. This is used to support
+ ** Content-Range in a GET/PUT.
+ **
+ ** NOTE: if this function is NULL (which is allowed), then any
+ ** operations using Content-Range will be refused.
+ */
+ dav_error * (*seek_stream)(dav_stream *stream, apr_off_t abs_position);
+
+ /*
+ ** If a GET is processed using a stream (open_stream, read_stream)
+ ** rather than via a sub-request (on get_pathname), then this function
+ ** is used to provide the repository with a way to set the headers
+ ** in the response.
+ **
+ ** This function may be called without a following deliver(), to
+ ** handle a HEAD request.
+ **
+ ** This may be NULL if handle_get is FALSE.
+ */
+ dav_error * (*set_headers)(request_rec *r,
+ const dav_resource *resource);
+
+ /*
+ ** The provider should deliver the resource into the specified filter.
+ ** Basically, this is the response to the GET method.
+ **
+ ** Note that this is called for all resources, including collections.
+ ** The provider should determine what has content to deliver or not.
+ **
+ ** set_headers will be called prior to this function, allowing the
+ ** provider to set the appropriate response headers.
+ **
+ ** This may be NULL if handle_get is FALSE.
+ ** ### maybe toss handle_get and just use this function as the marker
+ */
+ dav_error * (*deliver)(const dav_resource *resource,
+ ap_filter_t *output);
+
+ /* Create a collection resource. The resource must not already exist.
+ *
+ * Result == NULL if the collection was created successfully. Also, the
+ * resource object is updated to reflect that the resource exists, and
+ * is a collection.
+ */
+ dav_error * (*create_collection)(
+ dav_resource *resource
+ );
+
+ /* Copy one resource to another. The destination may exist, if it is
+ * versioned.
+ * Handles both files and collections. Properties are copied as well.
+ * If the destination exists and is versioned, the provider must update
+ * the destination to have identical content to the source,
+ * recursively for collections.
+ * The depth argument is ignored for a file, and can be either 0 or
+ * DAV_INFINITY for a collection.
+ * If an error occurs in a child resource, then the return value is
+ * non-NULL, and *response is set to a multistatus response.
+ * If the copy is successful, the dst resource object is
+ * updated to reflect that the resource exists.
+ */
+ dav_error * (*copy_resource)(
+ const dav_resource *src,
+ dav_resource *dst,
+ int depth,
+ dav_response **response
+ );
+
+ /* Move one resource to another. The destination must not exist.
+ * Handles both files and collections. Properties are moved as well.
+ * If an error occurs in a child resource, then the return value is
+ * non-NULL, and *response is set to a multistatus response.
+ * If the move is successful, the src and dst resource objects are
+ * updated to reflect that the source no longer exists, and the
+ * destination does.
+ */
+ dav_error * (*move_resource)(
+ dav_resource *src,
+ dav_resource *dst,
+ dav_response **response
+ );
+
+ /* Remove a resource. Handles both files and collections.
+ * Removes any associated properties as well.
+ * If an error occurs in a child resource, then the return value is
+ * non-NULL, and *response is set to a multistatus response.
+ * If the delete is successful, the resource object is updated to
+ * reflect that the resource no longer exists.
+ */
+ dav_error * (*remove_resource)(
+ dav_resource *resource,
+ dav_response **response
+ );
+
+ /* Walk a resource hierarchy.
+ *
+ * Iterates over the resource hierarchy specified by params->root.
+ * Control of the walk and the callback are specified by 'params'.
+ *
+ * An error may be returned. *response will contain multistatus
+ * responses (if any) suitable for the body of the error. It is also
+ * possible to return NULL, yet still have multistatus responses.
+ * In this case, typically the caller should return a 207 (Multistatus)
+ * and the responses (in the body) as the HTTP response.
+ */
+ dav_error * (*walk)(const dav_walk_params *params, int depth,
+ dav_response **response);
+
+ /* Get the entity tag for a resource */
+ const char * (*getetag)(const dav_resource *resource);
+
+ /*
+ ** If a provider needs a context to associate with this hooks structure,
+ ** then this field may be used. In most cases, it will just be NULL.
+ */
+ void *ctx;
+
+ /* Get the request rec for a resource */
+ request_rec * (*get_request_rec)(const dav_resource *resource);
+
+ /* Get the pathname for a resource */
+ const char * (*get_pathname)(const dav_resource *resource);
+};
+
+
+/* --------------------------------------------------------------------
+**
+** VERSIONING FUNCTIONS
+*/
+
+
+/* dav_add_vary_header
+ *
+ * If there were any headers in the request which require a Vary header
+ * in the response, add it.
+ */
+DAV_DECLARE(void) dav_add_vary_header(request_rec *in_req,
+ request_rec *out_req,
+ const dav_resource *resource);
+
+/*
+** Flags specifying auto-versioning behavior, returned by
+** the auto_versionable hook. The value returned depends
+** on both the state of the resource and the value of the
+** DAV:auto-versioning property for the resource.
+**
+** If the resource does not exist (null or lock-null),
+** DAV_AUTO_VERSION_ALWAYS causes creation of a new version-controlled resource
+**
+** If the resource is checked in,
+** DAV_AUTO_VERSION_ALWAYS causes it to be checked out always,
+** DAV_AUTO_VERSION_LOCKED causes it to be checked out only when locked
+**
+** If the resource is checked out,
+** DAV_AUTO_VERSION_ALWAYS causes it to be checked in always,
+** DAV_AUTO_VERSION_LOCKED causes it to be checked in when unlocked
+** (note: a provider should allow auto-checkin only for resources which
+** were automatically checked out)
+**
+** In all cases, DAV_AUTO_VERSION_NEVER results in no auto-versioning behavior.
+*/
+typedef enum {
+ DAV_AUTO_VERSION_NEVER,
+ DAV_AUTO_VERSION_ALWAYS,
+ DAV_AUTO_VERSION_LOCKED
+} dav_auto_version;
+
+/*
+** This structure is used to record what auto-versioning operations
+** were done to make a resource writable, so that they can be undone
+** at the end of a request.
+*/
+typedef struct {
+ int resource_versioned; /* 1 => resource was auto-version-controlled */
+ int resource_checkedout; /* 1 => resource was auto-checked-out */
+ int parent_checkedout; /* 1 => parent was auto-checked-out */
+ dav_resource *parent_resource; /* parent resource, if it was needed */
+} dav_auto_version_info;
+
+/* Ensure that a resource is writable. If there is no versioning
+ * provider, then this is essentially a no-op. Versioning repositories
+ * require explicit resource creation and checkout before they can
+ * be written to. If a new resource is to be created, or an existing
+ * resource deleted, the parent collection must be checked out as well.
+ *
+ * Set the parent_only flag to only make the parent collection writable.
+ * Otherwise, both parent and child are made writable as needed. If the
+ * child does not exist, then a new versioned resource is created and
+ * checked out.
+ *
+ * If auto-versioning is not enabled for a versioned resource, then an error is
+ * returned, since the resource cannot be modified.
+ *
+ * The dav_auto_version_info structure is filled in with enough information
+ * to restore both parent and child resources to the state they were in
+ * before the auto-versioning operations occurred.
+ */
+DAV_DECLARE(dav_error *) dav_auto_checkout(
+ request_rec *r,
+ dav_resource *resource,
+ int parent_only,
+ dav_auto_version_info *av_info);
+
+/* Revert the writability of resources back to what they were
+ * before they were modified. If undo == 0, then the resource
+ * modifications are maintained (i.e. they are checked in).
+ * If undo != 0, then resource modifications are discarded
+ * (i.e. they are unchecked out).
+ *
+ * Set the unlock flag to indicate that the resource is about
+ * to be unlocked; it will be checked in if the resource
+ * auto-versioning property indicates it should be. In this case,
+ * av_info is ignored, so it can be NULL.
+ *
+ * The resource argument may be NULL if only the parent resource
+ * was checked out (i.e. the parent_only was != 0 in the
+ * dav_auto_checkout call).
+ */
+DAV_DECLARE(dav_error *) dav_auto_checkin(
+ request_rec *r,
+ dav_resource *resource,
+ int undo,
+ int unlock,
+ dav_auto_version_info *av_info);
+
+/*
+** This structure is used to describe available reports
+**
+** "nmspace" should be valid XML and URL-quoted. mod_dav will place
+** double-quotes around it and use it in an xmlns declaration.
+*/
+typedef struct {
+ const char *nmspace; /* namespace of the XML report element */
+ const char *name; /* element name for the XML report */
+} dav_report_elem;
+
+
+/* Versioning provider hooks */
+struct dav_hooks_vsn
+{
+ /*
+ ** MANDATORY HOOKS
+ ** The following hooks are mandatory for all versioning providers;
+ ** they define the functionality needed to implement "core" versioning.
+ */
+
+ /* Return supported versioning options.
+ * Each dav_text item in the list will be returned as a separate
+ * DAV header. Providers are advised to limit the length of an
+ * individual text item to 63 characters, to conform to the limit
+ * used by MS Web Folders.
+ */
+ void (*get_vsn_options)(apr_pool_t *p, apr_text_header *phdr);
+
+ /* Get the value of a specific option for an OPTIONS request.
+ * The option being requested is given by the parsed XML
+ * element object "elem". The value of the option should be
+ * appended to the "option" text object.
+ */
+ dav_error * (*get_option)(const dav_resource *resource,
+ const apr_xml_elem *elem,
+ apr_text_header *option);
+
+ /* Determine whether a non-versioned (or non-existent) resource
+ * is versionable. Returns != 0 if resource can be versioned.
+ */
+ int (*versionable)(const dav_resource *resource);
+
+ /* Determine whether auto-versioning is enabled for a resource
+ * (which may not exist, or may not be versioned). If the resource
+ * is a checked-out resource, the provider must only enable
+ * auto-checkin if the resource was automatically checked out.
+ *
+ * The value returned depends on both the state of the resource
+ * and the value of its DAV:auto-version property. See the description
+ * of the dav_auto_version enumeration above for the details.
+ */
+ dav_auto_version (*auto_versionable)(const dav_resource *resource);
+
+ /* Put a resource under version control. If the resource already
+ * exists unversioned, then it becomes the initial version of the
+ * new version history, and it is replaced by a version selector
+ * which targets the new version.
+ *
+ * If the resource does not exist, then a new version-controlled
+ * resource is created which either targets an existing version (if the
+ * "target" argument is not NULL), or the initial, empty version
+ * in a new history resource (if the "target" argument is NULL).
+ *
+ * If successful, the resource object state is updated appropriately
+ * (that is, changed to refer to the new version-controlled resource).
+ */
+ dav_error * (*vsn_control)(dav_resource *resource,
+ const char *target);
+
+ /* Checkout a resource. If successful, the resource
+ * object state is updated appropriately.
+ *
+ * The auto_checkout flag will be set if this checkout is being
+ * done automatically, as part of some method which modifies
+ * the resource. The provider must remember that the resource
+ * was automatically checked out, so it can determine whether it
+ * can be automatically checked in. (Auto-checkin should only be
+ * enabled for resources which were automatically checked out.)
+ *
+ * If the working resource has a different URL from the
+ * target resource, a dav_resource descriptor is returned
+ * for the new working resource. Otherwise, the original
+ * resource descriptor will refer to the working resource.
+ * The working_resource argument can be NULL if the caller
+ * is not interested in the working resource.
+ *
+ * If the client has specified DAV:unreserved or DAV:fork-ok in the
+ * checkout request, then the corresponding flags are set. If
+ * DAV:activity-set has been specified, then create_activity is set
+ * if DAV:new was specified; otherwise, the DAV:href elements' CDATA
+ * (the actual href text) is passed in the "activities" array (each
+ * element of the array is a const char *). activities will be NULL
+ * no DAV:activity-set was provided or when create_activity is set.
+ */
+ dav_error * (*checkout)(dav_resource *resource,
+ int auto_checkout,
+ int is_unreserved, int is_fork_ok,
+ int create_activity,
+ apr_array_header_t *activities,
+ dav_resource **working_resource);
+
+ /* Uncheckout a checked-out resource. If successful, the resource
+ * object state is updated appropriately.
+ */
+ dav_error * (*uncheckout)(dav_resource *resource);
+
+ /* Checkin a checked-out resource. If successful, the resource
+ * object state is updated appropriately, and the
+ * version_resource descriptor will refer to the new version.
+ * The version_resource argument can be NULL if the caller
+ * is not interested in the new version resource.
+ *
+ * If the client has specified DAV:keep-checked-out in the checkin
+ * request, then the keep_checked_out flag is set. The provider
+ * should create a new version, but keep the resource in the
+ * checked-out state.
+ */
+ dav_error * (*checkin)(dav_resource *resource,
+ int keep_checked_out,
+ dav_resource **version_resource);
+
+ /*
+ ** Return the set of reports available at this resource.
+ **
+ ** An array of report elements should be returned, with an end-marker
+ ** element containing namespace==NULL. The value of the
+ ** DAV:supported-report-set property will be constructed and
+ ** returned.
+ */
+ dav_error * (*avail_reports)(const dav_resource *resource,
+ const dav_report_elem **reports);
+
+ /*
+ ** Determine whether a Label header can be used
+ ** with a particular report. The dav_xml_doc structure
+ ** contains the parsed report request body.
+ ** Returns 0 if the Label header is not allowed.
+ */
+ int (*report_label_header_allowed)(const apr_xml_doc *doc);
+
+ /*
+ ** Generate a report on a resource. Since a provider is free
+ ** to define its own reports, and the value of request headers
+ ** may affect the interpretation of a report, the request record
+ ** must be passed to this routine.
+ **
+ ** The dav_xml_doc structure contains the parsed report request
+ ** body. The report response should be generated into the specified
+ ** output filter.
+ **
+ ** If an error occurs, and a response has not yet been generated,
+ ** then an error can be returned from this function. mod_dav will
+ ** construct an appropriate error response. Once some output has
+ ** been placed into the filter, however, the provider should not
+ ** return an error -- there is no way that mod_dav can deliver it
+ ** properly.
+ **
+ ** ### maybe we need a way to signal an error anyways, and then
+ ** ### apache can abort the connection?
+ */
+ dav_error * (*deliver_report)(request_rec *r,
+ const dav_resource *resource,
+ const apr_xml_doc *doc,
+ ap_filter_t *output);
+
+ /*
+ ** OPTIONAL HOOKS
+ ** The following hooks are optional; if not defined, then the
+ ** corresponding protocol methods will be unsupported.
+ */
+
+ /*
+ ** Set the state of a checked-in version-controlled resource.
+ **
+ ** If the request specified a version, the version resource
+ ** represents that version. If the request specified a label,
+ ** then "version" is NULL, and "label" is the label.
+ **
+ ** The depth argument is ignored for a file, and can be 0, 1, or
+ ** DAV_INFINITY for a collection. The depth argument only applies
+ ** with a label, not a version.
+ **
+ ** If an error occurs in a child resource, then the return value is
+ ** non-NULL, and *response is set to a multistatus response.
+ **
+ ** This hook is optional; if not defined, then the UPDATE method
+ ** will not be supported.
+ */
+ dav_error * (*update)(const dav_resource *resource,
+ const dav_resource *version,
+ const char *label,
+ int depth,
+ dav_response **response);
+
+ /*
+ ** Add a label to a version. The resource is either a specific
+ ** version, or a version selector, in which case the label should
+ ** be added to the current target of the version selector. The
+ ** version selector cannot be checked out.
+ **
+ ** If replace != 0, any existing label by the same name is
+ ** effectively deleted first. Otherwise, it is an error to
+ ** attempt to add a label which already exists on some version
+ ** of the same history resource.
+ **
+ ** This hook is optional; if not defined, then the LABEL method
+ ** will not be supported. If it is defined, then the remove_label
+ ** hook must be defined also.
+ */
+ dav_error * (*add_label)(const dav_resource *resource,
+ const char *label,
+ int replace);
+
+ /*
+ ** Remove a label from a version. The resource is either a specific
+ ** version, or a version selector, in which case the label should
+ ** be added to the current target of the version selector. The
+ ** version selector cannot be checked out.
+ **
+ ** It is an error if no such label exists on the specified version.
+ **
+ ** This hook is optional, but if defined, the add_label hook
+ ** must be defined also.
+ */
+ dav_error * (*remove_label)(const dav_resource *resource,
+ const char *label);
+
+ /*
+ ** Determine whether a null resource can be created as a workspace.
+ ** The provider may restrict workspaces to certain locations.
+ ** Returns 0 if the resource cannot be a workspace.
+ **
+ ** This hook is optional; if the provider does not support workspaces,
+ ** it should be set to NULL.
+ */
+ int (*can_be_workspace)(const dav_resource *resource);
+
+ /*
+ ** Create a workspace resource. The resource must not already
+ ** exist. Any <DAV:mkworkspace> element is passed to the provider
+ ** in the "doc" structure; it may be empty.
+ **
+ ** If workspace creation is successful, the state of the resource
+ ** object is updated appropriately.
+ **
+ ** This hook is optional; if the provider does not support workspaces,
+ ** it should be set to NULL.
+ */
+ dav_error * (*make_workspace)(dav_resource *resource,
+ apr_xml_doc *doc);
+
+ /*
+ ** Determine whether a null resource can be created as an activity.
+ ** The provider may restrict activities to certain locations.
+ ** Returns 0 if the resource cannot be an activity.
+ **
+ ** This hook is optional; if the provider does not support activities,
+ ** it should be set to NULL.
+ */
+ int (*can_be_activity)(const dav_resource *resource);
+
+ /*
+ ** Create an activity resource. The resource must not already
+ ** exist.
+ **
+ ** If activity creation is successful, the state of the resource
+ ** object is updated appropriately.
+ **
+ ** This hook is optional; if the provider does not support activities,
+ ** it should be set to NULL.
+ */
+ dav_error * (*make_activity)(dav_resource *resource);
+
+ /*
+ ** Merge a resource (tree) into target resource (tree).
+ **
+ ** ### more doc...
+ **
+ ** This hook is optional; if the provider does not support merging,
+ ** then this should be set to NULL.
+ */
+ dav_error * (*merge)(dav_resource *target, dav_resource *source,
+ int no_auto_merge, int no_checkout,
+ apr_xml_elem *prop_elem,
+ ap_filter_t *output);
+
+ /*
+ ** If a provider needs a context to associate with this hooks structure,
+ ** then this field may be used. In most cases, it will just be NULL.
+ */
+ void *ctx;
+};
+
+
+/* --------------------------------------------------------------------
+**
+** BINDING FUNCTIONS
+*/
+
+/* binding provider hooks */
+struct dav_hooks_binding {
+
+ /* Determine whether a resource can be the target of a binding.
+ * Returns 0 if the resource cannot be a binding target.
+ */
+ int (*is_bindable)(const dav_resource *resource);
+
+ /* Create a binding to a resource.
+ * The resource argument is the target of the binding;
+ * the binding argument must be a resource which does not already
+ * exist.
+ */
+ dav_error * (*bind_resource)(const dav_resource *resource,
+ dav_resource *binding);
+
+ /*
+ ** If a provider needs a context to associate with this hooks structure,
+ ** then this field may be used. In most cases, it will just be NULL.
+ */
+ void *ctx;
+
+};
+
+
+/* --------------------------------------------------------------------
+**
+** SEARCH(DASL) FUNCTIONS
+*/
+
+/* search provider hooks */
+struct dav_hooks_search {
+ /* Set header for a OPTION method
+ * An error may be returned.
+ * To set a hadder, this function might call
+ * apr_table_setn(r->headers_out, "DASL", dasl_optin1);
+ *
+ * Examples:
+ * DASL: <DAV:basicsearch>
+ * DASL: <http://foo.bar.com/syntax1>
+ * DASL: <http://akuma.com/syntax2>
+ */
+ dav_error * (*set_option_head)(request_rec *r);
+
+ /* Search resources
+ * An error may be returned. *response will contain multistatus
+ * responses (if any) suitable for the body of the error. It is also
+ * possible to return NULL, yet still have multistatus responses.
+ * In this case, typically the caller should return a 207 (Multistatus)
+ * and the responses (in the body) as the HTTP response.
+ */
+ dav_error * (*search_resource)(request_rec *r,
+ dav_response **response);
+
+ /*
+ ** If a provider needs a context to associate with this hooks structure,
+ ** then this field may be used. In most cases, it will just be NULL.
+ */
+ void *ctx;
+
+};
+
+
+/* --------------------------------------------------------------------
+**
+** MISCELLANEOUS STUFF
+*/
+
+typedef struct {
+ int propid; /* live property ID */
+ const dav_hooks_liveprop *provider; /* the provider defining this prop */
+} dav_elem_private;
+
+/* --------------------------------------------------------------------
+**
+** DAV OPTIONS
+*/
+#define DAV_OPTIONS_EXTENSION_GROUP "dav_options"
+
+typedef struct dav_options_provider
+{
+ dav_error* (*dav_header)(request_rec *r,
+ const dav_resource *resource,
+ apr_text_header *phdr);
+
+ dav_error* (*dav_method)(request_rec *r,
+ const dav_resource *resource,
+ apr_text_header *phdr);
+
+ void *ctx;
+} dav_options_provider;
+
+extern DAV_DECLARE(const dav_options_provider *) dav_get_options_providers(const char *name);
+
+extern DAV_DECLARE(void) dav_options_provider_register(apr_pool_t *p,
+ const char *name,
+ const dav_options_provider *provider);
+
+/* --------------------------------------------------------------------
+**
+** DAV RESOURCE TYPE HOOKS
+*/
+
+typedef struct dav_resource_type_provider
+{
+ int (*get_resource_type)(const dav_resource *resource,
+ const char **name,
+ const char **uri);
+} dav_resource_type_provider;
+
+#define DAV_RESOURCE_TYPE_GROUP "dav_resource_type"
+
+DAV_DECLARE(void) dav_resource_type_provider_register(apr_pool_t *p,
+ const char *name,
+ const dav_resource_type_provider *provider);
+
+DAV_DECLARE(const dav_resource_type_provider *) dav_get_resource_type_providers(const char *name);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _MOD_DAV_H_ */
+/** @} */
+
diff --git a/modules/dav/main/mod_dav.mak b/modules/dav/main/mod_dav.mak
new file mode 100644
index 0000000..a107e22
--- /dev/null
+++ b/modules/dav/main/mod_dav.mak
@@ -0,0 +1,406 @@
+# Microsoft Developer Studio Generated NMAKE File, Based on mod_dav.dsp
+!IF "$(CFG)" == ""
+CFG=mod_dav - Win32 Release
+!MESSAGE No configuration specified. Defaulting to mod_dav - Win32 Release.
+!ENDIF
+
+!IF "$(CFG)" != "mod_dav - Win32 Release" && "$(CFG)" != "mod_dav - Win32 Debug"
+!MESSAGE Invalid configuration "$(CFG)" specified.
+!MESSAGE You can specify a configuration when running NMAKE
+!MESSAGE by defining the macro CFG on the command line. For example:
+!MESSAGE
+!MESSAGE NMAKE /f "mod_dav.mak" CFG="mod_dav - Win32 Release"
+!MESSAGE
+!MESSAGE Possible choices for configuration are:
+!MESSAGE
+!MESSAGE "mod_dav - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE "mod_dav - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE
+!ERROR An invalid configuration is specified.
+!ENDIF
+
+!IF "$(OS)" == "Windows_NT"
+NULL=
+!ELSE
+NULL=nul
+!ENDIF
+
+!IF "$(CFG)" == "mod_dav - Win32 Release"
+
+OUTDIR=.\Release
+INTDIR=.\Release
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+# Begin Custom Macros
+OutDir=.\Release
+# End Custom Macros
+
+!IF "$(RECURSE)" == "0"
+
+ALL : "$(OUTDIR)\mod_dav.so" "$(DS_POSTBUILD_DEP)"
+
+!ELSE
+
+ALL : "libhttpd - Win32 Release" "libaprutil - Win32 Release" "libapr - Win32 Release" "$(OUTDIR)\mod_dav.so" "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+!IF "$(RECURSE)" == "1"
+CLEAN :"libapr - Win32 ReleaseCLEAN" "libaprutil - Win32 ReleaseCLEAN" "libhttpd - Win32 ReleaseCLEAN"
+!ELSE
+CLEAN :
+!ENDIF
+ -@erase "$(INTDIR)\liveprop.obj"
+ -@erase "$(INTDIR)\mod_dav.obj"
+ -@erase "$(INTDIR)\mod_dav.res"
+ -@erase "$(INTDIR)\mod_dav_src.idb"
+ -@erase "$(INTDIR)\mod_dav_src.pdb"
+ -@erase "$(INTDIR)\props.obj"
+ -@erase "$(INTDIR)\providers.obj"
+ -@erase "$(INTDIR)\std_liveprop.obj"
+ -@erase "$(INTDIR)\util.obj"
+ -@erase "$(INTDIR)\util_lock.obj"
+ -@erase "$(OUTDIR)\mod_dav.exp"
+ -@erase "$(OUTDIR)\mod_dav.lib"
+ -@erase "$(OUTDIR)\mod_dav.pdb"
+ -@erase "$(OUTDIR)\mod_dav.so"
+
+"$(OUTDIR)" :
+ if not exist "$(OUTDIR)/$(NULL)" mkdir "$(OUTDIR)"
+
+CPP=cl.exe
+CPP_PROJ=/nologo /MD /W3 /Zi /O2 /Oy- /I "../../../include" /I "../../../srclib/apr/include" /I "../../../srclib/apr-util/include" /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /D "DAV_DECLARE_EXPORT" /Fo"$(INTDIR)\\" /Fd"$(INTDIR)\mod_dav_src" /FD /c
+
+.c{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.c{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+MTL=midl.exe
+MTL_PROJ=/nologo /D "NDEBUG" /mktyplib203 /win32
+RSC=rc.exe
+RSC_PROJ=/l 0x409 /fo"$(INTDIR)\mod_dav.res" /i "../../../include" /i "../../../srclib/apr/include" /d "NDEBUG" /d BIN_NAME="mod_dav.so" /d LONG_NAME="dav_module for Apache"
+BSC32=bscmake.exe
+BSC32_FLAGS=/nologo /o"$(OUTDIR)\mod_dav.bsc"
+BSC32_SBRS= \
+
+LINK32=link.exe
+LINK32_FLAGS=kernel32.lib ws2_32.lib mswsock.lib /nologo /subsystem:windows /dll /incremental:no /pdb:"$(OUTDIR)\mod_dav.pdb" /debug /out:"$(OUTDIR)\mod_dav.so" /implib:"$(OUTDIR)\mod_dav.lib" /base:@..\..\..\os\win32\BaseAddr.ref,mod_dav.so /opt:ref
+LINK32_OBJS= \
+ "$(INTDIR)\liveprop.obj" \
+ "$(INTDIR)\mod_dav.obj" \
+ "$(INTDIR)\props.obj" \
+ "$(INTDIR)\providers.obj" \
+ "$(INTDIR)\std_liveprop.obj" \
+ "$(INTDIR)\util.obj" \
+ "$(INTDIR)\util_lock.obj" \
+ "$(INTDIR)\mod_dav.res" \
+ "..\..\..\srclib\apr\Release\libapr-1.lib" \
+ "..\..\..\srclib\apr-util\Release\libaprutil-1.lib" \
+ "..\..\..\Release\libhttpd.lib"
+
+"$(OUTDIR)\mod_dav.so" : "$(OUTDIR)" $(DEF_FILE) $(LINK32_OBJS)
+ $(LINK32) @<<
+ $(LINK32_FLAGS) $(LINK32_OBJS)
+<<
+
+TargetPath=.\Release\mod_dav.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+
+# Begin Custom Macros
+OutDir=.\Release
+# End Custom Macros
+
+"$(DS_POSTBUILD_DEP)" : "$(OUTDIR)\mod_dav.so"
+ if exist .\Release\mod_dav.so.manifest mt.exe -manifest .\Release\mod_dav.so.manifest -outputresource:.\Release\mod_dav.so;2
+ echo Helper for Post-build step > "$(DS_POSTBUILD_DEP)"
+
+!ELSEIF "$(CFG)" == "mod_dav - Win32 Debug"
+
+OUTDIR=.\Debug
+INTDIR=.\Debug
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+# Begin Custom Macros
+OutDir=.\Debug
+# End Custom Macros
+
+!IF "$(RECURSE)" == "0"
+
+ALL : "$(OUTDIR)\mod_dav.so" "$(DS_POSTBUILD_DEP)"
+
+!ELSE
+
+ALL : "libhttpd - Win32 Debug" "libaprutil - Win32 Debug" "libapr - Win32 Debug" "$(OUTDIR)\mod_dav.so" "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+!IF "$(RECURSE)" == "1"
+CLEAN :"libapr - Win32 DebugCLEAN" "libaprutil - Win32 DebugCLEAN" "libhttpd - Win32 DebugCLEAN"
+!ELSE
+CLEAN :
+!ENDIF
+ -@erase "$(INTDIR)\liveprop.obj"
+ -@erase "$(INTDIR)\mod_dav.obj"
+ -@erase "$(INTDIR)\mod_dav.res"
+ -@erase "$(INTDIR)\mod_dav_src.idb"
+ -@erase "$(INTDIR)\mod_dav_src.pdb"
+ -@erase "$(INTDIR)\props.obj"
+ -@erase "$(INTDIR)\providers.obj"
+ -@erase "$(INTDIR)\std_liveprop.obj"
+ -@erase "$(INTDIR)\util.obj"
+ -@erase "$(INTDIR)\util_lock.obj"
+ -@erase "$(OUTDIR)\mod_dav.exp"
+ -@erase "$(OUTDIR)\mod_dav.lib"
+ -@erase "$(OUTDIR)\mod_dav.pdb"
+ -@erase "$(OUTDIR)\mod_dav.so"
+
+"$(OUTDIR)" :
+ if not exist "$(OUTDIR)/$(NULL)" mkdir "$(OUTDIR)"
+
+CPP=cl.exe
+CPP_PROJ=/nologo /MDd /W3 /Zi /Od /I "../../../include" /I "../../../srclib/apr/include" /I "../../../srclib/apr-util/include" /D "_DEBUG" /D "WIN32" /D "_WINDOWS" /D "DAV_DECLARE_EXPORT" /Fo"$(INTDIR)\\" /Fd"$(INTDIR)\mod_dav_src" /FD /EHsc /c
+
+.c{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.c{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+MTL=midl.exe
+MTL_PROJ=/nologo /D "_DEBUG" /mktyplib203 /win32
+RSC=rc.exe
+RSC_PROJ=/l 0x409 /fo"$(INTDIR)\mod_dav.res" /i "../../../include" /i "../../../srclib/apr/include" /d "_DEBUG" /d BIN_NAME="mod_dav.so" /d LONG_NAME="dav_module for Apache"
+BSC32=bscmake.exe
+BSC32_FLAGS=/nologo /o"$(OUTDIR)\mod_dav.bsc"
+BSC32_SBRS= \
+
+LINK32=link.exe
+LINK32_FLAGS=kernel32.lib ws2_32.lib mswsock.lib /nologo /subsystem:windows /dll /incremental:no /pdb:"$(OUTDIR)\mod_dav.pdb" /debug /out:"$(OUTDIR)\mod_dav.so" /implib:"$(OUTDIR)\mod_dav.lib" /base:@..\..\..\os\win32\BaseAddr.ref,mod_dav.so
+LINK32_OBJS= \
+ "$(INTDIR)\liveprop.obj" \
+ "$(INTDIR)\mod_dav.obj" \
+ "$(INTDIR)\props.obj" \
+ "$(INTDIR)\providers.obj" \
+ "$(INTDIR)\std_liveprop.obj" \
+ "$(INTDIR)\util.obj" \
+ "$(INTDIR)\util_lock.obj" \
+ "$(INTDIR)\mod_dav.res" \
+ "..\..\..\srclib\apr\Debug\libapr-1.lib" \
+ "..\..\..\srclib\apr-util\Debug\libaprutil-1.lib" \
+ "..\..\..\Debug\libhttpd.lib"
+
+"$(OUTDIR)\mod_dav.so" : "$(OUTDIR)" $(DEF_FILE) $(LINK32_OBJS)
+ $(LINK32) @<<
+ $(LINK32_FLAGS) $(LINK32_OBJS)
+<<
+
+TargetPath=.\Debug\mod_dav.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+
+# Begin Custom Macros
+OutDir=.\Debug
+# End Custom Macros
+
+"$(DS_POSTBUILD_DEP)" : "$(OUTDIR)\mod_dav.so"
+ if exist .\Debug\mod_dav.so.manifest mt.exe -manifest .\Debug\mod_dav.so.manifest -outputresource:.\Debug\mod_dav.so;2
+ echo Helper for Post-build step > "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+
+!IF "$(NO_EXTERNAL_DEPS)" != "1"
+!IF EXISTS("mod_dav.dep")
+!INCLUDE "mod_dav.dep"
+!ELSE
+!MESSAGE Warning: cannot find "mod_dav.dep"
+!ENDIF
+!ENDIF
+
+
+!IF "$(CFG)" == "mod_dav - Win32 Release" || "$(CFG)" == "mod_dav - Win32 Debug"
+SOURCE=.\liveprop.c
+
+"$(INTDIR)\liveprop.obj" : $(SOURCE) "$(INTDIR)"
+
+
+SOURCE=.\mod_dav.c
+
+"$(INTDIR)\mod_dav.obj" : $(SOURCE) "$(INTDIR)"
+
+
+SOURCE=.\props.c
+
+"$(INTDIR)\props.obj" : $(SOURCE) "$(INTDIR)"
+
+
+SOURCE=.\providers.c
+
+"$(INTDIR)\providers.obj" : $(SOURCE) "$(INTDIR)"
+
+
+SOURCE=.\std_liveprop.c
+
+"$(INTDIR)\std_liveprop.obj" : $(SOURCE) "$(INTDIR)"
+
+
+SOURCE=.\util.c
+
+"$(INTDIR)\util.obj" : $(SOURCE) "$(INTDIR)"
+
+
+SOURCE=.\util_lock.c
+
+"$(INTDIR)\util_lock.obj" : $(SOURCE) "$(INTDIR)"
+
+
+!IF "$(CFG)" == "mod_dav - Win32 Release"
+
+"libapr - Win32 Release" :
+ cd ".\..\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Release"
+ cd "..\..\modules\dav\main"
+
+"libapr - Win32 ReleaseCLEAN" :
+ cd ".\..\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Release" RECURSE=1 CLEAN
+ cd "..\..\modules\dav\main"
+
+!ELSEIF "$(CFG)" == "mod_dav - Win32 Debug"
+
+"libapr - Win32 Debug" :
+ cd ".\..\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Debug"
+ cd "..\..\modules\dav\main"
+
+"libapr - Win32 DebugCLEAN" :
+ cd ".\..\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Debug" RECURSE=1 CLEAN
+ cd "..\..\modules\dav\main"
+
+!ENDIF
+
+!IF "$(CFG)" == "mod_dav - Win32 Release"
+
+"libaprutil - Win32 Release" :
+ cd ".\..\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Release"
+ cd "..\..\modules\dav\main"
+
+"libaprutil - Win32 ReleaseCLEAN" :
+ cd ".\..\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Release" RECURSE=1 CLEAN
+ cd "..\..\modules\dav\main"
+
+!ELSEIF "$(CFG)" == "mod_dav - Win32 Debug"
+
+"libaprutil - Win32 Debug" :
+ cd ".\..\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Debug"
+ cd "..\..\modules\dav\main"
+
+"libaprutil - Win32 DebugCLEAN" :
+ cd ".\..\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Debug" RECURSE=1 CLEAN
+ cd "..\..\modules\dav\main"
+
+!ENDIF
+
+!IF "$(CFG)" == "mod_dav - Win32 Release"
+
+"libhttpd - Win32 Release" :
+ cd ".\..\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Release"
+ cd ".\modules\dav\main"
+
+"libhttpd - Win32 ReleaseCLEAN" :
+ cd ".\..\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Release" RECURSE=1 CLEAN
+ cd ".\modules\dav\main"
+
+!ELSEIF "$(CFG)" == "mod_dav - Win32 Debug"
+
+"libhttpd - Win32 Debug" :
+ cd ".\..\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Debug"
+ cd ".\modules\dav\main"
+
+"libhttpd - Win32 DebugCLEAN" :
+ cd ".\..\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Debug" RECURSE=1 CLEAN
+ cd ".\modules\dav\main"
+
+!ENDIF
+
+SOURCE=..\..\..\build\win32\httpd.rc
+
+!IF "$(CFG)" == "mod_dav - Win32 Release"
+
+
+"$(INTDIR)\mod_dav.res" : $(SOURCE) "$(INTDIR)"
+ $(RSC) /l 0x409 /fo"$(INTDIR)\mod_dav.res" /i "../../../include" /i "../../../srclib/apr/include" /i "../../../build\win32" /d "NDEBUG" /d BIN_NAME="mod_dav.so" /d LONG_NAME="dav_module for Apache" $(SOURCE)
+
+
+!ELSEIF "$(CFG)" == "mod_dav - Win32 Debug"
+
+
+"$(INTDIR)\mod_dav.res" : $(SOURCE) "$(INTDIR)"
+ $(RSC) /l 0x409 /fo"$(INTDIR)\mod_dav.res" /i "../../../include" /i "../../../srclib/apr/include" /i "../../../build\win32" /d "_DEBUG" /d BIN_NAME="mod_dav.so" /d LONG_NAME="dav_module for Apache" $(SOURCE)
+
+
+!ENDIF
+
+
+!ENDIF
+
diff --git a/modules/dav/main/props.c b/modules/dav/main/props.c
new file mode 100644
index 0000000..f64878e
--- /dev/null
+++ b/modules/dav/main/props.c
@@ -0,0 +1,1125 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/*
+** DAV extension module for Apache 2.0.*
+** - Property database handling (repository-independent)
+**
+** NOTES:
+**
+** PROPERTY DATABASE
+**
+** This version assumes that there is a per-resource database provider
+** to record properties. The database provider decides how and where to
+** store these databases.
+**
+** The DBM keys for the properties have the following form:
+**
+** namespace ":" propname
+**
+** For example: 5:author
+**
+** The namespace provides an integer index into the namespace table
+** (see below). propname is simply the property name, without a namespace
+** prefix.
+**
+** A special case exists for properties that had a prefix starting with
+** "xml". The XML Specification reserves these for future use. mod_dav
+** stores and retrieves them unchanged. The keys for these properties
+** have the form:
+**
+** ":" propname
+**
+** The propname will contain the prefix and the property name. For
+** example, a key might be ":xmlfoo:name"
+**
+** The ":name" style will also be used for properties that do not
+** exist within a namespace.
+**
+** The DBM values consist of two null-terminated strings, appended
+** together (the null-terms are retained and stored in the database).
+** The first string is the xml:lang value for the property. An empty
+** string signifies that a lang value was not in context for the value.
+** The second string is the property value itself.
+**
+**
+** NAMESPACE TABLE
+**
+** The namespace table is an array that lists each of the namespaces
+** that are in use by the properties in the given propdb. Each entry
+** in the array is a simple URI.
+**
+** For example: http://www.foo.bar/standards/props/
+**
+** The prefix used for the property is stripped and the URI for it
+** is entered into the namespace table. Also, any namespaces used
+** within the property value will be entered into the table (and
+** stripped from the child elements).
+**
+** The namespaces are stored in the DBM database under the "METADATA" key.
+**
+**
+** STRIPPING NAMESPACES
+**
+** Within the property values, the namespace declarations (xmlns...)
+** are stripped. Each element and attribute will have its prefix removed
+** and a new prefix inserted.
+**
+** This must be done so that we can return multiple properties in a
+** PROPFIND which may have (originally) used conflicting prefixes. For
+** that case, we must bind all property value elements to new namespace
+** values.
+**
+** This implies that clients must NOT be sensitive to the namespace
+** prefix used for their properties. It WILL change when the properties
+** are returned (we return them as "ns<index>", e.g. "ns5"). Also, the
+** property value can contain ONLY XML elements and CDATA. PI and comment
+** elements will be stripped. CDATA whitespace will be preserved, but
+** whitespace within element tags will be altered. Attribute ordering
+** may be altered. Element and CDATA ordering will be preserved.
+**
+**
+** ATTRIBUTES ON PROPERTY NAME ELEMENTS
+**
+** When getting/setting properties, the XML used looks like:
+**
+** <prop>
+** <propname1>value</propname1>
+** <propname2>value</propname1>
+** </prop>
+**
+** This implementation (mod_dav) DOES NOT save any attributes that are
+** associated with the <propname1> element. The property value is deemed
+** to be only the contents ("value" in the above example).
+**
+** We do store the xml:lang value (if any) that applies to the context
+** of the <propname1> element. Whether the xml:lang attribute is on
+** <propname1> itself, or from a higher level element, we will store it
+** with the property value.
+**
+**
+** VERSIONING
+**
+** The DBM db contains a key named "METADATA" that holds database-level
+** information, such as the namespace table. The record also contains the
+** db's version number as the very first 16-bit value. This first number
+** is actually stored as two single bytes: the first byte is a "major"
+** version number. The second byte is a "minor" number.
+**
+** If the major number is not what mod_dav expects, then the db is closed
+** immediately and an error is returned. A minor number change is
+** acceptable -- it is presumed that old/new dav_props.c can deal with
+** the database format. For example, a newer dav_props might update the
+** minor value and append information to the end of the metadata record
+** (which would be ignored by previous versions).
+**
+**
+** ISSUES:
+**
+** At the moment, for the dav_get_allprops() and dav_get_props() functions,
+** we must return a set of xmlns: declarations for ALL known namespaces
+** in the file. There isn't a way to filter this because we don't know
+** which are going to be used or not. Examining property names is not
+** sufficient because the property values could use entirely different
+** namespaces.
+**
+** ==> we must devise a scheme where we can "garbage collect" the namespace
+** entries from the property database.
+*/
+
+#include "apr.h"
+#include "apr_strings.h"
+
+#define APR_WANT_STDIO
+#define APR_WANT_BYTEFUNC
+#include "apr_want.h"
+
+#include "mod_dav.h"
+
+#include "http_log.h"
+#include "http_request.h"
+
+/*
+** There is some rough support for writable DAV:getcontenttype and
+** DAV:getcontentlanguage properties. If this #define is (1), then
+** this support is disabled.
+**
+** We are disabling it because of a lack of support in GET and PUT
+** operations. For GET, it would be "expensive" to look for a propdb,
+** open it, and attempt to extract the Content-Type and Content-Language
+** values for the response.
+** (Handling the PUT would not be difficult, though)
+*/
+#define DAV_DISABLE_WRITABLE_PROPS 1
+
+#define DAV_EMPTY_VALUE "\0" /* TWO null terms */
+
+struct dav_propdb {
+ apr_pool_t *p; /* the pool we should use */
+ request_rec *r; /* the request record */
+
+ const dav_resource *resource; /* the target resource */
+
+ int deferred; /* open of db has been deferred */
+ dav_db *db; /* underlying database containing props */
+
+ apr_array_header_t *ns_xlate; /* translation of an elem->ns to URI */
+ dav_namespace_map *mapping; /* namespace mapping */
+
+ dav_lockdb *lockdb; /* the lock database */
+
+ dav_buffer wb_lock; /* work buffer for lockdiscovery property */
+
+ /* if we ever run a GET subreq, it will be stored here */
+ request_rec *subreq;
+
+ /* hooks we should use for processing (based on the target resource) */
+ const dav_hooks_db *db_hooks;
+};
+
+/* NOTE: dav_core_props[] and the following enum must stay in sync. */
+/* ### move these into a "core" liveprop provider? */
+static const char * const dav_core_props[] =
+{
+ "getcontenttype",
+ "getcontentlanguage",
+ "lockdiscovery",
+ "supportedlock",
+
+ NULL /* sentinel */
+};
+enum {
+ DAV_PROPID_CORE_getcontenttype = DAV_PROPID_CORE,
+ DAV_PROPID_CORE_getcontentlanguage,
+ DAV_PROPID_CORE_lockdiscovery,
+ DAV_PROPID_CORE_supportedlock,
+
+ DAV_PROPID_CORE_UNKNOWN
+};
+
+/*
+** This structure is used to track information needed for a rollback.
+*/
+typedef struct dav_rollback_item {
+ /* select one of the two rollback context structures based on the
+ value of dav_prop_ctx.is_liveprop */
+ dav_deadprop_rollback *deadprop;
+ dav_liveprop_rollback *liveprop;
+
+} dav_rollback_item;
+
+
+static int dav_find_liveprop_provider(dav_propdb *propdb,
+ const char *ns_uri,
+ const char *propname,
+ const dav_hooks_liveprop **provider)
+{
+ int propid;
+
+ *provider = NULL;
+
+ if (ns_uri == NULL) {
+ /* policy: liveprop providers cannot define no-namespace properties */
+ return DAV_PROPID_CORE_UNKNOWN;
+ }
+
+ /* check liveprop providers first, so they can define core properties */
+ propid = dav_run_find_liveprop(propdb->resource, ns_uri, propname,
+ provider);
+ if (propid != 0) {
+ return propid;
+ }
+
+ /* check for core property */
+ if (strcmp(ns_uri, "DAV:") == 0) {
+ const char * const *p = dav_core_props;
+
+ for (propid = DAV_PROPID_CORE; *p != NULL; ++p, ++propid)
+ if (strcmp(propname, *p) == 0) {
+ return propid;
+ }
+ }
+
+ /* no provider for this property */
+ return DAV_PROPID_CORE_UNKNOWN;
+}
+
+static void dav_find_liveprop(dav_propdb *propdb, apr_xml_elem *elem)
+{
+ const char *ns_uri;
+ dav_elem_private *priv = elem->priv;
+ const dav_hooks_liveprop *hooks;
+
+
+ if (elem->ns == APR_XML_NS_NONE)
+ ns_uri = NULL;
+ else if (elem->ns == APR_XML_NS_DAV_ID)
+ ns_uri = "DAV:";
+ else
+ ns_uri = APR_XML_GET_URI_ITEM(propdb->ns_xlate, elem->ns);
+
+ priv->propid = dav_find_liveprop_provider(propdb, ns_uri, elem->name,
+ &hooks);
+
+ /* ### this test seems redundant... */
+ if (priv->propid != DAV_PROPID_CORE_UNKNOWN) {
+ priv->provider = hooks;
+ }
+}
+
+/* is the live property read/write? */
+static int dav_rw_liveprop(dav_propdb *propdb, dav_elem_private *priv)
+{
+ int propid = priv->propid;
+
+ /*
+ ** Check the liveprop provider (if this is a provider-defined prop)
+ */
+ if (priv->provider != NULL) {
+ return (*priv->provider->is_writable)(propdb->resource, propid);
+ }
+
+ /* these are defined as read-only */
+ if (propid == DAV_PROPID_CORE_lockdiscovery
+#if DAV_DISABLE_WRITABLE_PROPS
+ || propid == DAV_PROPID_CORE_getcontenttype
+ || propid == DAV_PROPID_CORE_getcontentlanguage
+#endif
+ || propid == DAV_PROPID_CORE_supportedlock
+ ) {
+
+ return 0;
+ }
+
+ /* these are defined as read/write */
+ if (propid == DAV_PROPID_CORE_getcontenttype
+ || propid == DAV_PROPID_CORE_getcontentlanguage
+ || propid == DAV_PROPID_CORE_UNKNOWN) {
+
+ return 1;
+ }
+
+ /*
+ ** We don't recognize the property, so it must be dead (and writable)
+ */
+ return 1;
+}
+
+/* do a sub-request to fetch properties for the target resource's URI. */
+static void dav_do_prop_subreq(dav_propdb *propdb)
+{
+ /* need to escape the uri that's in the resource struct because during
+ * the property walker it's not encoded. */
+ const char *e_uri = ap_escape_uri(propdb->resource->pool,
+ propdb->resource->uri);
+
+ /* perform a "GET" on the resource's URI (note that the resource
+ may not correspond to the current request!). */
+ propdb->subreq = ap_sub_req_lookup_uri(e_uri, propdb->r, NULL);
+}
+
+static dav_error * dav_insert_coreprop(dav_propdb *propdb,
+ int propid, const char *name,
+ dav_prop_insert what,
+ apr_text_header *phdr,
+ dav_prop_insert *inserted)
+{
+ const char *value = NULL;
+ dav_error *err;
+
+ *inserted = DAV_PROP_INSERT_NOTDEF;
+
+ /* fast-path the common case */
+ if (propid == DAV_PROPID_CORE_UNKNOWN)
+ return NULL;
+
+ switch (propid) {
+
+ case DAV_PROPID_CORE_lockdiscovery:
+ if (propdb->lockdb != NULL) {
+ dav_lock *locks;
+
+ if ((err = dav_lock_query(propdb->lockdb, propdb->resource,
+ &locks)) != NULL) {
+ return dav_push_error(propdb->p, err->status, 0,
+ "DAV:lockdiscovery could not be "
+ "determined due to a problem fetching "
+ "the locks for this resource.",
+ err);
+ }
+
+ /* fast-path the no-locks case */
+ if (locks == NULL) {
+ value = "";
+ }
+ else {
+ /*
+ ** This may modify the buffer. value may point to
+ ** wb_lock.pbuf or a string constant.
+ */
+ value = dav_lock_get_activelock(propdb->r, locks,
+ &propdb->wb_lock);
+
+ /* make a copy to isolate it from changes to wb_lock */
+ value = apr_pstrdup(propdb->p, propdb->wb_lock.buf);
+ }
+ }
+ break;
+
+ case DAV_PROPID_CORE_supportedlock:
+ if (propdb->lockdb != NULL) {
+ value = (*propdb->lockdb->hooks->get_supportedlock)(propdb->resource);
+ }
+ break;
+
+ case DAV_PROPID_CORE_getcontenttype:
+ if (propdb->subreq == NULL) {
+ dav_do_prop_subreq(propdb);
+ }
+ if (propdb->subreq->content_type != NULL) {
+ value = propdb->subreq->content_type;
+ }
+ break;
+
+ case DAV_PROPID_CORE_getcontentlanguage:
+ {
+ const char *lang;
+
+ if (propdb->subreq == NULL) {
+ dav_do_prop_subreq(propdb);
+ }
+ if ((lang = apr_table_get(propdb->subreq->headers_out,
+ "Content-Language")) != NULL) {
+ value = lang;
+ }
+ break;
+ }
+
+ default:
+ /* fall through to interpret as a dead property */
+ break;
+ }
+
+ /* if something was supplied, then insert it */
+ if (value != NULL) {
+ const char *s;
+
+ if (what == DAV_PROP_INSERT_SUPPORTED) {
+ /* use D: prefix to refer to the DAV: namespace URI,
+ * and let the namespace attribute default to "DAV:"
+ */
+ s = apr_psprintf(propdb->p,
+ "<D:supported-live-property D:name=\"%s\"/>" DEBUG_CR,
+ name);
+ }
+ else if (what == DAV_PROP_INSERT_VALUE && *value != '\0') {
+ /* use D: prefix to refer to the DAV: namespace URI */
+ s = apr_psprintf(propdb->p, "<D:%s>%s</D:%s>" DEBUG_CR,
+ name, value, name);
+ }
+ else {
+ /* use D: prefix to refer to the DAV: namespace URI */
+ s = apr_psprintf(propdb->p, "<D:%s/>" DEBUG_CR, name);
+ }
+ apr_text_append(propdb->p, phdr, s);
+
+ *inserted = what;
+ }
+
+ return NULL;
+}
+
+static dav_error * dav_insert_liveprop(dav_propdb *propdb,
+ const apr_xml_elem *elem,
+ dav_prop_insert what,
+ apr_text_header *phdr,
+ dav_prop_insert *inserted)
+{
+ dav_elem_private *priv = elem->priv;
+
+ *inserted = DAV_PROP_INSERT_NOTDEF;
+
+ if (priv->provider == NULL) {
+ /* this is a "core" property that we define */
+ return dav_insert_coreprop(propdb, priv->propid, elem->name,
+ what, phdr, inserted);
+ }
+
+ /* ask the provider (that defined this prop) to insert the prop */
+ *inserted = (*priv->provider->insert_prop)(propdb->resource, priv->propid,
+ what, phdr);
+
+ return NULL;
+}
+
+static void dav_output_prop_name(apr_pool_t *pool,
+ const dav_prop_name *name,
+ dav_xmlns_info *xi,
+ apr_text_header *phdr)
+{
+ const char *s;
+
+ if (*name->ns == '\0')
+ s = apr_psprintf(pool, "<%s/>" DEBUG_CR, name->name);
+ else {
+ const char *prefix = dav_xmlns_add_uri(xi, name->ns);
+
+ s = apr_psprintf(pool, "<%s:%s/>" DEBUG_CR, prefix, name->name);
+ }
+
+ apr_text_append(pool, phdr, s);
+}
+
+static void dav_insert_xmlns(apr_pool_t *p, const char *pre_prefix, long ns,
+ const char *ns_uri, apr_text_header *phdr)
+{
+ const char *s;
+
+ s = apr_psprintf(p, " xmlns:%s%ld=\"%s\"", pre_prefix, ns, ns_uri);
+ apr_text_append(p, phdr, s);
+}
+
+static dav_error *dav_really_open_db(dav_propdb *propdb, int ro)
+{
+ dav_error *err;
+
+ /* we're trying to open the db; turn off the 'deferred' flag */
+ propdb->deferred = 0;
+
+ /* ask the DB provider to open the thing */
+ err = (*propdb->db_hooks->open)(propdb->p, propdb->resource, ro,
+ &propdb->db);
+ if (err != NULL) {
+ return dav_push_error(propdb->p, HTTP_INTERNAL_SERVER_ERROR,
+ DAV_ERR_PROP_OPENING,
+ "Could not open the property database.",
+ err);
+ }
+
+ /*
+ ** NOTE: propdb->db could be NULL if we attempted to open a readonly
+ ** database that doesn't exist. If we require read/write
+ ** access, then a database was created and opened.
+ */
+
+ return NULL;
+}
+
+DAV_DECLARE(dav_error *)dav_open_propdb(request_rec *r, dav_lockdb *lockdb,
+ const dav_resource *resource,
+ int ro,
+ apr_array_header_t * ns_xlate,
+ dav_propdb **p_propdb)
+{
+ dav_propdb *propdb = apr_pcalloc(r->pool, sizeof(*propdb));
+
+ *p_propdb = NULL;
+
+#if DAV_DEBUG
+ if (resource->uri == NULL) {
+ return dav_new_error(r->pool, HTTP_INTERNAL_SERVER_ERROR, 0, 0,
+ "INTERNAL DESIGN ERROR: resource must define "
+ "its URI.");
+ }
+#endif
+
+ propdb->r = r;
+ apr_pool_create(&propdb->p, r->pool);
+ propdb->resource = resource;
+ propdb->ns_xlate = ns_xlate;
+
+ propdb->db_hooks = DAV_GET_HOOKS_PROPDB(r);
+
+ propdb->lockdb = lockdb;
+
+ /* always defer actual open, to avoid expense of accessing db
+ * when only live properties are involved
+ */
+ propdb->deferred = 1;
+
+ /* ### what to do about closing the propdb on server failure? */
+
+ *p_propdb = propdb;
+ return NULL;
+}
+
+DAV_DECLARE(void) dav_close_propdb(dav_propdb *propdb)
+{
+ if (propdb->db != NULL) {
+ (*propdb->db_hooks->close)(propdb->db);
+ }
+
+ /* Currently, mod_dav's pool usage doesn't allow clearing this pool. */
+#if 0
+ apr_pool_destroy(propdb->p);
+#endif
+}
+
+DAV_DECLARE(dav_get_props_result) dav_get_allprops(dav_propdb *propdb,
+ dav_prop_insert what)
+{
+ const dav_hooks_db *db_hooks = propdb->db_hooks;
+ apr_text_header hdr = { 0 };
+ apr_text_header hdr_ns = { 0 };
+ dav_get_props_result result = { 0 };
+ int found_contenttype = 0;
+ int found_contentlang = 0;
+ dav_prop_insert unused_inserted;
+
+ /* if not just getting supported live properties,
+ * scan all properties in the dead prop database
+ */
+ if (what != DAV_PROP_INSERT_SUPPORTED) {
+ if (propdb->deferred) {
+ /* ### what to do with db open error? */
+ (void) dav_really_open_db(propdb, 1 /*ro*/);
+ }
+
+ /* initialize the result with some start tags... */
+ apr_text_append(propdb->p, &hdr,
+ "<D:propstat>" DEBUG_CR
+ "<D:prop>" DEBUG_CR);
+
+ /* if there ARE properties, then scan them */
+ if (propdb->db != NULL) {
+ dav_xmlns_info *xi = dav_xmlns_create(propdb->p);
+ dav_prop_name name;
+ dav_error *err;
+
+ /* define (up front) any namespaces the db might need */
+ (void) (*db_hooks->define_namespaces)(propdb->db, xi);
+
+ /* get the first property name, beginning the scan */
+ err = (*db_hooks->first_name)(propdb->db, &name);
+ while (!err && name.ns) {
+
+ /*
+ ** We also look for <DAV:getcontenttype> and
+ ** <DAV:getcontentlanguage>. If they are not stored as dead
+ ** properties, then we need to perform a subrequest to get
+ ** their values (if any).
+ */
+ if (*name.ns == 'D' && strcmp(name.ns, "DAV:") == 0
+ && *name.name == 'g') {
+ if (strcmp(name.name, "getcontenttype") == 0) {
+ found_contenttype = 1;
+ }
+ else if (strcmp(name.name, "getcontentlanguage") == 0) {
+ found_contentlang = 1;
+ }
+ }
+
+ if (what == DAV_PROP_INSERT_VALUE) {
+ int found;
+
+ if ((err = (*db_hooks->output_value)(propdb->db, &name,
+ xi, &hdr,
+ &found)) != NULL) {
+ /* ### anything better to do? */
+ /* ### probably should enter a 500 error */
+ goto next_key;
+ }
+ /* assert: found == 1 */
+ }
+ else {
+ /* the value was not requested, so just add an empty
+ tag specifying the property name. */
+ dav_output_prop_name(propdb->p, &name, xi, &hdr);
+ }
+
+ next_key:
+ err = (*db_hooks->next_name)(propdb->db, &name);
+ }
+
+ /* all namespaces have been entered into xi. generate them into
+ the output now. */
+ dav_xmlns_generate(xi, &hdr_ns);
+
+ } /* propdb->db != NULL */
+
+ /* add namespaces for all the liveprop providers */
+ dav_add_all_liveprop_xmlns(propdb->p, &hdr_ns);
+ }
+
+ /* ask the liveprop providers to insert their properties */
+ dav_run_insert_all_liveprops(propdb->r, propdb->resource, what, &hdr);
+
+ /* insert the standard properties */
+ /* ### should be handling the return errors here */
+ (void)dav_insert_coreprop(propdb,
+ DAV_PROPID_CORE_supportedlock, "supportedlock",
+ what, &hdr, &unused_inserted);
+ (void)dav_insert_coreprop(propdb,
+ DAV_PROPID_CORE_lockdiscovery, "lockdiscovery",
+ what, &hdr, &unused_inserted);
+
+ /* if we didn't find these, then do the whole subreq thing. */
+ if (!found_contenttype) {
+ /* ### should be handling the return error here */
+ (void)dav_insert_coreprop(propdb,
+ DAV_PROPID_CORE_getcontenttype,
+ "getcontenttype",
+ what, &hdr, &unused_inserted);
+ }
+ if (!found_contentlang) {
+ /* ### should be handling the return error here */
+ (void)dav_insert_coreprop(propdb,
+ DAV_PROPID_CORE_getcontentlanguage,
+ "getcontentlanguage",
+ what, &hdr, &unused_inserted);
+ }
+
+ /* if not just reporting on supported live props,
+ * terminate the result */
+ if (what != DAV_PROP_INSERT_SUPPORTED) {
+ apr_text_append(propdb->p, &hdr,
+ "</D:prop>" DEBUG_CR
+ "<D:status>HTTP/1.1 200 OK</D:status>" DEBUG_CR
+ "</D:propstat>" DEBUG_CR);
+ }
+
+ result.propstats = hdr.first;
+ result.xmlns = hdr_ns.first;
+ return result;
+}
+
+DAV_DECLARE(dav_get_props_result) dav_get_props(dav_propdb *propdb,
+ apr_xml_doc *doc)
+{
+ const dav_hooks_db *db_hooks = propdb->db_hooks;
+ apr_xml_elem *elem = dav_find_child(doc->root, "prop");
+ apr_text_header hdr_good = { 0 };
+ apr_text_header hdr_bad = { 0 };
+ apr_text_header hdr_ns = { 0 };
+ int have_good = 0;
+ dav_get_props_result result = { 0 };
+ char *marks_liveprop;
+ dav_xmlns_info *xi;
+ int xi_filled = 0;
+
+ /* ### NOTE: we should pass in TWO buffers -- one for keys, one for
+ the marks */
+
+ /* we will ALWAYS provide a "good" result, even if it is EMPTY */
+ apr_text_append(propdb->p, &hdr_good,
+ "<D:propstat>" DEBUG_CR
+ "<D:prop>" DEBUG_CR);
+
+ /* ### the marks should be in a buffer! */
+ /* allocate zeroed-memory for the marks. These marks indicate which
+ liveprop namespaces we've generated into the output xmlns buffer */
+
+ /* same for the liveprops */
+ marks_liveprop = apr_pcalloc(propdb->p, dav_get_liveprop_ns_count() + 1);
+
+ xi = dav_xmlns_create(propdb->p);
+
+ for (elem = elem->first_child; elem; elem = elem->next) {
+ dav_elem_private *priv;
+ dav_error *err;
+ dav_prop_insert inserted;
+ dav_prop_name name;
+
+ /*
+ ** First try live property providers; if they don't handle
+ ** the property, then try looking it up in the propdb.
+ */
+
+ if (elem->priv == NULL) {
+ elem->priv = apr_pcalloc(propdb->p, sizeof(*priv));
+ }
+ priv = elem->priv;
+
+ /* cache the propid; dav_get_props() could be called many times */
+ if (priv->propid == 0)
+ dav_find_liveprop(propdb, elem);
+
+ if (priv->propid != DAV_PROPID_CORE_UNKNOWN) {
+
+ /* insert the property. returns 1 if an insertion was done. */
+ if ((err = dav_insert_liveprop(propdb, elem, DAV_PROP_INSERT_VALUE,
+ &hdr_good, &inserted)) != NULL) {
+ /* ### need to propagate the error to the caller... */
+ /* ### skip it for now, as if nothing was inserted */
+ }
+ if (inserted == DAV_PROP_INSERT_VALUE) {
+ have_good = 1;
+
+ /*
+ ** Add the liveprop's namespace URIs. Note that provider==NULL
+ ** for core properties.
+ */
+ if (priv->provider != NULL) {
+ const char * const * scan_ns_uri;
+
+ for (scan_ns_uri = priv->provider->namespace_uris;
+ *scan_ns_uri != NULL;
+ ++scan_ns_uri) {
+ long ns;
+
+ ns = dav_get_liveprop_ns_index(*scan_ns_uri);
+ if (marks_liveprop[ns])
+ continue;
+ marks_liveprop[ns] = 1;
+
+ dav_insert_xmlns(propdb->p, "lp", ns, *scan_ns_uri,
+ &hdr_ns);
+ }
+ }
+
+ /* property added. move on to the next property. */
+ continue;
+ }
+ else if (inserted == DAV_PROP_INSERT_NOTDEF) {
+ /* nothing to do. fall thru to allow property to be handled
+ as a dead property */
+ }
+#if DAV_DEBUG
+ else {
+#if 0
+ /* ### need to change signature to return an error */
+ return dav_new_error(propdb->p, HTTP_INTERNAL_SERVER_ERROR, 0,
+ 0,
+ "INTERNAL DESIGN ERROR: insert_liveprop "
+ "did not insert what was asked for.");
+#endif
+ }
+#endif
+ }
+
+ /* The property wasn't a live property, so look in the dead property
+ database. */
+
+ /* make sure propdb is really open */
+ if (propdb->deferred) {
+ /* ### what to do with db open error? */
+ (void) dav_really_open_db(propdb, 1 /*ro*/);
+ }
+
+ if (elem->ns == APR_XML_NS_NONE)
+ name.ns = "";
+ else
+ name.ns = APR_XML_GET_URI_ITEM(propdb->ns_xlate, elem->ns);
+ name.name = elem->name;
+
+ /* only bother to look if a database exists */
+ if (propdb->db != NULL) {
+ int found;
+
+ if ((err = (*db_hooks->output_value)(propdb->db, &name,
+ xi, &hdr_good,
+ &found)) != NULL) {
+ /* ### what to do? continue doesn't seem right... */
+ continue;
+ }
+
+ if (found) {
+ have_good = 1;
+
+ /* if we haven't added the db's namespaces, then do so... */
+ if (!xi_filled) {
+ (void) (*db_hooks->define_namespaces)(propdb->db, xi);
+ xi_filled = 1;
+ }
+ continue;
+ }
+ }
+
+ /* not found as a live OR dead property. add a record to the "bad"
+ propstats */
+
+ /* make sure we've started our "bad" propstat */
+ if (hdr_bad.first == NULL) {
+ apr_text_append(propdb->p, &hdr_bad,
+ "<D:propstat>" DEBUG_CR
+ "<D:prop>" DEBUG_CR);
+ }
+
+ /* output this property's name (into the bad propstats) */
+ dav_output_prop_name(propdb->p, &name, xi, &hdr_bad);
+ }
+
+ apr_text_append(propdb->p, &hdr_good,
+ "</D:prop>" DEBUG_CR
+ "<D:status>HTTP/1.1 200 OK</D:status>" DEBUG_CR
+ "</D:propstat>" DEBUG_CR);
+
+ /* default to start with the good */
+ result.propstats = hdr_good.first;
+
+ /* we may not have any "bad" results */
+ if (hdr_bad.first != NULL) {
+ /* "close" the bad propstat */
+ apr_text_append(propdb->p, &hdr_bad,
+ "</D:prop>" DEBUG_CR
+ "<D:status>HTTP/1.1 404 Not Found</D:status>" DEBUG_CR
+ "</D:propstat>" DEBUG_CR);
+
+ /* if there are no good props, then just return the bad */
+ if (!have_good) {
+ result.propstats = hdr_bad.first;
+ }
+ else {
+ /* hook the bad propstat to the end of the good one */
+ hdr_good.last->next = hdr_bad.first;
+ }
+ }
+
+ /* add in all the various namespaces, and return them */
+ dav_xmlns_generate(xi, &hdr_ns);
+ result.xmlns = hdr_ns.first;
+
+ return result;
+}
+
+DAV_DECLARE(void) dav_get_liveprop_supported(dav_propdb *propdb,
+ const char *ns_uri,
+ const char *propname,
+ apr_text_header *body)
+{
+ int propid;
+ const dav_hooks_liveprop *hooks;
+
+ propid = dav_find_liveprop_provider(propdb, ns_uri, propname, &hooks);
+
+ if (propid != DAV_PROPID_CORE_UNKNOWN) {
+ if (hooks == NULL) {
+ /* this is a "core" property that we define */
+ dav_prop_insert unused_inserted;
+ dav_insert_coreprop(propdb, propid, propname,
+ DAV_PROP_INSERT_SUPPORTED, body, &unused_inserted);
+ }
+ else {
+ (*hooks->insert_prop)(propdb->resource, propid,
+ DAV_PROP_INSERT_SUPPORTED, body);
+ }
+ }
+}
+
+DAV_DECLARE_NONSTD(void) dav_prop_validate(dav_prop_ctx *ctx)
+{
+ dav_propdb *propdb = ctx->propdb;
+ apr_xml_elem *prop = ctx->prop;
+ dav_elem_private *priv;
+
+ priv = ctx->prop->priv = apr_pcalloc(propdb->p, sizeof(*priv));
+
+ /*
+ ** Check to see if this is a live property, and fill the fields
+ ** in the XML elem, as appropriate.
+ **
+ ** Verify that the property is read/write. If not, then it cannot
+ ** be SET or DELETEd.
+ */
+ if (priv->propid == 0) {
+ dav_find_liveprop(propdb, prop);
+
+ /* it's a liveprop if a provider was found */
+ /* ### actually the "core" props should really be liveprops, but
+ ### there is no "provider" for those and the r/w props are
+ ### treated as dead props anyhow */
+ ctx->is_liveprop = priv->provider != NULL;
+ }
+
+ if (!dav_rw_liveprop(propdb, priv)) {
+ ctx->err = dav_new_error(propdb->p, HTTP_CONFLICT,
+ DAV_ERR_PROP_READONLY, 0,
+ "Property is read-only.");
+ return;
+ }
+
+ if (ctx->is_liveprop) {
+ int defer_to_dead = 0;
+
+ ctx->err = (*priv->provider->patch_validate)(propdb->resource,
+ prop, ctx->operation,
+ &ctx->liveprop_ctx,
+ &defer_to_dead);
+ if (ctx->err != NULL || !defer_to_dead)
+ return;
+
+ /* clear is_liveprop -- act as a dead prop now */
+ ctx->is_liveprop = 0;
+ }
+
+ /*
+ ** The property is supposed to be stored into the dead-property
+ ** database. Make sure the thing is truly open (and writable).
+ */
+ if (propdb->deferred
+ && (ctx->err = dav_really_open_db(propdb, 0 /* ro */)) != NULL) {
+ return;
+ }
+
+ /*
+ ** There should be an open, writable database in here!
+ **
+ ** Note: the database would be NULL if it was opened readonly and it
+ ** did not exist.
+ */
+ if (propdb->db == NULL) {
+ ctx->err = dav_new_error(propdb->p, HTTP_INTERNAL_SERVER_ERROR,
+ DAV_ERR_PROP_NO_DATABASE, 0,
+ "Attempted to set/remove a property "
+ "without a valid, open, read/write "
+ "property database.");
+ return;
+ }
+
+ if (ctx->operation == DAV_PROP_OP_SET) {
+ /*
+ ** Prep the element => propdb namespace index mapping, inserting
+ ** namespace URIs into the propdb that don't exist.
+ */
+ (void) (*propdb->db_hooks->map_namespaces)(propdb->db,
+ propdb->ns_xlate,
+ &propdb->mapping);
+ }
+ else if (ctx->operation == DAV_PROP_OP_DELETE) {
+ /*
+ ** There are no checks to perform here. If a property exists, then
+ ** we will delete it. If it does not exist, then it does not matter
+ ** (see S12.13.1).
+ **
+ ** Note that if a property does not exist, that does not rule out
+ ** that a SET will occur during this PROPPATCH (thusly creating it).
+ */
+ }
+}
+
+DAV_DECLARE_NONSTD(void) dav_prop_exec(dav_prop_ctx *ctx)
+{
+ dav_propdb *propdb = ctx->propdb;
+ dav_error *err = NULL;
+ dav_elem_private *priv = ctx->prop->priv;
+
+ ctx->rollback = apr_pcalloc(propdb->p, sizeof(*ctx->rollback));
+
+ if (ctx->is_liveprop) {
+ err = (*priv->provider->patch_exec)(propdb->resource,
+ ctx->prop, ctx->operation,
+ ctx->liveprop_ctx,
+ &ctx->rollback->liveprop);
+ }
+ else {
+ dav_prop_name name;
+
+ if (ctx->prop->ns == APR_XML_NS_NONE)
+ name.ns = "";
+ else
+ name.ns = APR_XML_GET_URI_ITEM(propdb->ns_xlate, ctx->prop->ns);
+ name.name = ctx->prop->name;
+
+ /* save the old value so that we can do a rollback. */
+ if ((err = (*propdb->db_hooks
+ ->get_rollback)(propdb->db, &name,
+ &ctx->rollback->deadprop)) != NULL)
+ goto error;
+
+ if (ctx->operation == DAV_PROP_OP_SET) {
+
+ /* Note: propdb->mapping was set in dav_prop_validate() */
+ err = (*propdb->db_hooks->store)(propdb->db, &name, ctx->prop,
+ propdb->mapping);
+
+ /*
+ ** If an error occurred, then assume that we didn't change the
+ ** value. Remove the rollback item so that we don't try to set
+ ** its value during the rollback.
+ */
+ /* ### euh... where is the removal? */
+ }
+ else if (ctx->operation == DAV_PROP_OP_DELETE) {
+
+ /*
+ ** Delete the property. Ignore errors -- the property is there, or
+ ** we are deleting it for a second time.
+ */
+ /* ### but what about other errors? */
+ (void) (*propdb->db_hooks->remove)(propdb->db, &name);
+ }
+ }
+
+ error:
+ /* push a more specific error here */
+ if (err != NULL) {
+ /*
+ ** Use HTTP_INTERNAL_SERVER_ERROR because we shouldn't have seen
+ ** any errors at this point.
+ */
+ ctx->err = dav_push_error(propdb->p, HTTP_INTERNAL_SERVER_ERROR,
+ DAV_ERR_PROP_EXEC,
+ "Could not execute PROPPATCH.", err);
+ }
+}
+
+DAV_DECLARE_NONSTD(void) dav_prop_commit(dav_prop_ctx *ctx)
+{
+ dav_elem_private *priv = ctx->prop->priv;
+
+ /*
+ ** Note that a commit implies ctx->err is NULL. The caller should assume
+ ** a status of HTTP_OK for this case.
+ */
+
+ if (ctx->is_liveprop) {
+ (*priv->provider->patch_commit)(ctx->propdb->resource,
+ ctx->operation,
+ ctx->liveprop_ctx,
+ ctx->rollback->liveprop);
+ }
+}
+
+DAV_DECLARE_NONSTD(void) dav_prop_rollback(dav_prop_ctx *ctx)
+{
+ dav_error *err = NULL;
+ dav_elem_private *priv = ctx->prop->priv;
+
+ /* do nothing if there is no rollback information. */
+ if (ctx->rollback == NULL)
+ return;
+
+ /*
+ ** ### if we have an error, and a rollback occurs, then the namespace
+ ** ### mods should not happen at all. Basically, the namespace management
+ ** ### is simply a bitch.
+ */
+
+ if (ctx->is_liveprop) {
+ err = (*priv->provider->patch_rollback)(ctx->propdb->resource,
+ ctx->operation,
+ ctx->liveprop_ctx,
+ ctx->rollback->liveprop);
+ }
+ else {
+ err = (*ctx->propdb->db_hooks
+ ->apply_rollback)(ctx->propdb->db, ctx->rollback->deadprop);
+ }
+
+ if (err != NULL) {
+ if (ctx->err == NULL)
+ ctx->err = err;
+ else {
+ dav_error *scan = err;
+
+ /* hook previous errors at the end of the rollback error */
+ while (scan->prev != NULL)
+ scan = scan->prev;
+ scan->prev = ctx->err;
+ ctx->err = err;
+ }
+ }
+}
diff --git a/modules/dav/main/providers.c b/modules/dav/main/providers.c
new file mode 100644
index 0000000..44870fd
--- /dev/null
+++ b/modules/dav/main/providers.c
@@ -0,0 +1,58 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "apr_pools.h"
+#include "apr_hash.h"
+#include "ap_provider.h"
+#include "mod_dav.h"
+
+#define DAV_PROVIDER_GROUP "dav"
+
+DAV_DECLARE(void) dav_register_provider(apr_pool_t *p, const char *name,
+ const dav_provider *provider)
+{
+ ap_register_provider(p, DAV_PROVIDER_GROUP, name, "0", provider);
+}
+
+DAV_DECLARE(const dav_provider *) dav_lookup_provider(const char *name)
+{
+ return ap_lookup_provider(DAV_PROVIDER_GROUP, name, "0");
+}
+
+DAV_DECLARE(void) dav_options_provider_register(apr_pool_t *p,
+ const char *name,
+ const dav_options_provider *provider)
+{
+ ap_register_provider(p, DAV_OPTIONS_EXTENSION_GROUP, name, "0", provider);
+}
+
+DAV_DECLARE(const dav_options_provider *) dav_get_options_providers(const char *name)
+{
+ return ap_lookup_provider(DAV_OPTIONS_EXTENSION_GROUP, name, "0");
+}
+
+
+DAV_DECLARE(void) dav_resource_type_provider_register(apr_pool_t *p,
+ const char *name,
+ const dav_resource_type_provider *provider)
+{
+ ap_register_provider(p, DAV_RESOURCE_TYPE_GROUP, name, "0", provider);
+}
+
+DAV_DECLARE(const dav_resource_type_provider *) dav_get_resource_type_providers(const char *name)
+{
+ return ap_lookup_provider(DAV_RESOURCE_TYPE_GROUP, name, "0");
+}
diff --git a/modules/dav/main/std_liveprop.c b/modules/dav/main/std_liveprop.c
new file mode 100644
index 0000000..e760c65
--- /dev/null
+++ b/modules/dav/main/std_liveprop.c
@@ -0,0 +1,226 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "httpd.h"
+#include "util_xml.h"
+#include "apr_strings.h"
+#include "ap_provider.h"
+
+#include "mod_dav.h"
+
+/* forward-declare */
+static const dav_hooks_liveprop dav_core_hooks_liveprop;
+
+/*
+** The namespace URIs that we use. There will only ever be "DAV:".
+*/
+static const char * const dav_core_namespace_uris[] =
+{
+ "DAV:",
+ NULL /* sentinel */
+};
+
+/*
+** Define each of the core properties that this provider will handle.
+** Note that all of them are in the DAV: namespace, which has a
+** provider-local index of 0.
+*/
+static const dav_liveprop_spec dav_core_props[] =
+{
+ { 0, "comment", DAV_PROPID_comment, 1 },
+ { 0, "creator-displayname", DAV_PROPID_creator_displayname, 1 },
+ { 0, "displayname", DAV_PROPID_displayname, 1 },
+ { 0, "resourcetype", DAV_PROPID_resourcetype, 0 },
+ { 0, "source", DAV_PROPID_source, 1 },
+
+ { 0 } /* sentinel */
+};
+
+static const dav_liveprop_group dav_core_liveprop_group =
+{
+ dav_core_props,
+ dav_core_namespace_uris,
+ &dav_core_hooks_liveprop
+};
+
+static dav_prop_insert dav_core_insert_prop(const dav_resource *resource,
+ int propid, dav_prop_insert what,
+ apr_text_header *phdr)
+{
+ const char *value = NULL;
+ const char *s;
+ apr_pool_t *p = resource->pool;
+ const dav_liveprop_spec *info;
+ long global_ns;
+
+ switch (propid)
+ {
+ case DAV_PROPID_resourcetype:
+ { /* additional type info provided by external modules ? */
+ int i;
+
+ apr_array_header_t *extensions =
+ ap_list_provider_names(p, DAV_RESOURCE_TYPE_GROUP, "0");
+ ap_list_provider_names_t *entry =
+ (ap_list_provider_names_t *)extensions->elts;
+
+ for (i = 0; i < extensions->nelts; i++, entry++) {
+ const dav_resource_type_provider *res_hooks =
+ dav_get_resource_type_providers(entry->provider_name);
+ const char *name = NULL, *uri = NULL;
+
+ if (!res_hooks || !res_hooks->get_resource_type)
+ continue;
+
+ if (!res_hooks->get_resource_type(resource, &name, &uri) &&
+ name) {
+
+ if (!uri || !strcasecmp(uri, "DAV:"))
+ value = apr_pstrcat(p, value ? value : "",
+ "<D:", name, "/>", NULL);
+ else
+ value = apr_pstrcat(p, value ? value : "",
+ "<x:", name,
+ " xmlns:x=\"", uri,
+ "\"/>", NULL);
+ }
+ }
+ }
+ switch (resource->type) {
+ case DAV_RESOURCE_TYPE_VERSION:
+ if (resource->baselined) {
+ value = apr_pstrcat(p, value ? value : "", "<D:baseline/>", NULL);
+ break;
+ }
+ /* fall through */
+ case DAV_RESOURCE_TYPE_REGULAR:
+ case DAV_RESOURCE_TYPE_WORKING:
+ if (resource->collection) {
+ value = apr_pstrcat(p, value ? value : "", "<D:collection/>", NULL);
+ }
+ else {
+ /* ### should we denote lock-null resources? */
+ if (value == NULL) {
+ value = ""; /* becomes: <D:resourcetype/> */
+ }
+ }
+ break;
+ case DAV_RESOURCE_TYPE_HISTORY:
+ value = apr_pstrcat(p, value ? value : "", "<D:version-history/>", NULL);
+ break;
+ case DAV_RESOURCE_TYPE_WORKSPACE:
+ value = apr_pstrcat(p, value ? value : "", "<D:collection/>", NULL);
+ break;
+ case DAV_RESOURCE_TYPE_ACTIVITY:
+ value = apr_pstrcat(p, value ? value : "", "<D:activity/>", NULL);
+ break;
+
+ default:
+ /* ### bad juju */
+ return DAV_PROP_INSERT_NOTDEF;
+ }
+ break;
+
+ case DAV_PROPID_comment:
+ case DAV_PROPID_creator_displayname:
+ case DAV_PROPID_displayname:
+ case DAV_PROPID_source:
+ default:
+ /*
+ ** This property is known, but not defined as a liveprop. However,
+ ** it may be a dead property.
+ */
+ return DAV_PROP_INSERT_NOTDEF;
+ }
+
+ /* assert: value != NULL */
+
+ /* get the information and global NS index for the property */
+ global_ns = dav_get_liveprop_info(propid, &dav_core_liveprop_group, &info);
+
+ /* assert: info != NULL && info->name != NULL */
+
+ if (what == DAV_PROP_INSERT_SUPPORTED) {
+ s = apr_psprintf(p,
+ "<D:supported-live-property D:name=\"%s\" "
+ "D:namespace=\"%s\"/>" DEBUG_CR,
+ info->name, dav_core_namespace_uris[info->ns]);
+ }
+ else if (what == DAV_PROP_INSERT_VALUE && *value != '\0') {
+ s = apr_psprintf(p, "<lp%ld:%s>%s</lp%ld:%s>" DEBUG_CR,
+ global_ns, info->name, value, global_ns, info->name);
+ }
+ else {
+ s = apr_psprintf(p, "<lp%ld:%s/>" DEBUG_CR, global_ns, info->name);
+ }
+ apr_text_append(p, phdr, s);
+
+ /* we inserted what was asked for */
+ return what;
+}
+
+static int dav_core_is_writable(const dav_resource *resource, int propid)
+{
+ const dav_liveprop_spec *info;
+
+ (void) dav_get_liveprop_info(propid, &dav_core_liveprop_group, &info);
+ return info->is_writable;
+}
+
+static dav_error * dav_core_patch_validate(const dav_resource *resource,
+ const apr_xml_elem *elem,
+ int operation, void **context,
+ int *defer_to_dead)
+{
+ /* all of our writable props go in the dead prop database */
+ *defer_to_dead = 1;
+
+ return NULL;
+}
+
+static const dav_hooks_liveprop dav_core_hooks_liveprop = {
+ dav_core_insert_prop,
+ dav_core_is_writable,
+ dav_core_namespace_uris,
+ dav_core_patch_validate,
+ NULL, /* patch_exec */
+ NULL, /* patch_commit */
+ NULL, /* patch_rollback */
+};
+
+DAV_DECLARE_NONSTD(int) dav_core_find_liveprop(
+ const dav_resource *resource,
+ const char *ns_uri, const char *name,
+ const dav_hooks_liveprop **hooks)
+{
+ return dav_do_find_liveprop(ns_uri, name, &dav_core_liveprop_group, hooks);
+}
+
+DAV_DECLARE_NONSTD(void) dav_core_insert_all_liveprops(
+ request_rec *r,
+ const dav_resource *resource,
+ dav_prop_insert what,
+ apr_text_header *phdr)
+{
+ (void) dav_core_insert_prop(resource, DAV_PROPID_resourcetype,
+ what, phdr);
+}
+
+DAV_DECLARE_NONSTD(void) dav_core_register_uris(apr_pool_t *p)
+{
+ /* register the namespace URIs */
+ dav_register_liveprop_group(p, &dav_core_liveprop_group);
+}
diff --git a/modules/dav/main/util.c b/modules/dav/main/util.c
new file mode 100644
index 0000000..9f24604
--- /dev/null
+++ b/modules/dav/main/util.c
@@ -0,0 +1,2152 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/*
+** DAV extension module for Apache 2.0.*
+** - various utilities, repository-independent
+*/
+
+#include "apr_strings.h"
+#include "apr_lib.h"
+
+#define APR_WANT_STRFUNC
+#include "apr_want.h"
+
+#include "mod_dav.h"
+
+#include "http_request.h"
+#include "http_config.h"
+#include "http_vhost.h"
+#include "http_log.h"
+#include "http_protocol.h"
+
+DAV_DECLARE(dav_error*) dav_new_error(apr_pool_t *p, int status, int error_id,
+ apr_status_t aprerr, const char *desc)
+{
+ dav_error *err = apr_pcalloc(p, sizeof(*err));
+
+ /* DBG3("dav_new_error: %d %d %s", status, error_id, desc ? desc : "(no desc)"); */
+
+ err->status = status;
+ err->error_id = error_id;
+ err->desc = desc;
+ err->aprerr = aprerr;
+
+ return err;
+}
+
+DAV_DECLARE(dav_error*) dav_new_error_tag(apr_pool_t *p, int status,
+ int error_id, apr_status_t aprerr,
+ const char *desc,
+ const char *namespace,
+ const char *tagname)
+{
+ dav_error *err = dav_new_error(p, status, error_id, aprerr, desc);
+
+ err->tagname = tagname;
+ err->namespace = namespace;
+
+ return err;
+}
+
+
+DAV_DECLARE(dav_error*) dav_push_error(apr_pool_t *p, int status,
+ int error_id, const char *desc,
+ dav_error *prev)
+{
+ dav_error *err = apr_pcalloc(p, sizeof(*err));
+
+ err->status = status;
+ err->error_id = error_id;
+ err->desc = desc;
+ err->prev = prev;
+
+ return err;
+}
+
+DAV_DECLARE(dav_error*) dav_join_error(dav_error *dest, dav_error *src)
+{
+ dav_error *curr = dest;
+
+ /* src error doesn't exist so nothing to join just return dest */
+ if (src == NULL) {
+ return dest;
+ }
+
+ /* dest error doesn't exist so nothing to join just return src */
+ if (curr == NULL) {
+ return src;
+ }
+
+ /* find last error in dest stack */
+ while (curr->prev != NULL) {
+ curr = curr->prev;
+ }
+
+ /* add the src error onto end of dest stack and return it */
+ curr->prev = src;
+ return dest;
+}
+
+DAV_DECLARE(void) dav_check_bufsize(apr_pool_t * p, dav_buffer *pbuf,
+ apr_size_t extra_needed)
+{
+ /* grow the buffer if necessary */
+ if (pbuf->cur_len + extra_needed > pbuf->alloc_len) {
+ char *newbuf;
+
+ pbuf->alloc_len += extra_needed + DAV_BUFFER_PAD;
+ newbuf = apr_palloc(p, pbuf->alloc_len);
+ memcpy(newbuf, pbuf->buf, pbuf->cur_len);
+ pbuf->buf = newbuf;
+ }
+}
+
+DAV_DECLARE(void) dav_set_bufsize(apr_pool_t * p, dav_buffer *pbuf,
+ apr_size_t size)
+{
+ /* NOTE: this does not retain prior contents */
+
+ /* NOTE: this function is used to init the first pointer, too, since
+ the PAD will be larger than alloc_len (0) for zeroed structures */
+
+ /* grow if we don't have enough for the requested size plus padding */
+ if (size + DAV_BUFFER_PAD > pbuf->alloc_len) {
+ /* set the new length; min of MINSIZE */
+ pbuf->alloc_len = size + DAV_BUFFER_PAD;
+ if (pbuf->alloc_len < DAV_BUFFER_MINSIZE)
+ pbuf->alloc_len = DAV_BUFFER_MINSIZE;
+
+ pbuf->buf = apr_palloc(p, pbuf->alloc_len);
+ }
+ pbuf->cur_len = size;
+}
+
+
+/* initialize a buffer and copy the specified (null-term'd) string into it */
+DAV_DECLARE(void) dav_buffer_init(apr_pool_t *p, dav_buffer *pbuf,
+ const char *str)
+{
+ dav_set_bufsize(p, pbuf, strlen(str));
+ memcpy(pbuf->buf, str, pbuf->cur_len + 1);
+}
+
+/* append a string to the end of the buffer, adjust length */
+DAV_DECLARE(void) dav_buffer_append(apr_pool_t *p, dav_buffer *pbuf,
+ const char *str)
+{
+ apr_size_t len = strlen(str);
+
+ dav_check_bufsize(p, pbuf, len + 1);
+ memcpy(pbuf->buf + pbuf->cur_len, str, len + 1);
+ pbuf->cur_len += len;
+}
+
+/* place a string on the end of the buffer, do NOT adjust length */
+DAV_DECLARE(void) dav_buffer_place(apr_pool_t *p, dav_buffer *pbuf,
+ const char *str)
+{
+ apr_size_t len = strlen(str);
+
+ dav_check_bufsize(p, pbuf, len + 1);
+ memcpy(pbuf->buf + pbuf->cur_len, str, len + 1);
+}
+
+/* place some memory on the end of a buffer; do NOT adjust length */
+DAV_DECLARE(void) dav_buffer_place_mem(apr_pool_t *p, dav_buffer *pbuf,
+ const void *mem, apr_size_t amt,
+ apr_size_t pad)
+{
+ dav_check_bufsize(p, pbuf, amt + pad);
+ memcpy(pbuf->buf + pbuf->cur_len, mem, amt);
+}
+
+/*
+** dav_lookup_uri()
+**
+** Extension for ap_sub_req_lookup_uri() which can't handle absolute
+** URIs properly.
+**
+** If NULL is returned, then an error occurred with parsing the URI or
+** the URI does not match the current server.
+*/
+DAV_DECLARE(dav_lookup_result) dav_lookup_uri(const char *uri,
+ request_rec * r,
+ int must_be_absolute)
+{
+ dav_lookup_result result = { 0 };
+ const char *scheme;
+ apr_port_t port;
+ apr_uri_t comp;
+ char *new_file;
+ const char *domain;
+
+ /* first thing to do is parse the URI into various components */
+ if (apr_uri_parse(r->pool, uri, &comp) != APR_SUCCESS) {
+ result.err.status = HTTP_BAD_REQUEST;
+ result.err.desc = "Invalid syntax in Destination URI.";
+ return result;
+ }
+
+ /* the URI must be an absoluteURI (WEBDAV S9.3) */
+ if (comp.scheme == NULL && must_be_absolute) {
+ result.err.status = HTTP_BAD_REQUEST;
+ result.err.desc = "Destination URI must be an absolute URI.";
+ return result;
+ }
+
+ /* the URI must not have a query (args) or a fragment */
+ if (comp.query != NULL || comp.fragment != NULL) {
+ result.err.status = HTTP_BAD_REQUEST;
+ result.err.desc =
+ "Destination URI contains invalid components "
+ "(a query or a fragment).";
+ return result;
+ }
+
+ /* If the scheme or port was provided, then make sure that it matches
+ the scheme/port of this request. If the request must be absolute,
+ then require the (explicit/implicit) scheme/port be matching.
+
+ ### hmm. if a port wasn't provided (does the parse return port==0?),
+ ### but we're on a non-standard port, then we won't detect that the
+ ### URI's port implies the wrong one.
+ */
+ if (comp.scheme != NULL || comp.port != 0 || must_be_absolute)
+ {
+ /* ### not sure this works if the current request came in via https: */
+ scheme = r->parsed_uri.scheme;
+ if (scheme == NULL)
+ scheme = ap_http_scheme(r);
+
+ /* insert a port if the URI did not contain one */
+ if (comp.port == 0)
+ comp.port = apr_uri_port_of_scheme(comp.scheme);
+
+ /* now, verify that the URI uses the same scheme as the current.
+ request. the port must match our port.
+ */
+ port = r->connection->local_addr->port;
+ if (strcasecmp(comp.scheme, scheme) != 0
+#ifdef APACHE_PORT_HANDLING_IS_BUSTED
+ || comp.port != port
+#endif
+ ) {
+ result.err.status = HTTP_BAD_GATEWAY;
+ result.err.desc = apr_psprintf(r->pool,
+ "Destination URI refers to "
+ "different scheme or port "
+ "(%s://hostname:%d)" APR_EOL_STR
+ "(want: %s://hostname:%d)",
+ comp.scheme ? comp.scheme : scheme,
+ comp.port ? comp.port : port,
+ scheme, port);
+ return result;
+ }
+ }
+
+ /* we have verified the scheme, port, and general structure */
+
+ /*
+ ** Hrm. IE5 will pass unqualified hostnames for both the
+ ** Host: and Destination: headers. This breaks the
+ ** http_vhost.c::matches_aliases function.
+ **
+ ** For now, qualify unqualified comp.hostnames with
+ ** r->server->server_hostname.
+ **
+ ** ### this is a big hack. Apache should provide a better way.
+ ** ### maybe the admin should list the unqualified hosts in a
+ ** ### <ServerAlias> block?
+ */
+ if (comp.hostname != NULL
+ && strrchr(comp.hostname, '.') == NULL
+ && (domain = strchr(r->server->server_hostname, '.')) != NULL) {
+ comp.hostname = apr_pstrcat(r->pool, comp.hostname, domain, NULL);
+ }
+
+ /* now, if a hostname was provided, then verify that it represents the
+ same server as the current connection. note that we just use our
+ port, since we've verified the URI matches ours */
+#ifdef APACHE_PORT_HANDLING_IS_BUSTED
+ if (comp.hostname != NULL &&
+ !ap_matches_request_vhost(r, comp.hostname, port)) {
+ result.err.status = HTTP_BAD_GATEWAY;
+ result.err.desc = "Destination URI refers to a different server.";
+ return result;
+ }
+#endif
+
+ /* we have verified that the requested URI denotes the same server as
+ the current request. Therefore, we can use ap_sub_req_lookup_uri() */
+
+ /* reconstruct a URI as just the path */
+ new_file = apr_uri_unparse(r->pool, &comp, APR_URI_UNP_OMITSITEPART);
+
+ /*
+ * Lookup the URI and return the sub-request. Note that we use the
+ * same HTTP method on the destination. This allows the destination
+ * to apply appropriate restrictions (e.g. readonly).
+ */
+ result.rnew = ap_sub_req_method_uri(r->method, new_file, r, NULL);
+
+ return result;
+}
+
+/* ---------------------------------------------------------------
+**
+** XML UTILITY FUNCTIONS
+*/
+
+/* validate that the root element uses a given DAV: tagname (TRUE==valid) */
+DAV_DECLARE(int) dav_validate_root(const apr_xml_doc *doc,
+ const char *tagname)
+{
+ return doc->root &&
+ doc->root->ns == APR_XML_NS_DAV_ID &&
+ strcmp(doc->root->name, tagname) == 0;
+}
+
+/* find and return the (unique) child with a given DAV: tagname */
+DAV_DECLARE(apr_xml_elem *) dav_find_child(const apr_xml_elem *elem,
+ const char *tagname)
+{
+ apr_xml_elem *child = elem->first_child;
+
+ for (; child; child = child->next)
+ if (child->ns == APR_XML_NS_DAV_ID && !strcmp(child->name, tagname))
+ return child;
+ return NULL;
+}
+
+/* gather up all the CDATA into a single string */
+DAV_DECLARE(const char *) dav_xml_get_cdata(const apr_xml_elem *elem, apr_pool_t *pool,
+ int strip_white)
+{
+ apr_size_t len = 0;
+ apr_text *scan;
+ const apr_xml_elem *child;
+ char *cdata;
+ char *s;
+ apr_size_t tlen;
+ const char *found_text = NULL; /* initialize to avoid gcc warning */
+ int found_count = 0;
+
+ for (scan = elem->first_cdata.first; scan != NULL; scan = scan->next) {
+ found_text = scan->text;
+ ++found_count;
+ len += strlen(found_text);
+ }
+
+ for (child = elem->first_child; child != NULL; child = child->next) {
+ for (scan = child->following_cdata.first;
+ scan != NULL;
+ scan = scan->next) {
+ found_text = scan->text;
+ ++found_count;
+ len += strlen(found_text);
+ }
+ }
+
+ /* some fast-path cases:
+ * 1) zero-length cdata
+ * 2) a single piece of cdata with no whitespace to strip
+ */
+ if (len == 0)
+ return "";
+ if (found_count == 1) {
+ if (!strip_white
+ || (!apr_isspace(*found_text)
+ && !apr_isspace(found_text[len - 1])))
+ return found_text;
+ }
+
+ cdata = s = apr_palloc(pool, len + 1);
+
+ for (scan = elem->first_cdata.first; scan != NULL; scan = scan->next) {
+ tlen = strlen(scan->text);
+ memcpy(s, scan->text, tlen);
+ s += tlen;
+ }
+
+ for (child = elem->first_child; child != NULL; child = child->next) {
+ for (scan = child->following_cdata.first;
+ scan != NULL;
+ scan = scan->next) {
+ tlen = strlen(scan->text);
+ memcpy(s, scan->text, tlen);
+ s += tlen;
+ }
+ }
+
+ *s = '\0';
+
+ if (strip_white) {
+ /* trim leading whitespace */
+ while (apr_isspace(*cdata)) { /* assume: return false for '\0' */
+ ++cdata;
+ --len;
+ }
+
+ /* trim trailing whitespace */
+ while (len-- > 0 && apr_isspace(cdata[len]))
+ continue;
+ cdata[len + 1] = '\0';
+ }
+
+ return cdata;
+}
+
+DAV_DECLARE(dav_xmlns_info *) dav_xmlns_create(apr_pool_t *pool)
+{
+ dav_xmlns_info *xi = apr_pcalloc(pool, sizeof(*xi));
+
+ xi->pool = pool;
+ xi->uri_prefix = apr_hash_make(pool);
+ xi->prefix_uri = apr_hash_make(pool);
+
+ return xi;
+}
+
+DAV_DECLARE(void) dav_xmlns_add(dav_xmlns_info *xi,
+ const char *prefix, const char *uri)
+{
+ /* this "should" not overwrite a prefix mapping */
+ apr_hash_set(xi->prefix_uri, prefix, APR_HASH_KEY_STRING, uri);
+
+ /* note: this may overwrite an existing URI->prefix mapping, but it
+ doesn't matter -- any prefix is usable to specify the URI. */
+ apr_hash_set(xi->uri_prefix, uri, APR_HASH_KEY_STRING, prefix);
+}
+
+DAV_DECLARE(const char *) dav_xmlns_add_uri(dav_xmlns_info *xi,
+ const char *uri)
+{
+ const char *prefix;
+
+ if ((prefix = apr_hash_get(xi->uri_prefix, uri,
+ APR_HASH_KEY_STRING)) != NULL)
+ return prefix;
+
+ prefix = apr_psprintf(xi->pool, "g%d", xi->count++);
+ dav_xmlns_add(xi, prefix, uri);
+ return prefix;
+}
+
+DAV_DECLARE(const char *) dav_xmlns_get_uri(dav_xmlns_info *xi,
+ const char *prefix)
+{
+ return apr_hash_get(xi->prefix_uri, prefix, APR_HASH_KEY_STRING);
+}
+
+DAV_DECLARE(const char *) dav_xmlns_get_prefix(dav_xmlns_info *xi,
+ const char *uri)
+{
+ return apr_hash_get(xi->uri_prefix, uri, APR_HASH_KEY_STRING);
+}
+
+DAV_DECLARE(void) dav_xmlns_generate(dav_xmlns_info *xi,
+ apr_text_header *phdr)
+{
+ apr_hash_index_t *hi = apr_hash_first(xi->pool, xi->prefix_uri);
+
+ for (; hi != NULL; hi = apr_hash_next(hi)) {
+ const void *prefix;
+ void *uri;
+ const char *s;
+
+ apr_hash_this(hi, &prefix, NULL, &uri);
+
+ s = apr_psprintf(xi->pool, " xmlns:%s=\"%s\"",
+ (const char *)prefix, (const char *)uri);
+ apr_text_append(xi->pool, phdr, s);
+ }
+}
+
+/* ---------------------------------------------------------------
+**
+** Timeout header processing
+**
+*/
+
+/* dav_get_timeout: If the Timeout: header exists, return a time_t
+ * when this lock is expected to expire. Otherwise, return
+ * a time_t of DAV_TIMEOUT_INFINITE.
+ *
+ * It's unclear if DAV clients are required to understand
+ * Seconds-xxx and Infinity time values. We assume that they do.
+ * In addition, for now, that's all we understand, too.
+ */
+DAV_DECLARE(time_t) dav_get_timeout(request_rec *r)
+{
+ time_t now, expires = DAV_TIMEOUT_INFINITE;
+
+ const char *timeout_const = apr_table_get(r->headers_in, "Timeout");
+ const char *timeout = apr_pstrdup(r->pool, timeout_const), *val;
+
+ if (timeout == NULL)
+ return DAV_TIMEOUT_INFINITE;
+
+ /* Use the first thing we understand, or infinity if
+ * we don't understand anything.
+ */
+
+ while ((val = ap_getword_white(r->pool, &timeout)) && strlen(val)) {
+ if (!strncmp(val, "Infinite", 8)) {
+ return DAV_TIMEOUT_INFINITE;
+ }
+
+ if (!strncmp(val, "Second-", 7)) {
+ val += 7;
+ /* ### We need to handle overflow better:
+ * ### timeout will be <= 2^32 - 1
+ */
+ expires = atol(val);
+ now = time(NULL);
+ return now + expires;
+ }
+ }
+
+ return DAV_TIMEOUT_INFINITE;
+}
+
+/* ---------------------------------------------------------------
+**
+** If Header processing
+**
+*/
+
+/* add_if_resource returns a new if_header, linking it to next_ih.
+ */
+static dav_if_header *dav_add_if_resource(apr_pool_t *p, dav_if_header *next_ih,
+ const char *uri, apr_size_t uri_len)
+{
+ dav_if_header *ih;
+
+ if ((ih = apr_pcalloc(p, sizeof(*ih))) == NULL)
+ return NULL;
+
+ ih->uri = uri;
+ ih->uri_len = uri_len;
+ ih->next = next_ih;
+
+ return ih;
+}
+
+/* add_if_state adds a condition to an if_header.
+ */
+static dav_error * dav_add_if_state(apr_pool_t *p, dav_if_header *ih,
+ const char *state_token,
+ dav_if_state_type t, int condition,
+ const dav_hooks_locks *locks_hooks)
+{
+ dav_if_state_list *new_sl;
+
+ new_sl = apr_pcalloc(p, sizeof(*new_sl));
+
+ new_sl->condition = condition;
+ new_sl->type = t;
+
+ if (t == dav_if_opaquelock) {
+ dav_error *err;
+
+ if ((err = (*locks_hooks->parse_locktoken)(p, state_token,
+ &new_sl->locktoken)) != NULL) {
+ /* If the state token cannot be parsed, treat it as an
+ * unknown state; this will evaluate to "false" later
+ * during If header validation. */
+ if (err->error_id == DAV_ERR_LOCK_UNK_STATE_TOKEN) {
+ new_sl->type = dav_if_unknown;
+ }
+ else {
+ /* ### maybe add a higher-level description */
+ return err;
+ }
+ }
+ }
+ else
+ new_sl->etag = state_token;
+
+ new_sl->next = ih->state;
+ ih->state = new_sl;
+
+ return NULL;
+}
+
+/* fetch_next_token returns the substring from str+1
+ * to the next occurrence of char term, or \0, whichever
+ * occurs first. Leading whitespace is ignored.
+ */
+static char *dav_fetch_next_token(char **str, char term)
+{
+ char *sp;
+ char *token;
+
+ token = *str + 1;
+
+ while (*token && (*token == ' ' || *token == '\t'))
+ token++;
+
+ if ((sp = strchr(token, term)) == NULL)
+ return NULL;
+
+ *sp = '\0';
+ *str = sp;
+ return token;
+}
+
+/* dav_process_if_header:
+ *
+ * If NULL (no error) is returned, then **if_header points to the
+ * "If" productions structure (or NULL if "If" is not present).
+ *
+ * ### this part is bogus:
+ * If an error is encountered, the error is logged. Parent should
+ * return err->status.
+ */
+static dav_error * dav_process_if_header(request_rec *r, dav_if_header **p_ih)
+{
+ dav_error *err;
+ char *str;
+ char *list;
+ const char *state_token;
+ const char *uri = NULL; /* scope of current production; NULL=no-tag */
+ apr_size_t uri_len = 0;
+ apr_status_t rv;
+ dav_if_header *ih = NULL;
+ apr_uri_t parsed_uri;
+ const dav_hooks_locks *locks_hooks = DAV_GET_HOOKS_LOCKS(r);
+ enum {no_tagged, tagged, unknown} list_type = unknown;
+ int condition;
+
+ *p_ih = NULL;
+
+ if ((str = apr_pstrdup(r->pool, apr_table_get(r->headers_in, "If"))) == NULL)
+ return NULL;
+
+ while (*str) {
+ switch(*str) {
+ case '<':
+ /* Tagged-list production - following states apply to this uri */
+ if (list_type == no_tagged
+ || ((uri = dav_fetch_next_token(&str, '>')) == NULL)) {
+ return dav_new_error(r->pool, HTTP_BAD_REQUEST,
+ DAV_ERR_IF_TAGGED, 0,
+ "Invalid If-header: unclosed \"<\" or "
+ "unexpected tagged-list production.");
+ }
+
+ /* 2518 specifies this must be an absolute URI; just take the
+ * relative part for later comparison against r->uri */
+ if ((rv = apr_uri_parse(r->pool, uri, &parsed_uri)) != APR_SUCCESS
+ || !parsed_uri.path) {
+ return dav_new_error(r->pool, HTTP_BAD_REQUEST,
+ DAV_ERR_IF_TAGGED, rv,
+ "Invalid URI in tagged If-header.");
+ }
+ /* note that parsed_uri.path is allocated; we can trash it */
+
+ /* clean up the URI a bit */
+ ap_getparents(parsed_uri.path);
+
+ /* the resources we will compare to have unencoded paths */
+ if (ap_unescape_url(parsed_uri.path) != OK) {
+ return dav_new_error(r->pool, HTTP_BAD_REQUEST,
+ DAV_ERR_IF_TAGGED, rv,
+ "Invalid percent encoded URI in "
+ "tagged If-header.");
+ }
+
+ uri_len = strlen(parsed_uri.path);
+ if (uri_len > 1 && parsed_uri.path[uri_len - 1] == '/') {
+ parsed_uri.path[--uri_len] = '\0';
+ }
+
+ uri = parsed_uri.path;
+ list_type = tagged;
+ break;
+
+ case '(':
+ /* List production */
+
+ /* If a uri has not been encountered, this is a No-Tagged-List */
+ if (list_type == unknown)
+ list_type = no_tagged;
+
+ if ((list = dav_fetch_next_token(&str, ')')) == NULL) {
+ return dav_new_error(r->pool, HTTP_BAD_REQUEST,
+ DAV_ERR_IF_UNCLOSED_PAREN, 0,
+ "Invalid If-header: unclosed \"(\".");
+ }
+
+ if ((ih = dav_add_if_resource(r->pool, ih, uri, uri_len)) == NULL) {
+ /* ### dav_add_if_resource() should return an error for us! */
+ return dav_new_error(r->pool, HTTP_BAD_REQUEST,
+ DAV_ERR_IF_PARSE, 0,
+ "Internal server error parsing \"If:\" "
+ "header.");
+ }
+
+ condition = DAV_IF_COND_NORMAL;
+
+ while (*list) {
+ /* List is the entire production (in a uri scope) */
+
+ switch (*list) {
+ case '<':
+ if ((state_token = dav_fetch_next_token(&list, '>')) == NULL) {
+ /* ### add a description to this error */
+ return dav_new_error(r->pool, HTTP_BAD_REQUEST,
+ DAV_ERR_IF_PARSE, 0, NULL);
+ }
+
+ if ((err = dav_add_if_state(r->pool, ih, state_token, dav_if_opaquelock,
+ condition, locks_hooks)) != NULL) {
+ /* ### maybe add a higher level description */
+ return err;
+ }
+ condition = DAV_IF_COND_NORMAL;
+ break;
+
+ case '[':
+ if ((state_token = dav_fetch_next_token(&list, ']')) == NULL) {
+ /* ### add a description to this error */
+ return dav_new_error(r->pool, HTTP_BAD_REQUEST,
+ DAV_ERR_IF_PARSE, 0, NULL);
+ }
+
+ if ((err = dav_add_if_state(r->pool, ih, state_token, dav_if_etag,
+ condition, locks_hooks)) != NULL) {
+ /* ### maybe add a higher level description */
+ return err;
+ }
+ condition = DAV_IF_COND_NORMAL;
+ break;
+
+ case 'N':
+ if (list[1] == 'o' && list[2] == 't') {
+ if (condition != DAV_IF_COND_NORMAL) {
+ return dav_new_error(r->pool, HTTP_BAD_REQUEST,
+ DAV_ERR_IF_MULTIPLE_NOT, 0,
+ "Invalid \"If:\" header: "
+ "Multiple \"not\" entries "
+ "for the same state.");
+ }
+ condition = DAV_IF_COND_NOT;
+ }
+ list += 2;
+ break;
+
+ case ' ':
+ case '\t':
+ break;
+
+ default:
+ return dav_new_error(r->pool, HTTP_BAD_REQUEST,
+ DAV_ERR_IF_UNK_CHAR, 0,
+ apr_psprintf(r->pool,
+ "Invalid \"If:\" "
+ "header: Unexpected "
+ "character encountered "
+ "(0x%02x, '%c').",
+ *list, *list));
+ }
+
+ list++;
+ }
+ break;
+
+ case ' ':
+ case '\t':
+ break;
+
+ default:
+ return dav_new_error(r->pool, HTTP_BAD_REQUEST,
+ DAV_ERR_IF_UNK_CHAR, 0,
+ apr_psprintf(r->pool,
+ "Invalid \"If:\" header: "
+ "Unexpected character "
+ "encountered (0x%02x, '%c').",
+ *str, *str));
+ }
+
+ str++;
+ }
+
+ *p_ih = ih;
+ return NULL;
+}
+
+static int dav_find_submitted_locktoken(const dav_if_header *if_header,
+ const dav_lock *lock_list,
+ const dav_hooks_locks *locks_hooks)
+{
+ for (; if_header != NULL; if_header = if_header->next) {
+ const dav_if_state_list *state_list;
+
+ for (state_list = if_header->state;
+ state_list != NULL;
+ state_list = state_list->next) {
+
+ if (state_list->type == dav_if_opaquelock) {
+ const dav_lock *lock;
+
+ /* given state_list->locktoken, match it */
+
+ /*
+ ** The resource will have one or more lock tokens. We only
+ ** need to match one of them against any token in the
+ ** If: header.
+ **
+ ** One token case: It is an exclusive or shared lock. Either
+ ** way, we must find it.
+ **
+ ** N token case: They are shared locks. By policy, we need
+ ** to match only one. The resource's other
+ ** tokens may belong to somebody else (so we
+ ** shouldn't see them in the If: header anyway)
+ */
+ for (lock = lock_list; lock != NULL; lock = lock->next) {
+
+ if (!(*locks_hooks->compare_locktoken)(state_list->locktoken, lock->locktoken)) {
+ return 1;
+ }
+ }
+ }
+ }
+ }
+
+ return 0;
+}
+
+/* dav_validate_resource_state:
+ * Returns NULL if path/uri meets if-header and lock requirements
+ */
+static dav_error * dav_validate_resource_state(apr_pool_t *p,
+ const dav_resource *resource,
+ dav_lockdb *lockdb,
+ const dav_if_header *if_header,
+ int flags,
+ dav_buffer *pbuf,
+ request_rec *r)
+{
+ dav_error *err;
+ const char *uri;
+ const char *etag;
+ const dav_hooks_locks *locks_hooks = (lockdb ? lockdb->hooks : NULL);
+ const dav_if_header *ifhdr_scan;
+ dav_if_state_list *state_list;
+ dav_lock *lock_list;
+ dav_lock *lock;
+ int num_matched;
+ int num_that_apply;
+ int seen_locktoken;
+ apr_size_t uri_len;
+ const char *reason = NULL;
+
+ /* DBG1("validate: <%s>", resource->uri); */
+
+ /*
+ ** The resource will have one of three states:
+ **
+ ** 1) No locks. We have no special requirements that the user supply
+ ** specific locktokens. One of the state lists must match, and
+ ** we're done.
+ **
+ ** 2) One exclusive lock. The locktoken must appear *anywhere* in the
+ ** If: header. Of course, asserting the token in a "Not" term will
+ ** quickly fail that state list :-). If the locktoken appears in
+ ** one of the state lists *and* one state list matches, then we're
+ ** done.
+ **
+ ** 3) One or more shared locks. One of the locktokens must appear
+ ** *anywhere* in the If: header. If one of the locktokens appears,
+ ** and we match one state list, then we are done.
+ **
+ ** The <seen_locktoken> variable determines whether we have seen one
+ ** of this resource's locktokens in the If: header.
+ */
+
+ /*
+ ** If this is a new lock request, <flags> will contain the requested
+ ** lock scope. Three rules apply:
+ **
+ ** 1) Do not require a (shared) locktoken to be seen (when we are
+ ** applying another shared lock)
+ ** 2) If the scope is exclusive and we see any locks, fail.
+ ** 3) If the scope is shared and we see an exclusive lock, fail.
+ */
+
+ if (lockdb == NULL) {
+ /* we're in State 1. no locks. */
+ lock_list = NULL;
+ }
+ else {
+ /*
+ ** ### hrm... we don't need to have these fully
+ ** ### resolved since we're only looking at the
+ ** ### locktokens...
+ **
+ ** ### use get_locks w/ calltype=PARTIAL
+ */
+ if ((err = dav_lock_query(lockdb, resource, &lock_list)) != NULL) {
+ return dav_push_error(p,
+ HTTP_INTERNAL_SERVER_ERROR, 0,
+ "The locks could not be queried for "
+ "verification against a possible \"If:\" "
+ "header.",
+ err);
+ }
+
+ /* lock_list now determines whether we're in State 1, 2, or 3. */
+ }
+
+ /*
+ ** For a new, exclusive lock: if any locks exist, fail.
+ ** For a new, shared lock: if an exclusive lock exists, fail.
+ ** else, do not require a token to be seen.
+ */
+ if (flags & DAV_LOCKSCOPE_EXCLUSIVE) {
+ if (lock_list != NULL) {
+ return dav_new_error(p, HTTP_LOCKED, 0, 0,
+ "Existing lock(s) on the requested resource "
+ "prevent an exclusive lock.");
+ }
+
+ /*
+ ** There are no locks, so we can pretend that we've already met
+ ** any requirement to find the resource's locks in an If: header.
+ */
+ seen_locktoken = 1;
+ }
+ else if (flags & DAV_LOCKSCOPE_SHARED) {
+ /*
+ ** Strictly speaking, we don't need this loop. Either the first
+ ** (and only) lock will be EXCLUSIVE, or none of them will be.
+ */
+ for (lock = lock_list; lock != NULL; lock = lock->next) {
+ if (lock->scope == DAV_LOCKSCOPE_EXCLUSIVE) {
+ return dav_new_error(p, HTTP_LOCKED, 0, 0,
+ "The requested resource is already "
+ "locked exclusively.");
+ }
+ }
+
+ /*
+ ** The locks on the resource (if any) are all shared. Set the
+ ** <seen_locktoken> flag to indicate that we do not need to find
+ ** the locks in an If: header.
+ */
+ seen_locktoken = 1;
+ }
+ else {
+ /*
+ ** For methods other than LOCK:
+ **
+ ** If we have no locks or if the resource is not being modified
+ ** (per RFC 4918 the lock token is not required on resources
+ ** we are not changing), then <seen_locktoken> can be set to true --
+ ** pretending that we've already met the requirement of seeing one
+ ** of the resource's locks in the If: header.
+ **
+ ** Otherwise, it must be cleared and we'll look for one.
+ */
+ seen_locktoken = (lock_list == NULL
+ || flags & DAV_VALIDATE_NO_MODIFY);
+ }
+
+ /*
+ ** If there is no If: header, then we can shortcut some logic:
+ **
+ ** 1) if we do not need to find a locktoken in the (non-existent) If:
+ ** header, then we are successful.
+ **
+ ** 2) if we must find a locktoken in the (non-existent) If: header, then
+ ** we fail.
+ */
+ if (if_header == NULL) {
+ if (seen_locktoken)
+ return NULL;
+
+ return dav_new_error(p, HTTP_LOCKED, 0, 0,
+ "This resource is locked and an \"If:\" header "
+ "was not supplied to allow access to the "
+ "resource.");
+ }
+ /* the If: header is present */
+
+ /*
+ ** If a dummy header is present (because of a Lock-Token: header), then
+ ** we are required to find that token in this resource's set of locks.
+ ** If we have no locks, then we immediately fail.
+ **
+ ** This is a 400 (Bad Request) since they should only submit a locktoken
+ ** that actually exists.
+ **
+ ** Don't issue this response if we're talking about the parent resource.
+ ** It is okay for that resource to NOT have this locktoken.
+ ** (in fact, it certainly will not: a dummy_header only occurs for the
+ ** UNLOCK method, the parent is checked only for locknull resources,
+ ** and the parent certainly does not have the (locknull's) locktoken)
+ */
+ if (lock_list == NULL && if_header->dummy_header) {
+ if (flags & DAV_VALIDATE_IS_PARENT)
+ return NULL;
+ return dav_new_error(p, HTTP_BAD_REQUEST, 0, 0,
+ "The locktoken specified in the \"Lock-Token:\" "
+ "header is invalid because this resource has no "
+ "outstanding locks.");
+ }
+
+ /*
+ ** Prepare the input URI. We want the URI to never have a trailing slash.
+ **
+ ** When URIs are placed into the dav_if_header structure, they are
+ ** guaranteed to never have a trailing slash. If the URIs are equivalent,
+ ** then it doesn't matter if they both lack a trailing slash -- they're
+ ** still equivalent.
+ **
+ ** Note: we could also ensure that a trailing slash is present on both
+ ** URIs, but the majority of URIs provided to us via a resource walk
+ ** will not contain that trailing slash.
+ */
+ uri = resource->uri;
+ uri_len = strlen(uri);
+ if (uri[uri_len - 1] == '/') {
+ dav_set_bufsize(p, pbuf, uri_len);
+ memcpy(pbuf->buf, uri, uri_len);
+ pbuf->buf[--uri_len] = '\0';
+ uri = pbuf->buf;
+ }
+
+ /* get the resource's etag; we may need it during the checks */
+ etag = (*resource->hooks->getetag)(resource);
+
+ /* how many state_lists apply to this URI? */
+ num_that_apply = 0;
+
+ /* If there are if-headers, fail if this resource
+ * does not match at least one state_list.
+ */
+ for (ifhdr_scan = if_header;
+ ifhdr_scan != NULL;
+ ifhdr_scan = ifhdr_scan->next) {
+
+ /* DBG2("uri=<%s> if_uri=<%s>", uri, ifhdr_scan->uri ? ifhdr_scan->uri : "(no uri)"); */
+
+ if (ifhdr_scan->uri != NULL
+ && (uri_len != ifhdr_scan->uri_len
+ || memcmp(uri, ifhdr_scan->uri, uri_len) != 0)) {
+ /*
+ ** A tagged-list's URI doesn't match this resource's URI.
+ ** Skip to the next state_list to see if it will match.
+ */
+ continue;
+ }
+
+ /* this state_list applies to this resource */
+
+ /*
+ ** ### only one state_list should ever apply! a no-tag, or a tagged
+ ** ### where S9.4.2 states only one can match.
+ **
+ ** ### revamp this code to loop thru ifhdr_scan until we find the
+ ** ### matching state_list. process it. stop.
+ */
+ ++num_that_apply;
+
+ /* To succeed, resource must match *all* of the states
+ * specified in the state_list.
+ */
+ for (state_list = ifhdr_scan->state;
+ state_list != NULL;
+ state_list = state_list->next) {
+
+ switch(state_list->type) {
+ case dav_if_etag:
+ {
+ const char *given_etag, *current_etag;
+ int mismatch;
+
+ /* Do a weak entity comparison function as defined in
+ * RFC 2616 13.3.3.
+ */
+ if (state_list->etag[0] == 'W' &&
+ state_list->etag[1] == '/') {
+ given_etag = state_list->etag + 2;
+ }
+ else {
+ given_etag = state_list->etag;
+ }
+ if (etag[0] == 'W' &&
+ etag[1] == '/') {
+ current_etag = etag + 2;
+ }
+ else {
+ current_etag = etag;
+ }
+
+ mismatch = strcmp(given_etag, current_etag);
+
+ if (state_list->condition == DAV_IF_COND_NORMAL && mismatch) {
+ /*
+ ** The specified entity-tag does not match the
+ ** entity-tag on the resource. This state_list is
+ ** not going to match. Bust outta here.
+ */
+ reason =
+ "an entity-tag was specified, but the resource's "
+ "actual ETag does not match.";
+ goto state_list_failed;
+ }
+ else if (state_list->condition == DAV_IF_COND_NOT
+ && !mismatch) {
+ /*
+ ** The specified entity-tag DOES match the
+ ** entity-tag on the resource. This state_list is
+ ** not going to match. Bust outta here.
+ */
+ reason =
+ "an entity-tag was specified using the \"Not\" form, "
+ "but the resource's actual ETag matches the provided "
+ "entity-tag.";
+ goto state_list_failed;
+ }
+ break;
+ }
+
+ case dav_if_opaquelock:
+ if (lockdb == NULL) {
+ if (state_list->condition == DAV_IF_COND_NOT) {
+ /* the locktoken is definitely not there! (success) */
+ continue;
+ }
+
+ /* condition == DAV_IF_COND_NORMAL */
+
+ /*
+ ** If no lockdb is provided, then validation fails for
+ ** this state_list (NORMAL means we were supposed to
+ ** find the token, which we obviously cannot do without
+ ** a lock database).
+ **
+ ** Go and try the next state list.
+ */
+ reason =
+ "a State-token was supplied, but a lock database "
+ "is not available for to provide the required lock.";
+ goto state_list_failed;
+ }
+
+ /* Resource validation 'fails' if:
+ * ANY of the lock->locktokens match
+ * a NOT state_list->locktoken,
+ * OR
+ * NONE of the lock->locktokens match
+ * a NORMAL state_list->locktoken.
+ */
+ num_matched = 0;
+ for (lock = lock_list; lock != NULL; lock = lock->next) {
+
+ /*
+ DBG2("compare: rsrc=%s ifhdr=%s",
+ (*locks_hooks->format_locktoken)(p, lock->locktoken),
+ (*locks_hooks->format_locktoken)(p, state_list->locktoken));
+ */
+
+ /* nothing to do if the locktokens do not match. */
+ if ((*locks_hooks->compare_locktoken)(state_list->locktoken, lock->locktoken)) {
+ continue;
+ }
+
+ /*
+ ** We have now matched up one of the resource's locktokens
+ ** to a locktoken in a State-token in the If: header.
+ ** Note this fact, so that we can pass the overall
+ ** requirement of seeing at least one of the resource's
+ ** locktokens.
+ */
+ seen_locktoken = 1;
+
+ if (state_list->condition == DAV_IF_COND_NOT) {
+ /*
+ ** This state requires that the specified locktoken
+ ** is NOT present on the resource. But we just found
+ ** it. There is no way this state-list can now
+ ** succeed, so go try another one.
+ */
+ reason =
+ "a State-token was supplied, which used a "
+ "\"Not\" condition. The State-token was found "
+ "in the locks on this resource";
+ goto state_list_failed;
+ }
+
+ /* condition == DAV_IF_COND_NORMAL */
+
+ /* Validate auth_user: If an authenticated user created
+ ** the lock, only the same user may submit that locktoken
+ ** to manipulate a resource.
+ */
+ if (lock->auth_user &&
+ (!r->user ||
+ strcmp(lock->auth_user, r->user))) {
+ const char *errmsg;
+
+ errmsg = apr_pstrcat(p, "User \"",
+ r->user,
+ "\" submitted a locktoken created "
+ "by user \"",
+ lock->auth_user, "\".", NULL);
+ return dav_new_error(p, HTTP_FORBIDDEN, 0, 0, errmsg);
+ }
+
+ /*
+ ** We just matched a specified State-Token to one of the
+ ** resource's locktokens.
+ **
+ ** Break out of the lock scan -- we only needed to find
+ ** one match (actually, there shouldn't be any other
+ ** matches in the lock list).
+ */
+ num_matched = 1;
+ break;
+ }
+
+ if (num_matched == 0
+ && state_list->condition == DAV_IF_COND_NORMAL) {
+ /*
+ ** We had a NORMAL state, meaning that we should have
+ ** found the State-Token within the locks on this
+ ** resource. We didn't, so this state_list must fail.
+ */
+ reason =
+ "a State-token was supplied, but it was not found "
+ "in the locks on this resource.";
+ goto state_list_failed;
+ }
+
+ break;
+
+ case dav_if_unknown:
+ /* Request is predicated on some unknown state token,
+ * which must be presumed to *not* match, so fail
+ * unless this is a Not condition. */
+
+ if (state_list->condition == DAV_IF_COND_NORMAL) {
+ reason =
+ "an unknown state token was supplied";
+ goto state_list_failed;
+ }
+ break;
+
+ } /* switch */
+ } /* foreach ( state_list ) */
+
+ /*
+ ** We've checked every state in this state_list and none of them
+ ** have failed. Since all of them succeeded, then we have a matching
+ ** state list and we may be done.
+ **
+ ** The next requirement is that we have seen one of the resource's
+ ** locktokens (if any). If we have, then we can just exit. If we
+ ** haven't, then we need to keep looking.
+ */
+ if (seen_locktoken) {
+ /* woo hoo! */
+ return NULL;
+ }
+
+ /*
+ ** Haven't seen one. Let's break out of the search and just look
+ ** for a matching locktoken.
+ */
+ break;
+
+ /*
+ ** This label is used when we detect that a state_list is not
+ ** going to match this resource. We bust out and try the next
+ ** state_list.
+ */
+ state_list_failed:
+ ;
+
+ } /* foreach ( ifhdr_scan ) */
+
+ /*
+ ** The above loop exits for one of two reasons:
+ ** 1) a state_list matched and seen_locktoken is false.
+ ** 2) all if_header structures were scanned, without (1) occurring
+ */
+
+ if (ifhdr_scan == NULL) {
+ /*
+ ** We finished the loop without finding any matching state lists.
+ */
+
+ /*
+ ** If none of the state_lists apply to this resource, then we
+ ** may have succeeded. Note that this scenario implies a
+ ** tagged-list with no matching state_lists. If the If: header
+ ** was a no-tag-list, then it would have applied to this resource.
+ **
+ ** S9.4.2 states that when no state_lists apply, then the header
+ ** should be ignored.
+ **
+ ** If we saw one of the resource's locktokens, then we're done.
+ ** If we did not see a locktoken, then we fail.
+ */
+ if (num_that_apply == 0) {
+ if (seen_locktoken)
+ return NULL;
+
+ /*
+ ** We may have aborted the scan before seeing the locktoken.
+ ** Rescan the If: header to see if we can find the locktoken
+ ** somewhere.
+ **
+ ** Note that seen_locktoken == 0 implies lock_list != NULL
+ ** which implies locks_hooks != NULL.
+ */
+ if (dav_find_submitted_locktoken(if_header, lock_list,
+ locks_hooks)) {
+ /*
+ ** We found a match! We're set... none of the If: header
+ ** assertions apply (implicit success), and the If: header
+ ** specified the locktoken somewhere. We're done.
+ */
+ return NULL;
+ }
+
+ return dav_new_error(p, HTTP_LOCKED, 0 /* error_id */, 0,
+ "This resource is locked and the \"If:\" "
+ "header did not specify one of the "
+ "locktokens for this resource's lock(s).");
+ }
+ /* else: one or more state_lists were applicable, but failed. */
+
+ /*
+ ** If the dummy_header did not match, then they specified an
+ ** incorrect token in the Lock-Token header. Forget whether the
+ ** If: statement matched or not... we'll tell them about the
+ ** bad Lock-Token first. That is considered a 400 (Bad Request).
+ */
+ if (if_header->dummy_header) {
+ return dav_new_error(p, HTTP_BAD_REQUEST, 0, 0,
+ "The locktoken specified in the "
+ "\"Lock-Token:\" header did not specify one "
+ "of this resource's locktoken(s).");
+ }
+
+ if (reason == NULL) {
+ return dav_new_error(p, HTTP_PRECONDITION_FAILED, 0, 0,
+ "The preconditions specified by the \"If:\" "
+ "header did not match this resource.");
+ }
+
+ return dav_new_error(p, HTTP_PRECONDITION_FAILED, 0, 0,
+ apr_psprintf(p,
+ "The precondition(s) specified by "
+ "the \"If:\" header did not match "
+ "this resource. At least one "
+ "failure is because: %s", reason));
+ }
+
+ /* assert seen_locktoken == 0 */
+
+ /*
+ ** ifhdr_scan != NULL implies we found a matching state_list.
+ **
+ ** Since we're still here, it also means that we have not yet found
+ ** one the resource's locktokens in the If: header.
+ **
+ ** Scan all the if_headers and states looking for one of this
+ ** resource's locktokens. Note that we need to go back and scan them
+ ** all -- we may have aborted a scan with a failure before we saw a
+ ** matching token.
+ **
+ ** Note that seen_locktoken == 0 implies lock_list != NULL which implies
+ ** locks_hooks != NULL.
+ */
+ if (dav_find_submitted_locktoken(if_header, lock_list, locks_hooks)) {
+ /*
+ ** We found a match! We're set... we have a matching state list,
+ ** and the If: header specified the locktoken somewhere. We're done.
+ */
+ return NULL;
+ }
+
+ /*
+ ** We had a matching state list, but the user agent did not specify one
+ ** of this resource's locktokens. Tell them so.
+ **
+ ** Note that we need to special-case the message on whether a "dummy"
+ ** header exists. If it exists, yet we didn't see a needed locktoken,
+ ** then that implies the dummy header (Lock-Token header) did NOT
+ ** specify one of this resource's locktokens. (this implies something
+ ** in the real If: header matched)
+ **
+ ** We want to note the 400 (Bad Request) in favor of a 423 (Locked).
+ */
+ if (if_header->dummy_header) {
+ return dav_new_error(p, HTTP_BAD_REQUEST, 0, 0,
+ "The locktoken specified in the "
+ "\"Lock-Token:\" header did not specify one "
+ "of this resource's locktoken(s).");
+ }
+
+ return dav_new_error(p, HTTP_LOCKED, 1 /* error_id */, 0,
+ "This resource is locked and the \"If:\" header "
+ "did not specify one of the "
+ "locktokens for this resource's lock(s).");
+}
+
+/* dav_validate_walker: Walker callback function to validate resource state */
+static dav_error * dav_validate_walker(dav_walk_resource *wres, int calltype)
+{
+ dav_walker_ctx *ctx = wres->walk_ctx;
+ dav_error *err;
+
+ if ((err = dav_validate_resource_state(ctx->w.pool, wres->resource,
+ ctx->w.lockdb,
+ ctx->if_header, ctx->flags,
+ &ctx->work_buf, ctx->r)) == NULL) {
+ /* There was no error, so just bug out. */
+ return NULL;
+ }
+
+ /*
+ ** If we have a serious server error, or if the request itself failed,
+ ** then just return error (not a multistatus).
+ */
+ if (ap_is_HTTP_SERVER_ERROR(err->status)
+ || (*wres->resource->hooks->is_same_resource)(wres->resource,
+ ctx->w.root)) {
+ /* ### maybe push a higher-level description? */
+ return err;
+ }
+
+ /* associate the error with the current URI */
+ dav_add_response(wres, err->status, NULL);
+
+ return NULL;
+}
+
+/* If-* header checking */
+static int dav_meets_conditions(request_rec *r, int resource_state)
+{
+ const char *if_match, *if_none_match;
+ int retVal;
+
+ /* If-Match '*' fix. Resource existence not checked by ap_meets_conditions.
+ * If-Match '*' request should succeed only if the resource exists. */
+ if ((if_match = apr_table_get(r->headers_in, "If-Match")) != NULL) {
+ if (if_match[0] == '*' && resource_state != DAV_RESOURCE_EXISTS)
+ return HTTP_PRECONDITION_FAILED;
+ }
+
+ retVal = ap_meets_conditions(r);
+
+ /* If-None-Match '*' fix. If-None-Match '*' request should succeed
+ * if the resource does not exist. */
+ if (retVal == HTTP_PRECONDITION_FAILED) {
+ /* Note. If if_none_match != NULL, if_none_match is the culprit.
+ * Since, in presence of If-None-Match,
+ * other If-* headers are undefined. */
+ if ((if_none_match =
+ apr_table_get(r->headers_in, "If-None-Match")) != NULL) {
+ if (if_none_match[0] == '*'
+ && resource_state != DAV_RESOURCE_EXISTS) {
+ return OK;
+ }
+ }
+ }
+
+ return retVal;
+}
+
+/*
+** dav_validate_request: Validate if-headers (and check for locks) on:
+** (1) r->filename @ depth;
+** (2) Parent of r->filename if check_parent == 1
+**
+** The check of parent should be done when it is necessary to verify that
+** the parent collection will accept a new member (ie current resource
+** state is null).
+**
+** Return OK on successful validation.
+** On error, return appropriate HTTP_* code, and log error. If a multi-stat
+** error is necessary, response will point to it, else NULL.
+*/
+DAV_DECLARE(dav_error *) dav_validate_request(request_rec *r,
+ dav_resource *resource,
+ int depth,
+ dav_locktoken *locktoken,
+ dav_response **response,
+ int flags,
+ dav_lockdb *lockdb)
+{
+ dav_error *err;
+ int result;
+ dav_if_header *if_header;
+ int lock_db_opened_locally = 0;
+ const dav_hooks_locks *locks_hooks = DAV_GET_HOOKS_LOCKS(r);
+ const dav_hooks_repository *repos_hooks = resource->hooks;
+ dav_buffer work_buf = { 0 };
+ dav_response *new_response;
+ int resource_state;
+ const char *etag;
+ int set_etag = 0;
+
+#if DAV_DEBUG
+ if (depth && response == NULL) {
+ /*
+ ** ### bleck. we can't return errors for other URIs unless we have
+ ** ### a "response" ptr.
+ */
+ return dav_new_error(r->pool, HTTP_INTERNAL_SERVER_ERROR, 0, 0,
+ "DESIGN ERROR: dav_validate_request called "
+ "with depth>0, but no response ptr.");
+ }
+#endif
+
+ if (response != NULL)
+ *response = NULL;
+
+ /* Set the ETag header required by dav_meets_conditions() */
+ etag = apr_table_get(r->headers_out, "ETag");
+ if (!etag) {
+ etag = (*resource->hooks->getetag)(resource);
+ if (etag && *etag) {
+ apr_table_set(r->headers_out, "ETag", etag);
+ set_etag = 1;
+ }
+ }
+ /* Do the standard checks for conditional requests using
+ * If-..-Since, If-Match etc */
+ resource_state = dav_get_resource_state(r, resource);
+ result = dav_meets_conditions(r, resource_state);
+ if (set_etag) {
+ /*
+ * If we have set an ETag to headers out above for
+ * dav_meets_conditions() revert this here as we do not want to set
+ * the ETag in responses to requests with methods where this might not
+ * be desired.
+ */
+ apr_table_unset(r->headers_out, "ETag");
+ }
+ if (result != OK) {
+ return dav_new_error(r->pool, result, 0, 0, NULL);
+ }
+
+ /* always parse (and later process) the If: header */
+ if ((err = dav_process_if_header(r, &if_header)) != NULL) {
+ /* ### maybe add higher-level description */
+ return err;
+ }
+
+ /* If a locktoken was specified, create a dummy if_header with which
+ * to validate resources. In the interim, figure out why DAV uses
+ * locktokens in an if-header without a Lock-Token header to refresh
+ * locks, but a Lock-Token header without an if-header to remove them.
+ */
+ if (locktoken != NULL) {
+ dav_if_header *ifhdr_new;
+
+ ifhdr_new = apr_pcalloc(r->pool, sizeof(*ifhdr_new));
+ ifhdr_new->uri = resource->uri;
+ ifhdr_new->uri_len = strlen(resource->uri);
+ ifhdr_new->dummy_header = 1;
+
+ ifhdr_new->state = apr_pcalloc(r->pool, sizeof(*ifhdr_new->state));
+ ifhdr_new->state->type = dav_if_opaquelock;
+ ifhdr_new->state->condition = DAV_IF_COND_NORMAL;
+ ifhdr_new->state->locktoken = locktoken;
+
+ ifhdr_new->next = if_header;
+ if_header = ifhdr_new;
+ }
+
+ /*
+ ** If necessary, open the lock database (read-only, lazily);
+ ** the validation process may need to retrieve or update lock info.
+ ** Otherwise, assume provided lockdb is valid and opened rw.
+ */
+ if (lockdb == NULL) {
+ if (locks_hooks != NULL) {
+ if ((err = (*locks_hooks->open_lockdb)(r, 0, 0, &lockdb)) != NULL) {
+ /* ### maybe insert higher-level comment */
+ return err;
+ }
+ lock_db_opened_locally = 1;
+ }
+ }
+
+ /* (1) Validate the specified resource, at the specified depth.
+ * Avoid the walk there is no if_header and we aren't planning
+ * to modify this resource. */
+ if (resource->exists && depth > 0 && !(!if_header && flags & DAV_VALIDATE_NO_MODIFY)) {
+ dav_walker_ctx ctx = { { 0 } };
+ dav_response *multi_status;
+
+ ctx.w.walk_type = DAV_WALKTYPE_NORMAL;
+ ctx.w.func = dav_validate_walker;
+ ctx.w.walk_ctx = &ctx;
+ ctx.w.pool = r->pool;
+ ctx.w.root = resource;
+
+ ctx.if_header = if_header;
+ ctx.r = r;
+ ctx.flags = flags;
+
+ if (lockdb != NULL) {
+ ctx.w.lockdb = lockdb;
+ ctx.w.walk_type |= DAV_WALKTYPE_LOCKNULL;
+ }
+
+ err = (*repos_hooks->walk)(&ctx.w, DAV_INFINITY, &multi_status);
+ if (err == NULL) {
+ *response = multi_status;
+ }
+ /* else: implies a 5xx status code occurred. */
+ }
+ else {
+ err = dav_validate_resource_state(r->pool, resource, lockdb,
+ if_header, flags, &work_buf, r);
+ }
+
+ /* (2) Validate the parent resource if requested */
+ if (err == NULL && (flags & DAV_VALIDATE_PARENT)) {
+ dav_resource *parent_resource;
+
+ err = (*repos_hooks->get_parent_resource)(resource, &parent_resource);
+
+ if (err == NULL && parent_resource == NULL) {
+ err = dav_new_error(r->pool, HTTP_FORBIDDEN, 0, 0,
+ "Cannot access parent of repository root.");
+ }
+ else if (err == NULL) {
+ err = dav_validate_resource_state(r->pool, parent_resource, lockdb,
+ if_header,
+ flags | DAV_VALIDATE_IS_PARENT,
+ &work_buf, r);
+
+ /*
+ ** This error occurred on the parent resource. This implies that
+ ** we have to create a multistatus response (to report the error
+ ** against a URI other than the Request-URI). "Convert" this error
+ ** into a multistatus response.
+ */
+ if (err != NULL) {
+ new_response = apr_pcalloc(r->pool, sizeof(*new_response));
+
+ new_response->href = parent_resource->uri;
+ new_response->status = err->status;
+ new_response->desc =
+ "A validation error has occurred on the parent resource, "
+ "preventing the operation on the resource specified by "
+ "the Request-URI.";
+ if (err->desc != NULL) {
+ new_response->desc = apr_pstrcat(r->pool,
+ new_response->desc,
+ " The error was: ",
+ err->desc, NULL);
+ }
+
+ /* assert: DAV_VALIDATE_PARENT implies response != NULL */
+ new_response->next = *response;
+ *response = new_response;
+
+ err = NULL;
+ }
+ }
+ }
+
+ if (lock_db_opened_locally)
+ (*locks_hooks->close_lockdb)(lockdb);
+
+ /*
+ ** If we don't have a (serious) error, and we have multistatus responses,
+ ** then we need to construct an "error". This error will be the overall
+ ** status returned, and the multistatus responses will go into its body.
+ **
+ ** For certain methods, the overall error will be a 424. The default is
+ ** to construct a standard 207 response.
+ */
+ if (err == NULL && response != NULL && *response != NULL) {
+ apr_text *propstat = NULL;
+
+ if ((flags & DAV_VALIDATE_USE_424) != 0) {
+ /* manufacture a 424 error to hold the multistatus response(s) */
+ return dav_new_error(r->pool, HTTP_FAILED_DEPENDENCY, 0, 0,
+ "An error occurred on another resource, "
+ "preventing the requested operation on "
+ "this resource.");
+ }
+
+ /*
+ ** Whatever caused the error, the Request-URI should have a 424
+ ** associated with it since we cannot complete the method.
+ **
+ ** For a LOCK operation, insert an empty DAV:lockdiscovery property.
+ ** For other methods, return a simple 424.
+ */
+ if ((flags & DAV_VALIDATE_ADD_LD) != 0) {
+ propstat = apr_pcalloc(r->pool, sizeof(*propstat));
+ propstat->text =
+ "<D:propstat>" DEBUG_CR
+ "<D:prop><D:lockdiscovery/></D:prop>" DEBUG_CR
+ "<D:status>HTTP/1.1 424 Failed Dependency</D:status>" DEBUG_CR
+ "</D:propstat>" DEBUG_CR;
+ }
+
+ /* create the 424 response */
+ new_response = apr_pcalloc(r->pool, sizeof(*new_response));
+ new_response->href = resource->uri;
+ new_response->status = HTTP_FAILED_DEPENDENCY;
+ new_response->propresult.propstats = propstat;
+ new_response->desc =
+ "An error occurred on another resource, preventing the "
+ "requested operation on this resource.";
+
+ new_response->next = *response;
+ *response = new_response;
+
+ /* manufacture a 207 error for the multistatus response(s) */
+ return dav_new_error(r->pool, HTTP_MULTI_STATUS, 0, 0,
+ "Error(s) occurred on resources during the "
+ "validation process.");
+ }
+
+ return err;
+}
+
+/* dav_get_locktoken_list:
+ *
+ * Sets ltl to a locktoken_list of all positive locktokens in header,
+ * else NULL if no If-header, or no positive locktokens.
+ */
+DAV_DECLARE(dav_error *) dav_get_locktoken_list(request_rec *r,
+ dav_locktoken_list **ltl)
+{
+ dav_error *err;
+ dav_if_header *if_header;
+ dav_if_state_list *if_state;
+ dav_locktoken_list *lock_token = NULL;
+
+ *ltl = NULL;
+
+ if ((err = dav_process_if_header(r, &if_header)) != NULL) {
+ /* ### add a higher-level description? */
+ return err;
+ }
+
+ while (if_header != NULL) {
+ if_state = if_header->state; /* Beginning of the if_state linked list */
+ while (if_state != NULL) {
+ if (if_state->condition == DAV_IF_COND_NORMAL
+ && if_state->type == dav_if_opaquelock) {
+ lock_token = apr_pcalloc(r->pool, sizeof(dav_locktoken_list));
+ lock_token->locktoken = if_state->locktoken;
+ lock_token->next = *ltl;
+ *ltl = lock_token;
+ }
+ if_state = if_state->next;
+ }
+ if_header = if_header->next;
+ }
+ if (*ltl == NULL) {
+ /* No nodes added */
+ return dav_new_error(r->pool, HTTP_BAD_REQUEST, DAV_ERR_IF_ABSENT, 0,
+ "No locktokens were specified in the \"If:\" "
+ "header, so the refresh could not be performed.");
+ }
+
+ return NULL;
+}
+
+#if 0 /* not needed right now... */
+
+static const char *strip_white(const char *s, apr_pool_t *pool)
+{
+ apr_size_t idx;
+
+ /* trim leading whitespace */
+ while (apr_isspace(*s)) /* assume: return false for '\0' */
+ ++s;
+
+ /* trim trailing whitespace */
+ idx = strlen(s) - 1;
+ if (apr_isspace(s[idx])) {
+ char *s2 = apr_pstrdup(pool, s);
+
+ while (apr_isspace(s2[idx]) && idx > 0)
+ --idx;
+ s2[idx + 1] = '\0';
+ return s2;
+ }
+
+ return s;
+}
+#endif
+
+#define DAV_LABEL_HDR "Label"
+
+/* dav_add_vary_header
+ *
+ * If there were any headers in the request which require a Vary header
+ * in the response, add it.
+ */
+DAV_DECLARE(void) dav_add_vary_header(request_rec *in_req,
+ request_rec *out_req,
+ const dav_resource *resource)
+{
+ const dav_hooks_vsn *vsn_hooks = DAV_GET_HOOKS_VSN(in_req);
+
+ /* ### this is probably all wrong... I think there is a function in
+ ### the Apache API to add things to the Vary header. need to check */
+
+ /* Only versioning headers require a Vary response header,
+ * so only do this check if there is a versioning provider */
+ if (vsn_hooks != NULL) {
+ const char *target = apr_table_get(in_req->headers_in, DAV_LABEL_HDR);
+
+ /* If Target-Selector specified, add it to the Vary header */
+ if (target != NULL) {
+ const char *vary = apr_table_get(out_req->headers_out, "Vary");
+
+ if (vary == NULL)
+ vary = DAV_LABEL_HDR;
+ else
+ vary = apr_pstrcat(out_req->pool, vary, "," DAV_LABEL_HDR,
+ NULL);
+
+ apr_table_setn(out_req->headers_out, "Vary", vary);
+ }
+ }
+}
+
+/* dav_can_auto_checkout
+ *
+ * Determine whether auto-checkout is enabled for a resource.
+ * r - the request_rec
+ * resource - the resource
+ * auto_version - the value of the auto_versionable hook for the resource
+ * lockdb - pointer to lock database (opened if necessary)
+ * auto_checkout - set to 1 if auto-checkout enabled
+ */
+static dav_error * dav_can_auto_checkout(
+ request_rec *r,
+ dav_resource *resource,
+ dav_auto_version auto_version,
+ dav_lockdb **lockdb,
+ int *auto_checkout)
+{
+ dav_error *err;
+ dav_lock *lock_list;
+
+ *auto_checkout = 0;
+
+ if (auto_version == DAV_AUTO_VERSION_ALWAYS) {
+ *auto_checkout = 1;
+ }
+ else if (auto_version == DAV_AUTO_VERSION_LOCKED) {
+ if (*lockdb == NULL) {
+ const dav_hooks_locks *locks_hooks = DAV_GET_HOOKS_LOCKS(r);
+
+ if (locks_hooks == NULL) {
+ return dav_new_error(r->pool, HTTP_INTERNAL_SERVER_ERROR, 0, 0,
+ "Auto-checkout is only enabled for locked resources, "
+ "but there is no lock provider.");
+ }
+
+ if ((err = (*locks_hooks->open_lockdb)(r, 0, 0, lockdb)) != NULL) {
+ return dav_push_error(r->pool, HTTP_INTERNAL_SERVER_ERROR, 0,
+ "Cannot open lock database to determine "
+ "auto-versioning behavior.",
+ err);
+ }
+ }
+
+ if ((err = dav_lock_query(*lockdb, resource, &lock_list)) != NULL) {
+ return dav_push_error(r->pool,
+ HTTP_INTERNAL_SERVER_ERROR, 0,
+ "The locks could not be queried for "
+ "determining auto-versioning behavior.",
+ err);
+ }
+
+ if (lock_list != NULL)
+ *auto_checkout = 1;
+ }
+
+ return NULL;
+}
+
+/* see mod_dav.h for docco */
+DAV_DECLARE(dav_error *) dav_auto_checkout(
+ request_rec *r,
+ dav_resource *resource,
+ int parent_only,
+ dav_auto_version_info *av_info)
+{
+ const dav_hooks_vsn *vsn_hooks = DAV_GET_HOOKS_VSN(r);
+ dav_lockdb *lockdb = NULL;
+ dav_error *err = NULL;
+
+ /* Initialize results */
+ memset(av_info, 0, sizeof(*av_info));
+
+ /* if no versioning provider, just return */
+ if (vsn_hooks == NULL)
+ return NULL;
+
+ /* check parent resource if requested or if resource must be created */
+ if (!resource->exists || parent_only) {
+ dav_resource *parent;
+
+ if ((err = (*resource->hooks->get_parent_resource)(resource,
+ &parent)) != NULL)
+ goto done;
+
+ if (parent == NULL || !parent->exists) {
+ err = dav_new_error(r->pool, HTTP_CONFLICT, 0, 0,
+ apr_psprintf(r->pool,
+ "Missing one or more intermediate "
+ "collections. Cannot create resource %s.",
+ ap_escape_html(r->pool, resource->uri)));
+ goto done;
+ }
+
+ av_info->parent_resource = parent;
+
+ /* if parent versioned and not checked out, see if it can be */
+ if (parent->versioned && !parent->working) {
+ int checkout_parent;
+
+ if ((err = dav_can_auto_checkout(r, parent,
+ (*vsn_hooks->auto_versionable)(parent),
+ &lockdb, &checkout_parent))
+ != NULL) {
+ goto done;
+ }
+
+ if (!checkout_parent) {
+ err = dav_new_error(r->pool, HTTP_CONFLICT, 0, 0,
+ "<DAV:cannot-modify-checked-in-parent>");
+ goto done;
+ }
+
+ /* Try to checkout the parent collection.
+ * Note that auto-versioning can only be applied to a version selector,
+ * so no separate working resource will be created.
+ */
+ if ((err = (*vsn_hooks->checkout)(parent, 1 /*auto_checkout*/,
+ 0, 0, 0, NULL, NULL))
+ != NULL)
+ {
+ err = dav_push_error(r->pool, HTTP_CONFLICT, 0,
+ apr_psprintf(r->pool,
+ "Unable to auto-checkout parent collection. "
+ "Cannot create resource %s.",
+ ap_escape_html(r->pool, resource->uri)),
+ err);
+ goto done;
+ }
+
+ /* remember that parent was checked out */
+ av_info->parent_checkedout = 1;
+ }
+ }
+
+ /* if only checking parent, we're done */
+ if (parent_only)
+ goto done;
+
+ /* if creating a new resource, see if it should be version-controlled */
+ if (!resource->exists
+ && (*vsn_hooks->auto_versionable)(resource) == DAV_AUTO_VERSION_ALWAYS) {
+
+ if ((err = (*vsn_hooks->vsn_control)(resource, NULL)) != NULL) {
+ err = dav_push_error(r->pool, HTTP_CONFLICT, 0,
+ apr_psprintf(r->pool,
+ "Unable to create versioned resource %s.",
+ ap_escape_html(r->pool, resource->uri)),
+ err);
+ goto done;
+ }
+
+ /* remember that resource was created */
+ av_info->resource_versioned = 1;
+ }
+
+ /* if resource is versioned, make sure it is checked out */
+ if (resource->versioned && !resource->working) {
+ int checkout_resource;
+
+ if ((err = dav_can_auto_checkout(r, resource,
+ (*vsn_hooks->auto_versionable)(resource),
+ &lockdb, &checkout_resource)) != NULL) {
+ goto done;
+ }
+
+ if (!checkout_resource) {
+ err = dav_new_error(r->pool, HTTP_CONFLICT, 0, 0,
+ "<DAV:cannot-modify-version-controlled-content>");
+ goto done;
+ }
+
+ /* Auto-versioning can only be applied to version selectors, so
+ * no separate working resource will be created. */
+ if ((err = (*vsn_hooks->checkout)(resource, 1 /*auto_checkout*/,
+ 0, 0, 0, NULL, NULL))
+ != NULL)
+ {
+ err = dav_push_error(r->pool, HTTP_CONFLICT, 0,
+ apr_psprintf(r->pool,
+ "Unable to checkout resource %s.",
+ ap_escape_html(r->pool, resource->uri)),
+ err);
+ goto done;
+ }
+
+ /* remember that resource was checked out */
+ av_info->resource_checkedout = 1;
+ }
+
+done:
+
+ /* make sure lock database is closed */
+ if (lockdb != NULL)
+ (*lockdb->hooks->close_lockdb)(lockdb);
+
+ /* if an error occurred, undo any auto-versioning operations already done */
+ if (err != NULL) {
+ dav_auto_checkin(r, resource, 1 /*undo*/, 0 /*unlock*/, av_info);
+ return err;
+ }
+
+ return NULL;
+}
+
+/* see mod_dav.h for docco */
+DAV_DECLARE(dav_error *) dav_auto_checkin(
+ request_rec *r,
+ dav_resource *resource,
+ int undo,
+ int unlock,
+ dav_auto_version_info *av_info)
+{
+ const dav_hooks_vsn *vsn_hooks = DAV_GET_HOOKS_VSN(r);
+ dav_error *err = NULL;
+ dav_auto_version auto_version;
+
+ /* If no versioning provider, this is a no-op */
+ if (vsn_hooks == NULL)
+ return NULL;
+
+ /* If undoing auto-checkouts, then do uncheckouts */
+ if (undo) {
+ if (resource != NULL) {
+ if (av_info->resource_checkedout) {
+ if ((err = (*vsn_hooks->uncheckout)(resource)) != NULL) {
+ return dav_push_error(r->pool, HTTP_INTERNAL_SERVER_ERROR, 0,
+ apr_psprintf(r->pool,
+ "Unable to undo auto-checkout "
+ "of resource %s.",
+ ap_escape_html(r->pool, resource->uri)),
+ err);
+ }
+ }
+
+ if (av_info->resource_versioned) {
+ dav_response *response;
+
+ /* ### should we do anything with the response? */
+ if ((err = (*resource->hooks->remove_resource)(resource,
+ &response)) != NULL) {
+ return dav_push_error(r->pool, HTTP_INTERNAL_SERVER_ERROR, 0,
+ apr_psprintf(r->pool,
+ "Unable to undo auto-version-control "
+ "of resource %s.",
+ ap_escape_html(r->pool, resource->uri)),
+ err);
+ }
+ }
+ }
+
+ if (av_info->parent_resource != NULL && av_info->parent_checkedout) {
+ if ((err = (*vsn_hooks->uncheckout)(av_info->parent_resource)) != NULL) {
+ return dav_push_error(r->pool, HTTP_INTERNAL_SERVER_ERROR, 0,
+ apr_psprintf(r->pool,
+ "Unable to undo auto-checkout "
+ "of parent collection %s.",
+ ap_escape_html(r->pool, av_info->parent_resource->uri)),
+ err);
+ }
+ }
+
+ return NULL;
+ }
+
+ /* If the resource was checked out, and auto-checkin is enabled,
+ * then check it in.
+ */
+ if (resource != NULL && resource->working
+ && (unlock || av_info->resource_checkedout)) {
+
+ auto_version = (*vsn_hooks->auto_versionable)(resource);
+
+ if (auto_version == DAV_AUTO_VERSION_ALWAYS ||
+ (unlock && (auto_version == DAV_AUTO_VERSION_LOCKED))) {
+
+ if ((err = (*vsn_hooks->checkin)(resource,
+ 0 /*keep_checked_out*/, NULL))
+ != NULL) {
+ return dav_push_error(r->pool, HTTP_INTERNAL_SERVER_ERROR, 0,
+ apr_psprintf(r->pool,
+ "Unable to auto-checkin resource %s.",
+ ap_escape_html(r->pool, resource->uri)),
+ err);
+ }
+ }
+ }
+
+ /* If parent resource was checked out, and auto-checkin is enabled,
+ * then check it in.
+ */
+ if (!unlock
+ && av_info->parent_checkedout
+ && av_info->parent_resource != NULL
+ && av_info->parent_resource->working) {
+
+ auto_version = (*vsn_hooks->auto_versionable)(av_info->parent_resource);
+
+ if (auto_version == DAV_AUTO_VERSION_ALWAYS) {
+ if ((err = (*vsn_hooks->checkin)(av_info->parent_resource,
+ 0 /*keep_checked_out*/, NULL))
+ != NULL) {
+ return dav_push_error(r->pool, HTTP_INTERNAL_SERVER_ERROR, 0,
+ apr_psprintf(r->pool,
+ "Unable to auto-checkin parent collection %s.",
+ ap_escape_html(r->pool, av_info->parent_resource->uri)),
+ err);
+ }
+ }
+ }
+
+ return NULL;
+}
diff --git a/modules/dav/main/util_lock.c b/modules/dav/main/util_lock.c
new file mode 100644
index 0000000..1b3a647
--- /dev/null
+++ b/modules/dav/main/util_lock.c
@@ -0,0 +1,798 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/*
+** DAV repository-independent lock functions
+*/
+
+#include "apr.h"
+#include "apr_strings.h"
+
+#include "mod_dav.h"
+#include "http_log.h"
+#include "http_config.h"
+#include "http_protocol.h"
+#include "http_core.h"
+
+APLOG_USE_MODULE(dav);
+
+/* ---------------------------------------------------------------
+**
+** Property-related lock functions
+**
+*/
+
+/*
+** dav_lock_get_activelock: Returns a <lockdiscovery> containing
+** an activelock element for every item in the lock_discovery tree
+*/
+DAV_DECLARE(const char *) dav_lock_get_activelock(request_rec *r,
+ dav_lock *lock,
+ dav_buffer *pbuf)
+{
+ dav_lock *lock_scan;
+ const dav_hooks_locks *hooks = DAV_GET_HOOKS_LOCKS(r);
+ int count = 0;
+ dav_buffer work_buf = { 0 };
+ apr_pool_t *p = r->pool;
+
+ /* If no locks or no lock provider, there are no locks */
+ if (lock == NULL || hooks == NULL) {
+ /*
+ ** Since resourcediscovery is defined with (activelock)*,
+ ** <D:activelock/> shouldn't be necessary for an empty lock.
+ */
+ return "";
+ }
+
+ /*
+ ** Note: it could be interesting to sum the lengths of the owners
+ ** and locktokens during this loop. However, the buffer
+ ** mechanism provides some rough padding so that we don't
+ ** really need to have an exact size. Further, constructing
+ ** locktoken strings could be relatively expensive.
+ */
+ for (lock_scan = lock; lock_scan != NULL; lock_scan = lock_scan->next)
+ count++;
+
+ /* if a buffer was not provided, then use an internal buffer */
+ if (pbuf == NULL)
+ pbuf = &work_buf;
+
+ /* reset the length before we start appending stuff */
+ pbuf->cur_len = 0;
+
+ /* prep the buffer with a "good" size */
+ dav_check_bufsize(p, pbuf, count * 300);
+
+ for (; lock != NULL; lock = lock->next) {
+ char tmp[100];
+
+#if DAV_DEBUG
+ if (lock->rectype == DAV_LOCKREC_INDIRECT_PARTIAL) {
+ /* ### crap. design error */
+ dav_buffer_append(p, pbuf,
+ "DESIGN ERROR: attempted to product an "
+ "activelock element from a partial, indirect "
+ "lock record. Creating an XML parsing error "
+ "to ease detection of this situation: <");
+ }
+#endif
+
+ dav_buffer_append(p, pbuf, "<D:activelock>" DEBUG_CR "<D:locktype>");
+ switch (lock->type) {
+ case DAV_LOCKTYPE_WRITE:
+ dav_buffer_append(p, pbuf, "<D:write/>");
+ break;
+ default:
+ /* ### internal error. log something? */
+ break;
+ }
+ dav_buffer_append(p, pbuf, "</D:locktype>" DEBUG_CR "<D:lockscope>");
+ switch (lock->scope) {
+ case DAV_LOCKSCOPE_EXCLUSIVE:
+ dav_buffer_append(p, pbuf, "<D:exclusive/>");
+ break;
+ case DAV_LOCKSCOPE_SHARED:
+ dav_buffer_append(p, pbuf, "<D:shared/>");
+ break;
+ default:
+ /* ### internal error. log something? */
+ break;
+ }
+ dav_buffer_append(p, pbuf, "</D:lockscope>" DEBUG_CR);
+ apr_snprintf(tmp, sizeof(tmp), "<D:depth>%s</D:depth>" DEBUG_CR,
+ lock->depth == DAV_INFINITY ? "infinity" : "0");
+ dav_buffer_append(p, pbuf, tmp);
+
+ if (lock->owner) {
+ /*
+ ** This contains a complete, self-contained <DAV:owner> element,
+ ** with namespace declarations and xml:lang handling. Just drop
+ ** it in.
+ */
+ dav_buffer_append(p, pbuf, lock->owner);
+ }
+
+ dav_buffer_append(p, pbuf, "<D:timeout>");
+ if (lock->timeout == DAV_TIMEOUT_INFINITE) {
+ dav_buffer_append(p, pbuf, "Infinite");
+ }
+ else {
+ time_t now = time(NULL);
+
+ /*
+ ** Check if the timeout is not, for any reason, already elapsed.
+ ** (e.g., because of a large collection, or disk under heavy load...)
+ */
+ if (now >= lock->timeout) {
+ dav_buffer_append(p, pbuf, "Second-0");
+ }
+ else {
+ apr_snprintf(tmp, sizeof(tmp), "Second-%lu", (long unsigned int)(lock->timeout - now));
+ dav_buffer_append(p, pbuf, tmp);
+ }
+ }
+
+ dav_buffer_append(p, pbuf,
+ "</D:timeout>" DEBUG_CR
+ "<D:locktoken>" DEBUG_CR
+ "<D:href>");
+ dav_buffer_append(p, pbuf,
+ (*hooks->format_locktoken)(p, lock->locktoken));
+ dav_buffer_append(p, pbuf,
+ "</D:href>" DEBUG_CR
+ "</D:locktoken>" DEBUG_CR
+ "</D:activelock>" DEBUG_CR);
+ }
+
+ return pbuf->buf;
+}
+
+/*
+** dav_lock_parse_lockinfo: Validates the given xml_doc to contain a
+** lockinfo XML element, then populates a dav_lock structure
+** with its contents.
+*/
+DAV_DECLARE(dav_error *) dav_lock_parse_lockinfo(request_rec *r,
+ const dav_resource *resource,
+ dav_lockdb *lockdb,
+ const apr_xml_doc *doc,
+ dav_lock **lock_request)
+{
+ apr_pool_t *p = r->pool;
+ dav_error *err;
+ apr_xml_elem *child;
+ dav_lock *lock;
+
+ if (!dav_validate_root(doc, "lockinfo")) {
+ return dav_new_error(p, HTTP_BAD_REQUEST, 0, 0,
+ "The request body contains an unexpected "
+ "XML root element.");
+ }
+
+ if ((err = (*lockdb->hooks->create_lock)(lockdb, resource,
+ &lock)) != NULL) {
+ return dav_push_error(p, err->status, 0,
+ "Could not parse the lockinfo due to an "
+ "internal problem creating a lock structure.",
+ err);
+ }
+
+ lock->depth = dav_get_depth(r, DAV_INFINITY);
+ if (lock->depth == -1) {
+ return dav_new_error(p, HTTP_BAD_REQUEST, 0, 0,
+ "An invalid Depth header was specified.");
+ }
+ lock->timeout = dav_get_timeout(r);
+
+ /* Parse elements in the XML body */
+ for (child = doc->root->first_child; child; child = child->next) {
+ if (strcmp(child->name, "locktype") == 0
+ && child->first_child
+ && lock->type == DAV_LOCKTYPE_UNKNOWN) {
+ if (strcmp(child->first_child->name, "write") == 0) {
+ lock->type = DAV_LOCKTYPE_WRITE;
+ continue;
+ }
+ }
+ if (strcmp(child->name, "lockscope") == 0
+ && child->first_child
+ && lock->scope == DAV_LOCKSCOPE_UNKNOWN) {
+ if (strcmp(child->first_child->name, "exclusive") == 0)
+ lock->scope = DAV_LOCKSCOPE_EXCLUSIVE;
+ else if (strcmp(child->first_child->name, "shared") == 0)
+ lock->scope = DAV_LOCKSCOPE_SHARED;
+ if (lock->scope != DAV_LOCKSCOPE_UNKNOWN)
+ continue;
+ }
+
+ if (strcmp(child->name, "owner") == 0 && lock->owner == NULL) {
+ const char *text;
+
+ /* quote all the values in the <DAV:owner> element */
+ apr_xml_quote_elem(p, child);
+
+ /*
+ ** Store a full <DAV:owner> element with namespace definitions
+ ** and an xml:lang definition, if applicable.
+ */
+ apr_xml_to_text(p, child, APR_XML_X2T_FULL_NS_LANG, doc->namespaces,
+ NULL, &text, NULL);
+ lock->owner = text;
+
+ continue;
+ }
+
+ return dav_new_error(p, HTTP_PRECONDITION_FAILED, 0, 0,
+ apr_psprintf(p,
+ "The server cannot satisfy the "
+ "LOCK request due to an unknown XML "
+ "element (\"%s\") within the "
+ "DAV:lockinfo element.",
+ child->name));
+ }
+
+ *lock_request = lock;
+ return NULL;
+}
+
+/* ---------------------------------------------------------------
+**
+** General lock functions
+**
+*/
+
+/* dav_lock_walker: Walker callback function to record indirect locks */
+static dav_error * dav_lock_walker(dav_walk_resource *wres, int calltype)
+{
+ dav_walker_ctx *ctx = wres->walk_ctx;
+ dav_error *err;
+
+ /* We don't want to set indirects on the target */
+ if ((*wres->resource->hooks->is_same_resource)(wres->resource,
+ ctx->w.root))
+ return NULL;
+
+ if ((err = (*ctx->w.lockdb->hooks->append_locks)(ctx->w.lockdb,
+ wres->resource, 1,
+ ctx->lock)) != NULL) {
+ if (ap_is_HTTP_SERVER_ERROR(err->status)) {
+ /* ### add a higher-level description? */
+ return err;
+ }
+
+ /* add to the multistatus response */
+ dav_add_response(wres, err->status, NULL);
+
+ /*
+ ** ### actually, this is probably wrong: we want to fail the whole
+ ** ### LOCK process if something goes bad. maybe the caller should
+ ** ### do a dav_unlock() (e.g. a rollback) if any errors occurred.
+ */
+ }
+
+ return NULL;
+}
+
+/*
+** dav_add_lock: Add a direct lock for resource, and indirect locks for
+** all children, bounded by depth.
+** ### assume request only contains one lock
+*/
+DAV_DECLARE(dav_error *) dav_add_lock(request_rec *r,
+ const dav_resource *resource,
+ dav_lockdb *lockdb, dav_lock *lock,
+ dav_response **response)
+{
+ dav_error *err;
+ int depth = lock->depth;
+
+ *response = NULL;
+
+ /* Requested lock can be:
+ * Depth: 0 for null resource, existing resource, or existing collection
+ * Depth: Inf for existing collection
+ */
+
+ /*
+ ** 2518 9.2 says to ignore depth if target is not a collection (it has
+ ** no internal children); pretend the client gave the correct depth.
+ */
+ if (!resource->collection) {
+ depth = 0;
+ }
+
+ /* In all cases, first add direct entry in lockdb */
+
+ /*
+ ** Append the new (direct) lock to the resource's existing locks.
+ **
+ ** Note: this also handles locknull resources
+ */
+ if ((err = (*lockdb->hooks->append_locks)(lockdb, resource, 0,
+ lock)) != NULL) {
+ /* ### maybe add a higher-level description */
+ return err;
+ }
+
+ if (depth > 0) {
+ /* Walk existing collection and set indirect locks */
+ dav_walker_ctx ctx = { { 0 } };
+ dav_response *multi_status;
+
+ ctx.w.walk_type = DAV_WALKTYPE_NORMAL | DAV_WALKTYPE_AUTH;
+ ctx.w.func = dav_lock_walker;
+ ctx.w.walk_ctx = &ctx;
+ ctx.w.pool = r->pool;
+ ctx.w.root = resource;
+ ctx.w.lockdb = lockdb;
+
+ ctx.r = r;
+ ctx.lock = lock;
+
+ err = (*resource->hooks->walk)(&ctx.w, DAV_INFINITY, &multi_status);
+ if (err != NULL) {
+ /* implies a 5xx status code occurred. screw the multistatus */
+ return err;
+ }
+
+ if (multi_status != NULL) {
+ /* manufacture a 207 error for the multistatus response */
+ *response = multi_status;
+ return dav_new_error(r->pool, HTTP_MULTI_STATUS, 0, 0,
+ "Error(s) occurred on resources during the "
+ "addition of a depth lock.");
+ }
+ }
+
+ return NULL;
+}
+
+/*
+** dav_lock_query: Opens the lock database. Returns a linked list of
+** dav_lock structures for all direct locks on path.
+*/
+DAV_DECLARE(dav_error*) dav_lock_query(dav_lockdb *lockdb,
+ const dav_resource *resource,
+ dav_lock **locks)
+{
+ /* If no lock database, return empty result */
+ if (lockdb == NULL) {
+ *locks = NULL;
+ return NULL;
+ }
+
+ /* ### insert a higher-level description? */
+ return (*lockdb->hooks->get_locks)(lockdb, resource,
+ DAV_GETLOCKS_RESOLVED,
+ locks);
+}
+
+/* dav_unlock_walker: Walker callback function to remove indirect locks */
+static dav_error * dav_unlock_walker(dav_walk_resource *wres, int calltype)
+{
+ dav_walker_ctx *ctx = wres->walk_ctx;
+ dav_error *err;
+
+ /* Before removing the lock, do any auto-checkin required */
+ if (wres->resource->working) {
+ /* ### get rid of this typecast */
+ if ((err = dav_auto_checkin(ctx->r, (dav_resource *) wres->resource,
+ 0 /*undo*/, 1 /*unlock*/, NULL))
+ != NULL) {
+ return err;
+ }
+ }
+
+ if ((err = (*ctx->w.lockdb->hooks->remove_lock)(ctx->w.lockdb,
+ wres->resource,
+ ctx->locktoken)) != NULL) {
+ /* ### should we stop or return a multistatus? looks like STOP */
+ /* ### add a higher-level description? */
+ return err;
+ }
+
+ return NULL;
+}
+
+/*
+** dav_get_direct_resource:
+**
+** Find a lock on the specified resource, then return the resource the
+** lock was applied to (in other words, given a (possibly) indirect lock,
+** return the direct lock's corresponding resource).
+**
+** If the lock is an indirect lock, this usually means traversing up the
+** namespace [repository] hierarchy. Note that some lock providers may be
+** able to return this information with a traversal.
+*/
+static dav_error * dav_get_direct_resource(apr_pool_t *p,
+ dav_lockdb *lockdb,
+ const dav_locktoken *locktoken,
+ const dav_resource *resource,
+ const dav_resource **direct_resource)
+{
+ if (lockdb->hooks->lookup_resource != NULL) {
+ return (*lockdb->hooks->lookup_resource)(lockdb, locktoken,
+ resource, direct_resource);
+ }
+
+ *direct_resource = NULL;
+
+ /* Find the top of this lock-
+ * If r->filename's direct locks include locktoken, use r->filename.
+ * If r->filename's indirect locks include locktoken, retry r->filename/..
+ * Else fail.
+ */
+ while (resource != NULL) {
+ dav_error *err;
+ dav_lock *lock;
+ dav_resource *parent;
+
+ /*
+ ** Find the lock specified by <locktoken> on <resource>. If it is
+ ** an indirect lock, then partial results are okay. We're just
+ ** trying to find the thing and know whether it is a direct or
+ ** an indirect lock.
+ */
+ if ((err = (*lockdb->hooks->find_lock)(lockdb, resource, locktoken,
+ 1, &lock)) != NULL) {
+ /* ### add a higher-level desc? */
+ return err;
+ }
+
+ /* not found! that's an error. */
+ if (lock == NULL) {
+ return dav_new_error(p, HTTP_BAD_REQUEST, 0, 0,
+ "The specified locktoken does not correspond "
+ "to an existing lock on this resource.");
+ }
+
+ if (lock->rectype == DAV_LOCKREC_DIRECT) {
+ /* we found the direct lock. return this resource. */
+
+ *direct_resource = resource;
+ return NULL;
+ }
+
+ /* the lock was indirect. move up a level in the URL namespace */
+ if ((err = (*resource->hooks->get_parent_resource)(resource,
+ &parent)) != NULL) {
+ /* ### add a higher-level desc? */
+ return err;
+ }
+ resource = parent;
+ }
+
+ return dav_new_error(p, HTTP_INTERNAL_SERVER_ERROR, 0, 0,
+ "The lock database is corrupt. A direct lock could "
+ "not be found for the corresponding indirect lock "
+ "on this resource.");
+}
+
+/*
+** dav_unlock: Removes all direct and indirect locks for r->filename,
+** with given locktoken. If locktoken == null_locktoken, all locks
+** are removed. If r->filename represents an indirect lock,
+** we must unlock the appropriate direct lock.
+** Returns OK or appropriate HTTP_* response and logs any errors.
+**
+** ### We've already crawled the tree to ensure everything was locked
+** by us; there should be no need to incorporate a rollback.
+*/
+DAV_DECLARE(int) dav_unlock(request_rec *r, const dav_resource *resource,
+ const dav_locktoken *locktoken)
+{
+ int result;
+ dav_lockdb *lockdb;
+ const dav_resource *lock_resource = resource;
+ const dav_hooks_locks *hooks = DAV_GET_HOOKS_LOCKS(r);
+ const dav_hooks_repository *repos_hooks = resource->hooks;
+ dav_walker_ctx ctx = { { 0 } };
+ dav_response *multi_status;
+ dav_error *err;
+
+ /* If no locks provider, then there is nothing to unlock. */
+ if (hooks == NULL) {
+ return OK;
+ }
+
+ /* 2518 requires the entire lock to be removed if resource/locktoken
+ * point to an indirect lock. We need resource of the _direct_
+ * lock in order to walk down the tree and remove the locks. So,
+ * If locktoken != null_locktoken,
+ * Walk up the resource hierarchy until we see a direct lock.
+ * Or, we could get the direct lock's db/key, pick out the URL
+ * and do a subrequest. I think walking up is faster and will work
+ * all the time.
+ * Else
+ * Just start removing all locks at and below resource.
+ */
+
+ if ((err = (*hooks->open_lockdb)(r, 0, 1, &lockdb)) != NULL) {
+ /* ### return err! maybe add a higher-level desc */
+ /* ### map result to something nice; log an error */
+ return HTTP_INTERNAL_SERVER_ERROR;
+ }
+
+ if (locktoken != NULL
+ && (err = dav_get_direct_resource(r->pool, lockdb,
+ locktoken, resource,
+ &lock_resource)) != NULL) {
+ /* ### add a higher-level desc? */
+ /* ### should return err! */
+ return err->status;
+ }
+
+ /* At this point, lock_resource/locktoken refers to a direct lock (key), ie
+ * the root of a depth > 0 lock, or locktoken is null.
+ */
+ ctx.w.walk_type = DAV_WALKTYPE_NORMAL | DAV_WALKTYPE_LOCKNULL;
+ ctx.w.func = dav_unlock_walker;
+ ctx.w.walk_ctx = &ctx;
+ ctx.w.pool = r->pool;
+ ctx.w.root = lock_resource;
+ ctx.w.lockdb = lockdb;
+
+ ctx.r = r;
+ ctx.locktoken = locktoken;
+
+ err = (*repos_hooks->walk)(&ctx.w, DAV_INFINITY, &multi_status);
+
+ /* ### fix this! */
+ /* ### do something with multi_status */
+ result = err == NULL ? OK : err->status;
+
+ (*hooks->close_lockdb)(lockdb);
+
+ return result;
+}
+
+/* dav_inherit_walker: Walker callback function to inherit locks */
+static dav_error * dav_inherit_walker(dav_walk_resource *wres, int calltype)
+{
+ dav_walker_ctx *ctx = wres->walk_ctx;
+
+ if (ctx->skip_root
+ && (*wres->resource->hooks->is_same_resource)(wres->resource,
+ ctx->w.root)) {
+ return NULL;
+ }
+
+ /* ### maybe add a higher-level desc */
+ return (*ctx->w.lockdb->hooks->append_locks)(ctx->w.lockdb,
+ wres->resource, 1,
+ ctx->lock);
+}
+
+/*
+** dav_inherit_locks: When a resource or collection is added to a collection,
+** locks on the collection should be inherited to the resource/collection.
+** (MOVE, MKCOL, etc) Here we propagate any direct or indirect locks from
+** parent of resource to resource and below.
+*/
+static dav_error * dav_inherit_locks(request_rec *r, dav_lockdb *lockdb,
+ const dav_resource *resource,
+ int use_parent)
+{
+ dav_error *err;
+ const dav_resource *which_resource;
+ dav_lock *locks;
+ dav_lock *scan;
+ dav_lock *prev;
+ dav_walker_ctx ctx = { { 0 } };
+ const dav_hooks_repository *repos_hooks = resource->hooks;
+ dav_response *multi_status;
+
+ if (use_parent) {
+ dav_resource *parent;
+ if ((err = (*repos_hooks->get_parent_resource)(resource,
+ &parent)) != NULL) {
+ /* ### add a higher-level desc? */
+ return err;
+ }
+ if (parent == NULL) {
+ /* ### map result to something nice; log an error */
+ return dav_new_error(r->pool, HTTP_INTERNAL_SERVER_ERROR, 0, 0,
+ "Could not fetch parent resource. Unable to "
+ "inherit locks from the parent and apply "
+ "them to this resource.");
+ }
+ which_resource = parent;
+ }
+ else {
+ which_resource = resource;
+ }
+
+ if ((err = (*lockdb->hooks->get_locks)(lockdb, which_resource,
+ DAV_GETLOCKS_PARTIAL,
+ &locks)) != NULL) {
+ /* ### maybe add a higher-level desc */
+ return err;
+ }
+
+ if (locks == NULL) {
+ /* No locks to propagate, just return */
+ return NULL;
+ }
+
+ /*
+ ** (1) Copy all indirect locks from our parent;
+ ** (2) Create indirect locks for the depth infinity, direct locks
+ ** in our parent.
+ **
+ ** The append_locks call in the walker callback will do the indirect
+ ** conversion, but we need to remove any direct locks that are NOT
+ ** depth "infinity".
+ */
+ for (scan = locks, prev = NULL;
+ scan != NULL;
+ prev = scan, scan = scan->next) {
+
+ if (scan->rectype == DAV_LOCKREC_DIRECT
+ && scan->depth != DAV_INFINITY) {
+
+ if (prev == NULL)
+ locks = scan->next;
+ else
+ prev->next = scan->next;
+ }
+ }
+
+ /* <locks> has all our new locks. Walk down and propagate them. */
+
+ ctx.w.walk_type = DAV_WALKTYPE_NORMAL | DAV_WALKTYPE_LOCKNULL;
+ ctx.w.func = dav_inherit_walker;
+ ctx.w.walk_ctx = &ctx;
+ ctx.w.pool = r->pool;
+ ctx.w.root = resource;
+ ctx.w.lockdb = lockdb;
+
+ ctx.r = r;
+ ctx.lock = locks;
+ ctx.skip_root = !use_parent;
+
+ /* ### do something with multi_status */
+ return (*repos_hooks->walk)(&ctx.w, DAV_INFINITY, &multi_status);
+}
+
+/* ---------------------------------------------------------------
+**
+** Functions dealing with lock-null resources
+**
+*/
+
+/*
+** dav_get_resource_state: Returns the state of the resource
+** r->filename: DAV_RESOURCE_NULL, DAV_RESOURCE_LOCK_NULL,
+** or DAV_RESOURCE_EXIST.
+**
+** Returns DAV_RESOURCE_ERROR if an error occurs.
+*/
+DAV_DECLARE(int) dav_get_resource_state(request_rec *r,
+ const dav_resource *resource)
+{
+ const dav_hooks_locks *hooks = DAV_GET_HOOKS_LOCKS(r);
+
+ if (resource->exists)
+ return DAV_RESOURCE_EXISTS;
+
+ if (hooks != NULL) {
+ dav_error *err;
+ dav_lockdb *lockdb;
+ int locks_present;
+
+ /*
+ ** A locknull resource has the form:
+ **
+ ** known-dir "/" locknull-file
+ **
+ ** It would be nice to look into <resource> to verify this form,
+ ** but it does not have enough information for us. Instead, we
+ ** can look at the path_info. If the form does not match, then
+ ** there is no way we could have a locknull resource -- it must
+ ** be a plain, null resource.
+ **
+ ** Apache sets r->filename to known-dir/unknown-file and r->path_info
+ ** to "" for the "proper" case. If anything is in path_info, then
+ ** it can't be a locknull resource.
+ **
+ ** ### I bet this path_info hack doesn't work for repositories.
+ ** ### Need input from repository implementors! What kind of
+ ** ### restructure do we need? New provider APIs?
+ */
+ if (r->path_info != NULL && *r->path_info != '\0') {
+ return DAV_RESOURCE_NULL;
+ }
+
+ if ((err = (*hooks->open_lockdb)(r, 1, 1, &lockdb)) == NULL) {
+ /* note that we might see some expired locks... *shrug* */
+ err = (*hooks->has_locks)(lockdb, resource, &locks_present);
+ (*hooks->close_lockdb)(lockdb);
+ }
+
+ if (err != NULL) {
+ /* ### don't log an error. return err. add higher-level desc. */
+
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(00623)
+ "Failed to query lock-null status for %s",
+ r->filename);
+
+ return DAV_RESOURCE_ERROR;
+ }
+
+ if (locks_present)
+ return DAV_RESOURCE_LOCK_NULL;
+ }
+
+ return DAV_RESOURCE_NULL;
+}
+
+DAV_DECLARE(dav_error *) dav_notify_created(request_rec *r,
+ dav_lockdb *lockdb,
+ const dav_resource *resource,
+ int resource_state,
+ int depth)
+{
+ dav_error *err;
+
+ if (resource_state == DAV_RESOURCE_LOCK_NULL) {
+
+ /*
+ ** The resource is no longer a locknull resource. This will remove
+ ** the special marker.
+ **
+ ** Note that a locknull resource has already inherited all of the
+ ** locks from the parent. We do not need to call dav_inherit_locks.
+ **
+ ** NOTE: some lock providers record locks for locknull resources using
+ ** a different key than for regular resources. this will shift
+ ** the lock information between the two key types.
+ */
+ (void)(*lockdb->hooks->remove_locknull_state)(lockdb, resource);
+
+ /*
+ ** There are resources under this one, which are new. We must
+ ** propagate the locks down to the new resources.
+ */
+ if (depth > 0 &&
+ (err = dav_inherit_locks(r, lockdb, resource, 0)) != NULL) {
+ /* ### add a higher level desc? */
+ return err;
+ }
+ }
+ else if (resource_state == DAV_RESOURCE_NULL) {
+
+ /* ### should pass depth to dav_inherit_locks so that it can
+ ** ### optimize for the depth==0 case.
+ */
+
+ /* this resource should inherit locks from its parent */
+ if ((err = dav_inherit_locks(r, lockdb, resource, 1)) != NULL) {
+
+ err = dav_push_error(r->pool, err->status, 0,
+ "The resource was created successfully, but "
+ "there was a problem inheriting locks from "
+ "the parent resource.",
+ err);
+ return err;
+ }
+ }
+ /* else the resource already exists and its locks are correct. */
+
+ return NULL;
+}