diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 15:01:30 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 15:01:30 +0000 |
commit | 6beeb1b708550be0d4a53b272283e17e5e35fe17 (patch) | |
tree | 1ce8673d4aaa948e5554000101f46536a1e4cc29 /modules/dav/main | |
parent | Initial commit. (diff) | |
download | apache2-6beeb1b708550be0d4a53b272283e17e5e35fe17.tar.xz apache2-6beeb1b708550be0d4a53b272283e17e5e35fe17.zip |
Adding upstream version 2.4.57.upstream/2.4.57upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'modules/dav/main')
-rw-r--r-- | modules/dav/main/Makefile.in | 3 | ||||
-rw-r--r-- | modules/dav/main/NWGNUmakefile | 268 | ||||
-rw-r--r-- | modules/dav/main/config5.m4 | 21 | ||||
-rw-r--r-- | modules/dav/main/liveprop.c | 140 | ||||
-rw-r--r-- | modules/dav/main/mod_dav.c | 5237 | ||||
-rw-r--r-- | modules/dav/main/mod_dav.dep | 354 | ||||
-rw-r--r-- | modules/dav/main/mod_dav.dsp | 147 | ||||
-rw-r--r-- | modules/dav/main/mod_dav.h | 2670 | ||||
-rw-r--r-- | modules/dav/main/mod_dav.mak | 406 | ||||
-rw-r--r-- | modules/dav/main/props.c | 1179 | ||||
-rw-r--r-- | modules/dav/main/providers.c | 58 | ||||
-rw-r--r-- | modules/dav/main/std_liveprop.c | 226 | ||||
-rw-r--r-- | modules/dav/main/util.c | 2213 | ||||
-rw-r--r-- | modules/dav/main/util_lock.c | 798 |
14 files changed, 13720 insertions, 0 deletions
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..d16ab4a --- /dev/null +++ b/modules/dav/main/mod_dav.c @@ -0,0 +1,5237 @@ +/* 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; + int allow_lockdiscovery; + +} 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); + newconf->allow_lockdiscovery = DAV_INHERIT_VALUE(parent, child, + allow_lockdiscovery); + + 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; +} + +DAV_DECLARE(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 the DAVLockDiscovery directive, which is FLAG. + */ +static const char *dav_cmd_davlockdiscovery(cmd_parms *cmd, void *config, + int arg) +{ + dav_dir_conf *conf = (dav_dir_conf *)config; + + if (arg) + conf->allow_lockdiscovery = DAV_ENABLED_ON; + else + conf->allow_lockdiscovery = 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: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) +{ + 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); + apr_pool_tag(subpool, "mod_dav-multistatus"); + + 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_pstrcat(r->pool, what, " ", ap_escape_html(r->pool, locn), + " has been created.", NULL); + 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 (ap_cstr_casecmp(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. + */ +DAV_DECLARE(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; +} + +DAV_DECLARE(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); +} + +DAV_DECLARE(void) dav_close_lockdb(dav_lockdb *lockdb) +{ + (lockdb->hooks->close_lockdb)(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; + + range_c = apr_table_get(r->headers_in, "content-range"); + if (range_c == NULL) + return 0; + + range = apr_pstrdup(r->pool, range_c); + if (ap_cstr_casecmpn(range, "bytes ", 6) != 0 + || (dash = ap_strchr(range + 6, '-')) == NULL + || (slash = ap_strchr(range + 6, '/')) == NULL) { + /* malformed header */ + return -1; + } + + *dash++ = *slash++ = '\0'; + + /* detect invalid ranges */ + if (!ap_parse_strict_length(range_start, range + 6)) { + return -1; + } + if (!ap_parse_strict_length(range_end, dash) + || *range_end < *range_start) { + return -1; + } + + if (*slash != '*') { + apr_off_t dummy; + + if (!ap_parse_strict_length(&dummy, slash) + || 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); + + /* check for any method preconditions */ + if (dav_run_method_precondition(r, resource, NULL, NULL, &err) != DECLINED + && err) { + 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); + + /* check for any method preconditions */ + if (dav_run_method_precondition(r, resource, NULL, NULL, &err) != DECLINED + && err) { + 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); + + /* check for any method preconditions */ + if (dav_run_method_precondition(r, resource, NULL, NULL, &err) != DECLINED + && err) { + 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) { + int status = err->status ? err->status : HTTP_FORBIDDEN; + if (status > 299) { + err = dav_push_error(r->pool, status, 0, + apr_psprintf(r->pool, + "Unable to PUT new contents for %s.", + ap_escape_html(r->pool, r->uri)), + err); + } + else { + err = NULL; + } + } + + 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); + + /* check for any method preconditions */ + if (dav_run_method_precondition(r, resource, NULL, NULL, &err) != DECLINED + && err) { + 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_pstrcat(r->pool, + "<D:supported-method D:name=\"", + elts[i].key, + "\"/>" DEBUG_CR, NULL); + 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_pstrcat(r->pool, + "<D:supported-method D:name=\"", + name, "\"/>" DEBUG_CR, NULL); + 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 */ + if ((err = dav_open_lockdb(r, 1, &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, DAV_PROPDB_RO, 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, + apr_text_header *body) +{ + apr_xml_elem *child; + apr_xml_attr *attr; + dav_error *err = NULL; + char *s; + apr_array_header_t *reports; + const dav_report_elem *rp; + + apr_text_append(r->pool, body, "<D:supported-report-set>" DEBUG_CR); + + reports = apr_array_make(r->pool, 5, sizeof(const char *)); + dav_run_gather_reports(r, resource, reports, &err); + if (err != 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 (elem->first_child == NULL) { + int i; + + /* show all supported reports */ + rp = (const dav_report_elem *)reports->elts; + for (i = 0; i < reports->nelts; i++, rp++) { + /* Note: we presume reports->namespace is + * properly XML/URL quoted */ + s = apr_pstrcat(r->pool, + "<D:supported-report D:name=\"", + rp->name, + "\" D:namespace=\"", + rp->nmspace, + "\"/>" DEBUG_CR, NULL); + 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; + int i; + + /* 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:"; + } + + rp = (const dav_report_elem *)reports->elts; + for (i = 0; i < reports->nelts; i++, rp++) { + if (strcmp(name, rp->name) == 0 + && strcmp(nmspace, rp->nmspace) == 0) { + /* Note: we presume reports->nmspace is + * properly XML/URL quoted + */ + s = apr_pstrcat(r->pool, + "<D:supported-report " + "D:name=\"", + rp->name, + "\" D:namespace=\"", + rp->nmspace, + "\"/>" DEBUG_CR, NULL); + 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 */ + + /* check for any method preconditions */ + if (dav_run_method_precondition(r, resource, NULL, doc, &err) != DECLINED + && err) { + return dav_handle_err(r, err, NULL); + } + + 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, &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_dir_conf *conf; + int flags = DAV_PROPDB_RO; + dav_error *err; + dav_propdb *propdb; + dav_get_props_result propstats = { 0 }; + + /* check for any method preconditions */ + if (dav_run_method_precondition(ctx->r, NULL, wres->resource, ctx->doc, &err) != DECLINED + && err) { + apr_pool_clear(ctx->scratchpool); + return NULL; + } + + conf = ap_get_module_config(ctx->r->per_dir_config, &dav_module); + if (conf && conf->allow_lockdiscovery == DAV_ENABLED_OFF) + flags |= DAV_PROPDB_DISABLE_LOCKDISCOVERY; + + /* + ** 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_popen_propdb(ctx->scratchpool, + ctx->r, ctx->w.lockdb, wres->resource, flags, + 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_stream_response(wres, 0, &propstats, ctx->scratchpool); + + dav_close_propdb(propdb); + + /* 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 ((result = ap_xml_parse_input(r, &doc)) != OK) { + return result; + } + /* note: doc == NULL if no request body */ + + /* check for any method preconditions */ + if (dav_run_method_precondition(r, resource, NULL, doc, &err) != DECLINED + && err) { + 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 (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); + apr_pool_tag(ctx.scratchpool, "mod_dav-scratch"); + + if ((err = dav_open_lockdb(r, 1, &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 ((result = ap_xml_parse_input(r, &doc)) != OK) { + return result; + } + /* note: doc == NULL if no request body */ + + /* check for any method preconditions */ + if (dav_run_method_precondition(r, resource, NULL, doc, &err) != DECLINED + && err) { + return dav_handle_err(r, err, NULL); + } + + if (!resource->exists) { + /* Apache will supply a default error for this. */ + return HTTP_NOT_FOUND; + } + + 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, + DAV_PROPDB_NONE, 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 (ap_cstr_casecmp(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) { + if (!ap_parse_strict_length(&r->remaining, lenp)) { + r->remaining = 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; + } + } + + 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); + + /* check for any method preconditions */ + if (dav_run_method_precondition(r, resource, NULL, NULL, &err) != DECLINED + && err) { + 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); + + /* check for any method preconditions */ + if (dav_run_method_precondition(r, resource, NULL, NULL, &err) != DECLINED + && err) { + 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_pstrcat(r->pool, "http://", nscp_host, nscp_path, NULL); + } + 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); + + /* check for any method preconditions */ + if (dav_run_method_precondition(r, resource, resnew, NULL, &err) != DECLINED + && err) { + 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 for any method preconditions */ + if (dav_run_method_precondition(r, resource, NULL, doc, &err) != DECLINED + && err) { + 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, <l)) != 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); + + /* check for any method preconditions */ + if (dav_run_method_precondition(r, resource, NULL, NULL, &err) != DECLINED + && err) { + 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 infinite lock. Thus, an UNLOCK + * on any part of an infinite 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); + + /* 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 */ + + /* check for any method preconditions */ + if (dav_run_method_precondition(r, resource, NULL, doc, &err) != DECLINED + && err) { + return dav_handle_err(r, err, NULL); + } + + /* remember the pre-creation resource state */ + resource_state = dav_get_resource_state(r, resource); + + 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); + + /* check for any method preconditions */ + if (dav_run_method_precondition(r, resource, NULL, doc, &err) != DECLINED + && err) { + 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); + + /* check for any method preconditions */ + if (dav_run_method_precondition(r, resource, NULL, NULL, &err) != DECLINED + && err) { + 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); + + /* check for any method preconditions */ + if (dav_run_method_precondition(r, resource, NULL, doc, &err) != DECLINED + && err) { + 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); + + /* check for any method preconditions */ + if (dav_run_method_precondition(r, resource, NULL, doc, &err) != DECLINED + && err) { + 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; + + /* original request */ + request_rec *r; + + /* 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 for any method preconditions */ + if (dav_run_method_precondition(ctx->r, NULL, wres->resource, NULL, &err) != DECLINED + && err) { + /* precondition failed, dropping through */ + } + + /* 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) + */ + else 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; + + /* parse the request body */ + if ((result = ap_xml_parse_input(r, &doc)) != OK) { + return result; + } + + /* 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); + + /* check for any method preconditions */ + if (dav_run_method_precondition(r, resource, NULL, doc, &err) != DECLINED + && err) { + 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; + } + + 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.r = r; + 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_core_deliver_report(request_rec *r, + const dav_resource *resource, + const apr_xml_doc *doc, + ap_filter_t *output, dav_error **err) +{ + const dav_hooks_vsn *vsn_hooks = DAV_GET_HOOKS_VSN(r); + + if (vsn_hooks) { + *err = (*vsn_hooks->deliver_report)(r, resource, doc, + r->output_filters); + return OK; + } + + return DECLINED; +} + +static void dav_core_gather_reports( + request_rec *r, + const dav_resource *resource, + apr_array_header_t *reports, + dav_error **err) +{ + const dav_hooks_vsn *vsn_hooks = DAV_GET_HOOKS_VSN(r); + + if (vsn_hooks) { + const dav_report_elem *rp; + + (*err) = (*vsn_hooks->avail_reports)(resource, &rp); + while (rp && rp->name) { + + dav_report_elem *report = apr_array_push(reports); + + report->nmspace = rp->nmspace; + report->name = rp->name; + + rp++; + } + } + +} + +static int dav_method_report(request_rec *r) +{ + dav_resource *resource; + const dav_hooks_vsn *vsn_hooks = DAV_GET_HOOKS_VSN(r); + apr_xml_doc *doc; + dav_error *err = NULL; + + int result; + int label_allowed; + + 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 ? (*vsn_hooks->report_label_header_allowed)(doc) : 0; + err = dav_get_resource(r, label_allowed, 0 /* use_checked_in */, + &resource); + if (err != NULL) { + return dav_handle_err(r, err, NULL); + } + + /* check for any method preconditions */ + if (dav_run_method_precondition(r, resource, NULL, doc, &err) != DECLINED + && err) { + 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); + err = NULL; + + /* run report hook */ + result = dav_run_deliver_report(r, resource, doc, + r->output_filters, &err); + if (err != 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; + } + switch (result) { + case OK: + return DONE; + case DECLINED: + /* No one handled the report */ + return HTTP_NOT_IMPLEMENTED; + default: + 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; + } + + /* check for any method preconditions */ + if (dav_run_method_precondition(r, resource, NULL, doc, &err) != DECLINED + && err) { + return dav_handle_err(r, err, NULL); + } + + 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); + + /* check for any method preconditions */ + if (dav_run_method_precondition(r, resource, NULL, NULL, &err) != DECLINED + && err) { + 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); + + /* check for any method preconditions */ + if (dav_run_method_precondition(r, source_resource, NULL, doc, &err) != DECLINED + && err) { + 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); + + /* check for any method preconditions */ + if (dav_run_method_precondition(r, source_resource, resource, doc, &err) != DECLINED + && err) { + 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); + + /* check for any method preconditions */ + if (dav_run_method_precondition(r, resource, NULL, NULL, &err) != DECLINED + && err) { + 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); + + /* check for any method preconditions */ + if (dav_run_method_precondition(r, resource, binding, NULL, &err) != DECLINED + && err) { + 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_hook_deliver_report(dav_core_deliver_report, + NULL, NULL, APR_HOOK_LAST); + dav_hook_gather_reports(dav_core_gather_reports, + NULL, NULL, APR_HOOK_LAST); + + 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"), + + /* per directory/location, or per server */ + AP_INIT_FLAG("DAVLockDiscovery", dav_cmd_davlockdiscovery, NULL, + ACCESS_CONF|RSRC_CONF, + "allow lock discovery by 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_HOOK_LINK(deliver_report) + APR_HOOK_LINK(gather_reports) + APR_HOOK_LINK(method_precondition) + ) + +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)) + +APR_IMPLEMENT_EXTERNAL_HOOK_RUN_FIRST(dav, DAV, int, deliver_report, + (request_rec *r, + const dav_resource *resource, + const apr_xml_doc *doc, + ap_filter_t *output, dav_error **err), + (r, resource, doc, output, err), DECLINED) + +APR_IMPLEMENT_EXTERNAL_HOOK_VOID(dav, DAV, gather_reports, + (request_rec *r, const dav_resource *resource, + apr_array_header_t *reports, dav_error **err), + (r, resource, reports, err)) + +APR_IMPLEMENT_EXTERNAL_HOOK_RUN_FIRST(dav, DAV, int, method_precondition, + (request_rec *r, + dav_resource *src, const dav_resource *dest, + const apr_xml_doc *doc, + dav_error **err), + (r, src, dest, doc, err), DECLINED) 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..eca34a2 --- /dev/null +++ b/modules/dav/main/mod_dav.h @@ -0,0 +1,2670 @@ +/* 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 \"-//W3C//DTD HTML 4.01//EN\" \"http://www.w3.org/TR/html4/strict.dtd\">\n<html>\n<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; + +DAV_DECLARE(dav_error *) dav_get_resource(request_rec *r, int label_allowed, + int use_checked_in, dav_resource **res_p); + + +/* -------------------------------------------------------------------- +** +** 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(int) dav_validate_root_ns(const apr_xml_doc *doc, + int ns, const char *tagname); +DAV_DECLARE(apr_xml_elem *) dav_find_child(const apr_xml_elem *elem, + const char *tagname); +DAV_DECLARE(apr_xml_elem *) dav_find_child_ns(const apr_xml_elem *elem, + int ns, const char *tagname); +DAV_DECLARE(apr_xml_elem *) dav_find_next_ns(const apr_xml_elem *elem, + int ns, const char *tagname); + +/* find and return the attribute with a name in the given namespace */ +DAV_DECLARE(apr_xml_attr *) dav_find_attr_ns(const apr_xml_elem *elem, + int ns, const char *attrname); + +/* find and return the attribute with a given DAV: tagname */ +DAV_DECLARE(apr_xml_attr *) dav_find_attr(const apr_xml_elem *elem, + const char *attrname); + +/* 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 and report extensions are not part of the dav_provider +** structure because they are handled through the APR_HOOK interface (to +** allow for multiple 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)) + +/* +** deliver_report: given a parsed report request, process the request +** an deliver the resulting report. +** +** The hook implementer should decide whether it should handle the given +** report, and if so, write the response to the output filter. If the +** report is not relevant, return DECLINED. +*/ +APR_DECLARE_EXTERNAL_HOOK(dav, DAV, int, deliver_report, + (request_rec *r, + const dav_resource *resource, + const apr_xml_doc *doc, + ap_filter_t *output, dav_error **err)) + +/* +** gather_reports: get all reports. +** +** The hook implementor should push one or more dav_report_elem structures +** containing report names into the specified array. These names are returned +** in the DAV:supported-reports-set property to let clients know +** what reports are supported by the installation. +** +*/ +APR_DECLARE_EXTERNAL_HOOK(dav, DAV, void, gather_reports, + (request_rec *r, const dav_resource *resource, + apr_array_header_t *reports, dav_error **err)) + +/* + ** method_precondition: check method preconditions. + ** + ** If a WebDAV extension needs to set any preconditions on a method, this + ** hook is where to do it. If the precondition fails, return an error + ** response with the tagname set to the value of the failed precondition. + ** + ** If the method requires an XML body, this will be read and provided as + ** the doc value. If not, doc is NULL. An extension that needs to verify + ** the non-XML body of a request should register an input filter to do so + ** within this hook. + ** + ** Methods like PUT will supply a single src resource, and the dst will + ** be NULL. + ** + ** Methods like COPY or MOVE will trigger this hook twice. The first + ** invocation will supply just the source resource. The second invocation + ** will supply a source and destination. This allows preconditions on the + ** source resource to be verified before making an attempt to get the + ** destination resource. + ** + ** Methods like PROPFIND and LABEL will trigger this hook initially for + ** the src resource, and then subsequently for each resource that has + ** been walked during processing, with the walked resource passed in dst, + ** and NULL passed in src. + ** + ** As a rule, the src resource originates from a request that has passed + ** through httpd's authn/authz hooks, while the dst resource has not. + */ +APR_DECLARE_EXTERNAL_HOOK(dav, DAV, int, method_precondition, + (request_rec *r, + dav_resource *src, const dav_resource *dst, + const apr_xml_doc *doc, dav_error **err)) + + +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); +DAV_DECLARE(const dav_provider *) dav_get_provider(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. + ** + ** Some DAV extensions, like CalDAV, specify both document elements + ** and property elements that need to be taken into account when + ** generating a property. The document element and property element + ** are made available in the dav_liveprop_elem structure under the + ** resource, accessible as follows: + ** + ** dav_get_liveprop_element(resource); + ** + ** 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); + +typedef struct { + const apr_xml_doc *doc; + const apr_xml_elem *elem; +} dav_liveprop_elem; + +/* + ** When calling insert_prop(), the associated request element and + ** document is accessible using the following call. + */ +DAV_DECLARE(dav_liveprop_elem *) dav_get_liveprop_element(const dav_resource + *resource); + +/* +** 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_open_lockdb(request_rec *r, + int ro, + dav_lockdb **lockdb); +DAV_DECLARE(void) dav_close_lockdb(dav_lockdb *lockdb); +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); +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; + +#define DAV_PROPDB_NONE 0 +#define DAV_PROPDB_RO 1 +#define DAV_PROPDB_DISABLE_LOCKDISCOVERY 2 + +DAV_DECLARE(dav_error *) dav_open_propdb( + request_rec *r, + dav_lockdb *lockdb, + const dav_resource *resource, + int flags, + apr_array_header_t *ns_xlate, + dav_propdb **propdb); + +DAV_DECLARE(dav_error *) dav_popen_propdb( + apr_pool_t *p, + request_rec *r, + dav_lockdb *lockdb, + const dav_resource *resource, + int flags, + 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..c320f8a --- /dev/null +++ b/modules/dav/main/props.c @@ -0,0 +1,1179 @@ +/* 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 */ + +#define DAV_PROP_ELEMENT "mod_dav-element" + +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 */ + + int flags; /* ro, disable lock discovery */ + + /* 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->p, + 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->flags & DAV_PROPDB_DISABLE_LOCKDISCOVERY) { + value = ""; + break; + } + + 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_pstrcat(propdb->p, + "<D:supported-live-property D:name=\"", + name, "\"/>" DEBUG_CR, NULL); + } + else if (what == DAV_PROP_INSERT_VALUE && *value != '\0') { + /* use D: prefix to refer to the DAV: namespace URI */ + s = apr_pstrcat(propdb->p, "<D:", name, ">", value, "</D:", name, + ">" DEBUG_CR, NULL); + } + else { + /* use D: prefix to refer to the DAV: namespace URI */ + s = apr_pstrcat(propdb->p, "<D:", name, "/>" DEBUG_CR, NULL); + } + 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_pstrcat(pool, "<", name->name, "/>" DEBUG_CR, NULL); + else { + const char *prefix = dav_xmlns_add_uri(xi, name->ns); + + s = apr_pstrcat(pool, "<", prefix, ":", name->name, "/>" DEBUG_CR, NULL); + } + + 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 flags, + apr_array_header_t * ns_xlate, + dav_propdb **p_propdb) +{ + return dav_popen_propdb(r->pool, r, lockdb, resource, + flags, ns_xlate, p_propdb); +} + +DAV_DECLARE(dav_error *)dav_popen_propdb(apr_pool_t *p, + request_rec *r, dav_lockdb *lockdb, + const dav_resource *resource, + int flags, + apr_array_header_t * ns_xlate, + dav_propdb **p_propdb) +{ + dav_propdb *propdb = NULL; + + propdb = apr_pcalloc(p, sizeof(*propdb)); + propdb->p = p; + + *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; + propdb->resource = resource; + propdb->ns_xlate = ns_xlate; + + propdb->db_hooks = DAV_GET_HOOKS_PROPDB(r); + + propdb->lockdb = lockdb; + + propdb->flags = flags; + + /* 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); + } + + if (propdb->subreq) { + ap_destroy_sub_req(propdb->subreq); + propdb->subreq = NULL; + } +} + +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 }; + dav_liveprop_elem *element; + char *marks_liveprop; + dav_xmlns_info *xi; + int xi_filled = 0; + + /* we lose both the document and the element when calling (insert_prop), + * make these available in the pool. + */ + element = dav_get_liveprop_element(propdb->resource); + if (!element) { + element = apr_pcalloc(propdb->resource->pool, sizeof(dav_liveprop_elem)); + apr_pool_userdata_setn(element, DAV_PROP_ELEMENT, NULL, propdb->resource->pool); + } + else { + memset(element, 0, sizeof(dav_liveprop_elem)); + } + element->doc = doc; + + /* ### 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; + + element->elem = elem; + + /* + ** 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 outlives propdb->p. Hence use the request pool */ + elem->priv = apr_pcalloc(propdb->r->pool, 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(dav_liveprop_elem *) dav_get_liveprop_element(const dav_resource *resource) +{ + dav_liveprop_elem *element; + + apr_pool_userdata_get((void **)&element, DAV_PROP_ELEMENT, resource->pool); + + return element; +} + +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. + ** + ** http://tools.ietf.org/html/rfc4918#section-14.23 says + ** "Specifying the removal of a property that does not exist is + ** not an error" + */ + /* ### 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..cb46b65 --- /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_pstrcat(p, + "<D:supported-live-property D:name=\"", info->name, + "\" D:namespace=\"", dav_core_namespace_uris[info->ns], + "\"/>" DEBUG_CR, NULL); + } + 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..3f7822f --- /dev/null +++ b/modules/dav/main/util.c @@ -0,0 +1,2213 @@ +/* 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; +} + +/* ### Unclear if this was designed to be used with an uninitialized + * dav_buffer struct, but is used on by dav_lock_get_activelock(). + * Hence check for pbuf->buf. */ +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); + if (pbuf->buf) + 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 (ap_cstr_casecmp(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_ns(const apr_xml_doc *doc, + int ns, const char *tagname) +{ + return doc->root && + doc->root->ns == ns && + strcmp(doc->root->name, tagname) == 0; +} + +/* 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 dav_validate_root_ns(doc, APR_XML_NS_DAV_ID, tagname); +} + +/* find and return the next child with a tagname in the given namespace */ +DAV_DECLARE(apr_xml_elem *) dav_find_next_ns(const apr_xml_elem *elem, + int ns, const char *tagname) +{ + apr_xml_elem *child = elem->next; + + for (; child; child = child->next) + if (child->ns == ns && !strcmp(child->name, tagname)) + return child; + return NULL; +} + +/* find and return the (unique) child with a tagname in the given namespace */ +DAV_DECLARE(apr_xml_elem *) dav_find_child_ns(const apr_xml_elem *elem, + int ns, const char *tagname) +{ + apr_xml_elem *child = elem->first_child; + + for (; child; child = child->next) + if (child->ns == ns && !strcmp(child->name, tagname)) + return child; + return NULL; +} + +/* 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) +{ + return dav_find_child_ns(elem, APR_XML_NS_DAV_ID, tagname); +} + +/* find and return the attribute with a name in the given namespace */ +DAV_DECLARE(apr_xml_attr *) dav_find_attr_ns(const apr_xml_elem *elem, + int ns, const char *attrname) +{ + apr_xml_attr *attr = elem->attr; + + for (; attr; attr = attr->next) + if (attr->ns == ns && !strcmp(attr->name, attrname)) + return attr; + return NULL; +} + +/* find and return the attribute with a given DAV: tagname */ +DAV_DECLARE(apr_xml_attr *) dav_find_attr(const apr_xml_elem *elem, + const char *attrname) +{ + return dav_find_attr_ns(elem, APR_XML_NS_DAV_ID, attrname); +} + +/* 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_pstrcat(xi->pool, " xmlns:", (const char *)prefix, "=\"", + (const char *)uri, "\"", NULL); + 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 */ + if (!ap_normalize_path(parsed_uri.path, + AP_NORMALIZE_NOT_ABOVE_ROOT | + AP_NORMALIZE_DECODE_UNRESERVED)) { + return dav_new_error(r->pool, HTTP_BAD_REQUEST, + DAV_ERR_IF_TAGGED, rv, + "Invalid URI path tagged If-header."); + } + + /* 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; + } + else { + return dav_new_error(r->pool, HTTP_BAD_REQUEST, + DAV_ERR_IF_UNK_CHAR, 0, + "Invalid \"If:\" header: " + "Unexpected character in 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').", + *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; +} |