5272 lines
182 KiB
C
5272 lines
182 KiB
C
/* 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;
|
|
const char *base;
|
|
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->base = DAV_INHERIT_VALUE(parent, child, base);
|
|
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 DAVBasePath directive, which is TAKE1
|
|
*/
|
|
static const char *dav_cmd_davbasepath(cmd_parms *cmd, void *config, const char *arg1)
|
|
{
|
|
dav_dir_conf *conf = config;
|
|
|
|
conf->base = arg1;
|
|
|
|
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, *base;
|
|
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)));
|
|
}
|
|
|
|
/* Take the repos root from DAVBasePath if configured, else the
|
|
* path of the enclosing section. */
|
|
base = conf->base ? conf->base : conf->dir;
|
|
|
|
/* resolve the resource */
|
|
err = (*conf->provider->repos->get_resource)(r, base,
|
|
label, use_checked_in,
|
|
res_p);
|
|
if (err != NULL) {
|
|
/* In the error path, give a hint that DavBasePath needs to be
|
|
* used if the location was configured via a regex match. */
|
|
if (!conf->base) {
|
|
core_dir_config *cdc = ap_get_core_module_config(r->per_dir_config);
|
|
|
|
if (cdc->r) {
|
|
ap_log_error(APLOG_MARK, APLOG_ERR, 0, NULL, APLOGNO(10484)
|
|
"failed to find repository for location configured "
|
|
"via regex match - missing DAVBasePath?");
|
|
}
|
|
}
|
|
|
|
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 | DAV_WALKTYPE_TOLERANT;
|
|
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 */
|
|
AP_INIT_TAKE1("DAVBasePath", dav_cmd_davbasepath, NULL, ACCESS_CONF,
|
|
"specify the DAV repository base URL"),
|
|
|
|
/* 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)
|